diff --git a/archive/1.0.2/jquery.jsPlumb-1.0.2-min.js b/archive/1.0.2/jquery.jsPlumb-1.0.2-min.js new file mode 100644 index 000000000..0c410e880 --- /dev/null +++ b/archive/1.0.2/jquery.jsPlumb-1.0.2-min.js @@ -0,0 +1,24 @@ +if(!Array.prototype.indexOf)Array.prototype.indexOf=function(r,n,w){n=+n||0;for(var p=this.length;ng)g=f;if(c<0){m+=c; +c=Math.abs(c);g+=c;t[0]+=c;l+=c;q+=c;b[0]+=c}c=Math.min(Math.min(s,d),Math.min(t[1],b[1]));f=Math.max(Math.max(s,d),Math.max(t[1],b[1]));if(f>i)i=f;if(c<0){j+=c;c=Math.abs(c);i+=c;t[1]+=c;s+=c;d+=c;b[1]+=c}return[m,j,g,i,l,s,q,d,t[0],t[1],b[0],b[1]]};this.paint=function(b,c){c.beginPath();c.moveTo(b[4],b[5]);c.bezierCurveTo(b[8],b[9],b[10],b[11],b[6],b[7]);c.stroke()}}},Endpoints:{Dot:function(a){a=a||{radius:10};var e=this;this.radius=a.radius;var b=0.5*this.radius,c=this.radius/3,f=function(d){try{return parseInt(d)}catch(g){if(d.substring(d.length- +1)=="%")return parseInt(d.substring(0,d-1))}},h=function(d){var g=b,i=c;if(d.offset)g=f(d.offset);if(d.innerRadius)i=f(d.innerRadius);return[g,i]};this.paint=function(d,g,i,m,j){var l=m.radius||e.radius;k.sizeCanvas(i,d[0]-l,d[1]-l,l*2,l*2);d=i.getContext("2d");i={};z(i,m);if(i.fillStyle==null)i.fillStyle=j.strokeStyle;z(d,i);j=/MSIE/.test(navigator.userAgent)&&!window.opera;if(m.gradient&&!j){j=h(m.gradient);g=d.createRadialGradient(l,l,l,l+(g[0]==1?j[0]*-1:j[0]),l+(g[1]==1?j[0]*-1:j[0]),j[1]);for(j= +0;j=4)b.orientation=[arguments[2],arguments[3]];if(arguments.length==6)b.offsets=[arguments[4],arguments[5]]}return new u(b)},repaint:function(a){var e=function(f,h){for(var d=o[h],g={absolutePosition:f.offset()},i=0;i0)B(a,"none"==e[0].canvas.style.display?"block":"none")}, +unload:function(){delete o;delete x;delete y}},E=function(a){var e=this;this.source=typeof a.source=="string"?$("#"+a.source):a.source;this.target=typeof a.target=="string"?$("#"+a.target):a.target;this.sourceId=$(this.source).attr("id");this.targetId=$(this.target).attr("id");this.drawEndpoints=a.drawEndpoints!=null?a.drawEndpoints:true;this.endpointsOnTop=a.endpointsOnTop!=null?a.endpointsOnTop:true;this.anchors=a.anchors||k.DEFAULT_ANCHORS||[k.Anchors.BottomCenter,k.Anchors.TopCenter];this.connector= +a.connector||k.DEFAULT_CONNECTOR||new k.Connectors.Bezier;this.paintStyle=a.paintStyle||k.DEFAULT_PAINT_STYLE;this.endpoints=[];if(!a.endpoints)a.endpoints=[null,null];this.endpoints[0]=a.endpoints[0]||a.endpoint||k.DEFAULT_ENDPOINTS[0]||k.DEFAULT_ENDPOINT||new k.Endpoints.Dot;this.endpoints[1]=a.endpoints[1]||a.endpoint||k.DEFAULT_ENDPOINTS[1]||k.DEFAULT_ENDPOINT||new k.Endpoints.Dot;this.endpointStyles=[];if(!a.endpointStyles)a.endpointStyles=[null,null];this.endpointStyles[0]=a.endpointStyles[0]|| +a.endpointStyle||k.DEFAULT_ENDPOINT_STYLES[0]||k.DEFAULT_ENDPOINT_STYLE;this.endpointStyles[1]=a.endpointStyles[1]||a.endpointStyle||k.DEFAULT_ENDPOINT_STYLES[1]||k.DEFAULT_ENDPOINT_STYLE;x[this.sourceId]=this.source.offset();y[this.sourceId]=[this.source.outerWidth(),this.source.outerHeight()];x[this.targetId]=this.target.offset();y[this.targetId]=[this.target.outerWidth(),this.target.outerHeight()];var b=C(k.connectorClass);this.canvas=b;if(this.drawEndpoints){this.sourceEndpointCanvas=C(k.endpointClass); +this.targetEndpointCanvas=C(k.endpointClass);if(this.endpointsOnTop){$(this.sourceEndpointCanvas).css("zIndex",this.source.css("zIndex")+1);$(this.targetEndpointCanvas).css("zIndex",this.target.css("zIndex")+1)}else{$(this.sourceEndpointCanvas).css("zIndex",this.source.css("zIndex")-1);$(this.targetEndpointCanvas).css("zIndex",this.target.css("zIndex")-1)}}this.paint=function(h,d,g){var i=h!=this.sourceId,m=i?this.sourceId:this.targetId,j=i?0:1,l=i?1:0;if(this.canvas.getContext){if(g){d=$("#"+h); +g=$("#"+m);y[h]=[d.outerWidth(),d.outerHeight()];y[m]=[g.outerWidth(),g.outerHeight()];x[h]=d.offset();x[m]=g.offset()}else{g=d.absolutePosition||d.offset;d=d!=null?g:$("#"+h).offset();x[h]=d}g=x[h];var s=x[m],q=y[h],t=y[m];d=b.getContext("2d");m=this.anchors[l].compute([g.left,g.top],q,[s.left,s.top],t);h=this.anchors[l].orientation;g=this.anchors[j].compute([s.left,s.top],t,[g.left,g.top],q);s=this.anchors[j].orientation;j=this.connector.compute(m,g,this.anchors[l],this.anchors[j],this.paintStyle.lineWidth); +k.sizeCanvas(b,j[0],j[1],j[2],j[3]);z(d,this.paintStyle);l=/MSIE/.test(navigator.userAgent)&&!window.opera;if(this.paintStyle.gradient&&!l){l=i?d.createLinearGradient(j[4],j[5],j[6],j[7]):d.createLinearGradient(j[6],j[7],j[4],j[5]);for(q=0;q w) w = maxx; + if (minx < 0) { + canvasX += minx; var ox = Math.abs(minx); + w += ox; CP[0] += ox; sx += ox; tx +=ox; CP2[0] += ox; + } + + var miny1 = Math.min(sy,ty); var miny2 = Math.min(CP[1], CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(sy,ty); var maxy2 = Math.max(CP[1], CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > h) h = maxy; + if (miny < 0) { + canvasY += miny; var oy = Math.abs(miny); + h += oy; CP[1] += oy; sy += oy; ty +=oy; CP2[1] += oy; + } + + // return [ canvasx, canvasy, canvasWidth, canvasHeight, + // sourceX, sourceY, targetX, targetY, + // controlPoint1_X, controlPoint1_Y, controlPoint2_X, controlPoint2_Y + return [canvasX, canvasY, w, h, sx, sy, tx, ty, CP[0], CP[1], CP2[0], CP2[1] ]; + }; + + this.paint = function(d, ctx) { + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + } + } + }, + + + /** + * Types of endpoint UIs. we supply three - a circle of default radius 10px, a rectangle of + * default size 20x20, and an image (with no default). you can supply others of these if you want to - see the documentation + * for a howto. + */ + Endpoints : { + + /** + * a round endpoint, with default radius 10 pixels. + */ + Dot : function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + } + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = {}; + applyPaintStyle(style, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + applyPaintStyle(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }, + + /** + * A Rectangular endpoint, with default size 20x20. + */ + Rectangle : function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + //todo: the fillStyle needs some thought. we want to support a few options: + // 1. nothing supplied; use the stroke color or the default if no stroke color. + // 2. a fill color supplied - use it + // 3. a gradient supplied - use it + // 4. setting the endpoint to the same color as the bg of the element it is attached to. + var style = {}; + applyPaintStyle(style, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + applyPaintStyle(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }, + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' property in the params object.. + */ + Image : function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + } + }, + + /** + * establishes a connection between two elements. + * @param params object containing setup for the connection. see documentation. + */ + connect : function(params) { + var jpc = new jsPlumbConnection(params); + var key = jpc.sourceId + "_" + jpc.targetId; + connections[key] = jpc; + var addToList = function(elId, jpc) { + var l = connections[elId]; + if (l == null) { + l = []; + connections[elId] = l; + } + l.push(jpc); + }; + + // register this connection. + addToList(jpc.sourceId, jpc); + addToList(jpc.targetId, jpc); + }, + + /** + * Remove one connection to an element. + * @param sourceId id of the first window in the connection + * @param targetId id of the second window in the connection + */ + detach : function(sourceId, targetId) { + var jpcs = connections[sourceId]; + var idx = -1; + for (var i = 0; i < jpcs.length; i++) { + if ((jpcs[i].sourceId == sourceId && jpcs[i].targetId == targetId) || (jpcs[i].targetId == sourceId && jpcs[i].sourceId == targetId)) { + _removeCanvas(jpcs[i].canvas); + if (jpcs[i].drawEndpoints) { + _removeCanvas(jpcs[i].targetEndpointCanvas); + _removeCanvas(jpcs[i].sourceEndpointCanvas); + } + idx = i; + break; + } + } + if (idx != -1) + jpcs.splice(idx, 1); + + // todo - dragging? if no more connections for an object turn off dragging by default, but + // allow an override on it? + }, + + /** + * remove all an element's connections. + */ + detachAll : function(elId) { + var jpcs = connections[elId]; + for (var i = 0; i < jpcs.length; i++) { + _removeCanvas(jpcs[i].canvas); + if (jpcs[i].drawEndpoints) { + _removeCanvas(jpcs[i].targetEndpointCanvas); + _removeCanvas(jpcs[i].sourceEndpointCanvas); + } + } + delete connections[elId]; + connections[elId] = []; + }, + + /** + * remove all connections. + */ + detachEverything : function() { + for (var elId in connections) { + var jpcs = connections[elId]; + if (jpcs.length) { + try { + for (var i = 0; i < jpcs.length; i++) { + + _removeCanvas(jpcs[i].canvas); + if (jpcs[i].drawEndpoints) { + _removeCanvas(jpcs[i].targetEndpointCanvas); + _removeCanvas(jpcs[i].sourceEndpointCanvas); + } + + } + } catch (e) { } + } + } + delete connections; + connections = []; + }, + + getConnections : function(elId) { + return connections[elId]; + }, + + /** + * Set an element's connections to be hidden. + */ + hide : function(elId) { + _setVisible(elId, "none"); + }, + + /** + * Creates an anchor with the given params. + * x - the x location of the anchor as a percentage of the total width. + * y - the y location of the anchor as a percentage of the total height. + * orientation - an [x,y] array indicating the general direction a connection from the anchor should go in. + * offsets - an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. optional. defaults to [0,0]. + */ + makeAnchor : function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed in but that makes the call very verbose. easier to use + // by just passing in four values. but for backwards compatibility if we are given only one value we assume it's a call in the old form. + var params = {}; + if (arguments.length == 1) $.extend(params, x); + else { + params = {x:x, y:y}; + if (arguments.length >= 4) { + params.orientation = [arguments[2], arguments[3]]; + } + if (arguments.length == 6) params.offsets = [arguments[4], arguments[5]]; + } + return new Anchor(params); + }, + + + /** + * repaint element and its connections. element may be an id or the actual jQuery object. + * this method gets new sizes for the elements before painting anything. + */ + repaint : function(el) { + var _repaint = function(el, elId) { + var jpcs = connections[elId]; + var idx = -1; + var loc = {'absolutePosition': el.offset()}; + for (var i = 0; i < jpcs.length; i++) { + jpcs[i].paint(elId, loc, true); + } + }; + + var _processElement = function(el) { + var ele = typeof(el)=='string' ? $("#" + el) : el; + var eleId = ele.attr("id"); + _repaint(ele, eleId); + }; + + // TODO: support a jQuery result object too! + + // support both lists... + if (typeof el =='object') { + for (var i = 0; i < el.length; i++) + _processElement(el[i]); + } // ...and single strings. + else _processElement(el); + }, + + /** + * repaint all connections. + */ + repaintEverything : function() { + for (var elId in connections) { + var jpcs = connections[elId]; + if (jpcs.length) { + try { + for (var i = 0; i < jpcs.length; i++) { + jpcs[i].repaint(); + } + } catch (e) { } + } + } + }, + + /** + * sets/unsets automatic repaint on window resize. + */ + setAutomaticRepaint : function(value) { + automaticRepaint = value; + }, + + /** + * Sets the default size jsPlumb will use for a new canvas (we create a square canvas so + * one value is all that is required). This is a hack for IE, because ExplorerCanvas seems + * to need for a canvas to be larger than what you are going to draw on it at initialisation + * time. The default value of this is 1200 pixels, which is quite large, but if for some + * reason you're drawing connectors that are bigger, you should adjust this value appropriately. + */ + setDefaultNewCanvasSize : function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }, + + /** + * Sets whether or not a given element is draggable, regardless of what any plumb command + * may request. + */ + setDraggable: function(element, draggable) { + _setDraggable(element, draggable); + }, + + /** + * Sets whether or not elements are draggable by default. Default for this is true. + */ + setDraggableByDefault: function(draggable) { + _draggableByDefault = draggable; + }, + + /** + * Sets the function to fire when the window size has changed and a repaint was fired. + */ + setRepaintFunction : function(f) { + repaintFunction = f; + }, + + /** + * Set an element's connections to be visible. + */ + show : function(elId) { + _setVisible(elId, "block"); + }, + + /** + * helper to size a canvas. + */ + sizeCanvas : function(canvas, x, y, w, h) { + canvas.style.height = h + "px"; canvas.height = h; + canvas.style.width = w + "px"; canvas.width = w; + canvas.style.left = x + "px"; canvas.style.top = y + "px"; + }, + + /** + * Toggles visibility of an element's connections. + */ + toggle : function(elId) { + var jpcs = connections[elId]; + if (jpcs.length > 0) + _setVisible(elId, "none" == jpcs[0].canvas.style.display ? "block" : "none"); + }, + + /** + * Unloads jsPlumb, deleting all storage. You should call this + */ + unload : function() { + delete connections; + delete offsets; + delete sizes; + delete draggableStates; + } +}; + +// ************** connection +// **************************************** +/** +* allowed params: +* source: source element (string or a jQuery element) (required) +* target: target element (string or a jQuery element) (required) +* anchors: optional array of anchor placements. defaults to BottomCenter for source +* and TopCenter for target. +*/ +var jsPlumbConnection = function(params) { + +// ************** get the source and target and register the connection. ******************* + var self = this; + // get source and target as jQuery objects + this.source = (typeof params.source == 'string') ? $("#" + params.source) : params.source; + this.target = (typeof params.target == 'string') ? $("#" + params.target) : params.target; + this.sourceId = $(this.source).attr("id"); + this.targetId = $(this.target).attr("id"); + this.drawEndpoints = params.drawEndpoints != null ? params.drawEndpoints : true; + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + + // get anchor + this.anchors = params.anchors || jsPlumb.DEFAULT_ANCHORS || [jsPlumb.Anchors.BottomCenter, jsPlumb.Anchors.TopCenter]; + // make connector + this.connector = params.connector || jsPlumb.DEFAULT_CONNECTOR || new jsPlumb.Connectors.Bezier(); + this.paintStyle = params.paintStyle || jsPlumb.DEFAULT_PAINT_STYLE; + // init endpoints + this.endpoints = []; + if(!params.endpoints) params.endpoints = [null,null]; + this.endpoints[0] = params.endpoints[0] || params.endpoint || jsPlumb.DEFAULT_ENDPOINTS[0] || jsPlumb.DEFAULT_ENDPOINT || new jsPlumb.Endpoints.Dot(); + this.endpoints[1] = params.endpoints[1] || params.endpoint || jsPlumb.DEFAULT_ENDPOINTS[1] ||jsPlumb.DEFAULT_ENDPOINT || new jsPlumb.Endpoints.Dot(); + this.endpointStyles = []; + if (!params.endpointStyles) params.endpointStyles = [null,null]; + this.endpointStyles[0] = params.endpointStyles[0] || params.endpointStyle || jsPlumb.DEFAULT_ENDPOINT_STYLES[0] || jsPlumb.DEFAULT_ENDPOINT_STYLE; + this.endpointStyles[1] = params.endpointStyles[1] || params.endpointStyle || jsPlumb.DEFAULT_ENDPOINT_STYLES[1] || jsPlumb.DEFAULT_ENDPOINT_STYLE; + + offsets[this.sourceId] = this.source.offset(); + sizes[this.sourceId] = [this.source.outerWidth(), this.source.outerHeight()]; + offsets[this.targetId] = this.target.offset(); + sizes[this.targetId] = [this.target.outerWidth(), this.target.outerHeight()]; + +// *************** create canvases on which the connection will be drawn ************ + var canvas = _newCanvas(jsPlumb.connectorClass); + this.canvas = canvas; + // create endpoint canvases + if (this.drawEndpoints) { + this.sourceEndpointCanvas = _newCanvas(jsPlumb.endpointClass); + this.targetEndpointCanvas = _newCanvas(jsPlumb.endpointClass); + // sit them on top of the underlying element? + var setZIndex = function(canvas, source, above) { + var adj = above ? 1 : -1; + var zIndex = $(source).css("zIndex"); + $(canvas).css("zIndex", zIndex != "auto" ? zIndex + adj : "auto"); + }; + + setZIndex(this.sourceEndpointCanvas, this.source, this.endpointsOnTop); + setZIndex(this.targetEndpointCanvas, this.target, this.endpointsOnTop); + } +// ************** store the anchors + + /** + * paints the connection. + * @param elId Id of the element that is in motion + * @param ui jQuery's event system ui object (present if we came from a drag to get here) + * @param recalc whether or not to recalculate element sizes. this is true if a repaint caused this to be painted. + */ + this.paint = function(elId, ui, recalc) { + // if the moving object is not the source we must transpose the two references. + var swap = !(elId == this.sourceId); + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + + if (this.canvas.getContext) { + + if (recalc) { + // get the current sizes of the two elements. + var s = $("#" + elId); + var t = $("#" + tId); + sizes[elId] = [s.outerWidth(), s.outerHeight()]; + sizes[tId] = [t.outerWidth(), t.outerHeight()]; + offsets[elId] = s.offset(); + offsets[tId] = t.offset(); + } else { + // faster to use the ui element if it was passed in. offset is a fallback. + // fix for change in 1.8 (absolutePosition renamed to offset). plugin is compatible with + // 1.8 and 1.7. + var pos = ui.absolutePosition || ui.offset; + var anOffset = ui != null ? pos : $("#" + elId).offset(); + offsets[elId] = anOffset; + } + + var myOffset = offsets[elId]; + var otherOffset = offsets[tId]; + var myWH = sizes[elId]; + var otherWH = sizes[tId]; + + var ctx = canvas.getContext('2d'); + var sAnchorP = this.anchors[sIdx].compute([myOffset.left, myOffset.top], myWH, [otherOffset.left, otherOffset.top], otherWH); + var sAnchorO = this.anchors[sIdx].orientation; + var tAnchorP = this.anchors[tIdx].compute([otherOffset.left, otherOffset.top], otherWH, [myOffset.left, myOffset.top], myWH); + var tAnchorO = this.anchors[tIdx].orientation; + var dim = this.connector.compute(sAnchorP, tAnchorP, this.anchors[sIdx], this.anchors[tIdx], this.paintStyle.lineWidth); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + applyPaintStyle(ctx, this.paintStyle); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (this.paintStyle.gradient && !ie) { + var g = swap ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + for (var i = 0; i < this.paintStyle.gradient.stops.length; i++) + g.addColorStop(this.paintStyle.gradient.stops[i][0],this.paintStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + + this.connector.paint(dim, ctx); + + if (this.drawEndpoints) { + var style = this.endpointStyle || this.paintStyle; + var sourceCanvas = swap ? this.targetEndpointCanvas : this.sourceEndpointCanvas; + var targetCanvas = swap ? this.sourceEndpointCanvas : this.targetEndpointCanvas; + this.endpoints[swap ? 1 : 0].paint(sAnchorP, sAnchorO, sourceCanvas, this.endpointStyles[swap ? 1 : 0] || this.paintStyle, this.paintStyle); + this.endpoints[swap ? 0 : 1].paint(tAnchorP, tAnchorO, targetCanvas, this.endpointStyles[swap ? 0 : 1] || this.paintStyle, this.paintStyle); + } + } + }; + + this.repaint = function() { + this.paint(this.sourceId, null, true); + }; + + // dragging + var draggable = params.draggable == null ? _draggableByDefault : params.draggable; + if (draggable && self.source.draggable) { + var dragOptions = params.dragOptions || jsPlumb.DEFAULT_DRAG_OPTIONS; + var dragCascade = dragOptions.drag || function(e,u) {}; + var initDrag = function(element, dragFunc) { + var opts = $.extend({drag:dragFunc}, dragOptions); + var draggable = draggableStates[element.attr("id")]; + opts.disabled = draggable == null ? false : !draggable; + element.draggable(opts); + }; + initDrag(this.source, function(event, ui) { + _draw(self.source, ui); + dragCascade(event, ui); + }); + initDrag(this.target, function(event, ui) { + _draw(self.target, ui); + dragCascade(event, ui); + }); + } + + // resizing (using the jquery.ba-resize plugin). todo: decide whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + + // finally, draw it. + var o = this.source.offset(); + this.paint(this.sourceId, {'absolutePosition': this.source.offset()}); +}; + +})(); + +// jQuery plugin code +(function($){ + $.fn.plumb = function(options) { + var defaults = { }; + var options = $.extend(defaults, options); + + return this.each(function() + { + var obj = $(this); + var params = {}; + params.source = obj; + for (var i in options) { + params[i] = options[i]; + } + jsPlumb.connect(params); + }); + }; + + $.fn.detach = function(options) { + return this.each(function() + { + var id = $(this).attr("id"); + if (typeof options == 'string') options = [options]; + for (var i = 0; i < options.length; i++) + jsPlumb.detach(id, options[i]); + }); + }; + + $.fn.detachAll = function(options) { + return this.each(function() + { + var id = $(this).attr("id"); + jsPlumb.detachAll(id); + }); + }; +})(jQuery); diff --git a/archive/1.0.4/jquery.jsPlumb-1.0.4-min.js b/archive/1.0.4/jquery.jsPlumb-1.0.4-min.js new file mode 100644 index 000000000..de166b54d --- /dev/null +++ b/archive/1.0.4/jquery.jsPlumb-1.0.4-min.js @@ -0,0 +1,25 @@ +if(!Array.prototype.indexOf)Array.prototype.indexOf=function(s,n,w){n=+n||0;for(var r=this.length;ng)g=f;if(c<0){j+=c;c=Math.abs(c);g+=c;p[0]+=c;k+=c;t+=c;b[0]+=c}c=Math.min(Math.min(q,e),Math.min(p[1], +b[1]));f=Math.max(Math.max(q,e),Math.max(p[1],b[1]));if(f>i)i=f;if(c<0){m+=c;c=Math.abs(c);i+=c;p[1]+=c;q+=c;e+=c;b[1]+=c}return[j,m,g,i,k,q,t,e,p[0],p[1],b[0],b[1]]};this.paint=function(b,c){c.beginPath();c.moveTo(b[4],b[5]);c.bezierCurveTo(b[8],b[9],b[10],b[11],b[6],b[7]);c.stroke()}}},Endpoints:{Dot:function(a){a=a||{radius:10};var d=this;this.radius=a.radius;var b=0.5*this.radius,c=this.radius/3,f=function(h){try{return parseInt(h)}catch(e){if(h.substring(h.length-1)=="%")return parseInt(h.substring(0, +h-1))}};this.paint=function(h,e,g,i,j){var m=i.radius||d.radius;l.sizeCanvas(g,h[0]-m,h[1]-m,m*2,m*2);h=g.getContext("2d");g={};$.extend(g,i);if(g.fillStyle==null)g.fillStyle=j.strokeStyle;$.extend(h,g);j=/MSIE/.test(navigator.userAgent)&&!window.opera;if(i.gradient&&!j){j=i.gradient;g=b;var k=c;if(j.offset)g=f(j.offset);if(j.innerRadius)k=f(j.innerRadius);j=[g,k];e=h.createRadialGradient(m,m,m,m+(e[0]==1?j[0]*-1:j[0]),m+(e[1]==1?j[0]*-1:j[0]),j[1]);for(j=0;j=4)b.orientation=[arguments[2],arguments[3]];if(arguments.length==6)b.offsets=[arguments[4],arguments[5]]}return new u(b)},repaint:function(a){var d=function(c){var f=typeof c=="string"?$("#"+c):c;c=f.attr("id");var h=o[c];f={absolutePosition:f.offset()};for(var e=0;e0)B(a,"none"==d[0].canvas.style.display? +"block":"none")},unload:function(){delete o;delete x;delete y;delete z}},H=function(a){var d=this;this.source=typeof a.source=="string"?$("#"+a.source):a.source;this.target=typeof a.target=="string"?$("#"+a.target):a.target;this.sourceId=$(this.source).attr("id");this.targetId=$(this.target).attr("id");this.drawEndpoints=a.drawEndpoints!=null?a.drawEndpoints:true;this.endpointsOnTop=a.endpointsOnTop!=null?a.endpointsOnTop:true;this.anchors=a.anchors||l.DEFAULT_ANCHORS||[l.Anchors.BottomCenter,l.Anchors.TopCenter]; +this.connector=a.connector||l.DEFAULT_CONNECTOR||new l.Connectors.Bezier;this.paintStyle=a.paintStyle||l.DEFAULT_PAINT_STYLE;this.endpoints=[];if(!a.endpoints)a.endpoints=[null,null];this.endpoints[0]=a.endpoints[0]||a.endpoint||l.DEFAULT_ENDPOINTS[0]||l.DEFAULT_ENDPOINT||new l.Endpoints.Dot;this.endpoints[1]=a.endpoints[1]||a.endpoint||l.DEFAULT_ENDPOINTS[1]||l.DEFAULT_ENDPOINT||new l.Endpoints.Dot;this.endpointStyles=[];if(!a.endpointStyles)a.endpointStyles=[null,null];this.endpointStyles[0]=a.endpointStyles[0]|| +a.endpointStyle||l.DEFAULT_ENDPOINT_STYLES[0]||l.DEFAULT_ENDPOINT_STYLE;this.endpointStyles[1]=a.endpointStyles[1]||a.endpointStyle||l.DEFAULT_ENDPOINT_STYLES[1]||l.DEFAULT_ENDPOINT_STYLE;x[this.sourceId]=this.source.offset();y[this.sourceId]=[this.source.outerWidth(),this.source.outerHeight()];x[this.targetId]=this.target.offset();y[this.targetId]=[this.target.outerWidth(),this.target.outerHeight()];var b=C(l.connectorClass);this.canvas=b;if(this.drawEndpoints){this.sourceEndpointCanvas=C(l.endpointClass); +this.targetEndpointCanvas=C(l.endpointClass);var c=function(e,g,i){i=i?1:-1;g=$(g).css("zIndex");$(e).css("zIndex",g!="auto"?g+i:"auto")};c(this.sourceEndpointCanvas,this.source,this.endpointsOnTop);c(this.targetEndpointCanvas,this.target,this.endpointsOnTop)}this.paint=function(e,g,i){var j=e!=this.sourceId,m=j?this.sourceId:this.targetId,k=j?0:1,q=j?1:0;if(this.canvas.getContext){if(i){g=$("#"+e);i=$("#"+m);y[e]=[g.outerWidth(),g.outerHeight()];y[m]=[i.outerWidth(),i.outerHeight()];x[e]=g.offset(); +x[m]=i.offset()}else{i=g.absolutePosition||g.offset;g=g!=null?i:$("#"+e).offset();x[e]=g}i=x[e];var t=x[m],p=y[e],F=y[m];g=b.getContext("2d");m=this.anchors[q].compute([i.left,i.top],p,[t.left,t.top],F);e=this.anchors[q].orientation;i=this.anchors[k].compute([t.left,t.top],F,[i.left,i.top],p);t=this.anchors[k].orientation;k=this.connector.compute(m,i,this.anchors[q],this.anchors[k],this.paintStyle.lineWidth);l.sizeCanvas(b,k[0],k[1],k[2],k[3]);$.extend(g,this.paintStyle);q=/MSIE/.test(navigator.userAgent)&& +!window.opera;if(this.paintStyle.gradient&&!q){q=j?g.createLinearGradient(k[4],k[5],k[6],k[7]):g.createLinearGradient(k[6],k[7],k[4],k[5]);for(p=0;p w) w = maxx; + if (minx < 0) { + canvasX += minx; var ox = Math.abs(minx); + w += ox; CP[0] += ox; sx += ox; tx +=ox; CP2[0] += ox; + } + + var miny1 = Math.min(sy,ty); var miny2 = Math.min(CP[1], CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(sy,ty); var maxy2 = Math.max(CP[1], CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > h) h = maxy; + if (miny < 0) { + canvasY += miny; var oy = Math.abs(miny); + h += oy; CP[1] += oy; sy += oy; ty +=oy; CP2[1] += oy; + } + + // return [ canvasx, canvasy, canvasWidth, canvasHeight, + // sourceX, sourceY, targetX, targetY, + // controlPoint1_X, controlPoint1_Y, controlPoint2_X, controlPoint2_Y + return [canvasX, canvasY, w, h, sx, sy, tx, ty, CP[0], CP[1], CP2[0], CP2[1] ]; + }; + + this.paint = function(d, ctx) { + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + } + } + }, + + + /** + * Types of endpoint UIs. we supply three - a circle of default radius 10px, a rectangle of + * default size 20x20, and an image (with no default). you can supply others of these if you want to - see the documentation + * for a howto. + */ + Endpoints : { + + /** + * a round endpoint, with default radius 10 pixels. + */ + Dot : function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + } + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = {}; + applyPaintStyle(style, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + applyPaintStyle(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }, + + /** + * A Rectangular endpoint, with default size 20x20. + */ + Rectangle : function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + //todo: the fillStyle needs some thought. we want to support a few options: + // 1. nothing supplied; use the stroke color or the default if no stroke color. + // 2. a fill color supplied - use it + // 3. a gradient supplied - use it + // 4. setting the endpoint to the same color as the bg of the element it is attached to. + var style = {}; + applyPaintStyle(style, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + applyPaintStyle(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }, + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' property in the params object.. + */ + Image : function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + } + }, + + /** + * establishes a connection between two elements. + * @param params object containing setup for the connection. see documentation. + */ + connect : function(params) { + var jpc = new jsPlumbConnection(params); + var key = jpc.sourceId + "_" + jpc.targetId; + connections[key] = jpc; + var addToList = function(elId, jpc) { + var l = connections[elId]; + if (l == null) { + l = []; + connections[elId] = l; + } + l.push(jpc); + }; + + // register this connection. + addToList(jpc.sourceId, jpc); + addToList(jpc.targetId, jpc); + }, + + /** + * Remove one connection to an element. + * @param sourceId id of the first window in the connection + * @param targetId id of the second window in the connection + */ + detach : function(sourceId, targetId) { + var jpcs = connections[sourceId]; + var idx = -1; + for (var i = 0; i < jpcs.length; i++) { + if ((jpcs[i].sourceId == sourceId && jpcs[i].targetId == targetId) || (jpcs[i].targetId == sourceId && jpcs[i].sourceId == targetId)) { + _removeCanvas(jpcs[i].canvas); + if (jpcs[i].drawEndpoints) { + _removeCanvas(jpcs[i].targetEndpointCanvas); + _removeCanvas(jpcs[i].sourceEndpointCanvas); + } + idx = i; + break; + } + } + if (idx != -1) + jpcs.splice(idx, 1); + + // todo - dragging? if no more connections for an object turn off dragging by default, but + // allow an override on it? + }, + + /** + * remove all an element's connections. + */ + detachAll : function(elId) { + var jpcs = connections[elId]; + for (var i = 0; i < jpcs.length; i++) { + _removeCanvas(jpcs[i].canvas); + if (jpcs[i].drawEndpoints) { + _removeCanvas(jpcs[i].targetEndpointCanvas); + _removeCanvas(jpcs[i].sourceEndpointCanvas); + } + } + delete connections[elId]; + connections[elId] = []; + }, + + /** + * remove all connections. + */ + detachEverything : function() { + for (var elId in connections) { + var jpcs = connections[elId]; + if (jpcs.length) { + try { + for (var i = 0; i < jpcs.length; i++) { + + _removeCanvas(jpcs[i].canvas); + if (jpcs[i].drawEndpoints) { + _removeCanvas(jpcs[i].targetEndpointCanvas); + _removeCanvas(jpcs[i].sourceEndpointCanvas); + } + + } + } catch (e) { } + } + } + delete connections; + connections = []; + }, + + getConnections : function(elId) { + return connections[elId]; + }, + + /** + * Set an element's connections to be hidden. + */ + hide : function(elId) { + _setVisible(elId, "none"); + }, + + /** + * Creates an anchor with the given params. + * x - the x location of the anchor as a percentage of the total width. + * y - the y location of the anchor as a percentage of the total height. + * orientation - an [x,y] array indicating the general direction a connection from the anchor should go in. + * offsets - an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. optional. defaults to [0,0]. + */ + makeAnchor : function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed in but that makes the call very verbose. easier to use + // by just passing in four values. but for backwards compatibility if we are given only one value we assume it's a call in the old form. + var params = {}; + if (arguments.length == 1) $.extend(params, x); + else { + params = {x:x, y:y}; + if (arguments.length >= 4) { + params.orientation = [arguments[2], arguments[3]]; + } + if (arguments.length == 6) params.offsets = [arguments[4], arguments[5]]; + } + return new Anchor(params); + }, + + + /** + * repaint element and its connections. element may be an id or the actual jQuery object. + * this method gets new sizes for the elements before painting anything. + */ + repaint : function(el) { + var _repaint = function(el, elId) { + var jpcs = connections[elId]; + var idx = -1; + var loc = {'absolutePosition': el.offset()}; + for (var i = 0; i < jpcs.length; i++) { + jpcs[i].paint(elId, loc, true); + } + }; + + var _processElement = function(el) { + var ele = typeof(el)=='string' ? $("#" + el) : el; + var eleId = ele.attr("id"); + _repaint(ele, eleId); + }; + + // TODO: support a jQuery result object too! + + // support both lists... + if (typeof el =='object') { + for (var i = 0; i < el.length; i++) + _processElement(el[i]); + } // ...and single strings. + else _processElement(el); + }, + + /** + * repaint all connections. + */ + repaintEverything : function() { + for (var elId in connections) { + var jpcs = connections[elId]; + if (jpcs.length) { + try { + for (var i = 0; i < jpcs.length; i++) { + jpcs[i].repaint(); + } + } catch (e) { } + } + } + }, + + /** + * sets/unsets automatic repaint on window resize. + */ + setAutomaticRepaint : function(value) { + automaticRepaint = value; + }, + + /** + * Sets the default size jsPlumb will use for a new canvas (we create a square canvas so + * one value is all that is required). This is a hack for IE, because ExplorerCanvas seems + * to need for a canvas to be larger than what you are going to draw on it at initialisation + * time. The default value of this is 1200 pixels, which is quite large, but if for some + * reason you're drawing connectors that are bigger, you should adjust this value appropriately. + */ + setDefaultNewCanvasSize : function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }, + + /** + * Sets whether or not a given element is draggable, regardless of what any plumb command + * may request. + */ + setDraggable: function(element, draggable) { + _setDraggable(element, draggable); + }, + + /** + * Sets whether or not elements are draggable by default. Default for this is true. + */ + setDraggableByDefault: function(draggable) { + _draggableByDefault = draggable; + }, + + /** + * Sets the function to fire when the window size has changed and a repaint was fired. + */ + setRepaintFunction : function(f) { + repaintFunction = f; + }, + + /** + * Set an element's connections to be visible. + */ + show : function(elId) { + _setVisible(elId, "block"); + }, + + /** + * helper to size a canvas. + */ + sizeCanvas : function(canvas, x, y, w, h) { + canvas.style.height = h + "px"; canvas.height = h; + canvas.style.width = w + "px"; canvas.width = w; + canvas.style.left = x + "px"; canvas.style.top = y + "px"; + }, + + /** + * Toggles visibility of an element's connections. + */ + toggle : function(elId) { + var jpcs = connections[elId]; + if (jpcs.length > 0) + _setVisible(elId, "none" == jpcs[0].canvas.style.display ? "block" : "none"); + }, + + /** + * Unloads jsPlumb, deleting all storage. You should call this + */ + unload : function() { + delete connections; + delete offsets; + delete sizes; + delete draggableStates; + } +}; + +// ************** connection +// **************************************** +/** +* allowed params: +* source: source element (string or a jQuery element) (required) +* target: target element (string or a jQuery element) (required) +* anchors: optional array of anchor placements. defaults to BottomCenter for source +* and TopCenter for target. +*/ +var jsPlumbConnection = function(params) { + +// ************** get the source and target and register the connection. ******************* + var self = this; + // get source and target as jQuery objects + this.source = (typeof params.source == 'string') ? $("#" + params.source) : params.source; + this.target = (typeof params.target == 'string') ? $("#" + params.target) : params.target; + this.sourceId = $(this.source).attr("id"); + this.targetId = $(this.target).attr("id"); + this.drawEndpoints = params.drawEndpoints != null ? params.drawEndpoints : true; + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + + // get anchor + this.anchors = params.anchors || jsPlumb.DEFAULT_ANCHORS || [jsPlumb.Anchors.BottomCenter, jsPlumb.Anchors.TopCenter]; + // make connector + this.connector = params.connector || jsPlumb.DEFAULT_CONNECTOR || new jsPlumb.Connectors.Bezier(); + this.paintStyle = params.paintStyle || jsPlumb.DEFAULT_PAINT_STYLE; + // init endpoints + this.endpoints = []; + if(!params.endpoints) params.endpoints = [null,null]; + this.endpoints[0] = params.endpoints[0] || params.endpoint || jsPlumb.DEFAULT_ENDPOINTS[0] || jsPlumb.DEFAULT_ENDPOINT || new jsPlumb.Endpoints.Dot(); + this.endpoints[1] = params.endpoints[1] || params.endpoint || jsPlumb.DEFAULT_ENDPOINTS[1] ||jsPlumb.DEFAULT_ENDPOINT || new jsPlumb.Endpoints.Dot(); + this.endpointStyles = []; + if (!params.endpointStyles) params.endpointStyles = [null,null]; + this.endpointStyles[0] = params.endpointStyles[0] || params.endpointStyle || jsPlumb.DEFAULT_ENDPOINT_STYLES[0] || jsPlumb.DEFAULT_ENDPOINT_STYLE; + this.endpointStyles[1] = params.endpointStyles[1] || params.endpointStyle || jsPlumb.DEFAULT_ENDPOINT_STYLES[1] || jsPlumb.DEFAULT_ENDPOINT_STYLE; + + offsets[this.sourceId] = this.source.offset(); + sizes[this.sourceId] = [this.source.outerWidth(), this.source.outerHeight()]; + offsets[this.targetId] = this.target.offset(); + sizes[this.targetId] = [this.target.outerWidth(), this.target.outerHeight()]; + +// *************** create canvases on which the connection will be drawn ************ + var canvas = _newCanvas(jsPlumb.connectorClass); + this.canvas = canvas; + // create endpoint canvases + if (this.drawEndpoints) { + this.sourceEndpointCanvas = _newCanvas(jsPlumb.endpointClass); + this.targetEndpointCanvas = _newCanvas(jsPlumb.endpointClass); + // sit them on top of the underlying element? + var setZIndex = function(canvas, source, above) { + var adj = above ? 1 : -1; + var zIndex = $(source).css("zIndex"); + $(canvas).css("zIndex", zIndex != "auto" ? zIndex + adj : "auto"); + }; + + setZIndex(this.sourceEndpointCanvas, this.source, this.endpointsOnTop); + setZIndex(this.targetEndpointCanvas, this.target, this.endpointsOnTop); + } +// ************** store the anchors + + /** + * paints the connection. + * @param elId Id of the element that is in motion + * @param ui jQuery's event system ui object (present if we came from a drag to get here) + * @param recalc whether or not to recalculate element sizes. this is true if a repaint caused this to be painted. + */ + this.paint = function(elId, ui, recalc) { + // if the moving object is not the source we must transpose the two references. + var swap = !(elId == this.sourceId); + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + + if (this.canvas.getContext) { + + if (recalc) { + // get the current sizes of the two elements. + var s = $("#" + elId); + var t = $("#" + tId); + sizes[elId] = [s.outerWidth(), s.outerHeight()]; + sizes[tId] = [t.outerWidth(), t.outerHeight()]; + offsets[elId] = s.offset(); + offsets[tId] = t.offset(); + } else { + // faster to use the ui element if it was passed in. offset is a fallback. + // fix for change in 1.8 (absolutePosition renamed to offset). plugin is compatible with + // 1.8 and 1.7. + var pos = ui.absolutePosition || ui.offset; + var anOffset = ui != null ? pos : $("#" + elId).offset(); + offsets[elId] = anOffset; + } + + var myOffset = offsets[elId]; + var otherOffset = offsets[tId]; + var myWH = sizes[elId]; + var otherWH = sizes[tId]; + + var ctx = canvas.getContext('2d'); + var sAnchorP = this.anchors[sIdx].compute([myOffset.left, myOffset.top], myWH, [otherOffset.left, otherOffset.top], otherWH); + var sAnchorO = this.anchors[sIdx].orientation; + var tAnchorP = this.anchors[tIdx].compute([otherOffset.left, otherOffset.top], otherWH, [myOffset.left, myOffset.top], myWH); + var tAnchorO = this.anchors[tIdx].orientation; + var dim = this.connector.compute(sAnchorP, tAnchorP, this.anchors[sIdx], this.anchors[tIdx], this.paintStyle.lineWidth); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + applyPaintStyle(ctx, this.paintStyle); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (this.paintStyle.gradient && !ie) { + var g = swap ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + for (var i = 0; i < this.paintStyle.gradient.stops.length; i++) + g.addColorStop(this.paintStyle.gradient.stops[i][0],this.paintStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + + this.connector.paint(dim, ctx); + + if (this.drawEndpoints) { + var style = this.endpointStyle || this.paintStyle; + var sourceCanvas = swap ? this.targetEndpointCanvas : this.sourceEndpointCanvas; + var targetCanvas = swap ? this.sourceEndpointCanvas : this.targetEndpointCanvas; + this.endpoints[swap ? 1 : 0].paint(sAnchorP, sAnchorO, sourceCanvas, this.endpointStyles[swap ? 1 : 0] || this.paintStyle, this.paintStyle); + this.endpoints[swap ? 0 : 1].paint(tAnchorP, tAnchorO, targetCanvas, this.endpointStyles[swap ? 0 : 1] || this.paintStyle, this.paintStyle); + } + } + }; + + this.repaint = function() { + this.paint(this.sourceId, null, true); + }; + + // dragging + var draggable = params.draggable == null ? _draggableByDefault : params.draggable; + if (draggable && self.source.draggable) { + var dragOptions = params.dragOptions || jsPlumb.DEFAULT_DRAG_OPTIONS; + var dragCascade = dragOptions.drag || function(e,u) {}; + var initDrag = function(element, dragFunc) { + var opts = $.extend({drag:dragFunc}, dragOptions); + var draggable = draggableStates[element.attr("id")]; + opts.disabled = draggable == null ? false : !draggable; + element.draggable(opts); + }; + initDrag(this.source, function(event, ui) { + _draw(self.source, ui); + dragCascade(event, ui); + }); + initDrag(this.target, function(event, ui) { + _draw(self.target, ui); + dragCascade(event, ui); + }); + } + + // resizing (using the jquery.ba-resize plugin). todo: decide whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + + // finally, draw it. + var o = this.source.offset(); + this.paint(this.sourceId, {'absolutePosition': this.source.offset()}); +}; + +})(); + +// jQuery plugin code +(function($){ + $.fn.plumb = function(options) { + var defaults = { }; + var options = $.extend(defaults, options); + + return this.each(function() + { + var obj = $(this); + var params = {}; + params.source = obj; + for (var i in options) { + params[i] = options[i]; + } + jsPlumb.connect(params); + }); + }; + + $.fn.detach = function(options) { + return this.each(function() + { + var id = $(this).attr("id"); + if (typeof options == 'string') options = [options]; + for (var i = 0; i < options.length; i++) + jsPlumb.detach(id, options[i]); + }); + }; + + $.fn.detachAll = function(options) { + return this.each(function() + { + var id = $(this).attr("id"); + jsPlumb.detachAll(id); + }); + }; +})(jQuery); diff --git a/archive/1.1.0/jquery.jsPlumb-1.1.0-RC1.js b/archive/1.1.0/jquery.jsPlumb-1.1.0-RC1.js new file mode 100644 index 000000000..d83633d76 --- /dev/null +++ b/archive/1.1.0/jquery.jsPlumb-1.1.0-RC1.js @@ -0,0 +1,1277 @@ +/* + * jsPlumb 1.1.0-RC1 + * + * Provides a way to visually connect elements on an HTML page. + * + * 1.1.0-RC1 contains the first pass at supporting connection editing. the mechanism used to register + * and retrieve connections has been turned around to be endpoint centric, rather than + * connection centric. this will allow us to create endpoints that have 0-N connections. + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + */ +if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function( v, b, s ) { + for( var i = +b || 0, l = this.length; i < l; i++ ) { + if( this[i]===v || s && this[i]==v ) { return i; } + } + return -1; + }; +} +(function() { + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + + var log = null; + + var repaintFunction = function() { jsPlumb.repaintEverything(); }; + var automaticRepaint = true; + function repaintEverything() { + if (automaticRepaint) + repaintFunction(); + }; + var resizeTimer = null; + $(window).bind('resize', function() { + if (resizeTimer) clearTimeout(resizeTimer); + resizeTimer = setTimeout(repaintEverything, 100); + }); + + /** + * map of element id -> endpoint lists. an element can have an arbitrary number of endpoints on it, + * and not all of them have to be connected to anything. + */ + var endpointsByElement = {}; + var offsets = []; + var floatingConnections = {}; + var draggableStates = {}; + var _draggableByDefault = true; + var sizes = []; + var _jsPlumbContextNode = null; + + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + /** + * helper method to add an item to a list, creating the list if it does not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + }; + + /** + * Handles the dragging of an element. + * @param element jQuery element + * @param ui UI object from jQuery's event system + */ + var _draw = function(element, ui) { + var id = $(element).attr("id"); + var endpoints = endpointsByElement[id]; + if (endpoints) { + //if (ui == null) _updateOffset(id, ui); + _updateOffset(id, ui); + var myOffset = offsets[id]; + var myWH = sizes[id]; + // loop through endpoints for this element + for (var i = 0; i < endpoints.length; i++) { + var e = endpoints[i]; + // first, paint the endpoint + + var anchorLoc = endpoints[i].anchor.compute([myOffset.left, myOffset.top], myWH); + // var anchorOrientation = endpoints[i].anchor.getOrientation(); + + //todo...connector paint style? we have lost that with the move to endpoint-centric. + // perhaps we only paint the endpoint here if it has no connections; it can use its own style. + //if (!e.connections || e.connections.length == 0) + e.paint(anchorLoc); + + // else { + //if (e.connections && e.connections.length > 0) { + // get all connections for the endpoint... + var l = e.connections; + for (var j = 0; j < l.length; j++) + l[j].paint(id, ui); // ...and paint them. + //} + } + } + }; + + /** + * helper function: the second argument is a function taking two args - the first is a + * jquery element, and the second is the element's id. + * + * the first argument may be one of three things: + * + * 1. a string, in the form "window1", for example. an element's id. if your id starts with a + * hash then jsPlumb does not append its own hash too... + * 2. a jquery element, already resolved using $(...). + * 3. a list of strings/jquery elements. + */ + var _elementProxy = function(element, fn) { + var retVal = null; + if (typeof element == 'object' && element.length) { + retVal = []; + for (var i = 0; i < element.length; i++) { + var el = typeof element[i] == 'string' ? $("#" + element[i]) : element[i]; + var id = el.attr("id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } + else { + var el = typeof element == 'string' ? + element.indexOf("#") == 0 ? $(element) : $("#" + element) + : element; + var id = el.attr("id"); + retVal = fn(el, id); + } + + return retVal; + }; + + /** + * Returns (creating if necessary) the DIV element that jsPlumb uses as the context for all of its + * canvases. having this makes it possible to makes calls like $("selector", context), which are + * faster than if you provide no context. also we can clear out everything easily like this, either + * on a detachEverything() call or during unload(). + */ + var _getContextNode = function() { + if (_jsPlumbContextNode == null) { + _jsPlumbContextNode= document.createElement("div"); + document.body.appendChild(_jsPlumbContextNode); + _jsPlumbContextNode.className = "_jsPlumb_context"; + } + return $(_jsPlumbContextNode); + }; + + /** + * gets an id for the given element, creating and setting one if necessary. + */ + var _getId = function(element) { + var id = $(element).attr("id"); + if (!id) { + id = "_jsPlumb_" + new String((new Date()).getTime()); + $(element).attr("id", id); + } + return id; + }; + + /** + * inits a draggable if it's not already initialised. + * todo: if the element was draggable already, like from some non-jsPlumb call, wrap the drag function. + */ + var _initDraggableIfNecessary = function(element, elementId, isDraggable, dragOptions) { + // dragging + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable && element.draggable) { + var options = dragOptions || jsPlumb.Defaults.DragOptions; + var dragCascade = options.drag || function(e,u) {}; + var initDrag = function(element, elementId, dragFunc) { + var opts = $.extend({drag:dragFunc}, options); + var draggable = draggableStates[elementId]; + opts.disabled = draggable == null ? false : !draggable; + element.draggable(opts); + }; + initDrag(element, elementId, function(event, ui) { + _draw(element, ui); + $(element).addClass("jsPlumb_dragged"); + dragCascade(event, ui); + }); + } + + }; + + var _log = function(msg) { + // not implemented. yet. + } + + /** + * helper to create a canvas. + * @param clazz optional class name for the canvas. + */ + var _newCanvas = function(clazz) { + var canvas = document.createElement("canvas"); + _getContextNode().append(canvas); + canvas.style.position="absolute"; + if (clazz) { canvas.className=clazz; } + + if (/MSIE/.test(navigator.userAgent) && !window.opera) { + // for IE we have to set a big canvas size. actually you can override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + /** + * performs the given function operation on all the connections found for the given element + * id; this means we find all the endpoints for the given element, and then for each endpoint + * find the connectors connected to it. then we pass each connection in to the given + * function. + */ + var _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + for (var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }; + /** + * perform an operation on all elements. + */ + var _operationOnAll = function(func) { + for (var elId in endpointsByElement) { + _operation(elId, func); + } + }; + /** + * helper to remove an element from the DOM. + */ + var _removeElement = function(element) { + if (element != null) { + try { _jsPlumbContextNode.removeChild(element); } + catch (e) { } + } + }; + /** + * helper to remove a list of elements from the DOM. + */ + var _removeElements = function(elements) { + for (var i in elements) + _removeElement(elements[i]); + }; + /** + * helper method to remove an item from a list. + */ + var _removeFromList = function(map, key, value) { + var l = map[key]; + if (l != null) { + var i = l.indexOf(value); + if (i >= 0) { + delete( l[i] ); + l.splice( i, 1 ); + return true; + } + } + return false; + }; + /** + * Sets whether or not the given element(s) should be draggable, regardless of what a particular + * plumb command may request. + * + * @param element May be a string, a jQuery elements, or a list of strings/jquery elements. + * @param draggable Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + var _helper = function(el, id) { + draggableStates[id] = draggable; + if (el.draggable) { + el.draggable("option", "disabled", !draggable); + } + }; + + return _elementProxy(element, _helper); + }; + /** + * private method to do the business of hiding/showing. + * @param elId Id of the element in question + * @param state String specifying a value for the css 'display' property ('block' or 'none'). + */ + var _setVisible = function(elId, state) { + var f = function(jpc) { + //todo should we find all the endpoints instead of going by connection? this will + jpc.canvas.style.display = state; + /*jpc.sourceEndpointCanvas.style.display = state; + jpc.targetEndpointCanvas.style.display = state;*/ + }; + + _operation(elId, f); + }; + /** + * toggles the draggable state of the element with the given id. + */ + var _toggleDraggable = function(el) { + var fn = function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + el.draggable("option", "disabled", !state); + return state; + }; + return _elementProxy(el, fn); + }; + /** + * private method to do the business of toggling hiding/showing. + * @param elId Id of the element in question + */ + var _toggleVisible = function(elId) { + var f = function(jpc) {; + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + /*jpc.sourceEndpointCanvas.style.display = state; + jpc.targetEndpointCanvas.style.display = state;*/ + }; + + _operation(elId, f); + }; + /** + * updates the offset and size for a given element, and stores the values. + * if 'ui' is not null we use that (it would have been passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we use the offset, outerWidth and outerHeight methods to get + * the current values. + */ + var _updateOffset = function(elId, ui, recalc) { + + if (log) log.debug("updating offset for element [" + elId + "]; ui is [" + ui + "]; recalc is [" + recalc + "]"); + + if (recalc || ui == null) { // if forced repaint or no ui helper available, we recalculate. + // get the current size and offset, and store them + var s = $("#" + elId); + sizes[elId] = [s.outerWidth(), s.outerHeight()]; + offsets[elId] = s.offset(); + } else { + // faster to use the ui element if it was passed in. + // fix for change in 1.8 (absolutePosition renamed to offset). plugin is compatible with + // 1.8 and 1.7. + + // todo: when the drag axis is supplied, the ui object passed in has incorrect values + // for the other axis - like say you set axis='x'. when you move the mouse up and down + // while dragging, the y values are for where the window would be if it was not + // just constrained to x. not sure if this is a jquery bug or whether there's a known + // trick or whatever. + var pos = ui.absolutePosition || ui.offset; + var anOffset = ui != null ? pos : $("#" + elId).offset(); + offsets[elId] = anOffset; + } + }; + /** + * wraps one function with another, creating a placeholder for the wrapped function + * if it was null. this is used to wrap the various drag/drop event functions - to allow + * jsPlumb to be notified of important lifecycle events without imposing itself on the user's + * drap/drop functionality. + * TODO: determine whether or not we should try/catch the plumb function, so that the cascade function is always executed. + */ + var _wrap = function(cascadeFunction, plumbFunction) { + cascadeFunction = cascadeFunction || function(e, ui) { }; + return function(e, ui) { + plumbFunction(e, ui); + cascadeFunction(e, ui); + }; + } + /** + * Anchor class. Anchors can be situated anywhere. + * params should contain three values, and may optionally have an 'offsets' argument: + * + * x : the x location of the anchor as a fraction of the total width. + * + * y : the y location of the anchor as a fraction of the total height. + * + * orientation : an [x,y] array indicating the general direction a connection + * from the anchor should go in. for more info on this, see the documentation, + * or the docs in jquery-jsPlumb-defaults-XXX.js for the default Anchors. + * + * offsets : an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. may be null. + * + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; this.y = params.y || 0; + var orientation = params.orientation || [0,0]; + this.offsets = params.offsets || [0,0]; + this.compute = function(xy, wh, txy, twh) { + return [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + } + this.getOrientation = function() { return orientation; }; + }; + + /** + * an anchor that floats. its orientation is computed dynamically from its position relative + * to the anchor it is floating relative to. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for purposes of calculating the orientation. + var ref = params.reference; + // these are used to store the current relative position of our anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating anchor is hovering over another anchor. + var orientation = null; + + this.compute = function(xy, wh, txy, twh) { + // set these for the getOrientation method to use. + xDir = 0;//xy[0] < txy[0] ? -1 : xy[0] == txy[0] ? 0 : 1; + yDir = 0;//xy[1] < txy[1] ? -1 : xy[1] == txy[1] ? 0 : 1; + return [xy[0], xy[1]]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other anchor: if it declares zero for some direction, we declare zero too. + // this might not be the most awesome. perhaps we can come up with a better way. it's just so that the line we draw looks + // like it makes sense. maybe this wont make sense. + return [Math.abs(o[0]) * xDir * -1, Math.abs(o[1]) * yDir * -1]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering over another anchor; + * we want to assume that anchor's orientation for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no longer hovering + * over another anchor; we should resume calculating orientation as we normally do. + */ + this.out = function() { + orientation = null; + }; + }; + + // ************** connection + // **************************************** + /** + * allowed params: + * source: source element (string or a jQuery element) (required) + * target: target element (string or a jQuery element) (required) + * + * anchors: optional array of anchor placements. defaults to BottomCenter for source + * and TopCenter for target. + */ + var Connection = function(params) { + + // ************** get the source and target and register the connection. ******************* + var self = this; + // get source and target as jQuery objects + this.source = (typeof params.source == 'string') ? $("#" + params.source) : params.source; + this.target = (typeof params.target == 'string') ? $("#" + params.target) : params.target; + this.sourceId = $(this.source).attr("id"); + this.targetId = $(this.target).attr("id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + + // init endpoints + this.endpoints = []; + this.endpointStyles = []; + var prepareEndpoint = function(existing, index, params) { + if (existing) self.endpoints[index] = existing; + else { + if(!params.endpoints) params.endpoints = [null,null]; + var ep = params.endpoints[index] || params.endpoint || jsPlumb.Defaults.Endpoints[index] || jsPlumb.Defaults.Endpoint|| new jsPlumb.Endpoints.Dot(); + if (!params.endpointStyles) params.endpointStyles = [null,null]; + var es = params.endpointStyles[index] || params.endpointStyle || jsPlumb.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyle; + var a = params.anchors ? params.anchors[index] : jsPlumb.Defaults.Anchors[index] || jsPlumb.Anchors.BottomCenter; + self.endpoints[index] = new Endpoint({style:es, endpoint:ep, connections:[self], anchor:a }); + } + }; + + prepareEndpoint(params.sourceEndpoint, 0, params); + prepareEndpoint(params.targetEndpoint, 1, params); + + // make connector. if an endpoint has a connector + paintstyle to use, we use that. + // otherwise we use sensible defaults. + //this.connector = params.connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + //this.paintStyle = params.paintStyle || jsPlumb.Defaults.PaintStyle; + this.paintStyle = this.endpoints[0].connectionStyle || this.endpoints[1].connectionStyle || params.paintStyle || jsPlumb.Defaults.PaintStyle; + + _updateOffset(this.sourceId); + _updateOffset(this.targetId); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var anchorLoc = this.endpoints[0].anchor.compute([myOffset.left, myOffset.top], myWH); + this.endpoints[0].paint(anchorLoc); + myOffset = offsets[this.targetId]; myWH = sizes[this.targetId]; + anchorLoc = this.endpoints[1].anchor.compute([myOffset.left, myOffset.top], myWH); + this.endpoints[1].paint(anchorLoc); + + // *************** create canvas on which the connection will be drawn ************ + var canvas = _newCanvas(jsPlumb.connectorClass); + this.canvas = canvas; + + /** + * paints the connection. + * @param elId Id of the element that is in motion + * @param ui jQuery's event system ui object (present if we came from a drag to get here) + * @param recalc whether or not to recalculate element sizes. this is true if a repaint caused this to be painted. + */ + this.paint = function(elId, ui, recalc) { + + if (log) log.debug("Painting Connection; element in motion is " + elId + "; ui is [" + ui + "]; recalc is [" + recalc + "]"); + + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = !(elId == this.sourceId); + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + if (this.canvas.getContext) { + + _updateOffset(elId, ui, recalc); + if (recalc) _updateOffset(tId); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var myOffset = offsets[elId]; + var otherOffset = offsets[tId]; + var myWH = sizes[elId]; + var otherWH = sizes[tId]; + + var ctx = canvas.getContext('2d'); + var sAnchorP = this.endpoints[sIdx].anchor.compute([myOffset.left, myOffset.top], myWH, [otherOffset.left, otherOffset.top], otherWH); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.compute([otherOffset.left, otherOffset.top], otherWH, [myOffset.left, myOffset.top], myWH); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, this.paintStyle.lineWidth); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + $.extend(ctx, this.paintStyle); + + if (this.paintStyle.gradient && !ie) { + var g = swap ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + for (var i = 0; i < this.paintStyle.gradient.stops.length; i++) + g.addColorStop(this.paintStyle.gradient.stops[i][0],this.paintStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + + this.connector.paint(dim, ctx); + + // this.endpoints[swap ? 1 : 0].paint(sAnchorP, this.paintStyle); + //this.endpoints[swap ? 0 : 1].paint(tAnchorP, this.paintStyle); + } + }; + + this.repaint = function() { + this.paint(this.sourceId, null, true); + }; + + _initDraggableIfNecessary(self.source, self.sourceId, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, self.targetId, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + }; + + /** + * models an endpoint. can have one to N connections emanating from it (although how to handle that in the UI is + * a very good question). also has a Canvas and paint style. + * + * params: + * + * anchor : anchor for the endpoint, of type jsPlumb.Anchor. may be null. + * endpoint : endpoint object, of type jsPlumb.Endpoint. may be null. + * style : endpoint style, a js object. may be null. + * source : element the endpoint is attached to, of type jquery object. Required. + * canvas : canvas element to use. may be, and most often is, null. + * connections : optional list of connections to configure the endpoint with. + * isSource : boolean. indicates the endpoint can act as a source of new connections. optional. + * dragOptions : if isSource is set to true, you can supply arguments for the jquery draggable method. optional. + * connectionStyle : if isSource is set to true, this is the paint style for connections from this endpoint. optional. + * connector : optional connector type to use. + * isTarget : boolean. indicates the endpoint can act as a target of new connections. optional. + * dropOptions : if isTarget is set to true, you can supply arguments for the jquery droppable method. optional. + * reattach : optional boolean that determines whether or not the connections reattach after they + * have been dragged off an endpoint and left floating. defaults to false - connections + * dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + params = params || {}; + // make a copy. then we can use the wrapper function. + params = $.extend({}, params); + var self = this; + self.anchor = params.anchor || jsPlumb.Anchors.TopCenter; + var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot(); + var _style = params.style || jsPlumb.Defaults.EndpointStyle; + this.connectionStyle = params.connectionStyle; + this.connector = params.connector; + var _element = params.source; + var _elementId = $(_element).attr("id"); + var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of. + this.canvas = params.canvas || _newCanvas(jsPlumb.endpointClass); + this.connections = params.connections || []; + var _reattach = params.reattach || false; + var floatingEndpoint = null; + this.addConnection = function(connection) { + self.connections.push(connection); + }; + this.removeConnection = function(connection) { + var idx = self.connections.indexOf(connection); + if (idx >= 0) + self.connections.splice(idx, 1); + }; + this.isFloating = function() { return floatingEndpoint != null; }; + /** + * first pass at default ConnectorSelector: returns the first connection, if we have any. + * modified a little, 5/10/2010: now it only returns a connector if we have not got equal to or more than _maxConnector connectors + * attached. otherwise it is assumed a new connector is ok. but note with this setup we can't select any other connection than the first + * one. what if this could return a list? that implies everything should work with a list - dragging etc. could be nasty. could also + * be cool. + */ + var connectorSelector = function() { + return self.connections.length == 0 || self.connections.length < _maxConnections ? null : self.connections[0]; + }; + + // get the jsplumb context...lookups are faster with a context. + var contextNode = _getContextNode(); + + this.isFull = function() { return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); }; + + /** + * paints the Endpoint, recalculating offset and anchor positions if necessary. + */ + this.paint = function(anchorPoint, connectorPaintStyle, canvas) { + + if (log) log.debug("Painting Endpoint with elementId [" + _elementId + "]; anchorPoint is [" + anchorPoint + "]"); + + if (anchorPoint == null) { + // do we always want to force a repaint here? i dont think so! + var xy = offsets[_elementId]; + var wh = sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset(_elementId); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + anchorPoint = self.anchor.compute([xy.left, xy.top], wh); + } + _endpoint.paint(anchorPoint, self.anchor.getOrientation(), canvas || self.canvas, _style, connectorPaintStyle || _style); + }; + + + // is this a connection source? we make it draggable and have the drag listener + // maintain a connection with a floating endpoint. + if (params.isSource && _element.draggable) { + + var n = null, id = null, + jpc = null, + existingJpc = false, existingJpcParams = null; + + // first the question is, how many connections are on this endpoint? if it's only one, then excellent. otherwise we will either need a way + // to select one connection from the list, or drag them all. if we had a pluggable 'ConnectorSelector' interface we could probably + // provide a way for people to implement their own UI components to do the connector selection. the question in that particular case would be how much + // information the interface needs from jsPlumb at execution time. if, however, we leave out the connector selection, and drag them all, + // that wouldn't be too hard to organise. perhaps that behaviour would be on a switch for the endpoint, or perhaps the ConnectorSelector + // interface returns a List, with the default implementation just returning everything. i think i like that. + // + // let's say for now that there is just one endpoint, cos we need to get this going before we can consider a list of them anyway. + // the major difference between that case and the case of a new endpoint is actually quite small - it's a question of where the + // Connection comes from. for a new one, we create a new one. obviously. otherwise we just get the jpc from the Endpoint + // (remember we're only assuming one connection right now). so all of the UI stuff we do to create the floating endpoint etc + // will still be valid, but when we stop dragging, we'll have to do something different. if we stop with a valid drop i think it will + // be the same process. but if we stop with an invalid drop we have to reset the Connection to how it was when we got it. + var start = function(e, ui) { + //if (!isFull()) { + n = document.createElement("div"); + contextNode.append(n); + // create and assign an id, and initialize the offset. + id = new String(new Date().getTime()); + $(n, contextNode).attr("id", id); + _updateOffset(id); + // store the id of the dragging div and the source element. the drop function + // will pick these up. + $(self.canvas, contextNode).attr("dragId", id); + $(self.canvas, contextNode).attr("elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor({reference:self.anchor}); + floatingEndpoint = new Endpoint({ + style:_style, + endpoint:_endpoint, + anchor:floatingAnchor, + source:n + }); + //floatingEndpoint.originalAnchor = + + jpc = connectorSelector(); + if (jpc == null) { + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = new Connection({ + sourceEndpoint:self, + targetEndpoint:floatingEndpoint, + source:$(_element), + target:$(n, contextNode), + anchors:[self.anchor, floatingAnchor], + paintStyle : params.connectionStyle, // this can be null. Connection will use the default. + connector: params.connector + }); + // todo ...unregister on stop + self.addConnection(jpc); + } else { + existingJpc = true; + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; + // probably we should remove the connection? and add it back if the user + // does not drop it somewhere proper. + self.removeConnection(jpc); + if (anchorIdx == 0){ + existingJpcParams = [jpc.source, jpc.sourceId]; + jpc.source = $(n, contextNode); + jpc.sourceId = id; + }else { + existingJpcParams = [jpc.target, jpc.targetId]; + jpc.target = $(n, contextNode); + jpc.targetId = id; + } + + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // todo unregister on stop + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + } + //}; + + var dragOptions = params.dragOptions || { }; + dragOptions = $.extend({ opacity:0.5, revert:true, helper:'clone' }, dragOptions); + + dragOptions.start = _wrap(dragOptions.start, start); + dragOptions.drag = _wrap(dragOptions.drag, function(e, ui) { + _draw($(n, contextNode), ui); + }); + dragOptions.stop = _wrap(dragOptions.stop, + function(e, ui) { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements([floatingEndpoint.canvas, n]); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (jpc.endpoints[idx] == floatingEndpoint) { + + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + if (_reattach) { + jpc.floatingAnchorIndex = null; + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } + else { + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeElement(jpc.canvas); + } + } else { + _removeElement(jpc.canvas); + self.removeConnection(jpc); + } + } + jpc = null; + delete floatingEndpoint; + } + ); + + $(self.canvas, contextNode).draggable(dragOptions); + } + + // connector target + if (params.isTarget && _element.droppable) { + var dropOptions = params.dropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = $.extend({}, dropOptions); + var originalAnchor = null; + dropOptions.drop = _wrap(dropOptions.drop, function(e, ui) { + var id = $(ui.draggable, contextNode).attr("dragId"); + var elId = $(ui.draggable, contextNode).attr("elId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // remove this jpc from the current endpoint + jpc.endpoints[idx].removeConnection(jpc); + if (jpc.suspendedEndpoint) + jpc.suspendedEndpoint.removeConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + _initDraggableIfNecessary(_element, _elementId, params.draggable, {}); + jsPlumb.repaint($(ui.draggable, contextNode).attr("elId")); + delete floatingConnections[id]; + }); + // what to do when something is dropped. + // 1. find the jpc that is being dragged. the target endpoint of the jpc will be the + // one that is being dragged. + // 2. arrange for the floating endpoint to be replaced with this endpoint; make sure + // everything gets registered ok etc. + // 3. arrange for the floating endpoint to be deleted. + // 4. make sure that the stop method of the drag does not cause the jpc to be cleaned up. we want to keep it now. + + // other considerations: when in the hover mode, we should switch the floating endpoint's + // orientation to be the same as the drop target. this will cause the connector to snap + // into the shape it will take if the user drops at that point. + + dropOptions.over = _wrap(dropOptions.over, function(event, ui) { + var id = $(ui.draggable, contextNode).attr("dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + }); + + dropOptions.out = _wrap(dropOptions.out, function(event, ui) { + var id = $(ui.draggable, contextNode).attr("dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + }); + + $(self.canvas, contextNode).droppable(dropOptions); + } + + // woo...add a plumb command to Endpoint. + this.plumb = function(params) { + // not much to think about. the target should be an Endpoint, but what else? + // all the style stuff is on the endpoint itself already. + //todo this should call the main plumb method, just with some different args. + + }; + + return self; + }; + + /** + * jsPlumb public API + */ + var jsPlumb = window.jsPlumb = { + + Defaults : { + Anchors : [ null, null ], + Connector : null, + DragOptions: { }, + DropOptions: { }, + Endpoint : null, + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : null }, + EndpointStyles : [ null, null ], + MaxConnections : null, + PaintStyle : { lineWidth : 10, strokeStyle : 'red' } + }, + + connectorClass : '_jsPlumb_connector', + endpointClass : '_jsPlumb_endpoint', + + Anchors : {}, + Connectors : {}, + Endpoints : {}, + + /** + * adds an endpoint to the element + */ + addEndpoint : function(target, params) { + params = $.extend({}, params); + var el = typeof target == 'string' ? $("#" + target) : target; + var id = $(el).attr("id"); + params.source = el; + _updateOffset(id); + var e = new Endpoint(params); + _addToList(endpointsByElement, id, e); + + var myOffset = offsets[id]; + var myWH = sizes[id]; + + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH); + e.paint(anchorLoc); + return e; + }, + + /** + * wrapper around standard jquery animate function; injects a call to jsPlumb in the + * 'step' function (creating it if necessary). this only supports the two-arg version + * of the animate call in jquery - the one that takes an object as the second arg. + */ + animate : function(el, properties, options) { + var ele = typeof(el)=='string' ? $("#" + el) : el; + var id = ele.attr("id"); + options = options || {}; + options.step = _wrap(options.step, function() { jsPlumb.repaint(id); }); + ele.animate(properties, options); + }, + + /** + * adds a list of endpoints to the element + */ + addEndpoints : function(target, endpoints) { + var results = []; + for (var i = 0; i < endpoints.length; i++) { + results.push(jsPlumb.addEndpoint(target, endpoints[i])); + } + return results; + }, + + /** + * establishes a connection between two elements. + * @param params object containing setup for the connection. see documentation. + */ + connect : function(params) { + if (params.sourceEndpoint && params.sourceEndpoint.isFull()) { + _log("could not add connection; source endpoint is full"); + return; + } + + if (params.targetEndpoint && params.targetEndpoint.isFull()) { + _log("could not add connection; target endpoint is full"); + return; + } + + var jpc = new Connection(params); + + // register endpoints for the element + _addToList(endpointsByElement, jpc.sourceId, jpc.endpoints[0]); + _addToList(endpointsByElement, jpc.targetId, jpc.endpoints[1]); + + jpc.endpoints[0].addConnection(jpc); + jpc.endpoints[1].addConnection(jpc); + + // force a paint + _draw(jpc.source); + + }, + + connectEndpoints : function(params) { + var jpc = Connection(params); + + }, + + /** + * Remove one connection to an element. + * @param sourceId id of the first window in the connection + * @param targetId id of the second window in the connection + * @return true if successful, false if not. + */ + detach : function(sourceId, targetId) { + var f = function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas); + /*_removeElement(jpc.targetEndpointCanvas); + _removeElement(jpc.sourceEndpointCanvas);*/ + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + return true; + } + }; + + // todo: how to cleanup the actual storage? a third arg to _operation? + _operation(sourceId, f); + }, + + /** + * remove all an element's connections. + * @param elId id of the + */ + detachAll : function(elId) { + + var f = function(jpc) { + // todo replace with _cleanupConnection call here. + _removeElement(jpc.canvas); + /*_removeElement(jpc.targetEndpointCanvas); + _removeElement(jpc.sourceEndpointCanvas);*/ + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + }; + _operation(elId, f); + //delete endpointsByElement[elId]; + }, + + /** + * remove all connections. and endpoints? probably not. + */ + detachEverything : function() { + var f = function(jpc) { + _removeElement(jpc.canvas); + /*_removeElement(jpc.targetEndpointCanvas); + _removeElement(jpc.sourceEndpointCanvas);*/ + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + }; + + _operationOnAll(f); + + /*delete endpointsByElement; + endpointsByElement = {};*/ + }, + + /** + * Set an element's connections to be hidden. + */ + hide : function(elId) { + _setVisible(elId, "none"); + }, + + /** + * Creates an anchor with the given params. + * x - the x location of the anchor as a fraction of the total width. + * y - the y location of the anchor as a fraction of the total height. + * orientation - an [x,y] array indicating the general direction a connection from the anchor should go in. + * offsets - an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. optional. defaults to [0,0]. + */ + makeAnchor : function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards compatibility if we are given only one value we assume it's a call in the old form. + var params = {}; + if (arguments.length == 1) $.extend(params, x); + else { + params = {x:x, y:y}; + if (arguments.length >= 4) { + params.orientation = [arguments[2], arguments[3]]; + } + if (arguments.length == 6) params.offsets = [arguments[4], arguments[5]]; + } + return new Anchor(params); + }, + + + /** + * repaint element and its connections. element may be an id or the actual jQuery object. + * this method gets new sizes for the elements before painting anything. + */ + repaint : function(el) { + + var _processElement = function(el) { + var ele = typeof(el)=='string' ? $("#" + el) : el; + _draw(ele); + }; + + // TODO: support a jQuery result object too! + + // support both lists... + if (typeof el =='object') { + for (var i = 0; i < el.length; i++) + _processElement(el[i]); + } // ...and single strings. + else _processElement(el); + }, + + /** + * repaint all connections. + */ + repaintEverything : function() { + for (var elId in endpointsByElement) { + _draw($("#" + elId)); + } + }, + + removeEndpoint : function(elId, endpoint) { + var ebe = endpointsByElement[elId]; + if (ebe) { + // var i = ebe.indexOf(endpoint); + // alert('element has ' + ebe.length); + //if (i > -1) { + //ebe.splice(i, 1); + if(_removeFromList(endpointsByElement, elId, endpoint)) + _removeElement(endpoint.canvas); + //} + } + }, + + /** + * sets/unsets automatic repaint on window resize. + */ + setAutomaticRepaint : function(value) { + automaticRepaint = value; + }, + + /** + * Sets the default size jsPlumb will use for a new canvas (we create a square canvas so + * one value is all that is required). This is a hack for IE, because ExplorerCanvas seems + * to need for a canvas to be larger than what you are going to draw on it at initialisation + * time. The default value of this is 1200 pixels, which is quite large, but if for some + * reason you're drawing connectors that are bigger, you should adjust this value appropriately. + */ + setDefaultNewCanvasSize : function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }, + + /** + * Sets whether or not a given element is draggable, regardless of what any plumb command + * may request. + */ + setDraggable: _setDraggable, + + /** + * Sets whether or not elements are draggable by default. Default for this is true. + */ + setDraggableByDefault: function(draggable) { + _draggableByDefault = draggable; + }, + + setDebugLog: function(debugLog) { + log = debugLog; + }, + + /** + * Sets the function to fire when the window size has changed and a repaint was fired. + */ + setRepaintFunction : function(f) { + repaintFunction = f; + }, + + /** + * Set an element's connections to be visible. + */ + show : function(elId) { + _setVisible(elId, "block"); + }, + + /** + * helper to size a canvas. + */ + sizeCanvas : function(canvas, x, y, w, h) { + canvas.style.height = h + "px"; canvas.height = h; + canvas.style.width = w + "px"; canvas.width = w; + canvas.style.left = x + "px"; canvas.style.top = y + "px"; + }, + + /** + * gets some test hooks. nothing writable. + */ + getTestHarness : function() { + return { + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + } + }; + }, + + /** + * Toggles visibility of an element's connections. kept for backwards compatibility + */ + toggle : _toggleVisible, + + /** + * new name for the old toggle method. + */ + toggleVisible : _toggleVisible, + + /** + * Toggles draggability (sic) of an element. + */ + toggleDraggable : _toggleDraggable, + + /** + * Unloads jsPlumb, deleting all storage. You should call this + */ + unload : function() { + delete endpointsByElement; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + document.body.removeChild(_jsPlumbContextNode); + } + }; + +})(); + +// jQuery plugin code +(function($){ + /** + * plumbs the results of the selector to some target, using the given options if supplied, + * or the defaults otherwise. + */ + $.fn.plumb = function(options) { + var options = $.extend({}, options); + + return this.each(function() + { + var params = $.extend({source:$(this)}, options); + jsPlumb.connect(params); + }); + }; + + /** + * detaches the results of the selector from the given target or list of targets - 'target' + * may be a String or a List. + */ + $.fn.detach = function(target) { + return this.each(function() + { + var id = $(this).attr("id"); + if (typeof target == 'string') target = [target]; + for (var i = 0; i < target.length; i++) + jsPlumb.detach(id, target[i]); + }); + }; + + /** + * detaches the results from the selector from all connections. + */ + $.fn.detachAll = function() { + return this.each(function() + { + var id = $(this).attr("id"); + jsPlumb.detachAll(id); + }); + }; + + /** + * adds an endpoint to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoint = function(options) { + var addedEndpoints = []; + this.each(function() + { + //var params = $.extend({source:$(this)}, options); + addedEndpoints.push(jsPlumb.addEndpoint($(this).attr("id"), options)); + }); + return addedEndpoints; + }; + + /** + * adds a list of endpoints to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoints = function(endpoints) { + var addedEndpoints = []; + return this.each(function() + { + var e = jsPlumb.addEndpoints($(this).attr("id"), endpoints); + for (var i = 0; i < e.length; i++) addedEndpoints.push(e[i]); + }); + return addedEndpoints; + }; + + /** + * remove the endpoint, if it exists, deleting its UI elements etc. + */ + $.fn.removeEndpoint = function(endpoint) { + this.each(function() + { + jsPlumb.removeEndpoint($(this).attr("id"), endpoint); + }); + }; + +})(jQuery); diff --git a/archive/1.1.0/jquery.jsPlumb-1.1.0.js b/archive/1.1.0/jquery.jsPlumb-1.1.0.js new file mode 100644 index 000000000..b27b586e9 --- /dev/null +++ b/archive/1.1.0/jquery.jsPlumb-1.1.0.js @@ -0,0 +1,1275 @@ +/* + * jsPlumb 1.1.0 + * + * Provides a way to visually connect elements on an HTML page. + * + * 1.1.0 contains the first released version of the code that supports draggable connectors. + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + */ +if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function( v, b, s ) { + for( var i = +b || 0, l = this.length; i < l; i++ ) { + if( this[i]===v || s && this[i]==v ) { return i; } + } + return -1; + }; +} +(function() { + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + + var log = null; + + var repaintFunction = function() { jsPlumb.repaintEverything(); }; + var automaticRepaint = true; + function repaintEverything() { + if (automaticRepaint) + repaintFunction(); + }; + var resizeTimer = null; + $(window).bind('resize', function() { + if (resizeTimer) clearTimeout(resizeTimer); + resizeTimer = setTimeout(repaintEverything, 100); + }); + + /** + * map of element id -> endpoint lists. an element can have an arbitrary number of endpoints on it, + * and not all of them have to be connected to anything. + */ + var endpointsByElement = {}; + var offsets = []; + var floatingConnections = {}; + var draggableStates = {}; + var _draggableByDefault = true; + var sizes = []; + var _jsPlumbContextNode = null; + + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + /** + * helper method to add an item to a list, creating the list if it does not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + }; + + /** + * Handles the dragging of an element. + * @param element jQuery element + * @param ui UI object from jQuery's event system + */ + var _draw = function(element, ui) { + var id = $(element).attr("id"); + var endpoints = endpointsByElement[id]; + if (endpoints) { + //if (ui == null) _updateOffset(id, ui); + _updateOffset(id, ui); + var myOffset = offsets[id]; + var myWH = sizes[id]; + // loop through endpoints for this element + for (var i = 0; i < endpoints.length; i++) { + var e = endpoints[i]; + // first, paint the endpoint + + var anchorLoc = endpoints[i].anchor.compute([myOffset.left, myOffset.top], myWH); + // var anchorOrientation = endpoints[i].anchor.getOrientation(); + + //todo...connector paint style? we have lost that with the move to endpoint-centric. + // perhaps we only paint the endpoint here if it has no connections; it can use its own style. + //if (!e.connections || e.connections.length == 0) + e.paint(anchorLoc); + + // else { + //if (e.connections && e.connections.length > 0) { + // get all connections for the endpoint... + var l = e.connections; + for (var j = 0; j < l.length; j++) + l[j].paint(id, ui); // ...and paint them. + //} + } + } + }; + + /** + * helper function: the second argument is a function taking two args - the first is a + * jquery element, and the second is the element's id. + * + * the first argument may be one of three things: + * + * 1. a string, in the form "window1", for example. an element's id. if your id starts with a + * hash then jsPlumb does not append its own hash too... + * 2. a jquery element, already resolved using $(...). + * 3. a list of strings/jquery elements. + */ + var _elementProxy = function(element, fn) { + var retVal = null; + if (typeof element == 'object' && element.length) { + retVal = []; + for (var i = 0; i < element.length; i++) { + var el = typeof element[i] == 'string' ? $("#" + element[i]) : element[i]; + var id = el.attr("id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } + else { + var el = typeof element == 'string' ? + element.indexOf("#") == 0 ? $(element) : $("#" + element) + : element; + var id = el.attr("id"); + retVal = fn(el, id); + } + + return retVal; + }; + + /** + * Returns (creating if necessary) the DIV element that jsPlumb uses as the context for all of its + * canvases. having this makes it possible to makes calls like $("selector", context), which are + * faster than if you provide no context. also we can clear out everything easily like this, either + * on a detachEverything() call or during unload(). + */ + var _getContextNode = function() { + if (_jsPlumbContextNode == null) { + _jsPlumbContextNode= document.createElement("div"); + document.body.appendChild(_jsPlumbContextNode); + _jsPlumbContextNode.className = "_jsPlumb_context"; + } + return $(_jsPlumbContextNode); + }; + + /** + * gets an id for the given element, creating and setting one if necessary. + */ + var _getId = function(element) { + var id = $(element).attr("id"); + if (!id) { + id = "_jsPlumb_" + new String((new Date()).getTime()); + $(element).attr("id", id); + } + return id; + }; + + /** + * inits a draggable if it's not already initialised. + * todo: if the element was draggable already, like from some non-jsPlumb call, wrap the drag function. + */ + var _initDraggableIfNecessary = function(element, elementId, isDraggable, dragOptions) { + // dragging + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable && element.draggable) { + var options = dragOptions || jsPlumb.Defaults.DragOptions; + var dragCascade = options.drag || function(e,u) {}; + var initDrag = function(element, elementId, dragFunc) { + var opts = $.extend({drag:dragFunc}, options); + var draggable = draggableStates[elementId]; + opts.disabled = draggable == null ? false : !draggable; + element.draggable(opts); + }; + initDrag(element, elementId, function(event, ui) { + _draw(element, ui); + $(element).addClass("jsPlumb_dragged"); + dragCascade(event, ui); + }); + } + + }; + + var _log = function(msg) { + // not implemented. yet. + } + + /** + * helper to create a canvas. + * @param clazz optional class name for the canvas. + */ + var _newCanvas = function(clazz) { + var canvas = document.createElement("canvas"); + _getContextNode().append(canvas); + canvas.style.position="absolute"; + if (clazz) { canvas.className=clazz; } + + if (/MSIE/.test(navigator.userAgent) && !window.opera) { + // for IE we have to set a big canvas size. actually you can override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + /** + * performs the given function operation on all the connections found for the given element + * id; this means we find all the endpoints for the given element, and then for each endpoint + * find the connectors connected to it. then we pass each connection in to the given + * function. + */ + var _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + for (var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }; + /** + * perform an operation on all elements. + */ + var _operationOnAll = function(func) { + for (var elId in endpointsByElement) { + _operation(elId, func); + } + }; + /** + * helper to remove an element from the DOM. + */ + var _removeElement = function(element) { + if (element != null) { + try { _jsPlumbContextNode.removeChild(element); } + catch (e) { } + } + }; + /** + * helper to remove a list of elements from the DOM. + */ + var _removeElements = function(elements) { + for (var i in elements) + _removeElement(elements[i]); + }; + /** + * helper method to remove an item from a list. + */ + var _removeFromList = function(map, key, value) { + var l = map[key]; + if (l != null) { + var i = l.indexOf(value); + if (i >= 0) { + delete( l[i] ); + l.splice( i, 1 ); + return true; + } + } + return false; + }; + /** + * Sets whether or not the given element(s) should be draggable, regardless of what a particular + * plumb command may request. + * + * @param element May be a string, a jQuery elements, or a list of strings/jquery elements. + * @param draggable Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + var _helper = function(el, id) { + draggableStates[id] = draggable; + if (el.draggable) { + el.draggable("option", "disabled", !draggable); + } + }; + + return _elementProxy(element, _helper); + }; + /** + * private method to do the business of hiding/showing. + * @param elId Id of the element in question + * @param state String specifying a value for the css 'display' property ('block' or 'none'). + */ + var _setVisible = function(elId, state) { + var f = function(jpc) { + //todo should we find all the endpoints instead of going by connection? this will + jpc.canvas.style.display = state; + /*jpc.sourceEndpointCanvas.style.display = state; + jpc.targetEndpointCanvas.style.display = state;*/ + }; + + _operation(elId, f); + }; + /** + * toggles the draggable state of the element with the given id. + */ + var _toggleDraggable = function(el) { + var fn = function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + el.draggable("option", "disabled", !state); + return state; + }; + return _elementProxy(el, fn); + }; + /** + * private method to do the business of toggling hiding/showing. + * @param elId Id of the element in question + */ + var _toggleVisible = function(elId) { + var f = function(jpc) {; + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + /*jpc.sourceEndpointCanvas.style.display = state; + jpc.targetEndpointCanvas.style.display = state;*/ + }; + + _operation(elId, f); + }; + /** + * updates the offset and size for a given element, and stores the values. + * if 'ui' is not null we use that (it would have been passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we use the offset, outerWidth and outerHeight methods to get + * the current values. + */ + var _updateOffset = function(elId, ui, recalc) { + + if (log) log.debug("updating offset for element [" + elId + "]; ui is [" + ui + "]; recalc is [" + recalc + "]"); + + if (recalc || ui == null) { // if forced repaint or no ui helper available, we recalculate. + // get the current size and offset, and store them + var s = $("#" + elId); + sizes[elId] = [s.outerWidth(), s.outerHeight()]; + offsets[elId] = s.offset(); + } else { + // faster to use the ui element if it was passed in. + // fix for change in 1.8 (absolutePosition renamed to offset). plugin is compatible with + // 1.8 and 1.7. + + // todo: when the drag axis is supplied, the ui object passed in has incorrect values + // for the other axis - like say you set axis='x'. when you move the mouse up and down + // while dragging, the y values are for where the window would be if it was not + // just constrained to x. not sure if this is a jquery bug or whether there's a known + // trick or whatever. + var pos = ui.absolutePosition || ui.offset; + var anOffset = ui != null ? pos : $("#" + elId).offset(); + offsets[elId] = anOffset; + } + }; + /** + * wraps one function with another, creating a placeholder for the wrapped function + * if it was null. this is used to wrap the various drag/drop event functions - to allow + * jsPlumb to be notified of important lifecycle events without imposing itself on the user's + * drap/drop functionality. + * TODO: determine whether or not we should try/catch the plumb function, so that the cascade function is always executed. + */ + var _wrap = function(cascadeFunction, plumbFunction) { + cascadeFunction = cascadeFunction || function(e, ui) { }; + return function(e, ui) { + plumbFunction(e, ui); + cascadeFunction(e, ui); + }; + } + /** + * Anchor class. Anchors can be situated anywhere. + * params should contain three values, and may optionally have an 'offsets' argument: + * + * x : the x location of the anchor as a fraction of the total width. + * + * y : the y location of the anchor as a fraction of the total height. + * + * orientation : an [x,y] array indicating the general direction a connection + * from the anchor should go in. for more info on this, see the documentation, + * or the docs in jquery-jsPlumb-defaults-XXX.js for the default Anchors. + * + * offsets : an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. may be null. + * + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; this.y = params.y || 0; + var orientation = params.orientation || [0,0]; + this.offsets = params.offsets || [0,0]; + this.compute = function(xy, wh, txy, twh) { + return [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + } + this.getOrientation = function() { return orientation; }; + }; + + /** + * an anchor that floats. its orientation is computed dynamically from its position relative + * to the anchor it is floating relative to. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for purposes of calculating the orientation. + var ref = params.reference; + // these are used to store the current relative position of our anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating anchor is hovering over another anchor. + var orientation = null; + + this.compute = function(xy, wh, txy, twh) { + // set these for the getOrientation method to use. + xDir = 0;//xy[0] < txy[0] ? -1 : xy[0] == txy[0] ? 0 : 1; + yDir = 0;//xy[1] < txy[1] ? -1 : xy[1] == txy[1] ? 0 : 1; + return [xy[0], xy[1]]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other anchor: if it declares zero for some direction, we declare zero too. + // this might not be the most awesome. perhaps we can come up with a better way. it's just so that the line we draw looks + // like it makes sense. maybe this wont make sense. + return [Math.abs(o[0]) * xDir * -1, Math.abs(o[1]) * yDir * -1]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering over another anchor; + * we want to assume that anchor's orientation for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no longer hovering + * over another anchor; we should resume calculating orientation as we normally do. + */ + this.out = function() { + orientation = null; + }; + }; + + // ************** connection + // **************************************** + /** + * allowed params: + * source: source element (string or a jQuery element) (required) + * target: target element (string or a jQuery element) (required) + * + * anchors: optional array of anchor placements. defaults to BottomCenter for source + * and TopCenter for target. + */ + var Connection = function(params) { + + // ************** get the source and target and register the connection. ******************* + var self = this; + // get source and target as jQuery objects + this.source = (typeof params.source == 'string') ? $("#" + params.source) : params.source; + this.target = (typeof params.target == 'string') ? $("#" + params.target) : params.target; + this.sourceId = $(this.source).attr("id"); + this.targetId = $(this.target).attr("id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + + // init endpoints + this.endpoints = []; + this.endpointStyles = []; + var prepareEndpoint = function(existing, index, params) { + if (existing) self.endpoints[index] = existing; + else { + if(!params.endpoints) params.endpoints = [null,null]; + var ep = params.endpoints[index] || params.endpoint || jsPlumb.Defaults.Endpoints[index] || jsPlumb.Defaults.Endpoint|| new jsPlumb.Endpoints.Dot(); + if (!params.endpointStyles) params.endpointStyles = [null,null]; + var es = params.endpointStyles[index] || params.endpointStyle || jsPlumb.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyle; + var a = params.anchors ? params.anchors[index] : jsPlumb.Defaults.Anchors[index] || jsPlumb.Anchors.BottomCenter; + self.endpoints[index] = new Endpoint({style:es, endpoint:ep, connections:[self], anchor:a }); + } + }; + + prepareEndpoint(params.sourceEndpoint, 0, params); + prepareEndpoint(params.targetEndpoint, 1, params); + + // make connector. if an endpoint has a connector + paintstyle to use, we use that. + // otherwise we use sensible defaults. + //this.connector = params.connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + //this.paintStyle = params.paintStyle || jsPlumb.Defaults.PaintStyle; + this.paintStyle = this.endpoints[0].connectionStyle || this.endpoints[1].connectionStyle || params.paintStyle || jsPlumb.Defaults.PaintStyle; + + _updateOffset(this.sourceId); + _updateOffset(this.targetId); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var anchorLoc = this.endpoints[0].anchor.compute([myOffset.left, myOffset.top], myWH); + this.endpoints[0].paint(anchorLoc); + myOffset = offsets[this.targetId]; myWH = sizes[this.targetId]; + anchorLoc = this.endpoints[1].anchor.compute([myOffset.left, myOffset.top], myWH); + this.endpoints[1].paint(anchorLoc); + + // *************** create canvas on which the connection will be drawn ************ + var canvas = _newCanvas(jsPlumb.connectorClass); + this.canvas = canvas; + + /** + * paints the connection. + * @param elId Id of the element that is in motion + * @param ui jQuery's event system ui object (present if we came from a drag to get here) + * @param recalc whether or not to recalculate element sizes. this is true if a repaint caused this to be painted. + */ + this.paint = function(elId, ui, recalc) { + + if (log) log.debug("Painting Connection; element in motion is " + elId + "; ui is [" + ui + "]; recalc is [" + recalc + "]"); + + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = !(elId == this.sourceId); + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + if (this.canvas.getContext) { + + _updateOffset(elId, ui, recalc); + if (recalc) _updateOffset(tId); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var myOffset = offsets[elId]; + var otherOffset = offsets[tId]; + var myWH = sizes[elId]; + var otherWH = sizes[tId]; + + var ctx = canvas.getContext('2d'); + var sAnchorP = this.endpoints[sIdx].anchor.compute([myOffset.left, myOffset.top], myWH, [otherOffset.left, otherOffset.top], otherWH); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.compute([otherOffset.left, otherOffset.top], otherWH, [myOffset.left, myOffset.top], myWH); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, this.paintStyle.lineWidth); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + $.extend(ctx, this.paintStyle); + + if (this.paintStyle.gradient && !ie) { + var g = swap ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + for (var i = 0; i < this.paintStyle.gradient.stops.length; i++) + g.addColorStop(this.paintStyle.gradient.stops[i][0],this.paintStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + + this.connector.paint(dim, ctx); + + // this.endpoints[swap ? 1 : 0].paint(sAnchorP, this.paintStyle); + //this.endpoints[swap ? 0 : 1].paint(tAnchorP, this.paintStyle); + } + }; + + this.repaint = function() { + this.paint(this.sourceId, null, true); + }; + + _initDraggableIfNecessary(self.source, self.sourceId, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, self.targetId, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + }; + + /** + * models an endpoint. can have one to N connections emanating from it (although how to handle that in the UI is + * a very good question). also has a Canvas and paint style. + * + * params: + * + * anchor : anchor for the endpoint, of type jsPlumb.Anchor. may be null. + * endpoint : endpoint object, of type jsPlumb.Endpoint. may be null. + * style : endpoint style, a js object. may be null. + * source : element the endpoint is attached to, of type jquery object. Required. + * canvas : canvas element to use. may be, and most often is, null. + * connections : optional list of connections to configure the endpoint with. + * isSource : boolean. indicates the endpoint can act as a source of new connections. optional. + * dragOptions : if isSource is set to true, you can supply arguments for the jquery draggable method. optional. + * connectionStyle : if isSource is set to true, this is the paint style for connections from this endpoint. optional. + * connector : optional connector type to use. + * isTarget : boolean. indicates the endpoint can act as a target of new connections. optional. + * dropOptions : if isTarget is set to true, you can supply arguments for the jquery droppable method. optional. + * reattach : optional boolean that determines whether or not the connections reattach after they + * have been dragged off an endpoint and left floating. defaults to false - connections + * dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + params = params || {}; + // make a copy. then we can use the wrapper function. + params = $.extend({}, params); + var self = this; + self.anchor = params.anchor || jsPlumb.Anchors.TopCenter; + var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot(); + var _style = params.style || jsPlumb.Defaults.EndpointStyle; + this.connectionStyle = params.connectionStyle; + this.connector = params.connector; + var _element = params.source; + var _elementId = $(_element).attr("id"); + var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of. + this.canvas = params.canvas || _newCanvas(jsPlumb.endpointClass); + this.connections = params.connections || []; + var _reattach = params.reattach || false; + var floatingEndpoint = null; + this.addConnection = function(connection) { + self.connections.push(connection); + }; + this.removeConnection = function(connection) { + var idx = self.connections.indexOf(connection); + if (idx >= 0) + self.connections.splice(idx, 1); + }; + this.isFloating = function() { return floatingEndpoint != null; }; + /** + * first pass at default ConnectorSelector: returns the first connection, if we have any. + * modified a little, 5/10/2010: now it only returns a connector if we have not got equal to or more than _maxConnector connectors + * attached. otherwise it is assumed a new connector is ok. but note with this setup we can't select any other connection than the first + * one. what if this could return a list? that implies everything should work with a list - dragging etc. could be nasty. could also + * be cool. + */ + var connectorSelector = function() { + return self.connections.length == 0 || self.connections.length < _maxConnections ? null : self.connections[0]; + }; + + // get the jsplumb context...lookups are faster with a context. + var contextNode = _getContextNode(); + + this.isFull = function() { return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); }; + + /** + * paints the Endpoint, recalculating offset and anchor positions if necessary. + */ + this.paint = function(anchorPoint, connectorPaintStyle, canvas) { + + if (log) log.debug("Painting Endpoint with elementId [" + _elementId + "]; anchorPoint is [" + anchorPoint + "]"); + + if (anchorPoint == null) { + // do we always want to force a repaint here? i dont think so! + var xy = offsets[_elementId]; + var wh = sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset(_elementId); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + anchorPoint = self.anchor.compute([xy.left, xy.top], wh); + } + _endpoint.paint(anchorPoint, self.anchor.getOrientation(), canvas || self.canvas, _style, connectorPaintStyle || _style); + }; + + + // is this a connection source? we make it draggable and have the drag listener + // maintain a connection with a floating endpoint. + if (params.isSource && _element.draggable) { + + var n = null, id = null, + jpc = null, + existingJpc = false, existingJpcParams = null; + + // first the question is, how many connections are on this endpoint? if it's only one, then excellent. otherwise we will either need a way + // to select one connection from the list, or drag them all. if we had a pluggable 'ConnectorSelector' interface we could probably + // provide a way for people to implement their own UI components to do the connector selection. the question in that particular case would be how much + // information the interface needs from jsPlumb at execution time. if, however, we leave out the connector selection, and drag them all, + // that wouldn't be too hard to organise. perhaps that behaviour would be on a switch for the endpoint, or perhaps the ConnectorSelector + // interface returns a List, with the default implementation just returning everything. i think i like that. + // + // let's say for now that there is just one endpoint, cos we need to get this going before we can consider a list of them anyway. + // the major difference between that case and the case of a new endpoint is actually quite small - it's a question of where the + // Connection comes from. for a new one, we create a new one. obviously. otherwise we just get the jpc from the Endpoint + // (remember we're only assuming one connection right now). so all of the UI stuff we do to create the floating endpoint etc + // will still be valid, but when we stop dragging, we'll have to do something different. if we stop with a valid drop i think it will + // be the same process. but if we stop with an invalid drop we have to reset the Connection to how it was when we got it. + var start = function(e, ui) { + //if (!isFull()) { + n = document.createElement("div"); + contextNode.append(n); + // create and assign an id, and initialize the offset. + id = new String(new Date().getTime()); + $(n, contextNode).attr("id", id); + _updateOffset(id); + // store the id of the dragging div and the source element. the drop function + // will pick these up. + $(self.canvas, contextNode).attr("dragId", id); + $(self.canvas, contextNode).attr("elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor({reference:self.anchor}); + floatingEndpoint = new Endpoint({ + style:_style, + endpoint:_endpoint, + anchor:floatingAnchor, + source:n + }); + //floatingEndpoint.originalAnchor = + + jpc = connectorSelector(); + if (jpc == null) { + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = new Connection({ + sourceEndpoint:self, + targetEndpoint:floatingEndpoint, + source:$(_element), + target:$(n, contextNode), + anchors:[self.anchor, floatingAnchor], + paintStyle : params.connectionStyle, // this can be null. Connection will use the default. + connector: params.connector + }); + // todo ...unregister on stop + self.addConnection(jpc); + } else { + existingJpc = true; + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; + // probably we should remove the connection? and add it back if the user + // does not drop it somewhere proper. + self.removeConnection(jpc); + if (anchorIdx == 0){ + existingJpcParams = [jpc.source, jpc.sourceId]; + jpc.source = $(n, contextNode); + jpc.sourceId = id; + }else { + existingJpcParams = [jpc.target, jpc.targetId]; + jpc.target = $(n, contextNode); + jpc.targetId = id; + } + + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // todo unregister on stop + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + } + //}; + + var dragOptions = params.dragOptions || { }; + dragOptions = $.extend({ opacity:0.5, revert:true, helper:'clone' }, dragOptions); + + dragOptions.start = _wrap(dragOptions.start, start); + dragOptions.drag = _wrap(dragOptions.drag, function(e, ui) { + _draw($(n, contextNode), ui); + }); + dragOptions.stop = _wrap(dragOptions.stop, + function(e, ui) { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements([floatingEndpoint.canvas, n]); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (jpc.endpoints[idx] == floatingEndpoint) { + + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + if (_reattach) { + jpc.floatingAnchorIndex = null; + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } + else { + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeElement(jpc.canvas); + } + } else { + _removeElement(jpc.canvas); + self.removeConnection(jpc); + } + } + jpc = null; + delete floatingEndpoint; + } + ); + + $(self.canvas, contextNode).draggable(dragOptions); + } + + // connector target + if (params.isTarget && _element.droppable) { + var dropOptions = params.dropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = $.extend({}, dropOptions); + var originalAnchor = null; + dropOptions.drop = _wrap(dropOptions.drop, function(e, ui) { + var id = $(ui.draggable, contextNode).attr("dragId"); + var elId = $(ui.draggable, contextNode).attr("elId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // remove this jpc from the current endpoint + jpc.endpoints[idx].removeConnection(jpc); + if (jpc.suspendedEndpoint) + jpc.suspendedEndpoint.removeConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + _initDraggableIfNecessary(_element, _elementId, params.draggable, {}); + jsPlumb.repaint($(ui.draggable, contextNode).attr("elId")); + delete floatingConnections[id]; + }); + // what to do when something is dropped. + // 1. find the jpc that is being dragged. the target endpoint of the jpc will be the + // one that is being dragged. + // 2. arrange for the floating endpoint to be replaced with this endpoint; make sure + // everything gets registered ok etc. + // 3. arrange for the floating endpoint to be deleted. + // 4. make sure that the stop method of the drag does not cause the jpc to be cleaned up. we want to keep it now. + + // other considerations: when in the hover mode, we should switch the floating endpoint's + // orientation to be the same as the drop target. this will cause the connector to snap + // into the shape it will take if the user drops at that point. + + dropOptions.over = _wrap(dropOptions.over, function(event, ui) { + var id = $(ui.draggable, contextNode).attr("dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + }); + + dropOptions.out = _wrap(dropOptions.out, function(event, ui) { + var id = $(ui.draggable, contextNode).attr("dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + }); + + $(self.canvas, contextNode).droppable(dropOptions); + } + + // woo...add a plumb command to Endpoint. + this.plumb = function(params) { + // not much to think about. the target should be an Endpoint, but what else? + // all the style stuff is on the endpoint itself already. + //todo this should call the main plumb method, just with some different args. + + }; + + return self; + }; + + /** + * jsPlumb public API + */ + var jsPlumb = window.jsPlumb = { + + Defaults : { + Anchors : [ null, null ], + Connector : null, + DragOptions: { }, + DropOptions: { }, + Endpoint : null, + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : null }, + EndpointStyles : [ null, null ], + MaxConnections : null, + PaintStyle : { lineWidth : 10, strokeStyle : 'red' } + }, + + connectorClass : '_jsPlumb_connector', + endpointClass : '_jsPlumb_endpoint', + + Anchors : {}, + Connectors : {}, + Endpoints : {}, + + /** + * adds an endpoint to the element + */ + addEndpoint : function(target, params) { + params = $.extend({}, params); + var el = typeof target == 'string' ? $("#" + target) : target; + var id = $(el).attr("id"); + params.source = el; + _updateOffset(id); + var e = new Endpoint(params); + _addToList(endpointsByElement, id, e); + + var myOffset = offsets[id]; + var myWH = sizes[id]; + + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH); + e.paint(anchorLoc); + return e; + }, + + /** + * wrapper around standard jquery animate function; injects a call to jsPlumb in the + * 'step' function (creating it if necessary). this only supports the two-arg version + * of the animate call in jquery - the one that takes an object as the second arg. + */ + animate : function(el, properties, options) { + var ele = typeof(el)=='string' ? $("#" + el) : el; + var id = ele.attr("id"); + options = options || {}; + options.step = _wrap(options.step, function() { jsPlumb.repaint(id); }); + ele.animate(properties, options); + }, + + /** + * adds a list of endpoints to the element + */ + addEndpoints : function(target, endpoints) { + var results = []; + for (var i = 0; i < endpoints.length; i++) { + results.push(jsPlumb.addEndpoint(target, endpoints[i])); + } + return results; + }, + + /** + * establishes a connection between two elements. + * @param params object containing setup for the connection. see documentation. + */ + connect : function(params) { + if (params.sourceEndpoint && params.sourceEndpoint.isFull()) { + _log("could not add connection; source endpoint is full"); + return; + } + + if (params.targetEndpoint && params.targetEndpoint.isFull()) { + _log("could not add connection; target endpoint is full"); + return; + } + + var jpc = new Connection(params); + + // register endpoints for the element + _addToList(endpointsByElement, jpc.sourceId, jpc.endpoints[0]); + _addToList(endpointsByElement, jpc.targetId, jpc.endpoints[1]); + + jpc.endpoints[0].addConnection(jpc); + jpc.endpoints[1].addConnection(jpc); + + // force a paint + _draw(jpc.source); + + }, + + connectEndpoints : function(params) { + var jpc = Connection(params); + + }, + + /** + * Remove one connection to an element. + * @param sourceId id of the first window in the connection + * @param targetId id of the second window in the connection + * @return true if successful, false if not. + */ + detach : function(sourceId, targetId) { + var f = function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas); + /*_removeElement(jpc.targetEndpointCanvas); + _removeElement(jpc.sourceEndpointCanvas);*/ + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + return true; + } + }; + + // todo: how to cleanup the actual storage? a third arg to _operation? + _operation(sourceId, f); + }, + + /** + * remove all an element's connections. + * @param elId id of the + */ + detachAll : function(elId) { + + var f = function(jpc) { + // todo replace with _cleanupConnection call here. + _removeElement(jpc.canvas); + /*_removeElement(jpc.targetEndpointCanvas); + _removeElement(jpc.sourceEndpointCanvas);*/ + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + }; + _operation(elId, f); + //delete endpointsByElement[elId]; + }, + + /** + * remove all connections. and endpoints? probably not. + */ + detachEverything : function() { + var f = function(jpc) { + _removeElement(jpc.canvas); + /*_removeElement(jpc.targetEndpointCanvas); + _removeElement(jpc.sourceEndpointCanvas);*/ + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + }; + + _operationOnAll(f); + + /*delete endpointsByElement; + endpointsByElement = {};*/ + }, + + /** + * Set an element's connections to be hidden. + */ + hide : function(elId) { + _setVisible(elId, "none"); + }, + + /** + * Creates an anchor with the given params. + * x - the x location of the anchor as a fraction of the total width. + * y - the y location of the anchor as a fraction of the total height. + * orientation - an [x,y] array indicating the general direction a connection from the anchor should go in. + * offsets - an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. optional. defaults to [0,0]. + */ + makeAnchor : function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards compatibility if we are given only one value we assume it's a call in the old form. + var params = {}; + if (arguments.length == 1) $.extend(params, x); + else { + params = {x:x, y:y}; + if (arguments.length >= 4) { + params.orientation = [arguments[2], arguments[3]]; + } + if (arguments.length == 6) params.offsets = [arguments[4], arguments[5]]; + } + return new Anchor(params); + }, + + + /** + * repaint element and its connections. element may be an id or the actual jQuery object. + * this method gets new sizes for the elements before painting anything. + */ + repaint : function(el) { + + var _processElement = function(el) { + var ele = typeof(el)=='string' ? $("#" + el) : el; + _draw(ele); + }; + + // TODO: support a jQuery result object too! + + // support both lists... + if (typeof el =='object') { + for (var i = 0; i < el.length; i++) + _processElement(el[i]); + } // ...and single strings. + else _processElement(el); + }, + + /** + * repaint all connections. + */ + repaintEverything : function() { + for (var elId in endpointsByElement) { + _draw($("#" + elId)); + } + }, + + removeEndpoint : function(elId, endpoint) { + var ebe = endpointsByElement[elId]; + if (ebe) { + // var i = ebe.indexOf(endpoint); + // alert('element has ' + ebe.length); + //if (i > -1) { + //ebe.splice(i, 1); + if(_removeFromList(endpointsByElement, elId, endpoint)) + _removeElement(endpoint.canvas); + //} + } + }, + + /** + * sets/unsets automatic repaint on window resize. + */ + setAutomaticRepaint : function(value) { + automaticRepaint = value; + }, + + /** + * Sets the default size jsPlumb will use for a new canvas (we create a square canvas so + * one value is all that is required). This is a hack for IE, because ExplorerCanvas seems + * to need for a canvas to be larger than what you are going to draw on it at initialisation + * time. The default value of this is 1200 pixels, which is quite large, but if for some + * reason you're drawing connectors that are bigger, you should adjust this value appropriately. + */ + setDefaultNewCanvasSize : function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }, + + /** + * Sets whether or not a given element is draggable, regardless of what any plumb command + * may request. + */ + setDraggable: _setDraggable, + + /** + * Sets whether or not elements are draggable by default. Default for this is true. + */ + setDraggableByDefault: function(draggable) { + _draggableByDefault = draggable; + }, + + setDebugLog: function(debugLog) { + log = debugLog; + }, + + /** + * Sets the function to fire when the window size has changed and a repaint was fired. + */ + setRepaintFunction : function(f) { + repaintFunction = f; + }, + + /** + * Set an element's connections to be visible. + */ + show : function(elId) { + _setVisible(elId, "block"); + }, + + /** + * helper to size a canvas. + */ + sizeCanvas : function(canvas, x, y, w, h) { + canvas.style.height = h + "px"; canvas.height = h; + canvas.style.width = w + "px"; canvas.width = w; + canvas.style.left = x + "px"; canvas.style.top = y + "px"; + }, + + /** + * gets some test hooks. nothing writable. + */ + getTestHarness : function() { + return { + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + } + }; + }, + + /** + * Toggles visibility of an element's connections. kept for backwards compatibility + */ + toggle : _toggleVisible, + + /** + * new name for the old toggle method. + */ + toggleVisible : _toggleVisible, + + /** + * Toggles draggability (sic) of an element. + */ + toggleDraggable : _toggleDraggable, + + /** + * Unloads jsPlumb, deleting all storage. You should call this + */ + unload : function() { + delete endpointsByElement; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + document.body.removeChild(_jsPlumbContextNode); + } + }; + +})(); + +// jQuery plugin code +(function($){ + /** + * plumbs the results of the selector to some target, using the given options if supplied, + * or the defaults otherwise. + */ + $.fn.plumb = function(options) { + var options = $.extend({}, options); + + return this.each(function() + { + var params = $.extend({source:$(this)}, options); + jsPlumb.connect(params); + }); + }; + + /** + * detaches the results of the selector from the given target or list of targets - 'target' + * may be a String or a List. + */ + $.fn.detach = function(target) { + return this.each(function() + { + var id = $(this).attr("id"); + if (typeof target == 'string') target = [target]; + for (var i = 0; i < target.length; i++) + jsPlumb.detach(id, target[i]); + }); + }; + + /** + * detaches the results from the selector from all connections. + */ + $.fn.detachAll = function() { + return this.each(function() + { + var id = $(this).attr("id"); + jsPlumb.detachAll(id); + }); + }; + + /** + * adds an endpoint to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoint = function(options) { + var addedEndpoints = []; + this.each(function() + { + //var params = $.extend({source:$(this)}, options); + addedEndpoints.push(jsPlumb.addEndpoint($(this).attr("id"), options)); + }); + return addedEndpoints; + }; + + /** + * adds a list of endpoints to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoints = function(endpoints) { + var addedEndpoints = []; + return this.each(function() + { + var e = jsPlumb.addEndpoints($(this).attr("id"), endpoints); + for (var i = 0; i < e.length; i++) addedEndpoints.push(e[i]); + }); + return addedEndpoints; + }; + + /** + * remove the endpoint, if it exists, deleting its UI elements etc. + */ + $.fn.removeEndpoint = function(endpoint) { + this.each(function() + { + jsPlumb.removeEndpoint($(this).attr("id"), endpoint); + }); + }; + +})(jQuery); diff --git a/archive/1.1.0/jquery.jsPlumb-all-1.1.0-min.js b/archive/1.1.0/jquery.jsPlumb-all-1.1.0-min.js new file mode 100644 index 000000000..03fc09a7e --- /dev/null +++ b/archive/1.1.0/jquery.jsPlumb-all-1.1.0-min.js @@ -0,0 +1,35 @@ +if(!Array.prototype.indexOf)Array.prototype.indexOf=function(j,h,d){h=+h||0;for(var e=this.length;h=0){delete a[c];a.splice(c,1);return true}}return false},X=function(a,b){N(a,function(c){c.canvas.style.display=b})},Y=function(a){N(a,function(b){b.canvas.style.display="none"==b.canvas.style.display?"block":"none"})},I=function(a,b,c){d&&d.debug("updating offset for element ["+a+"]; ui is ["+ +b+"]; recalc is ["+c+"]");if(c||b==null){b=$("#"+a);z[a]=[b.outerWidth(),b.outerHeight()];k[a]=b.offset()}else{c=b.absolutePosition||b.offset;b=b!=null?c:$("#"+a).offset();k[a]=b}},K=function(a,b){a=a||function(){};return function(c,g){b(c,g);a(c,g)}},aa=function(a){var b=this;this.x=a.x||0;this.y=a.y||0;var c=a.orientation||[0,0];this.offsets=a.offsets||[0,0];this.compute=function(g,n){return[g[0]+b.x*n[0]+b.offsets[0],g[1]+b.y*n[1]+b.offsets[1]]};this.getOrientation=function(){return c}},ba=function(a){var b= +a.reference,c=0,g=0,n=null;this.compute=function(o){g=c=0;return[o[0],o[1]]};this.getOrientation=function(){if(n)return n;else{var o=b.getOrientation();return[Math.abs(o[0])*c*-1,Math.abs(o[1])*g*-1]}};this.over=function(o){n=o.getOrientation()};this.out=function(){n=null}},R=function(a){var b=this;this.source=typeof a.source=="string"?$("#"+a.source):a.source;this.target=typeof a.target=="string"?$("#"+a.target):a.target;this.sourceId=$(this.source).attr("id");this.targetId=$(this.target).attr("id"); +this.endpointsOnTop=a.endpointsOnTop!=null?a.endpointsOnTop:true;this.endpoints=[];this.endpointStyles=[];var c=function(o,t,r){if(o)b.endpoints[t]=o;else{if(!r.endpoints)r.endpoints=[null,null];o=r.endpoints[t]||r.endpoint||w.Defaults.Endpoints[t]||w.Defaults.Endpoint||new w.Endpoints.Dot;if(!r.endpointStyles)r.endpointStyles=[null,null];b.endpoints[t]=new Q({style:r.endpointStyles[t]||r.endpointStyle||w.Defaults.EndpointStyles[t]||w.Defaults.EndpointStyle,endpoint:o,connections:[b],anchor:r.anchors? +r.anchors[t]:w.Defaults.Anchors[t]||w.Anchors.BottomCenter})}};c(a.sourceEndpoint,0,a);c(a.targetEndpoint,1,a);this.connector=this.endpoints[0].connector||this.endpoints[1].connector||a.connector||w.Defaults.Connector||new w.Connectors.Bezier;this.paintStyle=this.endpoints[0].connectionStyle||this.endpoints[1].connectionStyle||a.paintStyle||w.Defaults.PaintStyle;I(this.sourceId);I(this.targetId);c=k[this.sourceId];var g=z[this.sourceId];c=this.endpoints[0].anchor.compute([c.left,c.top],g);this.endpoints[0].paint(c); +c=k[this.targetId];g=z[this.targetId];c=this.endpoints[1].anchor.compute([c.left,c.top],g);this.endpoints[1].paint(c);var n=V(w.connectorClass);this.canvas=n;this.paint=function(o,t,r){d&&d.debug("Painting Connection; element in motion is "+o+"; ui is ["+t+"]; recalc is ["+r+"]");var u=o!=this.sourceId,x=u?this.sourceId:this.targetId,v=u?0:1,B=u?1:0;if(this.canvas.getContext){I(o,t,r);r&&I(x);t=k[o];r=k[x];o=z[o];var m=z[x];x=n.getContext("2d");var O=this.endpoints[B].anchor.compute([t.left,t.top], +o,[r.left,r.top],m);this.endpoints[B].anchor.getOrientation();t=this.endpoints[v].anchor.compute([r.left,r.top],m,[t.left,t.top],o);this.endpoints[v].anchor.getOrientation();v=this.connector.compute(O,t,this.endpoints[B].anchor,this.endpoints[v].anchor,this.paintStyle.lineWidth);w.sizeCanvas(n,v[0],v[1],v[2],v[3]);$.extend(x,this.paintStyle);if(this.paintStyle.gradient&&!h){u=u?x.createLinearGradient(v[4],v[5],v[6],v[7]):x.createLinearGradient(v[6],v[7],v[4],v[5]);for(B=0;B=0&&b.connections.splice(s,1)};this.isFloating=function(){return u!=null};var x=U();this.isFull=function(){return t<1?false:b.connections.length>=t};this.paint=function(s,G,D){d&&d.debug("Painting Endpoint with elementId ["+ +o+"]; anchorPoint is ["+s+"]");if(s==null){s=k[o];var C=z[o];if(s==null||C==null){I(o);s=k[o];C=z[o]}s=b.anchor.compute([s.left,s.top],C)}c.paint(s,b.anchor.getOrientation(),D||b.canvas,g,G||g)};if(a.isSource&&n.draggable){var v=null,B=null,m=null,O=false,L=null,A=a.dragOptions||{};A=$.extend({opacity:0.5,revert:true,helper:"clone"},A);A.start=K(A.start,function(){v=document.createElement("div");x.append(v);B=new String((new Date).getTime());$(v,x).attr("id",B);I(B);$(b.canvas,x).attr("dragId",B); +$(b.canvas,x).attr("elId",o);var s=new ba({reference:b.anchor});u=new Q({style:g,endpoint:c,anchor:s,source:v});m=b.connections.length==0||b.connections.length=4)c.orientation=[arguments[2],arguments[3]];if(arguments.length==6)c.offsets=[arguments[4],arguments[5]]}return new aa(c)},repaint:function(a){var b=function(g){g=typeof g=="string"?$("#"+g):g;M(g)};if(typeof a=="object")for(var c=0;c< +a.length;c++)b(a[c]);else b(a)},repaintEverything:function(){for(var a in f)M($("#"+a))},removeEndpoint:function(a,b){f[a]&&W(f,a,b)&&J(b.canvas)},setAutomaticRepaint:function(a){l=a},setDefaultNewCanvasSize:function(a){H=a},setDraggable:function(a,b){return T(a,function(c,g){q[g]=b;c.draggable&&c.draggable("option","disabled",!b)})},setDraggableByDefault:function(a){y=a},setDebugLog:function(a){d=a},setRepaintFunction:function(a){e=a},show:function(a){X(a,"block")},sizeCanvas:function(a,b,c,g,n){a.style.height= +n+"px";a.height=n;a.style.width=g+"px";a.width=g;a.style.left=b+"px";a.style.top=c+"px"},getTestHarness:function(){return{endpointCount:function(a){return(a=f[a])?a.length:0}}},toggle:Y,toggleVisible:Y,toggleDraggable:function(a){return T(a,function(b,c){var g=q[c]==null?y:q[c];g=!g;q[c]=g;b.draggable("option","disabled",!g);return g})},unload:function(){delete f;delete k;delete z;delete p;delete q;document.body.removeChild(E)}}})(); +(function(j){j.fn.plumb=function(h){h=j.extend({},h);return this.each(function(){var d=j.extend({source:j(this)},h);jsPlumb.connect(d)})};j.fn.detach=function(h){return this.each(function(){var d=j(this).attr("id");if(typeof h=="string")h=[h];for(var e=0;ek)k=l;if(e<0){q+=e;e=Math.abs(e);k+=e;F[0]+=e;z+=e;H+=e;d[0]+=e}e=Math.min(Math.min(E,f),Math.min(F[1], +d[1]));l=Math.max(Math.max(E,f),Math.max(F[1],d[1]));if(l>p)p=l;if(e<0){y+=e;e=Math.abs(e);p+=e;F[1]+=e;E+=e;f+=e;d[1]+=e}return[q,y,k,p,z,E,H,f,F[0],F[1],d[0],d[1]]};this.paint=function(d,e){e.beginPath();e.moveTo(d[4],d[5]);e.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]);e.stroke()}};jsPlumb.Endpoints.Dot=function(j){j=j||{radius:10};var h=this;this.radius=j.radius;var d=0.5*this.radius,e=this.radius/3,l=function(i){try{return parseInt(i)}catch(f){if(i.substring(i.length-1)=="%")return parseInt(i.substring(0, +i-1))}};this.paint=function(i,f,k,p,q){var y=p.radius||h.radius;jsPlumb.sizeCanvas(k,i[0]-y,i[1]-y,y*2,y*2);i=k.getContext("2d");k=$.extend({},p);if(k.fillStyle==null)k.fillStyle=q.strokeStyle;$.extend(i,k);q=/MSIE/.test(navigator.userAgent)&&!window.opera;if(p.gradient&&!q){q=p.gradient;k=d;var z=e;if(q.offset)k=l(q.offset);if(q.innerRadius)z=l(q.innerRadius);q=[k,z];f=i.createRadialGradient(y,y,y,y+(f[0]==1?q[0]*-1:q[0]),y+(f[1]==1?q[0]*-1:q[0]),q[1]);for(q=0;q endpoint lists. an element can have an arbitrary number of endpoints on it, + * and not all of them have to be connected to anything. + */ + var endpointsByElement = {}; + var offsets = []; + var floatingConnections = {}; + var draggableStates = {}; + var _draggableByDefault = true; + var sizes = []; + var _jsPlumbContextNode = null; + + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + /** + * helper method to add an item to a list, creating the list if it does not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + }; + + /** + * Handles the dragging of an element. + * @param element jQuery element + * @param ui UI object from jQuery's event system + */ + var _draw = function(element, ui) { + var id = $(element).attr("id"); + var endpoints = endpointsByElement[id]; + if (endpoints) { + //if (ui == null) _updateOffset(id, ui); + _updateOffset(id, ui); + var myOffset = offsets[id]; + var myWH = sizes[id]; + // loop through endpoints for this element + for (var i = 0; i < endpoints.length; i++) { + var e = endpoints[i]; + // first, paint the endpoint + + var anchorLoc = endpoints[i].anchor.compute([myOffset.left, myOffset.top], myWH); + // var anchorOrientation = endpoints[i].anchor.getOrientation(); + + //todo...connector paint style? we have lost that with the move to endpoint-centric. + // perhaps we only paint the endpoint here if it has no connections; it can use its own style. + //if (!e.connections || e.connections.length == 0) + e.paint(anchorLoc); + + // else { + //if (e.connections && e.connections.length > 0) { + // get all connections for the endpoint... + var l = e.connections; + for (var j = 0; j < l.length; j++) + l[j].paint(id, ui); // ...and paint them. + //} + } + } + }; + + /** + * helper function: the second argument is a function taking two args - the first is a + * jquery element, and the second is the element's id. + * + * the first argument may be one of three things: + * + * 1. a string, in the form "window1", for example. an element's id. if your id starts with a + * hash then jsPlumb does not append its own hash too... + * 2. a jquery element, already resolved using $(...). + * 3. a list of strings/jquery elements. + */ + var _elementProxy = function(element, fn) { + var retVal = null; + if (typeof element == 'object' && element.length) { + retVal = []; + for (var i = 0; i < element.length; i++) { + var el = typeof element[i] == 'string' ? $("#" + element[i]) : element[i]; + var id = el.attr("id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } + else { + var el = typeof element == 'string' ? + element.indexOf("#") == 0 ? $(element) : $("#" + element) + : element; + var id = el.attr("id"); + retVal = fn(el, id); + } + + return retVal; + }; + + /** + * Returns (creating if necessary) the DIV element that jsPlumb uses as the context for all of its + * canvases. having this makes it possible to makes calls like $("selector", context), which are + * faster than if you provide no context. also we can clear out everything easily like this, either + * on a detachEverything() call or during unload(). + */ + var _getContextNode = function() { + if (_jsPlumbContextNode == null) { + _jsPlumbContextNode= document.createElement("div"); + document.body.appendChild(_jsPlumbContextNode); + _jsPlumbContextNode.className = "_jsPlumb_context"; + } + return $(_jsPlumbContextNode); + }; + + /** + * gets an id for the given element, creating and setting one if necessary. + */ + var _getId = function(element) { + var id = $(element).attr("id"); + if (!id) { + id = "_jsPlumb_" + new String((new Date()).getTime()); + $(element).attr("id", id); + } + return id; + }; + + /** + * inits a draggable if it's not already initialised. + * todo: if the element was draggable already, like from some non-jsPlumb call, wrap the drag function. + */ + var _initDraggableIfNecessary = function(element, elementId, isDraggable, dragOptions) { + // dragging + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable && element.draggable) { + var options = dragOptions || jsPlumb.Defaults.DragOptions; + var dragCascade = options.drag || function(e,u) {}; + var initDrag = function(element, elementId, dragFunc) { + var opts = $.extend({drag:dragFunc}, options); + var draggable = draggableStates[elementId]; + opts.disabled = draggable == null ? false : !draggable; + element.draggable(opts); + }; + initDrag(element, elementId, function(event, ui) { + _draw(element, ui); + $(element).addClass("jsPlumb_dragged"); + dragCascade(event, ui); + }); + } + + }; + + var _log = function(msg) { + // not implemented. yet. + } + + /** + * helper to create a canvas. + * @param clazz optional class name for the canvas. + */ + var _newCanvas = function(clazz) { + var canvas = document.createElement("canvas"); + _getContextNode().append(canvas); + canvas.style.position="absolute"; + if (clazz) { canvas.className=clazz; } + + if (/MSIE/.test(navigator.userAgent) && !window.opera) { + // for IE we have to set a big canvas size. actually you can override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + /** + * performs the given function operation on all the connections found for the given element + * id; this means we find all the endpoints for the given element, and then for each endpoint + * find the connectors connected to it. then we pass each connection in to the given + * function. + */ + var _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + for (var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }; + /** + * perform an operation on all elements. + */ + var _operationOnAll = function(func) { + for (var elId in endpointsByElement) { + _operation(elId, func); + } + }; + /** + * helper to remove an element from the DOM. + */ + var _removeElement = function(element) { + if (element != null) { + try { _jsPlumbContextNode.removeChild(element); } + catch (e) { } + } + }; + /** + * helper to remove a list of elements from the DOM. + */ + var _removeElements = function(elements) { + for (var i in elements) + _removeElement(elements[i]); + }; + /** + * helper method to remove an item from a list. + */ + var _removeFromList = function(map, key, value) { + var l = map[key]; + if (l != null) { + var i = l.indexOf(value); + if (i >= 0) { + delete( l[i] ); + l.splice( i, 1 ); + return true; + } + } + return false; + }; + /** + * Sets whether or not the given element(s) should be draggable, regardless of what a particular + * plumb command may request. + * + * @param element May be a string, a jQuery elements, or a list of strings/jquery elements. + * @param draggable Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + var _helper = function(el, id) { + draggableStates[id] = draggable; + if (el.draggable) { + el.draggable("option", "disabled", !draggable); + } + }; + + return _elementProxy(element, _helper); + }; + /** + * private method to do the business of hiding/showing. + * @param elId Id of the element in question + * @param state String specifying a value for the css 'display' property ('block' or 'none'). + */ + var _setVisible = function(elId, state) { + var f = function(jpc) { + //todo should we find all the endpoints instead of going by connection? this will + jpc.canvas.style.display = state; + /*jpc.sourceEndpointCanvas.style.display = state; + jpc.targetEndpointCanvas.style.display = state;*/ + }; + + _operation(elId, f); + }; + /** + * toggles the draggable state of the element with the given id. + */ + var _toggleDraggable = function(el) { + var fn = function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + el.draggable("option", "disabled", !state); + return state; + }; + return _elementProxy(el, fn); + }; + /** + * private method to do the business of toggling hiding/showing. + * @param elId Id of the element in question + */ + var _toggleVisible = function(elId) { + var f = function(jpc) {; + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + /*jpc.sourceEndpointCanvas.style.display = state; + jpc.targetEndpointCanvas.style.display = state;*/ + }; + + _operation(elId, f); + }; + /** + * updates the offset and size for a given element, and stores the values. + * if 'ui' is not null we use that (it would have been passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we use the offset, outerWidth and outerHeight methods to get + * the current values. + */ + var _updateOffset = function(elId, ui, recalc) { + + if (log) log.debug("updating offset for element [" + elId + "]; ui is [" + ui + "]; recalc is [" + recalc + "]"); + + if (recalc || ui == null) { // if forced repaint or no ui helper available, we recalculate. + // get the current size and offset, and store them + var s = $("#" + elId); + sizes[elId] = [s.outerWidth(), s.outerHeight()]; + offsets[elId] = s.offset(); + } else { + // faster to use the ui element if it was passed in. + // fix for change in 1.8 (absolutePosition renamed to offset). plugin is compatible with + // 1.8 and 1.7. + + // todo: when the drag axis is supplied, the ui object passed in has incorrect values + // for the other axis - like say you set axis='x'. when you move the mouse up and down + // while dragging, the y values are for where the window would be if it was not + // just constrained to x. not sure if this is a jquery bug or whether there's a known + // trick or whatever. + var pos = ui.absolutePosition || ui.offset; + var anOffset = ui != null ? pos : $("#" + elId).offset(); + offsets[elId] = anOffset; + } + }; + /** + * wraps one function with another, creating a placeholder for the wrapped function + * if it was null. this is used to wrap the various drag/drop event functions - to allow + * jsPlumb to be notified of important lifecycle events without imposing itself on the user's + * drap/drop functionality. + * TODO: determine whether or not we should try/catch the plumb function, so that the cascade function is always executed. + */ + var _wrap = function(cascadeFunction, plumbFunction) { + cascadeFunction = cascadeFunction || function(e, ui) { }; + return function(e, ui) { + plumbFunction(e, ui); + cascadeFunction(e, ui); + }; + } + /** + * Anchor class. Anchors can be situated anywhere. + * params should contain three values, and may optionally have an 'offsets' argument: + * + * x : the x location of the anchor as a fraction of the total width. + * + * y : the y location of the anchor as a fraction of the total height. + * + * orientation : an [x,y] array indicating the general direction a connection + * from the anchor should go in. for more info on this, see the documentation, + * or the docs in jquery-jsPlumb-defaults-XXX.js for the default Anchors. + * + * offsets : an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. may be null. + * + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; this.y = params.y || 0; + var orientation = params.orientation || [0,0]; + this.offsets = params.offsets || [0,0]; + this.compute = function(xy, wh, txy, twh) { + return [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + } + this.getOrientation = function() { return orientation; }; + }; + + /** + * an anchor that floats. its orientation is computed dynamically from its position relative + * to the anchor it is floating relative to. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for purposes of calculating the orientation. + var ref = params.reference; + // these are used to store the current relative position of our anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating anchor is hovering over another anchor. + var orientation = null; + + this.compute = function(xy, wh, txy, twh) { + // set these for the getOrientation method to use. + xDir = 0;//xy[0] < txy[0] ? -1 : xy[0] == txy[0] ? 0 : 1; + yDir = 0;//xy[1] < txy[1] ? -1 : xy[1] == txy[1] ? 0 : 1; + return [xy[0], xy[1]]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other anchor: if it declares zero for some direction, we declare zero too. + // this might not be the most awesome. perhaps we can come up with a better way. it's just so that the line we draw looks + // like it makes sense. maybe this wont make sense. + return [Math.abs(o[0]) * xDir * -1, Math.abs(o[1]) * yDir * -1]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering over another anchor; + * we want to assume that anchor's orientation for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no longer hovering + * over another anchor; we should resume calculating orientation as we normally do. + */ + this.out = function() { + orientation = null; + }; + }; + + // ************** connection + // **************************************** + /** + * allowed params: + * source: source element (string or a jQuery element) (required) + * target: target element (string or a jQuery element) (required) + * + * anchors: optional array of anchor placements. defaults to BottomCenter for source + * and TopCenter for target. + */ + var Connection = function(params) { + + // ************** get the source and target and register the connection. ******************* + var self = this; + // get source and target as jQuery objects + this.source = (typeof params.source == 'string') ? $("#" + params.source) : params.source; + this.target = (typeof params.target == 'string') ? $("#" + params.target) : params.target; + this.sourceId = $(this.source).attr("id"); + this.targetId = $(this.target).attr("id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + + // init endpoints + this.endpoints = []; + this.endpointStyles = []; + var prepareEndpoint = function(existing, index, params) { + if (existing) self.endpoints[index] = existing; + else { + if(!params.endpoints) params.endpoints = [null,null]; + var ep = params.endpoints[index] || params.endpoint || jsPlumb.Defaults.Endpoints[index] || jsPlumb.Defaults.Endpoint|| new jsPlumb.Endpoints.Dot(); + if (!params.endpointStyles) params.endpointStyles = [null,null]; + var es = params.endpointStyles[index] || params.endpointStyle || jsPlumb.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyle; + var a = params.anchors ? params.anchors[index] : jsPlumb.Defaults.Anchors[index] || jsPlumb.Anchors.BottomCenter; + self.endpoints[index] = new Endpoint({style:es, endpoint:ep, connections:[self], anchor:a }); + } + }; + + prepareEndpoint(params.sourceEndpoint, 0, params); + prepareEndpoint(params.targetEndpoint, 1, params); + + // make connector. if an endpoint has a connector + paintstyle to use, we use that. + // otherwise we use sensible defaults. + //this.connector = params.connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + //this.paintStyle = params.paintStyle || jsPlumb.Defaults.PaintStyle; + this.paintStyle = this.endpoints[0].connectionStyle || this.endpoints[1].connectionStyle || params.paintStyle || jsPlumb.Defaults.PaintStyle; + + _updateOffset(this.sourceId); + _updateOffset(this.targetId); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var anchorLoc = this.endpoints[0].anchor.compute([myOffset.left, myOffset.top], myWH); + this.endpoints[0].paint(anchorLoc); + myOffset = offsets[this.targetId]; myWH = sizes[this.targetId]; + anchorLoc = this.endpoints[1].anchor.compute([myOffset.left, myOffset.top], myWH); + this.endpoints[1].paint(anchorLoc); + + // *************** create canvas on which the connection will be drawn ************ + var canvas = _newCanvas(jsPlumb.connectorClass); + this.canvas = canvas; + + /** + * paints the connection. + * @param elId Id of the element that is in motion + * @param ui jQuery's event system ui object (present if we came from a drag to get here) + * @param recalc whether or not to recalculate element sizes. this is true if a repaint caused this to be painted. + */ + this.paint = function(elId, ui, recalc) { + + if (log) log.debug("Painting Connection; element in motion is " + elId + "; ui is [" + ui + "]; recalc is [" + recalc + "]"); + + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = !(elId == this.sourceId); + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + if (this.canvas.getContext) { + + _updateOffset(elId, ui, recalc); + if (recalc) _updateOffset(tId); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var myOffset = offsets[elId]; + var otherOffset = offsets[tId]; + var myWH = sizes[elId]; + var otherWH = sizes[tId]; + + var ctx = canvas.getContext('2d'); + var sAnchorP = this.endpoints[sIdx].anchor.compute([myOffset.left, myOffset.top], myWH, [otherOffset.left, otherOffset.top], otherWH); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.compute([otherOffset.left, otherOffset.top], otherWH, [myOffset.left, myOffset.top], myWH); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, this.paintStyle.lineWidth); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + $.extend(ctx, this.paintStyle); + + if (this.paintStyle.gradient && !ie) { + var g = swap ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + for (var i = 0; i < this.paintStyle.gradient.stops.length; i++) + g.addColorStop(this.paintStyle.gradient.stops[i][0],this.paintStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + + this.connector.paint(dim, ctx); + + // this.endpoints[swap ? 1 : 0].paint(sAnchorP, this.paintStyle); + //this.endpoints[swap ? 0 : 1].paint(tAnchorP, this.paintStyle); + } + }; + + this.repaint = function() { + this.paint(this.sourceId, null, true); + }; + + _initDraggableIfNecessary(self.source, self.sourceId, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, self.targetId, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + }; + + /** + * models an endpoint. can have one to N connections emanating from it (although how to handle that in the UI is + * a very good question). also has a Canvas and paint style. + * + * params: + * + * anchor : anchor for the endpoint, of type jsPlumb.Anchor. may be null. + * endpoint : endpoint object, of type jsPlumb.Endpoint. may be null. + * style : endpoint style, a js object. may be null. + * source : element the endpoint is attached to, of type jquery object. Required. + * canvas : canvas element to use. may be, and most often is, null. + * connections : optional list of connections to configure the endpoint with. + * isSource : boolean. indicates the endpoint can act as a source of new connections. optional. + * dragOptions : if isSource is set to true, you can supply arguments for the jquery draggable method. optional. + * connectionStyle : if isSource is set to true, this is the paint style for connections from this endpoint. optional. + * connector : optional connector type to use. + * isTarget : boolean. indicates the endpoint can act as a target of new connections. optional. + * dropOptions : if isTarget is set to true, you can supply arguments for the jquery droppable method. optional. + * reattach : optional boolean that determines whether or not the connections reattach after they + * have been dragged off an endpoint and left floating. defaults to false - connections + * dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + params = params || {}; + // make a copy. then we can use the wrapper function. + params = $.extend({}, params); + var self = this; + self.anchor = params.anchor || jsPlumb.Anchors.TopCenter; + var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot(); + var _style = params.style || jsPlumb.Defaults.EndpointStyle; + this.connectionStyle = params.connectionStyle; + this.connector = params.connector; + var _element = params.source; + var _elementId = $(_element).attr("id"); + var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of. + this.canvas = params.canvas || _newCanvas(jsPlumb.endpointClass); + this.connections = params.connections || []; + var _reattach = params.reattach || false; + var floatingEndpoint = null; + this.addConnection = function(connection) { + self.connections.push(connection); + }; + this.removeConnection = function(connection) { + var idx = self.connections.indexOf(connection); + if (idx >= 0) + self.connections.splice(idx, 1); + }; + this.isFloating = function() { return floatingEndpoint != null; }; + /** + * first pass at default ConnectorSelector: returns the first connection, if we have any. + * modified a little, 5/10/2010: now it only returns a connector if we have not got equal to or more than _maxConnector connectors + * attached. otherwise it is assumed a new connector is ok. but note with this setup we can't select any other connection than the first + * one. what if this could return a list? that implies everything should work with a list - dragging etc. could be nasty. could also + * be cool. + */ + var connectorSelector = function() { + return self.connections.length == 0 || self.connections.length < _maxConnections ? null : self.connections[0]; + }; + + // get the jsplumb context...lookups are faster with a context. + var contextNode = _getContextNode(); + + this.isFull = function() { return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); }; + + /** + * paints the Endpoint, recalculating offset and anchor positions if necessary. + */ + this.paint = function(anchorPoint, connectorPaintStyle, canvas) { + + if (log) log.debug("Painting Endpoint with elementId [" + _elementId + "]; anchorPoint is [" + anchorPoint + "]"); + + if (anchorPoint == null) { + // do we always want to force a repaint here? i dont think so! + var xy = offsets[_elementId]; + var wh = sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset(_elementId); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + anchorPoint = self.anchor.compute([xy.left, xy.top], wh); + } + _endpoint.paint(anchorPoint, self.anchor.getOrientation(), canvas || self.canvas, _style, connectorPaintStyle || _style); + }; + + + // is this a connection source? we make it draggable and have the drag listener + // maintain a connection with a floating endpoint. + if (params.isSource && _element.draggable) { + + var n = null, id = null, + jpc = null, + existingJpc = false, existingJpcParams = null; + + // first the question is, how many connections are on this endpoint? if it's only one, then excellent. otherwise we will either need a way + // to select one connection from the list, or drag them all. if we had a pluggable 'ConnectorSelector' interface we could probably + // provide a way for people to implement their own UI components to do the connector selection. the question in that particular case would be how much + // information the interface needs from jsPlumb at execution time. if, however, we leave out the connector selection, and drag them all, + // that wouldn't be too hard to organise. perhaps that behaviour would be on a switch for the endpoint, or perhaps the ConnectorSelector + // interface returns a List, with the default implementation just returning everything. i think i like that. + // + // let's say for now that there is just one endpoint, cos we need to get this going before we can consider a list of them anyway. + // the major difference between that case and the case of a new endpoint is actually quite small - it's a question of where the + // Connection comes from. for a new one, we create a new one. obviously. otherwise we just get the jpc from the Endpoint + // (remember we're only assuming one connection right now). so all of the UI stuff we do to create the floating endpoint etc + // will still be valid, but when we stop dragging, we'll have to do something different. if we stop with a valid drop i think it will + // be the same process. but if we stop with an invalid drop we have to reset the Connection to how it was when we got it. + var start = function(e, ui) { + //if (!isFull()) { + n = document.createElement("div"); + contextNode.append(n); + // create and assign an id, and initialize the offset. + id = new String(new Date().getTime()); + $(n, contextNode).attr("id", id); + _updateOffset(id); + // store the id of the dragging div and the source element. the drop function + // will pick these up. + $(self.canvas, contextNode).attr("dragId", id); + $(self.canvas, contextNode).attr("elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor({reference:self.anchor}); + floatingEndpoint = new Endpoint({ + style:_style, + endpoint:_endpoint, + anchor:floatingAnchor, + source:n + }); + //floatingEndpoint.originalAnchor = + + jpc = connectorSelector(); + if (jpc == null) { + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = new Connection({ + sourceEndpoint:self, + targetEndpoint:floatingEndpoint, + source:$(_element), + target:$(n, contextNode), + anchors:[self.anchor, floatingAnchor], + paintStyle : params.connectionStyle, // this can be null. Connection will use the default. + connector: params.connector + }); + // todo ...unregister on stop + self.addConnection(jpc); + } else { + existingJpc = true; + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; + // probably we should remove the connection? and add it back if the user + // does not drop it somewhere proper. + self.removeConnection(jpc); + if (anchorIdx == 0){ + existingJpcParams = [jpc.source, jpc.sourceId]; + jpc.source = $(n, contextNode); + jpc.sourceId = id; + }else { + existingJpcParams = [jpc.target, jpc.targetId]; + jpc.target = $(n, contextNode); + jpc.targetId = id; + } + + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // todo unregister on stop + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + } + //}; + + var dragOptions = params.dragOptions || { }; + dragOptions = $.extend({ opacity:0.5, revert:true, helper:'clone' }, dragOptions); + + dragOptions.start = _wrap(dragOptions.start, start); + dragOptions.drag = _wrap(dragOptions.drag, function(e, ui) { + _draw($(n, contextNode), ui); + }); + dragOptions.stop = _wrap(dragOptions.stop, + function(e, ui) { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements([floatingEndpoint.canvas, n]); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (jpc.endpoints[idx] == floatingEndpoint) { + + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + if (_reattach) { + jpc.floatingAnchorIndex = null; + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } + else { + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeElement(jpc.canvas); + } + } else { + _removeElement(jpc.canvas); + self.removeConnection(jpc); + } + } + jpc = null; + delete floatingEndpoint; + } + ); + + $(self.canvas, contextNode).draggable(dragOptions); + } + + // connector target + if (params.isTarget && _element.droppable) { + var dropOptions = params.dropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = $.extend({}, dropOptions); + var originalAnchor = null; + dropOptions.drop = _wrap(dropOptions.drop, function(e, ui) { + var id = $(ui.draggable, contextNode).attr("dragId"); + var elId = $(ui.draggable, contextNode).attr("elId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // remove this jpc from the current endpoint + jpc.endpoints[idx].removeConnection(jpc); + if (jpc.suspendedEndpoint) + jpc.suspendedEndpoint.removeConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + _initDraggableIfNecessary(_element, _elementId, params.draggable, {}); + jsPlumb.repaint($(ui.draggable, contextNode).attr("elId")); + delete floatingConnections[id]; + }); + // what to do when something is dropped. + // 1. find the jpc that is being dragged. the target endpoint of the jpc will be the + // one that is being dragged. + // 2. arrange for the floating endpoint to be replaced with this endpoint; make sure + // everything gets registered ok etc. + // 3. arrange for the floating endpoint to be deleted. + // 4. make sure that the stop method of the drag does not cause the jpc to be cleaned up. we want to keep it now. + + // other considerations: when in the hover mode, we should switch the floating endpoint's + // orientation to be the same as the drop target. this will cause the connector to snap + // into the shape it will take if the user drops at that point. + + dropOptions.over = _wrap(dropOptions.over, function(event, ui) { + var id = $(ui.draggable, contextNode).attr("dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + }); + + dropOptions.out = _wrap(dropOptions.out, function(event, ui) { + var id = $(ui.draggable, contextNode).attr("dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + }); + + $(self.canvas, contextNode).droppable(dropOptions); + } + + // woo...add a plumb command to Endpoint. + this.plumb = function(params) { + // not much to think about. the target should be an Endpoint, but what else? + // all the style stuff is on the endpoint itself already. + //todo this should call the main plumb method, just with some different args. + + }; + + return self; + }; + + /** + * jsPlumb public API + */ + var jsPlumb = window.jsPlumb = { + + Defaults : { + Anchors : [ null, null ], + Connector : null, + DragOptions: { }, + DropOptions: { }, + Endpoint : null, + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : null }, + EndpointStyles : [ null, null ], + MaxConnections : null, + PaintStyle : { lineWidth : 10, strokeStyle : 'red' } + }, + + connectorClass : '_jsPlumb_connector', + endpointClass : '_jsPlumb_endpoint', + + Anchors : {}, + Connectors : {}, + Endpoints : {}, + + /** + * adds an endpoint to the element + */ + addEndpoint : function(target, params) { + params = $.extend({}, params); + var el = typeof target == 'string' ? $("#" + target) : target; + var id = $(el).attr("id"); + params.source = el; + _updateOffset(id); + var e = new Endpoint(params); + _addToList(endpointsByElement, id, e); + + var myOffset = offsets[id]; + var myWH = sizes[id]; + + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH); + e.paint(anchorLoc); + return e; + }, + + /** + * wrapper around standard jquery animate function; injects a call to jsPlumb in the + * 'step' function (creating it if necessary). this only supports the two-arg version + * of the animate call in jquery - the one that takes an object as the second arg. + */ + animate : function(el, properties, options) { + var ele = typeof(el)=='string' ? $("#" + el) : el; + var id = ele.attr("id"); + options = options || {}; + options.step = _wrap(options.step, function() { jsPlumb.repaint(id); }); + ele.animate(properties, options); + }, + + /** + * adds a list of endpoints to the element + */ + addEndpoints : function(target, endpoints) { + var results = []; + for (var i = 0; i < endpoints.length; i++) { + results.push(jsPlumb.addEndpoint(target, endpoints[i])); + } + return results; + }, + + /** + * establishes a connection between two elements. + * @param params object containing setup for the connection. see documentation. + */ + connect : function(params) { + if (params.sourceEndpoint && params.sourceEndpoint.isFull()) { + _log("could not add connection; source endpoint is full"); + return; + } + + if (params.targetEndpoint && params.targetEndpoint.isFull()) { + _log("could not add connection; target endpoint is full"); + return; + } + + var jpc = new Connection(params); + + // register endpoints for the element + _addToList(endpointsByElement, jpc.sourceId, jpc.endpoints[0]); + _addToList(endpointsByElement, jpc.targetId, jpc.endpoints[1]); + + jpc.endpoints[0].addConnection(jpc); + jpc.endpoints[1].addConnection(jpc); + + // force a paint + _draw(jpc.source); + + }, + + connectEndpoints : function(params) { + var jpc = Connection(params); + + }, + + /** + * Remove one connection to an element. + * @param sourceId id of the first window in the connection + * @param targetId id of the second window in the connection + * @return true if successful, false if not. + */ + detach : function(sourceId, targetId) { + var f = function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas); + /*_removeElement(jpc.targetEndpointCanvas); + _removeElement(jpc.sourceEndpointCanvas);*/ + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + return true; + } + }; + + // todo: how to cleanup the actual storage? a third arg to _operation? + _operation(sourceId, f); + }, + + /** + * remove all an element's connections. + * @param elId id of the + */ + detachAll : function(elId) { + + var f = function(jpc) { + // todo replace with _cleanupConnection call here. + _removeElement(jpc.canvas); + /*_removeElement(jpc.targetEndpointCanvas); + _removeElement(jpc.sourceEndpointCanvas);*/ + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + }; + _operation(elId, f); + //delete endpointsByElement[elId]; + }, + + /** + * remove all connections. and endpoints? probably not. + */ + detachEverything : function() { + var f = function(jpc) { + _removeElement(jpc.canvas); + /*_removeElement(jpc.targetEndpointCanvas); + _removeElement(jpc.sourceEndpointCanvas);*/ + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + }; + + _operationOnAll(f); + + /*delete endpointsByElement; + endpointsByElement = {};*/ + }, + + /** + * Set an element's connections to be hidden. + */ + hide : function(elId) { + _setVisible(elId, "none"); + }, + + /** + * Creates an anchor with the given params. + * x - the x location of the anchor as a fraction of the total width. + * y - the y location of the anchor as a fraction of the total height. + * orientation - an [x,y] array indicating the general direction a connection from the anchor should go in. + * offsets - an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. optional. defaults to [0,0]. + */ + makeAnchor : function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards compatibility if we are given only one value we assume it's a call in the old form. + var params = {}; + if (arguments.length == 1) $.extend(params, x); + else { + params = {x:x, y:y}; + if (arguments.length >= 4) { + params.orientation = [arguments[2], arguments[3]]; + } + if (arguments.length == 6) params.offsets = [arguments[4], arguments[5]]; + } + return new Anchor(params); + }, + + + /** + * repaint element and its connections. element may be an id or the actual jQuery object. + * this method gets new sizes for the elements before painting anything. + */ + repaint : function(el) { + + var _processElement = function(el) { + var ele = typeof(el)=='string' ? $("#" + el) : el; + _draw(ele); + }; + + // TODO: support a jQuery result object too! + + // support both lists... + if (typeof el =='object') { + for (var i = 0; i < el.length; i++) + _processElement(el[i]); + } // ...and single strings. + else _processElement(el); + }, + + /** + * repaint all connections. + */ + repaintEverything : function() { + for (var elId in endpointsByElement) { + _draw($("#" + elId)); + } + }, + + removeEndpoint : function(elId, endpoint) { + var ebe = endpointsByElement[elId]; + if (ebe) { + // var i = ebe.indexOf(endpoint); + // alert('element has ' + ebe.length); + //if (i > -1) { + //ebe.splice(i, 1); + if(_removeFromList(endpointsByElement, elId, endpoint)) + _removeElement(endpoint.canvas); + //} + } + }, + + /** + * sets/unsets automatic repaint on window resize. + */ + setAutomaticRepaint : function(value) { + automaticRepaint = value; + }, + + /** + * Sets the default size jsPlumb will use for a new canvas (we create a square canvas so + * one value is all that is required). This is a hack for IE, because ExplorerCanvas seems + * to need for a canvas to be larger than what you are going to draw on it at initialisation + * time. The default value of this is 1200 pixels, which is quite large, but if for some + * reason you're drawing connectors that are bigger, you should adjust this value appropriately. + */ + setDefaultNewCanvasSize : function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }, + + /** + * Sets whether or not a given element is draggable, regardless of what any plumb command + * may request. + */ + setDraggable: _setDraggable, + + /** + * Sets whether or not elements are draggable by default. Default for this is true. + */ + setDraggableByDefault: function(draggable) { + _draggableByDefault = draggable; + }, + + setDebugLog: function(debugLog) { + log = debugLog; + }, + + /** + * Sets the function to fire when the window size has changed and a repaint was fired. + */ + setRepaintFunction : function(f) { + repaintFunction = f; + }, + + /** + * Set an element's connections to be visible. + */ + show : function(elId) { + _setVisible(elId, "block"); + }, + + /** + * helper to size a canvas. + */ + sizeCanvas : function(canvas, x, y, w, h) { + canvas.style.height = h + "px"; canvas.height = h; + canvas.style.width = w + "px"; canvas.width = w; + canvas.style.left = x + "px"; canvas.style.top = y + "px"; + }, + + /** + * gets some test hooks. nothing writable. + */ + getTestHarness : function() { + return { + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + } + }; + }, + + /** + * Toggles visibility of an element's connections. kept for backwards compatibility + */ + toggle : _toggleVisible, + + /** + * new name for the old toggle method. + */ + toggleVisible : _toggleVisible, + + /** + * Toggles draggability (sic) of an element. + */ + toggleDraggable : _toggleDraggable, + + /** + * Unloads jsPlumb, deleting all storage. You should call this + */ + unload : function() { + delete endpointsByElement; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + document.body.removeChild(_jsPlumbContextNode); + } + }; + +})(); + +// jQuery plugin code +(function($){ + /** + * plumbs the results of the selector to some target, using the given options if supplied, + * or the defaults otherwise. + */ + $.fn.plumb = function(options) { + var options = $.extend({}, options); + + return this.each(function() + { + var params = $.extend({source:$(this)}, options); + jsPlumb.connect(params); + }); + }; + + /** + * detaches the results of the selector from the given target or list of targets - 'target' + * may be a String or a List. + */ + $.fn.detach = function(target) { + return this.each(function() + { + var id = $(this).attr("id"); + if (typeof target == 'string') target = [target]; + for (var i = 0; i < target.length; i++) + jsPlumb.detach(id, target[i]); + }); + }; + + /** + * detaches the results from the selector from all connections. + */ + $.fn.detachAll = function() { + return this.each(function() + { + var id = $(this).attr("id"); + jsPlumb.detachAll(id); + }); + }; + + /** + * adds an endpoint to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoint = function(options) { + var addedEndpoints = []; + this.each(function() + { + //var params = $.extend({source:$(this)}, options); + addedEndpoints.push(jsPlumb.addEndpoint($(this).attr("id"), options)); + }); + return addedEndpoints; + }; + + /** + * adds a list of endpoints to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoints = function(endpoints) { + var addedEndpoints = []; + return this.each(function() + { + var e = jsPlumb.addEndpoints($(this).attr("id"), endpoints); + for (var i = 0; i < e.length; i++) addedEndpoints.push(e[i]); + }); + return addedEndpoints; + }; + + /** + * remove the endpoint, if it exists, deleting its UI elements etc. + */ + $.fn.removeEndpoint = function(endpoint) { + this.each(function() + { + jsPlumb.removeEndpoint($(this).attr("id"), endpoint); + }); + }; + +})(jQuery); + + +/** +* jsPlumb-defaults-1.1.0 +* +* This script contains the default Anchors, Endpoints and Connectors for jsPlumb. It should be used with jsPlumb 1.1.0 and above; +* prior to version 1.1.0 of jsPlumb the defaults were included inside the main script. +* +* NOTE: for production usage you should use jsPlumb-all-1.1.0-min.js, which contains the main jsPlumb script and this script together, +* in a minified file. +*/ + +(function() { + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * getOrientation : function() { return [ox, oy]; } + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + jsPlumb.Anchors.TopCenter = jsPlumb.makeAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors.BottomCenter = jsPlumb.makeAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors.LeftMiddle = jsPlumb.makeAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors.RightMiddle = jsPlumb.makeAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors.Center = jsPlumb.makeAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors.TopRight = jsPlumb.makeAnchor(1, 0, 0,-1); + jsPlumb.Anchors.BottomRight = jsPlumb.makeAnchor(1, 1, 0, 1); + jsPlumb.Anchors.TopLeft = jsPlumb.makeAnchor(0, 0, 0, -1); + jsPlumb.Anchors.BottomLeft = jsPlumb.makeAnchor(0, 1, 0, 1); + + + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (w < 2 * lineWidth) { + w = 2 * lineWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - lineWidth; + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < 2 * lineWidth) { + // minimum size is 2 * line Width + h = 2 * lineWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - lineWidth; + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + var sx = sourcePos[0] < targetPos[0] ? w-xo : xo; + var sy = sourcePos[1] < targetPos[1] ? h-yo : yo; + var tx = sourcePos[0] < targetPos[0] ? xo : w-xo; + var ty = sourcePos[1] < targetPos[1] ? yo : h-yo; + var retVal = [ x, y, w, h, sx, sy, tx, ty ]; + + return retVal; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + }; + + /** + * This Connector draws a Bezier curve with two control points. + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + jsPlumb.Connectors.Bezier = function(curviness) { + + var self = this; + this.majorAnchor = curviness || 150; + this.minorAnchor = 10; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) + { + lineWidth = lineWidth || 0; + var w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth, h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + var canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2), canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + var sx = sourcePos[0] < targetPos[0] ? w - (lineWidth/2): (lineWidth/2), sy = sourcePos[1] < targetPos[1] ? h-(lineWidth/2) : (lineWidth/2); + var tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : w-(lineWidth/2), ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : h-(lineWidth/2); + var CP = self._findControlPoint([sx,sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + var CP2 = self._findControlPoint([tx,ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(sx,tx); var minx2 = Math.min(CP[0], CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(sx,tx); var maxx2 = Math.max(CP[0], CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > w) w = maxx; + if (minx < 0) { + canvasX += minx; var ox = Math.abs(minx); + w += ox; CP[0] += ox; sx += ox; tx +=ox; CP2[0] += ox; + } + + var miny1 = Math.min(sy,ty); var miny2 = Math.min(CP[1], CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(sy,ty); var maxy2 = Math.max(CP[1], CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > h) h = maxy; + if (miny < 0) { + canvasY += miny; var oy = Math.abs(miny); + h += oy; CP[1] += oy; sy += oy; ty +=oy; CP2[1] += oy; + } + + // return [ canvasx, canvasy, canvasWidth, canvasHeight, + // sourceX, sourceY, targetX, targetY, + // controlPoint1_X, controlPoint1_Y, controlPoint2_X, controlPoint2_Y + return [canvasX, canvasY, w, h, sx, sy, tx, ty, CP[0], CP[1], CP2[0], CP2[1] ]; + }; + + this.paint = function(d, ctx) { + + /*var img = new Image(); + img.src = "../img/pattern.jpg"; + ctx.fillStyle = ctx.createPattern(img, 'repeat-y');*/ + + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + //ctx.fill(); + } + }; + + + /** + * Types of endpoint UIs. we supply three - a circle of default radius 10px, a rectangle of + * default size 20x20, and an image (with no default). you can supply others of these if you want to - see the documentation + * for a howto. + */ + + /** + * a round endpoint, with default radius 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + } + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = $.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + $.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * A Rectangular endpoint, with default size 20x20. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + //todo: the fillStyle needs some thought. we want to support a few options: + // 1. nothing supplied; use the stroke color or the default if no stroke color. + // 2. a fill color supplied - use it + // 3. a gradient supplied - use it + // 4. setting the endpoint to the same color as the bg of the element it is attached to. + var style = $.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + $.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' property in the params object.. + */ + jsPlumb.Endpoints.Image = function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + }; +})(); diff --git a/archive/1.1.0/jquery.jsPlumb-defaults-1.1.0-RC1.js b/archive/1.1.0/jquery.jsPlumb-defaults-1.1.0-RC1.js new file mode 100644 index 000000000..8deab52e8 --- /dev/null +++ b/archive/1.1.0/jquery.jsPlumb-defaults-1.1.0-RC1.js @@ -0,0 +1,337 @@ +(function() { + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * getOrientation : function() { return [ox, oy]; } + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + jsPlumb.Anchors.TopCenter = jsPlumb.makeAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors.BottomCenter = jsPlumb.makeAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors.LeftMiddle = jsPlumb.makeAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors.RightMiddle = jsPlumb.makeAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors.Center = jsPlumb.makeAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors.TopRight = jsPlumb.makeAnchor(1, 0, 0,-1); + jsPlumb.Anchors.BottomRight = jsPlumb.makeAnchor(1, 1, 0, 1); + jsPlumb.Anchors.TopLeft = jsPlumb.makeAnchor(0, 0, 0, -1); + jsPlumb.Anchors.BottomLeft = jsPlumb.makeAnchor(0, 1, 0, 1); + + + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (w < 2 * lineWidth) { + w = 2 * lineWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - lineWidth; + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < 2 * lineWidth) { + // minimum size is 2 * line Width + h = 2 * lineWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - lineWidth; + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + var sx = sourcePos[0] < targetPos[0] ? w-xo : xo; + var sy = sourcePos[1] < targetPos[1] ? h-yo : yo; + var tx = sourcePos[0] < targetPos[0] ? xo : w-xo; + var ty = sourcePos[1] < targetPos[1] ? yo : h-yo; + var retVal = [ x, y, w, h, sx, sy, tx, ty ]; + + return retVal; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + }; + + /** + * This Connector draws a Bezier curve with two control points. + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + jsPlumb.Connectors.Bezier = function(curviness) { + + var self = this; + this.majorAnchor = curviness || 150; + this.minorAnchor = 10; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) + { + lineWidth = lineWidth || 0; + var w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth, h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + var canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2), canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + var sx = sourcePos[0] < targetPos[0] ? w - (lineWidth/2): (lineWidth/2), sy = sourcePos[1] < targetPos[1] ? h-(lineWidth/2) : (lineWidth/2); + var tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : w-(lineWidth/2), ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : h-(lineWidth/2); + var CP = self._findControlPoint([sx,sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + var CP2 = self._findControlPoint([tx,ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(sx,tx); var minx2 = Math.min(CP[0], CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(sx,tx); var maxx2 = Math.max(CP[0], CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > w) w = maxx; + if (minx < 0) { + canvasX += minx; var ox = Math.abs(minx); + w += ox; CP[0] += ox; sx += ox; tx +=ox; CP2[0] += ox; + } + + var miny1 = Math.min(sy,ty); var miny2 = Math.min(CP[1], CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(sy,ty); var maxy2 = Math.max(CP[1], CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > h) h = maxy; + if (miny < 0) { + canvasY += miny; var oy = Math.abs(miny); + h += oy; CP[1] += oy; sy += oy; ty +=oy; CP2[1] += oy; + } + + // return [ canvasx, canvasy, canvasWidth, canvasHeight, + // sourceX, sourceY, targetX, targetY, + // controlPoint1_X, controlPoint1_Y, controlPoint2_X, controlPoint2_Y + return [canvasX, canvasY, w, h, sx, sy, tx, ty, CP[0], CP[1], CP2[0], CP2[1] ]; + }; + + this.paint = function(d, ctx) { + + /*var img = new Image(); + img.src = "../img/pattern.jpg"; + ctx.fillStyle = ctx.createPattern(img, 'repeat-y');*/ + + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + //ctx.fill(); + } + }; + + + /** + * Types of endpoint UIs. we supply three - a circle of default radius 10px, a rectangle of + * default size 20x20, and an image (with no default). you can supply others of these if you want to - see the documentation + * for a howto. + */ + + /** + * a round endpoint, with default radius 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + } + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = $.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + $.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * A Rectangular endpoint, with default size 20x20. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + //todo: the fillStyle needs some thought. we want to support a few options: + // 1. nothing supplied; use the stroke color or the default if no stroke color. + // 2. a fill color supplied - use it + // 3. a gradient supplied - use it + // 4. setting the endpoint to the same color as the bg of the element it is attached to. + var style = $.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + $.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' property in the params object.. + */ + jsPlumb.Endpoints.Image = function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + }; +})(); \ No newline at end of file diff --git a/archive/1.1.0/jquery.jsPlumb-defaults-1.1.0.js b/archive/1.1.0/jquery.jsPlumb-defaults-1.1.0.js new file mode 100644 index 000000000..c71add762 --- /dev/null +++ b/archive/1.1.0/jquery.jsPlumb-defaults-1.1.0.js @@ -0,0 +1,347 @@ +/** +* jsPlumb-defaults-1.1.0 +* +* This script contains the default Anchors, Endpoints and Connectors for jsPlumb. It should be used with jsPlumb 1.1.0 and above; +* prior to version 1.1.0 of jsPlumb the defaults were included inside the main script. +* +* NOTE: for production usage you should use jsPlumb-all-1.1.0-min.js, which contains the main jsPlumb script and this script together, +* in a minified file. +*/ + +(function() { + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * getOrientation : function() { return [ox, oy]; } + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + jsPlumb.Anchors.TopCenter = jsPlumb.makeAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors.BottomCenter = jsPlumb.makeAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors.LeftMiddle = jsPlumb.makeAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors.RightMiddle = jsPlumb.makeAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors.Center = jsPlumb.makeAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors.TopRight = jsPlumb.makeAnchor(1, 0, 0,-1); + jsPlumb.Anchors.BottomRight = jsPlumb.makeAnchor(1, 1, 0, 1); + jsPlumb.Anchors.TopLeft = jsPlumb.makeAnchor(0, 0, 0, -1); + jsPlumb.Anchors.BottomLeft = jsPlumb.makeAnchor(0, 1, 0, 1); + + + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (w < 2 * lineWidth) { + w = 2 * lineWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - lineWidth; + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < 2 * lineWidth) { + // minimum size is 2 * line Width + h = 2 * lineWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - lineWidth; + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + var sx = sourcePos[0] < targetPos[0] ? w-xo : xo; + var sy = sourcePos[1] < targetPos[1] ? h-yo : yo; + var tx = sourcePos[0] < targetPos[0] ? xo : w-xo; + var ty = sourcePos[1] < targetPos[1] ? yo : h-yo; + var retVal = [ x, y, w, h, sx, sy, tx, ty ]; + + return retVal; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + }; + + /** + * This Connector draws a Bezier curve with two control points. + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + jsPlumb.Connectors.Bezier = function(curviness) { + + var self = this; + this.majorAnchor = curviness || 150; + this.minorAnchor = 10; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) + { + lineWidth = lineWidth || 0; + var w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth, h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + var canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2), canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + var sx = sourcePos[0] < targetPos[0] ? w - (lineWidth/2): (lineWidth/2), sy = sourcePos[1] < targetPos[1] ? h-(lineWidth/2) : (lineWidth/2); + var tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : w-(lineWidth/2), ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : h-(lineWidth/2); + var CP = self._findControlPoint([sx,sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + var CP2 = self._findControlPoint([tx,ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(sx,tx); var minx2 = Math.min(CP[0], CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(sx,tx); var maxx2 = Math.max(CP[0], CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > w) w = maxx; + if (minx < 0) { + canvasX += minx; var ox = Math.abs(minx); + w += ox; CP[0] += ox; sx += ox; tx +=ox; CP2[0] += ox; + } + + var miny1 = Math.min(sy,ty); var miny2 = Math.min(CP[1], CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(sy,ty); var maxy2 = Math.max(CP[1], CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > h) h = maxy; + if (miny < 0) { + canvasY += miny; var oy = Math.abs(miny); + h += oy; CP[1] += oy; sy += oy; ty +=oy; CP2[1] += oy; + } + + // return [ canvasx, canvasy, canvasWidth, canvasHeight, + // sourceX, sourceY, targetX, targetY, + // controlPoint1_X, controlPoint1_Y, controlPoint2_X, controlPoint2_Y + return [canvasX, canvasY, w, h, sx, sy, tx, ty, CP[0], CP[1], CP2[0], CP2[1] ]; + }; + + this.paint = function(d, ctx) { + + /*var img = new Image(); + img.src = "../img/pattern.jpg"; + ctx.fillStyle = ctx.createPattern(img, 'repeat-y');*/ + + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + //ctx.fill(); + } + }; + + + /** + * Types of endpoint UIs. we supply three - a circle of default radius 10px, a rectangle of + * default size 20x20, and an image (with no default). you can supply others of these if you want to - see the documentation + * for a howto. + */ + + /** + * a round endpoint, with default radius 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + } + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = $.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + $.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * A Rectangular endpoint, with default size 20x20. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + //todo: the fillStyle needs some thought. we want to support a few options: + // 1. nothing supplied; use the stroke color or the default if no stroke color. + // 2. a fill color supplied - use it + // 3. a gradient supplied - use it + // 4. setting the endpoint to the same color as the bg of the element it is attached to. + var style = $.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + $.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' property in the params object.. + */ + jsPlumb.Endpoints.Image = function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + }; +})(); \ No newline at end of file diff --git a/archive/1.1.1/jquery.jsPlumb-1.1.1-RC1.js b/archive/1.1.1/jquery.jsPlumb-1.1.1-RC1.js new file mode 100644 index 000000000..1a3300c46 --- /dev/null +++ b/archive/1.1.1/jquery.jsPlumb-1.1.1-RC1.js @@ -0,0 +1,1785 @@ +/* + * jsPlumb 1.1.1-RC1 + * + * Provides a way to visually connect elements on an HTML page. + * + * 1.1.1 contains bugfixes and API additions on 1.1.0. + * + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + */ + +(function() { + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + + var log = null; + + var repaintFunction = function() { jsPlumb.repaintEverything(); }; + var automaticRepaint = true; + function repaintEverything() { + if (automaticRepaint) + repaintFunction(); + }; + var resizeTimer = null; + + /*TODO: abstract this out from jQuery too! but how...because jsPlumb is not loaded yet. + $(window).bind('resize', function() { + if (resizeTimer) clearTimeout(resizeTimer); + resizeTimer = setTimeout(repaintEverything, 100); + });*/ + + /** + * map of element id -> endpoint lists. an element can have an arbitrary number of endpoints on it, + * and not all of them have to be connected to anything. + */ + var endpointsByElement = {}; + var offsets = []; + var floatingConnections = {}; + var draggableStates = {}; + var _draggableByDefault = true; + var sizes = []; + + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + var _findIndex = function( a, v, b, s ) { + + var _eq = function(o1, o2) { + if (o1 === o2) return true; + else if (typeof o1 == 'object' && typeof o2 == 'object') { + var same = true; + for(var propertyName in o1) { + if(!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for(var propertyName in o2) { + if(!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for( var i = +b || 0, l = a.length; i < l; i++ ) { + if( _eq(a[i], v)) return i; + } + return -1; + }; + + /** + * helper method to add an item to a list, creating the list if it does not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + }; + + /** + * Handles the dragging of an element. + * @param element jQuery element + * @param ui UI object from current library's event system + */ + var _draw = function(element, ui) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints) { + _updateOffset(id, ui); + var myOffset = offsets[id]; + var myWH = sizes[id]; + // loop through endpoints for this element + for (var i = 0; i < endpoints.length; i++) { + var e = endpoints[i]; + var anchorLoc = endpoints[i].anchor.compute([myOffset.left, myOffset.top], myWH); + e.paint(anchorLoc); + var l = e.connections; + for (var j = 0; j < l.length; j++) + l[j].paint(id, ui); // ...and paint them. + } + } + }; + + /** + * helper function: the second argument is a function taking two args - the first is a + * jquery element, and the second is the element's id. + * + * the first argument may be one of three things: + * + * 1. a string, in the form "window1", for example. an element's id. if your id starts with a + * hash then jsPlumb does not append its own hash too... + * 2. a jquery element, already resolved using $(...). + * 3. a list of strings/jquery elements. + */ + var _elementProxy = function(element, fn) { + var retVal = null; + if (typeof element == 'object' && element.length) { + retVal = []; + for (var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]); + var id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } + else { + var el = _getElementObject(element); + var id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + + return retVal; + }; + + /** + * gets the named attribute from the given element (id or element object) + */ + var _getAttribute = function(el, attName) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + return jsPlumb.CurrentLibrary.getAttribute(ele, attName); + }; + + var _setAttribute = function(el, attName, attValue) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + jsPlumb.CurrentLibrary.setAttribute(ele, attName, attValue); + }; + + var _addClass = function(el, clazz) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + jsPlumb.CurrentLibrary.addClass(ele, clazz); + }; + + var _getElementObject = function(elId) { + return jsPlumb.CurrentLibrary.getElementObject(elId); + }; + + var _getOffset = function(el) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + return jsPlumb.CurrentLibrary.getOffset(ele); + }; + + var _getSize = function(el) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + return jsPlumb.CurrentLibrary.getSize(ele); + }; + + var _initDraggable = function(el, options) { + return jsPlumb.CurrentLibrary.initDraggable(el, options); + }; + + var _isDragSupported = function(el) { + return jsPlumb.CurrentLibrary.isDragSupported(el); + }; + + var _initDroppable = function(el, options) { + return jsPlumb.CurrentLibrary.initDroppable(el, options); + }; + + var _isDropSupported = function(el) { + return jsPlumb.CurrentLibrary.isDropSupported(el); + }; + + var _animate= function(el, properties, options) { + return jsPlumb.CurrentLibrary.animate(el, properties, options); + }; + + var _appendCanvas = function(canvas) { + document.body.appendChild(canvas); + }; + + /** + * gets an id for the given element, creating and setting one if necessary. + */ + var _getId = function(element) { + var id = _getAttribute(element, "id"); + if (!id) { + id = "_jsPlumb_" + new String((new Date()).getTime()); + _setAttribute(element, "id", id); + } + return id; + }; + + /** + * inits a draggable if it's not already initialised. + * todo: if the element was draggable already, like from some non-jsPlumb call, wrap the drag function. + */ + var _initDraggableIfNecessary = function(element, elementId, isDraggable, dragOptions) { + // dragging + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable && _isDragSupported(element)) { + var options = dragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend({}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var initDrag = function(element, elementId, dragFunc) { + options[dragEvent] = _wrap(options[dragEvent], dragFunc); + var draggable = draggableStates[elementId]; + options.disabled = draggable == null ? false : !draggable; + _initDraggable(element, options); + }; + initDrag(element, elementId, function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + } + }; + + var _log = function(msg) { + // not implemented. yet. + } + + /** + * helper to create a canvas. + * @param clazz optional class name for the canvas. + */ + var _newCanvas = function(clazz) { + var canvas = document.createElement("canvas"); + _appendCanvas(canvas); + canvas.style.position="absolute"; + if (clazz) { canvas.className=clazz; } + _getId(canvas); // set an id. + + if (ie) { + // for IE we have to set a big canvas size. actually you can override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + /** + * performs the given function operation on all the connections found for the given element + * id; this means we find all the endpoints for the given element, and then for each endpoint + * find the connectors connected to it. then we pass each connection in to the given + * function. + */ + var _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + for (var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }; + /** + * perform an operation on all elements. + */ + var _operationOnAll = function(func) { + for (var elId in endpointsByElement) { + _operation(elId, func); + } + }; + /** + * helper to remove an element from the DOM. + */ + var _removeElement = function(element) { + if (element != null) { + try { document.body.removeChild(element); } + catch (e) { } + } + }; + /** + * helper to remove a list of elements from the DOM. + */ + var _removeElements = function(elements) { + for (var i in elements) + _removeElement(elements[i]); + }; + /** + * helper method to remove an item from a list. + */ + var _removeFromList = function(map, key, value) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete( l[i] ); + l.splice( i, 1 ); + return true; + } + } + return false; + }; + /** + * Sets whether or not the given element(s) should be draggable, regardless of what a particular + * plumb command may request. + * + * @param element May be a string, a jQuery elements, or a list of strings/jquery elements. + * @param draggable Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + var _helper = function(el, id) { + draggableStates[id] = draggable; + if (_isDragSupported(el)) { + // TODO: not library agnostic yet. + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + //el.draggable("option", "disabled", !draggable); + } + }; + + return _elementProxy(element, _helper); + }; + /** + * private method to do the business of hiding/showing. + * @param el either Id of the element in question or a jquery object for the element. + * @param state String specifying a value for the css 'display' property ('block' or 'none'). + */ + var _setVisible = function(el, state) { + var elId = _getAttribute(el, "id"); + var f = function(jpc) { + jpc.canvas.style.display = state; + }; + _operation(elId, f); + }; + /** + * toggles the draggable state of the given element(s). + * @param el either an id, or a jquery object, or a list of ids/jquery objects. + */ + var _toggleDraggable = function(el) { + var fn = function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + //el.draggable("option", "disabled", !state); + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }; + return _elementProxy(el, fn); + }; + /** + * private method to do the business of toggling hiding/showing. + * @param elId Id of the element in question + */ + var _toggleVisible = function(elId) { + var f = function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }; + + _operation(elId, f); + + //todo this should call _elementProxy, and pass in the _operation(elId, f) call as a function. cos _toggleDraggable does that. + }; + /** + * updates the offset and size for a given element, and stores the values. + * if 'offset' is not null we use that (it would have been passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we use the offset, outerWidth and outerHeight methods to get + * the current values. + */ + var _updateOffset = function(elId, offset, recalc) { + if (recalc || offset == null) { // if forced repaint or no offset available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + sizes[elId] = _getSize(elId); + offsets[elId] = _getOffset(elId); + } else { + offsets[elId] = offset; + } + }; + + /** + * wraps one function with another, creating a placeholder for the wrapped function + * if it was null. this is used to wrap the various drag/drop event functions - to allow + * jsPlumb to be notified of important lifecycle events without imposing itself on the user's + * drap/drop functionality. + * TODO: determine whether or not we should try/catch the plumb function, so that the cascade function is always executed. + */ + var _wrap = function(cascadeFunction, plumbFunction) { + cascadeFunction = cascadeFunction || function() { }; + return function() { + plumbFunction.apply(this, arguments); + cascadeFunction.apply(this, arguments); + }; + } + + /* + Class: Anchor + Models a position relative to the origin of some element that an Endpoint can be located. + */ + /* + Function: Anchor + + Constructor for the Anchor class + + Parameters: + params - Anchor parameters. This should contain three values, and may optionally have an 'offsets' argument: + + - x : the x location of the anchor as a fraction of the total width. + - y : the y location of the anchor as a fraction of the total height. + - orientation : an [x,y] array indicating the general direction a connection from the anchor should go in. for more info on this, see the documentation, or the docs in jquery-jsPlumb-defaults-XXX.js for the default Anchors. + - offsets : an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. may be null. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; this.y = params.y || 0; + var orientation = params.orientation || [0,0]; + this.offsets = params.offsets || [0,0]; + this.compute = function(xy, wh, txy, twh) { + return [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + } + this.getOrientation = function() { return orientation; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from its position relative to the anchor it is floating relative to. + * + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for purposes of calculating the orientation. + var ref = params.reference; + // these are used to store the current relative position of our anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating anchor is hovering over another anchor. + var orientation = null; + + this.compute = function(xy, wh, txy, twh) { + // set these for the getOrientation method to use. + xDir = 0;//xy[0] < txy[0] ? -1 : xy[0] == txy[0] ? 0 : 1; + yDir = 0;//xy[1] < txy[1] ? -1 : xy[1] == txy[1] ? 0 : 1; + return [xy[0], xy[1]]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other anchor: if it declares zero for some direction, we declare zero too. + // this might not be the most awesome. perhaps we can come up with a better way. it's just so that the line we draw looks + // like it makes sense. maybe this wont make sense. + return [Math.abs(o[0]) * xDir * -1, Math.abs(o[1]) * yDir * -1]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering over another anchor; + * we want to assume that anchor's orientation for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no longer hovering + * over another anchor; we should resume calculating orientation as we normally do. + */ + this.out = function() { + orientation = null; + }; + }; + + // ************** connection + // **************************************** + /** + * allowed params: + * source: source element (string or a jQuery element) (required) + * target: target element (string or a jQuery element) (required) + * + * anchors: optional array of anchor placements. defaults to BottomCenter for source + * and TopCenter for target. + */ + var Connection = function(params) { + + // ************** get the source and target and register the connection. ******************* + var self = this; + // get source and target as jQuery objects + this.source = _getElementObject(params.source); + this.target = _getElementObject(params.target); + this.sourceId = _getAttribute(this.source, "id"); + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + + // init endpoints + this.endpoints = []; + this.endpointStyles = []; + var prepareEndpoint = function(existing, index, params) { + if (existing) self.endpoints[index] = existing; + else { + if(!params.endpoints) params.endpoints = [null,null]; + var ep = params.endpoints[index] || params.endpoint || jsPlumb.Defaults.Endpoints[index] || jsPlumb.Defaults.Endpoint|| new jsPlumb.Endpoints.Dot(); + if (!params.endpointStyles) params.endpointStyles = [null,null]; + var es = params.endpointStyles[index] || params.endpointStyle || jsPlumb.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyle; + var a = params.anchors ? params.anchors[index] : jsPlumb.Defaults.Anchors[index] || jsPlumb.Anchors.BottomCenter; + self.endpoints[index] = new Endpoint({style:es, endpoint:ep, connections:[self], anchor:a }); + } + }; + + prepareEndpoint(params.sourceEndpoint, 0, params); + prepareEndpoint(params.targetEndpoint, 1, params); + + this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + this.paintStyle = this.endpoints[0].connectionStyle || this.endpoints[1].connectionStyle || params.paintStyle || jsPlumb.Defaults.PaintStyle; + + _updateOffset(this.sourceId); + _updateOffset(this.targetId); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var anchorLoc = this.endpoints[0].anchor.compute([myOffset.left, myOffset.top], myWH); + this.endpoints[0].paint(anchorLoc); + myOffset = offsets[this.targetId]; myWH = sizes[this.targetId]; + anchorLoc = this.endpoints[1].anchor.compute([myOffset.left, myOffset.top], myWH); + this.endpoints[1].paint(anchorLoc); + + // *************** create canvas on which the connection will be drawn ************ + var canvas = _newCanvas(jsPlumb.connectorClass); + this.canvas = canvas; + + /** + * paints the connection. + * @param elId Id of the element that is in motion + * @param ui current library's event system ui object (present if we came from a drag to get here) + * @param recalc whether or not to recalculate element sizes. this is true if a repaint caused this to be painted. + */ + this.paint = function(elId, ui, recalc) { + + if (log) log.debug("Painting Connection; element in motion is " + elId + "; ui is [" + ui + "]; recalc is [" + recalc + "]"); + + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = !(elId == this.sourceId); + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + if (this.canvas.getContext) { + + _updateOffset(elId, ui, recalc); + if (recalc) _updateOffset(tId); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var myOffset = offsets[elId]; + var otherOffset = offsets[tId]; + var myWH = sizes[elId]; + var otherWH = sizes[tId]; + + var ctx = canvas.getContext('2d'); + var sAnchorP = this.endpoints[sIdx].anchor.compute([myOffset.left, myOffset.top], myWH, [otherOffset.left, otherOffset.top], otherWH); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.compute([otherOffset.left, otherOffset.top], otherWH, [myOffset.left, myOffset.top], myWH); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, this.paintStyle.lineWidth); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + jsPlumb.extend(ctx, this.paintStyle); + + if (this.paintStyle.gradient && !ie) { + var g = swap ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + for (var i = 0; i < this.paintStyle.gradient.stops.length; i++) + g.addColorStop(this.paintStyle.gradient.stops[i][0],this.paintStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + + this.connector.paint(dim, ctx); + } + }; + + this.repaint = function() { + this.paint(this.sourceId, null, true); + }; + + _initDraggableIfNecessary(self.source, self.sourceId, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, self.targetId, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + }; + + /* + Class: Endpoint + + Models an endpoint. Can have one to N connections emanating from it (although how to handle that in the UI is + a very good question). also has a Canvas and paint style. + + */ + + /* + Function: Endpoint + + This is the Endpoint class constructor. + + Parameters: + + anchor - anchor for the endpoint, of type jsPlumb.Anchor. may be null. + endpoint - endpoint object, of type jsPlumb.Endpoint. may be null. + style - endpoint style, a js object. may be null. + source - element the endpoint is attached to, of type jquery object. Required. + canvas - canvas element to use. may be, and most often is, null. + connections - optional list of connections to configure the endpoint with. + isSource - boolean. indicates the endpoint can act as a source of new connections. optional. + dragOptions - if isSource is set to true, you can supply arguments for the jquery draggable method. optional. + connectionStyle - if isSource is set to true, this is the paint style for connections from this endpoint. optional. + connector - optional connector type to use. + isTarget - boolean. indicates the endpoint can act as a target of new connections. optional. + dropOptions - if isTarget is set to true, you can supply arguments for the jquery droppable method. optional. + reattach - optional boolean that determines whether or not the connections reattach after they + have been dragged off an endpoint and left floating. defaults to false: connections + dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + params = params || {}; + // make a copy. then we can use the wrapper function. + jsPlumb.extend({}, params); + var self = this; + self.anchor = params.anchor || jsPlumb.Anchors.TopCenter; + var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot(); + var _style = params.style || jsPlumb.Defaults.EndpointStyle; + this.connectionStyle = params.connectionStyle; + this.connector = params.connector; + var _element = params.source; + var _elementId = _getAttribute(_element, "id"); + var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of. + this.canvas = params.canvas || _newCanvas(jsPlumb.endpointClass); + this.connections = params.connections || []; + var _reattach = params.reattach || false; + var floatingEndpoint = null; + this.addConnection = function(connection) { + self.connections.push(connection); + }; + this.removeConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) + self.connections.splice(idx, 1); + }; + /** + * returns whether or not this endpoint is connected to the given endpoint. + * @param endpoint Endpoint to test. + * @since 1.1.1 + * + * todo: needs testing. will this work if the endpoint passed in is the source? + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + this.isFloating = function() { return floatingEndpoint != null; }; + /** + * first pass at default ConnectorSelector: returns the first connection, if we have any. + * modified a little, 5/10/2010: now it only returns a connector if we have not got equal to or more than _maxConnector connectors + * attached. otherwise it is assumed a new connector is ok. but note with this setup we can't select any other connection than the first + * one. what if this could return a list? that implies everything should work with a list - dragging etc. could be nasty. could also + * be cool. + */ + var connectorSelector = function() { + return self.connections.length == 0 || self.connections.length < _maxConnections ? null : self.connections[0]; + }; + + this.isFull = function() { return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); }; + + /** + * paints the Endpoint, recalculating offset and anchor positions if necessary. + */ + this.paint = function(anchorPoint, connectorPaintStyle, canvas) { + + if (log) log.debug("Painting Endpoint with elementId [" + _elementId + "]; anchorPoint is [" + anchorPoint + "]"); + + if (anchorPoint == null) { + // do we always want to force a repaint here? i dont think so! + var xy = offsets[_elementId]; + var wh = sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset(_elementId); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + anchorPoint = self.anchor.compute([xy.left, xy.top], wh); + } + _endpoint.paint(anchorPoint, self.anchor.getOrientation(), canvas || self.canvas, _style, connectorPaintStyle || _style); + }; + + + // is this a connection source? we make it draggable and have the drag listener + // maintain a connection with a floating endpoint. + if (params.isSource && _isDragSupported(_element)) { + + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + + // first the question is, how many connections are on this endpoint? if it's only one, then excellent. otherwise we will either need a way + // to select one connection from the list, or drag them all. if we had a pluggable 'ConnectorSelector' interface we could probably + // provide a way for people to implement their own UI components to do the connector selection. the question in that particular case would be how much + // information the interface needs from jsPlumb at execution time. if, however, we leave out the connector selection, and drag them all, + // that wouldn't be too hard to organise. perhaps that behaviour would be on a switch for the endpoint, or perhaps the ConnectorSelector + // interface returns a List, with the default implementation just returning everything. i think i like that. + // + // let's say for now that there is just one endpoint, cos we need to get this going before we can consider a list of them anyway. + // the major difference between that case and the case of a new endpoint is actually quite small - it's a question of where the + // Connection comes from. for a new one, we create a new one. obviously. otherwise we just get the jpc from the Endpoint + // (remember we're only assuming one connection right now). so all of the UI stuff we do to create the floating endpoint etc + // will still be valid, but when we stop dragging, we'll have to do something different. if we stop with a valid drop i think it will + // be the same process. but if we stop with an invalid drop we have to reset the Connection to how it was when we got it. + var start = function() { + n = document.createElement("div"); + _appendCanvas(n); + // create and assign an id, and initialize the offset. + var id = "" + new String(new Date().getTime()); + _setAttribute(_getElementObject(n), "id", id); + _updateOffset(id); + // store the id of the dragging div and the source element. the drop function + // will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor({reference:self.anchor}); + floatingEndpoint = new Endpoint({ + style:_style, + endpoint:_endpoint, + anchor:floatingAnchor, + source:n + }); + + jpc = connectorSelector(); + if (jpc == null) { + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = new Connection({ + sourceEndpoint:self, + targetEndpoint:floatingEndpoint, + source:_getElementObject(_element), + target:_getElementObject(n), + anchors:[self.anchor, floatingAnchor], + paintStyle : params.connectionStyle, // this can be null. Connection will use the default. + connector: params.connector + }); + // todo ...unregister on stop + self.addConnection(jpc); + } else { + existingJpc = true; + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; + // probably we should remove the connection? and add it back if the user + // does not drop it somewhere proper. + self.removeConnection(jpc); + if (anchorIdx == 0){ + existingJpcParams = [jpc.source, jpc.sourceId]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + }else { + existingJpcParams = [jpc.target, jpc.targetId]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // todo unregister on stop + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + }; + + var dragOptions = params.dragOptions || { }; + + var defaultOpts = jsPlumb.extend({}, jsPlumb.CurrentLibrary.defaultDragOptions); + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + var startEvent = jsPlumb.CurrentLibrary.dragEvents['start']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements([floatingEndpoint.canvas, n]); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + if (_reattach) { + jpc.floatingAnchorIndex = null; + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } + else { + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeElement(jpc.canvas); + } + } else { + _removeElement(jpc.canvas); + self.removeConnection(jpc); + } + } + jpc = null; + delete floatingEndpoint; + } + ); + + var i = _getElementObject(self.canvas); + _initDraggable(i, dragOptions); + }; + + // connector target + if (params.isTarget && _isDropSupported(_element)) { + var dropOptions = params.dropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend({}, dropOptions); + var originalAnchor = null; + dropOptions.drop = _wrap(dropOptions.drop, function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable), "dragId"); + var elId = _getAttribute(_getElementObject(draggable),"elId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // remove this jpc from the current endpoint + jpc.endpoints[idx].removeConnection(jpc); + if (jpc.suspendedEndpoint) + jpc.suspendedEndpoint.removeConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + _initDraggableIfNecessary(_element, _elementId, params.draggable, {}); + jsPlumb.repaint(elId); + + delete floatingConnections[id]; + }); + // what to do when something is dropped. + // 1. find the jpc that is being dragged. the target endpoint of the jpc will be the + // one that is being dragged. + // 2. arrange for the floating endpoint to be replaced with this endpoint; make sure + // everything gets registered ok etc. + // 3. arrange for the floating endpoint to be deleted. + // 4. make sure that the stop method of the drag does not cause the jpc to be cleaned up. we want to keep it now. + + // other considerations: when in the hover mode, we should switch the floating endpoint's + // orientation to be the same as the drop target. this will cause the connector to snap + // into the shape it will take if the user drops at that point. + + dropOptions.over = _wrap(dropOptions.over, function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + }); + + dropOptions.out = _wrap(dropOptions.out, function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + }); + + _initDroppable(_getElementObject(self.canvas), dropOptions); + } + + return self; + }; + + /* + Class: jsPlumb + + This is the main entry point to jsPlumb. There are a bunch of static methods you can + use to connect/disconnect etc. Some methods are also added to jQuery's "$.fn" object itself, but + in general jsPlumb is moving away from that model, preferring instead the static class + approach. One of the reasons for this is that with no methods added to the jQuery $.fn object, + it will be easier to provide support for other libraries such as MooTools. + */ + var jsPlumb = window.jsPlumb = { + + /* + Property: Defaults + + These are the default settings for jsPlumb, that is what will be used if you do not supply specific pieces of information + to the various API calls. A convenient way to implement your own look and feel can be to override these defaults by including a script + somewhere after the jsPlumb include, but before you make any calls to jsPlumb, for instance in this example we set the PaintStyle to be + a blue line of 27 pixels: + + > jsPlumb.Defaults.PaintStyle = { lineWidth:27, strokeStyle:'blue' } + + */ + Defaults : { + Anchors : [ null, null ], + Connector : null, + DragOptions: { }, + DropOptions: { }, + Endpoint : null, + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : null }, + EndpointStyles : [ null, null ], + MaxConnections : null, + PaintStyle : { lineWidth : 10, strokeStyle : 'red' } + }, + + /* + Property: connectorClass + + The CSS class to set on Connection canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + connectorClass : '_jsPlumb_connector', + + /* + Property: endpointClass + + The CSS class to set on Endpoint canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + endpointClass : '_jsPlumb_endpoint', + + /* + Property: Anchors + + Default jsPlumb Anchors. These are supplied in the file jquery.jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form jquery.jsPlumb-all-x.x.x.js. You can provide your own Anchors by supplying them in a script that is loaded after jsPlumb, for instance: + + > jsPlumb.Anchors.MyAnchor = { ....anchor code here. see the documentation. } + */ + Anchors : {}, + + /* + Property: Connectors + + Default jsPlumb Connectors. These are supplied in the file jquery.jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form jquery.jsPlumb-all-x.x.x.js. You can provide your own Connectors by supplying them in a script that is loaded after jsPlumb, for instance: + + > jsPlumb.Connectors.MyConnector = { ....connector code here. see the documentation. } + */ + Connectors : {}, + + /* + Property: Endpoints + + Default jsPlumb Endpoints. These are supplied in the file jquery.jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form jquery.jsPlumb-all-x.x.x.js. You can provide your own Endpoints by supplying them in a script that is loaded after jsPlumb, for instance: + + > jsPlumb.Endpoints.MyEndpoint = { ....endpoint code here. see the documentation. } + */ + Endpoints : {}, + + /* + Function: addEndpoint + + Adds an Endpoint to a given element. + + Parameters: + target - Element to add the endpoint to. either an element id, or a jQuery object representing some element. + params - Object containing Endpoint options (more info required) + + Returns: + + The newly created Endpoint. + + See Also: + + + */ + addEndpoint : function(target, params) { + params = jsPlumb.extend({}, params); + var el = _getElementObject(target); + var id = _getAttribute(target,"id"); + params.source = el; + _updateOffset(id); + var e = new Endpoint(params); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id]; + var myWH = sizes[id]; + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH); + e.paint(anchorLoc); + return e; + }, + + /* + Function: addEndpoint + + Adds a list of Endpoints to a given element. + + Parameters: + + target - element to add the endpoint to. either an element id, or a jQuery object representing some element. + endpoints - List of objects containing Endpoint options. one Endpoint is created for each entry in this list. + + Returns: + + List of newly created Endpoints, one for each entry in the 'endpoints' argument. + + See Also: + + + */ + addEndpoints : function(target, endpoints) { + var results = []; + for (var i = 0; i < endpoints.length; i++) { + results.push(jsPlumb.addEndpoint(target, endpoints[i])); + } + return results; + }, + + /* + Function: animate + + Wrapper around standard jQuery animate function; injects a call to jsPlumb in the 'step' function (creating it if necessary). + This only supports the two-arg version of the animate call in jQuery - the one that takes an 'options' object as the second arg. + + Parameters: + + el - Element to animate. Either an id, or a jQuery object representing the element. + properties - The 'properties' argument you want passed to the standard jQuery animate call. + options - The 'options' argument you want passed to the standard jQuery animate call. + + Returns: + + void + */ + animate : function(el, properties, options) { + var ele = _getElementObject(el); + var id = _getAttribute(el,"id"); + options = options || {}; + options.step = _wrap(options.step, function() { jsPlumb.repaint(id); }); + _animate(ele, properties, options); + }, + + /* + Function: connect + + Establishes a connection between two elements. + + Parameters: + + params - Object containing setup for the connection. see documentation. + + Returns: + + The newly created Connection. + + */ + connect : function(params) { + if (params.sourceEndpoint && params.sourceEndpoint.isFull()) { + _log("could not add connection; source endpoint is full"); + return; + } + + if (params.targetEndpoint && params.targetEndpoint.isFull()) { + _log("could not add connection; target endpoint is full"); + return; + } + + var jpc = new Connection(params); + + // register endpoints for the element. todo: is the test below sufficient? or should we test if the endpoint is already in the list, + // and add it only then? perhaps _addToList could be overloaded with a a 'test for existence first' parameter? + //_addToList(endpointsByElement, jpc.sourceId, jpc.endpoints[0]); + //_addToList(endpointsByElement, jpc.targetId, jpc.endpoints[1]); + + if (!params.sourceEndpoint) _addToList(endpointsByElement, jpc.sourceId, jpc.endpoints[0]); + if (!params.targetEndpoint) _addToList(endpointsByElement, jpc.targetId, jpc.endpoints[1]); + + jpc.endpoints[0].addConnection(jpc); + jpc.endpoints[1].addConnection(jpc); + + // force a paint + _draw(jpc.source); + + return jpc; + + }, + + /** + * not implemented yet. params object will have sourceEndpoint and targetEndpoint members; these will be Endpoints. + connectEndpoints : function(params) { + var jpc = Connection(params); + + },*/ + + /* + Function: detach + + Removes a connection. + + Parameters: + + sourceId - Id of the first element in the connection. A String. + targetId - iI of the second element in the connection. A String. + + Returns: + + true if successful, false if not. + */ + detach : function(sourceId, targetId) { + var f = function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + return true; + } + }; + + // todo: how to cleanup the actual storage? a third arg to _operation? + _operation(sourceId, f); + }, + + /* + Function: detachAll + + Removes all an element's connections. + + Parameters: + + el - either the id of the element, or a jQuery object for the element. + + Returns: + + void + */ + detachAll : function(el) { + var id = _getAttribute(el, "id"); + var f = function(jpc) { + // todo replace with _cleanupConnection call here. + _removeElement(jpc.canvas); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + }; + _operation(id, f); + //delete endpointsByElement[id]; ?? + }, + + /* + Function: detachEverything + + Remove all Connections from all elements, but leaves Endpoints in place. + + Returns: + + void + + See Also: + + + */ + detachEverything : function() { + var f = function(jpc) { + _removeElement(jpc.canvas); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + }; + + _operationOnAll(f); + + /*delete endpointsByElement; //?? + endpointsByElement = {};*/ //?? + }, + + extend : function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }, + + /* + Function: hide + + Sets an element's connections to be hidden. + + Parameters: + + el - either the id of the element, or a jQuery object for the element. + + Returns: + + void + */ + hide : function(el) { + _setVisible(el, "none"); + }, + + /* + Function: makeAnchor + + Creates an anchor with the given params. + + Parameters: + + x - the x location of the anchor as a fraction of the total width. + y - the y location of the anchor as a fraction of the total height. + xOrientation - value indicating the general direction a connection from the anchor should go in, in the x direction. + yOrientation - value indicating the general direction a connection from the anchor should go in, in the y direction. + xOffset - a fixed offset that should be applied in the x direction that should be applied after the x position has been figured out. optional. defaults to 0. + yOffset - a fixed offset that should be applied in the y direction that should be applied after the y position has been figured out. optional. defaults to 0. + + Returns: + + The newly created Anchor. + */ + makeAnchor : function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards compatibility if we are given only one value we assume it's a call in the old form. + var params = {}; + if (arguments.length == 1) jsPlumb.extend(params, x); + else { + params = {x:x, y:y}; + if (arguments.length >= 4) { + params.orientation = [arguments[2], arguments[3]]; + } + if (arguments.length == 6) params.offsets = [arguments[4], arguments[5]]; + } + return new Anchor(params); + }, + + + /* + Function: repaint + + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + + el - either the id of the element or a jQuery object representing the element. + + Returns: + + void + + See Also: + + + */ + repaint : function(el) { + + var _processElement = function(el) { + var ele = _getElementObject(el); + _draw(ele); + }; + + // TODO: support a jQuery result object too! + + // support both lists... + if (typeof el =='object') { + for (var i = 0; i < el.length; i++) + _processElement(el[i]); + } // ...and single strings. + else _processElement(el); + }, + + /* + Function: repaintEverything + + Repaints all connections. + + Returns: + + void + + See Also: + + + */ + repaintEverything : function() { + for (var elId in endpointsByElement) { + _draw(_getElementObject(elId)); + } + }, + + /* + Function: removeAllEndpoints + + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + + el - either an element id, or a jQuery object for an element. + + Returns: + + void + + See Also: + + + */ + removeAllEndpoints : function(el) { + var elId = _getAttribute(el, "id"); + // first remove all Connections. + jsPlumb.detachAll(elId); + var ebe = endpointsByElement[elId]; + for (var i in ebe) { + _removeElement(ebe[i].canvas); + } + endpointsByElement[elId] = []; + }, + + /* + Function: removeEndpoint + + Removes the given Endpoint from the given element. + + Parameters: + + el - either an element id, or a jQuery object for an element. + endpoint - Endpoint to remove. this is an Endpoint object, such as would have been returned from a call to addEndpoint. + + Returns: + + void + + See Also: + + + */ + removeEndpoint : function(el, endpoint) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + if (ebe) { + if(_removeFromList(endpointsByElement, elId, endpoint)) + _removeElement(endpoint.canvas); + } + }, + + /* + Function: setAutomaticRepaint + + Sets/unsets automatic repaint on window resize. + + Parameters: + + value - whether or not to automatically repaint when the window is resized. + + Returns: + + void + */ + setAutomaticRepaint : function(value) { + automaticRepaint = value; + }, + + /* + Function: setDefaultNewCanvasSize + + Sets the default size jsPlumb will use for a new canvas (we create a square canvas so one value is all that is required). + This is a hack for IE, because ExplorerCanvas seems to need for a canvas to be larger than what you are going to draw on + it at initialisation time. The default value of this is 1200 pixels, which is quite large, but if for some reason you're + drawing connectors that are bigger, you should adjust this value appropriately. + + Parameters: + + size - The default size to use. jsPlumb will use a square canvas so you need only supply one value. + + Returns: + + void + */ + setDefaultNewCanvasSize : function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }, + + /* + Function: setDraggable + + Sets whether or not a given element is draggable, regardless of what any plumb command may request. + + Parameters: + + el - either the id for the element, or a jQuery object representing the element. + + Returns: + + void + */ + setDraggable: _setDraggable, + + /* + Function: setDraggableByDefault + + Sets whether or not elements are draggable by default. Default for this is true. + + Parameters: + draggable - value to set + + Returns: + + void + */ + setDraggableByDefault: function(draggable) { + _draggableByDefault = draggable; + }, + + setDebugLog: function(debugLog) { + log = debugLog; + }, + + /* + Function: setRepaintFunction + + Sets the function to fire when the window size has changed and a repaint was fired. + + Parameters: + f - Function to execute. + + Returns: + void + */ + setRepaintFunction : function(f) { + repaintFunction = f; + }, + + /* + Function: show + + Sets an element's connections to be visible. + + Parameters: + el - either the id of the element, or a jQuery object for the element. + + Returns: + void + */ + show : function(el) { + _setVisible(el, "block"); + }, + + /* + Function: sizeCanvas + + Helper to size a canvas. You would typically use this when writing your own Connector or Endpoint implementation. + + Parameters: + x - [int] x position for the Canvas origin + y - [int] y position for the Canvas origin + w - [int] width of the canvas + h - [int] height of the canvas + + Returns: + void + */ + sizeCanvas : function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; canvas.height = h; + canvas.style.width = w + "px"; canvas.width = w; + canvas.style.left = x + "px"; canvas.style.top = y + "px"; + } + }, + + /** + * gets some test hooks. nothing writable. + */ + getTestHarness : function() { + return { + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + } + }; + }, + + /** + * Toggles visibility of an element's connections. kept for backwards compatibility + */ + toggle : _toggleVisible, + + /* + Function: toggleVisible + + Toggles visibility of an element's connections. + + Parameters: + el - either the element's id, or a jQuery object representing the element. + + Returns: + void, but should be updated to return the current state + */ + //TODO: update this method to return the current state. + toggleVisible : _toggleVisible, + + /* + Function: toggleDraggable + + Toggles draggability (sic) of an element's connections. + + Parameters: + el - either the element's id, or a jQuery object representing the element. + + Returns: + The current draggable state. + */ + toggleDraggable : _toggleDraggable, + + /* + Function: unload + + Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element + + Returns: + void + */ + unload : function() { + delete endpointsByElement; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + } + }; + +})(); + +// jQuery plugin code +(function($){ + /** + * plumbs the results of the selector to some target, using the given options if supplied, + * or the defaults otherwise. this is DEPRECATED. just use jsPlumb.connect(..) instead. + * @deprecated + */ + $.fn.plumb = function(options) { + var options = $.extend({}, options); + + return this.each(function() + { + var params = $.extend({source:$(this)}, options); + jsPlumb.connect(params); + }); + }; + + /** + * detaches the results of the selector from the given target or list of targets - 'target' + * may be a String or a List. + */ + $.fn.detach = function(target) { + return this.each(function() + { + var id = $(this).attr("id"); + if (typeof target == 'string') target = [target]; + for (var i = 0; i < target.length; i++) + jsPlumb.detach(id, target[i]); + }); + }; + + /** + * detaches the results from the selector from all connections. + */ + $.fn.detachAll = function() { + return this.each(function() + { + var id = $(this).attr("id"); + jsPlumb.detachAll(id); + }); + }; + + /** + * adds an endpoint to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoint = function(options) { + var addedEndpoints = []; + this.each(function() + { + addedEndpoints.push(jsPlumb.addEndpoint($(this).attr("id"), options)); + }); + return addedEndpoints[0]; + }; + + /** + * adds a list of endpoints to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoints = function(endpoints) { + var addedEndpoints = []; + return this.each(function() + { + var e = jsPlumb.addEndpoints($(this).attr("id"), endpoints); + for (var i = 0; i < e.length; i++) addedEndpoints.push(e[i]); + }); + return addedEndpoints; + }; + + /** + * remove the endpoint, if it exists, deleting its UI elements etc. + */ + $.fn.removeEndpoint = function(endpoint) { + this.each(function() + { + jsPlumb.removeEndpoint($(this).attr("id"), endpoint); + }); + }; + +})(jQuery); + + +/* the library agnostic functions, such as find offset, get id, get attribute, extend etc. currently +hardcoded to jQuery here; will be extracted to separate impls for different libraries. + +* much more work needs to be done on this. some things that spring to mind: animation, drag, drop. +* +* you should _never_ call these methods directly. jsPlumb uses them when it needs them. +* +*/ +(function() { + + jsPlumb.CurrentLibrary = { + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag' + }, + + /** + * default drag options for jQuery. + */ + defaultDragOptions : { opacity:0.5, revert:true, helper:'clone' }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + getElementObject : function(el) { + return typeof(el)=='string' ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options) { + if (el.length > 1) alert('poo'); + el.draggable(options); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + el.droppable(options); + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[1]; + return ui.absolutePosition || ui.offset; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + } + }; +})(); \ No newline at end of file diff --git a/archive/1.1.1/jquery.jsPlumb-all-1.1.1-min.js b/archive/1.1.1/jquery.jsPlumb-all-1.1.1-min.js new file mode 100644 index 000000000..5b3d24a72 --- /dev/null +++ b/archive/1.1.1/jquery.jsPlumb-all-1.1.1-min.js @@ -0,0 +1,39 @@ +(function(){function e(){o&&f()}var h=/MSIE/.test(navigator.userAgent)&&!window.opera,d=null,f=function(){j.repaintEverything()},o=true,m=null;$(window).bind("resize",function(){m&&clearTimeout(m);m=setTimeout(e,100)});var g={},l=[],s={},r={},y=true,z=[],H=1200,I=function(a,b,c){var i=function(q,t){if(q===t)return true;else if(typeof q=="object"&&typeof t=="object"){var u=true;for(var v in q)if(!i(q[v],t[v])){u=false;break}for(v in t)if(!i(t[v],q[v])){u=false;break}return u}};c=+c||0;for(var p=a.length;c< +p;c++)if(i(a[c],b))return c;return-1},E=function(a,b,c){var i=a[b];if(i==null){i=[];a[b]=i}i.push(c)},O=function(a,b){var c=C(a,"id"),i=g[c];if(i){J(c,b);for(var p=l[c],q=z[c],t=0;t=0){delete a[c];a.splice(c,1);return true}}return false},W=function(a,b){var c=C(a,"id");P(c,function(i){i.canvas.style.display=b})},X=function(a){P(a,function(b){b.canvas.style.display="none"==b.canvas.style.display? +"block":"none"})},J=function(a,b,c){if(c||b==null){w(a);b=j.CurrentLibrary.getElementObject(a);b=j.CurrentLibrary.getSize(b);z[a]=b;b=j.CurrentLibrary.getElementObject(a);b=j.CurrentLibrary.getOffset(b);l[a]=b}else l[a]=b},K=function(a,b){a=a||function(){};return function(){b.apply(this,arguments);a.apply(this,arguments)}},da=function(a){var b=this;this.x=a.x||0;this.y=a.y||0;var c=a.orientation||[0,0];this.offsets=a.offsets||[0,0];this.compute=function(i,p){return[i[0]+b.x*p[0]+b.offsets[0],i[1]+ +b.y*p[1]+b.offsets[1]]};this.getOrientation=function(){return c}},ea=function(a){var b=a.reference,c=0,i=0,p=null;this.compute=function(q){i=c=0;return[q[0],q[1]]};this.getOrientation=function(){if(p)return p;else{var q=b.getOrientation();return[Math.abs(q[0])*c*-1,Math.abs(q[1])*i*-1]}};this.over=function(q){p=q.getOrientation()};this.out=function(){p=null}},Y=function(a){var b=this;this.source=w(a.source);this.target=w(a.target);this.sourceId=C(this.source,"id");this.targetId=C(this.target,"id"); +this.endpointsOnTop=a.endpointsOnTop!=null?a.endpointsOnTop:true;this.endpoints=[];this.endpointStyles=[];var c=function(q,t,u){if(q)b.endpoints[t]=q;else{if(!u.endpoints)u.endpoints=[null,null];q=u.endpoints[t]||u.endpoint||j.Defaults.Endpoints[t]||j.Defaults.Endpoint||new j.Endpoints.Dot;if(!u.endpointStyles)u.endpointStyles=[null,null];b.endpoints[t]=new S({style:u.endpointStyles[t]||u.endpointStyle||j.Defaults.EndpointStyles[t]||j.Defaults.EndpointStyle,endpoint:q,connections:[b],anchor:u.anchors? +u.anchors[t]:j.Defaults.Anchors[t]||j.Anchors.BottomCenter})}};c(a.sourceEndpoint,0,a);c(a.targetEndpoint,1,a);this.connector=this.endpoints[0].connector||this.endpoints[1].connector||a.connector||j.Defaults.Connector||new j.Connectors.Bezier;this.paintStyle=this.endpoints[0].connectionStyle||this.endpoints[1].connectionStyle||a.paintStyle||j.Defaults.PaintStyle;J(this.sourceId);J(this.targetId);c=l[this.sourceId];var i=z[this.sourceId];c=this.endpoints[0].anchor.compute([c.left,c.top],i);this.endpoints[0].paint(c); +c=l[this.targetId];i=z[this.targetId];c=this.endpoints[1].anchor.compute([c.left,c.top],i);this.endpoints[1].paint(c);var p=U(j.connectorClass);this.canvas=p;this.paint=function(q,t,u){d&&d.debug("Painting Connection; element in motion is "+q+"; ui is ["+t+"]; recalc is ["+u+"]");var v=q!=this.sourceId,A=v?this.sourceId:this.targetId,k=v?0:1,F=v?1:0;if(this.canvas.getContext){J(q,t,u);u&&J(A);t=l[q];u=l[A];q=z[q];var G=z[A];A=p.getContext("2d");var x=this.endpoints[F].anchor.compute([t.left,t.top], +q,[u.left,u.top],G);this.endpoints[F].anchor.getOrientation();t=this.endpoints[k].anchor.compute([u.left,u.top],G,[t.left,t.top],q);this.endpoints[k].anchor.getOrientation();k=this.connector.compute(x,t,this.endpoints[F].anchor,this.endpoints[k].anchor,this.paintStyle.lineWidth);j.sizeCanvas(p,k[0],k[1],k[2],k[3]);j.extend(A,this.paintStyle);if(this.paintStyle.gradient&&!h){v=v?A.createLinearGradient(k[4],k[5],k[6],k[7]):A.createLinearGradient(k[6],k[7],k[4],k[5]);for(F=0;F=0&&b.connections.splice(n,1)};this.isConnectedTo=function(n){var D=false;if(n)for(var B=0;B=t};this.paint=function(n,D,B){d&&d.debug("Painting Endpoint with elementId ["+q+"]; anchorPoint is ["+n+"]");if(n==null){n=l[q];var M=z[q];if(n==null||M==null){J(q);n=l[q];M=z[q]}n=b.anchor.compute([n.left,n.top],M)}c.paint(n,b.anchor.getOrientation(),B||b.canvas,i,D||i)};if(a.isSource&&j.CurrentLibrary.isDragSupported(p)){var A=null,k=null,F=false,G=null,x=a.dragOptions||{},N=j.extend({},j.CurrentLibrary.defaultDragOptions);x=j.extend(N, +x);N=j.CurrentLibrary.dragEvents.start;var Z=j.CurrentLibrary.dragEvents.stop,aa=j.CurrentLibrary.dragEvents.drag;x[N]=K(x[N],function(){A=document.createElement("div");document.body.appendChild(A);var n=""+new String((new Date).getTime());Q(w(A),"id",n);J(n);Q(w(b.canvas),"dragId",n);Q(w(b.canvas),"elId",q);var D=new ea({reference:b.anchor});v=new S({style:i,endpoint:c,anchor:D,source:A});k=b.connections.length==0||b.connections.length=4)c.orientation=[arguments[2],arguments[3]];if(arguments.length==6)c.offsets=[arguments[4],arguments[5]]}return new da(c)},repaint:function(a){var b=function(i){i=w(i);O(i)};if(typeof a=="object")for(var c=0;c1&&alert("poo");e.draggable(h)},isDragSupported:function(e){return e.draggable},setDraggable:function(e,h){e.draggable("option","disabled",!h)},initDroppable:function(e,h){e.droppable(h)},isDropSupported:function(e){return e.droppable},animate:function(e,h,d){e.animate(h,d)},getUIPosition:function(e){e=e[1];return e.absolutePosition||e.offset},getDragObject:function(e){return e[1].draggable}}})(); +(function(){jsPlumb.Anchors.TopCenter=jsPlumb.makeAnchor(0.5,0,0,-1);jsPlumb.Anchors.BottomCenter=jsPlumb.makeAnchor(0.5,1,0,1);jsPlumb.Anchors.LeftMiddle=jsPlumb.makeAnchor(0,0.5,-1,0);jsPlumb.Anchors.RightMiddle=jsPlumb.makeAnchor(1,0.5,1,0);jsPlumb.Anchors.Center=jsPlumb.makeAnchor(0.5,0.5,0,0);jsPlumb.Anchors.TopRight=jsPlumb.makeAnchor(1,0,0,-1);jsPlumb.Anchors.BottomRight=jsPlumb.makeAnchor(1,1,0,1);jsPlumb.Anchors.TopLeft=jsPlumb.makeAnchor(0,0,0,-1);jsPlumb.Anchors.BottomLeft=jsPlumb.makeAnchor(0, +1,0,1);jsPlumb.Connectors.Straight=function(){this.compute=function(e,h,d,f,o){d=Math.abs(e[0]-h[0]);f=Math.abs(e[1]-h[1]);var m=0.45*d,g=0.45*f;d*=1.9;f*=1.9;var l=Math.min(e[0],h[0])-m,s=Math.min(e[1],h[1])-g;if(d<2*o){d=2*o;l=e[0]+(h[0]-e[0])/2-o;m=(d-Math.abs(e[0]-h[0]))/2}if(f<2*o){f=2*o;s=e[1]+(h[1]-e[1])/2-o;g=(f-Math.abs(e[1]-h[1]))/2}return[l,s,d,f,e[0]l)l=o;if(f<0){r+=f;f=Math.abs(f);l+=f;E[0]+=f;z+=f;I+=f;d[0]+=f}f=Math.min(Math.min(H,g),Math.min(E[1], +d[1]));o=Math.max(Math.max(H,g),Math.max(E[1],d[1]));if(o>s)s=o;if(f<0){y+=f;f=Math.abs(f);s+=f;E[1]+=f;H+=f;g+=f;d[1]+=f}return[r,y,l,s,z,H,I,g,E[0],E[1],d[0],d[1]]};this.paint=function(d,f){f.beginPath();f.moveTo(d[4],d[5]);f.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]);f.stroke()}};jsPlumb.Endpoints.Dot=function(e){e=e||{radius:10};var h=this;this.radius=e.radius;var d=0.5*this.radius,f=this.radius/3,o=function(m){try{return parseInt(m)}catch(g){if(m.substring(m.length-1)=="%")return parseInt(m.substring(0, +m-1))}};this.paint=function(m,g,l,s,r){var y=s.radius||h.radius;jsPlumb.sizeCanvas(l,m[0]-y,m[1]-y,y*2,y*2);m=l.getContext("2d");l=jsPlumb.extend({},s);if(l.fillStyle==null)l.fillStyle=r.strokeStyle;jsPlumb.extend(m,l);r=/MSIE/.test(navigator.userAgent)&&!window.opera;if(s.gradient&&!r){r=s.gradient;l=d;var z=f;if(r.offset)l=o(r.offset);if(r.innerRadius)z=o(r.innerRadius);r=[l,z];g=m.createRadialGradient(y,y,y,y+(g[0]==1?r[0]*-1:r[0]),y+(g[1]==1?r[0]*-1:r[0]),r[1]);for(r=0;r endpoint lists. an element can have an arbitrary number of endpoints on it, + * and not all of them have to be connected to anything. + */ + var endpointsByElement = {}; + var offsets = []; + var floatingConnections = {}; + var draggableStates = {}; + var _draggableByDefault = true; + var sizes = []; + + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + var _findIndex = function( a, v, b, s ) { + + var _eq = function(o1, o2) { + if (o1 === o2) return true; + else if (typeof o1 == 'object' && typeof o2 == 'object') { + var same = true; + for(var propertyName in o1) { + if(!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for(var propertyName in o2) { + if(!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for( var i = +b || 0, l = a.length; i < l; i++ ) { + if( _eq(a[i], v)) return i; + } + return -1; + }; + + /** + * helper method to add an item to a list, creating the list if it does not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + }; + + /** + * Handles the dragging of an element. + * @param element jQuery element + * @param ui UI object from current library's event system + */ + var _draw = function(element, ui) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints) { + _updateOffset(id, ui); + var myOffset = offsets[id]; + var myWH = sizes[id]; + // loop through endpoints for this element + for (var i = 0; i < endpoints.length; i++) { + var e = endpoints[i]; + var anchorLoc = endpoints[i].anchor.compute([myOffset.left, myOffset.top], myWH); + e.paint(anchorLoc); + var l = e.connections; + for (var j = 0; j < l.length; j++) + l[j].paint(id, ui); // ...and paint them. + } + } + }; + + /** + * helper function: the second argument is a function taking two args - the first is a + * jquery element, and the second is the element's id. + * + * the first argument may be one of three things: + * + * 1. a string, in the form "window1", for example. an element's id. if your id starts with a + * hash then jsPlumb does not append its own hash too... + * 2. a jquery element, already resolved using $(...). + * 3. a list of strings/jquery elements. + */ + var _elementProxy = function(element, fn) { + var retVal = null; + if (typeof element == 'object' && element.length) { + retVal = []; + for (var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]); + var id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } + else { + var el = _getElementObject(element); + var id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + + return retVal; + }; + + /** + * gets the named attribute from the given element (id or element object) + */ + var _getAttribute = function(el, attName) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + return jsPlumb.CurrentLibrary.getAttribute(ele, attName); + }; + + var _setAttribute = function(el, attName, attValue) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + jsPlumb.CurrentLibrary.setAttribute(ele, attName, attValue); + }; + + var _addClass = function(el, clazz) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + jsPlumb.CurrentLibrary.addClass(ele, clazz); + }; + + var _getElementObject = function(elId) { + return jsPlumb.CurrentLibrary.getElementObject(elId); + }; + + var _getOffset = function(el) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + return jsPlumb.CurrentLibrary.getOffset(ele); + }; + + var _getSize = function(el) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + return jsPlumb.CurrentLibrary.getSize(ele); + }; + + var _initDraggable = function(el, options) { + return jsPlumb.CurrentLibrary.initDraggable(el, options); + }; + + var _isDragSupported = function(el) { + return jsPlumb.CurrentLibrary.isDragSupported(el); + }; + + var _initDroppable = function(el, options) { + return jsPlumb.CurrentLibrary.initDroppable(el, options); + }; + + var _isDropSupported = function(el) { + return jsPlumb.CurrentLibrary.isDropSupported(el); + }; + + var _animate= function(el, properties, options) { + return jsPlumb.CurrentLibrary.animate(el, properties, options); + }; + + var _appendCanvas = function(canvas) { + document.body.appendChild(canvas); + }; + + /** + * gets an id for the given element, creating and setting one if necessary. + */ + var _getId = function(element) { + var id = _getAttribute(element, "id"); + if (!id) { + id = "_jsPlumb_" + new String((new Date()).getTime()); + _setAttribute(element, "id", id); + } + return id; + }; + + /** + * inits a draggable if it's not already initialised. + * todo: if the element was draggable already, like from some non-jsPlumb call, wrap the drag function. + */ + var _initDraggableIfNecessary = function(element, elementId, isDraggable, dragOptions) { + // dragging + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable && _isDragSupported(element)) { + var options = dragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend({}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var initDrag = function(element, elementId, dragFunc) { + options[dragEvent] = _wrap(options[dragEvent], dragFunc); + var draggable = draggableStates[elementId]; + options.disabled = draggable == null ? false : !draggable; + _initDraggable(element, options); + }; + initDrag(element, elementId, function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + } + }; + + var _log = function(msg) { + // not implemented. yet. + } + + /** + * helper to create a canvas. + * @param clazz optional class name for the canvas. + */ + var _newCanvas = function(clazz) { + var canvas = document.createElement("canvas"); + _appendCanvas(canvas); + canvas.style.position="absolute"; + if (clazz) { canvas.className=clazz; } + _getId(canvas); // set an id. + + if (ie) { + // for IE we have to set a big canvas size. actually you can override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + /** + * performs the given function operation on all the connections found for the given element + * id; this means we find all the endpoints for the given element, and then for each endpoint + * find the connectors connected to it. then we pass each connection in to the given + * function. + */ + var _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + for (var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }; + /** + * perform an operation on all elements. + */ + var _operationOnAll = function(func) { + for (var elId in endpointsByElement) { + _operation(elId, func); + } + }; + /** + * helper to remove an element from the DOM. + */ + var _removeElement = function(element) { + if (element != null) { + try { document.body.removeChild(element); } + catch (e) { } + } + }; + /** + * helper to remove a list of elements from the DOM. + */ + var _removeElements = function(elements) { + for (var i in elements) + _removeElement(elements[i]); + }; + /** + * helper method to remove an item from a list. + */ + var _removeFromList = function(map, key, value) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete( l[i] ); + l.splice( i, 1 ); + return true; + } + } + return false; + }; + /** + * Sets whether or not the given element(s) should be draggable, regardless of what a particular + * plumb command may request. + * + * @param element May be a string, a jQuery elements, or a list of strings/jquery elements. + * @param draggable Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + var _helper = function(el, id) { + draggableStates[id] = draggable; + if (_isDragSupported(el)) { + // TODO: not library agnostic yet. + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + //el.draggable("option", "disabled", !draggable); + } + }; + + return _elementProxy(element, _helper); + }; + /** + * private method to do the business of hiding/showing. + * @param el either Id of the element in question or a jquery object for the element. + * @param state String specifying a value for the css 'display' property ('block' or 'none'). + */ + var _setVisible = function(el, state) { + var elId = _getAttribute(el, "id"); + var f = function(jpc) { + jpc.canvas.style.display = state; + }; + _operation(elId, f); + }; + /** + * toggles the draggable state of the given element(s). + * @param el either an id, or a jquery object, or a list of ids/jquery objects. + */ + var _toggleDraggable = function(el) { + var fn = function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + //el.draggable("option", "disabled", !state); + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }; + return _elementProxy(el, fn); + }; + /** + * private method to do the business of toggling hiding/showing. + * @param elId Id of the element in question + */ + var _toggleVisible = function(elId) { + var f = function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }; + + _operation(elId, f); + + //todo this should call _elementProxy, and pass in the _operation(elId, f) call as a function. cos _toggleDraggable does that. + }; + /** + * updates the offset and size for a given element, and stores the values. + * if 'offset' is not null we use that (it would have been passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we use the offset, outerWidth and outerHeight methods to get + * the current values. + */ + var _updateOffset = function(elId, offset, recalc) { + if (recalc || offset == null) { // if forced repaint or no offset available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + sizes[elId] = _getSize(elId); + offsets[elId] = _getOffset(elId); + } else { + offsets[elId] = offset; + } + }; + + /** + * wraps one function with another, creating a placeholder for the wrapped function + * if it was null. this is used to wrap the various drag/drop event functions - to allow + * jsPlumb to be notified of important lifecycle events without imposing itself on the user's + * drap/drop functionality. + * TODO: determine whether or not we should try/catch the plumb function, so that the cascade function is always executed. + */ + var _wrap = function(cascadeFunction, plumbFunction) { + cascadeFunction = cascadeFunction || function() { }; + return function() { + plumbFunction.apply(this, arguments); + cascadeFunction.apply(this, arguments); + }; + } + + /* + Class: Anchor + Models a position relative to the origin of some element that an Endpoint can be located. + */ + /* + Function: Anchor + + Constructor for the Anchor class + + Parameters: + params - Anchor parameters. This should contain three values, and may optionally have an 'offsets' argument: + + - x : the x location of the anchor as a fraction of the total width. + - y : the y location of the anchor as a fraction of the total height. + - orientation : an [x,y] array indicating the general direction a connection from the anchor should go in. for more info on this, see the documentation, or the docs in jquery-jsPlumb-defaults-XXX.js for the default Anchors. + - offsets : an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. may be null. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; this.y = params.y || 0; + var orientation = params.orientation || [0,0]; + this.offsets = params.offsets || [0,0]; + this.compute = function(xy, wh, txy, twh) { + return [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + } + this.getOrientation = function() { return orientation; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from its position relative to the anchor it is floating relative to. + * + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for purposes of calculating the orientation. + var ref = params.reference; + // these are used to store the current relative position of our anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating anchor is hovering over another anchor. + var orientation = null; + + this.compute = function(xy, wh, txy, twh) { + // set these for the getOrientation method to use. + xDir = 0;//xy[0] < txy[0] ? -1 : xy[0] == txy[0] ? 0 : 1; + yDir = 0;//xy[1] < txy[1] ? -1 : xy[1] == txy[1] ? 0 : 1; + return [xy[0], xy[1]]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other anchor: if it declares zero for some direction, we declare zero too. + // this might not be the most awesome. perhaps we can come up with a better way. it's just so that the line we draw looks + // like it makes sense. maybe this wont make sense. + return [Math.abs(o[0]) * xDir * -1, Math.abs(o[1]) * yDir * -1]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering over another anchor; + * we want to assume that anchor's orientation for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no longer hovering + * over another anchor; we should resume calculating orientation as we normally do. + */ + this.out = function() { + orientation = null; + }; + }; + + // ************** connection + // **************************************** + /** + * allowed params: + * source: source element (string or a jQuery element) (required) + * target: target element (string or a jQuery element) (required) + * + * anchors: optional array of anchor placements. defaults to BottomCenter for source + * and TopCenter for target. + */ + var Connection = function(params) { + + // ************** get the source and target and register the connection. ******************* + var self = this; + // get source and target as jQuery objects + this.source = _getElementObject(params.source); + this.target = _getElementObject(params.target); + this.sourceId = _getAttribute(this.source, "id"); + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + + // init endpoints + this.endpoints = []; + this.endpointStyles = []; + var prepareEndpoint = function(existing, index, params) { + if (existing) self.endpoints[index] = existing; + else { + if(!params.endpoints) params.endpoints = [null,null]; + var ep = params.endpoints[index] || params.endpoint || jsPlumb.Defaults.Endpoints[index] || jsPlumb.Defaults.Endpoint|| new jsPlumb.Endpoints.Dot(); + if (!params.endpointStyles) params.endpointStyles = [null,null]; + var es = params.endpointStyles[index] || params.endpointStyle || jsPlumb.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyle; + var a = params.anchors ? params.anchors[index] : jsPlumb.Defaults.Anchors[index] || jsPlumb.Anchors.BottomCenter; + self.endpoints[index] = new Endpoint({style:es, endpoint:ep, connections:[self], anchor:a }); + } + }; + + prepareEndpoint(params.sourceEndpoint, 0, params); + prepareEndpoint(params.targetEndpoint, 1, params); + + this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + this.paintStyle = this.endpoints[0].connectionStyle || this.endpoints[1].connectionStyle || params.paintStyle || jsPlumb.Defaults.PaintStyle; + + _updateOffset(this.sourceId); + _updateOffset(this.targetId); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var anchorLoc = this.endpoints[0].anchor.compute([myOffset.left, myOffset.top], myWH); + this.endpoints[0].paint(anchorLoc); + myOffset = offsets[this.targetId]; myWH = sizes[this.targetId]; + anchorLoc = this.endpoints[1].anchor.compute([myOffset.left, myOffset.top], myWH); + this.endpoints[1].paint(anchorLoc); + + // *************** create canvas on which the connection will be drawn ************ + var canvas = _newCanvas(jsPlumb.connectorClass); + this.canvas = canvas; + + /** + * paints the connection. + * @param elId Id of the element that is in motion + * @param ui current library's event system ui object (present if we came from a drag to get here) + * @param recalc whether or not to recalculate element sizes. this is true if a repaint caused this to be painted. + */ + this.paint = function(elId, ui, recalc) { + + if (log) log.debug("Painting Connection; element in motion is " + elId + "; ui is [" + ui + "]; recalc is [" + recalc + "]"); + + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = !(elId == this.sourceId); + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + if (this.canvas.getContext) { + + _updateOffset(elId, ui, recalc); + if (recalc) _updateOffset(tId); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var myOffset = offsets[elId]; + var otherOffset = offsets[tId]; + var myWH = sizes[elId]; + var otherWH = sizes[tId]; + + var ctx = canvas.getContext('2d'); + var sAnchorP = this.endpoints[sIdx].anchor.compute([myOffset.left, myOffset.top], myWH, [otherOffset.left, otherOffset.top], otherWH); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.compute([otherOffset.left, otherOffset.top], otherWH, [myOffset.left, myOffset.top], myWH); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, this.paintStyle.lineWidth); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + jsPlumb.extend(ctx, this.paintStyle); + + if (this.paintStyle.gradient && !ie) { + var g = swap ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + for (var i = 0; i < this.paintStyle.gradient.stops.length; i++) + g.addColorStop(this.paintStyle.gradient.stops[i][0],this.paintStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + + this.connector.paint(dim, ctx); + } + }; + + this.repaint = function() { + this.paint(this.sourceId, null, true); + }; + + _initDraggableIfNecessary(self.source, self.sourceId, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, self.targetId, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + }; + + /* + Class: Endpoint + + Models an endpoint. Can have one to N connections emanating from it (although how to handle that in the UI is + a very good question). also has a Canvas and paint style. + + */ + + /* + Function: Endpoint + + This is the Endpoint class constructor. + + Parameters: + + anchor - anchor for the endpoint, of type jsPlumb.Anchor. may be null. + endpoint - endpoint object, of type jsPlumb.Endpoint. may be null. + style - endpoint style, a js object. may be null. + source - element the endpoint is attached to, of type jquery object. Required. + canvas - canvas element to use. may be, and most often is, null. + connections - optional list of connections to configure the endpoint with. + isSource - boolean. indicates the endpoint can act as a source of new connections. optional. + dragOptions - if isSource is set to true, you can supply arguments for the jquery draggable method. optional. + connectionStyle - if isSource is set to true, this is the paint style for connections from this endpoint. optional. + connector - optional connector type to use. + isTarget - boolean. indicates the endpoint can act as a target of new connections. optional. + dropOptions - if isTarget is set to true, you can supply arguments for the jquery droppable method. optional. + reattach - optional boolean that determines whether or not the connections reattach after they + have been dragged off an endpoint and left floating. defaults to false: connections + dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + params = params || {}; + // make a copy. then we can use the wrapper function. + jsPlumb.extend({}, params); + var self = this; + self.anchor = params.anchor || jsPlumb.Anchors.TopCenter; + var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot(); + var _style = params.style || jsPlumb.Defaults.EndpointStyle; + this.connectionStyle = params.connectionStyle; + this.connector = params.connector; + var _element = params.source; + var _elementId = _getAttribute(_element, "id"); + var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of. + this.canvas = params.canvas || _newCanvas(jsPlumb.endpointClass); + this.connections = params.connections || []; + var _reattach = params.reattach || false; + var floatingEndpoint = null; + this.addConnection = function(connection) { + self.connections.push(connection); + }; + this.removeConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) + self.connections.splice(idx, 1); + }; + /** + * returns whether or not this endpoint is connected to the given endpoint. + * @param endpoint Endpoint to test. + * @since 1.1.1 + * + * todo: needs testing. will this work if the endpoint passed in is the source? + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + this.isFloating = function() { return floatingEndpoint != null; }; + /** + * first pass at default ConnectorSelector: returns the first connection, if we have any. + * modified a little, 5/10/2010: now it only returns a connector if we have not got equal to or more than _maxConnector connectors + * attached. otherwise it is assumed a new connector is ok. but note with this setup we can't select any other connection than the first + * one. what if this could return a list? that implies everything should work with a list - dragging etc. could be nasty. could also + * be cool. + */ + var connectorSelector = function() { + return self.connections.length == 0 || self.connections.length < _maxConnections ? null : self.connections[0]; + }; + + this.isFull = function() { return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); }; + + /** + * paints the Endpoint, recalculating offset and anchor positions if necessary. + */ + this.paint = function(anchorPoint, connectorPaintStyle, canvas) { + + if (log) log.debug("Painting Endpoint with elementId [" + _elementId + "]; anchorPoint is [" + anchorPoint + "]"); + + if (anchorPoint == null) { + // do we always want to force a repaint here? i dont think so! + var xy = offsets[_elementId]; + var wh = sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset(_elementId); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + anchorPoint = self.anchor.compute([xy.left, xy.top], wh); + } + _endpoint.paint(anchorPoint, self.anchor.getOrientation(), canvas || self.canvas, _style, connectorPaintStyle || _style); + }; + + + // is this a connection source? we make it draggable and have the drag listener + // maintain a connection with a floating endpoint. + if (params.isSource && _isDragSupported(_element)) { + + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + + // first the question is, how many connections are on this endpoint? if it's only one, then excellent. otherwise we will either need a way + // to select one connection from the list, or drag them all. if we had a pluggable 'ConnectorSelector' interface we could probably + // provide a way for people to implement their own UI components to do the connector selection. the question in that particular case would be how much + // information the interface needs from jsPlumb at execution time. if, however, we leave out the connector selection, and drag them all, + // that wouldn't be too hard to organise. perhaps that behaviour would be on a switch for the endpoint, or perhaps the ConnectorSelector + // interface returns a List, with the default implementation just returning everything. i think i like that. + // + // let's say for now that there is just one endpoint, cos we need to get this going before we can consider a list of them anyway. + // the major difference between that case and the case of a new endpoint is actually quite small - it's a question of where the + // Connection comes from. for a new one, we create a new one. obviously. otherwise we just get the jpc from the Endpoint + // (remember we're only assuming one connection right now). so all of the UI stuff we do to create the floating endpoint etc + // will still be valid, but when we stop dragging, we'll have to do something different. if we stop with a valid drop i think it will + // be the same process. but if we stop with an invalid drop we have to reset the Connection to how it was when we got it. + var start = function() { + n = document.createElement("div"); + _appendCanvas(n); + // create and assign an id, and initialize the offset. + var id = "" + new String(new Date().getTime()); + _setAttribute(_getElementObject(n), "id", id); + _updateOffset(id); + // store the id of the dragging div and the source element. the drop function + // will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor({reference:self.anchor}); + floatingEndpoint = new Endpoint({ + style:_style, + endpoint:_endpoint, + anchor:floatingAnchor, + source:n + }); + + jpc = connectorSelector(); + if (jpc == null) { + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = new Connection({ + sourceEndpoint:self, + targetEndpoint:floatingEndpoint, + source:_getElementObject(_element), + target:_getElementObject(n), + anchors:[self.anchor, floatingAnchor], + paintStyle : params.connectionStyle, // this can be null. Connection will use the default. + connector: params.connector + }); + // todo ...unregister on stop + self.addConnection(jpc); + } else { + existingJpc = true; + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; + // probably we should remove the connection? and add it back if the user + // does not drop it somewhere proper. + self.removeConnection(jpc); + if (anchorIdx == 0){ + existingJpcParams = [jpc.source, jpc.sourceId]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + }else { + existingJpcParams = [jpc.target, jpc.targetId]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // todo unregister on stop + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + }; + + var dragOptions = params.dragOptions || { }; + + var defaultOpts = jsPlumb.extend({}, jsPlumb.CurrentLibrary.defaultDragOptions); + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + var startEvent = jsPlumb.CurrentLibrary.dragEvents['start']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements([floatingEndpoint.canvas, n]); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + if (_reattach) { + jpc.floatingAnchorIndex = null; + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } + else { + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeElement(jpc.canvas); + } + } else { + _removeElement(jpc.canvas); + self.removeConnection(jpc); + } + } + jpc = null; + delete floatingEndpoint; + } + ); + + var i = _getElementObject(self.canvas); + _initDraggable(i, dragOptions); + }; + + // connector target + if (params.isTarget && _isDropSupported(_element)) { + var dropOptions = params.dropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend({}, dropOptions); + var originalAnchor = null; + dropOptions.drop = _wrap(dropOptions.drop, function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable), "dragId"); + var elId = _getAttribute(_getElementObject(draggable),"elId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // remove this jpc from the current endpoint + jpc.endpoints[idx].removeConnection(jpc); + if (jpc.suspendedEndpoint) + jpc.suspendedEndpoint.removeConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + _initDraggableIfNecessary(_element, _elementId, params.draggable, {}); + jsPlumb.repaint(elId); + + delete floatingConnections[id]; + }); + // what to do when something is dropped. + // 1. find the jpc that is being dragged. the target endpoint of the jpc will be the + // one that is being dragged. + // 2. arrange for the floating endpoint to be replaced with this endpoint; make sure + // everything gets registered ok etc. + // 3. arrange for the floating endpoint to be deleted. + // 4. make sure that the stop method of the drag does not cause the jpc to be cleaned up. we want to keep it now. + + // other considerations: when in the hover mode, we should switch the floating endpoint's + // orientation to be the same as the drop target. this will cause the connector to snap + // into the shape it will take if the user drops at that point. + + dropOptions.over = _wrap(dropOptions.over, function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + }); + + dropOptions.out = _wrap(dropOptions.out, function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + }); + + _initDroppable(_getElementObject(self.canvas), dropOptions); + } + + return self; + }; + + /* + Class: jsPlumb + + This is the main entry point to jsPlumb. There are a bunch of static methods you can + use to connect/disconnect etc. Some methods are also added to jQuery's "$.fn" object itself, but + in general jsPlumb is moving away from that model, preferring instead the static class + approach. One of the reasons for this is that with no methods added to the jQuery $.fn object, + it will be easier to provide support for other libraries such as MooTools. + */ + var jsPlumb = window.jsPlumb = { + + /* + Property: Defaults + + These are the default settings for jsPlumb, that is what will be used if you do not supply specific pieces of information + to the various API calls. A convenient way to implement your own look and feel can be to override these defaults by including a script + somewhere after the jsPlumb include, but before you make any calls to jsPlumb, for instance in this example we set the PaintStyle to be + a blue line of 27 pixels: + + > jsPlumb.Defaults.PaintStyle = { lineWidth:27, strokeStyle:'blue' } + + */ + Defaults : { + Anchors : [ null, null ], + Connector : null, + DragOptions: { }, + DropOptions: { }, + Endpoint : null, + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : null }, + EndpointStyles : [ null, null ], + MaxConnections : null, + PaintStyle : { lineWidth : 10, strokeStyle : 'red' } + }, + + /* + Property: connectorClass + + The CSS class to set on Connection canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + connectorClass : '_jsPlumb_connector', + + /* + Property: endpointClass + + The CSS class to set on Endpoint canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + endpointClass : '_jsPlumb_endpoint', + + /* + Property: Anchors + + Default jsPlumb Anchors. These are supplied in the file jquery.jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form jquery.jsPlumb-all-x.x.x.js. You can provide your own Anchors by supplying them in a script that is loaded after jsPlumb, for instance: + + > jsPlumb.Anchors.MyAnchor = { ....anchor code here. see the documentation. } + */ + Anchors : {}, + + /* + Property: Connectors + + Default jsPlumb Connectors. These are supplied in the file jquery.jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form jquery.jsPlumb-all-x.x.x.js. You can provide your own Connectors by supplying them in a script that is loaded after jsPlumb, for instance: + + > jsPlumb.Connectors.MyConnector = { ....connector code here. see the documentation. } + */ + Connectors : {}, + + /* + Property: Endpoints + + Default jsPlumb Endpoints. These are supplied in the file jquery.jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form jquery.jsPlumb-all-x.x.x.js. You can provide your own Endpoints by supplying them in a script that is loaded after jsPlumb, for instance: + + > jsPlumb.Endpoints.MyEndpoint = { ....endpoint code here. see the documentation. } + */ + Endpoints : {}, + + /* + Function: addEndpoint + + Adds an Endpoint to a given element. + + Parameters: + target - Element to add the endpoint to. either an element id, or a jQuery object representing some element. + params - Object containing Endpoint options (more info required) + + Returns: + + The newly created Endpoint. + + See Also: + + + */ + addEndpoint : function(target, params) { + params = jsPlumb.extend({}, params); + var el = _getElementObject(target); + var id = _getAttribute(target,"id"); + params.source = el; + _updateOffset(id); + var e = new Endpoint(params); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id]; + var myWH = sizes[id]; + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH); + e.paint(anchorLoc); + return e; + }, + + /* + Function: addEndpoint + + Adds a list of Endpoints to a given element. + + Parameters: + + target - element to add the endpoint to. either an element id, or a jQuery object representing some element. + endpoints - List of objects containing Endpoint options. one Endpoint is created for each entry in this list. + + Returns: + + List of newly created Endpoints, one for each entry in the 'endpoints' argument. + + See Also: + + + */ + addEndpoints : function(target, endpoints) { + var results = []; + for (var i = 0; i < endpoints.length; i++) { + results.push(jsPlumb.addEndpoint(target, endpoints[i])); + } + return results; + }, + + /* + Function: animate + + Wrapper around standard jQuery animate function; injects a call to jsPlumb in the 'step' function (creating it if necessary). + This only supports the two-arg version of the animate call in jQuery - the one that takes an 'options' object as the second arg. + + Parameters: + + el - Element to animate. Either an id, or a jQuery object representing the element. + properties - The 'properties' argument you want passed to the standard jQuery animate call. + options - The 'options' argument you want passed to the standard jQuery animate call. + + Returns: + + void + */ + animate : function(el, properties, options) { + var ele = _getElementObject(el); + var id = _getAttribute(el,"id"); + options = options || {}; + options.step = _wrap(options.step, function() { jsPlumb.repaint(id); }); + _animate(ele, properties, options); + }, + + /* + Function: connect + + Establishes a connection between two elements. + + Parameters: + + params - Object containing setup for the connection. see documentation. + + Returns: + + The newly created Connection. + + */ + connect : function(params) { + if (params.sourceEndpoint && params.sourceEndpoint.isFull()) { + _log("could not add connection; source endpoint is full"); + return; + } + + if (params.targetEndpoint && params.targetEndpoint.isFull()) { + _log("could not add connection; target endpoint is full"); + return; + } + + var jpc = new Connection(params); + + // register endpoints for the element. todo: is the test below sufficient? or should we test if the endpoint is already in the list, + // and add it only then? perhaps _addToList could be overloaded with a a 'test for existence first' parameter? + //_addToList(endpointsByElement, jpc.sourceId, jpc.endpoints[0]); + //_addToList(endpointsByElement, jpc.targetId, jpc.endpoints[1]); + + if (!params.sourceEndpoint) _addToList(endpointsByElement, jpc.sourceId, jpc.endpoints[0]); + if (!params.targetEndpoint) _addToList(endpointsByElement, jpc.targetId, jpc.endpoints[1]); + + jpc.endpoints[0].addConnection(jpc); + jpc.endpoints[1].addConnection(jpc); + + // force a paint + _draw(jpc.source); + + return jpc; + + }, + + /** + * not implemented yet. params object will have sourceEndpoint and targetEndpoint members; these will be Endpoints. + connectEndpoints : function(params) { + var jpc = Connection(params); + + },*/ + + /* + Function: detach + + Removes a connection. + + Parameters: + + sourceId - Id of the first element in the connection. A String. + targetId - iI of the second element in the connection. A String. + + Returns: + + true if successful, false if not. + */ + detach : function(sourceId, targetId) { + var f = function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + return true; + } + }; + + // todo: how to cleanup the actual storage? a third arg to _operation? + _operation(sourceId, f); + }, + + /* + Function: detachAll + + Removes all an element's connections. + + Parameters: + + el - either the id of the element, or a jQuery object for the element. + + Returns: + + void + */ + detachAll : function(el) { + var id = _getAttribute(el, "id"); + var f = function(jpc) { + // todo replace with _cleanupConnection call here. + _removeElement(jpc.canvas); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + }; + _operation(id, f); + //delete endpointsByElement[id]; ?? + }, + + /* + Function: detachEverything + + Remove all Connections from all elements, but leaves Endpoints in place. + + Returns: + + void + + See Also: + + + */ + detachEverything : function() { + var f = function(jpc) { + _removeElement(jpc.canvas); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + }; + + _operationOnAll(f); + + /*delete endpointsByElement; //?? + endpointsByElement = {};*/ //?? + }, + + extend : function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }, + + /* + Function: hide + + Sets an element's connections to be hidden. + + Parameters: + + el - either the id of the element, or a jQuery object for the element. + + Returns: + + void + */ + hide : function(el) { + _setVisible(el, "none"); + }, + + /* + Function: makeAnchor + + Creates an anchor with the given params. + + Parameters: + + x - the x location of the anchor as a fraction of the total width. + y - the y location of the anchor as a fraction of the total height. + xOrientation - value indicating the general direction a connection from the anchor should go in, in the x direction. + yOrientation - value indicating the general direction a connection from the anchor should go in, in the y direction. + xOffset - a fixed offset that should be applied in the x direction that should be applied after the x position has been figured out. optional. defaults to 0. + yOffset - a fixed offset that should be applied in the y direction that should be applied after the y position has been figured out. optional. defaults to 0. + + Returns: + + The newly created Anchor. + */ + makeAnchor : function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards compatibility if we are given only one value we assume it's a call in the old form. + var params = {}; + if (arguments.length == 1) jsPlumb.extend(params, x); + else { + params = {x:x, y:y}; + if (arguments.length >= 4) { + params.orientation = [arguments[2], arguments[3]]; + } + if (arguments.length == 6) params.offsets = [arguments[4], arguments[5]]; + } + return new Anchor(params); + }, + + + /* + Function: repaint + + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + + el - either the id of the element or a jQuery object representing the element. + + Returns: + + void + + See Also: + + + */ + repaint : function(el) { + + var _processElement = function(el) { + var ele = _getElementObject(el); + _draw(ele); + }; + + // TODO: support a jQuery result object too! + + // support both lists... + if (typeof el =='object') { + for (var i = 0; i < el.length; i++) + _processElement(el[i]); + } // ...and single strings. + else _processElement(el); + }, + + /* + Function: repaintEverything + + Repaints all connections. + + Returns: + + void + + See Also: + + + */ + repaintEverything : function() { + for (var elId in endpointsByElement) { + _draw(_getElementObject(elId)); + } + }, + + /* + Function: removeAllEndpoints + + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + + el - either an element id, or a jQuery object for an element. + + Returns: + + void + + See Also: + + + */ + removeAllEndpoints : function(el) { + var elId = _getAttribute(el, "id"); + // first remove all Connections. + jsPlumb.detachAll(elId); + var ebe = endpointsByElement[elId]; + for (var i in ebe) { + _removeElement(ebe[i].canvas); + } + endpointsByElement[elId] = []; + }, + + /* + Function: removeEndpoint + + Removes the given Endpoint from the given element. + + Parameters: + + el - either an element id, or a jQuery object for an element. + endpoint - Endpoint to remove. this is an Endpoint object, such as would have been returned from a call to addEndpoint. + + Returns: + + void + + See Also: + + + */ + removeEndpoint : function(el, endpoint) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + if (ebe) { + if(_removeFromList(endpointsByElement, elId, endpoint)) + _removeElement(endpoint.canvas); + } + }, + + /* + Function: setAutomaticRepaint + + Sets/unsets automatic repaint on window resize. + + Parameters: + + value - whether or not to automatically repaint when the window is resized. + + Returns: + + void + */ + setAutomaticRepaint : function(value) { + automaticRepaint = value; + }, + + /* + Function: setDefaultNewCanvasSize + + Sets the default size jsPlumb will use for a new canvas (we create a square canvas so one value is all that is required). + This is a hack for IE, because ExplorerCanvas seems to need for a canvas to be larger than what you are going to draw on + it at initialisation time. The default value of this is 1200 pixels, which is quite large, but if for some reason you're + drawing connectors that are bigger, you should adjust this value appropriately. + + Parameters: + + size - The default size to use. jsPlumb will use a square canvas so you need only supply one value. + + Returns: + + void + */ + setDefaultNewCanvasSize : function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }, + + /* + Function: setDraggable + + Sets whether or not a given element is draggable, regardless of what any plumb command may request. + + Parameters: + + el - either the id for the element, or a jQuery object representing the element. + + Returns: + + void + */ + setDraggable: _setDraggable, + + /* + Function: setDraggableByDefault + + Sets whether or not elements are draggable by default. Default for this is true. + + Parameters: + draggable - value to set + + Returns: + + void + */ + setDraggableByDefault: function(draggable) { + _draggableByDefault = draggable; + }, + + setDebugLog: function(debugLog) { + log = debugLog; + }, + + /* + Function: setRepaintFunction + + Sets the function to fire when the window size has changed and a repaint was fired. + + Parameters: + f - Function to execute. + + Returns: + void + */ + setRepaintFunction : function(f) { + repaintFunction = f; + }, + + /* + Function: show + + Sets an element's connections to be visible. + + Parameters: + el - either the id of the element, or a jQuery object for the element. + + Returns: + void + */ + show : function(el) { + _setVisible(el, "block"); + }, + + /* + Function: sizeCanvas + + Helper to size a canvas. You would typically use this when writing your own Connector or Endpoint implementation. + + Parameters: + x - [int] x position for the Canvas origin + y - [int] y position for the Canvas origin + w - [int] width of the canvas + h - [int] height of the canvas + + Returns: + void + */ + sizeCanvas : function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; canvas.height = h; + canvas.style.width = w + "px"; canvas.width = w; + canvas.style.left = x + "px"; canvas.style.top = y + "px"; + } + }, + + /** + * gets some test hooks. nothing writable. + */ + getTestHarness : function() { + return { + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + } + }; + }, + + /** + * Toggles visibility of an element's connections. kept for backwards compatibility + */ + toggle : _toggleVisible, + + /* + Function: toggleVisible + + Toggles visibility of an element's connections. + + Parameters: + el - either the element's id, or a jQuery object representing the element. + + Returns: + void, but should be updated to return the current state + */ + //TODO: update this method to return the current state. + toggleVisible : _toggleVisible, + + /* + Function: toggleDraggable + + Toggles draggability (sic) of an element's connections. + + Parameters: + el - either the element's id, or a jQuery object representing the element. + + Returns: + The current draggable state. + */ + toggleDraggable : _toggleDraggable, + + /* + Function: unload + + Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element + + Returns: + void + */ + unload : function() { + delete endpointsByElement; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + } + }; + +})(); + +// jQuery plugin code +(function($){ + /** + * plumbs the results of the selector to some target, using the given options if supplied, + * or the defaults otherwise. this is DEPRECATED. just use jsPlumb.connect(..) instead. + * @deprecated + */ + $.fn.plumb = function(options) { + var options = $.extend({}, options); + + return this.each(function() + { + var params = $.extend({source:$(this)}, options); + jsPlumb.connect(params); + }); + }; + + /** + * detaches the results of the selector from the given target or list of targets - 'target' + * may be a String or a List. + */ + $.fn.detach = function(target) { + return this.each(function() + { + var id = $(this).attr("id"); + if (typeof target == 'string') target = [target]; + for (var i = 0; i < target.length; i++) + jsPlumb.detach(id, target[i]); + }); + }; + + /** + * detaches the results from the selector from all connections. + */ + $.fn.detachAll = function() { + return this.each(function() + { + var id = $(this).attr("id"); + jsPlumb.detachAll(id); + }); + }; + + /** + * adds an endpoint to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoint = function(options) { + var addedEndpoints = []; + this.each(function() + { + addedEndpoints.push(jsPlumb.addEndpoint($(this).attr("id"), options)); + }); + return addedEndpoints[0]; + }; + + /** + * adds a list of endpoints to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoints = function(endpoints) { + var addedEndpoints = []; + this.each(function() + { + var e = jsPlumb.addEndpoints($(this).attr("id"), endpoints); + for (var i = 0; i < e.length; i++) addedEndpoints.push(e[i]); + }); + return addedEndpoints; + }; + + /** + * remove the endpoint, if it exists, deleting its UI elements etc. + */ + $.fn.removeEndpoint = function(endpoint) { + this.each(function() + { + jsPlumb.removeEndpoint($(this).attr("id"), endpoint); + }); + }; + +})(jQuery); + + +/* the library agnostic functions, such as find offset, get id, get attribute, extend etc. currently +hardcoded to jQuery here; will be extracted to separate impls for different libraries. + +* much more work needs to be done on this. some things that spring to mind: animation, drag, drop. +* +* you should _never_ call these methods directly. jsPlumb uses them when it needs them. +* +*/ +(function() { + + jsPlumb.CurrentLibrary = { + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag' + }, + + /** + * default drag options for jQuery. + */ + defaultDragOptions : { opacity:0.5, revert:true, helper:'clone' }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + getElementObject : function(el) { + return typeof(el)=='string' ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options) { + if (el.length > 1) alert('poo'); + el.draggable(options); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + el.droppable(options); + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[1]; + return ui.absolutePosition || ui.offset; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + } + }; +})(); + +/** +* jsPlumb-defaults-1.1.1-RC1 +* +* This script contains the default Anchors, Endpoints and Connectors for jsPlumb. It should be used with jsPlumb 1.1.0 and above; +* prior to version 1.1.0 of jsPlumb the defaults were included inside the main script. +* +* Version 1.1.1 of this script adds the Triangle Endpoint, written by __________ and featured on this demo: +* +* http://www.mintdesign.ru/blog/javascript-jsplumb-making-graph +* http://www.mintdesign.ru/site/samples/jsplumb/jsPlumb-graph-sample.htm +* +* NOTE: for production usage you should use jsPlumb-all-1.1.1-min.js, which contains the main jsPlumb script and this script together, +* in a minified file. +*/ + +(function() { + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * getOrientation : function() { return [ox, oy]; } + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + jsPlumb.Anchors.TopCenter = jsPlumb.makeAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors.BottomCenter = jsPlumb.makeAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors.LeftMiddle = jsPlumb.makeAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors.RightMiddle = jsPlumb.makeAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors.Center = jsPlumb.makeAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors.TopRight = jsPlumb.makeAnchor(1, 0, 0,-1); + jsPlumb.Anchors.BottomRight = jsPlumb.makeAnchor(1, 1, 0, 1); + jsPlumb.Anchors.TopLeft = jsPlumb.makeAnchor(0, 0, 0, -1); + jsPlumb.Anchors.BottomLeft = jsPlumb.makeAnchor(0, 1, 0, 1); + + + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (w < 2 * lineWidth) { + w = 2 * lineWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - lineWidth; + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < 2 * lineWidth) { + // minimum size is 2 * line Width + h = 2 * lineWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - lineWidth; + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + var sx = sourcePos[0] < targetPos[0] ? w-xo : xo; + var sy = sourcePos[1] < targetPos[1] ? h-yo : yo; + var tx = sourcePos[0] < targetPos[0] ? xo : w-xo; + var ty = sourcePos[1] < targetPos[1] ? yo : h-yo; + var retVal = [ x, y, w, h, sx, sy, tx, ty ]; + + return retVal; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + }; + + /** + * This Connector draws a Bezier curve with two control points. + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + jsPlumb.Connectors.Bezier = function(curviness) { + + var self = this; + this.majorAnchor = curviness || 150; + this.minorAnchor = 10; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) + { + lineWidth = lineWidth || 0; + var w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth, h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + var canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2), canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + var sx = sourcePos[0] < targetPos[0] ? w - (lineWidth/2): (lineWidth/2), sy = sourcePos[1] < targetPos[1] ? h-(lineWidth/2) : (lineWidth/2); + var tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : w-(lineWidth/2), ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : h-(lineWidth/2); + var CP = self._findControlPoint([sx,sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + var CP2 = self._findControlPoint([tx,ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(sx,tx); var minx2 = Math.min(CP[0], CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(sx,tx); var maxx2 = Math.max(CP[0], CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > w) w = maxx; + if (minx < 0) { + canvasX += minx; var ox = Math.abs(minx); + w += ox; CP[0] += ox; sx += ox; tx +=ox; CP2[0] += ox; + } + + var miny1 = Math.min(sy,ty); var miny2 = Math.min(CP[1], CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(sy,ty); var maxy2 = Math.max(CP[1], CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > h) h = maxy; + if (miny < 0) { + canvasY += miny; var oy = Math.abs(miny); + h += oy; CP[1] += oy; sy += oy; ty +=oy; CP2[1] += oy; + } + + // return [ canvasx, canvasy, canvasWidth, canvasHeight, + // sourceX, sourceY, targetX, targetY, + // controlPoint1_X, controlPoint1_Y, controlPoint2_X, controlPoint2_Y + return [canvasX, canvasY, w, h, sx, sy, tx, ty, CP[0], CP[1], CP2[0], CP2[1] ]; + }; + + this.paint = function(d, ctx) { + + /*var img = new Image(); + img.src = "../img/pattern.jpg"; + ctx.fillStyle = ctx.createPattern(img, 'repeat-y');*/ + + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + //ctx.fill(); + } + }; + + + /** + * Types of endpoint UIs. we supply three - a circle of default radius 10px, a rectangle of + * default size 20x20, and an image (with no default). you can supply others of these if you want to - see the documentation + * for a howto. + */ + + /** + * a round endpoint, with default radius 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + } + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * A Rectangular endpoint, with default size 20x20. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + //todo: the fillStyle needs some thought. we want to support a few options: + // 1. nothing supplied; use the stroke color or the default if no stroke color. + // 2. a fill color supplied - use it + // 3. a gradient supplied - use it + // 4. setting the endpoint to the same color as the bg of the element it is attached to. + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + jsPlumb.Endpoints.Triangle = function(params) { + + params = params || { width:15, height:15 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - width/2; + var y = anchorPoint[1] - height/2; + + jsPlumb.sizeCanvas(canvas, x, y, width, height); + + var ctx = canvas.getContext('2d'); + var + offsetX = 0, + offsetY = 0, + angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = endpointStyle.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + ctx.fill(); + + }; + }; + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' property in the params object.. + */ + jsPlumb.Endpoints.Image = function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + }; +})(); \ No newline at end of file diff --git a/archive/1.1.1/jquery.jsPlumb-defaults-1.1.1-RC1.js b/archive/1.1.1/jquery.jsPlumb-defaults-1.1.1-RC1.js new file mode 100644 index 000000000..829f7c743 --- /dev/null +++ b/archive/1.1.1/jquery.jsPlumb-defaults-1.1.1-RC1.js @@ -0,0 +1,406 @@ +/** +* jsPlumb-defaults-1.1.1-RC1 +* +* This script contains the default Anchors, Endpoints and Connectors for jsPlumb. It should be used with jsPlumb 1.1.0 and above; +* prior to version 1.1.0 of jsPlumb the defaults were included inside the main script. +* +* Version 1.1.1 of this script adds the Triangle Endpoint, written by __________ and featured on this demo: +* +* http://www.mintdesign.ru/blog/javascript-jsplumb-making-graph +* http://www.mintdesign.ru/site/samples/jsplumb/jsPlumb-graph-sample.htm +* +* NOTE: for production usage you should use jsPlumb-all-1.1.1-min.js, which contains the main jsPlumb script and this script together, +* in a minified file. +*/ + +(function() { + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * getOrientation : function() { return [ox, oy]; } + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + jsPlumb.Anchors.TopCenter = jsPlumb.makeAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors.BottomCenter = jsPlumb.makeAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors.LeftMiddle = jsPlumb.makeAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors.RightMiddle = jsPlumb.makeAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors.Center = jsPlumb.makeAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors.TopRight = jsPlumb.makeAnchor(1, 0, 0,-1); + jsPlumb.Anchors.BottomRight = jsPlumb.makeAnchor(1, 1, 0, 1); + jsPlumb.Anchors.TopLeft = jsPlumb.makeAnchor(0, 0, 0, -1); + jsPlumb.Anchors.BottomLeft = jsPlumb.makeAnchor(0, 1, 0, 1); + + + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (w < 2 * lineWidth) { + w = 2 * lineWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - lineWidth; + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < 2 * lineWidth) { + // minimum size is 2 * line Width + h = 2 * lineWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - lineWidth; + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + var sx = sourcePos[0] < targetPos[0] ? w-xo : xo; + var sy = sourcePos[1] < targetPos[1] ? h-yo : yo; + var tx = sourcePos[0] < targetPos[0] ? xo : w-xo; + var ty = sourcePos[1] < targetPos[1] ? yo : h-yo; + var retVal = [ x, y, w, h, sx, sy, tx, ty ]; + + return retVal; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + }; + + /** + * This Connector draws a Bezier curve with two control points. + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + jsPlumb.Connectors.Bezier = function(curviness) { + + var self = this; + this.majorAnchor = curviness || 150; + this.minorAnchor = 10; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) + { + lineWidth = lineWidth || 0; + var w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth, h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + var canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2), canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + var sx = sourcePos[0] < targetPos[0] ? w - (lineWidth/2): (lineWidth/2), sy = sourcePos[1] < targetPos[1] ? h-(lineWidth/2) : (lineWidth/2); + var tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : w-(lineWidth/2), ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : h-(lineWidth/2); + var CP = self._findControlPoint([sx,sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + var CP2 = self._findControlPoint([tx,ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(sx,tx); var minx2 = Math.min(CP[0], CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(sx,tx); var maxx2 = Math.max(CP[0], CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > w) w = maxx; + if (minx < 0) { + canvasX += minx; var ox = Math.abs(minx); + w += ox; CP[0] += ox; sx += ox; tx +=ox; CP2[0] += ox; + } + + var miny1 = Math.min(sy,ty); var miny2 = Math.min(CP[1], CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(sy,ty); var maxy2 = Math.max(CP[1], CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > h) h = maxy; + if (miny < 0) { + canvasY += miny; var oy = Math.abs(miny); + h += oy; CP[1] += oy; sy += oy; ty +=oy; CP2[1] += oy; + } + + // return [ canvasx, canvasy, canvasWidth, canvasHeight, + // sourceX, sourceY, targetX, targetY, + // controlPoint1_X, controlPoint1_Y, controlPoint2_X, controlPoint2_Y + return [canvasX, canvasY, w, h, sx, sy, tx, ty, CP[0], CP[1], CP2[0], CP2[1] ]; + }; + + this.paint = function(d, ctx) { + + /*var img = new Image(); + img.src = "../img/pattern.jpg"; + ctx.fillStyle = ctx.createPattern(img, 'repeat-y');*/ + + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + //ctx.fill(); + } + }; + + + /** + * Types of endpoint UIs. we supply three - a circle of default radius 10px, a rectangle of + * default size 20x20, and an image (with no default). you can supply others of these if you want to - see the documentation + * for a howto. + */ + + /** + * a round endpoint, with default radius 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + } + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * A Rectangular endpoint, with default size 20x20. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + //todo: the fillStyle needs some thought. we want to support a few options: + // 1. nothing supplied; use the stroke color or the default if no stroke color. + // 2. a fill color supplied - use it + // 3. a gradient supplied - use it + // 4. setting the endpoint to the same color as the bg of the element it is attached to. + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + jsPlumb.Endpoints.Triangle = function(params) { + + params = params || { width:15, height:15 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - width/2; + var y = anchorPoint[1] - height/2; + + jsPlumb.sizeCanvas(canvas, x, y, width, height); + + var ctx = canvas.getContext('2d'); + var + offsetX = 0, + offsetY = 0, + angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = endpointStyle.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + ctx.fill(); + + }; + }; + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' property in the params object.. + */ + jsPlumb.Endpoints.Image = function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + }; +})(); \ No newline at end of file diff --git a/archive/1.2.1/jquery.jsPlumb-1.2.1-RC1.js b/archive/1.2.1/jquery.jsPlumb-1.2.1-RC1.js new file mode 100644 index 000000000..f4d299559 --- /dev/null +++ b/archive/1.2.1/jquery.jsPlumb-1.2.1-RC1.js @@ -0,0 +1,253 @@ +// jQuery plugin code; v 1.2.1-RC1 + +(function($){ + /** + * plumbs the results of the selector to some target, using the given options if supplied, + * or the defaults otherwise. + */ + $.fn.plumb = function(options) { + var options = $.extend({}, options); + + return this.each(function() + { + var params = $.extend({source:$(this)}, options); + jsPlumb.connect(params); + }); + }; + + /** + * detaches the results of the selector from the given target or list of targets - 'target' + * may be a String or a List. + */ + $.fn.detach = function(target) { + return this.each(function() { + if (target) { + var id = $(this).attr("id"); + if (typeof target == 'string') target = [target]; + for (var i = 0; i < target.length; i++) + jsPlumb.detach(id, target[i]); + } + }); + }; + + /** + * detaches the results from the selector from all connections. + */ + $.fn.detachAll = function() { + return this.each(function() + { + var id = $(this).attr("id"); + jsPlumb.detachAll(id); + }); + }; + + /** + * adds an endpoint to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoint = function(options) { + var addedEndpoints = []; + this.each(function() { + addedEndpoints.push(jsPlumb.addEndpoint($(this).attr("id"), options)); + }); + return addedEndpoints[0]; + }; + + /** + * adds a list of endpoints to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoints = function(endpoints) { + var addedEndpoints = []; + return this.each(function() { + var e = jsPlumb.addEndpoints($(this).attr("id"), endpoints); + for (var i = 0; i < e.length; i++) addedEndpoints.push(e[i]); + }); + return addedEndpoints; + }; + + /** + * remove the endpoint, if it exists, deleting its UI elements etc. + */ + $.fn.removeEndpoint = function(endpoint) { + this.each(function() { + jsPlumb.removeEndpoint($(this).attr("id"), endpoint); + }); + }; + +})(jQuery); + + +/*TODO: abstract this out from jQuery too! but how...because jsPlumb is not loaded yet. +$(window).bind('resize', function() { + if (resizeTimer) clearTimeout(resizeTimer); + resizeTimer = setTimeout(repaintEverything, 100); + });*/ + + + +/* + * the library agnostic functions, such as find offset, get id, get attribute, extend etc. + */ +(function() { + + jsPlumb.CurrentLibrary = { + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step', + 'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete' + }, + + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).append(child); + }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + * + * since 1.2.1 this method has had a cache applied to it. the cache is a very simple + * cache - a hashmap - so can grow and grow. this may not be optimum. but it does + * speed up performance: + * + * without cache, 312 connections take 5.22 seconds to create. + * with cache, 312 connections take 4.57 seconds to create. that's 13% faster. + */ + + getElementObject : function(el) { + return typeof(el)=='string' ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options) { + // remove helper directive if present. we know what's best! + options.helper = null; + //TODO: if 'revert' is set on the options it causes end points to animate back to + // where they came from, if the connection is aborted. do we care? probably not. + // the todo is to decide whether we care or not. + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.draggable(options); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.droppable(options); + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[1]; + return ui.absolutePosition || ui.offset; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + }, + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).remove(); + }, + + getScrollLeft : function(el) { + return el.scrollLeft(); + }, + + getScrollTop : function(el) { + return el.scrollTop(); + } + }; +})(); diff --git a/archive/1.2.1/jquery.jsPlumb-1.2.1-all-min.js b/archive/1.2.1/jquery.jsPlumb-1.2.1-all-min.js new file mode 100644 index 000000000..3e5116776 --- /dev/null +++ b/archive/1.2.1/jquery.jsPlumb-1.2.1-all-min.js @@ -0,0 +1,48 @@ +(function(){var g=function(){var e=this,h=/MSIE/.test(navigator.userAgent)&&!window.opera,o=null,n=function(){d.repaintEverything()},i=true,j={},s={},r=[],A={},E={},J=true,D=[],G="DEFAULT",aa=1200,O={},ba=function(a,b){var c=O[a];if(!c){c={};O[a]=c}b=b||"DEFAULT";var f=c[b];if(!f){f=0;c[b]=f}c[b]++},P=function(a,b,c){var f=function(l,p){if(l===p)return true;else if(typeof l=="object"&&typeof p=="object"){var t=true;for(var u in l)if(!f(l[u],p[u])){t=false;break}for(u in p)if(!f(p[u],l[u])){t=false; +break}return t}};c=+c||0;for(var k=a.length;c=0){delete a[c];a.splice(c,1);return true}}return false},ia=function(a,b){var c=B(a,"id");da(c,function(f){f.canvas.style.display=b})},la=function(a){da(a,function(b){b.canvas.style.display="none"==b.canvas.style.display?"block":"none"})},L=function(a,b,c){if(c||b==null){b=v(a);D[a]=ga(b);r[a]=fa(b)}else r[a]=b},H=function(a,b){a=a||function(){};b=b||function(){};return function(){try{b.apply(this,arguments)}catch(c){}try{a.apply(this,arguments)}catch(f){}}}, +ja=function(a){var b=this;this.x=a.x||0;this.y=a.y||0;var c=a.orientation||[0,0],f=null;this.offsets=a.offsets||[0,0];this.compute=function(k,l,p){ba("anchor compute");f=[k[0]+b.x*l[0]+b.offsets[0],k[1]+b.y*l[1]+b.offsets[1]];l=p?p.container:null;k={left:0,top:0};if(l!=null){var t=v(l);l=fa(t);p=d.CurrentLibrary.getScrollLeft(t);t=d.CurrentLibrary.getScrollTop(t);k.left=l.left-p;k.top=l.top-t;f[0]-=k.left;f[1]-=k.top}return f};this.getOrientation=function(){return c};this.equals=function(k){if(!k)return false; +var l=k.getOrientation(),p=this.getOrientation();return this.x==k.x&&this.y==k.y&&this.offsets[0]==k.offsets[0]&&this.offsets[1]==k.offsets[1]&&p[0]==l[0]&&p[1]==l[1]}},ma=function(a){var b=a.reference,c=ga(v(a.referenceCanvas)),f=0,k=0,l=null;this.compute=function(p){k=f=0;return[p[0]+c[0]/2,p[1]+c[1]/2]};this.getOrientation=function(){if(l)return l;else{var p=b.getOrientation();return[Math.abs(p[0])*f*-1,Math.abs(p[1])*k*-1]}};this.over=function(p){l=p.getOrientation()};this.out=function(){l=null}}, +ka=function(a){var b=this,c=new String("_jsplumb_c_"+(new Date).getTime());this.getId=function(){return c};this.container=a.container||e.Defaults.Container;this.source=v(a.source);this.target=v(a.target);this.sourceId=B(this.source,"id");this.targetId=B(this.target,"id");this.endpointsOnTop=a.endpointsOnTop!=null?a.endpointsOnTop:true;this.scope=a.scope;this.endpoints=[];this.endpointStyles=[];var f=function(p,t,u,x){if(p){b.endpoints[t]=p;p.addConnection(b)}else{if(!u.endpoints)u.endpoints=[null, +null];p=u.endpoints[t]||u.endpoint||e.Defaults.Endpoints[t]||d.Defaults.Endpoints[t]||e.Defaults.Endpoint||d.Defaults.Endpoint||new d.Endpoints.Dot;if(!u.endpointStyles)u.endpointStyles=[null,null];u=new Y({style:u.endpointStyles[t]||u.endpointStyle||e.Defaults.EndpointStyles[t]||d.Defaults.EndpointStyles[t]||e.Defaults.EndpointStyle||d.Defaults.EndpointStyle,endpoint:p,connections:[b],anchor:u.anchors?u.anchors[t]:e.Defaults.Anchors[t]||d.Defaults.Anchors[t]||e.Defaults.Anchor||d.Defaults.Anchor|| +d.Anchors.BottomCenter,source:x,container:b.container});return b.endpoints[t]=u}},k=f(a.sourceEndpoint,0,a,b.source);k&&Q(j,this.sourceId,k);(f=f(a.targetEndpoint,1,a,b.target))&&Q(j,this.targetId,f);if(!this.scope)this.scope=this.endpoints[0].scope;this.connector=this.endpoints[0].connector||this.endpoints[1].connector||a.connector||e.Defaults.Connector||d.Defaults.Connector||new d.Connectors.Bezier;this.paintStyle=this.endpoints[0].connectorStyle||this.endpoints[1].connectorStyle||a.paintStyle|| +e.Defaults.PaintStyle||d.Defaults.PaintStyle;L(this.sourceId);L(this.targetId);f=r[this.sourceId];k=D[this.sourceId];f=this.endpoints[0].anchor.compute([f.left,f.top],k,this.endpoints[0]);this.endpoints[0].paint(f);f=r[this.targetId];k=D[this.targetId];f=this.endpoints[1].anchor.compute([f.left,f.top],k,this.endpoints[1]);this.endpoints[1].paint(f);var l=ha(d.connectorClass,b.container);this.canvas=l;this.paint=function(p,t,u){o&&o.debug("Painting Connection; element in motion is "+p+"; ui is ["+ +t+"]; recalc is ["+u+"]");var x=p!=this.sourceId,F=x?this.sourceId:this.targetId,w=x?0:1,q=x?1:0;if(this.canvas.getContext){L(p,t,u);L(F);u=r[p];t=r[F];p=D[p];var Z=D[F];F=l.getContext("2d");u=this.endpoints[q].anchor.compute([u.left,u.top],p,this.endpoints[q]);this.endpoints[q].anchor.getOrientation();t=this.endpoints[w].anchor.compute([t.left,t.top],Z,this.endpoints[w]);this.endpoints[w].anchor.getOrientation();w=this.connector.compute(u,t,this.endpoints[q].anchor,this.endpoints[w].anchor,this.paintStyle.lineWidth); +d.sizeCanvas(l,w[0],w[1],w[2],w[3]);d.extend(F,this.paintStyle);if(this.paintStyle.gradient&&!h){x=x?F.createLinearGradient(w[4],w[5],w[6],w[7]):F.createLinearGradient(w[6],w[7],w[4],w[5]);for(q=0;q=0&&b.connections.splice(m,1)};this.makeInPlaceCopy=function(){return new Y({anchor:b.anchor,source:l,style:k,endpoint:f})};this.isConnectedTo=function(m){var C=false;if(m)for(var y=0;y=t};this.equals=function(m){return this.anchor.equals(m.anchor)&&true};this.paint=function(m,C,y){o&&o.debug("Painting Endpoint with elementId ["+p+"]; anchorPoint is ["+m+"]");if(m==null){m=r[p];var M=D[p];if(m==null||M==null){L(p);m=r[p];M=D[p]}m=b.anchor.compute([m.left,m.top],M,b)}f.paint(m,b.anchor.getOrientation(),y||b.canvas,k,C||k)};if(a.isSource&&d.CurrentLibrary.isDragSupported(l)){var w= +null;var q=c=null,Z=false,N=null,z=a.dragOptions||{},K=d.extend({},d.CurrentLibrary.defaultDragOptions);z=d.extend(K,z);z.scope=z.scope||b.scope;K=d.CurrentLibrary.dragEvents.start;var U=d.CurrentLibrary.dragEvents.stop,V=d.CurrentLibrary.dragEvents.drag;z[K]=H(z[K],function(){F=b.makeInPlaceCopy();F.paint();w=document.createElement("div");var m=v(w);document.body.appendChild(w);var C=""+new String((new Date).getTime());X(m,"id",C);L(C);X(v(b.canvas),"dragId",C);X(v(b.canvas),"elId",p);var y=new ma({reference:b.anchor, +referenceCanvas:b.canvas});x=new Y({style:{fillStyle:"rgba(0,0,0,0)"},endpoint:f,anchor:y,source:m});q=b.connections.length==0||b.connections.length0)for(var f=0;f0)for(var k=0;k0?P(f,l)!=-1:true){b[l]=[];for(c=0;c0?P(k,p.sourceId)!=-1:true)&&(a.length>0?P(a,p.targetId)!=-1:true)&&b[l].push({sourceId:p.sourceId,targetId:p.targetId})}}return b};this.getDefaultScope=function(){return G};this.hide=function(a){ia(a,"none")};this.makeAnchor=function(a,b){var c= +{};if(arguments.length==1)d.extend(c,a);else{c={x:a,y:b};if(arguments.length>=4)c.orientation=[arguments[2],arguments[3]];if(arguments.length==6)c.offsets=[arguments[4],arguments[5]]}var f=new ja(c);f.clone=function(){return new ja(c)};return f};this.repaint=function(a){var b=function(f){f=v(f);R(f)};if(typeof a=="object")for(var c=0;cj)j=o;if(h<0){r+=h;h=Math.abs(h);j+=h;G[0]+=h;E+=h;D+=h;e[0]+=h}h=Math.min(Math.min(J,i),Math.min(G[1], +e[1]));o=Math.max(Math.max(J,i),Math.max(G[1],e[1]));if(o>s)s=o;if(h<0){A+=h;h=Math.abs(h);s+=h;G[1]+=h;J+=h;i+=h;e[1]+=h}return[r,A,j,s,E,J,D,i,G[0],G[1],e[0],e[1]]};this.paint=function(e,h){h.beginPath();h.moveTo(e[4],e[5]);h.bezierCurveTo(e[8],e[9],e[10],e[11],e[6],e[7]);h.stroke()}};jsPlumb.Endpoints.Dot=function(g){g=g||{radius:10};var d=this;this.radius=g.radius;var e=0.5*this.radius,h=this.radius/3,o=function(n){try{return parseInt(n)}catch(i){if(n.substring(n.length-1)=="%")return parseInt(n.substring(0, +n-1))}};this.paint=function(n,i,j,s,r){var A=s.radius||d.radius;jsPlumb.sizeCanvas(j,n[0]-A,n[1]-A,A*2,A*2);n=j.getContext("2d");j=jsPlumb.extend({},s);if(j.fillStyle==null)j.fillStyle=r.strokeStyle;jsPlumb.extend(n,j);r=/MSIE/.test(navigator.userAgent)&&!window.opera;if(s.gradient&&!r){r=s.gradient;j=e;var E=h;if(r.offset)j=o(r.offset);if(r.innerRadius)E=o(r.innerRadius);r=[j,E];i=n.createRadialGradient(A,A,A,A+(i[0]==1?r[0]*-1:r[0]),A+(i[1]==1?r[0]*-1:r[0]),r[1]);for(r=0;r endpoint lists. an element can have an arbitrary number of endpoints on it, + * and not all of them have to be connected to anything. + */ + var endpointsByElement = {}; + var connectionsByScope = {}; + var offsets = []; + var floatingConnections = {}; + var draggableStates = {}; + var _draggableByDefault = true; + var sizes = []; + var DEFAULT_SCOPE = 'DEFAULT'; + + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + var traced = {}; + var _trace = function(category, event) { + var e = traced[category]; + if (!e) { + e = {}; + traced[category] = e; + } + event = event || 'DEFAULT'; + var ee = e[event]; + if (!ee) { + ee = 0; + e[event] = ee; + } + e[event]++; + }; + + var _clearAllTraces = function() { + delete traced; + traced = {}; + }; + + var _clearTrace = function(category, event) { + var c = traced[category]; + if (!c) return; + if (event) { + c[event] = 0; + } + else c['DEFAULT'] = 0; + }; + + var _getTrace = function(category) { + return traced[category] || {'DEFAULT' : 0 }; + }; + + + var _findIndex = function( a, v, b, s ) { + var _eq = function(o1, o2) { + if (o1 === o2) return true; + else if (typeof o1 == 'object' && typeof o2 == 'object') { + var same = true; + for(var propertyName in o1) { + if(!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for(var propertyName in o2) { + if(!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for( var i = +b || 0, l = a.length; i < l; i++ ) { + if( _eq(a[i], v)) return i; + } + return -1; + }; + + /** + * helper method to add an item to a list, creating the list if it does not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }; + + /** + * Handles the dragging of an element. + * @param element jQuery element + * @param ui UI object from current library's event system + */ + var _draw = function(element, ui) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints) { + var timestamp = '' + (new Date().getTime()); + _trace('draw'); + _updateOffset(id, ui); + var myOffset = offsets[id]; + var myWH = sizes[id]; + // loop through endpoints for this element + for (var i = 0; i < endpoints.length; i++) { + var e = endpoints[i]; + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH, e); + e.paint(anchorLoc); + var l = e.connections; + for (var j = 0; j < l.length; j++) + l[j].paint(id, ui, false, timestamp); // ...and paint them. + } + } + }; + + /** + * helper function: the second argument is a function taking two args - the first is an id + * ui element from the current library (or list of them), and the second is the function + * to run. + */ + var _elementProxy = function(element, fn) { + var retVal = null; + // TODO this might be jquery specific. in fact it probably is. + if (typeof element == 'object' && element.length) { + retVal = []; + for (var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]); + var id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } + else { + var el = _getElementObject(element); + var id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + + return retVal; + }; + + var _log = function(msg) { + //if (console) console.log(msg); + }; + + var _logFnCall = function(fn, args) { + /*if (console) { + var c = args.callee.caller.toString(); + var i = c.indexOf("{"); + var msg = fn + ' : ' + c.substring(0, i); + console.log(msg); + }*/ + }; + + var cached = {}; + var __getElementObject = function(el) { + + if (typeof(el)=='string') { + var e = cached[el]; + if (!e) { + e = jsPlumb.CurrentLibrary.getElementObject(el); + cached[el] = e; + } + return e; + } + else { + return jsPlumb.CurrentLibrary.getElementObject(el); + } + }; + + + /** + * gets the named attribute from the given element (id or element object) + */ + var _getAttribute = function(el, attName) { + var ele = __getElementObject(el); + return jsPlumb.CurrentLibrary.getAttribute(ele, attName); + }; + + var _setAttribute = function(el, attName, attValue) { + var ele = __getElementObject(el); + jsPlumb.CurrentLibrary.setAttribute(ele, attName, attValue); + }; + + var _addClass = function(el, clazz) { + var ele = __getElementObject(el); + jsPlumb.CurrentLibrary.addClass(ele, clazz); + }; + + var _getElementObject = function(elId) { + return __getElementObject(elId); + }; + + var _getOffset = function(el) { + var ele = __getElementObject(el); + return jsPlumb.CurrentLibrary.getOffset(ele); + }; + + var _getSize = function(el) { + var ele = __getElementObject(el); + return jsPlumb.CurrentLibrary.getSize(ele); + }; + + var _appendElement = function(canvas, parent) { + if (!parent) document.body.appendChild(canvas); + else jsPlumb.CurrentLibrary.appendElement(canvas, parent); + }; + + /** + * gets an id for the given element, creating and setting one if necessary. + */ + var _getId = function(element) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id) { + id = "_jsPlumb_" + new String((new Date()).getTime()); + _setAttribute(ele, "id", id); + } + return id; + }; + + /** + * inits a draggable if it's not already initialised. + * + * TODO: we need to hook in each library to this method. they need to be given the opportunity to + * wrap/insert lifecycle functions, because each library does different things. for instance, jQuery + * supports the notion of 'revert', which will clean up a dragged endpoint; MooTools does not. jQuery + * also supports 'opacity' and MooTools does not; jQuery supports z-index of the draggable; MooTools + * does not. i could go on. the point is...oh. initDraggable can do this. ok. + */ + var _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + // dragging + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend({}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + } + } + }; + + + /** + * helper to create a canvas. + * @param clazz optional class name for the canvas. + */ + var _newCanvas = function(clazz, parent) { + var canvas = document.createElement("canvas"); + _appendElement(canvas, parent); + canvas.style.position="absolute"; + if (clazz) { canvas.className=clazz; } + _getId(canvas); // set an id. + + if (ie) { + // for IE we have to set a big canvas size. actually you can override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + /** + * performs the given function operation on all the connections found for the given element + * id; this means we find all the endpoints for the given element, and then for each endpoint + * find the connectors connected to it. then we pass each connection in to the given + * function. + */ + var _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + for (var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }; + /** + * perform an operation on all elements. + */ + var _operationOnAll = function(func) { + for (var elId in endpointsByElement) { + _operation(elId, func); + } + }; + /** + * helper to remove an element from the DOM. + */ + var _removeElement = function(element, parent) { + if (element != null) { + if (!parent) { + try { document.body.removeChild(element); } + catch (e) { } + } else { + jsPlumb.CurrentLibrary.removeElement(element, parent); + } + } + }; + /** + * helper to remove a list of elements from the DOM. + */ + var _removeElements = function(elements, parent) { + for (var i in elements) + _removeElement(elements[i], parent); + }; + /** + * helper method to remove an item from a list. + */ + var _removeFromList = function(map, key, value) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete( l[i] ); + l.splice( i, 1 ); + return true; + } + } + return false; + }; + /** + * Sets whether or not the given element(s) should be draggable, regardless of what a particular + * plumb command may request. + * + * @param element May be a string, a jQuery elements, or a list of strings/jquery elements. + * @param draggable Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + var _helper = function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }; + + return _elementProxy(element, _helper); + }; + /** + * private method to do the business of hiding/showing. + * @param el either Id of the element in question or a jquery object for the element. + * @param state String specifying a value for the css 'display' property ('block' or 'none'). + */ + var _setVisible = function(el, state) { + var elId = _getAttribute(el, "id"); + var f = function(jpc) { + jpc.canvas.style.display = state; + }; + _operation(elId, f); + }; + /** + * toggles the draggable state of the given element(s). + * @param el either an id, or a jquery object, or a list of ids/jquery objects. + */ + var _toggleDraggable = function(el) { + var fn = function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }; + return _elementProxy(el, fn); + }; + /** + * private method to do the business of toggling hiding/showing. + * @param elId Id of the element in question + */ + var _toggleVisible = function(elId) { + var f = function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }; + + _operation(elId, f); + + //todo this should call _elementProxy, and pass in the _operation(elId, f) call as a function. cos _toggleDraggable does that. + }; + /** + * updates the offset and size for a given element, and stores the values. + * if 'offset' is not null we use that (it would have been passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we use the offset, outerWidth and outerHeight methods to get + * the current values. + */ + var _updateOffset = function(elId, offset, recalc) { + if (recalc || offset == null) { // if forced repaint or no offset available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + } else { + offsets[elId] = offset; + } + }; + + /** + * wraps one function with another, creating a placeholder for the wrapped function + * if it was null. this is used to wrap the various drag/drop event functions - to allow + * jsPlumb to be notified of important lifecycle events without imposing itself on the user's + * drap/drop functionality. + * TODO: determine whether or not we should support an error handler concept, if one of the functions fails. + */ + var _wrap = function(wrappedFunction, newFunction) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + try { newFunction.apply(this, arguments); } + catch (e) { + _log('jsPlumb function failed : ' + e); + } + try { wrappedFunction.apply(this, arguments); } + catch (e) { + _log('wrapped function failed : ' + e); + } + }; + }; + + /* + Class: Anchor + Models a position relative to the origin of some element that an Endpoint can be located. + */ + /* + Function: Anchor + + Constructor for the Anchor class + + Parameters: + params - Anchor parameters. This should contain three values, and may optionally have an 'offsets' argument: + + - x : the x location of the anchor as a fraction of the total width. + - y : the y location of the anchor as a fraction of the total height. + - orientation : an [x,y] array indicating the general direction a connection from the anchor should go in. for more info on this, see the documentation, or the docs in jquery-jsPlumb-defaults-XXX.js for the default Anchors. + - offsets : an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. may be null. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; this.y = params.y || 0; + var orientation = params.orientation || [0,0]; + var lastTimestamp = null; + var lastReturnValue = null; + this.offsets = params.offsets || [0,0]; + //TODO: fix this properly. since Anchors are often static, this timestamping business + // does not work at all well. the timestamp should be inside the Endpoint, because they + // are _never_ static. the method that Connection uses to find an anchor location should + // be done through the Endpoint class, which can then deal with the timestamp. + // and, in fact, we should find out whether or not we even get a speed enhancement from doing + // this. + this.compute = function(xy, wh, element) { + _trace('anchor compute'); + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1]]; + var container = element? element.container : null; + var containerAdjustment = {left:0, top:0 }; + if (container != null) { + var eo = _getElementObject(container); + var o = _getOffset(eo); + var sl = jsPlumb.CurrentLibrary.getScrollLeft(eo); + var st = jsPlumb.CurrentLibrary.getScrollTop(eo); + containerAdjustment.left = o.left - sl; + containerAdjustment.top = o.top - st; + lastReturnValue[0] = lastReturnValue[0] - containerAdjustment.left; + lastReturnValue[1] = lastReturnValue[1] - containerAdjustment.top; + } + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y && this.offsets[0] == anchor.offsets[0] && this.offsets[1] == anchor.offsets[1] && o[0] == ao[0] && o[1] == ao[1]; + }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from its position relative to the anchor it is floating relative to. + * + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(_getElementObject(refCanvas)); + + // these are used to store the current relative position of our anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating anchor is hovering over another anchor. + var orientation = null; + + this.compute = function(xy, wh, txy, twh) { + // set these for the getOrientation method to use. + xDir = 0;//xy[0] < txy[0] ? -1 : xy[0] == txy[0] ? 0 : 1; + yDir = 0;//xy[1] < txy[1] ? -1 : xy[1] == txy[1] ? 0 : 1; + return [xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other anchor: if it declares zero for some direction, we declare zero too. + // this might not be the most awesome. perhaps we can come up with a better way. it's just so that the line we draw looks + // like it makes sense. maybe this wont make sense. + return [Math.abs(o[0]) * xDir * -1, Math.abs(o[1]) * yDir * -1]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering over another anchor; + * we want to assume that anchor's orientation for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no longer hovering + * over another anchor; we should resume calculating orientation as we normally do. + */ + this.out = function() { + orientation = null; + }; + }; + + // ************** connection + // **************************************** + /** + * allowed params: + * source: source element (string or a jQuery element) (required) + * target: target element (string or a jQuery element) (required) + * + * anchors: optional array of anchor placements. defaults to BottomCenter for source + * and TopCenter for target. + */ + var Connection = function(params) { + + // ************** get the source and target and register the connection. ******************* + var self = this; + var id = new String('_jsplumb_c_' + (new Date()).getTime()); + this.getId = function() { return id; }; + this.container = params.container || _currentInstance.Defaults.Container; // may be null; we will append to the body if so. + // get source and target as jQuery objects + this.source = _getElementObject(params.source); + this.target = _getElementObject(params.target); + this.sourceId = _getAttribute(this.source, "id"); + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + this.scope = params.scope; // scope may have been passed in to the connect call. + // if it wasn't, we will pull it from the source endpoint, + // after having initialised the endpoints. + + // init endpoints + this.endpoints = []; + this.endpointStyles = []; + var prepareEndpoint = function(existing, index, params, element) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } + else { + if(!params.endpoints) params.endpoints = [null,null]; + var ep = params.endpoints[index] || params.endpoint || _currentInstance.Defaults.Endpoints[index] || jsPlumb.Defaults.Endpoints[index] || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || new jsPlumb.Endpoints.Dot(); + if (!params.endpointStyles) params.endpointStyles = [null,null]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + var a = params.anchors ? params.anchors[index] : _currentInstance.Defaults.Anchors[index] || jsPlumb.Defaults.Anchors[index] || _currentInstance.Defaults.Anchor || jsPlumb.Defaults.Anchor || jsPlumb.Anchors.BottomCenter; + var e = new Endpoint({style:es, endpoint:ep, connections:[self], anchor:a, source:element, container:self.container }); + self.endpoints[index] = e; + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || _currentInstance.Defaults.Connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + this.paintStyle = this.endpoints[0].connectorStyle || this.endpoints[1].connectorStyle || params.paintStyle || _currentInstance.Defaults.PaintStyle || jsPlumb.Defaults.PaintStyle; + + _updateOffset(this.sourceId); + _updateOffset(this.targetId); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var anchorLoc = this.endpoints[0].anchor.compute([myOffset.left, myOffset.top], myWH, this.endpoints[0]); + this.endpoints[0].paint(anchorLoc); + myOffset = offsets[this.targetId]; myWH = sizes[this.targetId]; + anchorLoc = this.endpoints[1].anchor.compute([myOffset.left, myOffset.top], myWH, this.endpoints[1]); + this.endpoints[1].paint(anchorLoc); + + // *************** create canvas on which the connection will be drawn ************ + var canvas = _newCanvas(jsPlumb.connectorClass, self.container); + this.canvas = canvas; + + /** + * paints the connection. + * @param elId Id of the element that is in motion + * @param ui current library's event system ui object (present if we came from a drag to get here) + * @param recalc whether or not to recalculate element sizes. this is true if a repaint caused this to be painted. + */ + this.paint = function(elId, ui, recalc, timestamp) { + + if (log) log.debug("Painting Connection; element in motion is " + elId + "; ui is [" + ui + "]; recalc is [" + recalc + "]"); + + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = !(elId == this.sourceId); + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + if (this.canvas.getContext) { + //if (recalc) { + _updateOffset(elId, ui, recalc); + _updateOffset(tId); // update the target if this is a forced repaint. otherwise, only the source has been moved. + //} + + var myOffset = offsets[elId]; + var otherOffset = offsets[tId]; + var myWH = sizes[elId]; + var otherWH = sizes[tId]; + + var ctx = canvas.getContext('2d'); + //TODO: why are these calculated again? they were calculated in the _draw function. + var sAnchorP = this.endpoints[sIdx].anchor.compute([myOffset.left, myOffset.top], myWH, this.endpoints[sIdx]); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.compute([otherOffset.left, otherOffset.top], otherWH, this.endpoints[tIdx]); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, this.paintStyle.lineWidth); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + jsPlumb.extend(ctx, this.paintStyle); + + if (this.paintStyle.gradient && !ie) { + var g = swap ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + for (var i = 0; i < this.paintStyle.gradient.stops.length; i++) + g.addColorStop(this.paintStyle.gradient.stops[i][0],this.paintStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + + this.connector.paint(dim, ctx); + } + }; + + //TODO: should this take a timestamp? probably. it reduces the amount of time + // spent figuring out anchor locations. + this.repaint = function() { + this.paint(this.sourceId, null, true); + }; + + _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + }; + + /* + Class: Endpoint + Models an endpoint. Can have one to N connections emanating from it (although how to handle that in the UI is + a very good question). also has a Canvas and paint style. + */ + + /* + Function: Endpoint + This is the Endpoint class constructor. + Parameters: + anchor - anchor for the endpoint, of type jsPlumb.Anchor. may be null. + endpoint - endpoint object, of type jsPlumb.Endpoint. may be null. + style - endpoint style, a js object. may be null. + source - element the endpoint is attached to, of type jquery object. Required. + canvas - canvas element to use. may be, and most often is, null. + connections - optional list of connections to configure the endpoint with. + isSource - boolean. indicates the endpoint can act as a source of new connections. optional. + dragOptions - if isSource is set to true, you can supply arguments for the jquery draggable method. optional. + connectionStyle - if isSource is set to true, this is the paint style for connections from this endpoint. optional. + connector - optional connector type to use. + isTarget - boolean. indicates the endpoint can act as a target of new connections. optional. + dropOptions - if isTarget is set to true, you can supply arguments for the jquery droppable method. optional. + reattach - optional boolean that determines whether or not the connections reattach after they + have been dragged off an endpoint and left floating. defaults to false: connections + dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + params = params || {}; + // make a copy. then we can use the wrapper function. + jsPlumb.extend({}, params); + var self = this; + var id = new String('_jsplumb_e_' + (new Date()).getTime()); + this.getId = function() { return id; }; + self.anchor = params.anchor || jsPlumb.Anchors.TopCenter; + var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot(); + var _style = params.style || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + this.connectorStyle = params.connectorStyle; + this.connector = params.connector; + var _element = params.source; + this.container = params.container || jsPlumb.Defaults.Container; + var _elementId = _getAttribute(_element, "id"); + var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of. + this.canvas = params.canvas || _newCanvas(jsPlumb.endpointClass, this.container); + this.connections = params.connections || []; + this.scope = params.scope || DEFAULT_SCOPE; + var _reattach = params.reattach || false; + var floatingEndpoint = null; + var inPlaceCopy = null; + this.addConnection = function(connection) { + self.connections.push(connection); + }; + this.removeConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) + self.connections.splice(idx, 1); + }; + this.makeInPlaceCopy = function() { + var e = new Endpoint({anchor:self.anchor, source:_element, style:_style, endpoint:_endpoint}); + return e; + }; + /** + * returns whether or not this endpoint is connected to the given endpoint. + * @param endpoint Endpoint to test. + * @since 1.1.1 + * + * todo: needs testing. will this work if the endpoint passed in is the source? + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + this.isFloating = function() { return floatingEndpoint != null; }; + /** + * first pass at default ConnectorSelector: returns the first connection, if we have any. + * modified a little, 5/10/2010: now it only returns a connector if we have not got equal to or more than _maxConnector connectors + * attached. otherwise it is assumed a new connector is ok. but note with this setup we can't select any other connection than the first + * one. what if this could return a list? that implies everything should work with a list - dragging etc. could be nasty. could also + * be cool. + */ + var connectorSelector = function() { + return self.connections.length == 0 || self.connections.length < _maxConnections ? null : self.connections[0]; + }; + + this.isFull = function() { return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); }; + + /** + * a deep equals check. everything must match, including the anchor, styles, everything. + * TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor) && + true; + }; + + /** + * paints the Endpoint, recalculating offset and anchor positions if necessary. + */ + this.paint = function(anchorPoint, connectorPaintStyle, canvas) { + + if (log) log.debug("Painting Endpoint with elementId [" + _elementId + "]; anchorPoint is [" + anchorPoint + "]"); + + if (anchorPoint == null) { + // do we always want to force a repaint here? i dont think so! + var xy = offsets[_elementId]; + var wh = sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset(_elementId); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + anchorPoint = self.anchor.compute([xy.left, xy.top], wh, self); + } + _endpoint.paint(anchorPoint, self.anchor.getOrientation(), canvas || self.canvas, _style, connectorPaintStyle || _style); + }; + + + // is this a connection source? we make it draggable and have the drag listener + // maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + + // first the question is, how many connections are on this endpoint? if it's only one, then excellent. otherwise we will either need a way + // to select one connection from the list, or drag them all. if we had a pluggable 'ConnectorSelector' interface we could probably + // provide a way for people to implement their own UI components to do the connector selection. the question in that particular case would be how much + // information the interface needs from jsPlumb at execution time. if, however, we leave out the connector selection, and drag them all, + // that wouldn't be too hard to organise. perhaps that behaviour would be on a switch for the endpoint, or perhaps the ConnectorSelector + // interface returns a List, with the default implementation just returning everything. i think i like that. + // + // let's say for now that there is just one endpoint, cos we need to get this going before we can consider a list of them anyway. + // the major difference between that case and the case of a new endpoint is actually quite small - it's a question of where the + // Connection comes from. for a new one, we create a new one. obviously. otherwise we just get the jpc from the Endpoint + // (remember we're only assuming one connection right now). so all of the UI stuff we do to create the floating endpoint etc + // will still be valid, but when we stop dragging, we'll have to do something different. if we stop with a valid drop i think it will + // be the same process. but if we stop with an invalid drop we have to reset the Connection to how it was when we got it. + var start = function() { + + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + var nE = _getElementObject(n); + _appendElement(n); // + // create and assign an id, and initialize the offset. + //TODO can't this be replaced with a _getId call? + var id = "" + new String(new Date().getTime()); + _setAttribute(nE, "id", id); + _updateOffset(id); + // store the id of the dragging div and the source element. the drop function + // will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor({reference:self.anchor, referenceCanvas:self.canvas}); + floatingEndpoint = new Endpoint({ style:{fillStyle:'rgba(0,0,0,0)'}, endpoint:_endpoint, anchor:floatingAnchor, source:nE }); + + jpc = connectorSelector(); + if (jpc == null) { + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = new Connection({ + sourceEndpoint:self, + targetEndpoint:floatingEndpoint, + source:_getElementObject(_element), + target:_getElementObject(n), + anchors:[self.anchor, floatingAnchor], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + connector: params.connector + }); + } else { + existingJpc = true; + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; + // probably we should remove the connection? and add it back if the user + // does not drop it somewhere proper. + self.removeConnection(jpc); + if (anchorIdx == 0){ + existingJpcParams = [jpc.source, jpc.sourceId]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + }else { + existingJpcParams = [jpc.target, jpc.targetId]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // todo unregister on stop + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + }; + + var dragOptions = params.dragOptions || { }; + + var defaultOpts = jsPlumb.extend({}, jsPlumb.CurrentLibrary.defaultDragOptions); + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + var startEvent = jsPlumb.CurrentLibrary.dragEvents['start']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements([n, floatingEndpoint.canvas]); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + if (_reattach) { + jpc.floatingAnchorIndex = null; + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } + else { + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeElement(jpc.canvas); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + } else { + _removeElement(jpc.canvas); + self.removeConnection(jpc); + } + } + jpc = null; + delete floatingEndpoint; + delete inPlaceCopy; + self.paint(); + } + ); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + }; + + // connector target + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend({}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + dropOptions[dropEvent]= _wrap(dropOptions[dropEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable), "dragId"); + var elId = _getAttribute(_getElementObject(draggable),"elId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // remove this jpc from the current endpoint + jpc.endpoints[idx].removeConnection(jpc); + if (jpc.suspendedEndpoint) + jpc.suspendedEndpoint.removeConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + _addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + jsPlumb.repaint(elId); + + delete floatingConnections[id]; + }); + + dropOptions[overEvent]= _wrap(dropOptions[overEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + }); + + jsPlumb.CurrentLibrary.initDroppable(_getElementObject(self.canvas), dropOptions); + } + + return self; + }; + + /* + Property: Defaults + These are the default settings for jsPlumb, that is what will be used if you do not supply specific pieces of information + to the various API calls. A convenient way to implement your own look and feel can be to override these defaults by including a script + somewhere after the jsPlumb include, but before you make any calls to jsPlumb, for instance in this example we set the PaintStyle to be + a blue line of 27 pixels: + > jsPlumb.Defaults.PaintStyle = { lineWidth:27, strokeStyle:'blue' } + */ + this.Defaults = { + Anchor : null, + Anchors : [ null, null ], + Connector : null, + Container : null, + DragOptions: { }, + DropOptions: { }, + Endpoint : null, + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : null }, + EndpointStyles : [ null, null ], + MaxConnections : null, + PaintStyle : { lineWidth : 10, strokeStyle : 'red' }, + Scope : "_jsPlumb_DefaultScope" + }; + + /* + Property: connectorClass + The CSS class to set on Connection canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = '_jsPlumb_connector'; + + /* + Property: endpointClass + The CSS class to set on Endpoint canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = '_jsPlumb_endpoint'; + + /* + Property: Anchors + Default jsPlumb Anchors. These are supplied in the file jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form .jsPlumb-all-x.x.x.js. You can provide your own Anchors by supplying them in a script that is loaded after jsPlumb, for instance: + > jsPlumb.Anchors.MyAnchor = { ....anchor code here. see the documentation. } + */ + this.Anchors = {}; + + /* + Property: Connectors + Default jsPlumb Connectors. These are supplied in the file jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form .jsPlumb-all-x.x.x.js. You can provide your own Connectors by supplying them in a script that is loaded after jsPlumb, for instance: + > jsPlumb.Connectors.MyConnector = { ....connector code here. see the documentation. } + */ + this.Connectors = {}; + + /* + Property: Endpoints + Default jsPlumb Endpoints. These are supplied in the file jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form .jsPlumb-all-x.x.x.js. You can provide your own Endpoints by supplying them in a script that is loaded after jsPlumb, for instance: + > jsPlumb.Endpoints.MyEndpoint = { ....endpoint code here. see the documentation. } + */ + this.Endpoints = {}; + + /* + Function: addEndpoint + Adds an Endpoint to a given element. + Parameters: + target - Element to add the endpoint to. either an element id, or a selector representing some element. + params - Object containing Endpoint options (more info required) + Returns: + The newly created Endpoint. + See Also: + + */ + this.addEndpoint = function(target, params) { + params = jsPlumb.extend({}, params); + var el = _getElementObject(target); + var id = _getAttribute(el,"id"); + params.source = el; + _updateOffset(id); + var e = new Endpoint(params); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id]; + var myWH = sizes[id]; + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH, e); + e.paint(anchorLoc); + return e; + }; + + /* + Function: addEndpoint + Adds a list of Endpoints to a given element. + Parameters: + target - element to add the endpoint to. either an element id, or a selector representing some element. + endpoints - List of objects containing Endpoint options. one Endpoint is created for each entry in this list. + Returns: + List of newly created Endpoints, one for each entry in the 'endpoints' argument. + See Also: + + */ + this.addEndpoints = function(target, endpoints) { + var results = []; + for (var i = 0; i < endpoints.length; i++) { + results.push(jsPlumb.addEndpoint(target, endpoints[i])); + } + return results; + }; + + /* + Function: animate + Wrapper around supporting library's animate function; injects a call to jsPlumb in the 'step' function (creating it if necessary). + This only supports the two-arg version of the animate call in jQuery - the one that takes an 'options' object as the second arg. + MooTools has only one method - a two arg one. Which is handy. + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the standard jQuery animate call. + options - The 'options' argument you want passed to the standard jQuery animate call. + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el); + var id = _getAttribute(el,"id"); + //TODO this is not agnostic yet. + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() + { + _currentInstance.repaint(id); + }); + + // you know, probably onComplete should repaint too. that will help keep up + // with fast animations. + options[completeFunction] = _wrap(options[completeFunction], function() + { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /** + * clears the cache used to lookup elements by their id. if you remove any elements + * from the DOM you should call this to ensure that if you add them back in jsPlumb does not + * have a stale handle. + */ + this.clearCache = function() { + delete cached; + cached = {}; + }; + + /* + Function: connect + Establishes a connection between two elements. + Parameters: + params - Object containing setup for the connection. see documentation. + Returns: + The newly created Connection. + */ + this.connect = function(params) { + if (params.sourceEndpoint && params.sourceEndpoint.isFull()) { + _log("could not add connection; source endpoint is full"); + return; + } + + if (params.targetEndpoint && params.targetEndpoint.isFull()) { + _log("could not add connection; target endpoint is full"); + return; + } + + var jpc = new Connection(params); + // add to list of connections (by scope). + _addToList(connectionsByScope, jpc.scope, jpc); + + // force a paint + _draw(jpc.source); + + return jpc; + }; + + /** + * not implemented yet. params object will have sourceEndpoint and targetEndpoint members; these will be Endpoints. + connectEndpoints : function(params) { + var jpc = Connection(params); + + },*/ + + /* + Function: detach + Removes a connection. + Parameters: + sourceId - Id of the first element in the connection. A String. + targetId - iI of the second element in the connection. A String. + Returns: + true if successful, false if not. + */ + this.detach = function(sourceId, targetId) { + var f = function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + return true; + } + + }; + + // todo: how to cleanup the actual storage? a third arg to _operation? + _operation(sourceId, f); + }; + + /* + Function: detachAll + Removes all an element's connections. + Parameters: + el - either the id of the element, or a selector for the element. + Returns: + void + */ + this.detachAll = function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + var c = endpoints[i].connections.length; + if (c > 0) { + for (var j = 0; j < c; j++) { + var jpc = endpoints[i].connections[0]; + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + } + } + } + }; + + /* + Function: detachEverything + Remove all Connections from all elements, but leaves Endpoints in place. + Returns: + void + See Also: + + */ + this.detachEverything = function() { + for(var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + var c = endpoints[i].connections.length; + if (c > 0) { + for (var j = 0; j < c; j++) { + var jpc = endpoints[i].connections[0]; + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + } + } + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + /* + Function: draggable + initialises the draggability of some element or elements. + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for (var i = 0; i < el.length; i++) + { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) + _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + Parameters: + o1 - object to extend + o2 - object to extend o1 with + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + Function: getConnections + Gets all or a subset of connections currently managed by this jsPlumb instance. + Parameters: + options - a JS object that holds options defining what sort of connections you're looking for. Valid values are: + scope this may be a String or a list of Strings. jsPlumb will only return Connections whose scope matches what this option + defines. If you omit it, you will be given Connections of every scope. + source may be a string or a list of strings; constraints results to have only Connections whose source is this object. + target may be a string or a list of strings; constraints results to have only Connections whose target is this object. + + The return value is a dictionary in this format: + + { + 'scope1': [ + {sourceId:'window1', targetId:'window2'}, + {sourceId:'window3', targetId:'window4'}, + {sourceId:'window1', targetId:'window3'} + ], + 'scope2': [ + {sourceId:'window1', targetId:'window3'} + ] + } + + */ + this.getConnections = function(options) { + var r = {}; + options = options || {}; + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') r.push(input); + else r = input; + } + return r; + }; + var scopes = prepareList(options.scope); + var sources = prepareList(options.source); + var targets = prepareList(options.target); + var filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }; + for (var i in connectionsByScope) { + if (filter(scopes, i)) { + r[i] = []; + for (var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + r[i].push({sourceId:c.sourceId, targetId:c.targetId}); + } + } + } + return r; + }; + + /* + Function: getDefaultScope + Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a scope to an endpoint + or connection allows you to support different types of connections in the same UI. but if you're only interested in one type of connection, + you don't need to supply a scope. this method will probably be used by very few people; it's good for testing though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: hide + Sets an element's connections to be hidden. + Parameters: + el - either the id of the element, or a selector for the element. + Returns: + void + */ + this.hide = function(el) { + _setVisible(el, "none"); + }; + + /* + Function: makeAnchor + Creates an anchor with the given params. + Parameters: + x - the x location of the anchor as a fraction of the total width. + y - the y location of the anchor as a fraction of the total height. + xOrientation - value indicating the general direction a connection from the anchor should go in, in the x direction. + yOrientation - value indicating the general direction a connection from the anchor should go in, in the y direction. + xOffset - a fixed offset that should be applied in the x direction that should be applied after the x position has been figured out. optional. defaults to 0. + yOffset - a fixed offset that should be applied in the y direction that should be applied after the y position has been figured out. optional. defaults to 0. + Returns: + The newly created Anchor. + */ + this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards compatibility if we are given only one value we assume it's a call in the old form. + var params = {}; + if (arguments.length == 1) jsPlumb.extend(params, x); + else { + params = {x:x, y:y}; + if (arguments.length >= 4) { + params.orientation = [arguments[2], arguments[3]]; + } + if (arguments.length == 6) params.offsets = [arguments[4], arguments[5]]; + } + var a = new Anchor(params); + a.clone = function() { return new Anchor(params); }; + return a; + }; + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + Parameters: + el - either the id of the element or a selector representing the element. + Returns: + void + See Also: + + */ + this.repaint = function(el) { + + var _processElement = function(el) { + var ele = _getElementObject(el); + _draw(ele); + }; + + // TODO: support a jQuery result object too! + + // support both lists... + if (typeof el =='object') { + for (var i = 0; i < el.length; i++) + _processElement(el[i]); + } // ...and single strings. + else _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + Returns: + void + See Also: + + */ + this.repaintEverything = function() { + for (var elId in endpointsByElement) { + _draw(_getElementObject(elId)); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + Parameters: + el - either an element id, or a selector for an element. + Returns: + void + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"); + // first remove all Connections. + jsPlumb.detachAll(elId); + var ebe = endpointsByElement[elId]; + for (var i in ebe) { + _removeElement(ebe[i].canvas); + } + endpointsByElement[elId] = []; + }; + + /* + Function: removeEveryEndpoint + Removes every Endpoint in this instance of jsPlumb. + Returns: + void + See Also: + + */ + this.removeEveryEndpoint = function() { + for(var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + _removeElement(endpoints[i].canvas, endpoints[i].container); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + }; + + /* + Function: removeEndpoint + Removes the given Endpoint from the given element. + Parameters: + el - either an element id, or a selector for an element. + endpoint - Endpoint to remove. this is an Endpoint object, such as would have been returned from a call to addEndpoint. + Returns: + void + See Also: + + */ + this.removeEndpoint = function(el, endpoint) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + if (ebe) { + if(_removeFromList(endpointsByElement, elId, endpoint)) + _removeElement(endpoint.canvas); + } + }; + + /* + Function:reset + removes all endpoints and connections and clears the element cache. + */ + this.reset = function() { + this.detachEverything(); + this.removeEveryEndpoint(); + this.clearCache(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + Parameters: + value - whether or not to automatically repaint when the window is resized. + Returns: + void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + Function: setDefaultNewCanvasSize + Sets the default size jsPlumb will use for a new canvas (we create a square canvas so one value is all that is required). + This is a hack for IE, because ExplorerCanvas seems to need for a canvas to be larger than what you are going to draw on + it at initialisation time. The default value of this is 1200 pixels, which is quite large, but if for some reason you're + drawing connectors that are bigger, you should adjust this value appropriately. + Parameters: + size - The default size to use. jsPlumb will use a square canvas so you need only supply one value. + Returns: + void + */ + this.setDefaultNewCanvasSize = function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }; + + /* + Function: setDefaultScope + Sets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a scope to an endpoint + or connection allows you to support different types of connections in the same UI. but if you're only interested in one type of connection, + you don't need to supply a scope. this method will probably be used by very few people; it just instructs jsPlumb to use a different key + for the default scope. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + Function: setDraggable + Sets whether or not a given element is draggable, regardless of what any plumb command may request. + Parameters: + el - either the id for the element, or a selector representing the element. + Returns: + void + */ + this.setDraggable = _setDraggable; + + /* + Function: setDraggableByDefault + Sets whether or not elements are draggable by default. Default for this is true. + Parameters: + draggable - value to set + Returns: + void + */ + this.setDraggableByDefault = function(draggable) { + _draggableByDefault = draggable; + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + Function: setRepaintFunction + Sets the function to fire when the window size has changed and a repaint was fired. + Parameters: + f - Function to execute. + Returns: + void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + Function: show + Sets an element's connections to be visible. + Parameters: + el - either the id of the element, or a selector for the element. + Returns: + void + */ + this.show = function(el) { + _setVisible(el, "block"); + }; + + /* + Function: sizeCanvas + Helper to size a canvas. You would typically use this when writing your own Connector or Endpoint implementation. + Parameters: + x - [int] x position for the Canvas origin + y - [int] y position for the Canvas origin + w - [int] width of the canvas + h - [int] height of the canvas + Returns: + void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; canvas.height = h; + canvas.style.width = w + "px"; canvas.width = w; + canvas.style.left = x + "px"; canvas.style.top = y + "px"; + // _currentInstance.CurrentLibrary.setPosition(canvas, x, y); + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards compatibility + */ + this.toggle = _toggleVisible; + + /* + Function: toggleVisible + Toggles visibility of an element's connections. + Parameters: + el - either the element's id, or a selector representing the element. + Returns: + void, but should be updated to return the current state + */ + //TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + Function: toggleDraggable + Toggles draggability (sic) of an element's connections. + Parameters: + el - either the element's id, or a selector representing the element. + Returns: + The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + Function: unload + Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element + Returns: + void + */ + this.unload = function() { + delete endpointsByElement; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + }; + + /* + Function: wrap + Helper method to wrap an existing function with one of your own. This is used by the various + implementations to wrap event callbacks for drag/drop etc; it allows jsPlumb to be + transparent in its handling of these things. If a user supplies their own event callback, + for anything, it will always be called. + Parameters: + */ + this.wrap = _wrap; + this.trace = _trace; + this.clearTrace = _clearTrace; + this.clearAllTraces = _clearAllTraces; + this.getTrace = _getTrace; + + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(); + if (_defaults) + jsPlumb.extend(j.Defaults, _defaults); + return j; + }; + +})(); + +/** +* jsPlumb-defaults-1.2-RC1 +* +* This script contains the default Anchors, Endpoints and Connectors for jsPlumb. It should be used with jsPlumb 1.1.0 and above; +* prior to version 1.1.0 of jsPlumb the defaults were included inside the main script. +* +* Version 1.1.1 of this script adds the Triangle Endpoint, written by __________ and featured on this demo: +* +* http://www.mintdesign.ru/blog/javascript-jsplumb-making-graph +* http://www.mintdesign.ru/site/samples/jsplumb/jsPlumb-graph-sample.htm +* +* NOTE: for production usage you should use jsPlumb-all-1.1.1-min.js, which contains the main jsPlumb script and this script together, +* in a minified file. +*/ + +(function() { + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * getOrientation : function() { return [ox, oy]; } + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + jsPlumb.Anchors.TopCenter = jsPlumb.makeAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors.BottomCenter = jsPlumb.makeAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors.LeftMiddle = jsPlumb.makeAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors.RightMiddle = jsPlumb.makeAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors.Center = jsPlumb.makeAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors.TopRight = jsPlumb.makeAnchor(1, 0, 0,-1); + jsPlumb.Anchors.BottomRight = jsPlumb.makeAnchor(1, 1, 0, 1); + jsPlumb.Anchors.TopLeft = jsPlumb.makeAnchor(0, 0, 0, -1); + jsPlumb.Anchors.BottomLeft = jsPlumb.makeAnchor(0, 1, 0, 1); + + + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (w < 2 * lineWidth) { + w = 2 * lineWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - lineWidth; + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < 2 * lineWidth) { + // minimum size is 2 * line Width + h = 2 * lineWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - lineWidth; + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + var sx = sourcePos[0] < targetPos[0] ? w-xo : xo; + var sy = sourcePos[1] < targetPos[1] ? h-yo : yo; + var tx = sourcePos[0] < targetPos[0] ? xo : w-xo; + var ty = sourcePos[1] < targetPos[1] ? yo : h-yo; + var retVal = [ x, y, w, h, sx, sy, tx, ty ]; + + return retVal; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + }; + + /** + * This Connector draws a Bezier curve with two control points. + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + jsPlumb.Connectors.Bezier = function(curviness) { + + var self = this; + this.majorAnchor = curviness || 150; + this.minorAnchor = 10; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) + { + lineWidth = lineWidth || 0; + var w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth, h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + var canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2), canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + var sx = sourcePos[0] < targetPos[0] ? w - (lineWidth/2): (lineWidth/2), sy = sourcePos[1] < targetPos[1] ? h-(lineWidth/2) : (lineWidth/2); + var tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : w-(lineWidth/2), ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : h-(lineWidth/2); + var CP = self._findControlPoint([sx,sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + var CP2 = self._findControlPoint([tx,ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(sx,tx); var minx2 = Math.min(CP[0], CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(sx,tx); var maxx2 = Math.max(CP[0], CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > w) w = maxx; + if (minx < 0) { + canvasX += minx; var ox = Math.abs(minx); + w += ox; CP[0] += ox; sx += ox; tx +=ox; CP2[0] += ox; + } + + var miny1 = Math.min(sy,ty); var miny2 = Math.min(CP[1], CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(sy,ty); var maxy2 = Math.max(CP[1], CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > h) h = maxy; + if (miny < 0) { + canvasY += miny; var oy = Math.abs(miny); + h += oy; CP[1] += oy; sy += oy; ty +=oy; CP2[1] += oy; + } + + // return [ canvasx, canvasy, canvasWidth, canvasHeight, + // sourceX, sourceY, targetX, targetY, + // controlPoint1_X, controlPoint1_Y, controlPoint2_X, controlPoint2_Y + return [canvasX, canvasY, w, h, sx, sy, tx, ty, CP[0], CP[1], CP2[0], CP2[1] ]; + }; + + this.paint = function(d, ctx) { + + /*var img = new Image(); + img.src = "../img/pattern.jpg"; + ctx.fillStyle = ctx.createPattern(img, 'repeat-y');*/ + + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + //ctx.fill(); + } + }; + + + /** + * Types of endpoint UIs. we supply four - a circle of default radius 10px, a rectangle of + * default size 20x20, an image (with no default), and a Triangle, of default size 15. + * you can supply others of these if you want to - see the documentation for a howto. + */ + + /** + * a round endpoint, with default radius 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + } + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * A Rectangular endpoint, with default size 20x20. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + //todo: the fillStyle needs some thought. we want to support a few options: + // 1. nothing supplied; use the stroke color or the default if no stroke color. + // 2. a fill color supplied - use it + // 3. a gradient supplied - use it + // 4. setting the endpoint to the same color as the bg of the element it is attached to. + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + jsPlumb.Endpoints.Triangle = function(params) { + + params = params || { width:15, height:15 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - width/2; + var y = anchorPoint[1] - height/2; + + jsPlumb.sizeCanvas(canvas, x, y, width, height); + + var ctx = canvas.getContext('2d'); + var + offsetX = 0, + offsetY = 0, + angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = endpointStyle.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + ctx.fill(); + + }; + }; + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' property in the params object.. + */ + jsPlumb.Endpoints.Image = function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + }; +})(); + +//jQuery plugin code; v 1.2.1-RC1 + +(function($){ + /** + * plumbs the results of the selector to some target, using the given options if supplied, + * or the defaults otherwise. + */ + $.fn.plumb = function(options) { + var options = $.extend({}, options); + + return this.each(function() + { + var params = $.extend({source:$(this)}, options); + jsPlumb.connect(params); + }); + }; + + /** + * detaches the results of the selector from the given target or list of targets - 'target' + * may be a String or a List. + */ + $.fn.detach = function(target) { + return this.each(function() { + if (target) { + var id = $(this).attr("id"); + if (typeof target == 'string') target = [target]; + for (var i = 0; i < target.length; i++) + jsPlumb.detach(id, target[i]); + } + }); + }; + + /** + * detaches the results from the selector from all connections. + */ + $.fn.detachAll = function() { + return this.each(function() + { + var id = $(this).attr("id"); + jsPlumb.detachAll(id); + }); + }; + + /** + * adds an endpoint to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoint = function(options) { + var addedEndpoints = []; + this.each(function() { + addedEndpoints.push(jsPlumb.addEndpoint($(this).attr("id"), options)); + }); + return addedEndpoints[0]; + }; + + /** + * adds a list of endpoints to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoints = function(endpoints) { + var addedEndpoints = []; + return this.each(function() { + var e = jsPlumb.addEndpoints($(this).attr("id"), endpoints); + for (var i = 0; i < e.length; i++) addedEndpoints.push(e[i]); + }); + return addedEndpoints; + }; + + /** + * remove the endpoint, if it exists, deleting its UI elements etc. + */ + $.fn.removeEndpoint = function(endpoint) { + this.each(function() { + jsPlumb.removeEndpoint($(this).attr("id"), endpoint); + }); + }; + +})(jQuery); + + +/*TODO: abstract this out from jQuery too! but how...because jsPlumb is not loaded yet. +$(window).bind('resize', function() { + if (resizeTimer) clearTimeout(resizeTimer); + resizeTimer = setTimeout(repaintEverything, 100); + });*/ + + + +/* + * the library agnostic functions, such as find offset, get id, get attribute, extend etc. + */ +(function() { + + jsPlumb.CurrentLibrary = { + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step', + 'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete' + }, + + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).append(child); + }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + * + * since 1.2.1 this method has had a cache applied to it. the cache is a very simple + * cache - a hashmap - so can grow and grow. this may not be optimum. but it does + * speed up performance: + * + * without cache, 312 connections take 5.22 seconds to create. + * with cache, 312 connections take 4.57 seconds to create. that's 13% faster. + */ + + getElementObject : function(el) { + return typeof(el)=='string' ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options) { + // remove helper directive if present. we know what's best! + options.helper = null; + //TODO: if 'revert' is set on the options it causes end points to animate back to + // where they came from, if the connection is aborted. do we care? probably not. + // the todo is to decide whether we care or not. + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.draggable(options); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.droppable(options); + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[1]; + return ui.absolutePosition || ui.offset; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + }, + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).remove(); + }, + + getScrollLeft : function(el) { + return el.scrollLeft(); + }, + + getScrollTop : function(el) { + return el.scrollTop(); + } + }; +})(); diff --git a/archive/1.2.1/jsPlumb-1.2.1-RC1.js b/archive/1.2.1/jsPlumb-1.2.1-RC1.js new file mode 100644 index 000000000..c620d294c --- /dev/null +++ b/archive/1.2.1/jsPlumb-1.2.1-RC1.js @@ -0,0 +1,1773 @@ +/* + * jsPlumb 1.2.1-RC1 + * + * Provides a way to visually connect elements on an HTML page. + * + * Several enhancements are planned for 1.2.1: + * + * - speed enhancements for dragging/animation (fewer element positioning lookups etc) + * + * - the ability to label connectors. each connector type will have to tell us the right place + * for a label to go. + * + * - the ability to interact with connectors/endpoints using the mouse + * + * - the log function should hook up to the console of whichever browser it is in. or library. + * + * - reinstate the context node to put all our canvases in + * + * - possibly support multiple jsplumb instances on the same page. this would not be too hard; + * it would just need to stop being a singleton. 07/05/10: this is done now. + * + * - support for devices with touch events. + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + */ + +(function() { + + /* + Class: jsPlumb + + This is the main entry point to jsPlumb. There are a bunch of static methods you can + use to connect/disconnect etc. Some methods are also added to jQuery's "$.fn" object itself, but + in general jsPlumb is moving away from that model, preferring instead the static class + approach. One of the reasons for this is that with no methods added to the jQuery $.fn object, + it will be easier to provide support for other libraries such as MooTools. + */ + var jsPlumbInstance = function() { + + var _currentInstance = this; + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + + var log = null; + + var repaintFunction = function() { jsPlumb.repaintEverything(); }; + var automaticRepaint = true; + function repaintEverything() { + if (automaticRepaint) + repaintFunction(); + }; + var resizeTimer = null; + + /** + * map of element id -> endpoint lists. an element can have an arbitrary number of endpoints on it, + * and not all of them have to be connected to anything. + */ + var endpointsByElement = {}; + var connectionsByScope = {}; + var offsets = []; + var floatingConnections = {}; + var draggableStates = {}; + var _draggableByDefault = true; + var sizes = []; + var DEFAULT_SCOPE = 'DEFAULT'; + + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + var traced = {}; + var _trace = function(category, event) { + var e = traced[category]; + if (!e) { + e = {}; + traced[category] = e; + } + event = event || 'DEFAULT'; + var ee = e[event]; + if (!ee) { + ee = 0; + e[event] = ee; + } + e[event]++; + }; + + var _clearAllTraces = function() { + delete traced; + traced = {}; + }; + + var _clearTrace = function(category, event) { + var c = traced[category]; + if (!c) return; + if (event) { + c[event] = 0; + } + else c['DEFAULT'] = 0; + }; + + var _getTrace = function(category) { + return traced[category] || {'DEFAULT' : 0 }; + }; + + + var _findIndex = function( a, v, b, s ) { + var _eq = function(o1, o2) { + if (o1 === o2) return true; + else if (typeof o1 == 'object' && typeof o2 == 'object') { + var same = true; + for(var propertyName in o1) { + if(!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for(var propertyName in o2) { + if(!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for( var i = +b || 0, l = a.length; i < l; i++ ) { + if( _eq(a[i], v)) return i; + } + return -1; + }; + + /** + * helper method to add an item to a list, creating the list if it does not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }; + + /** + * Handles the dragging of an element. + * @param element jQuery element + * @param ui UI object from current library's event system + */ + var _draw = function(element, ui) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints) { + var timestamp = '' + (new Date().getTime()); + _trace('draw'); + _updateOffset(id, ui); + var myOffset = offsets[id]; + var myWH = sizes[id]; + // loop through endpoints for this element + for (var i = 0; i < endpoints.length; i++) { + var e = endpoints[i]; + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH, e); + e.paint(anchorLoc); + var l = e.connections; + for (var j = 0; j < l.length; j++) + l[j].paint(id, ui, false, timestamp); // ...and paint them. + } + } + }; + + /** + * helper function: the second argument is a function taking two args - the first is an id + * ui element from the current library (or list of them), and the second is the function + * to run. + */ + var _elementProxy = function(element, fn) { + var retVal = null; + // TODO this might be jquery specific. in fact it probably is. + if (typeof element == 'object' && element.length) { + retVal = []; + for (var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]); + var id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } + else { + var el = _getElementObject(element); + var id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + + return retVal; + }; + + var _log = function(msg) { + //if (console) console.log(msg); + }; + + var _logFnCall = function(fn, args) { + /*if (console) { + var c = args.callee.caller.toString(); + var i = c.indexOf("{"); + var msg = fn + ' : ' + c.substring(0, i); + console.log(msg); + }*/ + }; + + var cached = {}; + var __getElementObject = function(el) { + + if (typeof(el)=='string') { + var e = cached[el]; + if (!e) { + e = jsPlumb.CurrentLibrary.getElementObject(el); + cached[el] = e; + } + return e; + } + else { + return jsPlumb.CurrentLibrary.getElementObject(el); + } + }; + + + /** + * gets the named attribute from the given element (id or element object) + */ + var _getAttribute = function(el, attName) { + var ele = __getElementObject(el); + return jsPlumb.CurrentLibrary.getAttribute(ele, attName); + }; + + var _setAttribute = function(el, attName, attValue) { + var ele = __getElementObject(el); + jsPlumb.CurrentLibrary.setAttribute(ele, attName, attValue); + }; + + var _addClass = function(el, clazz) { + var ele = __getElementObject(el); + jsPlumb.CurrentLibrary.addClass(ele, clazz); + }; + + var _getElementObject = function(elId) { + return __getElementObject(elId); + }; + + var _getOffset = function(el) { + var ele = __getElementObject(el); + return jsPlumb.CurrentLibrary.getOffset(ele); + }; + + var _getSize = function(el) { + var ele = __getElementObject(el); + return jsPlumb.CurrentLibrary.getSize(ele); + }; + + var _appendElement = function(canvas, parent) { + if (!parent) document.body.appendChild(canvas); + else jsPlumb.CurrentLibrary.appendElement(canvas, parent); + }; + + /** + * gets an id for the given element, creating and setting one if necessary. + */ + var _getId = function(element) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id) { + id = "_jsPlumb_" + new String((new Date()).getTime()); + _setAttribute(ele, "id", id); + } + return id; + }; + + /** + * inits a draggable if it's not already initialised. + * + * TODO: we need to hook in each library to this method. they need to be given the opportunity to + * wrap/insert lifecycle functions, because each library does different things. for instance, jQuery + * supports the notion of 'revert', which will clean up a dragged endpoint; MooTools does not. jQuery + * also supports 'opacity' and MooTools does not; jQuery supports z-index of the draggable; MooTools + * does not. i could go on. the point is...oh. initDraggable can do this. ok. + */ + var _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + // dragging + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend({}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + } + } + }; + + + /** + * helper to create a canvas. + * @param clazz optional class name for the canvas. + */ + var _newCanvas = function(clazz, parent) { + var canvas = document.createElement("canvas"); + _appendElement(canvas, parent); + canvas.style.position="absolute"; + if (clazz) { canvas.className=clazz; } + _getId(canvas); // set an id. + + if (ie) { + // for IE we have to set a big canvas size. actually you can override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + /** + * performs the given function operation on all the connections found for the given element + * id; this means we find all the endpoints for the given element, and then for each endpoint + * find the connectors connected to it. then we pass each connection in to the given + * function. + */ + var _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + for (var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }; + /** + * perform an operation on all elements. + */ + var _operationOnAll = function(func) { + for (var elId in endpointsByElement) { + _operation(elId, func); + } + }; + /** + * helper to remove an element from the DOM. + */ + var _removeElement = function(element, parent) { + if (element != null) { + if (!parent) { + try { document.body.removeChild(element); } + catch (e) { } + } else { + jsPlumb.CurrentLibrary.removeElement(element, parent); + } + } + }; + /** + * helper to remove a list of elements from the DOM. + */ + var _removeElements = function(elements, parent) { + for (var i in elements) + _removeElement(elements[i], parent); + }; + /** + * helper method to remove an item from a list. + */ + var _removeFromList = function(map, key, value) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete( l[i] ); + l.splice( i, 1 ); + return true; + } + } + return false; + }; + /** + * Sets whether or not the given element(s) should be draggable, regardless of what a particular + * plumb command may request. + * + * @param element May be a string, a jQuery elements, or a list of strings/jquery elements. + * @param draggable Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + var _helper = function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }; + + return _elementProxy(element, _helper); + }; + /** + * private method to do the business of hiding/showing. + * @param el either Id of the element in question or a jquery object for the element. + * @param state String specifying a value for the css 'display' property ('block' or 'none'). + */ + var _setVisible = function(el, state) { + var elId = _getAttribute(el, "id"); + var f = function(jpc) { + jpc.canvas.style.display = state; + }; + _operation(elId, f); + }; + /** + * toggles the draggable state of the given element(s). + * @param el either an id, or a jquery object, or a list of ids/jquery objects. + */ + var _toggleDraggable = function(el) { + var fn = function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }; + return _elementProxy(el, fn); + }; + /** + * private method to do the business of toggling hiding/showing. + * @param elId Id of the element in question + */ + var _toggleVisible = function(elId) { + var f = function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }; + + _operation(elId, f); + + //todo this should call _elementProxy, and pass in the _operation(elId, f) call as a function. cos _toggleDraggable does that. + }; + /** + * updates the offset and size for a given element, and stores the values. + * if 'offset' is not null we use that (it would have been passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we use the offset, outerWidth and outerHeight methods to get + * the current values. + */ + var _updateOffset = function(elId, offset, recalc) { + if (recalc || offset == null) { // if forced repaint or no offset available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + } else { + offsets[elId] = offset; + } + }; + + /** + * wraps one function with another, creating a placeholder for the wrapped function + * if it was null. this is used to wrap the various drag/drop event functions - to allow + * jsPlumb to be notified of important lifecycle events without imposing itself on the user's + * drap/drop functionality. + * TODO: determine whether or not we should support an error handler concept, if one of the functions fails. + */ + var _wrap = function(wrappedFunction, newFunction) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + try { newFunction.apply(this, arguments); } + catch (e) { + _log('jsPlumb function failed : ' + e); + } + try { wrappedFunction.apply(this, arguments); } + catch (e) { + _log('wrapped function failed : ' + e); + } + }; + }; + + /* + Class: Anchor + Models a position relative to the origin of some element that an Endpoint can be located. + */ + /* + Function: Anchor + + Constructor for the Anchor class + + Parameters: + params - Anchor parameters. This should contain three values, and may optionally have an 'offsets' argument: + + - x : the x location of the anchor as a fraction of the total width. + - y : the y location of the anchor as a fraction of the total height. + - orientation : an [x,y] array indicating the general direction a connection from the anchor should go in. for more info on this, see the documentation, or the docs in jquery-jsPlumb-defaults-XXX.js for the default Anchors. + - offsets : an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. may be null. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; this.y = params.y || 0; + var orientation = params.orientation || [0,0]; + var lastTimestamp = null; + var lastReturnValue = null; + this.offsets = params.offsets || [0,0]; + //TODO: fix this properly. since Anchors are often static, this timestamping business + // does not work at all well. the timestamp should be inside the Endpoint, because they + // are _never_ static. the method that Connection uses to find an anchor location should + // be done through the Endpoint class, which can then deal with the timestamp. + // and, in fact, we should find out whether or not we even get a speed enhancement from doing + // this. + this.compute = function(xy, wh, element) { + _trace('anchor compute'); + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1]]; + var container = element? element.container : null; + var containerAdjustment = {left:0, top:0 }; + if (container != null) { + var eo = _getElementObject(container); + var o = _getOffset(eo); + var sl = jsPlumb.CurrentLibrary.getScrollLeft(eo); + var st = jsPlumb.CurrentLibrary.getScrollTop(eo); + containerAdjustment.left = o.left - sl; + containerAdjustment.top = o.top - st; + lastReturnValue[0] = lastReturnValue[0] - containerAdjustment.left; + lastReturnValue[1] = lastReturnValue[1] - containerAdjustment.top; + } + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y && this.offsets[0] == anchor.offsets[0] && this.offsets[1] == anchor.offsets[1] && o[0] == ao[0] && o[1] == ao[1]; + }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from its position relative to the anchor it is floating relative to. + * + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(_getElementObject(refCanvas)); + + // these are used to store the current relative position of our anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating anchor is hovering over another anchor. + var orientation = null; + + this.compute = function(xy, wh, txy, twh) { + // set these for the getOrientation method to use. + xDir = 0;//xy[0] < txy[0] ? -1 : xy[0] == txy[0] ? 0 : 1; + yDir = 0;//xy[1] < txy[1] ? -1 : xy[1] == txy[1] ? 0 : 1; + return [xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other anchor: if it declares zero for some direction, we declare zero too. + // this might not be the most awesome. perhaps we can come up with a better way. it's just so that the line we draw looks + // like it makes sense. maybe this wont make sense. + return [Math.abs(o[0]) * xDir * -1, Math.abs(o[1]) * yDir * -1]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering over another anchor; + * we want to assume that anchor's orientation for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no longer hovering + * over another anchor; we should resume calculating orientation as we normally do. + */ + this.out = function() { + orientation = null; + }; + }; + + // ************** connection + // **************************************** + /** + * allowed params: + * source: source element (string or a jQuery element) (required) + * target: target element (string or a jQuery element) (required) + * + * anchors: optional array of anchor placements. defaults to BottomCenter for source + * and TopCenter for target. + */ + var Connection = function(params) { + + // ************** get the source and target and register the connection. ******************* + var self = this; + var id = new String('_jsplumb_c_' + (new Date()).getTime()); + this.getId = function() { return id; }; + this.container = params.container || _currentInstance.Defaults.Container; // may be null; we will append to the body if so. + // get source and target as jQuery objects + this.source = _getElementObject(params.source); + this.target = _getElementObject(params.target); + this.sourceId = _getAttribute(this.source, "id"); + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + this.scope = params.scope; // scope may have been passed in to the connect call. + // if it wasn't, we will pull it from the source endpoint, + // after having initialised the endpoints. + + // init endpoints + this.endpoints = []; + this.endpointStyles = []; + var prepareEndpoint = function(existing, index, params, element) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } + else { + if(!params.endpoints) params.endpoints = [null,null]; + var ep = params.endpoints[index] || params.endpoint || _currentInstance.Defaults.Endpoints[index] || jsPlumb.Defaults.Endpoints[index] || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || new jsPlumb.Endpoints.Dot(); + if (!params.endpointStyles) params.endpointStyles = [null,null]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + var a = params.anchors ? params.anchors[index] : _currentInstance.Defaults.Anchors[index] || jsPlumb.Defaults.Anchors[index] || _currentInstance.Defaults.Anchor || jsPlumb.Defaults.Anchor || jsPlumb.Anchors.BottomCenter; + var e = new Endpoint({style:es, endpoint:ep, connections:[self], anchor:a, source:element, container:self.container }); + self.endpoints[index] = e; + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || _currentInstance.Defaults.Connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + this.paintStyle = this.endpoints[0].connectorStyle || this.endpoints[1].connectorStyle || params.paintStyle || _currentInstance.Defaults.PaintStyle || jsPlumb.Defaults.PaintStyle; + + _updateOffset(this.sourceId); + _updateOffset(this.targetId); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var anchorLoc = this.endpoints[0].anchor.compute([myOffset.left, myOffset.top], myWH, this.endpoints[0]); + this.endpoints[0].paint(anchorLoc); + myOffset = offsets[this.targetId]; myWH = sizes[this.targetId]; + anchorLoc = this.endpoints[1].anchor.compute([myOffset.left, myOffset.top], myWH, this.endpoints[1]); + this.endpoints[1].paint(anchorLoc); + + // *************** create canvas on which the connection will be drawn ************ + var canvas = _newCanvas(jsPlumb.connectorClass, self.container); + this.canvas = canvas; + + /** + * paints the connection. + * @param elId Id of the element that is in motion + * @param ui current library's event system ui object (present if we came from a drag to get here) + * @param recalc whether or not to recalculate element sizes. this is true if a repaint caused this to be painted. + */ + this.paint = function(elId, ui, recalc, timestamp) { + + if (log) log.debug("Painting Connection; element in motion is " + elId + "; ui is [" + ui + "]; recalc is [" + recalc + "]"); + + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = !(elId == this.sourceId); + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + if (this.canvas.getContext) { + //if (recalc) { + _updateOffset(elId, ui, recalc); + _updateOffset(tId); // update the target if this is a forced repaint. otherwise, only the source has been moved. + //} + + var myOffset = offsets[elId]; + var otherOffset = offsets[tId]; + var myWH = sizes[elId]; + var otherWH = sizes[tId]; + + var ctx = canvas.getContext('2d'); + //TODO: why are these calculated again? they were calculated in the _draw function. + var sAnchorP = this.endpoints[sIdx].anchor.compute([myOffset.left, myOffset.top], myWH, this.endpoints[sIdx]); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.compute([otherOffset.left, otherOffset.top], otherWH, this.endpoints[tIdx]); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, this.paintStyle.lineWidth); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + jsPlumb.extend(ctx, this.paintStyle); + + if (this.paintStyle.gradient && !ie) { + var g = swap ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + for (var i = 0; i < this.paintStyle.gradient.stops.length; i++) + g.addColorStop(this.paintStyle.gradient.stops[i][0],this.paintStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + + this.connector.paint(dim, ctx); + } + }; + + //TODO: should this take a timestamp? probably. it reduces the amount of time + // spent figuring out anchor locations. + this.repaint = function() { + this.paint(this.sourceId, null, true); + }; + + _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + }; + + /* + Class: Endpoint + Models an endpoint. Can have one to N connections emanating from it (although how to handle that in the UI is + a very good question). also has a Canvas and paint style. + */ + + /* + Function: Endpoint + This is the Endpoint class constructor. + Parameters: + anchor - anchor for the endpoint, of type jsPlumb.Anchor. may be null. + endpoint - endpoint object, of type jsPlumb.Endpoint. may be null. + style - endpoint style, a js object. may be null. + source - element the endpoint is attached to, of type jquery object. Required. + canvas - canvas element to use. may be, and most often is, null. + connections - optional list of connections to configure the endpoint with. + isSource - boolean. indicates the endpoint can act as a source of new connections. optional. + dragOptions - if isSource is set to true, you can supply arguments for the jquery draggable method. optional. + connectionStyle - if isSource is set to true, this is the paint style for connections from this endpoint. optional. + connector - optional connector type to use. + isTarget - boolean. indicates the endpoint can act as a target of new connections. optional. + dropOptions - if isTarget is set to true, you can supply arguments for the jquery droppable method. optional. + reattach - optional boolean that determines whether or not the connections reattach after they + have been dragged off an endpoint and left floating. defaults to false: connections + dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + params = params || {}; + // make a copy. then we can use the wrapper function. + jsPlumb.extend({}, params); + var self = this; + var id = new String('_jsplumb_e_' + (new Date()).getTime()); + this.getId = function() { return id; }; + self.anchor = params.anchor || jsPlumb.Anchors.TopCenter; + var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot(); + var _style = params.style || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + this.connectorStyle = params.connectorStyle; + this.connector = params.connector; + var _element = params.source; + this.container = params.container || jsPlumb.Defaults.Container; + var _elementId = _getAttribute(_element, "id"); + var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of. + this.canvas = params.canvas || _newCanvas(jsPlumb.endpointClass, this.container); + this.connections = params.connections || []; + this.scope = params.scope || DEFAULT_SCOPE; + var _reattach = params.reattach || false; + var floatingEndpoint = null; + var inPlaceCopy = null; + this.addConnection = function(connection) { + self.connections.push(connection); + }; + this.removeConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) + self.connections.splice(idx, 1); + }; + this.makeInPlaceCopy = function() { + var e = new Endpoint({anchor:self.anchor, source:_element, style:_style, endpoint:_endpoint}); + return e; + }; + /** + * returns whether or not this endpoint is connected to the given endpoint. + * @param endpoint Endpoint to test. + * @since 1.1.1 + * + * todo: needs testing. will this work if the endpoint passed in is the source? + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + this.isFloating = function() { return floatingEndpoint != null; }; + /** + * first pass at default ConnectorSelector: returns the first connection, if we have any. + * modified a little, 5/10/2010: now it only returns a connector if we have not got equal to or more than _maxConnector connectors + * attached. otherwise it is assumed a new connector is ok. but note with this setup we can't select any other connection than the first + * one. what if this could return a list? that implies everything should work with a list - dragging etc. could be nasty. could also + * be cool. + */ + var connectorSelector = function() { + return self.connections.length == 0 || self.connections.length < _maxConnections ? null : self.connections[0]; + }; + + this.isFull = function() { return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); }; + + /** + * a deep equals check. everything must match, including the anchor, styles, everything. + * TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor) && + true; + }; + + /** + * paints the Endpoint, recalculating offset and anchor positions if necessary. + */ + this.paint = function(anchorPoint, connectorPaintStyle, canvas) { + + if (log) log.debug("Painting Endpoint with elementId [" + _elementId + "]; anchorPoint is [" + anchorPoint + "]"); + + if (anchorPoint == null) { + // do we always want to force a repaint here? i dont think so! + var xy = offsets[_elementId]; + var wh = sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset(_elementId); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + anchorPoint = self.anchor.compute([xy.left, xy.top], wh, self); + } + _endpoint.paint(anchorPoint, self.anchor.getOrientation(), canvas || self.canvas, _style, connectorPaintStyle || _style); + }; + + + // is this a connection source? we make it draggable and have the drag listener + // maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + + // first the question is, how many connections are on this endpoint? if it's only one, then excellent. otherwise we will either need a way + // to select one connection from the list, or drag them all. if we had a pluggable 'ConnectorSelector' interface we could probably + // provide a way for people to implement their own UI components to do the connector selection. the question in that particular case would be how much + // information the interface needs from jsPlumb at execution time. if, however, we leave out the connector selection, and drag them all, + // that wouldn't be too hard to organise. perhaps that behaviour would be on a switch for the endpoint, or perhaps the ConnectorSelector + // interface returns a List, with the default implementation just returning everything. i think i like that. + // + // let's say for now that there is just one endpoint, cos we need to get this going before we can consider a list of them anyway. + // the major difference between that case and the case of a new endpoint is actually quite small - it's a question of where the + // Connection comes from. for a new one, we create a new one. obviously. otherwise we just get the jpc from the Endpoint + // (remember we're only assuming one connection right now). so all of the UI stuff we do to create the floating endpoint etc + // will still be valid, but when we stop dragging, we'll have to do something different. if we stop with a valid drop i think it will + // be the same process. but if we stop with an invalid drop we have to reset the Connection to how it was when we got it. + var start = function() { + + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + var nE = _getElementObject(n); + _appendElement(n); // + // create and assign an id, and initialize the offset. + //TODO can't this be replaced with a _getId call? + var id = "" + new String(new Date().getTime()); + _setAttribute(nE, "id", id); + _updateOffset(id); + // store the id of the dragging div and the source element. the drop function + // will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor({reference:self.anchor, referenceCanvas:self.canvas}); + floatingEndpoint = new Endpoint({ style:{fillStyle:'rgba(0,0,0,0)'}, endpoint:_endpoint, anchor:floatingAnchor, source:nE }); + + jpc = connectorSelector(); + if (jpc == null) { + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = new Connection({ + sourceEndpoint:self, + targetEndpoint:floatingEndpoint, + source:_getElementObject(_element), + target:_getElementObject(n), + anchors:[self.anchor, floatingAnchor], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + connector: params.connector + }); + } else { + existingJpc = true; + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; + // probably we should remove the connection? and add it back if the user + // does not drop it somewhere proper. + self.removeConnection(jpc); + if (anchorIdx == 0){ + existingJpcParams = [jpc.source, jpc.sourceId]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + }else { + existingJpcParams = [jpc.target, jpc.targetId]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // todo unregister on stop + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + }; + + var dragOptions = params.dragOptions || { }; + + var defaultOpts = jsPlumb.extend({}, jsPlumb.CurrentLibrary.defaultDragOptions); + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + var startEvent = jsPlumb.CurrentLibrary.dragEvents['start']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements([n, floatingEndpoint.canvas]); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + if (_reattach) { + jpc.floatingAnchorIndex = null; + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } + else { + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeElement(jpc.canvas); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + } else { + _removeElement(jpc.canvas); + self.removeConnection(jpc); + } + } + jpc = null; + delete floatingEndpoint; + delete inPlaceCopy; + self.paint(); + } + ); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + }; + + // connector target + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend({}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + dropOptions[dropEvent]= _wrap(dropOptions[dropEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable), "dragId"); + var elId = _getAttribute(_getElementObject(draggable),"elId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // remove this jpc from the current endpoint + jpc.endpoints[idx].removeConnection(jpc); + if (jpc.suspendedEndpoint) + jpc.suspendedEndpoint.removeConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + _addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + jsPlumb.repaint(elId); + + delete floatingConnections[id]; + }); + + dropOptions[overEvent]= _wrap(dropOptions[overEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + }); + + jsPlumb.CurrentLibrary.initDroppable(_getElementObject(self.canvas), dropOptions); + } + + return self; + }; + + /* + Property: Defaults + These are the default settings for jsPlumb, that is what will be used if you do not supply specific pieces of information + to the various API calls. A convenient way to implement your own look and feel can be to override these defaults by including a script + somewhere after the jsPlumb include, but before you make any calls to jsPlumb, for instance in this example we set the PaintStyle to be + a blue line of 27 pixels: + > jsPlumb.Defaults.PaintStyle = { lineWidth:27, strokeStyle:'blue' } + */ + this.Defaults = { + Anchor : null, + Anchors : [ null, null ], + Connector : null, + Container : null, + DragOptions: { }, + DropOptions: { }, + Endpoint : null, + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : null }, + EndpointStyles : [ null, null ], + MaxConnections : null, + PaintStyle : { lineWidth : 10, strokeStyle : 'red' }, + Scope : "_jsPlumb_DefaultScope" + }; + + /* + Property: connectorClass + The CSS class to set on Connection canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = '_jsPlumb_connector'; + + /* + Property: endpointClass + The CSS class to set on Endpoint canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = '_jsPlumb_endpoint'; + + /* + Property: Anchors + Default jsPlumb Anchors. These are supplied in the file jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form .jsPlumb-all-x.x.x.js. You can provide your own Anchors by supplying them in a script that is loaded after jsPlumb, for instance: + > jsPlumb.Anchors.MyAnchor = { ....anchor code here. see the documentation. } + */ + this.Anchors = {}; + + /* + Property: Connectors + Default jsPlumb Connectors. These are supplied in the file jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form .jsPlumb-all-x.x.x.js. You can provide your own Connectors by supplying them in a script that is loaded after jsPlumb, for instance: + > jsPlumb.Connectors.MyConnector = { ....connector code here. see the documentation. } + */ + this.Connectors = {}; + + /* + Property: Endpoints + Default jsPlumb Endpoints. These are supplied in the file jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form .jsPlumb-all-x.x.x.js. You can provide your own Endpoints by supplying them in a script that is loaded after jsPlumb, for instance: + > jsPlumb.Endpoints.MyEndpoint = { ....endpoint code here. see the documentation. } + */ + this.Endpoints = {}; + + /* + Function: addEndpoint + Adds an Endpoint to a given element. + Parameters: + target - Element to add the endpoint to. either an element id, or a selector representing some element. + params - Object containing Endpoint options (more info required) + Returns: + The newly created Endpoint. + See Also: + + */ + this.addEndpoint = function(target, params) { + params = jsPlumb.extend({}, params); + var el = _getElementObject(target); + var id = _getAttribute(el,"id"); + params.source = el; + _updateOffset(id); + var e = new Endpoint(params); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id]; + var myWH = sizes[id]; + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH, e); + e.paint(anchorLoc); + return e; + }; + + /* + Function: addEndpoint + Adds a list of Endpoints to a given element. + Parameters: + target - element to add the endpoint to. either an element id, or a selector representing some element. + endpoints - List of objects containing Endpoint options. one Endpoint is created for each entry in this list. + Returns: + List of newly created Endpoints, one for each entry in the 'endpoints' argument. + See Also: + + */ + this.addEndpoints = function(target, endpoints) { + var results = []; + for (var i = 0; i < endpoints.length; i++) { + results.push(jsPlumb.addEndpoint(target, endpoints[i])); + } + return results; + }; + + /* + Function: animate + Wrapper around supporting library's animate function; injects a call to jsPlumb in the 'step' function (creating it if necessary). + This only supports the two-arg version of the animate call in jQuery - the one that takes an 'options' object as the second arg. + MooTools has only one method - a two arg one. Which is handy. + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the standard jQuery animate call. + options - The 'options' argument you want passed to the standard jQuery animate call. + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el); + var id = _getAttribute(el,"id"); + //TODO this is not agnostic yet. + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() + { + _currentInstance.repaint(id); + }); + + // you know, probably onComplete should repaint too. that will help keep up + // with fast animations. + options[completeFunction] = _wrap(options[completeFunction], function() + { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /** + * clears the cache used to lookup elements by their id. if you remove any elements + * from the DOM you should call this to ensure that if you add them back in jsPlumb does not + * have a stale handle. + */ + this.clearCache = function() { + delete cached; + cached = {}; + }; + + /* + Function: connect + Establishes a connection between two elements. + Parameters: + params - Object containing setup for the connection. see documentation. + Returns: + The newly created Connection. + */ + this.connect = function(params) { + if (params.sourceEndpoint && params.sourceEndpoint.isFull()) { + _log("could not add connection; source endpoint is full"); + return; + } + + if (params.targetEndpoint && params.targetEndpoint.isFull()) { + _log("could not add connection; target endpoint is full"); + return; + } + + var jpc = new Connection(params); + // add to list of connections (by scope). + _addToList(connectionsByScope, jpc.scope, jpc); + + // force a paint + _draw(jpc.source); + + return jpc; + }; + + /** + * not implemented yet. params object will have sourceEndpoint and targetEndpoint members; these will be Endpoints. + connectEndpoints : function(params) { + var jpc = Connection(params); + + },*/ + + /* + Function: detach + Removes a connection. + Parameters: + sourceId - Id of the first element in the connection. A String. + targetId - iI of the second element in the connection. A String. + Returns: + true if successful, false if not. + */ + this.detach = function(sourceId, targetId) { + var f = function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + return true; + } + + }; + + // todo: how to cleanup the actual storage? a third arg to _operation? + _operation(sourceId, f); + }; + + /* + Function: detachAll + Removes all an element's connections. + Parameters: + el - either the id of the element, or a selector for the element. + Returns: + void + */ + this.detachAll = function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + var c = endpoints[i].connections.length; + if (c > 0) { + for (var j = 0; j < c; j++) { + var jpc = endpoints[i].connections[0]; + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + } + } + } + }; + + /* + Function: detachEverything + Remove all Connections from all elements, but leaves Endpoints in place. + Returns: + void + See Also: + + */ + this.detachEverything = function() { + for(var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + var c = endpoints[i].connections.length; + if (c > 0) { + for (var j = 0; j < c; j++) { + var jpc = endpoints[i].connections[0]; + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + } + } + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + /* + Function: draggable + initialises the draggability of some element or elements. + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for (var i = 0; i < el.length; i++) + { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) + _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + Parameters: + o1 - object to extend + o2 - object to extend o1 with + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + Function: getConnections + Gets all or a subset of connections currently managed by this jsPlumb instance. + Parameters: + options - a JS object that holds options defining what sort of connections you're looking for. Valid values are: + scope this may be a String or a list of Strings. jsPlumb will only return Connections whose scope matches what this option + defines. If you omit it, you will be given Connections of every scope. + source may be a string or a list of strings; constraints results to have only Connections whose source is this object. + target may be a string or a list of strings; constraints results to have only Connections whose target is this object. + + The return value is a dictionary in this format: + + { + 'scope1': [ + {sourceId:'window1', targetId:'window2'}, + {sourceId:'window3', targetId:'window4'}, + {sourceId:'window1', targetId:'window3'} + ], + 'scope2': [ + {sourceId:'window1', targetId:'window3'} + ] + } + + */ + this.getConnections = function(options) { + var r = {}; + options = options || {}; + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') r.push(input); + else r = input; + } + return r; + }; + var scopes = prepareList(options.scope); + var sources = prepareList(options.source); + var targets = prepareList(options.target); + var filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }; + for (var i in connectionsByScope) { + if (filter(scopes, i)) { + r[i] = []; + for (var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + r[i].push({sourceId:c.sourceId, targetId:c.targetId}); + } + } + } + return r; + }; + + /* + Function: getDefaultScope + Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a scope to an endpoint + or connection allows you to support different types of connections in the same UI. but if you're only interested in one type of connection, + you don't need to supply a scope. this method will probably be used by very few people; it's good for testing though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: hide + Sets an element's connections to be hidden. + Parameters: + el - either the id of the element, or a selector for the element. + Returns: + void + */ + this.hide = function(el) { + _setVisible(el, "none"); + }; + + /* + Function: makeAnchor + Creates an anchor with the given params. + Parameters: + x - the x location of the anchor as a fraction of the total width. + y - the y location of the anchor as a fraction of the total height. + xOrientation - value indicating the general direction a connection from the anchor should go in, in the x direction. + yOrientation - value indicating the general direction a connection from the anchor should go in, in the y direction. + xOffset - a fixed offset that should be applied in the x direction that should be applied after the x position has been figured out. optional. defaults to 0. + yOffset - a fixed offset that should be applied in the y direction that should be applied after the y position has been figured out. optional. defaults to 0. + Returns: + The newly created Anchor. + */ + this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards compatibility if we are given only one value we assume it's a call in the old form. + var params = {}; + if (arguments.length == 1) jsPlumb.extend(params, x); + else { + params = {x:x, y:y}; + if (arguments.length >= 4) { + params.orientation = [arguments[2], arguments[3]]; + } + if (arguments.length == 6) params.offsets = [arguments[4], arguments[5]]; + } + var a = new Anchor(params); + a.clone = function() { return new Anchor(params); }; + return a; + }; + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + Parameters: + el - either the id of the element or a selector representing the element. + Returns: + void + See Also: + + */ + this.repaint = function(el) { + + var _processElement = function(el) { + var ele = _getElementObject(el); + _draw(ele); + }; + + // TODO: support a jQuery result object too! + + // support both lists... + if (typeof el =='object') { + for (var i = 0; i < el.length; i++) + _processElement(el[i]); + } // ...and single strings. + else _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + Returns: + void + See Also: + + */ + this.repaintEverything = function() { + for (var elId in endpointsByElement) { + _draw(_getElementObject(elId)); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + Parameters: + el - either an element id, or a selector for an element. + Returns: + void + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"); + // first remove all Connections. + jsPlumb.detachAll(elId); + var ebe = endpointsByElement[elId]; + for (var i in ebe) { + _removeElement(ebe[i].canvas); + } + endpointsByElement[elId] = []; + }; + + /* + Function: removeEveryEndpoint + Removes every Endpoint in this instance of jsPlumb. + Returns: + void + See Also: + + */ + this.removeEveryEndpoint = function() { + for(var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + _removeElement(endpoints[i].canvas, endpoints[i].container); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + }; + + /* + Function: removeEndpoint + Removes the given Endpoint from the given element. + Parameters: + el - either an element id, or a selector for an element. + endpoint - Endpoint to remove. this is an Endpoint object, such as would have been returned from a call to addEndpoint. + Returns: + void + See Also: + + */ + this.removeEndpoint = function(el, endpoint) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + if (ebe) { + if(_removeFromList(endpointsByElement, elId, endpoint)) + _removeElement(endpoint.canvas); + } + }; + + /* + Function:reset + removes all endpoints and connections and clears the element cache. + */ + this.reset = function() { + this.detachEverything(); + this.removeEveryEndpoint(); + this.clearCache(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + Parameters: + value - whether or not to automatically repaint when the window is resized. + Returns: + void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + Function: setDefaultNewCanvasSize + Sets the default size jsPlumb will use for a new canvas (we create a square canvas so one value is all that is required). + This is a hack for IE, because ExplorerCanvas seems to need for a canvas to be larger than what you are going to draw on + it at initialisation time. The default value of this is 1200 pixels, which is quite large, but if for some reason you're + drawing connectors that are bigger, you should adjust this value appropriately. + Parameters: + size - The default size to use. jsPlumb will use a square canvas so you need only supply one value. + Returns: + void + */ + this.setDefaultNewCanvasSize = function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }; + + /* + Function: setDefaultScope + Sets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a scope to an endpoint + or connection allows you to support different types of connections in the same UI. but if you're only interested in one type of connection, + you don't need to supply a scope. this method will probably be used by very few people; it just instructs jsPlumb to use a different key + for the default scope. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + Function: setDraggable + Sets whether or not a given element is draggable, regardless of what any plumb command may request. + Parameters: + el - either the id for the element, or a selector representing the element. + Returns: + void + */ + this.setDraggable = _setDraggable; + + /* + Function: setDraggableByDefault + Sets whether or not elements are draggable by default. Default for this is true. + Parameters: + draggable - value to set + Returns: + void + */ + this.setDraggableByDefault = function(draggable) { + _draggableByDefault = draggable; + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + Function: setRepaintFunction + Sets the function to fire when the window size has changed and a repaint was fired. + Parameters: + f - Function to execute. + Returns: + void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + Function: show + Sets an element's connections to be visible. + Parameters: + el - either the id of the element, or a selector for the element. + Returns: + void + */ + this.show = function(el) { + _setVisible(el, "block"); + }; + + /* + Function: sizeCanvas + Helper to size a canvas. You would typically use this when writing your own Connector or Endpoint implementation. + Parameters: + x - [int] x position for the Canvas origin + y - [int] y position for the Canvas origin + w - [int] width of the canvas + h - [int] height of the canvas + Returns: + void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; canvas.height = h; + canvas.style.width = w + "px"; canvas.width = w; + canvas.style.left = x + "px"; canvas.style.top = y + "px"; + // _currentInstance.CurrentLibrary.setPosition(canvas, x, y); + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards compatibility + */ + this.toggle = _toggleVisible; + + /* + Function: toggleVisible + Toggles visibility of an element's connections. + Parameters: + el - either the element's id, or a selector representing the element. + Returns: + void, but should be updated to return the current state + */ + //TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + Function: toggleDraggable + Toggles draggability (sic) of an element's connections. + Parameters: + el - either the element's id, or a selector representing the element. + Returns: + The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + Function: unload + Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element + Returns: + void + */ + this.unload = function() { + delete endpointsByElement; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + }; + + /* + Function: wrap + Helper method to wrap an existing function with one of your own. This is used by the various + implementations to wrap event callbacks for drag/drop etc; it allows jsPlumb to be + transparent in its handling of these things. If a user supplies their own event callback, + for anything, it will always be called. + Parameters: + */ + this.wrap = _wrap; + this.trace = _trace; + this.clearTrace = _clearTrace; + this.clearAllTraces = _clearAllTraces; + this.getTrace = _getTrace; + + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(); + if (_defaults) + jsPlumb.extend(j.Defaults, _defaults); + return j; + }; + +})(); diff --git a/archive/1.2.1/jsPlumb-defaults-1.2.1-RC1.js b/archive/1.2.1/jsPlumb-defaults-1.2.1-RC1.js new file mode 100644 index 000000000..179bfab03 --- /dev/null +++ b/archive/1.2.1/jsPlumb-defaults-1.2.1-RC1.js @@ -0,0 +1,406 @@ +/** +* jsPlumb-defaults-1.2-RC1 +* +* This script contains the default Anchors, Endpoints and Connectors for jsPlumb. It should be used with jsPlumb 1.1.0 and above; +* prior to version 1.1.0 of jsPlumb the defaults were included inside the main script. +* +* Version 1.1.1 of this script adds the Triangle Endpoint, written by __________ and featured on this demo: +* +* http://www.mintdesign.ru/blog/javascript-jsplumb-making-graph +* http://www.mintdesign.ru/site/samples/jsplumb/jsPlumb-graph-sample.htm +* +* NOTE: for production usage you should use jsPlumb-all-1.1.1-min.js, which contains the main jsPlumb script and this script together, +* in a minified file. +*/ + +(function() { + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * getOrientation : function() { return [ox, oy]; } + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + jsPlumb.Anchors.TopCenter = jsPlumb.makeAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors.BottomCenter = jsPlumb.makeAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors.LeftMiddle = jsPlumb.makeAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors.RightMiddle = jsPlumb.makeAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors.Center = jsPlumb.makeAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors.TopRight = jsPlumb.makeAnchor(1, 0, 0,-1); + jsPlumb.Anchors.BottomRight = jsPlumb.makeAnchor(1, 1, 0, 1); + jsPlumb.Anchors.TopLeft = jsPlumb.makeAnchor(0, 0, 0, -1); + jsPlumb.Anchors.BottomLeft = jsPlumb.makeAnchor(0, 1, 0, 1); + + + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (w < 2 * lineWidth) { + w = 2 * lineWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - lineWidth; + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < 2 * lineWidth) { + // minimum size is 2 * line Width + h = 2 * lineWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - lineWidth; + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + var sx = sourcePos[0] < targetPos[0] ? w-xo : xo; + var sy = sourcePos[1] < targetPos[1] ? h-yo : yo; + var tx = sourcePos[0] < targetPos[0] ? xo : w-xo; + var ty = sourcePos[1] < targetPos[1] ? yo : h-yo; + var retVal = [ x, y, w, h, sx, sy, tx, ty ]; + + return retVal; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + }; + + /** + * This Connector draws a Bezier curve with two control points. + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + jsPlumb.Connectors.Bezier = function(curviness) { + + var self = this; + this.majorAnchor = curviness || 150; + this.minorAnchor = 10; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) + { + lineWidth = lineWidth || 0; + var w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth, h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + var canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2), canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + var sx = sourcePos[0] < targetPos[0] ? w - (lineWidth/2): (lineWidth/2), sy = sourcePos[1] < targetPos[1] ? h-(lineWidth/2) : (lineWidth/2); + var tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : w-(lineWidth/2), ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : h-(lineWidth/2); + var CP = self._findControlPoint([sx,sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + var CP2 = self._findControlPoint([tx,ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(sx,tx); var minx2 = Math.min(CP[0], CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(sx,tx); var maxx2 = Math.max(CP[0], CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > w) w = maxx; + if (minx < 0) { + canvasX += minx; var ox = Math.abs(minx); + w += ox; CP[0] += ox; sx += ox; tx +=ox; CP2[0] += ox; + } + + var miny1 = Math.min(sy,ty); var miny2 = Math.min(CP[1], CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(sy,ty); var maxy2 = Math.max(CP[1], CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > h) h = maxy; + if (miny < 0) { + canvasY += miny; var oy = Math.abs(miny); + h += oy; CP[1] += oy; sy += oy; ty +=oy; CP2[1] += oy; + } + + // return [ canvasx, canvasy, canvasWidth, canvasHeight, + // sourceX, sourceY, targetX, targetY, + // controlPoint1_X, controlPoint1_Y, controlPoint2_X, controlPoint2_Y + return [canvasX, canvasY, w, h, sx, sy, tx, ty, CP[0], CP[1], CP2[0], CP2[1] ]; + }; + + this.paint = function(d, ctx) { + + /*var img = new Image(); + img.src = "../img/pattern.jpg"; + ctx.fillStyle = ctx.createPattern(img, 'repeat-y');*/ + + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + //ctx.fill(); + } + }; + + + /** + * Types of endpoint UIs. we supply four - a circle of default radius 10px, a rectangle of + * default size 20x20, an image (with no default), and a Triangle, of default size 15. + * you can supply others of these if you want to - see the documentation for a howto. + */ + + /** + * a round endpoint, with default radius 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + } + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * A Rectangular endpoint, with default size 20x20. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + //todo: the fillStyle needs some thought. we want to support a few options: + // 1. nothing supplied; use the stroke color or the default if no stroke color. + // 2. a fill color supplied - use it + // 3. a gradient supplied - use it + // 4. setting the endpoint to the same color as the bg of the element it is attached to. + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + jsPlumb.Endpoints.Triangle = function(params) { + + params = params || { width:15, height:15 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - width/2; + var y = anchorPoint[1] - height/2; + + jsPlumb.sizeCanvas(canvas, x, y, width, height); + + var ctx = canvas.getContext('2d'); + var + offsetX = 0, + offsetY = 0, + angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = endpointStyle.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + ctx.fill(); + + }; + }; + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' property in the params object.. + */ + jsPlumb.Endpoints.Image = function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + }; +})(); \ No newline at end of file diff --git a/archive/1.2.1/mootools-jsPlumb-1.2.1-all-min.js b/archive/1.2.1/mootools-jsPlumb-1.2.1-all-min.js new file mode 100644 index 000000000..2c962338b --- /dev/null +++ b/archive/1.2.1/mootools-jsPlumb-1.2.1-all-min.js @@ -0,0 +1,49 @@ +(function(){var u=function(){var g=this,i=/MSIE/.test(navigator.userAgent)&&!window.opera,q=null,m=function(){d.repaintEverything()},j=true,k={},f={},h=[],n={},w={},H=true,C=[],E="DEFAULT",J=1200,D={},ba=function(a,b){var c=D[a];if(!c){c={};D[a]=c}b=b||"DEFAULT";var e=c[b];if(!e){e=0;c[b]=e}c[b]++},Q=function(a,b,c){var e=function(o,r){if(o===r)return true;else if(typeof o=="object"&&typeof r=="object"){var t=true;for(var v in o)if(!e(o[v],r[v])){t=false;break}for(v in r)if(!e(r[v],o[v])){t=false; +break}return t}};c=+c||0;for(var l=a.length;c=0){delete a[c];a.splice(c,1);return true}}return false},ia=function(a,b){var c=F(a,"id");da(c,function(e){e.canvas.style.display=b})},la=function(a){da(a,function(b){b.canvas.style.display="none"==b.canvas.style.display?"block":"none"})},N=function(a,b,c){if(c||b==null){b=x(a);C[a]=ga(b);h[a]=fa(b)}else h[a]=b},K=function(a,b){a=a||function(){};b=b||function(){};return function(){try{b.apply(this,arguments)}catch(c){}try{a.apply(this,arguments)}catch(e){}}}, +ja=function(a){var b=this;this.x=a.x||0;this.y=a.y||0;var c=a.orientation||[0,0],e=null;this.offsets=a.offsets||[0,0];this.compute=function(l,o,r){ba("anchor compute");e=[l[0]+b.x*o[0]+b.offsets[0],l[1]+b.y*o[1]+b.offsets[1]];o=r?r.container:null;l={left:0,top:0};if(o!=null){var t=x(o);o=fa(t);r=d.CurrentLibrary.getScrollLeft(t);t=d.CurrentLibrary.getScrollTop(t);l.left=o.left-r;l.top=o.top-t;e[0]-=l.left;e[1]-=l.top}return e};this.getOrientation=function(){return c};this.equals=function(l){if(!l)return false; +var o=l.getOrientation(),r=this.getOrientation();return this.x==l.x&&this.y==l.y&&this.offsets[0]==l.offsets[0]&&this.offsets[1]==l.offsets[1]&&r[0]==o[0]&&r[1]==o[1]}},ma=function(a){var b=a.reference,c=ga(x(a.referenceCanvas)),e=0,l=0,o=null;this.compute=function(r){l=e=0;return[r[0]+c[0]/2,r[1]+c[1]/2]};this.getOrientation=function(){if(o)return o;else{var r=b.getOrientation();return[Math.abs(r[0])*e*-1,Math.abs(r[1])*l*-1]}};this.over=function(r){o=r.getOrientation()};this.out=function(){o=null}}, +ka=function(a){var b=this,c=new String("_jsplumb_c_"+(new Date).getTime());this.getId=function(){return c};this.container=a.container||g.Defaults.Container;this.source=x(a.source);this.target=x(a.target);this.sourceId=F(this.source,"id");this.targetId=F(this.target,"id");this.endpointsOnTop=a.endpointsOnTop!=null?a.endpointsOnTop:true;this.scope=a.scope;this.endpoints=[];this.endpointStyles=[];var e=function(r,t,v,z){if(r){b.endpoints[t]=r;r.addConnection(b)}else{if(!v.endpoints)v.endpoints=[null, +null];r=v.endpoints[t]||v.endpoint||g.Defaults.Endpoints[t]||d.Defaults.Endpoints[t]||g.Defaults.Endpoint||d.Defaults.Endpoint||new d.Endpoints.Dot;if(!v.endpointStyles)v.endpointStyles=[null,null];v=new Z({style:v.endpointStyles[t]||v.endpointStyle||g.Defaults.EndpointStyles[t]||d.Defaults.EndpointStyles[t]||g.Defaults.EndpointStyle||d.Defaults.EndpointStyle,endpoint:r,connections:[b],anchor:v.anchors?v.anchors[t]:g.Defaults.Anchors[t]||d.Defaults.Anchors[t]||g.Defaults.Anchor||d.Defaults.Anchor|| +d.Anchors.BottomCenter,source:z,container:b.container});return b.endpoints[t]=v}},l=e(a.sourceEndpoint,0,a,b.source);l&&R(k,this.sourceId,l);(e=e(a.targetEndpoint,1,a,b.target))&&R(k,this.targetId,e);if(!this.scope)this.scope=this.endpoints[0].scope;this.connector=this.endpoints[0].connector||this.endpoints[1].connector||a.connector||g.Defaults.Connector||d.Defaults.Connector||new d.Connectors.Bezier;this.paintStyle=this.endpoints[0].connectorStyle||this.endpoints[1].connectorStyle||a.paintStyle|| +g.Defaults.PaintStyle||d.Defaults.PaintStyle;N(this.sourceId);N(this.targetId);e=h[this.sourceId];l=C[this.sourceId];e=this.endpoints[0].anchor.compute([e.left,e.top],l,this.endpoints[0]);this.endpoints[0].paint(e);e=h[this.targetId];l=C[this.targetId];e=this.endpoints[1].anchor.compute([e.left,e.top],l,this.endpoints[1]);this.endpoints[1].paint(e);var o=ha(d.connectorClass,b.container);this.canvas=o;this.paint=function(r,t,v){q&&q.debug("Painting Connection; element in motion is "+r+"; ui is ["+ +t+"]; recalc is ["+v+"]");var z=r!=this.sourceId,I=z?this.sourceId:this.targetId,y=z?0:1,s=z?1:0;if(this.canvas.getContext){N(r,t,v);N(I);v=h[r];t=h[I];r=C[r];var aa=C[I];I=o.getContext("2d");v=this.endpoints[s].anchor.compute([v.left,v.top],r,this.endpoints[s]);this.endpoints[s].anchor.getOrientation();t=this.endpoints[y].anchor.compute([t.left,t.top],aa,this.endpoints[y]);this.endpoints[y].anchor.getOrientation();y=this.connector.compute(v,t,this.endpoints[s].anchor,this.endpoints[y].anchor,this.paintStyle.lineWidth); +d.sizeCanvas(o,y[0],y[1],y[2],y[3]);d.extend(I,this.paintStyle);if(this.paintStyle.gradient&&!i){z=z?I.createLinearGradient(y[4],y[5],y[6],y[7]):I.createLinearGradient(y[6],y[7],y[4],y[5]);for(s=0;s=0&&b.connections.splice(p,1)};this.makeInPlaceCopy=function(){return new Z({anchor:b.anchor,source:o,style:l,endpoint:e})};this.isConnectedTo=function(p){var G=false;if(p)for(var A=0;A=t};this.equals=function(p){return this.anchor.equals(p.anchor)&&true};this.paint=function(p,G,A){q&&q.debug("Painting Endpoint with elementId ["+r+"]; anchorPoint is ["+p+"]");if(p==null){p=h[r];var O=C[r];if(p==null||O==null){N(r);p=h[r];O=C[r]}p=b.anchor.compute([p.left,p.top],O,b)}e.paint(p,b.anchor.getOrientation(),A||b.canvas,l,G||l)};if(a.isSource&&d.CurrentLibrary.isDragSupported(o)){var y= +null;var s=c=null,aa=false,P=null,B=a.dragOptions||{},M=d.extend({},d.CurrentLibrary.defaultDragOptions);B=d.extend(M,B);B.scope=B.scope||b.scope;M=d.CurrentLibrary.dragEvents.start;var V=d.CurrentLibrary.dragEvents.stop,W=d.CurrentLibrary.dragEvents.drag;B[M]=K(B[M],function(){I=b.makeInPlaceCopy();I.paint();y=document.createElement("div");var p=x(y);document.body.appendChild(y);var G=""+new String((new Date).getTime());Y(p,"id",G);N(G);Y(x(b.canvas),"dragId",G);Y(x(b.canvas),"elId",r);var A=new ma({reference:b.anchor, +referenceCanvas:b.canvas});z=new Z({style:{fillStyle:"rgba(0,0,0,0)"},endpoint:e,anchor:A,source:p});s=b.connections.length==0||b.connections.length0)for(var e=0;e0)for(var l=0;l0?Q(e,o)!=-1:true){b[o]=[];for(c=0;c0?Q(l,r.sourceId)!=-1:true)&&(a.length>0?Q(a,r.targetId)!=-1:true)&&b[o].push({sourceId:r.sourceId,targetId:r.targetId})}}return b};this.getDefaultScope=function(){return E};this.hide=function(a){ia(a,"none")};this.makeAnchor=function(a,b){var c= +{};if(arguments.length==1)d.extend(c,a);else{c={x:a,y:b};if(arguments.length>=4)c.orientation=[arguments[2],arguments[3]];if(arguments.length==6)c.offsets=[arguments[4],arguments[5]]}var e=new ja(c);e.clone=function(){return new ja(c)};return e};this.repaint=function(a){var b=function(e){e=x(e);S(e)};if(typeof a=="object")for(var c=0;ck)k=q;if(i<0){h+=i;i=Math.abs(i);k+=i;E[0]+=i;w+=i;C+=i;g[0]+=i}i=Math.min(Math.min(H,j),Math.min(E[1], +g[1]));q=Math.max(Math.max(H,j),Math.max(E[1],g[1]));if(q>f)f=q;if(i<0){n+=i;i=Math.abs(i);f+=i;E[1]+=i;H+=i;j+=i;g[1]+=i}return[h,n,k,f,w,H,C,j,E[0],E[1],g[0],g[1]]};this.paint=function(g,i){i.beginPath();i.moveTo(g[4],g[5]);i.bezierCurveTo(g[8],g[9],g[10],g[11],g[6],g[7]);i.stroke()}};jsPlumb.Endpoints.Dot=function(u){u=u||{radius:10};var d=this;this.radius=u.radius;var g=0.5*this.radius,i=this.radius/3,q=function(m){try{return parseInt(m)}catch(j){if(m.substring(m.length-1)=="%")return parseInt(m.substring(0, +m-1))}};this.paint=function(m,j,k,f,h){var n=f.radius||d.radius;jsPlumb.sizeCanvas(k,m[0]-n,m[1]-n,n*2,n*2);m=k.getContext("2d");k=jsPlumb.extend({},f);if(k.fillStyle==null)k.fillStyle=h.strokeStyle;jsPlumb.extend(m,k);h=/MSIE/.test(navigator.userAgent)&&!window.opera;if(f.gradient&&!h){h=f.gradient;k=g;var w=i;if(h.offset)k=q(h.offset);if(h.innerRadius)w=q(h.innerRadius);h=[k,w];j=m.createRadialGradient(n,n,n,n+(j[0]==1?h[0]*-1:h[0]),n+(j[1]==1?h[0]*-1:h[0]),h[1]);for(h=0;h endpoint lists. an element can have an arbitrary number of endpoints on it, + * and not all of them have to be connected to anything. + */ + var endpointsByElement = {}; + var connectionsByScope = {}; + var offsets = []; + var floatingConnections = {}; + var draggableStates = {}; + var _draggableByDefault = true; + var sizes = []; + var DEFAULT_SCOPE = 'DEFAULT'; + + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + var traced = {}; + var _trace = function(category, event) { + var e = traced[category]; + if (!e) { + e = {}; + traced[category] = e; + } + event = event || 'DEFAULT'; + var ee = e[event]; + if (!ee) { + ee = 0; + e[event] = ee; + } + e[event]++; + }; + + var _clearAllTraces = function() { + delete traced; + traced = {}; + }; + + var _clearTrace = function(category, event) { + var c = traced[category]; + if (!c) return; + if (event) { + c[event] = 0; + } + else c['DEFAULT'] = 0; + }; + + var _getTrace = function(category) { + return traced[category] || {'DEFAULT' : 0 }; + }; + + + var _findIndex = function( a, v, b, s ) { + var _eq = function(o1, o2) { + if (o1 === o2) return true; + else if (typeof o1 == 'object' && typeof o2 == 'object') { + var same = true; + for(var propertyName in o1) { + if(!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for(var propertyName in o2) { + if(!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for( var i = +b || 0, l = a.length; i < l; i++ ) { + if( _eq(a[i], v)) return i; + } + return -1; + }; + + /** + * helper method to add an item to a list, creating the list if it does not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }; + + /** + * Handles the dragging of an element. + * @param element jQuery element + * @param ui UI object from current library's event system + */ + var _draw = function(element, ui) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints) { + var timestamp = '' + (new Date().getTime()); + _trace('draw'); + _updateOffset(id, ui); + var myOffset = offsets[id]; + var myWH = sizes[id]; + // loop through endpoints for this element + for (var i = 0; i < endpoints.length; i++) { + var e = endpoints[i]; + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH, e); + e.paint(anchorLoc); + var l = e.connections; + for (var j = 0; j < l.length; j++) + l[j].paint(id, ui, false, timestamp); // ...and paint them. + } + } + }; + + /** + * helper function: the second argument is a function taking two args - the first is an id + * ui element from the current library (or list of them), and the second is the function + * to run. + */ + var _elementProxy = function(element, fn) { + var retVal = null; + // TODO this might be jquery specific. in fact it probably is. + if (typeof element == 'object' && element.length) { + retVal = []; + for (var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]); + var id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } + else { + var el = _getElementObject(element); + var id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + + return retVal; + }; + + var _log = function(msg) { + //if (console) console.log(msg); + }; + + var _logFnCall = function(fn, args) { + /*if (console) { + var c = args.callee.caller.toString(); + var i = c.indexOf("{"); + var msg = fn + ' : ' + c.substring(0, i); + console.log(msg); + }*/ + }; + + var cached = {}; + var __getElementObject = function(el) { + + if (typeof(el)=='string') { + var e = cached[el]; + if (!e) { + e = jsPlumb.CurrentLibrary.getElementObject(el); + cached[el] = e; + } + return e; + } + else { + return jsPlumb.CurrentLibrary.getElementObject(el); + } + }; + + + /** + * gets the named attribute from the given element (id or element object) + */ + var _getAttribute = function(el, attName) { + var ele = __getElementObject(el); + return jsPlumb.CurrentLibrary.getAttribute(ele, attName); + }; + + var _setAttribute = function(el, attName, attValue) { + var ele = __getElementObject(el); + jsPlumb.CurrentLibrary.setAttribute(ele, attName, attValue); + }; + + var _addClass = function(el, clazz) { + var ele = __getElementObject(el); + jsPlumb.CurrentLibrary.addClass(ele, clazz); + }; + + var _getElementObject = function(elId) { + return __getElementObject(elId); + }; + + var _getOffset = function(el) { + var ele = __getElementObject(el); + return jsPlumb.CurrentLibrary.getOffset(ele); + }; + + var _getSize = function(el) { + var ele = __getElementObject(el); + return jsPlumb.CurrentLibrary.getSize(ele); + }; + + var _appendElement = function(canvas, parent) { + if (!parent) document.body.appendChild(canvas); + else jsPlumb.CurrentLibrary.appendElement(canvas, parent); + }; + + /** + * gets an id for the given element, creating and setting one if necessary. + */ + var _getId = function(element) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id) { + id = "_jsPlumb_" + new String((new Date()).getTime()); + _setAttribute(ele, "id", id); + } + return id; + }; + + /** + * inits a draggable if it's not already initialised. + * + * TODO: we need to hook in each library to this method. they need to be given the opportunity to + * wrap/insert lifecycle functions, because each library does different things. for instance, jQuery + * supports the notion of 'revert', which will clean up a dragged endpoint; MooTools does not. jQuery + * also supports 'opacity' and MooTools does not; jQuery supports z-index of the draggable; MooTools + * does not. i could go on. the point is...oh. initDraggable can do this. ok. + */ + var _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + // dragging + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend({}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + } + } + }; + + + /** + * helper to create a canvas. + * @param clazz optional class name for the canvas. + */ + var _newCanvas = function(clazz, parent) { + var canvas = document.createElement("canvas"); + _appendElement(canvas, parent); + canvas.style.position="absolute"; + if (clazz) { canvas.className=clazz; } + _getId(canvas); // set an id. + + if (ie) { + // for IE we have to set a big canvas size. actually you can override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + /** + * performs the given function operation on all the connections found for the given element + * id; this means we find all the endpoints for the given element, and then for each endpoint + * find the connectors connected to it. then we pass each connection in to the given + * function. + */ + var _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + for (var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }; + /** + * perform an operation on all elements. + */ + var _operationOnAll = function(func) { + for (var elId in endpointsByElement) { + _operation(elId, func); + } + }; + /** + * helper to remove an element from the DOM. + */ + var _removeElement = function(element, parent) { + if (element != null) { + if (!parent) { + try { document.body.removeChild(element); } + catch (e) { } + } else { + jsPlumb.CurrentLibrary.removeElement(element, parent); + } + } + }; + /** + * helper to remove a list of elements from the DOM. + */ + var _removeElements = function(elements, parent) { + for (var i in elements) + _removeElement(elements[i], parent); + }; + /** + * helper method to remove an item from a list. + */ + var _removeFromList = function(map, key, value) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete( l[i] ); + l.splice( i, 1 ); + return true; + } + } + return false; + }; + /** + * Sets whether or not the given element(s) should be draggable, regardless of what a particular + * plumb command may request. + * + * @param element May be a string, a jQuery elements, or a list of strings/jquery elements. + * @param draggable Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + var _helper = function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }; + + return _elementProxy(element, _helper); + }; + /** + * private method to do the business of hiding/showing. + * @param el either Id of the element in question or a jquery object for the element. + * @param state String specifying a value for the css 'display' property ('block' or 'none'). + */ + var _setVisible = function(el, state) { + var elId = _getAttribute(el, "id"); + var f = function(jpc) { + jpc.canvas.style.display = state; + }; + _operation(elId, f); + }; + /** + * toggles the draggable state of the given element(s). + * @param el either an id, or a jquery object, or a list of ids/jquery objects. + */ + var _toggleDraggable = function(el) { + var fn = function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }; + return _elementProxy(el, fn); + }; + /** + * private method to do the business of toggling hiding/showing. + * @param elId Id of the element in question + */ + var _toggleVisible = function(elId) { + var f = function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }; + + _operation(elId, f); + + //todo this should call _elementProxy, and pass in the _operation(elId, f) call as a function. cos _toggleDraggable does that. + }; + /** + * updates the offset and size for a given element, and stores the values. + * if 'offset' is not null we use that (it would have been passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we use the offset, outerWidth and outerHeight methods to get + * the current values. + */ + var _updateOffset = function(elId, offset, recalc) { + if (recalc || offset == null) { // if forced repaint or no offset available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + } else { + offsets[elId] = offset; + } + }; + + /** + * wraps one function with another, creating a placeholder for the wrapped function + * if it was null. this is used to wrap the various drag/drop event functions - to allow + * jsPlumb to be notified of important lifecycle events without imposing itself on the user's + * drap/drop functionality. + * TODO: determine whether or not we should support an error handler concept, if one of the functions fails. + */ + var _wrap = function(wrappedFunction, newFunction) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + try { newFunction.apply(this, arguments); } + catch (e) { + _log('jsPlumb function failed : ' + e); + } + try { wrappedFunction.apply(this, arguments); } + catch (e) { + _log('wrapped function failed : ' + e); + } + }; + }; + + /* + Class: Anchor + Models a position relative to the origin of some element that an Endpoint can be located. + */ + /* + Function: Anchor + + Constructor for the Anchor class + + Parameters: + params - Anchor parameters. This should contain three values, and may optionally have an 'offsets' argument: + + - x : the x location of the anchor as a fraction of the total width. + - y : the y location of the anchor as a fraction of the total height. + - orientation : an [x,y] array indicating the general direction a connection from the anchor should go in. for more info on this, see the documentation, or the docs in jquery-jsPlumb-defaults-XXX.js for the default Anchors. + - offsets : an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. may be null. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; this.y = params.y || 0; + var orientation = params.orientation || [0,0]; + var lastTimestamp = null; + var lastReturnValue = null; + this.offsets = params.offsets || [0,0]; + //TODO: fix this properly. since Anchors are often static, this timestamping business + // does not work at all well. the timestamp should be inside the Endpoint, because they + // are _never_ static. the method that Connection uses to find an anchor location should + // be done through the Endpoint class, which can then deal with the timestamp. + // and, in fact, we should find out whether or not we even get a speed enhancement from doing + // this. + this.compute = function(xy, wh, element) { + _trace('anchor compute'); + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1]]; + var container = element? element.container : null; + var containerAdjustment = {left:0, top:0 }; + if (container != null) { + var eo = _getElementObject(container); + var o = _getOffset(eo); + var sl = jsPlumb.CurrentLibrary.getScrollLeft(eo); + var st = jsPlumb.CurrentLibrary.getScrollTop(eo); + containerAdjustment.left = o.left - sl; + containerAdjustment.top = o.top - st; + lastReturnValue[0] = lastReturnValue[0] - containerAdjustment.left; + lastReturnValue[1] = lastReturnValue[1] - containerAdjustment.top; + } + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y && this.offsets[0] == anchor.offsets[0] && this.offsets[1] == anchor.offsets[1] && o[0] == ao[0] && o[1] == ao[1]; + }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from its position relative to the anchor it is floating relative to. + * + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(_getElementObject(refCanvas)); + + // these are used to store the current relative position of our anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating anchor is hovering over another anchor. + var orientation = null; + + this.compute = function(xy, wh, txy, twh) { + // set these for the getOrientation method to use. + xDir = 0;//xy[0] < txy[0] ? -1 : xy[0] == txy[0] ? 0 : 1; + yDir = 0;//xy[1] < txy[1] ? -1 : xy[1] == txy[1] ? 0 : 1; + return [xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other anchor: if it declares zero for some direction, we declare zero too. + // this might not be the most awesome. perhaps we can come up with a better way. it's just so that the line we draw looks + // like it makes sense. maybe this wont make sense. + return [Math.abs(o[0]) * xDir * -1, Math.abs(o[1]) * yDir * -1]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering over another anchor; + * we want to assume that anchor's orientation for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no longer hovering + * over another anchor; we should resume calculating orientation as we normally do. + */ + this.out = function() { + orientation = null; + }; + }; + + // ************** connection + // **************************************** + /** + * allowed params: + * source: source element (string or a jQuery element) (required) + * target: target element (string or a jQuery element) (required) + * + * anchors: optional array of anchor placements. defaults to BottomCenter for source + * and TopCenter for target. + */ + var Connection = function(params) { + + // ************** get the source and target and register the connection. ******************* + var self = this; + var id = new String('_jsplumb_c_' + (new Date()).getTime()); + this.getId = function() { return id; }; + this.container = params.container || _currentInstance.Defaults.Container; // may be null; we will append to the body if so. + // get source and target as jQuery objects + this.source = _getElementObject(params.source); + this.target = _getElementObject(params.target); + this.sourceId = _getAttribute(this.source, "id"); + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + this.scope = params.scope; // scope may have been passed in to the connect call. + // if it wasn't, we will pull it from the source endpoint, + // after having initialised the endpoints. + + // init endpoints + this.endpoints = []; + this.endpointStyles = []; + var prepareEndpoint = function(existing, index, params, element) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } + else { + if(!params.endpoints) params.endpoints = [null,null]; + var ep = params.endpoints[index] || params.endpoint || _currentInstance.Defaults.Endpoints[index] || jsPlumb.Defaults.Endpoints[index] || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || new jsPlumb.Endpoints.Dot(); + if (!params.endpointStyles) params.endpointStyles = [null,null]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + var a = params.anchors ? params.anchors[index] : _currentInstance.Defaults.Anchors[index] || jsPlumb.Defaults.Anchors[index] || _currentInstance.Defaults.Anchor || jsPlumb.Defaults.Anchor || jsPlumb.Anchors.BottomCenter; + var e = new Endpoint({style:es, endpoint:ep, connections:[self], anchor:a, source:element, container:self.container }); + self.endpoints[index] = e; + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || _currentInstance.Defaults.Connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + this.paintStyle = this.endpoints[0].connectorStyle || this.endpoints[1].connectorStyle || params.paintStyle || _currentInstance.Defaults.PaintStyle || jsPlumb.Defaults.PaintStyle; + + _updateOffset(this.sourceId); + _updateOffset(this.targetId); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var anchorLoc = this.endpoints[0].anchor.compute([myOffset.left, myOffset.top], myWH, this.endpoints[0]); + this.endpoints[0].paint(anchorLoc); + myOffset = offsets[this.targetId]; myWH = sizes[this.targetId]; + anchorLoc = this.endpoints[1].anchor.compute([myOffset.left, myOffset.top], myWH, this.endpoints[1]); + this.endpoints[1].paint(anchorLoc); + + // *************** create canvas on which the connection will be drawn ************ + var canvas = _newCanvas(jsPlumb.connectorClass, self.container); + this.canvas = canvas; + + /** + * paints the connection. + * @param elId Id of the element that is in motion + * @param ui current library's event system ui object (present if we came from a drag to get here) + * @param recalc whether or not to recalculate element sizes. this is true if a repaint caused this to be painted. + */ + this.paint = function(elId, ui, recalc, timestamp) { + + if (log) log.debug("Painting Connection; element in motion is " + elId + "; ui is [" + ui + "]; recalc is [" + recalc + "]"); + + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = !(elId == this.sourceId); + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + if (this.canvas.getContext) { + //if (recalc) { + _updateOffset(elId, ui, recalc); + _updateOffset(tId); // update the target if this is a forced repaint. otherwise, only the source has been moved. + //} + + var myOffset = offsets[elId]; + var otherOffset = offsets[tId]; + var myWH = sizes[elId]; + var otherWH = sizes[tId]; + + var ctx = canvas.getContext('2d'); + //TODO: why are these calculated again? they were calculated in the _draw function. + var sAnchorP = this.endpoints[sIdx].anchor.compute([myOffset.left, myOffset.top], myWH, this.endpoints[sIdx]); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.compute([otherOffset.left, otherOffset.top], otherWH, this.endpoints[tIdx]); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, this.paintStyle.lineWidth); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + jsPlumb.extend(ctx, this.paintStyle); + + if (this.paintStyle.gradient && !ie) { + var g = swap ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + for (var i = 0; i < this.paintStyle.gradient.stops.length; i++) + g.addColorStop(this.paintStyle.gradient.stops[i][0],this.paintStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + + this.connector.paint(dim, ctx); + } + }; + + //TODO: should this take a timestamp? probably. it reduces the amount of time + // spent figuring out anchor locations. + this.repaint = function() { + this.paint(this.sourceId, null, true); + }; + + _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + }; + + /* + Class: Endpoint + Models an endpoint. Can have one to N connections emanating from it (although how to handle that in the UI is + a very good question). also has a Canvas and paint style. + */ + + /* + Function: Endpoint + This is the Endpoint class constructor. + Parameters: + anchor - anchor for the endpoint, of type jsPlumb.Anchor. may be null. + endpoint - endpoint object, of type jsPlumb.Endpoint. may be null. + style - endpoint style, a js object. may be null. + source - element the endpoint is attached to, of type jquery object. Required. + canvas - canvas element to use. may be, and most often is, null. + connections - optional list of connections to configure the endpoint with. + isSource - boolean. indicates the endpoint can act as a source of new connections. optional. + dragOptions - if isSource is set to true, you can supply arguments for the jquery draggable method. optional. + connectionStyle - if isSource is set to true, this is the paint style for connections from this endpoint. optional. + connector - optional connector type to use. + isTarget - boolean. indicates the endpoint can act as a target of new connections. optional. + dropOptions - if isTarget is set to true, you can supply arguments for the jquery droppable method. optional. + reattach - optional boolean that determines whether or not the connections reattach after they + have been dragged off an endpoint and left floating. defaults to false: connections + dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + params = params || {}; + // make a copy. then we can use the wrapper function. + jsPlumb.extend({}, params); + var self = this; + var id = new String('_jsplumb_e_' + (new Date()).getTime()); + this.getId = function() { return id; }; + self.anchor = params.anchor || jsPlumb.Anchors.TopCenter; + var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot(); + var _style = params.style || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + this.connectorStyle = params.connectorStyle; + this.connector = params.connector; + var _element = params.source; + this.container = params.container || jsPlumb.Defaults.Container; + var _elementId = _getAttribute(_element, "id"); + var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of. + this.canvas = params.canvas || _newCanvas(jsPlumb.endpointClass, this.container); + this.connections = params.connections || []; + this.scope = params.scope || DEFAULT_SCOPE; + var _reattach = params.reattach || false; + var floatingEndpoint = null; + var inPlaceCopy = null; + this.addConnection = function(connection) { + self.connections.push(connection); + }; + this.removeConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) + self.connections.splice(idx, 1); + }; + this.makeInPlaceCopy = function() { + var e = new Endpoint({anchor:self.anchor, source:_element, style:_style, endpoint:_endpoint}); + return e; + }; + /** + * returns whether or not this endpoint is connected to the given endpoint. + * @param endpoint Endpoint to test. + * @since 1.1.1 + * + * todo: needs testing. will this work if the endpoint passed in is the source? + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + this.isFloating = function() { return floatingEndpoint != null; }; + /** + * first pass at default ConnectorSelector: returns the first connection, if we have any. + * modified a little, 5/10/2010: now it only returns a connector if we have not got equal to or more than _maxConnector connectors + * attached. otherwise it is assumed a new connector is ok. but note with this setup we can't select any other connection than the first + * one. what if this could return a list? that implies everything should work with a list - dragging etc. could be nasty. could also + * be cool. + */ + var connectorSelector = function() { + return self.connections.length == 0 || self.connections.length < _maxConnections ? null : self.connections[0]; + }; + + this.isFull = function() { return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); }; + + /** + * a deep equals check. everything must match, including the anchor, styles, everything. + * TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor) && + true; + }; + + /** + * paints the Endpoint, recalculating offset and anchor positions if necessary. + */ + this.paint = function(anchorPoint, connectorPaintStyle, canvas) { + + if (log) log.debug("Painting Endpoint with elementId [" + _elementId + "]; anchorPoint is [" + anchorPoint + "]"); + + if (anchorPoint == null) { + // do we always want to force a repaint here? i dont think so! + var xy = offsets[_elementId]; + var wh = sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset(_elementId); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + anchorPoint = self.anchor.compute([xy.left, xy.top], wh, self); + } + _endpoint.paint(anchorPoint, self.anchor.getOrientation(), canvas || self.canvas, _style, connectorPaintStyle || _style); + }; + + + // is this a connection source? we make it draggable and have the drag listener + // maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + + // first the question is, how many connections are on this endpoint? if it's only one, then excellent. otherwise we will either need a way + // to select one connection from the list, or drag them all. if we had a pluggable 'ConnectorSelector' interface we could probably + // provide a way for people to implement their own UI components to do the connector selection. the question in that particular case would be how much + // information the interface needs from jsPlumb at execution time. if, however, we leave out the connector selection, and drag them all, + // that wouldn't be too hard to organise. perhaps that behaviour would be on a switch for the endpoint, or perhaps the ConnectorSelector + // interface returns a List, with the default implementation just returning everything. i think i like that. + // + // let's say for now that there is just one endpoint, cos we need to get this going before we can consider a list of them anyway. + // the major difference between that case and the case of a new endpoint is actually quite small - it's a question of where the + // Connection comes from. for a new one, we create a new one. obviously. otherwise we just get the jpc from the Endpoint + // (remember we're only assuming one connection right now). so all of the UI stuff we do to create the floating endpoint etc + // will still be valid, but when we stop dragging, we'll have to do something different. if we stop with a valid drop i think it will + // be the same process. but if we stop with an invalid drop we have to reset the Connection to how it was when we got it. + var start = function() { + + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + var nE = _getElementObject(n); + _appendElement(n); // + // create and assign an id, and initialize the offset. + //TODO can't this be replaced with a _getId call? + var id = "" + new String(new Date().getTime()); + _setAttribute(nE, "id", id); + _updateOffset(id); + // store the id of the dragging div and the source element. the drop function + // will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor({reference:self.anchor, referenceCanvas:self.canvas}); + floatingEndpoint = new Endpoint({ style:{fillStyle:'rgba(0,0,0,0)'}, endpoint:_endpoint, anchor:floatingAnchor, source:nE }); + + jpc = connectorSelector(); + if (jpc == null) { + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = new Connection({ + sourceEndpoint:self, + targetEndpoint:floatingEndpoint, + source:_getElementObject(_element), + target:_getElementObject(n), + anchors:[self.anchor, floatingAnchor], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + connector: params.connector + }); + } else { + existingJpc = true; + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; + // probably we should remove the connection? and add it back if the user + // does not drop it somewhere proper. + self.removeConnection(jpc); + if (anchorIdx == 0){ + existingJpcParams = [jpc.source, jpc.sourceId]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + }else { + existingJpcParams = [jpc.target, jpc.targetId]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // todo unregister on stop + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + }; + + var dragOptions = params.dragOptions || { }; + + var defaultOpts = jsPlumb.extend({}, jsPlumb.CurrentLibrary.defaultDragOptions); + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + var startEvent = jsPlumb.CurrentLibrary.dragEvents['start']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements([n, floatingEndpoint.canvas]); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + if (_reattach) { + jpc.floatingAnchorIndex = null; + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } + else { + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeElement(jpc.canvas); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + } else { + _removeElement(jpc.canvas); + self.removeConnection(jpc); + } + } + jpc = null; + delete floatingEndpoint; + delete inPlaceCopy; + self.paint(); + } + ); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + }; + + // connector target + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend({}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + dropOptions[dropEvent]= _wrap(dropOptions[dropEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable), "dragId"); + var elId = _getAttribute(_getElementObject(draggable),"elId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // remove this jpc from the current endpoint + jpc.endpoints[idx].removeConnection(jpc); + if (jpc.suspendedEndpoint) + jpc.suspendedEndpoint.removeConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + _addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + jsPlumb.repaint(elId); + + delete floatingConnections[id]; + }); + + dropOptions[overEvent]= _wrap(dropOptions[overEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + }); + + jsPlumb.CurrentLibrary.initDroppable(_getElementObject(self.canvas), dropOptions); + } + + return self; + }; + + /* + Property: Defaults + These are the default settings for jsPlumb, that is what will be used if you do not supply specific pieces of information + to the various API calls. A convenient way to implement your own look and feel can be to override these defaults by including a script + somewhere after the jsPlumb include, but before you make any calls to jsPlumb, for instance in this example we set the PaintStyle to be + a blue line of 27 pixels: + > jsPlumb.Defaults.PaintStyle = { lineWidth:27, strokeStyle:'blue' } + */ + this.Defaults = { + Anchor : null, + Anchors : [ null, null ], + Connector : null, + Container : null, + DragOptions: { }, + DropOptions: { }, + Endpoint : null, + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : null }, + EndpointStyles : [ null, null ], + MaxConnections : null, + PaintStyle : { lineWidth : 10, strokeStyle : 'red' }, + Scope : "_jsPlumb_DefaultScope" + }; + + /* + Property: connectorClass + The CSS class to set on Connection canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = '_jsPlumb_connector'; + + /* + Property: endpointClass + The CSS class to set on Endpoint canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = '_jsPlumb_endpoint'; + + /* + Property: Anchors + Default jsPlumb Anchors. These are supplied in the file jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form .jsPlumb-all-x.x.x.js. You can provide your own Anchors by supplying them in a script that is loaded after jsPlumb, for instance: + > jsPlumb.Anchors.MyAnchor = { ....anchor code here. see the documentation. } + */ + this.Anchors = {}; + + /* + Property: Connectors + Default jsPlumb Connectors. These are supplied in the file jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form .jsPlumb-all-x.x.x.js. You can provide your own Connectors by supplying them in a script that is loaded after jsPlumb, for instance: + > jsPlumb.Connectors.MyConnector = { ....connector code here. see the documentation. } + */ + this.Connectors = {}; + + /* + Property: Endpoints + Default jsPlumb Endpoints. These are supplied in the file jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form .jsPlumb-all-x.x.x.js. You can provide your own Endpoints by supplying them in a script that is loaded after jsPlumb, for instance: + > jsPlumb.Endpoints.MyEndpoint = { ....endpoint code here. see the documentation. } + */ + this.Endpoints = {}; + + /* + Function: addEndpoint + Adds an Endpoint to a given element. + Parameters: + target - Element to add the endpoint to. either an element id, or a selector representing some element. + params - Object containing Endpoint options (more info required) + Returns: + The newly created Endpoint. + See Also: + + */ + this.addEndpoint = function(target, params) { + params = jsPlumb.extend({}, params); + var el = _getElementObject(target); + var id = _getAttribute(el,"id"); + params.source = el; + _updateOffset(id); + var e = new Endpoint(params); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id]; + var myWH = sizes[id]; + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH, e); + e.paint(anchorLoc); + return e; + }; + + /* + Function: addEndpoint + Adds a list of Endpoints to a given element. + Parameters: + target - element to add the endpoint to. either an element id, or a selector representing some element. + endpoints - List of objects containing Endpoint options. one Endpoint is created for each entry in this list. + Returns: + List of newly created Endpoints, one for each entry in the 'endpoints' argument. + See Also: + + */ + this.addEndpoints = function(target, endpoints) { + var results = []; + for (var i = 0; i < endpoints.length; i++) { + results.push(jsPlumb.addEndpoint(target, endpoints[i])); + } + return results; + }; + + /* + Function: animate + Wrapper around supporting library's animate function; injects a call to jsPlumb in the 'step' function (creating it if necessary). + This only supports the two-arg version of the animate call in jQuery - the one that takes an 'options' object as the second arg. + MooTools has only one method - a two arg one. Which is handy. + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the standard jQuery animate call. + options - The 'options' argument you want passed to the standard jQuery animate call. + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el); + var id = _getAttribute(el,"id"); + //TODO this is not agnostic yet. + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() + { + _currentInstance.repaint(id); + }); + + // you know, probably onComplete should repaint too. that will help keep up + // with fast animations. + options[completeFunction] = _wrap(options[completeFunction], function() + { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /** + * clears the cache used to lookup elements by their id. if you remove any elements + * from the DOM you should call this to ensure that if you add them back in jsPlumb does not + * have a stale handle. + */ + this.clearCache = function() { + delete cached; + cached = {}; + }; + + /* + Function: connect + Establishes a connection between two elements. + Parameters: + params - Object containing setup for the connection. see documentation. + Returns: + The newly created Connection. + */ + this.connect = function(params) { + if (params.sourceEndpoint && params.sourceEndpoint.isFull()) { + _log("could not add connection; source endpoint is full"); + return; + } + + if (params.targetEndpoint && params.targetEndpoint.isFull()) { + _log("could not add connection; target endpoint is full"); + return; + } + + var jpc = new Connection(params); + // add to list of connections (by scope). + _addToList(connectionsByScope, jpc.scope, jpc); + + // force a paint + _draw(jpc.source); + + return jpc; + }; + + /** + * not implemented yet. params object will have sourceEndpoint and targetEndpoint members; these will be Endpoints. + connectEndpoints : function(params) { + var jpc = Connection(params); + + },*/ + + /* + Function: detach + Removes a connection. + Parameters: + sourceId - Id of the first element in the connection. A String. + targetId - iI of the second element in the connection. A String. + Returns: + true if successful, false if not. + */ + this.detach = function(sourceId, targetId) { + var f = function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + return true; + } + + }; + + // todo: how to cleanup the actual storage? a third arg to _operation? + _operation(sourceId, f); + }; + + /* + Function: detachAll + Removes all an element's connections. + Parameters: + el - either the id of the element, or a selector for the element. + Returns: + void + */ + this.detachAll = function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + var c = endpoints[i].connections.length; + if (c > 0) { + for (var j = 0; j < c; j++) { + var jpc = endpoints[i].connections[0]; + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + } + } + } + }; + + /* + Function: detachEverything + Remove all Connections from all elements, but leaves Endpoints in place. + Returns: + void + See Also: + + */ + this.detachEverything = function() { + for(var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + var c = endpoints[i].connections.length; + if (c > 0) { + for (var j = 0; j < c; j++) { + var jpc = endpoints[i].connections[0]; + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + } + } + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + /* + Function: draggable + initialises the draggability of some element or elements. + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for (var i = 0; i < el.length; i++) + { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) + _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + Parameters: + o1 - object to extend + o2 - object to extend o1 with + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + Function: getConnections + Gets all or a subset of connections currently managed by this jsPlumb instance. + Parameters: + options - a JS object that holds options defining what sort of connections you're looking for. Valid values are: + scope this may be a String or a list of Strings. jsPlumb will only return Connections whose scope matches what this option + defines. If you omit it, you will be given Connections of every scope. + source may be a string or a list of strings; constraints results to have only Connections whose source is this object. + target may be a string or a list of strings; constraints results to have only Connections whose target is this object. + + The return value is a dictionary in this format: + + { + 'scope1': [ + {sourceId:'window1', targetId:'window2'}, + {sourceId:'window3', targetId:'window4'}, + {sourceId:'window1', targetId:'window3'} + ], + 'scope2': [ + {sourceId:'window1', targetId:'window3'} + ] + } + + */ + this.getConnections = function(options) { + var r = {}; + options = options || {}; + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') r.push(input); + else r = input; + } + return r; + }; + var scopes = prepareList(options.scope); + var sources = prepareList(options.source); + var targets = prepareList(options.target); + var filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }; + for (var i in connectionsByScope) { + if (filter(scopes, i)) { + r[i] = []; + for (var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + r[i].push({sourceId:c.sourceId, targetId:c.targetId}); + } + } + } + return r; + }; + + /* + Function: getDefaultScope + Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a scope to an endpoint + or connection allows you to support different types of connections in the same UI. but if you're only interested in one type of connection, + you don't need to supply a scope. this method will probably be used by very few people; it's good for testing though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: hide + Sets an element's connections to be hidden. + Parameters: + el - either the id of the element, or a selector for the element. + Returns: + void + */ + this.hide = function(el) { + _setVisible(el, "none"); + }; + + /* + Function: makeAnchor + Creates an anchor with the given params. + Parameters: + x - the x location of the anchor as a fraction of the total width. + y - the y location of the anchor as a fraction of the total height. + xOrientation - value indicating the general direction a connection from the anchor should go in, in the x direction. + yOrientation - value indicating the general direction a connection from the anchor should go in, in the y direction. + xOffset - a fixed offset that should be applied in the x direction that should be applied after the x position has been figured out. optional. defaults to 0. + yOffset - a fixed offset that should be applied in the y direction that should be applied after the y position has been figured out. optional. defaults to 0. + Returns: + The newly created Anchor. + */ + this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards compatibility if we are given only one value we assume it's a call in the old form. + var params = {}; + if (arguments.length == 1) jsPlumb.extend(params, x); + else { + params = {x:x, y:y}; + if (arguments.length >= 4) { + params.orientation = [arguments[2], arguments[3]]; + } + if (arguments.length == 6) params.offsets = [arguments[4], arguments[5]]; + } + var a = new Anchor(params); + a.clone = function() { return new Anchor(params); }; + return a; + }; + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + Parameters: + el - either the id of the element or a selector representing the element. + Returns: + void + See Also: + + */ + this.repaint = function(el) { + + var _processElement = function(el) { + var ele = _getElementObject(el); + _draw(ele); + }; + + // TODO: support a jQuery result object too! + + // support both lists... + if (typeof el =='object') { + for (var i = 0; i < el.length; i++) + _processElement(el[i]); + } // ...and single strings. + else _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + Returns: + void + See Also: + + */ + this.repaintEverything = function() { + for (var elId in endpointsByElement) { + _draw(_getElementObject(elId)); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + Parameters: + el - either an element id, or a selector for an element. + Returns: + void + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"); + // first remove all Connections. + jsPlumb.detachAll(elId); + var ebe = endpointsByElement[elId]; + for (var i in ebe) { + _removeElement(ebe[i].canvas); + } + endpointsByElement[elId] = []; + }; + + /* + Function: removeEveryEndpoint + Removes every Endpoint in this instance of jsPlumb. + Returns: + void + See Also: + + */ + this.removeEveryEndpoint = function() { + for(var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + _removeElement(endpoints[i].canvas, endpoints[i].container); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + }; + + /* + Function: removeEndpoint + Removes the given Endpoint from the given element. + Parameters: + el - either an element id, or a selector for an element. + endpoint - Endpoint to remove. this is an Endpoint object, such as would have been returned from a call to addEndpoint. + Returns: + void + See Also: + + */ + this.removeEndpoint = function(el, endpoint) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + if (ebe) { + if(_removeFromList(endpointsByElement, elId, endpoint)) + _removeElement(endpoint.canvas); + } + }; + + /* + Function:reset + removes all endpoints and connections and clears the element cache. + */ + this.reset = function() { + this.detachEverything(); + this.removeEveryEndpoint(); + this.clearCache(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + Parameters: + value - whether or not to automatically repaint when the window is resized. + Returns: + void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + Function: setDefaultNewCanvasSize + Sets the default size jsPlumb will use for a new canvas (we create a square canvas so one value is all that is required). + This is a hack for IE, because ExplorerCanvas seems to need for a canvas to be larger than what you are going to draw on + it at initialisation time. The default value of this is 1200 pixels, which is quite large, but if for some reason you're + drawing connectors that are bigger, you should adjust this value appropriately. + Parameters: + size - The default size to use. jsPlumb will use a square canvas so you need only supply one value. + Returns: + void + */ + this.setDefaultNewCanvasSize = function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }; + + /* + Function: setDefaultScope + Sets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a scope to an endpoint + or connection allows you to support different types of connections in the same UI. but if you're only interested in one type of connection, + you don't need to supply a scope. this method will probably be used by very few people; it just instructs jsPlumb to use a different key + for the default scope. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + Function: setDraggable + Sets whether or not a given element is draggable, regardless of what any plumb command may request. + Parameters: + el - either the id for the element, or a selector representing the element. + Returns: + void + */ + this.setDraggable = _setDraggable; + + /* + Function: setDraggableByDefault + Sets whether or not elements are draggable by default. Default for this is true. + Parameters: + draggable - value to set + Returns: + void + */ + this.setDraggableByDefault = function(draggable) { + _draggableByDefault = draggable; + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + Function: setRepaintFunction + Sets the function to fire when the window size has changed and a repaint was fired. + Parameters: + f - Function to execute. + Returns: + void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + Function: show + Sets an element's connections to be visible. + Parameters: + el - either the id of the element, or a selector for the element. + Returns: + void + */ + this.show = function(el) { + _setVisible(el, "block"); + }; + + /* + Function: sizeCanvas + Helper to size a canvas. You would typically use this when writing your own Connector or Endpoint implementation. + Parameters: + x - [int] x position for the Canvas origin + y - [int] y position for the Canvas origin + w - [int] width of the canvas + h - [int] height of the canvas + Returns: + void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; canvas.height = h; + canvas.style.width = w + "px"; canvas.width = w; + canvas.style.left = x + "px"; canvas.style.top = y + "px"; + // _currentInstance.CurrentLibrary.setPosition(canvas, x, y); + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards compatibility + */ + this.toggle = _toggleVisible; + + /* + Function: toggleVisible + Toggles visibility of an element's connections. + Parameters: + el - either the element's id, or a selector representing the element. + Returns: + void, but should be updated to return the current state + */ + //TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + Function: toggleDraggable + Toggles draggability (sic) of an element's connections. + Parameters: + el - either the element's id, or a selector representing the element. + Returns: + The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + Function: unload + Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element + Returns: + void + */ + this.unload = function() { + delete endpointsByElement; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + }; + + /* + Function: wrap + Helper method to wrap an existing function with one of your own. This is used by the various + implementations to wrap event callbacks for drag/drop etc; it allows jsPlumb to be + transparent in its handling of these things. If a user supplies their own event callback, + for anything, it will always be called. + Parameters: + */ + this.wrap = _wrap; + this.trace = _trace; + this.clearTrace = _clearTrace; + this.clearAllTraces = _clearAllTraces; + this.getTrace = _getTrace; + + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(); + if (_defaults) + jsPlumb.extend(j.Defaults, _defaults); + return j; + }; + +})(); + +/** +* jsPlumb-defaults-1.2-RC1 +* +* This script contains the default Anchors, Endpoints and Connectors for jsPlumb. It should be used with jsPlumb 1.1.0 and above; +* prior to version 1.1.0 of jsPlumb the defaults were included inside the main script. +* +* Version 1.1.1 of this script adds the Triangle Endpoint, written by __________ and featured on this demo: +* +* http://www.mintdesign.ru/blog/javascript-jsplumb-making-graph +* http://www.mintdesign.ru/site/samples/jsplumb/jsPlumb-graph-sample.htm +* +* NOTE: for production usage you should use jsPlumb-all-1.1.1-min.js, which contains the main jsPlumb script and this script together, +* in a minified file. +*/ + +(function() { + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * getOrientation : function() { return [ox, oy]; } + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + jsPlumb.Anchors.TopCenter = jsPlumb.makeAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors.BottomCenter = jsPlumb.makeAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors.LeftMiddle = jsPlumb.makeAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors.RightMiddle = jsPlumb.makeAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors.Center = jsPlumb.makeAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors.TopRight = jsPlumb.makeAnchor(1, 0, 0,-1); + jsPlumb.Anchors.BottomRight = jsPlumb.makeAnchor(1, 1, 0, 1); + jsPlumb.Anchors.TopLeft = jsPlumb.makeAnchor(0, 0, 0, -1); + jsPlumb.Anchors.BottomLeft = jsPlumb.makeAnchor(0, 1, 0, 1); + + + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (w < 2 * lineWidth) { + w = 2 * lineWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - lineWidth; + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < 2 * lineWidth) { + // minimum size is 2 * line Width + h = 2 * lineWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - lineWidth; + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + var sx = sourcePos[0] < targetPos[0] ? w-xo : xo; + var sy = sourcePos[1] < targetPos[1] ? h-yo : yo; + var tx = sourcePos[0] < targetPos[0] ? xo : w-xo; + var ty = sourcePos[1] < targetPos[1] ? yo : h-yo; + var retVal = [ x, y, w, h, sx, sy, tx, ty ]; + + return retVal; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + }; + + /** + * This Connector draws a Bezier curve with two control points. + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + jsPlumb.Connectors.Bezier = function(curviness) { + + var self = this; + this.majorAnchor = curviness || 150; + this.minorAnchor = 10; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) + { + lineWidth = lineWidth || 0; + var w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth, h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + var canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2), canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + var sx = sourcePos[0] < targetPos[0] ? w - (lineWidth/2): (lineWidth/2), sy = sourcePos[1] < targetPos[1] ? h-(lineWidth/2) : (lineWidth/2); + var tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : w-(lineWidth/2), ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : h-(lineWidth/2); + var CP = self._findControlPoint([sx,sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + var CP2 = self._findControlPoint([tx,ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(sx,tx); var minx2 = Math.min(CP[0], CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(sx,tx); var maxx2 = Math.max(CP[0], CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > w) w = maxx; + if (minx < 0) { + canvasX += minx; var ox = Math.abs(minx); + w += ox; CP[0] += ox; sx += ox; tx +=ox; CP2[0] += ox; + } + + var miny1 = Math.min(sy,ty); var miny2 = Math.min(CP[1], CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(sy,ty); var maxy2 = Math.max(CP[1], CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > h) h = maxy; + if (miny < 0) { + canvasY += miny; var oy = Math.abs(miny); + h += oy; CP[1] += oy; sy += oy; ty +=oy; CP2[1] += oy; + } + + // return [ canvasx, canvasy, canvasWidth, canvasHeight, + // sourceX, sourceY, targetX, targetY, + // controlPoint1_X, controlPoint1_Y, controlPoint2_X, controlPoint2_Y + return [canvasX, canvasY, w, h, sx, sy, tx, ty, CP[0], CP[1], CP2[0], CP2[1] ]; + }; + + this.paint = function(d, ctx) { + + /*var img = new Image(); + img.src = "../img/pattern.jpg"; + ctx.fillStyle = ctx.createPattern(img, 'repeat-y');*/ + + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + //ctx.fill(); + } + }; + + + /** + * Types of endpoint UIs. we supply four - a circle of default radius 10px, a rectangle of + * default size 20x20, an image (with no default), and a Triangle, of default size 15. + * you can supply others of these if you want to - see the documentation for a howto. + */ + + /** + * a round endpoint, with default radius 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + } + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * A Rectangular endpoint, with default size 20x20. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + //todo: the fillStyle needs some thought. we want to support a few options: + // 1. nothing supplied; use the stroke color or the default if no stroke color. + // 2. a fill color supplied - use it + // 3. a gradient supplied - use it + // 4. setting the endpoint to the same color as the bg of the element it is attached to. + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + jsPlumb.Endpoints.Triangle = function(params) { + + params = params || { width:15, height:15 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - width/2; + var y = anchorPoint[1] - height/2; + + jsPlumb.sizeCanvas(canvas, x, y, width, height); + + var ctx = canvas.getContext('2d'); + var + offsetX = 0, + offsetY = 0, + angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = endpointStyle.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + ctx.fill(); + + }; + }; + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' property in the params object.. + */ + jsPlumb.Endpoints.Image = function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + }; +})(); + +(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function() { + this.parent(); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}; + var _droppableOptions = {}; + var _draggablesByScope = {}; + var _draggablesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }; + + + jsPlumb.CurrentLibrary = { + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' + }, + + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).grab(child); + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + getElementObject : function(el) { + return $(el); + }, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + initDraggable : function(el, options) { + var drag = _draggablesById[el.get("id")]; + if (!drag) { + var originalZIndex = 0, originalCursor = null; + var dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + options['onStart'] = jsPlumb.wrap(options['onStart'], function() + { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() + { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + }); + + // DROPPABLES: + var scope = options['scope'] || jsPlumb.Defaults.Scope; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop'); + } + }; + + drag = new Drag.Move(el, options); + _add(_draggablesByScope, scope, drag); + _add(_draggablesById, el.get("id"), drag); + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + initDroppable : function(el, options) { + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _add(_droppables, scope, el); + _droppableOptions[el.get("id")] = options; + var filterFunc = function(entry) { + return entry.element != el; + }; + var draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + if (typeof Drag != undefined) + return typeof Drag.Move != undefined; + return false; + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + m.start(properties); + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[0]; + return { left: ui.offsetLeft, top: ui.offsetTop }; + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + }, + + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).dispose(); // ?? + }, + + getScrollLeft : function(el) { + return null; + }, + + getScrollTop : function(el) { + return null; + } + }; +})(); diff --git a/archive/1.2.2/jquery.jsPlumb-1.2.2-RC1.js b/archive/1.2.2/jquery.jsPlumb-1.2.2-RC1.js new file mode 100644 index 000000000..a556e6b6f --- /dev/null +++ b/archive/1.2.2/jquery.jsPlumb-1.2.2-RC1.js @@ -0,0 +1,259 @@ +/* + * jquery.jsPlumb 1.2.2-RC1 + * + * jQuery specific functionality for jsPlumb. + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + * NOTE: for production usage you should use jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, + * in a minified file. + * + * Dual licensed under MIT and GPL2. + * + */ + +(function($){ + /** + * plumbs the results of the selector to some target, using the given options if supplied, + * or the defaults otherwise. + */ + $.fn.plumb = function(options) { + var options = $.extend({}, options); + + return this.each(function() + { + var params = $.extend({source:$(this)}, options); + jsPlumb.connect(params); + }); + }; + + /** + * detaches the results of the selector from the given target or list of targets - 'target' + * may be a String or a List. + */ + $.fn.detach = function(target) { + return this.each(function() { + if (target) { + var id = $(this).attr("id"); + if (typeof target == 'string') target = [target]; + for (var i = 0; i < target.length; i++) + jsPlumb.detach(id, target[i]); + } + }); + }; + + /** + * detaches the results from the selector from all connections. + */ + $.fn.detachAll = function() { + return this.each(function() + { + var id = $(this).attr("id"); + jsPlumb.detachAll(id); + }); + }; + + /** + * adds an endpoint to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoint = function(options) { + var addedEndpoints = []; + this.each(function() { + addedEndpoints.push(jsPlumb.addEndpoint($(this).attr("id"), options)); + }); + return addedEndpoints[0]; + }; + + /** + * adds a list of endpoints to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoints = function(endpoints) { + var addedEndpoints = []; + return this.each(function() { + var e = jsPlumb.addEndpoints($(this).attr("id"), endpoints); + for (var i = 0; i < e.length; i++) addedEndpoints.push(e[i]); + }); + return addedEndpoints; + }; + + /** + * remove the endpoint, if it exists, deleting its UI elements etc. + */ + $.fn.removeEndpoint = function(endpoint) { + this.each(function() { + jsPlumb.removeEndpoint($(this).attr("id"), endpoint); + }); + }; + +})(jQuery); + + +/*TODO: abstract this out from jQuery too! but how...because jsPlumb is not loaded yet. +$(window).bind('resize', function() { + if (resizeTimer) clearTimeout(resizeTimer); + resizeTimer = setTimeout(repaintEverything, 100); + });*/ + + + +/* + * the library agnostic functions, such as find offset, get id, get attribute, extend etc. + */ +(function($) { + + jsPlumb.CurrentLibrary = { + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step', + 'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete' + }, + + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).append(child); + }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + * + */ + getElementObject : function(el) { + return typeof(el)=='string' ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options) { + // remove helper directive if present. we know what's best! + options.helper = null; + //TODO: if 'revert' is set on the options it causes end points to animate back to + // where they came from, if the connection is aborted. do we care? probably not. + // the todo is to decide whether we care or not. + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.draggable(options); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.droppable(options); + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[1]; + return ui.absolutePosition || ui.offset; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + }, + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).remove(); + }, + + getScrollLeft : function(el) { + return el.scrollLeft(); + }, + + getScrollTop : function(el) { + return el.scrollTop(); + } + }; +})(jQuery); diff --git a/archive/1.2.2/jquery.jsPlumb-1.2.2-all-min.js b/archive/1.2.2/jquery.jsPlumb-1.2.2-all-min.js new file mode 100644 index 000000000..858abb7e2 --- /dev/null +++ b/archive/1.2.2/jquery.jsPlumb-1.2.2-all-min.js @@ -0,0 +1,53 @@ +(function(){var m=function(){var f=this,g=/MSIE/.test(navigator.userAgent)&&!window.opera,o=null,n=function(){c.repaintEverything()},i=true,j={},v={},s={},x=[],G={},H={},L=true,D=[],da={},Y="DEFAULT",ea=1200,Q=function(a,b,d){var e=function(l,p){if(l===p)return true;else if(typeof l=="object"&&typeof p=="object"){var q=true;for(var t in l)if(!e(l[t],p[t])){q=false;break}for(t in p)if(!e(p[t],l[t])){q=false;break}return q}};d=+d||0;for(var h=a.length;d=0){delete a[d];a.splice(d,1);return true}}return false},ja=function(a,b){var d=F(a,"id");ba(d,function(e){e.canvas.style.display=b})},na=function(a){ba(a,function(b){b.canvas.style.display="none"==b.canvas.style.display?"block":"none"})},O=function(a,b,d){if(d||b==null){b=w(a);D[a]=ha(b);x[a]=ga(b)}else x[a]=b},J=function(a,b){a=a||function(){};b=b||function(){};return function(){var d=null;try{d=b.apply(this,arguments)}catch(e){}try{a.apply(this, +arguments)}catch(h){}return d}},ka=function(a){var b=this;this.x=a.x||0;this.y=a.y||0;var d=a.orientation||[0,0],e=null;this.offsets=a.offsets||[0,0];this.compute=function(h,l,p){e=[h[0]+b.x*l[0]+b.offsets[0],h[1]+b.y*l[1]+b.offsets[1]];l=p?p.container:null;h={left:0,top:0};if(l!=null){var q=w(l);l=ga(q);p=c.CurrentLibrary.getScrollLeft(q);q=c.CurrentLibrary.getScrollTop(q);h.left=l.left-p;h.top=l.top-q;e[0]-=h.left;e[1]-=h.top}return e};this.getOrientation=function(){return d};this.equals=function(h){if(!h)return false; +var l=h.getOrientation(),p=this.getOrientation();return this.x==h.x&&this.y==h.y&&this.offsets[0]==h.offsets[0]&&this.offsets[1]==h.offsets[1]&&p[0]==l[0]&&p[1]==l[1]}},oa=function(a){var b=a.reference,d=ha(w(a.referenceCanvas)),e=0,h=0,l=null;this.compute=function(p){h=e=0;return[p[0]+d[0]/2,p[1]+d[1]/2]};this.getOrientation=function(){if(l)return l;else{var p=b.getOrientation();return[Math.abs(p[0])*e*-1,Math.abs(p[1])*h*-1]}};this.over=function(p){l=p.getOrientation()};this.out=function(){l=null}}, +la=function(a){var b=this,d=new String("_jsplumb_c_"+(new Date).getTime());this.getId=function(){return d};this.container=a.container||f.Defaults.Container;this.source=w(a.source);this.target=w(a.target);if(a.sourceEndpoint)this.source=a.sourceEndpoint.getElement();if(a.targetEndpoint)this.target=a.targetEndpoint.getElement();this.sourceId=F(this.source,"id");this.targetId=F(this.target,"id");this.endpointsOnTop=a.endpointsOnTop!=null?a.endpointsOnTop:true;this.scope=a.scope;this.endpoints=[];this.endpointStyles= +[];var e=function(p,q,t,E){if(p){b.endpoints[q]=p;p.addConnection(b)}else{if(!t.endpoints)t.endpoints=[null,null];p=t.endpoints[q]||t.endpoint||f.Defaults.Endpoints[q]||c.Defaults.Endpoints[q]||f.Defaults.Endpoint||c.Defaults.Endpoint||new c.Endpoints.Dot;if(!t.endpointStyles)t.endpointStyles=[null,null];var A=t.endpointStyles[q]||t.endpointStyle||f.Defaults.EndpointStyles[q]||c.Defaults.EndpointStyles[q]||f.Defaults.EndpointStyle||c.Defaults.EndpointStyle,y=t.anchors?t.anchors[q]:f.Defaults.Anchors[q]|| +c.Defaults.Anchors[q]||f.Defaults.Anchor||c.Defaults.Anchor||c.Anchors.BottomCenter,C=t.uuids?t.uuids[q]:null;t=t.uuids?new V({style:A,endpoint:p,connections:[b],uuid:C,anchor:y,source:E,container:b.container}):new V({style:A,endpoint:p,connections:[b],anchor:y,source:E,container:b.container});return b.endpoints[q]=t}},h=e(a.sourceEndpoint,0,a,b.source);h&&N(j,this.sourceId,h);(e=e(a.targetEndpoint,1,a,b.target))&&N(j,this.targetId,e);if(!this.scope)this.scope=this.endpoints[0].scope;this.connector= +this.endpoints[0].connector||this.endpoints[1].connector||a.connector||f.Defaults.Connector||c.Defaults.Connector||new c.Connectors.Bezier;this.paintStyle=this.endpoints[0].connectorStyle||this.endpoints[1].connectorStyle||a.paintStyle||f.Defaults.PaintStyle||c.Defaults.PaintStyle;O(this.sourceId);O(this.targetId);e=x[this.sourceId];h=D[this.sourceId];e=this.endpoints[0].anchor.compute([e.left,e.top],h,this.endpoints[0]);this.endpoints[0].paint(e);e=x[this.targetId];h=D[this.targetId];e=this.endpoints[1].anchor.compute([e.left, +e.top],h,this.endpoints[1]);this.endpoints[1].paint(e);var l=ia(c.connectorClass,b.container);this.canvas=l;this.paint=function(p,q,t){o&&o.debug("Painting Connection; element in motion is "+p+"; ui is ["+q+"]; recalc is ["+t+"]");var E=p!=this.sourceId,A=E?this.sourceId:this.targetId,y=E?0:1,C=E?1:0;if(this.canvas.getContext){O(p,q,t);O(A);t=x[p];q=x[A];p=D[p];var r=D[A];A=l.getContext("2d");t=this.endpoints[C].anchor.compute([t.left,t.top],p,this.endpoints[C]);this.endpoints[C].anchor.getOrientation(); +q=this.endpoints[y].anchor.compute([q.left,q.top],r,this.endpoints[y]);this.endpoints[y].anchor.getOrientation();y=this.connector.compute(t,q,this.endpoints[C].anchor,this.endpoints[y].anchor,this.paintStyle.lineWidth);c.sizeCanvas(l,y[0],y[1],y[2],y[3]);c.extend(A,this.paintStyle);if(this.paintStyle.gradient&&!g){E=E?A.createLinearGradient(y[4],y[5],y[6],y[7]):A.createLinearGradient(y[6],y[7],y[4],y[5]);for(C=0;C=0){var u=k.endpoints[0]==b?k.endpoints[1]:k.endpoints[0];b.connections.splice(z,1);u.detach(k)}};this.detachAll=function(){for(;b.connections.length>0;)b.detach(b.connections[0])};this.detachFrom=function(k){for(var z=[],u=0;u=t};this.setDragAllowedWhenFull=function(k){b.dragAllowedWhenFull=k};this.equals=function(k){return this.anchor.equals(k.anchor)&& +true};this.paint=function(k,z,u){o&&o.debug("Painting Endpoint with elementId ["+q+"]; anchorPoint is ["+k+"]");if(k==null){k=x[q];var K=D[q];if(k==null||K==null){O(q);k=x[q];K=D[q]}k=b.anchor.compute([k.left,k.top],K,b)}e.paint(k,b.anchor.getOrientation(),u||b.canvas,h,z||h)};this.removeConnection=this.detach;if(a.isSource&&c.CurrentLibrary.isDragSupported(l)){var C=null;var r=d=null,ma=false,P=null,B=a.dragOptions||{},M=c.extend({},c.CurrentLibrary.defaultDragOptions);B=c.extend(M,B);B.scope=B.scope|| +b.scope;M=c.CurrentLibrary.dragEvents.start;var W=c.CurrentLibrary.dragEvents.stop,X=c.CurrentLibrary.dragEvents.drag;B[M]=J(B[M],function(){r=b.connections.length0)for(var e=0;e0)for(var h=0;h +0?Q(e,l)!=-1:true){b[l]=[];for(d=0;d0?Q(h,p.sourceId)!=-1:true)&&(a.length>0?Q(a,p.targetId)!=-1:true)&&b[l].push({sourceId:p.sourceId,targetId:p.targetId,source:p.source,target:p.target,sourceEndpoint:p.endpoints[0],targetEndpoint:p.endpoints[1]})}}return b};this.getDefaultScope=function(){return Y};this.getEndpoint=function(a){return v[a]};this.getId=R;this.hide=function(a){ja(a,"none")};this.makeAnchor=function(a,b){var d={};if(arguments.length==1)c.extend(d, +a);else{d={x:a,y:b};if(arguments.length>=4)d.orientation=[arguments[2],arguments[3]];if(arguments.length==6)d.offsets=[arguments[4],arguments[5]]}var e=new ka(d);e.clone=function(){return new ka(d)};return e};this.repaint=function(a){var b=function(e){e=w(e);T(e)};if(typeof a=="object")for(var d=0;dj)j=o;if(g<0){s+=g;g=Math.abs(g);j+=g;D[0]+=g;G+=g;L+=g;f[0]+=g}g=Math.min(Math.min(H,i),Math.min(D[1], +f[1]));o=Math.max(Math.max(H,i),Math.max(D[1],f[1]));if(o>v)v=o;if(g<0){x+=g;g=Math.abs(g);v+=g;D[1]+=g;H+=g;i+=g;f[1]+=g}return[s,x,j,v,G,H,L,i,D[0],D[1],f[0],f[1]]};this.paint=function(f,g){g.beginPath();g.moveTo(f[4],f[5]);g.bezierCurveTo(f[8],f[9],f[10],f[11],f[6],f[7]);g.stroke()}};jsPlumb.Endpoints.Dot=function(m){m=m||{radius:10};var c=this;this.radius=m.radius;var f=0.5*this.radius,g=this.radius/3,o=function(n){try{return parseInt(n)}catch(i){if(n.substring(n.length-1)=="%")return parseInt(n.substring(0, +n-1))}};this.paint=function(n,i,j,v,s){var x=v.radius||c.radius;jsPlumb.sizeCanvas(j,n[0]-x,n[1]-x,x*2,x*2);n=j.getContext("2d");j=jsPlumb.extend({},v);if(j.fillStyle==null)j.fillStyle=s.strokeStyle;jsPlumb.extend(n,j);s=/MSIE/.test(navigator.userAgent)&&!window.opera;if(v.gradient&&!s){s=v.gradient;j=f;var G=g;if(s.offset)j=o(s.offset);if(s.innerRadius)G=o(s.innerRadius);s=[j,G];i=n.createRadialGradient(x,x,x,x+(i[0]==1?s[0]*-1:s[0]),x+(i[1]==1?s[0]*-1:s[0]),s[1]);for(s=0;s endpoint lists. an element can have an arbitrary number of endpoints on it, + * and not all of them have to be connected to anything. + */ + var endpointsByElement = {}; + var endpointsByUUID = {}; + var connectionsByScope = {}; + var offsets = []; + var floatingConnections = {}; + var draggableStates = {}; + var _draggableByDefault = true; + var sizes = []; + var listeners = {}; // a map: keys are event types, values are lists of listeners. + var DEFAULT_SCOPE = 'DEFAULT'; + + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + var _findIndex = function( a, v, b, s ) { + var _eq = function(o1, o2) { + if (o1 === o2) return true; + else if (typeof o1 == 'object' && typeof o2 == 'object') { + var same = true; + for(var propertyName in o1) { + if(!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for(var propertyName in o2) { + if(!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for( var i = +b || 0, l = a.length; i < l; i++ ) { + if( _eq(a[i], v)) return i; + } + return -1; + }; + + /** + * adds a listener for the specified event type. + */ + var _addListener = function(eventType, listener) { + var doOne = function(e, l) { + _addToList(listeners, e, l); + }; + if (typeof eventType == 'object' && eventType.length) { + for (var i = 0; i < eventType.length; i++) doOne(eventType[i], listener); + } else doOne(eventType, listener); + }; + + /** + * helper method to add an item to a list, creating the list if it does not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }; + + var _appendElement = function(canvas, parent) { + if (!parent) document.body.appendChild(canvas); + else jsPlumb.CurrentLibrary.appendElement(canvas, parent); + }; + + /** + * Handles the dragging of an element. + * @param element jQuery element + * @param ui UI object from current library's event system + */ + var _draw = function(element, ui) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints) { + var timestamp = '' + (new Date().getTime()); + _updateOffset(id, ui); + var myOffset = offsets[id]; + var myWH = sizes[id]; + // loop through endpoints for this element + for (var i = 0; i < endpoints.length; i++) { + var e = endpoints[i]; + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH, e); + e.paint(anchorLoc); + var l = e.connections; + for (var j = 0; j < l.length; j++) + l[j].paint(id, ui, false, timestamp); // ...and paint them. + } + } + }; + + /** + * helper function: the second argument is a function taking two args - the first is an id + * ui element from the current library (or list of them), and the second is the function + * to run. + */ + var _elementProxy = function(element, fn) { + var retVal = null; + // TODO this might be jquery specific. in fact it probably is. + if (typeof element == 'object' && element.length) { + retVal = []; + for (var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]); + var id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } + else { + var el = _getElementObject(element); + var id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + + return retVal; + }; + + /** + * fires an event of the given type. + */ + var _fireEvent = function(eventType, data) { + var l = listeners[eventType]; + if (l) { + for (var i in l) { + try { + l[i][eventType](data); + } + catch (e) { + + _log("while firing event [" + eventType + "]; listener failed like this: " + e); + } + } + } + }; + + var _log = function(msg) { + //if (console) console.log(msg); + }; + + var _logFnCall = function(fn, args) { + /*if (console) { + var c = args.callee.caller.toString(); + var i = c.indexOf("{"); + var msg = fn + ' : ' + c.substring(0, i); + console.log(msg); + }*/ + }; + + var cached = {}; + var __getElementObject = function(el) { + + if (typeof(el)=='string') { + var e = cached[el]; + if (!e) { + e = jsPlumb.CurrentLibrary.getElementObject(el); + cached[el] = e; + } + return e; + } + else { + return jsPlumb.CurrentLibrary.getElementObject(el); + } + }; + + + /** + * gets the named attribute from the given element (id or element object) + */ + var _getAttribute = function(el, attName) { + var ele = __getElementObject(el); + return jsPlumb.CurrentLibrary.getAttribute(ele, attName); + }; + + var _setAttribute = function(el, attName, attValue) { + var ele = __getElementObject(el); + jsPlumb.CurrentLibrary.setAttribute(ele, attName, attValue); + }; + + var _addClass = function(el, clazz) { + var ele = __getElementObject(el); + jsPlumb.CurrentLibrary.addClass(ele, clazz); + }; + + var _getElementObject = function(elId) { + return __getElementObject(elId); + }; + + var _getOffset = function(el) { + var ele = __getElementObject(el); + return jsPlumb.CurrentLibrary.getOffset(ele); + }; + + var _getSize = function(el) { + var ele = __getElementObject(el); + return jsPlumb.CurrentLibrary.getSize(ele); + }; + + /** + * gets an id for the given element, creating and setting one if necessary. + */ + var _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id) { + //check if fixed uuid parameter is given + if(arguments.length == 2) + id = uuid; + else + id = "_jsPlumb_" + new String((new Date()).getTime()); + _setAttribute(ele, "id", id); + } + return id; + }; + + /** + * gets an Endpoint by uuid. + */ + var _getEndpoint = function(uuid) { + return endpointsByUUID[uuid]; + }; + + /** + * inits a draggable if it's not already initialised. + * + * TODO: we need to hook in each library to this method. they need to be given the opportunity to + * wrap/insert lifecycle functions, because each library does different things. for instance, jQuery + * supports the notion of 'revert', which will clean up a dragged endpoint; MooTools does not. jQuery + * also supports 'opacity' and MooTools does not; jQuery supports z-index of the draggable; MooTools + * does not. i could go on. the point is...oh. initDraggable can do this. ok. + */ + var _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + // dragging + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend({}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + } + } + }; + + + /** + * helper to create a canvas. + * @param clazz optional class name for the canvas. + */ + var _newCanvas = function(clazz, parent, uuid) { + var canvas = document.createElement("canvas"); + _appendElement(canvas, parent); + canvas.style.position="absolute"; + if (clazz) { canvas.className=clazz; } + + // set an id. if no id on the element and if uuid was supplied it will be used, otherwise we'll create one. + _getId(canvas, uuid); + + if (ie) { + // for IE we have to set a big canvas size. actually you can override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + /** + * performs the given function operation on all the connections found for the given element + * id; this means we find all the endpoints for the given element, and then for each endpoint + * find the connectors connected to it. then we pass each connection in to the given + * function. + */ + var _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + for (var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }; + /** + * perform an operation on all elements. + */ + var _operationOnAll = function(func) { + for (var elId in endpointsByElement) { + _operation(elId, func); + } + }; + /** + * helper to remove an element from the DOM. + */ + var _removeElement = function(element, parent) { + if (element != null) { + if (!parent) { + try { document.body.removeChild(element); } + catch (e) { } + } else { + jsPlumb.CurrentLibrary.removeElement(element, parent); + } + } + }; + /** + * helper to remove a list of elements from the DOM. + */ + var _removeElements = function(elements, parent) { + for (var i in elements) + _removeElement(elements[i], parent); + }; + /** + * helper method to remove an item from a list. + */ + var _removeFromList = function(map, key, value) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete( l[i] ); + l.splice( i, 1 ); + return true; + } + } + return false; + }; + /** + * Sets whether or not the given element(s) should be draggable, regardless of what a particular + * plumb command may request. + * + * @param element May be a string, a jQuery elements, or a list of strings/jquery elements. + * @param draggable Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + var _helper = function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }; + + return _elementProxy(element, _helper); + }; + /** + * private method to do the business of hiding/showing. + * @param el either Id of the element in question or a jquery object for the element. + * @param state String specifying a value for the css 'display' property ('block' or 'none'). + */ + var _setVisible = function(el, state) { + var elId = _getAttribute(el, "id"); + var f = function(jpc) { + jpc.canvas.style.display = state; + }; + _operation(elId, f); + }; + /** + * toggles the draggable state of the given element(s). + * @param el either an id, or a jquery object, or a list of ids/jquery objects. + */ + var _toggleDraggable = function(el) { + var fn = function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }; + return _elementProxy(el, fn); + }; + /** + * private method to do the business of toggling hiding/showing. + * @param elId Id of the element in question + */ + var _toggleVisible = function(elId) { + var f = function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }; + + _operation(elId, f); + + //todo this should call _elementProxy, and pass in the _operation(elId, f) call as a function. cos _toggleDraggable does that. + }; + /** + * updates the offset and size for a given element, and stores the values. + * if 'offset' is not null we use that (it would have been passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we use the offset, outerWidth and outerHeight methods to get + * the current values. + */ + var _updateOffset = function(elId, offset, recalc) { + if (recalc || offset == null) { // if forced repaint or no offset available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + } else { + offsets[elId] = offset; + } + }; + + /** + * wraps one function with another, creating a placeholder for the wrapped function + * if it was null. this is used to wrap the various drag/drop event functions - to allow + * jsPlumb to be notified of important lifecycle events without imposing itself on the user's + * drag/drop functionality. + * TODO: determine whether or not we should support an error handler concept, if one of the functions fails. + */ + var _wrap = function(wrappedFunction, newFunction) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { r = newFunction.apply(this, arguments); } + catch (e) { + _log('jsPlumb function failed : ' + e); + } + try { wrappedFunction.apply(this, arguments); + } + catch (e) { + _log('wrapped function failed : ' + e); + } + return r; + }; + }; + + /* + Class: Anchor + Models a position relative to the origin of some element that an Endpoint can be located. + */ + /* + Function: Anchor + + Constructor for the Anchor class + + Parameters: + params - Anchor parameters. This should contain three values, and may optionally have an 'offsets' argument: + + - x : the x location of the anchor as a fraction of the total width. + - y : the y location of the anchor as a fraction of the total height. + - orientation : an [x,y] array indicating the general direction a connection from the anchor should go in. for more info on this, see the documentation, or the docs in jquery-jsPlumb-defaults-XXX.js for the default Anchors. + - offsets : an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. may be null. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; this.y = params.y || 0; + var orientation = params.orientation || [0,0]; + var lastTimestamp = null; + var lastReturnValue = null; + this.offsets = params.offsets || [0,0]; + //TODO: fix this properly. since Anchors are often static, this timestamping business + // does not work at all well. the timestamp should be inside the Endpoint, because they + // are _never_ static. the method that Connection uses to find an anchor location should + // be done through the Endpoint class, which can then deal with the timestamp. + // and, in fact, we should find out whether or not we even get a speed enhancement from doing + // this. + this.compute = function(xy, wh, element) { + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1]]; + var container = element? element.container : null; + var containerAdjustment = {left:0, top:0 }; + if (container != null) { + var eo = _getElementObject(container); + var o = _getOffset(eo); + var sl = jsPlumb.CurrentLibrary.getScrollLeft(eo); + var st = jsPlumb.CurrentLibrary.getScrollTop(eo); + containerAdjustment.left = o.left - sl; + containerAdjustment.top = o.top - st; + lastReturnValue[0] = lastReturnValue[0] - containerAdjustment.left; + lastReturnValue[1] = lastReturnValue[1] - containerAdjustment.top; + } + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y && this.offsets[0] == anchor.offsets[0] && this.offsets[1] == anchor.offsets[1] && o[0] == ao[0] && o[1] == ao[1]; + }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from its position relative to the anchor it is floating relative to. + * + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(_getElementObject(refCanvas)); + + // these are used to store the current relative position of our anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating anchor is hovering over another anchor. + var orientation = null; + + this.compute = function(xy, wh, txy, twh) { + // set these for the getOrientation method to use. + xDir = 0;//xy[0] < txy[0] ? -1 : xy[0] == txy[0] ? 0 : 1; + yDir = 0;//xy[1] < txy[1] ? -1 : xy[1] == txy[1] ? 0 : 1; + return [xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other anchor: if it declares zero for some direction, we declare zero too. + // this might not be the most awesome. perhaps we can come up with a better way. it's just so that the line we draw looks + // like it makes sense. maybe this wont make sense. + return [Math.abs(o[0]) * xDir * -1, Math.abs(o[1]) * yDir * -1]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering over another anchor; + * we want to assume that anchor's orientation for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no longer hovering + * over another anchor; we should resume calculating orientation as we normally do. + */ + this.out = function() { + orientation = null; + }; + }; + + // ************** connection + // **************************************** + /** + * allowed params: + * source: source element (string or a jQuery element) (required) + * target: target element (string or a jQuery element) (required) + * + * anchors: optional array of anchor placements. defaults to BottomCenter for source + * and TopCenter for target. + */ + var Connection = function(params) { + + // ************** get the source and target and register the connection. ******************* + var self = this; + var id = new String('_jsplumb_c_' + (new Date()).getTime()); + this.getId = function() { return id; }; + this.container = params.container || _currentInstance.Defaults.Container; // may be null; we will append to the body if so. + // get source and target as jQuery objects + this.source = _getElementObject(params.source); + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. + if (params.sourceEndpoint) + this.source = params.sourceEndpoint.getElement(); + if (params.targetEndpoint) + this.target = params.targetEndpoint.getElement(); + this.sourceId = _getAttribute(this.source, "id"); + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + this.scope = params.scope; // scope may have been passed in to the connect call. + // if it wasn't, we will pull it from the source endpoint, + // after having initialised the endpoints. + // init endpoints + this.endpoints = []; + this.endpointStyles = []; + var prepareEndpoint = function(existing, index, params, element) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } + else { + if(!params.endpoints) params.endpoints = [null,null]; + var ep = params.endpoints[index] || params.endpoint || _currentInstance.Defaults.Endpoints[index] || jsPlumb.Defaults.Endpoints[index] || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || new jsPlumb.Endpoints.Dot(); + if (!params.endpointStyles) params.endpointStyles = [null,null]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + var a = params.anchors ? params.anchors[index] : _currentInstance.Defaults.Anchors[index] || jsPlumb.Defaults.Anchors[index] || _currentInstance.Defaults.Anchor || jsPlumb.Defaults.Anchor || jsPlumb.Anchors.BottomCenter; + var u = params.uuids ? params.uuids[index] : null; + if(params.uuids) + var e = new Endpoint({style:es, endpoint:ep, connections:[self], uuid:u, anchor:a, source:element, container:self.container }); + else + var e = new Endpoint({style:es, endpoint:ep, connections:[self], anchor:a, source:element, container:self.container }); + + self.endpoints[index] = e; + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || _currentInstance.Defaults.Connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + this.paintStyle = this.endpoints[0].connectorStyle || this.endpoints[1].connectorStyle || params.paintStyle || _currentInstance.Defaults.PaintStyle || jsPlumb.Defaults.PaintStyle; + + _updateOffset(this.sourceId); + _updateOffset(this.targetId); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var anchorLoc = this.endpoints[0].anchor.compute([myOffset.left, myOffset.top], myWH, this.endpoints[0]); + this.endpoints[0].paint(anchorLoc); + myOffset = offsets[this.targetId]; myWH = sizes[this.targetId]; + anchorLoc = this.endpoints[1].anchor.compute([myOffset.left, myOffset.top], myWH, this.endpoints[1]); + this.endpoints[1].paint(anchorLoc); + + // *************** create canvas on which the connection will be drawn ************ + var canvas = _newCanvas(jsPlumb.connectorClass, self.container); + this.canvas = canvas; + + /** + * paints the connection. + * @param elId Id of the element that is in motion + * @param ui current library's event system ui object (present if we came from a drag to get here) + * @param recalc whether or not to recalculate element sizes. this is true if a repaint caused this to be painted. + */ + this.paint = function(elId, ui, recalc, timestamp) { + + if (log) log.debug("Painting Connection; element in motion is " + elId + "; ui is [" + ui + "]; recalc is [" + recalc + "]"); + + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = !(elId == this.sourceId); + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + if (this.canvas.getContext) { + //if (recalc) { + _updateOffset(elId, ui, recalc); + _updateOffset(tId); // update the target if this is a forced repaint. otherwise, only the source has been moved. + //} + + var myOffset = offsets[elId]; + var otherOffset = offsets[tId]; + var myWH = sizes[elId]; + var otherWH = sizes[tId]; + + var ctx = canvas.getContext('2d'); + //TODO: why are these calculated again? they were calculated in the _draw function. + var sAnchorP = this.endpoints[sIdx].anchor.compute([myOffset.left, myOffset.top], myWH, this.endpoints[sIdx]); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.compute([otherOffset.left, otherOffset.top], otherWH, this.endpoints[tIdx]); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, this.paintStyle.lineWidth); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + jsPlumb.extend(ctx, this.paintStyle); + + if (this.paintStyle.gradient && !ie) { + var g = swap ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + for (var i = 0; i < this.paintStyle.gradient.stops.length; i++) + g.addColorStop(this.paintStyle.gradient.stops[i][0],this.paintStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + + this.connector.paint(dim, ctx); + } + }; + + //TODO: should this take a timestamp? probably. it reduces the amount of time + // spent figuring out anchor locations. + this.repaint = function() { + this.paint(this.sourceId, null, true); + }; + + _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + }; + + /* + Class: Endpoint + Models an endpoint. Can have one to N connections emanating from it (although how to handle that in the UI is + a very good question). also has a Canvas and paint style. + */ + + /* + Function: Endpoint + This is the Endpoint class constructor. + Parameters: + anchor - anchor for the endpoint, of type jsPlumb.Anchor. may be null. + endpoint - endpoint object, of type jsPlumb.Endpoint. may be null. + style - endpoint style, a js object. may be null. + source - element the endpoint is attached to, of type jquery object. Required. + canvas - canvas element to use. may be, and most often is, null. + connections - optional list of connections to configure the endpoint with. + isSource - boolean. indicates the endpoint can act as a source of new connections. optional. + dragOptions - if isSource is set to true, you can supply arguments for the jquery draggable method. optional. + connectionStyle - if isSource is set to true, this is the paint style for connections from this endpoint. optional. + connector - optional connector type to use. + isTarget - boolean. indicates the endpoint can act as a target of new connections. optional. + dropOptions - if isTarget is set to true, you can supply arguments for the jquery droppable method. optional. + reattach - optional boolean that determines whether or not the connections reattach after they + have been dragged off an endpoint and left floating. defaults to false: connections + dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + params = params || {}; + // make a copy. then we can use the wrapper function. + jsPlumb.extend({}, params); + var self = this; + var id = new String('_jsplumb_e_' + (new Date()).getTime()); + this.getId = function() { return id; }; + self.anchor = params.anchor || jsPlumb.Anchors.TopCenter; + var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot(); + var _style = params.style || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + this.connectorStyle = params.connectorStyle; + this.connector = params.connector; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + var _element = params.source; + var _uuid = params.uuid; + if (_uuid) endpointsByUUID[_uuid] = self; + this.container = params.container || jsPlumb.Defaults.Container; + var _elementId = _getAttribute(_element, "id"); + var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of. + this.canvas = params.canvas || _newCanvas(jsPlumb.endpointClass, this.container, params.uuid); + this.connections = params.connections || []; + this.scope = params.scope || DEFAULT_SCOPE; + var _reattach = params.reattach || false; + var floatingEndpoint = null; + var inPlaceCopy = null; + this.dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.addConnection = function(connection) { + self.connections.push(connection); + }; + this.detach = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + self.connections.splice(idx, 1); + t.detach(connection); + } + }; + + /** + * detaches all connections this Endpoint has + */ + this.detachAll = function() { + while(self.connections.length > 0) { + self.detach(self.connections[0]); + } + }; + + /** + * removes any connections from this Endpoint that are + * connected to the given target endpoint. + * @param targetEndpoint + */ + this.detachFrom = function(targetEndpoint) { + var c = []; + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for (var i = 0; i < c.length; i++) { + targetEndpoint.detach(c[i]); + self.detach(c[i]); + } + }; + + /** + * returns the DOM element this Endpoint is attached to. + * @returns + */ + this.getElement = function() { return _element; }; + + /** + * returns the UUID for this Endpoint, if there is one. + * @returns + */ + this.getUuid= function() { return _uuid; }; + + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + var e = new Endpoint({anchor:self.anchor, source:_element, style:_style, endpoint:_endpoint}); + return e; + }; + + + /** + * returns whether or not this endpoint is connected to the given endpoint. + * @param endpoint Endpoint to test. + * @since 1.1.1 + * + * todo: needs testing. will this work if the endpoint passed in is the source? + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + * @returns {Boolean} + */ + this.isFloating = function() { return floatingEndpoint != null; }; + /** + * first pass at default ConnectorSelector: returns the first connection, if we have any. + * modified a little, 5/10/2010: now it only returns a connector if we have not got equal to or more than _maxConnector connectors + * attached. otherwise it is assumed a new connector is ok. but note with this setup we can't select any other connection than the first + * one. what if this could return a list? that implies everything should work with a list - dragging etc. could be nasty. could also + * be cool. + */ + var connectorSelector = function() { + //return self.connections.length == 0 || self.connections.length < _maxConnections ? null : self.connections[0]; + if (self.connections.length < _maxConnections) return null; + //else if (self.connections.length == _maxConnections) return false; + else return self.connections[0]; + }; + + /** + * @returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); }; + + /** + * sets whether or not connnctions can be dragged from this Endpoint once it is full. you would use + * this in a UI in which you're going to provide some other way of breaking connections, if you need + * to break them at all. this property is by default true; use it in conjunction with the 'reattach' + * option on a connect call. + */ + this.setDragAllowedWhenFull = function(allowed) { + self.dragAllowedWhenFull = allowed; + }; + + /** + * a deep equals check. everything must match, including the anchor, styles, everything. + * TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor) && + true; + }; + + /** + * paints the Endpoint, recalculating offset and anchor positions if necessary. + */ + this.paint = function(anchorPoint, connectorPaintStyle, canvas) { + + if (log) log.debug("Painting Endpoint with elementId [" + _elementId + "]; anchorPoint is [" + anchorPoint + "]"); + + if (anchorPoint == null) { + // do we always want to force a repaint here? i dont think so! + var xy = offsets[_elementId]; + var wh = sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset(_elementId); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + anchorPoint = self.anchor.compute([xy.left, xy.top], wh, self); + } + _endpoint.paint(anchorPoint, self.anchor.getOrientation(), canvas || self.canvas, _style, connectorPaintStyle || _style); + }; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + + // is this a connection source? we make it draggable and have the drag listener + // maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + + // first the question is, how many connections are on this endpoint? if it's only one, then excellent. otherwise we will either need a way + // to select one connection from the list, or drag them all. if we had a pluggable 'ConnectorSelector' interface we could probably + // provide a way for people to implement their own UI components to do the connector selection. the question in that particular case would be how much + // information the interface needs from jsPlumb at execution time. if, however, we leave out the connector selection, and drag them all, + // that wouldn't be too hard to organise. perhaps that behaviour would be on a switch for the endpoint, or perhaps the ConnectorSelector + // interface returns a List, with the default implementation just returning everything. i think i like that. + // + // let's say for now that there is just one endpoint, cos we need to get this going before we can consider a list of them anyway. + // the major difference between that case and the case of a new endpoint is actually quite small - it's a question of where the + // Connection comes from. for a new one, we create a new one. obviously. otherwise we just get the jpc from the Endpoint + // (remember we're only assuming one connection right now). so all of the UI stuff we do to create the floating endpoint etc + // will still be valid, but when we stop dragging, we'll have to do something different. if we stop with a valid drop i think it will + // be the same process. but if we stop with an invalid drop we have to reset the Connection to how it was when we got it. + var start = function() { + + jpc = connectorSelector(); + if (self.isFull() && !self.dragAllowedWhenFull) return false; + + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + var nE = _getElementObject(n); + _appendElement(n); // + // create and assign an id, and initialize the offset. + //TODO can't this be replaced with a _getId call? + var id = "" + new String(new Date().getTime()); + _setAttribute(nE, "id", id); + _updateOffset(id); + // store the id of the dragging div and the source element. the drop function + // will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor({reference:self.anchor, referenceCanvas:self.canvas}); + floatingEndpoint = new Endpoint({ style:{fillStyle:'rgba(0,0,0,0)'}, endpoint:_endpoint, anchor:floatingAnchor, source:nE }); + + if (jpc == null) { + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = new Connection({ + sourceEndpoint:self, + targetEndpoint:floatingEndpoint, + source:_getElementObject(_element), + target:_getElementObject(n), + anchors:[self.anchor, floatingAnchor], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + connector: params.connector + }); + } else { + existingJpc = true; + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; + // probably we should remove the connection? and add it back if the user + // does not drop it somewhere proper. + self.removeConnection(jpc); + if (anchorIdx == 0){ + existingJpcParams = [jpc.source, jpc.sourceId]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + }else { + existingJpcParams = [jpc.target, jpc.targetId]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // todo unregister on stop + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + }; + + var dragOptions = params.dragOptions || { }; + + var defaultOpts = jsPlumb.extend({}, jsPlumb.CurrentLibrary.defaultDragOptions); + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + var startEvent = jsPlumb.CurrentLibrary.dragEvents['start']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements([n, floatingEndpoint.canvas]); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + if (_reattach) { + jpc.floatingAnchorIndex = null; + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } + else { + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeElement(jpc.canvas); + _removeFromList(connectionsByScope, jpc.scope, jpc); + _fireEvent("jsPlumbConnectionDetached", { + source:jpc.source, + target:jpc.target, + sourceId:jpc.sourceId, + targetId:jpc.targetId, + sourceEndpoint:jpc.endpoints[0], + targetEndpoint:jpc.endpoints[1] + }); + } + } else { + _removeElement(jpc.canvas); + //if (jpc.endpoints[1]) alert("target set"); + self.removeConnection(jpc); + } + } + jpc = null; + delete floatingEndpoint; + delete inPlaceCopy; + self.paint(); + } + ); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + }; + + // connector target + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend({}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + dropOptions[dropEvent]= _wrap(dropOptions[dropEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable), "dragId"); + var elId = _getAttribute(_getElementObject(draggable),"elId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + var oidx = idx == 0 ? 1 : 0; + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + //todo test that the target is not full. + // remove this jpc from the current endpoint + jpc.endpoints[idx].removeConnection(jpc); + if (jpc.suspendedEndpoint) + jpc.suspendedEndpoint.removeConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + // add the jpc to the other endpoint too. + jpc.endpoints[oidx].addConnection(jpc); + + _addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + jsPlumb.repaint(elId); + _fireEvent("jsPlumbConnection", { + source:jpc.source, + target:jpc.target, + sourceId:jpc.sourceId, + targetId:jpc.targetId, + sourceEndpoint:jpc.endpoints[0], + targetEndpoint:jpc.endpoints[1] + }); + } + // else there must be some cleanup required. + + delete floatingConnections[id]; + }); + + dropOptions[overEvent]= _wrap(dropOptions[overEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + }); + + jsPlumb.CurrentLibrary.initDroppable(_getElementObject(self.canvas), dropOptions); + } + + return self; + }; + + /* + Property: Defaults + These are the default settings for jsPlumb, that is what will be used if you do not supply specific pieces of information + to the various API calls. A convenient way to implement your own look and feel can be to override these defaults by including a script + somewhere after the jsPlumb include, but before you make any calls to jsPlumb, for instance in this example we set the PaintStyle to be + a blue line of 27 pixels: + > jsPlumb.Defaults.PaintStyle = { lineWidth:27, strokeStyle:'blue' } + */ + this.Defaults = { + Anchor : null, + Anchors : [ null, null ], + Connector : null, + Container : null, + DragOptions: { }, + DropOptions: { }, + Endpoint : null, + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : null }, + EndpointStyles : [ null, null ], + MaxConnections : null, + PaintStyle : { lineWidth : 10, strokeStyle : 'red' }, + Scope : "_jsPlumb_DefaultScope" + }; + + /* + Property: connectorClass + The CSS class to set on Connection canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = '_jsPlumb_connector'; + + /* + Property: endpointClass + The CSS class to set on Endpoint canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = '_jsPlumb_endpoint'; + + /* + Property: Anchors + Default jsPlumb Anchors. These are supplied in the file jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form .jsPlumb-all-x.x.x.js. You can provide your own Anchors by supplying them in a script that is loaded after jsPlumb, for instance: + > jsPlumb.Anchors.MyAnchor = { ....anchor code here. see the documentation. } + */ + this.Anchors = {}; + + /* + Property: Connectors + Default jsPlumb Connectors. These are supplied in the file jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form .jsPlumb-all-x.x.x.js. You can provide your own Connectors by supplying them in a script that is loaded after jsPlumb, for instance: + > jsPlumb.Connectors.MyConnector = { ....connector code here. see the documentation. } + */ + this.Connectors = {}; + + /* + Property: Endpoints + Default jsPlumb Endpoints. These are supplied in the file jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form .jsPlumb-all-x.x.x.js. You can provide your own Endpoints by supplying them in a script that is loaded after jsPlumb, for instance: + > jsPlumb.Endpoints.MyEndpoint = { ....endpoint code here. see the documentation. } + */ + this.Endpoints = {}; + + /* + Function: addEndpoint + Adds an Endpoint to a given element. + Parameters: + target - Element to add the endpoint to. either an element id, or a selector representing some element. + params - Object containing Endpoint options (more info required) + Returns: + The newly created Endpoint. + See Also: + + */ + this.addEndpoint = function(target, params) { + params = jsPlumb.extend({}, params); + var el = _getElementObject(target); + var id = _getAttribute(el,"id"); + params.source = el; + _updateOffset(id); + var e = new Endpoint(params); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id]; + var myWH = sizes[id]; + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH, e); + e.paint(anchorLoc); + return e; + }; + + /* + Function: addEndpoint + Adds a list of Endpoints to a given element. + Parameters: + target - element to add the endpoint to. either an element id, or a selector representing some element. + endpoints - List of objects containing Endpoint options. one Endpoint is created for each entry in this list. + Returns: + List of newly created Endpoints, one for each entry in the 'endpoints' argument. + See Also: + + */ + this.addEndpoints = function(target, endpoints) { + var results = []; + for (var i = 0; i < endpoints.length; i++) { + results.push(jsPlumb.addEndpoint(target, endpoints[i])); + } + return results; + }; + + /* + Function: animate + Wrapper around supporting library's animate function; injects a call to jsPlumb in the 'step' function (creating it if necessary). + This only supports the two-arg version of the animate call in jQuery - the one that takes an 'options' object as the second arg. + MooTools has only one method - a two arg one. Which is handy. + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the standard jQuery animate call. + options - The 'options' argument you want passed to the standard jQuery animate call. + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el); + var id = _getAttribute(el,"id"); + //TODO this is not agnostic yet. + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() + { + _currentInstance.repaint(id); + }); + + // you know, probably onComplete should repaint too. that will help keep up + // with fast animations. + options[completeFunction] = _wrap(options[completeFunction], function() + { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /** + * clears the cache used to lookup elements by their id. if you remove any elements + * from the DOM you should call this to ensure that if you add them back in jsPlumb does not + * have a stale handle. + */ + this.clearCache = function() { + delete cached; + cached = {}; + }; + + /* + Function: connect + Establishes a connection between two elements. + Parameters: + params - Object containing setup for the connection. see documentation. + Returns: + The newly created Connection. + */ + this.connect = function(params) { + var _p = jsPlumb.extend({}, params); + // test for endpoint uuids to connect + if (params.uuids) { + var _resolveByUuid = function(idx) { + var e = _getEndpoint(params.uuids[idx]); + if (!e) throw ("Endpoint with UUID " + params.uuids[idx] + " not found."); + return e; + }; + _p.sourceEndpoint = _resolveByUuid(0); + _p.targetEndpoint = _resolveByUuid(1); + } + + // now ensure that if we do have Endpoints already, they're not full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log("could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log("could not add connection; target endpoint is full"); + return; + } + + var jpc = new Connection(_p); + // add to list of connections (by scope). + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + _fireEvent("jsPlumbConnection", { + source:jpc.source, + target:jpc.target, + sourceId:jpc.sourceId, + targetId:jpc.targetId, + sourceEndpoint:jpc.endpoints[0], + targetEndpoint:jpc.endpoints[1] + }); + // force a paint + _draw(jpc.source); + + return jpc; + }; + + var fireDetachEvent = function(jpc) { + _fireEvent("jsPlumbConnectionDetached", { + source:jpc.source, + target:jpc.target, + sourceId:jpc.sourceId, + targetId:jpc.targetId, + sourceEndpoint:jpc.endpoints[0], + targetEndpoint:jpc.endpoints[1] + }); + }; + + /* + Function: detach + Removes a connection. + Parameters: + sourceId - Id of the first element in the connection. A String. + targetId - iI of the second element in the connection. A String. + Returns: + true if successful, false if not. + */ + this.detach = function(sourceId, targetId) { + if (arguments.length == 2) { + var f = function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + fireDetachEvent(jpc); + + return true; + } + + }; + + // todo: how to cleanup the actual storage? a third arg to _operation? + _operation(sourceId, f); + } + // this is the new version of the method, taking a JS object like the connect method does. + else if (arguments.length == 1) { + var _p = jsPlumb.extend({}, sourceId); // a backwards compatibility hack: sourceId should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1])); + } + else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } + else { + sourceId = _getId(_p.source); + targetId = _getId(_p.target); + var f = function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + fireDetachEvent(jpc); + + return true; + } + + }; + + // todo: how to cleanup the actual storage? a third arg to _operation? + _operation(sourceId, f); + } + + // fire an event + /*_fireEvent("jsPlumbDetach", { + source:jpc.source, + target:jpc.target, + sourceId:jpc.sourceId, + targetId:jpc.targetId, + sourceEndpoint:jpc.endpoints[0], + targetEndpoint:jpc.endpoints[1] + });*/ + /*// force a paint + _draw(jpc.source); + + return jpc;*/ + } + }; + + /* + Function: detachAll + Removes all an element's connections. + Parameters: + el - either the id of the element, or a selector for the element. + Returns: + void + */ + this.detachAll = function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + var c = endpoints[i].connections.length; + if (c > 0) { + for (var j = 0; j < c; j++) { + var jpc = endpoints[i].connections[0]; + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + fireDetachEvent(jpc); + } + } + } + } + }; + + /* + Function: detachEverything + Remove all Connections from all elements, but leaves Endpoints in place. + Returns: + void + See Also: + + */ + this.detachEverything = function() { + for(var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + var c = endpoints[i].connections.length; + if (c > 0) { + for (var j = 0; j < c; j++) { + var jpc = endpoints[i].connections[0]; + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + fireDetachEvent(jpc); + } + } + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + /* + Function: draggable + initialises the draggability of some element or elements. + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for (var i = 0; i < el.length; i++) + { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) + _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + Parameters: + o1 - object to extend + o2 - object to extend o1 with + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + Function: getConnections + Gets all or a subset of connections currently managed by this jsPlumb instance. + Parameters: + options - a JS object that holds options defining what sort of connections you're looking for. Valid values are: + scope this may be a String or a list of Strings. jsPlumb will only return Connections whose scope matches what this option + defines. If you omit it, you will be given Connections of every scope. + source may be a string or a list of strings; constraints results to have only Connections whose source is this object. + target may be a string or a list of strings; constraints results to have only Connections whose target is this object. + + The return value is a dictionary in this format: + + { + 'scope1': [ + {sourceId:'window1', targetId:'window2'}, + {sourceId:'window3', targetId:'window4'}, + {sourceId:'window1', targetId:'window3'} + ], + 'scope2': [ + {sourceId:'window1', targetId:'window3'} + ] + } + + */ + this.getConnections = function(options) { + var r = {}; + options = options || {}; + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') r.push(input); + else r = input; + } + return r; + }; + var scopes = prepareList(options.scope); + var sources = prepareList(options.source); + var targets = prepareList(options.target); + var filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }; + for (var i in connectionsByScope) { + if (filter(scopes, i)) { + r[i] = []; + for (var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + //r[i].push({sourceId:c.sourceId, targetId:c.targetId}); + r[i].push({ + sourceId:c.sourceId, + targetId:c.targetId, + source:c.source, + target:c.target, + sourceEndpoint:c.endpoints[0], + targetEndpoint:c.endpoints[1] + }); + } + } + } + return r; + }; + + /* + Function: getDefaultScope + Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a scope to an endpoint + or connection allows you to support different types of connections in the same UI. but if you're only interested in one type of connection, + you don't need to supply a scope. this method will probably be used by very few people; it's good for testing though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + gets an Endpoint by UUID + Parameters: + uuid - the UUID for the Endpoint + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /* + * gets an element's id, creating one if necessary. really only exposed for the lib-specific + * functionality to access; would be better to pass the current instance into the lib-specific + * code (even though this is a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + + /* + Function: hide + Sets an element's connections to be hidden. + Parameters: + el - either the id of the element, or a selector for the element. + Returns: + void + */ + this.hide = function(el) { + _setVisible(el, "none"); + }; + + /* + Function: makeAnchor + Creates an anchor with the given params. + Parameters: + x - the x location of the anchor as a fraction of the total width. + y - the y location of the anchor as a fraction of the total height. + xOrientation - value indicating the general direction a connection from the anchor should go in, in the x direction. + yOrientation - value indicating the general direction a connection from the anchor should go in, in the y direction. + xOffset - a fixed offset that should be applied in the x direction that should be applied after the x position has been figured out. optional. defaults to 0. + yOffset - a fixed offset that should be applied in the y direction that should be applied after the y position has been figured out. optional. defaults to 0. + Returns: + The newly created Anchor. + */ + this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards compatibility if we are given only one value we assume it's a call in the old form. + var params = {}; + if (arguments.length == 1) jsPlumb.extend(params, x); + else { + params = {x:x, y:y}; + if (arguments.length >= 4) { + params.orientation = [arguments[2], arguments[3]]; + } + if (arguments.length == 6) params.offsets = [arguments[4], arguments[5]]; + } + var a = new Anchor(params); + a.clone = function() { return new Anchor(params); }; + return a; + }; + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + Parameters: + el - either the id of the element or a selector representing the element. + Returns: + void + See Also: + + */ + this.repaint = function(el) { + + var _processElement = function(el) { + var ele = _getElementObject(el); + _draw(ele); + }; + + // TODO: support a jQuery result object too! + + // support both lists... + if (typeof el =='object') { + for (var i = 0; i < el.length; i++) + _processElement(el[i]); + } // ...and single strings. + else _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + Returns: + void + See Also: + + */ + this.repaintEverything = function() { + for (var elId in endpointsByElement) { + _draw(_getElementObject(elId)); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + Parameters: + el - either an element id, or a selector for an element. + Returns: + void + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"); + // first remove all Connections. + jsPlumb.detachAll(elId); + var ebe = endpointsByElement[elId]; + for (var i in ebe) { + _removeElement(ebe[i].canvas); + } + endpointsByElement[elId] = []; + }; + + /* + Function: removeEveryEndpoint + Removes every Endpoint in this instance of jsPlumb. + Returns: + void + See Also: + + */ + this.removeEveryEndpoint = function() { + for(var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + _removeElement(endpoints[i].canvas, endpoints[i].container); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + }; + + /* + Function: removeEndpoint + Removes the given Endpoint from the given element. + Parameters: + el - either an element id, or a selector for an element. + endpoint - Endpoint to remove. this is an Endpoint object, such as would have been returned from a call to addEndpoint. + Returns: + void + See Also: + + */ + this.removeEndpoint = function(el, endpoint) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + if (ebe) { + if(_removeFromList(endpointsByElement, elId, endpoint)) + _removeElement(endpoint.canvas); + } + }; + + /* + Function:reset + removes all endpoints and connections and clears the element cache. + */ + this.reset = function() { + this.detachEverything(); + this.removeEveryEndpoint(); + this.clearCache(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + Parameters: + value - whether or not to automatically repaint when the window is resized. + Returns: + void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + Function: setDefaultNewCanvasSize + Sets the default size jsPlumb will use for a new canvas (we create a square canvas so one value is all that is required). + This is a hack for IE, because ExplorerCanvas seems to need for a canvas to be larger than what you are going to draw on + it at initialisation time. The default value of this is 1200 pixels, which is quite large, but if for some reason you're + drawing connectors that are bigger, you should adjust this value appropriately. + Parameters: + size - The default size to use. jsPlumb will use a square canvas so you need only supply one value. + Returns: + void + */ + this.setDefaultNewCanvasSize = function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }; + + /* + Function: setDefaultScope + Sets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a scope to an endpoint + or connection allows you to support different types of connections in the same UI. but if you're only interested in one type of connection, + you don't need to supply a scope. this method will probably be used by very few people; it just instructs jsPlumb to use a different key + for the default scope. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + Function: setDraggable + Sets whether or not a given element is draggable, regardless of what any plumb command may request. + Parameters: + el - either the id for the element, or a selector representing the element. + Returns: + void + */ + this.setDraggable = _setDraggable; + + /* + Function: setDraggableByDefault + Sets whether or not elements are draggable by default. Default for this is true. + Parameters: + draggable - value to set + Returns: + void + */ + this.setDraggableByDefault = function(draggable) { + _draggableByDefault = draggable; + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + Function: setRepaintFunction + Sets the function to fire when the window size has changed and a repaint was fired. + Parameters: + f - Function to execute. + Returns: + void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + Function: show + Sets an element's connections to be visible. + Parameters: + el - either the id of the element, or a selector for the element. + Returns: + void + */ + this.show = function(el) { + _setVisible(el, "block"); + }; + + /* + Function: sizeCanvas + Helper to size a canvas. You would typically use this when writing your own Connector or Endpoint implementation. + Parameters: + x - [int] x position for the Canvas origin + y - [int] y position for the Canvas origin + w - [int] width of the canvas + h - [int] height of the canvas + Returns: + void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; canvas.height = h; + canvas.style.width = w + "px"; canvas.width = w; + canvas.style.left = x + "px"; canvas.style.top = y + "px"; + // _currentInstance.CurrentLibrary.setPosition(canvas, x, y); + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards compatibility + */ + this.toggle = _toggleVisible; + + /* + Function: toggleVisible + Toggles visibility of an element's connections. + Parameters: + el - either the element's id, or a selector representing the element. + Returns: + void, but should be updated to return the current state + */ + //TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + Function: toggleDraggable + Toggles draggability (sic) of an element's connections. + Parameters: + el - either the element's id, or a selector representing the element. + Returns: + The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + Function: unload + Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element + Returns: + void + */ + this.unload = function() { + delete endpointsByElement; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + }; + + /* + Function: wrap + Helper method to wrap an existing function with one of your own. This is used by the various + implementations to wrap event callbacks for drag/drop etc; it allows jsPlumb to be + transparent in its handling of these things. If a user supplies their own event callback, + for anything, it will always be called. + Parameters: + */ + this.wrap = _wrap; + this.addListener = _addListener; + + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(); + if (_defaults) + jsPlumb.extend(j.Defaults, _defaults); + return j; + }; + +})(); + +/* +* jsPlumb-defaults-1.2.2 +* +* This script contains the default Anchors, Endpoints and Connectors for jsPlumb. It should be used with jsPlumb 1.1.0 and above; +* prior to version 1.1.0 of jsPlumb the defaults were included inside the main script. +* +* Version 1.1.1 of this script adds the Triangle Endpoint, written by __________ and featured on this demo: +* +* http://www.mintdesign.ru/blog/javascript-jsplumb-making-graph +* http://www.mintdesign.ru/site/samples/jsplumb/jsPlumb-graph-sample.htm +* +* NOTE: for production usage you should use jquery.jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, +* in a minified file. +* +* Dual licensed under MIT and GPL2. +*/ + +(function() { + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * getOrientation : function() { return [ox, oy]; } + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + jsPlumb.Anchors.TopCenter = jsPlumb.makeAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors.BottomCenter = jsPlumb.makeAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors.LeftMiddle = jsPlumb.makeAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors.RightMiddle = jsPlumb.makeAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors.Center = jsPlumb.makeAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors.TopRight = jsPlumb.makeAnchor(1, 0, 0,-1); + jsPlumb.Anchors.BottomRight = jsPlumb.makeAnchor(1, 1, 0, 1); + jsPlumb.Anchors.TopLeft = jsPlumb.makeAnchor(0, 0, 0, -1); + jsPlumb.Anchors.BottomLeft = jsPlumb.makeAnchor(0, 1, 0, 1); + + + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (w < 2 * lineWidth) { + w = 2 * lineWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - lineWidth; + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < 2 * lineWidth) { + // minimum size is 2 * line Width + h = 2 * lineWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - lineWidth; + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + var sx = sourcePos[0] < targetPos[0] ? w-xo : xo; + var sy = sourcePos[1] < targetPos[1] ? h-yo : yo; + var tx = sourcePos[0] < targetPos[0] ? xo : w-xo; + var ty = sourcePos[1] < targetPos[1] ? yo : h-yo; + var retVal = [ x, y, w, h, sx, sy, tx, ty ]; + + return retVal; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + }; + + /** + * This Connector draws a Bezier curve with two control points. + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + jsPlumb.Connectors.Bezier = function(curviness) { + + var self = this; + this.majorAnchor = curviness || 150; + this.minorAnchor = 10; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) + { + lineWidth = lineWidth || 0; + var w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth, h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + var canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2), canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + var sx = sourcePos[0] < targetPos[0] ? w - (lineWidth/2): (lineWidth/2), sy = sourcePos[1] < targetPos[1] ? h-(lineWidth/2) : (lineWidth/2); + var tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : w-(lineWidth/2), ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : h-(lineWidth/2); + var CP = self._findControlPoint([sx,sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + var CP2 = self._findControlPoint([tx,ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(sx,tx); var minx2 = Math.min(CP[0], CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(sx,tx); var maxx2 = Math.max(CP[0], CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > w) w = maxx; + if (minx < 0) { + canvasX += minx; var ox = Math.abs(minx); + w += ox; CP[0] += ox; sx += ox; tx +=ox; CP2[0] += ox; + } + + var miny1 = Math.min(sy,ty); var miny2 = Math.min(CP[1], CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(sy,ty); var maxy2 = Math.max(CP[1], CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > h) h = maxy; + if (miny < 0) { + canvasY += miny; var oy = Math.abs(miny); + h += oy; CP[1] += oy; sy += oy; ty +=oy; CP2[1] += oy; + } + + // return [ canvasx, canvasy, canvasWidth, canvasHeight, + // sourceX, sourceY, targetX, targetY, + // controlPoint1_X, controlPoint1_Y, controlPoint2_X, controlPoint2_Y + return [canvasX, canvasY, w, h, sx, sy, tx, ty, CP[0], CP[1], CP2[0], CP2[1] ]; + }; + + this.paint = function(d, ctx) { + + /*var img = new Image(); + img.src = "../img/pattern.jpg"; + ctx.fillStyle = ctx.createPattern(img, 'repeat-y');*/ + + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + //ctx.fill(); + } + }; + + + /** + * Types of endpoint UIs. we supply four - a circle of default radius 10px, a rectangle of + * default size 20x20, an image (with no default), and a Triangle, of default size 15. + * you can supply others of these if you want to - see the documentation for a howto. + */ + + /** + * a round endpoint, with default radius 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + } + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * A Rectangular endpoint, with default size 20x20. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + //todo: the fillStyle needs some thought. we want to support a few options: + // 1. nothing supplied; use the stroke color or the default if no stroke color. + // 2. a fill color supplied - use it + // 3. a gradient supplied - use it + // 4. setting the endpoint to the same color as the bg of the element it is attached to. + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + jsPlumb.Endpoints.Triangle = function(params) { + + params = params || { width:15, height:15 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - width/2; + var y = anchorPoint[1] - height/2; + + jsPlumb.sizeCanvas(canvas, x, y, width, height); + + var ctx = canvas.getContext('2d'); + var + offsetX = 0, + offsetY = 0, + angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = endpointStyle.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + ctx.fill(); + + }; + }; + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' property in the params object.. + */ + jsPlumb.Endpoints.Image = function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + }; +})(); + +/* + * jquery.jsPlumb 1.2.2 + * + * jQuery specific functionality for jsPlumb. + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + * NOTE: for production usage you should use jquery.jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, + * in a minified file. + * + * Dual licensed under MIT and GPL2. + * + */ + +(function($){ + /** + * plumbs the results of the selector to some target, using the given options if supplied, + * or the defaults otherwise. + */ + $.fn.plumb = function(options) { + var options = $.extend({}, options); + + return this.each(function() + { + var params = $.extend({source:$(this)}, options); + jsPlumb.connect(params); + }); + }; + + /** + * detaches the results of the selector from the given target or list of targets - 'target' + * may be a String or a List. + */ + $.fn.detach = function(target) { + return this.each(function() { + if (target) { + var id = $(this).attr("id"); + if (typeof target == 'string') target = [target]; + for (var i = 0; i < target.length; i++) + jsPlumb.detach(id, target[i]); + } + }); + }; + + /** + * detaches the results from the selector from all connections. + */ + $.fn.detachAll = function() { + return this.each(function() + { + var id = $(this).attr("id"); + jsPlumb.detachAll(id); + }); + }; + + /** + * adds an endpoint to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoint = function(options) { + var addedEndpoints = []; + this.each(function() { + addedEndpoints.push(jsPlumb.addEndpoint($(this).attr("id"), options)); + }); + return addedEndpoints[0]; + }; + + /** + * adds a list of endpoints to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoints = function(endpoints) { + var addedEndpoints = []; + return this.each(function() { + var e = jsPlumb.addEndpoints($(this).attr("id"), endpoints); + for (var i = 0; i < e.length; i++) addedEndpoints.push(e[i]); + }); + return addedEndpoints; + }; + + /** + * remove the endpoint, if it exists, deleting its UI elements etc. + */ + $.fn.removeEndpoint = function(endpoint) { + this.each(function() { + jsPlumb.removeEndpoint($(this).attr("id"), endpoint); + }); + }; + +})(jQuery); + + +/*TODO: abstract this out from jQuery too! but how...because jsPlumb is not loaded yet. +$(window).bind('resize', function() { + if (resizeTimer) clearTimeout(resizeTimer); + resizeTimer = setTimeout(repaintEverything, 100); + });*/ + + + +/* + * the library agnostic functions, such as find offset, get id, get attribute, extend etc. + */ +(function($) { + + jsPlumb.CurrentLibrary = { + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step', + 'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete' + }, + + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).append(child); + }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + * + */ + getElementObject : function(el) { + return typeof(el)=='string' ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options) { + // remove helper directive if present. we know what's best! + options.helper = null; + //TODO: if 'revert' is set on the options it causes end points to animate back to + // where they came from, if the connection is aborted. do we care? probably not. + // the todo is to decide whether we care or not. + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.draggable(options); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.droppable(options); + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[1]; + return ui.absolutePosition || ui.offset; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + }, + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).remove(); + }, + + getScrollLeft : function(el) { + return el.scrollLeft(); + }, + + getScrollTop : function(el) { + return el.scrollTop(); + } + }; +})(jQuery); diff --git a/archive/1.2.2/jsPlumb-1.2.2-RC1.js b/archive/1.2.2/jsPlumb-1.2.2-RC1.js new file mode 100644 index 000000000..3c169a06d --- /dev/null +++ b/archive/1.2.2/jsPlumb-1.2.2-RC1.js @@ -0,0 +1,1983 @@ +/* + * jsPlumb 1.2.2-RC1 + * + * Provides a way to visually connect elements on an HTML page. + * + * additions since 1.2.1: + * + * - checks drag target to ensure it is not full when dropping a new connection (issue 13) + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + * Dual licensed under MIT and GPL2. + * + */ + +(function() { + + /* + Class: jsPlumb + + This is the main entry point to jsPlumb. There are a bunch of static methods you can + use to connect/disconnect etc. Some methods are also added to jQuery's "$.fn" object itself, but + in general jsPlumb is moving away from that model, preferring instead the static class + approach. One of the reasons for this is that with no methods added to the jQuery $.fn object, + it will be easier to provide support for other libraries such as MooTools. + */ + var jsPlumbInstance = function() { + + var _currentInstance = this; + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + + var log = null; + + var repaintFunction = function() { jsPlumb.repaintEverything(); }; + var automaticRepaint = true; + function repaintEverything() { + if (automaticRepaint) + repaintFunction(); + }; + var resizeTimer = null; + + /** + * map of element id -> endpoint lists. an element can have an arbitrary number of endpoints on it, + * and not all of them have to be connected to anything. + */ + var endpointsByElement = {}; + var endpointsByUUID = {}; + var connectionsByScope = {}; + var offsets = []; + var floatingConnections = {}; + var draggableStates = {}; + var _draggableByDefault = true; + var sizes = []; + var listeners = {}; // a map: keys are event types, values are lists of listeners. + var DEFAULT_SCOPE = 'DEFAULT'; + + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + var _findIndex = function( a, v, b, s ) { + var _eq = function(o1, o2) { + if (o1 === o2) return true; + else if (typeof o1 == 'object' && typeof o2 == 'object') { + var same = true; + for(var propertyName in o1) { + if(!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for(var propertyName in o2) { + if(!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for( var i = +b || 0, l = a.length; i < l; i++ ) { + if( _eq(a[i], v)) return i; + } + return -1; + }; + + /** + * adds a listener for the specified event type. + */ + var _addListener = function(eventType, listener) { + var doOne = function(e, l) { + _addToList(listeners, e, l); + }; + if (typeof eventType == 'object' && eventType.length) { + for (var i = 0; i < eventType.length; i++) doOne(eventType[i], listener); + } else doOne(eventType, listener); + }; + + /** + * helper method to add an item to a list, creating the list if it does not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }; + + var _appendElement = function(canvas, parent) { + if (!parent) document.body.appendChild(canvas); + else jsPlumb.CurrentLibrary.appendElement(canvas, parent); + }; + + /** + * Handles the dragging of an element. + * @param element jQuery element + * @param ui UI object from current library's event system + */ + var _draw = function(element, ui) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints) { + var timestamp = '' + (new Date().getTime()); + _updateOffset(id, ui); + var myOffset = offsets[id]; + var myWH = sizes[id]; + // loop through endpoints for this element + for (var i = 0; i < endpoints.length; i++) { + var e = endpoints[i]; + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH, e); + e.paint(anchorLoc); + var l = e.connections; + for (var j = 0; j < l.length; j++) + l[j].paint(id, ui, false, timestamp); // ...and paint them. + } + } + }; + + /** + * helper function: the second argument is a function taking two args - the first is an id + * ui element from the current library (or list of them), and the second is the function + * to run. + */ + var _elementProxy = function(element, fn) { + var retVal = null; + // TODO this might be jquery specific. in fact it probably is. + if (typeof element == 'object' && element.length) { + retVal = []; + for (var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]); + var id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } + else { + var el = _getElementObject(element); + var id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + + return retVal; + }; + + /** + * fires an event of the given type. + */ + var _fireEvent = function(eventType, data) { + var l = listeners[eventType]; + if (l) { + for (var i in l) { + try { + l[i][eventType](data); + } + catch (e) { + + _log("while firing event [" + eventType + "]; listener failed like this: " + e); + } + } + } + }; + + var _log = function(msg) { + //if (console) console.log(msg); + }; + + var _logFnCall = function(fn, args) { + /*if (console) { + var c = args.callee.caller.toString(); + var i = c.indexOf("{"); + var msg = fn + ' : ' + c.substring(0, i); + console.log(msg); + }*/ + }; + + var cached = {}; + var __getElementObject = function(el) { + + if (typeof(el)=='string') { + var e = cached[el]; + if (!e) { + e = jsPlumb.CurrentLibrary.getElementObject(el); + cached[el] = e; + } + return e; + } + else { + return jsPlumb.CurrentLibrary.getElementObject(el); + } + }; + + + /** + * gets the named attribute from the given element (id or element object) + */ + var _getAttribute = function(el, attName) { + var ele = __getElementObject(el); + return jsPlumb.CurrentLibrary.getAttribute(ele, attName); + }; + + var _setAttribute = function(el, attName, attValue) { + var ele = __getElementObject(el); + jsPlumb.CurrentLibrary.setAttribute(ele, attName, attValue); + }; + + var _addClass = function(el, clazz) { + var ele = __getElementObject(el); + jsPlumb.CurrentLibrary.addClass(ele, clazz); + }; + + var _getElementObject = function(elId) { + return __getElementObject(elId); + }; + + var _getOffset = function(el) { + var ele = __getElementObject(el); + return jsPlumb.CurrentLibrary.getOffset(ele); + }; + + var _getSize = function(el) { + var ele = __getElementObject(el); + return jsPlumb.CurrentLibrary.getSize(ele); + }; + + /** + * gets an id for the given element, creating and setting one if necessary. + */ + var _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id) { + //check if fixed uuid parameter is given + if(arguments.length == 2) + id = uuid; + else + id = "_jsPlumb_" + new String((new Date()).getTime()); + _setAttribute(ele, "id", id); + } + return id; + }; + + /** + * gets an Endpoint by uuid. + */ + var _getEndpoint = function(uuid) { + return endpointsByUUID[uuid]; + }; + + /** + * inits a draggable if it's not already initialised. + * + * TODO: we need to hook in each library to this method. they need to be given the opportunity to + * wrap/insert lifecycle functions, because each library does different things. for instance, jQuery + * supports the notion of 'revert', which will clean up a dragged endpoint; MooTools does not. jQuery + * also supports 'opacity' and MooTools does not; jQuery supports z-index of the draggable; MooTools + * does not. i could go on. the point is...oh. initDraggable can do this. ok. + */ + var _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + // dragging + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend({}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + } + } + }; + + + /** + * helper to create a canvas. + * @param clazz optional class name for the canvas. + */ + var _newCanvas = function(clazz, parent, uuid) { + var canvas = document.createElement("canvas"); + _appendElement(canvas, parent); + canvas.style.position="absolute"; + if (clazz) { canvas.className=clazz; } + + // set an id. if no id on the element and if uuid was supplied it will be used, otherwise we'll create one. + _getId(canvas, uuid); + + if (ie) { + // for IE we have to set a big canvas size. actually you can override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + /** + * performs the given function operation on all the connections found for the given element + * id; this means we find all the endpoints for the given element, and then for each endpoint + * find the connectors connected to it. then we pass each connection in to the given + * function. + */ + var _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + for (var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }; + /** + * perform an operation on all elements. + */ + var _operationOnAll = function(func) { + for (var elId in endpointsByElement) { + _operation(elId, func); + } + }; + /** + * helper to remove an element from the DOM. + */ + var _removeElement = function(element, parent) { + if (element != null) { + if (!parent) { + try { document.body.removeChild(element); } + catch (e) { } + } else { + jsPlumb.CurrentLibrary.removeElement(element, parent); + } + } + }; + /** + * helper to remove a list of elements from the DOM. + */ + var _removeElements = function(elements, parent) { + for (var i in elements) + _removeElement(elements[i], parent); + }; + /** + * helper method to remove an item from a list. + */ + var _removeFromList = function(map, key, value) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete( l[i] ); + l.splice( i, 1 ); + return true; + } + } + return false; + }; + /** + * Sets whether or not the given element(s) should be draggable, regardless of what a particular + * plumb command may request. + * + * @param element May be a string, a jQuery elements, or a list of strings/jquery elements. + * @param draggable Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + var _helper = function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }; + + return _elementProxy(element, _helper); + }; + /** + * private method to do the business of hiding/showing. + * @param el either Id of the element in question or a jquery object for the element. + * @param state String specifying a value for the css 'display' property ('block' or 'none'). + */ + var _setVisible = function(el, state) { + var elId = _getAttribute(el, "id"); + var f = function(jpc) { + jpc.canvas.style.display = state; + }; + _operation(elId, f); + }; + /** + * toggles the draggable state of the given element(s). + * @param el either an id, or a jquery object, or a list of ids/jquery objects. + */ + var _toggleDraggable = function(el) { + var fn = function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }; + return _elementProxy(el, fn); + }; + /** + * private method to do the business of toggling hiding/showing. + * @param elId Id of the element in question + */ + var _toggleVisible = function(elId) { + var f = function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }; + + _operation(elId, f); + + //todo this should call _elementProxy, and pass in the _operation(elId, f) call as a function. cos _toggleDraggable does that. + }; + /** + * updates the offset and size for a given element, and stores the values. + * if 'offset' is not null we use that (it would have been passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we use the offset, outerWidth and outerHeight methods to get + * the current values. + */ + var _updateOffset = function(elId, offset, recalc) { + if (recalc || offset == null) { // if forced repaint or no offset available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + } else { + offsets[elId] = offset; + } + }; + + /** + * wraps one function with another, creating a placeholder for the wrapped function + * if it was null. this is used to wrap the various drag/drop event functions - to allow + * jsPlumb to be notified of important lifecycle events without imposing itself on the user's + * drag/drop functionality. + * TODO: determine whether or not we should support an error handler concept, if one of the functions fails. + */ + var _wrap = function(wrappedFunction, newFunction) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { r = newFunction.apply(this, arguments); } + catch (e) { + _log('jsPlumb function failed : ' + e); + } + try { wrappedFunction.apply(this, arguments); + } + catch (e) { + _log('wrapped function failed : ' + e); + } + return r; + }; + }; + + /* + Class: Anchor + Models a position relative to the origin of some element that an Endpoint can be located. + */ + /* + Function: Anchor + + Constructor for the Anchor class + + Parameters: + params - Anchor parameters. This should contain three values, and may optionally have an 'offsets' argument: + + - x : the x location of the anchor as a fraction of the total width. + - y : the y location of the anchor as a fraction of the total height. + - orientation : an [x,y] array indicating the general direction a connection from the anchor should go in. for more info on this, see the documentation, or the docs in jquery-jsPlumb-defaults-XXX.js for the default Anchors. + - offsets : an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. may be null. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; this.y = params.y || 0; + var orientation = params.orientation || [0,0]; + var lastTimestamp = null; + var lastReturnValue = null; + this.offsets = params.offsets || [0,0]; + //TODO: fix this properly. since Anchors are often static, this timestamping business + // does not work at all well. the timestamp should be inside the Endpoint, because they + // are _never_ static. the method that Connection uses to find an anchor location should + // be done through the Endpoint class, which can then deal with the timestamp. + // and, in fact, we should find out whether or not we even get a speed enhancement from doing + // this. + this.compute = function(xy, wh, element) { + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1]]; + var container = element? element.container : null; + var containerAdjustment = {left:0, top:0 }; + if (container != null) { + var eo = _getElementObject(container); + var o = _getOffset(eo); + var sl = jsPlumb.CurrentLibrary.getScrollLeft(eo); + var st = jsPlumb.CurrentLibrary.getScrollTop(eo); + containerAdjustment.left = o.left - sl; + containerAdjustment.top = o.top - st; + lastReturnValue[0] = lastReturnValue[0] - containerAdjustment.left; + lastReturnValue[1] = lastReturnValue[1] - containerAdjustment.top; + } + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y && this.offsets[0] == anchor.offsets[0] && this.offsets[1] == anchor.offsets[1] && o[0] == ao[0] && o[1] == ao[1]; + }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from its position relative to the anchor it is floating relative to. + * + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(_getElementObject(refCanvas)); + + // these are used to store the current relative position of our anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating anchor is hovering over another anchor. + var orientation = null; + + this.compute = function(xy, wh, txy, twh) { + // set these for the getOrientation method to use. + xDir = 0;//xy[0] < txy[0] ? -1 : xy[0] == txy[0] ? 0 : 1; + yDir = 0;//xy[1] < txy[1] ? -1 : xy[1] == txy[1] ? 0 : 1; + return [xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other anchor: if it declares zero for some direction, we declare zero too. + // this might not be the most awesome. perhaps we can come up with a better way. it's just so that the line we draw looks + // like it makes sense. maybe this wont make sense. + return [Math.abs(o[0]) * xDir * -1, Math.abs(o[1]) * yDir * -1]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering over another anchor; + * we want to assume that anchor's orientation for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no longer hovering + * over another anchor; we should resume calculating orientation as we normally do. + */ + this.out = function() { + orientation = null; + }; + }; + + // ************** connection + // **************************************** + /** + * allowed params: + * source: source element (string or a jQuery element) (required) + * target: target element (string or a jQuery element) (required) + * + * anchors: optional array of anchor placements. defaults to BottomCenter for source + * and TopCenter for target. + */ + var Connection = function(params) { + + // ************** get the source and target and register the connection. ******************* + var self = this; + var id = new String('_jsplumb_c_' + (new Date()).getTime()); + this.getId = function() { return id; }; + this.container = params.container || _currentInstance.Defaults.Container; // may be null; we will append to the body if so. + // get source and target as jQuery objects + this.source = _getElementObject(params.source); + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. + if (params.sourceEndpoint) + this.source = params.sourceEndpoint.getElement(); + if (params.targetEndpoint) + this.target = params.targetEndpoint.getElement(); + this.sourceId = _getAttribute(this.source, "id"); + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + this.scope = params.scope; // scope may have been passed in to the connect call. + // if it wasn't, we will pull it from the source endpoint, + // after having initialised the endpoints. + // init endpoints + this.endpoints = []; + this.endpointStyles = []; + var prepareEndpoint = function(existing, index, params, element) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } + else { + if(!params.endpoints) params.endpoints = [null,null]; + var ep = params.endpoints[index] || params.endpoint || _currentInstance.Defaults.Endpoints[index] || jsPlumb.Defaults.Endpoints[index] || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || new jsPlumb.Endpoints.Dot(); + if (!params.endpointStyles) params.endpointStyles = [null,null]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + var a = params.anchors ? params.anchors[index] : _currentInstance.Defaults.Anchors[index] || jsPlumb.Defaults.Anchors[index] || _currentInstance.Defaults.Anchor || jsPlumb.Defaults.Anchor || jsPlumb.Anchors.BottomCenter; + var u = params.uuids ? params.uuids[index] : null; + if(params.uuids) + var e = new Endpoint({style:es, endpoint:ep, connections:[self], uuid:u, anchor:a, source:element, container:self.container }); + else + var e = new Endpoint({style:es, endpoint:ep, connections:[self], anchor:a, source:element, container:self.container }); + + self.endpoints[index] = e; + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || _currentInstance.Defaults.Connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + this.paintStyle = this.endpoints[0].connectorStyle || this.endpoints[1].connectorStyle || params.paintStyle || _currentInstance.Defaults.PaintStyle || jsPlumb.Defaults.PaintStyle; + + _updateOffset(this.sourceId); + _updateOffset(this.targetId); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var anchorLoc = this.endpoints[0].anchor.compute([myOffset.left, myOffset.top], myWH, this.endpoints[0]); + this.endpoints[0].paint(anchorLoc); + myOffset = offsets[this.targetId]; myWH = sizes[this.targetId]; + anchorLoc = this.endpoints[1].anchor.compute([myOffset.left, myOffset.top], myWH, this.endpoints[1]); + this.endpoints[1].paint(anchorLoc); + + // *************** create canvas on which the connection will be drawn ************ + var canvas = _newCanvas(jsPlumb.connectorClass, self.container); + this.canvas = canvas; + + /** + * paints the connection. + * @param elId Id of the element that is in motion + * @param ui current library's event system ui object (present if we came from a drag to get here) + * @param recalc whether or not to recalculate element sizes. this is true if a repaint caused this to be painted. + */ + this.paint = function(elId, ui, recalc, timestamp) { + + if (log) log.debug("Painting Connection; element in motion is " + elId + "; ui is [" + ui + "]; recalc is [" + recalc + "]"); + + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = !(elId == this.sourceId); + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + if (this.canvas.getContext) { + //if (recalc) { + _updateOffset(elId, ui, recalc); + _updateOffset(tId); // update the target if this is a forced repaint. otherwise, only the source has been moved. + //} + + var myOffset = offsets[elId]; + var otherOffset = offsets[tId]; + var myWH = sizes[elId]; + var otherWH = sizes[tId]; + + var ctx = canvas.getContext('2d'); + //TODO: why are these calculated again? they were calculated in the _draw function. + var sAnchorP = this.endpoints[sIdx].anchor.compute([myOffset.left, myOffset.top], myWH, this.endpoints[sIdx]); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.compute([otherOffset.left, otherOffset.top], otherWH, this.endpoints[tIdx]); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, this.paintStyle.lineWidth); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + jsPlumb.extend(ctx, this.paintStyle); + + if (this.paintStyle.gradient && !ie) { + var g = swap ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + for (var i = 0; i < this.paintStyle.gradient.stops.length; i++) + g.addColorStop(this.paintStyle.gradient.stops[i][0],this.paintStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + + this.connector.paint(dim, ctx); + } + }; + + //TODO: should this take a timestamp? probably. it reduces the amount of time + // spent figuring out anchor locations. + this.repaint = function() { + this.paint(this.sourceId, null, true); + }; + + _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + }; + + /* + Class: Endpoint + Models an endpoint. Can have one to N connections emanating from it (although how to handle that in the UI is + a very good question). also has a Canvas and paint style. + */ + + /* + Function: Endpoint + This is the Endpoint class constructor. + Parameters: + anchor - anchor for the endpoint, of type jsPlumb.Anchor. may be null. + endpoint - endpoint object, of type jsPlumb.Endpoint. may be null. + style - endpoint style, a js object. may be null. + source - element the endpoint is attached to, of type jquery object. Required. + canvas - canvas element to use. may be, and most often is, null. + connections - optional list of connections to configure the endpoint with. + isSource - boolean. indicates the endpoint can act as a source of new connections. optional. + dragOptions - if isSource is set to true, you can supply arguments for the jquery draggable method. optional. + connectionStyle - if isSource is set to true, this is the paint style for connections from this endpoint. optional. + connector - optional connector type to use. + isTarget - boolean. indicates the endpoint can act as a target of new connections. optional. + dropOptions - if isTarget is set to true, you can supply arguments for the jquery droppable method. optional. + reattach - optional boolean that determines whether or not the connections reattach after they + have been dragged off an endpoint and left floating. defaults to false: connections + dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + params = params || {}; + // make a copy. then we can use the wrapper function. + jsPlumb.extend({}, params); + var self = this; + var id = new String('_jsplumb_e_' + (new Date()).getTime()); + this.getId = function() { return id; }; + self.anchor = params.anchor || jsPlumb.Anchors.TopCenter; + var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot(); + var _style = params.style || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + this.connectorStyle = params.connectorStyle; + this.connector = params.connector; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + var _element = params.source; + var _uuid = params.uuid; + if (_uuid) endpointsByUUID[_uuid] = self; + this.container = params.container || jsPlumb.Defaults.Container; + var _elementId = _getAttribute(_element, "id"); + var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of. + this.canvas = params.canvas || _newCanvas(jsPlumb.endpointClass, this.container, params.uuid); + this.connections = params.connections || []; + this.scope = params.scope || DEFAULT_SCOPE; + var _reattach = params.reattach || false; + var floatingEndpoint = null; + var inPlaceCopy = null; + this.dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.addConnection = function(connection) { + self.connections.push(connection); + }; + this.detach = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + self.connections.splice(idx, 1); + t.detach(connection); + } + }; + + /** + * detaches all connections this Endpoint has + */ + this.detachAll = function() { + while(self.connections.length > 0) { + self.detach(self.connections[0]); + } + }; + + /** + * removes any connections from this Endpoint that are + * connected to the given target endpoint. + * @param targetEndpoint + */ + this.detachFrom = function(targetEndpoint) { + var c = []; + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for (var i = 0; i < c.length; i++) { + targetEndpoint.detach(c[i]); + self.detach(c[i]); + } + }; + + /** + * returns the DOM element this Endpoint is attached to. + * @returns + */ + this.getElement = function() { return _element; }; + + /** + * returns the UUID for this Endpoint, if there is one. + * @returns + */ + this.getUuid= function() { return _uuid; }; + + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + var e = new Endpoint({anchor:self.anchor, source:_element, style:_style, endpoint:_endpoint}); + return e; + }; + + + /** + * returns whether or not this endpoint is connected to the given endpoint. + * @param endpoint Endpoint to test. + * @since 1.1.1 + * + * todo: needs testing. will this work if the endpoint passed in is the source? + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + * @returns {Boolean} + */ + this.isFloating = function() { return floatingEndpoint != null; }; + /** + * first pass at default ConnectorSelector: returns the first connection, if we have any. + * modified a little, 5/10/2010: now it only returns a connector if we have not got equal to or more than _maxConnector connectors + * attached. otherwise it is assumed a new connector is ok. but note with this setup we can't select any other connection than the first + * one. what if this could return a list? that implies everything should work with a list - dragging etc. could be nasty. could also + * be cool. + */ + var connectorSelector = function() { + //return self.connections.length == 0 || self.connections.length < _maxConnections ? null : self.connections[0]; + if (self.connections.length < _maxConnections) return null; + //else if (self.connections.length == _maxConnections) return false; + else return self.connections[0]; + }; + + /** + * @returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); }; + + /** + * sets whether or not connnctions can be dragged from this Endpoint once it is full. you would use + * this in a UI in which you're going to provide some other way of breaking connections, if you need + * to break them at all. this property is by default true; use it in conjunction with the 'reattach' + * option on a connect call. + */ + this.setDragAllowedWhenFull = function(allowed) { + self.dragAllowedWhenFull = allowed; + }; + + /** + * a deep equals check. everything must match, including the anchor, styles, everything. + * TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor) && + true; + }; + + /** + * paints the Endpoint, recalculating offset and anchor positions if necessary. + */ + this.paint = function(anchorPoint, connectorPaintStyle, canvas) { + + if (log) log.debug("Painting Endpoint with elementId [" + _elementId + "]; anchorPoint is [" + anchorPoint + "]"); + + if (anchorPoint == null) { + // do we always want to force a repaint here? i dont think so! + var xy = offsets[_elementId]; + var wh = sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset(_elementId); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + anchorPoint = self.anchor.compute([xy.left, xy.top], wh, self); + } + _endpoint.paint(anchorPoint, self.anchor.getOrientation(), canvas || self.canvas, _style, connectorPaintStyle || _style); + }; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + + // is this a connection source? we make it draggable and have the drag listener + // maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + + // first the question is, how many connections are on this endpoint? if it's only one, then excellent. otherwise we will either need a way + // to select one connection from the list, or drag them all. if we had a pluggable 'ConnectorSelector' interface we could probably + // provide a way for people to implement their own UI components to do the connector selection. the question in that particular case would be how much + // information the interface needs from jsPlumb at execution time. if, however, we leave out the connector selection, and drag them all, + // that wouldn't be too hard to organise. perhaps that behaviour would be on a switch for the endpoint, or perhaps the ConnectorSelector + // interface returns a List, with the default implementation just returning everything. i think i like that. + // + // let's say for now that there is just one endpoint, cos we need to get this going before we can consider a list of them anyway. + // the major difference between that case and the case of a new endpoint is actually quite small - it's a question of where the + // Connection comes from. for a new one, we create a new one. obviously. otherwise we just get the jpc from the Endpoint + // (remember we're only assuming one connection right now). so all of the UI stuff we do to create the floating endpoint etc + // will still be valid, but when we stop dragging, we'll have to do something different. if we stop with a valid drop i think it will + // be the same process. but if we stop with an invalid drop we have to reset the Connection to how it was when we got it. + var start = function() { + + jpc = connectorSelector(); + if (self.isFull() && !self.dragAllowedWhenFull) return false; + + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + var nE = _getElementObject(n); + _appendElement(n); // + // create and assign an id, and initialize the offset. + //TODO can't this be replaced with a _getId call? + var id = "" + new String(new Date().getTime()); + _setAttribute(nE, "id", id); + _updateOffset(id); + // store the id of the dragging div and the source element. the drop function + // will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor({reference:self.anchor, referenceCanvas:self.canvas}); + floatingEndpoint = new Endpoint({ style:{fillStyle:'rgba(0,0,0,0)'}, endpoint:_endpoint, anchor:floatingAnchor, source:nE }); + + if (jpc == null) { + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = new Connection({ + sourceEndpoint:self, + targetEndpoint:floatingEndpoint, + source:_getElementObject(_element), + target:_getElementObject(n), + anchors:[self.anchor, floatingAnchor], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + connector: params.connector + }); + } else { + existingJpc = true; + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; + // probably we should remove the connection? and add it back if the user + // does not drop it somewhere proper. + self.removeConnection(jpc); + if (anchorIdx == 0){ + existingJpcParams = [jpc.source, jpc.sourceId]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + }else { + existingJpcParams = [jpc.target, jpc.targetId]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // todo unregister on stop + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + }; + + var dragOptions = params.dragOptions || { }; + + var defaultOpts = jsPlumb.extend({}, jsPlumb.CurrentLibrary.defaultDragOptions); + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + var startEvent = jsPlumb.CurrentLibrary.dragEvents['start']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements([n, floatingEndpoint.canvas]); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + if (_reattach) { + jpc.floatingAnchorIndex = null; + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } + else { + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeElement(jpc.canvas); + _removeFromList(connectionsByScope, jpc.scope, jpc); + _fireEvent("jsPlumbConnectionDetached", { + source:jpc.source, + target:jpc.target, + sourceId:jpc.sourceId, + targetId:jpc.targetId, + sourceEndpoint:jpc.endpoints[0], + targetEndpoint:jpc.endpoints[1] + }); + } + } else { + _removeElement(jpc.canvas); + //if (jpc.endpoints[1]) alert("target set"); + self.removeConnection(jpc); + } + } + jpc = null; + delete floatingEndpoint; + delete inPlaceCopy; + self.paint(); + } + ); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + }; + + // connector target + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend({}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + dropOptions[dropEvent]= _wrap(dropOptions[dropEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable), "dragId"); + var elId = _getAttribute(_getElementObject(draggable),"elId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + var oidx = idx == 0 ? 1 : 0; + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + //todo test that the target is not full. + // remove this jpc from the current endpoint + jpc.endpoints[idx].removeConnection(jpc); + if (jpc.suspendedEndpoint) + jpc.suspendedEndpoint.removeConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + // add the jpc to the other endpoint too. + jpc.endpoints[oidx].addConnection(jpc); + + _addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + jsPlumb.repaint(elId); + _fireEvent("jsPlumbConnection", { + source:jpc.source, + target:jpc.target, + sourceId:jpc.sourceId, + targetId:jpc.targetId, + sourceEndpoint:jpc.endpoints[0], + targetEndpoint:jpc.endpoints[1] + }); + } + // else there must be some cleanup required. + + delete floatingConnections[id]; + }); + + dropOptions[overEvent]= _wrap(dropOptions[overEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + }); + + jsPlumb.CurrentLibrary.initDroppable(_getElementObject(self.canvas), dropOptions); + } + + return self; + }; + + /* + Property: Defaults + These are the default settings for jsPlumb, that is what will be used if you do not supply specific pieces of information + to the various API calls. A convenient way to implement your own look and feel can be to override these defaults by including a script + somewhere after the jsPlumb include, but before you make any calls to jsPlumb, for instance in this example we set the PaintStyle to be + a blue line of 27 pixels: + > jsPlumb.Defaults.PaintStyle = { lineWidth:27, strokeStyle:'blue' } + */ + this.Defaults = { + Anchor : null, + Anchors : [ null, null ], + Connector : null, + Container : null, + DragOptions: { }, + DropOptions: { }, + Endpoint : null, + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : null }, + EndpointStyles : [ null, null ], + MaxConnections : null, + PaintStyle : { lineWidth : 10, strokeStyle : 'red' }, + Scope : "_jsPlumb_DefaultScope" + }; + + /* + Property: connectorClass + The CSS class to set on Connection canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = '_jsPlumb_connector'; + + /* + Property: endpointClass + The CSS class to set on Endpoint canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = '_jsPlumb_endpoint'; + + /* + Property: Anchors + Default jsPlumb Anchors. These are supplied in the file jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form .jsPlumb-all-x.x.x.js. You can provide your own Anchors by supplying them in a script that is loaded after jsPlumb, for instance: + > jsPlumb.Anchors.MyAnchor = { ....anchor code here. see the documentation. } + */ + this.Anchors = {}; + + /* + Property: Connectors + Default jsPlumb Connectors. These are supplied in the file jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form .jsPlumb-all-x.x.x.js. You can provide your own Connectors by supplying them in a script that is loaded after jsPlumb, for instance: + > jsPlumb.Connectors.MyConnector = { ....connector code here. see the documentation. } + */ + this.Connectors = {}; + + /* + Property: Endpoints + Default jsPlumb Endpoints. These are supplied in the file jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form .jsPlumb-all-x.x.x.js. You can provide your own Endpoints by supplying them in a script that is loaded after jsPlumb, for instance: + > jsPlumb.Endpoints.MyEndpoint = { ....endpoint code here. see the documentation. } + */ + this.Endpoints = {}; + + /* + Function: addEndpoint + Adds an Endpoint to a given element. + Parameters: + target - Element to add the endpoint to. either an element id, or a selector representing some element. + params - Object containing Endpoint options (more info required) + Returns: + The newly created Endpoint. + See Also: + + */ + this.addEndpoint = function(target, params) { + params = jsPlumb.extend({}, params); + var el = _getElementObject(target); + var id = _getAttribute(el,"id"); + params.source = el; + _updateOffset(id); + var e = new Endpoint(params); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id]; + var myWH = sizes[id]; + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH, e); + e.paint(anchorLoc); + return e; + }; + + /* + Function: addEndpoint + Adds a list of Endpoints to a given element. + Parameters: + target - element to add the endpoint to. either an element id, or a selector representing some element. + endpoints - List of objects containing Endpoint options. one Endpoint is created for each entry in this list. + Returns: + List of newly created Endpoints, one for each entry in the 'endpoints' argument. + See Also: + + */ + this.addEndpoints = function(target, endpoints) { + var results = []; + for (var i = 0; i < endpoints.length; i++) { + results.push(jsPlumb.addEndpoint(target, endpoints[i])); + } + return results; + }; + + /* + Function: animate + Wrapper around supporting library's animate function; injects a call to jsPlumb in the 'step' function (creating it if necessary). + This only supports the two-arg version of the animate call in jQuery - the one that takes an 'options' object as the second arg. + MooTools has only one method - a two arg one. Which is handy. + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the standard jQuery animate call. + options - The 'options' argument you want passed to the standard jQuery animate call. + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el); + var id = _getAttribute(el,"id"); + //TODO this is not agnostic yet. + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() + { + _currentInstance.repaint(id); + }); + + // you know, probably onComplete should repaint too. that will help keep up + // with fast animations. + options[completeFunction] = _wrap(options[completeFunction], function() + { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /** + * clears the cache used to lookup elements by their id. if you remove any elements + * from the DOM you should call this to ensure that if you add them back in jsPlumb does not + * have a stale handle. + */ + this.clearCache = function() { + delete cached; + cached = {}; + }; + + /* + Function: connect + Establishes a connection between two elements. + Parameters: + params - Object containing setup for the connection. see documentation. + Returns: + The newly created Connection. + */ + this.connect = function(params) { + var _p = jsPlumb.extend({}, params); + // test for endpoint uuids to connect + if (params.uuids) { + var _resolveByUuid = function(idx) { + var e = _getEndpoint(params.uuids[idx]); + if (!e) throw ("Endpoint with UUID " + params.uuids[idx] + " not found."); + return e; + }; + _p.sourceEndpoint = _resolveByUuid(0); + _p.targetEndpoint = _resolveByUuid(1); + } + + // now ensure that if we do have Endpoints already, they're not full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log("could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log("could not add connection; target endpoint is full"); + return; + } + + var jpc = new Connection(_p); + // add to list of connections (by scope). + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + _fireEvent("jsPlumbConnection", { + source:jpc.source, + target:jpc.target, + sourceId:jpc.sourceId, + targetId:jpc.targetId, + sourceEndpoint:jpc.endpoints[0], + targetEndpoint:jpc.endpoints[1] + }); + // force a paint + _draw(jpc.source); + + return jpc; + }; + + var fireDetachEvent = function(jpc) { + _fireEvent("jsPlumbConnectionDetached", { + source:jpc.source, + target:jpc.target, + sourceId:jpc.sourceId, + targetId:jpc.targetId, + sourceEndpoint:jpc.endpoints[0], + targetEndpoint:jpc.endpoints[1] + }); + }; + + /* + Function: detach + Removes a connection. + Parameters: + sourceId - Id of the first element in the connection. A String. + targetId - iI of the second element in the connection. A String. + Returns: + true if successful, false if not. + */ + this.detach = function(sourceId, targetId) { + if (arguments.length == 2) { + var f = function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + fireDetachEvent(jpc); + + return true; + } + + }; + + // todo: how to cleanup the actual storage? a third arg to _operation? + _operation(sourceId, f); + } + // this is the new version of the method, taking a JS object like the connect method does. + else if (arguments.length == 1) { + var _p = jsPlumb.extend({}, sourceId); // a backwards compatibility hack: sourceId should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1])); + } + else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } + else { + sourceId = _getId(_p.source); + targetId = _getId(_p.target); + var f = function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + fireDetachEvent(jpc); + + return true; + } + + }; + + // todo: how to cleanup the actual storage? a third arg to _operation? + _operation(sourceId, f); + } + + // fire an event + /*_fireEvent("jsPlumbDetach", { + source:jpc.source, + target:jpc.target, + sourceId:jpc.sourceId, + targetId:jpc.targetId, + sourceEndpoint:jpc.endpoints[0], + targetEndpoint:jpc.endpoints[1] + });*/ + /*// force a paint + _draw(jpc.source); + + return jpc;*/ + } + }; + + /* + Function: detachAll + Removes all an element's connections. + Parameters: + el - either the id of the element, or a selector for the element. + Returns: + void + */ + this.detachAll = function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + var c = endpoints[i].connections.length; + if (c > 0) { + for (var j = 0; j < c; j++) { + var jpc = endpoints[i].connections[0]; + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + fireDetachEvent(jpc); + } + } + } + } + }; + + /* + Function: detachEverything + Remove all Connections from all elements, but leaves Endpoints in place. + Returns: + void + See Also: + + */ + this.detachEverything = function() { + for(var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + var c = endpoints[i].connections.length; + if (c > 0) { + for (var j = 0; j < c; j++) { + var jpc = endpoints[i].connections[0]; + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + fireDetachEvent(jpc); + } + } + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + /* + Function: draggable + initialises the draggability of some element or elements. + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for (var i = 0; i < el.length; i++) + { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) + _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + Parameters: + o1 - object to extend + o2 - object to extend o1 with + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + Function: getConnections + Gets all or a subset of connections currently managed by this jsPlumb instance. + Parameters: + options - a JS object that holds options defining what sort of connections you're looking for. Valid values are: + scope this may be a String or a list of Strings. jsPlumb will only return Connections whose scope matches what this option + defines. If you omit it, you will be given Connections of every scope. + source may be a string or a list of strings; constraints results to have only Connections whose source is this object. + target may be a string or a list of strings; constraints results to have only Connections whose target is this object. + + The return value is a dictionary in this format: + + { + 'scope1': [ + {sourceId:'window1', targetId:'window2'}, + {sourceId:'window3', targetId:'window4'}, + {sourceId:'window1', targetId:'window3'} + ], + 'scope2': [ + {sourceId:'window1', targetId:'window3'} + ] + } + + */ + this.getConnections = function(options) { + var r = {}; + options = options || {}; + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') r.push(input); + else r = input; + } + return r; + }; + var scopes = prepareList(options.scope); + var sources = prepareList(options.source); + var targets = prepareList(options.target); + var filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }; + for (var i in connectionsByScope) { + if (filter(scopes, i)) { + r[i] = []; + for (var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + //r[i].push({sourceId:c.sourceId, targetId:c.targetId}); + r[i].push({ + sourceId:c.sourceId, + targetId:c.targetId, + source:c.source, + target:c.target, + sourceEndpoint:c.endpoints[0], + targetEndpoint:c.endpoints[1] + }); + } + } + } + return r; + }; + + /* + Function: getDefaultScope + Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a scope to an endpoint + or connection allows you to support different types of connections in the same UI. but if you're only interested in one type of connection, + you don't need to supply a scope. this method will probably be used by very few people; it's good for testing though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + gets an Endpoint by UUID + Parameters: + uuid - the UUID for the Endpoint + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /* + * gets an element's id, creating one if necessary. really only exposed for the lib-specific + * functionality to access; would be better to pass the current instance into the lib-specific + * code (even though this is a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + + /* + Function: hide + Sets an element's connections to be hidden. + Parameters: + el - either the id of the element, or a selector for the element. + Returns: + void + */ + this.hide = function(el) { + _setVisible(el, "none"); + }; + + /* + Function: makeAnchor + Creates an anchor with the given params. + Parameters: + x - the x location of the anchor as a fraction of the total width. + y - the y location of the anchor as a fraction of the total height. + xOrientation - value indicating the general direction a connection from the anchor should go in, in the x direction. + yOrientation - value indicating the general direction a connection from the anchor should go in, in the y direction. + xOffset - a fixed offset that should be applied in the x direction that should be applied after the x position has been figured out. optional. defaults to 0. + yOffset - a fixed offset that should be applied in the y direction that should be applied after the y position has been figured out. optional. defaults to 0. + Returns: + The newly created Anchor. + */ + this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards compatibility if we are given only one value we assume it's a call in the old form. + var params = {}; + if (arguments.length == 1) jsPlumb.extend(params, x); + else { + params = {x:x, y:y}; + if (arguments.length >= 4) { + params.orientation = [arguments[2], arguments[3]]; + } + if (arguments.length == 6) params.offsets = [arguments[4], arguments[5]]; + } + var a = new Anchor(params); + a.clone = function() { return new Anchor(params); }; + return a; + }; + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + Parameters: + el - either the id of the element or a selector representing the element. + Returns: + void + See Also: + + */ + this.repaint = function(el) { + + var _processElement = function(el) { + var ele = _getElementObject(el); + _draw(ele); + }; + + // TODO: support a jQuery result object too! + + // support both lists... + if (typeof el =='object') { + for (var i = 0; i < el.length; i++) + _processElement(el[i]); + } // ...and single strings. + else _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + Returns: + void + See Also: + + */ + this.repaintEverything = function() { + for (var elId in endpointsByElement) { + _draw(_getElementObject(elId)); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + Parameters: + el - either an element id, or a selector for an element. + Returns: + void + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"); + // first remove all Connections. + jsPlumb.detachAll(elId); + var ebe = endpointsByElement[elId]; + for (var i in ebe) { + _removeElement(ebe[i].canvas); + } + endpointsByElement[elId] = []; + }; + + /* + Function: removeEveryEndpoint + Removes every Endpoint in this instance of jsPlumb. + Returns: + void + See Also: + + */ + this.removeEveryEndpoint = function() { + for(var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + _removeElement(endpoints[i].canvas, endpoints[i].container); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + }; + + /* + Function: removeEndpoint + Removes the given Endpoint from the given element. + Parameters: + el - either an element id, or a selector for an element. + endpoint - Endpoint to remove. this is an Endpoint object, such as would have been returned from a call to addEndpoint. + Returns: + void + See Also: + + */ + this.removeEndpoint = function(el, endpoint) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + if (ebe) { + if(_removeFromList(endpointsByElement, elId, endpoint)) + _removeElement(endpoint.canvas); + } + }; + + /* + Function:reset + removes all endpoints and connections and clears the element cache. + */ + this.reset = function() { + this.detachEverything(); + this.removeEveryEndpoint(); + this.clearCache(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + Parameters: + value - whether or not to automatically repaint when the window is resized. + Returns: + void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + Function: setDefaultNewCanvasSize + Sets the default size jsPlumb will use for a new canvas (we create a square canvas so one value is all that is required). + This is a hack for IE, because ExplorerCanvas seems to need for a canvas to be larger than what you are going to draw on + it at initialisation time. The default value of this is 1200 pixels, which is quite large, but if for some reason you're + drawing connectors that are bigger, you should adjust this value appropriately. + Parameters: + size - The default size to use. jsPlumb will use a square canvas so you need only supply one value. + Returns: + void + */ + this.setDefaultNewCanvasSize = function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }; + + /* + Function: setDefaultScope + Sets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a scope to an endpoint + or connection allows you to support different types of connections in the same UI. but if you're only interested in one type of connection, + you don't need to supply a scope. this method will probably be used by very few people; it just instructs jsPlumb to use a different key + for the default scope. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + Function: setDraggable + Sets whether or not a given element is draggable, regardless of what any plumb command may request. + Parameters: + el - either the id for the element, or a selector representing the element. + Returns: + void + */ + this.setDraggable = _setDraggable; + + /* + Function: setDraggableByDefault + Sets whether or not elements are draggable by default. Default for this is true. + Parameters: + draggable - value to set + Returns: + void + */ + this.setDraggableByDefault = function(draggable) { + _draggableByDefault = draggable; + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + Function: setRepaintFunction + Sets the function to fire when the window size has changed and a repaint was fired. + Parameters: + f - Function to execute. + Returns: + void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + Function: show + Sets an element's connections to be visible. + Parameters: + el - either the id of the element, or a selector for the element. + Returns: + void + */ + this.show = function(el) { + _setVisible(el, "block"); + }; + + /* + Function: sizeCanvas + Helper to size a canvas. You would typically use this when writing your own Connector or Endpoint implementation. + Parameters: + x - [int] x position for the Canvas origin + y - [int] y position for the Canvas origin + w - [int] width of the canvas + h - [int] height of the canvas + Returns: + void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; canvas.height = h; + canvas.style.width = w + "px"; canvas.width = w; + canvas.style.left = x + "px"; canvas.style.top = y + "px"; + // _currentInstance.CurrentLibrary.setPosition(canvas, x, y); + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards compatibility + */ + this.toggle = _toggleVisible; + + /* + Function: toggleVisible + Toggles visibility of an element's connections. + Parameters: + el - either the element's id, or a selector representing the element. + Returns: + void, but should be updated to return the current state + */ + //TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + Function: toggleDraggable + Toggles draggability (sic) of an element's connections. + Parameters: + el - either the element's id, or a selector representing the element. + Returns: + The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + Function: unload + Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element + Returns: + void + */ + this.unload = function() { + delete endpointsByElement; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + }; + + /* + Function: wrap + Helper method to wrap an existing function with one of your own. This is used by the various + implementations to wrap event callbacks for drag/drop etc; it allows jsPlumb to be + transparent in its handling of these things. If a user supplies their own event callback, + for anything, it will always be called. + Parameters: + */ + this.wrap = _wrap; + this.addListener = _addListener; + + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(); + if (_defaults) + jsPlumb.extend(j.Defaults, _defaults); + return j; + }; + +})(); diff --git a/archive/1.2.2/jsPlumb-defaults-1.2.2-RC1.js b/archive/1.2.2/jsPlumb-defaults-1.2.2-RC1.js new file mode 100644 index 000000000..a991b2e0a --- /dev/null +++ b/archive/1.2.2/jsPlumb-defaults-1.2.2-RC1.js @@ -0,0 +1,408 @@ +/* +* jsPlumb-defaults-1.2.2-RC1 +* +* This script contains the default Anchors, Endpoints and Connectors for jsPlumb. It should be used with jsPlumb 1.1.0 and above; +* prior to version 1.1.0 of jsPlumb the defaults were included inside the main script. +* +* Version 1.1.1 of this script adds the Triangle Endpoint, written by __________ and featured on this demo: +* +* http://www.mintdesign.ru/blog/javascript-jsplumb-making-graph +* http://www.mintdesign.ru/site/samples/jsplumb/jsPlumb-graph-sample.htm +* +* NOTE: for production usage you should use jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, +* in a minified file. +* +* Dual licensed under MIT and GPL2. +*/ + +(function() { + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * getOrientation : function() { return [ox, oy]; } + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + jsPlumb.Anchors.TopCenter = jsPlumb.makeAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors.BottomCenter = jsPlumb.makeAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors.LeftMiddle = jsPlumb.makeAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors.RightMiddle = jsPlumb.makeAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors.Center = jsPlumb.makeAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors.TopRight = jsPlumb.makeAnchor(1, 0, 0,-1); + jsPlumb.Anchors.BottomRight = jsPlumb.makeAnchor(1, 1, 0, 1); + jsPlumb.Anchors.TopLeft = jsPlumb.makeAnchor(0, 0, 0, -1); + jsPlumb.Anchors.BottomLeft = jsPlumb.makeAnchor(0, 1, 0, 1); + + + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (w < 2 * lineWidth) { + w = 2 * lineWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - lineWidth; + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < 2 * lineWidth) { + // minimum size is 2 * line Width + h = 2 * lineWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - lineWidth; + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + var sx = sourcePos[0] < targetPos[0] ? w-xo : xo; + var sy = sourcePos[1] < targetPos[1] ? h-yo : yo; + var tx = sourcePos[0] < targetPos[0] ? xo : w-xo; + var ty = sourcePos[1] < targetPos[1] ? yo : h-yo; + var retVal = [ x, y, w, h, sx, sy, tx, ty ]; + + return retVal; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + }; + + /** + * This Connector draws a Bezier curve with two control points. + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + jsPlumb.Connectors.Bezier = function(curviness) { + + var self = this; + this.majorAnchor = curviness || 150; + this.minorAnchor = 10; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) + { + lineWidth = lineWidth || 0; + var w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth, h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + var canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2), canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + var sx = sourcePos[0] < targetPos[0] ? w - (lineWidth/2): (lineWidth/2), sy = sourcePos[1] < targetPos[1] ? h-(lineWidth/2) : (lineWidth/2); + var tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : w-(lineWidth/2), ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : h-(lineWidth/2); + var CP = self._findControlPoint([sx,sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + var CP2 = self._findControlPoint([tx,ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(sx,tx); var minx2 = Math.min(CP[0], CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(sx,tx); var maxx2 = Math.max(CP[0], CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > w) w = maxx; + if (minx < 0) { + canvasX += minx; var ox = Math.abs(minx); + w += ox; CP[0] += ox; sx += ox; tx +=ox; CP2[0] += ox; + } + + var miny1 = Math.min(sy,ty); var miny2 = Math.min(CP[1], CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(sy,ty); var maxy2 = Math.max(CP[1], CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > h) h = maxy; + if (miny < 0) { + canvasY += miny; var oy = Math.abs(miny); + h += oy; CP[1] += oy; sy += oy; ty +=oy; CP2[1] += oy; + } + + // return [ canvasx, canvasy, canvasWidth, canvasHeight, + // sourceX, sourceY, targetX, targetY, + // controlPoint1_X, controlPoint1_Y, controlPoint2_X, controlPoint2_Y + return [canvasX, canvasY, w, h, sx, sy, tx, ty, CP[0], CP[1], CP2[0], CP2[1] ]; + }; + + this.paint = function(d, ctx) { + + /*var img = new Image(); + img.src = "../img/pattern.jpg"; + ctx.fillStyle = ctx.createPattern(img, 'repeat-y');*/ + + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + //ctx.fill(); + } + }; + + + /** + * Types of endpoint UIs. we supply four - a circle of default radius 10px, a rectangle of + * default size 20x20, an image (with no default), and a Triangle, of default size 15. + * you can supply others of these if you want to - see the documentation for a howto. + */ + + /** + * a round endpoint, with default radius 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + } + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * A Rectangular endpoint, with default size 20x20. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + //todo: the fillStyle needs some thought. we want to support a few options: + // 1. nothing supplied; use the stroke color or the default if no stroke color. + // 2. a fill color supplied - use it + // 3. a gradient supplied - use it + // 4. setting the endpoint to the same color as the bg of the element it is attached to. + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + jsPlumb.Endpoints.Triangle = function(params) { + + params = params || { width:15, height:15 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - width/2; + var y = anchorPoint[1] - height/2; + + jsPlumb.sizeCanvas(canvas, x, y, width, height); + + var ctx = canvas.getContext('2d'); + var + offsetX = 0, + offsetY = 0, + angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = endpointStyle.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + ctx.fill(); + + }; + }; + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' property in the params object.. + */ + jsPlumb.Endpoints.Image = function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + }; +})(); \ No newline at end of file diff --git a/archive/1.2.2/mootools.jsPlumb-1.2.2-RC1.js b/archive/1.2.2/mootools.jsPlumb-1.2.2-RC1.js new file mode 100644 index 000000000..1917043b5 --- /dev/null +++ b/archive/1.2.2/mootools.jsPlumb-1.2.2-RC1.js @@ -0,0 +1,288 @@ +/* + * mootools.jsPlumb 1.2.2-RC1 + * + * MooTools specific functionality for jsPlumb. + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + * NOTE: for production usage you should use mootools.jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, + * in a minified file. + * + * Dual licensed under MIT and GPL2. + * + */ + +(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function() { + this.parent(); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}; + var _droppableOptions = {}; + var _draggablesByScope = {}; + var _draggablesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }; + + + jsPlumb.CurrentLibrary = { + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' + }, + + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).grab(child); + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + getElementObject : function(el) { + return $(el); + }, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + initDraggable : function(el, options) { + var id = jsPlumb.getId(el); + var drag = _draggablesById[id]; + if (!drag) { + var originalZIndex = 0, originalCursor = null; + var dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + options['onStart'] = jsPlumb.wrap(options['onStart'], function() + { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() + { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + }); + + // DROPPABLES: + var scope = options['scope'] || jsPlumb.Defaults.Scope; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop'); + } + }; + + drag = new Drag.Move(el, options); + _add(_draggablesByScope, scope, drag); + _add(_draggablesById, el.get("id"), drag); + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + initDroppable : function(el, options) { + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _add(_droppables, scope, el); + var id = jsPlumb.getId(el); + _droppableOptions[id] = options; + var filterFunc = function(entry) { + return entry.element != el; + }; + var draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + if (typeof Drag != undefined) + return typeof Drag.Move != undefined; + return false; + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + m.start(properties); + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[0]; + return { left: ui.offsetLeft, top: ui.offsetTop }; + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + }, + + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).dispose(); // ?? + }, + + getScrollLeft : function(el) { + return null; + }, + + getScrollTop : function(el) { + return null; + } + }; +})(); diff --git a/archive/1.2.2/mootools.jsPlumb-1.2.2-all-min.js b/archive/1.2.2/mootools.jsPlumb-1.2.2-all-min.js new file mode 100644 index 000000000..75b2cd7ac --- /dev/null +++ b/archive/1.2.2/mootools.jsPlumb-1.2.2-all-min.js @@ -0,0 +1,54 @@ +(function(){var x=function(){var f=this,i=/MSIE/.test(navigator.userAgent)&&!window.opera,q=null,o=function(){e.repaintEverything()},k=true,l={},g={},h={},p=[],u={},G={},J=true,D=[],K={},I="DEFAULT",fa=1200,S=function(a,b,c){var d=function(n,r){if(n===r)return true;else if(typeof n=="object"&&typeof r=="object"){var s=true;for(var v in n)if(!d(n[v],r[v])){s=false;break}for(v in r)if(!d(r[v],n[v])){s=false;break}return s}};c=+c||0;for(var j=a.length;c=0){delete a[c];a.splice(c,1);return true}}return false},ka=function(a,b){var c=H(a,"id");da(c,function(d){d.canvas.style.display=b})},oa=function(a){da(a,function(b){b.canvas.style.display="none"==b.canvas.style.display?"block":"none"})},Q=function(a,b,c){if(c||b==null){b=y(a);D[a]=ia(b);p[a]=ha(b)}else p[a]=b},M=function(a,b){a=a||function(){};b=b||function(){};return function(){var c=null;try{c=b.apply(this,arguments)}catch(d){}try{a.apply(this, +arguments)}catch(j){}return c}},la=function(a){var b=this;this.x=a.x||0;this.y=a.y||0;var c=a.orientation||[0,0],d=null;this.offsets=a.offsets||[0,0];this.compute=function(j,n,r){d=[j[0]+b.x*n[0]+b.offsets[0],j[1]+b.y*n[1]+b.offsets[1]];n=r?r.container:null;j={left:0,top:0};if(n!=null){var s=y(n);n=ha(s);r=e.CurrentLibrary.getScrollLeft(s);s=e.CurrentLibrary.getScrollTop(s);j.left=n.left-r;j.top=n.top-s;d[0]-=j.left;d[1]-=j.top}return d};this.getOrientation=function(){return c};this.equals=function(j){if(!j)return false; +var n=j.getOrientation(),r=this.getOrientation();return this.x==j.x&&this.y==j.y&&this.offsets[0]==j.offsets[0]&&this.offsets[1]==j.offsets[1]&&r[0]==n[0]&&r[1]==n[1]}},pa=function(a){var b=a.reference,c=ia(y(a.referenceCanvas)),d=0,j=0,n=null;this.compute=function(r){j=d=0;return[r[0]+c[0]/2,r[1]+c[1]/2]};this.getOrientation=function(){if(n)return n;else{var r=b.getOrientation();return[Math.abs(r[0])*d*-1,Math.abs(r[1])*j*-1]}};this.over=function(r){n=r.getOrientation()};this.out=function(){n=null}}, +ma=function(a){var b=this,c=new String("_jsplumb_c_"+(new Date).getTime());this.getId=function(){return c};this.container=a.container||f.Defaults.Container;this.source=y(a.source);this.target=y(a.target);if(a.sourceEndpoint)this.source=a.sourceEndpoint.getElement();if(a.targetEndpoint)this.target=a.targetEndpoint.getElement();this.sourceId=H(this.source,"id");this.targetId=H(this.target,"id");this.endpointsOnTop=a.endpointsOnTop!=null?a.endpointsOnTop:true;this.scope=a.scope;this.endpoints=[];this.endpointStyles= +[];var d=function(r,s,v,F){if(r){b.endpoints[s]=r;r.addConnection(b)}else{if(!v.endpoints)v.endpoints=[null,null];r=v.endpoints[s]||v.endpoint||f.Defaults.Endpoints[s]||e.Defaults.Endpoints[s]||f.Defaults.Endpoint||e.Defaults.Endpoint||new e.Endpoints.Dot;if(!v.endpointStyles)v.endpointStyles=[null,null];var B=v.endpointStyles[s]||v.endpointStyle||f.Defaults.EndpointStyles[s]||e.Defaults.EndpointStyles[s]||f.Defaults.EndpointStyle||e.Defaults.EndpointStyle,z=v.anchors?v.anchors[s]:f.Defaults.Anchors[s]|| +e.Defaults.Anchors[s]||f.Defaults.Anchor||e.Defaults.Anchor||e.Anchors.BottomCenter,E=v.uuids?v.uuids[s]:null;v=v.uuids?new X({style:B,endpoint:r,connections:[b],uuid:E,anchor:z,source:F,container:b.container}):new X({style:B,endpoint:r,connections:[b],anchor:z,source:F,container:b.container});return b.endpoints[s]=v}},j=d(a.sourceEndpoint,0,a,b.source);j&&P(l,this.sourceId,j);(d=d(a.targetEndpoint,1,a,b.target))&&P(l,this.targetId,d);if(!this.scope)this.scope=this.endpoints[0].scope;this.connector= +this.endpoints[0].connector||this.endpoints[1].connector||a.connector||f.Defaults.Connector||e.Defaults.Connector||new e.Connectors.Bezier;this.paintStyle=this.endpoints[0].connectorStyle||this.endpoints[1].connectorStyle||a.paintStyle||f.Defaults.PaintStyle||e.Defaults.PaintStyle;Q(this.sourceId);Q(this.targetId);d=p[this.sourceId];j=D[this.sourceId];d=this.endpoints[0].anchor.compute([d.left,d.top],j,this.endpoints[0]);this.endpoints[0].paint(d);d=p[this.targetId];j=D[this.targetId];d=this.endpoints[1].anchor.compute([d.left, +d.top],j,this.endpoints[1]);this.endpoints[1].paint(d);var n=ja(e.connectorClass,b.container);this.canvas=n;this.paint=function(r,s,v){q&&q.debug("Painting Connection; element in motion is "+r+"; ui is ["+s+"]; recalc is ["+v+"]");var F=r!=this.sourceId,B=F?this.sourceId:this.targetId,z=F?0:1,E=F?1:0;if(this.canvas.getContext){Q(r,s,v);Q(B);v=p[r];s=p[B];r=D[r];var t=D[B];B=n.getContext("2d");v=this.endpoints[E].anchor.compute([v.left,v.top],r,this.endpoints[E]);this.endpoints[E].anchor.getOrientation(); +s=this.endpoints[z].anchor.compute([s.left,s.top],t,this.endpoints[z]);this.endpoints[z].anchor.getOrientation();z=this.connector.compute(v,s,this.endpoints[E].anchor,this.endpoints[z].anchor,this.paintStyle.lineWidth);e.sizeCanvas(n,z[0],z[1],z[2],z[3]);e.extend(B,this.paintStyle);if(this.paintStyle.gradient&&!i){F=F?B.createLinearGradient(z[4],z[5],z[6],z[7]):B.createLinearGradient(z[6],z[7],z[4],z[5]);for(E=0;E=0){var w=m.endpoints[0]==b?m.endpoints[1]:m.endpoints[0];b.connections.splice(A,1);w.detach(m)}};this.detachAll=function(){for(;b.connections.length>0;)b.detach(b.connections[0])};this.detachFrom=function(m){for(var A=[],w=0;w=v};this.setDragAllowedWhenFull=function(m){b.dragAllowedWhenFull=m};this.equals=function(m){return this.anchor.equals(m.anchor)&& +true};this.paint=function(m,A,w){q&&q.debug("Painting Endpoint with elementId ["+s+"]; anchorPoint is ["+m+"]");if(m==null){m=p[s];var N=D[s];if(m==null||N==null){Q(s);m=p[s];N=D[s]}m=b.anchor.compute([m.left,m.top],N,b)}d.paint(m,b.anchor.getOrientation(),w||b.canvas,j,A||j)};this.removeConnection=this.detach;if(a.isSource&&e.CurrentLibrary.isDragSupported(n)){var E=null;var t=c=null,na=false,R=null,C=a.dragOptions||{},O=e.extend({},e.CurrentLibrary.defaultDragOptions);C=e.extend(O,C);C.scope=C.scope|| +b.scope;O=e.CurrentLibrary.dragEvents.start;var Y=e.CurrentLibrary.dragEvents.stop,Z=e.CurrentLibrary.dragEvents.drag;C[O]=M(C[O],function(){t=b.connections.length0)for(var d=0;d0)for(var j=0;j +0?S(d,n)!=-1:true){b[n]=[];for(c=0;c0?S(j,r.sourceId)!=-1:true)&&(a.length>0?S(a,r.targetId)!=-1:true)&&b[n].push({sourceId:r.sourceId,targetId:r.targetId,source:r.source,target:r.target,sourceEndpoint:r.endpoints[0],targetEndpoint:r.endpoints[1]})}}return b};this.getDefaultScope=function(){return I};this.getEndpoint=function(a){return g[a]};this.getId=T;this.hide=function(a){ka(a,"none")};this.makeAnchor=function(a,b){var c={};if(arguments.length==1)e.extend(c, +a);else{c={x:a,y:b};if(arguments.length>=4)c.orientation=[arguments[2],arguments[3]];if(arguments.length==6)c.offsets=[arguments[4],arguments[5]]}var d=new la(c);d.clone=function(){return new la(c)};return d};this.repaint=function(a){var b=function(d){d=y(d);V(d)};if(typeof a=="object")for(var c=0;cl)l=q;if(i<0){h+=i;i=Math.abs(i);l+=i;D[0]+=i;u+=i;J+=i;f[0]+=i}i=Math.min(Math.min(G,k),Math.min(D[1], +f[1]));q=Math.max(Math.max(G,k),Math.max(D[1],f[1]));if(q>g)g=q;if(i<0){p+=i;i=Math.abs(i);g+=i;D[1]+=i;G+=i;k+=i;f[1]+=i}return[h,p,l,g,u,G,J,k,D[0],D[1],f[0],f[1]]};this.paint=function(f,i){i.beginPath();i.moveTo(f[4],f[5]);i.bezierCurveTo(f[8],f[9],f[10],f[11],f[6],f[7]);i.stroke()}};jsPlumb.Endpoints.Dot=function(x){x=x||{radius:10};var e=this;this.radius=x.radius;var f=0.5*this.radius,i=this.radius/3,q=function(o){try{return parseInt(o)}catch(k){if(o.substring(o.length-1)=="%")return parseInt(o.substring(0, +o-1))}};this.paint=function(o,k,l,g,h){var p=g.radius||e.radius;jsPlumb.sizeCanvas(l,o[0]-p,o[1]-p,p*2,p*2);o=l.getContext("2d");l=jsPlumb.extend({},g);if(l.fillStyle==null)l.fillStyle=h.strokeStyle;jsPlumb.extend(o,l);h=/MSIE/.test(navigator.userAgent)&&!window.opera;if(g.gradient&&!h){h=g.gradient;l=f;var u=i;if(h.offset)l=q(h.offset);if(h.innerRadius)u=q(h.innerRadius);h=[l,u];k=o.createRadialGradient(p,p,p,p+(k[0]==1?h[0]*-1:h[0]),p+(k[1]==1?h[0]*-1:h[0]),h[1]);for(h=0;h endpoint lists. an element can have an arbitrary number of endpoints on it, + * and not all of them have to be connected to anything. + */ + var endpointsByElement = {}; + var endpointsByUUID = {}; + var connectionsByScope = {}; + var offsets = []; + var floatingConnections = {}; + var draggableStates = {}; + var _draggableByDefault = true; + var sizes = []; + var listeners = {}; // a map: keys are event types, values are lists of listeners. + var DEFAULT_SCOPE = 'DEFAULT'; + + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + var _findIndex = function( a, v, b, s ) { + var _eq = function(o1, o2) { + if (o1 === o2) return true; + else if (typeof o1 == 'object' && typeof o2 == 'object') { + var same = true; + for(var propertyName in o1) { + if(!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for(var propertyName in o2) { + if(!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for( var i = +b || 0, l = a.length; i < l; i++ ) { + if( _eq(a[i], v)) return i; + } + return -1; + }; + + /** + * adds a listener for the specified event type. + */ + var _addListener = function(eventType, listener) { + var doOne = function(e, l) { + _addToList(listeners, e, l); + }; + if (typeof eventType == 'object' && eventType.length) { + for (var i = 0; i < eventType.length; i++) doOne(eventType[i], listener); + } else doOne(eventType, listener); + }; + + /** + * helper method to add an item to a list, creating the list if it does not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }; + + var _appendElement = function(canvas, parent) { + if (!parent) document.body.appendChild(canvas); + else jsPlumb.CurrentLibrary.appendElement(canvas, parent); + }; + + /** + * Handles the dragging of an element. + * @param element jQuery element + * @param ui UI object from current library's event system + */ + var _draw = function(element, ui) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints) { + var timestamp = '' + (new Date().getTime()); + _updateOffset(id, ui); + var myOffset = offsets[id]; + var myWH = sizes[id]; + // loop through endpoints for this element + for (var i = 0; i < endpoints.length; i++) { + var e = endpoints[i]; + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH, e); + e.paint(anchorLoc); + var l = e.connections; + for (var j = 0; j < l.length; j++) + l[j].paint(id, ui, false, timestamp); // ...and paint them. + } + } + }; + + /** + * helper function: the second argument is a function taking two args - the first is an id + * ui element from the current library (or list of them), and the second is the function + * to run. + */ + var _elementProxy = function(element, fn) { + var retVal = null; + // TODO this might be jquery specific. in fact it probably is. + if (typeof element == 'object' && element.length) { + retVal = []; + for (var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]); + var id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } + else { + var el = _getElementObject(element); + var id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + + return retVal; + }; + + /** + * fires an event of the given type. + */ + var _fireEvent = function(eventType, data) { + var l = listeners[eventType]; + if (l) { + for (var i in l) { + try { + l[i][eventType](data); + } + catch (e) { + + _log("while firing event [" + eventType + "]; listener failed like this: " + e); + } + } + } + }; + + var _log = function(msg) { + //if (console) console.log(msg); + }; + + var _logFnCall = function(fn, args) { + /*if (console) { + var c = args.callee.caller.toString(); + var i = c.indexOf("{"); + var msg = fn + ' : ' + c.substring(0, i); + console.log(msg); + }*/ + }; + + var cached = {}; + var __getElementObject = function(el) { + + if (typeof(el)=='string') { + var e = cached[el]; + if (!e) { + e = jsPlumb.CurrentLibrary.getElementObject(el); + cached[el] = e; + } + return e; + } + else { + return jsPlumb.CurrentLibrary.getElementObject(el); + } + }; + + + /** + * gets the named attribute from the given element (id or element object) + */ + var _getAttribute = function(el, attName) { + var ele = __getElementObject(el); + return jsPlumb.CurrentLibrary.getAttribute(ele, attName); + }; + + var _setAttribute = function(el, attName, attValue) { + var ele = __getElementObject(el); + jsPlumb.CurrentLibrary.setAttribute(ele, attName, attValue); + }; + + var _addClass = function(el, clazz) { + var ele = __getElementObject(el); + jsPlumb.CurrentLibrary.addClass(ele, clazz); + }; + + var _getElementObject = function(elId) { + return __getElementObject(elId); + }; + + var _getOffset = function(el) { + var ele = __getElementObject(el); + return jsPlumb.CurrentLibrary.getOffset(ele); + }; + + var _getSize = function(el) { + var ele = __getElementObject(el); + return jsPlumb.CurrentLibrary.getSize(ele); + }; + + /** + * gets an id for the given element, creating and setting one if necessary. + */ + var _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id) { + //check if fixed uuid parameter is given + if(arguments.length == 2) + id = uuid; + else + id = "_jsPlumb_" + new String((new Date()).getTime()); + _setAttribute(ele, "id", id); + } + return id; + }; + + /** + * gets an Endpoint by uuid. + */ + var _getEndpoint = function(uuid) { + return endpointsByUUID[uuid]; + }; + + /** + * inits a draggable if it's not already initialised. + * + * TODO: we need to hook in each library to this method. they need to be given the opportunity to + * wrap/insert lifecycle functions, because each library does different things. for instance, jQuery + * supports the notion of 'revert', which will clean up a dragged endpoint; MooTools does not. jQuery + * also supports 'opacity' and MooTools does not; jQuery supports z-index of the draggable; MooTools + * does not. i could go on. the point is...oh. initDraggable can do this. ok. + */ + var _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + // dragging + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend({}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + } + } + }; + + + /** + * helper to create a canvas. + * @param clazz optional class name for the canvas. + */ + var _newCanvas = function(clazz, parent, uuid) { + var canvas = document.createElement("canvas"); + _appendElement(canvas, parent); + canvas.style.position="absolute"; + if (clazz) { canvas.className=clazz; } + + // set an id. if no id on the element and if uuid was supplied it will be used, otherwise we'll create one. + _getId(canvas, uuid); + + if (ie) { + // for IE we have to set a big canvas size. actually you can override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + /** + * performs the given function operation on all the connections found for the given element + * id; this means we find all the endpoints for the given element, and then for each endpoint + * find the connectors connected to it. then we pass each connection in to the given + * function. + */ + var _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + for (var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }; + /** + * perform an operation on all elements. + */ + var _operationOnAll = function(func) { + for (var elId in endpointsByElement) { + _operation(elId, func); + } + }; + /** + * helper to remove an element from the DOM. + */ + var _removeElement = function(element, parent) { + if (element != null) { + if (!parent) { + try { document.body.removeChild(element); } + catch (e) { } + } else { + jsPlumb.CurrentLibrary.removeElement(element, parent); + } + } + }; + /** + * helper to remove a list of elements from the DOM. + */ + var _removeElements = function(elements, parent) { + for (var i in elements) + _removeElement(elements[i], parent); + }; + /** + * helper method to remove an item from a list. + */ + var _removeFromList = function(map, key, value) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete( l[i] ); + l.splice( i, 1 ); + return true; + } + } + return false; + }; + /** + * Sets whether or not the given element(s) should be draggable, regardless of what a particular + * plumb command may request. + * + * @param element May be a string, a jQuery elements, or a list of strings/jquery elements. + * @param draggable Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + var _helper = function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }; + + return _elementProxy(element, _helper); + }; + /** + * private method to do the business of hiding/showing. + * @param el either Id of the element in question or a jquery object for the element. + * @param state String specifying a value for the css 'display' property ('block' or 'none'). + */ + var _setVisible = function(el, state) { + var elId = _getAttribute(el, "id"); + var f = function(jpc) { + jpc.canvas.style.display = state; + }; + _operation(elId, f); + }; + /** + * toggles the draggable state of the given element(s). + * @param el either an id, or a jquery object, or a list of ids/jquery objects. + */ + var _toggleDraggable = function(el) { + var fn = function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }; + return _elementProxy(el, fn); + }; + /** + * private method to do the business of toggling hiding/showing. + * @param elId Id of the element in question + */ + var _toggleVisible = function(elId) { + var f = function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }; + + _operation(elId, f); + + //todo this should call _elementProxy, and pass in the _operation(elId, f) call as a function. cos _toggleDraggable does that. + }; + /** + * updates the offset and size for a given element, and stores the values. + * if 'offset' is not null we use that (it would have been passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we use the offset, outerWidth and outerHeight methods to get + * the current values. + */ + var _updateOffset = function(elId, offset, recalc) { + if (recalc || offset == null) { // if forced repaint or no offset available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + } else { + offsets[elId] = offset; + } + }; + + /** + * wraps one function with another, creating a placeholder for the wrapped function + * if it was null. this is used to wrap the various drag/drop event functions - to allow + * jsPlumb to be notified of important lifecycle events without imposing itself on the user's + * drag/drop functionality. + * TODO: determine whether or not we should support an error handler concept, if one of the functions fails. + */ + var _wrap = function(wrappedFunction, newFunction) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { r = newFunction.apply(this, arguments); } + catch (e) { + _log('jsPlumb function failed : ' + e); + } + try { wrappedFunction.apply(this, arguments); + } + catch (e) { + _log('wrapped function failed : ' + e); + } + return r; + }; + }; + + /* + Class: Anchor + Models a position relative to the origin of some element that an Endpoint can be located. + */ + /* + Function: Anchor + + Constructor for the Anchor class + + Parameters: + params - Anchor parameters. This should contain three values, and may optionally have an 'offsets' argument: + + - x : the x location of the anchor as a fraction of the total width. + - y : the y location of the anchor as a fraction of the total height. + - orientation : an [x,y] array indicating the general direction a connection from the anchor should go in. for more info on this, see the documentation, or the docs in jquery-jsPlumb-defaults-XXX.js for the default Anchors. + - offsets : an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. may be null. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; this.y = params.y || 0; + var orientation = params.orientation || [0,0]; + var lastTimestamp = null; + var lastReturnValue = null; + this.offsets = params.offsets || [0,0]; + //TODO: fix this properly. since Anchors are often static, this timestamping business + // does not work at all well. the timestamp should be inside the Endpoint, because they + // are _never_ static. the method that Connection uses to find an anchor location should + // be done through the Endpoint class, which can then deal with the timestamp. + // and, in fact, we should find out whether or not we even get a speed enhancement from doing + // this. + this.compute = function(xy, wh, element) { + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1]]; + var container = element? element.container : null; + var containerAdjustment = {left:0, top:0 }; + if (container != null) { + var eo = _getElementObject(container); + var o = _getOffset(eo); + var sl = jsPlumb.CurrentLibrary.getScrollLeft(eo); + var st = jsPlumb.CurrentLibrary.getScrollTop(eo); + containerAdjustment.left = o.left - sl; + containerAdjustment.top = o.top - st; + lastReturnValue[0] = lastReturnValue[0] - containerAdjustment.left; + lastReturnValue[1] = lastReturnValue[1] - containerAdjustment.top; + } + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y && this.offsets[0] == anchor.offsets[0] && this.offsets[1] == anchor.offsets[1] && o[0] == ao[0] && o[1] == ao[1]; + }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from its position relative to the anchor it is floating relative to. + * + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(_getElementObject(refCanvas)); + + // these are used to store the current relative position of our anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating anchor is hovering over another anchor. + var orientation = null; + + this.compute = function(xy, wh, txy, twh) { + // set these for the getOrientation method to use. + xDir = 0;//xy[0] < txy[0] ? -1 : xy[0] == txy[0] ? 0 : 1; + yDir = 0;//xy[1] < txy[1] ? -1 : xy[1] == txy[1] ? 0 : 1; + return [xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other anchor: if it declares zero for some direction, we declare zero too. + // this might not be the most awesome. perhaps we can come up with a better way. it's just so that the line we draw looks + // like it makes sense. maybe this wont make sense. + return [Math.abs(o[0]) * xDir * -1, Math.abs(o[1]) * yDir * -1]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering over another anchor; + * we want to assume that anchor's orientation for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no longer hovering + * over another anchor; we should resume calculating orientation as we normally do. + */ + this.out = function() { + orientation = null; + }; + }; + + // ************** connection + // **************************************** + /** + * allowed params: + * source: source element (string or a jQuery element) (required) + * target: target element (string or a jQuery element) (required) + * + * anchors: optional array of anchor placements. defaults to BottomCenter for source + * and TopCenter for target. + */ + var Connection = function(params) { + + // ************** get the source and target and register the connection. ******************* + var self = this; + var id = new String('_jsplumb_c_' + (new Date()).getTime()); + this.getId = function() { return id; }; + this.container = params.container || _currentInstance.Defaults.Container; // may be null; we will append to the body if so. + // get source and target as jQuery objects + this.source = _getElementObject(params.source); + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. + if (params.sourceEndpoint) + this.source = params.sourceEndpoint.getElement(); + if (params.targetEndpoint) + this.target = params.targetEndpoint.getElement(); + this.sourceId = _getAttribute(this.source, "id"); + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + this.scope = params.scope; // scope may have been passed in to the connect call. + // if it wasn't, we will pull it from the source endpoint, + // after having initialised the endpoints. + // init endpoints + this.endpoints = []; + this.endpointStyles = []; + var prepareEndpoint = function(existing, index, params, element) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } + else { + if(!params.endpoints) params.endpoints = [null,null]; + var ep = params.endpoints[index] || params.endpoint || _currentInstance.Defaults.Endpoints[index] || jsPlumb.Defaults.Endpoints[index] || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || new jsPlumb.Endpoints.Dot(); + if (!params.endpointStyles) params.endpointStyles = [null,null]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + var a = params.anchors ? params.anchors[index] : _currentInstance.Defaults.Anchors[index] || jsPlumb.Defaults.Anchors[index] || _currentInstance.Defaults.Anchor || jsPlumb.Defaults.Anchor || jsPlumb.Anchors.BottomCenter; + var u = params.uuids ? params.uuids[index] : null; + if(params.uuids) + var e = new Endpoint({style:es, endpoint:ep, connections:[self], uuid:u, anchor:a, source:element, container:self.container }); + else + var e = new Endpoint({style:es, endpoint:ep, connections:[self], anchor:a, source:element, container:self.container }); + + self.endpoints[index] = e; + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || _currentInstance.Defaults.Connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + this.paintStyle = this.endpoints[0].connectorStyle || this.endpoints[1].connectorStyle || params.paintStyle || _currentInstance.Defaults.PaintStyle || jsPlumb.Defaults.PaintStyle; + + _updateOffset(this.sourceId); + _updateOffset(this.targetId); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var anchorLoc = this.endpoints[0].anchor.compute([myOffset.left, myOffset.top], myWH, this.endpoints[0]); + this.endpoints[0].paint(anchorLoc); + myOffset = offsets[this.targetId]; myWH = sizes[this.targetId]; + anchorLoc = this.endpoints[1].anchor.compute([myOffset.left, myOffset.top], myWH, this.endpoints[1]); + this.endpoints[1].paint(anchorLoc); + + // *************** create canvas on which the connection will be drawn ************ + var canvas = _newCanvas(jsPlumb.connectorClass, self.container); + this.canvas = canvas; + + /** + * paints the connection. + * @param elId Id of the element that is in motion + * @param ui current library's event system ui object (present if we came from a drag to get here) + * @param recalc whether or not to recalculate element sizes. this is true if a repaint caused this to be painted. + */ + this.paint = function(elId, ui, recalc, timestamp) { + + if (log) log.debug("Painting Connection; element in motion is " + elId + "; ui is [" + ui + "]; recalc is [" + recalc + "]"); + + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = !(elId == this.sourceId); + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + if (this.canvas.getContext) { + //if (recalc) { + _updateOffset(elId, ui, recalc); + _updateOffset(tId); // update the target if this is a forced repaint. otherwise, only the source has been moved. + //} + + var myOffset = offsets[elId]; + var otherOffset = offsets[tId]; + var myWH = sizes[elId]; + var otherWH = sizes[tId]; + + var ctx = canvas.getContext('2d'); + //TODO: why are these calculated again? they were calculated in the _draw function. + var sAnchorP = this.endpoints[sIdx].anchor.compute([myOffset.left, myOffset.top], myWH, this.endpoints[sIdx]); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.compute([otherOffset.left, otherOffset.top], otherWH, this.endpoints[tIdx]); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, this.paintStyle.lineWidth); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + jsPlumb.extend(ctx, this.paintStyle); + + if (this.paintStyle.gradient && !ie) { + var g = swap ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + for (var i = 0; i < this.paintStyle.gradient.stops.length; i++) + g.addColorStop(this.paintStyle.gradient.stops[i][0],this.paintStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + + this.connector.paint(dim, ctx); + } + }; + + //TODO: should this take a timestamp? probably. it reduces the amount of time + // spent figuring out anchor locations. + this.repaint = function() { + this.paint(this.sourceId, null, true); + }; + + _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + }; + + /* + Class: Endpoint + Models an endpoint. Can have one to N connections emanating from it (although how to handle that in the UI is + a very good question). also has a Canvas and paint style. + */ + + /* + Function: Endpoint + This is the Endpoint class constructor. + Parameters: + anchor - anchor for the endpoint, of type jsPlumb.Anchor. may be null. + endpoint - endpoint object, of type jsPlumb.Endpoint. may be null. + style - endpoint style, a js object. may be null. + source - element the endpoint is attached to, of type jquery object. Required. + canvas - canvas element to use. may be, and most often is, null. + connections - optional list of connections to configure the endpoint with. + isSource - boolean. indicates the endpoint can act as a source of new connections. optional. + dragOptions - if isSource is set to true, you can supply arguments for the jquery draggable method. optional. + connectionStyle - if isSource is set to true, this is the paint style for connections from this endpoint. optional. + connector - optional connector type to use. + isTarget - boolean. indicates the endpoint can act as a target of new connections. optional. + dropOptions - if isTarget is set to true, you can supply arguments for the jquery droppable method. optional. + reattach - optional boolean that determines whether or not the connections reattach after they + have been dragged off an endpoint and left floating. defaults to false: connections + dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + params = params || {}; + // make a copy. then we can use the wrapper function. + jsPlumb.extend({}, params); + var self = this; + var id = new String('_jsplumb_e_' + (new Date()).getTime()); + this.getId = function() { return id; }; + self.anchor = params.anchor || jsPlumb.Anchors.TopCenter; + var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot(); + var _style = params.style || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + this.connectorStyle = params.connectorStyle; + this.connector = params.connector; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + var _element = params.source; + var _uuid = params.uuid; + if (_uuid) endpointsByUUID[_uuid] = self; + this.container = params.container || jsPlumb.Defaults.Container; + var _elementId = _getAttribute(_element, "id"); + var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of. + this.canvas = params.canvas || _newCanvas(jsPlumb.endpointClass, this.container, params.uuid); + this.connections = params.connections || []; + this.scope = params.scope || DEFAULT_SCOPE; + var _reattach = params.reattach || false; + var floatingEndpoint = null; + var inPlaceCopy = null; + this.dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.addConnection = function(connection) { + self.connections.push(connection); + }; + this.detach = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + self.connections.splice(idx, 1); + t.detach(connection); + } + }; + + /** + * detaches all connections this Endpoint has + */ + this.detachAll = function() { + while(self.connections.length > 0) { + self.detach(self.connections[0]); + } + }; + + /** + * removes any connections from this Endpoint that are + * connected to the given target endpoint. + * @param targetEndpoint + */ + this.detachFrom = function(targetEndpoint) { + var c = []; + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for (var i = 0; i < c.length; i++) { + targetEndpoint.detach(c[i]); + self.detach(c[i]); + } + }; + + /** + * returns the DOM element this Endpoint is attached to. + * @returns + */ + this.getElement = function() { return _element; }; + + /** + * returns the UUID for this Endpoint, if there is one. + * @returns + */ + this.getUuid= function() { return _uuid; }; + + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + var e = new Endpoint({anchor:self.anchor, source:_element, style:_style, endpoint:_endpoint}); + return e; + }; + + + /** + * returns whether or not this endpoint is connected to the given endpoint. + * @param endpoint Endpoint to test. + * @since 1.1.1 + * + * todo: needs testing. will this work if the endpoint passed in is the source? + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + * @returns {Boolean} + */ + this.isFloating = function() { return floatingEndpoint != null; }; + /** + * first pass at default ConnectorSelector: returns the first connection, if we have any. + * modified a little, 5/10/2010: now it only returns a connector if we have not got equal to or more than _maxConnector connectors + * attached. otherwise it is assumed a new connector is ok. but note with this setup we can't select any other connection than the first + * one. what if this could return a list? that implies everything should work with a list - dragging etc. could be nasty. could also + * be cool. + */ + var connectorSelector = function() { + //return self.connections.length == 0 || self.connections.length < _maxConnections ? null : self.connections[0]; + if (self.connections.length < _maxConnections) return null; + //else if (self.connections.length == _maxConnections) return false; + else return self.connections[0]; + }; + + /** + * @returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); }; + + /** + * sets whether or not connnctions can be dragged from this Endpoint once it is full. you would use + * this in a UI in which you're going to provide some other way of breaking connections, if you need + * to break them at all. this property is by default true; use it in conjunction with the 'reattach' + * option on a connect call. + */ + this.setDragAllowedWhenFull = function(allowed) { + self.dragAllowedWhenFull = allowed; + }; + + /** + * a deep equals check. everything must match, including the anchor, styles, everything. + * TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor) && + true; + }; + + /** + * paints the Endpoint, recalculating offset and anchor positions if necessary. + */ + this.paint = function(anchorPoint, connectorPaintStyle, canvas) { + + if (log) log.debug("Painting Endpoint with elementId [" + _elementId + "]; anchorPoint is [" + anchorPoint + "]"); + + if (anchorPoint == null) { + // do we always want to force a repaint here? i dont think so! + var xy = offsets[_elementId]; + var wh = sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset(_elementId); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + anchorPoint = self.anchor.compute([xy.left, xy.top], wh, self); + } + _endpoint.paint(anchorPoint, self.anchor.getOrientation(), canvas || self.canvas, _style, connectorPaintStyle || _style); + }; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + + // is this a connection source? we make it draggable and have the drag listener + // maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + + // first the question is, how many connections are on this endpoint? if it's only one, then excellent. otherwise we will either need a way + // to select one connection from the list, or drag them all. if we had a pluggable 'ConnectorSelector' interface we could probably + // provide a way for people to implement their own UI components to do the connector selection. the question in that particular case would be how much + // information the interface needs from jsPlumb at execution time. if, however, we leave out the connector selection, and drag them all, + // that wouldn't be too hard to organise. perhaps that behaviour would be on a switch for the endpoint, or perhaps the ConnectorSelector + // interface returns a List, with the default implementation just returning everything. i think i like that. + // + // let's say for now that there is just one endpoint, cos we need to get this going before we can consider a list of them anyway. + // the major difference between that case and the case of a new endpoint is actually quite small - it's a question of where the + // Connection comes from. for a new one, we create a new one. obviously. otherwise we just get the jpc from the Endpoint + // (remember we're only assuming one connection right now). so all of the UI stuff we do to create the floating endpoint etc + // will still be valid, but when we stop dragging, we'll have to do something different. if we stop with a valid drop i think it will + // be the same process. but if we stop with an invalid drop we have to reset the Connection to how it was when we got it. + var start = function() { + + jpc = connectorSelector(); + if (self.isFull() && !self.dragAllowedWhenFull) return false; + + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + var nE = _getElementObject(n); + _appendElement(n); // + // create and assign an id, and initialize the offset. + //TODO can't this be replaced with a _getId call? + var id = "" + new String(new Date().getTime()); + _setAttribute(nE, "id", id); + _updateOffset(id); + // store the id of the dragging div and the source element. the drop function + // will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor({reference:self.anchor, referenceCanvas:self.canvas}); + floatingEndpoint = new Endpoint({ style:{fillStyle:'rgba(0,0,0,0)'}, endpoint:_endpoint, anchor:floatingAnchor, source:nE }); + + if (jpc == null) { + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = new Connection({ + sourceEndpoint:self, + targetEndpoint:floatingEndpoint, + source:_getElementObject(_element), + target:_getElementObject(n), + anchors:[self.anchor, floatingAnchor], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + connector: params.connector + }); + } else { + existingJpc = true; + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; + // probably we should remove the connection? and add it back if the user + // does not drop it somewhere proper. + self.removeConnection(jpc); + if (anchorIdx == 0){ + existingJpcParams = [jpc.source, jpc.sourceId]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + }else { + existingJpcParams = [jpc.target, jpc.targetId]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // todo unregister on stop + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + }; + + var dragOptions = params.dragOptions || { }; + + var defaultOpts = jsPlumb.extend({}, jsPlumb.CurrentLibrary.defaultDragOptions); + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + var startEvent = jsPlumb.CurrentLibrary.dragEvents['start']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements([n, floatingEndpoint.canvas]); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + if (_reattach) { + jpc.floatingAnchorIndex = null; + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } + else { + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeElement(jpc.canvas); + _removeFromList(connectionsByScope, jpc.scope, jpc); + _fireEvent("jsPlumbConnectionDetached", { + source:jpc.source, + target:jpc.target, + sourceId:jpc.sourceId, + targetId:jpc.targetId, + sourceEndpoint:jpc.endpoints[0], + targetEndpoint:jpc.endpoints[1] + }); + } + } else { + _removeElement(jpc.canvas); + //if (jpc.endpoints[1]) alert("target set"); + self.removeConnection(jpc); + } + } + jpc = null; + delete floatingEndpoint; + delete inPlaceCopy; + self.paint(); + } + ); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + }; + + // connector target + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend({}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + dropOptions[dropEvent]= _wrap(dropOptions[dropEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable), "dragId"); + var elId = _getAttribute(_getElementObject(draggable),"elId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + var oidx = idx == 0 ? 1 : 0; + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + //todo test that the target is not full. + // remove this jpc from the current endpoint + jpc.endpoints[idx].removeConnection(jpc); + if (jpc.suspendedEndpoint) + jpc.suspendedEndpoint.removeConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + // add the jpc to the other endpoint too. + jpc.endpoints[oidx].addConnection(jpc); + + _addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + jsPlumb.repaint(elId); + _fireEvent("jsPlumbConnection", { + source:jpc.source, + target:jpc.target, + sourceId:jpc.sourceId, + targetId:jpc.targetId, + sourceEndpoint:jpc.endpoints[0], + targetEndpoint:jpc.endpoints[1] + }); + } + // else there must be some cleanup required. + + delete floatingConnections[id]; + }); + + dropOptions[overEvent]= _wrap(dropOptions[overEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + }); + + jsPlumb.CurrentLibrary.initDroppable(_getElementObject(self.canvas), dropOptions); + } + + return self; + }; + + /* + Property: Defaults + These are the default settings for jsPlumb, that is what will be used if you do not supply specific pieces of information + to the various API calls. A convenient way to implement your own look and feel can be to override these defaults by including a script + somewhere after the jsPlumb include, but before you make any calls to jsPlumb, for instance in this example we set the PaintStyle to be + a blue line of 27 pixels: + > jsPlumb.Defaults.PaintStyle = { lineWidth:27, strokeStyle:'blue' } + */ + this.Defaults = { + Anchor : null, + Anchors : [ null, null ], + Connector : null, + Container : null, + DragOptions: { }, + DropOptions: { }, + Endpoint : null, + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : null }, + EndpointStyles : [ null, null ], + MaxConnections : null, + PaintStyle : { lineWidth : 10, strokeStyle : 'red' }, + Scope : "_jsPlumb_DefaultScope" + }; + + /* + Property: connectorClass + The CSS class to set on Connection canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = '_jsPlumb_connector'; + + /* + Property: endpointClass + The CSS class to set on Endpoint canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = '_jsPlumb_endpoint'; + + /* + Property: Anchors + Default jsPlumb Anchors. These are supplied in the file jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form .jsPlumb-all-x.x.x.js. You can provide your own Anchors by supplying them in a script that is loaded after jsPlumb, for instance: + > jsPlumb.Anchors.MyAnchor = { ....anchor code here. see the documentation. } + */ + this.Anchors = {}; + + /* + Property: Connectors + Default jsPlumb Connectors. These are supplied in the file jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form .jsPlumb-all-x.x.x.js. You can provide your own Connectors by supplying them in a script that is loaded after jsPlumb, for instance: + > jsPlumb.Connectors.MyConnector = { ....connector code here. see the documentation. } + */ + this.Connectors = {}; + + /* + Property: Endpoints + Default jsPlumb Endpoints. These are supplied in the file jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form .jsPlumb-all-x.x.x.js. You can provide your own Endpoints by supplying them in a script that is loaded after jsPlumb, for instance: + > jsPlumb.Endpoints.MyEndpoint = { ....endpoint code here. see the documentation. } + */ + this.Endpoints = {}; + + /* + Function: addEndpoint + Adds an Endpoint to a given element. + Parameters: + target - Element to add the endpoint to. either an element id, or a selector representing some element. + params - Object containing Endpoint options (more info required) + Returns: + The newly created Endpoint. + See Also: + + */ + this.addEndpoint = function(target, params) { + params = jsPlumb.extend({}, params); + var el = _getElementObject(target); + var id = _getAttribute(el,"id"); + params.source = el; + _updateOffset(id); + var e = new Endpoint(params); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id]; + var myWH = sizes[id]; + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH, e); + e.paint(anchorLoc); + return e; + }; + + /* + Function: addEndpoint + Adds a list of Endpoints to a given element. + Parameters: + target - element to add the endpoint to. either an element id, or a selector representing some element. + endpoints - List of objects containing Endpoint options. one Endpoint is created for each entry in this list. + Returns: + List of newly created Endpoints, one for each entry in the 'endpoints' argument. + See Also: + + */ + this.addEndpoints = function(target, endpoints) { + var results = []; + for (var i = 0; i < endpoints.length; i++) { + results.push(jsPlumb.addEndpoint(target, endpoints[i])); + } + return results; + }; + + /* + Function: animate + Wrapper around supporting library's animate function; injects a call to jsPlumb in the 'step' function (creating it if necessary). + This only supports the two-arg version of the animate call in jQuery - the one that takes an 'options' object as the second arg. + MooTools has only one method - a two arg one. Which is handy. + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the standard jQuery animate call. + options - The 'options' argument you want passed to the standard jQuery animate call. + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el); + var id = _getAttribute(el,"id"); + //TODO this is not agnostic yet. + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() + { + _currentInstance.repaint(id); + }); + + // you know, probably onComplete should repaint too. that will help keep up + // with fast animations. + options[completeFunction] = _wrap(options[completeFunction], function() + { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /** + * clears the cache used to lookup elements by their id. if you remove any elements + * from the DOM you should call this to ensure that if you add them back in jsPlumb does not + * have a stale handle. + */ + this.clearCache = function() { + delete cached; + cached = {}; + }; + + /* + Function: connect + Establishes a connection between two elements. + Parameters: + params - Object containing setup for the connection. see documentation. + Returns: + The newly created Connection. + */ + this.connect = function(params) { + var _p = jsPlumb.extend({}, params); + // test for endpoint uuids to connect + if (params.uuids) { + var _resolveByUuid = function(idx) { + var e = _getEndpoint(params.uuids[idx]); + if (!e) throw ("Endpoint with UUID " + params.uuids[idx] + " not found."); + return e; + }; + _p.sourceEndpoint = _resolveByUuid(0); + _p.targetEndpoint = _resolveByUuid(1); + } + + // now ensure that if we do have Endpoints already, they're not full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log("could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log("could not add connection; target endpoint is full"); + return; + } + + var jpc = new Connection(_p); + // add to list of connections (by scope). + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + _fireEvent("jsPlumbConnection", { + source:jpc.source, + target:jpc.target, + sourceId:jpc.sourceId, + targetId:jpc.targetId, + sourceEndpoint:jpc.endpoints[0], + targetEndpoint:jpc.endpoints[1] + }); + // force a paint + _draw(jpc.source); + + return jpc; + }; + + var fireDetachEvent = function(jpc) { + _fireEvent("jsPlumbConnectionDetached", { + source:jpc.source, + target:jpc.target, + sourceId:jpc.sourceId, + targetId:jpc.targetId, + sourceEndpoint:jpc.endpoints[0], + targetEndpoint:jpc.endpoints[1] + }); + }; + + /* + Function: detach + Removes a connection. + Parameters: + sourceId - Id of the first element in the connection. A String. + targetId - iI of the second element in the connection. A String. + Returns: + true if successful, false if not. + */ + this.detach = function(sourceId, targetId) { + if (arguments.length == 2) { + var f = function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + fireDetachEvent(jpc); + + return true; + } + + }; + + // todo: how to cleanup the actual storage? a third arg to _operation? + _operation(sourceId, f); + } + // this is the new version of the method, taking a JS object like the connect method does. + else if (arguments.length == 1) { + var _p = jsPlumb.extend({}, sourceId); // a backwards compatibility hack: sourceId should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1])); + } + else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } + else { + sourceId = _getId(_p.source); + targetId = _getId(_p.target); + var f = function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + fireDetachEvent(jpc); + + return true; + } + + }; + + // todo: how to cleanup the actual storage? a third arg to _operation? + _operation(sourceId, f); + } + + // fire an event + /*_fireEvent("jsPlumbDetach", { + source:jpc.source, + target:jpc.target, + sourceId:jpc.sourceId, + targetId:jpc.targetId, + sourceEndpoint:jpc.endpoints[0], + targetEndpoint:jpc.endpoints[1] + });*/ + /*// force a paint + _draw(jpc.source); + + return jpc;*/ + } + }; + + /* + Function: detachAll + Removes all an element's connections. + Parameters: + el - either the id of the element, or a selector for the element. + Returns: + void + */ + this.detachAll = function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + var c = endpoints[i].connections.length; + if (c > 0) { + for (var j = 0; j < c; j++) { + var jpc = endpoints[i].connections[0]; + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + fireDetachEvent(jpc); + } + } + } + } + }; + + /* + Function: detachEverything + Remove all Connections from all elements, but leaves Endpoints in place. + Returns: + void + See Also: + + */ + this.detachEverything = function() { + for(var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + var c = endpoints[i].connections.length; + if (c > 0) { + for (var j = 0; j < c; j++) { + var jpc = endpoints[i].connections[0]; + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + fireDetachEvent(jpc); + } + } + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + /* + Function: draggable + initialises the draggability of some element or elements. + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for (var i = 0; i < el.length; i++) + { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) + _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + Parameters: + o1 - object to extend + o2 - object to extend o1 with + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + Function: getConnections + Gets all or a subset of connections currently managed by this jsPlumb instance. + Parameters: + options - a JS object that holds options defining what sort of connections you're looking for. Valid values are: + scope this may be a String or a list of Strings. jsPlumb will only return Connections whose scope matches what this option + defines. If you omit it, you will be given Connections of every scope. + source may be a string or a list of strings; constraints results to have only Connections whose source is this object. + target may be a string or a list of strings; constraints results to have only Connections whose target is this object. + + The return value is a dictionary in this format: + + { + 'scope1': [ + {sourceId:'window1', targetId:'window2'}, + {sourceId:'window3', targetId:'window4'}, + {sourceId:'window1', targetId:'window3'} + ], + 'scope2': [ + {sourceId:'window1', targetId:'window3'} + ] + } + + */ + this.getConnections = function(options) { + var r = {}; + options = options || {}; + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') r.push(input); + else r = input; + } + return r; + }; + var scopes = prepareList(options.scope); + var sources = prepareList(options.source); + var targets = prepareList(options.target); + var filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }; + for (var i in connectionsByScope) { + if (filter(scopes, i)) { + r[i] = []; + for (var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + //r[i].push({sourceId:c.sourceId, targetId:c.targetId}); + r[i].push({ + sourceId:c.sourceId, + targetId:c.targetId, + source:c.source, + target:c.target, + sourceEndpoint:c.endpoints[0], + targetEndpoint:c.endpoints[1] + }); + } + } + } + return r; + }; + + /* + Function: getDefaultScope + Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a scope to an endpoint + or connection allows you to support different types of connections in the same UI. but if you're only interested in one type of connection, + you don't need to supply a scope. this method will probably be used by very few people; it's good for testing though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + gets an Endpoint by UUID + Parameters: + uuid - the UUID for the Endpoint + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /* + * gets an element's id, creating one if necessary. really only exposed for the lib-specific + * functionality to access; would be better to pass the current instance into the lib-specific + * code (even though this is a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + + /* + Function: hide + Sets an element's connections to be hidden. + Parameters: + el - either the id of the element, or a selector for the element. + Returns: + void + */ + this.hide = function(el) { + _setVisible(el, "none"); + }; + + /* + Function: makeAnchor + Creates an anchor with the given params. + Parameters: + x - the x location of the anchor as a fraction of the total width. + y - the y location of the anchor as a fraction of the total height. + xOrientation - value indicating the general direction a connection from the anchor should go in, in the x direction. + yOrientation - value indicating the general direction a connection from the anchor should go in, in the y direction. + xOffset - a fixed offset that should be applied in the x direction that should be applied after the x position has been figured out. optional. defaults to 0. + yOffset - a fixed offset that should be applied in the y direction that should be applied after the y position has been figured out. optional. defaults to 0. + Returns: + The newly created Anchor. + */ + this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards compatibility if we are given only one value we assume it's a call in the old form. + var params = {}; + if (arguments.length == 1) jsPlumb.extend(params, x); + else { + params = {x:x, y:y}; + if (arguments.length >= 4) { + params.orientation = [arguments[2], arguments[3]]; + } + if (arguments.length == 6) params.offsets = [arguments[4], arguments[5]]; + } + var a = new Anchor(params); + a.clone = function() { return new Anchor(params); }; + return a; + }; + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + Parameters: + el - either the id of the element or a selector representing the element. + Returns: + void + See Also: + + */ + this.repaint = function(el) { + + var _processElement = function(el) { + var ele = _getElementObject(el); + _draw(ele); + }; + + // TODO: support a jQuery result object too! + + // support both lists... + if (typeof el =='object') { + for (var i = 0; i < el.length; i++) + _processElement(el[i]); + } // ...and single strings. + else _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + Returns: + void + See Also: + + */ + this.repaintEverything = function() { + for (var elId in endpointsByElement) { + _draw(_getElementObject(elId)); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + Parameters: + el - either an element id, or a selector for an element. + Returns: + void + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"); + // first remove all Connections. + jsPlumb.detachAll(elId); + var ebe = endpointsByElement[elId]; + for (var i in ebe) { + _removeElement(ebe[i].canvas); + } + endpointsByElement[elId] = []; + }; + + /* + Function: removeEveryEndpoint + Removes every Endpoint in this instance of jsPlumb. + Returns: + void + See Also: + + */ + this.removeEveryEndpoint = function() { + for(var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + _removeElement(endpoints[i].canvas, endpoints[i].container); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + }; + + /* + Function: removeEndpoint + Removes the given Endpoint from the given element. + Parameters: + el - either an element id, or a selector for an element. + endpoint - Endpoint to remove. this is an Endpoint object, such as would have been returned from a call to addEndpoint. + Returns: + void + See Also: + + */ + this.removeEndpoint = function(el, endpoint) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + if (ebe) { + if(_removeFromList(endpointsByElement, elId, endpoint)) + _removeElement(endpoint.canvas); + } + }; + + /* + Function:reset + removes all endpoints and connections and clears the element cache. + */ + this.reset = function() { + this.detachEverything(); + this.removeEveryEndpoint(); + this.clearCache(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + Parameters: + value - whether or not to automatically repaint when the window is resized. + Returns: + void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + Function: setDefaultNewCanvasSize + Sets the default size jsPlumb will use for a new canvas (we create a square canvas so one value is all that is required). + This is a hack for IE, because ExplorerCanvas seems to need for a canvas to be larger than what you are going to draw on + it at initialisation time. The default value of this is 1200 pixels, which is quite large, but if for some reason you're + drawing connectors that are bigger, you should adjust this value appropriately. + Parameters: + size - The default size to use. jsPlumb will use a square canvas so you need only supply one value. + Returns: + void + */ + this.setDefaultNewCanvasSize = function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }; + + /* + Function: setDefaultScope + Sets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a scope to an endpoint + or connection allows you to support different types of connections in the same UI. but if you're only interested in one type of connection, + you don't need to supply a scope. this method will probably be used by very few people; it just instructs jsPlumb to use a different key + for the default scope. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + Function: setDraggable + Sets whether or not a given element is draggable, regardless of what any plumb command may request. + Parameters: + el - either the id for the element, or a selector representing the element. + Returns: + void + */ + this.setDraggable = _setDraggable; + + /* + Function: setDraggableByDefault + Sets whether or not elements are draggable by default. Default for this is true. + Parameters: + draggable - value to set + Returns: + void + */ + this.setDraggableByDefault = function(draggable) { + _draggableByDefault = draggable; + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + Function: setRepaintFunction + Sets the function to fire when the window size has changed and a repaint was fired. + Parameters: + f - Function to execute. + Returns: + void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + Function: show + Sets an element's connections to be visible. + Parameters: + el - either the id of the element, or a selector for the element. + Returns: + void + */ + this.show = function(el) { + _setVisible(el, "block"); + }; + + /* + Function: sizeCanvas + Helper to size a canvas. You would typically use this when writing your own Connector or Endpoint implementation. + Parameters: + x - [int] x position for the Canvas origin + y - [int] y position for the Canvas origin + w - [int] width of the canvas + h - [int] height of the canvas + Returns: + void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; canvas.height = h; + canvas.style.width = w + "px"; canvas.width = w; + canvas.style.left = x + "px"; canvas.style.top = y + "px"; + // _currentInstance.CurrentLibrary.setPosition(canvas, x, y); + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards compatibility + */ + this.toggle = _toggleVisible; + + /* + Function: toggleVisible + Toggles visibility of an element's connections. + Parameters: + el - either the element's id, or a selector representing the element. + Returns: + void, but should be updated to return the current state + */ + //TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + Function: toggleDraggable + Toggles draggability (sic) of an element's connections. + Parameters: + el - either the element's id, or a selector representing the element. + Returns: + The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + Function: unload + Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element + Returns: + void + */ + this.unload = function() { + delete endpointsByElement; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + }; + + /* + Function: wrap + Helper method to wrap an existing function with one of your own. This is used by the various + implementations to wrap event callbacks for drag/drop etc; it allows jsPlumb to be + transparent in its handling of these things. If a user supplies their own event callback, + for anything, it will always be called. + Parameters: + */ + this.wrap = _wrap; + this.addListener = _addListener; + + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(); + if (_defaults) + jsPlumb.extend(j.Defaults, _defaults); + return j; + }; + +})(); + +/* +* jsPlumb-defaults-1.2.2 +* +* This script contains the default Anchors, Endpoints and Connectors for jsPlumb. It should be used with jsPlumb 1.1.0 and above; +* prior to version 1.1.0 of jsPlumb the defaults were included inside the main script. +* +* Version 1.1.1 of this script adds the Triangle Endpoint, written by __________ and featured on this demo: +* +* http://www.mintdesign.ru/blog/javascript-jsplumb-making-graph +* http://www.mintdesign.ru/site/samples/jsplumb/jsPlumb-graph-sample.htm +* +* NOTE: for production usage you should use jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, +* in a minified file. +* +* Dual licensed under MIT and GPL2. +*/ + +(function() { + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * getOrientation : function() { return [ox, oy]; } + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + jsPlumb.Anchors.TopCenter = jsPlumb.makeAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors.BottomCenter = jsPlumb.makeAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors.LeftMiddle = jsPlumb.makeAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors.RightMiddle = jsPlumb.makeAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors.Center = jsPlumb.makeAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors.TopRight = jsPlumb.makeAnchor(1, 0, 0,-1); + jsPlumb.Anchors.BottomRight = jsPlumb.makeAnchor(1, 1, 0, 1); + jsPlumb.Anchors.TopLeft = jsPlumb.makeAnchor(0, 0, 0, -1); + jsPlumb.Anchors.BottomLeft = jsPlumb.makeAnchor(0, 1, 0, 1); + + + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (w < 2 * lineWidth) { + w = 2 * lineWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - lineWidth; + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < 2 * lineWidth) { + // minimum size is 2 * line Width + h = 2 * lineWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - lineWidth; + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + var sx = sourcePos[0] < targetPos[0] ? w-xo : xo; + var sy = sourcePos[1] < targetPos[1] ? h-yo : yo; + var tx = sourcePos[0] < targetPos[0] ? xo : w-xo; + var ty = sourcePos[1] < targetPos[1] ? yo : h-yo; + var retVal = [ x, y, w, h, sx, sy, tx, ty ]; + + return retVal; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + }; + + /** + * This Connector draws a Bezier curve with two control points. + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + jsPlumb.Connectors.Bezier = function(curviness) { + + var self = this; + this.majorAnchor = curviness || 150; + this.minorAnchor = 10; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) + { + lineWidth = lineWidth || 0; + var w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth, h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + var canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2), canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + var sx = sourcePos[0] < targetPos[0] ? w - (lineWidth/2): (lineWidth/2), sy = sourcePos[1] < targetPos[1] ? h-(lineWidth/2) : (lineWidth/2); + var tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : w-(lineWidth/2), ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : h-(lineWidth/2); + var CP = self._findControlPoint([sx,sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + var CP2 = self._findControlPoint([tx,ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(sx,tx); var minx2 = Math.min(CP[0], CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(sx,tx); var maxx2 = Math.max(CP[0], CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > w) w = maxx; + if (minx < 0) { + canvasX += minx; var ox = Math.abs(minx); + w += ox; CP[0] += ox; sx += ox; tx +=ox; CP2[0] += ox; + } + + var miny1 = Math.min(sy,ty); var miny2 = Math.min(CP[1], CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(sy,ty); var maxy2 = Math.max(CP[1], CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > h) h = maxy; + if (miny < 0) { + canvasY += miny; var oy = Math.abs(miny); + h += oy; CP[1] += oy; sy += oy; ty +=oy; CP2[1] += oy; + } + + // return [ canvasx, canvasy, canvasWidth, canvasHeight, + // sourceX, sourceY, targetX, targetY, + // controlPoint1_X, controlPoint1_Y, controlPoint2_X, controlPoint2_Y + return [canvasX, canvasY, w, h, sx, sy, tx, ty, CP[0], CP[1], CP2[0], CP2[1] ]; + }; + + this.paint = function(d, ctx) { + + /*var img = new Image(); + img.src = "../img/pattern.jpg"; + ctx.fillStyle = ctx.createPattern(img, 'repeat-y');*/ + + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + //ctx.fill(); + } + }; + + + /** + * Types of endpoint UIs. we supply four - a circle of default radius 10px, a rectangle of + * default size 20x20, an image (with no default), and a Triangle, of default size 15. + * you can supply others of these if you want to - see the documentation for a howto. + */ + + /** + * a round endpoint, with default radius 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + } + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * A Rectangular endpoint, with default size 20x20. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + //todo: the fillStyle needs some thought. we want to support a few options: + // 1. nothing supplied; use the stroke color or the default if no stroke color. + // 2. a fill color supplied - use it + // 3. a gradient supplied - use it + // 4. setting the endpoint to the same color as the bg of the element it is attached to. + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + jsPlumb.Endpoints.Triangle = function(params) { + + params = params || { width:15, height:15 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - width/2; + var y = anchorPoint[1] - height/2; + + jsPlumb.sizeCanvas(canvas, x, y, width, height); + + var ctx = canvas.getContext('2d'); + var + offsetX = 0, + offsetY = 0, + angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = endpointStyle.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + ctx.fill(); + + }; + }; + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' property in the params object.. + */ + jsPlumb.Endpoints.Image = function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + }; +})(); + +/* + * mootools.jsPlumb 1.2.2 + * + * MooTools specific functionality for jsPlumb. + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + * NOTE: for production usage you should use mootools.jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, + * in a minified file. + * + * Dual licensed under MIT and GPL2. + * + */ + +(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function() { + this.parent(); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}; + var _droppableOptions = {}; + var _draggablesByScope = {}; + var _draggablesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }; + + + jsPlumb.CurrentLibrary = { + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' + }, + + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).grab(child); + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + getElementObject : function(el) { + return $(el); + }, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + initDraggable : function(el, options) { + var id = jsPlumb.getId(el); + var drag = _draggablesById[id]; + if (!drag) { + var originalZIndex = 0, originalCursor = null; + var dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + options['onStart'] = jsPlumb.wrap(options['onStart'], function() + { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() + { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + }); + + // DROPPABLES: + var scope = options['scope'] || jsPlumb.Defaults.Scope; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop'); + } + }; + + drag = new Drag.Move(el, options); + _add(_draggablesByScope, scope, drag); + _add(_draggablesById, el.get("id"), drag); + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + initDroppable : function(el, options) { + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _add(_droppables, scope, el); + var id = jsPlumb.getId(el); + _droppableOptions[id] = options; + var filterFunc = function(entry) { + return entry.element != el; + }; + var draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + if (typeof Drag != undefined) + return typeof Drag.Move != undefined; + return false; + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + m.start(properties); + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[0]; + return { left: ui.offsetLeft, top: ui.offsetTop }; + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + }, + + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).dispose(); // ?? + }, + + getScrollLeft : function(el) { + return null; + }, + + getScrollTop : function(el) { + return null; + } + }; +})(); diff --git a/archive/1.2.3/jquery.jsPlumb-1.2.3-RC1.js b/archive/1.2.3/jquery.jsPlumb-1.2.3-RC1.js new file mode 100644 index 000000000..897216e38 --- /dev/null +++ b/archive/1.2.3/jquery.jsPlumb-1.2.3-RC1.js @@ -0,0 +1,268 @@ +/* + * jquery.jsPlumb 1.2.3-RC1 + * + * jQuery specific functionality for jsPlumb. + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + * NOTE: for production usage you should use jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, + * in a minified file. + * + * Dual licensed under MIT and GPL2. + * + */ + +(function($){ + /** + * plumbs the results of the selector to some target, using the given options if supplied, + * or the defaults otherwise. + */ + $.fn.plumb = function(options) { + var options = $.extend({}, options); + + return this.each(function() + { + var params = $.extend({source:$(this)}, options); + jsPlumb.connect(params); + }); + }; + + /** + * detaches the results of the selector from the given target or list of targets - 'target' + * may be a String or a List. + */ + $.fn.detach = function(target) { + return this.each(function() { + if (target) { + var id = $(this).attr("id"); + if (typeof target == 'string') target = [target]; + for (var i = 0; i < target.length; i++) + jsPlumb.detach(id, target[i]); + } + }); + }; + + /** + * detaches the results from the selector from all connections. + */ + $.fn.detachAll = function() { + return this.each(function() + { + var id = $(this).attr("id"); + jsPlumb.detachAll(id); + }); + }; + + /** + * adds an endpoint to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoint = function(options) { + var addedEndpoints = []; + this.each(function() { + addedEndpoints.push(jsPlumb.addEndpoint($(this).attr("id"), options)); + }); + return addedEndpoints[0]; + }; + + /** + * adds a list of endpoints to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoints = function(endpoints) { + var addedEndpoints = []; + return this.each(function() { + var e = jsPlumb.addEndpoints($(this).attr("id"), endpoints); + for (var i = 0; i < e.length; i++) addedEndpoints.push(e[i]); + }); + }; + + /** + * remove the endpoint, if it exists, deleting its UI elements etc. + */ + $.fn.removeEndpoint = function(endpoint) { + this.each(function() { + jsPlumb.removeEndpoint($(this).attr("id"), endpoint); + }); + }; + +})(jQuery); + + +/*TODO: abstract this out from jQuery too! but how...because jsPlumb is not loaded yet. +$(window).bind('resize', function() { + if (resizeTimer) clearTimeout(resizeTimer); + resizeTimer = setTimeout(repaintEverything, 100); + });*/ + + + +/* + * the library agnostic functions, such as find offset, get id, get attribute, extend etc. + */ +(function($) { + + jsPlumb.CurrentLibrary = { + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step', + 'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete' + }, + + bind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.bind(event, callback); + }, + + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).append(child); + }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + * + */ + getElementObject : function(el) { + return typeof(el)=='string' ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options) { + // remove helper directive if present. we know what's best! + options.helper = null; + //TODO: if 'revert' is set on the options it causes end points to animate back to + // where they came from, if the connection is aborted. do we care? probably not. + // the todo is to decide whether we care or not. + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.draggable(options); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.droppable(options); + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[1]; + //return ui.absolutePosition || ui.offset; + return ui.offset || ui.absolutePosition; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + }, + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).remove(); + }, + + getScrollLeft : function(el) { + return el.scrollLeft(); + }, + + getScrollTop : function(el) { + return el.scrollTop(); + }, + + setOffset : function(el, o) { + jsPlumb.CurrentLibrary.getElementObject(el).offset(o); + } + }; +})(jQuery); diff --git a/archive/1.2.3/jquery.jsPlumb-1.2.3-all-min.js b/archive/1.2.3/jquery.jsPlumb-1.2.3-all-min.js new file mode 100644 index 000000000..bcf51786b --- /dev/null +++ b/archive/1.2.3/jquery.jsPlumb-1.2.3-all-min.js @@ -0,0 +1 @@ +(function(){var a=function(){var j=this;var S=(/MSIE/.test(navigator.userAgent)&&!window.opera);var m=null;var Y=function(){b.repaintEverything()};var f=true;function X(){if(f){Y()}}var W=null;var ad=function(){var ah={};this.bind=function(aj,ak){var ai=ah[aj];if(!ai){ai=[];ah[aj]=ai}ai.push(ak)};this.fireUpdate=function(aj,ak){if(ah[aj]){for(var ai=0;ai0){var au=aj[0];var ay=au.endpoints[0]==ap?1:0;var al=ay==0?au.sourceId:au.targetId;var at=F[al],av=y[al];var ax=ap.anchor.compute([ar.left,ar.top],ao,ap,[at.left,at.top],av,au.endpoints[ay]);ap.paint(ax)}else{var ax=ap.anchor.compute([ar.left,ar.top],ao,ap);ap.paint(ax)}for(var ak=0;ak=0){delete (ah[ai]);ah.splice(ai,1);return true}}return false};var c=function(ai,ah){var aj=function(ak,al){T[al]=ah;if(b.CurrentLibrary.isDragSupported(ak)){b.CurrentLibrary.setDraggable(ak,ah)}};return q(ai,aj)};var ab=function(ai,ak){var ah=o(ai,"id");var aj=function(al){al.canvas.style.display=ak};D(ah,aj)};var v=function(ai){var ah=function(ak,aj){var al=T[aj]==null?H:T[aj];al=!al;T[aj]=al;b.CurrentLibrary.setDraggable(ak,al);return al};return q(ai,ah)};var e=function(ah){var ai=function(ak){var aj=("none"==ak.canvas.style.display);ak.canvas.style.display=aj?"block":"none"};D(ah,ai)};var k=function(ai,ak,ah){if(ah||ak==null){var aj=V(ai);y[ai]=h(aj);F[ai]=d(aj)}else{F[ai]=ak}};var aa=function(ai,ah){ai=ai||function(){};ah=ah||function(){};return function(){var aj=null;try{aj=ah.apply(this,arguments)}catch(ak){l("jsPlumb function failed : "+ak)}try{ai.apply(this,arguments)}catch(ak){l("wrapped function failed : "+ak)}return aj}};var M=function(al){var aj=this;this.x=al.x||0;this.y=al.y||0;var ai=al.orientation||[0,0];var ak=null;var ah=null;this.offsets=al.offsets||[0,0];this.compute=function(ay,an,av,aq,au,ar){ah=[ay[0]+(aj.x*an[0])+aj.offsets[0],ay[1]+(aj.y*an[1])+aj.offsets[1]];var ao=av?av.container:null;var aw={left:0,top:0};if(ao!=null){var am=V(ao);var ap=d(am);var at=b.CurrentLibrary.getScrollLeft(am);var ax=b.CurrentLibrary.getScrollTop(am);aw.left=ap.left-at;aw.top=ap.top-ax;ah[0]=ah[0]-aw.left;ah[1]=ah[1]-aw.top}return ah};this.getOrientation=function(){return ai};this.equals=function(am){if(!am){return false}var an=am.getOrientation();var ap=this.getOrientation();return this.x==am.x&&this.y==am.y&&this.offsets[0]==am.offsets[0]&&this.offsets[1]==am.offsets[1]&&ap[0]==an[0]&&ap[1]==an[1]}};var p=function(am){var ak=am.reference;var al=am.referenceCanvas;var aj=h(V(al));var ai=0,an=0;var ah=null;this.compute=function(aq,ao,ap,ar){ai=0;an=0;return[aq[0]+(aj[0]/2),aq[1]+(aj[1]/2)]};this.getOrientation=function(){if(ah){return ah}else{var ao=ak.getOrientation();return[Math.abs(ao[0])*ai*-1,Math.abs(ao[1])*an*-1]}};this.over=function(ao){ah=ao.getOrientation()};this.out=function(){ah=null}};var J=function(am){this.isSelective=true;this.isDynamic=true;var ak=am||[];this.addAnchor=function(an){ak.push(an)};var aj=ak.length>0?ak[0]:null;var al=ak.length>0?0:-1;this.locked=false;var ai=this;var ah=function(ap,an,au,at,ao){var ar=at[0]+(ap.x*ao[0]),aq=at[1]+(ap.y*ao[1]);return Math.sqrt(Math.pow(an-ar,2)+Math.pow(au-aq,2))};this.compute=function(aA,an,av,ao,aq,ap){if(ai.locked||ao==null||aq==null){return aj.compute(aA,an,av,ao,aq,ap)}var at=ao[0]+(aq[0]/2),ar=ao[1]+(aq[1]/2);var aw=-1,az=Infinity;for(var au=0;auax){ax=aI}}var aN=this.connector.compute(aA,aP,this.endpoints[aS].anchor,this.endpoints[aw].anchor,this.paintStyle.lineWidth,ax);b.sizeCanvas(ai,aN[0],aN[1],aN[2],aN[3]);var aG=function(aT,aW){aT.save();b.extend(aT,aW);if(aW.gradient&&!S){var aV=ar.connector.createGradient(aN,aT,(aH==this.sourceId));for(var aU=0;aU=0){var aN=aM.endpoints[0]==aw?aM.endpoints[1]:aM.endpoints[0];aw.connections.splice(aL,1);aN.detach(aM)}};this.detachAll=function(){while(aw.connections.length>0){aw.detach(aw.connections[0])}};this.detachFrom=function(aM){var aN=[];for(var aL=0;aL=aD)};this.setDragAllowedWhenFull=function(aL){aw.dragAllowedWhenFull=aL};this.equals=function(aL){return this.anchor.equals(aL.anchor)&&true};this.paint=function(aP,aN,aM){if(m){m.debug("Painting Endpoint with elementId ["+an+"]; anchorPoint is ["+aP+"]")}if(aP==null){var aO=F[an];var aL=y[an];if(aO==null||aL==null){k(an);aO=F[an];aL=y[an]}aP=aw.anchor.compute([aO.left,aO.top],aL,aw)}au.paint(aP,aw.anchor.getOrientation(),aM||aw.canvas,ao,aN||ao)};this.removeConnection=this.detach;if(aJ.isSource&&b.CurrentLibrary.isDragSupported(av)){var aC=null,ay=null,aB=null,ah=false,aj=null;var al=function(){aB=aG();if(aw.isFull()&&!aw.dragAllowedWhenFull){return false}k(an);ak=aw.makeInPlaceCopy();ak.paint();aC=document.createElement("div");var aN=V(aC);A(aC,aw.container);var aO=""+new String(new Date().getTime());R(aN,"id",aO);k(aO);R(V(aw.canvas),"dragId",aO);R(V(aw.canvas),"elId",an);var aM=new p({reference:aw.anchor,referenceCanvas:aw.canvas});aH=new ac({style:{fillStyle:"rgba(0,0,0,0)"},endpoint:au,anchor:aM,source:aN});if(aB==null){aw.anchor.locked=true;aB=new n({sourceEndpoint:aw,targetEndpoint:aH,source:V(av),target:V(aC),anchors:[aw.anchor,aM],paintStyle:aJ.connectorStyle,connector:aJ.connector})}else{ah=true;var aL=aB.sourceId==an?0:1;aB.floatingAnchorIndex=aL;aw.removeConnection(aB);if(aL==0){aj=[aB.source,aB.sourceId];aB.source=V(aC);aB.sourceId=aO}else{aj=[aB.target,aB.targetId];aB.target=V(aC);aB.targetId=aO}aB.endpoints[aL==0?1:0].anchor.locked=true;aB.suspendedEndpoint=aB.endpoints[aL];aB.endpoints[aL]=aH}I[aO]=aB;aH.addConnection(aB);B(U,aO,aH)};var aE=aJ.dragOptions||{};var az=b.extend({},b.CurrentLibrary.defaultDragOptions);aE=b.extend(az,aE);aE.scope=aE.scope||aw.scope;var aA=b.CurrentLibrary.dragEvents.start;var aI=b.CurrentLibrary.dragEvents.stop;var ap=b.CurrentLibrary.dragEvents.drag;aE[aA]=aa(aE[aA],al);aE[ap]=aa(aE[ap],function(){var aL=b.CurrentLibrary.getUIPosition(arguments);b.CurrentLibrary.setOffset(aC,aL);ae(V(aC),aL)});aE[aI]=aa(aE[aI],function(){u(U,ay,aH);z([aC,aH.canvas],av);P(ak.canvas,av);var aL=aB.floatingAnchorIndex==null?1:aB.floatingAnchorIndex;aB.endpoints[aL==0?1:0].anchor.locked=false;if(aB.endpoints[aL]==aH){if(ah&&aB.suspendedEndpoint){if(at){aB.floatingAnchorIndex=null;if(aL==0){aB.source=aj[0];aB.sourceId=aj[1]}else{aB.target=aj[0];aB.targetId=aj[1]}aB.endpoints[aL]=aB.suspendedEndpoint;aB.suspendedEndpoint.addConnection(aB);b.repaint(aj[1])}else{aB.endpoints[0].removeConnection(aB);aB.endpoints[1].removeConnection(aB);P(aB.canvas,aw.container);u(s,aB.scope,aB);K("jsPlumbConnectionDetached",{source:aB.source,target:aB.target,sourceId:aB.sourceId,targetId:aB.targetId,sourceEndpoint:aB.endpoints[0],targetEndpoint:aB.endpoints[1]})}}else{P(aB.canvas,aw.container);aw.removeConnection(aB)}}aw.anchor.locked=false;aB=null;delete aH;delete ak;aw.paint()});var aF=V(aw.canvas);b.CurrentLibrary.initDraggable(aF,aE)}if(aJ.isTarget&&b.CurrentLibrary.isDropSupported(av)){var aK=aJ.dropOptions||j.Defaults.DropOptions||b.Defaults.DropOptions;aK=b.extend({},aK);aK.scope=aK.scope||aw.scope;var ar=null;var ax=b.CurrentLibrary.dragEvents.drop;var am=b.CurrentLibrary.dragEvents.over;var ai=b.CurrentLibrary.dragEvents.out;aK[ax]=aa(aK[ax],function(){var aM=b.CurrentLibrary.getDragObject(arguments);var aQ=o(V(aM),"dragId");var aN=o(V(aM),"elId");var aP=I[aQ];var aL=aP.floatingAnchorIndex==null?1:aP.floatingAnchorIndex;var aO=aL==0?1:0;if(!aw.isFull()&&!(aL==0&&!aw.isSource)&&!(aL==1&&!aw.isTarget)){if(aL==0){aP.source=av;aP.sourceId=an}else{aP.target=av;aP.targetId=an}aP.endpoints[aL].removeConnection(aP);if(aP.suspendedEndpoint){aP.suspendedEndpoint.removeConnection(aP)}aP.endpoints[aL]=aw;aw.addConnection(aP);aP.endpoints[aO].addConnection(aP);B(s,aP.scope,aP);x(av,aJ.draggable,{});b.repaint(aN);K("jsPlumbConnection",{source:aP.source,target:aP.target,sourceId:aP.sourceId,targetId:aP.targetId,sourceEndpoint:aP.endpoints[0],targetEndpoint:aP.endpoints[1]})}delete I[aQ]});aK[am]=aa(aK[am],function(){var aM=b.CurrentLibrary.getDragObject(arguments);var aO=o(V(aM),"dragId");var aN=I[aO];var aL=aN.floatingAnchorIndex==null?1:aN.floatingAnchorIndex;aN.endpoints[aL].anchor.over(aw.anchor)});aK[ai]=aa(aK[ai],function(){var aM=b.CurrentLibrary.getDragObject(arguments);var aO=o(V(aM),"dragId");var aN=I[aO];var aL=aN.floatingAnchorIndex==null?1:aN.floatingAnchorIndex;aN.endpoints[aL].anchor.out()});b.CurrentLibrary.initDroppable(V(aw.canvas),aK)}return aw};this.Defaults={Anchor:null,Anchors:[null,null],BackgroundPaintStyle:null,Connector:null,Container:null,DragOptions:{},DropOptions:{},Endpoint:null,Endpoints:[null,null],EndpointStyle:{fillStyle:null},EndpointStyles:[null,null],LabelStyle:{fillStyle:"rgba(0,0,0,0)",color:"black"},MaxConnections:null,PaintStyle:{lineWidth:10,strokeStyle:"red"},Scope:"_jsPlumb_DefaultScope"};this.connectorClass="_jsPlumb_connector";this.endpointClass="_jsPlumb_endpoint";this.Anchors={};this.Connectors={};this.Endpoints={};this.Overlays={};this.addEndpoint=function(am,an){an=b.extend({},an);var ak=V(am);var ao=o(ak,"id");an.source=ak;k(ao);var al=new ac(an);B(U,ao,al);var ah=F[ao];var aj=y[ao];var ai=al.anchor.compute([ah.left,ah.top],aj,al);al.paint(ai);return al};this.addEndpoints=function(ak,ah){var aj=[];for(var ai=0;ai0){for(var ai=0;ai0){for(var ai=0;ai0?Q(at,ar)!=-1:true};for(var al in s){if(aj(ao,al)){ah[al]=[];for(var ak=0;ak=4){al.orientation=[arguments[2],arguments[3]]}if(arguments.length==6){al.offsets=[arguments[4],arguments[5]]}}var ai=new M(al);ai.clone=function(){return new M(al)};return ai};this.makeDynamicAnchor=function(ah){return new J(ah)};this.repaint=function(ai){var aj=function(ak){var al=V(ak);ae(al)};if(typeof ai=="object"){for(var ah=0;ah0?1:-1;var s=Math.abs(t*Math.sin(d));if(e>a){s=s*-1}var n=Math.abs(t*Math.cos(d));if(f>b){n=n*-1}return[r[0]+(q*n),r[1]+(q*s)]};this.perpendicularToPathAt=function(r,v,s){var t=m.pointAlongPathFrom(r,v);var q=m.gradientAtPoint(t.location);var o=Math.atan(-1/q);var u=s/2*Math.sin(o);var n=s/2*Math.cos(o);return[[t[0]+n,t[1]+u],[t[0]-n,t[1]-u]]};this.createGradient=function(o,n){return n.createLinearGradient(o[4],o[5],o[6],o[7])}};jsPlumb.Connectors.Bezier=function(a){var n=this;this.majorAnchor=a||150;this.minorAnchor=10;var g=null;this._findControlPoint=function(y,o,t,w,q){var v=w.getOrientation(),x=q.getOrientation();var s=v[0]!=x[0]||v[1]==x[1];var r=[];var z=n.majorAnchor,u=n.minorAnchor;if(!s){if(v[0]==0){r.push(o[0]j){j=t}if(w<0){d+=w;var x=Math.abs(w);j+=x;m[0]+=x;h+=x;b+=x;k[0]+=x}var F=Math.min(e,_ty);var D=Math.min(m[1],k[1]);var s=Math.min(F,D);var y=Math.max(e,_ty);var v=Math.max(m[1],k[1]);var q=Math.max(y,v);if(q>f){f=q}if(s<0){c+=s;var u=Math.abs(s);f+=u;m[1]+=u;e+=u;_ty+=u;k[1]+=u}if(C&&j0?1:-1,u=null;while(o0){g.strokeStyle=b.labelStyle.borderStyle||"black";g.strokeRect(l[0]-(a/2),l[1]-(e/2),a,e)}}}}})();(function(a){a.fn.plumb=function(b){var b=a.extend({},b);return this.each(function(){var c=a.extend({source:a(this)},b);jsPlumb.connect(c)})};a.fn.detach=function(b){return this.each(function(){if(b){var d=a(this).attr("id");if(typeof b=="string"){b=[b]}for(var c=0;c endpoint lists. an element can have an arbitrary number of endpoints on it, + * and not all of them have to be connected to anything. + */ + var endpointsByElement = {}; + var endpointsByUUID = {}; + var connectionsByScope = {}; + var offsets = []; + var floatingConnections = {}; + var draggableStates = {}; + var _draggableByDefault = true; + var sizes = []; + var listeners = {}; // a map: keys are event types, values are lists of listeners. + var DEFAULT_SCOPE = 'DEFAULT'; + + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + var _findIndex = function( a, v, b, s ) { + var _eq = function(o1, o2) { + if (o1 === o2) return true; + else if (typeof o1 == 'object' && typeof o2 == 'object') { + var same = true; + for(var propertyName in o1) { + if(!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for(var propertyName in o2) { + if(!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for( var i = +b || 0, l = a.length; i < l; i++ ) { + if( _eq(a[i], v)) return i; + } + return -1; + }; + + /** + * adds a listener for the specified event type. + */ + var _addListener = function(eventType, listener) { + var doOne = function(e, l) { + _addToList(listeners, e, l); + }; + if (typeof eventType == 'object' && eventType.length) { + for (var i = 0; i < eventType.length; i++) doOne(eventType[i], listener); + } else doOne(eventType, listener); + }; + + /** + * helper method to add an item to a list, creating the list if it does not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }; + + var _appendElement = function(canvas, parent) { + if (!parent) document.body.appendChild(canvas); + else jsPlumb.CurrentLibrary.appendElement(canvas, parent); + }; + + /** + * Handles the dragging of an element. + * @param element jQuery element + * @param ui UI object from current library's event system + */ + var _draw = function(element, ui) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints) { + var timestamp = '' + (new Date().getTime()); + _updateOffset(id, ui); + var myOffset = offsets[id]; + var myWH = sizes[id]; + // loop through endpoints for this element + for (var i = 0; i < endpoints.length; i++) { + var e = endpoints[i]; + var l = e.connections; + // here we have a little quandary. if an endpoint is connected to some other element, and it + // has a selective anchor, then we want to know that element's position when we find our + // anchor. if it has no connections then fine, we use the default anchor in the list we can + // select from. but if it has more than one connection, what then? we need to compute where + // it is best placed relative to all other elements, and we then want to lock that position + // for the rest of this paint cycle. if we do not do that, it's possible that an endpoint will + // be moved by an ensuing connector's paint. + if (e.anchor.isSelective && l.length > 0) { + var c = l[0]; + var oIdx = c.endpoints[0] == e ? 1 : 0; + var oId = oIdx == 0 ? c.sourceId : c.targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH, e, [oOffset.left, oOffset.top], oWH, c.endpoints[oIdx]); + e.paint(anchorLoc); + } + else { + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH, e); + e.paint(anchorLoc); + } + for (var j = 0; j < l.length; j++) { + l[j].paint(id, ui, false, timestamp); // ...and paint them. + var oIdx = l[j].endpoints[0] == e ? 1 : 0; + // if the other endpoint has a selective anchor then it might need to be repainted, because it may have just moved + // position. it is possible we could cache the knowledge about that and not run this paint code all the time. + if (l[j].endpoints[oIdx].anchor.isSelective) { + var oId = oIdx == 0 ? l[j].sourceId : l[j].targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + var anchorLoc = l[j].endpoints[oIdx].anchor.compute([oOffset.left, oOffset.top], oWH, l[j].endpoints[oIdx], [myOffset.left, myOffset.top], myWH, e); + l[j].endpoints[oIdx].paint(anchorLoc); + } + } + } + } + }; + + /** + * helper function: the second argument is a function taking two args - the first is an id + * ui element from the current library (or list of them), and the second is the function + * to run. + */ + var _elementProxy = function(element, fn) { + var retVal = null; + if (typeof element == 'object' && element.length) { + retVal = []; + for (var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]); + var id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } + else { + var el = _getElementObject(element); + var id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + + return retVal; + }; + + /** + * fires an event of the given type. + */ + var _fireEvent = function(eventType, data) { + var l = listeners[eventType]; + if (l) { + for (var i in l) { + try { + l[i][eventType](data); + } + catch (e) { + + _log("while firing event [" + eventType + "]; listener failed like this: " + e); + } + } + } + }; + + var _log = function(msg) { + //if (console) console.log(msg); + }; + + var _logFnCall = function(fn, args) { + /*if (console) { + var c = args.callee.caller.toString(); + var i = c.indexOf("{"); + var msg = fn + ' : ' + c.substring(0, i); + console.log(msg); + }*/ + }; + + var cached = {}; + var __getElementObject = function(el) { + + if (typeof(el)=='string') { + var e = cached[el]; + if (!e) { + e = jsPlumb.CurrentLibrary.getElementObject(el); + cached[el] = e; + } + return e; + } + else { + return jsPlumb.CurrentLibrary.getElementObject(el); + } + }; + + + /** + * gets the named attribute from the given element (id or element object) + */ + var _getAttribute = function(el, attName) { + var ele = __getElementObject(el); + return jsPlumb.CurrentLibrary.getAttribute(ele, attName); + }; + + var _setAttribute = function(el, attName, attValue) { + var ele = __getElementObject(el); + jsPlumb.CurrentLibrary.setAttribute(ele, attName, attValue); + }; + + var _addClass = function(el, clazz) { + var ele = __getElementObject(el); + jsPlumb.CurrentLibrary.addClass(ele, clazz); + }; + + var _getElementObject = function(elId) { + return __getElementObject(elId); + }; + + var _getOffset = function(el) { + var ele = __getElementObject(el); + return jsPlumb.CurrentLibrary.getOffset(ele); + }; + + var _getSize = function(el) { + var ele = __getElementObject(el); + return jsPlumb.CurrentLibrary.getSize(ele); + }; + + /** + * gets an id for the given element, creating and setting one if necessary. + */ + var _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id) { + //check if fixed uuid parameter is given + if(arguments.length == 2) + id = uuid; + else + id = "_jsPlumb_" + new String((new Date()).getTime()); + _setAttribute(ele, "id", id); + } + return id; + }; + + /** + * gets an Endpoint by uuid. + */ + var _getEndpoint = function(uuid) { + return endpointsByUUID[uuid]; + }; + + /** + * inits a draggable if it's not already initialised. + */ + var _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + // dragging + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend({}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + } + } + }; + + + /** + * helper to create a canvas. + * @param clazz optional class name for the canvas. + */ + var _newCanvas = function(clazz, parent, uuid) { + var canvas = document.createElement("canvas"); + _appendElement(canvas, parent); + canvas.style.position="absolute"; + if (clazz) { canvas.className=clazz; } + + // set an id. if no id on the element and if uuid was supplied it will be used, otherwise we'll create one. + _getId(canvas, uuid); + + if (ie) { + // for IE we have to set a big canvas size. actually you can override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + /** + * performs the given function operation on all the connections found for the given element + * id; this means we find all the endpoints for the given element, and then for each endpoint + * find the connectors connected to it. then we pass each connection in to the given + * function. + */ + var _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + for (var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }; + /** + * perform an operation on all elements. + */ + var _operationOnAll = function(func) { + for (var elId in endpointsByElement) { + _operation(elId, func); + } + }; + /** + * helper to remove an element from the DOM. + */ + var _removeElement = function(element, parent) { + if (element != null) { + if (!parent) { + try { document.body.removeChild(element); } + catch (e) { } + } else { + jsPlumb.CurrentLibrary.removeElement(element, parent); + } + } + }; + /** + * helper to remove a list of elements from the DOM. + */ + var _removeElements = function(elements, parent) { + for (var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }; + /** + * helper method to remove an item from a list. + */ + var _removeFromList = function(map, key, value) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete( l[i] ); + l.splice( i, 1 ); + return true; + } + } + return false; + }; + /** + * Sets whether or not the given element(s) should be draggable, regardless of what a particular + * plumb command may request. + * + * @param element May be a string, a jQuery elements, or a list of strings/jquery elements. + * @param draggable Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + var _helper = function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }; + + return _elementProxy(element, _helper); + }; + /** + * private method to do the business of hiding/showing. + * @param el either Id of the element in question or a jquery object for the element. + * @param state String specifying a value for the css 'display' property ('block' or 'none'). + */ + var _setVisible = function(el, state) { + var elId = _getAttribute(el, "id"); + var f = function(jpc) { + jpc.canvas.style.display = state; + }; + _operation(elId, f); + }; + /** + * toggles the draggable state of the given element(s). + * @param el either an id, or a jquery object, or a list of ids/jquery objects. + */ + var _toggleDraggable = function(el) { + var fn = function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }; + return _elementProxy(el, fn); + }; + /** + * private method to do the business of toggling hiding/showing. + * @param elId Id of the element in question + */ + var _toggleVisible = function(elId) { + var f = function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }; + + _operation(elId, f); + + //todo this should call _elementProxy, and pass in the _operation(elId, f) call as a function. cos _toggleDraggable does that. + }; + /** + * updates the offset and size for a given element, and stores the values. + * if 'offset' is not null we use that (it would have been passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we use the offset, outerWidth and outerHeight methods to get + * the current values. + */ + var _updateOffset = function(elId, offset, recalc) { + if (recalc || offset == null) { // if forced repaint or no offset available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + } else { + offsets[elId] = offset; + } + }; + + /** + * wraps one function with another, creating a placeholder for the wrapped function + * if it was null. this is used to wrap the various drag/drop event functions - to allow + * jsPlumb to be notified of important lifecycle events without imposing itself on the user's + * drag/drop functionality. + * TODO: determine whether or not we should support an error handler concept, if one of the functions fails. + */ + var _wrap = function(wrappedFunction, newFunction) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { r = newFunction.apply(this, arguments); } + catch (e) { + _log('jsPlumb function failed : ' + e); + } + try { wrappedFunction.apply(this, arguments); + } + catch (e) { + _log('wrapped function failed : ' + e); + } + return r; + }; + }; + + /* + Class: Anchor + Models a position relative to the origin of some element that an Endpoint can be located. + */ + /* + Function: Anchor + + Constructor for the Anchor class + + Parameters: + params - Anchor parameters. This should contain three values, and may optionally have an 'offsets' argument: + + - x : the x location of the anchor as a fraction of the total width. + - y : the y location of the anchor as a fraction of the total height. + - orientation : an [x,y] array indicating the general direction a connection from the anchor should go in. for more info on this, see the documentation, or the docs in jquery-jsPlumb-defaults-XXX.js for the default Anchors. + - offsets : an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. may be null. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; this.y = params.y || 0; + var orientation = params.orientation || [0,0]; + var lastTimestamp = null; + var lastReturnValue = null; + this.offsets = params.offsets || [0,0]; + this.compute = function(xy, wh, element, txy, twh, tElement) { + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1]]; + var container = element? element.container : null; + var containerAdjustment = {left:0, top:0 }; + if (container != null) { + var eo = _getElementObject(container); + var o = _getOffset(eo); + var sl = jsPlumb.CurrentLibrary.getScrollLeft(eo); + var st = jsPlumb.CurrentLibrary.getScrollTop(eo); + containerAdjustment.left = o.left - sl; + containerAdjustment.top = o.top - st; + lastReturnValue[0] = lastReturnValue[0] - containerAdjustment.left; + lastReturnValue[1] = lastReturnValue[1] - containerAdjustment.top; + } + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y && this.offsets[0] == anchor.offsets[0] && this.offsets[1] == anchor.offsets[1] && o[0] == ao[0] && o[1] == ao[1]; + }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from its position relative to the anchor it is floating relative to. + * + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(_getElementObject(refCanvas)); + + // these are used to store the current relative position of our anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating anchor is hovering over another anchor. + var orientation = null; + + this.compute = function(xy, wh, txy, twh) { + // set these for the getOrientation method to use. + xDir = 0;//xy[0] < txy[0] ? -1 : xy[0] == txy[0] ? 0 : 1; + yDir = 0;//xy[1] < txy[1] ? -1 : xy[1] == txy[1] ? 0 : 1; + return [xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other anchor: if it declares zero for some direction, we declare zero too. + // this might not be the most awesome. perhaps we can come up with a better way. it's just so that the line we draw looks + // like it makes sense. maybe this wont make sense. + return [Math.abs(o[0]) * xDir * -1, Math.abs(o[1]) * yDir * -1]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering over another anchor; + * we want to assume that anchor's orientation for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no longer hovering + * over another anchor; we should resume calculating orientation as we normally do. + */ + this.out = function() { + orientation = null; + }; + }; + + /** + * An Anchor that contains a list of other Anchors, and which it cycles through at compute time and finds the one that is located closest to + * the center of the target element, and returns that Anchor's compute method result. this causes endpoints to follow each other with respect + * to the orientation of their target elements - a useful feature for some applications. + */ + var DynamicAnchor = function(anchors) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = anchors || []; + this.addAnchor = function(anchor) { _anchors.push(anchor); }; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null; + var _curIndex = _anchors.length > 0 ? 0 : -1; + this.locked = false; + var self = this; + + //todo set a default anchor? + + var _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }; + + this.compute = function(xy, wh, element, txy, twh, tElement) { + // if anchor is locked or an opposite element was not given, we maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) { + return _curAnchor.compute(xy, wh, element, txy, twh, tElement); + } + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for (var i = 0; i < _anchors.length; i++) { + var d = _distance(_anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + _curAnchor = _anchors[minIdx]; + var pos = _curAnchor.compute(xy, wh, element, txy, twh, tElement); + return pos; + }; + + this.getOrientation = function() { + return _curAnchor != null ? _curAnchor.getOrientation() : [0,0]; + }; + + this.over = function(anchor) { + if (_curAnchor != null) _curAnchor.over(anchor); + }; + + this.out = function() { + if (_curAnchor != null) _curAnchor.out(); + }; + }; + + // ************** connection + // **************************************** + /** + * allowed params: + * source: source element (string or a jQuery element) (required) + * target: target element (string or a jQuery element) (required) + * + * anchors: optional array of anchor placements. defaults to BottomCenter for source + * and TopCenter for target. + */ + var Connection = function(params) { + + EventGenerator.apply(this); + // ************** get the source and target and register the connection. ******************* + var self = this; + var id = new String('_jsplumb_c_' + (new Date()).getTime()); + this.getId = function() { return id; }; + this.container = params.container || _currentInstance.Defaults.Container; // may be null; we will append to the body if so. + // get source and target as jQuery objects + this.source = _getElementObject(params.source); + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. + if (params.sourceEndpoint) + this.source = params.sourceEndpoint.getElement(); + if (params.targetEndpoint) + this.target = params.targetEndpoint.getElement(); + this.sourceId = _getAttribute(this.source, "id"); + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + this.scope = params.scope; // scope may have been passed in to the connect call. + // if it wasn't, we will pull it from the source endpoint, + // after having initialised the endpoints. + // init endpoints + this.endpoints = []; + this.endpointStyles = []; + var prepareEndpoint = function(existing, index, params, element) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } + else { + if(!params.endpoints) params.endpoints = [null,null]; + var ep = params.endpoints[index] || params.endpoint || _currentInstance.Defaults.Endpoints[index] || jsPlumb.Defaults.Endpoints[index] || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || new jsPlumb.Endpoints.Dot(); + if (!params.endpointStyles) params.endpointStyles = [null,null]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + var a = params.anchors ? params.anchors[index] : _currentInstance.Defaults.Anchors[index] || jsPlumb.Defaults.Anchors[index] || _currentInstance.Defaults.Anchor || jsPlumb.Defaults.Anchor || jsPlumb.Anchors.BottomCenter; + var u = params.uuids ? params.uuids[index] : null; + if(params.uuids) + var e = new Endpoint({style:es, endpoint:ep, connections:[self], uuid:u, anchor:a, source:element, container:self.container }); + else + var e = new Endpoint({style:es, endpoint:ep, connections:[self], anchor:a, source:element, container:self.container }); + + self.endpoints[index] = e; + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || _currentInstance.Defaults.Connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + this.paintStyle = this.endpoints[0].connectorStyle || this.endpoints[1].connectorStyle || params.paintStyle || _currentInstance.Defaults.PaintStyle || jsPlumb.Defaults.PaintStyle; + this.backgroundPaintStyle = this.endpoints[0].connectorBackgroundStyle || this.endpoints[1].connectorBackgroundStyle || params.backgroundPaintStyle || _currentInstance.Defaults.BackgroundPaintStyle || jsPlumb.Defaults.BackgroundPaintStyle; + + // init overlays + this.overlays = params.overlays || []; + this.addOverlay = function(overlay) { + overlays.push(overlay); + }; + + // this is a shortcut helper method to let people add a label as overlay. + this.labelStyle = params.labelStyle || _currentInstance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle;; + this.label = params.label; + if (this.label) { + this.overlays.push(new jsPlumb.Overlays.Label({labelStyle:this.labelStyle, label:this.label})); + }; + + _updateOffset(this.sourceId); + _updateOffset(this.targetId); + + // functions for mouse hover/select functionality + this.distanceFrom = function(point) { return self.connector.distanceFrom(point); }; + + this.setLabel = function(l) { + self.label = l; + _currentInstance.repaint(self.source); + }; + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var otherOffset = offsets[this.targetId]; otherWH = sizes[this.targetId]; + var anchorLoc = this.endpoints[0].anchor.compute([myOffset.left, myOffset.top], myWH, this.endpoints[0], [otherOffset.left, otherOffset.top], otherWH, this.endpoints[1]); + this.endpoints[0].paint(anchorLoc); + + anchorLoc = this.endpoints[1].anchor.compute([otherOffset.left, otherOffset.top], otherWH, this.endpoints[1], [myOffset.left, myOffset.top], myWH, this.endpoints[0]); + this.endpoints[1].paint(anchorLoc); + + // *************** create canvas on which the connection will be drawn ************ + var canvas = _newCanvas(jsPlumb.connectorClass, self.container); + this.canvas = canvas; + + /** + * paints the connection. + * @param elId Id of the element that is in motion + * @param ui current library's event system ui object (present if we came from a drag to get here) + * @param recalc whether or not to recalculate element sizes. this is true if a repaint caused this to be painted. + */ + this.paint = function(elId, ui, recalc, timestamp) { + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = false;//!(elId == this.sourceId); + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + if (this.canvas.getContext) { + _updateOffset(elId, ui, recalc); + _updateOffset(tId); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var myOffset = offsets[sId]; + var otherOffset = offsets[tId]; + var myWH = sizes[sId]; + var otherWH = sizes[tId]; + + var ctx = canvas.getContext('2d'); + //TODO: why are these calculated again? they were calculated in the _draw function. + var sAnchorP = this.endpoints[sIdx].anchor.compute([myOffset.left, myOffset.top], myWH, this.endpoints[sIdx], [otherOffset.left, otherOffset.top], otherWH, this.endpoints[tIdx]); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.compute([otherOffset.left, otherOffset.top], otherWH, this.endpoints[tIdx], [myOffset.left, myOffset.top], myWH, this.endpoints[sIdx]); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + + // paint overlays + var maxSize = 0; + for (var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + var s = o.computeMaxSize(self.connector, ctx); + if (s > maxSize) maxSize = s; + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, this.paintStyle.lineWidth, maxSize); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + + var _paintOneStyle = function(ctx, paintStyle) { + ctx.save(); + jsPlumb.extend(ctx, paintStyle); + if (paintStyle.gradient && !ie) { + var g = self.connector.createGradient(dim, ctx, (elId == this.sourceId)); + for (var i = 0; i < paintStyle.gradient.stops.length; i++) + g.addColorStop(paintStyle.gradient.stops[i][0],paintStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + self.connector.paint(dim, ctx); + ctx.restore(); + }; + + + // first check for the background style + if (this.backgroundPaintStyle != null) { + _paintOneStyle(ctx, this.backgroundPaintStyle); + } + _paintOneStyle(ctx, this.paintStyle); + + // paint overlays + for (var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + o.draw(self.connector, ctx); + } + } + }; + + //TODO: should this take a timestamp? probably. it reduces the amount of time + // spent figuring out anchor locations. + this.repaint = function() { + this.paint(this.sourceId, null, true); + }; + + _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + }; + + /* + Class: Endpoint + Models an endpoint. Can have one to N connections emanating from it (although how to handle that in the UI is + a very good question). also has a Canvas and paint style. + */ + + /* + Function: Endpoint + This is the Endpoint class constructor. + Parameters: + anchor - anchor for the endpoint, of type jsPlumb.Anchor. may be null. + endpoint - endpoint object, of type jsPlumb.Endpoint. may be null. + style - endpoint style, a js object. may be null. + source - element the endpoint is attached to, of type jquery object. Required. + canvas - canvas element to use. may be, and most often is, null. + connections - optional list of connections to configure the endpoint with. + isSource - boolean. indicates the endpoint can act as a source of new connections. optional. + dragOptions - if isSource is set to true, you can supply arguments for the jquery draggable method. optional. + connectionStyle - if isSource is set to true, this is the paint style for connections from this endpoint. optional. + connector - optional connector type to use. + isTarget - boolean. indicates the endpoint can act as a target of new connections. optional. + dropOptions - if isTarget is set to true, you can supply arguments for the jquery droppable method. optional. + reattach - optional boolean that determines whether or not the connections reattach after they + have been dragged off an endpoint and left floating. defaults to false: connections + dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + params = params || {}; + // make a copy. then we can use the wrapper function. + jsPlumb.extend({}, params); + var self = this; + var id = new String('_jsplumb_e_' + (new Date()).getTime()); + this.getId = function() { return id; }; + self.anchor = params.anchor || jsPlumb.Anchors.TopCenter; + //if (self.anchor.isDynamic) self.anchor.endpoint = self; + var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot(); + var _style = params.style || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + this.connectorStyle = params.connectorStyle; + this.connector = params.connector; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + var _element = params.source; + var _uuid = params.uuid; + if (_uuid) endpointsByUUID[_uuid] = self; + this.container = params.container || _currentInstance.Defaults.Container || jsPlumb.Defaults.Container; + var _elementId = _getAttribute(_element, "id"); + var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of. + this.canvas = params.canvas || _newCanvas(jsPlumb.endpointClass, this.container, params.uuid); + this.connections = params.connections || []; + this.scope = params.scope || DEFAULT_SCOPE; + var _reattach = params.reattach || false; + var floatingEndpoint = null; + var inPlaceCopy = null; + this.dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.addConnection = function(connection) { + self.connections.push(connection); + }; + this.detach = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + self.connections.splice(idx, 1); + t.detach(connection); + } + }; + + /** + * detaches all connections this Endpoint has + */ + this.detachAll = function() { + while(self.connections.length > 0) { + self.detach(self.connections[0]); + } + }; + + /** + * removes any connections from this Endpoint that are + * connected to the given target endpoint. + * @param targetEndpoint + */ + this.detachFrom = function(targetEndpoint) { + var c = []; + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for (var i = 0; i < c.length; i++) { + targetEndpoint.detach(c[i]); + self.detach(c[i]); + } + }; + + /** + * returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { return _element; }; + + /** + * returns the UUID for this Endpoint, if there is one. + */ + this.getUuid= function() { return _uuid; }; + + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + var e = new Endpoint({anchor:self.anchor, source:_element, style:_style, endpoint:_endpoint}); + return e; + }; + + + /** + * returns whether or not this endpoint is connected to the given endpoint. + * @param endpoint Endpoint to test. + * @since 1.1.1 + * + * todo: needs testing. will this work if the endpoint passed in is the source? + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + * @returns {Boolean} + */ + this.isFloating = function() { return floatingEndpoint != null; }; + /** + * first pass at default ConnectorSelector: returns the first connection, if we have any. + * modified a little, 5/10/2010: now it only returns a connector if we have not got equal to or more than _maxConnector connectors + * attached. otherwise it is assumed a new connector is ok. but note with this setup we can't select any other connection than the first + * one. what if this could return a list? that implies everything should work with a list - dragging etc. could be nasty. could also + * be cool. + */ + var connectorSelector = function() { + //return self.connections.length == 0 || self.connections.length < _maxConnections ? null : self.connections[0]; + if (self.connections.length < _maxConnections) return null; + //else if (self.connections.length == _maxConnections) return false; + else return self.connections[0]; + }; + + /** + * @returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); }; + + /** + * sets whether or not connnctions can be dragged from this Endpoint once it is full. you would use + * this in a UI in which you're going to provide some other way of breaking connections, if you need + * to break them at all. this property is by default true; use it in conjunction with the 'reattach' + * option on a connect call. + */ + this.setDragAllowedWhenFull = function(allowed) { + self.dragAllowedWhenFull = allowed; + }; + + /** + * a deep equals check. everything must match, including the anchor, styles, everything. + * TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor) && + true; + }; + + /** + * paints the Endpoint, recalculating offset and anchor positions if necessary. + */ + this.paint = function(anchorPoint, connectorPaintStyle, canvas) { + + if (log) log.debug("Painting Endpoint with elementId [" + _elementId + "]; anchorPoint is [" + anchorPoint + "]"); + + if (anchorPoint == null) { + // do we always want to force a repaint here? i dont think so! + var xy = offsets[_elementId]; + var wh = sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset(_elementId); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + anchorPoint = self.anchor.compute([xy.left, xy.top], wh, self); + } + _endpoint.paint(anchorPoint, self.anchor.getOrientation(), canvas || self.canvas, _style, connectorPaintStyle || _style); + }; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + + // is this a connection source? we make it draggable and have the drag listener + // maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + + // first the question is, how many connections are on this endpoint? if it's only one, then excellent. otherwise we will either need a way + // to select one connection from the list, or drag them all. if we had a pluggable 'ConnectorSelector' interface we could probably + // provide a way for people to implement their own UI components to do the connector selection. the question in that particular case would be how much + // information the interface needs from jsPlumb at execution time. if, however, we leave out the connector selection, and drag them all, + // that wouldn't be too hard to organise. perhaps that behaviour would be on a switch for the endpoint, or perhaps the ConnectorSelector + // interface returns a List, with the default implementation just returning everything. i think i like that. + // + // let's say for now that there is just one endpoint, cos we need to get this going before we can consider a list of them anyway. + // the major difference between that case and the case of a new endpoint is actually quite small - it's a question of where the + // Connection comes from. for a new one, we create a new one. obviously. otherwise we just get the jpc from the Endpoint + // (remember we're only assuming one connection right now). so all of the UI stuff we do to create the floating endpoint etc + // will still be valid, but when we stop dragging, we'll have to do something different. if we stop with a valid drop i think it will + // be the same process. but if we stop with an invalid drop we have to reset the Connection to how it was when we got it. + var start = function() { + + jpc = connectorSelector(); + if (self.isFull() && !self.dragAllowedWhenFull) return false; + + _updateOffset(_elementId); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + var nE = _getElementObject(n); + _appendElement(n, self.container); // + // create and assign an id, and initialize the offset. + //TODO can't this be replaced with a _getId call? + var id = "" + new String(new Date().getTime()); + _setAttribute(nE, "id", id); + _updateOffset(id); + // store the id of the dragging div and the source element. the drop function + // will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor({reference:self.anchor, referenceCanvas:self.canvas}); + floatingEndpoint = new Endpoint({ style:{fillStyle:'rgba(0,0,0,0)'}, endpoint:_endpoint, anchor:floatingAnchor, source:nE }); + + if (jpc == null) { + self.anchor.locked = true; + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = new Connection({ + sourceEndpoint:self, + targetEndpoint:floatingEndpoint, + source:_getElementObject(_element), + target:_getElementObject(n), + anchors:[self.anchor, floatingAnchor], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + connector: params.connector + }); + } else { + existingJpc = true; + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; + // probably we should remove the connection? and add it back if the user + // does not drop it somewhere proper. + self.removeConnection(jpc); + if (anchorIdx == 0){ + existingJpcParams = [jpc.source, jpc.sourceId]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + }else { + existingJpcParams = [jpc.target, jpc.targetId]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // todo unregister on stop + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + }; + + var dragOptions = params.dragOptions || { }; + + var defaultOpts = jsPlumb.extend({}, jsPlumb.CurrentLibrary.defaultDragOptions); + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + var startEvent = jsPlumb.CurrentLibrary.dragEvents['start']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + //$(n).offset(_ui); + jsPlumb.CurrentLibrary.setOffset(n, _ui); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements([n, floatingEndpoint.canvas], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + + if (_reattach) { + jpc.floatingAnchorIndex = null; + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } + else { + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeElement(jpc.canvas, self.container); + _removeFromList(connectionsByScope, jpc.scope, jpc); + _fireEvent("jsPlumbConnectionDetached", { + source:jpc.source, + target:jpc.target, + sourceId:jpc.sourceId, + targetId:jpc.targetId, + sourceEndpoint:jpc.endpoints[0], + targetEndpoint:jpc.endpoints[1] + }); + } + } else { + _removeElement(jpc.canvas, self.container); + //if (jpc.endpoints[1]) alert("target set"); + self.removeConnection(jpc); + } + } + self.anchor.locked = false; + jpc = null; + delete floatingEndpoint; + delete inPlaceCopy; + self.paint(); + } + ); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + }; + + // connector target + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend({}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + dropOptions[dropEvent]= _wrap(dropOptions[dropEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable), "dragId"); + var elId = _getAttribute(_getElementObject(draggable),"elId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + var oidx = idx == 0 ? 1 : 0; + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + //todo test that the target is not full. + // remove this jpc from the current endpoint + jpc.endpoints[idx].removeConnection(jpc); + if (jpc.suspendedEndpoint) + jpc.suspendedEndpoint.removeConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + // add the jpc to the other endpoint too. + jpc.endpoints[oidx].addConnection(jpc); + + _addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + jsPlumb.repaint(elId); + _fireEvent("jsPlumbConnection", { + source:jpc.source, + target:jpc.target, + sourceId:jpc.sourceId, + targetId:jpc.targetId, + sourceEndpoint:jpc.endpoints[0], + targetEndpoint:jpc.endpoints[1] + }); + } + // else there must be some cleanup required. + + delete floatingConnections[id]; + }); + + dropOptions[overEvent]= _wrap(dropOptions[overEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + }); + + jsPlumb.CurrentLibrary.initDroppable(_getElementObject(self.canvas), dropOptions); + } + + return self; + }; + + /* + Property: Defaults + These are the default settings for jsPlumb, that is what will be used if you do not supply specific pieces of information + to the various API calls. A convenient way to implement your own look and feel can be to override these defaults by including a script + somewhere after the jsPlumb include, but before you make any calls to jsPlumb, for instance in this example we set the PaintStyle to be + a blue line of 27 pixels: + > jsPlumb.Defaults.PaintStyle = { lineWidth:27, strokeStyle:'blue' } + */ + this.Defaults = { + Anchor : null, + Anchors : [ null, null ], + BackgroundPaintStyle : null , + Connector : null, + Container : null, + DragOptions: { }, + DropOptions: { }, + Endpoint : null, + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : null }, + EndpointStyles : [ null, null ], + LabelStyle : { fillStyle:"rgba(0,0,0,0)", color:"black" }, + MaxConnections : null, + // TODO: should we have OverlayStyle too? + PaintStyle : { lineWidth : 10, strokeStyle : 'red' }, + Scope : "_jsPlumb_DefaultScope" + + }; + + /* + Property: connectorClass + The CSS class to set on Connection canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = '_jsPlumb_connector'; + + /* + Property: endpointClass + The CSS class to set on Endpoint canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = '_jsPlumb_endpoint'; + + /* + Property: Anchors + Default jsPlumb Anchors. These are supplied in the file jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form .jsPlumb-all-x.x.x.js. You can provide your own Anchors by supplying them in a script that is loaded after jsPlumb, for instance: + > jsPlumb.Anchors.MyAnchor = { ....anchor code here. see the documentation. } + */ + this.Anchors = {}; + + /* + Property: Connectors + Default jsPlumb Connectors. These are supplied in the file jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form .jsPlumb-all-x.x.x.js. You can provide your own Connectors by supplying them in a script that is loaded after jsPlumb, for instance: + > jsPlumb.Connectors.MyConnector = { ....connector code here. see the documentation. } + */ + this.Connectors = {}; + + /* + Property: Endpoints + Default jsPlumb Endpoints. These are supplied in the file jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form .jsPlumb-all-x.x.x.js. You can provide your own Endpoints by supplying them in a script that is loaded after jsPlumb, for instance: + > jsPlumb.Endpoints.MyEndpoint = { ....endpoint code here. see the documentation. } + */ + this.Endpoints = {}; + + /* + Property:Overlays + Default jsPlumb Overlays such as Arrows and Labels. These are supplied in the file jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form .jsPlumb-all-x.x.x.js. You can provide your own Overlays by supplying them in a script that is loaded after jsPlumb, for instance: + > jsPlumb.Overlays.MyOverlay = { ....overlay code here. see the documentation. } + */ + this.Overlays = {}; + + /* + Function: addEndpoint + Adds an Endpoint to a given element. + Parameters: + target - Element to add the endpoint to. either an element id, or a selector representing some element. + params - Object containing Endpoint options (more info required) + Returns: + The newly created Endpoint. + See Also: + + */ + this.addEndpoint = function(target, params) { + params = jsPlumb.extend({}, params); + var el = _getElementObject(target); + var id = _getAttribute(el,"id"); + params.source = el; + _updateOffset(id); + var e = new Endpoint(params); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id]; + var myWH = sizes[id]; + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH, e); + e.paint(anchorLoc); + return e; + }; + + /* + Function: addEndpoint + Adds a list of Endpoints to a given element. + Parameters: + target - element to add the endpoint to. either an element id, or a selector representing some element. + endpoints - List of objects containing Endpoint options. one Endpoint is created for each entry in this list. + Returns: + List of newly created Endpoints, one for each entry in the 'endpoints' argument. + See Also: + + */ + this.addEndpoints = function(target, endpoints) { + var results = []; + for (var i = 0; i < endpoints.length; i++) { + results.push(_currentInstance.addEndpoint(target, endpoints[i])); + } + return results; + }; + + /* + Function: animate + Wrapper around supporting library's animate function; injects a call to jsPlumb in the 'step' function (creating it if necessary). + This only supports the two-arg version of the animate call in jQuery - the one that takes an 'options' object as the second arg. + MooTools has only one method - a two arg one. Which is handy. + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the standard jQuery animate call. + options - The 'options' argument you want passed to the standard jQuery animate call. + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el); + var id = _getAttribute(el,"id"); + //TODO this is not agnostic yet. + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() + { + _currentInstance.repaint(id); + }); + + // you know, probably onComplete should repaint too. that will help keep up + // with fast animations. + options[completeFunction] = _wrap(options[completeFunction], function() + { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /** + * clears the cache used to lookup elements by their id. if you remove any elements + * from the DOM you should call this to ensure that if you add them back in jsPlumb does not + * have a stale handle. + */ + this.clearCache = function() { + delete cached; + cached = {}; + }; + + this.autoConnect = function(params/*source, target, anchors, endpoint*/) { + var sources = [], targets = [], _endpoint = params.endpoint || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle, _anchors = anchors || _currentInstance.Defaults.DynamicAnchors || jsPlumb.Defaults.DynamicAnchors; + var _addAll = function(s, t) { + for (var i = 0 ; i < s.length; i++) t.push(s[i]); + }; + var source = params.source, target = params.target, anchors = params.anchors; + if (typeof source == 'string') + sources.push(_getElementObject(source)); + else + _addAll(source, sources); + if (typeof target == 'string') + targets.push(_getElementObject(target)); + else + _addAll(target, targets); + var connectOptions = jsPlumb.extend(params, {source:null,target:null,anchors:null}); + for (var i = 0; i < sources.length; i++) { + for (var j = 0; j < targets.length; j++) { + var e1 = jsPlumb.addEndpoint(sources[i], jsPlumb.extend({ anchor:jsPlumb.makeDynamicAnchor(_anchors) }, _endpoint)); + var e2 = jsPlumb.addEndpoint(targets[j], jsPlumb.extend({ anchor:jsPlumb.makeDynamicAnchor(_anchors) }, _endpoint)); + _currentInstance.connect(jsPlumb.extend(connectOptions, { sourceEndpoint:e1, targetEndpoint:e2 })); + } + } + }; + + /* + Function: connect + Establishes a connection between two elements. + Parameters: + params - Object containing setup for the connection. see documentation. + Returns: + The newly created Connection. + */ + this.connect = function(params) { + var _p = jsPlumb.extend({}, params); + // test for endpoint uuids to connect + if (params.uuids) { + var _resolveByUuid = function(idx) { + var e = _getEndpoint(params.uuids[idx]); + if (!e) throw ("Endpoint with UUID " + params.uuids[idx] + " not found."); + return e; + }; + _p.sourceEndpoint = _resolveByUuid(0); + _p.targetEndpoint = _resolveByUuid(1); + } + + // now ensure that if we do have Endpoints already, they're not full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log("could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log("could not add connection; target endpoint is full"); + return; + } + + var jpc = new Connection(_p); + // add to list of connections (by scope). + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + _fireEvent("jsPlumbConnection", { + source:jpc.source, + target:jpc.target, + sourceId:jpc.sourceId, + targetId:jpc.targetId, + sourceEndpoint:jpc.endpoints[0], + targetEndpoint:jpc.endpoints[1] + }); + // force a paint + _draw(jpc.source); + + return jpc; + }; + + var fireDetachEvent = function(jpc) { + _fireEvent("jsPlumbConnectionDetached", { + source:jpc.source, + target:jpc.target, + sourceId:jpc.sourceId, + targetId:jpc.targetId, + sourceEndpoint:jpc.endpoints[0], + targetEndpoint:jpc.endpoints[1] + }); + }; + + /* + Function: detach + Removes a connection. + Parameters: + sourceId - Id of the first element in the connection. A String. + targetId - iI of the second element in the connection. A String. + Returns: + true if successful, false if not. + */ + this.detach = function(sourceId, targetId) { + if (arguments.length == 2) { + var f = function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + fireDetachEvent(jpc); + + return true; + } + + }; + + // todo: how to cleanup the actual storage? a third arg to _operation? + _operation(sourceId, f); + } + // this is the new version of the method, taking a JS object like the connect method does. + else if (arguments.length == 1) { + var _p = jsPlumb.extend({}, sourceId); // a backwards compatibility hack: sourceId should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1])); + } + else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } + else { + sourceId = _getId(_p.source); + targetId = _getId(_p.target); + var f = function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + fireDetachEvent(jpc); + + return true; + } + + }; + + // todo: how to cleanup the actual storage? a third arg to _operation? + _operation(sourceId, f); + } + + // fire an event + /*_fireEvent("jsPlumbDetach", { + source:jpc.source, + target:jpc.target, + sourceId:jpc.sourceId, + targetId:jpc.targetId, + sourceEndpoint:jpc.endpoints[0], + targetEndpoint:jpc.endpoints[1] + });*/ + /*// force a paint + _draw(jpc.source); + + return jpc;*/ + } + }; + + /* + Function: detachAll + Removes all an element's connections. + Parameters: + el - either the id of the element, or a selector for the element. + Returns: + void + */ + this.detachAll = function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + var c = endpoints[i].connections.length; + if (c > 0) { + for (var j = 0; j < c; j++) { + var jpc = endpoints[i].connections[0]; + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + fireDetachEvent(jpc); + } + } + } + } + }; + + /* + Function: detachEverything + Remove all Connections from all elements, but leaves Endpoints in place. + Returns: + void + See Also: + + */ + this.detachEverything = function() { + for(var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + var c = endpoints[i].connections.length; + if (c > 0) { + for (var j = 0; j < c; j++) { + var jpc = endpoints[i].connections[0]; + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + fireDetachEvent(jpc); + } + } + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + /* + Function: draggable + initialises the draggability of some element or elements. + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for (var i = 0; i < el.length; i++) + { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) + _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + Parameters: + o1 - object to extend + o2 - object to extend o1 with + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + Function: getConnections + Gets all or a subset of connections currently managed by this jsPlumb instance. + Parameters: + options - a JS object that holds options defining what sort of connections you're looking for. Valid values are: + scope this may be a String or a list of Strings. jsPlumb will only return Connections whose scope matches what this option + defines. If you omit it, you will be given Connections of every scope. + source may be a string or a list of strings; constraints results to have only Connections whose source is this object. + target may be a string or a list of strings; constraints results to have only Connections whose target is this object. + + The return value is a dictionary in this format: + + { + 'scope1': [ + {sourceId:'window1', targetId:'window2'}, + {sourceId:'window3', targetId:'window4'}, + {sourceId:'window1', targetId:'window3'} + ], + 'scope2': [ + {sourceId:'window1', targetId:'window3'} + ] + } + + */ + this.getConnections = function(options) { + var r = {}; + options = options || {}; + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') r.push(input); + else r = input; + } + return r; + }; + var scopes = prepareList(options.scope); + var sources = prepareList(options.source); + var targets = prepareList(options.target); + var filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }; + for (var i in connectionsByScope) { + if (filter(scopes, i)) { + r[i] = []; + for (var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + r[i].push({ + sourceId:c.sourceId, + targetId:c.targetId, + source:c.source, + target:c.target, + sourceEndpoint:c.endpoints[0], + targetEndpoint:c.endpoints[1] + }); + } + } + } + return r; + }; + + /* + Function: getDefaultScope + Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a scope to an endpoint + or connection allows you to support different types of connections in the same UI. but if you're only interested in one type of connection, + you don't need to supply a scope. this method will probably be used by very few people; it's good for testing though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + gets an Endpoint by UUID + Parameters: + uuid - the UUID for the Endpoint + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /* + * gets an element's id, creating one if necessary. really only exposed for the lib-specific + * functionality to access; would be better to pass the current instance into the lib-specific + * code (even though this is a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + + /* + Function: hide + Sets an element's connections to be hidden. + Parameters: + el - either the id of the element, or a selector for the element. + Returns: + void + */ + this.hide = function(el) { + _setVisible(el, "none"); + }; + + /* + Function: makeAnchor + Creates an anchor with the given params. + Parameters: + x - the x location of the anchor as a fraction of the total width. + y - the y location of the anchor as a fraction of the total height. + xOrientation - value indicating the general direction a connection from the anchor should go in, in the x direction. + yOrientation - value indicating the general direction a connection from the anchor should go in, in the y direction. + xOffset - a fixed offset that should be applied in the x direction that should be applied after the x position has been figured out. optional. defaults to 0. + yOffset - a fixed offset that should be applied in the y direction that should be applied after the y position has been figured out. optional. defaults to 0. + Returns: + The newly created Anchor. + */ + this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards compatibility if we are given only one value we assume it's a call in the old form. + var params = {}; + if (arguments.length == 1) jsPlumb.extend(params, x); + else { + params = {x:x, y:y}; + if (arguments.length >= 4) { + params.orientation = [arguments[2], arguments[3]]; + } + if (arguments.length == 6) params.offsets = [arguments[4], arguments[5]]; + } + var a = new Anchor(params); + a.clone = function() { return new Anchor(params); }; + return a; + }; + + this.makeDynamicAnchor = function(anchors) { + return new DynamicAnchor(anchors); + }; + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + Parameters: + el - either the id of the element or a selector representing the element. + Returns: + void + See Also: + + */ + this.repaint = function(el) { + + var _processElement = function(el) { + var ele = _getElementObject(el); + _draw(ele); + }; + + // TODO: support a jQuery result object too! + + // support both lists... + if (typeof el =='object') { + for (var i = 0; i < el.length; i++) + _processElement(el[i]); + } // ...and single strings. + else _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + Returns: + void + See Also: + + */ + this.repaintEverything = function() { + for (var elId in endpointsByElement) { + _draw(_getElementObject(elId)); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + Parameters: + el - either an element id, or a selector for an element. + Returns: + void + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"); + // first remove all Connections. + jsPlumb.detachAll(elId); + var ebe = endpointsByElement[elId]; + for (var i in ebe) { + _removeElement(ebe[i].canvas, ebe[i].getElement()); + } + endpointsByElement[elId] = []; + }; + + /* + Function: removeEveryEndpoint + Removes every Endpoint in this instance of jsPlumb. + Returns: + void + See Also: + + */ + this.removeEveryEndpoint = function() { + for(var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + _removeElement(endpoints[i].canvas, endpoints[i].container); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + }; + + /* + Function: removeEndpoint + Removes the given Endpoint from the given element. + Parameters: + el - either an element id, or a selector for an element. + endpoint - Endpoint to remove. this is an Endpoint object, such as would have been returned from a call to addEndpoint. + Returns: + void + See Also: + + */ + this.removeEndpoint = function(el, endpoint) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + if (ebe) { + if(_removeFromList(endpointsByElement, elId, endpoint)) + _removeElement(endpoint.canvas, endpoint.getElement()); + } + }; + + /* + Function:reset + removes all endpoints and connections and clears the element cache. + */ + this.reset = function() { + this.detachEverything(); + this.removeEveryEndpoint(); + this.clearCache(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + Parameters: + value - whether or not to automatically repaint when the window is resized. + Returns: + void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + Function: setDefaultNewCanvasSize + Sets the default size jsPlumb will use for a new canvas (we create a square canvas so one value is all that is required). + This is a hack for IE, because ExplorerCanvas seems to need for a canvas to be larger than what you are going to draw on + it at initialisation time. The default value of this is 1200 pixels, which is quite large, but if for some reason you're + drawing connectors that are bigger, you should adjust this value appropriately. + Parameters: + size - The default size to use. jsPlumb will use a square canvas so you need only supply one value. + Returns: + void + */ + this.setDefaultNewCanvasSize = function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }; + + /* + Function: setDefaultScope + Sets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a scope to an endpoint + or connection allows you to support different types of connections in the same UI. but if you're only interested in one type of connection, + you don't need to supply a scope. this method will probably be used by very few people; it just instructs jsPlumb to use a different key + for the default scope. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + Function: setDraggable + Sets whether or not a given element is draggable, regardless of what any plumb command may request. + Parameters: + el - either the id for the element, or a selector representing the element. + Returns: + void + */ + this.setDraggable = _setDraggable; + + /* + Function: setDraggableByDefault + Sets whether or not elements are draggable by default. Default for this is true. + Parameters: + draggable - value to set + Returns: + void + */ + this.setDraggableByDefault = function(draggable) { + _draggableByDefault = draggable; + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + Function: setRepaintFunction + Sets the function to fire when the window size has changed and a repaint was fired. + Parameters: + f - Function to execute. + Returns: + void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + Function: show + Sets an element's connections to be visible. + Parameters: + el - either the id of the element, or a selector for the element. + Returns: + void + */ + this.show = function(el) { + _setVisible(el, "block"); + }; + + /* + Function: sizeCanvas + Helper to size a canvas. You would typically use this when writing your own Connector or Endpoint implementation. + Parameters: + x - [int] x position for the Canvas origin + y - [int] y position for the Canvas origin + w - [int] width of the canvas + h - [int] height of the canvas + Returns: + void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; canvas.height = h; + canvas.style.width = w + "px"; canvas.width = w; + canvas.style.left = x + "px"; canvas.style.top = y + "px"; + // _currentInstance.CurrentLibrary.setPosition(canvas, x, y); + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards compatibility + */ + this.toggle = _toggleVisible; + + /* + Function: toggleVisible + Toggles visibility of an element's connections. + Parameters: + el - either the element's id, or a selector representing the element. + Returns: + void, but should be updated to return the current state + */ + //TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + Function: toggleDraggable + Toggles draggability (sic) of an element's connections. + Parameters: + el - either the element's id, or a selector representing the element. + Returns: + The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + Function: unload + Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element + Returns: + void + */ + this.unload = function() { + delete endpointsByElement; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + }; + + /* + Function: wrap + Helper method to wrap an existing function with one of your own. This is used by the various + implementations to wrap event callbacks for drag/drop etc; it allows jsPlumb to be + transparent in its handling of these things. If a user supplies their own event callback, + for anything, it will always be called. + Parameters: + */ + this.wrap = _wrap; + this.addListener = _addListener; + + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(); + if (_defaults) + jsPlumb.extend(j.Defaults, _defaults); + return j; + }; +})(); +/* +* jsPlumb-defaults-1.2.3-RC1 +* +* This script contains the default Anchors, Endpoints, Connectors and Overlays for jsPlumb. It should be used with jsPlumb 1.1.0 and above; +* prior to version 1.1.0 of jsPlumb the defaults were included inside the main script. +* +* NOTE: for production usage you should use jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, +* in a minified file. +* +* Dual licensed under MIT and GPL2. +*/ + +(function() { + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * getOrientation : function() { return [ox, oy]; } + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + jsPlumb.Anchors.TopCenter = jsPlumb.makeAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors.BottomCenter = jsPlumb.makeAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors.LeftMiddle = jsPlumb.makeAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors.RightMiddle = jsPlumb.makeAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors.Center = jsPlumb.makeAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors.TopRight = jsPlumb.makeAnchor(1, 0, 0,-1); + jsPlumb.Anchors.BottomRight = jsPlumb.makeAnchor(1, 1, 0, 1); + jsPlumb.Anchors.TopLeft = jsPlumb.makeAnchor(0, 0, 0, -1); + jsPlumb.Anchors.BottomLeft = jsPlumb.makeAnchor(0, 1, 0, 1); + jsPlumb.Anchors.AutoDefault = function() { return jsPlumb.makeDynamicAnchor([jsPlumb.Anchors.TopCenter, jsPlumb.Anchors.RightMiddle, jsPlumb.Anchors.BottomCenter, jsPlumb.Anchors.LeftMiddle]); }; + + jsPlumb.Defaults.DynamicAnchors = [jsPlumb.Anchors.TopCenter, jsPlumb.Anchors.RightMiddle, jsPlumb.Anchors.BottomCenter, jsPlumb.Anchors.LeftMiddle]; + + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + var currentPoints = null; + var _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + + _dx = _tx - _sx, _dy = (_ty - _sy); + _m = _dy / _dx, _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + + return currentPoints; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + var xp = _sx + (location * _dx); + var yp = _m == Infinity ? xp + _b : (_m * xp) + _b; + return [xp, yp]; + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { return _m; }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + var orientation = distance > 0 ? 1 : -1; + var y = Math.abs(distance * Math.sin(_theta)); + if (_sy > _ty) y = y * -1; + var x = Math.abs(distance * Math.cos(_theta)); + if (_sx > _tx) x = x * -1; + return [p[0] + (orientation * x), p[1] + (orientation * y)]; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, distance, length) { + var p = self.pointAlongPathFrom(location, distance); + var m = self.gradientAtPoint(p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [[p[0] + x, p[1] + y], [p[0] - x, p[1] - y]]; + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + /** + * This Connector draws a Bezier curve with two control points. + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + jsPlumb.Connectors.Bezier = function(curviness) { + var self = this; + this.majorAnchor = curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _sx, _sy, _canvasX, _canvasY, _w, _h; + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) + { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx); var minx2 = Math.min(_CP[0], _CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(_sx,_tx); var maxx2 = Math.max(_CP[0], _CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty); var miny2 = Math.min(_CP[1], _CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(_sy,_ty); var maxy2 = Math.max(_CP[1], _CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + this.paint = function(d, ctx) { + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + }; + + /** + * returns the distance the given point is from the curve. not enabled for 1.2.3. didnt make the cut. next time. + * + this.distanceFrom = function(point) { + var curve = [ {x:currentPoints[4], y:currentPoints[5]}, + {x:currentPoints[8], y:currentPoints[9]}, + {x:currentPoints[10], y:currentPoints[11]}, + {x:currentPoints[6], y:currentPoints[7]}]; + return (jsPlumb.DistanceFromCurve(point, curve)); + };*/ + + var _quadraticPointOnPath = function(location) { + function B1(t) { return t*t; }; + function B2(t) { return 2*t*(1-t); }; + function B3(t) { return (1-t)*(1-t); }; + var x = _sx*B1(location) + _CP[0]*B2(location) + _CP2[0]*B3(location); + var y = _sy*B1(location) + _CP[1]*B2(location) + _CP2[1]*B3(location); + return [x,y]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + + // from http://13thparallel.com/archive/bezier-curves/ + function B1(t) { return t*t*t }; + function B2(t) { return 3*t*t*(1-t) }; + function B3(t) { return 3*t*(1-t)*(1-t) }; + function B4(t) { return (1-t)*(1-t)*(1-t) }; + + var x = _sx*B1(location) + _CP[0]*B2(location) + _CP2[0]*B3(location) + _tx*B4(location); + var y = _sy*B1(location) + _CP[1]*B2(location) + _CP2[1]*B3(location) + _ty*B4(location); + return [x,y]; + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + var p1 = self.pointOnPath(location); + var p2 = _quadraticPointOnPath(location); + var dy = p2[1] - p1[1], dx = p2[0] - p1[0]; + var rtn = Math.atan(dy / dx) ; + // http://bimixual.org/AnimationLibrary/beziertangents.html + return rtn; + }; + + /** + * finds the point that is 'distance' along the path from 'location'. this method returns both the x,y location of the point and also + * its 'location' (proportion of travel along the path). + */ + var _pointAlongPath = function(location, distance) { + var _dist = function(p1,p2) { return Math.sqrt(Math.pow(p1[0] - p2[0], 2) + Math.pow(p1[1] - p2[1], 2)); }; + var prev = self.pointOnPath(location), tally = 0, curLoc = location, direction = distance > 0 ? 1 : -1, cur = null; + while (tally < Math.abs(distance)) { + curLoc += (0.005 * direction); + cur = self.pointOnPath(curLoc); + tally += _dist(cur, prev); + prev = cur; + } + return {point:cur, location:curLoc}; + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return _pointAlongPath(location, distance).point; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, distance, length) { + var p = _pointAlongPath(location, distance); + var m = self.gradientAtPoint(p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [[p.point[0] + x, p.point[1] + y], [p.point[0] - x, p.point[1] - y]]; + }; + + this.createGradient = function(dim, ctx, swap) { + return (swap) ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + + /** + * Types of endpoint UIs. we supply four - a circle of default radius 10px, a rectangle of + * default size 20x20, an image (with no default), and a Triangle, of default size 15. + * you can supply others of these if you want to - see the documentation for a howto. + */ + + /** + * a round endpoint, with default radius 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + } + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * A Rectangular endpoint, with default size 20x20. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + jsPlumb.Endpoints.Triangle = function(params) { + + params = params || { width:15, height:15 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - width/2; + var y = anchorPoint[1] - height/2; + + jsPlumb.sizeCanvas(canvas, x, y, width, height); + + var ctx = canvas.getContext('2d'); + var offsetX = 0, offsetY = 0, angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = endpointStyle.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' property in the params object.. + */ + jsPlumb.Endpoints.Image = function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + }; + + /** + * An arrow overlay. you can provide: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + */ + jsPlumb.Overlays.Arrow = function(params) { + params = params || {}; + var self = this; + var length = params.length || 20; + var width = params.width || 20; + var fillStyle = params.fillStyle || "black"; + var strokeStyle = params.strokeStyle; + var lineWidth = params.lineWidth || 1; + this.loc = params.location || 0.5; + // how far along the arrow the lines folding back in come to. default is 62.3%. + var foldback = params.foldback || 0.623; + var _getFoldBackPoint = function(connector, loc) { + if (foldback == 0.5) return connector.pointOnPath(loc); + else { + var adj = 0.5 - foldback; // we calculate relative to the center + return connector.pointAlongPathFrom(loc, length * adj); + } + }; + + this.computeMaxSize = function() { return width * 1.5; } + + this.draw = function(connector, ctx) { + // this is the arrow head position + var hxy = connector.pointAlongPathFrom(self.loc, length / 2); + // this is the center of the tail + var txy = connector.pointAlongPathFrom(self.loc, -length / 2), tx = txy[0], ty = txy[1]; + // this is the tail vector + var tail = connector.perpendicularToPathAt(self.loc, -length / 2, width); + // this is the point the tail goes in to + var cxy = _getFoldBackPoint(connector, self.loc); + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(hxy[0], hxy[1]); + ctx.lineTo(tail[0][0], tail[0][1]); + ctx.lineTo(cxy[0], cxy[1]); + ctx.lineTo(tail[1][0], tail[1][1]); + ctx.lineTo(hxy[0], hxy[1]); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + + /** + * a basic arrow. this is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. See Arrow for params. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + /** + * a diamond. like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. See Arrow for params. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40; + var p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + /** + * A Label overlay. Params you can provide: + * + * labelStyle - js object containing style instructions for the label. defaults to jsPlumb.Defaults.LabelStyle. + * label - the label to paint. may be a string or a function that returns a string. nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * borderWidth - width of a border to paint. defaults to zero. + * borderStyle - strokeStyle to use when painting the border, if necessary. + */ + jsPlumb.Overlays.Label = function(params) { + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + var self = this; + var labelWidth = null, labelHeight = null, labelText = null, labelPadding = null; + this.location = params.location || 0.5; + this.computeMaxSize = function(connector, ctx) { + if (labelText) { + ctx.save(); + if (self.labelStyle.font) ctx.font = self.labelStyle.font; + var t = ctx.measureText(labelText).width; + // a fake text height measurement: use the width of upper case M + var h = ctx.measureText("M").width; + labelPadding = self.labelStyle.padding || 0.25; + labelWidth = t + (2 * t * labelPadding); + labelHeight = h + (2 * h * labelPadding); + ctx.restore(); + return Math.max(labelWidth, labelHeight) * 1.5; + } + return 0; + }; + this.draw = function(connector, ctx) { + // we allow label generation by a function here. you get given the Connection object as an argument. + labelText = typeof self.label == 'function' ? self.label(self) : self.label; + if (labelText) { + if (self.labelStyle.font) ctx.font = self.labelStyle.font; + var t = ctx.measureText(labelText).width; + // a fake text height measurement: use the width of upper case M + var h = ctx.measureText("M").width; + labelPadding = self.labelStyle.padding || 0.25; + labelWidth = t + (2 * t * labelPadding); + labelHeight = h + (2 * h * labelPadding); + var cxy = connector.pointOnPath(self.location); + if (self.labelStyle.font) ctx.font = self.labelStyle.font; + if (self.labelStyle.fillStyle) + ctx.fillStyle = self.labelStyle.fillStyle; + else + ctx.fillStyle = "rgba(0,0,0,0)"; + ctx.fillRect(cxy[0] - (labelWidth / 2), cxy[1] - (labelHeight / 2) , labelWidth , labelHeight ); + + if (self.labelStyle.color) ctx.fillStyle = self.labelStyle.color; + ctx.textBaseline = "middle"; + ctx.textAlign = "center"; + ctx.fillText(labelText, cxy[0], cxy[1]); + + // border + if (self.labelStyle.borderWidth > 0) { + ctx.strokeStyle = self.labelStyle.borderStyle || "black"; + ctx.strokeRect(cxy[0] - (labelWidth / 2), cxy[1] - (labelHeight / 2) , labelWidth , labelHeight ); + } + } + }; + }; +})();/* + * jquery.jsPlumb 1.2.3-RC1 + * + * jQuery specific functionality for jsPlumb. + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + * NOTE: for production usage you should use jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, + * in a minified file. + * + * Dual licensed under MIT and GPL2. + * + */ + +(function($){ + /** + * plumbs the results of the selector to some target, using the given options if supplied, + * or the defaults otherwise. + */ + $.fn.plumb = function(options) { + var options = $.extend({}, options); + + return this.each(function() + { + var params = $.extend({source:$(this)}, options); + jsPlumb.connect(params); + }); + }; + + /** + * detaches the results of the selector from the given target or list of targets - 'target' + * may be a String or a List. + */ + $.fn.detach = function(target) { + return this.each(function() { + if (target) { + var id = $(this).attr("id"); + if (typeof target == 'string') target = [target]; + for (var i = 0; i < target.length; i++) + jsPlumb.detach(id, target[i]); + } + }); + }; + + /** + * detaches the results from the selector from all connections. + */ + $.fn.detachAll = function() { + return this.each(function() + { + var id = $(this).attr("id"); + jsPlumb.detachAll(id); + }); + }; + + /** + * adds an endpoint to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoint = function(options) { + var addedEndpoints = []; + this.each(function() { + addedEndpoints.push(jsPlumb.addEndpoint($(this).attr("id"), options)); + }); + return addedEndpoints[0]; + }; + + /** + * adds a list of endpoints to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoints = function(endpoints) { + var addedEndpoints = []; + return this.each(function() { + var e = jsPlumb.addEndpoints($(this).attr("id"), endpoints); + for (var i = 0; i < e.length; i++) addedEndpoints.push(e[i]); + }); + }; + + /** + * remove the endpoint, if it exists, deleting its UI elements etc. + */ + $.fn.removeEndpoint = function(endpoint) { + this.each(function() { + jsPlumb.removeEndpoint($(this).attr("id"), endpoint); + }); + }; + +})(jQuery); + + +/*TODO: abstract this out from jQuery too! but how...because jsPlumb is not loaded yet. +$(window).bind('resize', function() { + if (resizeTimer) clearTimeout(resizeTimer); + resizeTimer = setTimeout(repaintEverything, 100); + });*/ + + + +/* + * the library agnostic functions, such as find offset, get id, get attribute, extend etc. + */ +(function($) { + + jsPlumb.CurrentLibrary = { + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step', + 'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete' + }, + + bind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.bind(event, callback); + }, + + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).append(child); + }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + * + */ + getElementObject : function(el) { + return typeof(el)=='string' ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options) { + // remove helper directive if present. we know what's best! + options.helper = null; + //TODO: if 'revert' is set on the options it causes end points to animate back to + // where they came from, if the connection is aborted. do we care? probably not. + // the todo is to decide whether we care or not. + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.draggable(options); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.droppable(options); + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[1]; + //return ui.absolutePosition || ui.offset; + return ui.offset || ui.absolutePosition; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + }, + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).remove(); + }, + + getScrollLeft : function(el) { + return el.scrollLeft(); + }, + + getScrollTop : function(el) { + return el.scrollTop(); + }, + + setOffset : function(el, o) { + jsPlumb.CurrentLibrary.getElementObject(el).offset(o); + } + }; +})(jQuery); diff --git a/archive/1.2.3/jsPlumb-1.2.3-RC1.js b/archive/1.2.3/jsPlumb-1.2.3-RC1.js new file mode 100644 index 000000000..17c410cbf --- /dev/null +++ b/archive/1.2.3/jsPlumb-1.2.3-RC1.js @@ -0,0 +1,2165 @@ +/* + * jsPlumb 1.2.3-RC1 + * + * Provides a way to visually connect elements on an HTML page. + * + * additions since 1.2.2: + * + * - labels + * - arrows + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + * Dual licensed under MIT and GPL2. + * + */ + +(function() { + + /* + Class: jsPlumb + + This is the main entry point to jsPlumb. There are a bunch of static methods you can + use to connect/disconnect etc. Some methods are also added to jQuery's "$.fn" object itself, but + in general jsPlumb is moving away from that model, preferring instead the static class + approach. One of the reasons for this is that with no methods added to the jQuery $.fn object, + it will be easier to provide support for other libraries such as MooTools. + */ + var jsPlumbInstance = function() { + + var _currentInstance = this; + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + var log = null; + + var repaintFunction = function() { jsPlumb.repaintEverything(); }; + var automaticRepaint = true; + function repaintEverything() { + if (automaticRepaint) + repaintFunction(); + }; + var resizeTimer = null; + + /** + * helper class for objects that generate events + */ + var EventGenerator = function() { + var _listeners = {}; + this.bind = function(event, listener) { + var l = _listeners[event]; + if (!l) { + l = []; + _listeners[event] = l; + } + l.push(listener); + }; + this.fireUpdate = function(event, value) { + if (_listeners[event]) { + for (var i = 0; i < _listeners[event].length; i++) { + try { + _listeners[event][i](value); + } + catch (e) { } + } + } + }; + }; + + + /** + * map of element id -> endpoint lists. an element can have an arbitrary number of endpoints on it, + * and not all of them have to be connected to anything. + */ + var endpointsByElement = {}; + var endpointsByUUID = {}; + var connectionsByScope = {}; + var offsets = []; + var floatingConnections = {}; + var draggableStates = {}; + var _draggableByDefault = true; + var sizes = []; + var listeners = {}; // a map: keys are event types, values are lists of listeners. + var DEFAULT_SCOPE = 'DEFAULT'; + + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + var _findIndex = function( a, v, b, s ) { + var _eq = function(o1, o2) { + if (o1 === o2) return true; + else if (typeof o1 == 'object' && typeof o2 == 'object') { + var same = true; + for(var propertyName in o1) { + if(!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for(var propertyName in o2) { + if(!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for( var i = +b || 0, l = a.length; i < l; i++ ) { + if( _eq(a[i], v)) return i; + } + return -1; + }; + + /** + * adds a listener for the specified event type. + */ + var _addListener = function(eventType, listener) { + var doOne = function(e, l) { + _addToList(listeners, e, l); + }; + if (typeof eventType == 'object' && eventType.length) { + for (var i = 0; i < eventType.length; i++) doOne(eventType[i], listener); + } else doOne(eventType, listener); + }; + + /** + * helper method to add an item to a list, creating the list if it does not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }; + + var _appendElement = function(canvas, parent) { + if (!parent) document.body.appendChild(canvas); + else jsPlumb.CurrentLibrary.appendElement(canvas, parent); + }; + + /** + * Handles the dragging of an element. + * @param element jQuery element + * @param ui UI object from current library's event system + */ + var _draw = function(element, ui) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints) { + var timestamp = '' + (new Date().getTime()); + _updateOffset(id, ui); + var myOffset = offsets[id]; + var myWH = sizes[id]; + // loop through endpoints for this element + for (var i = 0; i < endpoints.length; i++) { + var e = endpoints[i]; + var l = e.connections; + // here we have a little quandary. if an endpoint is connected to some other element, and it + // has a selective anchor, then we want to know that element's position when we find our + // anchor. if it has no connections then fine, we use the default anchor in the list we can + // select from. but if it has more than one connection, what then? we need to compute where + // it is best placed relative to all other elements, and we then want to lock that position + // for the rest of this paint cycle. if we do not do that, it's possible that an endpoint will + // be moved by an ensuing connector's paint. + if (e.anchor.isSelective && l.length > 0) { + var c = l[0]; + var oIdx = c.endpoints[0] == e ? 1 : 0; + var oId = oIdx == 0 ? c.sourceId : c.targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH, e, [oOffset.left, oOffset.top], oWH, c.endpoints[oIdx]); + e.paint(anchorLoc); + } + else { + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH, e); + e.paint(anchorLoc); + } + for (var j = 0; j < l.length; j++) { + l[j].paint(id, ui, false, timestamp); // ...and paint them. + var oIdx = l[j].endpoints[0] == e ? 1 : 0; + // if the other endpoint has a selective anchor then it might need to be repainted, because it may have just moved + // position. it is possible we could cache the knowledge about that and not run this paint code all the time. + if (l[j].endpoints[oIdx].anchor.isSelective) { + var oId = oIdx == 0 ? l[j].sourceId : l[j].targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + var anchorLoc = l[j].endpoints[oIdx].anchor.compute([oOffset.left, oOffset.top], oWH, l[j].endpoints[oIdx], [myOffset.left, myOffset.top], myWH, e); + l[j].endpoints[oIdx].paint(anchorLoc); + } + } + } + } + }; + + /** + * helper function: the second argument is a function taking two args - the first is an id + * ui element from the current library (or list of them), and the second is the function + * to run. + */ + var _elementProxy = function(element, fn) { + var retVal = null; + if (typeof element == 'object' && element.length) { + retVal = []; + for (var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]); + var id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } + else { + var el = _getElementObject(element); + var id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + + return retVal; + }; + + /** + * fires an event of the given type. + */ + var _fireEvent = function(eventType, data) { + var l = listeners[eventType]; + if (l) { + for (var i in l) { + try { + l[i][eventType](data); + } + catch (e) { + + _log("while firing event [" + eventType + "]; listener failed like this: " + e); + } + } + } + }; + + var _log = function(msg) { + //if (console) console.log(msg); + }; + + var _logFnCall = function(fn, args) { + /*if (console) { + var c = args.callee.caller.toString(); + var i = c.indexOf("{"); + var msg = fn + ' : ' + c.substring(0, i); + console.log(msg); + }*/ + }; + + var cached = {}; + var __getElementObject = function(el) { + + if (typeof(el)=='string') { + var e = cached[el]; + if (!e) { + e = jsPlumb.CurrentLibrary.getElementObject(el); + cached[el] = e; + } + return e; + } + else { + return jsPlumb.CurrentLibrary.getElementObject(el); + } + }; + + + /** + * gets the named attribute from the given element (id or element object) + */ + var _getAttribute = function(el, attName) { + var ele = __getElementObject(el); + return jsPlumb.CurrentLibrary.getAttribute(ele, attName); + }; + + var _setAttribute = function(el, attName, attValue) { + var ele = __getElementObject(el); + jsPlumb.CurrentLibrary.setAttribute(ele, attName, attValue); + }; + + var _addClass = function(el, clazz) { + var ele = __getElementObject(el); + jsPlumb.CurrentLibrary.addClass(ele, clazz); + }; + + var _getElementObject = function(elId) { + return __getElementObject(elId); + }; + + var _getOffset = function(el) { + var ele = __getElementObject(el); + return jsPlumb.CurrentLibrary.getOffset(ele); + }; + + var _getSize = function(el) { + var ele = __getElementObject(el); + return jsPlumb.CurrentLibrary.getSize(ele); + }; + + /** + * gets an id for the given element, creating and setting one if necessary. + */ + var _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id) { + //check if fixed uuid parameter is given + if(arguments.length == 2) + id = uuid; + else + id = "_jsPlumb_" + new String((new Date()).getTime()); + _setAttribute(ele, "id", id); + } + return id; + }; + + /** + * gets an Endpoint by uuid. + */ + var _getEndpoint = function(uuid) { + return endpointsByUUID[uuid]; + }; + + /** + * inits a draggable if it's not already initialised. + */ + var _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + // dragging + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend({}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + } + } + }; + + + /** + * helper to create a canvas. + * @param clazz optional class name for the canvas. + */ + var _newCanvas = function(clazz, parent, uuid) { + var canvas = document.createElement("canvas"); + _appendElement(canvas, parent); + canvas.style.position="absolute"; + if (clazz) { canvas.className=clazz; } + + // set an id. if no id on the element and if uuid was supplied it will be used, otherwise we'll create one. + _getId(canvas, uuid); + + if (ie) { + // for IE we have to set a big canvas size. actually you can override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + /** + * performs the given function operation on all the connections found for the given element + * id; this means we find all the endpoints for the given element, and then for each endpoint + * find the connectors connected to it. then we pass each connection in to the given + * function. + */ + var _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + for (var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }; + /** + * perform an operation on all elements. + */ + var _operationOnAll = function(func) { + for (var elId in endpointsByElement) { + _operation(elId, func); + } + }; + /** + * helper to remove an element from the DOM. + */ + var _removeElement = function(element, parent) { + if (element != null) { + if (!parent) { + try { document.body.removeChild(element); } + catch (e) { } + } else { + jsPlumb.CurrentLibrary.removeElement(element, parent); + } + } + }; + /** + * helper to remove a list of elements from the DOM. + */ + var _removeElements = function(elements, parent) { + for (var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }; + /** + * helper method to remove an item from a list. + */ + var _removeFromList = function(map, key, value) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete( l[i] ); + l.splice( i, 1 ); + return true; + } + } + return false; + }; + /** + * Sets whether or not the given element(s) should be draggable, regardless of what a particular + * plumb command may request. + * + * @param element May be a string, a jQuery elements, or a list of strings/jquery elements. + * @param draggable Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + var _helper = function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }; + + return _elementProxy(element, _helper); + }; + /** + * private method to do the business of hiding/showing. + * @param el either Id of the element in question or a jquery object for the element. + * @param state String specifying a value for the css 'display' property ('block' or 'none'). + */ + var _setVisible = function(el, state) { + var elId = _getAttribute(el, "id"); + var f = function(jpc) { + jpc.canvas.style.display = state; + }; + _operation(elId, f); + }; + /** + * toggles the draggable state of the given element(s). + * @param el either an id, or a jquery object, or a list of ids/jquery objects. + */ + var _toggleDraggable = function(el) { + var fn = function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }; + return _elementProxy(el, fn); + }; + /** + * private method to do the business of toggling hiding/showing. + * @param elId Id of the element in question + */ + var _toggleVisible = function(elId) { + var f = function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }; + + _operation(elId, f); + + //todo this should call _elementProxy, and pass in the _operation(elId, f) call as a function. cos _toggleDraggable does that. + }; + /** + * updates the offset and size for a given element, and stores the values. + * if 'offset' is not null we use that (it would have been passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we use the offset, outerWidth and outerHeight methods to get + * the current values. + */ + var _updateOffset = function(elId, offset, recalc) { + if (recalc || offset == null) { // if forced repaint or no offset available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + } else { + offsets[elId] = offset; + } + }; + + /** + * wraps one function with another, creating a placeholder for the wrapped function + * if it was null. this is used to wrap the various drag/drop event functions - to allow + * jsPlumb to be notified of important lifecycle events without imposing itself on the user's + * drag/drop functionality. + * TODO: determine whether or not we should support an error handler concept, if one of the functions fails. + */ + var _wrap = function(wrappedFunction, newFunction) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { r = newFunction.apply(this, arguments); } + catch (e) { + _log('jsPlumb function failed : ' + e); + } + try { wrappedFunction.apply(this, arguments); + } + catch (e) { + _log('wrapped function failed : ' + e); + } + return r; + }; + }; + + /* + Class: Anchor + Models a position relative to the origin of some element that an Endpoint can be located. + */ + /* + Function: Anchor + + Constructor for the Anchor class + + Parameters: + params - Anchor parameters. This should contain three values, and may optionally have an 'offsets' argument: + + - x : the x location of the anchor as a fraction of the total width. + - y : the y location of the anchor as a fraction of the total height. + - orientation : an [x,y] array indicating the general direction a connection from the anchor should go in. for more info on this, see the documentation, or the docs in jquery-jsPlumb-defaults-XXX.js for the default Anchors. + - offsets : an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. may be null. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; this.y = params.y || 0; + var orientation = params.orientation || [0,0]; + var lastTimestamp = null; + var lastReturnValue = null; + this.offsets = params.offsets || [0,0]; + this.compute = function(xy, wh, element, txy, twh, tElement) { + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1]]; + var container = element? element.container : null; + var containerAdjustment = {left:0, top:0 }; + if (container != null) { + var eo = _getElementObject(container); + var o = _getOffset(eo); + var sl = jsPlumb.CurrentLibrary.getScrollLeft(eo); + var st = jsPlumb.CurrentLibrary.getScrollTop(eo); + containerAdjustment.left = o.left - sl; + containerAdjustment.top = o.top - st; + lastReturnValue[0] = lastReturnValue[0] - containerAdjustment.left; + lastReturnValue[1] = lastReturnValue[1] - containerAdjustment.top; + } + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y && this.offsets[0] == anchor.offsets[0] && this.offsets[1] == anchor.offsets[1] && o[0] == ao[0] && o[1] == ao[1]; + }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from its position relative to the anchor it is floating relative to. + * + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(_getElementObject(refCanvas)); + + // these are used to store the current relative position of our anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating anchor is hovering over another anchor. + var orientation = null; + + this.compute = function(xy, wh, txy, twh) { + // set these for the getOrientation method to use. + xDir = 0;//xy[0] < txy[0] ? -1 : xy[0] == txy[0] ? 0 : 1; + yDir = 0;//xy[1] < txy[1] ? -1 : xy[1] == txy[1] ? 0 : 1; + return [xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other anchor: if it declares zero for some direction, we declare zero too. + // this might not be the most awesome. perhaps we can come up with a better way. it's just so that the line we draw looks + // like it makes sense. maybe this wont make sense. + return [Math.abs(o[0]) * xDir * -1, Math.abs(o[1]) * yDir * -1]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering over another anchor; + * we want to assume that anchor's orientation for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no longer hovering + * over another anchor; we should resume calculating orientation as we normally do. + */ + this.out = function() { + orientation = null; + }; + }; + + /** + * An Anchor that contains a list of other Anchors, and which it cycles through at compute time and finds the one that is located closest to + * the center of the target element, and returns that Anchor's compute method result. this causes endpoints to follow each other with respect + * to the orientation of their target elements - a useful feature for some applications. + */ + var DynamicAnchor = function(anchors) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = anchors || []; + this.addAnchor = function(anchor) { _anchors.push(anchor); }; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null; + var _curIndex = _anchors.length > 0 ? 0 : -1; + this.locked = false; + var self = this; + + //todo set a default anchor? + + var _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }; + + this.compute = function(xy, wh, element, txy, twh, tElement) { + // if anchor is locked or an opposite element was not given, we maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) { + return _curAnchor.compute(xy, wh, element, txy, twh, tElement); + } + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for (var i = 0; i < _anchors.length; i++) { + var d = _distance(_anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + _curAnchor = _anchors[minIdx]; + var pos = _curAnchor.compute(xy, wh, element, txy, twh, tElement); + return pos; + }; + + this.getOrientation = function() { + return _curAnchor != null ? _curAnchor.getOrientation() : [0,0]; + }; + + this.over = function(anchor) { + if (_curAnchor != null) _curAnchor.over(anchor); + }; + + this.out = function() { + if (_curAnchor != null) _curAnchor.out(); + }; + }; + + // ************** connection + // **************************************** + /** + * allowed params: + * source: source element (string or a jQuery element) (required) + * target: target element (string or a jQuery element) (required) + * + * anchors: optional array of anchor placements. defaults to BottomCenter for source + * and TopCenter for target. + */ + var Connection = function(params) { + + EventGenerator.apply(this); + // ************** get the source and target and register the connection. ******************* + var self = this; + var id = new String('_jsplumb_c_' + (new Date()).getTime()); + this.getId = function() { return id; }; + this.container = params.container || _currentInstance.Defaults.Container; // may be null; we will append to the body if so. + // get source and target as jQuery objects + this.source = _getElementObject(params.source); + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. + if (params.sourceEndpoint) + this.source = params.sourceEndpoint.getElement(); + if (params.targetEndpoint) + this.target = params.targetEndpoint.getElement(); + this.sourceId = _getAttribute(this.source, "id"); + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + this.scope = params.scope; // scope may have been passed in to the connect call. + // if it wasn't, we will pull it from the source endpoint, + // after having initialised the endpoints. + // init endpoints + this.endpoints = []; + this.endpointStyles = []; + var prepareEndpoint = function(existing, index, params, element) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } + else { + if(!params.endpoints) params.endpoints = [null,null]; + var ep = params.endpoints[index] || params.endpoint || _currentInstance.Defaults.Endpoints[index] || jsPlumb.Defaults.Endpoints[index] || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || new jsPlumb.Endpoints.Dot(); + if (!params.endpointStyles) params.endpointStyles = [null,null]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + var a = params.anchors ? params.anchors[index] : _currentInstance.Defaults.Anchors[index] || jsPlumb.Defaults.Anchors[index] || _currentInstance.Defaults.Anchor || jsPlumb.Defaults.Anchor || jsPlumb.Anchors.BottomCenter; + var u = params.uuids ? params.uuids[index] : null; + if(params.uuids) + var e = new Endpoint({style:es, endpoint:ep, connections:[self], uuid:u, anchor:a, source:element, container:self.container }); + else + var e = new Endpoint({style:es, endpoint:ep, connections:[self], anchor:a, source:element, container:self.container }); + + self.endpoints[index] = e; + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || _currentInstance.Defaults.Connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + this.paintStyle = this.endpoints[0].connectorStyle || this.endpoints[1].connectorStyle || params.paintStyle || _currentInstance.Defaults.PaintStyle || jsPlumb.Defaults.PaintStyle; + this.backgroundPaintStyle = this.endpoints[0].connectorBackgroundStyle || this.endpoints[1].connectorBackgroundStyle || params.backgroundPaintStyle || _currentInstance.Defaults.BackgroundPaintStyle || jsPlumb.Defaults.BackgroundPaintStyle; + + // init overlays + this.overlays = params.overlays || []; + this.addOverlay = function(overlay) { + overlays.push(overlay); + }; + + // this is a shortcut helper method to let people add a label as overlay. + this.labelStyle = params.labelStyle || _currentInstance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle;; + this.label = params.label; + if (this.label) { + this.overlays.push(new jsPlumb.Overlays.Label({labelStyle:this.labelStyle, label:this.label})); + }; + + _updateOffset(this.sourceId); + _updateOffset(this.targetId); + + // functions for mouse hover/select functionality + this.distanceFrom = function(point) { return self.connector.distanceFrom(point); }; + + this.setLabel = function(l) { + self.label = l; + _currentInstance.repaint(self.source); + }; + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var otherOffset = offsets[this.targetId]; otherWH = sizes[this.targetId]; + var anchorLoc = this.endpoints[0].anchor.compute([myOffset.left, myOffset.top], myWH, this.endpoints[0], [otherOffset.left, otherOffset.top], otherWH, this.endpoints[1]); + this.endpoints[0].paint(anchorLoc); + + anchorLoc = this.endpoints[1].anchor.compute([otherOffset.left, otherOffset.top], otherWH, this.endpoints[1], [myOffset.left, myOffset.top], myWH, this.endpoints[0]); + this.endpoints[1].paint(anchorLoc); + + // *************** create canvas on which the connection will be drawn ************ + var canvas = _newCanvas(jsPlumb.connectorClass, self.container); + this.canvas = canvas; + + /** + * paints the connection. + * @param elId Id of the element that is in motion + * @param ui current library's event system ui object (present if we came from a drag to get here) + * @param recalc whether or not to recalculate element sizes. this is true if a repaint caused this to be painted. + */ + this.paint = function(elId, ui, recalc, timestamp) { + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = false;//!(elId == this.sourceId); + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + if (this.canvas.getContext) { + _updateOffset(elId, ui, recalc); + _updateOffset(tId); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var myOffset = offsets[sId]; + var otherOffset = offsets[tId]; + var myWH = sizes[sId]; + var otherWH = sizes[tId]; + + var ctx = canvas.getContext('2d'); + //TODO: why are these calculated again? they were calculated in the _draw function. + var sAnchorP = this.endpoints[sIdx].anchor.compute([myOffset.left, myOffset.top], myWH, this.endpoints[sIdx], [otherOffset.left, otherOffset.top], otherWH, this.endpoints[tIdx]); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.compute([otherOffset.left, otherOffset.top], otherWH, this.endpoints[tIdx], [myOffset.left, myOffset.top], myWH, this.endpoints[sIdx]); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + + // paint overlays + var maxSize = 0; + for (var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + var s = o.computeMaxSize(self.connector, ctx); + if (s > maxSize) maxSize = s; + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, this.paintStyle.lineWidth, maxSize); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + + var _paintOneStyle = function(ctx, paintStyle) { + ctx.save(); + jsPlumb.extend(ctx, paintStyle); + if (paintStyle.gradient && !ie) { + var g = self.connector.createGradient(dim, ctx, (elId == this.sourceId)); + for (var i = 0; i < paintStyle.gradient.stops.length; i++) + g.addColorStop(paintStyle.gradient.stops[i][0],paintStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + self.connector.paint(dim, ctx); + ctx.restore(); + }; + + + // first check for the background style + if (this.backgroundPaintStyle != null) { + _paintOneStyle(ctx, this.backgroundPaintStyle); + } + _paintOneStyle(ctx, this.paintStyle); + + // paint overlays + for (var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + o.draw(self.connector, ctx); + } + } + }; + + //TODO: should this take a timestamp? probably. it reduces the amount of time + // spent figuring out anchor locations. + this.repaint = function() { + this.paint(this.sourceId, null, true); + }; + + _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + }; + + /* + Class: Endpoint + Models an endpoint. Can have one to N connections emanating from it (although how to handle that in the UI is + a very good question). also has a Canvas and paint style. + */ + + /* + Function: Endpoint + This is the Endpoint class constructor. + Parameters: + anchor - anchor for the endpoint, of type jsPlumb.Anchor. may be null. + endpoint - endpoint object, of type jsPlumb.Endpoint. may be null. + style - endpoint style, a js object. may be null. + source - element the endpoint is attached to, of type jquery object. Required. + canvas - canvas element to use. may be, and most often is, null. + connections - optional list of connections to configure the endpoint with. + isSource - boolean. indicates the endpoint can act as a source of new connections. optional. + dragOptions - if isSource is set to true, you can supply arguments for the jquery draggable method. optional. + connectionStyle - if isSource is set to true, this is the paint style for connections from this endpoint. optional. + connector - optional connector type to use. + isTarget - boolean. indicates the endpoint can act as a target of new connections. optional. + dropOptions - if isTarget is set to true, you can supply arguments for the jquery droppable method. optional. + reattach - optional boolean that determines whether or not the connections reattach after they + have been dragged off an endpoint and left floating. defaults to false: connections + dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + params = params || {}; + // make a copy. then we can use the wrapper function. + jsPlumb.extend({}, params); + var self = this; + var id = new String('_jsplumb_e_' + (new Date()).getTime()); + this.getId = function() { return id; }; + self.anchor = params.anchor || jsPlumb.Anchors.TopCenter; + //if (self.anchor.isDynamic) self.anchor.endpoint = self; + var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot(); + var _style = params.style || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + this.connectorStyle = params.connectorStyle; + this.connector = params.connector; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + var _element = params.source; + var _uuid = params.uuid; + if (_uuid) endpointsByUUID[_uuid] = self; + this.container = params.container || _currentInstance.Defaults.Container || jsPlumb.Defaults.Container; + var _elementId = _getAttribute(_element, "id"); + var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of. + this.canvas = params.canvas || _newCanvas(jsPlumb.endpointClass, this.container, params.uuid); + this.connections = params.connections || []; + this.scope = params.scope || DEFAULT_SCOPE; + var _reattach = params.reattach || false; + var floatingEndpoint = null; + var inPlaceCopy = null; + this.dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.addConnection = function(connection) { + self.connections.push(connection); + }; + this.detach = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + self.connections.splice(idx, 1); + t.detach(connection); + } + }; + + /** + * detaches all connections this Endpoint has + */ + this.detachAll = function() { + while(self.connections.length > 0) { + self.detach(self.connections[0]); + } + }; + + /** + * removes any connections from this Endpoint that are + * connected to the given target endpoint. + * @param targetEndpoint + */ + this.detachFrom = function(targetEndpoint) { + var c = []; + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for (var i = 0; i < c.length; i++) { + targetEndpoint.detach(c[i]); + self.detach(c[i]); + } + }; + + /** + * returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { return _element; }; + + /** + * returns the UUID for this Endpoint, if there is one. + */ + this.getUuid= function() { return _uuid; }; + + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + var e = new Endpoint({anchor:self.anchor, source:_element, style:_style, endpoint:_endpoint}); + return e; + }; + + + /** + * returns whether or not this endpoint is connected to the given endpoint. + * @param endpoint Endpoint to test. + * @since 1.1.1 + * + * todo: needs testing. will this work if the endpoint passed in is the source? + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + * @returns {Boolean} + */ + this.isFloating = function() { return floatingEndpoint != null; }; + /** + * first pass at default ConnectorSelector: returns the first connection, if we have any. + * modified a little, 5/10/2010: now it only returns a connector if we have not got equal to or more than _maxConnector connectors + * attached. otherwise it is assumed a new connector is ok. but note with this setup we can't select any other connection than the first + * one. what if this could return a list? that implies everything should work with a list - dragging etc. could be nasty. could also + * be cool. + */ + var connectorSelector = function() { + //return self.connections.length == 0 || self.connections.length < _maxConnections ? null : self.connections[0]; + if (self.connections.length < _maxConnections) return null; + //else if (self.connections.length == _maxConnections) return false; + else return self.connections[0]; + }; + + /** + * @returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); }; + + /** + * sets whether or not connnctions can be dragged from this Endpoint once it is full. you would use + * this in a UI in which you're going to provide some other way of breaking connections, if you need + * to break them at all. this property is by default true; use it in conjunction with the 'reattach' + * option on a connect call. + */ + this.setDragAllowedWhenFull = function(allowed) { + self.dragAllowedWhenFull = allowed; + }; + + /** + * a deep equals check. everything must match, including the anchor, styles, everything. + * TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor) && + true; + }; + + /** + * paints the Endpoint, recalculating offset and anchor positions if necessary. + */ + this.paint = function(anchorPoint, connectorPaintStyle, canvas) { + + if (log) log.debug("Painting Endpoint with elementId [" + _elementId + "]; anchorPoint is [" + anchorPoint + "]"); + + if (anchorPoint == null) { + // do we always want to force a repaint here? i dont think so! + var xy = offsets[_elementId]; + var wh = sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset(_elementId); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + anchorPoint = self.anchor.compute([xy.left, xy.top], wh, self); + } + _endpoint.paint(anchorPoint, self.anchor.getOrientation(), canvas || self.canvas, _style, connectorPaintStyle || _style); + }; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + + // is this a connection source? we make it draggable and have the drag listener + // maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + + // first the question is, how many connections are on this endpoint? if it's only one, then excellent. otherwise we will either need a way + // to select one connection from the list, or drag them all. if we had a pluggable 'ConnectorSelector' interface we could probably + // provide a way for people to implement their own UI components to do the connector selection. the question in that particular case would be how much + // information the interface needs from jsPlumb at execution time. if, however, we leave out the connector selection, and drag them all, + // that wouldn't be too hard to organise. perhaps that behaviour would be on a switch for the endpoint, or perhaps the ConnectorSelector + // interface returns a List, with the default implementation just returning everything. i think i like that. + // + // let's say for now that there is just one endpoint, cos we need to get this going before we can consider a list of them anyway. + // the major difference between that case and the case of a new endpoint is actually quite small - it's a question of where the + // Connection comes from. for a new one, we create a new one. obviously. otherwise we just get the jpc from the Endpoint + // (remember we're only assuming one connection right now). so all of the UI stuff we do to create the floating endpoint etc + // will still be valid, but when we stop dragging, we'll have to do something different. if we stop with a valid drop i think it will + // be the same process. but if we stop with an invalid drop we have to reset the Connection to how it was when we got it. + var start = function() { + + jpc = connectorSelector(); + if (self.isFull() && !self.dragAllowedWhenFull) return false; + + _updateOffset(_elementId); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + var nE = _getElementObject(n); + _appendElement(n, self.container); // + // create and assign an id, and initialize the offset. + //TODO can't this be replaced with a _getId call? + var id = "" + new String(new Date().getTime()); + _setAttribute(nE, "id", id); + _updateOffset(id); + // store the id of the dragging div and the source element. the drop function + // will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor({reference:self.anchor, referenceCanvas:self.canvas}); + floatingEndpoint = new Endpoint({ style:{fillStyle:'rgba(0,0,0,0)'}, endpoint:_endpoint, anchor:floatingAnchor, source:nE }); + + if (jpc == null) { + self.anchor.locked = true; + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = new Connection({ + sourceEndpoint:self, + targetEndpoint:floatingEndpoint, + source:_getElementObject(_element), + target:_getElementObject(n), + anchors:[self.anchor, floatingAnchor], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + connector: params.connector + }); + } else { + existingJpc = true; + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; + // probably we should remove the connection? and add it back if the user + // does not drop it somewhere proper. + self.removeConnection(jpc); + if (anchorIdx == 0){ + existingJpcParams = [jpc.source, jpc.sourceId]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + }else { + existingJpcParams = [jpc.target, jpc.targetId]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // todo unregister on stop + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + }; + + var dragOptions = params.dragOptions || { }; + + var defaultOpts = jsPlumb.extend({}, jsPlumb.CurrentLibrary.defaultDragOptions); + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + var startEvent = jsPlumb.CurrentLibrary.dragEvents['start']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + //$(n).offset(_ui); + jsPlumb.CurrentLibrary.setOffset(n, _ui); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements([n, floatingEndpoint.canvas], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + + if (_reattach) { + jpc.floatingAnchorIndex = null; + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } + else { + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeElement(jpc.canvas, self.container); + _removeFromList(connectionsByScope, jpc.scope, jpc); + _fireEvent("jsPlumbConnectionDetached", { + source:jpc.source, + target:jpc.target, + sourceId:jpc.sourceId, + targetId:jpc.targetId, + sourceEndpoint:jpc.endpoints[0], + targetEndpoint:jpc.endpoints[1] + }); + } + } else { + _removeElement(jpc.canvas, self.container); + //if (jpc.endpoints[1]) alert("target set"); + self.removeConnection(jpc); + } + } + self.anchor.locked = false; + jpc = null; + delete floatingEndpoint; + delete inPlaceCopy; + self.paint(); + } + ); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + }; + + // connector target + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend({}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + dropOptions[dropEvent]= _wrap(dropOptions[dropEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable), "dragId"); + var elId = _getAttribute(_getElementObject(draggable),"elId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + var oidx = idx == 0 ? 1 : 0; + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + //todo test that the target is not full. + // remove this jpc from the current endpoint + jpc.endpoints[idx].removeConnection(jpc); + if (jpc.suspendedEndpoint) + jpc.suspendedEndpoint.removeConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + // add the jpc to the other endpoint too. + jpc.endpoints[oidx].addConnection(jpc); + + _addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + jsPlumb.repaint(elId); + _fireEvent("jsPlumbConnection", { + source:jpc.source, + target:jpc.target, + sourceId:jpc.sourceId, + targetId:jpc.targetId, + sourceEndpoint:jpc.endpoints[0], + targetEndpoint:jpc.endpoints[1] + }); + } + // else there must be some cleanup required. + + delete floatingConnections[id]; + }); + + dropOptions[overEvent]= _wrap(dropOptions[overEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + }); + + jsPlumb.CurrentLibrary.initDroppable(_getElementObject(self.canvas), dropOptions); + } + + return self; + }; + + /* + Property: Defaults + These are the default settings for jsPlumb, that is what will be used if you do not supply specific pieces of information + to the various API calls. A convenient way to implement your own look and feel can be to override these defaults by including a script + somewhere after the jsPlumb include, but before you make any calls to jsPlumb, for instance in this example we set the PaintStyle to be + a blue line of 27 pixels: + > jsPlumb.Defaults.PaintStyle = { lineWidth:27, strokeStyle:'blue' } + */ + this.Defaults = { + Anchor : null, + Anchors : [ null, null ], + BackgroundPaintStyle : null , + Connector : null, + Container : null, + DragOptions: { }, + DropOptions: { }, + Endpoint : null, + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : null }, + EndpointStyles : [ null, null ], + LabelStyle : { fillStyle:"rgba(0,0,0,0)", color:"black" }, + MaxConnections : null, + // TODO: should we have OverlayStyle too? + PaintStyle : { lineWidth : 10, strokeStyle : 'red' }, + Scope : "_jsPlumb_DefaultScope" + + }; + + /* + Property: connectorClass + The CSS class to set on Connection canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = '_jsPlumb_connector'; + + /* + Property: endpointClass + The CSS class to set on Endpoint canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = '_jsPlumb_endpoint'; + + /* + Property: Anchors + Default jsPlumb Anchors. These are supplied in the file jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form .jsPlumb-all-x.x.x.js. You can provide your own Anchors by supplying them in a script that is loaded after jsPlumb, for instance: + > jsPlumb.Anchors.MyAnchor = { ....anchor code here. see the documentation. } + */ + this.Anchors = {}; + + /* + Property: Connectors + Default jsPlumb Connectors. These are supplied in the file jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form .jsPlumb-all-x.x.x.js. You can provide your own Connectors by supplying them in a script that is loaded after jsPlumb, for instance: + > jsPlumb.Connectors.MyConnector = { ....connector code here. see the documentation. } + */ + this.Connectors = {}; + + /* + Property: Endpoints + Default jsPlumb Endpoints. These are supplied in the file jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form .jsPlumb-all-x.x.x.js. You can provide your own Endpoints by supplying them in a script that is loaded after jsPlumb, for instance: + > jsPlumb.Endpoints.MyEndpoint = { ....endpoint code here. see the documentation. } + */ + this.Endpoints = {}; + + /* + Property:Overlays + Default jsPlumb Overlays such as Arrows and Labels. These are supplied in the file jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form .jsPlumb-all-x.x.x.js. You can provide your own Overlays by supplying them in a script that is loaded after jsPlumb, for instance: + > jsPlumb.Overlays.MyOverlay = { ....overlay code here. see the documentation. } + */ + this.Overlays = {}; + + /* + Function: addEndpoint + Adds an Endpoint to a given element. + Parameters: + target - Element to add the endpoint to. either an element id, or a selector representing some element. + params - Object containing Endpoint options (more info required) + Returns: + The newly created Endpoint. + See Also: + + */ + this.addEndpoint = function(target, params) { + params = jsPlumb.extend({}, params); + var el = _getElementObject(target); + var id = _getAttribute(el,"id"); + params.source = el; + _updateOffset(id); + var e = new Endpoint(params); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id]; + var myWH = sizes[id]; + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH, e); + e.paint(anchorLoc); + return e; + }; + + /* + Function: addEndpoint + Adds a list of Endpoints to a given element. + Parameters: + target - element to add the endpoint to. either an element id, or a selector representing some element. + endpoints - List of objects containing Endpoint options. one Endpoint is created for each entry in this list. + Returns: + List of newly created Endpoints, one for each entry in the 'endpoints' argument. + See Also: + + */ + this.addEndpoints = function(target, endpoints) { + var results = []; + for (var i = 0; i < endpoints.length; i++) { + results.push(_currentInstance.addEndpoint(target, endpoints[i])); + } + return results; + }; + + /* + Function: animate + Wrapper around supporting library's animate function; injects a call to jsPlumb in the 'step' function (creating it if necessary). + This only supports the two-arg version of the animate call in jQuery - the one that takes an 'options' object as the second arg. + MooTools has only one method - a two arg one. Which is handy. + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the standard jQuery animate call. + options - The 'options' argument you want passed to the standard jQuery animate call. + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el); + var id = _getAttribute(el,"id"); + //TODO this is not agnostic yet. + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() + { + _currentInstance.repaint(id); + }); + + // you know, probably onComplete should repaint too. that will help keep up + // with fast animations. + options[completeFunction] = _wrap(options[completeFunction], function() + { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /** + * clears the cache used to lookup elements by their id. if you remove any elements + * from the DOM you should call this to ensure that if you add them back in jsPlumb does not + * have a stale handle. + */ + this.clearCache = function() { + delete cached; + cached = {}; + }; + + this.autoConnect = function(params/*source, target, anchors, endpoint*/) { + var sources = [], targets = [], _endpoint = params.endpoint || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle, _anchors = anchors || _currentInstance.Defaults.DynamicAnchors || jsPlumb.Defaults.DynamicAnchors; + var _addAll = function(s, t) { + for (var i = 0 ; i < s.length; i++) t.push(s[i]); + }; + var source = params.source, target = params.target, anchors = params.anchors; + if (typeof source == 'string') + sources.push(_getElementObject(source)); + else + _addAll(source, sources); + if (typeof target == 'string') + targets.push(_getElementObject(target)); + else + _addAll(target, targets); + var connectOptions = jsPlumb.extend(params, {source:null,target:null,anchors:null}); + for (var i = 0; i < sources.length; i++) { + for (var j = 0; j < targets.length; j++) { + var e1 = jsPlumb.addEndpoint(sources[i], jsPlumb.extend({ anchor:jsPlumb.makeDynamicAnchor(_anchors) }, _endpoint)); + var e2 = jsPlumb.addEndpoint(targets[j], jsPlumb.extend({ anchor:jsPlumb.makeDynamicAnchor(_anchors) }, _endpoint)); + _currentInstance.connect(jsPlumb.extend(connectOptions, { sourceEndpoint:e1, targetEndpoint:e2 })); + } + } + }; + + /* + Function: connect + Establishes a connection between two elements. + Parameters: + params - Object containing setup for the connection. see documentation. + Returns: + The newly created Connection. + */ + this.connect = function(params) { + var _p = jsPlumb.extend({}, params); + // test for endpoint uuids to connect + if (params.uuids) { + var _resolveByUuid = function(idx) { + var e = _getEndpoint(params.uuids[idx]); + if (!e) throw ("Endpoint with UUID " + params.uuids[idx] + " not found."); + return e; + }; + _p.sourceEndpoint = _resolveByUuid(0); + _p.targetEndpoint = _resolveByUuid(1); + } + + // now ensure that if we do have Endpoints already, they're not full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log("could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log("could not add connection; target endpoint is full"); + return; + } + + var jpc = new Connection(_p); + // add to list of connections (by scope). + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + _fireEvent("jsPlumbConnection", { + source:jpc.source, + target:jpc.target, + sourceId:jpc.sourceId, + targetId:jpc.targetId, + sourceEndpoint:jpc.endpoints[0], + targetEndpoint:jpc.endpoints[1] + }); + // force a paint + _draw(jpc.source); + + return jpc; + }; + + var fireDetachEvent = function(jpc) { + _fireEvent("jsPlumbConnectionDetached", { + source:jpc.source, + target:jpc.target, + sourceId:jpc.sourceId, + targetId:jpc.targetId, + sourceEndpoint:jpc.endpoints[0], + targetEndpoint:jpc.endpoints[1] + }); + }; + + /* + Function: detach + Removes a connection. + Parameters: + sourceId - Id of the first element in the connection. A String. + targetId - iI of the second element in the connection. A String. + Returns: + true if successful, false if not. + */ + this.detach = function(sourceId, targetId) { + if (arguments.length == 2) { + var f = function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + fireDetachEvent(jpc); + + return true; + } + + }; + + // todo: how to cleanup the actual storage? a third arg to _operation? + _operation(sourceId, f); + } + // this is the new version of the method, taking a JS object like the connect method does. + else if (arguments.length == 1) { + var _p = jsPlumb.extend({}, sourceId); // a backwards compatibility hack: sourceId should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1])); + } + else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } + else { + sourceId = _getId(_p.source); + targetId = _getId(_p.target); + var f = function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + fireDetachEvent(jpc); + + return true; + } + + }; + + // todo: how to cleanup the actual storage? a third arg to _operation? + _operation(sourceId, f); + } + + // fire an event + /*_fireEvent("jsPlumbDetach", { + source:jpc.source, + target:jpc.target, + sourceId:jpc.sourceId, + targetId:jpc.targetId, + sourceEndpoint:jpc.endpoints[0], + targetEndpoint:jpc.endpoints[1] + });*/ + /*// force a paint + _draw(jpc.source); + + return jpc;*/ + } + }; + + /* + Function: detachAll + Removes all an element's connections. + Parameters: + el - either the id of the element, or a selector for the element. + Returns: + void + */ + this.detachAll = function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + var c = endpoints[i].connections.length; + if (c > 0) { + for (var j = 0; j < c; j++) { + var jpc = endpoints[i].connections[0]; + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + fireDetachEvent(jpc); + } + } + } + } + }; + + /* + Function: detachEverything + Remove all Connections from all elements, but leaves Endpoints in place. + Returns: + void + See Also: + + */ + this.detachEverything = function() { + for(var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + var c = endpoints[i].connections.length; + if (c > 0) { + for (var j = 0; j < c; j++) { + var jpc = endpoints[i].connections[0]; + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + fireDetachEvent(jpc); + } + } + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + /* + Function: draggable + initialises the draggability of some element or elements. + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for (var i = 0; i < el.length; i++) + { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) + _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + Parameters: + o1 - object to extend + o2 - object to extend o1 with + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + Function: getConnections + Gets all or a subset of connections currently managed by this jsPlumb instance. + Parameters: + options - a JS object that holds options defining what sort of connections you're looking for. Valid values are: + scope this may be a String or a list of Strings. jsPlumb will only return Connections whose scope matches what this option + defines. If you omit it, you will be given Connections of every scope. + source may be a string or a list of strings; constraints results to have only Connections whose source is this object. + target may be a string or a list of strings; constraints results to have only Connections whose target is this object. + + The return value is a dictionary in this format: + + { + 'scope1': [ + {sourceId:'window1', targetId:'window2'}, + {sourceId:'window3', targetId:'window4'}, + {sourceId:'window1', targetId:'window3'} + ], + 'scope2': [ + {sourceId:'window1', targetId:'window3'} + ] + } + + */ + this.getConnections = function(options) { + var r = {}; + options = options || {}; + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') r.push(input); + else r = input; + } + return r; + }; + var scopes = prepareList(options.scope); + var sources = prepareList(options.source); + var targets = prepareList(options.target); + var filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }; + for (var i in connectionsByScope) { + if (filter(scopes, i)) { + r[i] = []; + for (var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + r[i].push({ + sourceId:c.sourceId, + targetId:c.targetId, + source:c.source, + target:c.target, + sourceEndpoint:c.endpoints[0], + targetEndpoint:c.endpoints[1] + }); + } + } + } + return r; + }; + + /* + Function: getDefaultScope + Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a scope to an endpoint + or connection allows you to support different types of connections in the same UI. but if you're only interested in one type of connection, + you don't need to supply a scope. this method will probably be used by very few people; it's good for testing though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + gets an Endpoint by UUID + Parameters: + uuid - the UUID for the Endpoint + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /* + * gets an element's id, creating one if necessary. really only exposed for the lib-specific + * functionality to access; would be better to pass the current instance into the lib-specific + * code (even though this is a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + + /* + Function: hide + Sets an element's connections to be hidden. + Parameters: + el - either the id of the element, or a selector for the element. + Returns: + void + */ + this.hide = function(el) { + _setVisible(el, "none"); + }; + + /* + Function: makeAnchor + Creates an anchor with the given params. + Parameters: + x - the x location of the anchor as a fraction of the total width. + y - the y location of the anchor as a fraction of the total height. + xOrientation - value indicating the general direction a connection from the anchor should go in, in the x direction. + yOrientation - value indicating the general direction a connection from the anchor should go in, in the y direction. + xOffset - a fixed offset that should be applied in the x direction that should be applied after the x position has been figured out. optional. defaults to 0. + yOffset - a fixed offset that should be applied in the y direction that should be applied after the y position has been figured out. optional. defaults to 0. + Returns: + The newly created Anchor. + */ + this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards compatibility if we are given only one value we assume it's a call in the old form. + var params = {}; + if (arguments.length == 1) jsPlumb.extend(params, x); + else { + params = {x:x, y:y}; + if (arguments.length >= 4) { + params.orientation = [arguments[2], arguments[3]]; + } + if (arguments.length == 6) params.offsets = [arguments[4], arguments[5]]; + } + var a = new Anchor(params); + a.clone = function() { return new Anchor(params); }; + return a; + }; + + this.makeDynamicAnchor = function(anchors) { + return new DynamicAnchor(anchors); + }; + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + Parameters: + el - either the id of the element or a selector representing the element. + Returns: + void + See Also: + + */ + this.repaint = function(el) { + + var _processElement = function(el) { + var ele = _getElementObject(el); + _draw(ele); + }; + + // TODO: support a jQuery result object too! + + // support both lists... + if (typeof el =='object') { + for (var i = 0; i < el.length; i++) + _processElement(el[i]); + } // ...and single strings. + else _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + Returns: + void + See Also: + + */ + this.repaintEverything = function() { + for (var elId in endpointsByElement) { + _draw(_getElementObject(elId)); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + Parameters: + el - either an element id, or a selector for an element. + Returns: + void + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"); + // first remove all Connections. + jsPlumb.detachAll(elId); + var ebe = endpointsByElement[elId]; + for (var i in ebe) { + _removeElement(ebe[i].canvas, ebe[i].getElement()); + } + endpointsByElement[elId] = []; + }; + + /* + Function: removeEveryEndpoint + Removes every Endpoint in this instance of jsPlumb. + Returns: + void + See Also: + + */ + this.removeEveryEndpoint = function() { + for(var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + _removeElement(endpoints[i].canvas, endpoints[i].container); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + }; + + /* + Function: removeEndpoint + Removes the given Endpoint from the given element. + Parameters: + el - either an element id, or a selector for an element. + endpoint - Endpoint to remove. this is an Endpoint object, such as would have been returned from a call to addEndpoint. + Returns: + void + See Also: + + */ + this.removeEndpoint = function(el, endpoint) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + if (ebe) { + if(_removeFromList(endpointsByElement, elId, endpoint)) + _removeElement(endpoint.canvas, endpoint.getElement()); + } + }; + + /* + Function:reset + removes all endpoints and connections and clears the element cache. + */ + this.reset = function() { + this.detachEverything(); + this.removeEveryEndpoint(); + this.clearCache(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + Parameters: + value - whether or not to automatically repaint when the window is resized. + Returns: + void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + Function: setDefaultNewCanvasSize + Sets the default size jsPlumb will use for a new canvas (we create a square canvas so one value is all that is required). + This is a hack for IE, because ExplorerCanvas seems to need for a canvas to be larger than what you are going to draw on + it at initialisation time. The default value of this is 1200 pixels, which is quite large, but if for some reason you're + drawing connectors that are bigger, you should adjust this value appropriately. + Parameters: + size - The default size to use. jsPlumb will use a square canvas so you need only supply one value. + Returns: + void + */ + this.setDefaultNewCanvasSize = function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }; + + /* + Function: setDefaultScope + Sets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a scope to an endpoint + or connection allows you to support different types of connections in the same UI. but if you're only interested in one type of connection, + you don't need to supply a scope. this method will probably be used by very few people; it just instructs jsPlumb to use a different key + for the default scope. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + Function: setDraggable + Sets whether or not a given element is draggable, regardless of what any plumb command may request. + Parameters: + el - either the id for the element, or a selector representing the element. + Returns: + void + */ + this.setDraggable = _setDraggable; + + /* + Function: setDraggableByDefault + Sets whether or not elements are draggable by default. Default for this is true. + Parameters: + draggable - value to set + Returns: + void + */ + this.setDraggableByDefault = function(draggable) { + _draggableByDefault = draggable; + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + Function: setRepaintFunction + Sets the function to fire when the window size has changed and a repaint was fired. + Parameters: + f - Function to execute. + Returns: + void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + Function: show + Sets an element's connections to be visible. + Parameters: + el - either the id of the element, or a selector for the element. + Returns: + void + */ + this.show = function(el) { + _setVisible(el, "block"); + }; + + /* + Function: sizeCanvas + Helper to size a canvas. You would typically use this when writing your own Connector or Endpoint implementation. + Parameters: + x - [int] x position for the Canvas origin + y - [int] y position for the Canvas origin + w - [int] width of the canvas + h - [int] height of the canvas + Returns: + void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; canvas.height = h; + canvas.style.width = w + "px"; canvas.width = w; + canvas.style.left = x + "px"; canvas.style.top = y + "px"; + // _currentInstance.CurrentLibrary.setPosition(canvas, x, y); + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards compatibility + */ + this.toggle = _toggleVisible; + + /* + Function: toggleVisible + Toggles visibility of an element's connections. + Parameters: + el - either the element's id, or a selector representing the element. + Returns: + void, but should be updated to return the current state + */ + //TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + Function: toggleDraggable + Toggles draggability (sic) of an element's connections. + Parameters: + el - either the element's id, or a selector representing the element. + Returns: + The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + Function: unload + Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element + Returns: + void + */ + this.unload = function() { + delete endpointsByElement; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + }; + + /* + Function: wrap + Helper method to wrap an existing function with one of your own. This is used by the various + implementations to wrap event callbacks for drag/drop etc; it allows jsPlumb to be + transparent in its handling of these things. If a user supplies their own event callback, + for anything, it will always be called. + Parameters: + */ + this.wrap = _wrap; + this.addListener = _addListener; + + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(); + if (_defaults) + jsPlumb.extend(j.Defaults, _defaults); + return j; + }; +})(); diff --git a/archive/1.2.3/jsPlumb-defaults-1.2.3-RC1.js b/archive/1.2.3/jsPlumb-defaults-1.2.3-RC1.js new file mode 100644 index 000000000..9de8c362b --- /dev/null +++ b/archive/1.2.3/jsPlumb-defaults-1.2.3-RC1.js @@ -0,0 +1,700 @@ +/* +* jsPlumb-defaults-1.2.3-RC1 +* +* This script contains the default Anchors, Endpoints, Connectors and Overlays for jsPlumb. It should be used with jsPlumb 1.1.0 and above; +* prior to version 1.1.0 of jsPlumb the defaults were included inside the main script. +* +* NOTE: for production usage you should use jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, +* in a minified file. +* +* Dual licensed under MIT and GPL2. +*/ + +(function() { + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * getOrientation : function() { return [ox, oy]; } + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + jsPlumb.Anchors.TopCenter = jsPlumb.makeAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors.BottomCenter = jsPlumb.makeAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors.LeftMiddle = jsPlumb.makeAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors.RightMiddle = jsPlumb.makeAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors.Center = jsPlumb.makeAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors.TopRight = jsPlumb.makeAnchor(1, 0, 0,-1); + jsPlumb.Anchors.BottomRight = jsPlumb.makeAnchor(1, 1, 0, 1); + jsPlumb.Anchors.TopLeft = jsPlumb.makeAnchor(0, 0, 0, -1); + jsPlumb.Anchors.BottomLeft = jsPlumb.makeAnchor(0, 1, 0, 1); + jsPlumb.Anchors.AutoDefault = function() { return jsPlumb.makeDynamicAnchor([jsPlumb.Anchors.TopCenter, jsPlumb.Anchors.RightMiddle, jsPlumb.Anchors.BottomCenter, jsPlumb.Anchors.LeftMiddle]); }; + + jsPlumb.Defaults.DynamicAnchors = [jsPlumb.Anchors.TopCenter, jsPlumb.Anchors.RightMiddle, jsPlumb.Anchors.BottomCenter, jsPlumb.Anchors.LeftMiddle]; + + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + var currentPoints = null; + var _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + + _dx = _tx - _sx, _dy = (_ty - _sy); + _m = _dy / _dx, _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + + return currentPoints; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + var xp = _sx + (location * _dx); + var yp = _m == Infinity ? xp + _b : (_m * xp) + _b; + return [xp, yp]; + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { return _m; }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + var orientation = distance > 0 ? 1 : -1; + var y = Math.abs(distance * Math.sin(_theta)); + if (_sy > _ty) y = y * -1; + var x = Math.abs(distance * Math.cos(_theta)); + if (_sx > _tx) x = x * -1; + return [p[0] + (orientation * x), p[1] + (orientation * y)]; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, distance, length) { + var p = self.pointAlongPathFrom(location, distance); + var m = self.gradientAtPoint(p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [[p[0] + x, p[1] + y], [p[0] - x, p[1] - y]]; + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + /** + * This Connector draws a Bezier curve with two control points. + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + jsPlumb.Connectors.Bezier = function(curviness) { + var self = this; + this.majorAnchor = curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _sx, _sy, _canvasX, _canvasY, _w, _h; + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) + { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx); var minx2 = Math.min(_CP[0], _CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(_sx,_tx); var maxx2 = Math.max(_CP[0], _CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty); var miny2 = Math.min(_CP[1], _CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(_sy,_ty); var maxy2 = Math.max(_CP[1], _CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + this.paint = function(d, ctx) { + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + }; + + /** + * returns the distance the given point is from the curve. not enabled for 1.2.3. didnt make the cut. next time. + * + this.distanceFrom = function(point) { + var curve = [ {x:currentPoints[4], y:currentPoints[5]}, + {x:currentPoints[8], y:currentPoints[9]}, + {x:currentPoints[10], y:currentPoints[11]}, + {x:currentPoints[6], y:currentPoints[7]}]; + return (jsPlumb.DistanceFromCurve(point, curve)); + };*/ + + var _quadraticPointOnPath = function(location) { + function B1(t) { return t*t; }; + function B2(t) { return 2*t*(1-t); }; + function B3(t) { return (1-t)*(1-t); }; + var x = _sx*B1(location) + _CP[0]*B2(location) + _CP2[0]*B3(location); + var y = _sy*B1(location) + _CP[1]*B2(location) + _CP2[1]*B3(location); + return [x,y]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + + // from http://13thparallel.com/archive/bezier-curves/ + function B1(t) { return t*t*t }; + function B2(t) { return 3*t*t*(1-t) }; + function B3(t) { return 3*t*(1-t)*(1-t) }; + function B4(t) { return (1-t)*(1-t)*(1-t) }; + + var x = _sx*B1(location) + _CP[0]*B2(location) + _CP2[0]*B3(location) + _tx*B4(location); + var y = _sy*B1(location) + _CP[1]*B2(location) + _CP2[1]*B3(location) + _ty*B4(location); + return [x,y]; + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + var p1 = self.pointOnPath(location); + var p2 = _quadraticPointOnPath(location); + var dy = p2[1] - p1[1], dx = p2[0] - p1[0]; + var rtn = Math.atan(dy / dx) ; + // http://bimixual.org/AnimationLibrary/beziertangents.html + return rtn; + }; + + /** + * finds the point that is 'distance' along the path from 'location'. this method returns both the x,y location of the point and also + * its 'location' (proportion of travel along the path). + */ + var _pointAlongPath = function(location, distance) { + var _dist = function(p1,p2) { return Math.sqrt(Math.pow(p1[0] - p2[0], 2) + Math.pow(p1[1] - p2[1], 2)); }; + var prev = self.pointOnPath(location), tally = 0, curLoc = location, direction = distance > 0 ? 1 : -1, cur = null; + while (tally < Math.abs(distance)) { + curLoc += (0.005 * direction); + cur = self.pointOnPath(curLoc); + tally += _dist(cur, prev); + prev = cur; + } + return {point:cur, location:curLoc}; + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return _pointAlongPath(location, distance).point; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, distance, length) { + var p = _pointAlongPath(location, distance); + var m = self.gradientAtPoint(p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [[p.point[0] + x, p.point[1] + y], [p.point[0] - x, p.point[1] - y]]; + }; + + this.createGradient = function(dim, ctx, swap) { + return (swap) ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + + /** + * Types of endpoint UIs. we supply four - a circle of default radius 10px, a rectangle of + * default size 20x20, an image (with no default), and a Triangle, of default size 15. + * you can supply others of these if you want to - see the documentation for a howto. + */ + + /** + * a round endpoint, with default radius 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + } + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * A Rectangular endpoint, with default size 20x20. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + jsPlumb.Endpoints.Triangle = function(params) { + + params = params || { width:15, height:15 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - width/2; + var y = anchorPoint[1] - height/2; + + jsPlumb.sizeCanvas(canvas, x, y, width, height); + + var ctx = canvas.getContext('2d'); + var offsetX = 0, offsetY = 0, angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = endpointStyle.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' property in the params object.. + */ + jsPlumb.Endpoints.Image = function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + }; + + /** + * An arrow overlay. you can provide: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + */ + jsPlumb.Overlays.Arrow = function(params) { + params = params || {}; + var self = this; + var length = params.length || 20; + var width = params.width || 20; + var fillStyle = params.fillStyle || "black"; + var strokeStyle = params.strokeStyle; + var lineWidth = params.lineWidth || 1; + this.loc = params.location || 0.5; + // how far along the arrow the lines folding back in come to. default is 62.3%. + var foldback = params.foldback || 0.623; + var _getFoldBackPoint = function(connector, loc) { + if (foldback == 0.5) return connector.pointOnPath(loc); + else { + var adj = 0.5 - foldback; // we calculate relative to the center + return connector.pointAlongPathFrom(loc, length * adj); + } + }; + + this.computeMaxSize = function() { return width * 1.5; } + + this.draw = function(connector, ctx) { + // this is the arrow head position + var hxy = connector.pointAlongPathFrom(self.loc, length / 2); + // this is the center of the tail + var txy = connector.pointAlongPathFrom(self.loc, -length / 2), tx = txy[0], ty = txy[1]; + // this is the tail vector + var tail = connector.perpendicularToPathAt(self.loc, -length / 2, width); + // this is the point the tail goes in to + var cxy = _getFoldBackPoint(connector, self.loc); + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(hxy[0], hxy[1]); + ctx.lineTo(tail[0][0], tail[0][1]); + ctx.lineTo(cxy[0], cxy[1]); + ctx.lineTo(tail[1][0], tail[1][1]); + ctx.lineTo(hxy[0], hxy[1]); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + + /** + * a basic arrow. this is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. See Arrow for params. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + /** + * a diamond. like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. See Arrow for params. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40; + var p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + /** + * A Label overlay. Params you can provide: + * + * labelStyle - js object containing style instructions for the label. defaults to jsPlumb.Defaults.LabelStyle. + * label - the label to paint. may be a string or a function that returns a string. nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * borderWidth - width of a border to paint. defaults to zero. + * borderStyle - strokeStyle to use when painting the border, if necessary. + */ + jsPlumb.Overlays.Label = function(params) { + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + var self = this; + var labelWidth = null, labelHeight = null, labelText = null, labelPadding = null; + this.location = params.location || 0.5; + this.computeMaxSize = function(connector, ctx) { + if (labelText) { + ctx.save(); + if (self.labelStyle.font) ctx.font = self.labelStyle.font; + var t = ctx.measureText(labelText).width; + // a fake text height measurement: use the width of upper case M + var h = ctx.measureText("M").width; + labelPadding = self.labelStyle.padding || 0.25; + labelWidth = t + (2 * t * labelPadding); + labelHeight = h + (2 * h * labelPadding); + ctx.restore(); + return Math.max(labelWidth, labelHeight) * 1.5; + } + return 0; + }; + this.draw = function(connector, ctx) { + // we allow label generation by a function here. you get given the Connection object as an argument. + labelText = typeof self.label == 'function' ? self.label(self) : self.label; + if (labelText) { + if (self.labelStyle.font) ctx.font = self.labelStyle.font; + var t = ctx.measureText(labelText).width; + // a fake text height measurement: use the width of upper case M + var h = ctx.measureText("M").width; + labelPadding = self.labelStyle.padding || 0.25; + labelWidth = t + (2 * t * labelPadding); + labelHeight = h + (2 * h * labelPadding); + var cxy = connector.pointOnPath(self.location); + if (self.labelStyle.font) ctx.font = self.labelStyle.font; + if (self.labelStyle.fillStyle) + ctx.fillStyle = self.labelStyle.fillStyle; + else + ctx.fillStyle = "rgba(0,0,0,0)"; + ctx.fillRect(cxy[0] - (labelWidth / 2), cxy[1] - (labelHeight / 2) , labelWidth , labelHeight ); + + if (self.labelStyle.color) ctx.fillStyle = self.labelStyle.color; + ctx.textBaseline = "middle"; + ctx.textAlign = "center"; + ctx.fillText(labelText, cxy[0], cxy[1]); + + // border + if (self.labelStyle.borderWidth > 0) { + ctx.strokeStyle = self.labelStyle.borderStyle || "black"; + ctx.strokeRect(cxy[0] - (labelWidth / 2), cxy[1] - (labelHeight / 2) , labelWidth , labelHeight ); + } + } + }; + }; +})(); \ No newline at end of file diff --git a/archive/1.2.3/mootools.jsPlumb-1.2.3-RC1.js b/archive/1.2.3/mootools.jsPlumb-1.2.3-RC1.js new file mode 100644 index 000000000..c7acf7f28 --- /dev/null +++ b/archive/1.2.3/mootools.jsPlumb-1.2.3-RC1.js @@ -0,0 +1,298 @@ +/* + * mootools.jsPlumb 1.2.3-RC1 + * + * MooTools specific functionality for jsPlumb. + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + * NOTE: for production usage you should use mootools.jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, + * in a minified file. + * + * Dual licensed under MIT and GPL2. + * + */ + +(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function() { + this.parent(); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}; + var _droppableOptions = {}; + var _draggablesByScope = {}; + var _draggablesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }; + + + jsPlumb.CurrentLibrary = { + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' + }, + + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).grab(child); + }, + + bind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.addEvent(event, callback); + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + getElementObject : function(el) { + return $(el); + }, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + setOffset : function(el, o) { + jsPlumb.CurrentLibrary.getElementObject(el).setPosition({x:o.left, y:o.top}); + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + initDraggable : function(el, options) { + var id = jsPlumb.getId(el); + var drag = _draggablesById[id]; + if (!drag) { + var originalZIndex = 0, originalCursor = null; + var dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + options['onStart'] = jsPlumb.wrap(options['onStart'], function() + { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() + { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + }); + + // DROPPABLES: + var scope = options['scope'] || jsPlumb.Defaults.Scope; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop'); + } + }; + + drag = new Drag.Move(el, options); + _add(_draggablesByScope, scope, drag); + _add(_draggablesById, el.get("id"), drag); + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + initDroppable : function(el, options) { + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _add(_droppables, scope, el); + var id = jsPlumb.getId(el); + _droppableOptions[id] = options; + var filterFunc = function(entry) { + return entry.element != el; + }; + var draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + if (typeof Drag != undefined) + return typeof Drag.Move != undefined; + return false; + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + m.start(properties); + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[0]; + return { left: ui.offsetLeft, top: ui.offsetTop }; + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + }, + + + removeElement : function(element, parent) { + + jsPlumb.CurrentLibrary.getElementObject(element).dispose(); // ?? + }, + + getScrollLeft : function(el) { + return null; + }, + + getScrollTop : function(el) { + return null; + } + }; +})(); diff --git a/archive/1.2.3/mootools.jsPlumb-1.2.3-all-min.js b/archive/1.2.3/mootools.jsPlumb-1.2.3-all-min.js new file mode 100644 index 000000000..bceb21ef7 --- /dev/null +++ b/archive/1.2.3/mootools.jsPlumb-1.2.3-all-min.js @@ -0,0 +1 @@ +(function(){var a=function(){var j=this;var S=(/MSIE/.test(navigator.userAgent)&&!window.opera);var m=null;var Y=function(){b.repaintEverything()};var f=true;function X(){if(f){Y()}}var W=null;var ad=function(){var ah={};this.bind=function(aj,ak){var ai=ah[aj];if(!ai){ai=[];ah[aj]=ai}ai.push(ak)};this.fireUpdate=function(aj,ak){if(ah[aj]){for(var ai=0;ai0){var au=aj[0];var ay=au.endpoints[0]==ap?1:0;var al=ay==0?au.sourceId:au.targetId;var at=F[al],av=y[al];var ax=ap.anchor.compute([ar.left,ar.top],ao,ap,[at.left,at.top],av,au.endpoints[ay]);ap.paint(ax)}else{var ax=ap.anchor.compute([ar.left,ar.top],ao,ap);ap.paint(ax)}for(var ak=0;ak=0){delete (ah[ai]);ah.splice(ai,1);return true}}return false};var c=function(ai,ah){var aj=function(ak,al){T[al]=ah;if(b.CurrentLibrary.isDragSupported(ak)){b.CurrentLibrary.setDraggable(ak,ah)}};return q(ai,aj)};var ab=function(ai,ak){var ah=o(ai,"id");var aj=function(al){al.canvas.style.display=ak};D(ah,aj)};var v=function(ai){var ah=function(ak,aj){var al=T[aj]==null?H:T[aj];al=!al;T[aj]=al;b.CurrentLibrary.setDraggable(ak,al);return al};return q(ai,ah)};var e=function(ah){var ai=function(ak){var aj=("none"==ak.canvas.style.display);ak.canvas.style.display=aj?"block":"none"};D(ah,ai)};var k=function(ai,ak,ah){if(ah||ak==null){var aj=V(ai);y[ai]=h(aj);F[ai]=d(aj)}else{F[ai]=ak}};var aa=function(ai,ah){ai=ai||function(){};ah=ah||function(){};return function(){var aj=null;try{aj=ah.apply(this,arguments)}catch(ak){l("jsPlumb function failed : "+ak)}try{ai.apply(this,arguments)}catch(ak){l("wrapped function failed : "+ak)}return aj}};var M=function(al){var aj=this;this.x=al.x||0;this.y=al.y||0;var ai=al.orientation||[0,0];var ak=null;var ah=null;this.offsets=al.offsets||[0,0];this.compute=function(ay,an,av,aq,au,ar){ah=[ay[0]+(aj.x*an[0])+aj.offsets[0],ay[1]+(aj.y*an[1])+aj.offsets[1]];var ao=av?av.container:null;var aw={left:0,top:0};if(ao!=null){var am=V(ao);var ap=d(am);var at=b.CurrentLibrary.getScrollLeft(am);var ax=b.CurrentLibrary.getScrollTop(am);aw.left=ap.left-at;aw.top=ap.top-ax;ah[0]=ah[0]-aw.left;ah[1]=ah[1]-aw.top}return ah};this.getOrientation=function(){return ai};this.equals=function(am){if(!am){return false}var an=am.getOrientation();var ap=this.getOrientation();return this.x==am.x&&this.y==am.y&&this.offsets[0]==am.offsets[0]&&this.offsets[1]==am.offsets[1]&&ap[0]==an[0]&&ap[1]==an[1]}};var p=function(am){var ak=am.reference;var al=am.referenceCanvas;var aj=h(V(al));var ai=0,an=0;var ah=null;this.compute=function(aq,ao,ap,ar){ai=0;an=0;return[aq[0]+(aj[0]/2),aq[1]+(aj[1]/2)]};this.getOrientation=function(){if(ah){return ah}else{var ao=ak.getOrientation();return[Math.abs(ao[0])*ai*-1,Math.abs(ao[1])*an*-1]}};this.over=function(ao){ah=ao.getOrientation()};this.out=function(){ah=null}};var J=function(am){this.isSelective=true;this.isDynamic=true;var ak=am||[];this.addAnchor=function(an){ak.push(an)};var aj=ak.length>0?ak[0]:null;var al=ak.length>0?0:-1;this.locked=false;var ai=this;var ah=function(ap,an,au,at,ao){var ar=at[0]+(ap.x*ao[0]),aq=at[1]+(ap.y*ao[1]);return Math.sqrt(Math.pow(an-ar,2)+Math.pow(au-aq,2))};this.compute=function(aA,an,av,ao,aq,ap){if(ai.locked||ao==null||aq==null){return aj.compute(aA,an,av,ao,aq,ap)}var at=ao[0]+(aq[0]/2),ar=ao[1]+(aq[1]/2);var aw=-1,az=Infinity;for(var au=0;auax){ax=aI}}var aN=this.connector.compute(aA,aP,this.endpoints[aS].anchor,this.endpoints[aw].anchor,this.paintStyle.lineWidth,ax);b.sizeCanvas(ai,aN[0],aN[1],aN[2],aN[3]);var aG=function(aT,aW){aT.save();b.extend(aT,aW);if(aW.gradient&&!S){var aV=ar.connector.createGradient(aN,aT,(aH==this.sourceId));for(var aU=0;aU=0){var aN=aM.endpoints[0]==aw?aM.endpoints[1]:aM.endpoints[0];aw.connections.splice(aL,1);aN.detach(aM)}};this.detachAll=function(){while(aw.connections.length>0){aw.detach(aw.connections[0])}};this.detachFrom=function(aM){var aN=[];for(var aL=0;aL=aD)};this.setDragAllowedWhenFull=function(aL){aw.dragAllowedWhenFull=aL};this.equals=function(aL){return this.anchor.equals(aL.anchor)&&true};this.paint=function(aP,aN,aM){if(m){m.debug("Painting Endpoint with elementId ["+an+"]; anchorPoint is ["+aP+"]")}if(aP==null){var aO=F[an];var aL=y[an];if(aO==null||aL==null){k(an);aO=F[an];aL=y[an]}aP=aw.anchor.compute([aO.left,aO.top],aL,aw)}au.paint(aP,aw.anchor.getOrientation(),aM||aw.canvas,ao,aN||ao)};this.removeConnection=this.detach;if(aJ.isSource&&b.CurrentLibrary.isDragSupported(av)){var aC=null,ay=null,aB=null,ah=false,aj=null;var al=function(){aB=aG();if(aw.isFull()&&!aw.dragAllowedWhenFull){return false}k(an);ak=aw.makeInPlaceCopy();ak.paint();aC=document.createElement("div");var aN=V(aC);A(aC,aw.container);var aO=""+new String(new Date().getTime());R(aN,"id",aO);k(aO);R(V(aw.canvas),"dragId",aO);R(V(aw.canvas),"elId",an);var aM=new p({reference:aw.anchor,referenceCanvas:aw.canvas});aH=new ac({style:{fillStyle:"rgba(0,0,0,0)"},endpoint:au,anchor:aM,source:aN});if(aB==null){aw.anchor.locked=true;aB=new n({sourceEndpoint:aw,targetEndpoint:aH,source:V(av),target:V(aC),anchors:[aw.anchor,aM],paintStyle:aJ.connectorStyle,connector:aJ.connector})}else{ah=true;var aL=aB.sourceId==an?0:1;aB.floatingAnchorIndex=aL;aw.removeConnection(aB);if(aL==0){aj=[aB.source,aB.sourceId];aB.source=V(aC);aB.sourceId=aO}else{aj=[aB.target,aB.targetId];aB.target=V(aC);aB.targetId=aO}aB.endpoints[aL==0?1:0].anchor.locked=true;aB.suspendedEndpoint=aB.endpoints[aL];aB.endpoints[aL]=aH}I[aO]=aB;aH.addConnection(aB);B(U,aO,aH)};var aE=aJ.dragOptions||{};var az=b.extend({},b.CurrentLibrary.defaultDragOptions);aE=b.extend(az,aE);aE.scope=aE.scope||aw.scope;var aA=b.CurrentLibrary.dragEvents.start;var aI=b.CurrentLibrary.dragEvents.stop;var ap=b.CurrentLibrary.dragEvents.drag;aE[aA]=aa(aE[aA],al);aE[ap]=aa(aE[ap],function(){var aL=b.CurrentLibrary.getUIPosition(arguments);b.CurrentLibrary.setOffset(aC,aL);ae(V(aC),aL)});aE[aI]=aa(aE[aI],function(){u(U,ay,aH);z([aC,aH.canvas],av);P(ak.canvas,av);var aL=aB.floatingAnchorIndex==null?1:aB.floatingAnchorIndex;aB.endpoints[aL==0?1:0].anchor.locked=false;if(aB.endpoints[aL]==aH){if(ah&&aB.suspendedEndpoint){if(at){aB.floatingAnchorIndex=null;if(aL==0){aB.source=aj[0];aB.sourceId=aj[1]}else{aB.target=aj[0];aB.targetId=aj[1]}aB.endpoints[aL]=aB.suspendedEndpoint;aB.suspendedEndpoint.addConnection(aB);b.repaint(aj[1])}else{aB.endpoints[0].removeConnection(aB);aB.endpoints[1].removeConnection(aB);P(aB.canvas,aw.container);u(s,aB.scope,aB);K("jsPlumbConnectionDetached",{source:aB.source,target:aB.target,sourceId:aB.sourceId,targetId:aB.targetId,sourceEndpoint:aB.endpoints[0],targetEndpoint:aB.endpoints[1]})}}else{P(aB.canvas,aw.container);aw.removeConnection(aB)}}aw.anchor.locked=false;aB=null;delete aH;delete ak;aw.paint()});var aF=V(aw.canvas);b.CurrentLibrary.initDraggable(aF,aE)}if(aJ.isTarget&&b.CurrentLibrary.isDropSupported(av)){var aK=aJ.dropOptions||j.Defaults.DropOptions||b.Defaults.DropOptions;aK=b.extend({},aK);aK.scope=aK.scope||aw.scope;var ar=null;var ax=b.CurrentLibrary.dragEvents.drop;var am=b.CurrentLibrary.dragEvents.over;var ai=b.CurrentLibrary.dragEvents.out;aK[ax]=aa(aK[ax],function(){var aM=b.CurrentLibrary.getDragObject(arguments);var aQ=o(V(aM),"dragId");var aN=o(V(aM),"elId");var aP=I[aQ];var aL=aP.floatingAnchorIndex==null?1:aP.floatingAnchorIndex;var aO=aL==0?1:0;if(!aw.isFull()&&!(aL==0&&!aw.isSource)&&!(aL==1&&!aw.isTarget)){if(aL==0){aP.source=av;aP.sourceId=an}else{aP.target=av;aP.targetId=an}aP.endpoints[aL].removeConnection(aP);if(aP.suspendedEndpoint){aP.suspendedEndpoint.removeConnection(aP)}aP.endpoints[aL]=aw;aw.addConnection(aP);aP.endpoints[aO].addConnection(aP);B(s,aP.scope,aP);x(av,aJ.draggable,{});b.repaint(aN);K("jsPlumbConnection",{source:aP.source,target:aP.target,sourceId:aP.sourceId,targetId:aP.targetId,sourceEndpoint:aP.endpoints[0],targetEndpoint:aP.endpoints[1]})}delete I[aQ]});aK[am]=aa(aK[am],function(){var aM=b.CurrentLibrary.getDragObject(arguments);var aO=o(V(aM),"dragId");var aN=I[aO];var aL=aN.floatingAnchorIndex==null?1:aN.floatingAnchorIndex;aN.endpoints[aL].anchor.over(aw.anchor)});aK[ai]=aa(aK[ai],function(){var aM=b.CurrentLibrary.getDragObject(arguments);var aO=o(V(aM),"dragId");var aN=I[aO];var aL=aN.floatingAnchorIndex==null?1:aN.floatingAnchorIndex;aN.endpoints[aL].anchor.out()});b.CurrentLibrary.initDroppable(V(aw.canvas),aK)}return aw};this.Defaults={Anchor:null,Anchors:[null,null],BackgroundPaintStyle:null,Connector:null,Container:null,DragOptions:{},DropOptions:{},Endpoint:null,Endpoints:[null,null],EndpointStyle:{fillStyle:null},EndpointStyles:[null,null],LabelStyle:{fillStyle:"rgba(0,0,0,0)",color:"black"},MaxConnections:null,PaintStyle:{lineWidth:10,strokeStyle:"red"},Scope:"_jsPlumb_DefaultScope"};this.connectorClass="_jsPlumb_connector";this.endpointClass="_jsPlumb_endpoint";this.Anchors={};this.Connectors={};this.Endpoints={};this.Overlays={};this.addEndpoint=function(am,an){an=b.extend({},an);var ak=V(am);var ao=o(ak,"id");an.source=ak;k(ao);var al=new ac(an);B(U,ao,al);var ah=F[ao];var aj=y[ao];var ai=al.anchor.compute([ah.left,ah.top],aj,al);al.paint(ai);return al};this.addEndpoints=function(ak,ah){var aj=[];for(var ai=0;ai0){for(var ai=0;ai0){for(var ai=0;ai0?Q(at,ar)!=-1:true};for(var al in s){if(aj(ao,al)){ah[al]=[];for(var ak=0;ak=4){al.orientation=[arguments[2],arguments[3]]}if(arguments.length==6){al.offsets=[arguments[4],arguments[5]]}}var ai=new M(al);ai.clone=function(){return new M(al)};return ai};this.makeDynamicAnchor=function(ah){return new J(ah)};this.repaint=function(ai){var aj=function(ak){var al=V(ak);ae(al)};if(typeof ai=="object"){for(var ah=0;ah0?1:-1;var s=Math.abs(t*Math.sin(d));if(e>a){s=s*-1}var n=Math.abs(t*Math.cos(d));if(f>b){n=n*-1}return[r[0]+(q*n),r[1]+(q*s)]};this.perpendicularToPathAt=function(r,v,s){var t=m.pointAlongPathFrom(r,v);var q=m.gradientAtPoint(t.location);var o=Math.atan(-1/q);var u=s/2*Math.sin(o);var n=s/2*Math.cos(o);return[[t[0]+n,t[1]+u],[t[0]-n,t[1]-u]]};this.createGradient=function(o,n){return n.createLinearGradient(o[4],o[5],o[6],o[7])}};jsPlumb.Connectors.Bezier=function(a){var n=this;this.majorAnchor=a||150;this.minorAnchor=10;var g=null;this._findControlPoint=function(y,o,t,w,q){var v=w.getOrientation(),x=q.getOrientation();var s=v[0]!=x[0]||v[1]==x[1];var r=[];var z=n.majorAnchor,u=n.minorAnchor;if(!s){if(v[0]==0){r.push(o[0]j){j=t}if(w<0){d+=w;var x=Math.abs(w);j+=x;m[0]+=x;h+=x;b+=x;k[0]+=x}var F=Math.min(e,_ty);var D=Math.min(m[1],k[1]);var s=Math.min(F,D);var y=Math.max(e,_ty);var v=Math.max(m[1],k[1]);var q=Math.max(y,v);if(q>f){f=q}if(s<0){c+=s;var u=Math.abs(s);f+=u;m[1]+=u;e+=u;_ty+=u;k[1]+=u}if(C&&j0?1:-1,u=null;while(o0){g.strokeStyle=b.labelStyle.borderStyle||"black";g.strokeRect(l[0]-(a/2),l[1]-(e/2),a,e)}}}}})();(function(){var b=new Class({Extends:Fx.Morph,onStep:null,initialize:function(j,i){this.parent(j,i);if(i.onStep){this.onStep=i.onStep}},step:function(){this.parent();if(this.onStep){try{this.onStep()}catch(i){}}}});var f={};var c={};var h={};var d={};var g=function(j,l,k){if(l){var m=l.get("id");if(m){var i=c[m];if(i){if(i[k]){i[k](j,l)}}}}};var a=function(j,l){if(j){var k=j.get("id");if(k){var i=c[k];if(i){if(i.hoverClass){if(l){j.addClass(i.hoverClass)}else{j.removeClass(i.hoverClass)}}}}}};var e=function(m,j,k){var i=m[j];if(!i){i=[];m[j]=i}i.push(k)};jsPlumb.CurrentLibrary={dragEvents:{start:"onStart",stop:"onComplete",drag:"onDrag",step:"onStep",over:"onEnter",out:"onLeave",drop:"onDrop",complete:"onComplete"},appendElement:function(j,i){jsPlumb.CurrentLibrary.getElementObject(i).grab(j)},bind:function(i,j,k){i=jsPlumb.CurrentLibrary.getElementObject(i);i.addEvent(j,k)},extend:function(j,i){return $extend(j,i)},getElementObject:function(i){return $(i)},getOffset:function(i){var j=i.getPosition();return{left:j.x,top:j.y}},setOffset:function(i,j){jsPlumb.CurrentLibrary.getElementObject(i).setPosition({x:j.left,y:j.top})},getSize:function(j){var i=j.getSize();return[i.x,i.y]},getAttribute:function(i,j){return i.get(j)},setAttribute:function(j,k,i){j.set(k,i)},addClass:function(j,i){j.addClass(i)},initDraggable:function(j,r){var i=jsPlumb.getId(j);var m=d[i];if(!m){var k=0,l=null;var o=jsPlumb.Defaults.DragOptions.zIndex||2000;r.onStart=jsPlumb.wrap(r.onStart,function(){k=this.element.getStyle("z-index");this.element.setStyle("z-index",o);if(jsPlumb.Defaults.DragOptions.cursor){l=this.element.getStyle("cursor");this.element.setStyle("cursor",jsPlumb.Defaults.DragOptions.cursor)}});r.onComplete=jsPlumb.wrap(r.onComplete,function(){this.element.setStyle("z-index",k);if(l){this.element.setStyle("cursor",l)}});var q=r.scope||jsPlumb.Defaults.Scope;var n=function(s){return s.get("id")!=j.get("id")};var p=f[q]?f[q].filter(n):[];r.droppables=p;r.onLeave=jsPlumb.wrap(r.onLeave,function(s,t){if(t){a(t,false);g(s,t,"onLeave")}});r.onEnter=jsPlumb.wrap(r.onEnter,function(s,t){if(t){a(t,true);g(s,t,"onEnter")}});r.onDrop=function(s,t){if(t){a(t,false);g(s,t,"onDrop")}};m=new Drag.Move(j,r);e(h,q,m);e(d,j.get("id"),m);if(r.disabled){m.detach()}}return m},isDragSupported:function(j,i){return typeof Drag!="undefined"},setDraggable:function(k,j){var i=d[k.get("id")];if(i){i.each(function(l){if(j){l.attach()}else{l.detach()}})}},initDroppable:function(n,k){var m=k.scope||jsPlumb.Defaults.Scope;e(f,m,n);var p=jsPlumb.getId(n);c[p]=k;var o=function(i){return i.element!=n};var j=h[m]?h[m].filter(o):[];for(var l=0;l endpoint lists. an element can have an arbitrary number of endpoints on it, + * and not all of them have to be connected to anything. + */ + var endpointsByElement = {}; + var endpointsByUUID = {}; + var connectionsByScope = {}; + var offsets = []; + var floatingConnections = {}; + var draggableStates = {}; + var _draggableByDefault = true; + var sizes = []; + var listeners = {}; // a map: keys are event types, values are lists of listeners. + var DEFAULT_SCOPE = 'DEFAULT'; + + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + var _findIndex = function( a, v, b, s ) { + var _eq = function(o1, o2) { + if (o1 === o2) return true; + else if (typeof o1 == 'object' && typeof o2 == 'object') { + var same = true; + for(var propertyName in o1) { + if(!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for(var propertyName in o2) { + if(!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for( var i = +b || 0, l = a.length; i < l; i++ ) { + if( _eq(a[i], v)) return i; + } + return -1; + }; + + /** + * adds a listener for the specified event type. + */ + var _addListener = function(eventType, listener) { + var doOne = function(e, l) { + _addToList(listeners, e, l); + }; + if (typeof eventType == 'object' && eventType.length) { + for (var i = 0; i < eventType.length; i++) doOne(eventType[i], listener); + } else doOne(eventType, listener); + }; + + /** + * helper method to add an item to a list, creating the list if it does not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }; + + var _appendElement = function(canvas, parent) { + if (!parent) document.body.appendChild(canvas); + else jsPlumb.CurrentLibrary.appendElement(canvas, parent); + }; + + /** + * Handles the dragging of an element. + * @param element jQuery element + * @param ui UI object from current library's event system + */ + var _draw = function(element, ui) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints) { + var timestamp = '' + (new Date().getTime()); + _updateOffset(id, ui); + var myOffset = offsets[id]; + var myWH = sizes[id]; + // loop through endpoints for this element + for (var i = 0; i < endpoints.length; i++) { + var e = endpoints[i]; + var l = e.connections; + // here we have a little quandary. if an endpoint is connected to some other element, and it + // has a selective anchor, then we want to know that element's position when we find our + // anchor. if it has no connections then fine, we use the default anchor in the list we can + // select from. but if it has more than one connection, what then? we need to compute where + // it is best placed relative to all other elements, and we then want to lock that position + // for the rest of this paint cycle. if we do not do that, it's possible that an endpoint will + // be moved by an ensuing connector's paint. + if (e.anchor.isSelective && l.length > 0) { + var c = l[0]; + var oIdx = c.endpoints[0] == e ? 1 : 0; + var oId = oIdx == 0 ? c.sourceId : c.targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH, e, [oOffset.left, oOffset.top], oWH, c.endpoints[oIdx]); + e.paint(anchorLoc); + } + else { + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH, e); + e.paint(anchorLoc); + } + for (var j = 0; j < l.length; j++) { + l[j].paint(id, ui, false, timestamp); // ...and paint them. + var oIdx = l[j].endpoints[0] == e ? 1 : 0; + // if the other endpoint has a selective anchor then it might need to be repainted, because it may have just moved + // position. it is possible we could cache the knowledge about that and not run this paint code all the time. + if (l[j].endpoints[oIdx].anchor.isSelective) { + var oId = oIdx == 0 ? l[j].sourceId : l[j].targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + var anchorLoc = l[j].endpoints[oIdx].anchor.compute([oOffset.left, oOffset.top], oWH, l[j].endpoints[oIdx], [myOffset.left, myOffset.top], myWH, e); + l[j].endpoints[oIdx].paint(anchorLoc); + } + } + } + } + }; + + /** + * helper function: the second argument is a function taking two args - the first is an id + * ui element from the current library (or list of them), and the second is the function + * to run. + */ + var _elementProxy = function(element, fn) { + var retVal = null; + if (typeof element == 'object' && element.length) { + retVal = []; + for (var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]); + var id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } + else { + var el = _getElementObject(element); + var id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + + return retVal; + }; + + /** + * fires an event of the given type. + */ + var _fireEvent = function(eventType, data) { + var l = listeners[eventType]; + if (l) { + for (var i in l) { + try { + l[i][eventType](data); + } + catch (e) { + + _log("while firing event [" + eventType + "]; listener failed like this: " + e); + } + } + } + }; + + var _log = function(msg) { + //if (console) console.log(msg); + }; + + var _logFnCall = function(fn, args) { + /*if (console) { + var c = args.callee.caller.toString(); + var i = c.indexOf("{"); + var msg = fn + ' : ' + c.substring(0, i); + console.log(msg); + }*/ + }; + + var cached = {}; + var __getElementObject = function(el) { + + if (typeof(el)=='string') { + var e = cached[el]; + if (!e) { + e = jsPlumb.CurrentLibrary.getElementObject(el); + cached[el] = e; + } + return e; + } + else { + return jsPlumb.CurrentLibrary.getElementObject(el); + } + }; + + + /** + * gets the named attribute from the given element (id or element object) + */ + var _getAttribute = function(el, attName) { + var ele = __getElementObject(el); + return jsPlumb.CurrentLibrary.getAttribute(ele, attName); + }; + + var _setAttribute = function(el, attName, attValue) { + var ele = __getElementObject(el); + jsPlumb.CurrentLibrary.setAttribute(ele, attName, attValue); + }; + + var _addClass = function(el, clazz) { + var ele = __getElementObject(el); + jsPlumb.CurrentLibrary.addClass(ele, clazz); + }; + + var _getElementObject = function(elId) { + return __getElementObject(elId); + }; + + var _getOffset = function(el) { + var ele = __getElementObject(el); + return jsPlumb.CurrentLibrary.getOffset(ele); + }; + + var _getSize = function(el) { + var ele = __getElementObject(el); + return jsPlumb.CurrentLibrary.getSize(ele); + }; + + /** + * gets an id for the given element, creating and setting one if necessary. + */ + var _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id) { + //check if fixed uuid parameter is given + if(arguments.length == 2) + id = uuid; + else + id = "_jsPlumb_" + new String((new Date()).getTime()); + _setAttribute(ele, "id", id); + } + return id; + }; + + /** + * gets an Endpoint by uuid. + */ + var _getEndpoint = function(uuid) { + return endpointsByUUID[uuid]; + }; + + /** + * inits a draggable if it's not already initialised. + */ + var _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + // dragging + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend({}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + } + } + }; + + + /** + * helper to create a canvas. + * @param clazz optional class name for the canvas. + */ + var _newCanvas = function(clazz, parent, uuid) { + var canvas = document.createElement("canvas"); + _appendElement(canvas, parent); + canvas.style.position="absolute"; + if (clazz) { canvas.className=clazz; } + + // set an id. if no id on the element and if uuid was supplied it will be used, otherwise we'll create one. + _getId(canvas, uuid); + + if (ie) { + // for IE we have to set a big canvas size. actually you can override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + /** + * performs the given function operation on all the connections found for the given element + * id; this means we find all the endpoints for the given element, and then for each endpoint + * find the connectors connected to it. then we pass each connection in to the given + * function. + */ + var _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + for (var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }; + /** + * perform an operation on all elements. + */ + var _operationOnAll = function(func) { + for (var elId in endpointsByElement) { + _operation(elId, func); + } + }; + /** + * helper to remove an element from the DOM. + */ + var _removeElement = function(element, parent) { + if (element != null) { + if (!parent) { + try { document.body.removeChild(element); } + catch (e) { } + } else { + jsPlumb.CurrentLibrary.removeElement(element, parent); + } + } + }; + /** + * helper to remove a list of elements from the DOM. + */ + var _removeElements = function(elements, parent) { + for (var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }; + /** + * helper method to remove an item from a list. + */ + var _removeFromList = function(map, key, value) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete( l[i] ); + l.splice( i, 1 ); + return true; + } + } + return false; + }; + /** + * Sets whether or not the given element(s) should be draggable, regardless of what a particular + * plumb command may request. + * + * @param element May be a string, a jQuery elements, or a list of strings/jquery elements. + * @param draggable Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + var _helper = function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }; + + return _elementProxy(element, _helper); + }; + /** + * private method to do the business of hiding/showing. + * @param el either Id of the element in question or a jquery object for the element. + * @param state String specifying a value for the css 'display' property ('block' or 'none'). + */ + var _setVisible = function(el, state) { + var elId = _getAttribute(el, "id"); + var f = function(jpc) { + jpc.canvas.style.display = state; + }; + _operation(elId, f); + }; + /** + * toggles the draggable state of the given element(s). + * @param el either an id, or a jquery object, or a list of ids/jquery objects. + */ + var _toggleDraggable = function(el) { + var fn = function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }; + return _elementProxy(el, fn); + }; + /** + * private method to do the business of toggling hiding/showing. + * @param elId Id of the element in question + */ + var _toggleVisible = function(elId) { + var f = function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }; + + _operation(elId, f); + + //todo this should call _elementProxy, and pass in the _operation(elId, f) call as a function. cos _toggleDraggable does that. + }; + /** + * updates the offset and size for a given element, and stores the values. + * if 'offset' is not null we use that (it would have been passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we use the offset, outerWidth and outerHeight methods to get + * the current values. + */ + var _updateOffset = function(elId, offset, recalc) { + if (recalc || offset == null) { // if forced repaint or no offset available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + } else { + offsets[elId] = offset; + } + }; + + /** + * wraps one function with another, creating a placeholder for the wrapped function + * if it was null. this is used to wrap the various drag/drop event functions - to allow + * jsPlumb to be notified of important lifecycle events without imposing itself on the user's + * drag/drop functionality. + * TODO: determine whether or not we should support an error handler concept, if one of the functions fails. + */ + var _wrap = function(wrappedFunction, newFunction) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { r = newFunction.apply(this, arguments); } + catch (e) { + _log('jsPlumb function failed : ' + e); + } + try { wrappedFunction.apply(this, arguments); + } + catch (e) { + _log('wrapped function failed : ' + e); + } + return r; + }; + }; + + /* + Class: Anchor + Models a position relative to the origin of some element that an Endpoint can be located. + */ + /* + Function: Anchor + + Constructor for the Anchor class + + Parameters: + params - Anchor parameters. This should contain three values, and may optionally have an 'offsets' argument: + + - x : the x location of the anchor as a fraction of the total width. + - y : the y location of the anchor as a fraction of the total height. + - orientation : an [x,y] array indicating the general direction a connection from the anchor should go in. for more info on this, see the documentation, or the docs in jquery-jsPlumb-defaults-XXX.js for the default Anchors. + - offsets : an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. may be null. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; this.y = params.y || 0; + var orientation = params.orientation || [0,0]; + var lastTimestamp = null; + var lastReturnValue = null; + this.offsets = params.offsets || [0,0]; + this.compute = function(xy, wh, element, txy, twh, tElement) { + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1]]; + var container = element? element.container : null; + var containerAdjustment = {left:0, top:0 }; + if (container != null) { + var eo = _getElementObject(container); + var o = _getOffset(eo); + var sl = jsPlumb.CurrentLibrary.getScrollLeft(eo); + var st = jsPlumb.CurrentLibrary.getScrollTop(eo); + containerAdjustment.left = o.left - sl; + containerAdjustment.top = o.top - st; + lastReturnValue[0] = lastReturnValue[0] - containerAdjustment.left; + lastReturnValue[1] = lastReturnValue[1] - containerAdjustment.top; + } + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y && this.offsets[0] == anchor.offsets[0] && this.offsets[1] == anchor.offsets[1] && o[0] == ao[0] && o[1] == ao[1]; + }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from its position relative to the anchor it is floating relative to. + * + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(_getElementObject(refCanvas)); + + // these are used to store the current relative position of our anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating anchor is hovering over another anchor. + var orientation = null; + + this.compute = function(xy, wh, txy, twh) { + // set these for the getOrientation method to use. + xDir = 0;//xy[0] < txy[0] ? -1 : xy[0] == txy[0] ? 0 : 1; + yDir = 0;//xy[1] < txy[1] ? -1 : xy[1] == txy[1] ? 0 : 1; + return [xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other anchor: if it declares zero for some direction, we declare zero too. + // this might not be the most awesome. perhaps we can come up with a better way. it's just so that the line we draw looks + // like it makes sense. maybe this wont make sense. + return [Math.abs(o[0]) * xDir * -1, Math.abs(o[1]) * yDir * -1]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering over another anchor; + * we want to assume that anchor's orientation for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no longer hovering + * over another anchor; we should resume calculating orientation as we normally do. + */ + this.out = function() { + orientation = null; + }; + }; + + /** + * An Anchor that contains a list of other Anchors, and which it cycles through at compute time and finds the one that is located closest to + * the center of the target element, and returns that Anchor's compute method result. this causes endpoints to follow each other with respect + * to the orientation of their target elements - a useful feature for some applications. + */ + var DynamicAnchor = function(anchors) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = anchors || []; + this.addAnchor = function(anchor) { _anchors.push(anchor); }; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null; + var _curIndex = _anchors.length > 0 ? 0 : -1; + this.locked = false; + var self = this; + + //todo set a default anchor? + + var _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }; + + this.compute = function(xy, wh, element, txy, twh, tElement) { + // if anchor is locked or an opposite element was not given, we maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) { + return _curAnchor.compute(xy, wh, element, txy, twh, tElement); + } + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for (var i = 0; i < _anchors.length; i++) { + var d = _distance(_anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + _curAnchor = _anchors[minIdx]; + var pos = _curAnchor.compute(xy, wh, element, txy, twh, tElement); + return pos; + }; + + this.getOrientation = function() { + return _curAnchor != null ? _curAnchor.getOrientation() : [0,0]; + }; + + this.over = function(anchor) { + if (_curAnchor != null) _curAnchor.over(anchor); + }; + + this.out = function() { + if (_curAnchor != null) _curAnchor.out(); + }; + }; + + // ************** connection + // **************************************** + /** + * allowed params: + * source: source element (string or a jQuery element) (required) + * target: target element (string or a jQuery element) (required) + * + * anchors: optional array of anchor placements. defaults to BottomCenter for source + * and TopCenter for target. + */ + var Connection = function(params) { + + EventGenerator.apply(this); + // ************** get the source and target and register the connection. ******************* + var self = this; + var id = new String('_jsplumb_c_' + (new Date()).getTime()); + this.getId = function() { return id; }; + this.container = params.container || _currentInstance.Defaults.Container; // may be null; we will append to the body if so. + // get source and target as jQuery objects + this.source = _getElementObject(params.source); + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. + if (params.sourceEndpoint) + this.source = params.sourceEndpoint.getElement(); + if (params.targetEndpoint) + this.target = params.targetEndpoint.getElement(); + this.sourceId = _getAttribute(this.source, "id"); + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + this.scope = params.scope; // scope may have been passed in to the connect call. + // if it wasn't, we will pull it from the source endpoint, + // after having initialised the endpoints. + // init endpoints + this.endpoints = []; + this.endpointStyles = []; + var prepareEndpoint = function(existing, index, params, element) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } + else { + if(!params.endpoints) params.endpoints = [null,null]; + var ep = params.endpoints[index] || params.endpoint || _currentInstance.Defaults.Endpoints[index] || jsPlumb.Defaults.Endpoints[index] || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || new jsPlumb.Endpoints.Dot(); + if (!params.endpointStyles) params.endpointStyles = [null,null]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + var a = params.anchors ? params.anchors[index] : _currentInstance.Defaults.Anchors[index] || jsPlumb.Defaults.Anchors[index] || _currentInstance.Defaults.Anchor || jsPlumb.Defaults.Anchor || jsPlumb.Anchors.BottomCenter; + var u = params.uuids ? params.uuids[index] : null; + if(params.uuids) + var e = new Endpoint({style:es, endpoint:ep, connections:[self], uuid:u, anchor:a, source:element, container:self.container }); + else + var e = new Endpoint({style:es, endpoint:ep, connections:[self], anchor:a, source:element, container:self.container }); + + self.endpoints[index] = e; + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || _currentInstance.Defaults.Connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + this.paintStyle = this.endpoints[0].connectorStyle || this.endpoints[1].connectorStyle || params.paintStyle || _currentInstance.Defaults.PaintStyle || jsPlumb.Defaults.PaintStyle; + this.backgroundPaintStyle = this.endpoints[0].connectorBackgroundStyle || this.endpoints[1].connectorBackgroundStyle || params.backgroundPaintStyle || _currentInstance.Defaults.BackgroundPaintStyle || jsPlumb.Defaults.BackgroundPaintStyle; + + // init overlays + this.overlays = params.overlays || []; + this.addOverlay = function(overlay) { + overlays.push(overlay); + }; + + // this is a shortcut helper method to let people add a label as overlay. + this.labelStyle = params.labelStyle || _currentInstance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle;; + this.label = params.label; + if (this.label) { + this.overlays.push(new jsPlumb.Overlays.Label({labelStyle:this.labelStyle, label:this.label})); + }; + + _updateOffset(this.sourceId); + _updateOffset(this.targetId); + + // functions for mouse hover/select functionality + this.distanceFrom = function(point) { return self.connector.distanceFrom(point); }; + + this.setLabel = function(l) { + self.label = l; + _currentInstance.repaint(self.source); + }; + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var otherOffset = offsets[this.targetId]; otherWH = sizes[this.targetId]; + var anchorLoc = this.endpoints[0].anchor.compute([myOffset.left, myOffset.top], myWH, this.endpoints[0], [otherOffset.left, otherOffset.top], otherWH, this.endpoints[1]); + this.endpoints[0].paint(anchorLoc); + + anchorLoc = this.endpoints[1].anchor.compute([otherOffset.left, otherOffset.top], otherWH, this.endpoints[1], [myOffset.left, myOffset.top], myWH, this.endpoints[0]); + this.endpoints[1].paint(anchorLoc); + + // *************** create canvas on which the connection will be drawn ************ + var canvas = _newCanvas(jsPlumb.connectorClass, self.container); + this.canvas = canvas; + + /** + * paints the connection. + * @param elId Id of the element that is in motion + * @param ui current library's event system ui object (present if we came from a drag to get here) + * @param recalc whether or not to recalculate element sizes. this is true if a repaint caused this to be painted. + */ + this.paint = function(elId, ui, recalc, timestamp) { + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = false;//!(elId == this.sourceId); + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + if (this.canvas.getContext) { + _updateOffset(elId, ui, recalc); + _updateOffset(tId); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var myOffset = offsets[sId]; + var otherOffset = offsets[tId]; + var myWH = sizes[sId]; + var otherWH = sizes[tId]; + + var ctx = canvas.getContext('2d'); + //TODO: why are these calculated again? they were calculated in the _draw function. + var sAnchorP = this.endpoints[sIdx].anchor.compute([myOffset.left, myOffset.top], myWH, this.endpoints[sIdx], [otherOffset.left, otherOffset.top], otherWH, this.endpoints[tIdx]); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.compute([otherOffset.left, otherOffset.top], otherWH, this.endpoints[tIdx], [myOffset.left, myOffset.top], myWH, this.endpoints[sIdx]); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + + // paint overlays + var maxSize = 0; + for (var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + var s = o.computeMaxSize(self.connector, ctx); + if (s > maxSize) maxSize = s; + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, this.paintStyle.lineWidth, maxSize); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + + var _paintOneStyle = function(ctx, paintStyle) { + ctx.save(); + jsPlumb.extend(ctx, paintStyle); + if (paintStyle.gradient && !ie) { + var g = self.connector.createGradient(dim, ctx, (elId == this.sourceId)); + for (var i = 0; i < paintStyle.gradient.stops.length; i++) + g.addColorStop(paintStyle.gradient.stops[i][0],paintStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + self.connector.paint(dim, ctx); + ctx.restore(); + }; + + + // first check for the background style + if (this.backgroundPaintStyle != null) { + _paintOneStyle(ctx, this.backgroundPaintStyle); + } + _paintOneStyle(ctx, this.paintStyle); + + // paint overlays + for (var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + o.draw(self.connector, ctx); + } + } + }; + + //TODO: should this take a timestamp? probably. it reduces the amount of time + // spent figuring out anchor locations. + this.repaint = function() { + this.paint(this.sourceId, null, true); + }; + + _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + }; + + /* + Class: Endpoint + Models an endpoint. Can have one to N connections emanating from it (although how to handle that in the UI is + a very good question). also has a Canvas and paint style. + */ + + /* + Function: Endpoint + This is the Endpoint class constructor. + Parameters: + anchor - anchor for the endpoint, of type jsPlumb.Anchor. may be null. + endpoint - endpoint object, of type jsPlumb.Endpoint. may be null. + style - endpoint style, a js object. may be null. + source - element the endpoint is attached to, of type jquery object. Required. + canvas - canvas element to use. may be, and most often is, null. + connections - optional list of connections to configure the endpoint with. + isSource - boolean. indicates the endpoint can act as a source of new connections. optional. + dragOptions - if isSource is set to true, you can supply arguments for the jquery draggable method. optional. + connectionStyle - if isSource is set to true, this is the paint style for connections from this endpoint. optional. + connector - optional connector type to use. + isTarget - boolean. indicates the endpoint can act as a target of new connections. optional. + dropOptions - if isTarget is set to true, you can supply arguments for the jquery droppable method. optional. + reattach - optional boolean that determines whether or not the connections reattach after they + have been dragged off an endpoint and left floating. defaults to false: connections + dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + params = params || {}; + // make a copy. then we can use the wrapper function. + jsPlumb.extend({}, params); + var self = this; + var id = new String('_jsplumb_e_' + (new Date()).getTime()); + this.getId = function() { return id; }; + self.anchor = params.anchor || jsPlumb.Anchors.TopCenter; + //if (self.anchor.isDynamic) self.anchor.endpoint = self; + var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot(); + var _style = params.style || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + this.connectorStyle = params.connectorStyle; + this.connector = params.connector; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + var _element = params.source; + var _uuid = params.uuid; + if (_uuid) endpointsByUUID[_uuid] = self; + this.container = params.container || _currentInstance.Defaults.Container || jsPlumb.Defaults.Container; + var _elementId = _getAttribute(_element, "id"); + var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of. + this.canvas = params.canvas || _newCanvas(jsPlumb.endpointClass, this.container, params.uuid); + this.connections = params.connections || []; + this.scope = params.scope || DEFAULT_SCOPE; + var _reattach = params.reattach || false; + var floatingEndpoint = null; + var inPlaceCopy = null; + this.dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.addConnection = function(connection) { + self.connections.push(connection); + }; + this.detach = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + self.connections.splice(idx, 1); + t.detach(connection); + } + }; + + /** + * detaches all connections this Endpoint has + */ + this.detachAll = function() { + while(self.connections.length > 0) { + self.detach(self.connections[0]); + } + }; + + /** + * removes any connections from this Endpoint that are + * connected to the given target endpoint. + * @param targetEndpoint + */ + this.detachFrom = function(targetEndpoint) { + var c = []; + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for (var i = 0; i < c.length; i++) { + targetEndpoint.detach(c[i]); + self.detach(c[i]); + } + }; + + /** + * returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { return _element; }; + + /** + * returns the UUID for this Endpoint, if there is one. + */ + this.getUuid= function() { return _uuid; }; + + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + var e = new Endpoint({anchor:self.anchor, source:_element, style:_style, endpoint:_endpoint}); + return e; + }; + + + /** + * returns whether or not this endpoint is connected to the given endpoint. + * @param endpoint Endpoint to test. + * @since 1.1.1 + * + * todo: needs testing. will this work if the endpoint passed in is the source? + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + * @returns {Boolean} + */ + this.isFloating = function() { return floatingEndpoint != null; }; + /** + * first pass at default ConnectorSelector: returns the first connection, if we have any. + * modified a little, 5/10/2010: now it only returns a connector if we have not got equal to or more than _maxConnector connectors + * attached. otherwise it is assumed a new connector is ok. but note with this setup we can't select any other connection than the first + * one. what if this could return a list? that implies everything should work with a list - dragging etc. could be nasty. could also + * be cool. + */ + var connectorSelector = function() { + //return self.connections.length == 0 || self.connections.length < _maxConnections ? null : self.connections[0]; + if (self.connections.length < _maxConnections) return null; + //else if (self.connections.length == _maxConnections) return false; + else return self.connections[0]; + }; + + /** + * @returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); }; + + /** + * sets whether or not connnctions can be dragged from this Endpoint once it is full. you would use + * this in a UI in which you're going to provide some other way of breaking connections, if you need + * to break them at all. this property is by default true; use it in conjunction with the 'reattach' + * option on a connect call. + */ + this.setDragAllowedWhenFull = function(allowed) { + self.dragAllowedWhenFull = allowed; + }; + + /** + * a deep equals check. everything must match, including the anchor, styles, everything. + * TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor) && + true; + }; + + /** + * paints the Endpoint, recalculating offset and anchor positions if necessary. + */ + this.paint = function(anchorPoint, connectorPaintStyle, canvas) { + + if (log) log.debug("Painting Endpoint with elementId [" + _elementId + "]; anchorPoint is [" + anchorPoint + "]"); + + if (anchorPoint == null) { + // do we always want to force a repaint here? i dont think so! + var xy = offsets[_elementId]; + var wh = sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset(_elementId); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + anchorPoint = self.anchor.compute([xy.left, xy.top], wh, self); + } + _endpoint.paint(anchorPoint, self.anchor.getOrientation(), canvas || self.canvas, _style, connectorPaintStyle || _style); + }; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + + // is this a connection source? we make it draggable and have the drag listener + // maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + + // first the question is, how many connections are on this endpoint? if it's only one, then excellent. otherwise we will either need a way + // to select one connection from the list, or drag them all. if we had a pluggable 'ConnectorSelector' interface we could probably + // provide a way for people to implement their own UI components to do the connector selection. the question in that particular case would be how much + // information the interface needs from jsPlumb at execution time. if, however, we leave out the connector selection, and drag them all, + // that wouldn't be too hard to organise. perhaps that behaviour would be on a switch for the endpoint, or perhaps the ConnectorSelector + // interface returns a List, with the default implementation just returning everything. i think i like that. + // + // let's say for now that there is just one endpoint, cos we need to get this going before we can consider a list of them anyway. + // the major difference between that case and the case of a new endpoint is actually quite small - it's a question of where the + // Connection comes from. for a new one, we create a new one. obviously. otherwise we just get the jpc from the Endpoint + // (remember we're only assuming one connection right now). so all of the UI stuff we do to create the floating endpoint etc + // will still be valid, but when we stop dragging, we'll have to do something different. if we stop with a valid drop i think it will + // be the same process. but if we stop with an invalid drop we have to reset the Connection to how it was when we got it. + var start = function() { + + jpc = connectorSelector(); + if (self.isFull() && !self.dragAllowedWhenFull) return false; + + _updateOffset(_elementId); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + var nE = _getElementObject(n); + _appendElement(n, self.container); // + // create and assign an id, and initialize the offset. + //TODO can't this be replaced with a _getId call? + var id = "" + new String(new Date().getTime()); + _setAttribute(nE, "id", id); + _updateOffset(id); + // store the id of the dragging div and the source element. the drop function + // will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor({reference:self.anchor, referenceCanvas:self.canvas}); + floatingEndpoint = new Endpoint({ style:{fillStyle:'rgba(0,0,0,0)'}, endpoint:_endpoint, anchor:floatingAnchor, source:nE }); + + if (jpc == null) { + self.anchor.locked = true; + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = new Connection({ + sourceEndpoint:self, + targetEndpoint:floatingEndpoint, + source:_getElementObject(_element), + target:_getElementObject(n), + anchors:[self.anchor, floatingAnchor], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + connector: params.connector + }); + } else { + existingJpc = true; + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; + // probably we should remove the connection? and add it back if the user + // does not drop it somewhere proper. + self.removeConnection(jpc); + if (anchorIdx == 0){ + existingJpcParams = [jpc.source, jpc.sourceId]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + }else { + existingJpcParams = [jpc.target, jpc.targetId]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // todo unregister on stop + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + }; + + var dragOptions = params.dragOptions || { }; + + var defaultOpts = jsPlumb.extend({}, jsPlumb.CurrentLibrary.defaultDragOptions); + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + var startEvent = jsPlumb.CurrentLibrary.dragEvents['start']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + //$(n).offset(_ui); + jsPlumb.CurrentLibrary.setOffset(n, _ui); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements([n, floatingEndpoint.canvas], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + + if (_reattach) { + jpc.floatingAnchorIndex = null; + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } + else { + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeElement(jpc.canvas, self.container); + _removeFromList(connectionsByScope, jpc.scope, jpc); + _fireEvent("jsPlumbConnectionDetached", { + source:jpc.source, + target:jpc.target, + sourceId:jpc.sourceId, + targetId:jpc.targetId, + sourceEndpoint:jpc.endpoints[0], + targetEndpoint:jpc.endpoints[1] + }); + } + } else { + _removeElement(jpc.canvas, self.container); + //if (jpc.endpoints[1]) alert("target set"); + self.removeConnection(jpc); + } + } + self.anchor.locked = false; + jpc = null; + delete floatingEndpoint; + delete inPlaceCopy; + self.paint(); + } + ); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + }; + + // connector target + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend({}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + dropOptions[dropEvent]= _wrap(dropOptions[dropEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable), "dragId"); + var elId = _getAttribute(_getElementObject(draggable),"elId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + var oidx = idx == 0 ? 1 : 0; + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + //todo test that the target is not full. + // remove this jpc from the current endpoint + jpc.endpoints[idx].removeConnection(jpc); + if (jpc.suspendedEndpoint) + jpc.suspendedEndpoint.removeConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + // add the jpc to the other endpoint too. + jpc.endpoints[oidx].addConnection(jpc); + + _addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + jsPlumb.repaint(elId); + _fireEvent("jsPlumbConnection", { + source:jpc.source, + target:jpc.target, + sourceId:jpc.sourceId, + targetId:jpc.targetId, + sourceEndpoint:jpc.endpoints[0], + targetEndpoint:jpc.endpoints[1] + }); + } + // else there must be some cleanup required. + + delete floatingConnections[id]; + }); + + dropOptions[overEvent]= _wrap(dropOptions[overEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + }); + + jsPlumb.CurrentLibrary.initDroppable(_getElementObject(self.canvas), dropOptions); + } + + return self; + }; + + /* + Property: Defaults + These are the default settings for jsPlumb, that is what will be used if you do not supply specific pieces of information + to the various API calls. A convenient way to implement your own look and feel can be to override these defaults by including a script + somewhere after the jsPlumb include, but before you make any calls to jsPlumb, for instance in this example we set the PaintStyle to be + a blue line of 27 pixels: + > jsPlumb.Defaults.PaintStyle = { lineWidth:27, strokeStyle:'blue' } + */ + this.Defaults = { + Anchor : null, + Anchors : [ null, null ], + BackgroundPaintStyle : null , + Connector : null, + Container : null, + DragOptions: { }, + DropOptions: { }, + Endpoint : null, + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : null }, + EndpointStyles : [ null, null ], + LabelStyle : { fillStyle:"rgba(0,0,0,0)", color:"black" }, + MaxConnections : null, + // TODO: should we have OverlayStyle too? + PaintStyle : { lineWidth : 10, strokeStyle : 'red' }, + Scope : "_jsPlumb_DefaultScope" + + }; + + /* + Property: connectorClass + The CSS class to set on Connection canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = '_jsPlumb_connector'; + + /* + Property: endpointClass + The CSS class to set on Endpoint canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = '_jsPlumb_endpoint'; + + /* + Property: Anchors + Default jsPlumb Anchors. These are supplied in the file jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form .jsPlumb-all-x.x.x.js. You can provide your own Anchors by supplying them in a script that is loaded after jsPlumb, for instance: + > jsPlumb.Anchors.MyAnchor = { ....anchor code here. see the documentation. } + */ + this.Anchors = {}; + + /* + Property: Connectors + Default jsPlumb Connectors. These are supplied in the file jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form .jsPlumb-all-x.x.x.js. You can provide your own Connectors by supplying them in a script that is loaded after jsPlumb, for instance: + > jsPlumb.Connectors.MyConnector = { ....connector code here. see the documentation. } + */ + this.Connectors = {}; + + /* + Property: Endpoints + Default jsPlumb Endpoints. These are supplied in the file jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form .jsPlumb-all-x.x.x.js. You can provide your own Endpoints by supplying them in a script that is loaded after jsPlumb, for instance: + > jsPlumb.Endpoints.MyEndpoint = { ....endpoint code here. see the documentation. } + */ + this.Endpoints = {}; + + /* + Property:Overlays + Default jsPlumb Overlays such as Arrows and Labels. These are supplied in the file jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form .jsPlumb-all-x.x.x.js. You can provide your own Overlays by supplying them in a script that is loaded after jsPlumb, for instance: + > jsPlumb.Overlays.MyOverlay = { ....overlay code here. see the documentation. } + */ + this.Overlays = {}; + + /* + Function: addEndpoint + Adds an Endpoint to a given element. + Parameters: + target - Element to add the endpoint to. either an element id, or a selector representing some element. + params - Object containing Endpoint options (more info required) + Returns: + The newly created Endpoint. + See Also: + + */ + this.addEndpoint = function(target, params) { + params = jsPlumb.extend({}, params); + var el = _getElementObject(target); + var id = _getAttribute(el,"id"); + params.source = el; + _updateOffset(id); + var e = new Endpoint(params); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id]; + var myWH = sizes[id]; + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH, e); + e.paint(anchorLoc); + return e; + }; + + /* + Function: addEndpoint + Adds a list of Endpoints to a given element. + Parameters: + target - element to add the endpoint to. either an element id, or a selector representing some element. + endpoints - List of objects containing Endpoint options. one Endpoint is created for each entry in this list. + Returns: + List of newly created Endpoints, one for each entry in the 'endpoints' argument. + See Also: + + */ + this.addEndpoints = function(target, endpoints) { + var results = []; + for (var i = 0; i < endpoints.length; i++) { + results.push(_currentInstance.addEndpoint(target, endpoints[i])); + } + return results; + }; + + /* + Function: animate + Wrapper around supporting library's animate function; injects a call to jsPlumb in the 'step' function (creating it if necessary). + This only supports the two-arg version of the animate call in jQuery - the one that takes an 'options' object as the second arg. + MooTools has only one method - a two arg one. Which is handy. + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the standard jQuery animate call. + options - The 'options' argument you want passed to the standard jQuery animate call. + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el); + var id = _getAttribute(el,"id"); + //TODO this is not agnostic yet. + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() + { + _currentInstance.repaint(id); + }); + + // you know, probably onComplete should repaint too. that will help keep up + // with fast animations. + options[completeFunction] = _wrap(options[completeFunction], function() + { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /** + * clears the cache used to lookup elements by their id. if you remove any elements + * from the DOM you should call this to ensure that if you add them back in jsPlumb does not + * have a stale handle. + */ + this.clearCache = function() { + delete cached; + cached = {}; + }; + + this.autoConnect = function(params/*source, target, anchors, endpoint*/) { + var sources = [], targets = [], _endpoint = params.endpoint || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle, _anchors = anchors || _currentInstance.Defaults.DynamicAnchors || jsPlumb.Defaults.DynamicAnchors; + var _addAll = function(s, t) { + for (var i = 0 ; i < s.length; i++) t.push(s[i]); + }; + var source = params.source, target = params.target, anchors = params.anchors; + if (typeof source == 'string') + sources.push(_getElementObject(source)); + else + _addAll(source, sources); + if (typeof target == 'string') + targets.push(_getElementObject(target)); + else + _addAll(target, targets); + var connectOptions = jsPlumb.extend(params, {source:null,target:null,anchors:null}); + for (var i = 0; i < sources.length; i++) { + for (var j = 0; j < targets.length; j++) { + var e1 = jsPlumb.addEndpoint(sources[i], jsPlumb.extend({ anchor:jsPlumb.makeDynamicAnchor(_anchors) }, _endpoint)); + var e2 = jsPlumb.addEndpoint(targets[j], jsPlumb.extend({ anchor:jsPlumb.makeDynamicAnchor(_anchors) }, _endpoint)); + _currentInstance.connect(jsPlumb.extend(connectOptions, { sourceEndpoint:e1, targetEndpoint:e2 })); + } + } + }; + + /* + Function: connect + Establishes a connection between two elements. + Parameters: + params - Object containing setup for the connection. see documentation. + Returns: + The newly created Connection. + */ + this.connect = function(params) { + var _p = jsPlumb.extend({}, params); + // test for endpoint uuids to connect + if (params.uuids) { + var _resolveByUuid = function(idx) { + var e = _getEndpoint(params.uuids[idx]); + if (!e) throw ("Endpoint with UUID " + params.uuids[idx] + " not found."); + return e; + }; + _p.sourceEndpoint = _resolveByUuid(0); + _p.targetEndpoint = _resolveByUuid(1); + } + + // now ensure that if we do have Endpoints already, they're not full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log("could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log("could not add connection; target endpoint is full"); + return; + } + + var jpc = new Connection(_p); + // add to list of connections (by scope). + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + _fireEvent("jsPlumbConnection", { + source:jpc.source, + target:jpc.target, + sourceId:jpc.sourceId, + targetId:jpc.targetId, + sourceEndpoint:jpc.endpoints[0], + targetEndpoint:jpc.endpoints[1] + }); + // force a paint + _draw(jpc.source); + + return jpc; + }; + + var fireDetachEvent = function(jpc) { + _fireEvent("jsPlumbConnectionDetached", { + source:jpc.source, + target:jpc.target, + sourceId:jpc.sourceId, + targetId:jpc.targetId, + sourceEndpoint:jpc.endpoints[0], + targetEndpoint:jpc.endpoints[1] + }); + }; + + /* + Function: detach + Removes a connection. + Parameters: + sourceId - Id of the first element in the connection. A String. + targetId - iI of the second element in the connection. A String. + Returns: + true if successful, false if not. + */ + this.detach = function(sourceId, targetId) { + if (arguments.length == 2) { + var f = function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + fireDetachEvent(jpc); + + return true; + } + + }; + + // todo: how to cleanup the actual storage? a third arg to _operation? + _operation(sourceId, f); + } + // this is the new version of the method, taking a JS object like the connect method does. + else if (arguments.length == 1) { + var _p = jsPlumb.extend({}, sourceId); // a backwards compatibility hack: sourceId should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1])); + } + else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } + else { + sourceId = _getId(_p.source); + targetId = _getId(_p.target); + var f = function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + fireDetachEvent(jpc); + + return true; + } + + }; + + // todo: how to cleanup the actual storage? a third arg to _operation? + _operation(sourceId, f); + } + + // fire an event + /*_fireEvent("jsPlumbDetach", { + source:jpc.source, + target:jpc.target, + sourceId:jpc.sourceId, + targetId:jpc.targetId, + sourceEndpoint:jpc.endpoints[0], + targetEndpoint:jpc.endpoints[1] + });*/ + /*// force a paint + _draw(jpc.source); + + return jpc;*/ + } + }; + + /* + Function: detachAll + Removes all an element's connections. + Parameters: + el - either the id of the element, or a selector for the element. + Returns: + void + */ + this.detachAll = function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + var c = endpoints[i].connections.length; + if (c > 0) { + for (var j = 0; j < c; j++) { + var jpc = endpoints[i].connections[0]; + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + fireDetachEvent(jpc); + } + } + } + } + }; + + /* + Function: detachEverything + Remove all Connections from all elements, but leaves Endpoints in place. + Returns: + void + See Also: + + */ + this.detachEverything = function() { + for(var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + var c = endpoints[i].connections.length; + if (c > 0) { + for (var j = 0; j < c; j++) { + var jpc = endpoints[i].connections[0]; + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + fireDetachEvent(jpc); + } + } + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + /* + Function: draggable + initialises the draggability of some element or elements. + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for (var i = 0; i < el.length; i++) + { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) + _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + Parameters: + o1 - object to extend + o2 - object to extend o1 with + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + Function: getConnections + Gets all or a subset of connections currently managed by this jsPlumb instance. + Parameters: + options - a JS object that holds options defining what sort of connections you're looking for. Valid values are: + scope this may be a String or a list of Strings. jsPlumb will only return Connections whose scope matches what this option + defines. If you omit it, you will be given Connections of every scope. + source may be a string or a list of strings; constraints results to have only Connections whose source is this object. + target may be a string or a list of strings; constraints results to have only Connections whose target is this object. + + The return value is a dictionary in this format: + + { + 'scope1': [ + {sourceId:'window1', targetId:'window2'}, + {sourceId:'window3', targetId:'window4'}, + {sourceId:'window1', targetId:'window3'} + ], + 'scope2': [ + {sourceId:'window1', targetId:'window3'} + ] + } + + */ + this.getConnections = function(options) { + var r = {}; + options = options || {}; + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') r.push(input); + else r = input; + } + return r; + }; + var scopes = prepareList(options.scope); + var sources = prepareList(options.source); + var targets = prepareList(options.target); + var filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }; + for (var i in connectionsByScope) { + if (filter(scopes, i)) { + r[i] = []; + for (var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + r[i].push({ + sourceId:c.sourceId, + targetId:c.targetId, + source:c.source, + target:c.target, + sourceEndpoint:c.endpoints[0], + targetEndpoint:c.endpoints[1] + }); + } + } + } + return r; + }; + + /* + Function: getDefaultScope + Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a scope to an endpoint + or connection allows you to support different types of connections in the same UI. but if you're only interested in one type of connection, + you don't need to supply a scope. this method will probably be used by very few people; it's good for testing though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + gets an Endpoint by UUID + Parameters: + uuid - the UUID for the Endpoint + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /* + * gets an element's id, creating one if necessary. really only exposed for the lib-specific + * functionality to access; would be better to pass the current instance into the lib-specific + * code (even though this is a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + + /* + Function: hide + Sets an element's connections to be hidden. + Parameters: + el - either the id of the element, or a selector for the element. + Returns: + void + */ + this.hide = function(el) { + _setVisible(el, "none"); + }; + + /* + Function: makeAnchor + Creates an anchor with the given params. + Parameters: + x - the x location of the anchor as a fraction of the total width. + y - the y location of the anchor as a fraction of the total height. + xOrientation - value indicating the general direction a connection from the anchor should go in, in the x direction. + yOrientation - value indicating the general direction a connection from the anchor should go in, in the y direction. + xOffset - a fixed offset that should be applied in the x direction that should be applied after the x position has been figured out. optional. defaults to 0. + yOffset - a fixed offset that should be applied in the y direction that should be applied after the y position has been figured out. optional. defaults to 0. + Returns: + The newly created Anchor. + */ + this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards compatibility if we are given only one value we assume it's a call in the old form. + var params = {}; + if (arguments.length == 1) jsPlumb.extend(params, x); + else { + params = {x:x, y:y}; + if (arguments.length >= 4) { + params.orientation = [arguments[2], arguments[3]]; + } + if (arguments.length == 6) params.offsets = [arguments[4], arguments[5]]; + } + var a = new Anchor(params); + a.clone = function() { return new Anchor(params); }; + return a; + }; + + this.makeDynamicAnchor = function(anchors) { + return new DynamicAnchor(anchors); + }; + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + Parameters: + el - either the id of the element or a selector representing the element. + Returns: + void + See Also: + + */ + this.repaint = function(el) { + + var _processElement = function(el) { + var ele = _getElementObject(el); + _draw(ele); + }; + + // TODO: support a jQuery result object too! + + // support both lists... + if (typeof el =='object') { + for (var i = 0; i < el.length; i++) + _processElement(el[i]); + } // ...and single strings. + else _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + Returns: + void + See Also: + + */ + this.repaintEverything = function() { + for (var elId in endpointsByElement) { + _draw(_getElementObject(elId)); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + Parameters: + el - either an element id, or a selector for an element. + Returns: + void + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"); + // first remove all Connections. + jsPlumb.detachAll(elId); + var ebe = endpointsByElement[elId]; + for (var i in ebe) { + _removeElement(ebe[i].canvas, ebe[i].getElement()); + } + endpointsByElement[elId] = []; + }; + + /* + Function: removeEveryEndpoint + Removes every Endpoint in this instance of jsPlumb. + Returns: + void + See Also: + + */ + this.removeEveryEndpoint = function() { + for(var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + _removeElement(endpoints[i].canvas, endpoints[i].container); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + }; + + /* + Function: removeEndpoint + Removes the given Endpoint from the given element. + Parameters: + el - either an element id, or a selector for an element. + endpoint - Endpoint to remove. this is an Endpoint object, such as would have been returned from a call to addEndpoint. + Returns: + void + See Also: + + */ + this.removeEndpoint = function(el, endpoint) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + if (ebe) { + if(_removeFromList(endpointsByElement, elId, endpoint)) + _removeElement(endpoint.canvas, endpoint.getElement()); + } + }; + + /* + Function:reset + removes all endpoints and connections and clears the element cache. + */ + this.reset = function() { + this.detachEverything(); + this.removeEveryEndpoint(); + this.clearCache(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + Parameters: + value - whether or not to automatically repaint when the window is resized. + Returns: + void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + Function: setDefaultNewCanvasSize + Sets the default size jsPlumb will use for a new canvas (we create a square canvas so one value is all that is required). + This is a hack for IE, because ExplorerCanvas seems to need for a canvas to be larger than what you are going to draw on + it at initialisation time. The default value of this is 1200 pixels, which is quite large, but if for some reason you're + drawing connectors that are bigger, you should adjust this value appropriately. + Parameters: + size - The default size to use. jsPlumb will use a square canvas so you need only supply one value. + Returns: + void + */ + this.setDefaultNewCanvasSize = function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }; + + /* + Function: setDefaultScope + Sets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a scope to an endpoint + or connection allows you to support different types of connections in the same UI. but if you're only interested in one type of connection, + you don't need to supply a scope. this method will probably be used by very few people; it just instructs jsPlumb to use a different key + for the default scope. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + Function: setDraggable + Sets whether or not a given element is draggable, regardless of what any plumb command may request. + Parameters: + el - either the id for the element, or a selector representing the element. + Returns: + void + */ + this.setDraggable = _setDraggable; + + /* + Function: setDraggableByDefault + Sets whether or not elements are draggable by default. Default for this is true. + Parameters: + draggable - value to set + Returns: + void + */ + this.setDraggableByDefault = function(draggable) { + _draggableByDefault = draggable; + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + Function: setRepaintFunction + Sets the function to fire when the window size has changed and a repaint was fired. + Parameters: + f - Function to execute. + Returns: + void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + Function: show + Sets an element's connections to be visible. + Parameters: + el - either the id of the element, or a selector for the element. + Returns: + void + */ + this.show = function(el) { + _setVisible(el, "block"); + }; + + /* + Function: sizeCanvas + Helper to size a canvas. You would typically use this when writing your own Connector or Endpoint implementation. + Parameters: + x - [int] x position for the Canvas origin + y - [int] y position for the Canvas origin + w - [int] width of the canvas + h - [int] height of the canvas + Returns: + void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; canvas.height = h; + canvas.style.width = w + "px"; canvas.width = w; + canvas.style.left = x + "px"; canvas.style.top = y + "px"; + // _currentInstance.CurrentLibrary.setPosition(canvas, x, y); + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards compatibility + */ + this.toggle = _toggleVisible; + + /* + Function: toggleVisible + Toggles visibility of an element's connections. + Parameters: + el - either the element's id, or a selector representing the element. + Returns: + void, but should be updated to return the current state + */ + //TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + Function: toggleDraggable + Toggles draggability (sic) of an element's connections. + Parameters: + el - either the element's id, or a selector representing the element. + Returns: + The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + Function: unload + Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element + Returns: + void + */ + this.unload = function() { + delete endpointsByElement; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + }; + + /* + Function: wrap + Helper method to wrap an existing function with one of your own. This is used by the various + implementations to wrap event callbacks for drag/drop etc; it allows jsPlumb to be + transparent in its handling of these things. If a user supplies their own event callback, + for anything, it will always be called. + Parameters: + */ + this.wrap = _wrap; + this.addListener = _addListener; + + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(); + if (_defaults) + jsPlumb.extend(j.Defaults, _defaults); + return j; + }; +})(); +/* +* jsPlumb-defaults-1.2.3-RC1 +* +* This script contains the default Anchors, Endpoints, Connectors and Overlays for jsPlumb. It should be used with jsPlumb 1.1.0 and above; +* prior to version 1.1.0 of jsPlumb the defaults were included inside the main script. +* +* NOTE: for production usage you should use jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, +* in a minified file. +* +* Dual licensed under MIT and GPL2. +*/ + +(function() { + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * getOrientation : function() { return [ox, oy]; } + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + jsPlumb.Anchors.TopCenter = jsPlumb.makeAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors.BottomCenter = jsPlumb.makeAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors.LeftMiddle = jsPlumb.makeAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors.RightMiddle = jsPlumb.makeAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors.Center = jsPlumb.makeAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors.TopRight = jsPlumb.makeAnchor(1, 0, 0,-1); + jsPlumb.Anchors.BottomRight = jsPlumb.makeAnchor(1, 1, 0, 1); + jsPlumb.Anchors.TopLeft = jsPlumb.makeAnchor(0, 0, 0, -1); + jsPlumb.Anchors.BottomLeft = jsPlumb.makeAnchor(0, 1, 0, 1); + jsPlumb.Anchors.AutoDefault = function() { return jsPlumb.makeDynamicAnchor([jsPlumb.Anchors.TopCenter, jsPlumb.Anchors.RightMiddle, jsPlumb.Anchors.BottomCenter, jsPlumb.Anchors.LeftMiddle]); }; + + jsPlumb.Defaults.DynamicAnchors = [jsPlumb.Anchors.TopCenter, jsPlumb.Anchors.RightMiddle, jsPlumb.Anchors.BottomCenter, jsPlumb.Anchors.LeftMiddle]; + + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + var currentPoints = null; + var _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + + _dx = _tx - _sx, _dy = (_ty - _sy); + _m = _dy / _dx, _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + + return currentPoints; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + var xp = _sx + (location * _dx); + var yp = _m == Infinity ? xp + _b : (_m * xp) + _b; + return [xp, yp]; + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { return _m; }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + var orientation = distance > 0 ? 1 : -1; + var y = Math.abs(distance * Math.sin(_theta)); + if (_sy > _ty) y = y * -1; + var x = Math.abs(distance * Math.cos(_theta)); + if (_sx > _tx) x = x * -1; + return [p[0] + (orientation * x), p[1] + (orientation * y)]; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, distance, length) { + var p = self.pointAlongPathFrom(location, distance); + var m = self.gradientAtPoint(p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [[p[0] + x, p[1] + y], [p[0] - x, p[1] - y]]; + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + /** + * This Connector draws a Bezier curve with two control points. + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + jsPlumb.Connectors.Bezier = function(curviness) { + var self = this; + this.majorAnchor = curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _sx, _sy, _canvasX, _canvasY, _w, _h; + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) + { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx); var minx2 = Math.min(_CP[0], _CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(_sx,_tx); var maxx2 = Math.max(_CP[0], _CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty); var miny2 = Math.min(_CP[1], _CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(_sy,_ty); var maxy2 = Math.max(_CP[1], _CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + this.paint = function(d, ctx) { + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + }; + + /** + * returns the distance the given point is from the curve. not enabled for 1.2.3. didnt make the cut. next time. + * + this.distanceFrom = function(point) { + var curve = [ {x:currentPoints[4], y:currentPoints[5]}, + {x:currentPoints[8], y:currentPoints[9]}, + {x:currentPoints[10], y:currentPoints[11]}, + {x:currentPoints[6], y:currentPoints[7]}]; + return (jsPlumb.DistanceFromCurve(point, curve)); + };*/ + + var _quadraticPointOnPath = function(location) { + function B1(t) { return t*t; }; + function B2(t) { return 2*t*(1-t); }; + function B3(t) { return (1-t)*(1-t); }; + var x = _sx*B1(location) + _CP[0]*B2(location) + _CP2[0]*B3(location); + var y = _sy*B1(location) + _CP[1]*B2(location) + _CP2[1]*B3(location); + return [x,y]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + + // from http://13thparallel.com/archive/bezier-curves/ + function B1(t) { return t*t*t }; + function B2(t) { return 3*t*t*(1-t) }; + function B3(t) { return 3*t*(1-t)*(1-t) }; + function B4(t) { return (1-t)*(1-t)*(1-t) }; + + var x = _sx*B1(location) + _CP[0]*B2(location) + _CP2[0]*B3(location) + _tx*B4(location); + var y = _sy*B1(location) + _CP[1]*B2(location) + _CP2[1]*B3(location) + _ty*B4(location); + return [x,y]; + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + var p1 = self.pointOnPath(location); + var p2 = _quadraticPointOnPath(location); + var dy = p2[1] - p1[1], dx = p2[0] - p1[0]; + var rtn = Math.atan(dy / dx) ; + // http://bimixual.org/AnimationLibrary/beziertangents.html + return rtn; + }; + + /** + * finds the point that is 'distance' along the path from 'location'. this method returns both the x,y location of the point and also + * its 'location' (proportion of travel along the path). + */ + var _pointAlongPath = function(location, distance) { + var _dist = function(p1,p2) { return Math.sqrt(Math.pow(p1[0] - p2[0], 2) + Math.pow(p1[1] - p2[1], 2)); }; + var prev = self.pointOnPath(location), tally = 0, curLoc = location, direction = distance > 0 ? 1 : -1, cur = null; + while (tally < Math.abs(distance)) { + curLoc += (0.005 * direction); + cur = self.pointOnPath(curLoc); + tally += _dist(cur, prev); + prev = cur; + } + return {point:cur, location:curLoc}; + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return _pointAlongPath(location, distance).point; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, distance, length) { + var p = _pointAlongPath(location, distance); + var m = self.gradientAtPoint(p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [[p.point[0] + x, p.point[1] + y], [p.point[0] - x, p.point[1] - y]]; + }; + + this.createGradient = function(dim, ctx, swap) { + return (swap) ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + + /** + * Types of endpoint UIs. we supply four - a circle of default radius 10px, a rectangle of + * default size 20x20, an image (with no default), and a Triangle, of default size 15. + * you can supply others of these if you want to - see the documentation for a howto. + */ + + /** + * a round endpoint, with default radius 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + } + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * A Rectangular endpoint, with default size 20x20. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + jsPlumb.Endpoints.Triangle = function(params) { + + params = params || { width:15, height:15 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - width/2; + var y = anchorPoint[1] - height/2; + + jsPlumb.sizeCanvas(canvas, x, y, width, height); + + var ctx = canvas.getContext('2d'); + var offsetX = 0, offsetY = 0, angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = endpointStyle.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' property in the params object.. + */ + jsPlumb.Endpoints.Image = function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + }; + + /** + * An arrow overlay. you can provide: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + */ + jsPlumb.Overlays.Arrow = function(params) { + params = params || {}; + var self = this; + var length = params.length || 20; + var width = params.width || 20; + var fillStyle = params.fillStyle || "black"; + var strokeStyle = params.strokeStyle; + var lineWidth = params.lineWidth || 1; + this.loc = params.location || 0.5; + // how far along the arrow the lines folding back in come to. default is 62.3%. + var foldback = params.foldback || 0.623; + var _getFoldBackPoint = function(connector, loc) { + if (foldback == 0.5) return connector.pointOnPath(loc); + else { + var adj = 0.5 - foldback; // we calculate relative to the center + return connector.pointAlongPathFrom(loc, length * adj); + } + }; + + this.computeMaxSize = function() { return width * 1.5; } + + this.draw = function(connector, ctx) { + // this is the arrow head position + var hxy = connector.pointAlongPathFrom(self.loc, length / 2); + // this is the center of the tail + var txy = connector.pointAlongPathFrom(self.loc, -length / 2), tx = txy[0], ty = txy[1]; + // this is the tail vector + var tail = connector.perpendicularToPathAt(self.loc, -length / 2, width); + // this is the point the tail goes in to + var cxy = _getFoldBackPoint(connector, self.loc); + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(hxy[0], hxy[1]); + ctx.lineTo(tail[0][0], tail[0][1]); + ctx.lineTo(cxy[0], cxy[1]); + ctx.lineTo(tail[1][0], tail[1][1]); + ctx.lineTo(hxy[0], hxy[1]); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + + /** + * a basic arrow. this is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. See Arrow for params. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + /** + * a diamond. like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. See Arrow for params. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40; + var p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + /** + * A Label overlay. Params you can provide: + * + * labelStyle - js object containing style instructions for the label. defaults to jsPlumb.Defaults.LabelStyle. + * label - the label to paint. may be a string or a function that returns a string. nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * borderWidth - width of a border to paint. defaults to zero. + * borderStyle - strokeStyle to use when painting the border, if necessary. + */ + jsPlumb.Overlays.Label = function(params) { + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + var self = this; + var labelWidth = null, labelHeight = null, labelText = null, labelPadding = null; + this.location = params.location || 0.5; + this.computeMaxSize = function(connector, ctx) { + if (labelText) { + ctx.save(); + if (self.labelStyle.font) ctx.font = self.labelStyle.font; + var t = ctx.measureText(labelText).width; + // a fake text height measurement: use the width of upper case M + var h = ctx.measureText("M").width; + labelPadding = self.labelStyle.padding || 0.25; + labelWidth = t + (2 * t * labelPadding); + labelHeight = h + (2 * h * labelPadding); + ctx.restore(); + return Math.max(labelWidth, labelHeight) * 1.5; + } + return 0; + }; + this.draw = function(connector, ctx) { + // we allow label generation by a function here. you get given the Connection object as an argument. + labelText = typeof self.label == 'function' ? self.label(self) : self.label; + if (labelText) { + if (self.labelStyle.font) ctx.font = self.labelStyle.font; + var t = ctx.measureText(labelText).width; + // a fake text height measurement: use the width of upper case M + var h = ctx.measureText("M").width; + labelPadding = self.labelStyle.padding || 0.25; + labelWidth = t + (2 * t * labelPadding); + labelHeight = h + (2 * h * labelPadding); + var cxy = connector.pointOnPath(self.location); + if (self.labelStyle.font) ctx.font = self.labelStyle.font; + if (self.labelStyle.fillStyle) + ctx.fillStyle = self.labelStyle.fillStyle; + else + ctx.fillStyle = "rgba(0,0,0,0)"; + ctx.fillRect(cxy[0] - (labelWidth / 2), cxy[1] - (labelHeight / 2) , labelWidth , labelHeight ); + + if (self.labelStyle.color) ctx.fillStyle = self.labelStyle.color; + ctx.textBaseline = "middle"; + ctx.textAlign = "center"; + ctx.fillText(labelText, cxy[0], cxy[1]); + + // border + if (self.labelStyle.borderWidth > 0) { + ctx.strokeStyle = self.labelStyle.borderStyle || "black"; + ctx.strokeRect(cxy[0] - (labelWidth / 2), cxy[1] - (labelHeight / 2) , labelWidth , labelHeight ); + } + } + }; + }; +})();/* + * mootools.jsPlumb 1.2.3-RC1 + * + * MooTools specific functionality for jsPlumb. + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + * NOTE: for production usage you should use mootools.jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, + * in a minified file. + * + * Dual licensed under MIT and GPL2. + * + */ + +(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function() { + this.parent(); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}; + var _droppableOptions = {}; + var _draggablesByScope = {}; + var _draggablesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }; + + + jsPlumb.CurrentLibrary = { + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' + }, + + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).grab(child); + }, + + bind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.addEvent(event, callback); + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + getElementObject : function(el) { + return $(el); + }, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + setOffset : function(el, o) { + jsPlumb.CurrentLibrary.getElementObject(el).setPosition({x:o.left, y:o.top}); + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + initDraggable : function(el, options) { + var id = jsPlumb.getId(el); + var drag = _draggablesById[id]; + if (!drag) { + var originalZIndex = 0, originalCursor = null; + var dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + options['onStart'] = jsPlumb.wrap(options['onStart'], function() + { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() + { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + }); + + // DROPPABLES: + var scope = options['scope'] || jsPlumb.Defaults.Scope; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop'); + } + }; + + drag = new Drag.Move(el, options); + _add(_draggablesByScope, scope, drag); + _add(_draggablesById, el.get("id"), drag); + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + initDroppable : function(el, options) { + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _add(_droppables, scope, el); + var id = jsPlumb.getId(el); + _droppableOptions[id] = options; + var filterFunc = function(entry) { + return entry.element != el; + }; + var draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + if (typeof Drag != undefined) + return typeof Drag.Move != undefined; + return false; + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + m.start(properties); + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[0]; + return { left: ui.offsetLeft, top: ui.offsetTop }; + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + }, + + + removeElement : function(element, parent) { + + jsPlumb.CurrentLibrary.getElementObject(element).dispose(); // ?? + }, + + getScrollLeft : function(el) { + return null; + }, + + getScrollTop : function(el) { + return null; + } + }; +})(); diff --git a/archive/1.2.4/jquery.jsPlumb-1.2.4-RC1.js b/archive/1.2.4/jquery.jsPlumb-1.2.4-RC1.js new file mode 100644 index 000000000..f0a50aff2 --- /dev/null +++ b/archive/1.2.4/jquery.jsPlumb-1.2.4-RC1.js @@ -0,0 +1,275 @@ +/* + * jquery.jsPlumb 1.2.4-RC1 + * + * jQuery specific functionality for jsPlumb. + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + * NOTE: for production usage you should use jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, + * in a minified file. + * + * Dual licensed under MIT and GPL2. + * + */ + +(function($){ + /** + * plumbs the results of the selector to some target, using the given options if supplied, + * or the defaults otherwise. + */ + $.fn.plumb = function(options) { + var options = $.extend({}, options); + + return this.each(function() + { + var params = $.extend({source:$(this)}, options); + jsPlumb.connect(params); + }); + }; + + /** + * detaches the results of the selector from the given target or list of targets - 'target' + * may be a String or a List. + */ + $.fn.detach = function(target) { + return this.each(function() { + if (target) { + var id = $(this).attr("id"); + if (typeof target == 'string') target = [target]; + for (var i = 0; i < target.length; i++) + jsPlumb.detach(id, target[i]); + } + }); + }; + + /** + * detaches the results from the selector from all connections. + */ + $.fn.detachAll = function() { + return this.each(function() + { + var id = $(this).attr("id"); + jsPlumb.detachAll(id); + }); + }; + + /** + * adds an endpoint to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoint = function(options) { + var addedEndpoints = []; + this.each(function() { + addedEndpoints.push(jsPlumb.addEndpoint($(this).attr("id"), options)); + }); + return addedEndpoints[0]; + }; + + /** + * adds a list of endpoints to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoints = function(endpoints) { + var addedEndpoints = []; + return this.each(function() { + var e = jsPlumb.addEndpoints($(this).attr("id"), endpoints); + for (var i = 0; i < e.length; i++) addedEndpoints.push(e[i]); + }); + }; + + /** + * remove the endpoint, if it exists, deleting its UI elements etc. + */ + $.fn.removeEndpoint = function(endpoint) { + this.each(function() { + jsPlumb.removeEndpoint($(this).attr("id"), endpoint); + }); + }; + +})(jQuery); + + +/*TODO: abstract this out from jQuery too! but how...because jsPlumb is not loaded yet. +$(window).bind('resize', function() { + if (resizeTimer) clearTimeout(resizeTimer); + resizeTimer = setTimeout(repaintEverything, 100); + });*/ + + + +/* + * the library agnostic functions, such as find offset, get id, get attribute, extend etc. + */ +(function($) { + + jsPlumb.CurrentLibrary = { + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step', + 'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete' + }, + + bind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.bind(event, callback); + }, + + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).append(child); + }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + * + */ + getElementObject : function(el) { + return typeof(el)=='string' ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el.removeClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options) { + // remove helper directive if present. + options.helper = null; + //TODO: if 'revert' is set on the options it causes end points to animate back to + // where they came from, if the connection is aborted. do we care? probably not. + // the todo is to decide whether we care or not. + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.draggable(options); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.droppable(options); + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[1]; + //return ui.absolutePosition || ui.offset; + return ui.offset || ui.absolutePosition; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + }, + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).remove(); + }, + + getScrollLeft : function(el) { + return el.scrollLeft(); + }, + + getScrollTop : function(el) { + return el.scrollTop(); + }, + + setOffset : function(el, o) { + jsPlumb.CurrentLibrary.getElementObject(el).offset(o); + } + }; +})(jQuery); diff --git a/archive/1.2.4/jquery.jsPlumb-1.2.4-all-min.js b/archive/1.2.4/jquery.jsPlumb-1.2.4-all-min.js new file mode 100644 index 000000000..62d2ebda6 --- /dev/null +++ b/archive/1.2.4/jquery.jsPlumb-1.2.4-all-min.js @@ -0,0 +1,79 @@ +(function(){var O=function(){var o=function(){var a={};this.bind=function(b,d){if(b.constructor==Array)for(var h=0;h=0){delete a[d];a.splice(d,1);return true}}return false},ia=function(a,b){da(J(a,"id"),function(d){d.canvas.style.display=b})},ma=function(a){da(a,function(b){b.canvas.style.display="none"==b.canvas.style.display?"block":"none"})},W=function(a){var b=a.timestamp,d=a.recalc,h=a.offset;a=a.elId;if(!d)if(b&&b===k[a])return;if(d||h==null){d=v(a);r[a]=S(d); +y[a]=c.CurrentLibrary.getOffset(v(d));k[a]=b}else y[a]=h},U=function(a,b){a=a||function(){};b=b||function(){};return function(){var d=null;try{d=b.apply(this,arguments)}catch(h){G("jsPlumb function failed : "+h)}try{a.apply(this,arguments)}catch(n){G("wrapped function failed : "+n)}return d}},ja=function(a){var b=this;this.x=a.x||0;this.y=a.y||0;var d=a.orientation||[0,0],h=null;this.offsets=a.offsets||[0,0];b.timestamp=null;this.compute=function(n){var m=n.xy,A=n.wh,D=n.element;if((n=n.timestamp)&& +n===b.timestamp)return h;h=[m[0]+b.x*A[0]+b.offsets[0],m[1]+b.y*A[1]+b.offsets[1]];A=D?D.container:null;m={left:0,top:0};if(A!=null){var F=v(A);A=c.CurrentLibrary.getOffset(v(F));D=c.CurrentLibrary.getScrollLeft(F);F=c.CurrentLibrary.getScrollTop(F);m.left=A.left-D;m.top=A.top-F;h[0]-=m.left;h[1]-=m.top}b.timestamp=n;return h};this.getOrientation=function(){return d};this.equals=function(n){if(!n)return false;var m=n.getOrientation(),A=this.getOrientation();return this.x==n.x&&this.y==n.y&&this.offsets[0]== +n.offsets[0]&&this.offsets[1]==n.offsets[1]&&A[0]==m[0]&&A[1]==m[1]};this.getCurrentLocation=function(){return h}},na=function(a){var b=a.reference,d=S(v(a.referenceCanvas)),h=null,n=null;this.compute=function(m){var A=m.xy;m=m.element;A=[A[0]+d[0]/2,A[1]+d[1]/2];if(m.container!=null){m=c.CurrentLibrary.getOffset(v(m.container));A[0]-=m.left;A[1]-=m.top}return n=A};this.getOrientation=function(){if(h)return h;else{var m=b.getOrientation();return[Math.abs(m[0])*0*-1,Math.abs(m[1])*0*-1]}};this.over= +function(m){h=m.getOrientation()};this.out=function(){h=null};this.getCurrentLocation=function(){return n}},oa=function(a){this.isDynamic=this.isSelective=true;var b=a||[];for(a=0;a0?b[0]:null;this.locked=false;var h=this;this.compute=function(n){var m=n.xy,A=n.wh,D=n.txy,F=n.twh;if(h.locked||D==null||F==null)return d.compute(n); +else n.timestamp=null;var B=D[0]+F[0]/2;D=D[1]+F[1]/2;F=-1;for(var N=Infinity,K=0;KE)E=R}var M=this.connector.compute(P,B,this.endpoints[0].anchor,this.endpoints[1].anchor, +this.paintStyle.lineWidth,E);c.sizeCanvas(F,M[0],M[1],M[2],M[3]);P=function(Q,V){Q.save();c.extend(Q,V);if(V.gradient&&!w){for(var Z=b.connector.createGradient(M,Q,N==this.sourceId),p=0;p=0){b.connections.splice(z,1);L||(p.endpoints[0]==b?p.endpoints[1]:p.endpoints[0]).detach(p,true)}Y(p.canvas,p.container);ca(u,p.scope,p);$(p)};this.detachAll=function(){for(;b.connections.length>0;)b.detach(b.connections[0])};this.detachFrom= +function(p){for(var L=[],z=0;z=0&&b.connections.splice(p,1)};this.getElement=function(){return m};this.getUuid=function(){return A};this.makeInPlaceCopy=function(){return new ea({anchor:b.anchor,source:m,style:n,endpoint:h})};this.isConnectedTo=function(p){var L=false;if(p)for(var z= +0;z=N};this.setDragAllowedWhenFull=function(p){b.dragAllowedWhenFull=p};this.equals=function(p){return this.anchor.equals(p.anchor)};this.paint=function(p){p=p||{};var L=p.timestamp;if(!L||b.timestamp!==L){var z=p.anchorPoint,aa=p.canvas,pa=p.connectorPaintStyle;if(z==null){z=p.offset||y[B];p=p.dimensions||r[B]; +if(z==null||p==null){W({elId:B,timestamp:L});z=y[B];p=r[B]}p={xy:[z.left,z.top],wh:p,element:b,timestamp:L};if(b.anchor.isDynamic)if(b.connections.length>0){z=b.connections[0];var ka=z.endpoints[0]==b?1:0,fa=ka==0?z.sourceId:z.targetId,la=y[fa];fa=r[fa];p.txy=[la.left,la.top];p.twh=fa;p.tElement=z.endpoints[ka]}z=b.anchor.compute(p)}h.paint(z,b.anchor.getOrientation(),aa||b.canvas,n,pa||n);b.timestamp=L}};this.removeConnection=this.detach;if(a.isSource&&c.CurrentLibrary.isDragSupported(m)){var P= +null;var E=d=null,T=false,R=null,M=a.dragOptions||{},Q=c.extend({},c.CurrentLibrary.defaultDragOptions);M=c.extend(Q,M);M.scope=M.scope||b.scope;Q=c.CurrentLibrary.dragEvents.start;var V=c.CurrentLibrary.dragEvents.stop,Z=c.CurrentLibrary.dragEvents.drag;M[Q]=U(M[Q],function(){E=b.connections.length0)for(var h=0;h0)for(var n=0;n0?q(h,m)!=-1:true){b[m]=[];for(d=0;d< +u[m].length;d++){var A=u[m][d];(n.length>0?q(n,A.sourceId)!=-1:true)&&(a.length>0?q(a,A.targetId)!=-1:true)&&b[m].push({sourceId:A.sourceId,targetId:A.targetId,source:A.source,target:A.target,sourceEndpoint:A.endpoints[0],targetEndpoint:A.endpoints[1],connection:A})}}return b};this.getDefaultScope=function(){return j};this.getEndpoint=function(a){return x[a]};this.getId=X;this.hide=function(a){ia(a,"none")};this.makeAnchor=function(a,b){if(arguments.length==0)return null;var d={};if(arguments.length== +1){var h=arguments[0];if(h.compute&&h.getOrientation)return h;else if(typeof h=="string")return l.Anchors[arguments[0]]();else if(h.constructor==Array)return c.makeAnchor.apply(this,h);else typeof arguments[0]=="object"&&c.extend(d,a)}else{d={x:a,y:b};if(arguments.length>=4)d.orientation=[arguments[2],arguments[3]];if(arguments.length==6)d.offsets=[arguments[4],arguments[5]]}h=new ja(d);h.clone=function(){return new ja(d)};return h};this.makeAnchors=function(a){for(var b=[],d=0;d0?1:-1,C=Math.abs(j*Math.sin(y));if(e>s)C*=-1;var I=Math.abs(j*Math.cos(y));if(k>g)I*=-1;return[f[0]+q*I,f[1]+q*C]};this.perpendicularToPathAt=function(r,j,f){r=o.pointAlongPathFrom(r,j);j=o.gradientAtPoint(r.location);var q=Math.atan(-1/j);j=f/2*Math.sin(q);f=f/2*Math.cos(q);return[[r[0]+f,r[1]+j],[r[0]-f,r[1]-j]]};this.createGradient=function(r,j){return j.createLinearGradient(r[4],r[5],r[6],r[7])}};jsPlumb.Connectors.Bezier=function(o){var l=this;this.majorAnchor=o||150;this.minorAnchor=10; +var w=null;this._findControlPoint=function(f,q,C,I,H){I=I.getOrientation();H=H.getOrientation();var G=[],J=l.majorAnchor,v=l.minorAnchor;if(I[0]!=H[0]||I[1]==H[1]){H[0]==0?G.push(C[0]s)s=q;if(f<0){e+=f;f=Math.abs(f);s+=f;t[0]+=f;u+=f;y+=f;x[0]+=f}f=Math.min(Math.min(k,_ty),Math.min(t[1],x[1]));q=Math.max(Math.max(k,_ty),Math.max(t[1],x[1])); +if(q>r)r=q;if(f<0){g+=f;f=Math.abs(f);r+=f;t[1]+=f;k+=f;_ty+=f;x[1]+=f}if(G&&ss)s=r}g=s;s=k.measureText("M").width;u=l.labelStyle.padding||0.25;w=g+2*g*u;t=e.length*s+2*s*u;g=e.length*s;k.restore();e={width:w,height:t,lines:e,oneLine:s,padding:u,textHeight:g}}if(typeof l.label!="function")l.cachedDimensions=e;return e};this.computeMaxSize=function(k,e){var g=y(e);return g.width?Math.max(g.width,g.height)*1.5:0};this.draw=function(k, +e){var g=y(e);if(g.width){var s=k.pointOnPath(l.location);if(l.labelStyle.font)e.font=l.labelStyle.font;e.fillStyle=l.labelStyle.fillStyle?l.labelStyle.fillStyle:"rgba(0,0,0,0)";e.fillRect(s[0]-g.width/2,s[1]-g.height/2,g.width,g.height);if(l.labelStyle.color)e.fillStyle=l.labelStyle.color;e.textBaseline="middle";e.textAlign="center";for(i=0;i0){e.strokeStyle=l.labelStyle.borderStyle|| +"black";e.strokeRect(s[0]-g.width/2,s[1]-g.height/2,g.width,g.height)}}}};jsPlumb.Overlays.Image=function(o){var l=this;this.location=o.location||0.5;this.img=new Image;var w=null,t=null,x,u,y=o.events||{};this.img.onload=function(){l.ready=true};this.img.src=o.src||o.url;t=window.setInterval(function(){if(l.ready){window.clearInterval(t);w=document.createElement("img");w.src=l.img.src;w.style.position="absolute";w.style.display="none";w.className="_jsPlumb_overlay";document.body.appendChild(w);for(var e in y)jsPlumb.CurrentLibrary.bind(w, +e,y[e]);if(x&&u){k(x,u);x=u=null}}},250);this.computeMaxSize=function(){return[l.img.width,l.img.height]};var k=function(e,g){var s=e.pointOnPath(l.location),r=jsPlumb.CurrentLibrary.getElementObject(g.canvas);r=jsPlumb.CurrentLibrary.getOffset(r);jsPlumb.CurrentLibrary.setOffset(w,{left:r.left+s[0]-l.img.width/2,top:r.top+s[1]-l.img.height/2});w.style.display="block"};this.draw=function(e,g){if(l.ready)k(e,g);else{x=e;u=g}}}})(); +(function(O){O.fn.plumb=function(c){c=O.extend({},c);return this.each(function(){var o=O.extend({source:O(this)},c);jsPlumb.connect(o)})};O.fn.detach=function(c){return this.each(function(){if(c){var o=O(this).attr("id");if(typeof c=="string")c=[c];for(var l=0;l0?1:-1;for(var G=1;G<=5;G++){f=g[G].y==0?0:g[G].y>0?1:-1;f!=H&&C++;H=f}switch(C){case 0:g=0;break a;case 1:var J,v,S;C=g[0].y-g[5].y;f=g[5].x-g[0].x;H=g[0].x*g[5].y-g[5].x*g[0].y;G=max_distance_below=0;for(S=1;S<5;S++){J=C*g[S].x+f*g[S].y+H;if(J>G)G=J;else if(J0?1:-1,f=null;r endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + var endpointsByElement = {}; + var endpointsByUUID = {}; + var connectionsByScope = {}; + var offsets = {}; + var offsetTimestamps = {}; + var floatingConnections = {}; + var draggableStates = {}; + var _draggableByDefault = true; + var sizes = []; + var listeners = {}; // a map: keys are event types, values are lists of listeners. + var DEFAULT_SCOPE = 'DEFAULT'; + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + var _findIndex = function(a, v, b, s) { + var _eq = function(o1, o2) { + if (o1 === o2) + return true; + else if (typeof o1 == 'object' && typeof o2 == 'object') { + var same = true; + for ( var propertyName in o1) { + if (!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for ( var propertyName in o2) { + if (!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for ( var i = +b || 0, l = a.length; i < l; i++) { + if (_eq(a[i], v)) + return i; + } + return -1; + }; + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }; + + /** + * appends an element to the given parent, or the document body if no + * parent given. + */ + var _appendElement = function(canvas, parent) { + if (!parent) + document.body.appendChild(canvas); + else + jsPlumb.CurrentLibrary.appendElement(canvas, parent); + }; + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + var _timestamp = function() { + return "" + (new Date()).getTime(); + }; + + /** + * Draws an endpoint and its connections. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + var _draw = function(element, ui, timestamp) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (!timestamp) timestamp = _timestamp(); + if (endpoints) { + _updateOffset( { elId : id, offset : ui, recalc : false, timestamp : timestamp }); // timestamp is checked against last update cache; it is + // valid for one paint cycle. + var myOffset = offsets[id], myWH = sizes[id]; + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + var l = endpoints[i].connections; + for ( var j = 0; j < l.length; j++) { + l[j].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); // ...paint each connection. + // then, check for dynamic endpoint; need to repaint it. + var oIdx = l[j].endpoints[0] == endpoints[i] ? 1 : 0; + if (l[j].endpoints[oIdx].anchor.isDynamic && !l[j].endpoints[oIdx].isFloating()) { + var oId = oIdx == 0 ? l[j].sourceId : l[j].targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + // TODO i still want to make this faster. + var anchorLoc = l[j].endpoints[oIdx].anchor.compute( { + xy : [ oOffset.left, oOffset.top ], + wh : oWH, + element : l[j].endpoints[oIdx], + txy : [ myOffset.left, myOffset.top ], + twh : myWH, + tElement : endpoints[i] + }); + l[j].endpoints[oIdx].paint({ anchorLoc : anchorLoc }); + } + } + } + } + }; + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + var _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }; + + /** + * + */ + var _log = function(msg) { + if (_currentInstance.logEnabled && typeof console != "undefined") + console.log(msg); + }; + + /** + * gets the named attribute from the given element (id or element + * object) + */ + var _getAttribute = function(el, attName) { + return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); + }; + + var _setAttribute = function(el, attName, attValue) { + jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); + }; + + var _addClass = function(el, clazz) { + jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); + }; + + var _removeClass = function(el, clazz) { + jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); + }; + + var _getElementObject = function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el); + }; + + var _getOffset = function(el) { + return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); + }; + + var _getSize = function(el) { + return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); + }; + + /** + * gets an id for the given element, creating and setting one if + * necessary. + */ + var _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id) { + // check if fixed uuid parameter is given + if (arguments.length == 2) + id = uuid; + else + id = "_jsPlumb_" + _timestamp(); + _setAttribute(ele, "id", id); + } + return id; + }; + + /** + * gets an Endpoint by uuid. + */ + var _getEndpoint = function(uuid) { + return endpointsByUUID[uuid]; + }; + + /** + * inits a draggable if it's not already initialised. + */ + var _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + } + } + }; + + /** + * helper to create a canvas. + * + * @param clazz + * optional class name for the canvas. + */ + var _newCanvas = function(clazz, parent, uuid) { + var canvas = document.createElement("canvas"); + _appendElement(canvas, parent); + canvas.style.position = "absolute"; + if (clazz) canvas.className = clazz; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + _getId(canvas, uuid); + if (ie) { + // for IE we have to set a big canvas size. actually you can + // override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas + // you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + var _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }; + /** + * perform an operation on all elements. + */ + var _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }; + /** + * helper to remove an element from the DOM. + */ + var _removeElement = function(element, parent) { + if (element != null) { + if (!parent) { + try { + document.body.removeChild(element); + } catch (e) { + } + } else { + jsPlumb.CurrentLibrary.removeElement(element, parent); + } + } + }; + /** + * helper to remove a list of elements from the DOM. + */ + var _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }; + /** + * helper method to remove an item from a list. + */ + var _removeFromList = function(map, key, value) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete (l[i]); + l.splice(i, 1); + return true; + } + } + return false; + }; + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }; + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + var _setVisible = function(el, state) { + _operation(_getAttribute(el, "id"), function(jpc) { + jpc.canvas.style.display = state; + }); + }; + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + var _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }; + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + var _toggleVisible = function(elId) { + _operation(elId, function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }; + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we use the + * offset, outerWidth and outerHeight methods to get the current values. + */ + var _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return; + } + if (recalc || offset == null) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } else { + offsets[elId] = offset; + } + }; + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + */ + var _wrap = function(wrappedFunction, newFunction) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log('jsPlumb function failed : ' + e); + } + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log('wrapped function failed : ' + e); + } + return r; + }; + }; + + /* + * Class: Anchor Models a position relative to the origin of some + * element that an Endpoint can be located. + */ + /* + * Function: Anchor + * + * Constructor for the Anchor class + * + * Parameters: + * - x : the x location of the anchor as a fraction of the total width. - + * y : the y location of the anchor as a fraction of the total height. - + * orientation : an [x,y] array indicating the general direction a + * connection from the anchor should go in. for more info on this, see + * the documentation, or the docs in jquery-jsPlumb-defaults-XXX.js for + * the default Anchors. - offsets : an [x,y] array of fixed offsets that + * should be applied after the x,y position has been figured out. may be + * null. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null; + var lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + if (timestamp && timestamp === self.timestamp) { + return lastReturnValue; + } + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + var container = element ? element.container : null; + var containerAdjustment = { left : 0, top : 0 }; + if (container != null) { + var eo = _getElementObject(container); + var o = _getOffset(eo); + var sl = jsPlumb.CurrentLibrary.getScrollLeft(eo); + var st = jsPlumb.CurrentLibrary.getScrollTop(eo); + containerAdjustment.left = o.left - sl; + containerAdjustment.top = o.top - st; + lastReturnValue[0] = lastReturnValue[0] - containerAdjustment.left; + lastReturnValue[1] = lastReturnValue[1] - containerAdjustment.top; + } + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function() { + return orientation; + }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { + return lastReturnValue; + }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. + * + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(_getElementObject(refCanvas)); + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + var orientation = null; + var _lastResult = null; + + this.compute = function(params) { + var xy = params.xy, el = params.element; + var result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + if (el.container != null) { + var o = _getOffset(el.container); + result[0] = result[0] - o.left; + result[1] = result[1] - o.top; + } + _lastResult = result; + return result; + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { + orientation = null; + }; + + this.getCurrentLocation = function() { + return _lastResult; + }; + }; + + /* + * Class: DynamicAnchor + * + * An Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements - a useful + * feature for some applications. + * + * there has been a request to make the anchor selection process + * pluggable: closest centers does not work for everyone. + */ + var DynamicAnchor = function(anchors) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = anchors || []; + var _convert = function(anchor) { + return anchor.constructor == Array ? jsPlumb.makeAnchor(anchor) : anchor; + }; + for (var i = 0; i < _anchors.length; i++) + _anchors[i] = _convert(_anchors[i]); + + this.addAnchor = function(anchor) { + _anchors.push(_convert(anchor)); + }; + this.getAnchors = function() { return _anchors; }; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null; + var _curIndex = _anchors.length > 0 ? 0 : -1; + this.locked = false; + var self = this; + + var _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }; + + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp, txy = params.txy, twh = params.twh, tElement = params.tElement; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < _anchors.length; i++) { + var d = _distance(_anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + _curAnchor = _anchors[minIdx]; + var pos = _curAnchor.compute(params); + return pos; + }; + + this.getCurrentLocation = function() { + var cl = _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + return cl; + }; + + this.getOrientation = function() { + return _curAnchor != null ? _curAnchor.getOrientation() : [ 0, 0 ]; + }; + + this.over = function(anchor) { + if (_curAnchor != null) + _curAnchor.over(anchor); + }; + + this.out = function() { + if (_curAnchor != null) + _curAnchor.out(); + }; + }; + + /* + * Class:Connection + * + * The connecting line between two Endpoints. + * + */ + var Connection = function(params) { + + EventGenerator.apply(this); + // ************** get the source and target and register the + // connection. ******************* + var self = this; + var id = new String('_jsplumb_c_' + (new Date()).getTime()); + this.getId = function() { return id; }; + this.container = params.container || _currentInstance.Defaults.Container; // may be null; we will append to the body if so. + // get source and target as jQuery objects + this.source = _getElementObject(params.source); + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. + if (params.sourceEndpoint) this.source = params.sourceEndpoint.getElement(); + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + this.sourceId = _getAttribute(this.source, "id"); + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + this.scope = params.scope; // scope may have been passed in to the connect call. + // if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. init endpoints + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams) { + if (anchorParams) + return jsPlumb.makeAnchor(anchorParams); + }; + var prepareEndpoint = function(existing, index, params, element) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint + || new jsPlumb.Endpoints.Dot(); + if (ep.constructor == String) ep = new jsPlumb.Endpoints[ep](); + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + var a = params.anchors ? params.anchors[index] : _makeAnchor(_currentInstance.Defaults.Anchors[index]) || _makeAnchor(jsPlumb.Defaults.Anchors[index]) || _makeAnchor(_currentInstance.Defaults.Anchor) || _makeAnchor(jsPlumb.Defaults.Anchor) || _makeAnchor("BottomCenter"); + var u = params.uuids ? params.uuids[index] : null; + var e = new Endpoint( { style : es, endpoint : ep, connections : [ self ], uuid : u, anchor : a, source : element, container : self.container }); + self.endpoints[index] = e; + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || _currentInstance.Defaults.Connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + if (this.connector.constructor == String) this.connector = new jsPlumb.Connectors[this.connector](); // lets you use a string as shorthand. + this.paintStyle = this.endpoints[0].connectorStyle || this.endpoints[1].connectorStyle || params.paintStyle || _currentInstance.Defaults.PaintStyle || jsPlumb.Defaults.PaintStyle; + this.backgroundPaintStyle = this.endpoints[0].connectorBackgroundStyle || this.endpoints[1].connectorBackgroundStyle || params.backgroundPaintStyle || _currentInstance.Defaults.BackgroundPaintStyle || jsPlumb.Defaults.BackgroundPaintStyle; + + // init overlays + this.overlays = params.overlays || []; + this.addOverlay = function(overlay) { + overlays.push(overlay); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + this.labelStyle = params.labelStyle || _currentInstance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + if (this.label) { + this.overlays.push(new jsPlumb.Overlays.Label( { + labelStyle : this.labelStyle, + label : this.label + })); + } + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + // functions for mouse hover/select functionality + this.distanceFrom = function(point) { + return self.connector.distanceFrom(point); + }; + + this.setLabel = function(l) { + self.label = l; + _currentInstance.repaint(self.source); + }; + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var otherOffset = offsets[this.targetId]; + otherWH = sizes[this.targetId]; + // todo: why not fold this and the paint call that follows into one + // call? + var anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1] + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0] + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc }); + + var canvas = _newCanvas(jsPlumb.connectorClass, self.container); + this.canvas = canvas; + + /* + * Function: paint paints the connection. Parameters: - elId Id of + * the element that is in motion - ui current library's event system + * ui object (present if we came from a drag to get here) - recalc + * whether or not to recalculate element sizes. this is true if a + * repaint caused this to be painted. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp; + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the + // two references. + var swap = false;// !(elId == this.sourceId); + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + if (this.canvas.getContext) { + _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }); + _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + var myOffset = offsets[sId], otherOffset = offsets[tId], myWH = sizes[sId], otherWH = sizes[tId]; + var ctx = canvas.getContext('2d'); + var sAnchorP = this.endpoints[sIdx].anchor.getCurrentLocation(); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.getCurrentLocation(); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + + // paint overlays + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + var s = o.computeMaxSize(self.connector, ctx); + if (s > maxSize) + maxSize = s; + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, this.paintStyle.lineWidth, maxSize); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + + var _paintOneStyle = function(ctx, paintStyle) { + ctx.save(); + jsPlumb.extend(ctx, paintStyle); + if (paintStyle.gradient && !ie) { + var g = self.connector.createGradient(dim, ctx, (elId == this.sourceId)); + for ( var i = 0; i < paintStyle.gradient.stops.length; i++) + g.addColorStop(paintStyle.gradient.stops[i][0], paintStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + self.connector.paint(dim, ctx); + ctx.restore(); + }; + + // first check for the background style + if (this.backgroundPaintStyle != null) { + _paintOneStyle(ctx, this.backgroundPaintStyle); + } + _paintOneStyle(ctx, this.paintStyle); + + // paint overlays + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + o.draw(self.connector, ctx); + } + } + }; + + // TODO: should this take a timestamp? probably. it reduces the + // amount of time + // spent figuring out anchor locations. + this.repaint = function() { + this.paint({ elId : this.sourceId, recalc : true }); + }; + + _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide + // whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + }; + + /* + * Class: Endpoint Models an endpoint. Can have one to N connections + * emanating from it (although how to handle that in the UI is a very + * good question). also has a Canvas and paint style. + */ + + /* + * Function: Endpoint This is the Endpoint class constructor. + * Parameters: anchor - anchor for the endpoint, of type jsPlumb.Anchor. + * may be null. endpoint - endpoint object, of type jsPlumb.Endpoint. + * may be null. style - endpoint style, a js object. may be null. source - + * element the endpoint is attached to, of type jquery object. Required. + * canvas - canvas element to use. may be, and most often is, null. + * connections - optional list of connections to configure the endpoint + * with. isSource - boolean. indicates the endpoint can act as a source + * of new connections. optional. dragOptions - if isSource is set to + * true, you can supply arguments for the jquery draggable method. + * optional. connectionStyle - if isSource is set to true, this is the + * paint style for connections from this endpoint. optional. connector - + * optional connector type to use. isTarget - boolean. indicates the + * endpoint can act as a target of new connections. optional. + * dropOptions - if isTarget is set to true, you can supply arguments + * for the jquery droppable method. optional. reattach - optional + * boolean that determines whether or not the connections reattach after + * they have been dragged off an endpoint and left floating. defaults to + * false: connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + params = params || {}; + var self = this; + var id = new String('_jsplumb_e_' + (new Date()).getTime()); + this.getId = function() { return id; }; + self.anchor = params.anchor ? jsPlumb.makeAnchor(params.anchor) : jsPlumb.makeAnchor("TopCenter"); + var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot(); + if (_endpoint.constuctor == String) _endpoint = new jsPlumb.Endpoints[_endpoint](); + self.endpoint = _endpoint; + var _style = params.style || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + this.connectorStyle = params.connectorStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + var _element = params.source, _uuid = params.uuid; + var floatingEndpoint = null, inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + this.container = params.container || _currentInstance.Defaults.Container || jsPlumb.Defaults.Container; + var _elementId = _getAttribute(_element, "id"); + var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of. + this.canvas = params.canvas || _newCanvas(jsPlumb.endpointClass, this.container, params.uuid); + this.connections = params.connections || []; + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + var _reattach = params.reattach || false; + this.dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + this.addConnection = function(connection) { + self.connections.push(connection); + }; + this.detach = function(connection, ignoreTarget) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + // this avoids a circular loop + if (!ignoreTarget) { + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + t.detach(connection, true); + } + } + _removeElement(connection.canvas, connection.container); + _removeFromList(connectionsByScope, connection.scope, connection); + fireDetachEvent(connection); + }; + + /** + * detaches all connections this Endpoint has + */ + this.detachAll = function() { + while (self.connections.length > 0) { + self.detach(self.connections[0]); + } + }; + + /** + * removes any connections from this Endpoint that are connected to + * the given target endpoint. + * + * @param targetEndpoint + */ + this.detachFrom = function(targetEndpoint) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + self.detach(c[i]); + } + }; + + /** + * detach this Endpoint from the Connection, but leave the Connection alive. used when dragging. + */ + this.detachFromConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /** + * returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /** + * returns the UUID for this Endpoint, if there is one. + */ + this.getUuid = function() { + return _uuid; + }; + + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + var e = new Endpoint( { anchor : self.anchor, source : _element, style : _style, endpoint : _endpoint }); + return e; + }; + + /** + * returns whether or not this endpoint is connected to the given + * endpoint. + * + * @param endpoint + * Endpoint to test. + * @since 1.1.1 + * + * todo: needs testing. will this work if the endpoint passed in is + * the source? + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + * + * @returns {Boolean} + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + var connectorSelector = function() { + return (self.connections.length < _maxConnections) ? null : self.connections[0]; + }; + + /** + * @returns whether or not the Endpoint can accept any more + * Connections. + */ + this.isFull = function() { + return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); + }; + + /** + * sets whether or not connnctions can be dragged from this Endpoint + * once it is full. you would use this in a UI in which you're going + * to provide some other way of breaking connections, if you need to + * break them at all. this property is by default true; use it in + * conjunction with the 'reattach' option on a connect call. + */ + this.setDragAllowedWhenFull = function(allowed) { + self.dragAllowedWhenFull = allowed; + }; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + /** + * paints the Endpoint, recalculating offset and anchor positions if + * necessary. + */ + this.paint = function(params) { + + params = params || {}; + var timestamp = params.timestamp; + if (!timestamp || self.timestamp !== timestamp) { + var ap = params.anchorPoint, canvas = params.canvas, connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + // do we always want to force a repaint here? i dont + // think so! + var xy = params.offset || offsets[_elementId]; + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (self.anchor.isDynamic) { + if (self.connections.length > 0) { + var c = self.connections[0]; + var oIdx = c.endpoints[0] == self ? 1 : 0; + var oId = oIdx == 0 ? c.sourceId : c.targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + } + ap = self.anchor.compute(anchorParams); + } + _endpoint.paint(ap, self.anchor.getOrientation(), canvas || self.canvas, _style, connectorPaintStyle || _style); + self.timestamp = timestamp; + } + }; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener + // maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + var start = function() { + jpc = connectorSelector(); + if (self.isFull() && !self.dragAllowedWhenFull) return false; + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + n = document.createElement("div"); + var nE = _getElementObject(n); + _appendElement(n, self.container); // + // create and assign an id, and initialize the offset. + var id = _getId(nE); + _updateOffset( { elId : id }); + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor( { reference : self.anchor, referenceCanvas : self.canvas }); + floatingEndpoint = new Endpoint({ style : { fillStyle : 'rgba(0,0,0,0)' }, endpoint : _endpoint, anchor : floatingAnchor, source : nE }); + + if (jpc == null) { + self.anchor.locked = true; + // create a connection. one end is this endpoint, the + // other is a floating endpoint. + jpc = new Connection( { + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : _getElementObject(_element), + target : _getElementObject(n), + anchors : [ self.anchor, floatingAnchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + connector : params.connector, // this can also be null. Connection will use the default. + overlays : self.connectorOverlays // new in 1.2.4. + }); + } else { + existingJpc = true; + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; + // detach from the connection while dragging is occurring. + self.detachFromConnection(jpc); + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId ]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId ]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + // lock the other endpoint; if it is dynamic it will not + // move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new + // floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // TODO unregister on stop? or will floating endpoint's + // destruction be assured. + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be + // dragging the source at any time + // before this connection is either discarded or made into a + // permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + }; + + var dragOptions = params.dragOptions || {}; + var defaultOpts = jsPlumb.extend( {}, jsPlumb.CurrentLibrary.defaultDragOptions); + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + var startEvent = jsPlumb.CurrentLibrary.dragEvents['start']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], + function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + jsPlumb.CurrentLibrary.setOffset(n, _ui); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements( [ n, floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (_reattach) { + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } else { + jpc.endpoints[1].detach(jpc); + jpc.endpoints[0].detach(jpc); + // TODO - delete floating endpoint? + } + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElement(jpc.canvas, self.container); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint(); + jpc.repaint(); + jpc = null; + delete floatingEndpoint; + delete inPlaceCopy; + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + } + + // connector target + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable), "dragId"); + var elId = _getAttribute(_getElementObject(draggable), "elId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + var oidx = idx == 0 ? 1 : 0; + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // todo test that the target is not full. + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + _addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + jsPlumb.repaint(elId); + _currentInstance.fireUpdate("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + } + // else there must be some cleanup required? or not? + + delete floatingConnections[id]; + }); + + dropOptions[overEvent] = _wrap(dropOptions[overEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute( _getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + }); + + jsPlumb.CurrentLibrary.initDroppable(_getElementObject(self.canvas), dropOptions); + } + + return self; + }; + + /* + * Property: Defaults These are the default settings for jsPlumb, that + * is what will be used if you do not supply specific pieces of + * information to the various API calls. A convenient way to implement + * your own look and feel can be to override these defaults by including + * a script somewhere after the jsPlumb include, but before you make any + * calls to jsPlumb, for instance in this example we set the PaintStyle + * to be a blue line of 27 pixels: > jsPlumb.Defaults.PaintStyle = { + * lineWidth:27, strokeStyle:'blue' } + */ + this.Defaults = { + Anchor : null, + Anchors : [ null, null ], + BackgroundPaintStyle : null, + Connector : null, + Container : null, + DragOptions : {}, + DropOptions : {}, + Endpoint : null, + Endpoints : [ null, null ], + EndpointStyle : { + fillStyle : null + }, + EndpointStyles : [ null, null ], + LabelStyle : { + fillStyle : "rgba(0,0,0,0)", + color : "black" + }, + LogEnabled : true, + MaxConnections : null, + // TODO: should we have OverlayStyle too? + PaintStyle : { + lineWidth : 10, + strokeStyle : 'red' + }, + Scope : "_jsPlumb_DefaultScope" + }; + + this.logEnabled = this.Defaults.LogEnabled; + + /* + * Property: connectorClass The CSS class to set on Connection canvas + * elements. This value is a String and can have multiple classes; the + * entire String is appended as-is. + */ + this.connectorClass = '_jsPlumb_connector'; + + /* + * Property: endpointClass The CSS class to set on Endpoint canvas + * elements. This value is a String and can have multiple classes; the + * entire String is appended as-is. + */ + this.endpointClass = '_jsPlumb_endpoint'; + + /* + * Property: overlayClass The CSS class to set on an Overlay that is an + * HTML element. This value is a String and can have multiple classes; + * the entire String is appended as-is. + */ + this.overlayClass = '_jsPlumb_overlay'; + + /* + * Property: Anchors Default jsPlumb Anchors. These are supplied in the + * file jsPlumb-defaults-x.x.x.js, which is merged in with the main + * jsPlumb script to form .jsPlumb-all-x.x.x.js. You can + * provide your own Anchors by supplying them in a script that is loaded + * after jsPlumb, for instance: > jsPlumb.Anchors.MyAnchor = { + * ....anchor code here. see the documentation. } + */ + this.Anchors = {}; + + /* + * Property: Connectors Default jsPlumb Connectors. These are supplied + * in the file jsPlumb-defaults-x.x.x.js, which is merged in with the + * main jsPlumb script to form .jsPlumb-all-x.x.x.js. You can + * provide your own Connectors by supplying them in a script that is + * loaded after jsPlumb, for instance: > jsPlumb.Connectors.MyConnector = { + * ....connector code here. see the documentation. } + */ + this.Connectors = {}; + + /* + * Property: Endpoints Default jsPlumb Endpoints. These are supplied in + * the file jsPlumb-defaults-x.x.x.js, which is merged in with the main + * jsPlumb script to form .jsPlumb-all-x.x.x.js. You can + * provide your own Endpoints by supplying them in a script that is + * loaded after jsPlumb, for instance: > jsPlumb.Endpoints.MyEndpoint = { + * ....endpoint code here. see the documentation. } + */ + this.Endpoints = {}; + + /* + * Property:Overlays Default jsPlumb Overlays such as Arrows and Labels. + * These are supplied in the file jsPlumb-defaults-x.x.x.js, which is + * merged in with the main jsPlumb script to form + * .jsPlumb-all-x.x.x.js. You can provide your own Overlays by + * supplying them in a script that is loaded after jsPlumb, for + * instance: > jsPlumb.Overlays.MyOverlay = { ....overlay code here. see + * the documentation. } + */ + this.Overlays = {}; + + /* + * Function: addEndpoint Adds an Endpoint to a given element. + * Parameters: target - Element to add the endpoint to. either an + * element id, or a selector representing some element. params - Object + * containing Endpoint options (more info required) Returns: The newly + * created Endpoint. See Also: + */ + this.addEndpoint = function(target, params) { + params = jsPlumb.extend( {}, params); + params.endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + params.endpointStyle = params.endpointStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + var el = _getElementObject(target), id = _getAttribute(el, "id"); + params.source = el; + _updateOffset({ elId : id }); + var e = new Endpoint(params); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + return e; + }; + + /* + * Function: addEndpoint Adds a list of Endpoints to a given element. + * Parameters: target - element to add the endpoint to. either an + * element id, or a selector representing some element. endpoints - List + * of objects containing Endpoint options. one Endpoint is created for + * each entry in this list. Returns: List of newly created Endpoints, + * one for each entry in the 'endpoints' argument. See Also: + * + */ + this.addEndpoints = function(target, endpoints) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + results.push(_currentInstance.addEndpoint(target, endpoints[i])); + } + return results; + }; + + /* + * Function: animate Wrapper around supporting library's animate + * function; injects a call to jsPlumb in the 'step' function (creating + * it if necessary). This only supports the two-arg version of the + * animate call in jQuery - the one that takes an 'options' object as + * the second arg. MooTools has only one method - a two arg one. Which + * is handy. Parameters: el - Element to animate. Either an id, or a + * selector representing the element. properties - The 'properties' + * argument you want passed to the standard jQuery animate call. options - + * The 'options' argument you want passed to the standard jQuery animate + * call. Returns: void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // you know, probably onComplete should repaint too. that will help + // keep up + // with fast animations. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + this.autoConnect = function(params) { + var sources = [], targets = [], _endpoint = params.endpoint || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle, _anchors = anchors || jsPlumb.Defaults.DynamicAnchors(); + var _addAll = function(s, t) { + for ( var i = 0; i < s.length; i++) + t.push(s[i]); + }; + var source = params.source, target = params.target, anchors = params.anchors; + if (typeof source == 'string') + sources.push(_getElementObject(source)); + else + _addAll(source, sources); + if (typeof target == 'string') + targets.push(_getElementObject(target)); + else + _addAll(target, targets); + var connectOptions = jsPlumb.extend(params, { source : null, target : null, anchors : null }); + for ( var i = 0; i < sources.length; i++) { + for ( var j = 0; j < targets.length; j++) { + var e1 = jsPlumb.addEndpoint(sources[i], jsPlumb.extend( { anchor : jsPlumb.makeDynamicAnchor(_anchors) }, _endpoint)); + var e2 = jsPlumb.addEndpoint(targets[j], jsPlumb.extend( { anchor : jsPlumb.makeDynamicAnchor(_anchors) }, _endpoint)); + _currentInstance.connect(jsPlumb.extend(connectOptions, { sourceEndpoint : e1, targetEndpoint : e2 })); + } + } + }; + + /* + * Function: connect Establishes a connection between two elements. + * Parameters: params - Object containing setup for the connection. see + * documentation. Returns: The newly created Connection. + */ + this.connect = function(params) { + var _p = jsPlumb.extend( {}, params); + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not + // full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log("could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log("could not add connection; target endpoint is full"); + return; + } + + var jpc = new Connection(_p); + // add to list of connections (by scope). + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + _currentInstance.fireUpdate("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + // force a paint + _draw(jpc.source); + + return jpc; + }; + + /* + Function: deleteEndpoint + + deletes an endpoint and removes all connections it has (which removes the connections from the other endpoints involved too) + + You can call this with either a string uuid, or an Endpoint + + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElement(endpoint.canvas, endpoint.container); + // remove from endpointsbyElement + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + delete endpoint; + } + }; + + /* + * Function: deleteEveryEndpoint + * + * Deletes every Endpoint in this instance + * of jsPlumb. Use this instead of removeEveryEndpoint now. + * + * Returns: void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc) { + _currentInstance.fireUpdate("jsPlumbConnectionDetached", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + }; + + /* + * Function: + * detach Removes a connection. takes either (source, target) (the old way, maintained for backwards compatibility), or a params + * object with various possible values. + * + * + * Parameters: + * source - id or element object of the first element in the connection. + * target - id or element object of the second element in the connection. + * + * OR + * + * params: + * { + * + * Returns: true if successful, false if not. + */ + this.detach = function(source, target) { + if (arguments.length == 2) { + var s = _getElementObject(source), sId = _getId(s); + var t = _getElementObject(target), tId = _getId(t); + _operation(sId, function(jpc) { + if ((jpc.sourceId == sId && jpc.targetId == tId) || (jpc.targetId == sId && jpc.sourceId == tId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + fireDetachEvent(jpc); + } + }); + } + // this is the new version of the method, taking a JS object like + // the connect method does. + else if (arguments.length == 1) { + if (arguments[0].constructor == Connection) { + arguments[0].endpoints[0].detachFrom(arguments[0].endpoints[1]); + fireDetachEvent(arguments[0]); + } + else if (arguments[0].connection) { + arguments[0].connection.endpoints[0].detachFrom(arguments[0].connection.endpoints[1]); + fireDetachEvent(arguments[0].connection); + } + else { + var _p = jsPlumb.extend( {}, source); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1])); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + sourceId = _getId(_p.source); + targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + fireDetachEvent(jpc); + } + }); + } + } + } + }; + + /* + * Function: detachAll Removes all an element's connections. Parameters: + * el - either the id of the element, or a selector for the element. + * Returns: void + */ + this.detachAllConnections = function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + var c = endpoints[i].connections.length; + if (c > 0) { + for ( var j = 0; j < c; j++) { + var jpc = endpoints[i].connections[0]; + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + fireDetachEvent(jpc); + } + } + } + } + }; + + /** + * @deprecated Use detachAllConnections instead. this will be removed in jsPlumb 1.3. + */ + this.detachAll = this.detachAllConnections; + + /* + * Function: detachEverything Remove all Connections from all elements, + * but leaves Endpoints in place. Returns: void See Also: + * + */ + this.detachEveryConnection = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + var c = endpoints[i].connections.length; + if (c > 0) { + for ( var j = 0; j < c; j++) { + var jpc = endpoints[i].connections[0]; + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + fireDetachEvent(jpc); + } + } + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + /** + * @deprecated use detachEveryConnection instead. this will be removed in jsPlumb 1.3. + */ + this.detachEverything = this.detachEveryConnection; + + /* + * Function: draggable initialises the draggability of some element or + * elements. Parameters: el - either an element id, a list of element + * ids, or a selector. options - options to pass through to the + * underlying library Returns: void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + * Function: extend Wraps the underlying library's extend functionality. + * Parameters: o1 - object to extend o2 - object to extend o1 with + * Returns: o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getConnections Gets all or a subset of connections + * currently managed by this jsPlumb instance. Parameters: options - a + * JS object that holds options defining what sort of connections you're + * looking for. Valid values are: scope this may be a String or a list + * of Strings. jsPlumb will only return Connections whose scope matches + * what this option defines. If you omit it, you will be given + * Connections of every scope. source may be a string or a list of + * strings; constraints results to have only Connections whose source is + * this object. target may be a string or a list of strings; constraints + * results to have only Connections whose target is this object. + * + * The return value is a dictionary in this format: + * { 'scope1': [ {sourceId:'window1', targetId:'window2', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:}, + * {sourceId:'window3', targetId:'window4', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:}, + * {sourceId:'window1', targetId:'window3', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:} ], + * 'scope2': [ {sourceId:'window1', targetId:'window3', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:} ] } + * + */ + this.getConnections = function(options) { + var r = {}; + options = options || {}; + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') + r.push(input); + else + r = input; + } + return r; + }; + var scopes = prepareList(options.scope); + var sources = prepareList(options.source); + var targets = prepareList(options.target); + var filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + r[i] = []; + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + r[i].push({ sourceId : c.sourceId, targetId : c.targetId, source : c.source, target : c.target, sourceEndpoint : c.endpoints[0], targetEndpoint : c.endpoints[1], connection : c }); + } + } + } + return r; + }; + + /* + * Function: getDefaultScope Gets the default scope for connections and + * endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + * Function: getEndpoint gets an Endpoint by UUID Parameters: uuid - the + * UUID for the Endpoint Returns: Endpoint with the given UUID, null if + * nothing found. + */ + this.getEndpoint = _getEndpoint; + + /* + * gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + + /* + * Function: hide Sets an element's connections to be hidden. + * Parameters: el - either the id of the element, or a selector for the + * element. Returns: void + */ + this.hide = function(el) { + _setVisible(el, "none"); + }; + + /* + * Function: makeAnchor Creates an anchor with the given params. + * Parameters: x - the x location of the anchor as a fraction of the + * total width. y - the y location of the anchor as a fraction of the + * total height. xOrientation - value indicating the general direction a + * connection from the anchor should go in, in the x direction. + * yOrientation - value indicating the general direction a connection + * from the anchor should go in, in the y direction. xOffset - a fixed + * offset that should be applied in the x direction that should be + * applied after the x position has been figured out. optional. defaults + * to 0. yOffset - a fixed offset that should be applied in the y + * direction that should be applied after the y position has been + * figured out. optional. defaults to 0. + * -- OR -- + * + * params - {x:..., y:..., xOrientation etc } + * -- OR FROM 1.2.4 --- + * + * name - the name of some Anchor in the _currentInstance.Anchors array. + * -- OR FROM 1.2.4 --- + * + * coords - a list of coords for the anchor, like you would pass to + * jsPlumb.makeAnchor (eg [0.5,0.5,0,-1] - an anchor in the center of + * some element, oriented towards the top of the screen) + * -- OR FROM 1.2.4 --- + * + * anchor - an existing anchor. just gets passed back. it's handy + * internally to have this functionality. + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed + // in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards + // compatibility if we are given only one value we assume it's a + // call in the old form. + if (arguments.length == 0) return null; + var params = {}; + if (arguments.length == 1) { + var specimen = arguments[0]; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; + // is it the name of an anchor type? + else if (typeof specimen == "string") return _currentInstance.Anchors[arguments[0]](); + // is it an array of coordinates? + else if (specimen.constructor == Array) return jsPlumb.makeAnchor.apply(this, specimen); + // last we try the backwards compatibility stuff. + else if (typeof arguments[0] == "object") jsPlumb.extend(params, x); + } else { + params = { x : x, y : y }; + if (arguments.length >= 4) params.orientation = [ arguments[2], arguments[3] ]; + if (arguments.length == 6) params.offsets = [ arguments[4], arguments[5] ]; + } + var a = new Anchor(params); + a.clone = function() { + return new Anchor(params); + }; + return a; + }; + + /** + * makes a list of anchors from the given list of types, eg + * ["TopCenter", "RightMiddle", "BottomCenter"] + */ + this.makeAnchors = function(types) { + var r = []; + for ( var i = 0; i < types.length; i++) + r.push(_currentInstance.Anchors[types[i]]()); + return r; + }; + + this.makeDynamicAnchor = function(anchors) { + return new DynamicAnchor(anchors); + }; + + /* + * Function: repaint Repaints an element and its connections. This + * method gets new sizes for the elements before painting anything. + * Parameters: el - either the id of the element or a selector + * representing the element. Returns: void See Also: + */ + this.repaint = function(el) { + + var _processElement = function(el) { + _draw(_getElementObject(el)); + }; + + // support both lists... + if (typeof el == 'object') { + for ( var i = 0; i < el.length; i++) + _processElement(el[i]); + } // ...and single strings. + else + _processElement(el); + }; + + /* + * Function: repaintEverything Repaints all connections. Returns: void + * See Also: + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + * Function: removeAllEndpoints + * Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + * Parameters: el - either an element id, or a selector for an element. + * Returns: void + * See Also: + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + for ( var i in ebe) + _currentInstance.deleteEndpoint(ebe[i]); + endpointsByElement[elId] = []; + }; + + /** + * Function: removeEveryEndpoint + * Removes every Endpoint in this instance + * of jsPlumb. + * + * Returns: void + * + * See Also: + * + * @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /** + * Function: removeEndpoint Removes the given Endpoint from the given + * element. Parameters: el - either an element id, or a selector for an + * element. endpoint - Endpoint to remove. this is an Endpoint object, + * such as would have been returned from a call to addEndpoint. Returns: + * void See Also: + * + * @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + /** + * Function:reset + * removes all endpoints and connections and clears the + * listener list. to keep listeners just call jsPlumb.deleteEveryEndpoint. + */ + this.reset = function() { + this.deleteEveryEndpoint(); + this.clearListeners(); + }; + + /* + * Function: setAutomaticRepaint Sets/unsets automatic repaint on window + * resize. Parameters: value - whether or not to automatically repaint + * when the window is resized. Returns: void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + * Function: setDefaultNewCanvasSize Sets the default size jsPlumb will + * use for a new canvas (we create a square canvas so one value is all + * that is required). This is a hack for IE, because ExplorerCanvas + * seems to need for a canvas to be larger than what you are going to + * draw on it at initialisation time. The default value of this is 1200 + * pixels, which is quite large, but if for some reason you're drawing + * connectors that are bigger, you should adjust this value + * appropriately. Parameters: size - The default size to use. jsPlumb + * will use a square canvas so you need only supply one value. Returns: + * void + */ + this.setDefaultNewCanvasSize = function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }; + + /* + * Function: setDefaultScope Sets the default scope for connections and + * endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable Sets whether or not a given element is + * draggable, regardless of what any plumb command may request. + * Parameters: el - either the id for the element, or a selector + * representing the element. Returns: void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setDraggableByDefault Sets whether or not elements are + * draggable by default. Default for this is true. Parameters: draggable - + * value to set Returns: void + */ + this.setDraggableByDefault = function(draggable) { + _draggableByDefault = draggable; + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction Sets the function to fire when the + * window size has changed and a repaint was fired. Parameters: f - + * Function to execute. Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: show Sets an element's connections to be visible. + * Parameters: el - either the id of the element, or a selector for the + * element. Returns: void + */ + this.show = function(el) { + _setVisible(el, "block"); + }; + + /* + * Function: sizeCanvas Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * Parameters: x - [int] x position for the Canvas origin y - [int] y + * position for the Canvas origin w - [int] width of the canvas h - + * [int] height of the canvas Returns: void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible Toggles visibility of an element's + * connections. Parameters: el - either the element's id, or a selector + * representing the element. Returns: void, but should be updated to + * return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable Toggles draggability (sic) of an element's + * connections. Parameters: el - either the element's id, or a selector + * representing the element. Returns: The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload Unloads jsPlumb, deleting all storage. You should + * call this from an onunload attribute on the element Returns: + * void + */ + this.unload = function() { + delete endpointsByElement; + delete endpointsByUUID; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + }; + + /* + * Function: wrap Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. Parameters: + */ + this.wrap = _wrap; + + EventGenerator.apply(this); + this.addListener = this.bind; + + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(); + if (_defaults) + jsPlumb.extend(j.Defaults, _defaults); + return j; + }; +})(); +/* +* jsPlumb-defaults-1.2.4-RC1 +* +* This script contains the default Anchors, Endpoints, Connectors and Overlays for jsPlumb. It should be used with jsPlumb 1.1.0 and above; +* prior to version 1.1.0 of jsPlumb the defaults were included inside the main script. +* +* NOTE: for production usage you should use jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, +* in a minified file. +* +* Dual licensed under MIT and GPL2. +*/ + +(function() { + + var ie = !!!document.createElement('canvas').getContext; + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * getOrientation : function() { return [ox, oy]; } + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + var _curryAnchor = function(x,y,ox,oy) { + return function() { + return jsPlumb.makeAnchor(x,y,ox,oy); + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1); + + + jsPlumb.Defaults.DynamicAnchors = function() { + return jsPlumb.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"]); + }; + jsPlumb.Anchors["AutoDefault"] = function() { return jsPlumb.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors()); }; + + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + var currentPoints = null; + var _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + + _dx = _tx - _sx, _dy = (_ty - _sy); + _m = _dy / _dx, _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + + return currentPoints; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + var xp = _sx + (location * _dx); + var yp = _m == Infinity ? xp + _b : (_m * xp) + _b; + return [xp, yp]; + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { return _m; }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + var orientation = distance > 0 ? 1 : -1; + var y = Math.abs(distance * Math.sin(_theta)); + if (_sy > _ty) y = y * -1; + var x = Math.abs(distance * Math.cos(_theta)); + if (_sx > _tx) x = x * -1; + return [p[0] + (orientation * x), p[1] + (orientation * y)]; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, distance, length) { + var p = self.pointAlongPathFrom(location, distance); + var m = self.gradientAtPoint(p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [[p[0] + x, p[1] + y], [p[0] - x, p[1] - y]]; + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + /** + * This Connector draws a Bezier curve with two control points. + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + jsPlumb.Connectors.Bezier = function(curviness) { + var self = this; + this.majorAnchor = curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _sx, _sy, _canvasX, _canvasY, _w, _h; + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) + { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx); var minx2 = Math.min(_CP[0], _CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(_sx,_tx); var maxx2 = Math.max(_CP[0], _CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty); var miny2 = Math.min(_CP[1], _CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(_sy,_ty); var maxy2 = Math.max(_CP[1], _CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + this.paint = function(d, ctx) { + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the distance the given point is from the curve. not enabled for 1.2.3. didnt make the cut. next time. + * + this.distanceFrom = function(point) { + var curve = [ {x:currentPoints[4], y:currentPoints[5]}, + {x:currentPoints[8], y:currentPoints[9]}, + {x:currentPoints[10], y:currentPoints[11]}, + {x:currentPoints[6], y:currentPoints[7]}]; + return (jsBezier.distanceFromCurve(point, curve)); + };*/ + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, distance, length) { + return jsBezier.perpendicularToCurveAt(_makeCurve(), location, distance, length); + }; + + this.createGradient = function(dim, ctx, swap) { + return (swap) ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + + /** + * Types of endpoint UIs. we supply four - a circle of default radius 10px, a rectangle of + * default size 20x20, an image (with no default), and a Triangle, of default size 15. + * you can supply others of these if you want to - see the documentation for a howto. + */ + + /** + * a round endpoint, with default radius 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + } + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + if (endpointStyle.gradient && !ie) { + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * A Rectangular endpoint, with default size 20x20. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + jsPlumb.Endpoints.Triangle = function(params) { + + params = params || { width:15, height:15 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - width/2; + var y = anchorPoint[1] - height/2; + + jsPlumb.sizeCanvas(canvas, x, y, width, height); + + var ctx = canvas.getContext('2d'); + var offsetX = 0, offsetY = 0, angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = endpointStyle.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' or, since 1.2.4, a 'src' property in the params object.. + */ + jsPlumb.Endpoints.Image = function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.src || params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + }; + + /** + * An arrow overlay. you can provide: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + */ + jsPlumb.Overlays.Arrow = function(params) { + params = params || {}; + var self = this; + var length = params.length || 20; + var width = params.width || 20; + var fillStyle = params.fillStyle || "black"; + var strokeStyle = params.strokeStyle; + var lineWidth = params.lineWidth || 1; + this.loc = params.location || 0.5; + // how far along the arrow the lines folding back in come to. default is 62.3%. + var foldback = params.foldback || 0.623; + var _getFoldBackPoint = function(connector, loc) { + if (foldback == 0.5) return connector.pointOnPath(loc); + else { + var adj = 0.5 - foldback; // we calculate relative to the center + return connector.pointAlongPathFrom(loc, length * adj); + } + }; + + this.computeMaxSize = function() { return width * 1.5; } + + this.draw = function(connector, ctx) { + // this is the arrow head position + var hxy = connector.pointAlongPathFrom(self.loc, length / 2); + // this is the center of the tail + var txy = connector.pointAlongPathFrom(self.loc, -length / 2), tx = txy[0], ty = txy[1]; + // this is the tail vector + var tail = connector.perpendicularToPathAt(self.loc, -length / 2, width); + // this is the point the tail goes in to + var cxy = _getFoldBackPoint(connector, self.loc); + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(hxy[0], hxy[1]); + ctx.lineTo(tail[0][0], tail[0][1]); + ctx.lineTo(cxy[0], cxy[1]); + ctx.lineTo(tail[1][0], tail[1][1]); + ctx.lineTo(hxy[0], hxy[1]); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + + /** + * a basic arrow. this is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. See Arrow for params. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + /** + * a diamond. like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. See Arrow for params. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40; + var p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + /** + * A Label overlay. Params you can provide: + * + * labelStyle - js object containing style instructions for the label. defaults to jsPlumb.Defaults.LabelStyle. + * label - the label to paint. may be a string or a function that returns a string. nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * borderWidth - width of a border to paint. defaults to zero. + * borderStyle - strokeStyle to use when painting the border, if necessary. + */ + jsPlumb.Overlays.Label = function(params) { + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + var self = this; + var labelWidth = null, labelHeight = null, labelText = null, labelPadding = null; + this.location = params.location || 0.5; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var _textDimensions = function(ctx) { + if (self.cachedDimensions) return self.cachedDimensions; // return cached copy if we can. if we add a setLabel function remember to clear the cache. + labelText = typeof self.label == 'function' ? self.label(self) : self.label; + var d = {}; + if (labelText) { + var lines = labelText.split("\n"); + ctx.save(); + if (self.labelStyle.font) ctx.font = self.labelStyle.font; + var t = _widestLine(lines, ctx); + // a fake text height measurement: use the width of upper case M + var h = ctx.measureText("M").width; + labelPadding = self.labelStyle.padding || 0.25; + labelWidth = t + (2 * t * labelPadding); + labelHeight = (lines.length * h) + (2 * h * labelPadding); + var textHeight = lines.length * h; + ctx.restore(); + d = {width:labelWidth, height:labelHeight, lines:lines, oneLine:h, padding:labelPadding, textHeight:textHeight}; + } + if (typeof self.label != 'function') self.cachedDimensions = d; // cache it if we can. + return d; + }; + this.computeMaxSize = function(connector, ctx) { + var td = _textDimensions(ctx); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + var _widestLine = function(lines, ctx) { + var max = 0; + for (var i = 0; i < lines.length; i++) { + var t = ctx.measureText(lines[i]).width; + if (t > max) max = t; + } + return max; + }; + + this.draw = function(connector, ctx) { + var td = _textDimensions(ctx); + if (td.width) { + var cxy = connector.pointOnPath(self.location); + if (self.labelStyle.font) ctx.font = self.labelStyle.font; + if (self.labelStyle.fillStyle) + ctx.fillStyle = self.labelStyle.fillStyle; + else + ctx.fillStyle = "rgba(0,0,0,0)"; + ctx.fillRect(cxy[0] - (td.width / 2), cxy[1] - (td.height / 2) , td.width , td.height ); + + if (self.labelStyle.color) ctx.fillStyle = self.labelStyle.color; + ctx.textBaseline = "middle"; + ctx.textAlign = "center"; + for (i = 0; i < td.lines.length; i++) { + ctx.fillText(td.lines[i],cxy[0], cxy[1] - (td.textHeight / 2) + (td.oneLine/2) + (i*td.oneLine)); + } + + // border + if (self.labelStyle.borderWidth > 0) { + ctx.strokeStyle = self.labelStyle.borderStyle || "black"; + ctx.strokeRect(cxy[0] - (td.width / 2), cxy[1] - (td.height / 2) , td.width , td.height ); + } + } + }; + }; + + /** + * an image overlay. params may contain: + * + * location : proportion along the connector to draw the image. optional. + * src : image src. required. + * events : map of event names to functions; each event listener will be bound to the img. + */ + jsPlumb.Overlays.Image = function(params) { + var self = this; + this.location = params.location || 0.5; + this.img = new Image(); + var imgDiv = null; + var notReadyInterval = null; + var notReadyConnector, notReadyContext; + var events = params.events || {}; + var _init = function() { + if (self.ready) { + window.clearInterval(notReadyInterval); + imgDiv = document.createElement("img"); + imgDiv.src = self.img.src; + imgDiv.style.position = "absolute"; + imgDiv.style.display="none"; + imgDiv.className = "_jsPlumb_overlay"; + document.body.appendChild(imgDiv);// HMM + // attach events + for (var e in events) { + jsPlumb.CurrentLibrary.bind(imgDiv, e, events[e]); + } + if (notReadyConnector && notReadyContext) { + _draw(notReadyConnector, notReadyContext); + notReadyContext = null; + notReadyConnector = null; + } + } + }; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.src || params.url; + + notReadyInterval = window.setInterval(_init, 250); + + this.computeMaxSize = function(connector, ctx) { + return [self.img.width, self.img.height] + }; + + var _draw = function(connector, ctx) { + var cxy = connector.pointOnPath(self.location); + var canvas = jsPlumb.CurrentLibrary.getElementObject(ctx.canvas); + var canvasOffset = jsPlumb.CurrentLibrary.getOffset(canvas); + var o = {left:canvasOffset.left + cxy[0] - (self.img.width/2), top:canvasOffset.top + cxy[1] - (self.img.height/2)}; + jsPlumb.CurrentLibrary.setOffset(imgDiv, o); + imgDiv.style.display = "block"; + }; + + this.draw = function(connector, ctx) { + if (self.ready) + _draw(connector, ctx); + else { + notReadyConnector = connector; + notReadyContext = ctx; + } + }; + }; +})();/* + * jquery.jsPlumb 1.2.4-RC1 + * + * jQuery specific functionality for jsPlumb. + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + * NOTE: for production usage you should use jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, + * in a minified file. + * + * Dual licensed under MIT and GPL2. + * + */ + +(function($){ + /** + * plumbs the results of the selector to some target, using the given options if supplied, + * or the defaults otherwise. + */ + $.fn.plumb = function(options) { + var options = $.extend({}, options); + + return this.each(function() + { + var params = $.extend({source:$(this)}, options); + jsPlumb.connect(params); + }); + }; + + /** + * detaches the results of the selector from the given target or list of targets - 'target' + * may be a String or a List. + */ + $.fn.detach = function(target) { + return this.each(function() { + if (target) { + var id = $(this).attr("id"); + if (typeof target == 'string') target = [target]; + for (var i = 0; i < target.length; i++) + jsPlumb.detach(id, target[i]); + } + }); + }; + + /** + * detaches the results from the selector from all connections. + */ + $.fn.detachAll = function() { + return this.each(function() + { + var id = $(this).attr("id"); + jsPlumb.detachAll(id); + }); + }; + + /** + * adds an endpoint to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoint = function(options) { + var addedEndpoints = []; + this.each(function() { + addedEndpoints.push(jsPlumb.addEndpoint($(this).attr("id"), options)); + }); + return addedEndpoints[0]; + }; + + /** + * adds a list of endpoints to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoints = function(endpoints) { + var addedEndpoints = []; + return this.each(function() { + var e = jsPlumb.addEndpoints($(this).attr("id"), endpoints); + for (var i = 0; i < e.length; i++) addedEndpoints.push(e[i]); + }); + }; + + /** + * remove the endpoint, if it exists, deleting its UI elements etc. + */ + $.fn.removeEndpoint = function(endpoint) { + this.each(function() { + jsPlumb.removeEndpoint($(this).attr("id"), endpoint); + }); + }; + +})(jQuery); + + +/*TODO: abstract this out from jQuery too! but how...because jsPlumb is not loaded yet. +$(window).bind('resize', function() { + if (resizeTimer) clearTimeout(resizeTimer); + resizeTimer = setTimeout(repaintEverything, 100); + });*/ + + + +/* + * the library agnostic functions, such as find offset, get id, get attribute, extend etc. + */ +(function($) { + + jsPlumb.CurrentLibrary = { + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step', + 'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete' + }, + + bind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.bind(event, callback); + }, + + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).append(child); + }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + * + */ + getElementObject : function(el) { + return typeof(el)=='string' ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el.removeClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options) { + // remove helper directive if present. + options.helper = null; + //TODO: if 'revert' is set on the options it causes end points to animate back to + // where they came from, if the connection is aborted. do we care? probably not. + // the todo is to decide whether we care or not. + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.draggable(options); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.droppable(options); + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[1]; + //return ui.absolutePosition || ui.offset; + return ui.offset || ui.absolutePosition; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + }, + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).remove(); + }, + + getScrollLeft : function(el) { + return el.scrollLeft(); + }, + + getScrollTop : function(el) { + return el.scrollTop(); + }, + + setOffset : function(el, o) { + jsPlumb.CurrentLibrary.getElementObject(el).offset(o); + } + }; +})(jQuery); +/** +* a set of bezier curve functions, used by jsPlumb, and perhaps useful for other people. +* +* functions are all in the 'jsBezier' namespace. here's the list: +* +* distanceFromCurve(point, curve) +* gradientAtPoint(curve, location) +* nearestPointOnCurve(point, curve) ?? +* pointOnCurve(curve, location) +* pointAlongCurveFrom +* perpendicularToCurveAt +* quadraticPointOnCurve +*/ + +(function() { + +var MAXDEPTH = 64, EPSILON = Math.pow(2.0,-MAXDEPTH-1), DEGREE = 3, W_DEGREE = 5; +var V2Sub = function(from, p) { return {x:from.x - p.x, y:from.y - p.y }; }; +var V2Dot = function(v1, v2) { return (v1.x * v2.x) + (v1.y * v2.y); }; +var V2SquaredLength = function(v) { return (v.x * v.x) + (v.y * v.y); }; +var V2ScaleII = function(v, s) { return {x:v.x * s, y:v.y * s }; }; + +/** + * Calculates the distance that the point P is from the curve V. i think ;) + * i need to verify that. + */ +var _distanceFromCurve = function(P, V) { + var w, n_solutions, t; + var t_candidate = new Array(W_DEGREE); + w = _convertToBezierForm(P, V); + n_solutions = _findRoots(w, W_DEGREE, t_candidate, 0); + var dist, new_dist, p, v, i; + v = V2Sub(P, V[0]); + dist = V2SquaredLength(v); + t = 0.0; + for (i = 0; i < n_solutions; i++) { + p = Bezier(V, DEGREE, t_candidate[i], + null, null); + v = V2Sub(P, p); + new_dist = V2SquaredLength(v); + if (new_dist < dist) { + dist = new_dist; + t = t_candidate[i]; + } + } + v = V2Sub(P, V[DEGREE]); + new_dist = V2SquaredLength(v); + if (new_dist < dist) { + dist = new_dist; + t = 1.0; + } + return {t:t,d:dist}; +}; +var _nearestPointOnCurve = function(P, V) { + var td = _distanceFromCurve(P, V); + return Bezier(V, DEGREE, td.t, null, null); +}; +var _convertToBezierForm = function(P, V) { + var i, j, k, m, n, ub, lb, w, row, column; + var c = new Array(DEGREE+1), d = new Array(DEGREE); + var cdTable = []; + var z = [ [1.0, 0.6, 0.3, 0.1], [0.4, 0.6, 0.6, 0.4], [0.1, 0.3, 0.6, 1.0] ]; + for (i = 0; i <= DEGREE; i++) + c[i] = V2Sub(V[i], P); + for (i = 0; i <= DEGREE - 1; i++) { + d[i] = V2Sub(V[i+1], V[i]); + d[i] = V2ScaleII(d[i], 3.0); + } + for (row = 0; row <= DEGREE - 1; row++) { + for (column = 0; column <= DEGREE; column++) { + if (!cdTable[row]) cdTable[row] = []; + cdTable[row][column] = V2Dot(d[row], c[column]); + } + } + w = []; + for (i = 0; i <= W_DEGREE; i++) { + if (!w[i]) w[i] = []; + w[i].y = 0.0; + w[i].x = parseFloat(i) / W_DEGREE; + } + n = DEGREE; + m = DEGREE-1; + for (k = 0; k <= n + m; k++) { + lb = Math.max(0, k - m); + ub = Math.min(k, n); + for (i = lb; i <= ub; i++) { + j = k - i; + w[i+j].y += cdTable[j][i] * z[j][i]; + } + } + return (w); +}; +var _findRoots = function(w, degree, t, depth) { + var i; + var Left = new Array(W_DEGREE+1), Right = new Array(W_DEGREE+1); + var left_count, right_count; + var left_t = new Array(W_DEGREE+1), right_t = new Array(W_DEGREE+1); + switch (CrossingCount(w, degree)) { + case 0 : { + return 0; + } + case 1 : { + if (depth >= MAXDEPTH) { + t[0] = (w[0].x + w[W_DEGREE].x) / 2.0; + return 1; + } + if (ControlPolygonFlatEnough(w, degree)) { + t[0] = ComputeXIntercept(w, degree); + return 1; + } + break; + } + } + Bezier(w, degree, 0.5, Left, Right); + left_count = FindRoots(Left, degree, left_t, depth+1); + right_count = FindRoots(Right, degree, right_t, depth+1); + for (i = 0; i < left_count; i++) t[i] = left_t[i]; + for (i = 0; i < right_count; i++) t[i+left_count] = right_t[i]; + return (left_count+right_count); +}; +var CrossingCount = function(V, degree) { + var n_crossings = 0; + var sign, old_sign; + var SGN = function(x) { return x == 0 ? 0 : x > 0 ? 1 :-1; }; + sign = old_sign = SGN(V[0].y); + for (var i = 1; i <= degree; i++) { + sign = SGN(V[i].y); + if (sign != old_sign) n_crossings++; + old_sign = sign; + } + return n_crossings; +}; +var ControlPolygonFlatEnough = function(V, degree) { + var value, error; + var intercept_1, intercept_2, left_intercept, right_intercept; + var a, b, c, det, dInv, a1, b1, c1, a2, b2, c2; + a = V[0].y - V[degree].y; + b = V[degree].x - V[0].x; + c = V[0].x * V[degree].y - V[degree].x * V[0].y; + + var max_distance_above = max_distance_below = 0.0; + + for (var i = 1; i < degree; i++) + { + value = a * V[i].x + b * V[i].y + c; + + if (value > max_distance_above) + { + max_distance_above = value; + } + else if (value < max_distance_below) + { + max_distance_below = value; + } + } + a1 = 0.0; b1 = 1.0; c1 = 0.0; a2 = a; b2 = b; + c2 = c - max_distance_above; + det = a1 * b2 - a2 * b1; + dInv = 1.0/det; + intercept_1 = (b1 * c2 - b2 * c1) * dInv; + a2 = a; b2 = b; c2 = c - max_distance_below; + det = a1 * b2 - a2 * b1; + dInv = 1.0/det; + intercept_2 = (b1 * c2 - b2 * c1) * dInv; + left_intercept = Math.min(intercept_1, intercept_2); + right_intercept = Math.max(intercept_1, intercept_2); + error = right_intercept - left_intercept; + return (error < EPSILON)? 1 : 0; +}; +var ComputeXIntercept = function(V, degree) { + var XLK, YLK, XNM, YNM, XMK, YMK, det, detInv, S, T, X, Y; + XLK = 1.0 - 0.0; + YLK = 0.0 - 0.0; + XNM = V[degree].x - V[0].x; + YNM = V[degree].y - V[0].y; + XMK = V[0].x - 0.0; + YMK = V[0].y - 0.0; + det = XNM*YLK - YNM*XLK; + detInv = 1.0/det; + S = (XNM*YMK - YNM*XMK) * detInv; + X = 0.0 + XLK * S; + return X; +}; +var Bezier = function(V, degree, t, Left, Right) { + var Vtemp = new Array(); + for (var j =0; j <= degree; j++) { + if (!Vtemp[0]) Vtemp[0] = []; + Vtemp[0][j] = V[j]; + } + for (var i = 1; i <= degree; i++) { + for (var j =0 ; j <= degree - i; j++) { + if (!Vtemp[i]) Vtemp[i] = []; + if (!Vtemp[i][j]) Vtemp[i][j] = {}; + Vtemp[i][j].x = (1.0 - t) * Vtemp[i-1][j].x + t * Vtemp[i-1][j+1].x; + Vtemp[i][j].y = (1.0 - t) * Vtemp[i-1][j].y + t * Vtemp[i-1][j+1].y; + } + } + if (Left != null) + for (j = 0; j <= degree; j++) Left[j] = Vtemp[j][0]; + if (Right != null) + for (j = 0; j <= degree; j++) Right[j] = Vtemp[degree-j][j]; + return (Vtemp[degree][0]); +}; + +var _pointOnPath = function(curve, location) { + // from http://13thparallel.com/archive/bezier-curves/ + function B1(t) { return t*t*t }; + function B2(t) { return 3*t*t*(1-t) }; + function B3(t) { return 3*t*(1-t)*(1-t) }; + function B4(t) { return (1-t)*(1-t)*(1-t) }; + + var x = curve[0].x*B1(location) + curve[1].x * B2(location) + curve[2].x * B3(location) + curve[3].x * B4(location); + var y = curve[0].y*B1(location) + curve[1].y * B2(location) + curve[2].y * B3(location) + curve[3].y * B4(location); + return [x,y]; +}; + +var _quadraticPointOnPath = function(curve, location) { + function B1(t) { return t*t; }; + function B2(t) { return 2*t*(1-t); }; + function B3(t) { return (1-t)*(1-t); }; + var x = curve[0].x*B1(location) + curve[1].x*B2(location) + curve[2].x*B3(location); + var y = curve[0].y*B1(location) + curve[1].y*B2(location) + curve[2].y*B3(location); + return [x,y]; +}; + +/** + * finds the point that is 'distance' along the path from 'location'. this method returns both the x,y location of the point and also + * its 'location' (proportion of travel along the path). + */ +var _pointAlongPath = function(curve, location, distance) { + var _dist = function(p1,p2) { return Math.sqrt(Math.pow(p1[0] - p2[0], 2) + Math.pow(p1[1] - p2[1], 2)); }; + var prev = _pointOnPath(curve, location), tally = 0, curLoc = location, direction = distance > 0 ? 1 : -1, cur = null; + while (tally < Math.abs(distance)) { + curLoc += (0.005 * direction); + cur = _pointOnPath(curve, curLoc); + tally += _dist(cur, prev); + prev = cur; + } + return {point:cur, location:curLoc}; +}; + +var _pointAlongPathFrom = function(curve, location, distance) { + return _pointAlongPath(curve, location, distance).point; +}; + +/** + * returns the gradient of the connector at the given location, which is a decimal between 0 and 1 inclusive. + * + * thanks // http://bimixual.org/AnimationLibrary/beziertangents.html + */ +var _gradientAtPoint = function(curve, location) { + var p1 = _pointOnPath(curve, location); + var p2 = _quadraticPointOnPath(curve, location); + var dy = p2[1] - p1[1], dx = p2[0] - p1[0]; + return Math.atan(dy / dx); +}; + +/** + * calculates a line that is 'length' pixels long, perpendicular to, and centered on, the path at 'distance' pixels from the given location. + */ +var _perpendicularToPathAt = function(curve, location, distance, length) { + var p = _pointAlongPath(curve, location, distance); + var m = _gradientAtPoint(curve, p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [[p.point[0] + x, p.point[1] + y], [p.point[0] - x, p.point[1] - y]]; +}; + +var jsBezier = window.jsBezier = { + distanceFromCurve : _distanceFromCurve, + gradientAtPoint : _gradientAtPoint, + nearestPointOnCurve : _nearestPointOnCurve, + pointOnCurve : _pointOnPath, + pointAlongCurveFrom : _pointAlongPathFrom, + perpendicularToCurveAt : _perpendicularToPathAt, + quadraticPointOnCurve : _quadraticPointOnPath //TODO fold the two pointOnPath functions into one; it can detect what it was given. +}; + +})(); diff --git a/archive/1.2.4/jsBezier-0.1.js b/archive/1.2.4/jsBezier-0.1.js new file mode 100644 index 000000000..8ebba200e --- /dev/null +++ b/archive/1.2.4/jsBezier-0.1.js @@ -0,0 +1,293 @@ +/** +* a set of bezier curve functions, used by jsPlumb, and perhaps useful for other people. +* +* functions are all in the 'jsBezier' namespace. here's the list: +* +* distanceFromCurve(point, curve) +* gradientAtPoint(curve, location) +* nearestPointOnCurve(point, curve) ?? +* pointOnCurve(curve, location) +* pointAlongCurveFrom +* perpendicularToCurveAt +* quadraticPointOnCurve +*/ + +(function() { + +var MAXDEPTH = 64, EPSILON = Math.pow(2.0,-MAXDEPTH-1), DEGREE = 3, W_DEGREE = 5; +var V2Sub = function(from, p) { return {x:from.x - p.x, y:from.y - p.y }; }; +var V2Dot = function(v1, v2) { return (v1.x * v2.x) + (v1.y * v2.y); }; +var V2SquaredLength = function(v) { return (v.x * v.x) + (v.y * v.y); }; +var V2ScaleII = function(v, s) { return {x:v.x * s, y:v.y * s }; }; + +/** + * Calculates the distance that the point P is from the curve V. i think ;) + * i need to verify that. + */ +var _distanceFromCurve = function(P, V) { + var w, n_solutions, t; + var t_candidate = new Array(W_DEGREE); + w = _convertToBezierForm(P, V); + n_solutions = _findRoots(w, W_DEGREE, t_candidate, 0); + var dist, new_dist, p, v, i; + v = V2Sub(P, V[0]); + dist = V2SquaredLength(v); + t = 0.0; + for (i = 0; i < n_solutions; i++) { + p = Bezier(V, DEGREE, t_candidate[i], + null, null); + v = V2Sub(P, p); + new_dist = V2SquaredLength(v); + if (new_dist < dist) { + dist = new_dist; + t = t_candidate[i]; + } + } + v = V2Sub(P, V[DEGREE]); + new_dist = V2SquaredLength(v); + if (new_dist < dist) { + dist = new_dist; + t = 1.0; + } + return {t:t,d:dist}; +}; +var _nearestPointOnCurve = function(P, V) { + var td = _distanceFromCurve(P, V); + return Bezier(V, DEGREE, td.t, null, null); +}; +var _convertToBezierForm = function(P, V) { + var i, j, k, m, n, ub, lb, w, row, column; + var c = new Array(DEGREE+1), d = new Array(DEGREE); + var cdTable = []; + var z = [ [1.0, 0.6, 0.3, 0.1], [0.4, 0.6, 0.6, 0.4], [0.1, 0.3, 0.6, 1.0] ]; + for (i = 0; i <= DEGREE; i++) + c[i] = V2Sub(V[i], P); + for (i = 0; i <= DEGREE - 1; i++) { + d[i] = V2Sub(V[i+1], V[i]); + d[i] = V2ScaleII(d[i], 3.0); + } + for (row = 0; row <= DEGREE - 1; row++) { + for (column = 0; column <= DEGREE; column++) { + if (!cdTable[row]) cdTable[row] = []; + cdTable[row][column] = V2Dot(d[row], c[column]); + } + } + w = []; + for (i = 0; i <= W_DEGREE; i++) { + if (!w[i]) w[i] = []; + w[i].y = 0.0; + w[i].x = parseFloat(i) / W_DEGREE; + } + n = DEGREE; + m = DEGREE-1; + for (k = 0; k <= n + m; k++) { + lb = Math.max(0, k - m); + ub = Math.min(k, n); + for (i = lb; i <= ub; i++) { + j = k - i; + w[i+j].y += cdTable[j][i] * z[j][i]; + } + } + return (w); +}; +var _findRoots = function(w, degree, t, depth) { + var i; + var Left = new Array(W_DEGREE+1), Right = new Array(W_DEGREE+1); + var left_count, right_count; + var left_t = new Array(W_DEGREE+1), right_t = new Array(W_DEGREE+1); + switch (CrossingCount(w, degree)) { + case 0 : { + return 0; + } + case 1 : { + if (depth >= MAXDEPTH) { + t[0] = (w[0].x + w[W_DEGREE].x) / 2.0; + return 1; + } + if (ControlPolygonFlatEnough(w, degree)) { + t[0] = ComputeXIntercept(w, degree); + return 1; + } + break; + } + } + Bezier(w, degree, 0.5, Left, Right); + left_count = FindRoots(Left, degree, left_t, depth+1); + right_count = FindRoots(Right, degree, right_t, depth+1); + for (i = 0; i < left_count; i++) t[i] = left_t[i]; + for (i = 0; i < right_count; i++) t[i+left_count] = right_t[i]; + return (left_count+right_count); +}; +var CrossingCount = function(V, degree) { + var n_crossings = 0; + var sign, old_sign; + var SGN = function(x) { return x == 0 ? 0 : x > 0 ? 1 :-1; }; + sign = old_sign = SGN(V[0].y); + for (var i = 1; i <= degree; i++) { + sign = SGN(V[i].y); + if (sign != old_sign) n_crossings++; + old_sign = sign; + } + return n_crossings; +}; +var ControlPolygonFlatEnough = function(V, degree) { + var value, error; + var intercept_1, intercept_2, left_intercept, right_intercept; + var a, b, c, det, dInv, a1, b1, c1, a2, b2, c2; + a = V[0].y - V[degree].y; + b = V[degree].x - V[0].x; + c = V[0].x * V[degree].y - V[degree].x * V[0].y; + + var max_distance_above = max_distance_below = 0.0; + + for (var i = 1; i < degree; i++) + { + value = a * V[i].x + b * V[i].y + c; + + if (value > max_distance_above) + { + max_distance_above = value; + } + else if (value < max_distance_below) + { + max_distance_below = value; + } + } + a1 = 0.0; b1 = 1.0; c1 = 0.0; a2 = a; b2 = b; + c2 = c - max_distance_above; + det = a1 * b2 - a2 * b1; + dInv = 1.0/det; + intercept_1 = (b1 * c2 - b2 * c1) * dInv; + a2 = a; b2 = b; c2 = c - max_distance_below; + det = a1 * b2 - a2 * b1; + dInv = 1.0/det; + intercept_2 = (b1 * c2 - b2 * c1) * dInv; + left_intercept = Math.min(intercept_1, intercept_2); + right_intercept = Math.max(intercept_1, intercept_2); + error = right_intercept - left_intercept; + return (error < EPSILON)? 1 : 0; +}; +var ComputeXIntercept = function(V, degree) { + var XLK, YLK, XNM, YNM, XMK, YMK, det, detInv, S, T, X, Y; + XLK = 1.0 - 0.0; + YLK = 0.0 - 0.0; + XNM = V[degree].x - V[0].x; + YNM = V[degree].y - V[0].y; + XMK = V[0].x - 0.0; + YMK = V[0].y - 0.0; + det = XNM*YLK - YNM*XLK; + detInv = 1.0/det; + S = (XNM*YMK - YNM*XMK) * detInv; + X = 0.0 + XLK * S; + return X; +}; +var Bezier = function(V, degree, t, Left, Right) { + var Vtemp = new Array(); + for (var j =0; j <= degree; j++) { + if (!Vtemp[0]) Vtemp[0] = []; + Vtemp[0][j] = V[j]; + } + for (var i = 1; i <= degree; i++) { + for (var j =0 ; j <= degree - i; j++) { + if (!Vtemp[i]) Vtemp[i] = []; + if (!Vtemp[i][j]) Vtemp[i][j] = {}; + Vtemp[i][j].x = (1.0 - t) * Vtemp[i-1][j].x + t * Vtemp[i-1][j+1].x; + Vtemp[i][j].y = (1.0 - t) * Vtemp[i-1][j].y + t * Vtemp[i-1][j+1].y; + } + } + if (Left != null) + for (j = 0; j <= degree; j++) Left[j] = Vtemp[j][0]; + if (Right != null) + for (j = 0; j <= degree; j++) Right[j] = Vtemp[degree-j][j]; + return (Vtemp[degree][0]); +}; + +var _pointOnPath = function(curve, location) { + // from http://13thparallel.com/archive/bezier-curves/ + function B1(t) { return t*t*t }; + function B2(t) { return 3*t*t*(1-t) }; + function B3(t) { return 3*t*(1-t)*(1-t) }; + function B4(t) { return (1-t)*(1-t)*(1-t) }; + + var x = curve[0].x*B1(location) + curve[1].x * B2(location) + curve[2].x * B3(location) + curve[3].x * B4(location); + var y = curve[0].y*B1(location) + curve[1].y * B2(location) + curve[2].y * B3(location) + curve[3].y * B4(location); + return [x,y]; +}; + +var _quadraticPointOnPath = function(curve, location) { + function B1(t) { return t*t; }; + function B2(t) { return 2*t*(1-t); }; + function B3(t) { return (1-t)*(1-t); }; + var x = curve[0].x*B1(location) + curve[1].x*B2(location) + curve[2].x*B3(location); + var y = curve[0].y*B1(location) + curve[1].y*B2(location) + curve[2].y*B3(location); + return [x,y]; +}; + +/** + * finds the point that is 'distance' along the path from 'location'. this method returns both the x,y location of the point and also + * its 'location' (proportion of travel along the path). + */ +var _pointAlongPath = function(curve, location, distance) { + var _dist = function(p1,p2) { return Math.sqrt(Math.pow(p1[0] - p2[0], 2) + Math.pow(p1[1] - p2[1], 2)); }; + var prev = _pointOnPath(curve, location), tally = 0, curLoc = location, direction = distance > 0 ? 1 : -1, cur = null; + while (tally < Math.abs(distance)) { + curLoc += (0.005 * direction); + cur = _pointOnPath(curve, curLoc); + tally += _dist(cur, prev); + prev = cur; + } + return {point:cur, location:curLoc}; +}; + +var _pointAlongPathFrom = function(curve, location, distance) { + return _pointAlongPath(curve, location, distance).point; +}; + +/** + * returns the gradient of the connector at the given location, which is a decimal between 0 and 1 inclusive. + * + * thanks // http://bimixual.org/AnimationLibrary/beziertangents.html + */ +var _gradientAtPoint = function(curve, location) { + var p1 = _pointOnPath(curve, location); + var p2 = _quadraticPointOnPath(curve, location); + var dy = p2[1] - p1[1], dx = p2[0] - p1[0]; + return Math.atan(dy / dx); +}; + +/** + * calculates a line that is 'length' pixels long, perpendicular to, and centered on, the path at 'distance' pixels from the given location. + */ +var _perpendicularToPathAt = function(curve, location, distance, length) { + var p = _pointAlongPath(curve, location, distance); + var m = _gradientAtPoint(curve, p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [[p.point[0] + x, p.point[1] + y], [p.point[0] - x, p.point[1] - y]]; +}; + +var jsBezier = window.jsBezier = { + distanceFromCurve : _distanceFromCurve, + gradientAtPoint : _gradientAtPoint, + nearestPointOnCurve : _nearestPointOnCurve, + pointOnCurve : _pointOnPath, + pointAlongCurveFrom : _pointAlongPathFrom, + perpendicularToCurveAt : _perpendicularToPathAt, + quadraticPointOnCurve : _quadraticPointOnPath //TODO fold the two pointOnPath functions into one; it can detect what it was given. +}; + +})(); + + +/* +var bezCurve = [ + { x:0.0, y:0.0 }, + { x:1.0, y:2.0 }, + { x:3.0, y:3.0 }, + { x:4.0, y:2.0 } + ]; + var arbPoint = { x:3.5, y:2.0 }; + var pointOnCurve; + pointOnCurve = NearestPointOnCurve(arbPoint, bezCurve); +*/ \ No newline at end of file diff --git a/archive/1.2.4/jsPlumb-1.2.4-RC1.js b/archive/1.2.4/jsPlumb-1.2.4-RC1.js new file mode 100644 index 000000000..85562ddc8 --- /dev/null +++ b/archive/1.2.4/jsPlumb-1.2.4-RC1.js @@ -0,0 +1,2263 @@ +/* + * jsPlumb 1.2.4-RC1 + * + * Provides a way to visually connect elements on an HTML page. + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + * Dual licensed under MIT and GPL2. + */ + +(function() { + + var jsPlumbInstance = function() { + + /** + * helper class for objects that generate events + */ + var EventGenerator = function() { + var _listeners = {}; + this.bind = function(event, listener) { + var doOne = function(e, l) { _addToList(_listeners, e, l); }; + if (event.constructor == Array) { + for ( var i = 0; i < event.length; i++) + doOne(event[i], listener); + } else + doOne(event, listener); + }; + this.fireUpdate = function(event, value) { + if (_listeners[event]) { + for ( var i = 0; i < _listeners[event].length; i++) { + try { + _listeners[event][i][event](value); + } catch (e) { + _log("jsPlumb: fireUpdate failed for event " + + event + ";not fatal."); + } + } + } + }; + this.clearListeners = function() { + delete _listeners + _listeners = {}; + }; + }; + + var _currentInstance = this; + var ie = !!!document.createElement('canvas').getContext; + var log = null; + + var repaintFunction = function() { + jsPlumb.repaintEverything(); + }; + var automaticRepaint = true; + function repaintEverything() { + if (automaticRepaint) + repaintFunction(); + }; + var resizeTimer = null; + + /** + * map of element id -> endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + var endpointsByElement = {}; + var endpointsByUUID = {}; + var connectionsByScope = {}; + var offsets = {}; + var offsetTimestamps = {}; + var floatingConnections = {}; + var draggableStates = {}; + var _draggableByDefault = true; + var sizes = []; + var listeners = {}; // a map: keys are event types, values are lists of listeners. + var DEFAULT_SCOPE = 'DEFAULT'; + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + var _findIndex = function(a, v, b, s) { + var _eq = function(o1, o2) { + if (o1 === o2) + return true; + else if (typeof o1 == 'object' && typeof o2 == 'object') { + var same = true; + for ( var propertyName in o1) { + if (!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for ( var propertyName in o2) { + if (!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for ( var i = +b || 0, l = a.length; i < l; i++) { + if (_eq(a[i], v)) + return i; + } + return -1; + }; + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }; + + /** + * appends an element to the given parent, or the document body if no + * parent given. + */ + var _appendElement = function(canvas, parent) { + if (!parent) + document.body.appendChild(canvas); + else + jsPlumb.CurrentLibrary.appendElement(canvas, parent); + }; + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + var _timestamp = function() { + return "" + (new Date()).getTime(); + }; + + /** + * Draws an endpoint and its connections. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + var _draw = function(element, ui, timestamp) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (!timestamp) timestamp = _timestamp(); + if (endpoints) { + _updateOffset( { elId : id, offset : ui, recalc : false, timestamp : timestamp }); // timestamp is checked against last update cache; it is + // valid for one paint cycle. + var myOffset = offsets[id], myWH = sizes[id]; + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + var l = endpoints[i].connections; + for ( var j = 0; j < l.length; j++) { + l[j].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); // ...paint each connection. + // then, check for dynamic endpoint; need to repaint it. + var oIdx = l[j].endpoints[0] == endpoints[i] ? 1 : 0; + if (l[j].endpoints[oIdx].anchor.isDynamic && !l[j].endpoints[oIdx].isFloating()) { + var oId = oIdx == 0 ? l[j].sourceId : l[j].targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + // TODO i still want to make this faster. + var anchorLoc = l[j].endpoints[oIdx].anchor.compute( { + xy : [ oOffset.left, oOffset.top ], + wh : oWH, + element : l[j].endpoints[oIdx], + txy : [ myOffset.left, myOffset.top ], + twh : myWH, + tElement : endpoints[i] + }); + l[j].endpoints[oIdx].paint({ anchorLoc : anchorLoc }); + } + } + } + } + }; + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + var _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }; + + /** + * + */ + var _log = function(msg) { + if (_currentInstance.logEnabled && typeof console != "undefined") + console.log(msg); + }; + + /** + * gets the named attribute from the given element (id or element + * object) + */ + var _getAttribute = function(el, attName) { + return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); + }; + + var _setAttribute = function(el, attName, attValue) { + jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); + }; + + var _addClass = function(el, clazz) { + jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); + }; + + var _removeClass = function(el, clazz) { + jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); + }; + + var _getElementObject = function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el); + }; + + var _getOffset = function(el) { + return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); + }; + + var _getSize = function(el) { + return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); + }; + + /** + * gets an id for the given element, creating and setting one if + * necessary. + */ + var _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id) { + // check if fixed uuid parameter is given + if (arguments.length == 2) + id = uuid; + else + id = "_jsPlumb_" + _timestamp(); + _setAttribute(ele, "id", id); + } + return id; + }; + + /** + * gets an Endpoint by uuid. + */ + var _getEndpoint = function(uuid) { + return endpointsByUUID[uuid]; + }; + + /** + * inits a draggable if it's not already initialised. + */ + var _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + } + } + }; + + /** + * helper to create a canvas. + * + * @param clazz + * optional class name for the canvas. + */ + var _newCanvas = function(clazz, parent, uuid) { + var canvas = document.createElement("canvas"); + _appendElement(canvas, parent); + canvas.style.position = "absolute"; + if (clazz) canvas.className = clazz; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + _getId(canvas, uuid); + if (ie) { + // for IE we have to set a big canvas size. actually you can + // override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas + // you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + var _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }; + /** + * perform an operation on all elements. + */ + var _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }; + /** + * helper to remove an element from the DOM. + */ + var _removeElement = function(element, parent) { + if (element != null) { + if (!parent) { + try { + document.body.removeChild(element); + } catch (e) { + } + } else { + jsPlumb.CurrentLibrary.removeElement(element, parent); + } + } + }; + /** + * helper to remove a list of elements from the DOM. + */ + var _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }; + /** + * helper method to remove an item from a list. + */ + var _removeFromList = function(map, key, value) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete (l[i]); + l.splice(i, 1); + return true; + } + } + return false; + }; + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }; + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + var _setVisible = function(el, state) { + _operation(_getAttribute(el, "id"), function(jpc) { + jpc.canvas.style.display = state; + }); + }; + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + var _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }; + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + var _toggleVisible = function(elId) { + _operation(elId, function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }; + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we use the + * offset, outerWidth and outerHeight methods to get the current values. + */ + var _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return; + } + if (recalc || offset == null) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } else { + offsets[elId] = offset; + } + }; + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + */ + var _wrap = function(wrappedFunction, newFunction) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log('jsPlumb function failed : ' + e); + } + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log('wrapped function failed : ' + e); + } + return r; + }; + }; + + /* + * Class: Anchor Models a position relative to the origin of some + * element that an Endpoint can be located. + */ + /* + * Function: Anchor + * + * Constructor for the Anchor class + * + * Parameters: + * - x : the x location of the anchor as a fraction of the total width. - + * y : the y location of the anchor as a fraction of the total height. - + * orientation : an [x,y] array indicating the general direction a + * connection from the anchor should go in. for more info on this, see + * the documentation, or the docs in jquery-jsPlumb-defaults-XXX.js for + * the default Anchors. - offsets : an [x,y] array of fixed offsets that + * should be applied after the x,y position has been figured out. may be + * null. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null; + var lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + if (timestamp && timestamp === self.timestamp) { + return lastReturnValue; + } + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + var container = element ? element.container : null; + var containerAdjustment = { left : 0, top : 0 }; + if (container != null) { + var eo = _getElementObject(container); + var o = _getOffset(eo); + var sl = jsPlumb.CurrentLibrary.getScrollLeft(eo); + var st = jsPlumb.CurrentLibrary.getScrollTop(eo); + containerAdjustment.left = o.left - sl; + containerAdjustment.top = o.top - st; + lastReturnValue[0] = lastReturnValue[0] - containerAdjustment.left; + lastReturnValue[1] = lastReturnValue[1] - containerAdjustment.top; + } + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function() { + return orientation; + }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { + return lastReturnValue; + }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. + * + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(_getElementObject(refCanvas)); + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + var orientation = null; + var _lastResult = null; + + this.compute = function(params) { + var xy = params.xy, el = params.element; + var result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + if (el.container != null) { + var o = _getOffset(el.container); + result[0] = result[0] - o.left; + result[1] = result[1] - o.top; + } + _lastResult = result; + return result; + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { + orientation = null; + }; + + this.getCurrentLocation = function() { + return _lastResult; + }; + }; + + /* + * Class: DynamicAnchor + * + * An Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements - a useful + * feature for some applications. + * + * there has been a request to make the anchor selection process + * pluggable: closest centers does not work for everyone. + */ + var DynamicAnchor = function(anchors) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = anchors || []; + var _convert = function(anchor) { + return anchor.constructor == Array ? jsPlumb.makeAnchor(anchor) : anchor; + }; + for (var i = 0; i < _anchors.length; i++) + _anchors[i] = _convert(_anchors[i]); + + this.addAnchor = function(anchor) { + _anchors.push(_convert(anchor)); + }; + this.getAnchors = function() { return _anchors; }; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null; + var _curIndex = _anchors.length > 0 ? 0 : -1; + this.locked = false; + var self = this; + + var _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }; + + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp, txy = params.txy, twh = params.twh, tElement = params.tElement; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < _anchors.length; i++) { + var d = _distance(_anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + _curAnchor = _anchors[minIdx]; + var pos = _curAnchor.compute(params); + return pos; + }; + + this.getCurrentLocation = function() { + var cl = _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + return cl; + }; + + this.getOrientation = function() { + return _curAnchor != null ? _curAnchor.getOrientation() : [ 0, 0 ]; + }; + + this.over = function(anchor) { + if (_curAnchor != null) + _curAnchor.over(anchor); + }; + + this.out = function() { + if (_curAnchor != null) + _curAnchor.out(); + }; + }; + + /* + * Class:Connection + * + * The connecting line between two Endpoints. + * + */ + var Connection = function(params) { + + EventGenerator.apply(this); + // ************** get the source and target and register the + // connection. ******************* + var self = this; + var id = new String('_jsplumb_c_' + (new Date()).getTime()); + this.getId = function() { return id; }; + this.container = params.container || _currentInstance.Defaults.Container; // may be null; we will append to the body if so. + // get source and target as jQuery objects + this.source = _getElementObject(params.source); + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. + if (params.sourceEndpoint) this.source = params.sourceEndpoint.getElement(); + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + this.sourceId = _getAttribute(this.source, "id"); + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + this.scope = params.scope; // scope may have been passed in to the connect call. + // if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. init endpoints + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams) { + if (anchorParams) + return jsPlumb.makeAnchor(anchorParams); + }; + var prepareEndpoint = function(existing, index, params, element) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint + || new jsPlumb.Endpoints.Dot(); + if (ep.constructor == String) ep = new jsPlumb.Endpoints[ep](); + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + var a = params.anchors ? params.anchors[index] : _makeAnchor(_currentInstance.Defaults.Anchors[index]) || _makeAnchor(jsPlumb.Defaults.Anchors[index]) || _makeAnchor(_currentInstance.Defaults.Anchor) || _makeAnchor(jsPlumb.Defaults.Anchor) || _makeAnchor("BottomCenter"); + var u = params.uuids ? params.uuids[index] : null; + var e = new Endpoint( { style : es, endpoint : ep, connections : [ self ], uuid : u, anchor : a, source : element, container : self.container }); + self.endpoints[index] = e; + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || _currentInstance.Defaults.Connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + if (this.connector.constructor == String) this.connector = new jsPlumb.Connectors[this.connector](); // lets you use a string as shorthand. + this.paintStyle = this.endpoints[0].connectorStyle || this.endpoints[1].connectorStyle || params.paintStyle || _currentInstance.Defaults.PaintStyle || jsPlumb.Defaults.PaintStyle; + this.backgroundPaintStyle = this.endpoints[0].connectorBackgroundStyle || this.endpoints[1].connectorBackgroundStyle || params.backgroundPaintStyle || _currentInstance.Defaults.BackgroundPaintStyle || jsPlumb.Defaults.BackgroundPaintStyle; + + // init overlays + this.overlays = params.overlays || []; + this.addOverlay = function(overlay) { + overlays.push(overlay); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + this.labelStyle = params.labelStyle || _currentInstance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + if (this.label) { + this.overlays.push(new jsPlumb.Overlays.Label( { + labelStyle : this.labelStyle, + label : this.label + })); + } + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + // functions for mouse hover/select functionality + this.distanceFrom = function(point) { + return self.connector.distanceFrom(point); + }; + + this.setLabel = function(l) { + self.label = l; + _currentInstance.repaint(self.source); + }; + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var otherOffset = offsets[this.targetId]; + otherWH = sizes[this.targetId]; + // todo: why not fold this and the paint call that follows into one + // call? + var anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1] + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0] + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc }); + + var canvas = _newCanvas(jsPlumb.connectorClass, self.container); + this.canvas = canvas; + + /* + * Function: paint paints the connection. Parameters: - elId Id of + * the element that is in motion - ui current library's event system + * ui object (present if we came from a drag to get here) - recalc + * whether or not to recalculate element sizes. this is true if a + * repaint caused this to be painted. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp; + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the + // two references. + var swap = false;// !(elId == this.sourceId); + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + if (this.canvas.getContext) { + _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }); + _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + var myOffset = offsets[sId], otherOffset = offsets[tId], myWH = sizes[sId], otherWH = sizes[tId]; + var ctx = canvas.getContext('2d'); + var sAnchorP = this.endpoints[sIdx].anchor.getCurrentLocation(); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.getCurrentLocation(); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + + // paint overlays + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + var s = o.computeMaxSize(self.connector, ctx); + if (s > maxSize) + maxSize = s; + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, this.paintStyle.lineWidth, maxSize); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + + var _paintOneStyle = function(ctx, paintStyle) { + ctx.save(); + jsPlumb.extend(ctx, paintStyle); + if (paintStyle.gradient && !ie) { + var g = self.connector.createGradient(dim, ctx, (elId == this.sourceId)); + for ( var i = 0; i < paintStyle.gradient.stops.length; i++) + g.addColorStop(paintStyle.gradient.stops[i][0], paintStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + self.connector.paint(dim, ctx); + ctx.restore(); + }; + + // first check for the background style + if (this.backgroundPaintStyle != null) { + _paintOneStyle(ctx, this.backgroundPaintStyle); + } + _paintOneStyle(ctx, this.paintStyle); + + // paint overlays + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + o.draw(self.connector, ctx); + } + } + }; + + // TODO: should this take a timestamp? probably. it reduces the + // amount of time + // spent figuring out anchor locations. + this.repaint = function() { + this.paint({ elId : this.sourceId, recalc : true }); + }; + + _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide + // whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + }; + + /* + * Class: Endpoint Models an endpoint. Can have one to N connections + * emanating from it (although how to handle that in the UI is a very + * good question). also has a Canvas and paint style. + */ + + /* + * Function: Endpoint This is the Endpoint class constructor. + * Parameters: anchor - anchor for the endpoint, of type jsPlumb.Anchor. + * may be null. endpoint - endpoint object, of type jsPlumb.Endpoint. + * may be null. style - endpoint style, a js object. may be null. source - + * element the endpoint is attached to, of type jquery object. Required. + * canvas - canvas element to use. may be, and most often is, null. + * connections - optional list of connections to configure the endpoint + * with. isSource - boolean. indicates the endpoint can act as a source + * of new connections. optional. dragOptions - if isSource is set to + * true, you can supply arguments for the jquery draggable method. + * optional. connectionStyle - if isSource is set to true, this is the + * paint style for connections from this endpoint. optional. connector - + * optional connector type to use. isTarget - boolean. indicates the + * endpoint can act as a target of new connections. optional. + * dropOptions - if isTarget is set to true, you can supply arguments + * for the jquery droppable method. optional. reattach - optional + * boolean that determines whether or not the connections reattach after + * they have been dragged off an endpoint and left floating. defaults to + * false: connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + params = params || {}; + var self = this; + var id = new String('_jsplumb_e_' + (new Date()).getTime()); + this.getId = function() { return id; }; + self.anchor = params.anchor ? jsPlumb.makeAnchor(params.anchor) : jsPlumb.makeAnchor("TopCenter"); + var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot(); + if (_endpoint.constuctor == String) _endpoint = new jsPlumb.Endpoints[_endpoint](); + self.endpoint = _endpoint; + var _style = params.style || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + this.connectorStyle = params.connectorStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + var _element = params.source, _uuid = params.uuid; + var floatingEndpoint = null, inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + this.container = params.container || _currentInstance.Defaults.Container || jsPlumb.Defaults.Container; + var _elementId = _getAttribute(_element, "id"); + var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of. + this.canvas = params.canvas || _newCanvas(jsPlumb.endpointClass, this.container, params.uuid); + this.connections = params.connections || []; + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + var _reattach = params.reattach || false; + this.dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + this.addConnection = function(connection) { + self.connections.push(connection); + }; + this.detach = function(connection, ignoreTarget) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + // this avoids a circular loop + if (!ignoreTarget) { + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + t.detach(connection, true); + } + } + _removeElement(connection.canvas, connection.container); + _removeFromList(connectionsByScope, connection.scope, connection); + fireDetachEvent(connection); + }; + + /** + * detaches all connections this Endpoint has + */ + this.detachAll = function() { + while (self.connections.length > 0) { + self.detach(self.connections[0]); + } + }; + + /** + * removes any connections from this Endpoint that are connected to + * the given target endpoint. + * + * @param targetEndpoint + */ + this.detachFrom = function(targetEndpoint) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + self.detach(c[i]); + } + }; + + /** + * detach this Endpoint from the Connection, but leave the Connection alive. used when dragging. + */ + this.detachFromConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /** + * returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /** + * returns the UUID for this Endpoint, if there is one. + */ + this.getUuid = function() { + return _uuid; + }; + + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + var e = new Endpoint( { anchor : self.anchor, source : _element, style : _style, endpoint : _endpoint }); + return e; + }; + + /** + * returns whether or not this endpoint is connected to the given + * endpoint. + * + * @param endpoint + * Endpoint to test. + * @since 1.1.1 + * + * todo: needs testing. will this work if the endpoint passed in is + * the source? + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + * + * @returns {Boolean} + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + var connectorSelector = function() { + return (self.connections.length < _maxConnections) ? null : self.connections[0]; + }; + + /** + * @returns whether or not the Endpoint can accept any more + * Connections. + */ + this.isFull = function() { + return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); + }; + + /** + * sets whether or not connnctions can be dragged from this Endpoint + * once it is full. you would use this in a UI in which you're going + * to provide some other way of breaking connections, if you need to + * break them at all. this property is by default true; use it in + * conjunction with the 'reattach' option on a connect call. + */ + this.setDragAllowedWhenFull = function(allowed) { + self.dragAllowedWhenFull = allowed; + }; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + /** + * paints the Endpoint, recalculating offset and anchor positions if + * necessary. + */ + this.paint = function(params) { + + params = params || {}; + var timestamp = params.timestamp; + if (!timestamp || self.timestamp !== timestamp) { + var ap = params.anchorPoint, canvas = params.canvas, connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + // do we always want to force a repaint here? i dont + // think so! + var xy = params.offset || offsets[_elementId]; + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (self.anchor.isDynamic) { + if (self.connections.length > 0) { + var c = self.connections[0]; + var oIdx = c.endpoints[0] == self ? 1 : 0; + var oId = oIdx == 0 ? c.sourceId : c.targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + } + ap = self.anchor.compute(anchorParams); + } + _endpoint.paint(ap, self.anchor.getOrientation(), canvas || self.canvas, _style, connectorPaintStyle || _style); + self.timestamp = timestamp; + } + }; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener + // maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + var start = function() { + jpc = connectorSelector(); + if (self.isFull() && !self.dragAllowedWhenFull) return false; + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + n = document.createElement("div"); + var nE = _getElementObject(n); + _appendElement(n, self.container); // + // create and assign an id, and initialize the offset. + var id = _getId(nE); + _updateOffset( { elId : id }); + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor( { reference : self.anchor, referenceCanvas : self.canvas }); + floatingEndpoint = new Endpoint({ style : { fillStyle : 'rgba(0,0,0,0)' }, endpoint : _endpoint, anchor : floatingAnchor, source : nE }); + + if (jpc == null) { + self.anchor.locked = true; + // create a connection. one end is this endpoint, the + // other is a floating endpoint. + jpc = new Connection( { + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : _getElementObject(_element), + target : _getElementObject(n), + anchors : [ self.anchor, floatingAnchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + connector : params.connector, // this can also be null. Connection will use the default. + overlays : self.connectorOverlays // new in 1.2.4. + }); + } else { + existingJpc = true; + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; + // detach from the connection while dragging is occurring. + self.detachFromConnection(jpc); + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId ]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId ]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + // lock the other endpoint; if it is dynamic it will not + // move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new + // floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // TODO unregister on stop? or will floating endpoint's + // destruction be assured. + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be + // dragging the source at any time + // before this connection is either discarded or made into a + // permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + }; + + var dragOptions = params.dragOptions || {}; + var defaultOpts = jsPlumb.extend( {}, jsPlumb.CurrentLibrary.defaultDragOptions); + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + var startEvent = jsPlumb.CurrentLibrary.dragEvents['start']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], + function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + jsPlumb.CurrentLibrary.setOffset(n, _ui); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements( [ n, floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (_reattach) { + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } else { + jpc.endpoints[1].detach(jpc); + jpc.endpoints[0].detach(jpc); + // TODO - delete floating endpoint? + } + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElement(jpc.canvas, self.container); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint(); + jpc.repaint(); + jpc = null; + delete floatingEndpoint; + delete inPlaceCopy; + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + } + + // connector target + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable), "dragId"); + var elId = _getAttribute(_getElementObject(draggable), "elId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + var oidx = idx == 0 ? 1 : 0; + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // todo test that the target is not full. + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + _addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + jsPlumb.repaint(elId); + _currentInstance.fireUpdate("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + } + // else there must be some cleanup required? or not? + + delete floatingConnections[id]; + }); + + dropOptions[overEvent] = _wrap(dropOptions[overEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute( _getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + }); + + jsPlumb.CurrentLibrary.initDroppable(_getElementObject(self.canvas), dropOptions); + } + + return self; + }; + + /* + * Property: Defaults These are the default settings for jsPlumb, that + * is what will be used if you do not supply specific pieces of + * information to the various API calls. A convenient way to implement + * your own look and feel can be to override these defaults by including + * a script somewhere after the jsPlumb include, but before you make any + * calls to jsPlumb, for instance in this example we set the PaintStyle + * to be a blue line of 27 pixels: > jsPlumb.Defaults.PaintStyle = { + * lineWidth:27, strokeStyle:'blue' } + */ + this.Defaults = { + Anchor : null, + Anchors : [ null, null ], + BackgroundPaintStyle : null, + Connector : null, + Container : null, + DragOptions : {}, + DropOptions : {}, + Endpoint : null, + Endpoints : [ null, null ], + EndpointStyle : { + fillStyle : null + }, + EndpointStyles : [ null, null ], + LabelStyle : { + fillStyle : "rgba(0,0,0,0)", + color : "black" + }, + LogEnabled : true, + MaxConnections : null, + // TODO: should we have OverlayStyle too? + PaintStyle : { + lineWidth : 10, + strokeStyle : 'red' + }, + Scope : "_jsPlumb_DefaultScope" + }; + + this.logEnabled = this.Defaults.LogEnabled; + + /* + * Property: connectorClass The CSS class to set on Connection canvas + * elements. This value is a String and can have multiple classes; the + * entire String is appended as-is. + */ + this.connectorClass = '_jsPlumb_connector'; + + /* + * Property: endpointClass The CSS class to set on Endpoint canvas + * elements. This value is a String and can have multiple classes; the + * entire String is appended as-is. + */ + this.endpointClass = '_jsPlumb_endpoint'; + + /* + * Property: overlayClass The CSS class to set on an Overlay that is an + * HTML element. This value is a String and can have multiple classes; + * the entire String is appended as-is. + */ + this.overlayClass = '_jsPlumb_overlay'; + + /* + * Property: Anchors Default jsPlumb Anchors. These are supplied in the + * file jsPlumb-defaults-x.x.x.js, which is merged in with the main + * jsPlumb script to form .jsPlumb-all-x.x.x.js. You can + * provide your own Anchors by supplying them in a script that is loaded + * after jsPlumb, for instance: > jsPlumb.Anchors.MyAnchor = { + * ....anchor code here. see the documentation. } + */ + this.Anchors = {}; + + /* + * Property: Connectors Default jsPlumb Connectors. These are supplied + * in the file jsPlumb-defaults-x.x.x.js, which is merged in with the + * main jsPlumb script to form .jsPlumb-all-x.x.x.js. You can + * provide your own Connectors by supplying them in a script that is + * loaded after jsPlumb, for instance: > jsPlumb.Connectors.MyConnector = { + * ....connector code here. see the documentation. } + */ + this.Connectors = {}; + + /* + * Property: Endpoints Default jsPlumb Endpoints. These are supplied in + * the file jsPlumb-defaults-x.x.x.js, which is merged in with the main + * jsPlumb script to form .jsPlumb-all-x.x.x.js. You can + * provide your own Endpoints by supplying them in a script that is + * loaded after jsPlumb, for instance: > jsPlumb.Endpoints.MyEndpoint = { + * ....endpoint code here. see the documentation. } + */ + this.Endpoints = {}; + + /* + * Property:Overlays Default jsPlumb Overlays such as Arrows and Labels. + * These are supplied in the file jsPlumb-defaults-x.x.x.js, which is + * merged in with the main jsPlumb script to form + * .jsPlumb-all-x.x.x.js. You can provide your own Overlays by + * supplying them in a script that is loaded after jsPlumb, for + * instance: > jsPlumb.Overlays.MyOverlay = { ....overlay code here. see + * the documentation. } + */ + this.Overlays = {}; + + /* + * Function: addEndpoint Adds an Endpoint to a given element. + * Parameters: target - Element to add the endpoint to. either an + * element id, or a selector representing some element. params - Object + * containing Endpoint options (more info required) Returns: The newly + * created Endpoint. See Also: + */ + this.addEndpoint = function(target, params) { + params = jsPlumb.extend( {}, params); + params.endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + params.endpointStyle = params.endpointStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + var el = _getElementObject(target), id = _getAttribute(el, "id"); + params.source = el; + _updateOffset({ elId : id }); + var e = new Endpoint(params); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + return e; + }; + + /* + * Function: addEndpoint Adds a list of Endpoints to a given element. + * Parameters: target - element to add the endpoint to. either an + * element id, or a selector representing some element. endpoints - List + * of objects containing Endpoint options. one Endpoint is created for + * each entry in this list. Returns: List of newly created Endpoints, + * one for each entry in the 'endpoints' argument. See Also: + * + */ + this.addEndpoints = function(target, endpoints) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + results.push(_currentInstance.addEndpoint(target, endpoints[i])); + } + return results; + }; + + /* + * Function: animate Wrapper around supporting library's animate + * function; injects a call to jsPlumb in the 'step' function (creating + * it if necessary). This only supports the two-arg version of the + * animate call in jQuery - the one that takes an 'options' object as + * the second arg. MooTools has only one method - a two arg one. Which + * is handy. Parameters: el - Element to animate. Either an id, or a + * selector representing the element. properties - The 'properties' + * argument you want passed to the standard jQuery animate call. options - + * The 'options' argument you want passed to the standard jQuery animate + * call. Returns: void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // you know, probably onComplete should repaint too. that will help + // keep up + // with fast animations. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + this.autoConnect = function(params) { + var sources = [], targets = [], _endpoint = params.endpoint || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle, _anchors = anchors || jsPlumb.Defaults.DynamicAnchors(); + var _addAll = function(s, t) { + for ( var i = 0; i < s.length; i++) + t.push(s[i]); + }; + var source = params.source, target = params.target, anchors = params.anchors; + if (typeof source == 'string') + sources.push(_getElementObject(source)); + else + _addAll(source, sources); + if (typeof target == 'string') + targets.push(_getElementObject(target)); + else + _addAll(target, targets); + var connectOptions = jsPlumb.extend(params, { source : null, target : null, anchors : null }); + for ( var i = 0; i < sources.length; i++) { + for ( var j = 0; j < targets.length; j++) { + var e1 = jsPlumb.addEndpoint(sources[i], jsPlumb.extend( { anchor : jsPlumb.makeDynamicAnchor(_anchors) }, _endpoint)); + var e2 = jsPlumb.addEndpoint(targets[j], jsPlumb.extend( { anchor : jsPlumb.makeDynamicAnchor(_anchors) }, _endpoint)); + _currentInstance.connect(jsPlumb.extend(connectOptions, { sourceEndpoint : e1, targetEndpoint : e2 })); + } + } + }; + + /* + * Function: connect Establishes a connection between two elements. + * Parameters: params - Object containing setup for the connection. see + * documentation. Returns: The newly created Connection. + */ + this.connect = function(params) { + var _p = jsPlumb.extend( {}, params); + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not + // full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log("could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log("could not add connection; target endpoint is full"); + return; + } + + var jpc = new Connection(_p); + // add to list of connections (by scope). + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + _currentInstance.fireUpdate("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + // force a paint + _draw(jpc.source); + + return jpc; + }; + + /* + Function: deleteEndpoint + + deletes an endpoint and removes all connections it has (which removes the connections from the other endpoints involved too) + + You can call this with either a string uuid, or an Endpoint + + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElement(endpoint.canvas, endpoint.container); + // remove from endpointsbyElement + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + delete endpoint; + } + }; + + /* + * Function: deleteEveryEndpoint + * + * Deletes every Endpoint in this instance + * of jsPlumb. Use this instead of removeEveryEndpoint now. + * + * Returns: void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc) { + _currentInstance.fireUpdate("jsPlumbConnectionDetached", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + }; + + /* + * Function: + * detach Removes a connection. takes either (source, target) (the old way, maintained for backwards compatibility), or a params + * object with various possible values. + * + * + * Parameters: + * source - id or element object of the first element in the connection. + * target - id or element object of the second element in the connection. + * + * OR + * + * params: + * { + * + * Returns: true if successful, false if not. + */ + this.detach = function(source, target) { + if (arguments.length == 2) { + var s = _getElementObject(source), sId = _getId(s); + var t = _getElementObject(target), tId = _getId(t); + _operation(sId, function(jpc) { + if ((jpc.sourceId == sId && jpc.targetId == tId) || (jpc.targetId == sId && jpc.sourceId == tId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + fireDetachEvent(jpc); + } + }); + } + // this is the new version of the method, taking a JS object like + // the connect method does. + else if (arguments.length == 1) { + if (arguments[0].constructor == Connection) { + arguments[0].endpoints[0].detachFrom(arguments[0].endpoints[1]); + fireDetachEvent(arguments[0]); + } + else if (arguments[0].connection) { + arguments[0].connection.endpoints[0].detachFrom(arguments[0].connection.endpoints[1]); + fireDetachEvent(arguments[0].connection); + } + else { + var _p = jsPlumb.extend( {}, source); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1])); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + sourceId = _getId(_p.source); + targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + fireDetachEvent(jpc); + } + }); + } + } + } + }; + + /* + * Function: detachAll Removes all an element's connections. Parameters: + * el - either the id of the element, or a selector for the element. + * Returns: void + */ + this.detachAllConnections = function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + var c = endpoints[i].connections.length; + if (c > 0) { + for ( var j = 0; j < c; j++) { + var jpc = endpoints[i].connections[0]; + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + fireDetachEvent(jpc); + } + } + } + } + }; + + /** + * @deprecated Use detachAllConnections instead. this will be removed in jsPlumb 1.3. + */ + this.detachAll = this.detachAllConnections; + + /* + * Function: detachEverything Remove all Connections from all elements, + * but leaves Endpoints in place. Returns: void See Also: + * + */ + this.detachEveryConnection = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + var c = endpoints[i].connections.length; + if (c > 0) { + for ( var j = 0; j < c; j++) { + var jpc = endpoints[i].connections[0]; + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + fireDetachEvent(jpc); + } + } + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + /** + * @deprecated use detachEveryConnection instead. this will be removed in jsPlumb 1.3. + */ + this.detachEverything = this.detachEveryConnection; + + /* + * Function: draggable initialises the draggability of some element or + * elements. Parameters: el - either an element id, a list of element + * ids, or a selector. options - options to pass through to the + * underlying library Returns: void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + * Function: extend Wraps the underlying library's extend functionality. + * Parameters: o1 - object to extend o2 - object to extend o1 with + * Returns: o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getConnections Gets all or a subset of connections + * currently managed by this jsPlumb instance. Parameters: options - a + * JS object that holds options defining what sort of connections you're + * looking for. Valid values are: scope this may be a String or a list + * of Strings. jsPlumb will only return Connections whose scope matches + * what this option defines. If you omit it, you will be given + * Connections of every scope. source may be a string or a list of + * strings; constraints results to have only Connections whose source is + * this object. target may be a string or a list of strings; constraints + * results to have only Connections whose target is this object. + * + * The return value is a dictionary in this format: + * { 'scope1': [ {sourceId:'window1', targetId:'window2', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:}, + * {sourceId:'window3', targetId:'window4', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:}, + * {sourceId:'window1', targetId:'window3', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:} ], + * 'scope2': [ {sourceId:'window1', targetId:'window3', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:} ] } + * + */ + this.getConnections = function(options) { + var r = {}; + options = options || {}; + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') + r.push(input); + else + r = input; + } + return r; + }; + var scopes = prepareList(options.scope); + var sources = prepareList(options.source); + var targets = prepareList(options.target); + var filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + r[i] = []; + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + r[i].push({ sourceId : c.sourceId, targetId : c.targetId, source : c.source, target : c.target, sourceEndpoint : c.endpoints[0], targetEndpoint : c.endpoints[1], connection : c }); + } + } + } + return r; + }; + + /* + * Function: getDefaultScope Gets the default scope for connections and + * endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + * Function: getEndpoint gets an Endpoint by UUID Parameters: uuid - the + * UUID for the Endpoint Returns: Endpoint with the given UUID, null if + * nothing found. + */ + this.getEndpoint = _getEndpoint; + + /* + * gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + + /* + * Function: hide Sets an element's connections to be hidden. + * Parameters: el - either the id of the element, or a selector for the + * element. Returns: void + */ + this.hide = function(el) { + _setVisible(el, "none"); + }; + + /* + * Function: makeAnchor Creates an anchor with the given params. + * Parameters: x - the x location of the anchor as a fraction of the + * total width. y - the y location of the anchor as a fraction of the + * total height. xOrientation - value indicating the general direction a + * connection from the anchor should go in, in the x direction. + * yOrientation - value indicating the general direction a connection + * from the anchor should go in, in the y direction. xOffset - a fixed + * offset that should be applied in the x direction that should be + * applied after the x position has been figured out. optional. defaults + * to 0. yOffset - a fixed offset that should be applied in the y + * direction that should be applied after the y position has been + * figured out. optional. defaults to 0. + * -- OR -- + * + * params - {x:..., y:..., xOrientation etc } + * -- OR FROM 1.2.4 --- + * + * name - the name of some Anchor in the _currentInstance.Anchors array. + * -- OR FROM 1.2.4 --- + * + * coords - a list of coords for the anchor, like you would pass to + * jsPlumb.makeAnchor (eg [0.5,0.5,0,-1] - an anchor in the center of + * some element, oriented towards the top of the screen) + * -- OR FROM 1.2.4 --- + * + * anchor - an existing anchor. just gets passed back. it's handy + * internally to have this functionality. + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed + // in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards + // compatibility if we are given only one value we assume it's a + // call in the old form. + if (arguments.length == 0) return null; + var params = {}; + if (arguments.length == 1) { + var specimen = arguments[0]; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; + // is it the name of an anchor type? + else if (typeof specimen == "string") return _currentInstance.Anchors[arguments[0]](); + // is it an array of coordinates? + else if (specimen.constructor == Array) return jsPlumb.makeAnchor.apply(this, specimen); + // last we try the backwards compatibility stuff. + else if (typeof arguments[0] == "object") jsPlumb.extend(params, x); + } else { + params = { x : x, y : y }; + if (arguments.length >= 4) params.orientation = [ arguments[2], arguments[3] ]; + if (arguments.length == 6) params.offsets = [ arguments[4], arguments[5] ]; + } + var a = new Anchor(params); + a.clone = function() { + return new Anchor(params); + }; + return a; + }; + + /** + * makes a list of anchors from the given list of types, eg + * ["TopCenter", "RightMiddle", "BottomCenter"] + */ + this.makeAnchors = function(types) { + var r = []; + for ( var i = 0; i < types.length; i++) + r.push(_currentInstance.Anchors[types[i]]()); + return r; + }; + + this.makeDynamicAnchor = function(anchors) { + return new DynamicAnchor(anchors); + }; + + /* + * Function: repaint Repaints an element and its connections. This + * method gets new sizes for the elements before painting anything. + * Parameters: el - either the id of the element or a selector + * representing the element. Returns: void See Also: + */ + this.repaint = function(el) { + + var _processElement = function(el) { + _draw(_getElementObject(el)); + }; + + // support both lists... + if (typeof el == 'object') { + for ( var i = 0; i < el.length; i++) + _processElement(el[i]); + } // ...and single strings. + else + _processElement(el); + }; + + /* + * Function: repaintEverything Repaints all connections. Returns: void + * See Also: + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + * Function: removeAllEndpoints + * Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + * Parameters: el - either an element id, or a selector for an element. + * Returns: void + * See Also: + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + for ( var i in ebe) + _currentInstance.deleteEndpoint(ebe[i]); + endpointsByElement[elId] = []; + }; + + /** + * Function: removeEveryEndpoint + * Removes every Endpoint in this instance + * of jsPlumb. + * + * Returns: void + * + * See Also: + * + * @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /** + * Function: removeEndpoint Removes the given Endpoint from the given + * element. Parameters: el - either an element id, or a selector for an + * element. endpoint - Endpoint to remove. this is an Endpoint object, + * such as would have been returned from a call to addEndpoint. Returns: + * void See Also: + * + * @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + /** + * Function:reset + * removes all endpoints and connections and clears the + * listener list. to keep listeners just call jsPlumb.deleteEveryEndpoint. + */ + this.reset = function() { + this.deleteEveryEndpoint(); + this.clearListeners(); + }; + + /* + * Function: setAutomaticRepaint Sets/unsets automatic repaint on window + * resize. Parameters: value - whether or not to automatically repaint + * when the window is resized. Returns: void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + * Function: setDefaultNewCanvasSize Sets the default size jsPlumb will + * use for a new canvas (we create a square canvas so one value is all + * that is required). This is a hack for IE, because ExplorerCanvas + * seems to need for a canvas to be larger than what you are going to + * draw on it at initialisation time. The default value of this is 1200 + * pixels, which is quite large, but if for some reason you're drawing + * connectors that are bigger, you should adjust this value + * appropriately. Parameters: size - The default size to use. jsPlumb + * will use a square canvas so you need only supply one value. Returns: + * void + */ + this.setDefaultNewCanvasSize = function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }; + + /* + * Function: setDefaultScope Sets the default scope for connections and + * endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable Sets whether or not a given element is + * draggable, regardless of what any plumb command may request. + * Parameters: el - either the id for the element, or a selector + * representing the element. Returns: void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setDraggableByDefault Sets whether or not elements are + * draggable by default. Default for this is true. Parameters: draggable - + * value to set Returns: void + */ + this.setDraggableByDefault = function(draggable) { + _draggableByDefault = draggable; + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction Sets the function to fire when the + * window size has changed and a repaint was fired. Parameters: f - + * Function to execute. Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: show Sets an element's connections to be visible. + * Parameters: el - either the id of the element, or a selector for the + * element. Returns: void + */ + this.show = function(el) { + _setVisible(el, "block"); + }; + + /* + * Function: sizeCanvas Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * Parameters: x - [int] x position for the Canvas origin y - [int] y + * position for the Canvas origin w - [int] width of the canvas h - + * [int] height of the canvas Returns: void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible Toggles visibility of an element's + * connections. Parameters: el - either the element's id, or a selector + * representing the element. Returns: void, but should be updated to + * return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable Toggles draggability (sic) of an element's + * connections. Parameters: el - either the element's id, or a selector + * representing the element. Returns: The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload Unloads jsPlumb, deleting all storage. You should + * call this from an onunload attribute on the element Returns: + * void + */ + this.unload = function() { + delete endpointsByElement; + delete endpointsByUUID; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + }; + + /* + * Function: wrap Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. Parameters: + */ + this.wrap = _wrap; + + EventGenerator.apply(this); + this.addListener = this.bind; + + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(); + if (_defaults) + jsPlumb.extend(j.Defaults, _defaults); + return j; + }; +})(); diff --git a/archive/1.2.4/jsPlumb-defaults-1.2.4-RC1.js b/archive/1.2.4/jsPlumb-defaults-1.2.4-RC1.js new file mode 100644 index 000000000..6e5fc693e --- /dev/null +++ b/archive/1.2.4/jsPlumb-defaults-1.2.4-RC1.js @@ -0,0 +1,758 @@ +/* +* jsPlumb-defaults-1.2.4-RC1 +* +* This script contains the default Anchors, Endpoints, Connectors and Overlays for jsPlumb. It should be used with jsPlumb 1.1.0 and above; +* prior to version 1.1.0 of jsPlumb the defaults were included inside the main script. +* +* NOTE: for production usage you should use jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, +* in a minified file. +* +* Dual licensed under MIT and GPL2. +*/ + +(function() { + + var ie = !!!document.createElement('canvas').getContext; + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * getOrientation : function() { return [ox, oy]; } + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + var _curryAnchor = function(x,y,ox,oy) { + return function() { + return jsPlumb.makeAnchor(x,y,ox,oy); + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1); + + + jsPlumb.Defaults.DynamicAnchors = function() { + return jsPlumb.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"]); + }; + jsPlumb.Anchors["AutoDefault"] = function() { return jsPlumb.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors()); }; + + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + var currentPoints = null; + var _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + + _dx = _tx - _sx, _dy = (_ty - _sy); + _m = _dy / _dx, _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + + return currentPoints; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + var xp = _sx + (location * _dx); + var yp = _m == Infinity ? xp + _b : (_m * xp) + _b; + return [xp, yp]; + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { return _m; }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + var orientation = distance > 0 ? 1 : -1; + var y = Math.abs(distance * Math.sin(_theta)); + if (_sy > _ty) y = y * -1; + var x = Math.abs(distance * Math.cos(_theta)); + if (_sx > _tx) x = x * -1; + return [p[0] + (orientation * x), p[1] + (orientation * y)]; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, distance, length) { + var p = self.pointAlongPathFrom(location, distance); + var m = self.gradientAtPoint(p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [[p[0] + x, p[1] + y], [p[0] - x, p[1] - y]]; + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + /** + * This Connector draws a Bezier curve with two control points. + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + jsPlumb.Connectors.Bezier = function(curviness) { + var self = this; + this.majorAnchor = curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _sx, _sy, _canvasX, _canvasY, _w, _h; + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) + { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx); var minx2 = Math.min(_CP[0], _CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(_sx,_tx); var maxx2 = Math.max(_CP[0], _CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty); var miny2 = Math.min(_CP[1], _CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(_sy,_ty); var maxy2 = Math.max(_CP[1], _CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + this.paint = function(d, ctx) { + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the distance the given point is from the curve. not enabled for 1.2.3. didnt make the cut. next time. + * + this.distanceFrom = function(point) { + var curve = [ {x:currentPoints[4], y:currentPoints[5]}, + {x:currentPoints[8], y:currentPoints[9]}, + {x:currentPoints[10], y:currentPoints[11]}, + {x:currentPoints[6], y:currentPoints[7]}]; + return (jsBezier.distanceFromCurve(point, curve)); + };*/ + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, distance, length) { + return jsBezier.perpendicularToCurveAt(_makeCurve(), location, distance, length); + }; + + this.createGradient = function(dim, ctx, swap) { + return (swap) ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + + /** + * Types of endpoint UIs. we supply four - a circle of default radius 10px, a rectangle of + * default size 20x20, an image (with no default), and a Triangle, of default size 15. + * you can supply others of these if you want to - see the documentation for a howto. + */ + + /** + * a round endpoint, with default radius 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + } + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + if (endpointStyle.gradient && !ie) { + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * A Rectangular endpoint, with default size 20x20. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + jsPlumb.Endpoints.Triangle = function(params) { + + params = params || { width:15, height:15 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - width/2; + var y = anchorPoint[1] - height/2; + + jsPlumb.sizeCanvas(canvas, x, y, width, height); + + var ctx = canvas.getContext('2d'); + var offsetX = 0, offsetY = 0, angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = endpointStyle.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' or, since 1.2.4, a 'src' property in the params object.. + */ + jsPlumb.Endpoints.Image = function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.src || params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + }; + + /** + * An arrow overlay. you can provide: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + */ + jsPlumb.Overlays.Arrow = function(params) { + params = params || {}; + var self = this; + var length = params.length || 20; + var width = params.width || 20; + var fillStyle = params.fillStyle || "black"; + var strokeStyle = params.strokeStyle; + var lineWidth = params.lineWidth || 1; + this.loc = params.location || 0.5; + // how far along the arrow the lines folding back in come to. default is 62.3%. + var foldback = params.foldback || 0.623; + var _getFoldBackPoint = function(connector, loc) { + if (foldback == 0.5) return connector.pointOnPath(loc); + else { + var adj = 0.5 - foldback; // we calculate relative to the center + return connector.pointAlongPathFrom(loc, length * adj); + } + }; + + this.computeMaxSize = function() { return width * 1.5; } + + this.draw = function(connector, ctx) { + // this is the arrow head position + var hxy = connector.pointAlongPathFrom(self.loc, length / 2); + // this is the center of the tail + var txy = connector.pointAlongPathFrom(self.loc, -length / 2), tx = txy[0], ty = txy[1]; + // this is the tail vector + var tail = connector.perpendicularToPathAt(self.loc, -length / 2, width); + // this is the point the tail goes in to + var cxy = _getFoldBackPoint(connector, self.loc); + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(hxy[0], hxy[1]); + ctx.lineTo(tail[0][0], tail[0][1]); + ctx.lineTo(cxy[0], cxy[1]); + ctx.lineTo(tail[1][0], tail[1][1]); + ctx.lineTo(hxy[0], hxy[1]); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + + /** + * a basic arrow. this is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. See Arrow for params. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + /** + * a diamond. like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. See Arrow for params. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40; + var p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + /** + * A Label overlay. Params you can provide: + * + * labelStyle - js object containing style instructions for the label. defaults to jsPlumb.Defaults.LabelStyle. + * label - the label to paint. may be a string or a function that returns a string. nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * borderWidth - width of a border to paint. defaults to zero. + * borderStyle - strokeStyle to use when painting the border, if necessary. + */ + jsPlumb.Overlays.Label = function(params) { + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + var self = this; + var labelWidth = null, labelHeight = null, labelText = null, labelPadding = null; + this.location = params.location || 0.5; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var _textDimensions = function(ctx) { + if (self.cachedDimensions) return self.cachedDimensions; // return cached copy if we can. if we add a setLabel function remember to clear the cache. + labelText = typeof self.label == 'function' ? self.label(self) : self.label; + var d = {}; + if (labelText) { + var lines = labelText.split("\n"); + ctx.save(); + if (self.labelStyle.font) ctx.font = self.labelStyle.font; + var t = _widestLine(lines, ctx); + // a fake text height measurement: use the width of upper case M + var h = ctx.measureText("M").width; + labelPadding = self.labelStyle.padding || 0.25; + labelWidth = t + (2 * t * labelPadding); + labelHeight = (lines.length * h) + (2 * h * labelPadding); + var textHeight = lines.length * h; + ctx.restore(); + d = {width:labelWidth, height:labelHeight, lines:lines, oneLine:h, padding:labelPadding, textHeight:textHeight}; + } + if (typeof self.label != 'function') self.cachedDimensions = d; // cache it if we can. + return d; + }; + this.computeMaxSize = function(connector, ctx) { + var td = _textDimensions(ctx); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + var _widestLine = function(lines, ctx) { + var max = 0; + for (var i = 0; i < lines.length; i++) { + var t = ctx.measureText(lines[i]).width; + if (t > max) max = t; + } + return max; + }; + + this.draw = function(connector, ctx) { + var td = _textDimensions(ctx); + if (td.width) { + var cxy = connector.pointOnPath(self.location); + if (self.labelStyle.font) ctx.font = self.labelStyle.font; + if (self.labelStyle.fillStyle) + ctx.fillStyle = self.labelStyle.fillStyle; + else + ctx.fillStyle = "rgba(0,0,0,0)"; + ctx.fillRect(cxy[0] - (td.width / 2), cxy[1] - (td.height / 2) , td.width , td.height ); + + if (self.labelStyle.color) ctx.fillStyle = self.labelStyle.color; + ctx.textBaseline = "middle"; + ctx.textAlign = "center"; + for (i = 0; i < td.lines.length; i++) { + ctx.fillText(td.lines[i],cxy[0], cxy[1] - (td.textHeight / 2) + (td.oneLine/2) + (i*td.oneLine)); + } + + // border + if (self.labelStyle.borderWidth > 0) { + ctx.strokeStyle = self.labelStyle.borderStyle || "black"; + ctx.strokeRect(cxy[0] - (td.width / 2), cxy[1] - (td.height / 2) , td.width , td.height ); + } + } + }; + }; + + /** + * an image overlay. params may contain: + * + * location : proportion along the connector to draw the image. optional. + * src : image src. required. + * events : map of event names to functions; each event listener will be bound to the img. + */ + jsPlumb.Overlays.Image = function(params) { + var self = this; + this.location = params.location || 0.5; + this.img = new Image(); + var imgDiv = null; + var notReadyInterval = null; + var notReadyConnector, notReadyContext; + var events = params.events || {}; + var _init = function() { + if (self.ready) { + window.clearInterval(notReadyInterval); + imgDiv = document.createElement("img"); + imgDiv.src = self.img.src; + imgDiv.style.position = "absolute"; + imgDiv.style.display="none"; + imgDiv.className = "_jsPlumb_overlay"; + document.body.appendChild(imgDiv);// HMM + // attach events + for (var e in events) { + jsPlumb.CurrentLibrary.bind(imgDiv, e, events[e]); + } + if (notReadyConnector && notReadyContext) { + _draw(notReadyConnector, notReadyContext); + notReadyContext = null; + notReadyConnector = null; + } + } + }; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.src || params.url; + + notReadyInterval = window.setInterval(_init, 250); + + this.computeMaxSize = function(connector, ctx) { + return [self.img.width, self.img.height] + }; + + var _draw = function(connector, ctx) { + var cxy = connector.pointOnPath(self.location); + var canvas = jsPlumb.CurrentLibrary.getElementObject(ctx.canvas); + var canvasOffset = jsPlumb.CurrentLibrary.getOffset(canvas); + var o = {left:canvasOffset.left + cxy[0] - (self.img.width/2), top:canvasOffset.top + cxy[1] - (self.img.height/2)}; + jsPlumb.CurrentLibrary.setOffset(imgDiv, o); + imgDiv.style.display = "block"; + }; + + this.draw = function(connector, ctx) { + if (self.ready) + _draw(connector, ctx); + else { + notReadyConnector = connector; + notReadyContext = ctx; + } + }; + }; +})(); \ No newline at end of file diff --git a/archive/1.2.4/mootools.jsPlumb-1.2.4-RC1.js b/archive/1.2.4/mootools.jsPlumb-1.2.4-RC1.js new file mode 100644 index 000000000..428b98b54 --- /dev/null +++ b/archive/1.2.4/mootools.jsPlumb-1.2.4-RC1.js @@ -0,0 +1,303 @@ +/* + * mootools.jsPlumb 1.2.4-RC1 + * + * MooTools specific functionality for jsPlumb. + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + * NOTE: for production usage you should use mootools.jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, + * in a minified file. + * + * Dual licensed under MIT and GPL2. + * + */ + +(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function() { + this.parent(); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}; + var _droppableOptions = {}; + var _draggablesByScope = {}; + var _draggablesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }; + + + jsPlumb.CurrentLibrary = { + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' + }, + + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).grab(child); + }, + + bind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.addEvent(event, callback); + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + getElementObject : function(el) { + return $(el); + }, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + setOffset : function(el, o) { + jsPlumb.CurrentLibrary.getElementObject(el).setPosition({x:o.left, y:o.top}); + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el.removeClass(clazz); + }, + + initDraggable : function(el, options) { + var id = jsPlumb.getId(el); + var drag = _draggablesById[id]; + if (!drag) { + var originalZIndex = 0, originalCursor = null; + var dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + options['onStart'] = jsPlumb.wrap(options['onStart'], function() + { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() + { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + }); + + // DROPPABLES: + var scope = options['scope'] || jsPlumb.Defaults.Scope; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop'); + } + }; + + drag = new Drag.Move(el, options); + _add(_draggablesByScope, scope, drag); + _add(_draggablesById, el.get("id"), drag); + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + initDroppable : function(el, options) { + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _add(_droppables, scope, el); + var id = jsPlumb.getId(el); + _droppableOptions[id] = options; + var filterFunc = function(entry) { + return entry.element != el; + }; + var draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + if (typeof Drag != undefined) + return typeof Drag.Move != undefined; + return false; + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + m.start(properties); + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[0]; + return { left: ui.offsetLeft, top: ui.offsetTop }; + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + }, + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).dispose(); // ?? + }, + + getScrollLeft : function(el) { + return null; + }, + + getScrollTop : function(el) { + return null; + } + }; +})(); diff --git a/archive/1.2.4/mootools.jsPlumb-1.2.4-all-min.js b/archive/1.2.4/mootools.jsPlumb-1.2.4-all-min.js new file mode 100644 index 000000000..04ba2ec94 --- /dev/null +++ b/archive/1.2.4/mootools.jsPlumb-1.2.4-all-min.js @@ -0,0 +1,80 @@ +(function(){var Z=function(){var w=function(){var a={};this.bind=function(b,e){if(b.constructor==Array)for(var h=0;h=0){delete a[e];a.splice(e,1);return true}}return false},ja=function(a,b){ea(J(a,"id"),function(e){e.canvas.style.display=b})},na=function(a){ea(a,function(b){b.canvas.style.display="none"==b.canvas.style.display?"block":"none"})},V=function(a){var b=a.timestamp,e=a.recalc,h=a.offset;a=a.elId;if(!e)if(b&&b===f[a])return;if(e||h==null){e=x(a);q[a]=R(e); +j[a]=l.CurrentLibrary.getOffset(x(e));f[a]=b}else j[a]=h},T=function(a,b){a=a||function(){};b=b||function(){};return function(){var e=null;try{e=b.apply(this,arguments)}catch(h){G("jsPlumb function failed : "+h)}try{a.apply(this,arguments)}catch(n){G("wrapped function failed : "+n)}return e}},ka=function(a){var b=this;this.x=a.x||0;this.y=a.y||0;var e=a.orientation||[0,0],h=null;this.offsets=a.offsets||[0,0];b.timestamp=null;this.compute=function(n){var m=n.xy,A=n.wh,D=n.element;if((n=n.timestamp)&& +n===b.timestamp)return h;h=[m[0]+b.x*A[0]+b.offsets[0],m[1]+b.y*A[1]+b.offsets[1]];A=D?D.container:null;m={left:0,top:0};if(A!=null){var F=x(A);A=l.CurrentLibrary.getOffset(x(F));D=l.CurrentLibrary.getScrollLeft(F);F=l.CurrentLibrary.getScrollTop(F);m.left=A.left-D;m.top=A.top-F;h[0]-=m.left;h[1]-=m.top}b.timestamp=n;return h};this.getOrientation=function(){return e};this.equals=function(n){if(!n)return false;var m=n.getOrientation(),A=this.getOrientation();return this.x==n.x&&this.y==n.y&&this.offsets[0]== +n.offsets[0]&&this.offsets[1]==n.offsets[1]&&A[0]==m[0]&&A[1]==m[1]};this.getCurrentLocation=function(){return h}},oa=function(a){var b=a.reference,e=R(x(a.referenceCanvas)),h=null,n=null;this.compute=function(m){var A=m.xy;m=m.element;A=[A[0]+e[0]/2,A[1]+e[1]/2];if(m.container!=null){m=l.CurrentLibrary.getOffset(x(m.container));A[0]-=m.left;A[1]-=m.top}return n=A};this.getOrientation=function(){if(h)return h;else{var m=b.getOrientation();return[Math.abs(m[0])*0*-1,Math.abs(m[1])*0*-1]}};this.over= +function(m){h=m.getOrientation()};this.out=function(){h=null};this.getCurrentLocation=function(){return n}},pa=function(a){this.isDynamic=this.isSelective=true;var b=a||[];for(a=0;a0?b[0]:null;this.locked=false;var h=this;this.compute=function(n){var m=n.xy,A=n.wh,D=n.txy,F=n.twh;if(h.locked||D==null||F==null)return e.compute(n); +else n.timestamp=null;var B=D[0]+F[0]/2;D=D[1]+F[1]/2;F=-1;for(var N=Infinity,K=0;KE)E=Q}var M=this.connector.compute(O,B,this.endpoints[0].anchor,this.endpoints[1].anchor, +this.paintStyle.lineWidth,E);l.sizeCanvas(F,M[0],M[1],M[2],M[3]);O=function(P,U){P.save();l.extend(P,U);if(U.gradient&&!y){for(var Y=b.connector.createGradient(M,P,N==this.sourceId),s=0;s=0){b.connections.splice(z,1);L||(s.endpoints[0]==b?s.endpoints[1]:s.endpoints[0]).detach(s,true)}X(s.canvas,s.container);da(u,s.scope,s);aa(s)};this.detachAll=function(){for(;b.connections.length>0;)b.detach(b.connections[0])};this.detachFrom= +function(s){for(var L=[],z=0;z=0&&b.connections.splice(s,1)};this.getElement=function(){return m};this.getUuid=function(){return A};this.makeInPlaceCopy=function(){return new fa({anchor:b.anchor,source:m,style:n,endpoint:h})};this.isConnectedTo=function(s){var L=false;if(s)for(var z= +0;z=N};this.setDragAllowedWhenFull=function(s){b.dragAllowedWhenFull=s};this.equals=function(s){return this.anchor.equals(s.anchor)};this.paint=function(s){s=s||{};var L=s.timestamp;if(!L||b.timestamp!==L){var z=s.anchorPoint,ba=s.canvas,qa=s.connectorPaintStyle;if(z==null){z=s.offset||j[B];s=s.dimensions||q[B]; +if(z==null||s==null){V({elId:B,timestamp:L});z=j[B];s=q[B]}s={xy:[z.left,z.top],wh:s,element:b,timestamp:L};if(b.anchor.isDynamic)if(b.connections.length>0){z=b.connections[0];var la=z.endpoints[0]==b?1:0,ga=la==0?z.sourceId:z.targetId,ma=j[ga];ga=q[ga];s.txy=[ma.left,ma.top];s.twh=ga;s.tElement=z.endpoints[la]}z=b.anchor.compute(s)}h.paint(z,b.anchor.getOrientation(),ba||b.canvas,n,qa||n);b.timestamp=L}};this.removeConnection=this.detach;if(a.isSource&&l.CurrentLibrary.isDragSupported(m)){var O= +null;var E=e=null,S=false,Q=null,M=a.dragOptions||{},P=l.extend({},l.CurrentLibrary.defaultDragOptions);M=l.extend(P,M);M.scope=M.scope||b.scope;P=l.CurrentLibrary.dragEvents.start;var U=l.CurrentLibrary.dragEvents.stop,Y=l.CurrentLibrary.dragEvents.drag;M[P]=T(M[P],function(){E=b.connections.length0)for(var h=0;h0)for(var n=0;n0?p(h,m)!=-1:true){b[m]=[];for(e= +0;e0?p(n,A.sourceId)!=-1:true)&&(a.length>0?p(a,A.targetId)!=-1:true)&&b[m].push({sourceId:A.sourceId,targetId:A.targetId,source:A.source,target:A.target,sourceEndpoint:A.endpoints[0],targetEndpoint:A.endpoints[1],connection:A})}}return b};this.getDefaultScope=function(){return k};this.getEndpoint=function(a){return v[a]};this.getId=W;this.hide=function(a){ja(a,"none")};this.makeAnchor=function(a,b){if(arguments.length==0)return null;var e={};if(arguments.length== +1){var h=arguments[0];if(h.compute&&h.getOrientation)return h;else if(typeof h=="string")return o.Anchors[arguments[0]]();else if(h.constructor==Array)return l.makeAnchor.apply(this,h);else typeof arguments[0]=="object"&&l.extend(e,a)}else{e={x:a,y:b};if(arguments.length>=4)e.orientation=[arguments[2],arguments[3]];if(arguments.length==6)e.offsets=[arguments[4],arguments[5]]}h=new ka(e);h.clone=function(){return new ka(e)};return h};this.makeAnchors=function(a){for(var b=[],e=0;e0?1:-1,C=Math.abs(k*Math.sin(j));if(c>r)C*=-1;var I=Math.abs(k*Math.cos(j));if(f>d)I*=-1;return[g[0]+p*I,g[1]+p*C]};this.perpendicularToPathAt=function(q,k,g){q=w.pointAlongPathFrom(q,k);k=w.gradientAtPoint(q.location);var p=Math.atan(-1/k);k=g/2*Math.sin(p);g=g/2*Math.cos(p);return[[q[0]+g,q[1]+k],[q[0]-g,q[1]-k]]};this.createGradient=function(q,k){return k.createLinearGradient(q[4],q[5],q[6],q[7])}};jsPlumb.Connectors.Bezier=function(w){var o=this;this.majorAnchor=w||150;this.minorAnchor=10; +var y=null;this._findControlPoint=function(g,p,C,I,H){I=I.getOrientation();H=H.getOrientation();var G=[],J=o.majorAnchor,x=o.minorAnchor;if(I[0]!=H[0]||I[1]==H[1]){H[0]==0?G.push(C[0]r)r=p;if(g<0){c+=g;g=Math.abs(g);r+=g;t[0]+=g;u+=g;j+=g;v[0]+=g}g=Math.min(Math.min(f,_ty),Math.min(t[1],v[1]));p=Math.max(Math.max(f,_ty),Math.max(t[1],v[1])); +if(p>q)q=p;if(g<0){d+=g;g=Math.abs(g);q+=g;t[1]+=g;f+=g;_ty+=g;v[1]+=g}if(G&&rr)r=q}d=r;r=f.measureText("M").width;u=o.labelStyle.padding||0.25;y=d+2*d*u;t=c.length*r+2*r*u;d=c.length*r;f.restore();c={width:y,height:t,lines:c,oneLine:r,padding:u,textHeight:d}}if(typeof o.label!="function")o.cachedDimensions=c;return c};this.computeMaxSize=function(f,c){var d=j(c);return d.width?Math.max(d.width,d.height)*1.5:0};this.draw=function(f, +c){var d=j(c);if(d.width){var r=f.pointOnPath(o.location);if(o.labelStyle.font)c.font=o.labelStyle.font;c.fillStyle=o.labelStyle.fillStyle?o.labelStyle.fillStyle:"rgba(0,0,0,0)";c.fillRect(r[0]-d.width/2,r[1]-d.height/2,d.width,d.height);if(o.labelStyle.color)c.fillStyle=o.labelStyle.color;c.textBaseline="middle";c.textAlign="center";for(i=0;i0){c.strokeStyle=o.labelStyle.borderStyle|| +"black";c.strokeRect(r[0]-d.width/2,r[1]-d.height/2,d.width,d.height)}}}};jsPlumb.Overlays.Image=function(w){var o=this;this.location=w.location||0.5;this.img=new Image;var y=null,t=null,v,u,j=w.events||{};this.img.onload=function(){o.ready=true};this.img.src=w.src||w.url;t=window.setInterval(function(){if(o.ready){window.clearInterval(t);y=document.createElement("img");y.src=o.img.src;y.style.position="absolute";y.style.display="none";y.className="_jsPlumb_overlay";document.body.appendChild(y);for(var c in j)jsPlumb.CurrentLibrary.bind(y, +c,j[c]);if(v&&u){f(v,u);v=u=null}}},250);this.computeMaxSize=function(){return[o.img.width,o.img.height]};var f=function(c,d){var r=c.pointOnPath(o.location),q=jsPlumb.CurrentLibrary.getElementObject(d.canvas);q=jsPlumb.CurrentLibrary.getOffset(q);jsPlumb.CurrentLibrary.setOffset(y,{left:q.left+r[0]-o.img.width/2,top:q.top+r[1]-o.img.height/2});y.style.display="block"};this.draw=function(c,d){if(o.ready)f(c,d);else{v=c;u=d}}}})(); +(function(){var Z=new Class({Extends:Fx.Morph,onStep:null,initialize:function(j,f){this.parent(j,f);if(f.onStep)this.onStep=f.onStep},step:function(){this.parent();if(this.onStep)try{this.onStep()}catch(j){}}}),l={},w={},o={},y={},t=function(j,f,c){if(f){var d=f.get("id");if(d)(d=w[d])&&d[c]&&d[c](j,f)}},v=function(j,f){if(j){var c=j.get("id");if(c)if(c=w[c])if(c.hoverClass)f?j.addClass(c.hoverClass):j.removeClass(c.hoverClass)}},u=function(j,f,c){var d=j[f];if(!d){d=[];j[f]=d}d.push(c)};jsPlumb.CurrentLibrary= +{dragEvents:{start:"onStart",stop:"onComplete",drag:"onDrag",step:"onStep",over:"onEnter",out:"onLeave",drop:"onDrop",complete:"onComplete"},appendElement:function(j,f){jsPlumb.CurrentLibrary.getElementObject(f).grab(j)},bind:function(j,f,c){j=jsPlumb.CurrentLibrary.getElementObject(j);j.addEvent(f,c)},extend:function(j,f){return $extend(j,f)},getElementObject:function(j){return $(j)},getOffset:function(j){j=j.getPosition();return{left:j.x,top:j.y}},setOffset:function(j,f){jsPlumb.CurrentLibrary.getElementObject(j).setPosition({x:f.left, +y:f.top})},getSize:function(j){j=j.getSize();return[j.x,j.y]},getAttribute:function(j,f){return j.get(f)},setAttribute:function(j,f,c){j.set(f,c)},addClass:function(j,f){j.addClass(f)},removeClass:function(j,f){j.removeClass(f)},initDraggable:function(j,f){var c=jsPlumb.getId(j),d=y[c];if(!d){var r=0,q=null,k=jsPlumb.Defaults.DragOptions.zIndex||2E3;f.onStart=jsPlumb.wrap(f.onStart,function(){r=this.element.getStyle("z-index");this.element.setStyle("z-index",k);if(jsPlumb.Defaults.DragOptions.cursor){q= +this.element.getStyle("cursor");this.element.setStyle("cursor",jsPlumb.Defaults.DragOptions.cursor)}});f.onComplete=jsPlumb.wrap(f.onComplete,function(){this.element.setStyle("z-index",r);q&&this.element.setStyle("cursor",q)});c=f.scope||jsPlumb.Defaults.Scope;d=function(g){return g.get("id")!=j.get("id")};d=l[c]?l[c].filter(d):[];f.droppables=d;f.onLeave=jsPlumb.wrap(f.onLeave,function(g,p){if(p){v(p,false);t(g,p,"onLeave")}});f.onEnter=jsPlumb.wrap(f.onEnter,function(g,p){if(p){v(p,true);t(g,p, +"onEnter")}});f.onDrop=function(g,p){if(p){v(p,false);t(g,p,"onDrop")}};d=new Drag.Move(j,f);u(o,c,d);u(y,j.get("id"),d);f.disabled&&d.detach()}return d},isDragSupported:function(){return typeof Drag!="undefined"},setDraggable:function(j,f){var c=y[j.get("id")];c&&c.each(function(d){f?d.attach():d.detach()})},initDroppable:function(j,f){var c=f.scope||jsPlumb.Defaults.Scope;u(l,c,j);var d=jsPlumb.getId(j);w[d]=f;d=function(r){return r.element!=j};c=o[c]?o[c].filter(d):[];for(d=0;d0?1:-1;for(var G=1;G<=5;G++){g=d[G].y==0?0:d[G].y>0?1:-1;g!=H&&C++;H=g}switch(C){case 0:d=0;break a;case 1:var J,x,R;C=d[0].y-d[5].y;g=d[5].x-d[0].x;H=d[0].x*d[5].y-d[5].x*d[0].y;G=max_distance_below=0;for(R=1;R<5;R++){J=C*d[R].x+g*d[R].y+H;if(J>G)G=J;else if(J0?1:-1,g=null;q endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + var endpointsByElement = {}; + var endpointsByUUID = {}; + var connectionsByScope = {}; + var offsets = {}; + var offsetTimestamps = {}; + var floatingConnections = {}; + var draggableStates = {}; + var _draggableByDefault = true; + var sizes = []; + var listeners = {}; // a map: keys are event types, values are lists of listeners. + var DEFAULT_SCOPE = 'DEFAULT'; + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + var _findIndex = function(a, v, b, s) { + var _eq = function(o1, o2) { + if (o1 === o2) + return true; + else if (typeof o1 == 'object' && typeof o2 == 'object') { + var same = true; + for ( var propertyName in o1) { + if (!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for ( var propertyName in o2) { + if (!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for ( var i = +b || 0, l = a.length; i < l; i++) { + if (_eq(a[i], v)) + return i; + } + return -1; + }; + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }; + + /** + * appends an element to the given parent, or the document body if no + * parent given. + */ + var _appendElement = function(canvas, parent) { + if (!parent) + document.body.appendChild(canvas); + else + jsPlumb.CurrentLibrary.appendElement(canvas, parent); + }; + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + var _timestamp = function() { + return "" + (new Date()).getTime(); + }; + + /** + * Draws an endpoint and its connections. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + var _draw = function(element, ui, timestamp) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (!timestamp) timestamp = _timestamp(); + if (endpoints) { + _updateOffset( { elId : id, offset : ui, recalc : false, timestamp : timestamp }); // timestamp is checked against last update cache; it is + // valid for one paint cycle. + var myOffset = offsets[id], myWH = sizes[id]; + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + var l = endpoints[i].connections; + for ( var j = 0; j < l.length; j++) { + l[j].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); // ...paint each connection. + // then, check for dynamic endpoint; need to repaint it. + var oIdx = l[j].endpoints[0] == endpoints[i] ? 1 : 0; + if (l[j].endpoints[oIdx].anchor.isDynamic && !l[j].endpoints[oIdx].isFloating()) { + var oId = oIdx == 0 ? l[j].sourceId : l[j].targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + // TODO i still want to make this faster. + var anchorLoc = l[j].endpoints[oIdx].anchor.compute( { + xy : [ oOffset.left, oOffset.top ], + wh : oWH, + element : l[j].endpoints[oIdx], + txy : [ myOffset.left, myOffset.top ], + twh : myWH, + tElement : endpoints[i] + }); + l[j].endpoints[oIdx].paint({ anchorLoc : anchorLoc }); + } + } + } + } + }; + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + var _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }; + + /** + * + */ + var _log = function(msg) { + if (_currentInstance.logEnabled && typeof console != "undefined") + console.log(msg); + }; + + /** + * gets the named attribute from the given element (id or element + * object) + */ + var _getAttribute = function(el, attName) { + return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); + }; + + var _setAttribute = function(el, attName, attValue) { + jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); + }; + + var _addClass = function(el, clazz) { + jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); + }; + + var _removeClass = function(el, clazz) { + jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); + }; + + var _getElementObject = function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el); + }; + + var _getOffset = function(el) { + return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); + }; + + var _getSize = function(el) { + return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); + }; + + /** + * gets an id for the given element, creating and setting one if + * necessary. + */ + var _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id) { + // check if fixed uuid parameter is given + if (arguments.length == 2) + id = uuid; + else + id = "_jsPlumb_" + _timestamp(); + _setAttribute(ele, "id", id); + } + return id; + }; + + /** + * gets an Endpoint by uuid. + */ + var _getEndpoint = function(uuid) { + return endpointsByUUID[uuid]; + }; + + /** + * inits a draggable if it's not already initialised. + */ + var _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + } + } + }; + + /** + * helper to create a canvas. + * + * @param clazz + * optional class name for the canvas. + */ + var _newCanvas = function(clazz, parent, uuid) { + var canvas = document.createElement("canvas"); + _appendElement(canvas, parent); + canvas.style.position = "absolute"; + if (clazz) canvas.className = clazz; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + _getId(canvas, uuid); + if (ie) { + // for IE we have to set a big canvas size. actually you can + // override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas + // you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + var _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }; + /** + * perform an operation on all elements. + */ + var _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }; + /** + * helper to remove an element from the DOM. + */ + var _removeElement = function(element, parent) { + if (element != null) { + if (!parent) { + try { + document.body.removeChild(element); + } catch (e) { + } + } else { + jsPlumb.CurrentLibrary.removeElement(element, parent); + } + } + }; + /** + * helper to remove a list of elements from the DOM. + */ + var _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }; + /** + * helper method to remove an item from a list. + */ + var _removeFromList = function(map, key, value) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete (l[i]); + l.splice(i, 1); + return true; + } + } + return false; + }; + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }; + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + var _setVisible = function(el, state) { + _operation(_getAttribute(el, "id"), function(jpc) { + jpc.canvas.style.display = state; + }); + }; + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + var _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }; + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + var _toggleVisible = function(elId) { + _operation(elId, function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }; + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we use the + * offset, outerWidth and outerHeight methods to get the current values. + */ + var _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return; + } + if (recalc || offset == null) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } else { + offsets[elId] = offset; + } + }; + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + */ + var _wrap = function(wrappedFunction, newFunction) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log('jsPlumb function failed : ' + e); + } + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log('wrapped function failed : ' + e); + } + return r; + }; + }; + + /* + * Class: Anchor Models a position relative to the origin of some + * element that an Endpoint can be located. + */ + /* + * Function: Anchor + * + * Constructor for the Anchor class + * + * Parameters: + * - x : the x location of the anchor as a fraction of the total width. - + * y : the y location of the anchor as a fraction of the total height. - + * orientation : an [x,y] array indicating the general direction a + * connection from the anchor should go in. for more info on this, see + * the documentation, or the docs in jquery-jsPlumb-defaults-XXX.js for + * the default Anchors. - offsets : an [x,y] array of fixed offsets that + * should be applied after the x,y position has been figured out. may be + * null. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null; + var lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + if (timestamp && timestamp === self.timestamp) { + return lastReturnValue; + } + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + var container = element ? element.container : null; + var containerAdjustment = { left : 0, top : 0 }; + if (container != null) { + var eo = _getElementObject(container); + var o = _getOffset(eo); + var sl = jsPlumb.CurrentLibrary.getScrollLeft(eo); + var st = jsPlumb.CurrentLibrary.getScrollTop(eo); + containerAdjustment.left = o.left - sl; + containerAdjustment.top = o.top - st; + lastReturnValue[0] = lastReturnValue[0] - containerAdjustment.left; + lastReturnValue[1] = lastReturnValue[1] - containerAdjustment.top; + } + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function() { + return orientation; + }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { + return lastReturnValue; + }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. + * + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(_getElementObject(refCanvas)); + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + var orientation = null; + var _lastResult = null; + + this.compute = function(params) { + var xy = params.xy, el = params.element; + var result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + if (el.container != null) { + var o = _getOffset(el.container); + result[0] = result[0] - o.left; + result[1] = result[1] - o.top; + } + _lastResult = result; + return result; + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { + orientation = null; + }; + + this.getCurrentLocation = function() { + return _lastResult; + }; + }; + + /* + * Class: DynamicAnchor + * + * An Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements - a useful + * feature for some applications. + * + * there has been a request to make the anchor selection process + * pluggable: closest centers does not work for everyone. + */ + var DynamicAnchor = function(anchors) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = anchors || []; + var _convert = function(anchor) { + return anchor.constructor == Array ? jsPlumb.makeAnchor(anchor) : anchor; + }; + for (var i = 0; i < _anchors.length; i++) + _anchors[i] = _convert(_anchors[i]); + + this.addAnchor = function(anchor) { + _anchors.push(_convert(anchor)); + }; + this.getAnchors = function() { return _anchors; }; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null; + var _curIndex = _anchors.length > 0 ? 0 : -1; + this.locked = false; + var self = this; + + var _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }; + + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp, txy = params.txy, twh = params.twh, tElement = params.tElement; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < _anchors.length; i++) { + var d = _distance(_anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + _curAnchor = _anchors[minIdx]; + var pos = _curAnchor.compute(params); + return pos; + }; + + this.getCurrentLocation = function() { + var cl = _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + return cl; + }; + + this.getOrientation = function() { + return _curAnchor != null ? _curAnchor.getOrientation() : [ 0, 0 ]; + }; + + this.over = function(anchor) { + if (_curAnchor != null) + _curAnchor.over(anchor); + }; + + this.out = function() { + if (_curAnchor != null) + _curAnchor.out(); + }; + }; + + /* + * Class:Connection + * + * The connecting line between two Endpoints. + * + */ + var Connection = function(params) { + + EventGenerator.apply(this); + // ************** get the source and target and register the + // connection. ******************* + var self = this; + var id = new String('_jsplumb_c_' + (new Date()).getTime()); + this.getId = function() { return id; }; + this.container = params.container || _currentInstance.Defaults.Container; // may be null; we will append to the body if so. + // get source and target as jQuery objects + this.source = _getElementObject(params.source); + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. + if (params.sourceEndpoint) this.source = params.sourceEndpoint.getElement(); + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + this.sourceId = _getAttribute(this.source, "id"); + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + this.scope = params.scope; // scope may have been passed in to the connect call. + // if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. init endpoints + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams) { + if (anchorParams) + return jsPlumb.makeAnchor(anchorParams); + }; + var prepareEndpoint = function(existing, index, params, element) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint + || new jsPlumb.Endpoints.Dot(); + if (ep.constructor == String) ep = new jsPlumb.Endpoints[ep](); + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + var a = params.anchors ? params.anchors[index] : _makeAnchor(_currentInstance.Defaults.Anchors[index]) || _makeAnchor(jsPlumb.Defaults.Anchors[index]) || _makeAnchor(_currentInstance.Defaults.Anchor) || _makeAnchor(jsPlumb.Defaults.Anchor) || _makeAnchor("BottomCenter"); + var u = params.uuids ? params.uuids[index] : null; + var e = new Endpoint( { style : es, endpoint : ep, connections : [ self ], uuid : u, anchor : a, source : element, container : self.container }); + self.endpoints[index] = e; + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || _currentInstance.Defaults.Connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + if (this.connector.constructor == String) this.connector = new jsPlumb.Connectors[this.connector](); // lets you use a string as shorthand. + this.paintStyle = this.endpoints[0].connectorStyle || this.endpoints[1].connectorStyle || params.paintStyle || _currentInstance.Defaults.PaintStyle || jsPlumb.Defaults.PaintStyle; + this.backgroundPaintStyle = this.endpoints[0].connectorBackgroundStyle || this.endpoints[1].connectorBackgroundStyle || params.backgroundPaintStyle || _currentInstance.Defaults.BackgroundPaintStyle || jsPlumb.Defaults.BackgroundPaintStyle; + + // init overlays + this.overlays = params.overlays || []; + this.addOverlay = function(overlay) { + overlays.push(overlay); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + this.labelStyle = params.labelStyle || _currentInstance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + if (this.label) { + this.overlays.push(new jsPlumb.Overlays.Label( { + labelStyle : this.labelStyle, + label : this.label + })); + } + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + // functions for mouse hover/select functionality + this.distanceFrom = function(point) { + return self.connector.distanceFrom(point); + }; + + this.setLabel = function(l) { + self.label = l; + _currentInstance.repaint(self.source); + }; + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var otherOffset = offsets[this.targetId]; + otherWH = sizes[this.targetId]; + // todo: why not fold this and the paint call that follows into one + // call? + var anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1] + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0] + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc }); + + var canvas = _newCanvas(jsPlumb.connectorClass, self.container); + this.canvas = canvas; + + /* + * Function: paint paints the connection. Parameters: - elId Id of + * the element that is in motion - ui current library's event system + * ui object (present if we came from a drag to get here) - recalc + * whether or not to recalculate element sizes. this is true if a + * repaint caused this to be painted. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp; + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the + // two references. + var swap = false;// !(elId == this.sourceId); + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + if (this.canvas.getContext) { + _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }); + _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + var myOffset = offsets[sId], otherOffset = offsets[tId], myWH = sizes[sId], otherWH = sizes[tId]; + var ctx = canvas.getContext('2d'); + var sAnchorP = this.endpoints[sIdx].anchor.getCurrentLocation(); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.getCurrentLocation(); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + + // paint overlays + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + var s = o.computeMaxSize(self.connector, ctx); + if (s > maxSize) + maxSize = s; + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, this.paintStyle.lineWidth, maxSize); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + + var _paintOneStyle = function(ctx, paintStyle) { + ctx.save(); + jsPlumb.extend(ctx, paintStyle); + if (paintStyle.gradient && !ie) { + var g = self.connector.createGradient(dim, ctx, (elId == this.sourceId)); + for ( var i = 0; i < paintStyle.gradient.stops.length; i++) + g.addColorStop(paintStyle.gradient.stops[i][0], paintStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + self.connector.paint(dim, ctx); + ctx.restore(); + }; + + // first check for the background style + if (this.backgroundPaintStyle != null) { + _paintOneStyle(ctx, this.backgroundPaintStyle); + } + _paintOneStyle(ctx, this.paintStyle); + + // paint overlays + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + o.draw(self.connector, ctx); + } + } + }; + + // TODO: should this take a timestamp? probably. it reduces the + // amount of time + // spent figuring out anchor locations. + this.repaint = function() { + this.paint({ elId : this.sourceId, recalc : true }); + }; + + _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide + // whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + }; + + /* + * Class: Endpoint Models an endpoint. Can have one to N connections + * emanating from it (although how to handle that in the UI is a very + * good question). also has a Canvas and paint style. + */ + + /* + * Function: Endpoint This is the Endpoint class constructor. + * Parameters: anchor - anchor for the endpoint, of type jsPlumb.Anchor. + * may be null. endpoint - endpoint object, of type jsPlumb.Endpoint. + * may be null. style - endpoint style, a js object. may be null. source - + * element the endpoint is attached to, of type jquery object. Required. + * canvas - canvas element to use. may be, and most often is, null. + * connections - optional list of connections to configure the endpoint + * with. isSource - boolean. indicates the endpoint can act as a source + * of new connections. optional. dragOptions - if isSource is set to + * true, you can supply arguments for the jquery draggable method. + * optional. connectionStyle - if isSource is set to true, this is the + * paint style for connections from this endpoint. optional. connector - + * optional connector type to use. isTarget - boolean. indicates the + * endpoint can act as a target of new connections. optional. + * dropOptions - if isTarget is set to true, you can supply arguments + * for the jquery droppable method. optional. reattach - optional + * boolean that determines whether or not the connections reattach after + * they have been dragged off an endpoint and left floating. defaults to + * false: connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + params = params || {}; + var self = this; + var id = new String('_jsplumb_e_' + (new Date()).getTime()); + this.getId = function() { return id; }; + self.anchor = params.anchor ? jsPlumb.makeAnchor(params.anchor) : jsPlumb.makeAnchor("TopCenter"); + var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot(); + if (_endpoint.constuctor == String) _endpoint = new jsPlumb.Endpoints[_endpoint](); + self.endpoint = _endpoint; + var _style = params.style || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + this.connectorStyle = params.connectorStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + var _element = params.source, _uuid = params.uuid; + var floatingEndpoint = null, inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + this.container = params.container || _currentInstance.Defaults.Container || jsPlumb.Defaults.Container; + var _elementId = _getAttribute(_element, "id"); + var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of. + this.canvas = params.canvas || _newCanvas(jsPlumb.endpointClass, this.container, params.uuid); + this.connections = params.connections || []; + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + var _reattach = params.reattach || false; + this.dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + this.addConnection = function(connection) { + self.connections.push(connection); + }; + this.detach = function(connection, ignoreTarget) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + // this avoids a circular loop + if (!ignoreTarget) { + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + t.detach(connection, true); + } + } + _removeElement(connection.canvas, connection.container); + _removeFromList(connectionsByScope, connection.scope, connection); + fireDetachEvent(connection); + }; + + /** + * detaches all connections this Endpoint has + */ + this.detachAll = function() { + while (self.connections.length > 0) { + self.detach(self.connections[0]); + } + }; + + /** + * removes any connections from this Endpoint that are connected to + * the given target endpoint. + * + * @param targetEndpoint + */ + this.detachFrom = function(targetEndpoint) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + self.detach(c[i]); + } + }; + + /** + * detach this Endpoint from the Connection, but leave the Connection alive. used when dragging. + */ + this.detachFromConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /** + * returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /** + * returns the UUID for this Endpoint, if there is one. + */ + this.getUuid = function() { + return _uuid; + }; + + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + var e = new Endpoint( { anchor : self.anchor, source : _element, style : _style, endpoint : _endpoint }); + return e; + }; + + /** + * returns whether or not this endpoint is connected to the given + * endpoint. + * + * @param endpoint + * Endpoint to test. + * @since 1.1.1 + * + * todo: needs testing. will this work if the endpoint passed in is + * the source? + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + * + * @returns {Boolean} + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + var connectorSelector = function() { + return (self.connections.length < _maxConnections) ? null : self.connections[0]; + }; + + /** + * @returns whether or not the Endpoint can accept any more + * Connections. + */ + this.isFull = function() { + return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); + }; + + /** + * sets whether or not connnctions can be dragged from this Endpoint + * once it is full. you would use this in a UI in which you're going + * to provide some other way of breaking connections, if you need to + * break them at all. this property is by default true; use it in + * conjunction with the 'reattach' option on a connect call. + */ + this.setDragAllowedWhenFull = function(allowed) { + self.dragAllowedWhenFull = allowed; + }; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + /** + * paints the Endpoint, recalculating offset and anchor positions if + * necessary. + */ + this.paint = function(params) { + + params = params || {}; + var timestamp = params.timestamp; + if (!timestamp || self.timestamp !== timestamp) { + var ap = params.anchorPoint, canvas = params.canvas, connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + // do we always want to force a repaint here? i dont + // think so! + var xy = params.offset || offsets[_elementId]; + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (self.anchor.isDynamic) { + if (self.connections.length > 0) { + var c = self.connections[0]; + var oIdx = c.endpoints[0] == self ? 1 : 0; + var oId = oIdx == 0 ? c.sourceId : c.targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + } + ap = self.anchor.compute(anchorParams); + } + _endpoint.paint(ap, self.anchor.getOrientation(), canvas || self.canvas, _style, connectorPaintStyle || _style); + self.timestamp = timestamp; + } + }; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener + // maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + var start = function() { + jpc = connectorSelector(); + if (self.isFull() && !self.dragAllowedWhenFull) return false; + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + n = document.createElement("div"); + var nE = _getElementObject(n); + _appendElement(n, self.container); // + // create and assign an id, and initialize the offset. + var id = _getId(nE); + _updateOffset( { elId : id }); + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor( { reference : self.anchor, referenceCanvas : self.canvas }); + floatingEndpoint = new Endpoint({ style : { fillStyle : 'rgba(0,0,0,0)' }, endpoint : _endpoint, anchor : floatingAnchor, source : nE }); + + if (jpc == null) { + self.anchor.locked = true; + // create a connection. one end is this endpoint, the + // other is a floating endpoint. + jpc = new Connection( { + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : _getElementObject(_element), + target : _getElementObject(n), + anchors : [ self.anchor, floatingAnchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + connector : params.connector, // this can also be null. Connection will use the default. + overlays : self.connectorOverlays // new in 1.2.4. + }); + } else { + existingJpc = true; + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; + // detach from the connection while dragging is occurring. + self.detachFromConnection(jpc); + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId ]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId ]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + // lock the other endpoint; if it is dynamic it will not + // move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new + // floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // TODO unregister on stop? or will floating endpoint's + // destruction be assured. + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be + // dragging the source at any time + // before this connection is either discarded or made into a + // permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + }; + + var dragOptions = params.dragOptions || {}; + var defaultOpts = jsPlumb.extend( {}, jsPlumb.CurrentLibrary.defaultDragOptions); + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + var startEvent = jsPlumb.CurrentLibrary.dragEvents['start']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], + function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + jsPlumb.CurrentLibrary.setOffset(n, _ui); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements( [ n, floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (_reattach) { + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } else { + jpc.endpoints[1].detach(jpc); + jpc.endpoints[0].detach(jpc); + // TODO - delete floating endpoint? + } + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElement(jpc.canvas, self.container); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint(); + jpc.repaint(); + jpc = null; + delete floatingEndpoint; + delete inPlaceCopy; + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + } + + // connector target + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable), "dragId"); + var elId = _getAttribute(_getElementObject(draggable), "elId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + var oidx = idx == 0 ? 1 : 0; + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // todo test that the target is not full. + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + _addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + jsPlumb.repaint(elId); + _currentInstance.fireUpdate("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + } + // else there must be some cleanup required? or not? + + delete floatingConnections[id]; + }); + + dropOptions[overEvent] = _wrap(dropOptions[overEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute( _getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + }); + + jsPlumb.CurrentLibrary.initDroppable(_getElementObject(self.canvas), dropOptions); + } + + return self; + }; + + /* + * Property: Defaults These are the default settings for jsPlumb, that + * is what will be used if you do not supply specific pieces of + * information to the various API calls. A convenient way to implement + * your own look and feel can be to override these defaults by including + * a script somewhere after the jsPlumb include, but before you make any + * calls to jsPlumb, for instance in this example we set the PaintStyle + * to be a blue line of 27 pixels: > jsPlumb.Defaults.PaintStyle = { + * lineWidth:27, strokeStyle:'blue' } + */ + this.Defaults = { + Anchor : null, + Anchors : [ null, null ], + BackgroundPaintStyle : null, + Connector : null, + Container : null, + DragOptions : {}, + DropOptions : {}, + Endpoint : null, + Endpoints : [ null, null ], + EndpointStyle : { + fillStyle : null + }, + EndpointStyles : [ null, null ], + LabelStyle : { + fillStyle : "rgba(0,0,0,0)", + color : "black" + }, + LogEnabled : true, + MaxConnections : null, + // TODO: should we have OverlayStyle too? + PaintStyle : { + lineWidth : 10, + strokeStyle : 'red' + }, + Scope : "_jsPlumb_DefaultScope" + }; + + this.logEnabled = this.Defaults.LogEnabled; + + /* + * Property: connectorClass The CSS class to set on Connection canvas + * elements. This value is a String and can have multiple classes; the + * entire String is appended as-is. + */ + this.connectorClass = '_jsPlumb_connector'; + + /* + * Property: endpointClass The CSS class to set on Endpoint canvas + * elements. This value is a String and can have multiple classes; the + * entire String is appended as-is. + */ + this.endpointClass = '_jsPlumb_endpoint'; + + /* + * Property: overlayClass The CSS class to set on an Overlay that is an + * HTML element. This value is a String and can have multiple classes; + * the entire String is appended as-is. + */ + this.overlayClass = '_jsPlumb_overlay'; + + /* + * Property: Anchors Default jsPlumb Anchors. These are supplied in the + * file jsPlumb-defaults-x.x.x.js, which is merged in with the main + * jsPlumb script to form .jsPlumb-all-x.x.x.js. You can + * provide your own Anchors by supplying them in a script that is loaded + * after jsPlumb, for instance: > jsPlumb.Anchors.MyAnchor = { + * ....anchor code here. see the documentation. } + */ + this.Anchors = {}; + + /* + * Property: Connectors Default jsPlumb Connectors. These are supplied + * in the file jsPlumb-defaults-x.x.x.js, which is merged in with the + * main jsPlumb script to form .jsPlumb-all-x.x.x.js. You can + * provide your own Connectors by supplying them in a script that is + * loaded after jsPlumb, for instance: > jsPlumb.Connectors.MyConnector = { + * ....connector code here. see the documentation. } + */ + this.Connectors = {}; + + /* + * Property: Endpoints Default jsPlumb Endpoints. These are supplied in + * the file jsPlumb-defaults-x.x.x.js, which is merged in with the main + * jsPlumb script to form .jsPlumb-all-x.x.x.js. You can + * provide your own Endpoints by supplying them in a script that is + * loaded after jsPlumb, for instance: > jsPlumb.Endpoints.MyEndpoint = { + * ....endpoint code here. see the documentation. } + */ + this.Endpoints = {}; + + /* + * Property:Overlays Default jsPlumb Overlays such as Arrows and Labels. + * These are supplied in the file jsPlumb-defaults-x.x.x.js, which is + * merged in with the main jsPlumb script to form + * .jsPlumb-all-x.x.x.js. You can provide your own Overlays by + * supplying them in a script that is loaded after jsPlumb, for + * instance: > jsPlumb.Overlays.MyOverlay = { ....overlay code here. see + * the documentation. } + */ + this.Overlays = {}; + + /* + * Function: addEndpoint Adds an Endpoint to a given element. + * Parameters: target - Element to add the endpoint to. either an + * element id, or a selector representing some element. params - Object + * containing Endpoint options (more info required) Returns: The newly + * created Endpoint. See Also: + */ + this.addEndpoint = function(target, params) { + params = jsPlumb.extend( {}, params); + params.endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + params.endpointStyle = params.endpointStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + var el = _getElementObject(target), id = _getAttribute(el, "id"); + params.source = el; + _updateOffset({ elId : id }); + var e = new Endpoint(params); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + return e; + }; + + /* + * Function: addEndpoint Adds a list of Endpoints to a given element. + * Parameters: target - element to add the endpoint to. either an + * element id, or a selector representing some element. endpoints - List + * of objects containing Endpoint options. one Endpoint is created for + * each entry in this list. Returns: List of newly created Endpoints, + * one for each entry in the 'endpoints' argument. See Also: + * + */ + this.addEndpoints = function(target, endpoints) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + results.push(_currentInstance.addEndpoint(target, endpoints[i])); + } + return results; + }; + + /* + * Function: animate Wrapper around supporting library's animate + * function; injects a call to jsPlumb in the 'step' function (creating + * it if necessary). This only supports the two-arg version of the + * animate call in jQuery - the one that takes an 'options' object as + * the second arg. MooTools has only one method - a two arg one. Which + * is handy. Parameters: el - Element to animate. Either an id, or a + * selector representing the element. properties - The 'properties' + * argument you want passed to the standard jQuery animate call. options - + * The 'options' argument you want passed to the standard jQuery animate + * call. Returns: void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // you know, probably onComplete should repaint too. that will help + // keep up + // with fast animations. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + this.autoConnect = function(params) { + var sources = [], targets = [], _endpoint = params.endpoint || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle, _anchors = anchors || jsPlumb.Defaults.DynamicAnchors(); + var _addAll = function(s, t) { + for ( var i = 0; i < s.length; i++) + t.push(s[i]); + }; + var source = params.source, target = params.target, anchors = params.anchors; + if (typeof source == 'string') + sources.push(_getElementObject(source)); + else + _addAll(source, sources); + if (typeof target == 'string') + targets.push(_getElementObject(target)); + else + _addAll(target, targets); + var connectOptions = jsPlumb.extend(params, { source : null, target : null, anchors : null }); + for ( var i = 0; i < sources.length; i++) { + for ( var j = 0; j < targets.length; j++) { + var e1 = jsPlumb.addEndpoint(sources[i], jsPlumb.extend( { anchor : jsPlumb.makeDynamicAnchor(_anchors) }, _endpoint)); + var e2 = jsPlumb.addEndpoint(targets[j], jsPlumb.extend( { anchor : jsPlumb.makeDynamicAnchor(_anchors) }, _endpoint)); + _currentInstance.connect(jsPlumb.extend(connectOptions, { sourceEndpoint : e1, targetEndpoint : e2 })); + } + } + }; + + /* + * Function: connect Establishes a connection between two elements. + * Parameters: params - Object containing setup for the connection. see + * documentation. Returns: The newly created Connection. + */ + this.connect = function(params) { + var _p = jsPlumb.extend( {}, params); + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not + // full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log("could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log("could not add connection; target endpoint is full"); + return; + } + + var jpc = new Connection(_p); + // add to list of connections (by scope). + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + _currentInstance.fireUpdate("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + // force a paint + _draw(jpc.source); + + return jpc; + }; + + /* + Function: deleteEndpoint + + deletes an endpoint and removes all connections it has (which removes the connections from the other endpoints involved too) + + You can call this with either a string uuid, or an Endpoint + + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElement(endpoint.canvas, endpoint.container); + // remove from endpointsbyElement + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + delete endpoint; + } + }; + + /* + * Function: deleteEveryEndpoint + * + * Deletes every Endpoint in this instance + * of jsPlumb. Use this instead of removeEveryEndpoint now. + * + * Returns: void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc) { + _currentInstance.fireUpdate("jsPlumbConnectionDetached", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + }; + + /* + * Function: + * detach Removes a connection. takes either (source, target) (the old way, maintained for backwards compatibility), or a params + * object with various possible values. + * + * + * Parameters: + * source - id or element object of the first element in the connection. + * target - id or element object of the second element in the connection. + * + * OR + * + * params: + * { + * + * Returns: true if successful, false if not. + */ + this.detach = function(source, target) { + if (arguments.length == 2) { + var s = _getElementObject(source), sId = _getId(s); + var t = _getElementObject(target), tId = _getId(t); + _operation(sId, function(jpc) { + if ((jpc.sourceId == sId && jpc.targetId == tId) || (jpc.targetId == sId && jpc.sourceId == tId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + fireDetachEvent(jpc); + } + }); + } + // this is the new version of the method, taking a JS object like + // the connect method does. + else if (arguments.length == 1) { + if (arguments[0].constructor == Connection) { + arguments[0].endpoints[0].detachFrom(arguments[0].endpoints[1]); + fireDetachEvent(arguments[0]); + } + else if (arguments[0].connection) { + arguments[0].connection.endpoints[0].detachFrom(arguments[0].connection.endpoints[1]); + fireDetachEvent(arguments[0].connection); + } + else { + var _p = jsPlumb.extend( {}, source); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1])); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + sourceId = _getId(_p.source); + targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + fireDetachEvent(jpc); + } + }); + } + } + } + }; + + /* + * Function: detachAll Removes all an element's connections. Parameters: + * el - either the id of the element, or a selector for the element. + * Returns: void + */ + this.detachAllConnections = function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + var c = endpoints[i].connections.length; + if (c > 0) { + for ( var j = 0; j < c; j++) { + var jpc = endpoints[i].connections[0]; + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + fireDetachEvent(jpc); + } + } + } + } + }; + + /** + * @deprecated Use detachAllConnections instead. this will be removed in jsPlumb 1.3. + */ + this.detachAll = this.detachAllConnections; + + /* + * Function: detachEverything Remove all Connections from all elements, + * but leaves Endpoints in place. Returns: void See Also: + * + */ + this.detachEveryConnection = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + var c = endpoints[i].connections.length; + if (c > 0) { + for ( var j = 0; j < c; j++) { + var jpc = endpoints[i].connections[0]; + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + fireDetachEvent(jpc); + } + } + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + /** + * @deprecated use detachEveryConnection instead. this will be removed in jsPlumb 1.3. + */ + this.detachEverything = this.detachEveryConnection; + + /* + * Function: draggable initialises the draggability of some element or + * elements. Parameters: el - either an element id, a list of element + * ids, or a selector. options - options to pass through to the + * underlying library Returns: void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + * Function: extend Wraps the underlying library's extend functionality. + * Parameters: o1 - object to extend o2 - object to extend o1 with + * Returns: o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getConnections Gets all or a subset of connections + * currently managed by this jsPlumb instance. Parameters: options - a + * JS object that holds options defining what sort of connections you're + * looking for. Valid values are: scope this may be a String or a list + * of Strings. jsPlumb will only return Connections whose scope matches + * what this option defines. If you omit it, you will be given + * Connections of every scope. source may be a string or a list of + * strings; constraints results to have only Connections whose source is + * this object. target may be a string or a list of strings; constraints + * results to have only Connections whose target is this object. + * + * The return value is a dictionary in this format: + * { 'scope1': [ {sourceId:'window1', targetId:'window2', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:}, + * {sourceId:'window3', targetId:'window4', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:}, + * {sourceId:'window1', targetId:'window3', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:} ], + * 'scope2': [ {sourceId:'window1', targetId:'window3', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:} ] } + * + */ + this.getConnections = function(options) { + var r = {}; + options = options || {}; + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') + r.push(input); + else + r = input; + } + return r; + }; + var scopes = prepareList(options.scope); + var sources = prepareList(options.source); + var targets = prepareList(options.target); + var filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + r[i] = []; + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + r[i].push({ sourceId : c.sourceId, targetId : c.targetId, source : c.source, target : c.target, sourceEndpoint : c.endpoints[0], targetEndpoint : c.endpoints[1], connection : c }); + } + } + } + return r; + }; + + /* + * Function: getDefaultScope Gets the default scope for connections and + * endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + * Function: getEndpoint gets an Endpoint by UUID Parameters: uuid - the + * UUID for the Endpoint Returns: Endpoint with the given UUID, null if + * nothing found. + */ + this.getEndpoint = _getEndpoint; + + /* + * gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + + /* + * Function: hide Sets an element's connections to be hidden. + * Parameters: el - either the id of the element, or a selector for the + * element. Returns: void + */ + this.hide = function(el) { + _setVisible(el, "none"); + }; + + /* + * Function: makeAnchor Creates an anchor with the given params. + * Parameters: x - the x location of the anchor as a fraction of the + * total width. y - the y location of the anchor as a fraction of the + * total height. xOrientation - value indicating the general direction a + * connection from the anchor should go in, in the x direction. + * yOrientation - value indicating the general direction a connection + * from the anchor should go in, in the y direction. xOffset - a fixed + * offset that should be applied in the x direction that should be + * applied after the x position has been figured out. optional. defaults + * to 0. yOffset - a fixed offset that should be applied in the y + * direction that should be applied after the y position has been + * figured out. optional. defaults to 0. + * -- OR -- + * + * params - {x:..., y:..., xOrientation etc } + * -- OR FROM 1.2.4 --- + * + * name - the name of some Anchor in the _currentInstance.Anchors array. + * -- OR FROM 1.2.4 --- + * + * coords - a list of coords for the anchor, like you would pass to + * jsPlumb.makeAnchor (eg [0.5,0.5,0,-1] - an anchor in the center of + * some element, oriented towards the top of the screen) + * -- OR FROM 1.2.4 --- + * + * anchor - an existing anchor. just gets passed back. it's handy + * internally to have this functionality. + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed + // in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards + // compatibility if we are given only one value we assume it's a + // call in the old form. + if (arguments.length == 0) return null; + var params = {}; + if (arguments.length == 1) { + var specimen = arguments[0]; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; + // is it the name of an anchor type? + else if (typeof specimen == "string") return _currentInstance.Anchors[arguments[0]](); + // is it an array of coordinates? + else if (specimen.constructor == Array) return jsPlumb.makeAnchor.apply(this, specimen); + // last we try the backwards compatibility stuff. + else if (typeof arguments[0] == "object") jsPlumb.extend(params, x); + } else { + params = { x : x, y : y }; + if (arguments.length >= 4) params.orientation = [ arguments[2], arguments[3] ]; + if (arguments.length == 6) params.offsets = [ arguments[4], arguments[5] ]; + } + var a = new Anchor(params); + a.clone = function() { + return new Anchor(params); + }; + return a; + }; + + /** + * makes a list of anchors from the given list of types, eg + * ["TopCenter", "RightMiddle", "BottomCenter"] + */ + this.makeAnchors = function(types) { + var r = []; + for ( var i = 0; i < types.length; i++) + r.push(_currentInstance.Anchors[types[i]]()); + return r; + }; + + this.makeDynamicAnchor = function(anchors) { + return new DynamicAnchor(anchors); + }; + + /* + * Function: repaint Repaints an element and its connections. This + * method gets new sizes for the elements before painting anything. + * Parameters: el - either the id of the element or a selector + * representing the element. Returns: void See Also: + */ + this.repaint = function(el) { + + var _processElement = function(el) { + _draw(_getElementObject(el)); + }; + + // support both lists... + if (typeof el == 'object') { + for ( var i = 0; i < el.length; i++) + _processElement(el[i]); + } // ...and single strings. + else + _processElement(el); + }; + + /* + * Function: repaintEverything Repaints all connections. Returns: void + * See Also: + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + * Function: removeAllEndpoints + * Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + * Parameters: el - either an element id, or a selector for an element. + * Returns: void + * See Also: + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + for ( var i in ebe) + _currentInstance.deleteEndpoint(ebe[i]); + endpointsByElement[elId] = []; + }; + + /** + * Function: removeEveryEndpoint + * Removes every Endpoint in this instance + * of jsPlumb. + * + * Returns: void + * + * See Also: + * + * @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /** + * Function: removeEndpoint Removes the given Endpoint from the given + * element. Parameters: el - either an element id, or a selector for an + * element. endpoint - Endpoint to remove. this is an Endpoint object, + * such as would have been returned from a call to addEndpoint. Returns: + * void See Also: + * + * @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + /** + * Function:reset + * removes all endpoints and connections and clears the + * listener list. to keep listeners just call jsPlumb.deleteEveryEndpoint. + */ + this.reset = function() { + this.deleteEveryEndpoint(); + this.clearListeners(); + }; + + /* + * Function: setAutomaticRepaint Sets/unsets automatic repaint on window + * resize. Parameters: value - whether or not to automatically repaint + * when the window is resized. Returns: void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + * Function: setDefaultNewCanvasSize Sets the default size jsPlumb will + * use for a new canvas (we create a square canvas so one value is all + * that is required). This is a hack for IE, because ExplorerCanvas + * seems to need for a canvas to be larger than what you are going to + * draw on it at initialisation time. The default value of this is 1200 + * pixels, which is quite large, but if for some reason you're drawing + * connectors that are bigger, you should adjust this value + * appropriately. Parameters: size - The default size to use. jsPlumb + * will use a square canvas so you need only supply one value. Returns: + * void + */ + this.setDefaultNewCanvasSize = function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }; + + /* + * Function: setDefaultScope Sets the default scope for connections and + * endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable Sets whether or not a given element is + * draggable, regardless of what any plumb command may request. + * Parameters: el - either the id for the element, or a selector + * representing the element. Returns: void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setDraggableByDefault Sets whether or not elements are + * draggable by default. Default for this is true. Parameters: draggable - + * value to set Returns: void + */ + this.setDraggableByDefault = function(draggable) { + _draggableByDefault = draggable; + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction Sets the function to fire when the + * window size has changed and a repaint was fired. Parameters: f - + * Function to execute. Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: show Sets an element's connections to be visible. + * Parameters: el - either the id of the element, or a selector for the + * element. Returns: void + */ + this.show = function(el) { + _setVisible(el, "block"); + }; + + /* + * Function: sizeCanvas Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * Parameters: x - [int] x position for the Canvas origin y - [int] y + * position for the Canvas origin w - [int] width of the canvas h - + * [int] height of the canvas Returns: void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible Toggles visibility of an element's + * connections. Parameters: el - either the element's id, or a selector + * representing the element. Returns: void, but should be updated to + * return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable Toggles draggability (sic) of an element's + * connections. Parameters: el - either the element's id, or a selector + * representing the element. Returns: The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload Unloads jsPlumb, deleting all storage. You should + * call this from an onunload attribute on the element Returns: + * void + */ + this.unload = function() { + delete endpointsByElement; + delete endpointsByUUID; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + }; + + /* + * Function: wrap Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. Parameters: + */ + this.wrap = _wrap; + + EventGenerator.apply(this); + this.addListener = this.bind; + + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(); + if (_defaults) + jsPlumb.extend(j.Defaults, _defaults); + return j; + }; +})(); +/* +* jsPlumb-defaults-1.2.4-RC1 +* +* This script contains the default Anchors, Endpoints, Connectors and Overlays for jsPlumb. It should be used with jsPlumb 1.1.0 and above; +* prior to version 1.1.0 of jsPlumb the defaults were included inside the main script. +* +* NOTE: for production usage you should use jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, +* in a minified file. +* +* Dual licensed under MIT and GPL2. +*/ + +(function() { + + var ie = !!!document.createElement('canvas').getContext; + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * getOrientation : function() { return [ox, oy]; } + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + var _curryAnchor = function(x,y,ox,oy) { + return function() { + return jsPlumb.makeAnchor(x,y,ox,oy); + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1); + + + jsPlumb.Defaults.DynamicAnchors = function() { + return jsPlumb.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"]); + }; + jsPlumb.Anchors["AutoDefault"] = function() { return jsPlumb.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors()); }; + + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + var currentPoints = null; + var _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + + _dx = _tx - _sx, _dy = (_ty - _sy); + _m = _dy / _dx, _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + + return currentPoints; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + var xp = _sx + (location * _dx); + var yp = _m == Infinity ? xp + _b : (_m * xp) + _b; + return [xp, yp]; + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { return _m; }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + var orientation = distance > 0 ? 1 : -1; + var y = Math.abs(distance * Math.sin(_theta)); + if (_sy > _ty) y = y * -1; + var x = Math.abs(distance * Math.cos(_theta)); + if (_sx > _tx) x = x * -1; + return [p[0] + (orientation * x), p[1] + (orientation * y)]; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, distance, length) { + var p = self.pointAlongPathFrom(location, distance); + var m = self.gradientAtPoint(p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [[p[0] + x, p[1] + y], [p[0] - x, p[1] - y]]; + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + /** + * This Connector draws a Bezier curve with two control points. + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + jsPlumb.Connectors.Bezier = function(curviness) { + var self = this; + this.majorAnchor = curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _sx, _sy, _canvasX, _canvasY, _w, _h; + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) + { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx); var minx2 = Math.min(_CP[0], _CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(_sx,_tx); var maxx2 = Math.max(_CP[0], _CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty); var miny2 = Math.min(_CP[1], _CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(_sy,_ty); var maxy2 = Math.max(_CP[1], _CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + this.paint = function(d, ctx) { + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the distance the given point is from the curve. not enabled for 1.2.3. didnt make the cut. next time. + * + this.distanceFrom = function(point) { + var curve = [ {x:currentPoints[4], y:currentPoints[5]}, + {x:currentPoints[8], y:currentPoints[9]}, + {x:currentPoints[10], y:currentPoints[11]}, + {x:currentPoints[6], y:currentPoints[7]}]; + return (jsBezier.distanceFromCurve(point, curve)); + };*/ + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, distance, length) { + return jsBezier.perpendicularToCurveAt(_makeCurve(), location, distance, length); + }; + + this.createGradient = function(dim, ctx, swap) { + return (swap) ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + + /** + * Types of endpoint UIs. we supply four - a circle of default radius 10px, a rectangle of + * default size 20x20, an image (with no default), and a Triangle, of default size 15. + * you can supply others of these if you want to - see the documentation for a howto. + */ + + /** + * a round endpoint, with default radius 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + } + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + if (endpointStyle.gradient && !ie) { + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * A Rectangular endpoint, with default size 20x20. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + jsPlumb.Endpoints.Triangle = function(params) { + + params = params || { width:15, height:15 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - width/2; + var y = anchorPoint[1] - height/2; + + jsPlumb.sizeCanvas(canvas, x, y, width, height); + + var ctx = canvas.getContext('2d'); + var offsetX = 0, offsetY = 0, angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = endpointStyle.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' or, since 1.2.4, a 'src' property in the params object.. + */ + jsPlumb.Endpoints.Image = function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.src || params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + }; + + /** + * An arrow overlay. you can provide: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + */ + jsPlumb.Overlays.Arrow = function(params) { + params = params || {}; + var self = this; + var length = params.length || 20; + var width = params.width || 20; + var fillStyle = params.fillStyle || "black"; + var strokeStyle = params.strokeStyle; + var lineWidth = params.lineWidth || 1; + this.loc = params.location || 0.5; + // how far along the arrow the lines folding back in come to. default is 62.3%. + var foldback = params.foldback || 0.623; + var _getFoldBackPoint = function(connector, loc) { + if (foldback == 0.5) return connector.pointOnPath(loc); + else { + var adj = 0.5 - foldback; // we calculate relative to the center + return connector.pointAlongPathFrom(loc, length * adj); + } + }; + + this.computeMaxSize = function() { return width * 1.5; } + + this.draw = function(connector, ctx) { + // this is the arrow head position + var hxy = connector.pointAlongPathFrom(self.loc, length / 2); + // this is the center of the tail + var txy = connector.pointAlongPathFrom(self.loc, -length / 2), tx = txy[0], ty = txy[1]; + // this is the tail vector + var tail = connector.perpendicularToPathAt(self.loc, -length / 2, width); + // this is the point the tail goes in to + var cxy = _getFoldBackPoint(connector, self.loc); + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(hxy[0], hxy[1]); + ctx.lineTo(tail[0][0], tail[0][1]); + ctx.lineTo(cxy[0], cxy[1]); + ctx.lineTo(tail[1][0], tail[1][1]); + ctx.lineTo(hxy[0], hxy[1]); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + + /** + * a basic arrow. this is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. See Arrow for params. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + /** + * a diamond. like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. See Arrow for params. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40; + var p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + /** + * A Label overlay. Params you can provide: + * + * labelStyle - js object containing style instructions for the label. defaults to jsPlumb.Defaults.LabelStyle. + * label - the label to paint. may be a string or a function that returns a string. nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * borderWidth - width of a border to paint. defaults to zero. + * borderStyle - strokeStyle to use when painting the border, if necessary. + */ + jsPlumb.Overlays.Label = function(params) { + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + var self = this; + var labelWidth = null, labelHeight = null, labelText = null, labelPadding = null; + this.location = params.location || 0.5; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var _textDimensions = function(ctx) { + if (self.cachedDimensions) return self.cachedDimensions; // return cached copy if we can. if we add a setLabel function remember to clear the cache. + labelText = typeof self.label == 'function' ? self.label(self) : self.label; + var d = {}; + if (labelText) { + var lines = labelText.split("\n"); + ctx.save(); + if (self.labelStyle.font) ctx.font = self.labelStyle.font; + var t = _widestLine(lines, ctx); + // a fake text height measurement: use the width of upper case M + var h = ctx.measureText("M").width; + labelPadding = self.labelStyle.padding || 0.25; + labelWidth = t + (2 * t * labelPadding); + labelHeight = (lines.length * h) + (2 * h * labelPadding); + var textHeight = lines.length * h; + ctx.restore(); + d = {width:labelWidth, height:labelHeight, lines:lines, oneLine:h, padding:labelPadding, textHeight:textHeight}; + } + if (typeof self.label != 'function') self.cachedDimensions = d; // cache it if we can. + return d; + }; + this.computeMaxSize = function(connector, ctx) { + var td = _textDimensions(ctx); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + var _widestLine = function(lines, ctx) { + var max = 0; + for (var i = 0; i < lines.length; i++) { + var t = ctx.measureText(lines[i]).width; + if (t > max) max = t; + } + return max; + }; + + this.draw = function(connector, ctx) { + var td = _textDimensions(ctx); + if (td.width) { + var cxy = connector.pointOnPath(self.location); + if (self.labelStyle.font) ctx.font = self.labelStyle.font; + if (self.labelStyle.fillStyle) + ctx.fillStyle = self.labelStyle.fillStyle; + else + ctx.fillStyle = "rgba(0,0,0,0)"; + ctx.fillRect(cxy[0] - (td.width / 2), cxy[1] - (td.height / 2) , td.width , td.height ); + + if (self.labelStyle.color) ctx.fillStyle = self.labelStyle.color; + ctx.textBaseline = "middle"; + ctx.textAlign = "center"; + for (i = 0; i < td.lines.length; i++) { + ctx.fillText(td.lines[i],cxy[0], cxy[1] - (td.textHeight / 2) + (td.oneLine/2) + (i*td.oneLine)); + } + + // border + if (self.labelStyle.borderWidth > 0) { + ctx.strokeStyle = self.labelStyle.borderStyle || "black"; + ctx.strokeRect(cxy[0] - (td.width / 2), cxy[1] - (td.height / 2) , td.width , td.height ); + } + } + }; + }; + + /** + * an image overlay. params may contain: + * + * location : proportion along the connector to draw the image. optional. + * src : image src. required. + * events : map of event names to functions; each event listener will be bound to the img. + */ + jsPlumb.Overlays.Image = function(params) { + var self = this; + this.location = params.location || 0.5; + this.img = new Image(); + var imgDiv = null; + var notReadyInterval = null; + var notReadyConnector, notReadyContext; + var events = params.events || {}; + var _init = function() { + if (self.ready) { + window.clearInterval(notReadyInterval); + imgDiv = document.createElement("img"); + imgDiv.src = self.img.src; + imgDiv.style.position = "absolute"; + imgDiv.style.display="none"; + imgDiv.className = "_jsPlumb_overlay"; + document.body.appendChild(imgDiv);// HMM + // attach events + for (var e in events) { + jsPlumb.CurrentLibrary.bind(imgDiv, e, events[e]); + } + if (notReadyConnector && notReadyContext) { + _draw(notReadyConnector, notReadyContext); + notReadyContext = null; + notReadyConnector = null; + } + } + }; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.src || params.url; + + notReadyInterval = window.setInterval(_init, 250); + + this.computeMaxSize = function(connector, ctx) { + return [self.img.width, self.img.height] + }; + + var _draw = function(connector, ctx) { + var cxy = connector.pointOnPath(self.location); + var canvas = jsPlumb.CurrentLibrary.getElementObject(ctx.canvas); + var canvasOffset = jsPlumb.CurrentLibrary.getOffset(canvas); + var o = {left:canvasOffset.left + cxy[0] - (self.img.width/2), top:canvasOffset.top + cxy[1] - (self.img.height/2)}; + jsPlumb.CurrentLibrary.setOffset(imgDiv, o); + imgDiv.style.display = "block"; + }; + + this.draw = function(connector, ctx) { + if (self.ready) + _draw(connector, ctx); + else { + notReadyConnector = connector; + notReadyContext = ctx; + } + }; + }; +})();/* + * mootools.jsPlumb 1.2.4-RC1 + * + * MooTools specific functionality for jsPlumb. + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + * NOTE: for production usage you should use mootools.jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, + * in a minified file. + * + * Dual licensed under MIT and GPL2. + * + */ + +(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function() { + this.parent(); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}; + var _droppableOptions = {}; + var _draggablesByScope = {}; + var _draggablesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }; + + + jsPlumb.CurrentLibrary = { + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' + }, + + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).grab(child); + }, + + bind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.addEvent(event, callback); + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + getElementObject : function(el) { + return $(el); + }, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + setOffset : function(el, o) { + jsPlumb.CurrentLibrary.getElementObject(el).setPosition({x:o.left, y:o.top}); + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el.removeClass(clazz); + }, + + initDraggable : function(el, options) { + var id = jsPlumb.getId(el); + var drag = _draggablesById[id]; + if (!drag) { + var originalZIndex = 0, originalCursor = null; + var dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + options['onStart'] = jsPlumb.wrap(options['onStart'], function() + { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() + { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + }); + + // DROPPABLES: + var scope = options['scope'] || jsPlumb.Defaults.Scope; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop'); + } + }; + + drag = new Drag.Move(el, options); + _add(_draggablesByScope, scope, drag); + _add(_draggablesById, el.get("id"), drag); + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + initDroppable : function(el, options) { + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _add(_droppables, scope, el); + var id = jsPlumb.getId(el); + _droppableOptions[id] = options; + var filterFunc = function(entry) { + return entry.element != el; + }; + var draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + if (typeof Drag != undefined) + return typeof Drag.Move != undefined; + return false; + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + m.start(properties); + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[0]; + return { left: ui.offsetLeft, top: ui.offsetTop }; + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + }, + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).dispose(); // ?? + }, + + getScrollLeft : function(el) { + return null; + }, + + getScrollTop : function(el) { + return null; + } + }; +})(); +/** +* a set of bezier curve functions, used by jsPlumb, and perhaps useful for other people. +* +* functions are all in the 'jsBezier' namespace. here's the list: +* +* distanceFromCurve(point, curve) +* gradientAtPoint(curve, location) +* nearestPointOnCurve(point, curve) ?? +* pointOnCurve(curve, location) +* pointAlongCurveFrom +* perpendicularToCurveAt +* quadraticPointOnCurve +*/ + +(function() { + +var MAXDEPTH = 64, EPSILON = Math.pow(2.0,-MAXDEPTH-1), DEGREE = 3, W_DEGREE = 5; +var V2Sub = function(from, p) { return {x:from.x - p.x, y:from.y - p.y }; }; +var V2Dot = function(v1, v2) { return (v1.x * v2.x) + (v1.y * v2.y); }; +var V2SquaredLength = function(v) { return (v.x * v.x) + (v.y * v.y); }; +var V2ScaleII = function(v, s) { return {x:v.x * s, y:v.y * s }; }; + +/** + * Calculates the distance that the point P is from the curve V. i think ;) + * i need to verify that. + */ +var _distanceFromCurve = function(P, V) { + var w, n_solutions, t; + var t_candidate = new Array(W_DEGREE); + w = _convertToBezierForm(P, V); + n_solutions = _findRoots(w, W_DEGREE, t_candidate, 0); + var dist, new_dist, p, v, i; + v = V2Sub(P, V[0]); + dist = V2SquaredLength(v); + t = 0.0; + for (i = 0; i < n_solutions; i++) { + p = Bezier(V, DEGREE, t_candidate[i], + null, null); + v = V2Sub(P, p); + new_dist = V2SquaredLength(v); + if (new_dist < dist) { + dist = new_dist; + t = t_candidate[i]; + } + } + v = V2Sub(P, V[DEGREE]); + new_dist = V2SquaredLength(v); + if (new_dist < dist) { + dist = new_dist; + t = 1.0; + } + return {t:t,d:dist}; +}; +var _nearestPointOnCurve = function(P, V) { + var td = _distanceFromCurve(P, V); + return Bezier(V, DEGREE, td.t, null, null); +}; +var _convertToBezierForm = function(P, V) { + var i, j, k, m, n, ub, lb, w, row, column; + var c = new Array(DEGREE+1), d = new Array(DEGREE); + var cdTable = []; + var z = [ [1.0, 0.6, 0.3, 0.1], [0.4, 0.6, 0.6, 0.4], [0.1, 0.3, 0.6, 1.0] ]; + for (i = 0; i <= DEGREE; i++) + c[i] = V2Sub(V[i], P); + for (i = 0; i <= DEGREE - 1; i++) { + d[i] = V2Sub(V[i+1], V[i]); + d[i] = V2ScaleII(d[i], 3.0); + } + for (row = 0; row <= DEGREE - 1; row++) { + for (column = 0; column <= DEGREE; column++) { + if (!cdTable[row]) cdTable[row] = []; + cdTable[row][column] = V2Dot(d[row], c[column]); + } + } + w = []; + for (i = 0; i <= W_DEGREE; i++) { + if (!w[i]) w[i] = []; + w[i].y = 0.0; + w[i].x = parseFloat(i) / W_DEGREE; + } + n = DEGREE; + m = DEGREE-1; + for (k = 0; k <= n + m; k++) { + lb = Math.max(0, k - m); + ub = Math.min(k, n); + for (i = lb; i <= ub; i++) { + j = k - i; + w[i+j].y += cdTable[j][i] * z[j][i]; + } + } + return (w); +}; +var _findRoots = function(w, degree, t, depth) { + var i; + var Left = new Array(W_DEGREE+1), Right = new Array(W_DEGREE+1); + var left_count, right_count; + var left_t = new Array(W_DEGREE+1), right_t = new Array(W_DEGREE+1); + switch (CrossingCount(w, degree)) { + case 0 : { + return 0; + } + case 1 : { + if (depth >= MAXDEPTH) { + t[0] = (w[0].x + w[W_DEGREE].x) / 2.0; + return 1; + } + if (ControlPolygonFlatEnough(w, degree)) { + t[0] = ComputeXIntercept(w, degree); + return 1; + } + break; + } + } + Bezier(w, degree, 0.5, Left, Right); + left_count = FindRoots(Left, degree, left_t, depth+1); + right_count = FindRoots(Right, degree, right_t, depth+1); + for (i = 0; i < left_count; i++) t[i] = left_t[i]; + for (i = 0; i < right_count; i++) t[i+left_count] = right_t[i]; + return (left_count+right_count); +}; +var CrossingCount = function(V, degree) { + var n_crossings = 0; + var sign, old_sign; + var SGN = function(x) { return x == 0 ? 0 : x > 0 ? 1 :-1; }; + sign = old_sign = SGN(V[0].y); + for (var i = 1; i <= degree; i++) { + sign = SGN(V[i].y); + if (sign != old_sign) n_crossings++; + old_sign = sign; + } + return n_crossings; +}; +var ControlPolygonFlatEnough = function(V, degree) { + var value, error; + var intercept_1, intercept_2, left_intercept, right_intercept; + var a, b, c, det, dInv, a1, b1, c1, a2, b2, c2; + a = V[0].y - V[degree].y; + b = V[degree].x - V[0].x; + c = V[0].x * V[degree].y - V[degree].x * V[0].y; + + var max_distance_above = max_distance_below = 0.0; + + for (var i = 1; i < degree; i++) + { + value = a * V[i].x + b * V[i].y + c; + + if (value > max_distance_above) + { + max_distance_above = value; + } + else if (value < max_distance_below) + { + max_distance_below = value; + } + } + a1 = 0.0; b1 = 1.0; c1 = 0.0; a2 = a; b2 = b; + c2 = c - max_distance_above; + det = a1 * b2 - a2 * b1; + dInv = 1.0/det; + intercept_1 = (b1 * c2 - b2 * c1) * dInv; + a2 = a; b2 = b; c2 = c - max_distance_below; + det = a1 * b2 - a2 * b1; + dInv = 1.0/det; + intercept_2 = (b1 * c2 - b2 * c1) * dInv; + left_intercept = Math.min(intercept_1, intercept_2); + right_intercept = Math.max(intercept_1, intercept_2); + error = right_intercept - left_intercept; + return (error < EPSILON)? 1 : 0; +}; +var ComputeXIntercept = function(V, degree) { + var XLK, YLK, XNM, YNM, XMK, YMK, det, detInv, S, T, X, Y; + XLK = 1.0 - 0.0; + YLK = 0.0 - 0.0; + XNM = V[degree].x - V[0].x; + YNM = V[degree].y - V[0].y; + XMK = V[0].x - 0.0; + YMK = V[0].y - 0.0; + det = XNM*YLK - YNM*XLK; + detInv = 1.0/det; + S = (XNM*YMK - YNM*XMK) * detInv; + X = 0.0 + XLK * S; + return X; +}; +var Bezier = function(V, degree, t, Left, Right) { + var Vtemp = new Array(); + for (var j =0; j <= degree; j++) { + if (!Vtemp[0]) Vtemp[0] = []; + Vtemp[0][j] = V[j]; + } + for (var i = 1; i <= degree; i++) { + for (var j =0 ; j <= degree - i; j++) { + if (!Vtemp[i]) Vtemp[i] = []; + if (!Vtemp[i][j]) Vtemp[i][j] = {}; + Vtemp[i][j].x = (1.0 - t) * Vtemp[i-1][j].x + t * Vtemp[i-1][j+1].x; + Vtemp[i][j].y = (1.0 - t) * Vtemp[i-1][j].y + t * Vtemp[i-1][j+1].y; + } + } + if (Left != null) + for (j = 0; j <= degree; j++) Left[j] = Vtemp[j][0]; + if (Right != null) + for (j = 0; j <= degree; j++) Right[j] = Vtemp[degree-j][j]; + return (Vtemp[degree][0]); +}; + +var _pointOnPath = function(curve, location) { + // from http://13thparallel.com/archive/bezier-curves/ + function B1(t) { return t*t*t }; + function B2(t) { return 3*t*t*(1-t) }; + function B3(t) { return 3*t*(1-t)*(1-t) }; + function B4(t) { return (1-t)*(1-t)*(1-t) }; + + var x = curve[0].x*B1(location) + curve[1].x * B2(location) + curve[2].x * B3(location) + curve[3].x * B4(location); + var y = curve[0].y*B1(location) + curve[1].y * B2(location) + curve[2].y * B3(location) + curve[3].y * B4(location); + return [x,y]; +}; + +var _quadraticPointOnPath = function(curve, location) { + function B1(t) { return t*t; }; + function B2(t) { return 2*t*(1-t); }; + function B3(t) { return (1-t)*(1-t); }; + var x = curve[0].x*B1(location) + curve[1].x*B2(location) + curve[2].x*B3(location); + var y = curve[0].y*B1(location) + curve[1].y*B2(location) + curve[2].y*B3(location); + return [x,y]; +}; + +/** + * finds the point that is 'distance' along the path from 'location'. this method returns both the x,y location of the point and also + * its 'location' (proportion of travel along the path). + */ +var _pointAlongPath = function(curve, location, distance) { + var _dist = function(p1,p2) { return Math.sqrt(Math.pow(p1[0] - p2[0], 2) + Math.pow(p1[1] - p2[1], 2)); }; + var prev = _pointOnPath(curve, location), tally = 0, curLoc = location, direction = distance > 0 ? 1 : -1, cur = null; + while (tally < Math.abs(distance)) { + curLoc += (0.005 * direction); + cur = _pointOnPath(curve, curLoc); + tally += _dist(cur, prev); + prev = cur; + } + return {point:cur, location:curLoc}; +}; + +var _pointAlongPathFrom = function(curve, location, distance) { + return _pointAlongPath(curve, location, distance).point; +}; + +/** + * returns the gradient of the connector at the given location, which is a decimal between 0 and 1 inclusive. + * + * thanks // http://bimixual.org/AnimationLibrary/beziertangents.html + */ +var _gradientAtPoint = function(curve, location) { + var p1 = _pointOnPath(curve, location); + var p2 = _quadraticPointOnPath(curve, location); + var dy = p2[1] - p1[1], dx = p2[0] - p1[0]; + return Math.atan(dy / dx); +}; + +/** + * calculates a line that is 'length' pixels long, perpendicular to, and centered on, the path at 'distance' pixels from the given location. + */ +var _perpendicularToPathAt = function(curve, location, distance, length) { + var p = _pointAlongPath(curve, location, distance); + var m = _gradientAtPoint(curve, p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [[p.point[0] + x, p.point[1] + y], [p.point[0] - x, p.point[1] - y]]; +}; + +var jsBezier = window.jsBezier = { + distanceFromCurve : _distanceFromCurve, + gradientAtPoint : _gradientAtPoint, + nearestPointOnCurve : _nearestPointOnCurve, + pointOnCurve : _pointOnPath, + pointAlongCurveFrom : _pointAlongPathFrom, + perpendicularToCurveAt : _perpendicularToPathAt, + quadraticPointOnCurve : _quadraticPointOnPath //TODO fold the two pointOnPath functions into one; it can detect what it was given. +}; + +})(); diff --git a/archive/1.2.5/jquery.jsPlumb-1.2.5-RC1.js b/archive/1.2.5/jquery.jsPlumb-1.2.5-RC1.js new file mode 100644 index 000000000..73d74c3d2 --- /dev/null +++ b/archive/1.2.5/jquery.jsPlumb-1.2.5-RC1.js @@ -0,0 +1,312 @@ +/* + * jquery.jsPlumb 1.2.5-RC1 + * + * jQuery specific functionality for jsPlumb. + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + * NOTE: for production usage you should use jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, + * in a minified file. + * + * Dual licensed under MIT and GPL2. + * + */ + +(function($){ + /** + * plumbs the results of the selector to some target, using the given options if supplied, + * or the defaults otherwise. + */ + $.fn.plumb = function(options) { + var options = $.extend({}, options); + + return this.each(function() + { + var params = $.extend({source:$(this)}, options); + jsPlumb.connect(params); + }); + }; + + /** + * detaches the results of the selector from the given target or list of targets - 'target' + * may be a String or a List. + */ + $.fn.detach = function(target) { + return this.each(function() { + if (target) { + var id = $(this).attr("id"); + if (typeof target == 'string') target = [target]; + for (var i = 0; i < target.length; i++) + jsPlumb.detach(id, target[i]); + } + }); + }; + + /** + * detaches the results from the selector from all connections. + */ + $.fn.detachAll = function() { + return this.each(function() + { + var id = $(this).attr("id"); + jsPlumb.detachAll(id); + }); + }; + + /** + * adds an endpoint to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoint = function(options) { + var addedEndpoints = []; + this.each(function() { + addedEndpoints.push(jsPlumb.addEndpoint($(this).attr("id"), options)); + }); + return addedEndpoints[0]; + }; + + /** + * adds a list of endpoints to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoints = function(endpoints) { + var addedEndpoints = []; + return this.each(function() { + var e = jsPlumb.addEndpoints($(this).attr("id"), endpoints); + for (var i = 0; i < e.length; i++) addedEndpoints.push(e[i]); + }); + }; + + /** + * remove the endpoint, if it exists, deleting its UI elements etc. + */ + $.fn.removeEndpoint = function(endpoint) { + this.each(function() { + jsPlumb.removeEndpoint($(this).attr("id"), endpoint); + }); + }; + +})(jQuery); + + +/*TODO: abstract this out from jQuery too! but how...because jsPlumb is not loaded yet. +$(window).bind('resize', function() { + if (resizeTimer) clearTimeout(resizeTimer); + resizeTimer = setTimeout(repaintEverything, 100); + });*/ + + + +/* + * the library agnostic functions, such as find offset, get id, get attribute, extend etc. + * the full list is: + * + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setOffset sets the offset of some element. + */ +(function($) { + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * appends the given child to the given parent. + */ + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).append(child); + }, + + /** + * event binding wrapper. it just so happens that jQuery uses 'bind' also. yui3, for example, + * uses 'on'. + */ + bind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.bind(event, callback); + }, + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step', + 'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete' + }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + * + */ + getElementObject : function(el) { + return typeof(el)=='string' ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + getScrollLeft : function(el) { + return el.scrollLeft(); + }, + + getScrollTop : function(el) { + return el.scrollTop(); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[1]; + //return ui.absolutePosition || ui.offset; + return ui.offset || ui.absolutePosition; + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options) { + // remove helper directive if present. + options.helper = null; + //TODO: if 'revert' is set on the options it causes end points to animate back to + // where they came from, if the connection is aborted. do we care? probably not. + // the todo is to decide whether we care or not. + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.draggable(options); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.droppable(options); + }, + + isAlreadyDraggable : function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return el.hasClass("ui-draggable"); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).remove(); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + setOffset : function(el, o) { + jsPlumb.CurrentLibrary.getElementObject(el).offset(o); + } + }; +})(jQuery); diff --git a/archive/1.2.5/jquery.jsPlumb-1.2.5-all-min.js b/archive/1.2.5/jquery.jsPlumb-1.2.5-all-min.js new file mode 100644 index 000000000..64e0427af --- /dev/null +++ b/archive/1.2.5/jquery.jsPlumb-1.2.5-all-min.js @@ -0,0 +1 @@ +(function(){var a=function(){var ac=function(){var ah={};this.bind=function(ak,al){var ai=function(an,am){B(ah,an,am)};if(ak.constructor==Array){for(var aj=0;aj=0){delete (ah[ai]);ah.splice(ai,1);return true}}}return false};var c=function(ai,ah){return s(ai,function(aj,ak){S[ak]=ah;if(b.CurrentLibrary.isDragSupported(aj)){b.CurrentLibrary.setDraggable(aj,ah)}})};var aa=function(ah,ai){D(q(ah,"id"),function(aj){aj.canvas.style.display=ai})};var w=function(ah){return s(ah,function(aj,ai){var ak=S[ai]==null?I:S[ai];ak=!ak;S[ai]=ak;b.CurrentLibrary.setDraggable(aj,ak);return ak})};var e=function(ah){D(ah,function(aj){var ai=("none"==aj.canvas.style.display);aj.canvas.style.display=ai?"block":"none"})};var m=function(am){var ak=am.timestamp,ah=am.recalc,al=am.offset,ai=am.elId;if(!ah){if(ak&&ak===k[ai]){return}}if(ah||al==null){var aj=U(ai);y[ai]=h(aj);F[ai]=d(aj);k[ai]=ak}else{F[ai]=al}};var Z=function(aj,ah,ai){aj=aj||function(){};ah=ah||function(){};return function(){var ak=null;try{ak=ah.apply(this,arguments)}catch(al){n("jsPlumb function failed : "+al)}if(ai==null||(ak!==ai)){try{aj.apply(this,arguments)}catch(al){n("wrapped function failed : "+al)}}return ak}};var M=function(al){var aj=this;this.x=al.x||0;this.y=al.y||0;var ai=al.orientation||[0,0];var ak=null,ah=null;this.offsets=al.offsets||[0,0];aj.timestamp=null;this.compute=function(ar){var ax=ar.xy,an=ar.wh,at=ar.element,au=ar.timestamp;if(au&&au===aj.timestamp){return ah}ah=[ax[0]+(aj.x*an[0])+aj.offsets[0],ax[1]+(aj.y*an[1])+aj.offsets[1]];var ao=at?at.container:null;var av={left:0,top:0};if(ao!=null){var am=U(ao);var ap=d(am);var aq=b.CurrentLibrary.getScrollLeft(am);var aw=b.CurrentLibrary.getScrollTop(am);av.left=ap.left-aq;av.top=ap.top-aw;ah[0]=ah[0]-av.left;ah[1]=ah[1]-av.top}aj.timestamp=au;return ah};this.getOrientation=function(){return ai};this.equals=function(am){if(!am){return false}var an=am.getOrientation();var ap=this.getOrientation();return this.x==am.x&&this.y==am.y&&this.offsets[0]==am.offsets[0]&&this.offsets[1]==am.offsets[1]&&ap[0]==an[0]&&ap[1]==an[1]};this.getCurrentLocation=function(){return ah}};var r=function(an){var al=an.reference;var am=an.referenceCanvas;var aj=h(U(am));var ai=0,ao=0;var ah=null;var ak=null;this.compute=function(au){var ar=au.xy,aq=au.element;var ap=[ar[0]+(aj[0]/2),ar[1]+(aj[1]/2)];if(aq.container!=null){var at=d(aq.container);ap[0]=ap[0]-at.left;ap[1]=ap[1]-at.top}ak=ap;return ap};this.getOrientation=function(){if(ah){return ah}else{var ap=al.getOrientation();return[Math.abs(ap[0])*ai*-1,Math.abs(ap[1])*ao*-1]}};this.over=function(ap){ah=ap.getOrientation()};this.out=function(){ah=null};this.getCurrentLocation=function(){return ak}};var K=function(aj,ai){this.isSelective=true;this.isDynamic=true;var aq=aj||[];var ao=function(ar){return ar.constructor==M?ar:b.makeAnchor(ar)};for(var an=0;an0?aq[0]:null;var am=aq.length>0?0:-1;this.locked=false;var ap=this;var al=function(au,ar,aA,az,at){var aw=az[0]+(au.x*at[0]),av=az[1]+(au.y*at[1]);return Math.sqrt(Math.pow(ar-aw,2)+Math.pow(aA-av,2))};var ah=ai||function(aC,at,au,av,ar){var ax=au[0]+(av[0]/2),aw=au[1]+(av[1]/2);var az=-1,aB=Infinity;for(var ay=0;ayax){ax=aF}}var aK=this.connector.compute(az,aM,this.endpoints[aQ].anchor,this.endpoints[aw].anchor,this.paintStyle.lineWidth,ax);b.sizeCanvas(aj,aK[0],aK[1],aK[2],aK[3]);var aD=function(aR,aU){aR.save();b.extend(aR,aU);if(aU.gradient&&!R){var aT=at.connector.createGradient(aK,aR,(aE==this.sourceId));for(var aS=0;aS=0){au.connections.splice(aH,1);if(!aK){var aJ=aI.endpoints[0]==au?aI.endpoints[1]:aI.endpoints[0];aJ.detach(aI,true)}O(aI.canvas,aI.container);v(u,aI.scope,aI);if(!aK){N(aI)}}};this.detachAll=function(){while(au.connections.length>0){au.detach(au.connections[0])}};this.detachFrom=function(aI){var aJ=[];for(var aH=0;aH=0){au.connections.splice(aH,1)}};this.getElement=function(){return at};this.getUuid=function(){return ap};this.makeInPlaceCopy=function(){var aH=new ab({anchor:au.anchor,source:at,style:an,endpoint:ar});return aH};this.isConnectedTo=function(aJ){var aI=false;if(aJ){for(var aH=0;aH=aA)};this.setDragAllowedWhenFull=function(aH){au.dragAllowedWhenFull=aH};this.equals=function(aH){return this.anchor.equals(aH.anchor)};this.paint=function(aK){aK=aK||{};var aO=aK.timestamp;if(!aO||au.timestamp!==aO){var aN=aK.anchorPoint,aJ=aK.canvas,aL=aK.connectorPaintStyle;if(aN==null){var aT=aK.offset||F[am];var aH=aK.dimensions||y[am];if(aT==null||aH==null){m({elId:am,timestamp:aO});aT=F[am];aH=y[am]}var aI={xy:[aT.left,aT.top],wh:aH,element:au,timestamp:aO};if(au.anchor.isDynamic){if(au.connections.length>0){var aQ=au.connections[0];var aS=aQ.endpoints[0]==au?1:0;var aM=aS==0?aQ.sourceId:aQ.targetId;var aP=F[aM],aR=y[aM];aI.txy=[aP.left,aP.top];aI.twh=aR;aI.tElement=aQ.endpoints[aS]}}aN=au.anchor.compute(aI)}ar.paint(aN,au.anchor.getOrientation(),aJ||au.canvas,an,aL||an);au.timestamp=aO}};this.removeConnection=this.detach;if(aG.isSource&&b.CurrentLibrary.isDragSupported(at)){var az=null,av=null,ay=null,ah=false,ai=null;var ak=function(){ay=aD();if(au.isFull()&&!au.dragAllowedWhenFull){return false}m({elId:am});aj=au.makeInPlaceCopy();aj.paint();az=document.createElement("div");var aJ=U(az);A(az,au.container);var aK=ag(aJ);m({elId:aK});Q(U(au.canvas),"dragId",aK);Q(U(au.canvas),"elId",am);var aI=new r({reference:au.anchor,referenceCanvas:au.canvas});aE=new ab({style:{fillStyle:"rgba(0,0,0,0)"},endpoint:ar,anchor:aI,source:aJ});if(ay==null){au.anchor.locked=true;ay=new p({sourceEndpoint:au,targetEndpoint:aE,source:U(at),target:U(az),anchors:[au.anchor,aI],paintStyle:aG.connectorStyle,connector:aG.connector,overlays:au.connectorOverlays})}else{ah=true;al(U(aj.canvas));var aH=ay.sourceId==am?0:1;ay.floatingAnchorIndex=aH;au.detachFromConnection(ay);if(aH==0){ai=[ay.source,ay.sourceId];ay.source=U(az);ay.sourceId=aK}else{ai=[ay.target,ay.targetId];ay.target=U(az);ay.targetId=aK}ay.endpoints[aH==0?1:0].anchor.locked=true;ay.suspendedEndpoint=ay.endpoints[aH];ay.endpoints[aH]=aE}J[aK]=ay;aE.addConnection(ay);B(T,aK,aE)};var aB=aG.dragOptions||{};var aw=b.extend({},b.CurrentLibrary.defaultDragOptions);aB=b.extend(aw,aB);aB.scope=aB.scope||au.scope;var ax=b.CurrentLibrary.dragEvents.start;var aF=b.CurrentLibrary.dragEvents.stop;var ao=b.CurrentLibrary.dragEvents.drag;aB[ax]=Z(aB[ax],ak);aB[ao]=Z(aB[ao],function(){var aH=b.CurrentLibrary.getUIPosition(arguments);b.CurrentLibrary.setOffset(az,aH);ae(U(az),aH)});aB[aF]=Z(aB[aF],function(){v(T,av,aE);z([az,aE.canvas],at);O(aj.canvas,at);var aH=ay.floatingAnchorIndex==null?1:ay.floatingAnchorIndex;ay.endpoints[aH==0?1:0].anchor.locked=false;if(ay.endpoints[aH]==aE){if(ah&&ay.suspendedEndpoint){if(aH==0){ay.source=ai[0];ay.sourceId=ai[1]}else{ay.target=ai[0];ay.targetId=ai[1]}ay.endpoints[aH]=ay.suspendedEndpoint;if(aq){ay.floatingAnchorIndex=null;ay.suspendedEndpoint.addConnection(ay);b.repaint(ai[1])}else{ay.endpoints[aH==0?1:0].detach(ay)}}else{O(ay.canvas,au.container);au.detachFromConnection(ay)}}au.anchor.locked=false;au.paint();ay.repaint();ay=null;delete aj;delete T[aE.elementId];delete aE});var aC=U(au.canvas);b.CurrentLibrary.initDraggable(aC,aB)}var al=function(aK){if(aG.isTarget&&b.CurrentLibrary.isDropSupported(at)){var aH=aG.dropOptions||l.Defaults.DropOptions||b.Defaults.DropOptions;aH=b.extend({},aH);aH.scope=aH.scope||au.scope;var aN=null;var aL=b.CurrentLibrary.dragEvents.drop;var aM=b.CurrentLibrary.dragEvents.over;var aI=b.CurrentLibrary.dragEvents.out;var aJ=function(){var aP=U(b.CurrentLibrary.getDragObject(arguments));var aV=q(aP,"dragId");var aR=q(aP,"elId");var aT=J[aV];var aO=aT.floatingAnchorIndex==null?1:aT.floatingAnchorIndex,aS=aO==0?1:0;if(!au.isFull()&&!(aO==0&&!au.isSource)&&!(aO==1&&!au.isTarget)){if(aO==0){aT.source=at;aT.sourceId=am}else{aT.target=at;aT.targetId=am}aT.endpoints[aO].detachFromConnection(aT);if(aT.suspendedEndpoint){aT.suspendedEndpoint.detachFromConnection(aT)}aT.endpoints[aO]=au;au.addConnection(aT);if(!aT.suspendedEndpoint){B(u,aT.scope,aT);x(at,aG.draggable,{})}else{var aU=aT.suspendedEndpoint.getElement(),aQ=aT.suspendedEndpoint.elementId;l.fireUpdate("jsPlumbConnectionDetached",{source:aO==0?aU:aT.source,target:aO==1?aU:aT.target,sourceId:aO==0?aQ:aT.sourceId,targetId:aO==1?aQ:aT.targetId,sourceEndpoint:aO==0?aT.suspendedEndpoint:aT.endpoints[0],targetEndpoint:aO==1?aT.suspendedEndpoint:aT.endpoints[1]})}b.repaint(aR);l.fireUpdate("jsPlumbConnection",{source:aT.source,target:aT.target,sourceId:aT.sourceId,targetId:aT.targetId,sourceEndpoint:aT.endpoints[0],targetEndpoint:aT.endpoints[1]})}delete J[aV]};aH[aL]=Z(aH[aL],aJ);aH[aM]=Z(aH[aM],function(){var aP=b.CurrentLibrary.getDragObject(arguments);var aR=q(U(aP),"dragId");var aQ=J[aR];var aO=aQ.floatingAnchorIndex==null?1:aQ.floatingAnchorIndex;aQ.endpoints[aO].anchor.over(au.anchor)});aH[aI]=Z(aH[aI],function(){var aP=b.CurrentLibrary.getDragObject(arguments);var aR=q(U(aP),"dragId");var aQ=J[aR];var aO=aQ.floatingAnchorIndex==null?1:aQ.floatingAnchorIndex;aQ.endpoints[aO].anchor.out()});b.CurrentLibrary.initDroppable(aK,aH)}};al(U(au.canvas));return au};this.Defaults={Anchor:null,Anchors:[null,null],BackgroundPaintStyle:null,Connector:null,Container:null,DragOptions:{},DropOptions:{},Endpoint:null,Endpoints:[null,null],EndpointStyle:{fillStyle:null},EndpointStyles:[null,null],LabelStyle:{fillStyle:"rgba(0,0,0,0)",color:"black"},LogEnabled:true,MaxConnections:null,PaintStyle:{lineWidth:10,strokeStyle:"red"},Scope:"_jsPlumb_DefaultScope"};this.logEnabled=this.Defaults.LogEnabled;this.connectorClass="_jsPlumb_connector";this.endpointClass="_jsPlumb_endpoint";this.overlayClass="_jsPlumb_overlay";this.Anchors={};this.Connectors={};this.Endpoints={};this.Overlays={};this.addEndpoint=function(am,an){an=b.extend({},an);an.endpoint=an.endpoint||l.Defaults.Endpoint||b.Defaults.Endpoint;an.endpointStyle=an.endpointStyle||l.Defaults.EndpointStyle||b.Defaults.EndpointStyle;var ak=U(am),ao=q(ak,"id");an.source=ak;m({elId:ao});var al=new ab(an);B(T,ao,al);var ah=F[ao],aj=y[ao];var ai=al.anchor.compute({xy:[ah.left,ah.top],wh:aj,element:al});al.paint({anchorLoc:ai});return al};this.addEndpoints=function(ak,ah){var aj=[];for(var ai=0;ai0?P(at,ar)!=-1:true};for(var al in u){if(aj(ao,al)){ah[al]=[];for(var ak=0;ak=4){ai.orientation=[arguments[2],arguments[3]]}if(arguments.length==6){ai.offsets=[arguments[4],arguments[5]]}}var an=new M(ai);an.clone=function(){return new M(ai)};return an};this.makeAnchors=function(ai){var aj=[];for(var ah=0;ah0?1:-1;var v=Math.abs(w*Math.sin(f));if(g>c){v=v*-1}var r=Math.abs(w*Math.cos(f));if(h>d){r=r*-1}return{x:u.x+(t*r),y:u.y+(t*v)}};this.perpendicularToPathAt=function(u,v,A){var w=q.pointAlongPathFrom(u,A);var t=q.gradientAtPoint(w.location);var s=Math.atan(-1/t);var z=v/2*Math.sin(s);var r=v/2*Math.cos(s);return[{x:w.x+r,y:w.y+z},{x:w.x-r,y:w.y-z}]};this.createGradient=function(s,r){return r.createLinearGradient(s[4],s[5],s[6],s[7])}};jsPlumb.Connectors.Bezier=function(c){var q=this;this.majorAnchor=c||150;this.minorAnchor=10;var k=null;this._findControlPoint=function(A,r,v,y,s){var x=y.getOrientation(),z=s.getOrientation();var u=x[0]!=z[0]||x[1]==z[1];var t=[];var B=q.majorAnchor,w=q.minorAnchor;if(!u){if(x[0]==0){t.push(r[0]n){n=w}if(z<0){f+=z;var A=Math.abs(z);n+=A;p[0]+=A;l+=A;d+=A;o[0]+=A}var I=Math.min(g,_ty);var G=Math.min(p[1],o[1]);var v=Math.min(I,G);var B=Math.max(g,_ty);var y=Math.max(p[1],o[1]);var t=Math.max(B,y);if(t>h){h=t}if(v<0){e+=v;var x=Math.abs(v);h+=x;p[1]+=x;g+=x;_ty+=x;o[1]+=x}if(F&&nm){m=p}}return m};this.draw=function(n,m){var p=h(m);if(p.width){var o=n.pointOnPath(d.location);if(d.labelStyle.font){m.font=d.labelStyle.font}if(d.labelStyle.fillStyle){m.fillStyle=d.labelStyle.fillStyle}else{m.fillStyle="rgba(0,0,0,0)"}m.fillRect(o.x-(p.width/2),o.y-(p.height/2),p.width,p.height);if(d.labelStyle.color){m.fillStyle=d.labelStyle.color}m.textBaseline="middle";m.textAlign="center";for(i=0;i0){m.strokeStyle=d.labelStyle.borderStyle||"black";m.strokeRect(o.x-(p.width/2),o.y-(p.height/2),p.width,p.height)}}}};jsPlumb.Overlays.Image=function(e){var l=this;this.location=e.location||0.5;this.img=new Image();var m=null;var f=null;var d,c;var k=e.events||{};var h=function(){if(l.ready){window.clearInterval(f);m=document.createElement("img");m.src=l.img.src;m.style.position="absolute";m.style.display="none";m.className="_jsPlumb_overlay";document.body.appendChild(m);for(var n in k){jsPlumb.CurrentLibrary.bind(m,n,k[n])}if(d&&c){g(d,c);c=null;d=null}}};this.img.onload=function(){l.ready=true};this.img.src=e.src||e.url;f=window.setInterval(h,250);this.computeMaxSize=function(o,n){return[l.img.width,l.img.height]};var g=function(q,p){var s=q.pointOnPath(l.location);var r=jsPlumb.CurrentLibrary.getElementObject(p.canvas);var n=jsPlumb.CurrentLibrary.getOffset(r);var t={left:n.left+s.x-(l.img.width/2),top:n.top+s.y-(l.img.height/2)};jsPlumb.CurrentLibrary.setOffset(m,t);m.style.display="block"};this.draw=function(o,n){if(l.ready){g(o,n)}else{d=o;c=n}}}})();(function(a){a.fn.plumb=function(b){var b=a.extend({},b);return this.each(function(){var c=a.extend({source:a(this)},b);jsPlumb.connect(c)})};a.fn.detach=function(b){return this.each(function(){if(b){var d=a(this).attr("id");if(typeof b=="string"){b=[b]}for(var c=0;c0?1:-1}}var h={subtract:function(x,w){return{x:x.x-w.x,y:x.y-w.y}},dotProduct:function(x,w){return(x.x*w.x)+(x.y*w.y)},square:function(w){return Math.sqrt((w.x*w.x)+(w.y*w.y))},scale:function(w,x){return{x:w.x*x,y:w.y*x}}};var o=64,r=Math.pow(2,-o-1),d=3,a=5;var p=function(E,x){var B=new Array(a);var D=e(E,x);var A=q(D,a,B,0);var F=h.subtract(E,x[0]),C=h.square(F),G=0;for(var z=0;z=o){H[0]=(F[0].x+F[a].x)/2;return 1}if(n(F,y)){H[0]=f(F,y);return 1}break}t(F,y,0.5,C,D);B=q(C,y,G,z+1);E=q(D,y,x,z+1);for(A=0;AJ){J=I}else{if(I0?1:-1,E=null;while(z endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + var endpointsByElement = {}; + var endpointsByUUID = {}; + var connectionsByScope = {}; + var offsets = {}; + var offsetTimestamps = {}; + var floatingConnections = {}; + var draggableStates = {}; + var _draggableByDefault = true; + var sizes = []; + var listeners = {}; // a map: keys are event types, values are lists of listeners. + var DEFAULT_SCOPE = 'DEFAULT'; + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + var _findIndex = function(a, v, b, s) { + var _eq = function(o1, o2) { + if (o1 === o2) + return true; + else if (typeof o1 == 'object' && typeof o2 == 'object') { + var same = true; + for ( var propertyName in o1) { + if (!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for ( var propertyName in o2) { + if (!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for ( var i = +b || 0, l = a.length; i < l; i++) { + if (_eq(a[i], v)) + return i; + } + return -1; + }; + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }; + + /** + * appends an element to the given parent, or the document body if no + * parent given. + */ + var _appendElement = function(canvas, parent) { + if (!parent) + document.body.appendChild(canvas); + else + jsPlumb.CurrentLibrary.appendElement(canvas, parent); + }; + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + var _timestamp = function() { return "" + (new Date()).getTime(); }; + + /** + * Draws an endpoint and its connections. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + var _draw = function(element, ui, timestamp) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (!timestamp) timestamp = _timestamp(); + if (endpoints) { + _updateOffset( { elId : id, offset : ui, recalc : false, timestamp : timestamp }); // timestamp is checked against last update cache; it is + // valid for one paint cycle. + var myOffset = offsets[id], myWH = sizes[id]; + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + var l = endpoints[i].connections; + for ( var j = 0; j < l.length; j++) { + l[j].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); // ...paint each connection. + // then, check for dynamic endpoint; need to repaint it. + var oIdx = l[j].endpoints[0] == endpoints[i] ? 1 : 0; + if (l[j].endpoints[oIdx].anchor.isDynamic && !l[j].endpoints[oIdx].isFloating()) { + var oId = oIdx == 0 ? l[j].sourceId : l[j].targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + // TODO i still want to make this faster. + var anchorLoc = l[j].endpoints[oIdx].anchor.compute( { + xy : [ oOffset.left, oOffset.top ], + wh : oWH, + element : l[j].endpoints[oIdx], + txy : [ myOffset.left, myOffset.top ], + twh : myWH, + tElement : endpoints[i] + }); + l[j].endpoints[oIdx].paint({ anchorLoc : anchorLoc }); + } + } + } + } + }; + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + var _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }; + + /** + * + */ + var _log = function(msg) { + if (_currentInstance.logEnabled && typeof console != "undefined") + console.log(msg); + }; + + var _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }; + var _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }; + var _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }; + var _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }; + var _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }; + var _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }; + var _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }; + + /** + * gets an id for the given element, creating and setting one if + * necessary. + */ + var _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else + id = "jsPlumb_" + _timestamp(); + _setAttribute(ele, "id", id); + } + return id; + }; + + /** + * gets an Endpoint by uuid. + */ + var _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }; + + /** + * inits a draggable if it's not already initialised. + */ + var _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element) && !jsPlumb.CurrentLibrary.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + } + } + }; + + /** + * helper to create a canvas. + * + * @param clazz + * optional class name for the canvas. + */ + var _newCanvas = function(clazz, parent, uuid) { + var canvas = document.createElement("canvas"); + _appendElement(canvas, parent); + canvas.style.position = "absolute"; + if (clazz) canvas.className = clazz; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + _getId(canvas, uuid); + if (ie) { + // for IE we have to set a big canvas size. actually you can + // override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas + // you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + var _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }; + /** + * perform an operation on all elements. + */ + var _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }; + /** + * helper to remove an element from the DOM. + */ + var _removeElement = function(element, parent) { + if (element != null) { + if (!parent) { + try { + document.body.removeChild(element); + } catch (e) { + } + } else { + jsPlumb.CurrentLibrary.removeElement(element, parent); + } + } + }; + /** + * helper to remove a list of elements from the DOM. + */ + var _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }; + /** + * helper method to remove an item from a list. + */ + var _removeFromList = function(map, key, value) { + if (key != null) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete (l[i]); + l.splice(i, 1); + return true; + } + } + } + return false; + }; + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }; + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + var _setVisible = function(el, state) { + _operation(_getAttribute(el, "id"), function(jpc) { + jpc.canvas.style.display = state; + }); + }; + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + var _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }; + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + var _toggleVisible = function(elId) { + _operation(elId, function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }; + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we use the + * offset, outerWidth and outerHeight methods to get the current values. + */ + var _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return; + } + if (recalc || offset == null) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } else { + offsets[elId] = offset; + } + }; + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + var _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log('jsPlumb function failed : ' + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log('wrapped function failed : ' + e); + } + } + return r; + }; + }; + + /* + * Class: Anchor Models a position relative to the origin of some + * element that an Endpoint can be located. + */ + /* + * Function: Anchor + * + * Constructor for the Anchor class + * + * Parameters: + * - x : the x location of the anchor as a fraction of the total width. - + * y : the y location of the anchor as a fraction of the total height. - + * orientation : an [x,y] array indicating the general direction a + * connection from the anchor should go in. for more info on this, see + * the documentation, or the docs in jquery-jsPlumb-defaults-XXX.js for + * the default Anchors. - offsets : an [x,y] array of fixed offsets that + * should be applied after the x,y position has been figured out. may be + * null. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + if (timestamp && timestamp === self.timestamp) { + return lastReturnValue; + } + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + var container = element ? element.container : null; + var containerAdjustment = { left : 0, top : 0 }; + if (container != null) { + var eo = _getElementObject(container); + var o = _getOffset(eo); + var sl = jsPlumb.CurrentLibrary.getScrollLeft(eo); + var st = jsPlumb.CurrentLibrary.getScrollTop(eo); + containerAdjustment.left = o.left - sl; + containerAdjustment.top = o.top - st; + lastReturnValue[0] = lastReturnValue[0] - containerAdjustment.left; + lastReturnValue[1] = lastReturnValue[1] - containerAdjustment.top; + } + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. + * + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(_getElementObject(refCanvas)); + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + var orientation = null; + var _lastResult = null; + + this.compute = function(params) { + var xy = params.xy, el = params.element; + var result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + if (el.container != null) { + var o = _getOffset(el.container); + result[0] = result[0] - o.left; + result[1] = result[1] - o.top; + } + _lastResult = result; + return result; + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { orientation = anchor.getOrientation(); }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * Class: DynamicAnchor + * + * An Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements - a useful + * feature for some applications. + * + * the second argument - anchorSelector - is optional, but when provided should be a function + * that + */ + var DynamicAnchor = function(anchors, anchorSelector) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = anchors || []; + var _convert = function(anchor) { return anchor.constructor == Anchor ? anchor: jsPlumb.makeAnchor(anchor); }; + for (var i = 0; i < _anchors.length; i++) _anchors[i] = _convert(_anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null; + var _curIndex = _anchors.length > 0 ? 0 : -1; + this.locked = false; + var self = this; + + // helper method to calculate the distance between the centers of the two elements. + var _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }; + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + var _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + + var pos = _curAnchor.compute(params); + return pos; + }; + + this.getCurrentLocation = function() { + var cl = _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + return cl; + }; + + this.getOrientation = function() { return _curAnchor != null ? _curAnchor.getOrientation() : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + * Class:Connection + * + * The connecting line between two Endpoints. + * + */ + var Connection = function(params) { + + EventGenerator.apply(this); + // ************** get the source and target and register the + // connection. ******************* + var self = this; + var id = new String('_jsplumb_c_' + (new Date()).getTime()); + this.getId = function() { return id; }; + this.container = params.container || _currentInstance.Defaults.Container; // may be null; we will append to the body if so. + // get source and target as jQuery objects + this.source = _getElementObject(params.source); + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. + if (params.sourceEndpoint) this.source = params.sourceEndpoint.getElement(); + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + this.sourceId = _getAttribute(this.source, "id"); + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + this.scope = params.scope; // scope may have been passed in to the connect call. + // if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. init endpoints + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams) { + if (anchorParams) + return jsPlumb.makeAnchor(anchorParams); + }; + var prepareEndpoint = function(existing, index, params, element) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint + || new jsPlumb.Endpoints.Dot(); + if (ep.constructor == String) ep = new jsPlumb.Endpoints[ep](); + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + var a = params.anchors ? params.anchors[index] : _makeAnchor(_currentInstance.Defaults.Anchors[index]) || _makeAnchor(jsPlumb.Defaults.Anchors[index]) || _makeAnchor(_currentInstance.Defaults.Anchor) || _makeAnchor(jsPlumb.Defaults.Anchor) || _makeAnchor("BottomCenter"); + var u = params.uuids ? params.uuids[index] : null; + var e = new Endpoint( { style : es, endpoint : ep, connections : [ self ], uuid : u, anchor : a, source : element, container : self.container }); + self.endpoints[index] = e; + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || _currentInstance.Defaults.Connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + if (this.connector.constructor == String) this.connector = new jsPlumb.Connectors[this.connector](); // lets you use a string as shorthand. + this.paintStyle = this.endpoints[0].connectorStyle || this.endpoints[1].connectorStyle || params.paintStyle || _currentInstance.Defaults.PaintStyle || jsPlumb.Defaults.PaintStyle; + this.backgroundPaintStyle = this.endpoints[0].connectorBackgroundStyle || this.endpoints[1].connectorBackgroundStyle || params.backgroundPaintStyle || _currentInstance.Defaults.BackgroundPaintStyle || jsPlumb.Defaults.BackgroundPaintStyle; + + // init overlays + this.overlays = params.overlays || []; + this.addOverlay = function(overlay) { overlays.push(overlay); }; + + // this is a shortcut helper method to let people add a label as + // overlay. + this.labelStyle = params.labelStyle || _currentInstance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + if (this.label) { + this.overlays.push(new jsPlumb.Overlays.Label( { + labelStyle : this.labelStyle, + label : this.label + })); + } + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + // functions for mouse hover/select functionality + this.distanceFrom = function(point) { return self.connector.distanceFrom(point); }; + + this.setLabel = function(l) { + self.label = l; + _currentInstance.repaint(self.source); + }; + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var otherOffset = offsets[this.targetId]; + otherWH = sizes[this.targetId]; + // todo: why not fold this and the paint call that follows into one + // call? + var anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1] + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0] + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc }); + + var canvas = _newCanvas(jsPlumb.connectorClass, self.container); + this.canvas = canvas; + + /* + * Function: paint + * + * paints the connection. + * + * Parameters: + * elId Id of the element that is in motion + * ui current library's event system ui object (present if we came from a drag to get here) + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp; + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the + // two references. + var swap = false; + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + if (this.canvas.getContext) { + _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }); + _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + // var myOffset = offsets[sId], otherOffset = offsets[tId], myWH = sizes[sId], otherWH = sizes[tId]; + var ctx = canvas.getContext('2d'); + var sAnchorP = this.endpoints[sIdx].anchor.getCurrentLocation(); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.getCurrentLocation(); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + + // paint overlays + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + var s = o.computeMaxSize(self.connector, ctx); + if (s > maxSize) + maxSize = s; + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, this.paintStyle.lineWidth, maxSize); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + + var _paintOneStyle = function(ctx, paintStyle) { + ctx.save(); + jsPlumb.extend(ctx, paintStyle); + if (paintStyle.gradient && !ie) { + var g = self.connector.createGradient(dim, ctx, (elId == this.sourceId)); + for ( var i = 0; i < paintStyle.gradient.stops.length; i++) + g.addColorStop(paintStyle.gradient.stops[i][0], paintStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + self.connector.paint(dim, ctx); + ctx.restore(); + }; + + // first check for the background style + if (this.backgroundPaintStyle != null) { + _paintOneStyle(ctx, this.backgroundPaintStyle); + } + _paintOneStyle(ctx, this.paintStyle); + + // paint overlays + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + o.draw(self.connector, ctx); + } + } + }; + + // TODO: should this take a timestamp? probably. it reduces the + // amount of time + // spent figuring out anchor locations. + this.repaint = function() { + this.paint({ elId : this.sourceId, recalc : true }); + }; + + _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide + // whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + }; + + /* + * Class: Endpoint Models an endpoint. Can have one to N connections + * emanating from it (although how to handle that in the UI is a very + * good question). also has a Canvas and paint style. + */ + + /* + * Function: Endpoint This is the Endpoint class constructor. + * Parameters: anchor - anchor for the endpoint, of type jsPlumb.Anchor. + * may be null. endpoint - endpoint object, of type jsPlumb.Endpoint. + * may be null. style - endpoint style, a js object. may be null. source - + * element the endpoint is attached to, of type jquery object. Required. + * canvas - canvas element to use. may be, and most often is, null. + * connections - optional list of connections to configure the endpoint + * with. isSource - boolean. indicates the endpoint can act as a source + * of new connections. optional. dragOptions - if isSource is set to + * true, you can supply arguments for the jquery draggable method. + * optional. connectionStyle - if isSource is set to true, this is the + * paint style for connections from this endpoint. optional. connector - + * optional connector type to use. isTarget - boolean. indicates the + * endpoint can act as a target of new connections. optional. + * dropOptions - if isTarget is set to true, you can supply arguments + * for the jquery droppable method. optional. reattach - optional + * boolean that determines whether or not the connections reattach after + * they have been dragged off an endpoint and left floating. defaults to + * false: connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + params = params || {}; + var self = this; + var id = new String('_jsplumb_e_' + (new Date()).getTime()); + this.getId = function() { return id; }; + if (params.dynamicAnchors) + self.anchor = new DynamicAnchor(jsPlumb.makeAnchors(params.dynamicAnchors)); + else + self.anchor = params.anchor ? jsPlumb.makeAnchor(params.anchor) : params.anchors ? jsPlumb.makeAnchor(params.anchors) : jsPlumb.makeAnchor("TopCenter"); + var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot(); + if (_endpoint.constructor == String) _endpoint = new jsPlumb.Endpoints[_endpoint](); + self.endpoint = _endpoint; + var _style = params.style || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + this.connectorStyle = params.connectorStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + var _element = params.source, _uuid = params.uuid; + var floatingEndpoint = null, inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + this.container = params.container || _currentInstance.Defaults.Container || jsPlumb.Defaults.Container; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of. + this.canvas = params.canvas || _newCanvas(jsPlumb.endpointClass, this.container, params.uuid); + this.connections = params.connections || []; + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + var _reattach = params.reattach || false; + this.dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + this.addConnection = function(connection) { + self.connections.push(connection); + }; + this.detach = function(connection, ignoreTarget) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + // this avoids a circular loop + if (!ignoreTarget) { + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + t.detach(connection, true); + } + _removeElement(connection.canvas, connection.container); + _removeFromList(connectionsByScope, connection.scope, connection); + if(!ignoreTarget) fireDetachEvent(connection); + } + }; + + /** + * detaches all connections this Endpoint has + */ + this.detachAll = function() { + while (self.connections.length > 0) { + self.detach(self.connections[0]); + } + }; + + /** + * removes any connections from this Endpoint that are connected to + * the given target endpoint. + * + * @param targetEndpoint + */ + this.detachFrom = function(targetEndpoint) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + self.detach(c[i]); + } + }; + + /** + * detach this Endpoint from the Connection, but leave the Connection alive. used when dragging. + */ + this.detachFromConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /** + * returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /** + * returns the UUID for this Endpoint, if there is one. + */ + this.getUuid = function() { + return _uuid; + }; + + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + var e = new Endpoint( { anchor : self.anchor, source : _element, style : _style, endpoint : _endpoint }); + return e; + }; + + /** + * returns whether or not this endpoint is connected to the given + * endpoint. + * + * @param endpoint + * Endpoint to test. + * @since 1.1.1 + * + * todo: needs testing. will this work if the endpoint passed in is + * the source? + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + * + * @returns {Boolean} + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + var connectorSelector = function() { + return (self.connections.length < _maxConnections) ? null : self.connections[0]; + }; + + /** + * @returns whether or not the Endpoint can accept any more + * Connections. + */ + this.isFull = function() { + return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); + }; + + /** + * sets whether or not connnctions can be dragged from this Endpoint + * once it is full. you would use this in a UI in which you're going + * to provide some other way of breaking connections, if you need to + * break them at all. this property is by default true; use it in + * conjunction with the 'reattach' option on a connect call. + */ + this.setDragAllowedWhenFull = function(allowed) { + self.dragAllowedWhenFull = allowed; + }; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + /** + * paints the Endpoint, recalculating offset and anchor positions if + * necessary. + */ + this.paint = function(params) { + + params = params || {}; + var timestamp = params.timestamp; + if (!timestamp || self.timestamp !== timestamp) { + var ap = params.anchorPoint, canvas = params.canvas, connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + // do we always want to force a repaint here? i dont + // think so! + var xy = params.offset || offsets[_elementId]; + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (self.anchor.isDynamic) { + if (self.connections.length > 0) { + var c = self.connections[0]; + var oIdx = c.endpoints[0] == self ? 1 : 0; + var oId = oIdx == 0 ? c.sourceId : c.targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + } + ap = self.anchor.compute(anchorParams); + } + _endpoint.paint(ap, self.anchor.getOrientation(), canvas || self.canvas, _style, connectorPaintStyle || _style); + self.timestamp = timestamp; + } + }; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener + // maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + var start = function() { + jpc = connectorSelector(); + if (self.isFull() && !self.dragAllowedWhenFull) return false; + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + var nE = _getElementObject(n); + _appendElement(n, self.container); // + // create and assign an id, and initialize the offset. + var id = _getId(nE); + _updateOffset( { elId : id }); + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor( { reference : self.anchor, referenceCanvas : self.canvas }); + floatingEndpoint = new Endpoint({ style : { fillStyle : 'rgba(0,0,0,0)' }, endpoint : _endpoint, anchor : floatingAnchor, source : nE }); + + if (jpc == null) { + self.anchor.locked = true; + // create a connection. one end is this endpoint, the + // other is a floating endpoint. + jpc = new Connection( { + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : _getElementObject(_element), + target : _getElementObject(n), + anchors : [ self.anchor, floatingAnchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + connector : params.connector, // this can also be null. Connection will use the default. + overlays : self.connectorOverlays // new in 1.2.4. + }); + } else { + existingJpc = true; + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas)); + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; + // detach from the connection while dragging is occurring. + self.detachFromConnection(jpc); + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId ]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId ]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + // lock the other endpoint; if it is dynamic it will not + // move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new + // floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // TODO unregister on stop? or will floating endpoint's + // destruction be assured. + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be + // dragging the source at any time + // before this connection is either discarded or made into a + // permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + }; + + var dragOptions = params.dragOptions || {}; + var defaultOpts = jsPlumb.extend( {}, jsPlumb.CurrentLibrary.defaultDragOptions); + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + var startEvent = jsPlumb.CurrentLibrary.dragEvents['start']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], + function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + jsPlumb.CurrentLibrary.setOffset(n, _ui); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements( [ n, floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (_reattach) { + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } else { + jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc); // the main endpoint will inform the floating endpoint + // to disconnect, and also post the detached event. + } + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElement(jpc.canvas, self.container); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint(); + jpc.repaint(); + jpc = null; + delete inPlaceCopy; + delete endpointsByElement[floatingEndpoint.elementId]; + delete floatingEndpoint; + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas) { + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + var drop = function() { + var draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)); + var id = _getAttribute(draggable, "dragId"); + var elId = _getAttribute(draggable, "elId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // todo test that the target is not full. + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + if (!jpc.suspendedEndpoint) { // if a new connection, add it. TODO: move this to a jsPlumb internal method - addConnection or something. doesnt need to be exposed. + _addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + _currentInstance.fireUpdate("jsPlumbConnectionDetached", { + source : idx == 0 ? suspendedElement : jpc.source, target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1] + }); + } + + jsPlumb.repaint(elId); + + _currentInstance.fireUpdate("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + } + + delete floatingConnections[id]; + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute( _getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + }); + + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas)); + + return self; + }; + + /* + * Property: Defaults These are the default settings for jsPlumb, that + * is what will be used if you do not supply specific pieces of + * information to the various API calls. A convenient way to implement + * your own look and feel can be to override these defaults by including + * a script somewhere after the jsPlumb include, but before you make any + * calls to jsPlumb, for instance in this example we set the PaintStyle + * to be a blue line of 27 pixels: > jsPlumb.Defaults.PaintStyle = { + * lineWidth:27, strokeStyle:'blue' } + */ + this.Defaults = { + Anchor : null, + Anchors : [ null, null ], + BackgroundPaintStyle : null, + Connector : null, + Container : null, + DragOptions : {}, + DropOptions : {}, + Endpoint : null, + Endpoints : [ null, null ], + EndpointStyle : { + fillStyle : null + }, + EndpointStyles : [ null, null ], + LabelStyle : { + fillStyle : "rgba(0,0,0,0)", + color : "black" + }, + LogEnabled : true, + MaxConnections : null, + // TODO: should we have OverlayStyle too? + PaintStyle : { + lineWidth : 10, + strokeStyle : 'red' + }, + Scope : "_jsPlumb_DefaultScope" + }; + + this.logEnabled = this.Defaults.LogEnabled; + + /* + * Property: connectorClass The CSS class to set on Connection canvas + * elements. This value is a String and can have multiple classes; the + * entire String is appended as-is. + */ + this.connectorClass = '_jsPlumb_connector'; + + /* + * Property: endpointClass The CSS class to set on Endpoint canvas + * elements. This value is a String and can have multiple classes; the + * entire String is appended as-is. + */ + this.endpointClass = '_jsPlumb_endpoint'; + + /* + * Property: overlayClass The CSS class to set on an Overlay that is an + * HTML element. This value is a String and can have multiple classes; + * the entire String is appended as-is. + */ + this.overlayClass = '_jsPlumb_overlay'; + + /* + * Property: Anchors Default jsPlumb Anchors. These are supplied in the + * file jsPlumb-defaults-x.x.x.js, which is merged in with the main + * jsPlumb script to form .jsPlumb-all-x.x.x.js. You can + * provide your own Anchors by supplying them in a script that is loaded + * after jsPlumb, for instance: > jsPlumb.Anchors.MyAnchor = { + * ....anchor code here. see the documentation. } + */ + this.Anchors = {}; + + /* + * Property: Connectors Default jsPlumb Connectors. These are supplied + * in the file jsPlumb-defaults-x.x.x.js, which is merged in with the + * main jsPlumb script to form .jsPlumb-all-x.x.x.js. You can + * provide your own Connectors by supplying them in a script that is + * loaded after jsPlumb, for instance: > jsPlumb.Connectors.MyConnector = { + * ....connector code here. see the documentation. } + */ + this.Connectors = {}; + + /* + * Property: Endpoints Default jsPlumb Endpoints. These are supplied in + * the file jsPlumb-defaults-x.x.x.js, which is merged in with the main + * jsPlumb script to form .jsPlumb-all-x.x.x.js. You can + * provide your own Endpoints by supplying them in a script that is + * loaded after jsPlumb, for instance: > jsPlumb.Endpoints.MyEndpoint = { + * ....endpoint code here. see the documentation. } + */ + this.Endpoints = {}; + + /* + * Property:Overlays Default jsPlumb Overlays such as Arrows and Labels. + * These are supplied in the file jsPlumb-defaults-x.x.x.js, which is + * merged in with the main jsPlumb script to form + * .jsPlumb-all-x.x.x.js. You can provide your own Overlays by + * supplying them in a script that is loaded after jsPlumb, for + * instance: > jsPlumb.Overlays.MyOverlay = { ....overlay code here. see + * the documentation. } + */ + this.Overlays = {}; + + /* + * Function: addEndpoint Adds an Endpoint to a given element. + * Parameters: target - Element to add the endpoint to. either an + * element id, or a selector representing some element. params - Object + * containing Endpoint options (more info required) Returns: The newly + * created Endpoint. See Also: + */ + this.addEndpoint = function(target, params) { + params = jsPlumb.extend( {}, params); + params.endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + params.endpointStyle = params.endpointStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + var el = _getElementObject(target), id = _getAttribute(el, "id"); + params.source = el; + _updateOffset({ elId : id }); + var e = new Endpoint(params); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + return e; + }; + + /* + * Function: addEndpoint Adds a list of Endpoints to a given element. + * Parameters: target - element to add the endpoint to. either an + * element id, or a selector representing some element. endpoints - List + * of objects containing Endpoint options. one Endpoint is created for + * each entry in this list. Returns: List of newly created Endpoints, + * one for each entry in the 'endpoints' argument. See Also: + * + */ + this.addEndpoints = function(target, endpoints) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + results.push(_currentInstance.addEndpoint(target, endpoints[i])); + } + return results; + }; + + /* + * Function: animate Wrapper around supporting library's animate + * function; injects a call to jsPlumb in the 'step' function (creating + * it if necessary). This only supports the two-arg version of the + * animate call in jQuery - the one that takes an 'options' object as + * the second arg. MooTools has only one method - a two arg one. Which + * is handy. Parameters: el - Element to animate. Either an id, or a + * selector representing the element. properties - The 'properties' + * argument you want passed to the standard jQuery animate call. options - + * The 'options' argument you want passed to the standard jQuery animate + * call. Returns: void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // you know, probably onComplete should repaint too. that will help + // keep up + // with fast animations. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /* + * Function: connect Establishes a connection between two elements. + * Parameters: params - Object containing setup for the connection. see + * documentation. Returns: The newly created Connection. + */ + this.connect = function(params) { + var _p = jsPlumb.extend( {}, params); + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not + // full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log("could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log("could not add connection; target endpoint is full"); + return; + } + + // dynamic anchors + if (_p.dynamicAnchors) { + // these can either be an array of anchor coords, which we will use for both source and target, or an object with {source:[anchors], target:[anchors]}, in which + // case we will use a different set for each element. + var a = _p.dynamicAnchors.constructor == Array; + var sa = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.source)); + var ta = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.target)); + _p.anchors = [sa,ta]; + } + + var jpc = new Connection(_p); + // add to list of connections (by scope). + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + _currentInstance.fireUpdate("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + // force a paint + _draw(jpc.source); + + return jpc; + }; + + /* + Function: deleteEndpoint + + deletes an endpoint and removes all connections it has (which removes the connections from the other endpoints involved too) + + You can call this with either a string uuid, or an Endpoint + + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElement(endpoint.canvas, endpoint.container); + // remove from endpointsbyElement + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + delete endpoint; + } + }; + + /* + * Function: deleteEveryEndpoint + * + * Deletes every Endpoint in this instance + * of jsPlumb. Use this instead of removeEveryEndpoint now. + * + * Returns: void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc) { + _currentInstance.fireUpdate("jsPlumbConnectionDetached", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + }; + + /* + * Function: + * detach Removes a connection. takes either (source, target) (the old way, maintained for backwards compatibility), or a params + * object with various possible values. + * + * + * Parameters: + * source - id or element object of the first element in the connection. + * target - id or element object of the second element in the connection. + * + * OR + * + * params: + * { + * + * Returns: true if successful, false if not. + */ + this.detach = function(source, target) { + if (arguments.length == 2) { + var s = _getElementObject(source), sId = _getId(s); + var t = _getElementObject(target), tId = _getId(t); + _operation(sId, function(jpc) { + if ((jpc.sourceId == sId && jpc.targetId == tId) || (jpc.targetId == sId && jpc.sourceId == tId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + fireDetachEvent(jpc); + } + }); + } + // this is the new version of the method, taking a JS object like + // the connect method does. + else if (arguments.length == 1) { + if (arguments[0].constructor == Connection) { + arguments[0].endpoints[0].detachFrom(arguments[0].endpoints[1]); + fireDetachEvent(arguments[0]); + } + else if (arguments[0].connection) { + arguments[0].connection.endpoints[0].detachFrom(arguments[0].connection.endpoints[1]); + fireDetachEvent(arguments[0].connection); + } + else { + var _p = jsPlumb.extend( {}, source); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1])); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + sourceId = _getId(_p.source); + targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + fireDetachEvent(jpc); + } + }); + } + } + } + }; + + /* + * Function: detachAll Removes all an element's connections. Parameters: + * el - either the id of the element, or a selector for the element. + * Returns: void + */ + this.detachAllConnections = function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + }; + + /** + * @deprecated Use detachAllConnections instead. this will be removed in jsPlumb 1.3. + */ + this.detachAll = this.detachAllConnections; + + /* + * Function: detachEverything Remove all Connections from all elements, + * but leaves Endpoints in place. Returns: void See Also: + * + */ + this.detachEveryConnection = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + /** + * @deprecated use detachEveryConnection instead. this will be removed in jsPlumb 1.3. + */ + this.detachEverything = this.detachEveryConnection; + + /* + * Function: draggable initialises the draggability of some element or + * elements. Parameters: el - either an element id, a list of element + * ids, or a selector. options - options to pass through to the + * underlying library Returns: void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + * Function: extend Wraps the underlying library's extend functionality. + * Parameters: o1 - object to extend o2 - object to extend o1 with + * Returns: o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getConnections Gets all or a subset of connections + * currently managed by this jsPlumb instance. Parameters: options - a + * JS object that holds options defining what sort of connections you're + * looking for. Valid values are: scope this may be a String or a list + * of Strings. jsPlumb will only return Connections whose scope matches + * what this option defines. If you omit it, you will be given + * Connections of every scope. source may be a string or a list of + * strings; constraints results to have only Connections whose source is + * this object. target may be a string or a list of strings; constraints + * results to have only Connections whose target is this object. + * + * The return value is a dictionary in this format: + * { 'scope1': [ {sourceId:'window1', targetId:'window2', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:}, + * {sourceId:'window3', targetId:'window4', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:}, + * {sourceId:'window1', targetId:'window3', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:} ], + * 'scope2': [ {sourceId:'window1', targetId:'window3', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:} ] } + * + */ + this.getConnections = function(options) { + var r = {}; + options = options || {}; + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') + r.push(input); + else + r = input; + } + return r; + }; + var scopes = prepareList(options.scope); + var sources = prepareList(options.source); + var targets = prepareList(options.target); + var filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + r[i] = []; + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + r[i].push({ sourceId : c.sourceId, targetId : c.targetId, source : c.source, target : c.target, sourceEndpoint : c.endpoints[0], targetEndpoint : c.endpoints[1], connection : c }); + } + } + } + return r; + }; + + /* + * Function: getDefaultScope Gets the default scope for connections and + * endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + * Function: getEndpoint gets an Endpoint by UUID Parameters: uuid - the + * UUID for the Endpoint Returns: Endpoint with the given UUID, null if + * nothing found. + */ + this.getEndpoint = _getEndpoint; + + /* + * gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + + /* + * Function: hide Sets an element's connections to be hidden. + * Parameters: el - either the id of the element, or a selector for the + * element. Returns: void + */ + this.hide = function(el) { + _setVisible(el, "none"); + }; + + /* + * Function: makeAnchor Creates an anchor with the given params. + * + * You do not need to use this method. It is exposed because of the way jsPlumb is + * split into three scripts; this will change in the future. + * + * Parameters: x - the x location of the anchor as a fraction of the + * total width. y - the y location of the anchor as a fraction of the + * total height. xOrientation - value indicating the general direction a + * connection from the anchor should go in, in the x direction. + * yOrientation - value indicating the general direction a connection + * from the anchor should go in, in the y direction. xOffset - a fixed + * offset that should be applied in the x direction that should be + * applied after the x position has been figured out. optional. defaults + * to 0. yOffset - a fixed offset that should be applied in the y + * direction that should be applied after the y position has been + * figured out. optional. defaults to 0. + * -- OR -- + * + * params - {x:..., y:..., xOrientation etc } + * -- OR FROM 1.2.4 --- + * + * name - the name of some Anchor in the _currentInstance.Anchors array. + * -- OR FROM 1.2.4 --- + * + * coords - a list of coords for the anchor, like you would pass to + * jsPlumb.makeAnchor (eg [0.5,0.5,0,-1] - an anchor in the center of + * some element, oriented towards the top of the screen) + * -- OR FROM 1.2.4 --- + * + * anchor - an existing anchor. just gets passed back. it's handy + * internally to have this functionality. + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed + // in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards + // compatibility if we are given only one value we assume it's a + // call in the old form. + if (arguments.length == 0) return null; + var params = {}; + if (arguments.length == 1) { + var specimen = arguments[0]; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; + // is it the name of an anchor type? + else if (typeof specimen == "string") return _currentInstance.Anchors[arguments[0]](); + // is it an array of coordinates? + else if (specimen.constructor == Array) { + if (specimen[0].constructor == Array || specimen[0].constructor == String) + return new DynamicAnchor(specimen); + else + return jsPlumb.makeAnchor.apply(this, specimen); + } + // last we try the backwards compatibility stuff. + else if (typeof arguments[0] == "object") jsPlumb.extend(params, x); + } else { + params = { x : x, y : y }; + if (arguments.length >= 4) params.orientation = [ arguments[2], arguments[3] ]; + if (arguments.length == 6) params.offsets = [ arguments[4], arguments[5] ]; + } + var a = new Anchor(params); + a.clone = function() { + return new Anchor(params); + }; + return a; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types) { + var r = []; + for ( var i = 0; i < types.length; i++) + if (typeof types[i] == "string") + r.push(_currentInstance.Anchors[types[i]]()); + else if (types[i].constructor == Array) + r.push(jsPlumb.makeAnchor(types[i])); + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /* + * Function: repaint Repaints an element and its connections. This + * method gets new sizes for the elements before painting anything. + * Parameters: el - either the id of the element or a selector + * representing the element. Returns: void See Also: + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + * Function: repaintEverything Repaints all connections. + * Returns: void + * See Also: + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + * Function: removeAllEndpoints + * Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + * Parameters: el - either an element id, or a selector for an element. + * Returns: void + * See Also: + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + for ( var i in ebe) + _currentInstance.deleteEndpoint(ebe[i]); + endpointsByElement[elId] = []; + }; + + /* + * Function: removeEveryEndpoint + * Removes every Endpoint in this instance + * of jsPlumb. + * + * Returns: void + * + * See Also: + * + * @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /** + * Function: removeEndpoint Removes the given Endpoint from the given + * element. Parameters: el - either an element id, or a selector for an + * element. endpoint - Endpoint to remove. this is an Endpoint object, + * such as would have been returned from a call to addEndpoint. Returns: + * void See Also: + * + * @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + /** + * Function:reset + * removes all endpoints and connections and clears the + * listener list. to keep listeners just call jsPlumb.deleteEveryEndpoint. + */ + this.reset = function() { + this.deleteEveryEndpoint(); + this.clearListeners(); + }; + + /* + * Function: setAutomaticRepaint Sets/unsets automatic repaint on window + * resize. Parameters: value - whether or not to automatically repaint + * when the window is resized. Returns: void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + * Function: setDefaultNewCanvasSize Sets the default size jsPlumb will + * use for a new canvas (we create a square canvas so one value is all + * that is required). This is a hack for IE, because ExplorerCanvas + * seems to need for a canvas to be larger than what you are going to + * draw on it at initialisation time. The default value of this is 1200 + * pixels, which is quite large, but if for some reason you're drawing + * connectors that are bigger, you should adjust this value + * appropriately. Parameters: size - The default size to use. jsPlumb + * will use a square canvas so you need only supply one value. Returns: + * void + */ + this.setDefaultNewCanvasSize = function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }; + + /* + * Function: setDefaultScope Sets the default scope for connections and + * endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable Sets whether or not a given element is + * draggable, regardless of what any plumb command may request. + * Parameters: el - either the id for the element, or a selector + * representing the element. Returns: void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setDraggableByDefault Sets whether or not elements are + * draggable by default. Default for this is true. Parameters: draggable - + * value to set Returns: void + */ + this.setDraggableByDefault = function(draggable) { + _draggableByDefault = draggable; + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction Sets the function to fire when the + * window size has changed and a repaint was fired. Parameters: f - + * Function to execute. Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: show Sets an element's connections to be visible. + * Parameters: el - either the id of the element, or a selector for the + * element. Returns: void + */ + this.show = function(el) { + _setVisible(el, "block"); + }; + + /* + * Function: sizeCanvas Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * Parameters: x - [int] x position for the Canvas origin y - [int] y + * position for the Canvas origin w - [int] width of the canvas h - + * [int] height of the canvas Returns: void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible Toggles visibility of an element's + * connections. Parameters: el - either the element's id, or a selector + * representing the element. Returns: void, but should be updated to + * return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable Toggles draggability (sic) of an element's + * connections. Parameters: el - either the element's id, or a selector + * representing the element. Returns: The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload Unloads jsPlumb, deleting all storage. You should + * call this from an onunload attribute on the element Returns: + * void + */ + this.unload = function() { + delete endpointsByElement; + delete endpointsByUUID; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + }; + + /* + * Function: wrap + * + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + + EventGenerator.apply(this); + this.addListener = this.bind; + + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(); + if (_defaults) jsPlumb.extend(j.Defaults, _defaults); + return j; + }; +})(); +/* +* jsPlumb-defaults-1.2.4-RC1 +* +* This script contains the default Anchors, Endpoints, Connectors and Overlays for jsPlumb. It should be used with jsPlumb 1.1.0 and above; +* prior to version 1.1.0 of jsPlumb the defaults were included inside the main script. +* +* NOTE: for production usage you should use jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, +* in a minified file. +* +* Dual licensed under MIT and GPL2. +*/ + +(function() { + + var ie = !!!document.createElement('canvas').getContext; + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * getOrientation : function() { return [ox, oy]; } + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + var _curryAnchor = function(x,y,ox,oy) { + return function() { + return jsPlumb.makeAnchor(x,y,ox,oy); + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1); + + + jsPlumb.Defaults.DynamicAnchors = function() { + return jsPlumb.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"]); + }; + jsPlumb.Anchors["AutoDefault"] = function() { return jsPlumb.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors()); }; + + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + var currentPoints = null; + var _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + + _dx = _tx - _sx, _dy = (_ty - _sy); + _m = _dy / _dx, _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + + return currentPoints; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + var xp = _sx + (location * _dx); + var yp = _m == Infinity ? xp + _b : (_m * xp) + _b; + return {x:xp, y:yp}; + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { return _m; }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + var orientation = distance > 0 ? 1 : -1; + var y = Math.abs(distance * Math.sin(_theta)); + if (_sy > _ty) y = y * -1; + var x = Math.abs(distance * Math.cos(_theta)); + if (_sx > _tx) x = x * -1; + return {x:p.x + (orientation * x), y:p.y + (orientation * y)}; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = self.gradientAtPoint(p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + /** + * This Connector draws a Bezier curve with two control points. + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + jsPlumb.Connectors.Bezier = function(curviness) { + var self = this; + this.majorAnchor = curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _sx, _sy, _canvasX, _canvasY, _w, _h; + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) + { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx); var minx2 = Math.min(_CP[0], _CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(_sx,_tx); var maxx2 = Math.max(_CP[0], _CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty); var miny2 = Math.min(_CP[1], _CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(_sy,_ty); var maxy2 = Math.max(_CP[1], _CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + this.paint = function(d, ctx) { + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the distance the given point is from the curve. not enabled for 1.2.3. didnt make the cut. next time. + */ + this.distanceFrom = function(point) { + var curve = [ {x:currentPoints[4], y:currentPoints[5]}, + {x:currentPoints[8], y:currentPoints[9]}, + {x:currentPoints[10], y:currentPoints[11]}, + {x:currentPoints[6], y:currentPoints[7]}]; + return (jsBezier.distanceFromCurve(point, curve)); + }; + + this.nearestPointTo = function(point) { + var curve = [ {x:currentPoints[4], y:currentPoints[5]}, + {x:currentPoints[8], y:currentPoints[9]}, + {x:currentPoints[10], y:currentPoints[11]}, + {x:currentPoints[6], y:currentPoints[7]}]; + return (jsBezier.nearestPointOnCurve(point, curve)); + + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + return jsBezier.perpendicularToCurveAt(_makeCurve(), location, length, distance); + }; + + this.createGradient = function(dim, ctx, swap) { + return (swap) ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + + /** + * Types of endpoint UIs. we supply four - a circle of default radius 10px, a rectangle of + * default size 20x20, an image (with no default), and a Triangle, of default size 15. + * you can supply others of these if you want to - see the documentation for a howto. + */ + + /** + * a round endpoint, with default radius 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + } + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + if (endpointStyle.gradient && !ie) { + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * A Rectangular endpoint, with default size 20x20. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + jsPlumb.Endpoints.Triangle = function(params) { + + params = params || { width:15, height:15 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - width/2; + var y = anchorPoint[1] - height/2; + + jsPlumb.sizeCanvas(canvas, x, y, width, height); + + var ctx = canvas.getContext('2d'); + var offsetX = 0, offsetY = 0, angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = endpointStyle.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' or, since 1.2.4, a 'src' property in the params object.. + */ + jsPlumb.Endpoints.Image = function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.src || params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + }; + + /** + * An arrow overlay. you can provide: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + */ + jsPlumb.Overlays.Arrow = function(params) { + params = params || {}; + var self = this; + var length = params.length || 20; + var width = params.width || 20; + var fillStyle = params.fillStyle || "black"; + var strokeStyle = params.strokeStyle; + var lineWidth = params.lineWidth || 1; + this.loc = params.location || 0.5; + // how far along the arrow the lines folding back in come to. default is 62.3%. + var foldback = params.foldback || 0.623; + var _getFoldBackPoint = function(connector, loc) { + if (foldback == 0.5) return connector.pointOnPath(loc); + else { + var adj = 0.5 - foldback; // we calculate relative to the center + return connector.pointAlongPathFrom(loc, length * adj); + } + }; + + this.computeMaxSize = function() { return width * 1.5; } + + this.draw = function(connector, ctx) { + // this is the arrow head position + var hxy = connector.pointAlongPathFrom(self.loc, length / 2); + // this is the center of the tail + var txy = connector.pointAlongPathFrom(self.loc, -length / 2), tx = txy.x, ty = txy.y; + // this is the tail vector + var tail = connector.perpendicularToPathAt(self.loc, width, -length / 2); + // this is the point the tail goes in to + var cxy = _getFoldBackPoint(connector, self.loc); + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(hxy.x, hxy.y); + ctx.lineTo(tail[0].x, tail[0].y); + ctx.lineTo(cxy.x, cxy.y); + ctx.lineTo(tail[1].x, tail[1].y); + ctx.lineTo(hxy.x, hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + + /** + * a basic arrow. this is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. See Arrow for params. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + /** + * a diamond. like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. See Arrow for params. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40; + var p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + /** + * A Label overlay. Params you can provide: + * + * labelStyle - js object containing style instructions for the label. defaults to jsPlumb.Defaults.LabelStyle. + * label - the label to paint. may be a string or a function that returns a string. nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * borderWidth - width of a border to paint. defaults to zero. + * borderStyle - strokeStyle to use when painting the border, if necessary. + */ + jsPlumb.Overlays.Label = function(params) { + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + var self = this; + var labelWidth = null, labelHeight = null, labelText = null, labelPadding = null; + this.location = params.location || 0.5; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var _textDimensions = function(ctx) { + if (self.cachedDimensions) return self.cachedDimensions; // return cached copy if we can. if we add a setLabel function remember to clear the cache. + labelText = typeof self.label == 'function' ? self.label(self) : self.label; + var d = {}; + if (labelText) { + var lines = labelText.split(/\n|\r\n/); + ctx.save(); + if (self.labelStyle.font) ctx.font = self.labelStyle.font; + var t = _widestLine(lines, ctx); + // a fake text height measurement: use the width of upper case M + var h = ctx.measureText("M").width; + labelPadding = self.labelStyle.padding || 0.25; + labelWidth = t + (2 * t * labelPadding); + labelHeight = (lines.length * h) + (2 * h * labelPadding); + var textHeight = lines.length * h; + ctx.restore(); + d = {width:labelWidth, height:labelHeight, lines:lines, oneLine:h, padding:labelPadding, textHeight:textHeight}; + } + if (typeof self.label != 'function') self.cachedDimensions = d; // cache it if we can. + return d; + }; + this.computeMaxSize = function(connector, ctx) { + var td = _textDimensions(ctx); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + var _widestLine = function(lines, ctx) { + var max = 0; + for (var i = 0; i < lines.length; i++) { + var t = ctx.measureText(lines[i]).width; + if (t > max) max = t; + } + return max; + }; + + this.draw = function(connector, ctx) { + var td = _textDimensions(ctx); + if (td.width) { + var cxy = connector.pointOnPath(self.location); + if (self.labelStyle.font) ctx.font = self.labelStyle.font; + if (self.labelStyle.fillStyle) + ctx.fillStyle = self.labelStyle.fillStyle; + else + ctx.fillStyle = "rgba(0,0,0,0)"; + ctx.fillRect(cxy.x - (td.width / 2), cxy.y - (td.height / 2) , td.width , td.height ); + + if (self.labelStyle.color) ctx.fillStyle = self.labelStyle.color; + ctx.textBaseline = "middle"; + ctx.textAlign = "center"; + for (i = 0; i < td.lines.length; i++) { + ctx.fillText(td.lines[i],cxy.x, cxy.y - (td.textHeight / 2) + (td.oneLine/2) + (i*td.oneLine)); + } + + // border + if (self.labelStyle.borderWidth > 0) { + ctx.strokeStyle = self.labelStyle.borderStyle || "black"; + ctx.strokeRect(cxy.x - (td.width / 2), cxy.y - (td.height / 2) , td.width , td.height ); + } + } + }; + }; + + /** + * an image overlay. params may contain: + * + * location : proportion along the connector to draw the image. optional. + * src : image src. required. + * events : map of event names to functions; each event listener will be bound to the img. + */ + jsPlumb.Overlays.Image = function(params) { + var self = this; + this.location = params.location || 0.5; + this.img = new Image(); + var imgDiv = null; + var notReadyInterval = null; + var notReadyConnector, notReadyContext; + var events = params.events || {}; + var _init = function() { + if (self.ready) { + window.clearInterval(notReadyInterval); + imgDiv = document.createElement("img"); + imgDiv.src = self.img.src; + imgDiv.style.position = "absolute"; + imgDiv.style.display="none"; + imgDiv.className = "_jsPlumb_overlay"; + document.body.appendChild(imgDiv);// HMM + // attach events + for (var e in events) { + jsPlumb.CurrentLibrary.bind(imgDiv, e, events[e]); + } + if (notReadyConnector && notReadyContext) { + _draw(notReadyConnector, notReadyContext); + notReadyContext = null; + notReadyConnector = null; + } + } + }; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.src || params.url; + + notReadyInterval = window.setInterval(_init, 250); + + this.computeMaxSize = function(connector, ctx) { + return [self.img.width, self.img.height] + }; + + var _draw = function(connector, ctx) { + var cxy = connector.pointOnPath(self.location); + var canvas = jsPlumb.CurrentLibrary.getElementObject(ctx.canvas); + var canvasOffset = jsPlumb.CurrentLibrary.getOffset(canvas); + var o = {left:canvasOffset.left + cxy.x - (self.img.width/2), top:canvasOffset.top + cxy.y - (self.img.height/2)}; + jsPlumb.CurrentLibrary.setOffset(imgDiv, o); + imgDiv.style.display = "block"; + }; + + this.draw = function(connector, ctx) { + if (self.ready) + _draw(connector, ctx); + else { + notReadyConnector = connector; + notReadyContext = ctx; + } + }; + }; +})();/* + * jquery.jsPlumb 1.2.5-RC1 + * + * jQuery specific functionality for jsPlumb. + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + * NOTE: for production usage you should use jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, + * in a minified file. + * + * Dual licensed under MIT and GPL2. + * + */ + +(function($){ + /** + * plumbs the results of the selector to some target, using the given options if supplied, + * or the defaults otherwise. + */ + $.fn.plumb = function(options) { + var options = $.extend({}, options); + + return this.each(function() + { + var params = $.extend({source:$(this)}, options); + jsPlumb.connect(params); + }); + }; + + /** + * detaches the results of the selector from the given target or list of targets - 'target' + * may be a String or a List. + */ + $.fn.detach = function(target) { + return this.each(function() { + if (target) { + var id = $(this).attr("id"); + if (typeof target == 'string') target = [target]; + for (var i = 0; i < target.length; i++) + jsPlumb.detach(id, target[i]); + } + }); + }; + + /** + * detaches the results from the selector from all connections. + */ + $.fn.detachAll = function() { + return this.each(function() + { + var id = $(this).attr("id"); + jsPlumb.detachAll(id); + }); + }; + + /** + * adds an endpoint to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoint = function(options) { + var addedEndpoints = []; + this.each(function() { + addedEndpoints.push(jsPlumb.addEndpoint($(this).attr("id"), options)); + }); + return addedEndpoints[0]; + }; + + /** + * adds a list of endpoints to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoints = function(endpoints) { + var addedEndpoints = []; + return this.each(function() { + var e = jsPlumb.addEndpoints($(this).attr("id"), endpoints); + for (var i = 0; i < e.length; i++) addedEndpoints.push(e[i]); + }); + }; + + /** + * remove the endpoint, if it exists, deleting its UI elements etc. + */ + $.fn.removeEndpoint = function(endpoint) { + this.each(function() { + jsPlumb.removeEndpoint($(this).attr("id"), endpoint); + }); + }; + +})(jQuery); + + +/*TODO: abstract this out from jQuery too! but how...because jsPlumb is not loaded yet. +$(window).bind('resize', function() { + if (resizeTimer) clearTimeout(resizeTimer); + resizeTimer = setTimeout(repaintEverything, 100); + });*/ + + + +/* + * the library agnostic functions, such as find offset, get id, get attribute, extend etc. + * the full list is: + * + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setOffset sets the offset of some element. + */ +(function($) { + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * appends the given child to the given parent. + */ + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).append(child); + }, + + /** + * event binding wrapper. it just so happens that jQuery uses 'bind' also. yui3, for example, + * uses 'on'. + */ + bind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.bind(event, callback); + }, + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step', + 'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete' + }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + * + */ + getElementObject : function(el) { + return typeof(el)=='string' ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + getScrollLeft : function(el) { + return el.scrollLeft(); + }, + + getScrollTop : function(el) { + return el.scrollTop(); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[1]; + //return ui.absolutePosition || ui.offset; + return ui.offset || ui.absolutePosition; + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options) { + // remove helper directive if present. + options.helper = null; + //TODO: if 'revert' is set on the options it causes end points to animate back to + // where they came from, if the connection is aborted. do we care? probably not. + // the todo is to decide whether we care or not. + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.draggable(options); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.droppable(options); + }, + + isAlreadyDraggable : function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return el.hasClass("ui-draggable"); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).remove(); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + setOffset : function(el, o) { + jsPlumb.CurrentLibrary.getElementObject(el).offset(o); + } + }; +})(jQuery); +/** +* a set of Bezier curve functions that deal with Cubic Beziers, used by jsPlumb, and perhaps useful for other people. +* +* - functions are all in the 'jsBezier' namespace. +* +* - all input points should be in the format {x:.., y:..}. all output points are in this format too. +* +* - all input curves should be in the format [ {x:.., y:..}, {x:.., y:..}, {x:.., y:..}, {x:.., y:..} ] +* +* - 'location' as used as an input here refers to a decimal in the range 0-1 inclusive, which indicates a point some proportion along the length +* of the curve. location as output has the same format and meaning. +* +* +* Function List: +* -------------- +* +* distanceFromCurve(point, curve) +* +* Calculates the distance that the given point lies from the given Cubic Bezier. Note that it is computed relative to the center of the Bezier, +* so if you have stroked the curve with a wide pen you may wish to take that into account! The distance returned is relative to the values +* of the curve and the point - it will most likely be pixels. +* +* gradientAtPoint(curve, location) +* +* Calculates the gradient to the curve at the given location, as a decimal between 0 and 1 inclusive. +* +* nearestPointOnCurve(point, curve) +* +* Calculates the nearest point to the given point on the given curve. The return value of this is a JS object literal, containing both the +*point's coordinates and also the 'location' of the point (see above), for example: { point:{x:551,y:150}, location:0.263365 }. +* +* pointOnCurve(curve, location) +* +* Calculates the coordinates of the point on the given cubic Bezier curve at the given location. +* +* pointAlongCurveFrom(curve, location, distance) +* +* Calculates the coordinates of the point on the given curve that is 'distance' units from location. 'distance' should be in the same coordinate +* space as that used to construct the Bezier curve. For an HTML Canvas usage, for example, distance would be a measure of pixels. +* +* perpendicularToCurveAt(curve, location, length, distance) +* +* Calculates the perpendicular to the given curve at the given location. length is the length of the line you wish for (it will be centered +* on the point at 'location'). distance is optional, and allows you to specify a point along the path from the given location as the center of +* the perpendicular returned. The return value of this is an array of two points: [ {x:...,y:...}, {x:...,y:...} ]. +* +* quadraticPointOnCurve(curve, location) +* +* Calculates the coordinates of the point on the given quadratic Bezier curve at the given location. This function is used internally by +* pointOnCurve, and is exposed just because it seemed churlish not to do so. But remember that all the other functions in this library deal with +* cubic Beziers. +* +* +* references: +* +* http://webdocs.cs.ualberta.ca/~graphics/books/GraphicsGems/gems/NearestPoint.c +* http://13thparallel.com/archive/bezier-curves/ +* http://bimixual.org/AnimationLibrary/beziertangents.html +* +*/ + +(function() { + + if(typeof Math.sgn == "undefined") { + Math.sgn = function(x) { return x == 0 ? 0 : x > 0 ? 1 :-1; }; + } + + var Vectors = { + subtract : function(v1, v2) { return {x:v1.x - v2.x, y:v1.y - v2.y }; }, + dotProduct : function(v1, v2) { return (v1.x * v2.x) + (v1.y * v2.y); }, + square : function(v) { return Math.sqrt((v.x * v.x) + (v.y * v.y)); }, + scale : function(v, s) { return {x:v.x * s, y:v.y * s }; } + }; + + var MAXDEPTH = 64, EPSILON = Math.pow(2.0,-MAXDEPTH-1), DEGREE = 3, W_DEGREE = 5; + + /** + * Calculates the distance that the point lies from the curve. + * + * @param point a point in the form {x:567, y:3342} + * @param curve a Bezier curve in the form [{x:..., y:...}, {x:..., y:...}, {x:..., y:...}, {x:..., y:...}]. note that this is currently + * hardcoded to assume cubiz beziers, but would be better off supporting any degree. + * @return a JS object literal containing location and distance, for example: {location:0.35, distance:10}. Location is analogous to the location + * argument you pass to the pointOnPath function: it is a ratio of distance travelled along the curve. Distance is the distance in pixels from + * the point to the curve. + */ + var _distanceFromCurve = function(point, curve) { + var candidates = new Array(W_DEGREE); + var w = _convertToBezier(point, curve); + var numSolutions = _findRoots(w, W_DEGREE, candidates, 0); + var v = Vectors.subtract(point, curve[0]), dist = Vectors.square(v), t = 0.0; + for (var i = 0; i < numSolutions; i++) { + v = Vectors.subtract(point, _bezier(curve, DEGREE, candidates[i], null, null)); + var newDist = Vectors.square(v); + if (newDist < dist) { + dist = newDist; + t = candidates[i]; + } + } + v = Vectors.subtract(point, curve[DEGREE]); + newDist = Vectors.square(v); + if (newDist < dist) { + dist = newDist; + t = 1.0; + } + return {location:t, distance:dist}; + }; + /** + * finds the nearest point on the curve to the given point. + */ + var _nearestPointOnCurve = function(point, curve) { + var td = _distanceFromCurve(point, curve); + return {point:_bezier(curve, DEGREE, td.location, null, null), location:td.location}; + }; + /** + * internal method; converts to 5th degree Bezier form. + */ + var _convertToBezier = function(point, curve) { + var c = new Array(DEGREE+1), d = new Array(DEGREE), cdTable = [], w = []; + var z = [ [1.0, 0.6, 0.3, 0.1], [0.4, 0.6, 0.6, 0.4], [0.1, 0.3, 0.6, 1.0] ]; + for (var i = 0; i <= DEGREE; i++) c[i] = Vectors.subtract(curve[i], point); + for (var i = 0; i <= DEGREE - 1; i++) { + d[i] = Vectors.subtract(curve[i+1], curve[i]); + d[i] = Vectors.scale(d[i], 3.0); + } + for (var row = 0; row <= DEGREE - 1; row++) { + for (var column = 0; column <= DEGREE; column++) { + if (!cdTable[row]) cdTable[row] = []; + cdTable[row][column] = Vectors.dotProduct(d[row], c[column]); + } + } + for (i = 0; i <= W_DEGREE; i++) { + if (!w[i]) w[i] = []; + w[i].y = 0.0; + w[i].x = parseFloat(i) / W_DEGREE; + } + var n = DEGREE, m = DEGREE-1; + for (var k = 0; k <= n + m; k++) { + var lb = Math.max(0, k - m); + var ub = Math.min(k, n); + for (i = lb; i <= ub; i++) { + j = k - i; + w[i+j].y += cdTable[j][i] * z[j][i]; + } + } + return w; + }; + /** + * counts how many roots there are. + */ + var _findRoots = function(w, degree, t, depth) { + var i; + var Left = new Array(W_DEGREE+1), Right = new Array(W_DEGREE+1); + var left_count, right_count; + var left_t = new Array(W_DEGREE+1), right_t = new Array(W_DEGREE+1); + switch (_getCrossingCount(w, degree)) { + case 0 : { + return 0; + } + case 1 : { + if (depth >= MAXDEPTH) { + t[0] = (w[0].x + w[W_DEGREE].x) / 2.0; + return 1; + } + if (_isControlPolygonFlatEnough(w, degree)) { + t[0] = _computeXIntercept(w, degree); + return 1; + } + break; + } + } + _bezier(w, degree, 0.5, Left, Right); + left_count = _findRoots(Left, degree, left_t, depth+1); + right_count = _findRoots(Right, degree, right_t, depth+1); + for (i = 0; i < left_count; i++) t[i] = left_t[i]; + for (i = 0; i < right_count; i++) t[i+left_count] = right_t[i]; + return (left_count+right_count); + }; + var _getCrossingCount = function(curve, degree) { + var n_crossings = 0; + var sign, old_sign; + sign = old_sign = Math.sgn(curve[0].y); + for (var i = 1; i <= degree; i++) { + sign = Math.sgn(curve[i].y); + if (sign != old_sign) n_crossings++; + old_sign = sign; + } + return n_crossings; + }; + var _isControlPolygonFlatEnough = function(curve, degree) { + var error; + var intercept_1, intercept_2, left_intercept, right_intercept; + var a, b, c, det, dInv, a1, b1, c1, a2, b2, c2; + a = curve[0].y - curve[degree].y; + b = curve[degree].x - curve[0].x; + c = curve[0].x * curve[degree].y - curve[degree].x * curve[0].y; + + var max_distance_above = max_distance_below = 0.0; + + for (var i = 1; i < degree; i++) { + var value = a * curve[i].x + b * curve[i].y + c; + if (value > max_distance_above) + max_distance_above = value; + else if (value < max_distance_below) + max_distance_below = value; + } + + a1 = 0.0; b1 = 1.0; c1 = 0.0; a2 = a; b2 = b; + c2 = c - max_distance_above; + det = a1 * b2 - a2 * b1; + dInv = 1.0/det; + intercept_1 = (b1 * c2 - b2 * c1) * dInv; + a2 = a; b2 = b; c2 = c - max_distance_below; + det = a1 * b2 - a2 * b1; + dInv = 1.0/det; + intercept_2 = (b1 * c2 - b2 * c1) * dInv; + left_intercept = Math.min(intercept_1, intercept_2); + right_intercept = Math.max(intercept_1, intercept_2); + error = right_intercept - left_intercept; + return (error < EPSILON)? 1 : 0; + }; + var _computeXIntercept = function(curve, degree) { + var XLK = 1.0, YLK = 0.0; + var XNM = curve[degree].x - curve[0].x, YNM = curve[degree].y - curve[0].y; + var XMK = curve[0].x - 0.0, YMK = curve[0].y - 0.0; + var det = XNM*YLK - YNM*XLK, detInv = 1.0/det; + var S = (XNM*YMK - YNM*XMK) * detInv; + return 0.0 + XLK * S; + }; + var _bezier = function(curve, degree, t, left, right) { + var temp = [[]]; + for (var j =0; j <= degree; j++) temp[0][j] = curve[j]; + for (var i = 1; i <= degree; i++) { + for (var j =0 ; j <= degree - i; j++) { + if (!temp[i]) temp[i] = []; + if (!temp[i][j]) temp[i][j] = {}; + temp[i][j].x = (1.0 - t) * temp[i-1][j].x + t * temp[i-1][j+1].x; + temp[i][j].y = (1.0 - t) * temp[i-1][j].y + t * temp[i-1][j+1].y; + } + } + if (left != null) + for (j = 0; j <= degree; j++) left[j] = temp[j][0]; + if (right != null) + for (j = 0; j <= degree; j++) right[j] = temp[degree-j][j]; + + return (temp[degree][0]); + }; + + /** + * calculates a point on the curve, for a cubic bezier (TODO: fold this and the other function into one). + * @param curve an array of control points, eg [{x:10,y:20}, {x:50,y:50}, {x:100,y:100}, {x:120,y:100}]. For a cubic bezier this should have four points. + * @param location a decimal indicating the distance along the curve the point should be located at. this is the distance along the curve as it travels, taking the way it bends into account. should be a number from 0 to 1, inclusive. + */ + var _pointOnPath = function(curve, location) { + // from http://13thparallel.com/archive/bezier-curves/ + function B1(t) { return t*t*t }; + function B2(t) { return 3*t*t*(1-t) }; + function B3(t) { return 3*t*(1-t)*(1-t) }; + function B4(t) { return (1-t)*(1-t)*(1-t) }; + + var x = curve[0].x*B1(location) + curve[1].x * B2(location) + curve[2].x * B3(location) + curve[3].x * B4(location); + var y = curve[0].y*B1(location) + curve[1].y * B2(location) + curve[2].y * B3(location) + curve[3].y * B4(location); + //return [x,y]; + return {x:x, y:y}; + }; + + /** + * calculates a point on the curve, for a quadratic bezier (TODO: fold this and the other function into one). + * @param curve an array of control points, eg [{x:10,y:20}, {x:50,y:50}, {x:100,y:100}]. For a quadratic bezier this should have three points. + * @param location a decimal indicating the distance along the curve the point should be located at. this is the distance along the curve as it travels, taking the way it bends into account. should be a number from 0 to 1, inclusive. + */ + var _quadraticPointOnPath = function(curve, location) { + function B1(t) { return t*t; }; + function B2(t) { return 2*t*(1-t); }; + function B3(t) { return (1-t)*(1-t); }; + var x = curve[0].x*B1(location) + curve[1].x*B2(location) + curve[2].x*B3(location); + var y = curve[0].y*B1(location) + curve[1].y*B2(location) + curve[2].y*B3(location); + return {x:x,y:y}; + }; + + /** + * finds the point that is 'distance' along the path from 'location'. this method returns both the x,y location of the point and also + * its 'location' (proportion of travel along the path); the method below - _pointAlongPathFrom - calls this method and just returns the + * point. + */ + var _pointAlongPath = function(curve, location, distance) { + var _dist = function(p1,p2) { return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2)); }; + var prev = _pointOnPath(curve, location), tally = 0, curLoc = location, direction = distance > 0 ? 1 : -1, cur = null; + while (tally < Math.abs(distance)) { + curLoc += (0.005 * direction); + cur = _pointOnPath(curve, curLoc); + tally += _dist(cur, prev); + prev = cur; + } + return {point:cur, location:curLoc}; + }; + + /** + * finds the point that is 'distance' along the path from 'location'. this method returns both the x,y location of the point and also + * its 'location' (proportion of travel along the path). + */ + var _pointAlongPathFrom = function(curve, location, distance) { + return _pointAlongPath(curve, location, distance).point; + }; + + /** + * returns the gradient of the connector at the given location, which is a decimal between 0 and 1 inclusive. + * + * thanks // http://bimixual.org/AnimationLibrary/beziertangents.html + */ + var _gradientAtPoint = function(curve, location) { + var p1 = _pointOnPath(curve, location); + var p2 = _quadraticPointOnPath(curve, location); + var dy = p2.y - p1.y, dx = p2.x - p1.x; + return Math.atan(dy / dx); + }; + + /** + * calculates a line that is 'length' pixels long, perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * if distance is not supplied, the perpendicular for the given location is computed (ie. we set distance to zero). + */ + var _perpendicularToPathAt = function(curve, location, length, distance) { + distance = distance == null ? 0 : distance; + var p = _pointAlongPath(curve, location, distance); + var m = _gradientAtPoint(curve, p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.point.x + x, y:p.point.y + y}, {x:p.point.x - x, y:p.point.y - y}]; + }; + + var jsBezier = window.jsBezier = { + distanceFromCurve : _distanceFromCurve, + gradientAtPoint : _gradientAtPoint, + nearestPointOnCurve : _nearestPointOnCurve, + pointOnCurve : _pointOnPath, + pointAlongCurveFrom : _pointAlongPathFrom, + perpendicularToCurveAt : _perpendicularToPathAt, + quadraticPointOnCurve : _quadraticPointOnPath //TODO fold the two pointOnPath functions into one; it can detect what it was given. + }; +})(); diff --git a/archive/1.2.5/jsBezier-0.1.js b/archive/1.2.5/jsBezier-0.1.js new file mode 100644 index 000000000..52476d284 --- /dev/null +++ b/archive/1.2.5/jsBezier-0.1.js @@ -0,0 +1,341 @@ +/** +* a set of Bezier curve functions that deal with Cubic Beziers, used by jsPlumb, and perhaps useful for other people. +* +* - functions are all in the 'jsBezier' namespace. +* +* - all input points should be in the format {x:.., y:..}. all output points are in this format too. +* +* - all input curves should be in the format [ {x:.., y:..}, {x:.., y:..}, {x:.., y:..}, {x:.., y:..} ] +* +* - 'location' as used as an input here refers to a decimal in the range 0-1 inclusive, which indicates a point some proportion along the length +* of the curve. location as output has the same format and meaning. +* +* +* Function List: +* -------------- +* +* distanceFromCurve(point, curve) +* +* Calculates the distance that the given point lies from the given Cubic Bezier. Note that it is computed relative to the center of the Bezier, +* so if you have stroked the curve with a wide pen you may wish to take that into account! The distance returned is relative to the values +* of the curve and the point - it will most likely be pixels. +* +* gradientAtPoint(curve, location) +* +* Calculates the gradient to the curve at the given location, as a decimal between 0 and 1 inclusive. +* +* nearestPointOnCurve(point, curve) +* +* Calculates the nearest point to the given point on the given curve. The return value of this is a JS object literal, containing both the +*point's coordinates and also the 'location' of the point (see above), for example: { point:{x:551,y:150}, location:0.263365 }. +* +* pointOnCurve(curve, location) +* +* Calculates the coordinates of the point on the given cubic Bezier curve at the given location. +* +* pointAlongCurveFrom(curve, location, distance) +* +* Calculates the coordinates of the point on the given curve that is 'distance' units from location. 'distance' should be in the same coordinate +* space as that used to construct the Bezier curve. For an HTML Canvas usage, for example, distance would be a measure of pixels. +* +* perpendicularToCurveAt(curve, location, length, distance) +* +* Calculates the perpendicular to the given curve at the given location. length is the length of the line you wish for (it will be centered +* on the point at 'location'). distance is optional, and allows you to specify a point along the path from the given location as the center of +* the perpendicular returned. The return value of this is an array of two points: [ {x:...,y:...}, {x:...,y:...} ]. +* +* quadraticPointOnCurve(curve, location) +* +* Calculates the coordinates of the point on the given quadratic Bezier curve at the given location. This function is used internally by +* pointOnCurve, and is exposed just because it seemed churlish not to do so. But remember that all the other functions in this library deal with +* cubic Beziers. +* +* +* references: +* +* http://webdocs.cs.ualberta.ca/~graphics/books/GraphicsGems/gems/NearestPoint.c +* http://13thparallel.com/archive/bezier-curves/ +* http://bimixual.org/AnimationLibrary/beziertangents.html +* +*/ + +(function() { + + if(typeof Math.sgn == "undefined") { + Math.sgn = function(x) { return x == 0 ? 0 : x > 0 ? 1 :-1; }; + } + + var Vectors = { + subtract : function(v1, v2) { return {x:v1.x - v2.x, y:v1.y - v2.y }; }, + dotProduct : function(v1, v2) { return (v1.x * v2.x) + (v1.y * v2.y); }, + square : function(v) { return Math.sqrt((v.x * v.x) + (v.y * v.y)); }, + scale : function(v, s) { return {x:v.x * s, y:v.y * s }; } + }; + + var MAXDEPTH = 64, EPSILON = Math.pow(2.0,-MAXDEPTH-1), DEGREE = 3, W_DEGREE = 5; + + /** + * Calculates the distance that the point lies from the curve. + * + * @param point a point in the form {x:567, y:3342} + * @param curve a Bezier curve in the form [{x:..., y:...}, {x:..., y:...}, {x:..., y:...}, {x:..., y:...}]. note that this is currently + * hardcoded to assume cubiz beziers, but would be better off supporting any degree. + * @return a JS object literal containing location and distance, for example: {location:0.35, distance:10}. Location is analogous to the location + * argument you pass to the pointOnPath function: it is a ratio of distance travelled along the curve. Distance is the distance in pixels from + * the point to the curve. + */ + var _distanceFromCurve = function(point, curve) { + var candidates = new Array(W_DEGREE); + var w = _convertToBezier(point, curve); + var numSolutions = _findRoots(w, W_DEGREE, candidates, 0); + var v = Vectors.subtract(point, curve[0]), dist = Vectors.square(v), t = 0.0; + for (var i = 0; i < numSolutions; i++) { + v = Vectors.subtract(point, _bezier(curve, DEGREE, candidates[i], null, null)); + var newDist = Vectors.square(v); + if (newDist < dist) { + dist = newDist; + t = candidates[i]; + } + } + v = Vectors.subtract(point, curve[DEGREE]); + newDist = Vectors.square(v); + if (newDist < dist) { + dist = newDist; + t = 1.0; + } + return {location:t, distance:dist}; + }; + /** + * finds the nearest point on the curve to the given point. + */ + var _nearestPointOnCurve = function(point, curve) { + var td = _distanceFromCurve(point, curve); + return {point:_bezier(curve, DEGREE, td.location, null, null), location:td.location}; + }; + /** + * internal method; converts to 5th degree Bezier form. + */ + var _convertToBezier = function(point, curve) { + var c = new Array(DEGREE+1), d = new Array(DEGREE), cdTable = [], w = []; + var z = [ [1.0, 0.6, 0.3, 0.1], [0.4, 0.6, 0.6, 0.4], [0.1, 0.3, 0.6, 1.0] ]; + for (var i = 0; i <= DEGREE; i++) c[i] = Vectors.subtract(curve[i], point); + for (var i = 0; i <= DEGREE - 1; i++) { + d[i] = Vectors.subtract(curve[i+1], curve[i]); + d[i] = Vectors.scale(d[i], 3.0); + } + for (var row = 0; row <= DEGREE - 1; row++) { + for (var column = 0; column <= DEGREE; column++) { + if (!cdTable[row]) cdTable[row] = []; + cdTable[row][column] = Vectors.dotProduct(d[row], c[column]); + } + } + for (i = 0; i <= W_DEGREE; i++) { + if (!w[i]) w[i] = []; + w[i].y = 0.0; + w[i].x = parseFloat(i) / W_DEGREE; + } + var n = DEGREE, m = DEGREE-1; + for (var k = 0; k <= n + m; k++) { + var lb = Math.max(0, k - m); + var ub = Math.min(k, n); + for (i = lb; i <= ub; i++) { + j = k - i; + w[i+j].y += cdTable[j][i] * z[j][i]; + } + } + return w; + }; + /** + * counts how many roots there are. + */ + var _findRoots = function(w, degree, t, depth) { + var i; + var Left = new Array(W_DEGREE+1), Right = new Array(W_DEGREE+1); + var left_count, right_count; + var left_t = new Array(W_DEGREE+1), right_t = new Array(W_DEGREE+1); + switch (_getCrossingCount(w, degree)) { + case 0 : { + return 0; + } + case 1 : { + if (depth >= MAXDEPTH) { + t[0] = (w[0].x + w[W_DEGREE].x) / 2.0; + return 1; + } + if (_isControlPolygonFlatEnough(w, degree)) { + t[0] = _computeXIntercept(w, degree); + return 1; + } + break; + } + } + _bezier(w, degree, 0.5, Left, Right); + left_count = _findRoots(Left, degree, left_t, depth+1); + right_count = _findRoots(Right, degree, right_t, depth+1); + for (i = 0; i < left_count; i++) t[i] = left_t[i]; + for (i = 0; i < right_count; i++) t[i+left_count] = right_t[i]; + return (left_count+right_count); + }; + var _getCrossingCount = function(curve, degree) { + var n_crossings = 0; + var sign, old_sign; + sign = old_sign = Math.sgn(curve[0].y); + for (var i = 1; i <= degree; i++) { + sign = Math.sgn(curve[i].y); + if (sign != old_sign) n_crossings++; + old_sign = sign; + } + return n_crossings; + }; + var _isControlPolygonFlatEnough = function(curve, degree) { + var error; + var intercept_1, intercept_2, left_intercept, right_intercept; + var a, b, c, det, dInv, a1, b1, c1, a2, b2, c2; + a = curve[0].y - curve[degree].y; + b = curve[degree].x - curve[0].x; + c = curve[0].x * curve[degree].y - curve[degree].x * curve[0].y; + + var max_distance_above = max_distance_below = 0.0; + + for (var i = 1; i < degree; i++) { + var value = a * curve[i].x + b * curve[i].y + c; + if (value > max_distance_above) + max_distance_above = value; + else if (value < max_distance_below) + max_distance_below = value; + } + + a1 = 0.0; b1 = 1.0; c1 = 0.0; a2 = a; b2 = b; + c2 = c - max_distance_above; + det = a1 * b2 - a2 * b1; + dInv = 1.0/det; + intercept_1 = (b1 * c2 - b2 * c1) * dInv; + a2 = a; b2 = b; c2 = c - max_distance_below; + det = a1 * b2 - a2 * b1; + dInv = 1.0/det; + intercept_2 = (b1 * c2 - b2 * c1) * dInv; + left_intercept = Math.min(intercept_1, intercept_2); + right_intercept = Math.max(intercept_1, intercept_2); + error = right_intercept - left_intercept; + return (error < EPSILON)? 1 : 0; + }; + var _computeXIntercept = function(curve, degree) { + var XLK = 1.0, YLK = 0.0; + var XNM = curve[degree].x - curve[0].x, YNM = curve[degree].y - curve[0].y; + var XMK = curve[0].x - 0.0, YMK = curve[0].y - 0.0; + var det = XNM*YLK - YNM*XLK, detInv = 1.0/det; + var S = (XNM*YMK - YNM*XMK) * detInv; + return 0.0 + XLK * S; + }; + var _bezier = function(curve, degree, t, left, right) { + var temp = [[]]; + for (var j =0; j <= degree; j++) temp[0][j] = curve[j]; + for (var i = 1; i <= degree; i++) { + for (var j =0 ; j <= degree - i; j++) { + if (!temp[i]) temp[i] = []; + if (!temp[i][j]) temp[i][j] = {}; + temp[i][j].x = (1.0 - t) * temp[i-1][j].x + t * temp[i-1][j+1].x; + temp[i][j].y = (1.0 - t) * temp[i-1][j].y + t * temp[i-1][j+1].y; + } + } + if (left != null) + for (j = 0; j <= degree; j++) left[j] = temp[j][0]; + if (right != null) + for (j = 0; j <= degree; j++) right[j] = temp[degree-j][j]; + + return (temp[degree][0]); + }; + + /** + * calculates a point on the curve, for a cubic bezier (TODO: fold this and the other function into one). + * @param curve an array of control points, eg [{x:10,y:20}, {x:50,y:50}, {x:100,y:100}, {x:120,y:100}]. For a cubic bezier this should have four points. + * @param location a decimal indicating the distance along the curve the point should be located at. this is the distance along the curve as it travels, taking the way it bends into account. should be a number from 0 to 1, inclusive. + */ + var _pointOnPath = function(curve, location) { + // from http://13thparallel.com/archive/bezier-curves/ + function B1(t) { return t*t*t }; + function B2(t) { return 3*t*t*(1-t) }; + function B3(t) { return 3*t*(1-t)*(1-t) }; + function B4(t) { return (1-t)*(1-t)*(1-t) }; + + var x = curve[0].x*B1(location) + curve[1].x * B2(location) + curve[2].x * B3(location) + curve[3].x * B4(location); + var y = curve[0].y*B1(location) + curve[1].y * B2(location) + curve[2].y * B3(location) + curve[3].y * B4(location); + //return [x,y]; + return {x:x, y:y}; + }; + + /** + * calculates a point on the curve, for a quadratic bezier (TODO: fold this and the other function into one). + * @param curve an array of control points, eg [{x:10,y:20}, {x:50,y:50}, {x:100,y:100}]. For a quadratic bezier this should have three points. + * @param location a decimal indicating the distance along the curve the point should be located at. this is the distance along the curve as it travels, taking the way it bends into account. should be a number from 0 to 1, inclusive. + */ + var _quadraticPointOnPath = function(curve, location) { + function B1(t) { return t*t; }; + function B2(t) { return 2*t*(1-t); }; + function B3(t) { return (1-t)*(1-t); }; + var x = curve[0].x*B1(location) + curve[1].x*B2(location) + curve[2].x*B3(location); + var y = curve[0].y*B1(location) + curve[1].y*B2(location) + curve[2].y*B3(location); + return {x:x,y:y}; + }; + + /** + * finds the point that is 'distance' along the path from 'location'. this method returns both the x,y location of the point and also + * its 'location' (proportion of travel along the path); the method below - _pointAlongPathFrom - calls this method and just returns the + * point. + */ + var _pointAlongPath = function(curve, location, distance) { + var _dist = function(p1,p2) { return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2)); }; + var prev = _pointOnPath(curve, location), tally = 0, curLoc = location, direction = distance > 0 ? 1 : -1, cur = null; + while (tally < Math.abs(distance)) { + curLoc += (0.005 * direction); + cur = _pointOnPath(curve, curLoc); + tally += _dist(cur, prev); + prev = cur; + } + return {point:cur, location:curLoc}; + }; + + /** + * finds the point that is 'distance' along the path from 'location'. this method returns both the x,y location of the point and also + * its 'location' (proportion of travel along the path). + */ + var _pointAlongPathFrom = function(curve, location, distance) { + return _pointAlongPath(curve, location, distance).point; + }; + + /** + * returns the gradient of the connector at the given location, which is a decimal between 0 and 1 inclusive. + * + * thanks // http://bimixual.org/AnimationLibrary/beziertangents.html + */ + var _gradientAtPoint = function(curve, location) { + var p1 = _pointOnPath(curve, location); + var p2 = _quadraticPointOnPath(curve, location); + var dy = p2.y - p1.y, dx = p2.x - p1.x; + return Math.atan(dy / dx); + }; + + /** + * calculates a line that is 'length' pixels long, perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * if distance is not supplied, the perpendicular for the given location is computed (ie. we set distance to zero). + */ + var _perpendicularToPathAt = function(curve, location, length, distance) { + distance = distance == null ? 0 : distance; + var p = _pointAlongPath(curve, location, distance); + var m = _gradientAtPoint(curve, p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.point.x + x, y:p.point.y + y}, {x:p.point.x - x, y:p.point.y - y}]; + }; + + var jsBezier = window.jsBezier = { + distanceFromCurve : _distanceFromCurve, + gradientAtPoint : _gradientAtPoint, + nearestPointOnCurve : _nearestPointOnCurve, + pointOnCurve : _pointOnPath, + pointAlongCurveFrom : _pointAlongPathFrom, + perpendicularToCurveAt : _perpendicularToPathAt, + quadraticPointOnCurve : _quadraticPointOnPath //TODO fold the two pointOnPath functions into one; it can detect what it was given. + }; +})(); diff --git a/archive/1.2.5/jsBezier-0.2-min.js b/archive/1.2.5/jsBezier-0.2-min.js new file mode 100644 index 000000000..3b41333c6 --- /dev/null +++ b/archive/1.2.5/jsBezier-0.2-min.js @@ -0,0 +1,7 @@ +(function(){if(typeof Math.sgn=="undefined")Math.sgn=function(a){return a==0?0:a>0?1:-1};var p={subtract:function(a,b){return{x:a.x-b.x,y:a.y-b.y}},dotProduct:function(a,b){return a.x*b.x+a.y*b.y},square:function(a){return Math.sqrt(a.x*a.x+a.y*a.y)},scale:function(a,b){return{x:a.x*b,y:a.y*b}}},y=Math.pow(2,-65),u=function(a,b){for(var g=[],d=b.length-1,h=2*d-1,f=[],c=[],l=[],k=[],i=[[1,0.6,0.3,0.1],[0.4,0.6,0.6,0.4],[0.1,0.3,0.6,1]],e=0;e<=d;e++)f[e]=p.subtract(b[e],a);for(e=0;e<=d-1;e++){c[e]= +p.subtract(b[e+1],b[e]);c[e]=p.scale(c[e],3)}for(e=0;e<=d-1;e++)for(var m=0;m<=d;m++){l[e]||(l[e]=[]);l[e][m]=p.dotProduct(c[e],f[m])}for(e=0;e<=h;e++){k[e]||(k[e]=[]);k[e].y=0;k[e].x=parseFloat(e)/h}h=d-1;for(f=0;f<=d+h;f++){c=Math.min(f,d);for(e=Math.max(0,f-h);e<=c;e++){j=f-e;k[e+j].y+=l[j][e]*i[j][e]}}d=b.length-1;k=s(k,2*d-1,g,0);h=p.subtract(a,b[0]);l=p.square(h);for(e=i=0;e=64){g[0]=(a[0].x+a[b].x)/2;return 1}var n,o,q;k=a[0].y-a[b].y;i=a[b].x-a[0].x;e=a[0].x*a[b].y-a[b].x*a[0].y;m=max_distance_below=0;for(o=1;om)m=q;else if(q0?1:-1,c=null;h endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + var endpointsByElement = {}; + var endpointsByUUID = {}; + var connectionsByScope = {}; + var offsets = {}; + var offsetTimestamps = {}; + var floatingConnections = {}; + var draggableStates = {}; + var _draggableByDefault = true; + var sizes = []; + var listeners = {}; // a map: keys are event types, values are lists of listeners. + var DEFAULT_SCOPE = 'DEFAULT'; + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + var _findIndex = function(a, v, b, s) { + var _eq = function(o1, o2) { + if (o1 === o2) + return true; + else if (typeof o1 == 'object' && typeof o2 == 'object') { + var same = true; + for ( var propertyName in o1) { + if (!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for ( var propertyName in o2) { + if (!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for ( var i = +b || 0, l = a.length; i < l; i++) { + if (_eq(a[i], v)) + return i; + } + return -1; + }; + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }; + + /** + * appends an element to the given parent, or the document body if no + * parent given. + */ + var _appendElement = function(canvas, parent) { + if (!parent) + document.body.appendChild(canvas); + else + jsPlumb.CurrentLibrary.appendElement(canvas, parent); + }; + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + var _timestamp = function() { return "" + (new Date()).getTime(); }; + + /** + * Draws an endpoint and its connections. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + var _draw = function(element, ui, timestamp) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (!timestamp) timestamp = _timestamp(); + if (endpoints) { + _updateOffset( { elId : id, offset : ui, recalc : false, timestamp : timestamp }); // timestamp is checked against last update cache; it is + // valid for one paint cycle. + var myOffset = offsets[id], myWH = sizes[id]; + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + var l = endpoints[i].connections; + for ( var j = 0; j < l.length; j++) { + l[j].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); // ...paint each connection. + // then, check for dynamic endpoint; need to repaint it. + var oIdx = l[j].endpoints[0] == endpoints[i] ? 1 : 0; + if (l[j].endpoints[oIdx].anchor.isDynamic && !l[j].endpoints[oIdx].isFloating()) { + var oId = oIdx == 0 ? l[j].sourceId : l[j].targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + // TODO i still want to make this faster. + var anchorLoc = l[j].endpoints[oIdx].anchor.compute( { + xy : [ oOffset.left, oOffset.top ], + wh : oWH, + element : l[j].endpoints[oIdx], + txy : [ myOffset.left, myOffset.top ], + twh : myWH, + tElement : endpoints[i] + }); + l[j].endpoints[oIdx].paint({ anchorLoc : anchorLoc }); + } + } + } + } + }; + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + var _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }; + + /** + * + */ + var _log = function(msg) { + if (_currentInstance.logEnabled && typeof console != "undefined") + console.log(msg); + }; + + var _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }; + var _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }; + var _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }; + var _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }; + var _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }; + var _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }; + var _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }; + + /** + * gets an id for the given element, creating and setting one if + * necessary. + */ + var _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else + id = "jsPlumb_" + _timestamp(); + _setAttribute(ele, "id", id); + } + return id; + }; + + /** + * gets an Endpoint by uuid. + */ + var _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }; + + /** + * inits a draggable if it's not already initialised. + */ + var _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element) && !jsPlumb.CurrentLibrary.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + } + } + }; + + /** + * helper to create a canvas. + * + * @param clazz + * optional class name for the canvas. + */ + var _newCanvas = function(clazz, parent, uuid) { + var canvas = document.createElement("canvas"); + _appendElement(canvas, parent); + canvas.style.position = "absolute"; + if (clazz) canvas.className = clazz; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + _getId(canvas, uuid); + if (ie) { + // for IE we have to set a big canvas size. actually you can + // override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas + // you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + var _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }; + /** + * perform an operation on all elements. + */ + var _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }; + /** + * helper to remove an element from the DOM. + */ + var _removeElement = function(element, parent) { + if (element != null) { + if (!parent) { + try { + document.body.removeChild(element); + } catch (e) { + } + } else { + jsPlumb.CurrentLibrary.removeElement(element, parent); + } + } + }; + /** + * helper to remove a list of elements from the DOM. + */ + var _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }; + /** + * helper method to remove an item from a list. + */ + var _removeFromList = function(map, key, value) { + if (key != null) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete (l[i]); + l.splice(i, 1); + return true; + } + } + } + return false; + }; + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }; + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + var _setVisible = function(el, state) { + _operation(_getAttribute(el, "id"), function(jpc) { + jpc.canvas.style.display = state; + }); + }; + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + var _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }; + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + var _toggleVisible = function(elId) { + _operation(elId, function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }; + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we use the + * offset, outerWidth and outerHeight methods to get the current values. + */ + var _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return; + } + if (recalc || offset == null) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } else { + offsets[elId] = offset; + } + }; + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + var _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log('jsPlumb function failed : ' + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log('wrapped function failed : ' + e); + } + } + return r; + }; + }; + + /* + * Class: Anchor Models a position relative to the origin of some + * element that an Endpoint can be located. + */ + /* + * Function: Anchor + * + * Constructor for the Anchor class + * + * Parameters: + * - x : the x location of the anchor as a fraction of the total width. - + * y : the y location of the anchor as a fraction of the total height. - + * orientation : an [x,y] array indicating the general direction a + * connection from the anchor should go in. for more info on this, see + * the documentation, or the docs in jquery-jsPlumb-defaults-XXX.js for + * the default Anchors. - offsets : an [x,y] array of fixed offsets that + * should be applied after the x,y position has been figured out. may be + * null. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + if (timestamp && timestamp === self.timestamp) { + return lastReturnValue; + } + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + var container = element ? element.container : null; + var containerAdjustment = { left : 0, top : 0 }; + if (container != null) { + var eo = _getElementObject(container); + var o = _getOffset(eo); + var sl = jsPlumb.CurrentLibrary.getScrollLeft(eo); + var st = jsPlumb.CurrentLibrary.getScrollTop(eo); + containerAdjustment.left = o.left - sl; + containerAdjustment.top = o.top - st; + lastReturnValue[0] = lastReturnValue[0] - containerAdjustment.left; + lastReturnValue[1] = lastReturnValue[1] - containerAdjustment.top; + } + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. + * + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(_getElementObject(refCanvas)); + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + var orientation = null; + var _lastResult = null; + + this.compute = function(params) { + var xy = params.xy, el = params.element; + var result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + if (el.container != null) { + var o = _getOffset(el.container); + result[0] = result[0] - o.left; + result[1] = result[1] - o.top; + } + _lastResult = result; + return result; + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { orientation = anchor.getOrientation(); }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * Class: DynamicAnchor + * + * An Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements - a useful + * feature for some applications. + * + * the second argument - anchorSelector - is optional, but when provided should be a function + * that + */ + var DynamicAnchor = function(anchors, anchorSelector) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = anchors || []; + var _convert = function(anchor) { return anchor.constructor == Anchor ? anchor: jsPlumb.makeAnchor(anchor); }; + for (var i = 0; i < _anchors.length; i++) _anchors[i] = _convert(_anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null; + var _curIndex = _anchors.length > 0 ? 0 : -1; + this.locked = false; + var self = this; + + // helper method to calculate the distance between the centers of the two elements. + var _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }; + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + var _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + + var pos = _curAnchor.compute(params); + return pos; + }; + + this.getCurrentLocation = function() { + var cl = _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + return cl; + }; + + this.getOrientation = function() { return _curAnchor != null ? _curAnchor.getOrientation() : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + * Class:Connection + * + * The connecting line between two Endpoints. + * + */ + var Connection = function(params) { + + EventGenerator.apply(this); + // ************** get the source and target and register the + // connection. ******************* + var self = this; + var id = new String('_jsplumb_c_' + (new Date()).getTime()); + this.getId = function() { return id; }; + this.container = params.container || _currentInstance.Defaults.Container; // may be null; we will append to the body if so. + // get source and target as jQuery objects + this.source = _getElementObject(params.source); + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. + if (params.sourceEndpoint) this.source = params.sourceEndpoint.getElement(); + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + this.sourceId = _getAttribute(this.source, "id"); + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + this.scope = params.scope; // scope may have been passed in to the connect call. + // if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. init endpoints + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams) { + if (anchorParams) + return jsPlumb.makeAnchor(anchorParams); + }; + var prepareEndpoint = function(existing, index, params, element) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint + || new jsPlumb.Endpoints.Dot(); + if (ep.constructor == String) ep = new jsPlumb.Endpoints[ep](); + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + var a = params.anchors ? params.anchors[index] : _makeAnchor(_currentInstance.Defaults.Anchors[index]) || _makeAnchor(jsPlumb.Defaults.Anchors[index]) || _makeAnchor(_currentInstance.Defaults.Anchor) || _makeAnchor(jsPlumb.Defaults.Anchor) || _makeAnchor("BottomCenter"); + var u = params.uuids ? params.uuids[index] : null; + var e = new Endpoint( { style : es, endpoint : ep, connections : [ self ], uuid : u, anchor : a, source : element, container : self.container }); + self.endpoints[index] = e; + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || _currentInstance.Defaults.Connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + if (this.connector.constructor == String) this.connector = new jsPlumb.Connectors[this.connector](); // lets you use a string as shorthand. + this.paintStyle = this.endpoints[0].connectorStyle || this.endpoints[1].connectorStyle || params.paintStyle || _currentInstance.Defaults.PaintStyle || jsPlumb.Defaults.PaintStyle; + this.backgroundPaintStyle = this.endpoints[0].connectorBackgroundStyle || this.endpoints[1].connectorBackgroundStyle || params.backgroundPaintStyle || _currentInstance.Defaults.BackgroundPaintStyle || jsPlumb.Defaults.BackgroundPaintStyle; + + // init overlays + this.overlays = params.overlays || []; + this.addOverlay = function(overlay) { overlays.push(overlay); }; + + // this is a shortcut helper method to let people add a label as + // overlay. + this.labelStyle = params.labelStyle || _currentInstance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + if (this.label) { + this.overlays.push(new jsPlumb.Overlays.Label( { + labelStyle : this.labelStyle, + label : this.label + })); + } + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + // functions for mouse hover/select functionality + this.distanceFrom = function(point) { return self.connector.distanceFrom(point); }; + + this.setLabel = function(l) { + self.label = l; + _currentInstance.repaint(self.source); + }; + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var otherOffset = offsets[this.targetId]; + otherWH = sizes[this.targetId]; + // todo: why not fold this and the paint call that follows into one + // call? + var anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1] + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0] + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc }); + + var canvas = _newCanvas(jsPlumb.connectorClass, self.container); + this.canvas = canvas; + + /* + * Function: paint + * + * paints the connection. + * + * Parameters: + * elId Id of the element that is in motion + * ui current library's event system ui object (present if we came from a drag to get here) + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp; + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the + // two references. + var swap = false; + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + if (this.canvas.getContext) { + _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }); + _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + // var myOffset = offsets[sId], otherOffset = offsets[tId], myWH = sizes[sId], otherWH = sizes[tId]; + var ctx = canvas.getContext('2d'); + var sAnchorP = this.endpoints[sIdx].anchor.getCurrentLocation(); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.getCurrentLocation(); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + + // paint overlays + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + var s = o.computeMaxSize(self.connector, ctx); + if (s > maxSize) + maxSize = s; + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, this.paintStyle.lineWidth, maxSize); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + + var _paintOneStyle = function(ctx, paintStyle) { + ctx.save(); + jsPlumb.extend(ctx, paintStyle); + if (paintStyle.gradient && !ie) { + var g = self.connector.createGradient(dim, ctx, (elId == this.sourceId)); + for ( var i = 0; i < paintStyle.gradient.stops.length; i++) + g.addColorStop(paintStyle.gradient.stops[i][0], paintStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + self.connector.paint(dim, ctx); + ctx.restore(); + }; + + // first check for the background style + if (this.backgroundPaintStyle != null) { + _paintOneStyle(ctx, this.backgroundPaintStyle); + } + _paintOneStyle(ctx, this.paintStyle); + + // paint overlays + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + o.draw(self.connector, ctx); + } + } + }; + + // TODO: should this take a timestamp? probably. it reduces the + // amount of time + // spent figuring out anchor locations. + this.repaint = function() { + this.paint({ elId : this.sourceId, recalc : true }); + }; + + _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide + // whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + }; + + /* + * Class: Endpoint Models an endpoint. Can have one to N connections + * emanating from it (although how to handle that in the UI is a very + * good question). also has a Canvas and paint style. + */ + + /* + * Function: Endpoint This is the Endpoint class constructor. + * Parameters: anchor - anchor for the endpoint, of type jsPlumb.Anchor. + * may be null. endpoint - endpoint object, of type jsPlumb.Endpoint. + * may be null. style - endpoint style, a js object. may be null. source - + * element the endpoint is attached to, of type jquery object. Required. + * canvas - canvas element to use. may be, and most often is, null. + * connections - optional list of connections to configure the endpoint + * with. isSource - boolean. indicates the endpoint can act as a source + * of new connections. optional. dragOptions - if isSource is set to + * true, you can supply arguments for the jquery draggable method. + * optional. connectionStyle - if isSource is set to true, this is the + * paint style for connections from this endpoint. optional. connector - + * optional connector type to use. isTarget - boolean. indicates the + * endpoint can act as a target of new connections. optional. + * dropOptions - if isTarget is set to true, you can supply arguments + * for the jquery droppable method. optional. reattach - optional + * boolean that determines whether or not the connections reattach after + * they have been dragged off an endpoint and left floating. defaults to + * false: connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + params = params || {}; + var self = this; + var id = new String('_jsplumb_e_' + (new Date()).getTime()); + this.getId = function() { return id; }; + if (params.dynamicAnchors) + self.anchor = new DynamicAnchor(jsPlumb.makeAnchors(params.dynamicAnchors)); + else + self.anchor = params.anchor ? jsPlumb.makeAnchor(params.anchor) : params.anchors ? jsPlumb.makeAnchor(params.anchors) : jsPlumb.makeAnchor("TopCenter"); + var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot(); + if (_endpoint.constructor == String) _endpoint = new jsPlumb.Endpoints[_endpoint](); + self.endpoint = _endpoint; + var _style = params.style || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + this.connectorStyle = params.connectorStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + var _element = params.source, _uuid = params.uuid; + var floatingEndpoint = null, inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + this.container = params.container || _currentInstance.Defaults.Container || jsPlumb.Defaults.Container; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of. + this.canvas = params.canvas || _newCanvas(jsPlumb.endpointClass, this.container, params.uuid); + this.connections = params.connections || []; + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + var _reattach = params.reattach || false; + this.dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + this.addConnection = function(connection) { + self.connections.push(connection); + }; + this.detach = function(connection, ignoreTarget) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + // this avoids a circular loop + if (!ignoreTarget) { + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + t.detach(connection, true); + } + _removeElement(connection.canvas, connection.container); + _removeFromList(connectionsByScope, connection.scope, connection); + if(!ignoreTarget) fireDetachEvent(connection); + } + }; + + /** + * detaches all connections this Endpoint has + */ + this.detachAll = function() { + while (self.connections.length > 0) { + self.detach(self.connections[0]); + } + }; + + /** + * removes any connections from this Endpoint that are connected to + * the given target endpoint. + * + * @param targetEndpoint + */ + this.detachFrom = function(targetEndpoint) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + self.detach(c[i]); + } + }; + + /** + * detach this Endpoint from the Connection, but leave the Connection alive. used when dragging. + */ + this.detachFromConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /** + * returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /** + * returns the UUID for this Endpoint, if there is one. + */ + this.getUuid = function() { + return _uuid; + }; + + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + var e = new Endpoint( { anchor : self.anchor, source : _element, style : _style, endpoint : _endpoint }); + return e; + }; + + /** + * returns whether or not this endpoint is connected to the given + * endpoint. + * + * @param endpoint + * Endpoint to test. + * @since 1.1.1 + * + * todo: needs testing. will this work if the endpoint passed in is + * the source? + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + * + * @returns {Boolean} + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + var connectorSelector = function() { + return (self.connections.length < _maxConnections) ? null : self.connections[0]; + }; + + /** + * @returns whether or not the Endpoint can accept any more + * Connections. + */ + this.isFull = function() { + return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); + }; + + /** + * sets whether or not connnctions can be dragged from this Endpoint + * once it is full. you would use this in a UI in which you're going + * to provide some other way of breaking connections, if you need to + * break them at all. this property is by default true; use it in + * conjunction with the 'reattach' option on a connect call. + */ + this.setDragAllowedWhenFull = function(allowed) { + self.dragAllowedWhenFull = allowed; + }; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + /** + * paints the Endpoint, recalculating offset and anchor positions if + * necessary. + */ + this.paint = function(params) { + + params = params || {}; + var timestamp = params.timestamp; + if (!timestamp || self.timestamp !== timestamp) { + var ap = params.anchorPoint, canvas = params.canvas, connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + // do we always want to force a repaint here? i dont + // think so! + var xy = params.offset || offsets[_elementId]; + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (self.anchor.isDynamic) { + if (self.connections.length > 0) { + var c = self.connections[0]; + var oIdx = c.endpoints[0] == self ? 1 : 0; + var oId = oIdx == 0 ? c.sourceId : c.targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + } + ap = self.anchor.compute(anchorParams); + } + _endpoint.paint(ap, self.anchor.getOrientation(), canvas || self.canvas, _style, connectorPaintStyle || _style); + self.timestamp = timestamp; + } + }; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener + // maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + var start = function() { + jpc = connectorSelector(); + if (self.isFull() && !self.dragAllowedWhenFull) return false; + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + var nE = _getElementObject(n); + _appendElement(n, self.container); // + // create and assign an id, and initialize the offset. + var id = _getId(nE); + _updateOffset( { elId : id }); + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor( { reference : self.anchor, referenceCanvas : self.canvas }); + floatingEndpoint = new Endpoint({ style : { fillStyle : 'rgba(0,0,0,0)' }, endpoint : _endpoint, anchor : floatingAnchor, source : nE }); + + if (jpc == null) { + self.anchor.locked = true; + // create a connection. one end is this endpoint, the + // other is a floating endpoint. + jpc = new Connection( { + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : _getElementObject(_element), + target : _getElementObject(n), + anchors : [ self.anchor, floatingAnchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + connector : params.connector, // this can also be null. Connection will use the default. + overlays : self.connectorOverlays // new in 1.2.4. + }); + } else { + existingJpc = true; + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas)); + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; + // detach from the connection while dragging is occurring. + self.detachFromConnection(jpc); + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId ]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId ]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + // lock the other endpoint; if it is dynamic it will not + // move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new + // floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // TODO unregister on stop? or will floating endpoint's + // destruction be assured. + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be + // dragging the source at any time + // before this connection is either discarded or made into a + // permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + }; + + var dragOptions = params.dragOptions || {}; + var defaultOpts = jsPlumb.extend( {}, jsPlumb.CurrentLibrary.defaultDragOptions); + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + var startEvent = jsPlumb.CurrentLibrary.dragEvents['start']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], + function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + jsPlumb.CurrentLibrary.setOffset(n, _ui); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements( [ n, floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (_reattach) { + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } else { + jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc); // the main endpoint will inform the floating endpoint + // to disconnect, and also post the detached event. + } + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElement(jpc.canvas, self.container); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint(); + jpc.repaint(); + jpc = null; + delete inPlaceCopy; + delete endpointsByElement[floatingEndpoint.elementId]; + delete floatingEndpoint; + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas) { + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + var drop = function() { + var draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)); + var id = _getAttribute(draggable, "dragId"); + var elId = _getAttribute(draggable, "elId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // todo test that the target is not full. + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + if (!jpc.suspendedEndpoint) { // if a new connection, add it. TODO: move this to a jsPlumb internal method - addConnection or something. doesnt need to be exposed. + _addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + _currentInstance.fireUpdate("jsPlumbConnectionDetached", { + source : idx == 0 ? suspendedElement : jpc.source, target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1] + }); + } + + jsPlumb.repaint(elId); + + _currentInstance.fireUpdate("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + } + + delete floatingConnections[id]; + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute( _getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + }); + + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas)); + + return self; + }; + + /* + * Property: Defaults These are the default settings for jsPlumb, that + * is what will be used if you do not supply specific pieces of + * information to the various API calls. A convenient way to implement + * your own look and feel can be to override these defaults by including + * a script somewhere after the jsPlumb include, but before you make any + * calls to jsPlumb, for instance in this example we set the PaintStyle + * to be a blue line of 27 pixels: > jsPlumb.Defaults.PaintStyle = { + * lineWidth:27, strokeStyle:'blue' } + */ + this.Defaults = { + Anchor : null, + Anchors : [ null, null ], + BackgroundPaintStyle : null, + Connector : null, + Container : null, + DragOptions : {}, + DropOptions : {}, + Endpoint : null, + Endpoints : [ null, null ], + EndpointStyle : { + fillStyle : null + }, + EndpointStyles : [ null, null ], + LabelStyle : { + fillStyle : "rgba(0,0,0,0)", + color : "black" + }, + LogEnabled : true, + MaxConnections : null, + // TODO: should we have OverlayStyle too? + PaintStyle : { + lineWidth : 10, + strokeStyle : 'red' + }, + Scope : "_jsPlumb_DefaultScope" + }; + + this.logEnabled = this.Defaults.LogEnabled; + + /* + * Property: connectorClass The CSS class to set on Connection canvas + * elements. This value is a String and can have multiple classes; the + * entire String is appended as-is. + */ + this.connectorClass = '_jsPlumb_connector'; + + /* + * Property: endpointClass The CSS class to set on Endpoint canvas + * elements. This value is a String and can have multiple classes; the + * entire String is appended as-is. + */ + this.endpointClass = '_jsPlumb_endpoint'; + + /* + * Property: overlayClass The CSS class to set on an Overlay that is an + * HTML element. This value is a String and can have multiple classes; + * the entire String is appended as-is. + */ + this.overlayClass = '_jsPlumb_overlay'; + + /* + * Property: Anchors Default jsPlumb Anchors. These are supplied in the + * file jsPlumb-defaults-x.x.x.js, which is merged in with the main + * jsPlumb script to form .jsPlumb-all-x.x.x.js. You can + * provide your own Anchors by supplying them in a script that is loaded + * after jsPlumb, for instance: > jsPlumb.Anchors.MyAnchor = { + * ....anchor code here. see the documentation. } + */ + this.Anchors = {}; + + /* + * Property: Connectors Default jsPlumb Connectors. These are supplied + * in the file jsPlumb-defaults-x.x.x.js, which is merged in with the + * main jsPlumb script to form .jsPlumb-all-x.x.x.js. You can + * provide your own Connectors by supplying them in a script that is + * loaded after jsPlumb, for instance: > jsPlumb.Connectors.MyConnector = { + * ....connector code here. see the documentation. } + */ + this.Connectors = {}; + + /* + * Property: Endpoints Default jsPlumb Endpoints. These are supplied in + * the file jsPlumb-defaults-x.x.x.js, which is merged in with the main + * jsPlumb script to form .jsPlumb-all-x.x.x.js. You can + * provide your own Endpoints by supplying them in a script that is + * loaded after jsPlumb, for instance: > jsPlumb.Endpoints.MyEndpoint = { + * ....endpoint code here. see the documentation. } + */ + this.Endpoints = {}; + + /* + * Property:Overlays Default jsPlumb Overlays such as Arrows and Labels. + * These are supplied in the file jsPlumb-defaults-x.x.x.js, which is + * merged in with the main jsPlumb script to form + * .jsPlumb-all-x.x.x.js. You can provide your own Overlays by + * supplying them in a script that is loaded after jsPlumb, for + * instance: > jsPlumb.Overlays.MyOverlay = { ....overlay code here. see + * the documentation. } + */ + this.Overlays = {}; + + /* + * Function: addEndpoint Adds an Endpoint to a given element. + * Parameters: target - Element to add the endpoint to. either an + * element id, or a selector representing some element. params - Object + * containing Endpoint options (more info required) Returns: The newly + * created Endpoint. See Also: + */ + this.addEndpoint = function(target, params) { + params = jsPlumb.extend( {}, params); + params.endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + params.endpointStyle = params.endpointStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + var el = _getElementObject(target), id = _getAttribute(el, "id"); + params.source = el; + _updateOffset({ elId : id }); + var e = new Endpoint(params); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + return e; + }; + + /* + * Function: addEndpoint Adds a list of Endpoints to a given element. + * Parameters: target - element to add the endpoint to. either an + * element id, or a selector representing some element. endpoints - List + * of objects containing Endpoint options. one Endpoint is created for + * each entry in this list. Returns: List of newly created Endpoints, + * one for each entry in the 'endpoints' argument. See Also: + * + */ + this.addEndpoints = function(target, endpoints) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + results.push(_currentInstance.addEndpoint(target, endpoints[i])); + } + return results; + }; + + /* + * Function: animate Wrapper around supporting library's animate + * function; injects a call to jsPlumb in the 'step' function (creating + * it if necessary). This only supports the two-arg version of the + * animate call in jQuery - the one that takes an 'options' object as + * the second arg. MooTools has only one method - a two arg one. Which + * is handy. Parameters: el - Element to animate. Either an id, or a + * selector representing the element. properties - The 'properties' + * argument you want passed to the standard jQuery animate call. options - + * The 'options' argument you want passed to the standard jQuery animate + * call. Returns: void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // you know, probably onComplete should repaint too. that will help + // keep up + // with fast animations. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /* + * Function: connect Establishes a connection between two elements. + * Parameters: params - Object containing setup for the connection. see + * documentation. Returns: The newly created Connection. + */ + this.connect = function(params) { + var _p = jsPlumb.extend( {}, params); + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not + // full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log("could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log("could not add connection; target endpoint is full"); + return; + } + + // dynamic anchors + if (_p.dynamicAnchors) { + // these can either be an array of anchor coords, which we will use for both source and target, or an object with {source:[anchors], target:[anchors]}, in which + // case we will use a different set for each element. + var a = _p.dynamicAnchors.constructor == Array; + var sa = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.source)); + var ta = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.target)); + _p.anchors = [sa,ta]; + } + + var jpc = new Connection(_p); + // add to list of connections (by scope). + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + _currentInstance.fireUpdate("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + // force a paint + _draw(jpc.source); + + return jpc; + }; + + /* + Function: deleteEndpoint + + deletes an endpoint and removes all connections it has (which removes the connections from the other endpoints involved too) + + You can call this with either a string uuid, or an Endpoint + + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElement(endpoint.canvas, endpoint.container); + // remove from endpointsbyElement + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + delete endpoint; + } + }; + + /* + * Function: deleteEveryEndpoint + * + * Deletes every Endpoint in this instance + * of jsPlumb. Use this instead of removeEveryEndpoint now. + * + * Returns: void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc) { + _currentInstance.fireUpdate("jsPlumbConnectionDetached", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + }; + + /* + * Function: + * detach Removes a connection. takes either (source, target) (the old way, maintained for backwards compatibility), or a params + * object with various possible values. + * + * + * Parameters: + * source - id or element object of the first element in the connection. + * target - id or element object of the second element in the connection. + * + * OR + * + * params: + * { + * + * Returns: true if successful, false if not. + */ + this.detach = function(source, target) { + if (arguments.length == 2) { + var s = _getElementObject(source), sId = _getId(s); + var t = _getElementObject(target), tId = _getId(t); + _operation(sId, function(jpc) { + if ((jpc.sourceId == sId && jpc.targetId == tId) || (jpc.targetId == sId && jpc.sourceId == tId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + fireDetachEvent(jpc); + } + }); + } + // this is the new version of the method, taking a JS object like + // the connect method does. + else if (arguments.length == 1) { + if (arguments[0].constructor == Connection) { + arguments[0].endpoints[0].detachFrom(arguments[0].endpoints[1]); + fireDetachEvent(arguments[0]); + } + else if (arguments[0].connection) { + arguments[0].connection.endpoints[0].detachFrom(arguments[0].connection.endpoints[1]); + fireDetachEvent(arguments[0].connection); + } + else { + var _p = jsPlumb.extend( {}, source); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1])); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + sourceId = _getId(_p.source); + targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + fireDetachEvent(jpc); + } + }); + } + } + } + }; + + /* + * Function: detachAll Removes all an element's connections. Parameters: + * el - either the id of the element, or a selector for the element. + * Returns: void + */ + this.detachAllConnections = function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + }; + + /** + * @deprecated Use detachAllConnections instead. this will be removed in jsPlumb 1.3. + */ + this.detachAll = this.detachAllConnections; + + /* + * Function: detachEverything Remove all Connections from all elements, + * but leaves Endpoints in place. Returns: void See Also: + * + */ + this.detachEveryConnection = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + /** + * @deprecated use detachEveryConnection instead. this will be removed in jsPlumb 1.3. + */ + this.detachEverything = this.detachEveryConnection; + + /* + * Function: draggable initialises the draggability of some element or + * elements. Parameters: el - either an element id, a list of element + * ids, or a selector. options - options to pass through to the + * underlying library Returns: void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + * Function: extend Wraps the underlying library's extend functionality. + * Parameters: o1 - object to extend o2 - object to extend o1 with + * Returns: o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getConnections Gets all or a subset of connections + * currently managed by this jsPlumb instance. Parameters: options - a + * JS object that holds options defining what sort of connections you're + * looking for. Valid values are: scope this may be a String or a list + * of Strings. jsPlumb will only return Connections whose scope matches + * what this option defines. If you omit it, you will be given + * Connections of every scope. source may be a string or a list of + * strings; constraints results to have only Connections whose source is + * this object. target may be a string or a list of strings; constraints + * results to have only Connections whose target is this object. + * + * The return value is a dictionary in this format: + * { 'scope1': [ {sourceId:'window1', targetId:'window2', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:}, + * {sourceId:'window3', targetId:'window4', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:}, + * {sourceId:'window1', targetId:'window3', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:} ], + * 'scope2': [ {sourceId:'window1', targetId:'window3', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:} ] } + * + */ + this.getConnections = function(options) { + var r = {}; + options = options || {}; + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') + r.push(input); + else + r = input; + } + return r; + }; + var scopes = prepareList(options.scope); + var sources = prepareList(options.source); + var targets = prepareList(options.target); + var filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + r[i] = []; + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + r[i].push({ sourceId : c.sourceId, targetId : c.targetId, source : c.source, target : c.target, sourceEndpoint : c.endpoints[0], targetEndpoint : c.endpoints[1], connection : c }); + } + } + } + return r; + }; + + /* + * Function: getDefaultScope Gets the default scope for connections and + * endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + * Function: getEndpoint gets an Endpoint by UUID Parameters: uuid - the + * UUID for the Endpoint Returns: Endpoint with the given UUID, null if + * nothing found. + */ + this.getEndpoint = _getEndpoint; + + /* + * gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + + /* + * Function: hide Sets an element's connections to be hidden. + * Parameters: el - either the id of the element, or a selector for the + * element. Returns: void + */ + this.hide = function(el) { + _setVisible(el, "none"); + }; + + /* + * Function: makeAnchor Creates an anchor with the given params. + * + * You do not need to use this method. It is exposed because of the way jsPlumb is + * split into three scripts; this will change in the future. + * + * Parameters: x - the x location of the anchor as a fraction of the + * total width. y - the y location of the anchor as a fraction of the + * total height. xOrientation - value indicating the general direction a + * connection from the anchor should go in, in the x direction. + * yOrientation - value indicating the general direction a connection + * from the anchor should go in, in the y direction. xOffset - a fixed + * offset that should be applied in the x direction that should be + * applied after the x position has been figured out. optional. defaults + * to 0. yOffset - a fixed offset that should be applied in the y + * direction that should be applied after the y position has been + * figured out. optional. defaults to 0. + * -- OR -- + * + * params - {x:..., y:..., xOrientation etc } + * -- OR FROM 1.2.4 --- + * + * name - the name of some Anchor in the _currentInstance.Anchors array. + * -- OR FROM 1.2.4 --- + * + * coords - a list of coords for the anchor, like you would pass to + * jsPlumb.makeAnchor (eg [0.5,0.5,0,-1] - an anchor in the center of + * some element, oriented towards the top of the screen) + * -- OR FROM 1.2.4 --- + * + * anchor - an existing anchor. just gets passed back. it's handy + * internally to have this functionality. + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed + // in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards + // compatibility if we are given only one value we assume it's a + // call in the old form. + if (arguments.length == 0) return null; + var params = {}; + if (arguments.length == 1) { + var specimen = arguments[0]; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; + // is it the name of an anchor type? + else if (typeof specimen == "string") return _currentInstance.Anchors[arguments[0]](); + // is it an array of coordinates? + else if (specimen.constructor == Array) { + if (specimen[0].constructor == Array || specimen[0].constructor == String) + return new DynamicAnchor(specimen); + else + return jsPlumb.makeAnchor.apply(this, specimen); + } + // last we try the backwards compatibility stuff. + else if (typeof arguments[0] == "object") jsPlumb.extend(params, x); + } else { + params = { x : x, y : y }; + if (arguments.length >= 4) params.orientation = [ arguments[2], arguments[3] ]; + if (arguments.length == 6) params.offsets = [ arguments[4], arguments[5] ]; + } + var a = new Anchor(params); + a.clone = function() { + return new Anchor(params); + }; + return a; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types) { + var r = []; + for ( var i = 0; i < types.length; i++) + if (typeof types[i] == "string") + r.push(_currentInstance.Anchors[types[i]]()); + else if (types[i].constructor == Array) + r.push(jsPlumb.makeAnchor(types[i])); + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /* + * Function: repaint Repaints an element and its connections. This + * method gets new sizes for the elements before painting anything. + * Parameters: el - either the id of the element or a selector + * representing the element. Returns: void See Also: + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + * Function: repaintEverything Repaints all connections. + * Returns: void + * See Also: + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + * Function: removeAllEndpoints + * Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + * Parameters: el - either an element id, or a selector for an element. + * Returns: void + * See Also: + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + for ( var i in ebe) + _currentInstance.deleteEndpoint(ebe[i]); + endpointsByElement[elId] = []; + }; + + /* + * Function: removeEveryEndpoint + * Removes every Endpoint in this instance + * of jsPlumb. + * + * Returns: void + * + * See Also: + * + * @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /** + * Function: removeEndpoint Removes the given Endpoint from the given + * element. Parameters: el - either an element id, or a selector for an + * element. endpoint - Endpoint to remove. this is an Endpoint object, + * such as would have been returned from a call to addEndpoint. Returns: + * void See Also: + * + * @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + /** + * Function:reset + * removes all endpoints and connections and clears the + * listener list. to keep listeners just call jsPlumb.deleteEveryEndpoint. + */ + this.reset = function() { + this.deleteEveryEndpoint(); + this.clearListeners(); + }; + + /* + * Function: setAutomaticRepaint Sets/unsets automatic repaint on window + * resize. Parameters: value - whether or not to automatically repaint + * when the window is resized. Returns: void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + * Function: setDefaultNewCanvasSize Sets the default size jsPlumb will + * use for a new canvas (we create a square canvas so one value is all + * that is required). This is a hack for IE, because ExplorerCanvas + * seems to need for a canvas to be larger than what you are going to + * draw on it at initialisation time. The default value of this is 1200 + * pixels, which is quite large, but if for some reason you're drawing + * connectors that are bigger, you should adjust this value + * appropriately. Parameters: size - The default size to use. jsPlumb + * will use a square canvas so you need only supply one value. Returns: + * void + */ + this.setDefaultNewCanvasSize = function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }; + + /* + * Function: setDefaultScope Sets the default scope for connections and + * endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable Sets whether or not a given element is + * draggable, regardless of what any plumb command may request. + * Parameters: el - either the id for the element, or a selector + * representing the element. Returns: void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setDraggableByDefault Sets whether or not elements are + * draggable by default. Default for this is true. Parameters: draggable - + * value to set Returns: void + */ + this.setDraggableByDefault = function(draggable) { + _draggableByDefault = draggable; + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction Sets the function to fire when the + * window size has changed and a repaint was fired. Parameters: f - + * Function to execute. Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: show Sets an element's connections to be visible. + * Parameters: el - either the id of the element, or a selector for the + * element. Returns: void + */ + this.show = function(el) { + _setVisible(el, "block"); + }; + + /* + * Function: sizeCanvas Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * Parameters: x - [int] x position for the Canvas origin y - [int] y + * position for the Canvas origin w - [int] width of the canvas h - + * [int] height of the canvas Returns: void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible Toggles visibility of an element's + * connections. Parameters: el - either the element's id, or a selector + * representing the element. Returns: void, but should be updated to + * return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable Toggles draggability (sic) of an element's + * connections. Parameters: el - either the element's id, or a selector + * representing the element. Returns: The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload Unloads jsPlumb, deleting all storage. You should + * call this from an onunload attribute on the element Returns: + * void + */ + this.unload = function() { + delete endpointsByElement; + delete endpointsByUUID; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + }; + + /* + * Function: wrap + * + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + + EventGenerator.apply(this); + this.addListener = this.bind; + + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(); + if (_defaults) jsPlumb.extend(j.Defaults, _defaults); + return j; + }; +})(); diff --git a/archive/1.2.5/jsPlumb-1.2.5-tests.js b/archive/1.2.5/jsPlumb-1.2.5-tests.js new file mode 100644 index 000000000..b5991d6d3 --- /dev/null +++ b/archive/1.2.5/jsPlumb-1.2.5-tests.js @@ -0,0 +1,1109 @@ + +var _getContextNode = function() { + return $("#container"); +}; + +var assertContextExists = function() { + ok(_getContextNode().length == 1, 'context node exists'); +}; + +var assertContextSize = function(elementCount) { + equals(_getContextNode().children().length, elementCount, 'context has ' + elementCount + ' children'); +}; + +var assertContextEmpty = function() { + equals(_getContextNode().children.length, 0, 'context empty'); +}; + +var assertEndpointCount = function(elId, count) { + equals(jsPlumb.getTestHarness().endpointCount(elId), count, elId + " has " + count + (count > 1) ? "endpoints" : "endpoint"); +}; + +var assertConnectionCount = function(endpoint, count) { + equals(endpoint.connections.length, count, "endpoint has " + count + " connections"); +}; + +var assertConnectionByScopeCount = function(scope, count) { + equals(jsPlumb.getTestHarness().connectionCount(scope), count, 'Scope ' + scope + " has " + count + (count > 1) ? "connections" : "connection"); + +}; + +var _divs = []; +var _addDiv = function(id) { + var d1 = document.createElement("div"); + document.body.appendChild(d1); + $(d1).attr("id", id); + _divs.push(id); + return $(d1); +}; + +var _cleanup = function() { + for (var i in _divs) { + $("#" + _divs[i]).remove(); + } + _divs.splice(0, _divs.length - 1); + + jsPlumb.reset(); +}; + +$(document).ready(function() { + +module("jsPlumb", {teardown: _cleanup}); + +// setup the container +var container = document.createElement("div"); +container.id = "container"; +document.body.appendChild(container); +jsPlumb.Defaults.Container = "container"; + +test('findIndex method', function() { + var array = [ 1,2,3, "test", "a string", { 'foo':'bar', 'baz':1 }, { 'ding':'dong' } ]; + equals(jsPlumb.getTestHarness().findIndex(array, 1), 0, "find works for integers"); + equals(jsPlumb.getTestHarness().findIndex(array, "test"), 3, "find works for strings"); + equals(jsPlumb.getTestHarness().findIndex(array, { 'foo':'bar', 'baz':1 }), 5, "find works for objects"); + equals(jsPlumb.getTestHarness().findIndex(array, { 'ding':'dong', 'baz':1 }), -1, "find works properly for objects (objects have different length but some same properties)"); +}); + +test('jsPlumb setup', function() { + ok(jsPlumb, "loaded"); + ok($.fn.plumb, "plumb function"); + ok($.fn.detach, "detach"); + ok($.fn.detachAll, "detachAll"); + ok($.fn.addEndpoint, "addEndpoint"); + ok($.fn.addEndpoints, "addEndpoints"); +}); + +test('getId', function() { + var d10 = _addDiv('d10'); + equals(jsPlumb.getTestHarness().getId(d10), "d10"); +}); + +test('plumb two divs with default options', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + $(d2).plumb({target:"d1"}); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertContextSize(3); // two endpoint canvases and a connection. + assertEndpointCount("d1", 1); + assertEndpointCount("d2", 1); +}); + +test('create a simple endpoint', function() { + var d1 = _addDiv("d1"); + var e = $("#d1").addEndpoint({}); + ok(e, 'endpoint exists'); + assertEndpointCount("d1", 1); +}); + +test('create and remove a simple endpoint', function() { + var d1 = _addDiv("d1"); + var ee = $("#d1").addEndpoint({uuid:"78978597593"}); + ok(ee != null, "endpoint exists"); + assertEndpointCount("d1", 1); + assertContextSize(1); // one Endpoint canvas. + jsPlumb.removeEndpoint(d1, ee); + assertEndpointCount("d1", 0); + var e = jsPlumb.getEndpoint("78978597593"); + equals(e, null, "the endpoint has been deleted"); + assertContextSize(0); // no Endpoint canvases. +}); + +test('draggable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1"); + var e = jsPlumb.addEndpoint(d1, {isSource:true}); + ok(e, 'endpoint exists'); +}); + +test('droppable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1") + var e = jsPlumb.addEndpoint(d1, {isTarget:true}); + ok(e, 'endpoint exists'); +}); + +test('defaultEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = jsPlumb.addEndpoint(d3, {isSource:true}); + ok(e3.anchor, 'endpoint 3 has an anchor'); + var e4 = jsPlumb.addEndpoint(d4, {isSource:true}); + assertEndpointCount("d3", 1); + assertEndpointCount("d4", 1); + assertContextSize(2); // one canvas for each of our two endpoints. + ok(!e3.isFull(), "endpoint 3 is not full."); + jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // we have one connection + jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // should have refused the connection; default max is 1. + assertContextSize(3); // now we have one more canvas, for the Connection that was accepted. +}); + +test('specifiedEndpointMaxConnections', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = jsPlumb.addEndpoint(d5, {isSource:true, maxConnections:3}); + ok(e5.anchor, 'endpoint 5 has an anchor'); + var e6 = jsPlumb.addEndpoint(d6, {isSource:true, maxConnections:2}); // this one has max TWO + assertEndpointCount("d5", 1); assertEndpointCount("d6", 1); + assertContextSize(2); + ok(!e5.isFull(), "endpoint 5 is not full."); + jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 1); // we have one connection + assertContextSize(3); + jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // two connections + assertContextSize(4); + jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // should have refused; max is 2, for d4. + assertContextSize(4); +}); + +test('noEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + var e4 = jsPlumb.addEndpoint(d4, {isSource:true, maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertContextSize(3); + assertConnectionCount(e3, 1); // we have one connection + jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 2); // we have two. etc (default was one. this proves max is working). + assertContextSize(4); + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e4}); + assertConnectionCount(e4, 3); + assertContextSize(6); +}); + +test('anchors equal', function() { + var a1 = jsPlumb.makeAnchor(0, 1, 1, 1); + var a2 = jsPlumb.makeAnchor(0, 1, 1, 1); + ok(a1.equals(a2), "anchors are the same"); +}); + +test('anchors equal with offsets', function() { + var a1 = jsPlumb.makeAnchor(0, 1, 1, 1, 10, 13); + var a2 = jsPlumb.makeAnchor(0, 1, 1, 1, 10, 13); + ok(a1.equals(a2), "anchors are the same"); +}); + +test('anchors not equal', function() { + var a1 = jsPlumb.makeAnchor(0, 1, 0, 1); + var a2 = jsPlumb.makeAnchor(0, 1, 1, 1); + ok(!a1.equals(a2), "anchors are different"); +}); + +test('anchor not equal with offsets', function() { + var a1 = jsPlumb.makeAnchor(0, 1, 1, 1, 10, 13); + var a2 = jsPlumb.makeAnchor(0, 1, 1, 1); + ok(!a1.equals(a2), "anchors are different"); +}); + +test('detach plays nice when no target given', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + jsPlumb.connect({source:d3, target:d4}); + jsPlumb.detach(); +}); + +//TODO make sure you run this test with a single detach call, to ensure that +// single detach calls result in the connection being removed. detachEverything can +// just blow away the connectionsByScope array and recreate it. +test('jsPlumb.getConnections (simple case, default scope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + jsPlumb.connect({source:d5, target:d6}); + assertContextSize(3); + var c = jsPlumb.getConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 1, "there is one connection"); +}); + +test('jsPlumb.getConnections (simple case, default scope; detach by element id)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + jsPlumb.connect({source:d5, target:d6}); + jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 2, "there are two connections initially"); + jsPlumb.detach('d5', 'd6'); + c = jsPlumb.getConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); +}); + +test('jsPlumb.getConnections (simple case, default scope; detach by element id using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + jsPlumb.connect({source:d5, target:d6}); + jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 2, "there are two connections initially"); + jsPlumb.detach({source:'d5', target:'d6'}); + c = jsPlumb.getConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); +}); + +test('jsPlumb.getConnections (simple case, default scope; detach by element object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + jsPlumb.connect({source:d5, target:d6}); + jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 2, "there are two connections initially"); + jsPlumb.detach(d5, d6); + c = jsPlumb.getConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); +}); + +test('jsPlumb.getConnections (simple case, default scope; detach by element object using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + jsPlumb.connect({source:d5, target:d6}); + jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 2, "there are two connections initially"); + jsPlumb.detach({source:d5, target:d6}); + c = jsPlumb.getConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); +}); + +test('jsPlumb.getConnections (simple case, default scope; detach by Connection)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + var c56 = jsPlumb.connect({source:d5, target:d6}); + var c67 = jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 2, "there are two connections initially"); + jsPlumb.detach(c56); + assertContextSize(5); // check that the connection canvas was removed. + c = jsPlumb.getConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 1, "after detaching one, there is now one connection."); + +}); + +test('jsPlumb.getConnections (scope testScope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + jsPlumb.connect({source:d5, target:d6, scope:'testScope'}); + var c = jsPlumb.getConnections(); // will get all connections + equals(c['testScope'].length, 1, "there is one connection"); + equals(c['testScope'][0].sourceId, 'd5', "the connection's source is d5"); + equals(c['testScope'][0].targetId, 'd6', "the connection's source is d6"); +}); + +test('jsPlumb.getConnections (filtered by scope)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + jsPlumb.connect({source:d9, target:d10}); // default scope + var c = jsPlumb.getConnections({scope:'testScope'}); // will get all connections + equals(c[jsPlumb.getDefaultScope()], null); + equals(c['testScope'].length, 1); + // now supply a list of scopes + c = jsPlumb.getConnections({scope:[jsPlumb.getDefaultScope(),'testScope']}); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 1); + equals(c['testScope'].length, 1, "there is one connection in 'testScope'"); +}); + +test('jsPlumb.getConnections (filtered by scope and sourceId)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + jsPlumb.connect({source:d9, target:d8, scope:'testScope'}); + jsPlumb.connect({source:d9, target:d10}); // default scope + var c = jsPlumb.getConnections({scope:'testScope', source:'d8'}); // will get all connections with sourceId 'd8' + equals(c[jsPlumb.getDefaultScope()], null); + equals(c['testScope'].length, 1, "there is one connection in 'testScope' from d8"); +}); + +test('jsPlumb.getConnections (filtered by scope, source id and target id)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d12, target:d13, scope:'testScope'}); + jsPlumb.connect({source:d11, target:d13, scope:'testScope'}); + var c = jsPlumb.getConnections({scope:'testScope', source:'d11', target:'d13'}); + equals(c['testScope'].length, 1, "there is one connection from d11 to d13"); +}); + +test('jsPlumb.getConnections (filtered by a list of scopes)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = jsPlumb.getConnections({scope:['testScope','testScope3']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); +}); + +test('jsPlumb.getConnections (filtered by a list of scopes and source ids)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); +}); + +test('jsPlumb.getConnections (filtered by a list of scopes, source ids and target ids)', function() { + jsPlumb.detachEverything(); + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'), d14=_addDiv("d14"), d15=_addDiv("d15"); + jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d11, target:d14, scope:'testScope'}); + jsPlumb.connect({source:d11, target:d15, scope:'testScope'}); + jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + assertContextSize(18); + var c = jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11'], target:['d14', 'd15']}); + equals(c['testScope'].length, 2, 'there are two connections in testScope'); + equals(c['testScope3'].length, 0, 'there is no connections in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + var anEntry = c['testScope'][0]; + ok(anEntry.sourceEndpoint != null, "Source endpoint is set"); + ok(anEntry.targetEndpoint != null, "Target endpoint is set"); + equals(anEntry.source.attr("id"), "d11", "Source is div d11"); + equals(anEntry.target.attr("id"), "d14", "Target is div d14"); +}); + +test('connection event listeners', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null/*, returnedParams2 = null*/; + jsPlumb.addListener(["jsPlumbConnection"], { + // signature of the 'interface' method is jsPlumbConnection. params + // has source, target, sourceId, targetId, sourceEndpoint, targetEndpoint + jsPlumbConnection : function(params) { + returnedParams = $.extend({}, params); + } + }); + jsPlumb.connect({source:d1, target:d2}); + ok(returnedParams != null, "new connection listener event was fired"); + equals(returnedParams.sourceId, "d1", 'sourceid is set'); + equals(returnedParams.targetId, "d2", 'targetid is set'); + equals(returnedParams.source.attr("id"), "d1", 'source is set'); + equals(returnedParams.target.attr("id"), "d2" , 'target is set'); + ok(returnedParams.sourceEndpoint != null, "source endpoint is not null"); + ok(returnedParams.targetEndpoint != null, "target endpoint is not null"); + //jsPlumb.detachAll("d1"); + //ok(returnedParams2 != null, "removed connection listener event was fired"); +}); + +test('detach event listeners (detach by connection)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + jsPlumb.addListener(["jsPlumbConnectionDetached"], { + jsPlumbConnectionDetached : function(params) { + returnedParams = $.extend({}, params); + } + }); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); +}); + +test('detach event listeners (detach by element ids)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + jsPlumb.addListener(["jsPlumbConnectionDetached"], { + jsPlumbConnectionDetached : function(params) { + returnedParams = $.extend({}, params); + } + }); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach("d1","d2"); + ok(returnedParams != null, "removed connection listener event was fired"); +}); + +test('detach event listeners (detach by elements)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + jsPlumb.addListener(["jsPlumbConnectionDetached"], { + jsPlumbConnectionDetached : function(params) { + returnedParams = $.extend({}, params); + } + }); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach(d1,d2); + ok(returnedParams != null, "removed connection listener event was fired"); +}); + +test('detach event listeners (via Endpoint.detach method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + jsPlumb.addListener(["jsPlumbConnectionDetached"], { + jsPlumbConnectionDetached : function(params) { + returnedParams = $.extend({}, params); + } + }); + var conn = jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); +}); + +test('detach event listeners (via Endpoint.detachFrom method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + jsPlumb.addListener(["jsPlumbConnectionDetached"], { + jsPlumbConnectionDetached : function(params) { + returnedParams = $.extend({}, params); + } + }); + jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachFrom(e2); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); +}); + +test('detach event listeners (via Endpoint.detachAll method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + jsPlumb.addListener(["jsPlumbConnectionDetached"], { + jsPlumbConnectionDetached : function(params) { + returnedParams = $.extend({}, params); + } + }); + jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachAll(); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); +}); + +test('detach event listeners (via jsPlumb.deleteEndpoint method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + jsPlumb.addListener(["jsPlumbConnectionDetached"], { + jsPlumbConnectionDetached : function(params) { + returnedParams = $.extend({}, params); + } + }); + jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + jsPlumb.deleteEndpoint(e1); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(1); +}); + +test('detach event listeners (ensure cleared by jsPlumb.reset)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + jsPlumb.addListener(["jsPlumbConnectionDetached"], { + jsPlumbConnectionDetached : function(params) { + returnedParams = $.extend({}, params); + } + }); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach(d1,d2); + ok(returnedParams != null, "removed connection listener event was fired"); + returnedParams = null; + + jsPlumb.reset(); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach(d1,d2); + ok(returnedParams == null, "connection listener was cleared by jsPlumb.reset()"); +}); + +test('connection events that throw errors', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null, returnedParams2 = null; + jsPlumb.addListener(["jsPlumbConnection"], { + // signature of the 'interface' method is jsPlumbConnection. params + // has source, target, sourceId, targetId, sourceEndpoint, targetEndpoint + jsPlumbConnection : function(params) { + returnedParams = $.extend({}, params); + throw "oh no!"; + } + }); + jsPlumb.connect({source:d1, target:d2}); + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + jsPlumb.connect({source:d3, target:d4}); + ok(returnedParams != null, "new connection listener event was fired; we threw an error, jsPlumb survived."); +}); + +test("Endpoint.detachFrom", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + e16.detachFrom(e17); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); +}); + +test("Endpoint.detachFromConnection", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + e16.detachFromConnection(conn); + assertContextSize(3); // all canvases should remain; the connection was not removed. + // but endpoint e16 should have no connections now. + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 1); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); +}); + +test("Endpoint.detachAll", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); d18 = _addDiv("d18"); + var e16 = $("#d16").addEndpoint({isSource:true,maxConnections:-1}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = $("#d17").addEndpoint({isSource:true}); + var e18 = $("#d18").addEndpoint({isSource:true}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e18}); + assertConnectionCount(e16, 2); + assertConnectionCount(e17, 1); + assertConnectionCount(e18, 1); + assertContextSize(5); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 2); + e16.detachAll(); + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); +}); + +test("Endpoint.detach", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + e16.detach(conn); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); +}); + +test("Endpoint.isConnectedTo", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.isConnectedTo(e17), true, "e16 and e17 are connected"); +}); + +test("setting endpoint uuid", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); d18 = _addDiv("d18"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + equals(e16.getUuid(), uuid, "endpoint's uuid was set correctly"); +}); + +test("jsPlumb.getEndpoint (by uuid)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); +}); + +test("jsPlumb.deleteEndpoint (by uuid, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + jsPlumb.deleteEndpoint(uuid); + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + var ebe = jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + assertContextSize(0); +}); + +test("jsPlumb.deleteEndpoint (by uuid, connections too)", function() { + // create two endpoints (one with a uuid), add them to two divs and then connect them. + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + // check that the connection was ok. + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + // delete the endpoint that has a uuid. verify that the endpoint cannot be retrieved and that the connection has been removed, but that + // element d17 still has its Endpoint. + jsPlumb.deleteEndpoint(uuid); + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + var ebe = jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + ebe = jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 1, "element d17 still has its Endpoint"); + assertContextSize(1); + + // now delete d17's endpoint and check that it has gone. + jsPlumb.deleteEndpoint(e17); + f = jsPlumb.getEndpoint(e17); + equals(f, null, "endpoint has been deleted"); + ebe = jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 0, "element d17 no longer has any Endpoints"); + assertContextSize(0); +}); + +test("jsPlumb.deleteEndpoint (by reference, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + jsPlumb.deleteEndpoint(e16); + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + assertContextSize(0); +}); + +test("jsPlumb.deleteEndpoint (by reference, connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + jsPlumb.deleteEndpoint(e16); + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + assertContextSize(1); +}); + +test("jsPlumb.deleteEveryEndpoint", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + assertContextSize(2); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + jsPlumb.deleteEveryEndpoint(); + + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); +}); + +test("jsPlumb.deleteEveryEndpoint (connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertContextSize(3); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + jsPlumb.deleteEveryEndpoint(); + + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); // no canvases. so all connection canvases have been cleaned up too. + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); +}); + +test('jsPlumb.connect (between two Endpoints)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1); + assertEndpointCount("d2", 1); + jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1); // no new endpoint should have been added + assertEndpointCount("d2", 1); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. +}); + +test('jsPlumb.connect (by endpoint)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); +}); + +test("jsPlumb.connect (two Endpoints - that have been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1, uuid:srcEndpointUuid}); + var e2 = jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1, uuid:dstEndpointUuid}); + jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ] }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertContextSize(3); +}); + +test("jsPlumb.connect (two Endpoints - that have not been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ], source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + var e1 = jsPlumb.getEndpoint(srcEndpointUuid); + ok(e1 != null, "endpoint with src uuid added"); + ok(e1.canvas != null); + var e2 = jsPlumb.getEndpoint(dstEndpointUuid); + ok(e2 != null, "endpoint with target uuid added"); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); +}); + +test("jsPlumb.connect (two Endpoints - that have been already added - by endpoint reference)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1}); + var e2 = jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1}); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); +}); + +test("jsPlumb.connect (two elements)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + jsPlumb.connect({ source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); +}); + +test("jsPlumb.connect (Connector test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:new jsPlumb.Connectors.Straight() }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors.Straight, "Straight connector chosen for connection"); +}); + +test("jsPlumb.connect (Connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors.Straight, "Straight connector chosen for connection"); +}); + +test("jsPlumb.connect (Endpoint test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoint:new jsPlumb.Endpoints.Rectangle() }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints.Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints.Rectangle, "Rectangle endpoint chosen for connection target"); +}); + +test("jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints.Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints.Rectangle, "Rectangle endpoint chosen for connection target"); +}); + +test("jsPlumb.connect (Endpoints test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoints:[new jsPlumb.Endpoints.Rectangle(),new jsPlumb.Endpoints.Dot()] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints.Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints.Dot, "Dot endpoint chosen for connection target"); +}); + +test("jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints.Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints.Dot, "Dot endpoint chosen for connection target"); +}); + +test("jsPlumb.connect (by Endpoints, connector test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {}); + var e17 = jsPlumb.addEndpoint(d17, {}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:new jsPlumb.Connectors.Straight() }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors.Straight, "Straight connector chosen for connection"); +}); + +test("jsPlumb.connect (by Endpoints, connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {}); + var e17 = jsPlumb.addEndpoint(d17, {}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors.Straight, "Straight connector chosen for connection"); +}); + +test("jsPlumb.connect (by Endpoints, anchors as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = "TopCenter", a17 = "BottomCenter"; + var e16 = jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors.Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, 0.5, "endpoint 16 is at top center");equals(e16.anchor.y, 0, "endpoint 16 is at top center"); + equals(e17.anchor.x, 0.5, "endpoint 17 is at bottom center");equals(e17.anchor.y, 1, "endpoint 17 is at bottom center"); +}); + +test("jsPlumb.connect (by Endpoints, endpoints create anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = [0,0.5,0,-1], a17 = [1,0.0,-1,-1]; + var e16 = jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors.Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, a16[0]);equals(e16.anchor.y, a16[1]); + equals(e17.anchor.x, a17[0]);equals(e17.anchor.y, a17[1]); + equals(e16.anchor.getOrientation()[0], a16[2]); equals(e16.anchor.getOrientation()[1], a16[3]); + equals(e17.anchor.getOrientation()[0], a17[2]); equals(e17.anchor.getOrientation()[1], a17[3]); +}); + +test("jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchor')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {anchor:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = jsPlumb.addEndpoint(d17, {anchor:["TopCenter", "BottomCenter"]}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors.Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); +}); + +test("jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchors')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors.Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); +}); + + +// this test is for the original detach function; it should stay working after i mess with it +// a little. +test("jsPlumb.detach (by element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + var e3 = jsPlumb.addEndpoint(d1); + var e4 = jsPlumb.addEndpoint(d2); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + jsPlumb.connect({ sourceEndpoint:e3, targetEndpoint:e4 }); // make two connections to be sure this works ;) + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionCount(e3, 1); + assertConnectionCount(e4, 1); + + jsPlumb.detach("d1", "d2"); + + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionCount(e3, 0); + assertConnectionCount(e4, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); +}); + +// detach is being made to operate more like connect - by taking one argument with a whole +// bunch of possible params in it. if two args are passed in it will continue working +// in the old way. +test("jsPlumb.detach (params object, using element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + jsPlumb.detach({source:"d1", target:"d2"}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); +}); + +test("jsPlumb.detach (params object, using element objects)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + jsPlumb.detach({source:d1, target:d2}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + assertContextSize(2); +}); + +test("jsPlumb.detach (source and target as endpoint UUIDs)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {uuid:"abcdefg"}); + ok(jsPlumb.getEndpoint("abcdefg") != null, "e1 exists"); + var e2 = jsPlumb.addEndpoint(d2, {uuid:"hijklmn"}); + ok(jsPlumb.getEndpoint("hijklmn") != null, "e2 exists"); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + jsPlumb.detach({uuids:["abcdefg", "hijklmn"]}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + assertContextSize(2); +}); + +test("jsPlumb.detach (sourceEndpoint and targetEndpoint supplied)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + jsPlumb.detach({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + assertContextSize(2); +}); + + +test("jsPlumb.connect (connect by element, default endpoint, supplied dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var anchors = [ [0.25, 0, 0, -1], [1, 0.25, 1, 0], [0.75, 1, 0, 1], [0, 0.75, -1, 0] ]; + jsPlumb.connect({source:d1, target:d2, dynamicAnchors:anchors}); // auto connect with default endpoint and provided anchors + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + assertContextSize(2); +}); + +test("jsPlumb.connect (connect by element, supplied endpoint and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = jsPlumb.addEndpoint(d1, endpoint); + var e2 = jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + assertContextSize(3); + jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + assertContextSize(2); +}); + + +test("jsPlumb.connect (testing for connection event callback)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var connectCallback = null, detachCallback = null; + jsPlumb.bind(["jsPlumbConnection", "jsPlumbConnectionDetached"], { + jsPlumbConnection : function(params) { + connectCallback = $.extend({}, params); + }, + jsPlumbConnectionDetached : function(params) { + detachCallback = $.extend({}, params); + } + }); + jsPlumb.connect({source:d1, target:d2}); // auto connect with default endpoint and anchor set + ok(connectCallback != null, "connect callback was made"); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + jsPlumb.detach({source:d1, target:d2}); + assertContextSize(2); + ok(detachCallback != null, "detach callback was made"); +}); + +test("jsPlumb.makeDynamicAnchors (longhand)", function() { + var anchors = [jsPlumb.makeAnchor(0.2, 0, 0, -1), jsPlumb.makeAnchor(1, 0.2, 1, 0), + jsPlumb.makeAnchor(0.8, 1, 0, 1), jsPlumb.makeAnchor(0, 0.8, -1, 0) ]; + var dynamicAnchor = jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); +}); + +test("jsPlumb.makeDynamicAnchors (shorthand)", function() { + var anchors = [[0.2, 0, 0, -1], [1, 0.2, 1, 0], + [0.8, 1, 0, 1], [0, 0.8, -1, 0] ]; + var dynamicAnchor = jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); +}); + +/** + * leave this test at the bottom! + */ +test('unload test', function() { + jsPlumb.unload(); + var contextNode = $("._jsPlumb_context"); + ok(contextNode.length == 0, 'context node unloaded'); +}); + + +}); \ No newline at end of file diff --git a/archive/1.2.5/jsPlumb-defaults-1.2.5-RC1.js b/archive/1.2.5/jsPlumb-defaults-1.2.5-RC1.js new file mode 100644 index 000000000..2b787432e --- /dev/null +++ b/archive/1.2.5/jsPlumb-defaults-1.2.5-RC1.js @@ -0,0 +1,767 @@ +/* +* jsPlumb-defaults-1.2.4-RC1 +* +* This script contains the default Anchors, Endpoints, Connectors and Overlays for jsPlumb. It should be used with jsPlumb 1.1.0 and above; +* prior to version 1.1.0 of jsPlumb the defaults were included inside the main script. +* +* NOTE: for production usage you should use jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, +* in a minified file. +* +* Dual licensed under MIT and GPL2. +*/ + +(function() { + + var ie = !!!document.createElement('canvas').getContext; + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * getOrientation : function() { return [ox, oy]; } + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + var _curryAnchor = function(x,y,ox,oy) { + return function() { + return jsPlumb.makeAnchor(x,y,ox,oy); + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1); + + + jsPlumb.Defaults.DynamicAnchors = function() { + return jsPlumb.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"]); + }; + jsPlumb.Anchors["AutoDefault"] = function() { return jsPlumb.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors()); }; + + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + var currentPoints = null; + var _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + + _dx = _tx - _sx, _dy = (_ty - _sy); + _m = _dy / _dx, _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + + return currentPoints; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + var xp = _sx + (location * _dx); + var yp = _m == Infinity ? xp + _b : (_m * xp) + _b; + return {x:xp, y:yp}; + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { return _m; }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + var orientation = distance > 0 ? 1 : -1; + var y = Math.abs(distance * Math.sin(_theta)); + if (_sy > _ty) y = y * -1; + var x = Math.abs(distance * Math.cos(_theta)); + if (_sx > _tx) x = x * -1; + return {x:p.x + (orientation * x), y:p.y + (orientation * y)}; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = self.gradientAtPoint(p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + /** + * This Connector draws a Bezier curve with two control points. + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + jsPlumb.Connectors.Bezier = function(curviness) { + var self = this; + this.majorAnchor = curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _sx, _sy, _canvasX, _canvasY, _w, _h; + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) + { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx); var minx2 = Math.min(_CP[0], _CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(_sx,_tx); var maxx2 = Math.max(_CP[0], _CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty); var miny2 = Math.min(_CP[1], _CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(_sy,_ty); var maxy2 = Math.max(_CP[1], _CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + this.paint = function(d, ctx) { + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the distance the given point is from the curve. not enabled for 1.2.3. didnt make the cut. next time. + */ + this.distanceFrom = function(point) { + var curve = [ {x:currentPoints[4], y:currentPoints[5]}, + {x:currentPoints[8], y:currentPoints[9]}, + {x:currentPoints[10], y:currentPoints[11]}, + {x:currentPoints[6], y:currentPoints[7]}]; + return (jsBezier.distanceFromCurve(point, curve)); + }; + + this.nearestPointTo = function(point) { + var curve = [ {x:currentPoints[4], y:currentPoints[5]}, + {x:currentPoints[8], y:currentPoints[9]}, + {x:currentPoints[10], y:currentPoints[11]}, + {x:currentPoints[6], y:currentPoints[7]}]; + return (jsBezier.nearestPointOnCurve(point, curve)); + + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + return jsBezier.perpendicularToCurveAt(_makeCurve(), location, length, distance); + }; + + this.createGradient = function(dim, ctx, swap) { + return (swap) ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + + /** + * Types of endpoint UIs. we supply four - a circle of default radius 10px, a rectangle of + * default size 20x20, an image (with no default), and a Triangle, of default size 15. + * you can supply others of these if you want to - see the documentation for a howto. + */ + + /** + * a round endpoint, with default radius 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + } + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + if (endpointStyle.gradient && !ie) { + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * A Rectangular endpoint, with default size 20x20. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + jsPlumb.Endpoints.Triangle = function(params) { + + params = params || { width:15, height:15 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - width/2; + var y = anchorPoint[1] - height/2; + + jsPlumb.sizeCanvas(canvas, x, y, width, height); + + var ctx = canvas.getContext('2d'); + var offsetX = 0, offsetY = 0, angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = endpointStyle.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' or, since 1.2.4, a 'src' property in the params object.. + */ + jsPlumb.Endpoints.Image = function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.src || params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + }; + + /** + * An arrow overlay. you can provide: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + */ + jsPlumb.Overlays.Arrow = function(params) { + params = params || {}; + var self = this; + var length = params.length || 20; + var width = params.width || 20; + var fillStyle = params.fillStyle || "black"; + var strokeStyle = params.strokeStyle; + var lineWidth = params.lineWidth || 1; + this.loc = params.location || 0.5; + // how far along the arrow the lines folding back in come to. default is 62.3%. + var foldback = params.foldback || 0.623; + var _getFoldBackPoint = function(connector, loc) { + if (foldback == 0.5) return connector.pointOnPath(loc); + else { + var adj = 0.5 - foldback; // we calculate relative to the center + return connector.pointAlongPathFrom(loc, length * adj); + } + }; + + this.computeMaxSize = function() { return width * 1.5; } + + this.draw = function(connector, ctx) { + // this is the arrow head position + var hxy = connector.pointAlongPathFrom(self.loc, length / 2); + // this is the center of the tail + var txy = connector.pointAlongPathFrom(self.loc, -length / 2), tx = txy.x, ty = txy.y; + // this is the tail vector + var tail = connector.perpendicularToPathAt(self.loc, width, -length / 2); + // this is the point the tail goes in to + var cxy = _getFoldBackPoint(connector, self.loc); + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(hxy.x, hxy.y); + ctx.lineTo(tail[0].x, tail[0].y); + ctx.lineTo(cxy.x, cxy.y); + ctx.lineTo(tail[1].x, tail[1].y); + ctx.lineTo(hxy.x, hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + + /** + * a basic arrow. this is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. See Arrow for params. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + /** + * a diamond. like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. See Arrow for params. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40; + var p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + /** + * A Label overlay. Params you can provide: + * + * labelStyle - js object containing style instructions for the label. defaults to jsPlumb.Defaults.LabelStyle. + * label - the label to paint. may be a string or a function that returns a string. nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * borderWidth - width of a border to paint. defaults to zero. + * borderStyle - strokeStyle to use when painting the border, if necessary. + */ + jsPlumb.Overlays.Label = function(params) { + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + var self = this; + var labelWidth = null, labelHeight = null, labelText = null, labelPadding = null; + this.location = params.location || 0.5; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var _textDimensions = function(ctx) { + if (self.cachedDimensions) return self.cachedDimensions; // return cached copy if we can. if we add a setLabel function remember to clear the cache. + labelText = typeof self.label == 'function' ? self.label(self) : self.label; + var d = {}; + if (labelText) { + var lines = labelText.split(/\n|\r\n/); + ctx.save(); + if (self.labelStyle.font) ctx.font = self.labelStyle.font; + var t = _widestLine(lines, ctx); + // a fake text height measurement: use the width of upper case M + var h = ctx.measureText("M").width; + labelPadding = self.labelStyle.padding || 0.25; + labelWidth = t + (2 * t * labelPadding); + labelHeight = (lines.length * h) + (2 * h * labelPadding); + var textHeight = lines.length * h; + ctx.restore(); + d = {width:labelWidth, height:labelHeight, lines:lines, oneLine:h, padding:labelPadding, textHeight:textHeight}; + } + if (typeof self.label != 'function') self.cachedDimensions = d; // cache it if we can. + return d; + }; + this.computeMaxSize = function(connector, ctx) { + var td = _textDimensions(ctx); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + var _widestLine = function(lines, ctx) { + var max = 0; + for (var i = 0; i < lines.length; i++) { + var t = ctx.measureText(lines[i]).width; + if (t > max) max = t; + } + return max; + }; + + this.draw = function(connector, ctx) { + var td = _textDimensions(ctx); + if (td.width) { + var cxy = connector.pointOnPath(self.location); + if (self.labelStyle.font) ctx.font = self.labelStyle.font; + if (self.labelStyle.fillStyle) + ctx.fillStyle = self.labelStyle.fillStyle; + else + ctx.fillStyle = "rgba(0,0,0,0)"; + ctx.fillRect(cxy.x - (td.width / 2), cxy.y - (td.height / 2) , td.width , td.height ); + + if (self.labelStyle.color) ctx.fillStyle = self.labelStyle.color; + ctx.textBaseline = "middle"; + ctx.textAlign = "center"; + for (i = 0; i < td.lines.length; i++) { + ctx.fillText(td.lines[i],cxy.x, cxy.y - (td.textHeight / 2) + (td.oneLine/2) + (i*td.oneLine)); + } + + // border + if (self.labelStyle.borderWidth > 0) { + ctx.strokeStyle = self.labelStyle.borderStyle || "black"; + ctx.strokeRect(cxy.x - (td.width / 2), cxy.y - (td.height / 2) , td.width , td.height ); + } + } + }; + }; + + /** + * an image overlay. params may contain: + * + * location : proportion along the connector to draw the image. optional. + * src : image src. required. + * events : map of event names to functions; each event listener will be bound to the img. + */ + jsPlumb.Overlays.Image = function(params) { + var self = this; + this.location = params.location || 0.5; + this.img = new Image(); + var imgDiv = null; + var notReadyInterval = null; + var notReadyConnector, notReadyContext; + var events = params.events || {}; + var _init = function() { + if (self.ready) { + window.clearInterval(notReadyInterval); + imgDiv = document.createElement("img"); + imgDiv.src = self.img.src; + imgDiv.style.position = "absolute"; + imgDiv.style.display="none"; + imgDiv.className = "_jsPlumb_overlay"; + document.body.appendChild(imgDiv);// HMM + // attach events + for (var e in events) { + jsPlumb.CurrentLibrary.bind(imgDiv, e, events[e]); + } + if (notReadyConnector && notReadyContext) { + _draw(notReadyConnector, notReadyContext); + notReadyContext = null; + notReadyConnector = null; + } + } + }; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.src || params.url; + + notReadyInterval = window.setInterval(_init, 250); + + this.computeMaxSize = function(connector, ctx) { + return [self.img.width, self.img.height] + }; + + var _draw = function(connector, ctx) { + var cxy = connector.pointOnPath(self.location); + var canvas = jsPlumb.CurrentLibrary.getElementObject(ctx.canvas); + var canvasOffset = jsPlumb.CurrentLibrary.getOffset(canvas); + var o = {left:canvasOffset.left + cxy.x - (self.img.width/2), top:canvasOffset.top + cxy.y - (self.img.height/2)}; + jsPlumb.CurrentLibrary.setOffset(imgDiv, o); + imgDiv.style.display = "block"; + }; + + this.draw = function(connector, ctx) { + if (self.ready) + _draw(connector, ctx); + else { + notReadyConnector = connector; + notReadyContext = ctx; + } + }; + }; +})(); \ No newline at end of file diff --git a/archive/1.2.5/mootools.jsPlumb-1.2.5-RC1.js b/archive/1.2.5/mootools.jsPlumb-1.2.5-RC1.js new file mode 100644 index 000000000..fae10216f --- /dev/null +++ b/archive/1.2.5/mootools.jsPlumb-1.2.5-RC1.js @@ -0,0 +1,305 @@ +/* + * mootools.jsPlumb 1.2.5-RC1 + * + * MooTools specific functionality for jsPlumb. + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + * NOTE: for production usage you should use mootools.jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, + * in a minified file. + * + * Dual licensed under MIT and GPL2. + * + */ + +(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function() { + this.parent(); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}; + var _droppableOptions = {}; + var _draggablesByScope = {}; + var _draggablesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }; + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + var _getElementObject = function(el) { + return $(el); + }; + + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + m.start(properties); + }, + + appendElement : function(child, parent) { + _getElementObject(parent).grab(child); + }, + + bind : function(el, event, callback) { + el = _getElementObject(el); + el.addEvent(event, callback); + }, + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + }, + + getElementObject : _getElementObject, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + getScrollLeft : function(el) { + return null; + }, + + getScrollTop : function(el) { + return null; + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[0]; + return { left: ui.offsetLeft, top: ui.offsetTop }; + }, + + initDraggable : function(el, options) { + var id = jsPlumb.getId(el); + var drag = _draggablesById[id]; + if (!drag) { + var originalZIndex = 0, originalCursor = null; + var dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + options['onStart'] = jsPlumb.wrap(options['onStart'], function() + { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() + { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + }); + + // DROPPABLES: + var scope = options['scope'] || jsPlumb.Defaults.Scope; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop'); + } + }; + + drag = new Drag.Move(el, options); + _add(_draggablesByScope, scope, drag); + _add(_draggablesById, el.get("id"), drag); + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + initDroppable : function(el, options) { + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _add(_droppables, scope, el); + var id = jsPlumb.getId(el); + _droppableOptions[id] = options; + var filterFunc = function(entry) { return entry.element != el; }; + var draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + isAlreadyDraggable : function(el) { + return _draggablesById[jsPlumb.getId(el)] != null; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + return (typeof Drag != undefined && typeof Drag.Move != undefined); + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + _getElementObject(element).dispose(); // ?? + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + setOffset : function(el, o) { + _getElementObject(el).setPosition({x:o.left, y:o.top}); + } + }; +})(); diff --git a/archive/1.2.5/mootools.jsPlumb-1.2.5-all-min.js b/archive/1.2.5/mootools.jsPlumb-1.2.5-all-min.js new file mode 100644 index 000000000..afe33fa3b --- /dev/null +++ b/archive/1.2.5/mootools.jsPlumb-1.2.5-all-min.js @@ -0,0 +1 @@ +(function(){var a=function(){var ac=function(){var ah={};this.bind=function(ak,al){var ai=function(an,am){B(ah,an,am)};if(ak.constructor==Array){for(var aj=0;aj=0){delete (ah[ai]);ah.splice(ai,1);return true}}}return false};var c=function(ai,ah){return s(ai,function(aj,ak){S[ak]=ah;if(b.CurrentLibrary.isDragSupported(aj)){b.CurrentLibrary.setDraggable(aj,ah)}})};var aa=function(ah,ai){D(q(ah,"id"),function(aj){aj.canvas.style.display=ai})};var w=function(ah){return s(ah,function(aj,ai){var ak=S[ai]==null?I:S[ai];ak=!ak;S[ai]=ak;b.CurrentLibrary.setDraggable(aj,ak);return ak})};var e=function(ah){D(ah,function(aj){var ai=("none"==aj.canvas.style.display);aj.canvas.style.display=ai?"block":"none"})};var m=function(am){var ak=am.timestamp,ah=am.recalc,al=am.offset,ai=am.elId;if(!ah){if(ak&&ak===k[ai]){return}}if(ah||al==null){var aj=U(ai);y[ai]=h(aj);F[ai]=d(aj);k[ai]=ak}else{F[ai]=al}};var Z=function(aj,ah,ai){aj=aj||function(){};ah=ah||function(){};return function(){var ak=null;try{ak=ah.apply(this,arguments)}catch(al){n("jsPlumb function failed : "+al)}if(ai==null||(ak!==ai)){try{aj.apply(this,arguments)}catch(al){n("wrapped function failed : "+al)}}return ak}};var M=function(al){var aj=this;this.x=al.x||0;this.y=al.y||0;var ai=al.orientation||[0,0];var ak=null,ah=null;this.offsets=al.offsets||[0,0];aj.timestamp=null;this.compute=function(ar){var ax=ar.xy,an=ar.wh,at=ar.element,au=ar.timestamp;if(au&&au===aj.timestamp){return ah}ah=[ax[0]+(aj.x*an[0])+aj.offsets[0],ax[1]+(aj.y*an[1])+aj.offsets[1]];var ao=at?at.container:null;var av={left:0,top:0};if(ao!=null){var am=U(ao);var ap=d(am);var aq=b.CurrentLibrary.getScrollLeft(am);var aw=b.CurrentLibrary.getScrollTop(am);av.left=ap.left-aq;av.top=ap.top-aw;ah[0]=ah[0]-av.left;ah[1]=ah[1]-av.top}aj.timestamp=au;return ah};this.getOrientation=function(){return ai};this.equals=function(am){if(!am){return false}var an=am.getOrientation();var ap=this.getOrientation();return this.x==am.x&&this.y==am.y&&this.offsets[0]==am.offsets[0]&&this.offsets[1]==am.offsets[1]&&ap[0]==an[0]&&ap[1]==an[1]};this.getCurrentLocation=function(){return ah}};var r=function(an){var al=an.reference;var am=an.referenceCanvas;var aj=h(U(am));var ai=0,ao=0;var ah=null;var ak=null;this.compute=function(au){var ar=au.xy,aq=au.element;var ap=[ar[0]+(aj[0]/2),ar[1]+(aj[1]/2)];if(aq.container!=null){var at=d(aq.container);ap[0]=ap[0]-at.left;ap[1]=ap[1]-at.top}ak=ap;return ap};this.getOrientation=function(){if(ah){return ah}else{var ap=al.getOrientation();return[Math.abs(ap[0])*ai*-1,Math.abs(ap[1])*ao*-1]}};this.over=function(ap){ah=ap.getOrientation()};this.out=function(){ah=null};this.getCurrentLocation=function(){return ak}};var K=function(aj,ai){this.isSelective=true;this.isDynamic=true;var aq=aj||[];var ao=function(ar){return ar.constructor==M?ar:b.makeAnchor(ar)};for(var an=0;an0?aq[0]:null;var am=aq.length>0?0:-1;this.locked=false;var ap=this;var al=function(au,ar,aA,az,at){var aw=az[0]+(au.x*at[0]),av=az[1]+(au.y*at[1]);return Math.sqrt(Math.pow(ar-aw,2)+Math.pow(aA-av,2))};var ah=ai||function(aC,at,au,av,ar){var ax=au[0]+(av[0]/2),aw=au[1]+(av[1]/2);var az=-1,aB=Infinity;for(var ay=0;ayax){ax=aF}}var aK=this.connector.compute(az,aM,this.endpoints[aQ].anchor,this.endpoints[aw].anchor,this.paintStyle.lineWidth,ax);b.sizeCanvas(aj,aK[0],aK[1],aK[2],aK[3]);var aD=function(aR,aU){aR.save();b.extend(aR,aU);if(aU.gradient&&!R){var aT=at.connector.createGradient(aK,aR,(aE==this.sourceId));for(var aS=0;aS=0){au.connections.splice(aH,1);if(!aK){var aJ=aI.endpoints[0]==au?aI.endpoints[1]:aI.endpoints[0];aJ.detach(aI,true)}O(aI.canvas,aI.container);v(u,aI.scope,aI);if(!aK){N(aI)}}};this.detachAll=function(){while(au.connections.length>0){au.detach(au.connections[0])}};this.detachFrom=function(aI){var aJ=[];for(var aH=0;aH=0){au.connections.splice(aH,1)}};this.getElement=function(){return at};this.getUuid=function(){return ap};this.makeInPlaceCopy=function(){var aH=new ab({anchor:au.anchor,source:at,style:an,endpoint:ar});return aH};this.isConnectedTo=function(aJ){var aI=false;if(aJ){for(var aH=0;aH=aA)};this.setDragAllowedWhenFull=function(aH){au.dragAllowedWhenFull=aH};this.equals=function(aH){return this.anchor.equals(aH.anchor)};this.paint=function(aK){aK=aK||{};var aO=aK.timestamp;if(!aO||au.timestamp!==aO){var aN=aK.anchorPoint,aJ=aK.canvas,aL=aK.connectorPaintStyle;if(aN==null){var aT=aK.offset||F[am];var aH=aK.dimensions||y[am];if(aT==null||aH==null){m({elId:am,timestamp:aO});aT=F[am];aH=y[am]}var aI={xy:[aT.left,aT.top],wh:aH,element:au,timestamp:aO};if(au.anchor.isDynamic){if(au.connections.length>0){var aQ=au.connections[0];var aS=aQ.endpoints[0]==au?1:0;var aM=aS==0?aQ.sourceId:aQ.targetId;var aP=F[aM],aR=y[aM];aI.txy=[aP.left,aP.top];aI.twh=aR;aI.tElement=aQ.endpoints[aS]}}aN=au.anchor.compute(aI)}ar.paint(aN,au.anchor.getOrientation(),aJ||au.canvas,an,aL||an);au.timestamp=aO}};this.removeConnection=this.detach;if(aG.isSource&&b.CurrentLibrary.isDragSupported(at)){var az=null,av=null,ay=null,ah=false,ai=null;var ak=function(){ay=aD();if(au.isFull()&&!au.dragAllowedWhenFull){return false}m({elId:am});aj=au.makeInPlaceCopy();aj.paint();az=document.createElement("div");var aJ=U(az);A(az,au.container);var aK=ag(aJ);m({elId:aK});Q(U(au.canvas),"dragId",aK);Q(U(au.canvas),"elId",am);var aI=new r({reference:au.anchor,referenceCanvas:au.canvas});aE=new ab({style:{fillStyle:"rgba(0,0,0,0)"},endpoint:ar,anchor:aI,source:aJ});if(ay==null){au.anchor.locked=true;ay=new p({sourceEndpoint:au,targetEndpoint:aE,source:U(at),target:U(az),anchors:[au.anchor,aI],paintStyle:aG.connectorStyle,connector:aG.connector,overlays:au.connectorOverlays})}else{ah=true;al(U(aj.canvas));var aH=ay.sourceId==am?0:1;ay.floatingAnchorIndex=aH;au.detachFromConnection(ay);if(aH==0){ai=[ay.source,ay.sourceId];ay.source=U(az);ay.sourceId=aK}else{ai=[ay.target,ay.targetId];ay.target=U(az);ay.targetId=aK}ay.endpoints[aH==0?1:0].anchor.locked=true;ay.suspendedEndpoint=ay.endpoints[aH];ay.endpoints[aH]=aE}J[aK]=ay;aE.addConnection(ay);B(T,aK,aE)};var aB=aG.dragOptions||{};var aw=b.extend({},b.CurrentLibrary.defaultDragOptions);aB=b.extend(aw,aB);aB.scope=aB.scope||au.scope;var ax=b.CurrentLibrary.dragEvents.start;var aF=b.CurrentLibrary.dragEvents.stop;var ao=b.CurrentLibrary.dragEvents.drag;aB[ax]=Z(aB[ax],ak);aB[ao]=Z(aB[ao],function(){var aH=b.CurrentLibrary.getUIPosition(arguments);b.CurrentLibrary.setOffset(az,aH);ae(U(az),aH)});aB[aF]=Z(aB[aF],function(){v(T,av,aE);z([az,aE.canvas],at);O(aj.canvas,at);var aH=ay.floatingAnchorIndex==null?1:ay.floatingAnchorIndex;ay.endpoints[aH==0?1:0].anchor.locked=false;if(ay.endpoints[aH]==aE){if(ah&&ay.suspendedEndpoint){if(aH==0){ay.source=ai[0];ay.sourceId=ai[1]}else{ay.target=ai[0];ay.targetId=ai[1]}ay.endpoints[aH]=ay.suspendedEndpoint;if(aq){ay.floatingAnchorIndex=null;ay.suspendedEndpoint.addConnection(ay);b.repaint(ai[1])}else{ay.endpoints[aH==0?1:0].detach(ay)}}else{O(ay.canvas,au.container);au.detachFromConnection(ay)}}au.anchor.locked=false;au.paint();ay.repaint();ay=null;delete aj;delete T[aE.elementId];delete aE});var aC=U(au.canvas);b.CurrentLibrary.initDraggable(aC,aB)}var al=function(aK){if(aG.isTarget&&b.CurrentLibrary.isDropSupported(at)){var aH=aG.dropOptions||l.Defaults.DropOptions||b.Defaults.DropOptions;aH=b.extend({},aH);aH.scope=aH.scope||au.scope;var aN=null;var aL=b.CurrentLibrary.dragEvents.drop;var aM=b.CurrentLibrary.dragEvents.over;var aI=b.CurrentLibrary.dragEvents.out;var aJ=function(){var aP=U(b.CurrentLibrary.getDragObject(arguments));var aV=q(aP,"dragId");var aR=q(aP,"elId");var aT=J[aV];var aO=aT.floatingAnchorIndex==null?1:aT.floatingAnchorIndex,aS=aO==0?1:0;if(!au.isFull()&&!(aO==0&&!au.isSource)&&!(aO==1&&!au.isTarget)){if(aO==0){aT.source=at;aT.sourceId=am}else{aT.target=at;aT.targetId=am}aT.endpoints[aO].detachFromConnection(aT);if(aT.suspendedEndpoint){aT.suspendedEndpoint.detachFromConnection(aT)}aT.endpoints[aO]=au;au.addConnection(aT);if(!aT.suspendedEndpoint){B(u,aT.scope,aT);x(at,aG.draggable,{})}else{var aU=aT.suspendedEndpoint.getElement(),aQ=aT.suspendedEndpoint.elementId;l.fireUpdate("jsPlumbConnectionDetached",{source:aO==0?aU:aT.source,target:aO==1?aU:aT.target,sourceId:aO==0?aQ:aT.sourceId,targetId:aO==1?aQ:aT.targetId,sourceEndpoint:aO==0?aT.suspendedEndpoint:aT.endpoints[0],targetEndpoint:aO==1?aT.suspendedEndpoint:aT.endpoints[1]})}b.repaint(aR);l.fireUpdate("jsPlumbConnection",{source:aT.source,target:aT.target,sourceId:aT.sourceId,targetId:aT.targetId,sourceEndpoint:aT.endpoints[0],targetEndpoint:aT.endpoints[1]})}delete J[aV]};aH[aL]=Z(aH[aL],aJ);aH[aM]=Z(aH[aM],function(){var aP=b.CurrentLibrary.getDragObject(arguments);var aR=q(U(aP),"dragId");var aQ=J[aR];var aO=aQ.floatingAnchorIndex==null?1:aQ.floatingAnchorIndex;aQ.endpoints[aO].anchor.over(au.anchor)});aH[aI]=Z(aH[aI],function(){var aP=b.CurrentLibrary.getDragObject(arguments);var aR=q(U(aP),"dragId");var aQ=J[aR];var aO=aQ.floatingAnchorIndex==null?1:aQ.floatingAnchorIndex;aQ.endpoints[aO].anchor.out()});b.CurrentLibrary.initDroppable(aK,aH)}};al(U(au.canvas));return au};this.Defaults={Anchor:null,Anchors:[null,null],BackgroundPaintStyle:null,Connector:null,Container:null,DragOptions:{},DropOptions:{},Endpoint:null,Endpoints:[null,null],EndpointStyle:{fillStyle:null},EndpointStyles:[null,null],LabelStyle:{fillStyle:"rgba(0,0,0,0)",color:"black"},LogEnabled:true,MaxConnections:null,PaintStyle:{lineWidth:10,strokeStyle:"red"},Scope:"_jsPlumb_DefaultScope"};this.logEnabled=this.Defaults.LogEnabled;this.connectorClass="_jsPlumb_connector";this.endpointClass="_jsPlumb_endpoint";this.overlayClass="_jsPlumb_overlay";this.Anchors={};this.Connectors={};this.Endpoints={};this.Overlays={};this.addEndpoint=function(am,an){an=b.extend({},an);an.endpoint=an.endpoint||l.Defaults.Endpoint||b.Defaults.Endpoint;an.endpointStyle=an.endpointStyle||l.Defaults.EndpointStyle||b.Defaults.EndpointStyle;var ak=U(am),ao=q(ak,"id");an.source=ak;m({elId:ao});var al=new ab(an);B(T,ao,al);var ah=F[ao],aj=y[ao];var ai=al.anchor.compute({xy:[ah.left,ah.top],wh:aj,element:al});al.paint({anchorLoc:ai});return al};this.addEndpoints=function(ak,ah){var aj=[];for(var ai=0;ai0?P(at,ar)!=-1:true};for(var al in u){if(aj(ao,al)){ah[al]=[];for(var ak=0;ak=4){ai.orientation=[arguments[2],arguments[3]]}if(arguments.length==6){ai.offsets=[arguments[4],arguments[5]]}}var an=new M(ai);an.clone=function(){return new M(ai)};return an};this.makeAnchors=function(ai){var aj=[];for(var ah=0;ah0?1:-1;var v=Math.abs(w*Math.sin(f));if(g>c){v=v*-1}var r=Math.abs(w*Math.cos(f));if(h>d){r=r*-1}return{x:u.x+(t*r),y:u.y+(t*v)}};this.perpendicularToPathAt=function(u,v,A){var w=q.pointAlongPathFrom(u,A);var t=q.gradientAtPoint(w.location);var s=Math.atan(-1/t);var z=v/2*Math.sin(s);var r=v/2*Math.cos(s);return[{x:w.x+r,y:w.y+z},{x:w.x-r,y:w.y-z}]};this.createGradient=function(s,r){return r.createLinearGradient(s[4],s[5],s[6],s[7])}};jsPlumb.Connectors.Bezier=function(c){var q=this;this.majorAnchor=c||150;this.minorAnchor=10;var k=null;this._findControlPoint=function(A,r,v,y,s){var x=y.getOrientation(),z=s.getOrientation();var u=x[0]!=z[0]||x[1]==z[1];var t=[];var B=q.majorAnchor,w=q.minorAnchor;if(!u){if(x[0]==0){t.push(r[0]n){n=w}if(z<0){f+=z;var A=Math.abs(z);n+=A;p[0]+=A;l+=A;d+=A;o[0]+=A}var I=Math.min(g,_ty);var G=Math.min(p[1],o[1]);var v=Math.min(I,G);var B=Math.max(g,_ty);var y=Math.max(p[1],o[1]);var t=Math.max(B,y);if(t>h){h=t}if(v<0){e+=v;var x=Math.abs(v);h+=x;p[1]+=x;g+=x;_ty+=x;o[1]+=x}if(F&&nm){m=p}}return m};this.draw=function(n,m){var p=h(m);if(p.width){var o=n.pointOnPath(d.location);if(d.labelStyle.font){m.font=d.labelStyle.font}if(d.labelStyle.fillStyle){m.fillStyle=d.labelStyle.fillStyle}else{m.fillStyle="rgba(0,0,0,0)"}m.fillRect(o.x-(p.width/2),o.y-(p.height/2),p.width,p.height);if(d.labelStyle.color){m.fillStyle=d.labelStyle.color}m.textBaseline="middle";m.textAlign="center";for(i=0;i0){m.strokeStyle=d.labelStyle.borderStyle||"black";m.strokeRect(o.x-(p.width/2),o.y-(p.height/2),p.width,p.height)}}}};jsPlumb.Overlays.Image=function(e){var l=this;this.location=e.location||0.5;this.img=new Image();var m=null;var f=null;var d,c;var k=e.events||{};var h=function(){if(l.ready){window.clearInterval(f);m=document.createElement("img");m.src=l.img.src;m.style.position="absolute";m.style.display="none";m.className="_jsPlumb_overlay";document.body.appendChild(m);for(var n in k){jsPlumb.CurrentLibrary.bind(m,n,k[n])}if(d&&c){g(d,c);c=null;d=null}}};this.img.onload=function(){l.ready=true};this.img.src=e.src||e.url;f=window.setInterval(h,250);this.computeMaxSize=function(o,n){return[l.img.width,l.img.height]};var g=function(q,p){var s=q.pointOnPath(l.location);var r=jsPlumb.CurrentLibrary.getElementObject(p.canvas);var n=jsPlumb.CurrentLibrary.getOffset(r);var t={left:n.left+s.x-(l.img.width/2),top:n.top+s.y-(l.img.height/2)};jsPlumb.CurrentLibrary.setOffset(m,t);m.style.display="block"};this.draw=function(o,n){if(l.ready){g(o,n)}else{d=o;c=n}}}})();(function(){var d=new Class({Extends:Fx.Morph,onStep:null,initialize:function(m,l){this.parent(m,l);if(l.onStep){this.onStep=l.onStep}},step:function(){this.parent();if(this.onStep){try{this.onStep()}catch(l){}}}});var b={};var h={};var g={};var k={};var e=function(m,o,n){if(o){var p=o.get("id");if(p){var l=h[p];if(l){if(l[n]){l[n](m,o)}}}}};var f=function(m,o){if(m){var n=m.get("id");if(n){var l=h[n];if(l){if(l.hoverClass){if(o){m.addClass(l.hoverClass)}else{m.removeClass(l.hoverClass)}}}}}};var c=function(p,n,o){var m=p[n];if(!m){m=[];p[n]=m}m.push(o)};var a=function(l){return $(l)};jsPlumb.CurrentLibrary={addClass:function(m,l){m.addClass(l)},animate:function(p,o,n){var l=new d(p,n);l.start(o)},appendElement:function(m,l){a(l).grab(m)},bind:function(l,m,n){l=a(l);l.addEvent(m,n)},dragEvents:{start:"onStart",stop:"onComplete",drag:"onDrag",step:"onStep",over:"onEnter",out:"onLeave",drop:"onDrop",complete:"onComplete"},extend:function(m,l){return $extend(m,l)},getAttribute:function(l,m){return l.get(m)},getDragObject:function(l){return l[0]},getElementObject:a,getOffset:function(l){var m=l.getPosition();return{left:m.x,top:m.y}},getScrollLeft:function(l){return null},getScrollTop:function(l){return null},getSize:function(m){var l=m.getSize();return[l.x,l.y]},getUIPosition:function(l){var m=l[0];return{left:m.offsetLeft,top:m.offsetTop}},initDraggable:function(m,u){var l=jsPlumb.getId(m);var p=k[l];if(!p){var n=0,o=null;var r=jsPlumb.Defaults.DragOptions.zIndex||2000;u.onStart=jsPlumb.wrap(u.onStart,function(){n=this.element.getStyle("z-index");this.element.setStyle("z-index",r);if(jsPlumb.Defaults.DragOptions.cursor){o=this.element.getStyle("cursor");this.element.setStyle("cursor",jsPlumb.Defaults.DragOptions.cursor)}});u.onComplete=jsPlumb.wrap(u.onComplete,function(){this.element.setStyle("z-index",n);if(o){this.element.setStyle("cursor",o)}});var t=u.scope||jsPlumb.Defaults.Scope;var q=function(v){return v.get("id")!=m.get("id")};var s=b[t]?b[t].filter(q):[];u.droppables=s;u.onLeave=jsPlumb.wrap(u.onLeave,function(v,w){if(w){f(w,false);e(v,w,"onLeave")}});u.onEnter=jsPlumb.wrap(u.onEnter,function(v,w){if(w){f(w,true);e(v,w,"onEnter")}});u.onDrop=function(v,w){if(w){f(w,false);e(v,w,"onDrop")}};p=new Drag.Move(m,u);c(g,t,p);c(k,m.get("id"),p);if(u.disabled){p.detach()}}return p},initDroppable:function(p,m){var o=m.scope||jsPlumb.Defaults.Scope;c(b,o,p);var r=jsPlumb.getId(p);h[r]=m;var q=function(s){return s.element!=p};var l=g[o]?g[o].filter(q):[];for(var n=0;n0?1:-1}}var h={subtract:function(x,w){return{x:x.x-w.x,y:x.y-w.y}},dotProduct:function(x,w){return(x.x*w.x)+(x.y*w.y)},square:function(w){return Math.sqrt((w.x*w.x)+(w.y*w.y))},scale:function(w,x){return{x:w.x*x,y:w.y*x}}};var o=64,r=Math.pow(2,-o-1),d=3,a=5;var p=function(E,x){var B=new Array(a);var D=e(E,x);var A=q(D,a,B,0);var F=h.subtract(E,x[0]),C=h.square(F),G=0;for(var z=0;z=o){H[0]=(F[0].x+F[a].x)/2;return 1}if(n(F,y)){H[0]=f(F,y);return 1}break}t(F,y,0.5,C,D);B=q(C,y,G,z+1);E=q(D,y,x,z+1);for(A=0;AJ){J=I}else{if(I0?1:-1,E=null;while(z endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + var endpointsByElement = {}; + var endpointsByUUID = {}; + var connectionsByScope = {}; + var offsets = {}; + var offsetTimestamps = {}; + var floatingConnections = {}; + var draggableStates = {}; + var _draggableByDefault = true; + var sizes = []; + var listeners = {}; // a map: keys are event types, values are lists of listeners. + var DEFAULT_SCOPE = 'DEFAULT'; + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + var _findIndex = function(a, v, b, s) { + var _eq = function(o1, o2) { + if (o1 === o2) + return true; + else if (typeof o1 == 'object' && typeof o2 == 'object') { + var same = true; + for ( var propertyName in o1) { + if (!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for ( var propertyName in o2) { + if (!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for ( var i = +b || 0, l = a.length; i < l; i++) { + if (_eq(a[i], v)) + return i; + } + return -1; + }; + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }; + + /** + * appends an element to the given parent, or the document body if no + * parent given. + */ + var _appendElement = function(canvas, parent) { + if (!parent) + document.body.appendChild(canvas); + else + jsPlumb.CurrentLibrary.appendElement(canvas, parent); + }; + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + var _timestamp = function() { return "" + (new Date()).getTime(); }; + + /** + * Draws an endpoint and its connections. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + var _draw = function(element, ui, timestamp) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (!timestamp) timestamp = _timestamp(); + if (endpoints) { + _updateOffset( { elId : id, offset : ui, recalc : false, timestamp : timestamp }); // timestamp is checked against last update cache; it is + // valid for one paint cycle. + var myOffset = offsets[id], myWH = sizes[id]; + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + var l = endpoints[i].connections; + for ( var j = 0; j < l.length; j++) { + l[j].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); // ...paint each connection. + // then, check for dynamic endpoint; need to repaint it. + var oIdx = l[j].endpoints[0] == endpoints[i] ? 1 : 0; + if (l[j].endpoints[oIdx].anchor.isDynamic && !l[j].endpoints[oIdx].isFloating()) { + var oId = oIdx == 0 ? l[j].sourceId : l[j].targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + // TODO i still want to make this faster. + var anchorLoc = l[j].endpoints[oIdx].anchor.compute( { + xy : [ oOffset.left, oOffset.top ], + wh : oWH, + element : l[j].endpoints[oIdx], + txy : [ myOffset.left, myOffset.top ], + twh : myWH, + tElement : endpoints[i] + }); + l[j].endpoints[oIdx].paint({ anchorLoc : anchorLoc }); + } + } + } + } + }; + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + var _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }; + + /** + * + */ + var _log = function(msg) { + if (_currentInstance.logEnabled && typeof console != "undefined") + console.log(msg); + }; + + var _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }; + var _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }; + var _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }; + var _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }; + var _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }; + var _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }; + var _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }; + + /** + * gets an id for the given element, creating and setting one if + * necessary. + */ + var _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else + id = "jsPlumb_" + _timestamp(); + _setAttribute(ele, "id", id); + } + return id; + }; + + /** + * gets an Endpoint by uuid. + */ + var _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }; + + /** + * inits a draggable if it's not already initialised. + */ + var _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element) && !jsPlumb.CurrentLibrary.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + } + } + }; + + /** + * helper to create a canvas. + * + * @param clazz + * optional class name for the canvas. + */ + var _newCanvas = function(clazz, parent, uuid) { + var canvas = document.createElement("canvas"); + _appendElement(canvas, parent); + canvas.style.position = "absolute"; + if (clazz) canvas.className = clazz; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + _getId(canvas, uuid); + if (ie) { + // for IE we have to set a big canvas size. actually you can + // override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas + // you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + var _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }; + /** + * perform an operation on all elements. + */ + var _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }; + /** + * helper to remove an element from the DOM. + */ + var _removeElement = function(element, parent) { + if (element != null) { + if (!parent) { + try { + document.body.removeChild(element); + } catch (e) { + } + } else { + jsPlumb.CurrentLibrary.removeElement(element, parent); + } + } + }; + /** + * helper to remove a list of elements from the DOM. + */ + var _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }; + /** + * helper method to remove an item from a list. + */ + var _removeFromList = function(map, key, value) { + if (key != null) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete (l[i]); + l.splice(i, 1); + return true; + } + } + } + return false; + }; + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }; + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + var _setVisible = function(el, state) { + _operation(_getAttribute(el, "id"), function(jpc) { + jpc.canvas.style.display = state; + }); + }; + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + var _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }; + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + var _toggleVisible = function(elId) { + _operation(elId, function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }; + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we use the + * offset, outerWidth and outerHeight methods to get the current values. + */ + var _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return; + } + if (recalc || offset == null) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } else { + offsets[elId] = offset; + } + }; + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + var _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log('jsPlumb function failed : ' + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log('wrapped function failed : ' + e); + } + } + return r; + }; + }; + + /* + * Class: Anchor Models a position relative to the origin of some + * element that an Endpoint can be located. + */ + /* + * Function: Anchor + * + * Constructor for the Anchor class + * + * Parameters: + * - x : the x location of the anchor as a fraction of the total width. - + * y : the y location of the anchor as a fraction of the total height. - + * orientation : an [x,y] array indicating the general direction a + * connection from the anchor should go in. for more info on this, see + * the documentation, or the docs in jquery-jsPlumb-defaults-XXX.js for + * the default Anchors. - offsets : an [x,y] array of fixed offsets that + * should be applied after the x,y position has been figured out. may be + * null. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + if (timestamp && timestamp === self.timestamp) { + return lastReturnValue; + } + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + var container = element ? element.container : null; + var containerAdjustment = { left : 0, top : 0 }; + if (container != null) { + var eo = _getElementObject(container); + var o = _getOffset(eo); + var sl = jsPlumb.CurrentLibrary.getScrollLeft(eo); + var st = jsPlumb.CurrentLibrary.getScrollTop(eo); + containerAdjustment.left = o.left - sl; + containerAdjustment.top = o.top - st; + lastReturnValue[0] = lastReturnValue[0] - containerAdjustment.left; + lastReturnValue[1] = lastReturnValue[1] - containerAdjustment.top; + } + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. + * + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(_getElementObject(refCanvas)); + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + var orientation = null; + var _lastResult = null; + + this.compute = function(params) { + var xy = params.xy, el = params.element; + var result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + if (el.container != null) { + var o = _getOffset(el.container); + result[0] = result[0] - o.left; + result[1] = result[1] - o.top; + } + _lastResult = result; + return result; + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { orientation = anchor.getOrientation(); }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * Class: DynamicAnchor + * + * An Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements - a useful + * feature for some applications. + * + * the second argument - anchorSelector - is optional, but when provided should be a function + * that + */ + var DynamicAnchor = function(anchors, anchorSelector) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = anchors || []; + var _convert = function(anchor) { return anchor.constructor == Anchor ? anchor: jsPlumb.makeAnchor(anchor); }; + for (var i = 0; i < _anchors.length; i++) _anchors[i] = _convert(_anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null; + var _curIndex = _anchors.length > 0 ? 0 : -1; + this.locked = false; + var self = this; + + // helper method to calculate the distance between the centers of the two elements. + var _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }; + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + var _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + + var pos = _curAnchor.compute(params); + return pos; + }; + + this.getCurrentLocation = function() { + var cl = _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + return cl; + }; + + this.getOrientation = function() { return _curAnchor != null ? _curAnchor.getOrientation() : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + * Class:Connection + * + * The connecting line between two Endpoints. + * + */ + var Connection = function(params) { + + EventGenerator.apply(this); + // ************** get the source and target and register the + // connection. ******************* + var self = this; + var id = new String('_jsplumb_c_' + (new Date()).getTime()); + this.getId = function() { return id; }; + this.container = params.container || _currentInstance.Defaults.Container; // may be null; we will append to the body if so. + // get source and target as jQuery objects + this.source = _getElementObject(params.source); + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. + if (params.sourceEndpoint) this.source = params.sourceEndpoint.getElement(); + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + this.sourceId = _getAttribute(this.source, "id"); + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + this.scope = params.scope; // scope may have been passed in to the connect call. + // if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. init endpoints + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams) { + if (anchorParams) + return jsPlumb.makeAnchor(anchorParams); + }; + var prepareEndpoint = function(existing, index, params, element) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint + || new jsPlumb.Endpoints.Dot(); + if (ep.constructor == String) ep = new jsPlumb.Endpoints[ep](); + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + var a = params.anchors ? params.anchors[index] : _makeAnchor(_currentInstance.Defaults.Anchors[index]) || _makeAnchor(jsPlumb.Defaults.Anchors[index]) || _makeAnchor(_currentInstance.Defaults.Anchor) || _makeAnchor(jsPlumb.Defaults.Anchor) || _makeAnchor("BottomCenter"); + var u = params.uuids ? params.uuids[index] : null; + var e = new Endpoint( { style : es, endpoint : ep, connections : [ self ], uuid : u, anchor : a, source : element, container : self.container }); + self.endpoints[index] = e; + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || _currentInstance.Defaults.Connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + if (this.connector.constructor == String) this.connector = new jsPlumb.Connectors[this.connector](); // lets you use a string as shorthand. + this.paintStyle = this.endpoints[0].connectorStyle || this.endpoints[1].connectorStyle || params.paintStyle || _currentInstance.Defaults.PaintStyle || jsPlumb.Defaults.PaintStyle; + this.backgroundPaintStyle = this.endpoints[0].connectorBackgroundStyle || this.endpoints[1].connectorBackgroundStyle || params.backgroundPaintStyle || _currentInstance.Defaults.BackgroundPaintStyle || jsPlumb.Defaults.BackgroundPaintStyle; + + // init overlays + this.overlays = params.overlays || []; + this.addOverlay = function(overlay) { overlays.push(overlay); }; + + // this is a shortcut helper method to let people add a label as + // overlay. + this.labelStyle = params.labelStyle || _currentInstance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + if (this.label) { + this.overlays.push(new jsPlumb.Overlays.Label( { + labelStyle : this.labelStyle, + label : this.label + })); + } + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + // functions for mouse hover/select functionality + this.distanceFrom = function(point) { return self.connector.distanceFrom(point); }; + + this.setLabel = function(l) { + self.label = l; + _currentInstance.repaint(self.source); + }; + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var otherOffset = offsets[this.targetId]; + otherWH = sizes[this.targetId]; + // todo: why not fold this and the paint call that follows into one + // call? + var anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1] + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0] + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc }); + + var canvas = _newCanvas(jsPlumb.connectorClass, self.container); + this.canvas = canvas; + + /* + * Function: paint + * + * paints the connection. + * + * Parameters: + * elId Id of the element that is in motion + * ui current library's event system ui object (present if we came from a drag to get here) + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp; + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the + // two references. + var swap = false; + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + if (this.canvas.getContext) { + _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }); + _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + // var myOffset = offsets[sId], otherOffset = offsets[tId], myWH = sizes[sId], otherWH = sizes[tId]; + var ctx = canvas.getContext('2d'); + var sAnchorP = this.endpoints[sIdx].anchor.getCurrentLocation(); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.getCurrentLocation(); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + + // paint overlays + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + var s = o.computeMaxSize(self.connector, ctx); + if (s > maxSize) + maxSize = s; + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, this.paintStyle.lineWidth, maxSize); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + + var _paintOneStyle = function(ctx, paintStyle) { + ctx.save(); + jsPlumb.extend(ctx, paintStyle); + if (paintStyle.gradient && !ie) { + var g = self.connector.createGradient(dim, ctx, (elId == this.sourceId)); + for ( var i = 0; i < paintStyle.gradient.stops.length; i++) + g.addColorStop(paintStyle.gradient.stops[i][0], paintStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + self.connector.paint(dim, ctx); + ctx.restore(); + }; + + // first check for the background style + if (this.backgroundPaintStyle != null) { + _paintOneStyle(ctx, this.backgroundPaintStyle); + } + _paintOneStyle(ctx, this.paintStyle); + + // paint overlays + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + o.draw(self.connector, ctx); + } + } + }; + + // TODO: should this take a timestamp? probably. it reduces the + // amount of time + // spent figuring out anchor locations. + this.repaint = function() { + this.paint({ elId : this.sourceId, recalc : true }); + }; + + _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide + // whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + }; + + /* + * Class: Endpoint Models an endpoint. Can have one to N connections + * emanating from it (although how to handle that in the UI is a very + * good question). also has a Canvas and paint style. + */ + + /* + * Function: Endpoint This is the Endpoint class constructor. + * Parameters: anchor - anchor for the endpoint, of type jsPlumb.Anchor. + * may be null. endpoint - endpoint object, of type jsPlumb.Endpoint. + * may be null. style - endpoint style, a js object. may be null. source - + * element the endpoint is attached to, of type jquery object. Required. + * canvas - canvas element to use. may be, and most often is, null. + * connections - optional list of connections to configure the endpoint + * with. isSource - boolean. indicates the endpoint can act as a source + * of new connections. optional. dragOptions - if isSource is set to + * true, you can supply arguments for the jquery draggable method. + * optional. connectionStyle - if isSource is set to true, this is the + * paint style for connections from this endpoint. optional. connector - + * optional connector type to use. isTarget - boolean. indicates the + * endpoint can act as a target of new connections. optional. + * dropOptions - if isTarget is set to true, you can supply arguments + * for the jquery droppable method. optional. reattach - optional + * boolean that determines whether or not the connections reattach after + * they have been dragged off an endpoint and left floating. defaults to + * false: connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + params = params || {}; + var self = this; + var id = new String('_jsplumb_e_' + (new Date()).getTime()); + this.getId = function() { return id; }; + if (params.dynamicAnchors) + self.anchor = new DynamicAnchor(jsPlumb.makeAnchors(params.dynamicAnchors)); + else + self.anchor = params.anchor ? jsPlumb.makeAnchor(params.anchor) : params.anchors ? jsPlumb.makeAnchor(params.anchors) : jsPlumb.makeAnchor("TopCenter"); + var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot(); + if (_endpoint.constructor == String) _endpoint = new jsPlumb.Endpoints[_endpoint](); + self.endpoint = _endpoint; + var _style = params.style || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + this.connectorStyle = params.connectorStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + var _element = params.source, _uuid = params.uuid; + var floatingEndpoint = null, inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + this.container = params.container || _currentInstance.Defaults.Container || jsPlumb.Defaults.Container; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of. + this.canvas = params.canvas || _newCanvas(jsPlumb.endpointClass, this.container, params.uuid); + this.connections = params.connections || []; + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + var _reattach = params.reattach || false; + this.dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + this.addConnection = function(connection) { + self.connections.push(connection); + }; + this.detach = function(connection, ignoreTarget) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + // this avoids a circular loop + if (!ignoreTarget) { + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + t.detach(connection, true); + } + _removeElement(connection.canvas, connection.container); + _removeFromList(connectionsByScope, connection.scope, connection); + if(!ignoreTarget) fireDetachEvent(connection); + } + }; + + /** + * detaches all connections this Endpoint has + */ + this.detachAll = function() { + while (self.connections.length > 0) { + self.detach(self.connections[0]); + } + }; + + /** + * removes any connections from this Endpoint that are connected to + * the given target endpoint. + * + * @param targetEndpoint + */ + this.detachFrom = function(targetEndpoint) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + self.detach(c[i]); + } + }; + + /** + * detach this Endpoint from the Connection, but leave the Connection alive. used when dragging. + */ + this.detachFromConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /** + * returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /** + * returns the UUID for this Endpoint, if there is one. + */ + this.getUuid = function() { + return _uuid; + }; + + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + var e = new Endpoint( { anchor : self.anchor, source : _element, style : _style, endpoint : _endpoint }); + return e; + }; + + /** + * returns whether or not this endpoint is connected to the given + * endpoint. + * + * @param endpoint + * Endpoint to test. + * @since 1.1.1 + * + * todo: needs testing. will this work if the endpoint passed in is + * the source? + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + * + * @returns {Boolean} + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + var connectorSelector = function() { + return (self.connections.length < _maxConnections) ? null : self.connections[0]; + }; + + /** + * @returns whether or not the Endpoint can accept any more + * Connections. + */ + this.isFull = function() { + return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); + }; + + /** + * sets whether or not connnctions can be dragged from this Endpoint + * once it is full. you would use this in a UI in which you're going + * to provide some other way of breaking connections, if you need to + * break them at all. this property is by default true; use it in + * conjunction with the 'reattach' option on a connect call. + */ + this.setDragAllowedWhenFull = function(allowed) { + self.dragAllowedWhenFull = allowed; + }; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + /** + * paints the Endpoint, recalculating offset and anchor positions if + * necessary. + */ + this.paint = function(params) { + + params = params || {}; + var timestamp = params.timestamp; + if (!timestamp || self.timestamp !== timestamp) { + var ap = params.anchorPoint, canvas = params.canvas, connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + // do we always want to force a repaint here? i dont + // think so! + var xy = params.offset || offsets[_elementId]; + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (self.anchor.isDynamic) { + if (self.connections.length > 0) { + var c = self.connections[0]; + var oIdx = c.endpoints[0] == self ? 1 : 0; + var oId = oIdx == 0 ? c.sourceId : c.targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + } + ap = self.anchor.compute(anchorParams); + } + _endpoint.paint(ap, self.anchor.getOrientation(), canvas || self.canvas, _style, connectorPaintStyle || _style); + self.timestamp = timestamp; + } + }; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener + // maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + var start = function() { + jpc = connectorSelector(); + if (self.isFull() && !self.dragAllowedWhenFull) return false; + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + var nE = _getElementObject(n); + _appendElement(n, self.container); // + // create and assign an id, and initialize the offset. + var id = _getId(nE); + _updateOffset( { elId : id }); + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor( { reference : self.anchor, referenceCanvas : self.canvas }); + floatingEndpoint = new Endpoint({ style : { fillStyle : 'rgba(0,0,0,0)' }, endpoint : _endpoint, anchor : floatingAnchor, source : nE }); + + if (jpc == null) { + self.anchor.locked = true; + // create a connection. one end is this endpoint, the + // other is a floating endpoint. + jpc = new Connection( { + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : _getElementObject(_element), + target : _getElementObject(n), + anchors : [ self.anchor, floatingAnchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + connector : params.connector, // this can also be null. Connection will use the default. + overlays : self.connectorOverlays // new in 1.2.4. + }); + } else { + existingJpc = true; + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas)); + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; + // detach from the connection while dragging is occurring. + self.detachFromConnection(jpc); + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId ]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId ]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + // lock the other endpoint; if it is dynamic it will not + // move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new + // floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // TODO unregister on stop? or will floating endpoint's + // destruction be assured. + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be + // dragging the source at any time + // before this connection is either discarded or made into a + // permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + }; + + var dragOptions = params.dragOptions || {}; + var defaultOpts = jsPlumb.extend( {}, jsPlumb.CurrentLibrary.defaultDragOptions); + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + var startEvent = jsPlumb.CurrentLibrary.dragEvents['start']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], + function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + jsPlumb.CurrentLibrary.setOffset(n, _ui); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements( [ n, floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (_reattach) { + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } else { + jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc); // the main endpoint will inform the floating endpoint + // to disconnect, and also post the detached event. + } + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElement(jpc.canvas, self.container); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint(); + jpc.repaint(); + jpc = null; + delete inPlaceCopy; + delete endpointsByElement[floatingEndpoint.elementId]; + delete floatingEndpoint; + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas) { + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + var drop = function() { + var draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)); + var id = _getAttribute(draggable, "dragId"); + var elId = _getAttribute(draggable, "elId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // todo test that the target is not full. + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + if (!jpc.suspendedEndpoint) { // if a new connection, add it. TODO: move this to a jsPlumb internal method - addConnection or something. doesnt need to be exposed. + _addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + _currentInstance.fireUpdate("jsPlumbConnectionDetached", { + source : idx == 0 ? suspendedElement : jpc.source, target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1] + }); + } + + jsPlumb.repaint(elId); + + _currentInstance.fireUpdate("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + } + + delete floatingConnections[id]; + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute( _getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + }); + + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas)); + + return self; + }; + + /* + * Property: Defaults These are the default settings for jsPlumb, that + * is what will be used if you do not supply specific pieces of + * information to the various API calls. A convenient way to implement + * your own look and feel can be to override these defaults by including + * a script somewhere after the jsPlumb include, but before you make any + * calls to jsPlumb, for instance in this example we set the PaintStyle + * to be a blue line of 27 pixels: > jsPlumb.Defaults.PaintStyle = { + * lineWidth:27, strokeStyle:'blue' } + */ + this.Defaults = { + Anchor : null, + Anchors : [ null, null ], + BackgroundPaintStyle : null, + Connector : null, + Container : null, + DragOptions : {}, + DropOptions : {}, + Endpoint : null, + Endpoints : [ null, null ], + EndpointStyle : { + fillStyle : null + }, + EndpointStyles : [ null, null ], + LabelStyle : { + fillStyle : "rgba(0,0,0,0)", + color : "black" + }, + LogEnabled : true, + MaxConnections : null, + // TODO: should we have OverlayStyle too? + PaintStyle : { + lineWidth : 10, + strokeStyle : 'red' + }, + Scope : "_jsPlumb_DefaultScope" + }; + + this.logEnabled = this.Defaults.LogEnabled; + + /* + * Property: connectorClass The CSS class to set on Connection canvas + * elements. This value is a String and can have multiple classes; the + * entire String is appended as-is. + */ + this.connectorClass = '_jsPlumb_connector'; + + /* + * Property: endpointClass The CSS class to set on Endpoint canvas + * elements. This value is a String and can have multiple classes; the + * entire String is appended as-is. + */ + this.endpointClass = '_jsPlumb_endpoint'; + + /* + * Property: overlayClass The CSS class to set on an Overlay that is an + * HTML element. This value is a String and can have multiple classes; + * the entire String is appended as-is. + */ + this.overlayClass = '_jsPlumb_overlay'; + + /* + * Property: Anchors Default jsPlumb Anchors. These are supplied in the + * file jsPlumb-defaults-x.x.x.js, which is merged in with the main + * jsPlumb script to form .jsPlumb-all-x.x.x.js. You can + * provide your own Anchors by supplying them in a script that is loaded + * after jsPlumb, for instance: > jsPlumb.Anchors.MyAnchor = { + * ....anchor code here. see the documentation. } + */ + this.Anchors = {}; + + /* + * Property: Connectors Default jsPlumb Connectors. These are supplied + * in the file jsPlumb-defaults-x.x.x.js, which is merged in with the + * main jsPlumb script to form .jsPlumb-all-x.x.x.js. You can + * provide your own Connectors by supplying them in a script that is + * loaded after jsPlumb, for instance: > jsPlumb.Connectors.MyConnector = { + * ....connector code here. see the documentation. } + */ + this.Connectors = {}; + + /* + * Property: Endpoints Default jsPlumb Endpoints. These are supplied in + * the file jsPlumb-defaults-x.x.x.js, which is merged in with the main + * jsPlumb script to form .jsPlumb-all-x.x.x.js. You can + * provide your own Endpoints by supplying them in a script that is + * loaded after jsPlumb, for instance: > jsPlumb.Endpoints.MyEndpoint = { + * ....endpoint code here. see the documentation. } + */ + this.Endpoints = {}; + + /* + * Property:Overlays Default jsPlumb Overlays such as Arrows and Labels. + * These are supplied in the file jsPlumb-defaults-x.x.x.js, which is + * merged in with the main jsPlumb script to form + * .jsPlumb-all-x.x.x.js. You can provide your own Overlays by + * supplying them in a script that is loaded after jsPlumb, for + * instance: > jsPlumb.Overlays.MyOverlay = { ....overlay code here. see + * the documentation. } + */ + this.Overlays = {}; + + /* + * Function: addEndpoint Adds an Endpoint to a given element. + * Parameters: target - Element to add the endpoint to. either an + * element id, or a selector representing some element. params - Object + * containing Endpoint options (more info required) Returns: The newly + * created Endpoint. See Also: + */ + this.addEndpoint = function(target, params) { + params = jsPlumb.extend( {}, params); + params.endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + params.endpointStyle = params.endpointStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + var el = _getElementObject(target), id = _getAttribute(el, "id"); + params.source = el; + _updateOffset({ elId : id }); + var e = new Endpoint(params); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + return e; + }; + + /* + * Function: addEndpoint Adds a list of Endpoints to a given element. + * Parameters: target - element to add the endpoint to. either an + * element id, or a selector representing some element. endpoints - List + * of objects containing Endpoint options. one Endpoint is created for + * each entry in this list. Returns: List of newly created Endpoints, + * one for each entry in the 'endpoints' argument. See Also: + * + */ + this.addEndpoints = function(target, endpoints) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + results.push(_currentInstance.addEndpoint(target, endpoints[i])); + } + return results; + }; + + /* + * Function: animate Wrapper around supporting library's animate + * function; injects a call to jsPlumb in the 'step' function (creating + * it if necessary). This only supports the two-arg version of the + * animate call in jQuery - the one that takes an 'options' object as + * the second arg. MooTools has only one method - a two arg one. Which + * is handy. Parameters: el - Element to animate. Either an id, or a + * selector representing the element. properties - The 'properties' + * argument you want passed to the standard jQuery animate call. options - + * The 'options' argument you want passed to the standard jQuery animate + * call. Returns: void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // you know, probably onComplete should repaint too. that will help + // keep up + // with fast animations. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /* + * Function: connect Establishes a connection between two elements. + * Parameters: params - Object containing setup for the connection. see + * documentation. Returns: The newly created Connection. + */ + this.connect = function(params) { + var _p = jsPlumb.extend( {}, params); + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not + // full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log("could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log("could not add connection; target endpoint is full"); + return; + } + + // dynamic anchors + if (_p.dynamicAnchors) { + // these can either be an array of anchor coords, which we will use for both source and target, or an object with {source:[anchors], target:[anchors]}, in which + // case we will use a different set for each element. + var a = _p.dynamicAnchors.constructor == Array; + var sa = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.source)); + var ta = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.target)); + _p.anchors = [sa,ta]; + } + + var jpc = new Connection(_p); + // add to list of connections (by scope). + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + _currentInstance.fireUpdate("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + // force a paint + _draw(jpc.source); + + return jpc; + }; + + /* + Function: deleteEndpoint + + deletes an endpoint and removes all connections it has (which removes the connections from the other endpoints involved too) + + You can call this with either a string uuid, or an Endpoint + + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElement(endpoint.canvas, endpoint.container); + // remove from endpointsbyElement + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + delete endpoint; + } + }; + + /* + * Function: deleteEveryEndpoint + * + * Deletes every Endpoint in this instance + * of jsPlumb. Use this instead of removeEveryEndpoint now. + * + * Returns: void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc) { + _currentInstance.fireUpdate("jsPlumbConnectionDetached", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + }; + + /* + * Function: + * detach Removes a connection. takes either (source, target) (the old way, maintained for backwards compatibility), or a params + * object with various possible values. + * + * + * Parameters: + * source - id or element object of the first element in the connection. + * target - id or element object of the second element in the connection. + * + * OR + * + * params: + * { + * + * Returns: true if successful, false if not. + */ + this.detach = function(source, target) { + if (arguments.length == 2) { + var s = _getElementObject(source), sId = _getId(s); + var t = _getElementObject(target), tId = _getId(t); + _operation(sId, function(jpc) { + if ((jpc.sourceId == sId && jpc.targetId == tId) || (jpc.targetId == sId && jpc.sourceId == tId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + fireDetachEvent(jpc); + } + }); + } + // this is the new version of the method, taking a JS object like + // the connect method does. + else if (arguments.length == 1) { + if (arguments[0].constructor == Connection) { + arguments[0].endpoints[0].detachFrom(arguments[0].endpoints[1]); + fireDetachEvent(arguments[0]); + } + else if (arguments[0].connection) { + arguments[0].connection.endpoints[0].detachFrom(arguments[0].connection.endpoints[1]); + fireDetachEvent(arguments[0].connection); + } + else { + var _p = jsPlumb.extend( {}, source); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1])); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + sourceId = _getId(_p.source); + targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + fireDetachEvent(jpc); + } + }); + } + } + } + }; + + /* + * Function: detachAll Removes all an element's connections. Parameters: + * el - either the id of the element, or a selector for the element. + * Returns: void + */ + this.detachAllConnections = function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + }; + + /** + * @deprecated Use detachAllConnections instead. this will be removed in jsPlumb 1.3. + */ + this.detachAll = this.detachAllConnections; + + /* + * Function: detachEverything Remove all Connections from all elements, + * but leaves Endpoints in place. Returns: void See Also: + * + */ + this.detachEveryConnection = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + /** + * @deprecated use detachEveryConnection instead. this will be removed in jsPlumb 1.3. + */ + this.detachEverything = this.detachEveryConnection; + + /* + * Function: draggable initialises the draggability of some element or + * elements. Parameters: el - either an element id, a list of element + * ids, or a selector. options - options to pass through to the + * underlying library Returns: void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + * Function: extend Wraps the underlying library's extend functionality. + * Parameters: o1 - object to extend o2 - object to extend o1 with + * Returns: o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getConnections Gets all or a subset of connections + * currently managed by this jsPlumb instance. Parameters: options - a + * JS object that holds options defining what sort of connections you're + * looking for. Valid values are: scope this may be a String or a list + * of Strings. jsPlumb will only return Connections whose scope matches + * what this option defines. If you omit it, you will be given + * Connections of every scope. source may be a string or a list of + * strings; constraints results to have only Connections whose source is + * this object. target may be a string or a list of strings; constraints + * results to have only Connections whose target is this object. + * + * The return value is a dictionary in this format: + * { 'scope1': [ {sourceId:'window1', targetId:'window2', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:}, + * {sourceId:'window3', targetId:'window4', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:}, + * {sourceId:'window1', targetId:'window3', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:} ], + * 'scope2': [ {sourceId:'window1', targetId:'window3', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:} ] } + * + */ + this.getConnections = function(options) { + var r = {}; + options = options || {}; + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') + r.push(input); + else + r = input; + } + return r; + }; + var scopes = prepareList(options.scope); + var sources = prepareList(options.source); + var targets = prepareList(options.target); + var filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + r[i] = []; + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + r[i].push({ sourceId : c.sourceId, targetId : c.targetId, source : c.source, target : c.target, sourceEndpoint : c.endpoints[0], targetEndpoint : c.endpoints[1], connection : c }); + } + } + } + return r; + }; + + /* + * Function: getDefaultScope Gets the default scope for connections and + * endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + * Function: getEndpoint gets an Endpoint by UUID Parameters: uuid - the + * UUID for the Endpoint Returns: Endpoint with the given UUID, null if + * nothing found. + */ + this.getEndpoint = _getEndpoint; + + /* + * gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + + /* + * Function: hide Sets an element's connections to be hidden. + * Parameters: el - either the id of the element, or a selector for the + * element. Returns: void + */ + this.hide = function(el) { + _setVisible(el, "none"); + }; + + /* + * Function: makeAnchor Creates an anchor with the given params. + * + * You do not need to use this method. It is exposed because of the way jsPlumb is + * split into three scripts; this will change in the future. + * + * Parameters: x - the x location of the anchor as a fraction of the + * total width. y - the y location of the anchor as a fraction of the + * total height. xOrientation - value indicating the general direction a + * connection from the anchor should go in, in the x direction. + * yOrientation - value indicating the general direction a connection + * from the anchor should go in, in the y direction. xOffset - a fixed + * offset that should be applied in the x direction that should be + * applied after the x position has been figured out. optional. defaults + * to 0. yOffset - a fixed offset that should be applied in the y + * direction that should be applied after the y position has been + * figured out. optional. defaults to 0. + * -- OR -- + * + * params - {x:..., y:..., xOrientation etc } + * -- OR FROM 1.2.4 --- + * + * name - the name of some Anchor in the _currentInstance.Anchors array. + * -- OR FROM 1.2.4 --- + * + * coords - a list of coords for the anchor, like you would pass to + * jsPlumb.makeAnchor (eg [0.5,0.5,0,-1] - an anchor in the center of + * some element, oriented towards the top of the screen) + * -- OR FROM 1.2.4 --- + * + * anchor - an existing anchor. just gets passed back. it's handy + * internally to have this functionality. + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed + // in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards + // compatibility if we are given only one value we assume it's a + // call in the old form. + if (arguments.length == 0) return null; + var params = {}; + if (arguments.length == 1) { + var specimen = arguments[0]; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; + // is it the name of an anchor type? + else if (typeof specimen == "string") return _currentInstance.Anchors[arguments[0]](); + // is it an array of coordinates? + else if (specimen.constructor == Array) { + if (specimen[0].constructor == Array || specimen[0].constructor == String) + return new DynamicAnchor(specimen); + else + return jsPlumb.makeAnchor.apply(this, specimen); + } + // last we try the backwards compatibility stuff. + else if (typeof arguments[0] == "object") jsPlumb.extend(params, x); + } else { + params = { x : x, y : y }; + if (arguments.length >= 4) params.orientation = [ arguments[2], arguments[3] ]; + if (arguments.length == 6) params.offsets = [ arguments[4], arguments[5] ]; + } + var a = new Anchor(params); + a.clone = function() { + return new Anchor(params); + }; + return a; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types) { + var r = []; + for ( var i = 0; i < types.length; i++) + if (typeof types[i] == "string") + r.push(_currentInstance.Anchors[types[i]]()); + else if (types[i].constructor == Array) + r.push(jsPlumb.makeAnchor(types[i])); + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /* + * Function: repaint Repaints an element and its connections. This + * method gets new sizes for the elements before painting anything. + * Parameters: el - either the id of the element or a selector + * representing the element. Returns: void See Also: + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + * Function: repaintEverything Repaints all connections. + * Returns: void + * See Also: + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + * Function: removeAllEndpoints + * Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + * Parameters: el - either an element id, or a selector for an element. + * Returns: void + * See Also: + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + for ( var i in ebe) + _currentInstance.deleteEndpoint(ebe[i]); + endpointsByElement[elId] = []; + }; + + /* + * Function: removeEveryEndpoint + * Removes every Endpoint in this instance + * of jsPlumb. + * + * Returns: void + * + * See Also: + * + * @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /** + * Function: removeEndpoint Removes the given Endpoint from the given + * element. Parameters: el - either an element id, or a selector for an + * element. endpoint - Endpoint to remove. this is an Endpoint object, + * such as would have been returned from a call to addEndpoint. Returns: + * void See Also: + * + * @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + /** + * Function:reset + * removes all endpoints and connections and clears the + * listener list. to keep listeners just call jsPlumb.deleteEveryEndpoint. + */ + this.reset = function() { + this.deleteEveryEndpoint(); + this.clearListeners(); + }; + + /* + * Function: setAutomaticRepaint Sets/unsets automatic repaint on window + * resize. Parameters: value - whether or not to automatically repaint + * when the window is resized. Returns: void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + * Function: setDefaultNewCanvasSize Sets the default size jsPlumb will + * use for a new canvas (we create a square canvas so one value is all + * that is required). This is a hack for IE, because ExplorerCanvas + * seems to need for a canvas to be larger than what you are going to + * draw on it at initialisation time. The default value of this is 1200 + * pixels, which is quite large, but if for some reason you're drawing + * connectors that are bigger, you should adjust this value + * appropriately. Parameters: size - The default size to use. jsPlumb + * will use a square canvas so you need only supply one value. Returns: + * void + */ + this.setDefaultNewCanvasSize = function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }; + + /* + * Function: setDefaultScope Sets the default scope for connections and + * endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable Sets whether or not a given element is + * draggable, regardless of what any plumb command may request. + * Parameters: el - either the id for the element, or a selector + * representing the element. Returns: void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setDraggableByDefault Sets whether or not elements are + * draggable by default. Default for this is true. Parameters: draggable - + * value to set Returns: void + */ + this.setDraggableByDefault = function(draggable) { + _draggableByDefault = draggable; + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction Sets the function to fire when the + * window size has changed and a repaint was fired. Parameters: f - + * Function to execute. Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: show Sets an element's connections to be visible. + * Parameters: el - either the id of the element, or a selector for the + * element. Returns: void + */ + this.show = function(el) { + _setVisible(el, "block"); + }; + + /* + * Function: sizeCanvas Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * Parameters: x - [int] x position for the Canvas origin y - [int] y + * position for the Canvas origin w - [int] width of the canvas h - + * [int] height of the canvas Returns: void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible Toggles visibility of an element's + * connections. Parameters: el - either the element's id, or a selector + * representing the element. Returns: void, but should be updated to + * return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable Toggles draggability (sic) of an element's + * connections. Parameters: el - either the element's id, or a selector + * representing the element. Returns: The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload Unloads jsPlumb, deleting all storage. You should + * call this from an onunload attribute on the element Returns: + * void + */ + this.unload = function() { + delete endpointsByElement; + delete endpointsByUUID; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + }; + + /* + * Function: wrap + * + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + + EventGenerator.apply(this); + this.addListener = this.bind; + + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(); + if (_defaults) jsPlumb.extend(j.Defaults, _defaults); + return j; + }; +})(); +/* +* jsPlumb-defaults-1.2.4-RC1 +* +* This script contains the default Anchors, Endpoints, Connectors and Overlays for jsPlumb. It should be used with jsPlumb 1.1.0 and above; +* prior to version 1.1.0 of jsPlumb the defaults were included inside the main script. +* +* NOTE: for production usage you should use jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, +* in a minified file. +* +* Dual licensed under MIT and GPL2. +*/ + +(function() { + + var ie = !!!document.createElement('canvas').getContext; + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * getOrientation : function() { return [ox, oy]; } + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + var _curryAnchor = function(x,y,ox,oy) { + return function() { + return jsPlumb.makeAnchor(x,y,ox,oy); + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1); + + + jsPlumb.Defaults.DynamicAnchors = function() { + return jsPlumb.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"]); + }; + jsPlumb.Anchors["AutoDefault"] = function() { return jsPlumb.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors()); }; + + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + var currentPoints = null; + var _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + + _dx = _tx - _sx, _dy = (_ty - _sy); + _m = _dy / _dx, _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + + return currentPoints; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + var xp = _sx + (location * _dx); + var yp = _m == Infinity ? xp + _b : (_m * xp) + _b; + return {x:xp, y:yp}; + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { return _m; }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + var orientation = distance > 0 ? 1 : -1; + var y = Math.abs(distance * Math.sin(_theta)); + if (_sy > _ty) y = y * -1; + var x = Math.abs(distance * Math.cos(_theta)); + if (_sx > _tx) x = x * -1; + return {x:p.x + (orientation * x), y:p.y + (orientation * y)}; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = self.gradientAtPoint(p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + /** + * This Connector draws a Bezier curve with two control points. + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + jsPlumb.Connectors.Bezier = function(curviness) { + var self = this; + this.majorAnchor = curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _sx, _sy, _canvasX, _canvasY, _w, _h; + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) + { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx); var minx2 = Math.min(_CP[0], _CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(_sx,_tx); var maxx2 = Math.max(_CP[0], _CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty); var miny2 = Math.min(_CP[1], _CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(_sy,_ty); var maxy2 = Math.max(_CP[1], _CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + this.paint = function(d, ctx) { + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the distance the given point is from the curve. not enabled for 1.2.3. didnt make the cut. next time. + */ + this.distanceFrom = function(point) { + var curve = [ {x:currentPoints[4], y:currentPoints[5]}, + {x:currentPoints[8], y:currentPoints[9]}, + {x:currentPoints[10], y:currentPoints[11]}, + {x:currentPoints[6], y:currentPoints[7]}]; + return (jsBezier.distanceFromCurve(point, curve)); + }; + + this.nearestPointTo = function(point) { + var curve = [ {x:currentPoints[4], y:currentPoints[5]}, + {x:currentPoints[8], y:currentPoints[9]}, + {x:currentPoints[10], y:currentPoints[11]}, + {x:currentPoints[6], y:currentPoints[7]}]; + return (jsBezier.nearestPointOnCurve(point, curve)); + + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + return jsBezier.perpendicularToCurveAt(_makeCurve(), location, length, distance); + }; + + this.createGradient = function(dim, ctx, swap) { + return (swap) ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + + /** + * Types of endpoint UIs. we supply four - a circle of default radius 10px, a rectangle of + * default size 20x20, an image (with no default), and a Triangle, of default size 15. + * you can supply others of these if you want to - see the documentation for a howto. + */ + + /** + * a round endpoint, with default radius 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + } + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + if (endpointStyle.gradient && !ie) { + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * A Rectangular endpoint, with default size 20x20. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + jsPlumb.Endpoints.Triangle = function(params) { + + params = params || { width:15, height:15 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - width/2; + var y = anchorPoint[1] - height/2; + + jsPlumb.sizeCanvas(canvas, x, y, width, height); + + var ctx = canvas.getContext('2d'); + var offsetX = 0, offsetY = 0, angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = endpointStyle.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' or, since 1.2.4, a 'src' property in the params object.. + */ + jsPlumb.Endpoints.Image = function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.src || params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + }; + + /** + * An arrow overlay. you can provide: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + */ + jsPlumb.Overlays.Arrow = function(params) { + params = params || {}; + var self = this; + var length = params.length || 20; + var width = params.width || 20; + var fillStyle = params.fillStyle || "black"; + var strokeStyle = params.strokeStyle; + var lineWidth = params.lineWidth || 1; + this.loc = params.location || 0.5; + // how far along the arrow the lines folding back in come to. default is 62.3%. + var foldback = params.foldback || 0.623; + var _getFoldBackPoint = function(connector, loc) { + if (foldback == 0.5) return connector.pointOnPath(loc); + else { + var adj = 0.5 - foldback; // we calculate relative to the center + return connector.pointAlongPathFrom(loc, length * adj); + } + }; + + this.computeMaxSize = function() { return width * 1.5; } + + this.draw = function(connector, ctx) { + // this is the arrow head position + var hxy = connector.pointAlongPathFrom(self.loc, length / 2); + // this is the center of the tail + var txy = connector.pointAlongPathFrom(self.loc, -length / 2), tx = txy.x, ty = txy.y; + // this is the tail vector + var tail = connector.perpendicularToPathAt(self.loc, width, -length / 2); + // this is the point the tail goes in to + var cxy = _getFoldBackPoint(connector, self.loc); + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(hxy.x, hxy.y); + ctx.lineTo(tail[0].x, tail[0].y); + ctx.lineTo(cxy.x, cxy.y); + ctx.lineTo(tail[1].x, tail[1].y); + ctx.lineTo(hxy.x, hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + + /** + * a basic arrow. this is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. See Arrow for params. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + /** + * a diamond. like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. See Arrow for params. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40; + var p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + /** + * A Label overlay. Params you can provide: + * + * labelStyle - js object containing style instructions for the label. defaults to jsPlumb.Defaults.LabelStyle. + * label - the label to paint. may be a string or a function that returns a string. nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * borderWidth - width of a border to paint. defaults to zero. + * borderStyle - strokeStyle to use when painting the border, if necessary. + */ + jsPlumb.Overlays.Label = function(params) { + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + var self = this; + var labelWidth = null, labelHeight = null, labelText = null, labelPadding = null; + this.location = params.location || 0.5; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var _textDimensions = function(ctx) { + if (self.cachedDimensions) return self.cachedDimensions; // return cached copy if we can. if we add a setLabel function remember to clear the cache. + labelText = typeof self.label == 'function' ? self.label(self) : self.label; + var d = {}; + if (labelText) { + var lines = labelText.split(/\n|\r\n/); + ctx.save(); + if (self.labelStyle.font) ctx.font = self.labelStyle.font; + var t = _widestLine(lines, ctx); + // a fake text height measurement: use the width of upper case M + var h = ctx.measureText("M").width; + labelPadding = self.labelStyle.padding || 0.25; + labelWidth = t + (2 * t * labelPadding); + labelHeight = (lines.length * h) + (2 * h * labelPadding); + var textHeight = lines.length * h; + ctx.restore(); + d = {width:labelWidth, height:labelHeight, lines:lines, oneLine:h, padding:labelPadding, textHeight:textHeight}; + } + if (typeof self.label != 'function') self.cachedDimensions = d; // cache it if we can. + return d; + }; + this.computeMaxSize = function(connector, ctx) { + var td = _textDimensions(ctx); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + var _widestLine = function(lines, ctx) { + var max = 0; + for (var i = 0; i < lines.length; i++) { + var t = ctx.measureText(lines[i]).width; + if (t > max) max = t; + } + return max; + }; + + this.draw = function(connector, ctx) { + var td = _textDimensions(ctx); + if (td.width) { + var cxy = connector.pointOnPath(self.location); + if (self.labelStyle.font) ctx.font = self.labelStyle.font; + if (self.labelStyle.fillStyle) + ctx.fillStyle = self.labelStyle.fillStyle; + else + ctx.fillStyle = "rgba(0,0,0,0)"; + ctx.fillRect(cxy.x - (td.width / 2), cxy.y - (td.height / 2) , td.width , td.height ); + + if (self.labelStyle.color) ctx.fillStyle = self.labelStyle.color; + ctx.textBaseline = "middle"; + ctx.textAlign = "center"; + for (i = 0; i < td.lines.length; i++) { + ctx.fillText(td.lines[i],cxy.x, cxy.y - (td.textHeight / 2) + (td.oneLine/2) + (i*td.oneLine)); + } + + // border + if (self.labelStyle.borderWidth > 0) { + ctx.strokeStyle = self.labelStyle.borderStyle || "black"; + ctx.strokeRect(cxy.x - (td.width / 2), cxy.y - (td.height / 2) , td.width , td.height ); + } + } + }; + }; + + /** + * an image overlay. params may contain: + * + * location : proportion along the connector to draw the image. optional. + * src : image src. required. + * events : map of event names to functions; each event listener will be bound to the img. + */ + jsPlumb.Overlays.Image = function(params) { + var self = this; + this.location = params.location || 0.5; + this.img = new Image(); + var imgDiv = null; + var notReadyInterval = null; + var notReadyConnector, notReadyContext; + var events = params.events || {}; + var _init = function() { + if (self.ready) { + window.clearInterval(notReadyInterval); + imgDiv = document.createElement("img"); + imgDiv.src = self.img.src; + imgDiv.style.position = "absolute"; + imgDiv.style.display="none"; + imgDiv.className = "_jsPlumb_overlay"; + document.body.appendChild(imgDiv);// HMM + // attach events + for (var e in events) { + jsPlumb.CurrentLibrary.bind(imgDiv, e, events[e]); + } + if (notReadyConnector && notReadyContext) { + _draw(notReadyConnector, notReadyContext); + notReadyContext = null; + notReadyConnector = null; + } + } + }; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.src || params.url; + + notReadyInterval = window.setInterval(_init, 250); + + this.computeMaxSize = function(connector, ctx) { + return [self.img.width, self.img.height] + }; + + var _draw = function(connector, ctx) { + var cxy = connector.pointOnPath(self.location); + var canvas = jsPlumb.CurrentLibrary.getElementObject(ctx.canvas); + var canvasOffset = jsPlumb.CurrentLibrary.getOffset(canvas); + var o = {left:canvasOffset.left + cxy.x - (self.img.width/2), top:canvasOffset.top + cxy.y - (self.img.height/2)}; + jsPlumb.CurrentLibrary.setOffset(imgDiv, o); + imgDiv.style.display = "block"; + }; + + this.draw = function(connector, ctx) { + if (self.ready) + _draw(connector, ctx); + else { + notReadyConnector = connector; + notReadyContext = ctx; + } + }; + }; +})();/* + * mootools.jsPlumb 1.2.5-RC1 + * + * MooTools specific functionality for jsPlumb. + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + * NOTE: for production usage you should use mootools.jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, + * in a minified file. + * + * Dual licensed under MIT and GPL2. + * + */ + +(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function() { + this.parent(); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}; + var _droppableOptions = {}; + var _draggablesByScope = {}; + var _draggablesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }; + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + var _getElementObject = function(el) { + return $(el); + }; + + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + m.start(properties); + }, + + appendElement : function(child, parent) { + _getElementObject(parent).grab(child); + }, + + bind : function(el, event, callback) { + el = _getElementObject(el); + el.addEvent(event, callback); + }, + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + }, + + getElementObject : _getElementObject, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + getScrollLeft : function(el) { + return null; + }, + + getScrollTop : function(el) { + return null; + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[0]; + return { left: ui.offsetLeft, top: ui.offsetTop }; + }, + + initDraggable : function(el, options) { + var id = jsPlumb.getId(el); + var drag = _draggablesById[id]; + if (!drag) { + var originalZIndex = 0, originalCursor = null; + var dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + options['onStart'] = jsPlumb.wrap(options['onStart'], function() + { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() + { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + }); + + // DROPPABLES: + var scope = options['scope'] || jsPlumb.Defaults.Scope; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop'); + } + }; + + drag = new Drag.Move(el, options); + _add(_draggablesByScope, scope, drag); + _add(_draggablesById, el.get("id"), drag); + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + initDroppable : function(el, options) { + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _add(_droppables, scope, el); + var id = jsPlumb.getId(el); + _droppableOptions[id] = options; + var filterFunc = function(entry) { return entry.element != el; }; + var draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + isAlreadyDraggable : function(el) { + return _draggablesById[jsPlumb.getId(el)] != null; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + return (typeof Drag != undefined && typeof Drag.Move != undefined); + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + _getElementObject(element).dispose(); // ?? + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + setOffset : function(el, o) { + _getElementObject(el).setPosition({x:o.left, y:o.top}); + } + }; +})(); +/** +* a set of Bezier curve functions that deal with Cubic Beziers, used by jsPlumb, and perhaps useful for other people. +* +* - functions are all in the 'jsBezier' namespace. +* +* - all input points should be in the format {x:.., y:..}. all output points are in this format too. +* +* - all input curves should be in the format [ {x:.., y:..}, {x:.., y:..}, {x:.., y:..}, {x:.., y:..} ] +* +* - 'location' as used as an input here refers to a decimal in the range 0-1 inclusive, which indicates a point some proportion along the length +* of the curve. location as output has the same format and meaning. +* +* +* Function List: +* -------------- +* +* distanceFromCurve(point, curve) +* +* Calculates the distance that the given point lies from the given Cubic Bezier. Note that it is computed relative to the center of the Bezier, +* so if you have stroked the curve with a wide pen you may wish to take that into account! The distance returned is relative to the values +* of the curve and the point - it will most likely be pixels. +* +* gradientAtPoint(curve, location) +* +* Calculates the gradient to the curve at the given location, as a decimal between 0 and 1 inclusive. +* +* nearestPointOnCurve(point, curve) +* +* Calculates the nearest point to the given point on the given curve. The return value of this is a JS object literal, containing both the +*point's coordinates and also the 'location' of the point (see above), for example: { point:{x:551,y:150}, location:0.263365 }. +* +* pointOnCurve(curve, location) +* +* Calculates the coordinates of the point on the given cubic Bezier curve at the given location. +* +* pointAlongCurveFrom(curve, location, distance) +* +* Calculates the coordinates of the point on the given curve that is 'distance' units from location. 'distance' should be in the same coordinate +* space as that used to construct the Bezier curve. For an HTML Canvas usage, for example, distance would be a measure of pixels. +* +* perpendicularToCurveAt(curve, location, length, distance) +* +* Calculates the perpendicular to the given curve at the given location. length is the length of the line you wish for (it will be centered +* on the point at 'location'). distance is optional, and allows you to specify a point along the path from the given location as the center of +* the perpendicular returned. The return value of this is an array of two points: [ {x:...,y:...}, {x:...,y:...} ]. +* +* quadraticPointOnCurve(curve, location) +* +* Calculates the coordinates of the point on the given quadratic Bezier curve at the given location. This function is used internally by +* pointOnCurve, and is exposed just because it seemed churlish not to do so. But remember that all the other functions in this library deal with +* cubic Beziers. +* +* +* references: +* +* http://webdocs.cs.ualberta.ca/~graphics/books/GraphicsGems/gems/NearestPoint.c +* http://13thparallel.com/archive/bezier-curves/ +* http://bimixual.org/AnimationLibrary/beziertangents.html +* +*/ + +(function() { + + if(typeof Math.sgn == "undefined") { + Math.sgn = function(x) { return x == 0 ? 0 : x > 0 ? 1 :-1; }; + } + + var Vectors = { + subtract : function(v1, v2) { return {x:v1.x - v2.x, y:v1.y - v2.y }; }, + dotProduct : function(v1, v2) { return (v1.x * v2.x) + (v1.y * v2.y); }, + square : function(v) { return Math.sqrt((v.x * v.x) + (v.y * v.y)); }, + scale : function(v, s) { return {x:v.x * s, y:v.y * s }; } + }; + + var MAXDEPTH = 64, EPSILON = Math.pow(2.0,-MAXDEPTH-1), DEGREE = 3, W_DEGREE = 5; + + /** + * Calculates the distance that the point lies from the curve. + * + * @param point a point in the form {x:567, y:3342} + * @param curve a Bezier curve in the form [{x:..., y:...}, {x:..., y:...}, {x:..., y:...}, {x:..., y:...}]. note that this is currently + * hardcoded to assume cubiz beziers, but would be better off supporting any degree. + * @return a JS object literal containing location and distance, for example: {location:0.35, distance:10}. Location is analogous to the location + * argument you pass to the pointOnPath function: it is a ratio of distance travelled along the curve. Distance is the distance in pixels from + * the point to the curve. + */ + var _distanceFromCurve = function(point, curve) { + var candidates = new Array(W_DEGREE); + var w = _convertToBezier(point, curve); + var numSolutions = _findRoots(w, W_DEGREE, candidates, 0); + var v = Vectors.subtract(point, curve[0]), dist = Vectors.square(v), t = 0.0; + for (var i = 0; i < numSolutions; i++) { + v = Vectors.subtract(point, _bezier(curve, DEGREE, candidates[i], null, null)); + var newDist = Vectors.square(v); + if (newDist < dist) { + dist = newDist; + t = candidates[i]; + } + } + v = Vectors.subtract(point, curve[DEGREE]); + newDist = Vectors.square(v); + if (newDist < dist) { + dist = newDist; + t = 1.0; + } + return {location:t, distance:dist}; + }; + /** + * finds the nearest point on the curve to the given point. + */ + var _nearestPointOnCurve = function(point, curve) { + var td = _distanceFromCurve(point, curve); + return {point:_bezier(curve, DEGREE, td.location, null, null), location:td.location}; + }; + /** + * internal method; converts to 5th degree Bezier form. + */ + var _convertToBezier = function(point, curve) { + var c = new Array(DEGREE+1), d = new Array(DEGREE), cdTable = [], w = []; + var z = [ [1.0, 0.6, 0.3, 0.1], [0.4, 0.6, 0.6, 0.4], [0.1, 0.3, 0.6, 1.0] ]; + for (var i = 0; i <= DEGREE; i++) c[i] = Vectors.subtract(curve[i], point); + for (var i = 0; i <= DEGREE - 1; i++) { + d[i] = Vectors.subtract(curve[i+1], curve[i]); + d[i] = Vectors.scale(d[i], 3.0); + } + for (var row = 0; row <= DEGREE - 1; row++) { + for (var column = 0; column <= DEGREE; column++) { + if (!cdTable[row]) cdTable[row] = []; + cdTable[row][column] = Vectors.dotProduct(d[row], c[column]); + } + } + for (i = 0; i <= W_DEGREE; i++) { + if (!w[i]) w[i] = []; + w[i].y = 0.0; + w[i].x = parseFloat(i) / W_DEGREE; + } + var n = DEGREE, m = DEGREE-1; + for (var k = 0; k <= n + m; k++) { + var lb = Math.max(0, k - m); + var ub = Math.min(k, n); + for (i = lb; i <= ub; i++) { + j = k - i; + w[i+j].y += cdTable[j][i] * z[j][i]; + } + } + return w; + }; + /** + * counts how many roots there are. + */ + var _findRoots = function(w, degree, t, depth) { + var i; + var Left = new Array(W_DEGREE+1), Right = new Array(W_DEGREE+1); + var left_count, right_count; + var left_t = new Array(W_DEGREE+1), right_t = new Array(W_DEGREE+1); + switch (_getCrossingCount(w, degree)) { + case 0 : { + return 0; + } + case 1 : { + if (depth >= MAXDEPTH) { + t[0] = (w[0].x + w[W_DEGREE].x) / 2.0; + return 1; + } + if (_isControlPolygonFlatEnough(w, degree)) { + t[0] = _computeXIntercept(w, degree); + return 1; + } + break; + } + } + _bezier(w, degree, 0.5, Left, Right); + left_count = _findRoots(Left, degree, left_t, depth+1); + right_count = _findRoots(Right, degree, right_t, depth+1); + for (i = 0; i < left_count; i++) t[i] = left_t[i]; + for (i = 0; i < right_count; i++) t[i+left_count] = right_t[i]; + return (left_count+right_count); + }; + var _getCrossingCount = function(curve, degree) { + var n_crossings = 0; + var sign, old_sign; + sign = old_sign = Math.sgn(curve[0].y); + for (var i = 1; i <= degree; i++) { + sign = Math.sgn(curve[i].y); + if (sign != old_sign) n_crossings++; + old_sign = sign; + } + return n_crossings; + }; + var _isControlPolygonFlatEnough = function(curve, degree) { + var error; + var intercept_1, intercept_2, left_intercept, right_intercept; + var a, b, c, det, dInv, a1, b1, c1, a2, b2, c2; + a = curve[0].y - curve[degree].y; + b = curve[degree].x - curve[0].x; + c = curve[0].x * curve[degree].y - curve[degree].x * curve[0].y; + + var max_distance_above = max_distance_below = 0.0; + + for (var i = 1; i < degree; i++) { + var value = a * curve[i].x + b * curve[i].y + c; + if (value > max_distance_above) + max_distance_above = value; + else if (value < max_distance_below) + max_distance_below = value; + } + + a1 = 0.0; b1 = 1.0; c1 = 0.0; a2 = a; b2 = b; + c2 = c - max_distance_above; + det = a1 * b2 - a2 * b1; + dInv = 1.0/det; + intercept_1 = (b1 * c2 - b2 * c1) * dInv; + a2 = a; b2 = b; c2 = c - max_distance_below; + det = a1 * b2 - a2 * b1; + dInv = 1.0/det; + intercept_2 = (b1 * c2 - b2 * c1) * dInv; + left_intercept = Math.min(intercept_1, intercept_2); + right_intercept = Math.max(intercept_1, intercept_2); + error = right_intercept - left_intercept; + return (error < EPSILON)? 1 : 0; + }; + var _computeXIntercept = function(curve, degree) { + var XLK = 1.0, YLK = 0.0; + var XNM = curve[degree].x - curve[0].x, YNM = curve[degree].y - curve[0].y; + var XMK = curve[0].x - 0.0, YMK = curve[0].y - 0.0; + var det = XNM*YLK - YNM*XLK, detInv = 1.0/det; + var S = (XNM*YMK - YNM*XMK) * detInv; + return 0.0 + XLK * S; + }; + var _bezier = function(curve, degree, t, left, right) { + var temp = [[]]; + for (var j =0; j <= degree; j++) temp[0][j] = curve[j]; + for (var i = 1; i <= degree; i++) { + for (var j =0 ; j <= degree - i; j++) { + if (!temp[i]) temp[i] = []; + if (!temp[i][j]) temp[i][j] = {}; + temp[i][j].x = (1.0 - t) * temp[i-1][j].x + t * temp[i-1][j+1].x; + temp[i][j].y = (1.0 - t) * temp[i-1][j].y + t * temp[i-1][j+1].y; + } + } + if (left != null) + for (j = 0; j <= degree; j++) left[j] = temp[j][0]; + if (right != null) + for (j = 0; j <= degree; j++) right[j] = temp[degree-j][j]; + + return (temp[degree][0]); + }; + + /** + * calculates a point on the curve, for a cubic bezier (TODO: fold this and the other function into one). + * @param curve an array of control points, eg [{x:10,y:20}, {x:50,y:50}, {x:100,y:100}, {x:120,y:100}]. For a cubic bezier this should have four points. + * @param location a decimal indicating the distance along the curve the point should be located at. this is the distance along the curve as it travels, taking the way it bends into account. should be a number from 0 to 1, inclusive. + */ + var _pointOnPath = function(curve, location) { + // from http://13thparallel.com/archive/bezier-curves/ + function B1(t) { return t*t*t }; + function B2(t) { return 3*t*t*(1-t) }; + function B3(t) { return 3*t*(1-t)*(1-t) }; + function B4(t) { return (1-t)*(1-t)*(1-t) }; + + var x = curve[0].x*B1(location) + curve[1].x * B2(location) + curve[2].x * B3(location) + curve[3].x * B4(location); + var y = curve[0].y*B1(location) + curve[1].y * B2(location) + curve[2].y * B3(location) + curve[3].y * B4(location); + //return [x,y]; + return {x:x, y:y}; + }; + + /** + * calculates a point on the curve, for a quadratic bezier (TODO: fold this and the other function into one). + * @param curve an array of control points, eg [{x:10,y:20}, {x:50,y:50}, {x:100,y:100}]. For a quadratic bezier this should have three points. + * @param location a decimal indicating the distance along the curve the point should be located at. this is the distance along the curve as it travels, taking the way it bends into account. should be a number from 0 to 1, inclusive. + */ + var _quadraticPointOnPath = function(curve, location) { + function B1(t) { return t*t; }; + function B2(t) { return 2*t*(1-t); }; + function B3(t) { return (1-t)*(1-t); }; + var x = curve[0].x*B1(location) + curve[1].x*B2(location) + curve[2].x*B3(location); + var y = curve[0].y*B1(location) + curve[1].y*B2(location) + curve[2].y*B3(location); + return {x:x,y:y}; + }; + + /** + * finds the point that is 'distance' along the path from 'location'. this method returns both the x,y location of the point and also + * its 'location' (proportion of travel along the path); the method below - _pointAlongPathFrom - calls this method and just returns the + * point. + */ + var _pointAlongPath = function(curve, location, distance) { + var _dist = function(p1,p2) { return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2)); }; + var prev = _pointOnPath(curve, location), tally = 0, curLoc = location, direction = distance > 0 ? 1 : -1, cur = null; + while (tally < Math.abs(distance)) { + curLoc += (0.005 * direction); + cur = _pointOnPath(curve, curLoc); + tally += _dist(cur, prev); + prev = cur; + } + return {point:cur, location:curLoc}; + }; + + /** + * finds the point that is 'distance' along the path from 'location'. this method returns both the x,y location of the point and also + * its 'location' (proportion of travel along the path). + */ + var _pointAlongPathFrom = function(curve, location, distance) { + return _pointAlongPath(curve, location, distance).point; + }; + + /** + * returns the gradient of the connector at the given location, which is a decimal between 0 and 1 inclusive. + * + * thanks // http://bimixual.org/AnimationLibrary/beziertangents.html + */ + var _gradientAtPoint = function(curve, location) { + var p1 = _pointOnPath(curve, location); + var p2 = _quadraticPointOnPath(curve, location); + var dy = p2.y - p1.y, dx = p2.x - p1.x; + return Math.atan(dy / dx); + }; + + /** + * calculates a line that is 'length' pixels long, perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * if distance is not supplied, the perpendicular for the given location is computed (ie. we set distance to zero). + */ + var _perpendicularToPathAt = function(curve, location, length, distance) { + distance = distance == null ? 0 : distance; + var p = _pointAlongPath(curve, location, distance); + var m = _gradientAtPoint(curve, p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.point.x + x, y:p.point.y + y}, {x:p.point.x - x, y:p.point.y - y}]; + }; + + var jsBezier = window.jsBezier = { + distanceFromCurve : _distanceFromCurve, + gradientAtPoint : _gradientAtPoint, + nearestPointOnCurve : _nearestPointOnCurve, + pointOnCurve : _pointOnPath, + pointAlongCurveFrom : _pointAlongPathFrom, + perpendicularToCurveAt : _perpendicularToPathAt, + quadraticPointOnCurve : _quadraticPointOnPath //TODO fold the two pointOnPath functions into one; it can detect what it was given. + }; +})(); diff --git a/archive/1.2.5/yui.jsPlumb-1.2.5-RC1.js b/archive/1.2.5/yui.jsPlumb-1.2.5-RC1.js new file mode 100644 index 000000000..734ae1aa8 --- /dev/null +++ b/archive/1.2.5/yui.jsPlumb-1.2.5-RC1.js @@ -0,0 +1,287 @@ + +/* + * yui.jsPlumb 1.2.5-RC1 + * + * YUI3 specific functionality for jsPlumb. + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + * NOTE: for production usage you should use yui-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, + * in a minified file. + * + * Dual licensed under MIT and GPL2. + * + */ + +/** + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setOffset sets the offset of some element. + */ +(function() { + + var Y; + + YUI().use('node', 'dd', 'anim', function(_Y) { + Y = _Y; + }); + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }; + + var ddEvents = [ "drag:mouseDown", "drag:afterMouseDown", "drag:mouseup", + "drag:align", "drag:removeHandle", "drag:addHandle", "drag:removeInvalid", "drag:addInvalid", + "drag:start", "drag:end", "drag:drag", "drag:over", "drag:enter", + "drag:exit", "drag:drophit", "drag:dropmiss", "drop:over", "drop:enter", "drop:exit", "drop:hit" + ]; + + var animEvents = [ "tween" ]; + + /** + * helper function to curry callbacks for some element. + */ + var _wrapper = function(fn) { + return function() { fn.apply(this, arguments); }; + }; + + /** + * extracts options from the given options object, leaving out event handlers. + */ + var _getDDOptions = function(options) { + var o = {}; + for (var i in options) if (ddEvents.indexOf(i) == -1) o[i] = options[i]; + return o; + }; + + /** + * attaches all event handlers found in options to the given dragdrop object, and registering + * the given el as the element of interest. + */ + var _attachListeners = function(dd, options, eventList) { + for (var ev in options) { + if (eventList.indexOf(ev) != -1) { + var w = _wrapper(options[ev]); + dd.on(ev, w); + } + } + }; + + var _droppables = {}; + var _droppableOptions = {}; + var _draggablesByScope = {}; + var _draggablesById = {}; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + var _lastDragObject = null; + + var _extend = function(o1, o2) { + for (var i in o2) + o1[i] = o2[i]; + return o1; + }; + + var _getAttribute = function(el, attributeId) { + return el.getAttribute(attributeId); + }; + + var _getElementObject = function(el) { + return typeof el == 'string' ? Y.one('#' + el) : Y.one(el); + }; + + jsPlumb.CurrentLibrary = { + + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + var o = _extend({node:el, to:properties}, options); + var id = _getAttribute(el, "id"); + o["tween"] = jsPlumb.wrap(properties["tween"], function() { + jsPlumb.repaint(id); + }); + var a = new Y.Anim(o); + _attachListeners(a, o, animEvents); + a.run(); + }, + + appendElement : function(child, parent) { + _getElementObject (parent).append(child); + }, + + /** + * event binding wrapper. + */ + bind : function(el, event, callback) { + _getElementObject(el).on(event, callback); + }, + + dragEvents : { + 'start':'drag:start', 'stop':'drag:end', 'drag':'drag:drag', 'step':'step', + 'over':'drop:enter', 'out':'drop:exit', 'drop':'drop:hit' + }, + + extend : _extend, + + getAttribute : _getAttribute, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + // this is a workaround for the unfortunate fact that in YUI3, the 'drop:exit' event does + // not contain a reference to the drag that just exited. single-threaded js to the + // rescue: we'll just keep it for ourselves. + if (eventArgs[0].drag) _lastDragObject = eventArgs[0].drag.el; + return _lastDragObject; + }, + + getElementObject : _getElementObject, + + getOffset : function(el) { + var bcr = el._node.getBoundingClientRect(); + return { left:bcr.left, top:bcr.top }; + }, + + getScrollLeft : function(el) { + alert("YUI getScrollLeft not implemented yet"); + }, + + getScrollTop : function(el) { + alert("YUI getScrollTop not implemented yet"); + }, + + getSize : function(el) { + //TODO must be a better way to get this? + var bcr = _getElementObject(el)._node.getBoundingClientRect(); + return [ bcr.width, bcr.height ]; + }, + + getUIPosition : function(args) { + //TODO must be a better way to get this? args was passed through from the drag function + // in initDraggable above - args[0] here is the element that was inited. + var bcr = _getElementObject(args[0].currentTarget.el)._node.getBoundingClientRect(); + return { left:bcr.left, top:bcr.top }; + }, + + initDraggable : function(el, options) { + var _opts = _getDDOptions(options); + var id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drag(_opts); + dd.el = el; + + var scope = options['scope'] || jsPlumb.Defaults.Scope; + dd.scope = scope; + + _draggablesById[id] = dd; + _add(_draggablesByScope, scope, dd); + + _attachListeners(dd, options, ddEvents); + }, + + initDroppable : function(el, options) { + var _opts = _getDDOptions(options); + var id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drop(_opts); + + _droppableOptions[id] = options; + + options = _extend({}, options); + var scope = options['scope'] || jsPlumb.Defaults.Scope; + + options["drop:enter"] = jsPlumb.wrap(options["drop:enter"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, true); + }, true); + options["drop:exit"] = jsPlumb.wrap(options["drop:exit"], function(e) { + _checkHover(el, false); + }); + options["drop:hit"] = jsPlumb.wrap(options["drop:hit"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, false); + }, true); + + _attachListeners(dd, options, ddEvents); + }, + + isAlreadyDraggable : function(el) { + el = _getElementObject(el); + return el.hasClass("yui3-dd-draggable"); + }, + + isDragSupported : function(el) { return true; }, + isDropSupported : function(el) { return true; }, + removeClass : function(el, clazz) { el.removeClass(clazz); }, + removeElement : function(el) { _getElementObject(el).remove(); }, + + setAttribute : function(el, attributeName, attributeValue) { + el.setAttribute(attributeName, attributeValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + if (dd) dd.set("lock", !draggable); + }, + + setOffset : function(el, o) { + el = _getElementObject(el); + el.set("top", o.top); + el.set("left", o.left); + } + }; +})(); \ No newline at end of file diff --git a/archive/1.2.5/yui.jsPlumb-1.2.5-all-min.js b/archive/1.2.5/yui.jsPlumb-1.2.5-all-min.js new file mode 100644 index 000000000..13c75ed2f --- /dev/null +++ b/archive/1.2.5/yui.jsPlumb-1.2.5-all-min.js @@ -0,0 +1 @@ +(function(){var a=function(){var ac=function(){var ah={};this.bind=function(ak,al){var ai=function(an,am){B(ah,an,am)};if(ak.constructor==Array){for(var aj=0;aj=0){delete (ah[ai]);ah.splice(ai,1);return true}}}return false};var c=function(ai,ah){return s(ai,function(aj,ak){S[ak]=ah;if(b.CurrentLibrary.isDragSupported(aj)){b.CurrentLibrary.setDraggable(aj,ah)}})};var aa=function(ah,ai){D(q(ah,"id"),function(aj){aj.canvas.style.display=ai})};var w=function(ah){return s(ah,function(aj,ai){var ak=S[ai]==null?I:S[ai];ak=!ak;S[ai]=ak;b.CurrentLibrary.setDraggable(aj,ak);return ak})};var e=function(ah){D(ah,function(aj){var ai=("none"==aj.canvas.style.display);aj.canvas.style.display=ai?"block":"none"})};var m=function(am){var ak=am.timestamp,ah=am.recalc,al=am.offset,ai=am.elId;if(!ah){if(ak&&ak===k[ai]){return}}if(ah||al==null){var aj=U(ai);y[ai]=h(aj);F[ai]=d(aj);k[ai]=ak}else{F[ai]=al}};var Z=function(aj,ah,ai){aj=aj||function(){};ah=ah||function(){};return function(){var ak=null;try{ak=ah.apply(this,arguments)}catch(al){n("jsPlumb function failed : "+al)}if(ai==null||(ak!==ai)){try{aj.apply(this,arguments)}catch(al){n("wrapped function failed : "+al)}}return ak}};var M=function(al){var aj=this;this.x=al.x||0;this.y=al.y||0;var ai=al.orientation||[0,0];var ak=null,ah=null;this.offsets=al.offsets||[0,0];aj.timestamp=null;this.compute=function(ar){var ax=ar.xy,an=ar.wh,at=ar.element,au=ar.timestamp;if(au&&au===aj.timestamp){return ah}ah=[ax[0]+(aj.x*an[0])+aj.offsets[0],ax[1]+(aj.y*an[1])+aj.offsets[1]];var ao=at?at.container:null;var av={left:0,top:0};if(ao!=null){var am=U(ao);var ap=d(am);var aq=b.CurrentLibrary.getScrollLeft(am);var aw=b.CurrentLibrary.getScrollTop(am);av.left=ap.left-aq;av.top=ap.top-aw;ah[0]=ah[0]-av.left;ah[1]=ah[1]-av.top}aj.timestamp=au;return ah};this.getOrientation=function(){return ai};this.equals=function(am){if(!am){return false}var an=am.getOrientation();var ap=this.getOrientation();return this.x==am.x&&this.y==am.y&&this.offsets[0]==am.offsets[0]&&this.offsets[1]==am.offsets[1]&&ap[0]==an[0]&&ap[1]==an[1]};this.getCurrentLocation=function(){return ah}};var r=function(an){var al=an.reference;var am=an.referenceCanvas;var aj=h(U(am));var ai=0,ao=0;var ah=null;var ak=null;this.compute=function(au){var ar=au.xy,aq=au.element;var ap=[ar[0]+(aj[0]/2),ar[1]+(aj[1]/2)];if(aq.container!=null){var at=d(aq.container);ap[0]=ap[0]-at.left;ap[1]=ap[1]-at.top}ak=ap;return ap};this.getOrientation=function(){if(ah){return ah}else{var ap=al.getOrientation();return[Math.abs(ap[0])*ai*-1,Math.abs(ap[1])*ao*-1]}};this.over=function(ap){ah=ap.getOrientation()};this.out=function(){ah=null};this.getCurrentLocation=function(){return ak}};var K=function(aj,ai){this.isSelective=true;this.isDynamic=true;var aq=aj||[];var ao=function(ar){return ar.constructor==M?ar:b.makeAnchor(ar)};for(var an=0;an0?aq[0]:null;var am=aq.length>0?0:-1;this.locked=false;var ap=this;var al=function(au,ar,aA,az,at){var aw=az[0]+(au.x*at[0]),av=az[1]+(au.y*at[1]);return Math.sqrt(Math.pow(ar-aw,2)+Math.pow(aA-av,2))};var ah=ai||function(aC,at,au,av,ar){var ax=au[0]+(av[0]/2),aw=au[1]+(av[1]/2);var az=-1,aB=Infinity;for(var ay=0;ayax){ax=aF}}var aK=this.connector.compute(az,aM,this.endpoints[aQ].anchor,this.endpoints[aw].anchor,this.paintStyle.lineWidth,ax);b.sizeCanvas(aj,aK[0],aK[1],aK[2],aK[3]);var aD=function(aR,aU){aR.save();b.extend(aR,aU);if(aU.gradient&&!R){var aT=at.connector.createGradient(aK,aR,(aE==this.sourceId));for(var aS=0;aS=0){au.connections.splice(aH,1);if(!aK){var aJ=aI.endpoints[0]==au?aI.endpoints[1]:aI.endpoints[0];aJ.detach(aI,true)}O(aI.canvas,aI.container);v(u,aI.scope,aI);if(!aK){N(aI)}}};this.detachAll=function(){while(au.connections.length>0){au.detach(au.connections[0])}};this.detachFrom=function(aI){var aJ=[];for(var aH=0;aH=0){au.connections.splice(aH,1)}};this.getElement=function(){return at};this.getUuid=function(){return ap};this.makeInPlaceCopy=function(){var aH=new ab({anchor:au.anchor,source:at,style:an,endpoint:ar});return aH};this.isConnectedTo=function(aJ){var aI=false;if(aJ){for(var aH=0;aH=aA)};this.setDragAllowedWhenFull=function(aH){au.dragAllowedWhenFull=aH};this.equals=function(aH){return this.anchor.equals(aH.anchor)};this.paint=function(aK){aK=aK||{};var aO=aK.timestamp;if(!aO||au.timestamp!==aO){var aN=aK.anchorPoint,aJ=aK.canvas,aL=aK.connectorPaintStyle;if(aN==null){var aT=aK.offset||F[am];var aH=aK.dimensions||y[am];if(aT==null||aH==null){m({elId:am,timestamp:aO});aT=F[am];aH=y[am]}var aI={xy:[aT.left,aT.top],wh:aH,element:au,timestamp:aO};if(au.anchor.isDynamic){if(au.connections.length>0){var aQ=au.connections[0];var aS=aQ.endpoints[0]==au?1:0;var aM=aS==0?aQ.sourceId:aQ.targetId;var aP=F[aM],aR=y[aM];aI.txy=[aP.left,aP.top];aI.twh=aR;aI.tElement=aQ.endpoints[aS]}}aN=au.anchor.compute(aI)}ar.paint(aN,au.anchor.getOrientation(),aJ||au.canvas,an,aL||an);au.timestamp=aO}};this.removeConnection=this.detach;if(aG.isSource&&b.CurrentLibrary.isDragSupported(at)){var az=null,av=null,ay=null,ah=false,ai=null;var ak=function(){ay=aD();if(au.isFull()&&!au.dragAllowedWhenFull){return false}m({elId:am});aj=au.makeInPlaceCopy();aj.paint();az=document.createElement("div");var aJ=U(az);A(az,au.container);var aK=ag(aJ);m({elId:aK});Q(U(au.canvas),"dragId",aK);Q(U(au.canvas),"elId",am);var aI=new r({reference:au.anchor,referenceCanvas:au.canvas});aE=new ab({style:{fillStyle:"rgba(0,0,0,0)"},endpoint:ar,anchor:aI,source:aJ});if(ay==null){au.anchor.locked=true;ay=new p({sourceEndpoint:au,targetEndpoint:aE,source:U(at),target:U(az),anchors:[au.anchor,aI],paintStyle:aG.connectorStyle,connector:aG.connector,overlays:au.connectorOverlays})}else{ah=true;al(U(aj.canvas));var aH=ay.sourceId==am?0:1;ay.floatingAnchorIndex=aH;au.detachFromConnection(ay);if(aH==0){ai=[ay.source,ay.sourceId];ay.source=U(az);ay.sourceId=aK}else{ai=[ay.target,ay.targetId];ay.target=U(az);ay.targetId=aK}ay.endpoints[aH==0?1:0].anchor.locked=true;ay.suspendedEndpoint=ay.endpoints[aH];ay.endpoints[aH]=aE}J[aK]=ay;aE.addConnection(ay);B(T,aK,aE)};var aB=aG.dragOptions||{};var aw=b.extend({},b.CurrentLibrary.defaultDragOptions);aB=b.extend(aw,aB);aB.scope=aB.scope||au.scope;var ax=b.CurrentLibrary.dragEvents.start;var aF=b.CurrentLibrary.dragEvents.stop;var ao=b.CurrentLibrary.dragEvents.drag;aB[ax]=Z(aB[ax],ak);aB[ao]=Z(aB[ao],function(){var aH=b.CurrentLibrary.getUIPosition(arguments);b.CurrentLibrary.setOffset(az,aH);ae(U(az),aH)});aB[aF]=Z(aB[aF],function(){v(T,av,aE);z([az,aE.canvas],at);O(aj.canvas,at);var aH=ay.floatingAnchorIndex==null?1:ay.floatingAnchorIndex;ay.endpoints[aH==0?1:0].anchor.locked=false;if(ay.endpoints[aH]==aE){if(ah&&ay.suspendedEndpoint){if(aH==0){ay.source=ai[0];ay.sourceId=ai[1]}else{ay.target=ai[0];ay.targetId=ai[1]}ay.endpoints[aH]=ay.suspendedEndpoint;if(aq){ay.floatingAnchorIndex=null;ay.suspendedEndpoint.addConnection(ay);b.repaint(ai[1])}else{ay.endpoints[aH==0?1:0].detach(ay)}}else{O(ay.canvas,au.container);au.detachFromConnection(ay)}}au.anchor.locked=false;au.paint();ay.repaint();ay=null;delete aj;delete T[aE.elementId];delete aE});var aC=U(au.canvas);b.CurrentLibrary.initDraggable(aC,aB)}var al=function(aK){if(aG.isTarget&&b.CurrentLibrary.isDropSupported(at)){var aH=aG.dropOptions||l.Defaults.DropOptions||b.Defaults.DropOptions;aH=b.extend({},aH);aH.scope=aH.scope||au.scope;var aN=null;var aL=b.CurrentLibrary.dragEvents.drop;var aM=b.CurrentLibrary.dragEvents.over;var aI=b.CurrentLibrary.dragEvents.out;var aJ=function(){var aP=U(b.CurrentLibrary.getDragObject(arguments));var aV=q(aP,"dragId");var aR=q(aP,"elId");var aT=J[aV];var aO=aT.floatingAnchorIndex==null?1:aT.floatingAnchorIndex,aS=aO==0?1:0;if(!au.isFull()&&!(aO==0&&!au.isSource)&&!(aO==1&&!au.isTarget)){if(aO==0){aT.source=at;aT.sourceId=am}else{aT.target=at;aT.targetId=am}aT.endpoints[aO].detachFromConnection(aT);if(aT.suspendedEndpoint){aT.suspendedEndpoint.detachFromConnection(aT)}aT.endpoints[aO]=au;au.addConnection(aT);if(!aT.suspendedEndpoint){B(u,aT.scope,aT);x(at,aG.draggable,{})}else{var aU=aT.suspendedEndpoint.getElement(),aQ=aT.suspendedEndpoint.elementId;l.fireUpdate("jsPlumbConnectionDetached",{source:aO==0?aU:aT.source,target:aO==1?aU:aT.target,sourceId:aO==0?aQ:aT.sourceId,targetId:aO==1?aQ:aT.targetId,sourceEndpoint:aO==0?aT.suspendedEndpoint:aT.endpoints[0],targetEndpoint:aO==1?aT.suspendedEndpoint:aT.endpoints[1]})}b.repaint(aR);l.fireUpdate("jsPlumbConnection",{source:aT.source,target:aT.target,sourceId:aT.sourceId,targetId:aT.targetId,sourceEndpoint:aT.endpoints[0],targetEndpoint:aT.endpoints[1]})}delete J[aV]};aH[aL]=Z(aH[aL],aJ);aH[aM]=Z(aH[aM],function(){var aP=b.CurrentLibrary.getDragObject(arguments);var aR=q(U(aP),"dragId");var aQ=J[aR];var aO=aQ.floatingAnchorIndex==null?1:aQ.floatingAnchorIndex;aQ.endpoints[aO].anchor.over(au.anchor)});aH[aI]=Z(aH[aI],function(){var aP=b.CurrentLibrary.getDragObject(arguments);var aR=q(U(aP),"dragId");var aQ=J[aR];var aO=aQ.floatingAnchorIndex==null?1:aQ.floatingAnchorIndex;aQ.endpoints[aO].anchor.out()});b.CurrentLibrary.initDroppable(aK,aH)}};al(U(au.canvas));return au};this.Defaults={Anchor:null,Anchors:[null,null],BackgroundPaintStyle:null,Connector:null,Container:null,DragOptions:{},DropOptions:{},Endpoint:null,Endpoints:[null,null],EndpointStyle:{fillStyle:null},EndpointStyles:[null,null],LabelStyle:{fillStyle:"rgba(0,0,0,0)",color:"black"},LogEnabled:true,MaxConnections:null,PaintStyle:{lineWidth:10,strokeStyle:"red"},Scope:"_jsPlumb_DefaultScope"};this.logEnabled=this.Defaults.LogEnabled;this.connectorClass="_jsPlumb_connector";this.endpointClass="_jsPlumb_endpoint";this.overlayClass="_jsPlumb_overlay";this.Anchors={};this.Connectors={};this.Endpoints={};this.Overlays={};this.addEndpoint=function(am,an){an=b.extend({},an);an.endpoint=an.endpoint||l.Defaults.Endpoint||b.Defaults.Endpoint;an.endpointStyle=an.endpointStyle||l.Defaults.EndpointStyle||b.Defaults.EndpointStyle;var ak=U(am),ao=q(ak,"id");an.source=ak;m({elId:ao});var al=new ab(an);B(T,ao,al);var ah=F[ao],aj=y[ao];var ai=al.anchor.compute({xy:[ah.left,ah.top],wh:aj,element:al});al.paint({anchorLoc:ai});return al};this.addEndpoints=function(ak,ah){var aj=[];for(var ai=0;ai0?P(at,ar)!=-1:true};for(var al in u){if(aj(ao,al)){ah[al]=[];for(var ak=0;ak=4){ai.orientation=[arguments[2],arguments[3]]}if(arguments.length==6){ai.offsets=[arguments[4],arguments[5]]}}var an=new M(ai);an.clone=function(){return new M(ai)};return an};this.makeAnchors=function(ai){var aj=[];for(var ah=0;ah0?1:-1;var v=Math.abs(w*Math.sin(f));if(g>c){v=v*-1}var r=Math.abs(w*Math.cos(f));if(h>d){r=r*-1}return{x:u.x+(t*r),y:u.y+(t*v)}};this.perpendicularToPathAt=function(u,v,A){var w=q.pointAlongPathFrom(u,A);var t=q.gradientAtPoint(w.location);var s=Math.atan(-1/t);var z=v/2*Math.sin(s);var r=v/2*Math.cos(s);return[{x:w.x+r,y:w.y+z},{x:w.x-r,y:w.y-z}]};this.createGradient=function(s,r){return r.createLinearGradient(s[4],s[5],s[6],s[7])}};jsPlumb.Connectors.Bezier=function(c){var q=this;this.majorAnchor=c||150;this.minorAnchor=10;var k=null;this._findControlPoint=function(A,r,v,y,s){var x=y.getOrientation(),z=s.getOrientation();var u=x[0]!=z[0]||x[1]==z[1];var t=[];var B=q.majorAnchor,w=q.minorAnchor;if(!u){if(x[0]==0){t.push(r[0]n){n=w}if(z<0){f+=z;var A=Math.abs(z);n+=A;p[0]+=A;l+=A;d+=A;o[0]+=A}var I=Math.min(g,_ty);var G=Math.min(p[1],o[1]);var v=Math.min(I,G);var B=Math.max(g,_ty);var y=Math.max(p[1],o[1]);var t=Math.max(B,y);if(t>h){h=t}if(v<0){e+=v;var x=Math.abs(v);h+=x;p[1]+=x;g+=x;_ty+=x;o[1]+=x}if(F&&nm){m=p}}return m};this.draw=function(n,m){var p=h(m);if(p.width){var o=n.pointOnPath(d.location);if(d.labelStyle.font){m.font=d.labelStyle.font}if(d.labelStyle.fillStyle){m.fillStyle=d.labelStyle.fillStyle}else{m.fillStyle="rgba(0,0,0,0)"}m.fillRect(o.x-(p.width/2),o.y-(p.height/2),p.width,p.height);if(d.labelStyle.color){m.fillStyle=d.labelStyle.color}m.textBaseline="middle";m.textAlign="center";for(i=0;i0){m.strokeStyle=d.labelStyle.borderStyle||"black";m.strokeRect(o.x-(p.width/2),o.y-(p.height/2),p.width,p.height)}}}};jsPlumb.Overlays.Image=function(e){var l=this;this.location=e.location||0.5;this.img=new Image();var m=null;var f=null;var d,c;var k=e.events||{};var h=function(){if(l.ready){window.clearInterval(f);m=document.createElement("img");m.src=l.img.src;m.style.position="absolute";m.style.display="none";m.className="_jsPlumb_overlay";document.body.appendChild(m);for(var n in k){jsPlumb.CurrentLibrary.bind(m,n,k[n])}if(d&&c){g(d,c);c=null;d=null}}};this.img.onload=function(){l.ready=true};this.img.src=e.src||e.url;f=window.setInterval(h,250);this.computeMaxSize=function(o,n){return[l.img.width,l.img.height]};var g=function(q,p){var s=q.pointOnPath(l.location);var r=jsPlumb.CurrentLibrary.getElementObject(p.canvas);var n=jsPlumb.CurrentLibrary.getOffset(r);var t={left:n.left+s.x-(l.img.width/2),top:n.top+s.y-(l.img.height/2)};jsPlumb.CurrentLibrary.setOffset(m,t);m.style.display="block"};this.draw=function(o,n){if(l.ready){g(o,n)}else{d=o;c=n}}}})();(function(){var c;YUI().use("node","dd","anim",function(s){c=s});var e=function(v,t,u){var s=v[t];if(!s){s=[];v[t]=s}s.push(u)};var g=["drag:mouseDown","drag:afterMouseDown","drag:mouseup","drag:align","drag:removeHandle","drag:addHandle","drag:removeInvalid","drag:addInvalid","drag:start","drag:end","drag:drag","drag:over","drag:enter","drag:exit","drag:drophit","drag:dropmiss","drop:over","drop:enter","drop:exit","drop:hit"];var m=["tween"];var r=function(s){return function(){s.apply(this,arguments)}};var f=function(s){var u={};for(var t in s){if(g.indexOf(t)==-1){u[t]=s[t]}}return u};var n=function(s,u,x){for(var v in u){if(x.indexOf(v)!=-1){var t=r(u[v]);s.on(v,t)}}};var d={};var p={};var l={};var q={};var k=function(t,v){if(t){var u=t.get("id");if(u){var s=p[u];if(s){if(s.hoverClass){if(v){t.addClass(s.hoverClass)}else{t.removeClass(s.hoverClass)}}}}}};var h=null;var b=function(u,t){for(var s in t){u[s]=t[s]}return u};var o=function(s,t){return s.getAttribute(t)};var a=function(s){return typeof s=="string"?c.one("#"+s):c.one(s)};jsPlumb.CurrentLibrary={addClass:function(t,s){t.addClass(s)},animate:function(v,u,t){var w=b({node:v,to:u},t);var x=o(v,"id");w.tween=jsPlumb.wrap(u.tween,function(){jsPlumb.repaint(x)});var s=new c.Anim(w);n(s,w,m);s.run()},appendElement:function(t,s){a(s).append(t)},bind:function(s,t,u){a(s).on(t,u)},dragEvents:{start:"drag:start",stop:"drag:end",drag:"drag:drag",step:"step",over:"drop:enter",out:"drop:exit",drop:"drop:hit"},extend:b,getAttribute:o,getDragObject:function(s){if(s[0].drag){h=s[0].drag.el}return h},getElementObject:a,getOffset:function(s){var t=s._node.getBoundingClientRect();return{left:t.left,top:t.top}},getScrollLeft:function(s){alert("YUI getScrollLeft not implemented yet")},getScrollTop:function(s){alert("YUI getScrollTop not implemented yet")},getSize:function(s){var t=a(s)._node.getBoundingClientRect();return[t.width,t.height]},getUIPosition:function(s){var t=a(s[0].currentTarget.el)._node.getBoundingClientRect();return{left:t.left,top:t.top}},initDraggable:function(w,t){var u=f(t);var x=jsPlumb.getId(w);u.node="#"+x;var s=new c.DD.Drag(u);s.el=w;var v=t.scope||jsPlumb.Defaults.Scope;s.scope=v;q[x]=s;e(l,v,s);n(s,t,g)},initDroppable:function(w,t){var u=f(t);var x=jsPlumb.getId(w);u.node="#"+x;var s=new c.DD.Drop(u);p[x]=t;t=b({},t);var v=t.scope||jsPlumb.Defaults.Scope;t["drop:enter"]=jsPlumb.wrap(t["drop:enter"],function(y){if(y.drag.scope!==v){return true}k(w,true)},true);t["drop:exit"]=jsPlumb.wrap(t["drop:exit"],function(y){k(w,false)});t["drop:hit"]=jsPlumb.wrap(t["drop:hit"],function(y){if(y.drag.scope!==v){return true}k(w,false)},true);n(s,t,g)},isAlreadyDraggable:function(s){s=a(s);return s.hasClass("yui3-dd-draggable")},isDragSupported:function(s){return true},isDropSupported:function(s){return true},removeClass:function(t,s){t.removeClass(s)},removeElement:function(s){a(s).remove()},setAttribute:function(u,s,t){u.setAttribute(s,t)},setDraggable:function(u,t){var v=jsPlumb.getId(u);var s=q[v];if(s){s.set("lock",!t)}},setOffset:function(s,t){s=a(s);s.set("top",t.top);s.set("left",t.left)}}})();(function(){if(typeof Math.sgn=="undefined"){Math.sgn=function(w){return w==0?0:w>0?1:-1}}var h={subtract:function(x,w){return{x:x.x-w.x,y:x.y-w.y}},dotProduct:function(x,w){return(x.x*w.x)+(x.y*w.y)},square:function(w){return Math.sqrt((w.x*w.x)+(w.y*w.y))},scale:function(w,x){return{x:w.x*x,y:w.y*x}}};var o=64,r=Math.pow(2,-o-1),d=3,a=5;var p=function(E,x){var B=new Array(a);var D=e(E,x);var A=q(D,a,B,0);var F=h.subtract(E,x[0]),C=h.square(F),G=0;for(var z=0;z=o){H[0]=(F[0].x+F[a].x)/2;return 1}if(n(F,y)){H[0]=f(F,y);return 1}break}t(F,y,0.5,C,D);B=q(C,y,G,z+1);E=q(D,y,x,z+1);for(A=0;AJ){J=I}else{if(I0?1:-1,E=null;while(z endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + var endpointsByElement = {}; + var endpointsByUUID = {}; + var connectionsByScope = {}; + var offsets = {}; + var offsetTimestamps = {}; + var floatingConnections = {}; + var draggableStates = {}; + var _draggableByDefault = true; + var sizes = []; + var listeners = {}; // a map: keys are event types, values are lists of listeners. + var DEFAULT_SCOPE = 'DEFAULT'; + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + var _findIndex = function(a, v, b, s) { + var _eq = function(o1, o2) { + if (o1 === o2) + return true; + else if (typeof o1 == 'object' && typeof o2 == 'object') { + var same = true; + for ( var propertyName in o1) { + if (!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for ( var propertyName in o2) { + if (!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for ( var i = +b || 0, l = a.length; i < l; i++) { + if (_eq(a[i], v)) + return i; + } + return -1; + }; + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }; + + /** + * appends an element to the given parent, or the document body if no + * parent given. + */ + var _appendElement = function(canvas, parent) { + if (!parent) + document.body.appendChild(canvas); + else + jsPlumb.CurrentLibrary.appendElement(canvas, parent); + }; + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + var _timestamp = function() { return "" + (new Date()).getTime(); }; + + /** + * Draws an endpoint and its connections. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + var _draw = function(element, ui, timestamp) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (!timestamp) timestamp = _timestamp(); + if (endpoints) { + _updateOffset( { elId : id, offset : ui, recalc : false, timestamp : timestamp }); // timestamp is checked against last update cache; it is + // valid for one paint cycle. + var myOffset = offsets[id], myWH = sizes[id]; + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + var l = endpoints[i].connections; + for ( var j = 0; j < l.length; j++) { + l[j].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); // ...paint each connection. + // then, check for dynamic endpoint; need to repaint it. + var oIdx = l[j].endpoints[0] == endpoints[i] ? 1 : 0; + if (l[j].endpoints[oIdx].anchor.isDynamic && !l[j].endpoints[oIdx].isFloating()) { + var oId = oIdx == 0 ? l[j].sourceId : l[j].targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + // TODO i still want to make this faster. + var anchorLoc = l[j].endpoints[oIdx].anchor.compute( { + xy : [ oOffset.left, oOffset.top ], + wh : oWH, + element : l[j].endpoints[oIdx], + txy : [ myOffset.left, myOffset.top ], + twh : myWH, + tElement : endpoints[i] + }); + l[j].endpoints[oIdx].paint({ anchorLoc : anchorLoc }); + } + } + } + } + }; + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + var _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }; + + /** + * + */ + var _log = function(msg) { + if (_currentInstance.logEnabled && typeof console != "undefined") + console.log(msg); + }; + + var _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }; + var _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }; + var _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }; + var _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }; + var _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }; + var _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }; + var _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }; + + /** + * gets an id for the given element, creating and setting one if + * necessary. + */ + var _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else + id = "jsPlumb_" + _timestamp(); + _setAttribute(ele, "id", id); + } + return id; + }; + + /** + * gets an Endpoint by uuid. + */ + var _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }; + + /** + * inits a draggable if it's not already initialised. + */ + var _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element) && !jsPlumb.CurrentLibrary.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + } + } + }; + + /** + * helper to create a canvas. + * + * @param clazz + * optional class name for the canvas. + */ + var _newCanvas = function(clazz, parent, uuid) { + var canvas = document.createElement("canvas"); + _appendElement(canvas, parent); + canvas.style.position = "absolute"; + if (clazz) canvas.className = clazz; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + _getId(canvas, uuid); + if (ie) { + // for IE we have to set a big canvas size. actually you can + // override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas + // you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + var _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }; + /** + * perform an operation on all elements. + */ + var _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }; + /** + * helper to remove an element from the DOM. + */ + var _removeElement = function(element, parent) { + if (element != null) { + if (!parent) { + try { + document.body.removeChild(element); + } catch (e) { + } + } else { + jsPlumb.CurrentLibrary.removeElement(element, parent); + } + } + }; + /** + * helper to remove a list of elements from the DOM. + */ + var _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }; + /** + * helper method to remove an item from a list. + */ + var _removeFromList = function(map, key, value) { + if (key != null) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete (l[i]); + l.splice(i, 1); + return true; + } + } + } + return false; + }; + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }; + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + var _setVisible = function(el, state) { + _operation(_getAttribute(el, "id"), function(jpc) { + jpc.canvas.style.display = state; + }); + }; + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + var _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }; + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + var _toggleVisible = function(elId) { + _operation(elId, function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }; + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we use the + * offset, outerWidth and outerHeight methods to get the current values. + */ + var _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return; + } + if (recalc || offset == null) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } else { + offsets[elId] = offset; + } + }; + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + var _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log('jsPlumb function failed : ' + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log('wrapped function failed : ' + e); + } + } + return r; + }; + }; + + /* + * Class: Anchor Models a position relative to the origin of some + * element that an Endpoint can be located. + */ + /* + * Function: Anchor + * + * Constructor for the Anchor class + * + * Parameters: + * - x : the x location of the anchor as a fraction of the total width. - + * y : the y location of the anchor as a fraction of the total height. - + * orientation : an [x,y] array indicating the general direction a + * connection from the anchor should go in. for more info on this, see + * the documentation, or the docs in jquery-jsPlumb-defaults-XXX.js for + * the default Anchors. - offsets : an [x,y] array of fixed offsets that + * should be applied after the x,y position has been figured out. may be + * null. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + if (timestamp && timestamp === self.timestamp) { + return lastReturnValue; + } + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + var container = element ? element.container : null; + var containerAdjustment = { left : 0, top : 0 }; + if (container != null) { + var eo = _getElementObject(container); + var o = _getOffset(eo); + var sl = jsPlumb.CurrentLibrary.getScrollLeft(eo); + var st = jsPlumb.CurrentLibrary.getScrollTop(eo); + containerAdjustment.left = o.left - sl; + containerAdjustment.top = o.top - st; + lastReturnValue[0] = lastReturnValue[0] - containerAdjustment.left; + lastReturnValue[1] = lastReturnValue[1] - containerAdjustment.top; + } + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. + * + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(_getElementObject(refCanvas)); + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + var orientation = null; + var _lastResult = null; + + this.compute = function(params) { + var xy = params.xy, el = params.element; + var result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + if (el.container != null) { + var o = _getOffset(el.container); + result[0] = result[0] - o.left; + result[1] = result[1] - o.top; + } + _lastResult = result; + return result; + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { orientation = anchor.getOrientation(); }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * Class: DynamicAnchor + * + * An Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements - a useful + * feature for some applications. + * + * the second argument - anchorSelector - is optional, but when provided should be a function + * that + */ + var DynamicAnchor = function(anchors, anchorSelector) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = anchors || []; + var _convert = function(anchor) { return anchor.constructor == Anchor ? anchor: jsPlumb.makeAnchor(anchor); }; + for (var i = 0; i < _anchors.length; i++) _anchors[i] = _convert(_anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null; + var _curIndex = _anchors.length > 0 ? 0 : -1; + this.locked = false; + var self = this; + + // helper method to calculate the distance between the centers of the two elements. + var _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }; + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + var _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + + var pos = _curAnchor.compute(params); + return pos; + }; + + this.getCurrentLocation = function() { + var cl = _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + return cl; + }; + + this.getOrientation = function() { return _curAnchor != null ? _curAnchor.getOrientation() : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + * Class:Connection + * + * The connecting line between two Endpoints. + * + */ + var Connection = function(params) { + + EventGenerator.apply(this); + // ************** get the source and target and register the + // connection. ******************* + var self = this; + var id = new String('_jsplumb_c_' + (new Date()).getTime()); + this.getId = function() { return id; }; + this.container = params.container || _currentInstance.Defaults.Container; // may be null; we will append to the body if so. + // get source and target as jQuery objects + this.source = _getElementObject(params.source); + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. + if (params.sourceEndpoint) this.source = params.sourceEndpoint.getElement(); + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + this.sourceId = _getAttribute(this.source, "id"); + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + this.scope = params.scope; // scope may have been passed in to the connect call. + // if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. init endpoints + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams) { + if (anchorParams) + return jsPlumb.makeAnchor(anchorParams); + }; + var prepareEndpoint = function(existing, index, params, element) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint + || new jsPlumb.Endpoints.Dot(); + if (ep.constructor == String) ep = new jsPlumb.Endpoints[ep](); + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + var a = params.anchors ? params.anchors[index] : _makeAnchor(_currentInstance.Defaults.Anchors[index]) || _makeAnchor(jsPlumb.Defaults.Anchors[index]) || _makeAnchor(_currentInstance.Defaults.Anchor) || _makeAnchor(jsPlumb.Defaults.Anchor) || _makeAnchor("BottomCenter"); + var u = params.uuids ? params.uuids[index] : null; + var e = new Endpoint( { style : es, endpoint : ep, connections : [ self ], uuid : u, anchor : a, source : element, container : self.container }); + self.endpoints[index] = e; + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || _currentInstance.Defaults.Connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + if (this.connector.constructor == String) this.connector = new jsPlumb.Connectors[this.connector](); // lets you use a string as shorthand. + this.paintStyle = this.endpoints[0].connectorStyle || this.endpoints[1].connectorStyle || params.paintStyle || _currentInstance.Defaults.PaintStyle || jsPlumb.Defaults.PaintStyle; + this.backgroundPaintStyle = this.endpoints[0].connectorBackgroundStyle || this.endpoints[1].connectorBackgroundStyle || params.backgroundPaintStyle || _currentInstance.Defaults.BackgroundPaintStyle || jsPlumb.Defaults.BackgroundPaintStyle; + + // init overlays + this.overlays = params.overlays || []; + this.addOverlay = function(overlay) { overlays.push(overlay); }; + + // this is a shortcut helper method to let people add a label as + // overlay. + this.labelStyle = params.labelStyle || _currentInstance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + if (this.label) { + this.overlays.push(new jsPlumb.Overlays.Label( { + labelStyle : this.labelStyle, + label : this.label + })); + } + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + // functions for mouse hover/select functionality + this.distanceFrom = function(point) { return self.connector.distanceFrom(point); }; + + this.setLabel = function(l) { + self.label = l; + _currentInstance.repaint(self.source); + }; + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var otherOffset = offsets[this.targetId]; + otherWH = sizes[this.targetId]; + // todo: why not fold this and the paint call that follows into one + // call? + var anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1] + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0] + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc }); + + var canvas = _newCanvas(jsPlumb.connectorClass, self.container); + this.canvas = canvas; + + /* + * Function: paint + * + * paints the connection. + * + * Parameters: + * elId Id of the element that is in motion + * ui current library's event system ui object (present if we came from a drag to get here) + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp; + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the + // two references. + var swap = false; + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + if (this.canvas.getContext) { + _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }); + _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + // var myOffset = offsets[sId], otherOffset = offsets[tId], myWH = sizes[sId], otherWH = sizes[tId]; + var ctx = canvas.getContext('2d'); + var sAnchorP = this.endpoints[sIdx].anchor.getCurrentLocation(); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.getCurrentLocation(); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + + // paint overlays + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + var s = o.computeMaxSize(self.connector, ctx); + if (s > maxSize) + maxSize = s; + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, this.paintStyle.lineWidth, maxSize); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + + var _paintOneStyle = function(ctx, paintStyle) { + ctx.save(); + jsPlumb.extend(ctx, paintStyle); + if (paintStyle.gradient && !ie) { + var g = self.connector.createGradient(dim, ctx, (elId == this.sourceId)); + for ( var i = 0; i < paintStyle.gradient.stops.length; i++) + g.addColorStop(paintStyle.gradient.stops[i][0], paintStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + self.connector.paint(dim, ctx); + ctx.restore(); + }; + + // first check for the background style + if (this.backgroundPaintStyle != null) { + _paintOneStyle(ctx, this.backgroundPaintStyle); + } + _paintOneStyle(ctx, this.paintStyle); + + // paint overlays + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + o.draw(self.connector, ctx); + } + } + }; + + // TODO: should this take a timestamp? probably. it reduces the + // amount of time + // spent figuring out anchor locations. + this.repaint = function() { + this.paint({ elId : this.sourceId, recalc : true }); + }; + + _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide + // whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + }; + + /* + * Class: Endpoint Models an endpoint. Can have one to N connections + * emanating from it (although how to handle that in the UI is a very + * good question). also has a Canvas and paint style. + */ + + /* + * Function: Endpoint This is the Endpoint class constructor. + * Parameters: anchor - anchor for the endpoint, of type jsPlumb.Anchor. + * may be null. endpoint - endpoint object, of type jsPlumb.Endpoint. + * may be null. style - endpoint style, a js object. may be null. source - + * element the endpoint is attached to, of type jquery object. Required. + * canvas - canvas element to use. may be, and most often is, null. + * connections - optional list of connections to configure the endpoint + * with. isSource - boolean. indicates the endpoint can act as a source + * of new connections. optional. dragOptions - if isSource is set to + * true, you can supply arguments for the jquery draggable method. + * optional. connectionStyle - if isSource is set to true, this is the + * paint style for connections from this endpoint. optional. connector - + * optional connector type to use. isTarget - boolean. indicates the + * endpoint can act as a target of new connections. optional. + * dropOptions - if isTarget is set to true, you can supply arguments + * for the jquery droppable method. optional. reattach - optional + * boolean that determines whether or not the connections reattach after + * they have been dragged off an endpoint and left floating. defaults to + * false: connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + params = params || {}; + var self = this; + var id = new String('_jsplumb_e_' + (new Date()).getTime()); + this.getId = function() { return id; }; + if (params.dynamicAnchors) + self.anchor = new DynamicAnchor(jsPlumb.makeAnchors(params.dynamicAnchors)); + else + self.anchor = params.anchor ? jsPlumb.makeAnchor(params.anchor) : params.anchors ? jsPlumb.makeAnchor(params.anchors) : jsPlumb.makeAnchor("TopCenter"); + var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot(); + if (_endpoint.constructor == String) _endpoint = new jsPlumb.Endpoints[_endpoint](); + self.endpoint = _endpoint; + var _style = params.style || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + this.connectorStyle = params.connectorStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + var _element = params.source, _uuid = params.uuid; + var floatingEndpoint = null, inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + this.container = params.container || _currentInstance.Defaults.Container || jsPlumb.Defaults.Container; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of. + this.canvas = params.canvas || _newCanvas(jsPlumb.endpointClass, this.container, params.uuid); + this.connections = params.connections || []; + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + var _reattach = params.reattach || false; + this.dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + this.addConnection = function(connection) { + self.connections.push(connection); + }; + this.detach = function(connection, ignoreTarget) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + // this avoids a circular loop + if (!ignoreTarget) { + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + t.detach(connection, true); + } + _removeElement(connection.canvas, connection.container); + _removeFromList(connectionsByScope, connection.scope, connection); + if(!ignoreTarget) fireDetachEvent(connection); + } + }; + + /** + * detaches all connections this Endpoint has + */ + this.detachAll = function() { + while (self.connections.length > 0) { + self.detach(self.connections[0]); + } + }; + + /** + * removes any connections from this Endpoint that are connected to + * the given target endpoint. + * + * @param targetEndpoint + */ + this.detachFrom = function(targetEndpoint) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + self.detach(c[i]); + } + }; + + /** + * detach this Endpoint from the Connection, but leave the Connection alive. used when dragging. + */ + this.detachFromConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /** + * returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /** + * returns the UUID for this Endpoint, if there is one. + */ + this.getUuid = function() { + return _uuid; + }; + + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + var e = new Endpoint( { anchor : self.anchor, source : _element, style : _style, endpoint : _endpoint }); + return e; + }; + + /** + * returns whether or not this endpoint is connected to the given + * endpoint. + * + * @param endpoint + * Endpoint to test. + * @since 1.1.1 + * + * todo: needs testing. will this work if the endpoint passed in is + * the source? + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + * + * @returns {Boolean} + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + var connectorSelector = function() { + return (self.connections.length < _maxConnections) ? null : self.connections[0]; + }; + + /** + * @returns whether or not the Endpoint can accept any more + * Connections. + */ + this.isFull = function() { + return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); + }; + + /** + * sets whether or not connnctions can be dragged from this Endpoint + * once it is full. you would use this in a UI in which you're going + * to provide some other way of breaking connections, if you need to + * break them at all. this property is by default true; use it in + * conjunction with the 'reattach' option on a connect call. + */ + this.setDragAllowedWhenFull = function(allowed) { + self.dragAllowedWhenFull = allowed; + }; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + /** + * paints the Endpoint, recalculating offset and anchor positions if + * necessary. + */ + this.paint = function(params) { + + params = params || {}; + var timestamp = params.timestamp; + if (!timestamp || self.timestamp !== timestamp) { + var ap = params.anchorPoint, canvas = params.canvas, connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + // do we always want to force a repaint here? i dont + // think so! + var xy = params.offset || offsets[_elementId]; + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (self.anchor.isDynamic) { + if (self.connections.length > 0) { + var c = self.connections[0]; + var oIdx = c.endpoints[0] == self ? 1 : 0; + var oId = oIdx == 0 ? c.sourceId : c.targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + } + ap = self.anchor.compute(anchorParams); + } + _endpoint.paint(ap, self.anchor.getOrientation(), canvas || self.canvas, _style, connectorPaintStyle || _style); + self.timestamp = timestamp; + } + }; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener + // maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + var start = function() { + jpc = connectorSelector(); + if (self.isFull() && !self.dragAllowedWhenFull) return false; + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + var nE = _getElementObject(n); + _appendElement(n, self.container); // + // create and assign an id, and initialize the offset. + var id = _getId(nE); + _updateOffset( { elId : id }); + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor( { reference : self.anchor, referenceCanvas : self.canvas }); + floatingEndpoint = new Endpoint({ style : { fillStyle : 'rgba(0,0,0,0)' }, endpoint : _endpoint, anchor : floatingAnchor, source : nE }); + + if (jpc == null) { + self.anchor.locked = true; + // create a connection. one end is this endpoint, the + // other is a floating endpoint. + jpc = new Connection( { + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : _getElementObject(_element), + target : _getElementObject(n), + anchors : [ self.anchor, floatingAnchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + connector : params.connector, // this can also be null. Connection will use the default. + overlays : self.connectorOverlays // new in 1.2.4. + }); + } else { + existingJpc = true; + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas)); + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; + // detach from the connection while dragging is occurring. + self.detachFromConnection(jpc); + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId ]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId ]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + // lock the other endpoint; if it is dynamic it will not + // move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new + // floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // TODO unregister on stop? or will floating endpoint's + // destruction be assured. + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be + // dragging the source at any time + // before this connection is either discarded or made into a + // permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + }; + + var dragOptions = params.dragOptions || {}; + var defaultOpts = jsPlumb.extend( {}, jsPlumb.CurrentLibrary.defaultDragOptions); + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + var startEvent = jsPlumb.CurrentLibrary.dragEvents['start']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], + function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + jsPlumb.CurrentLibrary.setOffset(n, _ui); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements( [ n, floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (_reattach) { + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } else { + jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc); // the main endpoint will inform the floating endpoint + // to disconnect, and also post the detached event. + } + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElement(jpc.canvas, self.container); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint(); + jpc.repaint(); + jpc = null; + delete inPlaceCopy; + delete endpointsByElement[floatingEndpoint.elementId]; + delete floatingEndpoint; + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas) { + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + var drop = function() { + var draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)); + var id = _getAttribute(draggable, "dragId"); + var elId = _getAttribute(draggable, "elId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // todo test that the target is not full. + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + if (!jpc.suspendedEndpoint) { // if a new connection, add it. TODO: move this to a jsPlumb internal method - addConnection or something. doesnt need to be exposed. + _addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + _currentInstance.fireUpdate("jsPlumbConnectionDetached", { + source : idx == 0 ? suspendedElement : jpc.source, target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1] + }); + } + + jsPlumb.repaint(elId); + + _currentInstance.fireUpdate("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + } + + delete floatingConnections[id]; + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute( _getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + }); + + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas)); + + return self; + }; + + /* + * Property: Defaults These are the default settings for jsPlumb, that + * is what will be used if you do not supply specific pieces of + * information to the various API calls. A convenient way to implement + * your own look and feel can be to override these defaults by including + * a script somewhere after the jsPlumb include, but before you make any + * calls to jsPlumb, for instance in this example we set the PaintStyle + * to be a blue line of 27 pixels: > jsPlumb.Defaults.PaintStyle = { + * lineWidth:27, strokeStyle:'blue' } + */ + this.Defaults = { + Anchor : null, + Anchors : [ null, null ], + BackgroundPaintStyle : null, + Connector : null, + Container : null, + DragOptions : {}, + DropOptions : {}, + Endpoint : null, + Endpoints : [ null, null ], + EndpointStyle : { + fillStyle : null + }, + EndpointStyles : [ null, null ], + LabelStyle : { + fillStyle : "rgba(0,0,0,0)", + color : "black" + }, + LogEnabled : true, + MaxConnections : null, + // TODO: should we have OverlayStyle too? + PaintStyle : { + lineWidth : 10, + strokeStyle : 'red' + }, + Scope : "_jsPlumb_DefaultScope" + }; + + this.logEnabled = this.Defaults.LogEnabled; + + /* + * Property: connectorClass The CSS class to set on Connection canvas + * elements. This value is a String and can have multiple classes; the + * entire String is appended as-is. + */ + this.connectorClass = '_jsPlumb_connector'; + + /* + * Property: endpointClass The CSS class to set on Endpoint canvas + * elements. This value is a String and can have multiple classes; the + * entire String is appended as-is. + */ + this.endpointClass = '_jsPlumb_endpoint'; + + /* + * Property: overlayClass The CSS class to set on an Overlay that is an + * HTML element. This value is a String and can have multiple classes; + * the entire String is appended as-is. + */ + this.overlayClass = '_jsPlumb_overlay'; + + /* + * Property: Anchors Default jsPlumb Anchors. These are supplied in the + * file jsPlumb-defaults-x.x.x.js, which is merged in with the main + * jsPlumb script to form .jsPlumb-all-x.x.x.js. You can + * provide your own Anchors by supplying them in a script that is loaded + * after jsPlumb, for instance: > jsPlumb.Anchors.MyAnchor = { + * ....anchor code here. see the documentation. } + */ + this.Anchors = {}; + + /* + * Property: Connectors Default jsPlumb Connectors. These are supplied + * in the file jsPlumb-defaults-x.x.x.js, which is merged in with the + * main jsPlumb script to form .jsPlumb-all-x.x.x.js. You can + * provide your own Connectors by supplying them in a script that is + * loaded after jsPlumb, for instance: > jsPlumb.Connectors.MyConnector = { + * ....connector code here. see the documentation. } + */ + this.Connectors = {}; + + /* + * Property: Endpoints Default jsPlumb Endpoints. These are supplied in + * the file jsPlumb-defaults-x.x.x.js, which is merged in with the main + * jsPlumb script to form .jsPlumb-all-x.x.x.js. You can + * provide your own Endpoints by supplying them in a script that is + * loaded after jsPlumb, for instance: > jsPlumb.Endpoints.MyEndpoint = { + * ....endpoint code here. see the documentation. } + */ + this.Endpoints = {}; + + /* + * Property:Overlays Default jsPlumb Overlays such as Arrows and Labels. + * These are supplied in the file jsPlumb-defaults-x.x.x.js, which is + * merged in with the main jsPlumb script to form + * .jsPlumb-all-x.x.x.js. You can provide your own Overlays by + * supplying them in a script that is loaded after jsPlumb, for + * instance: > jsPlumb.Overlays.MyOverlay = { ....overlay code here. see + * the documentation. } + */ + this.Overlays = {}; + + /* + * Function: addEndpoint Adds an Endpoint to a given element. + * Parameters: target - Element to add the endpoint to. either an + * element id, or a selector representing some element. params - Object + * containing Endpoint options (more info required) Returns: The newly + * created Endpoint. See Also: + */ + this.addEndpoint = function(target, params) { + params = jsPlumb.extend( {}, params); + params.endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + params.endpointStyle = params.endpointStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + var el = _getElementObject(target), id = _getAttribute(el, "id"); + params.source = el; + _updateOffset({ elId : id }); + var e = new Endpoint(params); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + return e; + }; + + /* + * Function: addEndpoint Adds a list of Endpoints to a given element. + * Parameters: target - element to add the endpoint to. either an + * element id, or a selector representing some element. endpoints - List + * of objects containing Endpoint options. one Endpoint is created for + * each entry in this list. Returns: List of newly created Endpoints, + * one for each entry in the 'endpoints' argument. See Also: + * + */ + this.addEndpoints = function(target, endpoints) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + results.push(_currentInstance.addEndpoint(target, endpoints[i])); + } + return results; + }; + + /* + * Function: animate Wrapper around supporting library's animate + * function; injects a call to jsPlumb in the 'step' function (creating + * it if necessary). This only supports the two-arg version of the + * animate call in jQuery - the one that takes an 'options' object as + * the second arg. MooTools has only one method - a two arg one. Which + * is handy. Parameters: el - Element to animate. Either an id, or a + * selector representing the element. properties - The 'properties' + * argument you want passed to the standard jQuery animate call. options - + * The 'options' argument you want passed to the standard jQuery animate + * call. Returns: void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // you know, probably onComplete should repaint too. that will help + // keep up + // with fast animations. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /* + * Function: connect Establishes a connection between two elements. + * Parameters: params - Object containing setup for the connection. see + * documentation. Returns: The newly created Connection. + */ + this.connect = function(params) { + var _p = jsPlumb.extend( {}, params); + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not + // full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log("could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log("could not add connection; target endpoint is full"); + return; + } + + // dynamic anchors + if (_p.dynamicAnchors) { + // these can either be an array of anchor coords, which we will use for both source and target, or an object with {source:[anchors], target:[anchors]}, in which + // case we will use a different set for each element. + var a = _p.dynamicAnchors.constructor == Array; + var sa = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.source)); + var ta = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.target)); + _p.anchors = [sa,ta]; + } + + var jpc = new Connection(_p); + // add to list of connections (by scope). + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + _currentInstance.fireUpdate("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + // force a paint + _draw(jpc.source); + + return jpc; + }; + + /* + Function: deleteEndpoint + + deletes an endpoint and removes all connections it has (which removes the connections from the other endpoints involved too) + + You can call this with either a string uuid, or an Endpoint + + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElement(endpoint.canvas, endpoint.container); + // remove from endpointsbyElement + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + delete endpoint; + } + }; + + /* + * Function: deleteEveryEndpoint + * + * Deletes every Endpoint in this instance + * of jsPlumb. Use this instead of removeEveryEndpoint now. + * + * Returns: void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc) { + _currentInstance.fireUpdate("jsPlumbConnectionDetached", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + }; + + /* + * Function: + * detach Removes a connection. takes either (source, target) (the old way, maintained for backwards compatibility), or a params + * object with various possible values. + * + * + * Parameters: + * source - id or element object of the first element in the connection. + * target - id or element object of the second element in the connection. + * + * OR + * + * params: + * { + * + * Returns: true if successful, false if not. + */ + this.detach = function(source, target) { + if (arguments.length == 2) { + var s = _getElementObject(source), sId = _getId(s); + var t = _getElementObject(target), tId = _getId(t); + _operation(sId, function(jpc) { + if ((jpc.sourceId == sId && jpc.targetId == tId) || (jpc.targetId == sId && jpc.sourceId == tId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + fireDetachEvent(jpc); + } + }); + } + // this is the new version of the method, taking a JS object like + // the connect method does. + else if (arguments.length == 1) { + if (arguments[0].constructor == Connection) { + arguments[0].endpoints[0].detachFrom(arguments[0].endpoints[1]); + fireDetachEvent(arguments[0]); + } + else if (arguments[0].connection) { + arguments[0].connection.endpoints[0].detachFrom(arguments[0].connection.endpoints[1]); + fireDetachEvent(arguments[0].connection); + } + else { + var _p = jsPlumb.extend( {}, source); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1])); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + sourceId = _getId(_p.source); + targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + fireDetachEvent(jpc); + } + }); + } + } + } + }; + + /* + * Function: detachAll Removes all an element's connections. Parameters: + * el - either the id of the element, or a selector for the element. + * Returns: void + */ + this.detachAllConnections = function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + }; + + /** + * @deprecated Use detachAllConnections instead. this will be removed in jsPlumb 1.3. + */ + this.detachAll = this.detachAllConnections; + + /* + * Function: detachEverything Remove all Connections from all elements, + * but leaves Endpoints in place. Returns: void See Also: + * + */ + this.detachEveryConnection = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + /** + * @deprecated use detachEveryConnection instead. this will be removed in jsPlumb 1.3. + */ + this.detachEverything = this.detachEveryConnection; + + /* + * Function: draggable initialises the draggability of some element or + * elements. Parameters: el - either an element id, a list of element + * ids, or a selector. options - options to pass through to the + * underlying library Returns: void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + * Function: extend Wraps the underlying library's extend functionality. + * Parameters: o1 - object to extend o2 - object to extend o1 with + * Returns: o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getConnections Gets all or a subset of connections + * currently managed by this jsPlumb instance. Parameters: options - a + * JS object that holds options defining what sort of connections you're + * looking for. Valid values are: scope this may be a String or a list + * of Strings. jsPlumb will only return Connections whose scope matches + * what this option defines. If you omit it, you will be given + * Connections of every scope. source may be a string or a list of + * strings; constraints results to have only Connections whose source is + * this object. target may be a string or a list of strings; constraints + * results to have only Connections whose target is this object. + * + * The return value is a dictionary in this format: + * { 'scope1': [ {sourceId:'window1', targetId:'window2', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:}, + * {sourceId:'window3', targetId:'window4', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:}, + * {sourceId:'window1', targetId:'window3', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:} ], + * 'scope2': [ {sourceId:'window1', targetId:'window3', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:} ] } + * + */ + this.getConnections = function(options) { + var r = {}; + options = options || {}; + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') + r.push(input); + else + r = input; + } + return r; + }; + var scopes = prepareList(options.scope); + var sources = prepareList(options.source); + var targets = prepareList(options.target); + var filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + r[i] = []; + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + r[i].push({ sourceId : c.sourceId, targetId : c.targetId, source : c.source, target : c.target, sourceEndpoint : c.endpoints[0], targetEndpoint : c.endpoints[1], connection : c }); + } + } + } + return r; + }; + + /* + * Function: getDefaultScope Gets the default scope for connections and + * endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + * Function: getEndpoint gets an Endpoint by UUID Parameters: uuid - the + * UUID for the Endpoint Returns: Endpoint with the given UUID, null if + * nothing found. + */ + this.getEndpoint = _getEndpoint; + + /* + * gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + + /* + * Function: hide Sets an element's connections to be hidden. + * Parameters: el - either the id of the element, or a selector for the + * element. Returns: void + */ + this.hide = function(el) { + _setVisible(el, "none"); + }; + + /* + * Function: makeAnchor Creates an anchor with the given params. + * + * You do not need to use this method. It is exposed because of the way jsPlumb is + * split into three scripts; this will change in the future. + * + * Parameters: x - the x location of the anchor as a fraction of the + * total width. y - the y location of the anchor as a fraction of the + * total height. xOrientation - value indicating the general direction a + * connection from the anchor should go in, in the x direction. + * yOrientation - value indicating the general direction a connection + * from the anchor should go in, in the y direction. xOffset - a fixed + * offset that should be applied in the x direction that should be + * applied after the x position has been figured out. optional. defaults + * to 0. yOffset - a fixed offset that should be applied in the y + * direction that should be applied after the y position has been + * figured out. optional. defaults to 0. + * -- OR -- + * + * params - {x:..., y:..., xOrientation etc } + * -- OR FROM 1.2.4 --- + * + * name - the name of some Anchor in the _currentInstance.Anchors array. + * -- OR FROM 1.2.4 --- + * + * coords - a list of coords for the anchor, like you would pass to + * jsPlumb.makeAnchor (eg [0.5,0.5,0,-1] - an anchor in the center of + * some element, oriented towards the top of the screen) + * -- OR FROM 1.2.4 --- + * + * anchor - an existing anchor. just gets passed back. it's handy + * internally to have this functionality. + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed + // in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards + // compatibility if we are given only one value we assume it's a + // call in the old form. + if (arguments.length == 0) return null; + var params = {}; + if (arguments.length == 1) { + var specimen = arguments[0]; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; + // is it the name of an anchor type? + else if (typeof specimen == "string") return _currentInstance.Anchors[arguments[0]](); + // is it an array of coordinates? + else if (specimen.constructor == Array) { + if (specimen[0].constructor == Array || specimen[0].constructor == String) + return new DynamicAnchor(specimen); + else + return jsPlumb.makeAnchor.apply(this, specimen); + } + // last we try the backwards compatibility stuff. + else if (typeof arguments[0] == "object") jsPlumb.extend(params, x); + } else { + params = { x : x, y : y }; + if (arguments.length >= 4) params.orientation = [ arguments[2], arguments[3] ]; + if (arguments.length == 6) params.offsets = [ arguments[4], arguments[5] ]; + } + var a = new Anchor(params); + a.clone = function() { + return new Anchor(params); + }; + return a; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types) { + var r = []; + for ( var i = 0; i < types.length; i++) + if (typeof types[i] == "string") + r.push(_currentInstance.Anchors[types[i]]()); + else if (types[i].constructor == Array) + r.push(jsPlumb.makeAnchor(types[i])); + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /* + * Function: repaint Repaints an element and its connections. This + * method gets new sizes for the elements before painting anything. + * Parameters: el - either the id of the element or a selector + * representing the element. Returns: void See Also: + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + * Function: repaintEverything Repaints all connections. + * Returns: void + * See Also: + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + * Function: removeAllEndpoints + * Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + * Parameters: el - either an element id, or a selector for an element. + * Returns: void + * See Also: + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + for ( var i in ebe) + _currentInstance.deleteEndpoint(ebe[i]); + endpointsByElement[elId] = []; + }; + + /* + * Function: removeEveryEndpoint + * Removes every Endpoint in this instance + * of jsPlumb. + * + * Returns: void + * + * See Also: + * + * @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /** + * Function: removeEndpoint Removes the given Endpoint from the given + * element. Parameters: el - either an element id, or a selector for an + * element. endpoint - Endpoint to remove. this is an Endpoint object, + * such as would have been returned from a call to addEndpoint. Returns: + * void See Also: + * + * @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + /** + * Function:reset + * removes all endpoints and connections and clears the + * listener list. to keep listeners just call jsPlumb.deleteEveryEndpoint. + */ + this.reset = function() { + this.deleteEveryEndpoint(); + this.clearListeners(); + }; + + /* + * Function: setAutomaticRepaint Sets/unsets automatic repaint on window + * resize. Parameters: value - whether or not to automatically repaint + * when the window is resized. Returns: void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + * Function: setDefaultNewCanvasSize Sets the default size jsPlumb will + * use for a new canvas (we create a square canvas so one value is all + * that is required). This is a hack for IE, because ExplorerCanvas + * seems to need for a canvas to be larger than what you are going to + * draw on it at initialisation time. The default value of this is 1200 + * pixels, which is quite large, but if for some reason you're drawing + * connectors that are bigger, you should adjust this value + * appropriately. Parameters: size - The default size to use. jsPlumb + * will use a square canvas so you need only supply one value. Returns: + * void + */ + this.setDefaultNewCanvasSize = function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }; + + /* + * Function: setDefaultScope Sets the default scope for connections and + * endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable Sets whether or not a given element is + * draggable, regardless of what any plumb command may request. + * Parameters: el - either the id for the element, or a selector + * representing the element. Returns: void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setDraggableByDefault Sets whether or not elements are + * draggable by default. Default for this is true. Parameters: draggable - + * value to set Returns: void + */ + this.setDraggableByDefault = function(draggable) { + _draggableByDefault = draggable; + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction Sets the function to fire when the + * window size has changed and a repaint was fired. Parameters: f - + * Function to execute. Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: show Sets an element's connections to be visible. + * Parameters: el - either the id of the element, or a selector for the + * element. Returns: void + */ + this.show = function(el) { + _setVisible(el, "block"); + }; + + /* + * Function: sizeCanvas Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * Parameters: x - [int] x position for the Canvas origin y - [int] y + * position for the Canvas origin w - [int] width of the canvas h - + * [int] height of the canvas Returns: void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible Toggles visibility of an element's + * connections. Parameters: el - either the element's id, or a selector + * representing the element. Returns: void, but should be updated to + * return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable Toggles draggability (sic) of an element's + * connections. Parameters: el - either the element's id, or a selector + * representing the element. Returns: The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload Unloads jsPlumb, deleting all storage. You should + * call this from an onunload attribute on the element Returns: + * void + */ + this.unload = function() { + delete endpointsByElement; + delete endpointsByUUID; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + }; + + /* + * Function: wrap + * + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + + EventGenerator.apply(this); + this.addListener = this.bind; + + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(); + if (_defaults) jsPlumb.extend(j.Defaults, _defaults); + return j; + }; +})(); +/* +* jsPlumb-defaults-1.2.4-RC1 +* +* This script contains the default Anchors, Endpoints, Connectors and Overlays for jsPlumb. It should be used with jsPlumb 1.1.0 and above; +* prior to version 1.1.0 of jsPlumb the defaults were included inside the main script. +* +* NOTE: for production usage you should use jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, +* in a minified file. +* +* Dual licensed under MIT and GPL2. +*/ + +(function() { + + var ie = !!!document.createElement('canvas').getContext; + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * getOrientation : function() { return [ox, oy]; } + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + var _curryAnchor = function(x,y,ox,oy) { + return function() { + return jsPlumb.makeAnchor(x,y,ox,oy); + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1); + + + jsPlumb.Defaults.DynamicAnchors = function() { + return jsPlumb.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"]); + }; + jsPlumb.Anchors["AutoDefault"] = function() { return jsPlumb.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors()); }; + + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + var currentPoints = null; + var _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + + _dx = _tx - _sx, _dy = (_ty - _sy); + _m = _dy / _dx, _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + + return currentPoints; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + var xp = _sx + (location * _dx); + var yp = _m == Infinity ? xp + _b : (_m * xp) + _b; + return {x:xp, y:yp}; + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { return _m; }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + var orientation = distance > 0 ? 1 : -1; + var y = Math.abs(distance * Math.sin(_theta)); + if (_sy > _ty) y = y * -1; + var x = Math.abs(distance * Math.cos(_theta)); + if (_sx > _tx) x = x * -1; + return {x:p.x + (orientation * x), y:p.y + (orientation * y)}; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = self.gradientAtPoint(p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + /** + * This Connector draws a Bezier curve with two control points. + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + jsPlumb.Connectors.Bezier = function(curviness) { + var self = this; + this.majorAnchor = curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _sx, _sy, _canvasX, _canvasY, _w, _h; + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) + { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx); var minx2 = Math.min(_CP[0], _CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(_sx,_tx); var maxx2 = Math.max(_CP[0], _CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty); var miny2 = Math.min(_CP[1], _CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(_sy,_ty); var maxy2 = Math.max(_CP[1], _CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + this.paint = function(d, ctx) { + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the distance the given point is from the curve. not enabled for 1.2.3. didnt make the cut. next time. + */ + this.distanceFrom = function(point) { + var curve = [ {x:currentPoints[4], y:currentPoints[5]}, + {x:currentPoints[8], y:currentPoints[9]}, + {x:currentPoints[10], y:currentPoints[11]}, + {x:currentPoints[6], y:currentPoints[7]}]; + return (jsBezier.distanceFromCurve(point, curve)); + }; + + this.nearestPointTo = function(point) { + var curve = [ {x:currentPoints[4], y:currentPoints[5]}, + {x:currentPoints[8], y:currentPoints[9]}, + {x:currentPoints[10], y:currentPoints[11]}, + {x:currentPoints[6], y:currentPoints[7]}]; + return (jsBezier.nearestPointOnCurve(point, curve)); + + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + return jsBezier.perpendicularToCurveAt(_makeCurve(), location, length, distance); + }; + + this.createGradient = function(dim, ctx, swap) { + return (swap) ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + + /** + * Types of endpoint UIs. we supply four - a circle of default radius 10px, a rectangle of + * default size 20x20, an image (with no default), and a Triangle, of default size 15. + * you can supply others of these if you want to - see the documentation for a howto. + */ + + /** + * a round endpoint, with default radius 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + } + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + if (endpointStyle.gradient && !ie) { + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * A Rectangular endpoint, with default size 20x20. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + jsPlumb.Endpoints.Triangle = function(params) { + + params = params || { width:15, height:15 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - width/2; + var y = anchorPoint[1] - height/2; + + jsPlumb.sizeCanvas(canvas, x, y, width, height); + + var ctx = canvas.getContext('2d'); + var offsetX = 0, offsetY = 0, angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = endpointStyle.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' or, since 1.2.4, a 'src' property in the params object.. + */ + jsPlumb.Endpoints.Image = function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.src || params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + }; + + /** + * An arrow overlay. you can provide: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + */ + jsPlumb.Overlays.Arrow = function(params) { + params = params || {}; + var self = this; + var length = params.length || 20; + var width = params.width || 20; + var fillStyle = params.fillStyle || "black"; + var strokeStyle = params.strokeStyle; + var lineWidth = params.lineWidth || 1; + this.loc = params.location || 0.5; + // how far along the arrow the lines folding back in come to. default is 62.3%. + var foldback = params.foldback || 0.623; + var _getFoldBackPoint = function(connector, loc) { + if (foldback == 0.5) return connector.pointOnPath(loc); + else { + var adj = 0.5 - foldback; // we calculate relative to the center + return connector.pointAlongPathFrom(loc, length * adj); + } + }; + + this.computeMaxSize = function() { return width * 1.5; } + + this.draw = function(connector, ctx) { + // this is the arrow head position + var hxy = connector.pointAlongPathFrom(self.loc, length / 2); + // this is the center of the tail + var txy = connector.pointAlongPathFrom(self.loc, -length / 2), tx = txy.x, ty = txy.y; + // this is the tail vector + var tail = connector.perpendicularToPathAt(self.loc, width, -length / 2); + // this is the point the tail goes in to + var cxy = _getFoldBackPoint(connector, self.loc); + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(hxy.x, hxy.y); + ctx.lineTo(tail[0].x, tail[0].y); + ctx.lineTo(cxy.x, cxy.y); + ctx.lineTo(tail[1].x, tail[1].y); + ctx.lineTo(hxy.x, hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + + /** + * a basic arrow. this is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. See Arrow for params. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + /** + * a diamond. like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. See Arrow for params. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40; + var p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + /** + * A Label overlay. Params you can provide: + * + * labelStyle - js object containing style instructions for the label. defaults to jsPlumb.Defaults.LabelStyle. + * label - the label to paint. may be a string or a function that returns a string. nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * borderWidth - width of a border to paint. defaults to zero. + * borderStyle - strokeStyle to use when painting the border, if necessary. + */ + jsPlumb.Overlays.Label = function(params) { + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + var self = this; + var labelWidth = null, labelHeight = null, labelText = null, labelPadding = null; + this.location = params.location || 0.5; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var _textDimensions = function(ctx) { + if (self.cachedDimensions) return self.cachedDimensions; // return cached copy if we can. if we add a setLabel function remember to clear the cache. + labelText = typeof self.label == 'function' ? self.label(self) : self.label; + var d = {}; + if (labelText) { + var lines = labelText.split(/\n|\r\n/); + ctx.save(); + if (self.labelStyle.font) ctx.font = self.labelStyle.font; + var t = _widestLine(lines, ctx); + // a fake text height measurement: use the width of upper case M + var h = ctx.measureText("M").width; + labelPadding = self.labelStyle.padding || 0.25; + labelWidth = t + (2 * t * labelPadding); + labelHeight = (lines.length * h) + (2 * h * labelPadding); + var textHeight = lines.length * h; + ctx.restore(); + d = {width:labelWidth, height:labelHeight, lines:lines, oneLine:h, padding:labelPadding, textHeight:textHeight}; + } + if (typeof self.label != 'function') self.cachedDimensions = d; // cache it if we can. + return d; + }; + this.computeMaxSize = function(connector, ctx) { + var td = _textDimensions(ctx); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + var _widestLine = function(lines, ctx) { + var max = 0; + for (var i = 0; i < lines.length; i++) { + var t = ctx.measureText(lines[i]).width; + if (t > max) max = t; + } + return max; + }; + + this.draw = function(connector, ctx) { + var td = _textDimensions(ctx); + if (td.width) { + var cxy = connector.pointOnPath(self.location); + if (self.labelStyle.font) ctx.font = self.labelStyle.font; + if (self.labelStyle.fillStyle) + ctx.fillStyle = self.labelStyle.fillStyle; + else + ctx.fillStyle = "rgba(0,0,0,0)"; + ctx.fillRect(cxy.x - (td.width / 2), cxy.y - (td.height / 2) , td.width , td.height ); + + if (self.labelStyle.color) ctx.fillStyle = self.labelStyle.color; + ctx.textBaseline = "middle"; + ctx.textAlign = "center"; + for (i = 0; i < td.lines.length; i++) { + ctx.fillText(td.lines[i],cxy.x, cxy.y - (td.textHeight / 2) + (td.oneLine/2) + (i*td.oneLine)); + } + + // border + if (self.labelStyle.borderWidth > 0) { + ctx.strokeStyle = self.labelStyle.borderStyle || "black"; + ctx.strokeRect(cxy.x - (td.width / 2), cxy.y - (td.height / 2) , td.width , td.height ); + } + } + }; + }; + + /** + * an image overlay. params may contain: + * + * location : proportion along the connector to draw the image. optional. + * src : image src. required. + * events : map of event names to functions; each event listener will be bound to the img. + */ + jsPlumb.Overlays.Image = function(params) { + var self = this; + this.location = params.location || 0.5; + this.img = new Image(); + var imgDiv = null; + var notReadyInterval = null; + var notReadyConnector, notReadyContext; + var events = params.events || {}; + var _init = function() { + if (self.ready) { + window.clearInterval(notReadyInterval); + imgDiv = document.createElement("img"); + imgDiv.src = self.img.src; + imgDiv.style.position = "absolute"; + imgDiv.style.display="none"; + imgDiv.className = "_jsPlumb_overlay"; + document.body.appendChild(imgDiv);// HMM + // attach events + for (var e in events) { + jsPlumb.CurrentLibrary.bind(imgDiv, e, events[e]); + } + if (notReadyConnector && notReadyContext) { + _draw(notReadyConnector, notReadyContext); + notReadyContext = null; + notReadyConnector = null; + } + } + }; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.src || params.url; + + notReadyInterval = window.setInterval(_init, 250); + + this.computeMaxSize = function(connector, ctx) { + return [self.img.width, self.img.height] + }; + + var _draw = function(connector, ctx) { + var cxy = connector.pointOnPath(self.location); + var canvas = jsPlumb.CurrentLibrary.getElementObject(ctx.canvas); + var canvasOffset = jsPlumb.CurrentLibrary.getOffset(canvas); + var o = {left:canvasOffset.left + cxy.x - (self.img.width/2), top:canvasOffset.top + cxy.y - (self.img.height/2)}; + jsPlumb.CurrentLibrary.setOffset(imgDiv, o); + imgDiv.style.display = "block"; + }; + + this.draw = function(connector, ctx) { + if (self.ready) + _draw(connector, ctx); + else { + notReadyConnector = connector; + notReadyContext = ctx; + } + }; + }; +})(); +/* + * yui.jsPlumb 1.2.5-RC1 + * + * YUI3 specific functionality for jsPlumb. + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + * NOTE: for production usage you should use yui-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, + * in a minified file. + * + * Dual licensed under MIT and GPL2. + * + */ + +/** + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setOffset sets the offset of some element. + */ +(function() { + + var Y; + + YUI().use('node', 'dd', 'anim', function(_Y) { + Y = _Y; + }); + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }; + + var ddEvents = [ "drag:mouseDown", "drag:afterMouseDown", "drag:mouseup", + "drag:align", "drag:removeHandle", "drag:addHandle", "drag:removeInvalid", "drag:addInvalid", + "drag:start", "drag:end", "drag:drag", "drag:over", "drag:enter", + "drag:exit", "drag:drophit", "drag:dropmiss", "drop:over", "drop:enter", "drop:exit", "drop:hit" + ]; + + var animEvents = [ "tween" ]; + + /** + * helper function to curry callbacks for some element. + */ + var _wrapper = function(fn) { + return function() { fn.apply(this, arguments); }; + }; + + /** + * extracts options from the given options object, leaving out event handlers. + */ + var _getDDOptions = function(options) { + var o = {}; + for (var i in options) if (ddEvents.indexOf(i) == -1) o[i] = options[i]; + return o; + }; + + /** + * attaches all event handlers found in options to the given dragdrop object, and registering + * the given el as the element of interest. + */ + var _attachListeners = function(dd, options, eventList) { + for (var ev in options) { + if (eventList.indexOf(ev) != -1) { + var w = _wrapper(options[ev]); + dd.on(ev, w); + } + } + }; + + var _droppables = {}; + var _droppableOptions = {}; + var _draggablesByScope = {}; + var _draggablesById = {}; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + var _lastDragObject = null; + + var _extend = function(o1, o2) { + for (var i in o2) + o1[i] = o2[i]; + return o1; + }; + + var _getAttribute = function(el, attributeId) { + return el.getAttribute(attributeId); + }; + + var _getElementObject = function(el) { + return typeof el == 'string' ? Y.one('#' + el) : Y.one(el); + }; + + jsPlumb.CurrentLibrary = { + + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + var o = _extend({node:el, to:properties}, options); + var id = _getAttribute(el, "id"); + o["tween"] = jsPlumb.wrap(properties["tween"], function() { + jsPlumb.repaint(id); + }); + var a = new Y.Anim(o); + _attachListeners(a, o, animEvents); + a.run(); + }, + + appendElement : function(child, parent) { + _getElementObject (parent).append(child); + }, + + /** + * event binding wrapper. + */ + bind : function(el, event, callback) { + _getElementObject(el).on(event, callback); + }, + + dragEvents : { + 'start':'drag:start', 'stop':'drag:end', 'drag':'drag:drag', 'step':'step', + 'over':'drop:enter', 'out':'drop:exit', 'drop':'drop:hit' + }, + + extend : _extend, + + getAttribute : _getAttribute, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + // this is a workaround for the unfortunate fact that in YUI3, the 'drop:exit' event does + // not contain a reference to the drag that just exited. single-threaded js to the + // rescue: we'll just keep it for ourselves. + if (eventArgs[0].drag) _lastDragObject = eventArgs[0].drag.el; + return _lastDragObject; + }, + + getElementObject : _getElementObject, + + getOffset : function(el) { + var bcr = el._node.getBoundingClientRect(); + return { left:bcr.left, top:bcr.top }; + }, + + getScrollLeft : function(el) { + alert("YUI getScrollLeft not implemented yet"); + }, + + getScrollTop : function(el) { + alert("YUI getScrollTop not implemented yet"); + }, + + getSize : function(el) { + //TODO must be a better way to get this? + var bcr = _getElementObject(el)._node.getBoundingClientRect(); + return [ bcr.width, bcr.height ]; + }, + + getUIPosition : function(args) { + //TODO must be a better way to get this? args was passed through from the drag function + // in initDraggable above - args[0] here is the element that was inited. + var bcr = _getElementObject(args[0].currentTarget.el)._node.getBoundingClientRect(); + return { left:bcr.left, top:bcr.top }; + }, + + initDraggable : function(el, options) { + var _opts = _getDDOptions(options); + var id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drag(_opts); + dd.el = el; + + var scope = options['scope'] || jsPlumb.Defaults.Scope; + dd.scope = scope; + + _draggablesById[id] = dd; + _add(_draggablesByScope, scope, dd); + + _attachListeners(dd, options, ddEvents); + }, + + initDroppable : function(el, options) { + var _opts = _getDDOptions(options); + var id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drop(_opts); + + _droppableOptions[id] = options; + + options = _extend({}, options); + var scope = options['scope'] || jsPlumb.Defaults.Scope; + + options["drop:enter"] = jsPlumb.wrap(options["drop:enter"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, true); + }, true); + options["drop:exit"] = jsPlumb.wrap(options["drop:exit"], function(e) { + _checkHover(el, false); + }); + options["drop:hit"] = jsPlumb.wrap(options["drop:hit"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, false); + }, true); + + _attachListeners(dd, options, ddEvents); + }, + + isAlreadyDraggable : function(el) { + el = _getElementObject(el); + return el.hasClass("yui3-dd-draggable"); + }, + + isDragSupported : function(el) { return true; }, + isDropSupported : function(el) { return true; }, + removeClass : function(el, clazz) { el.removeClass(clazz); }, + removeElement : function(el) { _getElementObject(el).remove(); }, + + setAttribute : function(el, attributeName, attributeValue) { + el.setAttribute(attributeName, attributeValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + if (dd) dd.set("lock", !draggable); + }, + + setOffset : function(el, o) { + el = _getElementObject(el); + el.set("top", o.top); + el.set("left", o.left); + } + }; +})();/** +* a set of Bezier curve functions that deal with Cubic Beziers, used by jsPlumb, and perhaps useful for other people. +* +* - functions are all in the 'jsBezier' namespace. +* +* - all input points should be in the format {x:.., y:..}. all output points are in this format too. +* +* - all input curves should be in the format [ {x:.., y:..}, {x:.., y:..}, {x:.., y:..}, {x:.., y:..} ] +* +* - 'location' as used as an input here refers to a decimal in the range 0-1 inclusive, which indicates a point some proportion along the length +* of the curve. location as output has the same format and meaning. +* +* +* Function List: +* -------------- +* +* distanceFromCurve(point, curve) +* +* Calculates the distance that the given point lies from the given Cubic Bezier. Note that it is computed relative to the center of the Bezier, +* so if you have stroked the curve with a wide pen you may wish to take that into account! The distance returned is relative to the values +* of the curve and the point - it will most likely be pixels. +* +* gradientAtPoint(curve, location) +* +* Calculates the gradient to the curve at the given location, as a decimal between 0 and 1 inclusive. +* +* nearestPointOnCurve(point, curve) +* +* Calculates the nearest point to the given point on the given curve. The return value of this is a JS object literal, containing both the +*point's coordinates and also the 'location' of the point (see above), for example: { point:{x:551,y:150}, location:0.263365 }. +* +* pointOnCurve(curve, location) +* +* Calculates the coordinates of the point on the given cubic Bezier curve at the given location. +* +* pointAlongCurveFrom(curve, location, distance) +* +* Calculates the coordinates of the point on the given curve that is 'distance' units from location. 'distance' should be in the same coordinate +* space as that used to construct the Bezier curve. For an HTML Canvas usage, for example, distance would be a measure of pixels. +* +* perpendicularToCurveAt(curve, location, length, distance) +* +* Calculates the perpendicular to the given curve at the given location. length is the length of the line you wish for (it will be centered +* on the point at 'location'). distance is optional, and allows you to specify a point along the path from the given location as the center of +* the perpendicular returned. The return value of this is an array of two points: [ {x:...,y:...}, {x:...,y:...} ]. +* +* quadraticPointOnCurve(curve, location) +* +* Calculates the coordinates of the point on the given quadratic Bezier curve at the given location. This function is used internally by +* pointOnCurve, and is exposed just because it seemed churlish not to do so. But remember that all the other functions in this library deal with +* cubic Beziers. +* +* +* references: +* +* http://webdocs.cs.ualberta.ca/~graphics/books/GraphicsGems/gems/NearestPoint.c +* http://13thparallel.com/archive/bezier-curves/ +* http://bimixual.org/AnimationLibrary/beziertangents.html +* +*/ + +(function() { + + if(typeof Math.sgn == "undefined") { + Math.sgn = function(x) { return x == 0 ? 0 : x > 0 ? 1 :-1; }; + } + + var Vectors = { + subtract : function(v1, v2) { return {x:v1.x - v2.x, y:v1.y - v2.y }; }, + dotProduct : function(v1, v2) { return (v1.x * v2.x) + (v1.y * v2.y); }, + square : function(v) { return Math.sqrt((v.x * v.x) + (v.y * v.y)); }, + scale : function(v, s) { return {x:v.x * s, y:v.y * s }; } + }; + + var MAXDEPTH = 64, EPSILON = Math.pow(2.0,-MAXDEPTH-1), DEGREE = 3, W_DEGREE = 5; + + /** + * Calculates the distance that the point lies from the curve. + * + * @param point a point in the form {x:567, y:3342} + * @param curve a Bezier curve in the form [{x:..., y:...}, {x:..., y:...}, {x:..., y:...}, {x:..., y:...}]. note that this is currently + * hardcoded to assume cubiz beziers, but would be better off supporting any degree. + * @return a JS object literal containing location and distance, for example: {location:0.35, distance:10}. Location is analogous to the location + * argument you pass to the pointOnPath function: it is a ratio of distance travelled along the curve. Distance is the distance in pixels from + * the point to the curve. + */ + var _distanceFromCurve = function(point, curve) { + var candidates = new Array(W_DEGREE); + var w = _convertToBezier(point, curve); + var numSolutions = _findRoots(w, W_DEGREE, candidates, 0); + var v = Vectors.subtract(point, curve[0]), dist = Vectors.square(v), t = 0.0; + for (var i = 0; i < numSolutions; i++) { + v = Vectors.subtract(point, _bezier(curve, DEGREE, candidates[i], null, null)); + var newDist = Vectors.square(v); + if (newDist < dist) { + dist = newDist; + t = candidates[i]; + } + } + v = Vectors.subtract(point, curve[DEGREE]); + newDist = Vectors.square(v); + if (newDist < dist) { + dist = newDist; + t = 1.0; + } + return {location:t, distance:dist}; + }; + /** + * finds the nearest point on the curve to the given point. + */ + var _nearestPointOnCurve = function(point, curve) { + var td = _distanceFromCurve(point, curve); + return {point:_bezier(curve, DEGREE, td.location, null, null), location:td.location}; + }; + /** + * internal method; converts to 5th degree Bezier form. + */ + var _convertToBezier = function(point, curve) { + var c = new Array(DEGREE+1), d = new Array(DEGREE), cdTable = [], w = []; + var z = [ [1.0, 0.6, 0.3, 0.1], [0.4, 0.6, 0.6, 0.4], [0.1, 0.3, 0.6, 1.0] ]; + for (var i = 0; i <= DEGREE; i++) c[i] = Vectors.subtract(curve[i], point); + for (var i = 0; i <= DEGREE - 1; i++) { + d[i] = Vectors.subtract(curve[i+1], curve[i]); + d[i] = Vectors.scale(d[i], 3.0); + } + for (var row = 0; row <= DEGREE - 1; row++) { + for (var column = 0; column <= DEGREE; column++) { + if (!cdTable[row]) cdTable[row] = []; + cdTable[row][column] = Vectors.dotProduct(d[row], c[column]); + } + } + for (i = 0; i <= W_DEGREE; i++) { + if (!w[i]) w[i] = []; + w[i].y = 0.0; + w[i].x = parseFloat(i) / W_DEGREE; + } + var n = DEGREE, m = DEGREE-1; + for (var k = 0; k <= n + m; k++) { + var lb = Math.max(0, k - m); + var ub = Math.min(k, n); + for (i = lb; i <= ub; i++) { + j = k - i; + w[i+j].y += cdTable[j][i] * z[j][i]; + } + } + return w; + }; + /** + * counts how many roots there are. + */ + var _findRoots = function(w, degree, t, depth) { + var i; + var Left = new Array(W_DEGREE+1), Right = new Array(W_DEGREE+1); + var left_count, right_count; + var left_t = new Array(W_DEGREE+1), right_t = new Array(W_DEGREE+1); + switch (_getCrossingCount(w, degree)) { + case 0 : { + return 0; + } + case 1 : { + if (depth >= MAXDEPTH) { + t[0] = (w[0].x + w[W_DEGREE].x) / 2.0; + return 1; + } + if (_isControlPolygonFlatEnough(w, degree)) { + t[0] = _computeXIntercept(w, degree); + return 1; + } + break; + } + } + _bezier(w, degree, 0.5, Left, Right); + left_count = _findRoots(Left, degree, left_t, depth+1); + right_count = _findRoots(Right, degree, right_t, depth+1); + for (i = 0; i < left_count; i++) t[i] = left_t[i]; + for (i = 0; i < right_count; i++) t[i+left_count] = right_t[i]; + return (left_count+right_count); + }; + var _getCrossingCount = function(curve, degree) { + var n_crossings = 0; + var sign, old_sign; + sign = old_sign = Math.sgn(curve[0].y); + for (var i = 1; i <= degree; i++) { + sign = Math.sgn(curve[i].y); + if (sign != old_sign) n_crossings++; + old_sign = sign; + } + return n_crossings; + }; + var _isControlPolygonFlatEnough = function(curve, degree) { + var error; + var intercept_1, intercept_2, left_intercept, right_intercept; + var a, b, c, det, dInv, a1, b1, c1, a2, b2, c2; + a = curve[0].y - curve[degree].y; + b = curve[degree].x - curve[0].x; + c = curve[0].x * curve[degree].y - curve[degree].x * curve[0].y; + + var max_distance_above = max_distance_below = 0.0; + + for (var i = 1; i < degree; i++) { + var value = a * curve[i].x + b * curve[i].y + c; + if (value > max_distance_above) + max_distance_above = value; + else if (value < max_distance_below) + max_distance_below = value; + } + + a1 = 0.0; b1 = 1.0; c1 = 0.0; a2 = a; b2 = b; + c2 = c - max_distance_above; + det = a1 * b2 - a2 * b1; + dInv = 1.0/det; + intercept_1 = (b1 * c2 - b2 * c1) * dInv; + a2 = a; b2 = b; c2 = c - max_distance_below; + det = a1 * b2 - a2 * b1; + dInv = 1.0/det; + intercept_2 = (b1 * c2 - b2 * c1) * dInv; + left_intercept = Math.min(intercept_1, intercept_2); + right_intercept = Math.max(intercept_1, intercept_2); + error = right_intercept - left_intercept; + return (error < EPSILON)? 1 : 0; + }; + var _computeXIntercept = function(curve, degree) { + var XLK = 1.0, YLK = 0.0; + var XNM = curve[degree].x - curve[0].x, YNM = curve[degree].y - curve[0].y; + var XMK = curve[0].x - 0.0, YMK = curve[0].y - 0.0; + var det = XNM*YLK - YNM*XLK, detInv = 1.0/det; + var S = (XNM*YMK - YNM*XMK) * detInv; + return 0.0 + XLK * S; + }; + var _bezier = function(curve, degree, t, left, right) { + var temp = [[]]; + for (var j =0; j <= degree; j++) temp[0][j] = curve[j]; + for (var i = 1; i <= degree; i++) { + for (var j =0 ; j <= degree - i; j++) { + if (!temp[i]) temp[i] = []; + if (!temp[i][j]) temp[i][j] = {}; + temp[i][j].x = (1.0 - t) * temp[i-1][j].x + t * temp[i-1][j+1].x; + temp[i][j].y = (1.0 - t) * temp[i-1][j].y + t * temp[i-1][j+1].y; + } + } + if (left != null) + for (j = 0; j <= degree; j++) left[j] = temp[j][0]; + if (right != null) + for (j = 0; j <= degree; j++) right[j] = temp[degree-j][j]; + + return (temp[degree][0]); + }; + + /** + * calculates a point on the curve, for a cubic bezier (TODO: fold this and the other function into one). + * @param curve an array of control points, eg [{x:10,y:20}, {x:50,y:50}, {x:100,y:100}, {x:120,y:100}]. For a cubic bezier this should have four points. + * @param location a decimal indicating the distance along the curve the point should be located at. this is the distance along the curve as it travels, taking the way it bends into account. should be a number from 0 to 1, inclusive. + */ + var _pointOnPath = function(curve, location) { + // from http://13thparallel.com/archive/bezier-curves/ + function B1(t) { return t*t*t }; + function B2(t) { return 3*t*t*(1-t) }; + function B3(t) { return 3*t*(1-t)*(1-t) }; + function B4(t) { return (1-t)*(1-t)*(1-t) }; + + var x = curve[0].x*B1(location) + curve[1].x * B2(location) + curve[2].x * B3(location) + curve[3].x * B4(location); + var y = curve[0].y*B1(location) + curve[1].y * B2(location) + curve[2].y * B3(location) + curve[3].y * B4(location); + //return [x,y]; + return {x:x, y:y}; + }; + + /** + * calculates a point on the curve, for a quadratic bezier (TODO: fold this and the other function into one). + * @param curve an array of control points, eg [{x:10,y:20}, {x:50,y:50}, {x:100,y:100}]. For a quadratic bezier this should have three points. + * @param location a decimal indicating the distance along the curve the point should be located at. this is the distance along the curve as it travels, taking the way it bends into account. should be a number from 0 to 1, inclusive. + */ + var _quadraticPointOnPath = function(curve, location) { + function B1(t) { return t*t; }; + function B2(t) { return 2*t*(1-t); }; + function B3(t) { return (1-t)*(1-t); }; + var x = curve[0].x*B1(location) + curve[1].x*B2(location) + curve[2].x*B3(location); + var y = curve[0].y*B1(location) + curve[1].y*B2(location) + curve[2].y*B3(location); + return {x:x,y:y}; + }; + + /** + * finds the point that is 'distance' along the path from 'location'. this method returns both the x,y location of the point and also + * its 'location' (proportion of travel along the path); the method below - _pointAlongPathFrom - calls this method and just returns the + * point. + */ + var _pointAlongPath = function(curve, location, distance) { + var _dist = function(p1,p2) { return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2)); }; + var prev = _pointOnPath(curve, location), tally = 0, curLoc = location, direction = distance > 0 ? 1 : -1, cur = null; + while (tally < Math.abs(distance)) { + curLoc += (0.005 * direction); + cur = _pointOnPath(curve, curLoc); + tally += _dist(cur, prev); + prev = cur; + } + return {point:cur, location:curLoc}; + }; + + /** + * finds the point that is 'distance' along the path from 'location'. this method returns both the x,y location of the point and also + * its 'location' (proportion of travel along the path). + */ + var _pointAlongPathFrom = function(curve, location, distance) { + return _pointAlongPath(curve, location, distance).point; + }; + + /** + * returns the gradient of the connector at the given location, which is a decimal between 0 and 1 inclusive. + * + * thanks // http://bimixual.org/AnimationLibrary/beziertangents.html + */ + var _gradientAtPoint = function(curve, location) { + var p1 = _pointOnPath(curve, location); + var p2 = _quadraticPointOnPath(curve, location); + var dy = p2.y - p1.y, dx = p2.x - p1.x; + return Math.atan(dy / dx); + }; + + /** + * calculates a line that is 'length' pixels long, perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * if distance is not supplied, the perpendicular for the given location is computed (ie. we set distance to zero). + */ + var _perpendicularToPathAt = function(curve, location, length, distance) { + distance = distance == null ? 0 : distance; + var p = _pointAlongPath(curve, location, distance); + var m = _gradientAtPoint(curve, p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.point.x + x, y:p.point.y + y}, {x:p.point.x - x, y:p.point.y - y}]; + }; + + var jsBezier = window.jsBezier = { + distanceFromCurve : _distanceFromCurve, + gradientAtPoint : _gradientAtPoint, + nearestPointOnCurve : _nearestPointOnCurve, + pointOnCurve : _pointOnPath, + pointAlongCurveFrom : _pointAlongPathFrom, + perpendicularToCurveAt : _perpendicularToPathAt, + quadraticPointOnCurve : _quadraticPointOnPath //TODO fold the two pointOnPath functions into one; it can detect what it was given. + }; +})(); diff --git a/archive/1.2.6/jquery.jsPlumb-1.2.6-RC1.js b/archive/1.2.6/jquery.jsPlumb-1.2.6-RC1.js new file mode 100644 index 000000000..04d7f334e --- /dev/null +++ b/archive/1.2.6/jquery.jsPlumb-1.2.6-RC1.js @@ -0,0 +1,254 @@ +/* + * jquery.jsPlumb 1.2.6-RC1 + * + * jQuery specific functionality for jsPlumb. + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + * NOTE: for production usage you should use jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, + * in a minified file. + * + * Dual licensed under MIT and GPL2. + * + */ +/* + * the library agnostic functions, such as find offset, get id, get attribute, extend etc. + * the full list is: + * + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getPageXY gets the page event's xy location. + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * hasClass returns whether or not the given element has the given class. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + */ +(function($) { + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * appends the given child to the given parent. + */ + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).append(child); + }, + + /** + * event binding wrapper. it just so happens that jQuery uses 'bind' also. yui3, for example, + * uses 'on'. + */ + bind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.bind(event, callback); + }, + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step', + 'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete' + }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + getDocumentElement : function() { return document; }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + }, + + getDragScope : function(el) { + return el.draggable("option", "scope"); + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + * + */ + getElementObject : function(el) { + return typeof(el)=='string' ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getScrollLeft : function(el) { + return el.scrollLeft(); + }, + + getScrollTop : function(el) { + return el.scrollTop(); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[1], _offset = ui.offset; + return _offset || ui.absolutePosition; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options) { + // remove helper directive if present. + options.helper = null; + //TODO: if 'revert' is set on the options it causes end points to animate back to + // where they came from, if the connection is aborted. do we care? probably not. + // the todo is to decide whether we care or not. + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.draggable(options); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.droppable(options); + }, + + isAlreadyDraggable : function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return el.hasClass("ui-draggable"); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).remove(); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * sets the drag scope. probably time for a setDragOption method (roll this and the one above together) + * @param el + * @param scope + */ + setDragScope : function(el, scope) { + el.draggable("option", "scope", scope); + }, + + setOffset : function(el, o) { + jsPlumb.CurrentLibrary.getElementObject(el).offset(o); + } + }; + + $(document).ready(jsPlumb.init); +})(jQuery); diff --git a/archive/1.2.6/jquery.jsPlumb-1.2.6-all-min.js b/archive/1.2.6/jquery.jsPlumb-1.2.6-all-min.js new file mode 100644 index 000000000..5830735b8 --- /dev/null +++ b/archive/1.2.6/jquery.jsPlumb-1.2.6-all-min.js @@ -0,0 +1 @@ +(function(){var d=!!!document.createElement("canvas").getContext;var h=function(u,s,t){var r=u[s];if(r==null){r=[];u[s]=r}r.push(t);return r};var g=null;var n=function(r,s){return q.CurrentLibrary.getAttribute(b(r),s)};var f=function(s,t,r){q.CurrentLibrary.setAttribute(b(s),t,r)};var m=function(s,r){q.CurrentLibrary.addClass(b(s),r)};var k=function(s,r){return q.CurrentLibrary.hasClass(b(s),r)};var c=function(s,r){q.CurrentLibrary.removeClass(b(s),r)};var b=function(r){return q.CurrentLibrary.getElementObject(r)};var p=function(r){return q.CurrentLibrary.getOffset(b(r))};var e=function(r){return q.CurrentLibrary.getSize(b(r))};var l=function(r,s){if(r.logEnabled&&typeof console!="undefined"){console.log(s)}};var a=function(){var v={};var A=this;this.overlayPlacements=[];this.paintStyle=null,this.hoverPaintStyle=null;this._over=function(E){var G=p(b(A.canvas));var I=q.CurrentLibrary.getPageXY(E);var B=I[0]-G.left,H=I[1]-G.top;if(B>0&&H>0&&B=B&&D[2]<=H&&D[3]>=H)){return true}}if(!d){var F=A.canvas.getContext("2d").getImageData(parseInt(B),parseInt(H),1,1);return F.data[0]!=0||F.data[1]!=0||F.data[2]!=0||F.data[3]!=0}else{}}return false};this.bind=function(B,C){h(v,B,C)};this.fireUpdate=function(D,E,B){if(v[D]){for(var C=0;C=0){delete (aq[ar]);aq.splice(ar,1);return true}}}return false};var r=function(ar,aq){return C(ar,function(at,au){aa[au]=aq;if(q.CurrentLibrary.isDragSupported(at)){q.CurrentLibrary.setDraggable(at,aq)}})};var ai=function(aq,ar){N(n(aq,"id"),function(at){at.canvas.style.display=ar})};var H=function(aq){return C(aq,function(at,ar){var au=aa[ar]==null?R:aa[ar];au=!au;aa[ar]=au;q.CurrentLibrary.setDraggable(at,au);return au})};var s=function(aq){N(aq,function(at){var ar=("none"==at.canvas.style.display);at.canvas.style.display=ar?"block":"none"})};var y=function(aw){var au=aw.timestamp,aq=aw.recalc,av=aw.offset,ar=aw.elId;if(!aq){if(au&&au===w[ar]){return}}if(aq||av==null){var at=b(ar);if(at!=null){J[ar]=e(at);P[ar]=p(at);w[ar]=au}}else{P[ar]=av}};var ap=function(aq,ar){var at=b(aq);var au=n(at,"id");if(!au||au=="undefined"){if(arguments.length==2&&arguments[1]!=undefined){au=ar}else{au="jsPlumb_"+al()}f(at,"id",au)}return au};var ag=function(at,aq,ar){at=at||function(){};aq=aq||function(){};return function(){var au=null;try{au=aq.apply(this,arguments)}catch(av){l(x,"jsPlumb function failed : "+av)}if(ar==null||(au!==ar)){try{at.apply(this,arguments)}catch(av){l(x,"wrapped function failed : "+av)}}return au}};this.connectorClass="_jsPlumb_connector";this.endpointClass="_jsPlumb_endpoint";this.overlayClass="_jsPlumb_overlay";this.Anchors={};this.Connectors={};this.Endpoints={};this.Overlays={};this.addEndpoint=function(aw,au,aA){aA=aA||{};var ar=q.extend({},aA);q.extend(ar,au);ar.endpoint=ar.endpoint||x.Defaults.Endpoint||q.Defaults.Endpoint;ar.endpointStyle=ar.endpointStyle||x.Defaults.EndpointStyle||q.Defaults.EndpointStyle;var at=b(aw),aq=n(at,"id");ar.source=at;y({elId:aq});var ax=ah(ar);h(ab,aq,ax);var ay=P[aq],av=J[aq];var az=ax.anchor.compute({xy:[ay.left,ay.top],wh:av,element:ax});ax.paint({anchorLoc:az});return ax};this.addEndpoints=function(av,ar,aq){var au=[];for(var at=0;at0?Y(aC,aB)!=-1:true};for(var av in E){if(at(ay,av)){aq[av]=[];for(var au=0;au=4){ar.orientation=[arguments[2],arguments[3]]}if(arguments.length==6){ar.offsets=[arguments[4],arguments[5]]}}var ax=new V(ar);ax.clone=function(){return new V(ar)};return ax};this.makeAnchors=function(ar){var at=[];for(var aq=0;aq0?aA[0]:null;var aw=aA.length>0?0:-1;this.locked=false;var az=this;var av=function(aD,aB,aH,aG,aC){var aF=aG[0]+(aD.x*aC[0]),aE=aG[1]+(aD.y*aC[1]);return Math.sqrt(Math.pow(aB-aF,2)+Math.pow(aH-aE,2))};var aq=ar||function(aL,aC,aD,aE,aB){var aG=aD[0]+(aE[0]/2),aF=aD[1]+(aE[1]/2);var aI=-1,aK=Infinity;for(var aH=0;aHaO){aO=aW}}var a1=this.connector.compute(aQ,a3,this.endpoints[a7].anchor,this.endpoints[aN].anchor,az.paintStyleInUse.lineWidth,aO);q.sizeCanvas(at,a1[0],a1[1],a1[2],a1[3]);var aU=function(a8,ba){a8.save();q.extend(a8,ba);if(ba.gradient&&!d){var bb=az.connector.createGradient(a1,a8,(aV==this.sourceId));for(var a9=0;a9=0){aE.connections.splice(aQ,1);if(!aT){var aS=aR.endpoints[0]==aE?aR.endpoints[1]:aR.endpoints[0];aS.detach(aR,true)}X(aR.canvas,aR.container);G(E,aR.scope,aR);if(!aT){W(aR)}}};this.detachAll=function(){while(aE.connections.length>0){aE.detach(aE.connections[0])}};this.detachFrom=function(aR){var aS=[];for(var aQ=0;aQ=0){aE.connections.splice(aQ,1)}};this.getElement=function(){return aD};this.getUuid=function(){return az};this.makeInPlaceCopy=function(){var aQ=ah({anchor:aE.anchor,source:aD,paintStyle:this.paintStyle,endpoint:aC});return aQ};this.isConnectedTo=function(aS){var aR=false;if(aS){for(var aQ=0;aQ=aK)};this.setDragAllowedWhenFull=function(aQ){aA=aQ};this.setPaintStyle=this.setPaintStyle;this.setHoverPaintStyle=this.setHoverPaintStyle;this.setStyle=aE.setPaintStyle;this.equals=function(aQ){return this.anchor.equals(aQ.anchor)};this.paint=function(aT){aT=aT||{};var aX=aT.timestamp;if(!aX||aE.timestamp!==aX){var aW=aT.anchorPoint,aS=aT.canvas,aU=aT.connectorPaintStyle;if(aW==null){var a2=aT.offset||P[ax];var aQ=aT.dimensions||J[ax];if(a2==null||aQ==null){y({elId:ax,timestamp:aX});a2=P[ax];aQ=J[ax]}var aR={xy:[a2.left,a2.top],wh:aQ,element:aE,timestamp:aX};if(aE.anchor.isDynamic){if(aE.connections.length>0){var aZ=aE.connections[0];var a1=aZ.endpoints[0]==aE?1:0;var aV=a1==0?aZ.sourceId:aZ.targetId;var aY=P[aV],a0=J[aV];aR.txy=[aY.left,aY.top];aR.twh=a0;aR.tElement=aZ.endpoints[a1]}}aW=aE.anchor.compute(aR)}aC.paint(aW,aE.anchor.getOrientation(),aS||aE.canvas,aE.paintStyleInUse,aU||aE.paintStyleInUse);aE.timestamp=aX}};this.repaint=this.paint;this.removeConnection=this.detach;if(aP.isSource&&q.CurrentLibrary.isDragSupported(aD)){var aJ=null,aF=null,aI=null,aq=false,at=null;var av=function(){aI=aE.connectorSelector();if(aE.isFull()&&!aA){return false}y({elId:ax});au=aE.makeInPlaceCopy();au.paint();aJ=document.createElement("div");var aU=b(aJ);L(aJ,aE.container);var aW=ap(aU);y({elId:aW});f(b(aE.canvas),"dragId",aW);f(b(aE.canvas),"elId",ax);var aR=new B({reference:aE.anchor,referenceCanvas:aE.canvas});aN=ah({paintStyle:{fillStyle:"rgba(0,0,0,0)"},endpoint:aC,anchor:aR,source:aU});if(aI==null){aE.anchor.locked=true;aI=an({sourceEndpoint:aE,targetEndpoint:aN,source:b(aD),target:b(aJ),anchors:[aE.anchor,aR],paintStyle:aP.connectorStyle,hoverPaintStyle:aP.connectorHoverStyle,backgroundPaintStyle:aP.connectorBackgroundStyle,connector:aP.connector,overlays:aP.connectorOverlays});aI.setHover(false)}else{aq=true;aI.setHover(false);aw(b(au.canvas));var aQ=aI.sourceId==ax?0:1;aI.floatingAnchorIndex=aQ;aE.detachFromConnection(aI);var aV=b(aE.canvas);var aT=q.CurrentLibrary.getDragScope(aV);f(aV,"originalScope",aT);var aS="scope_"+(new Date()).getTime();if(aQ==0){at=[aI.source,aI.sourceId,aM,aT];aI.source=b(aJ);aI.sourceId=aW}else{at=[aI.target,aI.targetId,aM,aT];aI.target=b(aJ);aI.targetId=aW}q.CurrentLibrary.setDragScope(aM,aS);aI.endpoints[aQ==0?1:0].anchor.locked=true;aI.suspendedEndpoint=aI.endpoints[aQ];aI.endpoints[aQ]=aN}S[aW]=aI;aN.addConnection(aI);h(ab,aW,aN);x.currentlyDragging=true};var aL=aP.dragOptions||{};var aG=q.extend({},q.CurrentLibrary.defaultDragOptions);aL=q.extend(aG,aL);aL.scope=aL.scope||aE.scope;var aH=q.CurrentLibrary.dragEvents.start;var aO=q.CurrentLibrary.dragEvents.stop;var ay=q.CurrentLibrary.dragEvents.drag;aL[aH]=ag(aL[aH],av);aL[ay]=ag(aL[ay],function(){var aQ=q.CurrentLibrary.getUIPosition(arguments);q.CurrentLibrary.setOffset(aJ,aQ);am(b(aJ),aQ)});aL[aO]=ag(aL[aO],function(){G(ab,aF,aN);K([aJ,aN.canvas],aD);X(au.canvas,aD);var aQ=aI.floatingAnchorIndex==null?1:aI.floatingAnchorIndex;aI.endpoints[aQ==0?1:0].anchor.locked=false;if(aI.endpoints[aQ]==aN){if(aq&&aI.suspendedEndpoint){if(aQ==0){aI.source=at[0];aI.sourceId=at[1]}else{aI.target=at[0];aI.targetId=at[1]}q.CurrentLibrary.setDragScope(at[2],at[3]);aI.endpoints[aQ]=aI.suspendedEndpoint;if(aB){aI.floatingAnchorIndex=null;aI.suspendedEndpoint.addConnection(aI);q.repaint(at[1])}else{aI.endpoints[aQ==0?1:0].detach(aI)}}else{X(aI.canvas,aE.container);aE.detachFromConnection(aI)}}aE.anchor.locked=false;aE.paint();aI.repaint();aI=null;delete au;delete ab[aN.elementId];delete aN;x.currentlyDragging=false});var aM=b(aE.canvas);q.CurrentLibrary.initDraggable(aM,aL)}var aw=function(aT){if(aP.isTarget&&q.CurrentLibrary.isDropSupported(aD)){var aQ=aP.dropOptions||x.Defaults.DropOptions||q.Defaults.DropOptions;aQ=q.extend({},aQ);aQ.scope=aQ.scope||aE.scope;var aW=null;var aU=q.CurrentLibrary.dragEvents.drop;var aV=q.CurrentLibrary.dragEvents.over;var aR=q.CurrentLibrary.dragEvents.out;var aS=function(){var a5=b(q.CurrentLibrary.getDragObject(arguments));var aX=n(a5,"dragId");var aZ=n(a5,"elId");var a4=n(a5,"originalScope");if(a4){q.CurrentLibrary.setDragScope(a5,a4)}var a1=S[aX];var a2=a1.floatingAnchorIndex==null?1:a1.floatingAnchorIndex,a3=a2==0?1:0;if(!aE.isFull()&&!(a2==0&&!aE.isSource)&&!(a2==1&&!aE.isTarget)){if(a2==0){a1.source=aD;a1.sourceId=ax}else{a1.target=aD;a1.targetId=ax}a1.endpoints[a2].detachFromConnection(a1);if(a1.suspendedEndpoint){a1.suspendedEndpoint.detachFromConnection(a1)}a1.endpoints[a2]=aE;aE.addConnection(a1);if(!a1.suspendedEndpoint){h(E,a1.scope,a1);I(aD,aP.draggable,{})}else{var a0=a1.suspendedEndpoint.getElement(),aY=a1.suspendedEndpoint.elementId;x.fireUpdate("jsPlumbConnectionDetached",{source:a2==0?a0:a1.source,target:a2==1?a0:a1.target,sourceId:a2==0?aY:a1.sourceId,targetId:a2==1?aY:a1.targetId,sourceEndpoint:a2==0?a1.suspendedEndpoint:a1.endpoints[0],targetEndpoint:a2==1?a1.suspendedEndpoint:a1.endpoints[1],connection:a1})}q.repaint(aZ);x.fireUpdate("jsPlumbConnection",{source:a1.source,target:a1.target,sourceId:a1.sourceId,targetId:a1.targetId,sourceEndpoint:a1.endpoints[0],targetEndpoint:a1.endpoints[1],connection:a1})}x.currentlyDragging=false;delete S[aX]};aQ[aU]=ag(aQ[aU],aS);aQ[aV]=ag(aQ[aV],function(){var aY=q.CurrentLibrary.getDragObject(arguments);var a0=n(b(aY),"dragId");var aZ=S[a0];var aX=aZ.floatingAnchorIndex==null?1:aZ.floatingAnchorIndex;aZ.endpoints[aX].anchor.over(aE.anchor)});aQ[aR]=ag(aQ[aR],function(){var aY=q.CurrentLibrary.getDragObject(arguments);var a0=n(b(aY),"dragId");var aZ=S[a0];var aX=aZ.floatingAnchorIndex==null?1:aZ.floatingAnchorIndex;aZ.endpoints[aX].anchor.out()});q.CurrentLibrary.initDroppable(aT,aQ)}};aw(b(aE.canvas));return aE}};var q=window.jsPlumb=new o();q.getInstance=function(s){var r=new o(s);return r}})();(function(){var b=!!!document.createElement("canvas").getContext;var a=function(c,f,e,d){return function(){return jsPlumb.makeAnchor(c,f,e,d)}};jsPlumb.Anchors.TopCenter=a(0.5,0,0,-1);jsPlumb.Anchors.BottomCenter=a(0.5,1,0,1);jsPlumb.Anchors.LeftMiddle=a(0,0.5,-1,0);jsPlumb.Anchors.RightMiddle=a(1,0.5,1,0);jsPlumb.Anchors.Center=a(0.5,0.5,0,0);jsPlumb.Anchors.TopRight=a(1,0,0,-1);jsPlumb.Anchors.BottomRight=a(1,1,0,1);jsPlumb.Anchors.TopLeft=a(0,0,0,-1);jsPlumb.Anchors.BottomLeft=a(0,1,0,1);jsPlumb.Defaults.DynamicAnchors=function(){return jsPlumb.makeAnchors(["TopCenter","RightMiddle","BottomCenter","LeftMiddle"])};jsPlumb.Anchors.AutoDefault=function(){return jsPlumb.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors())};jsPlumb.Connectors.Straight=function(){var q=this;var k=null;var e,l,o,n,m,f,p,h,g,d,c;this.compute=function(u,I,E,r,B,t){var H=Math.abs(u[0]-I[0]);var A=Math.abs(u[1]-I[1]);var C=false,v=false;var z=0.45*H,s=0.45*A;H*=1.9;A*=1.9;var F=Math.min(u[0],I[0])-z;var D=Math.min(u[1],I[1])-s;var G=Math.max(2*B,t);if(H0?1:-1;var v=Math.abs(w*Math.sin(f));if(g>c){v=v*-1}var r=Math.abs(w*Math.cos(f));if(h>d){r=r*-1}return{x:u.x+(t*r),y:u.y+(t*v)}};this.perpendicularToPathAt=function(u,v,A){var w=q.pointAlongPathFrom(u,A);var t=q.gradientAtPoint(w.location);var s=Math.atan(-1/t);var z=v/2*Math.sin(s);var r=v/2*Math.cos(s);return[{x:w.x+r,y:w.y+z},{x:w.x-r,y:w.y-z}]};this.createGradient=function(s,r){return r.createLinearGradient(s[4],s[5],s[6],s[7])}};jsPlumb.Connectors.Bezier=function(g){var r=this;this.majorAnchor=150;if(g){if(g.constructor==Number){this.majorAnchor=g}else{if(g.curviness){this.majorAnchor=g.curviness}}}this.minorAnchor=10;var l=null;this._findControlPoint=function(B,s,w,z,t){var y=z.getOrientation(),A=t.getOrientation();var v=y[0]!=A[0]||y[1]==A[1];var u=[];var C=r.majorAnchor,x=r.minorAnchor;if(!v){if(y[0]==0){u.push(s[0]o){o=x}if(A<0){f+=A;var B=Math.abs(A);o+=B;q[0]+=B;m+=B;d+=B;p[0]+=B}var J=Math.min(h,c);var H=Math.min(q[1],p[1]);var w=Math.min(J,H);var C=Math.max(h,c);var z=Math.max(q[1],p[1]);var u=Math.max(C,z);if(u>k){k=u}if(w<0){e+=w;var y=Math.abs(w);k+=y;q[1]+=y;h+=y;c+=y;p[1]+=y}if(G&&o1?q:{x:s[0].x+((s[1].x-s[0].x)/2),y:s[0].y+((s[1].y-s[0].y)/2)};var A=p.x-l.x,x=p.y-l.y;q.x+=A;q.y+=x;k.x+=A;k.y+=x;s[0].x+=A;s[0].y+=x;s[1].x+=A;s[1].y+=x;r.x+=A;r.y+=x}var w=Math.min(r.x,s[0].x,s[1].x);var u=Math.max(r.x,s[0].x,s[1].x);var v=Math.min(r.y,s[0].y,s[1].y);var t=Math.max(r.y,s[0].y,s[1].y);z.lineWidth=e.lineWidth;z.beginPath();z.moveTo(r.x,r.y);z.lineTo(s[0].x,s[0].y);z.lineTo(q.x,q.y);z.lineTo(s[1].x,s[1].y);z.lineTo(r.x,r.y);z.closePath();if(e.strokeStyle){z.strokeStyle=e.strokeStyle;z.stroke()}z.fillStyle=e.fillStyle||y.strokeStyle;z.fill();return[w,u,v,t]}};jsPlumb.Overlays.PlainArrow=function(d){d=d||{};var c=jsPlumb.extend(d,{foldback:1});jsPlumb.Overlays.Arrow.call(this,c)};jsPlumb.Overlays.Diamond=function(e){e=e||{};var c=e.length||40;var d=jsPlumb.extend(e,{length:c/2,foldback:2});jsPlumb.Overlays.Arrow.call(this,d)};jsPlumb.Overlays.Label=function(l){this.labelStyle=l.labelStyle||jsPlumb.Defaults.LabelStyle;this.label=l.label;this.connection=l.connection;var d=this;var c=null,k=null,f=null,g=null;this.location=l.location||0.5;this.cachedDimensions=null;var h=function(n){if(d.cachedDimensions){return d.cachedDimensions}f=typeof d.label=="function"?d.label(d):d.label;var r={};if(f){var m=f.split(/\n|\r\n/);n.save();if(d.labelStyle.font){n.font=d.labelStyle.font}var o=e(m,n);var p=n.measureText("M").width;g=d.labelStyle.padding||0.25;c=o+(2*o*g);k=(m.length*p)+(2*p*g);var q=m.length*p;n.restore();r={width:c,height:k,lines:m,oneLine:p,padding:g,textHeight:q}}if(typeof d.label!="function"){d.cachedDimensions=r}return r};this.computeMaxSize=function(n,m){var o=h(m);return o.width?Math.max(o.width,o.height)*1.5:0};var e=function(o,n){var m=0;for(var q=0;qm){m=p}}return m};this.draw=function(p,o,q){var s=h(o);if(s.width){var r=p.pointOnPath(d.location);if(d.labelStyle.font){o.font=d.labelStyle.font}if(d.labelStyle.fillStyle){o.fillStyle=d.labelStyle.fillStyle}else{o.fillStyle="rgba(0,0,0,0)"}var n=r.x-(s.width/2);var m=r.y-(s.height/2);o.fillRect(n,m,s.width,s.height);if(d.labelStyle.color){o.fillStyle=d.labelStyle.color}o.textBaseline="middle";o.textAlign="center";for(i=0;i0){o.strokeStyle=d.labelStyle.borderStyle||"black";o.strokeRect(n,m,s.width,s.height)}return[n,n+s.width,m,m+s.height]}else{return[0,0,0,0]}}};jsPlumb.Overlays.Image=function(e){var l=this;this.location=e.location||0.5;this.img=new Image();this.connection=e.connection;var m=null;var f=null;var d,c;var k=e.events||{};var h=function(){if(l.ready){window.clearInterval(f);m=document.createElement("img");m.src=l.img.src;m.style.position="absolute";m.style.display="none";m.className="_jsPlumb_overlay";document.body.appendChild(m);for(var n in k){jsPlumb.CurrentLibrary.bind(m,n,k[n])}if(d&&c){g(d,c);c=null;d=null}}};this.img.onload=function(){l.ready=true};this.img.src=e.src||e.url;f=window.setInterval(h,250);this.computeMaxSize=function(o,n){return[l.img.width,l.img.height]};var g=function(q,v,u){if(m!=null){var r=q.pointOnPath(l.location);var p=jsPlumb.CurrentLibrary.getElementObject(v.canvas);var w=jsPlumb.CurrentLibrary.getOffset(p);var t=r.x-(l.img.width/2);var s=r.y-(l.img.height/2);var n={left:w.left+t,top:w.top+s};jsPlumb.CurrentLibrary.setOffset(m,n);m.style.display="block";return[t,t+l.img.width,s,s+l.img.height]}};this.draw=function(o,n){if(l.ready){return g(o,n)}else{d=o;c=n;return[0,0,0,0]}}}})();(function(){jsPlumb.Connectors.Flowchart=function(f){f=f||{};var o=this,b=f.minStubLength||30,k=[],h=[],m=[],g=[],a=[],n=[],d,c,q=function(u,t,B,A){var y=0;for(var s=0;s=t){r=u;s=(t-m[u][0])/a[u];break}}return{segment:k[r],proportion:s,index:r}};this.compute=function(u,J,F,r,C,s){k=[];h=[];a=[];g=[];segmentProportionals=[];d=J[0]u[0]?u[0]+((1-y)*t)-z:u[2]+(y*t)+z,y:r==0?u[3]:u[3]>u[1]?u[1]+((1-y)*t)-z:u[3]+(y*t)+z,segmentInfo:w};return x};this.perpendicularToPathAt=function(u,v,A){var w=o.pointAlongPathFrom(u,A);var t=h[w.segmentInfo.index];var s=Math.atan(-1/t);var z=v/2*Math.sin(s);var r=v/2*Math.cos(s);return[{x:w.x+r,y:w.y+z},{x:w.x-r,y:w.y-z}]}}})();(function(a){jsPlumb.CurrentLibrary={addClass:function(c,b){c.addClass(b)},animate:function(d,c,b){d.animate(c,b)},appendElement:function(c,b){jsPlumb.CurrentLibrary.getElementObject(b).append(c)},bind:function(b,c,d){b=jsPlumb.CurrentLibrary.getElementObject(b);b.bind(c,d)},dragEvents:{start:"start",stop:"stop",drag:"drag",step:"step",over:"over",out:"out",drop:"drop",complete:"complete"},extend:function(c,b){return a.extend(c,b)},getAttribute:function(b,c){return b.attr(c)},getDocumentElement:function(){return document},getDragObject:function(b){return b[1].draggable},getDragScope:function(b){return b.draggable("option","scope")},getElementObject:function(b){return typeof(b)=="string"?a("#"+b):a(b)},getOffset:function(b){return b.offset()},getPageXY:function(b){return[b.pageX,b.pageY]},getScrollLeft:function(b){return b.scrollLeft()},getScrollTop:function(b){return b.scrollTop()},getSize:function(b){return[b.outerWidth(),b.outerHeight()]},getUIPosition:function(c){var d=c[1],b=d.offset;return b||d.absolutePosition},hasClass:function(c,b){return c.hasClass(b)},initDraggable:function(c,b){b.helper=null;b.scope=b.scope||jsPlumb.Defaults.Scope;c.draggable(b)},initDroppable:function(c,b){b.scope=b.scope||jsPlumb.Defaults.Scope;c.droppable(b)},isAlreadyDraggable:function(b){b=jsPlumb.CurrentLibrary.getElementObject(b);return b.hasClass("ui-draggable")},isDragSupported:function(c,b){return c.draggable},isDropSupported:function(c,b){return c.droppable},removeClass:function(c,b){c.removeClass(b)},removeElement:function(b,c){jsPlumb.CurrentLibrary.getElementObject(b).remove()},setAttribute:function(c,d,b){c.attr(d,b)},setDraggable:function(c,b){c.draggable("option","disabled",!b)},setDragScope:function(c,b){c.draggable("option","scope",b)},setOffset:function(b,c){jsPlumb.CurrentLibrary.getElementObject(b).offset(c)}};a(document).ready(jsPlumb.init)})(jQuery);(function(){if(typeof Math.sgn=="undefined"){Math.sgn=function(m){return m==0?0:m>0?1:-1}}var b={subtract:function(n,m){return{x:n.x-m.x,y:n.y-m.y}},dotProduct:function(n,m){return n.x*m.x+n.y*m.y},square:function(m){return Math.sqrt(m.x*m.x+m.y*m.y)},scale:function(n,m){return{x:n.x*m,y:n.y*m}}},d=Math.pow(2,-65),h=function(y,x){for(var s=[],v=x.length-1,r=2*v-1,t=[],w=[],o=[],p=[],q=[[1,0.6,0.3,0.1],[0.4,0.6,0.6,0.4],[0.1,0.3,0.6,1]],u=0;u<=v;u++){t[u]=b.subtract(x[u],y)}for(u=0;u<=v-1;u++){w[u]=b.subtract(x[u+1],x[u]);w[u]=b.scale(w[u],3)}for(u=0;u<=v-1;u++){for(var n=0;n<=v;n++){o[u]||(o[u]=[]);o[u][n]=b.dotProduct(w[u],t[n])}}for(u=0;u<=r;u++){p[u]||(p[u]=[]);p[u].y=0;p[u].x=parseFloat(u)/r}r=v-1;for(t=0;t<=v+r;t++){w=Math.min(t,v);for(u=Math.max(0,t-r);u<=w;u++){j=t-u;p[u+j].y+=o[j][u]*q[j][u]}}v=x.length-1;p=l(p,2*v-1,s,0);r=b.subtract(y,x[0]);o=b.square(r);for(u=q=0;u=64){y[0]=(E[0].x+E[D].x)/2;return 1}var s,r,p;v=E[0].y-E[D].y;w=E[D].x-E[0].x;A=E[0].x*E[D].y-E[D].x*E[0].y;t=max_distance_below=0;for(r=1;rt){t=p}else{if(p0?1:-1,s=null;o 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + if (!ie) { + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + else { + // need to get fancy with the vml. + } + } + return false; + }; + + /* + * Binds a listener to an event. + * + * event - name of the event to bind to. + * listener - function to execute. + */ + this.bind = function(event, listener) { + _addToList(_listeners, event, listener); + }; + /* + * Fires an update for the given event. + * + * event - event to fire + * value - value to pass to the event listener(s). + * originalEvent - the original event from the browser + */ + this.fireUpdate = function(event, value, originalEvent) { + if (_listeners[event]) { + for ( var i = 0; i < _listeners[event].length; i++) { + try { + _listeners[event][i](value, originalEvent); + } catch (e) { + _log(self, "jsPlumb: fireUpdate failed for event " + + event + " : " + e + "; not fatal."); + } + } + } + }; + /* + * Clears either all listeners, or listeners for some specific event. + * + * event - optional. constrains the clear to just listeners for this event. + */ + this.clearListeners = function(event) { + if (event) { + delete _listeners[event]; + } else { + delete _listeners; + _listeners = {}; + } + }; + + var _mouseover = false; + var _mouseDown = false, _mouseDownAt = null, _posWhenMouseDown = null, _mouseWasDown = false, srcWhenMouseDown = null, + targetWhenMouseDown = null; + this.mousemove = function(e) { + var jpcl = jsPlumb.CurrentLibrary; + var pageXY = jpcl.getPageXY(e); + var ee = document.elementFromPoint(pageXY[0], pageXY[1]); + var _continue = _connectionBeingDragged == null && (_hasClass(ee, "_jsPlumb_endpoint") || _hasClass(ee, "_jsPlumb_connector")); + + if (_mouseDown && srcWhenMouseDown) { + _mouseWasDown = true; + _connectionBeingDragged = self; + var mouseNow = jpcl.getPageXY(e); + var dx = mouseNow[0] - _mouseDownAt[0]; + var dy = mouseNow[1] - _mouseDownAt[1]; + var newPos = {left:srcWhenMouseDown.left + dx, top:srcWhenMouseDown.top + dy}; + jpcl.setOffset(jpcl.getElementObject(self.source), newPos); + jsPlumb.repaint(self.source); + newPos = {left:targetWhenMouseDown.left + dx, top:targetWhenMouseDown.top + dy}; + jpcl.setOffset(jpcl.getElementObject(self.target), newPos); + jsPlumb.repaint(self.target); + } + else if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.setHover(_mouseover); + self.fireUpdate("mouseenter", self, e); + } + else if (_mouseover && (!self._over(e) || !_continue)) { + _mouseover = false; + /* if (self.hoverPaintStyle != null) { + self.paintStyleInUse = self.paintStyle; + self.repaint(); + _updateAttachedElements(); + }*/ + self.setHover(_mouseover); + self.fireUpdate("mouseexit", self, e); + } + }; + + /** + * sets/unsets the hover state of this element. + */ + this.setHover = function(hover, ignoreAttachedElements) { + if (self.hoverPaintStyle != null) { + self.paintStyleInUse = hover ? self.hoverPaintStyle : self.paintStyle; + self.repaint(); + // get the list of other affected elements. for a connection, its the endpoints. for an endpoint, its the connections! surprise. + if (!ignoreAttachedElements) + _updateAttachedElements(hover); + } + }; + + var _updateAttachedElements = function(state) { + var affectedElements = self.getAttachedElements(); // implemented in subclasses + if (affectedElements) { + for (var i = 0; i < affectedElements.length; i++) { + affectedElements[i].setHover(state, true); // tell the attached elements not to inform their own attached elements. + } + } + }; + + this.click = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fireUpdate("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fireUpdate("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _mouseDownAt = jsPlumb.CurrentLibrary.getPageXY(e); + if (self.canvas) _posWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.canvas)); + if (self.source) srcWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.source)); + if (self.target) targetWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.target)); + } + }; + + this.mouseup = function() { + if (self == _connectionBeingDragged) _connectionBeingDragged = null; + _mouseDown = false; + }; + + /* + * Sets the paint style and then repaints the element. + * + * style - Style to use. + */ + this.setPaintStyle = function(style) { + self.paintStyle = style; + self.paintStyleInUse = self.paintStyle; + self.repaint(); + }; + + /* + * Sets the paint style to use when the mouse is hovering over the element. This is null by default. + * + * style - Style to use when the mouse is hovering. + */ + this.setHoverPaintStyle = function(style) { + self.hoverPaintStyle = style; + self.repaint(); + }; + }; + + /** + * Class:jsPlumb + * The main jsPlumb class. One of these is registered on the Window as 'jsPlumb'; you can also get one yourself by making a call to jsPlumb.getInstance. This class is the class you use to establish Connections and add Endpoints. + */ + var jsPlumbInstance = function(_defaults) { + + /* + * Property: Defaults + * + * These are the default settings for jsPlumb. They are what will be used if you do not supply specific pieces of information to the various API calls. A convenient way to implement your own look and feel can be to override these defaults by including a script somewhere after the jsPlumb include, but before you make any calls to jsPlumb, for instance in this example we set the PaintStyle to be a blue line of 27 pixels: > jsPlumb.Defaults.PaintStyle = { lineWidth:27, strokeStyle:'blue' } + */ + this.Defaults = { + Anchor : null, + Anchors : [ null, null ], + BackgroundPaintStyle : null, + Connector : null, + Container : null, + DragOptions : { }, + DropOptions : { }, + Endpoint : null, + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : null }, + EndpointStyles : [ null, null ], + EndpointHoverStyle : null, + EndpointHoverStyles : [ null, null ], + HoverPaintStyle : null, + LabelStyle : { fillStyle : "rgba(0,0,0,0)", color : "black" }, + LogEnabled : true, + MaxConnections : null, + MouseEventsEnabled : false, + // TODO: should we have OverlayStyle too? + PaintStyle : { lineWidth : 10, strokeStyle : 'red' }, + Scope : "_jsPlumb_DefaultScope" + }; + if (_defaults) jsPlumb.extend(this.Defaults, _defaults); + + this.logEnabled = this.Defaults.LogEnabled; + + + EventGenerator.apply(this); + var _bb = this.bind; + this.bind = function(event, fn) { + if ("ready" === event && initialized) fn(); + else _bb(event, fn); + }; + + var _currentInstance = this; + var log = null; + + var repaintFunction = function() { + jsPlumb.repaintEverything(); + }; + var automaticRepaint = true; + function repaintEverything() { + if (automaticRepaint) + repaintFunction(); + }; + var resizeTimer = null; + + var initialized = false; + var connectionsByScope = {}; + /** + * map of element id -> endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + var endpointsByElement = {}; + var endpointsByUUID = {}; + var offsets = {}; + var offsetTimestamps = {}; + var floatingConnections = {}; + var draggableStates = {}; + var _mouseEventsEnabled = this.Defaults.MouseEventsEnabled; + var _draggableByDefault = true; + var canvasList = []; + var sizes = []; + var listeners = {}; // a map: keys are event types, values are lists of listeners. + var DEFAULT_SCOPE = 'DEFAULT'; + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + var _findIndex = function(a, v, b, s) { + var _eq = function(o1, o2) { + if (o1 === o2) + return true; + else if (typeof o1 == 'object' && typeof o2 == 'object') { + var same = true; + for ( var propertyName in o1) { + if (!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for ( var propertyName in o2) { + if (!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for ( var i = +b || 0, l = a.length; i < l; i++) { + if (_eq(a[i], v)) + return i; + } + return -1; + }; + + /** + * appends an element to the given parent, or the document body if no + * parent given. + */ + var _appendElement = function(canvas, parent) { + if (!parent) + document.body.appendChild(canvas); + else + jsPlumb.CurrentLibrary.appendElement(canvas, parent); + }; + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + var _timestamp = function() { return "" + (new Date()).getTime(); }; + + /** + * Draws an endpoint and its connections. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + var _draw = function(element, ui, timestamp) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (!timestamp) timestamp = _timestamp(); + if (endpoints) { + _updateOffset( { elId : id, offset : ui, recalc : false, timestamp : timestamp }); // timestamp is checked against last update cache; it is + // valid for one paint cycle. + var myOffset = offsets[id], myWH = sizes[id]; + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + var l = endpoints[i].connections; + for ( var j = 0; j < l.length; j++) { + l[j].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); // ...paint each connection. + // then, check for dynamic endpoint; need to repaint it. + var oIdx = l[j].endpoints[0] == endpoints[i] ? 1 : 0; + if (l[j].endpoints[oIdx].anchor.isDynamic && !l[j].endpoints[oIdx].isFloating()) { + var oId = oIdx == 0 ? l[j].sourceId : l[j].targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + // TODO i still want to make this faster. + var anchorLoc = l[j].endpoints[oIdx].anchor.compute( { + xy : [ oOffset.left, oOffset.top ], + wh : oWH, + element : l[j].endpoints[oIdx], + txy : [ myOffset.left, myOffset.top ], + twh : myWH, + tElement : endpoints[i] + }); + l[j].endpoints[oIdx].paint({ anchorLoc : anchorLoc }); + } + } + } + } + }; + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + var _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }; + + /** + * gets an Endpoint by uuid. + */ + var _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }; + + /** + * inits a draggable if it's not already initialised. + */ + var _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element) && !jsPlumb.CurrentLibrary.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + } + } + }; + + /** + * helper to create a canvas. + * + */ + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + _appendElement(canvas, params.container); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + _getId(canvas, params.uuid); + if (ie) { + // for IE we have to set a big canvas size. actually you can + // override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas + // you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + + var _newConnection = function(params) { + var connectionFunc = jsPlumb.Defaults.ConnectionType || Connection; + return new connectionFunc(params); + }; + + var _newEndpoint = function(params) { + var endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint; + return new endpointFunc(params); + }; + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + var _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }; + /** + * perform an operation on all elements. + */ + var _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }; + + /** + * helper to remove an element from the DOM. + */ + var _removeElement = function(element, parent) { + if (element != null) { + if (!parent) { + try { + document.body.removeChild(element); + } catch (e) { + } + } else { + jsPlumb.CurrentLibrary.removeElement(element, parent); + } + } + }; + /** + * helper to remove a list of elements from the DOM. + */ + var _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }; + /** + * helper method to remove an item from a list. + */ + var _removeFromList = function(map, key, value) { + if (key != null) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete (l[i]); + l.splice(i, 1); + return true; + } + } + } + return false; + }; + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }; + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + var _setVisible = function(el, state) { + _operation(_getAttribute(el, "id"), function(jpc) { + jpc.canvas.style.display = state; + }); + }; + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + var _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }; + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + var _toggleVisible = function(elId) { + _operation(elId, function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }; + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + var _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return; + } + if (recalc || offset == null) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + } + }; + +/** + * gets an id for the given element, creating and setting one if + * necessary. + */ + var _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else + id = "jsPlumb_" + _timestamp(); + _setAttribute(ele, "id", id); + } + return id; + }; + +/** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + var _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'jsPlumb function failed : ' + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'wrapped function failed : ' + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = '_jsPlumb_connector'; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = '_jsPlumb_endpoint'; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = '_jsPlumb_overlay'; + + /* + * Property: Anchors + * Default jsPlumb Anchors. These are supplied in the file jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script to form .jsPlumb-all-x.x.x.js. You can + * provide your own Anchors by supplying them in a script that is loaded after jsPlumb, for instance: > jsPlumb.Anchors.MyAnchor = { ....anchor code here. see the documentation. } + */ + this.Anchors = {}; + + /* + * Property: Connectors + * Default jsPlumb Connectors. These are supplied + * in the file jsPlumb-defaults-x.x.x.js, which is merged in with the + * main jsPlumb script to form .jsPlumb-all-x.x.x.js. You can + * provide your own Connectors by supplying them in a script that is + * loaded after jsPlumb, for instance: > jsPlumb.Connectors.MyConnector = { + * ....connector code here. see the documentation. } + */ + this.Connectors = {}; + + /* + * Property: Endpoints + * Default jsPlumb Endpoints. These are supplied in + * the file jsPlumb-defaults-x.x.x.js, which is merged in with the main + * jsPlumb script to form .jsPlumb-all-x.x.x.js. You can + * provide your own Endpoints by supplying them in a script that is + * loaded after jsPlumb, for instance: > jsPlumb.Endpoints.MyEndpoint = { + * ....endpoint code here. see the documentation. } + */ + this.Endpoints = {}; + + /* + * Property:Overlays + * Default jsPlumb Overlays such as Arrows and Labels. + * These are supplied in the file jsPlumb-defaults-x.x.x.js, which is + * merged in with the main jsPlumb script to form + * .jsPlumb-all-x.x.x.js. You can provide your own Overlays by + * supplying them in a script that is loaded after jsPlumb, for + * instance: > jsPlumb.Overlays.MyOverlay = { ....overlay code here. see + * the documentation. } + */ + this.Overlays = {}; + + /* + Function: addEndpoint + + Adds an Endpoint to a given element. + + Parameters: + + target - Element to add the endpoint to. either an element id, or a selector representing some element. + params - Object containing Endpoint options. For more information, see the docs for Endpoint's constructor. + referenceParams - Object containing more Endpoint options; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + The newly created Endpoint. + + See Also: + + */ + this.addEndpoint = function(target, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.endpointStyle = p.endpointStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + var el = _getElementObject(target), id = _getAttribute(el, "id"); + p.source = el; + _updateOffset({ elId : id }); + var e = _newEndpoint(p); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + return e; + }; + + /* + Function: addEndpoints + Adds a list of Endpoints to a given element. + + Parameters: + target - element to add the endpoint to. either an element id, or a selector representing some element. + endpoints - List of objects containing Endpoint options. one Endpoint is created for each entry in this list. + referenceParams - Object containing more Endpoint options; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created Endpoints, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(target, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + results.push(_currentInstance.addEndpoint(target, endpoints[i], referenceParams)); + } + return results; + }; + + /* + Function: animate + Wrapper around supporting library's animate function; injects a call to jsPlumb in the 'step' function (creating + it if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // probably, onComplete should repaint too. that will help + // keep up + // with fast animations. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /* + Function: connect + Establishes a connection between two elements (or Endpoints, which are themselves registered to elements). + + Parameters: + params - Object containing setup for the connection. See docs for Connection's constructor. + referenceParams - Optional object containing more params for the connection. Typically you would pass in data that a lot of connections are sharing here, such as connector style etc, and then use the main params for data specific to this connection. + + Returns: + The newly created Connection. + */ + this.connect = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not + // full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + // dynamic anchors. backwards compatibility here: from 1.2.6 onwards you don't need to specify "dynamicAnchors". the fact that some anchor consists + // of multiple definitions is enough to tell jsPlumb you want it to be dynamic. + if (_p.dynamicAnchors) { + // these can either be an array of anchor coords, which we will use for both source and target, or an object with {source:[anchors], target:[anchors]}, in which + // case we will use a different set for each element. + var a = _p.dynamicAnchors.constructor == Array; + var sa = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.source)); + var ta = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.target)); + _p.anchors = [sa,ta]; + } + + var jpc = _newConnection(_p); + // add to list of connections (by scope). + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + _currentInstance.fireUpdate("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + // force a paint + _draw(jpc.source); + + return jpc; + }; + + /* + Function: deleteEndpoint + Deletes an endpoint and removes all connections it has (which removes the connections from the other endpoints involved too) + + Parameters: + object - either an Endpoint object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElement(endpoint.canvas, endpoint.container); + // remove from endpointsbyElement + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + delete endpoint; + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every Endpoint, and their associated Connections, in this instance of jsPlumb. Do not unregister event listener (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc) { + _currentInstance.fireUpdate("jsPlumbConnectionDetached", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + }; + + /* + Function: detach + Removes a connection. takes either (source, target) (the old way, maintained for backwards compatibility), or a params + object with various possible values. + + Parameters: + source - id or element object of the first element in the connection. + target - id or element object of the second element in the connection. + params - a JS object containing the same parameters as you pass to jsPlumb.connect. If this is present then neither source nor + target should be present; it should be the only argument to the method. See the docs for Connection's constructor for information +about the parameters allowed in the params object. + Returns: + true if successful, false if not. + */ + this.detach = function(source, target) { + if (arguments.length == 2) { + var s = _getElementObject(source), sId = _getId(s); + var t = _getElementObject(target), tId = _getId(t); + _operation(sId, function(jpc) { + if ((jpc.sourceId == sId && jpc.targetId == tId) || (jpc.targetId == sId && jpc.sourceId == tId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + // this is the new version of the method, taking a JS object like + // the connect method does. + else if (arguments.length == 1) { + // TODO investigate whether or not this code still works when a user has supplied their own subclass of Connection. i suspect it may not. + if (arguments[0].constructor == Connection) { + //arguments[0].setHover(false); + arguments[0].endpoints[0].detachFrom(arguments[0].endpoints[1]); + } + else if (arguments[0].connection) { + //arguments[0].connection.setHover(false); + arguments[0].connection.endpoints[0].detachFrom(arguments[0].connection.endpoints[1]); + } + else { + var _p = jsPlumb.extend( {}, source); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1])); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source); + var targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + } + } + }; + + /* + Function: detachAll + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + + Returns: + void + */ + this.detachAllConnections = function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + }; + + /** + * @deprecated Use detachAllConnections instead. this will be removed in jsPlumb 1.3. + */ + this.detachAll = this.detachAllConnections; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + /** + * @deprecated use detachEveryConnection instead. this will be removed in jsPlumb 1.3. + */ + this.detachEverything = this.detachEveryConnection; + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of you library's draggable method so that jsPlumb can setup the appropriate callbacks. Your underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. + * Parameters: + * options - a JS object that holds options defining what sort of connections you're + * looking for. Valid values are: scope this may be a String or a list + * of Strings. jsPlumb will only return Connections whose scope matches + * what this option defines. If you omit it, you will be given + * Connections of every scope. source may be a string or a list of + * strings; constraints results to have only Connections whose source is + * this object. target may be a string or a list of strings; constraints + * results to have only Connections whose target is this object. + * + * The return value is a dictionary in this format: + * { 'scope1': [ {sourceId:'window1', targetId:'window2', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:}, + * {sourceId:'window3', targetId:'window4', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:}, + * {sourceId:'window1', targetId:'window3', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:} ], + * 'scope2': [ {sourceId:'window1', targetId:'window3', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:} ] } + * + */ + this.getConnections = function(options) { + var r = {}; + options = options || {}; + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') + r.push(input); + else + r = input; + } + return r; + }; + var scopes = prepareList(options.scope); + var sources = prepareList(options.source); + var targets = prepareList(options.target); + var filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + r[i] = []; + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + r[i].push({ sourceId : c.sourceId, targetId : c.targetId, source : c.source, target : c.target, sourceEndpoint : c.endpoints[0], targetEndpoint : c.endpoints[1], connection : c }); + } + } + } + return r; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + + Returns: + void + */ + this.hide = function(el) { + _setVisible(el, "none"); + }; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + var _bind = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && _mouseEventsEnabled) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + if (c[i][event](e)) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i][event](e)) return; + } + } + } + }); + }; + _bind("click"); + _bind("dblclick"); + _bind("mousemove"); + _bind("mousedown"); + _bind("mouseup"); + + initialized = true; + _currentInstance.fireUpdate("ready"); + }; + + /* + * Creates an anchor with the given params. + * + * You do not need to use this method. It is exposed because of the way jsPlumb is + * split into three scripts; this will change in the future. + * + * x - the x location of the anchor as a fraction of the + * total width. y - the y location of the anchor as a fraction of the + * total height. xOrientation - value indicating the general direction a + * connection from the anchor should go in, in the x direction. + * yOrientation - value indicating the general direction a connection + * from the anchor should go in, in the y direction. xOffset - a fixed + * offset that should be applied in the x direction that should be + * applied after the x position has been figured out. optional. defaults + * to 0. yOffset - a fixed offset that should be applied in the y + * direction that should be applied after the y position has been + * figured out. optional. defaults to 0. + * -- OR -- + * + * params - {x:..., y:..., xOrientation etc } + * -- OR FROM 1.2.4 --- + * + * name - the name of some Anchor in the _currentInstance.Anchors array. + * -- OR FROM 1.2.4 --- + * + * coords - a list of coords for the anchor, like you would pass to + * jsPlumb.makeAnchor (eg [0.5,0.5,0,-1] - an anchor in the center of + * some element, oriented towards the top of the screen) + * -- OR FROM 1.2.4 --- + * + * anchor - an existing anchor. just gets passed back. it's handy + * internally to have this functionality. + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed + // in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards + // compatibility if we are given only one value we assume it's a + // call in the old form. + if (arguments.length == 0) return null; + var params = {}; + if (arguments.length == 1) { + var specimen = arguments[0]; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; + // is it the name of an anchor type? + else if (typeof specimen == "string") return _currentInstance.Anchors[arguments[0]](); + // is it an array of coordinates? + else if (specimen.constructor == Array) { + if (specimen[0].constructor == Array || specimen[0].constructor == String) + return new DynamicAnchor(specimen); + else + return jsPlumb.makeAnchor.apply(this, specimen); + } + // last we try the backwards compatibility stuff. + else if (typeof arguments[0] == "object") jsPlumb.extend(params, x); + } else { + params = { x : x, y : y }; + if (arguments.length >= 4) params.orientation = [ arguments[2], arguments[3] ]; + if (arguments.length == 6) params.offsets = [ arguments[4], arguments[5] ]; + } + var a = new Anchor(params); + a.clone = function() { + return new Anchor(params); + }; + return a; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types) { + var r = []; + for ( var i = 0; i < types.length; i++) + if (typeof types[i] == "string") + r.push(_currentInstance.Anchors[types[i]]()); + else if (types[i].constructor == Array) + r.push(jsPlumb.makeAnchor(types[i])); + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + for ( var i in ebe) + _currentInstance.deleteEndpoint(ebe[i]); + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + this.deleteEveryEndpoint(); + this.clearListeners(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + + Parameters: + value - whether or not to automatically repaint when the window is resized. + + Returns: void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + * Function: setDefaultNewCanvasSize + * Sets the default size jsPlumb will use for a new canvas (we create a square canvas so one value is all + * that is required). This is a hack for IE, because ExplorerCanvas + * seems to need for a canvas to be larger than what you are going to + * draw on it at initialisation time. The default value of this is 1200 + * pixels, which is quite large, but if for some reason you're drawing + * connectors that are bigger, you should adjust this value + * appropriately. + * + * Parameters: + * size - The default size to use. jsPlumb will use a square canvas so you need only supply one value. + * + * Returns: + * void + */ + this.setDefaultNewCanvasSize = function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }; + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setDraggableByDefault + * Sets whether or not elements are draggable by default. Default for this is true. + * + * Parameters: + * draggable - value to set + * + * Returns: + * void + */ + this.setDraggableByDefault = function(draggable) { + _draggableByDefault = draggable; + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction + * Sets the function to fire when the window size has changed and a repaint was fired. + * + * Parameters: + * f - Function to execute. + * + * Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: setMouseEventsEnabled + * Sets whether or not mouse events are enabled. By default they are not; this is just because jsPlumb has to add mouse listeners + * to the document, which may result in a performance hit a user does not need. + * + * Parameters: + * enabled - whether or not mouse events should be enabled. + * + * Returns: + * void + */ + this.setMouseEventsEnabled = function(enabled) { + _mouseEventsEnabled = enabled; + }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * + * Returns: + * void + */ + this.show = function(el) { + _setVisible(el, "block"); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload + * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element. + * + * Returns: + * void + */ + this.unload = function() { + delete endpointsByElement; + delete endpointsByUUID; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + delete canvasList; + }; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + if (timestamp && timestamp === self.timestamp) { + return lastReturnValue; + } + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + var container = element ? element.container : null; + var containerAdjustment = { left : 0, top : 0 }; + if (container != null) { + var eo = _getElementObject(container); + var o = _getOffset(eo); + var sl = jsPlumb.CurrentLibrary.getScrollLeft(eo); + var st = jsPlumb.CurrentLibrary.getScrollTop(eo); + containerAdjustment.left = o.left - sl; + containerAdjustment.top = o.top - st; + lastReturnValue[0] = lastReturnValue[0] - containerAdjustment.left; + lastReturnValue[1] = lastReturnValue[1] - containerAdjustment.top; + } + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(_getElementObject(refCanvas)); + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + var orientation = null; + var _lastResult = null; + + this.compute = function(params) { + var xy = params.xy, el = params.element; + var result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + if (el.container != null) { + var o = _getOffset(el.container); + result[0] = result[0] - o.left; + result[1] = result[1] - o.top; + } + _lastResult = result; + return result; + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { orientation = anchor.getOrientation(); }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchors is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = anchors || []; + var _convert = function(anchor) { return anchor.constructor == Anchor ? anchor: jsPlumb.makeAnchor(anchor); }; + for (var i = 0; i < _anchors.length; i++) _anchors[i] = _convert(_anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null; + var _curIndex = _anchors.length > 0 ? 0 : -1; + this.locked = false; + var self = this; + + // helper method to calculate the distance between the centers of the two elements. + var _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }; + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + var _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + + var pos = _curAnchor.compute(params); + return pos; + }; + + this.getCurrentLocation = function() { + var cl = _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + return cl; + }; + + this.getOrientation = function() { return _curAnchor != null ? _curAnchor.getOrientation() : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * container - id of the containing div for this connection. optional; jsPlumb uses the default (which you can set, but which is the body by default) otherwise. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * backgroundPaintStyle - Parameters defining the appearance of the background of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * overlays - Optional array of Overlay definitions to appear on this Connection. + */ + var Connection = function(params) { + + EventGenerator.apply(this); + // ************** get the source and target and register the connection. ******************* + var self = this; + var visible = true; + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + }; + var id = new String('_jsplumb_c_' + (new Date()).getTime()); + this.getId = function() { return id; }; + this.container = params.container || _currentInstance.Defaults.Container; // may be null; we will append to the body if so. + // get source and target as jQuery objects + /** + Property: source + The source element for this Connection. + */ + this.source = _getElementObject(params.source); + /** + Property:target + The target element for this Connection. + */ + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. + if (params.sourceEndpoint) this.source = params.sourceEndpoint.getElement(); + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + /* + * Property: sourceId + * Id of the source element in the connection. + */ + this.sourceId = _getAttribute(this.source, "id"); + /* + * Property: targetId + * Id of the target element in the connection. + */ + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + this.getAttachedElements = function() { + return self.endpoints; + }; + + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams) { + if (anchorParams) + return jsPlumb.makeAnchor(anchorParams); + }; + var prepareEndpoint = function(existing, index, params, element) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint + || new jsPlumb.Endpoints.Dot(); + if (ep.constructor == String) + ep = new jsPlumb.Endpoints[ep](); + else if (ep.constructor == Array) { + ep = new jsPlumb.Endpoints[ep[0]](ep[1]); + } + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + var a = params.anchors ? params.anchors[index] : _makeAnchor(_currentInstance.Defaults.Anchors[index]) || _makeAnchor(jsPlumb.Defaults.Anchors[index]) || _makeAnchor(_currentInstance.Defaults.Anchor) || _makeAnchor(jsPlumb.Defaults.Anchor) || _makeAnchor("BottomCenter"); + var u = params.uuids ? params.uuids[index] : null; + var e = _newEndpoint( { paintStyle : es, hoverPaintStyle:ehs, endpoint : ep, connections : [ self ], uuid : u, anchor : a, source : element, container : self.container }); + self.endpoints[index] = e; + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector) + */ + this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || _currentInstance.Defaults.Connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + if (this.connector.constructor == String) + this.connector = new jsPlumb.Connectors[this.connector](); // lets you use a string as shorthand. + else if (this.connector.constructor == Array) + this.connector = new jsPlumb.Connectors[this.connector[0]](this.connector[1]); + this.paintStyle = this.endpoints[0].connectorStyle || this.endpoints[1].connectorStyle || params.paintStyle || _currentInstance.Defaults.PaintStyle || jsPlumb.Defaults.PaintStyle; + var backgroundPaintStyle = this.endpoints[0].connectorBackgroundStyle || this.endpoints[1].connectorBackgroundStyle || params.backgroundPaintStyle || _currentInstance.Defaults.BackgroundPaintStyle || jsPlumb.Defaults.BackgroundPaintStyle; + this.hoverPaintStyle = this.endpoints[0].connectorHoverStyle || this.endpoints[1].connectorHoverStyle || params.hoverPaintStyle || _currentInstance.Defaults.HoverPaintStyle || jsPlumb.Defaults.HoverPaintStyle; + this.paintStyleInUse = this.paintStyle; + + /* + * Property: overlays + * List of Overlays for this Connection. + */ + this.overlays = []; + if (params.overlays) { + for (var i = 0; i < params.overlays.length; i++) { + var o = params.overlays[i]; + if (o.constructor == Array) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0]; + var p = jsPlumb.CurrentLibrary.extend({connection:self}, o[1]); // make a copy of the object so as not to mess up anyone else's reference... + if (o.length == 3) jsPlumb.CurrentLibrary.extend(p, o[2]); + this.overlays.push(new jsPlumb.Overlays[type](p)); + } else if (o.constructor == String) { + this.overlays.push(new jsPlumb.Overlays[o]({connection:self})); + } + else this.overlays.push(o); + } + } + var overlayPlacements = []; + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + this.addOverlay = function(overlay) { overlays.push(overlay); }; + + // this is a shortcut helper method to let people add a label as + // overlay. + this.labelStyle = params.labelStyle || _currentInstance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + if (this.label) { + this.overlays.push(new jsPlumb.Overlays.Label( { + labelStyle : this.labelStyle, + label : this.label, + connection:self + })); + } + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String or a Function that returns a String. + */ + this.setLabel = function(l) { + self.label = l; + _currentInstance.repaint(self.source); + }; + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var otherOffset = offsets[this.targetId]; + var otherWH = sizes[this.targetId]; + var anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1] + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0] + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc }); + + var canvas = _newCanvas({"class":jsPlumb.connectorClass, container:self.container}); + this.canvas = canvas; + + /* + * Function: setBackgroundPaintStyle + * Sets the Connection's background paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + this.setBackgroundPaintStyle = function(style) { + backgroundPaintStyle = style; + self.repaint(); + }; + + /* + * Paints the connection. + * + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp; + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = false; + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + if (this.canvas.getContext) { + _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }); + _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + var ctx = canvas.getContext('2d'); + var sAnchorP = this.endpoints[sIdx].anchor.getCurrentLocation(); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.getCurrentLocation(); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + + // paint overlays + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + var s = o.computeMaxSize(self.connector, ctx); + if (s > maxSize) + maxSize = s; + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, self.paintStyleInUse.lineWidth, maxSize); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + + var _paintOneStyle = function(ctx, aStyle) { + ctx.save(); + jsPlumb.extend(ctx, aStyle); + if (aStyle.gradient && !ie) { + var g = self.connector.createGradient(dim, ctx, (elId == this.sourceId)); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + self.connector.paint(dim, ctx); + ctx.restore(); + }; + + // first check for the background style + if (backgroundPaintStyle != null) { + _paintOneStyle(ctx, backgroundPaintStyle); + } + _paintOneStyle(ctx, self.paintStyleInUse); + + // paint overlays + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + self.overlayPlacements[i] = o.draw(self.connector, ctx, self.paintStyleInUse); + } + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function() { + this.paint({ elId : this.sourceId, recalc : true }); + }; + + _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide + // whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + + //_registerConnection(self); + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have one to N connections emanating from it (although how to handle that in the UI is a very good question). also has a Canvas and paint style. + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * connections - optional list of Connections to configure the Endpoint with. + * container - optional element (as a string id) to use as the container for the canvas associated with this Endpoint. If not supplied, jsPlumb uses the default, which is the document body. A better way to use this container functionality is to set it on the defaults (jsPlumb.Defaults.Container="someElement"). + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorBackgroundStyle - if isSource is set to true, this is the background paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", 160 ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + EventGenerator.apply(this); + params = params || {}; + var self = this; + var visible = true; + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + var id = new String('_jsplumb_e_' + (new Date()).getTime()); + this.getId = function() { return id; }; + if (params.dynamicAnchors) + self.anchor = new DynamicAnchor(jsPlumb.makeAnchors(params.dynamicAnchors)); + else + self.anchor = params.anchor ? jsPlumb.makeAnchor(params.anchor) : params.anchors ? jsPlumb.makeAnchor(params.anchors) : jsPlumb.makeAnchor("TopCenter"); + var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot(); + if (_endpoint.constructor == String) + _endpoint = new jsPlumb.Endpoints[_endpoint](); + else if (_endpoint.constructor == Array) + _endpoint = new jsPlumb.Endpoints[_endpoint[0]](_endpoint[1]); + self.endpoint = _endpoint; + this.paintStyle = params.paintStyle || params.style || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + this.hoverPaintStyle = params.hoverPaintStyle || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + this.paintStyleInUse = this.paintStyle; + this.connectorStyle = params.connectorStyle; + this.connectorBackgroundStyle = params.connectorBackgroundStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + var _element = params.source, _uuid = params.uuid; + var floatingEndpoint = null, inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + this.container = params.container || _currentInstance.Defaults.Container || jsPlumb.Defaults.Container; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + /* + * Property: canvas + * The Endpoint's Canvas. + */ + this.canvas = params.canvas || _newCanvas({"class":jsPlumb.endpointClass, container:this.container, uuid:params.uuid}); + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + this.connections = params.connections || []; + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + var _reattach = params.reattach || false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + // this avoids a circular loop + if (!ignoreTarget) { + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + t.detach(connection, true); + } + _removeElement(connection.canvas, connection.container); + _removeFromList(connectionsByScope, connection.scope, connection); + if(!ignoreTarget) fireDetachEvent(connection); + } + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + */ + this.detachAll = function() { + while (self.connections.length > 0) { + self.detach(self.connections[0]); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + */ + this.detachFrom = function(targetEndpoint) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + c[i].setHover(false); + self.detach(c[i]); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + var e = _newEndpoint( { anchor : self.anchor, source : _element, paintStyle : this.paintStyle, endpoint : _endpoint }); + return e; + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + return (self.connections.length < _maxConnections) ? null : self.connections[0]; + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + + /* + * Function: setPaintStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + */ + this.setPaintStyle = this.setPaintStyle; // i do this so NaturalDocs can pick up the function definition. + + /* + * Function: setHoverPaintStyle + * Sets the hover paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * + * Parameters: + * style - Style object to set, for example { fillStyle:"yellow" }. + */ + this.setHoverPaintStyle = this.setHoverPaintStyle; // i do this so NaturalDocs can pick up the function definition. + + /* + * Sets the paint style of the Endpoint. + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + /* + * + * Paints the Endpoint, recalculating offset and anchor positions if necessary. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + + params = params || {}; + var timestamp = params.timestamp; + if (!timestamp || self.timestamp !== timestamp) { + var ap = params.anchorPoint, canvas = params.canvas, connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + // do we always want to force a repaint here? i dont + // think so! + var xy = params.offset || offsets[_elementId]; + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (self.anchor.isDynamic) { + if (self.connections.length > 0) { + var c = self.connections[0]; + var oIdx = c.endpoints[0] == self ? 1 : 0; + var oId = oIdx == 0 ? c.sourceId : c.targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + } + ap = self.anchor.compute(anchorParams); + } + _endpoint.paint(ap, self.anchor.getOrientation(), canvas || self.canvas, self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + self.timestamp = timestamp; + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + var start = function() { + jpc = self.connectorSelector(); + if (self.isFull() && !dragAllowedWhenFull) return false; + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + var nE = _getElementObject(n); + _appendElement(n, self.container); // + // create and assign an id, and initialize the offset. + var id = _getId(nE); + _updateOffset( { elId : id }); + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor( { reference : self.anchor, referenceCanvas : self.canvas }); + floatingEndpoint = _newEndpoint({ paintStyle : { fillStyle : 'rgba(0,0,0,0)' }, endpoint : _endpoint, anchor : floatingAnchor, source : nE }); + + if (jpc == null) { + self.anchor.locked = true; + // create a connection. one end is this endpoint, the + // other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : _getElementObject(_element), + target : _getElementObject(n), + anchors : [ self.anchor, floatingAnchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + backgroundPaintStyle:params.connectorBackgroundStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays + }); + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.setHover(false); + } else { + existingJpc = true; + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.setHover(false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas)); + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; // are we the source or the target? + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas); + var dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // get a new, temporary scope, to use (issue 57) + var newScope = "scope_" + (new Date()).getTime(); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + // set the new, temporary scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(i, newScope); + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // TODO unregister on stop? or will floating endpoint's + // destruction be assured. + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be + // dragging the source at any time + // before this connection is either discarded or made into a + // permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var dragOptions = params.dragOptions || {}; + var defaultOpts = jsPlumb.extend( {}, jsPlumb.CurrentLibrary.defaultDragOptions); + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + var startEvent = jsPlumb.CurrentLibrary.dragEvents['start']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], + function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + jsPlumb.CurrentLibrary.setOffset(n, _ui); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements( [ n, floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (_reattach) { + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } else { + jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc); // the main endpoint will inform the floating endpoint + // to disconnect, and also post the detached event. + } + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElement(jpc.canvas, self.container); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint(); + jpc.repaint(); + jpc = null; + delete inPlaceCopy; + delete endpointsByElement[floatingEndpoint.elementId]; + delete floatingEndpoint; + + _currentInstance.currentlyDragging = false; + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas) { + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + var drop = function() { + var draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)); + var id = _getAttribute(draggable, "dragId"); + var elId = _getAttribute(draggable, "elId"); + + // restore the original scope if necessary (issue 57) + var scope = _getAttribute(draggable, "originalScope"); + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // todo test that the target is not full. + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + if (!jpc.suspendedEndpoint) { // if a new connection, add it. TODO: move this to a jsPlumb internal method - addConnection or something. doesnt need to be exposed. + _addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + _currentInstance.fireUpdate("jsPlumbConnectionDetached", { + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }); + } + + jsPlumb.repaint(elId); + + _currentInstance.fireUpdate("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], + targetEndpoint : jpc.endpoints[1], + connection:jpc + }); + } + + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute( _getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + }); + + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas)); + + return self; + }; + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + //if (_defaults) jsPlumb.extend(j.Defaults, _defaults); + return j; + }; +})(); +/* +* jsPlumb-defaults-1.2.6-RC1 +* +* This script contains the default Anchors, Endpoints, Connectors and Overlays for jsPlumb. It should be used with jsPlumb 1.1.0 and above; +* prior to version 1.1.0 of jsPlumb the defaults were included inside the main script. +* +* NOTE: for production usage you should use jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, +* in a minified file. +* +* Dual licensed under MIT and GPL2. +*/ + +(function() { + + var ie = !!!document.createElement('canvas').getContext; + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * getOrientation : function() { return [ox, oy]; } + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + var _curryAnchor = function(x,y,ox,oy) { + return function() { + return jsPlumb.makeAnchor(x,y,ox,oy); + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1); + + + jsPlumb.Defaults.DynamicAnchors = function() { + return jsPlumb.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"]); + }; + jsPlumb.Anchors["AutoDefault"] = function() { return jsPlumb.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors()); }; + + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + var currentPoints = null; + var _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + + _dx = _tx - _sx, _dy = (_ty - _sy); + _m = _dy / _dx, _m2 = -1 / _m; + + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + + return currentPoints; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + var xp = _sx + (location * _dx); + var yp = (_m == Infinity || _m == -Infinity) ? _sy + (location * (_ty - _sy)) : (_m * xp) + _b; + return {x:xp, y:yp}; + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { return _m; }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + var orientation = distance > 0 ? 1 : -1; + var y = Math.abs(distance * Math.sin(_theta)); + if (_sy > _ty) y = y * -1; + var x = Math.abs(distance * Math.cos(_theta)); + if (_sx > _tx) x = x * -1; + return {x:p.x + (orientation * x), y:p.y + (orientation * y)}; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = self.gradientAtPoint(p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + /** + * This Connector draws a Bezier curve with two control points. + * + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * note that the method signature changed in 1.2.6 to take a params object, so the method + * argument was renamed. you can still provide just an integer to this constructor, though the + * preferred method is to use {curviness:XXX}. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + this.majorAnchor = 150; + // backwards compatibility (ideally we'd just use params.curviness || 150). + if (params) { + if (params.constructor == Number) this.majorAnchor = params; + else if (params.curviness) this.majorAnchor = params.curviness; + } + this.minorAnchor = 10; + var currentPoints = null; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h; + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) + { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx); var minx2 = Math.min(_CP[0], _CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(_sx,_tx); var maxx2 = Math.max(_CP[0], _CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty); var miny2 = Math.min(_CP[1], _CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(_sy,_ty); var maxy2 = Math.max(_CP[1], _CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + this.paint = function(d, ctx) { + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + return jsBezier.perpendicularToCurveAt(_makeCurve(), location, length, distance); + }; + + this.createGradient = function(dim, ctx, swap) { + return (swap) ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + + /** + * Types of endpoint UIs. we supply four - a circle of default radius 10px, a rectangle of + * default size 20x20, an image (with no default), and a Triangle, of default size 15. + * you can supply others of these if you want to - see the documentation for a howto. + */ + + /** + * a round endpoint, with default radius 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }; + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + if (endpointStyle.gradient && !ie) { + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * A Rectangular endpoint, with default size 20x20. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + jsPlumb.Endpoints.Triangle = function(params) { + + params = params || { width:55, height:55 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - width/2; + var y = anchorPoint[1] - height/2; + + jsPlumb.sizeCanvas(canvas, x, y, width, height); + + var ctx = canvas.getContext('2d'); + var offsetX = 0, offsetY = 0, angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = endpointStyle.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' or, since 1.2.4, a 'src' property in the params object.. + */ + jsPlumb.Endpoints.Image = function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.src || params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + }; + + /** + * An arrow overlay. you can provide: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + params = params || {}; + var self = this; + this.length = params.length || 20; + this.width = params.width || 20; + this.connection = params.connection; + var direction = (params.direction || 1) < 0 ? -1 : 1; + /*var fillStyle = params.fillStyle; + var strokeStyle = params.strokeStyle; + var lineWidth = params.lineWidth || 1;*/ + var paintStyle = params.paintStyle || { lineWidth:1 }; + this.loc = params.location == null ? 0.5 : params.location; + // how far along the arrow the lines folding back in come to. default is 62.3%. + var foldback = params.foldback || 0.623; + var _getFoldBackPoint = function(connector, loc) { + if (foldback == 0.5) return connector.pointOnPath(loc); + else { + var adj = 0.5 - foldback; // we calculate relative to the center + return connector.pointAlongPathFrom(loc, direction * self.length * adj); + } + }; + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.draw = function(connector, ctx, currentConnectionPaintStyle) { + + // this is the arrow head position + var hxy = connector.pointAlongPathFrom(self.loc, direction * (self.length / 2)); + // this is the center of the tail + var txy = connector.pointAlongPathFrom(self.loc, -1 * direction * (self.length / 2)), tx = txy.x, ty = txy.y; + // this is the tail vector + var tail = connector.perpendicularToPathAt(self.loc, self.width, -1 * direction * (self.length / 2)); + // this is the point the tail goes in to + var cxy = _getFoldBackPoint(connector, self.loc); + + // if loc = 1, then hxy should be flush with the element. + if (self.loc == 1) { + var lxy = connector.pointOnPath(self.loc); + var dx = lxy.x - hxy.x, dy = lxy.y - hxy.y; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + // if loc = 0, then tail midpoint should be flush with the element. + if (self.loc == 0) { + var lxy = connector.pointOnPath(self.loc); + var tailMid = foldback > 1 ? cxy : { + x:tail[0].x + ((tail[1].x - tail[0].x) / 2), + y:tail[0].y + ((tail[1].y - tail[0].y) / 2) + }; + var dx = lxy.x - tailMid.x, dy = lxy.y - tailMid.y; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x); + var maxx = Math.max(hxy.x, tail[0].x, tail[1].x); + var miny = Math.min(hxy.y, tail[0].y, tail[1].y); + var maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + ctx.lineWidth = paintStyle.lineWidth; + ctx.beginPath(); + ctx.moveTo(hxy.x, hxy.y); + ctx.lineTo(tail[0].x, tail[0].y); + ctx.lineTo(cxy.x, cxy.y); + ctx.lineTo(tail[1].x, tail[1].y); + ctx.lineTo(hxy.x, hxy.y); + ctx.closePath(); + + if (paintStyle.strokeStyle) { + ctx.strokeStyle = paintStyle.strokeStyle; + ctx.stroke(); + } + ctx.fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle; + ctx.fill(); + + + + return [ minx, maxx, miny, maxy]; + }; + }; + + /** + * a basic arrow. this is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. See Arrow for params. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + /** + * a diamond. like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. See Arrow for params. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40; + var p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + /** + * A Label overlay. Params you can provide: + * + * labelStyle - js object containing style instructions for the label. defaults to jsPlumb.Defaults.LabelStyle. + * label - the label to paint. may be a string or a function that returns a string. nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * borderWidth - width of a border to paint. defaults to zero. + * borderStyle - strokeStyle to use when painting the border, if necessary. + */ + jsPlumb.Overlays.Label = function(params) { + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + this.connection = params.connection; + var self = this; + var labelWidth = null, labelHeight = null, labelText = null, labelPadding = null; + this.location = params.location || 0.5; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var _textDimensions = function(ctx) { + if (self.cachedDimensions) return self.cachedDimensions; // return cached copy if we can. if we add a setLabel function remember to clear the cache. + labelText = typeof self.label == 'function' ? self.label(self) : self.label; + var d = {}; + if (labelText) { + var lines = labelText.split(/\n|\r\n/); + ctx.save(); + if (self.labelStyle.font) ctx.font = self.labelStyle.font; + var t = _widestLine(lines, ctx); + // a fake text height measurement: use the width of upper case M + var h = ctx.measureText("M").width; + labelPadding = self.labelStyle.padding || 0.25; + labelWidth = t + (2 * t * labelPadding); + labelHeight = (lines.length * h) + (2 * h * labelPadding); + var textHeight = lines.length * h; + ctx.restore(); + d = {width:labelWidth, height:labelHeight, lines:lines, oneLine:h, padding:labelPadding, textHeight:textHeight}; + } + if (typeof self.label != 'function') self.cachedDimensions = d; // cache it if we can. + return d; + }; + this.computeMaxSize = function(connector, ctx) { + var td = _textDimensions(ctx); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + var _widestLine = function(lines, ctx) { + var max = 0; + for (var i = 0; i < lines.length; i++) { + var t = ctx.measureText(lines[i]).width; + if (t > max) max = t; + } + return max; + }; + + this.draw = function(connector, ctx, currentConnectionPaintStyle) { + var td = _textDimensions(ctx); + if (td.width) { + var cxy = connector.pointOnPath(self.location); + if (self.labelStyle.font) ctx.font = self.labelStyle.font; + if (self.labelStyle.fillStyle) + ctx.fillStyle = self.labelStyle.fillStyle; + else + ctx.fillStyle = "rgba(0,0,0,0)"; + + var minx = cxy.x - (td.width / 2); + var miny = cxy.y - (td.height / 2); + + ctx.fillRect(minx, miny , td.width , td.height ); + + if (self.labelStyle.color) ctx.fillStyle = self.labelStyle.color; + ctx.textBaseline = "middle"; + ctx.textAlign = "center"; + for (i = 0; i < td.lines.length; i++) { + ctx.fillText(td.lines[i],cxy.x, cxy.y - (td.textHeight / 2) + (td.oneLine/2) + (i*td.oneLine)); + } + + // border + if (self.labelStyle.borderWidth > 0) { + ctx.strokeStyle = self.labelStyle.borderStyle || "black"; + ctx.strokeRect(minx, miny, td.width , td.height ); + } + + return [minx, minx+td.width, miny, miny+td.height]; + } + else return [0,0,0,0]; + }; + }; + + /** + * an image overlay. params may contain: + * + * location : proportion along the connector to draw the image. optional. + * src : image src. required. + * events : map of event names to functions; each event listener will be bound to the img. + */ + jsPlumb.Overlays.Image = function(params) { + var self = this; + this.location = params.location || 0.5; + this.img = new Image(); + this.connection = params.connection; + var imgDiv = null; + var notReadyInterval = null; + var notReadyConnector, notReadyContext; + var events = params.events || {}; + var _init = function() { + if (self.ready) { + window.clearInterval(notReadyInterval); + imgDiv = document.createElement("img"); + imgDiv.src = self.img.src; + imgDiv.style.position = "absolute"; + imgDiv.style.display="none"; + imgDiv.className = "_jsPlumb_overlay"; + document.body.appendChild(imgDiv);// HMM + // attach events + for (var e in events) { + jsPlumb.CurrentLibrary.bind(imgDiv, e, events[e]); + } + if (notReadyConnector && notReadyContext) { + _draw(notReadyConnector, notReadyContext); + notReadyContext = null; + notReadyConnector = null; + } + } + }; + this.img.onload = function() { + self.ready = true; + // jsPlumb.repaintAll(); + }; + this.img.src = params.src || params.url; + + notReadyInterval = window.setInterval(_init, 250); + + this.computeMaxSize = function(connector, ctx) { + return [ self.img.width, self.img.height ]; + }; + + var _draw = function(connector, ctx, currentConnectionPaintStyle) { + if (imgDiv != null) { + var cxy = connector.pointOnPath(self.location); + var canvas = jsPlumb.CurrentLibrary.getElementObject(ctx.canvas); + var canvasOffset = jsPlumb.CurrentLibrary.getOffset(canvas); + var minx = cxy.x - (self.img.width/2); + var miny = cxy.y - (self.img.height/2); + var o = {left:canvasOffset.left + minx, top:canvasOffset.top + miny}; + jsPlumb.CurrentLibrary.setOffset(imgDiv, o); + imgDiv.style.display = "block"; + return [minx,minx + self.img.width, miny, miny+self.img.height]; + } + }; + + this.draw = function(connector, ctx) { + if (self.ready) + return _draw(connector, ctx); + else { + notReadyConnector = connector; + notReadyContext = ctx; + return [0,0,0,0]; + } + }; + }; +})();;(function() { + + jsPlumb.Connectors.Flowchart = function(params) { + params = params || {}; + var self = this, + minStubLength = params.minStubLength || 30, + segments = [], + segmentGradients = [], + segmentProportions = [], + segmentLengths = [], + segmentProportionalLengths = [], + points = [], + swapX, + swapY, + /** + * recalculates the gradients of each segment, and the points at which the segments begin, proportional to the total length travelled + * by all the segments that constitute the connector. + */ + updateSegmentGradientsAndProportions = function(startX, startY, endX, endY) { + var total = 0; + for (var i = 0; i < segments.length; i++) { + var sx = i == 0 ? startX : segments[i][2], + sy = i == 0 ? startY : segments[i][3], + ex = segments[i][0], + ey = segments[i][1]; + + segmentGradients[i] = sx == ex ? Infinity : 0; + segmentLengths[i] = Math.abs(sx == ex ? ey - sy : ex - sx); + total += segmentLengths[i]; + } + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segmentLengths[i] / total; + segmentProportions[i] = [curLoc, (curLoc += (segmentLengths[i] / total)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0]; + var ly = segments.length == 0 ? sy : segments[segments.length - 1][1]; + segments.push([x, y, lx, ly]); + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment that contains the point which is 'location' distance along the entire path, where 'location' is + * a decimal between 0 and 1 inclusive. in this connector type paths are made up of a list of segments, each of which contributes some fraction to + * the total length. + */ + findSegmentForLocation = function(location) { + var idx = segmentProportions.length - 1, inSegmentProportion = 0; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + segments = []; + segmentGradients = []; + segmentProportionalLengths = []; + segmentLengths = []; + segmentProportionals = []; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + offx = (lw / 2) + (minStubLength * 2), + offy = (lw / 2) + (minStubLength * 2), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(), + to = targetAnchor.orientation || targetAnchor.getOrientation(), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx, + h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy; + if (w < minWidth) { + offx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + offy += (minWidth - h) / 2; + h = minWidth; + } + sx = swapX ? w-offx : offx, + sy = swapY ? h-offy : offy, + tx = swapX ? offx : w-offx , + ty = swapY ? offy : h-offy, + startStubX = sx + (so[0] * minStubLength), + startStubY = sy + (so[1] * minStubLength), + endStubX = tx + (to[0] * minStubLength), + endStubY = ty + (to[1] * minStubLength), + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2); + + x -= offx; y -= offy; + points = [x, y, w, h, sx, sy, tx, ty], extraPoints = []; + + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + if (so[0] == 0) { + var startStubIsBeforeEndStub = startStubY < endStubY; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } + } + else { + var startStubIsBeforeEndStub = startStubX < endStubX; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } + } + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentGradientsAndProportions(sx, sy, tx, ty); + + return points; + }; + + this.paint = function(dimensions, ctx) { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location) { + return self.pointAlongPathFrom(location, 0); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location) { + return segmentGradients[findSegmentForLocation(location)["index"]]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance) { + var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segmentLengths[s.index], m = segmentGradients[s.index]; + var e = { + //x : m == Infinity ? seg[2] : /*swapX ? seg[2] - (p * sl) - distance : */seg[2] + (p * sl) + distance, + + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + + + //y : m == 0 ? seg[3] : /*swapY ? seg[3] - (p * sl) - distance : */seg[3] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + //console.log("pointalongpath, swapX =" + swapX + ",swapY=" + swapY, "loc", location, "travel", (p * sl), "dist", distance, e.x, e.y, "seg", seg, "len", sl, "prop.", p); + + return e; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = segmentGradients[p.segmentInfo.index]; + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + + }; + }; +})();/* + * jquery.jsPlumb 1.2.6-RC1 + * + * jQuery specific functionality for jsPlumb. + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + * NOTE: for production usage you should use jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, + * in a minified file. + * + * Dual licensed under MIT and GPL2. + * + */ +/* + * the library agnostic functions, such as find offset, get id, get attribute, extend etc. + * the full list is: + * + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getPageXY gets the page event's xy location. + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * hasClass returns whether or not the given element has the given class. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + */ +(function($) { + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * appends the given child to the given parent. + */ + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).append(child); + }, + + /** + * event binding wrapper. it just so happens that jQuery uses 'bind' also. yui3, for example, + * uses 'on'. + */ + bind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.bind(event, callback); + }, + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step', + 'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete' + }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + getDocumentElement : function() { return document; }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + }, + + getDragScope : function(el) { + return el.draggable("option", "scope"); + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + * + */ + getElementObject : function(el) { + return typeof(el)=='string' ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getScrollLeft : function(el) { + return el.scrollLeft(); + }, + + getScrollTop : function(el) { + return el.scrollTop(); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[1], _offset = ui.offset; + return _offset || ui.absolutePosition; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options) { + // remove helper directive if present. + options.helper = null; + //TODO: if 'revert' is set on the options it causes end points to animate back to + // where they came from, if the connection is aborted. do we care? probably not. + // the todo is to decide whether we care or not. + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.draggable(options); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.droppable(options); + }, + + isAlreadyDraggable : function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return el.hasClass("ui-draggable"); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).remove(); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * sets the drag scope. probably time for a setDragOption method (roll this and the one above together) + * @param el + * @param scope + */ + setDragScope : function(el, scope) { + el.draggable("option", "scope", scope); + }, + + setOffset : function(el, o) { + jsPlumb.CurrentLibrary.getElementObject(el).offset(o); + } + }; + + $(document).ready(jsPlumb.init); +})(jQuery); +(function(){if(typeof Math.sgn=="undefined")Math.sgn=function(a){return a==0?0:a>0?1:-1};var p={subtract:function(a,b){return{x:a.x-b.x,y:a.y-b.y}},dotProduct:function(a,b){return a.x*b.x+a.y*b.y},square:function(a){return Math.sqrt(a.x*a.x+a.y*a.y)},scale:function(a,b){return{x:a.x*b,y:a.y*b}}},y=Math.pow(2,-65),u=function(a,b){for(var g=[],d=b.length-1,h=2*d-1,f=[],c=[],l=[],k=[],i=[[1,0.6,0.3,0.1],[0.4,0.6,0.6,0.4],[0.1,0.3,0.6,1]],e=0;e<=d;e++)f[e]=p.subtract(b[e],a);for(e=0;e<=d-1;e++){c[e]= +p.subtract(b[e+1],b[e]);c[e]=p.scale(c[e],3)}for(e=0;e<=d-1;e++)for(var m=0;m<=d;m++){l[e]||(l[e]=[]);l[e][m]=p.dotProduct(c[e],f[m])}for(e=0;e<=h;e++){k[e]||(k[e]=[]);k[e].y=0;k[e].x=parseFloat(e)/h}h=d-1;for(f=0;f<=d+h;f++){c=Math.min(f,d);for(e=Math.max(0,f-h);e<=c;e++){j=f-e;k[e+j].y+=l[j][e]*i[j][e]}}d=b.length-1;k=s(k,2*d-1,g,0);h=p.subtract(a,b[0]);l=p.square(h);for(e=i=0;e=64){g[0]=(a[0].x+a[b].x)/2;return 1}var n,o,q;k=a[0].y-a[b].y;i=a[b].x-a[0].x;e=a[0].x*a[b].y-a[b].x*a[0].y;m=max_distance_below=0;for(o=1;om)m=q;else if(q0?1:-1,c=null;h 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + if (!ie) { + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + else { + // need to get fancy with the vml. + } + } + return false; + }; + + /* + * Binds a listener to an event. + * + * event - name of the event to bind to. + * listener - function to execute. + */ + this.bind = function(event, listener) { + _addToList(_listeners, event, listener); + }; + /* + * Fires an update for the given event. + * + * event - event to fire + * value - value to pass to the event listener(s). + * originalEvent - the original event from the browser + */ + this.fireUpdate = function(event, value, originalEvent) { + if (_listeners[event]) { + for ( var i = 0; i < _listeners[event].length; i++) { + try { + _listeners[event][i](value, originalEvent); + } catch (e) { + _log(self, "jsPlumb: fireUpdate failed for event " + + event + " : " + e + "; not fatal."); + } + } + } + }; + /* + * Clears either all listeners, or listeners for some specific event. + * + * event - optional. constrains the clear to just listeners for this event. + */ + this.clearListeners = function(event) { + if (event) { + delete _listeners[event]; + } else { + delete _listeners; + _listeners = {}; + } + }; + + var _mouseover = false; + var _mouseDown = false, _mouseDownAt = null, _posWhenMouseDown = null, _mouseWasDown = false, srcWhenMouseDown = null, + targetWhenMouseDown = null; + this.mousemove = function(e) { + var jpcl = jsPlumb.CurrentLibrary; + var pageXY = jpcl.getPageXY(e); + var ee = document.elementFromPoint(pageXY[0], pageXY[1]); + var _continue = _connectionBeingDragged == null && (_hasClass(ee, "_jsPlumb_endpoint") || _hasClass(ee, "_jsPlumb_connector")); + + if (_mouseDown && srcWhenMouseDown) { + _mouseWasDown = true; + _connectionBeingDragged = self; + var mouseNow = jpcl.getPageXY(e); + var dx = mouseNow[0] - _mouseDownAt[0]; + var dy = mouseNow[1] - _mouseDownAt[1]; + var newPos = {left:srcWhenMouseDown.left + dx, top:srcWhenMouseDown.top + dy}; + jpcl.setOffset(jpcl.getElementObject(self.source), newPos); + jsPlumb.repaint(self.source); + newPos = {left:targetWhenMouseDown.left + dx, top:targetWhenMouseDown.top + dy}; + jpcl.setOffset(jpcl.getElementObject(self.target), newPos); + jsPlumb.repaint(self.target); + } + else if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.setHover(_mouseover); + self.fireUpdate("mouseenter", self, e); + } + else if (_mouseover && (!self._over(e) || !_continue)) { + _mouseover = false; + /* if (self.hoverPaintStyle != null) { + self.paintStyleInUse = self.paintStyle; + self.repaint(); + _updateAttachedElements(); + }*/ + self.setHover(_mouseover); + self.fireUpdate("mouseexit", self, e); + } + }; + + /** + * sets/unsets the hover state of this element. + */ + this.setHover = function(hover, ignoreAttachedElements) { + if (self.hoverPaintStyle != null) { + self.paintStyleInUse = hover ? self.hoverPaintStyle : self.paintStyle; + self.repaint(); + // get the list of other affected elements. for a connection, its the endpoints. for an endpoint, its the connections! surprise. + if (!ignoreAttachedElements) + _updateAttachedElements(hover); + } + }; + + var _updateAttachedElements = function(state) { + var affectedElements = self.getAttachedElements(); // implemented in subclasses + if (affectedElements) { + for (var i = 0; i < affectedElements.length; i++) { + affectedElements[i].setHover(state, true); // tell the attached elements not to inform their own attached elements. + } + } + }; + + this.click = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fireUpdate("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fireUpdate("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _mouseDownAt = jsPlumb.CurrentLibrary.getPageXY(e); + if (self.canvas) _posWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.canvas)); + if (self.source) srcWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.source)); + if (self.target) targetWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.target)); + } + }; + + this.mouseup = function() { + if (self == _connectionBeingDragged) _connectionBeingDragged = null; + _mouseDown = false; + }; + + /* + * Sets the paint style and then repaints the element. + * + * style - Style to use. + */ + this.setPaintStyle = function(style) { + self.paintStyle = style; + self.paintStyleInUse = self.paintStyle; + self.repaint(); + }; + + /* + * Sets the paint style to use when the mouse is hovering over the element. This is null by default. + * + * style - Style to use when the mouse is hovering. + */ + this.setHoverPaintStyle = function(style) { + self.hoverPaintStyle = style; + self.repaint(); + }; + }; + + /** + * Class:jsPlumb + * The main jsPlumb class. One of these is registered on the Window as 'jsPlumb'; you can also get one yourself by making a call to jsPlumb.getInstance. This class is the class you use to establish Connections and add Endpoints. + */ + var jsPlumbInstance = function(_defaults) { + + /* + * Property: Defaults + * + * These are the default settings for jsPlumb. They are what will be used if you do not supply specific pieces of information to the various API calls. A convenient way to implement your own look and feel can be to override these defaults by including a script somewhere after the jsPlumb include, but before you make any calls to jsPlumb, for instance in this example we set the PaintStyle to be a blue line of 27 pixels: > jsPlumb.Defaults.PaintStyle = { lineWidth:27, strokeStyle:'blue' } + */ + this.Defaults = { + Anchor : null, + Anchors : [ null, null ], + BackgroundPaintStyle : null, + Connector : null, + Container : null, + DragOptions : { }, + DropOptions : { }, + Endpoint : null, + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : null }, + EndpointStyles : [ null, null ], + EndpointHoverStyle : null, + EndpointHoverStyles : [ null, null ], + HoverPaintStyle : null, + LabelStyle : { fillStyle : "rgba(0,0,0,0)", color : "black" }, + LogEnabled : true, + MaxConnections : null, + MouseEventsEnabled : false, + // TODO: should we have OverlayStyle too? + PaintStyle : { lineWidth : 10, strokeStyle : 'red' }, + Scope : "_jsPlumb_DefaultScope" + }; + if (_defaults) jsPlumb.extend(this.Defaults, _defaults); + + this.logEnabled = this.Defaults.LogEnabled; + + + EventGenerator.apply(this); + var _bb = this.bind; + this.bind = function(event, fn) { + if ("ready" === event && initialized) fn(); + else _bb(event, fn); + }; + + var _currentInstance = this; + var log = null; + + var repaintFunction = function() { + jsPlumb.repaintEverything(); + }; + var automaticRepaint = true; + function repaintEverything() { + if (automaticRepaint) + repaintFunction(); + }; + var resizeTimer = null; + + var initialized = false; + var connectionsByScope = {}; + /** + * map of element id -> endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + var endpointsByElement = {}; + var endpointsByUUID = {}; + var offsets = {}; + var offsetTimestamps = {}; + var floatingConnections = {}; + var draggableStates = {}; + var _mouseEventsEnabled = this.Defaults.MouseEventsEnabled; + var _draggableByDefault = true; + var canvasList = []; + var sizes = []; + var listeners = {}; // a map: keys are event types, values are lists of listeners. + var DEFAULT_SCOPE = 'DEFAULT'; + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + var _findIndex = function(a, v, b, s) { + var _eq = function(o1, o2) { + if (o1 === o2) + return true; + else if (typeof o1 == 'object' && typeof o2 == 'object') { + var same = true; + for ( var propertyName in o1) { + if (!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for ( var propertyName in o2) { + if (!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for ( var i = +b || 0, l = a.length; i < l; i++) { + if (_eq(a[i], v)) + return i; + } + return -1; + }; + + /** + * appends an element to the given parent, or the document body if no + * parent given. + */ + var _appendElement = function(canvas, parent) { + if (!parent) + document.body.appendChild(canvas); + else + jsPlumb.CurrentLibrary.appendElement(canvas, parent); + }; + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + var _timestamp = function() { return "" + (new Date()).getTime(); }; + + /** + * Draws an endpoint and its connections. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + var _draw = function(element, ui, timestamp) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (!timestamp) timestamp = _timestamp(); + if (endpoints) { + _updateOffset( { elId : id, offset : ui, recalc : false, timestamp : timestamp }); // timestamp is checked against last update cache; it is + // valid for one paint cycle. + var myOffset = offsets[id], myWH = sizes[id]; + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + var l = endpoints[i].connections; + for ( var j = 0; j < l.length; j++) { + l[j].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); // ...paint each connection. + // then, check for dynamic endpoint; need to repaint it. + var oIdx = l[j].endpoints[0] == endpoints[i] ? 1 : 0; + if (l[j].endpoints[oIdx].anchor.isDynamic && !l[j].endpoints[oIdx].isFloating()) { + var oId = oIdx == 0 ? l[j].sourceId : l[j].targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + // TODO i still want to make this faster. + var anchorLoc = l[j].endpoints[oIdx].anchor.compute( { + xy : [ oOffset.left, oOffset.top ], + wh : oWH, + element : l[j].endpoints[oIdx], + txy : [ myOffset.left, myOffset.top ], + twh : myWH, + tElement : endpoints[i] + }); + l[j].endpoints[oIdx].paint({ anchorLoc : anchorLoc }); + } + } + } + } + }; + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + var _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }; + + /** + * gets an Endpoint by uuid. + */ + var _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }; + + /** + * inits a draggable if it's not already initialised. + */ + var _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element) && !jsPlumb.CurrentLibrary.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + } + } + }; + + /** + * helper to create a canvas. + * + */ + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + _appendElement(canvas, params.container); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + _getId(canvas, params.uuid); + if (ie) { + // for IE we have to set a big canvas size. actually you can + // override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas + // you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + + var _newConnection = function(params) { + var connectionFunc = jsPlumb.Defaults.ConnectionType || Connection; + return new connectionFunc(params); + }; + + var _newEndpoint = function(params) { + var endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint; + return new endpointFunc(params); + }; + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + var _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }; + /** + * perform an operation on all elements. + */ + var _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }; + + /** + * helper to remove an element from the DOM. + */ + var _removeElement = function(element, parent) { + if (element != null) { + if (!parent) { + try { + document.body.removeChild(element); + } catch (e) { + } + } else { + jsPlumb.CurrentLibrary.removeElement(element, parent); + } + } + }; + /** + * helper to remove a list of elements from the DOM. + */ + var _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }; + /** + * helper method to remove an item from a list. + */ + var _removeFromList = function(map, key, value) { + if (key != null) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete (l[i]); + l.splice(i, 1); + return true; + } + } + } + return false; + }; + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }; + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + var _setVisible = function(el, state) { + _operation(_getAttribute(el, "id"), function(jpc) { + jpc.canvas.style.display = state; + }); + }; + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + var _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }; + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + var _toggleVisible = function(elId) { + _operation(elId, function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }; + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + var _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return; + } + if (recalc || offset == null) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + } + }; + +/** + * gets an id for the given element, creating and setting one if + * necessary. + */ + var _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else + id = "jsPlumb_" + _timestamp(); + _setAttribute(ele, "id", id); + } + return id; + }; + +/** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + var _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'jsPlumb function failed : ' + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'wrapped function failed : ' + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = '_jsPlumb_connector'; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = '_jsPlumb_endpoint'; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = '_jsPlumb_overlay'; + + /* + * Property: Anchors + * Default jsPlumb Anchors. These are supplied in the file jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script to form .jsPlumb-all-x.x.x.js. You can + * provide your own Anchors by supplying them in a script that is loaded after jsPlumb, for instance: > jsPlumb.Anchors.MyAnchor = { ....anchor code here. see the documentation. } + */ + this.Anchors = {}; + + /* + * Property: Connectors + * Default jsPlumb Connectors. These are supplied + * in the file jsPlumb-defaults-x.x.x.js, which is merged in with the + * main jsPlumb script to form .jsPlumb-all-x.x.x.js. You can + * provide your own Connectors by supplying them in a script that is + * loaded after jsPlumb, for instance: > jsPlumb.Connectors.MyConnector = { + * ....connector code here. see the documentation. } + */ + this.Connectors = {}; + + /* + * Property: Endpoints + * Default jsPlumb Endpoints. These are supplied in + * the file jsPlumb-defaults-x.x.x.js, which is merged in with the main + * jsPlumb script to form .jsPlumb-all-x.x.x.js. You can + * provide your own Endpoints by supplying them in a script that is + * loaded after jsPlumb, for instance: > jsPlumb.Endpoints.MyEndpoint = { + * ....endpoint code here. see the documentation. } + */ + this.Endpoints = {}; + + /* + * Property:Overlays + * Default jsPlumb Overlays such as Arrows and Labels. + * These are supplied in the file jsPlumb-defaults-x.x.x.js, which is + * merged in with the main jsPlumb script to form + * .jsPlumb-all-x.x.x.js. You can provide your own Overlays by + * supplying them in a script that is loaded after jsPlumb, for + * instance: > jsPlumb.Overlays.MyOverlay = { ....overlay code here. see + * the documentation. } + */ + this.Overlays = {}; + + /* + Function: addEndpoint + + Adds an Endpoint to a given element. + + Parameters: + + target - Element to add the endpoint to. either an element id, or a selector representing some element. + params - Object containing Endpoint options. For more information, see the docs for Endpoint's constructor. + referenceParams - Object containing more Endpoint options; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + The newly created Endpoint. + + See Also: + + */ + this.addEndpoint = function(target, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.endpointStyle = p.endpointStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + var el = _getElementObject(target), id = _getAttribute(el, "id"); + p.source = el; + _updateOffset({ elId : id }); + var e = _newEndpoint(p); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + return e; + }; + + /* + Function: addEndpoints + Adds a list of Endpoints to a given element. + + Parameters: + target - element to add the endpoint to. either an element id, or a selector representing some element. + endpoints - List of objects containing Endpoint options. one Endpoint is created for each entry in this list. + referenceParams - Object containing more Endpoint options; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created Endpoints, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(target, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + results.push(_currentInstance.addEndpoint(target, endpoints[i], referenceParams)); + } + return results; + }; + + /* + Function: animate + Wrapper around supporting library's animate function; injects a call to jsPlumb in the 'step' function (creating + it if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // probably, onComplete should repaint too. that will help + // keep up + // with fast animations. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /* + Function: connect + Establishes a connection between two elements (or Endpoints, which are themselves registered to elements). + + Parameters: + params - Object containing setup for the connection. See docs for Connection's constructor. + referenceParams - Optional object containing more params for the connection. Typically you would pass in data that a lot of connections are sharing here, such as connector style etc, and then use the main params for data specific to this connection. + + Returns: + The newly created Connection. + */ + this.connect = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not + // full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + // dynamic anchors. backwards compatibility here: from 1.2.6 onwards you don't need to specify "dynamicAnchors". the fact that some anchor consists + // of multiple definitions is enough to tell jsPlumb you want it to be dynamic. + if (_p.dynamicAnchors) { + // these can either be an array of anchor coords, which we will use for both source and target, or an object with {source:[anchors], target:[anchors]}, in which + // case we will use a different set for each element. + var a = _p.dynamicAnchors.constructor == Array; + var sa = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.source)); + var ta = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.target)); + _p.anchors = [sa,ta]; + } + + var jpc = _newConnection(_p); + // add to list of connections (by scope). + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + _currentInstance.fireUpdate("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + // force a paint + _draw(jpc.source); + + return jpc; + }; + + /* + Function: deleteEndpoint + Deletes an endpoint and removes all connections it has (which removes the connections from the other endpoints involved too) + + Parameters: + object - either an Endpoint object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElement(endpoint.canvas, endpoint.container); + // remove from endpointsbyElement + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + delete endpoint; + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every Endpoint, and their associated Connections, in this instance of jsPlumb. Do not unregister event listener (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc) { + _currentInstance.fireUpdate("jsPlumbConnectionDetached", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + }; + + /* + Function: detach + Removes a connection. takes either (source, target) (the old way, maintained for backwards compatibility), or a params + object with various possible values. + + Parameters: + source - id or element object of the first element in the connection. + target - id or element object of the second element in the connection. + params - a JS object containing the same parameters as you pass to jsPlumb.connect. If this is present then neither source nor + target should be present; it should be the only argument to the method. See the docs for Connection's constructor for information +about the parameters allowed in the params object. + Returns: + true if successful, false if not. + */ + this.detach = function(source, target) { + if (arguments.length == 2) { + var s = _getElementObject(source), sId = _getId(s); + var t = _getElementObject(target), tId = _getId(t); + _operation(sId, function(jpc) { + if ((jpc.sourceId == sId && jpc.targetId == tId) || (jpc.targetId == sId && jpc.sourceId == tId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + // this is the new version of the method, taking a JS object like + // the connect method does. + else if (arguments.length == 1) { + // TODO investigate whether or not this code still works when a user has supplied their own subclass of Connection. i suspect it may not. + if (arguments[0].constructor == Connection) { + //arguments[0].setHover(false); + arguments[0].endpoints[0].detachFrom(arguments[0].endpoints[1]); + } + else if (arguments[0].connection) { + //arguments[0].connection.setHover(false); + arguments[0].connection.endpoints[0].detachFrom(arguments[0].connection.endpoints[1]); + } + else { + var _p = jsPlumb.extend( {}, source); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1])); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source); + var targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + } + } + }; + + /* + Function: detachAll + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + + Returns: + void + */ + this.detachAllConnections = function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + }; + + /** + * @deprecated Use detachAllConnections instead. this will be removed in jsPlumb 1.3. + */ + this.detachAll = this.detachAllConnections; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + /** + * @deprecated use detachEveryConnection instead. this will be removed in jsPlumb 1.3. + */ + this.detachEverything = this.detachEveryConnection; + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of you library's draggable method so that jsPlumb can setup the appropriate callbacks. Your underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. + * Parameters: + * options - a JS object that holds options defining what sort of connections you're + * looking for. Valid values are: scope this may be a String or a list + * of Strings. jsPlumb will only return Connections whose scope matches + * what this option defines. If you omit it, you will be given + * Connections of every scope. source may be a string or a list of + * strings; constraints results to have only Connections whose source is + * this object. target may be a string or a list of strings; constraints + * results to have only Connections whose target is this object. + * + * The return value is a dictionary in this format: + * { 'scope1': [ {sourceId:'window1', targetId:'window2', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:}, + * {sourceId:'window3', targetId:'window4', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:}, + * {sourceId:'window1', targetId:'window3', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:} ], + * 'scope2': [ {sourceId:'window1', targetId:'window3', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:} ] } + * + */ + this.getConnections = function(options) { + var r = {}; + options = options || {}; + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') + r.push(input); + else + r = input; + } + return r; + }; + var scopes = prepareList(options.scope); + var sources = prepareList(options.source); + var targets = prepareList(options.target); + var filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + r[i] = []; + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + r[i].push({ sourceId : c.sourceId, targetId : c.targetId, source : c.source, target : c.target, sourceEndpoint : c.endpoints[0], targetEndpoint : c.endpoints[1], connection : c }); + } + } + } + return r; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + + Returns: + void + */ + this.hide = function(el) { + _setVisible(el, "none"); + }; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + var _bind = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && _mouseEventsEnabled) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + if (c[i][event](e)) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i][event](e)) return; + } + } + } + }); + }; + _bind("click"); + _bind("dblclick"); + _bind("mousemove"); + _bind("mousedown"); + _bind("mouseup"); + + initialized = true; + _currentInstance.fireUpdate("ready"); + }; + + /* + * Creates an anchor with the given params. + * + * You do not need to use this method. It is exposed because of the way jsPlumb is + * split into three scripts; this will change in the future. + * + * x - the x location of the anchor as a fraction of the + * total width. y - the y location of the anchor as a fraction of the + * total height. xOrientation - value indicating the general direction a + * connection from the anchor should go in, in the x direction. + * yOrientation - value indicating the general direction a connection + * from the anchor should go in, in the y direction. xOffset - a fixed + * offset that should be applied in the x direction that should be + * applied after the x position has been figured out. optional. defaults + * to 0. yOffset - a fixed offset that should be applied in the y + * direction that should be applied after the y position has been + * figured out. optional. defaults to 0. + * -- OR -- + * + * params - {x:..., y:..., xOrientation etc } + * -- OR FROM 1.2.4 --- + * + * name - the name of some Anchor in the _currentInstance.Anchors array. + * -- OR FROM 1.2.4 --- + * + * coords - a list of coords for the anchor, like you would pass to + * jsPlumb.makeAnchor (eg [0.5,0.5,0,-1] - an anchor in the center of + * some element, oriented towards the top of the screen) + * -- OR FROM 1.2.4 --- + * + * anchor - an existing anchor. just gets passed back. it's handy + * internally to have this functionality. + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed + // in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards + // compatibility if we are given only one value we assume it's a + // call in the old form. + if (arguments.length == 0) return null; + var params = {}; + if (arguments.length == 1) { + var specimen = arguments[0]; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; + // is it the name of an anchor type? + else if (typeof specimen == "string") return _currentInstance.Anchors[arguments[0]](); + // is it an array of coordinates? + else if (specimen.constructor == Array) { + if (specimen[0].constructor == Array || specimen[0].constructor == String) + return new DynamicAnchor(specimen); + else + return jsPlumb.makeAnchor.apply(this, specimen); + } + // last we try the backwards compatibility stuff. + else if (typeof arguments[0] == "object") jsPlumb.extend(params, x); + } else { + params = { x : x, y : y }; + if (arguments.length >= 4) params.orientation = [ arguments[2], arguments[3] ]; + if (arguments.length == 6) params.offsets = [ arguments[4], arguments[5] ]; + } + var a = new Anchor(params); + a.clone = function() { + return new Anchor(params); + }; + return a; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types) { + var r = []; + for ( var i = 0; i < types.length; i++) + if (typeof types[i] == "string") + r.push(_currentInstance.Anchors[types[i]]()); + else if (types[i].constructor == Array) + r.push(jsPlumb.makeAnchor(types[i])); + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + for ( var i in ebe) + _currentInstance.deleteEndpoint(ebe[i]); + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + this.deleteEveryEndpoint(); + this.clearListeners(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + + Parameters: + value - whether or not to automatically repaint when the window is resized. + + Returns: void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + * Function: setDefaultNewCanvasSize + * Sets the default size jsPlumb will use for a new canvas (we create a square canvas so one value is all + * that is required). This is a hack for IE, because ExplorerCanvas + * seems to need for a canvas to be larger than what you are going to + * draw on it at initialisation time. The default value of this is 1200 + * pixels, which is quite large, but if for some reason you're drawing + * connectors that are bigger, you should adjust this value + * appropriately. + * + * Parameters: + * size - The default size to use. jsPlumb will use a square canvas so you need only supply one value. + * + * Returns: + * void + */ + this.setDefaultNewCanvasSize = function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }; + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setDraggableByDefault + * Sets whether or not elements are draggable by default. Default for this is true. + * + * Parameters: + * draggable - value to set + * + * Returns: + * void + */ + this.setDraggableByDefault = function(draggable) { + _draggableByDefault = draggable; + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction + * Sets the function to fire when the window size has changed and a repaint was fired. + * + * Parameters: + * f - Function to execute. + * + * Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: setMouseEventsEnabled + * Sets whether or not mouse events are enabled. By default they are not; this is just because jsPlumb has to add mouse listeners + * to the document, which may result in a performance hit a user does not need. + * + * Parameters: + * enabled - whether or not mouse events should be enabled. + * + * Returns: + * void + */ + this.setMouseEventsEnabled = function(enabled) { + _mouseEventsEnabled = enabled; + }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * + * Returns: + * void + */ + this.show = function(el) { + _setVisible(el, "block"); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload + * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element. + * + * Returns: + * void + */ + this.unload = function() { + delete endpointsByElement; + delete endpointsByUUID; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + delete canvasList; + }; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + if (timestamp && timestamp === self.timestamp) { + return lastReturnValue; + } + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + var container = element ? element.container : null; + var containerAdjustment = { left : 0, top : 0 }; + if (container != null) { + var eo = _getElementObject(container); + var o = _getOffset(eo); + var sl = jsPlumb.CurrentLibrary.getScrollLeft(eo); + var st = jsPlumb.CurrentLibrary.getScrollTop(eo); + containerAdjustment.left = o.left - sl; + containerAdjustment.top = o.top - st; + lastReturnValue[0] = lastReturnValue[0] - containerAdjustment.left; + lastReturnValue[1] = lastReturnValue[1] - containerAdjustment.top; + } + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(_getElementObject(refCanvas)); + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + var orientation = null; + var _lastResult = null; + + this.compute = function(params) { + var xy = params.xy, el = params.element; + var result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + if (el.container != null) { + var o = _getOffset(el.container); + result[0] = result[0] - o.left; + result[1] = result[1] - o.top; + } + _lastResult = result; + return result; + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { orientation = anchor.getOrientation(); }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchors is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = anchors || []; + var _convert = function(anchor) { return anchor.constructor == Anchor ? anchor: jsPlumb.makeAnchor(anchor); }; + for (var i = 0; i < _anchors.length; i++) _anchors[i] = _convert(_anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null; + var _curIndex = _anchors.length > 0 ? 0 : -1; + this.locked = false; + var self = this; + + // helper method to calculate the distance between the centers of the two elements. + var _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }; + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + var _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + + var pos = _curAnchor.compute(params); + return pos; + }; + + this.getCurrentLocation = function() { + var cl = _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + return cl; + }; + + this.getOrientation = function() { return _curAnchor != null ? _curAnchor.getOrientation() : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * container - id of the containing div for this connection. optional; jsPlumb uses the default (which you can set, but which is the body by default) otherwise. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * backgroundPaintStyle - Parameters defining the appearance of the background of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * overlays - Optional array of Overlay definitions to appear on this Connection. + */ + var Connection = function(params) { + + EventGenerator.apply(this); + // ************** get the source and target and register the connection. ******************* + var self = this; + var visible = true; + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + }; + var id = new String('_jsplumb_c_' + (new Date()).getTime()); + this.getId = function() { return id; }; + this.container = params.container || _currentInstance.Defaults.Container; // may be null; we will append to the body if so. + // get source and target as jQuery objects + /** + Property: source + The source element for this Connection. + */ + this.source = _getElementObject(params.source); + /** + Property:target + The target element for this Connection. + */ + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. + if (params.sourceEndpoint) this.source = params.sourceEndpoint.getElement(); + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + /* + * Property: sourceId + * Id of the source element in the connection. + */ + this.sourceId = _getAttribute(this.source, "id"); + /* + * Property: targetId + * Id of the target element in the connection. + */ + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + this.getAttachedElements = function() { + return self.endpoints; + }; + + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams) { + if (anchorParams) + return jsPlumb.makeAnchor(anchorParams); + }; + var prepareEndpoint = function(existing, index, params, element) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint + || new jsPlumb.Endpoints.Dot(); + if (ep.constructor == String) + ep = new jsPlumb.Endpoints[ep](); + else if (ep.constructor == Array) { + ep = new jsPlumb.Endpoints[ep[0]](ep[1]); + } + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + var a = params.anchors ? params.anchors[index] : _makeAnchor(_currentInstance.Defaults.Anchors[index]) || _makeAnchor(jsPlumb.Defaults.Anchors[index]) || _makeAnchor(_currentInstance.Defaults.Anchor) || _makeAnchor(jsPlumb.Defaults.Anchor) || _makeAnchor("BottomCenter"); + var u = params.uuids ? params.uuids[index] : null; + var e = _newEndpoint( { paintStyle : es, hoverPaintStyle:ehs, endpoint : ep, connections : [ self ], uuid : u, anchor : a, source : element, container : self.container }); + self.endpoints[index] = e; + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector) + */ + this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || _currentInstance.Defaults.Connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + if (this.connector.constructor == String) + this.connector = new jsPlumb.Connectors[this.connector](); // lets you use a string as shorthand. + else if (this.connector.constructor == Array) + this.connector = new jsPlumb.Connectors[this.connector[0]](this.connector[1]); + this.paintStyle = this.endpoints[0].connectorStyle || this.endpoints[1].connectorStyle || params.paintStyle || _currentInstance.Defaults.PaintStyle || jsPlumb.Defaults.PaintStyle; + var backgroundPaintStyle = this.endpoints[0].connectorBackgroundStyle || this.endpoints[1].connectorBackgroundStyle || params.backgroundPaintStyle || _currentInstance.Defaults.BackgroundPaintStyle || jsPlumb.Defaults.BackgroundPaintStyle; + this.hoverPaintStyle = this.endpoints[0].connectorHoverStyle || this.endpoints[1].connectorHoverStyle || params.hoverPaintStyle || _currentInstance.Defaults.HoverPaintStyle || jsPlumb.Defaults.HoverPaintStyle; + this.paintStyleInUse = this.paintStyle; + + /* + * Property: overlays + * List of Overlays for this Connection. + */ + this.overlays = []; + if (params.overlays) { + for (var i = 0; i < params.overlays.length; i++) { + var o = params.overlays[i]; + if (o.constructor == Array) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0]; + var p = jsPlumb.CurrentLibrary.extend({connection:self}, o[1]); // make a copy of the object so as not to mess up anyone else's reference... + if (o.length == 3) jsPlumb.CurrentLibrary.extend(p, o[2]); + this.overlays.push(new jsPlumb.Overlays[type](p)); + } else if (o.constructor == String) { + this.overlays.push(new jsPlumb.Overlays[o]({connection:self})); + } + else this.overlays.push(o); + } + } + var overlayPlacements = []; + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + this.addOverlay = function(overlay) { overlays.push(overlay); }; + + // this is a shortcut helper method to let people add a label as + // overlay. + this.labelStyle = params.labelStyle || _currentInstance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + if (this.label) { + this.overlays.push(new jsPlumb.Overlays.Label( { + labelStyle : this.labelStyle, + label : this.label, + connection:self + })); + } + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String or a Function that returns a String. + */ + this.setLabel = function(l) { + self.label = l; + _currentInstance.repaint(self.source); + }; + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var otherOffset = offsets[this.targetId]; + var otherWH = sizes[this.targetId]; + var anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1] + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0] + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc }); + + var canvas = _newCanvas({"class":jsPlumb.connectorClass, container:self.container}); + this.canvas = canvas; + + /* + * Function: setBackgroundPaintStyle + * Sets the Connection's background paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + this.setBackgroundPaintStyle = function(style) { + backgroundPaintStyle = style; + self.repaint(); + }; + + /* + * Paints the connection. + * + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp; + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = false; + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + if (this.canvas.getContext) { + _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }); + _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + var ctx = canvas.getContext('2d'); + var sAnchorP = this.endpoints[sIdx].anchor.getCurrentLocation(); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.getCurrentLocation(); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + + // paint overlays + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + var s = o.computeMaxSize(self.connector, ctx); + if (s > maxSize) + maxSize = s; + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, self.paintStyleInUse.lineWidth, maxSize); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + + var _paintOneStyle = function(ctx, aStyle) { + ctx.save(); + jsPlumb.extend(ctx, aStyle); + if (aStyle.gradient && !ie) { + var g = self.connector.createGradient(dim, ctx, (elId == this.sourceId)); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + self.connector.paint(dim, ctx); + ctx.restore(); + }; + + // first check for the background style + if (backgroundPaintStyle != null) { + _paintOneStyle(ctx, backgroundPaintStyle); + } + _paintOneStyle(ctx, self.paintStyleInUse); + + // paint overlays + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + self.overlayPlacements[i] = o.draw(self.connector, ctx, self.paintStyleInUse); + } + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function() { + this.paint({ elId : this.sourceId, recalc : true }); + }; + + _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide + // whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + + //_registerConnection(self); + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have one to N connections emanating from it (although how to handle that in the UI is a very good question). also has a Canvas and paint style. + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * connections - optional list of Connections to configure the Endpoint with. + * container - optional element (as a string id) to use as the container for the canvas associated with this Endpoint. If not supplied, jsPlumb uses the default, which is the document body. A better way to use this container functionality is to set it on the defaults (jsPlumb.Defaults.Container="someElement"). + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorBackgroundStyle - if isSource is set to true, this is the background paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", 160 ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + EventGenerator.apply(this); + params = params || {}; + var self = this; + var visible = true; + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + var id = new String('_jsplumb_e_' + (new Date()).getTime()); + this.getId = function() { return id; }; + if (params.dynamicAnchors) + self.anchor = new DynamicAnchor(jsPlumb.makeAnchors(params.dynamicAnchors)); + else + self.anchor = params.anchor ? jsPlumb.makeAnchor(params.anchor) : params.anchors ? jsPlumb.makeAnchor(params.anchors) : jsPlumb.makeAnchor("TopCenter"); + var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot(); + if (_endpoint.constructor == String) + _endpoint = new jsPlumb.Endpoints[_endpoint](); + else if (_endpoint.constructor == Array) + _endpoint = new jsPlumb.Endpoints[_endpoint[0]](_endpoint[1]); + self.endpoint = _endpoint; + this.paintStyle = params.paintStyle || params.style || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + this.hoverPaintStyle = params.hoverPaintStyle || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + this.paintStyleInUse = this.paintStyle; + this.connectorStyle = params.connectorStyle; + this.connectorBackgroundStyle = params.connectorBackgroundStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + var _element = params.source, _uuid = params.uuid; + var floatingEndpoint = null, inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + this.container = params.container || _currentInstance.Defaults.Container || jsPlumb.Defaults.Container; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + /* + * Property: canvas + * The Endpoint's Canvas. + */ + this.canvas = params.canvas || _newCanvas({"class":jsPlumb.endpointClass, container:this.container, uuid:params.uuid}); + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + this.connections = params.connections || []; + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + var _reattach = params.reattach || false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + // this avoids a circular loop + if (!ignoreTarget) { + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + t.detach(connection, true); + } + _removeElement(connection.canvas, connection.container); + _removeFromList(connectionsByScope, connection.scope, connection); + if(!ignoreTarget) fireDetachEvent(connection); + } + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + */ + this.detachAll = function() { + while (self.connections.length > 0) { + self.detach(self.connections[0]); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + */ + this.detachFrom = function(targetEndpoint) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + c[i].setHover(false); + self.detach(c[i]); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + var e = _newEndpoint( { anchor : self.anchor, source : _element, paintStyle : this.paintStyle, endpoint : _endpoint }); + return e; + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + return (self.connections.length < _maxConnections) ? null : self.connections[0]; + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + + /* + * Function: setPaintStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + */ + this.setPaintStyle = this.setPaintStyle; // i do this so NaturalDocs can pick up the function definition. + + /* + * Function: setHoverPaintStyle + * Sets the hover paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * + * Parameters: + * style - Style object to set, for example { fillStyle:"yellow" }. + */ + this.setHoverPaintStyle = this.setHoverPaintStyle; // i do this so NaturalDocs can pick up the function definition. + + /* + * Sets the paint style of the Endpoint. + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + /* + * + * Paints the Endpoint, recalculating offset and anchor positions if necessary. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + + params = params || {}; + var timestamp = params.timestamp; + if (!timestamp || self.timestamp !== timestamp) { + var ap = params.anchorPoint, canvas = params.canvas, connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + // do we always want to force a repaint here? i dont + // think so! + var xy = params.offset || offsets[_elementId]; + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (self.anchor.isDynamic) { + if (self.connections.length > 0) { + var c = self.connections[0]; + var oIdx = c.endpoints[0] == self ? 1 : 0; + var oId = oIdx == 0 ? c.sourceId : c.targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + } + ap = self.anchor.compute(anchorParams); + } + _endpoint.paint(ap, self.anchor.getOrientation(), canvas || self.canvas, self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + self.timestamp = timestamp; + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + var start = function() { + jpc = self.connectorSelector(); + if (self.isFull() && !dragAllowedWhenFull) return false; + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + var nE = _getElementObject(n); + _appendElement(n, self.container); // + // create and assign an id, and initialize the offset. + var id = _getId(nE); + _updateOffset( { elId : id }); + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor( { reference : self.anchor, referenceCanvas : self.canvas }); + floatingEndpoint = _newEndpoint({ paintStyle : { fillStyle : 'rgba(0,0,0,0)' }, endpoint : _endpoint, anchor : floatingAnchor, source : nE }); + + if (jpc == null) { + self.anchor.locked = true; + // create a connection. one end is this endpoint, the + // other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : _getElementObject(_element), + target : _getElementObject(n), + anchors : [ self.anchor, floatingAnchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + backgroundPaintStyle:params.connectorBackgroundStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays + }); + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.setHover(false); + } else { + existingJpc = true; + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.setHover(false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas)); + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; // are we the source or the target? + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas); + var dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // get a new, temporary scope, to use (issue 57) + var newScope = "scope_" + (new Date()).getTime(); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + // set the new, temporary scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(i, newScope); + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // TODO unregister on stop? or will floating endpoint's + // destruction be assured. + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be + // dragging the source at any time + // before this connection is either discarded or made into a + // permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var dragOptions = params.dragOptions || {}; + var defaultOpts = jsPlumb.extend( {}, jsPlumb.CurrentLibrary.defaultDragOptions); + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + var startEvent = jsPlumb.CurrentLibrary.dragEvents['start']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], + function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + jsPlumb.CurrentLibrary.setOffset(n, _ui); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements( [ n, floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (_reattach) { + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } else { + jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc); // the main endpoint will inform the floating endpoint + // to disconnect, and also post the detached event. + } + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElement(jpc.canvas, self.container); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint(); + jpc.repaint(); + jpc = null; + delete inPlaceCopy; + delete endpointsByElement[floatingEndpoint.elementId]; + delete floatingEndpoint; + + _currentInstance.currentlyDragging = false; + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas) { + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + var drop = function() { + var draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)); + var id = _getAttribute(draggable, "dragId"); + var elId = _getAttribute(draggable, "elId"); + + // restore the original scope if necessary (issue 57) + var scope = _getAttribute(draggable, "originalScope"); + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // todo test that the target is not full. + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + if (!jpc.suspendedEndpoint) { // if a new connection, add it. TODO: move this to a jsPlumb internal method - addConnection or something. doesnt need to be exposed. + _addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + _currentInstance.fireUpdate("jsPlumbConnectionDetached", { + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }); + } + + jsPlumb.repaint(elId); + + _currentInstance.fireUpdate("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], + targetEndpoint : jpc.endpoints[1], + connection:jpc + }); + } + + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute( _getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + }); + + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas)); + + return self; + }; + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + //if (_defaults) jsPlumb.extend(j.Defaults, _defaults); + return j; + }; +})(); diff --git a/archive/1.2.6/jsPlumb-1.2.6-tests.js b/archive/1.2.6/jsPlumb-1.2.6-tests.js new file mode 100644 index 000000000..d223cd582 --- /dev/null +++ b/archive/1.2.6/jsPlumb-1.2.6-tests.js @@ -0,0 +1,1416 @@ + +var _getContextNode = function() { + return $("#container"); +}; + +var assertContextExists = function() { + ok(_getContextNode().length == 1, 'context node exists'); +}; + +var assertContextSize = function(elementCount) { + equals(_getContextNode().children().length, elementCount, 'context has ' + elementCount + ' children'); +}; + +var assertContextEmpty = function() { + equals(_getContextNode().children.length, 0, 'context empty'); +}; + +var assertEndpointCount = function(elId, count) { + equals(jsPlumb.getTestHarness().endpointCount(elId), count, elId + " has " + count + (count > 1) ? "endpoints" : "endpoint"); +}; + +var assertConnectionCount = function(endpoint, count) { + equals(endpoint.connections.length, count, "endpoint has " + count + " connections"); +}; + +var assertConnectionByScopeCount = function(scope, count) { + equals(jsPlumb.getTestHarness().connectionCount(scope), count, 'Scope ' + scope + " has " + count + (count > 1) ? "connections" : "connection"); + +}; + +var _divs = []; +var _addDiv = function(id) { + var d1 = document.createElement("div"); + document.body.appendChild(d1); + $(d1).attr("id", id); + _divs.push(id); + return $(d1); +}; + +var _cleanup = function() { + for (var i in _divs) { + $("#" + _divs[i]).remove(); + } + _divs.splice(0, _divs.length - 1); + + jsPlumb.reset(); +}; + +$(document).ready(function() { + +module("jsPlumb", {teardown: _cleanup}); + +// setup the container +var container = document.createElement("div"); +container.id = "container"; +document.body.appendChild(container); +jsPlumb.Defaults.Container = "container"; + +test('findIndex method', function() { + var array = [ 1,2,3, "test", "a string", { 'foo':'bar', 'baz':1 }, { 'ding':'dong' } ]; + equals(jsPlumb.getTestHarness().findIndex(array, 1), 0, "find works for integers"); + equals(jsPlumb.getTestHarness().findIndex(array, "test"), 3, "find works for strings"); + equals(jsPlumb.getTestHarness().findIndex(array, { 'foo':'bar', 'baz':1 }), 5, "find works for objects"); + equals(jsPlumb.getTestHarness().findIndex(array, { 'ding':'dong', 'baz':1 }), -1, "find works properly for objects (objects have different length but some same properties)"); +}); + +test('jsPlumb setup', function() { + ok(jsPlumb, "loaded"); +}); + +test('getId', function() { + var d10 = _addDiv('d10'); + equals(jsPlumb.getTestHarness().getId(d10), "d10"); +}); + +test('create a simple endpoint', function() { + var d1 = _addDiv("d1"); + var e = jsPlumb.addEndpoint($("#d1"), {}); + ok(e, 'endpoint exists'); + assertEndpointCount("d1", 1); +}); + +test('create and remove a simple endpoint', function() { + var d1 = _addDiv("d1"); + var ee = jsPlumb.addEndpoint(d1, {uuid:"78978597593"}); + ok(ee != null, "endpoint exists"); + assertEndpointCount("d1", 1); + assertContextSize(1); // one Endpoint canvas. + jsPlumb.removeEndpoint(d1, ee); + assertEndpointCount("d1", 0); + var e = jsPlumb.getEndpoint("78978597593"); + equals(e, null, "the endpoint has been deleted"); + assertContextSize(0); // no Endpoint canvases. +}); + +test('draggable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1"); + var e = jsPlumb.addEndpoint(d1, {isSource:true}); + ok(e, 'endpoint exists'); +}); + +test('droppable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1") + var e = jsPlumb.addEndpoint(d1, {isTarget:true}); + ok(e, 'endpoint exists'); +}); + +test('defaultEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = jsPlumb.addEndpoint(d3, {isSource:true}); + ok(e3.anchor, 'endpoint 3 has an anchor'); + var e4 = jsPlumb.addEndpoint(d4, {isSource:true}); + assertEndpointCount("d3", 1); + assertEndpointCount("d4", 1); + assertContextSize(2); // one canvas for each of our two endpoints. + ok(!e3.isFull(), "endpoint 3 is not full."); + jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // we have one connection + jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // should have refused the connection; default max is 1. + assertContextSize(3); // now we have one more canvas, for the Connection that was accepted. +}); + +test('specifiedEndpointMaxConnections', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = jsPlumb.addEndpoint(d5, {isSource:true, maxConnections:3}); + ok(e5.anchor, 'endpoint 5 has an anchor'); + var e6 = jsPlumb.addEndpoint(d6, {isSource:true, maxConnections:2}); // this one has max TWO + assertEndpointCount("d5", 1); assertEndpointCount("d6", 1); + assertContextSize(2); + ok(!e5.isFull(), "endpoint 5 is not full."); + jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 1); // we have one connection + assertContextSize(3); + jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // two connections + assertContextSize(4); + jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // should have refused; max is 2, for d4. + assertContextSize(4); +}); + +test('noEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + var e4 = jsPlumb.addEndpoint(d4, {isSource:true, maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertContextSize(3); + assertConnectionCount(e3, 1); // we have one connection + jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 2); // we have two. etc (default was one. this proves max is working). + assertContextSize(4); + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e4}); + assertConnectionCount(e4, 3); + assertContextSize(6); +}); + +test('anchors equal', function() { + var a1 = jsPlumb.makeAnchor(0, 1, 1, 1); + var a2 = jsPlumb.makeAnchor(0, 1, 1, 1); + ok(a1.equals(a2), "anchors are the same"); +}); + +test('anchors equal with offsets', function() { + var a1 = jsPlumb.makeAnchor(0, 1, 1, 1, 10, 13); + var a2 = jsPlumb.makeAnchor(0, 1, 1, 1, 10, 13); + ok(a1.equals(a2), "anchors are the same"); +}); + +test('anchors not equal', function() { + var a1 = jsPlumb.makeAnchor(0, 1, 0, 1); + var a2 = jsPlumb.makeAnchor(0, 1, 1, 1); + ok(!a1.equals(a2), "anchors are different"); +}); + +test('anchor not equal with offsets', function() { + var a1 = jsPlumb.makeAnchor(0, 1, 1, 1, 10, 13); + var a2 = jsPlumb.makeAnchor(0, 1, 1, 1); + ok(!a1.equals(a2), "anchors are different"); +}); + +test('detach plays nice when no target given', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + jsPlumb.connect({source:d3, target:d4}); + jsPlumb.detach(); +}); + +// issue 81 +test('jsPlumb.detach should fire only one detach event (pass Connection as argument)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + jsPlumb.detach(conn); + equals(eventCount, 1); +}); + +// issue 81 +test('jsPlumb.detach should fire only one detach event (pass Connection as param in argument)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + jsPlumb.detach({connection:conn}); + equals(eventCount, 1); +}); + +// issue 81 +test('jsPlumb.detach should fire only one detach event (pass source and targets as strings as arguments in params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + jsPlumb.detach({source:"d5", target:"d6"}); + equals(eventCount, 1); +}); + +// issue 81 +test('jsPlumb.detach should fire only one detach event (pass source and targets as divs as arguments in params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + jsPlumb.detach({source:d5, target:d6}); + equals(eventCount, 1); +}); + +// issue 81 +test('jsPlumb.detach should fire only one detach event (pass source and targets as divs as arguments)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + jsPlumb.detach(d5, d6); + equals(eventCount, 1); +}); + +//TODO make sure you run this test with a single detach call, to ensure that +// single detach calls result in the connection being removed. detachEverything can +// just blow away the connectionsByScope array and recreate it. +test('jsPlumb.getConnections (simple case, default scope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + jsPlumb.connect({source:d5, target:d6}); + assertContextSize(3); + var c = jsPlumb.getConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 1, "there is one connection"); +}); + +test('jsPlumb.getConnections (simple case, default scope; detach by element id)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + jsPlumb.connect({source:d5, target:d6}); + jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 2, "there are two connections initially"); + jsPlumb.detach('d5', 'd6'); + c = jsPlumb.getConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); +}); + +test('jsPlumb.getConnections (simple case, default scope; detach by element id using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + jsPlumb.connect({source:d5, target:d6}); + jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 2, "there are two connections initially"); + jsPlumb.detach({source:'d5', target:'d6'}); + c = jsPlumb.getConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); +}); + +test('jsPlumb.getConnections (simple case, default scope; detach by element object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + jsPlumb.connect({source:d5, target:d6}); + jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 2, "there are two connections initially"); + jsPlumb.detach(d5, d6); + c = jsPlumb.getConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); +}); + +test('jsPlumb.getConnections (simple case, default scope; detach by element object using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + jsPlumb.connect({source:d5, target:d6}); + jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 2, "there are two connections initially"); + jsPlumb.detach({source:d5, target:d6}); + c = jsPlumb.getConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); +}); + +test('jsPlumb.getConnections (simple case, default scope; detach by Connection)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + var c56 = jsPlumb.connect({source:d5, target:d6}); + var c67 = jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 2, "there are two connections initially"); + jsPlumb.detach(c56); + assertContextSize(5); // check that the connection canvas was removed. + c = jsPlumb.getConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 1, "after detaching one, there is now one connection."); + +}); + +test('jsPlumb.getConnections (scope testScope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + jsPlumb.connect({source:d5, target:d6, scope:'testScope'}); + var c = jsPlumb.getConnections(); // will get all connections + equals(c['testScope'].length, 1, "there is one connection"); + equals(c['testScope'][0].sourceId, 'd5', "the connection's source is d5"); + equals(c['testScope'][0].targetId, 'd6', "the connection's source is d6"); +}); + +test('jsPlumb.getConnections (filtered by scope)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + jsPlumb.connect({source:d9, target:d10}); // default scope + var c = jsPlumb.getConnections({scope:'testScope'}); // will get all connections + equals(c[jsPlumb.getDefaultScope()], null); + equals(c['testScope'].length, 1); + // now supply a list of scopes + c = jsPlumb.getConnections({scope:[jsPlumb.getDefaultScope(),'testScope']}); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 1); + equals(c['testScope'].length, 1, "there is one connection in 'testScope'"); +}); + +test('jsPlumb.getConnections (filtered by scope and sourceId)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + jsPlumb.connect({source:d9, target:d8, scope:'testScope'}); + jsPlumb.connect({source:d9, target:d10}); // default scope + var c = jsPlumb.getConnections({scope:'testScope', source:'d8'}); // will get all connections with sourceId 'd8' + equals(c[jsPlumb.getDefaultScope()], null); + equals(c['testScope'].length, 1, "there is one connection in 'testScope' from d8"); +}); + +test('jsPlumb.getConnections (filtered by scope, source id and target id)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d12, target:d13, scope:'testScope'}); + jsPlumb.connect({source:d11, target:d13, scope:'testScope'}); + var c = jsPlumb.getConnections({scope:'testScope', source:'d11', target:'d13'}); + equals(c['testScope'].length, 1, "there is one connection from d11 to d13"); +}); + +test('jsPlumb.getConnections (filtered by a list of scopes)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = jsPlumb.getConnections({scope:['testScope','testScope3']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); +}); + +test('jsPlumb.getConnections (filtered by a list of scopes and source ids)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); +}); + +test('jsPlumb.getConnections (filtered by a list of scopes, source ids and target ids)', function() { + jsPlumb.detachEverything(); + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'), d14=_addDiv("d14"), d15=_addDiv("d15"); + jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d11, target:d14, scope:'testScope'}); + jsPlumb.connect({source:d11, target:d15, scope:'testScope'}); + jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + assertContextSize(18); + var c = jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11'], target:['d14', 'd15']}); + equals(c['testScope'].length, 2, 'there are two connections in testScope'); + equals(c['testScope3'].length, 0, 'there is no connections in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + var anEntry = c['testScope'][0]; + ok(anEntry.sourceEndpoint != null, "Source endpoint is set"); + ok(anEntry.targetEndpoint != null, "Target endpoint is set"); + equals(anEntry.source.attr("id"), "d11", "Source is div d11"); + equals(anEntry.target.attr("id"), "d14", "Target is div d14"); +}); + +test('connection event listeners', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null/*, returnedParams2 = null*/; + jsPlumb.bind("jsPlumbConnection", function(params) { + returnedParams = $.extend({}, params); + }); + jsPlumb.connect({source:d1, target:d2}); + ok(returnedParams != null, "new connection listener event was fired"); + equals(returnedParams.sourceId, "d1", 'sourceid is set'); + equals(returnedParams.targetId, "d2", 'targetid is set'); + equals(returnedParams.source.attr("id"), "d1", 'source is set'); + equals(returnedParams.target.attr("id"), "d2" , 'target is set'); + ok(returnedParams.sourceEndpoint != null, "source endpoint is not null"); + ok(returnedParams.targetEndpoint != null, "target endpoint is not null"); + //jsPlumb.detachAll("d1"); + //ok(returnedParams2 != null, "removed connection listener event was fired"); +}); + +test('detach event listeners (detach by connection)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); +}); + +test('detach event listeners (detach by element ids)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach("d1","d2"); + ok(returnedParams != null, "removed connection listener event was fired"); +}); + +test('detach event listeners (detach by elements)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach(d1,d2); + ok(returnedParams != null, "removed connection listener event was fired"); +}); + +test('detach event listeners (via Endpoint.detach method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); +}); + +test('detach event listeners (via Endpoint.detachFrom method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachFrom(e2); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); +}); + +test('detach event listeners (via Endpoint.detachAll method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachAll(); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); +}); + +test('detach event listeners (via jsPlumb.deleteEndpoint method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + jsPlumb.deleteEndpoint(e1); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(1); +}); + +test('detach event listeners (ensure cleared by jsPlumb.reset)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach(d1,d2); + ok(returnedParams != null, "removed connection listener event was fired"); + returnedParams = null; + + jsPlumb.reset(); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach(d1,d2); + ok(returnedParams == null, "connection listener was cleared by jsPlumb.reset()"); +}); + +test('connection events that throw errors', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null, returnedParams2 = null; + jsPlumb.bind("jsPlumbConnection", function(params) { + returnedParams = $.extend({}, params); + throw "oh no!"; + }); + jsPlumb.connect({source:d1, target:d2}); + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + jsPlumb.connect({source:d3, target:d4}); + ok(returnedParams != null, "new connection listener event was fired; we threw an error, jsPlumb survived."); +}); + +test("Endpoint.detachFrom", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + e16.detachFrom(e17); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); +}); + +test("Endpoint.detachFromConnection", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + e16.detachFromConnection(conn); + assertContextSize(3); // all canvases should remain; the connection was not removed. + // but endpoint e16 should have no connections now. + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 1); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); +}); + +test("Endpoint.detachAll", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + var e16 = jsPlumb.addEndpoint($("#d16"), {isSource:true,maxConnections:-1}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint($("#d17"), {isSource:true}); + var e18 = jsPlumb.addEndpoint($("#d18"), {isSource:true}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e18}); + assertConnectionCount(e16, 2); + assertConnectionCount(e17, 1); + assertConnectionCount(e18, 1); + assertContextSize(5); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 2); + e16.detachAll(); + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); +}); + +test("Endpoint.detach", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + e16.detach(conn); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); +}); + +test("Endpoint.isConnectedTo", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.isConnectedTo(e17), true, "e16 and e17 are connected"); +}); + +test("setting endpoint uuid", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + equals(e16.getUuid(), uuid, "endpoint's uuid was set correctly"); +}); + +test("jsPlumb.getEndpoint (by uuid)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); +}); + +test("jsPlumb.deleteEndpoint (by uuid, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + jsPlumb.deleteEndpoint(uuid); + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + var ebe = jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + assertContextSize(0); +}); + +test("jsPlumb.deleteEndpoint (by uuid, connections too)", function() { + // create two endpoints (one with a uuid), add them to two divs and then connect them. + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + // check that the connection was ok. + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + // delete the endpoint that has a uuid. verify that the endpoint cannot be retrieved and that the connection has been removed, but that + // element d17 still has its Endpoint. + jsPlumb.deleteEndpoint(uuid); + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + var ebe = jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + ebe = jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 1, "element d17 still has its Endpoint"); + assertContextSize(1); + + // now delete d17's endpoint and check that it has gone. + jsPlumb.deleteEndpoint(e17); + f = jsPlumb.getEndpoint(e17); + equals(f, null, "endpoint has been deleted"); + ebe = jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 0, "element d17 no longer has any Endpoints"); + assertContextSize(0); +}); + +test("jsPlumb.deleteEndpoint (by reference, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + jsPlumb.deleteEndpoint(e16); + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + assertContextSize(0); +}); + +test("jsPlumb.deleteEndpoint (by reference, connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + jsPlumb.deleteEndpoint(e16); + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + assertContextSize(1); +}); + +test("jsPlumb.deleteEveryEndpoint", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + assertContextSize(2); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + jsPlumb.deleteEveryEndpoint(); + + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); +}); + +test("jsPlumb.deleteEveryEndpoint (connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertContextSize(3); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + jsPlumb.deleteEveryEndpoint(); + + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); // no canvases. so all connection canvases have been cleaned up too. + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); +}); + +test("jsPlumb.addEndpoint (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {anchor:[0,0.5,0,-1]}); + var e17 = jsPlumb.addEndpoint(d17, {anchor:"TopCenter"}); + assertContextSize(2); + equals(e16.anchor.x, 0); + equals(e16.anchor.y, 0.5); + equals(e17.anchor.x, 0.5); + equals(e17.anchor.y, 0); +}); + +test("jsPlumb.addEndpoint (simple case, dynamic anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + assertContextSize(2); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); +}); + +test("jsPlumb.addEndpoint (simple case, two arg method)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchor:[0,0.5,0,-1]}); + var e17 = jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchor:"TopCenter"}); + assertContextSize(2); + equals(e16.anchor.x, 0); + equals(e16.anchor.y, 0.5); + equals(false, e16.isTarget); + equals(true, e16.isSource); + equals(e17.anchor.x, 0.5); + equals(e17.anchor.y, 0); + equals(true, e17.isTarget); + equals(false, e17.isSource); +}); + + +test("jsPlumb.addEndpoints (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false, anchor:[0,0.5,0,-1] }, { isTarget:true, isSource:false, anchor:"TopCenter" }]); + assertContextSize(2); + equals(e16[0].anchor.x, 0); + equals(e16[0].anchor.y, 0.5); + equals(false, e16[0].isTarget); + equals(true, e16[0].isSource); + equals(e16[1].anchor.x, 0.5); + equals(e16[1].anchor.y, 0); + equals(true, e16[1].isTarget); + equals(false, e16[1].isSource); +}); + +test("jsPlumb.addEndpoints (with reference params)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var refParams = {anchor:"RightMiddle"}; + var e16 = jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false}, { isTarget:true, isSource:false }], refParams); + assertContextSize(2); + equals(e16[0].anchor.x, 1); + equals(e16[0].anchor.y, 0.5); + equals(false, e16[0].isTarget); + equals(true, e16[0].isSource); + equals(e16[1].anchor.x, 1); + equals(e16[1].anchor.y, 0.5); + equals(true, e16[1].isTarget); + equals(false, e16[1].isSource); +}); + +test("jsPlumb.addEndpoint (simple case, dynamic anchors, two arg method)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchors:["TopCenter", "BottomCenter"]}); + assertContextSize(2); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); +}); + +test('jsPlumb.connect (between two Endpoints)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1); + assertEndpointCount("d2", 1); + jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1); // no new endpoint should have been added + assertEndpointCount("d2", 1); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. +}); + +test('jsPlumb.connect (between two Endpoints, and dont supply any parameters to the Endpoints.)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1); + assertEndpointCount("d2", 1); + jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1); // no new endpoint should have been added + assertEndpointCount("d2", 1); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. +}); + +test('jsPlumb.connect (by endpoint)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); +}); + +test("jsPlumb.connect (two Endpoints - that have been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1, uuid:srcEndpointUuid}); + var e2 = jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1, uuid:dstEndpointUuid}); + jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ] }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertContextSize(3); +}); + +test("jsPlumb.connect (two Endpoints - that have not been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ], source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + var e1 = jsPlumb.getEndpoint(srcEndpointUuid); + ok(e1 != null, "endpoint with src uuid added"); + ok(e1.canvas != null); + var e2 = jsPlumb.getEndpoint(dstEndpointUuid); + ok(e2 != null, "endpoint with target uuid added"); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); +}); + +test("jsPlumb.connect (two Endpoints - that have been already added - by endpoint reference)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1}); + var e2 = jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1}); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); +}); + +test("jsPlumb.connect (two elements)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + jsPlumb.connect({ source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); +}); + +test("jsPlumb.connect (Connector test, straight)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors.Straight, "Straight connector chosen for connection"); +}); + +test("jsPlumb.connect (Connector test, bezier, no params)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Bezier" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors.Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 150, "Bezier connector chose 150 curviness"); +}); + +test("jsPlumb.connect (Connector test, bezier, curviness as int)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", 200] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors.Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 200, "Bezier connector chose 200 curviness"); +}); + +test("jsPlumb.connect (Connector test, bezier, curviness as named option)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", {curviness:300}] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors.Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 300, "Bezier connector chose 300 curviness"); +}); + +test("jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors.Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); +}); + +test("jsPlumb.connect (anchors registered correctly; source and target anchors given, as strings)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:new jsPlumb.Connectors.Straight(), anchors:["LeftMiddle", "RightMiddle"] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors.Straight, "Straight connector chosen for connection"); + equals(0, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.5, conn.endpoints[0].anchor.y, "source anchor y"); + equals(1, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.5, conn.endpoints[1].anchor.y, "target anchor y"); +}); + +test("jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors.Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); +}); + + +test("jsPlumb.connect (two argument method in which some data is reused across connections)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"), d19 = _addDiv("d19"); + var sharedData = { connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }; + var conn = jsPlumb.connect({ source:d16, target:d17}, sharedData); + var conn2 = jsPlumb.connect({ source:d18, target:d19}, sharedData); + assertContextSize(6); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 2); + equals(conn.connector.constructor, jsPlumb.Connectors.Straight, "Straight connector chosen for connection"); + equals(conn2.connector.constructor, jsPlumb.Connectors.Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + equals(0.3, conn2.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn2.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn2.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn2.endpoints[1].anchor.y, "target anchor y"); +}); + +test("jsPlumb.connect (Connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors.Straight, "Straight connector chosen for connection"); +}); + +test("jsPlumb.connect (Endpoint test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoint:new jsPlumb.Endpoints.Rectangle() }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints.Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints.Rectangle, "Rectangle endpoint chosen for connection target"); +}); + +test("jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints.Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints.Rectangle, "Rectangle endpoint chosen for connection target"); +}); + +test("jsPlumb.connect (Endpoints test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoints:[new jsPlumb.Endpoints.Rectangle(),new jsPlumb.Endpoints.Dot()] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints.Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints.Dot, "Dot endpoint chosen for connection target"); +}); + +test("jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints.Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints.Dot, "Dot endpoint chosen for connection target"); +}); + +test("jsPlumb.connect (by Endpoints, connector test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {}); + var e17 = jsPlumb.addEndpoint(d17, {}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:new jsPlumb.Connectors.Straight() }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors.Straight, "Straight connector chosen for connection"); +}); + +test("jsPlumb.connect (by Endpoints, connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {}); + var e17 = jsPlumb.addEndpoint(d17, {}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors.Straight, "Straight connector chosen for connection"); +}); + +test("jsPlumb.connect (by Endpoints, anchors as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = "TopCenter", a17 = "BottomCenter"; + var e16 = jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors.Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, 0.5, "endpoint 16 is at top center");equals(e16.anchor.y, 0, "endpoint 16 is at top center"); + equals(e17.anchor.x, 0.5, "endpoint 17 is at bottom center");equals(e17.anchor.y, 1, "endpoint 17 is at bottom center"); +}); + +test("jsPlumb.connect (by Endpoints, endpoints create anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = [0,0.5,0,-1], a17 = [1,0.0,-1,-1]; + var e16 = jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors.Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, a16[0]);equals(e16.anchor.y, a16[1]); + equals(e17.anchor.x, a17[0]);equals(e17.anchor.y, a17[1]); + equals(e16.anchor.getOrientation()[0], a16[2]); equals(e16.anchor.getOrientation()[1], a16[3]); + equals(e17.anchor.getOrientation()[0], a17[2]); equals(e17.anchor.getOrientation()[1], a17[3]); +}); + +test("jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchor')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {anchor:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = jsPlumb.addEndpoint(d17, {anchor:["TopCenter", "BottomCenter"]}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors.Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); +}); + +test("jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchors')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors.Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); +}); + +test("jsPlumb.connect (connect by element, default endpoint, supplied dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var anchors = [ [0.25, 0, 0, -1], [1, 0.25, 1, 0], [0.75, 1, 0, 1], [0, 0.75, -1, 0] ]; + jsPlumb.connect({source:d1, target:d2, dynamicAnchors:anchors}); // auto connect with default endpoint and provided anchors + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + assertContextSize(2); +}); + +test("jsPlumb.connect (connect by element, supplied endpoint and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = jsPlumb.addEndpoint(d1, endpoint); + var e2 = jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + assertContextSize(3); + jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + assertContextSize(2); +}); + +test("jsPlumb.connect (connect by element, supplied endpoints using 'source' and 'target' (this test is identical to the one above apart from the param names), and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = jsPlumb.addEndpoint(d1, endpoint); + var e2 = jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + jsPlumb.connect({source:e1, target:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + assertContextSize(3); + jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + assertContextSize(2); +}); + + +test("jsPlumb.connect (testing for connection event callback)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var connectCallback = null, detachCallback = null; + jsPlumb.bind("jsPlumbConnection", function(params) { + connectCallback = $.extend({}, params); + }); + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + detachCallback = $.extend({}, params); + }); + jsPlumb.connect({source:d1, target:d2}); // auto connect with default endpoint and anchor set + ok(connectCallback != null, "connect callback was made"); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + jsPlumb.detach({source:d1, target:d2}); + assertContextSize(2); + ok(detachCallback != null, "detach callback was made"); +}); + +test("jsPlumb.connect (overlays, long-hand version)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = {width:40,length:40,location:0.7, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"}}; + var connection1 = jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ new jsPlumb.Overlays.Image({src:"../img/littledot.png", events:{"click":imageEventListener("window1", "window2")}}), + new jsPlumb.Overlays.Label({label:"CONNECTION 1", location:0.3}), + new jsPlumb.Overlays.Arrow(arrowSpec) ] + }); + equals(3, connection1.overlays.length); + equals(jsPlumb.Overlays.Image, connection1.overlays[0].constructor); + equals(jsPlumb.Overlays.Label, connection1.overlays[1].constructor); + + equals(jsPlumb.Overlays.Arrow, connection1.overlays[2].constructor); + equals(0.7, connection1.overlays[2].loc); + equals(40, connection1.overlays[2].width); + equals(40, connection1.overlays[2].length); +}); + +test("jsPlumb.connect (overlays, short-hand version)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var loc = { location:0.7 }; + var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} }; + var connection1 = jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ [ "Image", { src:"../img/littledot.png", events:{"click":imageEventListener("window1", "window2")}} ], + ["Label", {label:"CONNECTION 1", location:0.3}], + ["Arrow", arrowSpec, loc] ] + }); + equals(3, connection1.overlays.length); + equals(jsPlumb.Overlays.Image, connection1.overlays[0].constructor); + equals(jsPlumb.Overlays.Label, connection1.overlays[1].constructor); + + equals(jsPlumb.Overlays.Arrow, connection1.overlays[2].constructor); + equals(0.7, connection1.overlays[2].loc); + equals(40, connection1.overlays[2].width); + equals(40, connection1.overlays[2].length); +}); + +test("jsPlumb.connect, specify arrow overlay using string identifier only", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var conn = jsPlumb.connect({source:d1,target:d2,overlays:["Arrow"]}); + equals(jsPlumb.Overlays.Arrow, conn.overlays[0].constructor); +}); + +// this test is for the original detach function; it should stay working after i mess with it +// a little. +test("jsPlumb.detach (by element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + var e3 = jsPlumb.addEndpoint(d1); + var e4 = jsPlumb.addEndpoint(d2); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + jsPlumb.connect({ sourceEndpoint:e3, targetEndpoint:e4 }); // make two connections to be sure this works ;) + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionCount(e3, 1); + assertConnectionCount(e4, 1); + + jsPlumb.detach("d1", "d2"); + + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionCount(e3, 0); + assertConnectionCount(e4, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); +}); + +// detach is being made to operate more like connect - by taking one argument with a whole +// bunch of possible params in it. if two args are passed in it will continue working +// in the old way. +test("jsPlumb.detach (params object, using element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + jsPlumb.detach({source:"d1", target:"d2"}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); +}); + +test("jsPlumb.detach (params object, using element objects)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + jsPlumb.detach({source:d1, target:d2}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + assertContextSize(2); +}); + +test("jsPlumb.detach (source and target as endpoint UUIDs)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {uuid:"abcdefg"}); + ok(jsPlumb.getEndpoint("abcdefg") != null, "e1 exists"); + var e2 = jsPlumb.addEndpoint(d2, {uuid:"hijklmn"}); + ok(jsPlumb.getEndpoint("hijklmn") != null, "e2 exists"); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + jsPlumb.detach({uuids:["abcdefg", "hijklmn"]}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + assertContextSize(2); +}); + +test("jsPlumb.detach (sourceEndpoint and targetEndpoint supplied)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + jsPlumb.detach({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + assertContextSize(2); +}); + +test("jsPlumb.makeDynamicAnchors (longhand)", function() { + var anchors = [jsPlumb.makeAnchor(0.2, 0, 0, -1), jsPlumb.makeAnchor(1, 0.2, 1, 0), + jsPlumb.makeAnchor(0.8, 1, 0, 1), jsPlumb.makeAnchor(0, 0.8, -1, 0) ]; + var dynamicAnchor = jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); +}); + +test("jsPlumb.makeDynamicAnchors (shorthand)", function() { + var anchors = [[0.2, 0, 0, -1], [1, 0.2, 1, 0], + [0.8, 1, 0, 1], [0, 0.8, -1, 0] ]; + var dynamicAnchor = jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); +}); + +test("Connection.isVisible/setVisible", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c1 = jsPlumb.connect({source:d1,target:d2}); + equals(true, c1.isVisible(), "Connection is visible after creation."); + c1.setVisible(false); + equals(false, c1.isVisible(), "Connection is not visible after calling setVisible(false)."); + equals($(c1.canvas).css("display"), "none"); + c1.setVisible(true); + equals(true, c1.isVisible(), "Connection is visible after calling setVisible(true)."); + equals($(c1.canvas).css("display"), "block"); +}); + +test("Endpoint.isVisible/setVisible basic test (no connections)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals($(e1.canvas).css("display"), "none"); + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals($(e1.canvas).css("display"), "block"); +}); + +test("Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should track changes in the source, because it has only this connection.)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1), e2 = jsPlumb.addEndpoint(d2); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + var c1 = jsPlumb.connect({source:e1, target:e2}); + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals(false, e2.isVisible(), "other Endpoint is not visible either."); + equals(false, c1.isVisible(), "connection between the two is not visible either."); + + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals(true, e2.isVisible(), "other Endpoint is visible too"); + equals(true, c1.isVisible(), "connection between the two is visible too."); +}); + +test("Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should not track changes in the source, because it has another connection.)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var e1 = jsPlumb.addEndpoint(d1), e2 = jsPlumb.addEndpoint(d2, { maxConnections:2 }), e3 = jsPlumb.addEndpoint(d3); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + var c1 = jsPlumb.connect({source:e1, target:e2}); + var c2 = jsPlumb.connect({source:e2, target:e3}); + + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals(true, e2.isVisible(), "other Endpoint should still be visible."); + equals(true, e3.isVisible(), "third Endpoint should still be visible."); + equals(false, c1.isVisible(), "connection between the two is not visible either."); + equals(true, c2.isVisible(), "other connection is visible."); + + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals(true, e2.isVisible(), "other Endpoint is visible too"); + equals(true, c1.isVisible(), "connection between the two is visible too."); + equals(true, c2.isVisible(), "other connection is visible."); +}); + + +/** + * leave this test at the bottom! + */ +test('unload test', function() { + jsPlumb.unload(); + var contextNode = $("._jsPlumb_context"); + ok(contextNode.length == 0, 'context node unloaded'); +}); + + +}); diff --git a/archive/1.2.6/jsPlumb-connectors-raphael-1.2.6-RC1.js b/archive/1.2.6/jsPlumb-connectors-raphael-1.2.6-RC1.js new file mode 100644 index 000000000..745f13f38 --- /dev/null +++ b/archive/1.2.6/jsPlumb-connectors-raphael-1.2.6-RC1.js @@ -0,0 +1,22 @@ +jsPlumb.Connectors.SVGBezier = function(curviness) +{ + var paper = null; + + jsPlumb.Connectors.Bezier.apply(this, arguments); + + this.paint = function(d, ctx) { + if (paper) paper.remove(); // not ideal. we want to just move it, really. + // this is just an experiment right now. things will improve. + paper = Raphael(d[0], d[1], d[2], d[3]); + var path = "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]; + + // todo the stroke style etc should be passed in here, not read out of the context. + // it's only like this because the original implementation painted on canvas, and jsPlumb + // was taking care of paint styles etc. + + paper.path(path).attr({ + stroke:ctx.strokeStyle, + "stroke-width":ctx.lineWidth + }); + }; +}; \ No newline at end of file diff --git a/archive/1.2.6/jsPlumb-connectors-vml-1.2.6-RC1.js b/archive/1.2.6/jsPlumb-connectors-vml-1.2.6-RC1.js new file mode 100644 index 000000000..967834ddc --- /dev/null +++ b/archive/1.2.6/jsPlumb-connectors-vml-1.2.6-RC1.js @@ -0,0 +1,153 @@ +;(function() { + + document.createStyleSheet().addRule(".jsplumb_vml", "behavior:url(#default#VML);border:1px solid red;position:absolute;"); + document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); + + var scale = 1000, + _atts = function(o, atts) { + for (var i in atts) o.setAttribute(i, atts[i]); + }, + _node = function(name, d, atts) { + var o = document.createElement("jsplumb:" + name); + o.className = "jsplumb_vml"; + _pos(o, d); + atts["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _atts(o, atts); + document.body.appendChild(o); + return o; + }, + _pos = function(o,d) { + o.style.left = d[0] + "px"; + o.style.top = d[1] + "px"; + o.style.width= d[2] + "px"; + o.style.height= d[3] + "px"; + /* + o.left = d[0]; + o.top = d[1]; + o.width = d[2]; + o.height = d[3]; + */ + }, + _conv = function(v) { + return Math.floor(v * scale); + }, + _toHex = function(n) { + var _n = function(k) { + var h = Number(k).toString(16); + return k < 16 ? "0" + h : "" + h; + }; + var o = "#" + _n(n[0]) + _n(n[1]) + _n(n[2]); + if (n.length == 4) o = o + _n(n[3]); + return o; + }, + _convertStyle = function(s) { + var p = /(rgb[a]?\()(.*)(\))/; + if (s.match(p)) { + var parts = s.match(p)[2].split(","); + return _toHex(parts); + } + else return s; + }; + + /* + * Class: VmlComponent + * base class for Vml endpoints and connectors. + */ + var VmlComponent = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }; + + /* + * Class: VmlConnector + * base class for Vml connectors. extends VmlComponent. + */ + var VmlConnector = function() { + var self = this, vml = null; + VmlComponent.apply(this, arguments); + this.paint = function(d, style, anchor) { + if (style != null) { + var path = self.getPath(d), p = {}; + if (style.strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] =_convertStyle(style.strokeStyle); + p["strokeweight"] = style.lineWidth + "px"; + } + else p["stroked"] = "false"; + if (style.fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = _convertStyle(style.fillStyle); + } + else p["filled"] = "false"; + p["path"] = path; + if (vml == null) { + vml = _node("shape", d, p); + // console.log("c init at " + d[0] + "," + d[1] + "," +d[2] + "," + d[3] + "," + d[4] + "," + d[5]+ "," + d[6] + "," + d[7]); + } + else { + //console.log("c set pos " + d[0] + "," + d[1] + "," +d[2] + "," + d[3] + "," + d[4] + "," + d[5]+ "," + d[6] + "," + d[7]); + _pos(vml, d); + _atts(vml, p); + } + } + }; + }; + + /* + * Class: VmlEndpoint + * base class for Vml endpoints. extends VmlComponent. + */ + var VmlEndpoint = function() { + VmlComponent.apply(this, arguments); + var vml = null, self = this; + this.paint = function(d, style, anchor) { + var p = {}; + if (style.strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] =_convertStyle(style.strokeStyle); + p["strokeweight"] = style.lineWidth + "px"; + } + if (style.fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = style.fillStyle; + } + //d[2] = _conv(d[2]);d[3] = _conv(d[3]); + if (vml == null) { + vml = self.getVml(d, p, anchor); + //console.log("e init at " + d[0] + "," + d[1] + "," +d[2] + "," + d[3] + "," + d[4] ); + } + else { + _pos(vml, d); + _atts(vml, p); + //console.log("e set pos " + d[0] + "," + d[1] + "," +d[2] + "," + d[3] + "," + d[4] ); + } + }; + }; + + jsPlumb.Connectors.vml.Bezier = function() { + jsPlumb.Connectors.Bezier.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "M" + _conv(d[4]) + "," + _conv(d[5]) + " C" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Straight = function() { + jsPlumb.Connectors.Straight.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return " m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Endpoints.vml.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("oval", d, atts); }; + }; + + jsPlumb.Endpoints.vml.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("rect", d, atts); }; + }; +})(); diff --git a/archive/1.2.6/jsPlumb-defaults-1.2.6-RC1.js b/archive/1.2.6/jsPlumb-defaults-1.2.6-RC1.js new file mode 100644 index 000000000..2c7978553 --- /dev/null +++ b/archive/1.2.6/jsPlumb-defaults-1.2.6-RC1.js @@ -0,0 +1,813 @@ +/* +* jsPlumb-defaults-1.2.6-RC1 +* +* This script contains the default Anchors, Endpoints, Connectors and Overlays for jsPlumb. It should be used with jsPlumb 1.1.0 and above; +* prior to version 1.1.0 of jsPlumb the defaults were included inside the main script. +* +* NOTE: for production usage you should use jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, +* in a minified file. +* +* Dual licensed under MIT and GPL2. +*/ + +(function() { + + var ie = !!!document.createElement('canvas').getContext; + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * getOrientation : function() { return [ox, oy]; } + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + var _curryAnchor = function(x,y,ox,oy) { + return function() { + return jsPlumb.makeAnchor(x,y,ox,oy); + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1); + + + jsPlumb.Defaults.DynamicAnchors = function() { + return jsPlumb.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"]); + }; + jsPlumb.Anchors["AutoDefault"] = function() { return jsPlumb.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors()); }; + + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + var currentPoints = null; + var _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + + _dx = _tx - _sx, _dy = (_ty - _sy); + _m = _dy / _dx, _m2 = -1 / _m; + + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + + return currentPoints; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + var xp = _sx + (location * _dx); + var yp = (_m == Infinity || _m == -Infinity) ? _sy + (location * (_ty - _sy)) : (_m * xp) + _b; + return {x:xp, y:yp}; + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { return _m; }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + var orientation = distance > 0 ? 1 : -1; + var y = Math.abs(distance * Math.sin(_theta)); + if (_sy > _ty) y = y * -1; + var x = Math.abs(distance * Math.cos(_theta)); + if (_sx > _tx) x = x * -1; + return {x:p.x + (orientation * x), y:p.y + (orientation * y)}; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = self.gradientAtPoint(p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + /** + * This Connector draws a Bezier curve with two control points. + * + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * note that the method signature changed in 1.2.6 to take a params object, so the method + * argument was renamed. you can still provide just an integer to this constructor, though the + * preferred method is to use {curviness:XXX}. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + this.majorAnchor = 150; + // backwards compatibility (ideally we'd just use params.curviness || 150). + if (params) { + if (params.constructor == Number) this.majorAnchor = params; + else if (params.curviness) this.majorAnchor = params.curviness; + } + this.minorAnchor = 10; + var currentPoints = null; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h; + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) + { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx); var minx2 = Math.min(_CP[0], _CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(_sx,_tx); var maxx2 = Math.max(_CP[0], _CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty); var miny2 = Math.min(_CP[1], _CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(_sy,_ty); var maxy2 = Math.max(_CP[1], _CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + this.paint = function(d, ctx) { + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + return jsBezier.perpendicularToCurveAt(_makeCurve(), location, length, distance); + }; + + this.createGradient = function(dim, ctx, swap) { + return (swap) ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + + /** + * Types of endpoint UIs. we supply four - a circle of default radius 10px, a rectangle of + * default size 20x20, an image (with no default), and a Triangle, of default size 15. + * you can supply others of these if you want to - see the documentation for a howto. + */ + + /** + * a round endpoint, with default radius 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }; + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + if (endpointStyle.gradient && !ie) { + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * A Rectangular endpoint, with default size 20x20. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + jsPlumb.Endpoints.Triangle = function(params) { + + params = params || { width:55, height:55 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - width/2; + var y = anchorPoint[1] - height/2; + + jsPlumb.sizeCanvas(canvas, x, y, width, height); + + var ctx = canvas.getContext('2d'); + var offsetX = 0, offsetY = 0, angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = endpointStyle.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' or, since 1.2.4, a 'src' property in the params object.. + */ + jsPlumb.Endpoints.Image = function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.src || params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + }; + + /** + * An arrow overlay. you can provide: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + params = params || {}; + var self = this; + this.length = params.length || 20; + this.width = params.width || 20; + this.connection = params.connection; + var direction = (params.direction || 1) < 0 ? -1 : 1; + /*var fillStyle = params.fillStyle; + var strokeStyle = params.strokeStyle; + var lineWidth = params.lineWidth || 1;*/ + var paintStyle = params.paintStyle || { lineWidth:1 }; + this.loc = params.location == null ? 0.5 : params.location; + // how far along the arrow the lines folding back in come to. default is 62.3%. + var foldback = params.foldback || 0.623; + var _getFoldBackPoint = function(connector, loc) { + if (foldback == 0.5) return connector.pointOnPath(loc); + else { + var adj = 0.5 - foldback; // we calculate relative to the center + return connector.pointAlongPathFrom(loc, direction * self.length * adj); + } + }; + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.draw = function(connector, ctx, currentConnectionPaintStyle) { + + // this is the arrow head position + var hxy = connector.pointAlongPathFrom(self.loc, direction * (self.length / 2)); + // this is the center of the tail + var txy = connector.pointAlongPathFrom(self.loc, -1 * direction * (self.length / 2)), tx = txy.x, ty = txy.y; + // this is the tail vector + var tail = connector.perpendicularToPathAt(self.loc, self.width, -1 * direction * (self.length / 2)); + // this is the point the tail goes in to + var cxy = _getFoldBackPoint(connector, self.loc); + + // if loc = 1, then hxy should be flush with the element. + if (self.loc == 1) { + var lxy = connector.pointOnPath(self.loc); + var dx = lxy.x - hxy.x, dy = lxy.y - hxy.y; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + // if loc = 0, then tail midpoint should be flush with the element. + if (self.loc == 0) { + var lxy = connector.pointOnPath(self.loc); + var tailMid = foldback > 1 ? cxy : { + x:tail[0].x + ((tail[1].x - tail[0].x) / 2), + y:tail[0].y + ((tail[1].y - tail[0].y) / 2) + }; + var dx = lxy.x - tailMid.x, dy = lxy.y - tailMid.y; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x); + var maxx = Math.max(hxy.x, tail[0].x, tail[1].x); + var miny = Math.min(hxy.y, tail[0].y, tail[1].y); + var maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + ctx.lineWidth = paintStyle.lineWidth; + ctx.beginPath(); + ctx.moveTo(hxy.x, hxy.y); + ctx.lineTo(tail[0].x, tail[0].y); + ctx.lineTo(cxy.x, cxy.y); + ctx.lineTo(tail[1].x, tail[1].y); + ctx.lineTo(hxy.x, hxy.y); + ctx.closePath(); + + if (paintStyle.strokeStyle) { + ctx.strokeStyle = paintStyle.strokeStyle; + ctx.stroke(); + } + ctx.fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle; + ctx.fill(); + + + + return [ minx, maxx, miny, maxy]; + }; + }; + + /** + * a basic arrow. this is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. See Arrow for params. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + /** + * a diamond. like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. See Arrow for params. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40; + var p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + /** + * A Label overlay. Params you can provide: + * + * labelStyle - js object containing style instructions for the label. defaults to jsPlumb.Defaults.LabelStyle. + * label - the label to paint. may be a string or a function that returns a string. nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * borderWidth - width of a border to paint. defaults to zero. + * borderStyle - strokeStyle to use when painting the border, if necessary. + */ + jsPlumb.Overlays.Label = function(params) { + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + this.connection = params.connection; + var self = this; + var labelWidth = null, labelHeight = null, labelText = null, labelPadding = null; + this.location = params.location || 0.5; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var _textDimensions = function(ctx) { + if (self.cachedDimensions) return self.cachedDimensions; // return cached copy if we can. if we add a setLabel function remember to clear the cache. + labelText = typeof self.label == 'function' ? self.label(self) : self.label; + var d = {}; + if (labelText) { + var lines = labelText.split(/\n|\r\n/); + ctx.save(); + if (self.labelStyle.font) ctx.font = self.labelStyle.font; + var t = _widestLine(lines, ctx); + // a fake text height measurement: use the width of upper case M + var h = ctx.measureText("M").width; + labelPadding = self.labelStyle.padding || 0.25; + labelWidth = t + (2 * t * labelPadding); + labelHeight = (lines.length * h) + (2 * h * labelPadding); + var textHeight = lines.length * h; + ctx.restore(); + d = {width:labelWidth, height:labelHeight, lines:lines, oneLine:h, padding:labelPadding, textHeight:textHeight}; + } + if (typeof self.label != 'function') self.cachedDimensions = d; // cache it if we can. + return d; + }; + this.computeMaxSize = function(connector, ctx) { + var td = _textDimensions(ctx); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + var _widestLine = function(lines, ctx) { + var max = 0; + for (var i = 0; i < lines.length; i++) { + var t = ctx.measureText(lines[i]).width; + if (t > max) max = t; + } + return max; + }; + + this.draw = function(connector, ctx, currentConnectionPaintStyle) { + var td = _textDimensions(ctx); + if (td.width) { + var cxy = connector.pointOnPath(self.location); + if (self.labelStyle.font) ctx.font = self.labelStyle.font; + if (self.labelStyle.fillStyle) + ctx.fillStyle = self.labelStyle.fillStyle; + else + ctx.fillStyle = "rgba(0,0,0,0)"; + + var minx = cxy.x - (td.width / 2); + var miny = cxy.y - (td.height / 2); + + ctx.fillRect(minx, miny , td.width , td.height ); + + if (self.labelStyle.color) ctx.fillStyle = self.labelStyle.color; + ctx.textBaseline = "middle"; + ctx.textAlign = "center"; + for (i = 0; i < td.lines.length; i++) { + ctx.fillText(td.lines[i],cxy.x, cxy.y - (td.textHeight / 2) + (td.oneLine/2) + (i*td.oneLine)); + } + + // border + if (self.labelStyle.borderWidth > 0) { + ctx.strokeStyle = self.labelStyle.borderStyle || "black"; + ctx.strokeRect(minx, miny, td.width , td.height ); + } + + return [minx, minx+td.width, miny, miny+td.height]; + } + else return [0,0,0,0]; + }; + }; + + /** + * an image overlay. params may contain: + * + * location : proportion along the connector to draw the image. optional. + * src : image src. required. + * events : map of event names to functions; each event listener will be bound to the img. + */ + jsPlumb.Overlays.Image = function(params) { + var self = this; + this.location = params.location || 0.5; + this.img = new Image(); + this.connection = params.connection; + var imgDiv = null; + var notReadyInterval = null; + var notReadyConnector, notReadyContext; + var events = params.events || {}; + var _init = function() { + if (self.ready) { + window.clearInterval(notReadyInterval); + imgDiv = document.createElement("img"); + imgDiv.src = self.img.src; + imgDiv.style.position = "absolute"; + imgDiv.style.display="none"; + imgDiv.className = "_jsPlumb_overlay"; + document.body.appendChild(imgDiv);// HMM + // attach events + for (var e in events) { + jsPlumb.CurrentLibrary.bind(imgDiv, e, events[e]); + } + if (notReadyConnector && notReadyContext) { + _draw(notReadyConnector, notReadyContext); + notReadyContext = null; + notReadyConnector = null; + } + } + }; + this.img.onload = function() { + self.ready = true; + // jsPlumb.repaintAll(); + }; + this.img.src = params.src || params.url; + + notReadyInterval = window.setInterval(_init, 250); + + this.computeMaxSize = function(connector, ctx) { + return [ self.img.width, self.img.height ]; + }; + + var _draw = function(connector, ctx, currentConnectionPaintStyle) { + if (imgDiv != null) { + var cxy = connector.pointOnPath(self.location); + var canvas = jsPlumb.CurrentLibrary.getElementObject(ctx.canvas); + var canvasOffset = jsPlumb.CurrentLibrary.getOffset(canvas); + var minx = cxy.x - (self.img.width/2); + var miny = cxy.y - (self.img.height/2); + var o = {left:canvasOffset.left + minx, top:canvasOffset.top + miny}; + jsPlumb.CurrentLibrary.setOffset(imgDiv, o); + imgDiv.style.display = "block"; + return [minx,minx + self.img.width, miny, miny+self.img.height]; + } + }; + + this.draw = function(connector, ctx) { + if (self.ready) + return _draw(connector, ctx); + else { + notReadyConnector = connector; + notReadyContext = ctx; + return [0,0,0,0]; + } + }; + }; +})(); \ No newline at end of file diff --git a/archive/1.2.6/jsPlumb-flowchart-0.1-RC1.js b/archive/1.2.6/jsPlumb-flowchart-0.1-RC1.js new file mode 100644 index 000000000..d1c20f86c --- /dev/null +++ b/archive/1.2.6/jsPlumb-flowchart-0.1-RC1.js @@ -0,0 +1,221 @@ +;(function() { + + jsPlumb.Connectors.Flowchart = function(params) { + params = params || {}; + var self = this, + minStubLength = params.minStubLength || 30, + segments = [], + segmentGradients = [], + segmentProportions = [], + segmentLengths = [], + segmentProportionalLengths = [], + points = [], + swapX, + swapY, + /** + * recalculates the gradients of each segment, and the points at which the segments begin, proportional to the total length travelled + * by all the segments that constitute the connector. + */ + updateSegmentGradientsAndProportions = function(startX, startY, endX, endY) { + var total = 0; + for (var i = 0; i < segments.length; i++) { + var sx = i == 0 ? startX : segments[i][2], + sy = i == 0 ? startY : segments[i][3], + ex = segments[i][0], + ey = segments[i][1]; + + segmentGradients[i] = sx == ex ? Infinity : 0; + segmentLengths[i] = Math.abs(sx == ex ? ey - sy : ex - sx); + total += segmentLengths[i]; + } + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segmentLengths[i] / total; + segmentProportions[i] = [curLoc, (curLoc += (segmentLengths[i] / total)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0]; + var ly = segments.length == 0 ? sy : segments[segments.length - 1][1]; + segments.push([x, y, lx, ly]); + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment that contains the point which is 'location' distance along the entire path, where 'location' is + * a decimal between 0 and 1 inclusive. in this connector type paths are made up of a list of segments, each of which contributes some fraction to + * the total length. + */ + findSegmentForLocation = function(location) { + var idx = segmentProportions.length - 1, inSegmentProportion = 0; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + segments = []; + segmentGradients = []; + segmentProportionalLengths = []; + segmentLengths = []; + segmentProportionals = []; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + offx = (lw / 2) + (minStubLength * 2), + offy = (lw / 2) + (minStubLength * 2), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(), + to = targetAnchor.orientation || targetAnchor.getOrientation(), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx, + h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy; + if (w < minWidth) { + offx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + offy += (minWidth - h) / 2; + h = minWidth; + } + sx = swapX ? w-offx : offx, + sy = swapY ? h-offy : offy, + tx = swapX ? offx : w-offx , + ty = swapY ? offy : h-offy, + startStubX = sx + (so[0] * minStubLength), + startStubY = sy + (so[1] * minStubLength), + endStubX = tx + (to[0] * minStubLength), + endStubY = ty + (to[1] * minStubLength), + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2); + + x -= offx; y -= offy; + points = [x, y, w, h, sx, sy, tx, ty], extraPoints = []; + + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + if (so[0] == 0) { + var startStubIsBeforeEndStub = startStubY < endStubY; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } + } + else { + var startStubIsBeforeEndStub = startStubX < endStubX; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } + } + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentGradientsAndProportions(sx, sy, tx, ty); + + return points; + }; + + this.paint = function(dimensions, ctx) { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location) { + return self.pointAlongPathFrom(location, 0); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location) { + return segmentGradients[findSegmentForLocation(location)["index"]]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance) { + var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segmentLengths[s.index], m = segmentGradients[s.index]; + var e = { + //x : m == Infinity ? seg[2] : /*swapX ? seg[2] - (p * sl) - distance : */seg[2] + (p * sl) + distance, + + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + + + //y : m == 0 ? seg[3] : /*swapY ? seg[3] - (p * sl) - distance : */seg[3] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + //console.log("pointalongpath, swapX =" + swapX + ",swapY=" + swapY, "loc", location, "travel", (p * sl), "dist", distance, e.x, e.y, "seg", seg, "len", sl, "prop.", p); + + return e; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = segmentGradients[p.segmentInfo.index]; + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + + }; + }; +})(); \ No newline at end of file diff --git a/archive/1.2.6/mootools.jsPlumb-1.2.6-RC1.js b/archive/1.2.6/mootools.jsPlumb-1.2.6-RC1.js new file mode 100644 index 000000000..91921c6fe --- /dev/null +++ b/archive/1.2.6/mootools.jsPlumb-1.2.6-RC1.js @@ -0,0 +1,333 @@ +/* + * mootools.jsPlumb 1.2.6-RC1 + * + * MooTools specific functionality for jsPlumb. + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + * NOTE: for production usage you should use mootools.jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, + * in a minified file. + * + * Dual licensed under MIT and GPL2. + * + */ + +(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function(now) { + this.parent(now); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}; + var _droppableOptions = {}; + var _draggablesByScope = {}; + var _draggablesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }; + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + var _getElementObject = function(el) { + return $(el); + }; + + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + //var m = new Fx.Morph(el, options); + m.start(properties); + }, + + appendElement : function(child, parent) { + _getElementObject(parent).grab(child); + }, + + bind : function(el, event, callback) { + el = _getElementObject(el); + el.addEvent(event, callback); + }, + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el); + var drags = _draggablesById[id]; + return drags[0].scope; + }, + + getElementObject : _getElementObject, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + getPageXY : function(eventObject) { + return [eventObject.event.pageX, eventObject.event.pageY]; + }, + + getScrollLeft : function(el) { + return null; + }, + + getScrollTop : function(el) { + return null; + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[0]; + return { left: ui.offsetLeft, top: ui.offsetTop }; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options) { + var id = jsPlumb.getId(el); + var drag = _draggablesById[id]; + if (!drag) { + var originalZIndex = 0, originalCursor = null; + var dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + options['onStart'] = jsPlumb.wrap(options['onStart'], function() + { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() + { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + }); + + // DROPPABLES: + var scope = options['scope'] || jsPlumb.Defaults.Scope; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop'); + } + }; + + drag = new Drag.Move(el, options); + drag.scope = scope; + //console.log("drag scope initialized to ", scope); + _add(_draggablesByScope, scope, drag); + _add(_draggablesById, el.get("id"), drag); + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + initDroppable : function(el, options) { + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _add(_droppables, scope, el); + var id = jsPlumb.getId(el); + _droppableOptions[id] = options; + var filterFunc = function(entry) { return entry.element != el; }; + var draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + isAlreadyDraggable : function(el) { + return _draggablesById[jsPlumb.getId(el)] != null; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + return (typeof Drag != undefined && typeof Drag.Move != undefined); + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + _getElementObject(element).dispose(); // ?? + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + setDragScope : function(el, scope) { + var drag = _draggablesById[el.get("id")]; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + drag[0].droppables = droppables; + }, + + setOffset : function(el, o) { + _getElementObject(el).setPosition({x:o.left, y:o.top}); + } + }; + + window.addEvent('domready', jsPlumb.init); +})(); diff --git a/archive/1.2.6/mootools.jsPlumb-1.2.6-all-min.js b/archive/1.2.6/mootools.jsPlumb-1.2.6-all-min.js new file mode 100644 index 000000000..44482ed96 --- /dev/null +++ b/archive/1.2.6/mootools.jsPlumb-1.2.6-all-min.js @@ -0,0 +1 @@ +(function(){var d=!!!document.createElement("canvas").getContext;var h=function(u,s,t){var r=u[s];if(r==null){r=[];u[s]=r}r.push(t);return r};var g=null;var n=function(r,s){return q.CurrentLibrary.getAttribute(b(r),s)};var f=function(s,t,r){q.CurrentLibrary.setAttribute(b(s),t,r)};var m=function(s,r){q.CurrentLibrary.addClass(b(s),r)};var k=function(s,r){return q.CurrentLibrary.hasClass(b(s),r)};var c=function(s,r){q.CurrentLibrary.removeClass(b(s),r)};var b=function(r){return q.CurrentLibrary.getElementObject(r)};var p=function(r){return q.CurrentLibrary.getOffset(b(r))};var e=function(r){return q.CurrentLibrary.getSize(b(r))};var l=function(r,s){if(r.logEnabled&&typeof console!="undefined"){console.log(s)}};var a=function(){var v={};var A=this;this.overlayPlacements=[];this.paintStyle=null,this.hoverPaintStyle=null;this._over=function(E){var G=p(b(A.canvas));var I=q.CurrentLibrary.getPageXY(E);var B=I[0]-G.left,H=I[1]-G.top;if(B>0&&H>0&&B=B&&D[2]<=H&&D[3]>=H)){return true}}if(!d){var F=A.canvas.getContext("2d").getImageData(parseInt(B),parseInt(H),1,1);return F.data[0]!=0||F.data[1]!=0||F.data[2]!=0||F.data[3]!=0}else{}}return false};this.bind=function(B,C){h(v,B,C)};this.fireUpdate=function(D,E,B){if(v[D]){for(var C=0;C=0){delete (aq[ar]);aq.splice(ar,1);return true}}}return false};var r=function(ar,aq){return C(ar,function(at,au){aa[au]=aq;if(q.CurrentLibrary.isDragSupported(at)){q.CurrentLibrary.setDraggable(at,aq)}})};var ai=function(aq,ar){N(n(aq,"id"),function(at){at.canvas.style.display=ar})};var H=function(aq){return C(aq,function(at,ar){var au=aa[ar]==null?R:aa[ar];au=!au;aa[ar]=au;q.CurrentLibrary.setDraggable(at,au);return au})};var s=function(aq){N(aq,function(at){var ar=("none"==at.canvas.style.display);at.canvas.style.display=ar?"block":"none"})};var y=function(aw){var au=aw.timestamp,aq=aw.recalc,av=aw.offset,ar=aw.elId;if(!aq){if(au&&au===w[ar]){return}}if(aq||av==null){var at=b(ar);if(at!=null){J[ar]=e(at);P[ar]=p(at);w[ar]=au}}else{P[ar]=av}};var ap=function(aq,ar){var at=b(aq);var au=n(at,"id");if(!au||au=="undefined"){if(arguments.length==2&&arguments[1]!=undefined){au=ar}else{au="jsPlumb_"+al()}f(at,"id",au)}return au};var ag=function(at,aq,ar){at=at||function(){};aq=aq||function(){};return function(){var au=null;try{au=aq.apply(this,arguments)}catch(av){l(x,"jsPlumb function failed : "+av)}if(ar==null||(au!==ar)){try{at.apply(this,arguments)}catch(av){l(x,"wrapped function failed : "+av)}}return au}};this.connectorClass="_jsPlumb_connector";this.endpointClass="_jsPlumb_endpoint";this.overlayClass="_jsPlumb_overlay";this.Anchors={};this.Connectors={};this.Endpoints={};this.Overlays={};this.addEndpoint=function(aw,au,aA){aA=aA||{};var ar=q.extend({},aA);q.extend(ar,au);ar.endpoint=ar.endpoint||x.Defaults.Endpoint||q.Defaults.Endpoint;ar.endpointStyle=ar.endpointStyle||x.Defaults.EndpointStyle||q.Defaults.EndpointStyle;var at=b(aw),aq=n(at,"id");ar.source=at;y({elId:aq});var ax=ah(ar);h(ab,aq,ax);var ay=P[aq],av=J[aq];var az=ax.anchor.compute({xy:[ay.left,ay.top],wh:av,element:ax});ax.paint({anchorLoc:az});return ax};this.addEndpoints=function(av,ar,aq){var au=[];for(var at=0;at0?Y(aC,aB)!=-1:true};for(var av in E){if(at(ay,av)){aq[av]=[];for(var au=0;au=4){ar.orientation=[arguments[2],arguments[3]]}if(arguments.length==6){ar.offsets=[arguments[4],arguments[5]]}}var ax=new V(ar);ax.clone=function(){return new V(ar)};return ax};this.makeAnchors=function(ar){var at=[];for(var aq=0;aq0?aA[0]:null;var aw=aA.length>0?0:-1;this.locked=false;var az=this;var av=function(aD,aB,aH,aG,aC){var aF=aG[0]+(aD.x*aC[0]),aE=aG[1]+(aD.y*aC[1]);return Math.sqrt(Math.pow(aB-aF,2)+Math.pow(aH-aE,2))};var aq=ar||function(aL,aC,aD,aE,aB){var aG=aD[0]+(aE[0]/2),aF=aD[1]+(aE[1]/2);var aI=-1,aK=Infinity;for(var aH=0;aHaO){aO=aW}}var a1=this.connector.compute(aQ,a3,this.endpoints[a7].anchor,this.endpoints[aN].anchor,az.paintStyleInUse.lineWidth,aO);q.sizeCanvas(at,a1[0],a1[1],a1[2],a1[3]);var aU=function(a8,ba){a8.save();q.extend(a8,ba);if(ba.gradient&&!d){var bb=az.connector.createGradient(a1,a8,(aV==this.sourceId));for(var a9=0;a9=0){aE.connections.splice(aQ,1);if(!aT){var aS=aR.endpoints[0]==aE?aR.endpoints[1]:aR.endpoints[0];aS.detach(aR,true)}X(aR.canvas,aR.container);G(E,aR.scope,aR);if(!aT){W(aR)}}};this.detachAll=function(){while(aE.connections.length>0){aE.detach(aE.connections[0])}};this.detachFrom=function(aR){var aS=[];for(var aQ=0;aQ=0){aE.connections.splice(aQ,1)}};this.getElement=function(){return aD};this.getUuid=function(){return az};this.makeInPlaceCopy=function(){var aQ=ah({anchor:aE.anchor,source:aD,paintStyle:this.paintStyle,endpoint:aC});return aQ};this.isConnectedTo=function(aS){var aR=false;if(aS){for(var aQ=0;aQ=aK)};this.setDragAllowedWhenFull=function(aQ){aA=aQ};this.setPaintStyle=this.setPaintStyle;this.setHoverPaintStyle=this.setHoverPaintStyle;this.setStyle=aE.setPaintStyle;this.equals=function(aQ){return this.anchor.equals(aQ.anchor)};this.paint=function(aT){aT=aT||{};var aX=aT.timestamp;if(!aX||aE.timestamp!==aX){var aW=aT.anchorPoint,aS=aT.canvas,aU=aT.connectorPaintStyle;if(aW==null){var a2=aT.offset||P[ax];var aQ=aT.dimensions||J[ax];if(a2==null||aQ==null){y({elId:ax,timestamp:aX});a2=P[ax];aQ=J[ax]}var aR={xy:[a2.left,a2.top],wh:aQ,element:aE,timestamp:aX};if(aE.anchor.isDynamic){if(aE.connections.length>0){var aZ=aE.connections[0];var a1=aZ.endpoints[0]==aE?1:0;var aV=a1==0?aZ.sourceId:aZ.targetId;var aY=P[aV],a0=J[aV];aR.txy=[aY.left,aY.top];aR.twh=a0;aR.tElement=aZ.endpoints[a1]}}aW=aE.anchor.compute(aR)}aC.paint(aW,aE.anchor.getOrientation(),aS||aE.canvas,aE.paintStyleInUse,aU||aE.paintStyleInUse);aE.timestamp=aX}};this.repaint=this.paint;this.removeConnection=this.detach;if(aP.isSource&&q.CurrentLibrary.isDragSupported(aD)){var aJ=null,aF=null,aI=null,aq=false,at=null;var av=function(){aI=aE.connectorSelector();if(aE.isFull()&&!aA){return false}y({elId:ax});au=aE.makeInPlaceCopy();au.paint();aJ=document.createElement("div");var aU=b(aJ);L(aJ,aE.container);var aW=ap(aU);y({elId:aW});f(b(aE.canvas),"dragId",aW);f(b(aE.canvas),"elId",ax);var aR=new B({reference:aE.anchor,referenceCanvas:aE.canvas});aN=ah({paintStyle:{fillStyle:"rgba(0,0,0,0)"},endpoint:aC,anchor:aR,source:aU});if(aI==null){aE.anchor.locked=true;aI=an({sourceEndpoint:aE,targetEndpoint:aN,source:b(aD),target:b(aJ),anchors:[aE.anchor,aR],paintStyle:aP.connectorStyle,hoverPaintStyle:aP.connectorHoverStyle,backgroundPaintStyle:aP.connectorBackgroundStyle,connector:aP.connector,overlays:aP.connectorOverlays});aI.setHover(false)}else{aq=true;aI.setHover(false);aw(b(au.canvas));var aQ=aI.sourceId==ax?0:1;aI.floatingAnchorIndex=aQ;aE.detachFromConnection(aI);var aV=b(aE.canvas);var aT=q.CurrentLibrary.getDragScope(aV);f(aV,"originalScope",aT);var aS="scope_"+(new Date()).getTime();if(aQ==0){at=[aI.source,aI.sourceId,aM,aT];aI.source=b(aJ);aI.sourceId=aW}else{at=[aI.target,aI.targetId,aM,aT];aI.target=b(aJ);aI.targetId=aW}q.CurrentLibrary.setDragScope(aM,aS);aI.endpoints[aQ==0?1:0].anchor.locked=true;aI.suspendedEndpoint=aI.endpoints[aQ];aI.endpoints[aQ]=aN}S[aW]=aI;aN.addConnection(aI);h(ab,aW,aN);x.currentlyDragging=true};var aL=aP.dragOptions||{};var aG=q.extend({},q.CurrentLibrary.defaultDragOptions);aL=q.extend(aG,aL);aL.scope=aL.scope||aE.scope;var aH=q.CurrentLibrary.dragEvents.start;var aO=q.CurrentLibrary.dragEvents.stop;var ay=q.CurrentLibrary.dragEvents.drag;aL[aH]=ag(aL[aH],av);aL[ay]=ag(aL[ay],function(){var aQ=q.CurrentLibrary.getUIPosition(arguments);q.CurrentLibrary.setOffset(aJ,aQ);am(b(aJ),aQ)});aL[aO]=ag(aL[aO],function(){G(ab,aF,aN);K([aJ,aN.canvas],aD);X(au.canvas,aD);var aQ=aI.floatingAnchorIndex==null?1:aI.floatingAnchorIndex;aI.endpoints[aQ==0?1:0].anchor.locked=false;if(aI.endpoints[aQ]==aN){if(aq&&aI.suspendedEndpoint){if(aQ==0){aI.source=at[0];aI.sourceId=at[1]}else{aI.target=at[0];aI.targetId=at[1]}q.CurrentLibrary.setDragScope(at[2],at[3]);aI.endpoints[aQ]=aI.suspendedEndpoint;if(aB){aI.floatingAnchorIndex=null;aI.suspendedEndpoint.addConnection(aI);q.repaint(at[1])}else{aI.endpoints[aQ==0?1:0].detach(aI)}}else{X(aI.canvas,aE.container);aE.detachFromConnection(aI)}}aE.anchor.locked=false;aE.paint();aI.repaint();aI=null;delete au;delete ab[aN.elementId];delete aN;x.currentlyDragging=false});var aM=b(aE.canvas);q.CurrentLibrary.initDraggable(aM,aL)}var aw=function(aT){if(aP.isTarget&&q.CurrentLibrary.isDropSupported(aD)){var aQ=aP.dropOptions||x.Defaults.DropOptions||q.Defaults.DropOptions;aQ=q.extend({},aQ);aQ.scope=aQ.scope||aE.scope;var aW=null;var aU=q.CurrentLibrary.dragEvents.drop;var aV=q.CurrentLibrary.dragEvents.over;var aR=q.CurrentLibrary.dragEvents.out;var aS=function(){var a5=b(q.CurrentLibrary.getDragObject(arguments));var aX=n(a5,"dragId");var aZ=n(a5,"elId");var a4=n(a5,"originalScope");if(a4){q.CurrentLibrary.setDragScope(a5,a4)}var a1=S[aX];var a2=a1.floatingAnchorIndex==null?1:a1.floatingAnchorIndex,a3=a2==0?1:0;if(!aE.isFull()&&!(a2==0&&!aE.isSource)&&!(a2==1&&!aE.isTarget)){if(a2==0){a1.source=aD;a1.sourceId=ax}else{a1.target=aD;a1.targetId=ax}a1.endpoints[a2].detachFromConnection(a1);if(a1.suspendedEndpoint){a1.suspendedEndpoint.detachFromConnection(a1)}a1.endpoints[a2]=aE;aE.addConnection(a1);if(!a1.suspendedEndpoint){h(E,a1.scope,a1);I(aD,aP.draggable,{})}else{var a0=a1.suspendedEndpoint.getElement(),aY=a1.suspendedEndpoint.elementId;x.fireUpdate("jsPlumbConnectionDetached",{source:a2==0?a0:a1.source,target:a2==1?a0:a1.target,sourceId:a2==0?aY:a1.sourceId,targetId:a2==1?aY:a1.targetId,sourceEndpoint:a2==0?a1.suspendedEndpoint:a1.endpoints[0],targetEndpoint:a2==1?a1.suspendedEndpoint:a1.endpoints[1],connection:a1})}q.repaint(aZ);x.fireUpdate("jsPlumbConnection",{source:a1.source,target:a1.target,sourceId:a1.sourceId,targetId:a1.targetId,sourceEndpoint:a1.endpoints[0],targetEndpoint:a1.endpoints[1],connection:a1})}x.currentlyDragging=false;delete S[aX]};aQ[aU]=ag(aQ[aU],aS);aQ[aV]=ag(aQ[aV],function(){var aY=q.CurrentLibrary.getDragObject(arguments);var a0=n(b(aY),"dragId");var aZ=S[a0];var aX=aZ.floatingAnchorIndex==null?1:aZ.floatingAnchorIndex;aZ.endpoints[aX].anchor.over(aE.anchor)});aQ[aR]=ag(aQ[aR],function(){var aY=q.CurrentLibrary.getDragObject(arguments);var a0=n(b(aY),"dragId");var aZ=S[a0];var aX=aZ.floatingAnchorIndex==null?1:aZ.floatingAnchorIndex;aZ.endpoints[aX].anchor.out()});q.CurrentLibrary.initDroppable(aT,aQ)}};aw(b(aE.canvas));return aE}};var q=window.jsPlumb=new o();q.getInstance=function(s){var r=new o(s);return r}})();(function(){var b=!!!document.createElement("canvas").getContext;var a=function(c,f,e,d){return function(){return jsPlumb.makeAnchor(c,f,e,d)}};jsPlumb.Anchors.TopCenter=a(0.5,0,0,-1);jsPlumb.Anchors.BottomCenter=a(0.5,1,0,1);jsPlumb.Anchors.LeftMiddle=a(0,0.5,-1,0);jsPlumb.Anchors.RightMiddle=a(1,0.5,1,0);jsPlumb.Anchors.Center=a(0.5,0.5,0,0);jsPlumb.Anchors.TopRight=a(1,0,0,-1);jsPlumb.Anchors.BottomRight=a(1,1,0,1);jsPlumb.Anchors.TopLeft=a(0,0,0,-1);jsPlumb.Anchors.BottomLeft=a(0,1,0,1);jsPlumb.Defaults.DynamicAnchors=function(){return jsPlumb.makeAnchors(["TopCenter","RightMiddle","BottomCenter","LeftMiddle"])};jsPlumb.Anchors.AutoDefault=function(){return jsPlumb.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors())};jsPlumb.Connectors.Straight=function(){var q=this;var k=null;var e,l,o,n,m,f,p,h,g,d,c;this.compute=function(u,I,E,r,B,t){var H=Math.abs(u[0]-I[0]);var A=Math.abs(u[1]-I[1]);var C=false,v=false;var z=0.45*H,s=0.45*A;H*=1.9;A*=1.9;var F=Math.min(u[0],I[0])-z;var D=Math.min(u[1],I[1])-s;var G=Math.max(2*B,t);if(H0?1:-1;var v=Math.abs(w*Math.sin(f));if(g>c){v=v*-1}var r=Math.abs(w*Math.cos(f));if(h>d){r=r*-1}return{x:u.x+(t*r),y:u.y+(t*v)}};this.perpendicularToPathAt=function(u,v,A){var w=q.pointAlongPathFrom(u,A);var t=q.gradientAtPoint(w.location);var s=Math.atan(-1/t);var z=v/2*Math.sin(s);var r=v/2*Math.cos(s);return[{x:w.x+r,y:w.y+z},{x:w.x-r,y:w.y-z}]};this.createGradient=function(s,r){return r.createLinearGradient(s[4],s[5],s[6],s[7])}};jsPlumb.Connectors.Bezier=function(g){var r=this;this.majorAnchor=150;if(g){if(g.constructor==Number){this.majorAnchor=g}else{if(g.curviness){this.majorAnchor=g.curviness}}}this.minorAnchor=10;var l=null;this._findControlPoint=function(B,s,w,z,t){var y=z.getOrientation(),A=t.getOrientation();var v=y[0]!=A[0]||y[1]==A[1];var u=[];var C=r.majorAnchor,x=r.minorAnchor;if(!v){if(y[0]==0){u.push(s[0]o){o=x}if(A<0){f+=A;var B=Math.abs(A);o+=B;q[0]+=B;m+=B;d+=B;p[0]+=B}var J=Math.min(h,c);var H=Math.min(q[1],p[1]);var w=Math.min(J,H);var C=Math.max(h,c);var z=Math.max(q[1],p[1]);var u=Math.max(C,z);if(u>k){k=u}if(w<0){e+=w;var y=Math.abs(w);k+=y;q[1]+=y;h+=y;c+=y;p[1]+=y}if(G&&o1?q:{x:s[0].x+((s[1].x-s[0].x)/2),y:s[0].y+((s[1].y-s[0].y)/2)};var A=p.x-l.x,x=p.y-l.y;q.x+=A;q.y+=x;k.x+=A;k.y+=x;s[0].x+=A;s[0].y+=x;s[1].x+=A;s[1].y+=x;r.x+=A;r.y+=x}var w=Math.min(r.x,s[0].x,s[1].x);var u=Math.max(r.x,s[0].x,s[1].x);var v=Math.min(r.y,s[0].y,s[1].y);var t=Math.max(r.y,s[0].y,s[1].y);z.lineWidth=e.lineWidth;z.beginPath();z.moveTo(r.x,r.y);z.lineTo(s[0].x,s[0].y);z.lineTo(q.x,q.y);z.lineTo(s[1].x,s[1].y);z.lineTo(r.x,r.y);z.closePath();if(e.strokeStyle){z.strokeStyle=e.strokeStyle;z.stroke()}z.fillStyle=e.fillStyle||y.strokeStyle;z.fill();return[w,u,v,t]}};jsPlumb.Overlays.PlainArrow=function(d){d=d||{};var c=jsPlumb.extend(d,{foldback:1});jsPlumb.Overlays.Arrow.call(this,c)};jsPlumb.Overlays.Diamond=function(e){e=e||{};var c=e.length||40;var d=jsPlumb.extend(e,{length:c/2,foldback:2});jsPlumb.Overlays.Arrow.call(this,d)};jsPlumb.Overlays.Label=function(l){this.labelStyle=l.labelStyle||jsPlumb.Defaults.LabelStyle;this.label=l.label;this.connection=l.connection;var d=this;var c=null,k=null,f=null,g=null;this.location=l.location||0.5;this.cachedDimensions=null;var h=function(n){if(d.cachedDimensions){return d.cachedDimensions}f=typeof d.label=="function"?d.label(d):d.label;var r={};if(f){var m=f.split(/\n|\r\n/);n.save();if(d.labelStyle.font){n.font=d.labelStyle.font}var o=e(m,n);var p=n.measureText("M").width;g=d.labelStyle.padding||0.25;c=o+(2*o*g);k=(m.length*p)+(2*p*g);var q=m.length*p;n.restore();r={width:c,height:k,lines:m,oneLine:p,padding:g,textHeight:q}}if(typeof d.label!="function"){d.cachedDimensions=r}return r};this.computeMaxSize=function(n,m){var o=h(m);return o.width?Math.max(o.width,o.height)*1.5:0};var e=function(o,n){var m=0;for(var q=0;qm){m=p}}return m};this.draw=function(p,o,q){var s=h(o);if(s.width){var r=p.pointOnPath(d.location);if(d.labelStyle.font){o.font=d.labelStyle.font}if(d.labelStyle.fillStyle){o.fillStyle=d.labelStyle.fillStyle}else{o.fillStyle="rgba(0,0,0,0)"}var n=r.x-(s.width/2);var m=r.y-(s.height/2);o.fillRect(n,m,s.width,s.height);if(d.labelStyle.color){o.fillStyle=d.labelStyle.color}o.textBaseline="middle";o.textAlign="center";for(i=0;i0){o.strokeStyle=d.labelStyle.borderStyle||"black";o.strokeRect(n,m,s.width,s.height)}return[n,n+s.width,m,m+s.height]}else{return[0,0,0,0]}}};jsPlumb.Overlays.Image=function(e){var l=this;this.location=e.location||0.5;this.img=new Image();this.connection=e.connection;var m=null;var f=null;var d,c;var k=e.events||{};var h=function(){if(l.ready){window.clearInterval(f);m=document.createElement("img");m.src=l.img.src;m.style.position="absolute";m.style.display="none";m.className="_jsPlumb_overlay";document.body.appendChild(m);for(var n in k){jsPlumb.CurrentLibrary.bind(m,n,k[n])}if(d&&c){g(d,c);c=null;d=null}}};this.img.onload=function(){l.ready=true};this.img.src=e.src||e.url;f=window.setInterval(h,250);this.computeMaxSize=function(o,n){return[l.img.width,l.img.height]};var g=function(q,v,u){if(m!=null){var r=q.pointOnPath(l.location);var p=jsPlumb.CurrentLibrary.getElementObject(v.canvas);var w=jsPlumb.CurrentLibrary.getOffset(p);var t=r.x-(l.img.width/2);var s=r.y-(l.img.height/2);var n={left:w.left+t,top:w.top+s};jsPlumb.CurrentLibrary.setOffset(m,n);m.style.display="block";return[t,t+l.img.width,s,s+l.img.height]}};this.draw=function(o,n){if(l.ready){return g(o,n)}else{d=o;c=n;return[0,0,0,0]}}}})();(function(){jsPlumb.Connectors.Flowchart=function(f){f=f||{};var o=this,b=f.minStubLength||30,k=[],h=[],m=[],g=[],a=[],n=[],d,c,q=function(u,t,B,A){var y=0;for(var s=0;s=t){r=u;s=(t-m[u][0])/a[u];break}}return{segment:k[r],proportion:s,index:r}};this.compute=function(u,J,F,r,C,s){k=[];h=[];a=[];g=[];segmentProportionals=[];d=J[0]u[0]?u[0]+((1-y)*t)-z:u[2]+(y*t)+z,y:r==0?u[3]:u[3]>u[1]?u[1]+((1-y)*t)-z:u[3]+(y*t)+z,segmentInfo:w};return x};this.perpendicularToPathAt=function(u,v,A){var w=o.pointAlongPathFrom(u,A);var t=h[w.segmentInfo.index];var s=Math.atan(-1/t);var z=v/2*Math.sin(s);var r=v/2*Math.cos(s);return[{x:w.x+r,y:w.y+z},{x:w.x-r,y:w.y-z}]}}})();(function(){var d=new Class({Extends:Fx.Morph,onStep:null,initialize:function(m,l){this.parent(m,l);if(l.onStep){this.onStep=l.onStep}},step:function(l){this.parent(l);if(this.onStep){try{this.onStep()}catch(m){}}}});var b={};var h={};var g={};var k={};var e=function(m,o,n){if(o){var p=o.get("id");if(p){var l=h[p];if(l){if(l[n]){l[n](m,o)}}}}};var f=function(m,o){if(m){var n=m.get("id");if(n){var l=h[n];if(l){if(l.hoverClass){if(o){m.addClass(l.hoverClass)}else{m.removeClass(l.hoverClass)}}}}}};var c=function(p,n,o){var m=p[n];if(!m){m=[];p[n]=m}m.push(o)};var a=function(l){return $(l)};jsPlumb.CurrentLibrary={addClass:function(m,l){m.addClass(l)},animate:function(p,o,n){var l=new d(p,n);l.start(o)},appendElement:function(m,l){a(l).grab(m)},bind:function(l,m,n){l=a(l);l.addEvent(m,n)},dragEvents:{start:"onStart",stop:"onComplete",drag:"onDrag",step:"onStep",over:"onEnter",out:"onLeave",drop:"onDrop",complete:"onComplete"},extend:function(m,l){return $extend(m,l)},getAttribute:function(l,m){return l.get(m)},getDragObject:function(l){return l[0]},getDragScope:function(m){var n=jsPlumb.getId(m);var l=k[n];return l[0].scope},getElementObject:a,getOffset:function(l){var m=l.getPosition();return{left:m.x,top:m.y}},getPageXY:function(l){return[l.event.pageX,l.event.pageY]},getScrollLeft:function(l){return null},getScrollTop:function(l){return null},getSize:function(m){var l=m.getSize();return[l.x,l.y]},getUIPosition:function(l){var m=l[0];return{left:m.offsetLeft,top:m.offsetTop}},hasClass:function(m,l){return m.hasClass(l)},initDraggable:function(m,u){var l=jsPlumb.getId(m);var p=k[l];if(!p){var n=0,o=null;var r=jsPlumb.Defaults.DragOptions.zIndex||2000;u.onStart=jsPlumb.wrap(u.onStart,function(){n=this.element.getStyle("z-index");this.element.setStyle("z-index",r);if(jsPlumb.Defaults.DragOptions.cursor){o=this.element.getStyle("cursor");this.element.setStyle("cursor",jsPlumb.Defaults.DragOptions.cursor)}});u.onComplete=jsPlumb.wrap(u.onComplete,function(){this.element.setStyle("z-index",n);if(o){this.element.setStyle("cursor",o)}});var t=u.scope||jsPlumb.Defaults.Scope;var q=function(v){return v.get("id")!=m.get("id")};var s=b[t]?b[t].filter(q):[];u.droppables=s;u.onLeave=jsPlumb.wrap(u.onLeave,function(v,w){if(w){f(w,false);e(v,w,"onLeave")}});u.onEnter=jsPlumb.wrap(u.onEnter,function(v,w){if(w){f(w,true);e(v,w,"onEnter")}});u.onDrop=function(v,w){if(w){f(w,false);e(v,w,"onDrop")}};p=new Drag.Move(m,u);p.scope=t;c(g,t,p);c(k,m.get("id"),p);if(u.disabled){p.detach()}}return p},initDroppable:function(p,m){var o=m.scope||jsPlumb.Defaults.Scope;c(b,o,p);var r=jsPlumb.getId(p);h[r]=m;var q=function(s){return s.element!=p};var l=g[o]?g[o].filter(q):[];for(var n=0;n0?1:-1}}var b={subtract:function(n,m){return{x:n.x-m.x,y:n.y-m.y}},dotProduct:function(n,m){return n.x*m.x+n.y*m.y},square:function(m){return Math.sqrt(m.x*m.x+m.y*m.y)},scale:function(n,m){return{x:n.x*m,y:n.y*m}}},d=Math.pow(2,-65),h=function(y,x){for(var s=[],v=x.length-1,r=2*v-1,t=[],w=[],o=[],p=[],q=[[1,0.6,0.3,0.1],[0.4,0.6,0.6,0.4],[0.1,0.3,0.6,1]],u=0;u<=v;u++){t[u]=b.subtract(x[u],y)}for(u=0;u<=v-1;u++){w[u]=b.subtract(x[u+1],x[u]);w[u]=b.scale(w[u],3)}for(u=0;u<=v-1;u++){for(var n=0;n<=v;n++){o[u]||(o[u]=[]);o[u][n]=b.dotProduct(w[u],t[n])}}for(u=0;u<=r;u++){p[u]||(p[u]=[]);p[u].y=0;p[u].x=parseFloat(u)/r}r=v-1;for(t=0;t<=v+r;t++){w=Math.min(t,v);for(u=Math.max(0,t-r);u<=w;u++){j=t-u;p[u+j].y+=o[j][u]*q[j][u]}}v=x.length-1;p=l(p,2*v-1,s,0);r=b.subtract(y,x[0]);o=b.square(r);for(u=q=0;u=64){y[0]=(E[0].x+E[D].x)/2;return 1}var s,r,p;v=E[0].y-E[D].y;w=E[D].x-E[0].x;A=E[0].x*E[D].y-E[D].x*E[0].y;t=max_distance_below=0;for(r=1;rt){t=p}else{if(p0?1:-1,s=null;o 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + if (!ie) { + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + else { + // need to get fancy with the vml. + } + } + return false; + }; + + /* + * Binds a listener to an event. + * + * event - name of the event to bind to. + * listener - function to execute. + */ + this.bind = function(event, listener) { + _addToList(_listeners, event, listener); + }; + /* + * Fires an update for the given event. + * + * event - event to fire + * value - value to pass to the event listener(s). + * originalEvent - the original event from the browser + */ + this.fireUpdate = function(event, value, originalEvent) { + if (_listeners[event]) { + for ( var i = 0; i < _listeners[event].length; i++) { + try { + _listeners[event][i](value, originalEvent); + } catch (e) { + _log(self, "jsPlumb: fireUpdate failed for event " + + event + " : " + e + "; not fatal."); + } + } + } + }; + /* + * Clears either all listeners, or listeners for some specific event. + * + * event - optional. constrains the clear to just listeners for this event. + */ + this.clearListeners = function(event) { + if (event) { + delete _listeners[event]; + } else { + delete _listeners; + _listeners = {}; + } + }; + + var _mouseover = false; + var _mouseDown = false, _mouseDownAt = null, _posWhenMouseDown = null, _mouseWasDown = false, srcWhenMouseDown = null, + targetWhenMouseDown = null; + this.mousemove = function(e) { + var jpcl = jsPlumb.CurrentLibrary; + var pageXY = jpcl.getPageXY(e); + var ee = document.elementFromPoint(pageXY[0], pageXY[1]); + var _continue = _connectionBeingDragged == null && (_hasClass(ee, "_jsPlumb_endpoint") || _hasClass(ee, "_jsPlumb_connector")); + + if (_mouseDown && srcWhenMouseDown) { + _mouseWasDown = true; + _connectionBeingDragged = self; + var mouseNow = jpcl.getPageXY(e); + var dx = mouseNow[0] - _mouseDownAt[0]; + var dy = mouseNow[1] - _mouseDownAt[1]; + var newPos = {left:srcWhenMouseDown.left + dx, top:srcWhenMouseDown.top + dy}; + jpcl.setOffset(jpcl.getElementObject(self.source), newPos); + jsPlumb.repaint(self.source); + newPos = {left:targetWhenMouseDown.left + dx, top:targetWhenMouseDown.top + dy}; + jpcl.setOffset(jpcl.getElementObject(self.target), newPos); + jsPlumb.repaint(self.target); + } + else if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.setHover(_mouseover); + self.fireUpdate("mouseenter", self, e); + } + else if (_mouseover && (!self._over(e) || !_continue)) { + _mouseover = false; + /* if (self.hoverPaintStyle != null) { + self.paintStyleInUse = self.paintStyle; + self.repaint(); + _updateAttachedElements(); + }*/ + self.setHover(_mouseover); + self.fireUpdate("mouseexit", self, e); + } + }; + + /** + * sets/unsets the hover state of this element. + */ + this.setHover = function(hover, ignoreAttachedElements) { + if (self.hoverPaintStyle != null) { + self.paintStyleInUse = hover ? self.hoverPaintStyle : self.paintStyle; + self.repaint(); + // get the list of other affected elements. for a connection, its the endpoints. for an endpoint, its the connections! surprise. + if (!ignoreAttachedElements) + _updateAttachedElements(hover); + } + }; + + var _updateAttachedElements = function(state) { + var affectedElements = self.getAttachedElements(); // implemented in subclasses + if (affectedElements) { + for (var i = 0; i < affectedElements.length; i++) { + affectedElements[i].setHover(state, true); // tell the attached elements not to inform their own attached elements. + } + } + }; + + this.click = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fireUpdate("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fireUpdate("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _mouseDownAt = jsPlumb.CurrentLibrary.getPageXY(e); + if (self.canvas) _posWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.canvas)); + if (self.source) srcWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.source)); + if (self.target) targetWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.target)); + } + }; + + this.mouseup = function() { + if (self == _connectionBeingDragged) _connectionBeingDragged = null; + _mouseDown = false; + }; + + /* + * Sets the paint style and then repaints the element. + * + * style - Style to use. + */ + this.setPaintStyle = function(style) { + self.paintStyle = style; + self.paintStyleInUse = self.paintStyle; + self.repaint(); + }; + + /* + * Sets the paint style to use when the mouse is hovering over the element. This is null by default. + * + * style - Style to use when the mouse is hovering. + */ + this.setHoverPaintStyle = function(style) { + self.hoverPaintStyle = style; + self.repaint(); + }; + }; + + /** + * Class:jsPlumb + * The main jsPlumb class. One of these is registered on the Window as 'jsPlumb'; you can also get one yourself by making a call to jsPlumb.getInstance. This class is the class you use to establish Connections and add Endpoints. + */ + var jsPlumbInstance = function(_defaults) { + + /* + * Property: Defaults + * + * These are the default settings for jsPlumb. They are what will be used if you do not supply specific pieces of information to the various API calls. A convenient way to implement your own look and feel can be to override these defaults by including a script somewhere after the jsPlumb include, but before you make any calls to jsPlumb, for instance in this example we set the PaintStyle to be a blue line of 27 pixels: > jsPlumb.Defaults.PaintStyle = { lineWidth:27, strokeStyle:'blue' } + */ + this.Defaults = { + Anchor : null, + Anchors : [ null, null ], + BackgroundPaintStyle : null, + Connector : null, + Container : null, + DragOptions : { }, + DropOptions : { }, + Endpoint : null, + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : null }, + EndpointStyles : [ null, null ], + EndpointHoverStyle : null, + EndpointHoverStyles : [ null, null ], + HoverPaintStyle : null, + LabelStyle : { fillStyle : "rgba(0,0,0,0)", color : "black" }, + LogEnabled : true, + MaxConnections : null, + MouseEventsEnabled : false, + // TODO: should we have OverlayStyle too? + PaintStyle : { lineWidth : 10, strokeStyle : 'red' }, + Scope : "_jsPlumb_DefaultScope" + }; + if (_defaults) jsPlumb.extend(this.Defaults, _defaults); + + this.logEnabled = this.Defaults.LogEnabled; + + + EventGenerator.apply(this); + var _bb = this.bind; + this.bind = function(event, fn) { + if ("ready" === event && initialized) fn(); + else _bb(event, fn); + }; + + var _currentInstance = this; + var log = null; + + var repaintFunction = function() { + jsPlumb.repaintEverything(); + }; + var automaticRepaint = true; + function repaintEverything() { + if (automaticRepaint) + repaintFunction(); + }; + var resizeTimer = null; + + var initialized = false; + var connectionsByScope = {}; + /** + * map of element id -> endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + var endpointsByElement = {}; + var endpointsByUUID = {}; + var offsets = {}; + var offsetTimestamps = {}; + var floatingConnections = {}; + var draggableStates = {}; + var _mouseEventsEnabled = this.Defaults.MouseEventsEnabled; + var _draggableByDefault = true; + var canvasList = []; + var sizes = []; + var listeners = {}; // a map: keys are event types, values are lists of listeners. + var DEFAULT_SCOPE = 'DEFAULT'; + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + var _findIndex = function(a, v, b, s) { + var _eq = function(o1, o2) { + if (o1 === o2) + return true; + else if (typeof o1 == 'object' && typeof o2 == 'object') { + var same = true; + for ( var propertyName in o1) { + if (!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for ( var propertyName in o2) { + if (!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for ( var i = +b || 0, l = a.length; i < l; i++) { + if (_eq(a[i], v)) + return i; + } + return -1; + }; + + /** + * appends an element to the given parent, or the document body if no + * parent given. + */ + var _appendElement = function(canvas, parent) { + if (!parent) + document.body.appendChild(canvas); + else + jsPlumb.CurrentLibrary.appendElement(canvas, parent); + }; + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + var _timestamp = function() { return "" + (new Date()).getTime(); }; + + /** + * Draws an endpoint and its connections. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + var _draw = function(element, ui, timestamp) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (!timestamp) timestamp = _timestamp(); + if (endpoints) { + _updateOffset( { elId : id, offset : ui, recalc : false, timestamp : timestamp }); // timestamp is checked against last update cache; it is + // valid for one paint cycle. + var myOffset = offsets[id], myWH = sizes[id]; + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + var l = endpoints[i].connections; + for ( var j = 0; j < l.length; j++) { + l[j].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); // ...paint each connection. + // then, check for dynamic endpoint; need to repaint it. + var oIdx = l[j].endpoints[0] == endpoints[i] ? 1 : 0; + if (l[j].endpoints[oIdx].anchor.isDynamic && !l[j].endpoints[oIdx].isFloating()) { + var oId = oIdx == 0 ? l[j].sourceId : l[j].targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + // TODO i still want to make this faster. + var anchorLoc = l[j].endpoints[oIdx].anchor.compute( { + xy : [ oOffset.left, oOffset.top ], + wh : oWH, + element : l[j].endpoints[oIdx], + txy : [ myOffset.left, myOffset.top ], + twh : myWH, + tElement : endpoints[i] + }); + l[j].endpoints[oIdx].paint({ anchorLoc : anchorLoc }); + } + } + } + } + }; + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + var _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }; + + /** + * gets an Endpoint by uuid. + */ + var _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }; + + /** + * inits a draggable if it's not already initialised. + */ + var _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element) && !jsPlumb.CurrentLibrary.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + } + } + }; + + /** + * helper to create a canvas. + * + */ + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + _appendElement(canvas, params.container); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + _getId(canvas, params.uuid); + if (ie) { + // for IE we have to set a big canvas size. actually you can + // override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas + // you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + + var _newConnection = function(params) { + var connectionFunc = jsPlumb.Defaults.ConnectionType || Connection; + return new connectionFunc(params); + }; + + var _newEndpoint = function(params) { + var endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint; + return new endpointFunc(params); + }; + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + var _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }; + /** + * perform an operation on all elements. + */ + var _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }; + + /** + * helper to remove an element from the DOM. + */ + var _removeElement = function(element, parent) { + if (element != null) { + if (!parent) { + try { + document.body.removeChild(element); + } catch (e) { + } + } else { + jsPlumb.CurrentLibrary.removeElement(element, parent); + } + } + }; + /** + * helper to remove a list of elements from the DOM. + */ + var _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }; + /** + * helper method to remove an item from a list. + */ + var _removeFromList = function(map, key, value) { + if (key != null) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete (l[i]); + l.splice(i, 1); + return true; + } + } + } + return false; + }; + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }; + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + var _setVisible = function(el, state) { + _operation(_getAttribute(el, "id"), function(jpc) { + jpc.canvas.style.display = state; + }); + }; + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + var _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }; + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + var _toggleVisible = function(elId) { + _operation(elId, function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }; + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + var _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return; + } + if (recalc || offset == null) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + } + }; + +/** + * gets an id for the given element, creating and setting one if + * necessary. + */ + var _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else + id = "jsPlumb_" + _timestamp(); + _setAttribute(ele, "id", id); + } + return id; + }; + +/** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + var _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'jsPlumb function failed : ' + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'wrapped function failed : ' + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = '_jsPlumb_connector'; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = '_jsPlumb_endpoint'; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = '_jsPlumb_overlay'; + + /* + * Property: Anchors + * Default jsPlumb Anchors. These are supplied in the file jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script to form .jsPlumb-all-x.x.x.js. You can + * provide your own Anchors by supplying them in a script that is loaded after jsPlumb, for instance: > jsPlumb.Anchors.MyAnchor = { ....anchor code here. see the documentation. } + */ + this.Anchors = {}; + + /* + * Property: Connectors + * Default jsPlumb Connectors. These are supplied + * in the file jsPlumb-defaults-x.x.x.js, which is merged in with the + * main jsPlumb script to form .jsPlumb-all-x.x.x.js. You can + * provide your own Connectors by supplying them in a script that is + * loaded after jsPlumb, for instance: > jsPlumb.Connectors.MyConnector = { + * ....connector code here. see the documentation. } + */ + this.Connectors = {}; + + /* + * Property: Endpoints + * Default jsPlumb Endpoints. These are supplied in + * the file jsPlumb-defaults-x.x.x.js, which is merged in with the main + * jsPlumb script to form .jsPlumb-all-x.x.x.js. You can + * provide your own Endpoints by supplying them in a script that is + * loaded after jsPlumb, for instance: > jsPlumb.Endpoints.MyEndpoint = { + * ....endpoint code here. see the documentation. } + */ + this.Endpoints = {}; + + /* + * Property:Overlays + * Default jsPlumb Overlays such as Arrows and Labels. + * These are supplied in the file jsPlumb-defaults-x.x.x.js, which is + * merged in with the main jsPlumb script to form + * .jsPlumb-all-x.x.x.js. You can provide your own Overlays by + * supplying them in a script that is loaded after jsPlumb, for + * instance: > jsPlumb.Overlays.MyOverlay = { ....overlay code here. see + * the documentation. } + */ + this.Overlays = {}; + + /* + Function: addEndpoint + + Adds an Endpoint to a given element. + + Parameters: + + target - Element to add the endpoint to. either an element id, or a selector representing some element. + params - Object containing Endpoint options. For more information, see the docs for Endpoint's constructor. + referenceParams - Object containing more Endpoint options; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + The newly created Endpoint. + + See Also: + + */ + this.addEndpoint = function(target, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.endpointStyle = p.endpointStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + var el = _getElementObject(target), id = _getAttribute(el, "id"); + p.source = el; + _updateOffset({ elId : id }); + var e = _newEndpoint(p); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + return e; + }; + + /* + Function: addEndpoints + Adds a list of Endpoints to a given element. + + Parameters: + target - element to add the endpoint to. either an element id, or a selector representing some element. + endpoints - List of objects containing Endpoint options. one Endpoint is created for each entry in this list. + referenceParams - Object containing more Endpoint options; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created Endpoints, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(target, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + results.push(_currentInstance.addEndpoint(target, endpoints[i], referenceParams)); + } + return results; + }; + + /* + Function: animate + Wrapper around supporting library's animate function; injects a call to jsPlumb in the 'step' function (creating + it if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // probably, onComplete should repaint too. that will help + // keep up + // with fast animations. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /* + Function: connect + Establishes a connection between two elements (or Endpoints, which are themselves registered to elements). + + Parameters: + params - Object containing setup for the connection. See docs for Connection's constructor. + referenceParams - Optional object containing more params for the connection. Typically you would pass in data that a lot of connections are sharing here, such as connector style etc, and then use the main params for data specific to this connection. + + Returns: + The newly created Connection. + */ + this.connect = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not + // full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + // dynamic anchors. backwards compatibility here: from 1.2.6 onwards you don't need to specify "dynamicAnchors". the fact that some anchor consists + // of multiple definitions is enough to tell jsPlumb you want it to be dynamic. + if (_p.dynamicAnchors) { + // these can either be an array of anchor coords, which we will use for both source and target, or an object with {source:[anchors], target:[anchors]}, in which + // case we will use a different set for each element. + var a = _p.dynamicAnchors.constructor == Array; + var sa = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.source)); + var ta = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.target)); + _p.anchors = [sa,ta]; + } + + var jpc = _newConnection(_p); + // add to list of connections (by scope). + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + _currentInstance.fireUpdate("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + // force a paint + _draw(jpc.source); + + return jpc; + }; + + /* + Function: deleteEndpoint + Deletes an endpoint and removes all connections it has (which removes the connections from the other endpoints involved too) + + Parameters: + object - either an Endpoint object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElement(endpoint.canvas, endpoint.container); + // remove from endpointsbyElement + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + delete endpoint; + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every Endpoint, and their associated Connections, in this instance of jsPlumb. Do not unregister event listener (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc) { + _currentInstance.fireUpdate("jsPlumbConnectionDetached", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + }; + + /* + Function: detach + Removes a connection. takes either (source, target) (the old way, maintained for backwards compatibility), or a params + object with various possible values. + + Parameters: + source - id or element object of the first element in the connection. + target - id or element object of the second element in the connection. + params - a JS object containing the same parameters as you pass to jsPlumb.connect. If this is present then neither source nor + target should be present; it should be the only argument to the method. See the docs for Connection's constructor for information +about the parameters allowed in the params object. + Returns: + true if successful, false if not. + */ + this.detach = function(source, target) { + if (arguments.length == 2) { + var s = _getElementObject(source), sId = _getId(s); + var t = _getElementObject(target), tId = _getId(t); + _operation(sId, function(jpc) { + if ((jpc.sourceId == sId && jpc.targetId == tId) || (jpc.targetId == sId && jpc.sourceId == tId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + // this is the new version of the method, taking a JS object like + // the connect method does. + else if (arguments.length == 1) { + // TODO investigate whether or not this code still works when a user has supplied their own subclass of Connection. i suspect it may not. + if (arguments[0].constructor == Connection) { + //arguments[0].setHover(false); + arguments[0].endpoints[0].detachFrom(arguments[0].endpoints[1]); + } + else if (arguments[0].connection) { + //arguments[0].connection.setHover(false); + arguments[0].connection.endpoints[0].detachFrom(arguments[0].connection.endpoints[1]); + } + else { + var _p = jsPlumb.extend( {}, source); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1])); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source); + var targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + } + } + }; + + /* + Function: detachAll + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + + Returns: + void + */ + this.detachAllConnections = function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + }; + + /** + * @deprecated Use detachAllConnections instead. this will be removed in jsPlumb 1.3. + */ + this.detachAll = this.detachAllConnections; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + /** + * @deprecated use detachEveryConnection instead. this will be removed in jsPlumb 1.3. + */ + this.detachEverything = this.detachEveryConnection; + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of you library's draggable method so that jsPlumb can setup the appropriate callbacks. Your underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. + * Parameters: + * options - a JS object that holds options defining what sort of connections you're + * looking for. Valid values are: scope this may be a String or a list + * of Strings. jsPlumb will only return Connections whose scope matches + * what this option defines. If you omit it, you will be given + * Connections of every scope. source may be a string or a list of + * strings; constraints results to have only Connections whose source is + * this object. target may be a string or a list of strings; constraints + * results to have only Connections whose target is this object. + * + * The return value is a dictionary in this format: + * { 'scope1': [ {sourceId:'window1', targetId:'window2', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:}, + * {sourceId:'window3', targetId:'window4', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:}, + * {sourceId:'window1', targetId:'window3', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:} ], + * 'scope2': [ {sourceId:'window1', targetId:'window3', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:} ] } + * + */ + this.getConnections = function(options) { + var r = {}; + options = options || {}; + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') + r.push(input); + else + r = input; + } + return r; + }; + var scopes = prepareList(options.scope); + var sources = prepareList(options.source); + var targets = prepareList(options.target); + var filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + r[i] = []; + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + r[i].push({ sourceId : c.sourceId, targetId : c.targetId, source : c.source, target : c.target, sourceEndpoint : c.endpoints[0], targetEndpoint : c.endpoints[1], connection : c }); + } + } + } + return r; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + + Returns: + void + */ + this.hide = function(el) { + _setVisible(el, "none"); + }; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + var _bind = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && _mouseEventsEnabled) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + if (c[i][event](e)) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i][event](e)) return; + } + } + } + }); + }; + _bind("click"); + _bind("dblclick"); + _bind("mousemove"); + _bind("mousedown"); + _bind("mouseup"); + + initialized = true; + _currentInstance.fireUpdate("ready"); + }; + + /* + * Creates an anchor with the given params. + * + * You do not need to use this method. It is exposed because of the way jsPlumb is + * split into three scripts; this will change in the future. + * + * x - the x location of the anchor as a fraction of the + * total width. y - the y location of the anchor as a fraction of the + * total height. xOrientation - value indicating the general direction a + * connection from the anchor should go in, in the x direction. + * yOrientation - value indicating the general direction a connection + * from the anchor should go in, in the y direction. xOffset - a fixed + * offset that should be applied in the x direction that should be + * applied after the x position has been figured out. optional. defaults + * to 0. yOffset - a fixed offset that should be applied in the y + * direction that should be applied after the y position has been + * figured out. optional. defaults to 0. + * -- OR -- + * + * params - {x:..., y:..., xOrientation etc } + * -- OR FROM 1.2.4 --- + * + * name - the name of some Anchor in the _currentInstance.Anchors array. + * -- OR FROM 1.2.4 --- + * + * coords - a list of coords for the anchor, like you would pass to + * jsPlumb.makeAnchor (eg [0.5,0.5,0,-1] - an anchor in the center of + * some element, oriented towards the top of the screen) + * -- OR FROM 1.2.4 --- + * + * anchor - an existing anchor. just gets passed back. it's handy + * internally to have this functionality. + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed + // in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards + // compatibility if we are given only one value we assume it's a + // call in the old form. + if (arguments.length == 0) return null; + var params = {}; + if (arguments.length == 1) { + var specimen = arguments[0]; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; + // is it the name of an anchor type? + else if (typeof specimen == "string") return _currentInstance.Anchors[arguments[0]](); + // is it an array of coordinates? + else if (specimen.constructor == Array) { + if (specimen[0].constructor == Array || specimen[0].constructor == String) + return new DynamicAnchor(specimen); + else + return jsPlumb.makeAnchor.apply(this, specimen); + } + // last we try the backwards compatibility stuff. + else if (typeof arguments[0] == "object") jsPlumb.extend(params, x); + } else { + params = { x : x, y : y }; + if (arguments.length >= 4) params.orientation = [ arguments[2], arguments[3] ]; + if (arguments.length == 6) params.offsets = [ arguments[4], arguments[5] ]; + } + var a = new Anchor(params); + a.clone = function() { + return new Anchor(params); + }; + return a; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types) { + var r = []; + for ( var i = 0; i < types.length; i++) + if (typeof types[i] == "string") + r.push(_currentInstance.Anchors[types[i]]()); + else if (types[i].constructor == Array) + r.push(jsPlumb.makeAnchor(types[i])); + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + for ( var i in ebe) + _currentInstance.deleteEndpoint(ebe[i]); + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + this.deleteEveryEndpoint(); + this.clearListeners(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + + Parameters: + value - whether or not to automatically repaint when the window is resized. + + Returns: void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + * Function: setDefaultNewCanvasSize + * Sets the default size jsPlumb will use for a new canvas (we create a square canvas so one value is all + * that is required). This is a hack for IE, because ExplorerCanvas + * seems to need for a canvas to be larger than what you are going to + * draw on it at initialisation time. The default value of this is 1200 + * pixels, which is quite large, but if for some reason you're drawing + * connectors that are bigger, you should adjust this value + * appropriately. + * + * Parameters: + * size - The default size to use. jsPlumb will use a square canvas so you need only supply one value. + * + * Returns: + * void + */ + this.setDefaultNewCanvasSize = function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }; + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setDraggableByDefault + * Sets whether or not elements are draggable by default. Default for this is true. + * + * Parameters: + * draggable - value to set + * + * Returns: + * void + */ + this.setDraggableByDefault = function(draggable) { + _draggableByDefault = draggable; + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction + * Sets the function to fire when the window size has changed and a repaint was fired. + * + * Parameters: + * f - Function to execute. + * + * Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: setMouseEventsEnabled + * Sets whether or not mouse events are enabled. By default they are not; this is just because jsPlumb has to add mouse listeners + * to the document, which may result in a performance hit a user does not need. + * + * Parameters: + * enabled - whether or not mouse events should be enabled. + * + * Returns: + * void + */ + this.setMouseEventsEnabled = function(enabled) { + _mouseEventsEnabled = enabled; + }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * + * Returns: + * void + */ + this.show = function(el) { + _setVisible(el, "block"); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload + * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element. + * + * Returns: + * void + */ + this.unload = function() { + delete endpointsByElement; + delete endpointsByUUID; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + delete canvasList; + }; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + if (timestamp && timestamp === self.timestamp) { + return lastReturnValue; + } + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + var container = element ? element.container : null; + var containerAdjustment = { left : 0, top : 0 }; + if (container != null) { + var eo = _getElementObject(container); + var o = _getOffset(eo); + var sl = jsPlumb.CurrentLibrary.getScrollLeft(eo); + var st = jsPlumb.CurrentLibrary.getScrollTop(eo); + containerAdjustment.left = o.left - sl; + containerAdjustment.top = o.top - st; + lastReturnValue[0] = lastReturnValue[0] - containerAdjustment.left; + lastReturnValue[1] = lastReturnValue[1] - containerAdjustment.top; + } + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(_getElementObject(refCanvas)); + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + var orientation = null; + var _lastResult = null; + + this.compute = function(params) { + var xy = params.xy, el = params.element; + var result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + if (el.container != null) { + var o = _getOffset(el.container); + result[0] = result[0] - o.left; + result[1] = result[1] - o.top; + } + _lastResult = result; + return result; + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { orientation = anchor.getOrientation(); }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchors is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = anchors || []; + var _convert = function(anchor) { return anchor.constructor == Anchor ? anchor: jsPlumb.makeAnchor(anchor); }; + for (var i = 0; i < _anchors.length; i++) _anchors[i] = _convert(_anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null; + var _curIndex = _anchors.length > 0 ? 0 : -1; + this.locked = false; + var self = this; + + // helper method to calculate the distance between the centers of the two elements. + var _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }; + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + var _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + + var pos = _curAnchor.compute(params); + return pos; + }; + + this.getCurrentLocation = function() { + var cl = _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + return cl; + }; + + this.getOrientation = function() { return _curAnchor != null ? _curAnchor.getOrientation() : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * container - id of the containing div for this connection. optional; jsPlumb uses the default (which you can set, but which is the body by default) otherwise. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * backgroundPaintStyle - Parameters defining the appearance of the background of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * overlays - Optional array of Overlay definitions to appear on this Connection. + */ + var Connection = function(params) { + + EventGenerator.apply(this); + // ************** get the source and target and register the connection. ******************* + var self = this; + var visible = true; + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + }; + var id = new String('_jsplumb_c_' + (new Date()).getTime()); + this.getId = function() { return id; }; + this.container = params.container || _currentInstance.Defaults.Container; // may be null; we will append to the body if so. + // get source and target as jQuery objects + /** + Property: source + The source element for this Connection. + */ + this.source = _getElementObject(params.source); + /** + Property:target + The target element for this Connection. + */ + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. + if (params.sourceEndpoint) this.source = params.sourceEndpoint.getElement(); + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + /* + * Property: sourceId + * Id of the source element in the connection. + */ + this.sourceId = _getAttribute(this.source, "id"); + /* + * Property: targetId + * Id of the target element in the connection. + */ + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + this.getAttachedElements = function() { + return self.endpoints; + }; + + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams) { + if (anchorParams) + return jsPlumb.makeAnchor(anchorParams); + }; + var prepareEndpoint = function(existing, index, params, element) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint + || new jsPlumb.Endpoints.Dot(); + if (ep.constructor == String) + ep = new jsPlumb.Endpoints[ep](); + else if (ep.constructor == Array) { + ep = new jsPlumb.Endpoints[ep[0]](ep[1]); + } + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + var a = params.anchors ? params.anchors[index] : _makeAnchor(_currentInstance.Defaults.Anchors[index]) || _makeAnchor(jsPlumb.Defaults.Anchors[index]) || _makeAnchor(_currentInstance.Defaults.Anchor) || _makeAnchor(jsPlumb.Defaults.Anchor) || _makeAnchor("BottomCenter"); + var u = params.uuids ? params.uuids[index] : null; + var e = _newEndpoint( { paintStyle : es, hoverPaintStyle:ehs, endpoint : ep, connections : [ self ], uuid : u, anchor : a, source : element, container : self.container }); + self.endpoints[index] = e; + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector) + */ + this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || _currentInstance.Defaults.Connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + if (this.connector.constructor == String) + this.connector = new jsPlumb.Connectors[this.connector](); // lets you use a string as shorthand. + else if (this.connector.constructor == Array) + this.connector = new jsPlumb.Connectors[this.connector[0]](this.connector[1]); + this.paintStyle = this.endpoints[0].connectorStyle || this.endpoints[1].connectorStyle || params.paintStyle || _currentInstance.Defaults.PaintStyle || jsPlumb.Defaults.PaintStyle; + var backgroundPaintStyle = this.endpoints[0].connectorBackgroundStyle || this.endpoints[1].connectorBackgroundStyle || params.backgroundPaintStyle || _currentInstance.Defaults.BackgroundPaintStyle || jsPlumb.Defaults.BackgroundPaintStyle; + this.hoverPaintStyle = this.endpoints[0].connectorHoverStyle || this.endpoints[1].connectorHoverStyle || params.hoverPaintStyle || _currentInstance.Defaults.HoverPaintStyle || jsPlumb.Defaults.HoverPaintStyle; + this.paintStyleInUse = this.paintStyle; + + /* + * Property: overlays + * List of Overlays for this Connection. + */ + this.overlays = []; + if (params.overlays) { + for (var i = 0; i < params.overlays.length; i++) { + var o = params.overlays[i]; + if (o.constructor == Array) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0]; + var p = jsPlumb.CurrentLibrary.extend({connection:self}, o[1]); // make a copy of the object so as not to mess up anyone else's reference... + if (o.length == 3) jsPlumb.CurrentLibrary.extend(p, o[2]); + this.overlays.push(new jsPlumb.Overlays[type](p)); + } else if (o.constructor == String) { + this.overlays.push(new jsPlumb.Overlays[o]({connection:self})); + } + else this.overlays.push(o); + } + } + var overlayPlacements = []; + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + this.addOverlay = function(overlay) { overlays.push(overlay); }; + + // this is a shortcut helper method to let people add a label as + // overlay. + this.labelStyle = params.labelStyle || _currentInstance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + if (this.label) { + this.overlays.push(new jsPlumb.Overlays.Label( { + labelStyle : this.labelStyle, + label : this.label, + connection:self + })); + } + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String or a Function that returns a String. + */ + this.setLabel = function(l) { + self.label = l; + _currentInstance.repaint(self.source); + }; + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var otherOffset = offsets[this.targetId]; + var otherWH = sizes[this.targetId]; + var anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1] + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0] + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc }); + + var canvas = _newCanvas({"class":jsPlumb.connectorClass, container:self.container}); + this.canvas = canvas; + + /* + * Function: setBackgroundPaintStyle + * Sets the Connection's background paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + this.setBackgroundPaintStyle = function(style) { + backgroundPaintStyle = style; + self.repaint(); + }; + + /* + * Paints the connection. + * + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp; + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = false; + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + if (this.canvas.getContext) { + _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }); + _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + var ctx = canvas.getContext('2d'); + var sAnchorP = this.endpoints[sIdx].anchor.getCurrentLocation(); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.getCurrentLocation(); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + + // paint overlays + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + var s = o.computeMaxSize(self.connector, ctx); + if (s > maxSize) + maxSize = s; + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, self.paintStyleInUse.lineWidth, maxSize); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + + var _paintOneStyle = function(ctx, aStyle) { + ctx.save(); + jsPlumb.extend(ctx, aStyle); + if (aStyle.gradient && !ie) { + var g = self.connector.createGradient(dim, ctx, (elId == this.sourceId)); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + self.connector.paint(dim, ctx); + ctx.restore(); + }; + + // first check for the background style + if (backgroundPaintStyle != null) { + _paintOneStyle(ctx, backgroundPaintStyle); + } + _paintOneStyle(ctx, self.paintStyleInUse); + + // paint overlays + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + self.overlayPlacements[i] = o.draw(self.connector, ctx, self.paintStyleInUse); + } + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function() { + this.paint({ elId : this.sourceId, recalc : true }); + }; + + _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide + // whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + + //_registerConnection(self); + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have one to N connections emanating from it (although how to handle that in the UI is a very good question). also has a Canvas and paint style. + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * connections - optional list of Connections to configure the Endpoint with. + * container - optional element (as a string id) to use as the container for the canvas associated with this Endpoint. If not supplied, jsPlumb uses the default, which is the document body. A better way to use this container functionality is to set it on the defaults (jsPlumb.Defaults.Container="someElement"). + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorBackgroundStyle - if isSource is set to true, this is the background paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", 160 ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + EventGenerator.apply(this); + params = params || {}; + var self = this; + var visible = true; + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + var id = new String('_jsplumb_e_' + (new Date()).getTime()); + this.getId = function() { return id; }; + if (params.dynamicAnchors) + self.anchor = new DynamicAnchor(jsPlumb.makeAnchors(params.dynamicAnchors)); + else + self.anchor = params.anchor ? jsPlumb.makeAnchor(params.anchor) : params.anchors ? jsPlumb.makeAnchor(params.anchors) : jsPlumb.makeAnchor("TopCenter"); + var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot(); + if (_endpoint.constructor == String) + _endpoint = new jsPlumb.Endpoints[_endpoint](); + else if (_endpoint.constructor == Array) + _endpoint = new jsPlumb.Endpoints[_endpoint[0]](_endpoint[1]); + self.endpoint = _endpoint; + this.paintStyle = params.paintStyle || params.style || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + this.hoverPaintStyle = params.hoverPaintStyle || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + this.paintStyleInUse = this.paintStyle; + this.connectorStyle = params.connectorStyle; + this.connectorBackgroundStyle = params.connectorBackgroundStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + var _element = params.source, _uuid = params.uuid; + var floatingEndpoint = null, inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + this.container = params.container || _currentInstance.Defaults.Container || jsPlumb.Defaults.Container; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + /* + * Property: canvas + * The Endpoint's Canvas. + */ + this.canvas = params.canvas || _newCanvas({"class":jsPlumb.endpointClass, container:this.container, uuid:params.uuid}); + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + this.connections = params.connections || []; + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + var _reattach = params.reattach || false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + // this avoids a circular loop + if (!ignoreTarget) { + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + t.detach(connection, true); + } + _removeElement(connection.canvas, connection.container); + _removeFromList(connectionsByScope, connection.scope, connection); + if(!ignoreTarget) fireDetachEvent(connection); + } + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + */ + this.detachAll = function() { + while (self.connections.length > 0) { + self.detach(self.connections[0]); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + */ + this.detachFrom = function(targetEndpoint) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + c[i].setHover(false); + self.detach(c[i]); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + var e = _newEndpoint( { anchor : self.anchor, source : _element, paintStyle : this.paintStyle, endpoint : _endpoint }); + return e; + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + return (self.connections.length < _maxConnections) ? null : self.connections[0]; + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + + /* + * Function: setPaintStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + */ + this.setPaintStyle = this.setPaintStyle; // i do this so NaturalDocs can pick up the function definition. + + /* + * Function: setHoverPaintStyle + * Sets the hover paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * + * Parameters: + * style - Style object to set, for example { fillStyle:"yellow" }. + */ + this.setHoverPaintStyle = this.setHoverPaintStyle; // i do this so NaturalDocs can pick up the function definition. + + /* + * Sets the paint style of the Endpoint. + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + /* + * + * Paints the Endpoint, recalculating offset and anchor positions if necessary. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + + params = params || {}; + var timestamp = params.timestamp; + if (!timestamp || self.timestamp !== timestamp) { + var ap = params.anchorPoint, canvas = params.canvas, connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + // do we always want to force a repaint here? i dont + // think so! + var xy = params.offset || offsets[_elementId]; + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (self.anchor.isDynamic) { + if (self.connections.length > 0) { + var c = self.connections[0]; + var oIdx = c.endpoints[0] == self ? 1 : 0; + var oId = oIdx == 0 ? c.sourceId : c.targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + } + ap = self.anchor.compute(anchorParams); + } + _endpoint.paint(ap, self.anchor.getOrientation(), canvas || self.canvas, self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + self.timestamp = timestamp; + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + var start = function() { + jpc = self.connectorSelector(); + if (self.isFull() && !dragAllowedWhenFull) return false; + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + var nE = _getElementObject(n); + _appendElement(n, self.container); // + // create and assign an id, and initialize the offset. + var id = _getId(nE); + _updateOffset( { elId : id }); + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor( { reference : self.anchor, referenceCanvas : self.canvas }); + floatingEndpoint = _newEndpoint({ paintStyle : { fillStyle : 'rgba(0,0,0,0)' }, endpoint : _endpoint, anchor : floatingAnchor, source : nE }); + + if (jpc == null) { + self.anchor.locked = true; + // create a connection. one end is this endpoint, the + // other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : _getElementObject(_element), + target : _getElementObject(n), + anchors : [ self.anchor, floatingAnchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + backgroundPaintStyle:params.connectorBackgroundStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays + }); + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.setHover(false); + } else { + existingJpc = true; + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.setHover(false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas)); + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; // are we the source or the target? + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas); + var dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // get a new, temporary scope, to use (issue 57) + var newScope = "scope_" + (new Date()).getTime(); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + // set the new, temporary scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(i, newScope); + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // TODO unregister on stop? or will floating endpoint's + // destruction be assured. + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be + // dragging the source at any time + // before this connection is either discarded or made into a + // permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var dragOptions = params.dragOptions || {}; + var defaultOpts = jsPlumb.extend( {}, jsPlumb.CurrentLibrary.defaultDragOptions); + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + var startEvent = jsPlumb.CurrentLibrary.dragEvents['start']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], + function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + jsPlumb.CurrentLibrary.setOffset(n, _ui); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements( [ n, floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (_reattach) { + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } else { + jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc); // the main endpoint will inform the floating endpoint + // to disconnect, and also post the detached event. + } + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElement(jpc.canvas, self.container); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint(); + jpc.repaint(); + jpc = null; + delete inPlaceCopy; + delete endpointsByElement[floatingEndpoint.elementId]; + delete floatingEndpoint; + + _currentInstance.currentlyDragging = false; + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas) { + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + var drop = function() { + var draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)); + var id = _getAttribute(draggable, "dragId"); + var elId = _getAttribute(draggable, "elId"); + + // restore the original scope if necessary (issue 57) + var scope = _getAttribute(draggable, "originalScope"); + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // todo test that the target is not full. + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + if (!jpc.suspendedEndpoint) { // if a new connection, add it. TODO: move this to a jsPlumb internal method - addConnection or something. doesnt need to be exposed. + _addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + _currentInstance.fireUpdate("jsPlumbConnectionDetached", { + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }); + } + + jsPlumb.repaint(elId); + + _currentInstance.fireUpdate("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], + targetEndpoint : jpc.endpoints[1], + connection:jpc + }); + } + + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute( _getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + }); + + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas)); + + return self; + }; + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + //if (_defaults) jsPlumb.extend(j.Defaults, _defaults); + return j; + }; +})(); +/* +* jsPlumb-defaults-1.2.6-RC1 +* +* This script contains the default Anchors, Endpoints, Connectors and Overlays for jsPlumb. It should be used with jsPlumb 1.1.0 and above; +* prior to version 1.1.0 of jsPlumb the defaults were included inside the main script. +* +* NOTE: for production usage you should use jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, +* in a minified file. +* +* Dual licensed under MIT and GPL2. +*/ + +(function() { + + var ie = !!!document.createElement('canvas').getContext; + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * getOrientation : function() { return [ox, oy]; } + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + var _curryAnchor = function(x,y,ox,oy) { + return function() { + return jsPlumb.makeAnchor(x,y,ox,oy); + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1); + + + jsPlumb.Defaults.DynamicAnchors = function() { + return jsPlumb.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"]); + }; + jsPlumb.Anchors["AutoDefault"] = function() { return jsPlumb.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors()); }; + + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + var currentPoints = null; + var _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + + _dx = _tx - _sx, _dy = (_ty - _sy); + _m = _dy / _dx, _m2 = -1 / _m; + + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + + return currentPoints; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + var xp = _sx + (location * _dx); + var yp = (_m == Infinity || _m == -Infinity) ? _sy + (location * (_ty - _sy)) : (_m * xp) + _b; + return {x:xp, y:yp}; + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { return _m; }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + var orientation = distance > 0 ? 1 : -1; + var y = Math.abs(distance * Math.sin(_theta)); + if (_sy > _ty) y = y * -1; + var x = Math.abs(distance * Math.cos(_theta)); + if (_sx > _tx) x = x * -1; + return {x:p.x + (orientation * x), y:p.y + (orientation * y)}; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = self.gradientAtPoint(p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + /** + * This Connector draws a Bezier curve with two control points. + * + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * note that the method signature changed in 1.2.6 to take a params object, so the method + * argument was renamed. you can still provide just an integer to this constructor, though the + * preferred method is to use {curviness:XXX}. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + this.majorAnchor = 150; + // backwards compatibility (ideally we'd just use params.curviness || 150). + if (params) { + if (params.constructor == Number) this.majorAnchor = params; + else if (params.curviness) this.majorAnchor = params.curviness; + } + this.minorAnchor = 10; + var currentPoints = null; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h; + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) + { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx); var minx2 = Math.min(_CP[0], _CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(_sx,_tx); var maxx2 = Math.max(_CP[0], _CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty); var miny2 = Math.min(_CP[1], _CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(_sy,_ty); var maxy2 = Math.max(_CP[1], _CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + this.paint = function(d, ctx) { + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + return jsBezier.perpendicularToCurveAt(_makeCurve(), location, length, distance); + }; + + this.createGradient = function(dim, ctx, swap) { + return (swap) ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + + /** + * Types of endpoint UIs. we supply four - a circle of default radius 10px, a rectangle of + * default size 20x20, an image (with no default), and a Triangle, of default size 15. + * you can supply others of these if you want to - see the documentation for a howto. + */ + + /** + * a round endpoint, with default radius 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }; + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + if (endpointStyle.gradient && !ie) { + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * A Rectangular endpoint, with default size 20x20. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + jsPlumb.Endpoints.Triangle = function(params) { + + params = params || { width:55, height:55 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - width/2; + var y = anchorPoint[1] - height/2; + + jsPlumb.sizeCanvas(canvas, x, y, width, height); + + var ctx = canvas.getContext('2d'); + var offsetX = 0, offsetY = 0, angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = endpointStyle.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' or, since 1.2.4, a 'src' property in the params object.. + */ + jsPlumb.Endpoints.Image = function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.src || params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + }; + + /** + * An arrow overlay. you can provide: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + params = params || {}; + var self = this; + this.length = params.length || 20; + this.width = params.width || 20; + this.connection = params.connection; + var direction = (params.direction || 1) < 0 ? -1 : 1; + /*var fillStyle = params.fillStyle; + var strokeStyle = params.strokeStyle; + var lineWidth = params.lineWidth || 1;*/ + var paintStyle = params.paintStyle || { lineWidth:1 }; + this.loc = params.location == null ? 0.5 : params.location; + // how far along the arrow the lines folding back in come to. default is 62.3%. + var foldback = params.foldback || 0.623; + var _getFoldBackPoint = function(connector, loc) { + if (foldback == 0.5) return connector.pointOnPath(loc); + else { + var adj = 0.5 - foldback; // we calculate relative to the center + return connector.pointAlongPathFrom(loc, direction * self.length * adj); + } + }; + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.draw = function(connector, ctx, currentConnectionPaintStyle) { + + // this is the arrow head position + var hxy = connector.pointAlongPathFrom(self.loc, direction * (self.length / 2)); + // this is the center of the tail + var txy = connector.pointAlongPathFrom(self.loc, -1 * direction * (self.length / 2)), tx = txy.x, ty = txy.y; + // this is the tail vector + var tail = connector.perpendicularToPathAt(self.loc, self.width, -1 * direction * (self.length / 2)); + // this is the point the tail goes in to + var cxy = _getFoldBackPoint(connector, self.loc); + + // if loc = 1, then hxy should be flush with the element. + if (self.loc == 1) { + var lxy = connector.pointOnPath(self.loc); + var dx = lxy.x - hxy.x, dy = lxy.y - hxy.y; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + // if loc = 0, then tail midpoint should be flush with the element. + if (self.loc == 0) { + var lxy = connector.pointOnPath(self.loc); + var tailMid = foldback > 1 ? cxy : { + x:tail[0].x + ((tail[1].x - tail[0].x) / 2), + y:tail[0].y + ((tail[1].y - tail[0].y) / 2) + }; + var dx = lxy.x - tailMid.x, dy = lxy.y - tailMid.y; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x); + var maxx = Math.max(hxy.x, tail[0].x, tail[1].x); + var miny = Math.min(hxy.y, tail[0].y, tail[1].y); + var maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + ctx.lineWidth = paintStyle.lineWidth; + ctx.beginPath(); + ctx.moveTo(hxy.x, hxy.y); + ctx.lineTo(tail[0].x, tail[0].y); + ctx.lineTo(cxy.x, cxy.y); + ctx.lineTo(tail[1].x, tail[1].y); + ctx.lineTo(hxy.x, hxy.y); + ctx.closePath(); + + if (paintStyle.strokeStyle) { + ctx.strokeStyle = paintStyle.strokeStyle; + ctx.stroke(); + } + ctx.fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle; + ctx.fill(); + + + + return [ minx, maxx, miny, maxy]; + }; + }; + + /** + * a basic arrow. this is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. See Arrow for params. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + /** + * a diamond. like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. See Arrow for params. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40; + var p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + /** + * A Label overlay. Params you can provide: + * + * labelStyle - js object containing style instructions for the label. defaults to jsPlumb.Defaults.LabelStyle. + * label - the label to paint. may be a string or a function that returns a string. nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * borderWidth - width of a border to paint. defaults to zero. + * borderStyle - strokeStyle to use when painting the border, if necessary. + */ + jsPlumb.Overlays.Label = function(params) { + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + this.connection = params.connection; + var self = this; + var labelWidth = null, labelHeight = null, labelText = null, labelPadding = null; + this.location = params.location || 0.5; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var _textDimensions = function(ctx) { + if (self.cachedDimensions) return self.cachedDimensions; // return cached copy if we can. if we add a setLabel function remember to clear the cache. + labelText = typeof self.label == 'function' ? self.label(self) : self.label; + var d = {}; + if (labelText) { + var lines = labelText.split(/\n|\r\n/); + ctx.save(); + if (self.labelStyle.font) ctx.font = self.labelStyle.font; + var t = _widestLine(lines, ctx); + // a fake text height measurement: use the width of upper case M + var h = ctx.measureText("M").width; + labelPadding = self.labelStyle.padding || 0.25; + labelWidth = t + (2 * t * labelPadding); + labelHeight = (lines.length * h) + (2 * h * labelPadding); + var textHeight = lines.length * h; + ctx.restore(); + d = {width:labelWidth, height:labelHeight, lines:lines, oneLine:h, padding:labelPadding, textHeight:textHeight}; + } + if (typeof self.label != 'function') self.cachedDimensions = d; // cache it if we can. + return d; + }; + this.computeMaxSize = function(connector, ctx) { + var td = _textDimensions(ctx); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + var _widestLine = function(lines, ctx) { + var max = 0; + for (var i = 0; i < lines.length; i++) { + var t = ctx.measureText(lines[i]).width; + if (t > max) max = t; + } + return max; + }; + + this.draw = function(connector, ctx, currentConnectionPaintStyle) { + var td = _textDimensions(ctx); + if (td.width) { + var cxy = connector.pointOnPath(self.location); + if (self.labelStyle.font) ctx.font = self.labelStyle.font; + if (self.labelStyle.fillStyle) + ctx.fillStyle = self.labelStyle.fillStyle; + else + ctx.fillStyle = "rgba(0,0,0,0)"; + + var minx = cxy.x - (td.width / 2); + var miny = cxy.y - (td.height / 2); + + ctx.fillRect(minx, miny , td.width , td.height ); + + if (self.labelStyle.color) ctx.fillStyle = self.labelStyle.color; + ctx.textBaseline = "middle"; + ctx.textAlign = "center"; + for (i = 0; i < td.lines.length; i++) { + ctx.fillText(td.lines[i],cxy.x, cxy.y - (td.textHeight / 2) + (td.oneLine/2) + (i*td.oneLine)); + } + + // border + if (self.labelStyle.borderWidth > 0) { + ctx.strokeStyle = self.labelStyle.borderStyle || "black"; + ctx.strokeRect(minx, miny, td.width , td.height ); + } + + return [minx, minx+td.width, miny, miny+td.height]; + } + else return [0,0,0,0]; + }; + }; + + /** + * an image overlay. params may contain: + * + * location : proportion along the connector to draw the image. optional. + * src : image src. required. + * events : map of event names to functions; each event listener will be bound to the img. + */ + jsPlumb.Overlays.Image = function(params) { + var self = this; + this.location = params.location || 0.5; + this.img = new Image(); + this.connection = params.connection; + var imgDiv = null; + var notReadyInterval = null; + var notReadyConnector, notReadyContext; + var events = params.events || {}; + var _init = function() { + if (self.ready) { + window.clearInterval(notReadyInterval); + imgDiv = document.createElement("img"); + imgDiv.src = self.img.src; + imgDiv.style.position = "absolute"; + imgDiv.style.display="none"; + imgDiv.className = "_jsPlumb_overlay"; + document.body.appendChild(imgDiv);// HMM + // attach events + for (var e in events) { + jsPlumb.CurrentLibrary.bind(imgDiv, e, events[e]); + } + if (notReadyConnector && notReadyContext) { + _draw(notReadyConnector, notReadyContext); + notReadyContext = null; + notReadyConnector = null; + } + } + }; + this.img.onload = function() { + self.ready = true; + // jsPlumb.repaintAll(); + }; + this.img.src = params.src || params.url; + + notReadyInterval = window.setInterval(_init, 250); + + this.computeMaxSize = function(connector, ctx) { + return [ self.img.width, self.img.height ]; + }; + + var _draw = function(connector, ctx, currentConnectionPaintStyle) { + if (imgDiv != null) { + var cxy = connector.pointOnPath(self.location); + var canvas = jsPlumb.CurrentLibrary.getElementObject(ctx.canvas); + var canvasOffset = jsPlumb.CurrentLibrary.getOffset(canvas); + var minx = cxy.x - (self.img.width/2); + var miny = cxy.y - (self.img.height/2); + var o = {left:canvasOffset.left + minx, top:canvasOffset.top + miny}; + jsPlumb.CurrentLibrary.setOffset(imgDiv, o); + imgDiv.style.display = "block"; + return [minx,minx + self.img.width, miny, miny+self.img.height]; + } + }; + + this.draw = function(connector, ctx) { + if (self.ready) + return _draw(connector, ctx); + else { + notReadyConnector = connector; + notReadyContext = ctx; + return [0,0,0,0]; + } + }; + }; +})();;(function() { + + jsPlumb.Connectors.Flowchart = function(params) { + params = params || {}; + var self = this, + minStubLength = params.minStubLength || 30, + segments = [], + segmentGradients = [], + segmentProportions = [], + segmentLengths = [], + segmentProportionalLengths = [], + points = [], + swapX, + swapY, + /** + * recalculates the gradients of each segment, and the points at which the segments begin, proportional to the total length travelled + * by all the segments that constitute the connector. + */ + updateSegmentGradientsAndProportions = function(startX, startY, endX, endY) { + var total = 0; + for (var i = 0; i < segments.length; i++) { + var sx = i == 0 ? startX : segments[i][2], + sy = i == 0 ? startY : segments[i][3], + ex = segments[i][0], + ey = segments[i][1]; + + segmentGradients[i] = sx == ex ? Infinity : 0; + segmentLengths[i] = Math.abs(sx == ex ? ey - sy : ex - sx); + total += segmentLengths[i]; + } + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segmentLengths[i] / total; + segmentProportions[i] = [curLoc, (curLoc += (segmentLengths[i] / total)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0]; + var ly = segments.length == 0 ? sy : segments[segments.length - 1][1]; + segments.push([x, y, lx, ly]); + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment that contains the point which is 'location' distance along the entire path, where 'location' is + * a decimal between 0 and 1 inclusive. in this connector type paths are made up of a list of segments, each of which contributes some fraction to + * the total length. + */ + findSegmentForLocation = function(location) { + var idx = segmentProportions.length - 1, inSegmentProportion = 0; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + segments = []; + segmentGradients = []; + segmentProportionalLengths = []; + segmentLengths = []; + segmentProportionals = []; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + offx = (lw / 2) + (minStubLength * 2), + offy = (lw / 2) + (minStubLength * 2), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(), + to = targetAnchor.orientation || targetAnchor.getOrientation(), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx, + h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy; + if (w < minWidth) { + offx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + offy += (minWidth - h) / 2; + h = minWidth; + } + sx = swapX ? w-offx : offx, + sy = swapY ? h-offy : offy, + tx = swapX ? offx : w-offx , + ty = swapY ? offy : h-offy, + startStubX = sx + (so[0] * minStubLength), + startStubY = sy + (so[1] * minStubLength), + endStubX = tx + (to[0] * minStubLength), + endStubY = ty + (to[1] * minStubLength), + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2); + + x -= offx; y -= offy; + points = [x, y, w, h, sx, sy, tx, ty], extraPoints = []; + + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + if (so[0] == 0) { + var startStubIsBeforeEndStub = startStubY < endStubY; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } + } + else { + var startStubIsBeforeEndStub = startStubX < endStubX; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } + } + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentGradientsAndProportions(sx, sy, tx, ty); + + return points; + }; + + this.paint = function(dimensions, ctx) { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location) { + return self.pointAlongPathFrom(location, 0); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location) { + return segmentGradients[findSegmentForLocation(location)["index"]]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance) { + var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segmentLengths[s.index], m = segmentGradients[s.index]; + var e = { + //x : m == Infinity ? seg[2] : /*swapX ? seg[2] - (p * sl) - distance : */seg[2] + (p * sl) + distance, + + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + + + //y : m == 0 ? seg[3] : /*swapY ? seg[3] - (p * sl) - distance : */seg[3] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + //console.log("pointalongpath, swapX =" + swapX + ",swapY=" + swapY, "loc", location, "travel", (p * sl), "dist", distance, e.x, e.y, "seg", seg, "len", sl, "prop.", p); + + return e; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = segmentGradients[p.segmentInfo.index]; + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + + }; + }; +})();/* + * mootools.jsPlumb 1.2.6-RC1 + * + * MooTools specific functionality for jsPlumb. + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + * NOTE: for production usage you should use mootools.jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, + * in a minified file. + * + * Dual licensed under MIT and GPL2. + * + */ + +(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function(now) { + this.parent(now); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}; + var _droppableOptions = {}; + var _draggablesByScope = {}; + var _draggablesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }; + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + var _getElementObject = function(el) { + return $(el); + }; + + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + //var m = new Fx.Morph(el, options); + m.start(properties); + }, + + appendElement : function(child, parent) { + _getElementObject(parent).grab(child); + }, + + bind : function(el, event, callback) { + el = _getElementObject(el); + el.addEvent(event, callback); + }, + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el); + var drags = _draggablesById[id]; + return drags[0].scope; + }, + + getElementObject : _getElementObject, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + getPageXY : function(eventObject) { + return [eventObject.event.pageX, eventObject.event.pageY]; + }, + + getScrollLeft : function(el) { + return null; + }, + + getScrollTop : function(el) { + return null; + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[0]; + return { left: ui.offsetLeft, top: ui.offsetTop }; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options) { + var id = jsPlumb.getId(el); + var drag = _draggablesById[id]; + if (!drag) { + var originalZIndex = 0, originalCursor = null; + var dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + options['onStart'] = jsPlumb.wrap(options['onStart'], function() + { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() + { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + }); + + // DROPPABLES: + var scope = options['scope'] || jsPlumb.Defaults.Scope; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop'); + } + }; + + drag = new Drag.Move(el, options); + drag.scope = scope; + //console.log("drag scope initialized to ", scope); + _add(_draggablesByScope, scope, drag); + _add(_draggablesById, el.get("id"), drag); + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + initDroppable : function(el, options) { + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _add(_droppables, scope, el); + var id = jsPlumb.getId(el); + _droppableOptions[id] = options; + var filterFunc = function(entry) { return entry.element != el; }; + var draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + isAlreadyDraggable : function(el) { + return _draggablesById[jsPlumb.getId(el)] != null; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + return (typeof Drag != undefined && typeof Drag.Move != undefined); + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + _getElementObject(element).dispose(); // ?? + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + setDragScope : function(el, scope) { + var drag = _draggablesById[el.get("id")]; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + drag[0].droppables = droppables; + }, + + setOffset : function(el, o) { + _getElementObject(el).setPosition({x:o.left, y:o.top}); + } + }; + + window.addEvent('domready', jsPlumb.init); +})(); +(function(){if(typeof Math.sgn=="undefined")Math.sgn=function(a){return a==0?0:a>0?1:-1};var p={subtract:function(a,b){return{x:a.x-b.x,y:a.y-b.y}},dotProduct:function(a,b){return a.x*b.x+a.y*b.y},square:function(a){return Math.sqrt(a.x*a.x+a.y*a.y)},scale:function(a,b){return{x:a.x*b,y:a.y*b}}},y=Math.pow(2,-65),u=function(a,b){for(var g=[],d=b.length-1,h=2*d-1,f=[],c=[],l=[],k=[],i=[[1,0.6,0.3,0.1],[0.4,0.6,0.6,0.4],[0.1,0.3,0.6,1]],e=0;e<=d;e++)f[e]=p.subtract(b[e],a);for(e=0;e<=d-1;e++){c[e]= +p.subtract(b[e+1],b[e]);c[e]=p.scale(c[e],3)}for(e=0;e<=d-1;e++)for(var m=0;m<=d;m++){l[e]||(l[e]=[]);l[e][m]=p.dotProduct(c[e],f[m])}for(e=0;e<=h;e++){k[e]||(k[e]=[]);k[e].y=0;k[e].x=parseFloat(e)/h}h=d-1;for(f=0;f<=d+h;f++){c=Math.min(f,d);for(e=Math.max(0,f-h);e<=c;e++){j=f-e;k[e+j].y+=l[j][e]*i[j][e]}}d=b.length-1;k=s(k,2*d-1,g,0);h=p.subtract(a,b[0]);l=p.square(h);for(e=i=0;e=64){g[0]=(a[0].x+a[b].x)/2;return 1}var n,o,q;k=a[0].y-a[b].y;i=a[b].x-a[0].x;e=a[0].x*a[b].y-a[b].x*a[0].y;m=max_distance_below=0;for(o=1;om)m=q;else if(q0?1:-1,c=null;h0&&H>0&&B=B&&D[2]<=H&&D[3]>=H)){return true}}if(!d){var F=A.canvas.getContext("2d").getImageData(parseInt(B),parseInt(H),1,1);return F.data[0]!=0||F.data[1]!=0||F.data[2]!=0||F.data[3]!=0}else{}}return false};this.bind=function(B,C){h(v,B,C)};this.fireUpdate=function(D,E,B){if(v[D]){for(var C=0;C=0){delete (aq[ar]);aq.splice(ar,1);return true}}}return false};var r=function(ar,aq){return C(ar,function(at,au){aa[au]=aq;if(q.CurrentLibrary.isDragSupported(at)){q.CurrentLibrary.setDraggable(at,aq)}})};var ai=function(aq,ar){N(n(aq,"id"),function(at){at.canvas.style.display=ar})};var H=function(aq){return C(aq,function(at,ar){var au=aa[ar]==null?R:aa[ar];au=!au;aa[ar]=au;q.CurrentLibrary.setDraggable(at,au);return au})};var s=function(aq){N(aq,function(at){var ar=("none"==at.canvas.style.display);at.canvas.style.display=ar?"block":"none"})};var y=function(aw){var au=aw.timestamp,aq=aw.recalc,av=aw.offset,ar=aw.elId;if(!aq){if(au&&au===w[ar]){return}}if(aq||av==null){var at=b(ar);if(at!=null){J[ar]=e(at);P[ar]=p(at);w[ar]=au}}else{P[ar]=av}};var ap=function(aq,ar){var at=b(aq);var au=n(at,"id");if(!au||au=="undefined"){if(arguments.length==2&&arguments[1]!=undefined){au=ar}else{au="jsPlumb_"+al()}f(at,"id",au)}return au};var ag=function(at,aq,ar){at=at||function(){};aq=aq||function(){};return function(){var au=null;try{au=aq.apply(this,arguments)}catch(av){l(x,"jsPlumb function failed : "+av)}if(ar==null||(au!==ar)){try{at.apply(this,arguments)}catch(av){l(x,"wrapped function failed : "+av)}}return au}};this.connectorClass="_jsPlumb_connector";this.endpointClass="_jsPlumb_endpoint";this.overlayClass="_jsPlumb_overlay";this.Anchors={};this.Connectors={};this.Endpoints={};this.Overlays={};this.addEndpoint=function(aw,au,aA){aA=aA||{};var ar=q.extend({},aA);q.extend(ar,au);ar.endpoint=ar.endpoint||x.Defaults.Endpoint||q.Defaults.Endpoint;ar.endpointStyle=ar.endpointStyle||x.Defaults.EndpointStyle||q.Defaults.EndpointStyle;var at=b(aw),aq=n(at,"id");ar.source=at;y({elId:aq});var ax=ah(ar);h(ab,aq,ax);var ay=P[aq],av=J[aq];var az=ax.anchor.compute({xy:[ay.left,ay.top],wh:av,element:ax});ax.paint({anchorLoc:az});return ax};this.addEndpoints=function(av,ar,aq){var au=[];for(var at=0;at0?Y(aC,aB)!=-1:true};for(var av in E){if(at(ay,av)){aq[av]=[];for(var au=0;au=4){ar.orientation=[arguments[2],arguments[3]]}if(arguments.length==6){ar.offsets=[arguments[4],arguments[5]]}}var ax=new V(ar);ax.clone=function(){return new V(ar)};return ax};this.makeAnchors=function(ar){var at=[];for(var aq=0;aq0?aA[0]:null;var aw=aA.length>0?0:-1;this.locked=false;var az=this;var av=function(aD,aB,aH,aG,aC){var aF=aG[0]+(aD.x*aC[0]),aE=aG[1]+(aD.y*aC[1]);return Math.sqrt(Math.pow(aB-aF,2)+Math.pow(aH-aE,2))};var aq=ar||function(aL,aC,aD,aE,aB){var aG=aD[0]+(aE[0]/2),aF=aD[1]+(aE[1]/2);var aI=-1,aK=Infinity;for(var aH=0;aHaO){aO=aW}}var a1=this.connector.compute(aQ,a3,this.endpoints[a7].anchor,this.endpoints[aN].anchor,az.paintStyleInUse.lineWidth,aO);q.sizeCanvas(at,a1[0],a1[1],a1[2],a1[3]);var aU=function(a8,ba){a8.save();q.extend(a8,ba);if(ba.gradient&&!d){var bb=az.connector.createGradient(a1,a8,(aV==this.sourceId));for(var a9=0;a9=0){aE.connections.splice(aQ,1);if(!aT){var aS=aR.endpoints[0]==aE?aR.endpoints[1]:aR.endpoints[0];aS.detach(aR,true)}X(aR.canvas,aR.container);G(E,aR.scope,aR);if(!aT){W(aR)}}};this.detachAll=function(){while(aE.connections.length>0){aE.detach(aE.connections[0])}};this.detachFrom=function(aR){var aS=[];for(var aQ=0;aQ=0){aE.connections.splice(aQ,1)}};this.getElement=function(){return aD};this.getUuid=function(){return az};this.makeInPlaceCopy=function(){var aQ=ah({anchor:aE.anchor,source:aD,paintStyle:this.paintStyle,endpoint:aC});return aQ};this.isConnectedTo=function(aS){var aR=false;if(aS){for(var aQ=0;aQ=aK)};this.setDragAllowedWhenFull=function(aQ){aA=aQ};this.setPaintStyle=this.setPaintStyle;this.setHoverPaintStyle=this.setHoverPaintStyle;this.setStyle=aE.setPaintStyle;this.equals=function(aQ){return this.anchor.equals(aQ.anchor)};this.paint=function(aT){aT=aT||{};var aX=aT.timestamp;if(!aX||aE.timestamp!==aX){var aW=aT.anchorPoint,aS=aT.canvas,aU=aT.connectorPaintStyle;if(aW==null){var a2=aT.offset||P[ax];var aQ=aT.dimensions||J[ax];if(a2==null||aQ==null){y({elId:ax,timestamp:aX});a2=P[ax];aQ=J[ax]}var aR={xy:[a2.left,a2.top],wh:aQ,element:aE,timestamp:aX};if(aE.anchor.isDynamic){if(aE.connections.length>0){var aZ=aE.connections[0];var a1=aZ.endpoints[0]==aE?1:0;var aV=a1==0?aZ.sourceId:aZ.targetId;var aY=P[aV],a0=J[aV];aR.txy=[aY.left,aY.top];aR.twh=a0;aR.tElement=aZ.endpoints[a1]}}aW=aE.anchor.compute(aR)}aC.paint(aW,aE.anchor.getOrientation(),aS||aE.canvas,aE.paintStyleInUse,aU||aE.paintStyleInUse);aE.timestamp=aX}};this.repaint=this.paint;this.removeConnection=this.detach;if(aP.isSource&&q.CurrentLibrary.isDragSupported(aD)){var aJ=null,aF=null,aI=null,aq=false,at=null;var av=function(){aI=aE.connectorSelector();if(aE.isFull()&&!aA){return false}y({elId:ax});au=aE.makeInPlaceCopy();au.paint();aJ=document.createElement("div");var aU=b(aJ);L(aJ,aE.container);var aW=ap(aU);y({elId:aW});f(b(aE.canvas),"dragId",aW);f(b(aE.canvas),"elId",ax);var aR=new B({reference:aE.anchor,referenceCanvas:aE.canvas});aN=ah({paintStyle:{fillStyle:"rgba(0,0,0,0)"},endpoint:aC,anchor:aR,source:aU});if(aI==null){aE.anchor.locked=true;aI=an({sourceEndpoint:aE,targetEndpoint:aN,source:b(aD),target:b(aJ),anchors:[aE.anchor,aR],paintStyle:aP.connectorStyle,hoverPaintStyle:aP.connectorHoverStyle,backgroundPaintStyle:aP.connectorBackgroundStyle,connector:aP.connector,overlays:aP.connectorOverlays});aI.setHover(false)}else{aq=true;aI.setHover(false);aw(b(au.canvas));var aQ=aI.sourceId==ax?0:1;aI.floatingAnchorIndex=aQ;aE.detachFromConnection(aI);var aV=b(aE.canvas);var aT=q.CurrentLibrary.getDragScope(aV);f(aV,"originalScope",aT);var aS="scope_"+(new Date()).getTime();if(aQ==0){at=[aI.source,aI.sourceId,aM,aT];aI.source=b(aJ);aI.sourceId=aW}else{at=[aI.target,aI.targetId,aM,aT];aI.target=b(aJ);aI.targetId=aW}q.CurrentLibrary.setDragScope(aM,aS);aI.endpoints[aQ==0?1:0].anchor.locked=true;aI.suspendedEndpoint=aI.endpoints[aQ];aI.endpoints[aQ]=aN}S[aW]=aI;aN.addConnection(aI);h(ab,aW,aN);x.currentlyDragging=true};var aL=aP.dragOptions||{};var aG=q.extend({},q.CurrentLibrary.defaultDragOptions);aL=q.extend(aG,aL);aL.scope=aL.scope||aE.scope;var aH=q.CurrentLibrary.dragEvents.start;var aO=q.CurrentLibrary.dragEvents.stop;var ay=q.CurrentLibrary.dragEvents.drag;aL[aH]=ag(aL[aH],av);aL[ay]=ag(aL[ay],function(){var aQ=q.CurrentLibrary.getUIPosition(arguments);q.CurrentLibrary.setOffset(aJ,aQ);am(b(aJ),aQ)});aL[aO]=ag(aL[aO],function(){G(ab,aF,aN);K([aJ,aN.canvas],aD);X(au.canvas,aD);var aQ=aI.floatingAnchorIndex==null?1:aI.floatingAnchorIndex;aI.endpoints[aQ==0?1:0].anchor.locked=false;if(aI.endpoints[aQ]==aN){if(aq&&aI.suspendedEndpoint){if(aQ==0){aI.source=at[0];aI.sourceId=at[1]}else{aI.target=at[0];aI.targetId=at[1]}q.CurrentLibrary.setDragScope(at[2],at[3]);aI.endpoints[aQ]=aI.suspendedEndpoint;if(aB){aI.floatingAnchorIndex=null;aI.suspendedEndpoint.addConnection(aI);q.repaint(at[1])}else{aI.endpoints[aQ==0?1:0].detach(aI)}}else{X(aI.canvas,aE.container);aE.detachFromConnection(aI)}}aE.anchor.locked=false;aE.paint();aI.repaint();aI=null;delete au;delete ab[aN.elementId];delete aN;x.currentlyDragging=false});var aM=b(aE.canvas);q.CurrentLibrary.initDraggable(aM,aL)}var aw=function(aT){if(aP.isTarget&&q.CurrentLibrary.isDropSupported(aD)){var aQ=aP.dropOptions||x.Defaults.DropOptions||q.Defaults.DropOptions;aQ=q.extend({},aQ);aQ.scope=aQ.scope||aE.scope;var aW=null;var aU=q.CurrentLibrary.dragEvents.drop;var aV=q.CurrentLibrary.dragEvents.over;var aR=q.CurrentLibrary.dragEvents.out;var aS=function(){var a5=b(q.CurrentLibrary.getDragObject(arguments));var aX=n(a5,"dragId");var aZ=n(a5,"elId");var a4=n(a5,"originalScope");if(a4){q.CurrentLibrary.setDragScope(a5,a4)}var a1=S[aX];var a2=a1.floatingAnchorIndex==null?1:a1.floatingAnchorIndex,a3=a2==0?1:0;if(!aE.isFull()&&!(a2==0&&!aE.isSource)&&!(a2==1&&!aE.isTarget)){if(a2==0){a1.source=aD;a1.sourceId=ax}else{a1.target=aD;a1.targetId=ax}a1.endpoints[a2].detachFromConnection(a1);if(a1.suspendedEndpoint){a1.suspendedEndpoint.detachFromConnection(a1)}a1.endpoints[a2]=aE;aE.addConnection(a1);if(!a1.suspendedEndpoint){h(E,a1.scope,a1);I(aD,aP.draggable,{})}else{var a0=a1.suspendedEndpoint.getElement(),aY=a1.suspendedEndpoint.elementId;x.fireUpdate("jsPlumbConnectionDetached",{source:a2==0?a0:a1.source,target:a2==1?a0:a1.target,sourceId:a2==0?aY:a1.sourceId,targetId:a2==1?aY:a1.targetId,sourceEndpoint:a2==0?a1.suspendedEndpoint:a1.endpoints[0],targetEndpoint:a2==1?a1.suspendedEndpoint:a1.endpoints[1],connection:a1})}q.repaint(aZ);x.fireUpdate("jsPlumbConnection",{source:a1.source,target:a1.target,sourceId:a1.sourceId,targetId:a1.targetId,sourceEndpoint:a1.endpoints[0],targetEndpoint:a1.endpoints[1],connection:a1})}x.currentlyDragging=false;delete S[aX]};aQ[aU]=ag(aQ[aU],aS);aQ[aV]=ag(aQ[aV],function(){var aY=q.CurrentLibrary.getDragObject(arguments);var a0=n(b(aY),"dragId");var aZ=S[a0];var aX=aZ.floatingAnchorIndex==null?1:aZ.floatingAnchorIndex;aZ.endpoints[aX].anchor.over(aE.anchor)});aQ[aR]=ag(aQ[aR],function(){var aY=q.CurrentLibrary.getDragObject(arguments);var a0=n(b(aY),"dragId");var aZ=S[a0];var aX=aZ.floatingAnchorIndex==null?1:aZ.floatingAnchorIndex;aZ.endpoints[aX].anchor.out()});q.CurrentLibrary.initDroppable(aT,aQ)}};aw(b(aE.canvas));return aE}};var q=window.jsPlumb=new o();q.getInstance=function(s){var r=new o(s);return r}})();(function(){var b=!!!document.createElement("canvas").getContext;var a=function(c,f,e,d){return function(){return jsPlumb.makeAnchor(c,f,e,d)}};jsPlumb.Anchors.TopCenter=a(0.5,0,0,-1);jsPlumb.Anchors.BottomCenter=a(0.5,1,0,1);jsPlumb.Anchors.LeftMiddle=a(0,0.5,-1,0);jsPlumb.Anchors.RightMiddle=a(1,0.5,1,0);jsPlumb.Anchors.Center=a(0.5,0.5,0,0);jsPlumb.Anchors.TopRight=a(1,0,0,-1);jsPlumb.Anchors.BottomRight=a(1,1,0,1);jsPlumb.Anchors.TopLeft=a(0,0,0,-1);jsPlumb.Anchors.BottomLeft=a(0,1,0,1);jsPlumb.Defaults.DynamicAnchors=function(){return jsPlumb.makeAnchors(["TopCenter","RightMiddle","BottomCenter","LeftMiddle"])};jsPlumb.Anchors.AutoDefault=function(){return jsPlumb.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors())};jsPlumb.Connectors.Straight=function(){var q=this;var k=null;var e,l,o,n,m,f,p,h,g,d,c;this.compute=function(u,I,E,r,B,t){var H=Math.abs(u[0]-I[0]);var A=Math.abs(u[1]-I[1]);var C=false,v=false;var z=0.45*H,s=0.45*A;H*=1.9;A*=1.9;var F=Math.min(u[0],I[0])-z;var D=Math.min(u[1],I[1])-s;var G=Math.max(2*B,t);if(H0?1:-1;var v=Math.abs(w*Math.sin(f));if(g>c){v=v*-1}var r=Math.abs(w*Math.cos(f));if(h>d){r=r*-1}return{x:u.x+(t*r),y:u.y+(t*v)}};this.perpendicularToPathAt=function(u,v,A){var w=q.pointAlongPathFrom(u,A);var t=q.gradientAtPoint(w.location);var s=Math.atan(-1/t);var z=v/2*Math.sin(s);var r=v/2*Math.cos(s);return[{x:w.x+r,y:w.y+z},{x:w.x-r,y:w.y-z}]};this.createGradient=function(s,r){return r.createLinearGradient(s[4],s[5],s[6],s[7])}};jsPlumb.Connectors.Bezier=function(g){var r=this;this.majorAnchor=150;if(g){if(g.constructor==Number){this.majorAnchor=g}else{if(g.curviness){this.majorAnchor=g.curviness}}}this.minorAnchor=10;var l=null;this._findControlPoint=function(B,s,w,z,t){var y=z.getOrientation(),A=t.getOrientation();var v=y[0]!=A[0]||y[1]==A[1];var u=[];var C=r.majorAnchor,x=r.minorAnchor;if(!v){if(y[0]==0){u.push(s[0]o){o=x}if(A<0){f+=A;var B=Math.abs(A);o+=B;q[0]+=B;m+=B;d+=B;p[0]+=B}var J=Math.min(h,c);var H=Math.min(q[1],p[1]);var w=Math.min(J,H);var C=Math.max(h,c);var z=Math.max(q[1],p[1]);var u=Math.max(C,z);if(u>k){k=u}if(w<0){e+=w;var y=Math.abs(w);k+=y;q[1]+=y;h+=y;c+=y;p[1]+=y}if(G&&o1?q:{x:s[0].x+((s[1].x-s[0].x)/2),y:s[0].y+((s[1].y-s[0].y)/2)};var A=p.x-l.x,x=p.y-l.y;q.x+=A;q.y+=x;k.x+=A;k.y+=x;s[0].x+=A;s[0].y+=x;s[1].x+=A;s[1].y+=x;r.x+=A;r.y+=x}var w=Math.min(r.x,s[0].x,s[1].x);var u=Math.max(r.x,s[0].x,s[1].x);var v=Math.min(r.y,s[0].y,s[1].y);var t=Math.max(r.y,s[0].y,s[1].y);z.lineWidth=e.lineWidth;z.beginPath();z.moveTo(r.x,r.y);z.lineTo(s[0].x,s[0].y);z.lineTo(q.x,q.y);z.lineTo(s[1].x,s[1].y);z.lineTo(r.x,r.y);z.closePath();if(e.strokeStyle){z.strokeStyle=e.strokeStyle;z.stroke()}z.fillStyle=e.fillStyle||y.strokeStyle;z.fill();return[w,u,v,t]}};jsPlumb.Overlays.PlainArrow=function(d){d=d||{};var c=jsPlumb.extend(d,{foldback:1});jsPlumb.Overlays.Arrow.call(this,c)};jsPlumb.Overlays.Diamond=function(e){e=e||{};var c=e.length||40;var d=jsPlumb.extend(e,{length:c/2,foldback:2});jsPlumb.Overlays.Arrow.call(this,d)};jsPlumb.Overlays.Label=function(l){this.labelStyle=l.labelStyle||jsPlumb.Defaults.LabelStyle;this.label=l.label;this.connection=l.connection;var d=this;var c=null,k=null,f=null,g=null;this.location=l.location||0.5;this.cachedDimensions=null;var h=function(n){if(d.cachedDimensions){return d.cachedDimensions}f=typeof d.label=="function"?d.label(d):d.label;var r={};if(f){var m=f.split(/\n|\r\n/);n.save();if(d.labelStyle.font){n.font=d.labelStyle.font}var o=e(m,n);var p=n.measureText("M").width;g=d.labelStyle.padding||0.25;c=o+(2*o*g);k=(m.length*p)+(2*p*g);var q=m.length*p;n.restore();r={width:c,height:k,lines:m,oneLine:p,padding:g,textHeight:q}}if(typeof d.label!="function"){d.cachedDimensions=r}return r};this.computeMaxSize=function(n,m){var o=h(m);return o.width?Math.max(o.width,o.height)*1.5:0};var e=function(o,n){var m=0;for(var q=0;qm){m=p}}return m};this.draw=function(p,o,q){var s=h(o);if(s.width){var r=p.pointOnPath(d.location);if(d.labelStyle.font){o.font=d.labelStyle.font}if(d.labelStyle.fillStyle){o.fillStyle=d.labelStyle.fillStyle}else{o.fillStyle="rgba(0,0,0,0)"}var n=r.x-(s.width/2);var m=r.y-(s.height/2);o.fillRect(n,m,s.width,s.height);if(d.labelStyle.color){o.fillStyle=d.labelStyle.color}o.textBaseline="middle";o.textAlign="center";for(i=0;i0){o.strokeStyle=d.labelStyle.borderStyle||"black";o.strokeRect(n,m,s.width,s.height)}return[n,n+s.width,m,m+s.height]}else{return[0,0,0,0]}}};jsPlumb.Overlays.Image=function(e){var l=this;this.location=e.location||0.5;this.img=new Image();this.connection=e.connection;var m=null;var f=null;var d,c;var k=e.events||{};var h=function(){if(l.ready){window.clearInterval(f);m=document.createElement("img");m.src=l.img.src;m.style.position="absolute";m.style.display="none";m.className="_jsPlumb_overlay";document.body.appendChild(m);for(var n in k){jsPlumb.CurrentLibrary.bind(m,n,k[n])}if(d&&c){g(d,c);c=null;d=null}}};this.img.onload=function(){l.ready=true};this.img.src=e.src||e.url;f=window.setInterval(h,250);this.computeMaxSize=function(o,n){return[l.img.width,l.img.height]};var g=function(q,v,u){if(m!=null){var r=q.pointOnPath(l.location);var p=jsPlumb.CurrentLibrary.getElementObject(v.canvas);var w=jsPlumb.CurrentLibrary.getOffset(p);var t=r.x-(l.img.width/2);var s=r.y-(l.img.height/2);var n={left:w.left+t,top:w.top+s};jsPlumb.CurrentLibrary.setOffset(m,n);m.style.display="block";return[t,t+l.img.width,s,s+l.img.height]}};this.draw=function(o,n){if(l.ready){return g(o,n)}else{d=o;c=n;return[0,0,0,0]}}}})();(function(){jsPlumb.Connectors.Flowchart=function(f){f=f||{};var o=this,b=f.minStubLength||30,k=[],h=[],m=[],g=[],a=[],n=[],d,c,q=function(u,t,B,A){var y=0;for(var s=0;s=t){r=u;s=(t-m[u][0])/a[u];break}}return{segment:k[r],proportion:s,index:r}};this.compute=function(u,J,F,r,C,s){k=[];h=[];a=[];g=[];segmentProportionals=[];d=J[0]u[0]?u[0]+((1-y)*t)-z:u[2]+(y*t)+z,y:r==0?u[3]:u[3]>u[1]?u[1]+((1-y)*t)-z:u[3]+(y*t)+z,segmentInfo:w};return x};this.perpendicularToPathAt=function(u,v,A){var w=o.pointAlongPathFrom(u,A);var t=h[w.segmentInfo.index];var s=Math.atan(-1/t);var z=v/2*Math.sin(s);var r=v/2*Math.cos(s);return[{x:w.x+r,y:w.y+z},{x:w.x-r,y:w.y-z}]}}})();(function(){if(!Array.prototype.indexOf){Array.prototype.indexOf=function(w,t,y){for(var x=+t||0,u=this.length;x0?1:-1}}var b={subtract:function(n,m){return{x:n.x-m.x,y:n.y-m.y}},dotProduct:function(n,m){return n.x*m.x+n.y*m.y},square:function(m){return Math.sqrt(m.x*m.x+m.y*m.y)},scale:function(n,m){return{x:n.x*m,y:n.y*m}}},d=Math.pow(2,-65),h=function(y,x){for(var s=[],v=x.length-1,r=2*v-1,t=[],w=[],o=[],p=[],q=[[1,0.6,0.3,0.1],[0.4,0.6,0.6,0.4],[0.1,0.3,0.6,1]],u=0;u<=v;u++){t[u]=b.subtract(x[u],y)}for(u=0;u<=v-1;u++){w[u]=b.subtract(x[u+1],x[u]);w[u]=b.scale(w[u],3)}for(u=0;u<=v-1;u++){for(var n=0;n<=v;n++){o[u]||(o[u]=[]);o[u][n]=b.dotProduct(w[u],t[n])}}for(u=0;u<=r;u++){p[u]||(p[u]=[]);p[u].y=0;p[u].x=parseFloat(u)/r}r=v-1;for(t=0;t<=v+r;t++){w=Math.min(t,v);for(u=Math.max(0,t-r);u<=w;u++){j=t-u;p[u+j].y+=o[j][u]*q[j][u]}}v=x.length-1;p=l(p,2*v-1,s,0);r=b.subtract(y,x[0]);o=b.square(r);for(u=q=0;u=64){y[0]=(E[0].x+E[D].x)/2;return 1}var s,r,p;v=E[0].y-E[D].y;w=E[D].x-E[0].x;A=E[0].x*E[D].y-E[D].x*E[0].y;t=max_distance_below=0;for(r=1;rt){t=p}else{if(p0?1:-1,s=null;o 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + if (!ie) { + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + else { + // need to get fancy with the vml. + } + } + return false; + }; + + /* + * Binds a listener to an event. + * + * event - name of the event to bind to. + * listener - function to execute. + */ + this.bind = function(event, listener) { + _addToList(_listeners, event, listener); + }; + /* + * Fires an update for the given event. + * + * event - event to fire + * value - value to pass to the event listener(s). + * originalEvent - the original event from the browser + */ + this.fireUpdate = function(event, value, originalEvent) { + if (_listeners[event]) { + for ( var i = 0; i < _listeners[event].length; i++) { + try { + _listeners[event][i](value, originalEvent); + } catch (e) { + _log(self, "jsPlumb: fireUpdate failed for event " + + event + " : " + e + "; not fatal."); + } + } + } + }; + /* + * Clears either all listeners, or listeners for some specific event. + * + * event - optional. constrains the clear to just listeners for this event. + */ + this.clearListeners = function(event) { + if (event) { + delete _listeners[event]; + } else { + delete _listeners; + _listeners = {}; + } + }; + + var _mouseover = false; + var _mouseDown = false, _mouseDownAt = null, _posWhenMouseDown = null, _mouseWasDown = false, srcWhenMouseDown = null, + targetWhenMouseDown = null; + this.mousemove = function(e) { + var jpcl = jsPlumb.CurrentLibrary; + var pageXY = jpcl.getPageXY(e); + var ee = document.elementFromPoint(pageXY[0], pageXY[1]); + var _continue = _connectionBeingDragged == null && (_hasClass(ee, "_jsPlumb_endpoint") || _hasClass(ee, "_jsPlumb_connector")); + + if (_mouseDown && srcWhenMouseDown) { + _mouseWasDown = true; + _connectionBeingDragged = self; + var mouseNow = jpcl.getPageXY(e); + var dx = mouseNow[0] - _mouseDownAt[0]; + var dy = mouseNow[1] - _mouseDownAt[1]; + var newPos = {left:srcWhenMouseDown.left + dx, top:srcWhenMouseDown.top + dy}; + jpcl.setOffset(jpcl.getElementObject(self.source), newPos); + jsPlumb.repaint(self.source); + newPos = {left:targetWhenMouseDown.left + dx, top:targetWhenMouseDown.top + dy}; + jpcl.setOffset(jpcl.getElementObject(self.target), newPos); + jsPlumb.repaint(self.target); + } + else if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.setHover(_mouseover); + self.fireUpdate("mouseenter", self, e); + } + else if (_mouseover && (!self._over(e) || !_continue)) { + _mouseover = false; + /* if (self.hoverPaintStyle != null) { + self.paintStyleInUse = self.paintStyle; + self.repaint(); + _updateAttachedElements(); + }*/ + self.setHover(_mouseover); + self.fireUpdate("mouseexit", self, e); + } + }; + + /** + * sets/unsets the hover state of this element. + */ + this.setHover = function(hover, ignoreAttachedElements) { + if (self.hoverPaintStyle != null) { + self.paintStyleInUse = hover ? self.hoverPaintStyle : self.paintStyle; + self.repaint(); + // get the list of other affected elements. for a connection, its the endpoints. for an endpoint, its the connections! surprise. + if (!ignoreAttachedElements) + _updateAttachedElements(hover); + } + }; + + var _updateAttachedElements = function(state) { + var affectedElements = self.getAttachedElements(); // implemented in subclasses + if (affectedElements) { + for (var i = 0; i < affectedElements.length; i++) { + affectedElements[i].setHover(state, true); // tell the attached elements not to inform their own attached elements. + } + } + }; + + this.click = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fireUpdate("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fireUpdate("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _mouseDownAt = jsPlumb.CurrentLibrary.getPageXY(e); + if (self.canvas) _posWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.canvas)); + if (self.source) srcWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.source)); + if (self.target) targetWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.target)); + } + }; + + this.mouseup = function() { + if (self == _connectionBeingDragged) _connectionBeingDragged = null; + _mouseDown = false; + }; + + /* + * Sets the paint style and then repaints the element. + * + * style - Style to use. + */ + this.setPaintStyle = function(style) { + self.paintStyle = style; + self.paintStyleInUse = self.paintStyle; + self.repaint(); + }; + + /* + * Sets the paint style to use when the mouse is hovering over the element. This is null by default. + * + * style - Style to use when the mouse is hovering. + */ + this.setHoverPaintStyle = function(style) { + self.hoverPaintStyle = style; + self.repaint(); + }; + }; + + /** + * Class:jsPlumb + * The main jsPlumb class. One of these is registered on the Window as 'jsPlumb'; you can also get one yourself by making a call to jsPlumb.getInstance. This class is the class you use to establish Connections and add Endpoints. + */ + var jsPlumbInstance = function(_defaults) { + + /* + * Property: Defaults + * + * These are the default settings for jsPlumb. They are what will be used if you do not supply specific pieces of information to the various API calls. A convenient way to implement your own look and feel can be to override these defaults by including a script somewhere after the jsPlumb include, but before you make any calls to jsPlumb, for instance in this example we set the PaintStyle to be a blue line of 27 pixels: > jsPlumb.Defaults.PaintStyle = { lineWidth:27, strokeStyle:'blue' } + */ + this.Defaults = { + Anchor : null, + Anchors : [ null, null ], + BackgroundPaintStyle : null, + Connector : null, + Container : null, + DragOptions : { }, + DropOptions : { }, + Endpoint : null, + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : null }, + EndpointStyles : [ null, null ], + EndpointHoverStyle : null, + EndpointHoverStyles : [ null, null ], + HoverPaintStyle : null, + LabelStyle : { fillStyle : "rgba(0,0,0,0)", color : "black" }, + LogEnabled : true, + MaxConnections : null, + MouseEventsEnabled : false, + // TODO: should we have OverlayStyle too? + PaintStyle : { lineWidth : 10, strokeStyle : 'red' }, + Scope : "_jsPlumb_DefaultScope" + }; + if (_defaults) jsPlumb.extend(this.Defaults, _defaults); + + this.logEnabled = this.Defaults.LogEnabled; + + + EventGenerator.apply(this); + var _bb = this.bind; + this.bind = function(event, fn) { + if ("ready" === event && initialized) fn(); + else _bb(event, fn); + }; + + var _currentInstance = this; + var log = null; + + var repaintFunction = function() { + jsPlumb.repaintEverything(); + }; + var automaticRepaint = true; + function repaintEverything() { + if (automaticRepaint) + repaintFunction(); + }; + var resizeTimer = null; + + var initialized = false; + var connectionsByScope = {}; + /** + * map of element id -> endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + var endpointsByElement = {}; + var endpointsByUUID = {}; + var offsets = {}; + var offsetTimestamps = {}; + var floatingConnections = {}; + var draggableStates = {}; + var _mouseEventsEnabled = this.Defaults.MouseEventsEnabled; + var _draggableByDefault = true; + var canvasList = []; + var sizes = []; + var listeners = {}; // a map: keys are event types, values are lists of listeners. + var DEFAULT_SCOPE = 'DEFAULT'; + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + var _findIndex = function(a, v, b, s) { + var _eq = function(o1, o2) { + if (o1 === o2) + return true; + else if (typeof o1 == 'object' && typeof o2 == 'object') { + var same = true; + for ( var propertyName in o1) { + if (!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for ( var propertyName in o2) { + if (!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for ( var i = +b || 0, l = a.length; i < l; i++) { + if (_eq(a[i], v)) + return i; + } + return -1; + }; + + /** + * appends an element to the given parent, or the document body if no + * parent given. + */ + var _appendElement = function(canvas, parent) { + if (!parent) + document.body.appendChild(canvas); + else + jsPlumb.CurrentLibrary.appendElement(canvas, parent); + }; + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + var _timestamp = function() { return "" + (new Date()).getTime(); }; + + /** + * Draws an endpoint and its connections. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + var _draw = function(element, ui, timestamp) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (!timestamp) timestamp = _timestamp(); + if (endpoints) { + _updateOffset( { elId : id, offset : ui, recalc : false, timestamp : timestamp }); // timestamp is checked against last update cache; it is + // valid for one paint cycle. + var myOffset = offsets[id], myWH = sizes[id]; + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + var l = endpoints[i].connections; + for ( var j = 0; j < l.length; j++) { + l[j].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); // ...paint each connection. + // then, check for dynamic endpoint; need to repaint it. + var oIdx = l[j].endpoints[0] == endpoints[i] ? 1 : 0; + if (l[j].endpoints[oIdx].anchor.isDynamic && !l[j].endpoints[oIdx].isFloating()) { + var oId = oIdx == 0 ? l[j].sourceId : l[j].targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + // TODO i still want to make this faster. + var anchorLoc = l[j].endpoints[oIdx].anchor.compute( { + xy : [ oOffset.left, oOffset.top ], + wh : oWH, + element : l[j].endpoints[oIdx], + txy : [ myOffset.left, myOffset.top ], + twh : myWH, + tElement : endpoints[i] + }); + l[j].endpoints[oIdx].paint({ anchorLoc : anchorLoc }); + } + } + } + } + }; + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + var _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }; + + /** + * gets an Endpoint by uuid. + */ + var _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }; + + /** + * inits a draggable if it's not already initialised. + */ + var _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element) && !jsPlumb.CurrentLibrary.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + } + } + }; + + /** + * helper to create a canvas. + * + */ + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + _appendElement(canvas, params.container); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + _getId(canvas, params.uuid); + if (ie) { + // for IE we have to set a big canvas size. actually you can + // override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas + // you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + + var _newConnection = function(params) { + var connectionFunc = jsPlumb.Defaults.ConnectionType || Connection; + return new connectionFunc(params); + }; + + var _newEndpoint = function(params) { + var endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint; + return new endpointFunc(params); + }; + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + var _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }; + /** + * perform an operation on all elements. + */ + var _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }; + + /** + * helper to remove an element from the DOM. + */ + var _removeElement = function(element, parent) { + if (element != null) { + if (!parent) { + try { + document.body.removeChild(element); + } catch (e) { + } + } else { + jsPlumb.CurrentLibrary.removeElement(element, parent); + } + } + }; + /** + * helper to remove a list of elements from the DOM. + */ + var _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }; + /** + * helper method to remove an item from a list. + */ + var _removeFromList = function(map, key, value) { + if (key != null) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete (l[i]); + l.splice(i, 1); + return true; + } + } + } + return false; + }; + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }; + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + var _setVisible = function(el, state) { + _operation(_getAttribute(el, "id"), function(jpc) { + jpc.canvas.style.display = state; + }); + }; + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + var _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }; + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + var _toggleVisible = function(elId) { + _operation(elId, function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }; + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + var _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return; + } + if (recalc || offset == null) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + } + }; + +/** + * gets an id for the given element, creating and setting one if + * necessary. + */ + var _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else + id = "jsPlumb_" + _timestamp(); + _setAttribute(ele, "id", id); + } + return id; + }; + +/** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + var _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'jsPlumb function failed : ' + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'wrapped function failed : ' + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = '_jsPlumb_connector'; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = '_jsPlumb_endpoint'; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = '_jsPlumb_overlay'; + + /* + * Property: Anchors + * Default jsPlumb Anchors. These are supplied in the file jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script to form .jsPlumb-all-x.x.x.js. You can + * provide your own Anchors by supplying them in a script that is loaded after jsPlumb, for instance: > jsPlumb.Anchors.MyAnchor = { ....anchor code here. see the documentation. } + */ + this.Anchors = {}; + + /* + * Property: Connectors + * Default jsPlumb Connectors. These are supplied + * in the file jsPlumb-defaults-x.x.x.js, which is merged in with the + * main jsPlumb script to form .jsPlumb-all-x.x.x.js. You can + * provide your own Connectors by supplying them in a script that is + * loaded after jsPlumb, for instance: > jsPlumb.Connectors.MyConnector = { + * ....connector code here. see the documentation. } + */ + this.Connectors = {}; + + /* + * Property: Endpoints + * Default jsPlumb Endpoints. These are supplied in + * the file jsPlumb-defaults-x.x.x.js, which is merged in with the main + * jsPlumb script to form .jsPlumb-all-x.x.x.js. You can + * provide your own Endpoints by supplying them in a script that is + * loaded after jsPlumb, for instance: > jsPlumb.Endpoints.MyEndpoint = { + * ....endpoint code here. see the documentation. } + */ + this.Endpoints = {}; + + /* + * Property:Overlays + * Default jsPlumb Overlays such as Arrows and Labels. + * These are supplied in the file jsPlumb-defaults-x.x.x.js, which is + * merged in with the main jsPlumb script to form + * .jsPlumb-all-x.x.x.js. You can provide your own Overlays by + * supplying them in a script that is loaded after jsPlumb, for + * instance: > jsPlumb.Overlays.MyOverlay = { ....overlay code here. see + * the documentation. } + */ + this.Overlays = {}; + + /* + Function: addEndpoint + + Adds an Endpoint to a given element. + + Parameters: + + target - Element to add the endpoint to. either an element id, or a selector representing some element. + params - Object containing Endpoint options. For more information, see the docs for Endpoint's constructor. + referenceParams - Object containing more Endpoint options; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + The newly created Endpoint. + + See Also: + + */ + this.addEndpoint = function(target, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.endpointStyle = p.endpointStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + var el = _getElementObject(target), id = _getAttribute(el, "id"); + p.source = el; + _updateOffset({ elId : id }); + var e = _newEndpoint(p); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + return e; + }; + + /* + Function: addEndpoints + Adds a list of Endpoints to a given element. + + Parameters: + target - element to add the endpoint to. either an element id, or a selector representing some element. + endpoints - List of objects containing Endpoint options. one Endpoint is created for each entry in this list. + referenceParams - Object containing more Endpoint options; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created Endpoints, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(target, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + results.push(_currentInstance.addEndpoint(target, endpoints[i], referenceParams)); + } + return results; + }; + + /* + Function: animate + Wrapper around supporting library's animate function; injects a call to jsPlumb in the 'step' function (creating + it if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // probably, onComplete should repaint too. that will help + // keep up + // with fast animations. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /* + Function: connect + Establishes a connection between two elements (or Endpoints, which are themselves registered to elements). + + Parameters: + params - Object containing setup for the connection. See docs for Connection's constructor. + referenceParams - Optional object containing more params for the connection. Typically you would pass in data that a lot of connections are sharing here, such as connector style etc, and then use the main params for data specific to this connection. + + Returns: + The newly created Connection. + */ + this.connect = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not + // full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + // dynamic anchors. backwards compatibility here: from 1.2.6 onwards you don't need to specify "dynamicAnchors". the fact that some anchor consists + // of multiple definitions is enough to tell jsPlumb you want it to be dynamic. + if (_p.dynamicAnchors) { + // these can either be an array of anchor coords, which we will use for both source and target, or an object with {source:[anchors], target:[anchors]}, in which + // case we will use a different set for each element. + var a = _p.dynamicAnchors.constructor == Array; + var sa = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.source)); + var ta = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.target)); + _p.anchors = [sa,ta]; + } + + var jpc = _newConnection(_p); + // add to list of connections (by scope). + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + _currentInstance.fireUpdate("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + // force a paint + _draw(jpc.source); + + return jpc; + }; + + /* + Function: deleteEndpoint + Deletes an endpoint and removes all connections it has (which removes the connections from the other endpoints involved too) + + Parameters: + object - either an Endpoint object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElement(endpoint.canvas, endpoint.container); + // remove from endpointsbyElement + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + delete endpoint; + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every Endpoint, and their associated Connections, in this instance of jsPlumb. Do not unregister event listener (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc) { + _currentInstance.fireUpdate("jsPlumbConnectionDetached", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + }; + + /* + Function: detach + Removes a connection. takes either (source, target) (the old way, maintained for backwards compatibility), or a params + object with various possible values. + + Parameters: + source - id or element object of the first element in the connection. + target - id or element object of the second element in the connection. + params - a JS object containing the same parameters as you pass to jsPlumb.connect. If this is present then neither source nor + target should be present; it should be the only argument to the method. See the docs for Connection's constructor for information +about the parameters allowed in the params object. + Returns: + true if successful, false if not. + */ + this.detach = function(source, target) { + if (arguments.length == 2) { + var s = _getElementObject(source), sId = _getId(s); + var t = _getElementObject(target), tId = _getId(t); + _operation(sId, function(jpc) { + if ((jpc.sourceId == sId && jpc.targetId == tId) || (jpc.targetId == sId && jpc.sourceId == tId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + // this is the new version of the method, taking a JS object like + // the connect method does. + else if (arguments.length == 1) { + // TODO investigate whether or not this code still works when a user has supplied their own subclass of Connection. i suspect it may not. + if (arguments[0].constructor == Connection) { + //arguments[0].setHover(false); + arguments[0].endpoints[0].detachFrom(arguments[0].endpoints[1]); + } + else if (arguments[0].connection) { + //arguments[0].connection.setHover(false); + arguments[0].connection.endpoints[0].detachFrom(arguments[0].connection.endpoints[1]); + } + else { + var _p = jsPlumb.extend( {}, source); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1])); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source); + var targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas, jpc.container); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + } + } + }; + + /* + Function: detachAll + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + + Returns: + void + */ + this.detachAllConnections = function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + }; + + /** + * @deprecated Use detachAllConnections instead. this will be removed in jsPlumb 1.3. + */ + this.detachAll = this.detachAllConnections; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + /** + * @deprecated use detachEveryConnection instead. this will be removed in jsPlumb 1.3. + */ + this.detachEverything = this.detachEveryConnection; + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of you library's draggable method so that jsPlumb can setup the appropriate callbacks. Your underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. + * Parameters: + * options - a JS object that holds options defining what sort of connections you're + * looking for. Valid values are: scope this may be a String or a list + * of Strings. jsPlumb will only return Connections whose scope matches + * what this option defines. If you omit it, you will be given + * Connections of every scope. source may be a string or a list of + * strings; constraints results to have only Connections whose source is + * this object. target may be a string or a list of strings; constraints + * results to have only Connections whose target is this object. + * + * The return value is a dictionary in this format: + * { 'scope1': [ {sourceId:'window1', targetId:'window2', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:}, + * {sourceId:'window3', targetId:'window4', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:}, + * {sourceId:'window1', targetId:'window3', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:} ], + * 'scope2': [ {sourceId:'window1', targetId:'window3', source:, + * target:, sourceEndpoint:, + * targetEndpoint:, connection:} ] } + * + */ + this.getConnections = function(options) { + var r = {}; + options = options || {}; + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') + r.push(input); + else + r = input; + } + return r; + }; + var scopes = prepareList(options.scope); + var sources = prepareList(options.source); + var targets = prepareList(options.target); + var filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + r[i] = []; + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + r[i].push({ sourceId : c.sourceId, targetId : c.targetId, source : c.source, target : c.target, sourceEndpoint : c.endpoints[0], targetEndpoint : c.endpoints[1], connection : c }); + } + } + } + return r; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + + Returns: + void + */ + this.hide = function(el) { + _setVisible(el, "none"); + }; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + var _bind = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && _mouseEventsEnabled) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + if (c[i][event](e)) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i][event](e)) return; + } + } + } + }); + }; + _bind("click"); + _bind("dblclick"); + _bind("mousemove"); + _bind("mousedown"); + _bind("mouseup"); + + initialized = true; + _currentInstance.fireUpdate("ready"); + }; + + /* + * Creates an anchor with the given params. + * + * You do not need to use this method. It is exposed because of the way jsPlumb is + * split into three scripts; this will change in the future. + * + * x - the x location of the anchor as a fraction of the + * total width. y - the y location of the anchor as a fraction of the + * total height. xOrientation - value indicating the general direction a + * connection from the anchor should go in, in the x direction. + * yOrientation - value indicating the general direction a connection + * from the anchor should go in, in the y direction. xOffset - a fixed + * offset that should be applied in the x direction that should be + * applied after the x position has been figured out. optional. defaults + * to 0. yOffset - a fixed offset that should be applied in the y + * direction that should be applied after the y position has been + * figured out. optional. defaults to 0. + * -- OR -- + * + * params - {x:..., y:..., xOrientation etc } + * -- OR FROM 1.2.4 --- + * + * name - the name of some Anchor in the _currentInstance.Anchors array. + * -- OR FROM 1.2.4 --- + * + * coords - a list of coords for the anchor, like you would pass to + * jsPlumb.makeAnchor (eg [0.5,0.5,0,-1] - an anchor in the center of + * some element, oriented towards the top of the screen) + * -- OR FROM 1.2.4 --- + * + * anchor - an existing anchor. just gets passed back. it's handy + * internally to have this functionality. + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed + // in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards + // compatibility if we are given only one value we assume it's a + // call in the old form. + if (arguments.length == 0) return null; + var params = {}; + if (arguments.length == 1) { + var specimen = arguments[0]; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; + // is it the name of an anchor type? + else if (typeof specimen == "string") return _currentInstance.Anchors[arguments[0]](); + // is it an array of coordinates? + else if (specimen.constructor == Array) { + if (specimen[0].constructor == Array || specimen[0].constructor == String) + return new DynamicAnchor(specimen); + else + return jsPlumb.makeAnchor.apply(this, specimen); + } + // last we try the backwards compatibility stuff. + else if (typeof arguments[0] == "object") jsPlumb.extend(params, x); + } else { + params = { x : x, y : y }; + if (arguments.length >= 4) params.orientation = [ arguments[2], arguments[3] ]; + if (arguments.length == 6) params.offsets = [ arguments[4], arguments[5] ]; + } + var a = new Anchor(params); + a.clone = function() { + return new Anchor(params); + }; + return a; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types) { + var r = []; + for ( var i = 0; i < types.length; i++) + if (typeof types[i] == "string") + r.push(_currentInstance.Anchors[types[i]]()); + else if (types[i].constructor == Array) + r.push(jsPlumb.makeAnchor(types[i])); + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + for ( var i in ebe) + _currentInstance.deleteEndpoint(ebe[i]); + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + this.deleteEveryEndpoint(); + this.clearListeners(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + + Parameters: + value - whether or not to automatically repaint when the window is resized. + + Returns: void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + * Function: setDefaultNewCanvasSize + * Sets the default size jsPlumb will use for a new canvas (we create a square canvas so one value is all + * that is required). This is a hack for IE, because ExplorerCanvas + * seems to need for a canvas to be larger than what you are going to + * draw on it at initialisation time. The default value of this is 1200 + * pixels, which is quite large, but if for some reason you're drawing + * connectors that are bigger, you should adjust this value + * appropriately. + * + * Parameters: + * size - The default size to use. jsPlumb will use a square canvas so you need only supply one value. + * + * Returns: + * void + */ + this.setDefaultNewCanvasSize = function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }; + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setDraggableByDefault + * Sets whether or not elements are draggable by default. Default for this is true. + * + * Parameters: + * draggable - value to set + * + * Returns: + * void + */ + this.setDraggableByDefault = function(draggable) { + _draggableByDefault = draggable; + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction + * Sets the function to fire when the window size has changed and a repaint was fired. + * + * Parameters: + * f - Function to execute. + * + * Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: setMouseEventsEnabled + * Sets whether or not mouse events are enabled. By default they are not; this is just because jsPlumb has to add mouse listeners + * to the document, which may result in a performance hit a user does not need. + * + * Parameters: + * enabled - whether or not mouse events should be enabled. + * + * Returns: + * void + */ + this.setMouseEventsEnabled = function(enabled) { + _mouseEventsEnabled = enabled; + }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * + * Returns: + * void + */ + this.show = function(el) { + _setVisible(el, "block"); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload + * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element. + * + * Returns: + * void + */ + this.unload = function() { + delete endpointsByElement; + delete endpointsByUUID; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + delete canvasList; + }; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + if (timestamp && timestamp === self.timestamp) { + return lastReturnValue; + } + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + var container = element ? element.container : null; + var containerAdjustment = { left : 0, top : 0 }; + if (container != null) { + var eo = _getElementObject(container); + var o = _getOffset(eo); + var sl = jsPlumb.CurrentLibrary.getScrollLeft(eo); + var st = jsPlumb.CurrentLibrary.getScrollTop(eo); + containerAdjustment.left = o.left - sl; + containerAdjustment.top = o.top - st; + lastReturnValue[0] = lastReturnValue[0] - containerAdjustment.left; + lastReturnValue[1] = lastReturnValue[1] - containerAdjustment.top; + } + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(_getElementObject(refCanvas)); + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + var orientation = null; + var _lastResult = null; + + this.compute = function(params) { + var xy = params.xy, el = params.element; + var result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + if (el.container != null) { + var o = _getOffset(el.container); + result[0] = result[0] - o.left; + result[1] = result[1] - o.top; + } + _lastResult = result; + return result; + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { orientation = anchor.getOrientation(); }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchors is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = anchors || []; + var _convert = function(anchor) { return anchor.constructor == Anchor ? anchor: jsPlumb.makeAnchor(anchor); }; + for (var i = 0; i < _anchors.length; i++) _anchors[i] = _convert(_anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null; + var _curIndex = _anchors.length > 0 ? 0 : -1; + this.locked = false; + var self = this; + + // helper method to calculate the distance between the centers of the two elements. + var _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }; + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + var _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + + var pos = _curAnchor.compute(params); + return pos; + }; + + this.getCurrentLocation = function() { + var cl = _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + return cl; + }; + + this.getOrientation = function() { return _curAnchor != null ? _curAnchor.getOrientation() : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * container - id of the containing div for this connection. optional; jsPlumb uses the default (which you can set, but which is the body by default) otherwise. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * backgroundPaintStyle - Parameters defining the appearance of the background of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * overlays - Optional array of Overlay definitions to appear on this Connection. + */ + var Connection = function(params) { + + EventGenerator.apply(this); + // ************** get the source and target and register the connection. ******************* + var self = this; + var visible = true; + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + }; + var id = new String('_jsplumb_c_' + (new Date()).getTime()); + this.getId = function() { return id; }; + this.container = params.container || _currentInstance.Defaults.Container; // may be null; we will append to the body if so. + // get source and target as jQuery objects + /** + Property: source + The source element for this Connection. + */ + this.source = _getElementObject(params.source); + /** + Property:target + The target element for this Connection. + */ + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. + if (params.sourceEndpoint) this.source = params.sourceEndpoint.getElement(); + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + /* + * Property: sourceId + * Id of the source element in the connection. + */ + this.sourceId = _getAttribute(this.source, "id"); + /* + * Property: targetId + * Id of the target element in the connection. + */ + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + this.getAttachedElements = function() { + return self.endpoints; + }; + + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams) { + if (anchorParams) + return jsPlumb.makeAnchor(anchorParams); + }; + var prepareEndpoint = function(existing, index, params, element) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint + || new jsPlumb.Endpoints.Dot(); + if (ep.constructor == String) + ep = new jsPlumb.Endpoints[ep](); + else if (ep.constructor == Array) { + ep = new jsPlumb.Endpoints[ep[0]](ep[1]); + } + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + var a = params.anchors ? params.anchors[index] : _makeAnchor(_currentInstance.Defaults.Anchors[index]) || _makeAnchor(jsPlumb.Defaults.Anchors[index]) || _makeAnchor(_currentInstance.Defaults.Anchor) || _makeAnchor(jsPlumb.Defaults.Anchor) || _makeAnchor("BottomCenter"); + var u = params.uuids ? params.uuids[index] : null; + var e = _newEndpoint( { paintStyle : es, hoverPaintStyle:ehs, endpoint : ep, connections : [ self ], uuid : u, anchor : a, source : element, container : self.container }); + self.endpoints[index] = e; + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector) + */ + this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || _currentInstance.Defaults.Connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + if (this.connector.constructor == String) + this.connector = new jsPlumb.Connectors[this.connector](); // lets you use a string as shorthand. + else if (this.connector.constructor == Array) + this.connector = new jsPlumb.Connectors[this.connector[0]](this.connector[1]); + this.paintStyle = this.endpoints[0].connectorStyle || this.endpoints[1].connectorStyle || params.paintStyle || _currentInstance.Defaults.PaintStyle || jsPlumb.Defaults.PaintStyle; + var backgroundPaintStyle = this.endpoints[0].connectorBackgroundStyle || this.endpoints[1].connectorBackgroundStyle || params.backgroundPaintStyle || _currentInstance.Defaults.BackgroundPaintStyle || jsPlumb.Defaults.BackgroundPaintStyle; + this.hoverPaintStyle = this.endpoints[0].connectorHoverStyle || this.endpoints[1].connectorHoverStyle || params.hoverPaintStyle || _currentInstance.Defaults.HoverPaintStyle || jsPlumb.Defaults.HoverPaintStyle; + this.paintStyleInUse = this.paintStyle; + + /* + * Property: overlays + * List of Overlays for this Connection. + */ + this.overlays = []; + if (params.overlays) { + for (var i = 0; i < params.overlays.length; i++) { + var o = params.overlays[i]; + if (o.constructor == Array) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0]; + var p = jsPlumb.CurrentLibrary.extend({connection:self}, o[1]); // make a copy of the object so as not to mess up anyone else's reference... + if (o.length == 3) jsPlumb.CurrentLibrary.extend(p, o[2]); + this.overlays.push(new jsPlumb.Overlays[type](p)); + } else if (o.constructor == String) { + this.overlays.push(new jsPlumb.Overlays[o]({connection:self})); + } + else this.overlays.push(o); + } + } + var overlayPlacements = []; + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + this.addOverlay = function(overlay) { overlays.push(overlay); }; + + // this is a shortcut helper method to let people add a label as + // overlay. + this.labelStyle = params.labelStyle || _currentInstance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + if (this.label) { + this.overlays.push(new jsPlumb.Overlays.Label( { + labelStyle : this.labelStyle, + label : this.label, + connection:self + })); + } + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String or a Function that returns a String. + */ + this.setLabel = function(l) { + self.label = l; + _currentInstance.repaint(self.source); + }; + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var otherOffset = offsets[this.targetId]; + var otherWH = sizes[this.targetId]; + var anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1] + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0] + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc }); + + var canvas = _newCanvas({"class":jsPlumb.connectorClass, container:self.container}); + this.canvas = canvas; + + /* + * Function: setBackgroundPaintStyle + * Sets the Connection's background paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + this.setBackgroundPaintStyle = function(style) { + backgroundPaintStyle = style; + self.repaint(); + }; + + /* + * Paints the connection. + * + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp; + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = false; + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + if (this.canvas.getContext) { + _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }); + _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + var ctx = canvas.getContext('2d'); + var sAnchorP = this.endpoints[sIdx].anchor.getCurrentLocation(); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.getCurrentLocation(); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + + // paint overlays + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + var s = o.computeMaxSize(self.connector, ctx); + if (s > maxSize) + maxSize = s; + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, self.paintStyleInUse.lineWidth, maxSize); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + + var _paintOneStyle = function(ctx, aStyle) { + ctx.save(); + jsPlumb.extend(ctx, aStyle); + if (aStyle.gradient && !ie) { + var g = self.connector.createGradient(dim, ctx, (elId == this.sourceId)); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + self.connector.paint(dim, ctx); + ctx.restore(); + }; + + // first check for the background style + if (backgroundPaintStyle != null) { + _paintOneStyle(ctx, backgroundPaintStyle); + } + _paintOneStyle(ctx, self.paintStyleInUse); + + // paint overlays + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + self.overlayPlacements[i] = o.draw(self.connector, ctx, self.paintStyleInUse); + } + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function() { + this.paint({ elId : this.sourceId, recalc : true }); + }; + + _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide + // whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + + //_registerConnection(self); + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have one to N connections emanating from it (although how to handle that in the UI is a very good question). also has a Canvas and paint style. + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * connections - optional list of Connections to configure the Endpoint with. + * container - optional element (as a string id) to use as the container for the canvas associated with this Endpoint. If not supplied, jsPlumb uses the default, which is the document body. A better way to use this container functionality is to set it on the defaults (jsPlumb.Defaults.Container="someElement"). + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorBackgroundStyle - if isSource is set to true, this is the background paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", 160 ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + EventGenerator.apply(this); + params = params || {}; + var self = this; + var visible = true; + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + var id = new String('_jsplumb_e_' + (new Date()).getTime()); + this.getId = function() { return id; }; + if (params.dynamicAnchors) + self.anchor = new DynamicAnchor(jsPlumb.makeAnchors(params.dynamicAnchors)); + else + self.anchor = params.anchor ? jsPlumb.makeAnchor(params.anchor) : params.anchors ? jsPlumb.makeAnchor(params.anchors) : jsPlumb.makeAnchor("TopCenter"); + var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot(); + if (_endpoint.constructor == String) + _endpoint = new jsPlumb.Endpoints[_endpoint](); + else if (_endpoint.constructor == Array) + _endpoint = new jsPlumb.Endpoints[_endpoint[0]](_endpoint[1]); + self.endpoint = _endpoint; + this.paintStyle = params.paintStyle || params.style || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + this.hoverPaintStyle = params.hoverPaintStyle || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + this.paintStyleInUse = this.paintStyle; + this.connectorStyle = params.connectorStyle; + this.connectorBackgroundStyle = params.connectorBackgroundStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + var _element = params.source, _uuid = params.uuid; + var floatingEndpoint = null, inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + this.container = params.container || _currentInstance.Defaults.Container || jsPlumb.Defaults.Container; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + /* + * Property: canvas + * The Endpoint's Canvas. + */ + this.canvas = params.canvas || _newCanvas({"class":jsPlumb.endpointClass, container:this.container, uuid:params.uuid}); + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + this.connections = params.connections || []; + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + var _reattach = params.reattach || false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + // this avoids a circular loop + if (!ignoreTarget) { + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + t.detach(connection, true); + } + _removeElement(connection.canvas, connection.container); + _removeFromList(connectionsByScope, connection.scope, connection); + if(!ignoreTarget) fireDetachEvent(connection); + } + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + */ + this.detachAll = function() { + while (self.connections.length > 0) { + self.detach(self.connections[0]); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + */ + this.detachFrom = function(targetEndpoint) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + c[i].setHover(false); + self.detach(c[i]); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + var e = _newEndpoint( { anchor : self.anchor, source : _element, paintStyle : this.paintStyle, endpoint : _endpoint }); + return e; + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + return (self.connections.length < _maxConnections) ? null : self.connections[0]; + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + + /* + * Function: setPaintStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + */ + this.setPaintStyle = this.setPaintStyle; // i do this so NaturalDocs can pick up the function definition. + + /* + * Function: setHoverPaintStyle + * Sets the hover paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * + * Parameters: + * style - Style object to set, for example { fillStyle:"yellow" }. + */ + this.setHoverPaintStyle = this.setHoverPaintStyle; // i do this so NaturalDocs can pick up the function definition. + + /* + * Sets the paint style of the Endpoint. + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + /* + * + * Paints the Endpoint, recalculating offset and anchor positions if necessary. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + + params = params || {}; + var timestamp = params.timestamp; + if (!timestamp || self.timestamp !== timestamp) { + var ap = params.anchorPoint, canvas = params.canvas, connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + // do we always want to force a repaint here? i dont + // think so! + var xy = params.offset || offsets[_elementId]; + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (self.anchor.isDynamic) { + if (self.connections.length > 0) { + var c = self.connections[0]; + var oIdx = c.endpoints[0] == self ? 1 : 0; + var oId = oIdx == 0 ? c.sourceId : c.targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + } + ap = self.anchor.compute(anchorParams); + } + _endpoint.paint(ap, self.anchor.getOrientation(), canvas || self.canvas, self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + self.timestamp = timestamp; + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + var start = function() { + jpc = self.connectorSelector(); + if (self.isFull() && !dragAllowedWhenFull) return false; + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + var nE = _getElementObject(n); + _appendElement(n, self.container); // + // create and assign an id, and initialize the offset. + var id = _getId(nE); + _updateOffset( { elId : id }); + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor( { reference : self.anchor, referenceCanvas : self.canvas }); + floatingEndpoint = _newEndpoint({ paintStyle : { fillStyle : 'rgba(0,0,0,0)' }, endpoint : _endpoint, anchor : floatingAnchor, source : nE }); + + if (jpc == null) { + self.anchor.locked = true; + // create a connection. one end is this endpoint, the + // other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : _getElementObject(_element), + target : _getElementObject(n), + anchors : [ self.anchor, floatingAnchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + backgroundPaintStyle:params.connectorBackgroundStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays + }); + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.setHover(false); + } else { + existingJpc = true; + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.setHover(false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas)); + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; // are we the source or the target? + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas); + var dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // get a new, temporary scope, to use (issue 57) + var newScope = "scope_" + (new Date()).getTime(); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + // set the new, temporary scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(i, newScope); + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // TODO unregister on stop? or will floating endpoint's + // destruction be assured. + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be + // dragging the source at any time + // before this connection is either discarded or made into a + // permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var dragOptions = params.dragOptions || {}; + var defaultOpts = jsPlumb.extend( {}, jsPlumb.CurrentLibrary.defaultDragOptions); + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + var startEvent = jsPlumb.CurrentLibrary.dragEvents['start']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], + function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + jsPlumb.CurrentLibrary.setOffset(n, _ui); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements( [ n, floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (_reattach) { + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } else { + jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc); // the main endpoint will inform the floating endpoint + // to disconnect, and also post the detached event. + } + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElement(jpc.canvas, self.container); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint(); + jpc.repaint(); + jpc = null; + delete inPlaceCopy; + delete endpointsByElement[floatingEndpoint.elementId]; + delete floatingEndpoint; + + _currentInstance.currentlyDragging = false; + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas) { + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + var drop = function() { + var draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)); + var id = _getAttribute(draggable, "dragId"); + var elId = _getAttribute(draggable, "elId"); + + // restore the original scope if necessary (issue 57) + var scope = _getAttribute(draggable, "originalScope"); + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // todo test that the target is not full. + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + if (!jpc.suspendedEndpoint) { // if a new connection, add it. TODO: move this to a jsPlumb internal method - addConnection or something. doesnt need to be exposed. + _addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + _currentInstance.fireUpdate("jsPlumbConnectionDetached", { + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }); + } + + jsPlumb.repaint(elId); + + _currentInstance.fireUpdate("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], + targetEndpoint : jpc.endpoints[1], + connection:jpc + }); + } + + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute( _getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + }); + + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas)); + + return self; + }; + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + //if (_defaults) jsPlumb.extend(j.Defaults, _defaults); + return j; + }; +})(); +/* +* jsPlumb-defaults-1.2.6-RC1 +* +* This script contains the default Anchors, Endpoints, Connectors and Overlays for jsPlumb. It should be used with jsPlumb 1.1.0 and above; +* prior to version 1.1.0 of jsPlumb the defaults were included inside the main script. +* +* NOTE: for production usage you should use jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, +* in a minified file. +* +* Dual licensed under MIT and GPL2. +*/ + +(function() { + + var ie = !!!document.createElement('canvas').getContext; + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * getOrientation : function() { return [ox, oy]; } + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + var _curryAnchor = function(x,y,ox,oy) { + return function() { + return jsPlumb.makeAnchor(x,y,ox,oy); + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1); + + + jsPlumb.Defaults.DynamicAnchors = function() { + return jsPlumb.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"]); + }; + jsPlumb.Anchors["AutoDefault"] = function() { return jsPlumb.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors()); }; + + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + var currentPoints = null; + var _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + + _dx = _tx - _sx, _dy = (_ty - _sy); + _m = _dy / _dx, _m2 = -1 / _m; + + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + + return currentPoints; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + var xp = _sx + (location * _dx); + var yp = (_m == Infinity || _m == -Infinity) ? _sy + (location * (_ty - _sy)) : (_m * xp) + _b; + return {x:xp, y:yp}; + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { return _m; }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + var orientation = distance > 0 ? 1 : -1; + var y = Math.abs(distance * Math.sin(_theta)); + if (_sy > _ty) y = y * -1; + var x = Math.abs(distance * Math.cos(_theta)); + if (_sx > _tx) x = x * -1; + return {x:p.x + (orientation * x), y:p.y + (orientation * y)}; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = self.gradientAtPoint(p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + /** + * This Connector draws a Bezier curve with two control points. + * + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * note that the method signature changed in 1.2.6 to take a params object, so the method + * argument was renamed. you can still provide just an integer to this constructor, though the + * preferred method is to use {curviness:XXX}. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + this.majorAnchor = 150; + // backwards compatibility (ideally we'd just use params.curviness || 150). + if (params) { + if (params.constructor == Number) this.majorAnchor = params; + else if (params.curviness) this.majorAnchor = params.curviness; + } + this.minorAnchor = 10; + var currentPoints = null; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h; + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) + { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx); var minx2 = Math.min(_CP[0], _CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(_sx,_tx); var maxx2 = Math.max(_CP[0], _CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty); var miny2 = Math.min(_CP[1], _CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(_sy,_ty); var maxy2 = Math.max(_CP[1], _CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + this.paint = function(d, ctx) { + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + return jsBezier.perpendicularToCurveAt(_makeCurve(), location, length, distance); + }; + + this.createGradient = function(dim, ctx, swap) { + return (swap) ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + + /** + * Types of endpoint UIs. we supply four - a circle of default radius 10px, a rectangle of + * default size 20x20, an image (with no default), and a Triangle, of default size 15. + * you can supply others of these if you want to - see the documentation for a howto. + */ + + /** + * a round endpoint, with default radius 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }; + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + if (endpointStyle.gradient && !ie) { + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * A Rectangular endpoint, with default size 20x20. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + jsPlumb.Endpoints.Triangle = function(params) { + + params = params || { width:55, height:55 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - width/2; + var y = anchorPoint[1] - height/2; + + jsPlumb.sizeCanvas(canvas, x, y, width, height); + + var ctx = canvas.getContext('2d'); + var offsetX = 0, offsetY = 0, angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = endpointStyle.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' or, since 1.2.4, a 'src' property in the params object.. + */ + jsPlumb.Endpoints.Image = function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.src || params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + }; + + /** + * An arrow overlay. you can provide: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + params = params || {}; + var self = this; + this.length = params.length || 20; + this.width = params.width || 20; + this.connection = params.connection; + var direction = (params.direction || 1) < 0 ? -1 : 1; + /*var fillStyle = params.fillStyle; + var strokeStyle = params.strokeStyle; + var lineWidth = params.lineWidth || 1;*/ + var paintStyle = params.paintStyle || { lineWidth:1 }; + this.loc = params.location == null ? 0.5 : params.location; + // how far along the arrow the lines folding back in come to. default is 62.3%. + var foldback = params.foldback || 0.623; + var _getFoldBackPoint = function(connector, loc) { + if (foldback == 0.5) return connector.pointOnPath(loc); + else { + var adj = 0.5 - foldback; // we calculate relative to the center + return connector.pointAlongPathFrom(loc, direction * self.length * adj); + } + }; + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.draw = function(connector, ctx, currentConnectionPaintStyle) { + + // this is the arrow head position + var hxy = connector.pointAlongPathFrom(self.loc, direction * (self.length / 2)); + // this is the center of the tail + var txy = connector.pointAlongPathFrom(self.loc, -1 * direction * (self.length / 2)), tx = txy.x, ty = txy.y; + // this is the tail vector + var tail = connector.perpendicularToPathAt(self.loc, self.width, -1 * direction * (self.length / 2)); + // this is the point the tail goes in to + var cxy = _getFoldBackPoint(connector, self.loc); + + // if loc = 1, then hxy should be flush with the element. + if (self.loc == 1) { + var lxy = connector.pointOnPath(self.loc); + var dx = lxy.x - hxy.x, dy = lxy.y - hxy.y; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + // if loc = 0, then tail midpoint should be flush with the element. + if (self.loc == 0) { + var lxy = connector.pointOnPath(self.loc); + var tailMid = foldback > 1 ? cxy : { + x:tail[0].x + ((tail[1].x - tail[0].x) / 2), + y:tail[0].y + ((tail[1].y - tail[0].y) / 2) + }; + var dx = lxy.x - tailMid.x, dy = lxy.y - tailMid.y; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x); + var maxx = Math.max(hxy.x, tail[0].x, tail[1].x); + var miny = Math.min(hxy.y, tail[0].y, tail[1].y); + var maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + ctx.lineWidth = paintStyle.lineWidth; + ctx.beginPath(); + ctx.moveTo(hxy.x, hxy.y); + ctx.lineTo(tail[0].x, tail[0].y); + ctx.lineTo(cxy.x, cxy.y); + ctx.lineTo(tail[1].x, tail[1].y); + ctx.lineTo(hxy.x, hxy.y); + ctx.closePath(); + + if (paintStyle.strokeStyle) { + ctx.strokeStyle = paintStyle.strokeStyle; + ctx.stroke(); + } + ctx.fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle; + ctx.fill(); + + + + return [ minx, maxx, miny, maxy]; + }; + }; + + /** + * a basic arrow. this is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. See Arrow for params. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + /** + * a diamond. like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. See Arrow for params. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40; + var p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + /** + * A Label overlay. Params you can provide: + * + * labelStyle - js object containing style instructions for the label. defaults to jsPlumb.Defaults.LabelStyle. + * label - the label to paint. may be a string or a function that returns a string. nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * borderWidth - width of a border to paint. defaults to zero. + * borderStyle - strokeStyle to use when painting the border, if necessary. + */ + jsPlumb.Overlays.Label = function(params) { + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + this.connection = params.connection; + var self = this; + var labelWidth = null, labelHeight = null, labelText = null, labelPadding = null; + this.location = params.location || 0.5; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var _textDimensions = function(ctx) { + if (self.cachedDimensions) return self.cachedDimensions; // return cached copy if we can. if we add a setLabel function remember to clear the cache. + labelText = typeof self.label == 'function' ? self.label(self) : self.label; + var d = {}; + if (labelText) { + var lines = labelText.split(/\n|\r\n/); + ctx.save(); + if (self.labelStyle.font) ctx.font = self.labelStyle.font; + var t = _widestLine(lines, ctx); + // a fake text height measurement: use the width of upper case M + var h = ctx.measureText("M").width; + labelPadding = self.labelStyle.padding || 0.25; + labelWidth = t + (2 * t * labelPadding); + labelHeight = (lines.length * h) + (2 * h * labelPadding); + var textHeight = lines.length * h; + ctx.restore(); + d = {width:labelWidth, height:labelHeight, lines:lines, oneLine:h, padding:labelPadding, textHeight:textHeight}; + } + if (typeof self.label != 'function') self.cachedDimensions = d; // cache it if we can. + return d; + }; + this.computeMaxSize = function(connector, ctx) { + var td = _textDimensions(ctx); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + var _widestLine = function(lines, ctx) { + var max = 0; + for (var i = 0; i < lines.length; i++) { + var t = ctx.measureText(lines[i]).width; + if (t > max) max = t; + } + return max; + }; + + this.draw = function(connector, ctx, currentConnectionPaintStyle) { + var td = _textDimensions(ctx); + if (td.width) { + var cxy = connector.pointOnPath(self.location); + if (self.labelStyle.font) ctx.font = self.labelStyle.font; + if (self.labelStyle.fillStyle) + ctx.fillStyle = self.labelStyle.fillStyle; + else + ctx.fillStyle = "rgba(0,0,0,0)"; + + var minx = cxy.x - (td.width / 2); + var miny = cxy.y - (td.height / 2); + + ctx.fillRect(minx, miny , td.width , td.height ); + + if (self.labelStyle.color) ctx.fillStyle = self.labelStyle.color; + ctx.textBaseline = "middle"; + ctx.textAlign = "center"; + for (i = 0; i < td.lines.length; i++) { + ctx.fillText(td.lines[i],cxy.x, cxy.y - (td.textHeight / 2) + (td.oneLine/2) + (i*td.oneLine)); + } + + // border + if (self.labelStyle.borderWidth > 0) { + ctx.strokeStyle = self.labelStyle.borderStyle || "black"; + ctx.strokeRect(minx, miny, td.width , td.height ); + } + + return [minx, minx+td.width, miny, miny+td.height]; + } + else return [0,0,0,0]; + }; + }; + + /** + * an image overlay. params may contain: + * + * location : proportion along the connector to draw the image. optional. + * src : image src. required. + * events : map of event names to functions; each event listener will be bound to the img. + */ + jsPlumb.Overlays.Image = function(params) { + var self = this; + this.location = params.location || 0.5; + this.img = new Image(); + this.connection = params.connection; + var imgDiv = null; + var notReadyInterval = null; + var notReadyConnector, notReadyContext; + var events = params.events || {}; + var _init = function() { + if (self.ready) { + window.clearInterval(notReadyInterval); + imgDiv = document.createElement("img"); + imgDiv.src = self.img.src; + imgDiv.style.position = "absolute"; + imgDiv.style.display="none"; + imgDiv.className = "_jsPlumb_overlay"; + document.body.appendChild(imgDiv);// HMM + // attach events + for (var e in events) { + jsPlumb.CurrentLibrary.bind(imgDiv, e, events[e]); + } + if (notReadyConnector && notReadyContext) { + _draw(notReadyConnector, notReadyContext); + notReadyContext = null; + notReadyConnector = null; + } + } + }; + this.img.onload = function() { + self.ready = true; + // jsPlumb.repaintAll(); + }; + this.img.src = params.src || params.url; + + notReadyInterval = window.setInterval(_init, 250); + + this.computeMaxSize = function(connector, ctx) { + return [ self.img.width, self.img.height ]; + }; + + var _draw = function(connector, ctx, currentConnectionPaintStyle) { + if (imgDiv != null) { + var cxy = connector.pointOnPath(self.location); + var canvas = jsPlumb.CurrentLibrary.getElementObject(ctx.canvas); + var canvasOffset = jsPlumb.CurrentLibrary.getOffset(canvas); + var minx = cxy.x - (self.img.width/2); + var miny = cxy.y - (self.img.height/2); + var o = {left:canvasOffset.left + minx, top:canvasOffset.top + miny}; + jsPlumb.CurrentLibrary.setOffset(imgDiv, o); + imgDiv.style.display = "block"; + return [minx,minx + self.img.width, miny, miny+self.img.height]; + } + }; + + this.draw = function(connector, ctx) { + if (self.ready) + return _draw(connector, ctx); + else { + notReadyConnector = connector; + notReadyContext = ctx; + return [0,0,0,0]; + } + }; + }; +})();;(function() { + + jsPlumb.Connectors.Flowchart = function(params) { + params = params || {}; + var self = this, + minStubLength = params.minStubLength || 30, + segments = [], + segmentGradients = [], + segmentProportions = [], + segmentLengths = [], + segmentProportionalLengths = [], + points = [], + swapX, + swapY, + /** + * recalculates the gradients of each segment, and the points at which the segments begin, proportional to the total length travelled + * by all the segments that constitute the connector. + */ + updateSegmentGradientsAndProportions = function(startX, startY, endX, endY) { + var total = 0; + for (var i = 0; i < segments.length; i++) { + var sx = i == 0 ? startX : segments[i][2], + sy = i == 0 ? startY : segments[i][3], + ex = segments[i][0], + ey = segments[i][1]; + + segmentGradients[i] = sx == ex ? Infinity : 0; + segmentLengths[i] = Math.abs(sx == ex ? ey - sy : ex - sx); + total += segmentLengths[i]; + } + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segmentLengths[i] / total; + segmentProportions[i] = [curLoc, (curLoc += (segmentLengths[i] / total)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0]; + var ly = segments.length == 0 ? sy : segments[segments.length - 1][1]; + segments.push([x, y, lx, ly]); + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment that contains the point which is 'location' distance along the entire path, where 'location' is + * a decimal between 0 and 1 inclusive. in this connector type paths are made up of a list of segments, each of which contributes some fraction to + * the total length. + */ + findSegmentForLocation = function(location) { + var idx = segmentProportions.length - 1, inSegmentProportion = 0; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + segments = []; + segmentGradients = []; + segmentProportionalLengths = []; + segmentLengths = []; + segmentProportionals = []; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + offx = (lw / 2) + (minStubLength * 2), + offy = (lw / 2) + (minStubLength * 2), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(), + to = targetAnchor.orientation || targetAnchor.getOrientation(), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx, + h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy; + if (w < minWidth) { + offx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + offy += (minWidth - h) / 2; + h = minWidth; + } + sx = swapX ? w-offx : offx, + sy = swapY ? h-offy : offy, + tx = swapX ? offx : w-offx , + ty = swapY ? offy : h-offy, + startStubX = sx + (so[0] * minStubLength), + startStubY = sy + (so[1] * minStubLength), + endStubX = tx + (to[0] * minStubLength), + endStubY = ty + (to[1] * minStubLength), + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2); + + x -= offx; y -= offy; + points = [x, y, w, h, sx, sy, tx, ty], extraPoints = []; + + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + if (so[0] == 0) { + var startStubIsBeforeEndStub = startStubY < endStubY; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } + } + else { + var startStubIsBeforeEndStub = startStubX < endStubX; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } + } + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentGradientsAndProportions(sx, sy, tx, ty); + + return points; + }; + + this.paint = function(dimensions, ctx) { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location) { + return self.pointAlongPathFrom(location, 0); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location) { + return segmentGradients[findSegmentForLocation(location)["index"]]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance) { + var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segmentLengths[s.index], m = segmentGradients[s.index]; + var e = { + //x : m == Infinity ? seg[2] : /*swapX ? seg[2] - (p * sl) - distance : */seg[2] + (p * sl) + distance, + + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + + + //y : m == 0 ? seg[3] : /*swapY ? seg[3] - (p * sl) - distance : */seg[3] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + //console.log("pointalongpath, swapX =" + swapX + ",swapY=" + swapY, "loc", location, "travel", (p * sl), "dist", distance, e.x, e.y, "seg", seg, "len", sl, "prop.", p); + + return e; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = segmentGradients[p.segmentInfo.index]; + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + + }; + }; +})(); +/* + * yui.jsPlumb 1.2.6-RC1 + * + * YUI3 specific functionality for jsPlumb. + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + * NOTE: for production usage you should use yui-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, + * in a minified file. + * + * Dual licensed under MIT and GPL2. + * + */ + +/** + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + */ +(function() { + + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function( v, b, s ) { + for( var i = +b || 0, l = this.length; i < l; i++ ) { + if( this[i]===v || s && this[i]==v ) { return i; } + } + return -1; + }; + } + + var Y; + + YUI().use('node', 'dd', 'anim', function(_Y) { + Y = _Y; + Y.on("domready", function() { + jsPlumb.init(); + }); + }); + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }; + + var ddEvents = [ "drag:mouseDown", "drag:afterMouseDown", "drag:mouseup", + "drag:align", "drag:removeHandle", "drag:addHandle", "drag:removeInvalid", "drag:addInvalid", + "drag:start", "drag:end", "drag:drag", "drag:over", "drag:enter", + "drag:exit", "drag:drophit", "drag:dropmiss", "drop:over", "drop:enter", "drop:exit", "drop:hit" + ]; + + var animEvents = [ "tween" ]; + + /** + * helper function to curry callbacks for some element. + */ + var _wrapper = function(fn) { + return function() { fn.apply(this, arguments); }; + }; + + /** + * extracts options from the given options object, leaving out event handlers. + */ + var _getDDOptions = function(options) { + var o = {}; + for (var i in options) if (ddEvents.indexOf(i) == -1) o[i] = options[i]; + return o; + }; + + /** + * attaches all event handlers found in options to the given dragdrop object, and registering + * the given el as the element of interest. + */ + var _attachListeners = function(dd, options, eventList) { + for (var ev in options) { + if (eventList.indexOf(ev) != -1) { + var w = _wrapper(options[ev]); + dd.on(ev, w); + } + } + }; + + var _droppables = {}; + var _droppableOptions = {}; + var _draggablesByScope = {}; + var _draggablesById = {}; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + var _lastDragObject = null; + + var _extend = function(o1, o2) { + for (var i in o2) + o1[i] = o2[i]; + return o1; + }; + + var _getAttribute = function(el, attributeId) { + return el.getAttribute(attributeId); + }; + + var _getElementObject = function(el) { + return typeof el == 'string' ? Y.one('#' + el) : Y.one(el); + }; + + jsPlumb.CurrentLibrary = { + + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + var o = _extend({node:el, to:properties}, options); + var id = _getAttribute(el, "id"); + o["tween"] = jsPlumb.wrap(properties["tween"], function() { + jsPlumb.repaint(id); + }); + var a = new Y.Anim(o); + _attachListeners(a, o, animEvents); + a.run(); + }, + + appendElement : function(child, parent) { + _getElementObject (parent).append(child); + }, + + /** + * event binding wrapper. + */ + bind : function(el, event, callback) { + _getElementObject(el).on(event, callback); + }, + + dragEvents : { + 'start':'drag:start', 'stop':'drag:end', 'drag':'drag:drag', 'step':'step', + 'over':'drop:enter', 'out':'drop:exit', 'drop':'drop:hit' + }, + + extend : _extend, + + getAttribute : _getAttribute, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + // this is a workaround for the unfortunate fact that in YUI3, the 'drop:exit' event does + // not contain a reference to the drag that just exited. single-threaded js to the + // rescue: we'll just keep it for ourselves. + if (eventArgs[0].drag) _lastDragObject = eventArgs[0].drag.el; + return _lastDragObject; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + return dd.scope; + }, + + getElementObject : _getElementObject, + + getOffset : function(el) { + var bcr = el._node.getBoundingClientRect(); + return { left:bcr.left, top:bcr.top }; + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getScrollLeft : function(el) { + alert("YUI getScrollLeft not implemented yet"); + }, + + getScrollTop : function(el) { + alert("YUI getScrollTop not implemented yet"); + }, + + getSize : function(el) { + //TODO must be a better way to get this? + var bcr = _getElementObject(el)._node.getBoundingClientRect(); + return [ bcr.right - bcr.left, bcr.bottom - bcr.top]; // for some reason, in IE, the bounding rect does not always have width,height precomputed. + }, + + getUIPosition : function(args) { + //TODO must be a better way to get this? args was passed through from the drag function + // in initDraggable above - args[0] here is the element that was inited. + var bcr = _getElementObject(args[0].currentTarget.el)._node.getBoundingClientRect(); + return { left:bcr.left, top:bcr.top }; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options) { + var _opts = _getDDOptions(options); + var id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drag(_opts); + dd.el = el; + + var scope = options['scope'] || jsPlumb.Defaults.Scope; + dd.scope = scope; + + _draggablesById[id] = dd; + _add(_draggablesByScope, scope, dd); + + _attachListeners(dd, options, ddEvents); + }, + + initDroppable : function(el, options) { + var _opts = _getDDOptions(options); + var id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drop(_opts); + + _droppableOptions[id] = options; + + options = _extend({}, options); + var scope = options['scope'] || jsPlumb.Defaults.Scope; + + options["drop:enter"] = jsPlumb.wrap(options["drop:enter"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, true); + }, true); + options["drop:exit"] = jsPlumb.wrap(options["drop:exit"], function(e) { + _checkHover(el, false); + }); + options["drop:hit"] = jsPlumb.wrap(options["drop:hit"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, false); + }, true); + + _attachListeners(dd, options, ddEvents); + }, + + isAlreadyDraggable : function(el) { + el = _getElementObject(el); + return el.hasClass("yui3-dd-draggable"); + }, + + isDragSupported : function(el) { return true; }, + isDropSupported : function(el) { return true; }, + removeClass : function(el, clazz) { el.removeClass(clazz); }, + removeElement : function(el) { _getElementObject(el).remove(); }, + + setAttribute : function(el, attributeName, attributeValue) { + el.setAttribute(attributeName, attributeValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + if (dd) dd.set("lock", !draggable); + }, + + setDragScope : function(el, scope) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + if (dd) dd.scope = scope; + }, + + setOffset : function(el, o) { + el = _getElementObject(el); + el.set("top", o.top); + el.set("left", o.left); + } + }; +})();(function(){if(typeof Math.sgn=="undefined")Math.sgn=function(a){return a==0?0:a>0?1:-1};var p={subtract:function(a,b){return{x:a.x-b.x,y:a.y-b.y}},dotProduct:function(a,b){return a.x*b.x+a.y*b.y},square:function(a){return Math.sqrt(a.x*a.x+a.y*a.y)},scale:function(a,b){return{x:a.x*b,y:a.y*b}}},y=Math.pow(2,-65),u=function(a,b){for(var g=[],d=b.length-1,h=2*d-1,f=[],c=[],l=[],k=[],i=[[1,0.6,0.3,0.1],[0.4,0.6,0.6,0.4],[0.1,0.3,0.6,1]],e=0;e<=d;e++)f[e]=p.subtract(b[e],a);for(e=0;e<=d-1;e++){c[e]= +p.subtract(b[e+1],b[e]);c[e]=p.scale(c[e],3)}for(e=0;e<=d-1;e++)for(var m=0;m<=d;m++){l[e]||(l[e]=[]);l[e][m]=p.dotProduct(c[e],f[m])}for(e=0;e<=h;e++){k[e]||(k[e]=[]);k[e].y=0;k[e].x=parseFloat(e)/h}h=d-1;for(f=0;f<=d+h;f++){c=Math.min(f,d);for(e=Math.max(0,f-h);e<=c;e++){j=f-e;k[e+j].y+=l[j][e]*i[j][e]}}d=b.length-1;k=s(k,2*d-1,g,0);h=p.subtract(a,b[0]);l=p.square(h);for(e=i=0;e=64){g[0]=(a[0].x+a[b].x)/2;return 1}var n,o,q;k=a[0].y-a[b].y;i=a[b].x-a[0].x;e=a[0].x*a[b].y-a[b].x*a[0].y;m=max_distance_below=0;for(o=1;om)m=q;else if(q0?1:-1,c=null;h=0){delete a[c];a.splice(c,1);return true}}return false},Y=function(a,b){var c=B(a,"id");U(c,function(h){h.canvas.style.display=b})},Z=function(a){U(a,function(b){b.canvas.style.display="none"==b.canvas.style.display?"block":"none"})},L=function(a,b,c){if(c||b==null){x(a);r[a]=V(a);b=j.CurrentLibrary.getElementObject(a); +b=j.CurrentLibrary.getOffset(b);l[a]=b}else l[a]=b},I=function(a,b){a=a||function(){};b=b||function(){};return function(){try{b.apply(this,arguments)}catch(c){}try{a.apply(this,arguments)}catch(h){}}},ba=function(a){var b=this;this.x=a.x||0;this.y=a.y||0;var c=a.orientation||[0,0];this.offsets=a.offsets||[0,0];this.compute=function(h,m){return[h[0]+b.x*m[0]+b.offsets[0],h[1]+b.y*m[1]+b.offsets[1]]};this.getOrientation=function(){return c}},ca=function(a){var b=a.reference,c=V(a.referenceCanvas),h= +0,m=0,p=null;this.compute=function(s){m=h=0;return[s[0]+c[0]/2,s[1]+c[1]/2]};this.getOrientation=function(){if(p)return p;else{var s=b.getOrientation();return[Math.abs(s[0])*h*-1,Math.abs(s[1])*m*-1]}};this.over=function(s){p=s.getOrientation()};this.out=function(){p=null}},aa=function(a){var b=this;this.source=x(a.source);this.target=x(a.target);this.sourceId=B(this.source,"id");this.targetId=B(this.target,"id");this.endpointsOnTop=a.endpointsOnTop!=null?a.endpointsOnTop:true;this.endpoints=[];this.endpointStyles= +[];var c=function(p,s,u){if(p)b.endpoints[s]=p;else{if(!u.endpoints)u.endpoints=[null,null];p=u.endpoints[s]||u.endpoint||j.Defaults.Endpoints[s]||j.Defaults.Endpoint||new j.Endpoints.Dot;if(!u.endpointStyles)u.endpointStyles=[null,null];b.endpoints[s]=new S({style:u.endpointStyles[s]||u.endpointStyle||j.Defaults.EndpointStyles[s]||j.Defaults.EndpointStyle,endpoint:p,connections:[b],anchor:u.anchors?u.anchors[s]:j.Defaults.Anchors[s]||j.Anchors.BottomCenter,source:b.source})}};c(a.sourceEndpoint, +0,a);c(a.targetEndpoint,1,a);this.connector=this.endpoints[0].connector||this.endpoints[1].connector||a.connector||j.Defaults.Connector||new j.Connectors.Bezier;this.paintStyle=this.endpoints[0].connectorStyle||this.endpoints[1].connectorStyle||a.paintStyle||j.Defaults.PaintStyle;L(this.sourceId);L(this.targetId);c=l[this.sourceId];var h=r[this.sourceId];c=this.endpoints[0].anchor.compute([c.left,c.top],h);this.endpoints[0].paint(c);c=l[this.targetId];h=r[this.targetId];c=this.endpoints[1].anchor.compute([c.left, +c.top],h);this.endpoints[1].paint(c);var m=W(j.connectorClass);this.canvas=m;this.paint=function(p,s,u){g&&g.debug("Painting Connection; element in motion is "+p+"; ui is ["+s+"]; recalc is ["+u+"]");var v=p!=this.sourceId,D=v?this.sourceId:this.targetId,w=v?0:1,q=v?1:0;if(this.canvas.getContext){L(p,s,u);u&&L(D);s=l[p];u=l[D];p=r[p];var O=r[D];D=m.getContext("2d");var J=this.endpoints[q].anchor.compute([s.left,s.top],p,[u.left,u.top],O);this.endpoints[q].anchor.getOrientation();s=this.endpoints[w].anchor.compute([u.left, +u.top],O,[s.left,s.top],p);this.endpoints[w].anchor.getOrientation();w=this.connector.compute(J,s,this.endpoints[q].anchor,this.endpoints[w].anchor,this.paintStyle.lineWidth);j.sizeCanvas(m,w[0],w[1],w[2],w[3]);j.extend(D,this.paintStyle);if(this.paintStyle.gradient&&!e){v=v?D.createLinearGradient(w[4],w[5],w[6],w[7]):D.createLinearGradient(w[6],w[7],w[4],w[5]);for(q=0;q=0&&b.connections.splice(n,1)};this.makeInPlaceCopy=function(){return new S({anchor:b.anchor,source:m,style:h,endpoint:c})};this.isConnectedTo=function(n){var C=false;if(n)for(var z=0;z=s};this.paint=function(n,C,z){g&&g.debug("Painting Endpoint with elementId ["+p+"]; anchorPoint is ["+n+"]");if(n==null){n=l[p];var N=r[p];if(n==null||N==null){L(p);n=l[p];N=r[p]}n=b.anchor.compute([n.left,n.top],N)}c.paint(n,b.anchor.getOrientation(),z||b.canvas,h,C||h)};if(a.isSource&&j.CurrentLibrary.isDragSupported(m)){var w=null,q=null,O=false,J=null,A=a.dragOptions|| +{},K=j.extend({},j.CurrentLibrary.defaultDragOptions);A=j.extend(K,A);K=j.CurrentLibrary.dragEvents.start;var P=j.CurrentLibrary.dragEvents.stop,Q=j.CurrentLibrary.dragEvents.drag;A[K]=I(A[K],function(){D=b.makeInPlaceCopy();D.paint();w=document.createElement("div");document.body.appendChild(w);var n=""+new String((new Date).getTime());R(x(w),"id",n);L(n);R(x(b.canvas),"dragId",n);R(x(b.canvas),"elId",p);var C=new ca({reference:b.anchor,referenceCanvas:b.canvas});v=new S({style:{fillStyle:"rgba(0,0,0,0)"}, +endpoint:c,anchor:C,source:w});q=b.connections.length==0||b.connections.length0)for(var h=0;h0)for(var m=0;m=4)c.orientation=[arguments[2],arguments[3]];if(arguments.length==6)c.offsets=[arguments[4],arguments[5]]}return new ba(c)},repaint:function(a){var b=function(h){h=x(h);F(h)};if(typeof a=="object")for(var c=0;co)o=k;if(f<0){r+=f;f=Math.abs(f);o+=f;G[0]+=f;E+=f;F+=f;d[0]+=f}f=Math.min(Math.min(H,i),Math.min(G[1], +d[1]));k=Math.max(Math.max(H,i),Math.max(G[1],d[1]));if(k>t)t=k;if(f<0){y+=f;f=Math.abs(f);t+=f;G[1]+=f;H+=f;i+=f;d[1]+=f}return[r,y,o,t,E,H,F,i,G[0],G[1],d[0],d[1]]};this.paint=function(d,f){f.beginPath();f.moveTo(d[4],d[5]);f.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]);f.stroke()}};jsPlumb.Endpoints.Dot=function(e){e=e||{radius:10};var g=this;this.radius=e.radius;var d=0.5*this.radius,f=this.radius/3,k=function(l){try{return parseInt(l)}catch(i){if(l.substring(l.length-1)=="%")return parseInt(l.substring(0, +l-1))}};this.paint=function(l,i,o,t,r){var y=t.radius||g.radius;jsPlumb.sizeCanvas(o,l[0]-y,l[1]-y,y*2,y*2);l=o.getContext("2d");o=jsPlumb.extend({},t);if(o.fillStyle==null)o.fillStyle=r.strokeStyle;jsPlumb.extend(l,o);r=/MSIE/.test(navigator.userAgent)&&!window.opera;if(t.gradient&&!r){r=t.gradient;o=d;var E=f;if(r.offset)o=k(r.offset);if(r.innerRadius)E=k(r.innerRadius);r=[o,E];i=l.createRadialGradient(y,y,y,y+(i[0]==1?r[0]*-1:r[0]),y+(i[1]==1?r[0]*-1:r[0]),r[1]);for(r=0;r endpoint lists. an element can have an arbitrary number of endpoints on it, + * and not all of them have to be connected to anything. + */ + var endpointsByElement = {}; + var offsets = []; + var floatingConnections = {}; + var draggableStates = {}; + var _draggableByDefault = true; + var sizes = []; + + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + var _findIndex = function( a, v, b, s ) { + var _eq = function(o1, o2) { + if (o1 === o2) return true; + else if (typeof o1 == 'object' && typeof o2 == 'object') { + var same = true; + for(var propertyName in o1) { + if(!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for(var propertyName in o2) { + if(!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for( var i = +b || 0, l = a.length; i < l; i++ ) { + if( _eq(a[i], v)) return i; + } + return -1; + }; + + /** + * helper method to add an item to a list, creating the list if it does not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + }; + + /** + * Handles the dragging of an element. + * @param element jQuery element + * @param ui UI object from current library's event system + */ + var _draw = function(element, ui) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints) { + _updateOffset(id, ui); + var myOffset = offsets[id]; + var myWH = sizes[id]; + // loop through endpoints for this element + for (var i = 0; i < endpoints.length; i++) { + var e = endpoints[i]; + var anchorLoc = endpoints[i].anchor.compute([myOffset.left, myOffset.top], myWH); + e.paint(anchorLoc); + var l = e.connections; + for (var j = 0; j < l.length; j++) + l[j].paint(id, ui); // ...and paint them. + } + } + }; + + /** + * helper function: the second argument is a function taking two args - the first is an id + * ui element from the current library (or list of them), and the second is the function + * to run. + */ + var _elementProxy = function(element, fn) { + var retVal = null; + if (typeof element == 'object' && element.length) { + retVal = []; + for (var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]); + var id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } + else { + var el = _getElementObject(element); + var id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + + return retVal; + }; + + /** + * gets the named attribute from the given element (id or element object) + */ + var _getAttribute = function(el, attName) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + return jsPlumb.CurrentLibrary.getAttribute(ele, attName); + }; + + var _setAttribute = function(el, attName, attValue) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + jsPlumb.CurrentLibrary.setAttribute(ele, attName, attValue); + }; + + var _addClass = function(el, clazz) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + jsPlumb.CurrentLibrary.addClass(ele, clazz); + }; + + var _getElementObject = function(elId) { + return jsPlumb.CurrentLibrary.getElementObject(elId); + }; + + var _getOffset = function(el) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + return jsPlumb.CurrentLibrary.getOffset(ele); + }; + + var _getSize = function(el) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + return jsPlumb.CurrentLibrary.getSize(ele); + }; + + var _appendCanvas = function(canvas) { + document.body.appendChild(canvas); + }; + + /** + * gets an id for the given element, creating and setting one if necessary. + */ + var _getId = function(element) { + var id = _getAttribute(element, "id"); + if (!id) { + id = "_jsPlumb_" + new String((new Date()).getTime()); + _setAttribute(element, "id", id); + } + return id; + }; + + /** + * inits a draggable if it's not already initialised. + * + * TODO: we need to hook in each library to this method. they need to be given the opportunity to + * wrap/insert lifecycle functions, because each library does different things. for instance, jQuery + * supports the notion of 'revert', which will clean up a dragged endpoint; MooTools does not. jQuery + * also supports 'opacity' and MooTools does not; jQuery supports z-index of the draggable; MooTools + * does not. i could go on. the point is...oh. initDraggable can do this. ok. + */ + var _initDraggableIfNecessary = function(element, elementId, isDraggable, dragOptions) { + // dragging + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable && jsPlumb.CurrentLibrary.isDragSupported(element)) { + var options = dragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend({}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var initDrag = function(element, elementId, dragFunc) { + options[dragEvent] = _wrap(options[dragEvent], dragFunc); + var draggable = draggableStates[elementId]; + // TODO: this is still jQuery specific. + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + }; + initDrag(element, elementId, function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + } + }; + + var _log = function(msg) { + // not implemented. yet. + } + + /** + * helper to create a canvas. + * @param clazz optional class name for the canvas. + */ + var _newCanvas = function(clazz) { + var canvas = document.createElement("canvas"); + _appendCanvas(canvas); + canvas.style.position="absolute"; + if (clazz) { canvas.className=clazz; } + _getId(canvas); // set an id. + + if (ie) { + // for IE we have to set a big canvas size. actually you can override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + /** + * performs the given function operation on all the connections found for the given element + * id; this means we find all the endpoints for the given element, and then for each endpoint + * find the connectors connected to it. then we pass each connection in to the given + * function. + */ + var _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + for (var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }; + /** + * perform an operation on all elements. + */ + var _operationOnAll = function(func) { + for (var elId in endpointsByElement) { + _operation(elId, func); + } + }; + /** + * helper to remove an element from the DOM. + */ + var _removeElement = function(element) { + if (element != null) { + try { document.body.removeChild(element); } + catch (e) { } + } + }; + /** + * helper to remove a list of elements from the DOM. + */ + var _removeElements = function(elements) { + for (var i in elements) + _removeElement(elements[i]); + }; + /** + * helper method to remove an item from a list. + */ + var _removeFromList = function(map, key, value) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete( l[i] ); + l.splice( i, 1 ); + return true; + } + } + return false; + }; + /** + * Sets whether or not the given element(s) should be draggable, regardless of what a particular + * plumb command may request. + * + * @param element May be a string, a jQuery elements, or a list of strings/jquery elements. + * @param draggable Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + var _helper = function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }; + + return _elementProxy(element, _helper); + }; + /** + * private method to do the business of hiding/showing. + * @param el either Id of the element in question or a jquery object for the element. + * @param state String specifying a value for the css 'display' property ('block' or 'none'). + */ + var _setVisible = function(el, state) { + var elId = _getAttribute(el, "id"); + var f = function(jpc) { + jpc.canvas.style.display = state; + }; + _operation(elId, f); + }; + /** + * toggles the draggable state of the given element(s). + * @param el either an id, or a jquery object, or a list of ids/jquery objects. + */ + var _toggleDraggable = function(el) { + var fn = function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + //el.draggable("option", "disabled", !state); + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }; + return _elementProxy(el, fn); + }; + /** + * private method to do the business of toggling hiding/showing. + * @param elId Id of the element in question + */ + var _toggleVisible = function(elId) { + var f = function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }; + + _operation(elId, f); + + //todo this should call _elementProxy, and pass in the _operation(elId, f) call as a function. cos _toggleDraggable does that. + }; + /** + * updates the offset and size for a given element, and stores the values. + * if 'offset' is not null we use that (it would have been passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we use the offset, outerWidth and outerHeight methods to get + * the current values. + */ + var _updateOffset = function(elId, offset, recalc) { + if (recalc || offset == null) { // if forced repaint or no offset available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + sizes[elId] = _getSize(elId); + offsets[elId] = _getOffset(elId); + } else { + offsets[elId] = offset; + } + }; + + /** + * wraps one function with another, creating a placeholder for the wrapped function + * if it was null. this is used to wrap the various drag/drop event functions - to allow + * jsPlumb to be notified of important lifecycle events without imposing itself on the user's + * drap/drop functionality. + * TODO: determine whether or not we should support an error handler concept, if one of the functions fails. + */ + var _wrap = function(wrappedFunction, newFunction) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + try { newFunction.apply(this, arguments); } + catch (e) { } + try { wrappedFunction.apply(this, arguments); } + catch (e) { } + }; + } + + /* + Class: Anchor + Models a position relative to the origin of some element that an Endpoint can be located. + */ + /* + Function: Anchor + + Constructor for the Anchor class + + Parameters: + params - Anchor parameters. This should contain three values, and may optionally have an 'offsets' argument: + + - x : the x location of the anchor as a fraction of the total width. + - y : the y location of the anchor as a fraction of the total height. + - orientation : an [x,y] array indicating the general direction a connection from the anchor should go in. for more info on this, see the documentation, or the docs in jquery-jsPlumb-defaults-XXX.js for the default Anchors. + - offsets : an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. may be null. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; this.y = params.y || 0; + var orientation = params.orientation || [0,0]; + this.offsets = params.offsets || [0,0]; + this.compute = function(xy, wh, txy, twh) { + return [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + } + this.getOrientation = function() { return orientation; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from its position relative to the anchor it is floating relative to. + * + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(refCanvas); + + // these are used to store the current relative position of our anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating anchor is hovering over another anchor. + var orientation = null; + + this.compute = function(xy, wh, txy, twh) { + // set these for the getOrientation method to use. + xDir = 0;//xy[0] < txy[0] ? -1 : xy[0] == txy[0] ? 0 : 1; + yDir = 0;//xy[1] < txy[1] ? -1 : xy[1] == txy[1] ? 0 : 1; + return [xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other anchor: if it declares zero for some direction, we declare zero too. + // this might not be the most awesome. perhaps we can come up with a better way. it's just so that the line we draw looks + // like it makes sense. maybe this wont make sense. + return [Math.abs(o[0]) * xDir * -1, Math.abs(o[1]) * yDir * -1]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering over another anchor; + * we want to assume that anchor's orientation for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no longer hovering + * over another anchor; we should resume calculating orientation as we normally do. + */ + this.out = function() { + orientation = null; + }; + }; + + // ************** connection + // **************************************** + /** + * allowed params: + * source: source element (string or a jQuery element) (required) + * target: target element (string or a jQuery element) (required) + * + * anchors: optional array of anchor placements. defaults to BottomCenter for source + * and TopCenter for target. + */ + var Connection = function(params) { + + // ************** get the source and target and register the connection. ******************* + var self = this; + // get source and target as jQuery objects + this.source = _getElementObject(params.source); + this.target = _getElementObject(params.target); + this.sourceId = _getAttribute(this.source, "id"); + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + + // init endpoints + this.endpoints = []; + this.endpointStyles = []; + var prepareEndpoint = function(existing, index, params) { + if (existing) self.endpoints[index] = existing; + else { + if(!params.endpoints) params.endpoints = [null,null]; + var ep = params.endpoints[index] || params.endpoint || jsPlumb.Defaults.Endpoints[index] || jsPlumb.Defaults.Endpoint|| new jsPlumb.Endpoints.Dot(); + if (!params.endpointStyles) params.endpointStyles = [null,null]; + var es = params.endpointStyles[index] || params.endpointStyle || jsPlumb.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyle; + var a = params.anchors ? params.anchors[index] : jsPlumb.Defaults.Anchors[index] || jsPlumb.Anchors.BottomCenter; + self.endpoints[index] = new Endpoint({style:es, endpoint:ep, connections:[self], anchor:a, source:self.source }); + } + }; + + prepareEndpoint(params.sourceEndpoint, 0, params); + prepareEndpoint(params.targetEndpoint, 1, params); + + this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + this.paintStyle = this.endpoints[0].connectorStyle || this.endpoints[1].connectorStyle || params.paintStyle || jsPlumb.Defaults.PaintStyle; + + _updateOffset(this.sourceId); + _updateOffset(this.targetId); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var anchorLoc = this.endpoints[0].anchor.compute([myOffset.left, myOffset.top], myWH); + this.endpoints[0].paint(anchorLoc); + myOffset = offsets[this.targetId]; myWH = sizes[this.targetId]; + anchorLoc = this.endpoints[1].anchor.compute([myOffset.left, myOffset.top], myWH); + this.endpoints[1].paint(anchorLoc); + + // *************** create canvas on which the connection will be drawn ************ + var canvas = _newCanvas(jsPlumb.connectorClass); + this.canvas = canvas; + + /** + * paints the connection. + * @param elId Id of the element that is in motion + * @param ui current library's event system ui object (present if we came from a drag to get here) + * @param recalc whether or not to recalculate element sizes. this is true if a repaint caused this to be painted. + */ + this.paint = function(elId, ui, recalc) { + + if (log) log.debug("Painting Connection; element in motion is " + elId + "; ui is [" + ui + "]; recalc is [" + recalc + "]"); + + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = !(elId == this.sourceId); + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + if (this.canvas.getContext) { + + _updateOffset(elId, ui, recalc); + if (recalc) _updateOffset(tId); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var myOffset = offsets[elId]; + var otherOffset = offsets[tId]; + var myWH = sizes[elId]; + var otherWH = sizes[tId]; + + var ctx = canvas.getContext('2d'); + var sAnchorP = this.endpoints[sIdx].anchor.compute([myOffset.left, myOffset.top], myWH, [otherOffset.left, otherOffset.top], otherWH); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.compute([otherOffset.left, otherOffset.top], otherWH, [myOffset.left, myOffset.top], myWH); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, this.paintStyle.lineWidth); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + jsPlumb.extend(ctx, this.paintStyle); + + if (this.paintStyle.gradient && !ie) { + var g = swap ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + for (var i = 0; i < this.paintStyle.gradient.stops.length; i++) + g.addColorStop(this.paintStyle.gradient.stops[i][0],this.paintStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + + this.connector.paint(dim, ctx); + } + }; + + this.repaint = function() { + this.paint(this.sourceId, null, true); + }; + + _initDraggableIfNecessary(self.source, self.sourceId, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, self.targetId, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + }; + + /* + Class: Endpoint + + Models an endpoint. Can have one to N connections emanating from it (although how to handle that in the UI is + a very good question). also has a Canvas and paint style. + + */ + + /* + Function: Endpoint + + This is the Endpoint class constructor. + + Parameters: + + anchor - anchor for the endpoint, of type jsPlumb.Anchor. may be null. + endpoint - endpoint object, of type jsPlumb.Endpoint. may be null. + style - endpoint style, a js object. may be null. + source - element the endpoint is attached to, of type jquery object. Required. + canvas - canvas element to use. may be, and most often is, null. + connections - optional list of connections to configure the endpoint with. + isSource - boolean. indicates the endpoint can act as a source of new connections. optional. + dragOptions - if isSource is set to true, you can supply arguments for the jquery draggable method. optional. + connectionStyle - if isSource is set to true, this is the paint style for connections from this endpoint. optional. + connector - optional connector type to use. + isTarget - boolean. indicates the endpoint can act as a target of new connections. optional. + dropOptions - if isTarget is set to true, you can supply arguments for the jquery droppable method. optional. + reattach - optional boolean that determines whether or not the connections reattach after they + have been dragged off an endpoint and left floating. defaults to false: connections + dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + params = params || {}; + // make a copy. then we can use the wrapper function. + jsPlumb.extend({}, params); + var self = this; + self.anchor = params.anchor || jsPlumb.Anchors.TopCenter; + var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot(); + var _style = params.style || jsPlumb.Defaults.EndpointStyle; + this.connectorStyle = params.connectorStyle; + this.connector = params.connector; + var _element = params.source; + var _elementId = _getAttribute(_element, "id"); + var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of. + this.canvas = params.canvas || _newCanvas(jsPlumb.endpointClass); + this.connections = params.connections || []; + var _reattach = params.reattach || false; + var floatingEndpoint = null; + var inPlaceCopy = null; + this.addConnection = function(connection) { + + self.connections.push(connection); + }; + this.removeConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) + self.connections.splice(idx, 1); + }; + this.makeInPlaceCopy = function() { + var e = new Endpoint({anchor:self.anchor, source:_element, style:_style, endpoint:_endpoint}); + return e; + }; + /** + * returns whether or not this endpoint is connected to the given endpoint. + * @param endpoint Endpoint to test. + * @since 1.1.1 + * + * todo: needs testing. will this work if the endpoint passed in is the source? + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + this.isFloating = function() { return floatingEndpoint != null; }; + /** + * first pass at default ConnectorSelector: returns the first connection, if we have any. + * modified a little, 5/10/2010: now it only returns a connector if we have not got equal to or more than _maxConnector connectors + * attached. otherwise it is assumed a new connector is ok. but note with this setup we can't select any other connection than the first + * one. what if this could return a list? that implies everything should work with a list - dragging etc. could be nasty. could also + * be cool. + */ + var connectorSelector = function() { + return self.connections.length == 0 || self.connections.length < _maxConnections ? null : self.connections[0]; + }; + + this.isFull = function() { return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); }; + + /** + * paints the Endpoint, recalculating offset and anchor positions if necessary. + */ + this.paint = function(anchorPoint, connectorPaintStyle, canvas) { + + if (log) log.debug("Painting Endpoint with elementId [" + _elementId + "]; anchorPoint is [" + anchorPoint + "]"); + + if (anchorPoint == null) { + // do we always want to force a repaint here? i dont think so! + var xy = offsets[_elementId]; + var wh = sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset(_elementId); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + anchorPoint = self.anchor.compute([xy.left, xy.top], wh); + } + _endpoint.paint(anchorPoint, self.anchor.getOrientation(), canvas || self.canvas, _style, connectorPaintStyle || _style); + }; + + + // is this a connection source? we make it draggable and have the drag listener + // maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + + // first the question is, how many connections are on this endpoint? if it's only one, then excellent. otherwise we will either need a way + // to select one connection from the list, or drag them all. if we had a pluggable 'ConnectorSelector' interface we could probably + // provide a way for people to implement their own UI components to do the connector selection. the question in that particular case would be how much + // information the interface needs from jsPlumb at execution time. if, however, we leave out the connector selection, and drag them all, + // that wouldn't be too hard to organise. perhaps that behaviour would be on a switch for the endpoint, or perhaps the ConnectorSelector + // interface returns a List, with the default implementation just returning everything. i think i like that. + // + // let's say for now that there is just one endpoint, cos we need to get this going before we can consider a list of them anyway. + // the major difference between that case and the case of a new endpoint is actually quite small - it's a question of where the + // Connection comes from. for a new one, we create a new one. obviously. otherwise we just get the jpc from the Endpoint + // (remember we're only assuming one connection right now). so all of the UI stuff we do to create the floating endpoint etc + // will still be valid, but when we stop dragging, we'll have to do something different. if we stop with a valid drop i think it will + // be the same process. but if we stop with an invalid drop we have to reset the Connection to how it was when we got it. + var start = function() { + + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + _appendCanvas(n); + // create and assign an id, and initialize the offset. + var id = "" + new String(new Date().getTime()); + _setAttribute(_getElementObject(n), "id", id); + _updateOffset(id); + // store the id of the dragging div and the source element. the drop function + // will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor({reference:self.anchor, referenceCanvas:self.canvas}); + floatingEndpoint = new Endpoint({ + style:{fillStyle:'rgba(0,0,0,0)'}, + endpoint:_endpoint, + anchor:floatingAnchor, + source:n + }); + + jpc = connectorSelector(); + if (jpc == null) { + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = new Connection({ + sourceEndpoint:self, + targetEndpoint:floatingEndpoint, + source:_getElementObject(_element), + target:_getElementObject(n), + anchors:[self.anchor, floatingAnchor], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + connector: params.connector + }); + // todo ...unregister on stop + self.addConnection(jpc); + } else { + existingJpc = true; + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; + // probably we should remove the connection? and add it back if the user + // does not drop it somewhere proper. + self.removeConnection(jpc); + if (anchorIdx == 0){ + existingJpcParams = [jpc.source, jpc.sourceId]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + }else { + existingJpcParams = [jpc.target, jpc.targetId]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // todo unregister on stop + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + }; + + var dragOptions = params.dragOptions || { }; + + var defaultOpts = jsPlumb.extend({}, jsPlumb.CurrentLibrary.defaultDragOptions); + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + var startEvent = jsPlumb.CurrentLibrary.dragEvents['start']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements([floatingEndpoint.canvas, n, inPlaceCopy.canvas]); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + if (_reattach) { + jpc.floatingAnchorIndex = null; + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } + else { + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeElement(jpc.canvas); + } + } else { + _removeElement(jpc.canvas); + self.removeConnection(jpc); + } + } + jpc = null; + delete floatingEndpoint; + delete inPlaceCopy; + self.paint(); + } + ); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + }; + + // connector target + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend({}, dropOptions); + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + dropOptions[dropEvent]= _wrap(dropOptions[dropEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable), "dragId"); + var elId = _getAttribute(_getElementObject(draggable),"elId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // remove this jpc from the current endpoint + jpc.endpoints[idx].removeConnection(jpc); + if (jpc.suspendedEndpoint) + jpc.suspendedEndpoint.removeConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + _initDraggableIfNecessary(_element, _elementId, params.draggable, {}); + jsPlumb.repaint(elId); + + delete floatingConnections[id]; + }); + + dropOptions[overEvent]= _wrap(dropOptions[overEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + }); + + jsPlumb.CurrentLibrary.initDroppable(_getElementObject(self.canvas), dropOptions); + } + + return self; + }; + + /* + Class: jsPlumb + + This is the main entry point to jsPlumb. There are a bunch of static methods you can + use to connect/disconnect etc. Some methods are also added to jQuery's "$.fn" object itself, but + in general jsPlumb is moving away from that model, preferring instead the static class + approach. One of the reasons for this is that with no methods added to the jQuery $.fn object, + it will be easier to provide support for other libraries such as MooTools. + */ + var jsPlumb = window.jsPlumb = { + + /* + Property: Defaults + + These are the default settings for jsPlumb, that is what will be used if you do not supply specific pieces of information + to the various API calls. A convenient way to implement your own look and feel can be to override these defaults by including a script + somewhere after the jsPlumb include, but before you make any calls to jsPlumb, for instance in this example we set the PaintStyle to be + a blue line of 27 pixels: + + > jsPlumb.Defaults.PaintStyle = { lineWidth:27, strokeStyle:'blue' } + + */ + Defaults : { + Anchors : [ null, null ], + Connector : null, + DragOptions: { }, + DropOptions: { }, + Endpoint : null, + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : null }, + EndpointStyles : [ null, null ], + MaxConnections : null, + PaintStyle : { lineWidth : 10, strokeStyle : 'red' }, + Scope : "_jsPlumb_DefaultScope" + }, + + /* + Property: connectorClass + + The CSS class to set on Connection canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + connectorClass : '_jsPlumb_connector', + + /* + Property: endpointClass + + The CSS class to set on Endpoint canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + endpointClass : '_jsPlumb_endpoint', + + /* + Property: Anchors + + Default jsPlumb Anchors. These are supplied in the file jquery.jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form jquery.jsPlumb-all-x.x.x.js. You can provide your own Anchors by supplying them in a script that is loaded after jsPlumb, for instance: + + > jsPlumb.Anchors.MyAnchor = { ....anchor code here. see the documentation. } + */ + Anchors : {}, + + /* + Property: Connectors + + Default jsPlumb Connectors. These are supplied in the file jquery.jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form jquery.jsPlumb-all-x.x.x.js. You can provide your own Connectors by supplying them in a script that is loaded after jsPlumb, for instance: + + > jsPlumb.Connectors.MyConnector = { ....connector code here. see the documentation. } + */ + Connectors : {}, + + /* + Property: Endpoints + + Default jsPlumb Endpoints. These are supplied in the file jquery.jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form jquery.jsPlumb-all-x.x.x.js. You can provide your own Endpoints by supplying them in a script that is loaded after jsPlumb, for instance: + + > jsPlumb.Endpoints.MyEndpoint = { ....endpoint code here. see the documentation. } + */ + Endpoints : {}, + + /* + Function: addEndpoint + + Adds an Endpoint to a given element. + + Parameters: + target - Element to add the endpoint to. either an element id, or a jQuery object representing some element. + params - Object containing Endpoint options (more info required) + + Returns: + + The newly created Endpoint. + + See Also: + + + */ + addEndpoint : function(target, params) { + params = jsPlumb.extend({}, params); + var el = _getElementObject(target); + var id = _getAttribute(target,"id"); + params.source = el; + _updateOffset(id); + var e = new Endpoint(params); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id]; + var myWH = sizes[id]; + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH); + e.paint(anchorLoc); + return e; + }, + + /* + Function: addEndpoint + + Adds a list of Endpoints to a given element. + + Parameters: + + target - element to add the endpoint to. either an element id, or a jQuery object representing some element. + endpoints - List of objects containing Endpoint options. one Endpoint is created for each entry in this list. + + Returns: + + List of newly created Endpoints, one for each entry in the 'endpoints' argument. + + See Also: + + + */ + addEndpoints : function(target, endpoints) { + var results = []; + for (var i = 0; i < endpoints.length; i++) { + results.push(jsPlumb.addEndpoint(target, endpoints[i])); + } + return results; + }, + + /* + Function: animate + + Wrapper around standard jQuery animate function; injects a call to jsPlumb in the 'step' function (creating it if necessary). + This only supports the two-arg version of the animate call in jQuery - the one that takes an 'options' object as the second arg. + + Parameters: + + el - Element to animate. Either an id, or a jQuery object representing the element. + properties - The 'properties' argument you want passed to the standard jQuery animate call. + options - The 'options' argument you want passed to the standard jQuery animate call. + + Returns: + + void + */ + animate : function(el, properties, options) { + var ele = _getElementObject(el); + var id = _getAttribute(el,"id"); + //TODO this is not agnostic yet. + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + options[stepFunction] = _wrap(options[stepFunction], function() + { + jsPlumb.repaint(id); + }); + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }, + + /* + Function: connect + + Establishes a connection between two elements. + + Parameters: + + params - Object containing setup for the connection. see documentation. + + Returns: + + The newly created Connection. + + */ + connect : function(params) { + if (params.sourceEndpoint && params.sourceEndpoint.isFull()) { + _log("could not add connection; source endpoint is full"); + return; + } + + if (params.targetEndpoint && params.targetEndpoint.isFull()) { + _log("could not add connection; target endpoint is full"); + return; + } + + var jpc = new Connection(params); + + // register endpoints for the element. todo: is the test below sufficient? or should we test if the endpoint is already in the list, + // and add it only then? perhaps _addToList could be overloaded with a a 'test for existence first' parameter? + //_addToList(endpointsByElement, jpc.sourceId, jpc.endpoints[0]); + //_addToList(endpointsByElement, jpc.targetId, jpc.endpoints[1]); + + if (!params.sourceEndpoint) _addToList(endpointsByElement, jpc.sourceId, jpc.endpoints[0]); + if (!params.targetEndpoint) _addToList(endpointsByElement, jpc.targetId, jpc.endpoints[1]); + + jpc.endpoints[0].addConnection(jpc); + jpc.endpoints[1].addConnection(jpc); + + // force a paint + _draw(jpc.source); + + return jpc; + + }, + + /** + * not implemented yet. params object will have sourceEndpoint and targetEndpoint members; these will be Endpoints. + connectEndpoints : function(params) { + var jpc = Connection(params); + + },*/ + + /* + Function: detach + + Removes a connection. + + Parameters: + + sourceId - Id of the first element in the connection. A String. + targetId - iI of the second element in the connection. A String. + + Returns: + + true if successful, false if not. + */ + detach : function(sourceId, targetId) { + var f = function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + return true; + } + }; + + // todo: how to cleanup the actual storage? a third arg to _operation? + _operation(sourceId, f); + }, + + /* + Function: detachAll + + Removes all an element's connections. + + Parameters: + + el - either the id of the element, or a jQuery object for the element. + + Returns: + + void + */ + detachAll : function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + var c = endpoints[i].connections.length; + if (c > 0) { + for (var j = 0; j < c; j++) { + var jpc = endpoints[i].connections[0]; + _removeElement(jpc.canvas); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + } + } + } + } + }, + + /* + Function: detachEverything + + Remove all Connections from all elements, but leaves Endpoints in place. + + Returns: + + void + + See Also: + + + */ + detachEverything : function() { + for(var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + var c = endpoints[i].connections.length; + if (c > 0) { + for (var j = 0; j < c; j++) { + var jpc = endpoints[i].connections[0]; + _removeElement(jpc.canvas); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + } + } + } + } + } + }, + + extend : function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }, + + /* + Function: hide + + Sets an element's connections to be hidden. + + Parameters: + + el - either the id of the element, or a jQuery object for the element. + + Returns: + + void + */ + hide : function(el) { + _setVisible(el, "none"); + }, + + /* + Function: makeAnchor + + Creates an anchor with the given params. + + Parameters: + + x - the x location of the anchor as a fraction of the total width. + y - the y location of the anchor as a fraction of the total height. + xOrientation - value indicating the general direction a connection from the anchor should go in, in the x direction. + yOrientation - value indicating the general direction a connection from the anchor should go in, in the y direction. + xOffset - a fixed offset that should be applied in the x direction that should be applied after the x position has been figured out. optional. defaults to 0. + yOffset - a fixed offset that should be applied in the y direction that should be applied after the y position has been figured out. optional. defaults to 0. + + Returns: + + The newly created Anchor. + */ + makeAnchor : function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards compatibility if we are given only one value we assume it's a call in the old form. + var params = {}; + if (arguments.length == 1) jsPlumb.extend(params, x); + else { + params = {x:x, y:y}; + if (arguments.length >= 4) { + params.orientation = [arguments[2], arguments[3]]; + } + if (arguments.length == 6) params.offsets = [arguments[4], arguments[5]]; + } + return new Anchor(params); + }, + + + /* + Function: repaint + + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + + el - either the id of the element or a jQuery object representing the element. + + Returns: + + void + + See Also: + + + */ + repaint : function(el) { + + var _processElement = function(el) { + var ele = _getElementObject(el); + _draw(ele); + }; + + // TODO: support a jQuery result object too! + + // support both lists... + if (typeof el =='object') { + for (var i = 0; i < el.length; i++) + _processElement(el[i]); + } // ...and single strings. + else _processElement(el); + }, + + /* + Function: repaintEverything + + Repaints all connections. + + Returns: + + void + + See Also: + + + */ + repaintEverything : function() { + for (var elId in endpointsByElement) { + _draw(_getElementObject(elId)); + } + }, + + /* + Function: removeAllEndpoints + + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + + el - either an element id, or a jQuery object for an element. + + Returns: + + void + + See Also: + + + */ + removeAllEndpoints : function(el) { + var elId = _getAttribute(el, "id"); + // first remove all Connections. + jsPlumb.detachAll(elId); + var ebe = endpointsByElement[elId]; + for (var i in ebe) { + _removeElement(ebe[i].canvas); + } + endpointsByElement[elId] = []; + }, + + /* + Function: removeEndpoint + + Removes the given Endpoint from the given element. + + Parameters: + + el - either an element id, or a jQuery object for an element. + endpoint - Endpoint to remove. this is an Endpoint object, such as would have been returned from a call to addEndpoint. + + Returns: + + void + + See Also: + + + */ + removeEndpoint : function(el, endpoint) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + if (ebe) { + if(_removeFromList(endpointsByElement, elId, endpoint)) + _removeElement(endpoint.canvas); + } + }, + + /* + Function: setAutomaticRepaint + + Sets/unsets automatic repaint on window resize. + + Parameters: + + value - whether or not to automatically repaint when the window is resized. + + Returns: + + void + */ + setAutomaticRepaint : function(value) { + automaticRepaint = value; + }, + + /* + Function: setDefaultNewCanvasSize + + Sets the default size jsPlumb will use for a new canvas (we create a square canvas so one value is all that is required). + This is a hack for IE, because ExplorerCanvas seems to need for a canvas to be larger than what you are going to draw on + it at initialisation time. The default value of this is 1200 pixels, which is quite large, but if for some reason you're + drawing connectors that are bigger, you should adjust this value appropriately. + + Parameters: + + size - The default size to use. jsPlumb will use a square canvas so you need only supply one value. + + Returns: + + void + */ + setDefaultNewCanvasSize : function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }, + + /* + Function: setDraggable + + Sets whether or not a given element is draggable, regardless of what any plumb command may request. + + Parameters: + + el - either the id for the element, or a jQuery object representing the element. + + Returns: + + void + */ + setDraggable: _setDraggable, + + /* + Function: setDraggableByDefault + + Sets whether or not elements are draggable by default. Default for this is true. + + Parameters: + draggable - value to set + + Returns: + + void + */ + setDraggableByDefault: function(draggable) { + _draggableByDefault = draggable; + }, + + setDebugLog: function(debugLog) { + log = debugLog; + }, + + /* + Function: setRepaintFunction + + Sets the function to fire when the window size has changed and a repaint was fired. + + Parameters: + f - Function to execute. + + Returns: + void + */ + setRepaintFunction : function(f) { + repaintFunction = f; + }, + + /* + Function: show + + Sets an element's connections to be visible. + + Parameters: + el - either the id of the element, or a jQuery object for the element. + + Returns: + void + */ + show : function(el) { + _setVisible(el, "block"); + }, + + /* + Function: sizeCanvas + + Helper to size a canvas. You would typically use this when writing your own Connector or Endpoint implementation. + + Parameters: + x - [int] x position for the Canvas origin + y - [int] y position for the Canvas origin + w - [int] width of the canvas + h - [int] height of the canvas + + Returns: + void + */ + sizeCanvas : function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; canvas.height = h; + canvas.style.width = w + "px"; canvas.width = w; + canvas.style.left = x + "px"; canvas.style.top = y + "px"; + } + }, + + /** + * gets some test hooks. nothing writable. + */ + getTestHarness : function() { + return { + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + + findIndex : _findIndex + }; + }, + + /** + * Toggles visibility of an element's connections. kept for backwards compatibility + */ + toggle : _toggleVisible, + + /* + Function: toggleVisible + + Toggles visibility of an element's connections. + + Parameters: + el - either the element's id, or a jQuery object representing the element. + + Returns: + void, but should be updated to return the current state + */ + //TODO: update this method to return the current state. + toggleVisible : _toggleVisible, + + /* + Function: toggleDraggable + + Toggles draggability (sic) of an element's connections. + + Parameters: + el - either the element's id, or a jQuery object representing the element. + + Returns: + The current draggable state. + */ + toggleDraggable : _toggleDraggable, + + /* + Function: unload + + Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element + + Returns: + void + */ + unload : function() { + delete endpointsByElement; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + }, + + /* + Function: wrap + Helper method to wrap an existing function with one of your own. This is used by the various + implementations to wrap event callbacks for drag/drop etc; it allows jsPlumb to be + transparent in its handling of these things. If a user supplies their own event callback, + for anything, it will always be called. + Parameters: + + */ + wrap : _wrap + }; + +})(); + + +//jQuery plugin code +(function($){ + /** + * plumbs the results of the selector to some target, using the given options if supplied, + * or the defaults otherwise. + */ + $.fn.plumb = function(options) { + var options = $.extend({}, options); + + return this.each(function() + { + var params = $.extend({source:$(this)}, options); + jsPlumb.connect(params); + }); + }; + + /** + * detaches the results of the selector from the given target or list of targets - 'target' + * may be a String or a List. + */ + $.fn.detach = function(target) { + return this.each(function() + { + var id = $(this).attr("id"); + if (typeof target == 'string') target = [target]; + for (var i = 0; i < target.length; i++) + jsPlumb.detach(id, target[i]); + }); + }; + + /** + * detaches the results from the selector from all connections. + */ + $.fn.detachAll = function() { + return this.each(function() + { + var id = $(this).attr("id"); + jsPlumb.detachAll(id); + }); + }; + + /** + * adds an endpoint to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoint = function(options) { + var addedEndpoints = []; + this.each(function() + { + addedEndpoints.push(jsPlumb.addEndpoint($(this).attr("id"), options)); + }); + return addedEndpoints[0]; + }; + + /** + * adds a list of endpoints to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoints = function(endpoints) { + var addedEndpoints = []; + return this.each(function() + { + var e = jsPlumb.addEndpoints($(this).attr("id"), endpoints); + for (var i = 0; i < e.length; i++) addedEndpoints.push(e[i]); + }); + return addedEndpoints; + }; + + /** + * remove the endpoint, if it exists, deleting its UI elements etc. + */ + $.fn.removeEndpoint = function(endpoint) { + this.each(function() + { + jsPlumb.removeEndpoint($(this).attr("id"), endpoint); + }); + }; + +})(jQuery); + + +/* + * the library agnostic functions, such as find offset, get id, get attribute, extend etc. + */ +(function() { + + jsPlumb.CurrentLibrary = { + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step', + 'over':'over', 'out':'out', 'drop':'drop' + }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + getElementObject : function(el) { + return typeof(el)=='string' ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options) { + // remove helper directive if present. we know what's best! + options.helper = null; + //TODO: if 'revert' is set on the options it causes end points to animate back to + // where they came from, if the connection is aborted. do we care? probably not. + // the todo is to decide whether we care or not. + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.draggable(options); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.droppable(options); + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[1]; + return ui.absolutePosition || ui.offset; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + } + }; +})(); + +(function() { + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * getOrientation : function() { return [ox, oy]; } + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + jsPlumb.Anchors.TopCenter = jsPlumb.makeAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors.BottomCenter = jsPlumb.makeAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors.LeftMiddle = jsPlumb.makeAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors.RightMiddle = jsPlumb.makeAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors.Center = jsPlumb.makeAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors.TopRight = jsPlumb.makeAnchor(1, 0, 0,-1); + jsPlumb.Anchors.BottomRight = jsPlumb.makeAnchor(1, 1, 0, 1); + jsPlumb.Anchors.TopLeft = jsPlumb.makeAnchor(0, 0, 0, -1); + jsPlumb.Anchors.BottomLeft = jsPlumb.makeAnchor(0, 1, 0, 1); + + + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (w < 2 * lineWidth) { + w = 2 * lineWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - lineWidth; + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < 2 * lineWidth) { + // minimum size is 2 * line Width + h = 2 * lineWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - lineWidth; + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + var sx = sourcePos[0] < targetPos[0] ? w-xo : xo; + var sy = sourcePos[1] < targetPos[1] ? h-yo : yo; + var tx = sourcePos[0] < targetPos[0] ? xo : w-xo; + var ty = sourcePos[1] < targetPos[1] ? yo : h-yo; + var retVal = [ x, y, w, h, sx, sy, tx, ty ]; + + return retVal; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + }; + + /** + * This Connector draws a Bezier curve with two control points. + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + jsPlumb.Connectors.Bezier = function(curviness) { + + var self = this; + this.majorAnchor = curviness || 150; + this.minorAnchor = 10; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) + { + lineWidth = lineWidth || 0; + var w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth, h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + var canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2), canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + var sx = sourcePos[0] < targetPos[0] ? w - (lineWidth/2): (lineWidth/2), sy = sourcePos[1] < targetPos[1] ? h-(lineWidth/2) : (lineWidth/2); + var tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : w-(lineWidth/2), ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : h-(lineWidth/2); + var CP = self._findControlPoint([sx,sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + var CP2 = self._findControlPoint([tx,ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(sx,tx); var minx2 = Math.min(CP[0], CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(sx,tx); var maxx2 = Math.max(CP[0], CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > w) w = maxx; + if (minx < 0) { + canvasX += minx; var ox = Math.abs(minx); + w += ox; CP[0] += ox; sx += ox; tx +=ox; CP2[0] += ox; + } + + var miny1 = Math.min(sy,ty); var miny2 = Math.min(CP[1], CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(sy,ty); var maxy2 = Math.max(CP[1], CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > h) h = maxy; + if (miny < 0) { + canvasY += miny; var oy = Math.abs(miny); + h += oy; CP[1] += oy; sy += oy; ty +=oy; CP2[1] += oy; + } + + // return [ canvasx, canvasy, canvasWidth, canvasHeight, + // sourceX, sourceY, targetX, targetY, + // controlPoint1_X, controlPoint1_Y, controlPoint2_X, controlPoint2_Y + return [canvasX, canvasY, w, h, sx, sy, tx, ty, CP[0], CP[1], CP2[0], CP2[1] ]; + }; + + this.paint = function(d, ctx) { + + /*var img = new Image(); + img.src = "../img/pattern.jpg"; + ctx.fillStyle = ctx.createPattern(img, 'repeat-y');*/ + + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + //ctx.fill(); + } + }; + + + /** + * Types of endpoint UIs. we supply four - a circle of default radius 10px, a rectangle of + * default size 20x20, an image (with no default), and a Triangle, of default size 15. + * you can supply others of these if you want to - see the documentation for a howto. + */ + + /** + * a round endpoint, with default radius 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + } + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * A Rectangular endpoint, with default size 20x20. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + //todo: the fillStyle needs some thought. we want to support a few options: + // 1. nothing supplied; use the stroke color or the default if no stroke color. + // 2. a fill color supplied - use it + // 3. a gradient supplied - use it + // 4. setting the endpoint to the same color as the bg of the element it is attached to. + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + jsPlumb.Endpoints.Triangle = function(params) { + + params = params || { width:15, height:15 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - width/2; + var y = anchorPoint[1] - height/2; + + jsPlumb.sizeCanvas(canvas, x, y, width, height); + + var ctx = canvas.getContext('2d'); + var + offsetX = 0, + offsetY = 0, + angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = endpointStyle.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + ctx.fill(); + + }; + }; + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' property in the params object.. + */ + jsPlumb.Endpoints.Image = function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + }; +})(); diff --git a/archive/1.2/jsPlumb-1.2-RC1.js b/archive/1.2/jsPlumb-1.2-RC1.js new file mode 100644 index 000000000..6083f0e49 --- /dev/null +++ b/archive/1.2/jsPlumb-1.2-RC1.js @@ -0,0 +1,1577 @@ +/* + * jsPlumb 1.2-RC1 + * + * Provides a way to visually connect elements on an HTML page. + * + * 1.1.1 contains bugfixes and API additions on 1.1.0. + * + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + */ + +(function() { + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + + var log = null; + + var repaintFunction = function() { jsPlumb.repaintEverything(); }; + var automaticRepaint = true; + function repaintEverything() { + if (automaticRepaint) + repaintFunction(); + }; + var resizeTimer = null; + + /*TODO: abstract this out from jQuery too! but how...because jsPlumb is not loaded yet. + $(window).bind('resize', function() { + if (resizeTimer) clearTimeout(resizeTimer); + resizeTimer = setTimeout(repaintEverything, 100); + });*/ + + /** + * map of element id -> endpoint lists. an element can have an arbitrary number of endpoints on it, + * and not all of them have to be connected to anything. + */ + var endpointsByElement = {}; + var offsets = []; + var floatingConnections = {}; + var draggableStates = {}; + var _draggableByDefault = true; + var sizes = []; + + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + var _findIndex = function( a, v, b, s ) { + var _eq = function(o1, o2) { + if (o1 === o2) return true; + else if (typeof o1 == 'object' && typeof o2 == 'object') { + var same = true; + for(var propertyName in o1) { + if(!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for(var propertyName in o2) { + if(!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for( var i = +b || 0, l = a.length; i < l; i++ ) { + if( _eq(a[i], v)) return i; + } + return -1; + }; + + /** + * helper method to add an item to a list, creating the list if it does not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + }; + + /** + * Handles the dragging of an element. + * @param element jQuery element + * @param ui UI object from current library's event system + */ + var _draw = function(element, ui) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints) { + _updateOffset(id, ui); + var myOffset = offsets[id]; + var myWH = sizes[id]; + // loop through endpoints for this element + for (var i = 0; i < endpoints.length; i++) { + var e = endpoints[i]; + var anchorLoc = endpoints[i].anchor.compute([myOffset.left, myOffset.top], myWH); + e.paint(anchorLoc); + var l = e.connections; + for (var j = 0; j < l.length; j++) + l[j].paint(id, ui); // ...and paint them. + } + } + }; + + /** + * helper function: the second argument is a function taking two args - the first is an id + * ui element from the current library (or list of them), and the second is the function + * to run. + */ + var _elementProxy = function(element, fn) { + var retVal = null; + if (typeof element == 'object' && element.length) { + retVal = []; + for (var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]); + var id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } + else { + var el = _getElementObject(element); + var id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + + return retVal; + }; + + /** + * gets the named attribute from the given element (id or element object) + */ + var _getAttribute = function(el, attName) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + return jsPlumb.CurrentLibrary.getAttribute(ele, attName); + }; + + var _setAttribute = function(el, attName, attValue) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + jsPlumb.CurrentLibrary.setAttribute(ele, attName, attValue); + }; + + var _addClass = function(el, clazz) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + jsPlumb.CurrentLibrary.addClass(ele, clazz); + }; + + var _getElementObject = function(elId) { + return jsPlumb.CurrentLibrary.getElementObject(elId); + }; + + var _getOffset = function(el) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + return jsPlumb.CurrentLibrary.getOffset(ele); + }; + + var _getSize = function(el) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + return jsPlumb.CurrentLibrary.getSize(ele); + }; + + var _appendCanvas = function(canvas) { + document.body.appendChild(canvas); + }; + + /** + * gets an id for the given element, creating and setting one if necessary. + */ + var _getId = function(element) { + var id = _getAttribute(element, "id"); + if (!id) { + id = "_jsPlumb_" + new String((new Date()).getTime()); + _setAttribute(element, "id", id); + } + return id; + }; + + /** + * inits a draggable if it's not already initialised. + * + * TODO: we need to hook in each library to this method. they need to be given the opportunity to + * wrap/insert lifecycle functions, because each library does different things. for instance, jQuery + * supports the notion of 'revert', which will clean up a dragged endpoint; MooTools does not. jQuery + * also supports 'opacity' and MooTools does not; jQuery supports z-index of the draggable; MooTools + * does not. i could go on. the point is...oh. initDraggable can do this. ok. + */ + var _initDraggableIfNecessary = function(element, elementId, isDraggable, dragOptions) { + // dragging + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable && jsPlumb.CurrentLibrary.isDragSupported(element)) { + var options = dragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend({}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var initDrag = function(element, elementId, dragFunc) { + options[dragEvent] = _wrap(options[dragEvent], dragFunc); + var draggable = draggableStates[elementId]; + // TODO: this is still jQuery specific. + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + }; + initDrag(element, elementId, function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + } + }; + + var _log = function(msg) { + // not implemented. yet. + } + + /** + * helper to create a canvas. + * @param clazz optional class name for the canvas. + */ + var _newCanvas = function(clazz) { + var canvas = document.createElement("canvas"); + _appendCanvas(canvas); + canvas.style.position="absolute"; + if (clazz) { canvas.className=clazz; } + _getId(canvas); // set an id. + + if (ie) { + // for IE we have to set a big canvas size. actually you can override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + /** + * performs the given function operation on all the connections found for the given element + * id; this means we find all the endpoints for the given element, and then for each endpoint + * find the connectors connected to it. then we pass each connection in to the given + * function. + */ + var _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + for (var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }; + /** + * perform an operation on all elements. + */ + var _operationOnAll = function(func) { + for (var elId in endpointsByElement) { + _operation(elId, func); + } + }; + /** + * helper to remove an element from the DOM. + */ + var _removeElement = function(element) { + if (element != null) { + try { document.body.removeChild(element); } + catch (e) { } + } + }; + /** + * helper to remove a list of elements from the DOM. + */ + var _removeElements = function(elements) { + for (var i in elements) + _removeElement(elements[i]); + }; + /** + * helper method to remove an item from a list. + */ + var _removeFromList = function(map, key, value) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete( l[i] ); + l.splice( i, 1 ); + return true; + } + } + return false; + }; + /** + * Sets whether or not the given element(s) should be draggable, regardless of what a particular + * plumb command may request. + * + * @param element May be a string, a jQuery elements, or a list of strings/jquery elements. + * @param draggable Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + var _helper = function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }; + + return _elementProxy(element, _helper); + }; + /** + * private method to do the business of hiding/showing. + * @param el either Id of the element in question or a jquery object for the element. + * @param state String specifying a value for the css 'display' property ('block' or 'none'). + */ + var _setVisible = function(el, state) { + var elId = _getAttribute(el, "id"); + var f = function(jpc) { + jpc.canvas.style.display = state; + }; + _operation(elId, f); + }; + /** + * toggles the draggable state of the given element(s). + * @param el either an id, or a jquery object, or a list of ids/jquery objects. + */ + var _toggleDraggable = function(el) { + var fn = function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + //el.draggable("option", "disabled", !state); + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }; + return _elementProxy(el, fn); + }; + /** + * private method to do the business of toggling hiding/showing. + * @param elId Id of the element in question + */ + var _toggleVisible = function(elId) { + var f = function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }; + + _operation(elId, f); + + //todo this should call _elementProxy, and pass in the _operation(elId, f) call as a function. cos _toggleDraggable does that. + }; + /** + * updates the offset and size for a given element, and stores the values. + * if 'offset' is not null we use that (it would have been passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we use the offset, outerWidth and outerHeight methods to get + * the current values. + */ + var _updateOffset = function(elId, offset, recalc) { + if (recalc || offset == null) { // if forced repaint or no offset available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + sizes[elId] = _getSize(elId); + offsets[elId] = _getOffset(elId); + } else { + offsets[elId] = offset; + } + }; + + /** + * wraps one function with another, creating a placeholder for the wrapped function + * if it was null. this is used to wrap the various drag/drop event functions - to allow + * jsPlumb to be notified of important lifecycle events without imposing itself on the user's + * drap/drop functionality. + * TODO: determine whether or not we should support an error handler concept, if one of the functions fails. + */ + var _wrap = function(wrappedFunction, newFunction) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + try { newFunction.apply(this, arguments); } + catch (e) { } + try { wrappedFunction.apply(this, arguments); } + catch (e) { } + }; + } + + /* + Class: Anchor + Models a position relative to the origin of some element that an Endpoint can be located. + */ + /* + Function: Anchor + + Constructor for the Anchor class + + Parameters: + params - Anchor parameters. This should contain three values, and may optionally have an 'offsets' argument: + + - x : the x location of the anchor as a fraction of the total width. + - y : the y location of the anchor as a fraction of the total height. + - orientation : an [x,y] array indicating the general direction a connection from the anchor should go in. for more info on this, see the documentation, or the docs in jquery-jsPlumb-defaults-XXX.js for the default Anchors. + - offsets : an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. may be null. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; this.y = params.y || 0; + var orientation = params.orientation || [0,0]; + this.offsets = params.offsets || [0,0]; + this.compute = function(xy, wh, txy, twh) { + return [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + } + this.getOrientation = function() { return orientation; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from its position relative to the anchor it is floating relative to. + * + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(refCanvas); + + // these are used to store the current relative position of our anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating anchor is hovering over another anchor. + var orientation = null; + + this.compute = function(xy, wh, txy, twh) { + // set these for the getOrientation method to use. + xDir = 0;//xy[0] < txy[0] ? -1 : xy[0] == txy[0] ? 0 : 1; + yDir = 0;//xy[1] < txy[1] ? -1 : xy[1] == txy[1] ? 0 : 1; + return [xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other anchor: if it declares zero for some direction, we declare zero too. + // this might not be the most awesome. perhaps we can come up with a better way. it's just so that the line we draw looks + // like it makes sense. maybe this wont make sense. + return [Math.abs(o[0]) * xDir * -1, Math.abs(o[1]) * yDir * -1]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering over another anchor; + * we want to assume that anchor's orientation for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no longer hovering + * over another anchor; we should resume calculating orientation as we normally do. + */ + this.out = function() { + orientation = null; + }; + }; + + // ************** connection + // **************************************** + /** + * allowed params: + * source: source element (string or a jQuery element) (required) + * target: target element (string or a jQuery element) (required) + * + * anchors: optional array of anchor placements. defaults to BottomCenter for source + * and TopCenter for target. + */ + var Connection = function(params) { + + // ************** get the source and target and register the connection. ******************* + var self = this; + // get source and target as jQuery objects + this.source = _getElementObject(params.source); + this.target = _getElementObject(params.target); + this.sourceId = _getAttribute(this.source, "id"); + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + + // init endpoints + this.endpoints = []; + this.endpointStyles = []; + var prepareEndpoint = function(existing, index, params) { + if (existing) self.endpoints[index] = existing; + else { + if(!params.endpoints) params.endpoints = [null,null]; + var ep = params.endpoints[index] || params.endpoint || jsPlumb.Defaults.Endpoints[index] || jsPlumb.Defaults.Endpoint|| new jsPlumb.Endpoints.Dot(); + if (!params.endpointStyles) params.endpointStyles = [null,null]; + var es = params.endpointStyles[index] || params.endpointStyle || jsPlumb.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyle; + var a = params.anchors ? params.anchors[index] : jsPlumb.Defaults.Anchors[index] || jsPlumb.Anchors.BottomCenter; + self.endpoints[index] = new Endpoint({style:es, endpoint:ep, connections:[self], anchor:a, source:self.source }); + } + }; + + prepareEndpoint(params.sourceEndpoint, 0, params); + prepareEndpoint(params.targetEndpoint, 1, params); + + this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + this.paintStyle = this.endpoints[0].connectorStyle || this.endpoints[1].connectorStyle || params.paintStyle || jsPlumb.Defaults.PaintStyle; + + _updateOffset(this.sourceId); + _updateOffset(this.targetId); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var anchorLoc = this.endpoints[0].anchor.compute([myOffset.left, myOffset.top], myWH); + this.endpoints[0].paint(anchorLoc); + myOffset = offsets[this.targetId]; myWH = sizes[this.targetId]; + anchorLoc = this.endpoints[1].anchor.compute([myOffset.left, myOffset.top], myWH); + this.endpoints[1].paint(anchorLoc); + + // *************** create canvas on which the connection will be drawn ************ + var canvas = _newCanvas(jsPlumb.connectorClass); + this.canvas = canvas; + + /** + * paints the connection. + * @param elId Id of the element that is in motion + * @param ui current library's event system ui object (present if we came from a drag to get here) + * @param recalc whether or not to recalculate element sizes. this is true if a repaint caused this to be painted. + */ + this.paint = function(elId, ui, recalc) { + + if (log) log.debug("Painting Connection; element in motion is " + elId + "; ui is [" + ui + "]; recalc is [" + recalc + "]"); + + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = !(elId == this.sourceId); + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + if (this.canvas.getContext) { + + _updateOffset(elId, ui, recalc); + if (recalc) _updateOffset(tId); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var myOffset = offsets[elId]; + var otherOffset = offsets[tId]; + var myWH = sizes[elId]; + var otherWH = sizes[tId]; + + var ctx = canvas.getContext('2d'); + var sAnchorP = this.endpoints[sIdx].anchor.compute([myOffset.left, myOffset.top], myWH, [otherOffset.left, otherOffset.top], otherWH); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.compute([otherOffset.left, otherOffset.top], otherWH, [myOffset.left, myOffset.top], myWH); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, this.paintStyle.lineWidth); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + jsPlumb.extend(ctx, this.paintStyle); + + if (this.paintStyle.gradient && !ie) { + var g = swap ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + for (var i = 0; i < this.paintStyle.gradient.stops.length; i++) + g.addColorStop(this.paintStyle.gradient.stops[i][0],this.paintStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + + this.connector.paint(dim, ctx); + } + }; + + this.repaint = function() { + this.paint(this.sourceId, null, true); + }; + + _initDraggableIfNecessary(self.source, self.sourceId, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, self.targetId, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + }; + + /* + Class: Endpoint + + Models an endpoint. Can have one to N connections emanating from it (although how to handle that in the UI is + a very good question). also has a Canvas and paint style. + + */ + + /* + Function: Endpoint + + This is the Endpoint class constructor. + + Parameters: + + anchor - anchor for the endpoint, of type jsPlumb.Anchor. may be null. + endpoint - endpoint object, of type jsPlumb.Endpoint. may be null. + style - endpoint style, a js object. may be null. + source - element the endpoint is attached to, of type jquery object. Required. + canvas - canvas element to use. may be, and most often is, null. + connections - optional list of connections to configure the endpoint with. + isSource - boolean. indicates the endpoint can act as a source of new connections. optional. + dragOptions - if isSource is set to true, you can supply arguments for the jquery draggable method. optional. + connectionStyle - if isSource is set to true, this is the paint style for connections from this endpoint. optional. + connector - optional connector type to use. + isTarget - boolean. indicates the endpoint can act as a target of new connections. optional. + dropOptions - if isTarget is set to true, you can supply arguments for the jquery droppable method. optional. + reattach - optional boolean that determines whether or not the connections reattach after they + have been dragged off an endpoint and left floating. defaults to false: connections + dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + params = params || {}; + // make a copy. then we can use the wrapper function. + jsPlumb.extend({}, params); + var self = this; + self.anchor = params.anchor || jsPlumb.Anchors.TopCenter; + var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot(); + var _style = params.style || jsPlumb.Defaults.EndpointStyle; + this.connectorStyle = params.connectorStyle; + this.connector = params.connector; + var _element = params.source; + var _elementId = _getAttribute(_element, "id"); + var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of. + this.canvas = params.canvas || _newCanvas(jsPlumb.endpointClass); + this.connections = params.connections || []; + var _reattach = params.reattach || false; + var floatingEndpoint = null; + var inPlaceCopy = null; + this.addConnection = function(connection) { + + self.connections.push(connection); + }; + this.removeConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) + self.connections.splice(idx, 1); + }; + this.makeInPlaceCopy = function() { + var e = new Endpoint({anchor:self.anchor, source:_element, style:_style, endpoint:_endpoint}); + return e; + }; + /** + * returns whether or not this endpoint is connected to the given endpoint. + * @param endpoint Endpoint to test. + * @since 1.1.1 + * + * todo: needs testing. will this work if the endpoint passed in is the source? + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + this.isFloating = function() { return floatingEndpoint != null; }; + /** + * first pass at default ConnectorSelector: returns the first connection, if we have any. + * modified a little, 5/10/2010: now it only returns a connector if we have not got equal to or more than _maxConnector connectors + * attached. otherwise it is assumed a new connector is ok. but note with this setup we can't select any other connection than the first + * one. what if this could return a list? that implies everything should work with a list - dragging etc. could be nasty. could also + * be cool. + */ + var connectorSelector = function() { + return self.connections.length == 0 || self.connections.length < _maxConnections ? null : self.connections[0]; + }; + + this.isFull = function() { return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); }; + + /** + * paints the Endpoint, recalculating offset and anchor positions if necessary. + */ + this.paint = function(anchorPoint, connectorPaintStyle, canvas) { + + if (log) log.debug("Painting Endpoint with elementId [" + _elementId + "]; anchorPoint is [" + anchorPoint + "]"); + + if (anchorPoint == null) { + // do we always want to force a repaint here? i dont think so! + var xy = offsets[_elementId]; + var wh = sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset(_elementId); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + anchorPoint = self.anchor.compute([xy.left, xy.top], wh); + } + _endpoint.paint(anchorPoint, self.anchor.getOrientation(), canvas || self.canvas, _style, connectorPaintStyle || _style); + }; + + + // is this a connection source? we make it draggable and have the drag listener + // maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + + // first the question is, how many connections are on this endpoint? if it's only one, then excellent. otherwise we will either need a way + // to select one connection from the list, or drag them all. if we had a pluggable 'ConnectorSelector' interface we could probably + // provide a way for people to implement their own UI components to do the connector selection. the question in that particular case would be how much + // information the interface needs from jsPlumb at execution time. if, however, we leave out the connector selection, and drag them all, + // that wouldn't be too hard to organise. perhaps that behaviour would be on a switch for the endpoint, or perhaps the ConnectorSelector + // interface returns a List, with the default implementation just returning everything. i think i like that. + // + // let's say for now that there is just one endpoint, cos we need to get this going before we can consider a list of them anyway. + // the major difference between that case and the case of a new endpoint is actually quite small - it's a question of where the + // Connection comes from. for a new one, we create a new one. obviously. otherwise we just get the jpc from the Endpoint + // (remember we're only assuming one connection right now). so all of the UI stuff we do to create the floating endpoint etc + // will still be valid, but when we stop dragging, we'll have to do something different. if we stop with a valid drop i think it will + // be the same process. but if we stop with an invalid drop we have to reset the Connection to how it was when we got it. + var start = function() { + + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + _appendCanvas(n); + // create and assign an id, and initialize the offset. + var id = "" + new String(new Date().getTime()); + _setAttribute(_getElementObject(n), "id", id); + _updateOffset(id); + // store the id of the dragging div and the source element. the drop function + // will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor({reference:self.anchor, referenceCanvas:self.canvas}); + floatingEndpoint = new Endpoint({ + style:{fillStyle:'rgba(0,0,0,0)'}, + endpoint:_endpoint, + anchor:floatingAnchor, + source:n + }); + + jpc = connectorSelector(); + if (jpc == null) { + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = new Connection({ + sourceEndpoint:self, + targetEndpoint:floatingEndpoint, + source:_getElementObject(_element), + target:_getElementObject(n), + anchors:[self.anchor, floatingAnchor], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + connector: params.connector + }); + // todo ...unregister on stop + self.addConnection(jpc); + } else { + existingJpc = true; + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; + // probably we should remove the connection? and add it back if the user + // does not drop it somewhere proper. + self.removeConnection(jpc); + if (anchorIdx == 0){ + existingJpcParams = [jpc.source, jpc.sourceId]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + }else { + existingJpcParams = [jpc.target, jpc.targetId]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // todo unregister on stop + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + }; + + var dragOptions = params.dragOptions || { }; + + var defaultOpts = jsPlumb.extend({}, jsPlumb.CurrentLibrary.defaultDragOptions); + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + var startEvent = jsPlumb.CurrentLibrary.dragEvents['start']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements([floatingEndpoint.canvas, n, inPlaceCopy.canvas]); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + if (_reattach) { + jpc.floatingAnchorIndex = null; + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } + else { + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeElement(jpc.canvas); + } + } else { + _removeElement(jpc.canvas); + self.removeConnection(jpc); + } + } + jpc = null; + delete floatingEndpoint; + delete inPlaceCopy; + self.paint(); + } + ); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + }; + + // connector target + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend({}, dropOptions); + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + dropOptions[dropEvent]= _wrap(dropOptions[dropEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable), "dragId"); + var elId = _getAttribute(_getElementObject(draggable),"elId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // remove this jpc from the current endpoint + jpc.endpoints[idx].removeConnection(jpc); + if (jpc.suspendedEndpoint) + jpc.suspendedEndpoint.removeConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + _initDraggableIfNecessary(_element, _elementId, params.draggable, {}); + jsPlumb.repaint(elId); + + delete floatingConnections[id]; + }); + + dropOptions[overEvent]= _wrap(dropOptions[overEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + }); + + jsPlumb.CurrentLibrary.initDroppable(_getElementObject(self.canvas), dropOptions); + } + + return self; + }; + + /* + Class: jsPlumb + + This is the main entry point to jsPlumb. There are a bunch of static methods you can + use to connect/disconnect etc. Some methods are also added to jQuery's "$.fn" object itself, but + in general jsPlumb is moving away from that model, preferring instead the static class + approach. One of the reasons for this is that with no methods added to the jQuery $.fn object, + it will be easier to provide support for other libraries such as MooTools. + */ + var jsPlumb = window.jsPlumb = { + + /* + Property: Defaults + + These are the default settings for jsPlumb, that is what will be used if you do not supply specific pieces of information + to the various API calls. A convenient way to implement your own look and feel can be to override these defaults by including a script + somewhere after the jsPlumb include, but before you make any calls to jsPlumb, for instance in this example we set the PaintStyle to be + a blue line of 27 pixels: + + > jsPlumb.Defaults.PaintStyle = { lineWidth:27, strokeStyle:'blue' } + + */ + Defaults : { + Anchors : [ null, null ], + Connector : null, + DragOptions: { }, + DropOptions: { }, + Endpoint : null, + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : null }, + EndpointStyles : [ null, null ], + MaxConnections : null, + PaintStyle : { lineWidth : 10, strokeStyle : 'red' }, + Scope : "_jsPlumb_DefaultScope" + }, + + /* + Property: connectorClass + + The CSS class to set on Connection canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + connectorClass : '_jsPlumb_connector', + + /* + Property: endpointClass + + The CSS class to set on Endpoint canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + endpointClass : '_jsPlumb_endpoint', + + /* + Property: Anchors + + Default jsPlumb Anchors. These are supplied in the file jquery.jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form jquery.jsPlumb-all-x.x.x.js. You can provide your own Anchors by supplying them in a script that is loaded after jsPlumb, for instance: + + > jsPlumb.Anchors.MyAnchor = { ....anchor code here. see the documentation. } + */ + Anchors : {}, + + /* + Property: Connectors + + Default jsPlumb Connectors. These are supplied in the file jquery.jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form jquery.jsPlumb-all-x.x.x.js. You can provide your own Connectors by supplying them in a script that is loaded after jsPlumb, for instance: + + > jsPlumb.Connectors.MyConnector = { ....connector code here. see the documentation. } + */ + Connectors : {}, + + /* + Property: Endpoints + + Default jsPlumb Endpoints. These are supplied in the file jquery.jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form jquery.jsPlumb-all-x.x.x.js. You can provide your own Endpoints by supplying them in a script that is loaded after jsPlumb, for instance: + + > jsPlumb.Endpoints.MyEndpoint = { ....endpoint code here. see the documentation. } + */ + Endpoints : {}, + + /* + Function: addEndpoint + + Adds an Endpoint to a given element. + + Parameters: + target - Element to add the endpoint to. either an element id, or a jQuery object representing some element. + params - Object containing Endpoint options (more info required) + + Returns: + + The newly created Endpoint. + + See Also: + + + */ + addEndpoint : function(target, params) { + params = jsPlumb.extend({}, params); + var el = _getElementObject(target); + var id = _getAttribute(target,"id"); + params.source = el; + _updateOffset(id); + var e = new Endpoint(params); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id]; + var myWH = sizes[id]; + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH); + e.paint(anchorLoc); + return e; + }, + + /* + Function: addEndpoint + + Adds a list of Endpoints to a given element. + + Parameters: + + target - element to add the endpoint to. either an element id, or a jQuery object representing some element. + endpoints - List of objects containing Endpoint options. one Endpoint is created for each entry in this list. + + Returns: + + List of newly created Endpoints, one for each entry in the 'endpoints' argument. + + See Also: + + + */ + addEndpoints : function(target, endpoints) { + var results = []; + for (var i = 0; i < endpoints.length; i++) { + results.push(jsPlumb.addEndpoint(target, endpoints[i])); + } + return results; + }, + + /* + Function: animate + + Wrapper around standard jQuery animate function; injects a call to jsPlumb in the 'step' function (creating it if necessary). + This only supports the two-arg version of the animate call in jQuery - the one that takes an 'options' object as the second arg. + + Parameters: + + el - Element to animate. Either an id, or a jQuery object representing the element. + properties - The 'properties' argument you want passed to the standard jQuery animate call. + options - The 'options' argument you want passed to the standard jQuery animate call. + + Returns: + + void + */ + animate : function(el, properties, options) { + var ele = _getElementObject(el); + var id = _getAttribute(el,"id"); + //TODO this is not agnostic yet. + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + options[stepFunction] = _wrap(options[stepFunction], function() + { + jsPlumb.repaint(id); + }); + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }, + + /* + Function: connect + + Establishes a connection between two elements. + + Parameters: + + params - Object containing setup for the connection. see documentation. + + Returns: + + The newly created Connection. + + */ + connect : function(params) { + if (params.sourceEndpoint && params.sourceEndpoint.isFull()) { + _log("could not add connection; source endpoint is full"); + return; + } + + if (params.targetEndpoint && params.targetEndpoint.isFull()) { + _log("could not add connection; target endpoint is full"); + return; + } + + var jpc = new Connection(params); + + // register endpoints for the element. todo: is the test below sufficient? or should we test if the endpoint is already in the list, + // and add it only then? perhaps _addToList could be overloaded with a a 'test for existence first' parameter? + //_addToList(endpointsByElement, jpc.sourceId, jpc.endpoints[0]); + //_addToList(endpointsByElement, jpc.targetId, jpc.endpoints[1]); + + if (!params.sourceEndpoint) _addToList(endpointsByElement, jpc.sourceId, jpc.endpoints[0]); + if (!params.targetEndpoint) _addToList(endpointsByElement, jpc.targetId, jpc.endpoints[1]); + + jpc.endpoints[0].addConnection(jpc); + jpc.endpoints[1].addConnection(jpc); + + // force a paint + _draw(jpc.source); + + return jpc; + + }, + + /** + * not implemented yet. params object will have sourceEndpoint and targetEndpoint members; these will be Endpoints. + connectEndpoints : function(params) { + var jpc = Connection(params); + + },*/ + + /* + Function: detach + + Removes a connection. + + Parameters: + + sourceId - Id of the first element in the connection. A String. + targetId - iI of the second element in the connection. A String. + + Returns: + + true if successful, false if not. + */ + detach : function(sourceId, targetId) { + var f = function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + return true; + } + }; + + // todo: how to cleanup the actual storage? a third arg to _operation? + _operation(sourceId, f); + }, + + /* + Function: detachAll + + Removes all an element's connections. + + Parameters: + + el - either the id of the element, or a jQuery object for the element. + + Returns: + + void + */ + detachAll : function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + var c = endpoints[i].connections.length; + if (c > 0) { + for (var j = 0; j < c; j++) { + var jpc = endpoints[i].connections[0]; + _removeElement(jpc.canvas); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + } + } + } + } + }, + + /* + Function: detachEverything + + Remove all Connections from all elements, but leaves Endpoints in place. + + Returns: + + void + + See Also: + + + */ + detachEverything : function() { + for(var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + var c = endpoints[i].connections.length; + if (c > 0) { + for (var j = 0; j < c; j++) { + var jpc = endpoints[i].connections[0]; + _removeElement(jpc.canvas); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + } + } + } + } + } + }, + + extend : function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }, + + /* + Function: hide + + Sets an element's connections to be hidden. + + Parameters: + + el - either the id of the element, or a jQuery object for the element. + + Returns: + + void + */ + hide : function(el) { + _setVisible(el, "none"); + }, + + /* + Function: makeAnchor + + Creates an anchor with the given params. + + Parameters: + + x - the x location of the anchor as a fraction of the total width. + y - the y location of the anchor as a fraction of the total height. + xOrientation - value indicating the general direction a connection from the anchor should go in, in the x direction. + yOrientation - value indicating the general direction a connection from the anchor should go in, in the y direction. + xOffset - a fixed offset that should be applied in the x direction that should be applied after the x position has been figured out. optional. defaults to 0. + yOffset - a fixed offset that should be applied in the y direction that should be applied after the y position has been figured out. optional. defaults to 0. + + Returns: + + The newly created Anchor. + */ + makeAnchor : function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards compatibility if we are given only one value we assume it's a call in the old form. + var params = {}; + if (arguments.length == 1) jsPlumb.extend(params, x); + else { + params = {x:x, y:y}; + if (arguments.length >= 4) { + params.orientation = [arguments[2], arguments[3]]; + } + if (arguments.length == 6) params.offsets = [arguments[4], arguments[5]]; + } + return new Anchor(params); + }, + + + /* + Function: repaint + + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + + el - either the id of the element or a jQuery object representing the element. + + Returns: + + void + + See Also: + + + */ + repaint : function(el) { + + var _processElement = function(el) { + var ele = _getElementObject(el); + _draw(ele); + }; + + // TODO: support a jQuery result object too! + + // support both lists... + if (typeof el =='object') { + for (var i = 0; i < el.length; i++) + _processElement(el[i]); + } // ...and single strings. + else _processElement(el); + }, + + /* + Function: repaintEverything + + Repaints all connections. + + Returns: + + void + + See Also: + + + */ + repaintEverything : function() { + for (var elId in endpointsByElement) { + _draw(_getElementObject(elId)); + } + }, + + /* + Function: removeAllEndpoints + + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + + el - either an element id, or a jQuery object for an element. + + Returns: + + void + + See Also: + + + */ + removeAllEndpoints : function(el) { + var elId = _getAttribute(el, "id"); + // first remove all Connections. + jsPlumb.detachAll(elId); + var ebe = endpointsByElement[elId]; + for (var i in ebe) { + _removeElement(ebe[i].canvas); + } + endpointsByElement[elId] = []; + }, + + /* + Function: removeEndpoint + + Removes the given Endpoint from the given element. + + Parameters: + + el - either an element id, or a jQuery object for an element. + endpoint - Endpoint to remove. this is an Endpoint object, such as would have been returned from a call to addEndpoint. + + Returns: + + void + + See Also: + + + */ + removeEndpoint : function(el, endpoint) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + if (ebe) { + if(_removeFromList(endpointsByElement, elId, endpoint)) + _removeElement(endpoint.canvas); + } + }, + + /* + Function: setAutomaticRepaint + + Sets/unsets automatic repaint on window resize. + + Parameters: + + value - whether or not to automatically repaint when the window is resized. + + Returns: + + void + */ + setAutomaticRepaint : function(value) { + automaticRepaint = value; + }, + + /* + Function: setDefaultNewCanvasSize + + Sets the default size jsPlumb will use for a new canvas (we create a square canvas so one value is all that is required). + This is a hack for IE, because ExplorerCanvas seems to need for a canvas to be larger than what you are going to draw on + it at initialisation time. The default value of this is 1200 pixels, which is quite large, but if for some reason you're + drawing connectors that are bigger, you should adjust this value appropriately. + + Parameters: + + size - The default size to use. jsPlumb will use a square canvas so you need only supply one value. + + Returns: + + void + */ + setDefaultNewCanvasSize : function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }, + + /* + Function: setDraggable + + Sets whether or not a given element is draggable, regardless of what any plumb command may request. + + Parameters: + + el - either the id for the element, or a jQuery object representing the element. + + Returns: + + void + */ + setDraggable: _setDraggable, + + /* + Function: setDraggableByDefault + + Sets whether or not elements are draggable by default. Default for this is true. + + Parameters: + draggable - value to set + + Returns: + + void + */ + setDraggableByDefault: function(draggable) { + _draggableByDefault = draggable; + }, + + setDebugLog: function(debugLog) { + log = debugLog; + }, + + /* + Function: setRepaintFunction + + Sets the function to fire when the window size has changed and a repaint was fired. + + Parameters: + f - Function to execute. + + Returns: + void + */ + setRepaintFunction : function(f) { + repaintFunction = f; + }, + + /* + Function: show + + Sets an element's connections to be visible. + + Parameters: + el - either the id of the element, or a jQuery object for the element. + + Returns: + void + */ + show : function(el) { + _setVisible(el, "block"); + }, + + /* + Function: sizeCanvas + + Helper to size a canvas. You would typically use this when writing your own Connector or Endpoint implementation. + + Parameters: + x - [int] x position for the Canvas origin + y - [int] y position for the Canvas origin + w - [int] width of the canvas + h - [int] height of the canvas + + Returns: + void + */ + sizeCanvas : function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; canvas.height = h; + canvas.style.width = w + "px"; canvas.width = w; + canvas.style.left = x + "px"; canvas.style.top = y + "px"; + } + }, + + /** + * gets some test hooks. nothing writable. + */ + getTestHarness : function() { + return { + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + + findIndex : _findIndex + }; + }, + + /** + * Toggles visibility of an element's connections. kept for backwards compatibility + */ + toggle : _toggleVisible, + + /* + Function: toggleVisible + + Toggles visibility of an element's connections. + + Parameters: + el - either the element's id, or a jQuery object representing the element. + + Returns: + void, but should be updated to return the current state + */ + //TODO: update this method to return the current state. + toggleVisible : _toggleVisible, + + /* + Function: toggleDraggable + + Toggles draggability (sic) of an element's connections. + + Parameters: + el - either the element's id, or a jQuery object representing the element. + + Returns: + The current draggable state. + */ + toggleDraggable : _toggleDraggable, + + /* + Function: unload + + Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element + + Returns: + void + */ + unload : function() { + delete endpointsByElement; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + }, + + /* + Function: wrap + Helper method to wrap an existing function with one of your own. This is used by the various + implementations to wrap event callbacks for drag/drop etc; it allows jsPlumb to be + transparent in its handling of these things. If a user supplies their own event callback, + for anything, it will always be called. + Parameters: + + */ + wrap : _wrap + }; + +})(); \ No newline at end of file diff --git a/archive/1.2/jsPlumb-defaults-1.2-RC1.js b/archive/1.2/jsPlumb-defaults-1.2-RC1.js new file mode 100644 index 000000000..179bfab03 --- /dev/null +++ b/archive/1.2/jsPlumb-defaults-1.2-RC1.js @@ -0,0 +1,406 @@ +/** +* jsPlumb-defaults-1.2-RC1 +* +* This script contains the default Anchors, Endpoints and Connectors for jsPlumb. It should be used with jsPlumb 1.1.0 and above; +* prior to version 1.1.0 of jsPlumb the defaults were included inside the main script. +* +* Version 1.1.1 of this script adds the Triangle Endpoint, written by __________ and featured on this demo: +* +* http://www.mintdesign.ru/blog/javascript-jsplumb-making-graph +* http://www.mintdesign.ru/site/samples/jsplumb/jsPlumb-graph-sample.htm +* +* NOTE: for production usage you should use jsPlumb-all-1.1.1-min.js, which contains the main jsPlumb script and this script together, +* in a minified file. +*/ + +(function() { + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * getOrientation : function() { return [ox, oy]; } + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + jsPlumb.Anchors.TopCenter = jsPlumb.makeAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors.BottomCenter = jsPlumb.makeAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors.LeftMiddle = jsPlumb.makeAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors.RightMiddle = jsPlumb.makeAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors.Center = jsPlumb.makeAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors.TopRight = jsPlumb.makeAnchor(1, 0, 0,-1); + jsPlumb.Anchors.BottomRight = jsPlumb.makeAnchor(1, 1, 0, 1); + jsPlumb.Anchors.TopLeft = jsPlumb.makeAnchor(0, 0, 0, -1); + jsPlumb.Anchors.BottomLeft = jsPlumb.makeAnchor(0, 1, 0, 1); + + + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (w < 2 * lineWidth) { + w = 2 * lineWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - lineWidth; + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < 2 * lineWidth) { + // minimum size is 2 * line Width + h = 2 * lineWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - lineWidth; + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + var sx = sourcePos[0] < targetPos[0] ? w-xo : xo; + var sy = sourcePos[1] < targetPos[1] ? h-yo : yo; + var tx = sourcePos[0] < targetPos[0] ? xo : w-xo; + var ty = sourcePos[1] < targetPos[1] ? yo : h-yo; + var retVal = [ x, y, w, h, sx, sy, tx, ty ]; + + return retVal; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + }; + + /** + * This Connector draws a Bezier curve with two control points. + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + jsPlumb.Connectors.Bezier = function(curviness) { + + var self = this; + this.majorAnchor = curviness || 150; + this.minorAnchor = 10; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) + { + lineWidth = lineWidth || 0; + var w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth, h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + var canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2), canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + var sx = sourcePos[0] < targetPos[0] ? w - (lineWidth/2): (lineWidth/2), sy = sourcePos[1] < targetPos[1] ? h-(lineWidth/2) : (lineWidth/2); + var tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : w-(lineWidth/2), ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : h-(lineWidth/2); + var CP = self._findControlPoint([sx,sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + var CP2 = self._findControlPoint([tx,ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(sx,tx); var minx2 = Math.min(CP[0], CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(sx,tx); var maxx2 = Math.max(CP[0], CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > w) w = maxx; + if (minx < 0) { + canvasX += minx; var ox = Math.abs(minx); + w += ox; CP[0] += ox; sx += ox; tx +=ox; CP2[0] += ox; + } + + var miny1 = Math.min(sy,ty); var miny2 = Math.min(CP[1], CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(sy,ty); var maxy2 = Math.max(CP[1], CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > h) h = maxy; + if (miny < 0) { + canvasY += miny; var oy = Math.abs(miny); + h += oy; CP[1] += oy; sy += oy; ty +=oy; CP2[1] += oy; + } + + // return [ canvasx, canvasy, canvasWidth, canvasHeight, + // sourceX, sourceY, targetX, targetY, + // controlPoint1_X, controlPoint1_Y, controlPoint2_X, controlPoint2_Y + return [canvasX, canvasY, w, h, sx, sy, tx, ty, CP[0], CP[1], CP2[0], CP2[1] ]; + }; + + this.paint = function(d, ctx) { + + /*var img = new Image(); + img.src = "../img/pattern.jpg"; + ctx.fillStyle = ctx.createPattern(img, 'repeat-y');*/ + + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + //ctx.fill(); + } + }; + + + /** + * Types of endpoint UIs. we supply four - a circle of default radius 10px, a rectangle of + * default size 20x20, an image (with no default), and a Triangle, of default size 15. + * you can supply others of these if you want to - see the documentation for a howto. + */ + + /** + * a round endpoint, with default radius 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + } + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * A Rectangular endpoint, with default size 20x20. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + //todo: the fillStyle needs some thought. we want to support a few options: + // 1. nothing supplied; use the stroke color or the default if no stroke color. + // 2. a fill color supplied - use it + // 3. a gradient supplied - use it + // 4. setting the endpoint to the same color as the bg of the element it is attached to. + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + jsPlumb.Endpoints.Triangle = function(params) { + + params = params || { width:15, height:15 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - width/2; + var y = anchorPoint[1] - height/2; + + jsPlumb.sizeCanvas(canvas, x, y, width, height); + + var ctx = canvas.getContext('2d'); + var + offsetX = 0, + offsetY = 0, + angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = endpointStyle.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + ctx.fill(); + + }; + }; + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' property in the params object.. + */ + jsPlumb.Endpoints.Image = function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + }; +})(); \ No newline at end of file diff --git a/archive/1.2/mootools.jsPlumb-1.2-RC1.js b/archive/1.2/mootools.jsPlumb-1.2-RC1.js new file mode 100644 index 000000000..3e39f3bbc --- /dev/null +++ b/archive/1.2/mootools.jsPlumb-1.2-RC1.js @@ -0,0 +1,254 @@ +(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function() { + this.parent(); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}; + var _droppableOptions = {}; + var _draggablesByScope = {}; + var _draggablesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }; + + + jsPlumb.CurrentLibrary = { + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + getElementObject : function(el) { + return $(el); + }, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + initDraggable : function(el, options) { + var drag = _draggablesById[el.get("id")]; + if (!drag) { + var originalZIndex = 0, originalCursor = null; + var dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + options['onStart'] = jsPlumb.wrap(options['onStart'], function() + { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() + { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + }); + + // DROPPABLES: + var scope = options['scope'] || jsPlumb.Defaults.Scope; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop'); + } + }; + + drag = new Drag.Move(el, options); + _add(_draggablesByScope, scope, drag); + _add(_draggablesById, el.get("id"), drag); + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + initDroppable : function(el, options) { + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _add(_droppables, scope, el); + _droppableOptions[el.get("id")] = options; + var filterFunc = function(entry) { + return entry.element != el; + }; + var draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + if (typeof Drag != undefined) + return typeof Drag.Move != undefined; + return false; + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + m.start(properties); + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[0]; + return { left: ui.offsetLeft, top: ui.offsetTop }; + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + } + }; +})(); diff --git a/archive/1.2/mootools.jsPlumb-1.2-all-min.js b/archive/1.2/mootools.jsPlumb-1.2-all-min.js new file mode 100644 index 000000000..d4fc9d00e --- /dev/null +++ b/archive/1.2/mootools.jsPlumb-1.2-all-min.js @@ -0,0 +1,41 @@ +(function(){var u=/MSIE/.test(navigator.userAgent)&&!window.opera,s=null,f=function(){j.repaintEverything()},g=true,l={},k=[],h={},m={},d=true,e=[],p=1200,w=function(a,b,c){var i=function(q,t){if(q===t)return true;else if(typeof q=="object"&&typeof t=="object"){var v=true;for(var x in q)if(!i(q[x],t[x])){v=false;break}for(x in t)if(!i(t[x],q[x])){v=false;break}return v}};c=+c||0;for(var n=a.length;c=0){delete a[c];a.splice(c,1);return true}}return false},Y=function(a,b){var c=z(a,"id");U(c,function(i){i.canvas.style.display=b})},Z=function(a){U(a,function(b){b.canvas.style.display="none"==b.canvas.style.display?"block":"none"})},M=function(a,b,c){if(c||b==null){A(a);e[a]=V(a);b=j.CurrentLibrary.getElementObject(a); +b=j.CurrentLibrary.getOffset(b);k[a]=b}else k[a]=b},J=function(a,b){a=a||function(){};b=b||function(){};return function(){try{b.apply(this,arguments)}catch(c){}try{a.apply(this,arguments)}catch(i){}}},ba=function(a){var b=this;this.x=a.x||0;this.y=a.y||0;var c=a.orientation||[0,0];this.offsets=a.offsets||[0,0];this.compute=function(i,n){return[i[0]+b.x*n[0]+b.offsets[0],i[1]+b.y*n[1]+b.offsets[1]]};this.getOrientation=function(){return c}},ca=function(a){var b=a.reference,c=V(a.referenceCanvas),i= +0,n=0,q=null;this.compute=function(t){n=i=0;return[t[0]+c[0]/2,t[1]+c[1]/2]};this.getOrientation=function(){if(q)return q;else{var t=b.getOrientation();return[Math.abs(t[0])*i*-1,Math.abs(t[1])*n*-1]}};this.over=function(t){q=t.getOrientation()};this.out=function(){q=null}},aa=function(a){var b=this;this.source=A(a.source);this.target=A(a.target);this.sourceId=z(this.source,"id");this.targetId=z(this.target,"id");this.endpointsOnTop=a.endpointsOnTop!=null?a.endpointsOnTop:true;this.endpoints=[];this.endpointStyles= +[];var c=function(q,t,v){if(q)b.endpoints[t]=q;else{if(!v.endpoints)v.endpoints=[null,null];q=v.endpoints[t]||v.endpoint||j.Defaults.Endpoints[t]||j.Defaults.Endpoint||new j.Endpoints.Dot;if(!v.endpointStyles)v.endpointStyles=[null,null];b.endpoints[t]=new S({style:v.endpointStyles[t]||v.endpointStyle||j.Defaults.EndpointStyles[t]||j.Defaults.EndpointStyle,endpoint:q,connections:[b],anchor:v.anchors?v.anchors[t]:j.Defaults.Anchors[t]||j.Anchors.BottomCenter,source:b.source})}};c(a.sourceEndpoint, +0,a);c(a.targetEndpoint,1,a);this.connector=this.endpoints[0].connector||this.endpoints[1].connector||a.connector||j.Defaults.Connector||new j.Connectors.Bezier;this.paintStyle=this.endpoints[0].connectorStyle||this.endpoints[1].connectorStyle||a.paintStyle||j.Defaults.PaintStyle;M(this.sourceId);M(this.targetId);c=k[this.sourceId];var i=e[this.sourceId];c=this.endpoints[0].anchor.compute([c.left,c.top],i);this.endpoints[0].paint(c);c=k[this.targetId];i=e[this.targetId];c=this.endpoints[1].anchor.compute([c.left, +c.top],i);this.endpoints[1].paint(c);var n=W(j.connectorClass);this.canvas=n;this.paint=function(q,t,v){s&&s.debug("Painting Connection; element in motion is "+q+"; ui is ["+t+"]; recalc is ["+v+"]");var x=q!=this.sourceId,H=x?this.sourceId:this.targetId,y=x?0:1,r=x?1:0;if(this.canvas.getContext){M(q,t,v);v&&M(H);t=k[q];v=k[H];q=e[q];var P=e[H];H=n.getContext("2d");var K=this.endpoints[r].anchor.compute([t.left,t.top],q,[v.left,v.top],P);this.endpoints[r].anchor.getOrientation();t=this.endpoints[y].anchor.compute([v.left, +v.top],P,[t.left,t.top],q);this.endpoints[y].anchor.getOrientation();y=this.connector.compute(K,t,this.endpoints[r].anchor,this.endpoints[y].anchor,this.paintStyle.lineWidth);j.sizeCanvas(n,y[0],y[1],y[2],y[3]);j.extend(H,this.paintStyle);if(this.paintStyle.gradient&&!u){x=x?H.createLinearGradient(y[4],y[5],y[6],y[7]):H.createLinearGradient(y[6],y[7],y[4],y[5]);for(r=0;r=0&&b.connections.splice(o,1)};this.makeInPlaceCopy=function(){return new S({anchor:b.anchor,source:n,style:i,endpoint:c})};this.isConnectedTo=function(o){var D=false;if(o)for(var B=0;B=t};this.paint=function(o,D,B){s&&s.debug("Painting Endpoint with elementId ["+q+"]; anchorPoint is ["+o+"]");if(o==null){o=k[q];var O=e[q];if(o==null||O==null){M(q);o=k[q];O=e[q]}o=b.anchor.compute([o.left,o.top],O)}c.paint(o,b.anchor.getOrientation(),B||b.canvas,i,D||i)};if(a.isSource&&j.CurrentLibrary.isDragSupported(n)){var y=null,r=null,P=false,K=null,C=a.dragOptions|| +{},L=j.extend({},j.CurrentLibrary.defaultDragOptions);C=j.extend(L,C);L=j.CurrentLibrary.dragEvents.start;var Q=j.CurrentLibrary.dragEvents.stop,R=j.CurrentLibrary.dragEvents.drag;C[L]=J(C[L],function(){H=b.makeInPlaceCopy();H.paint();y=document.createElement("div");document.body.appendChild(y);var o=""+new String((new Date).getTime());F(A(y),"id",o);M(o);F(A(b.canvas),"dragId",o);F(A(b.canvas),"elId",q);var D=new ca({reference:b.anchor,referenceCanvas:b.canvas});x=new S({style:{fillStyle:"rgba(0,0,0,0)"}, +endpoint:c,anchor:D,source:y});r=b.connections.length==0||b.connections.length0)for(var i=0;i0)for(var n=0;n=4)c.orientation=[arguments[2],arguments[3]];if(arguments.length==6)c.offsets=[arguments[4],arguments[5]]}return new ba(c)},repaint:function(a){var b=function(i){i=A(i);I(i)};if(typeof a=="object")for(var c=0;cm)m=l;if(g<0){e+=g;g=Math.abs(g);m+=g;G[0]+=g;w+=g;I+=g;f[0]+=g}g=Math.min(Math.min(E,h),Math.min(G[1], +f[1]));l=Math.max(Math.max(E,h),Math.max(G[1],f[1]));if(l>d)d=l;if(g<0){p+=g;g=Math.abs(g);d+=g;G[1]+=g;E+=g;h+=g;f[1]+=g}return[e,p,m,d,w,E,I,h,G[0],G[1],f[0],f[1]]};this.paint=function(f,g){g.beginPath();g.moveTo(f[4],f[5]);g.bezierCurveTo(f[8],f[9],f[10],f[11],f[6],f[7]);g.stroke()}};jsPlumb.Endpoints.Dot=function(u){u=u||{radius:10};var s=this;this.radius=u.radius;var f=0.5*this.radius,g=this.radius/3,l=function(k){try{return parseInt(k)}catch(h){if(k.substring(k.length-1)=="%")return parseInt(k.substring(0, +k-1))}};this.paint=function(k,h,m,d,e){var p=d.radius||s.radius;jsPlumb.sizeCanvas(m,k[0]-p,k[1]-p,p*2,p*2);k=m.getContext("2d");m=jsPlumb.extend({},d);if(m.fillStyle==null)m.fillStyle=e.strokeStyle;jsPlumb.extend(k,m);e=/MSIE/.test(navigator.userAgent)&&!window.opera;if(d.gradient&&!e){e=d.gradient;m=f;var w=g;if(e.offset)m=l(e.offset);if(e.innerRadius)w=l(e.innerRadius);e=[m,w];h=k.createRadialGradient(p,p,p,p+(h[0]==1?e[0]*-1:e[0]),p+(h[1]==1?e[0]*-1:e[0]),e[1]);for(e=0;e endpoint lists. an element can have an arbitrary number of endpoints on it, + * and not all of them have to be connected to anything. + */ + var endpointsByElement = {}; + var offsets = []; + var floatingConnections = {}; + var draggableStates = {}; + var _draggableByDefault = true; + var sizes = []; + + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + var _findIndex = function( a, v, b, s ) { + var _eq = function(o1, o2) { + if (o1 === o2) return true; + else if (typeof o1 == 'object' && typeof o2 == 'object') { + var same = true; + for(var propertyName in o1) { + if(!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for(var propertyName in o2) { + if(!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for( var i = +b || 0, l = a.length; i < l; i++ ) { + if( _eq(a[i], v)) return i; + } + return -1; + }; + + /** + * helper method to add an item to a list, creating the list if it does not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + }; + + /** + * Handles the dragging of an element. + * @param element jQuery element + * @param ui UI object from current library's event system + */ + var _draw = function(element, ui) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints) { + _updateOffset(id, ui); + var myOffset = offsets[id]; + var myWH = sizes[id]; + // loop through endpoints for this element + for (var i = 0; i < endpoints.length; i++) { + var e = endpoints[i]; + var anchorLoc = endpoints[i].anchor.compute([myOffset.left, myOffset.top], myWH); + e.paint(anchorLoc); + var l = e.connections; + for (var j = 0; j < l.length; j++) + l[j].paint(id, ui); // ...and paint them. + } + } + }; + + /** + * helper function: the second argument is a function taking two args - the first is an id + * ui element from the current library (or list of them), and the second is the function + * to run. + */ + var _elementProxy = function(element, fn) { + var retVal = null; + if (typeof element == 'object' && element.length) { + retVal = []; + for (var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]); + var id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } + else { + var el = _getElementObject(element); + var id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + + return retVal; + }; + + /** + * gets the named attribute from the given element (id or element object) + */ + var _getAttribute = function(el, attName) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + return jsPlumb.CurrentLibrary.getAttribute(ele, attName); + }; + + var _setAttribute = function(el, attName, attValue) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + jsPlumb.CurrentLibrary.setAttribute(ele, attName, attValue); + }; + + var _addClass = function(el, clazz) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + jsPlumb.CurrentLibrary.addClass(ele, clazz); + }; + + var _getElementObject = function(elId) { + return jsPlumb.CurrentLibrary.getElementObject(elId); + }; + + var _getOffset = function(el) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + return jsPlumb.CurrentLibrary.getOffset(ele); + }; + + var _getSize = function(el) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + return jsPlumb.CurrentLibrary.getSize(ele); + }; + + var _appendCanvas = function(canvas) { + document.body.appendChild(canvas); + }; + + /** + * gets an id for the given element, creating and setting one if necessary. + */ + var _getId = function(element) { + var id = _getAttribute(element, "id"); + if (!id) { + id = "_jsPlumb_" + new String((new Date()).getTime()); + _setAttribute(element, "id", id); + } + return id; + }; + + /** + * inits a draggable if it's not already initialised. + * + * TODO: we need to hook in each library to this method. they need to be given the opportunity to + * wrap/insert lifecycle functions, because each library does different things. for instance, jQuery + * supports the notion of 'revert', which will clean up a dragged endpoint; MooTools does not. jQuery + * also supports 'opacity' and MooTools does not; jQuery supports z-index of the draggable; MooTools + * does not. i could go on. the point is...oh. initDraggable can do this. ok. + */ + var _initDraggableIfNecessary = function(element, elementId, isDraggable, dragOptions) { + // dragging + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable && jsPlumb.CurrentLibrary.isDragSupported(element)) { + var options = dragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend({}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var initDrag = function(element, elementId, dragFunc) { + options[dragEvent] = _wrap(options[dragEvent], dragFunc); + var draggable = draggableStates[elementId]; + // TODO: this is still jQuery specific. + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + }; + initDrag(element, elementId, function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + } + }; + + var _log = function(msg) { + // not implemented. yet. + } + + /** + * helper to create a canvas. + * @param clazz optional class name for the canvas. + */ + var _newCanvas = function(clazz) { + var canvas = document.createElement("canvas"); + _appendCanvas(canvas); + canvas.style.position="absolute"; + if (clazz) { canvas.className=clazz; } + _getId(canvas); // set an id. + + if (ie) { + // for IE we have to set a big canvas size. actually you can override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + /** + * performs the given function operation on all the connections found for the given element + * id; this means we find all the endpoints for the given element, and then for each endpoint + * find the connectors connected to it. then we pass each connection in to the given + * function. + */ + var _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + for (var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }; + /** + * perform an operation on all elements. + */ + var _operationOnAll = function(func) { + for (var elId in endpointsByElement) { + _operation(elId, func); + } + }; + /** + * helper to remove an element from the DOM. + */ + var _removeElement = function(element) { + if (element != null) { + try { document.body.removeChild(element); } + catch (e) { } + } + }; + /** + * helper to remove a list of elements from the DOM. + */ + var _removeElements = function(elements) { + for (var i in elements) + _removeElement(elements[i]); + }; + /** + * helper method to remove an item from a list. + */ + var _removeFromList = function(map, key, value) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete( l[i] ); + l.splice( i, 1 ); + return true; + } + } + return false; + }; + /** + * Sets whether or not the given element(s) should be draggable, regardless of what a particular + * plumb command may request. + * + * @param element May be a string, a jQuery elements, or a list of strings/jquery elements. + * @param draggable Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + var _helper = function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }; + + return _elementProxy(element, _helper); + }; + /** + * private method to do the business of hiding/showing. + * @param el either Id of the element in question or a jquery object for the element. + * @param state String specifying a value for the css 'display' property ('block' or 'none'). + */ + var _setVisible = function(el, state) { + var elId = _getAttribute(el, "id"); + var f = function(jpc) { + jpc.canvas.style.display = state; + }; + _operation(elId, f); + }; + /** + * toggles the draggable state of the given element(s). + * @param el either an id, or a jquery object, or a list of ids/jquery objects. + */ + var _toggleDraggable = function(el) { + var fn = function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + //el.draggable("option", "disabled", !state); + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }; + return _elementProxy(el, fn); + }; + /** + * private method to do the business of toggling hiding/showing. + * @param elId Id of the element in question + */ + var _toggleVisible = function(elId) { + var f = function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }; + + _operation(elId, f); + + //todo this should call _elementProxy, and pass in the _operation(elId, f) call as a function. cos _toggleDraggable does that. + }; + /** + * updates the offset and size for a given element, and stores the values. + * if 'offset' is not null we use that (it would have been passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we use the offset, outerWidth and outerHeight methods to get + * the current values. + */ + var _updateOffset = function(elId, offset, recalc) { + if (recalc || offset == null) { // if forced repaint or no offset available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + sizes[elId] = _getSize(elId); + offsets[elId] = _getOffset(elId); + } else { + offsets[elId] = offset; + } + }; + + /** + * wraps one function with another, creating a placeholder for the wrapped function + * if it was null. this is used to wrap the various drag/drop event functions - to allow + * jsPlumb to be notified of important lifecycle events without imposing itself on the user's + * drap/drop functionality. + * TODO: determine whether or not we should support an error handler concept, if one of the functions fails. + */ + var _wrap = function(wrappedFunction, newFunction) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + try { newFunction.apply(this, arguments); } + catch (e) { } + try { wrappedFunction.apply(this, arguments); } + catch (e) { } + }; + } + + /* + Class: Anchor + Models a position relative to the origin of some element that an Endpoint can be located. + */ + /* + Function: Anchor + + Constructor for the Anchor class + + Parameters: + params - Anchor parameters. This should contain three values, and may optionally have an 'offsets' argument: + + - x : the x location of the anchor as a fraction of the total width. + - y : the y location of the anchor as a fraction of the total height. + - orientation : an [x,y] array indicating the general direction a connection from the anchor should go in. for more info on this, see the documentation, or the docs in jquery-jsPlumb-defaults-XXX.js for the default Anchors. + - offsets : an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. may be null. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; this.y = params.y || 0; + var orientation = params.orientation || [0,0]; + this.offsets = params.offsets || [0,0]; + this.compute = function(xy, wh, txy, twh) { + return [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + } + this.getOrientation = function() { return orientation; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from its position relative to the anchor it is floating relative to. + * + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(refCanvas); + + // these are used to store the current relative position of our anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating anchor is hovering over another anchor. + var orientation = null; + + this.compute = function(xy, wh, txy, twh) { + // set these for the getOrientation method to use. + xDir = 0;//xy[0] < txy[0] ? -1 : xy[0] == txy[0] ? 0 : 1; + yDir = 0;//xy[1] < txy[1] ? -1 : xy[1] == txy[1] ? 0 : 1; + return [xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other anchor: if it declares zero for some direction, we declare zero too. + // this might not be the most awesome. perhaps we can come up with a better way. it's just so that the line we draw looks + // like it makes sense. maybe this wont make sense. + return [Math.abs(o[0]) * xDir * -1, Math.abs(o[1]) * yDir * -1]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering over another anchor; + * we want to assume that anchor's orientation for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no longer hovering + * over another anchor; we should resume calculating orientation as we normally do. + */ + this.out = function() { + orientation = null; + }; + }; + + // ************** connection + // **************************************** + /** + * allowed params: + * source: source element (string or a jQuery element) (required) + * target: target element (string or a jQuery element) (required) + * + * anchors: optional array of anchor placements. defaults to BottomCenter for source + * and TopCenter for target. + */ + var Connection = function(params) { + + // ************** get the source and target and register the connection. ******************* + var self = this; + // get source and target as jQuery objects + this.source = _getElementObject(params.source); + this.target = _getElementObject(params.target); + this.sourceId = _getAttribute(this.source, "id"); + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + + // init endpoints + this.endpoints = []; + this.endpointStyles = []; + var prepareEndpoint = function(existing, index, params) { + if (existing) self.endpoints[index] = existing; + else { + if(!params.endpoints) params.endpoints = [null,null]; + var ep = params.endpoints[index] || params.endpoint || jsPlumb.Defaults.Endpoints[index] || jsPlumb.Defaults.Endpoint|| new jsPlumb.Endpoints.Dot(); + if (!params.endpointStyles) params.endpointStyles = [null,null]; + var es = params.endpointStyles[index] || params.endpointStyle || jsPlumb.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyle; + var a = params.anchors ? params.anchors[index] : jsPlumb.Defaults.Anchors[index] || jsPlumb.Anchors.BottomCenter; + self.endpoints[index] = new Endpoint({style:es, endpoint:ep, connections:[self], anchor:a, source:self.source }); + } + }; + + prepareEndpoint(params.sourceEndpoint, 0, params); + prepareEndpoint(params.targetEndpoint, 1, params); + + this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + this.paintStyle = this.endpoints[0].connectorStyle || this.endpoints[1].connectorStyle || params.paintStyle || jsPlumb.Defaults.PaintStyle; + + _updateOffset(this.sourceId); + _updateOffset(this.targetId); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var anchorLoc = this.endpoints[0].anchor.compute([myOffset.left, myOffset.top], myWH); + this.endpoints[0].paint(anchorLoc); + myOffset = offsets[this.targetId]; myWH = sizes[this.targetId]; + anchorLoc = this.endpoints[1].anchor.compute([myOffset.left, myOffset.top], myWH); + this.endpoints[1].paint(anchorLoc); + + // *************** create canvas on which the connection will be drawn ************ + var canvas = _newCanvas(jsPlumb.connectorClass); + this.canvas = canvas; + + /** + * paints the connection. + * @param elId Id of the element that is in motion + * @param ui current library's event system ui object (present if we came from a drag to get here) + * @param recalc whether or not to recalculate element sizes. this is true if a repaint caused this to be painted. + */ + this.paint = function(elId, ui, recalc) { + + if (log) log.debug("Painting Connection; element in motion is " + elId + "; ui is [" + ui + "]; recalc is [" + recalc + "]"); + + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = !(elId == this.sourceId); + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + if (this.canvas.getContext) { + + _updateOffset(elId, ui, recalc); + if (recalc) _updateOffset(tId); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var myOffset = offsets[elId]; + var otherOffset = offsets[tId]; + var myWH = sizes[elId]; + var otherWH = sizes[tId]; + + var ctx = canvas.getContext('2d'); + var sAnchorP = this.endpoints[sIdx].anchor.compute([myOffset.left, myOffset.top], myWH, [otherOffset.left, otherOffset.top], otherWH); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.compute([otherOffset.left, otherOffset.top], otherWH, [myOffset.left, myOffset.top], myWH); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, this.paintStyle.lineWidth); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + jsPlumb.extend(ctx, this.paintStyle); + + if (this.paintStyle.gradient && !ie) { + var g = swap ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + for (var i = 0; i < this.paintStyle.gradient.stops.length; i++) + g.addColorStop(this.paintStyle.gradient.stops[i][0],this.paintStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + + this.connector.paint(dim, ctx); + } + }; + + this.repaint = function() { + this.paint(this.sourceId, null, true); + }; + + _initDraggableIfNecessary(self.source, self.sourceId, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, self.targetId, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + }; + + /* + Class: Endpoint + + Models an endpoint. Can have one to N connections emanating from it (although how to handle that in the UI is + a very good question). also has a Canvas and paint style. + + */ + + /* + Function: Endpoint + + This is the Endpoint class constructor. + + Parameters: + + anchor - anchor for the endpoint, of type jsPlumb.Anchor. may be null. + endpoint - endpoint object, of type jsPlumb.Endpoint. may be null. + style - endpoint style, a js object. may be null. + source - element the endpoint is attached to, of type jquery object. Required. + canvas - canvas element to use. may be, and most often is, null. + connections - optional list of connections to configure the endpoint with. + isSource - boolean. indicates the endpoint can act as a source of new connections. optional. + dragOptions - if isSource is set to true, you can supply arguments for the jquery draggable method. optional. + connectionStyle - if isSource is set to true, this is the paint style for connections from this endpoint. optional. + connector - optional connector type to use. + isTarget - boolean. indicates the endpoint can act as a target of new connections. optional. + dropOptions - if isTarget is set to true, you can supply arguments for the jquery droppable method. optional. + reattach - optional boolean that determines whether or not the connections reattach after they + have been dragged off an endpoint and left floating. defaults to false: connections + dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + params = params || {}; + // make a copy. then we can use the wrapper function. + jsPlumb.extend({}, params); + var self = this; + self.anchor = params.anchor || jsPlumb.Anchors.TopCenter; + var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot(); + var _style = params.style || jsPlumb.Defaults.EndpointStyle; + this.connectorStyle = params.connectorStyle; + this.connector = params.connector; + var _element = params.source; + var _elementId = _getAttribute(_element, "id"); + var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of. + this.canvas = params.canvas || _newCanvas(jsPlumb.endpointClass); + this.connections = params.connections || []; + var _reattach = params.reattach || false; + var floatingEndpoint = null; + var inPlaceCopy = null; + this.addConnection = function(connection) { + + self.connections.push(connection); + }; + this.removeConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) + self.connections.splice(idx, 1); + }; + this.makeInPlaceCopy = function() { + var e = new Endpoint({anchor:self.anchor, source:_element, style:_style, endpoint:_endpoint}); + return e; + }; + /** + * returns whether or not this endpoint is connected to the given endpoint. + * @param endpoint Endpoint to test. + * @since 1.1.1 + * + * todo: needs testing. will this work if the endpoint passed in is the source? + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + this.isFloating = function() { return floatingEndpoint != null; }; + /** + * first pass at default ConnectorSelector: returns the first connection, if we have any. + * modified a little, 5/10/2010: now it only returns a connector if we have not got equal to or more than _maxConnector connectors + * attached. otherwise it is assumed a new connector is ok. but note with this setup we can't select any other connection than the first + * one. what if this could return a list? that implies everything should work with a list - dragging etc. could be nasty. could also + * be cool. + */ + var connectorSelector = function() { + return self.connections.length == 0 || self.connections.length < _maxConnections ? null : self.connections[0]; + }; + + this.isFull = function() { return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); }; + + /** + * paints the Endpoint, recalculating offset and anchor positions if necessary. + */ + this.paint = function(anchorPoint, connectorPaintStyle, canvas) { + + if (log) log.debug("Painting Endpoint with elementId [" + _elementId + "]; anchorPoint is [" + anchorPoint + "]"); + + if (anchorPoint == null) { + // do we always want to force a repaint here? i dont think so! + var xy = offsets[_elementId]; + var wh = sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset(_elementId); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + anchorPoint = self.anchor.compute([xy.left, xy.top], wh); + } + _endpoint.paint(anchorPoint, self.anchor.getOrientation(), canvas || self.canvas, _style, connectorPaintStyle || _style); + }; + + + // is this a connection source? we make it draggable and have the drag listener + // maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + + // first the question is, how many connections are on this endpoint? if it's only one, then excellent. otherwise we will either need a way + // to select one connection from the list, or drag them all. if we had a pluggable 'ConnectorSelector' interface we could probably + // provide a way for people to implement their own UI components to do the connector selection. the question in that particular case would be how much + // information the interface needs from jsPlumb at execution time. if, however, we leave out the connector selection, and drag them all, + // that wouldn't be too hard to organise. perhaps that behaviour would be on a switch for the endpoint, or perhaps the ConnectorSelector + // interface returns a List, with the default implementation just returning everything. i think i like that. + // + // let's say for now that there is just one endpoint, cos we need to get this going before we can consider a list of them anyway. + // the major difference between that case and the case of a new endpoint is actually quite small - it's a question of where the + // Connection comes from. for a new one, we create a new one. obviously. otherwise we just get the jpc from the Endpoint + // (remember we're only assuming one connection right now). so all of the UI stuff we do to create the floating endpoint etc + // will still be valid, but when we stop dragging, we'll have to do something different. if we stop with a valid drop i think it will + // be the same process. but if we stop with an invalid drop we have to reset the Connection to how it was when we got it. + var start = function() { + + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + _appendCanvas(n); + // create and assign an id, and initialize the offset. + var id = "" + new String(new Date().getTime()); + _setAttribute(_getElementObject(n), "id", id); + _updateOffset(id); + // store the id of the dragging div and the source element. the drop function + // will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor({reference:self.anchor, referenceCanvas:self.canvas}); + floatingEndpoint = new Endpoint({ + style:{fillStyle:'rgba(0,0,0,0)'}, + endpoint:_endpoint, + anchor:floatingAnchor, + source:n + }); + + jpc = connectorSelector(); + if (jpc == null) { + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = new Connection({ + sourceEndpoint:self, + targetEndpoint:floatingEndpoint, + source:_getElementObject(_element), + target:_getElementObject(n), + anchors:[self.anchor, floatingAnchor], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + connector: params.connector + }); + // todo ...unregister on stop + self.addConnection(jpc); + } else { + existingJpc = true; + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; + // probably we should remove the connection? and add it back if the user + // does not drop it somewhere proper. + self.removeConnection(jpc); + if (anchorIdx == 0){ + existingJpcParams = [jpc.source, jpc.sourceId]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + }else { + existingJpcParams = [jpc.target, jpc.targetId]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // todo unregister on stop + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + }; + + var dragOptions = params.dragOptions || { }; + + var defaultOpts = jsPlumb.extend({}, jsPlumb.CurrentLibrary.defaultDragOptions); + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + var startEvent = jsPlumb.CurrentLibrary.dragEvents['start']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements([floatingEndpoint.canvas, n, inPlaceCopy.canvas]); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + if (_reattach) { + jpc.floatingAnchorIndex = null; + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } + else { + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeElement(jpc.canvas); + } + } else { + _removeElement(jpc.canvas); + self.removeConnection(jpc); + } + } + jpc = null; + delete floatingEndpoint; + delete inPlaceCopy; + self.paint(); + } + ); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + }; + + // connector target + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend({}, dropOptions); + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + dropOptions[dropEvent]= _wrap(dropOptions[dropEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable), "dragId"); + var elId = _getAttribute(_getElementObject(draggable),"elId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // remove this jpc from the current endpoint + jpc.endpoints[idx].removeConnection(jpc); + if (jpc.suspendedEndpoint) + jpc.suspendedEndpoint.removeConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + _initDraggableIfNecessary(_element, _elementId, params.draggable, {}); + jsPlumb.repaint(elId); + + delete floatingConnections[id]; + }); + + dropOptions[overEvent]= _wrap(dropOptions[overEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + }); + + jsPlumb.CurrentLibrary.initDroppable(_getElementObject(self.canvas), dropOptions); + } + + return self; + }; + + /* + Class: jsPlumb + + This is the main entry point to jsPlumb. There are a bunch of static methods you can + use to connect/disconnect etc. Some methods are also added to jQuery's "$.fn" object itself, but + in general jsPlumb is moving away from that model, preferring instead the static class + approach. One of the reasons for this is that with no methods added to the jQuery $.fn object, + it will be easier to provide support for other libraries such as MooTools. + */ + var jsPlumb = window.jsPlumb = { + + /* + Property: Defaults + + These are the default settings for jsPlumb, that is what will be used if you do not supply specific pieces of information + to the various API calls. A convenient way to implement your own look and feel can be to override these defaults by including a script + somewhere after the jsPlumb include, but before you make any calls to jsPlumb, for instance in this example we set the PaintStyle to be + a blue line of 27 pixels: + + > jsPlumb.Defaults.PaintStyle = { lineWidth:27, strokeStyle:'blue' } + + */ + Defaults : { + Anchors : [ null, null ], + Connector : null, + DragOptions: { }, + DropOptions: { }, + Endpoint : null, + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : null }, + EndpointStyles : [ null, null ], + MaxConnections : null, + PaintStyle : { lineWidth : 10, strokeStyle : 'red' }, + Scope : "_jsPlumb_DefaultScope" + }, + + /* + Property: connectorClass + + The CSS class to set on Connection canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + connectorClass : '_jsPlumb_connector', + + /* + Property: endpointClass + + The CSS class to set on Endpoint canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + endpointClass : '_jsPlumb_endpoint', + + /* + Property: Anchors + + Default jsPlumb Anchors. These are supplied in the file jquery.jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form jquery.jsPlumb-all-x.x.x.js. You can provide your own Anchors by supplying them in a script that is loaded after jsPlumb, for instance: + + > jsPlumb.Anchors.MyAnchor = { ....anchor code here. see the documentation. } + */ + Anchors : {}, + + /* + Property: Connectors + + Default jsPlumb Connectors. These are supplied in the file jquery.jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form jquery.jsPlumb-all-x.x.x.js. You can provide your own Connectors by supplying them in a script that is loaded after jsPlumb, for instance: + + > jsPlumb.Connectors.MyConnector = { ....connector code here. see the documentation. } + */ + Connectors : {}, + + /* + Property: Endpoints + + Default jsPlumb Endpoints. These are supplied in the file jquery.jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form jquery.jsPlumb-all-x.x.x.js. You can provide your own Endpoints by supplying them in a script that is loaded after jsPlumb, for instance: + + > jsPlumb.Endpoints.MyEndpoint = { ....endpoint code here. see the documentation. } + */ + Endpoints : {}, + + /* + Function: addEndpoint + + Adds an Endpoint to a given element. + + Parameters: + target - Element to add the endpoint to. either an element id, or a jQuery object representing some element. + params - Object containing Endpoint options (more info required) + + Returns: + + The newly created Endpoint. + + See Also: + + + */ + addEndpoint : function(target, params) { + params = jsPlumb.extend({}, params); + var el = _getElementObject(target); + var id = _getAttribute(target,"id"); + params.source = el; + _updateOffset(id); + var e = new Endpoint(params); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id]; + var myWH = sizes[id]; + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH); + e.paint(anchorLoc); + return e; + }, + + /* + Function: addEndpoint + + Adds a list of Endpoints to a given element. + + Parameters: + + target - element to add the endpoint to. either an element id, or a jQuery object representing some element. + endpoints - List of objects containing Endpoint options. one Endpoint is created for each entry in this list. + + Returns: + + List of newly created Endpoints, one for each entry in the 'endpoints' argument. + + See Also: + + + */ + addEndpoints : function(target, endpoints) { + var results = []; + for (var i = 0; i < endpoints.length; i++) { + results.push(jsPlumb.addEndpoint(target, endpoints[i])); + } + return results; + }, + + /* + Function: animate + + Wrapper around standard jQuery animate function; injects a call to jsPlumb in the 'step' function (creating it if necessary). + This only supports the two-arg version of the animate call in jQuery - the one that takes an 'options' object as the second arg. + + Parameters: + + el - Element to animate. Either an id, or a jQuery object representing the element. + properties - The 'properties' argument you want passed to the standard jQuery animate call. + options - The 'options' argument you want passed to the standard jQuery animate call. + + Returns: + + void + */ + animate : function(el, properties, options) { + var ele = _getElementObject(el); + var id = _getAttribute(el,"id"); + //TODO this is not agnostic yet. + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + options[stepFunction] = _wrap(options[stepFunction], function() + { + jsPlumb.repaint(id); + }); + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }, + + /* + Function: connect + + Establishes a connection between two elements. + + Parameters: + + params - Object containing setup for the connection. see documentation. + + Returns: + + The newly created Connection. + + */ + connect : function(params) { + if (params.sourceEndpoint && params.sourceEndpoint.isFull()) { + _log("could not add connection; source endpoint is full"); + return; + } + + if (params.targetEndpoint && params.targetEndpoint.isFull()) { + _log("could not add connection; target endpoint is full"); + return; + } + + var jpc = new Connection(params); + + // register endpoints for the element. todo: is the test below sufficient? or should we test if the endpoint is already in the list, + // and add it only then? perhaps _addToList could be overloaded with a a 'test for existence first' parameter? + //_addToList(endpointsByElement, jpc.sourceId, jpc.endpoints[0]); + //_addToList(endpointsByElement, jpc.targetId, jpc.endpoints[1]); + + if (!params.sourceEndpoint) _addToList(endpointsByElement, jpc.sourceId, jpc.endpoints[0]); + if (!params.targetEndpoint) _addToList(endpointsByElement, jpc.targetId, jpc.endpoints[1]); + + jpc.endpoints[0].addConnection(jpc); + jpc.endpoints[1].addConnection(jpc); + + // force a paint + _draw(jpc.source); + + return jpc; + + }, + + /** + * not implemented yet. params object will have sourceEndpoint and targetEndpoint members; these will be Endpoints. + connectEndpoints : function(params) { + var jpc = Connection(params); + + },*/ + + /* + Function: detach + + Removes a connection. + + Parameters: + + sourceId - Id of the first element in the connection. A String. + targetId - iI of the second element in the connection. A String. + + Returns: + + true if successful, false if not. + */ + detach : function(sourceId, targetId) { + var f = function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + return true; + } + }; + + // todo: how to cleanup the actual storage? a third arg to _operation? + _operation(sourceId, f); + }, + + /* + Function: detachAll + + Removes all an element's connections. + + Parameters: + + el - either the id of the element, or a jQuery object for the element. + + Returns: + + void + */ + detachAll : function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + var c = endpoints[i].connections.length; + if (c > 0) { + for (var j = 0; j < c; j++) { + var jpc = endpoints[i].connections[0]; + _removeElement(jpc.canvas); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + } + } + } + } + }, + + /* + Function: detachEverything + + Remove all Connections from all elements, but leaves Endpoints in place. + + Returns: + + void + + See Also: + + + */ + detachEverything : function() { + for(var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + var c = endpoints[i].connections.length; + if (c > 0) { + for (var j = 0; j < c; j++) { + var jpc = endpoints[i].connections[0]; + _removeElement(jpc.canvas); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + } + } + } + } + } + }, + + extend : function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }, + + /* + Function: hide + + Sets an element's connections to be hidden. + + Parameters: + + el - either the id of the element, or a jQuery object for the element. + + Returns: + + void + */ + hide : function(el) { + _setVisible(el, "none"); + }, + + /* + Function: makeAnchor + + Creates an anchor with the given params. + + Parameters: + + x - the x location of the anchor as a fraction of the total width. + y - the y location of the anchor as a fraction of the total height. + xOrientation - value indicating the general direction a connection from the anchor should go in, in the x direction. + yOrientation - value indicating the general direction a connection from the anchor should go in, in the y direction. + xOffset - a fixed offset that should be applied in the x direction that should be applied after the x position has been figured out. optional. defaults to 0. + yOffset - a fixed offset that should be applied in the y direction that should be applied after the y position has been figured out. optional. defaults to 0. + + Returns: + + The newly created Anchor. + */ + makeAnchor : function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards compatibility if we are given only one value we assume it's a call in the old form. + var params = {}; + if (arguments.length == 1) jsPlumb.extend(params, x); + else { + params = {x:x, y:y}; + if (arguments.length >= 4) { + params.orientation = [arguments[2], arguments[3]]; + } + if (arguments.length == 6) params.offsets = [arguments[4], arguments[5]]; + } + return new Anchor(params); + }, + + + /* + Function: repaint + + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + + el - either the id of the element or a jQuery object representing the element. + + Returns: + + void + + See Also: + + + */ + repaint : function(el) { + + var _processElement = function(el) { + var ele = _getElementObject(el); + _draw(ele); + }; + + // TODO: support a jQuery result object too! + + // support both lists... + if (typeof el =='object') { + for (var i = 0; i < el.length; i++) + _processElement(el[i]); + } // ...and single strings. + else _processElement(el); + }, + + /* + Function: repaintEverything + + Repaints all connections. + + Returns: + + void + + See Also: + + + */ + repaintEverything : function() { + for (var elId in endpointsByElement) { + _draw(_getElementObject(elId)); + } + }, + + /* + Function: removeAllEndpoints + + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + + el - either an element id, or a jQuery object for an element. + + Returns: + + void + + See Also: + + + */ + removeAllEndpoints : function(el) { + var elId = _getAttribute(el, "id"); + // first remove all Connections. + jsPlumb.detachAll(elId); + var ebe = endpointsByElement[elId]; + for (var i in ebe) { + _removeElement(ebe[i].canvas); + } + endpointsByElement[elId] = []; + }, + + /* + Function: removeEndpoint + + Removes the given Endpoint from the given element. + + Parameters: + + el - either an element id, or a jQuery object for an element. + endpoint - Endpoint to remove. this is an Endpoint object, such as would have been returned from a call to addEndpoint. + + Returns: + + void + + See Also: + + + */ + removeEndpoint : function(el, endpoint) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + if (ebe) { + if(_removeFromList(endpointsByElement, elId, endpoint)) + _removeElement(endpoint.canvas); + } + }, + + /* + Function: setAutomaticRepaint + + Sets/unsets automatic repaint on window resize. + + Parameters: + + value - whether or not to automatically repaint when the window is resized. + + Returns: + + void + */ + setAutomaticRepaint : function(value) { + automaticRepaint = value; + }, + + /* + Function: setDefaultNewCanvasSize + + Sets the default size jsPlumb will use for a new canvas (we create a square canvas so one value is all that is required). + This is a hack for IE, because ExplorerCanvas seems to need for a canvas to be larger than what you are going to draw on + it at initialisation time. The default value of this is 1200 pixels, which is quite large, but if for some reason you're + drawing connectors that are bigger, you should adjust this value appropriately. + + Parameters: + + size - The default size to use. jsPlumb will use a square canvas so you need only supply one value. + + Returns: + + void + */ + setDefaultNewCanvasSize : function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }, + + /* + Function: setDraggable + + Sets whether or not a given element is draggable, regardless of what any plumb command may request. + + Parameters: + + el - either the id for the element, or a jQuery object representing the element. + + Returns: + + void + */ + setDraggable: _setDraggable, + + /* + Function: setDraggableByDefault + + Sets whether or not elements are draggable by default. Default for this is true. + + Parameters: + draggable - value to set + + Returns: + + void + */ + setDraggableByDefault: function(draggable) { + _draggableByDefault = draggable; + }, + + setDebugLog: function(debugLog) { + log = debugLog; + }, + + /* + Function: setRepaintFunction + + Sets the function to fire when the window size has changed and a repaint was fired. + + Parameters: + f - Function to execute. + + Returns: + void + */ + setRepaintFunction : function(f) { + repaintFunction = f; + }, + + /* + Function: show + + Sets an element's connections to be visible. + + Parameters: + el - either the id of the element, or a jQuery object for the element. + + Returns: + void + */ + show : function(el) { + _setVisible(el, "block"); + }, + + /* + Function: sizeCanvas + + Helper to size a canvas. You would typically use this when writing your own Connector or Endpoint implementation. + + Parameters: + x - [int] x position for the Canvas origin + y - [int] y position for the Canvas origin + w - [int] width of the canvas + h - [int] height of the canvas + + Returns: + void + */ + sizeCanvas : function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; canvas.height = h; + canvas.style.width = w + "px"; canvas.width = w; + canvas.style.left = x + "px"; canvas.style.top = y + "px"; + } + }, + + /** + * gets some test hooks. nothing writable. + */ + getTestHarness : function() { + return { + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + + findIndex : _findIndex + }; + }, + + /** + * Toggles visibility of an element's connections. kept for backwards compatibility + */ + toggle : _toggleVisible, + + /* + Function: toggleVisible + + Toggles visibility of an element's connections. + + Parameters: + el - either the element's id, or a jQuery object representing the element. + + Returns: + void, but should be updated to return the current state + */ + //TODO: update this method to return the current state. + toggleVisible : _toggleVisible, + + /* + Function: toggleDraggable + + Toggles draggability (sic) of an element's connections. + + Parameters: + el - either the element's id, or a jQuery object representing the element. + + Returns: + The current draggable state. + */ + toggleDraggable : _toggleDraggable, + + /* + Function: unload + + Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element + + Returns: + void + */ + unload : function() { + delete endpointsByElement; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + }, + + /* + Function: wrap + Helper method to wrap an existing function with one of your own. This is used by the various + implementations to wrap event callbacks for drag/drop etc; it allows jsPlumb to be + transparent in its handling of these things. If a user supplies their own event callback, + for anything, it will always be called. + Parameters: + + */ + wrap : _wrap + }; + +})(); + +(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function() { + this.parent(); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}; + var _droppableOptions = {}; + var _draggablesByScope = {}; + var _draggablesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }; + + + jsPlumb.CurrentLibrary = { + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop' + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + getElementObject : function(el) { + return $(el); + }, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + initDraggable : function(el, options) { + var drag = _draggablesById[el.get("id")]; + if (!drag) { + var originalZIndex = 0, originalCursor = null; + var dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + options['onStart'] = jsPlumb.wrap(options['onStart'], function() + { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() + { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + }); + + // DROPPABLES: + var scope = options['scope'] || jsPlumb.Defaults.Scope; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop'); + } + }; + + drag = new Drag.Move(el, options); + _add(_draggablesByScope, scope, drag); + _add(_draggablesById, el.get("id"), drag); + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + initDroppable : function(el, options) { + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _add(_droppables, scope, el); + _droppableOptions[el.get("id")] = options; + var filterFunc = function(entry) { + return entry.element != el; + }; + var draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + if (typeof Drag != undefined) + return typeof Drag.Move != undefined; + return false; + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + m.start(properties); + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[0]; + return { left: ui.offsetLeft, top: ui.offsetTop }; + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + } + }; +})(); + +(function() { + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * getOrientation : function() { return [ox, oy]; } + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + jsPlumb.Anchors.TopCenter = jsPlumb.makeAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors.BottomCenter = jsPlumb.makeAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors.LeftMiddle = jsPlumb.makeAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors.RightMiddle = jsPlumb.makeAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors.Center = jsPlumb.makeAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors.TopRight = jsPlumb.makeAnchor(1, 0, 0,-1); + jsPlumb.Anchors.BottomRight = jsPlumb.makeAnchor(1, 1, 0, 1); + jsPlumb.Anchors.TopLeft = jsPlumb.makeAnchor(0, 0, 0, -1); + jsPlumb.Anchors.BottomLeft = jsPlumb.makeAnchor(0, 1, 0, 1); + + + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (w < 2 * lineWidth) { + w = 2 * lineWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - lineWidth; + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < 2 * lineWidth) { + // minimum size is 2 * line Width + h = 2 * lineWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - lineWidth; + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + var sx = sourcePos[0] < targetPos[0] ? w-xo : xo; + var sy = sourcePos[1] < targetPos[1] ? h-yo : yo; + var tx = sourcePos[0] < targetPos[0] ? xo : w-xo; + var ty = sourcePos[1] < targetPos[1] ? yo : h-yo; + var retVal = [ x, y, w, h, sx, sy, tx, ty ]; + + return retVal; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + }; + + /** + * This Connector draws a Bezier curve with two control points. + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + jsPlumb.Connectors.Bezier = function(curviness) { + + var self = this; + this.majorAnchor = curviness || 150; + this.minorAnchor = 10; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) + { + lineWidth = lineWidth || 0; + var w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth, h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + var canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2), canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + var sx = sourcePos[0] < targetPos[0] ? w - (lineWidth/2): (lineWidth/2), sy = sourcePos[1] < targetPos[1] ? h-(lineWidth/2) : (lineWidth/2); + var tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : w-(lineWidth/2), ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : h-(lineWidth/2); + var CP = self._findControlPoint([sx,sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + var CP2 = self._findControlPoint([tx,ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(sx,tx); var minx2 = Math.min(CP[0], CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(sx,tx); var maxx2 = Math.max(CP[0], CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > w) w = maxx; + if (minx < 0) { + canvasX += minx; var ox = Math.abs(minx); + w += ox; CP[0] += ox; sx += ox; tx +=ox; CP2[0] += ox; + } + + var miny1 = Math.min(sy,ty); var miny2 = Math.min(CP[1], CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(sy,ty); var maxy2 = Math.max(CP[1], CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > h) h = maxy; + if (miny < 0) { + canvasY += miny; var oy = Math.abs(miny); + h += oy; CP[1] += oy; sy += oy; ty +=oy; CP2[1] += oy; + } + + // return [ canvasx, canvasy, canvasWidth, canvasHeight, + // sourceX, sourceY, targetX, targetY, + // controlPoint1_X, controlPoint1_Y, controlPoint2_X, controlPoint2_Y + return [canvasX, canvasY, w, h, sx, sy, tx, ty, CP[0], CP[1], CP2[0], CP2[1] ]; + }; + + this.paint = function(d, ctx) { + + /*var img = new Image(); + img.src = "../img/pattern.jpg"; + ctx.fillStyle = ctx.createPattern(img, 'repeat-y');*/ + + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + //ctx.fill(); + } + }; + + + /** + * Types of endpoint UIs. we supply four - a circle of default radius 10px, a rectangle of + * default size 20x20, an image (with no default), and a Triangle, of default size 15. + * you can supply others of these if you want to - see the documentation for a howto. + */ + + /** + * a round endpoint, with default radius 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + } + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * A Rectangular endpoint, with default size 20x20. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + //todo: the fillStyle needs some thought. we want to support a few options: + // 1. nothing supplied; use the stroke color or the default if no stroke color. + // 2. a fill color supplied - use it + // 3. a gradient supplied - use it + // 4. setting the endpoint to the same color as the bg of the element it is attached to. + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + jsPlumb.Endpoints.Triangle = function(params) { + + params = params || { width:15, height:15 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - width/2; + var y = anchorPoint[1] - height/2; + + jsPlumb.sizeCanvas(canvas, x, y, width, height); + + var ctx = canvas.getContext('2d'); + var + offsetX = 0, + offsetY = 0, + angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = endpointStyle.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + ctx.fill(); + + }; + }; + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' property in the params object.. + */ + jsPlumb.Endpoints.Image = function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + }; +})(); diff --git a/archive/1.2/yui.jsPlumb-1.2-RC1.js b/archive/1.2/yui.jsPlumb-1.2-RC1.js new file mode 100644 index 000000000..a92848e0c --- /dev/null +++ b/archive/1.2/yui.jsPlumb-1.2-RC1.js @@ -0,0 +1,73 @@ +(function() { + + var Y; + + YUI().use('node', function(_Y) { + Y = _Y; + }); + + jsPlumb.CurrentLibrary = { + + dragEvents : { + 'start':'drag:start', 'stop':'drag:stop', 'drag':'drag:drag', 'step':'step', + 'over':'over', 'out':'out', 'drop':'drop' + }, + + extend : function(o1, o2) { + for (var i in o2) + o1[i] = o2[i]; + return o1; + }, + + getElementObject : function(el) { + return typeof el == 'string' ? Y.one('#' + el) : el; + }, + + getAttribute : function(el, attributeId) { + return el.getAttribute(attributeId); + }, + + getSize : function(el) { + //TODO must be a proper way to get this. + var bcr = el._node.getBoundingClientRect(); + return [ bcr.width, bcr.height ]; + }, + + getOffset : function(el) { + var bcr = el._node.getBoundingClientRect(); + return { left:bcr.left, top:bcr.top }; + }, + + setAttribute : function(el, attributeName, attributeValue) { + el.setAttribute(attributeName, attributeValue); + }, + + isDragSupported : function(el) { return true; }, + isDropSupported : function(el) { return false; }, + + initDraggable : function(el, options) { + var drag = options['drag:drag']; + new YUI().use('dd-drag', function(Y) { + var dd = new Y.DD.Drag({ + node: '#' + el.getAttribute('id') + }); + dd.on('drag:drag', drag); + }); + }, + + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + removeClass : function(el, clazz) { + el.removeClass(clazz); + }, + + getUIPosition : function(args) { + var t = args[0].target.nodeXY; + return { left:t[0], top:t[1] }; + } + + }; + +})(); \ No newline at end of file diff --git a/archive/1.3.0/jquery.jsPlumb-1.3.0-RC1.js b/archive/1.3.0/jquery.jsPlumb-1.3.0-RC1.js new file mode 100644 index 000000000..93df4f190 --- /dev/null +++ b/archive/1.3.0/jquery.jsPlumb-1.3.0-RC1.js @@ -0,0 +1,273 @@ +/* + * jsPlumb + * + * jquery.jsPlumb 1.3.0-RC1 + * + * jQuery specific functionality for jsPlumb. + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under MIT, GPL2 and Beer licenses. + * + */ +/* + * the library specific functions, such as find offset, get id, get attribute, extend etc. + * the full list is: + * + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getPageXY gets the page event's xy location. + * getParent gets the parent of some element. + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * hasClass returns whether or not the given element has the given class. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + */ +(function($) { + + //var getBoundingClientRectSupported = "getBoundingClientRect" in document.documentElement; + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * appends the given child to the given parent. + */ + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).append(child); + }, + + /** + * event binding wrapper. it just so happens that jQuery uses 'bind' also. yui3, for example, + * uses 'on'. + */ + bind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.bind(event, callback); + }, + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step', + 'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete' + }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + getDocumentElement : function() { return document; }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + }, + + getDragScope : function(el) { + return el.draggable("option", "scope"); + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + * + */ + getElementObject : function(el) { + return typeof(el)=='string' ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).parent(); + }, + + getScrollLeft : function(el) { + return el.scrollLeft(); + }, + + getScrollTop : function(el) { + return el.scrollTop(); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + + // this code is a workaround for the case that the element being dragged has a margin set on it. jquery UI passes + // in the wrong offset if the element has a margin (it doesn't take the margin into account). the getBoundingClientRect + // method, which is in pretty much all browsers now, reports the right numbers. but it introduces a noticeable lag, which + // i don't like. + + /*if ( getBoundingClientRectSupported ) { + var r = eventArgs[1].helper[0].getBoundingClientRect(); + return { left : r.left, top: r.top }; + } else {*/ + var ui = eventArgs[1], _offset = ui.offset; + return _offset || ui.absolutePosition; + //} + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options) { + // remove helper directive if present. + options.helper = null; + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.draggable(options); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.droppable(options); + }, + + isAlreadyDraggable : function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return el.hasClass("ui-draggable"); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).remove(); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * sets the drag scope. probably time for a setDragOption method (roll this and the one above together) + * @param el + * @param scope + */ + setDragScope : function(el, scope) { + el.draggable("option", "scope", scope); + }, + + setOffset : function(el, o) { + jsPlumb.CurrentLibrary.getElementObject(el).offset(o); + } + }; + + $(document).ready(jsPlumb.init); + +})(jQuery); diff --git a/archive/1.3.0/jquery.jsPlumb-1.3.0-all-min.js b/archive/1.3.0/jquery.jsPlumb-1.3.0-all-min.js new file mode 100644 index 000000000..2898a6106 --- /dev/null +++ b/archive/1.3.0/jquery.jsPlumb-1.3.0-all-min.js @@ -0,0 +1 @@ +(function(){var o=!!!document.createElement("canvas").getContext;var s=!!document.createElement("canvas").getContext;var d=!!window.SVGAngle||document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1");var a=!(s|d);var l=function(y,z,w,C){var B=function(F,E){if(F===E){return true}else{if(typeof F=="object"&&typeof E=="object"){var G=true;for(var D in F){if(!B(F[D],E[D])){G=false;break}}for(var D in E){if(!B(E[D],F[D])){G=false;break}}return G}}};for(var A=+w||0,x=y.length;A=0){delete (ax[ay]);ax.splice(ay,1);return true}}}return false},w=function(ay,ax){return J(ay,function(az,aA){ag[aA]=ax;if(i.CurrentLibrary.isDragSupported(az)){i.CurrentLibrary.setDraggable(az,ax)}})},ao=function(ax,ay){V(c(ax,"id"),function(az){az.canvas.style.display=ay})},O=function(ax){return J(ax,function(az,ay){var aA=ag[ay]==null?Y:ag[ay];aA=!aA;ag[ay]=aA;i.CurrentLibrary.setDraggable(az,aA);return aA})},y=function(ax){V(ax,function(az){var ay=("none"==az.canvas.style.display);az.canvas.style.display=ay?"block":"none"})},F=function(aC){var aA=aC.timestamp,ax=aC.recalc,aB=aC.offset,ay=aC.elId;if(!ax){if(aA&&aA===D[ay]){return}}if(ax||aB==null){var az=v(ay);if(az!=null){Q[ay]=b(az);W[ay]=n(az);D[ay]=aA}}else{W[ay]=aB}},aw=function(ax,ay){var az=v(ax);var aA=c(az,"id");if(!aA||aA=="undefined"){if(arguments.length==2&&arguments[1]!=undefined){aA=ay}else{aA="jsPlumb_"+ar()}e(az,"id",aA)}return aA},am=function(az,ax,ay){az=az||function(){};ax=ax||function(){};return function(){var aA=null;try{aA=ax.apply(this,arguments)}catch(aB){k(E,"jsPlumb function failed : "+aB)}if(ay==null||(aA!==ay)){try{az.apply(this,arguments)}catch(aB){k(E,"wrapped function failed : "+aB)}}return aA}};this.connectorClass="_jsPlumb_connector";this.endpointClass="_jsPlumb_endpoint";this.overlayClass="_jsPlumb_overlay";this.Anchors={};this.Connectors={canvas:{},svg:{},vml:{}};this.Endpoints={canvas:{},svg:{},vml:{}};this.Overlays={canvas:{},svg:{},vml:{}};this.addEndpoint=function(az,aA,aJ){aJ=aJ||{};var ay=i.extend({},aJ);i.extend(ay,aA);ay.endpoint=ay.endpoint||E.Defaults.Endpoint||i.Defaults.Endpoint;ay.paintStyle=ay.paintStyle||E.Defaults.EndpointStyle||i.Defaults.EndpointStyle;az=x(az);var aB=[],aE=az.length&&az.constructor!=String?az:[az];for(var aC=0;aC0?l(aK,aJ)!=-1:true},aB=aE.length>1?{}:[],aH=function(aK,aL){if(aE.length>1){var aJ=aB[aK];if(aJ==null){aJ=[];aB[aK]=aJ}aJ.push(aL)}else{aB.push(aL)}};for(var aA in M){if(ay(aE,aA)){for(var az=0;az=4){ay.orientation=[arguments[2],arguments[3]]}if(arguments.length==6){ay.offsets=[arguments[4],arguments[5]]}}var aD=new ac(ay);aD.clone=function(){return new ac(ay)};return aD};this.makeAnchors=function(ay){var az=[];for(var ax=0;ax0?aG[0]:null;var aC=aG.length>0?0:-1;this.locked=false;var aF=this;var aB=function(aJ,aH,aN,aM,aI){var aL=aM[0]+(aJ.x*aI[0]),aK=aM[1]+(aJ.y*aI[1]);return Math.sqrt(Math.pow(aH-aL,2)+Math.pow(aN-aK,2))};var ax=ay||function(aR,aI,aJ,aK,aH){var aM=aJ[0]+(aK[0]/2),aL=aJ[1]+(aK[1]/2);var aO=-1,aQ=Infinity;for(var aN=0;aNaX){aX=a4}}var a8=this.connector.compute(aZ,ba,this.endpoints[be].anchor,this.endpoints[aW].anchor,aI.paintStyleInUse.lineWidth,aX);aI.connector.paint(a8,aI.paintStyleInUse);for(var a9=0;a9=0){aM.connections.splice(aY,1);if(!a1){var a0=aZ.endpoints[0]==aM?aZ.endpoints[1]:aZ.endpoints[0];a0.detach(aZ,true);if(aZ.endpointToDeleteOnDetach&&aZ.endpointToDeleteOnDetach.connections.length==0){i.deleteEndpoint(aZ.endpointToDeleteOnDetach)}}R(aZ.connector.getDisplayElements(),aZ.parent);N(M,aZ.scope,aZ);if(!a1){ad(aZ)}}};this.detachAll=function(){while(aM.connections.length>0){aM.detach(aM.connections[0])}};this.detachFrom=function(aZ){var a0=[];for(var aY=0;aY=0){aM.connections.splice(aY,1)}};this.getElement=function(){return aL};this.getUuid=function(){return aH};this.makeInPlaceCopy=function(){return an({anchor:aM.anchor,source:aL,paintStyle:this.paintStyle,endpoint:aK})};this.isConnectedTo=function(a0){var aZ=false;if(a0){for(var aY=0;aY0){var a8=aM.connections[0];var ba=a8.endpoints[0]==aM?1:0;var a3=ba==0?a8.sourceId:a8.targetId;var a7=W[a3],a9=Q[a3];aZ.txy=[a7.left,a7.top];aZ.twh=a9;aZ.tElement=a8.endpoints[ba]}}a4=aM.anchor.compute(aZ)}var a6=aK.compute(a4,aM.anchor.getOrientation(),aM.paintStyleInUse,a2||aM.paintStyleInUse);aK.paint(a6,aM.paintStyleInUse,aM.anchor);aM.timestamp=a5}};this.repaint=this.paint;this.removeConnection=this.detach;if(aX.isSource&&i.CurrentLibrary.isDragSupported(aL)){var aR=null,aN=null,aQ=null,ax=false,aA=null;var aC=function(){aQ=aM.connectorSelector();if(aM.isFull()&&!aI){return false}F({elId:aE});aB=aM.makeInPlaceCopy();aB.paint();aR=document.createElement("div");aR.style.position="absolute";var a5=v(aR);S(aR,aM.parent);var aY=aw(a5);var a6=v(aB.canvas),a4=i.CurrentLibrary.getOffset(a6),a1=i.CurrentLibrary.getSize(a6);i.CurrentLibrary.setOffset(aR,a4);F({elId:aY});e(v(aM.canvas),"dragId",aY);e(v(aM.canvas),"elId",aE);var a7=new I({reference:aM.anchor,referenceCanvas:aM.canvas});aV=an({paintStyle:aM.paintStyle,endpoint:aK,anchor:a7,source:a5});if(aQ==null){aM.anchor.locked=true;aQ=au({sourceEndpoint:aM,targetEndpoint:aV,source:v(aL),target:v(aR),anchors:[aM.anchor,a7],paintStyle:aX.connectorStyle,hoverPaintStyle:aX.connectorHoverStyle,connector:aX.connector,overlays:aX.connectorOverlays});aQ.connector.setHover(false)}else{ax=true;aQ.connector.setHover(false);aD(v(aB.canvas));var aZ=aQ.sourceId==aE?0:1;aQ.floatingAnchorIndex=aZ;aM.detachFromConnection(aQ);var a3=v(aM.canvas);var a2=i.CurrentLibrary.getDragScope(a3);e(a3,"originalScope",a2);var a0="scope_"+(new Date()).getTime();if(aZ==0){aA=[aQ.source,aQ.sourceId,aU,a2];aQ.source=v(aR);aQ.sourceId=aY}else{aA=[aQ.target,aQ.targetId,aU,a2];aQ.target=v(aR);aQ.targetId=aY}i.CurrentLibrary.setDragScope(aU,a0);aQ.endpoints[aZ==0?1:0].anchor.locked=true;aQ.suspendedEndpoint=aQ.endpoints[aZ];aQ.endpoints[aZ]=aV}Z[aY]=aQ;aV.addConnection(aQ);T(ah,aY,aV);E.currentlyDragging=true};var ay=i.CurrentLibrary,aT=aX.dragOptions||{},aO=i.extend({},ay.defaultDragOptions),aP=ay.dragEvents.start,aW=ay.dragEvents.stop,aG=ay.dragEvents.drag;aT=i.extend(aO,aT);aT.scope=aT.scope||aM.scope;aT[aP]=am(aT[aP],aC);aT[aG]=am(aT[aG],function(){var aY=i.CurrentLibrary.getUIPosition(arguments);i.CurrentLibrary.setOffset(aR,aY);at(v(aR),aY)});aT[aW]=am(aT[aW],function(){N(ah,aN,aV);R([aR,aV.canvas],aL);ae(aB.canvas,aL);var aY=aQ.floatingAnchorIndex==null?1:aQ.floatingAnchorIndex;aQ.endpoints[aY==0?1:0].anchor.locked=false;if(aQ.endpoints[aY]==aV){if(ax&&aQ.suspendedEndpoint){if(aY==0){aQ.source=aA[0];aQ.sourceId=aA[1]}else{aQ.target=aA[0];aQ.targetId=aA[1]}i.CurrentLibrary.setDragScope(aA[2],aA[3]);aQ.endpoints[aY]=aQ.suspendedEndpoint;if(aJ){aQ.floatingAnchorIndex=null;aQ.suspendedEndpoint.addConnection(aQ);i.repaint(aA[1])}else{aQ.endpoints[aY==0?1:0].detach(aQ)}}else{R(aQ.connector.getDisplayElements(),aM.parent);aM.detachFromConnection(aQ)}}aM.anchor.locked=false;aM.paint();aQ.setHover(false);aQ.repaint();aQ=null;delete aB;delete ah[aV.elementId];aV=null;delete aV;E.currentlyDragging=false});var aU=v(aM.canvas);i.CurrentLibrary.initDraggable(aU,aT)}var aD=function(a1){if(aX.isTarget&&i.CurrentLibrary.isDropSupported(aL)){var aY=aX.dropOptions||E.Defaults.DropOptions||i.Defaults.DropOptions;aY=i.extend({},aY);aY.scope=aY.scope||aM.scope;var a4=null;var a2=i.CurrentLibrary.dragEvents.drop;var a3=i.CurrentLibrary.dragEvents.over;var aZ=i.CurrentLibrary.dragEvents.out;var a0=function(){var bd=v(i.CurrentLibrary.getDragObject(arguments));var a5=c(bd,"dragId");var a7=c(bd,"elId");var bc=c(bd,"originalScope");if(bc){i.CurrentLibrary.setDragScope(bd,bc)}var a9=Z[a5];var ba=a9.floatingAnchorIndex==null?1:a9.floatingAnchorIndex,bb=ba==0?1:0;if(!aM.isFull()&&!(ba==0&&!aM.isSource)&&!(ba==1&&!aM.isTarget)){if(ba==0){a9.source=aL;a9.sourceId=aE}else{a9.target=aL;a9.targetId=aE}a9.endpoints[ba].detachFromConnection(a9);if(a9.suspendedEndpoint){a9.suspendedEndpoint.detachFromConnection(a9)}a9.endpoints[ba]=aM;aM.addConnection(a9);if(!a9.suspendedEndpoint){T(M,a9.scope,a9);P(aL,aX.draggable,{})}else{var a8=a9.suspendedEndpoint.getElement(),a6=a9.suspendedEndpoint.elementId;E.fire("jsPlumbConnectionDetached",{source:ba==0?a8:a9.source,target:ba==1?a8:a9.target,sourceId:ba==0?a6:a9.sourceId,targetId:ba==1?a6:a9.targetId,sourceEndpoint:ba==0?a9.suspendedEndpoint:a9.endpoints[0],targetEndpoint:ba==1?a9.suspendedEndpoint:a9.endpoints[1],connection:a9})}i.repaint(a7);E.fire("jsPlumbConnection",{source:a9.source,target:a9.target,sourceId:a9.sourceId,targetId:a9.targetId,sourceEndpoint:a9.endpoints[0],targetEndpoint:a9.endpoints[1],connection:a9})}E.currentlyDragging=false;delete Z[a5]};aY[a2]=am(aY[a2],a0);aY[a3]=am(aY[a3],function(){var a6=i.CurrentLibrary.getDragObject(arguments);var a8=c(v(a6),"dragId");var a7=Z[a8];if(a7!=null){var a5=a7.floatingAnchorIndex==null?1:a7.floatingAnchorIndex;a7.endpoints[a5].anchor.over(aM.anchor)}});aY[aZ]=am(aY[aZ],function(){var a6=i.CurrentLibrary.getDragObject(arguments),a8=c(v(a6),"dragId"),a7=Z[a8];if(a7!=null){var a5=a7.floatingAnchorIndex==null?1:a7.floatingAnchorIndex;a7.endpoints[a5].anchor.out()}});i.CurrentLibrary.initDroppable(a1,aY)}};aD(v(aM.canvas));return aM}};var i=window.jsPlumb=new r();i.getInstance=function(x){var w=new r(x);return w};var m=function(w,B,A,z){return function(){return i.makeAnchor(w,B,A,z)}};i.Anchors.TopCenter=m(0.5,0,0,-1);i.Anchors.BottomCenter=m(0.5,1,0,1);i.Anchors.LeftMiddle=m(0,0.5,-1,0);i.Anchors.RightMiddle=m(1,0.5,1,0);i.Anchors.Center=m(0.5,0.5,0,0);i.Anchors.TopRight=m(1,0,0,-1);i.Anchors.BottomRight=m(1,1,0,1);i.Anchors.TopLeft=m(0,0,0,-1);i.Anchors.BottomLeft=m(0,1,0,1);i.Defaults.DynamicAnchors=function(){return i.makeAnchors(["TopCenter","RightMiddle","BottomCenter","LeftMiddle"])};i.Anchors.AutoDefault=function(){return i.makeDynamicAnchor(i.Defaults.DynamicAnchors())}})();(function(){jsPlumb.DOMElementComponent=function(a){jsPlumb.jsPlumbUIComponent.apply(this,arguments);this.mousemove=this.dblclick=this.click=this.mousedown=this.mouseup=function(b){}};jsPlumb.Connectors.Straight=function(){var n=this;var g=null;var c,h,l,k,i,d,m,f,e,b,a;this.compute=function(r,F,B,o,v,q){var E=Math.abs(r[0]-F[0]);var u=Math.abs(r[1]-F[1]);var z=false,s=false;var t=0.45*E,p=0.45*u;E*=1.9;u*=1.9;var C=Math.min(r[0],F[0])-t;var A=Math.min(r[1],F[1])-p;var D=Math.max(2*v,q);if(E0?1:-1;var t=Math.abs(u*Math.sin(d));if(e>a){t=t*-1}var o=Math.abs(u*Math.cos(d));if(f>b){o=o*-1}return{x:s.x+(r*o),y:s.y+(r*t)}};this.perpendicularToPathAt=function(s,t,w){var u=n.pointAlongPathFrom(s,w);var r=n.gradientAtPoint(u.location);var q=Math.atan(-1/r);var v=t/2*Math.sin(q);var o=t/2*Math.cos(q);return[{x:u.x+o,y:u.y+v},{x:u.x-o,y:u.y-v}]}};jsPlumb.Connectors.Bezier=function(e){var o=this;e=e||{};this.majorAnchor=e.curviness||150;this.minorAnchor=10;var h=null;this._findControlPoint=function(z,q,u,x,r){var w=x.getOrientation(),y=r.getOrientation();var t=w[0]!=y[0]||w[1]==y[1];var s=[];var A=o.majorAnchor,v=o.minorAnchor;if(!t){if(w[0]==0){s.push(q[0]l){l=u}if(x<0){d+=x;var y=Math.abs(x);l+=y;n[0]+=y;i+=y;b+=y;m[0]+=y}var G=Math.min(f,a);var E=Math.min(n[1],m[1]);var t=Math.min(G,E);var z=Math.max(f,a);var w=Math.max(n[1],m[1]);var r=Math.max(z,w);if(r>g){g=r}if(t<0){c+=t;var v=Math.abs(t);g+=v;n[1]+=v;f+=v;a+=v;m[1]+=v}if(D&&l=s){q=t;r=(s-l[t][0])/a[t];break}}return{segment:i[q],proportion:r,index:q}};this.compute=function(Q,v,P,r,q,H){i=[];h=[];a=[];g=[];segmentProportionals=[];d=v[0]t[0]?t[0]+((1-x)*r)-y:t[2]+(x*r)+y,y:q==0?t[3]:t[3]>t[1]?t[1]+((1-x)*r)-y:t[3]+(x*r)+y,segmentInfo:v};return w};this.perpendicularToPathAt=function(t,u,z){var v=n.pointAlongPathFrom(t,z);var s=h[v.segmentInfo.index];var r=Math.atan(-1/s);var w=u/2*Math.sin(r);var q=u/2*Math.cos(r);return[{x:v.x+q,y:v.y+w},{x:v.x-q,y:v.y-w}]}};jsPlumb.Endpoints.Dot=function(b){var a=this;b=b||{};this.radius=b.radius||10;this.defaultOffset=0.5*this.radius;this.defaultInnerRadius=this.radius/3;this.compute=function(g,d,i,f){var e=i.radius||a.radius;var c=g[0]-e;var h=g[1]-e;return[c,h,e*2,e*2,e]}};jsPlumb.Endpoints.Rectangle=function(b){var a=this;b=b||{};this.width=b.width||20;this.height=b.height||20;this.compute=function(h,e,k,g){var f=k.width||a.width;var d=k.height||a.height;var c=h[0]-(f/2);var i=h[1]-(d/2);return[c,i,f,d]}};jsPlumb.Endpoints.Image=function(d){jsPlumb.DOMElementComponent.apply(this,arguments);var a=this,c=false;this.img=new Image();a.ready=false;this.img.onload=function(){a.ready=true};this.img.src=d.src||d.url;this.compute=function(g,e,h,f){a.anchorPoint=g;if(a.ready){return[g[0]-a.img.width/2,g[1]-a.img.height/2,a.img.width,a.img.height]}else{return[0,0,0,0]}};a.canvas=document.createElement("img"),c=false;a.canvas.style.margin=0;a.canvas.style.padding=0;a.canvas.style.outline=0;a.canvas.style.position="absolute";a.canvas.className=jsPlumb.endpointClass;jsPlumb.appendElement(a.canvas,d.parent);a.attachListeners(a.canvas,a);var b=function(k,i,g){if(!c){a.canvas.setAttribute("src",a.img.src);c=true}var h=a.img.width,f=a.img.height,e=a.anchorPoint[0]-(h/2),l=a.anchorPoint[1]-(f/2);jsPlumb.sizeCanvas(a.canvas,e,l,h,f)};this.paint=function(g,f,e){if(a.ready){b(g,f,e)}else{window.setTimeout(function(){a.paint(g,f,e)},200)}}};jsPlumb.Endpoints.Blank=function(){jsPlumb.DOMElementComponent.apply(this,arguments);this.compute=function(){return[0,0,0,0]};self.canvas=document.createElement("div");this.paint=function(){}};jsPlumb.Endpoints.Triangle=function(a){a=a||{};a.width=a.width||55;param.height=a.height||55;this.width=a.width;this.height=a.height;this.compute=function(g,d,i,f){var e=i.width||self.width;var c=i.height||self.height;var b=g[0]-(e/2);var h=g[1]-(c/2);return[b,h,e,c]}};jsPlumb.Overlays.Arrow=function(f){f=f||{};var b=this;this.length=f.length||20;this.width=f.width||20;this.id=f.id;this.connection=f.connection;var e=(f.direction||1)<0?-1:1;var c=f.paintStyle||{lineWidth:1};this.loc=f.location==null?0.5:f.location;var a=f.foldback||0.623;var d=function(g,i){if(a==0.5){return g.pointOnPath(i)}else{var h=0.5-a;return g.pointAlongPathFrom(i,e*b.length*h)}};this.computeMaxSize=function(){return b.width*1.5};this.draw=function(k,q,w){var y=k.pointAlongPathFrom(b.loc,e*(b.length/2));var t=k.pointAlongPathFrom(b.loc,-1*e*(b.length/2)),B=t.x,A=t.y;var r=k.perpendicularToPathAt(b.loc,b.width,-1*e*(b.length/2));var i=d(k,b.loc);if(b.loc==1){var h=k.pointOnPath(b.loc);var v=(h.x-y.x)*e,u=(h.y-y.y)*e;i.x+=v;i.y+=u;t.x+=v;t.y+=u;r[0].x+=v;r[0].y+=u;r[1].x+=v;r[1].y+=u;y.x+=v;y.y+=u}if(b.loc==0){var h=k.pointOnPath(b.loc);var s=a>1?i:{x:r[0].x+((r[1].x-r[0].x)/2),y:r[0].y+((r[1].y-r[0].y)/2)};var v=(h.x-s.x)*e,u=(h.y-s.y)*e;i.x+=v;i.y+=u;t.x+=v;t.y+=u;r[0].x+=v;r[0].y+=u;r[1].x+=v;r[1].y+=u;y.x+=v;y.y+=u}var o=Math.min(y.x,r[0].x,r[1].x);var n=Math.max(y.x,r[0].x,r[1].x);var m=Math.min(y.y,r[0].y,r[1].y);var l=Math.max(y.y,r[0].y,r[1].y);var z={hxy:y,tail:r,cxy:i},x=c.strokeStyle||q.strokeStyle,p=c.fillStyle||q.strokeStyle,g=c.lineWidth||q.lineWidth;b.paint(k,z,g,x,p,w);return[o,n,m,l]}};jsPlumb.Overlays.PlainArrow=function(b){b=b||{};var a=jsPlumb.extend(b,{foldback:1});jsPlumb.Overlays.Arrow.call(this,a)};jsPlumb.Overlays.Diamond=function(c){c=c||{};var a=c.length||40;var b=jsPlumb.extend(c,{length:a/2,foldback:2});jsPlumb.Overlays.Arrow.call(this,b)};jsPlumb.Overlays.Label=function(d){jsPlumb.DOMElementComponent.apply(this,arguments);this.labelStyle=d.labelStyle||jsPlumb.Defaults.LabelStyle;this.labelStyle.font=this.labelStyle.font||"12px sans-serif";this.label=d.label;this.connection=d.connection;this.id=d.id;var k=this;var h=null,e=null,c=null,b=null;this.location=d.location||0.5;this.cachedDimensions=null;var i=false,c=null,a=document.createElement("div");a.style.position="absolute";a.style.textAlign="center";a.style.cursor="pointer";a.style.font=k.labelStyle.font;a.style.color=k.labelStyle.color||"black";if(k.labelStyle.fillStyle){a.style.background=k.labelStyle.fillStyle}if(k.labelStyle.borderWidth>0){var g=k.labelStyle.borderStyle?k.labelStyle.borderStyle:"black";a.style.border=k.labelStyle.borderWidth+"px solid "+g}if(k.labelStyle.padding){a.style.padding=k.labelStyle.padding}var f=d._jsPlumb.overlayClass+" "+(k.labelStyle.cssClass?k.labelStyle.cssClass:d.cssClass?d.cssClass:"");a.className=f;jsPlumb.appendElement(a,d.connection.parent);jsPlumb.getId(a);k.attachListeners(a,k);this.paint=function(l,n,m){if(!i){l.appendDisplayElement(a);k.attachListeners(a,l);i=true}a.style.left=(m[0]+n.minx)+"px";a.style.top=(m[1]+n.miny)+"px"};this.getTextDimensions=function(l){c=typeof k.label=="function"?k.label(k):k.label;a.innerHTML=c.replace(/\r\n/g,"
");var n=jsPlumb.CurrentLibrary.getElementObject(a),m=jsPlumb.CurrentLibrary.getSize(n);return{width:m[0],height:m[1]}};this.computeMaxSize=function(l){var m=k.getTextDimensions(l);return m.width?Math.max(m.width,m.height)*1.5:0};this.draw=function(n,p,o){var r=k.getTextDimensions(n);if(r.width!=null){var q=n.pointOnPath(k.location);var m=q.x-(r.width/2);var l=q.y-(r.height/2);k.paint(n,{minx:m,miny:l,td:r,cxy:q},o);return[m,m+r.width,l,l+r.height]}else{return[0,0,0,0]}}}})();(function(){var h={"stroke-linejoin":"joinstyle",joinstyle:"joinstyle",endcap:"endcap",miterlimit:"miterlimit"};if(document.createStyleSheet){document.createStyleSheet().addRule(".jsplumb_vml","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:textbox","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:oval","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:rect","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:stroke","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:shape","behavior:url(#default#VML);position:absolute;");document.namespaces.add("jsplumb","urn:schemas-microsoft-com:vml")}var c=1000,d=function(p,q){for(var n in q){p[n]=q[n]}},m=function(n,q,r){r=r||{};var p=document.createElement("jsplumb:"+n);p.className=(r["class"]?r["class"]+" ":"")+"jsplumb_vml";l(p,q);d(p,r);return p},l=function(p,n){p.style.left=n[0]+"px";p.style.top=n[1]+"px";p.style.width=n[2]+"px";p.style.height=n[3]+"px";p.style.position="absolute"},g=function(n){return Math.floor(n*c)},a=function(p,n){var v=p,u=function(o){return o.length==1?"0"+o:o},q=function(o){return u(Number(o).toString(16))},r=/(rgb[a]?\()(.*)(\))/;if(p.match(r)){var t=p.match(r)[2].split(",");v="#"+q(t[0])+q(t[1])+q(t[2]);if(!n&&t.length==4){v=v+q(t[3])}}return v},f=function(s,r,p){var u={};if(r.strokeStyle){u.stroked="true";u.strokecolor=a(r.strokeStyle,true);u.strokeweight=r.lineWidth+"px"}else{u.stroked="false"}if(r.fillStyle){u.filled="true";u.fillcolor=a(r.fillStyle,true)}else{u.filled="false"}if(r.dashstyle){if(p.strokeNode==null){p.strokeNode=m("stroke",[0,0,0,0],{dashstyle:r.dashstyle});s.appendChild(p.strokeNode)}else{p.strokeNode.dashstyle=r.dashstyle}}else{if(r["stroke-dasharray"]&&r.lineWidth){var o=r["stroke-dasharray"].indexOf(",")==-1?" ":",",t=r["stroke-dasharray"].split(o),n="";for(var q=0;q0&&F>0&&z=z&&B[2]<=F&&B[3]>=F)){return true}}var D=v.canvas.getContext("2d").getImageData(parseInt(z),parseInt(F),1,1);return D.data[0]!=0||D.data[1]!=0||D.data[2]!=0||D.data[3]!=0}return false};var u=false;var t=false,y=null,x=false;var w=function(A,z){return A!=null&&f(A,z)};this.mousemove=function(C){var E=e(C),B=i(C),A=document.elementFromPoint(B[0],B[1]),D=w(A,"_jsPlumb_overlay");var z=o==null&&(w(A,"_jsPlumb_endpoint")||w(A,"_jsPlumb_connector"));if(!u&&z&&v._over(C)){u=true;v.fire("mouseenter",v,C);return true}else{if(u&&(!v._over(C)||!z)&&!D){u=false;v.fire("mouseexit",v,C)}}v.fire("mousemove",v,C)};this.click=function(z){if(u&&v._over(z)&&!x){v.fire("click",v,z)}x=false};this.dblclick=function(z){if(u&&v._over(z)&&!x){v.fire("dblclick",v,z)}x=false};this.mousedown=function(z){if(v._over(z)&&!t){t=true;y=m(s(v.canvas));v.fire("mousedown",v,z)}};this.mouseup=function(z){t=false;v.fire("mouseup",v,z)}};var p=function(u){var t=document.createElement("canvas");jsPlumb.appendElement(t,u.parent);t.style.position="absolute";if(u["class"]){t.className=u["class"]}u._jsPlumb.getId(t,u.uuid);return t};var d=jsPlumb.CanvasConnector=function(x){n.apply(this,arguments);var t=function(B,z){u.ctx.save();jsPlumb.extend(u.ctx,z);if(z.gradient){var A=u.createGradient(B,u.ctx);for(var y=0;y0?1:-1}}var b={subtract:function(m,l){return{x:m.x-l.x,y:m.y-l.y}},dotProduct:function(m,l){return m.x*l.x+m.y*l.y},square:function(l){return Math.sqrt(l.x*l.x+l.y*l.y)},scale:function(m,l){return{x:m.x*l,y:m.y*l}}},d=Math.pow(2,-65),h=function(y,x){for(var s=[],v=x.length-1,r=2*v-1,t=[],w=[],o=[],p=[],q=[[1,0.6,0.3,0.1],[0.4,0.6,0.6,0.4],[0.1,0.3,0.6,1]],u=0;u<=v;u++){t[u]=b.subtract(x[u],y)}for(u=0;u<=v-1;u++){w[u]=b.subtract(x[u+1],x[u]);w[u]=b.scale(w[u],3)}for(u=0;u<=v-1;u++){for(var n=0;n<=v;n++){o[u]||(o[u]=[]);o[u][n]=b.dotProduct(w[u],t[n])}}for(u=0;u<=r;u++){p[u]||(p[u]=[]);p[u].y=0;p[u].x=parseFloat(u)/r}r=v-1;for(t=0;t<=v+r;t++){w=Math.min(t,v);for(u=Math.max(0,t-r);u<=w;u++){j=t-u;p[u+j].y+=o[j][u]*q[j][u]}}v=x.length-1;p=k(p,2*v-1,s,0);r=b.subtract(y,x[0]);o=b.square(r);for(u=q=0;u=64){y[0]=(E[0].x+E[D].x)/2;return 1}var s,r,p;v=E[0].y-E[D].y;w=E[D].x-E[0].x;A=E[0].x*E[D].y-E[D].x*E[0].y;t=max_distance_below=0;for(r=1;rt){t=p}else{if(p0?1:-1,r=null;n endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + endpointsByElement = {}, + endpointsByUUID = {}, + offsets = {}, + offsetTimestamps = {}, + floatingConnections = {}, + draggableStates = {}, + _mouseEventsEnabled = this.Defaults.MouseEventsEnabled, + _draggableByDefault = true, + canvasList = [], + sizes = [], + listeners = {}, // a map: keys are event types, values are lists of listeners. + DEFAULT_SCOPE = this.Defaults.Scope, + renderMode = null, // will be set in init() + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }, + + /** + * appends an element to the given parent, or the document body if no + * parent given. + */ + _appendElement = function(el, parent) { + if (!parent) + document.body.appendChild(el); + else + jsPlumb.CurrentLibrary.appendElement(el, parent); + }, + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + _timestamp = function() { return "" + (new Date()).getTime(); }, + + /** + * YUI, for some reason, put the result of a Y.all call into an object that contains + * a '_nodes' array, instead of handing back an array-like object like the other + * libraries do. + */ + _convertYUICollection = function(c) { + return c._nodes ? c._nodes : c; + }, + + /** + * Draws an endpoint and its connections. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + _draw = function(element, ui, timestamp) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (!timestamp) timestamp = _timestamp(); + if (endpoints) { + _updateOffset( { elId : id, offset : ui, recalc : false, timestamp : timestamp }); // timestamp is checked against last update cache; it is + // valid for one paint cycle. + var myOffset = offsets[id], myWH = sizes[id]; + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + var l = endpoints[i].connections; + for ( var j = 0; j < l.length; j++) { + l[j].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); // ...paint each connection. + // then, check for dynamic endpoint; need to repaint it. + var oIdx = l[j].endpoints[0] == endpoints[i] ? 1 : 0; + if (l[j].endpoints[oIdx].anchor.isDynamic && !l[j].endpoints[oIdx].isFloating()) { + var oId = oIdx == 0 ? l[j].sourceId : l[j].targetId, + oOffset = offsets[oId], oWH = sizes[oId], + // TODO i still want to make this faster. + anchorLoc = l[j].endpoints[oIdx].anchor.compute( { + xy : [ oOffset.left, oOffset.top ], + wh : oWH, + element : l[j].endpoints[oIdx], + txy : [ myOffset.left, myOffset.top ], + twh : myWH, + tElement : endpoints[i] + }); + l[j].endpoints[oIdx].paint({ anchorLoc : anchorLoc }); + } + } + } + } + }, + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }, + + /** + * gets an Endpoint by uuid. + */ + _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }, + + /** + * inits a draggable if it's not already initialised. + */ + _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element) && !jsPlumb.CurrentLibrary.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + } + } + }, + + _newConnection = function(params) { + var connectionFunc = jsPlumb.Defaults.ConnectionType || Connection, + endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint, + parent = jsPlumb.CurrentLibrary.getParent; + + if (params.sourceEndpoint) + params["parent"] = params.sourceEndpoint.parent; + else if (params.source.constructor == endpointFunc) + params["parent"] = params.source.parent; + else params["parent"] = parent(params.source); + + params["_jsPlumb"] = _currentInstance; + var con = new connectionFunc(params); + _eventFireProxy("click", "click", con); + _eventFireProxy("dblclick", "dblclick", con); + return con; + }, + + _eventFireProxy = function(event, proxyEvent, obj) { + obj.bind(event, function(e) { + _currentInstance.fire(proxyEvent, obj, e); + }); + }, + + _newEndpoint = function(params) { + var endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint; + params["parent"] = jsPlumb.CurrentLibrary.getParent(params.source); + params["_jsPlumb"] = _currentInstance, + ep = new endpointFunc(params); + _eventFireProxy("click", "endpointClick", ep); + _eventFireProxy("dblclick", "endpointDblClick", ep); + return ep; + }, + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }, + /** + * perform an operation on all elements. + */ + _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }, + + /** + * helper to remove an element from the DOM. + */ + _removeElement = function(element, parent) { + if (element != null && element.parentNode != null) { + element.parentNode.removeChild(element); + } + }, + /** + * helper to remove a list of elements from the DOM. + */ + _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }, + /** + * helper method to remove an item from a list. + */ + _removeFromList = function(map, key, value) { + if (key != null) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete (l[i]); + l.splice(i, 1); + return true; + } + } + } + return false; + }, + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }, + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + _setVisible = function(el, state) { + _operation(_getAttribute(el, "id"), function(jpc) { + jpc.canvas.style.display = state; + }); + }, + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }, + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + _toggleVisible = function(elId) { + _operation(elId, function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }, + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return; + } + if (recalc || offset == null) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + } + }, + +/** + * gets an id for the given element, creating and setting one if + * necessary. + */ + _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else + id = "jsPlumb_" + _timestamp(); + _setAttribute(ele, "id", id); + } + return id; + }, + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'jsPlumb function failed : ' + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'wrapped function failed : ' + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = "_jsPlumb_connector"; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = "_jsPlumb_endpoint"; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = "_jsPlumb_overlay"; + + this.Anchors = {}; + + this.Connectors = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Endpoints = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Overlays = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + +// ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***************************************** + /* + * Function: bind + * Bind to an event on jsPlumb. + * + * Parameters: + * event - the event to bind. Available events on jsPlumb are: + * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback. + * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback. + * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback. + * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback. + * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback. + * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback. + * + * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event. + */ + + /* + * Function: clearListeners + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + +// *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *********************************************************** + + /* + Function: addEndpoint + + Adds an to a given element or elements. + + Parameters: + + el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + params - Object containing Endpoint constructor arguments. For more information, see . + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some + shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in + this object are anything that 'params' can contain. See . + + Returns: + The newly created , if el referred to a single element. Otherwise, an array of newly created s. + + See Also: + + */ + this.addEndpoint = function(el, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + + // YUI wrapper + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + var _el = _getElementObject(inputs[i]), id = _getId(_el); + p.source = _el; + _updateOffset({ elId : id }); + var e = _newEndpoint(p); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + results.push(e); + } + + return results.length == 1 ? results[0] : results; + }; + + /* + Function: addEndpoints + Adds a list of s to a given element or elements. + + Parameters: + target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See 's constructor documentation. + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created s, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(el, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + Array.prototype.push.apply(results, _currentInstance.addEndpoint(el, endpoints[i], referenceParams)); + } + return results; + }; + + /* + Function: animate + This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating + the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // onComplete repaints, just to make sure everything looks good at the end of the animation. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /* + Function: connect + Establishes a between two elements (or s, which are themselves registered to elements). + + Parameters: + params - Object containing constructor arguments for the Connection. See 's constructor documentation. + referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of + Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection. + + Returns: + The newly created . + */ + this.connect = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + // dynamic anchors. backwards compatibility here: from 1.2.6 onwards you don't need to specify "dynamicAnchors". the fact that some anchor consists + // of multiple definitions is enough to tell jsPlumb you want it to be dynamic. + if (_p.dynamicAnchors) { + // these can either be an array of anchor coords, which we will use for both source and target, or an object with {source:[anchors], target:[anchors]}, in which + // case we will use a different set for each element. + var a = _p.dynamicAnchors.constructor == Array; + var sa = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.source)); + var ta = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.target)); + _p.anchors = [sa,ta]; + } + + var jpc = _newConnection(_p); + // add to list of connections (by scope). + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + _currentInstance.fire("jsPlumbConnection", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + // force a paint + _draw(jpc.source); + + return jpc; + }; + + /* + Function: deleteEndpoint + Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too) + + Parameters: + object - either an object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElement(endpoint.canvas, endpoint.parent); + // remove from endpointsbyElement + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + delete endpoint; + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every , and their associated s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc) { + _currentInstance.fire("jsPlumbConnectionDetached", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + }; + + /* + Function: detach + Detaches and then removes a . Takes either (source, target) (the old way, maintained for backwards compatibility), or a params + object with various possible values. + + Parameters: + source - id or element object of the first element in the Connection. + target - id or element object of the second element in the Connection. + params - a JS object containing the same parameters as you pass to jsPlumb.connect. If this is present then neither source nor + target should be present; it should be the only argument to the method. See the docs for 's constructor for information +about the parameters allowed in the params object. + Returns: + true if successful, false if not. + */ + this.detach = function(source, target) { + if (arguments.length == 2) { + var s = _getElementObject(source), sId = _getId(s); + var t = _getElementObject(target), tId = _getId(t); + _operation(sId, function(jpc) { + if ((jpc.sourceId == sId && jpc.targetId == tId) || (jpc.targetId == sId && jpc.sourceId == tId)) { + _removeElements(jpc.connector.getDisplayElements(), jpc.parent); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + // this is the new version of the method, taking a JS object like + // the connect method does. + else if (arguments.length == 1) { + // TODO investigate whether or not this code still works when a user has supplied their own subclass of Connection. i suspect it may not. + if (arguments[0].constructor == Connection) { + arguments[0].endpoints[0].detachFrom(arguments[0].endpoints[1]); + } + else if (arguments[0].connection) { + arguments[0].connection.endpoints[0].detachFrom(arguments[0].connection.endpoints[1]); + } + else { + var _p = jsPlumb.extend( {}, source); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1])); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source); + var targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElements(jpc.connector.getDisplayElements(), jpc.parent); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + } + } + }; + + /* + Function: detachAll + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + + Returns: + void + */ + this.detachAllConnections = function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + }; + + /** + * @deprecated Use detachAllConnections instead. this will be removed in jsPlumb 1.3. + */ + this.detachAll = this.detachAllConnections; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + /** + * @deprecated use detachEveryConnection instead. this will be removed in jsPlumb 1.3. + */ + this.detachEverything = this.detachEveryConnection; + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of you library's draggable method so that jsPlumb can setup the appropriate callbacks. Your underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method, + * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the + * default scope). If multiple scopes are passed in, the return value will be a map of { scope -> [ connection... ] }. + * + * Parameters + * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list + * of connections that are in the given scope. + * options - if the argument is a JS object, you can specify a finer-grained filter: + * + * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes. + * - *source* either a string representing an element id, or a selector. constrains the result to connections having this source. + * - *target* either a string representing an element id, or a selector. constrains the result to connections having this target. + * + */ + this.getConnections = function(options) { + if (!options) { + options = {}; + } else if (options.constructor == String) { + options = { "scope": options }; + } + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') + r.push(input); + else + r = input; + } + return r; + }; + var scope = options.scope || jsPlumb.getDefaultScope(), + scopes = prepareList(scope), + sources = prepareList(options.source), + targets = prepareList(options.target), + filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }, + results = scopes.length > 1 ? {} : [], + _addOne = function(scope, obj) { + if (scopes.length > 1) { + var ss = results[scope]; + if (ss == null) { + ss = []; results[scope] = ss; + } + ss.push(obj); + } else results.push(obj); + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + _addOne(i, c); + } + } + } + return results; + }; + + /* + * Function: getAllConnections + * Gets all connections, as a map of { scope -> [ connection... ] }. + */ + this.getAllConnections = function() { + return connectionsByScope; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /** + * Function:getEndpoints + * Gets the list of Endpoints for a given selector, or element id. + * @param el + * @return + */ + this.getEndpoints = function(el) { + return endpointsByElement[_getId(el)]; + }; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + + this.appendElement = _appendElement; + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + + Returns: + void + */ + this.hide = function(el) { + _setVisible(el, "none"); + }; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + if (!initialized) { + _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run. + + var _bind = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && _mouseEventsEnabled && renderMode == jsPlumb.CANVAS) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + var t = c[i].connector[event](e); + if (t) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i].endpoint[event](e)) return; + } + } + } + }); + }; + _bind("click"); + _bind("dblclick"); + _bind("mousemove"); + _bind("mousedown"); + _bind("mouseup"); + + initialized = true; + _currentInstance.fire("ready"); + } + }; + + this.jsPlumbUIComponent = jsPlumbUIComponent; + this.EventGenerator = EventGenerator; + + /* + * Creates an anchor with the given params. + * + * You do not need to use this method. It is exposed because of the way jsPlumb is + * split into three scripts; this will change in the future. + * + * x - the x location of the anchor as a fraction of the + * total width. y - the y location of the anchor as a fraction of the + * total height. xOrientation - value indicating the general direction a + * connection from the anchor should go in, in the x direction. + * yOrientation - value indicating the general direction a connection + * from the anchor should go in, in the y direction. xOffset - a fixed + * offset that should be applied in the x direction that should be + * applied after the x position has been figured out. optional. defaults + * to 0. yOffset - a fixed offset that should be applied in the y + * direction that should be applied after the y position has been + * figured out. optional. defaults to 0. + * -- OR -- + * + * params - {x:..., y:..., xOrientation etc } + * -- OR FROM 1.2.4 --- + * + * name - the name of some Anchor in the _currentInstance.Anchors array. + * -- OR FROM 1.2.4 --- + * + * coords - a list of coords for the anchor, like you would pass to + * jsPlumb.makeAnchor (eg [0.5,0.5,0,-1] - an anchor in the center of + * some element, oriented towards the top of the screen) + * -- OR FROM 1.2.4 --- + * + * anchor - an existing anchor. just gets passed back. it's handy + * internally to have this functionality. + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed + // in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards + // compatibility if we are given only one value we assume it's a + // call in the old form. + if (arguments.length == 0) return null; + var params = {}; + if (arguments.length == 1) { + var specimen = arguments[0]; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; + // is it the name of an anchor type? + else if (typeof specimen == "string") return _currentInstance.Anchors[arguments[0]](); + // is it an array of coordinates? + else if (specimen.constructor == Array) { + if (specimen[0].constructor == Array || specimen[0].constructor == String) + return new DynamicAnchor(specimen); + else + return jsPlumb.makeAnchor.apply(this, specimen); + } + // last we try the backwards compatibility stuff. + else if (typeof arguments[0] == "object") jsPlumb.extend(params, x); + } else { + params = { x : x, y : y }; + if (arguments.length >= 4) params.orientation = [ arguments[2], arguments[3] ]; + if (arguments.length == 6) params.offsets = [ arguments[4], arguments[5] ]; + } + var a = new Anchor(params); + a.clone = function() { + return new Anchor(params); + }; + return a; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types) { + var r = []; + for ( var i = 0; i < types.length; i++) + if (typeof types[i] == "string") + r.push(_currentInstance.Anchors[types[i]]()); + else if (types[i].constructor == Array) + r.push(jsPlumb.makeAnchor(types[i])); + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /** + * Function: makeTarget + * Makes some DOM element a Connection target, allowing you to drag connections to it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a target. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * scope optional. scope for the drop zone. + * dropOptions optional. same stuff as you would pass to dropOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to false. whether or not to delete + * any Endpoints created by a connection to this target if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + this.makeTarget = function(el, params, referenceParams) { + + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + var jpcl = jsPlumb.CurrentLibrary, + scope = p.scope || _currentInstance.Defaults.Scope, + deleteEndpointsOnDetach = p.deleteEndpointsOnDetach || false, + _doOne = function(_el) { + var dropOptions = jsPlumb.extend({}, p.dropOptions || {}); + var _drop = function() { + var draggable = _getElementObject(jpcl.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + // restore the original scope if necessary (issue 57) + scope = _getAttribute(draggable, "originalScope"); + + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + // get the connection, to then get its endpoint + var jpc = floatingConnections[id], + source = jpc.endpoints[0], + _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : null, + // make a new Endpoint + newEndpoint = jsPlumb.addEndpoint(_el, _endpoint); + + var c = jsPlumb.connect({ + source:source, + target:newEndpoint, + scope:scope + }); + if (deleteEndpointsOnDetach) + c.endpointToDeleteOnDetach = newEndpoint; + }; + + var dropEvent = jpcl.dragEvents['drop']; + dropOptions["scope"] = dropOptions["scope"] || scope; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop); + + jpcl.initDroppable(_el, dropOptions); + }; + + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements drop targets. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeTargets = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeTarget(els[i], params, referenceParams); + } + }; + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + for ( var i in ebe) + _currentInstance.deleteEndpoint(ebe[i]); + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + this.deleteEveryEndpoint(); + this.clearListeners(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + + Parameters: + value - whether or not to automatically repaint when the window is resized. + + Returns: void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setDraggableByDefault + * Sets whether or not elements are draggable by default. Default for this is true. + * + * Parameters: + * draggable - value to set + * + * Returns: + * void + */ + this.setDraggableByDefault = function(draggable) { + _draggableByDefault = draggable; + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction + * Sets the function to fire when the window size has changed and a repaint was fired. + * + * Parameters: + * f - Function to execute. + * + * Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: setMouseEventsEnabled + * Sets whether or not mouse events are enabled. Default is true. + * + * Parameters: + * enabled - whether or not mouse events should be enabled. + * + * Returns: + * void + */ + this.setMouseEventsEnabled = function(enabled) { + _mouseEventsEnabled = enabled; + }; + + /* + * Constant for use with the setRenderMode method + */ + this.CANVAS = "canvas"; + + /* + * Constant for use with the setRenderMode method + */ + this.SVG = "svg"; + + this.VML = "vml"; + + /* + * Function: setRenderMode + * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that + * what you asked for is not supported (and that VML is). If you asked for VML but the browser does + * not support it, jsPlumb uses SVG. + * + * Returns: + * the render mode that jsPlumb set, which of course may be different from that requested. + */ + this.setRenderMode = function(mode) { + if (mode) + mode = mode.toLowerCase(); + else + return; + if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); + // now test we actually have the capability to do this. + if (mode === jsPlumb.CANVAS && canvasAvailable) + renderMode = jsPlumb.CANVAS; + else if (mode === jsPlumb.SVG && svgAvailable) + renderMode = jsPlumb.SVG; + else if (vmlAvailable) + renderMode = jsPlumb.VML; + + return renderMode; + }; + + this.getRenderMode = function() { return renderMode; }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * + * Returns: + * void + */ + this.show = function(el) { + _setVisible(el, "block"); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId, + makeAnchor:self.makeAnchor, + makeDynamicAnchor:self.makeDynamicAnchor + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload + * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element. + * + * Returns: + * void + */ + this.unload = function() { + delete endpointsByElement; + delete endpointsByUUID; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + delete canvasList; + }; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + if (timestamp && timestamp === self.timestamp) { + return lastReturnValue; + } + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + //* + var container = element ? element.parent : null; + if (container != null) { + var parentPosition = element.canvas.parentNode.style.position; + var parentIsPositioned = parentPosition != null && parentPosition != "" && parentPosition != "static"; + if (parentIsPositioned) { + console.log("positioning"); + var o = _getOffset(element.canvas.parentNode); + lastReturnValue[0] = lastReturnValue[0] - o.left; + lastReturnValue[1] = lastReturnValue[1] - o.top; + } + } + //*/ + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + * + * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(_getElementObject(refCanvas)); + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + var orientation = null; + var _lastResult = null; + + this.compute = function(params) { + var xy = params.xy, element = params.element; + var result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + //* + var container = element ? element.parent : null; + if (container != null) { + var parentPosition = element.canvas.parentNode.style.position; + var parentIsPositioned = parentPosition != null && parentPosition != "" && parentPosition != "static"; + if (parentIsPositioned) { + var o = _getOffset(element.canvas.parentNode); + result[0] = result[0] - o.left; + result[1] = result[1] - o.top; + } + } + //*/ + + _lastResult = result; + return result; + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { orientation = anchor.getOrientation(); }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchors is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = []; + var _convert = function(anchor) { return anchor.constructor == Anchor ? anchor: jsPlumb.makeAnchor(anchor); }; + for (var i = 0; i < anchors.length; i++) _anchors[i] = _convert(anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null; + var _curIndex = _anchors.length > 0 ? 0 : -1; + this.locked = false; + var self = this; + + // helper method to calculate the distance between the centers of the two elements. + var _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }; + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + var _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + + var pos = _curAnchor.compute(params); + return pos; + }; + + this.getCurrentLocation = function() { + var cl = _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + return cl; + }; + + this.getOrientation = function() { return _curAnchor != null ? _curAnchor.getOrientation() : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * overlays - Optional array of Overlay definitions to appear on this Connection. + */ + var Connection = function(params) { + + jsPlumbUIComponent.apply(this, arguments); + // ************** get the source and target and register the connection. ******************* + var self = this; + var visible = true; + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none"; + }; + var id = new String('_jsplumb_c_' + (new Date()).getTime()); + this.getId = function() { return id; }; + this.parent = params.parent; + /** + Property: source + The source element for this Connection. + */ + this.source = _getElementObject(params.source); + /** + Property:target + The target element for this Connection. + */ + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. + if (params.sourceEndpoint) this.source = params.sourceEndpoint.getElement(); + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + /* + * Property: sourceId + * Id of the source element in the connection. + */ + this.sourceId = _getAttribute(this.source, "id"); + /* + * Property: targetId + * Id of the target element in the connection. + */ + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + + /** + * implementation of abstract method in EventGenerator + * @return list of attached elements. in our case, a list of Endpoints. + */ + this.getAttachedElements = function() { + return self.endpoints; + }; + + /** + * implementation of abstract method in EventGenerator + */ + var srcWhenMouseDown = null, targetWhenMouseDown = null; + this.savePosition = function() { + srcWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.source)); + targetWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.target)); + }; + + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams) { + if (anchorParams) + return jsPlumb.makeAnchor(anchorParams); + }; + var prepareEndpoint = function(existing, index, params, element, connectorPaintStyle, connectorHoverPaintStyle) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] + || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint; + + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified. + if (es.fillStyle == null && connectorPaintStyle != null) + es.fillStyle = connectorPaintStyle.strokeStyle; + + // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does: + //* + if (es.outlineColor == null && connectorPaintStyle != null) + es.outlineColor = connectorPaintStyle.outlineColor; + if (es.outlineWidth == null && connectorPaintStyle != null) + es.outlineWidth = connectorPaintStyle.outlineWidth; + //*/ + + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure? + if (connectorHoverPaintStyle != null) { + if (ehs == null) ehs = {}; + if (ehs.fillStyle == null) { + ehs.fillStyle = connectorHoverPaintStyle.strokeStyle; + } + } + var a = params.anchors ? params.anchors[index] : _makeAnchor(_currentInstance.Defaults.Anchors[index]) || _makeAnchor(jsPlumb.Defaults.Anchors[index]) || _makeAnchor(_currentInstance.Defaults.Anchor) || _makeAnchor(jsPlumb.Defaults.Anchor); + var u = params.uuids ? params.uuids[index] : null; + var e = _newEndpoint( { paintStyle : es, hoverPaintStyle:ehs, endpoint : ep, connections : [ self ], uuid : u, anchor : a, source : element }); + self.endpoints[index] = e; + + if (params.drawEndpoints === false) e.setVisible(false, true, true); + + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source, params.paintStyle, params.hoverPaintStyle); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target, params.paintStyle, params.hoverPaintStyle); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + /* + * Function: setConnector + * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method - the same + * thing that you would set as the 'connector' property on a jsPlumb.connect call. + * + * Parameters: + * connector - Connector definition + */ + this.setConnector = function(connector, doNotRepaint) { + if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent); + var connectorArgs = { _jsPlumb:self._jsPlumb, parent:params.parent, cssClass:params.cssClass }; + if (connector.constructor == String) + this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand. + else if (connector.constructor == Array) + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs)); + this.canvas = this.connector.canvas; + var _mouseDown = false, _mouseWasDown = false, _mouseDownAt = null; + // add mouse events + this.connector.bind("click", function(con, e) { + _mouseWasDown = false; + self.fire("click", self, e); + }); + this.connector.bind("dblclick", function(con, e) { _mouseWasDown = false;self.fire("dblclick", self, e); }); + this.connector.bind("mouseenter", function(con, e) { + if (!self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(true); + } + self.fire("mouseenter", self, e); + } + }); + this.connector.bind("mouseexit", function(con, e) { + if (self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(false); + } + self.fire("mouseexit", self, e); + } + }); + this.connector.bind("mousedown", function(con, e) { + _mouseDown = true; + _mouseDownAt = jsPlumb.CurrentLibrary.getPageXY(e); + self.savePosition(); + }); + this.connector.bind("mouseup", function(con, e) { + _mouseDown = false; + if (self.connector == _connectionBeingDragged) _connectionBeingDragged = null; + }); + + if (!doNotRepaint) self.repaint(); + }; + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc) + */ + + self.setConnector(this.endpoints[0].connector || + this.endpoints[1].connector || + params.connector || + _currentInstance.Defaults.Connector || + jsPlumb.Defaults.Connector, true); + + this.setPaintStyle(this.endpoints[0].connectorStyle || + this.endpoints[1].connectorStyle || + params.paintStyle || + _currentInstance.Defaults.PaintStyle || + jsPlumb.Defaults.PaintStyle, true); + + this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || + this.endpoints[1].connectorHoverStyle || + params.hoverPaintStyle || + _currentInstance.Defaults.HoverPaintStyle || + jsPlumb.Defaults.HoverPaintStyle, true); + + this.paintStyleInUse = this.paintStyle; + + /* + * Property: overlays + * List of Overlays for this Connection. + */ + this.overlays = []; + var _overlays = params.overlays || _currentInstance.Defaults.Overlays; + if (_overlays) { + for (var i = 0; i < _overlays.length; i++) { + var o = _overlays[i], _newOverlay = null, _overlayEvents = null; + if (o.constructor == Array) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0]; + var p = jsPlumb.CurrentLibrary.extend({connection:self, _jsPlumb:_currentInstance}, o[1]); // make a copy of the object so as not to mess up anyone else's reference... + if (o.length == 3) jsPlumb.CurrentLibrary.extend(p, o[2]); + _newOverlay = new jsPlumb.Overlays[renderMode][type](p); + if (p.events) { + for (var evt in p.events) { + _newOverlay.bind(evt, p.events[evt]); + } + } + } else if (o.constructor == String) { + _newOverlay = new jsPlumb.Overlays[renderMode][o]({connection:self, _jsPlumb:_currentInstance}); + } else { + _newOverlay = o; + } + + + + this.overlays.push(_newOverlay); + } + } + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Connection. + * + * Parameters: + * event - the event to bind. Available events on a Connection are: + * - *click* : notification that a Connection was clicked. + * - *dblclick* : notification that a Connection was double clicked. + * - *mouseenter* : notification that the mouse is over a Connection. + * - *mouseexit* : notification that the mouse exited a Connection. + * + * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Connection's paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Connection will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Connection. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + this.addOverlay = function(overlay) { self.overlays.push(overlay); }; + + /** + * Function: removeAllOverlays + * Removes all overlays from the Connection, and then repaints. + */ + this.removeAllOverlays = function() { + self.overlays.splice(0, self.overlays.length); + self.repaint(); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + this.removeOverlay = function(overlayId) { + var idx = -1; + for (var i = 0; i < self.overlays.length; i++) { + if (overlayId === self.overlays[i].id) { + idx = i; + break; + } + } + if (idx != -1) self.overlays.splice(idx, 1); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + this.removeOverlays = function() { + for (var i = 0; i < arguments.length; i++) + self.removeOverlay(arguments[i]); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + this.labelStyle = params.labelStyle || _currentInstance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + if (this.label) { + this.overlays.push(new jsPlumb.Overlays[renderMode].Label( { + cssClass:params.cssClass, + labelStyle : this.labelStyle, + label : this.label, + connection:self, + _jsPlumb:_currentInstance + })); + } + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String or a Function that returns a String. + */ + this.setLabel = function(l) { + self.label = l; + _currentInstance.repaint(self.source); + }; + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var otherOffset = offsets[this.targetId]; + var otherWH = sizes[this.targetId]; + var anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1] + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0] + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc }); + + /* + * Paints the Connection. Not exposed for public usage. + * + * Parameters: + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp; + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = false; + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }); + _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + var sAnchorP = this.endpoints[sIdx].anchor.getCurrentLocation(); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.getCurrentLocation(); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + + /* paint overlays*/ + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + var s = o.computeMaxSize(self.connector); + if (s > maxSize) + maxSize = s; + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, self.paintStyleInUse.lineWidth, maxSize); + + self.connector.paint(dim, self.paintStyleInUse); + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim); + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function() { + this.paint({ elId : this.sourceId, recalc : true }); + }; + + _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide + // whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + + // just to make sure the UI gets initialised fully on all browsers. + self.repaint(); + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1 + * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't + * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you + * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or + * 'target' Endpoints for Connections. + * + * + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * connections - optional list of Connections to configure the Endpoint with. + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + params = params || {}; + var self = this; +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Endpoint. + * + * Parameters: + * event - the event to bind. Available events on an Endpoint are: + * - *click* : notification that a Endpoint was clicked. + * - *dblclick* : notification that a Endpoint was double clicked. + * - *mouseenter* : notification that the mouse is over a Endpoint. + * - *mouseexit* : notification that the mouse exited a Endpoint. + * + * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Endpoint's paint style and then repaints the Endpoint. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Endpoint will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Endpoint. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + var visible = true; + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + var id = new String('_jsplumb_e_' + (new Date()).getTime()); + this.getId = function() { return id; }; + if (params.dynamicAnchors) + self.anchor = new DynamicAnchor(jsPlumb.makeAnchors(params.dynamicAnchors)); + else + self.anchor = params.anchor ? jsPlumb.makeAnchor(params.anchor) : params.anchors ? jsPlumb.makeAnchor(params.anchors) : jsPlumb.makeAnchor("TopCenter"); + var _endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot", + endpointArgs = { _jsPlumb:self._jsPlumb, parent:params.parent }; + if (_endpoint.constructor == String) + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint](endpointArgs); + else if (_endpoint.constructor == Array) + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint[0]](jsPlumb.extend(_endpoint[1], endpointArgs )); + else _endpoint = _endpoint.clone(); + self.endpoint = _endpoint; + this.endpoint.bind("click", function(e) { self.fire("click", self, e); }); + this.endpoint.bind("dblclick", function(e) { self.fire("dblclick", self, e); }); + this.setPaintStyle(params.paintStyle || + params.style || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyle, true); + this.setHoverPaintStyle(params.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyle, true); + this.paintStyleInUse = this.paintStyle; + this.connectorStyle = params.connectorStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.parent = params.parent; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + var _element = params.source, + _uuid = params.uuid, + floatingEndpoint = null, + inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + this.element = _element; + var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + /* + * Property: canvas + * The Endpoint's Canvas. + */ + this.canvas = this.endpoint.canvas; + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + this.connections = params.connections || []; + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + var _reattach = params.reattach || false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + + // this avoids a circular loop + if (!ignoreTarget) { + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + t.detach(connection, true); + // check connection to see if we want to delete the other endpoint. + // if the user uses makeTarget to make some element a target for connections, + // it is possible that they will have set 'endpointToDeleteOnDetach': when + // you make a connection to an element that acts as a target (note: NOT an + // Endpoint; just some div as a target), Endpoints are created for that + // connection. so if you then delete that Connection, it is feasible you + // will want these auto-generated endpoints to be removed. + if (connection.endpointToDeleteOnDetach && connection.endpointToDeleteOnDetach.connections.length == 0) + jsPlumb.deleteEndpoint(connection.endpointToDeleteOnDetach); + } + _removeElements(connection.connector.getDisplayElements(), connection.parent); + _removeFromList(connectionsByScope, connection.scope, connection); + if(!ignoreTarget) fireDetachEvent(connection); + } + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + */ + this.detachAll = function() { + while (self.connections.length > 0) { + self.detach(self.connections[0]); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + */ + this.detachFrom = function(targetEndpoint) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + c[i].setHover(false); + self.detach(c[i]); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + return _newEndpoint( { anchor : self.anchor, source : _element, paintStyle : this.paintStyle, endpoint : _endpoint }); + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + return (self.connections.length < _maxConnections) ? null : self.connections[0]; + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + /* + * Function: setStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to + * setStyle and deprecate it if so. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + * + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + /* + * Function: paint + * Paints the Endpoint, recalculating offset and anchor positions if necessary. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + + params = params || {}; + var timestamp = params.timestamp; + if (!timestamp || self.timestamp !== timestamp) { + var ap = params.anchorPoint, canvas = params.canvas, connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + var xy = params.offset || offsets[_elementId]; + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (self.anchor.isDynamic) { + if (self.connections.length > 0) { + var c = self.connections[0]; + var oIdx = c.endpoints[0] == self ? 1 : 0; + var oId = oIdx == 0 ? c.sourceId : c.targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + } + ap = self.anchor.compute(anchorParams); + } + + var d = _endpoint.compute(ap, self.anchor.getOrientation(), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + _endpoint.paint(d, self.paintStyleInUse, self.anchor); + + self.timestamp = timestamp; + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + var start = function() { + jpc = self.connectorSelector(); + if (self.isFull() && !dragAllowedWhenFull) return false; + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + n.style.position = "absolute"; + var nE = _getElementObject(n); + _appendElement(n, self.parent); + // create and assign an id, and initialize the offset. + var id = _getId(nE); + + // set the offset of this div to be where 'inPlaceCopy' is, to start with. + var ipcoel = _getElementObject(inPlaceCopy.canvas), + ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel), + ipcs = jsPlumb.CurrentLibrary.getSize(ipcoel); + jsPlumb.CurrentLibrary.setOffset(n, ipco); + + _updateOffset( { elId : id }); + + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor( { reference : self.anchor, referenceCanvas : self.canvas }); + floatingEndpoint = _newEndpoint({ paintStyle : self.paintStyle, endpoint : _endpoint, anchor : floatingAnchor, source : nE }); + + if (jpc == null) { + self.anchor.locked = true; + // create a connection. one end is this endpoint, the + // other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : _getElementObject(_element), + target : _getElementObject(n), + anchors : [ self.anchor, floatingAnchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays + }); + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.connector.setHover(false); + } else { + existingJpc = true; + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.connector.setHover(false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas)); + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; // are we the source or the target? + + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas); + var dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // get a new, temporary scope, to use (issue 57) + var newScope = "scope_" + (new Date()).getTime(); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + // set the new, temporary scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(i, newScope); + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // TODO unregister on stop? or will floating endpoint's + // destruction be assured. + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be + // dragging the source at any time + // before this connection is either discarded or made into a + // permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var jpcl = jsPlumb.CurrentLibrary, + dragOptions = params.dragOptions || {}, + defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions), + startEvent = jpcl.dragEvents['start'], + stopEvent = jpcl.dragEvents['stop'], + dragEvent = jpcl.dragEvents['drag']; + + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], + function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + jsPlumb.CurrentLibrary.setOffset(n, _ui); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements( [ n, floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (_reattach) { + + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } else { + jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc); // the main endpoint will inform the floating endpoint + // to disconnect, and also post the detached event. + } + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElements(jpc.connector.getDisplayElements(), self.parent); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint(); + jpc.setHover(false); + jpc.repaint(); + jpc = null; + delete inPlaceCopy; + delete endpointsByElement[floatingEndpoint.elementId]; + floatingEndpoint = null; + delete floatingEndpoint; + + _currentInstance.currentlyDragging = false; + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas) { + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + var drop = function() { + var draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)); + var id = _getAttribute(draggable, "dragId"); + var elId = _getAttribute(draggable, "elId"); + + // restore the original scope if necessary (issue 57) + var scope = _getAttribute(draggable, "originalScope"); + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + var jpc = floatingConnections[id]; + + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // todo test that the target is not full. + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + if (!jpc.suspendedEndpoint) { + _addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + _currentInstance.fire("jsPlumbConnectionDetached", { + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }); + } + + jsPlumb.repaint(elId); + + _currentInstance.fire("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], + targetEndpoint : jpc.endpoints[1], + connection:jpc + }); + } + + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute( _getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + } + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute(_getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + } + }); + + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas)); + + return self; + }; + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + //if (_defaults) jsPlumb.extend(j.Defaults, _defaults); + return j; + }; + + var _curryAnchor = function(x,y,ox,oy) { + return function() { + return jsPlumb.makeAnchor(x,y,ox,oy); + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1); + + jsPlumb.Defaults.DynamicAnchors = function() { + return jsPlumb.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"]); + }; + jsPlumb.Anchors["AutoDefault"] = function() { return jsPlumb.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors()); }; + + +})(); +/* +* jsPlumb-defaults-1.3.0-RC1 +* +* Copyright 2010 - 2011 Simon Porritt http://jsplumb.org +* +* Triple licensed under the MIT, GPL2 and Beer licenses. +*/ + +(function() { + + + + /** + * + * Helper class to consume unused mouse events by components that are DOM elements and + * are used by all of the different rendering modes. + * + */ + jsPlumb.DOMElementComponent = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + // when render mode is canvas, these functions may be called by the canvas mouse handler. + // this component is safe to pipe this stuff to /dev/null. + this.mousemove = + this.dblclick = + this.click = + this.mousedown = + this.mouseup = function(e) { }; + }; + + /** + * Class: Connectors.Straight + * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + var currentPoints = null; + var _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + _dx = _tx - _sx, _dy = (_ty - _sy); + _m = _dy / _dx, _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + + return currentPoints; + }; + + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + var xp = _sx + (location * _dx); + var yp = (_m == Infinity || _m == -Infinity) ? _sy + (location * (_ty - _sy)) : (_m * xp) + _b; + return {x:xp, y:yp}; + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { return _m; }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + var orientation = distance > 0 ? 1 : -1; + var y = Math.abs(distance * Math.sin(_theta)); + if (_sy > _ty) y = y * -1; + var x = Math.abs(distance * Math.cos(_theta)); + if (_sx > _tx) x = x * -1; + return {x:p.x + (orientation * x), y:p.y + (orientation * y)}; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = self.gradientAtPoint(p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + }; + }; + + + /** + * Class:Connectors.Bezier + * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's + * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below. + */ + /** + * Function:Constructor + * + * Parameters: + * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line. + * Optional; defaults to 150. + * + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + params = params || {}; + this.majorAnchor = params.curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h; + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) + { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx); var minx2 = Math.min(_CP[0], _CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(_sx,_tx); var maxx2 = Math.max(_CP[0], _CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty); var miny2 = Math.min(_CP[1], _CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(_sy,_ty); var maxy2 = Math.max(_CP[1], _CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + return jsBezier.perpendicularToCurveAt(_makeCurve(), location, length, distance); + }; + + }; + + + /** + * Class: Connectors.Flowchart + * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments. + */ + /** + * Function: Constructor + * + * Parameters: + * stub - minimum length for the stub at each end of the connector. defaults to 30 pixels. + */ + jsPlumb.Connectors.Flowchart = function(params) { + params = params || {}; + var self = this, + minStubLength = params.stub || params.minStubLength /* bwds compat. */ || 30, + segments = [], + segmentGradients = [], + segmentProportions = [], + segmentLengths = [], + segmentProportionalLengths = [], + points = [], + swapX, + swapY, + /** + * recalculates the gradients of each segment, and the points at which the segments begin, proportional to the total length travelled + * by all the segments that constitute the connector. + */ + updateSegmentGradientsAndProportions = function(startX, startY, endX, endY) { + var total = 0; + for (var i = 0; i < segments.length; i++) { + var sx = i == 0 ? startX : segments[i][2], + sy = i == 0 ? startY : segments[i][3], + ex = segments[i][0], + ey = segments[i][1]; + + segmentGradients[i] = sx == ex ? Infinity : 0; + segmentLengths[i] = Math.abs(sx == ex ? ey - sy : ex - sx); + total += segmentLengths[i]; + } + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segmentLengths[i] / total; + segmentProportions[i] = [curLoc, (curLoc += (segmentLengths[i] / total)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0]; + var ly = segments.length == 0 ? sy : segments[segments.length - 1][1]; + segments.push([x, y, lx, ly]); + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment + * that contains the point which is 'location' distance along the entire path, where + * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths + * are made up of a list of segments, each of which contributes some fraction to + * the total length. + */ + findSegmentForLocation = function(location) { + var idx = segmentProportions.length - 1, inSegmentProportion = 0; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + segments = []; + segmentGradients = []; + segmentProportionalLengths = []; + segmentLengths = []; + segmentProportionals = []; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + offx = (lw / 2) + (minStubLength * 2), + offy = (lw / 2) + (minStubLength * 2), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(), + to = targetAnchor.orientation || targetAnchor.getOrientation(), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx, + h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy; + if (w < minWidth) { + offx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + offy += (minWidth - h) / 2; + h = minWidth; + } + var sx = swapX ? w-offx : offx, + sy = swapY ? h-offy : offy, + tx = swapX ? offx : w-offx , + ty = swapY ? offy : h-offy, + startStubX = sx + (so[0] * minStubLength), + startStubY = sy + (so[1] * minStubLength), + endStubX = tx + (to[0] * minStubLength), + endStubY = ty + (to[1] * minStubLength), + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2); + + x -= offx; y -= offy; + points = [x, y, w, h, sx, sy, tx, ty], extraPoints = []; + + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + if (so[0] == 0) { + var startStubIsBeforeEndStub = startStubY < endStubY; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } + } + else { + var startStubIsBeforeEndStub = startStubX < endStubX; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } + } + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentGradientsAndProportions(sx, sy, tx, ty); + + return points; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location) { + return self.pointAlongPathFrom(location, 0); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location) { + return segmentGradients[findSegmentForLocation(location)["index"]]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance) { + var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segmentLengths[s.index], m = segmentGradients[s.index]; + var e = { + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + return e; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = segmentGradients[p.segmentInfo.index]; + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + + }; + + }; + + // ********************************* END OF CONNECTOR TYPES ******************************************************************* + + // ********************************* ENDPOINT TYPES ******************************************************************* + + /** + * Class: Endpoints.Dot + * A round endpoint, with default radius 10 pixels. + */ + + /** + * Function: Constructor + * + * Parameters: + * + * radius - radius of the endpoint. defaults to 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + var self = this; + params = params || {}; + this.radius = params.radius || 10; + this.defaultOffset = 0.5 * this.radius; + this.defaultInnerRadius = this.radius / 3; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var r = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - r; + var y = anchorPoint[1] - r; + return [ x, y, r * 2, r * 2, r ]; + }; + }; + + /** + * Class: Endpoints.Rectangle + * A Rectangular Endpoint, with default size 20x20. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the endpoint. defaults to 20 pixels. + * height - height of the endpoint. defaults to 20 pixels. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + var self = this; + params = params || {}; + this.width = params.width || 20; + this.height = params.height || 20; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + return [ x, y, width, height]; + }; + }; + + /** + * Class: Endpoints.Image + * Draws an image as the Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * src - location of the image to use. + */ + jsPlumb.Endpoints.Image = function(params) { + + jsPlumb.DOMElementComponent.apply(this, arguments); + + var self = this, initialized = false; + this.img = new Image(); + self.ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.src || params.url; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + self.anchorPoint = anchorPoint; + if (self.ready) return [anchorPoint[0] - self.img.width / 2, anchorPoint[1] - self.img.height/ 2, self.img.width, self.img.height]; + else return [0,0,0,0]; + }; + + self.canvas = document.createElement("img"), initialized = false; + self.canvas.style["margin"] = 0; + self.canvas.style["padding"] = 0; + self.canvas.style["outline"] = 0; + self.canvas.style["position"] = "absolute"; + self.canvas.className = jsPlumb.endpointClass; + jsPlumb.appendElement(self.canvas, params.parent); + self.attachListeners(self.canvas, self); + + var actuallyPaint = function(d, style, anchor) { + if (!initialized) { + self.canvas.setAttribute("src", self.img.src); + initialized = true; + } + var width = self.img.width, + height = self.img.height, + x = self.anchorPoint[0] - (width/2), + y = self.anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(self.canvas, x, y, width, height); + }; + + this.paint = function(d, style, anchor) { + if (self.ready) { + actuallyPaint(d, style, anchor); + } + else { + window.setTimeout(function() { + self.paint(d, style, anchor); + }, 200); + } + }; + }; + + /** + * Class: Endpoints.Blank + * An Endpoint that paints nothing on the screen, and cannot be interacted with using the mouse. There are no constructor parameters for this Endpoint. + */ + jsPlumb.Endpoints.Blank = function() { + jsPlumb.DOMElementComponent.apply(this, arguments); + this.compute = function() { + return [0,0,0,0]; + }; + + self.canvas = document.createElement("div"); + this.paint = function() { }; + }; + + /** + * Class: Endpoints.Triangle + * A triangular Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the triangle's base. defaults to 55 pixels. + * height - height of the triangle from base to apex. defaults to 55 pixels. + */ + jsPlumb.Endpoints.Triangle = function(params) { + params = params || { }; + params.width = params.width || 55; + param.height = params.height || 55; + this.width = params.width; + this.height = params.height; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + return [ x, y, width, height ]; + }; + }; +// ********************************* END OF ENDPOINT TYPES ******************************************************************* + + +// ********************************* OVERLAY DEFINITIONS *********************************************************************** + + /** + * Class: Overlays.Arrow + * + * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length + * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction + * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line + * across the tail. + */ + /** + * Function: Constructor + * + * Parameters: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + params = params || {}; + var self = this; + this.length = params.length || 20; + this.width = params.width || 20; + this.id = params.id; + this.connection = params.connection; + var direction = (params.direction || 1) < 0 ? -1 : 1; + var paintStyle = params.paintStyle || { lineWidth:1 }; + this.loc = params.location == null ? 0.5 : params.location; + // how far along the arrow the lines folding back in come to. default is 62.3%. + var foldback = params.foldback || 0.623; + var _getFoldBackPoint = function(connector, loc) { + if (foldback == 0.5) return connector.pointOnPath(loc); + else { + var adj = 0.5 - foldback; // we calculate relative to the center + return connector.pointAlongPathFrom(loc, direction * self.length * adj); + } + }; + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + // this is the arrow head position + var hxy = connector.pointAlongPathFrom(self.loc, direction * (self.length / 2)); + // this is the center of the tail + var txy = connector.pointAlongPathFrom(self.loc, -1 * direction * (self.length / 2)), tx = txy.x, ty = txy.y; + // this is the tail vector + var tail = connector.perpendicularToPathAt(self.loc, self.width, -1 * direction * (self.length / 2)); + // this is the point the tail goes in to + var cxy = _getFoldBackPoint(connector, self.loc); + + // if loc = 1, then hxy should be flush with the element, or if direction == -1, the tail midpoint. + if (self.loc == 1) { + var lxy = connector.pointOnPath(self.loc); + // TODO determine why the 1.2.6 released version does not + // use 'direction' in the two equations below, yet both + // that and 1.3.0 still paint the arrows correctly. + var dx = (lxy.x - hxy.x) * direction, dy = (lxy.y - hxy.y) * direction; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + // if loc = 0, then tail midpoint should be flush with the element, or, if direction == -1, hxy should be. + if (self.loc == 0) { + var lxy = connector.pointOnPath(self.loc); + var tailMid = foldback > 1 ? cxy : { + x:tail[0].x + ((tail[1].x - tail[0].x) / 2), + y:tail[0].y + ((tail[1].y - tail[0].y) / 2) + }; + var dx = (lxy.x - tailMid.x) * direction, dy = (lxy.y - tailMid.y) * direction; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x); + var maxx = Math.max(hxy.x, tail[0].x, tail[1].x); + var miny = Math.min(hxy.y, tail[0].y, tail[1].y); + var maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + var d = { hxy:hxy, tail:tail, cxy:cxy }, + strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle, + fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle, + lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth; + + self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions); + + return [ minx, maxx, miny, maxy]; + }; + }; + + /** + * Class: Overlays.PlainArrow + * + * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + /** + * Class: Overlays.Diamond + * + * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40; + var p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + + + /** + * Class: Overlays.Label + * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb + * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter + * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it + * puts on the Label's 'style' attribute, so the end result is the same. + */ + /** + * Function: Constructor + * + * Parameters: + * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes + * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle. + * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * labelStyle - (deprecated) js object containing style instructions for the label. defaults to jsPlumb.Defaults.LabelStyle. + * borderWidth - (deprecated) width of a border to paint. defaults to zero. + * borderStyle - (deprecated) strokeStyle to use when painting the border, if necessary. + * + */ + jsPlumb.Overlays.Label = function(params) { + jsPlumb.DOMElementComponent.apply(this, arguments); + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.labelStyle.font = this.labelStyle.font || "12px sans-serif"; + this.label = params.label; + this.connection = params.connection; + this.id = params.id; + var self = this; + var labelWidth = null, labelHeight = null, labelText = null, labelPadding = null; + this.location = params.location || 0.5; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var initialised = false, + labelText = null, + div = document.createElement("div"); + div.style["position"] = "absolute"; + div.style["textAlign"] = "center"; + div.style["cursor"] = "pointer"; + div.style["font"] = self.labelStyle.font; + div.style["color"] = self.labelStyle.color || "black"; + if (self.labelStyle.fillStyle) div.style["background"] = self.labelStyle.fillStyle;//_convertStyle(self.labelStyle.fillStyle, true); + if (self.labelStyle.borderWidth > 0) { + var dStyle = self.labelStyle.borderStyle ? self.labelStyle.borderStyle/*_convertStyle(self.labelStyle.borderStyle, true)*/ : "black"; + div.style["border"] = self.labelStyle.borderWidth + "px solid " + dStyle; + } + if (self.labelStyle.padding) div.style["padding"] = self.labelStyle.padding; + + var clazz = params["_jsPlumb"].overlayClass + " " + + (self.labelStyle.cssClass ? self.labelStyle.cssClass : + params.cssClass ? params.cssClass : ""); + + div.className = clazz; + + jsPlumb.appendElement(div, params.connection.parent); + jsPlumb.getId(div); + self.attachListeners(div, self); + this.paint = function(connector, d, connectorDimensions) { + if (!initialised) { + connector.appendDisplayElement(div); + self.attachListeners(div, connector); + initialised = true; + } + div.style.left = (connectorDimensions[0] + d.minx) + "px"; + div.style.top = (connectorDimensions[1] + d.miny) + "px"; + }; + + this.getTextDimensions = function(connector) { + labelText = typeof self.label == 'function' ? self.label(self) : self.label; + div.innerHTML = labelText.replace(/\r\n/g, "
"); + var de = jsPlumb.CurrentLibrary.getElementObject(div), + s = jsPlumb.CurrentLibrary.getSize(de); + return {width:s[0], height:s[1]}; + }; + + this.computeMaxSize = function(connector) { + var td = self.getTextDimensions(connector); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + var td = self.getTextDimensions(connector); + if (td.width != null) { + var cxy = connector.pointOnPath(self.location); + + var minx = cxy.x - (td.width / 2); + var miny = cxy.y - (td.height / 2); + + self.paint(connector, { + minx:minx, + miny:miny, + td:td, + cxy:cxy + }, connectorDimensions); + + return [minx, minx+td.width, miny, miny+td.height]; + } + else return [0,0,0,0]; + }; + }; + + // ********************************* END OF OVERLAY DEFINITIONS *********************************************************************** + + // ********************************* OVERLAY CANVAS RENDERERS*********************************************************************** + + // ********************************* END OF OVERLAY CANVAS RENDERERS *********************************************************************** +})();;(function() { + + // http://ajaxian.com/archives/the-vml-changes-in-ie-8 + // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/ + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + + var vmlAttributeMap = { + "stroke-linejoin":"joinstyle", + "joinstyle":"joinstyle", + "endcap":"endcap", + "miterlimit":"miterlimit" + }; + + if (document.createStyleSheet) { + + // this is the style rule for IE7/6: it uses a CSS class, tidy. + document.createStyleSheet().addRule(".jsplumb_vml", "behavior:url(#default#VML);position:absolute;"); + + // these are for VML in IE8. you have to explicitly call out which elements + // you're going to expect to support VML! + // + // try to avoid IE8. it is recommended you set X-UA-Compatible="IE=7" if you can. + // + document.createStyleSheet().addRule("jsplumb\\:textbox", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:oval", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:rect", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:stroke", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:shape", "behavior:url(#default#VML);position:absolute;"); + + // in this page it is also mentioned that IE requires the extra arg to the namespace + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either. + // var iev = document.documentMode; + //if (!iev || iev < 8) + document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); + //else + // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML"); + } + + var scale = 1000, + _atts = function(o, atts) { + for (var i in atts) { + // IE8 fix: setattribute does not work after an element has been added to the dom! + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + //o.setAttribute(i, atts[i]); + o[i] = atts[i]; + } + }, + _node = function(name, d, atts) { + atts = atts || {}; + var o = document.createElement("jsplumb:" + name); + o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml"; + _pos(o, d); + _atts(o, atts); + return o; + }, + _pos = function(o,d) { + o.style.left = d[0] + "px"; + o.style.top = d[1] + "px"; + o.style.width= d[2] + "px"; + o.style.height= d[3] + "px"; + o.style.position = "absolute"; + }, + _conv = function(v) { + return Math.floor(v * scale); + }, + _convertStyle = function(s, ignoreAlpha) { + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + + return o; + }, + _applyStyles = function(node, style, component) { + var styleToWrite = {}; + if (style.strokeStyle) { + styleToWrite["stroked"] = "true"; + styleToWrite["strokecolor"] =_convertStyle(style.strokeStyle, true); + styleToWrite["strokeweight"] = style.lineWidth + "px"; + } + else styleToWrite["stroked"] = "false"; + + if (style.fillStyle) { + styleToWrite["filled"] = "true"; + styleToWrite["fillcolor"] = _convertStyle(style.fillStyle, true); + } + else styleToWrite["filled"] = "false"; + + if(style["dashstyle"]) { + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = style["dashstyle"]; + } + else if (style["stroke-dasharray"] && style["lineWidth"]) { + var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",", + parts = style["stroke-dasharray"].split(sep), + styleToUse = ""; + for(var i = 0; i < parts.length; i++) { + styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep); + } + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = styleToUse; + } + + _atts(node, styleToWrite); + }, + /* + * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. + */ + VmlComponent = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }, + /* + * Base class for Vml connectors. extends VmlComponent. + */ + VmlConnector = function(params) { + var self = this; + self.strokeNode = null; + self.canvas = null; + VmlComponent.apply(this, arguments); + clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : ""); + this.paint = function(d, style, anchor) { + if (style != null) { + var path = self.getPath(d), p = { "path":path }; + + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + outlineStyle = { + strokeStyle:_convertStyle(style.outlineColor), + lineWidth:outlineStrokeWidth + }; + + if (self.bgCanvas == null) { + p["class"] = clazz; + self.bgCanvas = _node("shape", d, p); + jsPlumb.appendElement(self.bgCanvas, params.parent); + displayElements.push(self.bgCanvas); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.bgCanvas, d); + _atts(self.bgCanvas, p); + } + + _applyStyles(self.bgCanvas, outlineStyle, self); + } + + if (self.canvas == null) { + p["class"] = clazz; + self.canvas = _node("shape", d, p); + jsPlumb.appendElement(self.canvas, params.parent); + displayElements.push(self.canvas); + + self.attachListeners(self.canvas, self); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.canvas, d); + _atts(self.canvas, p); + } + + _applyStyles(self.canvas, style, self); + } + }; + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + self.canvas.parentNode.appendChild(el); + displayElements.push(el); + }; + }, + /* + * + * Base class for Vml Endpoints. extends VmlComponent. + * + */ + VmlEndpoint = function(params) { + VmlComponent.apply(this, arguments); + var vml = null, self = this; + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function(d, style, anchor) { + var p = { }; + + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (vml == null) { + p["class"] = jsPlumb.endpointClass; + vml = self.getVml([0,0, d[2], d[3]], p, anchor); + self.canvas.appendChild(vml); + self.attachListeners(vml, self); + } + else { + //p["coordsize"] = "1,1";//(d[2] * scale) + "," + (d[3] * scale); again, unsure. + _pos(vml, [0,0, d[2], d[3]]); + _atts(vml, p); + } + + _applyStyles(vml, style); + }; + }; + + jsPlumb.Connectors.vml.Bezier = function() { + jsPlumb.Connectors.Bezier.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Straight = function() { + jsPlumb.Connectors.Straight.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Flowchart = function() { + jsPlumb.Connectors.Flowchart.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l"; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]); + } + // finally draw a line to the end + p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e"; + return p; + }; + }; + + jsPlumb.Endpoints.vml.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("oval", d, atts); }; + }; + + jsPlumb.Endpoints.vml.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("rect", d, atts); }; + }; + + /* + * VML Image Endpoint is the same as the default image endpoint. + */ + jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image; + + /** + * placeholder for Blank endpoint in vml renderer. + */ + jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank; + + /** + * VML Label renderer. uses the default label renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label; + + var AbstractVmlArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + VmlComponent.apply(this, arguments); + var self = this, canvas = null, path =null; + var getPath = function(d, connectorDimensions) { + return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) + + " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + + " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + + " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + + " x e"; + }; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) { + var p = {}; + if (strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] =_convertStyle(strokeStyle, true); + } + if (lineWidth) p["strokeweight"] = lineWidth + "px"; + if (fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = fillStyle; + } + var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + w = Math.abs(xmax - xmin), + h = Math.abs(ymax - ymin), + dim = [xmin, ymin, w, h]; + + // for VML, we create overlays using shapes that have the same dimensions and + // coordsize as their connector - overlays calculate themselves relative to the + // connector (it's how it's been done since the original canvas implementation, because + // for canvas that makes sense). + p["path"] = getPath(d, connectorDimensions); + p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale); + + dim[0] = connectorDimensions[0]; + dim[1] = connectorDimensions[1]; + dim[2] = connectorDimensions[2]; + dim[3] = connectorDimensions[3]; + + if (canvas == null) { + //p["class"] = jsPlumb.overlayClass; // TODO currentInstance? + canvas = _node("shape", dim, p); + connector.appendDisplayElement(canvas); + self.attachListeners(canvas, connector); + } + else { + _pos(canvas, dim); + _atts(canvas, p); + } + }; + }; + + jsPlumb.Overlays.vml.Arrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.vml.PlainArrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.vml.Diamond = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/** + * SVG support for jsPlumb. + * + * things to investigate: + * + * gradients: https://developer.mozilla.org/en/svg_in_html_introduction + * css:http://tutorials.jenkov.com/svg/svg-and-css.html + * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath + * pointer events: https://developer.mozilla.org/en/css/pointer-events + * + */ +;(function() { + + var svgAttributeMap = { + "stroke-linejoin":"stroke-linejoin", + "joinstyle":"stroke-linejoin", + "stroke-dashoffset":"stroke-dashoffset" + }; + + var ns = { + svg:"http://www.w3.org/2000/svg", + xhtml:"http://www.w3.org/1999/xhtml" + }, + _attr = function(node, attributes) { + for (var i in attributes) + node.setAttribute(i, "" + attributes[i]); + }, + _node = function(name, attributes) { + var n = document.createElementNS(ns.svg, name); + attributes = attributes || {}; + attributes["version"] = "1.1"; + attributes["xmnls"] = ns.xhtml; + _attr(n, attributes); + return n; + }, + _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; }, + _convertStyle = function(s, ignoreAlpha) { + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + + return o; + }, + _clearGradient = function(parent) { + for (var i = 0; i < parent.childNodes.length; i++) { + if (parent.childNodes[i].tagName == "linearGradient" || parent.childNodes[i].tagName == "radialGradient") + parent.removeChild(parent.childNodes[i]); + } + }, + _updateGradient = function(parent, node, style, dimensions) { + var id = "jsplumb_gradient_" + (new Date()).getTime(); + // first clear out any existing gradient + _clearGradient(parent); + // this checks for an 'offset' property in the gradient, and in the absence of it, assumes + // we want a linear gradient. if it's there, we create a radial gradient. + // it is possible that a more explicit means of defining the gradient type would be + // better. relying on 'offset' means that we can never have a radial gradient that uses + // some default offset, for instance. + if (!style.gradient.offset) { + var g = _node("linearGradient", {id:id}); + parent.appendChild(g); + } + else { + var g = _node("radialGradient", { + id:id + }); + parent.appendChild(g); + } + + // the svg radial gradient seems to treat stops in the reverse + // order to how canvas does it. so we want to keep all the maths the same, but + // iterate the actual style declarations in reverse order, if the x indexes are not in order. + for (var i = 0; i < style.gradient.stops.length; i++) { + // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of + // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments. + // so, not too concerned about leaving this in for now. + var styleToUse = i; + if (dimensions.length == 8) + styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i; + else + styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i; + var stopColor = _convertStyle(style.gradient.stops[styleToUse][1], true); + var s = _node("stop", {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor}); + g.appendChild(s); + } + var applyGradientTo = style.strokeStyle ? "stroke" : "fill"; + node.setAttribute("style", applyGradientTo + ":url(#" + id + ")"); + }, + _applyStyles = function(parent, node, style, dimensions) { + + if (style.gradient) { + _updateGradient(parent, node, style, dimensions); + } + else { + // make sure we clear any existing gradient + _clearGradient(parent); + node.setAttribute("style", ""); + } + + node.setAttribute("fill", style.fillStyle ? _convertStyle(style.fillStyle, true) : "none"); + node.setAttribute("stroke", style.strokeStyle ? _convertStyle(style.strokeStyle, true) : "none"); + if (style.lineWidth) { + node.setAttribute("stroke-width", style.lineWidth); + } + + // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like + // the syntax in VML but is actually kind of nasty: values are given in the pixel + // coordinate space, whereas in VML they are multiples of the width of the stroked + // line, which makes a lot more sense. for that reason, jsPlumb is supporting both + // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from + // VML, which will be the preferred method. the code below this converts a dashstyle + // attribute given in terms of stroke width into a pixel representation, by using the + // stroke's lineWidth. + if(style["stroke-dasharray"]) { + node.setAttribute("stroke-dasharray", style["stroke-dasharray"]); + } + if (style["dashstyle"] && style["lineWidth"]) { + var sep = style["dashstyle"].indexOf(",") == -1 ? " " : ",", + parts = style["dashstyle"].split(sep), + styleToUse = ""; + parts.forEach(function(p) { + styleToUse += (Math.floor(p * style.lineWidth) + sep); + }); + node.setAttribute("stroke-dasharray", styleToUse); + } + + // extra attributes such as join type, dash offset. + for (var i in svgAttributeMap) { + if (style[i]) { + node.setAttribute(svgAttributeMap[i], style[i]); + } + } + }, + _decodeFont = function(f) { + var r = /([0-9].)(p[xt])\s(.*)/; + var bits = f.match(r); + return {size:bits[1] + bits[2], font:bits[3]}; + }; + + /* + * Base class for SVG components. + */ + var SvgComponent = function(cssClass, originalArgs, pointerEventsSpec) { + var self = this; + pointerEventsSpec = pointerEventsSpec || "all"; + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + self.canvas = null, self.path = null, self.svg = null; + + this.setHover = function() { }; + + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.sizeCanvas(self.canvas,0,0,1,1); + + var clazz = cssClass + " " + (originalArgs[0].cssClass || ""); + + self.svg = _node("svg", { + "style":"", + "width":0, + "height":0, + "pointer-events":pointerEventsSpec, + "class": clazz + }); + + jsPlumb.appendElement(self.canvas, originalArgs[0]["parent"]); + self.canvas.appendChild(self.svg); + + // TODO this displayElement stuff is common between all components, across all + // renderers. would be best moved to jsPlumbUIComponent. + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + this.paint = function(d, style, anchor) { + if (style != null) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + _attr(self.svg, { + "style":_pos([0,0,d[2], d[3]]), + "width": d[2], + "height": d[3] + }); + self._paint.apply(this, arguments); + } + }; + }; + + /* + * Base class for SVG connectors. + */ + var SvgConnector = function(params) { + var self = this; + SvgComponent.apply(this, [ params["_jsPlumb"].connectorClass, arguments, "none" ]); + this._paint = function(d, style) { + var p = self.getPath(d), a = { "d":p }, outlineStyle = null; + a["pointer-events"] = "all"; + + // outline style. actually means drawing an svg object underneath the main one. + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + outlineStyle = { + strokeStyle:_convertStyle(style.outlineColor), + lineWidth:outlineStrokeWidth + }; + + if (self.bgPath == null) { + self.bgPath = _node("path", a); + self.svg.appendChild(self.bgPath); + self.attachListeners(self.bgPath, self); + } + else { + _attr(self.bgPath, a); + } + + _applyStyles(self.svg, self.bgPath, outlineStyle, d); + } + + + if (self.path == null) { + self.path = _node("path", a); + self.svg.appendChild(self.path); + self.attachListeners(self.path, self); + } + else { + _attr(self.path, a); + } + + _applyStyles(self.svg, self.path, style, d); + }; + }; + + /* + * SVG Bezier Connector + */ + jsPlumb.Connectors.svg.Bezier = function(params) { + jsPlumb.Connectors.Bezier.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]; }; + }; + + /* + * SVG straight line Connector + */ + jsPlumb.Connectors.svg.Straight = function(params) { + jsPlumb.Connectors.Straight.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; }; + }; + + jsPlumb.Connectors.svg.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "M " + dimensions[4] + "," + dimensions[5]; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)]; + } + // finally draw a line to the end + p = p + " " + dimensions[6] + "," + dimensions[7]; + return p; + }; + }; + + /* + * Base class for SVG endpoints. + */ + var SvgEndpoint = function(params) { + var self = this; + SvgComponent.apply(this, [ params["_jsPlumb"].endpointClass, arguments, "all" ]); + this._paint = function(d, style) { + var s = jsPlumb.extend({}, style); + if (s.outlineColor) { + s.strokeWidth = s.outlineWidth; + s.strokeStyle = _convertStyle(s.outlineColor, true); + } + + if (self.node == null) { + self.node = self.makeNode(d, s); + self.svg.appendChild(self.node); + self.attachListeners(self.node, self); + } + _applyStyles(self.svg, self.node, s, d); + _pos(self.node, d); + }; + }; + + /* + * SVG Dot Endpoint + */ + jsPlumb.Endpoints.svg.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("circle", { + "cx" : d[2] / 2, + "cy" : d[3] / 2, + "r" : d[2] / 2 + }); + }; + }; + + /* + * SVG Rectangle Endpoint + */ + jsPlumb.Endpoints.svg.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("rect", { + "width":d[2], + "height":d[3] + }); + }; + }; + + /* + * SVG Image Endpoint is the default image endpoint. + */ + jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image; + /* + * Blank endpoint in svg renderer is the default Blank endpoint. + */ + jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank; + /* + * Label endpoint in svg renderer is the default Label endpoint. + */ + jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label; + + + var AbstractSvgArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + var self = this, path =null; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + } + + _attr(path, { + "d" : makePath(d), + stroke : strokeStyle ? strokeStyle : null, + fill : fillStyle ? fillStyle : null + }); + }; + var makePath = function(d) { + return "M" + d.hxy.x + "," + d.hxy.y + + " L" + d.tail[0].x + "," + d.tail[0].y + + " L" + d.cxy.x + "," + d.cxy.y + + " L" + d.tail[1].x + "," + d.tail[1].y + + " L" + d.hxy.x + "," + d.hxy.y; + }; + }; + + jsPlumb.Overlays.svg.Arrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.svg.PlainArrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.svg.Diamond = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();;(function() { + +// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS ******************************************************************* + + // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. + var _connectionBeingDragged = null, + _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }, + _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }, + _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }, + _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, + _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }, + _setOffset = function(el, o) { jsPlumb.CurrentLibrary.setOffset(el, o); }; + + /* + * Class:CanvasMouseAdapter + * Provides support for mouse events on canvases. + */ + var CanvasMouseAdapter = function() { + var self = this; + self.overlayPlacements = []; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + jsPlumb.EventGenerator.apply(this, arguments); + /** + * returns whether or not the given event is ojver a painted area of the canvas. + */ + this._over = function(e) { + var o = _getOffset(_getElementObject(self.canvas)), + pageXY = _pageXY(e), + x = pageXY[0] - o.left, y = pageXY[1] - o.top; + if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + return false; + }; + + var _mouseover = false; + var _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false; + var _nullSafeHasClass = function(el, clazz) { + return el != null && _hasClass(el, clazz); + }; + this.mousemove = function(e) { + var pageXY = _pageXY(e), clientXY = _clientXY(e), + ee = document.elementFromPoint(clientXY[0], clientXY[1]), + eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); + var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); + if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.fire("mouseenter", self, e); + return true; + } + // TODO here there is a remote chance that the overlay the mouse moved onto + // is actually not an overlay for the current component. a more thorough check would + // be to ensure the overlay belonged to the current component. + else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { + _mouseover = false; + self.fire("mouseexit", self, e); + } + self.fire("mousemove", self, e); + }; + + this.click = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); + self.fire("mousedown", self, e); + } + }; + + this.mouseup = function(e) { + //if (self == _connectionBeingDragged) _connectionBeingDragged = null; + _mouseDown = false; + self.fire("mouseup", self, e); + }; + }; + + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + jsPlumb.appendElement(canvas, params.parent); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + params["_jsPlumb"].getId(canvas, params.uuid); + + return canvas; + }; + + /** + * Class:CanvasConnector + * Superclass for Canvas Connector renderers. + */ + var CanvasConnector = jsPlumb.CanvasConnector = function(params) { + + CanvasMouseAdapter.apply(this, arguments); + + var _paintOneStyle = function(dim, aStyle) { + self.ctx.save(); + jsPlumb.extend(self.ctx, aStyle); + if (aStyle.gradient) { + var g = self.createGradient(dim, self.ctx); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + self.ctx.strokeStyle = g; + } + self._paint(dim); + self.ctx.restore(); + }; + + var self = this, + clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent + }); + self.ctx = self.canvas.getContext("2d"); + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + self.paint = function(dim, style) { + if (style != null) { + + jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); + + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + _paintOneStyle(dim, outlineStyle); + } + _paintOneStyle(dim, style); + } + }; + }; + + /** + * Class:CanvasEndpoint + * Superclass for Canvas Endpoint renderers. + */ + var CanvasEndpoint = function(params) { + var self = this; + CanvasMouseAdapter.apply(this, arguments); + var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent + }); + self.ctx = self.canvas.getContext("2d"); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + // _paintOneStyle(d, outlineStyle); + } + + self._paint.apply(this, arguments); + }; + }; + + jsPlumb.Endpoints.canvas.Dot = function(params) { + var self = this; + jsPlumb.Endpoints.Dot.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }; + var calculateAdjustments = function(gradient) { + var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius; + gradient.offset && (offsetAdjustment = parseValue(gradient.offset)); + gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius)); + return [offsetAdjustment, innerRadius]; + }; + this._paint = function(d, style, anchor) { + if (style != null) { + var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(); + jsPlumb.extend(ctx, style); + if (style.gradient) { + var adjustments = calculateAdjustments(style.gradient), + yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0], + xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0], + g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + ctx.beginPath(); + ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + } + }; + }; + + jsPlumb.Endpoints.canvas.Rectangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) { + + var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(); + jsPlumb.extend(ctx, style); + + /* canvas gradient */ + if (style.gradient) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, d[2], d[3]); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + jsPlumb.Endpoints.canvas.Triangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Triangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) + { + var width = d[2], height = d[3], x = d[0], y = d[1]; + + var ctx = self.canvas.getContext('2d'); + var offsetX = 0, offsetY = 0, angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = style.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + /* + * Canvas Image Endpoint: uses the default version, which creates an tag. + */ + jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image; + + /* + * Blank endpoint in all renderers is just the default Blank endpoint. + */ + jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank; + + /* + * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Bezier = function() { + var self = this; + jsPlumb.Connectors.Bezier.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO i doubt this handles the case that source and target are swapped. + this.createGradient = function(dim, ctx, swap) { + return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + /* + * Canvas straight line Connector. Draws a straight line onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Straight = function() { + var self = this; + jsPlumb.Connectors.Straight.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO this does not handle the case that src and target are swapped. + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + jsPlumb.Connectors.canvas.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + +// ********************************* END OF CANVAS RENDERERS ******************************************************************* + + jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label; + + /** + * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. + */ + var CanvasOverlay = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }; + + var AbstractCanvasArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + CanvasOverlay.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + var ctx = connector.ctx; + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(d.hxy.x, d.hxy.y); + ctx.lineTo(d.tail[0].x, d.tail[0].y); + ctx.lineTo(d.cxy.x, d.cxy.y); + ctx.lineTo(d.tail[1].x, d.tail[1].y); + ctx.lineTo(d.hxy.x, d.hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + }; + + jsPlumb.Overlays.canvas.Arrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.canvas.PlainArrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.canvas.Diamond = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/* + * jsPlumb + * + * jquery.jsPlumb 1.3.0-RC1 + * + * jQuery specific functionality for jsPlumb. + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under MIT, GPL2 and Beer licenses. + * + */ +/* + * the library specific functions, such as find offset, get id, get attribute, extend etc. + * the full list is: + * + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getPageXY gets the page event's xy location. + * getParent gets the parent of some element. + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * hasClass returns whether or not the given element has the given class. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + */ +(function($) { + + //var getBoundingClientRectSupported = "getBoundingClientRect" in document.documentElement; + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * appends the given child to the given parent. + */ + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).append(child); + }, + + /** + * event binding wrapper. it just so happens that jQuery uses 'bind' also. yui3, for example, + * uses 'on'. + */ + bind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.bind(event, callback); + }, + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step', + 'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete' + }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + getDocumentElement : function() { return document; }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + }, + + getDragScope : function(el) { + return el.draggable("option", "scope"); + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + * + */ + getElementObject : function(el) { + return typeof(el)=='string' ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).parent(); + }, + + getScrollLeft : function(el) { + return el.scrollLeft(); + }, + + getScrollTop : function(el) { + return el.scrollTop(); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + + // this code is a workaround for the case that the element being dragged has a margin set on it. jquery UI passes + // in the wrong offset if the element has a margin (it doesn't take the margin into account). the getBoundingClientRect + // method, which is in pretty much all browsers now, reports the right numbers. but it introduces a noticeable lag, which + // i don't like. + + /*if ( getBoundingClientRectSupported ) { + var r = eventArgs[1].helper[0].getBoundingClientRect(); + return { left : r.left, top: r.top }; + } else {*/ + var ui = eventArgs[1], _offset = ui.offset; + return _offset || ui.absolutePosition; + //} + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options) { + // remove helper directive if present. + options.helper = null; + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.draggable(options); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.droppable(options); + }, + + isAlreadyDraggable : function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return el.hasClass("ui-draggable"); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).remove(); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * sets the drag scope. probably time for a setDragOption method (roll this and the one above together) + * @param el + * @param scope + */ + setDragScope : function(el, scope) { + el.draggable("option", "scope", scope); + }, + + setOffset : function(el, o) { + jsPlumb.CurrentLibrary.getElementObject(el).offset(o); + } + }; + + $(document).ready(jsPlumb.init); + +})(jQuery); +(function(){if(typeof Math.sgn=="undefined")Math.sgn=function(a){return a==0?0:a>0?1:-1};var p={subtract:function(a,b){return{x:a.x-b.x,y:a.y-b.y}},dotProduct:function(a,b){return a.x*b.x+a.y*b.y},square:function(a){return Math.sqrt(a.x*a.x+a.y*a.y)},scale:function(a,b){return{x:a.x*b,y:a.y*b}}},y=Math.pow(2,-65),u=function(a,b){for(var g=[],d=b.length-1,h=2*d-1,f=[],c=[],l=[],k=[],i=[[1,0.6,0.3,0.1],[0.4,0.6,0.6,0.4],[0.1,0.3,0.6,1]],e=0;e<=d;e++)f[e]=p.subtract(b[e],a);for(e=0;e<=d-1;e++){c[e]= +p.subtract(b[e+1],b[e]);c[e]=p.scale(c[e],3)}for(e=0;e<=d-1;e++)for(var m=0;m<=d;m++){l[e]||(l[e]=[]);l[e][m]=p.dotProduct(c[e],f[m])}for(e=0;e<=h;e++){k[e]||(k[e]=[]);k[e].y=0;k[e].x=parseFloat(e)/h}h=d-1;for(f=0;f<=d+h;f++){c=Math.min(f,d);for(e=Math.max(0,f-h);e<=c;e++){j=f-e;k[e+j].y+=l[j][e]*i[j][e]}}d=b.length-1;k=s(k,2*d-1,g,0);h=p.subtract(a,b[0]);l=p.square(h);for(e=i=0;e=64){g[0]=(a[0].x+a[b].x)/2;return 1}var n,o,q;k=a[0].y-a[b].y;i=a[b].x-a[0].x;e=a[0].x*a[b].y-a[b].x*a[0].y;m=max_distance_below=0;for(o=1;om)m=q;else if(q0?1:-1,c=null;h endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + endpointsByElement = {}, + endpointsByUUID = {}, + offsets = {}, + offsetTimestamps = {}, + floatingConnections = {}, + draggableStates = {}, + _mouseEventsEnabled = this.Defaults.MouseEventsEnabled, + _draggableByDefault = true, + canvasList = [], + sizes = [], + listeners = {}, // a map: keys are event types, values are lists of listeners. + DEFAULT_SCOPE = this.Defaults.Scope, + renderMode = null, // will be set in init() + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }, + + /** + * appends an element to the given parent, or the document body if no + * parent given. + */ + _appendElement = function(el, parent) { + if (!parent) + document.body.appendChild(el); + else + jsPlumb.CurrentLibrary.appendElement(el, parent); + }, + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + _timestamp = function() { return "" + (new Date()).getTime(); }, + + /** + * YUI, for some reason, put the result of a Y.all call into an object that contains + * a '_nodes' array, instead of handing back an array-like object like the other + * libraries do. + */ + _convertYUICollection = function(c) { + return c._nodes ? c._nodes : c; + }, + + /** + * Draws an endpoint and its connections. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + _draw = function(element, ui, timestamp) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (!timestamp) timestamp = _timestamp(); + if (endpoints) { + _updateOffset( { elId : id, offset : ui, recalc : false, timestamp : timestamp }); // timestamp is checked against last update cache; it is + // valid for one paint cycle. + var myOffset = offsets[id], myWH = sizes[id]; + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + var l = endpoints[i].connections; + for ( var j = 0; j < l.length; j++) { + l[j].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); // ...paint each connection. + // then, check for dynamic endpoint; need to repaint it. + var oIdx = l[j].endpoints[0] == endpoints[i] ? 1 : 0; + if (l[j].endpoints[oIdx].anchor.isDynamic && !l[j].endpoints[oIdx].isFloating()) { + var oId = oIdx == 0 ? l[j].sourceId : l[j].targetId, + oOffset = offsets[oId], oWH = sizes[oId], + // TODO i still want to make this faster. + anchorLoc = l[j].endpoints[oIdx].anchor.compute( { + xy : [ oOffset.left, oOffset.top ], + wh : oWH, + element : l[j].endpoints[oIdx], + txy : [ myOffset.left, myOffset.top ], + twh : myWH, + tElement : endpoints[i] + }); + l[j].endpoints[oIdx].paint({ anchorLoc : anchorLoc }); + } + } + } + } + }, + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }, + + /** + * gets an Endpoint by uuid. + */ + _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }, + + /** + * inits a draggable if it's not already initialised. + */ + _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element) && !jsPlumb.CurrentLibrary.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + } + } + }, + + _newConnection = function(params) { + var connectionFunc = jsPlumb.Defaults.ConnectionType || Connection, + endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint, + parent = jsPlumb.CurrentLibrary.getParent; + + if (params.sourceEndpoint) + params["parent"] = params.sourceEndpoint.parent; + else if (params.source.constructor == endpointFunc) + params["parent"] = params.source.parent; + else params["parent"] = parent(params.source); + + params["_jsPlumb"] = _currentInstance; + var con = new connectionFunc(params); + _eventFireProxy("click", "click", con); + _eventFireProxy("dblclick", "dblclick", con); + return con; + }, + + _eventFireProxy = function(event, proxyEvent, obj) { + obj.bind(event, function(e) { + _currentInstance.fire(proxyEvent, obj, e); + }); + }, + + _newEndpoint = function(params) { + var endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint; + params["parent"] = jsPlumb.CurrentLibrary.getParent(params.source); + params["_jsPlumb"] = _currentInstance, + ep = new endpointFunc(params); + _eventFireProxy("click", "endpointClick", ep); + _eventFireProxy("dblclick", "endpointDblClick", ep); + return ep; + }, + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }, + /** + * perform an operation on all elements. + */ + _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }, + + /** + * helper to remove an element from the DOM. + */ + _removeElement = function(element, parent) { + if (element != null && element.parentNode != null) { + element.parentNode.removeChild(element); + } + }, + /** + * helper to remove a list of elements from the DOM. + */ + _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }, + /** + * helper method to remove an item from a list. + */ + _removeFromList = function(map, key, value) { + if (key != null) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete (l[i]); + l.splice(i, 1); + return true; + } + } + } + return false; + }, + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }, + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + _setVisible = function(el, state) { + _operation(_getAttribute(el, "id"), function(jpc) { + jpc.canvas.style.display = state; + }); + }, + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }, + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + _toggleVisible = function(elId) { + _operation(elId, function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }, + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return; + } + if (recalc || offset == null) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + } + }, + +/** + * gets an id for the given element, creating and setting one if + * necessary. + */ + _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else + id = "jsPlumb_" + _timestamp(); + _setAttribute(ele, "id", id); + } + return id; + }, + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'jsPlumb function failed : ' + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'wrapped function failed : ' + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = "_jsPlumb_connector"; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = "_jsPlumb_endpoint"; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = "_jsPlumb_overlay"; + + this.Anchors = {}; + + this.Connectors = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Endpoints = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Overlays = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + +// ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***************************************** + /* + * Function: bind + * Bind to an event on jsPlumb. + * + * Parameters: + * event - the event to bind. Available events on jsPlumb are: + * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback. + * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback. + * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback. + * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback. + * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback. + * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback. + * + * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event. + */ + + /* + * Function: clearListeners + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + +// *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *********************************************************** + + /* + Function: addEndpoint + + Adds an to a given element or elements. + + Parameters: + + el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + params - Object containing Endpoint constructor arguments. For more information, see . + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some + shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in + this object are anything that 'params' can contain. See . + + Returns: + The newly created , if el referred to a single element. Otherwise, an array of newly created s. + + See Also: + + */ + this.addEndpoint = function(el, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + + // YUI wrapper + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + var _el = _getElementObject(inputs[i]), id = _getId(_el); + p.source = _el; + _updateOffset({ elId : id }); + var e = _newEndpoint(p); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + results.push(e); + } + + return results.length == 1 ? results[0] : results; + }; + + /* + Function: addEndpoints + Adds a list of s to a given element or elements. + + Parameters: + target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See 's constructor documentation. + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created s, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(el, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + Array.prototype.push.apply(results, _currentInstance.addEndpoint(el, endpoints[i], referenceParams)); + } + return results; + }; + + /* + Function: animate + This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating + the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // onComplete repaints, just to make sure everything looks good at the end of the animation. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /* + Function: connect + Establishes a between two elements (or s, which are themselves registered to elements). + + Parameters: + params - Object containing constructor arguments for the Connection. See 's constructor documentation. + referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of + Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection. + + Returns: + The newly created . + */ + this.connect = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + // dynamic anchors. backwards compatibility here: from 1.2.6 onwards you don't need to specify "dynamicAnchors". the fact that some anchor consists + // of multiple definitions is enough to tell jsPlumb you want it to be dynamic. + if (_p.dynamicAnchors) { + // these can either be an array of anchor coords, which we will use for both source and target, or an object with {source:[anchors], target:[anchors]}, in which + // case we will use a different set for each element. + var a = _p.dynamicAnchors.constructor == Array; + var sa = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.source)); + var ta = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.target)); + _p.anchors = [sa,ta]; + } + + var jpc = _newConnection(_p); + // add to list of connections (by scope). + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + _currentInstance.fire("jsPlumbConnection", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + // force a paint + _draw(jpc.source); + + return jpc; + }; + + /* + Function: deleteEndpoint + Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too) + + Parameters: + object - either an object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElement(endpoint.canvas, endpoint.parent); + // remove from endpointsbyElement + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + delete endpoint; + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every , and their associated s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc) { + _currentInstance.fire("jsPlumbConnectionDetached", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + }; + + /* + Function: detach + Detaches and then removes a . Takes either (source, target) (the old way, maintained for backwards compatibility), or a params + object with various possible values. + + Parameters: + source - id or element object of the first element in the Connection. + target - id or element object of the second element in the Connection. + params - a JS object containing the same parameters as you pass to jsPlumb.connect. If this is present then neither source nor + target should be present; it should be the only argument to the method. See the docs for 's constructor for information +about the parameters allowed in the params object. + Returns: + true if successful, false if not. + */ + this.detach = function(source, target) { + if (arguments.length == 2) { + var s = _getElementObject(source), sId = _getId(s); + var t = _getElementObject(target), tId = _getId(t); + _operation(sId, function(jpc) { + if ((jpc.sourceId == sId && jpc.targetId == tId) || (jpc.targetId == sId && jpc.sourceId == tId)) { + _removeElements(jpc.connector.getDisplayElements(), jpc.parent); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + // this is the new version of the method, taking a JS object like + // the connect method does. + else if (arguments.length == 1) { + // TODO investigate whether or not this code still works when a user has supplied their own subclass of Connection. i suspect it may not. + if (arguments[0].constructor == Connection) { + arguments[0].endpoints[0].detachFrom(arguments[0].endpoints[1]); + } + else if (arguments[0].connection) { + arguments[0].connection.endpoints[0].detachFrom(arguments[0].connection.endpoints[1]); + } + else { + var _p = jsPlumb.extend( {}, source); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1])); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source); + var targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElements(jpc.connector.getDisplayElements(), jpc.parent); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + } + } + }; + + /* + Function: detachAll + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + + Returns: + void + */ + this.detachAllConnections = function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + }; + + /** + * @deprecated Use detachAllConnections instead. this will be removed in jsPlumb 1.3. + */ + this.detachAll = this.detachAllConnections; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + /** + * @deprecated use detachEveryConnection instead. this will be removed in jsPlumb 1.3. + */ + this.detachEverything = this.detachEveryConnection; + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of you library's draggable method so that jsPlumb can setup the appropriate callbacks. Your underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method, + * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the + * default scope). If multiple scopes are passed in, the return value will be a map of { scope -> [ connection... ] }. + * + * Parameters + * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list + * of connections that are in the given scope. + * options - if the argument is a JS object, you can specify a finer-grained filter: + * + * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes. + * - *source* either a string representing an element id, or a selector. constrains the result to connections having this source. + * - *target* either a string representing an element id, or a selector. constrains the result to connections having this target. + * + */ + this.getConnections = function(options) { + if (!options) { + options = {}; + } else if (options.constructor == String) { + options = { "scope": options }; + } + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') + r.push(input); + else + r = input; + } + return r; + }; + var scope = options.scope || jsPlumb.getDefaultScope(), + scopes = prepareList(scope), + sources = prepareList(options.source), + targets = prepareList(options.target), + filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }, + results = scopes.length > 1 ? {} : [], + _addOne = function(scope, obj) { + if (scopes.length > 1) { + var ss = results[scope]; + if (ss == null) { + ss = []; results[scope] = ss; + } + ss.push(obj); + } else results.push(obj); + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + _addOne(i, c); + } + } + } + return results; + }; + + /* + * Function: getAllConnections + * Gets all connections, as a map of { scope -> [ connection... ] }. + */ + this.getAllConnections = function() { + return connectionsByScope; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /** + * Function:getEndpoints + * Gets the list of Endpoints for a given selector, or element id. + * @param el + * @return + */ + this.getEndpoints = function(el) { + return endpointsByElement[_getId(el)]; + }; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + + this.appendElement = _appendElement; + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + + Returns: + void + */ + this.hide = function(el) { + _setVisible(el, "none"); + }; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + if (!initialized) { + _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run. + + var _bind = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && _mouseEventsEnabled && renderMode == jsPlumb.CANVAS) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + var t = c[i].connector[event](e); + if (t) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i].endpoint[event](e)) return; + } + } + } + }); + }; + _bind("click"); + _bind("dblclick"); + _bind("mousemove"); + _bind("mousedown"); + _bind("mouseup"); + + initialized = true; + _currentInstance.fire("ready"); + } + }; + + this.jsPlumbUIComponent = jsPlumbUIComponent; + this.EventGenerator = EventGenerator; + + /* + * Creates an anchor with the given params. + * + * You do not need to use this method. It is exposed because of the way jsPlumb is + * split into three scripts; this will change in the future. + * + * x - the x location of the anchor as a fraction of the + * total width. y - the y location of the anchor as a fraction of the + * total height. xOrientation - value indicating the general direction a + * connection from the anchor should go in, in the x direction. + * yOrientation - value indicating the general direction a connection + * from the anchor should go in, in the y direction. xOffset - a fixed + * offset that should be applied in the x direction that should be + * applied after the x position has been figured out. optional. defaults + * to 0. yOffset - a fixed offset that should be applied in the y + * direction that should be applied after the y position has been + * figured out. optional. defaults to 0. + * -- OR -- + * + * params - {x:..., y:..., xOrientation etc } + * -- OR FROM 1.2.4 --- + * + * name - the name of some Anchor in the _currentInstance.Anchors array. + * -- OR FROM 1.2.4 --- + * + * coords - a list of coords for the anchor, like you would pass to + * jsPlumb.makeAnchor (eg [0.5,0.5,0,-1] - an anchor in the center of + * some element, oriented towards the top of the screen) + * -- OR FROM 1.2.4 --- + * + * anchor - an existing anchor. just gets passed back. it's handy + * internally to have this functionality. + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed + // in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards + // compatibility if we are given only one value we assume it's a + // call in the old form. + if (arguments.length == 0) return null; + var params = {}; + if (arguments.length == 1) { + var specimen = arguments[0]; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; + // is it the name of an anchor type? + else if (typeof specimen == "string") return _currentInstance.Anchors[arguments[0]](); + // is it an array of coordinates? + else if (specimen.constructor == Array) { + if (specimen[0].constructor == Array || specimen[0].constructor == String) + return new DynamicAnchor(specimen); + else + return jsPlumb.makeAnchor.apply(this, specimen); + } + // last we try the backwards compatibility stuff. + else if (typeof arguments[0] == "object") jsPlumb.extend(params, x); + } else { + params = { x : x, y : y }; + if (arguments.length >= 4) params.orientation = [ arguments[2], arguments[3] ]; + if (arguments.length == 6) params.offsets = [ arguments[4], arguments[5] ]; + } + var a = new Anchor(params); + a.clone = function() { + return new Anchor(params); + }; + return a; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types) { + var r = []; + for ( var i = 0; i < types.length; i++) + if (typeof types[i] == "string") + r.push(_currentInstance.Anchors[types[i]]()); + else if (types[i].constructor == Array) + r.push(jsPlumb.makeAnchor(types[i])); + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /** + * Function: makeTarget + * Makes some DOM element a Connection target, allowing you to drag connections to it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a target. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * scope optional. scope for the drop zone. + * dropOptions optional. same stuff as you would pass to dropOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to false. whether or not to delete + * any Endpoints created by a connection to this target if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + this.makeTarget = function(el, params, referenceParams) { + + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + var jpcl = jsPlumb.CurrentLibrary, + scope = p.scope || _currentInstance.Defaults.Scope, + deleteEndpointsOnDetach = p.deleteEndpointsOnDetach || false, + _doOne = function(_el) { + var dropOptions = jsPlumb.extend({}, p.dropOptions || {}); + var _drop = function() { + var draggable = _getElementObject(jpcl.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + // restore the original scope if necessary (issue 57) + scope = _getAttribute(draggable, "originalScope"); + + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + // get the connection, to then get its endpoint + var jpc = floatingConnections[id], + source = jpc.endpoints[0], + _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : null, + // make a new Endpoint + newEndpoint = jsPlumb.addEndpoint(_el, _endpoint); + + var c = jsPlumb.connect({ + source:source, + target:newEndpoint, + scope:scope + }); + if (deleteEndpointsOnDetach) + c.endpointToDeleteOnDetach = newEndpoint; + }; + + var dropEvent = jpcl.dragEvents['drop']; + dropOptions["scope"] = dropOptions["scope"] || scope; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop); + + jpcl.initDroppable(_el, dropOptions); + }; + + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements drop targets. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeTargets = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeTarget(els[i], params, referenceParams); + } + }; + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + for ( var i in ebe) + _currentInstance.deleteEndpoint(ebe[i]); + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + this.deleteEveryEndpoint(); + this.clearListeners(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + + Parameters: + value - whether or not to automatically repaint when the window is resized. + + Returns: void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setDraggableByDefault + * Sets whether or not elements are draggable by default. Default for this is true. + * + * Parameters: + * draggable - value to set + * + * Returns: + * void + */ + this.setDraggableByDefault = function(draggable) { + _draggableByDefault = draggable; + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction + * Sets the function to fire when the window size has changed and a repaint was fired. + * + * Parameters: + * f - Function to execute. + * + * Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: setMouseEventsEnabled + * Sets whether or not mouse events are enabled. Default is true. + * + * Parameters: + * enabled - whether or not mouse events should be enabled. + * + * Returns: + * void + */ + this.setMouseEventsEnabled = function(enabled) { + _mouseEventsEnabled = enabled; + }; + + /* + * Constant for use with the setRenderMode method + */ + this.CANVAS = "canvas"; + + /* + * Constant for use with the setRenderMode method + */ + this.SVG = "svg"; + + this.VML = "vml"; + + /* + * Function: setRenderMode + * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that + * what you asked for is not supported (and that VML is). If you asked for VML but the browser does + * not support it, jsPlumb uses SVG. + * + * Returns: + * the render mode that jsPlumb set, which of course may be different from that requested. + */ + this.setRenderMode = function(mode) { + if (mode) + mode = mode.toLowerCase(); + else + return; + if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); + // now test we actually have the capability to do this. + if (mode === jsPlumb.CANVAS && canvasAvailable) + renderMode = jsPlumb.CANVAS; + else if (mode === jsPlumb.SVG && svgAvailable) + renderMode = jsPlumb.SVG; + else if (vmlAvailable) + renderMode = jsPlumb.VML; + + return renderMode; + }; + + this.getRenderMode = function() { return renderMode; }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * + * Returns: + * void + */ + this.show = function(el) { + _setVisible(el, "block"); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId, + makeAnchor:self.makeAnchor, + makeDynamicAnchor:self.makeDynamicAnchor + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload + * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element. + * + * Returns: + * void + */ + this.unload = function() { + delete endpointsByElement; + delete endpointsByUUID; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + delete canvasList; + }; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + if (timestamp && timestamp === self.timestamp) { + return lastReturnValue; + } + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + //* + var container = element ? element.parent : null; + if (container != null) { + var parentPosition = element.canvas.parentNode.style.position; + var parentIsPositioned = parentPosition != null && parentPosition != "" && parentPosition != "static"; + if (parentIsPositioned) { + console.log("positioning"); + var o = _getOffset(element.canvas.parentNode); + lastReturnValue[0] = lastReturnValue[0] - o.left; + lastReturnValue[1] = lastReturnValue[1] - o.top; + } + } + //*/ + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + * + * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(_getElementObject(refCanvas)); + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + var orientation = null; + var _lastResult = null; + + this.compute = function(params) { + var xy = params.xy, element = params.element; + var result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + //* + var container = element ? element.parent : null; + if (container != null) { + var parentPosition = element.canvas.parentNode.style.position; + var parentIsPositioned = parentPosition != null && parentPosition != "" && parentPosition != "static"; + if (parentIsPositioned) { + var o = _getOffset(element.canvas.parentNode); + result[0] = result[0] - o.left; + result[1] = result[1] - o.top; + } + } + //*/ + + _lastResult = result; + return result; + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { orientation = anchor.getOrientation(); }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchors is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = []; + var _convert = function(anchor) { return anchor.constructor == Anchor ? anchor: jsPlumb.makeAnchor(anchor); }; + for (var i = 0; i < anchors.length; i++) _anchors[i] = _convert(anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null; + var _curIndex = _anchors.length > 0 ? 0 : -1; + this.locked = false; + var self = this; + + // helper method to calculate the distance between the centers of the two elements. + var _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }; + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + var _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + + var pos = _curAnchor.compute(params); + return pos; + }; + + this.getCurrentLocation = function() { + var cl = _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + return cl; + }; + + this.getOrientation = function() { return _curAnchor != null ? _curAnchor.getOrientation() : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * overlays - Optional array of Overlay definitions to appear on this Connection. + */ + var Connection = function(params) { + + jsPlumbUIComponent.apply(this, arguments); + // ************** get the source and target and register the connection. ******************* + var self = this; + var visible = true; + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none"; + }; + var id = new String('_jsplumb_c_' + (new Date()).getTime()); + this.getId = function() { return id; }; + this.parent = params.parent; + /** + Property: source + The source element for this Connection. + */ + this.source = _getElementObject(params.source); + /** + Property:target + The target element for this Connection. + */ + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. + if (params.sourceEndpoint) this.source = params.sourceEndpoint.getElement(); + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + /* + * Property: sourceId + * Id of the source element in the connection. + */ + this.sourceId = _getAttribute(this.source, "id"); + /* + * Property: targetId + * Id of the target element in the connection. + */ + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + + /** + * implementation of abstract method in EventGenerator + * @return list of attached elements. in our case, a list of Endpoints. + */ + this.getAttachedElements = function() { + return self.endpoints; + }; + + /** + * implementation of abstract method in EventGenerator + */ + var srcWhenMouseDown = null, targetWhenMouseDown = null; + this.savePosition = function() { + srcWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.source)); + targetWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.target)); + }; + + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams) { + if (anchorParams) + return jsPlumb.makeAnchor(anchorParams); + }; + var prepareEndpoint = function(existing, index, params, element, connectorPaintStyle, connectorHoverPaintStyle) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] + || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint; + + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified. + if (es.fillStyle == null && connectorPaintStyle != null) + es.fillStyle = connectorPaintStyle.strokeStyle; + + // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does: + //* + if (es.outlineColor == null && connectorPaintStyle != null) + es.outlineColor = connectorPaintStyle.outlineColor; + if (es.outlineWidth == null && connectorPaintStyle != null) + es.outlineWidth = connectorPaintStyle.outlineWidth; + //*/ + + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure? + if (connectorHoverPaintStyle != null) { + if (ehs == null) ehs = {}; + if (ehs.fillStyle == null) { + ehs.fillStyle = connectorHoverPaintStyle.strokeStyle; + } + } + var a = params.anchors ? params.anchors[index] : _makeAnchor(_currentInstance.Defaults.Anchors[index]) || _makeAnchor(jsPlumb.Defaults.Anchors[index]) || _makeAnchor(_currentInstance.Defaults.Anchor) || _makeAnchor(jsPlumb.Defaults.Anchor); + var u = params.uuids ? params.uuids[index] : null; + var e = _newEndpoint( { paintStyle : es, hoverPaintStyle:ehs, endpoint : ep, connections : [ self ], uuid : u, anchor : a, source : element }); + self.endpoints[index] = e; + + if (params.drawEndpoints === false) e.setVisible(false, true, true); + + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source, params.paintStyle, params.hoverPaintStyle); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target, params.paintStyle, params.hoverPaintStyle); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + /* + * Function: setConnector + * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method - the same + * thing that you would set as the 'connector' property on a jsPlumb.connect call. + * + * Parameters: + * connector - Connector definition + */ + this.setConnector = function(connector, doNotRepaint) { + if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent); + var connectorArgs = { _jsPlumb:self._jsPlumb, parent:params.parent, cssClass:params.cssClass }; + if (connector.constructor == String) + this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand. + else if (connector.constructor == Array) + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs)); + this.canvas = this.connector.canvas; + var _mouseDown = false, _mouseWasDown = false, _mouseDownAt = null; + // add mouse events + this.connector.bind("click", function(con, e) { + _mouseWasDown = false; + self.fire("click", self, e); + }); + this.connector.bind("dblclick", function(con, e) { _mouseWasDown = false;self.fire("dblclick", self, e); }); + this.connector.bind("mouseenter", function(con, e) { + if (!self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(true); + } + self.fire("mouseenter", self, e); + } + }); + this.connector.bind("mouseexit", function(con, e) { + if (self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(false); + } + self.fire("mouseexit", self, e); + } + }); + this.connector.bind("mousedown", function(con, e) { + _mouseDown = true; + _mouseDownAt = jsPlumb.CurrentLibrary.getPageXY(e); + self.savePosition(); + }); + this.connector.bind("mouseup", function(con, e) { + _mouseDown = false; + if (self.connector == _connectionBeingDragged) _connectionBeingDragged = null; + }); + + if (!doNotRepaint) self.repaint(); + }; + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc) + */ + + self.setConnector(this.endpoints[0].connector || + this.endpoints[1].connector || + params.connector || + _currentInstance.Defaults.Connector || + jsPlumb.Defaults.Connector, true); + + this.setPaintStyle(this.endpoints[0].connectorStyle || + this.endpoints[1].connectorStyle || + params.paintStyle || + _currentInstance.Defaults.PaintStyle || + jsPlumb.Defaults.PaintStyle, true); + + this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || + this.endpoints[1].connectorHoverStyle || + params.hoverPaintStyle || + _currentInstance.Defaults.HoverPaintStyle || + jsPlumb.Defaults.HoverPaintStyle, true); + + this.paintStyleInUse = this.paintStyle; + + /* + * Property: overlays + * List of Overlays for this Connection. + */ + this.overlays = []; + var _overlays = params.overlays || _currentInstance.Defaults.Overlays; + if (_overlays) { + for (var i = 0; i < _overlays.length; i++) { + var o = _overlays[i], _newOverlay = null, _overlayEvents = null; + if (o.constructor == Array) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0]; + var p = jsPlumb.CurrentLibrary.extend({connection:self, _jsPlumb:_currentInstance}, o[1]); // make a copy of the object so as not to mess up anyone else's reference... + if (o.length == 3) jsPlumb.CurrentLibrary.extend(p, o[2]); + _newOverlay = new jsPlumb.Overlays[renderMode][type](p); + if (p.events) { + for (var evt in p.events) { + _newOverlay.bind(evt, p.events[evt]); + } + } + } else if (o.constructor == String) { + _newOverlay = new jsPlumb.Overlays[renderMode][o]({connection:self, _jsPlumb:_currentInstance}); + } else { + _newOverlay = o; + } + + + + this.overlays.push(_newOverlay); + } + } + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Connection. + * + * Parameters: + * event - the event to bind. Available events on a Connection are: + * - *click* : notification that a Connection was clicked. + * - *dblclick* : notification that a Connection was double clicked. + * - *mouseenter* : notification that the mouse is over a Connection. + * - *mouseexit* : notification that the mouse exited a Connection. + * + * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Connection's paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Connection will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Connection. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + this.addOverlay = function(overlay) { self.overlays.push(overlay); }; + + /** + * Function: removeAllOverlays + * Removes all overlays from the Connection, and then repaints. + */ + this.removeAllOverlays = function() { + self.overlays.splice(0, self.overlays.length); + self.repaint(); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + this.removeOverlay = function(overlayId) { + var idx = -1; + for (var i = 0; i < self.overlays.length; i++) { + if (overlayId === self.overlays[i].id) { + idx = i; + break; + } + } + if (idx != -1) self.overlays.splice(idx, 1); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + this.removeOverlays = function() { + for (var i = 0; i < arguments.length; i++) + self.removeOverlay(arguments[i]); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + this.labelStyle = params.labelStyle || _currentInstance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + if (this.label) { + this.overlays.push(new jsPlumb.Overlays[renderMode].Label( { + cssClass:params.cssClass, + labelStyle : this.labelStyle, + label : this.label, + connection:self, + _jsPlumb:_currentInstance + })); + } + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String or a Function that returns a String. + */ + this.setLabel = function(l) { + self.label = l; + _currentInstance.repaint(self.source); + }; + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var otherOffset = offsets[this.targetId]; + var otherWH = sizes[this.targetId]; + var anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1] + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0] + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc }); + + /* + * Paints the Connection. Not exposed for public usage. + * + * Parameters: + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp; + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = false; + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }); + _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + var sAnchorP = this.endpoints[sIdx].anchor.getCurrentLocation(); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.getCurrentLocation(); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + + /* paint overlays*/ + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + var s = o.computeMaxSize(self.connector); + if (s > maxSize) + maxSize = s; + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, self.paintStyleInUse.lineWidth, maxSize); + + self.connector.paint(dim, self.paintStyleInUse); + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim); + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function() { + this.paint({ elId : this.sourceId, recalc : true }); + }; + + _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide + // whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + + // just to make sure the UI gets initialised fully on all browsers. + self.repaint(); + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1 + * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't + * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you + * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or + * 'target' Endpoints for Connections. + * + * + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * connections - optional list of Connections to configure the Endpoint with. + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + params = params || {}; + var self = this; +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Endpoint. + * + * Parameters: + * event - the event to bind. Available events on an Endpoint are: + * - *click* : notification that a Endpoint was clicked. + * - *dblclick* : notification that a Endpoint was double clicked. + * - *mouseenter* : notification that the mouse is over a Endpoint. + * - *mouseexit* : notification that the mouse exited a Endpoint. + * + * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Endpoint's paint style and then repaints the Endpoint. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Endpoint will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Endpoint. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + var visible = true; + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + var id = new String('_jsplumb_e_' + (new Date()).getTime()); + this.getId = function() { return id; }; + if (params.dynamicAnchors) + self.anchor = new DynamicAnchor(jsPlumb.makeAnchors(params.dynamicAnchors)); + else + self.anchor = params.anchor ? jsPlumb.makeAnchor(params.anchor) : params.anchors ? jsPlumb.makeAnchor(params.anchors) : jsPlumb.makeAnchor("TopCenter"); + var _endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot", + endpointArgs = { _jsPlumb:self._jsPlumb, parent:params.parent }; + if (_endpoint.constructor == String) + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint](endpointArgs); + else if (_endpoint.constructor == Array) + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint[0]](jsPlumb.extend(_endpoint[1], endpointArgs )); + else _endpoint = _endpoint.clone(); + self.endpoint = _endpoint; + this.endpoint.bind("click", function(e) { self.fire("click", self, e); }); + this.endpoint.bind("dblclick", function(e) { self.fire("dblclick", self, e); }); + this.setPaintStyle(params.paintStyle || + params.style || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyle, true); + this.setHoverPaintStyle(params.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyle, true); + this.paintStyleInUse = this.paintStyle; + this.connectorStyle = params.connectorStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.parent = params.parent; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + var _element = params.source, + _uuid = params.uuid, + floatingEndpoint = null, + inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + this.element = _element; + var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + /* + * Property: canvas + * The Endpoint's Canvas. + */ + this.canvas = this.endpoint.canvas; + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + this.connections = params.connections || []; + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + var _reattach = params.reattach || false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + + // this avoids a circular loop + if (!ignoreTarget) { + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + t.detach(connection, true); + // check connection to see if we want to delete the other endpoint. + // if the user uses makeTarget to make some element a target for connections, + // it is possible that they will have set 'endpointToDeleteOnDetach': when + // you make a connection to an element that acts as a target (note: NOT an + // Endpoint; just some div as a target), Endpoints are created for that + // connection. so if you then delete that Connection, it is feasible you + // will want these auto-generated endpoints to be removed. + if (connection.endpointToDeleteOnDetach && connection.endpointToDeleteOnDetach.connections.length == 0) + jsPlumb.deleteEndpoint(connection.endpointToDeleteOnDetach); + } + _removeElements(connection.connector.getDisplayElements(), connection.parent); + _removeFromList(connectionsByScope, connection.scope, connection); + if(!ignoreTarget) fireDetachEvent(connection); + } + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + */ + this.detachAll = function() { + while (self.connections.length > 0) { + self.detach(self.connections[0]); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + */ + this.detachFrom = function(targetEndpoint) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + c[i].setHover(false); + self.detach(c[i]); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + return _newEndpoint( { anchor : self.anchor, source : _element, paintStyle : this.paintStyle, endpoint : _endpoint }); + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + return (self.connections.length < _maxConnections) ? null : self.connections[0]; + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + /* + * Function: setStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to + * setStyle and deprecate it if so. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + * + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + /* + * Function: paint + * Paints the Endpoint, recalculating offset and anchor positions if necessary. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + + params = params || {}; + var timestamp = params.timestamp; + if (!timestamp || self.timestamp !== timestamp) { + var ap = params.anchorPoint, canvas = params.canvas, connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + var xy = params.offset || offsets[_elementId]; + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (self.anchor.isDynamic) { + if (self.connections.length > 0) { + var c = self.connections[0]; + var oIdx = c.endpoints[0] == self ? 1 : 0; + var oId = oIdx == 0 ? c.sourceId : c.targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + } + ap = self.anchor.compute(anchorParams); + } + + var d = _endpoint.compute(ap, self.anchor.getOrientation(), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + _endpoint.paint(d, self.paintStyleInUse, self.anchor); + + self.timestamp = timestamp; + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + var start = function() { + jpc = self.connectorSelector(); + if (self.isFull() && !dragAllowedWhenFull) return false; + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + n.style.position = "absolute"; + var nE = _getElementObject(n); + _appendElement(n, self.parent); + // create and assign an id, and initialize the offset. + var id = _getId(nE); + + // set the offset of this div to be where 'inPlaceCopy' is, to start with. + var ipcoel = _getElementObject(inPlaceCopy.canvas), + ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel), + ipcs = jsPlumb.CurrentLibrary.getSize(ipcoel); + jsPlumb.CurrentLibrary.setOffset(n, ipco); + + _updateOffset( { elId : id }); + + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor( { reference : self.anchor, referenceCanvas : self.canvas }); + floatingEndpoint = _newEndpoint({ paintStyle : self.paintStyle, endpoint : _endpoint, anchor : floatingAnchor, source : nE }); + + if (jpc == null) { + self.anchor.locked = true; + // create a connection. one end is this endpoint, the + // other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : _getElementObject(_element), + target : _getElementObject(n), + anchors : [ self.anchor, floatingAnchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays + }); + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.connector.setHover(false); + } else { + existingJpc = true; + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.connector.setHover(false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas)); + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; // are we the source or the target? + + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas); + var dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // get a new, temporary scope, to use (issue 57) + var newScope = "scope_" + (new Date()).getTime(); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + // set the new, temporary scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(i, newScope); + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // TODO unregister on stop? or will floating endpoint's + // destruction be assured. + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be + // dragging the source at any time + // before this connection is either discarded or made into a + // permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var jpcl = jsPlumb.CurrentLibrary, + dragOptions = params.dragOptions || {}, + defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions), + startEvent = jpcl.dragEvents['start'], + stopEvent = jpcl.dragEvents['stop'], + dragEvent = jpcl.dragEvents['drag']; + + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], + function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + jsPlumb.CurrentLibrary.setOffset(n, _ui); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements( [ n, floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (_reattach) { + + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } else { + jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc); // the main endpoint will inform the floating endpoint + // to disconnect, and also post the detached event. + } + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElements(jpc.connector.getDisplayElements(), self.parent); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint(); + jpc.setHover(false); + jpc.repaint(); + jpc = null; + delete inPlaceCopy; + delete endpointsByElement[floatingEndpoint.elementId]; + floatingEndpoint = null; + delete floatingEndpoint; + + _currentInstance.currentlyDragging = false; + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas) { + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + var drop = function() { + var draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)); + var id = _getAttribute(draggable, "dragId"); + var elId = _getAttribute(draggable, "elId"); + + // restore the original scope if necessary (issue 57) + var scope = _getAttribute(draggable, "originalScope"); + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + var jpc = floatingConnections[id]; + + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // todo test that the target is not full. + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + if (!jpc.suspendedEndpoint) { + _addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + _currentInstance.fire("jsPlumbConnectionDetached", { + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }); + } + + jsPlumb.repaint(elId); + + _currentInstance.fire("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], + targetEndpoint : jpc.endpoints[1], + connection:jpc + }); + } + + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute( _getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + } + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute(_getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + } + }); + + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas)); + + return self; + }; + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + //if (_defaults) jsPlumb.extend(j.Defaults, _defaults); + return j; + }; + + var _curryAnchor = function(x,y,ox,oy) { + return function() { + return jsPlumb.makeAnchor(x,y,ox,oy); + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1); + + jsPlumb.Defaults.DynamicAnchors = function() { + return jsPlumb.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"]); + }; + jsPlumb.Anchors["AutoDefault"] = function() { return jsPlumb.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors()); }; + + +})(); diff --git a/archive/1.3.0/jsPlumb-1.3.0-tests.js b/archive/1.3.0/jsPlumb-1.3.0-tests.js new file mode 100644 index 000000000..ea3442c93 --- /dev/null +++ b/archive/1.3.0/jsPlumb-1.3.0-tests.js @@ -0,0 +1,1629 @@ + +var _getContextNode = function() { + return $("#container"); +}; + +var assertContextExists = function() { + ok(_getContextNode().length == 1, "context node exists"); +}; + +var assertContextSize = function(elementCount) { + //equals(_getContextNode().children().length - _divs.length, elementCount, 'context has ' + elementCount + ' children'); +}; + +var assertContextEmpty = function() { + equals(_getContextNode().children.length, 0, "context empty"); +}; + +var assertEndpointCount = function(elId, count) { + equals(jsPlumb.getTestHarness().endpointCount(elId), count, elId + " has " + count + (count > 1) ? "endpoints" : "endpoint"); +}; + +var assertConnectionCount = function(endpoint, count) { + equals(endpoint.connections.length, count, "endpoint has " + count + " connections"); +}; + +var assertConnectionByScopeCount = function(scope, count) { + equals(jsPlumb.getTestHarness().connectionCount(scope), count, 'Scope ' + scope + " has " + count + (count > 1) ? "connections" : "connection"); + +}; + +var _divs = []; +var _addDiv = function(id) { + var d1 = document.createElement("div"); + _getContextNode().append(d1); + d1 = jsPlumb.CurrentLibrary.getElementObject(d1); + jsPlumb.CurrentLibrary.setAttribute(d1, "id", id); + _divs.push(id); + return d1; +}; + +var _cleanup = function() { + + jsPlumb.reset(); + + for (var i in _divs) { + $("#" + _divs[i]).remove(); + } + _divs.splice(0, _divs.length - 1); +}; + +var testSuite = function(renderMode) { + + module("jsPlumb", {teardown: _cleanup}); + + // setup the container + var container = document.createElement("div"); + container.id = "container"; + document.body.appendChild(container); + + jsPlumb.setRenderMode(renderMode); + + test(renderMode + ': findIndex method', function() { + var array = [ 1,2,3, "test", "a string", { 'foo':'bar', 'baz':1 }, { 'ding':'dong' } ]; + equals(jsPlumb.getTestHarness().findIndex(array, 1), 0, "find works for integers"); + equals(jsPlumb.getTestHarness().findIndex(array, "test"), 3, "find works for strings"); + equals(jsPlumb.getTestHarness().findIndex(array, { 'foo':'bar', 'baz':1 }), 5, "find works for objects"); + equals(jsPlumb.getTestHarness().findIndex(array, { 'ding':'dong', 'baz':1 }), -1, "find works properly for objects (objects have different length but some same properties)"); + }); + + test(renderMode + ': jsPlumb setup', function() { + ok(jsPlumb, "loaded"); + }); + + test(renderMode + ': getId', function() { + var d10 = _addDiv('d10'); + equals(jsPlumb.getTestHarness().getId(d10), "d10"); + }); + + test(renderMode + ': create a simple endpoint', function() { + var d1 = _addDiv("d1"); + var e = jsPlumb.addEndpoint($("#d1"), {}); + ok(e, 'endpoint exists'); + assertEndpointCount("d1", 1); + }); + + test(renderMode + ': create and remove a simple endpoint', function() { + var d1 = _addDiv("d1"); + var ee = jsPlumb.addEndpoint(d1, {uuid:"78978597593"}); + ok(ee != null, "endpoint exists"); + var e = jsPlumb.getEndpoint("78978597593"); + ok(e != null, "the endpoint could be retrieved by UUID"); + assertEndpointCount("d1", 1); + assertContextSize(1); // one Endpoint canvas. + jsPlumb.removeEndpoint(d1, ee); + assertEndpointCount("d1", 0); + e = jsPlumb.getEndpoint("78978597593"); + equals(e, null, "the endpoint has been deleted"); + assertContextSize(0); // no Endpoint canvases. + }); + + test(renderMode + ': create two simple endpoints, registered using a selector', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + d1.addClass("window");d2.addClass("window"); + var endpoints = jsPlumb.addEndpoint($(".window"), {}); + equals(endpoints.length, 2, "endpoint added to both windows"); + assertEndpointCount("d1", 1); + assertEndpointCount("d2", 1); + }); + + test(renderMode + ': create two simple endpoints, registered using an array of element ids', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + d1.addClass("window");d2.addClass("window"); + var endpoints = jsPlumb.addEndpoint(["d1", "d2"], {}); + equals(endpoints.length, 2, "endpoint added to both windows"); + assertEndpointCount("d1", 1); + assertEndpointCount("d2", 1); + }); + + test(renderMode + ': draggable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1"); + var e = jsPlumb.addEndpoint(d1, {isSource:true}); + ok(e, 'endpoint exists'); + }); + + test(renderMode + ': droppable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1") + var e = jsPlumb.addEndpoint(d1, {isTarget:true}); + ok(e, 'endpoint exists'); + }); + + test(renderMode + ': defaultEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = jsPlumb.addEndpoint(d3, {isSource:true}); + ok(e3.anchor, 'endpoint 3 has an anchor'); + var e4 = jsPlumb.addEndpoint(d4, {isSource:true}); + assertEndpointCount("d3", 1); + assertEndpointCount("d4", 1); + assertContextSize(2); // one canvas for each of our two endpoints. + ok(!e3.isFull(), "endpoint 3 is not full."); + jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // we have one connection + jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // should have refused the connection; default max is 1. + assertContextSize(3); // now we have one more canvas, for the Connection that was accepted. + }); + + test(renderMode + ': specifiedEndpointMaxConnections', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = jsPlumb.addEndpoint(d5, {isSource:true, maxConnections:3}); + ok(e5.anchor, 'endpoint 5 has an anchor'); + var e6 = jsPlumb.addEndpoint(d6, {isSource:true, maxConnections:2}); // this one has max TWO + assertEndpointCount("d5", 1); assertEndpointCount("d6", 1); + assertContextSize(2); + ok(!e5.isFull(), "endpoint 5 is not full."); + jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 1); // we have one connection + assertContextSize(3); + jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // two connections + assertContextSize(4); + jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // should have refused; max is 2, for d4. + assertContextSize(4); + }); + + test(renderMode + ': noEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + var e4 = jsPlumb.addEndpoint(d4, {isSource:true, maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertContextSize(3); + assertConnectionCount(e3, 1); // we have one connection + jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 2); // we have two. etc (default was one. this proves max is working). + assertContextSize(4); + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e4}); + assertConnectionCount(e4, 3); + assertContextSize(6); + }); + + test(renderMode + ': anchors equal', function() { + var a1 = jsPlumb.makeAnchor(0, 1, 1, 1); + var a2 = jsPlumb.makeAnchor(0, 1, 1, 1); + ok(a1.equals(a2), "anchors are the same"); + }); + + test(renderMode + ': anchors equal with offsets', function() { + var a1 = jsPlumb.makeAnchor(0, 1, 1, 1, 10, 13); + var a2 = jsPlumb.makeAnchor(0, 1, 1, 1, 10, 13); + ok(a1.equals(a2), "anchors are the same"); + }); + + test(renderMode + ': anchors not equal', function() { + var a1 = jsPlumb.makeAnchor(0, 1, 0, 1); + var a2 = jsPlumb.makeAnchor(0, 1, 1, 1); + ok(!a1.equals(a2), "anchors are different"); + }); + + test(renderMode + ': anchor not equal with offsets', function() { + var a1 = jsPlumb.makeAnchor(0, 1, 1, 1, 10, 13); + var a2 = jsPlumb.makeAnchor(0, 1, 1, 1); + ok(!a1.equals(a2), "anchors are different"); + }); + + test(renderMode + ': detach plays nice when no target given', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + jsPlumb.connect({source:d3, target:d4}); + jsPlumb.detach(); + }); + + // issue 81 + test(renderMode + ': jsPlumb.detach should fire only one detach event (pass Connection as argument)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + jsPlumb.detach(conn); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': jsPlumb.detach should fire only one detach event (pass Connection as param in argument)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + jsPlumb.detach({connection:conn}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': jsPlumb.detach should fire only one detach event (pass source and targets as strings as arguments in params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + jsPlumb.detach({source:"d5", target:"d6"}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': jsPlumb.detach should fire only one detach event (pass source and targets as divs as arguments in params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + jsPlumb.detach({source:d5, target:d6}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': jsPlumb.detach should fire only one detach event (pass source and targets as divs as arguments)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + jsPlumb.detach(d5, d6); + equals(eventCount, 1); + }); + + //TODO make sure you run this test with a single detach call, to ensure that + // single detach calls result in the connection being removed. detachEverything can + // just blow away the connectionsByScope array and recreate it. + test(renderMode + ': jsPlumb.getConnections (simple case, default scope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + jsPlumb.connect({source:d5, target:d6}); + assertContextSize(3); + var c = jsPlumb.getConnections(); // will get all connections in the default scope. + equals(c.length, 1, "there is one connection"); + }); + + test(renderMode + ': jsPlumb.getConnections (simple case, default scope; detach by element id)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + jsPlumb.connect({source:d5, target:d6}); + jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections in the default scope + equals(c.length, 2, "there are two connections initially"); + jsPlumb.detach('d5', 'd6'); + c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': jsPlumb.getConnections (simple case, default scope; detach by element id using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + jsPlumb.connect({source:d5, target:d6}); + jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + jsPlumb.detach({source:'d5', target:'d6'}); + c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': jsPlumb.getConnections (simple case, default scope; detach by element object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + jsPlumb.connect({source:d5, target:d6}); + jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + jsPlumb.detach(d5, d6); + c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': jsPlumb.getConnections (simple case, default scope; detach by element object using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + jsPlumb.connect({source:d5, target:d6}); + jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + jsPlumb.detach({source:d5, target:d6}); + c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': jsPlumb.getConnections (simple case, default scope; detach by Connection)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + var c56 = jsPlumb.connect({source:d5, target:d6}); + var c67 = jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + jsPlumb.detach(c56); + assertContextSize(5); // check that the connection canvas was removed. + c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + + }); + + test(renderMode + ': jsPlumb.getConnections (scope testScope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + jsPlumb.connect({source:d5, target:d6, scope:'testScope'}); + var c = jsPlumb.getConnections("testScope"); // will get all connections in testScope + equals(c.length, 1, "there is one connection"); + equals(c[0].sourceId, 'd5', "the connection's source is d5"); + equals(c[0].targetId, 'd6', "the connection's source is d6"); + c = jsPlumb.getConnections(); // will get all connections in default scope; should be none. + equals(c.length, 0, "there are no connections in the default scope"); + }); + + test(renderMode + ': jsPlumb.getAllConnections (filtered by scope)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + jsPlumb.connect({source:d9, target:d10}); // default scope + var c = jsPlumb.getAllConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 1); + equals(c['testScope'].length, 1); + // now supply a list of scopes + c = jsPlumb.getConnections(); + equals(c.length, 1); + c = jsPlumb.getConnections("testScope"); + equals(c.length, 1, "there is one connection in 'testScope'"); + }); + + test(renderMode + ': jsPlumb.getConnections (filtered by scope and sourceId)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + jsPlumb.connect({source:d9, target:d8, scope:'testScope'}); + jsPlumb.connect({source:d9, target:d10}); // default scope + var c = jsPlumb.getConnections({scope:'testScope', source:'d8'}); // will get all connections with sourceId 'd8' + equals(c.length, 1, "there is one connection in 'testScope' from d8"); + }); + + test(renderMode + ': jsPlumb.getConnections (filtered by scope, source id and target id)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d12, target:d13, scope:'testScope'}); + jsPlumb.connect({source:d11, target:d13, scope:'testScope'}); + var c = jsPlumb.getConnections({scope:'testScope', source:'d11', target:'d13'}); + equals(c.length, 1, "there is one connection from d11 to d13"); + }); + + test(renderMode + ': jsPlumb.getConnections (filtered by a list of scopes)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = jsPlumb.getConnections({scope:['testScope','testScope3']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + }); + + test(renderMode + ': jsPlumb.getConnections (filtered by a list of scopes and source ids)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + }); + + test(renderMode + ': jsPlumb.getConnections (filtered by a list of scopes, source ids and target ids)', function() { + jsPlumb.detachEverything(); + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'), d14=_addDiv("d14"), d15=_addDiv("d15"); + jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d11, target:d14, scope:'testScope'}); + jsPlumb.connect({source:d11, target:d15, scope:'testScope'}); + jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + assertContextSize(18); + var c = jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11'], target:['d14', 'd15']}); + equals(c['testScope'].length, 2, 'there are two connections in testScope'); + equals(c['testScope3'], null, 'there are no connections in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + var anEntry = c['testScope'][0]; + ok(anEntry.endpoints[0] != null, "Source endpoint is set"); + ok(anEntry.endpoints[1] != null, "Target endpoint is set"); + equals(anEntry.source.attr("id"), "d11", "Source is div d11"); + equals(anEntry.target.attr("id"), "d14", "Target is div d14"); + }); + + test(renderMode + ': getEndpoints, one Endpoint added by addEndpoint, get Endpoints by selector', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + jsPlumb.addEndpoint(d1); + var e = jsPlumb.getEndpoints(d1); + equals(e.length, 1, "there is one endpoint for element d1"); + }); + + test(renderMode + ': getEndpoints, one Endpoint added by addEndpoint, get Endpoints by id', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + jsPlumb.addEndpoint(d1); + var e = jsPlumb.getEndpoints("d1"); + equals(e.length, 1, "there is one endpoint for element d1"); + }); + + test(renderMode + ': connection event listeners', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null/*, returnedParams2 = null*/; + jsPlumb.bind("jsPlumbConnection", function(params) { + returnedParams = $.extend({}, params); + }); + var c = jsPlumb.connect({source:d1, target:d2}); + ok(returnedParams != null, "new connection listener event was fired"); + ok(returnedParams.connection != null, 'connection is set'); + equals(returnedParams.sourceId, "d1", 'sourceid is set'); + equals(returnedParams.targetId, "d2", 'targetid is set'); + equals(returnedParams.source.attr("id"), "d1", 'source is set'); + equals(returnedParams.target.attr("id"), "d2" , 'target is set'); + ok(returnedParams.sourceEndpoint != null, "source endpoint is not null"); + ok(returnedParams.targetEndpoint != null, "target endpoint is not null"); + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + jsPlumb.detach(c); + ok(returnedParams.connection != null, 'connection is set'); + //jsPlumb.detachAll("d1"); + //ok(returnedParams2 != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (detach by connection)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + ok(returnedParams.connection != null, "removed connection listener event passed in connection"); + }); + + test(renderMode + ': detach event listeners (detach by element ids)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach("d1","d2"); + ok(returnedParams != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (detach by elements)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach(d1,d2); + ok(returnedParams != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detach method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detachFrom method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachFrom(e2); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detachAll method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachAll(); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via jsPlumb.deleteEndpoint method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + jsPlumb.deleteEndpoint(e1); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(1); + }); + + test(renderMode + ': detach event listeners (ensure cleared by jsPlumb.reset)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach(d1,d2); + ok(returnedParams != null, "removed connection listener event was fired"); + returnedParams = null; + + jsPlumb.reset(); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach(d1,d2); + ok(returnedParams == null, "connection listener was cleared by jsPlumb.reset()"); + }); + + test(renderMode + ': connection events that throw errors', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null, returnedParams2 = null; + jsPlumb.bind("jsPlumbConnection", function(params) { + returnedParams = $.extend({}, params); + throw "oh no!"; + }); + jsPlumb.connect({source:d1, target:d2}); + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + jsPlumb.connect({source:d3, target:d4}); + ok(returnedParams != null, "new connection listener event was fired; we threw an error, jsPlumb survived."); + }); + + test(renderMode + ": Endpoint.detachFrom", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + e16.detachFrom(e17); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + }); + + test(renderMode + ": Endpoint.detachFromConnection", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + e16.detachFromConnection(conn); + assertContextSize(3); // all canvases should remain; the connection was not removed. + // but endpoint e16 should have no connections now. + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 1); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + }); + + test(renderMode + ": Endpoint.detachAll", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + var e16 = jsPlumb.addEndpoint($("#d16"), {isSource:true,maxConnections:-1}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint($("#d17"), {isSource:true}); + var e18 = jsPlumb.addEndpoint($("#d18"), {isSource:true}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e18}); + assertConnectionCount(e16, 2); + assertConnectionCount(e17, 1); + assertConnectionCount(e18, 1); + assertContextSize(5); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 2); + e16.detachAll(); + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + }); + + test(renderMode + ": Endpoint.detach", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + e16.detach(conn); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + }); + + test(renderMode + ": Endpoint.isConnectedTo", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.isConnectedTo(e17), true, "e16 and e17 are connected"); + }); + + test(renderMode + ": setting endpoint uuid", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + equals(e16.getUuid(), uuid, "endpoint's uuid was set correctly"); + }); + + test(renderMode + ": jsPlumb.getEndpoint (by uuid)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + }); + + test(renderMode + ": jsPlumb.deleteEndpoint (by uuid, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + jsPlumb.deleteEndpoint(uuid); + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + var ebe = jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + assertContextSize(0); + }); + + test(renderMode + ": jsPlumb.deleteEndpoint (by uuid, connections too)", function() { + // create two endpoints (one with a uuid), add them to two divs and then connect them. + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + // check that the connection was ok. + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + // delete the endpoint that has a uuid. verify that the endpoint cannot be retrieved and that the connection has been removed, but that + // element d17 still has its Endpoint. + jsPlumb.deleteEndpoint(uuid); + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + var ebe = jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + ebe = jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 1, "element d17 still has its Endpoint"); + assertContextSize(1); + + // now delete d17's endpoint and check that it has gone. + jsPlumb.deleteEndpoint(e17); + f = jsPlumb.getEndpoint(e17); + equals(f, null, "endpoint has been deleted"); + ebe = jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 0, "element d17 no longer has any Endpoints"); + assertContextSize(0); + }); + + test(renderMode + ": jsPlumb.deleteEndpoint (by reference, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + jsPlumb.deleteEndpoint(e16); + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + assertContextSize(0); + }); + + test(renderMode + ": jsPlumb.deleteEndpoint (by reference, connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + jsPlumb.deleteEndpoint(e16); + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + assertContextSize(1); + }); + + test(renderMode + ": jsPlumb.deleteEveryEndpoint", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + assertContextSize(2); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + jsPlumb.deleteEveryEndpoint(); + + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); + }); + + test(renderMode + ": jsPlumb.deleteEveryEndpoint (connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertContextSize(3); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + jsPlumb.deleteEveryEndpoint(); + + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); // no canvases. so all connection canvases have been cleaned up too. + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + }); + + test(renderMode + ": jsPlumb.addEndpoint (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {anchor:[0,0.5,0,-1]}); + var e17 = jsPlumb.addEndpoint(d17, {anchor:"TopCenter"}); + assertContextSize(2); + equals(e16.anchor.x, 0); + equals(e16.anchor.y, 0.5); + equals(e17.anchor.x, 0.5); + equals(e17.anchor.y, 0); + }); + + test(renderMode + ": jsPlumb.addEndpoint (simple case, dynamic anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + assertContextSize(2); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": jsPlumb.addEndpoint (simple case, two arg method)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchor:[0,0.5,0,-1]}); + var e17 = jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchor:"TopCenter"}); + assertContextSize(2); + equals(e16.anchor.x, 0); + equals(e16.anchor.y, 0.5); + equals(false, e16.isTarget); + equals(true, e16.isSource); + equals(e17.anchor.x, 0.5); + equals(e17.anchor.y, 0); + equals(true, e17.isTarget); + equals(false, e17.isSource); + }); + + + test(renderMode + ": jsPlumb.addEndpoints (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false, anchor:[0,0.5,0,-1] }, { isTarget:true, isSource:false, anchor:"TopCenter" }]); + assertContextSize(2); + equals(e16[0].anchor.x, 0); + equals(e16[0].anchor.y, 0.5); + equals(false, e16[0].isTarget); + equals(true, e16[0].isSource); + equals(e16[1].anchor.x, 0.5); + equals(e16[1].anchor.y, 0); + equals(true, e16[1].isTarget); + equals(false, e16[1].isSource); + }); + + test(renderMode + ": jsPlumb.addEndpoints (with reference params)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var refParams = {anchor:"RightMiddle"}; + var e16 = jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false}, { isTarget:true, isSource:false }], refParams); + assertContextSize(2); + equals(e16[0].anchor.x, 1); + equals(e16[0].anchor.y, 0.5); + equals(false, e16[0].isTarget); + equals(true, e16[0].isSource); + equals(e16[1].anchor.x, 1); + equals(e16[1].anchor.y, 0.5); + equals(true, e16[1].isTarget); + equals(false, e16[1].isSource); + }); + + test(renderMode + ": jsPlumb.addEndpoint (simple case, dynamic anchors, two arg method)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchors:["TopCenter", "BottomCenter"]}); + assertContextSize(2); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": jsPlumb.makeTarget (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + jsPlumb.makeTarget(d17, { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": jsPlumb.makeTarget (specify two divs in an array)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + jsPlumb.makeTarget([d16, d17], { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": jsPlumb.makeTarget (specify two divs by id in an array)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + jsPlumb.makeTarget(["d16", "d17"], { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": jsPlumb.makeTarget (specify divs by selector)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + d16.addClass("FOO");d17.addClass("FOO"); + jsPlumb.makeTarget($(".FOO"), { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ': jsPlumb.connect (between two Endpoints)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1); + assertEndpointCount("d2", 1); + jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1); // no new endpoint should have been added + assertEndpointCount("d2", 1); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. + }); + + test(renderMode + ': jsPlumb.connect (between two Endpoints, and dont supply any parameters to the Endpoints.)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1); + assertEndpointCount("d2", 1); + jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1); // no new endpoint should have been added + assertEndpointCount("d2", 1); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. + }); + + test(renderMode + ': jsPlumb.connect (by endpoint)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + }); + + test(renderMode + ": jsPlumb.connect (two Endpoints - that have been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1, uuid:srcEndpointUuid}); + var e2 = jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1, uuid:dstEndpointUuid}); + jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ] }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertContextSize(3); + }); + + test(renderMode + ": jsPlumb.connect (two Endpoints - that have not been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ], source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + var e1 = jsPlumb.getEndpoint(srcEndpointUuid); + ok(e1 != null, "endpoint with src uuid added"); + ok(e1.canvas != null); + var e2 = jsPlumb.getEndpoint(dstEndpointUuid); + ok(e2 != null, "endpoint with target uuid added"); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + }); + + test(renderMode + ": jsPlumb.connect (two Endpoints - that have been already added - by endpoint reference)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1}); + var e2 = jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1}); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + }); + + test(renderMode + ": jsPlumb.connect (two elements)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + jsPlumb.connect({ source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + }); + + test(renderMode + ": jsPlumb.connect (Connector test, straight)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": jsPlumb.connect (Connector test, bezier, no params)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Bezier" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 150, "Bezier connector chose 150 curviness"); + }); + + test(renderMode + ": jsPlumb.connect (Connector test, bezier, curviness as int)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", { curviness:200 }] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Canvas Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 200, "Bezier connector chose 200 curviness"); + }); + + /*jsPlumb.setRenderMode(jsPlumb.SVG); + test(renderMode + ": jsPlumb.connect (Connector test, bezier, curviness as int)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", 200] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors.svg.Bezier, "SVG Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 200, "Bezier connector chose 200 curviness"); + });*/ + + test(renderMode + ": jsPlumb.connect (Connector test, bezier, curviness as named option)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", {curviness:300}] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 300, "Bezier connector chose 300 curviness"); + }); + + test(renderMode + ": jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Canvas Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": jsPlumb.connect (anchors registered correctly; source and target anchors given, as strings)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:["LeftMiddle", "RightMiddle"] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.5, conn.endpoints[0].anchor.y, "source anchor y"); + equals(1, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.5, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + + test(renderMode + ": jsPlumb.connect (two argument method in which some data is reused across connections)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"), d19 = _addDiv("d19"); + var sharedData = { connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }; + var conn = jsPlumb.connect({ source:d16, target:d17}, sharedData); + var conn2 = jsPlumb.connect({ source:d18, target:d19}, sharedData); + assertContextSize(6); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 2); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(conn2.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + equals(0.3, conn2.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn2.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn2.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn2.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": jsPlumb.connect (Connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": jsPlumb.connect (Endpoint test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection target"); + }); + + test(renderMode + ": jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection target"); + }); + + test(renderMode + ": jsPlumb.connect (Endpoints test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Dot, "Dot endpoint chosen for connection target"); + }); + + test(renderMode + ": jsPlumb.connect (Blank Endpoint specified via 'endpoint' param)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoint:"Blank" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection target"); + }); + + test(renderMode + ": jsPlumb.connect (Blank Endpoint specified via 'endpoints' param)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoints:["Blank", "Blank" ] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection target"); + }); + + test(renderMode + ": jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Dot, "Dot endpoint chosen for connection target"); + }); + + test(renderMode + ": jsPlumb.connect (by Endpoints, connector test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {}); + var e17 = jsPlumb.addEndpoint(d17, {}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": jsPlumb.connect (by Endpoints, connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {}); + var e17 = jsPlumb.addEndpoint(d17, {}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": jsPlumb.connect (by Endpoints, anchors as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = "TopCenter", a17 = "BottomCenter"; + var e16 = jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, 0.5, "endpoint 16 is at top center");equals(e16.anchor.y, 0, "endpoint 16 is at top center"); + equals(e17.anchor.x, 0.5, "endpoint 17 is at bottom center");equals(e17.anchor.y, 1, "endpoint 17 is at bottom center"); + }); + + test(renderMode + ": jsPlumb.connect (by Endpoints, endpoints create anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = [0,0.5,0,-1], a17 = [1,0.0,-1,-1]; + var e16 = jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, a16[0]);equals(e16.anchor.y, a16[1]); + equals(e17.anchor.x, a17[0]);equals(e17.anchor.y, a17[1]); + equals(e16.anchor.getOrientation()[0], a16[2]); equals(e16.anchor.getOrientation()[1], a16[3]); + equals(e17.anchor.getOrientation()[0], a17[2]); equals(e17.anchor.getOrientation()[1], a17[3]); + }); + + test(renderMode + ": jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchor')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {anchor:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = jsPlumb.addEndpoint(d17, {anchor:["TopCenter", "BottomCenter"]}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchors')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": jsPlumb.connect (connect by element, default endpoint, supplied dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var anchors = [ [0.25, 0, 0, -1], [1, 0.25, 1, 0], [0.75, 1, 0, 1], [0, 0.75, -1, 0] ]; + jsPlumb.connect({source:d1, target:d2, dynamicAnchors:anchors}); // auto connect with default endpoint and provided anchors + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + assertContextSize(2); + }); + + test(renderMode + ": jsPlumb.connect (connect by element, supplied endpoint and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = jsPlumb.addEndpoint(d1, endpoint); + var e2 = jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + assertContextSize(3); + jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + assertContextSize(2); + }); + + test(renderMode + ": jsPlumb.connect (connect by element, supplied endpoints using 'source' and 'target' (this test is identical to the one above apart from the param names), and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = jsPlumb.addEndpoint(d1, endpoint); + var e2 = jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + jsPlumb.connect({source:e1, target:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + assertContextSize(3); + jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + assertContextSize(2); + }); + + + test(renderMode + ": jsPlumb.connect (testing for connection event callback)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var connectCallback = null, detachCallback = null; + jsPlumb.bind("jsPlumbConnection", function(params) { + connectCallback = $.extend({}, params); + }); + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + detachCallback = $.extend({}, params); + }); + jsPlumb.connect({source:d1, target:d2}); // auto connect with default endpoint and anchor set + ok(connectCallback != null, "connect callback was made"); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + jsPlumb.detach({source:d1, target:d2}); + assertContextSize(2); + ok(detachCallback != null, "detach callback was made"); + }); + + test(renderMode + ": jsPlumb.connect (setting cssClass on Connector)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = jsPlumb.connect({source:d1,target:d2,cssClass:"CSS"}); + var has = renderMode == jsPlumb.SVG ? function(clazz) { + return c.canvas.childNodes[0].className.baseVal.indexOf(clazz) != -1; + } : renderMode == jsPlumb.CANVAS ? function(clazz) { + return $(c.canvas).hasClass(clazz); + } : function(clazz) { + return $(c.connector.canvas).hasClass(clazz); + }; + + ok(has("CSS"), "custom cssClass set correctly"); + ok(has(jsPlumb.connectorClass), "basic connector class set correctly"); + }); + + test(renderMode + ": jsPlumb.connect (overlays, long-hand version)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = {width:40,length:40,location:0.7, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"}}; + var connection1 = jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + }); + + test(renderMode + ": jsPlumb.connect (overlays, long-hand version, IDs specified)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + equals("aLabel", connection1.overlays[0].id); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + equals("anArrow", connection1.overlays[1].id); + }); + + test(renderMode + ": jsPlumb.connect (remover single overlay by id)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + connection1.removeOverlay("aLabel"); + equals(1, connection1.overlays.length); + equals("anArrow", connection1.overlays[0].id); + }); + + test(renderMode + ": jsPlumb.connect (remove multiple overlays by id)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + connection1.removeOverlays("aLabel", "anArrow"); + equals(0, connection1.overlays.length); + }); + + test(renderMode + ": jsPlumb.connect (overlays, short-hand version)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var loc = { location:0.7 }; + var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} }; + var connection1 = jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label", {label:"CONNECTION 1", location:0.3}], + ["Arrow", arrowSpec, loc] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + }); + + test(renderMode + ": jsPlumb.connect (removeAllOverlays)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var loc = { location:0.7 }; + var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} }; + var connection1 = jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label", {label:"CONNECTION 1", location:0.3}], + ["Arrow", arrowSpec, loc] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + + connection1.removeAllOverlays(); + equals(0, connection1.overlays.length); + }); + + test(renderMode + ": jsPlumb.connect, specify arrow overlay using string identifier only", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var conn = jsPlumb.connect({source:d1,target:d2,overlays:["Arrow"]}); + equals(jsPlumb.Overlays[renderMode].Arrow, conn.overlays[0].constructor); + }); + + // this test is for the original detach function; it should stay working after i mess with it + // a little. + test(renderMode + ": jsPlumb.detach (by element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + var e3 = jsPlumb.addEndpoint(d1); + var e4 = jsPlumb.addEndpoint(d2); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + jsPlumb.connect({ sourceEndpoint:e3, targetEndpoint:e4 }); // make two connections to be sure this works ;) + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionCount(e3, 1); + assertConnectionCount(e4, 1); + + jsPlumb.detach("d1", "d2"); + + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionCount(e3, 0); + assertConnectionCount(e4, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + }); + + // detach is being made to operate more like connect - by taking one argument with a whole + // bunch of possible params in it. if two args are passed in it will continue working + // in the old way. + test(renderMode + ": jsPlumb.detach (params object, using element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + jsPlumb.detach({source:"d1", target:"d2"}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + }); + + test(renderMode + ": jsPlumb.detach (params object, using element objects)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + jsPlumb.detach({source:d1, target:d2}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + assertContextSize(2); + }); + + test(renderMode + ": jsPlumb.detach (source and target as endpoint UUIDs)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {uuid:"abcdefg"}); + ok(jsPlumb.getEndpoint("abcdefg") != null, "e1 exists"); + var e2 = jsPlumb.addEndpoint(d2, {uuid:"hijklmn"}); + ok(jsPlumb.getEndpoint("hijklmn") != null, "e2 exists"); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + jsPlumb.detach({uuids:["abcdefg", "hijklmn"]}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + assertContextSize(2); + }); + + test(renderMode + ": jsPlumb.detach (sourceEndpoint and targetEndpoint supplied)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + jsPlumb.detach({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + assertContextSize(2); + }); + + test(renderMode + ": jsPlumb.makeDynamicAnchors (longhand)", function() { + var anchors = [jsPlumb.makeAnchor(0.2, 0, 0, -1), jsPlumb.makeAnchor(1, 0.2, 1, 0), + jsPlumb.makeAnchor(0.8, 1, 0, 1), jsPlumb.makeAnchor(0, 0.8, -1, 0) ]; + var dynamicAnchor = jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); + }); + + test(renderMode + ": jsPlumb.makeDynamicAnchors (shorthand)", function() { + var anchors = [[0.2, 0, 0, -1], [1, 0.2, 1, 0], + [0.8, 1, 0, 1], [0, 0.8, -1, 0] ]; + var dynamicAnchor = jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); + }); + + test(renderMode + ": Connection.isVisible/setVisible", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c1 = jsPlumb.connect({source:d1,target:d2}); + equals(true, c1.isVisible(), "Connection is visible after creation."); + c1.setVisible(false); + equals(false, c1.isVisible(), "Connection is not visible after calling setVisible(false)."); + equals($(c1.connector.canvas).css("display"), "none"); + c1.setVisible(true); + equals(true, c1.isVisible(), "Connection is visible after calling setVisible(true)."); + equals($(c1.connector.canvas).css("display"), "block"); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible basic test (no connections)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals($(e1.canvas).css("display"), "none"); + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals($(e1.canvas).css("display"), "block"); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should track changes in the source, because it has only this connection.)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1), e2 = jsPlumb.addEndpoint(d2); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + var c1 = jsPlumb.connect({source:e1, target:e2}); + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals(false, e2.isVisible(), "other Endpoint is not visible either."); + equals(false, c1.isVisible(), "connection between the two is not visible either."); + + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals(true, e2.isVisible(), "other Endpoint is visible too"); + equals(true, c1.isVisible(), "connection between the two is visible too."); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should not track changes in the source, because it has another connection.)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var e1 = jsPlumb.addEndpoint(d1), e2 = jsPlumb.addEndpoint(d2, { maxConnections:2 }), e3 = jsPlumb.addEndpoint(d3); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + var c1 = jsPlumb.connect({source:e1, target:e2}); + var c2 = jsPlumb.connect({source:e2, target:e3}); + + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals(true, e2.isVisible(), "other Endpoint should still be visible."); + equals(true, e3.isVisible(), "third Endpoint should still be visible."); + equals(false, c1.isVisible(), "connection between the two is not visible either."); + equals(true, c2.isVisible(), "other connection is visible."); + + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals(true, e2.isVisible(), "other Endpoint is visible too"); + equals(true, c1.isVisible(), "connection between the two is visible too."); + equals(true, c2.isVisible(), "other connection is visible."); + }); + + + /** + * leave this test at the bottom! + */ + test(renderMode + ': unload test', function() { + jsPlumb.unload(); + var contextNode = $("._jsPlumb_context"); + ok(contextNode.length == 0, 'context node unloaded'); + }); +}; + diff --git a/archive/1.3.0/jsPlumb-defaults-1.3.0-RC1.js b/archive/1.3.0/jsPlumb-defaults-1.3.0-RC1.js new file mode 100644 index 000000000..b1352d9a5 --- /dev/null +++ b/archive/1.3.0/jsPlumb-defaults-1.3.0-RC1.js @@ -0,0 +1,907 @@ +/* +* jsPlumb-defaults-1.3.0-RC1 +* +* Copyright 2010 - 2011 Simon Porritt http://jsplumb.org +* +* Triple licensed under the MIT, GPL2 and Beer licenses. +*/ + +(function() { + + + + /** + * + * Helper class to consume unused mouse events by components that are DOM elements and + * are used by all of the different rendering modes. + * + */ + jsPlumb.DOMElementComponent = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + // when render mode is canvas, these functions may be called by the canvas mouse handler. + // this component is safe to pipe this stuff to /dev/null. + this.mousemove = + this.dblclick = + this.click = + this.mousedown = + this.mouseup = function(e) { }; + }; + + /** + * Class: Connectors.Straight + * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + var currentPoints = null; + var _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + _dx = _tx - _sx, _dy = (_ty - _sy); + _m = _dy / _dx, _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + + return currentPoints; + }; + + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + var xp = _sx + (location * _dx); + var yp = (_m == Infinity || _m == -Infinity) ? _sy + (location * (_ty - _sy)) : (_m * xp) + _b; + return {x:xp, y:yp}; + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { return _m; }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + var orientation = distance > 0 ? 1 : -1; + var y = Math.abs(distance * Math.sin(_theta)); + if (_sy > _ty) y = y * -1; + var x = Math.abs(distance * Math.cos(_theta)); + if (_sx > _tx) x = x * -1; + return {x:p.x + (orientation * x), y:p.y + (orientation * y)}; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = self.gradientAtPoint(p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + }; + }; + + + /** + * Class:Connectors.Bezier + * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's + * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below. + */ + /** + * Function:Constructor + * + * Parameters: + * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line. + * Optional; defaults to 150. + * + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + params = params || {}; + this.majorAnchor = params.curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h; + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) + { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx); var minx2 = Math.min(_CP[0], _CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(_sx,_tx); var maxx2 = Math.max(_CP[0], _CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty); var miny2 = Math.min(_CP[1], _CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(_sy,_ty); var maxy2 = Math.max(_CP[1], _CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + return jsBezier.perpendicularToCurveAt(_makeCurve(), location, length, distance); + }; + + }; + + + /** + * Class: Connectors.Flowchart + * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments. + */ + /** + * Function: Constructor + * + * Parameters: + * stub - minimum length for the stub at each end of the connector. defaults to 30 pixels. + */ + jsPlumb.Connectors.Flowchart = function(params) { + params = params || {}; + var self = this, + minStubLength = params.stub || params.minStubLength /* bwds compat. */ || 30, + segments = [], + segmentGradients = [], + segmentProportions = [], + segmentLengths = [], + segmentProportionalLengths = [], + points = [], + swapX, + swapY, + /** + * recalculates the gradients of each segment, and the points at which the segments begin, proportional to the total length travelled + * by all the segments that constitute the connector. + */ + updateSegmentGradientsAndProportions = function(startX, startY, endX, endY) { + var total = 0; + for (var i = 0; i < segments.length; i++) { + var sx = i == 0 ? startX : segments[i][2], + sy = i == 0 ? startY : segments[i][3], + ex = segments[i][0], + ey = segments[i][1]; + + segmentGradients[i] = sx == ex ? Infinity : 0; + segmentLengths[i] = Math.abs(sx == ex ? ey - sy : ex - sx); + total += segmentLengths[i]; + } + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segmentLengths[i] / total; + segmentProportions[i] = [curLoc, (curLoc += (segmentLengths[i] / total)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0]; + var ly = segments.length == 0 ? sy : segments[segments.length - 1][1]; + segments.push([x, y, lx, ly]); + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment + * that contains the point which is 'location' distance along the entire path, where + * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths + * are made up of a list of segments, each of which contributes some fraction to + * the total length. + */ + findSegmentForLocation = function(location) { + var idx = segmentProportions.length - 1, inSegmentProportion = 0; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + segments = []; + segmentGradients = []; + segmentProportionalLengths = []; + segmentLengths = []; + segmentProportionals = []; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + offx = (lw / 2) + (minStubLength * 2), + offy = (lw / 2) + (minStubLength * 2), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(), + to = targetAnchor.orientation || targetAnchor.getOrientation(), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx, + h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy; + if (w < minWidth) { + offx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + offy += (minWidth - h) / 2; + h = minWidth; + } + var sx = swapX ? w-offx : offx, + sy = swapY ? h-offy : offy, + tx = swapX ? offx : w-offx , + ty = swapY ? offy : h-offy, + startStubX = sx + (so[0] * minStubLength), + startStubY = sy + (so[1] * minStubLength), + endStubX = tx + (to[0] * minStubLength), + endStubY = ty + (to[1] * minStubLength), + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2); + + x -= offx; y -= offy; + points = [x, y, w, h, sx, sy, tx, ty], extraPoints = []; + + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + if (so[0] == 0) { + var startStubIsBeforeEndStub = startStubY < endStubY; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } + } + else { + var startStubIsBeforeEndStub = startStubX < endStubX; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } + } + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentGradientsAndProportions(sx, sy, tx, ty); + + return points; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location) { + return self.pointAlongPathFrom(location, 0); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location) { + return segmentGradients[findSegmentForLocation(location)["index"]]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance) { + var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segmentLengths[s.index], m = segmentGradients[s.index]; + var e = { + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + return e; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = segmentGradients[p.segmentInfo.index]; + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + + }; + + }; + + // ********************************* END OF CONNECTOR TYPES ******************************************************************* + + // ********************************* ENDPOINT TYPES ******************************************************************* + + /** + * Class: Endpoints.Dot + * A round endpoint, with default radius 10 pixels. + */ + + /** + * Function: Constructor + * + * Parameters: + * + * radius - radius of the endpoint. defaults to 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + var self = this; + params = params || {}; + this.radius = params.radius || 10; + this.defaultOffset = 0.5 * this.radius; + this.defaultInnerRadius = this.radius / 3; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var r = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - r; + var y = anchorPoint[1] - r; + return [ x, y, r * 2, r * 2, r ]; + }; + }; + + /** + * Class: Endpoints.Rectangle + * A Rectangular Endpoint, with default size 20x20. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the endpoint. defaults to 20 pixels. + * height - height of the endpoint. defaults to 20 pixels. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + var self = this; + params = params || {}; + this.width = params.width || 20; + this.height = params.height || 20; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + return [ x, y, width, height]; + }; + }; + + /** + * Class: Endpoints.Image + * Draws an image as the Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * src - location of the image to use. + */ + jsPlumb.Endpoints.Image = function(params) { + + jsPlumb.DOMElementComponent.apply(this, arguments); + + var self = this, initialized = false; + this.img = new Image(); + self.ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.src || params.url; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + self.anchorPoint = anchorPoint; + if (self.ready) return [anchorPoint[0] - self.img.width / 2, anchorPoint[1] - self.img.height/ 2, self.img.width, self.img.height]; + else return [0,0,0,0]; + }; + + self.canvas = document.createElement("img"), initialized = false; + self.canvas.style["margin"] = 0; + self.canvas.style["padding"] = 0; + self.canvas.style["outline"] = 0; + self.canvas.style["position"] = "absolute"; + self.canvas.className = jsPlumb.endpointClass; + jsPlumb.appendElement(self.canvas, params.parent); + self.attachListeners(self.canvas, self); + + var actuallyPaint = function(d, style, anchor) { + if (!initialized) { + self.canvas.setAttribute("src", self.img.src); + initialized = true; + } + var width = self.img.width, + height = self.img.height, + x = self.anchorPoint[0] - (width/2), + y = self.anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(self.canvas, x, y, width, height); + }; + + this.paint = function(d, style, anchor) { + if (self.ready) { + actuallyPaint(d, style, anchor); + } + else { + window.setTimeout(function() { + self.paint(d, style, anchor); + }, 200); + } + }; + }; + + /** + * Class: Endpoints.Blank + * An Endpoint that paints nothing on the screen, and cannot be interacted with using the mouse. There are no constructor parameters for this Endpoint. + */ + jsPlumb.Endpoints.Blank = function() { + jsPlumb.DOMElementComponent.apply(this, arguments); + this.compute = function() { + return [0,0,0,0]; + }; + + self.canvas = document.createElement("div"); + this.paint = function() { }; + }; + + /** + * Class: Endpoints.Triangle + * A triangular Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the triangle's base. defaults to 55 pixels. + * height - height of the triangle from base to apex. defaults to 55 pixels. + */ + jsPlumb.Endpoints.Triangle = function(params) { + params = params || { }; + params.width = params.width || 55; + param.height = params.height || 55; + this.width = params.width; + this.height = params.height; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + return [ x, y, width, height ]; + }; + }; +// ********************************* END OF ENDPOINT TYPES ******************************************************************* + + +// ********************************* OVERLAY DEFINITIONS *********************************************************************** + + /** + * Class: Overlays.Arrow + * + * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length + * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction + * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line + * across the tail. + */ + /** + * Function: Constructor + * + * Parameters: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + params = params || {}; + var self = this; + this.length = params.length || 20; + this.width = params.width || 20; + this.id = params.id; + this.connection = params.connection; + var direction = (params.direction || 1) < 0 ? -1 : 1; + var paintStyle = params.paintStyle || { lineWidth:1 }; + this.loc = params.location == null ? 0.5 : params.location; + // how far along the arrow the lines folding back in come to. default is 62.3%. + var foldback = params.foldback || 0.623; + var _getFoldBackPoint = function(connector, loc) { + if (foldback == 0.5) return connector.pointOnPath(loc); + else { + var adj = 0.5 - foldback; // we calculate relative to the center + return connector.pointAlongPathFrom(loc, direction * self.length * adj); + } + }; + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + // this is the arrow head position + var hxy = connector.pointAlongPathFrom(self.loc, direction * (self.length / 2)); + // this is the center of the tail + var txy = connector.pointAlongPathFrom(self.loc, -1 * direction * (self.length / 2)), tx = txy.x, ty = txy.y; + // this is the tail vector + var tail = connector.perpendicularToPathAt(self.loc, self.width, -1 * direction * (self.length / 2)); + // this is the point the tail goes in to + var cxy = _getFoldBackPoint(connector, self.loc); + + // if loc = 1, then hxy should be flush with the element, or if direction == -1, the tail midpoint. + if (self.loc == 1) { + var lxy = connector.pointOnPath(self.loc); + // TODO determine why the 1.2.6 released version does not + // use 'direction' in the two equations below, yet both + // that and 1.3.0 still paint the arrows correctly. + var dx = (lxy.x - hxy.x) * direction, dy = (lxy.y - hxy.y) * direction; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + // if loc = 0, then tail midpoint should be flush with the element, or, if direction == -1, hxy should be. + if (self.loc == 0) { + var lxy = connector.pointOnPath(self.loc); + var tailMid = foldback > 1 ? cxy : { + x:tail[0].x + ((tail[1].x - tail[0].x) / 2), + y:tail[0].y + ((tail[1].y - tail[0].y) / 2) + }; + var dx = (lxy.x - tailMid.x) * direction, dy = (lxy.y - tailMid.y) * direction; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x); + var maxx = Math.max(hxy.x, tail[0].x, tail[1].x); + var miny = Math.min(hxy.y, tail[0].y, tail[1].y); + var maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + var d = { hxy:hxy, tail:tail, cxy:cxy }, + strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle, + fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle, + lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth; + + self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions); + + return [ minx, maxx, miny, maxy]; + }; + }; + + /** + * Class: Overlays.PlainArrow + * + * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + /** + * Class: Overlays.Diamond + * + * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40; + var p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + + + /** + * Class: Overlays.Label + * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb + * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter + * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it + * puts on the Label's 'style' attribute, so the end result is the same. + */ + /** + * Function: Constructor + * + * Parameters: + * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes + * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle. + * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * labelStyle - (deprecated) js object containing style instructions for the label. defaults to jsPlumb.Defaults.LabelStyle. + * borderWidth - (deprecated) width of a border to paint. defaults to zero. + * borderStyle - (deprecated) strokeStyle to use when painting the border, if necessary. + * + */ + jsPlumb.Overlays.Label = function(params) { + jsPlumb.DOMElementComponent.apply(this, arguments); + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.labelStyle.font = this.labelStyle.font || "12px sans-serif"; + this.label = params.label; + this.connection = params.connection; + this.id = params.id; + var self = this; + var labelWidth = null, labelHeight = null, labelText = null, labelPadding = null; + this.location = params.location || 0.5; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var initialised = false, + labelText = null, + div = document.createElement("div"); + div.style["position"] = "absolute"; + div.style["textAlign"] = "center"; + div.style["cursor"] = "pointer"; + div.style["font"] = self.labelStyle.font; + div.style["color"] = self.labelStyle.color || "black"; + if (self.labelStyle.fillStyle) div.style["background"] = self.labelStyle.fillStyle;//_convertStyle(self.labelStyle.fillStyle, true); + if (self.labelStyle.borderWidth > 0) { + var dStyle = self.labelStyle.borderStyle ? self.labelStyle.borderStyle/*_convertStyle(self.labelStyle.borderStyle, true)*/ : "black"; + div.style["border"] = self.labelStyle.borderWidth + "px solid " + dStyle; + } + if (self.labelStyle.padding) div.style["padding"] = self.labelStyle.padding; + + var clazz = params["_jsPlumb"].overlayClass + " " + + (self.labelStyle.cssClass ? self.labelStyle.cssClass : + params.cssClass ? params.cssClass : ""); + + div.className = clazz; + + jsPlumb.appendElement(div, params.connection.parent); + jsPlumb.getId(div); + self.attachListeners(div, self); + this.paint = function(connector, d, connectorDimensions) { + if (!initialised) { + connector.appendDisplayElement(div); + self.attachListeners(div, connector); + initialised = true; + } + div.style.left = (connectorDimensions[0] + d.minx) + "px"; + div.style.top = (connectorDimensions[1] + d.miny) + "px"; + }; + + this.getTextDimensions = function(connector) { + labelText = typeof self.label == 'function' ? self.label(self) : self.label; + div.innerHTML = labelText.replace(/\r\n/g, "
"); + var de = jsPlumb.CurrentLibrary.getElementObject(div), + s = jsPlumb.CurrentLibrary.getSize(de); + return {width:s[0], height:s[1]}; + }; + + this.computeMaxSize = function(connector) { + var td = self.getTextDimensions(connector); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + var td = self.getTextDimensions(connector); + if (td.width != null) { + var cxy = connector.pointOnPath(self.location); + + var minx = cxy.x - (td.width / 2); + var miny = cxy.y - (td.height / 2); + + self.paint(connector, { + minx:minx, + miny:miny, + td:td, + cxy:cxy + }, connectorDimensions); + + return [minx, minx+td.width, miny, miny+td.height]; + } + else return [0,0,0,0]; + }; + }; + + // ********************************* END OF OVERLAY DEFINITIONS *********************************************************************** + + // ********************************* OVERLAY CANVAS RENDERERS*********************************************************************** + + // ********************************* END OF OVERLAY CANVAS RENDERERS *********************************************************************** +})(); \ No newline at end of file diff --git a/archive/1.3.0/jsPlumb-flowchart-0.1-RC1.js b/archive/1.3.0/jsPlumb-flowchart-0.1-RC1.js new file mode 100644 index 000000000..d1c20f86c --- /dev/null +++ b/archive/1.3.0/jsPlumb-flowchart-0.1-RC1.js @@ -0,0 +1,221 @@ +;(function() { + + jsPlumb.Connectors.Flowchart = function(params) { + params = params || {}; + var self = this, + minStubLength = params.minStubLength || 30, + segments = [], + segmentGradients = [], + segmentProportions = [], + segmentLengths = [], + segmentProportionalLengths = [], + points = [], + swapX, + swapY, + /** + * recalculates the gradients of each segment, and the points at which the segments begin, proportional to the total length travelled + * by all the segments that constitute the connector. + */ + updateSegmentGradientsAndProportions = function(startX, startY, endX, endY) { + var total = 0; + for (var i = 0; i < segments.length; i++) { + var sx = i == 0 ? startX : segments[i][2], + sy = i == 0 ? startY : segments[i][3], + ex = segments[i][0], + ey = segments[i][1]; + + segmentGradients[i] = sx == ex ? Infinity : 0; + segmentLengths[i] = Math.abs(sx == ex ? ey - sy : ex - sx); + total += segmentLengths[i]; + } + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segmentLengths[i] / total; + segmentProportions[i] = [curLoc, (curLoc += (segmentLengths[i] / total)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0]; + var ly = segments.length == 0 ? sy : segments[segments.length - 1][1]; + segments.push([x, y, lx, ly]); + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment that contains the point which is 'location' distance along the entire path, where 'location' is + * a decimal between 0 and 1 inclusive. in this connector type paths are made up of a list of segments, each of which contributes some fraction to + * the total length. + */ + findSegmentForLocation = function(location) { + var idx = segmentProportions.length - 1, inSegmentProportion = 0; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + segments = []; + segmentGradients = []; + segmentProportionalLengths = []; + segmentLengths = []; + segmentProportionals = []; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + offx = (lw / 2) + (minStubLength * 2), + offy = (lw / 2) + (minStubLength * 2), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(), + to = targetAnchor.orientation || targetAnchor.getOrientation(), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx, + h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy; + if (w < minWidth) { + offx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + offy += (minWidth - h) / 2; + h = minWidth; + } + sx = swapX ? w-offx : offx, + sy = swapY ? h-offy : offy, + tx = swapX ? offx : w-offx , + ty = swapY ? offy : h-offy, + startStubX = sx + (so[0] * minStubLength), + startStubY = sy + (so[1] * minStubLength), + endStubX = tx + (to[0] * minStubLength), + endStubY = ty + (to[1] * minStubLength), + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2); + + x -= offx; y -= offy; + points = [x, y, w, h, sx, sy, tx, ty], extraPoints = []; + + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + if (so[0] == 0) { + var startStubIsBeforeEndStub = startStubY < endStubY; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } + } + else { + var startStubIsBeforeEndStub = startStubX < endStubX; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } + } + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentGradientsAndProportions(sx, sy, tx, ty); + + return points; + }; + + this.paint = function(dimensions, ctx) { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location) { + return self.pointAlongPathFrom(location, 0); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location) { + return segmentGradients[findSegmentForLocation(location)["index"]]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance) { + var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segmentLengths[s.index], m = segmentGradients[s.index]; + var e = { + //x : m == Infinity ? seg[2] : /*swapX ? seg[2] - (p * sl) - distance : */seg[2] + (p * sl) + distance, + + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + + + //y : m == 0 ? seg[3] : /*swapY ? seg[3] - (p * sl) - distance : */seg[3] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + //console.log("pointalongpath, swapX =" + swapX + ",swapY=" + swapY, "loc", location, "travel", (p * sl), "dist", distance, e.x, e.y, "seg", seg, "len", sl, "prop.", p); + + return e; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = segmentGradients[p.segmentInfo.index]; + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + + }; + }; +})(); \ No newline at end of file diff --git a/archive/1.3.0/jsPlumb-renderers-canvas-1.3.0-RC1.js b/archive/1.3.0/jsPlumb-renderers-canvas-1.3.0-RC1.js new file mode 100644 index 000000000..b1b16d43e --- /dev/null +++ b/archive/1.3.0/jsPlumb-renderers-canvas-1.3.0-RC1.js @@ -0,0 +1,441 @@ +;(function() { + +// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS ******************************************************************* + + // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. + var _connectionBeingDragged = null, + _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }, + _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }, + _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }, + _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, + _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }, + _setOffset = function(el, o) { jsPlumb.CurrentLibrary.setOffset(el, o); }; + + /* + * Class:CanvasMouseAdapter + * Provides support for mouse events on canvases. + */ + var CanvasMouseAdapter = function() { + var self = this; + self.overlayPlacements = []; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + jsPlumb.EventGenerator.apply(this, arguments); + /** + * returns whether or not the given event is ojver a painted area of the canvas. + */ + this._over = function(e) { + var o = _getOffset(_getElementObject(self.canvas)), + pageXY = _pageXY(e), + x = pageXY[0] - o.left, y = pageXY[1] - o.top; + if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + return false; + }; + + var _mouseover = false; + var _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false; + var _nullSafeHasClass = function(el, clazz) { + return el != null && _hasClass(el, clazz); + }; + this.mousemove = function(e) { + var pageXY = _pageXY(e), clientXY = _clientXY(e), + ee = document.elementFromPoint(clientXY[0], clientXY[1]), + eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); + var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); + if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.fire("mouseenter", self, e); + return true; + } + // TODO here there is a remote chance that the overlay the mouse moved onto + // is actually not an overlay for the current component. a more thorough check would + // be to ensure the overlay belonged to the current component. + else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { + _mouseover = false; + self.fire("mouseexit", self, e); + } + self.fire("mousemove", self, e); + }; + + this.click = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); + self.fire("mousedown", self, e); + } + }; + + this.mouseup = function(e) { + //if (self == _connectionBeingDragged) _connectionBeingDragged = null; + _mouseDown = false; + self.fire("mouseup", self, e); + }; + }; + + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + jsPlumb.appendElement(canvas, params.parent); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + params["_jsPlumb"].getId(canvas, params.uuid); + + return canvas; + }; + + /** + * Class:CanvasConnector + * Superclass for Canvas Connector renderers. + */ + var CanvasConnector = jsPlumb.CanvasConnector = function(params) { + + CanvasMouseAdapter.apply(this, arguments); + + var _paintOneStyle = function(dim, aStyle) { + self.ctx.save(); + jsPlumb.extend(self.ctx, aStyle); + if (aStyle.gradient) { + var g = self.createGradient(dim, self.ctx); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + self.ctx.strokeStyle = g; + } + self._paint(dim); + self.ctx.restore(); + }; + + var self = this, + clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent + }); + self.ctx = self.canvas.getContext("2d"); + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + self.paint = function(dim, style) { + if (style != null) { + + jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); + + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + _paintOneStyle(dim, outlineStyle); + } + _paintOneStyle(dim, style); + } + }; + }; + + /** + * Class:CanvasEndpoint + * Superclass for Canvas Endpoint renderers. + */ + var CanvasEndpoint = function(params) { + var self = this; + CanvasMouseAdapter.apply(this, arguments); + var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent + }); + self.ctx = self.canvas.getContext("2d"); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + // _paintOneStyle(d, outlineStyle); + } + + self._paint.apply(this, arguments); + }; + }; + + jsPlumb.Endpoints.canvas.Dot = function(params) { + var self = this; + jsPlumb.Endpoints.Dot.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }; + var calculateAdjustments = function(gradient) { + var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius; + gradient.offset && (offsetAdjustment = parseValue(gradient.offset)); + gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius)); + return [offsetAdjustment, innerRadius]; + }; + this._paint = function(d, style, anchor) { + if (style != null) { + var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(); + jsPlumb.extend(ctx, style); + if (style.gradient) { + var adjustments = calculateAdjustments(style.gradient), + yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0], + xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0], + g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + ctx.beginPath(); + ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + } + }; + }; + + jsPlumb.Endpoints.canvas.Rectangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) { + + var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(); + jsPlumb.extend(ctx, style); + + /* canvas gradient */ + if (style.gradient) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, d[2], d[3]); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + jsPlumb.Endpoints.canvas.Triangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Triangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) + { + var width = d[2], height = d[3], x = d[0], y = d[1]; + + var ctx = self.canvas.getContext('2d'); + var offsetX = 0, offsetY = 0, angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = style.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + /* + * Canvas Image Endpoint: uses the default version, which creates an tag. + */ + jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image; + + /* + * Blank endpoint in all renderers is just the default Blank endpoint. + */ + jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank; + + /* + * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Bezier = function() { + var self = this; + jsPlumb.Connectors.Bezier.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO i doubt this handles the case that source and target are swapped. + this.createGradient = function(dim, ctx, swap) { + return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + /* + * Canvas straight line Connector. Draws a straight line onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Straight = function() { + var self = this; + jsPlumb.Connectors.Straight.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO this does not handle the case that src and target are swapped. + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + jsPlumb.Connectors.canvas.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + +// ********************************* END OF CANVAS RENDERERS ******************************************************************* + + jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label; + + /** + * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. + */ + var CanvasOverlay = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }; + + var AbstractCanvasArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + CanvasOverlay.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + var ctx = connector.ctx; + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(d.hxy.x, d.hxy.y); + ctx.lineTo(d.tail[0].x, d.tail[0].y); + ctx.lineTo(d.cxy.x, d.cxy.y); + ctx.lineTo(d.tail[1].x, d.tail[1].y); + ctx.lineTo(d.hxy.x, d.hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + }; + + jsPlumb.Overlays.canvas.Arrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.canvas.PlainArrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.canvas.Diamond = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.0/jsPlumb-renderers-svg-1.3.0-RC1.js b/archive/1.3.0/jsPlumb-renderers-svg-1.3.0-RC1.js new file mode 100644 index 000000000..4ae58fdda --- /dev/null +++ b/archive/1.3.0/jsPlumb-renderers-svg-1.3.0-RC1.js @@ -0,0 +1,382 @@ +/** + * SVG support for jsPlumb. + * + * things to investigate: + * + * gradients: https://developer.mozilla.org/en/svg_in_html_introduction + * css:http://tutorials.jenkov.com/svg/svg-and-css.html + * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath + * pointer events: https://developer.mozilla.org/en/css/pointer-events + * + */ +;(function() { + + var svgAttributeMap = { + "stroke-linejoin":"stroke-linejoin", + "joinstyle":"stroke-linejoin", + "stroke-dashoffset":"stroke-dashoffset" + }; + + var ns = { + svg:"http://www.w3.org/2000/svg", + xhtml:"http://www.w3.org/1999/xhtml" + }, + _attr = function(node, attributes) { + for (var i in attributes) + node.setAttribute(i, "" + attributes[i]); + }, + _node = function(name, attributes) { + var n = document.createElementNS(ns.svg, name); + attributes = attributes || {}; + attributes["version"] = "1.1"; + attributes["xmnls"] = ns.xhtml; + _attr(n, attributes); + return n; + }, + _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; }, + _convertStyle = function(s, ignoreAlpha) { + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + + return o; + }, + _clearGradient = function(parent) { + for (var i = 0; i < parent.childNodes.length; i++) { + if (parent.childNodes[i].tagName == "linearGradient" || parent.childNodes[i].tagName == "radialGradient") + parent.removeChild(parent.childNodes[i]); + } + }, + _updateGradient = function(parent, node, style, dimensions) { + var id = "jsplumb_gradient_" + (new Date()).getTime(); + // first clear out any existing gradient + _clearGradient(parent); + // this checks for an 'offset' property in the gradient, and in the absence of it, assumes + // we want a linear gradient. if it's there, we create a radial gradient. + // it is possible that a more explicit means of defining the gradient type would be + // better. relying on 'offset' means that we can never have a radial gradient that uses + // some default offset, for instance. + if (!style.gradient.offset) { + var g = _node("linearGradient", {id:id}); + parent.appendChild(g); + } + else { + var g = _node("radialGradient", { + id:id + }); + parent.appendChild(g); + } + + // the svg radial gradient seems to treat stops in the reverse + // order to how canvas does it. so we want to keep all the maths the same, but + // iterate the actual style declarations in reverse order, if the x indexes are not in order. + for (var i = 0; i < style.gradient.stops.length; i++) { + // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of + // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments. + // so, not too concerned about leaving this in for now. + var styleToUse = i; + if (dimensions.length == 8) + styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i; + else + styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i; + var stopColor = _convertStyle(style.gradient.stops[styleToUse][1], true); + var s = _node("stop", {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor}); + g.appendChild(s); + } + var applyGradientTo = style.strokeStyle ? "stroke" : "fill"; + node.setAttribute("style", applyGradientTo + ":url(#" + id + ")"); + }, + _applyStyles = function(parent, node, style, dimensions) { + + if (style.gradient) { + _updateGradient(parent, node, style, dimensions); + } + else { + // make sure we clear any existing gradient + _clearGradient(parent); + node.setAttribute("style", ""); + } + + node.setAttribute("fill", style.fillStyle ? _convertStyle(style.fillStyle, true) : "none"); + node.setAttribute("stroke", style.strokeStyle ? _convertStyle(style.strokeStyle, true) : "none"); + if (style.lineWidth) { + node.setAttribute("stroke-width", style.lineWidth); + } + + // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like + // the syntax in VML but is actually kind of nasty: values are given in the pixel + // coordinate space, whereas in VML they are multiples of the width of the stroked + // line, which makes a lot more sense. for that reason, jsPlumb is supporting both + // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from + // VML, which will be the preferred method. the code below this converts a dashstyle + // attribute given in terms of stroke width into a pixel representation, by using the + // stroke's lineWidth. + if(style["stroke-dasharray"]) { + node.setAttribute("stroke-dasharray", style["stroke-dasharray"]); + } + if (style["dashstyle"] && style["lineWidth"]) { + var sep = style["dashstyle"].indexOf(",") == -1 ? " " : ",", + parts = style["dashstyle"].split(sep), + styleToUse = ""; + parts.forEach(function(p) { + styleToUse += (Math.floor(p * style.lineWidth) + sep); + }); + node.setAttribute("stroke-dasharray", styleToUse); + } + + // extra attributes such as join type, dash offset. + for (var i in svgAttributeMap) { + if (style[i]) { + node.setAttribute(svgAttributeMap[i], style[i]); + } + } + }, + _decodeFont = function(f) { + var r = /([0-9].)(p[xt])\s(.*)/; + var bits = f.match(r); + return {size:bits[1] + bits[2], font:bits[3]}; + }; + + /* + * Base class for SVG components. + */ + var SvgComponent = function(cssClass, originalArgs, pointerEventsSpec) { + var self = this; + pointerEventsSpec = pointerEventsSpec || "all"; + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + self.canvas = null, self.path = null, self.svg = null; + + this.setHover = function() { }; + + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.sizeCanvas(self.canvas,0,0,1,1); + + var clazz = cssClass + " " + (originalArgs[0].cssClass || ""); + + self.svg = _node("svg", { + "style":"", + "width":0, + "height":0, + "pointer-events":pointerEventsSpec, + "class": clazz + }); + + jsPlumb.appendElement(self.canvas, originalArgs[0]["parent"]); + self.canvas.appendChild(self.svg); + + // TODO this displayElement stuff is common between all components, across all + // renderers. would be best moved to jsPlumbUIComponent. + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + this.paint = function(d, style, anchor) { + if (style != null) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + _attr(self.svg, { + "style":_pos([0,0,d[2], d[3]]), + "width": d[2], + "height": d[3] + }); + self._paint.apply(this, arguments); + } + }; + }; + + /* + * Base class for SVG connectors. + */ + var SvgConnector = function(params) { + var self = this; + SvgComponent.apply(this, [ params["_jsPlumb"].connectorClass, arguments, "none" ]); + this._paint = function(d, style) { + var p = self.getPath(d), a = { "d":p }, outlineStyle = null; + a["pointer-events"] = "all"; + + // outline style. actually means drawing an svg object underneath the main one. + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + outlineStyle = { + strokeStyle:_convertStyle(style.outlineColor), + lineWidth:outlineStrokeWidth + }; + + if (self.bgPath == null) { + self.bgPath = _node("path", a); + self.svg.appendChild(self.bgPath); + self.attachListeners(self.bgPath, self); + } + else { + _attr(self.bgPath, a); + } + + _applyStyles(self.svg, self.bgPath, outlineStyle, d); + } + + + if (self.path == null) { + self.path = _node("path", a); + self.svg.appendChild(self.path); + self.attachListeners(self.path, self); + } + else { + _attr(self.path, a); + } + + _applyStyles(self.svg, self.path, style, d); + }; + }; + + /* + * SVG Bezier Connector + */ + jsPlumb.Connectors.svg.Bezier = function(params) { + jsPlumb.Connectors.Bezier.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]; }; + }; + + /* + * SVG straight line Connector + */ + jsPlumb.Connectors.svg.Straight = function(params) { + jsPlumb.Connectors.Straight.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; }; + }; + + jsPlumb.Connectors.svg.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "M " + dimensions[4] + "," + dimensions[5]; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)]; + } + // finally draw a line to the end + p = p + " " + dimensions[6] + "," + dimensions[7]; + return p; + }; + }; + + /* + * Base class for SVG endpoints. + */ + var SvgEndpoint = function(params) { + var self = this; + SvgComponent.apply(this, [ params["_jsPlumb"].endpointClass, arguments, "all" ]); + this._paint = function(d, style) { + var s = jsPlumb.extend({}, style); + if (s.outlineColor) { + s.strokeWidth = s.outlineWidth; + s.strokeStyle = _convertStyle(s.outlineColor, true); + } + + if (self.node == null) { + self.node = self.makeNode(d, s); + self.svg.appendChild(self.node); + self.attachListeners(self.node, self); + } + _applyStyles(self.svg, self.node, s, d); + _pos(self.node, d); + }; + }; + + /* + * SVG Dot Endpoint + */ + jsPlumb.Endpoints.svg.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("circle", { + "cx" : d[2] / 2, + "cy" : d[3] / 2, + "r" : d[2] / 2 + }); + }; + }; + + /* + * SVG Rectangle Endpoint + */ + jsPlumb.Endpoints.svg.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("rect", { + "width":d[2], + "height":d[3] + }); + }; + }; + + /* + * SVG Image Endpoint is the default image endpoint. + */ + jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image; + /* + * Blank endpoint in svg renderer is the default Blank endpoint. + */ + jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank; + /* + * Label endpoint in svg renderer is the default Label endpoint. + */ + jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label; + + + var AbstractSvgArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + var self = this, path =null; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + } + + _attr(path, { + "d" : makePath(d), + stroke : strokeStyle ? strokeStyle : null, + fill : fillStyle ? fillStyle : null + }); + }; + var makePath = function(d) { + return "M" + d.hxy.x + "," + d.hxy.y + + " L" + d.tail[0].x + "," + d.tail[0].y + + " L" + d.cxy.x + "," + d.cxy.y + + " L" + d.tail[1].x + "," + d.tail[1].y + + " L" + d.hxy.x + "," + d.hxy.y; + }; + }; + + jsPlumb.Overlays.svg.Arrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.svg.PlainArrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.svg.Diamond = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.0/jsPlumb-renderers-vml-1.3.0-RC1.js b/archive/1.3.0/jsPlumb-renderers-vml-1.3.0-RC1.js new file mode 100644 index 000000000..c684083e9 --- /dev/null +++ b/archive/1.3.0/jsPlumb-renderers-vml-1.3.0-RC1.js @@ -0,0 +1,348 @@ +;(function() { + + // http://ajaxian.com/archives/the-vml-changes-in-ie-8 + // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/ + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + + var vmlAttributeMap = { + "stroke-linejoin":"joinstyle", + "joinstyle":"joinstyle", + "endcap":"endcap", + "miterlimit":"miterlimit" + }; + + if (document.createStyleSheet) { + + // this is the style rule for IE7/6: it uses a CSS class, tidy. + document.createStyleSheet().addRule(".jsplumb_vml", "behavior:url(#default#VML);position:absolute;"); + + // these are for VML in IE8. you have to explicitly call out which elements + // you're going to expect to support VML! + // + // try to avoid IE8. it is recommended you set X-UA-Compatible="IE=7" if you can. + // + document.createStyleSheet().addRule("jsplumb\\:textbox", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:oval", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:rect", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:stroke", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:shape", "behavior:url(#default#VML);position:absolute;"); + + // in this page it is also mentioned that IE requires the extra arg to the namespace + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either. + // var iev = document.documentMode; + //if (!iev || iev < 8) + document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); + //else + // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML"); + } + + var scale = 1000, + _atts = function(o, atts) { + for (var i in atts) { + // IE8 fix: setattribute does not work after an element has been added to the dom! + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + //o.setAttribute(i, atts[i]); + o[i] = atts[i]; + } + }, + _node = function(name, d, atts) { + atts = atts || {}; + var o = document.createElement("jsplumb:" + name); + o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml"; + _pos(o, d); + _atts(o, atts); + return o; + }, + _pos = function(o,d) { + o.style.left = d[0] + "px"; + o.style.top = d[1] + "px"; + o.style.width= d[2] + "px"; + o.style.height= d[3] + "px"; + o.style.position = "absolute"; + }, + _conv = function(v) { + return Math.floor(v * scale); + }, + _convertStyle = function(s, ignoreAlpha) { + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + + return o; + }, + _applyStyles = function(node, style, component) { + var styleToWrite = {}; + if (style.strokeStyle) { + styleToWrite["stroked"] = "true"; + styleToWrite["strokecolor"] =_convertStyle(style.strokeStyle, true); + styleToWrite["strokeweight"] = style.lineWidth + "px"; + } + else styleToWrite["stroked"] = "false"; + + if (style.fillStyle) { + styleToWrite["filled"] = "true"; + styleToWrite["fillcolor"] = _convertStyle(style.fillStyle, true); + } + else styleToWrite["filled"] = "false"; + + if(style["dashstyle"]) { + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = style["dashstyle"]; + } + else if (style["stroke-dasharray"] && style["lineWidth"]) { + var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",", + parts = style["stroke-dasharray"].split(sep), + styleToUse = ""; + for(var i = 0; i < parts.length; i++) { + styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep); + } + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = styleToUse; + } + + _atts(node, styleToWrite); + }, + /* + * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. + */ + VmlComponent = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }, + /* + * Base class for Vml connectors. extends VmlComponent. + */ + VmlConnector = function(params) { + var self = this; + self.strokeNode = null; + self.canvas = null; + VmlComponent.apply(this, arguments); + clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : ""); + this.paint = function(d, style, anchor) { + if (style != null) { + var path = self.getPath(d), p = { "path":path }; + + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + outlineStyle = { + strokeStyle:_convertStyle(style.outlineColor), + lineWidth:outlineStrokeWidth + }; + + if (self.bgCanvas == null) { + p["class"] = clazz; + self.bgCanvas = _node("shape", d, p); + jsPlumb.appendElement(self.bgCanvas, params.parent); + displayElements.push(self.bgCanvas); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.bgCanvas, d); + _atts(self.bgCanvas, p); + } + + _applyStyles(self.bgCanvas, outlineStyle, self); + } + + if (self.canvas == null) { + p["class"] = clazz; + self.canvas = _node("shape", d, p); + jsPlumb.appendElement(self.canvas, params.parent); + displayElements.push(self.canvas); + + self.attachListeners(self.canvas, self); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.canvas, d); + _atts(self.canvas, p); + } + + _applyStyles(self.canvas, style, self); + } + }; + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + self.canvas.parentNode.appendChild(el); + displayElements.push(el); + }; + }, + /* + * + * Base class for Vml Endpoints. extends VmlComponent. + * + */ + VmlEndpoint = function(params) { + VmlComponent.apply(this, arguments); + var vml = null, self = this; + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function(d, style, anchor) { + var p = { }; + + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (vml == null) { + p["class"] = jsPlumb.endpointClass; + vml = self.getVml([0,0, d[2], d[3]], p, anchor); + self.canvas.appendChild(vml); + self.attachListeners(vml, self); + } + else { + //p["coordsize"] = "1,1";//(d[2] * scale) + "," + (d[3] * scale); again, unsure. + _pos(vml, [0,0, d[2], d[3]]); + _atts(vml, p); + } + + _applyStyles(vml, style); + }; + }; + + jsPlumb.Connectors.vml.Bezier = function() { + jsPlumb.Connectors.Bezier.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Straight = function() { + jsPlumb.Connectors.Straight.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Flowchart = function() { + jsPlumb.Connectors.Flowchart.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l"; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]); + } + // finally draw a line to the end + p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e"; + return p; + }; + }; + + jsPlumb.Endpoints.vml.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("oval", d, atts); }; + }; + + jsPlumb.Endpoints.vml.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("rect", d, atts); }; + }; + + /* + * VML Image Endpoint is the same as the default image endpoint. + */ + jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image; + + /** + * placeholder for Blank endpoint in vml renderer. + */ + jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank; + + /** + * VML Label renderer. uses the default label renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label; + + var AbstractVmlArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + VmlComponent.apply(this, arguments); + var self = this, canvas = null, path =null; + var getPath = function(d, connectorDimensions) { + return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) + + " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + + " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + + " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + + " x e"; + }; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) { + var p = {}; + if (strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] =_convertStyle(strokeStyle, true); + } + if (lineWidth) p["strokeweight"] = lineWidth + "px"; + if (fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = fillStyle; + } + var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + w = Math.abs(xmax - xmin), + h = Math.abs(ymax - ymin), + dim = [xmin, ymin, w, h]; + + // for VML, we create overlays using shapes that have the same dimensions and + // coordsize as their connector - overlays calculate themselves relative to the + // connector (it's how it's been done since the original canvas implementation, because + // for canvas that makes sense). + p["path"] = getPath(d, connectorDimensions); + p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale); + + dim[0] = connectorDimensions[0]; + dim[1] = connectorDimensions[1]; + dim[2] = connectorDimensions[2]; + dim[3] = connectorDimensions[3]; + + if (canvas == null) { + //p["class"] = jsPlumb.overlayClass; // TODO currentInstance? + canvas = _node("shape", dim, p); + connector.appendDisplayElement(canvas); + self.attachListeners(canvas, connector); + } + else { + _pos(canvas, dim); + _atts(canvas, p); + } + }; + }; + + jsPlumb.Overlays.vml.Arrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.vml.PlainArrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.vml.Diamond = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.0/mootools.jsPlumb-1.3.0-RC1.js b/archive/1.3.0/mootools.jsPlumb-1.3.0-RC1.js new file mode 100644 index 000000000..5f292deaa --- /dev/null +++ b/archive/1.3.0/mootools.jsPlumb-1.3.0-RC1.js @@ -0,0 +1,341 @@ +/* + * mootools.jsPlumb 1.3.0-RC1 + * + * MooTools specific functionality for jsPlumb. + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + * NOTE: for production usage you should use mootools.jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, + * in a minified file. + * + * Dual licensed under MIT and GPL2. + * + */ + +;(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function(now) { + this.parent(now); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }; + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + var _getElementObject = function(el) { + return $(el); + }; + + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + m.start(properties); + }, + + appendElement : function(child, parent) { + _getElementObject(parent).grab(child); + }, + + bind : function(el, event, callback) { + el = _getElementObject(el); + el.addEvent(event, callback); + }, + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.event.clientX, eventObject.event.clientY]; + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el); + var drags = _draggablesById[id]; + return drags[0].scope; + }, + + getElementObject : _getElementObject, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + getPageXY : function(eventObject) { + return [eventObject.event.pageX, eventObject.event.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).getParent(); + }, + + getScrollLeft : function(el) { + return null; + }, + + getScrollTop : function(el) { + return null; + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[0], + p = jsPlumb.CurrentLibrary.getElementObject(ui).getPosition(); + return { left:p.x, top:p.y }; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options) { + var id = jsPlumb.getId(el); + var drag = _draggablesById[id]; + if (!drag) { + var originalZIndex = 0, originalCursor = null; + var dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + options['onStart'] = jsPlumb.wrap(options['onStart'], function() + { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() + { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + }); + + // DROPPABLES: + var scope = options['scope'] || jsPlumb.Defaults.Scope; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop'); + } + }; + + drag = new Drag.Move(el, options); + drag.scope = scope; + //console.log("drag scope initialized to ", scope); + _add(_draggablesByScope, scope, drag); + _add(_draggablesById, el.get("id"), drag); + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + initDroppable : function(el, options) { + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _add(_droppables, scope, el); + var id = jsPlumb.getId(el); + _droppableOptions[id] = options; + var filterFunc = function(entry) { return entry.element != el; }; + var draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + isAlreadyDraggable : function(el) { + return _draggablesById[jsPlumb.getId(el)] != null; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + return (typeof Drag != undefined && typeof Drag.Move != undefined); + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + _getElementObject(element).dispose(); // ?? + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + setDragScope : function(el, scope) { + var drag = _draggablesById[el.get("id")]; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + drag[0].droppables = droppables; + }, + + setOffset : function(el, o) { + _getElementObject(el).setPosition({x:o.left, y:o.top}); + } + }; + + window.addEvent('domready', jsPlumb.init); +})(); diff --git a/archive/1.3.0/mootools.jsPlumb-1.3.0-all-min.js b/archive/1.3.0/mootools.jsPlumb-1.3.0-all-min.js new file mode 100644 index 000000000..c9f098d43 --- /dev/null +++ b/archive/1.3.0/mootools.jsPlumb-1.3.0-all-min.js @@ -0,0 +1 @@ +(function(){var o=!!!document.createElement("canvas").getContext;var s=!!document.createElement("canvas").getContext;var d=!!window.SVGAngle||document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1");var a=!(s|d);var l=function(y,z,w,C){var B=function(F,E){if(F===E){return true}else{if(typeof F=="object"&&typeof E=="object"){var G=true;for(var D in F){if(!B(F[D],E[D])){G=false;break}}for(var D in E){if(!B(E[D],F[D])){G=false;break}}return G}}};for(var A=+w||0,x=y.length;A=0){delete (ax[ay]);ax.splice(ay,1);return true}}}return false},w=function(ay,ax){return J(ay,function(az,aA){ag[aA]=ax;if(i.CurrentLibrary.isDragSupported(az)){i.CurrentLibrary.setDraggable(az,ax)}})},ao=function(ax,ay){V(c(ax,"id"),function(az){az.canvas.style.display=ay})},O=function(ax){return J(ax,function(az,ay){var aA=ag[ay]==null?Y:ag[ay];aA=!aA;ag[ay]=aA;i.CurrentLibrary.setDraggable(az,aA);return aA})},y=function(ax){V(ax,function(az){var ay=("none"==az.canvas.style.display);az.canvas.style.display=ay?"block":"none"})},F=function(aC){var aA=aC.timestamp,ax=aC.recalc,aB=aC.offset,ay=aC.elId;if(!ax){if(aA&&aA===D[ay]){return}}if(ax||aB==null){var az=v(ay);if(az!=null){Q[ay]=b(az);W[ay]=n(az);D[ay]=aA}}else{W[ay]=aB}},aw=function(ax,ay){var az=v(ax);var aA=c(az,"id");if(!aA||aA=="undefined"){if(arguments.length==2&&arguments[1]!=undefined){aA=ay}else{aA="jsPlumb_"+ar()}e(az,"id",aA)}return aA},am=function(az,ax,ay){az=az||function(){};ax=ax||function(){};return function(){var aA=null;try{aA=ax.apply(this,arguments)}catch(aB){k(E,"jsPlumb function failed : "+aB)}if(ay==null||(aA!==ay)){try{az.apply(this,arguments)}catch(aB){k(E,"wrapped function failed : "+aB)}}return aA}};this.connectorClass="_jsPlumb_connector";this.endpointClass="_jsPlumb_endpoint";this.overlayClass="_jsPlumb_overlay";this.Anchors={};this.Connectors={canvas:{},svg:{},vml:{}};this.Endpoints={canvas:{},svg:{},vml:{}};this.Overlays={canvas:{},svg:{},vml:{}};this.addEndpoint=function(az,aA,aJ){aJ=aJ||{};var ay=i.extend({},aJ);i.extend(ay,aA);ay.endpoint=ay.endpoint||E.Defaults.Endpoint||i.Defaults.Endpoint;ay.paintStyle=ay.paintStyle||E.Defaults.EndpointStyle||i.Defaults.EndpointStyle;az=x(az);var aB=[],aE=az.length&&az.constructor!=String?az:[az];for(var aC=0;aC0?l(aK,aJ)!=-1:true},aB=aE.length>1?{}:[],aH=function(aK,aL){if(aE.length>1){var aJ=aB[aK];if(aJ==null){aJ=[];aB[aK]=aJ}aJ.push(aL)}else{aB.push(aL)}};for(var aA in M){if(ay(aE,aA)){for(var az=0;az=4){ay.orientation=[arguments[2],arguments[3]]}if(arguments.length==6){ay.offsets=[arguments[4],arguments[5]]}}var aD=new ac(ay);aD.clone=function(){return new ac(ay)};return aD};this.makeAnchors=function(ay){var az=[];for(var ax=0;ax0?aG[0]:null;var aC=aG.length>0?0:-1;this.locked=false;var aF=this;var aB=function(aJ,aH,aN,aM,aI){var aL=aM[0]+(aJ.x*aI[0]),aK=aM[1]+(aJ.y*aI[1]);return Math.sqrt(Math.pow(aH-aL,2)+Math.pow(aN-aK,2))};var ax=ay||function(aR,aI,aJ,aK,aH){var aM=aJ[0]+(aK[0]/2),aL=aJ[1]+(aK[1]/2);var aO=-1,aQ=Infinity;for(var aN=0;aNaX){aX=a4}}var a8=this.connector.compute(aZ,ba,this.endpoints[be].anchor,this.endpoints[aW].anchor,aI.paintStyleInUse.lineWidth,aX);aI.connector.paint(a8,aI.paintStyleInUse);for(var a9=0;a9=0){aM.connections.splice(aY,1);if(!a1){var a0=aZ.endpoints[0]==aM?aZ.endpoints[1]:aZ.endpoints[0];a0.detach(aZ,true);if(aZ.endpointToDeleteOnDetach&&aZ.endpointToDeleteOnDetach.connections.length==0){i.deleteEndpoint(aZ.endpointToDeleteOnDetach)}}R(aZ.connector.getDisplayElements(),aZ.parent);N(M,aZ.scope,aZ);if(!a1){ad(aZ)}}};this.detachAll=function(){while(aM.connections.length>0){aM.detach(aM.connections[0])}};this.detachFrom=function(aZ){var a0=[];for(var aY=0;aY=0){aM.connections.splice(aY,1)}};this.getElement=function(){return aL};this.getUuid=function(){return aH};this.makeInPlaceCopy=function(){return an({anchor:aM.anchor,source:aL,paintStyle:this.paintStyle,endpoint:aK})};this.isConnectedTo=function(a0){var aZ=false;if(a0){for(var aY=0;aY0){var a8=aM.connections[0];var ba=a8.endpoints[0]==aM?1:0;var a3=ba==0?a8.sourceId:a8.targetId;var a7=W[a3],a9=Q[a3];aZ.txy=[a7.left,a7.top];aZ.twh=a9;aZ.tElement=a8.endpoints[ba]}}a4=aM.anchor.compute(aZ)}var a6=aK.compute(a4,aM.anchor.getOrientation(),aM.paintStyleInUse,a2||aM.paintStyleInUse);aK.paint(a6,aM.paintStyleInUse,aM.anchor);aM.timestamp=a5}};this.repaint=this.paint;this.removeConnection=this.detach;if(aX.isSource&&i.CurrentLibrary.isDragSupported(aL)){var aR=null,aN=null,aQ=null,ax=false,aA=null;var aC=function(){aQ=aM.connectorSelector();if(aM.isFull()&&!aI){return false}F({elId:aE});aB=aM.makeInPlaceCopy();aB.paint();aR=document.createElement("div");aR.style.position="absolute";var a5=v(aR);S(aR,aM.parent);var aY=aw(a5);var a6=v(aB.canvas),a4=i.CurrentLibrary.getOffset(a6),a1=i.CurrentLibrary.getSize(a6);i.CurrentLibrary.setOffset(aR,a4);F({elId:aY});e(v(aM.canvas),"dragId",aY);e(v(aM.canvas),"elId",aE);var a7=new I({reference:aM.anchor,referenceCanvas:aM.canvas});aV=an({paintStyle:aM.paintStyle,endpoint:aK,anchor:a7,source:a5});if(aQ==null){aM.anchor.locked=true;aQ=au({sourceEndpoint:aM,targetEndpoint:aV,source:v(aL),target:v(aR),anchors:[aM.anchor,a7],paintStyle:aX.connectorStyle,hoverPaintStyle:aX.connectorHoverStyle,connector:aX.connector,overlays:aX.connectorOverlays});aQ.connector.setHover(false)}else{ax=true;aQ.connector.setHover(false);aD(v(aB.canvas));var aZ=aQ.sourceId==aE?0:1;aQ.floatingAnchorIndex=aZ;aM.detachFromConnection(aQ);var a3=v(aM.canvas);var a2=i.CurrentLibrary.getDragScope(a3);e(a3,"originalScope",a2);var a0="scope_"+(new Date()).getTime();if(aZ==0){aA=[aQ.source,aQ.sourceId,aU,a2];aQ.source=v(aR);aQ.sourceId=aY}else{aA=[aQ.target,aQ.targetId,aU,a2];aQ.target=v(aR);aQ.targetId=aY}i.CurrentLibrary.setDragScope(aU,a0);aQ.endpoints[aZ==0?1:0].anchor.locked=true;aQ.suspendedEndpoint=aQ.endpoints[aZ];aQ.endpoints[aZ]=aV}Z[aY]=aQ;aV.addConnection(aQ);T(ah,aY,aV);E.currentlyDragging=true};var ay=i.CurrentLibrary,aT=aX.dragOptions||{},aO=i.extend({},ay.defaultDragOptions),aP=ay.dragEvents.start,aW=ay.dragEvents.stop,aG=ay.dragEvents.drag;aT=i.extend(aO,aT);aT.scope=aT.scope||aM.scope;aT[aP]=am(aT[aP],aC);aT[aG]=am(aT[aG],function(){var aY=i.CurrentLibrary.getUIPosition(arguments);i.CurrentLibrary.setOffset(aR,aY);at(v(aR),aY)});aT[aW]=am(aT[aW],function(){N(ah,aN,aV);R([aR,aV.canvas],aL);ae(aB.canvas,aL);var aY=aQ.floatingAnchorIndex==null?1:aQ.floatingAnchorIndex;aQ.endpoints[aY==0?1:0].anchor.locked=false;if(aQ.endpoints[aY]==aV){if(ax&&aQ.suspendedEndpoint){if(aY==0){aQ.source=aA[0];aQ.sourceId=aA[1]}else{aQ.target=aA[0];aQ.targetId=aA[1]}i.CurrentLibrary.setDragScope(aA[2],aA[3]);aQ.endpoints[aY]=aQ.suspendedEndpoint;if(aJ){aQ.floatingAnchorIndex=null;aQ.suspendedEndpoint.addConnection(aQ);i.repaint(aA[1])}else{aQ.endpoints[aY==0?1:0].detach(aQ)}}else{R(aQ.connector.getDisplayElements(),aM.parent);aM.detachFromConnection(aQ)}}aM.anchor.locked=false;aM.paint();aQ.setHover(false);aQ.repaint();aQ=null;delete aB;delete ah[aV.elementId];aV=null;delete aV;E.currentlyDragging=false});var aU=v(aM.canvas);i.CurrentLibrary.initDraggable(aU,aT)}var aD=function(a1){if(aX.isTarget&&i.CurrentLibrary.isDropSupported(aL)){var aY=aX.dropOptions||E.Defaults.DropOptions||i.Defaults.DropOptions;aY=i.extend({},aY);aY.scope=aY.scope||aM.scope;var a4=null;var a2=i.CurrentLibrary.dragEvents.drop;var a3=i.CurrentLibrary.dragEvents.over;var aZ=i.CurrentLibrary.dragEvents.out;var a0=function(){var bd=v(i.CurrentLibrary.getDragObject(arguments));var a5=c(bd,"dragId");var a7=c(bd,"elId");var bc=c(bd,"originalScope");if(bc){i.CurrentLibrary.setDragScope(bd,bc)}var a9=Z[a5];var ba=a9.floatingAnchorIndex==null?1:a9.floatingAnchorIndex,bb=ba==0?1:0;if(!aM.isFull()&&!(ba==0&&!aM.isSource)&&!(ba==1&&!aM.isTarget)){if(ba==0){a9.source=aL;a9.sourceId=aE}else{a9.target=aL;a9.targetId=aE}a9.endpoints[ba].detachFromConnection(a9);if(a9.suspendedEndpoint){a9.suspendedEndpoint.detachFromConnection(a9)}a9.endpoints[ba]=aM;aM.addConnection(a9);if(!a9.suspendedEndpoint){T(M,a9.scope,a9);P(aL,aX.draggable,{})}else{var a8=a9.suspendedEndpoint.getElement(),a6=a9.suspendedEndpoint.elementId;E.fire("jsPlumbConnectionDetached",{source:ba==0?a8:a9.source,target:ba==1?a8:a9.target,sourceId:ba==0?a6:a9.sourceId,targetId:ba==1?a6:a9.targetId,sourceEndpoint:ba==0?a9.suspendedEndpoint:a9.endpoints[0],targetEndpoint:ba==1?a9.suspendedEndpoint:a9.endpoints[1],connection:a9})}i.repaint(a7);E.fire("jsPlumbConnection",{source:a9.source,target:a9.target,sourceId:a9.sourceId,targetId:a9.targetId,sourceEndpoint:a9.endpoints[0],targetEndpoint:a9.endpoints[1],connection:a9})}E.currentlyDragging=false;delete Z[a5]};aY[a2]=am(aY[a2],a0);aY[a3]=am(aY[a3],function(){var a6=i.CurrentLibrary.getDragObject(arguments);var a8=c(v(a6),"dragId");var a7=Z[a8];if(a7!=null){var a5=a7.floatingAnchorIndex==null?1:a7.floatingAnchorIndex;a7.endpoints[a5].anchor.over(aM.anchor)}});aY[aZ]=am(aY[aZ],function(){var a6=i.CurrentLibrary.getDragObject(arguments),a8=c(v(a6),"dragId"),a7=Z[a8];if(a7!=null){var a5=a7.floatingAnchorIndex==null?1:a7.floatingAnchorIndex;a7.endpoints[a5].anchor.out()}});i.CurrentLibrary.initDroppable(a1,aY)}};aD(v(aM.canvas));return aM}};var i=window.jsPlumb=new r();i.getInstance=function(x){var w=new r(x);return w};var m=function(w,B,A,z){return function(){return i.makeAnchor(w,B,A,z)}};i.Anchors.TopCenter=m(0.5,0,0,-1);i.Anchors.BottomCenter=m(0.5,1,0,1);i.Anchors.LeftMiddle=m(0,0.5,-1,0);i.Anchors.RightMiddle=m(1,0.5,1,0);i.Anchors.Center=m(0.5,0.5,0,0);i.Anchors.TopRight=m(1,0,0,-1);i.Anchors.BottomRight=m(1,1,0,1);i.Anchors.TopLeft=m(0,0,0,-1);i.Anchors.BottomLeft=m(0,1,0,1);i.Defaults.DynamicAnchors=function(){return i.makeAnchors(["TopCenter","RightMiddle","BottomCenter","LeftMiddle"])};i.Anchors.AutoDefault=function(){return i.makeDynamicAnchor(i.Defaults.DynamicAnchors())}})();(function(){jsPlumb.DOMElementComponent=function(a){jsPlumb.jsPlumbUIComponent.apply(this,arguments);this.mousemove=this.dblclick=this.click=this.mousedown=this.mouseup=function(b){}};jsPlumb.Connectors.Straight=function(){var n=this;var g=null;var c,h,l,k,i,d,m,f,e,b,a;this.compute=function(r,F,B,o,v,q){var E=Math.abs(r[0]-F[0]);var u=Math.abs(r[1]-F[1]);var z=false,s=false;var t=0.45*E,p=0.45*u;E*=1.9;u*=1.9;var C=Math.min(r[0],F[0])-t;var A=Math.min(r[1],F[1])-p;var D=Math.max(2*v,q);if(E0?1:-1;var t=Math.abs(u*Math.sin(d));if(e>a){t=t*-1}var o=Math.abs(u*Math.cos(d));if(f>b){o=o*-1}return{x:s.x+(r*o),y:s.y+(r*t)}};this.perpendicularToPathAt=function(s,t,w){var u=n.pointAlongPathFrom(s,w);var r=n.gradientAtPoint(u.location);var q=Math.atan(-1/r);var v=t/2*Math.sin(q);var o=t/2*Math.cos(q);return[{x:u.x+o,y:u.y+v},{x:u.x-o,y:u.y-v}]}};jsPlumb.Connectors.Bezier=function(e){var o=this;e=e||{};this.majorAnchor=e.curviness||150;this.minorAnchor=10;var h=null;this._findControlPoint=function(z,q,u,x,r){var w=x.getOrientation(),y=r.getOrientation();var t=w[0]!=y[0]||w[1]==y[1];var s=[];var A=o.majorAnchor,v=o.minorAnchor;if(!t){if(w[0]==0){s.push(q[0]l){l=u}if(x<0){d+=x;var y=Math.abs(x);l+=y;n[0]+=y;i+=y;b+=y;m[0]+=y}var G=Math.min(f,a);var E=Math.min(n[1],m[1]);var t=Math.min(G,E);var z=Math.max(f,a);var w=Math.max(n[1],m[1]);var r=Math.max(z,w);if(r>g){g=r}if(t<0){c+=t;var v=Math.abs(t);g+=v;n[1]+=v;f+=v;a+=v;m[1]+=v}if(D&&l=s){q=t;r=(s-l[t][0])/a[t];break}}return{segment:i[q],proportion:r,index:q}};this.compute=function(Q,v,P,r,q,H){i=[];h=[];a=[];g=[];segmentProportionals=[];d=v[0]t[0]?t[0]+((1-x)*r)-y:t[2]+(x*r)+y,y:q==0?t[3]:t[3]>t[1]?t[1]+((1-x)*r)-y:t[3]+(x*r)+y,segmentInfo:v};return w};this.perpendicularToPathAt=function(t,u,z){var v=n.pointAlongPathFrom(t,z);var s=h[v.segmentInfo.index];var r=Math.atan(-1/s);var w=u/2*Math.sin(r);var q=u/2*Math.cos(r);return[{x:v.x+q,y:v.y+w},{x:v.x-q,y:v.y-w}]}};jsPlumb.Endpoints.Dot=function(b){var a=this;b=b||{};this.radius=b.radius||10;this.defaultOffset=0.5*this.radius;this.defaultInnerRadius=this.radius/3;this.compute=function(g,d,i,f){var e=i.radius||a.radius;var c=g[0]-e;var h=g[1]-e;return[c,h,e*2,e*2,e]}};jsPlumb.Endpoints.Rectangle=function(b){var a=this;b=b||{};this.width=b.width||20;this.height=b.height||20;this.compute=function(h,e,k,g){var f=k.width||a.width;var d=k.height||a.height;var c=h[0]-(f/2);var i=h[1]-(d/2);return[c,i,f,d]}};jsPlumb.Endpoints.Image=function(d){jsPlumb.DOMElementComponent.apply(this,arguments);var a=this,c=false;this.img=new Image();a.ready=false;this.img.onload=function(){a.ready=true};this.img.src=d.src||d.url;this.compute=function(g,e,h,f){a.anchorPoint=g;if(a.ready){return[g[0]-a.img.width/2,g[1]-a.img.height/2,a.img.width,a.img.height]}else{return[0,0,0,0]}};a.canvas=document.createElement("img"),c=false;a.canvas.style.margin=0;a.canvas.style.padding=0;a.canvas.style.outline=0;a.canvas.style.position="absolute";a.canvas.className=jsPlumb.endpointClass;jsPlumb.appendElement(a.canvas,d.parent);a.attachListeners(a.canvas,a);var b=function(k,i,g){if(!c){a.canvas.setAttribute("src",a.img.src);c=true}var h=a.img.width,f=a.img.height,e=a.anchorPoint[0]-(h/2),l=a.anchorPoint[1]-(f/2);jsPlumb.sizeCanvas(a.canvas,e,l,h,f)};this.paint=function(g,f,e){if(a.ready){b(g,f,e)}else{window.setTimeout(function(){a.paint(g,f,e)},200)}}};jsPlumb.Endpoints.Blank=function(){jsPlumb.DOMElementComponent.apply(this,arguments);this.compute=function(){return[0,0,0,0]};self.canvas=document.createElement("div");this.paint=function(){}};jsPlumb.Endpoints.Triangle=function(a){a=a||{};a.width=a.width||55;param.height=a.height||55;this.width=a.width;this.height=a.height;this.compute=function(g,d,i,f){var e=i.width||self.width;var c=i.height||self.height;var b=g[0]-(e/2);var h=g[1]-(c/2);return[b,h,e,c]}};jsPlumb.Overlays.Arrow=function(f){f=f||{};var b=this;this.length=f.length||20;this.width=f.width||20;this.id=f.id;this.connection=f.connection;var e=(f.direction||1)<0?-1:1;var c=f.paintStyle||{lineWidth:1};this.loc=f.location==null?0.5:f.location;var a=f.foldback||0.623;var d=function(g,i){if(a==0.5){return g.pointOnPath(i)}else{var h=0.5-a;return g.pointAlongPathFrom(i,e*b.length*h)}};this.computeMaxSize=function(){return b.width*1.5};this.draw=function(k,q,w){var y=k.pointAlongPathFrom(b.loc,e*(b.length/2));var t=k.pointAlongPathFrom(b.loc,-1*e*(b.length/2)),B=t.x,A=t.y;var r=k.perpendicularToPathAt(b.loc,b.width,-1*e*(b.length/2));var i=d(k,b.loc);if(b.loc==1){var h=k.pointOnPath(b.loc);var v=(h.x-y.x)*e,u=(h.y-y.y)*e;i.x+=v;i.y+=u;t.x+=v;t.y+=u;r[0].x+=v;r[0].y+=u;r[1].x+=v;r[1].y+=u;y.x+=v;y.y+=u}if(b.loc==0){var h=k.pointOnPath(b.loc);var s=a>1?i:{x:r[0].x+((r[1].x-r[0].x)/2),y:r[0].y+((r[1].y-r[0].y)/2)};var v=(h.x-s.x)*e,u=(h.y-s.y)*e;i.x+=v;i.y+=u;t.x+=v;t.y+=u;r[0].x+=v;r[0].y+=u;r[1].x+=v;r[1].y+=u;y.x+=v;y.y+=u}var o=Math.min(y.x,r[0].x,r[1].x);var n=Math.max(y.x,r[0].x,r[1].x);var m=Math.min(y.y,r[0].y,r[1].y);var l=Math.max(y.y,r[0].y,r[1].y);var z={hxy:y,tail:r,cxy:i},x=c.strokeStyle||q.strokeStyle,p=c.fillStyle||q.strokeStyle,g=c.lineWidth||q.lineWidth;b.paint(k,z,g,x,p,w);return[o,n,m,l]}};jsPlumb.Overlays.PlainArrow=function(b){b=b||{};var a=jsPlumb.extend(b,{foldback:1});jsPlumb.Overlays.Arrow.call(this,a)};jsPlumb.Overlays.Diamond=function(c){c=c||{};var a=c.length||40;var b=jsPlumb.extend(c,{length:a/2,foldback:2});jsPlumb.Overlays.Arrow.call(this,b)};jsPlumb.Overlays.Label=function(d){jsPlumb.DOMElementComponent.apply(this,arguments);this.labelStyle=d.labelStyle||jsPlumb.Defaults.LabelStyle;this.labelStyle.font=this.labelStyle.font||"12px sans-serif";this.label=d.label;this.connection=d.connection;this.id=d.id;var k=this;var h=null,e=null,c=null,b=null;this.location=d.location||0.5;this.cachedDimensions=null;var i=false,c=null,a=document.createElement("div");a.style.position="absolute";a.style.textAlign="center";a.style.cursor="pointer";a.style.font=k.labelStyle.font;a.style.color=k.labelStyle.color||"black";if(k.labelStyle.fillStyle){a.style.background=k.labelStyle.fillStyle}if(k.labelStyle.borderWidth>0){var g=k.labelStyle.borderStyle?k.labelStyle.borderStyle:"black";a.style.border=k.labelStyle.borderWidth+"px solid "+g}if(k.labelStyle.padding){a.style.padding=k.labelStyle.padding}var f=d._jsPlumb.overlayClass+" "+(k.labelStyle.cssClass?k.labelStyle.cssClass:d.cssClass?d.cssClass:"");a.className=f;jsPlumb.appendElement(a,d.connection.parent);jsPlumb.getId(a);k.attachListeners(a,k);this.paint=function(l,n,m){if(!i){l.appendDisplayElement(a);k.attachListeners(a,l);i=true}a.style.left=(m[0]+n.minx)+"px";a.style.top=(m[1]+n.miny)+"px"};this.getTextDimensions=function(l){c=typeof k.label=="function"?k.label(k):k.label;a.innerHTML=c.replace(/\r\n/g,"
");var n=jsPlumb.CurrentLibrary.getElementObject(a),m=jsPlumb.CurrentLibrary.getSize(n);return{width:m[0],height:m[1]}};this.computeMaxSize=function(l){var m=k.getTextDimensions(l);return m.width?Math.max(m.width,m.height)*1.5:0};this.draw=function(n,p,o){var r=k.getTextDimensions(n);if(r.width!=null){var q=n.pointOnPath(k.location);var m=q.x-(r.width/2);var l=q.y-(r.height/2);k.paint(n,{minx:m,miny:l,td:r,cxy:q},o);return[m,m+r.width,l,l+r.height]}else{return[0,0,0,0]}}}})();(function(){var h={"stroke-linejoin":"joinstyle",joinstyle:"joinstyle",endcap:"endcap",miterlimit:"miterlimit"};if(document.createStyleSheet){document.createStyleSheet().addRule(".jsplumb_vml","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:textbox","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:oval","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:rect","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:stroke","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:shape","behavior:url(#default#VML);position:absolute;");document.namespaces.add("jsplumb","urn:schemas-microsoft-com:vml")}var c=1000,d=function(p,q){for(var n in q){p[n]=q[n]}},m=function(n,q,r){r=r||{};var p=document.createElement("jsplumb:"+n);p.className=(r["class"]?r["class"]+" ":"")+"jsplumb_vml";l(p,q);d(p,r);return p},l=function(p,n){p.style.left=n[0]+"px";p.style.top=n[1]+"px";p.style.width=n[2]+"px";p.style.height=n[3]+"px";p.style.position="absolute"},g=function(n){return Math.floor(n*c)},a=function(p,n){var v=p,u=function(o){return o.length==1?"0"+o:o},q=function(o){return u(Number(o).toString(16))},r=/(rgb[a]?\()(.*)(\))/;if(p.match(r)){var t=p.match(r)[2].split(",");v="#"+q(t[0])+q(t[1])+q(t[2]);if(!n&&t.length==4){v=v+q(t[3])}}return v},f=function(s,r,p){var u={};if(r.strokeStyle){u.stroked="true";u.strokecolor=a(r.strokeStyle,true);u.strokeweight=r.lineWidth+"px"}else{u.stroked="false"}if(r.fillStyle){u.filled="true";u.fillcolor=a(r.fillStyle,true)}else{u.filled="false"}if(r.dashstyle){if(p.strokeNode==null){p.strokeNode=m("stroke",[0,0,0,0],{dashstyle:r.dashstyle});s.appendChild(p.strokeNode)}else{p.strokeNode.dashstyle=r.dashstyle}}else{if(r["stroke-dasharray"]&&r.lineWidth){var o=r["stroke-dasharray"].indexOf(",")==-1?" ":",",t=r["stroke-dasharray"].split(o),n="";for(var q=0;q0&&F>0&&z=z&&B[2]<=F&&B[3]>=F)){return true}}var D=v.canvas.getContext("2d").getImageData(parseInt(z),parseInt(F),1,1);return D.data[0]!=0||D.data[1]!=0||D.data[2]!=0||D.data[3]!=0}return false};var u=false;var t=false,y=null,x=false;var w=function(A,z){return A!=null&&f(A,z)};this.mousemove=function(C){var E=e(C),B=i(C),A=document.elementFromPoint(B[0],B[1]),D=w(A,"_jsPlumb_overlay");var z=o==null&&(w(A,"_jsPlumb_endpoint")||w(A,"_jsPlumb_connector"));if(!u&&z&&v._over(C)){u=true;v.fire("mouseenter",v,C);return true}else{if(u&&(!v._over(C)||!z)&&!D){u=false;v.fire("mouseexit",v,C)}}v.fire("mousemove",v,C)};this.click=function(z){if(u&&v._over(z)&&!x){v.fire("click",v,z)}x=false};this.dblclick=function(z){if(u&&v._over(z)&&!x){v.fire("dblclick",v,z)}x=false};this.mousedown=function(z){if(v._over(z)&&!t){t=true;y=m(s(v.canvas));v.fire("mousedown",v,z)}};this.mouseup=function(z){t=false;v.fire("mouseup",v,z)}};var p=function(u){var t=document.createElement("canvas");jsPlumb.appendElement(t,u.parent);t.style.position="absolute";if(u["class"]){t.className=u["class"]}u._jsPlumb.getId(t,u.uuid);return t};var d=jsPlumb.CanvasConnector=function(x){n.apply(this,arguments);var t=function(B,z){u.ctx.save();jsPlumb.extend(u.ctx,z);if(z.gradient){var A=u.createGradient(B,u.ctx);for(var y=0;y0?1:-1}}var b={subtract:function(m,l){return{x:m.x-l.x,y:m.y-l.y}},dotProduct:function(m,l){return m.x*l.x+m.y*l.y},square:function(l){return Math.sqrt(l.x*l.x+l.y*l.y)},scale:function(m,l){return{x:m.x*l,y:m.y*l}}},d=Math.pow(2,-65),h=function(y,x){for(var s=[],v=x.length-1,r=2*v-1,t=[],w=[],o=[],p=[],q=[[1,0.6,0.3,0.1],[0.4,0.6,0.6,0.4],[0.1,0.3,0.6,1]],u=0;u<=v;u++){t[u]=b.subtract(x[u],y)}for(u=0;u<=v-1;u++){w[u]=b.subtract(x[u+1],x[u]);w[u]=b.scale(w[u],3)}for(u=0;u<=v-1;u++){for(var n=0;n<=v;n++){o[u]||(o[u]=[]);o[u][n]=b.dotProduct(w[u],t[n])}}for(u=0;u<=r;u++){p[u]||(p[u]=[]);p[u].y=0;p[u].x=parseFloat(u)/r}r=v-1;for(t=0;t<=v+r;t++){w=Math.min(t,v);for(u=Math.max(0,t-r);u<=w;u++){j=t-u;p[u+j].y+=o[j][u]*q[j][u]}}v=x.length-1;p=k(p,2*v-1,s,0);r=b.subtract(y,x[0]);o=b.square(r);for(u=q=0;u=64){y[0]=(E[0].x+E[D].x)/2;return 1}var s,r,p;v=E[0].y-E[D].y;w=E[D].x-E[0].x;A=E[0].x*E[D].y-E[D].x*E[0].y;t=max_distance_below=0;for(r=1;rt){t=p}else{if(p0?1:-1,r=null;n endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + endpointsByElement = {}, + endpointsByUUID = {}, + offsets = {}, + offsetTimestamps = {}, + floatingConnections = {}, + draggableStates = {}, + _mouseEventsEnabled = this.Defaults.MouseEventsEnabled, + _draggableByDefault = true, + canvasList = [], + sizes = [], + listeners = {}, // a map: keys are event types, values are lists of listeners. + DEFAULT_SCOPE = this.Defaults.Scope, + renderMode = null, // will be set in init() + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }, + + /** + * appends an element to the given parent, or the document body if no + * parent given. + */ + _appendElement = function(el, parent) { + if (!parent) + document.body.appendChild(el); + else + jsPlumb.CurrentLibrary.appendElement(el, parent); + }, + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + _timestamp = function() { return "" + (new Date()).getTime(); }, + + /** + * YUI, for some reason, put the result of a Y.all call into an object that contains + * a '_nodes' array, instead of handing back an array-like object like the other + * libraries do. + */ + _convertYUICollection = function(c) { + return c._nodes ? c._nodes : c; + }, + + /** + * Draws an endpoint and its connections. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + _draw = function(element, ui, timestamp) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (!timestamp) timestamp = _timestamp(); + if (endpoints) { + _updateOffset( { elId : id, offset : ui, recalc : false, timestamp : timestamp }); // timestamp is checked against last update cache; it is + // valid for one paint cycle. + var myOffset = offsets[id], myWH = sizes[id]; + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + var l = endpoints[i].connections; + for ( var j = 0; j < l.length; j++) { + l[j].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); // ...paint each connection. + // then, check for dynamic endpoint; need to repaint it. + var oIdx = l[j].endpoints[0] == endpoints[i] ? 1 : 0; + if (l[j].endpoints[oIdx].anchor.isDynamic && !l[j].endpoints[oIdx].isFloating()) { + var oId = oIdx == 0 ? l[j].sourceId : l[j].targetId, + oOffset = offsets[oId], oWH = sizes[oId], + // TODO i still want to make this faster. + anchorLoc = l[j].endpoints[oIdx].anchor.compute( { + xy : [ oOffset.left, oOffset.top ], + wh : oWH, + element : l[j].endpoints[oIdx], + txy : [ myOffset.left, myOffset.top ], + twh : myWH, + tElement : endpoints[i] + }); + l[j].endpoints[oIdx].paint({ anchorLoc : anchorLoc }); + } + } + } + } + }, + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }, + + /** + * gets an Endpoint by uuid. + */ + _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }, + + /** + * inits a draggable if it's not already initialised. + */ + _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element) && !jsPlumb.CurrentLibrary.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + } + } + }, + + _newConnection = function(params) { + var connectionFunc = jsPlumb.Defaults.ConnectionType || Connection, + endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint, + parent = jsPlumb.CurrentLibrary.getParent; + + if (params.sourceEndpoint) + params["parent"] = params.sourceEndpoint.parent; + else if (params.source.constructor == endpointFunc) + params["parent"] = params.source.parent; + else params["parent"] = parent(params.source); + + params["_jsPlumb"] = _currentInstance; + var con = new connectionFunc(params); + _eventFireProxy("click", "click", con); + _eventFireProxy("dblclick", "dblclick", con); + return con; + }, + + _eventFireProxy = function(event, proxyEvent, obj) { + obj.bind(event, function(e) { + _currentInstance.fire(proxyEvent, obj, e); + }); + }, + + _newEndpoint = function(params) { + var endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint; + params["parent"] = jsPlumb.CurrentLibrary.getParent(params.source); + params["_jsPlumb"] = _currentInstance, + ep = new endpointFunc(params); + _eventFireProxy("click", "endpointClick", ep); + _eventFireProxy("dblclick", "endpointDblClick", ep); + return ep; + }, + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }, + /** + * perform an operation on all elements. + */ + _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }, + + /** + * helper to remove an element from the DOM. + */ + _removeElement = function(element, parent) { + if (element != null && element.parentNode != null) { + element.parentNode.removeChild(element); + } + }, + /** + * helper to remove a list of elements from the DOM. + */ + _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }, + /** + * helper method to remove an item from a list. + */ + _removeFromList = function(map, key, value) { + if (key != null) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete (l[i]); + l.splice(i, 1); + return true; + } + } + } + return false; + }, + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }, + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + _setVisible = function(el, state) { + _operation(_getAttribute(el, "id"), function(jpc) { + jpc.canvas.style.display = state; + }); + }, + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }, + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + _toggleVisible = function(elId) { + _operation(elId, function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }, + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return; + } + if (recalc || offset == null) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + } + }, + +/** + * gets an id for the given element, creating and setting one if + * necessary. + */ + _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else + id = "jsPlumb_" + _timestamp(); + _setAttribute(ele, "id", id); + } + return id; + }, + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'jsPlumb function failed : ' + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'wrapped function failed : ' + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = "_jsPlumb_connector"; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = "_jsPlumb_endpoint"; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = "_jsPlumb_overlay"; + + this.Anchors = {}; + + this.Connectors = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Endpoints = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Overlays = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + +// ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***************************************** + /* + * Function: bind + * Bind to an event on jsPlumb. + * + * Parameters: + * event - the event to bind. Available events on jsPlumb are: + * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback. + * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback. + * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback. + * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback. + * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback. + * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback. + * + * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event. + */ + + /* + * Function: clearListeners + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + +// *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *********************************************************** + + /* + Function: addEndpoint + + Adds an to a given element or elements. + + Parameters: + + el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + params - Object containing Endpoint constructor arguments. For more information, see . + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some + shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in + this object are anything that 'params' can contain. See . + + Returns: + The newly created , if el referred to a single element. Otherwise, an array of newly created s. + + See Also: + + */ + this.addEndpoint = function(el, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + + // YUI wrapper + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + var _el = _getElementObject(inputs[i]), id = _getId(_el); + p.source = _el; + _updateOffset({ elId : id }); + var e = _newEndpoint(p); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + results.push(e); + } + + return results.length == 1 ? results[0] : results; + }; + + /* + Function: addEndpoints + Adds a list of s to a given element or elements. + + Parameters: + target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See 's constructor documentation. + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created s, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(el, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + Array.prototype.push.apply(results, _currentInstance.addEndpoint(el, endpoints[i], referenceParams)); + } + return results; + }; + + /* + Function: animate + This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating + the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // onComplete repaints, just to make sure everything looks good at the end of the animation. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /* + Function: connect + Establishes a between two elements (or s, which are themselves registered to elements). + + Parameters: + params - Object containing constructor arguments for the Connection. See 's constructor documentation. + referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of + Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection. + + Returns: + The newly created . + */ + this.connect = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + // dynamic anchors. backwards compatibility here: from 1.2.6 onwards you don't need to specify "dynamicAnchors". the fact that some anchor consists + // of multiple definitions is enough to tell jsPlumb you want it to be dynamic. + if (_p.dynamicAnchors) { + // these can either be an array of anchor coords, which we will use for both source and target, or an object with {source:[anchors], target:[anchors]}, in which + // case we will use a different set for each element. + var a = _p.dynamicAnchors.constructor == Array; + var sa = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.source)); + var ta = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.target)); + _p.anchors = [sa,ta]; + } + + var jpc = _newConnection(_p); + // add to list of connections (by scope). + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + _currentInstance.fire("jsPlumbConnection", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + // force a paint + _draw(jpc.source); + + return jpc; + }; + + /* + Function: deleteEndpoint + Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too) + + Parameters: + object - either an object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElement(endpoint.canvas, endpoint.parent); + // remove from endpointsbyElement + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + delete endpoint; + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every , and their associated s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc) { + _currentInstance.fire("jsPlumbConnectionDetached", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + }; + + /* + Function: detach + Detaches and then removes a . Takes either (source, target) (the old way, maintained for backwards compatibility), or a params + object with various possible values. + + Parameters: + source - id or element object of the first element in the Connection. + target - id or element object of the second element in the Connection. + params - a JS object containing the same parameters as you pass to jsPlumb.connect. If this is present then neither source nor + target should be present; it should be the only argument to the method. See the docs for 's constructor for information +about the parameters allowed in the params object. + Returns: + true if successful, false if not. + */ + this.detach = function(source, target) { + if (arguments.length == 2) { + var s = _getElementObject(source), sId = _getId(s); + var t = _getElementObject(target), tId = _getId(t); + _operation(sId, function(jpc) { + if ((jpc.sourceId == sId && jpc.targetId == tId) || (jpc.targetId == sId && jpc.sourceId == tId)) { + _removeElements(jpc.connector.getDisplayElements(), jpc.parent); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + // this is the new version of the method, taking a JS object like + // the connect method does. + else if (arguments.length == 1) { + // TODO investigate whether or not this code still works when a user has supplied their own subclass of Connection. i suspect it may not. + if (arguments[0].constructor == Connection) { + arguments[0].endpoints[0].detachFrom(arguments[0].endpoints[1]); + } + else if (arguments[0].connection) { + arguments[0].connection.endpoints[0].detachFrom(arguments[0].connection.endpoints[1]); + } + else { + var _p = jsPlumb.extend( {}, source); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1])); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source); + var targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElements(jpc.connector.getDisplayElements(), jpc.parent); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + } + } + }; + + /* + Function: detachAll + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + + Returns: + void + */ + this.detachAllConnections = function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + }; + + /** + * @deprecated Use detachAllConnections instead. this will be removed in jsPlumb 1.3. + */ + this.detachAll = this.detachAllConnections; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + /** + * @deprecated use detachEveryConnection instead. this will be removed in jsPlumb 1.3. + */ + this.detachEverything = this.detachEveryConnection; + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of you library's draggable method so that jsPlumb can setup the appropriate callbacks. Your underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method, + * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the + * default scope). If multiple scopes are passed in, the return value will be a map of { scope -> [ connection... ] }. + * + * Parameters + * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list + * of connections that are in the given scope. + * options - if the argument is a JS object, you can specify a finer-grained filter: + * + * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes. + * - *source* either a string representing an element id, or a selector. constrains the result to connections having this source. + * - *target* either a string representing an element id, or a selector. constrains the result to connections having this target. + * + */ + this.getConnections = function(options) { + if (!options) { + options = {}; + } else if (options.constructor == String) { + options = { "scope": options }; + } + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') + r.push(input); + else + r = input; + } + return r; + }; + var scope = options.scope || jsPlumb.getDefaultScope(), + scopes = prepareList(scope), + sources = prepareList(options.source), + targets = prepareList(options.target), + filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }, + results = scopes.length > 1 ? {} : [], + _addOne = function(scope, obj) { + if (scopes.length > 1) { + var ss = results[scope]; + if (ss == null) { + ss = []; results[scope] = ss; + } + ss.push(obj); + } else results.push(obj); + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + _addOne(i, c); + } + } + } + return results; + }; + + /* + * Function: getAllConnections + * Gets all connections, as a map of { scope -> [ connection... ] }. + */ + this.getAllConnections = function() { + return connectionsByScope; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /** + * Function:getEndpoints + * Gets the list of Endpoints for a given selector, or element id. + * @param el + * @return + */ + this.getEndpoints = function(el) { + return endpointsByElement[_getId(el)]; + }; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + + this.appendElement = _appendElement; + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + + Returns: + void + */ + this.hide = function(el) { + _setVisible(el, "none"); + }; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + if (!initialized) { + _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run. + + var _bind = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && _mouseEventsEnabled && renderMode == jsPlumb.CANVAS) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + var t = c[i].connector[event](e); + if (t) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i].endpoint[event](e)) return; + } + } + } + }); + }; + _bind("click"); + _bind("dblclick"); + _bind("mousemove"); + _bind("mousedown"); + _bind("mouseup"); + + initialized = true; + _currentInstance.fire("ready"); + } + }; + + this.jsPlumbUIComponent = jsPlumbUIComponent; + this.EventGenerator = EventGenerator; + + /* + * Creates an anchor with the given params. + * + * You do not need to use this method. It is exposed because of the way jsPlumb is + * split into three scripts; this will change in the future. + * + * x - the x location of the anchor as a fraction of the + * total width. y - the y location of the anchor as a fraction of the + * total height. xOrientation - value indicating the general direction a + * connection from the anchor should go in, in the x direction. + * yOrientation - value indicating the general direction a connection + * from the anchor should go in, in the y direction. xOffset - a fixed + * offset that should be applied in the x direction that should be + * applied after the x position has been figured out. optional. defaults + * to 0. yOffset - a fixed offset that should be applied in the y + * direction that should be applied after the y position has been + * figured out. optional. defaults to 0. + * -- OR -- + * + * params - {x:..., y:..., xOrientation etc } + * -- OR FROM 1.2.4 --- + * + * name - the name of some Anchor in the _currentInstance.Anchors array. + * -- OR FROM 1.2.4 --- + * + * coords - a list of coords for the anchor, like you would pass to + * jsPlumb.makeAnchor (eg [0.5,0.5,0,-1] - an anchor in the center of + * some element, oriented towards the top of the screen) + * -- OR FROM 1.2.4 --- + * + * anchor - an existing anchor. just gets passed back. it's handy + * internally to have this functionality. + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed + // in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards + // compatibility if we are given only one value we assume it's a + // call in the old form. + if (arguments.length == 0) return null; + var params = {}; + if (arguments.length == 1) { + var specimen = arguments[0]; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; + // is it the name of an anchor type? + else if (typeof specimen == "string") return _currentInstance.Anchors[arguments[0]](); + // is it an array of coordinates? + else if (specimen.constructor == Array) { + if (specimen[0].constructor == Array || specimen[0].constructor == String) + return new DynamicAnchor(specimen); + else + return jsPlumb.makeAnchor.apply(this, specimen); + } + // last we try the backwards compatibility stuff. + else if (typeof arguments[0] == "object") jsPlumb.extend(params, x); + } else { + params = { x : x, y : y }; + if (arguments.length >= 4) params.orientation = [ arguments[2], arguments[3] ]; + if (arguments.length == 6) params.offsets = [ arguments[4], arguments[5] ]; + } + var a = new Anchor(params); + a.clone = function() { + return new Anchor(params); + }; + return a; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types) { + var r = []; + for ( var i = 0; i < types.length; i++) + if (typeof types[i] == "string") + r.push(_currentInstance.Anchors[types[i]]()); + else if (types[i].constructor == Array) + r.push(jsPlumb.makeAnchor(types[i])); + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /** + * Function: makeTarget + * Makes some DOM element a Connection target, allowing you to drag connections to it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a target. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * scope optional. scope for the drop zone. + * dropOptions optional. same stuff as you would pass to dropOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to false. whether or not to delete + * any Endpoints created by a connection to this target if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + this.makeTarget = function(el, params, referenceParams) { + + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + var jpcl = jsPlumb.CurrentLibrary, + scope = p.scope || _currentInstance.Defaults.Scope, + deleteEndpointsOnDetach = p.deleteEndpointsOnDetach || false, + _doOne = function(_el) { + var dropOptions = jsPlumb.extend({}, p.dropOptions || {}); + var _drop = function() { + var draggable = _getElementObject(jpcl.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + // restore the original scope if necessary (issue 57) + scope = _getAttribute(draggable, "originalScope"); + + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + // get the connection, to then get its endpoint + var jpc = floatingConnections[id], + source = jpc.endpoints[0], + _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : null, + // make a new Endpoint + newEndpoint = jsPlumb.addEndpoint(_el, _endpoint); + + var c = jsPlumb.connect({ + source:source, + target:newEndpoint, + scope:scope + }); + if (deleteEndpointsOnDetach) + c.endpointToDeleteOnDetach = newEndpoint; + }; + + var dropEvent = jpcl.dragEvents['drop']; + dropOptions["scope"] = dropOptions["scope"] || scope; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop); + + jpcl.initDroppable(_el, dropOptions); + }; + + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements drop targets. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeTargets = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeTarget(els[i], params, referenceParams); + } + }; + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + for ( var i in ebe) + _currentInstance.deleteEndpoint(ebe[i]); + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + this.deleteEveryEndpoint(); + this.clearListeners(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + + Parameters: + value - whether or not to automatically repaint when the window is resized. + + Returns: void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setDraggableByDefault + * Sets whether or not elements are draggable by default. Default for this is true. + * + * Parameters: + * draggable - value to set + * + * Returns: + * void + */ + this.setDraggableByDefault = function(draggable) { + _draggableByDefault = draggable; + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction + * Sets the function to fire when the window size has changed and a repaint was fired. + * + * Parameters: + * f - Function to execute. + * + * Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: setMouseEventsEnabled + * Sets whether or not mouse events are enabled. Default is true. + * + * Parameters: + * enabled - whether or not mouse events should be enabled. + * + * Returns: + * void + */ + this.setMouseEventsEnabled = function(enabled) { + _mouseEventsEnabled = enabled; + }; + + /* + * Constant for use with the setRenderMode method + */ + this.CANVAS = "canvas"; + + /* + * Constant for use with the setRenderMode method + */ + this.SVG = "svg"; + + this.VML = "vml"; + + /* + * Function: setRenderMode + * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that + * what you asked for is not supported (and that VML is). If you asked for VML but the browser does + * not support it, jsPlumb uses SVG. + * + * Returns: + * the render mode that jsPlumb set, which of course may be different from that requested. + */ + this.setRenderMode = function(mode) { + if (mode) + mode = mode.toLowerCase(); + else + return; + if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); + // now test we actually have the capability to do this. + if (mode === jsPlumb.CANVAS && canvasAvailable) + renderMode = jsPlumb.CANVAS; + else if (mode === jsPlumb.SVG && svgAvailable) + renderMode = jsPlumb.SVG; + else if (vmlAvailable) + renderMode = jsPlumb.VML; + + return renderMode; + }; + + this.getRenderMode = function() { return renderMode; }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * + * Returns: + * void + */ + this.show = function(el) { + _setVisible(el, "block"); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId, + makeAnchor:self.makeAnchor, + makeDynamicAnchor:self.makeDynamicAnchor + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload + * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element. + * + * Returns: + * void + */ + this.unload = function() { + delete endpointsByElement; + delete endpointsByUUID; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + delete canvasList; + }; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + if (timestamp && timestamp === self.timestamp) { + return lastReturnValue; + } + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + //* + var container = element ? element.parent : null; + if (container != null) { + var parentPosition = element.canvas.parentNode.style.position; + var parentIsPositioned = parentPosition != null && parentPosition != "" && parentPosition != "static"; + if (parentIsPositioned) { + console.log("positioning"); + var o = _getOffset(element.canvas.parentNode); + lastReturnValue[0] = lastReturnValue[0] - o.left; + lastReturnValue[1] = lastReturnValue[1] - o.top; + } + } + //*/ + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + * + * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(_getElementObject(refCanvas)); + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + var orientation = null; + var _lastResult = null; + + this.compute = function(params) { + var xy = params.xy, element = params.element; + var result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + //* + var container = element ? element.parent : null; + if (container != null) { + var parentPosition = element.canvas.parentNode.style.position; + var parentIsPositioned = parentPosition != null && parentPosition != "" && parentPosition != "static"; + if (parentIsPositioned) { + var o = _getOffset(element.canvas.parentNode); + result[0] = result[0] - o.left; + result[1] = result[1] - o.top; + } + } + //*/ + + _lastResult = result; + return result; + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { orientation = anchor.getOrientation(); }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchors is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = []; + var _convert = function(anchor) { return anchor.constructor == Anchor ? anchor: jsPlumb.makeAnchor(anchor); }; + for (var i = 0; i < anchors.length; i++) _anchors[i] = _convert(anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null; + var _curIndex = _anchors.length > 0 ? 0 : -1; + this.locked = false; + var self = this; + + // helper method to calculate the distance between the centers of the two elements. + var _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }; + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + var _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + + var pos = _curAnchor.compute(params); + return pos; + }; + + this.getCurrentLocation = function() { + var cl = _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + return cl; + }; + + this.getOrientation = function() { return _curAnchor != null ? _curAnchor.getOrientation() : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * overlays - Optional array of Overlay definitions to appear on this Connection. + */ + var Connection = function(params) { + + jsPlumbUIComponent.apply(this, arguments); + // ************** get the source and target and register the connection. ******************* + var self = this; + var visible = true; + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none"; + }; + var id = new String('_jsplumb_c_' + (new Date()).getTime()); + this.getId = function() { return id; }; + this.parent = params.parent; + /** + Property: source + The source element for this Connection. + */ + this.source = _getElementObject(params.source); + /** + Property:target + The target element for this Connection. + */ + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. + if (params.sourceEndpoint) this.source = params.sourceEndpoint.getElement(); + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + /* + * Property: sourceId + * Id of the source element in the connection. + */ + this.sourceId = _getAttribute(this.source, "id"); + /* + * Property: targetId + * Id of the target element in the connection. + */ + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + + /** + * implementation of abstract method in EventGenerator + * @return list of attached elements. in our case, a list of Endpoints. + */ + this.getAttachedElements = function() { + return self.endpoints; + }; + + /** + * implementation of abstract method in EventGenerator + */ + var srcWhenMouseDown = null, targetWhenMouseDown = null; + this.savePosition = function() { + srcWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.source)); + targetWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.target)); + }; + + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams) { + if (anchorParams) + return jsPlumb.makeAnchor(anchorParams); + }; + var prepareEndpoint = function(existing, index, params, element, connectorPaintStyle, connectorHoverPaintStyle) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] + || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint; + + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified. + if (es.fillStyle == null && connectorPaintStyle != null) + es.fillStyle = connectorPaintStyle.strokeStyle; + + // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does: + //* + if (es.outlineColor == null && connectorPaintStyle != null) + es.outlineColor = connectorPaintStyle.outlineColor; + if (es.outlineWidth == null && connectorPaintStyle != null) + es.outlineWidth = connectorPaintStyle.outlineWidth; + //*/ + + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure? + if (connectorHoverPaintStyle != null) { + if (ehs == null) ehs = {}; + if (ehs.fillStyle == null) { + ehs.fillStyle = connectorHoverPaintStyle.strokeStyle; + } + } + var a = params.anchors ? params.anchors[index] : _makeAnchor(_currentInstance.Defaults.Anchors[index]) || _makeAnchor(jsPlumb.Defaults.Anchors[index]) || _makeAnchor(_currentInstance.Defaults.Anchor) || _makeAnchor(jsPlumb.Defaults.Anchor); + var u = params.uuids ? params.uuids[index] : null; + var e = _newEndpoint( { paintStyle : es, hoverPaintStyle:ehs, endpoint : ep, connections : [ self ], uuid : u, anchor : a, source : element }); + self.endpoints[index] = e; + + if (params.drawEndpoints === false) e.setVisible(false, true, true); + + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source, params.paintStyle, params.hoverPaintStyle); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target, params.paintStyle, params.hoverPaintStyle); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + /* + * Function: setConnector + * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method - the same + * thing that you would set as the 'connector' property on a jsPlumb.connect call. + * + * Parameters: + * connector - Connector definition + */ + this.setConnector = function(connector, doNotRepaint) { + if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent); + var connectorArgs = { _jsPlumb:self._jsPlumb, parent:params.parent, cssClass:params.cssClass }; + if (connector.constructor == String) + this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand. + else if (connector.constructor == Array) + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs)); + this.canvas = this.connector.canvas; + var _mouseDown = false, _mouseWasDown = false, _mouseDownAt = null; + // add mouse events + this.connector.bind("click", function(con, e) { + _mouseWasDown = false; + self.fire("click", self, e); + }); + this.connector.bind("dblclick", function(con, e) { _mouseWasDown = false;self.fire("dblclick", self, e); }); + this.connector.bind("mouseenter", function(con, e) { + if (!self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(true); + } + self.fire("mouseenter", self, e); + } + }); + this.connector.bind("mouseexit", function(con, e) { + if (self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(false); + } + self.fire("mouseexit", self, e); + } + }); + this.connector.bind("mousedown", function(con, e) { + _mouseDown = true; + _mouseDownAt = jsPlumb.CurrentLibrary.getPageXY(e); + self.savePosition(); + }); + this.connector.bind("mouseup", function(con, e) { + _mouseDown = false; + if (self.connector == _connectionBeingDragged) _connectionBeingDragged = null; + }); + + if (!doNotRepaint) self.repaint(); + }; + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc) + */ + + self.setConnector(this.endpoints[0].connector || + this.endpoints[1].connector || + params.connector || + _currentInstance.Defaults.Connector || + jsPlumb.Defaults.Connector, true); + + this.setPaintStyle(this.endpoints[0].connectorStyle || + this.endpoints[1].connectorStyle || + params.paintStyle || + _currentInstance.Defaults.PaintStyle || + jsPlumb.Defaults.PaintStyle, true); + + this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || + this.endpoints[1].connectorHoverStyle || + params.hoverPaintStyle || + _currentInstance.Defaults.HoverPaintStyle || + jsPlumb.Defaults.HoverPaintStyle, true); + + this.paintStyleInUse = this.paintStyle; + + /* + * Property: overlays + * List of Overlays for this Connection. + */ + this.overlays = []; + var _overlays = params.overlays || _currentInstance.Defaults.Overlays; + if (_overlays) { + for (var i = 0; i < _overlays.length; i++) { + var o = _overlays[i], _newOverlay = null, _overlayEvents = null; + if (o.constructor == Array) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0]; + var p = jsPlumb.CurrentLibrary.extend({connection:self, _jsPlumb:_currentInstance}, o[1]); // make a copy of the object so as not to mess up anyone else's reference... + if (o.length == 3) jsPlumb.CurrentLibrary.extend(p, o[2]); + _newOverlay = new jsPlumb.Overlays[renderMode][type](p); + if (p.events) { + for (var evt in p.events) { + _newOverlay.bind(evt, p.events[evt]); + } + } + } else if (o.constructor == String) { + _newOverlay = new jsPlumb.Overlays[renderMode][o]({connection:self, _jsPlumb:_currentInstance}); + } else { + _newOverlay = o; + } + + + + this.overlays.push(_newOverlay); + } + } + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Connection. + * + * Parameters: + * event - the event to bind. Available events on a Connection are: + * - *click* : notification that a Connection was clicked. + * - *dblclick* : notification that a Connection was double clicked. + * - *mouseenter* : notification that the mouse is over a Connection. + * - *mouseexit* : notification that the mouse exited a Connection. + * + * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Connection's paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Connection will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Connection. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + this.addOverlay = function(overlay) { self.overlays.push(overlay); }; + + /** + * Function: removeAllOverlays + * Removes all overlays from the Connection, and then repaints. + */ + this.removeAllOverlays = function() { + self.overlays.splice(0, self.overlays.length); + self.repaint(); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + this.removeOverlay = function(overlayId) { + var idx = -1; + for (var i = 0; i < self.overlays.length; i++) { + if (overlayId === self.overlays[i].id) { + idx = i; + break; + } + } + if (idx != -1) self.overlays.splice(idx, 1); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + this.removeOverlays = function() { + for (var i = 0; i < arguments.length; i++) + self.removeOverlay(arguments[i]); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + this.labelStyle = params.labelStyle || _currentInstance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + if (this.label) { + this.overlays.push(new jsPlumb.Overlays[renderMode].Label( { + cssClass:params.cssClass, + labelStyle : this.labelStyle, + label : this.label, + connection:self, + _jsPlumb:_currentInstance + })); + } + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String or a Function that returns a String. + */ + this.setLabel = function(l) { + self.label = l; + _currentInstance.repaint(self.source); + }; + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var otherOffset = offsets[this.targetId]; + var otherWH = sizes[this.targetId]; + var anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1] + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0] + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc }); + + /* + * Paints the Connection. Not exposed for public usage. + * + * Parameters: + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp; + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = false; + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }); + _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + var sAnchorP = this.endpoints[sIdx].anchor.getCurrentLocation(); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.getCurrentLocation(); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + + /* paint overlays*/ + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + var s = o.computeMaxSize(self.connector); + if (s > maxSize) + maxSize = s; + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, self.paintStyleInUse.lineWidth, maxSize); + + self.connector.paint(dim, self.paintStyleInUse); + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim); + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function() { + this.paint({ elId : this.sourceId, recalc : true }); + }; + + _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide + // whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + + // just to make sure the UI gets initialised fully on all browsers. + self.repaint(); + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1 + * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't + * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you + * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or + * 'target' Endpoints for Connections. + * + * + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * connections - optional list of Connections to configure the Endpoint with. + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + params = params || {}; + var self = this; +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Endpoint. + * + * Parameters: + * event - the event to bind. Available events on an Endpoint are: + * - *click* : notification that a Endpoint was clicked. + * - *dblclick* : notification that a Endpoint was double clicked. + * - *mouseenter* : notification that the mouse is over a Endpoint. + * - *mouseexit* : notification that the mouse exited a Endpoint. + * + * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Endpoint's paint style and then repaints the Endpoint. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Endpoint will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Endpoint. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + var visible = true; + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + var id = new String('_jsplumb_e_' + (new Date()).getTime()); + this.getId = function() { return id; }; + if (params.dynamicAnchors) + self.anchor = new DynamicAnchor(jsPlumb.makeAnchors(params.dynamicAnchors)); + else + self.anchor = params.anchor ? jsPlumb.makeAnchor(params.anchor) : params.anchors ? jsPlumb.makeAnchor(params.anchors) : jsPlumb.makeAnchor("TopCenter"); + var _endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot", + endpointArgs = { _jsPlumb:self._jsPlumb, parent:params.parent }; + if (_endpoint.constructor == String) + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint](endpointArgs); + else if (_endpoint.constructor == Array) + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint[0]](jsPlumb.extend(_endpoint[1], endpointArgs )); + else _endpoint = _endpoint.clone(); + self.endpoint = _endpoint; + this.endpoint.bind("click", function(e) { self.fire("click", self, e); }); + this.endpoint.bind("dblclick", function(e) { self.fire("dblclick", self, e); }); + this.setPaintStyle(params.paintStyle || + params.style || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyle, true); + this.setHoverPaintStyle(params.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyle, true); + this.paintStyleInUse = this.paintStyle; + this.connectorStyle = params.connectorStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.parent = params.parent; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + var _element = params.source, + _uuid = params.uuid, + floatingEndpoint = null, + inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + this.element = _element; + var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + /* + * Property: canvas + * The Endpoint's Canvas. + */ + this.canvas = this.endpoint.canvas; + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + this.connections = params.connections || []; + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + var _reattach = params.reattach || false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + + // this avoids a circular loop + if (!ignoreTarget) { + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + t.detach(connection, true); + // check connection to see if we want to delete the other endpoint. + // if the user uses makeTarget to make some element a target for connections, + // it is possible that they will have set 'endpointToDeleteOnDetach': when + // you make a connection to an element that acts as a target (note: NOT an + // Endpoint; just some div as a target), Endpoints are created for that + // connection. so if you then delete that Connection, it is feasible you + // will want these auto-generated endpoints to be removed. + if (connection.endpointToDeleteOnDetach && connection.endpointToDeleteOnDetach.connections.length == 0) + jsPlumb.deleteEndpoint(connection.endpointToDeleteOnDetach); + } + _removeElements(connection.connector.getDisplayElements(), connection.parent); + _removeFromList(connectionsByScope, connection.scope, connection); + if(!ignoreTarget) fireDetachEvent(connection); + } + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + */ + this.detachAll = function() { + while (self.connections.length > 0) { + self.detach(self.connections[0]); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + */ + this.detachFrom = function(targetEndpoint) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + c[i].setHover(false); + self.detach(c[i]); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + return _newEndpoint( { anchor : self.anchor, source : _element, paintStyle : this.paintStyle, endpoint : _endpoint }); + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + return (self.connections.length < _maxConnections) ? null : self.connections[0]; + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + /* + * Function: setStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to + * setStyle and deprecate it if so. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + * + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + /* + * Function: paint + * Paints the Endpoint, recalculating offset and anchor positions if necessary. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + + params = params || {}; + var timestamp = params.timestamp; + if (!timestamp || self.timestamp !== timestamp) { + var ap = params.anchorPoint, canvas = params.canvas, connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + var xy = params.offset || offsets[_elementId]; + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (self.anchor.isDynamic) { + if (self.connections.length > 0) { + var c = self.connections[0]; + var oIdx = c.endpoints[0] == self ? 1 : 0; + var oId = oIdx == 0 ? c.sourceId : c.targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + } + ap = self.anchor.compute(anchorParams); + } + + var d = _endpoint.compute(ap, self.anchor.getOrientation(), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + _endpoint.paint(d, self.paintStyleInUse, self.anchor); + + self.timestamp = timestamp; + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + var start = function() { + jpc = self.connectorSelector(); + if (self.isFull() && !dragAllowedWhenFull) return false; + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + n.style.position = "absolute"; + var nE = _getElementObject(n); + _appendElement(n, self.parent); + // create and assign an id, and initialize the offset. + var id = _getId(nE); + + // set the offset of this div to be where 'inPlaceCopy' is, to start with. + var ipcoel = _getElementObject(inPlaceCopy.canvas), + ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel), + ipcs = jsPlumb.CurrentLibrary.getSize(ipcoel); + jsPlumb.CurrentLibrary.setOffset(n, ipco); + + _updateOffset( { elId : id }); + + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor( { reference : self.anchor, referenceCanvas : self.canvas }); + floatingEndpoint = _newEndpoint({ paintStyle : self.paintStyle, endpoint : _endpoint, anchor : floatingAnchor, source : nE }); + + if (jpc == null) { + self.anchor.locked = true; + // create a connection. one end is this endpoint, the + // other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : _getElementObject(_element), + target : _getElementObject(n), + anchors : [ self.anchor, floatingAnchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays + }); + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.connector.setHover(false); + } else { + existingJpc = true; + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.connector.setHover(false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas)); + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; // are we the source or the target? + + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas); + var dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // get a new, temporary scope, to use (issue 57) + var newScope = "scope_" + (new Date()).getTime(); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + // set the new, temporary scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(i, newScope); + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // TODO unregister on stop? or will floating endpoint's + // destruction be assured. + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be + // dragging the source at any time + // before this connection is either discarded or made into a + // permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var jpcl = jsPlumb.CurrentLibrary, + dragOptions = params.dragOptions || {}, + defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions), + startEvent = jpcl.dragEvents['start'], + stopEvent = jpcl.dragEvents['stop'], + dragEvent = jpcl.dragEvents['drag']; + + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], + function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + jsPlumb.CurrentLibrary.setOffset(n, _ui); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements( [ n, floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (_reattach) { + + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } else { + jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc); // the main endpoint will inform the floating endpoint + // to disconnect, and also post the detached event. + } + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElements(jpc.connector.getDisplayElements(), self.parent); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint(); + jpc.setHover(false); + jpc.repaint(); + jpc = null; + delete inPlaceCopy; + delete endpointsByElement[floatingEndpoint.elementId]; + floatingEndpoint = null; + delete floatingEndpoint; + + _currentInstance.currentlyDragging = false; + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas) { + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + var drop = function() { + var draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)); + var id = _getAttribute(draggable, "dragId"); + var elId = _getAttribute(draggable, "elId"); + + // restore the original scope if necessary (issue 57) + var scope = _getAttribute(draggable, "originalScope"); + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + var jpc = floatingConnections[id]; + + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // todo test that the target is not full. + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + if (!jpc.suspendedEndpoint) { + _addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + _currentInstance.fire("jsPlumbConnectionDetached", { + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }); + } + + jsPlumb.repaint(elId); + + _currentInstance.fire("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], + targetEndpoint : jpc.endpoints[1], + connection:jpc + }); + } + + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute( _getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + } + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute(_getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + } + }); + + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas)); + + return self; + }; + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + //if (_defaults) jsPlumb.extend(j.Defaults, _defaults); + return j; + }; + + var _curryAnchor = function(x,y,ox,oy) { + return function() { + return jsPlumb.makeAnchor(x,y,ox,oy); + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1); + + jsPlumb.Defaults.DynamicAnchors = function() { + return jsPlumb.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"]); + }; + jsPlumb.Anchors["AutoDefault"] = function() { return jsPlumb.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors()); }; + + +})(); +/* +* jsPlumb-defaults-1.3.0-RC1 +* +* Copyright 2010 - 2011 Simon Porritt http://jsplumb.org +* +* Triple licensed under the MIT, GPL2 and Beer licenses. +*/ + +(function() { + + + + /** + * + * Helper class to consume unused mouse events by components that are DOM elements and + * are used by all of the different rendering modes. + * + */ + jsPlumb.DOMElementComponent = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + // when render mode is canvas, these functions may be called by the canvas mouse handler. + // this component is safe to pipe this stuff to /dev/null. + this.mousemove = + this.dblclick = + this.click = + this.mousedown = + this.mouseup = function(e) { }; + }; + + /** + * Class: Connectors.Straight + * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + var currentPoints = null; + var _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + _dx = _tx - _sx, _dy = (_ty - _sy); + _m = _dy / _dx, _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + + return currentPoints; + }; + + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + var xp = _sx + (location * _dx); + var yp = (_m == Infinity || _m == -Infinity) ? _sy + (location * (_ty - _sy)) : (_m * xp) + _b; + return {x:xp, y:yp}; + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { return _m; }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + var orientation = distance > 0 ? 1 : -1; + var y = Math.abs(distance * Math.sin(_theta)); + if (_sy > _ty) y = y * -1; + var x = Math.abs(distance * Math.cos(_theta)); + if (_sx > _tx) x = x * -1; + return {x:p.x + (orientation * x), y:p.y + (orientation * y)}; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = self.gradientAtPoint(p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + }; + }; + + + /** + * Class:Connectors.Bezier + * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's + * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below. + */ + /** + * Function:Constructor + * + * Parameters: + * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line. + * Optional; defaults to 150. + * + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + params = params || {}; + this.majorAnchor = params.curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h; + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) + { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx); var minx2 = Math.min(_CP[0], _CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(_sx,_tx); var maxx2 = Math.max(_CP[0], _CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty); var miny2 = Math.min(_CP[1], _CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(_sy,_ty); var maxy2 = Math.max(_CP[1], _CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + return jsBezier.perpendicularToCurveAt(_makeCurve(), location, length, distance); + }; + + }; + + + /** + * Class: Connectors.Flowchart + * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments. + */ + /** + * Function: Constructor + * + * Parameters: + * stub - minimum length for the stub at each end of the connector. defaults to 30 pixels. + */ + jsPlumb.Connectors.Flowchart = function(params) { + params = params || {}; + var self = this, + minStubLength = params.stub || params.minStubLength /* bwds compat. */ || 30, + segments = [], + segmentGradients = [], + segmentProportions = [], + segmentLengths = [], + segmentProportionalLengths = [], + points = [], + swapX, + swapY, + /** + * recalculates the gradients of each segment, and the points at which the segments begin, proportional to the total length travelled + * by all the segments that constitute the connector. + */ + updateSegmentGradientsAndProportions = function(startX, startY, endX, endY) { + var total = 0; + for (var i = 0; i < segments.length; i++) { + var sx = i == 0 ? startX : segments[i][2], + sy = i == 0 ? startY : segments[i][3], + ex = segments[i][0], + ey = segments[i][1]; + + segmentGradients[i] = sx == ex ? Infinity : 0; + segmentLengths[i] = Math.abs(sx == ex ? ey - sy : ex - sx); + total += segmentLengths[i]; + } + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segmentLengths[i] / total; + segmentProportions[i] = [curLoc, (curLoc += (segmentLengths[i] / total)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0]; + var ly = segments.length == 0 ? sy : segments[segments.length - 1][1]; + segments.push([x, y, lx, ly]); + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment + * that contains the point which is 'location' distance along the entire path, where + * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths + * are made up of a list of segments, each of which contributes some fraction to + * the total length. + */ + findSegmentForLocation = function(location) { + var idx = segmentProportions.length - 1, inSegmentProportion = 0; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + segments = []; + segmentGradients = []; + segmentProportionalLengths = []; + segmentLengths = []; + segmentProportionals = []; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + offx = (lw / 2) + (minStubLength * 2), + offy = (lw / 2) + (minStubLength * 2), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(), + to = targetAnchor.orientation || targetAnchor.getOrientation(), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx, + h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy; + if (w < minWidth) { + offx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + offy += (minWidth - h) / 2; + h = minWidth; + } + var sx = swapX ? w-offx : offx, + sy = swapY ? h-offy : offy, + tx = swapX ? offx : w-offx , + ty = swapY ? offy : h-offy, + startStubX = sx + (so[0] * minStubLength), + startStubY = sy + (so[1] * minStubLength), + endStubX = tx + (to[0] * minStubLength), + endStubY = ty + (to[1] * minStubLength), + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2); + + x -= offx; y -= offy; + points = [x, y, w, h, sx, sy, tx, ty], extraPoints = []; + + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + if (so[0] == 0) { + var startStubIsBeforeEndStub = startStubY < endStubY; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } + } + else { + var startStubIsBeforeEndStub = startStubX < endStubX; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } + } + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentGradientsAndProportions(sx, sy, tx, ty); + + return points; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location) { + return self.pointAlongPathFrom(location, 0); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location) { + return segmentGradients[findSegmentForLocation(location)["index"]]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance) { + var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segmentLengths[s.index], m = segmentGradients[s.index]; + var e = { + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + return e; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = segmentGradients[p.segmentInfo.index]; + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + + }; + + }; + + // ********************************* END OF CONNECTOR TYPES ******************************************************************* + + // ********************************* ENDPOINT TYPES ******************************************************************* + + /** + * Class: Endpoints.Dot + * A round endpoint, with default radius 10 pixels. + */ + + /** + * Function: Constructor + * + * Parameters: + * + * radius - radius of the endpoint. defaults to 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + var self = this; + params = params || {}; + this.radius = params.radius || 10; + this.defaultOffset = 0.5 * this.radius; + this.defaultInnerRadius = this.radius / 3; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var r = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - r; + var y = anchorPoint[1] - r; + return [ x, y, r * 2, r * 2, r ]; + }; + }; + + /** + * Class: Endpoints.Rectangle + * A Rectangular Endpoint, with default size 20x20. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the endpoint. defaults to 20 pixels. + * height - height of the endpoint. defaults to 20 pixels. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + var self = this; + params = params || {}; + this.width = params.width || 20; + this.height = params.height || 20; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + return [ x, y, width, height]; + }; + }; + + /** + * Class: Endpoints.Image + * Draws an image as the Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * src - location of the image to use. + */ + jsPlumb.Endpoints.Image = function(params) { + + jsPlumb.DOMElementComponent.apply(this, arguments); + + var self = this, initialized = false; + this.img = new Image(); + self.ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.src || params.url; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + self.anchorPoint = anchorPoint; + if (self.ready) return [anchorPoint[0] - self.img.width / 2, anchorPoint[1] - self.img.height/ 2, self.img.width, self.img.height]; + else return [0,0,0,0]; + }; + + self.canvas = document.createElement("img"), initialized = false; + self.canvas.style["margin"] = 0; + self.canvas.style["padding"] = 0; + self.canvas.style["outline"] = 0; + self.canvas.style["position"] = "absolute"; + self.canvas.className = jsPlumb.endpointClass; + jsPlumb.appendElement(self.canvas, params.parent); + self.attachListeners(self.canvas, self); + + var actuallyPaint = function(d, style, anchor) { + if (!initialized) { + self.canvas.setAttribute("src", self.img.src); + initialized = true; + } + var width = self.img.width, + height = self.img.height, + x = self.anchorPoint[0] - (width/2), + y = self.anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(self.canvas, x, y, width, height); + }; + + this.paint = function(d, style, anchor) { + if (self.ready) { + actuallyPaint(d, style, anchor); + } + else { + window.setTimeout(function() { + self.paint(d, style, anchor); + }, 200); + } + }; + }; + + /** + * Class: Endpoints.Blank + * An Endpoint that paints nothing on the screen, and cannot be interacted with using the mouse. There are no constructor parameters for this Endpoint. + */ + jsPlumb.Endpoints.Blank = function() { + jsPlumb.DOMElementComponent.apply(this, arguments); + this.compute = function() { + return [0,0,0,0]; + }; + + self.canvas = document.createElement("div"); + this.paint = function() { }; + }; + + /** + * Class: Endpoints.Triangle + * A triangular Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the triangle's base. defaults to 55 pixels. + * height - height of the triangle from base to apex. defaults to 55 pixels. + */ + jsPlumb.Endpoints.Triangle = function(params) { + params = params || { }; + params.width = params.width || 55; + param.height = params.height || 55; + this.width = params.width; + this.height = params.height; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + return [ x, y, width, height ]; + }; + }; +// ********************************* END OF ENDPOINT TYPES ******************************************************************* + + +// ********************************* OVERLAY DEFINITIONS *********************************************************************** + + /** + * Class: Overlays.Arrow + * + * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length + * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction + * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line + * across the tail. + */ + /** + * Function: Constructor + * + * Parameters: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + params = params || {}; + var self = this; + this.length = params.length || 20; + this.width = params.width || 20; + this.id = params.id; + this.connection = params.connection; + var direction = (params.direction || 1) < 0 ? -1 : 1; + var paintStyle = params.paintStyle || { lineWidth:1 }; + this.loc = params.location == null ? 0.5 : params.location; + // how far along the arrow the lines folding back in come to. default is 62.3%. + var foldback = params.foldback || 0.623; + var _getFoldBackPoint = function(connector, loc) { + if (foldback == 0.5) return connector.pointOnPath(loc); + else { + var adj = 0.5 - foldback; // we calculate relative to the center + return connector.pointAlongPathFrom(loc, direction * self.length * adj); + } + }; + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + // this is the arrow head position + var hxy = connector.pointAlongPathFrom(self.loc, direction * (self.length / 2)); + // this is the center of the tail + var txy = connector.pointAlongPathFrom(self.loc, -1 * direction * (self.length / 2)), tx = txy.x, ty = txy.y; + // this is the tail vector + var tail = connector.perpendicularToPathAt(self.loc, self.width, -1 * direction * (self.length / 2)); + // this is the point the tail goes in to + var cxy = _getFoldBackPoint(connector, self.loc); + + // if loc = 1, then hxy should be flush with the element, or if direction == -1, the tail midpoint. + if (self.loc == 1) { + var lxy = connector.pointOnPath(self.loc); + // TODO determine why the 1.2.6 released version does not + // use 'direction' in the two equations below, yet both + // that and 1.3.0 still paint the arrows correctly. + var dx = (lxy.x - hxy.x) * direction, dy = (lxy.y - hxy.y) * direction; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + // if loc = 0, then tail midpoint should be flush with the element, or, if direction == -1, hxy should be. + if (self.loc == 0) { + var lxy = connector.pointOnPath(self.loc); + var tailMid = foldback > 1 ? cxy : { + x:tail[0].x + ((tail[1].x - tail[0].x) / 2), + y:tail[0].y + ((tail[1].y - tail[0].y) / 2) + }; + var dx = (lxy.x - tailMid.x) * direction, dy = (lxy.y - tailMid.y) * direction; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x); + var maxx = Math.max(hxy.x, tail[0].x, tail[1].x); + var miny = Math.min(hxy.y, tail[0].y, tail[1].y); + var maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + var d = { hxy:hxy, tail:tail, cxy:cxy }, + strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle, + fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle, + lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth; + + self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions); + + return [ minx, maxx, miny, maxy]; + }; + }; + + /** + * Class: Overlays.PlainArrow + * + * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + /** + * Class: Overlays.Diamond + * + * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40; + var p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + + + /** + * Class: Overlays.Label + * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb + * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter + * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it + * puts on the Label's 'style' attribute, so the end result is the same. + */ + /** + * Function: Constructor + * + * Parameters: + * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes + * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle. + * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * labelStyle - (deprecated) js object containing style instructions for the label. defaults to jsPlumb.Defaults.LabelStyle. + * borderWidth - (deprecated) width of a border to paint. defaults to zero. + * borderStyle - (deprecated) strokeStyle to use when painting the border, if necessary. + * + */ + jsPlumb.Overlays.Label = function(params) { + jsPlumb.DOMElementComponent.apply(this, arguments); + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.labelStyle.font = this.labelStyle.font || "12px sans-serif"; + this.label = params.label; + this.connection = params.connection; + this.id = params.id; + var self = this; + var labelWidth = null, labelHeight = null, labelText = null, labelPadding = null; + this.location = params.location || 0.5; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var initialised = false, + labelText = null, + div = document.createElement("div"); + div.style["position"] = "absolute"; + div.style["textAlign"] = "center"; + div.style["cursor"] = "pointer"; + div.style["font"] = self.labelStyle.font; + div.style["color"] = self.labelStyle.color || "black"; + if (self.labelStyle.fillStyle) div.style["background"] = self.labelStyle.fillStyle;//_convertStyle(self.labelStyle.fillStyle, true); + if (self.labelStyle.borderWidth > 0) { + var dStyle = self.labelStyle.borderStyle ? self.labelStyle.borderStyle/*_convertStyle(self.labelStyle.borderStyle, true)*/ : "black"; + div.style["border"] = self.labelStyle.borderWidth + "px solid " + dStyle; + } + if (self.labelStyle.padding) div.style["padding"] = self.labelStyle.padding; + + var clazz = params["_jsPlumb"].overlayClass + " " + + (self.labelStyle.cssClass ? self.labelStyle.cssClass : + params.cssClass ? params.cssClass : ""); + + div.className = clazz; + + jsPlumb.appendElement(div, params.connection.parent); + jsPlumb.getId(div); + self.attachListeners(div, self); + this.paint = function(connector, d, connectorDimensions) { + if (!initialised) { + connector.appendDisplayElement(div); + self.attachListeners(div, connector); + initialised = true; + } + div.style.left = (connectorDimensions[0] + d.minx) + "px"; + div.style.top = (connectorDimensions[1] + d.miny) + "px"; + }; + + this.getTextDimensions = function(connector) { + labelText = typeof self.label == 'function' ? self.label(self) : self.label; + div.innerHTML = labelText.replace(/\r\n/g, "
"); + var de = jsPlumb.CurrentLibrary.getElementObject(div), + s = jsPlumb.CurrentLibrary.getSize(de); + return {width:s[0], height:s[1]}; + }; + + this.computeMaxSize = function(connector) { + var td = self.getTextDimensions(connector); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + var td = self.getTextDimensions(connector); + if (td.width != null) { + var cxy = connector.pointOnPath(self.location); + + var minx = cxy.x - (td.width / 2); + var miny = cxy.y - (td.height / 2); + + self.paint(connector, { + minx:minx, + miny:miny, + td:td, + cxy:cxy + }, connectorDimensions); + + return [minx, minx+td.width, miny, miny+td.height]; + } + else return [0,0,0,0]; + }; + }; + + // ********************************* END OF OVERLAY DEFINITIONS *********************************************************************** + + // ********************************* OVERLAY CANVAS RENDERERS*********************************************************************** + + // ********************************* END OF OVERLAY CANVAS RENDERERS *********************************************************************** +})();;(function() { + + // http://ajaxian.com/archives/the-vml-changes-in-ie-8 + // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/ + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + + var vmlAttributeMap = { + "stroke-linejoin":"joinstyle", + "joinstyle":"joinstyle", + "endcap":"endcap", + "miterlimit":"miterlimit" + }; + + if (document.createStyleSheet) { + + // this is the style rule for IE7/6: it uses a CSS class, tidy. + document.createStyleSheet().addRule(".jsplumb_vml", "behavior:url(#default#VML);position:absolute;"); + + // these are for VML in IE8. you have to explicitly call out which elements + // you're going to expect to support VML! + // + // try to avoid IE8. it is recommended you set X-UA-Compatible="IE=7" if you can. + // + document.createStyleSheet().addRule("jsplumb\\:textbox", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:oval", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:rect", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:stroke", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:shape", "behavior:url(#default#VML);position:absolute;"); + + // in this page it is also mentioned that IE requires the extra arg to the namespace + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either. + // var iev = document.documentMode; + //if (!iev || iev < 8) + document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); + //else + // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML"); + } + + var scale = 1000, + _atts = function(o, atts) { + for (var i in atts) { + // IE8 fix: setattribute does not work after an element has been added to the dom! + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + //o.setAttribute(i, atts[i]); + o[i] = atts[i]; + } + }, + _node = function(name, d, atts) { + atts = atts || {}; + var o = document.createElement("jsplumb:" + name); + o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml"; + _pos(o, d); + _atts(o, atts); + return o; + }, + _pos = function(o,d) { + o.style.left = d[0] + "px"; + o.style.top = d[1] + "px"; + o.style.width= d[2] + "px"; + o.style.height= d[3] + "px"; + o.style.position = "absolute"; + }, + _conv = function(v) { + return Math.floor(v * scale); + }, + _convertStyle = function(s, ignoreAlpha) { + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + + return o; + }, + _applyStyles = function(node, style, component) { + var styleToWrite = {}; + if (style.strokeStyle) { + styleToWrite["stroked"] = "true"; + styleToWrite["strokecolor"] =_convertStyle(style.strokeStyle, true); + styleToWrite["strokeweight"] = style.lineWidth + "px"; + } + else styleToWrite["stroked"] = "false"; + + if (style.fillStyle) { + styleToWrite["filled"] = "true"; + styleToWrite["fillcolor"] = _convertStyle(style.fillStyle, true); + } + else styleToWrite["filled"] = "false"; + + if(style["dashstyle"]) { + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = style["dashstyle"]; + } + else if (style["stroke-dasharray"] && style["lineWidth"]) { + var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",", + parts = style["stroke-dasharray"].split(sep), + styleToUse = ""; + for(var i = 0; i < parts.length; i++) { + styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep); + } + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = styleToUse; + } + + _atts(node, styleToWrite); + }, + /* + * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. + */ + VmlComponent = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }, + /* + * Base class for Vml connectors. extends VmlComponent. + */ + VmlConnector = function(params) { + var self = this; + self.strokeNode = null; + self.canvas = null; + VmlComponent.apply(this, arguments); + clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : ""); + this.paint = function(d, style, anchor) { + if (style != null) { + var path = self.getPath(d), p = { "path":path }; + + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + outlineStyle = { + strokeStyle:_convertStyle(style.outlineColor), + lineWidth:outlineStrokeWidth + }; + + if (self.bgCanvas == null) { + p["class"] = clazz; + self.bgCanvas = _node("shape", d, p); + jsPlumb.appendElement(self.bgCanvas, params.parent); + displayElements.push(self.bgCanvas); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.bgCanvas, d); + _atts(self.bgCanvas, p); + } + + _applyStyles(self.bgCanvas, outlineStyle, self); + } + + if (self.canvas == null) { + p["class"] = clazz; + self.canvas = _node("shape", d, p); + jsPlumb.appendElement(self.canvas, params.parent); + displayElements.push(self.canvas); + + self.attachListeners(self.canvas, self); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.canvas, d); + _atts(self.canvas, p); + } + + _applyStyles(self.canvas, style, self); + } + }; + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + self.canvas.parentNode.appendChild(el); + displayElements.push(el); + }; + }, + /* + * + * Base class for Vml Endpoints. extends VmlComponent. + * + */ + VmlEndpoint = function(params) { + VmlComponent.apply(this, arguments); + var vml = null, self = this; + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function(d, style, anchor) { + var p = { }; + + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (vml == null) { + p["class"] = jsPlumb.endpointClass; + vml = self.getVml([0,0, d[2], d[3]], p, anchor); + self.canvas.appendChild(vml); + self.attachListeners(vml, self); + } + else { + //p["coordsize"] = "1,1";//(d[2] * scale) + "," + (d[3] * scale); again, unsure. + _pos(vml, [0,0, d[2], d[3]]); + _atts(vml, p); + } + + _applyStyles(vml, style); + }; + }; + + jsPlumb.Connectors.vml.Bezier = function() { + jsPlumb.Connectors.Bezier.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Straight = function() { + jsPlumb.Connectors.Straight.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Flowchart = function() { + jsPlumb.Connectors.Flowchart.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l"; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]); + } + // finally draw a line to the end + p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e"; + return p; + }; + }; + + jsPlumb.Endpoints.vml.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("oval", d, atts); }; + }; + + jsPlumb.Endpoints.vml.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("rect", d, atts); }; + }; + + /* + * VML Image Endpoint is the same as the default image endpoint. + */ + jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image; + + /** + * placeholder for Blank endpoint in vml renderer. + */ + jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank; + + /** + * VML Label renderer. uses the default label renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label; + + var AbstractVmlArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + VmlComponent.apply(this, arguments); + var self = this, canvas = null, path =null; + var getPath = function(d, connectorDimensions) { + return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) + + " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + + " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + + " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + + " x e"; + }; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) { + var p = {}; + if (strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] =_convertStyle(strokeStyle, true); + } + if (lineWidth) p["strokeweight"] = lineWidth + "px"; + if (fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = fillStyle; + } + var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + w = Math.abs(xmax - xmin), + h = Math.abs(ymax - ymin), + dim = [xmin, ymin, w, h]; + + // for VML, we create overlays using shapes that have the same dimensions and + // coordsize as their connector - overlays calculate themselves relative to the + // connector (it's how it's been done since the original canvas implementation, because + // for canvas that makes sense). + p["path"] = getPath(d, connectorDimensions); + p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale); + + dim[0] = connectorDimensions[0]; + dim[1] = connectorDimensions[1]; + dim[2] = connectorDimensions[2]; + dim[3] = connectorDimensions[3]; + + if (canvas == null) { + //p["class"] = jsPlumb.overlayClass; // TODO currentInstance? + canvas = _node("shape", dim, p); + connector.appendDisplayElement(canvas); + self.attachListeners(canvas, connector); + } + else { + _pos(canvas, dim); + _atts(canvas, p); + } + }; + }; + + jsPlumb.Overlays.vml.Arrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.vml.PlainArrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.vml.Diamond = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/** + * SVG support for jsPlumb. + * + * things to investigate: + * + * gradients: https://developer.mozilla.org/en/svg_in_html_introduction + * css:http://tutorials.jenkov.com/svg/svg-and-css.html + * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath + * pointer events: https://developer.mozilla.org/en/css/pointer-events + * + */ +;(function() { + + var svgAttributeMap = { + "stroke-linejoin":"stroke-linejoin", + "joinstyle":"stroke-linejoin", + "stroke-dashoffset":"stroke-dashoffset" + }; + + var ns = { + svg:"http://www.w3.org/2000/svg", + xhtml:"http://www.w3.org/1999/xhtml" + }, + _attr = function(node, attributes) { + for (var i in attributes) + node.setAttribute(i, "" + attributes[i]); + }, + _node = function(name, attributes) { + var n = document.createElementNS(ns.svg, name); + attributes = attributes || {}; + attributes["version"] = "1.1"; + attributes["xmnls"] = ns.xhtml; + _attr(n, attributes); + return n; + }, + _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; }, + _convertStyle = function(s, ignoreAlpha) { + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + + return o; + }, + _clearGradient = function(parent) { + for (var i = 0; i < parent.childNodes.length; i++) { + if (parent.childNodes[i].tagName == "linearGradient" || parent.childNodes[i].tagName == "radialGradient") + parent.removeChild(parent.childNodes[i]); + } + }, + _updateGradient = function(parent, node, style, dimensions) { + var id = "jsplumb_gradient_" + (new Date()).getTime(); + // first clear out any existing gradient + _clearGradient(parent); + // this checks for an 'offset' property in the gradient, and in the absence of it, assumes + // we want a linear gradient. if it's there, we create a radial gradient. + // it is possible that a more explicit means of defining the gradient type would be + // better. relying on 'offset' means that we can never have a radial gradient that uses + // some default offset, for instance. + if (!style.gradient.offset) { + var g = _node("linearGradient", {id:id}); + parent.appendChild(g); + } + else { + var g = _node("radialGradient", { + id:id + }); + parent.appendChild(g); + } + + // the svg radial gradient seems to treat stops in the reverse + // order to how canvas does it. so we want to keep all the maths the same, but + // iterate the actual style declarations in reverse order, if the x indexes are not in order. + for (var i = 0; i < style.gradient.stops.length; i++) { + // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of + // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments. + // so, not too concerned about leaving this in for now. + var styleToUse = i; + if (dimensions.length == 8) + styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i; + else + styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i; + var stopColor = _convertStyle(style.gradient.stops[styleToUse][1], true); + var s = _node("stop", {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor}); + g.appendChild(s); + } + var applyGradientTo = style.strokeStyle ? "stroke" : "fill"; + node.setAttribute("style", applyGradientTo + ":url(#" + id + ")"); + }, + _applyStyles = function(parent, node, style, dimensions) { + + if (style.gradient) { + _updateGradient(parent, node, style, dimensions); + } + else { + // make sure we clear any existing gradient + _clearGradient(parent); + node.setAttribute("style", ""); + } + + node.setAttribute("fill", style.fillStyle ? _convertStyle(style.fillStyle, true) : "none"); + node.setAttribute("stroke", style.strokeStyle ? _convertStyle(style.strokeStyle, true) : "none"); + if (style.lineWidth) { + node.setAttribute("stroke-width", style.lineWidth); + } + + // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like + // the syntax in VML but is actually kind of nasty: values are given in the pixel + // coordinate space, whereas in VML they are multiples of the width of the stroked + // line, which makes a lot more sense. for that reason, jsPlumb is supporting both + // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from + // VML, which will be the preferred method. the code below this converts a dashstyle + // attribute given in terms of stroke width into a pixel representation, by using the + // stroke's lineWidth. + if(style["stroke-dasharray"]) { + node.setAttribute("stroke-dasharray", style["stroke-dasharray"]); + } + if (style["dashstyle"] && style["lineWidth"]) { + var sep = style["dashstyle"].indexOf(",") == -1 ? " " : ",", + parts = style["dashstyle"].split(sep), + styleToUse = ""; + parts.forEach(function(p) { + styleToUse += (Math.floor(p * style.lineWidth) + sep); + }); + node.setAttribute("stroke-dasharray", styleToUse); + } + + // extra attributes such as join type, dash offset. + for (var i in svgAttributeMap) { + if (style[i]) { + node.setAttribute(svgAttributeMap[i], style[i]); + } + } + }, + _decodeFont = function(f) { + var r = /([0-9].)(p[xt])\s(.*)/; + var bits = f.match(r); + return {size:bits[1] + bits[2], font:bits[3]}; + }; + + /* + * Base class for SVG components. + */ + var SvgComponent = function(cssClass, originalArgs, pointerEventsSpec) { + var self = this; + pointerEventsSpec = pointerEventsSpec || "all"; + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + self.canvas = null, self.path = null, self.svg = null; + + this.setHover = function() { }; + + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.sizeCanvas(self.canvas,0,0,1,1); + + var clazz = cssClass + " " + (originalArgs[0].cssClass || ""); + + self.svg = _node("svg", { + "style":"", + "width":0, + "height":0, + "pointer-events":pointerEventsSpec, + "class": clazz + }); + + jsPlumb.appendElement(self.canvas, originalArgs[0]["parent"]); + self.canvas.appendChild(self.svg); + + // TODO this displayElement stuff is common between all components, across all + // renderers. would be best moved to jsPlumbUIComponent. + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + this.paint = function(d, style, anchor) { + if (style != null) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + _attr(self.svg, { + "style":_pos([0,0,d[2], d[3]]), + "width": d[2], + "height": d[3] + }); + self._paint.apply(this, arguments); + } + }; + }; + + /* + * Base class for SVG connectors. + */ + var SvgConnector = function(params) { + var self = this; + SvgComponent.apply(this, [ params["_jsPlumb"].connectorClass, arguments, "none" ]); + this._paint = function(d, style) { + var p = self.getPath(d), a = { "d":p }, outlineStyle = null; + a["pointer-events"] = "all"; + + // outline style. actually means drawing an svg object underneath the main one. + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + outlineStyle = { + strokeStyle:_convertStyle(style.outlineColor), + lineWidth:outlineStrokeWidth + }; + + if (self.bgPath == null) { + self.bgPath = _node("path", a); + self.svg.appendChild(self.bgPath); + self.attachListeners(self.bgPath, self); + } + else { + _attr(self.bgPath, a); + } + + _applyStyles(self.svg, self.bgPath, outlineStyle, d); + } + + + if (self.path == null) { + self.path = _node("path", a); + self.svg.appendChild(self.path); + self.attachListeners(self.path, self); + } + else { + _attr(self.path, a); + } + + _applyStyles(self.svg, self.path, style, d); + }; + }; + + /* + * SVG Bezier Connector + */ + jsPlumb.Connectors.svg.Bezier = function(params) { + jsPlumb.Connectors.Bezier.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]; }; + }; + + /* + * SVG straight line Connector + */ + jsPlumb.Connectors.svg.Straight = function(params) { + jsPlumb.Connectors.Straight.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; }; + }; + + jsPlumb.Connectors.svg.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "M " + dimensions[4] + "," + dimensions[5]; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)]; + } + // finally draw a line to the end + p = p + " " + dimensions[6] + "," + dimensions[7]; + return p; + }; + }; + + /* + * Base class for SVG endpoints. + */ + var SvgEndpoint = function(params) { + var self = this; + SvgComponent.apply(this, [ params["_jsPlumb"].endpointClass, arguments, "all" ]); + this._paint = function(d, style) { + var s = jsPlumb.extend({}, style); + if (s.outlineColor) { + s.strokeWidth = s.outlineWidth; + s.strokeStyle = _convertStyle(s.outlineColor, true); + } + + if (self.node == null) { + self.node = self.makeNode(d, s); + self.svg.appendChild(self.node); + self.attachListeners(self.node, self); + } + _applyStyles(self.svg, self.node, s, d); + _pos(self.node, d); + }; + }; + + /* + * SVG Dot Endpoint + */ + jsPlumb.Endpoints.svg.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("circle", { + "cx" : d[2] / 2, + "cy" : d[3] / 2, + "r" : d[2] / 2 + }); + }; + }; + + /* + * SVG Rectangle Endpoint + */ + jsPlumb.Endpoints.svg.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("rect", { + "width":d[2], + "height":d[3] + }); + }; + }; + + /* + * SVG Image Endpoint is the default image endpoint. + */ + jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image; + /* + * Blank endpoint in svg renderer is the default Blank endpoint. + */ + jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank; + /* + * Label endpoint in svg renderer is the default Label endpoint. + */ + jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label; + + + var AbstractSvgArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + var self = this, path =null; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + } + + _attr(path, { + "d" : makePath(d), + stroke : strokeStyle ? strokeStyle : null, + fill : fillStyle ? fillStyle : null + }); + }; + var makePath = function(d) { + return "M" + d.hxy.x + "," + d.hxy.y + + " L" + d.tail[0].x + "," + d.tail[0].y + + " L" + d.cxy.x + "," + d.cxy.y + + " L" + d.tail[1].x + "," + d.tail[1].y + + " L" + d.hxy.x + "," + d.hxy.y; + }; + }; + + jsPlumb.Overlays.svg.Arrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.svg.PlainArrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.svg.Diamond = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();;(function() { + +// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS ******************************************************************* + + // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. + var _connectionBeingDragged = null, + _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }, + _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }, + _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }, + _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, + _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }, + _setOffset = function(el, o) { jsPlumb.CurrentLibrary.setOffset(el, o); }; + + /* + * Class:CanvasMouseAdapter + * Provides support for mouse events on canvases. + */ + var CanvasMouseAdapter = function() { + var self = this; + self.overlayPlacements = []; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + jsPlumb.EventGenerator.apply(this, arguments); + /** + * returns whether or not the given event is ojver a painted area of the canvas. + */ + this._over = function(e) { + var o = _getOffset(_getElementObject(self.canvas)), + pageXY = _pageXY(e), + x = pageXY[0] - o.left, y = pageXY[1] - o.top; + if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + return false; + }; + + var _mouseover = false; + var _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false; + var _nullSafeHasClass = function(el, clazz) { + return el != null && _hasClass(el, clazz); + }; + this.mousemove = function(e) { + var pageXY = _pageXY(e), clientXY = _clientXY(e), + ee = document.elementFromPoint(clientXY[0], clientXY[1]), + eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); + var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); + if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.fire("mouseenter", self, e); + return true; + } + // TODO here there is a remote chance that the overlay the mouse moved onto + // is actually not an overlay for the current component. a more thorough check would + // be to ensure the overlay belonged to the current component. + else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { + _mouseover = false; + self.fire("mouseexit", self, e); + } + self.fire("mousemove", self, e); + }; + + this.click = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); + self.fire("mousedown", self, e); + } + }; + + this.mouseup = function(e) { + //if (self == _connectionBeingDragged) _connectionBeingDragged = null; + _mouseDown = false; + self.fire("mouseup", self, e); + }; + }; + + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + jsPlumb.appendElement(canvas, params.parent); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + params["_jsPlumb"].getId(canvas, params.uuid); + + return canvas; + }; + + /** + * Class:CanvasConnector + * Superclass for Canvas Connector renderers. + */ + var CanvasConnector = jsPlumb.CanvasConnector = function(params) { + + CanvasMouseAdapter.apply(this, arguments); + + var _paintOneStyle = function(dim, aStyle) { + self.ctx.save(); + jsPlumb.extend(self.ctx, aStyle); + if (aStyle.gradient) { + var g = self.createGradient(dim, self.ctx); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + self.ctx.strokeStyle = g; + } + self._paint(dim); + self.ctx.restore(); + }; + + var self = this, + clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent + }); + self.ctx = self.canvas.getContext("2d"); + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + self.paint = function(dim, style) { + if (style != null) { + + jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); + + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + _paintOneStyle(dim, outlineStyle); + } + _paintOneStyle(dim, style); + } + }; + }; + + /** + * Class:CanvasEndpoint + * Superclass for Canvas Endpoint renderers. + */ + var CanvasEndpoint = function(params) { + var self = this; + CanvasMouseAdapter.apply(this, arguments); + var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent + }); + self.ctx = self.canvas.getContext("2d"); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + // _paintOneStyle(d, outlineStyle); + } + + self._paint.apply(this, arguments); + }; + }; + + jsPlumb.Endpoints.canvas.Dot = function(params) { + var self = this; + jsPlumb.Endpoints.Dot.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }; + var calculateAdjustments = function(gradient) { + var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius; + gradient.offset && (offsetAdjustment = parseValue(gradient.offset)); + gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius)); + return [offsetAdjustment, innerRadius]; + }; + this._paint = function(d, style, anchor) { + if (style != null) { + var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(); + jsPlumb.extend(ctx, style); + if (style.gradient) { + var adjustments = calculateAdjustments(style.gradient), + yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0], + xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0], + g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + ctx.beginPath(); + ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + } + }; + }; + + jsPlumb.Endpoints.canvas.Rectangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) { + + var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(); + jsPlumb.extend(ctx, style); + + /* canvas gradient */ + if (style.gradient) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, d[2], d[3]); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + jsPlumb.Endpoints.canvas.Triangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Triangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) + { + var width = d[2], height = d[3], x = d[0], y = d[1]; + + var ctx = self.canvas.getContext('2d'); + var offsetX = 0, offsetY = 0, angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = style.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + /* + * Canvas Image Endpoint: uses the default version, which creates an tag. + */ + jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image; + + /* + * Blank endpoint in all renderers is just the default Blank endpoint. + */ + jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank; + + /* + * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Bezier = function() { + var self = this; + jsPlumb.Connectors.Bezier.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO i doubt this handles the case that source and target are swapped. + this.createGradient = function(dim, ctx, swap) { + return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + /* + * Canvas straight line Connector. Draws a straight line onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Straight = function() { + var self = this; + jsPlumb.Connectors.Straight.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO this does not handle the case that src and target are swapped. + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + jsPlumb.Connectors.canvas.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + +// ********************************* END OF CANVAS RENDERERS ******************************************************************* + + jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label; + + /** + * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. + */ + var CanvasOverlay = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }; + + var AbstractCanvasArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + CanvasOverlay.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + var ctx = connector.ctx; + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(d.hxy.x, d.hxy.y); + ctx.lineTo(d.tail[0].x, d.tail[0].y); + ctx.lineTo(d.cxy.x, d.cxy.y); + ctx.lineTo(d.tail[1].x, d.tail[1].y); + ctx.lineTo(d.hxy.x, d.hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + }; + + jsPlumb.Overlays.canvas.Arrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.canvas.PlainArrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.canvas.Diamond = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/* + * mootools.jsPlumb 1.3.0-RC1 + * + * MooTools specific functionality for jsPlumb. + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + * NOTE: for production usage you should use mootools.jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, + * in a minified file. + * + * Dual licensed under MIT and GPL2. + * + */ + +;(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function(now) { + this.parent(now); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }; + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + var _getElementObject = function(el) { + return $(el); + }; + + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + m.start(properties); + }, + + appendElement : function(child, parent) { + _getElementObject(parent).grab(child); + }, + + bind : function(el, event, callback) { + el = _getElementObject(el); + el.addEvent(event, callback); + }, + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.event.clientX, eventObject.event.clientY]; + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el); + var drags = _draggablesById[id]; + return drags[0].scope; + }, + + getElementObject : _getElementObject, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + getPageXY : function(eventObject) { + return [eventObject.event.pageX, eventObject.event.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).getParent(); + }, + + getScrollLeft : function(el) { + return null; + }, + + getScrollTop : function(el) { + return null; + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[0], + p = jsPlumb.CurrentLibrary.getElementObject(ui).getPosition(); + return { left:p.x, top:p.y }; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options) { + var id = jsPlumb.getId(el); + var drag = _draggablesById[id]; + if (!drag) { + var originalZIndex = 0, originalCursor = null; + var dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + options['onStart'] = jsPlumb.wrap(options['onStart'], function() + { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() + { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + }); + + // DROPPABLES: + var scope = options['scope'] || jsPlumb.Defaults.Scope; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop'); + } + }; + + drag = new Drag.Move(el, options); + drag.scope = scope; + //console.log("drag scope initialized to ", scope); + _add(_draggablesByScope, scope, drag); + _add(_draggablesById, el.get("id"), drag); + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + initDroppable : function(el, options) { + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _add(_droppables, scope, el); + var id = jsPlumb.getId(el); + _droppableOptions[id] = options; + var filterFunc = function(entry) { return entry.element != el; }; + var draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + isAlreadyDraggable : function(el) { + return _draggablesById[jsPlumb.getId(el)] != null; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + return (typeof Drag != undefined && typeof Drag.Move != undefined); + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + _getElementObject(element).dispose(); // ?? + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + setDragScope : function(el, scope) { + var drag = _draggablesById[el.get("id")]; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + drag[0].droppables = droppables; + }, + + setOffset : function(el, o) { + _getElementObject(el).setPosition({x:o.left, y:o.top}); + } + }; + + window.addEvent('domready', jsPlumb.init); +})(); +(function(){if(typeof Math.sgn=="undefined")Math.sgn=function(a){return a==0?0:a>0?1:-1};var p={subtract:function(a,b){return{x:a.x-b.x,y:a.y-b.y}},dotProduct:function(a,b){return a.x*b.x+a.y*b.y},square:function(a){return Math.sqrt(a.x*a.x+a.y*a.y)},scale:function(a,b){return{x:a.x*b,y:a.y*b}}},y=Math.pow(2,-65),u=function(a,b){for(var g=[],d=b.length-1,h=2*d-1,f=[],c=[],l=[],k=[],i=[[1,0.6,0.3,0.1],[0.4,0.6,0.6,0.4],[0.1,0.3,0.6,1]],e=0;e<=d;e++)f[e]=p.subtract(b[e],a);for(e=0;e<=d-1;e++){c[e]= +p.subtract(b[e+1],b[e]);c[e]=p.scale(c[e],3)}for(e=0;e<=d-1;e++)for(var m=0;m<=d;m++){l[e]||(l[e]=[]);l[e][m]=p.dotProduct(c[e],f[m])}for(e=0;e<=h;e++){k[e]||(k[e]=[]);k[e].y=0;k[e].x=parseFloat(e)/h}h=d-1;for(f=0;f<=d+h;f++){c=Math.min(f,d);for(e=Math.max(0,f-h);e<=c;e++){j=f-e;k[e+j].y+=l[j][e]*i[j][e]}}d=b.length-1;k=s(k,2*d-1,g,0);h=p.subtract(a,b[0]);l=p.square(h);for(e=i=0;e=64){g[0]=(a[0].x+a[b].x)/2;return 1}var n,o,q;k=a[0].y-a[b].y;i=a[b].x-a[0].x;e=a[0].x*a[b].y-a[b].x*a[0].y;m=max_distance_below=0;for(o=1;om)m=q;else if(q0?1:-1,c=null;h=0){delete (ax[ay]);ax.splice(ay,1);return true}}}return false},w=function(ay,ax){return J(ay,function(az,aA){ag[aA]=ax;if(i.CurrentLibrary.isDragSupported(az)){i.CurrentLibrary.setDraggable(az,ax)}})},ao=function(ax,ay){V(c(ax,"id"),function(az){az.canvas.style.display=ay})},O=function(ax){return J(ax,function(az,ay){var aA=ag[ay]==null?Y:ag[ay];aA=!aA;ag[ay]=aA;i.CurrentLibrary.setDraggable(az,aA);return aA})},y=function(ax){V(ax,function(az){var ay=("none"==az.canvas.style.display);az.canvas.style.display=ay?"block":"none"})},F=function(aC){var aA=aC.timestamp,ax=aC.recalc,aB=aC.offset,ay=aC.elId;if(!ax){if(aA&&aA===D[ay]){return}}if(ax||aB==null){var az=v(ay);if(az!=null){Q[ay]=b(az);W[ay]=n(az);D[ay]=aA}}else{W[ay]=aB}},aw=function(ax,ay){var az=v(ax);var aA=c(az,"id");if(!aA||aA=="undefined"){if(arguments.length==2&&arguments[1]!=undefined){aA=ay}else{aA="jsPlumb_"+ar()}e(az,"id",aA)}return aA},am=function(az,ax,ay){az=az||function(){};ax=ax||function(){};return function(){var aA=null;try{aA=ax.apply(this,arguments)}catch(aB){k(E,"jsPlumb function failed : "+aB)}if(ay==null||(aA!==ay)){try{az.apply(this,arguments)}catch(aB){k(E,"wrapped function failed : "+aB)}}return aA}};this.connectorClass="_jsPlumb_connector";this.endpointClass="_jsPlumb_endpoint";this.overlayClass="_jsPlumb_overlay";this.Anchors={};this.Connectors={canvas:{},svg:{},vml:{}};this.Endpoints={canvas:{},svg:{},vml:{}};this.Overlays={canvas:{},svg:{},vml:{}};this.addEndpoint=function(az,aA,aJ){aJ=aJ||{};var ay=i.extend({},aJ);i.extend(ay,aA);ay.endpoint=ay.endpoint||E.Defaults.Endpoint||i.Defaults.Endpoint;ay.paintStyle=ay.paintStyle||E.Defaults.EndpointStyle||i.Defaults.EndpointStyle;az=x(az);var aB=[],aE=az.length&&az.constructor!=String?az:[az];for(var aC=0;aC0?l(aK,aJ)!=-1:true},aB=aE.length>1?{}:[],aH=function(aK,aL){if(aE.length>1){var aJ=aB[aK];if(aJ==null){aJ=[];aB[aK]=aJ}aJ.push(aL)}else{aB.push(aL)}};for(var aA in M){if(ay(aE,aA)){for(var az=0;az=4){ay.orientation=[arguments[2],arguments[3]]}if(arguments.length==6){ay.offsets=[arguments[4],arguments[5]]}}var aD=new ac(ay);aD.clone=function(){return new ac(ay)};return aD};this.makeAnchors=function(ay){var az=[];for(var ax=0;ax0?aG[0]:null;var aC=aG.length>0?0:-1;this.locked=false;var aF=this;var aB=function(aJ,aH,aN,aM,aI){var aL=aM[0]+(aJ.x*aI[0]),aK=aM[1]+(aJ.y*aI[1]);return Math.sqrt(Math.pow(aH-aL,2)+Math.pow(aN-aK,2))};var ax=ay||function(aR,aI,aJ,aK,aH){var aM=aJ[0]+(aK[0]/2),aL=aJ[1]+(aK[1]/2);var aO=-1,aQ=Infinity;for(var aN=0;aNaX){aX=a4}}var a8=this.connector.compute(aZ,ba,this.endpoints[be].anchor,this.endpoints[aW].anchor,aI.paintStyleInUse.lineWidth,aX);aI.connector.paint(a8,aI.paintStyleInUse);for(var a9=0;a9=0){aM.connections.splice(aY,1);if(!a1){var a0=aZ.endpoints[0]==aM?aZ.endpoints[1]:aZ.endpoints[0];a0.detach(aZ,true);if(aZ.endpointToDeleteOnDetach&&aZ.endpointToDeleteOnDetach.connections.length==0){i.deleteEndpoint(aZ.endpointToDeleteOnDetach)}}R(aZ.connector.getDisplayElements(),aZ.parent);N(M,aZ.scope,aZ);if(!a1){ad(aZ)}}};this.detachAll=function(){while(aM.connections.length>0){aM.detach(aM.connections[0])}};this.detachFrom=function(aZ){var a0=[];for(var aY=0;aY=0){aM.connections.splice(aY,1)}};this.getElement=function(){return aL};this.getUuid=function(){return aH};this.makeInPlaceCopy=function(){return an({anchor:aM.anchor,source:aL,paintStyle:this.paintStyle,endpoint:aK})};this.isConnectedTo=function(a0){var aZ=false;if(a0){for(var aY=0;aY0){var a8=aM.connections[0];var ba=a8.endpoints[0]==aM?1:0;var a3=ba==0?a8.sourceId:a8.targetId;var a7=W[a3],a9=Q[a3];aZ.txy=[a7.left,a7.top];aZ.twh=a9;aZ.tElement=a8.endpoints[ba]}}a4=aM.anchor.compute(aZ)}var a6=aK.compute(a4,aM.anchor.getOrientation(),aM.paintStyleInUse,a2||aM.paintStyleInUse);aK.paint(a6,aM.paintStyleInUse,aM.anchor);aM.timestamp=a5}};this.repaint=this.paint;this.removeConnection=this.detach;if(aX.isSource&&i.CurrentLibrary.isDragSupported(aL)){var aR=null,aN=null,aQ=null,ax=false,aA=null;var aC=function(){aQ=aM.connectorSelector();if(aM.isFull()&&!aI){return false}F({elId:aE});aB=aM.makeInPlaceCopy();aB.paint();aR=document.createElement("div");aR.style.position="absolute";var a5=v(aR);S(aR,aM.parent);var aY=aw(a5);var a6=v(aB.canvas),a4=i.CurrentLibrary.getOffset(a6),a1=i.CurrentLibrary.getSize(a6);i.CurrentLibrary.setOffset(aR,a4);F({elId:aY});e(v(aM.canvas),"dragId",aY);e(v(aM.canvas),"elId",aE);var a7=new I({reference:aM.anchor,referenceCanvas:aM.canvas});aV=an({paintStyle:aM.paintStyle,endpoint:aK,anchor:a7,source:a5});if(aQ==null){aM.anchor.locked=true;aQ=au({sourceEndpoint:aM,targetEndpoint:aV,source:v(aL),target:v(aR),anchors:[aM.anchor,a7],paintStyle:aX.connectorStyle,hoverPaintStyle:aX.connectorHoverStyle,connector:aX.connector,overlays:aX.connectorOverlays});aQ.connector.setHover(false)}else{ax=true;aQ.connector.setHover(false);aD(v(aB.canvas));var aZ=aQ.sourceId==aE?0:1;aQ.floatingAnchorIndex=aZ;aM.detachFromConnection(aQ);var a3=v(aM.canvas);var a2=i.CurrentLibrary.getDragScope(a3);e(a3,"originalScope",a2);var a0="scope_"+(new Date()).getTime();if(aZ==0){aA=[aQ.source,aQ.sourceId,aU,a2];aQ.source=v(aR);aQ.sourceId=aY}else{aA=[aQ.target,aQ.targetId,aU,a2];aQ.target=v(aR);aQ.targetId=aY}i.CurrentLibrary.setDragScope(aU,a0);aQ.endpoints[aZ==0?1:0].anchor.locked=true;aQ.suspendedEndpoint=aQ.endpoints[aZ];aQ.endpoints[aZ]=aV}Z[aY]=aQ;aV.addConnection(aQ);T(ah,aY,aV);E.currentlyDragging=true};var ay=i.CurrentLibrary,aT=aX.dragOptions||{},aO=i.extend({},ay.defaultDragOptions),aP=ay.dragEvents.start,aW=ay.dragEvents.stop,aG=ay.dragEvents.drag;aT=i.extend(aO,aT);aT.scope=aT.scope||aM.scope;aT[aP]=am(aT[aP],aC);aT[aG]=am(aT[aG],function(){var aY=i.CurrentLibrary.getUIPosition(arguments);i.CurrentLibrary.setOffset(aR,aY);at(v(aR),aY)});aT[aW]=am(aT[aW],function(){N(ah,aN,aV);R([aR,aV.canvas],aL);ae(aB.canvas,aL);var aY=aQ.floatingAnchorIndex==null?1:aQ.floatingAnchorIndex;aQ.endpoints[aY==0?1:0].anchor.locked=false;if(aQ.endpoints[aY]==aV){if(ax&&aQ.suspendedEndpoint){if(aY==0){aQ.source=aA[0];aQ.sourceId=aA[1]}else{aQ.target=aA[0];aQ.targetId=aA[1]}i.CurrentLibrary.setDragScope(aA[2],aA[3]);aQ.endpoints[aY]=aQ.suspendedEndpoint;if(aJ){aQ.floatingAnchorIndex=null;aQ.suspendedEndpoint.addConnection(aQ);i.repaint(aA[1])}else{aQ.endpoints[aY==0?1:0].detach(aQ)}}else{R(aQ.connector.getDisplayElements(),aM.parent);aM.detachFromConnection(aQ)}}aM.anchor.locked=false;aM.paint();aQ.setHover(false);aQ.repaint();aQ=null;delete aB;delete ah[aV.elementId];aV=null;delete aV;E.currentlyDragging=false});var aU=v(aM.canvas);i.CurrentLibrary.initDraggable(aU,aT)}var aD=function(a1){if(aX.isTarget&&i.CurrentLibrary.isDropSupported(aL)){var aY=aX.dropOptions||E.Defaults.DropOptions||i.Defaults.DropOptions;aY=i.extend({},aY);aY.scope=aY.scope||aM.scope;var a4=null;var a2=i.CurrentLibrary.dragEvents.drop;var a3=i.CurrentLibrary.dragEvents.over;var aZ=i.CurrentLibrary.dragEvents.out;var a0=function(){var bd=v(i.CurrentLibrary.getDragObject(arguments));var a5=c(bd,"dragId");var a7=c(bd,"elId");var bc=c(bd,"originalScope");if(bc){i.CurrentLibrary.setDragScope(bd,bc)}var a9=Z[a5];var ba=a9.floatingAnchorIndex==null?1:a9.floatingAnchorIndex,bb=ba==0?1:0;if(!aM.isFull()&&!(ba==0&&!aM.isSource)&&!(ba==1&&!aM.isTarget)){if(ba==0){a9.source=aL;a9.sourceId=aE}else{a9.target=aL;a9.targetId=aE}a9.endpoints[ba].detachFromConnection(a9);if(a9.suspendedEndpoint){a9.suspendedEndpoint.detachFromConnection(a9)}a9.endpoints[ba]=aM;aM.addConnection(a9);if(!a9.suspendedEndpoint){T(M,a9.scope,a9);P(aL,aX.draggable,{})}else{var a8=a9.suspendedEndpoint.getElement(),a6=a9.suspendedEndpoint.elementId;E.fire("jsPlumbConnectionDetached",{source:ba==0?a8:a9.source,target:ba==1?a8:a9.target,sourceId:ba==0?a6:a9.sourceId,targetId:ba==1?a6:a9.targetId,sourceEndpoint:ba==0?a9.suspendedEndpoint:a9.endpoints[0],targetEndpoint:ba==1?a9.suspendedEndpoint:a9.endpoints[1],connection:a9})}i.repaint(a7);E.fire("jsPlumbConnection",{source:a9.source,target:a9.target,sourceId:a9.sourceId,targetId:a9.targetId,sourceEndpoint:a9.endpoints[0],targetEndpoint:a9.endpoints[1],connection:a9})}E.currentlyDragging=false;delete Z[a5]};aY[a2]=am(aY[a2],a0);aY[a3]=am(aY[a3],function(){var a6=i.CurrentLibrary.getDragObject(arguments);var a8=c(v(a6),"dragId");var a7=Z[a8];if(a7!=null){var a5=a7.floatingAnchorIndex==null?1:a7.floatingAnchorIndex;a7.endpoints[a5].anchor.over(aM.anchor)}});aY[aZ]=am(aY[aZ],function(){var a6=i.CurrentLibrary.getDragObject(arguments),a8=c(v(a6),"dragId"),a7=Z[a8];if(a7!=null){var a5=a7.floatingAnchorIndex==null?1:a7.floatingAnchorIndex;a7.endpoints[a5].anchor.out()}});i.CurrentLibrary.initDroppable(a1,aY)}};aD(v(aM.canvas));return aM}};var i=window.jsPlumb=new r();i.getInstance=function(x){var w=new r(x);return w};var m=function(w,B,A,z){return function(){return i.makeAnchor(w,B,A,z)}};i.Anchors.TopCenter=m(0.5,0,0,-1);i.Anchors.BottomCenter=m(0.5,1,0,1);i.Anchors.LeftMiddle=m(0,0.5,-1,0);i.Anchors.RightMiddle=m(1,0.5,1,0);i.Anchors.Center=m(0.5,0.5,0,0);i.Anchors.TopRight=m(1,0,0,-1);i.Anchors.BottomRight=m(1,1,0,1);i.Anchors.TopLeft=m(0,0,0,-1);i.Anchors.BottomLeft=m(0,1,0,1);i.Defaults.DynamicAnchors=function(){return i.makeAnchors(["TopCenter","RightMiddle","BottomCenter","LeftMiddle"])};i.Anchors.AutoDefault=function(){return i.makeDynamicAnchor(i.Defaults.DynamicAnchors())}})();(function(){jsPlumb.DOMElementComponent=function(a){jsPlumb.jsPlumbUIComponent.apply(this,arguments);this.mousemove=this.dblclick=this.click=this.mousedown=this.mouseup=function(b){}};jsPlumb.Connectors.Straight=function(){var n=this;var g=null;var c,h,l,k,i,d,m,f,e,b,a;this.compute=function(r,F,B,o,v,q){var E=Math.abs(r[0]-F[0]);var u=Math.abs(r[1]-F[1]);var z=false,s=false;var t=0.45*E,p=0.45*u;E*=1.9;u*=1.9;var C=Math.min(r[0],F[0])-t;var A=Math.min(r[1],F[1])-p;var D=Math.max(2*v,q);if(E0?1:-1;var t=Math.abs(u*Math.sin(d));if(e>a){t=t*-1}var o=Math.abs(u*Math.cos(d));if(f>b){o=o*-1}return{x:s.x+(r*o),y:s.y+(r*t)}};this.perpendicularToPathAt=function(s,t,w){var u=n.pointAlongPathFrom(s,w);var r=n.gradientAtPoint(u.location);var q=Math.atan(-1/r);var v=t/2*Math.sin(q);var o=t/2*Math.cos(q);return[{x:u.x+o,y:u.y+v},{x:u.x-o,y:u.y-v}]}};jsPlumb.Connectors.Bezier=function(e){var o=this;e=e||{};this.majorAnchor=e.curviness||150;this.minorAnchor=10;var h=null;this._findControlPoint=function(z,q,u,x,r){var w=x.getOrientation(),y=r.getOrientation();var t=w[0]!=y[0]||w[1]==y[1];var s=[];var A=o.majorAnchor,v=o.minorAnchor;if(!t){if(w[0]==0){s.push(q[0]l){l=u}if(x<0){d+=x;var y=Math.abs(x);l+=y;n[0]+=y;i+=y;b+=y;m[0]+=y}var G=Math.min(f,a);var E=Math.min(n[1],m[1]);var t=Math.min(G,E);var z=Math.max(f,a);var w=Math.max(n[1],m[1]);var r=Math.max(z,w);if(r>g){g=r}if(t<0){c+=t;var v=Math.abs(t);g+=v;n[1]+=v;f+=v;a+=v;m[1]+=v}if(D&&l=s){q=t;r=(s-l[t][0])/a[t];break}}return{segment:i[q],proportion:r,index:q}};this.compute=function(Q,v,P,r,q,H){i=[];h=[];a=[];g=[];segmentProportionals=[];d=v[0]t[0]?t[0]+((1-x)*r)-y:t[2]+(x*r)+y,y:q==0?t[3]:t[3]>t[1]?t[1]+((1-x)*r)-y:t[3]+(x*r)+y,segmentInfo:v};return w};this.perpendicularToPathAt=function(t,u,z){var v=n.pointAlongPathFrom(t,z);var s=h[v.segmentInfo.index];var r=Math.atan(-1/s);var w=u/2*Math.sin(r);var q=u/2*Math.cos(r);return[{x:v.x+q,y:v.y+w},{x:v.x-q,y:v.y-w}]}};jsPlumb.Endpoints.Dot=function(b){var a=this;b=b||{};this.radius=b.radius||10;this.defaultOffset=0.5*this.radius;this.defaultInnerRadius=this.radius/3;this.compute=function(g,d,i,f){var e=i.radius||a.radius;var c=g[0]-e;var h=g[1]-e;return[c,h,e*2,e*2,e]}};jsPlumb.Endpoints.Rectangle=function(b){var a=this;b=b||{};this.width=b.width||20;this.height=b.height||20;this.compute=function(h,e,k,g){var f=k.width||a.width;var d=k.height||a.height;var c=h[0]-(f/2);var i=h[1]-(d/2);return[c,i,f,d]}};jsPlumb.Endpoints.Image=function(d){jsPlumb.DOMElementComponent.apply(this,arguments);var a=this,c=false;this.img=new Image();a.ready=false;this.img.onload=function(){a.ready=true};this.img.src=d.src||d.url;this.compute=function(g,e,h,f){a.anchorPoint=g;if(a.ready){return[g[0]-a.img.width/2,g[1]-a.img.height/2,a.img.width,a.img.height]}else{return[0,0,0,0]}};a.canvas=document.createElement("img"),c=false;a.canvas.style.margin=0;a.canvas.style.padding=0;a.canvas.style.outline=0;a.canvas.style.position="absolute";a.canvas.className=jsPlumb.endpointClass;jsPlumb.appendElement(a.canvas,d.parent);a.attachListeners(a.canvas,a);var b=function(k,i,g){if(!c){a.canvas.setAttribute("src",a.img.src);c=true}var h=a.img.width,f=a.img.height,e=a.anchorPoint[0]-(h/2),l=a.anchorPoint[1]-(f/2);jsPlumb.sizeCanvas(a.canvas,e,l,h,f)};this.paint=function(g,f,e){if(a.ready){b(g,f,e)}else{window.setTimeout(function(){a.paint(g,f,e)},200)}}};jsPlumb.Endpoints.Blank=function(){jsPlumb.DOMElementComponent.apply(this,arguments);this.compute=function(){return[0,0,0,0]};self.canvas=document.createElement("div");this.paint=function(){}};jsPlumb.Endpoints.Triangle=function(a){a=a||{};a.width=a.width||55;param.height=a.height||55;this.width=a.width;this.height=a.height;this.compute=function(g,d,i,f){var e=i.width||self.width;var c=i.height||self.height;var b=g[0]-(e/2);var h=g[1]-(c/2);return[b,h,e,c]}};jsPlumb.Overlays.Arrow=function(f){f=f||{};var b=this;this.length=f.length||20;this.width=f.width||20;this.id=f.id;this.connection=f.connection;var e=(f.direction||1)<0?-1:1;var c=f.paintStyle||{lineWidth:1};this.loc=f.location==null?0.5:f.location;var a=f.foldback||0.623;var d=function(g,i){if(a==0.5){return g.pointOnPath(i)}else{var h=0.5-a;return g.pointAlongPathFrom(i,e*b.length*h)}};this.computeMaxSize=function(){return b.width*1.5};this.draw=function(k,q,w){var y=k.pointAlongPathFrom(b.loc,e*(b.length/2));var t=k.pointAlongPathFrom(b.loc,-1*e*(b.length/2)),B=t.x,A=t.y;var r=k.perpendicularToPathAt(b.loc,b.width,-1*e*(b.length/2));var i=d(k,b.loc);if(b.loc==1){var h=k.pointOnPath(b.loc);var v=(h.x-y.x)*e,u=(h.y-y.y)*e;i.x+=v;i.y+=u;t.x+=v;t.y+=u;r[0].x+=v;r[0].y+=u;r[1].x+=v;r[1].y+=u;y.x+=v;y.y+=u}if(b.loc==0){var h=k.pointOnPath(b.loc);var s=a>1?i:{x:r[0].x+((r[1].x-r[0].x)/2),y:r[0].y+((r[1].y-r[0].y)/2)};var v=(h.x-s.x)*e,u=(h.y-s.y)*e;i.x+=v;i.y+=u;t.x+=v;t.y+=u;r[0].x+=v;r[0].y+=u;r[1].x+=v;r[1].y+=u;y.x+=v;y.y+=u}var o=Math.min(y.x,r[0].x,r[1].x);var n=Math.max(y.x,r[0].x,r[1].x);var m=Math.min(y.y,r[0].y,r[1].y);var l=Math.max(y.y,r[0].y,r[1].y);var z={hxy:y,tail:r,cxy:i},x=c.strokeStyle||q.strokeStyle,p=c.fillStyle||q.strokeStyle,g=c.lineWidth||q.lineWidth;b.paint(k,z,g,x,p,w);return[o,n,m,l]}};jsPlumb.Overlays.PlainArrow=function(b){b=b||{};var a=jsPlumb.extend(b,{foldback:1});jsPlumb.Overlays.Arrow.call(this,a)};jsPlumb.Overlays.Diamond=function(c){c=c||{};var a=c.length||40;var b=jsPlumb.extend(c,{length:a/2,foldback:2});jsPlumb.Overlays.Arrow.call(this,b)};jsPlumb.Overlays.Label=function(d){jsPlumb.DOMElementComponent.apply(this,arguments);this.labelStyle=d.labelStyle||jsPlumb.Defaults.LabelStyle;this.labelStyle.font=this.labelStyle.font||"12px sans-serif";this.label=d.label;this.connection=d.connection;this.id=d.id;var k=this;var h=null,e=null,c=null,b=null;this.location=d.location||0.5;this.cachedDimensions=null;var i=false,c=null,a=document.createElement("div");a.style.position="absolute";a.style.textAlign="center";a.style.cursor="pointer";a.style.font=k.labelStyle.font;a.style.color=k.labelStyle.color||"black";if(k.labelStyle.fillStyle){a.style.background=k.labelStyle.fillStyle}if(k.labelStyle.borderWidth>0){var g=k.labelStyle.borderStyle?k.labelStyle.borderStyle:"black";a.style.border=k.labelStyle.borderWidth+"px solid "+g}if(k.labelStyle.padding){a.style.padding=k.labelStyle.padding}var f=d._jsPlumb.overlayClass+" "+(k.labelStyle.cssClass?k.labelStyle.cssClass:d.cssClass?d.cssClass:"");a.className=f;jsPlumb.appendElement(a,d.connection.parent);jsPlumb.getId(a);k.attachListeners(a,k);this.paint=function(l,n,m){if(!i){l.appendDisplayElement(a);k.attachListeners(a,l);i=true}a.style.left=(m[0]+n.minx)+"px";a.style.top=(m[1]+n.miny)+"px"};this.getTextDimensions=function(l){c=typeof k.label=="function"?k.label(k):k.label;a.innerHTML=c.replace(/\r\n/g,"
");var n=jsPlumb.CurrentLibrary.getElementObject(a),m=jsPlumb.CurrentLibrary.getSize(n);return{width:m[0],height:m[1]}};this.computeMaxSize=function(l){var m=k.getTextDimensions(l);return m.width?Math.max(m.width,m.height)*1.5:0};this.draw=function(n,p,o){var r=k.getTextDimensions(n);if(r.width!=null){var q=n.pointOnPath(k.location);var m=q.x-(r.width/2);var l=q.y-(r.height/2);k.paint(n,{minx:m,miny:l,td:r,cxy:q},o);return[m,m+r.width,l,l+r.height]}else{return[0,0,0,0]}}}})();(function(){var h={"stroke-linejoin":"joinstyle",joinstyle:"joinstyle",endcap:"endcap",miterlimit:"miterlimit"};if(document.createStyleSheet){document.createStyleSheet().addRule(".jsplumb_vml","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:textbox","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:oval","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:rect","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:stroke","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:shape","behavior:url(#default#VML);position:absolute;");document.namespaces.add("jsplumb","urn:schemas-microsoft-com:vml")}var c=1000,d=function(p,q){for(var n in q){p[n]=q[n]}},m=function(n,q,r){r=r||{};var p=document.createElement("jsplumb:"+n);p.className=(r["class"]?r["class"]+" ":"")+"jsplumb_vml";l(p,q);d(p,r);return p},l=function(p,n){p.style.left=n[0]+"px";p.style.top=n[1]+"px";p.style.width=n[2]+"px";p.style.height=n[3]+"px";p.style.position="absolute"},g=function(n){return Math.floor(n*c)},a=function(p,n){var v=p,u=function(o){return o.length==1?"0"+o:o},q=function(o){return u(Number(o).toString(16))},r=/(rgb[a]?\()(.*)(\))/;if(p.match(r)){var t=p.match(r)[2].split(",");v="#"+q(t[0])+q(t[1])+q(t[2]);if(!n&&t.length==4){v=v+q(t[3])}}return v},f=function(s,r,p){var u={};if(r.strokeStyle){u.stroked="true";u.strokecolor=a(r.strokeStyle,true);u.strokeweight=r.lineWidth+"px"}else{u.stroked="false"}if(r.fillStyle){u.filled="true";u.fillcolor=a(r.fillStyle,true)}else{u.filled="false"}if(r.dashstyle){if(p.strokeNode==null){p.strokeNode=m("stroke",[0,0,0,0],{dashstyle:r.dashstyle});s.appendChild(p.strokeNode)}else{p.strokeNode.dashstyle=r.dashstyle}}else{if(r["stroke-dasharray"]&&r.lineWidth){var o=r["stroke-dasharray"].indexOf(",")==-1?" ":",",t=r["stroke-dasharray"].split(o),n="";for(var q=0;q0&&F>0&&z=z&&B[2]<=F&&B[3]>=F)){return true}}var D=v.canvas.getContext("2d").getImageData(parseInt(z),parseInt(F),1,1);return D.data[0]!=0||D.data[1]!=0||D.data[2]!=0||D.data[3]!=0}return false};var u=false;var t=false,y=null,x=false;var w=function(A,z){return A!=null&&f(A,z)};this.mousemove=function(C){var E=e(C),B=i(C),A=document.elementFromPoint(B[0],B[1]),D=w(A,"_jsPlumb_overlay");var z=o==null&&(w(A,"_jsPlumb_endpoint")||w(A,"_jsPlumb_connector"));if(!u&&z&&v._over(C)){u=true;v.fire("mouseenter",v,C);return true}else{if(u&&(!v._over(C)||!z)&&!D){u=false;v.fire("mouseexit",v,C)}}v.fire("mousemove",v,C)};this.click=function(z){if(u&&v._over(z)&&!x){v.fire("click",v,z)}x=false};this.dblclick=function(z){if(u&&v._over(z)&&!x){v.fire("dblclick",v,z)}x=false};this.mousedown=function(z){if(v._over(z)&&!t){t=true;y=m(s(v.canvas));v.fire("mousedown",v,z)}};this.mouseup=function(z){t=false;v.fire("mouseup",v,z)}};var p=function(u){var t=document.createElement("canvas");jsPlumb.appendElement(t,u.parent);t.style.position="absolute";if(u["class"]){t.className=u["class"]}u._jsPlumb.getId(t,u.uuid);return t};var d=jsPlumb.CanvasConnector=function(x){n.apply(this,arguments);var t=function(B,z){u.ctx.save();jsPlumb.extend(u.ctx,z);if(z.gradient){var A=u.createGradient(B,u.ctx);for(var y=0;y0?1:-1}}var b={subtract:function(m,l){return{x:m.x-l.x,y:m.y-l.y}},dotProduct:function(m,l){return m.x*l.x+m.y*l.y},square:function(l){return Math.sqrt(l.x*l.x+l.y*l.y)},scale:function(m,l){return{x:m.x*l,y:m.y*l}}},d=Math.pow(2,-65),h=function(y,x){for(var s=[],v=x.length-1,r=2*v-1,t=[],w=[],o=[],p=[],q=[[1,0.6,0.3,0.1],[0.4,0.6,0.6,0.4],[0.1,0.3,0.6,1]],u=0;u<=v;u++){t[u]=b.subtract(x[u],y)}for(u=0;u<=v-1;u++){w[u]=b.subtract(x[u+1],x[u]);w[u]=b.scale(w[u],3)}for(u=0;u<=v-1;u++){for(var n=0;n<=v;n++){o[u]||(o[u]=[]);o[u][n]=b.dotProduct(w[u],t[n])}}for(u=0;u<=r;u++){p[u]||(p[u]=[]);p[u].y=0;p[u].x=parseFloat(u)/r}r=v-1;for(t=0;t<=v+r;t++){w=Math.min(t,v);for(u=Math.max(0,t-r);u<=w;u++){j=t-u;p[u+j].y+=o[j][u]*q[j][u]}}v=x.length-1;p=k(p,2*v-1,s,0);r=b.subtract(y,x[0]);o=b.square(r);for(u=q=0;u=64){y[0]=(E[0].x+E[D].x)/2;return 1}var s,r,p;v=E[0].y-E[D].y;w=E[D].x-E[0].x;A=E[0].x*E[D].y-E[D].x*E[0].y;t=max_distance_below=0;for(r=1;rt){t=p}else{if(p0?1:-1,r=null;n endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + endpointsByElement = {}, + endpointsByUUID = {}, + offsets = {}, + offsetTimestamps = {}, + floatingConnections = {}, + draggableStates = {}, + _mouseEventsEnabled = this.Defaults.MouseEventsEnabled, + _draggableByDefault = true, + canvasList = [], + sizes = [], + listeners = {}, // a map: keys are event types, values are lists of listeners. + DEFAULT_SCOPE = this.Defaults.Scope, + renderMode = null, // will be set in init() + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }, + + /** + * appends an element to the given parent, or the document body if no + * parent given. + */ + _appendElement = function(el, parent) { + if (!parent) + document.body.appendChild(el); + else + jsPlumb.CurrentLibrary.appendElement(el, parent); + }, + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + _timestamp = function() { return "" + (new Date()).getTime(); }, + + /** + * YUI, for some reason, put the result of a Y.all call into an object that contains + * a '_nodes' array, instead of handing back an array-like object like the other + * libraries do. + */ + _convertYUICollection = function(c) { + return c._nodes ? c._nodes : c; + }, + + /** + * Draws an endpoint and its connections. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + _draw = function(element, ui, timestamp) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (!timestamp) timestamp = _timestamp(); + if (endpoints) { + _updateOffset( { elId : id, offset : ui, recalc : false, timestamp : timestamp }); // timestamp is checked against last update cache; it is + // valid for one paint cycle. + var myOffset = offsets[id], myWH = sizes[id]; + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + var l = endpoints[i].connections; + for ( var j = 0; j < l.length; j++) { + l[j].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); // ...paint each connection. + // then, check for dynamic endpoint; need to repaint it. + var oIdx = l[j].endpoints[0] == endpoints[i] ? 1 : 0; + if (l[j].endpoints[oIdx].anchor.isDynamic && !l[j].endpoints[oIdx].isFloating()) { + var oId = oIdx == 0 ? l[j].sourceId : l[j].targetId, + oOffset = offsets[oId], oWH = sizes[oId], + // TODO i still want to make this faster. + anchorLoc = l[j].endpoints[oIdx].anchor.compute( { + xy : [ oOffset.left, oOffset.top ], + wh : oWH, + element : l[j].endpoints[oIdx], + txy : [ myOffset.left, myOffset.top ], + twh : myWH, + tElement : endpoints[i] + }); + l[j].endpoints[oIdx].paint({ anchorLoc : anchorLoc }); + } + } + } + } + }, + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }, + + /** + * gets an Endpoint by uuid. + */ + _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }, + + /** + * inits a draggable if it's not already initialised. + */ + _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element) && !jsPlumb.CurrentLibrary.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + } + } + }, + + _newConnection = function(params) { + var connectionFunc = jsPlumb.Defaults.ConnectionType || Connection, + endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint, + parent = jsPlumb.CurrentLibrary.getParent; + + if (params.sourceEndpoint) + params["parent"] = params.sourceEndpoint.parent; + else if (params.source.constructor == endpointFunc) + params["parent"] = params.source.parent; + else params["parent"] = parent(params.source); + + params["_jsPlumb"] = _currentInstance; + var con = new connectionFunc(params); + _eventFireProxy("click", "click", con); + _eventFireProxy("dblclick", "dblclick", con); + return con; + }, + + _eventFireProxy = function(event, proxyEvent, obj) { + obj.bind(event, function(e) { + _currentInstance.fire(proxyEvent, obj, e); + }); + }, + + _newEndpoint = function(params) { + var endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint; + params["parent"] = jsPlumb.CurrentLibrary.getParent(params.source); + params["_jsPlumb"] = _currentInstance, + ep = new endpointFunc(params); + _eventFireProxy("click", "endpointClick", ep); + _eventFireProxy("dblclick", "endpointDblClick", ep); + return ep; + }, + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }, + /** + * perform an operation on all elements. + */ + _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }, + + /** + * helper to remove an element from the DOM. + */ + _removeElement = function(element, parent) { + if (element != null && element.parentNode != null) { + element.parentNode.removeChild(element); + } + }, + /** + * helper to remove a list of elements from the DOM. + */ + _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }, + /** + * helper method to remove an item from a list. + */ + _removeFromList = function(map, key, value) { + if (key != null) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete (l[i]); + l.splice(i, 1); + return true; + } + } + } + return false; + }, + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }, + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + _setVisible = function(el, state) { + _operation(_getAttribute(el, "id"), function(jpc) { + jpc.canvas.style.display = state; + }); + }, + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }, + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + _toggleVisible = function(elId) { + _operation(elId, function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }, + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return; + } + if (recalc || offset == null) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + } + }, + +/** + * gets an id for the given element, creating and setting one if + * necessary. + */ + _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else + id = "jsPlumb_" + _timestamp(); + _setAttribute(ele, "id", id); + } + return id; + }, + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'jsPlumb function failed : ' + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'wrapped function failed : ' + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = "_jsPlumb_connector"; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = "_jsPlumb_endpoint"; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = "_jsPlumb_overlay"; + + this.Anchors = {}; + + this.Connectors = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Endpoints = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Overlays = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + +// ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***************************************** + /* + * Function: bind + * Bind to an event on jsPlumb. + * + * Parameters: + * event - the event to bind. Available events on jsPlumb are: + * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback. + * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback. + * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback. + * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback. + * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback. + * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback. + * + * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event. + */ + + /* + * Function: clearListeners + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + +// *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *********************************************************** + + /* + Function: addEndpoint + + Adds an to a given element or elements. + + Parameters: + + el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + params - Object containing Endpoint constructor arguments. For more information, see . + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some + shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in + this object are anything that 'params' can contain. See . + + Returns: + The newly created , if el referred to a single element. Otherwise, an array of newly created s. + + See Also: + + */ + this.addEndpoint = function(el, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + + // YUI wrapper + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + var _el = _getElementObject(inputs[i]), id = _getId(_el); + p.source = _el; + _updateOffset({ elId : id }); + var e = _newEndpoint(p); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + results.push(e); + } + + return results.length == 1 ? results[0] : results; + }; + + /* + Function: addEndpoints + Adds a list of s to a given element or elements. + + Parameters: + target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See 's constructor documentation. + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created s, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(el, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + Array.prototype.push.apply(results, _currentInstance.addEndpoint(el, endpoints[i], referenceParams)); + } + return results; + }; + + /* + Function: animate + This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating + the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // onComplete repaints, just to make sure everything looks good at the end of the animation. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /* + Function: connect + Establishes a between two elements (or s, which are themselves registered to elements). + + Parameters: + params - Object containing constructor arguments for the Connection. See 's constructor documentation. + referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of + Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection. + + Returns: + The newly created . + */ + this.connect = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + // dynamic anchors. backwards compatibility here: from 1.2.6 onwards you don't need to specify "dynamicAnchors". the fact that some anchor consists + // of multiple definitions is enough to tell jsPlumb you want it to be dynamic. + if (_p.dynamicAnchors) { + // these can either be an array of anchor coords, which we will use for both source and target, or an object with {source:[anchors], target:[anchors]}, in which + // case we will use a different set for each element. + var a = _p.dynamicAnchors.constructor == Array; + var sa = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.source)); + var ta = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.target)); + _p.anchors = [sa,ta]; + } + + var jpc = _newConnection(_p); + // add to list of connections (by scope). + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + _currentInstance.fire("jsPlumbConnection", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + // force a paint + _draw(jpc.source); + + return jpc; + }; + + /* + Function: deleteEndpoint + Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too) + + Parameters: + object - either an object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElement(endpoint.canvas, endpoint.parent); + // remove from endpointsbyElement + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + delete endpoint; + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every , and their associated s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc) { + _currentInstance.fire("jsPlumbConnectionDetached", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + }; + + /* + Function: detach + Detaches and then removes a . Takes either (source, target) (the old way, maintained for backwards compatibility), or a params + object with various possible values. + + Parameters: + source - id or element object of the first element in the Connection. + target - id or element object of the second element in the Connection. + params - a JS object containing the same parameters as you pass to jsPlumb.connect. If this is present then neither source nor + target should be present; it should be the only argument to the method. See the docs for 's constructor for information +about the parameters allowed in the params object. + Returns: + true if successful, false if not. + */ + this.detach = function(source, target) { + if (arguments.length == 2) { + var s = _getElementObject(source), sId = _getId(s); + var t = _getElementObject(target), tId = _getId(t); + _operation(sId, function(jpc) { + if ((jpc.sourceId == sId && jpc.targetId == tId) || (jpc.targetId == sId && jpc.sourceId == tId)) { + _removeElements(jpc.connector.getDisplayElements(), jpc.parent); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + // this is the new version of the method, taking a JS object like + // the connect method does. + else if (arguments.length == 1) { + // TODO investigate whether or not this code still works when a user has supplied their own subclass of Connection. i suspect it may not. + if (arguments[0].constructor == Connection) { + arguments[0].endpoints[0].detachFrom(arguments[0].endpoints[1]); + } + else if (arguments[0].connection) { + arguments[0].connection.endpoints[0].detachFrom(arguments[0].connection.endpoints[1]); + } + else { + var _p = jsPlumb.extend( {}, source); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1])); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source); + var targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElements(jpc.connector.getDisplayElements(), jpc.parent); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + } + } + }; + + /* + Function: detachAll + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + + Returns: + void + */ + this.detachAllConnections = function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + }; + + /** + * @deprecated Use detachAllConnections instead. this will be removed in jsPlumb 1.3. + */ + this.detachAll = this.detachAllConnections; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + /** + * @deprecated use detachEveryConnection instead. this will be removed in jsPlumb 1.3. + */ + this.detachEverything = this.detachEveryConnection; + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of you library's draggable method so that jsPlumb can setup the appropriate callbacks. Your underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method, + * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the + * default scope). If multiple scopes are passed in, the return value will be a map of { scope -> [ connection... ] }. + * + * Parameters + * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list + * of connections that are in the given scope. + * options - if the argument is a JS object, you can specify a finer-grained filter: + * + * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes. + * - *source* either a string representing an element id, or a selector. constrains the result to connections having this source. + * - *target* either a string representing an element id, or a selector. constrains the result to connections having this target. + * + */ + this.getConnections = function(options) { + if (!options) { + options = {}; + } else if (options.constructor == String) { + options = { "scope": options }; + } + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') + r.push(input); + else + r = input; + } + return r; + }; + var scope = options.scope || jsPlumb.getDefaultScope(), + scopes = prepareList(scope), + sources = prepareList(options.source), + targets = prepareList(options.target), + filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }, + results = scopes.length > 1 ? {} : [], + _addOne = function(scope, obj) { + if (scopes.length > 1) { + var ss = results[scope]; + if (ss == null) { + ss = []; results[scope] = ss; + } + ss.push(obj); + } else results.push(obj); + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + _addOne(i, c); + } + } + } + return results; + }; + + /* + * Function: getAllConnections + * Gets all connections, as a map of { scope -> [ connection... ] }. + */ + this.getAllConnections = function() { + return connectionsByScope; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /** + * Function:getEndpoints + * Gets the list of Endpoints for a given selector, or element id. + * @param el + * @return + */ + this.getEndpoints = function(el) { + return endpointsByElement[_getId(el)]; + }; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + + this.appendElement = _appendElement; + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + + Returns: + void + */ + this.hide = function(el) { + _setVisible(el, "none"); + }; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + if (!initialized) { + _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run. + + var _bind = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && _mouseEventsEnabled && renderMode == jsPlumb.CANVAS) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + var t = c[i].connector[event](e); + if (t) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i].endpoint[event](e)) return; + } + } + } + }); + }; + _bind("click"); + _bind("dblclick"); + _bind("mousemove"); + _bind("mousedown"); + _bind("mouseup"); + + initialized = true; + _currentInstance.fire("ready"); + } + }; + + this.jsPlumbUIComponent = jsPlumbUIComponent; + this.EventGenerator = EventGenerator; + + /* + * Creates an anchor with the given params. + * + * You do not need to use this method. It is exposed because of the way jsPlumb is + * split into three scripts; this will change in the future. + * + * x - the x location of the anchor as a fraction of the + * total width. y - the y location of the anchor as a fraction of the + * total height. xOrientation - value indicating the general direction a + * connection from the anchor should go in, in the x direction. + * yOrientation - value indicating the general direction a connection + * from the anchor should go in, in the y direction. xOffset - a fixed + * offset that should be applied in the x direction that should be + * applied after the x position has been figured out. optional. defaults + * to 0. yOffset - a fixed offset that should be applied in the y + * direction that should be applied after the y position has been + * figured out. optional. defaults to 0. + * -- OR -- + * + * params - {x:..., y:..., xOrientation etc } + * -- OR FROM 1.2.4 --- + * + * name - the name of some Anchor in the _currentInstance.Anchors array. + * -- OR FROM 1.2.4 --- + * + * coords - a list of coords for the anchor, like you would pass to + * jsPlumb.makeAnchor (eg [0.5,0.5,0,-1] - an anchor in the center of + * some element, oriented towards the top of the screen) + * -- OR FROM 1.2.4 --- + * + * anchor - an existing anchor. just gets passed back. it's handy + * internally to have this functionality. + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed + // in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards + // compatibility if we are given only one value we assume it's a + // call in the old form. + if (arguments.length == 0) return null; + var params = {}; + if (arguments.length == 1) { + var specimen = arguments[0]; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; + // is it the name of an anchor type? + else if (typeof specimen == "string") return _currentInstance.Anchors[arguments[0]](); + // is it an array of coordinates? + else if (specimen.constructor == Array) { + if (specimen[0].constructor == Array || specimen[0].constructor == String) + return new DynamicAnchor(specimen); + else + return jsPlumb.makeAnchor.apply(this, specimen); + } + // last we try the backwards compatibility stuff. + else if (typeof arguments[0] == "object") jsPlumb.extend(params, x); + } else { + params = { x : x, y : y }; + if (arguments.length >= 4) params.orientation = [ arguments[2], arguments[3] ]; + if (arguments.length == 6) params.offsets = [ arguments[4], arguments[5] ]; + } + var a = new Anchor(params); + a.clone = function() { + return new Anchor(params); + }; + return a; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types) { + var r = []; + for ( var i = 0; i < types.length; i++) + if (typeof types[i] == "string") + r.push(_currentInstance.Anchors[types[i]]()); + else if (types[i].constructor == Array) + r.push(jsPlumb.makeAnchor(types[i])); + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /** + * Function: makeTarget + * Makes some DOM element a Connection target, allowing you to drag connections to it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a target. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * scope optional. scope for the drop zone. + * dropOptions optional. same stuff as you would pass to dropOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to false. whether or not to delete + * any Endpoints created by a connection to this target if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + this.makeTarget = function(el, params, referenceParams) { + + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + var jpcl = jsPlumb.CurrentLibrary, + scope = p.scope || _currentInstance.Defaults.Scope, + deleteEndpointsOnDetach = p.deleteEndpointsOnDetach || false, + _doOne = function(_el) { + var dropOptions = jsPlumb.extend({}, p.dropOptions || {}); + var _drop = function() { + var draggable = _getElementObject(jpcl.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + // restore the original scope if necessary (issue 57) + scope = _getAttribute(draggable, "originalScope"); + + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + // get the connection, to then get its endpoint + var jpc = floatingConnections[id], + source = jpc.endpoints[0], + _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : null, + // make a new Endpoint + newEndpoint = jsPlumb.addEndpoint(_el, _endpoint); + + var c = jsPlumb.connect({ + source:source, + target:newEndpoint, + scope:scope + }); + if (deleteEndpointsOnDetach) + c.endpointToDeleteOnDetach = newEndpoint; + }; + + var dropEvent = jpcl.dragEvents['drop']; + dropOptions["scope"] = dropOptions["scope"] || scope; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop); + + jpcl.initDroppable(_el, dropOptions); + }; + + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements drop targets. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeTargets = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeTarget(els[i], params, referenceParams); + } + }; + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + for ( var i in ebe) + _currentInstance.deleteEndpoint(ebe[i]); + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + this.deleteEveryEndpoint(); + this.clearListeners(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + + Parameters: + value - whether or not to automatically repaint when the window is resized. + + Returns: void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setDraggableByDefault + * Sets whether or not elements are draggable by default. Default for this is true. + * + * Parameters: + * draggable - value to set + * + * Returns: + * void + */ + this.setDraggableByDefault = function(draggable) { + _draggableByDefault = draggable; + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction + * Sets the function to fire when the window size has changed and a repaint was fired. + * + * Parameters: + * f - Function to execute. + * + * Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: setMouseEventsEnabled + * Sets whether or not mouse events are enabled. Default is true. + * + * Parameters: + * enabled - whether or not mouse events should be enabled. + * + * Returns: + * void + */ + this.setMouseEventsEnabled = function(enabled) { + _mouseEventsEnabled = enabled; + }; + + /* + * Constant for use with the setRenderMode method + */ + this.CANVAS = "canvas"; + + /* + * Constant for use with the setRenderMode method + */ + this.SVG = "svg"; + + this.VML = "vml"; + + /* + * Function: setRenderMode + * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that + * what you asked for is not supported (and that VML is). If you asked for VML but the browser does + * not support it, jsPlumb uses SVG. + * + * Returns: + * the render mode that jsPlumb set, which of course may be different from that requested. + */ + this.setRenderMode = function(mode) { + if (mode) + mode = mode.toLowerCase(); + else + return; + if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); + // now test we actually have the capability to do this. + if (mode === jsPlumb.CANVAS && canvasAvailable) + renderMode = jsPlumb.CANVAS; + else if (mode === jsPlumb.SVG && svgAvailable) + renderMode = jsPlumb.SVG; + else if (vmlAvailable) + renderMode = jsPlumb.VML; + + return renderMode; + }; + + this.getRenderMode = function() { return renderMode; }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * + * Returns: + * void + */ + this.show = function(el) { + _setVisible(el, "block"); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId, + makeAnchor:self.makeAnchor, + makeDynamicAnchor:self.makeDynamicAnchor + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload + * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element. + * + * Returns: + * void + */ + this.unload = function() { + delete endpointsByElement; + delete endpointsByUUID; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + delete canvasList; + }; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + if (timestamp && timestamp === self.timestamp) { + return lastReturnValue; + } + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + //* + var container = element ? element.parent : null; + if (container != null) { + var parentPosition = element.canvas.parentNode.style.position; + var parentIsPositioned = parentPosition != null && parentPosition != "" && parentPosition != "static"; + if (parentIsPositioned) { + console.log("positioning"); + var o = _getOffset(element.canvas.parentNode); + lastReturnValue[0] = lastReturnValue[0] - o.left; + lastReturnValue[1] = lastReturnValue[1] - o.top; + } + } + //*/ + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + * + * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(_getElementObject(refCanvas)); + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + var orientation = null; + var _lastResult = null; + + this.compute = function(params) { + var xy = params.xy, element = params.element; + var result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + //* + var container = element ? element.parent : null; + if (container != null) { + var parentPosition = element.canvas.parentNode.style.position; + var parentIsPositioned = parentPosition != null && parentPosition != "" && parentPosition != "static"; + if (parentIsPositioned) { + var o = _getOffset(element.canvas.parentNode); + result[0] = result[0] - o.left; + result[1] = result[1] - o.top; + } + } + //*/ + + _lastResult = result; + return result; + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { orientation = anchor.getOrientation(); }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchors is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = []; + var _convert = function(anchor) { return anchor.constructor == Anchor ? anchor: jsPlumb.makeAnchor(anchor); }; + for (var i = 0; i < anchors.length; i++) _anchors[i] = _convert(anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null; + var _curIndex = _anchors.length > 0 ? 0 : -1; + this.locked = false; + var self = this; + + // helper method to calculate the distance between the centers of the two elements. + var _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }; + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + var _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + + var pos = _curAnchor.compute(params); + return pos; + }; + + this.getCurrentLocation = function() { + var cl = _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + return cl; + }; + + this.getOrientation = function() { return _curAnchor != null ? _curAnchor.getOrientation() : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * overlays - Optional array of Overlay definitions to appear on this Connection. + */ + var Connection = function(params) { + + jsPlumbUIComponent.apply(this, arguments); + // ************** get the source and target and register the connection. ******************* + var self = this; + var visible = true; + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none"; + }; + var id = new String('_jsplumb_c_' + (new Date()).getTime()); + this.getId = function() { return id; }; + this.parent = params.parent; + /** + Property: source + The source element for this Connection. + */ + this.source = _getElementObject(params.source); + /** + Property:target + The target element for this Connection. + */ + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. + if (params.sourceEndpoint) this.source = params.sourceEndpoint.getElement(); + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + /* + * Property: sourceId + * Id of the source element in the connection. + */ + this.sourceId = _getAttribute(this.source, "id"); + /* + * Property: targetId + * Id of the target element in the connection. + */ + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + + /** + * implementation of abstract method in EventGenerator + * @return list of attached elements. in our case, a list of Endpoints. + */ + this.getAttachedElements = function() { + return self.endpoints; + }; + + /** + * implementation of abstract method in EventGenerator + */ + var srcWhenMouseDown = null, targetWhenMouseDown = null; + this.savePosition = function() { + srcWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.source)); + targetWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.target)); + }; + + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams) { + if (anchorParams) + return jsPlumb.makeAnchor(anchorParams); + }; + var prepareEndpoint = function(existing, index, params, element, connectorPaintStyle, connectorHoverPaintStyle) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] + || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint; + + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified. + if (es.fillStyle == null && connectorPaintStyle != null) + es.fillStyle = connectorPaintStyle.strokeStyle; + + // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does: + //* + if (es.outlineColor == null && connectorPaintStyle != null) + es.outlineColor = connectorPaintStyle.outlineColor; + if (es.outlineWidth == null && connectorPaintStyle != null) + es.outlineWidth = connectorPaintStyle.outlineWidth; + //*/ + + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure? + if (connectorHoverPaintStyle != null) { + if (ehs == null) ehs = {}; + if (ehs.fillStyle == null) { + ehs.fillStyle = connectorHoverPaintStyle.strokeStyle; + } + } + var a = params.anchors ? params.anchors[index] : _makeAnchor(_currentInstance.Defaults.Anchors[index]) || _makeAnchor(jsPlumb.Defaults.Anchors[index]) || _makeAnchor(_currentInstance.Defaults.Anchor) || _makeAnchor(jsPlumb.Defaults.Anchor); + var u = params.uuids ? params.uuids[index] : null; + var e = _newEndpoint( { paintStyle : es, hoverPaintStyle:ehs, endpoint : ep, connections : [ self ], uuid : u, anchor : a, source : element }); + self.endpoints[index] = e; + + if (params.drawEndpoints === false) e.setVisible(false, true, true); + + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source, params.paintStyle, params.hoverPaintStyle); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target, params.paintStyle, params.hoverPaintStyle); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + /* + * Function: setConnector + * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method - the same + * thing that you would set as the 'connector' property on a jsPlumb.connect call. + * + * Parameters: + * connector - Connector definition + */ + this.setConnector = function(connector, doNotRepaint) { + if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent); + var connectorArgs = { _jsPlumb:self._jsPlumb, parent:params.parent, cssClass:params.cssClass }; + if (connector.constructor == String) + this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand. + else if (connector.constructor == Array) + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs)); + this.canvas = this.connector.canvas; + var _mouseDown = false, _mouseWasDown = false, _mouseDownAt = null; + // add mouse events + this.connector.bind("click", function(con, e) { + _mouseWasDown = false; + self.fire("click", self, e); + }); + this.connector.bind("dblclick", function(con, e) { _mouseWasDown = false;self.fire("dblclick", self, e); }); + this.connector.bind("mouseenter", function(con, e) { + if (!self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(true); + } + self.fire("mouseenter", self, e); + } + }); + this.connector.bind("mouseexit", function(con, e) { + if (self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(false); + } + self.fire("mouseexit", self, e); + } + }); + this.connector.bind("mousedown", function(con, e) { + _mouseDown = true; + _mouseDownAt = jsPlumb.CurrentLibrary.getPageXY(e); + self.savePosition(); + }); + this.connector.bind("mouseup", function(con, e) { + _mouseDown = false; + if (self.connector == _connectionBeingDragged) _connectionBeingDragged = null; + }); + + if (!doNotRepaint) self.repaint(); + }; + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc) + */ + + self.setConnector(this.endpoints[0].connector || + this.endpoints[1].connector || + params.connector || + _currentInstance.Defaults.Connector || + jsPlumb.Defaults.Connector, true); + + this.setPaintStyle(this.endpoints[0].connectorStyle || + this.endpoints[1].connectorStyle || + params.paintStyle || + _currentInstance.Defaults.PaintStyle || + jsPlumb.Defaults.PaintStyle, true); + + this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || + this.endpoints[1].connectorHoverStyle || + params.hoverPaintStyle || + _currentInstance.Defaults.HoverPaintStyle || + jsPlumb.Defaults.HoverPaintStyle, true); + + this.paintStyleInUse = this.paintStyle; + + /* + * Property: overlays + * List of Overlays for this Connection. + */ + this.overlays = []; + var _overlays = params.overlays || _currentInstance.Defaults.Overlays; + if (_overlays) { + for (var i = 0; i < _overlays.length; i++) { + var o = _overlays[i], _newOverlay = null, _overlayEvents = null; + if (o.constructor == Array) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0]; + var p = jsPlumb.CurrentLibrary.extend({connection:self, _jsPlumb:_currentInstance}, o[1]); // make a copy of the object so as not to mess up anyone else's reference... + if (o.length == 3) jsPlumb.CurrentLibrary.extend(p, o[2]); + _newOverlay = new jsPlumb.Overlays[renderMode][type](p); + if (p.events) { + for (var evt in p.events) { + _newOverlay.bind(evt, p.events[evt]); + } + } + } else if (o.constructor == String) { + _newOverlay = new jsPlumb.Overlays[renderMode][o]({connection:self, _jsPlumb:_currentInstance}); + } else { + _newOverlay = o; + } + + + + this.overlays.push(_newOverlay); + } + } + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Connection. + * + * Parameters: + * event - the event to bind. Available events on a Connection are: + * - *click* : notification that a Connection was clicked. + * - *dblclick* : notification that a Connection was double clicked. + * - *mouseenter* : notification that the mouse is over a Connection. + * - *mouseexit* : notification that the mouse exited a Connection. + * + * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Connection's paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Connection will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Connection. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + this.addOverlay = function(overlay) { self.overlays.push(overlay); }; + + /** + * Function: removeAllOverlays + * Removes all overlays from the Connection, and then repaints. + */ + this.removeAllOverlays = function() { + self.overlays.splice(0, self.overlays.length); + self.repaint(); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + this.removeOverlay = function(overlayId) { + var idx = -1; + for (var i = 0; i < self.overlays.length; i++) { + if (overlayId === self.overlays[i].id) { + idx = i; + break; + } + } + if (idx != -1) self.overlays.splice(idx, 1); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + this.removeOverlays = function() { + for (var i = 0; i < arguments.length; i++) + self.removeOverlay(arguments[i]); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + this.labelStyle = params.labelStyle || _currentInstance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + if (this.label) { + this.overlays.push(new jsPlumb.Overlays[renderMode].Label( { + cssClass:params.cssClass, + labelStyle : this.labelStyle, + label : this.label, + connection:self, + _jsPlumb:_currentInstance + })); + } + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String or a Function that returns a String. + */ + this.setLabel = function(l) { + self.label = l; + _currentInstance.repaint(self.source); + }; + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var otherOffset = offsets[this.targetId]; + var otherWH = sizes[this.targetId]; + var anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1] + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0] + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc }); + + /* + * Paints the Connection. Not exposed for public usage. + * + * Parameters: + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp; + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = false; + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }); + _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + var sAnchorP = this.endpoints[sIdx].anchor.getCurrentLocation(); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.getCurrentLocation(); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + + /* paint overlays*/ + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + var s = o.computeMaxSize(self.connector); + if (s > maxSize) + maxSize = s; + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, self.paintStyleInUse.lineWidth, maxSize); + + self.connector.paint(dim, self.paintStyleInUse); + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim); + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function() { + this.paint({ elId : this.sourceId, recalc : true }); + }; + + _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide + // whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + + // just to make sure the UI gets initialised fully on all browsers. + self.repaint(); + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1 + * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't + * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you + * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or + * 'target' Endpoints for Connections. + * + * + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * connections - optional list of Connections to configure the Endpoint with. + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + params = params || {}; + var self = this; +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Endpoint. + * + * Parameters: + * event - the event to bind. Available events on an Endpoint are: + * - *click* : notification that a Endpoint was clicked. + * - *dblclick* : notification that a Endpoint was double clicked. + * - *mouseenter* : notification that the mouse is over a Endpoint. + * - *mouseexit* : notification that the mouse exited a Endpoint. + * + * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Endpoint's paint style and then repaints the Endpoint. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Endpoint will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Endpoint. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + var visible = true; + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + var id = new String('_jsplumb_e_' + (new Date()).getTime()); + this.getId = function() { return id; }; + if (params.dynamicAnchors) + self.anchor = new DynamicAnchor(jsPlumb.makeAnchors(params.dynamicAnchors)); + else + self.anchor = params.anchor ? jsPlumb.makeAnchor(params.anchor) : params.anchors ? jsPlumb.makeAnchor(params.anchors) : jsPlumb.makeAnchor("TopCenter"); + var _endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot", + endpointArgs = { _jsPlumb:self._jsPlumb, parent:params.parent }; + if (_endpoint.constructor == String) + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint](endpointArgs); + else if (_endpoint.constructor == Array) + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint[0]](jsPlumb.extend(_endpoint[1], endpointArgs )); + else _endpoint = _endpoint.clone(); + self.endpoint = _endpoint; + this.endpoint.bind("click", function(e) { self.fire("click", self, e); }); + this.endpoint.bind("dblclick", function(e) { self.fire("dblclick", self, e); }); + this.setPaintStyle(params.paintStyle || + params.style || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyle, true); + this.setHoverPaintStyle(params.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyle, true); + this.paintStyleInUse = this.paintStyle; + this.connectorStyle = params.connectorStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.parent = params.parent; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + var _element = params.source, + _uuid = params.uuid, + floatingEndpoint = null, + inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + this.element = _element; + var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + /* + * Property: canvas + * The Endpoint's Canvas. + */ + this.canvas = this.endpoint.canvas; + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + this.connections = params.connections || []; + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + var _reattach = params.reattach || false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + + // this avoids a circular loop + if (!ignoreTarget) { + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + t.detach(connection, true); + // check connection to see if we want to delete the other endpoint. + // if the user uses makeTarget to make some element a target for connections, + // it is possible that they will have set 'endpointToDeleteOnDetach': when + // you make a connection to an element that acts as a target (note: NOT an + // Endpoint; just some div as a target), Endpoints are created for that + // connection. so if you then delete that Connection, it is feasible you + // will want these auto-generated endpoints to be removed. + if (connection.endpointToDeleteOnDetach && connection.endpointToDeleteOnDetach.connections.length == 0) + jsPlumb.deleteEndpoint(connection.endpointToDeleteOnDetach); + } + _removeElements(connection.connector.getDisplayElements(), connection.parent); + _removeFromList(connectionsByScope, connection.scope, connection); + if(!ignoreTarget) fireDetachEvent(connection); + } + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + */ + this.detachAll = function() { + while (self.connections.length > 0) { + self.detach(self.connections[0]); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + */ + this.detachFrom = function(targetEndpoint) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + c[i].setHover(false); + self.detach(c[i]); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + return _newEndpoint( { anchor : self.anchor, source : _element, paintStyle : this.paintStyle, endpoint : _endpoint }); + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + return (self.connections.length < _maxConnections) ? null : self.connections[0]; + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + /* + * Function: setStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to + * setStyle and deprecate it if so. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + * + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + /* + * Function: paint + * Paints the Endpoint, recalculating offset and anchor positions if necessary. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + + params = params || {}; + var timestamp = params.timestamp; + if (!timestamp || self.timestamp !== timestamp) { + var ap = params.anchorPoint, canvas = params.canvas, connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + var xy = params.offset || offsets[_elementId]; + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (self.anchor.isDynamic) { + if (self.connections.length > 0) { + var c = self.connections[0]; + var oIdx = c.endpoints[0] == self ? 1 : 0; + var oId = oIdx == 0 ? c.sourceId : c.targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + } + ap = self.anchor.compute(anchorParams); + } + + var d = _endpoint.compute(ap, self.anchor.getOrientation(), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + _endpoint.paint(d, self.paintStyleInUse, self.anchor); + + self.timestamp = timestamp; + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + var start = function() { + jpc = self.connectorSelector(); + if (self.isFull() && !dragAllowedWhenFull) return false; + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + n.style.position = "absolute"; + var nE = _getElementObject(n); + _appendElement(n, self.parent); + // create and assign an id, and initialize the offset. + var id = _getId(nE); + + // set the offset of this div to be where 'inPlaceCopy' is, to start with. + var ipcoel = _getElementObject(inPlaceCopy.canvas), + ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel), + ipcs = jsPlumb.CurrentLibrary.getSize(ipcoel); + jsPlumb.CurrentLibrary.setOffset(n, ipco); + + _updateOffset( { elId : id }); + + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor( { reference : self.anchor, referenceCanvas : self.canvas }); + floatingEndpoint = _newEndpoint({ paintStyle : self.paintStyle, endpoint : _endpoint, anchor : floatingAnchor, source : nE }); + + if (jpc == null) { + self.anchor.locked = true; + // create a connection. one end is this endpoint, the + // other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : _getElementObject(_element), + target : _getElementObject(n), + anchors : [ self.anchor, floatingAnchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays + }); + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.connector.setHover(false); + } else { + existingJpc = true; + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.connector.setHover(false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas)); + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; // are we the source or the target? + + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas); + var dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // get a new, temporary scope, to use (issue 57) + var newScope = "scope_" + (new Date()).getTime(); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + // set the new, temporary scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(i, newScope); + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // TODO unregister on stop? or will floating endpoint's + // destruction be assured. + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be + // dragging the source at any time + // before this connection is either discarded or made into a + // permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var jpcl = jsPlumb.CurrentLibrary, + dragOptions = params.dragOptions || {}, + defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions), + startEvent = jpcl.dragEvents['start'], + stopEvent = jpcl.dragEvents['stop'], + dragEvent = jpcl.dragEvents['drag']; + + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], + function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + jsPlumb.CurrentLibrary.setOffset(n, _ui); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements( [ n, floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (_reattach) { + + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } else { + jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc); // the main endpoint will inform the floating endpoint + // to disconnect, and also post the detached event. + } + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElements(jpc.connector.getDisplayElements(), self.parent); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint(); + jpc.setHover(false); + jpc.repaint(); + jpc = null; + delete inPlaceCopy; + delete endpointsByElement[floatingEndpoint.elementId]; + floatingEndpoint = null; + delete floatingEndpoint; + + _currentInstance.currentlyDragging = false; + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas) { + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + var drop = function() { + var draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)); + var id = _getAttribute(draggable, "dragId"); + var elId = _getAttribute(draggable, "elId"); + + // restore the original scope if necessary (issue 57) + var scope = _getAttribute(draggable, "originalScope"); + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + var jpc = floatingConnections[id]; + + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // todo test that the target is not full. + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + if (!jpc.suspendedEndpoint) { + _addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + _currentInstance.fire("jsPlumbConnectionDetached", { + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }); + } + + jsPlumb.repaint(elId); + + _currentInstance.fire("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], + targetEndpoint : jpc.endpoints[1], + connection:jpc + }); + } + + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute( _getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + } + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute(_getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + } + }); + + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas)); + + return self; + }; + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + //if (_defaults) jsPlumb.extend(j.Defaults, _defaults); + return j; + }; + + var _curryAnchor = function(x,y,ox,oy) { + return function() { + return jsPlumb.makeAnchor(x,y,ox,oy); + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1); + + jsPlumb.Defaults.DynamicAnchors = function() { + return jsPlumb.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"]); + }; + jsPlumb.Anchors["AutoDefault"] = function() { return jsPlumb.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors()); }; + + +})(); +/* +* jsPlumb-defaults-1.3.0-RC1 +* +* Copyright 2010 - 2011 Simon Porritt http://jsplumb.org +* +* Triple licensed under the MIT, GPL2 and Beer licenses. +*/ + +(function() { + + + + /** + * + * Helper class to consume unused mouse events by components that are DOM elements and + * are used by all of the different rendering modes. + * + */ + jsPlumb.DOMElementComponent = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + // when render mode is canvas, these functions may be called by the canvas mouse handler. + // this component is safe to pipe this stuff to /dev/null. + this.mousemove = + this.dblclick = + this.click = + this.mousedown = + this.mouseup = function(e) { }; + }; + + /** + * Class: Connectors.Straight + * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + var currentPoints = null; + var _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + _dx = _tx - _sx, _dy = (_ty - _sy); + _m = _dy / _dx, _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + + return currentPoints; + }; + + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + var xp = _sx + (location * _dx); + var yp = (_m == Infinity || _m == -Infinity) ? _sy + (location * (_ty - _sy)) : (_m * xp) + _b; + return {x:xp, y:yp}; + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { return _m; }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + var orientation = distance > 0 ? 1 : -1; + var y = Math.abs(distance * Math.sin(_theta)); + if (_sy > _ty) y = y * -1; + var x = Math.abs(distance * Math.cos(_theta)); + if (_sx > _tx) x = x * -1; + return {x:p.x + (orientation * x), y:p.y + (orientation * y)}; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = self.gradientAtPoint(p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + }; + }; + + + /** + * Class:Connectors.Bezier + * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's + * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below. + */ + /** + * Function:Constructor + * + * Parameters: + * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line. + * Optional; defaults to 150. + * + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + params = params || {}; + this.majorAnchor = params.curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h; + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) + { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx); var minx2 = Math.min(_CP[0], _CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(_sx,_tx); var maxx2 = Math.max(_CP[0], _CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty); var miny2 = Math.min(_CP[1], _CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(_sy,_ty); var maxy2 = Math.max(_CP[1], _CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + return jsBezier.perpendicularToCurveAt(_makeCurve(), location, length, distance); + }; + + }; + + + /** + * Class: Connectors.Flowchart + * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments. + */ + /** + * Function: Constructor + * + * Parameters: + * stub - minimum length for the stub at each end of the connector. defaults to 30 pixels. + */ + jsPlumb.Connectors.Flowchart = function(params) { + params = params || {}; + var self = this, + minStubLength = params.stub || params.minStubLength /* bwds compat. */ || 30, + segments = [], + segmentGradients = [], + segmentProportions = [], + segmentLengths = [], + segmentProportionalLengths = [], + points = [], + swapX, + swapY, + /** + * recalculates the gradients of each segment, and the points at which the segments begin, proportional to the total length travelled + * by all the segments that constitute the connector. + */ + updateSegmentGradientsAndProportions = function(startX, startY, endX, endY) { + var total = 0; + for (var i = 0; i < segments.length; i++) { + var sx = i == 0 ? startX : segments[i][2], + sy = i == 0 ? startY : segments[i][3], + ex = segments[i][0], + ey = segments[i][1]; + + segmentGradients[i] = sx == ex ? Infinity : 0; + segmentLengths[i] = Math.abs(sx == ex ? ey - sy : ex - sx); + total += segmentLengths[i]; + } + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segmentLengths[i] / total; + segmentProportions[i] = [curLoc, (curLoc += (segmentLengths[i] / total)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0]; + var ly = segments.length == 0 ? sy : segments[segments.length - 1][1]; + segments.push([x, y, lx, ly]); + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment + * that contains the point which is 'location' distance along the entire path, where + * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths + * are made up of a list of segments, each of which contributes some fraction to + * the total length. + */ + findSegmentForLocation = function(location) { + var idx = segmentProportions.length - 1, inSegmentProportion = 0; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + segments = []; + segmentGradients = []; + segmentProportionalLengths = []; + segmentLengths = []; + segmentProportionals = []; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + offx = (lw / 2) + (minStubLength * 2), + offy = (lw / 2) + (minStubLength * 2), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(), + to = targetAnchor.orientation || targetAnchor.getOrientation(), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx, + h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy; + if (w < minWidth) { + offx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + offy += (minWidth - h) / 2; + h = minWidth; + } + var sx = swapX ? w-offx : offx, + sy = swapY ? h-offy : offy, + tx = swapX ? offx : w-offx , + ty = swapY ? offy : h-offy, + startStubX = sx + (so[0] * minStubLength), + startStubY = sy + (so[1] * minStubLength), + endStubX = tx + (to[0] * minStubLength), + endStubY = ty + (to[1] * minStubLength), + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2); + + x -= offx; y -= offy; + points = [x, y, w, h, sx, sy, tx, ty], extraPoints = []; + + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + if (so[0] == 0) { + var startStubIsBeforeEndStub = startStubY < endStubY; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } + } + else { + var startStubIsBeforeEndStub = startStubX < endStubX; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } + } + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentGradientsAndProportions(sx, sy, tx, ty); + + return points; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location) { + return self.pointAlongPathFrom(location, 0); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location) { + return segmentGradients[findSegmentForLocation(location)["index"]]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance) { + var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segmentLengths[s.index], m = segmentGradients[s.index]; + var e = { + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + return e; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = segmentGradients[p.segmentInfo.index]; + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + + }; + + }; + + // ********************************* END OF CONNECTOR TYPES ******************************************************************* + + // ********************************* ENDPOINT TYPES ******************************************************************* + + /** + * Class: Endpoints.Dot + * A round endpoint, with default radius 10 pixels. + */ + + /** + * Function: Constructor + * + * Parameters: + * + * radius - radius of the endpoint. defaults to 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + var self = this; + params = params || {}; + this.radius = params.radius || 10; + this.defaultOffset = 0.5 * this.radius; + this.defaultInnerRadius = this.radius / 3; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var r = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - r; + var y = anchorPoint[1] - r; + return [ x, y, r * 2, r * 2, r ]; + }; + }; + + /** + * Class: Endpoints.Rectangle + * A Rectangular Endpoint, with default size 20x20. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the endpoint. defaults to 20 pixels. + * height - height of the endpoint. defaults to 20 pixels. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + var self = this; + params = params || {}; + this.width = params.width || 20; + this.height = params.height || 20; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + return [ x, y, width, height]; + }; + }; + + /** + * Class: Endpoints.Image + * Draws an image as the Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * src - location of the image to use. + */ + jsPlumb.Endpoints.Image = function(params) { + + jsPlumb.DOMElementComponent.apply(this, arguments); + + var self = this, initialized = false; + this.img = new Image(); + self.ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.src || params.url; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + self.anchorPoint = anchorPoint; + if (self.ready) return [anchorPoint[0] - self.img.width / 2, anchorPoint[1] - self.img.height/ 2, self.img.width, self.img.height]; + else return [0,0,0,0]; + }; + + self.canvas = document.createElement("img"), initialized = false; + self.canvas.style["margin"] = 0; + self.canvas.style["padding"] = 0; + self.canvas.style["outline"] = 0; + self.canvas.style["position"] = "absolute"; + self.canvas.className = jsPlumb.endpointClass; + jsPlumb.appendElement(self.canvas, params.parent); + self.attachListeners(self.canvas, self); + + var actuallyPaint = function(d, style, anchor) { + if (!initialized) { + self.canvas.setAttribute("src", self.img.src); + initialized = true; + } + var width = self.img.width, + height = self.img.height, + x = self.anchorPoint[0] - (width/2), + y = self.anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(self.canvas, x, y, width, height); + }; + + this.paint = function(d, style, anchor) { + if (self.ready) { + actuallyPaint(d, style, anchor); + } + else { + window.setTimeout(function() { + self.paint(d, style, anchor); + }, 200); + } + }; + }; + + /** + * Class: Endpoints.Blank + * An Endpoint that paints nothing on the screen, and cannot be interacted with using the mouse. There are no constructor parameters for this Endpoint. + */ + jsPlumb.Endpoints.Blank = function() { + jsPlumb.DOMElementComponent.apply(this, arguments); + this.compute = function() { + return [0,0,0,0]; + }; + + self.canvas = document.createElement("div"); + this.paint = function() { }; + }; + + /** + * Class: Endpoints.Triangle + * A triangular Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the triangle's base. defaults to 55 pixels. + * height - height of the triangle from base to apex. defaults to 55 pixels. + */ + jsPlumb.Endpoints.Triangle = function(params) { + params = params || { }; + params.width = params.width || 55; + param.height = params.height || 55; + this.width = params.width; + this.height = params.height; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + return [ x, y, width, height ]; + }; + }; +// ********************************* END OF ENDPOINT TYPES ******************************************************************* + + +// ********************************* OVERLAY DEFINITIONS *********************************************************************** + + /** + * Class: Overlays.Arrow + * + * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length + * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction + * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line + * across the tail. + */ + /** + * Function: Constructor + * + * Parameters: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + params = params || {}; + var self = this; + this.length = params.length || 20; + this.width = params.width || 20; + this.id = params.id; + this.connection = params.connection; + var direction = (params.direction || 1) < 0 ? -1 : 1; + var paintStyle = params.paintStyle || { lineWidth:1 }; + this.loc = params.location == null ? 0.5 : params.location; + // how far along the arrow the lines folding back in come to. default is 62.3%. + var foldback = params.foldback || 0.623; + var _getFoldBackPoint = function(connector, loc) { + if (foldback == 0.5) return connector.pointOnPath(loc); + else { + var adj = 0.5 - foldback; // we calculate relative to the center + return connector.pointAlongPathFrom(loc, direction * self.length * adj); + } + }; + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + // this is the arrow head position + var hxy = connector.pointAlongPathFrom(self.loc, direction * (self.length / 2)); + // this is the center of the tail + var txy = connector.pointAlongPathFrom(self.loc, -1 * direction * (self.length / 2)), tx = txy.x, ty = txy.y; + // this is the tail vector + var tail = connector.perpendicularToPathAt(self.loc, self.width, -1 * direction * (self.length / 2)); + // this is the point the tail goes in to + var cxy = _getFoldBackPoint(connector, self.loc); + + // if loc = 1, then hxy should be flush with the element, or if direction == -1, the tail midpoint. + if (self.loc == 1) { + var lxy = connector.pointOnPath(self.loc); + // TODO determine why the 1.2.6 released version does not + // use 'direction' in the two equations below, yet both + // that and 1.3.0 still paint the arrows correctly. + var dx = (lxy.x - hxy.x) * direction, dy = (lxy.y - hxy.y) * direction; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + // if loc = 0, then tail midpoint should be flush with the element, or, if direction == -1, hxy should be. + if (self.loc == 0) { + var lxy = connector.pointOnPath(self.loc); + var tailMid = foldback > 1 ? cxy : { + x:tail[0].x + ((tail[1].x - tail[0].x) / 2), + y:tail[0].y + ((tail[1].y - tail[0].y) / 2) + }; + var dx = (lxy.x - tailMid.x) * direction, dy = (lxy.y - tailMid.y) * direction; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x); + var maxx = Math.max(hxy.x, tail[0].x, tail[1].x); + var miny = Math.min(hxy.y, tail[0].y, tail[1].y); + var maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + var d = { hxy:hxy, tail:tail, cxy:cxy }, + strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle, + fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle, + lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth; + + self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions); + + return [ minx, maxx, miny, maxy]; + }; + }; + + /** + * Class: Overlays.PlainArrow + * + * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + /** + * Class: Overlays.Diamond + * + * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40; + var p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + + + /** + * Class: Overlays.Label + * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb + * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter + * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it + * puts on the Label's 'style' attribute, so the end result is the same. + */ + /** + * Function: Constructor + * + * Parameters: + * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes + * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle. + * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * labelStyle - (deprecated) js object containing style instructions for the label. defaults to jsPlumb.Defaults.LabelStyle. + * borderWidth - (deprecated) width of a border to paint. defaults to zero. + * borderStyle - (deprecated) strokeStyle to use when painting the border, if necessary. + * + */ + jsPlumb.Overlays.Label = function(params) { + jsPlumb.DOMElementComponent.apply(this, arguments); + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.labelStyle.font = this.labelStyle.font || "12px sans-serif"; + this.label = params.label; + this.connection = params.connection; + this.id = params.id; + var self = this; + var labelWidth = null, labelHeight = null, labelText = null, labelPadding = null; + this.location = params.location || 0.5; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var initialised = false, + labelText = null, + div = document.createElement("div"); + div.style["position"] = "absolute"; + div.style["textAlign"] = "center"; + div.style["cursor"] = "pointer"; + div.style["font"] = self.labelStyle.font; + div.style["color"] = self.labelStyle.color || "black"; + if (self.labelStyle.fillStyle) div.style["background"] = self.labelStyle.fillStyle;//_convertStyle(self.labelStyle.fillStyle, true); + if (self.labelStyle.borderWidth > 0) { + var dStyle = self.labelStyle.borderStyle ? self.labelStyle.borderStyle/*_convertStyle(self.labelStyle.borderStyle, true)*/ : "black"; + div.style["border"] = self.labelStyle.borderWidth + "px solid " + dStyle; + } + if (self.labelStyle.padding) div.style["padding"] = self.labelStyle.padding; + + var clazz = params["_jsPlumb"].overlayClass + " " + + (self.labelStyle.cssClass ? self.labelStyle.cssClass : + params.cssClass ? params.cssClass : ""); + + div.className = clazz; + + jsPlumb.appendElement(div, params.connection.parent); + jsPlumb.getId(div); + self.attachListeners(div, self); + this.paint = function(connector, d, connectorDimensions) { + if (!initialised) { + connector.appendDisplayElement(div); + self.attachListeners(div, connector); + initialised = true; + } + div.style.left = (connectorDimensions[0] + d.minx) + "px"; + div.style.top = (connectorDimensions[1] + d.miny) + "px"; + }; + + this.getTextDimensions = function(connector) { + labelText = typeof self.label == 'function' ? self.label(self) : self.label; + div.innerHTML = labelText.replace(/\r\n/g, "
"); + var de = jsPlumb.CurrentLibrary.getElementObject(div), + s = jsPlumb.CurrentLibrary.getSize(de); + return {width:s[0], height:s[1]}; + }; + + this.computeMaxSize = function(connector) { + var td = self.getTextDimensions(connector); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + var td = self.getTextDimensions(connector); + if (td.width != null) { + var cxy = connector.pointOnPath(self.location); + + var minx = cxy.x - (td.width / 2); + var miny = cxy.y - (td.height / 2); + + self.paint(connector, { + minx:minx, + miny:miny, + td:td, + cxy:cxy + }, connectorDimensions); + + return [minx, minx+td.width, miny, miny+td.height]; + } + else return [0,0,0,0]; + }; + }; + + // ********************************* END OF OVERLAY DEFINITIONS *********************************************************************** + + // ********************************* OVERLAY CANVAS RENDERERS*********************************************************************** + + // ********************************* END OF OVERLAY CANVAS RENDERERS *********************************************************************** +})();;(function() { + + // http://ajaxian.com/archives/the-vml-changes-in-ie-8 + // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/ + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + + var vmlAttributeMap = { + "stroke-linejoin":"joinstyle", + "joinstyle":"joinstyle", + "endcap":"endcap", + "miterlimit":"miterlimit" + }; + + if (document.createStyleSheet) { + + // this is the style rule for IE7/6: it uses a CSS class, tidy. + document.createStyleSheet().addRule(".jsplumb_vml", "behavior:url(#default#VML);position:absolute;"); + + // these are for VML in IE8. you have to explicitly call out which elements + // you're going to expect to support VML! + // + // try to avoid IE8. it is recommended you set X-UA-Compatible="IE=7" if you can. + // + document.createStyleSheet().addRule("jsplumb\\:textbox", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:oval", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:rect", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:stroke", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:shape", "behavior:url(#default#VML);position:absolute;"); + + // in this page it is also mentioned that IE requires the extra arg to the namespace + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either. + // var iev = document.documentMode; + //if (!iev || iev < 8) + document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); + //else + // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML"); + } + + var scale = 1000, + _atts = function(o, atts) { + for (var i in atts) { + // IE8 fix: setattribute does not work after an element has been added to the dom! + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + //o.setAttribute(i, atts[i]); + o[i] = atts[i]; + } + }, + _node = function(name, d, atts) { + atts = atts || {}; + var o = document.createElement("jsplumb:" + name); + o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml"; + _pos(o, d); + _atts(o, atts); + return o; + }, + _pos = function(o,d) { + o.style.left = d[0] + "px"; + o.style.top = d[1] + "px"; + o.style.width= d[2] + "px"; + o.style.height= d[3] + "px"; + o.style.position = "absolute"; + }, + _conv = function(v) { + return Math.floor(v * scale); + }, + _convertStyle = function(s, ignoreAlpha) { + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + + return o; + }, + _applyStyles = function(node, style, component) { + var styleToWrite = {}; + if (style.strokeStyle) { + styleToWrite["stroked"] = "true"; + styleToWrite["strokecolor"] =_convertStyle(style.strokeStyle, true); + styleToWrite["strokeweight"] = style.lineWidth + "px"; + } + else styleToWrite["stroked"] = "false"; + + if (style.fillStyle) { + styleToWrite["filled"] = "true"; + styleToWrite["fillcolor"] = _convertStyle(style.fillStyle, true); + } + else styleToWrite["filled"] = "false"; + + if(style["dashstyle"]) { + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = style["dashstyle"]; + } + else if (style["stroke-dasharray"] && style["lineWidth"]) { + var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",", + parts = style["stroke-dasharray"].split(sep), + styleToUse = ""; + for(var i = 0; i < parts.length; i++) { + styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep); + } + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = styleToUse; + } + + _atts(node, styleToWrite); + }, + /* + * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. + */ + VmlComponent = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }, + /* + * Base class for Vml connectors. extends VmlComponent. + */ + VmlConnector = function(params) { + var self = this; + self.strokeNode = null; + self.canvas = null; + VmlComponent.apply(this, arguments); + clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : ""); + this.paint = function(d, style, anchor) { + if (style != null) { + var path = self.getPath(d), p = { "path":path }; + + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + outlineStyle = { + strokeStyle:_convertStyle(style.outlineColor), + lineWidth:outlineStrokeWidth + }; + + if (self.bgCanvas == null) { + p["class"] = clazz; + self.bgCanvas = _node("shape", d, p); + jsPlumb.appendElement(self.bgCanvas, params.parent); + displayElements.push(self.bgCanvas); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.bgCanvas, d); + _atts(self.bgCanvas, p); + } + + _applyStyles(self.bgCanvas, outlineStyle, self); + } + + if (self.canvas == null) { + p["class"] = clazz; + self.canvas = _node("shape", d, p); + jsPlumb.appendElement(self.canvas, params.parent); + displayElements.push(self.canvas); + + self.attachListeners(self.canvas, self); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.canvas, d); + _atts(self.canvas, p); + } + + _applyStyles(self.canvas, style, self); + } + }; + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + self.canvas.parentNode.appendChild(el); + displayElements.push(el); + }; + }, + /* + * + * Base class for Vml Endpoints. extends VmlComponent. + * + */ + VmlEndpoint = function(params) { + VmlComponent.apply(this, arguments); + var vml = null, self = this; + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function(d, style, anchor) { + var p = { }; + + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (vml == null) { + p["class"] = jsPlumb.endpointClass; + vml = self.getVml([0,0, d[2], d[3]], p, anchor); + self.canvas.appendChild(vml); + self.attachListeners(vml, self); + } + else { + //p["coordsize"] = "1,1";//(d[2] * scale) + "," + (d[3] * scale); again, unsure. + _pos(vml, [0,0, d[2], d[3]]); + _atts(vml, p); + } + + _applyStyles(vml, style); + }; + }; + + jsPlumb.Connectors.vml.Bezier = function() { + jsPlumb.Connectors.Bezier.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Straight = function() { + jsPlumb.Connectors.Straight.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Flowchart = function() { + jsPlumb.Connectors.Flowchart.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l"; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]); + } + // finally draw a line to the end + p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e"; + return p; + }; + }; + + jsPlumb.Endpoints.vml.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("oval", d, atts); }; + }; + + jsPlumb.Endpoints.vml.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("rect", d, atts); }; + }; + + /* + * VML Image Endpoint is the same as the default image endpoint. + */ + jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image; + + /** + * placeholder for Blank endpoint in vml renderer. + */ + jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank; + + /** + * VML Label renderer. uses the default label renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label; + + var AbstractVmlArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + VmlComponent.apply(this, arguments); + var self = this, canvas = null, path =null; + var getPath = function(d, connectorDimensions) { + return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) + + " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + + " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + + " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + + " x e"; + }; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) { + var p = {}; + if (strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] =_convertStyle(strokeStyle, true); + } + if (lineWidth) p["strokeweight"] = lineWidth + "px"; + if (fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = fillStyle; + } + var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + w = Math.abs(xmax - xmin), + h = Math.abs(ymax - ymin), + dim = [xmin, ymin, w, h]; + + // for VML, we create overlays using shapes that have the same dimensions and + // coordsize as their connector - overlays calculate themselves relative to the + // connector (it's how it's been done since the original canvas implementation, because + // for canvas that makes sense). + p["path"] = getPath(d, connectorDimensions); + p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale); + + dim[0] = connectorDimensions[0]; + dim[1] = connectorDimensions[1]; + dim[2] = connectorDimensions[2]; + dim[3] = connectorDimensions[3]; + + if (canvas == null) { + //p["class"] = jsPlumb.overlayClass; // TODO currentInstance? + canvas = _node("shape", dim, p); + connector.appendDisplayElement(canvas); + self.attachListeners(canvas, connector); + } + else { + _pos(canvas, dim); + _atts(canvas, p); + } + }; + }; + + jsPlumb.Overlays.vml.Arrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.vml.PlainArrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.vml.Diamond = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/** + * SVG support for jsPlumb. + * + * things to investigate: + * + * gradients: https://developer.mozilla.org/en/svg_in_html_introduction + * css:http://tutorials.jenkov.com/svg/svg-and-css.html + * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath + * pointer events: https://developer.mozilla.org/en/css/pointer-events + * + */ +;(function() { + + var svgAttributeMap = { + "stroke-linejoin":"stroke-linejoin", + "joinstyle":"stroke-linejoin", + "stroke-dashoffset":"stroke-dashoffset" + }; + + var ns = { + svg:"http://www.w3.org/2000/svg", + xhtml:"http://www.w3.org/1999/xhtml" + }, + _attr = function(node, attributes) { + for (var i in attributes) + node.setAttribute(i, "" + attributes[i]); + }, + _node = function(name, attributes) { + var n = document.createElementNS(ns.svg, name); + attributes = attributes || {}; + attributes["version"] = "1.1"; + attributes["xmnls"] = ns.xhtml; + _attr(n, attributes); + return n; + }, + _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; }, + _convertStyle = function(s, ignoreAlpha) { + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + + return o; + }, + _clearGradient = function(parent) { + for (var i = 0; i < parent.childNodes.length; i++) { + if (parent.childNodes[i].tagName == "linearGradient" || parent.childNodes[i].tagName == "radialGradient") + parent.removeChild(parent.childNodes[i]); + } + }, + _updateGradient = function(parent, node, style, dimensions) { + var id = "jsplumb_gradient_" + (new Date()).getTime(); + // first clear out any existing gradient + _clearGradient(parent); + // this checks for an 'offset' property in the gradient, and in the absence of it, assumes + // we want a linear gradient. if it's there, we create a radial gradient. + // it is possible that a more explicit means of defining the gradient type would be + // better. relying on 'offset' means that we can never have a radial gradient that uses + // some default offset, for instance. + if (!style.gradient.offset) { + var g = _node("linearGradient", {id:id}); + parent.appendChild(g); + } + else { + var g = _node("radialGradient", { + id:id + }); + parent.appendChild(g); + } + + // the svg radial gradient seems to treat stops in the reverse + // order to how canvas does it. so we want to keep all the maths the same, but + // iterate the actual style declarations in reverse order, if the x indexes are not in order. + for (var i = 0; i < style.gradient.stops.length; i++) { + // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of + // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments. + // so, not too concerned about leaving this in for now. + var styleToUse = i; + if (dimensions.length == 8) + styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i; + else + styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i; + var stopColor = _convertStyle(style.gradient.stops[styleToUse][1], true); + var s = _node("stop", {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor}); + g.appendChild(s); + } + var applyGradientTo = style.strokeStyle ? "stroke" : "fill"; + node.setAttribute("style", applyGradientTo + ":url(#" + id + ")"); + }, + _applyStyles = function(parent, node, style, dimensions) { + + if (style.gradient) { + _updateGradient(parent, node, style, dimensions); + } + else { + // make sure we clear any existing gradient + _clearGradient(parent); + node.setAttribute("style", ""); + } + + node.setAttribute("fill", style.fillStyle ? _convertStyle(style.fillStyle, true) : "none"); + node.setAttribute("stroke", style.strokeStyle ? _convertStyle(style.strokeStyle, true) : "none"); + if (style.lineWidth) { + node.setAttribute("stroke-width", style.lineWidth); + } + + // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like + // the syntax in VML but is actually kind of nasty: values are given in the pixel + // coordinate space, whereas in VML they are multiples of the width of the stroked + // line, which makes a lot more sense. for that reason, jsPlumb is supporting both + // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from + // VML, which will be the preferred method. the code below this converts a dashstyle + // attribute given in terms of stroke width into a pixel representation, by using the + // stroke's lineWidth. + if(style["stroke-dasharray"]) { + node.setAttribute("stroke-dasharray", style["stroke-dasharray"]); + } + if (style["dashstyle"] && style["lineWidth"]) { + var sep = style["dashstyle"].indexOf(",") == -1 ? " " : ",", + parts = style["dashstyle"].split(sep), + styleToUse = ""; + parts.forEach(function(p) { + styleToUse += (Math.floor(p * style.lineWidth) + sep); + }); + node.setAttribute("stroke-dasharray", styleToUse); + } + + // extra attributes such as join type, dash offset. + for (var i in svgAttributeMap) { + if (style[i]) { + node.setAttribute(svgAttributeMap[i], style[i]); + } + } + }, + _decodeFont = function(f) { + var r = /([0-9].)(p[xt])\s(.*)/; + var bits = f.match(r); + return {size:bits[1] + bits[2], font:bits[3]}; + }; + + /* + * Base class for SVG components. + */ + var SvgComponent = function(cssClass, originalArgs, pointerEventsSpec) { + var self = this; + pointerEventsSpec = pointerEventsSpec || "all"; + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + self.canvas = null, self.path = null, self.svg = null; + + this.setHover = function() { }; + + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.sizeCanvas(self.canvas,0,0,1,1); + + var clazz = cssClass + " " + (originalArgs[0].cssClass || ""); + + self.svg = _node("svg", { + "style":"", + "width":0, + "height":0, + "pointer-events":pointerEventsSpec, + "class": clazz + }); + + jsPlumb.appendElement(self.canvas, originalArgs[0]["parent"]); + self.canvas.appendChild(self.svg); + + // TODO this displayElement stuff is common between all components, across all + // renderers. would be best moved to jsPlumbUIComponent. + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + this.paint = function(d, style, anchor) { + if (style != null) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + _attr(self.svg, { + "style":_pos([0,0,d[2], d[3]]), + "width": d[2], + "height": d[3] + }); + self._paint.apply(this, arguments); + } + }; + }; + + /* + * Base class for SVG connectors. + */ + var SvgConnector = function(params) { + var self = this; + SvgComponent.apply(this, [ params["_jsPlumb"].connectorClass, arguments, "none" ]); + this._paint = function(d, style) { + var p = self.getPath(d), a = { "d":p }, outlineStyle = null; + a["pointer-events"] = "all"; + + // outline style. actually means drawing an svg object underneath the main one. + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + outlineStyle = { + strokeStyle:_convertStyle(style.outlineColor), + lineWidth:outlineStrokeWidth + }; + + if (self.bgPath == null) { + self.bgPath = _node("path", a); + self.svg.appendChild(self.bgPath); + self.attachListeners(self.bgPath, self); + } + else { + _attr(self.bgPath, a); + } + + _applyStyles(self.svg, self.bgPath, outlineStyle, d); + } + + + if (self.path == null) { + self.path = _node("path", a); + self.svg.appendChild(self.path); + self.attachListeners(self.path, self); + } + else { + _attr(self.path, a); + } + + _applyStyles(self.svg, self.path, style, d); + }; + }; + + /* + * SVG Bezier Connector + */ + jsPlumb.Connectors.svg.Bezier = function(params) { + jsPlumb.Connectors.Bezier.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]; }; + }; + + /* + * SVG straight line Connector + */ + jsPlumb.Connectors.svg.Straight = function(params) { + jsPlumb.Connectors.Straight.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; }; + }; + + jsPlumb.Connectors.svg.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "M " + dimensions[4] + "," + dimensions[5]; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)]; + } + // finally draw a line to the end + p = p + " " + dimensions[6] + "," + dimensions[7]; + return p; + }; + }; + + /* + * Base class for SVG endpoints. + */ + var SvgEndpoint = function(params) { + var self = this; + SvgComponent.apply(this, [ params["_jsPlumb"].endpointClass, arguments, "all" ]); + this._paint = function(d, style) { + var s = jsPlumb.extend({}, style); + if (s.outlineColor) { + s.strokeWidth = s.outlineWidth; + s.strokeStyle = _convertStyle(s.outlineColor, true); + } + + if (self.node == null) { + self.node = self.makeNode(d, s); + self.svg.appendChild(self.node); + self.attachListeners(self.node, self); + } + _applyStyles(self.svg, self.node, s, d); + _pos(self.node, d); + }; + }; + + /* + * SVG Dot Endpoint + */ + jsPlumb.Endpoints.svg.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("circle", { + "cx" : d[2] / 2, + "cy" : d[3] / 2, + "r" : d[2] / 2 + }); + }; + }; + + /* + * SVG Rectangle Endpoint + */ + jsPlumb.Endpoints.svg.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("rect", { + "width":d[2], + "height":d[3] + }); + }; + }; + + /* + * SVG Image Endpoint is the default image endpoint. + */ + jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image; + /* + * Blank endpoint in svg renderer is the default Blank endpoint. + */ + jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank; + /* + * Label endpoint in svg renderer is the default Label endpoint. + */ + jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label; + + + var AbstractSvgArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + var self = this, path =null; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + } + + _attr(path, { + "d" : makePath(d), + stroke : strokeStyle ? strokeStyle : null, + fill : fillStyle ? fillStyle : null + }); + }; + var makePath = function(d) { + return "M" + d.hxy.x + "," + d.hxy.y + + " L" + d.tail[0].x + "," + d.tail[0].y + + " L" + d.cxy.x + "," + d.cxy.y + + " L" + d.tail[1].x + "," + d.tail[1].y + + " L" + d.hxy.x + "," + d.hxy.y; + }; + }; + + jsPlumb.Overlays.svg.Arrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.svg.PlainArrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.svg.Diamond = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();;(function() { + +// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS ******************************************************************* + + // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. + var _connectionBeingDragged = null, + _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }, + _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }, + _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }, + _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, + _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }, + _setOffset = function(el, o) { jsPlumb.CurrentLibrary.setOffset(el, o); }; + + /* + * Class:CanvasMouseAdapter + * Provides support for mouse events on canvases. + */ + var CanvasMouseAdapter = function() { + var self = this; + self.overlayPlacements = []; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + jsPlumb.EventGenerator.apply(this, arguments); + /** + * returns whether or not the given event is ojver a painted area of the canvas. + */ + this._over = function(e) { + var o = _getOffset(_getElementObject(self.canvas)), + pageXY = _pageXY(e), + x = pageXY[0] - o.left, y = pageXY[1] - o.top; + if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + return false; + }; + + var _mouseover = false; + var _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false; + var _nullSafeHasClass = function(el, clazz) { + return el != null && _hasClass(el, clazz); + }; + this.mousemove = function(e) { + var pageXY = _pageXY(e), clientXY = _clientXY(e), + ee = document.elementFromPoint(clientXY[0], clientXY[1]), + eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); + var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); + if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.fire("mouseenter", self, e); + return true; + } + // TODO here there is a remote chance that the overlay the mouse moved onto + // is actually not an overlay for the current component. a more thorough check would + // be to ensure the overlay belonged to the current component. + else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { + _mouseover = false; + self.fire("mouseexit", self, e); + } + self.fire("mousemove", self, e); + }; + + this.click = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); + self.fire("mousedown", self, e); + } + }; + + this.mouseup = function(e) { + //if (self == _connectionBeingDragged) _connectionBeingDragged = null; + _mouseDown = false; + self.fire("mouseup", self, e); + }; + }; + + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + jsPlumb.appendElement(canvas, params.parent); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + params["_jsPlumb"].getId(canvas, params.uuid); + + return canvas; + }; + + /** + * Class:CanvasConnector + * Superclass for Canvas Connector renderers. + */ + var CanvasConnector = jsPlumb.CanvasConnector = function(params) { + + CanvasMouseAdapter.apply(this, arguments); + + var _paintOneStyle = function(dim, aStyle) { + self.ctx.save(); + jsPlumb.extend(self.ctx, aStyle); + if (aStyle.gradient) { + var g = self.createGradient(dim, self.ctx); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + self.ctx.strokeStyle = g; + } + self._paint(dim); + self.ctx.restore(); + }; + + var self = this, + clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent + }); + self.ctx = self.canvas.getContext("2d"); + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + self.paint = function(dim, style) { + if (style != null) { + + jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); + + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + _paintOneStyle(dim, outlineStyle); + } + _paintOneStyle(dim, style); + } + }; + }; + + /** + * Class:CanvasEndpoint + * Superclass for Canvas Endpoint renderers. + */ + var CanvasEndpoint = function(params) { + var self = this; + CanvasMouseAdapter.apply(this, arguments); + var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent + }); + self.ctx = self.canvas.getContext("2d"); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + // _paintOneStyle(d, outlineStyle); + } + + self._paint.apply(this, arguments); + }; + }; + + jsPlumb.Endpoints.canvas.Dot = function(params) { + var self = this; + jsPlumb.Endpoints.Dot.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }; + var calculateAdjustments = function(gradient) { + var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius; + gradient.offset && (offsetAdjustment = parseValue(gradient.offset)); + gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius)); + return [offsetAdjustment, innerRadius]; + }; + this._paint = function(d, style, anchor) { + if (style != null) { + var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(); + jsPlumb.extend(ctx, style); + if (style.gradient) { + var adjustments = calculateAdjustments(style.gradient), + yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0], + xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0], + g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + ctx.beginPath(); + ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + } + }; + }; + + jsPlumb.Endpoints.canvas.Rectangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) { + + var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(); + jsPlumb.extend(ctx, style); + + /* canvas gradient */ + if (style.gradient) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, d[2], d[3]); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + jsPlumb.Endpoints.canvas.Triangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Triangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) + { + var width = d[2], height = d[3], x = d[0], y = d[1]; + + var ctx = self.canvas.getContext('2d'); + var offsetX = 0, offsetY = 0, angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = style.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + /* + * Canvas Image Endpoint: uses the default version, which creates an tag. + */ + jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image; + + /* + * Blank endpoint in all renderers is just the default Blank endpoint. + */ + jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank; + + /* + * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Bezier = function() { + var self = this; + jsPlumb.Connectors.Bezier.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO i doubt this handles the case that source and target are swapped. + this.createGradient = function(dim, ctx, swap) { + return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + /* + * Canvas straight line Connector. Draws a straight line onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Straight = function() { + var self = this; + jsPlumb.Connectors.Straight.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO this does not handle the case that src and target are swapped. + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + jsPlumb.Connectors.canvas.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + +// ********************************* END OF CANVAS RENDERERS ******************************************************************* + + jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label; + + /** + * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. + */ + var CanvasOverlay = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }; + + var AbstractCanvasArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + CanvasOverlay.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + var ctx = connector.ctx; + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(d.hxy.x, d.hxy.y); + ctx.lineTo(d.tail[0].x, d.tail[0].y); + ctx.lineTo(d.cxy.x, d.cxy.y); + ctx.lineTo(d.tail[1].x, d.tail[1].y); + ctx.lineTo(d.hxy.x, d.hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + }; + + jsPlumb.Overlays.canvas.Arrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.canvas.PlainArrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.canvas.Diamond = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); +/* + * yui.jsPlumb 1.3.0-RC1 + * + * YUI3 specific functionality for jsPlumb. + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + * NOTE: for production usage you should use yui-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, + * in a minified file. + * + * Dual licensed under MIT and GPL2. + * + */ + +/** + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + */ +(function() { + + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function( v, b, s ) { + for( var i = +b || 0, l = this.length; i < l; i++ ) { + if( this[i]===v || s && this[i]==v ) { return i; } + } + return -1; + }; + } + + var Y; + + YUI().use('node', 'dd', 'anim', function(_Y) { + Y = _Y; + Y.on("domready", function() { jsPlumb.init(); }); + }); + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }, + ddEvents = [ "drag:mouseDown", "drag:afterMouseDown", "drag:mouseup", + "drag:align", "drag:removeHandle", "drag:addHandle", "drag:removeInvalid", "drag:addInvalid", + "drag:start", "drag:end", "drag:drag", "drag:over", "drag:enter", + "drag:exit", "drag:drophit", "drag:dropmiss", "drop:over", "drop:enter", "drop:exit", "drop:hit" + ], + animEvents = [ "tween" ], + /** + * helper function to curry callbacks for some element. + */ + _wrapper = function(fn) { + return function() { + try { + fn.apply(this, arguments); + } + catch (e) { + console.log("wrap fail", e); + } + }; + }, + /** + * extracts options from the given options object, leaving out event handlers. + */ + _getDDOptions = function(options) { + var o = {}; + for (var i in options) if (ddEvents.indexOf(i) == -1) o[i] = options[i]; + return o; + }, + /** + * attaches all event handlers found in options to the given dragdrop object, and registering + * the given el as the element of interest. + */ + _attachListeners = function(dd, options, eventList) { + for (var ev in options) { + if (eventList.indexOf(ev) != -1) { + var w = _wrapper(options[ev]); + dd.on(ev, w); + } + } + }, + _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }, + _lastDragObject = null, + _extend = function(o1, o2) { + for (var i in o2) + o1[i] = o2[i]; + return o1; + }, + _getAttribute = function(el, attributeId) { + return el.getAttribute(attributeId); + }, + _getElementObject = function(el) { + return typeof el == 'string' ? Y.one('#' + el) : Y.one(el); + }; + + jsPlumb.CurrentLibrary = { + + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + var o = _extend({node:el, to:properties}, options); + var id = _getAttribute(el, "id"); + o["tween"] = jsPlumb.wrap(properties["tween"], function() { + jsPlumb.repaint(id); + }); + var a = new Y.Anim(o); + _attachListeners(a, o, animEvents); + a.run(); + }, + + appendElement : function(child, parent) { + _getElementObject (parent).append(child); + }, + + /** + * event binding wrapper. + */ + bind : function(el, event, callback) { + _getElementObject(el).on(event, callback); + }, + + dragEvents : { + "start":"drag:start", "stop":"drag:end", "drag":"drag:drag", "step":"step", + "over":"drop:enter", "out":"drop:exit", "drop":"drop:hit" + }, + + extend : _extend, + + getAttribute : _getAttribute, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + // this is a workaround for the unfortunate fact that in YUI3, the 'drop:exit' event does + // not contain a reference to the drag that just exited. single-threaded js to the + // rescue: we'll just keep it for ourselves. + if (eventArgs[0].drag) _lastDragObject = eventArgs[0].drag.el; + return _lastDragObject; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + return dd.scope; + }, + + getElementObject : _getElementObject, + + getOffset : function(el) { + var o = Y.DOM.getXY(el._node); + return {left:o[0], top:o[1]}; + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).get("parentNode"); + }, + + getScrollLeft : function(el) { + return 0; + }, + + getScrollTop : function(el) { + return 0; + }, + + getSize : function(el) { + //TODO must be a better way to get this? + //console.log("getSize"); + /*var bcr = _getElementObject(el)._node.getBoundingClientRect(); + return [ bcr.right - bcr.left, bcr.bottom - bcr.top]; // for some reason, in IE, the bounding rect does not always have width,height precomputed.*/ + return [ el._node.offsetWidth, el._node.offsetHeight ]; + }, + + getUIPosition : function(args) { + var n = args[0].currentTarget.el._node, + o = Y.DOM.getXY(n); + return {left:o[0], top:o[1]}; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options) { + var _opts = _getDDOptions(options); + var id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drag(_opts); + dd.el = el; + + var scope = options['scope'] || jsPlumb.Defaults.Scope; + dd.scope = scope; + + _draggablesById[id] = dd; + _add(_draggablesByScope, scope, dd); + + _attachListeners(dd, options, ddEvents); + }, + + initDroppable : function(el, options) { + var _opts = _getDDOptions(options); + var id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drop(_opts); + + _droppableOptions[id] = options; + + options = _extend({}, options); + var scope = options['scope'] || jsPlumb.Defaults.Scope; + + options["drop:enter"] = jsPlumb.wrap(options["drop:enter"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, true); + }, true); + options["drop:exit"] = jsPlumb.wrap(options["drop:exit"], function(e) { + _checkHover(el, false); + }); + options["drop:hit"] = jsPlumb.wrap(options["drop:hit"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, false); + }, true); + + _attachListeners(dd, options, ddEvents); + }, + + isAlreadyDraggable : function(el) { + el = _getElementObject(el); + return el.hasClass("yui3-dd-draggable"); + }, + + isDragSupported : function(el) { return true; }, + isDropSupported : function(el) { return true; }, + removeClass : function(el, clazz) { el.removeClass(clazz); }, + removeElement : function(el) { _getElementObject(el).remove(); }, + + setAttribute : function(el, attributeName, attributeValue) { + el.setAttribute(attributeName, attributeValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + if (dd) dd.set("lock", !draggable); + }, + + setDragScope : function(el, scope) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + if (dd) dd.scope = scope; + }, + + setOffset : function(el, o) { + el = _getElementObject(el); + el.set("top", o.top); + el.set("left", o.left); + } + }; +})();(function(){if(typeof Math.sgn=="undefined")Math.sgn=function(a){return a==0?0:a>0?1:-1};var p={subtract:function(a,b){return{x:a.x-b.x,y:a.y-b.y}},dotProduct:function(a,b){return a.x*b.x+a.y*b.y},square:function(a){return Math.sqrt(a.x*a.x+a.y*a.y)},scale:function(a,b){return{x:a.x*b,y:a.y*b}}},y=Math.pow(2,-65),u=function(a,b){for(var g=[],d=b.length-1,h=2*d-1,f=[],c=[],l=[],k=[],i=[[1,0.6,0.3,0.1],[0.4,0.6,0.6,0.4],[0.1,0.3,0.6,1]],e=0;e<=d;e++)f[e]=p.subtract(b[e],a);for(e=0;e<=d-1;e++){c[e]= +p.subtract(b[e+1],b[e]);c[e]=p.scale(c[e],3)}for(e=0;e<=d-1;e++)for(var m=0;m<=d;m++){l[e]||(l[e]=[]);l[e][m]=p.dotProduct(c[e],f[m])}for(e=0;e<=h;e++){k[e]||(k[e]=[]);k[e].y=0;k[e].x=parseFloat(e)/h}h=d-1;for(f=0;f<=d+h;f++){c=Math.min(f,d);for(e=Math.max(0,f-h);e<=c;e++){j=f-e;k[e+j].y+=l[j][e]*i[j][e]}}d=b.length-1;k=s(k,2*d-1,g,0);h=p.subtract(a,b[0]);l=p.square(h);for(e=i=0;e=64){g[0]=(a[0].x+a[b].x)/2;return 1}var n,o,q;k=a[0].y-a[b].y;i=a[b].x-a[0].x;e=a[0].x*a[b].y-a[b].x*a[0].y;m=max_distance_below=0;for(o=1;om)m=q;else if(q0?1:-1,c=null;h=0){delete (ax[ay]);ax.splice(ay,1);return true}}}return false},w=function(ay,ax){return J(ay,function(az,aA){ag[aA]=ax;if(i.CurrentLibrary.isDragSupported(az)){i.CurrentLibrary.setDraggable(az,ax)}})},ao=function(ax,ay){V(c(ax,"id"),function(az){az.canvas.style.display=ay})},O=function(ax){return J(ax,function(az,ay){var aA=ag[ay]==null?Y:ag[ay];aA=!aA;ag[ay]=aA;i.CurrentLibrary.setDraggable(az,aA);return aA})},y=function(ax){V(ax,function(az){var ay=("none"==az.canvas.style.display);az.canvas.style.display=ay?"block":"none"})},F=function(aC){var aA=aC.timestamp,ax=aC.recalc,aB=aC.offset,ay=aC.elId;if(!ax){if(aA&&aA===D[ay]){return W[ay]}}if(ax||aB==null){var az=v(ay);if(az!=null){Q[ay]=b(az);W[ay]=n(az);D[ay]=aA}}else{W[ay]=aB}return W[ay]},aw=function(ax,ay){var az=v(ax);var aA=c(az,"id");if(!aA||aA=="undefined"){if(arguments.length==2&&arguments[1]!=undefined){aA=ay}else{aA="jsPlumb_"+ar()}e(az,"id",aA)}return aA},am=function(az,ax,ay){az=az||function(){};ax=ax||function(){};return function(){var aA=null;try{aA=ax.apply(this,arguments)}catch(aB){k(E,"jsPlumb function failed : "+aB)}if(ay==null||(aA!==ay)){try{az.apply(this,arguments)}catch(aB){k(E,"wrapped function failed : "+aB)}}return aA}};this.connectorClass="_jsPlumb_connector";this.endpointClass="_jsPlumb_endpoint";this.overlayClass="_jsPlumb_overlay";this.Anchors={};this.Connectors={canvas:{},svg:{},vml:{}};this.Endpoints={canvas:{},svg:{},vml:{}};this.Overlays={canvas:{},svg:{},vml:{}};this.addEndpoint=function(az,aA,aJ){aJ=aJ||{};var ay=i.extend({},aJ);i.extend(ay,aA);ay.endpoint=ay.endpoint||E.Defaults.Endpoint||i.Defaults.Endpoint;ay.paintStyle=ay.paintStyle||E.Defaults.EndpointStyle||i.Defaults.EndpointStyle;az=x(az);var aB=[],aE=az.length&&az.constructor!=String?az:[az];for(var aC=0;aC0?l(aK,aJ)!=-1:true},aB=aE.length>1?{}:[],aH=function(aK,aL){if(aE.length>1){var aJ=aB[aK];if(aJ==null){aJ=[];aB[aK]=aJ}aJ.push(aL)}else{aB.push(aL)}};for(var aA in M){if(ay(aE,aA)){for(var az=0;az=4){ay.orientation=[arguments[2],arguments[3]]}if(arguments.length==6){ay.offsets=[arguments[4],arguments[5]]}}var aD=new ac(ay);aD.clone=function(){return new ac(ay)};return aD};this.makeAnchors=function(ay){var az=[];for(var ax=0;ax0?aG[0]:null;var aC=aG.length>0?0:-1;this.locked=false;var aF=this;var aB=function(aJ,aH,aN,aM,aI){var aL=aM[0]+(aJ.x*aI[0]),aK=aM[1]+(aJ.y*aI[1]);return Math.sqrt(Math.pow(aH-aL,2)+Math.pow(aN-aK,2))};var ax=ay||function(aR,aI,aJ,aK,aH){var aM=aJ[0]+(aK[0]/2),aL=aJ[1]+(aK[1]/2);var aO=-1,aQ=Infinity;for(var aN=0;aNaX){aX=a4}}var a8=this.connector.compute(aZ,ba,this.endpoints[be].anchor,this.endpoints[aW].anchor,aI.paintStyleInUse.lineWidth,aX);aI.connector.paint(a8,aI.paintStyleInUse);for(var a9=0;a9=0){aM.connections.splice(aY,1);if(!a1){var a0=aZ.endpoints[0]==aM?aZ.endpoints[1]:aZ.endpoints[0];a0.detach(aZ,true);if(aZ.endpointToDeleteOnDetach&&aZ.endpointToDeleteOnDetach.connections.length==0){i.deleteEndpoint(aZ.endpointToDeleteOnDetach)}}R(aZ.connector.getDisplayElements(),aZ.parent);N(M,aZ.scope,aZ);if(!a1){ad(aZ)}}};this.detachAll=function(){while(aM.connections.length>0){aM.detach(aM.connections[0])}};this.detachFrom=function(aZ){var a0=[];for(var aY=0;aY=0){aM.connections.splice(aY,1)}};this.getElement=function(){return aL};this.getUuid=function(){return aH};this.makeInPlaceCopy=function(){return an({anchor:aM.anchor,source:aL,paintStyle:this.paintStyle,endpoint:aK})};this.isConnectedTo=function(a0){var aZ=false;if(a0){for(var aY=0;aY0){var a8=aM.connections[0];var ba=a8.endpoints[0]==aM?1:0;var a3=ba==0?a8.sourceId:a8.targetId;var a7=W[a3],a9=Q[a3];aZ.txy=[a7.left,a7.top];aZ.twh=a9;aZ.tElement=a8.endpoints[ba]}}a4=aM.anchor.compute(aZ)}var a6=aK.compute(a4,aM.anchor.getOrientation(),aM.paintStyleInUse,a2||aM.paintStyleInUse);aK.paint(a6,aM.paintStyleInUse,aM.anchor);aM.timestamp=a5}};this.repaint=this.paint;this.removeConnection=this.detach;if(aX.isSource&&i.CurrentLibrary.isDragSupported(aL)){var aR=null,aN=null,aQ=null,ax=false,aA=null;var aC=function(){aQ=aM.connectorSelector();if(aM.isFull()&&!aI){return false}F({elId:aE});aB=aM.makeInPlaceCopy();aB.paint();aR=document.createElement("div");aR.style.position="absolute";var a5=v(aR);S(aR,aM.parent);var aY=aw(a5);var a6=v(aB.canvas),a4=i.CurrentLibrary.getOffset(a6),a0=aB.canvas.offsetParent.tagName.toLowerCase()==="body"?{left:0,top:0}:n(aB.canvas.offsetParent);i.CurrentLibrary.setOffset(aR,{left:a4.left-a0.left,top:a4.top-a0.top});F({elId:aY});e(v(aM.canvas),"dragId",aY);e(v(aM.canvas),"elId",aE);var a7=new I({reference:aM.anchor,referenceCanvas:aM.canvas});aV=an({paintStyle:aM.paintStyle,endpoint:aK,anchor:a7,source:a5});if(aQ==null){aM.anchor.locked=true;aQ=au({sourceEndpoint:aM,targetEndpoint:aV,source:v(aL),target:v(aR),anchors:[aM.anchor,a7],paintStyle:aX.connectorStyle,hoverPaintStyle:aX.connectorHoverStyle,connector:aX.connector,overlays:aX.connectorOverlays});aQ.connector.setHover(false)}else{ax=true;aQ.connector.setHover(false);aD(v(aB.canvas));var aZ=aQ.sourceId==aE?0:1;aQ.floatingAnchorIndex=aZ;aM.detachFromConnection(aQ);var a3=v(aM.canvas);var a2=i.CurrentLibrary.getDragScope(a3);e(a3,"originalScope",a2);var a1="scope_"+(new Date()).getTime();if(aZ==0){aA=[aQ.source,aQ.sourceId,aU,a2];aQ.source=v(aR);aQ.sourceId=aY}else{aA=[aQ.target,aQ.targetId,aU,a2];aQ.target=v(aR);aQ.targetId=aY}i.CurrentLibrary.setDragScope(aU,a1);aQ.endpoints[aZ==0?1:0].anchor.locked=true;aQ.suspendedEndpoint=aQ.endpoints[aZ];aQ.endpoints[aZ]=aV}Z[aY]=aQ;aV.addConnection(aQ);T(ah,aY,aV);E.currentlyDragging=true};var ay=i.CurrentLibrary,aT=aX.dragOptions||{},aO=i.extend({},ay.defaultDragOptions),aP=ay.dragEvents.start,aW=ay.dragEvents.stop,aG=ay.dragEvents.drag;aT=i.extend(aO,aT);aT.scope=aT.scope||aM.scope;aT[aP]=am(aT[aP],aC);aT[aG]=am(aT[aG],function(){var aY=i.CurrentLibrary.getUIPosition(arguments);i.CurrentLibrary.setOffset(aR,aY);at(v(aR),aY)});aT[aW]=am(aT[aW],function(){N(ah,aN,aV);R([aR,aV.canvas],aL);ae(aB.canvas,aL);var aY=aQ.floatingAnchorIndex==null?1:aQ.floatingAnchorIndex;aQ.endpoints[aY==0?1:0].anchor.locked=false;if(aQ.endpoints[aY]==aV){if(ax&&aQ.suspendedEndpoint){if(aY==0){aQ.source=aA[0];aQ.sourceId=aA[1]}else{aQ.target=aA[0];aQ.targetId=aA[1]}i.CurrentLibrary.setDragScope(aA[2],aA[3]);aQ.endpoints[aY]=aQ.suspendedEndpoint;if(aJ){aQ.floatingAnchorIndex=null;aQ.suspendedEndpoint.addConnection(aQ);i.repaint(aA[1])}else{aQ.endpoints[aY==0?1:0].detach(aQ)}}else{R(aQ.connector.getDisplayElements(),aM.parent);aM.detachFromConnection(aQ)}}aM.anchor.locked=false;aM.paint();aQ.setHover(false);aQ.repaint();aQ=null;delete aB;delete ah[aV.elementId];aV=null;delete aV;E.currentlyDragging=false});var aU=v(aM.canvas);i.CurrentLibrary.initDraggable(aU,aT)}var aD=function(a1){if(aX.isTarget&&i.CurrentLibrary.isDropSupported(aL)){var aY=aX.dropOptions||E.Defaults.DropOptions||i.Defaults.DropOptions;aY=i.extend({},aY);aY.scope=aY.scope||aM.scope;var a4=null;var a2=i.CurrentLibrary.dragEvents.drop;var a3=i.CurrentLibrary.dragEvents.over;var aZ=i.CurrentLibrary.dragEvents.out;var a0=function(){var bd=v(i.CurrentLibrary.getDragObject(arguments));var a5=c(bd,"dragId");var a7=c(bd,"elId");var bc=c(bd,"originalScope");if(bc){i.CurrentLibrary.setDragScope(bd,bc)}var a9=Z[a5];var ba=a9.floatingAnchorIndex==null?1:a9.floatingAnchorIndex,bb=ba==0?1:0;if(!aM.isFull()&&!(ba==0&&!aM.isSource)&&!(ba==1&&!aM.isTarget)){if(ba==0){a9.source=aL;a9.sourceId=aE}else{a9.target=aL;a9.targetId=aE}a9.endpoints[ba].detachFromConnection(a9);if(a9.suspendedEndpoint){a9.suspendedEndpoint.detachFromConnection(a9)}a9.endpoints[ba]=aM;aM.addConnection(a9);if(!a9.suspendedEndpoint){T(M,a9.scope,a9);P(aL,aX.draggable,{})}else{var a8=a9.suspendedEndpoint.getElement(),a6=a9.suspendedEndpoint.elementId;E.fire("jsPlumbConnectionDetached",{source:ba==0?a8:a9.source,target:ba==1?a8:a9.target,sourceId:ba==0?a6:a9.sourceId,targetId:ba==1?a6:a9.targetId,sourceEndpoint:ba==0?a9.suspendedEndpoint:a9.endpoints[0],targetEndpoint:ba==1?a9.suspendedEndpoint:a9.endpoints[1],connection:a9})}i.repaint(a7);E.fire("jsPlumbConnection",{source:a9.source,target:a9.target,sourceId:a9.sourceId,targetId:a9.targetId,sourceEndpoint:a9.endpoints[0],targetEndpoint:a9.endpoints[1],connection:a9})}E.currentlyDragging=false;delete Z[a5]};aY[a2]=am(aY[a2],a0);aY[a3]=am(aY[a3],function(){var a6=i.CurrentLibrary.getDragObject(arguments);var a8=c(v(a6),"dragId");var a7=Z[a8];if(a7!=null){var a5=a7.floatingAnchorIndex==null?1:a7.floatingAnchorIndex;a7.endpoints[a5].anchor.over(aM.anchor)}});aY[aZ]=am(aY[aZ],function(){var a6=i.CurrentLibrary.getDragObject(arguments),a8=c(v(a6),"dragId"),a7=Z[a8];if(a7!=null){var a5=a7.floatingAnchorIndex==null?1:a7.floatingAnchorIndex;a7.endpoints[a5].anchor.out()}});i.CurrentLibrary.initDroppable(a1,aY)}};aD(v(aM.canvas));return aM}};var i=window.jsPlumb=new r();i.getInstance=function(x){var w=new r(x);return w};var m=function(w,B,A,z){return function(){return i.makeAnchor(w,B,A,z)}};i.Anchors.TopCenter=m(0.5,0,0,-1);i.Anchors.BottomCenter=m(0.5,1,0,1);i.Anchors.LeftMiddle=m(0,0.5,-1,0);i.Anchors.RightMiddle=m(1,0.5,1,0);i.Anchors.Center=m(0.5,0.5,0,0);i.Anchors.TopRight=m(1,0,0,-1);i.Anchors.BottomRight=m(1,1,0,1);i.Anchors.TopLeft=m(0,0,0,-1);i.Anchors.BottomLeft=m(0,1,0,1);i.Defaults.DynamicAnchors=function(){return i.makeAnchors(["TopCenter","RightMiddle","BottomCenter","LeftMiddle"])};i.Anchors.AutoDefault=function(){return i.makeDynamicAnchor(i.Defaults.DynamicAnchors())}})();(function(){jsPlumb.DOMElementComponent=function(a){jsPlumb.jsPlumbUIComponent.apply(this,arguments);this.mousemove=this.dblclick=this.click=this.mousedown=this.mouseup=function(b){}};jsPlumb.Connectors.Straight=function(){var n=this;var g=null;var c,h,l,k,i,d,m,f,e,b,a;this.compute=function(r,F,B,o,v,q){var E=Math.abs(r[0]-F[0]);var u=Math.abs(r[1]-F[1]);var z=false,s=false;var t=0.45*E,p=0.45*u;E*=1.9;u*=1.9;var C=Math.min(r[0],F[0])-t;var A=Math.min(r[1],F[1])-p;var D=Math.max(2*v,q);if(E0?1:-1;var t=Math.abs(u*Math.sin(d));if(e>a){t=t*-1}var o=Math.abs(u*Math.cos(d));if(f>b){o=o*-1}return{x:s.x+(r*o),y:s.y+(r*t)}};this.perpendicularToPathAt=function(s,t,w){var u=n.pointAlongPathFrom(s,w);var r=n.gradientAtPoint(u.location);var q=Math.atan(-1/r);var v=t/2*Math.sin(q);var o=t/2*Math.cos(q);return[{x:u.x+o,y:u.y+v},{x:u.x-o,y:u.y-v}]}};jsPlumb.Connectors.Bezier=function(e){var o=this;e=e||{};this.majorAnchor=e.curviness||150;this.minorAnchor=10;var h=null;this._findControlPoint=function(z,q,u,x,r){var w=x.getOrientation(),y=r.getOrientation();var t=w[0]!=y[0]||w[1]==y[1];var s=[];var A=o.majorAnchor,v=o.minorAnchor;if(!t){if(w[0]==0){s.push(q[0]l){l=u}if(x<0){d+=x;var y=Math.abs(x);l+=y;n[0]+=y;i+=y;b+=y;m[0]+=y}var G=Math.min(f,a);var E=Math.min(n[1],m[1]);var t=Math.min(G,E);var z=Math.max(f,a);var w=Math.max(n[1],m[1]);var r=Math.max(z,w);if(r>g){g=r}if(t<0){c+=t;var v=Math.abs(t);g+=v;n[1]+=v;f+=v;a+=v;m[1]+=v}if(D&&l=s){q=t;r=(s-l[t][0])/a[t];break}}return{segment:i[q],proportion:r,index:q}};this.compute=function(Q,v,P,r,q,H){i=[];h=[];a=[];g=[];segmentProportionals=[];d=v[0]t[0]?t[0]+((1-x)*r)-y:t[2]+(x*r)+y,y:q==0?t[3]:t[3]>t[1]?t[1]+((1-x)*r)-y:t[3]+(x*r)+y,segmentInfo:v};return w};this.perpendicularToPathAt=function(t,u,z){var v=n.pointAlongPathFrom(t,z);var s=h[v.segmentInfo.index];var r=Math.atan(-1/s);var w=u/2*Math.sin(r);var q=u/2*Math.cos(r);return[{x:v.x+q,y:v.y+w},{x:v.x-q,y:v.y-w}]}};jsPlumb.Endpoints.Dot=function(b){var a=this;b=b||{};this.radius=b.radius||10;this.defaultOffset=0.5*this.radius;this.defaultInnerRadius=this.radius/3;this.compute=function(g,d,i,f){var e=i.radius||a.radius;var c=g[0]-e;var h=g[1]-e;return[c,h,e*2,e*2,e]}};jsPlumb.Endpoints.Rectangle=function(b){var a=this;b=b||{};this.width=b.width||20;this.height=b.height||20;this.compute=function(h,e,k,g){var f=k.width||a.width;var d=k.height||a.height;var c=h[0]-(f/2);var i=h[1]-(d/2);return[c,i,f,d]}};jsPlumb.Endpoints.Image=function(d){jsPlumb.DOMElementComponent.apply(this,arguments);var a=this,c=false;this.img=new Image();a.ready=false;this.img.onload=function(){a.ready=true};this.img.src=d.src||d.url;this.compute=function(g,e,h,f){a.anchorPoint=g;if(a.ready){return[g[0]-a.img.width/2,g[1]-a.img.height/2,a.img.width,a.img.height]}else{return[0,0,0,0]}};a.canvas=document.createElement("img"),c=false;a.canvas.style.margin=0;a.canvas.style.padding=0;a.canvas.style.outline=0;a.canvas.style.position="absolute";a.canvas.className=jsPlumb.endpointClass;jsPlumb.appendElement(a.canvas,d.parent);a.attachListeners(a.canvas,a);var b=function(k,i,g){if(!c){a.canvas.setAttribute("src",a.img.src);c=true}var h=a.img.width,f=a.img.height,e=a.anchorPoint[0]-(h/2),l=a.anchorPoint[1]-(f/2);jsPlumb.sizeCanvas(a.canvas,e,l,h,f)};this.paint=function(g,f,e){if(a.ready){b(g,f,e)}else{window.setTimeout(function(){a.paint(g,f,e)},200)}}};jsPlumb.Endpoints.Blank=function(){jsPlumb.DOMElementComponent.apply(this,arguments);this.compute=function(){return[0,0,0,0]};self.canvas=document.createElement("div");this.paint=function(){}};jsPlumb.Endpoints.Triangle=function(a){a=a||{};a.width=a.width||55;param.height=a.height||55;this.width=a.width;this.height=a.height;this.compute=function(g,d,i,f){var e=i.width||self.width;var c=i.height||self.height;var b=g[0]-(e/2);var h=g[1]-(c/2);return[b,h,e,c]}};jsPlumb.Overlays.Arrow=function(f){f=f||{};var b=this;this.length=f.length||20;this.width=f.width||20;this.id=f.id;this.connection=f.connection;var e=(f.direction||1)<0?-1:1;var c=f.paintStyle||{lineWidth:1};this.loc=f.location==null?0.5:f.location;var a=f.foldback||0.623;var d=function(g,i){if(a==0.5){return g.pointOnPath(i)}else{var h=0.5-a;return g.pointAlongPathFrom(i,e*b.length*h)}};this.computeMaxSize=function(){return b.width*1.5};this.draw=function(k,q,w){var y=k.pointAlongPathFrom(b.loc,e*(b.length/2));var t=k.pointAlongPathFrom(b.loc,-1*e*(b.length/2)),B=t.x,A=t.y;var r=k.perpendicularToPathAt(b.loc,b.width,-1*e*(b.length/2));var i=d(k,b.loc);if(b.loc==1){var h=k.pointOnPath(b.loc);var v=(h.x-y.x)*e,u=(h.y-y.y)*e;i.x+=v;i.y+=u;t.x+=v;t.y+=u;r[0].x+=v;r[0].y+=u;r[1].x+=v;r[1].y+=u;y.x+=v;y.y+=u}if(b.loc==0){var h=k.pointOnPath(b.loc);var s=a>1?i:{x:r[0].x+((r[1].x-r[0].x)/2),y:r[0].y+((r[1].y-r[0].y)/2)};var v=(h.x-s.x)*e,u=(h.y-s.y)*e;i.x+=v;i.y+=u;t.x+=v;t.y+=u;r[0].x+=v;r[0].y+=u;r[1].x+=v;r[1].y+=u;y.x+=v;y.y+=u}var o=Math.min(y.x,r[0].x,r[1].x);var n=Math.max(y.x,r[0].x,r[1].x);var m=Math.min(y.y,r[0].y,r[1].y);var l=Math.max(y.y,r[0].y,r[1].y);var z={hxy:y,tail:r,cxy:i},x=c.strokeStyle||q.strokeStyle,p=c.fillStyle||q.strokeStyle,g=c.lineWidth||q.lineWidth;b.paint(k,z,g,x,p,w);return[o,n,m,l]}};jsPlumb.Overlays.PlainArrow=function(b){b=b||{};var a=jsPlumb.extend(b,{foldback:1});jsPlumb.Overlays.Arrow.call(this,a)};jsPlumb.Overlays.Diamond=function(c){c=c||{};var a=c.length||40;var b=jsPlumb.extend(c,{length:a/2,foldback:2});jsPlumb.Overlays.Arrow.call(this,b)};jsPlumb.Overlays.Label=function(d){jsPlumb.DOMElementComponent.apply(this,arguments);this.labelStyle=d.labelStyle||jsPlumb.Defaults.LabelStyle;this.labelStyle.font=this.labelStyle.font||"12px sans-serif";this.label=d.label;this.connection=d.connection;this.id=d.id;var k=this;var h=null,e=null,c=null,b=null;this.location=d.location||0.5;this.cachedDimensions=null;var i=false,c=null,a=document.createElement("div");a.style.position="absolute";a.style.textAlign="center";a.style.cursor="pointer";a.style.font=k.labelStyle.font;a.style.color=k.labelStyle.color||"black";if(k.labelStyle.fillStyle){a.style.background=k.labelStyle.fillStyle}if(k.labelStyle.borderWidth>0){var g=k.labelStyle.borderStyle?k.labelStyle.borderStyle:"black";a.style.border=k.labelStyle.borderWidth+"px solid "+g}if(k.labelStyle.padding){a.style.padding=k.labelStyle.padding}var f=d._jsPlumb.overlayClass+" "+(k.labelStyle.cssClass?k.labelStyle.cssClass:d.cssClass?d.cssClass:"");a.className=f;jsPlumb.appendElement(a,d.connection.parent);jsPlumb.getId(a);k.attachListeners(a,k);this.paint=function(l,n,m){if(!i){l.appendDisplayElement(a);k.attachListeners(a,l);i=true}a.style.left=(m[0]+n.minx)+"px";a.style.top=(m[1]+n.miny)+"px"};this.getTextDimensions=function(l){c=typeof k.label=="function"?k.label(k):k.label;a.innerHTML=c.replace(/\r\n/g,"
");var n=jsPlumb.CurrentLibrary.getElementObject(a),m=jsPlumb.CurrentLibrary.getSize(n);return{width:m[0],height:m[1]}};this.computeMaxSize=function(l){var m=k.getTextDimensions(l);return m.width?Math.max(m.width,m.height)*1.5:0};this.draw=function(n,p,o){var r=k.getTextDimensions(n);if(r.width!=null){var q=n.pointOnPath(k.location);var m=q.x-(r.width/2);var l=q.y-(r.height/2);k.paint(n,{minx:m,miny:l,td:r,cxy:q},o);return[m,m+r.width,l,l+r.height]}else{return[0,0,0,0]}}}})();(function(){var h={"stroke-linejoin":"joinstyle",joinstyle:"joinstyle",endcap:"endcap",miterlimit:"miterlimit"};if(document.createStyleSheet){document.createStyleSheet().addRule(".jsplumb_vml","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:textbox","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:oval","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:rect","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:stroke","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:shape","behavior:url(#default#VML);position:absolute;");document.namespaces.add("jsplumb","urn:schemas-microsoft-com:vml")}var c=1000,d=function(p,q){for(var n in q){p[n]=q[n]}},m=function(n,q,r){r=r||{};var p=document.createElement("jsplumb:"+n);p.className=(r["class"]?r["class"]+" ":"")+"jsplumb_vml";l(p,q);d(p,r);return p},l=function(p,n){p.style.left=n[0]+"px";p.style.top=n[1]+"px";p.style.width=n[2]+"px";p.style.height=n[3]+"px";p.style.position="absolute"},g=function(n){return Math.floor(n*c)},a=function(p,n){var v=p,u=function(o){return o.length==1?"0"+o:o},q=function(o){return u(Number(o).toString(16))},r=/(rgb[a]?\()(.*)(\))/;if(p.match(r)){var t=p.match(r)[2].split(",");v="#"+q(t[0])+q(t[1])+q(t[2]);if(!n&&t.length==4){v=v+q(t[3])}}return v},f=function(s,r,p){var u={};if(r.strokeStyle){u.stroked="true";u.strokecolor=a(r.strokeStyle,true);u.strokeweight=r.lineWidth+"px"}else{u.stroked="false"}if(r.fillStyle){u.filled="true";u.fillcolor=a(r.fillStyle,true)}else{u.filled="false"}if(r.dashstyle){if(p.strokeNode==null){p.strokeNode=m("stroke",[0,0,0,0],{dashstyle:r.dashstyle});s.appendChild(p.strokeNode)}else{p.strokeNode.dashstyle=r.dashstyle}}else{if(r["stroke-dasharray"]&&r.lineWidth){var o=r["stroke-dasharray"].indexOf(",")==-1?" ":",",t=r["stroke-dasharray"].split(o),n="";for(var q=0;q0&&F>0&&z=z&&B[2]<=F&&B[3]>=F)){return true}}var D=v.canvas.getContext("2d").getImageData(parseInt(z),parseInt(F),1,1);return D.data[0]!=0||D.data[1]!=0||D.data[2]!=0||D.data[3]!=0}return false};var u=false;var t=false,y=null,x=false;var w=function(A,z){return A!=null&&f(A,z)};this.mousemove=function(C){var E=e(C),B=i(C),A=document.elementFromPoint(B[0],B[1]),D=w(A,"_jsPlumb_overlay");var z=o==null&&(w(A,"_jsPlumb_endpoint")||w(A,"_jsPlumb_connector"));if(!u&&z&&v._over(C)){u=true;v.fire("mouseenter",v,C);return true}else{if(u&&(!v._over(C)||!z)&&!D){u=false;v.fire("mouseexit",v,C)}}v.fire("mousemove",v,C)};this.click=function(z){if(u&&v._over(z)&&!x){v.fire("click",v,z)}x=false};this.dblclick=function(z){if(u&&v._over(z)&&!x){v.fire("dblclick",v,z)}x=false};this.mousedown=function(z){if(v._over(z)&&!t){t=true;y=m(s(v.canvas));v.fire("mousedown",v,z)}};this.mouseup=function(z){t=false;v.fire("mouseup",v,z)}};var p=function(u){var t=document.createElement("canvas");jsPlumb.appendElement(t,u.parent);t.style.position="absolute";if(u["class"]){t.className=u["class"]}u._jsPlumb.getId(t,u.uuid);return t};var d=jsPlumb.CanvasConnector=function(x){n.apply(this,arguments);var t=function(B,z){u.ctx.save();jsPlumb.extend(u.ctx,z);if(z.gradient){var A=u.createGradient(B,u.ctx);for(var y=0;y0?1:-1}}var b={subtract:function(m,l){return{x:m.x-l.x,y:m.y-l.y}},dotProduct:function(m,l){return m.x*l.x+m.y*l.y},square:function(l){return Math.sqrt(l.x*l.x+l.y*l.y)},scale:function(m,l){return{x:m.x*l,y:m.y*l}}},d=Math.pow(2,-65),h=function(y,x){for(var s=[],v=x.length-1,r=2*v-1,t=[],w=[],o=[],p=[],q=[[1,0.6,0.3,0.1],[0.4,0.6,0.6,0.4],[0.1,0.3,0.6,1]],u=0;u<=v;u++){t[u]=b.subtract(x[u],y)}for(u=0;u<=v-1;u++){w[u]=b.subtract(x[u+1],x[u]);w[u]=b.scale(w[u],3)}for(u=0;u<=v-1;u++){for(var n=0;n<=v;n++){o[u]||(o[u]=[]);o[u][n]=b.dotProduct(w[u],t[n])}}for(u=0;u<=r;u++){p[u]||(p[u]=[]);p[u].y=0;p[u].x=parseFloat(u)/r}r=v-1;for(t=0;t<=v+r;t++){w=Math.min(t,v);for(u=Math.max(0,t-r);u<=w;u++){j=t-u;p[u+j].y+=o[j][u]*q[j][u]}}v=x.length-1;p=k(p,2*v-1,s,0);r=b.subtract(y,x[0]);o=b.square(r);for(u=q=0;u=64){y[0]=(E[0].x+E[D].x)/2;return 1}var s,r,p;v=E[0].y-E[D].y;w=E[D].x-E[0].x;A=E[0].x*E[D].y-E[D].x*E[0].y;t=max_distance_below=0;for(r=1;rt){t=p}else{if(p0?1:-1,r=null;n endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + endpointsByElement = {}, + endpointsByUUID = {}, + offsets = {}, + offsetTimestamps = {}, + floatingConnections = {}, + draggableStates = {}, + _mouseEventsEnabled = this.Defaults.MouseEventsEnabled, + _draggableByDefault = true, + canvasList = [], + sizes = [], + listeners = {}, // a map: keys are event types, values are lists of listeners. + DEFAULT_SCOPE = this.Defaults.Scope, + renderMode = null, // will be set in init() + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }, + + /** + * appends an element to the given parent, or the document body if no + * parent given. + */ + _appendElement = function(el, parent) { + if (!parent) + document.body.appendChild(el); + else + jsPlumb.CurrentLibrary.appendElement(el, parent); + }, + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + _timestamp = function() { return "" + (new Date()).getTime(); }, + + /** + * YUI, for some reason, put the result of a Y.all call into an object that contains + * a '_nodes' array, instead of handing back an array-like object like the other + * libraries do. + */ + _convertYUICollection = function(c) { + return c._nodes ? c._nodes : c; + }, + + /** + * Draws an endpoint and its connections. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + _draw = function(element, ui, timestamp) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (!timestamp) timestamp = _timestamp(); + if (endpoints) { + _updateOffset( { elId : id, offset : ui, recalc : false, timestamp : timestamp }); // timestamp is checked against last update cache; it is + // valid for one paint cycle. + var myOffset = offsets[id], myWH = sizes[id]; + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + var l = endpoints[i].connections; + for ( var j = 0; j < l.length; j++) { + l[j].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); // ...paint each connection. + // then, check for dynamic endpoint; need to repaint it. + var oIdx = l[j].endpoints[0] == endpoints[i] ? 1 : 0; + if (l[j].endpoints[oIdx].anchor.isDynamic && !l[j].endpoints[oIdx].isFloating()) { + var oId = oIdx == 0 ? l[j].sourceId : l[j].targetId, + oOffset = offsets[oId], oWH = sizes[oId], + // TODO i still want to make this faster. + anchorLoc = l[j].endpoints[oIdx].anchor.compute( { + xy : [ oOffset.left, oOffset.top ], + wh : oWH, + element : l[j].endpoints[oIdx], + txy : [ myOffset.left, myOffset.top ], + twh : myWH, + tElement : endpoints[i] + }); + l[j].endpoints[oIdx].paint({ anchorLoc : anchorLoc }); + } + } + } + } + }, + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }, + + /** + * gets an Endpoint by uuid. + */ + _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }, + + /** + * inits a draggable if it's not already initialised. + */ + _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element) && !jsPlumb.CurrentLibrary.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + } + } + }, + + _newConnection = function(params) { + var connectionFunc = jsPlumb.Defaults.ConnectionType || Connection, + endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint, + parent = jsPlumb.CurrentLibrary.getParent; + + if (params.sourceEndpoint) + params["parent"] = params.sourceEndpoint.parent; + else if (params.source.constructor == endpointFunc) + params["parent"] = params.source.parent; + else params["parent"] = parent(params.source); + + params["_jsPlumb"] = _currentInstance; + var con = new connectionFunc(params); + _eventFireProxy("click", "click", con); + _eventFireProxy("dblclick", "dblclick", con); + return con; + }, + + _eventFireProxy = function(event, proxyEvent, obj) { + obj.bind(event, function(e) { + _currentInstance.fire(proxyEvent, obj, e); + }); + }, + + _newEndpoint = function(params) { + var endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint; + params["parent"] = jsPlumb.CurrentLibrary.getParent(params.source); + params["_jsPlumb"] = _currentInstance, + ep = new endpointFunc(params); + _eventFireProxy("click", "endpointClick", ep); + _eventFireProxy("dblclick", "endpointDblClick", ep); + return ep; + }, + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }, + /** + * perform an operation on all elements. + */ + _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }, + + /** + * helper to remove an element from the DOM. + */ + _removeElement = function(element, parent) { + if (element != null && element.parentNode != null) { + element.parentNode.removeChild(element); + } + }, + /** + * helper to remove a list of elements from the DOM. + */ + _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }, + /** + * helper method to remove an item from a list. + */ + _removeFromList = function(map, key, value) { + if (key != null) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete (l[i]); + l.splice(i, 1); + return true; + } + } + } + return false; + }, + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }, + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + _setVisible = function(el, state) { + _operation(_getAttribute(el, "id"), function(jpc) { + jpc.canvas.style.display = state; + }); + }, + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }, + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + _toggleVisible = function(elId) { + _operation(elId, function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }, + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return offsets[elId]; + } + if (recalc || offset == null) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + } + return offsets[elId]; + }, + +/** + * gets an id for the given element, creating and setting one if + * necessary. + */ + _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else + id = "jsPlumb_" + _timestamp(); + _setAttribute(ele, "id", id); + } + return id; + }, + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'jsPlumb function failed : ' + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'wrapped function failed : ' + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = "_jsPlumb_connector"; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = "_jsPlumb_endpoint"; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = "_jsPlumb_overlay"; + + this.Anchors = {}; + + this.Connectors = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Endpoints = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Overlays = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + +// ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***************************************** + /* + * Function: bind + * Bind to an event on jsPlumb. + * + * Parameters: + * event - the event to bind. Available events on jsPlumb are: + * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback. + * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback. + * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback. + * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback. + * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback. + * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback. + * + * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event. + */ + + /* + * Function: clearListeners + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + +// *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *********************************************************** + + /* + Function: addEndpoint + + Adds an to a given element or elements. + + Parameters: + + el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + params - Object containing Endpoint constructor arguments. For more information, see . + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some + shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in + this object are anything that 'params' can contain. See . + + Returns: + The newly created , if el referred to a single element. Otherwise, an array of newly created s. + + See Also: + + */ + this.addEndpoint = function(el, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + + // YUI wrapper + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + var _el = _getElementObject(inputs[i]), id = _getId(_el); + p.source = _el; + _updateOffset({ elId : id }); + var e = _newEndpoint(p); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + results.push(e); + } + + return results.length == 1 ? results[0] : results; + }; + + /* + Function: addEndpoints + Adds a list of s to a given element or elements. + + Parameters: + target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See 's constructor documentation. + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created s, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(el, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams); + if (e.constructor == Array) + Array.prototype.push.apply(results, e); + else results.push(e); + } + return results; + }; + + /* + Function: animate + This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating + the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // onComplete repaints, just to make sure everything looks good at the end of the animation. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /* + Function: connect + Establishes a between two elements (or s, which are themselves registered to elements). + + Parameters: + params - Object containing constructor arguments for the Connection. See 's constructor documentation. + referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of + Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection. + + Returns: + The newly created . + */ + this.connect = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + // dynamic anchors. backwards compatibility here: from 1.2.6 onwards you don't need to specify "dynamicAnchors". the fact that some anchor consists + // of multiple definitions is enough to tell jsPlumb you want it to be dynamic. + if (_p.dynamicAnchors) { + // these can either be an array of anchor coords, which we will use for both source and target, or an object with {source:[anchors], target:[anchors]}, in which + // case we will use a different set for each element. + var a = _p.dynamicAnchors.constructor == Array; + var sa = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.source)); + var ta = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.target)); + _p.anchors = [sa,ta]; + } + + var jpc = _newConnection(_p); + // add to list of connections (by scope). + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + _currentInstance.fire("jsPlumbConnection", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + // force a paint + _draw(jpc.source); + + return jpc; + }; + + /* + Function: deleteEndpoint + Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too) + + Parameters: + object - either an object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElement(endpoint.canvas, endpoint.parent); + // remove from endpointsbyElement + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + delete endpoint; + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every , and their associated s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc) { + _currentInstance.fire("jsPlumbConnectionDetached", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + }; + + /* + Function: detach + Detaches and then removes a . Takes either (source, target) (the old way, maintained for backwards compatibility), or a params + object with various possible values. + + Parameters: + source - id or element object of the first element in the Connection. + target - id or element object of the second element in the Connection. + params - a JS object containing the same parameters as you pass to jsPlumb.connect. If this is present then neither source nor + target should be present; it should be the only argument to the method. See the docs for 's constructor for information +about the parameters allowed in the params object. + Returns: + true if successful, false if not. + */ + this.detach = function(source, target) { + if (arguments.length == 2) { + var s = _getElementObject(source), sId = _getId(s); + var t = _getElementObject(target), tId = _getId(t); + _operation(sId, function(jpc) { + if ((jpc.sourceId == sId && jpc.targetId == tId) || (jpc.targetId == sId && jpc.sourceId == tId)) { + _removeElements(jpc.connector.getDisplayElements(), jpc.parent); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + // this is the new version of the method, taking a JS object like + // the connect method does. + else if (arguments.length == 1) { + // TODO investigate whether or not this code still works when a user has supplied their own subclass of Connection. i suspect it may not. + if (arguments[0].constructor == Connection) { + arguments[0].endpoints[0].detachFrom(arguments[0].endpoints[1]); + } + else if (arguments[0].connection) { + arguments[0].connection.endpoints[0].detachFrom(arguments[0].connection.endpoints[1]); + } + else { + var _p = jsPlumb.extend( {}, source); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1])); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source); + var targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElements(jpc.connector.getDisplayElements(), jpc.parent); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + } + } + }; + + /* + Function: detachAll + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + + Returns: + void + */ + this.detachAllConnections = function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + }; + + /** + * @deprecated Use detachAllConnections instead. this will be removed in jsPlumb 1.3. + */ + this.detachAll = this.detachAllConnections; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + /** + * @deprecated use detachEveryConnection instead. this will be removed in jsPlumb 1.3. + */ + this.detachEverything = this.detachEveryConnection; + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of you library's draggable method so that jsPlumb can setup the appropriate callbacks. Your underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method, + * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the + * default scope). If multiple scopes are passed in, the return value will be a map of { scope -> [ connection... ] }. + * + * Parameters + * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list + * of connections that are in the given scope. + * options - if the argument is a JS object, you can specify a finer-grained filter: + * + * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes. + * - *source* either a string representing an element id, or a selector. constrains the result to connections having this source. + * - *target* either a string representing an element id, or a selector. constrains the result to connections having this target. + * + */ + this.getConnections = function(options) { + if (!options) { + options = {}; + } else if (options.constructor == String) { + options = { "scope": options }; + } + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') + r.push(input); + else + r = input; + } + return r; + }; + var scope = options.scope || jsPlumb.getDefaultScope(), + scopes = prepareList(scope), + sources = prepareList(options.source), + targets = prepareList(options.target), + filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }, + results = scopes.length > 1 ? {} : [], + _addOne = function(scope, obj) { + if (scopes.length > 1) { + var ss = results[scope]; + if (ss == null) { + ss = []; results[scope] = ss; + } + ss.push(obj); + } else results.push(obj); + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + _addOne(i, c); + } + } + } + return results; + }; + + /* + * Function: getAllConnections + * Gets all connections, as a map of { scope -> [ connection... ] }. + */ + this.getAllConnections = function() { + return connectionsByScope; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /** + * Function:getEndpoints + * Gets the list of Endpoints for a given selector, or element id. + * @param el + * @return + */ + this.getEndpoints = function(el) { + return endpointsByElement[_getId(el)]; + }; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + + this.appendElement = _appendElement; + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + + Returns: + void + */ + this.hide = function(el) { + _setVisible(el, "none"); + }; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + if (!initialized) { + _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run. + + var _bind = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && _mouseEventsEnabled && renderMode == jsPlumb.CANVAS) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + var t = c[i].connector[event](e); + if (t) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i].endpoint[event](e)) return; + } + } + } + }); + }; + _bind("click"); + _bind("dblclick"); + _bind("mousemove"); + _bind("mousedown"); + _bind("mouseup"); + + initialized = true; + _currentInstance.fire("ready"); + } + }; + + this.jsPlumbUIComponent = jsPlumbUIComponent; + this.EventGenerator = EventGenerator; + + /* + * Creates an anchor with the given params. + * + * You do not need to use this method. It is exposed because of the way jsPlumb is + * split into three scripts; this will change in the future. + * + * x - the x location of the anchor as a fraction of the + * total width. y - the y location of the anchor as a fraction of the + * total height. xOrientation - value indicating the general direction a + * connection from the anchor should go in, in the x direction. + * yOrientation - value indicating the general direction a connection + * from the anchor should go in, in the y direction. xOffset - a fixed + * offset that should be applied in the x direction that should be + * applied after the x position has been figured out. optional. defaults + * to 0. yOffset - a fixed offset that should be applied in the y + * direction that should be applied after the y position has been + * figured out. optional. defaults to 0. + * -- OR -- + * + * params - {x:..., y:..., xOrientation etc } + * -- OR FROM 1.2.4 --- + * + * name - the name of some Anchor in the _currentInstance.Anchors array. + * -- OR FROM 1.2.4 --- + * + * coords - a list of coords for the anchor, like you would pass to + * jsPlumb.makeAnchor (eg [0.5,0.5,0,-1] - an anchor in the center of + * some element, oriented towards the top of the screen) + * -- OR FROM 1.2.4 --- + * + * anchor - an existing anchor. just gets passed back. it's handy + * internally to have this functionality. + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed + // in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards + // compatibility if we are given only one value we assume it's a + // call in the old form. + if (arguments.length == 0) return null; + var params = {}; + if (arguments.length == 1) { + var specimen = arguments[0]; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; + // is it the name of an anchor type? + else if (typeof specimen == "string") return _currentInstance.Anchors[arguments[0]](); + // is it an array of coordinates? + else if (specimen.constructor == Array) { + if (specimen[0].constructor == Array || specimen[0].constructor == String) + return new DynamicAnchor(specimen); + else + return jsPlumb.makeAnchor.apply(this, specimen); + } + // last we try the backwards compatibility stuff. + else if (typeof arguments[0] == "object") jsPlumb.extend(params, x); + } else { + params = { x : x, y : y }; + if (arguments.length >= 4) params.orientation = [ arguments[2], arguments[3] ]; + if (arguments.length == 6) params.offsets = [ arguments[4], arguments[5] ]; + } + var a = new Anchor(params); + a.clone = function() { + return new Anchor(params); + }; + return a; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types) { + var r = []; + for ( var i = 0; i < types.length; i++) + if (typeof types[i] == "string") + r.push(_currentInstance.Anchors[types[i]]()); + else if (types[i].constructor == Array) + r.push(jsPlumb.makeAnchor(types[i])); + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /** + * Function: makeTarget + * Makes some DOM element a Connection target, allowing you to drag connections to it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a target. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * scope optional. scope for the drop zone. + * dropOptions optional. same stuff as you would pass to dropOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to false. whether or not to delete + * any Endpoints created by a connection to this target if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + this.makeTarget = function(el, params, referenceParams) { + + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + var jpcl = jsPlumb.CurrentLibrary, + scope = p.scope || _currentInstance.Defaults.Scope, + deleteEndpointsOnDetach = p.deleteEndpointsOnDetach || false, + _doOne = function(_el) { + var dropOptions = jsPlumb.extend({}, p.dropOptions || {}); + var _drop = function() { + var draggable = _getElementObject(jpcl.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + // restore the original scope if necessary (issue 57) + scope = _getAttribute(draggable, "originalScope"); + + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + // get the connection, to then get its endpoint + var jpc = floatingConnections[id], + source = jpc.endpoints[0], + _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : null, + // make a new Endpoint + newEndpoint = jsPlumb.addEndpoint(_el, _endpoint); + + var c = jsPlumb.connect({ + source:source, + target:newEndpoint, + scope:scope + }); + if (deleteEndpointsOnDetach) + c.endpointToDeleteOnDetach = newEndpoint; + }; + + var dropEvent = jpcl.dragEvents['drop']; + dropOptions["scope"] = dropOptions["scope"] || scope; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop); + + jpcl.initDroppable(_el, dropOptions); + }; + + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements drop targets. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeTargets = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeTarget(els[i], params, referenceParams); + } + }; + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + for ( var i in ebe) + _currentInstance.deleteEndpoint(ebe[i]); + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + this.deleteEveryEndpoint(); + this.clearListeners(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + + Parameters: + value - whether or not to automatically repaint when the window is resized. + + Returns: void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setDraggableByDefault + * Sets whether or not elements are draggable by default. Default for this is true. + * + * Parameters: + * draggable - value to set + * + * Returns: + * void + */ + this.setDraggableByDefault = function(draggable) { + _draggableByDefault = draggable; + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction + * Sets the function to fire when the window size has changed and a repaint was fired. + * + * Parameters: + * f - Function to execute. + * + * Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: setMouseEventsEnabled + * Sets whether or not mouse events are enabled. Default is true. + * + * Parameters: + * enabled - whether or not mouse events should be enabled. + * + * Returns: + * void + */ + this.setMouseEventsEnabled = function(enabled) { + _mouseEventsEnabled = enabled; + }; + + /* + * Constant for use with the setRenderMode method + */ + this.CANVAS = "canvas"; + + /* + * Constant for use with the setRenderMode method + */ + this.SVG = "svg"; + + this.VML = "vml"; + + /* + * Function: setRenderMode + * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that + * what you asked for is not supported (and that VML is). If you asked for VML but the browser does + * not support it, jsPlumb uses SVG. + * + * Returns: + * the render mode that jsPlumb set, which of course may be different from that requested. + */ + this.setRenderMode = function(mode) { + if (mode) + mode = mode.toLowerCase(); + else + return; + if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); + // now test we actually have the capability to do this. + if (mode === jsPlumb.CANVAS && canvasAvailable) + renderMode = jsPlumb.CANVAS; + else if (mode === jsPlumb.SVG && svgAvailable) + renderMode = jsPlumb.SVG; + else if (vmlAvailable) + renderMode = jsPlumb.VML; + + return renderMode; + }; + + this.getRenderMode = function() { return renderMode; }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * + * Returns: + * void + */ + this.show = function(el) { + _setVisible(el, "block"); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId, + makeAnchor:self.makeAnchor, + makeDynamicAnchor:self.makeDynamicAnchor + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload + * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element. + * + * Returns: + * void + */ + this.unload = function() { + delete endpointsByElement; + delete endpointsByUUID; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + delete canvasList; + }; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + if (timestamp && timestamp === self.timestamp) { + return lastReturnValue; + } + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + if (element.canvas) { + var po = element.canvas.offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(element.canvas.offsetParent); + lastReturnValue[0] = lastReturnValue[0] - po.left; + lastReturnValue[1] = lastReturnValue[1] - po.top; + } + + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + * + * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(_getElementObject(refCanvas)); + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + var orientation = null; + var _lastResult = null; + + this.compute = function(params) { + var xy = params.xy, element = params.element; + var result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + + if (element.canvas) { + var po = element.canvas.offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(element.canvas.offsetParent); + result[0] = result[0] - po.left; + result[1] = result[1] - po.top; + } + + _lastResult = result; + return result; + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { orientation = anchor.getOrientation(); }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchors is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = []; + var _convert = function(anchor) { return anchor.constructor == Anchor ? anchor: jsPlumb.makeAnchor(anchor); }; + for (var i = 0; i < anchors.length; i++) _anchors[i] = _convert(anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null; + var _curIndex = _anchors.length > 0 ? 0 : -1; + this.locked = false; + var self = this; + + // helper method to calculate the distance between the centers of the two elements. + var _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }; + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + var _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + + var pos = _curAnchor.compute(params); + return pos; + }; + + this.getCurrentLocation = function() { + var cl = _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + return cl; + }; + + this.getOrientation = function() { return _curAnchor != null ? _curAnchor.getOrientation() : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * overlays - Optional array of Overlay definitions to appear on this Connection. + */ + var Connection = function(params) { + + jsPlumbUIComponent.apply(this, arguments); + // ************** get the source and target and register the connection. ******************* + var self = this; + var visible = true; + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none"; + }; + var id = new String('_jsplumb_c_' + (new Date()).getTime()); + this.getId = function() { return id; }; + this.parent = params.parent; + /** + Property: source + The source element for this Connection. + */ + this.source = _getElementObject(params.source); + /** + Property:target + The target element for this Connection. + */ + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. + if (params.sourceEndpoint) this.source = params.sourceEndpoint.getElement(); + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + /* + * Property: sourceId + * Id of the source element in the connection. + */ + this.sourceId = _getAttribute(this.source, "id"); + /* + * Property: targetId + * Id of the target element in the connection. + */ + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + + /** + * implementation of abstract method in EventGenerator + * @return list of attached elements. in our case, a list of Endpoints. + */ + this.getAttachedElements = function() { + return self.endpoints; + }; + + /** + * implementation of abstract method in EventGenerator + */ + var srcWhenMouseDown = null, targetWhenMouseDown = null; + this.savePosition = function() { + srcWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.source)); + targetWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.target)); + }; + + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams) { + if (anchorParams) + return jsPlumb.makeAnchor(anchorParams); + }; + var prepareEndpoint = function(existing, index, params, element, connectorPaintStyle, connectorHoverPaintStyle) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] + || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint; + + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified. + if (es.fillStyle == null && connectorPaintStyle != null) + es.fillStyle = connectorPaintStyle.strokeStyle; + + // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does: + //* + if (es.outlineColor == null && connectorPaintStyle != null) + es.outlineColor = connectorPaintStyle.outlineColor; + if (es.outlineWidth == null && connectorPaintStyle != null) + es.outlineWidth = connectorPaintStyle.outlineWidth; + //*/ + + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure? + if (connectorHoverPaintStyle != null) { + if (ehs == null) ehs = {}; + if (ehs.fillStyle == null) { + ehs.fillStyle = connectorHoverPaintStyle.strokeStyle; + } + } + var a = params.anchors ? params.anchors[index] : _makeAnchor(_currentInstance.Defaults.Anchors[index]) || _makeAnchor(jsPlumb.Defaults.Anchors[index]) || _makeAnchor(_currentInstance.Defaults.Anchor) || _makeAnchor(jsPlumb.Defaults.Anchor); + var u = params.uuids ? params.uuids[index] : null; + var e = _newEndpoint( { paintStyle : es, hoverPaintStyle:ehs, endpoint : ep, connections : [ self ], uuid : u, anchor : a, source : element }); + self.endpoints[index] = e; + + if (params.drawEndpoints === false) e.setVisible(false, true, true); + + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source, params.paintStyle, params.hoverPaintStyle); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target, params.paintStyle, params.hoverPaintStyle); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + /* + * Function: setConnector + * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method - the same + * thing that you would set as the 'connector' property on a jsPlumb.connect call. + * + * Parameters: + * connector - Connector definition + */ + this.setConnector = function(connector, doNotRepaint) { + if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent); + var connectorArgs = { _jsPlumb:self._jsPlumb, parent:params.parent, cssClass:params.cssClass }; + if (connector.constructor == String) + this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand. + else if (connector.constructor == Array) + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs)); + this.canvas = this.connector.canvas; + var _mouseDown = false, _mouseWasDown = false, _mouseDownAt = null; + // add mouse events + this.connector.bind("click", function(con, e) { + _mouseWasDown = false; + self.fire("click", self, e); + }); + this.connector.bind("dblclick", function(con, e) { _mouseWasDown = false;self.fire("dblclick", self, e); }); + this.connector.bind("mouseenter", function(con, e) { + if (!self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(true); + } + self.fire("mouseenter", self, e); + } + }); + this.connector.bind("mouseexit", function(con, e) { + if (self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(false); + } + self.fire("mouseexit", self, e); + } + }); + this.connector.bind("mousedown", function(con, e) { + _mouseDown = true; + _mouseDownAt = jsPlumb.CurrentLibrary.getPageXY(e); + self.savePosition(); + }); + this.connector.bind("mouseup", function(con, e) { + _mouseDown = false; + if (self.connector == _connectionBeingDragged) _connectionBeingDragged = null; + }); + + if (!doNotRepaint) self.repaint(); + }; + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc) + */ + + self.setConnector(this.endpoints[0].connector || + this.endpoints[1].connector || + params.connector || + _currentInstance.Defaults.Connector || + jsPlumb.Defaults.Connector, true); + + this.setPaintStyle(this.endpoints[0].connectorStyle || + this.endpoints[1].connectorStyle || + params.paintStyle || + _currentInstance.Defaults.PaintStyle || + jsPlumb.Defaults.PaintStyle, true); + + this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || + this.endpoints[1].connectorHoverStyle || + params.hoverPaintStyle || + _currentInstance.Defaults.HoverPaintStyle || + jsPlumb.Defaults.HoverPaintStyle, true); + + this.paintStyleInUse = this.paintStyle; + + /* + * Property: overlays + * List of Overlays for this Connection. + */ + this.overlays = []; + var _overlays = params.overlays || _currentInstance.Defaults.Overlays; + if (_overlays) { + for (var i = 0; i < _overlays.length; i++) { + var o = _overlays[i], _newOverlay = null, _overlayEvents = null; + if (o.constructor == Array) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0]; + var p = jsPlumb.CurrentLibrary.extend({connection:self, _jsPlumb:_currentInstance}, o[1]); // make a copy of the object so as not to mess up anyone else's reference... + if (o.length == 3) jsPlumb.CurrentLibrary.extend(p, o[2]); + _newOverlay = new jsPlumb.Overlays[renderMode][type](p); + if (p.events) { + for (var evt in p.events) { + _newOverlay.bind(evt, p.events[evt]); + } + } + } else if (o.constructor == String) { + _newOverlay = new jsPlumb.Overlays[renderMode][o]({connection:self, _jsPlumb:_currentInstance}); + } else { + _newOverlay = o; + } + + + + this.overlays.push(_newOverlay); + } + } + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Connection. + * + * Parameters: + * event - the event to bind. Available events on a Connection are: + * - *click* : notification that a Connection was clicked. + * - *dblclick* : notification that a Connection was double clicked. + * - *mouseenter* : notification that the mouse is over a Connection. + * - *mouseexit* : notification that the mouse exited a Connection. + * + * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Connection's paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Connection will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Connection. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + this.addOverlay = function(overlay) { self.overlays.push(overlay); }; + + /** + * Function: removeAllOverlays + * Removes all overlays from the Connection, and then repaints. + */ + this.removeAllOverlays = function() { + self.overlays.splice(0, self.overlays.length); + self.repaint(); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + this.removeOverlay = function(overlayId) { + var idx = -1; + for (var i = 0; i < self.overlays.length; i++) { + if (overlayId === self.overlays[i].id) { + idx = i; + break; + } + } + if (idx != -1) self.overlays.splice(idx, 1); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + this.removeOverlays = function() { + for (var i = 0; i < arguments.length; i++) + self.removeOverlay(arguments[i]); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + this.labelStyle = params.labelStyle || _currentInstance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + if (this.label) { + this.overlays.push(new jsPlumb.Overlays[renderMode].Label( { + cssClass:params.cssClass, + labelStyle : this.labelStyle, + label : this.label, + connection:self, + _jsPlumb:_currentInstance + })); + } + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String or a Function that returns a String. + */ + this.setLabel = function(l) { + self.label = l; + _currentInstance.repaint(self.source); + }; + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var otherOffset = offsets[this.targetId]; + var otherWH = sizes[this.targetId]; + var anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1] + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0] + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc }); + + /* + * Paints the Connection. Not exposed for public usage. + * + * Parameters: + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp; + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = false; + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }); + _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + var sAnchorP = this.endpoints[sIdx].anchor.getCurrentLocation(); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.getCurrentLocation(); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + + /* paint overlays*/ + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + var s = o.computeMaxSize(self.connector); + if (s > maxSize) + maxSize = s; + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, self.paintStyleInUse.lineWidth, maxSize); + + self.connector.paint(dim, self.paintStyleInUse); + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim); + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function() { + this.paint({ elId : this.sourceId, recalc : true }); + }; + + _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide + // whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + + // just to make sure the UI gets initialised fully on all browsers. + self.repaint(); + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1 + * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't + * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you + * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or + * 'target' Endpoints for Connections. + * + * + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * connections - optional list of Connections to configure the Endpoint with. + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + params = params || {}; + var self = this; +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Endpoint. + * + * Parameters: + * event - the event to bind. Available events on an Endpoint are: + * - *click* : notification that a Endpoint was clicked. + * - *dblclick* : notification that a Endpoint was double clicked. + * - *mouseenter* : notification that the mouse is over a Endpoint. + * - *mouseexit* : notification that the mouse exited a Endpoint. + * + * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Endpoint's paint style and then repaints the Endpoint. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Endpoint will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Endpoint. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + var visible = true; + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + var id = new String('_jsplumb_e_' + (new Date()).getTime()); + this.getId = function() { return id; }; + if (params.dynamicAnchors) + self.anchor = new DynamicAnchor(jsPlumb.makeAnchors(params.dynamicAnchors)); + else + self.anchor = params.anchor ? jsPlumb.makeAnchor(params.anchor) : params.anchors ? jsPlumb.makeAnchor(params.anchors) : jsPlumb.makeAnchor("TopCenter"); + var _endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot", + endpointArgs = { _jsPlumb:self._jsPlumb, parent:params.parent }; + if (_endpoint.constructor == String) + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint](endpointArgs); + else if (_endpoint.constructor == Array) + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint[0]](jsPlumb.extend(_endpoint[1], endpointArgs )); + else _endpoint = _endpoint.clone(); + self.endpoint = _endpoint; + this.endpoint.bind("click", function(e) { self.fire("click", self, e); }); + this.endpoint.bind("dblclick", function(e) { self.fire("dblclick", self, e); }); + this.setPaintStyle(params.paintStyle || + params.style || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyle, true); + this.setHoverPaintStyle(params.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyle, true); + this.paintStyleInUse = this.paintStyle; + this.connectorStyle = params.connectorStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.parent = params.parent; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + var _element = params.source, + _uuid = params.uuid, + floatingEndpoint = null, + inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + this.element = _element; + var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + /* + * Property: canvas + * The Endpoint's Canvas. + */ + this.canvas = this.endpoint.canvas; + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + this.connections = params.connections || []; + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + var _reattach = params.reattach || false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + + // this avoids a circular loop + if (!ignoreTarget) { + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + t.detach(connection, true); + // check connection to see if we want to delete the other endpoint. + // if the user uses makeTarget to make some element a target for connections, + // it is possible that they will have set 'endpointToDeleteOnDetach': when + // you make a connection to an element that acts as a target (note: NOT an + // Endpoint; just some div as a target), Endpoints are created for that + // connection. so if you then delete that Connection, it is feasible you + // will want these auto-generated endpoints to be removed. + if (connection.endpointToDeleteOnDetach && connection.endpointToDeleteOnDetach.connections.length == 0) + jsPlumb.deleteEndpoint(connection.endpointToDeleteOnDetach); + } + _removeElements(connection.connector.getDisplayElements(), connection.parent); + _removeFromList(connectionsByScope, connection.scope, connection); + if(!ignoreTarget) fireDetachEvent(connection); + } + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + */ + this.detachAll = function() { + while (self.connections.length > 0) { + self.detach(self.connections[0]); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + */ + this.detachFrom = function(targetEndpoint) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + c[i].setHover(false); + self.detach(c[i]); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + return _newEndpoint( { anchor : self.anchor, source : _element, paintStyle : this.paintStyle, endpoint : _endpoint }); + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + return (self.connections.length < _maxConnections) ? null : self.connections[0]; + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + /* + * Function: setStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to + * setStyle and deprecate it if so. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + * + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + /* + * Function: paint + * Paints the Endpoint, recalculating offset and anchor positions if necessary. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + + params = params || {}; + var timestamp = params.timestamp; + if (!timestamp || self.timestamp !== timestamp) { + var ap = params.anchorPoint, canvas = params.canvas, connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + var xy = params.offset || offsets[_elementId]; + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (self.anchor.isDynamic) { + if (self.connections.length > 0) { + var c = self.connections[0]; + var oIdx = c.endpoints[0] == self ? 1 : 0; + var oId = oIdx == 0 ? c.sourceId : c.targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + } + ap = self.anchor.compute(anchorParams); + } + + var d = _endpoint.compute(ap, self.anchor.getOrientation(), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + _endpoint.paint(d, self.paintStyleInUse, self.anchor); + + self.timestamp = timestamp; + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + var start = function() { + jpc = self.connectorSelector(); + if (self.isFull() && !dragAllowedWhenFull) return false; + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + n.style.position = "absolute"; + var nE = _getElementObject(n); + _appendElement(n, self.parent); + // create and assign an id, and initialize the offset. + var id = _getId(nE); + + // set the offset of this div to be where 'inPlaceCopy' is, to start with. + var ipcoel = _getElementObject(inPlaceCopy.canvas), + ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel), + po = inPlaceCopy.canvas.offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(inPlaceCopy.canvas.offsetParent); + jsPlumb.CurrentLibrary.setOffset(n, {left:ipco.left - po.left, top:ipco.top-po.top}); + + _updateOffset( { elId : id }); + + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor( { reference : self.anchor, referenceCanvas : self.canvas }); + floatingEndpoint = _newEndpoint({ paintStyle : self.paintStyle, endpoint : _endpoint, anchor : floatingAnchor, source : nE }); + + if (jpc == null) { + self.anchor.locked = true; + // create a connection. one end is this endpoint, the + // other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : _getElementObject(_element), + target : _getElementObject(n), + anchors : [ self.anchor, floatingAnchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays + }); + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.connector.setHover(false); + } else { + existingJpc = true; + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.connector.setHover(false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas)); + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; // are we the source or the target? + + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas); + var dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // get a new, temporary scope, to use (issue 57) + var newScope = "scope_" + (new Date()).getTime(); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + // set the new, temporary scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(i, newScope); + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // TODO unregister on stop? or will floating endpoint's + // destruction be assured. + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be + // dragging the source at any time + // before this connection is either discarded or made into a + // permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var jpcl = jsPlumb.CurrentLibrary, + dragOptions = params.dragOptions || {}, + defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions), + startEvent = jpcl.dragEvents['start'], + stopEvent = jpcl.dragEvents['stop'], + dragEvent = jpcl.dragEvents['drag']; + + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], + function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + jsPlumb.CurrentLibrary.setOffset(n, _ui); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements( [ n, floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (_reattach) { + + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } else { + jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc); // the main endpoint will inform the floating endpoint + // to disconnect, and also post the detached event. + } + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElements(jpc.connector.getDisplayElements(), self.parent); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint(); + jpc.setHover(false); + jpc.repaint(); + jpc = null; + delete inPlaceCopy; + delete endpointsByElement[floatingEndpoint.elementId]; + floatingEndpoint = null; + delete floatingEndpoint; + + _currentInstance.currentlyDragging = false; + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas) { + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + var drop = function() { + var draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)); + var id = _getAttribute(draggable, "dragId"); + var elId = _getAttribute(draggable, "elId"); + + // restore the original scope if necessary (issue 57) + var scope = _getAttribute(draggable, "originalScope"); + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + var jpc = floatingConnections[id]; + + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // todo test that the target is not full. + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + if (!jpc.suspendedEndpoint) { + _addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + _currentInstance.fire("jsPlumbConnectionDetached", { + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }); + } + + jsPlumb.repaint(elId); + + _currentInstance.fire("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], + targetEndpoint : jpc.endpoints[1], + connection:jpc + }); + } + + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute( _getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + } + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute(_getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + } + }); + + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas)); + + return self; + }; + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + //if (_defaults) jsPlumb.extend(j.Defaults, _defaults); + return j; + }; + + var _curryAnchor = function(x,y,ox,oy) { + return function() { + return jsPlumb.makeAnchor(x,y,ox,oy); + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1); + + jsPlumb.Defaults.DynamicAnchors = function() { + return jsPlumb.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"]); + }; + jsPlumb.Anchors["AutoDefault"] = function() { return jsPlumb.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors()); }; + + +})(); +/* +* jsPlumb-defaults-1.3.0-RC1 +* +* Copyright 2010 - 2011 Simon Porritt http://jsplumb.org +* +* Triple licensed under the MIT, GPL2 and Beer licenses. +*/ + +(function() { + + + + /** + * + * Helper class to consume unused mouse events by components that are DOM elements and + * are used by all of the different rendering modes. + * + */ + jsPlumb.DOMElementComponent = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + // when render mode is canvas, these functions may be called by the canvas mouse handler. + // this component is safe to pipe this stuff to /dev/null. + this.mousemove = + this.dblclick = + this.click = + this.mousedown = + this.mouseup = function(e) { }; + }; + + /** + * Class: Connectors.Straight + * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + var currentPoints = null; + var _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + _dx = _tx - _sx, _dy = (_ty - _sy); + _m = _dy / _dx, _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + + return currentPoints; + }; + + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + var xp = _sx + (location * _dx); + var yp = (_m == Infinity || _m == -Infinity) ? _sy + (location * (_ty - _sy)) : (_m * xp) + _b; + return {x:xp, y:yp}; + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { return _m; }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + var orientation = distance > 0 ? 1 : -1; + var y = Math.abs(distance * Math.sin(_theta)); + if (_sy > _ty) y = y * -1; + var x = Math.abs(distance * Math.cos(_theta)); + if (_sx > _tx) x = x * -1; + return {x:p.x + (orientation * x), y:p.y + (orientation * y)}; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = self.gradientAtPoint(p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + }; + }; + + + /** + * Class:Connectors.Bezier + * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's + * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below. + */ + /** + * Function:Constructor + * + * Parameters: + * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line. + * Optional; defaults to 150. + * + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + params = params || {}; + this.majorAnchor = params.curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h; + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) + { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx); var minx2 = Math.min(_CP[0], _CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(_sx,_tx); var maxx2 = Math.max(_CP[0], _CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty); var miny2 = Math.min(_CP[1], _CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(_sy,_ty); var maxy2 = Math.max(_CP[1], _CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + return jsBezier.perpendicularToCurveAt(_makeCurve(), location, length, distance); + }; + + }; + + + /** + * Class: Connectors.Flowchart + * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments. + */ + /** + * Function: Constructor + * + * Parameters: + * stub - minimum length for the stub at each end of the connector. defaults to 30 pixels. + */ + jsPlumb.Connectors.Flowchart = function(params) { + params = params || {}; + var self = this, + minStubLength = params.stub || params.minStubLength /* bwds compat. */ || 30, + segments = [], + segmentGradients = [], + segmentProportions = [], + segmentLengths = [], + segmentProportionalLengths = [], + points = [], + swapX, + swapY, + /** + * recalculates the gradients of each segment, and the points at which the segments begin, proportional to the total length travelled + * by all the segments that constitute the connector. + */ + updateSegmentGradientsAndProportions = function(startX, startY, endX, endY) { + var total = 0; + for (var i = 0; i < segments.length; i++) { + var sx = i == 0 ? startX : segments[i][2], + sy = i == 0 ? startY : segments[i][3], + ex = segments[i][0], + ey = segments[i][1]; + + segmentGradients[i] = sx == ex ? Infinity : 0; + segmentLengths[i] = Math.abs(sx == ex ? ey - sy : ex - sx); + total += segmentLengths[i]; + } + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segmentLengths[i] / total; + segmentProportions[i] = [curLoc, (curLoc += (segmentLengths[i] / total)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0]; + var ly = segments.length == 0 ? sy : segments[segments.length - 1][1]; + segments.push([x, y, lx, ly]); + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment + * that contains the point which is 'location' distance along the entire path, where + * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths + * are made up of a list of segments, each of which contributes some fraction to + * the total length. + */ + findSegmentForLocation = function(location) { + var idx = segmentProportions.length - 1, inSegmentProportion = 0; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + segments = []; + segmentGradients = []; + segmentProportionalLengths = []; + segmentLengths = []; + segmentProportionals = []; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + offx = (lw / 2) + (minStubLength * 2), + offy = (lw / 2) + (minStubLength * 2), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(), + to = targetAnchor.orientation || targetAnchor.getOrientation(), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx, + h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy; + if (w < minWidth) { + offx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + offy += (minWidth - h) / 2; + h = minWidth; + } + var sx = swapX ? w-offx : offx, + sy = swapY ? h-offy : offy, + tx = swapX ? offx : w-offx , + ty = swapY ? offy : h-offy, + startStubX = sx + (so[0] * minStubLength), + startStubY = sy + (so[1] * minStubLength), + endStubX = tx + (to[0] * minStubLength), + endStubY = ty + (to[1] * minStubLength), + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2); + + x -= offx; y -= offy; + points = [x, y, w, h, sx, sy, tx, ty], extraPoints = []; + + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + if (so[0] == 0) { + var startStubIsBeforeEndStub = startStubY < endStubY; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } + } + else { + var startStubIsBeforeEndStub = startStubX < endStubX; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } + } + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentGradientsAndProportions(sx, sy, tx, ty); + + return points; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location) { + return self.pointAlongPathFrom(location, 0); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location) { + return segmentGradients[findSegmentForLocation(location)["index"]]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance) { + var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segmentLengths[s.index], m = segmentGradients[s.index]; + var e = { + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + return e; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = segmentGradients[p.segmentInfo.index]; + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + + }; + + }; + + // ********************************* END OF CONNECTOR TYPES ******************************************************************* + + // ********************************* ENDPOINT TYPES ******************************************************************* + + /** + * Class: Endpoints.Dot + * A round endpoint, with default radius 10 pixels. + */ + + /** + * Function: Constructor + * + * Parameters: + * + * radius - radius of the endpoint. defaults to 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + var self = this; + params = params || {}; + this.radius = params.radius || 10; + this.defaultOffset = 0.5 * this.radius; + this.defaultInnerRadius = this.radius / 3; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var r = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - r; + var y = anchorPoint[1] - r; + return [ x, y, r * 2, r * 2, r ]; + }; + }; + + /** + * Class: Endpoints.Rectangle + * A Rectangular Endpoint, with default size 20x20. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the endpoint. defaults to 20 pixels. + * height - height of the endpoint. defaults to 20 pixels. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + var self = this; + params = params || {}; + this.width = params.width || 20; + this.height = params.height || 20; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + return [ x, y, width, height]; + }; + }; + + /** + * Class: Endpoints.Image + * Draws an image as the Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * src - location of the image to use. + */ + jsPlumb.Endpoints.Image = function(params) { + + jsPlumb.DOMElementComponent.apply(this, arguments); + + var self = this, initialized = false; + this.img = new Image(); + self.ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.src || params.url; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + self.anchorPoint = anchorPoint; + if (self.ready) return [anchorPoint[0] - self.img.width / 2, anchorPoint[1] - self.img.height/ 2, self.img.width, self.img.height]; + else return [0,0,0,0]; + }; + + self.canvas = document.createElement("img"), initialized = false; + self.canvas.style["margin"] = 0; + self.canvas.style["padding"] = 0; + self.canvas.style["outline"] = 0; + self.canvas.style["position"] = "absolute"; + self.canvas.className = jsPlumb.endpointClass; + jsPlumb.appendElement(self.canvas, params.parent); + self.attachListeners(self.canvas, self); + + var actuallyPaint = function(d, style, anchor) { + if (!initialized) { + self.canvas.setAttribute("src", self.img.src); + initialized = true; + } + var width = self.img.width, + height = self.img.height, + x = self.anchorPoint[0] - (width/2), + y = self.anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(self.canvas, x, y, width, height); + }; + + this.paint = function(d, style, anchor) { + if (self.ready) { + actuallyPaint(d, style, anchor); + } + else { + window.setTimeout(function() { + self.paint(d, style, anchor); + }, 200); + } + }; + }; + + /** + * Class: Endpoints.Blank + * An Endpoint that paints nothing on the screen, and cannot be interacted with using the mouse. There are no constructor parameters for this Endpoint. + */ + jsPlumb.Endpoints.Blank = function() { + jsPlumb.DOMElementComponent.apply(this, arguments); + this.compute = function() { + return [0,0,0,0]; + }; + + self.canvas = document.createElement("div"); + this.paint = function() { }; + }; + + /** + * Class: Endpoints.Triangle + * A triangular Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the triangle's base. defaults to 55 pixels. + * height - height of the triangle from base to apex. defaults to 55 pixels. + */ + jsPlumb.Endpoints.Triangle = function(params) { + params = params || { }; + params.width = params.width || 55; + param.height = params.height || 55; + this.width = params.width; + this.height = params.height; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + return [ x, y, width, height ]; + }; + }; +// ********************************* END OF ENDPOINT TYPES ******************************************************************* + + +// ********************************* OVERLAY DEFINITIONS *********************************************************************** + + /** + * Class: Overlays.Arrow + * + * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length + * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction + * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line + * across the tail. + */ + /** + * Function: Constructor + * + * Parameters: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + params = params || {}; + var self = this; + this.length = params.length || 20; + this.width = params.width || 20; + this.id = params.id; + this.connection = params.connection; + var direction = (params.direction || 1) < 0 ? -1 : 1; + var paintStyle = params.paintStyle || { lineWidth:1 }; + this.loc = params.location == null ? 0.5 : params.location; + // how far along the arrow the lines folding back in come to. default is 62.3%. + var foldback = params.foldback || 0.623; + var _getFoldBackPoint = function(connector, loc) { + if (foldback == 0.5) return connector.pointOnPath(loc); + else { + var adj = 0.5 - foldback; // we calculate relative to the center + return connector.pointAlongPathFrom(loc, direction * self.length * adj); + } + }; + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + // this is the arrow head position + var hxy = connector.pointAlongPathFrom(self.loc, direction * (self.length / 2)); + // this is the center of the tail + var txy = connector.pointAlongPathFrom(self.loc, -1 * direction * (self.length / 2)), tx = txy.x, ty = txy.y; + // this is the tail vector + var tail = connector.perpendicularToPathAt(self.loc, self.width, -1 * direction * (self.length / 2)); + // this is the point the tail goes in to + var cxy = _getFoldBackPoint(connector, self.loc); + + // if loc = 1, then hxy should be flush with the element, or if direction == -1, the tail midpoint. + if (self.loc == 1) { + var lxy = connector.pointOnPath(self.loc); + // TODO determine why the 1.2.6 released version does not + // use 'direction' in the two equations below, yet both + // that and 1.3.0 still paint the arrows correctly. + var dx = (lxy.x - hxy.x) * direction, dy = (lxy.y - hxy.y) * direction; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + // if loc = 0, then tail midpoint should be flush with the element, or, if direction == -1, hxy should be. + if (self.loc == 0) { + var lxy = connector.pointOnPath(self.loc); + var tailMid = foldback > 1 ? cxy : { + x:tail[0].x + ((tail[1].x - tail[0].x) / 2), + y:tail[0].y + ((tail[1].y - tail[0].y) / 2) + }; + var dx = (lxy.x - tailMid.x) * direction, dy = (lxy.y - tailMid.y) * direction; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x); + var maxx = Math.max(hxy.x, tail[0].x, tail[1].x); + var miny = Math.min(hxy.y, tail[0].y, tail[1].y); + var maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + var d = { hxy:hxy, tail:tail, cxy:cxy }, + strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle, + fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle, + lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth; + + self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions); + + return [ minx, maxx, miny, maxy]; + }; + }; + + /** + * Class: Overlays.PlainArrow + * + * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + /** + * Class: Overlays.Diamond + * + * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40; + var p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + + + /** + * Class: Overlays.Label + * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb + * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter + * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it + * puts on the Label's 'style' attribute, so the end result is the same. + */ + /** + * Function: Constructor + * + * Parameters: + * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes + * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle. + * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * labelStyle - (deprecated) js object containing style instructions for the label. defaults to jsPlumb.Defaults.LabelStyle. + * borderWidth - (deprecated) width of a border to paint. defaults to zero. + * borderStyle - (deprecated) strokeStyle to use when painting the border, if necessary. + * + */ + jsPlumb.Overlays.Label = function(params) { + jsPlumb.DOMElementComponent.apply(this, arguments); + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.labelStyle.font = this.labelStyle.font || "12px sans-serif"; + this.label = params.label; + this.connection = params.connection; + this.id = params.id; + var self = this; + var labelWidth = null, labelHeight = null, labelText = null, labelPadding = null; + this.location = params.location || 0.5; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var initialised = false, + labelText = null, + div = document.createElement("div"); + div.style["position"] = "absolute"; + div.style["textAlign"] = "center"; + div.style["cursor"] = "pointer"; + div.style["font"] = self.labelStyle.font; + div.style["color"] = self.labelStyle.color || "black"; + if (self.labelStyle.fillStyle) div.style["background"] = self.labelStyle.fillStyle;//_convertStyle(self.labelStyle.fillStyle, true); + if (self.labelStyle.borderWidth > 0) { + var dStyle = self.labelStyle.borderStyle ? self.labelStyle.borderStyle/*_convertStyle(self.labelStyle.borderStyle, true)*/ : "black"; + div.style["border"] = self.labelStyle.borderWidth + "px solid " + dStyle; + } + if (self.labelStyle.padding) div.style["padding"] = self.labelStyle.padding; + + var clazz = params["_jsPlumb"].overlayClass + " " + + (self.labelStyle.cssClass ? self.labelStyle.cssClass : + params.cssClass ? params.cssClass : ""); + + div.className = clazz; + + jsPlumb.appendElement(div, params.connection.parent); + jsPlumb.getId(div); + self.attachListeners(div, self); + this.paint = function(connector, d, connectorDimensions) { + if (!initialised) { + connector.appendDisplayElement(div); + self.attachListeners(div, connector); + initialised = true; + } + div.style.left = (connectorDimensions[0] + d.minx) + "px"; + div.style.top = (connectorDimensions[1] + d.miny) + "px"; + }; + + this.getTextDimensions = function(connector) { + labelText = typeof self.label == 'function' ? self.label(self) : self.label; + div.innerHTML = labelText.replace(/\r\n/g, "
"); + var de = jsPlumb.CurrentLibrary.getElementObject(div), + s = jsPlumb.CurrentLibrary.getSize(de); + return {width:s[0], height:s[1]}; + }; + + this.computeMaxSize = function(connector) { + var td = self.getTextDimensions(connector); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + var td = self.getTextDimensions(connector); + if (td.width != null) { + var cxy = connector.pointOnPath(self.location); + + var minx = cxy.x - (td.width / 2); + var miny = cxy.y - (td.height / 2); + + self.paint(connector, { + minx:minx, + miny:miny, + td:td, + cxy:cxy + }, connectorDimensions); + + return [minx, minx+td.width, miny, miny+td.height]; + } + else return [0,0,0,0]; + }; + }; + + // ********************************* END OF OVERLAY DEFINITIONS *********************************************************************** + + // ********************************* OVERLAY CANVAS RENDERERS*********************************************************************** + + // ********************************* END OF OVERLAY CANVAS RENDERERS *********************************************************************** +})();;(function() { + + // http://ajaxian.com/archives/the-vml-changes-in-ie-8 + // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/ + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + + var vmlAttributeMap = { + "stroke-linejoin":"joinstyle", + "joinstyle":"joinstyle", + "endcap":"endcap", + "miterlimit":"miterlimit" + }; + + if (document.createStyleSheet) { + + // this is the style rule for IE7/6: it uses a CSS class, tidy. + document.createStyleSheet().addRule(".jsplumb_vml", "behavior:url(#default#VML);position:absolute;"); + + // these are for VML in IE8. you have to explicitly call out which elements + // you're going to expect to support VML! + // + // try to avoid IE8. it is recommended you set X-UA-Compatible="IE=7" if you can. + // + document.createStyleSheet().addRule("jsplumb\\:textbox", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:oval", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:rect", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:stroke", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:shape", "behavior:url(#default#VML);position:absolute;"); + + // in this page it is also mentioned that IE requires the extra arg to the namespace + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either. + // var iev = document.documentMode; + //if (!iev || iev < 8) + document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); + //else + // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML"); + } + + var scale = 1000, + _atts = function(o, atts) { + for (var i in atts) { + // IE8 fix: setattribute does not work after an element has been added to the dom! + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + //o.setAttribute(i, atts[i]); + o[i] = atts[i]; + } + }, + _node = function(name, d, atts) { + atts = atts || {}; + var o = document.createElement("jsplumb:" + name); + o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml"; + _pos(o, d); + _atts(o, atts); + return o; + }, + _pos = function(o,d) { + o.style.left = d[0] + "px"; + o.style.top = d[1] + "px"; + o.style.width= d[2] + "px"; + o.style.height= d[3] + "px"; + o.style.position = "absolute"; + }, + _conv = function(v) { + return Math.floor(v * scale); + }, + _convertStyle = function(s, ignoreAlpha) { + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + + return o; + }, + _applyStyles = function(node, style, component) { + var styleToWrite = {}; + if (style.strokeStyle) { + styleToWrite["stroked"] = "true"; + styleToWrite["strokecolor"] =_convertStyle(style.strokeStyle, true); + styleToWrite["strokeweight"] = style.lineWidth + "px"; + } + else styleToWrite["stroked"] = "false"; + + if (style.fillStyle) { + styleToWrite["filled"] = "true"; + styleToWrite["fillcolor"] = _convertStyle(style.fillStyle, true); + } + else styleToWrite["filled"] = "false"; + + if(style["dashstyle"]) { + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = style["dashstyle"]; + } + else if (style["stroke-dasharray"] && style["lineWidth"]) { + var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",", + parts = style["stroke-dasharray"].split(sep), + styleToUse = ""; + for(var i = 0; i < parts.length; i++) { + styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep); + } + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = styleToUse; + } + + _atts(node, styleToWrite); + }, + /* + * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. + */ + VmlComponent = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }, + /* + * Base class for Vml connectors. extends VmlComponent. + */ + VmlConnector = function(params) { + var self = this; + self.strokeNode = null; + self.canvas = null; + VmlComponent.apply(this, arguments); + clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : ""); + this.paint = function(d, style, anchor) { + if (style != null) { + var path = self.getPath(d), p = { "path":path }; + + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + outlineStyle = { + strokeStyle:_convertStyle(style.outlineColor), + lineWidth:outlineStrokeWidth + }; + + if (self.bgCanvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.bgCanvas = _node("shape", d, p); + jsPlumb.appendElement(self.bgCanvas, params.parent); + _pos(self.bgCanvas, d); + displayElements.push(self.bgCanvas); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.bgCanvas, d); + _atts(self.bgCanvas, p); + } + + _applyStyles(self.bgCanvas, outlineStyle, self); + } + + if (self.canvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.canvas = _node("shape", d, p); + jsPlumb.appendElement(self.canvas, params.parent); + displayElements.push(self.canvas); + + self.attachListeners(self.canvas, self); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.canvas, d); + _atts(self.canvas, p); + } + + _applyStyles(self.canvas, style, self); + } + }; + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + self.canvas.parentNode.appendChild(el); + displayElements.push(el); + }; + }, + /* + * + * Base class for Vml Endpoints. extends VmlComponent. + * + */ + VmlEndpoint = function(params) { + VmlComponent.apply(this, arguments); + var vml = null, self = this; + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function(d, style, anchor) { + var p = { }; + + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (vml == null) { + p["class"] = jsPlumb.endpointClass; + vml = self.getVml([0,0, d[2], d[3]], p, anchor); + self.canvas.appendChild(vml); + self.attachListeners(vml, self); + } + else { + //p["coordsize"] = "1,1";//(d[2] * scale) + "," + (d[3] * scale); again, unsure. + _pos(vml, [0,0, d[2], d[3]]); + _atts(vml, p); + } + + _applyStyles(vml, style); + }; + }; + + jsPlumb.Connectors.vml.Bezier = function() { + jsPlumb.Connectors.Bezier.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Straight = function() { + jsPlumb.Connectors.Straight.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Flowchart = function() { + jsPlumb.Connectors.Flowchart.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l"; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]); + } + // finally draw a line to the end + p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e"; + return p; + }; + }; + + jsPlumb.Endpoints.vml.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("oval", d, atts); }; + }; + + jsPlumb.Endpoints.vml.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("rect", d, atts); }; + }; + + /* + * VML Image Endpoint is the same as the default image endpoint. + */ + jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image; + + /** + * placeholder for Blank endpoint in vml renderer. + */ + jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank; + + /** + * VML Label renderer. uses the default label renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label; + + var AbstractVmlArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + VmlComponent.apply(this, arguments); + var self = this, canvas = null, path =null; + var getPath = function(d, connectorDimensions) { + return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) + + " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + + " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + + " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + + " x e"; + }; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) { + var p = {}; + if (strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] =_convertStyle(strokeStyle, true); + } + if (lineWidth) p["strokeweight"] = lineWidth + "px"; + if (fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = fillStyle; + } + var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + w = Math.abs(xmax - xmin), + h = Math.abs(ymax - ymin), + dim = [xmin, ymin, w, h]; + + // for VML, we create overlays using shapes that have the same dimensions and + // coordsize as their connector - overlays calculate themselves relative to the + // connector (it's how it's been done since the original canvas implementation, because + // for canvas that makes sense). + p["path"] = getPath(d, connectorDimensions); + p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale); + + dim[0] = connectorDimensions[0]; + dim[1] = connectorDimensions[1]; + dim[2] = connectorDimensions[2]; + dim[3] = connectorDimensions[3]; + + if (canvas == null) { + //p["class"] = jsPlumb.overlayClass; // TODO currentInstance? + canvas = _node("shape", dim, p); + connector.appendDisplayElement(canvas); + self.attachListeners(canvas, connector); + } + else { + _pos(canvas, dim); + _atts(canvas, p); + } + }; + }; + + jsPlumb.Overlays.vml.Arrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.vml.PlainArrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.vml.Diamond = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/** + * SVG support for jsPlumb. + * + * things to investigate: + * + * gradients: https://developer.mozilla.org/en/svg_in_html_introduction + * css:http://tutorials.jenkov.com/svg/svg-and-css.html + * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath + * pointer events: https://developer.mozilla.org/en/css/pointer-events + * + */ +;(function() { + + var svgAttributeMap = { + "stroke-linejoin":"stroke-linejoin", + "joinstyle":"stroke-linejoin", + "stroke-dashoffset":"stroke-dashoffset" + }; + + var ns = { + svg:"http://www.w3.org/2000/svg", + xhtml:"http://www.w3.org/1999/xhtml" + }, + _attr = function(node, attributes) { + for (var i in attributes) + node.setAttribute(i, "" + attributes[i]); + }, + _node = function(name, attributes) { + var n = document.createElementNS(ns.svg, name); + attributes = attributes || {}; + attributes["version"] = "1.1"; + attributes["xmnls"] = ns.xhtml; + _attr(n, attributes); + return n; + }, + _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; }, + _convertStyle = function(s, ignoreAlpha) { + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + + return o; + }, + _clearGradient = function(parent) { + for (var i = 0; i < parent.childNodes.length; i++) { + if (parent.childNodes[i].tagName == "linearGradient" || parent.childNodes[i].tagName == "radialGradient") + parent.removeChild(parent.childNodes[i]); + } + }, + _updateGradient = function(parent, node, style, dimensions) { + var id = "jsplumb_gradient_" + (new Date()).getTime(); + // first clear out any existing gradient + _clearGradient(parent); + // this checks for an 'offset' property in the gradient, and in the absence of it, assumes + // we want a linear gradient. if it's there, we create a radial gradient. + // it is possible that a more explicit means of defining the gradient type would be + // better. relying on 'offset' means that we can never have a radial gradient that uses + // some default offset, for instance. + if (!style.gradient.offset) { + var g = _node("linearGradient", {id:id}); + parent.appendChild(g); + } + else { + var g = _node("radialGradient", { + id:id + }); + parent.appendChild(g); + } + + // the svg radial gradient seems to treat stops in the reverse + // order to how canvas does it. so we want to keep all the maths the same, but + // iterate the actual style declarations in reverse order, if the x indexes are not in order. + for (var i = 0; i < style.gradient.stops.length; i++) { + // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of + // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments. + // so, not too concerned about leaving this in for now. + var styleToUse = i; + if (dimensions.length == 8) + styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i; + else + styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i; + var stopColor = _convertStyle(style.gradient.stops[styleToUse][1], true); + var s = _node("stop", {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor}); + g.appendChild(s); + } + var applyGradientTo = style.strokeStyle ? "stroke" : "fill"; + node.setAttribute("style", applyGradientTo + ":url(#" + id + ")"); + }, + _applyStyles = function(parent, node, style, dimensions) { + + if (style.gradient) { + _updateGradient(parent, node, style, dimensions); + } + else { + // make sure we clear any existing gradient + _clearGradient(parent); + node.setAttribute("style", ""); + } + + node.setAttribute("fill", style.fillStyle ? _convertStyle(style.fillStyle, true) : "none"); + node.setAttribute("stroke", style.strokeStyle ? _convertStyle(style.strokeStyle, true) : "none"); + if (style.lineWidth) { + node.setAttribute("stroke-width", style.lineWidth); + } + + // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like + // the syntax in VML but is actually kind of nasty: values are given in the pixel + // coordinate space, whereas in VML they are multiples of the width of the stroked + // line, which makes a lot more sense. for that reason, jsPlumb is supporting both + // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from + // VML, which will be the preferred method. the code below this converts a dashstyle + // attribute given in terms of stroke width into a pixel representation, by using the + // stroke's lineWidth. + if(style["stroke-dasharray"]) { + node.setAttribute("stroke-dasharray", style["stroke-dasharray"]); + } + if (style["dashstyle"] && style["lineWidth"]) { + var sep = style["dashstyle"].indexOf(",") == -1 ? " " : ",", + parts = style["dashstyle"].split(sep), + styleToUse = ""; + parts.forEach(function(p) { + styleToUse += (Math.floor(p * style.lineWidth) + sep); + }); + node.setAttribute("stroke-dasharray", styleToUse); + } + + // extra attributes such as join type, dash offset. + for (var i in svgAttributeMap) { + if (style[i]) { + node.setAttribute(svgAttributeMap[i], style[i]); + } + } + }, + _decodeFont = function(f) { + var r = /([0-9].)(p[xt])\s(.*)/; + var bits = f.match(r); + return {size:bits[1] + bits[2], font:bits[3]}; + }; + + /* + * Base class for SVG components. + */ + var SvgComponent = function(cssClass, originalArgs, pointerEventsSpec) { + var self = this; + pointerEventsSpec = pointerEventsSpec || "all"; + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + self.canvas = null, self.path = null, self.svg = null; + + this.setHover = function() { }; + + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.sizeCanvas(self.canvas,0,0,1,1); + + var clazz = cssClass + " " + (originalArgs[0].cssClass || ""); + + self.svg = _node("svg", { + "style":"", + "width":0, + "height":0, + "pointer-events":pointerEventsSpec, + "class": clazz + }); + + jsPlumb.appendElement(self.canvas, originalArgs[0]["parent"]); + self.canvas.appendChild(self.svg); + + // TODO this displayElement stuff is common between all components, across all + // renderers. would be best moved to jsPlumbUIComponent. + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + this.paint = function(d, style, anchor) { + if (style != null) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + _attr(self.svg, { + "style":_pos([0,0,d[2], d[3]]), + "width": d[2], + "height": d[3] + }); + self._paint.apply(this, arguments); + } + }; + }; + + /* + * Base class for SVG connectors. + */ + var SvgConnector = function(params) { + var self = this; + SvgComponent.apply(this, [ params["_jsPlumb"].connectorClass, arguments, "none" ]); + this._paint = function(d, style) { + var p = self.getPath(d), a = { "d":p }, outlineStyle = null; + a["pointer-events"] = "all"; + + // outline style. actually means drawing an svg object underneath the main one. + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + outlineStyle = { + strokeStyle:_convertStyle(style.outlineColor), + lineWidth:outlineStrokeWidth + }; + + if (self.bgPath == null) { + self.bgPath = _node("path", a); + self.svg.appendChild(self.bgPath); + self.attachListeners(self.bgPath, self); + } + else { + _attr(self.bgPath, a); + } + + _applyStyles(self.svg, self.bgPath, outlineStyle, d); + } + + + if (self.path == null) { + self.path = _node("path", a); + self.svg.appendChild(self.path); + self.attachListeners(self.path, self); + } + else { + _attr(self.path, a); + } + + _applyStyles(self.svg, self.path, style, d); + }; + }; + + /* + * SVG Bezier Connector + */ + jsPlumb.Connectors.svg.Bezier = function(params) { + jsPlumb.Connectors.Bezier.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]; }; + }; + + /* + * SVG straight line Connector + */ + jsPlumb.Connectors.svg.Straight = function(params) { + jsPlumb.Connectors.Straight.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; }; + }; + + jsPlumb.Connectors.svg.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "M " + dimensions[4] + "," + dimensions[5]; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)]; + } + // finally draw a line to the end + p = p + " " + dimensions[6] + "," + dimensions[7]; + return p; + }; + }; + + /* + * Base class for SVG endpoints. + */ + var SvgEndpoint = function(params) { + var self = this; + SvgComponent.apply(this, [ params["_jsPlumb"].endpointClass, arguments, "all" ]); + this._paint = function(d, style) { + var s = jsPlumb.extend({}, style); + if (s.outlineColor) { + s.strokeWidth = s.outlineWidth; + s.strokeStyle = _convertStyle(s.outlineColor, true); + } + + if (self.node == null) { + self.node = self.makeNode(d, s); + self.svg.appendChild(self.node); + self.attachListeners(self.node, self); + } + _applyStyles(self.svg, self.node, s, d); + _pos(self.node, d); + }; + }; + + /* + * SVG Dot Endpoint + */ + jsPlumb.Endpoints.svg.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("circle", { + "cx" : d[2] / 2, + "cy" : d[3] / 2, + "r" : d[2] / 2 + }); + }; + }; + + /* + * SVG Rectangle Endpoint + */ + jsPlumb.Endpoints.svg.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("rect", { + "width":d[2], + "height":d[3] + }); + }; + }; + + /* + * SVG Image Endpoint is the default image endpoint. + */ + jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image; + /* + * Blank endpoint in svg renderer is the default Blank endpoint. + */ + jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank; + /* + * Label endpoint in svg renderer is the default Label endpoint. + */ + jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label; + + + var AbstractSvgArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + var self = this, path =null; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + } + + _attr(path, { + "d" : makePath(d), + stroke : strokeStyle ? strokeStyle : null, + fill : fillStyle ? fillStyle : null + }); + }; + var makePath = function(d) { + return "M" + d.hxy.x + "," + d.hxy.y + + " L" + d.tail[0].x + "," + d.tail[0].y + + " L" + d.cxy.x + "," + d.cxy.y + + " L" + d.tail[1].x + "," + d.tail[1].y + + " L" + d.hxy.x + "," + d.hxy.y; + }; + }; + + jsPlumb.Overlays.svg.Arrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.svg.PlainArrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.svg.Diamond = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();;(function() { + +// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS ******************************************************************* + + // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. + var _connectionBeingDragged = null, + _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }, + _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }, + _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }, + _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, + _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }, + _setOffset = function(el, o) { jsPlumb.CurrentLibrary.setOffset(el, o); }; + + /* + * Class:CanvasMouseAdapter + * Provides support for mouse events on canvases. + */ + var CanvasMouseAdapter = function() { + var self = this; + self.overlayPlacements = []; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + jsPlumb.EventGenerator.apply(this, arguments); + /** + * returns whether or not the given event is ojver a painted area of the canvas. + */ + this._over = function(e) { + var o = _getOffset(_getElementObject(self.canvas)), + pageXY = _pageXY(e), + x = pageXY[0] - o.left, y = pageXY[1] - o.top; + if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + return false; + }; + + var _mouseover = false; + var _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false; + var _nullSafeHasClass = function(el, clazz) { + return el != null && _hasClass(el, clazz); + }; + this.mousemove = function(e) { + var pageXY = _pageXY(e), clientXY = _clientXY(e), + ee = document.elementFromPoint(clientXY[0], clientXY[1]), + eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); + var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); + if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.fire("mouseenter", self, e); + return true; + } + // TODO here there is a remote chance that the overlay the mouse moved onto + // is actually not an overlay for the current component. a more thorough check would + // be to ensure the overlay belonged to the current component. + else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { + _mouseover = false; + self.fire("mouseexit", self, e); + } + self.fire("mousemove", self, e); + }; + + this.click = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); + self.fire("mousedown", self, e); + } + }; + + this.mouseup = function(e) { + //if (self == _connectionBeingDragged) _connectionBeingDragged = null; + _mouseDown = false; + self.fire("mouseup", self, e); + }; + }; + + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + jsPlumb.appendElement(canvas, params.parent); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + params["_jsPlumb"].getId(canvas, params.uuid); + + return canvas; + }; + + /** + * Class:CanvasConnector + * Superclass for Canvas Connector renderers. + */ + var CanvasConnector = jsPlumb.CanvasConnector = function(params) { + + CanvasMouseAdapter.apply(this, arguments); + + var _paintOneStyle = function(dim, aStyle) { + self.ctx.save(); + jsPlumb.extend(self.ctx, aStyle); + if (aStyle.gradient) { + var g = self.createGradient(dim, self.ctx); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + self.ctx.strokeStyle = g; + } + self._paint(dim); + self.ctx.restore(); + }; + + var self = this, + clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent + }); + self.ctx = self.canvas.getContext("2d"); + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + self.paint = function(dim, style) { + if (style != null) { + + jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); + + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + _paintOneStyle(dim, outlineStyle); + } + _paintOneStyle(dim, style); + } + }; + }; + + /** + * Class:CanvasEndpoint + * Superclass for Canvas Endpoint renderers. + */ + var CanvasEndpoint = function(params) { + var self = this; + CanvasMouseAdapter.apply(this, arguments); + var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent + }); + self.ctx = self.canvas.getContext("2d"); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + // _paintOneStyle(d, outlineStyle); + } + + self._paint.apply(this, arguments); + }; + }; + + jsPlumb.Endpoints.canvas.Dot = function(params) { + var self = this; + jsPlumb.Endpoints.Dot.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }; + var calculateAdjustments = function(gradient) { + var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius; + gradient.offset && (offsetAdjustment = parseValue(gradient.offset)); + gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius)); + return [offsetAdjustment, innerRadius]; + }; + this._paint = function(d, style, anchor) { + if (style != null) { + var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(); + jsPlumb.extend(ctx, style); + if (style.gradient) { + var adjustments = calculateAdjustments(style.gradient), + yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0], + xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0], + g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + ctx.beginPath(); + ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + } + }; + }; + + jsPlumb.Endpoints.canvas.Rectangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) { + + var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(); + jsPlumb.extend(ctx, style); + + /* canvas gradient */ + if (style.gradient) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, d[2], d[3]); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + jsPlumb.Endpoints.canvas.Triangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Triangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) + { + var width = d[2], height = d[3], x = d[0], y = d[1]; + + var ctx = self.canvas.getContext('2d'); + var offsetX = 0, offsetY = 0, angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = style.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + /* + * Canvas Image Endpoint: uses the default version, which creates an tag. + */ + jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image; + + /* + * Blank endpoint in all renderers is just the default Blank endpoint. + */ + jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank; + + /* + * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Bezier = function() { + var self = this; + jsPlumb.Connectors.Bezier.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO i doubt this handles the case that source and target are swapped. + this.createGradient = function(dim, ctx, swap) { + return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + /* + * Canvas straight line Connector. Draws a straight line onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Straight = function() { + var self = this; + jsPlumb.Connectors.Straight.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO this does not handle the case that src and target are swapped. + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + jsPlumb.Connectors.canvas.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + +// ********************************* END OF CANVAS RENDERERS ******************************************************************* + + jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label; + + /** + * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. + */ + var CanvasOverlay = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }; + + var AbstractCanvasArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + CanvasOverlay.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + var ctx = connector.ctx; + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(d.hxy.x, d.hxy.y); + ctx.lineTo(d.tail[0].x, d.tail[0].y); + ctx.lineTo(d.cxy.x, d.cxy.y); + ctx.lineTo(d.tail[1].x, d.tail[1].y); + ctx.lineTo(d.hxy.x, d.hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + }; + + jsPlumb.Overlays.canvas.Arrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.canvas.PlainArrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.canvas.Diamond = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/* + * jsPlumb + * + * jquery.jsPlumb 1.3.0-RC1 + * + * jQuery specific functionality for jsPlumb. + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under MIT, GPL2 and Beer licenses. + * + */ +/* + * the library specific functions, such as find offset, get id, get attribute, extend etc. + * the full list is: + * + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getPageXY gets the page event's xy location. + * getParent gets the parent of some element. + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * hasClass returns whether or not the given element has the given class. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + */ +(function($) { + + //var getBoundingClientRectSupported = "getBoundingClientRect" in document.documentElement; + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * appends the given child to the given parent. + */ + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).append(child); + }, + + /** + * event binding wrapper. it just so happens that jQuery uses 'bind' also. yui3, for example, + * uses 'on'. + */ + bind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.bind(event, callback); + }, + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step', + 'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete' + }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + getDocumentElement : function() { return document; }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + }, + + getDragScope : function(el) { + return el.draggable("option", "scope"); + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + * + */ + getElementObject : function(el) { + return typeof(el)=='string' ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).parent(); + }, + + getScrollLeft : function(el) { + return el.scrollLeft(); + }, + + getScrollTop : function(el) { + return el.scrollTop(); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + + // this code is a workaround for the case that the element being dragged has a margin set on it. jquery UI passes + // in the wrong offset if the element has a margin (it doesn't take the margin into account). the getBoundingClientRect + // method, which is in pretty much all browsers now, reports the right numbers. but it introduces a noticeable lag, which + // i don't like. + + /*if ( getBoundingClientRectSupported ) { + var r = eventArgs[1].helper[0].getBoundingClientRect(); + return { left : r.left, top: r.top }; + } else {*/ + var ui = eventArgs[1], _offset = ui.offset; + return _offset || ui.absolutePosition; + //} + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options) { + // remove helper directive if present. + options.helper = null; + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.draggable(options); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.droppable(options); + }, + + isAlreadyDraggable : function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return el.hasClass("ui-draggable"); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).remove(); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * sets the drag scope. probably time for a setDragOption method (roll this and the one above together) + * @param el + * @param scope + */ + setDragScope : function(el, scope) { + el.draggable("option", "scope", scope); + }, + + setOffset : function(el, o) { + jsPlumb.CurrentLibrary.getElementObject(el).offset(o); + } + }; + + $(document).ready(jsPlumb.init); + +})(jQuery); +(function(){if(typeof Math.sgn=="undefined")Math.sgn=function(a){return a==0?0:a>0?1:-1};var p={subtract:function(a,b){return{x:a.x-b.x,y:a.y-b.y}},dotProduct:function(a,b){return a.x*b.x+a.y*b.y},square:function(a){return Math.sqrt(a.x*a.x+a.y*a.y)},scale:function(a,b){return{x:a.x*b,y:a.y*b}}},y=Math.pow(2,-65),u=function(a,b){for(var g=[],d=b.length-1,h=2*d-1,f=[],c=[],l=[],k=[],i=[[1,0.6,0.3,0.1],[0.4,0.6,0.6,0.4],[0.1,0.3,0.6,1]],e=0;e<=d;e++)f[e]=p.subtract(b[e],a);for(e=0;e<=d-1;e++){c[e]= +p.subtract(b[e+1],b[e]);c[e]=p.scale(c[e],3)}for(e=0;e<=d-1;e++)for(var m=0;m<=d;m++){l[e]||(l[e]=[]);l[e][m]=p.dotProduct(c[e],f[m])}for(e=0;e<=h;e++){k[e]||(k[e]=[]);k[e].y=0;k[e].x=parseFloat(e)/h}h=d-1;for(f=0;f<=d+h;f++){c=Math.min(f,d);for(e=Math.max(0,f-h);e<=c;e++){j=f-e;k[e+j].y+=l[j][e]*i[j][e]}}d=b.length-1;k=s(k,2*d-1,g,0);h=p.subtract(a,b[0]);l=p.square(h);for(e=i=0;e=64){g[0]=(a[0].x+a[b].x)/2;return 1}var n,o,q;k=a[0].y-a[b].y;i=a[b].x-a[0].x;e=a[0].x*a[b].y-a[b].x*a[0].y;m=max_distance_below=0;for(o=1;om)m=q;else if(q0?1:-1,c=null;h endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + endpointsByElement = {}, + endpointsByUUID = {}, + offsets = {}, + offsetTimestamps = {}, + floatingConnections = {}, + draggableStates = {}, + _mouseEventsEnabled = this.Defaults.MouseEventsEnabled, + _draggableByDefault = true, + canvasList = [], + sizes = [], + listeners = {}, // a map: keys are event types, values are lists of listeners. + DEFAULT_SCOPE = this.Defaults.Scope, + renderMode = null, // will be set in init() + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }, + + /** + * appends an element to the given parent, or the document body if no + * parent given. + */ + _appendElement = function(el, parent) { + if (!parent) + document.body.appendChild(el); + else + jsPlumb.CurrentLibrary.appendElement(el, parent); + }, + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + _timestamp = function() { return "" + (new Date()).getTime(); }, + + /** + * YUI, for some reason, put the result of a Y.all call into an object that contains + * a '_nodes' array, instead of handing back an array-like object like the other + * libraries do. + */ + _convertYUICollection = function(c) { + return c._nodes ? c._nodes : c; + }, + + /** + * Draws an endpoint and its connections. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + _draw = function(element, ui, timestamp) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (!timestamp) timestamp = _timestamp(); + if (endpoints) { + _updateOffset( { elId : id, offset : ui, recalc : false, timestamp : timestamp }); // timestamp is checked against last update cache; it is + // valid for one paint cycle. + var myOffset = offsets[id], myWH = sizes[id]; + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + var l = endpoints[i].connections; + for ( var j = 0; j < l.length; j++) { + l[j].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); // ...paint each connection. + // then, check for dynamic endpoint; need to repaint it. + var oIdx = l[j].endpoints[0] == endpoints[i] ? 1 : 0; + if (l[j].endpoints[oIdx].anchor.isDynamic && !l[j].endpoints[oIdx].isFloating()) { + var oId = oIdx == 0 ? l[j].sourceId : l[j].targetId, + oOffset = offsets[oId], oWH = sizes[oId], + // TODO i still want to make this faster. + anchorLoc = l[j].endpoints[oIdx].anchor.compute( { + xy : [ oOffset.left, oOffset.top ], + wh : oWH, + element : l[j].endpoints[oIdx], + txy : [ myOffset.left, myOffset.top ], + twh : myWH, + tElement : endpoints[i] + }); + l[j].endpoints[oIdx].paint({ anchorLoc : anchorLoc }); + } + } + } + } + }, + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }, + + /** + * gets an Endpoint by uuid. + */ + _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }, + + /** + * inits a draggable if it's not already initialised. + */ + _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element) && !jsPlumb.CurrentLibrary.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + } + } + }, + + _newConnection = function(params) { + var connectionFunc = jsPlumb.Defaults.ConnectionType || Connection, + endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint, + parent = jsPlumb.CurrentLibrary.getParent; + + if (params.sourceEndpoint) + params["parent"] = params.sourceEndpoint.parent; + else if (params.source.constructor == endpointFunc) + params["parent"] = params.source.parent; + else params["parent"] = parent(params.source); + + params["_jsPlumb"] = _currentInstance; + var con = new connectionFunc(params); + _eventFireProxy("click", "click", con); + _eventFireProxy("dblclick", "dblclick", con); + return con; + }, + + _eventFireProxy = function(event, proxyEvent, obj) { + obj.bind(event, function(e) { + _currentInstance.fire(proxyEvent, obj, e); + }); + }, + + _newEndpoint = function(params) { + var endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint; + params["parent"] = jsPlumb.CurrentLibrary.getParent(params.source); + params["_jsPlumb"] = _currentInstance, + ep = new endpointFunc(params); + _eventFireProxy("click", "endpointClick", ep); + _eventFireProxy("dblclick", "endpointDblClick", ep); + return ep; + }, + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }, + /** + * perform an operation on all elements. + */ + _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }, + + /** + * helper to remove an element from the DOM. + */ + _removeElement = function(element, parent) { + if (element != null && element.parentNode != null) { + element.parentNode.removeChild(element); + } + }, + /** + * helper to remove a list of elements from the DOM. + */ + _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }, + /** + * helper method to remove an item from a list. + */ + _removeFromList = function(map, key, value) { + if (key != null) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete (l[i]); + l.splice(i, 1); + return true; + } + } + } + return false; + }, + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }, + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + _setVisible = function(el, state) { + _operation(_getAttribute(el, "id"), function(jpc) { + jpc.canvas.style.display = state; + }); + }, + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }, + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + _toggleVisible = function(elId) { + _operation(elId, function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }, + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return offsets[elId]; + } + if (recalc || offset == null) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + } + return offsets[elId]; + }, + +/** + * gets an id for the given element, creating and setting one if + * necessary. + */ + _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else + id = "jsPlumb_" + _timestamp(); + _setAttribute(ele, "id", id); + } + return id; + }, + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'jsPlumb function failed : ' + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'wrapped function failed : ' + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = "_jsPlumb_connector"; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = "_jsPlumb_endpoint"; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = "_jsPlumb_overlay"; + + this.Anchors = {}; + + this.Connectors = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Endpoints = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Overlays = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + +// ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***************************************** + /* + * Function: bind + * Bind to an event on jsPlumb. + * + * Parameters: + * event - the event to bind. Available events on jsPlumb are: + * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback. + * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback. + * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback. + * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback. + * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback. + * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback. + * + * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event. + */ + + /* + * Function: clearListeners + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + +// *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *********************************************************** + + /* + Function: addEndpoint + + Adds an to a given element or elements. + + Parameters: + + el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + params - Object containing Endpoint constructor arguments. For more information, see . + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some + shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in + this object are anything that 'params' can contain. See . + + Returns: + The newly created , if el referred to a single element. Otherwise, an array of newly created s. + + See Also: + + */ + this.addEndpoint = function(el, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + + // YUI wrapper + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + var _el = _getElementObject(inputs[i]), id = _getId(_el); + p.source = _el; + _updateOffset({ elId : id }); + var e = _newEndpoint(p); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + results.push(e); + } + + return results.length == 1 ? results[0] : results; + }; + + /* + Function: addEndpoints + Adds a list of s to a given element or elements. + + Parameters: + target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See 's constructor documentation. + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created s, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(el, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams); + if (e.constructor == Array) + Array.prototype.push.apply(results, e); + else results.push(e); + } + return results; + }; + + /* + Function: animate + This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating + the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // onComplete repaints, just to make sure everything looks good at the end of the animation. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /* + Function: connect + Establishes a between two elements (or s, which are themselves registered to elements). + + Parameters: + params - Object containing constructor arguments for the Connection. See 's constructor documentation. + referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of + Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection. + + Returns: + The newly created . + */ + this.connect = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + // dynamic anchors. backwards compatibility here: from 1.2.6 onwards you don't need to specify "dynamicAnchors". the fact that some anchor consists + // of multiple definitions is enough to tell jsPlumb you want it to be dynamic. + if (_p.dynamicAnchors) { + // these can either be an array of anchor coords, which we will use for both source and target, or an object with {source:[anchors], target:[anchors]}, in which + // case we will use a different set for each element. + var a = _p.dynamicAnchors.constructor == Array; + var sa = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.source)); + var ta = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.target)); + _p.anchors = [sa,ta]; + } + + var jpc = _newConnection(_p); + // add to list of connections (by scope). + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + _currentInstance.fire("jsPlumbConnection", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + // force a paint + _draw(jpc.source); + + return jpc; + }; + + /* + Function: deleteEndpoint + Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too) + + Parameters: + object - either an object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElement(endpoint.canvas, endpoint.parent); + // remove from endpointsbyElement + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + delete endpoint; + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every , and their associated s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc) { + _currentInstance.fire("jsPlumbConnectionDetached", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + }; + + /* + Function: detach + Detaches and then removes a . Takes either (source, target) (the old way, maintained for backwards compatibility), or a params + object with various possible values. + + Parameters: + source - id or element object of the first element in the Connection. + target - id or element object of the second element in the Connection. + params - a JS object containing the same parameters as you pass to jsPlumb.connect. If this is present then neither source nor + target should be present; it should be the only argument to the method. See the docs for 's constructor for information +about the parameters allowed in the params object. + Returns: + true if successful, false if not. + */ + this.detach = function(source, target) { + if (arguments.length == 2) { + var s = _getElementObject(source), sId = _getId(s); + var t = _getElementObject(target), tId = _getId(t); + _operation(sId, function(jpc) { + if ((jpc.sourceId == sId && jpc.targetId == tId) || (jpc.targetId == sId && jpc.sourceId == tId)) { + _removeElements(jpc.connector.getDisplayElements(), jpc.parent); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + // this is the new version of the method, taking a JS object like + // the connect method does. + else if (arguments.length == 1) { + // TODO investigate whether or not this code still works when a user has supplied their own subclass of Connection. i suspect it may not. + if (arguments[0].constructor == Connection) { + arguments[0].endpoints[0].detachFrom(arguments[0].endpoints[1]); + } + else if (arguments[0].connection) { + arguments[0].connection.endpoints[0].detachFrom(arguments[0].connection.endpoints[1]); + } + else { + var _p = jsPlumb.extend( {}, source); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1])); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source); + var targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElements(jpc.connector.getDisplayElements(), jpc.parent); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + } + } + }; + + /* + Function: detachAll + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + + Returns: + void + */ + this.detachAllConnections = function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + }; + + /** + * @deprecated Use detachAllConnections instead. this will be removed in jsPlumb 1.3. + */ + this.detachAll = this.detachAllConnections; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + /** + * @deprecated use detachEveryConnection instead. this will be removed in jsPlumb 1.3. + */ + this.detachEverything = this.detachEveryConnection; + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of you library's draggable method so that jsPlumb can setup the appropriate callbacks. Your underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method, + * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the + * default scope). If multiple scopes are passed in, the return value will be a map of { scope -> [ connection... ] }. + * + * Parameters + * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list + * of connections that are in the given scope. + * options - if the argument is a JS object, you can specify a finer-grained filter: + * + * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes. + * - *source* either a string representing an element id, or a selector. constrains the result to connections having this source. + * - *target* either a string representing an element id, or a selector. constrains the result to connections having this target. + * + */ + this.getConnections = function(options) { + if (!options) { + options = {}; + } else if (options.constructor == String) { + options = { "scope": options }; + } + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') + r.push(input); + else + r = input; + } + return r; + }; + var scope = options.scope || jsPlumb.getDefaultScope(), + scopes = prepareList(scope), + sources = prepareList(options.source), + targets = prepareList(options.target), + filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }, + results = scopes.length > 1 ? {} : [], + _addOne = function(scope, obj) { + if (scopes.length > 1) { + var ss = results[scope]; + if (ss == null) { + ss = []; results[scope] = ss; + } + ss.push(obj); + } else results.push(obj); + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + _addOne(i, c); + } + } + } + return results; + }; + + /* + * Function: getAllConnections + * Gets all connections, as a map of { scope -> [ connection... ] }. + */ + this.getAllConnections = function() { + return connectionsByScope; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /** + * Function:getEndpoints + * Gets the list of Endpoints for a given selector, or element id. + * @param el + * @return + */ + this.getEndpoints = function(el) { + return endpointsByElement[_getId(el)]; + }; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + + this.appendElement = _appendElement; + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + + Returns: + void + */ + this.hide = function(el) { + _setVisible(el, "none"); + }; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + if (!initialized) { + _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run. + + var _bind = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && _mouseEventsEnabled && renderMode == jsPlumb.CANVAS) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + var t = c[i].connector[event](e); + if (t) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i].endpoint[event](e)) return; + } + } + } + }); + }; + _bind("click"); + _bind("dblclick"); + _bind("mousemove"); + _bind("mousedown"); + _bind("mouseup"); + + initialized = true; + _currentInstance.fire("ready"); + } + }; + + this.jsPlumbUIComponent = jsPlumbUIComponent; + this.EventGenerator = EventGenerator; + + /* + * Creates an anchor with the given params. + * + * You do not need to use this method. It is exposed because of the way jsPlumb is + * split into three scripts; this will change in the future. + * + * x - the x location of the anchor as a fraction of the + * total width. y - the y location of the anchor as a fraction of the + * total height. xOrientation - value indicating the general direction a + * connection from the anchor should go in, in the x direction. + * yOrientation - value indicating the general direction a connection + * from the anchor should go in, in the y direction. xOffset - a fixed + * offset that should be applied in the x direction that should be + * applied after the x position has been figured out. optional. defaults + * to 0. yOffset - a fixed offset that should be applied in the y + * direction that should be applied after the y position has been + * figured out. optional. defaults to 0. + * -- OR -- + * + * params - {x:..., y:..., xOrientation etc } + * -- OR FROM 1.2.4 --- + * + * name - the name of some Anchor in the _currentInstance.Anchors array. + * -- OR FROM 1.2.4 --- + * + * coords - a list of coords for the anchor, like you would pass to + * jsPlumb.makeAnchor (eg [0.5,0.5,0,-1] - an anchor in the center of + * some element, oriented towards the top of the screen) + * -- OR FROM 1.2.4 --- + * + * anchor - an existing anchor. just gets passed back. it's handy + * internally to have this functionality. + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed + // in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards + // compatibility if we are given only one value we assume it's a + // call in the old form. + if (arguments.length == 0) return null; + var params = {}; + if (arguments.length == 1) { + var specimen = arguments[0]; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; + // is it the name of an anchor type? + else if (typeof specimen == "string") return _currentInstance.Anchors[arguments[0]](); + // is it an array of coordinates? + else if (specimen.constructor == Array) { + if (specimen[0].constructor == Array || specimen[0].constructor == String) + return new DynamicAnchor(specimen); + else + return jsPlumb.makeAnchor.apply(this, specimen); + } + // last we try the backwards compatibility stuff. + else if (typeof arguments[0] == "object") jsPlumb.extend(params, x); + } else { + params = { x : x, y : y }; + if (arguments.length >= 4) params.orientation = [ arguments[2], arguments[3] ]; + if (arguments.length == 6) params.offsets = [ arguments[4], arguments[5] ]; + } + var a = new Anchor(params); + a.clone = function() { + return new Anchor(params); + }; + return a; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types) { + var r = []; + for ( var i = 0; i < types.length; i++) + if (typeof types[i] == "string") + r.push(_currentInstance.Anchors[types[i]]()); + else if (types[i].constructor == Array) + r.push(jsPlumb.makeAnchor(types[i])); + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /** + * Function: makeTarget + * Makes some DOM element a Connection target, allowing you to drag connections to it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a target. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * scope optional. scope for the drop zone. + * dropOptions optional. same stuff as you would pass to dropOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to false. whether or not to delete + * any Endpoints created by a connection to this target if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + this.makeTarget = function(el, params, referenceParams) { + + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + var jpcl = jsPlumb.CurrentLibrary, + scope = p.scope || _currentInstance.Defaults.Scope, + deleteEndpointsOnDetach = p.deleteEndpointsOnDetach || false, + _doOne = function(_el) { + var dropOptions = jsPlumb.extend({}, p.dropOptions || {}); + var _drop = function() { + var draggable = _getElementObject(jpcl.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + // restore the original scope if necessary (issue 57) + scope = _getAttribute(draggable, "originalScope"); + + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + // get the connection, to then get its endpoint + var jpc = floatingConnections[id], + source = jpc.endpoints[0], + _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : null, + // make a new Endpoint + newEndpoint = jsPlumb.addEndpoint(_el, _endpoint); + + var c = jsPlumb.connect({ + source:source, + target:newEndpoint, + scope:scope + }); + if (deleteEndpointsOnDetach) + c.endpointToDeleteOnDetach = newEndpoint; + }; + + var dropEvent = jpcl.dragEvents['drop']; + dropOptions["scope"] = dropOptions["scope"] || scope; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop); + + jpcl.initDroppable(_el, dropOptions); + }; + + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements drop targets. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeTargets = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeTarget(els[i], params, referenceParams); + } + }; + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + for ( var i in ebe) + _currentInstance.deleteEndpoint(ebe[i]); + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + this.deleteEveryEndpoint(); + this.clearListeners(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + + Parameters: + value - whether or not to automatically repaint when the window is resized. + + Returns: void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setDraggableByDefault + * Sets whether or not elements are draggable by default. Default for this is true. + * + * Parameters: + * draggable - value to set + * + * Returns: + * void + */ + this.setDraggableByDefault = function(draggable) { + _draggableByDefault = draggable; + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction + * Sets the function to fire when the window size has changed and a repaint was fired. + * + * Parameters: + * f - Function to execute. + * + * Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: setMouseEventsEnabled + * Sets whether or not mouse events are enabled. Default is true. + * + * Parameters: + * enabled - whether or not mouse events should be enabled. + * + * Returns: + * void + */ + this.setMouseEventsEnabled = function(enabled) { + _mouseEventsEnabled = enabled; + }; + + /* + * Constant for use with the setRenderMode method + */ + this.CANVAS = "canvas"; + + /* + * Constant for use with the setRenderMode method + */ + this.SVG = "svg"; + + this.VML = "vml"; + + /* + * Function: setRenderMode + * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that + * what you asked for is not supported (and that VML is). If you asked for VML but the browser does + * not support it, jsPlumb uses SVG. + * + * Returns: + * the render mode that jsPlumb set, which of course may be different from that requested. + */ + this.setRenderMode = function(mode) { + if (mode) + mode = mode.toLowerCase(); + else + return; + if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); + // now test we actually have the capability to do this. + if (mode === jsPlumb.CANVAS && canvasAvailable) + renderMode = jsPlumb.CANVAS; + else if (mode === jsPlumb.SVG && svgAvailable) + renderMode = jsPlumb.SVG; + else if (vmlAvailable) + renderMode = jsPlumb.VML; + + return renderMode; + }; + + this.getRenderMode = function() { return renderMode; }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * + * Returns: + * void + */ + this.show = function(el) { + _setVisible(el, "block"); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId, + makeAnchor:self.makeAnchor, + makeDynamicAnchor:self.makeDynamicAnchor + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload + * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element. + * + * Returns: + * void + */ + this.unload = function() { + delete endpointsByElement; + delete endpointsByUUID; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + delete canvasList; + }; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + if (timestamp && timestamp === self.timestamp) { + return lastReturnValue; + } + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + if (element.canvas) { + var po = element.canvas.offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(element.canvas.offsetParent); + lastReturnValue[0] = lastReturnValue[0] - po.left; + lastReturnValue[1] = lastReturnValue[1] - po.top; + } + + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + * + * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(_getElementObject(refCanvas)); + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + var orientation = null; + var _lastResult = null; + + this.compute = function(params) { + var xy = params.xy, element = params.element; + var result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + + if (element.canvas) { + var po = element.canvas.offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(element.canvas.offsetParent); + result[0] = result[0] - po.left; + result[1] = result[1] - po.top; + } + + _lastResult = result; + return result; + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { orientation = anchor.getOrientation(); }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchors is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = []; + var _convert = function(anchor) { return anchor.constructor == Anchor ? anchor: jsPlumb.makeAnchor(anchor); }; + for (var i = 0; i < anchors.length; i++) _anchors[i] = _convert(anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null; + var _curIndex = _anchors.length > 0 ? 0 : -1; + this.locked = false; + var self = this; + + // helper method to calculate the distance between the centers of the two elements. + var _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }; + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + var _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + + var pos = _curAnchor.compute(params); + return pos; + }; + + this.getCurrentLocation = function() { + var cl = _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + return cl; + }; + + this.getOrientation = function() { return _curAnchor != null ? _curAnchor.getOrientation() : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * overlays - Optional array of Overlay definitions to appear on this Connection. + */ + var Connection = function(params) { + + jsPlumbUIComponent.apply(this, arguments); + // ************** get the source and target and register the connection. ******************* + var self = this; + var visible = true; + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none"; + }; + var id = new String('_jsplumb_c_' + (new Date()).getTime()); + this.getId = function() { return id; }; + this.parent = params.parent; + /** + Property: source + The source element for this Connection. + */ + this.source = _getElementObject(params.source); + /** + Property:target + The target element for this Connection. + */ + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. + if (params.sourceEndpoint) this.source = params.sourceEndpoint.getElement(); + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + /* + * Property: sourceId + * Id of the source element in the connection. + */ + this.sourceId = _getAttribute(this.source, "id"); + /* + * Property: targetId + * Id of the target element in the connection. + */ + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + + /** + * implementation of abstract method in EventGenerator + * @return list of attached elements. in our case, a list of Endpoints. + */ + this.getAttachedElements = function() { + return self.endpoints; + }; + + /** + * implementation of abstract method in EventGenerator + */ + var srcWhenMouseDown = null, targetWhenMouseDown = null; + this.savePosition = function() { + srcWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.source)); + targetWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.target)); + }; + + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams) { + if (anchorParams) + return jsPlumb.makeAnchor(anchorParams); + }; + var prepareEndpoint = function(existing, index, params, element, connectorPaintStyle, connectorHoverPaintStyle) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] + || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint; + + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified. + if (es.fillStyle == null && connectorPaintStyle != null) + es.fillStyle = connectorPaintStyle.strokeStyle; + + // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does: + //* + if (es.outlineColor == null && connectorPaintStyle != null) + es.outlineColor = connectorPaintStyle.outlineColor; + if (es.outlineWidth == null && connectorPaintStyle != null) + es.outlineWidth = connectorPaintStyle.outlineWidth; + //*/ + + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure? + if (connectorHoverPaintStyle != null) { + if (ehs == null) ehs = {}; + if (ehs.fillStyle == null) { + ehs.fillStyle = connectorHoverPaintStyle.strokeStyle; + } + } + var a = params.anchors ? params.anchors[index] : _makeAnchor(_currentInstance.Defaults.Anchors[index]) || _makeAnchor(jsPlumb.Defaults.Anchors[index]) || _makeAnchor(_currentInstance.Defaults.Anchor) || _makeAnchor(jsPlumb.Defaults.Anchor); + var u = params.uuids ? params.uuids[index] : null; + var e = _newEndpoint( { paintStyle : es, hoverPaintStyle:ehs, endpoint : ep, connections : [ self ], uuid : u, anchor : a, source : element }); + self.endpoints[index] = e; + + if (params.drawEndpoints === false) e.setVisible(false, true, true); + + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source, params.paintStyle, params.hoverPaintStyle); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target, params.paintStyle, params.hoverPaintStyle); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + /* + * Function: setConnector + * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method - the same + * thing that you would set as the 'connector' property on a jsPlumb.connect call. + * + * Parameters: + * connector - Connector definition + */ + this.setConnector = function(connector, doNotRepaint) { + if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent); + var connectorArgs = { _jsPlumb:self._jsPlumb, parent:params.parent, cssClass:params.cssClass }; + if (connector.constructor == String) + this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand. + else if (connector.constructor == Array) + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs)); + this.canvas = this.connector.canvas; + var _mouseDown = false, _mouseWasDown = false, _mouseDownAt = null; + // add mouse events + this.connector.bind("click", function(con, e) { + _mouseWasDown = false; + self.fire("click", self, e); + }); + this.connector.bind("dblclick", function(con, e) { _mouseWasDown = false;self.fire("dblclick", self, e); }); + this.connector.bind("mouseenter", function(con, e) { + if (!self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(true); + } + self.fire("mouseenter", self, e); + } + }); + this.connector.bind("mouseexit", function(con, e) { + if (self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(false); + } + self.fire("mouseexit", self, e); + } + }); + this.connector.bind("mousedown", function(con, e) { + _mouseDown = true; + _mouseDownAt = jsPlumb.CurrentLibrary.getPageXY(e); + self.savePosition(); + }); + this.connector.bind("mouseup", function(con, e) { + _mouseDown = false; + if (self.connector == _connectionBeingDragged) _connectionBeingDragged = null; + }); + + if (!doNotRepaint) self.repaint(); + }; + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc) + */ + + self.setConnector(this.endpoints[0].connector || + this.endpoints[1].connector || + params.connector || + _currentInstance.Defaults.Connector || + jsPlumb.Defaults.Connector, true); + + this.setPaintStyle(this.endpoints[0].connectorStyle || + this.endpoints[1].connectorStyle || + params.paintStyle || + _currentInstance.Defaults.PaintStyle || + jsPlumb.Defaults.PaintStyle, true); + + this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || + this.endpoints[1].connectorHoverStyle || + params.hoverPaintStyle || + _currentInstance.Defaults.HoverPaintStyle || + jsPlumb.Defaults.HoverPaintStyle, true); + + this.paintStyleInUse = this.paintStyle; + + /* + * Property: overlays + * List of Overlays for this Connection. + */ + this.overlays = []; + var _overlays = params.overlays || _currentInstance.Defaults.Overlays; + if (_overlays) { + for (var i = 0; i < _overlays.length; i++) { + var o = _overlays[i], _newOverlay = null, _overlayEvents = null; + if (o.constructor == Array) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0]; + var p = jsPlumb.CurrentLibrary.extend({connection:self, _jsPlumb:_currentInstance}, o[1]); // make a copy of the object so as not to mess up anyone else's reference... + if (o.length == 3) jsPlumb.CurrentLibrary.extend(p, o[2]); + _newOverlay = new jsPlumb.Overlays[renderMode][type](p); + if (p.events) { + for (var evt in p.events) { + _newOverlay.bind(evt, p.events[evt]); + } + } + } else if (o.constructor == String) { + _newOverlay = new jsPlumb.Overlays[renderMode][o]({connection:self, _jsPlumb:_currentInstance}); + } else { + _newOverlay = o; + } + + + + this.overlays.push(_newOverlay); + } + } + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Connection. + * + * Parameters: + * event - the event to bind. Available events on a Connection are: + * - *click* : notification that a Connection was clicked. + * - *dblclick* : notification that a Connection was double clicked. + * - *mouseenter* : notification that the mouse is over a Connection. + * - *mouseexit* : notification that the mouse exited a Connection. + * + * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Connection's paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Connection will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Connection. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + this.addOverlay = function(overlay) { self.overlays.push(overlay); }; + + /** + * Function: removeAllOverlays + * Removes all overlays from the Connection, and then repaints. + */ + this.removeAllOverlays = function() { + self.overlays.splice(0, self.overlays.length); + self.repaint(); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + this.removeOverlay = function(overlayId) { + var idx = -1; + for (var i = 0; i < self.overlays.length; i++) { + if (overlayId === self.overlays[i].id) { + idx = i; + break; + } + } + if (idx != -1) self.overlays.splice(idx, 1); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + this.removeOverlays = function() { + for (var i = 0; i < arguments.length; i++) + self.removeOverlay(arguments[i]); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + this.labelStyle = params.labelStyle || _currentInstance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + if (this.label) { + this.overlays.push(new jsPlumb.Overlays[renderMode].Label( { + cssClass:params.cssClass, + labelStyle : this.labelStyle, + label : this.label, + connection:self, + _jsPlumb:_currentInstance + })); + } + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String or a Function that returns a String. + */ + this.setLabel = function(l) { + self.label = l; + _currentInstance.repaint(self.source); + }; + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var otherOffset = offsets[this.targetId]; + var otherWH = sizes[this.targetId]; + var anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1] + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0] + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc }); + + /* + * Paints the Connection. Not exposed for public usage. + * + * Parameters: + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp; + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = false; + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }); + _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + var sAnchorP = this.endpoints[sIdx].anchor.getCurrentLocation(); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.getCurrentLocation(); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + + /* paint overlays*/ + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + var s = o.computeMaxSize(self.connector); + if (s > maxSize) + maxSize = s; + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, self.paintStyleInUse.lineWidth, maxSize); + + self.connector.paint(dim, self.paintStyleInUse); + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim); + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function() { + this.paint({ elId : this.sourceId, recalc : true }); + }; + + _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide + // whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + + // just to make sure the UI gets initialised fully on all browsers. + self.repaint(); + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1 + * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't + * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you + * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or + * 'target' Endpoints for Connections. + * + * + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * connections - optional list of Connections to configure the Endpoint with. + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + params = params || {}; + var self = this; +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Endpoint. + * + * Parameters: + * event - the event to bind. Available events on an Endpoint are: + * - *click* : notification that a Endpoint was clicked. + * - *dblclick* : notification that a Endpoint was double clicked. + * - *mouseenter* : notification that the mouse is over a Endpoint. + * - *mouseexit* : notification that the mouse exited a Endpoint. + * + * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Endpoint's paint style and then repaints the Endpoint. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Endpoint will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Endpoint. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + var visible = true; + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + var id = new String('_jsplumb_e_' + (new Date()).getTime()); + this.getId = function() { return id; }; + if (params.dynamicAnchors) + self.anchor = new DynamicAnchor(jsPlumb.makeAnchors(params.dynamicAnchors)); + else + self.anchor = params.anchor ? jsPlumb.makeAnchor(params.anchor) : params.anchors ? jsPlumb.makeAnchor(params.anchors) : jsPlumb.makeAnchor("TopCenter"); + var _endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot", + endpointArgs = { _jsPlumb:self._jsPlumb, parent:params.parent }; + if (_endpoint.constructor == String) + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint](endpointArgs); + else if (_endpoint.constructor == Array) + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint[0]](jsPlumb.extend(_endpoint[1], endpointArgs )); + else _endpoint = _endpoint.clone(); + self.endpoint = _endpoint; + this.endpoint.bind("click", function(e) { self.fire("click", self, e); }); + this.endpoint.bind("dblclick", function(e) { self.fire("dblclick", self, e); }); + this.setPaintStyle(params.paintStyle || + params.style || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyle, true); + this.setHoverPaintStyle(params.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyle, true); + this.paintStyleInUse = this.paintStyle; + this.connectorStyle = params.connectorStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.parent = params.parent; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + var _element = params.source, + _uuid = params.uuid, + floatingEndpoint = null, + inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + this.element = _element; + var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + /* + * Property: canvas + * The Endpoint's Canvas. + */ + this.canvas = this.endpoint.canvas; + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + this.connections = params.connections || []; + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + var _reattach = params.reattach || false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + + // this avoids a circular loop + if (!ignoreTarget) { + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + t.detach(connection, true); + // check connection to see if we want to delete the other endpoint. + // if the user uses makeTarget to make some element a target for connections, + // it is possible that they will have set 'endpointToDeleteOnDetach': when + // you make a connection to an element that acts as a target (note: NOT an + // Endpoint; just some div as a target), Endpoints are created for that + // connection. so if you then delete that Connection, it is feasible you + // will want these auto-generated endpoints to be removed. + if (connection.endpointToDeleteOnDetach && connection.endpointToDeleteOnDetach.connections.length == 0) + jsPlumb.deleteEndpoint(connection.endpointToDeleteOnDetach); + } + _removeElements(connection.connector.getDisplayElements(), connection.parent); + _removeFromList(connectionsByScope, connection.scope, connection); + if(!ignoreTarget) fireDetachEvent(connection); + } + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + */ + this.detachAll = function() { + while (self.connections.length > 0) { + self.detach(self.connections[0]); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + */ + this.detachFrom = function(targetEndpoint) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + c[i].setHover(false); + self.detach(c[i]); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + return _newEndpoint( { anchor : self.anchor, source : _element, paintStyle : this.paintStyle, endpoint : _endpoint }); + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + return (self.connections.length < _maxConnections) ? null : self.connections[0]; + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + /* + * Function: setStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to + * setStyle and deprecate it if so. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + * + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + /* + * Function: paint + * Paints the Endpoint, recalculating offset and anchor positions if necessary. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + + params = params || {}; + var timestamp = params.timestamp; + if (!timestamp || self.timestamp !== timestamp) { + var ap = params.anchorPoint, canvas = params.canvas, connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + var xy = params.offset || offsets[_elementId]; + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (self.anchor.isDynamic) { + if (self.connections.length > 0) { + var c = self.connections[0]; + var oIdx = c.endpoints[0] == self ? 1 : 0; + var oId = oIdx == 0 ? c.sourceId : c.targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + } + ap = self.anchor.compute(anchorParams); + } + + var d = _endpoint.compute(ap, self.anchor.getOrientation(), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + _endpoint.paint(d, self.paintStyleInUse, self.anchor); + + self.timestamp = timestamp; + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + var start = function() { + jpc = self.connectorSelector(); + if (self.isFull() && !dragAllowedWhenFull) return false; + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + n.style.position = "absolute"; + var nE = _getElementObject(n); + _appendElement(n, self.parent); + // create and assign an id, and initialize the offset. + var id = _getId(nE); + + // set the offset of this div to be where 'inPlaceCopy' is, to start with. + var ipcoel = _getElementObject(inPlaceCopy.canvas), + ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel), + po = inPlaceCopy.canvas.offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(inPlaceCopy.canvas.offsetParent); + jsPlumb.CurrentLibrary.setOffset(n, {left:ipco.left - po.left, top:ipco.top-po.top}); + + _updateOffset( { elId : id }); + + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor( { reference : self.anchor, referenceCanvas : self.canvas }); + floatingEndpoint = _newEndpoint({ paintStyle : self.paintStyle, endpoint : _endpoint, anchor : floatingAnchor, source : nE }); + + if (jpc == null) { + self.anchor.locked = true; + // create a connection. one end is this endpoint, the + // other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : _getElementObject(_element), + target : _getElementObject(n), + anchors : [ self.anchor, floatingAnchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays + }); + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.connector.setHover(false); + } else { + existingJpc = true; + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.connector.setHover(false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas)); + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; // are we the source or the target? + + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas); + var dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // get a new, temporary scope, to use (issue 57) + var newScope = "scope_" + (new Date()).getTime(); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + // set the new, temporary scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(i, newScope); + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // TODO unregister on stop? or will floating endpoint's + // destruction be assured. + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be + // dragging the source at any time + // before this connection is either discarded or made into a + // permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var jpcl = jsPlumb.CurrentLibrary, + dragOptions = params.dragOptions || {}, + defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions), + startEvent = jpcl.dragEvents['start'], + stopEvent = jpcl.dragEvents['stop'], + dragEvent = jpcl.dragEvents['drag']; + + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], + function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + jsPlumb.CurrentLibrary.setOffset(n, _ui); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements( [ n, floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (_reattach) { + + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } else { + jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc); // the main endpoint will inform the floating endpoint + // to disconnect, and also post the detached event. + } + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElements(jpc.connector.getDisplayElements(), self.parent); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint(); + jpc.setHover(false); + jpc.repaint(); + jpc = null; + delete inPlaceCopy; + delete endpointsByElement[floatingEndpoint.elementId]; + floatingEndpoint = null; + delete floatingEndpoint; + + _currentInstance.currentlyDragging = false; + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas) { + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + var drop = function() { + var draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)); + var id = _getAttribute(draggable, "dragId"); + var elId = _getAttribute(draggable, "elId"); + + // restore the original scope if necessary (issue 57) + var scope = _getAttribute(draggable, "originalScope"); + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + var jpc = floatingConnections[id]; + + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // todo test that the target is not full. + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + if (!jpc.suspendedEndpoint) { + _addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + _currentInstance.fire("jsPlumbConnectionDetached", { + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }); + } + + jsPlumb.repaint(elId); + + _currentInstance.fire("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], + targetEndpoint : jpc.endpoints[1], + connection:jpc + }); + } + + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute( _getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + } + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute(_getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + } + }); + + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas)); + + return self; + }; + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + //if (_defaults) jsPlumb.extend(j.Defaults, _defaults); + return j; + }; + + var _curryAnchor = function(x,y,ox,oy) { + return function() { + return jsPlumb.makeAnchor(x,y,ox,oy); + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1); + + jsPlumb.Defaults.DynamicAnchors = function() { + return jsPlumb.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"]); + }; + jsPlumb.Anchors["AutoDefault"] = function() { return jsPlumb.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors()); }; + + +})(); diff --git a/archive/1.3.1/jsPlumb-1.3.1-tests.js b/archive/1.3.1/jsPlumb-1.3.1-tests.js new file mode 100644 index 000000000..ea3442c93 --- /dev/null +++ b/archive/1.3.1/jsPlumb-1.3.1-tests.js @@ -0,0 +1,1629 @@ + +var _getContextNode = function() { + return $("#container"); +}; + +var assertContextExists = function() { + ok(_getContextNode().length == 1, "context node exists"); +}; + +var assertContextSize = function(elementCount) { + //equals(_getContextNode().children().length - _divs.length, elementCount, 'context has ' + elementCount + ' children'); +}; + +var assertContextEmpty = function() { + equals(_getContextNode().children.length, 0, "context empty"); +}; + +var assertEndpointCount = function(elId, count) { + equals(jsPlumb.getTestHarness().endpointCount(elId), count, elId + " has " + count + (count > 1) ? "endpoints" : "endpoint"); +}; + +var assertConnectionCount = function(endpoint, count) { + equals(endpoint.connections.length, count, "endpoint has " + count + " connections"); +}; + +var assertConnectionByScopeCount = function(scope, count) { + equals(jsPlumb.getTestHarness().connectionCount(scope), count, 'Scope ' + scope + " has " + count + (count > 1) ? "connections" : "connection"); + +}; + +var _divs = []; +var _addDiv = function(id) { + var d1 = document.createElement("div"); + _getContextNode().append(d1); + d1 = jsPlumb.CurrentLibrary.getElementObject(d1); + jsPlumb.CurrentLibrary.setAttribute(d1, "id", id); + _divs.push(id); + return d1; +}; + +var _cleanup = function() { + + jsPlumb.reset(); + + for (var i in _divs) { + $("#" + _divs[i]).remove(); + } + _divs.splice(0, _divs.length - 1); +}; + +var testSuite = function(renderMode) { + + module("jsPlumb", {teardown: _cleanup}); + + // setup the container + var container = document.createElement("div"); + container.id = "container"; + document.body.appendChild(container); + + jsPlumb.setRenderMode(renderMode); + + test(renderMode + ': findIndex method', function() { + var array = [ 1,2,3, "test", "a string", { 'foo':'bar', 'baz':1 }, { 'ding':'dong' } ]; + equals(jsPlumb.getTestHarness().findIndex(array, 1), 0, "find works for integers"); + equals(jsPlumb.getTestHarness().findIndex(array, "test"), 3, "find works for strings"); + equals(jsPlumb.getTestHarness().findIndex(array, { 'foo':'bar', 'baz':1 }), 5, "find works for objects"); + equals(jsPlumb.getTestHarness().findIndex(array, { 'ding':'dong', 'baz':1 }), -1, "find works properly for objects (objects have different length but some same properties)"); + }); + + test(renderMode + ': jsPlumb setup', function() { + ok(jsPlumb, "loaded"); + }); + + test(renderMode + ': getId', function() { + var d10 = _addDiv('d10'); + equals(jsPlumb.getTestHarness().getId(d10), "d10"); + }); + + test(renderMode + ': create a simple endpoint', function() { + var d1 = _addDiv("d1"); + var e = jsPlumb.addEndpoint($("#d1"), {}); + ok(e, 'endpoint exists'); + assertEndpointCount("d1", 1); + }); + + test(renderMode + ': create and remove a simple endpoint', function() { + var d1 = _addDiv("d1"); + var ee = jsPlumb.addEndpoint(d1, {uuid:"78978597593"}); + ok(ee != null, "endpoint exists"); + var e = jsPlumb.getEndpoint("78978597593"); + ok(e != null, "the endpoint could be retrieved by UUID"); + assertEndpointCount("d1", 1); + assertContextSize(1); // one Endpoint canvas. + jsPlumb.removeEndpoint(d1, ee); + assertEndpointCount("d1", 0); + e = jsPlumb.getEndpoint("78978597593"); + equals(e, null, "the endpoint has been deleted"); + assertContextSize(0); // no Endpoint canvases. + }); + + test(renderMode + ': create two simple endpoints, registered using a selector', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + d1.addClass("window");d2.addClass("window"); + var endpoints = jsPlumb.addEndpoint($(".window"), {}); + equals(endpoints.length, 2, "endpoint added to both windows"); + assertEndpointCount("d1", 1); + assertEndpointCount("d2", 1); + }); + + test(renderMode + ': create two simple endpoints, registered using an array of element ids', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + d1.addClass("window");d2.addClass("window"); + var endpoints = jsPlumb.addEndpoint(["d1", "d2"], {}); + equals(endpoints.length, 2, "endpoint added to both windows"); + assertEndpointCount("d1", 1); + assertEndpointCount("d2", 1); + }); + + test(renderMode + ': draggable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1"); + var e = jsPlumb.addEndpoint(d1, {isSource:true}); + ok(e, 'endpoint exists'); + }); + + test(renderMode + ': droppable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1") + var e = jsPlumb.addEndpoint(d1, {isTarget:true}); + ok(e, 'endpoint exists'); + }); + + test(renderMode + ': defaultEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = jsPlumb.addEndpoint(d3, {isSource:true}); + ok(e3.anchor, 'endpoint 3 has an anchor'); + var e4 = jsPlumb.addEndpoint(d4, {isSource:true}); + assertEndpointCount("d3", 1); + assertEndpointCount("d4", 1); + assertContextSize(2); // one canvas for each of our two endpoints. + ok(!e3.isFull(), "endpoint 3 is not full."); + jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // we have one connection + jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // should have refused the connection; default max is 1. + assertContextSize(3); // now we have one more canvas, for the Connection that was accepted. + }); + + test(renderMode + ': specifiedEndpointMaxConnections', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = jsPlumb.addEndpoint(d5, {isSource:true, maxConnections:3}); + ok(e5.anchor, 'endpoint 5 has an anchor'); + var e6 = jsPlumb.addEndpoint(d6, {isSource:true, maxConnections:2}); // this one has max TWO + assertEndpointCount("d5", 1); assertEndpointCount("d6", 1); + assertContextSize(2); + ok(!e5.isFull(), "endpoint 5 is not full."); + jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 1); // we have one connection + assertContextSize(3); + jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // two connections + assertContextSize(4); + jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // should have refused; max is 2, for d4. + assertContextSize(4); + }); + + test(renderMode + ': noEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + var e4 = jsPlumb.addEndpoint(d4, {isSource:true, maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertContextSize(3); + assertConnectionCount(e3, 1); // we have one connection + jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 2); // we have two. etc (default was one. this proves max is working). + assertContextSize(4); + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e4}); + assertConnectionCount(e4, 3); + assertContextSize(6); + }); + + test(renderMode + ': anchors equal', function() { + var a1 = jsPlumb.makeAnchor(0, 1, 1, 1); + var a2 = jsPlumb.makeAnchor(0, 1, 1, 1); + ok(a1.equals(a2), "anchors are the same"); + }); + + test(renderMode + ': anchors equal with offsets', function() { + var a1 = jsPlumb.makeAnchor(0, 1, 1, 1, 10, 13); + var a2 = jsPlumb.makeAnchor(0, 1, 1, 1, 10, 13); + ok(a1.equals(a2), "anchors are the same"); + }); + + test(renderMode + ': anchors not equal', function() { + var a1 = jsPlumb.makeAnchor(0, 1, 0, 1); + var a2 = jsPlumb.makeAnchor(0, 1, 1, 1); + ok(!a1.equals(a2), "anchors are different"); + }); + + test(renderMode + ': anchor not equal with offsets', function() { + var a1 = jsPlumb.makeAnchor(0, 1, 1, 1, 10, 13); + var a2 = jsPlumb.makeAnchor(0, 1, 1, 1); + ok(!a1.equals(a2), "anchors are different"); + }); + + test(renderMode + ': detach plays nice when no target given', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + jsPlumb.connect({source:d3, target:d4}); + jsPlumb.detach(); + }); + + // issue 81 + test(renderMode + ': jsPlumb.detach should fire only one detach event (pass Connection as argument)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + jsPlumb.detach(conn); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': jsPlumb.detach should fire only one detach event (pass Connection as param in argument)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + jsPlumb.detach({connection:conn}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': jsPlumb.detach should fire only one detach event (pass source and targets as strings as arguments in params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + jsPlumb.detach({source:"d5", target:"d6"}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': jsPlumb.detach should fire only one detach event (pass source and targets as divs as arguments in params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + jsPlumb.detach({source:d5, target:d6}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': jsPlumb.detach should fire only one detach event (pass source and targets as divs as arguments)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + jsPlumb.detach(d5, d6); + equals(eventCount, 1); + }); + + //TODO make sure you run this test with a single detach call, to ensure that + // single detach calls result in the connection being removed. detachEverything can + // just blow away the connectionsByScope array and recreate it. + test(renderMode + ': jsPlumb.getConnections (simple case, default scope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + jsPlumb.connect({source:d5, target:d6}); + assertContextSize(3); + var c = jsPlumb.getConnections(); // will get all connections in the default scope. + equals(c.length, 1, "there is one connection"); + }); + + test(renderMode + ': jsPlumb.getConnections (simple case, default scope; detach by element id)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + jsPlumb.connect({source:d5, target:d6}); + jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections in the default scope + equals(c.length, 2, "there are two connections initially"); + jsPlumb.detach('d5', 'd6'); + c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': jsPlumb.getConnections (simple case, default scope; detach by element id using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + jsPlumb.connect({source:d5, target:d6}); + jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + jsPlumb.detach({source:'d5', target:'d6'}); + c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': jsPlumb.getConnections (simple case, default scope; detach by element object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + jsPlumb.connect({source:d5, target:d6}); + jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + jsPlumb.detach(d5, d6); + c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': jsPlumb.getConnections (simple case, default scope; detach by element object using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + jsPlumb.connect({source:d5, target:d6}); + jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + jsPlumb.detach({source:d5, target:d6}); + c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': jsPlumb.getConnections (simple case, default scope; detach by Connection)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + var c56 = jsPlumb.connect({source:d5, target:d6}); + var c67 = jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + jsPlumb.detach(c56); + assertContextSize(5); // check that the connection canvas was removed. + c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + + }); + + test(renderMode + ': jsPlumb.getConnections (scope testScope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + jsPlumb.connect({source:d5, target:d6, scope:'testScope'}); + var c = jsPlumb.getConnections("testScope"); // will get all connections in testScope + equals(c.length, 1, "there is one connection"); + equals(c[0].sourceId, 'd5', "the connection's source is d5"); + equals(c[0].targetId, 'd6', "the connection's source is d6"); + c = jsPlumb.getConnections(); // will get all connections in default scope; should be none. + equals(c.length, 0, "there are no connections in the default scope"); + }); + + test(renderMode + ': jsPlumb.getAllConnections (filtered by scope)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + jsPlumb.connect({source:d9, target:d10}); // default scope + var c = jsPlumb.getAllConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 1); + equals(c['testScope'].length, 1); + // now supply a list of scopes + c = jsPlumb.getConnections(); + equals(c.length, 1); + c = jsPlumb.getConnections("testScope"); + equals(c.length, 1, "there is one connection in 'testScope'"); + }); + + test(renderMode + ': jsPlumb.getConnections (filtered by scope and sourceId)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + jsPlumb.connect({source:d9, target:d8, scope:'testScope'}); + jsPlumb.connect({source:d9, target:d10}); // default scope + var c = jsPlumb.getConnections({scope:'testScope', source:'d8'}); // will get all connections with sourceId 'd8' + equals(c.length, 1, "there is one connection in 'testScope' from d8"); + }); + + test(renderMode + ': jsPlumb.getConnections (filtered by scope, source id and target id)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d12, target:d13, scope:'testScope'}); + jsPlumb.connect({source:d11, target:d13, scope:'testScope'}); + var c = jsPlumb.getConnections({scope:'testScope', source:'d11', target:'d13'}); + equals(c.length, 1, "there is one connection from d11 to d13"); + }); + + test(renderMode + ': jsPlumb.getConnections (filtered by a list of scopes)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = jsPlumb.getConnections({scope:['testScope','testScope3']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + }); + + test(renderMode + ': jsPlumb.getConnections (filtered by a list of scopes and source ids)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + }); + + test(renderMode + ': jsPlumb.getConnections (filtered by a list of scopes, source ids and target ids)', function() { + jsPlumb.detachEverything(); + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'), d14=_addDiv("d14"), d15=_addDiv("d15"); + jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d11, target:d14, scope:'testScope'}); + jsPlumb.connect({source:d11, target:d15, scope:'testScope'}); + jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + assertContextSize(18); + var c = jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11'], target:['d14', 'd15']}); + equals(c['testScope'].length, 2, 'there are two connections in testScope'); + equals(c['testScope3'], null, 'there are no connections in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + var anEntry = c['testScope'][0]; + ok(anEntry.endpoints[0] != null, "Source endpoint is set"); + ok(anEntry.endpoints[1] != null, "Target endpoint is set"); + equals(anEntry.source.attr("id"), "d11", "Source is div d11"); + equals(anEntry.target.attr("id"), "d14", "Target is div d14"); + }); + + test(renderMode + ': getEndpoints, one Endpoint added by addEndpoint, get Endpoints by selector', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + jsPlumb.addEndpoint(d1); + var e = jsPlumb.getEndpoints(d1); + equals(e.length, 1, "there is one endpoint for element d1"); + }); + + test(renderMode + ': getEndpoints, one Endpoint added by addEndpoint, get Endpoints by id', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + jsPlumb.addEndpoint(d1); + var e = jsPlumb.getEndpoints("d1"); + equals(e.length, 1, "there is one endpoint for element d1"); + }); + + test(renderMode + ': connection event listeners', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null/*, returnedParams2 = null*/; + jsPlumb.bind("jsPlumbConnection", function(params) { + returnedParams = $.extend({}, params); + }); + var c = jsPlumb.connect({source:d1, target:d2}); + ok(returnedParams != null, "new connection listener event was fired"); + ok(returnedParams.connection != null, 'connection is set'); + equals(returnedParams.sourceId, "d1", 'sourceid is set'); + equals(returnedParams.targetId, "d2", 'targetid is set'); + equals(returnedParams.source.attr("id"), "d1", 'source is set'); + equals(returnedParams.target.attr("id"), "d2" , 'target is set'); + ok(returnedParams.sourceEndpoint != null, "source endpoint is not null"); + ok(returnedParams.targetEndpoint != null, "target endpoint is not null"); + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + jsPlumb.detach(c); + ok(returnedParams.connection != null, 'connection is set'); + //jsPlumb.detachAll("d1"); + //ok(returnedParams2 != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (detach by connection)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + ok(returnedParams.connection != null, "removed connection listener event passed in connection"); + }); + + test(renderMode + ': detach event listeners (detach by element ids)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach("d1","d2"); + ok(returnedParams != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (detach by elements)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach(d1,d2); + ok(returnedParams != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detach method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detachFrom method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachFrom(e2); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detachAll method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachAll(); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via jsPlumb.deleteEndpoint method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + jsPlumb.deleteEndpoint(e1); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(1); + }); + + test(renderMode + ': detach event listeners (ensure cleared by jsPlumb.reset)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach(d1,d2); + ok(returnedParams != null, "removed connection listener event was fired"); + returnedParams = null; + + jsPlumb.reset(); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach(d1,d2); + ok(returnedParams == null, "connection listener was cleared by jsPlumb.reset()"); + }); + + test(renderMode + ': connection events that throw errors', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null, returnedParams2 = null; + jsPlumb.bind("jsPlumbConnection", function(params) { + returnedParams = $.extend({}, params); + throw "oh no!"; + }); + jsPlumb.connect({source:d1, target:d2}); + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + jsPlumb.connect({source:d3, target:d4}); + ok(returnedParams != null, "new connection listener event was fired; we threw an error, jsPlumb survived."); + }); + + test(renderMode + ": Endpoint.detachFrom", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + e16.detachFrom(e17); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + }); + + test(renderMode + ": Endpoint.detachFromConnection", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + e16.detachFromConnection(conn); + assertContextSize(3); // all canvases should remain; the connection was not removed. + // but endpoint e16 should have no connections now. + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 1); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + }); + + test(renderMode + ": Endpoint.detachAll", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + var e16 = jsPlumb.addEndpoint($("#d16"), {isSource:true,maxConnections:-1}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint($("#d17"), {isSource:true}); + var e18 = jsPlumb.addEndpoint($("#d18"), {isSource:true}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e18}); + assertConnectionCount(e16, 2); + assertConnectionCount(e17, 1); + assertConnectionCount(e18, 1); + assertContextSize(5); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 2); + e16.detachAll(); + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + }); + + test(renderMode + ": Endpoint.detach", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + e16.detach(conn); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + }); + + test(renderMode + ": Endpoint.isConnectedTo", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.isConnectedTo(e17), true, "e16 and e17 are connected"); + }); + + test(renderMode + ": setting endpoint uuid", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + equals(e16.getUuid(), uuid, "endpoint's uuid was set correctly"); + }); + + test(renderMode + ": jsPlumb.getEndpoint (by uuid)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + }); + + test(renderMode + ": jsPlumb.deleteEndpoint (by uuid, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + jsPlumb.deleteEndpoint(uuid); + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + var ebe = jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + assertContextSize(0); + }); + + test(renderMode + ": jsPlumb.deleteEndpoint (by uuid, connections too)", function() { + // create two endpoints (one with a uuid), add them to two divs and then connect them. + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + // check that the connection was ok. + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + // delete the endpoint that has a uuid. verify that the endpoint cannot be retrieved and that the connection has been removed, but that + // element d17 still has its Endpoint. + jsPlumb.deleteEndpoint(uuid); + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + var ebe = jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + ebe = jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 1, "element d17 still has its Endpoint"); + assertContextSize(1); + + // now delete d17's endpoint and check that it has gone. + jsPlumb.deleteEndpoint(e17); + f = jsPlumb.getEndpoint(e17); + equals(f, null, "endpoint has been deleted"); + ebe = jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 0, "element d17 no longer has any Endpoints"); + assertContextSize(0); + }); + + test(renderMode + ": jsPlumb.deleteEndpoint (by reference, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + jsPlumb.deleteEndpoint(e16); + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + assertContextSize(0); + }); + + test(renderMode + ": jsPlumb.deleteEndpoint (by reference, connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + jsPlumb.deleteEndpoint(e16); + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + assertContextSize(1); + }); + + test(renderMode + ": jsPlumb.deleteEveryEndpoint", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + assertContextSize(2); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + jsPlumb.deleteEveryEndpoint(); + + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); + }); + + test(renderMode + ": jsPlumb.deleteEveryEndpoint (connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertContextSize(3); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + jsPlumb.deleteEveryEndpoint(); + + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); // no canvases. so all connection canvases have been cleaned up too. + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + }); + + test(renderMode + ": jsPlumb.addEndpoint (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {anchor:[0,0.5,0,-1]}); + var e17 = jsPlumb.addEndpoint(d17, {anchor:"TopCenter"}); + assertContextSize(2); + equals(e16.anchor.x, 0); + equals(e16.anchor.y, 0.5); + equals(e17.anchor.x, 0.5); + equals(e17.anchor.y, 0); + }); + + test(renderMode + ": jsPlumb.addEndpoint (simple case, dynamic anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + assertContextSize(2); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": jsPlumb.addEndpoint (simple case, two arg method)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchor:[0,0.5,0,-1]}); + var e17 = jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchor:"TopCenter"}); + assertContextSize(2); + equals(e16.anchor.x, 0); + equals(e16.anchor.y, 0.5); + equals(false, e16.isTarget); + equals(true, e16.isSource); + equals(e17.anchor.x, 0.5); + equals(e17.anchor.y, 0); + equals(true, e17.isTarget); + equals(false, e17.isSource); + }); + + + test(renderMode + ": jsPlumb.addEndpoints (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false, anchor:[0,0.5,0,-1] }, { isTarget:true, isSource:false, anchor:"TopCenter" }]); + assertContextSize(2); + equals(e16[0].anchor.x, 0); + equals(e16[0].anchor.y, 0.5); + equals(false, e16[0].isTarget); + equals(true, e16[0].isSource); + equals(e16[1].anchor.x, 0.5); + equals(e16[1].anchor.y, 0); + equals(true, e16[1].isTarget); + equals(false, e16[1].isSource); + }); + + test(renderMode + ": jsPlumb.addEndpoints (with reference params)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var refParams = {anchor:"RightMiddle"}; + var e16 = jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false}, { isTarget:true, isSource:false }], refParams); + assertContextSize(2); + equals(e16[0].anchor.x, 1); + equals(e16[0].anchor.y, 0.5); + equals(false, e16[0].isTarget); + equals(true, e16[0].isSource); + equals(e16[1].anchor.x, 1); + equals(e16[1].anchor.y, 0.5); + equals(true, e16[1].isTarget); + equals(false, e16[1].isSource); + }); + + test(renderMode + ": jsPlumb.addEndpoint (simple case, dynamic anchors, two arg method)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchors:["TopCenter", "BottomCenter"]}); + assertContextSize(2); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": jsPlumb.makeTarget (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + jsPlumb.makeTarget(d17, { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": jsPlumb.makeTarget (specify two divs in an array)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + jsPlumb.makeTarget([d16, d17], { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": jsPlumb.makeTarget (specify two divs by id in an array)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + jsPlumb.makeTarget(["d16", "d17"], { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": jsPlumb.makeTarget (specify divs by selector)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + d16.addClass("FOO");d17.addClass("FOO"); + jsPlumb.makeTarget($(".FOO"), { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ': jsPlumb.connect (between two Endpoints)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1); + assertEndpointCount("d2", 1); + jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1); // no new endpoint should have been added + assertEndpointCount("d2", 1); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. + }); + + test(renderMode + ': jsPlumb.connect (between two Endpoints, and dont supply any parameters to the Endpoints.)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1); + assertEndpointCount("d2", 1); + jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1); // no new endpoint should have been added + assertEndpointCount("d2", 1); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. + }); + + test(renderMode + ': jsPlumb.connect (by endpoint)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + }); + + test(renderMode + ": jsPlumb.connect (two Endpoints - that have been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1, uuid:srcEndpointUuid}); + var e2 = jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1, uuid:dstEndpointUuid}); + jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ] }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertContextSize(3); + }); + + test(renderMode + ": jsPlumb.connect (two Endpoints - that have not been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ], source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + var e1 = jsPlumb.getEndpoint(srcEndpointUuid); + ok(e1 != null, "endpoint with src uuid added"); + ok(e1.canvas != null); + var e2 = jsPlumb.getEndpoint(dstEndpointUuid); + ok(e2 != null, "endpoint with target uuid added"); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + }); + + test(renderMode + ": jsPlumb.connect (two Endpoints - that have been already added - by endpoint reference)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1}); + var e2 = jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1}); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + }); + + test(renderMode + ": jsPlumb.connect (two elements)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + jsPlumb.connect({ source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + }); + + test(renderMode + ": jsPlumb.connect (Connector test, straight)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": jsPlumb.connect (Connector test, bezier, no params)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Bezier" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 150, "Bezier connector chose 150 curviness"); + }); + + test(renderMode + ": jsPlumb.connect (Connector test, bezier, curviness as int)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", { curviness:200 }] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Canvas Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 200, "Bezier connector chose 200 curviness"); + }); + + /*jsPlumb.setRenderMode(jsPlumb.SVG); + test(renderMode + ": jsPlumb.connect (Connector test, bezier, curviness as int)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", 200] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors.svg.Bezier, "SVG Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 200, "Bezier connector chose 200 curviness"); + });*/ + + test(renderMode + ": jsPlumb.connect (Connector test, bezier, curviness as named option)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", {curviness:300}] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 300, "Bezier connector chose 300 curviness"); + }); + + test(renderMode + ": jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Canvas Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": jsPlumb.connect (anchors registered correctly; source and target anchors given, as strings)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:["LeftMiddle", "RightMiddle"] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.5, conn.endpoints[0].anchor.y, "source anchor y"); + equals(1, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.5, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + + test(renderMode + ": jsPlumb.connect (two argument method in which some data is reused across connections)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"), d19 = _addDiv("d19"); + var sharedData = { connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }; + var conn = jsPlumb.connect({ source:d16, target:d17}, sharedData); + var conn2 = jsPlumb.connect({ source:d18, target:d19}, sharedData); + assertContextSize(6); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 2); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(conn2.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + equals(0.3, conn2.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn2.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn2.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn2.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": jsPlumb.connect (Connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": jsPlumb.connect (Endpoint test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection target"); + }); + + test(renderMode + ": jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection target"); + }); + + test(renderMode + ": jsPlumb.connect (Endpoints test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Dot, "Dot endpoint chosen for connection target"); + }); + + test(renderMode + ": jsPlumb.connect (Blank Endpoint specified via 'endpoint' param)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoint:"Blank" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection target"); + }); + + test(renderMode + ": jsPlumb.connect (Blank Endpoint specified via 'endpoints' param)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoints:["Blank", "Blank" ] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection target"); + }); + + test(renderMode + ": jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Dot, "Dot endpoint chosen for connection target"); + }); + + test(renderMode + ": jsPlumb.connect (by Endpoints, connector test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {}); + var e17 = jsPlumb.addEndpoint(d17, {}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": jsPlumb.connect (by Endpoints, connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {}); + var e17 = jsPlumb.addEndpoint(d17, {}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": jsPlumb.connect (by Endpoints, anchors as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = "TopCenter", a17 = "BottomCenter"; + var e16 = jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, 0.5, "endpoint 16 is at top center");equals(e16.anchor.y, 0, "endpoint 16 is at top center"); + equals(e17.anchor.x, 0.5, "endpoint 17 is at bottom center");equals(e17.anchor.y, 1, "endpoint 17 is at bottom center"); + }); + + test(renderMode + ": jsPlumb.connect (by Endpoints, endpoints create anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = [0,0.5,0,-1], a17 = [1,0.0,-1,-1]; + var e16 = jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, a16[0]);equals(e16.anchor.y, a16[1]); + equals(e17.anchor.x, a17[0]);equals(e17.anchor.y, a17[1]); + equals(e16.anchor.getOrientation()[0], a16[2]); equals(e16.anchor.getOrientation()[1], a16[3]); + equals(e17.anchor.getOrientation()[0], a17[2]); equals(e17.anchor.getOrientation()[1], a17[3]); + }); + + test(renderMode + ": jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchor')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {anchor:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = jsPlumb.addEndpoint(d17, {anchor:["TopCenter", "BottomCenter"]}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchors')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": jsPlumb.connect (connect by element, default endpoint, supplied dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var anchors = [ [0.25, 0, 0, -1], [1, 0.25, 1, 0], [0.75, 1, 0, 1], [0, 0.75, -1, 0] ]; + jsPlumb.connect({source:d1, target:d2, dynamicAnchors:anchors}); // auto connect with default endpoint and provided anchors + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + assertContextSize(2); + }); + + test(renderMode + ": jsPlumb.connect (connect by element, supplied endpoint and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = jsPlumb.addEndpoint(d1, endpoint); + var e2 = jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + assertContextSize(3); + jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + assertContextSize(2); + }); + + test(renderMode + ": jsPlumb.connect (connect by element, supplied endpoints using 'source' and 'target' (this test is identical to the one above apart from the param names), and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = jsPlumb.addEndpoint(d1, endpoint); + var e2 = jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + jsPlumb.connect({source:e1, target:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + assertContextSize(3); + jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + assertContextSize(2); + }); + + + test(renderMode + ": jsPlumb.connect (testing for connection event callback)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var connectCallback = null, detachCallback = null; + jsPlumb.bind("jsPlumbConnection", function(params) { + connectCallback = $.extend({}, params); + }); + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + detachCallback = $.extend({}, params); + }); + jsPlumb.connect({source:d1, target:d2}); // auto connect with default endpoint and anchor set + ok(connectCallback != null, "connect callback was made"); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + jsPlumb.detach({source:d1, target:d2}); + assertContextSize(2); + ok(detachCallback != null, "detach callback was made"); + }); + + test(renderMode + ": jsPlumb.connect (setting cssClass on Connector)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = jsPlumb.connect({source:d1,target:d2,cssClass:"CSS"}); + var has = renderMode == jsPlumb.SVG ? function(clazz) { + return c.canvas.childNodes[0].className.baseVal.indexOf(clazz) != -1; + } : renderMode == jsPlumb.CANVAS ? function(clazz) { + return $(c.canvas).hasClass(clazz); + } : function(clazz) { + return $(c.connector.canvas).hasClass(clazz); + }; + + ok(has("CSS"), "custom cssClass set correctly"); + ok(has(jsPlumb.connectorClass), "basic connector class set correctly"); + }); + + test(renderMode + ": jsPlumb.connect (overlays, long-hand version)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = {width:40,length:40,location:0.7, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"}}; + var connection1 = jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + }); + + test(renderMode + ": jsPlumb.connect (overlays, long-hand version, IDs specified)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + equals("aLabel", connection1.overlays[0].id); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + equals("anArrow", connection1.overlays[1].id); + }); + + test(renderMode + ": jsPlumb.connect (remover single overlay by id)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + connection1.removeOverlay("aLabel"); + equals(1, connection1.overlays.length); + equals("anArrow", connection1.overlays[0].id); + }); + + test(renderMode + ": jsPlumb.connect (remove multiple overlays by id)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + connection1.removeOverlays("aLabel", "anArrow"); + equals(0, connection1.overlays.length); + }); + + test(renderMode + ": jsPlumb.connect (overlays, short-hand version)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var loc = { location:0.7 }; + var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} }; + var connection1 = jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label", {label:"CONNECTION 1", location:0.3}], + ["Arrow", arrowSpec, loc] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + }); + + test(renderMode + ": jsPlumb.connect (removeAllOverlays)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var loc = { location:0.7 }; + var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} }; + var connection1 = jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label", {label:"CONNECTION 1", location:0.3}], + ["Arrow", arrowSpec, loc] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + + connection1.removeAllOverlays(); + equals(0, connection1.overlays.length); + }); + + test(renderMode + ": jsPlumb.connect, specify arrow overlay using string identifier only", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var conn = jsPlumb.connect({source:d1,target:d2,overlays:["Arrow"]}); + equals(jsPlumb.Overlays[renderMode].Arrow, conn.overlays[0].constructor); + }); + + // this test is for the original detach function; it should stay working after i mess with it + // a little. + test(renderMode + ": jsPlumb.detach (by element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + var e3 = jsPlumb.addEndpoint(d1); + var e4 = jsPlumb.addEndpoint(d2); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + jsPlumb.connect({ sourceEndpoint:e3, targetEndpoint:e4 }); // make two connections to be sure this works ;) + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionCount(e3, 1); + assertConnectionCount(e4, 1); + + jsPlumb.detach("d1", "d2"); + + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionCount(e3, 0); + assertConnectionCount(e4, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + }); + + // detach is being made to operate more like connect - by taking one argument with a whole + // bunch of possible params in it. if two args are passed in it will continue working + // in the old way. + test(renderMode + ": jsPlumb.detach (params object, using element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + jsPlumb.detach({source:"d1", target:"d2"}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + }); + + test(renderMode + ": jsPlumb.detach (params object, using element objects)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + jsPlumb.detach({source:d1, target:d2}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + assertContextSize(2); + }); + + test(renderMode + ": jsPlumb.detach (source and target as endpoint UUIDs)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {uuid:"abcdefg"}); + ok(jsPlumb.getEndpoint("abcdefg") != null, "e1 exists"); + var e2 = jsPlumb.addEndpoint(d2, {uuid:"hijklmn"}); + ok(jsPlumb.getEndpoint("hijklmn") != null, "e2 exists"); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + jsPlumb.detach({uuids:["abcdefg", "hijklmn"]}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + assertContextSize(2); + }); + + test(renderMode + ": jsPlumb.detach (sourceEndpoint and targetEndpoint supplied)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + jsPlumb.detach({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + assertContextSize(2); + }); + + test(renderMode + ": jsPlumb.makeDynamicAnchors (longhand)", function() { + var anchors = [jsPlumb.makeAnchor(0.2, 0, 0, -1), jsPlumb.makeAnchor(1, 0.2, 1, 0), + jsPlumb.makeAnchor(0.8, 1, 0, 1), jsPlumb.makeAnchor(0, 0.8, -1, 0) ]; + var dynamicAnchor = jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); + }); + + test(renderMode + ": jsPlumb.makeDynamicAnchors (shorthand)", function() { + var anchors = [[0.2, 0, 0, -1], [1, 0.2, 1, 0], + [0.8, 1, 0, 1], [0, 0.8, -1, 0] ]; + var dynamicAnchor = jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); + }); + + test(renderMode + ": Connection.isVisible/setVisible", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c1 = jsPlumb.connect({source:d1,target:d2}); + equals(true, c1.isVisible(), "Connection is visible after creation."); + c1.setVisible(false); + equals(false, c1.isVisible(), "Connection is not visible after calling setVisible(false)."); + equals($(c1.connector.canvas).css("display"), "none"); + c1.setVisible(true); + equals(true, c1.isVisible(), "Connection is visible after calling setVisible(true)."); + equals($(c1.connector.canvas).css("display"), "block"); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible basic test (no connections)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals($(e1.canvas).css("display"), "none"); + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals($(e1.canvas).css("display"), "block"); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should track changes in the source, because it has only this connection.)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1), e2 = jsPlumb.addEndpoint(d2); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + var c1 = jsPlumb.connect({source:e1, target:e2}); + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals(false, e2.isVisible(), "other Endpoint is not visible either."); + equals(false, c1.isVisible(), "connection between the two is not visible either."); + + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals(true, e2.isVisible(), "other Endpoint is visible too"); + equals(true, c1.isVisible(), "connection between the two is visible too."); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should not track changes in the source, because it has another connection.)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var e1 = jsPlumb.addEndpoint(d1), e2 = jsPlumb.addEndpoint(d2, { maxConnections:2 }), e3 = jsPlumb.addEndpoint(d3); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + var c1 = jsPlumb.connect({source:e1, target:e2}); + var c2 = jsPlumb.connect({source:e2, target:e3}); + + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals(true, e2.isVisible(), "other Endpoint should still be visible."); + equals(true, e3.isVisible(), "third Endpoint should still be visible."); + equals(false, c1.isVisible(), "connection between the two is not visible either."); + equals(true, c2.isVisible(), "other connection is visible."); + + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals(true, e2.isVisible(), "other Endpoint is visible too"); + equals(true, c1.isVisible(), "connection between the two is visible too."); + equals(true, c2.isVisible(), "other connection is visible."); + }); + + + /** + * leave this test at the bottom! + */ + test(renderMode + ': unload test', function() { + jsPlumb.unload(); + var contextNode = $("._jsPlumb_context"); + ok(contextNode.length == 0, 'context node unloaded'); + }); +}; + diff --git a/archive/1.3.1/jsPlumb-defaults-1.3.1-RC1.js b/archive/1.3.1/jsPlumb-defaults-1.3.1-RC1.js new file mode 100644 index 000000000..b1352d9a5 --- /dev/null +++ b/archive/1.3.1/jsPlumb-defaults-1.3.1-RC1.js @@ -0,0 +1,907 @@ +/* +* jsPlumb-defaults-1.3.0-RC1 +* +* Copyright 2010 - 2011 Simon Porritt http://jsplumb.org +* +* Triple licensed under the MIT, GPL2 and Beer licenses. +*/ + +(function() { + + + + /** + * + * Helper class to consume unused mouse events by components that are DOM elements and + * are used by all of the different rendering modes. + * + */ + jsPlumb.DOMElementComponent = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + // when render mode is canvas, these functions may be called by the canvas mouse handler. + // this component is safe to pipe this stuff to /dev/null. + this.mousemove = + this.dblclick = + this.click = + this.mousedown = + this.mouseup = function(e) { }; + }; + + /** + * Class: Connectors.Straight + * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + var currentPoints = null; + var _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + _dx = _tx - _sx, _dy = (_ty - _sy); + _m = _dy / _dx, _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + + return currentPoints; + }; + + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + var xp = _sx + (location * _dx); + var yp = (_m == Infinity || _m == -Infinity) ? _sy + (location * (_ty - _sy)) : (_m * xp) + _b; + return {x:xp, y:yp}; + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { return _m; }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + var orientation = distance > 0 ? 1 : -1; + var y = Math.abs(distance * Math.sin(_theta)); + if (_sy > _ty) y = y * -1; + var x = Math.abs(distance * Math.cos(_theta)); + if (_sx > _tx) x = x * -1; + return {x:p.x + (orientation * x), y:p.y + (orientation * y)}; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = self.gradientAtPoint(p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + }; + }; + + + /** + * Class:Connectors.Bezier + * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's + * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below. + */ + /** + * Function:Constructor + * + * Parameters: + * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line. + * Optional; defaults to 150. + * + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + params = params || {}; + this.majorAnchor = params.curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h; + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) + { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx); var minx2 = Math.min(_CP[0], _CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(_sx,_tx); var maxx2 = Math.max(_CP[0], _CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty); var miny2 = Math.min(_CP[1], _CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(_sy,_ty); var maxy2 = Math.max(_CP[1], _CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + return jsBezier.perpendicularToCurveAt(_makeCurve(), location, length, distance); + }; + + }; + + + /** + * Class: Connectors.Flowchart + * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments. + */ + /** + * Function: Constructor + * + * Parameters: + * stub - minimum length for the stub at each end of the connector. defaults to 30 pixels. + */ + jsPlumb.Connectors.Flowchart = function(params) { + params = params || {}; + var self = this, + minStubLength = params.stub || params.minStubLength /* bwds compat. */ || 30, + segments = [], + segmentGradients = [], + segmentProportions = [], + segmentLengths = [], + segmentProportionalLengths = [], + points = [], + swapX, + swapY, + /** + * recalculates the gradients of each segment, and the points at which the segments begin, proportional to the total length travelled + * by all the segments that constitute the connector. + */ + updateSegmentGradientsAndProportions = function(startX, startY, endX, endY) { + var total = 0; + for (var i = 0; i < segments.length; i++) { + var sx = i == 0 ? startX : segments[i][2], + sy = i == 0 ? startY : segments[i][3], + ex = segments[i][0], + ey = segments[i][1]; + + segmentGradients[i] = sx == ex ? Infinity : 0; + segmentLengths[i] = Math.abs(sx == ex ? ey - sy : ex - sx); + total += segmentLengths[i]; + } + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segmentLengths[i] / total; + segmentProportions[i] = [curLoc, (curLoc += (segmentLengths[i] / total)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0]; + var ly = segments.length == 0 ? sy : segments[segments.length - 1][1]; + segments.push([x, y, lx, ly]); + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment + * that contains the point which is 'location' distance along the entire path, where + * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths + * are made up of a list of segments, each of which contributes some fraction to + * the total length. + */ + findSegmentForLocation = function(location) { + var idx = segmentProportions.length - 1, inSegmentProportion = 0; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + segments = []; + segmentGradients = []; + segmentProportionalLengths = []; + segmentLengths = []; + segmentProportionals = []; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + offx = (lw / 2) + (minStubLength * 2), + offy = (lw / 2) + (minStubLength * 2), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(), + to = targetAnchor.orientation || targetAnchor.getOrientation(), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx, + h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy; + if (w < minWidth) { + offx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + offy += (minWidth - h) / 2; + h = minWidth; + } + var sx = swapX ? w-offx : offx, + sy = swapY ? h-offy : offy, + tx = swapX ? offx : w-offx , + ty = swapY ? offy : h-offy, + startStubX = sx + (so[0] * minStubLength), + startStubY = sy + (so[1] * minStubLength), + endStubX = tx + (to[0] * minStubLength), + endStubY = ty + (to[1] * minStubLength), + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2); + + x -= offx; y -= offy; + points = [x, y, w, h, sx, sy, tx, ty], extraPoints = []; + + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + if (so[0] == 0) { + var startStubIsBeforeEndStub = startStubY < endStubY; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } + } + else { + var startStubIsBeforeEndStub = startStubX < endStubX; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } + } + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentGradientsAndProportions(sx, sy, tx, ty); + + return points; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location) { + return self.pointAlongPathFrom(location, 0); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location) { + return segmentGradients[findSegmentForLocation(location)["index"]]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance) { + var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segmentLengths[s.index], m = segmentGradients[s.index]; + var e = { + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + return e; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = segmentGradients[p.segmentInfo.index]; + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + + }; + + }; + + // ********************************* END OF CONNECTOR TYPES ******************************************************************* + + // ********************************* ENDPOINT TYPES ******************************************************************* + + /** + * Class: Endpoints.Dot + * A round endpoint, with default radius 10 pixels. + */ + + /** + * Function: Constructor + * + * Parameters: + * + * radius - radius of the endpoint. defaults to 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + var self = this; + params = params || {}; + this.radius = params.radius || 10; + this.defaultOffset = 0.5 * this.radius; + this.defaultInnerRadius = this.radius / 3; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var r = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - r; + var y = anchorPoint[1] - r; + return [ x, y, r * 2, r * 2, r ]; + }; + }; + + /** + * Class: Endpoints.Rectangle + * A Rectangular Endpoint, with default size 20x20. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the endpoint. defaults to 20 pixels. + * height - height of the endpoint. defaults to 20 pixels. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + var self = this; + params = params || {}; + this.width = params.width || 20; + this.height = params.height || 20; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + return [ x, y, width, height]; + }; + }; + + /** + * Class: Endpoints.Image + * Draws an image as the Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * src - location of the image to use. + */ + jsPlumb.Endpoints.Image = function(params) { + + jsPlumb.DOMElementComponent.apply(this, arguments); + + var self = this, initialized = false; + this.img = new Image(); + self.ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.src || params.url; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + self.anchorPoint = anchorPoint; + if (self.ready) return [anchorPoint[0] - self.img.width / 2, anchorPoint[1] - self.img.height/ 2, self.img.width, self.img.height]; + else return [0,0,0,0]; + }; + + self.canvas = document.createElement("img"), initialized = false; + self.canvas.style["margin"] = 0; + self.canvas.style["padding"] = 0; + self.canvas.style["outline"] = 0; + self.canvas.style["position"] = "absolute"; + self.canvas.className = jsPlumb.endpointClass; + jsPlumb.appendElement(self.canvas, params.parent); + self.attachListeners(self.canvas, self); + + var actuallyPaint = function(d, style, anchor) { + if (!initialized) { + self.canvas.setAttribute("src", self.img.src); + initialized = true; + } + var width = self.img.width, + height = self.img.height, + x = self.anchorPoint[0] - (width/2), + y = self.anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(self.canvas, x, y, width, height); + }; + + this.paint = function(d, style, anchor) { + if (self.ready) { + actuallyPaint(d, style, anchor); + } + else { + window.setTimeout(function() { + self.paint(d, style, anchor); + }, 200); + } + }; + }; + + /** + * Class: Endpoints.Blank + * An Endpoint that paints nothing on the screen, and cannot be interacted with using the mouse. There are no constructor parameters for this Endpoint. + */ + jsPlumb.Endpoints.Blank = function() { + jsPlumb.DOMElementComponent.apply(this, arguments); + this.compute = function() { + return [0,0,0,0]; + }; + + self.canvas = document.createElement("div"); + this.paint = function() { }; + }; + + /** + * Class: Endpoints.Triangle + * A triangular Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the triangle's base. defaults to 55 pixels. + * height - height of the triangle from base to apex. defaults to 55 pixels. + */ + jsPlumb.Endpoints.Triangle = function(params) { + params = params || { }; + params.width = params.width || 55; + param.height = params.height || 55; + this.width = params.width; + this.height = params.height; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + return [ x, y, width, height ]; + }; + }; +// ********************************* END OF ENDPOINT TYPES ******************************************************************* + + +// ********************************* OVERLAY DEFINITIONS *********************************************************************** + + /** + * Class: Overlays.Arrow + * + * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length + * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction + * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line + * across the tail. + */ + /** + * Function: Constructor + * + * Parameters: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + params = params || {}; + var self = this; + this.length = params.length || 20; + this.width = params.width || 20; + this.id = params.id; + this.connection = params.connection; + var direction = (params.direction || 1) < 0 ? -1 : 1; + var paintStyle = params.paintStyle || { lineWidth:1 }; + this.loc = params.location == null ? 0.5 : params.location; + // how far along the arrow the lines folding back in come to. default is 62.3%. + var foldback = params.foldback || 0.623; + var _getFoldBackPoint = function(connector, loc) { + if (foldback == 0.5) return connector.pointOnPath(loc); + else { + var adj = 0.5 - foldback; // we calculate relative to the center + return connector.pointAlongPathFrom(loc, direction * self.length * adj); + } + }; + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + // this is the arrow head position + var hxy = connector.pointAlongPathFrom(self.loc, direction * (self.length / 2)); + // this is the center of the tail + var txy = connector.pointAlongPathFrom(self.loc, -1 * direction * (self.length / 2)), tx = txy.x, ty = txy.y; + // this is the tail vector + var tail = connector.perpendicularToPathAt(self.loc, self.width, -1 * direction * (self.length / 2)); + // this is the point the tail goes in to + var cxy = _getFoldBackPoint(connector, self.loc); + + // if loc = 1, then hxy should be flush with the element, or if direction == -1, the tail midpoint. + if (self.loc == 1) { + var lxy = connector.pointOnPath(self.loc); + // TODO determine why the 1.2.6 released version does not + // use 'direction' in the two equations below, yet both + // that and 1.3.0 still paint the arrows correctly. + var dx = (lxy.x - hxy.x) * direction, dy = (lxy.y - hxy.y) * direction; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + // if loc = 0, then tail midpoint should be flush with the element, or, if direction == -1, hxy should be. + if (self.loc == 0) { + var lxy = connector.pointOnPath(self.loc); + var tailMid = foldback > 1 ? cxy : { + x:tail[0].x + ((tail[1].x - tail[0].x) / 2), + y:tail[0].y + ((tail[1].y - tail[0].y) / 2) + }; + var dx = (lxy.x - tailMid.x) * direction, dy = (lxy.y - tailMid.y) * direction; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x); + var maxx = Math.max(hxy.x, tail[0].x, tail[1].x); + var miny = Math.min(hxy.y, tail[0].y, tail[1].y); + var maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + var d = { hxy:hxy, tail:tail, cxy:cxy }, + strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle, + fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle, + lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth; + + self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions); + + return [ minx, maxx, miny, maxy]; + }; + }; + + /** + * Class: Overlays.PlainArrow + * + * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + /** + * Class: Overlays.Diamond + * + * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40; + var p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + + + /** + * Class: Overlays.Label + * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb + * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter + * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it + * puts on the Label's 'style' attribute, so the end result is the same. + */ + /** + * Function: Constructor + * + * Parameters: + * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes + * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle. + * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * labelStyle - (deprecated) js object containing style instructions for the label. defaults to jsPlumb.Defaults.LabelStyle. + * borderWidth - (deprecated) width of a border to paint. defaults to zero. + * borderStyle - (deprecated) strokeStyle to use when painting the border, if necessary. + * + */ + jsPlumb.Overlays.Label = function(params) { + jsPlumb.DOMElementComponent.apply(this, arguments); + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.labelStyle.font = this.labelStyle.font || "12px sans-serif"; + this.label = params.label; + this.connection = params.connection; + this.id = params.id; + var self = this; + var labelWidth = null, labelHeight = null, labelText = null, labelPadding = null; + this.location = params.location || 0.5; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var initialised = false, + labelText = null, + div = document.createElement("div"); + div.style["position"] = "absolute"; + div.style["textAlign"] = "center"; + div.style["cursor"] = "pointer"; + div.style["font"] = self.labelStyle.font; + div.style["color"] = self.labelStyle.color || "black"; + if (self.labelStyle.fillStyle) div.style["background"] = self.labelStyle.fillStyle;//_convertStyle(self.labelStyle.fillStyle, true); + if (self.labelStyle.borderWidth > 0) { + var dStyle = self.labelStyle.borderStyle ? self.labelStyle.borderStyle/*_convertStyle(self.labelStyle.borderStyle, true)*/ : "black"; + div.style["border"] = self.labelStyle.borderWidth + "px solid " + dStyle; + } + if (self.labelStyle.padding) div.style["padding"] = self.labelStyle.padding; + + var clazz = params["_jsPlumb"].overlayClass + " " + + (self.labelStyle.cssClass ? self.labelStyle.cssClass : + params.cssClass ? params.cssClass : ""); + + div.className = clazz; + + jsPlumb.appendElement(div, params.connection.parent); + jsPlumb.getId(div); + self.attachListeners(div, self); + this.paint = function(connector, d, connectorDimensions) { + if (!initialised) { + connector.appendDisplayElement(div); + self.attachListeners(div, connector); + initialised = true; + } + div.style.left = (connectorDimensions[0] + d.minx) + "px"; + div.style.top = (connectorDimensions[1] + d.miny) + "px"; + }; + + this.getTextDimensions = function(connector) { + labelText = typeof self.label == 'function' ? self.label(self) : self.label; + div.innerHTML = labelText.replace(/\r\n/g, "
"); + var de = jsPlumb.CurrentLibrary.getElementObject(div), + s = jsPlumb.CurrentLibrary.getSize(de); + return {width:s[0], height:s[1]}; + }; + + this.computeMaxSize = function(connector) { + var td = self.getTextDimensions(connector); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + var td = self.getTextDimensions(connector); + if (td.width != null) { + var cxy = connector.pointOnPath(self.location); + + var minx = cxy.x - (td.width / 2); + var miny = cxy.y - (td.height / 2); + + self.paint(connector, { + minx:minx, + miny:miny, + td:td, + cxy:cxy + }, connectorDimensions); + + return [minx, minx+td.width, miny, miny+td.height]; + } + else return [0,0,0,0]; + }; + }; + + // ********************************* END OF OVERLAY DEFINITIONS *********************************************************************** + + // ********************************* OVERLAY CANVAS RENDERERS*********************************************************************** + + // ********************************* END OF OVERLAY CANVAS RENDERERS *********************************************************************** +})(); \ No newline at end of file diff --git a/archive/1.3.1/jsPlumb-renderers-canvas-1.3.1-RC1.js b/archive/1.3.1/jsPlumb-renderers-canvas-1.3.1-RC1.js new file mode 100644 index 000000000..b1b16d43e --- /dev/null +++ b/archive/1.3.1/jsPlumb-renderers-canvas-1.3.1-RC1.js @@ -0,0 +1,441 @@ +;(function() { + +// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS ******************************************************************* + + // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. + var _connectionBeingDragged = null, + _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }, + _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }, + _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }, + _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, + _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }, + _setOffset = function(el, o) { jsPlumb.CurrentLibrary.setOffset(el, o); }; + + /* + * Class:CanvasMouseAdapter + * Provides support for mouse events on canvases. + */ + var CanvasMouseAdapter = function() { + var self = this; + self.overlayPlacements = []; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + jsPlumb.EventGenerator.apply(this, arguments); + /** + * returns whether or not the given event is ojver a painted area of the canvas. + */ + this._over = function(e) { + var o = _getOffset(_getElementObject(self.canvas)), + pageXY = _pageXY(e), + x = pageXY[0] - o.left, y = pageXY[1] - o.top; + if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + return false; + }; + + var _mouseover = false; + var _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false; + var _nullSafeHasClass = function(el, clazz) { + return el != null && _hasClass(el, clazz); + }; + this.mousemove = function(e) { + var pageXY = _pageXY(e), clientXY = _clientXY(e), + ee = document.elementFromPoint(clientXY[0], clientXY[1]), + eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); + var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); + if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.fire("mouseenter", self, e); + return true; + } + // TODO here there is a remote chance that the overlay the mouse moved onto + // is actually not an overlay for the current component. a more thorough check would + // be to ensure the overlay belonged to the current component. + else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { + _mouseover = false; + self.fire("mouseexit", self, e); + } + self.fire("mousemove", self, e); + }; + + this.click = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); + self.fire("mousedown", self, e); + } + }; + + this.mouseup = function(e) { + //if (self == _connectionBeingDragged) _connectionBeingDragged = null; + _mouseDown = false; + self.fire("mouseup", self, e); + }; + }; + + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + jsPlumb.appendElement(canvas, params.parent); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + params["_jsPlumb"].getId(canvas, params.uuid); + + return canvas; + }; + + /** + * Class:CanvasConnector + * Superclass for Canvas Connector renderers. + */ + var CanvasConnector = jsPlumb.CanvasConnector = function(params) { + + CanvasMouseAdapter.apply(this, arguments); + + var _paintOneStyle = function(dim, aStyle) { + self.ctx.save(); + jsPlumb.extend(self.ctx, aStyle); + if (aStyle.gradient) { + var g = self.createGradient(dim, self.ctx); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + self.ctx.strokeStyle = g; + } + self._paint(dim); + self.ctx.restore(); + }; + + var self = this, + clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent + }); + self.ctx = self.canvas.getContext("2d"); + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + self.paint = function(dim, style) { + if (style != null) { + + jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); + + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + _paintOneStyle(dim, outlineStyle); + } + _paintOneStyle(dim, style); + } + }; + }; + + /** + * Class:CanvasEndpoint + * Superclass for Canvas Endpoint renderers. + */ + var CanvasEndpoint = function(params) { + var self = this; + CanvasMouseAdapter.apply(this, arguments); + var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent + }); + self.ctx = self.canvas.getContext("2d"); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + // _paintOneStyle(d, outlineStyle); + } + + self._paint.apply(this, arguments); + }; + }; + + jsPlumb.Endpoints.canvas.Dot = function(params) { + var self = this; + jsPlumb.Endpoints.Dot.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }; + var calculateAdjustments = function(gradient) { + var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius; + gradient.offset && (offsetAdjustment = parseValue(gradient.offset)); + gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius)); + return [offsetAdjustment, innerRadius]; + }; + this._paint = function(d, style, anchor) { + if (style != null) { + var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(); + jsPlumb.extend(ctx, style); + if (style.gradient) { + var adjustments = calculateAdjustments(style.gradient), + yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0], + xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0], + g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + ctx.beginPath(); + ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + } + }; + }; + + jsPlumb.Endpoints.canvas.Rectangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) { + + var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(); + jsPlumb.extend(ctx, style); + + /* canvas gradient */ + if (style.gradient) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, d[2], d[3]); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + jsPlumb.Endpoints.canvas.Triangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Triangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) + { + var width = d[2], height = d[3], x = d[0], y = d[1]; + + var ctx = self.canvas.getContext('2d'); + var offsetX = 0, offsetY = 0, angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = style.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + /* + * Canvas Image Endpoint: uses the default version, which creates an tag. + */ + jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image; + + /* + * Blank endpoint in all renderers is just the default Blank endpoint. + */ + jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank; + + /* + * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Bezier = function() { + var self = this; + jsPlumb.Connectors.Bezier.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO i doubt this handles the case that source and target are swapped. + this.createGradient = function(dim, ctx, swap) { + return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + /* + * Canvas straight line Connector. Draws a straight line onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Straight = function() { + var self = this; + jsPlumb.Connectors.Straight.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO this does not handle the case that src and target are swapped. + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + jsPlumb.Connectors.canvas.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + +// ********************************* END OF CANVAS RENDERERS ******************************************************************* + + jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label; + + /** + * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. + */ + var CanvasOverlay = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }; + + var AbstractCanvasArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + CanvasOverlay.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + var ctx = connector.ctx; + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(d.hxy.x, d.hxy.y); + ctx.lineTo(d.tail[0].x, d.tail[0].y); + ctx.lineTo(d.cxy.x, d.cxy.y); + ctx.lineTo(d.tail[1].x, d.tail[1].y); + ctx.lineTo(d.hxy.x, d.hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + }; + + jsPlumb.Overlays.canvas.Arrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.canvas.PlainArrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.canvas.Diamond = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.1/jsPlumb-renderers-svg-1.3.1-RC1.js b/archive/1.3.1/jsPlumb-renderers-svg-1.3.1-RC1.js new file mode 100644 index 000000000..4ae58fdda --- /dev/null +++ b/archive/1.3.1/jsPlumb-renderers-svg-1.3.1-RC1.js @@ -0,0 +1,382 @@ +/** + * SVG support for jsPlumb. + * + * things to investigate: + * + * gradients: https://developer.mozilla.org/en/svg_in_html_introduction + * css:http://tutorials.jenkov.com/svg/svg-and-css.html + * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath + * pointer events: https://developer.mozilla.org/en/css/pointer-events + * + */ +;(function() { + + var svgAttributeMap = { + "stroke-linejoin":"stroke-linejoin", + "joinstyle":"stroke-linejoin", + "stroke-dashoffset":"stroke-dashoffset" + }; + + var ns = { + svg:"http://www.w3.org/2000/svg", + xhtml:"http://www.w3.org/1999/xhtml" + }, + _attr = function(node, attributes) { + for (var i in attributes) + node.setAttribute(i, "" + attributes[i]); + }, + _node = function(name, attributes) { + var n = document.createElementNS(ns.svg, name); + attributes = attributes || {}; + attributes["version"] = "1.1"; + attributes["xmnls"] = ns.xhtml; + _attr(n, attributes); + return n; + }, + _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; }, + _convertStyle = function(s, ignoreAlpha) { + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + + return o; + }, + _clearGradient = function(parent) { + for (var i = 0; i < parent.childNodes.length; i++) { + if (parent.childNodes[i].tagName == "linearGradient" || parent.childNodes[i].tagName == "radialGradient") + parent.removeChild(parent.childNodes[i]); + } + }, + _updateGradient = function(parent, node, style, dimensions) { + var id = "jsplumb_gradient_" + (new Date()).getTime(); + // first clear out any existing gradient + _clearGradient(parent); + // this checks for an 'offset' property in the gradient, and in the absence of it, assumes + // we want a linear gradient. if it's there, we create a radial gradient. + // it is possible that a more explicit means of defining the gradient type would be + // better. relying on 'offset' means that we can never have a radial gradient that uses + // some default offset, for instance. + if (!style.gradient.offset) { + var g = _node("linearGradient", {id:id}); + parent.appendChild(g); + } + else { + var g = _node("radialGradient", { + id:id + }); + parent.appendChild(g); + } + + // the svg radial gradient seems to treat stops in the reverse + // order to how canvas does it. so we want to keep all the maths the same, but + // iterate the actual style declarations in reverse order, if the x indexes are not in order. + for (var i = 0; i < style.gradient.stops.length; i++) { + // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of + // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments. + // so, not too concerned about leaving this in for now. + var styleToUse = i; + if (dimensions.length == 8) + styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i; + else + styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i; + var stopColor = _convertStyle(style.gradient.stops[styleToUse][1], true); + var s = _node("stop", {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor}); + g.appendChild(s); + } + var applyGradientTo = style.strokeStyle ? "stroke" : "fill"; + node.setAttribute("style", applyGradientTo + ":url(#" + id + ")"); + }, + _applyStyles = function(parent, node, style, dimensions) { + + if (style.gradient) { + _updateGradient(parent, node, style, dimensions); + } + else { + // make sure we clear any existing gradient + _clearGradient(parent); + node.setAttribute("style", ""); + } + + node.setAttribute("fill", style.fillStyle ? _convertStyle(style.fillStyle, true) : "none"); + node.setAttribute("stroke", style.strokeStyle ? _convertStyle(style.strokeStyle, true) : "none"); + if (style.lineWidth) { + node.setAttribute("stroke-width", style.lineWidth); + } + + // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like + // the syntax in VML but is actually kind of nasty: values are given in the pixel + // coordinate space, whereas in VML they are multiples of the width of the stroked + // line, which makes a lot more sense. for that reason, jsPlumb is supporting both + // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from + // VML, which will be the preferred method. the code below this converts a dashstyle + // attribute given in terms of stroke width into a pixel representation, by using the + // stroke's lineWidth. + if(style["stroke-dasharray"]) { + node.setAttribute("stroke-dasharray", style["stroke-dasharray"]); + } + if (style["dashstyle"] && style["lineWidth"]) { + var sep = style["dashstyle"].indexOf(",") == -1 ? " " : ",", + parts = style["dashstyle"].split(sep), + styleToUse = ""; + parts.forEach(function(p) { + styleToUse += (Math.floor(p * style.lineWidth) + sep); + }); + node.setAttribute("stroke-dasharray", styleToUse); + } + + // extra attributes such as join type, dash offset. + for (var i in svgAttributeMap) { + if (style[i]) { + node.setAttribute(svgAttributeMap[i], style[i]); + } + } + }, + _decodeFont = function(f) { + var r = /([0-9].)(p[xt])\s(.*)/; + var bits = f.match(r); + return {size:bits[1] + bits[2], font:bits[3]}; + }; + + /* + * Base class for SVG components. + */ + var SvgComponent = function(cssClass, originalArgs, pointerEventsSpec) { + var self = this; + pointerEventsSpec = pointerEventsSpec || "all"; + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + self.canvas = null, self.path = null, self.svg = null; + + this.setHover = function() { }; + + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.sizeCanvas(self.canvas,0,0,1,1); + + var clazz = cssClass + " " + (originalArgs[0].cssClass || ""); + + self.svg = _node("svg", { + "style":"", + "width":0, + "height":0, + "pointer-events":pointerEventsSpec, + "class": clazz + }); + + jsPlumb.appendElement(self.canvas, originalArgs[0]["parent"]); + self.canvas.appendChild(self.svg); + + // TODO this displayElement stuff is common between all components, across all + // renderers. would be best moved to jsPlumbUIComponent. + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + this.paint = function(d, style, anchor) { + if (style != null) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + _attr(self.svg, { + "style":_pos([0,0,d[2], d[3]]), + "width": d[2], + "height": d[3] + }); + self._paint.apply(this, arguments); + } + }; + }; + + /* + * Base class for SVG connectors. + */ + var SvgConnector = function(params) { + var self = this; + SvgComponent.apply(this, [ params["_jsPlumb"].connectorClass, arguments, "none" ]); + this._paint = function(d, style) { + var p = self.getPath(d), a = { "d":p }, outlineStyle = null; + a["pointer-events"] = "all"; + + // outline style. actually means drawing an svg object underneath the main one. + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + outlineStyle = { + strokeStyle:_convertStyle(style.outlineColor), + lineWidth:outlineStrokeWidth + }; + + if (self.bgPath == null) { + self.bgPath = _node("path", a); + self.svg.appendChild(self.bgPath); + self.attachListeners(self.bgPath, self); + } + else { + _attr(self.bgPath, a); + } + + _applyStyles(self.svg, self.bgPath, outlineStyle, d); + } + + + if (self.path == null) { + self.path = _node("path", a); + self.svg.appendChild(self.path); + self.attachListeners(self.path, self); + } + else { + _attr(self.path, a); + } + + _applyStyles(self.svg, self.path, style, d); + }; + }; + + /* + * SVG Bezier Connector + */ + jsPlumb.Connectors.svg.Bezier = function(params) { + jsPlumb.Connectors.Bezier.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]; }; + }; + + /* + * SVG straight line Connector + */ + jsPlumb.Connectors.svg.Straight = function(params) { + jsPlumb.Connectors.Straight.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; }; + }; + + jsPlumb.Connectors.svg.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "M " + dimensions[4] + "," + dimensions[5]; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)]; + } + // finally draw a line to the end + p = p + " " + dimensions[6] + "," + dimensions[7]; + return p; + }; + }; + + /* + * Base class for SVG endpoints. + */ + var SvgEndpoint = function(params) { + var self = this; + SvgComponent.apply(this, [ params["_jsPlumb"].endpointClass, arguments, "all" ]); + this._paint = function(d, style) { + var s = jsPlumb.extend({}, style); + if (s.outlineColor) { + s.strokeWidth = s.outlineWidth; + s.strokeStyle = _convertStyle(s.outlineColor, true); + } + + if (self.node == null) { + self.node = self.makeNode(d, s); + self.svg.appendChild(self.node); + self.attachListeners(self.node, self); + } + _applyStyles(self.svg, self.node, s, d); + _pos(self.node, d); + }; + }; + + /* + * SVG Dot Endpoint + */ + jsPlumb.Endpoints.svg.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("circle", { + "cx" : d[2] / 2, + "cy" : d[3] / 2, + "r" : d[2] / 2 + }); + }; + }; + + /* + * SVG Rectangle Endpoint + */ + jsPlumb.Endpoints.svg.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("rect", { + "width":d[2], + "height":d[3] + }); + }; + }; + + /* + * SVG Image Endpoint is the default image endpoint. + */ + jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image; + /* + * Blank endpoint in svg renderer is the default Blank endpoint. + */ + jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank; + /* + * Label endpoint in svg renderer is the default Label endpoint. + */ + jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label; + + + var AbstractSvgArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + var self = this, path =null; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + } + + _attr(path, { + "d" : makePath(d), + stroke : strokeStyle ? strokeStyle : null, + fill : fillStyle ? fillStyle : null + }); + }; + var makePath = function(d) { + return "M" + d.hxy.x + "," + d.hxy.y + + " L" + d.tail[0].x + "," + d.tail[0].y + + " L" + d.cxy.x + "," + d.cxy.y + + " L" + d.tail[1].x + "," + d.tail[1].y + + " L" + d.hxy.x + "," + d.hxy.y; + }; + }; + + jsPlumb.Overlays.svg.Arrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.svg.PlainArrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.svg.Diamond = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.1/jsPlumb-renderers-vml-1.3.1-RC1.js b/archive/1.3.1/jsPlumb-renderers-vml-1.3.1-RC1.js new file mode 100644 index 000000000..ab582b030 --- /dev/null +++ b/archive/1.3.1/jsPlumb-renderers-vml-1.3.1-RC1.js @@ -0,0 +1,351 @@ +;(function() { + + // http://ajaxian.com/archives/the-vml-changes-in-ie-8 + // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/ + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + + var vmlAttributeMap = { + "stroke-linejoin":"joinstyle", + "joinstyle":"joinstyle", + "endcap":"endcap", + "miterlimit":"miterlimit" + }; + + if (document.createStyleSheet) { + + // this is the style rule for IE7/6: it uses a CSS class, tidy. + document.createStyleSheet().addRule(".jsplumb_vml", "behavior:url(#default#VML);position:absolute;"); + + // these are for VML in IE8. you have to explicitly call out which elements + // you're going to expect to support VML! + // + // try to avoid IE8. it is recommended you set X-UA-Compatible="IE=7" if you can. + // + document.createStyleSheet().addRule("jsplumb\\:textbox", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:oval", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:rect", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:stroke", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:shape", "behavior:url(#default#VML);position:absolute;"); + + // in this page it is also mentioned that IE requires the extra arg to the namespace + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either. + // var iev = document.documentMode; + //if (!iev || iev < 8) + document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); + //else + // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML"); + } + + var scale = 1000, + _atts = function(o, atts) { + for (var i in atts) { + // IE8 fix: setattribute does not work after an element has been added to the dom! + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + //o.setAttribute(i, atts[i]); + o[i] = atts[i]; + } + }, + _node = function(name, d, atts) { + atts = atts || {}; + var o = document.createElement("jsplumb:" + name); + o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml"; + _pos(o, d); + _atts(o, atts); + return o; + }, + _pos = function(o,d) { + o.style.left = d[0] + "px"; + o.style.top = d[1] + "px"; + o.style.width= d[2] + "px"; + o.style.height= d[3] + "px"; + o.style.position = "absolute"; + }, + _conv = function(v) { + return Math.floor(v * scale); + }, + _convertStyle = function(s, ignoreAlpha) { + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + + return o; + }, + _applyStyles = function(node, style, component) { + var styleToWrite = {}; + if (style.strokeStyle) { + styleToWrite["stroked"] = "true"; + styleToWrite["strokecolor"] =_convertStyle(style.strokeStyle, true); + styleToWrite["strokeweight"] = style.lineWidth + "px"; + } + else styleToWrite["stroked"] = "false"; + + if (style.fillStyle) { + styleToWrite["filled"] = "true"; + styleToWrite["fillcolor"] = _convertStyle(style.fillStyle, true); + } + else styleToWrite["filled"] = "false"; + + if(style["dashstyle"]) { + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = style["dashstyle"]; + } + else if (style["stroke-dasharray"] && style["lineWidth"]) { + var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",", + parts = style["stroke-dasharray"].split(sep), + styleToUse = ""; + for(var i = 0; i < parts.length; i++) { + styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep); + } + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = styleToUse; + } + + _atts(node, styleToWrite); + }, + /* + * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. + */ + VmlComponent = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }, + /* + * Base class for Vml connectors. extends VmlComponent. + */ + VmlConnector = function(params) { + var self = this; + self.strokeNode = null; + self.canvas = null; + VmlComponent.apply(this, arguments); + clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : ""); + this.paint = function(d, style, anchor) { + if (style != null) { + var path = self.getPath(d), p = { "path":path }; + + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + outlineStyle = { + strokeStyle:_convertStyle(style.outlineColor), + lineWidth:outlineStrokeWidth + }; + + if (self.bgCanvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.bgCanvas = _node("shape", d, p); + jsPlumb.appendElement(self.bgCanvas, params.parent); + _pos(self.bgCanvas, d); + displayElements.push(self.bgCanvas); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.bgCanvas, d); + _atts(self.bgCanvas, p); + } + + _applyStyles(self.bgCanvas, outlineStyle, self); + } + + if (self.canvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.canvas = _node("shape", d, p); + jsPlumb.appendElement(self.canvas, params.parent); + displayElements.push(self.canvas); + + self.attachListeners(self.canvas, self); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.canvas, d); + _atts(self.canvas, p); + } + + _applyStyles(self.canvas, style, self); + } + }; + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + self.canvas.parentNode.appendChild(el); + displayElements.push(el); + }; + }, + /* + * + * Base class for Vml Endpoints. extends VmlComponent. + * + */ + VmlEndpoint = function(params) { + VmlComponent.apply(this, arguments); + var vml = null, self = this; + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function(d, style, anchor) { + var p = { }; + + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (vml == null) { + p["class"] = jsPlumb.endpointClass; + vml = self.getVml([0,0, d[2], d[3]], p, anchor); + self.canvas.appendChild(vml); + self.attachListeners(vml, self); + } + else { + //p["coordsize"] = "1,1";//(d[2] * scale) + "," + (d[3] * scale); again, unsure. + _pos(vml, [0,0, d[2], d[3]]); + _atts(vml, p); + } + + _applyStyles(vml, style); + }; + }; + + jsPlumb.Connectors.vml.Bezier = function() { + jsPlumb.Connectors.Bezier.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Straight = function() { + jsPlumb.Connectors.Straight.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Flowchart = function() { + jsPlumb.Connectors.Flowchart.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l"; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]); + } + // finally draw a line to the end + p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e"; + return p; + }; + }; + + jsPlumb.Endpoints.vml.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("oval", d, atts); }; + }; + + jsPlumb.Endpoints.vml.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("rect", d, atts); }; + }; + + /* + * VML Image Endpoint is the same as the default image endpoint. + */ + jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image; + + /** + * placeholder for Blank endpoint in vml renderer. + */ + jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank; + + /** + * VML Label renderer. uses the default label renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label; + + var AbstractVmlArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + VmlComponent.apply(this, arguments); + var self = this, canvas = null, path =null; + var getPath = function(d, connectorDimensions) { + return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) + + " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + + " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + + " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + + " x e"; + }; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) { + var p = {}; + if (strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] =_convertStyle(strokeStyle, true); + } + if (lineWidth) p["strokeweight"] = lineWidth + "px"; + if (fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = fillStyle; + } + var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + w = Math.abs(xmax - xmin), + h = Math.abs(ymax - ymin), + dim = [xmin, ymin, w, h]; + + // for VML, we create overlays using shapes that have the same dimensions and + // coordsize as their connector - overlays calculate themselves relative to the + // connector (it's how it's been done since the original canvas implementation, because + // for canvas that makes sense). + p["path"] = getPath(d, connectorDimensions); + p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale); + + dim[0] = connectorDimensions[0]; + dim[1] = connectorDimensions[1]; + dim[2] = connectorDimensions[2]; + dim[3] = connectorDimensions[3]; + + if (canvas == null) { + //p["class"] = jsPlumb.overlayClass; // TODO currentInstance? + canvas = _node("shape", dim, p); + connector.appendDisplayElement(canvas); + self.attachListeners(canvas, connector); + } + else { + _pos(canvas, dim); + _atts(canvas, p); + } + }; + }; + + jsPlumb.Overlays.vml.Arrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.vml.PlainArrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.vml.Diamond = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.1/mootools.jsPlumb-1.3.1-RC1.js b/archive/1.3.1/mootools.jsPlumb-1.3.1-RC1.js new file mode 100644 index 000000000..5f292deaa --- /dev/null +++ b/archive/1.3.1/mootools.jsPlumb-1.3.1-RC1.js @@ -0,0 +1,341 @@ +/* + * mootools.jsPlumb 1.3.0-RC1 + * + * MooTools specific functionality for jsPlumb. + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + * NOTE: for production usage you should use mootools.jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, + * in a minified file. + * + * Dual licensed under MIT and GPL2. + * + */ + +;(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function(now) { + this.parent(now); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }; + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + var _getElementObject = function(el) { + return $(el); + }; + + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + m.start(properties); + }, + + appendElement : function(child, parent) { + _getElementObject(parent).grab(child); + }, + + bind : function(el, event, callback) { + el = _getElementObject(el); + el.addEvent(event, callback); + }, + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.event.clientX, eventObject.event.clientY]; + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el); + var drags = _draggablesById[id]; + return drags[0].scope; + }, + + getElementObject : _getElementObject, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + getPageXY : function(eventObject) { + return [eventObject.event.pageX, eventObject.event.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).getParent(); + }, + + getScrollLeft : function(el) { + return null; + }, + + getScrollTop : function(el) { + return null; + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[0], + p = jsPlumb.CurrentLibrary.getElementObject(ui).getPosition(); + return { left:p.x, top:p.y }; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options) { + var id = jsPlumb.getId(el); + var drag = _draggablesById[id]; + if (!drag) { + var originalZIndex = 0, originalCursor = null; + var dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + options['onStart'] = jsPlumb.wrap(options['onStart'], function() + { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() + { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + }); + + // DROPPABLES: + var scope = options['scope'] || jsPlumb.Defaults.Scope; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop'); + } + }; + + drag = new Drag.Move(el, options); + drag.scope = scope; + //console.log("drag scope initialized to ", scope); + _add(_draggablesByScope, scope, drag); + _add(_draggablesById, el.get("id"), drag); + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + initDroppable : function(el, options) { + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _add(_droppables, scope, el); + var id = jsPlumb.getId(el); + _droppableOptions[id] = options; + var filterFunc = function(entry) { return entry.element != el; }; + var draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + isAlreadyDraggable : function(el) { + return _draggablesById[jsPlumb.getId(el)] != null; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + return (typeof Drag != undefined && typeof Drag.Move != undefined); + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + _getElementObject(element).dispose(); // ?? + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + setDragScope : function(el, scope) { + var drag = _draggablesById[el.get("id")]; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + drag[0].droppables = droppables; + }, + + setOffset : function(el, o) { + _getElementObject(el).setPosition({x:o.left, y:o.top}); + } + }; + + window.addEvent('domready', jsPlumb.init); +})(); diff --git a/archive/1.3.1/mootools.jsPlumb-1.3.1-all-min.js b/archive/1.3.1/mootools.jsPlumb-1.3.1-all-min.js new file mode 100644 index 000000000..5394d67dd --- /dev/null +++ b/archive/1.3.1/mootools.jsPlumb-1.3.1-all-min.js @@ -0,0 +1 @@ +(function(){var o=!!!document.createElement("canvas").getContext;var s=!!document.createElement("canvas").getContext;var d=!!window.SVGAngle||document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1");var a=!(s|d);var l=function(y,z,w,C){var B=function(F,E){if(F===E){return true}else{if(typeof F=="object"&&typeof E=="object"){var G=true;for(var D in F){if(!B(F[D],E[D])){G=false;break}}for(var D in E){if(!B(E[D],F[D])){G=false;break}}return G}}};for(var A=+w||0,x=y.length;A=0){delete (ax[ay]);ax.splice(ay,1);return true}}}return false},w=function(ay,ax){return J(ay,function(az,aA){ag[aA]=ax;if(i.CurrentLibrary.isDragSupported(az)){i.CurrentLibrary.setDraggable(az,ax)}})},ao=function(ax,ay){V(c(ax,"id"),function(az){az.canvas.style.display=ay})},O=function(ax){return J(ax,function(az,ay){var aA=ag[ay]==null?Y:ag[ay];aA=!aA;ag[ay]=aA;i.CurrentLibrary.setDraggable(az,aA);return aA})},y=function(ax){V(ax,function(az){var ay=("none"==az.canvas.style.display);az.canvas.style.display=ay?"block":"none"})},F=function(aC){var aA=aC.timestamp,ax=aC.recalc,aB=aC.offset,ay=aC.elId;if(!ax){if(aA&&aA===D[ay]){return W[ay]}}if(ax||aB==null){var az=v(ay);if(az!=null){Q[ay]=b(az);W[ay]=n(az);D[ay]=aA}}else{W[ay]=aB}return W[ay]},aw=function(ax,ay){var az=v(ax);var aA=c(az,"id");if(!aA||aA=="undefined"){if(arguments.length==2&&arguments[1]!=undefined){aA=ay}else{aA="jsPlumb_"+ar()}e(az,"id",aA)}return aA},am=function(az,ax,ay){az=az||function(){};ax=ax||function(){};return function(){var aA=null;try{aA=ax.apply(this,arguments)}catch(aB){k(E,"jsPlumb function failed : "+aB)}if(ay==null||(aA!==ay)){try{az.apply(this,arguments)}catch(aB){k(E,"wrapped function failed : "+aB)}}return aA}};this.connectorClass="_jsPlumb_connector";this.endpointClass="_jsPlumb_endpoint";this.overlayClass="_jsPlumb_overlay";this.Anchors={};this.Connectors={canvas:{},svg:{},vml:{}};this.Endpoints={canvas:{},svg:{},vml:{}};this.Overlays={canvas:{},svg:{},vml:{}};this.addEndpoint=function(az,aA,aJ){aJ=aJ||{};var ay=i.extend({},aJ);i.extend(ay,aA);ay.endpoint=ay.endpoint||E.Defaults.Endpoint||i.Defaults.Endpoint;ay.paintStyle=ay.paintStyle||E.Defaults.EndpointStyle||i.Defaults.EndpointStyle;az=x(az);var aB=[],aE=az.length&&az.constructor!=String?az:[az];for(var aC=0;aC0?l(aK,aJ)!=-1:true},aB=aE.length>1?{}:[],aH=function(aK,aL){if(aE.length>1){var aJ=aB[aK];if(aJ==null){aJ=[];aB[aK]=aJ}aJ.push(aL)}else{aB.push(aL)}};for(var aA in M){if(ay(aE,aA)){for(var az=0;az=4){ay.orientation=[arguments[2],arguments[3]]}if(arguments.length==6){ay.offsets=[arguments[4],arguments[5]]}}var aD=new ac(ay);aD.clone=function(){return new ac(ay)};return aD};this.makeAnchors=function(ay){var az=[];for(var ax=0;ax0?aG[0]:null;var aC=aG.length>0?0:-1;this.locked=false;var aF=this;var aB=function(aJ,aH,aN,aM,aI){var aL=aM[0]+(aJ.x*aI[0]),aK=aM[1]+(aJ.y*aI[1]);return Math.sqrt(Math.pow(aH-aL,2)+Math.pow(aN-aK,2))};var ax=ay||function(aR,aI,aJ,aK,aH){var aM=aJ[0]+(aK[0]/2),aL=aJ[1]+(aK[1]/2);var aO=-1,aQ=Infinity;for(var aN=0;aNaX){aX=a4}}var a8=this.connector.compute(aZ,ba,this.endpoints[be].anchor,this.endpoints[aW].anchor,aI.paintStyleInUse.lineWidth,aX);aI.connector.paint(a8,aI.paintStyleInUse);for(var a9=0;a9=0){aM.connections.splice(aY,1);if(!a1){var a0=aZ.endpoints[0]==aM?aZ.endpoints[1]:aZ.endpoints[0];a0.detach(aZ,true);if(aZ.endpointToDeleteOnDetach&&aZ.endpointToDeleteOnDetach.connections.length==0){i.deleteEndpoint(aZ.endpointToDeleteOnDetach)}}R(aZ.connector.getDisplayElements(),aZ.parent);N(M,aZ.scope,aZ);if(!a1){ad(aZ)}}};this.detachAll=function(){while(aM.connections.length>0){aM.detach(aM.connections[0])}};this.detachFrom=function(aZ){var a0=[];for(var aY=0;aY=0){aM.connections.splice(aY,1)}};this.getElement=function(){return aL};this.getUuid=function(){return aH};this.makeInPlaceCopy=function(){return an({anchor:aM.anchor,source:aL,paintStyle:this.paintStyle,endpoint:aK})};this.isConnectedTo=function(a0){var aZ=false;if(a0){for(var aY=0;aY0){var a8=aM.connections[0];var ba=a8.endpoints[0]==aM?1:0;var a3=ba==0?a8.sourceId:a8.targetId;var a7=W[a3],a9=Q[a3];aZ.txy=[a7.left,a7.top];aZ.twh=a9;aZ.tElement=a8.endpoints[ba]}}a4=aM.anchor.compute(aZ)}var a6=aK.compute(a4,aM.anchor.getOrientation(),aM.paintStyleInUse,a2||aM.paintStyleInUse);aK.paint(a6,aM.paintStyleInUse,aM.anchor);aM.timestamp=a5}};this.repaint=this.paint;this.removeConnection=this.detach;if(aX.isSource&&i.CurrentLibrary.isDragSupported(aL)){var aR=null,aN=null,aQ=null,ax=false,aA=null;var aC=function(){aQ=aM.connectorSelector();if(aM.isFull()&&!aI){return false}F({elId:aE});aB=aM.makeInPlaceCopy();aB.paint();aR=document.createElement("div");aR.style.position="absolute";var a5=v(aR);S(aR,aM.parent);var aY=aw(a5);var a6=v(aB.canvas),a4=i.CurrentLibrary.getOffset(a6),a0=aB.canvas.offsetParent.tagName.toLowerCase()==="body"?{left:0,top:0}:n(aB.canvas.offsetParent);i.CurrentLibrary.setOffset(aR,{left:a4.left-a0.left,top:a4.top-a0.top});F({elId:aY});e(v(aM.canvas),"dragId",aY);e(v(aM.canvas),"elId",aE);var a7=new I({reference:aM.anchor,referenceCanvas:aM.canvas});aV=an({paintStyle:aM.paintStyle,endpoint:aK,anchor:a7,source:a5});if(aQ==null){aM.anchor.locked=true;aQ=au({sourceEndpoint:aM,targetEndpoint:aV,source:v(aL),target:v(aR),anchors:[aM.anchor,a7],paintStyle:aX.connectorStyle,hoverPaintStyle:aX.connectorHoverStyle,connector:aX.connector,overlays:aX.connectorOverlays});aQ.connector.setHover(false)}else{ax=true;aQ.connector.setHover(false);aD(v(aB.canvas));var aZ=aQ.sourceId==aE?0:1;aQ.floatingAnchorIndex=aZ;aM.detachFromConnection(aQ);var a3=v(aM.canvas);var a2=i.CurrentLibrary.getDragScope(a3);e(a3,"originalScope",a2);var a1="scope_"+(new Date()).getTime();if(aZ==0){aA=[aQ.source,aQ.sourceId,aU,a2];aQ.source=v(aR);aQ.sourceId=aY}else{aA=[aQ.target,aQ.targetId,aU,a2];aQ.target=v(aR);aQ.targetId=aY}i.CurrentLibrary.setDragScope(aU,a1);aQ.endpoints[aZ==0?1:0].anchor.locked=true;aQ.suspendedEndpoint=aQ.endpoints[aZ];aQ.endpoints[aZ]=aV}Z[aY]=aQ;aV.addConnection(aQ);T(ah,aY,aV);E.currentlyDragging=true};var ay=i.CurrentLibrary,aT=aX.dragOptions||{},aO=i.extend({},ay.defaultDragOptions),aP=ay.dragEvents.start,aW=ay.dragEvents.stop,aG=ay.dragEvents.drag;aT=i.extend(aO,aT);aT.scope=aT.scope||aM.scope;aT[aP]=am(aT[aP],aC);aT[aG]=am(aT[aG],function(){var aY=i.CurrentLibrary.getUIPosition(arguments);i.CurrentLibrary.setOffset(aR,aY);at(v(aR),aY)});aT[aW]=am(aT[aW],function(){N(ah,aN,aV);R([aR,aV.canvas],aL);ae(aB.canvas,aL);var aY=aQ.floatingAnchorIndex==null?1:aQ.floatingAnchorIndex;aQ.endpoints[aY==0?1:0].anchor.locked=false;if(aQ.endpoints[aY]==aV){if(ax&&aQ.suspendedEndpoint){if(aY==0){aQ.source=aA[0];aQ.sourceId=aA[1]}else{aQ.target=aA[0];aQ.targetId=aA[1]}i.CurrentLibrary.setDragScope(aA[2],aA[3]);aQ.endpoints[aY]=aQ.suspendedEndpoint;if(aJ){aQ.floatingAnchorIndex=null;aQ.suspendedEndpoint.addConnection(aQ);i.repaint(aA[1])}else{aQ.endpoints[aY==0?1:0].detach(aQ)}}else{R(aQ.connector.getDisplayElements(),aM.parent);aM.detachFromConnection(aQ)}}aM.anchor.locked=false;aM.paint();aQ.setHover(false);aQ.repaint();aQ=null;delete aB;delete ah[aV.elementId];aV=null;delete aV;E.currentlyDragging=false});var aU=v(aM.canvas);i.CurrentLibrary.initDraggable(aU,aT)}var aD=function(a1){if(aX.isTarget&&i.CurrentLibrary.isDropSupported(aL)){var aY=aX.dropOptions||E.Defaults.DropOptions||i.Defaults.DropOptions;aY=i.extend({},aY);aY.scope=aY.scope||aM.scope;var a4=null;var a2=i.CurrentLibrary.dragEvents.drop;var a3=i.CurrentLibrary.dragEvents.over;var aZ=i.CurrentLibrary.dragEvents.out;var a0=function(){var bd=v(i.CurrentLibrary.getDragObject(arguments));var a5=c(bd,"dragId");var a7=c(bd,"elId");var bc=c(bd,"originalScope");if(bc){i.CurrentLibrary.setDragScope(bd,bc)}var a9=Z[a5];var ba=a9.floatingAnchorIndex==null?1:a9.floatingAnchorIndex,bb=ba==0?1:0;if(!aM.isFull()&&!(ba==0&&!aM.isSource)&&!(ba==1&&!aM.isTarget)){if(ba==0){a9.source=aL;a9.sourceId=aE}else{a9.target=aL;a9.targetId=aE}a9.endpoints[ba].detachFromConnection(a9);if(a9.suspendedEndpoint){a9.suspendedEndpoint.detachFromConnection(a9)}a9.endpoints[ba]=aM;aM.addConnection(a9);if(!a9.suspendedEndpoint){T(M,a9.scope,a9);P(aL,aX.draggable,{})}else{var a8=a9.suspendedEndpoint.getElement(),a6=a9.suspendedEndpoint.elementId;E.fire("jsPlumbConnectionDetached",{source:ba==0?a8:a9.source,target:ba==1?a8:a9.target,sourceId:ba==0?a6:a9.sourceId,targetId:ba==1?a6:a9.targetId,sourceEndpoint:ba==0?a9.suspendedEndpoint:a9.endpoints[0],targetEndpoint:ba==1?a9.suspendedEndpoint:a9.endpoints[1],connection:a9})}i.repaint(a7);E.fire("jsPlumbConnection",{source:a9.source,target:a9.target,sourceId:a9.sourceId,targetId:a9.targetId,sourceEndpoint:a9.endpoints[0],targetEndpoint:a9.endpoints[1],connection:a9})}E.currentlyDragging=false;delete Z[a5]};aY[a2]=am(aY[a2],a0);aY[a3]=am(aY[a3],function(){var a6=i.CurrentLibrary.getDragObject(arguments);var a8=c(v(a6),"dragId");var a7=Z[a8];if(a7!=null){var a5=a7.floatingAnchorIndex==null?1:a7.floatingAnchorIndex;a7.endpoints[a5].anchor.over(aM.anchor)}});aY[aZ]=am(aY[aZ],function(){var a6=i.CurrentLibrary.getDragObject(arguments),a8=c(v(a6),"dragId"),a7=Z[a8];if(a7!=null){var a5=a7.floatingAnchorIndex==null?1:a7.floatingAnchorIndex;a7.endpoints[a5].anchor.out()}});i.CurrentLibrary.initDroppable(a1,aY)}};aD(v(aM.canvas));return aM}};var i=window.jsPlumb=new r();i.getInstance=function(x){var w=new r(x);return w};var m=function(w,B,A,z){return function(){return i.makeAnchor(w,B,A,z)}};i.Anchors.TopCenter=m(0.5,0,0,-1);i.Anchors.BottomCenter=m(0.5,1,0,1);i.Anchors.LeftMiddle=m(0,0.5,-1,0);i.Anchors.RightMiddle=m(1,0.5,1,0);i.Anchors.Center=m(0.5,0.5,0,0);i.Anchors.TopRight=m(1,0,0,-1);i.Anchors.BottomRight=m(1,1,0,1);i.Anchors.TopLeft=m(0,0,0,-1);i.Anchors.BottomLeft=m(0,1,0,1);i.Defaults.DynamicAnchors=function(){return i.makeAnchors(["TopCenter","RightMiddle","BottomCenter","LeftMiddle"])};i.Anchors.AutoDefault=function(){return i.makeDynamicAnchor(i.Defaults.DynamicAnchors())}})();(function(){jsPlumb.DOMElementComponent=function(a){jsPlumb.jsPlumbUIComponent.apply(this,arguments);this.mousemove=this.dblclick=this.click=this.mousedown=this.mouseup=function(b){}};jsPlumb.Connectors.Straight=function(){var n=this;var g=null;var c,h,l,k,i,d,m,f,e,b,a;this.compute=function(r,F,B,o,v,q){var E=Math.abs(r[0]-F[0]);var u=Math.abs(r[1]-F[1]);var z=false,s=false;var t=0.45*E,p=0.45*u;E*=1.9;u*=1.9;var C=Math.min(r[0],F[0])-t;var A=Math.min(r[1],F[1])-p;var D=Math.max(2*v,q);if(E0?1:-1;var t=Math.abs(u*Math.sin(d));if(e>a){t=t*-1}var o=Math.abs(u*Math.cos(d));if(f>b){o=o*-1}return{x:s.x+(r*o),y:s.y+(r*t)}};this.perpendicularToPathAt=function(s,t,w){var u=n.pointAlongPathFrom(s,w);var r=n.gradientAtPoint(u.location);var q=Math.atan(-1/r);var v=t/2*Math.sin(q);var o=t/2*Math.cos(q);return[{x:u.x+o,y:u.y+v},{x:u.x-o,y:u.y-v}]}};jsPlumb.Connectors.Bezier=function(e){var o=this;e=e||{};this.majorAnchor=e.curviness||150;this.minorAnchor=10;var h=null;this._findControlPoint=function(z,q,u,x,r){var w=x.getOrientation(),y=r.getOrientation();var t=w[0]!=y[0]||w[1]==y[1];var s=[];var A=o.majorAnchor,v=o.minorAnchor;if(!t){if(w[0]==0){s.push(q[0]l){l=u}if(x<0){d+=x;var y=Math.abs(x);l+=y;n[0]+=y;i+=y;b+=y;m[0]+=y}var G=Math.min(f,a);var E=Math.min(n[1],m[1]);var t=Math.min(G,E);var z=Math.max(f,a);var w=Math.max(n[1],m[1]);var r=Math.max(z,w);if(r>g){g=r}if(t<0){c+=t;var v=Math.abs(t);g+=v;n[1]+=v;f+=v;a+=v;m[1]+=v}if(D&&l=s){q=t;r=(s-l[t][0])/a[t];break}}return{segment:i[q],proportion:r,index:q}};this.compute=function(Q,v,P,r,q,H){i=[];h=[];a=[];g=[];segmentProportionals=[];d=v[0]t[0]?t[0]+((1-x)*r)-y:t[2]+(x*r)+y,y:q==0?t[3]:t[3]>t[1]?t[1]+((1-x)*r)-y:t[3]+(x*r)+y,segmentInfo:v};return w};this.perpendicularToPathAt=function(t,u,z){var v=n.pointAlongPathFrom(t,z);var s=h[v.segmentInfo.index];var r=Math.atan(-1/s);var w=u/2*Math.sin(r);var q=u/2*Math.cos(r);return[{x:v.x+q,y:v.y+w},{x:v.x-q,y:v.y-w}]}};jsPlumb.Endpoints.Dot=function(b){var a=this;b=b||{};this.radius=b.radius||10;this.defaultOffset=0.5*this.radius;this.defaultInnerRadius=this.radius/3;this.compute=function(g,d,i,f){var e=i.radius||a.radius;var c=g[0]-e;var h=g[1]-e;return[c,h,e*2,e*2,e]}};jsPlumb.Endpoints.Rectangle=function(b){var a=this;b=b||{};this.width=b.width||20;this.height=b.height||20;this.compute=function(h,e,k,g){var f=k.width||a.width;var d=k.height||a.height;var c=h[0]-(f/2);var i=h[1]-(d/2);return[c,i,f,d]}};jsPlumb.Endpoints.Image=function(d){jsPlumb.DOMElementComponent.apply(this,arguments);var a=this,c=false;this.img=new Image();a.ready=false;this.img.onload=function(){a.ready=true};this.img.src=d.src||d.url;this.compute=function(g,e,h,f){a.anchorPoint=g;if(a.ready){return[g[0]-a.img.width/2,g[1]-a.img.height/2,a.img.width,a.img.height]}else{return[0,0,0,0]}};a.canvas=document.createElement("img"),c=false;a.canvas.style.margin=0;a.canvas.style.padding=0;a.canvas.style.outline=0;a.canvas.style.position="absolute";a.canvas.className=jsPlumb.endpointClass;jsPlumb.appendElement(a.canvas,d.parent);a.attachListeners(a.canvas,a);var b=function(k,i,g){if(!c){a.canvas.setAttribute("src",a.img.src);c=true}var h=a.img.width,f=a.img.height,e=a.anchorPoint[0]-(h/2),l=a.anchorPoint[1]-(f/2);jsPlumb.sizeCanvas(a.canvas,e,l,h,f)};this.paint=function(g,f,e){if(a.ready){b(g,f,e)}else{window.setTimeout(function(){a.paint(g,f,e)},200)}}};jsPlumb.Endpoints.Blank=function(){jsPlumb.DOMElementComponent.apply(this,arguments);this.compute=function(){return[0,0,0,0]};self.canvas=document.createElement("div");this.paint=function(){}};jsPlumb.Endpoints.Triangle=function(a){a=a||{};a.width=a.width||55;param.height=a.height||55;this.width=a.width;this.height=a.height;this.compute=function(g,d,i,f){var e=i.width||self.width;var c=i.height||self.height;var b=g[0]-(e/2);var h=g[1]-(c/2);return[b,h,e,c]}};jsPlumb.Overlays.Arrow=function(f){f=f||{};var b=this;this.length=f.length||20;this.width=f.width||20;this.id=f.id;this.connection=f.connection;var e=(f.direction||1)<0?-1:1;var c=f.paintStyle||{lineWidth:1};this.loc=f.location==null?0.5:f.location;var a=f.foldback||0.623;var d=function(g,i){if(a==0.5){return g.pointOnPath(i)}else{var h=0.5-a;return g.pointAlongPathFrom(i,e*b.length*h)}};this.computeMaxSize=function(){return b.width*1.5};this.draw=function(k,q,w){var y=k.pointAlongPathFrom(b.loc,e*(b.length/2));var t=k.pointAlongPathFrom(b.loc,-1*e*(b.length/2)),B=t.x,A=t.y;var r=k.perpendicularToPathAt(b.loc,b.width,-1*e*(b.length/2));var i=d(k,b.loc);if(b.loc==1){var h=k.pointOnPath(b.loc);var v=(h.x-y.x)*e,u=(h.y-y.y)*e;i.x+=v;i.y+=u;t.x+=v;t.y+=u;r[0].x+=v;r[0].y+=u;r[1].x+=v;r[1].y+=u;y.x+=v;y.y+=u}if(b.loc==0){var h=k.pointOnPath(b.loc);var s=a>1?i:{x:r[0].x+((r[1].x-r[0].x)/2),y:r[0].y+((r[1].y-r[0].y)/2)};var v=(h.x-s.x)*e,u=(h.y-s.y)*e;i.x+=v;i.y+=u;t.x+=v;t.y+=u;r[0].x+=v;r[0].y+=u;r[1].x+=v;r[1].y+=u;y.x+=v;y.y+=u}var o=Math.min(y.x,r[0].x,r[1].x);var n=Math.max(y.x,r[0].x,r[1].x);var m=Math.min(y.y,r[0].y,r[1].y);var l=Math.max(y.y,r[0].y,r[1].y);var z={hxy:y,tail:r,cxy:i},x=c.strokeStyle||q.strokeStyle,p=c.fillStyle||q.strokeStyle,g=c.lineWidth||q.lineWidth;b.paint(k,z,g,x,p,w);return[o,n,m,l]}};jsPlumb.Overlays.PlainArrow=function(b){b=b||{};var a=jsPlumb.extend(b,{foldback:1});jsPlumb.Overlays.Arrow.call(this,a)};jsPlumb.Overlays.Diamond=function(c){c=c||{};var a=c.length||40;var b=jsPlumb.extend(c,{length:a/2,foldback:2});jsPlumb.Overlays.Arrow.call(this,b)};jsPlumb.Overlays.Label=function(d){jsPlumb.DOMElementComponent.apply(this,arguments);this.labelStyle=d.labelStyle||jsPlumb.Defaults.LabelStyle;this.labelStyle.font=this.labelStyle.font||"12px sans-serif";this.label=d.label;this.connection=d.connection;this.id=d.id;var k=this;var h=null,e=null,c=null,b=null;this.location=d.location||0.5;this.cachedDimensions=null;var i=false,c=null,a=document.createElement("div");a.style.position="absolute";a.style.textAlign="center";a.style.cursor="pointer";a.style.font=k.labelStyle.font;a.style.color=k.labelStyle.color||"black";if(k.labelStyle.fillStyle){a.style.background=k.labelStyle.fillStyle}if(k.labelStyle.borderWidth>0){var g=k.labelStyle.borderStyle?k.labelStyle.borderStyle:"black";a.style.border=k.labelStyle.borderWidth+"px solid "+g}if(k.labelStyle.padding){a.style.padding=k.labelStyle.padding}var f=d._jsPlumb.overlayClass+" "+(k.labelStyle.cssClass?k.labelStyle.cssClass:d.cssClass?d.cssClass:"");a.className=f;jsPlumb.appendElement(a,d.connection.parent);jsPlumb.getId(a);k.attachListeners(a,k);this.paint=function(l,n,m){if(!i){l.appendDisplayElement(a);k.attachListeners(a,l);i=true}a.style.left=(m[0]+n.minx)+"px";a.style.top=(m[1]+n.miny)+"px"};this.getTextDimensions=function(l){c=typeof k.label=="function"?k.label(k):k.label;a.innerHTML=c.replace(/\r\n/g,"
");var n=jsPlumb.CurrentLibrary.getElementObject(a),m=jsPlumb.CurrentLibrary.getSize(n);return{width:m[0],height:m[1]}};this.computeMaxSize=function(l){var m=k.getTextDimensions(l);return m.width?Math.max(m.width,m.height)*1.5:0};this.draw=function(n,p,o){var r=k.getTextDimensions(n);if(r.width!=null){var q=n.pointOnPath(k.location);var m=q.x-(r.width/2);var l=q.y-(r.height/2);k.paint(n,{minx:m,miny:l,td:r,cxy:q},o);return[m,m+r.width,l,l+r.height]}else{return[0,0,0,0]}}}})();(function(){var h={"stroke-linejoin":"joinstyle",joinstyle:"joinstyle",endcap:"endcap",miterlimit:"miterlimit"};if(document.createStyleSheet){document.createStyleSheet().addRule(".jsplumb_vml","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:textbox","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:oval","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:rect","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:stroke","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:shape","behavior:url(#default#VML);position:absolute;");document.namespaces.add("jsplumb","urn:schemas-microsoft-com:vml")}var c=1000,d=function(p,q){for(var n in q){p[n]=q[n]}},m=function(n,q,r){r=r||{};var p=document.createElement("jsplumb:"+n);p.className=(r["class"]?r["class"]+" ":"")+"jsplumb_vml";l(p,q);d(p,r);return p},l=function(p,n){p.style.left=n[0]+"px";p.style.top=n[1]+"px";p.style.width=n[2]+"px";p.style.height=n[3]+"px";p.style.position="absolute"},g=function(n){return Math.floor(n*c)},a=function(p,n){var v=p,u=function(o){return o.length==1?"0"+o:o},q=function(o){return u(Number(o).toString(16))},r=/(rgb[a]?\()(.*)(\))/;if(p.match(r)){var t=p.match(r)[2].split(",");v="#"+q(t[0])+q(t[1])+q(t[2]);if(!n&&t.length==4){v=v+q(t[3])}}return v},f=function(s,r,p){var u={};if(r.strokeStyle){u.stroked="true";u.strokecolor=a(r.strokeStyle,true);u.strokeweight=r.lineWidth+"px"}else{u.stroked="false"}if(r.fillStyle){u.filled="true";u.fillcolor=a(r.fillStyle,true)}else{u.filled="false"}if(r.dashstyle){if(p.strokeNode==null){p.strokeNode=m("stroke",[0,0,0,0],{dashstyle:r.dashstyle});s.appendChild(p.strokeNode)}else{p.strokeNode.dashstyle=r.dashstyle}}else{if(r["stroke-dasharray"]&&r.lineWidth){var o=r["stroke-dasharray"].indexOf(",")==-1?" ":",",t=r["stroke-dasharray"].split(o),n="";for(var q=0;q0&&F>0&&z=z&&B[2]<=F&&B[3]>=F)){return true}}var D=v.canvas.getContext("2d").getImageData(parseInt(z),parseInt(F),1,1);return D.data[0]!=0||D.data[1]!=0||D.data[2]!=0||D.data[3]!=0}return false};var u=false;var t=false,y=null,x=false;var w=function(A,z){return A!=null&&f(A,z)};this.mousemove=function(C){var E=e(C),B=i(C),A=document.elementFromPoint(B[0],B[1]),D=w(A,"_jsPlumb_overlay");var z=o==null&&(w(A,"_jsPlumb_endpoint")||w(A,"_jsPlumb_connector"));if(!u&&z&&v._over(C)){u=true;v.fire("mouseenter",v,C);return true}else{if(u&&(!v._over(C)||!z)&&!D){u=false;v.fire("mouseexit",v,C)}}v.fire("mousemove",v,C)};this.click=function(z){if(u&&v._over(z)&&!x){v.fire("click",v,z)}x=false};this.dblclick=function(z){if(u&&v._over(z)&&!x){v.fire("dblclick",v,z)}x=false};this.mousedown=function(z){if(v._over(z)&&!t){t=true;y=m(s(v.canvas));v.fire("mousedown",v,z)}};this.mouseup=function(z){t=false;v.fire("mouseup",v,z)}};var p=function(u){var t=document.createElement("canvas");jsPlumb.appendElement(t,u.parent);t.style.position="absolute";if(u["class"]){t.className=u["class"]}u._jsPlumb.getId(t,u.uuid);return t};var d=jsPlumb.CanvasConnector=function(x){n.apply(this,arguments);var t=function(B,z){u.ctx.save();jsPlumb.extend(u.ctx,z);if(z.gradient){var A=u.createGradient(B,u.ctx);for(var y=0;y0?1:-1}}var b={subtract:function(m,l){return{x:m.x-l.x,y:m.y-l.y}},dotProduct:function(m,l){return m.x*l.x+m.y*l.y},square:function(l){return Math.sqrt(l.x*l.x+l.y*l.y)},scale:function(m,l){return{x:m.x*l,y:m.y*l}}},d=Math.pow(2,-65),h=function(y,x){for(var s=[],v=x.length-1,r=2*v-1,t=[],w=[],o=[],p=[],q=[[1,0.6,0.3,0.1],[0.4,0.6,0.6,0.4],[0.1,0.3,0.6,1]],u=0;u<=v;u++){t[u]=b.subtract(x[u],y)}for(u=0;u<=v-1;u++){w[u]=b.subtract(x[u+1],x[u]);w[u]=b.scale(w[u],3)}for(u=0;u<=v-1;u++){for(var n=0;n<=v;n++){o[u]||(o[u]=[]);o[u][n]=b.dotProduct(w[u],t[n])}}for(u=0;u<=r;u++){p[u]||(p[u]=[]);p[u].y=0;p[u].x=parseFloat(u)/r}r=v-1;for(t=0;t<=v+r;t++){w=Math.min(t,v);for(u=Math.max(0,t-r);u<=w;u++){j=t-u;p[u+j].y+=o[j][u]*q[j][u]}}v=x.length-1;p=k(p,2*v-1,s,0);r=b.subtract(y,x[0]);o=b.square(r);for(u=q=0;u=64){y[0]=(E[0].x+E[D].x)/2;return 1}var s,r,p;v=E[0].y-E[D].y;w=E[D].x-E[0].x;A=E[0].x*E[D].y-E[D].x*E[0].y;t=max_distance_below=0;for(r=1;rt){t=p}else{if(p0?1:-1,r=null;n endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + endpointsByElement = {}, + endpointsByUUID = {}, + offsets = {}, + offsetTimestamps = {}, + floatingConnections = {}, + draggableStates = {}, + _mouseEventsEnabled = this.Defaults.MouseEventsEnabled, + _draggableByDefault = true, + canvasList = [], + sizes = [], + listeners = {}, // a map: keys are event types, values are lists of listeners. + DEFAULT_SCOPE = this.Defaults.Scope, + renderMode = null, // will be set in init() + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }, + + /** + * appends an element to the given parent, or the document body if no + * parent given. + */ + _appendElement = function(el, parent) { + if (!parent) + document.body.appendChild(el); + else + jsPlumb.CurrentLibrary.appendElement(el, parent); + }, + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + _timestamp = function() { return "" + (new Date()).getTime(); }, + + /** + * YUI, for some reason, put the result of a Y.all call into an object that contains + * a '_nodes' array, instead of handing back an array-like object like the other + * libraries do. + */ + _convertYUICollection = function(c) { + return c._nodes ? c._nodes : c; + }, + + /** + * Draws an endpoint and its connections. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + _draw = function(element, ui, timestamp) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (!timestamp) timestamp = _timestamp(); + if (endpoints) { + _updateOffset( { elId : id, offset : ui, recalc : false, timestamp : timestamp }); // timestamp is checked against last update cache; it is + // valid for one paint cycle. + var myOffset = offsets[id], myWH = sizes[id]; + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + var l = endpoints[i].connections; + for ( var j = 0; j < l.length; j++) { + l[j].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); // ...paint each connection. + // then, check for dynamic endpoint; need to repaint it. + var oIdx = l[j].endpoints[0] == endpoints[i] ? 1 : 0; + if (l[j].endpoints[oIdx].anchor.isDynamic && !l[j].endpoints[oIdx].isFloating()) { + var oId = oIdx == 0 ? l[j].sourceId : l[j].targetId, + oOffset = offsets[oId], oWH = sizes[oId], + // TODO i still want to make this faster. + anchorLoc = l[j].endpoints[oIdx].anchor.compute( { + xy : [ oOffset.left, oOffset.top ], + wh : oWH, + element : l[j].endpoints[oIdx], + txy : [ myOffset.left, myOffset.top ], + twh : myWH, + tElement : endpoints[i] + }); + l[j].endpoints[oIdx].paint({ anchorLoc : anchorLoc }); + } + } + } + } + }, + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }, + + /** + * gets an Endpoint by uuid. + */ + _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }, + + /** + * inits a draggable if it's not already initialised. + */ + _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element) && !jsPlumb.CurrentLibrary.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + } + } + }, + + _newConnection = function(params) { + var connectionFunc = jsPlumb.Defaults.ConnectionType || Connection, + endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint, + parent = jsPlumb.CurrentLibrary.getParent; + + if (params.sourceEndpoint) + params["parent"] = params.sourceEndpoint.parent; + else if (params.source.constructor == endpointFunc) + params["parent"] = params.source.parent; + else params["parent"] = parent(params.source); + + params["_jsPlumb"] = _currentInstance; + var con = new connectionFunc(params); + _eventFireProxy("click", "click", con); + _eventFireProxy("dblclick", "dblclick", con); + return con; + }, + + _eventFireProxy = function(event, proxyEvent, obj) { + obj.bind(event, function(e) { + _currentInstance.fire(proxyEvent, obj, e); + }); + }, + + _newEndpoint = function(params) { + var endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint; + params["parent"] = jsPlumb.CurrentLibrary.getParent(params.source); + params["_jsPlumb"] = _currentInstance, + ep = new endpointFunc(params); + _eventFireProxy("click", "endpointClick", ep); + _eventFireProxy("dblclick", "endpointDblClick", ep); + return ep; + }, + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }, + /** + * perform an operation on all elements. + */ + _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }, + + /** + * helper to remove an element from the DOM. + */ + _removeElement = function(element, parent) { + if (element != null && element.parentNode != null) { + element.parentNode.removeChild(element); + } + }, + /** + * helper to remove a list of elements from the DOM. + */ + _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }, + /** + * helper method to remove an item from a list. + */ + _removeFromList = function(map, key, value) { + if (key != null) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete (l[i]); + l.splice(i, 1); + return true; + } + } + } + return false; + }, + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }, + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + _setVisible = function(el, state) { + _operation(_getAttribute(el, "id"), function(jpc) { + jpc.canvas.style.display = state; + }); + }, + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }, + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + _toggleVisible = function(elId) { + _operation(elId, function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }, + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return offsets[elId]; + } + if (recalc || offset == null) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + } + return offsets[elId]; + }, + +/** + * gets an id for the given element, creating and setting one if + * necessary. + */ + _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else + id = "jsPlumb_" + _timestamp(); + _setAttribute(ele, "id", id); + } + return id; + }, + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'jsPlumb function failed : ' + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'wrapped function failed : ' + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = "_jsPlumb_connector"; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = "_jsPlumb_endpoint"; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = "_jsPlumb_overlay"; + + this.Anchors = {}; + + this.Connectors = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Endpoints = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Overlays = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + +// ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***************************************** + /* + * Function: bind + * Bind to an event on jsPlumb. + * + * Parameters: + * event - the event to bind. Available events on jsPlumb are: + * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback. + * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback. + * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback. + * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback. + * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback. + * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback. + * + * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event. + */ + + /* + * Function: clearListeners + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + +// *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *********************************************************** + + /* + Function: addEndpoint + + Adds an to a given element or elements. + + Parameters: + + el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + params - Object containing Endpoint constructor arguments. For more information, see . + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some + shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in + this object are anything that 'params' can contain. See . + + Returns: + The newly created , if el referred to a single element. Otherwise, an array of newly created s. + + See Also: + + */ + this.addEndpoint = function(el, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + + // YUI wrapper + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + var _el = _getElementObject(inputs[i]), id = _getId(_el); + p.source = _el; + _updateOffset({ elId : id }); + var e = _newEndpoint(p); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + results.push(e); + } + + return results.length == 1 ? results[0] : results; + }; + + /* + Function: addEndpoints + Adds a list of s to a given element or elements. + + Parameters: + target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See 's constructor documentation. + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created s, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(el, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams); + if (e.constructor == Array) + Array.prototype.push.apply(results, e); + else results.push(e); + } + return results; + }; + + /* + Function: animate + This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating + the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // onComplete repaints, just to make sure everything looks good at the end of the animation. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /* + Function: connect + Establishes a between two elements (or s, which are themselves registered to elements). + + Parameters: + params - Object containing constructor arguments for the Connection. See 's constructor documentation. + referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of + Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection. + + Returns: + The newly created . + */ + this.connect = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + // dynamic anchors. backwards compatibility here: from 1.2.6 onwards you don't need to specify "dynamicAnchors". the fact that some anchor consists + // of multiple definitions is enough to tell jsPlumb you want it to be dynamic. + if (_p.dynamicAnchors) { + // these can either be an array of anchor coords, which we will use for both source and target, or an object with {source:[anchors], target:[anchors]}, in which + // case we will use a different set for each element. + var a = _p.dynamicAnchors.constructor == Array; + var sa = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.source)); + var ta = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.target)); + _p.anchors = [sa,ta]; + } + + var jpc = _newConnection(_p); + // add to list of connections (by scope). + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + _currentInstance.fire("jsPlumbConnection", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + // force a paint + _draw(jpc.source); + + return jpc; + }; + + /* + Function: deleteEndpoint + Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too) + + Parameters: + object - either an object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElement(endpoint.canvas, endpoint.parent); + // remove from endpointsbyElement + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + delete endpoint; + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every , and their associated s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc) { + _currentInstance.fire("jsPlumbConnectionDetached", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + }; + + /* + Function: detach + Detaches and then removes a . Takes either (source, target) (the old way, maintained for backwards compatibility), or a params + object with various possible values. + + Parameters: + source - id or element object of the first element in the Connection. + target - id or element object of the second element in the Connection. + params - a JS object containing the same parameters as you pass to jsPlumb.connect. If this is present then neither source nor + target should be present; it should be the only argument to the method. See the docs for 's constructor for information +about the parameters allowed in the params object. + Returns: + true if successful, false if not. + */ + this.detach = function(source, target) { + if (arguments.length == 2) { + var s = _getElementObject(source), sId = _getId(s); + var t = _getElementObject(target), tId = _getId(t); + _operation(sId, function(jpc) { + if ((jpc.sourceId == sId && jpc.targetId == tId) || (jpc.targetId == sId && jpc.sourceId == tId)) { + _removeElements(jpc.connector.getDisplayElements(), jpc.parent); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + // this is the new version of the method, taking a JS object like + // the connect method does. + else if (arguments.length == 1) { + // TODO investigate whether or not this code still works when a user has supplied their own subclass of Connection. i suspect it may not. + if (arguments[0].constructor == Connection) { + arguments[0].endpoints[0].detachFrom(arguments[0].endpoints[1]); + } + else if (arguments[0].connection) { + arguments[0].connection.endpoints[0].detachFrom(arguments[0].connection.endpoints[1]); + } + else { + var _p = jsPlumb.extend( {}, source); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1])); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source); + var targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElements(jpc.connector.getDisplayElements(), jpc.parent); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + } + } + }; + + /* + Function: detachAll + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + + Returns: + void + */ + this.detachAllConnections = function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + }; + + /** + * @deprecated Use detachAllConnections instead. this will be removed in jsPlumb 1.3. + */ + this.detachAll = this.detachAllConnections; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + /** + * @deprecated use detachEveryConnection instead. this will be removed in jsPlumb 1.3. + */ + this.detachEverything = this.detachEveryConnection; + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of you library's draggable method so that jsPlumb can setup the appropriate callbacks. Your underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method, + * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the + * default scope). If multiple scopes are passed in, the return value will be a map of { scope -> [ connection... ] }. + * + * Parameters + * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list + * of connections that are in the given scope. + * options - if the argument is a JS object, you can specify a finer-grained filter: + * + * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes. + * - *source* either a string representing an element id, or a selector. constrains the result to connections having this source. + * - *target* either a string representing an element id, or a selector. constrains the result to connections having this target. + * + */ + this.getConnections = function(options) { + if (!options) { + options = {}; + } else if (options.constructor == String) { + options = { "scope": options }; + } + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') + r.push(input); + else + r = input; + } + return r; + }; + var scope = options.scope || jsPlumb.getDefaultScope(), + scopes = prepareList(scope), + sources = prepareList(options.source), + targets = prepareList(options.target), + filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }, + results = scopes.length > 1 ? {} : [], + _addOne = function(scope, obj) { + if (scopes.length > 1) { + var ss = results[scope]; + if (ss == null) { + ss = []; results[scope] = ss; + } + ss.push(obj); + } else results.push(obj); + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + _addOne(i, c); + } + } + } + return results; + }; + + /* + * Function: getAllConnections + * Gets all connections, as a map of { scope -> [ connection... ] }. + */ + this.getAllConnections = function() { + return connectionsByScope; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /** + * Function:getEndpoints + * Gets the list of Endpoints for a given selector, or element id. + * @param el + * @return + */ + this.getEndpoints = function(el) { + return endpointsByElement[_getId(el)]; + }; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + + this.appendElement = _appendElement; + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + + Returns: + void + */ + this.hide = function(el) { + _setVisible(el, "none"); + }; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + if (!initialized) { + _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run. + + var _bind = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && _mouseEventsEnabled && renderMode == jsPlumb.CANVAS) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + var t = c[i].connector[event](e); + if (t) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i].endpoint[event](e)) return; + } + } + } + }); + }; + _bind("click"); + _bind("dblclick"); + _bind("mousemove"); + _bind("mousedown"); + _bind("mouseup"); + + initialized = true; + _currentInstance.fire("ready"); + } + }; + + this.jsPlumbUIComponent = jsPlumbUIComponent; + this.EventGenerator = EventGenerator; + + /* + * Creates an anchor with the given params. + * + * You do not need to use this method. It is exposed because of the way jsPlumb is + * split into three scripts; this will change in the future. + * + * x - the x location of the anchor as a fraction of the + * total width. y - the y location of the anchor as a fraction of the + * total height. xOrientation - value indicating the general direction a + * connection from the anchor should go in, in the x direction. + * yOrientation - value indicating the general direction a connection + * from the anchor should go in, in the y direction. xOffset - a fixed + * offset that should be applied in the x direction that should be + * applied after the x position has been figured out. optional. defaults + * to 0. yOffset - a fixed offset that should be applied in the y + * direction that should be applied after the y position has been + * figured out. optional. defaults to 0. + * -- OR -- + * + * params - {x:..., y:..., xOrientation etc } + * -- OR FROM 1.2.4 --- + * + * name - the name of some Anchor in the _currentInstance.Anchors array. + * -- OR FROM 1.2.4 --- + * + * coords - a list of coords for the anchor, like you would pass to + * jsPlumb.makeAnchor (eg [0.5,0.5,0,-1] - an anchor in the center of + * some element, oriented towards the top of the screen) + * -- OR FROM 1.2.4 --- + * + * anchor - an existing anchor. just gets passed back. it's handy + * internally to have this functionality. + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed + // in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards + // compatibility if we are given only one value we assume it's a + // call in the old form. + if (arguments.length == 0) return null; + var params = {}; + if (arguments.length == 1) { + var specimen = arguments[0]; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; + // is it the name of an anchor type? + else if (typeof specimen == "string") return _currentInstance.Anchors[arguments[0]](); + // is it an array of coordinates? + else if (specimen.constructor == Array) { + if (specimen[0].constructor == Array || specimen[0].constructor == String) + return new DynamicAnchor(specimen); + else + return jsPlumb.makeAnchor.apply(this, specimen); + } + // last we try the backwards compatibility stuff. + else if (typeof arguments[0] == "object") jsPlumb.extend(params, x); + } else { + params = { x : x, y : y }; + if (arguments.length >= 4) params.orientation = [ arguments[2], arguments[3] ]; + if (arguments.length == 6) params.offsets = [ arguments[4], arguments[5] ]; + } + var a = new Anchor(params); + a.clone = function() { + return new Anchor(params); + }; + return a; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types) { + var r = []; + for ( var i = 0; i < types.length; i++) + if (typeof types[i] == "string") + r.push(_currentInstance.Anchors[types[i]]()); + else if (types[i].constructor == Array) + r.push(jsPlumb.makeAnchor(types[i])); + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /** + * Function: makeTarget + * Makes some DOM element a Connection target, allowing you to drag connections to it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a target. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * scope optional. scope for the drop zone. + * dropOptions optional. same stuff as you would pass to dropOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to false. whether or not to delete + * any Endpoints created by a connection to this target if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + this.makeTarget = function(el, params, referenceParams) { + + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + var jpcl = jsPlumb.CurrentLibrary, + scope = p.scope || _currentInstance.Defaults.Scope, + deleteEndpointsOnDetach = p.deleteEndpointsOnDetach || false, + _doOne = function(_el) { + var dropOptions = jsPlumb.extend({}, p.dropOptions || {}); + var _drop = function() { + var draggable = _getElementObject(jpcl.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + // restore the original scope if necessary (issue 57) + scope = _getAttribute(draggable, "originalScope"); + + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + // get the connection, to then get its endpoint + var jpc = floatingConnections[id], + source = jpc.endpoints[0], + _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : null, + // make a new Endpoint + newEndpoint = jsPlumb.addEndpoint(_el, _endpoint); + + var c = jsPlumb.connect({ + source:source, + target:newEndpoint, + scope:scope + }); + if (deleteEndpointsOnDetach) + c.endpointToDeleteOnDetach = newEndpoint; + }; + + var dropEvent = jpcl.dragEvents['drop']; + dropOptions["scope"] = dropOptions["scope"] || scope; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop); + + jpcl.initDroppable(_el, dropOptions); + }; + + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements drop targets. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeTargets = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeTarget(els[i], params, referenceParams); + } + }; + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + for ( var i in ebe) + _currentInstance.deleteEndpoint(ebe[i]); + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + this.deleteEveryEndpoint(); + this.clearListeners(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + + Parameters: + value - whether or not to automatically repaint when the window is resized. + + Returns: void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setDraggableByDefault + * Sets whether or not elements are draggable by default. Default for this is true. + * + * Parameters: + * draggable - value to set + * + * Returns: + * void + */ + this.setDraggableByDefault = function(draggable) { + _draggableByDefault = draggable; + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction + * Sets the function to fire when the window size has changed and a repaint was fired. + * + * Parameters: + * f - Function to execute. + * + * Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: setMouseEventsEnabled + * Sets whether or not mouse events are enabled. Default is true. + * + * Parameters: + * enabled - whether or not mouse events should be enabled. + * + * Returns: + * void + */ + this.setMouseEventsEnabled = function(enabled) { + _mouseEventsEnabled = enabled; + }; + + /* + * Constant for use with the setRenderMode method + */ + this.CANVAS = "canvas"; + + /* + * Constant for use with the setRenderMode method + */ + this.SVG = "svg"; + + this.VML = "vml"; + + /* + * Function: setRenderMode + * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that + * what you asked for is not supported (and that VML is). If you asked for VML but the browser does + * not support it, jsPlumb uses SVG. + * + * Returns: + * the render mode that jsPlumb set, which of course may be different from that requested. + */ + this.setRenderMode = function(mode) { + if (mode) + mode = mode.toLowerCase(); + else + return; + if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); + // now test we actually have the capability to do this. + if (mode === jsPlumb.CANVAS && canvasAvailable) + renderMode = jsPlumb.CANVAS; + else if (mode === jsPlumb.SVG && svgAvailable) + renderMode = jsPlumb.SVG; + else if (vmlAvailable) + renderMode = jsPlumb.VML; + + return renderMode; + }; + + this.getRenderMode = function() { return renderMode; }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * + * Returns: + * void + */ + this.show = function(el) { + _setVisible(el, "block"); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId, + makeAnchor:self.makeAnchor, + makeDynamicAnchor:self.makeDynamicAnchor + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload + * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element. + * + * Returns: + * void + */ + this.unload = function() { + delete endpointsByElement; + delete endpointsByUUID; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + delete canvasList; + }; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + if (timestamp && timestamp === self.timestamp) { + return lastReturnValue; + } + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + if (element.canvas) { + var po = element.canvas.offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(element.canvas.offsetParent); + lastReturnValue[0] = lastReturnValue[0] - po.left; + lastReturnValue[1] = lastReturnValue[1] - po.top; + } + + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + * + * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(_getElementObject(refCanvas)); + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + var orientation = null; + var _lastResult = null; + + this.compute = function(params) { + var xy = params.xy, element = params.element; + var result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + + if (element.canvas) { + var po = element.canvas.offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(element.canvas.offsetParent); + result[0] = result[0] - po.left; + result[1] = result[1] - po.top; + } + + _lastResult = result; + return result; + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { orientation = anchor.getOrientation(); }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchors is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = []; + var _convert = function(anchor) { return anchor.constructor == Anchor ? anchor: jsPlumb.makeAnchor(anchor); }; + for (var i = 0; i < anchors.length; i++) _anchors[i] = _convert(anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null; + var _curIndex = _anchors.length > 0 ? 0 : -1; + this.locked = false; + var self = this; + + // helper method to calculate the distance between the centers of the two elements. + var _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }; + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + var _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + + var pos = _curAnchor.compute(params); + return pos; + }; + + this.getCurrentLocation = function() { + var cl = _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + return cl; + }; + + this.getOrientation = function() { return _curAnchor != null ? _curAnchor.getOrientation() : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * overlays - Optional array of Overlay definitions to appear on this Connection. + */ + var Connection = function(params) { + + jsPlumbUIComponent.apply(this, arguments); + // ************** get the source and target and register the connection. ******************* + var self = this; + var visible = true; + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none"; + }; + var id = new String('_jsplumb_c_' + (new Date()).getTime()); + this.getId = function() { return id; }; + this.parent = params.parent; + /** + Property: source + The source element for this Connection. + */ + this.source = _getElementObject(params.source); + /** + Property:target + The target element for this Connection. + */ + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. + if (params.sourceEndpoint) this.source = params.sourceEndpoint.getElement(); + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + /* + * Property: sourceId + * Id of the source element in the connection. + */ + this.sourceId = _getAttribute(this.source, "id"); + /* + * Property: targetId + * Id of the target element in the connection. + */ + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + + /** + * implementation of abstract method in EventGenerator + * @return list of attached elements. in our case, a list of Endpoints. + */ + this.getAttachedElements = function() { + return self.endpoints; + }; + + /** + * implementation of abstract method in EventGenerator + */ + var srcWhenMouseDown = null, targetWhenMouseDown = null; + this.savePosition = function() { + srcWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.source)); + targetWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.target)); + }; + + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams) { + if (anchorParams) + return jsPlumb.makeAnchor(anchorParams); + }; + var prepareEndpoint = function(existing, index, params, element, connectorPaintStyle, connectorHoverPaintStyle) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] + || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint; + + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified. + if (es.fillStyle == null && connectorPaintStyle != null) + es.fillStyle = connectorPaintStyle.strokeStyle; + + // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does: + //* + if (es.outlineColor == null && connectorPaintStyle != null) + es.outlineColor = connectorPaintStyle.outlineColor; + if (es.outlineWidth == null && connectorPaintStyle != null) + es.outlineWidth = connectorPaintStyle.outlineWidth; + //*/ + + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure? + if (connectorHoverPaintStyle != null) { + if (ehs == null) ehs = {}; + if (ehs.fillStyle == null) { + ehs.fillStyle = connectorHoverPaintStyle.strokeStyle; + } + } + var a = params.anchors ? params.anchors[index] : _makeAnchor(_currentInstance.Defaults.Anchors[index]) || _makeAnchor(jsPlumb.Defaults.Anchors[index]) || _makeAnchor(_currentInstance.Defaults.Anchor) || _makeAnchor(jsPlumb.Defaults.Anchor); + var u = params.uuids ? params.uuids[index] : null; + var e = _newEndpoint( { paintStyle : es, hoverPaintStyle:ehs, endpoint : ep, connections : [ self ], uuid : u, anchor : a, source : element }); + self.endpoints[index] = e; + + if (params.drawEndpoints === false) e.setVisible(false, true, true); + + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source, params.paintStyle, params.hoverPaintStyle); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target, params.paintStyle, params.hoverPaintStyle); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + /* + * Function: setConnector + * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method - the same + * thing that you would set as the 'connector' property on a jsPlumb.connect call. + * + * Parameters: + * connector - Connector definition + */ + this.setConnector = function(connector, doNotRepaint) { + if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent); + var connectorArgs = { _jsPlumb:self._jsPlumb, parent:params.parent, cssClass:params.cssClass }; + if (connector.constructor == String) + this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand. + else if (connector.constructor == Array) + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs)); + this.canvas = this.connector.canvas; + var _mouseDown = false, _mouseWasDown = false, _mouseDownAt = null; + // add mouse events + this.connector.bind("click", function(con, e) { + _mouseWasDown = false; + self.fire("click", self, e); + }); + this.connector.bind("dblclick", function(con, e) { _mouseWasDown = false;self.fire("dblclick", self, e); }); + this.connector.bind("mouseenter", function(con, e) { + if (!self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(true); + } + self.fire("mouseenter", self, e); + } + }); + this.connector.bind("mouseexit", function(con, e) { + if (self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(false); + } + self.fire("mouseexit", self, e); + } + }); + this.connector.bind("mousedown", function(con, e) { + _mouseDown = true; + _mouseDownAt = jsPlumb.CurrentLibrary.getPageXY(e); + self.savePosition(); + }); + this.connector.bind("mouseup", function(con, e) { + _mouseDown = false; + if (self.connector == _connectionBeingDragged) _connectionBeingDragged = null; + }); + + if (!doNotRepaint) self.repaint(); + }; + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc) + */ + + self.setConnector(this.endpoints[0].connector || + this.endpoints[1].connector || + params.connector || + _currentInstance.Defaults.Connector || + jsPlumb.Defaults.Connector, true); + + this.setPaintStyle(this.endpoints[0].connectorStyle || + this.endpoints[1].connectorStyle || + params.paintStyle || + _currentInstance.Defaults.PaintStyle || + jsPlumb.Defaults.PaintStyle, true); + + this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || + this.endpoints[1].connectorHoverStyle || + params.hoverPaintStyle || + _currentInstance.Defaults.HoverPaintStyle || + jsPlumb.Defaults.HoverPaintStyle, true); + + this.paintStyleInUse = this.paintStyle; + + /* + * Property: overlays + * List of Overlays for this Connection. + */ + this.overlays = []; + var _overlays = params.overlays || _currentInstance.Defaults.Overlays; + if (_overlays) { + for (var i = 0; i < _overlays.length; i++) { + var o = _overlays[i], _newOverlay = null, _overlayEvents = null; + if (o.constructor == Array) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0]; + var p = jsPlumb.CurrentLibrary.extend({connection:self, _jsPlumb:_currentInstance}, o[1]); // make a copy of the object so as not to mess up anyone else's reference... + if (o.length == 3) jsPlumb.CurrentLibrary.extend(p, o[2]); + _newOverlay = new jsPlumb.Overlays[renderMode][type](p); + if (p.events) { + for (var evt in p.events) { + _newOverlay.bind(evt, p.events[evt]); + } + } + } else if (o.constructor == String) { + _newOverlay = new jsPlumb.Overlays[renderMode][o]({connection:self, _jsPlumb:_currentInstance}); + } else { + _newOverlay = o; + } + + + + this.overlays.push(_newOverlay); + } + } + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Connection. + * + * Parameters: + * event - the event to bind. Available events on a Connection are: + * - *click* : notification that a Connection was clicked. + * - *dblclick* : notification that a Connection was double clicked. + * - *mouseenter* : notification that the mouse is over a Connection. + * - *mouseexit* : notification that the mouse exited a Connection. + * + * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Connection's paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Connection will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Connection. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + this.addOverlay = function(overlay) { self.overlays.push(overlay); }; + + /** + * Function: removeAllOverlays + * Removes all overlays from the Connection, and then repaints. + */ + this.removeAllOverlays = function() { + self.overlays.splice(0, self.overlays.length); + self.repaint(); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + this.removeOverlay = function(overlayId) { + var idx = -1; + for (var i = 0; i < self.overlays.length; i++) { + if (overlayId === self.overlays[i].id) { + idx = i; + break; + } + } + if (idx != -1) self.overlays.splice(idx, 1); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + this.removeOverlays = function() { + for (var i = 0; i < arguments.length; i++) + self.removeOverlay(arguments[i]); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + this.labelStyle = params.labelStyle || _currentInstance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + if (this.label) { + this.overlays.push(new jsPlumb.Overlays[renderMode].Label( { + cssClass:params.cssClass, + labelStyle : this.labelStyle, + label : this.label, + connection:self, + _jsPlumb:_currentInstance + })); + } + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String or a Function that returns a String. + */ + this.setLabel = function(l) { + self.label = l; + _currentInstance.repaint(self.source); + }; + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var otherOffset = offsets[this.targetId]; + var otherWH = sizes[this.targetId]; + var anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1] + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0] + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc }); + + /* + * Paints the Connection. Not exposed for public usage. + * + * Parameters: + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp; + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = false; + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }); + _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + var sAnchorP = this.endpoints[sIdx].anchor.getCurrentLocation(); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.getCurrentLocation(); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + + /* paint overlays*/ + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + var s = o.computeMaxSize(self.connector); + if (s > maxSize) + maxSize = s; + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, self.paintStyleInUse.lineWidth, maxSize); + + self.connector.paint(dim, self.paintStyleInUse); + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim); + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function() { + this.paint({ elId : this.sourceId, recalc : true }); + }; + + _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide + // whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + + // just to make sure the UI gets initialised fully on all browsers. + self.repaint(); + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1 + * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't + * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you + * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or + * 'target' Endpoints for Connections. + * + * + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * connections - optional list of Connections to configure the Endpoint with. + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + params = params || {}; + var self = this; +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Endpoint. + * + * Parameters: + * event - the event to bind. Available events on an Endpoint are: + * - *click* : notification that a Endpoint was clicked. + * - *dblclick* : notification that a Endpoint was double clicked. + * - *mouseenter* : notification that the mouse is over a Endpoint. + * - *mouseexit* : notification that the mouse exited a Endpoint. + * + * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Endpoint's paint style and then repaints the Endpoint. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Endpoint will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Endpoint. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + var visible = true; + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + var id = new String('_jsplumb_e_' + (new Date()).getTime()); + this.getId = function() { return id; }; + if (params.dynamicAnchors) + self.anchor = new DynamicAnchor(jsPlumb.makeAnchors(params.dynamicAnchors)); + else + self.anchor = params.anchor ? jsPlumb.makeAnchor(params.anchor) : params.anchors ? jsPlumb.makeAnchor(params.anchors) : jsPlumb.makeAnchor("TopCenter"); + var _endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot", + endpointArgs = { _jsPlumb:self._jsPlumb, parent:params.parent }; + if (_endpoint.constructor == String) + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint](endpointArgs); + else if (_endpoint.constructor == Array) + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint[0]](jsPlumb.extend(_endpoint[1], endpointArgs )); + else _endpoint = _endpoint.clone(); + self.endpoint = _endpoint; + this.endpoint.bind("click", function(e) { self.fire("click", self, e); }); + this.endpoint.bind("dblclick", function(e) { self.fire("dblclick", self, e); }); + this.setPaintStyle(params.paintStyle || + params.style || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyle, true); + this.setHoverPaintStyle(params.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyle, true); + this.paintStyleInUse = this.paintStyle; + this.connectorStyle = params.connectorStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.parent = params.parent; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + var _element = params.source, + _uuid = params.uuid, + floatingEndpoint = null, + inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + this.element = _element; + var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + /* + * Property: canvas + * The Endpoint's Canvas. + */ + this.canvas = this.endpoint.canvas; + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + this.connections = params.connections || []; + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + var _reattach = params.reattach || false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + + // this avoids a circular loop + if (!ignoreTarget) { + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + t.detach(connection, true); + // check connection to see if we want to delete the other endpoint. + // if the user uses makeTarget to make some element a target for connections, + // it is possible that they will have set 'endpointToDeleteOnDetach': when + // you make a connection to an element that acts as a target (note: NOT an + // Endpoint; just some div as a target), Endpoints are created for that + // connection. so if you then delete that Connection, it is feasible you + // will want these auto-generated endpoints to be removed. + if (connection.endpointToDeleteOnDetach && connection.endpointToDeleteOnDetach.connections.length == 0) + jsPlumb.deleteEndpoint(connection.endpointToDeleteOnDetach); + } + _removeElements(connection.connector.getDisplayElements(), connection.parent); + _removeFromList(connectionsByScope, connection.scope, connection); + if(!ignoreTarget) fireDetachEvent(connection); + } + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + */ + this.detachAll = function() { + while (self.connections.length > 0) { + self.detach(self.connections[0]); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + */ + this.detachFrom = function(targetEndpoint) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + c[i].setHover(false); + self.detach(c[i]); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + return _newEndpoint( { anchor : self.anchor, source : _element, paintStyle : this.paintStyle, endpoint : _endpoint }); + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + return (self.connections.length < _maxConnections) ? null : self.connections[0]; + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + /* + * Function: setStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to + * setStyle and deprecate it if so. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + * + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + /* + * Function: paint + * Paints the Endpoint, recalculating offset and anchor positions if necessary. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + + params = params || {}; + var timestamp = params.timestamp; + if (!timestamp || self.timestamp !== timestamp) { + var ap = params.anchorPoint, canvas = params.canvas, connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + var xy = params.offset || offsets[_elementId]; + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (self.anchor.isDynamic) { + if (self.connections.length > 0) { + var c = self.connections[0]; + var oIdx = c.endpoints[0] == self ? 1 : 0; + var oId = oIdx == 0 ? c.sourceId : c.targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + } + ap = self.anchor.compute(anchorParams); + } + + var d = _endpoint.compute(ap, self.anchor.getOrientation(), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + _endpoint.paint(d, self.paintStyleInUse, self.anchor); + + self.timestamp = timestamp; + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + var start = function() { + jpc = self.connectorSelector(); + if (self.isFull() && !dragAllowedWhenFull) return false; + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + n.style.position = "absolute"; + var nE = _getElementObject(n); + _appendElement(n, self.parent); + // create and assign an id, and initialize the offset. + var id = _getId(nE); + + // set the offset of this div to be where 'inPlaceCopy' is, to start with. + var ipcoel = _getElementObject(inPlaceCopy.canvas), + ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel), + po = inPlaceCopy.canvas.offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(inPlaceCopy.canvas.offsetParent); + jsPlumb.CurrentLibrary.setOffset(n, {left:ipco.left - po.left, top:ipco.top-po.top}); + + _updateOffset( { elId : id }); + + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor( { reference : self.anchor, referenceCanvas : self.canvas }); + floatingEndpoint = _newEndpoint({ paintStyle : self.paintStyle, endpoint : _endpoint, anchor : floatingAnchor, source : nE }); + + if (jpc == null) { + self.anchor.locked = true; + // create a connection. one end is this endpoint, the + // other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : _getElementObject(_element), + target : _getElementObject(n), + anchors : [ self.anchor, floatingAnchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays + }); + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.connector.setHover(false); + } else { + existingJpc = true; + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.connector.setHover(false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas)); + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; // are we the source or the target? + + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas); + var dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // get a new, temporary scope, to use (issue 57) + var newScope = "scope_" + (new Date()).getTime(); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + // set the new, temporary scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(i, newScope); + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // TODO unregister on stop? or will floating endpoint's + // destruction be assured. + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be + // dragging the source at any time + // before this connection is either discarded or made into a + // permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var jpcl = jsPlumb.CurrentLibrary, + dragOptions = params.dragOptions || {}, + defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions), + startEvent = jpcl.dragEvents['start'], + stopEvent = jpcl.dragEvents['stop'], + dragEvent = jpcl.dragEvents['drag']; + + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], + function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + jsPlumb.CurrentLibrary.setOffset(n, _ui); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements( [ n, floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (_reattach) { + + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } else { + jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc); // the main endpoint will inform the floating endpoint + // to disconnect, and also post the detached event. + } + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElements(jpc.connector.getDisplayElements(), self.parent); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint(); + jpc.setHover(false); + jpc.repaint(); + jpc = null; + delete inPlaceCopy; + delete endpointsByElement[floatingEndpoint.elementId]; + floatingEndpoint = null; + delete floatingEndpoint; + + _currentInstance.currentlyDragging = false; + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas) { + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + var drop = function() { + var draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)); + var id = _getAttribute(draggable, "dragId"); + var elId = _getAttribute(draggable, "elId"); + + // restore the original scope if necessary (issue 57) + var scope = _getAttribute(draggable, "originalScope"); + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + var jpc = floatingConnections[id]; + + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // todo test that the target is not full. + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + if (!jpc.suspendedEndpoint) { + _addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + _currentInstance.fire("jsPlumbConnectionDetached", { + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }); + } + + jsPlumb.repaint(elId); + + _currentInstance.fire("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], + targetEndpoint : jpc.endpoints[1], + connection:jpc + }); + } + + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute( _getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + } + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute(_getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + } + }); + + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas)); + + return self; + }; + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + //if (_defaults) jsPlumb.extend(j.Defaults, _defaults); + return j; + }; + + var _curryAnchor = function(x,y,ox,oy) { + return function() { + return jsPlumb.makeAnchor(x,y,ox,oy); + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1); + + jsPlumb.Defaults.DynamicAnchors = function() { + return jsPlumb.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"]); + }; + jsPlumb.Anchors["AutoDefault"] = function() { return jsPlumb.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors()); }; + + +})(); +/* +* jsPlumb-defaults-1.3.0-RC1 +* +* Copyright 2010 - 2011 Simon Porritt http://jsplumb.org +* +* Triple licensed under the MIT, GPL2 and Beer licenses. +*/ + +(function() { + + + + /** + * + * Helper class to consume unused mouse events by components that are DOM elements and + * are used by all of the different rendering modes. + * + */ + jsPlumb.DOMElementComponent = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + // when render mode is canvas, these functions may be called by the canvas mouse handler. + // this component is safe to pipe this stuff to /dev/null. + this.mousemove = + this.dblclick = + this.click = + this.mousedown = + this.mouseup = function(e) { }; + }; + + /** + * Class: Connectors.Straight + * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + var currentPoints = null; + var _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + _dx = _tx - _sx, _dy = (_ty - _sy); + _m = _dy / _dx, _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + + return currentPoints; + }; + + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + var xp = _sx + (location * _dx); + var yp = (_m == Infinity || _m == -Infinity) ? _sy + (location * (_ty - _sy)) : (_m * xp) + _b; + return {x:xp, y:yp}; + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { return _m; }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + var orientation = distance > 0 ? 1 : -1; + var y = Math.abs(distance * Math.sin(_theta)); + if (_sy > _ty) y = y * -1; + var x = Math.abs(distance * Math.cos(_theta)); + if (_sx > _tx) x = x * -1; + return {x:p.x + (orientation * x), y:p.y + (orientation * y)}; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = self.gradientAtPoint(p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + }; + }; + + + /** + * Class:Connectors.Bezier + * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's + * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below. + */ + /** + * Function:Constructor + * + * Parameters: + * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line. + * Optional; defaults to 150. + * + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + params = params || {}; + this.majorAnchor = params.curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h; + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) + { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx); var minx2 = Math.min(_CP[0], _CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(_sx,_tx); var maxx2 = Math.max(_CP[0], _CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty); var miny2 = Math.min(_CP[1], _CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(_sy,_ty); var maxy2 = Math.max(_CP[1], _CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + return jsBezier.perpendicularToCurveAt(_makeCurve(), location, length, distance); + }; + + }; + + + /** + * Class: Connectors.Flowchart + * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments. + */ + /** + * Function: Constructor + * + * Parameters: + * stub - minimum length for the stub at each end of the connector. defaults to 30 pixels. + */ + jsPlumb.Connectors.Flowchart = function(params) { + params = params || {}; + var self = this, + minStubLength = params.stub || params.minStubLength /* bwds compat. */ || 30, + segments = [], + segmentGradients = [], + segmentProportions = [], + segmentLengths = [], + segmentProportionalLengths = [], + points = [], + swapX, + swapY, + /** + * recalculates the gradients of each segment, and the points at which the segments begin, proportional to the total length travelled + * by all the segments that constitute the connector. + */ + updateSegmentGradientsAndProportions = function(startX, startY, endX, endY) { + var total = 0; + for (var i = 0; i < segments.length; i++) { + var sx = i == 0 ? startX : segments[i][2], + sy = i == 0 ? startY : segments[i][3], + ex = segments[i][0], + ey = segments[i][1]; + + segmentGradients[i] = sx == ex ? Infinity : 0; + segmentLengths[i] = Math.abs(sx == ex ? ey - sy : ex - sx); + total += segmentLengths[i]; + } + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segmentLengths[i] / total; + segmentProportions[i] = [curLoc, (curLoc += (segmentLengths[i] / total)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0]; + var ly = segments.length == 0 ? sy : segments[segments.length - 1][1]; + segments.push([x, y, lx, ly]); + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment + * that contains the point which is 'location' distance along the entire path, where + * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths + * are made up of a list of segments, each of which contributes some fraction to + * the total length. + */ + findSegmentForLocation = function(location) { + var idx = segmentProportions.length - 1, inSegmentProportion = 0; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + segments = []; + segmentGradients = []; + segmentProportionalLengths = []; + segmentLengths = []; + segmentProportionals = []; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + offx = (lw / 2) + (minStubLength * 2), + offy = (lw / 2) + (minStubLength * 2), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(), + to = targetAnchor.orientation || targetAnchor.getOrientation(), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx, + h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy; + if (w < minWidth) { + offx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + offy += (minWidth - h) / 2; + h = minWidth; + } + var sx = swapX ? w-offx : offx, + sy = swapY ? h-offy : offy, + tx = swapX ? offx : w-offx , + ty = swapY ? offy : h-offy, + startStubX = sx + (so[0] * minStubLength), + startStubY = sy + (so[1] * minStubLength), + endStubX = tx + (to[0] * minStubLength), + endStubY = ty + (to[1] * minStubLength), + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2); + + x -= offx; y -= offy; + points = [x, y, w, h, sx, sy, tx, ty], extraPoints = []; + + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + if (so[0] == 0) { + var startStubIsBeforeEndStub = startStubY < endStubY; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } + } + else { + var startStubIsBeforeEndStub = startStubX < endStubX; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } + } + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentGradientsAndProportions(sx, sy, tx, ty); + + return points; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location) { + return self.pointAlongPathFrom(location, 0); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location) { + return segmentGradients[findSegmentForLocation(location)["index"]]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance) { + var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segmentLengths[s.index], m = segmentGradients[s.index]; + var e = { + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + return e; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = segmentGradients[p.segmentInfo.index]; + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + + }; + + }; + + // ********************************* END OF CONNECTOR TYPES ******************************************************************* + + // ********************************* ENDPOINT TYPES ******************************************************************* + + /** + * Class: Endpoints.Dot + * A round endpoint, with default radius 10 pixels. + */ + + /** + * Function: Constructor + * + * Parameters: + * + * radius - radius of the endpoint. defaults to 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + var self = this; + params = params || {}; + this.radius = params.radius || 10; + this.defaultOffset = 0.5 * this.radius; + this.defaultInnerRadius = this.radius / 3; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var r = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - r; + var y = anchorPoint[1] - r; + return [ x, y, r * 2, r * 2, r ]; + }; + }; + + /** + * Class: Endpoints.Rectangle + * A Rectangular Endpoint, with default size 20x20. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the endpoint. defaults to 20 pixels. + * height - height of the endpoint. defaults to 20 pixels. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + var self = this; + params = params || {}; + this.width = params.width || 20; + this.height = params.height || 20; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + return [ x, y, width, height]; + }; + }; + + /** + * Class: Endpoints.Image + * Draws an image as the Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * src - location of the image to use. + */ + jsPlumb.Endpoints.Image = function(params) { + + jsPlumb.DOMElementComponent.apply(this, arguments); + + var self = this, initialized = false; + this.img = new Image(); + self.ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.src || params.url; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + self.anchorPoint = anchorPoint; + if (self.ready) return [anchorPoint[0] - self.img.width / 2, anchorPoint[1] - self.img.height/ 2, self.img.width, self.img.height]; + else return [0,0,0,0]; + }; + + self.canvas = document.createElement("img"), initialized = false; + self.canvas.style["margin"] = 0; + self.canvas.style["padding"] = 0; + self.canvas.style["outline"] = 0; + self.canvas.style["position"] = "absolute"; + self.canvas.className = jsPlumb.endpointClass; + jsPlumb.appendElement(self.canvas, params.parent); + self.attachListeners(self.canvas, self); + + var actuallyPaint = function(d, style, anchor) { + if (!initialized) { + self.canvas.setAttribute("src", self.img.src); + initialized = true; + } + var width = self.img.width, + height = self.img.height, + x = self.anchorPoint[0] - (width/2), + y = self.anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(self.canvas, x, y, width, height); + }; + + this.paint = function(d, style, anchor) { + if (self.ready) { + actuallyPaint(d, style, anchor); + } + else { + window.setTimeout(function() { + self.paint(d, style, anchor); + }, 200); + } + }; + }; + + /** + * Class: Endpoints.Blank + * An Endpoint that paints nothing on the screen, and cannot be interacted with using the mouse. There are no constructor parameters for this Endpoint. + */ + jsPlumb.Endpoints.Blank = function() { + jsPlumb.DOMElementComponent.apply(this, arguments); + this.compute = function() { + return [0,0,0,0]; + }; + + self.canvas = document.createElement("div"); + this.paint = function() { }; + }; + + /** + * Class: Endpoints.Triangle + * A triangular Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the triangle's base. defaults to 55 pixels. + * height - height of the triangle from base to apex. defaults to 55 pixels. + */ + jsPlumb.Endpoints.Triangle = function(params) { + params = params || { }; + params.width = params.width || 55; + param.height = params.height || 55; + this.width = params.width; + this.height = params.height; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + return [ x, y, width, height ]; + }; + }; +// ********************************* END OF ENDPOINT TYPES ******************************************************************* + + +// ********************************* OVERLAY DEFINITIONS *********************************************************************** + + /** + * Class: Overlays.Arrow + * + * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length + * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction + * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line + * across the tail. + */ + /** + * Function: Constructor + * + * Parameters: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + params = params || {}; + var self = this; + this.length = params.length || 20; + this.width = params.width || 20; + this.id = params.id; + this.connection = params.connection; + var direction = (params.direction || 1) < 0 ? -1 : 1; + var paintStyle = params.paintStyle || { lineWidth:1 }; + this.loc = params.location == null ? 0.5 : params.location; + // how far along the arrow the lines folding back in come to. default is 62.3%. + var foldback = params.foldback || 0.623; + var _getFoldBackPoint = function(connector, loc) { + if (foldback == 0.5) return connector.pointOnPath(loc); + else { + var adj = 0.5 - foldback; // we calculate relative to the center + return connector.pointAlongPathFrom(loc, direction * self.length * adj); + } + }; + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + // this is the arrow head position + var hxy = connector.pointAlongPathFrom(self.loc, direction * (self.length / 2)); + // this is the center of the tail + var txy = connector.pointAlongPathFrom(self.loc, -1 * direction * (self.length / 2)), tx = txy.x, ty = txy.y; + // this is the tail vector + var tail = connector.perpendicularToPathAt(self.loc, self.width, -1 * direction * (self.length / 2)); + // this is the point the tail goes in to + var cxy = _getFoldBackPoint(connector, self.loc); + + // if loc = 1, then hxy should be flush with the element, or if direction == -1, the tail midpoint. + if (self.loc == 1) { + var lxy = connector.pointOnPath(self.loc); + // TODO determine why the 1.2.6 released version does not + // use 'direction' in the two equations below, yet both + // that and 1.3.0 still paint the arrows correctly. + var dx = (lxy.x - hxy.x) * direction, dy = (lxy.y - hxy.y) * direction; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + // if loc = 0, then tail midpoint should be flush with the element, or, if direction == -1, hxy should be. + if (self.loc == 0) { + var lxy = connector.pointOnPath(self.loc); + var tailMid = foldback > 1 ? cxy : { + x:tail[0].x + ((tail[1].x - tail[0].x) / 2), + y:tail[0].y + ((tail[1].y - tail[0].y) / 2) + }; + var dx = (lxy.x - tailMid.x) * direction, dy = (lxy.y - tailMid.y) * direction; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x); + var maxx = Math.max(hxy.x, tail[0].x, tail[1].x); + var miny = Math.min(hxy.y, tail[0].y, tail[1].y); + var maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + var d = { hxy:hxy, tail:tail, cxy:cxy }, + strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle, + fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle, + lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth; + + self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions); + + return [ minx, maxx, miny, maxy]; + }; + }; + + /** + * Class: Overlays.PlainArrow + * + * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + /** + * Class: Overlays.Diamond + * + * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40; + var p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + + + /** + * Class: Overlays.Label + * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb + * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter + * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it + * puts on the Label's 'style' attribute, so the end result is the same. + */ + /** + * Function: Constructor + * + * Parameters: + * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes + * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle. + * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * labelStyle - (deprecated) js object containing style instructions for the label. defaults to jsPlumb.Defaults.LabelStyle. + * borderWidth - (deprecated) width of a border to paint. defaults to zero. + * borderStyle - (deprecated) strokeStyle to use when painting the border, if necessary. + * + */ + jsPlumb.Overlays.Label = function(params) { + jsPlumb.DOMElementComponent.apply(this, arguments); + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.labelStyle.font = this.labelStyle.font || "12px sans-serif"; + this.label = params.label; + this.connection = params.connection; + this.id = params.id; + var self = this; + var labelWidth = null, labelHeight = null, labelText = null, labelPadding = null; + this.location = params.location || 0.5; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var initialised = false, + labelText = null, + div = document.createElement("div"); + div.style["position"] = "absolute"; + div.style["textAlign"] = "center"; + div.style["cursor"] = "pointer"; + div.style["font"] = self.labelStyle.font; + div.style["color"] = self.labelStyle.color || "black"; + if (self.labelStyle.fillStyle) div.style["background"] = self.labelStyle.fillStyle;//_convertStyle(self.labelStyle.fillStyle, true); + if (self.labelStyle.borderWidth > 0) { + var dStyle = self.labelStyle.borderStyle ? self.labelStyle.borderStyle/*_convertStyle(self.labelStyle.borderStyle, true)*/ : "black"; + div.style["border"] = self.labelStyle.borderWidth + "px solid " + dStyle; + } + if (self.labelStyle.padding) div.style["padding"] = self.labelStyle.padding; + + var clazz = params["_jsPlumb"].overlayClass + " " + + (self.labelStyle.cssClass ? self.labelStyle.cssClass : + params.cssClass ? params.cssClass : ""); + + div.className = clazz; + + jsPlumb.appendElement(div, params.connection.parent); + jsPlumb.getId(div); + self.attachListeners(div, self); + this.paint = function(connector, d, connectorDimensions) { + if (!initialised) { + connector.appendDisplayElement(div); + self.attachListeners(div, connector); + initialised = true; + } + div.style.left = (connectorDimensions[0] + d.minx) + "px"; + div.style.top = (connectorDimensions[1] + d.miny) + "px"; + }; + + this.getTextDimensions = function(connector) { + labelText = typeof self.label == 'function' ? self.label(self) : self.label; + div.innerHTML = labelText.replace(/\r\n/g, "
"); + var de = jsPlumb.CurrentLibrary.getElementObject(div), + s = jsPlumb.CurrentLibrary.getSize(de); + return {width:s[0], height:s[1]}; + }; + + this.computeMaxSize = function(connector) { + var td = self.getTextDimensions(connector); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + var td = self.getTextDimensions(connector); + if (td.width != null) { + var cxy = connector.pointOnPath(self.location); + + var minx = cxy.x - (td.width / 2); + var miny = cxy.y - (td.height / 2); + + self.paint(connector, { + minx:minx, + miny:miny, + td:td, + cxy:cxy + }, connectorDimensions); + + return [minx, minx+td.width, miny, miny+td.height]; + } + else return [0,0,0,0]; + }; + }; + + // ********************************* END OF OVERLAY DEFINITIONS *********************************************************************** + + // ********************************* OVERLAY CANVAS RENDERERS*********************************************************************** + + // ********************************* END OF OVERLAY CANVAS RENDERERS *********************************************************************** +})();;(function() { + + // http://ajaxian.com/archives/the-vml-changes-in-ie-8 + // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/ + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + + var vmlAttributeMap = { + "stroke-linejoin":"joinstyle", + "joinstyle":"joinstyle", + "endcap":"endcap", + "miterlimit":"miterlimit" + }; + + if (document.createStyleSheet) { + + // this is the style rule for IE7/6: it uses a CSS class, tidy. + document.createStyleSheet().addRule(".jsplumb_vml", "behavior:url(#default#VML);position:absolute;"); + + // these are for VML in IE8. you have to explicitly call out which elements + // you're going to expect to support VML! + // + // try to avoid IE8. it is recommended you set X-UA-Compatible="IE=7" if you can. + // + document.createStyleSheet().addRule("jsplumb\\:textbox", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:oval", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:rect", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:stroke", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:shape", "behavior:url(#default#VML);position:absolute;"); + + // in this page it is also mentioned that IE requires the extra arg to the namespace + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either. + // var iev = document.documentMode; + //if (!iev || iev < 8) + document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); + //else + // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML"); + } + + var scale = 1000, + _atts = function(o, atts) { + for (var i in atts) { + // IE8 fix: setattribute does not work after an element has been added to the dom! + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + //o.setAttribute(i, atts[i]); + o[i] = atts[i]; + } + }, + _node = function(name, d, atts) { + atts = atts || {}; + var o = document.createElement("jsplumb:" + name); + o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml"; + _pos(o, d); + _atts(o, atts); + return o; + }, + _pos = function(o,d) { + o.style.left = d[0] + "px"; + o.style.top = d[1] + "px"; + o.style.width= d[2] + "px"; + o.style.height= d[3] + "px"; + o.style.position = "absolute"; + }, + _conv = function(v) { + return Math.floor(v * scale); + }, + _convertStyle = function(s, ignoreAlpha) { + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + + return o; + }, + _applyStyles = function(node, style, component) { + var styleToWrite = {}; + if (style.strokeStyle) { + styleToWrite["stroked"] = "true"; + styleToWrite["strokecolor"] =_convertStyle(style.strokeStyle, true); + styleToWrite["strokeweight"] = style.lineWidth + "px"; + } + else styleToWrite["stroked"] = "false"; + + if (style.fillStyle) { + styleToWrite["filled"] = "true"; + styleToWrite["fillcolor"] = _convertStyle(style.fillStyle, true); + } + else styleToWrite["filled"] = "false"; + + if(style["dashstyle"]) { + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = style["dashstyle"]; + } + else if (style["stroke-dasharray"] && style["lineWidth"]) { + var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",", + parts = style["stroke-dasharray"].split(sep), + styleToUse = ""; + for(var i = 0; i < parts.length; i++) { + styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep); + } + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = styleToUse; + } + + _atts(node, styleToWrite); + }, + /* + * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. + */ + VmlComponent = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }, + /* + * Base class for Vml connectors. extends VmlComponent. + */ + VmlConnector = function(params) { + var self = this; + self.strokeNode = null; + self.canvas = null; + VmlComponent.apply(this, arguments); + clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : ""); + this.paint = function(d, style, anchor) { + if (style != null) { + var path = self.getPath(d), p = { "path":path }; + + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + outlineStyle = { + strokeStyle:_convertStyle(style.outlineColor), + lineWidth:outlineStrokeWidth + }; + + if (self.bgCanvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.bgCanvas = _node("shape", d, p); + jsPlumb.appendElement(self.bgCanvas, params.parent); + _pos(self.bgCanvas, d); + displayElements.push(self.bgCanvas); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.bgCanvas, d); + _atts(self.bgCanvas, p); + } + + _applyStyles(self.bgCanvas, outlineStyle, self); + } + + if (self.canvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.canvas = _node("shape", d, p); + jsPlumb.appendElement(self.canvas, params.parent); + displayElements.push(self.canvas); + + self.attachListeners(self.canvas, self); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.canvas, d); + _atts(self.canvas, p); + } + + _applyStyles(self.canvas, style, self); + } + }; + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + self.canvas.parentNode.appendChild(el); + displayElements.push(el); + }; + }, + /* + * + * Base class for Vml Endpoints. extends VmlComponent. + * + */ + VmlEndpoint = function(params) { + VmlComponent.apply(this, arguments); + var vml = null, self = this; + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function(d, style, anchor) { + var p = { }; + + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (vml == null) { + p["class"] = jsPlumb.endpointClass; + vml = self.getVml([0,0, d[2], d[3]], p, anchor); + self.canvas.appendChild(vml); + self.attachListeners(vml, self); + } + else { + //p["coordsize"] = "1,1";//(d[2] * scale) + "," + (d[3] * scale); again, unsure. + _pos(vml, [0,0, d[2], d[3]]); + _atts(vml, p); + } + + _applyStyles(vml, style); + }; + }; + + jsPlumb.Connectors.vml.Bezier = function() { + jsPlumb.Connectors.Bezier.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Straight = function() { + jsPlumb.Connectors.Straight.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Flowchart = function() { + jsPlumb.Connectors.Flowchart.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l"; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]); + } + // finally draw a line to the end + p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e"; + return p; + }; + }; + + jsPlumb.Endpoints.vml.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("oval", d, atts); }; + }; + + jsPlumb.Endpoints.vml.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("rect", d, atts); }; + }; + + /* + * VML Image Endpoint is the same as the default image endpoint. + */ + jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image; + + /** + * placeholder for Blank endpoint in vml renderer. + */ + jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank; + + /** + * VML Label renderer. uses the default label renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label; + + var AbstractVmlArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + VmlComponent.apply(this, arguments); + var self = this, canvas = null, path =null; + var getPath = function(d, connectorDimensions) { + return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) + + " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + + " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + + " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + + " x e"; + }; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) { + var p = {}; + if (strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] =_convertStyle(strokeStyle, true); + } + if (lineWidth) p["strokeweight"] = lineWidth + "px"; + if (fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = fillStyle; + } + var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + w = Math.abs(xmax - xmin), + h = Math.abs(ymax - ymin), + dim = [xmin, ymin, w, h]; + + // for VML, we create overlays using shapes that have the same dimensions and + // coordsize as their connector - overlays calculate themselves relative to the + // connector (it's how it's been done since the original canvas implementation, because + // for canvas that makes sense). + p["path"] = getPath(d, connectorDimensions); + p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale); + + dim[0] = connectorDimensions[0]; + dim[1] = connectorDimensions[1]; + dim[2] = connectorDimensions[2]; + dim[3] = connectorDimensions[3]; + + if (canvas == null) { + //p["class"] = jsPlumb.overlayClass; // TODO currentInstance? + canvas = _node("shape", dim, p); + connector.appendDisplayElement(canvas); + self.attachListeners(canvas, connector); + } + else { + _pos(canvas, dim); + _atts(canvas, p); + } + }; + }; + + jsPlumb.Overlays.vml.Arrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.vml.PlainArrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.vml.Diamond = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/** + * SVG support for jsPlumb. + * + * things to investigate: + * + * gradients: https://developer.mozilla.org/en/svg_in_html_introduction + * css:http://tutorials.jenkov.com/svg/svg-and-css.html + * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath + * pointer events: https://developer.mozilla.org/en/css/pointer-events + * + */ +;(function() { + + var svgAttributeMap = { + "stroke-linejoin":"stroke-linejoin", + "joinstyle":"stroke-linejoin", + "stroke-dashoffset":"stroke-dashoffset" + }; + + var ns = { + svg:"http://www.w3.org/2000/svg", + xhtml:"http://www.w3.org/1999/xhtml" + }, + _attr = function(node, attributes) { + for (var i in attributes) + node.setAttribute(i, "" + attributes[i]); + }, + _node = function(name, attributes) { + var n = document.createElementNS(ns.svg, name); + attributes = attributes || {}; + attributes["version"] = "1.1"; + attributes["xmnls"] = ns.xhtml; + _attr(n, attributes); + return n; + }, + _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; }, + _convertStyle = function(s, ignoreAlpha) { + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + + return o; + }, + _clearGradient = function(parent) { + for (var i = 0; i < parent.childNodes.length; i++) { + if (parent.childNodes[i].tagName == "linearGradient" || parent.childNodes[i].tagName == "radialGradient") + parent.removeChild(parent.childNodes[i]); + } + }, + _updateGradient = function(parent, node, style, dimensions) { + var id = "jsplumb_gradient_" + (new Date()).getTime(); + // first clear out any existing gradient + _clearGradient(parent); + // this checks for an 'offset' property in the gradient, and in the absence of it, assumes + // we want a linear gradient. if it's there, we create a radial gradient. + // it is possible that a more explicit means of defining the gradient type would be + // better. relying on 'offset' means that we can never have a radial gradient that uses + // some default offset, for instance. + if (!style.gradient.offset) { + var g = _node("linearGradient", {id:id}); + parent.appendChild(g); + } + else { + var g = _node("radialGradient", { + id:id + }); + parent.appendChild(g); + } + + // the svg radial gradient seems to treat stops in the reverse + // order to how canvas does it. so we want to keep all the maths the same, but + // iterate the actual style declarations in reverse order, if the x indexes are not in order. + for (var i = 0; i < style.gradient.stops.length; i++) { + // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of + // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments. + // so, not too concerned about leaving this in for now. + var styleToUse = i; + if (dimensions.length == 8) + styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i; + else + styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i; + var stopColor = _convertStyle(style.gradient.stops[styleToUse][1], true); + var s = _node("stop", {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor}); + g.appendChild(s); + } + var applyGradientTo = style.strokeStyle ? "stroke" : "fill"; + node.setAttribute("style", applyGradientTo + ":url(#" + id + ")"); + }, + _applyStyles = function(parent, node, style, dimensions) { + + if (style.gradient) { + _updateGradient(parent, node, style, dimensions); + } + else { + // make sure we clear any existing gradient + _clearGradient(parent); + node.setAttribute("style", ""); + } + + node.setAttribute("fill", style.fillStyle ? _convertStyle(style.fillStyle, true) : "none"); + node.setAttribute("stroke", style.strokeStyle ? _convertStyle(style.strokeStyle, true) : "none"); + if (style.lineWidth) { + node.setAttribute("stroke-width", style.lineWidth); + } + + // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like + // the syntax in VML but is actually kind of nasty: values are given in the pixel + // coordinate space, whereas in VML they are multiples of the width of the stroked + // line, which makes a lot more sense. for that reason, jsPlumb is supporting both + // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from + // VML, which will be the preferred method. the code below this converts a dashstyle + // attribute given in terms of stroke width into a pixel representation, by using the + // stroke's lineWidth. + if(style["stroke-dasharray"]) { + node.setAttribute("stroke-dasharray", style["stroke-dasharray"]); + } + if (style["dashstyle"] && style["lineWidth"]) { + var sep = style["dashstyle"].indexOf(",") == -1 ? " " : ",", + parts = style["dashstyle"].split(sep), + styleToUse = ""; + parts.forEach(function(p) { + styleToUse += (Math.floor(p * style.lineWidth) + sep); + }); + node.setAttribute("stroke-dasharray", styleToUse); + } + + // extra attributes such as join type, dash offset. + for (var i in svgAttributeMap) { + if (style[i]) { + node.setAttribute(svgAttributeMap[i], style[i]); + } + } + }, + _decodeFont = function(f) { + var r = /([0-9].)(p[xt])\s(.*)/; + var bits = f.match(r); + return {size:bits[1] + bits[2], font:bits[3]}; + }; + + /* + * Base class for SVG components. + */ + var SvgComponent = function(cssClass, originalArgs, pointerEventsSpec) { + var self = this; + pointerEventsSpec = pointerEventsSpec || "all"; + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + self.canvas = null, self.path = null, self.svg = null; + + this.setHover = function() { }; + + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.sizeCanvas(self.canvas,0,0,1,1); + + var clazz = cssClass + " " + (originalArgs[0].cssClass || ""); + + self.svg = _node("svg", { + "style":"", + "width":0, + "height":0, + "pointer-events":pointerEventsSpec, + "class": clazz + }); + + jsPlumb.appendElement(self.canvas, originalArgs[0]["parent"]); + self.canvas.appendChild(self.svg); + + // TODO this displayElement stuff is common between all components, across all + // renderers. would be best moved to jsPlumbUIComponent. + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + this.paint = function(d, style, anchor) { + if (style != null) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + _attr(self.svg, { + "style":_pos([0,0,d[2], d[3]]), + "width": d[2], + "height": d[3] + }); + self._paint.apply(this, arguments); + } + }; + }; + + /* + * Base class for SVG connectors. + */ + var SvgConnector = function(params) { + var self = this; + SvgComponent.apply(this, [ params["_jsPlumb"].connectorClass, arguments, "none" ]); + this._paint = function(d, style) { + var p = self.getPath(d), a = { "d":p }, outlineStyle = null; + a["pointer-events"] = "all"; + + // outline style. actually means drawing an svg object underneath the main one. + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + outlineStyle = { + strokeStyle:_convertStyle(style.outlineColor), + lineWidth:outlineStrokeWidth + }; + + if (self.bgPath == null) { + self.bgPath = _node("path", a); + self.svg.appendChild(self.bgPath); + self.attachListeners(self.bgPath, self); + } + else { + _attr(self.bgPath, a); + } + + _applyStyles(self.svg, self.bgPath, outlineStyle, d); + } + + + if (self.path == null) { + self.path = _node("path", a); + self.svg.appendChild(self.path); + self.attachListeners(self.path, self); + } + else { + _attr(self.path, a); + } + + _applyStyles(self.svg, self.path, style, d); + }; + }; + + /* + * SVG Bezier Connector + */ + jsPlumb.Connectors.svg.Bezier = function(params) { + jsPlumb.Connectors.Bezier.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]; }; + }; + + /* + * SVG straight line Connector + */ + jsPlumb.Connectors.svg.Straight = function(params) { + jsPlumb.Connectors.Straight.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; }; + }; + + jsPlumb.Connectors.svg.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "M " + dimensions[4] + "," + dimensions[5]; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)]; + } + // finally draw a line to the end + p = p + " " + dimensions[6] + "," + dimensions[7]; + return p; + }; + }; + + /* + * Base class for SVG endpoints. + */ + var SvgEndpoint = function(params) { + var self = this; + SvgComponent.apply(this, [ params["_jsPlumb"].endpointClass, arguments, "all" ]); + this._paint = function(d, style) { + var s = jsPlumb.extend({}, style); + if (s.outlineColor) { + s.strokeWidth = s.outlineWidth; + s.strokeStyle = _convertStyle(s.outlineColor, true); + } + + if (self.node == null) { + self.node = self.makeNode(d, s); + self.svg.appendChild(self.node); + self.attachListeners(self.node, self); + } + _applyStyles(self.svg, self.node, s, d); + _pos(self.node, d); + }; + }; + + /* + * SVG Dot Endpoint + */ + jsPlumb.Endpoints.svg.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("circle", { + "cx" : d[2] / 2, + "cy" : d[3] / 2, + "r" : d[2] / 2 + }); + }; + }; + + /* + * SVG Rectangle Endpoint + */ + jsPlumb.Endpoints.svg.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("rect", { + "width":d[2], + "height":d[3] + }); + }; + }; + + /* + * SVG Image Endpoint is the default image endpoint. + */ + jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image; + /* + * Blank endpoint in svg renderer is the default Blank endpoint. + */ + jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank; + /* + * Label endpoint in svg renderer is the default Label endpoint. + */ + jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label; + + + var AbstractSvgArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + var self = this, path =null; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + } + + _attr(path, { + "d" : makePath(d), + stroke : strokeStyle ? strokeStyle : null, + fill : fillStyle ? fillStyle : null + }); + }; + var makePath = function(d) { + return "M" + d.hxy.x + "," + d.hxy.y + + " L" + d.tail[0].x + "," + d.tail[0].y + + " L" + d.cxy.x + "," + d.cxy.y + + " L" + d.tail[1].x + "," + d.tail[1].y + + " L" + d.hxy.x + "," + d.hxy.y; + }; + }; + + jsPlumb.Overlays.svg.Arrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.svg.PlainArrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.svg.Diamond = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();;(function() { + +// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS ******************************************************************* + + // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. + var _connectionBeingDragged = null, + _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }, + _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }, + _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }, + _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, + _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }, + _setOffset = function(el, o) { jsPlumb.CurrentLibrary.setOffset(el, o); }; + + /* + * Class:CanvasMouseAdapter + * Provides support for mouse events on canvases. + */ + var CanvasMouseAdapter = function() { + var self = this; + self.overlayPlacements = []; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + jsPlumb.EventGenerator.apply(this, arguments); + /** + * returns whether or not the given event is ojver a painted area of the canvas. + */ + this._over = function(e) { + var o = _getOffset(_getElementObject(self.canvas)), + pageXY = _pageXY(e), + x = pageXY[0] - o.left, y = pageXY[1] - o.top; + if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + return false; + }; + + var _mouseover = false; + var _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false; + var _nullSafeHasClass = function(el, clazz) { + return el != null && _hasClass(el, clazz); + }; + this.mousemove = function(e) { + var pageXY = _pageXY(e), clientXY = _clientXY(e), + ee = document.elementFromPoint(clientXY[0], clientXY[1]), + eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); + var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); + if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.fire("mouseenter", self, e); + return true; + } + // TODO here there is a remote chance that the overlay the mouse moved onto + // is actually not an overlay for the current component. a more thorough check would + // be to ensure the overlay belonged to the current component. + else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { + _mouseover = false; + self.fire("mouseexit", self, e); + } + self.fire("mousemove", self, e); + }; + + this.click = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); + self.fire("mousedown", self, e); + } + }; + + this.mouseup = function(e) { + //if (self == _connectionBeingDragged) _connectionBeingDragged = null; + _mouseDown = false; + self.fire("mouseup", self, e); + }; + }; + + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + jsPlumb.appendElement(canvas, params.parent); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + params["_jsPlumb"].getId(canvas, params.uuid); + + return canvas; + }; + + /** + * Class:CanvasConnector + * Superclass for Canvas Connector renderers. + */ + var CanvasConnector = jsPlumb.CanvasConnector = function(params) { + + CanvasMouseAdapter.apply(this, arguments); + + var _paintOneStyle = function(dim, aStyle) { + self.ctx.save(); + jsPlumb.extend(self.ctx, aStyle); + if (aStyle.gradient) { + var g = self.createGradient(dim, self.ctx); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + self.ctx.strokeStyle = g; + } + self._paint(dim); + self.ctx.restore(); + }; + + var self = this, + clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent + }); + self.ctx = self.canvas.getContext("2d"); + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + self.paint = function(dim, style) { + if (style != null) { + + jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); + + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + _paintOneStyle(dim, outlineStyle); + } + _paintOneStyle(dim, style); + } + }; + }; + + /** + * Class:CanvasEndpoint + * Superclass for Canvas Endpoint renderers. + */ + var CanvasEndpoint = function(params) { + var self = this; + CanvasMouseAdapter.apply(this, arguments); + var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent + }); + self.ctx = self.canvas.getContext("2d"); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + // _paintOneStyle(d, outlineStyle); + } + + self._paint.apply(this, arguments); + }; + }; + + jsPlumb.Endpoints.canvas.Dot = function(params) { + var self = this; + jsPlumb.Endpoints.Dot.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }; + var calculateAdjustments = function(gradient) { + var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius; + gradient.offset && (offsetAdjustment = parseValue(gradient.offset)); + gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius)); + return [offsetAdjustment, innerRadius]; + }; + this._paint = function(d, style, anchor) { + if (style != null) { + var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(); + jsPlumb.extend(ctx, style); + if (style.gradient) { + var adjustments = calculateAdjustments(style.gradient), + yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0], + xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0], + g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + ctx.beginPath(); + ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + } + }; + }; + + jsPlumb.Endpoints.canvas.Rectangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) { + + var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(); + jsPlumb.extend(ctx, style); + + /* canvas gradient */ + if (style.gradient) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, d[2], d[3]); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + jsPlumb.Endpoints.canvas.Triangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Triangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) + { + var width = d[2], height = d[3], x = d[0], y = d[1]; + + var ctx = self.canvas.getContext('2d'); + var offsetX = 0, offsetY = 0, angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = style.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + /* + * Canvas Image Endpoint: uses the default version, which creates an tag. + */ + jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image; + + /* + * Blank endpoint in all renderers is just the default Blank endpoint. + */ + jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank; + + /* + * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Bezier = function() { + var self = this; + jsPlumb.Connectors.Bezier.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO i doubt this handles the case that source and target are swapped. + this.createGradient = function(dim, ctx, swap) { + return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + /* + * Canvas straight line Connector. Draws a straight line onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Straight = function() { + var self = this; + jsPlumb.Connectors.Straight.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO this does not handle the case that src and target are swapped. + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + jsPlumb.Connectors.canvas.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + +// ********************************* END OF CANVAS RENDERERS ******************************************************************* + + jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label; + + /** + * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. + */ + var CanvasOverlay = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }; + + var AbstractCanvasArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + CanvasOverlay.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + var ctx = connector.ctx; + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(d.hxy.x, d.hxy.y); + ctx.lineTo(d.tail[0].x, d.tail[0].y); + ctx.lineTo(d.cxy.x, d.cxy.y); + ctx.lineTo(d.tail[1].x, d.tail[1].y); + ctx.lineTo(d.hxy.x, d.hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + }; + + jsPlumb.Overlays.canvas.Arrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.canvas.PlainArrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.canvas.Diamond = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/* + * mootools.jsPlumb 1.3.0-RC1 + * + * MooTools specific functionality for jsPlumb. + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + * NOTE: for production usage you should use mootools.jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, + * in a minified file. + * + * Dual licensed under MIT and GPL2. + * + */ + +;(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function(now) { + this.parent(now); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }; + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + var _getElementObject = function(el) { + return $(el); + }; + + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + m.start(properties); + }, + + appendElement : function(child, parent) { + _getElementObject(parent).grab(child); + }, + + bind : function(el, event, callback) { + el = _getElementObject(el); + el.addEvent(event, callback); + }, + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.event.clientX, eventObject.event.clientY]; + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el); + var drags = _draggablesById[id]; + return drags[0].scope; + }, + + getElementObject : _getElementObject, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + getPageXY : function(eventObject) { + return [eventObject.event.pageX, eventObject.event.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).getParent(); + }, + + getScrollLeft : function(el) { + return null; + }, + + getScrollTop : function(el) { + return null; + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[0], + p = jsPlumb.CurrentLibrary.getElementObject(ui).getPosition(); + return { left:p.x, top:p.y }; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options) { + var id = jsPlumb.getId(el); + var drag = _draggablesById[id]; + if (!drag) { + var originalZIndex = 0, originalCursor = null; + var dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + options['onStart'] = jsPlumb.wrap(options['onStart'], function() + { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() + { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + }); + + // DROPPABLES: + var scope = options['scope'] || jsPlumb.Defaults.Scope; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop'); + } + }; + + drag = new Drag.Move(el, options); + drag.scope = scope; + //console.log("drag scope initialized to ", scope); + _add(_draggablesByScope, scope, drag); + _add(_draggablesById, el.get("id"), drag); + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + initDroppable : function(el, options) { + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _add(_droppables, scope, el); + var id = jsPlumb.getId(el); + _droppableOptions[id] = options; + var filterFunc = function(entry) { return entry.element != el; }; + var draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + isAlreadyDraggable : function(el) { + return _draggablesById[jsPlumb.getId(el)] != null; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + return (typeof Drag != undefined && typeof Drag.Move != undefined); + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + _getElementObject(element).dispose(); // ?? + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + setDragScope : function(el, scope) { + var drag = _draggablesById[el.get("id")]; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + drag[0].droppables = droppables; + }, + + setOffset : function(el, o) { + _getElementObject(el).setPosition({x:o.left, y:o.top}); + } + }; + + window.addEvent('domready', jsPlumb.init); +})(); +(function(){if(typeof Math.sgn=="undefined")Math.sgn=function(a){return a==0?0:a>0?1:-1};var p={subtract:function(a,b){return{x:a.x-b.x,y:a.y-b.y}},dotProduct:function(a,b){return a.x*b.x+a.y*b.y},square:function(a){return Math.sqrt(a.x*a.x+a.y*a.y)},scale:function(a,b){return{x:a.x*b,y:a.y*b}}},y=Math.pow(2,-65),u=function(a,b){for(var g=[],d=b.length-1,h=2*d-1,f=[],c=[],l=[],k=[],i=[[1,0.6,0.3,0.1],[0.4,0.6,0.6,0.4],[0.1,0.3,0.6,1]],e=0;e<=d;e++)f[e]=p.subtract(b[e],a);for(e=0;e<=d-1;e++){c[e]= +p.subtract(b[e+1],b[e]);c[e]=p.scale(c[e],3)}for(e=0;e<=d-1;e++)for(var m=0;m<=d;m++){l[e]||(l[e]=[]);l[e][m]=p.dotProduct(c[e],f[m])}for(e=0;e<=h;e++){k[e]||(k[e]=[]);k[e].y=0;k[e].x=parseFloat(e)/h}h=d-1;for(f=0;f<=d+h;f++){c=Math.min(f,d);for(e=Math.max(0,f-h);e<=c;e++){j=f-e;k[e+j].y+=l[j][e]*i[j][e]}}d=b.length-1;k=s(k,2*d-1,g,0);h=p.subtract(a,b[0]);l=p.square(h);for(e=i=0;e=64){g[0]=(a[0].x+a[b].x)/2;return 1}var n,o,q;k=a[0].y-a[b].y;i=a[b].x-a[0].x;e=a[0].x*a[b].y-a[b].x*a[0].y;m=max_distance_below=0;for(o=1;om)m=q;else if(q0?1:-1,c=null;h=0){delete (ax[ay]);ax.splice(ay,1);return true}}}return false},w=function(ay,ax){return J(ay,function(az,aA){ag[aA]=ax;if(i.CurrentLibrary.isDragSupported(az)){i.CurrentLibrary.setDraggable(az,ax)}})},ao=function(ax,ay){V(c(ax,"id"),function(az){az.canvas.style.display=ay})},O=function(ax){return J(ax,function(az,ay){var aA=ag[ay]==null?Y:ag[ay];aA=!aA;ag[ay]=aA;i.CurrentLibrary.setDraggable(az,aA);return aA})},y=function(ax){V(ax,function(az){var ay=("none"==az.canvas.style.display);az.canvas.style.display=ay?"block":"none"})},F=function(aC){var aA=aC.timestamp,ax=aC.recalc,aB=aC.offset,ay=aC.elId;if(!ax){if(aA&&aA===D[ay]){return W[ay]}}if(ax||aB==null){var az=v(ay);if(az!=null){Q[ay]=b(az);W[ay]=n(az);D[ay]=aA}}else{W[ay]=aB}return W[ay]},aw=function(ax,ay){var az=v(ax);var aA=c(az,"id");if(!aA||aA=="undefined"){if(arguments.length==2&&arguments[1]!=undefined){aA=ay}else{aA="jsPlumb_"+ar()}e(az,"id",aA)}return aA},am=function(az,ax,ay){az=az||function(){};ax=ax||function(){};return function(){var aA=null;try{aA=ax.apply(this,arguments)}catch(aB){k(E,"jsPlumb function failed : "+aB)}if(ay==null||(aA!==ay)){try{az.apply(this,arguments)}catch(aB){k(E,"wrapped function failed : "+aB)}}return aA}};this.connectorClass="_jsPlumb_connector";this.endpointClass="_jsPlumb_endpoint";this.overlayClass="_jsPlumb_overlay";this.Anchors={};this.Connectors={canvas:{},svg:{},vml:{}};this.Endpoints={canvas:{},svg:{},vml:{}};this.Overlays={canvas:{},svg:{},vml:{}};this.addEndpoint=function(az,aA,aJ){aJ=aJ||{};var ay=i.extend({},aJ);i.extend(ay,aA);ay.endpoint=ay.endpoint||E.Defaults.Endpoint||i.Defaults.Endpoint;ay.paintStyle=ay.paintStyle||E.Defaults.EndpointStyle||i.Defaults.EndpointStyle;az=x(az);var aB=[],aE=az.length&&az.constructor!=String?az:[az];for(var aC=0;aC0?l(aK,aJ)!=-1:true},aB=aE.length>1?{}:[],aH=function(aK,aL){if(aE.length>1){var aJ=aB[aK];if(aJ==null){aJ=[];aB[aK]=aJ}aJ.push(aL)}else{aB.push(aL)}};for(var aA in M){if(ay(aE,aA)){for(var az=0;az=4){ay.orientation=[arguments[2],arguments[3]]}if(arguments.length==6){ay.offsets=[arguments[4],arguments[5]]}}var aD=new ac(ay);aD.clone=function(){return new ac(ay)};return aD};this.makeAnchors=function(ay){var az=[];for(var ax=0;ax0?aG[0]:null;var aC=aG.length>0?0:-1;this.locked=false;var aF=this;var aB=function(aJ,aH,aN,aM,aI){var aL=aM[0]+(aJ.x*aI[0]),aK=aM[1]+(aJ.y*aI[1]);return Math.sqrt(Math.pow(aH-aL,2)+Math.pow(aN-aK,2))};var ax=ay||function(aR,aI,aJ,aK,aH){var aM=aJ[0]+(aK[0]/2),aL=aJ[1]+(aK[1]/2);var aO=-1,aQ=Infinity;for(var aN=0;aNaX){aX=a4}}var a8=this.connector.compute(aZ,ba,this.endpoints[be].anchor,this.endpoints[aW].anchor,aI.paintStyleInUse.lineWidth,aX);aI.connector.paint(a8,aI.paintStyleInUse);for(var a9=0;a9=0){aM.connections.splice(aY,1);if(!a1){var a0=aZ.endpoints[0]==aM?aZ.endpoints[1]:aZ.endpoints[0];a0.detach(aZ,true);if(aZ.endpointToDeleteOnDetach&&aZ.endpointToDeleteOnDetach.connections.length==0){i.deleteEndpoint(aZ.endpointToDeleteOnDetach)}}R(aZ.connector.getDisplayElements(),aZ.parent);N(M,aZ.scope,aZ);if(!a1){ad(aZ)}}};this.detachAll=function(){while(aM.connections.length>0){aM.detach(aM.connections[0])}};this.detachFrom=function(aZ){var a0=[];for(var aY=0;aY=0){aM.connections.splice(aY,1)}};this.getElement=function(){return aL};this.getUuid=function(){return aH};this.makeInPlaceCopy=function(){return an({anchor:aM.anchor,source:aL,paintStyle:this.paintStyle,endpoint:aK})};this.isConnectedTo=function(a0){var aZ=false;if(a0){for(var aY=0;aY0){var a8=aM.connections[0];var ba=a8.endpoints[0]==aM?1:0;var a3=ba==0?a8.sourceId:a8.targetId;var a7=W[a3],a9=Q[a3];aZ.txy=[a7.left,a7.top];aZ.twh=a9;aZ.tElement=a8.endpoints[ba]}}a4=aM.anchor.compute(aZ)}var a6=aK.compute(a4,aM.anchor.getOrientation(),aM.paintStyleInUse,a2||aM.paintStyleInUse);aK.paint(a6,aM.paintStyleInUse,aM.anchor);aM.timestamp=a5}};this.repaint=this.paint;this.removeConnection=this.detach;if(aX.isSource&&i.CurrentLibrary.isDragSupported(aL)){var aR=null,aN=null,aQ=null,ax=false,aA=null;var aC=function(){aQ=aM.connectorSelector();if(aM.isFull()&&!aI){return false}F({elId:aE});aB=aM.makeInPlaceCopy();aB.paint();aR=document.createElement("div");aR.style.position="absolute";var a5=v(aR);S(aR,aM.parent);var aY=aw(a5);var a6=v(aB.canvas),a4=i.CurrentLibrary.getOffset(a6),a0=aB.canvas.offsetParent.tagName.toLowerCase()==="body"?{left:0,top:0}:n(aB.canvas.offsetParent);i.CurrentLibrary.setOffset(aR,{left:a4.left-a0.left,top:a4.top-a0.top});F({elId:aY});e(v(aM.canvas),"dragId",aY);e(v(aM.canvas),"elId",aE);var a7=new I({reference:aM.anchor,referenceCanvas:aM.canvas});aV=an({paintStyle:aM.paintStyle,endpoint:aK,anchor:a7,source:a5});if(aQ==null){aM.anchor.locked=true;aQ=au({sourceEndpoint:aM,targetEndpoint:aV,source:v(aL),target:v(aR),anchors:[aM.anchor,a7],paintStyle:aX.connectorStyle,hoverPaintStyle:aX.connectorHoverStyle,connector:aX.connector,overlays:aX.connectorOverlays});aQ.connector.setHover(false)}else{ax=true;aQ.connector.setHover(false);aD(v(aB.canvas));var aZ=aQ.sourceId==aE?0:1;aQ.floatingAnchorIndex=aZ;aM.detachFromConnection(aQ);var a3=v(aM.canvas);var a2=i.CurrentLibrary.getDragScope(a3);e(a3,"originalScope",a2);var a1="scope_"+(new Date()).getTime();if(aZ==0){aA=[aQ.source,aQ.sourceId,aU,a2];aQ.source=v(aR);aQ.sourceId=aY}else{aA=[aQ.target,aQ.targetId,aU,a2];aQ.target=v(aR);aQ.targetId=aY}i.CurrentLibrary.setDragScope(aU,a1);aQ.endpoints[aZ==0?1:0].anchor.locked=true;aQ.suspendedEndpoint=aQ.endpoints[aZ];aQ.endpoints[aZ]=aV}Z[aY]=aQ;aV.addConnection(aQ);T(ah,aY,aV);E.currentlyDragging=true};var ay=i.CurrentLibrary,aT=aX.dragOptions||{},aO=i.extend({},ay.defaultDragOptions),aP=ay.dragEvents.start,aW=ay.dragEvents.stop,aG=ay.dragEvents.drag;aT=i.extend(aO,aT);aT.scope=aT.scope||aM.scope;aT[aP]=am(aT[aP],aC);aT[aG]=am(aT[aG],function(){var aY=i.CurrentLibrary.getUIPosition(arguments);i.CurrentLibrary.setOffset(aR,aY);at(v(aR),aY)});aT[aW]=am(aT[aW],function(){N(ah,aN,aV);R([aR,aV.canvas],aL);ae(aB.canvas,aL);var aY=aQ.floatingAnchorIndex==null?1:aQ.floatingAnchorIndex;aQ.endpoints[aY==0?1:0].anchor.locked=false;if(aQ.endpoints[aY]==aV){if(ax&&aQ.suspendedEndpoint){if(aY==0){aQ.source=aA[0];aQ.sourceId=aA[1]}else{aQ.target=aA[0];aQ.targetId=aA[1]}i.CurrentLibrary.setDragScope(aA[2],aA[3]);aQ.endpoints[aY]=aQ.suspendedEndpoint;if(aJ){aQ.floatingAnchorIndex=null;aQ.suspendedEndpoint.addConnection(aQ);i.repaint(aA[1])}else{aQ.endpoints[aY==0?1:0].detach(aQ)}}else{R(aQ.connector.getDisplayElements(),aM.parent);aM.detachFromConnection(aQ)}}aM.anchor.locked=false;aM.paint();aQ.setHover(false);aQ.repaint();aQ=null;delete aB;delete ah[aV.elementId];aV=null;delete aV;E.currentlyDragging=false});var aU=v(aM.canvas);i.CurrentLibrary.initDraggable(aU,aT)}var aD=function(a1){if(aX.isTarget&&i.CurrentLibrary.isDropSupported(aL)){var aY=aX.dropOptions||E.Defaults.DropOptions||i.Defaults.DropOptions;aY=i.extend({},aY);aY.scope=aY.scope||aM.scope;var a4=null;var a2=i.CurrentLibrary.dragEvents.drop;var a3=i.CurrentLibrary.dragEvents.over;var aZ=i.CurrentLibrary.dragEvents.out;var a0=function(){var bd=v(i.CurrentLibrary.getDragObject(arguments));var a5=c(bd,"dragId");var a7=c(bd,"elId");var bc=c(bd,"originalScope");if(bc){i.CurrentLibrary.setDragScope(bd,bc)}var a9=Z[a5];var ba=a9.floatingAnchorIndex==null?1:a9.floatingAnchorIndex,bb=ba==0?1:0;if(!aM.isFull()&&!(ba==0&&!aM.isSource)&&!(ba==1&&!aM.isTarget)){if(ba==0){a9.source=aL;a9.sourceId=aE}else{a9.target=aL;a9.targetId=aE}a9.endpoints[ba].detachFromConnection(a9);if(a9.suspendedEndpoint){a9.suspendedEndpoint.detachFromConnection(a9)}a9.endpoints[ba]=aM;aM.addConnection(a9);if(!a9.suspendedEndpoint){T(M,a9.scope,a9);P(aL,aX.draggable,{})}else{var a8=a9.suspendedEndpoint.getElement(),a6=a9.suspendedEndpoint.elementId;E.fire("jsPlumbConnectionDetached",{source:ba==0?a8:a9.source,target:ba==1?a8:a9.target,sourceId:ba==0?a6:a9.sourceId,targetId:ba==1?a6:a9.targetId,sourceEndpoint:ba==0?a9.suspendedEndpoint:a9.endpoints[0],targetEndpoint:ba==1?a9.suspendedEndpoint:a9.endpoints[1],connection:a9})}i.repaint(a7);E.fire("jsPlumbConnection",{source:a9.source,target:a9.target,sourceId:a9.sourceId,targetId:a9.targetId,sourceEndpoint:a9.endpoints[0],targetEndpoint:a9.endpoints[1],connection:a9})}E.currentlyDragging=false;delete Z[a5]};aY[a2]=am(aY[a2],a0);aY[a3]=am(aY[a3],function(){var a6=i.CurrentLibrary.getDragObject(arguments);var a8=c(v(a6),"dragId");var a7=Z[a8];if(a7!=null){var a5=a7.floatingAnchorIndex==null?1:a7.floatingAnchorIndex;a7.endpoints[a5].anchor.over(aM.anchor)}});aY[aZ]=am(aY[aZ],function(){var a6=i.CurrentLibrary.getDragObject(arguments),a8=c(v(a6),"dragId"),a7=Z[a8];if(a7!=null){var a5=a7.floatingAnchorIndex==null?1:a7.floatingAnchorIndex;a7.endpoints[a5].anchor.out()}});i.CurrentLibrary.initDroppable(a1,aY)}};aD(v(aM.canvas));return aM}};var i=window.jsPlumb=new r();i.getInstance=function(x){var w=new r(x);return w};var m=function(w,B,A,z){return function(){return i.makeAnchor(w,B,A,z)}};i.Anchors.TopCenter=m(0.5,0,0,-1);i.Anchors.BottomCenter=m(0.5,1,0,1);i.Anchors.LeftMiddle=m(0,0.5,-1,0);i.Anchors.RightMiddle=m(1,0.5,1,0);i.Anchors.Center=m(0.5,0.5,0,0);i.Anchors.TopRight=m(1,0,0,-1);i.Anchors.BottomRight=m(1,1,0,1);i.Anchors.TopLeft=m(0,0,0,-1);i.Anchors.BottomLeft=m(0,1,0,1);i.Defaults.DynamicAnchors=function(){return i.makeAnchors(["TopCenter","RightMiddle","BottomCenter","LeftMiddle"])};i.Anchors.AutoDefault=function(){return i.makeDynamicAnchor(i.Defaults.DynamicAnchors())}})();(function(){jsPlumb.DOMElementComponent=function(a){jsPlumb.jsPlumbUIComponent.apply(this,arguments);this.mousemove=this.dblclick=this.click=this.mousedown=this.mouseup=function(b){}};jsPlumb.Connectors.Straight=function(){var n=this;var g=null;var c,h,l,k,i,d,m,f,e,b,a;this.compute=function(r,F,B,o,v,q){var E=Math.abs(r[0]-F[0]);var u=Math.abs(r[1]-F[1]);var z=false,s=false;var t=0.45*E,p=0.45*u;E*=1.9;u*=1.9;var C=Math.min(r[0],F[0])-t;var A=Math.min(r[1],F[1])-p;var D=Math.max(2*v,q);if(E0?1:-1;var t=Math.abs(u*Math.sin(d));if(e>a){t=t*-1}var o=Math.abs(u*Math.cos(d));if(f>b){o=o*-1}return{x:s.x+(r*o),y:s.y+(r*t)}};this.perpendicularToPathAt=function(s,t,w){var u=n.pointAlongPathFrom(s,w);var r=n.gradientAtPoint(u.location);var q=Math.atan(-1/r);var v=t/2*Math.sin(q);var o=t/2*Math.cos(q);return[{x:u.x+o,y:u.y+v},{x:u.x-o,y:u.y-v}]}};jsPlumb.Connectors.Bezier=function(e){var o=this;e=e||{};this.majorAnchor=e.curviness||150;this.minorAnchor=10;var h=null;this._findControlPoint=function(z,q,u,x,r){var w=x.getOrientation(),y=r.getOrientation();var t=w[0]!=y[0]||w[1]==y[1];var s=[];var A=o.majorAnchor,v=o.minorAnchor;if(!t){if(w[0]==0){s.push(q[0]l){l=u}if(x<0){d+=x;var y=Math.abs(x);l+=y;n[0]+=y;i+=y;b+=y;m[0]+=y}var G=Math.min(f,a);var E=Math.min(n[1],m[1]);var t=Math.min(G,E);var z=Math.max(f,a);var w=Math.max(n[1],m[1]);var r=Math.max(z,w);if(r>g){g=r}if(t<0){c+=t;var v=Math.abs(t);g+=v;n[1]+=v;f+=v;a+=v;m[1]+=v}if(D&&l=s){q=t;r=(s-l[t][0])/a[t];break}}return{segment:i[q],proportion:r,index:q}};this.compute=function(Q,v,P,r,q,H){i=[];h=[];a=[];g=[];segmentProportionals=[];d=v[0]t[0]?t[0]+((1-x)*r)-y:t[2]+(x*r)+y,y:q==0?t[3]:t[3]>t[1]?t[1]+((1-x)*r)-y:t[3]+(x*r)+y,segmentInfo:v};return w};this.perpendicularToPathAt=function(t,u,z){var v=n.pointAlongPathFrom(t,z);var s=h[v.segmentInfo.index];var r=Math.atan(-1/s);var w=u/2*Math.sin(r);var q=u/2*Math.cos(r);return[{x:v.x+q,y:v.y+w},{x:v.x-q,y:v.y-w}]}};jsPlumb.Endpoints.Dot=function(b){var a=this;b=b||{};this.radius=b.radius||10;this.defaultOffset=0.5*this.radius;this.defaultInnerRadius=this.radius/3;this.compute=function(g,d,i,f){var e=i.radius||a.radius;var c=g[0]-e;var h=g[1]-e;return[c,h,e*2,e*2,e]}};jsPlumb.Endpoints.Rectangle=function(b){var a=this;b=b||{};this.width=b.width||20;this.height=b.height||20;this.compute=function(h,e,k,g){var f=k.width||a.width;var d=k.height||a.height;var c=h[0]-(f/2);var i=h[1]-(d/2);return[c,i,f,d]}};jsPlumb.Endpoints.Image=function(d){jsPlumb.DOMElementComponent.apply(this,arguments);var a=this,c=false;this.img=new Image();a.ready=false;this.img.onload=function(){a.ready=true};this.img.src=d.src||d.url;this.compute=function(g,e,h,f){a.anchorPoint=g;if(a.ready){return[g[0]-a.img.width/2,g[1]-a.img.height/2,a.img.width,a.img.height]}else{return[0,0,0,0]}};a.canvas=document.createElement("img"),c=false;a.canvas.style.margin=0;a.canvas.style.padding=0;a.canvas.style.outline=0;a.canvas.style.position="absolute";a.canvas.className=jsPlumb.endpointClass;jsPlumb.appendElement(a.canvas,d.parent);a.attachListeners(a.canvas,a);var b=function(k,i,g){if(!c){a.canvas.setAttribute("src",a.img.src);c=true}var h=a.img.width,f=a.img.height,e=a.anchorPoint[0]-(h/2),l=a.anchorPoint[1]-(f/2);jsPlumb.sizeCanvas(a.canvas,e,l,h,f)};this.paint=function(g,f,e){if(a.ready){b(g,f,e)}else{window.setTimeout(function(){a.paint(g,f,e)},200)}}};jsPlumb.Endpoints.Blank=function(){jsPlumb.DOMElementComponent.apply(this,arguments);this.compute=function(){return[0,0,0,0]};self.canvas=document.createElement("div");this.paint=function(){}};jsPlumb.Endpoints.Triangle=function(a){a=a||{};a.width=a.width||55;param.height=a.height||55;this.width=a.width;this.height=a.height;this.compute=function(g,d,i,f){var e=i.width||self.width;var c=i.height||self.height;var b=g[0]-(e/2);var h=g[1]-(c/2);return[b,h,e,c]}};jsPlumb.Overlays.Arrow=function(f){f=f||{};var b=this;this.length=f.length||20;this.width=f.width||20;this.id=f.id;this.connection=f.connection;var e=(f.direction||1)<0?-1:1;var c=f.paintStyle||{lineWidth:1};this.loc=f.location==null?0.5:f.location;var a=f.foldback||0.623;var d=function(g,i){if(a==0.5){return g.pointOnPath(i)}else{var h=0.5-a;return g.pointAlongPathFrom(i,e*b.length*h)}};this.computeMaxSize=function(){return b.width*1.5};this.draw=function(k,q,w){var y=k.pointAlongPathFrom(b.loc,e*(b.length/2));var t=k.pointAlongPathFrom(b.loc,-1*e*(b.length/2)),B=t.x,A=t.y;var r=k.perpendicularToPathAt(b.loc,b.width,-1*e*(b.length/2));var i=d(k,b.loc);if(b.loc==1){var h=k.pointOnPath(b.loc);var v=(h.x-y.x)*e,u=(h.y-y.y)*e;i.x+=v;i.y+=u;t.x+=v;t.y+=u;r[0].x+=v;r[0].y+=u;r[1].x+=v;r[1].y+=u;y.x+=v;y.y+=u}if(b.loc==0){var h=k.pointOnPath(b.loc);var s=a>1?i:{x:r[0].x+((r[1].x-r[0].x)/2),y:r[0].y+((r[1].y-r[0].y)/2)};var v=(h.x-s.x)*e,u=(h.y-s.y)*e;i.x+=v;i.y+=u;t.x+=v;t.y+=u;r[0].x+=v;r[0].y+=u;r[1].x+=v;r[1].y+=u;y.x+=v;y.y+=u}var o=Math.min(y.x,r[0].x,r[1].x);var n=Math.max(y.x,r[0].x,r[1].x);var m=Math.min(y.y,r[0].y,r[1].y);var l=Math.max(y.y,r[0].y,r[1].y);var z={hxy:y,tail:r,cxy:i},x=c.strokeStyle||q.strokeStyle,p=c.fillStyle||q.strokeStyle,g=c.lineWidth||q.lineWidth;b.paint(k,z,g,x,p,w);return[o,n,m,l]}};jsPlumb.Overlays.PlainArrow=function(b){b=b||{};var a=jsPlumb.extend(b,{foldback:1});jsPlumb.Overlays.Arrow.call(this,a)};jsPlumb.Overlays.Diamond=function(c){c=c||{};var a=c.length||40;var b=jsPlumb.extend(c,{length:a/2,foldback:2});jsPlumb.Overlays.Arrow.call(this,b)};jsPlumb.Overlays.Label=function(d){jsPlumb.DOMElementComponent.apply(this,arguments);this.labelStyle=d.labelStyle||jsPlumb.Defaults.LabelStyle;this.labelStyle.font=this.labelStyle.font||"12px sans-serif";this.label=d.label;this.connection=d.connection;this.id=d.id;var k=this;var h=null,e=null,c=null,b=null;this.location=d.location||0.5;this.cachedDimensions=null;var i=false,c=null,a=document.createElement("div");a.style.position="absolute";a.style.textAlign="center";a.style.cursor="pointer";a.style.font=k.labelStyle.font;a.style.color=k.labelStyle.color||"black";if(k.labelStyle.fillStyle){a.style.background=k.labelStyle.fillStyle}if(k.labelStyle.borderWidth>0){var g=k.labelStyle.borderStyle?k.labelStyle.borderStyle:"black";a.style.border=k.labelStyle.borderWidth+"px solid "+g}if(k.labelStyle.padding){a.style.padding=k.labelStyle.padding}var f=d._jsPlumb.overlayClass+" "+(k.labelStyle.cssClass?k.labelStyle.cssClass:d.cssClass?d.cssClass:"");a.className=f;jsPlumb.appendElement(a,d.connection.parent);jsPlumb.getId(a);k.attachListeners(a,k);this.paint=function(l,n,m){if(!i){l.appendDisplayElement(a);k.attachListeners(a,l);i=true}a.style.left=(m[0]+n.minx)+"px";a.style.top=(m[1]+n.miny)+"px"};this.getTextDimensions=function(l){c=typeof k.label=="function"?k.label(k):k.label;a.innerHTML=c.replace(/\r\n/g,"
");var n=jsPlumb.CurrentLibrary.getElementObject(a),m=jsPlumb.CurrentLibrary.getSize(n);return{width:m[0],height:m[1]}};this.computeMaxSize=function(l){var m=k.getTextDimensions(l);return m.width?Math.max(m.width,m.height)*1.5:0};this.draw=function(n,p,o){var r=k.getTextDimensions(n);if(r.width!=null){var q=n.pointOnPath(k.location);var m=q.x-(r.width/2);var l=q.y-(r.height/2);k.paint(n,{minx:m,miny:l,td:r,cxy:q},o);return[m,m+r.width,l,l+r.height]}else{return[0,0,0,0]}}}})();(function(){var h={"stroke-linejoin":"joinstyle",joinstyle:"joinstyle",endcap:"endcap",miterlimit:"miterlimit"};if(document.createStyleSheet){document.createStyleSheet().addRule(".jsplumb_vml","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:textbox","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:oval","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:rect","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:stroke","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:shape","behavior:url(#default#VML);position:absolute;");document.namespaces.add("jsplumb","urn:schemas-microsoft-com:vml")}var c=1000,d=function(p,q){for(var n in q){p[n]=q[n]}},m=function(n,q,r){r=r||{};var p=document.createElement("jsplumb:"+n);p.className=(r["class"]?r["class"]+" ":"")+"jsplumb_vml";l(p,q);d(p,r);return p},l=function(p,n){p.style.left=n[0]+"px";p.style.top=n[1]+"px";p.style.width=n[2]+"px";p.style.height=n[3]+"px";p.style.position="absolute"},g=function(n){return Math.floor(n*c)},a=function(p,n){var v=p,u=function(o){return o.length==1?"0"+o:o},q=function(o){return u(Number(o).toString(16))},r=/(rgb[a]?\()(.*)(\))/;if(p.match(r)){var t=p.match(r)[2].split(",");v="#"+q(t[0])+q(t[1])+q(t[2]);if(!n&&t.length==4){v=v+q(t[3])}}return v},f=function(s,r,p){var u={};if(r.strokeStyle){u.stroked="true";u.strokecolor=a(r.strokeStyle,true);u.strokeweight=r.lineWidth+"px"}else{u.stroked="false"}if(r.fillStyle){u.filled="true";u.fillcolor=a(r.fillStyle,true)}else{u.filled="false"}if(r.dashstyle){if(p.strokeNode==null){p.strokeNode=m("stroke",[0,0,0,0],{dashstyle:r.dashstyle});s.appendChild(p.strokeNode)}else{p.strokeNode.dashstyle=r.dashstyle}}else{if(r["stroke-dasharray"]&&r.lineWidth){var o=r["stroke-dasharray"].indexOf(",")==-1?" ":",",t=r["stroke-dasharray"].split(o),n="";for(var q=0;q0&&F>0&&z=z&&B[2]<=F&&B[3]>=F)){return true}}var D=v.canvas.getContext("2d").getImageData(parseInt(z),parseInt(F),1,1);return D.data[0]!=0||D.data[1]!=0||D.data[2]!=0||D.data[3]!=0}return false};var u=false;var t=false,y=null,x=false;var w=function(A,z){return A!=null&&f(A,z)};this.mousemove=function(C){var E=e(C),B=i(C),A=document.elementFromPoint(B[0],B[1]),D=w(A,"_jsPlumb_overlay");var z=o==null&&(w(A,"_jsPlumb_endpoint")||w(A,"_jsPlumb_connector"));if(!u&&z&&v._over(C)){u=true;v.fire("mouseenter",v,C);return true}else{if(u&&(!v._over(C)||!z)&&!D){u=false;v.fire("mouseexit",v,C)}}v.fire("mousemove",v,C)};this.click=function(z){if(u&&v._over(z)&&!x){v.fire("click",v,z)}x=false};this.dblclick=function(z){if(u&&v._over(z)&&!x){v.fire("dblclick",v,z)}x=false};this.mousedown=function(z){if(v._over(z)&&!t){t=true;y=m(s(v.canvas));v.fire("mousedown",v,z)}};this.mouseup=function(z){t=false;v.fire("mouseup",v,z)}};var p=function(u){var t=document.createElement("canvas");jsPlumb.appendElement(t,u.parent);t.style.position="absolute";if(u["class"]){t.className=u["class"]}u._jsPlumb.getId(t,u.uuid);return t};var d=jsPlumb.CanvasConnector=function(x){n.apply(this,arguments);var t=function(B,z){u.ctx.save();jsPlumb.extend(u.ctx,z);if(z.gradient){var A=u.createGradient(B,u.ctx);for(var y=0;y0?1:-1}}var b={subtract:function(m,l){return{x:m.x-l.x,y:m.y-l.y}},dotProduct:function(m,l){return m.x*l.x+m.y*l.y},square:function(l){return Math.sqrt(l.x*l.x+l.y*l.y)},scale:function(m,l){return{x:m.x*l,y:m.y*l}}},d=Math.pow(2,-65),h=function(y,x){for(var s=[],v=x.length-1,r=2*v-1,t=[],w=[],o=[],p=[],q=[[1,0.6,0.3,0.1],[0.4,0.6,0.6,0.4],[0.1,0.3,0.6,1]],u=0;u<=v;u++){t[u]=b.subtract(x[u],y)}for(u=0;u<=v-1;u++){w[u]=b.subtract(x[u+1],x[u]);w[u]=b.scale(w[u],3)}for(u=0;u<=v-1;u++){for(var n=0;n<=v;n++){o[u]||(o[u]=[]);o[u][n]=b.dotProduct(w[u],t[n])}}for(u=0;u<=r;u++){p[u]||(p[u]=[]);p[u].y=0;p[u].x=parseFloat(u)/r}r=v-1;for(t=0;t<=v+r;t++){w=Math.min(t,v);for(u=Math.max(0,t-r);u<=w;u++){j=t-u;p[u+j].y+=o[j][u]*q[j][u]}}v=x.length-1;p=k(p,2*v-1,s,0);r=b.subtract(y,x[0]);o=b.square(r);for(u=q=0;u=64){y[0]=(E[0].x+E[D].x)/2;return 1}var s,r,p;v=E[0].y-E[D].y;w=E[D].x-E[0].x;A=E[0].x*E[D].y-E[D].x*E[0].y;t=max_distance_below=0;for(r=1;rt){t=p}else{if(p0?1:-1,r=null;n endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + endpointsByElement = {}, + endpointsByUUID = {}, + offsets = {}, + offsetTimestamps = {}, + floatingConnections = {}, + draggableStates = {}, + _mouseEventsEnabled = this.Defaults.MouseEventsEnabled, + _draggableByDefault = true, + canvasList = [], + sizes = [], + listeners = {}, // a map: keys are event types, values are lists of listeners. + DEFAULT_SCOPE = this.Defaults.Scope, + renderMode = null, // will be set in init() + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }, + + /** + * appends an element to the given parent, or the document body if no + * parent given. + */ + _appendElement = function(el, parent) { + if (!parent) + document.body.appendChild(el); + else + jsPlumb.CurrentLibrary.appendElement(el, parent); + }, + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + _timestamp = function() { return "" + (new Date()).getTime(); }, + + /** + * YUI, for some reason, put the result of a Y.all call into an object that contains + * a '_nodes' array, instead of handing back an array-like object like the other + * libraries do. + */ + _convertYUICollection = function(c) { + return c._nodes ? c._nodes : c; + }, + + /** + * Draws an endpoint and its connections. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + _draw = function(element, ui, timestamp) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (!timestamp) timestamp = _timestamp(); + if (endpoints) { + _updateOffset( { elId : id, offset : ui, recalc : false, timestamp : timestamp }); // timestamp is checked against last update cache; it is + // valid for one paint cycle. + var myOffset = offsets[id], myWH = sizes[id]; + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + var l = endpoints[i].connections; + for ( var j = 0; j < l.length; j++) { + l[j].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); // ...paint each connection. + // then, check for dynamic endpoint; need to repaint it. + var oIdx = l[j].endpoints[0] == endpoints[i] ? 1 : 0; + if (l[j].endpoints[oIdx].anchor.isDynamic && !l[j].endpoints[oIdx].isFloating()) { + var oId = oIdx == 0 ? l[j].sourceId : l[j].targetId, + oOffset = offsets[oId], oWH = sizes[oId], + // TODO i still want to make this faster. + anchorLoc = l[j].endpoints[oIdx].anchor.compute( { + xy : [ oOffset.left, oOffset.top ], + wh : oWH, + element : l[j].endpoints[oIdx], + txy : [ myOffset.left, myOffset.top ], + twh : myWH, + tElement : endpoints[i] + }); + l[j].endpoints[oIdx].paint({ anchorLoc : anchorLoc }); + } + } + } + } + }, + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }, + + /** + * gets an Endpoint by uuid. + */ + _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }, + + /** + * inits a draggable if it's not already initialised. + */ + _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element) && !jsPlumb.CurrentLibrary.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + } + } + }, + + _newConnection = function(params) { + var connectionFunc = jsPlumb.Defaults.ConnectionType || Connection, + endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint, + parent = jsPlumb.CurrentLibrary.getParent; + + if (params.sourceEndpoint) + params["parent"] = params.sourceEndpoint.parent; + else if (params.source.constructor == endpointFunc) + params["parent"] = params.source.parent; + else params["parent"] = parent(params.source); + + params["_jsPlumb"] = _currentInstance; + var con = new connectionFunc(params); + _eventFireProxy("click", "click", con); + _eventFireProxy("dblclick", "dblclick", con); + return con; + }, + + _eventFireProxy = function(event, proxyEvent, obj) { + obj.bind(event, function(e) { + _currentInstance.fire(proxyEvent, obj, e); + }); + }, + + _newEndpoint = function(params) { + var endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint; + params["parent"] = jsPlumb.CurrentLibrary.getParent(params.source); + params["_jsPlumb"] = _currentInstance, + ep = new endpointFunc(params); + _eventFireProxy("click", "endpointClick", ep); + _eventFireProxy("dblclick", "endpointDblClick", ep); + return ep; + }, + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }, + /** + * perform an operation on all elements. + */ + _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }, + + /** + * helper to remove an element from the DOM. + */ + _removeElement = function(element, parent) { + if (element != null && element.parentNode != null) { + element.parentNode.removeChild(element); + } + }, + /** + * helper to remove a list of elements from the DOM. + */ + _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }, + /** + * helper method to remove an item from a list. + */ + _removeFromList = function(map, key, value) { + if (key != null) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete (l[i]); + l.splice(i, 1); + return true; + } + } + } + return false; + }, + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }, + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + _setVisible = function(el, state) { + _operation(_getAttribute(el, "id"), function(jpc) { + jpc.canvas.style.display = state; + }); + }, + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }, + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + _toggleVisible = function(elId) { + _operation(elId, function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }, + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return offsets[elId]; + } + if (recalc || offset == null) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + } + return offsets[elId]; + }, + +/** + * gets an id for the given element, creating and setting one if + * necessary. + */ + _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else + id = "jsPlumb_" + _timestamp(); + _setAttribute(ele, "id", id); + } + return id; + }, + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'jsPlumb function failed : ' + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'wrapped function failed : ' + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = "_jsPlumb_connector"; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = "_jsPlumb_endpoint"; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = "_jsPlumb_overlay"; + + this.Anchors = {}; + + this.Connectors = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Endpoints = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Overlays = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + +// ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***************************************** + /* + * Function: bind + * Bind to an event on jsPlumb. + * + * Parameters: + * event - the event to bind. Available events on jsPlumb are: + * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback. + * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback. + * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback. + * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback. + * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback. + * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback. + * + * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event. + */ + + /* + * Function: clearListeners + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + +// *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *********************************************************** + + /* + Function: addEndpoint + + Adds an to a given element or elements. + + Parameters: + + el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + params - Object containing Endpoint constructor arguments. For more information, see . + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some + shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in + this object are anything that 'params' can contain. See . + + Returns: + The newly created , if el referred to a single element. Otherwise, an array of newly created s. + + See Also: + + */ + this.addEndpoint = function(el, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + + // YUI wrapper + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + var _el = _getElementObject(inputs[i]), id = _getId(_el); + p.source = _el; + _updateOffset({ elId : id }); + var e = _newEndpoint(p); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + results.push(e); + } + + return results.length == 1 ? results[0] : results; + }; + + /* + Function: addEndpoints + Adds a list of s to a given element or elements. + + Parameters: + target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See 's constructor documentation. + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created s, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(el, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams); + if (e.constructor == Array) + Array.prototype.push.apply(results, e); + else results.push(e); + } + return results; + }; + + /* + Function: animate + This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating + the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // onComplete repaints, just to make sure everything looks good at the end of the animation. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /* + Function: connect + Establishes a between two elements (or s, which are themselves registered to elements). + + Parameters: + params - Object containing constructor arguments for the Connection. See 's constructor documentation. + referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of + Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection. + + Returns: + The newly created . + */ + this.connect = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + // dynamic anchors. backwards compatibility here: from 1.2.6 onwards you don't need to specify "dynamicAnchors". the fact that some anchor consists + // of multiple definitions is enough to tell jsPlumb you want it to be dynamic. + if (_p.dynamicAnchors) { + // these can either be an array of anchor coords, which we will use for both source and target, or an object with {source:[anchors], target:[anchors]}, in which + // case we will use a different set for each element. + var a = _p.dynamicAnchors.constructor == Array; + var sa = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.source)); + var ta = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.target)); + _p.anchors = [sa,ta]; + } + + var jpc = _newConnection(_p); + // add to list of connections (by scope). + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + _currentInstance.fire("jsPlumbConnection", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + // force a paint + _draw(jpc.source); + + return jpc; + }; + + /* + Function: deleteEndpoint + Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too) + + Parameters: + object - either an object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElement(endpoint.canvas, endpoint.parent); + // remove from endpointsbyElement + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + delete endpoint; + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every , and their associated s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc) { + _currentInstance.fire("jsPlumbConnectionDetached", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + }; + + /* + Function: detach + Detaches and then removes a . Takes either (source, target) (the old way, maintained for backwards compatibility), or a params + object with various possible values. + + Parameters: + source - id or element object of the first element in the Connection. + target - id or element object of the second element in the Connection. + params - a JS object containing the same parameters as you pass to jsPlumb.connect. If this is present then neither source nor + target should be present; it should be the only argument to the method. See the docs for 's constructor for information +about the parameters allowed in the params object. + Returns: + true if successful, false if not. + */ + this.detach = function(source, target) { + if (arguments.length == 2) { + var s = _getElementObject(source), sId = _getId(s); + var t = _getElementObject(target), tId = _getId(t); + _operation(sId, function(jpc) { + if ((jpc.sourceId == sId && jpc.targetId == tId) || (jpc.targetId == sId && jpc.sourceId == tId)) { + _removeElements(jpc.connector.getDisplayElements(), jpc.parent); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + // this is the new version of the method, taking a JS object like + // the connect method does. + else if (arguments.length == 1) { + // TODO investigate whether or not this code still works when a user has supplied their own subclass of Connection. i suspect it may not. + if (arguments[0].constructor == Connection) { + arguments[0].endpoints[0].detachFrom(arguments[0].endpoints[1]); + } + else if (arguments[0].connection) { + arguments[0].connection.endpoints[0].detachFrom(arguments[0].connection.endpoints[1]); + } + else { + var _p = jsPlumb.extend( {}, source); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1])); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source); + var targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElements(jpc.connector.getDisplayElements(), jpc.parent); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + } + } + }; + + /* + Function: detachAll + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + + Returns: + void + */ + this.detachAllConnections = function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + }; + + /** + * @deprecated Use detachAllConnections instead. this will be removed in jsPlumb 1.3. + */ + this.detachAll = this.detachAllConnections; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + /** + * @deprecated use detachEveryConnection instead. this will be removed in jsPlumb 1.3. + */ + this.detachEverything = this.detachEveryConnection; + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of you library's draggable method so that jsPlumb can setup the appropriate callbacks. Your underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method, + * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the + * default scope). If multiple scopes are passed in, the return value will be a map of { scope -> [ connection... ] }. + * + * Parameters + * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list + * of connections that are in the given scope. + * options - if the argument is a JS object, you can specify a finer-grained filter: + * + * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes. + * - *source* either a string representing an element id, or a selector. constrains the result to connections having this source. + * - *target* either a string representing an element id, or a selector. constrains the result to connections having this target. + * + */ + this.getConnections = function(options) { + if (!options) { + options = {}; + } else if (options.constructor == String) { + options = { "scope": options }; + } + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') + r.push(input); + else + r = input; + } + return r; + }; + var scope = options.scope || jsPlumb.getDefaultScope(), + scopes = prepareList(scope), + sources = prepareList(options.source), + targets = prepareList(options.target), + filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }, + results = scopes.length > 1 ? {} : [], + _addOne = function(scope, obj) { + if (scopes.length > 1) { + var ss = results[scope]; + if (ss == null) { + ss = []; results[scope] = ss; + } + ss.push(obj); + } else results.push(obj); + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + _addOne(i, c); + } + } + } + return results; + }; + + /* + * Function: getAllConnections + * Gets all connections, as a map of { scope -> [ connection... ] }. + */ + this.getAllConnections = function() { + return connectionsByScope; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /** + * Function:getEndpoints + * Gets the list of Endpoints for a given selector, or element id. + * @param el + * @return + */ + this.getEndpoints = function(el) { + return endpointsByElement[_getId(el)]; + }; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + + this.appendElement = _appendElement; + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + + Returns: + void + */ + this.hide = function(el) { + _setVisible(el, "none"); + }; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + if (!initialized) { + _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run. + + var _bind = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && _mouseEventsEnabled && renderMode == jsPlumb.CANVAS) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + var t = c[i].connector[event](e); + if (t) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i].endpoint[event](e)) return; + } + } + } + }); + }; + _bind("click"); + _bind("dblclick"); + _bind("mousemove"); + _bind("mousedown"); + _bind("mouseup"); + + initialized = true; + _currentInstance.fire("ready"); + } + }; + + this.jsPlumbUIComponent = jsPlumbUIComponent; + this.EventGenerator = EventGenerator; + + /* + * Creates an anchor with the given params. + * + * You do not need to use this method. It is exposed because of the way jsPlumb is + * split into three scripts; this will change in the future. + * + * x - the x location of the anchor as a fraction of the + * total width. y - the y location of the anchor as a fraction of the + * total height. xOrientation - value indicating the general direction a + * connection from the anchor should go in, in the x direction. + * yOrientation - value indicating the general direction a connection + * from the anchor should go in, in the y direction. xOffset - a fixed + * offset that should be applied in the x direction that should be + * applied after the x position has been figured out. optional. defaults + * to 0. yOffset - a fixed offset that should be applied in the y + * direction that should be applied after the y position has been + * figured out. optional. defaults to 0. + * -- OR -- + * + * params - {x:..., y:..., xOrientation etc } + * -- OR FROM 1.2.4 --- + * + * name - the name of some Anchor in the _currentInstance.Anchors array. + * -- OR FROM 1.2.4 --- + * + * coords - a list of coords for the anchor, like you would pass to + * jsPlumb.makeAnchor (eg [0.5,0.5,0,-1] - an anchor in the center of + * some element, oriented towards the top of the screen) + * -- OR FROM 1.2.4 --- + * + * anchor - an existing anchor. just gets passed back. it's handy + * internally to have this functionality. + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed + // in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards + // compatibility if we are given only one value we assume it's a + // call in the old form. + if (arguments.length == 0) return null; + var params = {}; + if (arguments.length == 1) { + var specimen = arguments[0]; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; + // is it the name of an anchor type? + else if (typeof specimen == "string") return _currentInstance.Anchors[arguments[0]](); + // is it an array of coordinates? + else if (specimen.constructor == Array) { + if (specimen[0].constructor == Array || specimen[0].constructor == String) + return new DynamicAnchor(specimen); + else + return jsPlumb.makeAnchor.apply(this, specimen); + } + // last we try the backwards compatibility stuff. + else if (typeof arguments[0] == "object") jsPlumb.extend(params, x); + } else { + params = { x : x, y : y }; + if (arguments.length >= 4) params.orientation = [ arguments[2], arguments[3] ]; + if (arguments.length == 6) params.offsets = [ arguments[4], arguments[5] ]; + } + var a = new Anchor(params); + a.clone = function() { + return new Anchor(params); + }; + return a; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types) { + var r = []; + for ( var i = 0; i < types.length; i++) + if (typeof types[i] == "string") + r.push(_currentInstance.Anchors[types[i]]()); + else if (types[i].constructor == Array) + r.push(jsPlumb.makeAnchor(types[i])); + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /** + * Function: makeTarget + * Makes some DOM element a Connection target, allowing you to drag connections to it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a target. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * scope optional. scope for the drop zone. + * dropOptions optional. same stuff as you would pass to dropOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to false. whether or not to delete + * any Endpoints created by a connection to this target if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + this.makeTarget = function(el, params, referenceParams) { + + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + var jpcl = jsPlumb.CurrentLibrary, + scope = p.scope || _currentInstance.Defaults.Scope, + deleteEndpointsOnDetach = p.deleteEndpointsOnDetach || false, + _doOne = function(_el) { + var dropOptions = jsPlumb.extend({}, p.dropOptions || {}); + var _drop = function() { + var draggable = _getElementObject(jpcl.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + // restore the original scope if necessary (issue 57) + scope = _getAttribute(draggable, "originalScope"); + + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + // get the connection, to then get its endpoint + var jpc = floatingConnections[id], + source = jpc.endpoints[0], + _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : null, + // make a new Endpoint + newEndpoint = jsPlumb.addEndpoint(_el, _endpoint); + + var c = jsPlumb.connect({ + source:source, + target:newEndpoint, + scope:scope + }); + if (deleteEndpointsOnDetach) + c.endpointToDeleteOnDetach = newEndpoint; + }; + + var dropEvent = jpcl.dragEvents['drop']; + dropOptions["scope"] = dropOptions["scope"] || scope; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop); + + jpcl.initDroppable(_el, dropOptions); + }; + + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements drop targets. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeTargets = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeTarget(els[i], params, referenceParams); + } + }; + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + for ( var i in ebe) + _currentInstance.deleteEndpoint(ebe[i]); + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + this.deleteEveryEndpoint(); + this.clearListeners(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + + Parameters: + value - whether or not to automatically repaint when the window is resized. + + Returns: void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setDraggableByDefault + * Sets whether or not elements are draggable by default. Default for this is true. + * + * Parameters: + * draggable - value to set + * + * Returns: + * void + */ + this.setDraggableByDefault = function(draggable) { + _draggableByDefault = draggable; + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction + * Sets the function to fire when the window size has changed and a repaint was fired. + * + * Parameters: + * f - Function to execute. + * + * Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: setMouseEventsEnabled + * Sets whether or not mouse events are enabled. Default is true. + * + * Parameters: + * enabled - whether or not mouse events should be enabled. + * + * Returns: + * void + */ + this.setMouseEventsEnabled = function(enabled) { + _mouseEventsEnabled = enabled; + }; + + /* + * Constant for use with the setRenderMode method + */ + this.CANVAS = "canvas"; + + /* + * Constant for use with the setRenderMode method + */ + this.SVG = "svg"; + + this.VML = "vml"; + + /* + * Function: setRenderMode + * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that + * what you asked for is not supported (and that VML is). If you asked for VML but the browser does + * not support it, jsPlumb uses SVG. + * + * Returns: + * the render mode that jsPlumb set, which of course may be different from that requested. + */ + this.setRenderMode = function(mode) { + if (mode) + mode = mode.toLowerCase(); + else + return; + if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); + // now test we actually have the capability to do this. + if (mode === jsPlumb.CANVAS && canvasAvailable) + renderMode = jsPlumb.CANVAS; + else if (mode === jsPlumb.SVG && svgAvailable) + renderMode = jsPlumb.SVG; + else if (vmlAvailable) + renderMode = jsPlumb.VML; + + return renderMode; + }; + + this.getRenderMode = function() { return renderMode; }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * + * Returns: + * void + */ + this.show = function(el) { + _setVisible(el, "block"); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId, + makeAnchor:self.makeAnchor, + makeDynamicAnchor:self.makeDynamicAnchor + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload + * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element. + * + * Returns: + * void + */ + this.unload = function() { + delete endpointsByElement; + delete endpointsByUUID; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + delete canvasList; + }; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + if (timestamp && timestamp === self.timestamp) { + return lastReturnValue; + } + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + if (element.canvas) { + var po = element.canvas.offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(element.canvas.offsetParent); + lastReturnValue[0] = lastReturnValue[0] - po.left; + lastReturnValue[1] = lastReturnValue[1] - po.top; + } + + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + * + * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(_getElementObject(refCanvas)); + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + var orientation = null; + var _lastResult = null; + + this.compute = function(params) { + var xy = params.xy, element = params.element; + var result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + + if (element.canvas) { + var po = element.canvas.offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(element.canvas.offsetParent); + result[0] = result[0] - po.left; + result[1] = result[1] - po.top; + } + + _lastResult = result; + return result; + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { orientation = anchor.getOrientation(); }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchors is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = []; + var _convert = function(anchor) { return anchor.constructor == Anchor ? anchor: jsPlumb.makeAnchor(anchor); }; + for (var i = 0; i < anchors.length; i++) _anchors[i] = _convert(anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null; + var _curIndex = _anchors.length > 0 ? 0 : -1; + this.locked = false; + var self = this; + + // helper method to calculate the distance between the centers of the two elements. + var _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }; + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + var _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + + var pos = _curAnchor.compute(params); + return pos; + }; + + this.getCurrentLocation = function() { + var cl = _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + return cl; + }; + + this.getOrientation = function() { return _curAnchor != null ? _curAnchor.getOrientation() : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * overlays - Optional array of Overlay definitions to appear on this Connection. + */ + var Connection = function(params) { + + jsPlumbUIComponent.apply(this, arguments); + // ************** get the source and target and register the connection. ******************* + var self = this; + var visible = true; + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none"; + }; + var id = new String('_jsplumb_c_' + (new Date()).getTime()); + this.getId = function() { return id; }; + this.parent = params.parent; + /** + Property: source + The source element for this Connection. + */ + this.source = _getElementObject(params.source); + /** + Property:target + The target element for this Connection. + */ + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. + if (params.sourceEndpoint) this.source = params.sourceEndpoint.getElement(); + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + /* + * Property: sourceId + * Id of the source element in the connection. + */ + this.sourceId = _getAttribute(this.source, "id"); + /* + * Property: targetId + * Id of the target element in the connection. + */ + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + + /** + * implementation of abstract method in EventGenerator + * @return list of attached elements. in our case, a list of Endpoints. + */ + this.getAttachedElements = function() { + return self.endpoints; + }; + + /** + * implementation of abstract method in EventGenerator + */ + var srcWhenMouseDown = null, targetWhenMouseDown = null; + this.savePosition = function() { + srcWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.source)); + targetWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.target)); + }; + + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams) { + if (anchorParams) + return jsPlumb.makeAnchor(anchorParams); + }; + var prepareEndpoint = function(existing, index, params, element, connectorPaintStyle, connectorHoverPaintStyle) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] + || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint; + + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified. + if (es.fillStyle == null && connectorPaintStyle != null) + es.fillStyle = connectorPaintStyle.strokeStyle; + + // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does: + //* + if (es.outlineColor == null && connectorPaintStyle != null) + es.outlineColor = connectorPaintStyle.outlineColor; + if (es.outlineWidth == null && connectorPaintStyle != null) + es.outlineWidth = connectorPaintStyle.outlineWidth; + //*/ + + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure? + if (connectorHoverPaintStyle != null) { + if (ehs == null) ehs = {}; + if (ehs.fillStyle == null) { + ehs.fillStyle = connectorHoverPaintStyle.strokeStyle; + } + } + var a = params.anchors ? params.anchors[index] : _makeAnchor(_currentInstance.Defaults.Anchors[index]) || _makeAnchor(jsPlumb.Defaults.Anchors[index]) || _makeAnchor(_currentInstance.Defaults.Anchor) || _makeAnchor(jsPlumb.Defaults.Anchor); + var u = params.uuids ? params.uuids[index] : null; + var e = _newEndpoint( { paintStyle : es, hoverPaintStyle:ehs, endpoint : ep, connections : [ self ], uuid : u, anchor : a, source : element }); + self.endpoints[index] = e; + + if (params.drawEndpoints === false) e.setVisible(false, true, true); + + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source, params.paintStyle, params.hoverPaintStyle); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target, params.paintStyle, params.hoverPaintStyle); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + /* + * Function: setConnector + * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method - the same + * thing that you would set as the 'connector' property on a jsPlumb.connect call. + * + * Parameters: + * connector - Connector definition + */ + this.setConnector = function(connector, doNotRepaint) { + if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent); + var connectorArgs = { _jsPlumb:self._jsPlumb, parent:params.parent, cssClass:params.cssClass }; + if (connector.constructor == String) + this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand. + else if (connector.constructor == Array) + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs)); + this.canvas = this.connector.canvas; + var _mouseDown = false, _mouseWasDown = false, _mouseDownAt = null; + // add mouse events + this.connector.bind("click", function(con, e) { + _mouseWasDown = false; + self.fire("click", self, e); + }); + this.connector.bind("dblclick", function(con, e) { _mouseWasDown = false;self.fire("dblclick", self, e); }); + this.connector.bind("mouseenter", function(con, e) { + if (!self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(true); + } + self.fire("mouseenter", self, e); + } + }); + this.connector.bind("mouseexit", function(con, e) { + if (self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(false); + } + self.fire("mouseexit", self, e); + } + }); + this.connector.bind("mousedown", function(con, e) { + _mouseDown = true; + _mouseDownAt = jsPlumb.CurrentLibrary.getPageXY(e); + self.savePosition(); + }); + this.connector.bind("mouseup", function(con, e) { + _mouseDown = false; + if (self.connector == _connectionBeingDragged) _connectionBeingDragged = null; + }); + + if (!doNotRepaint) self.repaint(); + }; + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc) + */ + + self.setConnector(this.endpoints[0].connector || + this.endpoints[1].connector || + params.connector || + _currentInstance.Defaults.Connector || + jsPlumb.Defaults.Connector, true); + + this.setPaintStyle(this.endpoints[0].connectorStyle || + this.endpoints[1].connectorStyle || + params.paintStyle || + _currentInstance.Defaults.PaintStyle || + jsPlumb.Defaults.PaintStyle, true); + + this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || + this.endpoints[1].connectorHoverStyle || + params.hoverPaintStyle || + _currentInstance.Defaults.HoverPaintStyle || + jsPlumb.Defaults.HoverPaintStyle, true); + + this.paintStyleInUse = this.paintStyle; + + /* + * Property: overlays + * List of Overlays for this Connection. + */ + this.overlays = []; + var _overlays = params.overlays || _currentInstance.Defaults.Overlays; + if (_overlays) { + for (var i = 0; i < _overlays.length; i++) { + var o = _overlays[i], _newOverlay = null, _overlayEvents = null; + if (o.constructor == Array) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0]; + var p = jsPlumb.CurrentLibrary.extend({connection:self, _jsPlumb:_currentInstance}, o[1]); // make a copy of the object so as not to mess up anyone else's reference... + if (o.length == 3) jsPlumb.CurrentLibrary.extend(p, o[2]); + _newOverlay = new jsPlumb.Overlays[renderMode][type](p); + if (p.events) { + for (var evt in p.events) { + _newOverlay.bind(evt, p.events[evt]); + } + } + } else if (o.constructor == String) { + _newOverlay = new jsPlumb.Overlays[renderMode][o]({connection:self, _jsPlumb:_currentInstance}); + } else { + _newOverlay = o; + } + + + + this.overlays.push(_newOverlay); + } + } + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Connection. + * + * Parameters: + * event - the event to bind. Available events on a Connection are: + * - *click* : notification that a Connection was clicked. + * - *dblclick* : notification that a Connection was double clicked. + * - *mouseenter* : notification that the mouse is over a Connection. + * - *mouseexit* : notification that the mouse exited a Connection. + * + * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Connection's paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Connection will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Connection. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + this.addOverlay = function(overlay) { self.overlays.push(overlay); }; + + /** + * Function: removeAllOverlays + * Removes all overlays from the Connection, and then repaints. + */ + this.removeAllOverlays = function() { + self.overlays.splice(0, self.overlays.length); + self.repaint(); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + this.removeOverlay = function(overlayId) { + var idx = -1; + for (var i = 0; i < self.overlays.length; i++) { + if (overlayId === self.overlays[i].id) { + idx = i; + break; + } + } + if (idx != -1) self.overlays.splice(idx, 1); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + this.removeOverlays = function() { + for (var i = 0; i < arguments.length; i++) + self.removeOverlay(arguments[i]); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + this.labelStyle = params.labelStyle || _currentInstance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + if (this.label) { + this.overlays.push(new jsPlumb.Overlays[renderMode].Label( { + cssClass:params.cssClass, + labelStyle : this.labelStyle, + label : this.label, + connection:self, + _jsPlumb:_currentInstance + })); + } + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String or a Function that returns a String. + */ + this.setLabel = function(l) { + self.label = l; + _currentInstance.repaint(self.source); + }; + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var otherOffset = offsets[this.targetId]; + var otherWH = sizes[this.targetId]; + var anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1] + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0] + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc }); + + /* + * Paints the Connection. Not exposed for public usage. + * + * Parameters: + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp; + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = false; + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }); + _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + var sAnchorP = this.endpoints[sIdx].anchor.getCurrentLocation(); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.getCurrentLocation(); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + + /* paint overlays*/ + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + var s = o.computeMaxSize(self.connector); + if (s > maxSize) + maxSize = s; + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, self.paintStyleInUse.lineWidth, maxSize); + + self.connector.paint(dim, self.paintStyleInUse); + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim); + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function() { + this.paint({ elId : this.sourceId, recalc : true }); + }; + + _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide + // whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + + // just to make sure the UI gets initialised fully on all browsers. + self.repaint(); + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1 + * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't + * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you + * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or + * 'target' Endpoints for Connections. + * + * + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * connections - optional list of Connections to configure the Endpoint with. + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + params = params || {}; + var self = this; +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Endpoint. + * + * Parameters: + * event - the event to bind. Available events on an Endpoint are: + * - *click* : notification that a Endpoint was clicked. + * - *dblclick* : notification that a Endpoint was double clicked. + * - *mouseenter* : notification that the mouse is over a Endpoint. + * - *mouseexit* : notification that the mouse exited a Endpoint. + * + * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Endpoint's paint style and then repaints the Endpoint. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Endpoint will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Endpoint. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + var visible = true; + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + var id = new String('_jsplumb_e_' + (new Date()).getTime()); + this.getId = function() { return id; }; + if (params.dynamicAnchors) + self.anchor = new DynamicAnchor(jsPlumb.makeAnchors(params.dynamicAnchors)); + else + self.anchor = params.anchor ? jsPlumb.makeAnchor(params.anchor) : params.anchors ? jsPlumb.makeAnchor(params.anchors) : jsPlumb.makeAnchor("TopCenter"); + var _endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot", + endpointArgs = { _jsPlumb:self._jsPlumb, parent:params.parent }; + if (_endpoint.constructor == String) + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint](endpointArgs); + else if (_endpoint.constructor == Array) + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint[0]](jsPlumb.extend(_endpoint[1], endpointArgs )); + else _endpoint = _endpoint.clone(); + self.endpoint = _endpoint; + this.endpoint.bind("click", function(e) { self.fire("click", self, e); }); + this.endpoint.bind("dblclick", function(e) { self.fire("dblclick", self, e); }); + this.setPaintStyle(params.paintStyle || + params.style || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyle, true); + this.setHoverPaintStyle(params.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyle, true); + this.paintStyleInUse = this.paintStyle; + this.connectorStyle = params.connectorStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.parent = params.parent; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + var _element = params.source, + _uuid = params.uuid, + floatingEndpoint = null, + inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + this.element = _element; + var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + /* + * Property: canvas + * The Endpoint's Canvas. + */ + this.canvas = this.endpoint.canvas; + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + this.connections = params.connections || []; + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + var _reattach = params.reattach || false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + + // this avoids a circular loop + if (!ignoreTarget) { + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + t.detach(connection, true); + // check connection to see if we want to delete the other endpoint. + // if the user uses makeTarget to make some element a target for connections, + // it is possible that they will have set 'endpointToDeleteOnDetach': when + // you make a connection to an element that acts as a target (note: NOT an + // Endpoint; just some div as a target), Endpoints are created for that + // connection. so if you then delete that Connection, it is feasible you + // will want these auto-generated endpoints to be removed. + if (connection.endpointToDeleteOnDetach && connection.endpointToDeleteOnDetach.connections.length == 0) + jsPlumb.deleteEndpoint(connection.endpointToDeleteOnDetach); + } + _removeElements(connection.connector.getDisplayElements(), connection.parent); + _removeFromList(connectionsByScope, connection.scope, connection); + if(!ignoreTarget) fireDetachEvent(connection); + } + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + */ + this.detachAll = function() { + while (self.connections.length > 0) { + self.detach(self.connections[0]); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + */ + this.detachFrom = function(targetEndpoint) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + c[i].setHover(false); + self.detach(c[i]); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + return _newEndpoint( { anchor : self.anchor, source : _element, paintStyle : this.paintStyle, endpoint : _endpoint }); + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + return (self.connections.length < _maxConnections) ? null : self.connections[0]; + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + /* + * Function: setStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to + * setStyle and deprecate it if so. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + * + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + /* + * Function: paint + * Paints the Endpoint, recalculating offset and anchor positions if necessary. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + + params = params || {}; + var timestamp = params.timestamp; + if (!timestamp || self.timestamp !== timestamp) { + var ap = params.anchorPoint, canvas = params.canvas, connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + var xy = params.offset || offsets[_elementId]; + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (self.anchor.isDynamic) { + if (self.connections.length > 0) { + var c = self.connections[0]; + var oIdx = c.endpoints[0] == self ? 1 : 0; + var oId = oIdx == 0 ? c.sourceId : c.targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + } + ap = self.anchor.compute(anchorParams); + } + + var d = _endpoint.compute(ap, self.anchor.getOrientation(), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + _endpoint.paint(d, self.paintStyleInUse, self.anchor); + + self.timestamp = timestamp; + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + var start = function() { + jpc = self.connectorSelector(); + if (self.isFull() && !dragAllowedWhenFull) return false; + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + n.style.position = "absolute"; + var nE = _getElementObject(n); + _appendElement(n, self.parent); + // create and assign an id, and initialize the offset. + var id = _getId(nE); + + // set the offset of this div to be where 'inPlaceCopy' is, to start with. + var ipcoel = _getElementObject(inPlaceCopy.canvas), + ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel), + po = inPlaceCopy.canvas.offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(inPlaceCopy.canvas.offsetParent); + jsPlumb.CurrentLibrary.setOffset(n, {left:ipco.left - po.left, top:ipco.top-po.top}); + + _updateOffset( { elId : id }); + + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor( { reference : self.anchor, referenceCanvas : self.canvas }); + floatingEndpoint = _newEndpoint({ paintStyle : self.paintStyle, endpoint : _endpoint, anchor : floatingAnchor, source : nE }); + + if (jpc == null) { + self.anchor.locked = true; + // create a connection. one end is this endpoint, the + // other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : _getElementObject(_element), + target : _getElementObject(n), + anchors : [ self.anchor, floatingAnchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays + }); + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.connector.setHover(false); + } else { + existingJpc = true; + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.connector.setHover(false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas)); + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; // are we the source or the target? + + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas); + var dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // get a new, temporary scope, to use (issue 57) + var newScope = "scope_" + (new Date()).getTime(); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + // set the new, temporary scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(i, newScope); + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // TODO unregister on stop? or will floating endpoint's + // destruction be assured. + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be + // dragging the source at any time + // before this connection is either discarded or made into a + // permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var jpcl = jsPlumb.CurrentLibrary, + dragOptions = params.dragOptions || {}, + defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions), + startEvent = jpcl.dragEvents['start'], + stopEvent = jpcl.dragEvents['stop'], + dragEvent = jpcl.dragEvents['drag']; + + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], + function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + jsPlumb.CurrentLibrary.setOffset(n, _ui); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements( [ n, floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (_reattach) { + + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } else { + jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc); // the main endpoint will inform the floating endpoint + // to disconnect, and also post the detached event. + } + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElements(jpc.connector.getDisplayElements(), self.parent); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint(); + jpc.setHover(false); + jpc.repaint(); + jpc = null; + delete inPlaceCopy; + delete endpointsByElement[floatingEndpoint.elementId]; + floatingEndpoint = null; + delete floatingEndpoint; + + _currentInstance.currentlyDragging = false; + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas) { + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + var drop = function() { + var draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)); + var id = _getAttribute(draggable, "dragId"); + var elId = _getAttribute(draggable, "elId"); + + // restore the original scope if necessary (issue 57) + var scope = _getAttribute(draggable, "originalScope"); + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + var jpc = floatingConnections[id]; + + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // todo test that the target is not full. + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + if (!jpc.suspendedEndpoint) { + _addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + _currentInstance.fire("jsPlumbConnectionDetached", { + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }); + } + + jsPlumb.repaint(elId); + + _currentInstance.fire("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], + targetEndpoint : jpc.endpoints[1], + connection:jpc + }); + } + + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute( _getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + } + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute(_getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + } + }); + + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas)); + + return self; + }; + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + //if (_defaults) jsPlumb.extend(j.Defaults, _defaults); + return j; + }; + + var _curryAnchor = function(x,y,ox,oy) { + return function() { + return jsPlumb.makeAnchor(x,y,ox,oy); + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1); + + jsPlumb.Defaults.DynamicAnchors = function() { + return jsPlumb.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"]); + }; + jsPlumb.Anchors["AutoDefault"] = function() { return jsPlumb.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors()); }; + + +})(); +/* +* jsPlumb-defaults-1.3.0-RC1 +* +* Copyright 2010 - 2011 Simon Porritt http://jsplumb.org +* +* Triple licensed under the MIT, GPL2 and Beer licenses. +*/ + +(function() { + + + + /** + * + * Helper class to consume unused mouse events by components that are DOM elements and + * are used by all of the different rendering modes. + * + */ + jsPlumb.DOMElementComponent = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + // when render mode is canvas, these functions may be called by the canvas mouse handler. + // this component is safe to pipe this stuff to /dev/null. + this.mousemove = + this.dblclick = + this.click = + this.mousedown = + this.mouseup = function(e) { }; + }; + + /** + * Class: Connectors.Straight + * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + var currentPoints = null; + var _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + _dx = _tx - _sx, _dy = (_ty - _sy); + _m = _dy / _dx, _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + + return currentPoints; + }; + + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + var xp = _sx + (location * _dx); + var yp = (_m == Infinity || _m == -Infinity) ? _sy + (location * (_ty - _sy)) : (_m * xp) + _b; + return {x:xp, y:yp}; + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { return _m; }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + var orientation = distance > 0 ? 1 : -1; + var y = Math.abs(distance * Math.sin(_theta)); + if (_sy > _ty) y = y * -1; + var x = Math.abs(distance * Math.cos(_theta)); + if (_sx > _tx) x = x * -1; + return {x:p.x + (orientation * x), y:p.y + (orientation * y)}; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = self.gradientAtPoint(p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + }; + }; + + + /** + * Class:Connectors.Bezier + * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's + * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below. + */ + /** + * Function:Constructor + * + * Parameters: + * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line. + * Optional; defaults to 150. + * + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + params = params || {}; + this.majorAnchor = params.curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h; + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) + { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx); var minx2 = Math.min(_CP[0], _CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(_sx,_tx); var maxx2 = Math.max(_CP[0], _CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty); var miny2 = Math.min(_CP[1], _CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(_sy,_ty); var maxy2 = Math.max(_CP[1], _CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + return jsBezier.perpendicularToCurveAt(_makeCurve(), location, length, distance); + }; + + }; + + + /** + * Class: Connectors.Flowchart + * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments. + */ + /** + * Function: Constructor + * + * Parameters: + * stub - minimum length for the stub at each end of the connector. defaults to 30 pixels. + */ + jsPlumb.Connectors.Flowchart = function(params) { + params = params || {}; + var self = this, + minStubLength = params.stub || params.minStubLength /* bwds compat. */ || 30, + segments = [], + segmentGradients = [], + segmentProportions = [], + segmentLengths = [], + segmentProportionalLengths = [], + points = [], + swapX, + swapY, + /** + * recalculates the gradients of each segment, and the points at which the segments begin, proportional to the total length travelled + * by all the segments that constitute the connector. + */ + updateSegmentGradientsAndProportions = function(startX, startY, endX, endY) { + var total = 0; + for (var i = 0; i < segments.length; i++) { + var sx = i == 0 ? startX : segments[i][2], + sy = i == 0 ? startY : segments[i][3], + ex = segments[i][0], + ey = segments[i][1]; + + segmentGradients[i] = sx == ex ? Infinity : 0; + segmentLengths[i] = Math.abs(sx == ex ? ey - sy : ex - sx); + total += segmentLengths[i]; + } + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segmentLengths[i] / total; + segmentProportions[i] = [curLoc, (curLoc += (segmentLengths[i] / total)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0]; + var ly = segments.length == 0 ? sy : segments[segments.length - 1][1]; + segments.push([x, y, lx, ly]); + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment + * that contains the point which is 'location' distance along the entire path, where + * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths + * are made up of a list of segments, each of which contributes some fraction to + * the total length. + */ + findSegmentForLocation = function(location) { + var idx = segmentProportions.length - 1, inSegmentProportion = 0; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + segments = []; + segmentGradients = []; + segmentProportionalLengths = []; + segmentLengths = []; + segmentProportionals = []; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + offx = (lw / 2) + (minStubLength * 2), + offy = (lw / 2) + (minStubLength * 2), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(), + to = targetAnchor.orientation || targetAnchor.getOrientation(), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx, + h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy; + if (w < minWidth) { + offx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + offy += (minWidth - h) / 2; + h = minWidth; + } + var sx = swapX ? w-offx : offx, + sy = swapY ? h-offy : offy, + tx = swapX ? offx : w-offx , + ty = swapY ? offy : h-offy, + startStubX = sx + (so[0] * minStubLength), + startStubY = sy + (so[1] * minStubLength), + endStubX = tx + (to[0] * minStubLength), + endStubY = ty + (to[1] * minStubLength), + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2); + + x -= offx; y -= offy; + points = [x, y, w, h, sx, sy, tx, ty], extraPoints = []; + + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + if (so[0] == 0) { + var startStubIsBeforeEndStub = startStubY < endStubY; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } + } + else { + var startStubIsBeforeEndStub = startStubX < endStubX; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } + } + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentGradientsAndProportions(sx, sy, tx, ty); + + return points; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location) { + return self.pointAlongPathFrom(location, 0); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location) { + return segmentGradients[findSegmentForLocation(location)["index"]]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance) { + var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segmentLengths[s.index], m = segmentGradients[s.index]; + var e = { + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + return e; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = segmentGradients[p.segmentInfo.index]; + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + + }; + + }; + + // ********************************* END OF CONNECTOR TYPES ******************************************************************* + + // ********************************* ENDPOINT TYPES ******************************************************************* + + /** + * Class: Endpoints.Dot + * A round endpoint, with default radius 10 pixels. + */ + + /** + * Function: Constructor + * + * Parameters: + * + * radius - radius of the endpoint. defaults to 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + var self = this; + params = params || {}; + this.radius = params.radius || 10; + this.defaultOffset = 0.5 * this.radius; + this.defaultInnerRadius = this.radius / 3; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var r = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - r; + var y = anchorPoint[1] - r; + return [ x, y, r * 2, r * 2, r ]; + }; + }; + + /** + * Class: Endpoints.Rectangle + * A Rectangular Endpoint, with default size 20x20. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the endpoint. defaults to 20 pixels. + * height - height of the endpoint. defaults to 20 pixels. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + var self = this; + params = params || {}; + this.width = params.width || 20; + this.height = params.height || 20; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + return [ x, y, width, height]; + }; + }; + + /** + * Class: Endpoints.Image + * Draws an image as the Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * src - location of the image to use. + */ + jsPlumb.Endpoints.Image = function(params) { + + jsPlumb.DOMElementComponent.apply(this, arguments); + + var self = this, initialized = false; + this.img = new Image(); + self.ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.src || params.url; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + self.anchorPoint = anchorPoint; + if (self.ready) return [anchorPoint[0] - self.img.width / 2, anchorPoint[1] - self.img.height/ 2, self.img.width, self.img.height]; + else return [0,0,0,0]; + }; + + self.canvas = document.createElement("img"), initialized = false; + self.canvas.style["margin"] = 0; + self.canvas.style["padding"] = 0; + self.canvas.style["outline"] = 0; + self.canvas.style["position"] = "absolute"; + self.canvas.className = jsPlumb.endpointClass; + jsPlumb.appendElement(self.canvas, params.parent); + self.attachListeners(self.canvas, self); + + var actuallyPaint = function(d, style, anchor) { + if (!initialized) { + self.canvas.setAttribute("src", self.img.src); + initialized = true; + } + var width = self.img.width, + height = self.img.height, + x = self.anchorPoint[0] - (width/2), + y = self.anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(self.canvas, x, y, width, height); + }; + + this.paint = function(d, style, anchor) { + if (self.ready) { + actuallyPaint(d, style, anchor); + } + else { + window.setTimeout(function() { + self.paint(d, style, anchor); + }, 200); + } + }; + }; + + /** + * Class: Endpoints.Blank + * An Endpoint that paints nothing on the screen, and cannot be interacted with using the mouse. There are no constructor parameters for this Endpoint. + */ + jsPlumb.Endpoints.Blank = function() { + jsPlumb.DOMElementComponent.apply(this, arguments); + this.compute = function() { + return [0,0,0,0]; + }; + + self.canvas = document.createElement("div"); + this.paint = function() { }; + }; + + /** + * Class: Endpoints.Triangle + * A triangular Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the triangle's base. defaults to 55 pixels. + * height - height of the triangle from base to apex. defaults to 55 pixels. + */ + jsPlumb.Endpoints.Triangle = function(params) { + params = params || { }; + params.width = params.width || 55; + param.height = params.height || 55; + this.width = params.width; + this.height = params.height; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + return [ x, y, width, height ]; + }; + }; +// ********************************* END OF ENDPOINT TYPES ******************************************************************* + + +// ********************************* OVERLAY DEFINITIONS *********************************************************************** + + /** + * Class: Overlays.Arrow + * + * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length + * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction + * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line + * across the tail. + */ + /** + * Function: Constructor + * + * Parameters: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + params = params || {}; + var self = this; + this.length = params.length || 20; + this.width = params.width || 20; + this.id = params.id; + this.connection = params.connection; + var direction = (params.direction || 1) < 0 ? -1 : 1; + var paintStyle = params.paintStyle || { lineWidth:1 }; + this.loc = params.location == null ? 0.5 : params.location; + // how far along the arrow the lines folding back in come to. default is 62.3%. + var foldback = params.foldback || 0.623; + var _getFoldBackPoint = function(connector, loc) { + if (foldback == 0.5) return connector.pointOnPath(loc); + else { + var adj = 0.5 - foldback; // we calculate relative to the center + return connector.pointAlongPathFrom(loc, direction * self.length * adj); + } + }; + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + // this is the arrow head position + var hxy = connector.pointAlongPathFrom(self.loc, direction * (self.length / 2)); + // this is the center of the tail + var txy = connector.pointAlongPathFrom(self.loc, -1 * direction * (self.length / 2)), tx = txy.x, ty = txy.y; + // this is the tail vector + var tail = connector.perpendicularToPathAt(self.loc, self.width, -1 * direction * (self.length / 2)); + // this is the point the tail goes in to + var cxy = _getFoldBackPoint(connector, self.loc); + + // if loc = 1, then hxy should be flush with the element, or if direction == -1, the tail midpoint. + if (self.loc == 1) { + var lxy = connector.pointOnPath(self.loc); + // TODO determine why the 1.2.6 released version does not + // use 'direction' in the two equations below, yet both + // that and 1.3.0 still paint the arrows correctly. + var dx = (lxy.x - hxy.x) * direction, dy = (lxy.y - hxy.y) * direction; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + // if loc = 0, then tail midpoint should be flush with the element, or, if direction == -1, hxy should be. + if (self.loc == 0) { + var lxy = connector.pointOnPath(self.loc); + var tailMid = foldback > 1 ? cxy : { + x:tail[0].x + ((tail[1].x - tail[0].x) / 2), + y:tail[0].y + ((tail[1].y - tail[0].y) / 2) + }; + var dx = (lxy.x - tailMid.x) * direction, dy = (lxy.y - tailMid.y) * direction; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x); + var maxx = Math.max(hxy.x, tail[0].x, tail[1].x); + var miny = Math.min(hxy.y, tail[0].y, tail[1].y); + var maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + var d = { hxy:hxy, tail:tail, cxy:cxy }, + strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle, + fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle, + lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth; + + self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions); + + return [ minx, maxx, miny, maxy]; + }; + }; + + /** + * Class: Overlays.PlainArrow + * + * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + /** + * Class: Overlays.Diamond + * + * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40; + var p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + }; + + + + /** + * Class: Overlays.Label + * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb + * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter + * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it + * puts on the Label's 'style' attribute, so the end result is the same. + */ + /** + * Function: Constructor + * + * Parameters: + * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes + * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle. + * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * labelStyle - (deprecated) js object containing style instructions for the label. defaults to jsPlumb.Defaults.LabelStyle. + * borderWidth - (deprecated) width of a border to paint. defaults to zero. + * borderStyle - (deprecated) strokeStyle to use when painting the border, if necessary. + * + */ + jsPlumb.Overlays.Label = function(params) { + jsPlumb.DOMElementComponent.apply(this, arguments); + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.labelStyle.font = this.labelStyle.font || "12px sans-serif"; + this.label = params.label; + this.connection = params.connection; + this.id = params.id; + var self = this; + var labelWidth = null, labelHeight = null, labelText = null, labelPadding = null; + this.location = params.location || 0.5; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var initialised = false, + labelText = null, + div = document.createElement("div"); + div.style["position"] = "absolute"; + div.style["textAlign"] = "center"; + div.style["cursor"] = "pointer"; + div.style["font"] = self.labelStyle.font; + div.style["color"] = self.labelStyle.color || "black"; + if (self.labelStyle.fillStyle) div.style["background"] = self.labelStyle.fillStyle;//_convertStyle(self.labelStyle.fillStyle, true); + if (self.labelStyle.borderWidth > 0) { + var dStyle = self.labelStyle.borderStyle ? self.labelStyle.borderStyle/*_convertStyle(self.labelStyle.borderStyle, true)*/ : "black"; + div.style["border"] = self.labelStyle.borderWidth + "px solid " + dStyle; + } + if (self.labelStyle.padding) div.style["padding"] = self.labelStyle.padding; + + var clazz = params["_jsPlumb"].overlayClass + " " + + (self.labelStyle.cssClass ? self.labelStyle.cssClass : + params.cssClass ? params.cssClass : ""); + + div.className = clazz; + + jsPlumb.appendElement(div, params.connection.parent); + jsPlumb.getId(div); + self.attachListeners(div, self); + this.paint = function(connector, d, connectorDimensions) { + if (!initialised) { + connector.appendDisplayElement(div); + self.attachListeners(div, connector); + initialised = true; + } + div.style.left = (connectorDimensions[0] + d.minx) + "px"; + div.style.top = (connectorDimensions[1] + d.miny) + "px"; + }; + + this.getTextDimensions = function(connector) { + labelText = typeof self.label == 'function' ? self.label(self) : self.label; + div.innerHTML = labelText.replace(/\r\n/g, "
"); + var de = jsPlumb.CurrentLibrary.getElementObject(div), + s = jsPlumb.CurrentLibrary.getSize(de); + return {width:s[0], height:s[1]}; + }; + + this.computeMaxSize = function(connector) { + var td = self.getTextDimensions(connector); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + var td = self.getTextDimensions(connector); + if (td.width != null) { + var cxy = connector.pointOnPath(self.location); + + var minx = cxy.x - (td.width / 2); + var miny = cxy.y - (td.height / 2); + + self.paint(connector, { + minx:minx, + miny:miny, + td:td, + cxy:cxy + }, connectorDimensions); + + return [minx, minx+td.width, miny, miny+td.height]; + } + else return [0,0,0,0]; + }; + }; + + // ********************************* END OF OVERLAY DEFINITIONS *********************************************************************** + + // ********************************* OVERLAY CANVAS RENDERERS*********************************************************************** + + // ********************************* END OF OVERLAY CANVAS RENDERERS *********************************************************************** +})();;(function() { + + // http://ajaxian.com/archives/the-vml-changes-in-ie-8 + // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/ + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + + var vmlAttributeMap = { + "stroke-linejoin":"joinstyle", + "joinstyle":"joinstyle", + "endcap":"endcap", + "miterlimit":"miterlimit" + }; + + if (document.createStyleSheet) { + + // this is the style rule for IE7/6: it uses a CSS class, tidy. + document.createStyleSheet().addRule(".jsplumb_vml", "behavior:url(#default#VML);position:absolute;"); + + // these are for VML in IE8. you have to explicitly call out which elements + // you're going to expect to support VML! + // + // try to avoid IE8. it is recommended you set X-UA-Compatible="IE=7" if you can. + // + document.createStyleSheet().addRule("jsplumb\\:textbox", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:oval", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:rect", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:stroke", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:shape", "behavior:url(#default#VML);position:absolute;"); + + // in this page it is also mentioned that IE requires the extra arg to the namespace + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either. + // var iev = document.documentMode; + //if (!iev || iev < 8) + document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); + //else + // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML"); + } + + var scale = 1000, + _atts = function(o, atts) { + for (var i in atts) { + // IE8 fix: setattribute does not work after an element has been added to the dom! + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + //o.setAttribute(i, atts[i]); + o[i] = atts[i]; + } + }, + _node = function(name, d, atts) { + atts = atts || {}; + var o = document.createElement("jsplumb:" + name); + o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml"; + _pos(o, d); + _atts(o, atts); + return o; + }, + _pos = function(o,d) { + o.style.left = d[0] + "px"; + o.style.top = d[1] + "px"; + o.style.width= d[2] + "px"; + o.style.height= d[3] + "px"; + o.style.position = "absolute"; + }, + _conv = function(v) { + return Math.floor(v * scale); + }, + _convertStyle = function(s, ignoreAlpha) { + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + + return o; + }, + _applyStyles = function(node, style, component) { + var styleToWrite = {}; + if (style.strokeStyle) { + styleToWrite["stroked"] = "true"; + styleToWrite["strokecolor"] =_convertStyle(style.strokeStyle, true); + styleToWrite["strokeweight"] = style.lineWidth + "px"; + } + else styleToWrite["stroked"] = "false"; + + if (style.fillStyle) { + styleToWrite["filled"] = "true"; + styleToWrite["fillcolor"] = _convertStyle(style.fillStyle, true); + } + else styleToWrite["filled"] = "false"; + + if(style["dashstyle"]) { + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = style["dashstyle"]; + } + else if (style["stroke-dasharray"] && style["lineWidth"]) { + var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",", + parts = style["stroke-dasharray"].split(sep), + styleToUse = ""; + for(var i = 0; i < parts.length; i++) { + styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep); + } + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = styleToUse; + } + + _atts(node, styleToWrite); + }, + /* + * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. + */ + VmlComponent = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }, + /* + * Base class for Vml connectors. extends VmlComponent. + */ + VmlConnector = function(params) { + var self = this; + self.strokeNode = null; + self.canvas = null; + VmlComponent.apply(this, arguments); + clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : ""); + this.paint = function(d, style, anchor) { + if (style != null) { + var path = self.getPath(d), p = { "path":path }; + + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + outlineStyle = { + strokeStyle:_convertStyle(style.outlineColor), + lineWidth:outlineStrokeWidth + }; + + if (self.bgCanvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.bgCanvas = _node("shape", d, p); + jsPlumb.appendElement(self.bgCanvas, params.parent); + _pos(self.bgCanvas, d); + displayElements.push(self.bgCanvas); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.bgCanvas, d); + _atts(self.bgCanvas, p); + } + + _applyStyles(self.bgCanvas, outlineStyle, self); + } + + if (self.canvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.canvas = _node("shape", d, p); + jsPlumb.appendElement(self.canvas, params.parent); + displayElements.push(self.canvas); + + self.attachListeners(self.canvas, self); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.canvas, d); + _atts(self.canvas, p); + } + + _applyStyles(self.canvas, style, self); + } + }; + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + self.canvas.parentNode.appendChild(el); + displayElements.push(el); + }; + }, + /* + * + * Base class for Vml Endpoints. extends VmlComponent. + * + */ + VmlEndpoint = function(params) { + VmlComponent.apply(this, arguments); + var vml = null, self = this; + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function(d, style, anchor) { + var p = { }; + + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (vml == null) { + p["class"] = jsPlumb.endpointClass; + vml = self.getVml([0,0, d[2], d[3]], p, anchor); + self.canvas.appendChild(vml); + self.attachListeners(vml, self); + } + else { + //p["coordsize"] = "1,1";//(d[2] * scale) + "," + (d[3] * scale); again, unsure. + _pos(vml, [0,0, d[2], d[3]]); + _atts(vml, p); + } + + _applyStyles(vml, style); + }; + }; + + jsPlumb.Connectors.vml.Bezier = function() { + jsPlumb.Connectors.Bezier.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Straight = function() { + jsPlumb.Connectors.Straight.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Flowchart = function() { + jsPlumb.Connectors.Flowchart.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l"; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]); + } + // finally draw a line to the end + p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e"; + return p; + }; + }; + + jsPlumb.Endpoints.vml.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("oval", d, atts); }; + }; + + jsPlumb.Endpoints.vml.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("rect", d, atts); }; + }; + + /* + * VML Image Endpoint is the same as the default image endpoint. + */ + jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image; + + /** + * placeholder for Blank endpoint in vml renderer. + */ + jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank; + + /** + * VML Label renderer. uses the default label renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label; + + var AbstractVmlArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + VmlComponent.apply(this, arguments); + var self = this, canvas = null, path =null; + var getPath = function(d, connectorDimensions) { + return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) + + " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + + " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + + " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + + " x e"; + }; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) { + var p = {}; + if (strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] =_convertStyle(strokeStyle, true); + } + if (lineWidth) p["strokeweight"] = lineWidth + "px"; + if (fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = fillStyle; + } + var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + w = Math.abs(xmax - xmin), + h = Math.abs(ymax - ymin), + dim = [xmin, ymin, w, h]; + + // for VML, we create overlays using shapes that have the same dimensions and + // coordsize as their connector - overlays calculate themselves relative to the + // connector (it's how it's been done since the original canvas implementation, because + // for canvas that makes sense). + p["path"] = getPath(d, connectorDimensions); + p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale); + + dim[0] = connectorDimensions[0]; + dim[1] = connectorDimensions[1]; + dim[2] = connectorDimensions[2]; + dim[3] = connectorDimensions[3]; + + if (canvas == null) { + //p["class"] = jsPlumb.overlayClass; // TODO currentInstance? + canvas = _node("shape", dim, p); + connector.appendDisplayElement(canvas); + self.attachListeners(canvas, connector); + } + else { + _pos(canvas, dim); + _atts(canvas, p); + } + }; + }; + + jsPlumb.Overlays.vml.Arrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.vml.PlainArrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.vml.Diamond = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/** + * SVG support for jsPlumb. + * + * things to investigate: + * + * gradients: https://developer.mozilla.org/en/svg_in_html_introduction + * css:http://tutorials.jenkov.com/svg/svg-and-css.html + * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath + * pointer events: https://developer.mozilla.org/en/css/pointer-events + * + */ +;(function() { + + var svgAttributeMap = { + "stroke-linejoin":"stroke-linejoin", + "joinstyle":"stroke-linejoin", + "stroke-dashoffset":"stroke-dashoffset" + }; + + var ns = { + svg:"http://www.w3.org/2000/svg", + xhtml:"http://www.w3.org/1999/xhtml" + }, + _attr = function(node, attributes) { + for (var i in attributes) + node.setAttribute(i, "" + attributes[i]); + }, + _node = function(name, attributes) { + var n = document.createElementNS(ns.svg, name); + attributes = attributes || {}; + attributes["version"] = "1.1"; + attributes["xmnls"] = ns.xhtml; + _attr(n, attributes); + return n; + }, + _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; }, + _convertStyle = function(s, ignoreAlpha) { + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + + return o; + }, + _clearGradient = function(parent) { + for (var i = 0; i < parent.childNodes.length; i++) { + if (parent.childNodes[i].tagName == "linearGradient" || parent.childNodes[i].tagName == "radialGradient") + parent.removeChild(parent.childNodes[i]); + } + }, + _updateGradient = function(parent, node, style, dimensions) { + var id = "jsplumb_gradient_" + (new Date()).getTime(); + // first clear out any existing gradient + _clearGradient(parent); + // this checks for an 'offset' property in the gradient, and in the absence of it, assumes + // we want a linear gradient. if it's there, we create a radial gradient. + // it is possible that a more explicit means of defining the gradient type would be + // better. relying on 'offset' means that we can never have a radial gradient that uses + // some default offset, for instance. + if (!style.gradient.offset) { + var g = _node("linearGradient", {id:id}); + parent.appendChild(g); + } + else { + var g = _node("radialGradient", { + id:id + }); + parent.appendChild(g); + } + + // the svg radial gradient seems to treat stops in the reverse + // order to how canvas does it. so we want to keep all the maths the same, but + // iterate the actual style declarations in reverse order, if the x indexes are not in order. + for (var i = 0; i < style.gradient.stops.length; i++) { + // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of + // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments. + // so, not too concerned about leaving this in for now. + var styleToUse = i; + if (dimensions.length == 8) + styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i; + else + styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i; + var stopColor = _convertStyle(style.gradient.stops[styleToUse][1], true); + var s = _node("stop", {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor}); + g.appendChild(s); + } + var applyGradientTo = style.strokeStyle ? "stroke" : "fill"; + node.setAttribute("style", applyGradientTo + ":url(#" + id + ")"); + }, + _applyStyles = function(parent, node, style, dimensions) { + + if (style.gradient) { + _updateGradient(parent, node, style, dimensions); + } + else { + // make sure we clear any existing gradient + _clearGradient(parent); + node.setAttribute("style", ""); + } + + node.setAttribute("fill", style.fillStyle ? _convertStyle(style.fillStyle, true) : "none"); + node.setAttribute("stroke", style.strokeStyle ? _convertStyle(style.strokeStyle, true) : "none"); + if (style.lineWidth) { + node.setAttribute("stroke-width", style.lineWidth); + } + + // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like + // the syntax in VML but is actually kind of nasty: values are given in the pixel + // coordinate space, whereas in VML they are multiples of the width of the stroked + // line, which makes a lot more sense. for that reason, jsPlumb is supporting both + // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from + // VML, which will be the preferred method. the code below this converts a dashstyle + // attribute given in terms of stroke width into a pixel representation, by using the + // stroke's lineWidth. + if(style["stroke-dasharray"]) { + node.setAttribute("stroke-dasharray", style["stroke-dasharray"]); + } + if (style["dashstyle"] && style["lineWidth"]) { + var sep = style["dashstyle"].indexOf(",") == -1 ? " " : ",", + parts = style["dashstyle"].split(sep), + styleToUse = ""; + parts.forEach(function(p) { + styleToUse += (Math.floor(p * style.lineWidth) + sep); + }); + node.setAttribute("stroke-dasharray", styleToUse); + } + + // extra attributes such as join type, dash offset. + for (var i in svgAttributeMap) { + if (style[i]) { + node.setAttribute(svgAttributeMap[i], style[i]); + } + } + }, + _decodeFont = function(f) { + var r = /([0-9].)(p[xt])\s(.*)/; + var bits = f.match(r); + return {size:bits[1] + bits[2], font:bits[3]}; + }; + + /* + * Base class for SVG components. + */ + var SvgComponent = function(cssClass, originalArgs, pointerEventsSpec) { + var self = this; + pointerEventsSpec = pointerEventsSpec || "all"; + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + self.canvas = null, self.path = null, self.svg = null; + + this.setHover = function() { }; + + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.sizeCanvas(self.canvas,0,0,1,1); + + var clazz = cssClass + " " + (originalArgs[0].cssClass || ""); + + self.svg = _node("svg", { + "style":"", + "width":0, + "height":0, + "pointer-events":pointerEventsSpec, + "class": clazz + }); + + jsPlumb.appendElement(self.canvas, originalArgs[0]["parent"]); + self.canvas.appendChild(self.svg); + + // TODO this displayElement stuff is common between all components, across all + // renderers. would be best moved to jsPlumbUIComponent. + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + this.paint = function(d, style, anchor) { + if (style != null) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + _attr(self.svg, { + "style":_pos([0,0,d[2], d[3]]), + "width": d[2], + "height": d[3] + }); + self._paint.apply(this, arguments); + } + }; + }; + + /* + * Base class for SVG connectors. + */ + var SvgConnector = function(params) { + var self = this; + SvgComponent.apply(this, [ params["_jsPlumb"].connectorClass, arguments, "none" ]); + this._paint = function(d, style) { + var p = self.getPath(d), a = { "d":p }, outlineStyle = null; + a["pointer-events"] = "all"; + + // outline style. actually means drawing an svg object underneath the main one. + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + outlineStyle = { + strokeStyle:_convertStyle(style.outlineColor), + lineWidth:outlineStrokeWidth + }; + + if (self.bgPath == null) { + self.bgPath = _node("path", a); + self.svg.appendChild(self.bgPath); + self.attachListeners(self.bgPath, self); + } + else { + _attr(self.bgPath, a); + } + + _applyStyles(self.svg, self.bgPath, outlineStyle, d); + } + + + if (self.path == null) { + self.path = _node("path", a); + self.svg.appendChild(self.path); + self.attachListeners(self.path, self); + } + else { + _attr(self.path, a); + } + + _applyStyles(self.svg, self.path, style, d); + }; + }; + + /* + * SVG Bezier Connector + */ + jsPlumb.Connectors.svg.Bezier = function(params) { + jsPlumb.Connectors.Bezier.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]; }; + }; + + /* + * SVG straight line Connector + */ + jsPlumb.Connectors.svg.Straight = function(params) { + jsPlumb.Connectors.Straight.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; }; + }; + + jsPlumb.Connectors.svg.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "M " + dimensions[4] + "," + dimensions[5]; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)]; + } + // finally draw a line to the end + p = p + " " + dimensions[6] + "," + dimensions[7]; + return p; + }; + }; + + /* + * Base class for SVG endpoints. + */ + var SvgEndpoint = function(params) { + var self = this; + SvgComponent.apply(this, [ params["_jsPlumb"].endpointClass, arguments, "all" ]); + this._paint = function(d, style) { + var s = jsPlumb.extend({}, style); + if (s.outlineColor) { + s.strokeWidth = s.outlineWidth; + s.strokeStyle = _convertStyle(s.outlineColor, true); + } + + if (self.node == null) { + self.node = self.makeNode(d, s); + self.svg.appendChild(self.node); + self.attachListeners(self.node, self); + } + _applyStyles(self.svg, self.node, s, d); + _pos(self.node, d); + }; + }; + + /* + * SVG Dot Endpoint + */ + jsPlumb.Endpoints.svg.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("circle", { + "cx" : d[2] / 2, + "cy" : d[3] / 2, + "r" : d[2] / 2 + }); + }; + }; + + /* + * SVG Rectangle Endpoint + */ + jsPlumb.Endpoints.svg.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("rect", { + "width":d[2], + "height":d[3] + }); + }; + }; + + /* + * SVG Image Endpoint is the default image endpoint. + */ + jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image; + /* + * Blank endpoint in svg renderer is the default Blank endpoint. + */ + jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank; + /* + * Label endpoint in svg renderer is the default Label endpoint. + */ + jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label; + + + var AbstractSvgArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + var self = this, path =null; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + } + + _attr(path, { + "d" : makePath(d), + stroke : strokeStyle ? strokeStyle : null, + fill : fillStyle ? fillStyle : null + }); + }; + var makePath = function(d) { + return "M" + d.hxy.x + "," + d.hxy.y + + " L" + d.tail[0].x + "," + d.tail[0].y + + " L" + d.cxy.x + "," + d.cxy.y + + " L" + d.tail[1].x + "," + d.tail[1].y + + " L" + d.hxy.x + "," + d.hxy.y; + }; + }; + + jsPlumb.Overlays.svg.Arrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.svg.PlainArrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.svg.Diamond = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();;(function() { + +// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS ******************************************************************* + + // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. + var _connectionBeingDragged = null, + _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }, + _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }, + _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }, + _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, + _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }, + _setOffset = function(el, o) { jsPlumb.CurrentLibrary.setOffset(el, o); }; + + /* + * Class:CanvasMouseAdapter + * Provides support for mouse events on canvases. + */ + var CanvasMouseAdapter = function() { + var self = this; + self.overlayPlacements = []; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + jsPlumb.EventGenerator.apply(this, arguments); + /** + * returns whether or not the given event is ojver a painted area of the canvas. + */ + this._over = function(e) { + var o = _getOffset(_getElementObject(self.canvas)), + pageXY = _pageXY(e), + x = pageXY[0] - o.left, y = pageXY[1] - o.top; + if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + return false; + }; + + var _mouseover = false; + var _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false; + var _nullSafeHasClass = function(el, clazz) { + return el != null && _hasClass(el, clazz); + }; + this.mousemove = function(e) { + var pageXY = _pageXY(e), clientXY = _clientXY(e), + ee = document.elementFromPoint(clientXY[0], clientXY[1]), + eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); + var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); + if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.fire("mouseenter", self, e); + return true; + } + // TODO here there is a remote chance that the overlay the mouse moved onto + // is actually not an overlay for the current component. a more thorough check would + // be to ensure the overlay belonged to the current component. + else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { + _mouseover = false; + self.fire("mouseexit", self, e); + } + self.fire("mousemove", self, e); + }; + + this.click = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); + self.fire("mousedown", self, e); + } + }; + + this.mouseup = function(e) { + //if (self == _connectionBeingDragged) _connectionBeingDragged = null; + _mouseDown = false; + self.fire("mouseup", self, e); + }; + }; + + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + jsPlumb.appendElement(canvas, params.parent); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + params["_jsPlumb"].getId(canvas, params.uuid); + + return canvas; + }; + + /** + * Class:CanvasConnector + * Superclass for Canvas Connector renderers. + */ + var CanvasConnector = jsPlumb.CanvasConnector = function(params) { + + CanvasMouseAdapter.apply(this, arguments); + + var _paintOneStyle = function(dim, aStyle) { + self.ctx.save(); + jsPlumb.extend(self.ctx, aStyle); + if (aStyle.gradient) { + var g = self.createGradient(dim, self.ctx); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + self.ctx.strokeStyle = g; + } + self._paint(dim); + self.ctx.restore(); + }; + + var self = this, + clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent + }); + self.ctx = self.canvas.getContext("2d"); + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + self.paint = function(dim, style) { + if (style != null) { + + jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); + + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + _paintOneStyle(dim, outlineStyle); + } + _paintOneStyle(dim, style); + } + }; + }; + + /** + * Class:CanvasEndpoint + * Superclass for Canvas Endpoint renderers. + */ + var CanvasEndpoint = function(params) { + var self = this; + CanvasMouseAdapter.apply(this, arguments); + var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent + }); + self.ctx = self.canvas.getContext("2d"); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + // _paintOneStyle(d, outlineStyle); + } + + self._paint.apply(this, arguments); + }; + }; + + jsPlumb.Endpoints.canvas.Dot = function(params) { + var self = this; + jsPlumb.Endpoints.Dot.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }; + var calculateAdjustments = function(gradient) { + var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius; + gradient.offset && (offsetAdjustment = parseValue(gradient.offset)); + gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius)); + return [offsetAdjustment, innerRadius]; + }; + this._paint = function(d, style, anchor) { + if (style != null) { + var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(); + jsPlumb.extend(ctx, style); + if (style.gradient) { + var adjustments = calculateAdjustments(style.gradient), + yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0], + xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0], + g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + ctx.beginPath(); + ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + } + }; + }; + + jsPlumb.Endpoints.canvas.Rectangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) { + + var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(); + jsPlumb.extend(ctx, style); + + /* canvas gradient */ + if (style.gradient) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, d[2], d[3]); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + jsPlumb.Endpoints.canvas.Triangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Triangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) + { + var width = d[2], height = d[3], x = d[0], y = d[1]; + + var ctx = self.canvas.getContext('2d'); + var offsetX = 0, offsetY = 0, angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = style.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + /* + * Canvas Image Endpoint: uses the default version, which creates an tag. + */ + jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image; + + /* + * Blank endpoint in all renderers is just the default Blank endpoint. + */ + jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank; + + /* + * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Bezier = function() { + var self = this; + jsPlumb.Connectors.Bezier.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO i doubt this handles the case that source and target are swapped. + this.createGradient = function(dim, ctx, swap) { + return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + /* + * Canvas straight line Connector. Draws a straight line onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Straight = function() { + var self = this; + jsPlumb.Connectors.Straight.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO this does not handle the case that src and target are swapped. + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + jsPlumb.Connectors.canvas.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + +// ********************************* END OF CANVAS RENDERERS ******************************************************************* + + jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label; + + /** + * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. + */ + var CanvasOverlay = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }; + + var AbstractCanvasArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + CanvasOverlay.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + var ctx = connector.ctx; + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(d.hxy.x, d.hxy.y); + ctx.lineTo(d.tail[0].x, d.tail[0].y); + ctx.lineTo(d.cxy.x, d.cxy.y); + ctx.lineTo(d.tail[1].x, d.tail[1].y); + ctx.lineTo(d.hxy.x, d.hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + }; + + jsPlumb.Overlays.canvas.Arrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.canvas.PlainArrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.canvas.Diamond = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); +/* + * yui.jsPlumb 1.3.0-RC1 + * + * YUI3 specific functionality for jsPlumb. + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + * NOTE: for production usage you should use yui-all-x.x.x-min.js, which contains the main jsPlumb script and this script together, + * in a minified file. + * + * Dual licensed under MIT and GPL2. + * + */ + +/** + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + */ +(function() { + + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function( v, b, s ) { + for( var i = +b || 0, l = this.length; i < l; i++ ) { + if( this[i]===v || s && this[i]==v ) { return i; } + } + return -1; + }; + } + + var Y; + + YUI().use('node', 'dd', 'anim', function(_Y) { + Y = _Y; + Y.on("domready", function() { jsPlumb.init(); }); + }); + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }, + ddEvents = [ "drag:mouseDown", "drag:afterMouseDown", "drag:mouseup", + "drag:align", "drag:removeHandle", "drag:addHandle", "drag:removeInvalid", "drag:addInvalid", + "drag:start", "drag:end", "drag:drag", "drag:over", "drag:enter", + "drag:exit", "drag:drophit", "drag:dropmiss", "drop:over", "drop:enter", "drop:exit", "drop:hit" + ], + animEvents = [ "tween" ], + /** + * helper function to curry callbacks for some element. + */ + _wrapper = function(fn) { + return function() { + try { + fn.apply(this, arguments); + } + catch (e) { + console.log("wrap fail", e); + } + }; + }, + /** + * extracts options from the given options object, leaving out event handlers. + */ + _getDDOptions = function(options) { + var o = {}; + for (var i in options) if (ddEvents.indexOf(i) == -1) o[i] = options[i]; + return o; + }, + /** + * attaches all event handlers found in options to the given dragdrop object, and registering + * the given el as the element of interest. + */ + _attachListeners = function(dd, options, eventList) { + for (var ev in options) { + if (eventList.indexOf(ev) != -1) { + var w = _wrapper(options[ev]); + dd.on(ev, w); + } + } + }, + _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }, + _lastDragObject = null, + _extend = function(o1, o2) { + for (var i in o2) + o1[i] = o2[i]; + return o1; + }, + _getAttribute = function(el, attributeId) { + return el.getAttribute(attributeId); + }, + _getElementObject = function(el) { + return typeof el == 'string' ? Y.one('#' + el) : Y.one(el); + }; + + jsPlumb.CurrentLibrary = { + + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + var o = _extend({node:el, to:properties}, options); + var id = _getAttribute(el, "id"); + o["tween"] = jsPlumb.wrap(properties["tween"], function() { + jsPlumb.repaint(id); + }); + var a = new Y.Anim(o); + _attachListeners(a, o, animEvents); + a.run(); + }, + + appendElement : function(child, parent) { + _getElementObject (parent).append(child); + }, + + /** + * event binding wrapper. + */ + bind : function(el, event, callback) { + _getElementObject(el).on(event, callback); + }, + + dragEvents : { + "start":"drag:start", "stop":"drag:end", "drag":"drag:drag", "step":"step", + "over":"drop:enter", "out":"drop:exit", "drop":"drop:hit" + }, + + extend : _extend, + + getAttribute : _getAttribute, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + // this is a workaround for the unfortunate fact that in YUI3, the 'drop:exit' event does + // not contain a reference to the drag that just exited. single-threaded js to the + // rescue: we'll just keep it for ourselves. + if (eventArgs[0].drag) _lastDragObject = eventArgs[0].drag.el; + return _lastDragObject; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + return dd.scope; + }, + + getElementObject : _getElementObject, + + getOffset : function(el) { + var o = Y.DOM.getXY(el._node); + return {left:o[0], top:o[1]}; + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).get("parentNode"); + }, + + getScrollLeft : function(el) { + return 0; + }, + + getScrollTop : function(el) { + return 0; + }, + + getSize : function(el) { + //TODO must be a better way to get this? + //console.log("getSize"); + /*var bcr = _getElementObject(el)._node.getBoundingClientRect(); + return [ bcr.right - bcr.left, bcr.bottom - bcr.top]; // for some reason, in IE, the bounding rect does not always have width,height precomputed.*/ + return [ el._node.offsetWidth, el._node.offsetHeight ]; + }, + + getUIPosition : function(args) { + var n = args[0].currentTarget.el._node, + o = Y.DOM.getXY(n); + return {left:o[0], top:o[1]}; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options) { + var _opts = _getDDOptions(options); + var id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drag(_opts); + dd.el = el; + + var scope = options['scope'] || jsPlumb.Defaults.Scope; + dd.scope = scope; + + _draggablesById[id] = dd; + _add(_draggablesByScope, scope, dd); + + _attachListeners(dd, options, ddEvents); + }, + + initDroppable : function(el, options) { + var _opts = _getDDOptions(options); + var id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drop(_opts); + + _droppableOptions[id] = options; + + options = _extend({}, options); + var scope = options['scope'] || jsPlumb.Defaults.Scope; + + options["drop:enter"] = jsPlumb.wrap(options["drop:enter"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, true); + }, true); + options["drop:exit"] = jsPlumb.wrap(options["drop:exit"], function(e) { + _checkHover(el, false); + }); + options["drop:hit"] = jsPlumb.wrap(options["drop:hit"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, false); + }, true); + + _attachListeners(dd, options, ddEvents); + }, + + isAlreadyDraggable : function(el) { + el = _getElementObject(el); + return el.hasClass("yui3-dd-draggable"); + }, + + isDragSupported : function(el) { return true; }, + isDropSupported : function(el) { return true; }, + removeClass : function(el, clazz) { el.removeClass(clazz); }, + removeElement : function(el) { _getElementObject(el).remove(); }, + + setAttribute : function(el, attributeName, attributeValue) { + el.setAttribute(attributeName, attributeValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + if (dd) dd.set("lock", !draggable); + }, + + setDragScope : function(el, scope) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + if (dd) dd.scope = scope; + }, + + setOffset : function(el, o) { + el = _getElementObject(el); + el.set("top", o.top); + el.set("left", o.left); + } + }; +})();(function(){if(typeof Math.sgn=="undefined")Math.sgn=function(a){return a==0?0:a>0?1:-1};var p={subtract:function(a,b){return{x:a.x-b.x,y:a.y-b.y}},dotProduct:function(a,b){return a.x*b.x+a.y*b.y},square:function(a){return Math.sqrt(a.x*a.x+a.y*a.y)},scale:function(a,b){return{x:a.x*b,y:a.y*b}}},y=Math.pow(2,-65),u=function(a,b){for(var g=[],d=b.length-1,h=2*d-1,f=[],c=[],l=[],k=[],i=[[1,0.6,0.3,0.1],[0.4,0.6,0.6,0.4],[0.1,0.3,0.6,1]],e=0;e<=d;e++)f[e]=p.subtract(b[e],a);for(e=0;e<=d-1;e++){c[e]= +p.subtract(b[e+1],b[e]);c[e]=p.scale(c[e],3)}for(e=0;e<=d-1;e++)for(var m=0;m<=d;m++){l[e]||(l[e]=[]);l[e][m]=p.dotProduct(c[e],f[m])}for(e=0;e<=h;e++){k[e]||(k[e]=[]);k[e].y=0;k[e].x=parseFloat(e)/h}h=d-1;for(f=0;f<=d+h;f++){c=Math.min(f,d);for(e=Math.max(0,f-h);e<=c;e++){j=f-e;k[e+j].y+=l[j][e]*i[j][e]}}d=b.length-1;k=s(k,2*d-1,g,0);h=p.subtract(a,b[0]);l=p.square(h);for(e=i=0;e=64){g[0]=(a[0].x+a[b].x)/2;return 1}var n,o,q;k=a[0].y-a[b].y;i=a[b].x-a[0].x;e=a[0].x*a[b].y-a[b].x*a[0].y;m=max_distance_below=0;for(o=1;om)m=q;else if(q0?1:-1,c=null;h 0 ? e[0].tagName : null; + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + + // this code is a workaround for the case that the element being dragged has a margin set on it. jquery UI passes + // in the wrong offset if the element has a margin (it doesn't take the margin into account). the getBoundingClientRect + // method, which is in pretty much all browsers now, reports the right numbers. but it introduces a noticeable lag, which + // i don't like. + + /*if ( getBoundingClientRectSupported ) { + var r = eventArgs[1].helper[0].getBoundingClientRect(); + return { left : r.left, top: r.top }; + } else {*/ + if (eventArgs.length == 1) { + ret = { left: eventArgs[0].pageX, top:eventArgs[0].pageY }; + } + else { + var ui = eventArgs[1], _offset = ui.offset; + ret = _offset || ui.absolutePosition; + } + return ret; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options, isPlumbedComponent) { + options = options || {}; + // remove helper directive if present. + options.helper = null; + if (isPlumbedComponent) + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.draggable(options); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.droppable(options); + }, + + isAlreadyDraggable : function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return el.hasClass("ui-draggable"); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el[0].className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.removeClass(el[0], clazz); + } + } + catch (e) { + // SVGAnimatedString not supported; no problem. + } + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).remove(); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * sets the drag scope. probably time for a setDragOption method (roll this and the one above together) + * @param el + * @param scope + */ + setDragScope : function(el, scope) { + el.draggable("option", "scope", scope); + }, + + setOffset : function(el, o) { + jsPlumb.CurrentLibrary.getElementObject(el).offset(o); + }, + + /** + * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself. + * the other libraries do not. yui, in fact, cannot even pass an original event. we have to pull out stuff + * from the originalEvent to put in an options object for YUI. + * @param el + * @param event + * @param originalEvent + */ + trigger : function(el, event, originalEvent) { + //originalEvent.stopPropagation(); + //jsPlumb.CurrentLibrary.getElementObject(el).trigger(originalEvent); + var h = jQuery._data(jsPlumb.CurrentLibrary.getElementObject(el)[0], "handle"); + h(originalEvent); + //originalEvent.stopPropagation(); + }, + + /** + * event unbinding wrapper. it just so happens that jQuery uses 'unbind' also. yui3, for example, + * uses..something else. + */ + unbind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.unbind(event, callback); + } + }; + + $(document).ready(jsPlumb.init); + +})(jQuery); + diff --git a/build/1.3.10/js/jquery.jsPlumb-1.3.10-all-min.js b/archive/1.3.10/js/jquery.jsPlumb-1.3.10-all-min.js similarity index 100% rename from build/1.3.10/js/jquery.jsPlumb-1.3.10-all-min.js rename to archive/1.3.10/js/jquery.jsPlumb-1.3.10-all-min.js diff --git a/build/1.3.10/js/jquery.jsPlumb-1.3.10-all.js b/archive/1.3.10/js/jquery.jsPlumb-1.3.10-all.js similarity index 100% rename from build/1.3.10/js/jquery.jsPlumb-1.3.10-all.js rename to archive/1.3.10/js/jquery.jsPlumb-1.3.10-all.js diff --git a/build/1.3.10/js/jsPlumb-1.3.10-tests.js b/archive/1.3.10/js/jsPlumb-1.3.10-tests.js similarity index 100% rename from build/1.3.10/js/jsPlumb-1.3.10-tests.js rename to archive/1.3.10/js/jsPlumb-1.3.10-tests.js diff --git a/build/1.3.10/js/lib/qunit.js b/archive/1.3.10/js/lib/qunit.js similarity index 100% rename from build/1.3.10/js/lib/qunit.js rename to archive/1.3.10/js/lib/qunit.js diff --git a/build/1.3.10/js/mootools.jsPlumb-1.3.10-all-min.js b/archive/1.3.10/js/mootools.jsPlumb-1.3.10-all-min.js similarity index 100% rename from build/1.3.10/js/mootools.jsPlumb-1.3.10-all-min.js rename to archive/1.3.10/js/mootools.jsPlumb-1.3.10-all-min.js diff --git a/build/1.3.10/js/mootools.jsPlumb-1.3.10-all.js b/archive/1.3.10/js/mootools.jsPlumb-1.3.10-all.js similarity index 100% rename from build/1.3.10/js/mootools.jsPlumb-1.3.10-all.js rename to archive/1.3.10/js/mootools.jsPlumb-1.3.10-all.js diff --git a/build/1.3.10/js/yui.jsPlumb-1.3.10-all-min.js b/archive/1.3.10/js/yui.jsPlumb-1.3.10-all-min.js similarity index 100% rename from build/1.3.10/js/yui.jsPlumb-1.3.10-all-min.js rename to archive/1.3.10/js/yui.jsPlumb-1.3.10-all-min.js diff --git a/build/1.3.10/js/yui.jsPlumb-1.3.10-all.js b/archive/1.3.10/js/yui.jsPlumb-1.3.10-all.js similarity index 100% rename from build/1.3.10/js/yui.jsPlumb-1.3.10-all.js rename to archive/1.3.10/js/yui.jsPlumb-1.3.10-all.js diff --git a/archive/1.3.10/jsPlumb-1.3.10-RC1.js b/archive/1.3.10/jsPlumb-1.3.10-RC1.js new file mode 100644 index 000000000..1422d4a53 --- /dev/null +++ b/archive/1.3.10/jsPlumb-1.3.10-RC1.js @@ -0,0 +1,5645 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.9 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the jsPlumb core code. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + /** + * Class:jsPlumb + * The jsPlumb engine, registered as a static object in the window. This object contains all of the methods you will use to + * create and maintain Connections and Endpoints. + */ + + var canvasAvailable = !!document.createElement('canvas').getContext, + svgAvailable = !!window.SVGAngle || document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"), + // http://stackoverflow.com/questions/654112/how-do-you-detect-support-for-vml-or-svg-in-a-browser + vmlAvailable = function() { + if(vmlAvailable.vml == undefined) { + var a = document.body.appendChild(document.createElement('div')); + a.innerHTML = ''; + var b = a.firstChild; + b.style.behavior = "url(#default#VML)"; + vmlAvailable.vml = b ? typeof b.adj == "object": true; + a.parentNode.removeChild(a); + } + return vmlAvailable.vml; + }; + + var _findWithFunction = jsPlumbUtil.findWithFunction, + _indexOf = jsPlumbUtil.indexOf, + _removeWithFunction = jsPlumbUtil.removeWithFunction, + _remove = jsPlumbUtil.remove, + // TODO support insert index + _addWithFunction = jsPlumbUtil.addWithFunction, + _addToList = jsPlumbUtil.addToList, + /** + an isArray function that even works across iframes...see here: + + http://tobyho.com/2011/01/28/checking-types-in-javascript/ + + i was originally using "a.constructor == Array" as a test. + */ + _isArray = jsPlumbUtil.isArray, + _isString = jsPlumbUtil.isString, + _isObject = jsPlumbUtil.isObject; + + // for those browsers that dont have it. they still don't have it! but at least they won't crash. + if (!window.console) + window.console = { time:function(){}, timeEnd:function(){}, group:function(){}, groupEnd:function(){}, log:function(){} }; + + var _connectionBeingDragged = null, + _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }, + _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }, + _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }, + _log = jsPlumbUtil.log, + _group = jsPlumbUtil.group, + _groupEnd = jsPlumbUtil.groupEnd, + _time = jsPlumbUtil.time, + _timeEnd = jsPlumbUtil.timeEnd, + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + _timestamp = function() { return "" + (new Date()).getTime(); }, + + /* + * Class:jsPlumbUIComponent + * Abstract superclass for UI components Endpoint and Connection. Provides the abstraction of paintStyle/hoverPaintStyle, + * and also extends jsPlumbUtil.EventGenerator to provide the bind and fire methods. + */ + jsPlumbUIComponent = function(params) { + var self = this, + a = arguments, + _hover = false, + parameters = params.parameters || {}, + idPrefix = self.idPrefix, + id = idPrefix + (new Date()).getTime(), + paintStyle = null, + hoverPaintStyle = null; + + self._jsPlumb = params["_jsPlumb"]; + self.getId = function() { return id; }; + self.tooltip = params.tooltip; + self.hoverClass = params.hoverClass || self._jsPlumb.Defaults.HoverClass || jsPlumb.Defaults.HoverClass; + + // all components can generate events + jsPlumbUtil.EventGenerator.apply(this); + // all components get this clone function. + // TODO issue 116 showed a problem with this - it seems 'a' that is in + // the clone function's scope is shared by all invocations of it, the classic + // JS closure problem. for now, jsPlumb does a version of this inline where + // it used to call clone. but it would be nice to find some time to look + // further at this. + this.clone = function() { + var o = new Object(); + self.constructor.apply(o, a); + return o; + }; + + this.getParameter = function(name) { return parameters[name]; }, + this.getParameters = function() { + return parameters; + }, + this.setParameter = function(name, value) { parameters[name] = value; }, + this.setParameters = function(p) { parameters = p; }, + this.overlayPlacements = []; + + // user can supply a beforeDetach callback, which will be executed before a detach + // is performed; returning false prevents the detach. + var beforeDetach = params.beforeDetach; + this.isDetachAllowed = function(connection) { + var r = self._jsPlumb.checkCondition("beforeDetach", connection ); + if (beforeDetach) { + try { + r = beforeDetach(connection); + } + catch (e) { _log("jsPlumb: beforeDetach callback failed", e); } + } + return r; + }; + + // user can supply a beforeDrop callback, which will be executed before a dropped + // connection is confirmed. user can return false to reject connection. + var beforeDrop = params.beforeDrop; + this.isDropAllowed = function(sourceId, targetId, scope, connection, dropEndpoint) { + var r = self._jsPlumb.checkCondition("beforeDrop", { + sourceId:sourceId, + targetId:targetId, + scope:scope, + connection:connection, + dropEndpoint:dropEndpoint + }); + if (beforeDrop) { + try { + r = beforeDrop({ + sourceId:sourceId, + targetId:targetId, + scope:scope, + connection:connection, + dropEndpoint:dropEndpoint + }); + } + catch (e) { _log("jsPlumb: beforeDrop callback failed", e); } + } + return r; + }; + + // helper method to update the hover style whenever it, or paintStyle, changes. + // we use paintStyle as the foundation and merge hoverPaintStyle over the + // top. + var _updateHoverStyle = function() { + if (paintStyle && hoverPaintStyle) { + var mergedHoverStyle = {}; + jsPlumb.extend(mergedHoverStyle, paintStyle); + jsPlumb.extend(mergedHoverStyle, hoverPaintStyle); + delete self["hoverPaintStyle"]; + // we want the fillStyle of paintStyle to override a gradient, if possible. + if (mergedHoverStyle.gradient && paintStyle.fillStyle) + delete mergedHoverStyle["gradient"]; + hoverPaintStyle = mergedHoverStyle; + } + }; + + /* + * Sets the paint style and then repaints the element. + * + * Parameters: + * style - Style to use. + */ + this.setPaintStyle = function(style, doNotRepaint) { + paintStyle = style; + self.paintStyleInUse = paintStyle; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /** + * Gets the component's paint style. + * + * Returns: + * the component's paint style. if there is no hoverPaintStyle set then this will be the paint style used all the time, otherwise this is the style used when the mouse is not hovering. + */ + this.getPaintStyle = function() { + return paintStyle; + }; + + /* + * Sets the paint style to use when the mouse is hovering over the element. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the component will not be repainted. useful when setting things up initially. + */ + this.setHoverPaintStyle = function(style, doNotRepaint) { + hoverPaintStyle = style; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /** + * Gets the component's hover paint style. + * + * Returns: + * the component's hover paint style. may be null. + */ + this.getHoverPaintStyle = function() { + return hoverPaintStyle; + }; + + /* + * sets/unsets the hover state of this element. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + this.setHover = function(hover, ignoreAttachedElements, timestamp) { + // while dragging, we ignore these events. this keeps the UI from flashing and + // swishing and whatevering. + if (!self._jsPlumb.currentlyDragging && !self._jsPlumb.isHoverSuspended()) { + + _hover = hover; + if (self.hoverClass != null && self.canvas != null) { + if (hover) + jpcl.addClass(self.canvas, self.hoverClass); + else + jpcl.removeClass(self.canvas, self.hoverClass); + } + if (hoverPaintStyle != null) { + self.paintStyleInUse = hover ? hoverPaintStyle : paintStyle; + timestamp = timestamp || _timestamp(); + self.repaint({timestamp:timestamp, recalc:false}); + } + // get the list of other affected elements, if supported by this component. + // for a connection, its the endpoints. for an endpoint, its the connections! surprise. + if (self.getAttachedElements && !ignoreAttachedElements) + _updateAttachedElements(hover, _timestamp(), self); + } + }; + + this.isHover = function() { return _hover; }; + + var jpcl = jsPlumb.CurrentLibrary, + events = [ "click", "dblclick", "mouseenter", "mouseout", "mousemove", "mousedown", "mouseup", "contextmenu" ], + eventFilters = { "mouseout":"mouseexit" }, + bindOne = function(o, c, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.bind(o, evt, function(ee) { + c.fire(filteredEvent, c, ee); + }); + }, + unbindOne = function(o, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.unbind(o, evt); + }; + + this.attachListeners = function(o, c) { + for (var i = 0; i < events.length; i++) { + bindOne(o, c, events[i]); + } + }; + + var _updateAttachedElements = function(state, timestamp, sourceElement) { + var affectedElements = self.getAttachedElements(); // implemented in subclasses + if (affectedElements) { + for (var i = 0; i < affectedElements.length; i++) { + if (!sourceElement || sourceElement != affectedElements[i]) + affectedElements[i].setHover(state, true, timestamp); // tell the attached elements not to inform their own attached elements. + } + } + }; + + this.reattachListenersForElement = function(o) { + if (arguments.length > 1) { + for (var i = 0; i < events.length; i++) + unbindOne(o, events[i]); + for (var i = 1; i < arguments.length; i++) + self.attachListeners(o, arguments[i]); + } + }; + }, + + overlayCapableJsPlumbUIComponent = function(params) { + jsPlumbUIComponent.apply(this, arguments); + var self = this; + /* + * Property: overlays + * List of Overlays for this component. + */ + this.overlays = []; + + var processOverlay = function(o) { + var _newOverlay = null; + if (_isArray(o)) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0], + // make a copy of the object so as not to mess up anyone else's reference... + p = jsPlumb.extend({component:self, _jsPlumb:self._jsPlumb}, o[1]); + if (o.length == 3) jsPlumb.extend(p, o[2]); + _newOverlay = new jsPlumb.Overlays[self._jsPlumb.getRenderMode()][type](p); + if (p.events) { + for (var evt in p.events) { + _newOverlay.bind(evt, p.events[evt]); + } + } + } else if (o.constructor == String) { + _newOverlay = new jsPlumb.Overlays[self._jsPlumb.getRenderMode()][o]({component:self, _jsPlumb:self._jsPlumb}); + } else { + _newOverlay = o; + } + + self.overlays.push(_newOverlay); + }, + calculateOverlaysToAdd = function(params) { + var defaultKeys = self.defaultOverlayKeys || [], + o = params.overlays, + checkKey = function(k) { + return self._jsPlumb.Defaults[k] || jsPlumb.Defaults[k] || []; + }; + + if (!o) o = []; + + for (var i = 0; i < defaultKeys.length; i++) + o.unshift.apply(o, checkKey(defaultKeys[i])); + + return o; + } + + var _overlays = calculateOverlaysToAdd(params);//params.overlays || self._jsPlumb.Defaults.Overlays; + if (_overlays) { + for (var i = 0; i < _overlays.length; i++) { + processOverlay(_overlays[i]); + } + } + + // overlay finder helper method + var _getOverlayIndex = function(id) { + var idx = -1; + for (var i = 0; i < self.overlays.length; i++) { + if (id === self.overlays[i].id) { + idx = i; + break; + } + } + return idx; + }; + + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + this.addOverlay = function(overlay) { + processOverlay(overlay); + self.repaint(); + }; + + /* + * Function: getOverlay + * Gets an overlay, by ID. Note: by ID. You would pass an 'id' parameter + * in to the Overlay's constructor arguments, and then use that to retrieve + * it via this method. + */ + this.getOverlay = function(id) { + var idx = _getOverlayIndex(id); + return idx >= 0 ? self.overlays[idx] : null; + }; + + /* + * Function:getOverlays + * Gets all the overlays for this component. + */ + this.getOverlays = function() { + return self.overlays; + }; + + /* + * Function: hideOverlay + * Hides the overlay specified by the given id. + */ + this.hideOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.hide(); + }; + + this.hideOverlays = function() { + for (var i = 0; i < self.overlays.length; i++) + self.overlays[i].hide(); + }; + + /* + * Function: showOverlay + * Shows the overlay specified by the given id. + */ + this.showOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.show(); + }; + + this.showOverlays = function() { + for (var i = 0; i < self.overlays.length; i++) + self.overlays[i].show(); + }; + + /** + * Function: removeAllOverlays + * Removes all overlays from the Connection, and then repaints. + */ + this.removeAllOverlays = function() { + for (var i in self.overlays) + self.overlays[i].cleanup(); + + self.overlays.splice(0, self.overlays.length); + self.repaint(); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + this.removeOverlay = function(overlayId) { + var idx = _getOverlayIndex(overlayId); + if (idx != -1) { + var o = self.overlays[idx]; + o.cleanup(); + self.overlays.splice(idx, 1); + } + }; + + /** + * Function:removeOverlays + * Removes a set of overlays by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + this.removeOverlays = function() { + for (var i = 0; i < arguments.length; i++) + self.removeOverlay(arguments[i]); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + var _internalLabelOverlayId = "__label", + _makeLabelOverlay = function(params) { + + var _params = { + cssClass:params.cssClass, + labelStyle : this.labelStyle, + id:_internalLabelOverlayId, + component:self, + _jsPlumb:self._jsPlumb + }, + mergedParams = jsPlumb.extend(_params, params); + + return new jsPlumb.Overlays[self._jsPlumb.getRenderMode()].Label( mergedParams ); + }; + if (params.label) { + var loc = params.labelLocation || self.defaultLabelLocation || 0.5, + labelStyle = params.labelStyle || self._jsPlumb.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.overlays.push(_makeLabelOverlay({ + label:params.label, + location:loc, + labelStyle:labelStyle + })); + } + + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String, a Function that returns a String, or a params object containing { "label", "labelStyle", "location", "cssClass" }. Note that this uses innerHTML on the label div, so keep + * that in mind if you need escaped HTML. + */ + this.setLabel = function(l) { + var lo = self.getOverlay(_internalLabelOverlayId); + if (!lo) { + var params = l.constructor == String || l.constructor == Function ? { label:l } : l; + lo = _makeLabelOverlay(params); + this.overlays.push(lo); + } + else { + if (l.constructor == String || l.constructor == Function) lo.setLabel(l); + else { + if (l.label) lo.setLabel(l.label); + if (l.location) lo.setLocation(l.location); + } + } + + self.repaint(); + }; + + /* + Function:getLabel + Returns the label text for this component (or a function if you are labelling with a function). + This does not return the overlay itself; this is a convenience method which is a pair with + setLabel; together they allow you to add and access a Label Overlay without having to create the + Overlay object itself. For access to the underlying label overlay that jsPlumb has created, + use getLabelOverlay. + */ + this.getLabel = function() { + var lo = self.getOverlay(_internalLabelOverlayId); + return lo != null ? lo.getLabel() : null; + }; + + /* + Function:getLabelOverlay + Returns the underlying internal label overlay, which will exist if you specified a label on + a connect or addEndpoint call, or have called setLabel at any stage. + */ + this.getLabelOverlay = function() { + return self.getOverlay(_internalLabelOverlayId); + } + }, + + _bindListeners = function(obj, _self, _hoverFunction) { + obj.bind("click", function(ep, e) { _self.fire("click", _self, e); }); + obj.bind("dblclick", function(ep, e) { _self.fire("dblclick", _self, e); }); + obj.bind("contextmenu", function(ep, e) { _self.fire("contextmenu", _self, e); }); + obj.bind("mouseenter", function(ep, e) { + if (!_self.isHover()) { + _hoverFunction(true); + _self.fire("mouseenter", _self, e); + } + }); + obj.bind("mouseexit", function(ep, e) { + if (_self.isHover()) { + _hoverFunction(false); + _self.fire("mouseexit", _self, e); + } + }); + }; + + var _jsPlumbInstanceIndex = 0, + getInstanceIndex = function() { + var i = _jsPlumbInstanceIndex + 1; + _jsPlumbInstanceIndex++; + return i; + }; + + var jsPlumbInstance = function(_defaults) { + + /* + * Property: Defaults + * + * These are the default settings for jsPlumb. They are what will be used if you do not supply specific pieces of information + * to the various API calls. A convenient way to implement your own look and feel can be to override these defaults + * by including a script somewhere after the jsPlumb include, but before you make any calls to jsPlumb. + * + * Properties: + * - *Anchor* The default anchor to use for all connections (both source and target). Default is "BottomCenter". + * - *Anchors* The default anchors to use ([source, target]) for all connections. Defaults are ["BottomCenter", "BottomCenter"]. + * - *ConnectionsDetachable* Whether or not connections are detachable by default (using the mouse). Defaults to true. + * - *ConnectionOverlays* The default overlay definitions for Connections. Defaults to an empty list. + * - *Connector* The default connector definition to use for all connections. Default is "Bezier". + * - *Container* Optional selector or element id that instructs jsPlumb to append elements it creates to a specific element. + * - *DragOptions* The default drag options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *DropOptions* The default drop options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *Endpoint* The default endpoint definition to use for all connections (both source and target). Default is "Dot". + * - *EndpointOverlays* The default overlay definitions for Endpoints. Defaults to an empty list. + * - *Endpoints* The default endpoint definitions ([ source, target ]) to use for all connections. Defaults are ["Dot", "Dot"]. + * - *EndpointStyle* The default style definition to use for all endpoints. Default is fillStyle:"#456". + * - *EndpointStyles* The default style definitions ([ source, target ]) to use for all endpoints. Defaults are empty. + * - *EndpointHoverStyle* The default hover style definition to use for all endpoints. Default is null. + * - *EndpointHoverStyles* The default hover style definitions ([ source, target ]) to use for all endpoints. Defaults are null. + * - *HoverPaintStyle* The default hover style definition to use for all connections. Defaults are null. + * - *LabelStyle* The default style to use for label overlays on connections. + * - *LogEnabled* Whether or not the jsPlumb log is enabled. defaults to false. + * - *Overlays* The default overlay definitions (for both Connections and Endpoint). Defaults to an empty list. + * - *MaxConnections* The default maximum number of connections for an Endpoint. Defaults to 1. + * - *PaintStyle* The default paint style for a connection. Default is line width of 8 pixels, with color "#456". + * - *RenderMode* What mode to use to paint with. If you're on IE<9, you don't really get to choose this. You'll just get VML. Otherwise, the jsPlumb default is to use SVG. + * - *Scope* The default "scope" to use for connections. Scope lets you assign connections to different categories. + */ + this.Defaults = { + Anchor : "BottomCenter", + Anchors : [ null, null ], + ConnectionsDetachable : true, + ConnectionOverlays : [ ], + Connector : "Bezier", + Container : null, + DragOptions : { }, + DropOptions : { }, + Endpoint : "Dot", + EndpointOverlays : [ ], + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : "#456" }, + EndpointStyles : [ null, null ], + EndpointHoverStyle : null, + EndpointHoverStyles : [ null, null ], + HoverPaintStyle : null, + LabelStyle : { color : "black" }, + LogEnabled : false, + Overlays : [ ], + MaxConnections : 1, + PaintStyle : { lineWidth : 8, strokeStyle : "#456" }, + //Reattach:false, + RenderMode : "svg", + Scope : "jsPlumb_DefaultScope" + }; + if (_defaults) jsPlumb.extend(this.Defaults, _defaults); + + this.logEnabled = this.Defaults.LogEnabled; + + jsPlumbUtil.EventGenerator.apply(this); + var _currentInstance = this, + _instanceIndex = getInstanceIndex(), + _bb = _currentInstance.bind, + _initialDefaults = {}; + + for (var i in this.Defaults) + _initialDefaults[i] = this.Defaults[i]; + + this.bind = function(event, fn) { + if ("ready" === event && initialized) fn(); + else _bb.apply(_currentInstance,[event, fn]); + }; + + /* + Function: importDefaults + Imports all the given defaults into this instance of jsPlumb. + */ + _currentInstance.importDefaults = function(d) { + for (var i in d) { + _currentInstance.Defaults[i] = d[i]; + } + }; + + /* + Function:restoreDefaults + Restores the default settings to "factory" values. + */ + _currentInstance.restoreDefaults = function() { + _currentInstance.Defaults = jsPlumb.extend({}, _initialDefaults); + }; + + var log = null, + repaintFunction = function() { + jsPlumb.repaintEverything(); + }, + automaticRepaint = true, + repaintEverything = function() { + if (automaticRepaint) + repaintFunction(); + }, + resizeTimer = null, + initialized = false, + connectionsByScope = {}, + /** + * map of element id -> endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + endpointsByElement = {}, + endpointsByUUID = {}, + offsets = {}, + offsetTimestamps = {}, + floatingConnections = {}, + draggableStates = {}, + canvasList = [], + sizes = [], + //listeners = {}, // a map: keys are event types, values are lists of listeners. + DEFAULT_SCOPE = this.Defaults.Scope, + renderMode = null, // will be set in init() + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }, + + /** + * appends an element to some other element, which is calculated as follows: + * + * 1. if _currentInstance.Defaults.Container exists, use that element. + * 2. if the 'parent' parameter exists, use that. + * 3. otherwise just use the document body. + * + */ + _appendElement = function(el, parent) { + if (_currentInstance.Defaults.Container) + jsPlumb.CurrentLibrary.appendElement(el, _currentInstance.Defaults.Container); + else if (!parent) + document.body.appendChild(el); + else + jsPlumb.CurrentLibrary.appendElement(el, parent); + }, + + _curIdStamp = 1, + _idstamp = function() { return "" + _curIdStamp++; }, + + /** + * YUI, for some reason, put the result of a Y.all call into an object that contains + * a '_nodes' array, instead of handing back an array-like object like the other + * libraries do. + */ + _convertYUICollection = function(c) { + return c._nodes ? c._nodes : c; + }, + + _suspendDrawing = false, + /* + sets whether or not to suspend drawing. you should use this if you need to connect a whole load of things in one go. + it will save you a lot of time. + */ + _setSuspendDrawing = function(val, repaintAfterwards) { + _suspendDrawing = val; + if (repaintAfterwards) _currentInstance.repaintEverything(); + }, + + /** + * Draws an endpoint and its connections. this is the main entry point into drawing connections as well + * as endpoints, since jsPlumb is endpoint-centric under the hood. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + _draw = function(element, ui, timestamp) { + if (!_suspendDrawing) { + var id = _getAttribute(element, "id"), + repaintEls = _currentInstance.dragManager.getElementsForDraggable(id); + + if (timestamp == null) timestamp = _timestamp(); + + _currentInstance.anchorManager.redraw(id, ui, timestamp); + + if (repaintEls) { + for (var i in repaintEls) { + _currentInstance.anchorManager.redraw(repaintEls[i].id, ui, timestamp, repaintEls[i].offset); + } + } + } + }, + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + _elementProxy = function(element, fn) { + var retVal = null; + if (_isArray(element)) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }, + + /** + * gets an Endpoint by uuid. + */ + _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }, + + /** + * inits a draggable if it's not already initialised. + */ + _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? false : isDraggable, + jpcl = jsPlumb.CurrentLibrary; + if (draggable) { + if (jpcl.isDragSupported(element) && !jpcl.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jpcl.dragEvents["drag"], + stopEvent = jpcl.dragEvents["stop"], + startEvent = jpcl.dragEvents["start"]; + + options[startEvent] = _wrap(options[startEvent], function() { + _currentInstance.setHoverSuspended(true); + }); + + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jpcl.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jpcl.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + _currentInstance.setHoverSuspended(false); + }); + var elId = _getId(element); // need ID + draggableStates[elId] = true; + var draggable = draggableStates[elId]; + options.disabled = draggable == null ? false : !draggable; + jpcl.initDraggable(element, options, false); + _currentInstance.dragManager.register(element); + } + } + }, + + /* + * prepares a final params object that can be passed to _newConnection, taking into account defaults, events, etc. + */ + _prepareConnectionParams = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + // hotwire endpoints passed as source or target to sourceEndpoint/targetEndpoint, respectively. + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not full. + // source: + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + // target: + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + // copy in any connectorOverlays that were specified on the source endpoint. + // it doesnt copy target endpoint overlays. i'm not sure if we want it to or not. + if (_p.sourceEndpoint && _p.sourceEndpoint.connectorOverlays) { + _p.overlays = _p.overlays || []; + for (var i = 0; i < _p.sourceEndpoint.connectorOverlays.length; i++) { + _p.overlays.push(_p.sourceEndpoint.connectorOverlays[i]); + } + } + + // tooltip. params.tooltip takes precedence, then sourceEndpoint.connectorTooltip. + _p.tooltip = params.tooltip; + if (!_p.tooltip && _p.sourceEndpoint && _p.sourceEndpoint.connectorTooltip) + _p.tooltip = _p.sourceEndpoint.connectorTooltip; + + // if there's a target specified (which of course there should be), and there is no + // target endpoint specified, and 'newConnection' was not set to true, then we check to + // see if a prior call to makeTarget has provided us with the specs for the target endpoint, and + // we use those if so. additionally, if the makeTarget call was specified with 'uniqueEndpoint' set + // to true, then if that target endpoint has already been created, we re-use it. + if (_p.target && !_p.target.endpoint && !_p.targetEndpoint && !_p.newConnection) { + var tid = _getId(_p.target), + tep =_targetEndpointDefinitions[tid], + existingUniqueEndpoint = _targetEndpoints[tid]; + + if (tep) { + // if target not enabled, return. + if (!_targetsEnabled[tid]) return; + + // check for max connections?? + var newEndpoint = existingUniqueEndpoint != null ? existingUniqueEndpoint : _currentInstance.addEndpoint(_p.target, tep); + if (_targetEndpointsUnique[tid]) _targetEndpoints[tid] = newEndpoint; + _p.targetEndpoint = newEndpoint; + newEndpoint._makeTargetCreator = true; + } + } + + // same thing, but for source. + if (_p.source && !_p.source.endpoint && !_p.sourceEndpoint && !_p.newConnection) { + var tid = _getId(_p.source), + tep = _sourceEndpointDefinitions[tid], + existingUniqueEndpoint = _sourceEndpoints[tid]; + + if (tep) { + // if source not enabled, return. + if (!_sourcesEnabled[tid]) return; + + var newEndpoint = existingUniqueEndpoint != null ? existingUniqueEndpoint : _currentInstance.addEndpoint(_p.source, tep); + if (_sourceEndpointsUnique[tid]) _sourceEndpoints[tid] = newEndpoint; + _p.sourceEndpoint = newEndpoint; + } + } + + return _p; + }, + + _newConnection = function(params) { + var connectionFunc = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + endpointFunc = _currentInstance.Defaults.EndpointType || Endpoint, + parent = jsPlumb.CurrentLibrary.getParent; + + if (params.container) + params["parent"] = params.container; + else { + if (params.sourceEndpoint) + params["parent"] = params.sourceEndpoint.parent; + else if (params.source.constructor == endpointFunc) + params["parent"] = params.source.parent; + else params["parent"] = parent(params.source); + } + + params["_jsPlumb"] = _currentInstance; + var con = new connectionFunc(params); + con.id = "con_" + _idstamp(); + _eventFireProxy("click", "click", con); + _eventFireProxy("dblclick", "dblclick", con); + _eventFireProxy("contextmenu", "contextmenu", con); + return con; + }, + + /** + * adds the connection to the backing model, fires an event if necessary and then redraws + */ + _finaliseConnection = function(jpc, params, originalEvent) { + params = params || {}; + // add to list of connections (by scope). + if (!jpc.suspendedEndpoint) + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + if (!params.doNotFireConnectionEvent && params.fireEvent !== false) { + _currentInstance.fire("jsPlumbConnection", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }, originalEvent); + } + // always inform the anchor manager + // except that if jpc has a suspended endpoint it's not true to say the + // connection is new; it has just (possibly) moved. the question is whether + // to make that call here or in the anchor manager. i think perhaps here. + _currentInstance.anchorManager.newConnection(jpc); + // force a paint + _draw(jpc.source); + }, + + _eventFireProxy = function(event, proxyEvent, obj) { + obj.bind(event, function(originalObject, originalEvent) { + _currentInstance.fire(proxyEvent, obj, originalEvent); + }); + }, + + /** + * for the given endpoint params, returns an appropriate parent element for the UI elements that will be added. + * this function is used by _newEndpoint (directly below), and also in the makeSource function in jsPlumb. + * + * the logic is to first look for a "container" member of params, and pass that back if found. otherwise we + * handoff to the 'getParent' function in the current library. + */ + _getParentFromParams = function(params) { + if (params.container) + return params.container; + else { + var tag = jsPlumb.CurrentLibrary.getTagName(params.source), + p = jsPlumb.CurrentLibrary.getParent(params.source); + if (tag && tag.toLowerCase() === "td") + return jsPlumb.CurrentLibrary.getParent(p); + else return p; + } + }, + + /** + factory method to prepare a new endpoint. this should always be used instead of creating Endpoints + manually, since this method attaches event listeners and an id. + */ + _newEndpoint = function(params) { + var endpointFunc = _currentInstance.Defaults.EndpointType || Endpoint; + params.parent = _getParentFromParams(params); + params["_jsPlumb"] = _currentInstance; + var ep = new endpointFunc(params); + ep.id = "ep_" + _idstamp(); + _eventFireProxy("click", "endpointClick", ep); + _eventFireProxy("dblclick", "endpointDblClick", ep); + _eventFireProxy("contextmenu", "contextmenu", ep); + return ep; + }, + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + _operation = function(elId, func, endpointFunc) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + if (endpointFunc) endpointFunc(endpoints[i]); + } + } + }, + /** + * perform an operation on all elements. + */ + _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }, + + /** + * helper to remove an element from the DOM. + */ + _removeElement = function(element, parent) { + if (element != null && element.parentNode != null) { + element.parentNode.removeChild(element); + } + }, + /** + * helper to remove a list of elements from the DOM. + */ + _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }, + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }, + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + _setVisible = function(el, state, alsoChangeEndpoints) { + state = state === "block"; + var endpointFunc = null; + if (alsoChangeEndpoints) { + if (state) endpointFunc = function(ep) { + ep.setVisible(true, true, true); + }; + else endpointFunc = function(ep) { + ep.setVisible(false, true, true); + }; + } + var id = _getAttribute(el, "id"); + _operation(id, function(jpc) { + if (state && alsoChangeEndpoints) { + // this test is necessary because this functionality is new, and i wanted to maintain backwards compatibility. + // this block will only set a connection to be visible if the other endpoint in the connection is also visible. + var oidx = jpc.sourceId === id ? 1 : 0; + if (jpc.endpoints[oidx].isVisible()) jpc.setVisible(true); + } + else // the default behaviour for show, and what always happens for hide, is to just set the visibility without getting clever. + jpc.setVisible(state); + }, endpointFunc); + }, + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? false : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }, + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + _toggleVisible = function(elId, changeEndpoints) { + var endpointFunc = null; + if (changeEndpoints) { + endpointFunc = function(ep) { + var state = ep.isVisible(); + ep.setVisible(!state); + }; + } + _operation(elId, function(jpc) { + var state = jpc.isVisible(); + jpc.setVisible(!state); + }, endpointFunc); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }, + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return offsets[elId]; + } + if (recalc || !offset) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + if (sizes[elId] == null) { + var s = _getElementObject(elId); + if (s != null) + sizes[elId] = _getSize(s); + } + } + + if(offsets[elId] && !offsets[elId].right) { + offsets[elId].right = offsets[elId].left + sizes[elId][0]; + offsets[elId].bottom = offsets[elId].top + sizes[elId][1]; + offsets[elId].width = sizes[elId][0]; + offsets[elId].height = sizes[elId][1]; + offsets[elId].centerx = offsets[elId].left + (offsets[elId].width / 2); + offsets[elId].centery = offsets[elId].top + (offsets[elId].height / 2); + } + return offsets[elId]; + }, + + // TODO comparison performance + _getCachedData = function(elId) { + var o = offsets[elId]; + if (!o) o = _updateOffset({elId:elId}); + return {o:o, s:sizes[elId]}; + }, + + /** + * gets an id for the given element, creating and setting one if + * necessary. the id is of the form + * + * jsPlumb__ + * + * where "index in instance" is a monotonically increasing integer that starts at 0, + * for each instance. this method is used not only to assign ids to elements that do not + * have them but also to connections and endpoints. + */ + _getId = function(element, uuid, doNotCreateIfNotFound) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else if (arguments.length == 1 || (arguments.length == 3 && !arguments[2])) + id = "jsPlumb_" + _instanceIndex + "_" + _idstamp(); + _setAttribute(ele, "id", id); + } + return id; + }, + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, "jsPlumb function failed : " + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, "wrapped function failed : " + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = "_jsPlumb_connector"; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = "_jsPlumb_endpoint"; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = "_jsPlumb_overlay"; + + this.Anchors = {}; + + this.Connectors = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Endpoints = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Overlays = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + +// ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***************************************** + /* + * Function: bind + * Bind to an event on jsPlumb. + * + * Parameters: + * event - the event to bind. Available events on jsPlumb are: + * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback. + * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback. + * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback. + * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback. + * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback. + * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback. + * - *beforeDrop* : notification that a Connection is about to be dropped. Returning false from this method cancels the drop. jsPlumb passes { sourceId, targetId, scope, connection, dropEndpoint } to your callback. For more information, refer to the jsPlumb documentation. + * - *beforeDetach* : notification that a Connection is about to be detached. Returning false from this method cancels the detach. jsPlumb passes the Connection to your callback. For more information, refer to the jsPlumb documentation. + * - *connectionDrag* : notification that an existing Connection is being dragged. jsPlumb passes the Connection to your callback function. + * - *connectionDragEnd* : notification that the drag of an existing Connection has ended. jsPlumb passes the Connection to your callback function. + * + * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event. + */ + + /* + * Function: unbind + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + +// *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *********************************************************** + + +// --------------------------- jsPLumbInstance public API --------------------------------------------------------- + + /* + Function: addClass + + Helper method to abstract out differences in setting css classes on the different renderer types. + */ + this.addClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.addClass(el, clazz); + }; + + /* + Function: removeClass + + Helper method to abstract out differences in setting css classes on the different renderer types. + */ + this.removeClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.removeClass(el, clazz); + }; + + /* + Function: hasClass + + Helper method to abstract out differences in testing for css classes on the different renderer types. + */ + this.hasClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.hasClass(el, clazz); + }; + + /* + Function: addEndpoint + + Adds an to a given element or elements. + + Parameters: + + el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + params - Object containing Endpoint constructor arguments. For more information, see . + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some + shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in + this object are anything that 'params' can contain. See . + + Returns: + The newly created , if el referred to a single element. Otherwise, an array of newly created s. + + See Also: + + */ + this.addEndpoint = function(el, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // YUI wrapper + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + var _el = _getElementObject(inputs[i]), id = _getId(_el); + p.source = _el; + _updateOffset({ elId : id }); + var e = _newEndpoint(p); + if (p.parentAnchor) e.parentAnchor = p.parentAnchor; + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + results.push(e); + _currentInstance.dragManager.endpointAdded(_el); + } + + return results.length == 1 ? results[0] : results; + }; + + /* + Function: addEndpoints + Adds a list of s to a given element or elements. + + Parameters: + target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See 's constructor documentation. + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created s, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(el, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams); + if (_isArray(e)) + Array.prototype.push.apply(results, e); + else results.push(e); + } + return results; + }; + + /* + Function: animate + This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating + the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // onComplete repaints, just to make sure everything looks good at the end of the animation. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /** + * checks for a listener for the given condition, executing it if found, passing in the given value. + * condition listeners would have been attached using "bind" (which is, you could argue, now overloaded, since + * firing click events etc is a bit different to what this does). i thought about adding a "bindCondition" + * or something, but decided against it, for the sake of simplicity. jsPlumb will never fire one of these + * condition events anyway. + */ + this.checkCondition = function(conditionName, value) { + var l = _currentInstance.getListener(conditionName); + var r = true; + if (l && l.length > 0) { + try { + for (var i = 0 ; i < l.length; i++) { + r = r && l[i](value); + } + } + catch (e) { + _log(_currentInstance, "cannot check condition [" + conditionName + "]" + e); + } + } + return r; + }; + + /* + Function: connect + Establishes a between two elements (or s, which are themselves registered to elements). + + Parameters: + params - Object containing constructor arguments for the Connection. See 's constructor documentation. + referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of + Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection. + + Returns: + The newly created . + */ + this.connect = function(params, referenceParams) { + // prepare a final set of parameters to create connection with + var _p = _prepareConnectionParams(params, referenceParams); + // TODO probably a nicer return value if the connection was not made. _prepareConnectionParams + // will return null (and log something) if either endpoint was full. what would be nicer is to + // create a dedicated 'error' object. + if (_p) { + // a connect call will delete its created endpoints on detach, unless otherwise specified. + // this is because the endpoints belong to this connection only, and are no use to + // anyone else, so they hang around like a bad smell. + if (_p.deleteEndpointsOnDetach == null) + _p.deleteEndpointsOnDetach = true; + + // create the connection. it is not yet registered + var jpc = _newConnection(_p); + // now add it the model, fire an event, and redraw + _finaliseConnection(jpc, _p); + return jpc; + } + }; + + /* + Function: deleteEndpoint + Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too) + + Parameters: + object - either an object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElements(endpoint.endpoint.getDisplayElements()); + _currentInstance.anchorManager.deleteEndpoint(endpoint); + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + _currentInstance.dragManager.endpointDeleted(endpoint); + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every , and their associated s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc, doFireEvent, originalEvent) { + // may have been given a connection, or in special cases, an object + var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + argIsConnection = jpc.constructor == connType, + params = argIsConnection ? { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + } : jpc; + + if (doFireEvent) _currentInstance.fire("jsPlumbConnectionDetached", params, originalEvent); + _currentInstance.anchorManager.connectionDetached(params); + }, + /** + fires an event to indicate an existing connection is being dragged. + */ + fireConnectionDraggingEvent = function(jpc) { + _currentInstance.fire("connectionDrag", jpc); + }, + fireConnectionDragStopEvent = function(jpc) { + _currentInstance.fire("connectionDragStop", jpc); + }; + + + /* + Function: detach + Detaches and then removes a . From 1.3.5 this method has been altered to remove support for + specifying Connections by various parameters; you can now pass in a Connection as the first argument and + an optional parameters object as a second argument. If you need the functionality this method provided + before 1.3.5 then you should use the getConnections method to get the list of Connections to detach, and + then iterate through them, calling this for each one. + + Parameters: + connection - the to detach + params - optional parameters to the detach call. valid values here are + fireEvent : defaults to false; indicates you want jsPlumb to fire a connection + detached event. The thinking behind this is that if you made a programmatic + call to detach an event, you probably don't need the callback. + forceDetach : defaults to false. allows you to override any beforeDetach listeners that may be registered. + + Returns: + true if successful, false if not. + */ + this.detach = function() { + + if (arguments.length == 0) return; + var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + firstArgIsConnection = arguments[0].constructor == connType, + params = arguments.length == 2 ? firstArgIsConnection ? (arguments[1] || {}) : arguments[0] : arguments[0], + fireEvent = (params.fireEvent !== false), + forceDetach = params.forceDetach, + connection = firstArgIsConnection ? arguments[0] : params.connection; + + if (connection) { + if (forceDetach || (connection.isDetachAllowed(connection) + && connection.endpoints[0].isDetachAllowed(connection) + && connection.endpoints[1].isDetachAllowed(connection))) { + if (forceDetach || _currentInstance.checkCondition("beforeDetach", connection)) + connection.endpoints[0].detach(connection, false, true, fireEvent); // TODO check this param iscorrect for endpoint's detach method + } + } + else { + var _p = jsPlumb.extend( {}, params); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1]), fireEvent); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source), + targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + if (_currentInstance.checkCondition("beforeDetach", jpc)) { + jpc.endpoints[0].detach(jpc, false, true, fireEvent); + } + } + }); + } + } + }; + + /* + Function: detachAllConnections + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + params - optional parameters. alowed values: + fireEvent : defaults to true, whether or not to fire the detach event. + + Returns: + void + */ + this.detachAllConnections = function(el, params) { + params = params || {}; + el = _getElementObject(el); + var id = _getAttribute(el, "id"), + endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + }; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Parameters: + params - optional params object containing: + fireEvent : whether or not to fire detach events. defaults to true. + + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function(params) { + params = params || {}; + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of your + library's draggable method so that jsPlumb can setup the appropriate callbacks. Your + underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + // helpers for select/selectEndpoints + var _setOperation = function(list, func, args, selector) { + for (var i = 0; i < list.length; i++) { + list[i][func].apply(list[i], args); + } + return selector(list); + }, + _getOperation = function(list, func, args) { + var out = []; + for (var i = 0; i < list.length; i++) { + out.push([ list[i][func].apply(list[i], args), list[i] ]); + } + return out; + }, + setter = function(list, func, selector) { + return function() { + return _setOperation(list, func, arguments, selector); + }; + }, + getter = function(list, func) { + return function() { + return _getOperation(list, func, arguments); + }; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method, + * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the + * default scope). If multiple scopes are passed in, the return value will be a map of { scope -> [ connection... ] }. + * + * Parameters: + * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list + * of connections that are in the given scope. use '*' for all scopes. + * options - if the argument is a JS object, you can specify a finer-grained filter: + * + * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes. Also may have the value '*', indicating any scope. + * - *source* either a string representing an element id, a selector, or an array of ids. Also may have the value '*', indicating any source. Constrains the result to connections having this/these element(s) as source. + * - *target* either a string representing an element id, a selector, or an array of ids. Also may have the value '*', indicating any target. Constrains the result to connections having this/these element(s) as target. + * flat - return results in a flat array (don't return an object whose keys are scopes and whose values are lists per scope). + * + */ + this.getConnections = function(options, flat) { + if (!options) { + options = {}; + } else if (options.constructor == String) { + options = { "scope": options }; + } + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') { + if (input === "*") return input; + r.push(input); + } + else + r = input; + } + return r; + }, + scope = options.scope || _currentInstance.getDefaultScope(), + scopes = prepareList(scope), + sources = prepareList(options.source), + targets = prepareList(options.target), + filter = function(list, value) { + if (list === "*") return true; + return list.length > 0 ? _indexOf(list, value) != -1 : true; + }, + results = (!flat && scopes.length > 1) ? {} : [], + _addOne = function(scope, obj) { + if (!flat && scopes.length > 1) { + var ss = results[scope]; + if (ss == null) { + ss = []; results[scope] = ss; + } + ss.push(obj); + } else results.push(obj); + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + _addOne(i, c); + } + } + } + return results; + }; + + var _makeConnectionSelectHandler = function(list) { + //var + return { + // setters + setHover:setter(list, "setHover", _makeConnectionSelectHandler), + removeAllOverlays:setter(list, "removeAllOverlays", _makeConnectionSelectHandler), + setLabel:setter(list, "setLabel", _makeConnectionSelectHandler), + addOverlay:setter(list, "addOverlay", _makeConnectionSelectHandler), + removeOverlay:setter(list, "removeOverlay", _makeConnectionSelectHandler), + removeOverlays:setter(list, "removeOverlays", _makeConnectionSelectHandler), + showOverlay:setter(list, "showOverlay", _makeConnectionSelectHandler), + hideOverlay:setter(list, "hideOverlay", _makeConnectionSelectHandler), + showOverlays:setter(list, "showOverlays", _makeConnectionSelectHandler), + hideOverlays:setter(list, "hideOverlays", _makeConnectionSelectHandler), + setPaintStyle:setter(list, "setPaintStyle", _makeConnectionSelectHandler), + setHoverPaintStyle:setter(list, "setHoverPaintStyle", _makeConnectionSelectHandler), + setDetachable:setter(list, "setDetachable", _makeConnectionSelectHandler), + setConnector:setter(list, "setConnector", _makeConnectionSelectHandler), + setParameter:setter(list, "setParameter", _makeConnectionSelectHandler), + setParameters:setter(list, "setParameters", _makeConnectionSelectHandler), + + detach:function() { + for (var i = 0; i < list.length; i++) + _currentInstance.detach(list[i]); + }, + + // getters + getLabel:getter(list, "getLabel"), + getOverlay:getter(list, "getOverlay"), + isHover:getter(list, "isHover"), + isDetachable:getter(list, "isDetachable"), + getParameter:getter(list, "getParameter"), + getParameters:getter(list, "getParameters"), + getPaintStyle:getter(list, "getPaintStyle"), + getHoverPaintStyle:getter(list, "getHoverPaintStyle"), + + // util + length:list.length, + each:function(f) { + for (var i = 0; i < list.length; i++) { + f(list[i]); + } + return _makeConnectionSelectHandler(list); + }, + get:function(idx) { + return list[idx]; + } + + }; + }; + + /* + * Function: select + * Selects a set of Connections, using the filter options from the getConnections method, and returns an object + * that allows you to perform an operation on all of the Connections at once. The return value from any of these + * operations is the original list of Connections, allowing operations to be chained (for 'setter' type operations). + * 'getter' type operations return an array of values, where each entry is a [Connection, return value] pair. + * + * Parameters: + * scope - see getConnections + * source - see getConnections + * target - see getConnections + * + * Returns: + * A list of Connections on which operations may be executed. 'Setter' type operations can be chained; 'getter' type operations + * return an array of [Connection, value] pairs, one entry for each Connection in the list returned. The full list of operations + * is as follows (where not specified, the operation's effect or return value is the same as the corresponding method on Connection) : + * - setHover + * - removeAllOverlays + * - setLabel + * - addOverlay + * - removeOverlay + * - removeOverlays + * - showOverlay + * - hideOverlay + * - showOverlays + * - hideOverlays + * - setPaintStyle + * - setHoverPaintStyle + * - setDetachable + * - setConnector + * - setParameter + * - setParameters + * - getLabel + * - getOverlay + * - isHover + * - isDetachable + * - getParameter + * - getParameters + * - getPaintStyle + * - getHoverPaintStyle + * - detach detaches all the connections in the list. not chainable and does not return anything. + * - length : returns the length of the list. + * - get(index) : returns the Connection at 'index' in the list. + * - each(function(connection)...) : allows you to specify your own function to execute; this function is chainable. + */ + this.select = function(params) { + params = params || {}; + params.scope = params.scope || "*"; + var c = _currentInstance.getConnections(params, true); + return _makeConnectionSelectHandler(c); + }; + + /* + * Function: getAllConnections + * Gets all connections, as a map of { scope -> [ connection... ] }. + */ + this.getAllConnections = function() { + return connectionsByScope; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /** + * Function:getEndpoints + * Gets the list of Endpoints for a given selector, or element id. + * @param el + * @return + */ + this.getEndpoints = function(el) { + return endpointsByElement[_getId(el)]; + }; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + this.getOffset = function(id) { + var o = offsets[id]; + return _updateOffset({elId:id}); + }; + + this.getSelector = function(spec) { + return jsPlumb.CurrentLibrary.getSelector(spec); + }; + + this.getSize = function(id) { + var s = sizes[id]; + if (!s) _updateOffset({elId:id}); + return sizes[id]; + }; + + this.appendElement = _appendElement; + + var _hoverSuspended = false; + this.isHoverSuspended = function() { return _hoverSuspended; }; + this.setHoverSuspended = function(s) { _hoverSuspended = s; }; + + this.isCanvasAvailable = function() { return canvasAvailable; }; + this.isSVGAvailable = function() { return svgAvailable; }; + this.isVMLAvailable = vmlAvailable; + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + changeEndpoints - whether not to also hide endpoints on the element. by default this is false. + + Returns: + void + */ + this.hide = function(el, changeEndpoints) { + _setVisible(el, "none", changeEndpoints); + }; + + // exposed for other objects to use to get a unique id. + this.idstamp = _idstamp; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + if (!initialized) { + _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run. + + var bindOne = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && renderMode == jsPlumb.CANVAS) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + var t = c[i].connector[event](e); + if (t) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i].endpoint[event](e)) return; + } + } + } + }); + }; + bindOne("click");bindOne("dblclick");bindOne("mousemove");bindOne("mousedown");bindOne("mouseup");bindOne("contextmenu"); + + initialized = true; + _currentInstance.fire("ready"); + } + }; + + this.log = log; + this.jsPlumbUIComponent = jsPlumbUIComponent; + + /* + * Creates an anchor with the given params. + * + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function() { + if (arguments.length == 0) return null; + var specimen = arguments[0], elementId = arguments[1], jsPlumbInstance = arguments[2], newAnchor = null; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; //TODO hazy here about whether it should be added or is already added somehow. + // is it the name of an anchor type? + else if (typeof specimen == "string") { + newAnchor = jsPlumb.Anchors[arguments[0]]({elementId:elementId, jsPlumbInstance:_currentInstance}); + } + // is it an array? it will be one of: + // an array of [name, params] - this defines a single anchor + // an array of arrays - this defines some dynamic anchors + // an array of numbers - this defines a single anchor. + else if (_isArray(specimen)) { + if (_isArray(specimen[0]) || _isString(specimen[0])) { + if (specimen.length == 2 && _isString(specimen[0]) && _isObject(specimen[1])) { + var pp = jsPlumb.extend({elementId:elementId, jsPlumbInstance:_currentInstance}, specimen[1]); + newAnchor = jsPlumb.Anchors[specimen[0]](pp); + } + else + newAnchor = new DynamicAnchor(specimen, null, elementId); + } + else { + var anchorParams = { + x:specimen[0], y:specimen[1], + orientation : (specimen.length >= 4) ? [ specimen[2], specimen[3] ] : [0,0], + offsets : (specimen.length == 6) ? [ specimen[4], specimen[5] ] : [ 0, 0 ], + elementId:elementId + }; + newAnchor = new Anchor(anchorParams); + newAnchor.clone = function() { return new Anchor(anchorParams); }; + } + } + + if (!newAnchor.id) newAnchor.id = "anchor_" + _idstamp(); + return newAnchor; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types, elementId, jsPlumbInstance) { + var r = []; + for ( var i = 0; i < types.length; i++) { + if (typeof types[i] == "string") + r.push(jsPlumb.Anchors[types[i]]({elementId:elementId, jsPlumbInstance:jsPlumbInstance})); + else if (_isArray(types[i])) + r.push(_currentInstance.makeAnchor(types[i], elementId, jsPlumbInstance)); + } + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /** + * Function: makeTarget + * Makes some DOM element a Connection target, allowing you to drag connections to it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a target. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * scope optional. scope for the drop zone. + * dropOptions optional. same stuff as you would pass to dropOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to true. whether or not to delete + * any Endpoints created by a connection to this target if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + var _targetEndpointDefinitions = {}, + _targetEndpoints = {}, + _targetEndpointsUnique = {}, + _targetMaxConnections = {}, + _setEndpointPaintStylesAndAnchor = function(ep, epIndex) { + ep.paintStyle = ep.paintStyle || + _currentInstance.Defaults.EndpointStyles[epIndex] || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyles[epIndex] || + jsPlumb.Defaults.EndpointStyle; + ep.hoverPaintStyle = ep.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyles[epIndex] || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyles[epIndex] || + jsPlumb.Defaults.EndpointHoverStyle; + + ep.anchor = ep.anchor || + _currentInstance.Defaults.Anchors[epIndex] || + _currentInstance.Defaults.Anchor || + jsPlumb.Defaults.Anchors[epIndex] || + jsPlumb.Defaults.Anchor; + + ep.endpoint = ep.endpoint || + _currentInstance.Defaults.Endpoints[epIndex] || + _currentInstance.Defaults.Endpoint || + jsPlumb.Defaults.Endpoints[epIndex] || + jsPlumb.Defaults.Endpoint; + }; + + this.makeTarget = function(el, params, referenceParams) { + + var p = jsPlumb.extend({_jsPlumb:_currentInstance}, referenceParams); + jsPlumb.extend(p, params); + _setEndpointPaintStylesAndAnchor(p, 1); + var jpcl = jsPlumb.CurrentLibrary, + targetScope = p.scope || _currentInstance.Defaults.Scope, + deleteEndpointsOnDetach = !(p.deleteEndpointsOnDetach === false), + maxConnections = p.maxConnections || -1, + _doOne = function(_el) { + + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el); + _targetEndpointDefinitions[elid] = p; + _targetEndpointsUnique[elid] = p.uniqueEndpoint, + _targetMaxConnections[elid] = maxConnections, + _targetsEnabled[elid] = true, + proxyComponent = new jsPlumbUIComponent(p); + + var dropOptions = jsPlumb.extend({}, p.dropOptions || {}), + _drop = function() { + + var originalEvent = jsPlumb.CurrentLibrary.getDropEvent(arguments), + targetCount = _currentInstance.select({target:elid}).length; + + if (!_targetsEnabled[elid] || _targetMaxConnections[elid] > 0 && targetCount >= _targetMaxConnections[elid]){ + console.log("target element " + elid + " is full."); + return false; + } + + _currentInstance.currentlyDragging = false; + var draggable = _getElementObject(jpcl.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + // restore the original scope if necessary (issue 57) + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id], + source = jpc.endpoints[0], + _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : {}; + + // unlock the source anchor to allow it to refresh its position if necessary + source.anchor.locked = false; + + if (scope) jpcl.setDragScope(draggable, scope); + + // check if drop is allowed here. + //var _continue = jpc.isDropAllowed(jpc.sourceId, _getId(_el), jpc.scope); + var _continue = proxyComponent.isDropAllowed(jpc.sourceId, _getId(_el), jpc.scope, jpc, null); + + // regardless of whether the connection is ok, reconfigure the existing connection to + // point at the current info. we need this to be correct for the detach event that will follow. + // clear the source endpoint from the list to detach. we will detach this connection at this + // point, but we want to keep the source endpoint. the target is a floating endpoint and should + // be removed. TODO need to figure out whether this code can result in endpoints kicking around + // when they shouldnt be. like is this a full detach of a connection? can it be? + if (jpc.endpointsToDeleteOnDetach) { + if (source === jpc.endpointsToDeleteOnDetach[0]) + jpc.endpointsToDeleteOnDetach[0] = null; + else if (source === jpc.endpointsToDeleteOnDetach[1]) + jpc.endpointsToDeleteOnDetach[1] = null; + } + // reinstate any suspended endpoint; this just puts the connection back into + // a state in which it will report sensible values if someone asks it about + // its target. we're going to throw this connection away shortly so it doesnt matter + // if we manipulate it a bit. + if (jpc.suspendedEndpoint) { + jpc.targetId = jpc.suspendedEndpoint.elementId; + jpc.target = jpcl.getElementObject(jpc.suspendedEndpoint.elementId); + jpc.endpoints[1] = jpc.suspendedEndpoint; + } + + if (_continue) { + + // detach this connection from the source. + source.detach(jpc, false, true, false); + + // make a new Endpoint for the target + var newEndpoint = _targetEndpoints[elid] || _currentInstance.addEndpoint(_el, p); + if (p.uniqueEndpoint) _targetEndpoints[elid] = newEndpoint; // may of course just store what it just pulled out. that's ok. + newEndpoint._makeTargetCreator = true; + + // if the anchor has a 'positionFinder' set, then delegate to that function to find + // out where to locate the anchor. + if (newEndpoint.anchor.positionFinder != null) { + var dropPosition = jpcl.getUIPosition(arguments), + elPosition = jpcl.getOffset(_el), + elSize = jpcl.getSize(_el), + ap = newEndpoint.anchor.positionFinder(dropPosition, elPosition, elSize, newEndpoint.anchor.constructorParams); + newEndpoint.anchor.x = ap[0]; + newEndpoint.anchor.y = ap[1]; + // now figure an orientation for it..kind of hard to know what to do actually. probably the best thing i can do is to + // support specifying an orientation in the anchor's spec. if one is not supplied then i will make the orientation + // be what will cause the most natural link to the source: it will be pointing at the source, but it needs to be + // specified in one axis only, and so how to make that choice? i think i will use whichever axis is the one in which + // the target is furthest away from the source. + } + var c = _currentInstance.connect({ + source:source, + target:newEndpoint, + scope:scope, + previousConnection:jpc, + container:jpc.parent, + deleteEndpointsOnDetach:deleteEndpointsOnDetach, + // 'endpointWillMoveAfterConnection' is set by the makeSource function, and it indicates that the + // given endpoint will actually transfer from the element it is currently attached to to some other + // element after a connection has been established. in that case, we do not want to fire the + // connection event, since it will have the wrong data in it; makeSource will do it for us. + // this is controlled by the 'parent' parameter on a makeSource call. + doNotFireConnectionEvent:source.endpointWillMoveAfterConnection + }); + + // delete the original target endpoint. but only want to do this if the endpoint was created + // automatically and has no other connections. + if (jpc.endpoints[1]._makeTargetCreator && jpc.endpoints[1].connections.length < 2) + _currentInstance.deleteEndpoint(jpc.endpoints[1]); + + if (deleteEndpointsOnDetach) + c.endpointsToDeleteOnDetach = [ source, newEndpoint ]; + + c.repaint(); + } + // if not allowed to drop... + else { + // TODO this code is identical (pretty much) to what happens when a connection + // dragged from a normal endpoint is in this situation. refactor. + // is this an existing connection, and will we reattach? + if (jpc.suspendedEndpoint) { + if (source.isReattach) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + _currentInstance.repaint(source.elementId); + } + else + source.detach(jpc, false, true, true, originalEvent); // otherwise, detach the connection and tell everyone about it. + } + + } + }; + + var dropEvent = jpcl.dragEvents['drop']; + dropOptions["scope"] = dropOptions["scope"] || targetScope; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop); + + jpcl.initDroppable(_el, dropOptions, true); + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + + return _currentInstance; + }; + + /* + * Function: unmakeTarget + * Sets the given element to no longer be a connection target. + * Parameters: + * el - either a string id or a selector representing the element. + * Returns: + * The current jsPlumb instance. + */ + this.unmakeTarget = function(el, doNotClearArrays) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + var elid = _getId(el); + + // TODO this is not an exhaustive unmake of a target, since it does not remove the droppable stuff from + // the element. the effect will be to prevent it form behaving as a target, but it's not completely purged. + if (!doNotClearArrays) { + delete _targetEndpointDefinitions[elid]; + delete _targetEndpointsUnique[elid]; + delete _targetMaxConnections[elid]; + delete _targetsEnabled[elid]; + } + + return _currentInstance; + }; + + /** + * helper method to make a list of elements drop targets. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeTargets = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeTarget(els[i], params, referenceParams); + } + }; + + /** + * Function: makeSource + * Makes some DOM element a Connection source, allowing you to drag connections from it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a source. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * parent optional. the element to add Endpoints to when a Connection is established. if you omit this, + * Endpoints will be added to 'el'. + * scope optional. scope for the connections dragged from this element. + * dragOptions optional. same stuff as you would pass to dragOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to false. whether or not to delete + * any Endpoints created by a connection from this source if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * filter - optional function to call when the user presses the mouse button to start a drag. This function is passed the original + * event and the element on which the associated makeSource call was made. If it returns anything other than false, + * the drag begins as usual. But if it returns false (the boolean false, not something falsey), the drag is aborted. + * + * + */ + var _sourceEndpointDefinitions = {}, + _sourceEndpoints = {}, + _sourceEndpointsUnique = {}, + _sourcesEnabled = {}, + _sourceTriggers = {}, + _targetsEnabled = {}; + + this.makeSource = function(el, params, referenceParams) { + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + _setEndpointPaintStylesAndAnchor(p, 0); + var jpcl = jsPlumb.CurrentLibrary, + _doOne = function(_el) { + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el), + parent = p.parent, + idToRegisterAgainst = parent != null ? _currentInstance.getId(jpcl.getElementObject(parent)) : elid; + + _sourceEndpointDefinitions[idToRegisterAgainst] = p; + _sourceEndpointsUnique[idToRegisterAgainst] = p.uniqueEndpoint; + _sourcesEnabled[idToRegisterAgainst] = true; + + var stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"], + dragOptions = jsPlumb.extend({ }, p.dragOptions || {}), + existingDrag = dragOptions.drag, + existingStop = dragOptions.stop, + ep = null, + endpointAddedButNoDragYet = false; + + // set scope if its not set in dragOptions but was passed in in params + dragOptions["scope"] = dragOptions["scope"] || p.scope; + + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + if (existingDrag) existingDrag.apply(this, arguments); + endpointAddedButNoDragYet = false; + }); + + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], function() { + if (existingStop) existingStop.apply(this, arguments); + + //_currentlyDown = false; + _currentInstance.currentlyDragging = false; + + if (ep.connections.length == 0) + _currentInstance.deleteEndpoint(ep); + else { + + jpcl.unbind(ep.canvas, "mousedown"); + + // reset the anchor to the anchor that was initially provided. the one we were using to drag + // the connection was just a placeholder that was located at the place the user pressed the + // mouse button to initiate the drag. + var anchorDef = p.anchor || _currentInstance.Defaults.Anchor, + oldAnchor = ep.anchor, + oldConnection = ep.connections[0]; + + ep.anchor = _currentInstance.makeAnchor(anchorDef, elid, _currentInstance); + + if (p.parent) { + var parent = jpcl.getElementObject(p.parent); + if (parent) { + var currentId = ep.elementId; + + var potentialParent = p.container || _currentInstance.Defaults.Container || jsPlumb.Defaults.Container; + + ep.setElement(parent, potentialParent); + ep.endpointWillMoveAfterConnection = false; + _currentInstance.anchorManager.rehomeEndpoint(currentId, parent); + oldConnection.previousConnection = null; + // remove from connectionsByScope + _removeWithFunction(connectionsByScope[oldConnection.scope], function(c) { + return c.id === oldConnection.id; + }); + _currentInstance.anchorManager.connectionDetached({ + sourceId:oldConnection.sourceId, + targetId:oldConnection.targetId, + connection:oldConnection + }); + _finaliseConnection(oldConnection); + } + } + + ep.repaint(); + _currentInstance.repaint(ep.elementId); + _currentInstance.repaint(oldConnection.targetId); + + } + }); + // when the user presses the mouse, add an Endpoint, if we are enabled. + var mouseDownListener = function(e) { + + // if disabled, return. + if (!_sourcesEnabled[idToRegisterAgainst]) return; + + // if a filter was given, run it, and return if it says no. + if (params.filter) { + // pass the original event to the user: + var r = params.filter(jpcl.getOriginalEvent(e), _el); + if (r === false) return; + } + + // make sure we have the latest offset for this div + var myOffsetInfo = _updateOffset({elId:elid}); + + var x = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width, + y = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height, + parentX = x, + parentY = y; + + // if there is a parent, the endpoint will actually be added to it now, rather than the div + // that was the source. in that case, we have to adjust the anchor position so it refers to + // the parent. + if (p.parent) { + var pEl = jpcl.getElementObject(p.parent), + pId = _getId(pEl); + myOffsetInfo = _updateOffset({elId:pId}); + parentX = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width, + parentY = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height; + } + + // we need to override the anchor in here, and force 'isSource', but we don't want to mess with + // the params passed in, because after a connection is established we're going to reset the endpoint + // to have the anchor we were given. + var tempEndpointParams = {}; + jsPlumb.extend(tempEndpointParams, p); + tempEndpointParams.isSource = true; + tempEndpointParams.anchor = [x,y,0,0]; + tempEndpointParams.parentAnchor = [ parentX, parentY, 0, 0 ]; + tempEndpointParams.dragOptions = dragOptions; + // if a parent was given we need to turn that into a "container" argument. this is, by default, + // the parent of the element we will move to, so parent of p.parent in this case. however, if + // the user has specified a 'container' on the endpoint definition or on + // the defaults, we should use that. + if (p.parent) { + var potentialParent = tempEndpointParams.container || _currentInstance.Defaults.Container || jsPlumb.Defaults.Container; + if (potentialParent) + tempEndpointParams.container = potentialParent; + else + tempEndpointParams.container = jsPlumb.CurrentLibrary.getParent(p.parent); + } + + ep = _currentInstance.addEndpoint(elid, tempEndpointParams); + + endpointAddedButNoDragYet = true; + // we set this to prevent connections from firing attach events before this function has had a chance + // to move the endpoint. + ep.endpointWillMoveAfterConnection = p.parent != null; + ep.endpointWillMoveTo = p.parent ? jpcl.getElementObject(p.parent) : null; + + var _delTempEndpoint = function() { + // this mouseup event is fired only if no dragging occurred, by jquery and yui, but for mootools + // it is fired even if dragging has occurred, in which case we would blow away a perfectly + // legitimate endpoint, were it not for this check. the flag is set after adding an + // endpoint and cleared in a drag listener we set in the dragOptions above. + if(endpointAddedButNoDragYet) { + _currentInstance.deleteEndpoint(ep); + } + }; + + _currentInstance.registerListener(ep.canvas, "mouseup", _delTempEndpoint); + _currentInstance.registerListener(_el, "mouseup", _delTempEndpoint); + + // and then trigger its mousedown event, which will kick off a drag, which will start dragging + // a new connection from this endpoint. + jpcl.trigger(ep.canvas, "mousedown", e); + }; + + // register this on jsPlumb so that it can be cleared by a reset. + _currentInstance.registerListener(_el, "mousedown", mouseDownListener); + _sourceTriggers[elid] = mouseDownListener; + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + + return _currentInstance; + }; + + /** + * Function: unmakeSource + * Sets the given element to no longer be a connection source. + * Parameters: + * el - either a string id or a selector representing the element. + * Returns: + * The current jsPlumb instance. + */ + this.unmakeSource = function(el, doNotClearArrays) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + var id = _getId(el), + mouseDownListener = _sourceTriggers[id]; + + if (mouseDownListener) + _currentInstance.unregisterListener(_el, "mousedown", mouseDownListener); + + if (!doNotClearArrays) { + delete _sourceEndpointDefinitions[id]; + delete _sourceEndpointsUnique[id]; + delete _sourcesEnabled[id]; + delete _sourceTriggers[id]; + } + + return _currentInstance; + }; + + /* + * Function: unmakeEverySource + * Resets all elements in this instance of jsPlumb so that none of them are connection sources. + * Returns: + * The current jsPlumb instance. + */ + this.unmakeEverySource = function() { + for (var i in _sourcesEnabled) + _currentInstance.unmakeSource(i, true); + + _sourceEndpointDefinitions = {}; + _sourceEndpointsUnique = {}; + _sourcesEnabled = {}; + _sourceTriggers = {}; + }; + + /* + * Function: unmakeEveryTarget + * Resets all elements in this instance of jsPlumb so that none of them are connection targets. + * Returns: + * The current jsPlumb instance. + */ + this.unmakeEveryTarget = function() { + for (var i in _targetsEnabled) + _currentInstance.unmakeTarget(i, true); + + _targetEndpointDefinitions = {}; + _targetEndpointsUnique = {}; + _targetMaxConnections = {}; + _targetsEnabled = {}; + + return _currentInstance; + }; + + /* + * Function: makeSources + * Makes all elements in some array or a selector connection sources. + * Parameters: + * els - either an array of ids or a selector + * params - parameters to configure each element as a source with + * referenceParams - extra parameters to configure each element as a source with. + * + * Returns: + * The current jsPlumb instance. + */ + this.makeSources = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeSource(els[i], params, referenceParams); + } + + return _currentInstance; + }; + + // does the work of setting a source enabled or disabled. + var _setEnabled = function(type, el, state, toggle) { + var a = type == "source" ? _sourcesEnabled : _targetsEnabled; + + if (_isString(el)) a[el] = toggle ? !a[el] : state; + else if (el.length) { + el = _convertYUICollection(el); + for (var i = 0; i < el.length; i++) { + var id = _el = jsPlumb.CurrentLibrary.getElementObject(el[i]), id = _getId(_el); + a[id] = toggle ? !a[id] : state; + } + } + return _currentInstance; + }; + + /* + Function: setSourceEnabled + Sets the enabled state of one or more elements that were previously made a connection source with the makeSource + method. + + Parameters: + el - either a string representing some element's id, or an array of ids, or a selector. + state - true to enable the element(s), false to disable it. + + Returns: + The current jsPlumb instance. + */ + this.setSourceEnabled = function(el, state) { + return _setEnabled("source", el, state); + }; + + /* + * Function: toggleSourceEnabled + * Toggles the source enabled state of the given element or elements. + * + * Parameters: + * el - either a string representing some element's id, or an array of ids, or a selector. + * state - true to enable the element(s), false to disable it. + * + * Returns: + * The current enabled state of the source. + */ + this.toggleSourceEnabled = function(el) { + _setEnabled("source", el, null, true); + return _currentInstance.isSourceEnabled(el); + }; + + /* + * Function: isSource + * Returns whether or not the given element is registered as a connection source. + * + * Parameters: + * el - either a string id, or a selector representing a single element. + * + * Returns: + * True if source, false if not. + */ + this.isSource = function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return _sourcesEnabled[_getId(el)] != null; + }; + + /* + * Function: isSourceEnabled + * Returns whether or not the given connection source is enabled. + * + * Parameters: + * el - either a string id, or a selector representing a single element. + * + * Returns: + * True if enabled, false if not. + */ + this.isSourceEnabled = function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return _sourcesEnabled[_getId(el)] === true; + }; + + /* + * Function: setTargetEnabled + * Sets the enabled state of one or more elements that were previously made a connection target with the makeTarget method. + * method. + * + * Parameters: + * el - either a string representing some element's id, or an array of ids, or a selector. + * state - true to enable the element(s), false to disable it. + * + * Returns: + * The current jsPlumb instance. + */ + this.setTargetEnabled = function(el, state) { + return _setEnabled("target", el, state); + }; + + /* + * Function: toggleTargetEnabled + * Toggles the target enabled state of the given element or elements. + * + * Parameters: + * el - either a string representing some element's id, or an array of ids, or a selector. + * state - true to enable the element(s), false to disable it. + * + * Returns: + * The current enabled state of the target. + */ + this.toggleTargetEnabled = function(el) { + return _setEnabled("target", el, null, true); + return _currentInstance.isTargetEnabled(el); + }; + + /* + Function: isTarget + Returns whether or not the given element is registered as a connection target. + + Parameters: + el - either a string id, or a selector representing a single element. + + Returns: + True if source, false if not. + */ + this.isTarget = function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return _targetsEnabled[_getId(el)] != null; + }; + + /* + Function: isTargetEnabled + Returns whether or not the given connection target is enabled. + + Parameters: + el - either a string id, or a selector representing a single element. + + Returns: + True if enabled, false if not. + */ + this.isTargetEnabled = function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return _targetsEnabled[_getId(el)] === true; + }; + + /* + Function: ready + Helper method to bind a function to jsPlumb's ready event. + */ + this.ready = function(fn) { + _currentInstance.bind("ready", fn); + }, + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, null); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"), + ebe = endpointsByElement[elId]; + if (ebe) { + for ( var i = 0; i < ebe.length; i++) + _currentInstance.deleteEndpoint(ebe[i]); + } + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + var _registeredListeners = {}, + _unbindRegisteredListeners = function() { + for (var i in _registeredListeners) { + for (var j = 0; j < _registeredListeners[i].length; j++) { + var info = _registeredListeners[i][j]; + jsPlumb.CurrentLibrary.unbind(info.el, info.event, info.listener); + } + } + _registeredListeners = {}; + }; + + // internal register listener method. gives us a hook to clean things up + // with if the user calls jsPlumb.reset. + this.registerListener = function(el, type, listener) { + jsPlumb.CurrentLibrary.bind(el, type, listener); + _addToList(_registeredListeners, type, {el:el, event:type, listener:listener}); + }; + + this.unregisterListener = function(el, type, listener) { + jsPlumb.CurrentLibrary.unbind(el, type, listener); + _removeWithFunction(_registeredListeners, function(rl) { + return rl.type == type && rl.listener == listener; + }); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + _currentInstance.deleteEveryEndpoint(); + _currentInstance.unbind(); + _targetEndpointDefinitions = {}; + _targetEndpoints = {}; + _targetEndpointsUnique = {}; + _targetMaxConnections = {}; + _sourceEndpointDefinitions = {}; + _sourceEndpoints = {}; + _sourceEndpointsUnique = {}; + _unbindRegisteredListeners(); + _currentInstance.anchorManager.reset(); + _currentInstance.dragManager.reset(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + + Parameters: + value - whether or not to automatically repaint when the window is resized. + + Returns: void + * + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + };*/ + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setId + * Changes the id of some element, adjusting all connections and endpoints + * + * Parameters: + * el - a selector, a DOM element, or a string. + * newId - string. + */ + this.setId = function(el, newId, doNotSetAttribute) { + + var id = el.constructor == String ? el : _currentInstance.getId(el), + sConns = _currentInstance.getConnections({source:id, scope:'*'}, true), + tConns = _currentInstance.getConnections({target:id, scope:'*'}, true); + + newId = "" + newId; + + if (!doNotSetAttribute) { + el = jsPlumb.CurrentLibrary.getElementObject(id); + jsPlumb.CurrentLibrary.setAttribute(el, "id", newId); + } + + el = jsPlumb.CurrentLibrary.getElementObject(newId); + + + endpointsByElement[newId] = endpointsByElement[id] || []; + for (var i = 0; i < endpointsByElement[newId].length; i++) { + endpointsByElement[newId][i].elementId = newId; + endpointsByElement[newId][i].element = el; + endpointsByElement[newId][i].anchor.elementId = newId; + } + delete endpointsByElement[id]; + + _currentInstance.anchorManager.changeId(id, newId); + + var _conns = function(list, epIdx, type) { + for (var i = 0; i < list.length; i++) { + list[i].endpoints[epIdx].elementId = newId; + list[i].endpoints[epIdx].element = el; + list[i][type + "Id"] = newId; + list[i][type] = el; + } + }; + _conns(sConns, 0, "source"); + _conns(tConns, 1, "target"); + }; + + /* + * Function: setIdChanged + * Notify jsPlumb that the element with oldId has had its id changed to newId. + */ + this.setIdChanged = function(oldId, newId) { + _currentInstance.setId(oldId, newId, true); + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction + * Sets the function to fire when the window size has changed and a repaint was fired. + * + * Parameters: + * f - Function to execute. + * + * Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: setSuspendDrawing + * Suspends drawing operations. This can be used when you have a lot of connections to make or endpoints to register; + * it will save you a lot of time. + */ + this.setSuspendDrawing = _setSuspendDrawing; + + /* + * Constant for use with the setRenderMode method + */ + this.CANVAS = "canvas"; + + /* + * Constant for use with the setRenderMode method + */ + this.SVG = "svg"; + + this.VML = "vml"; + + /* + * Function: setRenderMode + * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that + * what you asked for is not supported (and that VML is). If you asked for VML but the browser does + * not support it, jsPlumb uses SVG. + * + * Returns: + * the render mode that jsPlumb set, which of course may be different from that requested. + */ + this.setRenderMode = function(mode) { + if (mode) + mode = mode.toLowerCase(); + else + return; + if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); + // now test we actually have the capability to do this. + if (mode === jsPlumb.SVG) { + if (svgAvailable) renderMode = jsPlumb.SVG + else if (canvasAvailable) renderMode = jsPlumb.CANVAS + else if (vmlAvailable()) renderMode = jsPlumb.VML + } + else if (mode === jsPlumb.CANVAS && canvasAvailable) renderMode = jsPlumb.CANVAS; + else if (vmlAvailable()) renderMode = jsPlumb.VML; + + return renderMode; + }; + + this.getRenderMode = function() { return renderMode; }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * changeEndpoints - whether or not to also change the visible state of the endpoints on the element. this also has a bearing on + * other connections on those endpoints: if their other endpoint is also visible, the connections are made visible. + * + * Returns: + * void + */ + this.show = function(el, changeEndpoints) { + _setVisible(el, "block", changeEndpoints); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + //findIndex : _findIndex, + getId : _getId, + makeAnchor:self.makeAnchor, + makeDynamicAnchor:self.makeDynamicAnchor + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * changeEndpoints - whether or not to also toggle the endpoints on the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload + * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element. + * + * Returns: + * void + */ + this.unload = function() { + // this used to do something, but it turns out that what it did was nothing. + // now it exists only for backwards compatibility. + }; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + var adjustForParentOffsetAndScroll = function(xy, el) { + + var offsetParent = null, result = xy; + if (el.tagName.toLowerCase() === "svg" && el.parentNode) { + offsetParent = el.parentNode; + } + else if (el.offsetParent) { + offsetParent = el.offsetParent; + } + if (offsetParent != null) { + var po = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(offsetParent), + so = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : {left:offsetParent.scrollLeft, top:offsetParent.scrollTop}; + + + // i thought it might be cool to do this: + // lastReturnValue[0] = lastReturnValue[0] - offsetParent.offsetLeft + offsetParent.scrollLeft; + // lastReturnValue[1] = lastReturnValue[1] - offsetParent.offsetTop + offsetParent.scrollTop; + // but i think it ignores margins. my reasoning was that it's quicker to not hand off to some underlying + // library. + + result[0] = xy[0] - po.left + so.left; + result[1] = xy[1] - po.top + so.top; + } + + return result; + + }; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + this.elementId = params.elementId; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + + if (timestamp && timestamp === self.timestamp) + return lastReturnValue; + + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + + // adjust loc if there is an offsetParent + lastReturnValue = adjustForParentOffsetAndScroll(lastReturnValue, element.canvas); + + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function(_endpoint) { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + * + * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference, + // the canvas this refers to. + refCanvas = params.referenceCanvas, + size = _getSize(_getElementObject(refCanvas)), + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + xDir = 0, yDir = 0, + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + orientation = null, + _lastResult = null; + + // set these to 0 each; they are used by certain types of connectors in the loopback case, + // when the connector is trying to clear the element it is on. but for floating anchor it's not + // very important. + this.x = 0; this.y = 0; + + this.isFloating = true; + + this.compute = function(params) { + var xy = params.xy, element = params.element, + result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + + // adjust loc if there is an offsetParent + result = adjustForParentOffsetAndScroll(result, element.canvas); + + _lastResult = result; + return result; + }; + + this.getOrientation = function(_endpoint) { + if (orientation) return orientation; + else { + var o = ref.getOrientation(_endpoint); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchor is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector, elementId) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = [], self = this, + _convert = function(anchor) { + return anchor.constructor == Anchor ? anchor: _currentInstance.makeAnchor(anchor, elementId, _currentInstance); + }; + for (var i = 0; i < anchors.length; i++) + _anchors[i] = _convert(anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + this.locked = false; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null, + _curIndex = _anchors.length > 0 ? 0 : -1, + self = this, + + // helper method to calculate the distance between the centers of the two elements. + _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }, + + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + self.x = _curAnchor.x; + self.y = _curAnchor.y; + + return _curAnchor.compute(params); + }; + + this.getCurrentLocation = function() { + return _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + }; + + this.getOrientation = function(_endpoint) { return _curAnchor != null ? _curAnchor.getOrientation(_endpoint) : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + manages anchors for all elements. + */ + // "continuous" anchors: anchors that pick their location based on how many connections the given element has. + // this requires looking at a lot more elements than normal - anything that has had a Continuous anchor applied has + // to be recalculated. so this manager is used as a reference point. the first time, with a new timestamp, that + // a continuous anchor is asked to compute, it calls this guy. or maybe, even, this guy gets called somewhere else + // and compute only ever returns pre-computed values. either way, this is the central point, and we want it to + // be called as few times as possible. + var continuousAnchors = {}, + continuousAnchorLocations = {}, + continuousAnchorOrientations = {}, + Orientation = { HORIZONTAL : "horizontal", VERTICAL : "vertical", DIAGONAL : "diagonal", IDENTITY:"identity" }, + + // TODO this functions uses a crude method of determining orientation between two elements. + // 'diagonal' should be chosen when the angle of the line between the two centers is around + // one of 45, 135, 225 and 315 degrees. maybe +- 15 degrees. + calculateOrientation = function(sourceId, targetId, sd, td) { + + if (sourceId === targetId) return { + orientation:Orientation.IDENTITY, + a:["top", "top"] + }; + + var theta = Math.atan2((td.centery - sd.centery) , (td.centerx - sd.centerx)), + theta2 = Math.atan2((sd.centery - td.centery) , (sd.centerx - td.centerx)), + h = ((sd.left <= td.left && sd.right >= td.left) || (sd.left <= td.right && sd.right >= td.right) || + (sd.left <= td.left && sd.right >= td.right) || (td.left <= sd.left && td.right >= sd.right)), + v = ((sd.top <= td.top && sd.bottom >= td.top) || (sd.top <= td.bottom && sd.bottom >= td.bottom) || + (sd.top <= td.top && sd.bottom >= td.bottom) || (td.top <= sd.top && td.bottom >= sd.bottom)); + + if (! (h || v)) { + var a = null, rls = false, rrs = false, sortValue = null; + if (td.left > sd.left && td.top > sd.top) + a = ["right", "top"]; + else if (td.left > sd.left && sd.top > td.top) + a = [ "top", "left"]; + else if (td.left < sd.left && td.top < sd.top) + a = [ "top", "right"]; + else if (td.left < sd.left && td.top > sd.top) + a = ["left", "top" ]; + + return { orientation:Orientation.DIAGONAL, a:a, theta:theta, theta2:theta2 }; + } + else if (h) return { + orientation:Orientation.HORIZONTAL, + a:sd.top < td.top ? ["bottom", "top"] : ["top", "bottom"], + theta:theta, theta2:theta2 + } + else return { + orientation:Orientation.VERTICAL, + a:sd.left < td.left ? ["right", "left"] : ["left", "right"], + theta:theta, theta2:theta2 + } + }, + placeAnchorsOnLine = function(desc, elementDimensions, elementPosition, + connections, horizontal, otherMultiplier, reverse) { + var a = [], step = elementDimensions[horizontal ? 0 : 1] / (connections.length + 1); + + for (var i = 0; i < connections.length; i++) { + var val = (i + 1) * step, other = otherMultiplier * elementDimensions[horizontal ? 1 : 0]; + if (reverse) + val = elementDimensions[horizontal ? 0 : 1] - val; + + var dx = (horizontal ? val : other), x = elementPosition[0] + dx, xp = dx / elementDimensions[0], + dy = (horizontal ? other : val), y = elementPosition[1] + dy, yp = dy / elementDimensions[1]; + + a.push([ x, y, xp, yp, connections[i][1], connections[i][2] ]); + } + + return a; + }, + standardEdgeSort = function(a, b) { return a[0] > b[0] ? 1 : -1 }, + currySort = function(reverseAngles) { + return function(a,b) { + var r = true; + if (reverseAngles) { + if (a[0][0] < b[0][0]) + r = true; + else + r = a[0][1] > b[0][1]; + } + else { + if (a[0][0] > b[0][0]) + r= true; + else + r =a[0][1] > b[0][1]; + } + return r === false ? -1 : 1; + }; + }, + leftSort = function(a,b) { + // first get adjusted values + var p1 = a[0][0] < 0 ? -Math.PI - a[0][0] : Math.PI - a[0][0], + p2 = b[0][0] < 0 ? -Math.PI - b[0][0] : Math.PI - b[0][0]; + if (p1 > p2) return 1; + else return a[0][1] > b[0][1] ? 1 : -1; + }, + edgeSortFunctions = { + "top":standardEdgeSort, + "right":currySort(true), + "bottom":currySort(true), + "left":leftSort + }, + _sortHelper = function(_array, _fn) { + return _array.sort(_fn); + }, + placeAnchors = function(elementId, _anchorLists) { + var sS = sizes[elementId], sO = offsets[elementId], + placeSomeAnchors = function(desc, elementDimensions, elementPosition, unsortedConnections, isHorizontal, otherMultiplier, orientation) { + if (unsortedConnections.length > 0) { + var sc = _sortHelper(unsortedConnections, edgeSortFunctions[desc]), // puts them in order based on the target element's pos on screen + reverse = desc === "right" || desc === "top", + anchors = placeAnchorsOnLine(desc, elementDimensions, + elementPosition, sc, + isHorizontal, otherMultiplier, reverse ); + + // takes a computed anchor position and adjusts it for parent offset and scroll, then stores it. + var _setAnchorLocation = function(endpoint, anchorPos) { + var a = adjustForParentOffsetAndScroll([anchorPos[0], anchorPos[1]], endpoint.canvas); + continuousAnchorLocations[endpoint.id] = [ a[0], a[1], anchorPos[2], anchorPos[3] ]; + continuousAnchorOrientations[endpoint.id] = orientation; + }; + + for (var i = 0; i < anchors.length; i++) { + var c = anchors[i][4], weAreSource = c.endpoints[0].elementId === elementId, weAreTarget = c.endpoints[1].elementId === elementId; + if (weAreSource) + _setAnchorLocation(c.endpoints[0], anchors[i]); + else if (weAreTarget) + _setAnchorLocation(c.endpoints[1], anchors[i]); + } + } + }; + + placeSomeAnchors("bottom", sS, [sO.left,sO.top], _anchorLists.bottom, true, 1, [0,1]); + placeSomeAnchors("top", sS, [sO.left,sO.top], _anchorLists.top, true, 0, [0,-1]); + placeSomeAnchors("left", sS, [sO.left,sO.top], _anchorLists.left, false, 0, [-1,0]); + placeSomeAnchors("right", sS, [sO.left,sO.top], _anchorLists.right, false, 1, [1,0]); + }, + AnchorManager = function() { + var _amEndpoints = {}, + connectionsByElementId = {}, + self = this, + anchorLists = {}; + + this.reset = function() { + _amEndpoints = {}; + connectionsByElementId = {}; + anchorLists = {}; + }; + this.newConnection = function(conn) { + var sourceId = conn.sourceId, targetId = conn.targetId, + ep = conn.endpoints, + doRegisterTarget = true, + registerConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + + if ((sourceId == targetId) && otherAnchor.isContinuous){ + // remove the target endpoint's canvas. we dont need it. + jsPlumb.CurrentLibrary.removeElement(ep[1].canvas); + doRegisterTarget = false; + } + _addToList(connectionsByElementId, elId, [c, otherEndpoint, otherAnchor.constructor == DynamicAnchor]); + }; + + registerConnection(0, ep[0], ep[0].anchor, targetId, conn); + if (doRegisterTarget) + registerConnection(1, ep[1], ep[1].anchor, sourceId, conn); + }; + this.connectionDetached = function(connInfo) { + var connection = connInfo.connection || connInfo; + var sourceId = connection.sourceId, + targetId = connection.targetId, + ep = connection.endpoints, + removeConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + if (otherAnchor.constructor == FloatingAnchor) { + // no-op + } + else { + _removeWithFunction(connectionsByElementId[elId], function(_c) { + return _c[0].id == c.id; + }); + } + }; + + removeConnection(1, ep[1], ep[1].anchor, sourceId, connection); + removeConnection(0, ep[0], ep[0].anchor, targetId, connection); + + // remove from anchorLists + var sEl = connection.sourceId, + tEl = connection.targetId, + sE = connection.endpoints[0].id, + tE = connection.endpoints[1].id, + _remove = function(list, eId) { + if (list) { // transient anchors dont get entries in this list. + var f = function(e) { return e[4] == eId; }; + _removeWithFunction(list["top"], f); + _removeWithFunction(list["left"], f); + _removeWithFunction(list["bottom"], f); + _removeWithFunction(list["right"], f); + } + }; + + _remove(anchorLists[sEl], sE); + _remove(anchorLists[tEl], tE); + self.redraw(sEl); + self.redraw(tEl); + }; + this.add = function(endpoint, elementId) { + _addToList(_amEndpoints, elementId, endpoint); + }; + this.changeId = function(oldId, newId) { + connectionsByElementId[newId] = connectionsByElementId[oldId]; + _amEndpoints[newId] = _amEndpoints[oldId]; + delete connectionsByElementId[oldId]; + delete _amEndpoints[oldId]; + }; + this.getConnectionsFor = function(elementId) { + return connectionsByElementId[elementId] || []; + }; + this.getEndpointsFor = function(elementId) { + return _amEndpoints[elementId] || []; + }; + this.deleteEndpoint = function(endpoint) { + _removeWithFunction(_amEndpoints[endpoint.elementId], function(e) { + return e.id == endpoint.id; + }); + }; + this.clearFor = function(elementId) { + delete _amEndpoints[elementId]; + _amEndpoints[elementId] = []; + }; + // updates the given anchor list by either updating an existing anchor's info, or adding it. this function + // also removes the anchor from its previous list, if the edge it is on has changed. + // all connections found along the way (those that are connected to one of the faces this function + // operates on) are added to the connsToPaint list, as are their endpoints. in this way we know to repaint + // them wthout having to calculate anything else about them. + var _updateAnchorList = function(lists, theta, order, conn, aBoolean, otherElId, idx, reverse, edgeId, elId, connsToPaint, endpointsToPaint) { + // first try to find the exact match, but keep track of the first index of a matching element id along the way.s + var exactIdx = -1, + firstMatchingElIdx = -1, + endpoint = conn.endpoints[idx], + endpointId = endpoint.id, + oIdx = [1,0][idx], + values = [ [ theta, order ], conn, aBoolean, otherElId, endpointId ], + listToAddTo = lists[edgeId], + listToRemoveFrom = endpoint._continuousAnchorEdge ? lists[endpoint._continuousAnchorEdge] : null; + + if (listToRemoveFrom) { + var rIdx = _findWithFunction(listToRemoveFrom, function(e) { return e[4] == endpointId }); + if (rIdx != -1) { + listToRemoveFrom.splice(rIdx, 1); + // get all connections from this list + for (var i = 0; i < listToRemoveFrom.length; i++) { + _addWithFunction(connsToPaint, listToRemoveFrom[i][1], function(c) { return c.id == listToRemoveFrom[i][1].id }); + _addWithFunction(endpointsToPaint, listToRemoveFrom[i][1].endpoints[idx], function(e) { return e.id == listToRemoveFrom[i][1].endpoints[idx].id }); + } + } + } + + for (var i = 0; i < listToAddTo.length; i++) { + if (idx == 1 && listToAddTo[i][3] === otherElId && firstMatchingElIdx == -1) + firstMatchingElIdx = i; + _addWithFunction(connsToPaint, listToAddTo[i][1], function(c) { return c.id == listToAddTo[i][1].id }); + _addWithFunction(endpointsToPaint, listToAddTo[i][1].endpoints[idx], function(e) { return e.id == listToAddTo[i][1].endpoints[idx].id }); + } + if (exactIdx != -1) { + listToAddTo[exactIdx] = values; + } + else { + var insertIdx = reverse ? firstMatchingElIdx != -1 ? firstMatchingElIdx : 0 : listToAddTo.length; // of course we will get this from having looked through the array shortly. + listToAddTo.splice(insertIdx, 0, values); + } + + // store this for next time. + endpoint._continuousAnchorEdge = edgeId; + }; + this.redraw = function(elementId, ui, timestamp, offsetToUI) { + // get all the endpoints for this element + var ep = _amEndpoints[elementId] || [], + endpointConnections = connectionsByElementId[elementId] || [], + connectionsToPaint = [], + endpointsToPaint = [], + anchorsToUpdate = []; + + timestamp = timestamp || _timestamp(); + // offsetToUI are values that would have been calculated in the dragManager when registering + // an endpoint for an element that had a parent (somewhere in the hierarchy) that had been + // registered as draggable. + offsetToUI = offsetToUI || {left:0, top:0}; + if (ui) { + ui = { + left:ui.left + offsetToUI.left, + top:ui.top + offsetToUI.top + } + } + + _updateOffset( { elId : elementId, offset : ui, recalc : false, timestamp : timestamp }); + // valid for one paint cycle. + var myOffset = offsets[elementId], + myWH = sizes[elementId], + orientationCache = {}; + + // actually, first we should compute the orientation of this element to all other elements to which + // this element is connected with a continuous anchor (whether both ends of the connection have + // a continuous anchor or just one) + //for (var i = 0; i < continuousAnchorConnections.length; i++) { + for (var i = 0; i < endpointConnections.length; i++) { + var conn = endpointConnections[i][0], + sourceId = conn.sourceId, + targetId = conn.targetId, + sourceContinuous = conn.endpoints[0].anchor.isContinuous, + targetContinuous = conn.endpoints[1].anchor.isContinuous; + + if (sourceContinuous || targetContinuous) { + var oKey = sourceId + "_" + targetId, + oKey2 = targetId + "_" + sourceId, + o = orientationCache[oKey], + oIdx = conn.sourceId == elementId ? 1 : 0; + + if (sourceContinuous && !anchorLists[sourceId]) anchorLists[sourceId] = { top:[], right:[], bottom:[], left:[] }; + if (targetContinuous && !anchorLists[targetId]) anchorLists[targetId] = { top:[], right:[], bottom:[], left:[] }; + + if (elementId != targetId) _updateOffset( { elId : targetId, timestamp : timestamp }); + if (elementId != sourceId) _updateOffset( { elId : sourceId, timestamp : timestamp }); + + var td = _getCachedData(targetId), + sd = _getCachedData(sourceId); + + if (targetId == sourceId && (sourceContinuous || targetContinuous)) { + // here we may want to improve this by somehow determining the face we'd like + // to put the connector on. ideally, when drawing, the face should be calculated + // by determining which face is closest to the point at which the mouse button + // was released. for now, we're putting it on the top face. + _updateAnchorList(anchorLists[sourceId], -Math.PI / 2, 0, conn, false, targetId, 0, false, "top", sourceId, connectionsToPaint, endpointsToPaint) + } + else { + if (!o) { + o = calculateOrientation(sourceId, targetId, sd.o, td.o); + orientationCache[oKey] = o; + // this would be a performance enhancement, but the computed angles need to be clamped to + //the (-PI/2 -> PI/2) range in order for the sorting to work properly. + /* orientationCache[oKey2] = { + orientation:o.orientation, + a:[o.a[1], o.a[0]], + theta:o.theta + Math.PI, + theta2:o.theta2 + Math.PI + };*/ + } + if (sourceContinuous) _updateAnchorList(anchorLists[sourceId], o.theta, 0, conn, false, targetId, 0, false, o.a[0], sourceId, connectionsToPaint, endpointsToPaint); + if (targetContinuous) _updateAnchorList(anchorLists[targetId], o.theta2, -1, conn, true, sourceId, 1, true, o.a[1], targetId, connectionsToPaint, endpointsToPaint); + } + + if (sourceContinuous) _addWithFunction(anchorsToUpdate, sourceId, function(a) { return a === sourceId; }); + if (targetContinuous) _addWithFunction(anchorsToUpdate, targetId, function(a) { return a === targetId; }); + _addWithFunction(connectionsToPaint, conn, function(c) { return c.id == conn.id; }); + if ((sourceContinuous && oIdx == 0) || (targetContinuous && oIdx == 1)) + _addWithFunction(endpointsToPaint, conn.endpoints[oIdx], function(e) { return e.id == conn.endpoints[oIdx].id; }); + } + } + + // now place all the continuous anchors we need to; + for (var i = 0; i < anchorsToUpdate.length; i++) { + placeAnchors(anchorsToUpdate[i], anchorLists[anchorsToUpdate[i]]); + } + + // now that continuous anchors have been placed, paint all the endpoints for this element + // TODO performance: add the endpoint ids to a temp array, and then when iterating in the next + // loop, check that we didn't just paint that endpoint. we can probably shave off a few more milliseconds this way. + for (var i = 0; i < ep.length; i++) { + ep[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + // ... and any other endpoints we came across as a result of the continuous anchors. + for (var i = 0; i < endpointsToPaint.length; i++) { + endpointsToPaint[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + + // paint all the standard and "dynamic connections", which are connections whose other anchor is + // static and therefore does need to be recomputed; we make sure that happens only one time. + + // TODO we could have compiled a list of these in the first pass through connections; might save some time. + for (var i = 0; i < endpointConnections.length; i++) { + var otherEndpoint = endpointConnections[i][1]; + if (otherEndpoint.anchor.constructor == DynamicAnchor) { + otherEndpoint.paint({ elementWithPrecedence:elementId }); + _addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; }); + // all the connections for the other endpoint now need to be repainted + for (var k = 0; k < otherEndpoint.connections.length; k++) { + if (otherEndpoint.connections[k] !== endpointConnections[i][0]) + _addWithFunction(connectionsToPaint, otherEndpoint.connections[k], function(c) { return c.id == otherEndpoint.connections[k].id; }); + } + } else if (otherEndpoint.anchor.constructor == Anchor) { + _addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; }); + } + } + // paint current floating connection for this element, if there is one. + var fc = floatingConnections[elementId]; + if (fc) + fc.paint({timestamp:timestamp, recalc:false, elId:elementId}); + + // paint all the connections + for (var i = 0; i < connectionsToPaint.length; i++) { + connectionsToPaint[i].paint({elId:elementId, timestamp:timestamp, recalc:false}); + } + }; + this.rehomeEndpoint = function(currentId, element) { + var eps = _amEndpoints[currentId] || [], //, + elementId = _currentInstance.getId(element); + for (var i = 0; i < eps.length; i++) { + self.add(eps[i], elementId); + } + eps.splice(0, eps.length); + }; + }; + _currentInstance.anchorManager = new AnchorManager(); + _currentInstance.continuousAnchorFactory = { + get:function(params) { + var existing = continuousAnchors[params.elementId]; + if (!existing) { + existing = { + type:"Continuous", + compute : function(params) { + return continuousAnchorLocations[params.element.id] || [0,0]; + }, + getCurrentLocation : function(endpoint) { + return continuousAnchorLocations[endpoint.id] || [0,0]; + }, + getOrientation : function(endpoint) { + return continuousAnchorOrientations[endpoint.id] || [0,0]; + }, + isDynamic : true, + isContinuous : true + }; + continuousAnchors[params.elementId] = existing; + } + return existing; + } + }; + + /** + Manages dragging for some instance of jsPlumb. + + */ + var DragManager = function() { + + var _draggables = {}, _dlist = [], _delements = {}, _elementsWithEndpoints = {}; + + /** + register some element as draggable. right now the drag init stuff is done elsewhere, and it is + possible that will continue to be the case. + */ + this.register = function(el) { + var jpcl = jsPlumb.CurrentLibrary; + el = jpcl.getElementObject(el); + var id = _currentInstance.getId(el), + domEl = jpcl.getDOMElement(el); + if (!_draggables[id]) { + _draggables[id] = el; + _dlist.push(el); + _delements[id] = {}; + } + + // look for child elements that have endpoints and register them against this draggable. + var _oneLevel = function(p) { + var pEl = jpcl.getElementObject(p), + pOff = jpcl.getOffset(pEl); + + for (var i = 0; i < p.childNodes.length; i++) { + if (p.childNodes[i].nodeType != 3) { + var cEl = jpcl.getElementObject(p.childNodes[i]), + cid = _currentInstance.getId(cEl, null, true); + if (cid && _elementsWithEndpoints[cid] && _elementsWithEndpoints[cid] > 0) { + var cOff = jpcl.getOffset(cEl); + _delements[id][cid] = { + id:cid, + offset:{ + left:cOff.left - pOff.left, + top:cOff.top - pOff.top + } + }; + } + } + } + }; + + _oneLevel(domEl); + }; + + /** + notification that an endpoint was added to the given el. we go up from that el's parent + node, looking for a parent that has been registered as a draggable. if we find one, we add this + el to that parent's list of elements to update on drag (if it is not there already) + */ + this.endpointAdded = function(el) { + var jpcl = jsPlumb.CurrentLibrary, b = document.body, id = _currentInstance.getId(el), c = jpcl.getDOMElement(el), + p = c.parentNode, done = p == b; + + _elementsWithEndpoints[id] = _elementsWithEndpoints[id] ? _elementsWithEndpoints[id] + 1 : 1; + + while (p != b) { + var pid = _currentInstance.getId(p); + if (_draggables[pid]) { + var idx = -1, pEl = jpcl.getElementObject(p), pLoc = jpcl.getOffset(pEl); + + if (_delements[pid][id] == null) { + var cLoc = jsPlumb.CurrentLibrary.getOffset(el); + _delements[pid][id] = { + id:id, + offset:{ + left:cLoc.left - pLoc.left, + top:cLoc.top - pLoc.top + } + }; + } + break; + } + p = p.parentNode; + } + }; + + this.endpointDeleted = function(endpoint) { + if (_elementsWithEndpoints[endpoint.elementId]) { + _elementsWithEndpoints[endpoint.elementId]--; + if (_elementsWithEndpoints[endpoint.elementId] <= 0) { + for (var i in _delements) { + delete _delements[i][endpoint.elementId]; + } + } + } + }; + + this.getElementsForDraggable = function(id) { + return _delements[id]; + }; + + this.reset = function() { + _draggables = {}; + _dlist = []; + _delements = {}; + _elementsWithEndpoints = {}; + }; + + }; + _currentInstance.dragManager = new DragManager(); + + + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. You should not ever create one of these directly. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * container - optional id or selector instructing jsPlumb where to attach all the elements it creates for this connection. you should read the documentation for a full discussion of this. + * detachable - optional, defaults to true. Defines whether or not the connection may be detached using the mouse. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * overlays - Optional array of Overlay definitions to appear on this Connection. + * drawEndpoints - if false, instructs jsPlumb to not draw the endpoints for this Connection. Be careful with this: it only really works when you tell jsPlumb to attach elements to the document body. Read the documentation for a full discussion of this. + * parameters - Optional JS object containing parameters to set on the Connection. These parameters are then available via the getParameter method. + */ + var Connection = function(params) { + var self = this, visible = true; + self.idPrefix = "_jsplumb_c_"; + self.defaultLabelLocation = 0.5; + self.defaultOverlayKeys = ["Overlays", "ConnectionOverlays"]; + this.parent = params.parent; + overlayCapableJsPlumbUIComponent.apply(this, arguments); + // ************** get the source and target and register the connection. ******************* + + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + self[v ? "showOverlays" : "hideOverlays"](); + if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none"; + }; + + /** + Property: source + The source element for this Connection. + */ + this.source = _getElementObject(params.source); + /** + Property:target + The target element for this Connection. + */ + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. but + // source is not overridden if the Endpoint has declared it is not the final target of a connection; + // instead we use the source that the Endpoint declares will be the final source element. + if (params.sourceEndpoint) { + this.source = params.sourceEndpoint.endpointWillMoveTo || params.sourceEndpoint.getElement(); + } + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + + // if a new connection is the result of moving some existing connection, params.previousConnection + // will have that Connection in it. listeners for the jsPlumbConnection event can look for that + // member and take action if they need to. + self.previousConnection = params.previousConnection; + + var _cost = params.cost; + self.getCost = function() { return _cost; }; + self.setCost = function(c) { _cost = c; }; + + var _bidirectional = params.bidirectional === false ? false : true; + self.isBidirectional = function() { return _bidirectional; }; + + /* + * Property: sourceId + * Id of the source element in the connection. + */ + this.sourceId = _getAttribute(this.source, "id"); + /* + * Property: targetId + * Id of the target element in the connection. + */ + this.targetId = _getAttribute(this.target, "id"); + + /** + * implementation of abstract method in jsPlumbUtil.EventGenerator + * @return list of attached elements. in our case, a list of Endpoints. + */ + this.getAttachedElements = function() { + return self.endpoints; + }; + + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams, elementId) { + if (anchorParams) + return _currentInstance.makeAnchor(anchorParams, elementId, _currentInstance); + }, + prepareEndpoint = function(existing, index, params, element, elementId, connectorPaintStyle, connectorHoverPaintStyle) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] + || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint; + + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified. + if (es.fillStyle == null && connectorPaintStyle != null) + es.fillStyle = connectorPaintStyle.strokeStyle; + + // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does: + //* + if (es.outlineColor == null && connectorPaintStyle != null) + es.outlineColor = connectorPaintStyle.outlineColor; + if (es.outlineWidth == null && connectorPaintStyle != null) + es.outlineWidth = connectorPaintStyle.outlineWidth; + //*/ + + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure? + if (connectorHoverPaintStyle != null) { + if (ehs == null) ehs = {}; + if (ehs.fillStyle == null) { + ehs.fillStyle = connectorHoverPaintStyle.strokeStyle; + } + } + var a = params.anchors ? params.anchors[index] : + params.anchor ? params.anchor : + _makeAnchor(_currentInstance.Defaults.Anchors[index], elementId) || + _makeAnchor(jsPlumb.Defaults.Anchors[index], elementId) || + _makeAnchor(_currentInstance.Defaults.Anchor, elementId) || + _makeAnchor(jsPlumb.Defaults.Anchor, elementId), + u = params.uuids ? params.uuids[index] : null, + e = _newEndpoint({ + paintStyle : es, + hoverPaintStyle:ehs, + endpoint : ep, + connections : [ self ], + uuid : u, + anchor : a, + source : element, + scope : params.scope, + container:params.container, + reattach:params.reattach, + detachable:params.detachable + }); + self.endpoints[index] = e; + + + if (params.drawEndpoints === false) e.setVisible(false, true, true); + + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, + 0, + params, + self.source, + self.sourceId, + params.paintStyle, + params.hoverPaintStyle); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + + // if there were no endpoints supplied and the source element is the target element, we will reuse the source + // endpoint that was just created. + var existingTargetEndpoint = ((self.sourceId == self.targetId) && params.targetEndpoint == null) ? eS : params.targetEndpoint, + eT = prepareEndpoint(existingTargetEndpoint, + 1, + params, + self.target, + self.targetId, + params.paintStyle, + params.hoverPaintStyle); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + // if delete endpoints on detach, keep a record of just exactly which endpoints they are. + if (params.deleteEndpointsOnDetach) + self.endpointsToDeleteOnDetach = [eS, eT]; + + var _detachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.detachable === false) _detachable = false; + if(self.endpoints[0].connectionsDetachable === false) _detachable = false; + if(self.endpoints[1].connectionsDetachable === false) _detachable = false; + + // inherit connectin cost if it was set on source endpoint + if (_cost == null) _cost = self.endpoints[0].getConnectionCost(); + // inherit bidirectional flag if set no source endpoint + if (params.bidirectional == null) _bidirectional = self.endpoints[0].areConnectionsBidirectional(); + + /* + Function: isDetachable + Returns whether or not this connection can be detached from its target/source endpoint. by default this + is false; use it in conjunction with the 'reattach' parameter. + */ + this.isDetachable = function() { + return _detachable === true; + }; + + /* + Function: setDetachable + Sets whether or not this connection is detachable. + */ + this.setDetachable = function(detachable) { + _detachable = detachable === true; + }; + + // merge all the parameters objects into the connection. parameters set + // on the connection take precedence; then target endpoint params, then + // finally source endpoint params. + // TODO jsPlumb.extend could be made to take more than two args, and it would + // apply the second through nth args in order. + var _p = jsPlumb.extend({}, this.endpoints[0].getParameters()); + jsPlumb.extend(_p, this.endpoints[1].getParameters()); + jsPlumb.extend(_p, self.getParameters()); + self.setParameters(_p); + + // override setHover to pass it down to the underlying connector + var _sh = self.setHover; + + self.setHover = function(state) { + self.connector.setHover.apply(self.connector, arguments); + _sh.apply(self, arguments); + }; + + var _internalHover = function(state) { + if (_connectionBeingDragged == null) { + self.setHover(state, false); + } + }; + + /* + * Function: setConnector + * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method - the same + * thing that you would set as the 'connector' property on a jsPlumb.connect call. + * + * Parameters: + * connector - Connector definition + */ + this.setConnector = function(connector, doNotRepaint) { + if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent); + var connectorArgs = { + _jsPlumb:self._jsPlumb, + parent:params.parent, + cssClass:params.cssClass, + container:params.container, + tooltip:self.tooltip + }; + if (_isString(connector)) + this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand. + else if (_isArray(connector)) + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs)); + self.canvas = self.connector.canvas; + // binds mouse listeners to the current connector. + _bindListeners(self.connector, self, _internalHover); + if (!doNotRepaint) self.repaint(); + }; + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc) + */ + + self.setConnector(this.endpoints[0].connector || + this.endpoints[1].connector || + params.connector || + _currentInstance.Defaults.Connector || + jsPlumb.Defaults.Connector, true); + + this.setPaintStyle(this.endpoints[0].connectorStyle || + this.endpoints[1].connectorStyle || + params.paintStyle || + _currentInstance.Defaults.PaintStyle || + jsPlumb.Defaults.PaintStyle, true); + + this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || + this.endpoints[1].connectorHoverStyle || + params.hoverPaintStyle || + _currentInstance.Defaults.HoverPaintStyle || + jsPlumb.Defaults.HoverPaintStyle, true); + + this.paintStyleInUse = this.getPaintStyle(); + + + this.moveParent = function(newParent) { + var jpcl = jsPlumb.CurrentLibrary, curParent = jpcl.getParent(self.connector.canvas); + if (self.connector.bgCanvas) { + jpcl.removeElement(self.connector.bgCanvas, curParent); + jpcl.appendElement(self.connector.bgCanvas, newParent); + } + jpcl.removeElement(self.connector.canvas, curParent); + jpcl.appendElement(self.connector.canvas, newParent); + // this only applies for DOMOverlays + for (var i = 0; i < self.overlays.length; i++) { + if (self.overlays[i].isAppendedAtTopLevel) { + jpcl.removeElement(self.overlays[i].canvas, curParent); + jpcl.appendElement(self.overlays[i].canvas, newParent); + if (self.overlays[i].reattachListeners) + self.overlays[i].reattachListeners(self.connector); + } + } + if (self.connector.reattachListeners) // this is for SVG/VML; change an element's parent and you have to reinit its listeners. + self.connector.reattachListeners(); // the Canvas implementation doesn't have to care about this + }; + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Connection. + * + * Parameters: + * event - the event to bind. Available events on a Connection are: + * - *click* : notification that a Connection was clicked. + * - *dblclick* : notification that a Connection was double clicked. + * - *mouseenter* : notification that the mouse is over a Connection. + * - *mouseexit* : notification that the mouse exited a Connection. + * - *contextmenu* : notification that the user right-clicked on the Connection. + * + * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Connection's paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: getPaintStyle + * Gets the Connection's paint style. This is not necessarily the paint style in use at the time; + * this is the paint style for the connection when the mouse it not hovering over it. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Connection will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Connection. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + + /* + * Function: getParameter + * Gets the named parameter; returns null if no parameter with that name is set. Parameter values may have been supplied to a 'connect' or 'addEndpoint' call (connections made with the mouse get a copy of all parameters set on each of their Endpoints), or the parameter value may have been set with setParameter. + * + * Parameters: + * key - Parameter name. + */ + + /* + * Function: setParameter + * Sets the named parameter to the given value. + * + * Parameters: + * key - Parameter name. + * value - Parameter value. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId], + otherOffset = offsets[this.targetId], + otherWH = sizes[this.targetId], + initialTimestamp = _timestamp(), + anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + elementId:this.endpoints[0].elementId, + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1], + timestamp:initialTimestamp + }); + + this.endpoints[0].paint( { anchorLoc : anchorLoc, timestamp:initialTimestamp }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + elementId:this.endpoints[1].elementId, + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0], + timestamp:initialTimestamp + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc, timestamp:initialTimestamp }); + + /* + * Paints the Connection. Not exposed for public usage. + * + * Parameters: + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp, + // if the moving object is not the source we must transpose the two references. + swap = false, + tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId, + tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + + var sourceInfo = _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }), + targetInfo = _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var sE = this.endpoints[sIdx], tE = this.endpoints[tIdx], + sAnchorP = sE.anchor.getCurrentLocation(sE), + tAnchorP = tE.anchor.getCurrentLocation(tE); + + /* paint overlays*/ + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible()) maxSize = Math.max(maxSize, o.computeMaxSize(self.connector)); + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, + this.endpoints[sIdx], this.endpoints[tIdx], + this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, + self.paintStyleInUse.lineWidth, maxSize, + sourceInfo, + targetInfo); + + self.connector.paint(dim, self.paintStyleInUse); + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim); + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function(params) { + params = params || {}; + var recalc = !(params.recalc === false); + this.paint({ elId : this.sourceId, recalc : recalc, timestamp:params.timestamp }); + }; + + }; + +// ENDPOINT HELPER FUNCTIONS + var _makeConnectionDragHandler = function(placeholder) { + var stopped = false; + return { + + drag : function() { + if (stopped) { + stopped = false; + return true; + } + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments), + el = placeholder.element; + if (el) { + jsPlumb.CurrentLibrary.setOffset(el, _ui); + _draw(_getElementObject(el), _ui); + } + }, + stopDrag : function() { + stopped = true; + } + }; + }; + + var _makeFloatingEndpoint = function(paintStyle, referenceAnchor, endpoint, referenceCanvas, sourceElement) { + var floatingAnchor = new FloatingAnchor( { reference : referenceAnchor, referenceCanvas : referenceCanvas }); + + //setting the scope here should not be the way to fix that mootools issue. it should be fixed by not + // adding the floating endpoint as a droppable. that makes more sense anyway! + + return _newEndpoint({ paintStyle : paintStyle, endpoint : endpoint, anchor : floatingAnchor, source : sourceElement, scope:"__floating" }); + }; + + /** + * creates a placeholder div for dragging purposes, adds it to the DOM, and pre-computes its offset. then returns + * both the element id and a selector for the element. + */ + var _makeDraggablePlaceholder = function(placeholder, parent) { + var n = document.createElement("div"); + n.style.position = "absolute"; + var placeholderDragElement = _getElementObject(n); + _appendElement(n, parent); + var id = _getId(placeholderDragElement); + _updateOffset( { elId : id }); + // create and assign an id, and initialize the offset. + placeholder.id = id; + placeholder.element = placeholderDragElement; + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1 + * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't + * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you + * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or + * 'target' Endpoints for Connections. + * + * + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * enabled - optional, defaults to true. Indicates whether or not the Endpoint should be enabled for mouse events (drag/drop). + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * container - optional id or selector instructing jsPlumb where to attach the element it creates for this endpoint. you should read the documentation for a full discussion of this. + * connections - optional list of Connections to configure the Endpoint with. + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * connectionsDetachable - optional, defaults to true. Sets whether connections to/from this Endpoint should be detachable or not. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + * parameters - Optional JS object containing parameters to set on the Endpoint. These parameters are then available via the getParameter method. When a connection is made involving this Endpoint, the parameters from this Endpoint are copied into that Connection. Source Endpoint parameters override target Endpoint parameters if they both have a parameter with the same name. + */ + var Endpoint = function(params) { + var self = this; + self.idPrefix = "_jsplumb_e_"; + self.defaultLabelLocation = [ 0.5, 0.5 ]; + self.defaultOverlayKeys = ["Overlays", "EndpointOverlays"]; + this.parent = params.parent; + overlayCapableJsPlumbUIComponent.apply(this, arguments); + params = params || {}; + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Endpoint. + * + * Parameters: + * event - the event to bind. Available events on an Endpoint are: + * - *click* : notification that a Endpoint was clicked. + * - *dblclick* : notification that a Endpoint was double clicked. + * - *mouseenter* : notification that the mouse is over a Endpoint. + * - *mouseexit* : notification that the mouse exited a Endpoint. + * - *contextmenu* : notification that the user right-clicked on the Endpoint. + * + * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Endpoint's paint style and then repaints the Endpoint. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: getPaintStyle + * Gets the Endpoint's paint style. This is not necessarily the paint style in use at the time; + * this is the paint style for the Endpoint when the mouse it not hovering over it. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Endpoint will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Endpoint. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + + /* + * Function: getParameter + * Gets the named parameter; returns null if no parameter with that name is set. Parameters may have been set on the Endpoint in the 'addEndpoint' call, or they may have been set with the setParameter function. + * + * Parameters: + * key - Parameter name. + */ + + /* + * Function: setParameter + * Sets the named parameter to the given value. + * + * Parameters: + * key - Parameter name. + * value - Parameter value. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + var visible = true, __enabled = !(params.enabled === false); + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + self[v ? "showOverlays" : "hideOverlays"](); + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + + /* + Function: isEnabled + Returns whether or not the Endpoint is enabled for drag/drop connections. + */ + this.isEnabled = function() { return __enabled; }; + + /* + Function: setEnabled + Sets whether or not the Endpoint is enabled for drag/drop connections. + */ + this.setEnabled = function(e) { __enabled = e; }; + + var _element = params.source, _uuid = params.uuid, floatingEndpoint = null, inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + this.element = _element; + + var _connectionCost = params.connectionCost; + this.getConnectionCost = function() { return _connectionCost; }; + this.setConnectionCost = function(c) { + _connectionCost = c; + }; + + var _connectionsBidirectional = params.connectionsBidirectional === false ? false : true; + this.areConnectionsBidirectional = function() { return _connectionsBidirectional; }; + this.setConnectionsBidirectional = function(b) { _connectionsBidirectional = b; }; + + self.anchor = params.anchor ? _currentInstance.makeAnchor(params.anchor, _elementId, _currentInstance) : params.anchors ? _currentInstance.makeAnchor(params.anchors, _elementId, _currentInstance) : _currentInstance.makeAnchor("TopCenter", _elementId, _currentInstance); + + // ANCHOR MANAGER + if (!params._transient) // in place copies, for example, are transient. they will never need to be retrieved during a paint cycle, because they dont move, and then they are deleted. + _currentInstance.anchorManager.add(self, _elementId); + + var _endpoint = null, originalEndpoint = null; + this.setEndpoint = function(ep) { + var endpointArgs = { + _jsPlumb:self._jsPlumb, + parent:params.parent, + container:params.container, + tooltip:params.tooltip, + connectorTooltip:params.connectorTooltip, + endpoint:self + }; + if (_isString(ep)) + _endpoint = new jsPlumb.Endpoints[renderMode][ep](endpointArgs); + else if (_isArray(ep)) { + endpointArgs = jsPlumb.extend(ep[1], endpointArgs); + _endpoint = new jsPlumb.Endpoints[renderMode][ep[0]](endpointArgs); + } + else { + _endpoint = ep.clone(); + } + + // assign a clone function using a copy of endpointArgs. this is used when a drag starts: the endpoint that was dragged is cloned, + // and the clone is left in its place while the original one goes off on a magical journey. + // the copy is to get around a closure problem, in which endpointArgs ends up getting shared by + // the whole world. + var argsForClone = jsPlumb.extend({}, endpointArgs); + _endpoint.clone = function() { + var o = new Object(); + _endpoint.constructor.apply(o, [argsForClone]); + return o; + }; + + self.endpoint = _endpoint; + self.type = self.endpoint.type; + }; + + this.setEndpoint(params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot"); + originalEndpoint = _endpoint; + + // override setHover to pass it down to the underlying endpoint + var _sh = self.setHover; + self.setHover = function() { + self.endpoint.setHover.apply(self.endpoint, arguments); + _sh.apply(self, arguments); + }; + // endpoint delegates to first connection for hover, if there is one. + var internalHover = function(state) { + if (self.connections.length > 0) + self.connections[0].setHover(state, false); + else + self.setHover(state); + }; + + // bind listeners from endpoint to self, with the internal hover function defined above. + _bindListeners(self.endpoint, self, internalHover); + + this.setPaintStyle(params.paintStyle || + params.style || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyle, true); + this.setHoverPaintStyle(params.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyle, true); + this.paintStyleInUse = this.getPaintStyle(); + var originalPaintStyle = this.getPaintStyle(); + this.connectorStyle = params.connectorStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.connectorTooltip = params.connectorTooltip; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + + var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + /* + * Property: canvas + * The Endpoint's Canvas. + */ + this.canvas = this.endpoint.canvas; + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + this.connections = params.connections || []; + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + self.isReattach = params.reattach || false; + self.connectionsDetachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.connectionsDetachable === false || params.detachable === false) + self.connectionsDetachable = false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget, forceDetach, fireEvent, originalEvent) { + var idx = _findWithFunction(self.connections, function(c) { return c.id == connection.id}), + actuallyDetached = false; + fireEvent = (fireEvent !== false); + if (idx >= 0) { + // 1. does the connection have a before detach (note this also checks jsPlumb's bound + // detach handlers; but then Endpoint's check will, too, hmm.) + if (forceDetach || connection._forceDetach || connection.isDetachable() || connection.isDetachAllowed(connection)) { + // get the target endpoint + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + // it would be nice to check with both endpoints that it is ok to detach. but + // for this we'll have to get a bit fancier: right now if you use the same beforeDetach + // interceptor for two endpoints (which is kind of common, because it's part of the + // endpoint definition), then it gets fired twice. so in fact we need to loop through + // each beforeDetach and see if it returns false, at which point we exit. but if it + // returns true, we have to check the next one. however we need to track which ones + // have already been run, and not run them again. + if (forceDetach || connection._forceDetach || (self.isDetachAllowed(connection) /*&& t.isDetachAllowed(connection)*/)) { + + self.connections.splice(idx, 1); + + // this avoids a circular loop + if (!ignoreTarget) { + + t.detach(connection, true, forceDetach); + // check connection to see if we want to delete the endpoints associated with it. + // we only detach those that have just this connection; this scenario is most + // likely if we got to this bit of code because it is set by the methods that + // create their own endpoints, like .connect or .makeTarget. the user is + // not likely to have interacted with those endpoints. + if (connection.endpointsToDeleteOnDetach){ + for (var i = 0; i < connection.endpointsToDeleteOnDetach.length; i++) { + var cde = connection.endpointsToDeleteOnDetach[i]; + if (cde && cde.connections.length == 0) + _currentInstance.deleteEndpoint(cde); + } + } + } + _removeElements(connection.connector.getDisplayElements(), connection.parent); + _removeWithFunction(connectionsByScope[connection.scope], function(c) { + return c.id == connection.id; + }); + actuallyDetached = true; + var doFireEvent = (!ignoreTarget && fireEvent) + fireDetachEvent(connection, doFireEvent, originalEvent); + } + } + } + return actuallyDetached; + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + * + * Parameters: + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachAll = function(fireEvent, originalEvent) { + while (self.connections.length > 0) { + self.detach(self.connections[0], false, true, fireEvent, originalEvent); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachFrom = function(targetEndpoint, fireEvent, originalEvent) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + if (self.detach(c[i], false, true, fireEvent, originalEvent)) + c[i].setHover(false, false); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findWithFunction(self.connections, function(c) { return c.id == connection.id}); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: setElement + * Sets the DOM element this Endpoint is attached to. + */ + this.setElement = function(el, container) { + + // TODO possibly have this object take charge of moving the UI components into the appropriate + // parent. this is used only by makeSource right now, and that function takes care of + // moving the UI bits and pieces. however it would s + var parentId = _getId(el); + // remove the endpoint from the list for the current endpoint's element + _removeWithFunction(endpointsByElement[self.elementId], function(e) { + return e.id == self.id; + }); + _element = _getElementObject(el); + _elementId = _getId(_element); + self.elementId = _elementId; + // need to get the new parent now + var newParentElement = _getParentFromParams({source:parentId, container:container}), + curParent = jpcl.getParent(self.canvas); + jpcl.removeElement(self.canvas, curParent); + jpcl.appendElement(self.canvas, newParentElement); + + // now move connection(s)...i would expect there to be only one but we will iterate. + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].moveParent(newParentElement); + self.connections[i].sourceId = _elementId; + self.connections[i].source = _element; + } + _addToList(endpointsByElement, parentId, self); + //_currentInstance.repaint(parentId); + + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + var loc = self.anchor.getCurrentLocation(self), + o = self.anchor.getOrientation(self), + inPlaceAnchor = { + compute:function() { return [ loc[0], loc[1] ]}, + getCurrentLocation : function() { return [ loc[0], loc[1] ]}, + getOrientation:function() { return o; } + }; + + return _newEndpoint( { + anchor : inPlaceAnchor, + source : _element, + paintStyle : this.getPaintStyle(), + endpoint : _endpoint, + _transient:true, + scope:self.scope + }); + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + var candidate = self.connections[0]; + if (self.isTarget && candidate) return candidate; + else { + return (self.connections.length < _maxConnections) || _maxConnections == -1 ? null : candidate; + } + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + /* + * Function: setStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to + * setStyle and deprecate it if so. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + * + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + // a helper function that tries to find a connection to the given element, and returns it if so. if elementWithPrecedence is null, + // or no connection to it is found, we return the first connection in our list. + var findConnectionToUseForDynamicAnchor = function(elementWithPrecedence) { + var idx = 0; + if (elementWithPrecedence != null) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].sourceId == elementWithPrecedence || self.connections[i].targetId == elementWithPrecedence) { + idx = i; + break; + } + } + } + + return self.connections[idx]; + }; + + /* + * Function: paint + * Paints the Endpoint, recalculating offset and anchor positions if necessary. This does NOT paint + * any of the Endpoint's connections. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + params = params || {}; + var timestamp = params.timestamp, + recalc = !(params.recalc === false); + if (!timestamp || self.timestamp !== timestamp) { + _updateOffset({ elId:_elementId, timestamp:timestamp, recalc:recalc }); + var xy = params.offset || offsets[_elementId]; + if(xy) { + var ap = params.anchorPoint,connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (recalc && self.anchor.isDynamic && self.connections.length > 0) { + var c = findConnectionToUseForDynamicAnchor(params.elementWithPrecedence), + oIdx = c.endpoints[0] == self ? 1 : 0, + oId = oIdx == 0 ? c.sourceId : c.targetId, + oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + ap = self.anchor.compute(anchorParams); + } + + // switched _endpoint to self here; continuous anchors, for instance, look for the + // endpoint's id, but here "_endpoint" is the renderer, not the actual endpoint. + // TODO need to verify this does not break anything. + //var d = _endpoint.compute(ap, self.anchor.getOrientation(_endpoint), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + var d = _endpoint.compute(ap, self.anchor.getOrientation(self), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + _endpoint.paint(d, self.paintStyleInUse, self.anchor); + self.timestamp = timestamp; + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) self.overlayPlacements[i] = o.draw(self.endpoint, self.paintStyleInUse, d); + } + } + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var placeholderInfo = { id:null, element:null }, + jpc = null, + existingJpc = false, + existingJpcParams = null, + _dragHandler = _makeConnectionDragHandler(placeholderInfo); + + var start = function() { + // drag might have started on an endpoint that is not actually a source, but which has + // one or more connections. + jpc = self.connectorSelector(); + var _continue = true; + // if not enabled, return + if (!self.isEnabled()) _continue = false; + // if no connection and we're not a source, return. + if (jpc == null && !params.isSource) _continue = false; + // otherwise if we're full and not allowed to drag, also return false. + if (params.isSource && self.isFull() && !dragAllowedWhenFull) _continue = false; + // if the connection was setup as not detachable or one of its endpoints + // was setup as connectionsDetachable = false, or Defaults.ConnectionsDetachable + // is set to false... + if (jpc != null && !jpc.isDetachable()) _continue = false; + + if (_continue === false) { + // this is for mootools and yui. returning false from this causes jquery to stop drag. + // the events are wrapped in both mootools and yui anyway, but i don't think returning + // false from the start callback would stop a drag. + if (jsPlumb.CurrentLibrary.stopDrag) jsPlumb.CurrentLibrary.stopDrag(); + _dragHandler.stopDrag(); + return false; + } + + // if we're not full but there was a connection, make it null. we'll create a new one. + if (jpc && !self.isFull() && params.isSource) jpc = null; + + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + _makeDraggablePlaceholder(placeholderInfo, self.parent); + + // set the offset of this div to be where 'inPlaceCopy' is, to start with. + // TODO merge this code with the code in both Anchor and FloatingAnchor, because it + // does the same stuff. + var ipcoel = _getElementObject(inPlaceCopy.canvas), + ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel), + po = adjustForParentOffsetAndScroll([ipco.left, ipco.top], inPlaceCopy.canvas); + jsPlumb.CurrentLibrary.setOffset(placeholderInfo.element, {left:po[0], top:po[1]}); + + // when using makeSource and a parent, we first draw the source anchor on the source element, then + // move it to the parent. note that this happens after drawing the placeholder for the + // first time. + if (self.parentAnchor) self.anchor = _currentInstance.makeAnchor(self.parentAnchor, self.elementId, _currentInstance); + + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", placeholderInfo.id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + + // create a floating endpoint. + // here we test to see if a dragProxy was specified in this endpoint's constructor params, and + // if so, we create that endpoint instead of cloning ourselves. + if (params.proxy) { + /* var floatingAnchor = new FloatingAnchor( { reference : self.anchor, referenceCanvas : self.canvas }); + + floatingEndpoint = _newEndpoint({ + paintStyle : params.proxy.paintStyle, + endpoint : params.proxy.endpoint, + anchor : floatingAnchor, + source : placeholderInfo.element, + scope:"__floating" + }); + + //$(self.canvas).hide(); + */ + self.setPaintStyle(params.proxy.paintStyle); + // if we do this, we have to cleanup the old one. like just remove its display parts + //self.setEndpoint(params.proxy.endpoint); + + } + // else { + floatingEndpoint = _makeFloatingEndpoint(self.getPaintStyle(), self.anchor, _endpoint, self.canvas, placeholderInfo.element); + // } + + if (jpc == null) { + self.anchor.locked = true; + self.setHover(false, false); + // TODO the hover call above does not reset any target endpoint's hover + // states. + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : self.endpointWillMoveTo || _getElementObject(_element), // for makeSource with parent option. ensure source element is represented correctly. + target : placeholderInfo.element, + anchors : [ self.anchor, floatingEndpoint.anchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays + }); + + } else { + existingJpc = true; + jpc.connector.setHover(false, false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas), false, true); + // new anchor idx + var anchorIdx = jpc.endpoints[0].id == self.id ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas), + dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // now we want to get this endpoint's DROP scope, and set it for now: we can only be dropped on drop zones + // that have our drop scope (issue 57). + var dropScope = jsPlumb.CurrentLibrary.getDropScope(c); + jsPlumb.CurrentLibrary.setDragScope(c, dropScope); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = placeholderInfo.element; + jpc.sourceId = placeholderInfo.id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = placeholderInfo.element; + jpc.targetId = placeholderInfo.id; + } + + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.suspendedEndpoint.setHover(false); + jpc.endpoints[anchorIdx] = floatingEndpoint; + + // fire an event that informs that a connection is being dragged + fireConnectionDraggingEvent(jpc); + + } + // register it and register connection on it. + floatingConnections[placeholderInfo.id] = jpc; + floatingEndpoint.addConnection(jpc); + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, placeholderInfo.id, floatingEndpoint); + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var jpcl = jsPlumb.CurrentLibrary, + dragOptions = params.dragOptions || {}, + defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions), + startEvent = jpcl.dragEvents["start"], + stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"]; + + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + // extracted drag handler function so can be used by makeSource + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], _dragHandler.drag); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + var originalEvent = jpcl.getDropEvent(arguments); + _currentInstance.currentlyDragging = false; + _removeWithFunction(endpointsByElement[placeholderInfo.id], function(e) { + return e.id == floatingEndpoint.id; + }); + _removeElements( [ placeholderInfo.element[0], floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + _currentInstance.anchorManager.clearFor(placeholderInfo.id); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + self.setPaintStyle(originalPaintStyle); // reset to original; may have been changed by drag proxy. + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (self.isReattach || jpc._forceDetach || !jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc, false, false, true, originalEvent)) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + _currentInstance.repaint(existingJpcParams[1]); + } + jpc._forceDetach = null; + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElements(jpc.connector.getDisplayElements(), self.parent); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint({recalc:false}); + jpc.setHover(false, false); + + fireConnectionDragStopEvent(jpc); + + jpc = null; + inPlaceCopy = null; + delete endpointsByElement[floatingEndpoint.elementId]; + floatingEndpoint.anchor = null; + floatingEndpoint = null; + _currentInstance.currentlyDragging = false; + + + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions, true); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas, forceInit, isTransient, endpoint) { + if ((params.isTarget || forceInit) && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop'], + overEvent = jsPlumb.CurrentLibrary.dragEvents['over'], + outEvent = jsPlumb.CurrentLibrary.dragEvents['out'], + drop = function() { + + var originalEvent = jsPlumb.CurrentLibrary.getDropEvent(arguments), + draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + elId = _getAttribute(draggable, "elId"), + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id]; + + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + + // restore the original scope if necessary (issue 57) + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + var endpointEnabled = endpoint != null ? endpoint.isEnabled() : true; + + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget) && endpointEnabled) { + + var _doContinue = true; + + // the second check here is for the case that the user is dropping it back + // where it came from. + if (jpc.suspendedEndpoint && jpc.suspendedEndpoint.id != self.id) { + if (idx == 0) { + jpc.source = jpc.suspendedEndpoint.element; + jpc.sourceId = jpc.suspendedEndpoint.elementId; + } else { + jpc.target = jpc.suspendedEndpoint.element; + jpc.targetId = jpc.suspendedEndpoint.elementId; + } + + if (!jpc.isDetachAllowed(jpc) || !jpc.endpoints[idx].isDetachAllowed(jpc) || !jpc.suspendedEndpoint.isDetachAllowed(jpc) || !_currentInstance.checkCondition("beforeDetach", jpc)) + _doContinue = false; + } + + // these have to be set before testing for beforeDrop. + if (idx == 0) { + jpc.source = self.element; + jpc.sourceId = self.elementId; + } else { + jpc.target = self.element; + jpc.targetId = self.elementId; + } + + // now check beforeDrop. this will be available only on Endpoints that are setup to + // have a beforeDrop condition (although, secretly, under the hood all Endpoints and + // the Connection have them, because they are on jsPlumbUIComponent. shhh!), because + // it only makes sense to have it on a target endpoint. + _doContinue = _doContinue && self.isDropAllowed(jpc.sourceId, jpc.targetId, jpc.scope, jpc, self); + + if (_doContinue) { + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + + // copy our parameters in to the connection: + var params = self.getParameters(); + for (var aParam in params) + jpc.setParameter(aParam, params[aParam]); + + if (!jpc.suspendedEndpoint) { + //_initDraggableIfNecessary(self.element, params.draggable, {}); + if (params.draggable) + jsPlumb.CurrentLibrary.initDraggable(self.element, dragOptions, true); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + fireDetachEvent({ + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }, true, originalEvent); + } + + // finalise will inform the anchor manager and also add to + // connectionsByScope if necessary. + _finaliseConnection(jpc, null, originalEvent); + } + else { + // otherwise just put it back on the endpoint it was on before the drag. + if (jpc.suspendedEndpoint) { + // self.detachFrom(jpc); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.setHover(false); + jpc._forceDetach = true; + if (idx == 0) { + jpc.source = jpc.suspendedEndpoint.element; + jpc.sourceId = jpc.suspendedEndpoint.elementId; + } else { + jpc.target = jpc.suspendedEndpoint.element; + jpc.targetId = jpc.suspendedEndpoint.elementId;; + } + jpc.suspendedEndpoint.addConnection(jpc); + + jpc.endpoints[0].repaint(); + jpc.repaint(); + _currentInstance.repaint(jpc.source.elementId); + jpc._forceDetach = false; + } + } + + jpc.floatingAnchorIndex = null; + } + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + } + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], function() { + if (self.isTarget) { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + } + } + }); + dropOptions[outEvent] = _wrap(dropOptions[outEvent], function() { + if (self.isTarget) { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + } + } + }); + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions, true, isTransient); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas), true, !(params._transient || self.anchor.isFloating), self); + + return self; + }; + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + j.init(); + return j; + }; + + var _curryAnchor = function(x, y, ox, oy, type, fnInit) { + return function(params) { + params = params || {}; + //var a = jsPlumb.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance); + var a = params.jsPlumbInstance.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance); + a.type = type; + if (fnInit) fnInit(a, params); + return a; + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1, "TopCenter"); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1, "BottomCenter"); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0, "LeftMiddle"); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0, "RightMiddle"); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0, "Center"); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1, "TopRight"); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1, "BottomRight"); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1, "TopLeft"); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1, "BottomLeft"); + + // TODO test that this does not break with the current instance idea + jsPlumb.Defaults.DynamicAnchors = function(params) { + return params.jsPlumbInstance.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"], params.elementId, params.jsPlumbInstance); + }; + jsPlumb.Anchors["AutoDefault"] = function(params) { + var a = params.jsPlumbInstance.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors(params)); + a.type = "AutoDefault"; + return a; + }; + + jsPlumb.Anchors["Assign"] = _curryAnchor(0,0,0,0,"Assign", function(anchor, params) { + // find what to use as the "position finder". the user may have supplied a String which represents + // the id of a position finder in jsPlumb.AnchorPositionFinders, or the user may have supplied the + // position finder as a function. we find out what to use and then set it on the anchor. + var pf = params.position || "Fixed"; + anchor.positionFinder = pf.constructor == String ? params.jsPlumbInstance.AnchorPositionFinders[pf] : pf; + // always set the constructor params; the position finder might need them later (the Grid one does, + // for example) + anchor.constructorParams = params; + }); + + // Continuous anchor is just curried through to the 'get' method of the continuous anchor + // factory. + jsPlumb.Anchors["Continuous"] = function(params) { + return params.jsPlumbInstance.continuousAnchorFactory.get(params); + }; + + // these are the default anchor positions finders, which are used by the makeTarget function. supply + // a position finder argument to that function allows you to specify where the resulting anchor will + // be located + jsPlumb.AnchorPositionFinders = { + "Fixed": function(dp, ep, es, params) { + return [ (dp.left - ep.left) / es[0], (dp.top - ep.top) / es[1] ]; + }, + "Grid":function(dp, ep, es, params) { + var dx = dp.left - ep.left, dy = dp.top - ep.top, + gx = es[0] / (params.grid[0]), gy = es[1] / (params.grid[1]), + mx = Math.floor(dx / gx), my = Math.floor(dy / gy); + return [ ((mx * gx) + (gx / 2)) / es[0], ((my * gy) + (gy / 2)) / es[1] ]; + } + }; +})(); diff --git a/archive/1.3.10/jsPlumb-1.3.10-tests.js b/archive/1.3.10/jsPlumb-1.3.10-tests.js new file mode 100644 index 000000000..c10bebf5f --- /dev/null +++ b/archive/1.3.10/jsPlumb-1.3.10-tests.js @@ -0,0 +1,3924 @@ +// _jsPlumb qunit tests. + +QUnit.config.reorder = false; + +var _getContextNode = function() { + return $("#container"); +}; + +var assertContextExists = function() { + ok(_getContextNode().length == 1, "context node exists"); +}; + +var assertContextSize = function(elementCount) { + //equals(_getContextNode().children().length - _divs.length, elementCount, 'context has ' + elementCount + ' children'); +}; + +var assertContextEmpty = function() { + equals(_getContextNode().children.length, 0, "context empty"); +}; + +var assertEndpointCount = function(elId, count, _jsPlumb) { + equals(_jsPlumb.getTestHarness().endpointCount(elId), count, elId + " has " + count + (count > 1) ? "endpoints" : "endpoint"); + equals(_jsPlumb.anchorManager.getEndpointsFor(elId).length, count, "anchor manager has " + count + (count > 1) ? "endpoints" : "endpoint"); +}; + +var assertConnectionCount = function(endpoint, count) { + equals(endpoint.connections.length, count, "endpoint has " + count + " connections"); +}; + +var assertConnectionByScopeCount = function(scope, count, _jsPlumb) { + equals(_jsPlumb.getTestHarness().connectionCount(scope), count, 'Scope ' + scope + " has " + count + (count > 1) ? "connections" : "connection"); + +}; + +var _divs = []; +var _addDiv = function(id, parent) { + var d1 = document.createElement("div"); + if (parent) parent.append(d1); else _getContextNode().append(d1); + d1 = jsPlumb.CurrentLibrary.getElementObject(d1); + jsPlumb.CurrentLibrary.setAttribute(d1, "id", id); + _divs.push(id); + return d1; +}; + +var defaults = null, + _cleanup = function(_jsPlumb) { + + _jsPlumb.reset(); + _jsPlumb.Defaults = defaults; + + for (var i in _divs) { + $("#" + _divs[i]).remove(); + } + _divs.splice(0, _divs.length - 1); + + $("#container").empty(); +}; + +var testSuite = function(renderMode, _jsPlumb) { + + + module("jsPlumb", { + teardown: function() { + _cleanup(_jsPlumb); + }, + setup:function() { + defaults = jsPlumb.extend({}, _jsPlumb.Defaults); + } + }); + + // setup the container + var container = document.createElement("div"); + container.id = "container"; + document.body.appendChild(container); + + var jpcl = jsPlumb.CurrentLibrary; + + _jsPlumb.setRenderMode(renderMode); + + test(renderMode + " : getElementObject", function() { + var e = document.createElement("div"); + e.id = "FOO"; + document.body.appendChild(e); + var el = jpcl.getElementObject(e); + equals(jpcl.getAttribute(el, "id"), "FOO"); + }); + + test(renderMode + " : getDOMElement", function() { + var e = document.createElement("div"); + e.id = "FOO"; + document.body.appendChild(e); + var el = jpcl.getElementObject(e); + var e2 = jpcl.getDOMElement(el); + equals(e2.id, "FOO"); + }); + + test(renderMode + ': _jsPlumb setup', function() { + ok(_jsPlumb, "loaded"); + }); + + test(renderMode + ': getId', function() { + var d10 = _addDiv('d10'); + equals(_jsPlumb.getTestHarness().getId(d10), "d10"); + }); + + test(renderMode + ': create a simple endpoint', function() { + var d1 = _addDiv("d1"); + var e = _jsPlumb.addEndpoint($("#d1"), {}); + ok(e, 'endpoint exists'); + assertEndpointCount("d1", 1, _jsPlumb); + ok(e.id != null, "endpoint has had an id assigned"); + }); + + test(renderMode + ': create and remove a simple endpoint', function() { + var d1 = _addDiv("d1"); + var ee = _jsPlumb.addEndpoint(d1, {uuid:"78978597593"}); + ok(ee != null, "endpoint exists"); + var e = _jsPlumb.getEndpoint("78978597593"); + ok(e != null, "the endpoint could be retrieved by UUID"); + ok(e.id != null, "the endpoint has had an id assigned to it"); + assertEndpointCount("d1", 1, _jsPlumb); + assertContextSize(1); // one Endpoint canvas. + _jsPlumb.removeEndpoint(d1, ee); + assertEndpointCount("d1", 0, _jsPlumb); + e = _jsPlumb.getEndpoint("78978597593"); + equals(e, null, "the endpoint has been deleted"); + assertContextSize(0); // no Endpoint canvases. + }); + + test(renderMode + ': create two simple endpoints, registered using a selector', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + d1.addClass("window");d2.addClass("window"); + var endpoints = _jsPlumb.addEndpoint($(".window"), {}); + equals(endpoints.length, 2, "endpoint added to both windows"); + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + }); + + test(renderMode + ': create two simple endpoints, registered using an array of element ids', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + d1.addClass("window");d2.addClass("window"); + var endpoints = _jsPlumb.addEndpoint(["d1", "d2"], {}); + equals(endpoints.length, 2, "endpoint added to both windows"); + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + }); + + test(renderMode + ': draggable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1"); + var e = _jsPlumb.addEndpoint(d1, {isSource:true}); + ok(e, 'endpoint exists'); + }); + + test(renderMode + ': droppable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1") + var e = _jsPlumb.addEndpoint(d1, {isTarget:true}); + ok(e, 'endpoint exists'); + }); + + test(renderMode + ': defaultEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = _jsPlumb.addEndpoint(d3, {isSource:true}); + ok(e3.anchor, 'endpoint 3 has an anchor'); + var e4 = _jsPlumb.addEndpoint(d4, {isSource:true}); + assertEndpointCount("d3", 1, _jsPlumb); + assertEndpointCount("d4", 1, _jsPlumb); + assertContextSize(2); // one canvas for each of our two endpoints. + ok(!e3.isFull(), "endpoint 3 is not full."); + _jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // we have one connection + _jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // should have refused the connection; default max is 1. + assertContextSize(3); // now we have one more canvas, for the Connection that was accepted. + }); + + test(renderMode + ': specifiedEndpointMaxConnections', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = _jsPlumb.addEndpoint(d5, {isSource:true, maxConnections:3}); + ok(e5.anchor, 'endpoint 5 has an anchor'); + var e6 = _jsPlumb.addEndpoint(d6, {isSource:true, maxConnections:2}); // this one has max TWO + assertEndpointCount("d5", 1, _jsPlumb); assertEndpointCount("d6", 1, _jsPlumb); + assertContextSize(2); + ok(!e5.isFull(), "endpoint 5 is not full."); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 1); // we have one connection + assertContextSize(3); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // two connections + assertContextSize(4); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // should have refused; max is 2, for d4. + assertContextSize(4); + }); + + test(renderMode + ': noEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = _jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + var e4 = _jsPlumb.addEndpoint(d4, {isSource:true, maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertContextSize(3); + assertConnectionCount(e3, 1); // we have one connection + _jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 2); // we have two. etc (default was one. this proves max is working). + assertContextSize(4); + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = _jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e4}); + assertConnectionCount(e4, 3); + assertContextSize(6); + }); + + test(renderMode + ': anchors equal', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + ok(a1.equals(a2), "anchors are the same"); + }); + + test(renderMode + ': anchors equal with offsets', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb); + ok(a1.equals(a2), "anchors are the same"); + }); + + test(renderMode + ': anchors not equal', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 0, 1], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + ok(!a1.equals(a2), "anchors are different"); + }); + + test(renderMode + ': anchor not equal with offsets', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + ok(!a1.equals(a2), "anchors are different"); + }); + + test(renderMode + ': detach does not fail when no arguments are provided', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + _jsPlumb.connect({source:d3, target:d4}); + _jsPlumb.detach(); + }); + + // test that detach does not fire an event by default + test(renderMode + ': _jsPlumb.detach should fire detach event by default', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach(conn); + equals(eventCount, 1); + }); + + // test that detach does not fire an event by default + test(renderMode + ': _jsPlumb.detach should fire detach event by default, using params object', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({connection:conn}); + equals(eventCount, 1); + }); + + // test that detach fires an event when instructed to do so + test(renderMode + ': _jsPlumb.detach should not fire detach event when instructed to not do so', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach(conn, {fireEvent:false}); + equals(eventCount, 0); + }); + + // issue 81 + test(renderMode + ': _jsPlumb.detach should fire only one detach event (pass Connection as argument)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach(conn); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': _jsPlumb.detach should fire only one detach event (pass Connection as param in argument)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({connection:conn}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': detach should fire only one detach event (pass source and targets as strings as arguments in params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({source:"d5", target:"d6"}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': detach should fire only one detach event (pass source and targets as divs as arguments in params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({source:d5, target:d6, fireEvent:true}); + equals(eventCount, 1); + }); + + //TODO make sure you run this test with a single detach call, to ensure that + // single detach calls result in the connection being removed. detachEveryConnection can + // just blow away the connectionsByScope array and recreate it. + test(renderMode + ': getConnections (simple case, default scope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + _jsPlumb.connect({source:d5, target:d6}); + assertContextSize(3); + var c = _jsPlumb.getConnections(); // will get all connections in the default scope. + equals(c.length, 1, "there is one connection"); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by element id using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + _jsPlumb.connect({source:d5, target:d6}); + _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach({source:'d5', target:'d6'}); + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by id using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + _jsPlumb.connect({source:d5, target:d6}); + _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach({source:"d5", target:"d6"}); + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by element object using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + _jsPlumb.connect({source:d5, target:d6}); + _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach({source:d5, target:d6}); + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by Connection)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + var c56 = _jsPlumb.connect({source:d5, target:d6}); + var c67 = _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach(c56); + assertContextSize(5); // check that the connection canvas was removed. + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + }); + +// beforeDetach functionality + + test(renderMode + ": detach; beforeDetach on connect call returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { return false; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": detach; beforeDetach on connect call returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { return true; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": detach; beforeDetach on connect call throws an exception; we treat it with the contempt it deserves and pretend it said the detach was ok.", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { throw "i am an example of badly coded beforeDetach, but i don't break jsPlumb "; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": detach; beforeDetach on addEndpoint call to source Endpoint returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return false; } }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": detach; beforeDetach on addEndpoint call to source Endpoint returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return true; } }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + + test(renderMode + ": Endpoint.detach; beforeDetach on addEndpoint call to source Endpoint returns false; Endpoint.detach returns false too (the UI needs it to)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return false; } }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + var success = e1.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + ok(!success, "Endpoint reported detach did not execute"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns false but detach is forced", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c, {forceDetach:true}); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was denied but forced anyway"); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was denied but forced anyway"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return true; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach bound to _jsPlumb returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + _jsPlumb.bind("beforeDetach", function(connection) { + ok(connection.sourceId === "d1", "connection is provided and configured with correct source"); + ok(connection.targetId === "d2", "connection is provided and configured with correct target"); + return false; + }); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach bound to _jsPlumb returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + _jsPlumb.bind("beforeDetach", function(connection) { + ok(connection.sourceId === "d1", "connection is provided and configured with correct source"); + ok(connection.targetId === "d2", "connection is provided and configured with correct target"); + return true; + }); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was denied"); + }); + + test(renderMode + ": _jsPlumb.detachAllConnections ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detachAllConnections(d1); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachAll was called"); + }); + + test(renderMode + ": _jsPlumb.detachEveryConnection ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detachEveryConnection(); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachEveryConnection was called"); + }); + + test(renderMode + ": Endpoint.detachAll ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + e1.detachAll(); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachAllConnections was called"); + }); + +// ******** end of beforeDetach tests ************** + +// detachEveryConnection/detachAllConnections fireEvent overrides tests + + test(renderMode + ": _jsPlumb.detachEveryConnection fires events", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0; + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachEveryConnection(); + equals(connCount, 0, "no connections registered"); + }); + + test(renderMode + ": _jsPlumb.detachEveryConnection doesn't fire events when instructed not to", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0; + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachEveryConnection({fireEvent:false}); + equals(connCount, 2, "two connections registered"); + }); + + test(renderMode + ": _jsPlumb.detachAllConnections fires events", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0, + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachAllConnections("d1"); + equals(connCount, 0, "no connections registered"); + }); + + test(renderMode + ": _jsPlumb.detachAllConnections doesn't fire events when instructed not to", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0, + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachAllConnections("d1", {fireEvent:false}); + equals(connCount, 2, "two connections registered"); + }); + + test(renderMode + ': getConnections (scope testScope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + _jsPlumb.connect({source:d5, target:d6, scope:'testScope'}); + var c = _jsPlumb.getConnections("testScope"); // will get all connections in testScope + equals(c.length, 1, "there is one connection"); + equals(c[0].sourceId, 'd5', "the connection's source is d5"); + equals(c[0].targetId, 'd6', "the connection's source is d6"); + c = _jsPlumb.getConnections(); // will get all connections in default scope; should be none. + equals(c.length, 0, "there are no connections in the default scope"); + }); + + test(renderMode + ': _jsPlumb.getAllConnections (filtered by scope)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + _jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + _jsPlumb.connect({source:d9, target:d10}); // default scope + var c = _jsPlumb.getAllConnections(); // will get all connections + equals(c[_jsPlumb.getDefaultScope()].length, 1); + equals(c['testScope'].length, 1); + // now supply a list of scopes + c = _jsPlumb.getConnections(); + equals(c.length, 1); + c = _jsPlumb.getConnections("testScope"); + equals(c.length, 1, "there is one connection in 'testScope'"); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by scope and sourceId)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + _jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + _jsPlumb.connect({source:d9, target:d8, scope:'testScope'}); + _jsPlumb.connect({source:d9, target:d10}); // default scope + var c = _jsPlumb.getConnections({scope:'testScope', source:'d8'}); // will get all connections with sourceId 'd8' + equals(c.length, 1, "there is one connection in 'testScope' from d8"); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by scope, source id and target id)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope'}); + var c = _jsPlumb.getConnections({scope:'testScope', source:'d11', target:'d13'}); + equals(c.length, 1, "there is one connection from d11 to d13"); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = _jsPlumb.getConnections({scope:['testScope','testScope3']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes and source ids)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = _jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes, source ids and target ids)', function() { + _jsPlumb.detachEveryConnection(); + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'), d14=_addDiv("d14"), d15=_addDiv("d15"); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d11, target:d14, scope:'testScope'}); + _jsPlumb.connect({source:d11, target:d15, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + assertContextSize(18); + var c = _jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11'], target:['d14', 'd15']}); + equals(c['testScope'].length, 2, 'there are two connections in testScope'); + equals(c['testScope3'], null, 'there are no connections in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + var anEntry = c['testScope'][0]; + ok(anEntry.endpoints[0] != null, "Source endpoint is set"); + ok(anEntry.endpoints[1] != null, "Target endpoint is set"); + equals(anEntry.source.attr("id"), "d11", "Source is div d11"); + equals(anEntry.target.attr("id"), "d14", "Target is div d14"); + }); + + test(renderMode + ': getEndpoints, one Endpoint added by addEndpoint, get Endpoints by selector', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + _jsPlumb.addEndpoint(d1); + var e = _jsPlumb.getEndpoints(d1); + equals(e.length, 1, "there is one endpoint for element d1"); + }); + + test(renderMode + ': getEndpoints, one Endpoint added by addEndpoint, get Endpoints by id', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + _jsPlumb.addEndpoint(d1); + var e = _jsPlumb.getEndpoints("d1"); + equals(e.length, 1, "there is one endpoint for element d1"); + }); + + test(renderMode + ': connection event listeners', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null/*, returnedParams2 = null*/; + _jsPlumb.bind("jsPlumbConnection", function(params) { + returnedParams = $.extend({}, params); + }); + var c = _jsPlumb.connect({source:d1, target:d2}); + ok(returnedParams != null, "new connection listener event was fired"); + ok(returnedParams.connection != null, 'connection is set'); + equals(returnedParams.sourceId, "d1", 'sourceid is set'); + equals(returnedParams.targetId, "d2", 'targetid is set'); + equals(returnedParams.source.attr("id"), "d1", 'source is set'); + equals(returnedParams.target.attr("id"), "d2" , 'target is set'); + ok(returnedParams.sourceEndpoint != null, "source endpoint is not null"); + ok(returnedParams.targetEndpoint != null, "target endpoint is not null"); + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.detach(c); + ok(returnedParams.connection != null, 'connection is set'); + }); + + test(renderMode + ': detach event listeners (detach by connection)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + ok(returnedParams.connection != null, "removed connection listener event passed in connection"); + }); + + test(renderMode + ': detach event listeners (detach by element ids)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach({source:"d1",target:"d2"}); + ok(returnedParams != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (detach by elements)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach({source:d1,target:d2}); + ok(returnedParams != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detach method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detachFrom method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachFrom(e2); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detachAll method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachAll(); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via _jsPlumb.deleteEndpoint method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + _jsPlumb.deleteEndpoint(e1); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(1); + }); + + test(renderMode + ': detach event listeners (ensure cleared by _jsPlumb.reset)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach({source:d1,target:d2, fireEvent:true}); + ok(returnedParams != null, "removed connection listener event was fired"); + returnedParams = null; + + _jsPlumb.reset(); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach(d1,d2); + ok(returnedParams == null, "connection listener was cleared by _jsPlumb.reset()"); + }); + + test(renderMode + ': connection events that throw errors', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null, returnedParams2 = null; + _jsPlumb.bind("jsPlumbConnection", function(params) { + returnedParams = $.extend({}, params); + throw "oh no!"; + }); + _jsPlumb.connect({source:d1, target:d2}); + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + _jsPlumb.connect({source:d3, target:d4}); + ok(returnedParams != null, "new connection listener event was fired; we threw an error, _jsPlumb survived."); + }); + + test(renderMode + ': unbinding connection event listeners, connection', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var count = 0; + _jsPlumb.bind("jsPlumbConnection", function(params) { + count++; + }); + var c = _jsPlumb.connect({source:d1, target:d2}); + ok(count == 1, "received one event"); + _jsPlumb.unbind("jsPlumbConnection"); + var c2 = _jsPlumb.connect({source:d1, target:d2}); + ok(count == 1, "still received only one event"); + + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + count--; + }); + _jsPlumb.detach(c); + ok(count == 0, "count of events is now zero"); + }); + + test(renderMode + ': unbinding connection event listeners, detach', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var count = 0; + _jsPlumb.bind("jsPlumbConnection", function(params) { + count++; + }); + var c = _jsPlumb.connect({source:d1, target:d2}); + ok(count == 1, "received one event"); + var c2 = _jsPlumb.connect({source:d1, target:d2}); + ok(count == 2, "received two events"); + + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + count--; + }); + _jsPlumb.detach(c); + ok(count == 1, "count of events is now one"); + _jsPlumb.unbind("jsPlumbConnectionDetached"); + _jsPlumb.detach(c2); + ok(count == 1, "count of events is still one"); + }); + + test(renderMode + ': unbinding connection event listeners, all listeners', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var count = 0; + _jsPlumb.bind("jsPlumbConnection", function(params) { + count++; + }); + var c = _jsPlumb.connect({source:d1, target:d2}), + c2 = _jsPlumb.connect({source:d1, target:d2}), + c3 = _jsPlumb.connect({source:d1, target:d2}); + + ok(count == 3, "received three events"); + + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + count--; + }); + _jsPlumb.detach(c); + ok(count == 2, "count of events is now two"); + + _jsPlumb.unbind(); // unbind everything + + _jsPlumb.detach(c2); + _jsPlumb.detach(c3); + _jsPlumb.connect({source:d1, target:d2}) + _jsPlumb.connect({source:d1, target:d2}) + _jsPlumb.connect({source:d1, target:d2}) + _jsPlumb.connect({source:d1, target:d2}) + + ok(count == 2, "count of events is still two"); + }); + + test(renderMode + ": Endpoint.detachFrom", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + e16.detachFrom(e17); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by _jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": Endpoint.detachFromConnection", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + e16.detachFromConnection(conn); + assertContextSize(3); // all canvases should remain; the connection was not removed. + // but endpoint e16 should have no connections now. + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": Endpoint.detachAll", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + var e16 = _jsPlumb.addEndpoint($("#d16"), {isSource:true,maxConnections:-1}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint($("#d17"), {isSource:true}); + var e18 = _jsPlumb.addEndpoint($("#d18"), {isSource:true}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e18}); + assertConnectionCount(e16, 2); + assertConnectionCount(e17, 1); + assertConnectionCount(e18, 1); + assertContextSize(5); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 2, _jsPlumb); + e16.detachAll(); + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": Endpoint.detach", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + e16.detach(conn); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by _jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": Endpoint.isConnectedTo", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.isConnectedTo(e17), true, "e16 and e17 are connected"); + }); + + asyncTest(renderMode + "jsPlumbUtil.setImage on Endpoint, with supplied onload", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { + endpoint:[ "Image", { + src:"../img/endpointTest1.png", + onload:function(imgEp) { + _jsPlumb.repaint("d1"); + ok(imgEp.img.src.indexOf("endpointTest1.png") != -1, "image source is correct"); + ok(imgEp.img.src.indexOf("endpointTest1.png") != -1, "image elementsource is correct"); + + imgEp.canvas.setAttribute("id", "iwilllookforthis"); + + ok(document.getElementById("iwilllookforthis") != null, "image element is present"); + _jsPlumb.removeAllEndpoints("d1"); + ok(document.getElementById("iwilllookforthis") == null, "image element was removed after remove endpoint"); + } + } ] + }; + start(); + _jsPlumb.addEndpoint(d1, e); + }); + + test(renderMode + ": setting endpoint uuid", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + equals(e16.getUuid(), uuid, "endpoint's uuid was set correctly"); + }); + + test(renderMode + ": _jsPlumb.getEndpoint (by uuid)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by uuid, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + _jsPlumb.deleteEndpoint(uuid); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + var ebe = _jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by uuid, connections too)", function() { + // create two endpoints (one with a uuid), add them to two divs and then connect them. + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + // check that the connection was ok. + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + // delete the endpoint that has a uuid. verify that the endpoint cannot be retrieved and that the connection has been removed, but that + // element d17 still has its Endpoint. + _jsPlumb.deleteEndpoint(uuid); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + var ebe = _jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + ebe = _jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 1, "element d17 still has its Endpoint"); + assertContextSize(1); + + // now delete d17's endpoint and check that it has gone. + _jsPlumb.deleteEndpoint(e17); + f = _jsPlumb.getEndpoint(e17); + equals(f, null, "endpoint has been deleted"); + ebe = _jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 0, "element d17 no longer has any Endpoints"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by reference, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + _jsPlumb.deleteEndpoint(e16); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by reference, connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + _jsPlumb.deleteEndpoint(e16); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + assertContextSize(1); + }); + + test(renderMode + ": _jsPlumb.deleteEveryEndpoint", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + assertContextSize(2); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + _jsPlumb.deleteEveryEndpoint(); + + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = _jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEveryEndpoint (connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + _jsPlumb.deleteEveryEndpoint(); + + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = _jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); // no canvases. so all connection canvases have been cleaned up too. + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchor:[0,0.5,0,-1]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:"TopCenter"}); + assertContextSize(2); + equals(e16.anchor.x, 0); + equals(e16.anchor.y, 0.5); + equals(e17.anchor.x, 0.5); + equals(e17.anchor.y, 0); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (tooltip)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {tooltip:"FOO"}), + e17 = _jsPlumb.addEndpoint(d17, {tooltip:"BAZ"}); + assertContextSize(2); + equals(e16.canvas.getAttribute("title"), "FOO"); + equals(e17.canvas.getAttribute("title"), "BAZ"); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case, dynamic anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + assertContextSize(2); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case, two arg method)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchor:[0,0.5,0,-1]}); + var e17 = _jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchor:"TopCenter"}); + assertContextSize(2); + equals(e16.anchor.x, 0); + equals(e16.anchor.y, 0.5); + equals(false, e16.isTarget); + equals(true, e16.isSource); + equals(e17.anchor.x, 0.5); + equals(e17.anchor.y, 0); + equals(true, e17.isTarget); + equals(false, e17.isSource); + }); + + + test(renderMode + ": _jsPlumb.addEndpoints (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false, anchor:[0,0.5,0,-1] }, { isTarget:true, isSource:false, anchor:"TopCenter" }]); + assertContextSize(2); + equals(e16[0].anchor.x, 0); + equals(e16[0].anchor.y, 0.5); + equals(false, e16[0].isTarget); + equals(true, e16[0].isSource); + equals(e16[1].anchor.x, 0.5); + equals(e16[1].anchor.y, 0); + equals(true, e16[1].isTarget); + equals(false, e16[1].isSource); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (with reference params)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var refParams = {anchor:"RightMiddle"}; + var e16 = _jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false}, { isTarget:true, isSource:false }], refParams); + assertContextSize(2); + equals(e16[0].anchor.x, 1); + equals(e16[0].anchor.y, 0.5); + equals(false, e16[0].isTarget); + equals(true, e16[0].isSource); + equals(e16[1].anchor.x, 1); + equals(e16[1].anchor.y, 0.5); + equals(true, e16[1].isTarget); + equals(false, e16[1].isSource); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case, dynamic anchors, two arg method)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchors:["TopCenter", "BottomCenter"]}); + assertContextSize(2); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (default overlays)", function() { + _jsPlumb.Defaults.Overlays = [ + [ "Label", { id:"label" } ] + ]; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16), + e2 = _jsPlumb.addEndpoint(d17); + + ok(e1.getOverlay("label") != null, "endpoint 1 has overlay from defaults"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (default overlays)", function() { + _jsPlumb.Defaults.Overlays = [ + [ "Label", { id:"label" } ] + ]; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16, { + overlays:[ + ["Label", { id:"label2", location:[ 0.5, 1 ] } ] + ] + }), + e2 = _jsPlumb.addEndpoint(d17); + + ok(e1.getOverlay("label") != null, "endpoint 1 has overlay from defaults"); + ok(e1.getOverlay("label2") != null, "endpoint 1 has overlay from addEndpoint call"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (end point set label)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16), + e2 = _jsPlumb.addEndpoint(d17); + + e1.setLabel("FOO"); + equals(e1.getLabel(), "FOO", "endpoint's label is correct"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (end point set label in constructor, as string)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16, {label:"FOO"}), + e2 = _jsPlumb.addEndpoint(d17); + + equals(e1.getLabel(), "FOO", "endpoint's label is correct"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (end point set label in constructor, as function)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16, {label:function() { return "BAZ"; }, labelLocation:0.1}), + e2 = _jsPlumb.addEndpoint(d17); + + equals(e1.getLabel()(), "BAZ", "endpoint's label is correct"); + equals(e1.getLabelOverlay().getLocation(), 0.1, "endpoint's label's location is correct"); + }); + +// ****************** makeTarget (and associated methods) tests ******************************************** + + test(renderMode + ": _jsPlumb.makeTarget (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.makeTarget (specify two divs in an array)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.makeTarget([d16, d17], { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.makeTarget (specify two divs by id in an array)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.makeTarget(["d16", "d17"], { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.makeTarget (specify divs by selector)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + d16.addClass("FOO");d17.addClass("FOO"); + _jsPlumb.makeTarget($(".FOO"), { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e.length, 1, "d17 has one endpoint"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case, two connect calls)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17"}); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 2, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case, two connect calls, uniqueEndpoint set)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true, anchor:"LeftMiddle", uniqueEndpoint:true, maxConnections:-1 }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17"}); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].connections.length, 2, "endpoint on d17 has two connections"); + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (newConnection:true specified)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17", newConnection:true}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0.5, "anchor is BottomCenter"); //here we should be seeing the default anchor + equals(e[0].anchor.y, 1, "anchor is BottomCenter"); //here we should be seeing the default anchor + }); + +// jsPlumb.connect, after makeSource has been called on some element + test(renderMode + ": _jsPlumb.connect after makeSource (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeSource (simple case, two connect calls)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 2, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case, two connect calls, uniqueEndpoint set)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true, anchor:"LeftMiddle", uniqueEndpoint:true, maxConnections:-1 }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].connections.length, 2, "endpoint on d17 has two connections"); + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (newConnection:true specified)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16, newConnection:true}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0.5, "anchor is BottomCenter"); //here we should be seeing the default anchor + equals(e[0].anchor.y, 1, "anchor is BottomCenter"); //here we should be seeing the default anchor + }); + + test(renderMode + ": _jsPlumb.connect after makeSource on child; wth parent set (parent should be recognised)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18", d17); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d18, { isSource:true,anchor:"LeftMiddle", parent:d17 }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + assertEndpointCount("d18", 0, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + // makeSource, then disable it. should not be able to make a connection from it. + test(renderMode + ": _jsPlumb.connect after makeSource and setSourceEnabled(false) (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.setSourceEnabled(d17, false); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + }); + + // makeSource, then disable it. should not be able to make a connection from it. + test(renderMode + ": _jsPlumb.connect after makeSource and setSourceEnabled(false) (selector as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.setSourceEnabled($("div"), false); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + }); + + // makeSource, then toggle its enabled state. should not be able to make a connection from it. + test(renderMode + ": _jsPlumb.connect after makeSource and toggleSourceEnabled() (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.toggleSourceEnabled(d17); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + + _jsPlumb.toggleSourceEnabled(d17); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + // makeSource, then disable it. should not be able to make a connection from it. + test(renderMode + ": _jsPlumb.connect after makeSource and toggleSourceEnabled() (selector as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.toggleSourceEnabled($("div")); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + _jsPlumb.toggleSourceEnabled($("div")); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + test(renderMode + ": jsPlumb.isSource and jsPlumb.isSourceEnabled", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + ok(_jsPlumb.isSource(d17) == true, "d17 is recognised as connection source"); + ok(_jsPlumb.isSourceEnabled(d17) == true, "d17 is recognised as enabled"); + _jsPlumb.setSourceEnabled(d17, false); + ok(_jsPlumb.isSourceEnabled(d17) == false, "d17 is recognised as disabled"); + }); + + // makeSource, then disable it. should not be able to make a connection to it. + test(renderMode + ": _jsPlumb.connect after makeTarget and setTargetEnabled(false) (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.setTargetEnabled(d17, false); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + }); + + // makeTarget, then disable it. should not be able to make a connection to it. + test(renderMode + ": _jsPlumb.connect after makeTarget and setTargetEnabled(false) (selector as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.setTargetEnabled($("div"), false); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + }); + + // makeTarget, then toggle its enabled state. should not be able to make a connection to it. + test(renderMode + ": _jsPlumb.connect after makeTarget and toggleTargetEnabled() (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.toggleTargetEnabled(d17); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + + _jsPlumb.toggleTargetEnabled(d17); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + // makeTarget, then disable it. should not be able to make a connection to it. + test(renderMode + ": _jsPlumb.connect after makeTarget and toggleTargetEnabled() (selector as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.toggleTargetEnabled($("div")); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + _jsPlumb.toggleTargetEnabled($("div")); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + test(renderMode + ": jsPlumb.isTarget and jsPlumb.isTargetEnabled", function() { + var d17 = _addDiv("d17"); + _jsPlumb.makeTarget(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + ok(_jsPlumb.isTarget(d17) == true, "d17 is recognised as connection target"); + ok(_jsPlumb.isTargetEnabled(d17) == true, "d17 is recognised as enabled"); + _jsPlumb.setTargetEnabled(d17, false); + ok(_jsPlumb.isTargetEnabled(d17) == false, "d17 is recognised as disabled"); + }); + + // makeSource, then unmake it. should not be able to make a connection from it. then connect to it, which should succeed, + // because jsPlumb will just add a new endpoint. + test(renderMode + ": jsPlumb.unmakeSource (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + ok(_jsPlumb.isSource(d17) == true, "d17 is currently a source"); + // unmake source + _jsPlumb.unmakeSource(d17); + ok(_jsPlumb.isSource(d17) == false, "d17 is no longer a source"); + + // this should succeed, because d17 is no longer a source and so jsPlumb will just create and add a new endpoint to d17. + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + // maketarget, then unmake it. should not be able to make a connection to it. + test(renderMode + ": jsPlumb.unmakeTarget (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + ok(_jsPlumb.isTarget(d17) == true, "d17 is currently a target"); + // unmake target + _jsPlumb.unmakeTarget(d17); + ok(_jsPlumb.isTarget(d17) == false, "d17 is no longer a target"); + + // this should succeed, because d17 is no longer a target and so jsPlumb will just create and add a new endpoint to d17. + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + + test(renderMode + ": jsPlumb.removeEverySource and removeEveryTarget (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + _jsPlumb.makeSource(d16).makeTarget(d17).makeSource(d18); + ok(_jsPlumb.isSource(d16) == true, "d16 is a source"); + ok(_jsPlumb.isTarget(d17) == true, "d17 is a target"); + ok(_jsPlumb.isSource(d18) == true, "d18 is a source"); + + _jsPlumb.unmakeEverySource(); + _jsPlumb.unmakeEveryTarget(); + + ok(_jsPlumb.isSource(d16) == false, "d16 is no longer a source"); + ok(_jsPlumb.isTarget(d17) == false, "d17 is no longer a target"); + ok(_jsPlumb.isSource(d18) == false, "d18 is no longer a source"); + }); + +// *********************** end of makeTarget (and associated methods) tests ************************ + + test(renderMode + ': _jsPlumb.connect (between two Endpoints)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1, _jsPlumb); // no new endpoint should have been added + assertEndpointCount("d2", 1, _jsPlumb); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. + ok(c.id != null, "connection has had an id assigned"); + }); + + test(renderMode + ': _jsPlumb.connect (Endpoint connectorTooltip parameter)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1, {connectorTooltip:"FOO"}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + equals(c.connector.canvas.getAttribute("title"), "FOO", "connector canvas has label attribute set"); + }); + + test(renderMode + ': _jsPlumb.connect (tooltip parameter)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2, tooltip:"FOO"}); + equals(c.connector.canvas.getAttribute("title"), "FOO", "connector canvas has label attribute set"); + }); + + test(renderMode + ': _jsPlumb.connect (between two Endpoints, and dont supply any parameters to the Endpoints.)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1, _jsPlumb); // no new endpoint should have been added + assertEndpointCount("d2", 1, _jsPlumb); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. + }); + + test(renderMode + " : _jsPlumb.connect, passing 'anchors' array" ,function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, anchors:["LeftMiddle", "RightMiddle"]}); + + equals(c.endpoints[0].anchor.x, 0, "source anchor is at x=0"); + equals(c.endpoints[0].anchor.y, 0.5, "source anchor is at y=0.5"); + equals(c.endpoints[1].anchor.x, 1, "target anchor is at x=1"); + equals(c.endpoints[1].anchor.y, 0.5, "target anchor is at y=0.5"); + }); + + test(renderMode + ': _jsPlumb.connect (by endpoint)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + }); + + test(renderMode + ': _jsPlumb.connect (cost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17, cost:567}); + assertContextSize(3); + equals(c.getCost(), 567, "connection cost is 567"); + }); + + test(renderMode + ': _jsPlumb.connect (default cost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), undefined, "default connection cost is 1"); + }); + + test(renderMode + ': _jsPlumb.connect (set cost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), undefined, "default connection cost is 1"); + c.setCost(8989); + equals(c.getCost(), 8989, "connection cost is 8989"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (connectionCost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionCost:567}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), 567, "connection cost is 567"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (change connectionCost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionCost:567, maxConnections:-1}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1}); + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), 567, "connection cost is 567"); + e16.setConnectionCost(23); + var c2 = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(c2.getCost(), 23, "connection cost is 23 after change on endpoint"); + }); + + test(renderMode + ': _jsPlumb.connect (default bidirectional)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true}), + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.isBidirectional(), true, "default connection is bidirectional"); + }); + + test(renderMode + ': _jsPlumb.connect (bidirectional false)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true}), + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17, bidirectional:false}); + assertContextSize(3); + equals(c.isBidirectional(), false, "connection is not bidirectional"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (connectionsBidirectional)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionsBidirectional:true, maxConnections:-1}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.isBidirectional(), true, "connection is bidrectional"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (change connectionsBidirectional)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionsBidirectional:true, maxConnections:-1}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1}); + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.isBidirectional(), true, "connection is bidirectional"); + e16.setConnectionsBidirectional(false); + var c2 = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(c2.isBidirectional(), false, "connection is not bidirectional"); + }); + + test(renderMode + ": _jsPlumb.connect (two Endpoints - that have been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = _jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1, uuid:srcEndpointUuid}); + var e2 = _jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1, uuid:dstEndpointUuid}); + _jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ] }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertContextSize(3); + }); + + test(renderMode + ": _jsPlumb.connect (two Endpoints - that have not been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ], source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + var e1 = _jsPlumb.getEndpoint(srcEndpointUuid); + ok(e1 != null, "endpoint with src uuid added"); + ok(e1.canvas != null); + var e2 = _jsPlumb.getEndpoint(dstEndpointUuid); + ok(e2 != null, "endpoint with target uuid added"); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.connect (two Endpoints - that have been already added - by endpoint reference)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = _jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1}); + var e2 = _jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1}); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.connect (two elements)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.connect({ source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, straight)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, bezier, no params)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Bezier" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 150, "Bezier connector chose 150 curviness"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, bezier, curviness as int)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", { curviness:200 }] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Canvas Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 200, "Bezier connector chose 200 curviness"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, bezier, curviness as named option)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", {curviness:300}] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 300, "Bezier connector chose 300 curviness"); + }); + + test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Canvas Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as strings)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:["LeftMiddle", "RightMiddle"] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.5, conn.endpoints[0].anchor.y, "source anchor y"); + equals(1, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.5, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + + test(renderMode + ": _jsPlumb.connect (two argument method in which some data is reused across connections)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"), d19 = _addDiv("d19"); + var sharedData = { connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }; + var conn = _jsPlumb.connect({ source:d16, target:d17}, sharedData); + var conn2 = _jsPlumb.connect({ source:d18, target:d19}, sharedData); + assertContextSize(6); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 2, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(conn2.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + equals(0.3, conn2.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn2.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn2.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn2.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoint test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoints test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Dot, "Dot endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Blank Endpoint specified via 'endpoint' param)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Blank" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Blank Endpoint specified via 'endpoints' param)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Blank", "Blank" ] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Dot, "Dot endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, connector test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {}); + var e17 = _jsPlumb.addEndpoint(d17, {}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {}); + var e17 = _jsPlumb.addEndpoint(d17, {}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, anchors as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = "TopCenter", a17 = "BottomCenter"; + var e16 = _jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, 0.5, "endpoint 16 is at top center");equals(e16.anchor.y, 0, "endpoint 16 is at top center"); + equals(e17.anchor.x, 0.5, "endpoint 17 is at bottom center");equals(e17.anchor.y, 1, "endpoint 17 is at bottom center"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = [0,0.5,0,-1], a17 = [1,0.0,-1,-1]; + var e16 = _jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, a16[0]);equals(e16.anchor.y, a16[1]); + equals(e17.anchor.x, a17[0]);equals(e17.anchor.y, a17[1]); + equals(e16.anchor.getOrientation()[0], a16[2]); equals(e16.anchor.getOrientation()[1], a16[3]); + equals(e17.anchor.getOrientation()[0], a17[2]); equals(e17.anchor.getOrientation()[1], a17[3]); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchor')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchor:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:["TopCenter", "BottomCenter"]}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchors')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, default endpoint, supplied dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var anchors = [ [0.25, 0, 0, -1], [1, 0.25, 1, 0], [0.75, 1, 0, 1], [0, 0.75, -1, 0] ]; + _jsPlumb.connect({source:d1, target:d2, dynamicAnchors:anchors}); // auto connect with default endpoint and provided anchors + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.detach({source:d1, target:d2}); + // this changed in 1.3.5, because auto generated endpoints are now removed by detach. so i added the test below this one + // to check that the deleteEndpointsOnDetach flag is honoured. + assertEndpointCount("d1", 0, _jsPlumb);assertEndpointCount("d2", 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, default endpoint, supplied dynamic anchors, delete on detach false)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var anchors = [ [0.25, 0, 0, -1], [1, 0.25, 1, 0], [0.75, 1, 0, 1], [0, 0.75, -1, 0] ]; + _jsPlumb.connect({ + source:d1, + target:d2, + dynamicAnchors:anchors, + deleteEndpointsOnDetach:false + }); // auto connect with default endpoint and provided anchors + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.detach({source:d1, target:d2}); + // this changed in 1.3.5, because auto generated endpoints are now removed by detach. so i added this test + // to check that the deleteEndpointsOnDetach flag is honoured. + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, supplied endpoint and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = _jsPlumb.addEndpoint(d1, endpoint); + var e2 = _jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(3); + _jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, supplied endpoints using 'source' and 'target' (this test is identical to the one above apart from the param names), and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = _jsPlumb.addEndpoint(d1, endpoint); + var e2 = _jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + _jsPlumb.connect({source:e1, target:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(3); + _jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + " detachable parameter defaults to true on _jsPlumb.connect", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), true, "connections detachable by default"); + }); + + test(renderMode + " detachable parameter set to false on _jsPlumb.connect", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, detachable:false}); + equals(c.isDetachable(), false, "connection detachable"); + }); + + test(renderMode + " setDetachable on initially detachable connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), true, "connection initially detachable"); + c.setDetachable(false); + equals(c.isDetachable(), false, "connection not detachable"); + }); + + test(renderMode + " setDetachable on initially not detachable connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, detachable:false }); + equals(c.isDetachable(), false, "connection not initially detachable"); + c.setDetachable(true); + equals(c.isDetachable(), true, "connection now detachable"); + }); + + test(renderMode + " _jsPlumb.Defaults.ConnectionsDetachable", function() { + _jsPlumb.Defaults.ConnectionsDetachable = false; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), false, "connections not detachable by default (overrode the defaults)"); + _jsPlumb.Defaults.ConnectionsDetachable = true; + }); + + + test(renderMode + ": _jsPlumb.connect (testing for connection event callback)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var connectCallback = null, detachCallback = null; + _jsPlumb.bind("jsPlumbConnection", function(params) { + connectCallback = $.extend({}, params); + }); + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + detachCallback = $.extend({}, params); + }); + _jsPlumb.connect({source:d1, target:d2}); // auto connect with default endpoint and anchor set + ok(connectCallback != null, "connect callback was made"); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.detach({source:d1, target:d2}); + assertContextSize(2); + ok(detachCallback != null, "detach callback was made"); + }); + + test(renderMode + ": _jsPlumb.connect (setting cssClass on Connector)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2,cssClass:"CSS"}); + var has = function(clazz) { + var cn = c.connector.canvas.className, + cns = cn.constructor == String ? cn : cn.baseVal; + + return cns.indexOf(clazz) != -1; + }; + ok(has("CSS"), "custom cssClass set correctly"); + ok(has(_jsPlumb.connectorClass), "basic connector class set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (overlays, long-hand version)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = {width:40,length:40,location:0.7, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"}}; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + }); + + test(renderMode + ": _jsPlumb.connect (overlays, long-hand version, IDs specified)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + equals("aLabel", connection1.overlays[0].id); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + equals("anArrow", connection1.overlays[1].id); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays)", function() { + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays + overlays specified in connect call)", function() { + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, overlays:[ + ["Label", {id:"label"}] + ]}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("label") != null, "Label overlay created from connect call"); + }); + + test(renderMode + ": _jsPlumb.connect (default connection overlays)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + }); + + test(renderMode + ": _jsPlumb.connect (default connection overlays + overlays specified in connect call)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, overlays:[ + ["Label", {id:"label"}] + ]}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("label") != null, "Label overlay created from connect call"); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays + default connection overlays)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow2" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("arrow2") != null, "Arrow overlay created from connection defaults"); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays + default connection overlays)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow2" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, overlays:[ + ["Label", {id:"label"}] + ]}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("arrow2") != null, "Arrow overlay created from connection defaults"); + ok(c.getOverlay("label") != null, "Label overlay created from connect call"); + }); + + test(renderMode + ": _jsPlumb.connect (label overlay set using 'label')", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO" + }); + + equals(c.getLabel(), "FOO", "label is set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction, with string)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2}); + + c.setLabel("FOO"); + equals(c.getLabel(), "FOO", "label is set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction, with function)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2}); + + c.setLabel(function() { return "BAR"; }); + equals(c.getLabel()(), "BAR", "label is set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction, with params object)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2}); + + c.setLabel({ + label:"BAZ", + cssClass:"CLASSY", + location:0.9 + }); + var lo = c.getLabelOverlay(); + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "BAZ", "label overlay has correct value"); + equals(lo.getLocation(), 0.9, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction with existing label set, with params object)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO", + labelLocation:0.2 + }); + + var lo = c.getLabelOverlay(); + equals(lo.getLocation(), 0.2, "label overlay has correct location"); + + c.setLabel({ + label:"BAZ", + cssClass:"CLASSY", + location:0.9 + }); + + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "BAZ", "label overlay has correct value"); + equals(lo.getLocation(), 0.9, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (getLabelOverlay, label on connect call)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO" + }), + lo = c.getLabelOverlay(); + + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "FOO", "label overlay has correct value"); + equals(lo.getLocation(), 0.5, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (getLabelOverlay, label on connect call, location set)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO", + labelLocation:0.2 + }), + lo = c.getLabelOverlay(); + + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "FOO", "label overlay has correct value"); + equals(lo.getLocation(), 0.2, "label overlay has correct location"); + }); + + + test(renderMode + ": _jsPlumb.connect (remove single overlay by id)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + connection1.removeOverlay("aLabel"); + equals(1, connection1.overlays.length); + equals("anArrow", connection1.overlays[0].id); + }); + + test(renderMode + ": _jsPlumb.connect (remove multiple overlays by id)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + connection1.removeOverlays("aLabel", "anArrow"); + equals(0, connection1.overlays.length); + }); + + test(renderMode + ": _jsPlumb.connect (overlays, short-hand version)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var loc = { location:0.7 }; + var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label", {label:"CONNECTION 1", location:0.3}], + ["Arrow", arrowSpec, loc] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + }); + + test(renderMode + ": _jsPlumb.connect (removeAllOverlays)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var loc = { location:0.7 }; + var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label", {label:"CONNECTION 1", location:0.3, cssClass:"PPPP"}], + ["Arrow", arrowSpec, loc] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + + connection1.removeAllOverlays(); + equals(0, connection1.overlays.length); + equals(0, $(".PPPP").length); + }); + + test(renderMode + ": _jsPlumb.connect, specify arrow overlay using string identifier only", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:["Arrow"]}); + equals(jsPlumb.Overlays[renderMode].Arrow, conn.overlays[0].constructor); + }); + + test(renderMode + ": Connection.getOverlay method, existing overlay", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("arrowOverlay"); + ok(overlay != null); + }); + + test(renderMode + ": Connection.getOverlay method, non-existent overlay", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("IDONTEXIST"); + ok(overlay == null); + }); + + test(renderMode + ": Overlay.setVisible method", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("arrowOverlay"); + ok(overlay.isVisible()); + overlay.setVisible(false); + ok(!overlay.isVisible()); + overlay.setVisible(true); + ok(overlay.isVisible()); + }); + + // this test is for the original detach function; it should stay working after i mess with it + // a little. + test(renderMode + ": _jsPlumb.detach (by element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + var e3 = _jsPlumb.addEndpoint(d1); + var e4 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + _jsPlumb.connect({ sourceEndpoint:e3, targetEndpoint:e4 }); // make two connections to be sure this works ;) + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionCount(e3, 1); + assertConnectionCount(e4, 1); + + _jsPlumb.detach({source:"d1", target:"d2"}); + + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionCount(e3, 0); + assertConnectionCount(e4, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + // detach is being made to operate more like connect - by taking one argument with a whole + // bunch of possible params in it. if two args are passed in it will continue working + // in the old way. + test(renderMode + ": _jsPlumb.detach (params object, using element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({source:"d1", target:"d2"}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); +/* + test(renderMode + ": _jsPlumb.detach (params object, using target only)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e1 = _jsPlumb.addEndpoint(d1, {maxConnections:2}), + e2 = _jsPlumb.addEndpoint(d2), + e3 = _jsPlumb.addEndpoint(d3); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e3 }); + assertConnectionCount(e1, 2); + assertConnectionCount(e2, 1); + assertConnectionCount(e3, 1); + _jsPlumb.detach({target:"d2"}); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 0); + assertConnectionCount(e3, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1); + }); +*/ + test(renderMode + ": _jsPlumb.detach (params object, using element objects)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({source:d1, target:d2}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.detach (source and target as endpoint UUIDs)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {uuid:"abcdefg"}); + ok(_jsPlumb.getEndpoint("abcdefg") != null, "e1 exists"); + var e2 = _jsPlumb.addEndpoint(d2, {uuid:"hijklmn"}); + ok(_jsPlumb.getEndpoint("hijklmn") != null, "e2 exists"); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({uuids:["abcdefg", "hijklmn"]}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.detach (sourceEndpoint and targetEndpoint supplied)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.makeDynamicAnchors (longhand)", function() { + var anchors = [_jsPlumb.makeAnchor([0.2, 0, 0, -1], null, _jsPlumb), _jsPlumb.makeAnchor([1, 0.2, 1, 0], null, _jsPlumb), + _jsPlumb.makeAnchor([0.8, 1, 0, 1], null, _jsPlumb), _jsPlumb.makeAnchor([0, 0.8, -1, 0], null, _jsPlumb) ]; + var dynamicAnchor = _jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); + }); + + test(renderMode + ": _jsPlumb.makeDynamicAnchors (shorthand)", function() { + var anchors = [[0.2, 0, 0, -1], [1, 0.2, 1, 0], + [0.8, 1, 0, 1], [0, 0.8, -1, 0] ]; + var dynamicAnchor = _jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); + }); + + test(renderMode + ": Connection.isVisible/setVisible", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c1 = _jsPlumb.connect({source:d1,target:d2}); + equals(true, c1.isVisible(), "Connection is visible after creation."); + c1.setVisible(false); + equals(false, c1.isVisible(), "Connection is not visible after calling setVisible(false)."); + equals($(c1.connector.canvas).css("display"), "none"); + c1.setVisible(true); + equals(true, c1.isVisible(), "Connection is visible after calling setVisible(true)."); + equals($(c1.connector.canvas).css("display"), "block"); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible basic test (no connections)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals($(e1.canvas).css("display"), "none"); + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals($(e1.canvas).css("display"), "block"); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should track changes in the source, because it has only this connection.)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + var c1 = _jsPlumb.connect({source:e1, target:e2}); + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals(false, e2.isVisible(), "other Endpoint is not visible either."); + equals(false, c1.isVisible(), "connection between the two is not visible either."); + + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals(true, e2.isVisible(), "other Endpoint is visible too"); + equals(true, c1.isVisible(), "connection between the two is visible too."); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should not track changes in the source, because it has another connection.)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2, { maxConnections:2 }), e3 = _jsPlumb.addEndpoint(d3); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + var c1 = _jsPlumb.connect({source:e1, target:e2}); + var c2 = _jsPlumb.connect({source:e2, target:e3}); + + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals(true, e2.isVisible(), "other Endpoint should still be visible."); + equals(true, e3.isVisible(), "third Endpoint should still be visible."); + equals(false, c1.isVisible(), "connection between the two is not visible either."); + equals(true, c2.isVisible(), "other connection is visible."); + + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals(true, e2.isVisible(), "other Endpoint is visible too"); + equals(true, c1.isVisible(), "connection between the two is visible too."); + equals(true, c2.isVisible(), "other connection is visible."); + }); + + // tests of the functionality that allows a user to specify that they want elements appended to the document body + test(renderMode + " _jsPlumb.Defaults.Container, specified with a selector", function() { + _jsPlumb.Defaults.Container = $("body"); + equals($("#container")[0].childNodes.length, 0, "container has no nodes"); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals($("#container")[0].childNodes.length, 2, "container has two div elements"); // the divs we added have been added to the 'container' div. + // but we have told _jsPlumb to add its canvas to the body, so this connect call should not add another few elements to the container: + _jsPlumb.connect({source:d1, target:d2}); + equals($("#container")[0].childNodes.length, 2, "container still has two div elements"); + }); + + // tests of the functionality that allows a user to specify that they want elements appended to some specific container. + test(renderMode + " _jsPlumb.Defaults.Container, specified with DOM element", function() { + _jsPlumb.Defaults.Container = document.getElementsByTagName("body")[0]; + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length, "two divs added to the container"); // the divs we added have been added to the 'container' div. + // but we have told _jsPlumb to add its canvas to the body, so this connect call should not add another few elements to the container: + var bodyElementCount = $("body")[0].childNodes.length; + _jsPlumb.connect({source:d1, target:d2}); + equals(2, $("#container")[0].childNodes.length, "still only two children in container; elements were added to the body by _jsPlumb"); + // test to see if 3 elements have been added + equals(bodyElementCount + 3, $("body")[0].childNodes.length, "3 new elements added to the document body"); + }); + + test(renderMode + " container specified to connect call, with a selector", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length); // the divs we added have been added to the 'container' div. + var bodyElementCount = $("body")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to the body, so this connect call should not add another few elements to the container: + _jsPlumb.connect({source:d1, target:d2, container:$("body")}); + equals(2, $("#container")[0].childNodes.length); + equals(bodyElementCount + 3, $("body")[0].childNodes.length, "3 new elements added to the document body"); + }); + + test(renderMode + " container specified to connect call, with a string ID", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + equals(3, $("#container")[0].childNodes.length, "container has divs we added"); // the divs we added have been added to the 'container' div. + var d3ElementCount = $("#d3")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to "d3", so this connect call should not add another few elements to the container: + _jsPlumb.connect({source:d1, target:d2, container:"d3"}); + equals(3, $("#container")[0].childNodes.length, "container still has only the divs we added"); + equals(d3ElementCount + 3, $("#d3")[0].childNodes.length, "3 new elements added to div d3"); + }); + + test(renderMode + " container specified to addEndpoint call, with a selector", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length); // the divs we added have been added to the 'container' div. + var bodyElementCount = $("body")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to the body, so this connect call should not add another few elements to the container: + _jsPlumb.addEndpoint(d1, {container:$("body")}); + equals(2, $("#container")[0].childNodes.length); + equals(bodyElementCount + 1, $("body")[0].childNodes.length, "1 new element added to the document body"); + }); + + test(renderMode + " container specified to addEndpoint call, with a string ID", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + equals(3, $("#container")[0].childNodes.length, "container has divs we added"); // the divs we added have been added to the 'container' div. + var d3ElementCount = $("#d3")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to "d3", so this connect call should not add another few elements to the container: + _jsPlumb.addEndpoint(d1, { container:"d3" }); + equals(3, $("#container")[0].childNodes.length, "container still has only the divs we added"); + equals(d3ElementCount + 1, $("#d3")[0].childNodes.length, "1 new element added to div d3"); + }); + + test(renderMode + " detachable defaults to true when connection made between two endpoints", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2), + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection not detachable"); + }); + + test(renderMode + " connection detachable when target endpoint has connectionsDetachable set to true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2, {connectionsDetachable:true}), + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection detachable because connectionsDetachable was set on target endpoint"); + }); + + test(renderMode + " connection detachable when source endpoint has connectionsDetachable set to true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {connectionsDetachable:true}), e2 = _jsPlumb.addEndpoint(d2), + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection detachable because connectionsDetachable was set on source endpoint"); + }); + + test(renderMode + " Connector has 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.connector.type, "Bezier", "Bezier connector has type set"); + + var c2 = _jsPlumb.connect({source:d1, target:d2, connector:"Straight"}); + equals(c2.connector.type, "Straight", "Straight connector has type set"); + + var c3 = _jsPlumb.connect({source:d1, target:d2, connector:"Flowchart"}); + equals(c3.connector.type, "Flowchart", "Flowchart connector has type set"); + }); + + test(renderMode + " Endpoints have 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.endpoints[0].type, "Dot", "Dot endpoint has type set"); + + var c2 = _jsPlumb.connect({source:d1, target:d2, endpoints:["Rectangle", "Blank"]}); + equals(c2.endpoints[1].type, "Blank", "Blank endpoint has type set"); + equals(c2.endpoints[0].type, "Rectangle", "Rectangle endpoint has type set"); + }); + + test(renderMode + " Overlays have 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = _jsPlumb.connect({ + source:d1, + target:d2, + overlays:[ "Arrow", "Label", "PlainArrow", "Diamond" ] + }); + equals(c.overlays[0].type, "Arrow", "Arrow overlay has type set"); + equals(c.overlays[1].type, "Label", "Label overlay has type set"); + equals(c.overlays[2].type, "PlainArrow", "PlainArrow overlay has type set"); + equals(c.overlays[3].type, "Diamond", "Diamond overlay has type set"); + }); + + test(renderMode + " _jsPlumb.hide, original one-arg version", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + _jsPlumb.hide(d1); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(true, e1.isVisible(), "endpoint 1 is still visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + _jsPlumb.show(d1); + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + }); + + test(renderMode + " _jsPlumb.hide, two-arg version, endpoints should also be hidden", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + _jsPlumb.show(d1); // now show d1, but do not alter the endpoints. e1 should still be hidden + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + test(renderMode + " _jsPlumb.show, two-arg version, endpoints should become visible", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + _jsPlumb.show(d1, true); // now show d1, and alter the endpoints. e1 should be visible. + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(true, e1.isVisible(), "endpoint 1 is visible again."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + test(renderMode + " _jsPlumb.show, two-arg version, endpoints should become visible, but not all connections, because some other endpoints are not visible.", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e11 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + e3 = _jsPlumb.addEndpoint(d3, e), + c1 = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e11, target:e3}); + + // we now have d1 connected to both d3 and d2. we'll hide d1, and everything on d1 should be hidden. + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "connection 1 is no longer visible."); + equals(false, c2.isVisible(), "connection 2 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(false, e11.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + equals(true, e3.isVisible(), "endpoint 3 is still visible."); + + // now, we will also hide d3. making d1 visible again should NOT result in c2 becoming visible, because the other endpoint + // for c2 is e3, which is not visible. + _jsPlumb.hide(d3, true); + equals(false, e3.isVisible(), "endpoint 3 is no longer visible."); + + _jsPlumb.show(d1, true); // now show d1, and alter the endpoints. e1 should be visible, c1 should be visible, but c2 should not. + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(false, c2.isVisible(), "Connection 2 is not visible."); + equals(true, e1.isVisible(), "endpoint 1 is visible again."); + equals(true, e11.isVisible(), "endpoint 11 is visible again."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + equals(false, e3.isVisible(), "endpoint 3 is still not visible."); + }); + + /* + test(renderMode + " _jsPlumb.hide, two-arg version, endpoints should also be hidden", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + /** + * test for issue 132: label leaves its element in the DOM after it has been + * removed from a connection. + */ + test(renderMode + " label cleans itself up properly", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label", cssClass:"foo"}] + ]}); + ok($(".foo").length == 1, "label element exists in DOM"); + c.removeOverlay("label"); + ok($(".foo").length == 0, "label element does not exist in DOM"); + }); + + test(renderMode + " arrow cleans itself up properly", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Arrow", {id:"arrow"}] + ]}); + ok(c.getOverlay("arrow") != null, "arrow overlay exists"); + c.removeOverlay("arrow"); + ok(c.getOverlay("arrow") == null, "arrow overlay has been removed"); + }); + + test(renderMode + " label overlay getElement function", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label"}] + ]}); + ok(c.getOverlay("label").getElement() != null, "label overlay exposes element via getElement method"); + }); + + test(renderMode + " label overlay provides getLabel and setLabel methods", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label", label:"foo"}] + ]}); + var o = c.getOverlay("label"), e = o.getElement(); + ok(e.innerHTML == "foo", "label text is set to original value"); + o.setLabel("baz"); + ok(e.innerHTML == "baz", "label text is set to new value 'baz'"); + ok(o.getLabel() === "baz", "getLabel function works correctly with String"); + // now try functions + var aFunction = function() { return "aFunction"; }; + o.setLabel(aFunction); + ok(e.innerHTML == "aFunction", "label text is set to new value from Function"); + ok(o.getLabel() === aFunction, "getLabel function works correctly with Function"); + }); + + test(renderMode + " parameters object works for Endpoint", function() { + var d1 = _addDiv("d1"), + f = function() { alert("FOO!"); }, + e = _jsPlumb.addEndpoint(d1, { + isSource:true, + parameters:{ + "string":"param1", + "int":4, + "function":f + } + }); + ok(e.getParameter("string") === "param1", "getParameter(String) works correctly"); + ok(e.getParameter("int") === 4, "getParameter(int) works correctly"); + ok(e.getParameter("function") == f, "getParameter(Function) works correctly"); + }); + + test(renderMode + " parameters object works for Connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + f = function() { alert("FOO!"); }; + var c = _jsPlumb.connect({ + source:d1, + target:d2, + parameters:{ + "string":"param1", + "int":4, + "function":f + } + }); + ok(c.getParameter("string") === "param1", "getParameter(String) works correctly"); + ok(c.getParameter("int") === 4, "getParameter(int) works correctly"); + ok(c.getParameter("function") == f, "getParameter(Function) works correctly"); + }); + + test(renderMode + " parameters set on Endpoints and Connections are all merged, and merged correctly at that.", function() { + var d1 = _addDiv("d1"), + d2 = _addDiv("d2"), + e = _jsPlumb.addEndpoint(d1, { + isSource:true, + parameters:{ + "string":"sourceEndpoint", + "int":0, + "function":function() { return "sourceEndpoint"; } + } + }), + e2 = _jsPlumb.addEndpoint(d2, { + isTarget:true, + parameters:{ + "int":1, + "function":function() { return "targetEndpoint"; } + } + }), + c = _jsPlumb.connect({source:e, target:e2, parameters:{ + "function":function() { return "connection"; } + }}); + + ok(c.getParameter("string") === "sourceEndpoint", "getParameter(String) works correctly"); + ok(c.getParameter("int") === 1, "getParameter(int) works correctly"); + ok(c.getParameter("function")() == "connection", "getParameter(Function) works correctly"); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers standard connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2}); + equals(_jsPlumb.anchorManager.getConnectionsFor("d1").length, 1); + equals(_jsPlumb.anchorManager.getEndpointsFor("d1").length, 1); + equals(_jsPlumb.anchorManager.getConnectionsFor("d2").length, 1); + equals(_jsPlumb.anchorManager.getEndpointsFor("d2").length, 1); + var c2 = _jsPlumb.connect({source:d1, target:d2}); + equals(_jsPlumb.anchorManager.getConnectionsFor("d1").length, 2); + equals(_jsPlumb.anchorManager.getConnectionsFor("d2").length, 2); + equals(_jsPlumb.anchorManager.getEndpointsFor("d1").length, 2); + equals(_jsPlumb.anchorManager.getEndpointsFor("d2").length, 2); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers dynamic anchor connection, and removes it.", function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var c = _jsPlumb.connect({source:d3, target:d4, anchors:["AutoDefault", "AutoDefault"]}); + + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 1); + + var c2 = _jsPlumb.connect({source:d3, target:d4}); + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 2); + equals(_jsPlumb.anchorManager.getConnectionsFor("d4").length, 2); + + equals(_jsPlumb.anchorManager.getEndpointsFor("d3").length, 2); + _jsPlumb.detach(c); + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 1); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers continuous anchor connection, and removes it.", function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var c = _jsPlumb.connect({source:d3, target:d4, anchors:["Continuous", "Continuous"]}); + + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 1); + equals(_jsPlumb.anchorManager.getConnectionsFor("d4").length, 1); + + _jsPlumb.detach(c); + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 0); + equals(_jsPlumb.anchorManager.getConnectionsFor("d4").length, 0); + + _jsPlumb.reset(); + equals(_jsPlumb.anchorManager.getEndpointsFor("d4").length, 0); + }); + +// ------------- utility functions - math stuff, mostly -------------------------- + + var tolerance = 0.00000005, withinTolerance = function(v1, v2, msg) { + if (Math.abs(v1 - v2) < tolerance) ok(true, msg + "; expected " + v1 + " and got it"); + else { + ok(false, msg + "; expected " + v1 + " got " + v2); + } + }; + test(renderMode + " jsPlumbUtil.gradient, segment 1", function() { + var p1 = [2,2], p2 = [3,1], m = jsPlumbUtil.gradient(p1,p2); + equals(m, -1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.normal, segment 1", function() { + var p1 = [2,2], p2 = [3,1], m = jsPlumbUtil.normal(p1,p2); + equals(m, 1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.gradient, segment 2", function() { + var p1 = [2,2], p2 = [3,3], m = jsPlumbUtil.gradient(p1,p2); + equals(m, 1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.normal, segment 2", function() { + var p1 = [2,2], p2 = [3,3], m = jsPlumbUtil.normal(p1,p2); + equals(m, -1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.gradient, segment 3", function() { + var p1 = [2,2], p2 = [1,3], m = jsPlumbUtil.gradient(p1,p2); + equals(m, -1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.normal, segment 3", function() { + var p1 = [2,2], p2 = [1,3], m = jsPlumbUtil.normal(p1,p2); + equals(m, 1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.gradient, segment 4", function() { + var p1 = [2,2], p2 = [1,1], m = jsPlumbUtil.gradient(p1,p2); + equals(m, 1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.normal, segment 4", function() { + var p1 = [2,2], p2 = [1,1], m = jsPlumbUtil.normal(p1,p2); + equals(m, -1, "normal calculated correctly for simple case"); + }); + test(renderMode + "jsPlumbUtil.pointOnLine, segment 1", function() { + var p1 = {x:2,y:2}, p2={x:3, y:1}, + target = jsPlumbUtil.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumbUtil.pointOnLine, segment 2", function() { + var p1 = {x:2,y:2}, p2={x:3, y:3}, + target = jsPlumbUtil.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumbUtil.pointOnLine, segment 3", function() { + var p1 = {x:2,y:2}, p2={x:1, y:3}, + target = jsPlumbUtil.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumbUtil.pointOnLine, segment 4", function() { + var p1 = {x:2,y:2}, p2={x:1, y:1}, + target = jsPlumbUtil.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumbUtil.perpendicularLineTo, segment 1", function() { + var p1 = {x:2, y:2}, p2={x:3, y:1}, m = jsPlumbUtil.gradient(p1, p2), + l = jsPlumbUtil.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(4, l[0].x, "point 1 x is correct"); + withinTolerance(2, l[0].y, "point 1 y is correct"); + + withinTolerance(2, l[1].x, "point 2 x is correct"); + withinTolerance(0, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumbUtil.perpendicularLineTo, segment 2", function() { + var p1 = {x:2, y:2}, p2={x:3, y:3}, m = jsPlumbUtil.gradient(p1, p2), + l = jsPlumbUtil.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(4, l[0].x, "point 1 x is correct"); + withinTolerance(2, l[0].y, "point 1 y is correct"); + + withinTolerance(2, l[1].x, "point 2 x is correct"); + withinTolerance(4, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumbUtil.perpendicularLineTo, segment 3", function() { + var p1 = {x:2, y:2}, p2={x:1, y:3}, m = jsPlumbUtil.gradient(p1, p2), + l = jsPlumbUtil.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(2, l[0].x, "point 1 x is correct"); + withinTolerance(4, l[0].y, "point 1 y is correct"); + + withinTolerance(0, l[1].x, "point 2 x is correct"); + withinTolerance(2, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumbUtil.perpendicularLineTo, segment 4", function() { + var p1 = {x:2, y:2}, p2={x:1, y:1}, m = jsPlumbUtil.gradient(p1, p2), + l = jsPlumbUtil.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(2, l[0].x, "point 1 x is correct"); + withinTolerance(0, l[0].y, "point 1 y is correct"); + + withinTolerance(0, l[1].x, "point 2 x is correct"); + withinTolerance(2, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumbUtil.intersects, with intersection", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 3, y:4, w:3, h:3}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, with no intersection", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 13, y:4, w:3, h:3}; + + ok(!jsPlumbUtil.intersects(r1, r2), "r1 and r2 do not intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, with intersection, equal Y", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 1, y:2, w:3, h:6}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, with intersection, equal X", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 2, y:1, w:4, h:6}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, identical rectangles", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 2, y:2, w:4, h:6}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, corners touch (intersection)", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 6, y:8, w:3, h:3}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, one rectangle contained within the other", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 0, y:0, w:23, h:23}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "setImage on Endpoint", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + originalUrl = "../../img/endpointTest1.png", + e = { + endpoint:[ "Image", { src:originalUrl } ] + }, + ep = _jsPlumb.addEndpoint(d1, e); + }); + test(renderMode + "setImage on Endpoint, with supplied onload", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { + endpoint:[ "Image", { + src:"../../img/endpointTest1.png", + onload:function(imgEp) { + equals("../../img/endpointTest1.png", imgEp.img.src); + equals(ep.endpoint.canvas.getAttribute("src"), imgEp.img.src); + } + } ] + }, + ep = _jsPlumb.addEndpoint(d1, e); + ep.setImage("../../img/littledot.png", function(imgEp) { + equals("../../img/littledot.png", imgEp.img.src); + equals(ep.endpoint.canvas.getAttribute("src"), imgEp.img.src); + }); + }); + + test(renderMode + "attach endpoint to table when desired element was td", function() { + var table = document.createElement("table"), + tr = document.createElement("tr"), + td = document.createElement("td"); + table.appendChild(tr); tr.appendChild(td); + document.body.appendChild(table); + var ep = _jsPlumb.addEndpoint(td); + equals(ep.canvas.parentNode.tagName.toLowerCase(), "table"); + }); + +// issue 190 - regressions with getInstance. these tests ensure that generated ids are unique across +// instances. + + test(renderMode + " id clashes between instances", function() { + var d1 = document.createElement("div"), + d2 = document.createElement("div"), + _jsp2 = jsPlumb.getInstance(); + + document.body.appendChild(d1); + document.body.appendChild(d2); + + _jsPlumb.addEndpoint(d1); + _jsp2.addEndpoint(d2); + + var id1 = d1.getAttribute("id"), + id2 = d2.getAttribute("id"); + + var idx = id1.indexOf("_"), idx2 = id1.lastIndexOf("_"), v1 = id1.substring(idx, idx2); + var idx3 = id2.indexOf("_"), idx4 = id2.lastIndexOf("_"), v2 = id2.substring(idx3, idx4); + + ok (v1 != v2, "instance versions are different : " + v1 + " : " + v2); + }); + + test(renderMode + " id clashes between instances", function() { + var d1 = document.createElement("div"), + d2 = document.createElement("div"), + _jsp2 = jsPlumb.getInstance(); + + document.body.appendChild(d1); + document.body.appendChild(d2); + + _jsPlumb.addEndpoint(d1); + _jsPlumb.addEndpoint(d2); + + var id1 = d1.getAttribute("id"), + id2 = d2.getAttribute("id"); + + var idx = id1.indexOf("_"), idx2 = id1.lastIndexOf("_"), v1 = id1.substring(idx, idx2); + var idx3 = id2.indexOf("_"), idx4 = id2.lastIndexOf("_"), v2 = id2.substring(idx3, idx4); + + ok (v1 == v2, "instance versions are the same : " + v1 + " : " + v2); + }); + + test(renderMode + " importDefaults", function() { + _jsPlumb.Defaults.Anchors = ["LeftMiddle", "RightMiddle"]; + var d1 = _addDiv("d1"), + d2 = _addDiv(d2), + c = _jsPlumb.connect({source:d1, target:d2}), + e = c.endpoints[0]; + + equals(e.anchor.x, 0, "left middle anchor"); + equals(e.anchor.y, 0.5, "left middle anchor"); + + _jsPlumb.importDefaults({ + Anchors:["TopLeft", "TopRight"] + }); + + var conn = _jsPlumb.connect({source:d1, target:d2}), + e1 = conn.endpoints[0], e2 = conn.endpoints[1]; + + equals(e1.anchor.x, 0, "top leftanchor"); + equals(e2.anchor.y, 0, "top left anchor"); + equals(e2.anchor.x, 1, "top right anchor"); + equals(e2.anchor.y, 0, "top right anchor"); + + }); + + test(renderMode + " restoreDefaults", function() { + _jsPlumb.importDefaults({ + Anchors:["TopLeft", "TopRight"] + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), conn = _jsPlumb.connect({source:d1, target:d2}), + e1 = conn.endpoints[0], e2 = conn.endpoints[1]; + + equals(e1.anchor.x, 0, "top leftanchor"); + equals(e2.anchor.y, 0, "top left anchor"); + equals(e2.anchor.x, 1, "top right anchor"); + equals(e2.anchor.y, 0, "top right anchor"); + + _jsPlumb.restoreDefaults(); + + var conn2 = _jsPlumb.connect({source:d1, target:d2}), + e3 = conn2.endpoints[0], e4 = conn2.endpoints[1]; + + equals(e3.anchor.x, 0.5, "bottom center anchor"); + equals(e3.anchor.y, 1, "bottom center anchor"); + equals(e4.anchor.x, 0.5, "bottom center anchor"); + equals(e4.anchor.y, 1, "bottom center anchor"); + }); + +// setId function + + test(renderMode + " setId, taking two strings, only default scope", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId("d1", "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a selector and a string, only default scope", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1"), "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a DOM element and a string, only default scope", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1")[0], "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking two strings, mix of scopes", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2, scope:"FOO"}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId("d1", "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a selector and a string, mix of scopes", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2, scope:"FOO"}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1"), "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a DOM element and a string, mix of scopes", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2, scope:"FOO"}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1")[0], "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setIdChanged, ", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + $("#d1").attr("id", "d3"); + + _jsPlumb.setIdChanged("d1", "d3"); + + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " endpoint hide/show should hide/show overlays", function() { + _addDiv("d1"); + var e1 = _jsPlumb.addEndpoint("d1", { + overlays:[ + [ "Label", { id:"label", label:"foo" } ] + ] + }), + o = e1.getOverlay("label"); + + ok(o.isVisible(), "overlay is initially visible"); + _jsPlumb.hide("d1", true); + ok(!o.isVisible(), "overlay is no longer visible"); + }); + + test(renderMode + " connection hide/show should hide/show overlays", function() { + _addDiv("d1");_addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", + overlays:[ + [ "Label", { id:"label", label:"foo" } ] + ] + }), + o = c.getOverlay("label"); + + ok(o.isVisible(), "overlay is initially visible"); + _jsPlumb.hide("d1", true); + ok(!o.isVisible(), "overlay is no longer visible"); + }); + + test(renderMode + " select, basic test", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2"}), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 1, "one connection selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; dont filter on scope.", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 2, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scope", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + s = _jsPlumb.select({source:"d1", scope:"FOO"}); + + equals(s.length, 1, "one connection selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scopes", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + c3 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAZ"}) + s = _jsPlumb.select({source:"d1", scope:["FOO","BAR"]}); + + equals(s.length, 2, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; scope but no scope filter; single source id", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + c3 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAZ"}), + c4 = _jsPlumb.connect({source:"d2", target:"d1", scope:"BOZ"}), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 3, "three connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scopes; single source id", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + c3 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAZ"}), + c4 = _jsPlumb.connect({source:"d2", target:"d1", scope:"BOZ"}), + s = _jsPlumb.select({source:"d1", scope:["FOO","BAR", "BOZ"]}); + + equals(s.length, 2, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scope; dont supply sourceid", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + s = _jsPlumb.select({ scope:"FOO" }); + + equals(s.length, 1, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scope; dont supply sourceid", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d2", target:"d1", scope:"BAR"}), + s = _jsPlumb.select({ scope:"FOO" }); + + equals(s.length, 1, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, two connections, with overlays", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({ + source:"d1", + target:"d2", + overlays:[ + ["Label", {id:"l"}] + ] + }), + c2 = _jsPlumb.connect({ + source:"d1", + target:"d2", + overlays:[ + ["Label", {id:"l"}] + ] + }), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 2, "two connections selected"); + ok(s.get(0).getOverlay("l") != null, "connection has overlay"); + ok(s.get(1).getOverlay("l") != null, "connection has overlay"); + }); + + test(renderMode + " select, chaining with setHover and hideOverlay", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({ + source:"d1", + target:"d2", + overlays:[ + ["Label", {id:"l"}] + ] + }); + s = _jsPlumb.select({source:"d1"}); + + s.setHover(false).hideOverlay("l"); + + ok(!(s.get(0).isHover()), "connection is not hover"); + ok(!(s.get(0).getOverlay("l").isVisible()), "overlay is not visible"); + }); + + test(renderMode + " select, .each function", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10) + }); + } + + var s = _jsPlumb.select(); + equals(s.length, 5, "there are five connections"); + + var t = ""; + s.each(function(connection) { + t += "f"; + }); + equals("fffff", t, ".each is working"); + }); + + test(renderMode + " select, multiple connections + chaining", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + overlays:[ + ["Arrow", {location:0.3}], + ["Arrow", {location:0.7}] + ] + }); + } + + var s = _jsPlumb.select().removeAllOverlays().setParameter("foo", "bar").setHover(false).setLabel("baz"); + equals(s.length, 5, "there are five connections"); + + for (var j = 0; j < 5; j++) { + equals(s.get(j).getOverlays().length, 1, "one overlay: the label"); + equals(s.get(j).getParameter("foo"), "bar", "parameter foo has value 'bar'"); + ok(!(s.get(j).isHover()), "hover is set to false"); + equals(s.get(j).getLabel(), "baz", "label is set to 'baz'"); + } + }); + + test(renderMode + " select, simple getter", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + label:"FOO" + }); + } + + var lbls = _jsPlumb.select().getLabel(); + equals(lbls.length, 5, "there are five labels"); + + for (var j = 0; j < 5; j++) { + equals(lbls[j][0], "FOO", "label has value 'FOO'"); + } + }); + + test(renderMode + " select, getter + chaining", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + label:"FOO" + }); + } + + var params = _jsPlumb.select().removeAllOverlays().setParameter("foo", "bar").getParameter("foo"); + equals(params.length, 5, "there are five params"); + + for (var j = 0; j < 5; j++) { + equals(params[j][0], "bar", "parameter has value 'bar'"); + } + }); + + + test(renderMode + " select, detach method", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + label:"FOO" + }); + } + + var params = _jsPlumb.select().detach(); + + equals(jsPlumb.select().length, 0, "there are no connections"); + }); + +// setPaintStyle/getPaintStyle tests + + test(renderMode + " setPaintStyle", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), c = _jsPlumb.connect({source:d1, target:d2}); + c.setPaintStyle({strokeStyle:"FOO", lineWidth:999}); + equals(c.paintStyleInUse.strokeStyle, "FOO", "strokeStyle was set"); + equals(c.paintStyleInUse.lineWidth, 999, "lineWidth was set"); + + c.setHoverPaintStyle({strokeStyle:"BAZ", lineWidth:444}); + c.setHover(true); + equals(c.paintStyleInUse.strokeStyle, "BAZ", "strokeStyle was set"); + equals(c.paintStyleInUse.lineWidth, 444, "lineWidth was set"); + + equals(c.getPaintStyle().strokeStyle, "FOO", "getPaintStyle returns correct value"); + equals(c.getHoverPaintStyle().strokeStyle, "BAZ", "getHoverPaintStyle returns correct value"); + }); + +// ******************* getEndpoints ************************************************ + + test(renderMode + " getEndpoints", function() { + _addDiv("d1");_addDiv("d2"); + + _jsPlumb.addEndpoint("d1"); + _jsPlumb.addEndpoint("d2"); + _jsPlumb.addEndpoint("d1"); + + var e = _jsPlumb.getEndpoints("d1"), + e2 = _jsPlumb.getEndpoints("d2"); + equals(e.length, 2, "two endpoints on d1"); + equals(e2.length, 1, "one endpoint on d2"); + }); + + /** + * leave this test at the bottom! + */ + test(renderMode + ': unload test', function() { + _jsPlumb.unload(); + var contextNode = $(".__jsPlumb_context"); + ok(contextNode.length == 0, 'context node unloaded'); + }); +}; + diff --git a/archive/1.3.10/jsPlumb-connectors-statemachine-1.3.10-RC1.js b/archive/1.3.10/jsPlumb-connectors-statemachine-1.3.10-RC1.js new file mode 100644 index 000000000..03627db22 --- /dev/null +++ b/archive/1.3.10/jsPlumb-connectors-statemachine-1.3.10-RC1.js @@ -0,0 +1,465 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.9 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the state machine connectors. + * + * Thanks to Brainstorm Mobile Solutions for supporting the development of these. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + var Line = function(x1, y1, x2, y2) { + + this.m = (y2 - y1) / (x2 - x1); + this.b = -1 * ((this.m * x1) - y1); + + this.rectIntersect = function(x,y,w,h) { + var results = []; + + // try top face + // the equation of the top face is y = (0 * x) + b; y = b. + var xInt = (y - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try right face + var yInt = (this.m * (x + w)) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + // bottom face + var xInt = ((y + h) - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try left face + var yInt = (this.m * x) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + if (results.length == 2) { + var midx = (results[0][0] + results[1][0]) / 2, midy = (results[0][1] + results[1][1]) / 2; + results.push([ midx,midy ]); + // now calculate the segment inside the rectangle where the midpoint lies. + var xseg = midx <= x + (w / 2) ? -1 : 1, + yseg = midy <= y + (h / 2) ? -1 : 1; + results.push([xseg, yseg]); + return results; + } + + return null; + + }; + }, + _segment = function(x1, y1, x2, y2) { + if (x1 <= x2 && y2 <= y1) return 1; + else if (x1 <= x2 && y1 <= y2) return 2; + else if (x2 <= x1 && y2 >= y1) return 3; + return 4; + }, + + // the control point we will use depends on the faces to which each end of the connection is assigned, specifically whether or not the + // two faces are parallel or perpendicular. if they are parallel then the control point lies on the midpoint of the axis in which they + // are parellel and varies only in the other axis; this variation is proportional to the distance that the anchor points lie from the + // center of that face. if the two faces are perpendicular then the control point is at some distance from both the midpoints; the amount and + // direction are dependent on the orientation of the two elements. 'seg', passed in to this method, tells you which segment the target element + // lies in with respect to the source: 1 is top right, 2 is bottom right, 3 is bottom left, 4 is top left. + // + // sourcePos and targetPos are arrays of info about where on the source and target each anchor is located. their contents are: + // + // 0 - absolute x + // 1 - absolute y + // 2 - proportional x in element (0 is left edge, 1 is right edge) + // 3 - proportional y in element (0 is top edge, 1 is bottom edge) + // + _findControlPoint = function(midx, midy, segment, sourceEdge, targetEdge, dx, dy, distance, proximityLimit) { + + // TODO (maybe) + // - if anchor pos is 0.5, make the control point take into account the relative position of the elements. + if (distance <= proximityLimit) return [midx, midy]; + + if (segment == 1) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 2) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 3) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 4) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + }; + + /* + Function: StateMachine constructor + + Allowed parameters: + curviness - measure of how "curvy" the connectors will be. this is translated as the distance that the + Bezier curve's control point is from the midpoint of the straight line connecting the two + endpoints, and does not mean that the connector is this wide. The Bezier curve never reaches + its control points; they act as gravitational masses. defaults to 10. + margin - distance from element to start and end connectors, in pixels. defaults to 5. + proximityLimit - sets the distance beneath which the elements are consider too close together to bother with fancy + curves. by default this is 80 pixels. + loopbackRadius - the radius of a loopback connector. optional; defaults to 25. + showLoopback - If set to false this tells the connector that it is ok to paint connections whose source and target is the same element with a connector running through the element. The default value for this is true; the connector always makes a loopback connection loop around the element rather than passing through it. + */ + jsPlumb.Connectors.StateMachine = function(params) { + var self = this, + currentPoints = null, + _sx, _sy, _tx, _ty, _controlPoint = [], + curviness = params.curviness || 10, + margin = params.margin || 5, + proximityLimit = params.proximityLimit || 80, + clockwise = params.orientation && params.orientation == "clockwise", + loopbackRadius = params.loopbackRadius || 25, + isLoopback = false, + showLoopback = params.showLoopback !== false; + + this.type = "StateMachine"; + params = params || {}; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *= 1.9; + //ensure at least one pixel width + lineWidth = lineWidth || 1; + var x = Math.min(sourcePos[0], targetPos[0]) - xo, + y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (!showLoopback || (sourceEndpoint.elementId != targetEndpoint.elementId)) { + + isLoopback = false; + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + + // now adjust for the margin + if (sourcePos[2] == 0) _sx -= margin; + if (sourcePos[2] == 1) _sx += margin; + if (sourcePos[3] == 0) _sy -= margin; + if (sourcePos[3] == 1) _sy += margin; + if (targetPos[2] == 0) _tx -= margin; + if (targetPos[2] == 1) _tx += margin; + if (targetPos[3] == 0) _ty -= margin; + if (targetPos[3] == 1) _ty += margin; + + // + // these connectors are quadratic bezier curves, having a single control point. if both anchors + // are located at 0.5 on their respective faces, the control point is set to the midpoint and you + // get a straight line. this is also the case if the two anchors are within 'proximityLimit', since + // it seems to make good aesthetic sense to do that. outside of that, the control point is positioned + // at 'curviness' pixels away along the normal to the straight line connecting the two anchors. + // + // there may be two improvements to this. firstly, we might actually support the notion of avoiding nodes + // in the UI, or at least making a good effort at doing so. if a connection would pass underneath some node, + // for example, we might increase the distance the control point is away from the midpoint in a bid to + // steer it around that node. this will work within limits, but i think those limits would also be the likely + // limits for, once again, aesthetic good sense in the layout of a chart using these connectors. + // + // the second possible change is actually two possible changes: firstly, it is possible we should gradually + // decrease the 'curviness' as the distance between the anchors decreases; start tailing it off to 0 at some + // point (which should be configurable). secondly, we might slightly increase the 'curviness' for connectors + // with respect to how far their anchor is from the center of its respective face. this could either look cool, + // or stupid, and may indeed work only in a way that is so subtle as to have been a waste of time. + // + + var _midx = (_sx + _tx) / 2, _midy = (_sy + _ty) / 2, + m2 = (-1 * _midx) / _midy, theta2 = Math.atan(m2), + dy = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.sin(theta2)), + dx = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.cos(theta2)), + segment = _segment(_sx, _sy, _tx, _ty), + distance = Math.sqrt(Math.pow(_tx - _sx, 2) + Math.pow(_ty - _sy, 2)); + + // calculate the control point. this code will be where we'll put in a rudimentary element avoidance scheme; it + // will work by extending the control point to force the curve to be, um, curvier. + _controlPoint = _findControlPoint(_midx, + _midy, + segment, + sourcePos, + targetPos, + curviness, curviness, + distance, + proximityLimit); + + + var requiredWidth = Math.max(Math.abs(_controlPoint[0] - _sx) * 3, Math.abs(_controlPoint[0] - _tx) * 3, Math.abs(_tx-_sx), 2 * lineWidth, minWidth), + requiredHeight = Math.max(Math.abs(_controlPoint[1] - _sy) * 3, Math.abs(_controlPoint[1] - _ty) * 3, Math.abs(_ty-_sy), 2 * lineWidth, minWidth); + + if (w < requiredWidth) { + var dw = requiredWidth - w; + x -= (dw / 2); + _sx += (dw / 2); + _tx += (dw / 2); + w = requiredWidth; + _controlPoint[0] += (dw / 2); + } + + if (h < requiredHeight) { + var dh = requiredHeight - h; + y -= (dh / 2); + _sy += (dh / 2); + _ty += (dh / 2); + h = requiredHeight; + _controlPoint[1] += (dh / 2); + } + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty, _controlPoint[0], _controlPoint[1] ]; + } + else { + isLoopback = true; + // a loopback connector. draw an arc from one anchor to the other. + // i guess we'll do this the same way as the others. just the control point will be a fair distance away. + var x1 = sourcePos[0], x2 = sourcePos[0], y1 = sourcePos[1] - margin, y2 = sourcePos[1] - margin, + cx = x1, cy = y1 - loopbackRadius; + + // canvas sizing stuff, to ensure the whole painted area is visible. + w = ((2 * lineWidth) + (4 * loopbackRadius)), h = ((2 * lineWidth) + (4 * loopbackRadius)); + x = cx - loopbackRadius - lineWidth - loopbackRadius, y = cy - loopbackRadius - lineWidth - loopbackRadius; + currentPoints = [ x, y, w, h, cx-x, cy-y, loopbackRadius, clockwise, x1-x, y1-y, x2-x, y2-y]; + } + + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_tx, y:_ty }, + { x:_controlPoint[0], y:_controlPoint[1] }, + { x:_controlPoint[0] + 1, y:_controlPoint[1] + 1}, + { x:_sx, y:_sy } + ]; + }; + + var _translateLocation = function(curve, location, absolute) { + if (absolute) + location = jsBezier.locationAlongCurveFrom(curve, location > 0 ? 0 : 1, location); + + return location; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location, absolute) { + if (isLoopback) { + if (absolute) { + var circumference = Math.PI * 2 * loopbackRadius; + location = location / circumference; + } + + if (location > 0 && location < 1) location = 1 - location; + +// current points are [ x, y, width, height, center x, center y, radius, clockwise, startx, starty, endx, endy ] + // so the path length is the circumference of the circle + //var len = 2 * Math.PI * currentPoints[6], + // map 'location' to an angle. 0 is PI/2 when the connector is on the top face; if we + // support other faces it will have to be calculated for each one. 1 is also PI/2. + // 0.5 is -PI/2. + var startAngle = (location * 2 * Math.PI) + (Math.PI / 2), + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + + } + else { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.pointOnCurve(c, location); + } + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location, absolute) { + if (isLoopback) { + // todo if absolute, location is a proportion of circumference + if (absolute) { + var circumference = Math.PI * 2 * loopbackRadius; + location = location / circumference; + } + + return Math.atan(location * 2 * Math.PI); + } + else { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.gradientAtPoint(c, location); + } + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance, absolute) { + if (isLoopback) { + + if (absolute) { + var circumference = Math.PI * 2 * loopbackRadius; + location = location / circumference; + } + + if (location > 0 && location < 1) location = 1 - location; + + var circumference = 2 * Math.PI * currentPoints[6], + arcSpan = distance / circumference * 2 * Math.PI, + startAngle = (location * 2 * Math.PI) - arcSpan + (Math.PI / 2), + + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + } + else { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.pointAlongCurveFrom(c, location, distance); + } + }; + + }; + + /* + * Canvas state machine renderer. + */ + jsPlumb.Connectors.canvas.StateMachine = function(params) { + params = params || {}; + var self = this, drawGuideline = params.drawGuideline || true, avoidSelector = params.avoidSelector; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.CanvasConnector.apply(this, arguments); + + + this._paint = function(dimensions) { + + if (dimensions.length == 10) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[8], dimensions[9], dimensions[6], dimensions[7]); + self.ctx.stroke(); + } + else { + // a loopback connector + self.ctx.save(); + self.ctx.beginPath(); + var startAngle = 0, // Starting point on circle + endAngle = 2 * Math.PI, // End point on circle + clockwise = dimensions[7]; // clockwise or anticlockwise + self.ctx.arc(dimensions[4],dimensions[5],dimensions[6],0, endAngle, clockwise); + self.ctx.stroke(); + self.ctx.closePath(); + self.ctx.restore(); + } + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + /* + * SVG State Machine renderer + */ + jsPlumb.Connectors.svg.StateMachine = function() { + var self = this; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.SvgConnector.apply(this, arguments); + this.getPath = function(d) { + + if (d.length == 10) + return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[8] + " " + d[9] + " " + d[6] + " " + d[7]; + else { + // loopback + return "M" + (d[8] + 4) + " " + d[9] + " A " + d[6] + " " + d[6] + " 0 1,0 " + (d[8]-4) + " " + d[9]; + } + }; + }; + + /* + * VML state machine renderer + */ + jsPlumb.Connectors.vml.StateMachine = function() { + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.VmlConnector.apply(this, arguments); + var _conv = jsPlumb.vml.convertValue; + this.getPath = function(d) { + if (d.length == 10) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + } + else { + // loopback + var left = _conv(d[8] - d[6]), + top = _conv(d[9] - (2 * d[6])), + right = left + _conv(2 * d[6]), + bottom = top + _conv(2 * d[6]), + posString = left + "," + top + "," + right + "," + bottom; + + var o = "ar " + posString + "," + _conv(d[8]) + "," + + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + " e"; + + return o; + } + }; + }; + +})(); + +/* + // now for a rudimentary avoidance scheme. TODO: how to set this in a cross-library way? + // if (avoidSelector) { + // var testLine = new Line(sourcePos[0] + _sx,sourcePos[1] + _sy,sourcePos[0] + _tx,sourcePos[1] + _ty); + // var sel = jsPlumb.getSelector(avoidSelector); + // for (var i = 0; i < sel.length; i++) { + // var id = jsPlumb.getId(sel[i]); + // if (id != sourceEndpoint.elementId && id != targetEndpoint.elementId) { + // o = jsPlumb.getOffset(id), s = jsPlumb.getSize(id); +// +// if (o && s) { +// var collision = testLine.rectIntersect(o.left,o.top,s[0],s[1]); +// if (collision) { + // set the control point to be a certain distance from the midpoint of the two points that + // the line crosses on the rectangle. + // TODO where will this 75 number come from? + // _controlX = collision[2][0] + (75 * collision[3][0]); + // / _controlY = collision[2][1] + (75 * collision[3][1]); +// } +// } + // } + // } + //} + */ \ No newline at end of file diff --git a/archive/1.3.10/jsPlumb-defaults-1.3.10-RC1.js b/archive/1.3.10/jsPlumb-defaults-1.3.10-RC1.js new file mode 100644 index 000000000..54ac541c4 --- /dev/null +++ b/archive/1.3.10/jsPlumb-defaults-1.3.10-RC1.js @@ -0,0 +1,1188 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.9 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the default Connectors, Endpoint and Overlay definitions. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +(function() { + + /** + * + * Helper class to consume unused mouse events by components that are DOM elements and + * are used by all of the different rendering modes. + * + */ + jsPlumb.DOMElementComponent = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + // when render mode is canvas, these functions may be called by the canvas mouse handler. + // this component is safe to pipe this stuff to /dev/null. + this.mousemove = + this.dblclick = + this.click = + this.mousedown = + this.mouseup = function(e) { }; + }; + + /** + * Class: Connectors.Straight + * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters. + */ + jsPlumb.Connectors.Straight = function() { + this.type = "Straight"; + var self = this, + currentPoints = null, + _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty, _segment, _length; + + /** + * Computes the new size and position of the canvas. + */ + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + _dx = _tx - _sx, _dy = _ty - _sy; + //_m = _dy / _dx, _m2 = -1 / _m; + _m = jsPlumbUtil.gradient({x:_sx, y:_sy}, {x:_tx, y:_ty}), _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + //_segment = jsPlumbUtil.segment({x:_sx, y:_sy}, {x:_tx, y:_ty}); + _length = Math.sqrt((_dx * _dx) + (_dy * _dy)); + + return currentPoints; + }; + + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location, absolute) { + if (location == 0 && !absolute) + return { x:_sx, y:_sy }; + else if (location == 1 && !absolute) + return { x:_tx, y:_ty }; + else { + var l = absolute ? location > 0 ? location : _length + location : location * _length; + return jsPlumbUtil.pointOnLine({x:_sx, y:_sy}, {x:_tx, y:_ty}, l); + } + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { + return _m; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + * this hands off to jsPlumbUtil to do the maths, supplying two points and the distance. + */ + this.pointAlongPathFrom = function(location, distance, absolute) { + var p = self.pointOnPath(location, absolute), + farAwayPoint = location == 1 ? { + x:_sx + ((_tx - _sx) * 10), + y:_sy + ((_sy - _ty) * 10) + } : {x:_tx, y:_ty }; + + return jsPlumbUtil.pointOnLine(p, farAwayPoint, distance); + }; + }; + + + /** + * Class:Connectors.Bezier + * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's + * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below. + */ + /** + * Function:Constructor + * + * Parameters: + * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line. + * Optional; defaults to 150. + * stub - optional value for a distance to travel from the connector's endpoint before beginning the Bezier curve. defaults to 0. + * + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + params = params || {}; + this.majorAnchor = params.curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + this.type = "Bezier"; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(sourceEndpoint), + too = targetAnchor.getOrientation(targetEndpoint), + perpendicular = soo[0] != too[0] || soo[1] == too[1], + p = [], + ma = self.majorAnchor, mi = self.minorAnchor; + + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h, _sStubX, _sStubY, _tStubX, _tStubY; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetEndpoint, sourceEndpoint, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx), minx2 = Math.min(_CP[0], _CP2[0]), minx = Math.min(minx1,minx2), + maxx1 = Math.max(_sx,_tx), maxx2 = Math.max(_CP[0], _CP2[0]), maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty), miny2 = Math.min(_CP[1], _CP2[1]), miny = Math.min(miny1,miny2), + maxy1 = Math.max(_sy,_ty), maxy2 = Math.max(_CP[1], _CP2[1]), maxy = Math.max(maxy1,maxy2); + + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, + _sx, _sy, _tx, _ty, + _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + var _translateLocation = function(curve, location, absolute) { + if (absolute) + location = jsBezier.locationAlongCurveFrom(curve, location > 0 ? 0 : 1, location); + + return location; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location, absolute) { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.pointOnCurve(c, location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location, absolute) { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.gradientAtPoint(c, location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance, absolute) { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.pointAlongCurveFrom(c, location, distance); + }; + }; + + + /** + * Class: Connectors.Flowchart + * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments. + */ + /** + * Function: Constructor + * + * Parameters: + * stub - minimum length for the stub at each end of the connector. This can be an integer, giving a value for both ends of the connections, + * or an array of two integers, giving separate values for each end. The default is an integer with value 30 (pixels). + * gap - gap to leave between the end of the connector and the element on which the endpoint resides. if you make this larger than stub then you will see some odd looking behaviour. defaults to 0 pixels. + */ + jsPlumb.Connectors.Flowchart = function(params) { + this.type = "Flowchart"; + params = params || {}; + var self = this, + stub = params.stub || params.minStubLength /* bwds compat. */ || 30, + sourceStub = jsPlumbUtil.isArray(stub) ? stub[0] : stub, + targetStub = jsPlumbUtil.isArray(stub) ? stub[1] : stub, + gap = params.gap || 0, + segments = [], + totalLength = 0, + segmentProportions = [], + segmentProportionalLengths = [], + points = [], + swapX, swapY, + maxX = -Infinity, maxY = -Infinity, + minX = Infinity, minY = Infinity, + /** + * recalculates the points at which the segments begin and end, proportional to the total length travelled + * by all the segments that constitute the connector. we use this to help with pointOnPath calculations. + */ + updateSegmentProportions = function(startX, startY, endX, endY) { + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segments[i][5] / totalLength; + segmentProportions[i] = [curLoc, (curLoc += (segments[i][5] / totalLength)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0], + ly = segments.length == 0 ? sy : segments[segments.length - 1][1], + m = x == lx ? Infinity : 0, + l = Math.abs(x == lx ? y - ly : x - lx); + segments.push([x, y, lx, ly, m, l]); + totalLength += l; + + maxX = Math.max(maxX, x); + maxY = Math.max(maxY, y); + minX = Math.min(minX, x); + minY = Math.min(minY, y); + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment + * that contains the point which is 'location' distance along the entire path, where + * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths + * are made up of a list of segments, each of which contributes some fraction to + * the total length. + * From 1.3.10 this also supports the 'absolute' property, which lets us specify a location + * as the absolute distance in pixels, rather than a proportion of the total path. + */ + findSegmentForLocation = function(location, absolute) { + if (absolute) { + location = location > 0 ? location / totalLength : (totalLength + location) / totalLength; + } + + var idx = segmentProportions.length - 1, inSegmentProportion = 1; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, + sourceAnchor, targetAnchor, lineWidth, minWidth, sourceInfo, targetInfo) { + + segments = []; + segmentProportions = []; + totalLength = 0; + segmentProportionalLengths = []; + maxX = maxY = -Infinity; + minX = minY = Infinity; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + sourceOffx = (lw / 2) + (sourceStub + targetStub), + targetOffx = (lw / 2) + (targetStub + sourceStub), + sourceOffy = (lw / 2) + (sourceStub + targetStub), + targetOffy = (lw / 2) + (targetStub + sourceStub), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(sourceEndpoint), + to = targetAnchor.orientation || targetAnchor.getOrientation(targetEndpoint), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + sourceOffx + targetOffx, + h = Math.abs(targetPos[1] - sourcePos[1]) + sourceOffy + targetOffy; + + // if either anchor does not have an orientation set, we derive one from their relative + // positions. we fix the axis to be the one in which the two elements are further apart, and + // point each anchor at the other element. this is also used when dragging a new connection. + if (so[0] == 0 && so[1] == 0 || to[0] == 0 && to[1] == 0) { + var index = w > h ? 0 : 1, oIndex = [1,0][index]; + so = []; to = []; + so[index] = sourcePos[index] > targetPos[index] ? -1 : 1; + to[index] = sourcePos[index] > targetPos[index] ? 1 : -1; + so[oIndex] = 0; + to[oIndex] = 0; + } + + if (w < minWidth) { + sourceOffx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + sourceOffy += (minWidth - h) / 2; + h = minWidth; + } + + var sx = swapX ? (w - targetOffx) +( gap * so[0]) : sourceOffx + (gap * so[0]), + sy = swapY ? (h - targetOffy) + (gap * so[1]) : sourceOffy + (gap * so[1]), + tx = swapX ? sourceOffx + (gap * to[0]) : (w - targetOffx) + (gap * to[0]), + ty = swapY ? sourceOffy + (gap * to[1]) : (h - targetOffy) + (gap * to[1]), + startStubX = sx + (so[0] * sourceStub), + startStubY = sy + (so[1] * sourceStub), + endStubX = tx + (to[0] * targetStub), + endStubY = ty + (to[1] * targetStub), + isXGreaterThanStubTimes2 = Math.abs(sx - tx) > (sourceStub + targetStub), + isYGreaterThanStubTimes2 = Math.abs(sy - ty) > (sourceStub + targetStub), + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2), + oProduct = ((so[0] * to[0]) + (so[1] * to[1])), + opposite = oProduct == -1, + perpendicular = oProduct == 0, + orthogonal = oProduct == 1; + + x -= sourceOffx; y -= sourceOffy; + points = [x, y, w, h, sx, sy, tx, ty]; + var extraPoints = []; + + var sourceAxis = so[0] == 0 ? "y" : "x", + anchorOrientation = opposite ? "opposite" : orthogonal ? "orthogonal" : "perpendicular", + segment = jsPlumbUtil.segment([sx, sy], [tx, ty]), + flipSourceSegments = so[sourceAxis == "x" ? 0 : 1] == -1, + flipSegments = { + "x":[null, 4, 3, 2, 1], + "y":[null, 2, 1, 4, 3] + } + + if (flipSourceSegments) + segment = flipSegments[sourceAxis][segment]; + + /*if (segment == 1 || segment == 2) { + if (sourceAxis == "x") + addSegment(Math.max(startStubX, endStubX), startStubY, sx, sy, tx, ty); + else + addSegment(startStubX, Math.max(startStubY, endStubY), sx, sy, tx, ty); + } + else { + if (sourceAxis == "x") + addSegment(Math.min(startStubX, endStubX), startStubY, sx, sy, tx, ty); + else + addSegment(startStubX, Math.min(startStubY, endStubY), sx, sy, tx, ty); + }*/ + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + var findClearedLine = function(start, mult, anchorPos, dimension) { + return start + (mult * (( 1 - anchorPos) * dimension) + Math.max(sourceStub, targetStub)); + //mx = so[0] == 0 ? startStubX + ((1 - sourceAnchor.x) * sourceInfo.width) + stub : startStubX, + }, + + lineCalculators = { + oppositex : function() { + if (sourceEndpoint.elementId == targetEndpoint.elementId) { + var _y = startStubY + ((1 - sourceAnchor.y) * sourceInfo.height) + Math.max(sourceStub, targetStub); + return [ [ startStubX, _y ], [ endStubX, _y ]]; + } + else if (isXGreaterThanStubTimes2 && (segment == 1 || segment == 2)) { + return [[ midx, sy ], [ midx, ty ]]; + } + else { + return [[ startStubX, midy ], [endStubX, midy ]]; + } + }, + orthogonalx : function() { + if (segment == 1 || segment == 2) { + return [ [ endStubX, startStubY ]]; + } + else { + return [ [ startStubX, endStubY ]]; + } + }, + perpendicularx : function() { + var my = (ty + sy) / 2; + if ((segment == 1 && to[1] == 1) || (segment == 2 && to[1] == -1)) { + if (Math.abs(tx - sx) > Math.max(sourceStub, targetStub)) + return [ [endStubX, startStubY ]]; + else + return [ [startStubX, startStubY ], [ startStubX, my ], [ endStubX, my ]]; + } + else if ((segment == 3 && to[1] == -1) || (segment == 4 && to[1] == 1)) { + return [ [ startStubX, my ], [ endStubX, my ]]; + } + else if ((segment == 3 && to[1] == 1) || (segment == 4 && to[1] == -1)) { + return [ [ startStubX, endStubY ]]; + } + else if ((segment == 1 && to[1] == -1) || (segment == 2 && to[1] == 1)) { + if (Math.abs(tx - sx) > Math.max(sourceStub, targetStub)) + return [ [ midx, startStubY ], [ midx, endStubY ]]; + else + return [ [ startStubX, endStubY ]]; + } + }, + oppositey : function() { + if (sourceEndpoint.elementId == targetEndpoint.elementId) { + var _x = startStubX + ((1 - sourceAnchor.x) * sourceInfo.width) + Math.max(sourceStub, targetStub); + return [ [ _x, startStubY ], [ _x, endStubY ]]; + } + else if (isYGreaterThanStubTimes2 && (segment == 2 || segment == 3)) { + return [[ sx, midy ], [ tx, midy ]]; + } + else { + return [[ midx, startStubY ], [midx, endStubY ]]; + } + }, + orthogonaly : function() { + if (segment == 2 || segment == 3) { + return [ [ startStubX, endStubY ]]; + } + else { + return [ [ endStubX, startStubY ]]; + } + }, + perpendiculary : function() { + var mx = (tx + sx) / 2; + if ((segment == 2 && to[0] == -1) || (segment == 3 && to[0] == 1)) { + if (Math.abs(tx - sx) > Math.max(sourceStub, targetStub)) + return [ [startStubX, endStubY ]]; + else + return [ [startStubX, midy ], [ endStubX, midy ]]; + } + else if ((segment == 1 && to[0] == -1) || (segment == 4 && to[0] == 1)) { + var mx = (tx + sx) / 2; + return [ [ mx, startStubY ], [ mx, endStubY ]]; + } + else if ((segment == 1 && to[0] == 1) || (segment == 4 && to[0] == -1)) { + return [ [ endStubX, startStubY ]]; + } + else if ((segment == 2 && to[0] == 1) || (segment == 3 && to[0] == -1)) { + if (Math.abs(ty - sy) > Math.max(sourceStub, targetStub)) + return [ [ startStubX, midy ], [ endStubX, midy ]]; + else + return [ [ endStubX, startStubY ]]; + } + } + }; + + var p = lineCalculators[anchorOrientation + sourceAxis](); + if (p) { + for (var i = 0; i < p.length; i++) { + addSegment(p[i][0], p[i][1], sx, sy, tx, ty); + } + } + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentProportions(sx, sy, tx, ty); + + // adjust the max values of the canvas if we have a value that is larger than what we previously set. + // + if (maxY > points[3]) points[3] = maxY + (lineWidth * 2); + if (maxX > points[2]) points[2] = maxX + (lineWidth * 2); + + return points; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location, absolute) { + return self.pointAlongPathFrom(location, 0, absolute); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location, absolute) { + return segments[findSegmentForLocation(location, absolute)["index"]][4]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance, absolute) { + var s = findSegmentForLocation(location, absolute), seg = s.segment, p = s.proportion, sl = segments[s.index][5], m = segments[s.index][4]; + var e = { + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + return e; + }; + }; + + // ********************************* END OF CONNECTOR TYPES ******************************************************************* + + // ********************************* ENDPOINT TYPES ******************************************************************* + + /** + * Class: Endpoints.Dot + * A round endpoint, with default radius 10 pixels. + */ + + /** + * Function: Constructor + * + * Parameters: + * + * radius - radius of the endpoint. defaults to 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + this.type = "Dot"; + var self = this; + params = params || {}; + this.radius = params.radius || 10; + this.defaultOffset = 0.5 * this.radius; + this.defaultInnerRadius = this.radius / 3; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var r = endpointStyle.radius || self.radius, + x = anchorPoint[0] - r, + y = anchorPoint[1] - r; + return [ x, y, r * 2, r * 2, r ]; + }; + }; + + /** + * Class: Endpoints.Rectangle + * A Rectangular Endpoint, with default size 20x20. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the endpoint. defaults to 20 pixels. + * height - height of the endpoint. defaults to 20 pixels. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + this.type = "Rectangle"; + var self = this; + params = params || {}; + this.width = params.width || 20; + this.height = params.height || 20; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height]; + }; + }; + + + var DOMElementEndpoint = function(params) { + jsPlumb.DOMElementComponent.apply(this, arguments); + var self = this; + + var displayElements = [ ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + }; + /** + * Class: Endpoints.Image + * Draws an image as the Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * src - location of the image to use. + */ + jsPlumb.Endpoints.Image = function(params) { + + this.type = "Image"; + DOMElementEndpoint.apply(this, arguments); + + var self = this, + initialized = false, + widthToUse = params.width, + heightToUse = params.height, + _onload = null, + _endpoint = params.endpoint; + + this.img = new Image(); + self.ready = false; + + this.img.onload = function() { + self.ready = true; + widthToUse = widthToUse || self.img.width; + heightToUse = heightToUse || self.img.height; + if (_onload) { + _onload(self); + } + }; + + /* + Function: setImage + Sets the Image to use in this Endpoint. + + Parameters: + img - may be a URL or an Image object + onload - optional; a callback to execute once the image has loaded. + */ + _endpoint.setImage = function(img, onload) { + var s = img.constructor == String ? img : img.src; + _onload = onload; + self.img.src = img; + + if (self.canvas != null) + self.canvas.setAttribute("src", img); + }; + + _endpoint.setImage(params.src || params.url, params.onload); + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + self.anchorPoint = anchorPoint; + if (self.ready) return [anchorPoint[0] - widthToUse / 2, anchorPoint[1] - heightToUse / 2, + widthToUse, heightToUse]; + else return [0,0,0,0]; + }; + + self.canvas = document.createElement("img"), initialized = false; + self.canvas.style["margin"] = 0; + self.canvas.style["padding"] = 0; + self.canvas.style["outline"] = 0; + self.canvas.style["position"] = "absolute"; + var clazz = params.cssClass ? " " + params.cssClass : ""; + self.canvas.className = jsPlumb.endpointClass + clazz; + if (widthToUse) self.canvas.setAttribute("width", widthToUse); + if (heightToUse) self.canvas.setAttribute("height", heightToUse); + jsPlumb.appendElement(self.canvas, params.parent); + self.attachListeners(self.canvas, self); + + var actuallyPaint = function(d, style, anchor) { + if (!initialized) { + self.canvas.setAttribute("src", self.img.src); + self.appendDisplayElement(self.canvas); + initialized = true; + } + var x = self.anchorPoint[0] - (widthToUse / 2), + y = self.anchorPoint[1] - (heightToUse / 2); + jsPlumb.sizeCanvas(self.canvas, x, y, widthToUse, heightToUse); + }; + + this.paint = function(d, style, anchor) { + if (self.ready) { + actuallyPaint(d, style, anchor); + } + else { + window.setTimeout(function() { + self.paint(d, style, anchor); + }, 200); + } + }; + }; + + /** + * Class: Endpoints.Blank + * An Endpoint that paints nothing (visible) on the screen. Supports cssClass and hoverClass parameters like all Endpoints. + */ + jsPlumb.Endpoints.Blank = function(params) { + var self = this; + this.type = "Blank"; + DOMElementEndpoint.apply(this, arguments); + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + return [anchorPoint[0], anchorPoint[1],10,0]; + }; + + self.canvas = document.createElement("div"); + self.canvas.style.display = "block"; + self.canvas.style.width = "1px"; + self.canvas.style.height = "1px"; + self.canvas.style.background = "transparent"; + self.canvas.style.position = "absolute"; + self.canvas.className = self._jsPlumb.endpointClass; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + }; + }; + + /** + * Class: Endpoints.Triangle + * A triangular Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the triangle's base. defaults to 55 pixels. + * height - height of the triangle from base to apex. defaults to 55 pixels. + */ + jsPlumb.Endpoints.Triangle = function(params) { + this.type = "Triangle"; + params = params || { }; + params.width = params.width || 55; + params.height = params.height || 55; + this.width = params.width; + this.height = params.height; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height ]; + }; + }; +// ********************************* END OF ENDPOINT TYPES ******************************************************************* + + +// ********************************* OVERLAY DEFINITIONS *********************************************************************** + + var AbstractOverlay = function(params) { + var visible = true, self = this; + this.isAppendedAtTopLevel = true; + this.component = params.component; + this.loc = params.location == null ? 0.5 : params.location; + this.endpointLoc = params.endpointLocation == null ? [ 0.5, 0.5] : params.endpointLocation; + this.setVisible = function(val) { + visible = val; + self.component.repaint(); + }; + this.isVisible = function() { return visible; }; + this.hide = function() { self.setVisible(false); }; + this.show = function() { self.setVisible(true); }; + + this.incrementLocation = function(amount) { + self.loc += amount; + self.component.repaint(); + }; + this.setLocation = function(l) { + self.loc = l; + self.component.repaint(); + }; + this.getLocation = function() { + return self.loc; + }; + }; + + + /** + * Class: Overlays.Arrow + * + * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length + * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction + * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line + * across the tail. + */ + /** + * Function: Constructor + * + * Parameters: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + this.type = "Arrow"; + AbstractOverlay.apply(this, arguments); + this.isAppendedAtTopLevel = false; + params = params || {}; + var self = this; + + this.length = params.length || 20; + this.width = params.width || 20; + this.id = params.id; + var direction = (params.direction || 1) < 0 ? -1 : 1, + paintStyle = params.paintStyle || { lineWidth:1 }, + // how far along the arrow the lines folding back in come to. default is 62.3%. + foldback = params.foldback || 0.623; + + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.cleanup = function() { }; // nothing to clean up for Arrows + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var hxy, mid, txy, tail, cxy; + if (connector.pointAlongPathFrom) { + + if (jsPlumbUtil.isString(self.loc) || self.loc > 1 || self.loc < 0) { + var l = parseInt(self.loc); + hxy = connector.pointAlongPathFrom(l, direction * self.length / 2, true), + mid = connector.pointOnPath(l, true), + txy = jsPlumbUtil.pointOnLine(hxy, mid, self.length); + } + else if (self.loc == 1) { + hxy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, -1); + txy = jsPlumbUtil.pointOnLine(hxy, mid, self.length); + } + else if (self.loc == 0) { + txy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, 1); + hxy = jsPlumbUtil.pointOnLine(txy, mid, self.length); + } + else { + hxy = connector.pointAlongPathFrom(self.loc, direction * self.length / 2), + mid = connector.pointOnPath(self.loc), + txy = jsPlumbUtil.pointOnLine(hxy, mid, self.length); + } + + tail = jsPlumbUtil.perpendicularLineTo(hxy, txy, self.width); + cxy = jsPlumbUtil.pointOnLine(hxy, txy, foldback * self.length); + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x), + maxx = Math.max(hxy.x, tail[0].x, tail[1].x), + miny = Math.min(hxy.y, tail[0].y, tail[1].y), + maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + var d = { hxy:hxy, tail:tail, cxy:cxy }, + strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle, + fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle, + lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth; + + self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions); + + return [ minx, maxx, miny, maxy]; + } + else return [0,0,0,0]; + }; + }; + + /** + * Class: Overlays.PlainArrow + * + * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "PlainArrow"; + }; + + /** + * Class: Overlays.Diamond + * + * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40, + p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "Diamond"; + }; + + + + /** + * Class: Overlays.Label + * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb + * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter + * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it + * puts on the Label's 'style' attribute, so the end result is the same. + */ + /** + * Function: Constructor + * + * Parameters: + * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes + * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle. + * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * + */ + jsPlumb.Overlays.Label = function(params) { + this.type = "Label"; + jsPlumb.DOMElementComponent.apply(this, arguments); + AbstractOverlay.apply(this, arguments); + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.id = params.id; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var label = params.label || "", + self = this, + initialised = false, + div = document.createElement("div"), + labelText = null; + div.style["position"] = "absolute"; + + var clazz = params["_jsPlumb"].overlayClass + " " + + (self.labelStyle.cssClass ? self.labelStyle.cssClass : + params.cssClass ? params.cssClass : ""); + + div.className = clazz; + + jsPlumb.appendElement(div, params.component.parent); + jsPlumb.getId(div); + self.attachListeners(div, self); + self.canvas = div; + + //override setVisible + var osv = self.setVisible; + self.setVisible = function(state) { + osv(state); // call superclass + div.style.display = state ? "block" : "none"; + }; + + this.getElement = function() { + return div; + }; + + this.cleanup = function() { + if (div != null) jsPlumb.CurrentLibrary.removeElement(div); + }; + + /* + * Function: setLabel + * sets the label's, um, label. you would think i'd call this function + * 'setText', but you can pass either a Function or a String to this, so + * it makes more sense as 'setLabel'. This uses innerHTML on the label div, so keep + * that in mind if you need escaped HTML. + */ + this.setLabel = function(l) { + label = l; + labelText = null; + self.component.repaint(); + }; + + this.getLabel = function() { + return label; + }; + + this.paint = function(component, d, componentDimensions) { + if (!initialised) { + component.appendDisplayElement(div); + self.attachListeners(div, component); + initialised = true; + } + div.style.left = (componentDimensions[0] + d.minx) + "px"; + div.style.top = (componentDimensions[1] + d.miny) + "px"; + }; + + this.getTextDimensions = function() { + if (typeof label == "function") { + var lt = label(self); + div.innerHTML = lt.replace(/\r\n/g, "
"); + } + else { + if (labelText == null) { + labelText = label; + div.innerHTML = labelText.replace(/\r\n/g, "
"); + } + } + var de = jsPlumb.CurrentLibrary.getElementObject(div), + s = jsPlumb.CurrentLibrary.getSize(de); + return {width:s[0], height:s[1]}; + }; + + this.computeMaxSize = function(connector) { + var td = self.getTextDimensions(connector); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + + this.draw = function(component, currentConnectionPaintStyle, componentDimensions) { + var td = self.getTextDimensions(component); + if (td.width != null) { + var cxy = {x:0,y:0}; + if (component.pointOnPath) { + var loc = self.loc, absolute = false; + if (jsPlumbUtil.isString(self.loc) || self.loc < 0 || self.loc > 1) { + loc = parseInt(self.loc); + absolute = true; + } + cxy = component.pointOnPath(loc, absolute); // a connection + } + else { + var locToUse = self.loc.constructor == Array ? self.loc : self.endpointLoc; + cxy = { x:locToUse[0] * componentDimensions[2], + y:locToUse[1] * componentDimensions[3] }; + } + + minx = cxy.x - (td.width / 2), + miny = cxy.y - (td.height / 2); + + self.paint(component, { + minx:minx, + miny:miny, + td:td, + cxy:cxy + }, componentDimensions); + + return [minx, minx+td.width, miny, miny+td.height]; + } + else return [0,0,0,0]; + }; + + this.reattachListeners = function(connector) { + if (div) { + self.reattachListenersForElement(div, self, connector); + } + }; + }; + + // this is really just a test overlay, so its undocumented and doesnt take any parameters. but i was loth to delete it. + jsPlumb.Overlays.GuideLines = function() { + var self = this; + self.length = 50; + self.lineWidth = 5; + this.type = "GuideLines"; + AbstractOverlay.apply(this, arguments); + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var head = connector.pointAlongPathFrom(self.loc, self.length / 2), + mid = connector.pointOnPath(self.loc), + tail = jsPlumbUtil.pointOnLine(head, mid, self.length), + tailLine = jsPlumbUtil.perpendicularLineTo(head, tail, 40), + headLine = jsPlumbUtil.perpendicularLineTo(tail, head, 20); + + self.paint(connector, [head, tail, tailLine, headLine], self.lineWidth, "red", null, connectorDimensions); + + return [Math.min(head.x, tail.x), Math.min(head.y, tail.y), Math.max(head.x, tail.x), Math.max(head.y,tail.y)]; + }; + + this.computeMaxSize = function() { return 50; }; + + this.cleanup = function() { }; // nothing to clean up for GuideLines + }; + + // ********************************* END OF OVERLAY DEFINITIONS *********************************************************************** + + // ********************************* OVERLAY CANVAS RENDERERS*********************************************************************** + + // ********************************* END OF OVERLAY CANVAS RENDERERS *********************************************************************** +})(); \ No newline at end of file diff --git a/archive/1.3.10/jsPlumb-renderers-canvas-1.3.10-RC1.js b/archive/1.3.10/jsPlumb-renderers-canvas-1.3.10-RC1.js new file mode 100644 index 000000000..e011c923e --- /dev/null +++ b/archive/1.3.10/jsPlumb-renderers-canvas-1.3.10-RC1.js @@ -0,0 +1,507 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.9 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the HTML5 canvas renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + +// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS ******************************************************************* + + // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. + var _connectionBeingDragged = null, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, + _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }; + + /* + * Class:CanvasMouseAdapter + * Provides support for mouse events on canvases. + */ + var CanvasMouseAdapter = function() { + var self = this; + self.overlayPlacements = []; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + jsPlumbUtil.EventGenerator.apply(this, arguments); + /** + * returns whether or not the given event is ojver a painted area of the canvas. + */ + this._over = function(e) { + var o = _getOffset(_getElementObject(self.canvas)), + pageXY = _pageXY(e), + x = pageXY[0] - o.left, y = pageXY[1] - o.top; + if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + return false; + }; + + var _mouseover = false, _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false, + _nullSafeHasClass = function(el, clazz) { + return el != null && _hasClass(el, clazz); + }; + this.mousemove = function(e) { + var pageXY = _pageXY(e), clientXY = _clientXY(e), + ee = document.elementFromPoint(clientXY[0], clientXY[1]), + eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); + var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); + if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.fire("mouseenter", self, e); + return true; + } + // TODO here there is a remote chance that the overlay the mouse moved onto + // is actually not an overlay for the current component. a more thorough check would + // be to ensure the overlay belonged to the current component. + else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { + _mouseover = false; + self.fire("mouseexit", self, e); + } + self.fire("mousemove", self, e); + }; + + this.click = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); + self.fire("mousedown", self, e); + } + }; + + this.mouseup = function(e) { + _mouseDown = false; + self.fire("mouseup", self, e); + }; + + this.contextmenu = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("contextmenu", self, e); + _mouseWasDown = false; + }; + }; + + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + params["_jsPlumb"].appendElement(canvas, params.parent); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + params["_jsPlumb"].getId(canvas, params.uuid); + if (params.tooltip) canvas.setAttribute("title", params.tooltip); + + return canvas; + }; + + var CanvasComponent = function(params) { + CanvasMouseAdapter.apply(this, arguments); + + var displayElements = [ ]; + this.getDisplayElements = function() { return displayElements; }; + this.appendDisplayElement = function(el) { displayElements.push(el); }; + }; + + /** + * Class:CanvasConnector + * Superclass for Canvas Connector renderers. + */ + var CanvasConnector = jsPlumb.CanvasConnector = function(params) { + + CanvasComponent.apply(this, arguments); + + var _paintOneStyle = function(dim, aStyle) { + self.ctx.save(); + jsPlumb.extend(self.ctx, aStyle); + if (aStyle.gradient) { + var g = self.createGradient(dim, self.ctx); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + self.ctx.strokeStyle = g; + } + self._paint(dim, aStyle); + self.ctx.restore(); + }; + + var self = this, + clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:params.tooltip + }); + self.ctx = self.canvas.getContext("2d"); + + self.appendDisplayElement(self.canvas); + + self.paint = function(dim, style) { + if (style != null) { + jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + _paintOneStyle(dim, outlineStyle); + } + _paintOneStyle(dim, style); + } + }; + }; + + /** + * Class:CanvasEndpoint + * Superclass for Canvas Endpoint renderers. + */ + var CanvasEndpoint = function(params) { + var self = this; + CanvasComponent.apply(this, arguments); + var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""), + canvasParams = { + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:self.tooltip + }; + self.canvas = _newCanvas(canvasParams); + self.ctx = self.canvas.getContext("2d"); + + self.appendDisplayElement(self.canvas); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + } + + self._paint.apply(this, arguments); + }; + }; + + jsPlumb.Endpoints.canvas.Dot = function(params) { + jsPlumb.Endpoints.Dot.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + var self = this, + parseValue = function(value) { + try { return parseInt(value); } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }, + calculateAdjustments = function(gradient) { + var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius; + gradient.offset && (offsetAdjustment = parseValue(gradient.offset)); + gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius)); + return [offsetAdjustment, innerRadius]; + }; + this._paint = function(d, style, anchor) { + if (style != null) { + var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + if (style.gradient) { + var adjustments = calculateAdjustments(style.gradient), + yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0], + xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0], + g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + ctx.beginPath(); + ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + } + }; + }; + + jsPlumb.Endpoints.canvas.Rectangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) { + + var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + + /* canvas gradient */ + if (style.gradient) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, d[2], d[3]); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + jsPlumb.Endpoints.canvas.Triangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Triangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) + { + var width = d[2], height = d[3], x = d[0], y = d[1], + ctx = self.canvas.getContext('2d'), + offsetX = 0, offsetY = 0, angle = 0, + orientation = anchor.getOrientation(self); + + if( orientation[0] == 1 ) { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = style.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + /* + * Canvas Image Endpoint: uses the default version, which creates an tag. + */ + jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image; + + /* + * Blank endpoint in all renderers is just the default Blank endpoint. + */ + jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank; + + /* + * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Bezier = function() { + var self = this; + jsPlumb.Connectors.Bezier.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions, style) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO i doubt this handles the case that source and target are swapped. + this.createGradient = function(dim, ctx, swap) { + return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + /* + * Canvas straight line Connector. Draws a straight line onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Straight = function() { + var self = this, + segmentMultipliers = [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ]; + + jsPlumb.Connectors.Straight.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions, style) { + + self.ctx.beginPath(); + + if (style.dashstyle && style.dashstyle.split(" ").length == 2) { + // only a very simple dashed style is supported - having two values, which define the stroke length + // (as a multiple of the stroke width) and then the space length (also as a multiple of stroke width). + var ds = style.dashstyle.split(" "); + if (ds.length != 2) ds = [2, 2]; + var dss = [ ds[0] * style.lineWidth, ds[1] * style.lineWidth ], + m = (dimensions[6] - dimensions[4]) / (dimensions[7] - dimensions[5]), + s = jsPlumbUtil.segment([dimensions[4], dimensions[5]], [ dimensions[6], dimensions[7] ]), + sm = segmentMultipliers[s], + theta = Math.atan(m), + l = Math.sqrt(Math.pow(dimensions[6] - dimensions[4], 2) + Math.pow(dimensions[7] - dimensions[5], 2)), + repeats = Math.floor(l / (dss[0] + dss[1])), + curPos = [dimensions[4], dimensions[5]]; + + + // TODO: the question here is why could we not support this in all connector types? it's really + // just a case of going along and asking jsPlumb for the next point on the path a few times, until it + // reaches the end. every type of connector supports that method, after all. but right now its only the + // bezier connector that gives you back the new location on the path along with the x,y coordinates, which + // we would need. we'd start out at loc=0 and ask for the point along the path that is dss[0] pixels away. + // we then ask for the point that is (dss[0] + dss[1]) pixels away; and from that one we need not just the + // x,y but the location, cos we're gonna plug that location back in in order to find where that dash ends. + // + // it also strikes me that it should be trivial to support arbitrary dash styles (having more or less than two + // entries). you'd just iterate that array using a step size of 2, and generify the (rss[0] + rss[1]) + // computation to be sum(rss[0]..rss[n]). + + for (var i = 0; i < repeats; i++) { + self.ctx.moveTo(curPos[0], curPos[1]); + + var nextEndX = curPos[0] + (Math.abs(Math.sin(theta) * dss[0]) * sm[0]), + nextEndY = curPos[1] + (Math.abs(Math.cos(theta) * dss[0]) * sm[1]), + nextStartX = curPos[0] + (Math.abs(Math.sin(theta) * (dss[0] + dss[1])) * sm[0]), + nextStartY = curPos[1] + (Math.abs(Math.cos(theta) * (dss[0] + dss[1])) * sm[1]) + + self.ctx.lineTo(nextEndX, nextEndY); + curPos = [nextStartX, nextStartY]; + } + + // now draw the last bit + self.ctx.moveTo(curPos[0], curPos[1]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + + } + else { + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + } + + self.ctx.stroke(); + }; + + // TODO this does not handle the case that src and target are swapped. + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + jsPlumb.Connectors.canvas.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions, style) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + +// ********************************* END OF CANVAS RENDERERS ******************************************************************* + + jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label; + + /** + * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. + */ + var CanvasOverlay = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }; + + var AbstractCanvasArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + CanvasOverlay.apply(this, originalArgs); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + var ctx = connector.ctx; + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(d.hxy.x, d.hxy.y); + ctx.lineTo(d.tail[0].x, d.tail[0].y); + ctx.lineTo(d.cxy.x, d.cxy.y); + ctx.lineTo(d.tail[1].x, d.tail[1].y); + ctx.lineTo(d.hxy.x, d.hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + }; + + jsPlumb.Overlays.canvas.Arrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.canvas.PlainArrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.canvas.Diamond = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.10/jsPlumb-renderers-svg-1.3.10-RC1.js b/archive/1.3.10/jsPlumb-renderers-svg-1.3.10-RC1.js new file mode 100644 index 000000000..fb538370d --- /dev/null +++ b/archive/1.3.10/jsPlumb-renderers-svg-1.3.10-RC1.js @@ -0,0 +1,550 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.9 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the SVG renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +/** + * SVG support for jsPlumb. + * + * things to investigate: + * + * gradients: https://developer.mozilla.org/en/svg_in_html_introduction + * css:http://tutorials.jenkov.com/svg/svg-and-css.html + * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath + * pointer events: https://developer.mozilla.org/en/css/pointer-events + * + * IE9 hover jquery: http://forum.jquery.com/topic/1-6-2-broke-svg-hover-events + * + */ +;(function() { + + var svgAttributeMap = { + "joinstyle":"stroke-linejoin", + "stroke-linejoin":"stroke-linejoin", + "stroke-dashoffset":"stroke-dashoffset", + "stroke-linecap":"stroke-linecap" + }, + STROKE_DASHARRAY = "stroke-dasharray", + DASHSTYLE = "dashstyle", + LINEAR_GRADIENT = "linearGradient", + RADIAL_GRADIENT = "radialGradient", + FILL = "fill", + STOP = "stop", + STROKE = "stroke", + STROKE_WIDTH = "stroke-width", + STYLE = "style", + NONE = "none", + JSPLUMB_GRADIENT = "jsplumb_gradient_", + LINE_WIDTH = "lineWidth", + ns = { + svg:"http://www.w3.org/2000/svg", + xhtml:"http://www.w3.org/1999/xhtml" + }, + _attr = function(node, attributes) { + for (var i in attributes) + node.setAttribute(i, "" + attributes[i]); + }, + _node = function(name, attributes) { + var n = document.createElementNS(ns.svg, name); + attributes = attributes || {}; + attributes["version"] = "1.1"; + attributes["xmlns"] = ns.xhtml; + _attr(n, attributes); + return n; + }, + _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; }, + _clearGradient = function(parent) { + for (var i = 0; i < parent.childNodes.length; i++) { + if (parent.childNodes[i].tagName == LINEAR_GRADIENT || parent.childNodes[i].tagName == RADIAL_GRADIENT) + parent.removeChild(parent.childNodes[i]); + } + }, + _updateGradient = function(parent, node, style, dimensions, uiComponent) { + var id = JSPLUMB_GRADIENT + uiComponent._jsPlumb.idstamp(); + // first clear out any existing gradient + _clearGradient(parent); + // this checks for an 'offset' property in the gradient, and in the absence of it, assumes + // we want a linear gradient. if it's there, we create a radial gradient. + // it is possible that a more explicit means of defining the gradient type would be + // better. relying on 'offset' means that we can never have a radial gradient that uses + // some default offset, for instance. + // issue 244 suggested the 'gradientUnits' attribute; without this, straight/flowchart connectors with gradients would + // not show gradients when the line was perfectly horizontal or vertical. + if (!style.gradient.offset) { + var g = _node(LINEAR_GRADIENT, {id:id, gradientUnits:"userSpaceOnUse"}); + parent.appendChild(g); + } + else { + var g = _node(RADIAL_GRADIENT, { + id:id + }); + parent.appendChild(g); + } + + // the svg radial gradient seems to treat stops in the reverse + // order to how canvas does it. so we want to keep all the maths the same, but + // iterate the actual style declarations in reverse order, if the x indexes are not in order. + for (var i = 0; i < style.gradient.stops.length; i++) { + // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of + // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments. + // so, not too concerned about leaving this in for now. + var styleToUse = i; + if (dimensions.length == 8) + styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i; + else + styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i; + var stopColor = jsPlumbUtil.convertStyle(style.gradient.stops[styleToUse][1], true); + var s = _node(STOP, {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor}); + g.appendChild(s); + } + var applyGradientTo = style.strokeStyle ? STROKE : FILL; + node.setAttribute(STYLE, applyGradientTo + ":url(#" + id + ")"); + }, + _applyStyles = function(parent, node, style, dimensions, uiComponent) { + + if (style.gradient) { + _updateGradient(parent, node, style, dimensions, uiComponent); + } + else { + // make sure we clear any existing gradient + _clearGradient(parent); + node.setAttribute(STYLE, ""); + } + + node.setAttribute(FILL, style.fillStyle ? jsPlumbUtil.convertStyle(style.fillStyle, true) : NONE); + node.setAttribute(STROKE, style.strokeStyle ? jsPlumbUtil.convertStyle(style.strokeStyle, true) : NONE); + if (style.lineWidth) { + node.setAttribute(STROKE_WIDTH, style.lineWidth); + } + + // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like + // the syntax in VML but is actually kind of nasty: values are given in the pixel + // coordinate space, whereas in VML they are multiples of the width of the stroked + // line, which makes a lot more sense. for that reason, jsPlumb is supporting both + // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from + // VML, which will be the preferred method. the code below this converts a dashstyle + // attribute given in terms of stroke width into a pixel representation, by using the + // stroke's lineWidth. + if (style[DASHSTYLE] && style[LINE_WIDTH] && !style[STROKE_DASHARRAY]) { + var sep = style[DASHSTYLE].indexOf(",") == -1 ? " " : ",", + parts = style[DASHSTYLE].split(sep), + styleToUse = ""; + parts.forEach(function(p) { + styleToUse += (Math.floor(p * style.lineWidth) + sep); + }); + node.setAttribute(STROKE_DASHARRAY, styleToUse); + } + else if(style[STROKE_DASHARRAY]) { + node.setAttribute(STROKE_DASHARRAY, style[STROKE_DASHARRAY]); + } + + // extra attributes such as join type, dash offset. + for (var i in svgAttributeMap) { + if (style[i]) { + node.setAttribute(svgAttributeMap[i], style[i]); + } + } + }, + _decodeFont = function(f) { + var r = /([0-9].)(p[xt])\s(.*)/; + var bits = f.match(r); + return {size:bits[1] + bits[2], font:bits[3]}; + }, + _classManip = function(el, add, clazz) { + var classesToAddOrRemove = clazz.split(" "), + className = el.className, + curClasses = className.baseVal.split(" "); + + for (var i = 0; i < classesToAddOrRemove.length; i++) { + if (add) { + if (curClasses.indexOf(classesToAddOrRemove[i]) == -1) + curClasses.push(classesToAddOrRemove[i]); + } + else { + var idx = curClasses.indexOf(classesToAddOrRemove[i]); + if (idx != -1) + curClasses.splice(idx, 1); + } + } + + el.className.baseVal = curClasses.join(" "); + }, + _addClass = function(el, clazz) { + _classManip(el, true, clazz); + }, + _removeClass = function(el, clazz) { + _classManip(el, false, clazz); + }; + + /** + utility methods for other objects to use. + */ + jsPlumbUtil.svg = { + addClass:_addClass, + removeClass:_removeClass, + node:_node, + attr:_attr, + pos:_pos + }; + + /* + * Base class for SVG components. + */ + //var SvgComponent = function(cssClass, originalArgs, pointerEventsSpec) { + var SvgComponent = function(params) { + var self = this, + pointerEventsSpec = params.pointerEventsSpec || "all"; + jsPlumb.jsPlumbUIComponent.apply(this, params.originalArgs); + self.canvas = null, self.path = null, self.svg = null; + + var clazz = params.cssClass + " " + (params.originalArgs[0].cssClass || ""), + svgParams = { + "style":"", + "width":0, + "height":0, + "pointer-events":pointerEventsSpec, + "position":"absolute" + }; + if (self.tooltip) svgParams["title"] = self.tooltip; + self.svg = _node("svg", svgParams); + if (params.useDivWrapper) { + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.sizeCanvas(self.canvas,0,0,1,1); + self.canvas.className = clazz; + if (self.tooltip) self.canvas.setAttribute("title", self.tooltip); + } + else { + _attr(self.svg, { "class":clazz }); + self.canvas = self.svg; + } + + params._jsPlumb.appendElement(self.canvas, params.originalArgs[0]["parent"]); + if (params.useDivWrapper) self.canvas.appendChild(self.svg); + + // TODO this displayElement stuff is common between all components, across all + // renderers. would be best moved to jsPlumbUIComponent. + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + this.paint = function(d, style, anchor) { + if (style != null) { + var x = d[0], y = d[1]; + if (params.useDivWrapper) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + x = 0, y = 0; + } + _attr(self.svg, { + "style":_pos([x, y, d[2], d[3]]), + "width": d[2], + "height": d[3] + }); + self._paint.apply(this, arguments); + } + }; + }; + + /* + * Base class for SVG connectors. + */ + var SvgConnector = jsPlumb.SvgConnector = function(params) { + var self = this; + SvgComponent.apply(this, [ { + cssClass:params["_jsPlumb"].connectorClass, + originalArgs:arguments, + pointerEventsSpec:"none", + tooltip:params.tooltip, + _jsPlumb:params["_jsPlumb"] + } ]); + this._paint = function(d, style) { + var p = self.getPath(d), a = { "d":p }, outlineStyle = null; + a["pointer-events"] = "all"; + + // outline style. actually means drawing an svg object underneath the main one. + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = jsPlumb.CurrentLibrary.extend({}, style); + outlineStyle.strokeStyle = jsPlumbUtil.convertStyle(style.outlineColor); + outlineStyle.lineWidth = outlineStrokeWidth; + + if (self.bgPath == null) { + self.bgPath = _node("path", a); + self.svg.appendChild(self.bgPath); + self.attachListeners(self.bgPath, self); + } + else { + _attr(self.bgPath, a); + } + + _applyStyles(self.svg, self.bgPath, outlineStyle, d, self); + } + + + // test - see below + // a["clip-path"]= "url(#testClip)"; + + if (self.path == null) { + self.path = _node("path", a); + self.svg.appendChild(self.path); + self.attachListeners(self.path, self); + + /* + this is a test of a clip path. i'm looking into using one of these to animate a jsplumb connection. + you could do this by walking along the line, stepping along a little at a time, and setting the clip + path to extend as far as that point. + + self.clip = _node("clipPath", {id:"testClip", clipPathUnits:"objectBoundingBox"}); + self.svg.appendChild(self.clip); + self.clip.appendChild(_node("rect", { + x:"0",y:"0",width:"0.5",height:"1" + })); + */ + } + else { + _attr(self.path, a); + } + + _applyStyles(self.svg, self.path, style, d, self); + }; + + this.reattachListeners = function() { + if (self.bgPath) self.reattachListenersForElement(self.bgPath, self); + if (self.path) self.reattachListenersForElement(self.path, self); + }; + + }; + + /* + * SVG Bezier Connector + */ + jsPlumb.Connectors.svg.Bezier = function(params) { + jsPlumb.Connectors.Bezier.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { + var _p = "M " + d[4] + " " + d[5]; + _p += (" C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]); + return _p; + }; + }; + + /* + * SVG straight line Connector + */ + jsPlumb.Connectors.svg.Straight = function(params) { + jsPlumb.Connectors.Straight.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; }; + }; + + jsPlumb.Connectors.svg.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "M " + dimensions[4] + "," + dimensions[5]; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)]; + } + // finally draw a line to the end + p = p + " " + dimensions[6] + "," + dimensions[7]; + return p; + }; + }; + + /* + * Base class for SVG endpoints. + */ + var SvgEndpoint = window.SvgEndpoint = function(params) { + var self = this; + SvgComponent.apply(this, [ { + cssClass:params["_jsPlumb"].endpointClass, + originalArgs:arguments, + pointerEventsSpec:"all", + useDivWrapper:true, + _jsPlumb:params["_jsPlumb"] + } ]); + this._paint = function(d, style) { + var s = jsPlumb.extend({}, style); + if (s.outlineColor) { + s.strokeWidth = s.outlineWidth; + s.strokeStyle = jsPlumbUtil.convertStyle(s.outlineColor, true); + } + + if (self.node == null) { + self.node = self.makeNode(d, s); + self.svg.appendChild(self.node); + self.attachListeners(self.node, self); + } + _applyStyles(self.svg, self.node, s, d, self); + _pos(self.node, d); + }; + + this.reattachListeners = function() { + if (self.node) self.reattachListenersForElement(self.node, self); + }; + }; + + /* + * SVG Dot Endpoint + */ + jsPlumb.Endpoints.svg.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("circle", { + "cx" : d[2] / 2, + "cy" : d[3] / 2, + "r" : d[2] / 2 + }); + }; + }; + + /* + * SVG Rectangle Endpoint + */ + jsPlumb.Endpoints.svg.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("rect", { + "width":d[2], + "height":d[3] + }); + }; + }; + + /* + * SVG Image Endpoint is the default image endpoint. + */ + jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image; + /* + * Blank endpoint in svg renderer is the default Blank endpoint. + */ + jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank; + /* + * Label endpoint in svg renderer is the default Label endpoint. + */ + jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label; + + var AbstractSvgArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + this.isAppendedAtTopLevel = false; + var self = this, path = null; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path", { + "pointer-events":"all" + }); + connector.svg.appendChild(path); + + self.attachListeners(path, connector); + self.attachListeners(path, self); + } + var clazz = originalArgs && (originalArgs.length == 1) ? (originalArgs[0].cssClass || "") : ""; + + _attr(path, { + "d" : makePath(d), + "class" : clazz, + stroke : strokeStyle ? strokeStyle : null, + fill : fillStyle ? fillStyle : null + }); + }; + var makePath = function(d) { + return "M" + d.hxy.x + "," + d.hxy.y + + " L" + d.tail[0].x + "," + d.tail[0].y + + " L" + d.cxy.x + "," + d.cxy.y + + " L" + d.tail[1].x + "," + d.tail[1].y + + " L" + d.hxy.x + "," + d.hxy.y; + }; + this.reattachListeners = function() { + if (path) self.reattachListenersForElement(path, self); + }; + this.cleanup = function() { + if (path != null) jsPlumb.CurrentLibrary.removeElement(path); + }; + }; + + jsPlumb.Overlays.svg.Arrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.svg.PlainArrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.svg.Diamond = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; + + // a test + jsPlumb.Overlays.svg.GuideLines = function() { + var path = null, self = this, path2 = null, p1_1, p1_2; + jsPlumb.Overlays.GuideLines.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + + p1_1 = _node("path"); + connector.svg.appendChild(p1_1); + self.attachListeners(p1_1, connector); + self.attachListeners(p1_1, self); + + p1_2 = _node("path"); + connector.svg.appendChild(p1_2); + self.attachListeners(p1_2, connector); + self.attachListeners(p1_2, self); + + } + + _attr(path, { + "d" : makePath(d[0], d[1]), + stroke : "red", + fill : null + }); + + _attr(p1_1, { + "d" : makePath(d[2][0], d[2][1]), + stroke : "blue", + fill : null + }); + + _attr(p1_2, { + "d" : makePath(d[3][0], d[3][1]), + stroke : "green", + fill : null + }); + }; + + var makePath = function(d1, d2) { + return "M " + d1.x + "," + d1.y + + " L" + d2.x + "," + d2.y; + }; + + }; +})(); \ No newline at end of file diff --git a/archive/1.3.10/jsPlumb-renderers-vml-1.3.10-RC1.js b/archive/1.3.10/jsPlumb-renderers-vml-1.3.10-RC1.js new file mode 100644 index 000000000..b7c913895 --- /dev/null +++ b/archive/1.3.10/jsPlumb-renderers-vml-1.3.10-RC1.js @@ -0,0 +1,445 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.10 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the VML renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + // http://ajaxian.com/archives/the-vml-changes-in-ie-8 + // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/ + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + + var vmlAttributeMap = { + "stroke-linejoin":"joinstyle", + "joinstyle":"joinstyle", + "endcap":"endcap", + "miterlimit":"miterlimit" + }, + jsPlumbStylesheet = null; + + if (document.createStyleSheet && document.namespaces) { + + var ruleClasses = [ + ".jsplumb_vml", "jsplumb\\:textbox", "jsplumb\\:oval", "jsplumb\\:rect", + "jsplumb\\:stroke", "jsplumb\\:shape", "jsplumb\\:group" + ], + rule = "behavior:url(#default#VML);position:absolute;"; + + jsPlumbStylesheet = document.createStyleSheet(); + + for (var i = 0; i < ruleClasses.length; i++) + jsPlumbStylesheet.addRule(ruleClasses[i], rule); + + // in this page it is also mentioned that IE requires the extra arg to the namespace + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either. + // var iev = document.documentMode; + //if (!iev || iev < 8) + document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); + //else + // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML"); + } + + jsPlumb.vml = {}; + + var scale = 1000, + + _groupMap = {}, + _getGroup = function(container, connectorClass) { + var id = jsPlumb.getId(container), + g = _groupMap[id]; + if(!g) { + g = _node("group", [0,0,scale, scale], {"class":connectorClass}); + //g.style.position=absolute; + //g["coordsize"] = "1000,1000"; + g.style.backgroundColor="red"; + _groupMap[id] = g; + jsPlumb.appendElement(g, container); // todo if this gets reinstated, remember to use the current jsplumb instance. + //document.body.appendChild(g); + } + return g; + }, + _atts = function(o, atts) { + for (var i in atts) { + // IE8 fix: setattribute does not work after an element has been added to the dom! + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + //o.setAttribute(i, atts[i]); + + /*There is an additional problem when accessing VML elements by using get/setAttribute. The simple solution is following: + + if (document.documentMode==8) { + ele.opacity=1; + } else { + ele.setAttribute(‘opacity’,1); + } + */ + + o[i] = atts[i]; + } + }, + _node = function(name, d, atts, parent, _jsPlumb) { + atts = atts || {}; + var o = document.createElement("jsplumb:" + name); + _jsPlumb.appendElement(o, parent); + o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml"; + _pos(o, d); + _atts(o, atts); + return o; + }, + _pos = function(o,d) { + o.style.left = d[0] + "px"; + o.style.top = d[1] + "px"; + o.style.width= d[2] + "px"; + o.style.height= d[3] + "px"; + o.style.position = "absolute"; + }, + _conv = jsPlumb.vml.convertValue = function(v) { + return Math.floor(v * scale); + }, + // tests if the given style is "transparent" and then sets the appropriate opacity node to 0 if so, + // or 1 if not. TODO in the future, support variable opacity. + _maybeSetOpacity = function(styleToWrite, styleToCheck, type, component) { + if ("transparent" === styleToCheck) + component.setOpacity(type, "0.0"); + else + component.setOpacity(type, "1.0"); + }, + _applyStyles = function(node, style, component, _jsPlumb) { + var styleToWrite = {}; + if (style.strokeStyle) { + styleToWrite["stroked"] = "true"; + var strokeColor = jsPlumbUtil.convertStyle(style.strokeStyle, true); + styleToWrite["strokecolor"] = strokeColor; + _maybeSetOpacity(styleToWrite, strokeColor, "stroke", component); + styleToWrite["strokeweight"] = style.lineWidth + "px"; + } + else styleToWrite["stroked"] = "false"; + + if (style.fillStyle) { + styleToWrite["filled"] = "true"; + var fillColor = jsPlumbUtil.convertStyle(style.fillStyle, true); + styleToWrite["fillcolor"] = fillColor; + _maybeSetOpacity(styleToWrite, fillColor, "fill", component); + } + else styleToWrite["filled"] = "false"; + + if(style["dashstyle"]) { + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }, node, _jsPlumb); + } + else + component.strokeNode.dashstyle = style["dashstyle"]; + } + else if (style["stroke-dasharray"] && style["lineWidth"]) { + var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",", + parts = style["stroke-dasharray"].split(sep), + styleToUse = ""; + for(var i = 0; i < parts.length; i++) { + styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep); + } + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }, node, _jsPlumb); + //node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = styleToUse; + } + + _atts(node, styleToWrite); + }, + /* + * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. + */ + VmlComponent = function() { + var self = this; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.opacityNodes = { + "stroke":null, + "fill":null + }; + this.initOpacityNodes = function(vml) { + self.opacityNodes["stroke"] = _node("stroke", [0,0,1,1], {opacity:"0.0"}, vml, self._jsPlumb); + self.opacityNodes["fill"] = _node("fill", [0,0,1,1], {opacity:"0.0"}, vml, self._jsPlumb); + }; + this.setOpacity = function(type, value) { + var node = self.opacityNodes[type]; + if (node) node["opacity"] = "" + value; + }; + var displayElements = [ ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el, doNotAppendToCanvas) { + if (!doNotAppendToCanvas) self.canvas.parentNode.appendChild(el); + displayElements.push(el); + }; + }, + /* + * Base class for Vml connectors. extends VmlComponent. + */ + VmlConnector = jsPlumb.VmlConnector = function(params) { + var self = this; + self.strokeNode = null; + self.canvas = null; + VmlComponent.apply(this, arguments); + var clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : ""); + this.paint = function(d, style, anchor) { + if (style != null) { + var path = self.getPath(d), p = { "path":path }; + + //* + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle : jsPlumbUtil.convertStyle(style.outlineColor), + lineWidth : outlineStrokeWidth + }; + for (var aa in vmlAttributeMap) outlineStyle[aa] = style[aa]; + + if (self.bgCanvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.bgCanvas = _node("shape", d, p, params.parent, self._jsPlumb); + _pos(self.bgCanvas, d); + self.appendDisplayElement(self.bgCanvas, true); + self.attachListeners(self.bgCanvas, self); + self.initOpacityNodes(self.bgCanvas, ["stroke"]); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.bgCanvas, d); + _atts(self.bgCanvas, p); + } + + _applyStyles(self.bgCanvas, outlineStyle, self); + } + //*/ + + if (self.canvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + if (self.tooltip) p["label"] = self.tooltip; + self.canvas = _node("shape", d, p, params.parent, self._jsPlumb); + //var group = _getGroup(params.parent); // test of append everything to a group + //group.appendChild(self.canvas); // sort of works but not exactly; + //params["_jsPlumb"].appendElement(self.canvas, params.parent); //before introduction of groups + + self.appendDisplayElement(self.canvas, true); + self.attachListeners(self.canvas, self); + self.initOpacityNodes(self.canvas, ["stroke"]); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.canvas, d); + _atts(self.canvas, p); + } + + _applyStyles(self.canvas, style, self, self._jsPlumb); + } + }; + + //self.appendDisplayElement(self.canvas); + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + }, + /* + * + * Base class for Vml Endpoints. extends VmlComponent. + * + */ + VmlEndpoint = window.VmlEndpoint = function(params) { + VmlComponent.apply(this, arguments); + var vml = null, self = this, opacityStrokeNode = null, opacityFillNode = null; + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + + var clazz = self._jsPlumb.endpointClass + (params.cssClass ? (" " + params.cssClass) : ""); + + //var group = _getGroup(params.parent); + //group.appendChild(self.canvas); + params["_jsPlumb"].appendElement(self.canvas, params.parent); + + if (self.tooltip) self.canvas.setAttribute("label", self.tooltip); + + this.paint = function(d, style, anchor) { + var p = { }; + + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (vml == null) { + p["class"] = clazz; + vml = self.getVml([0,0, d[2], d[3]], p, anchor, self.canvas, self._jsPlumb); + self.attachListeners(vml, self); + + self.appendDisplayElement(vml, true); + self.appendDisplayElement(self.canvas, true); + + self.initOpacityNodes(vml, ["fill"]); + } + else { + _pos(vml, [0,0, d[2], d[3]]); + _atts(vml, p); + } + + _applyStyles(vml, style, self); + }; + + this.reattachListeners = function() { + if (vml) self.reattachListenersForElement(vml, self); + }; + }; + + jsPlumb.Connectors.vml.Bezier = function() { + jsPlumb.Connectors.Bezier.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Straight = function() { + jsPlumb.Connectors.Straight.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Flowchart = function() { + jsPlumb.Connectors.Flowchart.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l"; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]); + } + // finally draw a line to the end + p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e"; + return p; + }; + }; + + jsPlumb.Endpoints.vml.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor, parent, _jsPlumb) { return _node("oval", d, atts, parent, _jsPlumb); }; + }; + + jsPlumb.Endpoints.vml.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor, parent, _jsPlumb) { return _node("rect", d, atts, parent, _jsPlumb); }; + }; + + /* + * VML Image Endpoint is the same as the default image endpoint. + */ + jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image; + + /** + * placeholder for Blank endpoint in vml renderer. + */ + jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank; + + /** + * VML Label renderer. uses the default label renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label; + + var AbstractVmlArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + VmlComponent.apply(this, originalArgs); + var self = this, path = null; + self.canvas = null; + self.isAppendedAtTopLevel = true; + var getPath = function(d, connectorDimensions) { + return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) + + " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + + " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + + " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + + " x e"; + }; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) { + var p = {}; + if (strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] = jsPlumbUtil.convertStyle(strokeStyle, true); + } + if (lineWidth) p["strokeweight"] = lineWidth + "px"; + if (fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = fillStyle; + } + var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + w = Math.abs(xmax - xmin), + h = Math.abs(ymax - ymin), + dim = [xmin, ymin, w, h]; + + // for VML, we create overlays using shapes that have the same dimensions and + // coordsize as their connector - overlays calculate themselves relative to the + // connector (it's how it's been done since the original canvas implementation, because + // for canvas that makes sense). + p["path"] = getPath(d, connectorDimensions); + p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale); + + dim[0] = connectorDimensions[0]; + dim[1] = connectorDimensions[1]; + dim[2] = connectorDimensions[2]; + dim[3] = connectorDimensions[3]; + + if (self.canvas == null) { + //p["class"] = jsPlumb.overlayClass; // TODO currentInstance? + self.canvas = _node("shape", dim, p, connector.canvas.parentNode, connector._jsPlumb); + connector.appendDisplayElement(self.canvas, true); + self.attachListeners(self.canvas, connector); + self.attachListeners(self.canvas, self); + } + else { + _pos(self.canvas, dim); + _atts(self.canvas, p); + } + }; + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + + this.cleanup = function() { + if (self.canvas != null) jsPlumb.CurrentLibrary.removeElement(self.canvas); + }; + }; + + jsPlumb.Overlays.vml.Arrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.vml.PlainArrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.vml.Diamond = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.10/jsPlumb-util-1.3.10-RC1.js b/archive/1.3.10/jsPlumb-util-1.3.10-RC1.js new file mode 100644 index 000000000..1bec54540 --- /dev/null +++ b/archive/1.3.10/jsPlumb-util-1.3.10-RC1.js @@ -0,0 +1,221 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.9 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the util functions + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ +jsPlumbUtil = { + isArray : function(a) { + return Object.prototype.toString.call(a) === "[object Array]"; + }, + isString : function(s) { + return typeof s === "string"; + }, + isObject : function(o) { + return Object.prototype.toString.call(o) === "[object Object]"; + }, + convertStyle : function(s, ignoreAlpha) { + // TODO: jsPlumb should support a separate 'opacity' style member. + if ("transparent" === s) return s; + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + return o; + }, + gradient : function(p1, p2) { + p1 = jsPlumbUtil.isArray(p1) ? p1 : [p1.x, p1.y]; + p2 = jsPlumbUtil.isArray(p2) ? p2 : [p2.x, p2.y]; + return (p2[1] - p1[1]) / (p2[0] - p1[0]); + }, + normal : function(p1, p2) { + return -1 / jsPlumbUtil.gradient(p1,p2); + }, + lineLength : function(p1, p2) { + p1 = jsPlumbUtil.isArray(p1) ? p1 : [p1.x, p1.y]; + p2 = jsPlumbUtil.isArray(p2) ? p2 : [p2.x, p2.y]; + return Math.sqrt(Math.pow(p2[1] - p1[1], 2) + Math.pow(p2[0] - p1[0], 2)); + }, + segment : function(p1, p2) { + p1 = jsPlumbUtil.isArray(p1) ? p1 : [p1.x, p1.y]; + p2 = jsPlumbUtil.isArray(p2) ? p2 : [p2.x, p2.y]; + if (p2[0] > p1[0]) { + return (p2[1] > p1[1]) ? 2 : 1; + } + else { + return (p2[1] > p1[1]) ? 3 : 4; + } + }, + intersects : function(r1, r2) { + var x1 = r1.x, x2 = r1.x + r1.w, y1 = r1.y, y2 = r1.y + r1.h, + a1 = r2.x, a2 = r2.x + r2.w, b1 = r2.y, b2 = r2.y + r2.h; + + return ( (x1 <= a1 && a1 <= x2) && (y1 <= b1 && b1 <= y2) ) || + ( (x1 <= a2 && a2 <= x2) && (y1 <= b1 && b1 <= y2) ) || + ( (x1 <= a1 && a1 <= x2) && (y1 <= b2 && b2 <= y2) ) || + ( (x1 <= a2 && a1 <= x2) && (y1 <= b2 && b2 <= y2) ) || + + ( (a1 <= x1 && x1 <= a2) && (b1 <= y1 && y1 <= b2) ) || + ( (a1 <= x2 && x2 <= a2) && (b1 <= y1 && y1 <= b2) ) || + ( (a1 <= x1 && x1 <= a2) && (b1 <= y2 && y2 <= b2) ) || + ( (a1 <= x2 && x1 <= a2) && (b1 <= y2 && y2 <= b2) ); + }, + segmentMultipliers : [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ], + inverseSegmentMultipliers : [null, [-1, -1], [-1, 1], [1, 1], [1, -1] ], + pointOnLine : function(fromPoint, toPoint, distance) { + var m = jsPlumbUtil.gradient(fromPoint, toPoint), + s = jsPlumbUtil.segment(fromPoint, toPoint), + segmentMultiplier = distance > 0 ? jsPlumbUtil.segmentMultipliers[s] : jsPlumbUtil.inverseSegmentMultipliers[s], + theta = Math.atan(m), + y = Math.abs(distance * Math.sin(theta)) * segmentMultiplier[1], + x = Math.abs(distance * Math.cos(theta)) * segmentMultiplier[0]; + return { x:fromPoint.x + x, y:fromPoint.y + y }; + }, + /** + * calculates a perpendicular to the line fromPoint->toPoint, that passes through toPoint and is 'length' long. + * @param fromPoint + * @param toPoint + * @param length + */ + perpendicularLineTo : function(fromPoint, toPoint, length) { + var m = jsPlumbUtil.gradient(fromPoint, toPoint), + theta2 = Math.atan(-1 / m), + y = length / 2 * Math.sin(theta2), + x = length / 2 * Math.cos(theta2); + return [{x:toPoint.x + x, y:toPoint.y + y}, {x:toPoint.x - x, y:toPoint.y - y}]; + }, + findWithFunction : function(a, f) { + if (a) + for (var i = 0; i < a.length; i++) if (f(a[i])) return i; + return -1; + }, + indexOf : function(l, v) { + return jsPlumbUtil.findWithFunction(l, function(_v) { return _v == v; }); + }, + removeWithFunction : function(a, f) { + var idx = jsPlumbUtil.findWithFunction(a, f); + if (idx > -1) a.splice(idx, 1); + return idx != -1; + }, + remove : function(l, v) { + var idx = jsPlumbUtil.indexOf(l, v); + if (idx > -1) l.splice(idx, 1); + return idx != -1; + }, + // TODO support insert index + addWithFunction : function(list, item, hashFunction) { + if (jsPlumbUtil.findWithFunction(list, hashFunction) == -1) list.push(item); + }, + addToList : function(map, key, value) { + var l = map[key]; + if (l == null) { + l = [], map[key] = l; + } + l.push(value); + return l; + }, + /** + * EventGenerator + * Superclass for objects that generate events - jsPlumb extends this, as does jsPlumbUIComponent, which all the UI elements extend. + */ + EventGenerator : function() { + var _listeners = {}, self = this; + + // this is a list of events that should re-throw any errors that occur during their dispatch. as of 1.3.0 this is private to + // jsPlumb, but it seems feasible that people might want to manipulate this list. the thinking is that we don't want event + // listeners to bring down jsPlumb - or do we. i can't make up my mind about this, but i know i want to hear about it if the "ready" + // event fails, because then my page has most likely not initialised. so i have this halfway-house solution. it will be interesting + // to hear what other people think. + var eventsToDieOn = [ "ready" ]; + + /* + * Binds a listener to an event. + * + * Parameters: + * event - name of the event to bind to. + * listener - function to execute. + */ + this.bind = function(event, listener) { + jsPlumbUtil.addToList(_listeners, event, listener); + return self; + }; + /* + * Fires an update for the given event. + * + * Parameters: + * event - event to fire + * value - value to pass to the event listener(s). + * originalEvent - the original event from the browser + */ + this.fire = function(event, value, originalEvent) { + if (_listeners[event]) { + for ( var i = 0; i < _listeners[event].length; i++) { + // doing it this way rather than catching and then possibly re-throwing means that an error propagated by this + // method will have the whole call stack available in the debugger. + if (jsPlumbUtil.findWithFunction(eventsToDieOn, function(e) { return e === event}) != -1) + _listeners[event][i](value, originalEvent); + else { + // for events we don't want to die on, catch and log. + try { + _listeners[event][i](value, originalEvent); + } catch (e) { + jsPlumbUtil.log("jsPlumb: fire failed for event " + event + " : " + e); + } + } + } + } + return self; + }; + /* + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + this.unbind = function(event) { + if (event) + delete _listeners[event]; + else { + delete _listeners; + _listeners = {}; + } + return self; + }; + + this.getListener = function(forEvent) { + return _listeners[forEvent]; + }; + }, + logEnabled : true, + log : function() { + if (jsPlumbUtil.logEnabled && typeof console != "undefined") { + try { + var msg = arguments[arguments.length - 1]; + console.log(msg); + } + catch (e) {} + } + }, + group : function(g) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.group(g); }, + groupEnd : function(g) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.groupEnd(g); }, + time : function(t) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.time(t); }, + timeEnd : function(t) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.timeEnd(t); } +}; \ No newline at end of file diff --git a/archive/1.3.10/mootools.jsPlumb-1.3.10-RC1.js b/archive/1.3.10/mootools.jsPlumb-1.3.10-RC1.js new file mode 100644 index 000000000..8a23e0e90 --- /dev/null +++ b/archive/1.3.10/mootools.jsPlumb-1.3.10-RC1.js @@ -0,0 +1,463 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.9 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the MooTools adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function(now) { + this.parent(now); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event, originalEvent) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr, originalEvent); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, _scope, value) { + var l = list[_scope]; + if (!l) { + l = []; + list[_scope] = l; + } + l.push(value); + }; + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + var _getElementObject = function(el) { + return $(el); + }, + + _removeNonPermanentDroppables = function(drag) { + /* + // remove non-permanent droppables from all arrays + var dbs = _droppables[drag.scope], d = []; + if (dbs) { + var d = []; + for (var i=0; i < dbs.length; i++) { + var isPermanent = dbs[i].getAttribute("_isPermanentDroppable"); + if (isPermanent === "true") d.push(dbs[i]); + } + dbs.splice(0, dbs.length); + _droppables[drag.scope] = d; + } + d = []; + // clear out transient droppables from the drag itself + for(var i = 0; i < drag.droppables.length; i++) { + var isPermanent = drag.droppables[i].getAttribute("_isPermanentDroppable"); + if (isPermanent === "true") d.push(drag.droppables[i]); + } + drag.droppables.splice(0, drag.droppables.length); // release old ones + drag.droppables = d; + //*/ + }; + + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el) + try { + if (el.className.constructor == SVGAnimatedString) { + jsPlumbUtil.svg.addClass(el, clazz); + } + else el.addClass(clazz); + } + catch (e) { + // SVGAnimatedString not supported; no problem. + el.addClass(clazz); + } + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + m.start(properties); + }, + + appendElement : function(child, parent) { + _getElementObject(parent).grab(child); + }, + + bind : function(el, event, callback) { + el = _getElementObject(el); + el.addEvent(event, callback); + }, + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.event.clientX, eventObject.event.clientY]; + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el), + drags = _draggablesById[id]; + return drags[0].scope; + }, + + getDropEvent : function(args) { + return args[2]; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getDOMElement : function(el) { + // MooTools just decorates the DOM elements. so we have either an ID or an Element here. + return typeof(el) == "String" ? document.getElementById(el) : el; + }, + + getElementObject : _getElementObject, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + getOriginalEvent : function(e) { + return e.event; + }, + + getPageXY : function(eventObject) { + return [eventObject.event.pageX, eventObject.event.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).getParent(); + }, + + getScrollLeft : function(el) { + return null; + }, + + getScrollTop : function(el) { + return null; + }, + + getSelector : function(spec) { + return $$(spec); + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e != null ? e.tagName : null; + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[0], + p = jsPlumb.CurrentLibrary.getElementObject(ui).getPosition(); + return { left:p.x, top:p.y }; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options, isPlumbedComponent) { + var id = jsPlumb.getId(el); + var drag = _draggablesById[id]; + if (!drag) { + var originalZIndex = 0, + originalCursor = null, + dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + + options['onStart'] = jsPlumb.wrap(options['onStart'], function() { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + drag.originalZIndex = originalZIndex; + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + + _removeNonPermanentDroppables(drag); + }); + + // DROPPABLES - only relevant if this is a plumbed component, ie. not just the result of the user making some DOM element + // draggable. this is the only library adapter that has to care about this parameter. + var scope = "" + (options["scope"] || jsPlumb.Defaults.Scope), + filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }, + droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + + if (isPlumbedComponent) { + + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr, event) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop', event); + } + }; + } + else + options["droppables"] = []; + + drag = new Drag.Move(el, options); + drag.scope = scope; + drag.originalZIndex = originalZIndex; + _add(_draggablesById, el.get("id"), drag); + // again, only keep a record of this for scope stuff if this is a plumbed component (an endpoint) + if (isPlumbedComponent) { + _add(_draggablesByScope, scope, drag); + } + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + initDroppable : function(el, options, isPlumbedComponent, isPermanent) { + var scope = options["scope"]; + _add(_droppables, scope, el); + var id = jsPlumb.getId(el); + + el.setAttribute("_isPermanentDroppable", isPermanent); // we use this to clear out droppables on drag complete. + _droppableOptions[id] = options; + _droppableScopesById[id] = scope; + var filterFunc = function(entry) { return entry.element != el; }, + draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + isAlreadyDraggable : function(el) { + return _draggablesById[jsPlumb.getId(el)] != null; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + return (typeof Drag != undefined && typeof Drag.Move != undefined); + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el.className.constructor == SVGAnimatedString) { + jsPlumbUtil.svg.removeClass(el, clazz); + } + else el.removeClass(clazz); + } + catch (e) { + // SVGAnimatedString not supported; no problem. + el.removeClass(clazz); + } + }, + + removeElement : function(element, parent) { + var el = _getElementObject(element); + if (el) el.dispose(); // ?? + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + setDragScope : function(el, scope) { + var drag = _draggablesById[el.get("id")]; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + drag[0].droppables = droppables; + }, + + setOffset : function(el, o) { + _getElementObject(el).setPosition({x:o.left, y:o.top}); + }, + + stopDrag : function() { + for (var i in _draggablesById) { + for (var j = 0; j < _draggablesById[i].length; j++) { + var d = _draggablesById[i][j]; + d.stop(); + if (d.originalZIndex != 0) + d.element.setStyle("z-index", d.originalZIndex); + } + } + }, + + /** + * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself. + * the other libraries do not. yui, in fact, cannot even pass an original event. we have to pull out stuff + * from the originalEvent to put in an options object for YUI. + * @param el + * @param event + * @param originalEvent + */ + trigger : function(el, event, originalEvent) { + originalEvent.stopPropagation(); + _getElementObject(el).fireEvent(event, originalEvent); + }, + + unbind : function(el, event, callback) { + el = _getElementObject(el); + el.removeEvent(event, callback); + } + }; + + window.addEvent('domready', jsPlumb.init); +})(); diff --git a/build/1.3.10/tests/android-svg.html b/archive/1.3.10/tests/android-svg.html similarity index 100% rename from build/1.3.10/tests/android-svg.html rename to archive/1.3.10/tests/android-svg.html diff --git a/build/1.3.10/tests/qunit-all.html b/archive/1.3.10/tests/qunit-all.html similarity index 100% rename from build/1.3.10/tests/qunit-all.html rename to archive/1.3.10/tests/qunit-all.html diff --git a/build/1.3.10/tests/qunit-canvas-jquery-instance.html b/archive/1.3.10/tests/qunit-canvas-jquery-instance.html similarity index 100% rename from build/1.3.10/tests/qunit-canvas-jquery-instance.html rename to archive/1.3.10/tests/qunit-canvas-jquery-instance.html diff --git a/build/1.3.10/tests/qunit-canvas-jquery.html b/archive/1.3.10/tests/qunit-canvas-jquery.html similarity index 100% rename from build/1.3.10/tests/qunit-canvas-jquery.html rename to archive/1.3.10/tests/qunit-canvas-jquery.html diff --git a/build/1.3.10/tests/qunit-canvas-mootools.html b/archive/1.3.10/tests/qunit-canvas-mootools.html similarity index 100% rename from build/1.3.10/tests/qunit-canvas-mootools.html rename to archive/1.3.10/tests/qunit-canvas-mootools.html diff --git a/build/1.3.10/tests/qunit-svg-jquery-instance.html b/archive/1.3.10/tests/qunit-svg-jquery-instance.html similarity index 100% rename from build/1.3.10/tests/qunit-svg-jquery-instance.html rename to archive/1.3.10/tests/qunit-svg-jquery-instance.html diff --git a/build/1.3.10/tests/qunit-svg-jquery.html b/archive/1.3.10/tests/qunit-svg-jquery.html similarity index 100% rename from build/1.3.10/tests/qunit-svg-jquery.html rename to archive/1.3.10/tests/qunit-svg-jquery.html diff --git a/build/1.3.10/tests/qunit-vml-jquery-instance.html b/archive/1.3.10/tests/qunit-vml-jquery-instance.html similarity index 100% rename from build/1.3.10/tests/qunit-vml-jquery-instance.html rename to archive/1.3.10/tests/qunit-vml-jquery-instance.html diff --git a/build/1.3.10/tests/qunit-vml-jquery.html b/archive/1.3.10/tests/qunit-vml-jquery.html similarity index 100% rename from build/1.3.10/tests/qunit-vml-jquery.html rename to archive/1.3.10/tests/qunit-vml-jquery.html diff --git a/build/1.3.10/tests/qunit.css b/archive/1.3.10/tests/qunit.css similarity index 100% rename from build/1.3.10/tests/qunit.css rename to archive/1.3.10/tests/qunit.css diff --git a/archive/1.3.10/yui.jsPlumb-1.3.10-RC1.js b/archive/1.3.10/yui.jsPlumb-1.3.10-RC1.js new file mode 100644 index 000000000..3f0c69f4f --- /dev/null +++ b/archive/1.3.10/yui.jsPlumb-1.3.10-RC1.js @@ -0,0 +1,384 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.9 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the YUI3 adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +/** + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getOriginalEvent gets the original browser event from some wrapper event. + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + */ +(function() { + + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function( v, b, s ) { + for( var i = +b || 0, l = this.length; i < l; i++ ) { + if( this[i]===v || s && this[i]==v ) { return i; } + } + return -1; + }; + } + + var Y; + + YUI().use('node', 'dd', 'anim', 'node-event-simulate', function(_Y) { + Y = _Y; + Y.on("domready", function() { jsPlumb.init(); }); + }); + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }, + ddEvents = [ "drag:mouseDown", "drag:afterMouseDown", "drag:mouseup", + "drag:align", "drag:removeHandle", "drag:addHandle", "drag:removeInvalid", "drag:addInvalid", + "drag:start", "drag:end", "drag:drag", "drag:over", "drag:enter", + "drag:exit", "drag:drophit", "drag:dropmiss", "drop:over", "drop:enter", "drop:exit", "drop:hit" + ], + animEvents = [ "tween" ], + /** + * helper function to curry callbacks for some element. + */ + _wrapper = function(fn) { + return function() { + try { + return fn.apply(this, arguments); + } + catch (e) { } + }; + }, + /** + * extracts options from the given options object, leaving out event handlers. + */ + _getDDOptions = function(options) { + var o = {}; + for (var i in options) if (ddEvents.indexOf(i) == -1) o[i] = options[i]; + return o; + }, + /** + * attaches all event handlers found in options to the given dragdrop object, and registering + * the given el as the element of interest. + */ + _attachListeners = function(dd, options, eventList) { + for (var ev in options) { + if (eventList.indexOf(ev) != -1) { + var w = _wrapper(options[ev]); + dd.on(ev, w); + } + } + }, + _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}, + _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }, + _lastDragObject = null, + _extend = function(o1, o2) { + for (var i in o2) + o1[i] = o2[i]; + return o1; + }, + _getAttribute = function(el, attributeId) { + return el.getAttribute(attributeId); + }, + _getElementObject = function(el) { + if (el == null) return null; + var eee = null; + eee = typeof el == 'string' ? Y.one('#' + el) : el._node ? el : Y.one(el); + return eee; + }; + + jsPlumb.CurrentLibrary = { + + addClass : function(el, clazz) { + jsPlumb.CurrentLibrary.getElementObject(el).addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + var o = _extend({node:el, to:properties}, options), + id = _getAttribute(el, "id"); + o["tween"] = jsPlumb.wrap(properties["tween"], function() { + // TODO should use a current instance. + jsPlumb.repaint(id); + }); + var a = new Y.Anim(o); + _attachListeners(a, o, animEvents); + a.run(); + }, + + appendElement : function(child, parent) { + _getElementObject (parent).append(child); + }, + + /** + * event binding wrapper. + */ + bind : function(el, event, callback) { + _getElementObject(el).on(event, callback); + }, + + dragEvents : { + "start":"drag:start", "stop":"drag:end", "drag":"drag:drag", "step":"step", + "over":"drop:enter", "out":"drop:exit", "drop":"drop:hit" + }, + + extend : _extend, + + getAttribute : _getAttribute, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + // this is a workaround for the unfortunate fact that in YUI3, the 'drop:exit' event does + // not contain a reference to the drag that just exited. single-threaded js to the + // rescue: we'll just keep it for ourselves. + if (eventArgs[0].drag) _lastDragObject = eventArgs[0].drag.el; + return _lastDragObject; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el), + dd = _draggablesById[id]; + return dd.scope; + }, + + getDropEvent : function(args) { + return args[0]; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getDOMElement : function(el) { + if (typeof(el) == "String") + return document.getElementById(el); + else if (el._node) + return el._node; + else return el; + }, + + getElementObject : _getElementObject, + + getOffset : function(el) { + var o = Y.DOM.getXY(el._node); + return {left:o[0], top:o[1]}; + }, + + getOriginalEvent : function(e) { + return e._event; + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).get("parentNode"); + }, + + getScrollLeft : function(el) { + return 0; + }, + + getScrollTop : function(el) { + return 0; + }, + + getSelector : function(spec) { + var s = Y.all(spec); + return s && s ._nodes ? s._nodes : []; + }, + + getSize : function(el) { + return [ el._node.offsetWidth, el._node.offsetHeight ]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e != null && e._node != null ? e._node.tagName : null; + }, + + getUIPosition : function(args) { + var n = args[0].currentTarget.el._node, + o = Y.DOM.getXY(n); + return {left:o[0], top:o[1]}; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options, isPlumbedComponent) { + var _opts = _getDDOptions(options), + id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drag(_opts); + dd.el = el; + + if (isPlumbedComponent) { + var scope = options['scope'] || jsPlumb.Defaults.Scope; + dd.scope = scope; + _add(_draggablesByScope, scope, dd); + } + + _draggablesById[id] = dd; + + _attachListeners(dd, options, ddEvents); + }, + + initDroppable : function(el, options) { + var _opts = _getDDOptions(options), + id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drop(_opts); + + _droppableOptions[id] = options; + + options = _extend({}, options); + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _droppableScopesById[id] = scope; + + options["drop:enter"] = jsPlumb.wrap(options["drop:enter"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, true); + }, true); + options["drop:exit"] = jsPlumb.wrap(options["drop:exit"], function(e) { + _checkHover(el, false); + }); + options["drop:hit"] = jsPlumb.wrap(options["drop:hit"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, false); + }, true); + + _attachListeners(dd, options, ddEvents); + }, + + isAlreadyDraggable : function(el) { + el = _getElementObject(el); + return el.hasClass("yui3-dd-draggable"); + }, + + isDragSupported : function(el) { return true; }, + isDropSupported : function(el) { return true; }, + removeClass : function(el, clazz) { + jsPlumb.CurrentLibrary.getElementObject(el).removeClass(clazz); + }, + removeElement : function(el) { _getElementObject(el).remove(); }, + + setAttribute : function(el, attributeName, attributeValue) { + el.setAttribute(attributeName, attributeValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + var id = jsPlumb.getId(el), + dd = _draggablesById[id]; + if (dd) dd.set("lock", !draggable); + }, + + setDragScope : function(el, scope) { + var id = jsPlumb.getId(el), + dd = _draggablesById[id]; + if (dd) dd.scope = scope; + }, + + setOffset : function(el, o) { + el = _getElementObject(el); + el.set("top", o.top); + el.set("left", o.left); + }, + + stopDrag : function() { + Y.DD.DDM.stopDrag(); + }, + + trigger : function(el, event, originalEvent) { + originalEvent.stopPropagation(); + _getElementObject(el).simulate(event, { + pageX:originalEvent.pageX, + pageY:originalEvent.pageY, + clientX:originalEvent.clientX, + clientY:originalEvent.clientY + }); + }, + + /** + * event unbinding wrapper. + */ + unbind : function(el, event, callback) { + _getElementObject(el).detach(event, callback); + } + }; +})(); \ No newline at end of file diff --git a/build/1.3.11/demo/apidocs/files/jquery-jsPlumb-1-3-11-all-js.html b/archive/1.3.11/demo/apidocs/files/jquery-jsPlumb-1-3-11-all-js.html similarity index 100% rename from build/1.3.11/demo/apidocs/files/jquery-jsPlumb-1-3-11-all-js.html rename to archive/1.3.11/demo/apidocs/files/jquery-jsPlumb-1-3-11-all-js.html diff --git a/build/1.3.11/demo/apidocs/index.html b/archive/1.3.11/demo/apidocs/index.html similarity index 100% rename from build/1.3.11/demo/apidocs/index.html rename to archive/1.3.11/demo/apidocs/index.html diff --git a/build/1.3.11/demo/apidocs/index/Classes.html b/archive/1.3.11/demo/apidocs/index/Classes.html similarity index 100% rename from build/1.3.11/demo/apidocs/index/Classes.html rename to archive/1.3.11/demo/apidocs/index/Classes.html diff --git a/build/1.3.11/demo/apidocs/index/Files.html b/archive/1.3.11/demo/apidocs/index/Files.html similarity index 100% rename from build/1.3.11/demo/apidocs/index/Files.html rename to archive/1.3.11/demo/apidocs/index/Files.html diff --git a/build/1.3.11/demo/apidocs/index/Functions.html b/archive/1.3.11/demo/apidocs/index/Functions.html similarity index 100% rename from build/1.3.11/demo/apidocs/index/Functions.html rename to archive/1.3.11/demo/apidocs/index/Functions.html diff --git a/build/1.3.11/demo/apidocs/index/Functions2.html b/archive/1.3.11/demo/apidocs/index/Functions2.html similarity index 100% rename from build/1.3.11/demo/apidocs/index/Functions2.html rename to archive/1.3.11/demo/apidocs/index/Functions2.html diff --git a/build/1.3.11/demo/apidocs/index/General.html b/archive/1.3.11/demo/apidocs/index/General.html similarity index 100% rename from build/1.3.11/demo/apidocs/index/General.html rename to archive/1.3.11/demo/apidocs/index/General.html diff --git a/build/1.3.11/demo/apidocs/index/General2.html b/archive/1.3.11/demo/apidocs/index/General2.html similarity index 100% rename from build/1.3.11/demo/apidocs/index/General2.html rename to archive/1.3.11/demo/apidocs/index/General2.html diff --git a/build/1.3.11/demo/apidocs/index/Properties.html b/archive/1.3.11/demo/apidocs/index/Properties.html similarity index 100% rename from build/1.3.11/demo/apidocs/index/Properties.html rename to archive/1.3.11/demo/apidocs/index/Properties.html diff --git a/build/1.3.11/demo/apidocs/javascript/main.js b/archive/1.3.11/demo/apidocs/javascript/main.js similarity index 100% rename from build/1.3.11/demo/apidocs/javascript/main.js rename to archive/1.3.11/demo/apidocs/javascript/main.js diff --git a/build/1.3.11/demo/apidocs/javascript/prettify.js b/archive/1.3.11/demo/apidocs/javascript/prettify.js similarity index 100% rename from build/1.3.11/demo/apidocs/javascript/prettify.js rename to archive/1.3.11/demo/apidocs/javascript/prettify.js diff --git a/build/1.3.11/demo/apidocs/javascript/searchdata.js b/archive/1.3.11/demo/apidocs/javascript/searchdata.js similarity index 100% rename from build/1.3.11/demo/apidocs/javascript/searchdata.js rename to archive/1.3.11/demo/apidocs/javascript/searchdata.js diff --git a/build/1.3.11/demo/apidocs/search/ClassesC.html b/archive/1.3.11/demo/apidocs/search/ClassesC.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/ClassesC.html rename to archive/1.3.11/demo/apidocs/search/ClassesC.html diff --git a/build/1.3.11/demo/apidocs/search/ClassesE.html b/archive/1.3.11/demo/apidocs/search/ClassesE.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/ClassesE.html rename to archive/1.3.11/demo/apidocs/search/ClassesE.html diff --git a/build/1.3.11/demo/apidocs/search/ClassesO.html b/archive/1.3.11/demo/apidocs/search/ClassesO.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/ClassesO.html rename to archive/1.3.11/demo/apidocs/search/ClassesO.html diff --git a/build/1.3.11/demo/apidocs/search/FilesJ.html b/archive/1.3.11/demo/apidocs/search/FilesJ.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/FilesJ.html rename to archive/1.3.11/demo/apidocs/search/FilesJ.html diff --git a/build/1.3.11/demo/apidocs/search/FunctionsA.html b/archive/1.3.11/demo/apidocs/search/FunctionsA.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/FunctionsA.html rename to archive/1.3.11/demo/apidocs/search/FunctionsA.html diff --git a/build/1.3.11/demo/apidocs/search/FunctionsB.html b/archive/1.3.11/demo/apidocs/search/FunctionsB.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/FunctionsB.html rename to archive/1.3.11/demo/apidocs/search/FunctionsB.html diff --git a/build/1.3.11/demo/apidocs/search/FunctionsC.html b/archive/1.3.11/demo/apidocs/search/FunctionsC.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/FunctionsC.html rename to archive/1.3.11/demo/apidocs/search/FunctionsC.html diff --git a/build/1.3.11/demo/apidocs/search/FunctionsD.html b/archive/1.3.11/demo/apidocs/search/FunctionsD.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/FunctionsD.html rename to archive/1.3.11/demo/apidocs/search/FunctionsD.html diff --git a/build/1.3.11/demo/apidocs/search/FunctionsE.html b/archive/1.3.11/demo/apidocs/search/FunctionsE.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/FunctionsE.html rename to archive/1.3.11/demo/apidocs/search/FunctionsE.html diff --git a/build/1.3.11/demo/apidocs/search/FunctionsG.html b/archive/1.3.11/demo/apidocs/search/FunctionsG.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/FunctionsG.html rename to archive/1.3.11/demo/apidocs/search/FunctionsG.html diff --git a/build/1.3.11/demo/apidocs/search/FunctionsH.html b/archive/1.3.11/demo/apidocs/search/FunctionsH.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/FunctionsH.html rename to archive/1.3.11/demo/apidocs/search/FunctionsH.html diff --git a/build/1.3.11/demo/apidocs/search/FunctionsI.html b/archive/1.3.11/demo/apidocs/search/FunctionsI.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/FunctionsI.html rename to archive/1.3.11/demo/apidocs/search/FunctionsI.html diff --git a/build/1.3.11/demo/apidocs/search/FunctionsM.html b/archive/1.3.11/demo/apidocs/search/FunctionsM.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/FunctionsM.html rename to archive/1.3.11/demo/apidocs/search/FunctionsM.html diff --git a/build/1.3.11/demo/apidocs/search/FunctionsP.html b/archive/1.3.11/demo/apidocs/search/FunctionsP.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/FunctionsP.html rename to archive/1.3.11/demo/apidocs/search/FunctionsP.html diff --git a/build/1.3.11/demo/apidocs/search/FunctionsR.html b/archive/1.3.11/demo/apidocs/search/FunctionsR.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/FunctionsR.html rename to archive/1.3.11/demo/apidocs/search/FunctionsR.html diff --git a/build/1.3.11/demo/apidocs/search/FunctionsS.html b/archive/1.3.11/demo/apidocs/search/FunctionsS.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/FunctionsS.html rename to archive/1.3.11/demo/apidocs/search/FunctionsS.html diff --git a/build/1.3.11/demo/apidocs/search/FunctionsT.html b/archive/1.3.11/demo/apidocs/search/FunctionsT.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/FunctionsT.html rename to archive/1.3.11/demo/apidocs/search/FunctionsT.html diff --git a/build/1.3.11/demo/apidocs/search/FunctionsU.html b/archive/1.3.11/demo/apidocs/search/FunctionsU.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/FunctionsU.html rename to archive/1.3.11/demo/apidocs/search/FunctionsU.html diff --git a/build/1.3.11/demo/apidocs/search/GeneralA.html b/archive/1.3.11/demo/apidocs/search/GeneralA.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/GeneralA.html rename to archive/1.3.11/demo/apidocs/search/GeneralA.html diff --git a/build/1.3.11/demo/apidocs/search/GeneralB.html b/archive/1.3.11/demo/apidocs/search/GeneralB.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/GeneralB.html rename to archive/1.3.11/demo/apidocs/search/GeneralB.html diff --git a/build/1.3.11/demo/apidocs/search/GeneralC.html b/archive/1.3.11/demo/apidocs/search/GeneralC.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/GeneralC.html rename to archive/1.3.11/demo/apidocs/search/GeneralC.html diff --git a/build/1.3.11/demo/apidocs/search/GeneralD.html b/archive/1.3.11/demo/apidocs/search/GeneralD.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/GeneralD.html rename to archive/1.3.11/demo/apidocs/search/GeneralD.html diff --git a/build/1.3.11/demo/apidocs/search/GeneralE.html b/archive/1.3.11/demo/apidocs/search/GeneralE.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/GeneralE.html rename to archive/1.3.11/demo/apidocs/search/GeneralE.html diff --git a/build/1.3.11/demo/apidocs/search/GeneralF.html b/archive/1.3.11/demo/apidocs/search/GeneralF.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/GeneralF.html rename to archive/1.3.11/demo/apidocs/search/GeneralF.html diff --git a/build/1.3.11/demo/apidocs/search/GeneralG.html b/archive/1.3.11/demo/apidocs/search/GeneralG.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/GeneralG.html rename to archive/1.3.11/demo/apidocs/search/GeneralG.html diff --git a/build/1.3.11/demo/apidocs/search/GeneralH.html b/archive/1.3.11/demo/apidocs/search/GeneralH.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/GeneralH.html rename to archive/1.3.11/demo/apidocs/search/GeneralH.html diff --git a/build/1.3.11/demo/apidocs/search/GeneralI.html b/archive/1.3.11/demo/apidocs/search/GeneralI.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/GeneralI.html rename to archive/1.3.11/demo/apidocs/search/GeneralI.html diff --git a/build/1.3.11/demo/apidocs/search/GeneralJ.html b/archive/1.3.11/demo/apidocs/search/GeneralJ.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/GeneralJ.html rename to archive/1.3.11/demo/apidocs/search/GeneralJ.html diff --git a/build/1.3.11/demo/apidocs/search/GeneralM.html b/archive/1.3.11/demo/apidocs/search/GeneralM.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/GeneralM.html rename to archive/1.3.11/demo/apidocs/search/GeneralM.html diff --git a/build/1.3.11/demo/apidocs/search/GeneralO.html b/archive/1.3.11/demo/apidocs/search/GeneralO.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/GeneralO.html rename to archive/1.3.11/demo/apidocs/search/GeneralO.html diff --git a/build/1.3.11/demo/apidocs/search/GeneralP.html b/archive/1.3.11/demo/apidocs/search/GeneralP.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/GeneralP.html rename to archive/1.3.11/demo/apidocs/search/GeneralP.html diff --git a/build/1.3.11/demo/apidocs/search/GeneralR.html b/archive/1.3.11/demo/apidocs/search/GeneralR.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/GeneralR.html rename to archive/1.3.11/demo/apidocs/search/GeneralR.html diff --git a/build/1.3.11/demo/apidocs/search/GeneralS.html b/archive/1.3.11/demo/apidocs/search/GeneralS.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/GeneralS.html rename to archive/1.3.11/demo/apidocs/search/GeneralS.html diff --git a/build/1.3.11/demo/apidocs/search/GeneralT.html b/archive/1.3.11/demo/apidocs/search/GeneralT.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/GeneralT.html rename to archive/1.3.11/demo/apidocs/search/GeneralT.html diff --git a/build/1.3.11/demo/apidocs/search/GeneralU.html b/archive/1.3.11/demo/apidocs/search/GeneralU.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/GeneralU.html rename to archive/1.3.11/demo/apidocs/search/GeneralU.html diff --git a/build/1.3.11/demo/apidocs/search/NoResults.html b/archive/1.3.11/demo/apidocs/search/NoResults.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/NoResults.html rename to archive/1.3.11/demo/apidocs/search/NoResults.html diff --git a/build/1.3.11/demo/apidocs/search/PropertiesC.html b/archive/1.3.11/demo/apidocs/search/PropertiesC.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/PropertiesC.html rename to archive/1.3.11/demo/apidocs/search/PropertiesC.html diff --git a/build/1.3.11/demo/apidocs/search/PropertiesD.html b/archive/1.3.11/demo/apidocs/search/PropertiesD.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/PropertiesD.html rename to archive/1.3.11/demo/apidocs/search/PropertiesD.html diff --git a/build/1.3.11/demo/apidocs/search/PropertiesE.html b/archive/1.3.11/demo/apidocs/search/PropertiesE.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/PropertiesE.html rename to archive/1.3.11/demo/apidocs/search/PropertiesE.html diff --git a/build/1.3.11/demo/apidocs/search/PropertiesO.html b/archive/1.3.11/demo/apidocs/search/PropertiesO.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/PropertiesO.html rename to archive/1.3.11/demo/apidocs/search/PropertiesO.html diff --git a/build/1.3.11/demo/apidocs/search/PropertiesS.html b/archive/1.3.11/demo/apidocs/search/PropertiesS.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/PropertiesS.html rename to archive/1.3.11/demo/apidocs/search/PropertiesS.html diff --git a/build/1.3.11/demo/apidocs/search/PropertiesT.html b/archive/1.3.11/demo/apidocs/search/PropertiesT.html similarity index 100% rename from build/1.3.11/demo/apidocs/search/PropertiesT.html rename to archive/1.3.11/demo/apidocs/search/PropertiesT.html diff --git a/build/1.3.11/demo/apidocs/styles/main.css b/archive/1.3.11/demo/apidocs/styles/main.css similarity index 100% rename from build/1.3.11/demo/apidocs/styles/main.css rename to archive/1.3.11/demo/apidocs/styles/main.css diff --git a/build/1.3.11/demo/css/anchorDemo.css b/archive/1.3.11/demo/css/anchorDemo.css similarity index 100% rename from build/1.3.11/demo/css/anchorDemo.css rename to archive/1.3.11/demo/css/anchorDemo.css diff --git a/build/1.3.11/demo/css/chartDemo.css b/archive/1.3.11/demo/css/chartDemo.css similarity index 100% rename from build/1.3.11/demo/css/chartDemo.css rename to archive/1.3.11/demo/css/chartDemo.css diff --git a/build/1.3.11/demo/css/demo.css b/archive/1.3.11/demo/css/demo.css similarity index 100% rename from build/1.3.11/demo/css/demo.css rename to archive/1.3.11/demo/css/demo.css diff --git a/build/1.3.11/demo/css/dragAnimDemo.css b/archive/1.3.11/demo/css/dragAnimDemo.css similarity index 100% rename from build/1.3.11/demo/css/dragAnimDemo.css rename to archive/1.3.11/demo/css/dragAnimDemo.css diff --git a/build/1.3.11/demo/css/draggableConnectorsDemo.css b/archive/1.3.11/demo/css/draggableConnectorsDemo.css similarity index 100% rename from build/1.3.11/demo/css/draggableConnectorsDemo.css rename to archive/1.3.11/demo/css/draggableConnectorsDemo.css diff --git a/build/1.3.11/demo/css/dynamicAnchorsDemo.css b/archive/1.3.11/demo/css/dynamicAnchorsDemo.css similarity index 100% rename from build/1.3.11/demo/css/dynamicAnchorsDemo.css rename to archive/1.3.11/demo/css/dynamicAnchorsDemo.css diff --git a/build/1.3.11/demo/css/flowchartDemo.css b/archive/1.3.11/demo/css/flowchartDemo.css similarity index 100% rename from build/1.3.11/demo/css/flowchartDemo.css rename to archive/1.3.11/demo/css/flowchartDemo.css diff --git a/build/1.3.11/demo/css/jsPlumbDemo.css b/archive/1.3.11/demo/css/jsPlumbDemo.css similarity index 100% rename from build/1.3.11/demo/css/jsPlumbDemo.css rename to archive/1.3.11/demo/css/jsPlumbDemo.css diff --git a/build/1.3.11/demo/css/makeTargetDemo.css b/archive/1.3.11/demo/css/makeTargetDemo.css similarity index 100% rename from build/1.3.11/demo/css/makeTargetDemo.css rename to archive/1.3.11/demo/css/makeTargetDemo.css diff --git a/build/1.3.11/demo/css/multipleJsPlumbDemo.css b/archive/1.3.11/demo/css/multipleJsPlumbDemo.css similarity index 100% rename from build/1.3.11/demo/css/multipleJsPlumbDemo.css rename to archive/1.3.11/demo/css/multipleJsPlumbDemo.css diff --git a/build/1.3.11/demo/css/selectDemo.css b/archive/1.3.11/demo/css/selectDemo.css similarity index 100% rename from build/1.3.11/demo/css/selectDemo.css rename to archive/1.3.11/demo/css/selectDemo.css diff --git a/build/1.3.11/demo/css/stateMachineDemo.css b/archive/1.3.11/demo/css/stateMachineDemo.css similarity index 100% rename from build/1.3.11/demo/css/stateMachineDemo.css rename to archive/1.3.11/demo/css/stateMachineDemo.css diff --git a/build/1.3.11/demo/doc/archive/1.2.6/content.html b/archive/1.3.11/demo/doc/archive/1.2.6/content.html similarity index 100% rename from build/1.3.11/demo/doc/archive/1.2.6/content.html rename to archive/1.3.11/demo/doc/archive/1.2.6/content.html diff --git a/build/1.3.11/demo/doc/archive/1.2.6/index.html b/archive/1.3.11/demo/doc/archive/1.2.6/index.html similarity index 100% rename from build/1.3.11/demo/doc/archive/1.2.6/index.html rename to archive/1.3.11/demo/doc/archive/1.2.6/index.html diff --git a/build/1.3.11/demo/doc/archive/1.2.6/usage.html b/archive/1.3.11/demo/doc/archive/1.2.6/usage.html similarity index 100% rename from build/1.3.11/demo/doc/archive/1.2.6/usage.html rename to archive/1.3.11/demo/doc/archive/1.2.6/usage.html diff --git a/build/1.3.11/demo/doc/archive/1.3.10/content.html b/archive/1.3.11/demo/doc/archive/1.3.10/content.html similarity index 100% rename from build/1.3.11/demo/doc/archive/1.3.10/content.html rename to archive/1.3.11/demo/doc/archive/1.3.10/content.html diff --git a/build/1.3.11/demo/doc/archive/1.3.10/index.html b/archive/1.3.11/demo/doc/archive/1.3.10/index.html similarity index 100% rename from build/1.3.11/demo/doc/archive/1.3.10/index.html rename to archive/1.3.11/demo/doc/archive/1.3.10/index.html diff --git a/build/1.3.11/demo/doc/archive/1.3.10/jsPlumbDoc.css b/archive/1.3.11/demo/doc/archive/1.3.10/jsPlumbDoc.css similarity index 100% rename from build/1.3.11/demo/doc/archive/1.3.10/jsPlumbDoc.css rename to archive/1.3.11/demo/doc/archive/1.3.10/jsPlumbDoc.css diff --git a/build/1.3.11/demo/doc/archive/1.3.10/usage.html b/archive/1.3.11/demo/doc/archive/1.3.10/usage.html similarity index 100% rename from build/1.3.11/demo/doc/archive/1.3.10/usage.html rename to archive/1.3.11/demo/doc/archive/1.3.10/usage.html diff --git a/build/1.3.11/demo/doc/archive/1.3.2/content.html b/archive/1.3.11/demo/doc/archive/1.3.2/content.html similarity index 100% rename from build/1.3.11/demo/doc/archive/1.3.2/content.html rename to archive/1.3.11/demo/doc/archive/1.3.2/content.html diff --git a/build/1.3.11/demo/doc/archive/1.3.2/index.html b/archive/1.3.11/demo/doc/archive/1.3.2/index.html similarity index 100% rename from build/1.3.11/demo/doc/archive/1.3.2/index.html rename to archive/1.3.11/demo/doc/archive/1.3.2/index.html diff --git a/build/1.3.11/demo/doc/archive/1.3.2/usage.html b/archive/1.3.11/demo/doc/archive/1.3.2/usage.html similarity index 100% rename from build/1.3.11/demo/doc/archive/1.3.2/usage.html rename to archive/1.3.11/demo/doc/archive/1.3.2/usage.html diff --git a/build/1.3.11/demo/doc/archive/1.3.3/content.html b/archive/1.3.11/demo/doc/archive/1.3.3/content.html similarity index 100% rename from build/1.3.11/demo/doc/archive/1.3.3/content.html rename to archive/1.3.11/demo/doc/archive/1.3.3/content.html diff --git a/build/1.3.11/demo/doc/archive/1.3.3/index.html b/archive/1.3.11/demo/doc/archive/1.3.3/index.html similarity index 100% rename from build/1.3.11/demo/doc/archive/1.3.3/index.html rename to archive/1.3.11/demo/doc/archive/1.3.3/index.html diff --git a/build/1.3.11/demo/doc/archive/1.3.3/jsPlumbDoc.css b/archive/1.3.11/demo/doc/archive/1.3.3/jsPlumbDoc.css similarity index 100% rename from build/1.3.11/demo/doc/archive/1.3.3/jsPlumbDoc.css rename to archive/1.3.11/demo/doc/archive/1.3.3/jsPlumbDoc.css diff --git a/build/1.3.11/demo/doc/archive/1.3.3/usage.html b/archive/1.3.11/demo/doc/archive/1.3.3/usage.html similarity index 100% rename from build/1.3.11/demo/doc/archive/1.3.3/usage.html rename to archive/1.3.11/demo/doc/archive/1.3.3/usage.html diff --git a/build/1.3.11/demo/doc/archive/1.3.4/content.html b/archive/1.3.11/demo/doc/archive/1.3.4/content.html similarity index 100% rename from build/1.3.11/demo/doc/archive/1.3.4/content.html rename to archive/1.3.11/demo/doc/archive/1.3.4/content.html diff --git a/build/1.3.11/demo/doc/archive/1.3.4/index.html b/archive/1.3.11/demo/doc/archive/1.3.4/index.html similarity index 100% rename from build/1.3.11/demo/doc/archive/1.3.4/index.html rename to archive/1.3.11/demo/doc/archive/1.3.4/index.html diff --git a/build/1.3.11/demo/doc/archive/1.3.4/jsPlumbDoc.css b/archive/1.3.11/demo/doc/archive/1.3.4/jsPlumbDoc.css similarity index 100% rename from build/1.3.11/demo/doc/archive/1.3.4/jsPlumbDoc.css rename to archive/1.3.11/demo/doc/archive/1.3.4/jsPlumbDoc.css diff --git a/build/1.3.11/demo/doc/archive/1.3.4/usage.html b/archive/1.3.11/demo/doc/archive/1.3.4/usage.html similarity index 100% rename from build/1.3.11/demo/doc/archive/1.3.4/usage.html rename to archive/1.3.11/demo/doc/archive/1.3.4/usage.html diff --git a/build/1.3.11/demo/doc/archive/1.3.5/content.html b/archive/1.3.11/demo/doc/archive/1.3.5/content.html similarity index 100% rename from build/1.3.11/demo/doc/archive/1.3.5/content.html rename to archive/1.3.11/demo/doc/archive/1.3.5/content.html diff --git a/build/1.3.11/demo/doc/archive/1.3.5/index.html b/archive/1.3.11/demo/doc/archive/1.3.5/index.html similarity index 100% rename from build/1.3.11/demo/doc/archive/1.3.5/index.html rename to archive/1.3.11/demo/doc/archive/1.3.5/index.html diff --git a/build/1.3.11/demo/doc/archive/1.3.5/jsPlumbDoc.css b/archive/1.3.11/demo/doc/archive/1.3.5/jsPlumbDoc.css similarity index 100% rename from build/1.3.11/demo/doc/archive/1.3.5/jsPlumbDoc.css rename to archive/1.3.11/demo/doc/archive/1.3.5/jsPlumbDoc.css diff --git a/build/1.3.11/demo/doc/archive/1.3.5/usage.html b/archive/1.3.11/demo/doc/archive/1.3.5/usage.html similarity index 100% rename from build/1.3.11/demo/doc/archive/1.3.5/usage.html rename to archive/1.3.11/demo/doc/archive/1.3.5/usage.html diff --git a/build/1.3.11/demo/doc/archive/1.3.6/content.html b/archive/1.3.11/demo/doc/archive/1.3.6/content.html similarity index 100% rename from build/1.3.11/demo/doc/archive/1.3.6/content.html rename to archive/1.3.11/demo/doc/archive/1.3.6/content.html diff --git a/build/1.3.11/demo/doc/archive/1.3.6/index.html b/archive/1.3.11/demo/doc/archive/1.3.6/index.html similarity index 100% rename from build/1.3.11/demo/doc/archive/1.3.6/index.html rename to archive/1.3.11/demo/doc/archive/1.3.6/index.html diff --git a/build/1.3.11/demo/doc/archive/1.3.6/jsPlumbDoc.css b/archive/1.3.11/demo/doc/archive/1.3.6/jsPlumbDoc.css similarity index 100% rename from build/1.3.11/demo/doc/archive/1.3.6/jsPlumbDoc.css rename to archive/1.3.11/demo/doc/archive/1.3.6/jsPlumbDoc.css diff --git a/build/1.3.11/demo/doc/archive/1.3.6/usage.html b/archive/1.3.11/demo/doc/archive/1.3.6/usage.html similarity index 100% rename from build/1.3.11/demo/doc/archive/1.3.6/usage.html rename to archive/1.3.11/demo/doc/archive/1.3.6/usage.html diff --git a/build/1.3.11/demo/doc/archive/1.3.7/content.html b/archive/1.3.11/demo/doc/archive/1.3.7/content.html similarity index 100% rename from build/1.3.11/demo/doc/archive/1.3.7/content.html rename to archive/1.3.11/demo/doc/archive/1.3.7/content.html diff --git a/build/1.3.11/demo/doc/archive/1.3.7/index.html b/archive/1.3.11/demo/doc/archive/1.3.7/index.html similarity index 100% rename from build/1.3.11/demo/doc/archive/1.3.7/index.html rename to archive/1.3.11/demo/doc/archive/1.3.7/index.html diff --git a/build/1.3.11/demo/doc/archive/1.3.7/jsPlumbDoc.css b/archive/1.3.11/demo/doc/archive/1.3.7/jsPlumbDoc.css similarity index 100% rename from build/1.3.11/demo/doc/archive/1.3.7/jsPlumbDoc.css rename to archive/1.3.11/demo/doc/archive/1.3.7/jsPlumbDoc.css diff --git a/build/1.3.11/demo/doc/archive/1.3.7/usage.html b/archive/1.3.11/demo/doc/archive/1.3.7/usage.html similarity index 100% rename from build/1.3.11/demo/doc/archive/1.3.7/usage.html rename to archive/1.3.11/demo/doc/archive/1.3.7/usage.html diff --git a/build/1.3.11/demo/doc/archive/1.3.8/content.html b/archive/1.3.11/demo/doc/archive/1.3.8/content.html similarity index 100% rename from build/1.3.11/demo/doc/archive/1.3.8/content.html rename to archive/1.3.11/demo/doc/archive/1.3.8/content.html diff --git a/build/1.3.11/demo/doc/archive/1.3.8/index.html b/archive/1.3.11/demo/doc/archive/1.3.8/index.html similarity index 100% rename from build/1.3.11/demo/doc/archive/1.3.8/index.html rename to archive/1.3.11/demo/doc/archive/1.3.8/index.html diff --git a/build/1.3.11/demo/doc/archive/1.3.8/jsPlumbDoc.css b/archive/1.3.11/demo/doc/archive/1.3.8/jsPlumbDoc.css similarity index 100% rename from build/1.3.11/demo/doc/archive/1.3.8/jsPlumbDoc.css rename to archive/1.3.11/demo/doc/archive/1.3.8/jsPlumbDoc.css diff --git a/build/1.3.11/demo/doc/archive/1.3.8/usage.html b/archive/1.3.11/demo/doc/archive/1.3.8/usage.html similarity index 100% rename from build/1.3.11/demo/doc/archive/1.3.8/usage.html rename to archive/1.3.11/demo/doc/archive/1.3.8/usage.html diff --git a/build/1.3.11/demo/doc/archive/1.3.9/content.html b/archive/1.3.11/demo/doc/archive/1.3.9/content.html similarity index 100% rename from build/1.3.11/demo/doc/archive/1.3.9/content.html rename to archive/1.3.11/demo/doc/archive/1.3.9/content.html diff --git a/build/1.3.11/demo/doc/archive/1.3.9/index.html b/archive/1.3.11/demo/doc/archive/1.3.9/index.html similarity index 100% rename from build/1.3.11/demo/doc/archive/1.3.9/index.html rename to archive/1.3.11/demo/doc/archive/1.3.9/index.html diff --git a/build/1.3.11/demo/doc/archive/1.3.9/jsPlumbDoc.css b/archive/1.3.11/demo/doc/archive/1.3.9/jsPlumbDoc.css similarity index 100% rename from build/1.3.11/demo/doc/archive/1.3.9/jsPlumbDoc.css rename to archive/1.3.11/demo/doc/archive/1.3.9/jsPlumbDoc.css diff --git a/build/1.3.11/demo/doc/archive/1.3.9/usage.html b/archive/1.3.11/demo/doc/archive/1.3.9/usage.html similarity index 100% rename from build/1.3.11/demo/doc/archive/1.3.9/usage.html rename to archive/1.3.11/demo/doc/archive/1.3.9/usage.html diff --git a/build/1.3.11/demo/doc/content.html b/archive/1.3.11/demo/doc/content.html similarity index 100% rename from build/1.3.11/demo/doc/content.html rename to archive/1.3.11/demo/doc/content.html diff --git a/build/1.3.11/demo/doc/index.html b/archive/1.3.11/demo/doc/index.html similarity index 100% rename from build/1.3.11/demo/doc/index.html rename to archive/1.3.11/demo/doc/index.html diff --git a/build/1.3.11/demo/doc/jsPlumbDoc.css b/archive/1.3.11/demo/doc/jsPlumbDoc.css similarity index 100% rename from build/1.3.11/demo/doc/jsPlumbDoc.css rename to archive/1.3.11/demo/doc/jsPlumbDoc.css diff --git a/build/1.3.11/demo/doc/usage.html b/archive/1.3.11/demo/doc/usage.html similarity index 100% rename from build/1.3.11/demo/doc/usage.html rename to archive/1.3.11/demo/doc/usage.html diff --git a/build/1.3.11/demo/img/bigdot.jpg b/archive/1.3.11/demo/img/bigdot.jpg similarity index 100% rename from build/1.3.11/demo/img/bigdot.jpg rename to archive/1.3.11/demo/img/bigdot.jpg diff --git a/build/1.3.11/demo/img/bigdot.png b/archive/1.3.11/demo/img/bigdot.png similarity index 100% rename from build/1.3.11/demo/img/bigdot.png rename to archive/1.3.11/demo/img/bigdot.png diff --git a/build/1.3.11/demo/img/bigdot.xcf b/archive/1.3.11/demo/img/bigdot.xcf similarity index 100% rename from build/1.3.11/demo/img/bigdot.xcf rename to archive/1.3.11/demo/img/bigdot.xcf diff --git a/build/1.3.11/demo/img/dragging_1.jpg b/archive/1.3.11/demo/img/dragging_1.jpg similarity index 100% rename from build/1.3.11/demo/img/dragging_1.jpg rename to archive/1.3.11/demo/img/dragging_1.jpg diff --git a/build/1.3.11/demo/img/dragging_2.jpg b/archive/1.3.11/demo/img/dragging_2.jpg similarity index 100% rename from build/1.3.11/demo/img/dragging_2.jpg rename to archive/1.3.11/demo/img/dragging_2.jpg diff --git a/build/1.3.11/demo/img/dragging_3.jpg b/archive/1.3.11/demo/img/dragging_3.jpg similarity index 100% rename from build/1.3.11/demo/img/dragging_3.jpg rename to archive/1.3.11/demo/img/dragging_3.jpg diff --git a/build/1.3.11/demo/img/dynamicAnchorBg.jpg b/archive/1.3.11/demo/img/dynamicAnchorBg.jpg similarity index 100% rename from build/1.3.11/demo/img/dynamicAnchorBg.jpg rename to archive/1.3.11/demo/img/dynamicAnchorBg.jpg diff --git a/build/1.3.11/demo/img/endpointTest1.png b/archive/1.3.11/demo/img/endpointTest1.png similarity index 100% rename from build/1.3.11/demo/img/endpointTest1.png rename to archive/1.3.11/demo/img/endpointTest1.png diff --git a/build/1.3.11/demo/img/endpointTest1.xcf b/archive/1.3.11/demo/img/endpointTest1.xcf similarity index 100% rename from build/1.3.11/demo/img/endpointTest1.xcf rename to archive/1.3.11/demo/img/endpointTest1.xcf diff --git a/build/1.3.11/demo/img/index-bg.gif b/archive/1.3.11/demo/img/index-bg.gif similarity index 100% rename from build/1.3.11/demo/img/index-bg.gif rename to archive/1.3.11/demo/img/index-bg.gif diff --git a/build/1.3.11/demo/img/issue4_final.jpg b/archive/1.3.11/demo/img/issue4_final.jpg similarity index 100% rename from build/1.3.11/demo/img/issue4_final.jpg rename to archive/1.3.11/demo/img/issue4_final.jpg diff --git a/build/1.3.11/demo/img/littledot.png b/archive/1.3.11/demo/img/littledot.png similarity index 100% rename from build/1.3.11/demo/img/littledot.png rename to archive/1.3.11/demo/img/littledot.png diff --git a/build/1.3.11/demo/img/littledot.xcf b/archive/1.3.11/demo/img/littledot.xcf similarity index 100% rename from build/1.3.11/demo/img/littledot.xcf rename to archive/1.3.11/demo/img/littledot.xcf diff --git a/build/1.3.11/demo/img/pattern.jpg b/archive/1.3.11/demo/img/pattern.jpg similarity index 100% rename from build/1.3.11/demo/img/pattern.jpg rename to archive/1.3.11/demo/img/pattern.jpg diff --git a/build/1.3.11/demo/img/swappedAnchors.jpg b/archive/1.3.11/demo/img/swappedAnchors.jpg similarity index 100% rename from build/1.3.11/demo/img/swappedAnchors.jpg rename to archive/1.3.11/demo/img/swappedAnchors.jpg diff --git a/build/1.3.11/demo/index.html b/archive/1.3.11/demo/index.html similarity index 100% rename from build/1.3.11/demo/index.html rename to archive/1.3.11/demo/index.html diff --git a/build/1.3.11/demo/jquery/anchorDemo.html b/archive/1.3.11/demo/jquery/anchorDemo.html similarity index 100% rename from build/1.3.11/demo/jquery/anchorDemo.html rename to archive/1.3.11/demo/jquery/anchorDemo.html diff --git a/build/1.3.11/demo/jquery/chartDemo.html b/archive/1.3.11/demo/jquery/chartDemo.html similarity index 100% rename from build/1.3.11/demo/jquery/chartDemo.html rename to archive/1.3.11/demo/jquery/chartDemo.html diff --git a/build/1.3.11/demo/jquery/demo.html b/archive/1.3.11/demo/jquery/demo.html similarity index 100% rename from build/1.3.11/demo/jquery/demo.html rename to archive/1.3.11/demo/jquery/demo.html diff --git a/build/1.3.11/demo/jquery/dragAnimDemo.html b/archive/1.3.11/demo/jquery/dragAnimDemo.html similarity index 100% rename from build/1.3.11/demo/jquery/dragAnimDemo.html rename to archive/1.3.11/demo/jquery/dragAnimDemo.html diff --git a/build/1.3.11/demo/jquery/draggableConnectorsDemo.html b/archive/1.3.11/demo/jquery/draggableConnectorsDemo.html similarity index 100% rename from build/1.3.11/demo/jquery/draggableConnectorsDemo.html rename to archive/1.3.11/demo/jquery/draggableConnectorsDemo.html diff --git a/build/1.3.11/demo/jquery/dynamicAnchorsDemo.html b/archive/1.3.11/demo/jquery/dynamicAnchorsDemo.html similarity index 100% rename from build/1.3.11/demo/jquery/dynamicAnchorsDemo.html rename to archive/1.3.11/demo/jquery/dynamicAnchorsDemo.html diff --git a/build/1.3.11/demo/jquery/flowchartConnectorsDemo.html b/archive/1.3.11/demo/jquery/flowchartConnectorsDemo.html similarity index 100% rename from build/1.3.11/demo/jquery/flowchartConnectorsDemo.html rename to archive/1.3.11/demo/jquery/flowchartConnectorsDemo.html diff --git a/build/1.3.11/demo/jquery/loadTest.html b/archive/1.3.11/demo/jquery/loadTest.html similarity index 100% rename from build/1.3.11/demo/jquery/loadTest.html rename to archive/1.3.11/demo/jquery/loadTest.html diff --git a/build/1.3.11/demo/jquery/makeSourceDemo.html b/archive/1.3.11/demo/jquery/makeSourceDemo.html similarity index 100% rename from build/1.3.11/demo/jquery/makeSourceDemo.html rename to archive/1.3.11/demo/jquery/makeSourceDemo.html diff --git a/build/1.3.11/demo/jquery/makeTargetDemo.html b/archive/1.3.11/demo/jquery/makeTargetDemo.html similarity index 100% rename from build/1.3.11/demo/jquery/makeTargetDemo.html rename to archive/1.3.11/demo/jquery/makeTargetDemo.html diff --git a/build/1.3.11/demo/jquery/stateMachineDemo.html b/archive/1.3.11/demo/jquery/stateMachineDemo.html similarity index 100% rename from build/1.3.11/demo/jquery/stateMachineDemo.html rename to archive/1.3.11/demo/jquery/stateMachineDemo.html diff --git a/build/1.3.11/demo/js/anchorDemo-jquery.js b/archive/1.3.11/demo/js/anchorDemo-jquery.js similarity index 100% rename from build/1.3.11/demo/js/anchorDemo-jquery.js rename to archive/1.3.11/demo/js/anchorDemo-jquery.js diff --git a/build/1.3.11/demo/js/anchorDemo-mootools.js b/archive/1.3.11/demo/js/anchorDemo-mootools.js similarity index 100% rename from build/1.3.11/demo/js/anchorDemo-mootools.js rename to archive/1.3.11/demo/js/anchorDemo-mootools.js diff --git a/build/1.3.11/demo/js/anchorDemo-yui3.js b/archive/1.3.11/demo/js/anchorDemo-yui3.js similarity index 100% rename from build/1.3.11/demo/js/anchorDemo-yui3.js rename to archive/1.3.11/demo/js/anchorDemo-yui3.js diff --git a/build/1.3.11/demo/js/anchorDemo.js b/archive/1.3.11/demo/js/anchorDemo.js similarity index 100% rename from build/1.3.11/demo/js/anchorDemo.js rename to archive/1.3.11/demo/js/anchorDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.0/anchorDemo.js b/archive/1.3.11/demo/js/archive/1.3.0/anchorDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.0/anchorDemo.js rename to archive/1.3.11/demo/js/archive/1.3.0/anchorDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.0/chartDemo.js b/archive/1.3.11/demo/js/archive/1.3.0/chartDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.0/chartDemo.js rename to archive/1.3.11/demo/js/archive/1.3.0/chartDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.0/demo-helper-jquery.js b/archive/1.3.11/demo/js/archive/1.3.0/demo-helper-jquery.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.0/demo-helper-jquery.js rename to archive/1.3.11/demo/js/archive/1.3.0/demo-helper-jquery.js diff --git a/build/1.3.11/demo/js/archive/1.3.0/demo-helper-mootools.js b/archive/1.3.11/demo/js/archive/1.3.0/demo-helper-mootools.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.0/demo-helper-mootools.js rename to archive/1.3.11/demo/js/archive/1.3.0/demo-helper-mootools.js diff --git a/build/1.3.11/demo/js/archive/1.3.0/demo-helper-yui3.js b/archive/1.3.11/demo/js/archive/1.3.0/demo-helper-yui3.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.0/demo-helper-yui3.js rename to archive/1.3.11/demo/js/archive/1.3.0/demo-helper-yui3.js diff --git a/build/1.3.11/demo/js/archive/1.3.0/demo.js b/archive/1.3.11/demo/js/archive/1.3.0/demo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.0/demo.js rename to archive/1.3.11/demo/js/archive/1.3.0/demo.js diff --git a/build/1.3.11/demo/js/archive/1.3.0/dragAnimDemo-jquery.js b/archive/1.3.11/demo/js/archive/1.3.0/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.0/dragAnimDemo-jquery.js rename to archive/1.3.11/demo/js/archive/1.3.0/dragAnimDemo-jquery.js diff --git a/build/1.3.11/demo/js/archive/1.3.0/dragAnimDemo-mootools.js b/archive/1.3.11/demo/js/archive/1.3.0/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.0/dragAnimDemo-mootools.js rename to archive/1.3.11/demo/js/archive/1.3.0/dragAnimDemo-mootools.js diff --git a/build/1.3.11/demo/js/archive/1.3.0/dragAnimDemo-yui3.js b/archive/1.3.11/demo/js/archive/1.3.0/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.0/dragAnimDemo-yui3.js rename to archive/1.3.11/demo/js/archive/1.3.0/dragAnimDemo-yui3.js diff --git a/build/1.3.11/demo/js/archive/1.3.0/dragAnimDemo.js b/archive/1.3.11/demo/js/archive/1.3.0/dragAnimDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.0/dragAnimDemo.js rename to archive/1.3.11/demo/js/archive/1.3.0/dragAnimDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js b/archive/1.3.11/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js rename to archive/1.3.11/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.11/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js b/archive/1.3.11/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js rename to archive/1.3.11/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.11/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js b/archive/1.3.11/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js rename to archive/1.3.11/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.11/demo/js/archive/1.3.0/draggableConnectorsDemo.js b/archive/1.3.11/demo/js/archive/1.3.0/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.0/draggableConnectorsDemo.js rename to archive/1.3.11/demo/js/archive/1.3.0/draggableConnectorsDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.0/dynamicAnchorsDemo.js b/archive/1.3.11/demo/js/archive/1.3.0/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.0/dynamicAnchorsDemo.js rename to archive/1.3.11/demo/js/archive/1.3.0/dynamicAnchorsDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.0/flowchartConnectorsDemo.js b/archive/1.3.11/demo/js/archive/1.3.0/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.0/flowchartConnectorsDemo.js rename to archive/1.3.11/demo/js/archive/1.3.0/flowchartConnectorsDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.1/anchorDemo.js b/archive/1.3.11/demo/js/archive/1.3.1/anchorDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.1/anchorDemo.js rename to archive/1.3.11/demo/js/archive/1.3.1/anchorDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.1/chartDemo.js b/archive/1.3.11/demo/js/archive/1.3.1/chartDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.1/chartDemo.js rename to archive/1.3.11/demo/js/archive/1.3.1/chartDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.1/demo-helper-jquery.js b/archive/1.3.11/demo/js/archive/1.3.1/demo-helper-jquery.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.1/demo-helper-jquery.js rename to archive/1.3.11/demo/js/archive/1.3.1/demo-helper-jquery.js diff --git a/build/1.3.11/demo/js/archive/1.3.1/demo-helper-mootools.js b/archive/1.3.11/demo/js/archive/1.3.1/demo-helper-mootools.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.1/demo-helper-mootools.js rename to archive/1.3.11/demo/js/archive/1.3.1/demo-helper-mootools.js diff --git a/build/1.3.11/demo/js/archive/1.3.1/demo-helper-yui3.js b/archive/1.3.11/demo/js/archive/1.3.1/demo-helper-yui3.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.1/demo-helper-yui3.js rename to archive/1.3.11/demo/js/archive/1.3.1/demo-helper-yui3.js diff --git a/build/1.3.11/demo/js/archive/1.3.1/demo.js b/archive/1.3.11/demo/js/archive/1.3.1/demo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.1/demo.js rename to archive/1.3.11/demo/js/archive/1.3.1/demo.js diff --git a/build/1.3.11/demo/js/archive/1.3.1/dragAnimDemo-jquery.js b/archive/1.3.11/demo/js/archive/1.3.1/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.1/dragAnimDemo-jquery.js rename to archive/1.3.11/demo/js/archive/1.3.1/dragAnimDemo-jquery.js diff --git a/build/1.3.11/demo/js/archive/1.3.1/dragAnimDemo-mootools.js b/archive/1.3.11/demo/js/archive/1.3.1/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.1/dragAnimDemo-mootools.js rename to archive/1.3.11/demo/js/archive/1.3.1/dragAnimDemo-mootools.js diff --git a/build/1.3.11/demo/js/archive/1.3.1/dragAnimDemo-yui3.js b/archive/1.3.11/demo/js/archive/1.3.1/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.1/dragAnimDemo-yui3.js rename to archive/1.3.11/demo/js/archive/1.3.1/dragAnimDemo-yui3.js diff --git a/build/1.3.11/demo/js/archive/1.3.1/dragAnimDemo.js b/archive/1.3.11/demo/js/archive/1.3.1/dragAnimDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.1/dragAnimDemo.js rename to archive/1.3.11/demo/js/archive/1.3.1/dragAnimDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js b/archive/1.3.11/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js rename to archive/1.3.11/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.11/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js b/archive/1.3.11/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js rename to archive/1.3.11/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.11/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js b/archive/1.3.11/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js rename to archive/1.3.11/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.11/demo/js/archive/1.3.1/draggableConnectorsDemo.js b/archive/1.3.11/demo/js/archive/1.3.1/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.1/draggableConnectorsDemo.js rename to archive/1.3.11/demo/js/archive/1.3.1/draggableConnectorsDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.1/dynamicAnchorsDemo.js b/archive/1.3.11/demo/js/archive/1.3.1/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.1/dynamicAnchorsDemo.js rename to archive/1.3.11/demo/js/archive/1.3.1/dynamicAnchorsDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.1/flowchartConnectorsDemo.js b/archive/1.3.11/demo/js/archive/1.3.1/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.1/flowchartConnectorsDemo.js rename to archive/1.3.11/demo/js/archive/1.3.1/flowchartConnectorsDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.2/anchorDemo.js b/archive/1.3.11/demo/js/archive/1.3.2/anchorDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.2/anchorDemo.js rename to archive/1.3.11/demo/js/archive/1.3.2/anchorDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.2/chartDemo.js b/archive/1.3.11/demo/js/archive/1.3.2/chartDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.2/chartDemo.js rename to archive/1.3.11/demo/js/archive/1.3.2/chartDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.2/demo-helper-jquery.js b/archive/1.3.11/demo/js/archive/1.3.2/demo-helper-jquery.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.2/demo-helper-jquery.js rename to archive/1.3.11/demo/js/archive/1.3.2/demo-helper-jquery.js diff --git a/build/1.3.11/demo/js/archive/1.3.2/demo-helper-mootools.js b/archive/1.3.11/demo/js/archive/1.3.2/demo-helper-mootools.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.2/demo-helper-mootools.js rename to archive/1.3.11/demo/js/archive/1.3.2/demo-helper-mootools.js diff --git a/build/1.3.11/demo/js/archive/1.3.2/demo-helper-yui3.js b/archive/1.3.11/demo/js/archive/1.3.2/demo-helper-yui3.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.2/demo-helper-yui3.js rename to archive/1.3.11/demo/js/archive/1.3.2/demo-helper-yui3.js diff --git a/build/1.3.11/demo/js/archive/1.3.2/demo.js b/archive/1.3.11/demo/js/archive/1.3.2/demo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.2/demo.js rename to archive/1.3.11/demo/js/archive/1.3.2/demo.js diff --git a/build/1.3.11/demo/js/archive/1.3.2/dragAnimDemo-jquery.js b/archive/1.3.11/demo/js/archive/1.3.2/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.2/dragAnimDemo-jquery.js rename to archive/1.3.11/demo/js/archive/1.3.2/dragAnimDemo-jquery.js diff --git a/build/1.3.11/demo/js/archive/1.3.2/dragAnimDemo-mootools.js b/archive/1.3.11/demo/js/archive/1.3.2/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.2/dragAnimDemo-mootools.js rename to archive/1.3.11/demo/js/archive/1.3.2/dragAnimDemo-mootools.js diff --git a/build/1.3.11/demo/js/archive/1.3.2/dragAnimDemo-yui3.js b/archive/1.3.11/demo/js/archive/1.3.2/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.2/dragAnimDemo-yui3.js rename to archive/1.3.11/demo/js/archive/1.3.2/dragAnimDemo-yui3.js diff --git a/build/1.3.11/demo/js/archive/1.3.2/dragAnimDemo.js b/archive/1.3.11/demo/js/archive/1.3.2/dragAnimDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.2/dragAnimDemo.js rename to archive/1.3.11/demo/js/archive/1.3.2/dragAnimDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js b/archive/1.3.11/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js rename to archive/1.3.11/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.11/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js b/archive/1.3.11/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js rename to archive/1.3.11/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.11/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js b/archive/1.3.11/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js rename to archive/1.3.11/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.11/demo/js/archive/1.3.2/draggableConnectorsDemo.js b/archive/1.3.11/demo/js/archive/1.3.2/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.2/draggableConnectorsDemo.js rename to archive/1.3.11/demo/js/archive/1.3.2/draggableConnectorsDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.2/dynamicAnchorsDemo.js b/archive/1.3.11/demo/js/archive/1.3.2/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.2/dynamicAnchorsDemo.js rename to archive/1.3.11/demo/js/archive/1.3.2/dynamicAnchorsDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.2/flowchartConnectorsDemo.js b/archive/1.3.11/demo/js/archive/1.3.2/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.2/flowchartConnectorsDemo.js rename to archive/1.3.11/demo/js/archive/1.3.2/flowchartConnectorsDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.3/anchorDemo.js b/archive/1.3.11/demo/js/archive/1.3.3/anchorDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.3/anchorDemo.js rename to archive/1.3.11/demo/js/archive/1.3.3/anchorDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.3/chartDemo.js b/archive/1.3.11/demo/js/archive/1.3.3/chartDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.3/chartDemo.js rename to archive/1.3.11/demo/js/archive/1.3.3/chartDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.3/demo-helper-jquery.js b/archive/1.3.11/demo/js/archive/1.3.3/demo-helper-jquery.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.3/demo-helper-jquery.js rename to archive/1.3.11/demo/js/archive/1.3.3/demo-helper-jquery.js diff --git a/build/1.3.11/demo/js/archive/1.3.3/demo-helper-mootools.js b/archive/1.3.11/demo/js/archive/1.3.3/demo-helper-mootools.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.3/demo-helper-mootools.js rename to archive/1.3.11/demo/js/archive/1.3.3/demo-helper-mootools.js diff --git a/build/1.3.11/demo/js/archive/1.3.3/demo-helper-yui3.js b/archive/1.3.11/demo/js/archive/1.3.3/demo-helper-yui3.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.3/demo-helper-yui3.js rename to archive/1.3.11/demo/js/archive/1.3.3/demo-helper-yui3.js diff --git a/build/1.3.11/demo/js/archive/1.3.3/demo.js b/archive/1.3.11/demo/js/archive/1.3.3/demo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.3/demo.js rename to archive/1.3.11/demo/js/archive/1.3.3/demo.js diff --git a/build/1.3.11/demo/js/archive/1.3.3/dragAnimDemo-jquery.js b/archive/1.3.11/demo/js/archive/1.3.3/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.3/dragAnimDemo-jquery.js rename to archive/1.3.11/demo/js/archive/1.3.3/dragAnimDemo-jquery.js diff --git a/build/1.3.11/demo/js/archive/1.3.3/dragAnimDemo-mootools.js b/archive/1.3.11/demo/js/archive/1.3.3/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.3/dragAnimDemo-mootools.js rename to archive/1.3.11/demo/js/archive/1.3.3/dragAnimDemo-mootools.js diff --git a/build/1.3.11/demo/js/archive/1.3.3/dragAnimDemo-yui3.js b/archive/1.3.11/demo/js/archive/1.3.3/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.3/dragAnimDemo-yui3.js rename to archive/1.3.11/demo/js/archive/1.3.3/dragAnimDemo-yui3.js diff --git a/build/1.3.11/demo/js/archive/1.3.3/dragAnimDemo.js b/archive/1.3.11/demo/js/archive/1.3.3/dragAnimDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.3/dragAnimDemo.js rename to archive/1.3.11/demo/js/archive/1.3.3/dragAnimDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js b/archive/1.3.11/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js rename to archive/1.3.11/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.11/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js b/archive/1.3.11/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js rename to archive/1.3.11/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.11/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js b/archive/1.3.11/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js rename to archive/1.3.11/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.11/demo/js/archive/1.3.3/draggableConnectorsDemo.js b/archive/1.3.11/demo/js/archive/1.3.3/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.3/draggableConnectorsDemo.js rename to archive/1.3.11/demo/js/archive/1.3.3/draggableConnectorsDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.3/dynamicAnchorsDemo.js b/archive/1.3.11/demo/js/archive/1.3.3/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.3/dynamicAnchorsDemo.js rename to archive/1.3.11/demo/js/archive/1.3.3/dynamicAnchorsDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.3/flowchartConnectorsDemo.js b/archive/1.3.11/demo/js/archive/1.3.3/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.3/flowchartConnectorsDemo.js rename to archive/1.3.11/demo/js/archive/1.3.3/flowchartConnectorsDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js b/archive/1.3.11/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js rename to archive/1.3.11/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js diff --git a/build/1.3.11/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all.js b/archive/1.3.11/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all.js rename to archive/1.3.11/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all.js diff --git a/build/1.3.11/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js b/archive/1.3.11/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js rename to archive/1.3.11/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js diff --git a/build/1.3.11/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js b/archive/1.3.11/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js rename to archive/1.3.11/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js diff --git a/build/1.3.11/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js b/archive/1.3.11/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js rename to archive/1.3.11/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js diff --git a/build/1.3.11/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js b/archive/1.3.11/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js rename to archive/1.3.11/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js diff --git a/build/1.3.11/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js b/archive/1.3.11/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js rename to archive/1.3.11/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js diff --git a/build/1.3.11/demo/js/archive/1.3.4/anchorDemo.js b/archive/1.3.11/demo/js/archive/1.3.4/anchorDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.4/anchorDemo.js rename to archive/1.3.11/demo/js/archive/1.3.4/anchorDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.4/chartDemo.js b/archive/1.3.11/demo/js/archive/1.3.4/chartDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.4/chartDemo.js rename to archive/1.3.11/demo/js/archive/1.3.4/chartDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.4/demo-helper-jquery.js b/archive/1.3.11/demo/js/archive/1.3.4/demo-helper-jquery.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.4/demo-helper-jquery.js rename to archive/1.3.11/demo/js/archive/1.3.4/demo-helper-jquery.js diff --git a/build/1.3.11/demo/js/archive/1.3.4/demo-helper-mootools.js b/archive/1.3.11/demo/js/archive/1.3.4/demo-helper-mootools.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.4/demo-helper-mootools.js rename to archive/1.3.11/demo/js/archive/1.3.4/demo-helper-mootools.js diff --git a/build/1.3.11/demo/js/archive/1.3.4/demo-helper-yui3.js b/archive/1.3.11/demo/js/archive/1.3.4/demo-helper-yui3.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.4/demo-helper-yui3.js rename to archive/1.3.11/demo/js/archive/1.3.4/demo-helper-yui3.js diff --git a/build/1.3.11/demo/js/archive/1.3.4/demo-list.js b/archive/1.3.11/demo/js/archive/1.3.4/demo-list.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.4/demo-list.js rename to archive/1.3.11/demo/js/archive/1.3.4/demo-list.js diff --git a/build/1.3.11/demo/js/archive/1.3.4/demo.js b/archive/1.3.11/demo/js/archive/1.3.4/demo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.4/demo.js rename to archive/1.3.11/demo/js/archive/1.3.4/demo.js diff --git a/build/1.3.11/demo/js/archive/1.3.4/dragAnimDemo-jquery.js b/archive/1.3.11/demo/js/archive/1.3.4/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.4/dragAnimDemo-jquery.js rename to archive/1.3.11/demo/js/archive/1.3.4/dragAnimDemo-jquery.js diff --git a/build/1.3.11/demo/js/archive/1.3.4/dragAnimDemo-mootools.js b/archive/1.3.11/demo/js/archive/1.3.4/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.4/dragAnimDemo-mootools.js rename to archive/1.3.11/demo/js/archive/1.3.4/dragAnimDemo-mootools.js diff --git a/build/1.3.11/demo/js/archive/1.3.4/dragAnimDemo-yui3.js b/archive/1.3.11/demo/js/archive/1.3.4/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.4/dragAnimDemo-yui3.js rename to archive/1.3.11/demo/js/archive/1.3.4/dragAnimDemo-yui3.js diff --git a/build/1.3.11/demo/js/archive/1.3.4/dragAnimDemo.js b/archive/1.3.11/demo/js/archive/1.3.4/dragAnimDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.4/dragAnimDemo.js rename to archive/1.3.11/demo/js/archive/1.3.4/dragAnimDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.4/draggableConnectorsDemo-jquery.js b/archive/1.3.11/demo/js/archive/1.3.4/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.4/draggableConnectorsDemo-jquery.js rename to archive/1.3.11/demo/js/archive/1.3.4/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.11/demo/js/archive/1.3.4/draggableConnectorsDemo-mootools.js b/archive/1.3.11/demo/js/archive/1.3.4/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.4/draggableConnectorsDemo-mootools.js rename to archive/1.3.11/demo/js/archive/1.3.4/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.11/demo/js/archive/1.3.4/draggableConnectorsDemo-yui3.js b/archive/1.3.11/demo/js/archive/1.3.4/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.4/draggableConnectorsDemo-yui3.js rename to archive/1.3.11/demo/js/archive/1.3.4/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.11/demo/js/archive/1.3.4/draggableConnectorsDemo.js b/archive/1.3.11/demo/js/archive/1.3.4/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.4/draggableConnectorsDemo.js rename to archive/1.3.11/demo/js/archive/1.3.4/draggableConnectorsDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.4/dynamicAnchorsDemo.js b/archive/1.3.11/demo/js/archive/1.3.4/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.4/dynamicAnchorsDemo.js rename to archive/1.3.11/demo/js/archive/1.3.4/dynamicAnchorsDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.4/flowchartConnectorsDemo.js b/archive/1.3.11/demo/js/archive/1.3.4/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.4/flowchartConnectorsDemo.js rename to archive/1.3.11/demo/js/archive/1.3.4/flowchartConnectorsDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.4/makeSourceDemo.js b/archive/1.3.11/demo/js/archive/1.3.4/makeSourceDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.4/makeSourceDemo.js rename to archive/1.3.11/demo/js/archive/1.3.4/makeSourceDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.4/makeTargetDemo.js b/archive/1.3.11/demo/js/archive/1.3.4/makeTargetDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.4/makeTargetDemo.js rename to archive/1.3.11/demo/js/archive/1.3.4/makeTargetDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.4/stateMachineDemo-jquery.js b/archive/1.3.11/demo/js/archive/1.3.4/stateMachineDemo-jquery.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.4/stateMachineDemo-jquery.js rename to archive/1.3.11/demo/js/archive/1.3.4/stateMachineDemo-jquery.js diff --git a/build/1.3.11/demo/js/archive/1.3.4/stateMachineDemo-mootools.js b/archive/1.3.11/demo/js/archive/1.3.4/stateMachineDemo-mootools.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.4/stateMachineDemo-mootools.js rename to archive/1.3.11/demo/js/archive/1.3.4/stateMachineDemo-mootools.js diff --git a/build/1.3.11/demo/js/archive/1.3.4/stateMachineDemo-yui3.js b/archive/1.3.11/demo/js/archive/1.3.4/stateMachineDemo-yui3.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.4/stateMachineDemo-yui3.js rename to archive/1.3.11/demo/js/archive/1.3.4/stateMachineDemo-yui3.js diff --git a/build/1.3.11/demo/js/archive/1.3.4/stateMachineDemo.js b/archive/1.3.11/demo/js/archive/1.3.4/stateMachineDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.4/stateMachineDemo.js rename to archive/1.3.11/demo/js/archive/1.3.4/stateMachineDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.5/anchorDemo.js b/archive/1.3.11/demo/js/archive/1.3.5/anchorDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.5/anchorDemo.js rename to archive/1.3.11/demo/js/archive/1.3.5/anchorDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.5/chartDemo.js b/archive/1.3.11/demo/js/archive/1.3.5/chartDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.5/chartDemo.js rename to archive/1.3.11/demo/js/archive/1.3.5/chartDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.5/demo-helper-jquery.js b/archive/1.3.11/demo/js/archive/1.3.5/demo-helper-jquery.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.5/demo-helper-jquery.js rename to archive/1.3.11/demo/js/archive/1.3.5/demo-helper-jquery.js diff --git a/build/1.3.11/demo/js/archive/1.3.5/demo-helper-mootools.js b/archive/1.3.11/demo/js/archive/1.3.5/demo-helper-mootools.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.5/demo-helper-mootools.js rename to archive/1.3.11/demo/js/archive/1.3.5/demo-helper-mootools.js diff --git a/build/1.3.11/demo/js/archive/1.3.5/demo-helper-yui3.js b/archive/1.3.11/demo/js/archive/1.3.5/demo-helper-yui3.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.5/demo-helper-yui3.js rename to archive/1.3.11/demo/js/archive/1.3.5/demo-helper-yui3.js diff --git a/build/1.3.11/demo/js/archive/1.3.5/demo-list.js b/archive/1.3.11/demo/js/archive/1.3.5/demo-list.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.5/demo-list.js rename to archive/1.3.11/demo/js/archive/1.3.5/demo-list.js diff --git a/build/1.3.11/demo/js/archive/1.3.5/demo.js b/archive/1.3.11/demo/js/archive/1.3.5/demo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.5/demo.js rename to archive/1.3.11/demo/js/archive/1.3.5/demo.js diff --git a/build/1.3.11/demo/js/archive/1.3.5/dragAnimDemo-jquery.js b/archive/1.3.11/demo/js/archive/1.3.5/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.5/dragAnimDemo-jquery.js rename to archive/1.3.11/demo/js/archive/1.3.5/dragAnimDemo-jquery.js diff --git a/build/1.3.11/demo/js/archive/1.3.5/dragAnimDemo-mootools.js b/archive/1.3.11/demo/js/archive/1.3.5/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.5/dragAnimDemo-mootools.js rename to archive/1.3.11/demo/js/archive/1.3.5/dragAnimDemo-mootools.js diff --git a/build/1.3.11/demo/js/archive/1.3.5/dragAnimDemo-yui3.js b/archive/1.3.11/demo/js/archive/1.3.5/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.5/dragAnimDemo-yui3.js rename to archive/1.3.11/demo/js/archive/1.3.5/dragAnimDemo-yui3.js diff --git a/build/1.3.11/demo/js/archive/1.3.5/dragAnimDemo.js b/archive/1.3.11/demo/js/archive/1.3.5/dragAnimDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.5/dragAnimDemo.js rename to archive/1.3.11/demo/js/archive/1.3.5/dragAnimDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.5/draggableConnectorsDemo-jquery.js b/archive/1.3.11/demo/js/archive/1.3.5/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.5/draggableConnectorsDemo-jquery.js rename to archive/1.3.11/demo/js/archive/1.3.5/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.11/demo/js/archive/1.3.5/draggableConnectorsDemo-mootools.js b/archive/1.3.11/demo/js/archive/1.3.5/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.5/draggableConnectorsDemo-mootools.js rename to archive/1.3.11/demo/js/archive/1.3.5/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.11/demo/js/archive/1.3.5/draggableConnectorsDemo-yui3.js b/archive/1.3.11/demo/js/archive/1.3.5/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.5/draggableConnectorsDemo-yui3.js rename to archive/1.3.11/demo/js/archive/1.3.5/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.11/demo/js/archive/1.3.5/draggableConnectorsDemo.js b/archive/1.3.11/demo/js/archive/1.3.5/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.5/draggableConnectorsDemo.js rename to archive/1.3.11/demo/js/archive/1.3.5/draggableConnectorsDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.5/dynamicAnchorsDemo.js b/archive/1.3.11/demo/js/archive/1.3.5/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.5/dynamicAnchorsDemo.js rename to archive/1.3.11/demo/js/archive/1.3.5/dynamicAnchorsDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.5/flowchartConnectorsDemo.js b/archive/1.3.11/demo/js/archive/1.3.5/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.5/flowchartConnectorsDemo.js rename to archive/1.3.11/demo/js/archive/1.3.5/flowchartConnectorsDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.5/loadTest.js b/archive/1.3.11/demo/js/archive/1.3.5/loadTest.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.5/loadTest.js rename to archive/1.3.11/demo/js/archive/1.3.5/loadTest.js diff --git a/build/1.3.11/demo/js/archive/1.3.5/makeSourceDemo.js b/archive/1.3.11/demo/js/archive/1.3.5/makeSourceDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.5/makeSourceDemo.js rename to archive/1.3.11/demo/js/archive/1.3.5/makeSourceDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.5/makeTargetDemo.js b/archive/1.3.11/demo/js/archive/1.3.5/makeTargetDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.5/makeTargetDemo.js rename to archive/1.3.11/demo/js/archive/1.3.5/makeTargetDemo.js diff --git a/build/1.3.11/demo/js/archive/1.3.5/stateMachineDemo-jquery.js b/archive/1.3.11/demo/js/archive/1.3.5/stateMachineDemo-jquery.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.5/stateMachineDemo-jquery.js rename to archive/1.3.11/demo/js/archive/1.3.5/stateMachineDemo-jquery.js diff --git a/build/1.3.11/demo/js/archive/1.3.5/stateMachineDemo-mootools.js b/archive/1.3.11/demo/js/archive/1.3.5/stateMachineDemo-mootools.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.5/stateMachineDemo-mootools.js rename to archive/1.3.11/demo/js/archive/1.3.5/stateMachineDemo-mootools.js diff --git a/build/1.3.11/demo/js/archive/1.3.5/stateMachineDemo-yui3.js b/archive/1.3.11/demo/js/archive/1.3.5/stateMachineDemo-yui3.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.5/stateMachineDemo-yui3.js rename to archive/1.3.11/demo/js/archive/1.3.5/stateMachineDemo-yui3.js diff --git a/build/1.3.11/demo/js/archive/1.3.5/stateMachineDemo.js b/archive/1.3.11/demo/js/archive/1.3.5/stateMachineDemo.js similarity index 100% rename from build/1.3.11/demo/js/archive/1.3.5/stateMachineDemo.js rename to archive/1.3.11/demo/js/archive/1.3.5/stateMachineDemo.js diff --git a/build/1.3.11/demo/js/chartDemo.js b/archive/1.3.11/demo/js/chartDemo.js similarity index 100% rename from build/1.3.11/demo/js/chartDemo.js rename to archive/1.3.11/demo/js/chartDemo.js diff --git a/build/1.3.11/demo/js/demo-helper-jquery.js b/archive/1.3.11/demo/js/demo-helper-jquery.js similarity index 100% rename from build/1.3.11/demo/js/demo-helper-jquery.js rename to archive/1.3.11/demo/js/demo-helper-jquery.js diff --git a/build/1.3.11/demo/js/demo-helper-mootools.js b/archive/1.3.11/demo/js/demo-helper-mootools.js similarity index 100% rename from build/1.3.11/demo/js/demo-helper-mootools.js rename to archive/1.3.11/demo/js/demo-helper-mootools.js diff --git a/build/1.3.11/demo/js/demo-helper-yui3.js b/archive/1.3.11/demo/js/demo-helper-yui3.js similarity index 100% rename from build/1.3.11/demo/js/demo-helper-yui3.js rename to archive/1.3.11/demo/js/demo-helper-yui3.js diff --git a/build/1.3.11/demo/js/demo-list.js b/archive/1.3.11/demo/js/demo-list.js similarity index 100% rename from build/1.3.11/demo/js/demo-list.js rename to archive/1.3.11/demo/js/demo-list.js diff --git a/build/1.3.11/demo/js/demo.js b/archive/1.3.11/demo/js/demo.js similarity index 100% rename from build/1.3.11/demo/js/demo.js rename to archive/1.3.11/demo/js/demo.js diff --git a/build/1.3.11/demo/js/dragAnimDemo-jquery.js b/archive/1.3.11/demo/js/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.11/demo/js/dragAnimDemo-jquery.js rename to archive/1.3.11/demo/js/dragAnimDemo-jquery.js diff --git a/build/1.3.11/demo/js/dragAnimDemo-mootools.js b/archive/1.3.11/demo/js/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.11/demo/js/dragAnimDemo-mootools.js rename to archive/1.3.11/demo/js/dragAnimDemo-mootools.js diff --git a/build/1.3.11/demo/js/dragAnimDemo-yui3.js b/archive/1.3.11/demo/js/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.11/demo/js/dragAnimDemo-yui3.js rename to archive/1.3.11/demo/js/dragAnimDemo-yui3.js diff --git a/build/1.3.11/demo/js/dragAnimDemo.js b/archive/1.3.11/demo/js/dragAnimDemo.js similarity index 100% rename from build/1.3.11/demo/js/dragAnimDemo.js rename to archive/1.3.11/demo/js/dragAnimDemo.js diff --git a/build/1.3.11/demo/js/draggableConnectorsDemo-jquery.js b/archive/1.3.11/demo/js/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.11/demo/js/draggableConnectorsDemo-jquery.js rename to archive/1.3.11/demo/js/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.11/demo/js/draggableConnectorsDemo-mootools.js b/archive/1.3.11/demo/js/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.11/demo/js/draggableConnectorsDemo-mootools.js rename to archive/1.3.11/demo/js/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.11/demo/js/draggableConnectorsDemo-yui3.js b/archive/1.3.11/demo/js/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.11/demo/js/draggableConnectorsDemo-yui3.js rename to archive/1.3.11/demo/js/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.11/demo/js/draggableConnectorsDemo.js b/archive/1.3.11/demo/js/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.11/demo/js/draggableConnectorsDemo.js rename to archive/1.3.11/demo/js/draggableConnectorsDemo.js diff --git a/build/1.3.11/demo/js/dynamicAnchorsDemo.js b/archive/1.3.11/demo/js/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.11/demo/js/dynamicAnchorsDemo.js rename to archive/1.3.11/demo/js/dynamicAnchorsDemo.js diff --git a/build/1.3.11/demo/js/flowchartConnectorsDemo.js b/archive/1.3.11/demo/js/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.11/demo/js/flowchartConnectorsDemo.js rename to archive/1.3.11/demo/js/flowchartConnectorsDemo.js diff --git a/build/1.3.11/demo/js/jquery.jsPlumb-1.3.11-all-min.js b/archive/1.3.11/demo/js/jquery.jsPlumb-1.3.11-all-min.js similarity index 100% rename from build/1.3.11/demo/js/jquery.jsPlumb-1.3.11-all-min.js rename to archive/1.3.11/demo/js/jquery.jsPlumb-1.3.11-all-min.js diff --git a/build/1.3.11/demo/js/jquery.jsPlumb-1.3.11-all.js b/archive/1.3.11/demo/js/jquery.jsPlumb-1.3.11-all.js similarity index 100% rename from build/1.3.11/demo/js/jquery.jsPlumb-1.3.11-all.js rename to archive/1.3.11/demo/js/jquery.jsPlumb-1.3.11-all.js diff --git a/build/1.3.11/demo/js/loadTest.js b/archive/1.3.11/demo/js/loadTest.js similarity index 100% rename from build/1.3.11/demo/js/loadTest.js rename to archive/1.3.11/demo/js/loadTest.js diff --git a/build/1.3.11/demo/js/makeSourceDemo.js b/archive/1.3.11/demo/js/makeSourceDemo.js similarity index 100% rename from build/1.3.11/demo/js/makeSourceDemo.js rename to archive/1.3.11/demo/js/makeSourceDemo.js diff --git a/build/1.3.11/demo/js/makeTargetDemo.js b/archive/1.3.11/demo/js/makeTargetDemo.js similarity index 100% rename from build/1.3.11/demo/js/makeTargetDemo.js rename to archive/1.3.11/demo/js/makeTargetDemo.js diff --git a/build/1.3.11/demo/js/mootools-1.3.2.1-more.js b/archive/1.3.11/demo/js/mootools-1.3.2.1-more.js similarity index 100% rename from build/1.3.11/demo/js/mootools-1.3.2.1-more.js rename to archive/1.3.11/demo/js/mootools-1.3.2.1-more.js diff --git a/build/1.3.11/demo/js/mootools.jsPlumb-1.3.11-all-min.js b/archive/1.3.11/demo/js/mootools.jsPlumb-1.3.11-all-min.js similarity index 100% rename from build/1.3.11/demo/js/mootools.jsPlumb-1.3.11-all-min.js rename to archive/1.3.11/demo/js/mootools.jsPlumb-1.3.11-all-min.js diff --git a/build/1.3.11/demo/js/mootools.jsPlumb-1.3.11-all.js b/archive/1.3.11/demo/js/mootools.jsPlumb-1.3.11-all.js similarity index 100% rename from build/1.3.11/demo/js/mootools.jsPlumb-1.3.11-all.js rename to archive/1.3.11/demo/js/mootools.jsPlumb-1.3.11-all.js diff --git a/build/1.3.11/demo/js/stateMachineDemo-jquery.js b/archive/1.3.11/demo/js/stateMachineDemo-jquery.js similarity index 100% rename from build/1.3.11/demo/js/stateMachineDemo-jquery.js rename to archive/1.3.11/demo/js/stateMachineDemo-jquery.js diff --git a/build/1.3.11/demo/js/stateMachineDemo-mootools.js b/archive/1.3.11/demo/js/stateMachineDemo-mootools.js similarity index 100% rename from build/1.3.11/demo/js/stateMachineDemo-mootools.js rename to archive/1.3.11/demo/js/stateMachineDemo-mootools.js diff --git a/build/1.3.11/demo/js/stateMachineDemo-yui3.js b/archive/1.3.11/demo/js/stateMachineDemo-yui3.js similarity index 100% rename from build/1.3.11/demo/js/stateMachineDemo-yui3.js rename to archive/1.3.11/demo/js/stateMachineDemo-yui3.js diff --git a/build/1.3.11/demo/js/stateMachineDemo.js b/archive/1.3.11/demo/js/stateMachineDemo.js similarity index 100% rename from build/1.3.11/demo/js/stateMachineDemo.js rename to archive/1.3.11/demo/js/stateMachineDemo.js diff --git a/build/1.3.11/demo/js/yui-3.3.0-min.js b/archive/1.3.11/demo/js/yui-3.3.0-min.js similarity index 100% rename from build/1.3.11/demo/js/yui-3.3.0-min.js rename to archive/1.3.11/demo/js/yui-3.3.0-min.js diff --git a/build/1.3.11/demo/js/yui.jsPlumb-1.3.11-all-min.js b/archive/1.3.11/demo/js/yui.jsPlumb-1.3.11-all-min.js similarity index 100% rename from build/1.3.11/demo/js/yui.jsPlumb-1.3.11-all-min.js rename to archive/1.3.11/demo/js/yui.jsPlumb-1.3.11-all-min.js diff --git a/build/1.3.11/demo/js/yui.jsPlumb-1.3.11-all.js b/archive/1.3.11/demo/js/yui.jsPlumb-1.3.11-all.js similarity index 100% rename from build/1.3.11/demo/js/yui.jsPlumb-1.3.11-all.js rename to archive/1.3.11/demo/js/yui.jsPlumb-1.3.11-all.js diff --git a/build/1.3.11/demo/mootools/anchorDemo.html b/archive/1.3.11/demo/mootools/anchorDemo.html similarity index 100% rename from build/1.3.11/demo/mootools/anchorDemo.html rename to archive/1.3.11/demo/mootools/anchorDemo.html diff --git a/build/1.3.11/demo/mootools/chartDemo.html b/archive/1.3.11/demo/mootools/chartDemo.html similarity index 100% rename from build/1.3.11/demo/mootools/chartDemo.html rename to archive/1.3.11/demo/mootools/chartDemo.html diff --git a/build/1.3.11/demo/mootools/demo.html b/archive/1.3.11/demo/mootools/demo.html similarity index 100% rename from build/1.3.11/demo/mootools/demo.html rename to archive/1.3.11/demo/mootools/demo.html diff --git a/build/1.3.11/demo/mootools/dragAnimDemo.html b/archive/1.3.11/demo/mootools/dragAnimDemo.html similarity index 100% rename from build/1.3.11/demo/mootools/dragAnimDemo.html rename to archive/1.3.11/demo/mootools/dragAnimDemo.html diff --git a/build/1.3.11/demo/mootools/draggableConnectorsDemo.html b/archive/1.3.11/demo/mootools/draggableConnectorsDemo.html similarity index 100% rename from build/1.3.11/demo/mootools/draggableConnectorsDemo.html rename to archive/1.3.11/demo/mootools/draggableConnectorsDemo.html diff --git a/build/1.3.11/demo/mootools/dynamicAnchorsDemo.html b/archive/1.3.11/demo/mootools/dynamicAnchorsDemo.html similarity index 100% rename from build/1.3.11/demo/mootools/dynamicAnchorsDemo.html rename to archive/1.3.11/demo/mootools/dynamicAnchorsDemo.html diff --git a/build/1.3.11/demo/mootools/flowchartConnectorsDemo.html b/archive/1.3.11/demo/mootools/flowchartConnectorsDemo.html similarity index 100% rename from build/1.3.11/demo/mootools/flowchartConnectorsDemo.html rename to archive/1.3.11/demo/mootools/flowchartConnectorsDemo.html diff --git a/build/1.3.11/demo/mootools/stateMachineDemo.html b/archive/1.3.11/demo/mootools/stateMachineDemo.html similarity index 100% rename from build/1.3.11/demo/mootools/stateMachineDemo.html rename to archive/1.3.11/demo/mootools/stateMachineDemo.html diff --git a/build/1.3.11/demo/yui3/anchorDemo.html b/archive/1.3.11/demo/yui3/anchorDemo.html similarity index 100% rename from build/1.3.11/demo/yui3/anchorDemo.html rename to archive/1.3.11/demo/yui3/anchorDemo.html diff --git a/build/1.3.11/demo/yui3/chartDemo.html b/archive/1.3.11/demo/yui3/chartDemo.html similarity index 100% rename from build/1.3.11/demo/yui3/chartDemo.html rename to archive/1.3.11/demo/yui3/chartDemo.html diff --git a/build/1.3.11/demo/yui3/demo.html b/archive/1.3.11/demo/yui3/demo.html similarity index 100% rename from build/1.3.11/demo/yui3/demo.html rename to archive/1.3.11/demo/yui3/demo.html diff --git a/build/1.3.11/demo/yui3/dragAnimDemo.html b/archive/1.3.11/demo/yui3/dragAnimDemo.html similarity index 100% rename from build/1.3.11/demo/yui3/dragAnimDemo.html rename to archive/1.3.11/demo/yui3/dragAnimDemo.html diff --git a/build/1.3.11/demo/yui3/draggableConnectorsDemo.html b/archive/1.3.11/demo/yui3/draggableConnectorsDemo.html similarity index 100% rename from build/1.3.11/demo/yui3/draggableConnectorsDemo.html rename to archive/1.3.11/demo/yui3/draggableConnectorsDemo.html diff --git a/build/1.3.11/demo/yui3/dynamicAnchorsDemo.html b/archive/1.3.11/demo/yui3/dynamicAnchorsDemo.html similarity index 100% rename from build/1.3.11/demo/yui3/dynamicAnchorsDemo.html rename to archive/1.3.11/demo/yui3/dynamicAnchorsDemo.html diff --git a/build/1.3.11/demo/yui3/flowchartConnectorsDemo.html b/archive/1.3.11/demo/yui3/flowchartConnectorsDemo.html similarity index 100% rename from build/1.3.11/demo/yui3/flowchartConnectorsDemo.html rename to archive/1.3.11/demo/yui3/flowchartConnectorsDemo.html diff --git a/build/1.3.11/demo/yui3/stateMachineDemo.html b/archive/1.3.11/demo/yui3/stateMachineDemo.html similarity index 100% rename from build/1.3.11/demo/yui3/stateMachineDemo.html rename to archive/1.3.11/demo/yui3/stateMachineDemo.html diff --git a/archive/1.3.11/jquery.jsPlumb-1.3.11-RC1.js b/archive/1.3.11/jquery.jsPlumb-1.3.11-RC1.js new file mode 100644 index 000000000..39c255250 --- /dev/null +++ b/archive/1.3.11/jquery.jsPlumb-1.3.11-RC1.js @@ -0,0 +1,374 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.11 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the jQuery adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ +/* + * the library specific functions, such as find offset, get id, get attribute, extend etc. + * the full list is: + * + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getDropScope gets the drop scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getOriginalEvent gets the original browser event from some wrapper event + * getPageXY gets the page event's xy location. + * getParent gets the parent of some element. + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * hasClass returns whether or not the given element has the given class. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + * trigger triggers some event on an element. + * unbind unbinds some listener from some element. + */ +(function($) { + + //var getBoundingClientRectSupported = "getBoundingClientRect" in document.documentElement; + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el[0].className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.addClass(el[0], clazz); + } + } + catch (e) { + // SVGAnimatedString not supported; no problem. + } + el.addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * appends the given child to the given parent. + */ + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).append(child); + }, + + /** + * executes ana ajax call. + */ + ajax : function(params) { + params = params || {}; + params.type = params.type || "get"; + $.ajax(params); + }, + + /** + * event binding wrapper. it just so happens that jQuery uses 'bind' also. yui3, for example, + * uses 'on'. + */ + bind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.bind(event, callback); + }, + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step', + 'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete' + }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + getDocumentElement : function() { return document; }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + }, + + getDragScope : function(el) { + return el.draggable("option", "scope"); + }, + + getDropEvent : function(args) { + return args[0]; + }, + + getDropScope : function(el) { + return el.droppable("option", "scope"); + }, + + /** + * gets a DOM element from the given input, which might be a string (in which case we just do document.getElementById), + * a selector (in which case we return el[0]), or a DOM element already (we assume this if it's not either of the other + * two cases). this is the opposite of getElementObject below. + */ + getDOMElement : function(el) { + if (typeof(el) == "string") return document.getElementById(el); + else if (el.context) return el[0]; + else return el; + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + * + */ + getElementObject : function(el) { + return typeof(el) == "string" ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + getOriginalEvent : function(e) { + return e.originalEvent; + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).parent(); + }, + + getScrollLeft : function(el) { + return el.scrollLeft(); + }, + + getScrollTop : function(el) { + return el.scrollTop(); + }, + + getSelector : function(spec) { + return $(spec); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e.length > 0 ? e[0].tagName : null; + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + + // this code is a workaround for the case that the element being dragged has a margin set on it. jquery UI passes + // in the wrong offset if the element has a margin (it doesn't take the margin into account). the getBoundingClientRect + // method, which is in pretty much all browsers now, reports the right numbers. but it introduces a noticeable lag, which + // i don't like. + + /*if ( getBoundingClientRectSupported ) { + var r = eventArgs[1].helper[0].getBoundingClientRect(); + return { left : r.left, top: r.top }; + } else {*/ + if (eventArgs.length == 1) { + ret = { left: eventArgs[0].pageX, top:eventArgs[0].pageY }; + } + else { + var ui = eventArgs[1], _offset = ui.offset; + ret = _offset || ui.absolutePosition; + } + return ret; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options, isPlumbedComponent) { + options = options || {}; + // remove helper directive if present. + options.helper = null; + if (isPlumbedComponent) + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.draggable(options); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.droppable(options); + }, + + isAlreadyDraggable : function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return el.hasClass("ui-draggable"); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el[0].className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.removeClass(el[0], clazz); + } + } + catch (e) { + // SVGAnimatedString not supported; no problem. + } + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).remove(); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * sets the drag scope. probably time for a setDragOption method (roll this and the one above together) + * @param el + * @param scope + */ + setDragScope : function(el, scope) { + el.draggable("option", "scope", scope); + }, + + setOffset : function(el, o) { + jsPlumb.CurrentLibrary.getElementObject(el).offset(o); + }, + + /** + * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself. + * the other libraries do not. yui, in fact, cannot even pass an original event. we have to pull out stuff + * from the originalEvent to put in an options object for YUI. + * @param el + * @param event + * @param originalEvent + */ + trigger : function(el, event, originalEvent) { + //originalEvent.stopPropagation(); + //jsPlumb.CurrentLibrary.getElementObject(el).trigger(originalEvent); + var h = jQuery._data(jsPlumb.CurrentLibrary.getElementObject(el)[0], "handle"); + h(originalEvent); + //originalEvent.stopPropagation(); + }, + + /** + * event unbinding wrapper. it just so happens that jQuery uses 'unbind' also. yui3, for example, + * uses..something else. + */ + unbind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.unbind(event, callback); + } + }; + + $(document).ready(jsPlumb.init); + +})(jQuery); + diff --git a/build/1.3.11/js/jquery.jsPlumb-1.3.11-all-min.js b/archive/1.3.11/js/jquery.jsPlumb-1.3.11-all-min.js similarity index 100% rename from build/1.3.11/js/jquery.jsPlumb-1.3.11-all-min.js rename to archive/1.3.11/js/jquery.jsPlumb-1.3.11-all-min.js diff --git a/build/1.3.11/js/jquery.jsPlumb-1.3.11-all.js b/archive/1.3.11/js/jquery.jsPlumb-1.3.11-all.js similarity index 100% rename from build/1.3.11/js/jquery.jsPlumb-1.3.11-all.js rename to archive/1.3.11/js/jquery.jsPlumb-1.3.11-all.js diff --git a/build/1.3.11/js/jsPlumb-1.3.11-tests.js b/archive/1.3.11/js/jsPlumb-1.3.11-tests.js similarity index 100% rename from build/1.3.11/js/jsPlumb-1.3.11-tests.js rename to archive/1.3.11/js/jsPlumb-1.3.11-tests.js diff --git a/build/1.3.11/js/lib/qunit.js b/archive/1.3.11/js/lib/qunit.js similarity index 100% rename from build/1.3.11/js/lib/qunit.js rename to archive/1.3.11/js/lib/qunit.js diff --git a/build/1.3.11/js/mootools.jsPlumb-1.3.11-all-min.js b/archive/1.3.11/js/mootools.jsPlumb-1.3.11-all-min.js similarity index 100% rename from build/1.3.11/js/mootools.jsPlumb-1.3.11-all-min.js rename to archive/1.3.11/js/mootools.jsPlumb-1.3.11-all-min.js diff --git a/build/1.3.11/js/mootools.jsPlumb-1.3.11-all.js b/archive/1.3.11/js/mootools.jsPlumb-1.3.11-all.js similarity index 100% rename from build/1.3.11/js/mootools.jsPlumb-1.3.11-all.js rename to archive/1.3.11/js/mootools.jsPlumb-1.3.11-all.js diff --git a/build/1.3.11/js/yui.jsPlumb-1.3.11-all-min.js b/archive/1.3.11/js/yui.jsPlumb-1.3.11-all-min.js similarity index 100% rename from build/1.3.11/js/yui.jsPlumb-1.3.11-all-min.js rename to archive/1.3.11/js/yui.jsPlumb-1.3.11-all-min.js diff --git a/build/1.3.11/js/yui.jsPlumb-1.3.11-all.js b/archive/1.3.11/js/yui.jsPlumb-1.3.11-all.js similarity index 100% rename from build/1.3.11/js/yui.jsPlumb-1.3.11-all.js rename to archive/1.3.11/js/yui.jsPlumb-1.3.11-all.js diff --git a/archive/1.3.11/jsPlumb-1.3.11-RC1.js b/archive/1.3.11/jsPlumb-1.3.11-RC1.js new file mode 100644 index 000000000..769d34989 --- /dev/null +++ b/archive/1.3.11/jsPlumb-1.3.11-RC1.js @@ -0,0 +1,5823 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.11 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the jsPlumb core code. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + /** + * Class:jsPlumb + * The jsPlumb engine, registered as a static object in the window. This object contains all of the methods you will use to + * create and maintain Connections and Endpoints. + */ + + var canvasAvailable = !!document.createElement('canvas').getContext, + svgAvailable = !!window.SVGAngle || document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"), + // http://stackoverflow.com/questions/654112/how-do-you-detect-support-for-vml-or-svg-in-a-browser + vmlAvailable = function() { + if(vmlAvailable.vml == undefined) { + var a = document.body.appendChild(document.createElement('div')); + a.innerHTML = ''; + var b = a.firstChild; + b.style.behavior = "url(#default#VML)"; + vmlAvailable.vml = b ? typeof b.adj == "object": true; + a.parentNode.removeChild(a); + } + return vmlAvailable.vml; + }; + + var _findWithFunction = jsPlumbUtil.findWithFunction, + _indexOf = jsPlumbUtil.indexOf, + _removeWithFunction = jsPlumbUtil.removeWithFunction, + _remove = jsPlumbUtil.remove, + // TODO support insert index + _addWithFunction = jsPlumbUtil.addWithFunction, + _addToList = jsPlumbUtil.addToList, + /** + an isArray function that even works across iframes...see here: + + http://tobyho.com/2011/01/28/checking-types-in-javascript/ + + i was originally using "a.constructor == Array" as a test. + */ + _isArray = jsPlumbUtil.isArray, + _isString = jsPlumbUtil.isString, + _isObject = jsPlumbUtil.isObject; + + // for those browsers that dont have it. they still don't have it! but at least they won't crash. + if (!window.console) + window.console = { time:function(){}, timeEnd:function(){}, group:function(){}, groupEnd:function(){}, log:function(){} }; + + var _connectionBeingDragged = null, + _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }, + _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }, + _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }, + _log = jsPlumbUtil.log, + _group = jsPlumbUtil.group, + _groupEnd = jsPlumbUtil.groupEnd, + _time = jsPlumbUtil.time, + _timeEnd = jsPlumbUtil.timeEnd, + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + _timestamp = function() { return "" + (new Date()).getTime(); }, + + /* + * Class:jsPlumbUIComponent + * Abstract superclass for UI components Endpoint and Connection. Provides the abstraction of paintStyle/hoverPaintStyle, + * and also extends jsPlumbUtil.EventGenerator to provide the bind and fire methods. + */ + jsPlumbUIComponent = function(params) { + var self = this, + a = arguments, + _hover = false, + parameters = params.parameters || {}, + idPrefix = self.idPrefix, + id = idPrefix + (new Date()).getTime(), + paintStyle = null, + hoverPaintStyle = null; + + self._jsPlumb = params["_jsPlumb"]; + self.getId = function() { return id; }; + self.tooltip = params.tooltip; + self.hoverClass = params.hoverClass || self._jsPlumb.Defaults.HoverClass || jsPlumb.Defaults.HoverClass; + + // all components can generate events + jsPlumbUtil.EventGenerator.apply(this); + // all components get this clone function. + // TODO issue 116 showed a problem with this - it seems 'a' that is in + // the clone function's scope is shared by all invocations of it, the classic + // JS closure problem. for now, jsPlumb does a version of this inline where + // it used to call clone. but it would be nice to find some time to look + // further at this. + this.clone = function() { + var o = new Object(); + self.constructor.apply(o, a); + return o; + }; + + this.getParameter = function(name) { return parameters[name]; }, + this.getParameters = function() { + return parameters; + }, + this.setParameter = function(name, value) { parameters[name] = value; }, + this.setParameters = function(p) { parameters = p; }, + this.overlayPlacements = []; + + // user can supply a beforeDetach callback, which will be executed before a detach + // is performed; returning false prevents the detach. + var beforeDetach = params.beforeDetach; + this.isDetachAllowed = function(connection) { + var r = self._jsPlumb.checkCondition("beforeDetach", connection ); + if (beforeDetach) { + try { + r = beforeDetach(connection); + } + catch (e) { _log("jsPlumb: beforeDetach callback failed", e); } + } + return r; + }; + + // user can supply a beforeDrop callback, which will be executed before a dropped + // connection is confirmed. user can return false to reject connection. + var beforeDrop = params.beforeDrop; + this.isDropAllowed = function(sourceId, targetId, scope, connection, dropEndpoint) { + var r = self._jsPlumb.checkCondition("beforeDrop", { + sourceId:sourceId, + targetId:targetId, + scope:scope, + connection:connection, + dropEndpoint:dropEndpoint + }); + if (beforeDrop) { + try { + r = beforeDrop({ + sourceId:sourceId, + targetId:targetId, + scope:scope, + connection:connection, + dropEndpoint:dropEndpoint + }); + } + catch (e) { _log("jsPlumb: beforeDrop callback failed", e); } + } + return r; + }; + + // helper method to update the hover style whenever it, or paintStyle, changes. + // we use paintStyle as the foundation and merge hoverPaintStyle over the + // top. + var _updateHoverStyle = function() { + if (paintStyle && hoverPaintStyle) { + var mergedHoverStyle = {}; + jsPlumb.extend(mergedHoverStyle, paintStyle); + jsPlumb.extend(mergedHoverStyle, hoverPaintStyle); + delete self["hoverPaintStyle"]; + // we want the fillStyle of paintStyle to override a gradient, if possible. + if (mergedHoverStyle.gradient && paintStyle.fillStyle) + delete mergedHoverStyle["gradient"]; + hoverPaintStyle = mergedHoverStyle; + } + }; + + /* + * Sets the paint style and then repaints the element. + * + * Parameters: + * style - Style to use. + */ + this.setPaintStyle = function(style, doNotRepaint) { + paintStyle = style; + self.paintStyleInUse = paintStyle; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /** + * Gets the component's paint style. + * + * Returns: + * the component's paint style. if there is no hoverPaintStyle set then this will be the paint style used all the time, otherwise this is the style used when the mouse is not hovering. + */ + this.getPaintStyle = function() { + return paintStyle; + }; + + /* + * Sets the paint style to use when the mouse is hovering over the element. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the component will not be repainted. useful when setting things up initially. + */ + this.setHoverPaintStyle = function(style, doNotRepaint) { + hoverPaintStyle = style; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /** + * Gets the component's hover paint style. + * + * Returns: + * the component's hover paint style. may be null. + */ + this.getHoverPaintStyle = function() { + return hoverPaintStyle; + }; + + /* + * sets/unsets the hover state of this element. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + this.setHover = function(hover, ignoreAttachedElements, timestamp) { + // while dragging, we ignore these events. this keeps the UI from flashing and + // swishing and whatevering. + if (!self._jsPlumb.currentlyDragging && !self._jsPlumb.isHoverSuspended()) { + + _hover = hover; + if (self.hoverClass != null && self.canvas != null) { + if (hover) + jpcl.addClass(self.canvas, self.hoverClass); + else + jpcl.removeClass(self.canvas, self.hoverClass); + } + if (hoverPaintStyle != null) { + self.paintStyleInUse = hover ? hoverPaintStyle : paintStyle; + timestamp = timestamp || _timestamp(); + self.repaint({timestamp:timestamp, recalc:false}); + } + // get the list of other affected elements, if supported by this component. + // for a connection, its the endpoints. for an endpoint, its the connections! surprise. + if (self.getAttachedElements && !ignoreAttachedElements) + _updateAttachedElements(hover, _timestamp(), self); + } + }; + + this.isHover = function() { return _hover; }; + + var jpcl = jsPlumb.CurrentLibrary, + events = [ "click", "dblclick", "mouseenter", "mouseout", "mousemove", "mousedown", "mouseup", "contextmenu" ], + eventFilters = { "mouseout":"mouseexit" }, + bindOne = function(o, c, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.bind(o, evt, function(ee) { + c.fire(filteredEvent, c, ee); + }); + }, + unbindOne = function(o, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.unbind(o, evt); + }; + + this.attachListeners = function(o, c) { + for (var i = 0; i < events.length; i++) { + bindOne(o, c, events[i]); + } + }; + + var _updateAttachedElements = function(state, timestamp, sourceElement) { + var affectedElements = self.getAttachedElements(); // implemented in subclasses + if (affectedElements) { + for (var i = 0; i < affectedElements.length; i++) { + if (!sourceElement || sourceElement != affectedElements[i]) + affectedElements[i].setHover(state, true, timestamp); // tell the attached elements not to inform their own attached elements. + } + } + }; + + this.reattachListenersForElement = function(o) { + if (arguments.length > 1) { + for (var i = 0; i < events.length; i++) + unbindOne(o, events[i]); + for (var i = 1; i < arguments.length; i++) + self.attachListeners(o, arguments[i]); + } + }; + }, + + overlayCapableJsPlumbUIComponent = function(params) { + jsPlumbUIComponent.apply(this, arguments); + var self = this; + /* + * Property: overlays + * List of Overlays for this component. + */ + this.overlays = []; + + var processOverlay = function(o) { + var _newOverlay = null; + if (_isArray(o)) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0], + // make a copy of the object so as not to mess up anyone else's reference... + p = jsPlumb.extend({component:self, _jsPlumb:self._jsPlumb}, o[1]); + if (o.length == 3) jsPlumb.extend(p, o[2]); + _newOverlay = new jsPlumb.Overlays[self._jsPlumb.getRenderMode()][type](p); + if (p.events) { + for (var evt in p.events) { + _newOverlay.bind(evt, p.events[evt]); + } + } + } else if (o.constructor == String) { + _newOverlay = new jsPlumb.Overlays[self._jsPlumb.getRenderMode()][o]({component:self, _jsPlumb:self._jsPlumb}); + } else { + _newOverlay = o; + } + + self.overlays.push(_newOverlay); + }, + calculateOverlaysToAdd = function(params) { + var defaultKeys = self.defaultOverlayKeys || [], + o = params.overlays, + checkKey = function(k) { + return self._jsPlumb.Defaults[k] || jsPlumb.Defaults[k] || []; + }; + + if (!o) o = []; + + for (var i = 0; i < defaultKeys.length; i++) + o.unshift.apply(o, checkKey(defaultKeys[i])); + + return o; + } + + var _overlays = calculateOverlaysToAdd(params);//params.overlays || self._jsPlumb.Defaults.Overlays; + if (_overlays) { + for (var i = 0; i < _overlays.length; i++) { + processOverlay(_overlays[i]); + } + } + + // overlay finder helper method + var _getOverlayIndex = function(id) { + var idx = -1; + for (var i = 0; i < self.overlays.length; i++) { + if (id === self.overlays[i].id) { + idx = i; + break; + } + } + return idx; + }; + + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + this.addOverlay = function(overlay) { + processOverlay(overlay); + self.repaint(); + }; + + /* + * Function: getOverlay + * Gets an overlay, by ID. Note: by ID. You would pass an 'id' parameter + * in to the Overlay's constructor arguments, and then use that to retrieve + * it via this method. + */ + this.getOverlay = function(id) { + var idx = _getOverlayIndex(id); + return idx >= 0 ? self.overlays[idx] : null; + }; + + /* + * Function:getOverlays + * Gets all the overlays for this component. + */ + this.getOverlays = function() { + return self.overlays; + }; + + /* + * Function: hideOverlay + * Hides the overlay specified by the given id. + */ + this.hideOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.hide(); + }; + + this.hideOverlays = function() { + for (var i = 0; i < self.overlays.length; i++) + self.overlays[i].hide(); + }; + + /* + * Function: showOverlay + * Shows the overlay specified by the given id. + */ + this.showOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.show(); + }; + + this.showOverlays = function() { + for (var i = 0; i < self.overlays.length; i++) + self.overlays[i].show(); + }; + + /** + * Function: removeAllOverlays + * Removes all overlays from the Connection, and then repaints. + */ + this.removeAllOverlays = function() { + for (var i in self.overlays) + self.overlays[i].cleanup(); + + self.overlays.splice(0, self.overlays.length); + self.repaint(); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + this.removeOverlay = function(overlayId) { + var idx = _getOverlayIndex(overlayId); + if (idx != -1) { + var o = self.overlays[idx]; + o.cleanup(); + self.overlays.splice(idx, 1); + } + }; + + /** + * Function:removeOverlays + * Removes a set of overlays by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + this.removeOverlays = function() { + for (var i = 0; i < arguments.length; i++) + self.removeOverlay(arguments[i]); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + var _internalLabelOverlayId = "__label", + _makeLabelOverlay = function(params) { + + var _params = { + cssClass:params.cssClass, + labelStyle : this.labelStyle, + id:_internalLabelOverlayId, + component:self, + _jsPlumb:self._jsPlumb + }, + mergedParams = jsPlumb.extend(_params, params); + + return new jsPlumb.Overlays[self._jsPlumb.getRenderMode()].Label( mergedParams ); + }; + if (params.label) { + var loc = params.labelLocation || self.defaultLabelLocation || 0.5, + labelStyle = params.labelStyle || self._jsPlumb.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.overlays.push(_makeLabelOverlay({ + label:params.label, + location:loc, + labelStyle:labelStyle + })); + } + + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String, a Function that returns a String, or a params object containing { "label", "labelStyle", "location", "cssClass" }. Note that this uses innerHTML on the label div, so keep + * that in mind if you need escaped HTML. + */ + this.setLabel = function(l) { + var lo = self.getOverlay(_internalLabelOverlayId); + if (!lo) { + var params = l.constructor == String || l.constructor == Function ? { label:l } : l; + lo = _makeLabelOverlay(params); + this.overlays.push(lo); + } + else { + if (l.constructor == String || l.constructor == Function) lo.setLabel(l); + else { + if (l.label) lo.setLabel(l.label); + if (l.location) lo.setLocation(l.location); + } + } + + if (!self._jsPlumb.isSuspendDrawing()) + self.repaint(); + }; + + /* + Function:getLabel + Returns the label text for this component (or a function if you are labelling with a function). + This does not return the overlay itself; this is a convenience method which is a pair with + setLabel; together they allow you to add and access a Label Overlay without having to create the + Overlay object itself. For access to the underlying label overlay that jsPlumb has created, + use getLabelOverlay. + */ + this.getLabel = function() { + var lo = self.getOverlay(_internalLabelOverlayId); + return lo != null ? lo.getLabel() : null; + }; + + /* + Function:getLabelOverlay + Returns the underlying internal label overlay, which will exist if you specified a label on + a connect or addEndpoint call, or have called setLabel at any stage. + */ + this.getLabelOverlay = function() { + return self.getOverlay(_internalLabelOverlayId); + } + }, + + _bindListeners = function(obj, _self, _hoverFunction) { + obj.bind("click", function(ep, e) { _self.fire("click", _self, e); }); + obj.bind("dblclick", function(ep, e) { _self.fire("dblclick", _self, e); }); + obj.bind("contextmenu", function(ep, e) { _self.fire("contextmenu", _self, e); }); + obj.bind("mouseenter", function(ep, e) { + if (!_self.isHover()) { + _hoverFunction(true); + _self.fire("mouseenter", _self, e); + } + }); + obj.bind("mouseexit", function(ep, e) { + if (_self.isHover()) { + _hoverFunction(false); + _self.fire("mouseexit", _self, e); + } + }); + }; + + var _jsPlumbInstanceIndex = 0, + getInstanceIndex = function() { + var i = _jsPlumbInstanceIndex + 1; + _jsPlumbInstanceIndex++; + return i; + }; + + var jsPlumbInstance = function(_defaults) { + + /* + * Property: Defaults + * + * These are the default settings for jsPlumb. They are what will be used if you do not supply specific pieces of information + * to the various API calls. A convenient way to implement your own look and feel can be to override these defaults + * by including a script somewhere after the jsPlumb include, but before you make any calls to jsPlumb. + * + * Properties: + * - *Anchor* The default anchor to use for all connections (both source and target). Default is "BottomCenter". + * - *Anchors* The default anchors to use ([source, target]) for all connections. Defaults are ["BottomCenter", "BottomCenter"]. + * - *ConnectionsDetachable* Whether or not connections are detachable by default (using the mouse). Defaults to true. + * - *ConnectionOverlays* The default overlay definitions for Connections. Defaults to an empty list. + * - *Connector* The default connector definition to use for all connections. Default is "Bezier". + * - *Container* Optional selector or element id that instructs jsPlumb to append elements it creates to a specific element. + * - *DragOptions* The default drag options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *DropOptions* The default drop options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *Endpoint* The default endpoint definition to use for all connections (both source and target). Default is "Dot". + * - *EndpointOverlays* The default overlay definitions for Endpoints. Defaults to an empty list. + * - *Endpoints* The default endpoint definitions ([ source, target ]) to use for all connections. Defaults are ["Dot", "Dot"]. + * - *EndpointStyle* The default style definition to use for all endpoints. Default is fillStyle:"#456". + * - *EndpointStyles* The default style definitions ([ source, target ]) to use for all endpoints. Defaults are empty. + * - *EndpointHoverStyle* The default hover style definition to use for all endpoints. Default is null. + * - *EndpointHoverStyles* The default hover style definitions ([ source, target ]) to use for all endpoints. Defaults are null. + * - *HoverPaintStyle* The default hover style definition to use for all connections. Defaults are null. + * - *LabelStyle* The default style to use for label overlays on connections. + * - *LogEnabled* Whether or not the jsPlumb log is enabled. defaults to false. + * - *Overlays* The default overlay definitions (for both Connections and Endpoint). Defaults to an empty list. + * - *MaxConnections* The default maximum number of connections for an Endpoint. Defaults to 1. + * - *PaintStyle* The default paint style for a connection. Default is line width of 8 pixels, with color "#456". + * - *RenderMode* What mode to use to paint with. If you're on IE<9, you don't really get to choose this. You'll just get VML. Otherwise, the jsPlumb default is to use SVG. + * - *Scope* The default "scope" to use for connections. Scope lets you assign connections to different categories. + */ + this.Defaults = { + Anchor : "BottomCenter", + Anchors : [ null, null ], + ConnectionsDetachable : true, + ConnectionOverlays : [ ], + Connector : "Bezier", + Container : null, + DragOptions : { }, + DropOptions : { }, + Endpoint : "Dot", + EndpointOverlays : [ ], + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : "#456" }, + EndpointStyles : [ null, null ], + EndpointHoverStyle : null, + EndpointHoverStyles : [ null, null ], + HoverPaintStyle : null, + LabelStyle : { color : "black" }, + LogEnabled : false, + Overlays : [ ], + MaxConnections : 1, + PaintStyle : { lineWidth : 8, strokeStyle : "#456" }, + //Reattach:false, + RenderMode : "svg", + Scope : "jsPlumb_DefaultScope" + }; + if (_defaults) jsPlumb.extend(this.Defaults, _defaults); + + this.logEnabled = this.Defaults.LogEnabled; + + jsPlumbUtil.EventGenerator.apply(this); + var _currentInstance = this, + _instanceIndex = getInstanceIndex(), + _bb = _currentInstance.bind, + _initialDefaults = {}; + + for (var i in this.Defaults) + _initialDefaults[i] = this.Defaults[i]; + + this.bind = function(event, fn) { + if ("ready" === event && initialized) fn(); + else _bb.apply(_currentInstance,[event, fn]); + }; + + /* + Function: importDefaults + Imports all the given defaults into this instance of jsPlumb. + */ + _currentInstance.importDefaults = function(d) { + for (var i in d) { + _currentInstance.Defaults[i] = d[i]; + } + }; + + /* + Function:restoreDefaults + Restores the default settings to "factory" values. + */ + _currentInstance.restoreDefaults = function() { + _currentInstance.Defaults = jsPlumb.extend({}, _initialDefaults); + }; + + var log = null, + repaintFunction = function() { + jsPlumb.repaintEverything(); + }, + automaticRepaint = true, + repaintEverything = function() { + if (automaticRepaint) + repaintFunction(); + }, + resizeTimer = null, + initialized = false, + connectionsByScope = {}, + /** + * map of element id -> endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + endpointsByElement = {}, + endpointsByUUID = {}, + offsets = {}, + offsetTimestamps = {}, + floatingConnections = {}, + draggableStates = {}, + canvasList = [], + sizes = [], + //listeners = {}, // a map: keys are event types, values are lists of listeners. + DEFAULT_SCOPE = this.Defaults.Scope, + renderMode = null, // will be set in init() + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }, + + /** + * appends an element to some other element, which is calculated as follows: + * + * 1. if _currentInstance.Defaults.Container exists, use that element. + * 2. if the 'parent' parameter exists, use that. + * 3. otherwise just use the document body. + * + */ + _appendElement = function(el, parent) { + if (_currentInstance.Defaults.Container) + jsPlumb.CurrentLibrary.appendElement(el, _currentInstance.Defaults.Container); + else if (!parent) + document.body.appendChild(el); + else + jsPlumb.CurrentLibrary.appendElement(el, parent); + }, + + _curIdStamp = 1, + _idstamp = function() { return "" + _curIdStamp++; }, + + /** + * YUI, for some reason, put the result of a Y.all call into an object that contains + * a '_nodes' array, instead of handing back an array-like object like the other + * libraries do. + */ + _convertYUICollection = function(c) { + return c._nodes ? c._nodes : c; + }, + + /** + * Draws an endpoint and its connections. this is the main entry point into drawing connections as well + * as endpoints, since jsPlumb is endpoint-centric under the hood. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + _draw = function(element, ui, timestamp) { + if (!_suspendDrawing) { + var id = _getAttribute(element, "id"), + repaintEls = _currentInstance.dragManager.getElementsForDraggable(id); + + if (timestamp == null) timestamp = _timestamp(); + + _currentInstance.anchorManager.redraw(id, ui, timestamp); + + if (repaintEls) { + for (var i in repaintEls) { + _currentInstance.anchorManager.redraw(repaintEls[i].id, ui, timestamp, repaintEls[i].offset); + } + } + } + }, + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + _elementProxy = function(element, fn) { + var retVal = null; + if (_isArray(element)) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }, + + /** + * gets an Endpoint by uuid. + */ + _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }, + + /** + * inits a draggable if it's not already initialised. + */ + _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? false : isDraggable, + jpcl = jsPlumb.CurrentLibrary; + if (draggable) { + if (jpcl.isDragSupported(element) && !jpcl.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jpcl.dragEvents["drag"], + stopEvent = jpcl.dragEvents["stop"], + startEvent = jpcl.dragEvents["start"]; + + options[startEvent] = _wrap(options[startEvent], function() { + _currentInstance.setHoverSuspended(true); + }); + + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jpcl.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jpcl.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + _currentInstance.setHoverSuspended(false); + }); + var elId = _getId(element); // need ID + draggableStates[elId] = true; + var draggable = draggableStates[elId]; + options.disabled = draggable == null ? false : !draggable; + jpcl.initDraggable(element, options, false); + _currentInstance.dragManager.register(element); + } + } + }, + + /* + * prepares a final params object that can be passed to _newConnection, taking into account defaults, events, etc. + */ + _prepareConnectionParams = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + // hotwire endpoints passed as source or target to sourceEndpoint/targetEndpoint, respectively. + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not full. + // source: + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + // target: + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + // copy in any connectorOverlays that were specified on the source endpoint. + // it doesnt copy target endpoint overlays. i'm not sure if we want it to or not. + if (_p.sourceEndpoint && _p.sourceEndpoint.connectorOverlays) { + _p.overlays = _p.overlays || []; + for (var i = 0; i < _p.sourceEndpoint.connectorOverlays.length; i++) { + _p.overlays.push(_p.sourceEndpoint.connectorOverlays[i]); + } + } + + // tooltip. params.tooltip takes precedence, then sourceEndpoint.connectorTooltip. + _p.tooltip = params.tooltip; + if (!_p.tooltip && _p.sourceEndpoint && _p.sourceEndpoint.connectorTooltip) + _p.tooltip = _p.sourceEndpoint.connectorTooltip; + + // if there's a target specified (which of course there should be), and there is no + // target endpoint specified, and 'newConnection' was not set to true, then we check to + // see if a prior call to makeTarget has provided us with the specs for the target endpoint, and + // we use those if so. additionally, if the makeTarget call was specified with 'uniqueEndpoint' set + // to true, then if that target endpoint has already been created, we re-use it. + if (_p.target && !_p.target.endpoint && !_p.targetEndpoint && !_p.newConnection) { + var tid = _getId(_p.target), + tep =_targetEndpointDefinitions[tid], + existingUniqueEndpoint = _targetEndpoints[tid]; + + if (tep) { + // if target not enabled, return. + if (!_targetsEnabled[tid]) return; + + // check for max connections?? + var newEndpoint = existingUniqueEndpoint != null ? existingUniqueEndpoint : _currentInstance.addEndpoint(_p.target, tep); + if (_targetEndpointsUnique[tid]) _targetEndpoints[tid] = newEndpoint; + _p.targetEndpoint = newEndpoint; + newEndpoint._makeTargetCreator = true; + } + } + + // same thing, but for source. + if (_p.source && !_p.source.endpoint && !_p.sourceEndpoint && !_p.newConnection) { + var tid = _getId(_p.source), + tep = _sourceEndpointDefinitions[tid], + existingUniqueEndpoint = _sourceEndpoints[tid]; + + if (tep) { + // if source not enabled, return. + if (!_sourcesEnabled[tid]) return; + + var newEndpoint = existingUniqueEndpoint != null ? existingUniqueEndpoint : _currentInstance.addEndpoint(_p.source, tep); + if (_sourceEndpointsUnique[tid]) _sourceEndpoints[tid] = newEndpoint; + _p.sourceEndpoint = newEndpoint; + } + } + + return _p; + }, + + _newConnection = function(params) { + var connectionFunc = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + endpointFunc = _currentInstance.Defaults.EndpointType || Endpoint, + parent = jsPlumb.CurrentLibrary.getParent; + + if (params.container) + params["parent"] = params.container; + else { + if (params.sourceEndpoint) + params["parent"] = params.sourceEndpoint.parent; + else if (params.source.constructor == endpointFunc) + params["parent"] = params.source.parent; + else params["parent"] = parent(params.source); + } + + params["_jsPlumb"] = _currentInstance; + var con = new connectionFunc(params); + con.id = "con_" + _idstamp(); + _eventFireProxy("click", "click", con); + _eventFireProxy("dblclick", "dblclick", con); + _eventFireProxy("contextmenu", "contextmenu", con); + return con; + }, + + /** + * adds the connection to the backing model, fires an event if necessary and then redraws + */ + _finaliseConnection = function(jpc, params, originalEvent) { + params = params || {}; + // add to list of connections (by scope). + if (!jpc.suspendedEndpoint) + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + if (!params.doNotFireConnectionEvent && params.fireEvent !== false) { + + var eventArgs = { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }; + + _currentInstance.fire("jsPlumbConnection", eventArgs, originalEvent); + // this is from 1.3.11 onwards. "jsPlumbConnection" always felt so unnecessary, so + // I've added this alias in 1.3.11, with a view to removing "jsPlumbConnection" completely in a future version. be aware, of course, you should only register listeners for one or the other of these events. + _currentInstance.fire("connection", eventArgs, originalEvent); + } + + // always inform the anchor manager + // except that if jpc has a suspended endpoint it's not true to say the + // connection is new; it has just (possibly) moved. the question is whether + // to make that call here or in the anchor manager. i think perhaps here. + _currentInstance.anchorManager.newConnection(jpc); + // force a paint + _draw(jpc.source); + }, + + _eventFireProxy = function(event, proxyEvent, obj) { + obj.bind(event, function(originalObject, originalEvent) { + _currentInstance.fire(proxyEvent, obj, originalEvent); + }); + }, + + /** + * for the given endpoint params, returns an appropriate parent element for the UI elements that will be added. + * this function is used by _newEndpoint (directly below), and also in the makeSource function in jsPlumb. + * + * the logic is to first look for a "container" member of params, and pass that back if found. otherwise we + * handoff to the 'getParent' function in the current library. + */ + _getParentFromParams = function(params) { + if (params.container) + return params.container; + else { + var tag = jsPlumb.CurrentLibrary.getTagName(params.source), + p = jsPlumb.CurrentLibrary.getParent(params.source); + if (tag && tag.toLowerCase() === "td") + return jsPlumb.CurrentLibrary.getParent(p); + else return p; + } + }, + + /** + factory method to prepare a new endpoint. this should always be used instead of creating Endpoints + manually, since this method attaches event listeners and an id. + */ + _newEndpoint = function(params) { + var endpointFunc = _currentInstance.Defaults.EndpointType || Endpoint; + params.parent = _getParentFromParams(params); + params["_jsPlumb"] = _currentInstance; + var ep = new endpointFunc(params); + ep.id = "ep_" + _idstamp(); + _eventFireProxy("click", "endpointClick", ep); + _eventFireProxy("dblclick", "endpointDblClick", ep); + _eventFireProxy("contextmenu", "contextmenu", ep); + return ep; + }, + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + _operation = function(elId, func, endpointFunc) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + if (endpointFunc) endpointFunc(endpoints[i]); + } + } + }, + /** + * perform an operation on all elements. + */ + _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }, + + /** + * helper to remove an element from the DOM. + */ + _removeElement = function(element, parent) { + if (element != null && element.parentNode != null) { + element.parentNode.removeChild(element); + } + }, + /** + * helper to remove a list of elements from the DOM. + */ + _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }, + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }, + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + _setVisible = function(el, state, alsoChangeEndpoints) { + state = state === "block"; + var endpointFunc = null; + if (alsoChangeEndpoints) { + if (state) endpointFunc = function(ep) { + ep.setVisible(true, true, true); + }; + else endpointFunc = function(ep) { + ep.setVisible(false, true, true); + }; + } + var id = _getAttribute(el, "id"); + _operation(id, function(jpc) { + if (state && alsoChangeEndpoints) { + // this test is necessary because this functionality is new, and i wanted to maintain backwards compatibility. + // this block will only set a connection to be visible if the other endpoint in the connection is also visible. + var oidx = jpc.sourceId === id ? 1 : 0; + if (jpc.endpoints[oidx].isVisible()) jpc.setVisible(true); + } + else // the default behaviour for show, and what always happens for hide, is to just set the visibility without getting clever. + jpc.setVisible(state); + }, endpointFunc); + }, + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? false : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }, + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + _toggleVisible = function(elId, changeEndpoints) { + var endpointFunc = null; + if (changeEndpoints) { + endpointFunc = function(ep) { + var state = ep.isVisible(); + ep.setVisible(!state); + }; + } + _operation(elId, function(jpc) { + var state = jpc.isVisible(); + jpc.setVisible(!state); + }, endpointFunc); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }, + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return offsets[elId]; + } + if (recalc || !offset) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + if (sizes[elId] == null) { + var s = _getElementObject(elId); + if (s != null) + sizes[elId] = _getSize(s); + } + } + + if(offsets[elId] && !offsets[elId].right) { + offsets[elId].right = offsets[elId].left + sizes[elId][0]; + offsets[elId].bottom = offsets[elId].top + sizes[elId][1]; + offsets[elId].width = sizes[elId][0]; + offsets[elId].height = sizes[elId][1]; + offsets[elId].centerx = offsets[elId].left + (offsets[elId].width / 2); + offsets[elId].centery = offsets[elId].top + (offsets[elId].height / 2); + } + return offsets[elId]; + }, + + // TODO comparison performance + _getCachedData = function(elId) { + var o = offsets[elId]; + if (!o) o = _updateOffset({elId:elId}); + return {o:o, s:sizes[elId]}; + }, + + /** + * gets an id for the given element, creating and setting one if + * necessary. the id is of the form + * + * jsPlumb__ + * + * where "index in instance" is a monotonically increasing integer that starts at 0, + * for each instance. this method is used not only to assign ids to elements that do not + * have them but also to connections and endpoints. + */ + _getId = function(element, uuid, doNotCreateIfNotFound) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else if (arguments.length == 1 || (arguments.length == 3 && !arguments[2])) + id = "jsPlumb_" + _instanceIndex + "_" + _idstamp(); + _setAttribute(ele, "id", id); + } + return id; + }, + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, "jsPlumb function failed : " + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, "wrapped function failed : " + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = "_jsPlumb_connector"; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = "_jsPlumb_endpoint"; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = "_jsPlumb_overlay"; + + this.Anchors = {}; + + this.Connectors = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Endpoints = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Overlays = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + +// ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***************************************** + /* + * Function: bind + * Bind to an event on jsPlumb. + * + * Parameters: + * event - the event to bind. Available events on jsPlumb are: + * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback. + * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback. + * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback. + * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback. + * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback. + * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback. + * - *beforeDrop* : notification that a Connection is about to be dropped. Returning false from this method cancels the drop. jsPlumb passes { sourceId, targetId, scope, connection, dropEndpoint } to your callback. For more information, refer to the jsPlumb documentation. + * - *beforeDetach* : notification that a Connection is about to be detached. Returning false from this method cancels the detach. jsPlumb passes the Connection to your callback. For more information, refer to the jsPlumb documentation. + * - *connectionDrag* : notification that an existing Connection is being dragged. jsPlumb passes the Connection to your callback function. + * - *connectionDragEnd* : notification that the drag of an existing Connection has ended. jsPlumb passes the Connection to your callback function. + * + * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event. + */ + + /* + * Function: unbind + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + +// *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *********************************************************** + + +// --------------------------- jsPLumbInstance public API --------------------------------------------------------- + + /* + Function: addClass + + Helper method to abstract out differences in setting css classes on the different renderer types. + */ + this.addClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.addClass(el, clazz); + }; + + /* + Function: removeClass + + Helper method to abstract out differences in setting css classes on the different renderer types. + */ + this.removeClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.removeClass(el, clazz); + }; + + /* + Function: hasClass + + Helper method to abstract out differences in testing for css classes on the different renderer types. + */ + this.hasClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.hasClass(el, clazz); + }; + + /* + Function: addEndpoint + + Adds an to a given element or elements. + + Parameters: + + el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + params - Object containing Endpoint constructor arguments. For more information, see . + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some + shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in + this object are anything that 'params' can contain. See . + + Returns: + The newly created , if el referred to a single element. Otherwise, an array of newly created s. + + See Also: + + */ + this.addEndpoint = function(el, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // YUI wrapper + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + var _el = _getElementObject(inputs[i]), id = _getId(_el); + p.source = _el; + _updateOffset({ elId : id }); + var e = _newEndpoint(p); + if (p.parentAnchor) e.parentAnchor = p.parentAnchor; + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + results.push(e); + _currentInstance.dragManager.endpointAdded(_el); + } + + return results.length == 1 ? results[0] : results; + }; + + /* + Function: addEndpoints + Adds a list of s to a given element or elements. + + Parameters: + target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See 's constructor documentation. + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created s, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(el, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams); + if (_isArray(e)) + Array.prototype.push.apply(results, e); + else results.push(e); + } + return results; + }; + + /* + Function: animate + This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating + the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // onComplete repaints, just to make sure everything looks good at the end of the animation. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /** + * checks for a listener for the given condition, executing it if found, passing in the given value. + * condition listeners would have been attached using "bind" (which is, you could argue, now overloaded, since + * firing click events etc is a bit different to what this does). i thought about adding a "bindCondition" + * or something, but decided against it, for the sake of simplicity. jsPlumb will never fire one of these + * condition events anyway. + */ + this.checkCondition = function(conditionName, value) { + var l = _currentInstance.getListener(conditionName); + var r = true; + if (l && l.length > 0) { + try { + for (var i = 0 ; i < l.length; i++) { + r = r && l[i](value); + } + } + catch (e) { + _log(_currentInstance, "cannot check condition [" + conditionName + "]" + e); + } + } + return r; + }; + + /* + Function: connect + Establishes a between two elements (or s, which are themselves registered to elements). + + Parameters: + params - Object containing constructor arguments for the Connection. See 's constructor documentation. + referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of + Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection. + + Returns: + The newly created . + */ + this.connect = function(params, referenceParams) { + // prepare a final set of parameters to create connection with + var _p = _prepareConnectionParams(params, referenceParams); + // TODO probably a nicer return value if the connection was not made. _prepareConnectionParams + // will return null (and log something) if either endpoint was full. what would be nicer is to + // create a dedicated 'error' object. + if (_p) { + // a connect call will delete its created endpoints on detach, unless otherwise specified. + // this is because the endpoints belong to this connection only, and are no use to + // anyone else, so they hang around like a bad smell. + if (_p.deleteEndpointsOnDetach == null) + _p.deleteEndpointsOnDetach = true; + + // create the connection. it is not yet registered + var jpc = _newConnection(_p); + // now add it the model, fire an event, and redraw + _finaliseConnection(jpc, _p); + return jpc; + } + }; + + /* + Function: deleteEndpoint + Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too) + + Parameters: + object - either an object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElements(endpoint.endpoint.getDisplayElements()); + _currentInstance.anchorManager.deleteEndpoint(endpoint); + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + _currentInstance.dragManager.endpointDeleted(endpoint); + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every , and their associated s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + _currentInstance.setSuspendDrawing(true); + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + + _currentInstance.setSuspendDrawing(false, true); + }; + + var fireDetachEvent = function(jpc, doFireEvent, originalEvent) { + // may have been given a connection, or in special cases, an object + var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + argIsConnection = jpc.constructor == connType, + params = argIsConnection ? { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + } : jpc; + + if (doFireEvent) { + _currentInstance.fire("jsPlumbConnectionDetached", params, originalEvent); + // introduced in 1.3.11..an alias because the original event name is unwieldy. in future versions this will be the only event and the other will no longer be fired. + _currentInstance.fire("connectionDetached", params, originalEvent); + } + _currentInstance.anchorManager.connectionDetached(params); + }, + /** + fires an event to indicate an existing connection is being dragged. + */ + fireConnectionDraggingEvent = function(jpc) { + _currentInstance.fire("connectionDrag", jpc); + }, + fireConnectionDragStopEvent = function(jpc) { + _currentInstance.fire("connectionDragStop", jpc); + }; + + + /* + Function: detach + Detaches and then removes a . From 1.3.5 this method has been altered to remove support for + specifying Connections by various parameters; you can now pass in a Connection as the first argument and + an optional parameters object as a second argument. If you need the functionality this method provided + before 1.3.5 then you should use the getConnections method to get the list of Connections to detach, and + then iterate through them, calling this for each one. + + Parameters: + connection - the to detach + params - optional parameters to the detach call. valid values here are + fireEvent : defaults to false; indicates you want jsPlumb to fire a connection + detached event. The thinking behind this is that if you made a programmatic + call to detach an event, you probably don't need the callback. + forceDetach : defaults to false. allows you to override any beforeDetach listeners that may be registered. + + Returns: + true if successful, false if not. + */ + this.detach = function() { + + if (arguments.length == 0) return; + var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + firstArgIsConnection = arguments[0].constructor == connType, + params = arguments.length == 2 ? firstArgIsConnection ? (arguments[1] || {}) : arguments[0] : arguments[0], + fireEvent = (params.fireEvent !== false), + forceDetach = params.forceDetach, + connection = firstArgIsConnection ? arguments[0] : params.connection; + + if (connection) { + if (forceDetach || (connection.isDetachAllowed(connection) + && connection.endpoints[0].isDetachAllowed(connection) + && connection.endpoints[1].isDetachAllowed(connection))) { + if (forceDetach || _currentInstance.checkCondition("beforeDetach", connection)) + connection.endpoints[0].detach(connection, false, true, fireEvent); // TODO check this param iscorrect for endpoint's detach method + } + } + else { + var _p = jsPlumb.extend( {}, params); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1]), fireEvent); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source), + targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + if (_currentInstance.checkCondition("beforeDetach", jpc)) { + jpc.endpoints[0].detach(jpc, false, true, fireEvent); + } + } + }); + } + } + }; + + /* + Function: detachAllConnections + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + params - optional parameters. alowed values: + fireEvent : defaults to true, whether or not to fire the detach event. + + Returns: + void + */ + this.detachAllConnections = function(el, params) { + params = params || {}; + el = _getElementObject(el); + var id = _getAttribute(el, "id"), + endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + }; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Parameters: + params - optional params object containing: + fireEvent : whether or not to fire detach events. defaults to true. + + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function(params) { + params = params || {}; + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of your + library's draggable method so that jsPlumb can setup the appropriate callbacks. Your + underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + // helpers for select/selectEndpoints + var _setOperation = function(list, func, args, selector) { + for (var i = 0; i < list.length; i++) { + list[i][func].apply(list[i], args); + } + return selector(list); + }, + _getOperation = function(list, func, args) { + var out = []; + for (var i = 0; i < list.length; i++) { + out.push([ list[i][func].apply(list[i], args), list[i] ]); + } + return out; + }, + setter = function(list, func, selector) { + return function() { + return _setOperation(list, func, arguments, selector); + }; + }, + getter = function(list, func) { + return function() { + return _getOperation(list, func, arguments); + }; + }, + prepareList = function(input, doNotGetIds) { + var r = []; + if (input) { + if (typeof input == 'string') { + if (input === "*") return input; + r.push(input); + } + else { + if (doNotGetIds) r = input; + else { + for (var i = 0; i < input.length; i++) + r.push(_getId(_getElementObject(input[i]))); + } + } + } + return r; + }, + filterList = function(list, value, missingIsFalse) { + if (list === "*") return true; + return list.length > 0 ? _indexOf(list, value) != -1 : !missingIsFalse; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method, + * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the + * default scope). If multiple scopes are passed in, the return value will be a map of { scope -> [ connection... ] }. + * + * Parameters: + * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list + * of connections that are in the given scope. use '*' for all scopes. + * options - if the argument is a JS object, you can specify a finer-grained filter: + * + * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes. Also may have the value '*', indicating any scope. + * - *source* either a string representing an element id, a selector, or an array of ids. Also may have the value '*', indicating any source. Constrains the result to connections having this/these element(s) as source. + * - *target* either a string representing an element id, a selector, or an array of ids. Also may have the value '*', indicating any target. Constrains the result to connections having this/these element(s) as target. + * flat - return results in a flat array (don't return an object whose keys are scopes and whose values are lists per scope). + * + */ + this.getConnections = function(options, flat) { + if (!options) { + options = {}; + } else if (options.constructor == String) { + options = { "scope": options }; + } + var + scope = options.scope || _currentInstance.getDefaultScope(), + scopes = prepareList(scope, true), + sources = prepareList(options.source), + targets = prepareList(options.target), + results = (!flat && scopes.length > 1) ? {} : [], + _addOne = function(scope, obj) { + if (!flat && scopes.length > 1) { + var ss = results[scope]; + if (ss == null) { + ss = []; results[scope] = ss; + } + ss.push(obj); + } else results.push(obj); + }; + for ( var i in connectionsByScope) { + if (filterList(scopes, i)) { + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filterList(sources, c.sourceId) && filterList(targets, c.targetId)) + _addOne(i, c); + } + } + } + return results; + }; + + var _curryEach = function(list, executor) { + return function(f) { + for (var i = 0; i < list.length; i++) { + f(list[i]); + } + return executor(list); + }; + }, + _curryGet = function(list) { + return function(idx) { + return list[idx]; + }; + }; + + var _makeCommonSelectHandler = function(list, executor) { + return { + // setters + setHover:setter(list, "setHover", executor), + removeAllOverlays:setter(list, "removeAllOverlays", executor), + setLabel:setter(list, "setLabel", executor), + addOverlay:setter(list, "addOverlay", executor), + removeOverlay:setter(list, "removeOverlay", executor), + removeOverlays:setter(list, "removeOverlays", executor), + showOverlay:setter(list, "showOverlay", executor), + hideOverlay:setter(list, "hideOverlay", executor), + showOverlays:setter(list, "showOverlays", executor), + hideOverlays:setter(list, "hideOverlays", executor), + setPaintStyle:setter(list, "setPaintStyle", executor), + setHoverPaintStyle:setter(list, "setHoverPaintStyle", executor), + setParameter:setter(list, "setParameter", executor), + setParameters:setter(list, "setParameters", executor), + + // getters + getLabel:getter(list, "getLabel"), + getOverlay:getter(list, "getOverlay"), + isHover:getter(list, "isHover"), + getParameter:getter(list, "getParameter"), + getParameters:getter(list, "getParameters"), + getPaintStyle:getter(list, "getPaintStyle"), + getHoverPaintStyle:getter(list, "getHoverPaintStyle"), + + // util + length:list.length, + each:_curryEach(list, executor), + get:_curryGet(list) + }; + + }; + + var _makeConnectionSelectHandler = function(list) { + var common = _makeCommonSelectHandler(list, _makeConnectionSelectHandler); + return jsPlumb.CurrentLibrary.extend(common, { + // setters + setDetachable:setter(list, "setDetachable", _makeConnectionSelectHandler), + setConnector:setter(list, "setConnector", _makeConnectionSelectHandler), + detach:function() { + for (var i = 0; i < list.length; i++) + _currentInstance.detach(list[i]); + }, + // getters + isDetachable:getter(list, "isDetachable") + }); + }; + + var _makeEndpointSelectHandler = function(list) { + var common = _makeCommonSelectHandler(list, _makeConnectionSelectHandler); + return jsPlumb.CurrentLibrary.extend(common, { + detachAll:function() { + for (var i = 0; i < list.length; i++) + list[i].detachAll(); + }, + "delete":function() { + for (var i = 0; i < list.length; i++) + _currentInstance.deleteEndpoint(list[i]); + } + }); + }; + + /* + * Function: select + * Selects a set of Connections, using the filter options from the getConnections method, and returns an object + * that allows you to perform an operation on all of the Connections at once. The return value from any of these + * operations is the original list of Connections, allowing operations to be chained (for 'setter' type operations). + * 'getter' type operations return an array of values, where each entry is a [Connection, return value] pair. + * + * Parameters: + * scope - see getConnections + * source - see getConnections + * target - see getConnections + * + * Returns: + * A list of Connections on which operations may be executed. 'Setter' type operations can be chained; 'getter' type operations + * return an array of [Connection, value] pairs, one entry for each Connection in the list returned. The full list of operations + * is as follows (where not specified, the operation's effect or return value is the same as the corresponding method on Connection) : + * - setHover + * - removeAllOverlays + * - setLabel + * - addOverlay + * - removeOverlay + * - removeOverlays + * - showOverlay + * - hideOverlay + * - showOverlays + * - hideOverlays + * - setPaintStyle + * - setHoverPaintStyle + * - setDetachable + * - setConnector + * - setParameter + * - setParameters + * - getLabel + * - getOverlay + * - isHover + * - isDetachable + * - getParameter + * - getParameters + * - getPaintStyle + * - getHoverPaintStyle + * - detach detaches all the connections in the list. not chainable and does not return anything. + * - length : returns the length of the list. + * - get(index) : returns the Connection at 'index' in the list. + * - each(function(connection)...) : allows you to specify your own function to execute; this function is chainable. + */ + this.select = function(params) { + params = params || {}; + params.scope = params.scope || "*"; + var c = _currentInstance.getConnections(params, true); + return _makeConnectionSelectHandler(c); + }; + + /* + * Function: selectEndpoints + * Selects a set of Endpoints and returns an object that allows you to execute various different methods on them at once. The return + * value from any of these operations is the original list of Endpoints, allowing operations to be chained (for 'setter' type + * operations). 'getter' type operations return an array of values, where each entry is an [Endpoint, return value] pair. + * + * Parameters: + * scope - either a string or an array of strings. + * source - either a string, a dom element, or a selector, or an array of any mix of these. limits returned endpoints to those that are declared as a source endpoint on any elements identified. + * target - either a string, a dom element, or a selector, or an array of any mix of these. limits returned endpoints to those that are declared as a target endpoint on any elements identified. + * element - either a string, a dom element, or a selector, or an array of any mix of these. limits returned endpoints to those that are declared as either a source OR a target endpoint on any elements identified. + * + * Returns: + * A list of Endpoints on which operations may be executed. 'Setter' type operations can be chained; 'getter' type operations + * return an array of [Endpoint, value] pairs, one entry for each Endpoint in the list returned. The full list of operations + * is as follows (where not specified, the operation's effect or return value is the same as the corresponding method on Endpoint) : + * - setHover + * - removeAllOverlays + * - setLabel + * - addOverlay + * - removeOverlay + * - removeOverlays + * - showOverlay + * - hideOverlay + * - showOverlays + * - hideOverlays + * - setPaintStyle + * - setHoverPaintStyle + * - setParameter + * - setParameters + * - getLabel + * - getOverlay + * - isHover + * - isDetachable + * - getParameter + * - getParameters + * - getPaintStyle + * - getHoverPaintStyle + * - detachAll Detaches all the Connections from every Endpoint in the list. not chainable and does not return anything. + * - delete Deletes every Endpoint in the list. not chainable and does not return anything. + * - length : returns the length of the list. + * - get(index) : returns the Endpoint at 'index' in the list. + * - each(function(endpoint)...) : allows you to specify your own function to execute; this function is chainable. + */ + this.selectEndpoints = function(params) { + params = params || {}; + params.scope = params.scope || "*"; + var noElementFilters = !params.element && !params.source && !params.target, + elements = noElementFilters ? "*" : prepareList(params.element), + sources = noElementFilters ? "*" : prepareList(params.source), + targets = noElementFilters ? "*" : prepareList(params.target), + scopes = prepareList(params.scope, true); + + var ep = []; + + for (var el in endpointsByElement) { + var either = filterList(elements, el, true), + source = filterList(sources, el, true), + sourceMatchExact = sources != "*", + target = filterList(targets, el, true), + targetMatchExact = targets != "*"; + + // if they requested 'either' then just match scope. otherwise if they requested 'source' (not as a wildcard) then we have to match only endpoints that have isSource set to to true, and the same thing with isTarget. + if ( either || source || target ) { + inner: + for (var i = 0; i < endpointsByElement[el].length; i++) { + var _ep = endpointsByElement[el][i]; + if (filterList(scopes, _ep.scope, true)) { + + var noMatchSource = (sourceMatchExact && sources.length > 0 && !_ep.isSource), + noMatchTarget = (targetMatchExact && targets.length > 0 && !_ep.isTarget); + + if (noMatchSource || noMatchTarget) + continue inner; + + ep.push(_ep); + } + } + } + } + + return _makeEndpointSelectHandler(ep); + }; + + /* + * Function: getAllConnections + * Gets all connections, as a map of { scope -> [ connection... ] }. + */ + this.getAllConnections = function() { + return connectionsByScope; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /** + * Function:getEndpoints + * Gets the list of Endpoints for a given selector, or element id. + * @param el + * @return + */ + this.getEndpoints = function(el) { + return endpointsByElement[_getId(el)]; + }; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + this.getOffset = function(id) { + var o = offsets[id]; + return _updateOffset({elId:id}); + }; + + this.getSelector = function(spec) { + return jsPlumb.CurrentLibrary.getSelector(spec); + }; + + this.getSize = function(id) { + var s = sizes[id]; + if (!s) _updateOffset({elId:id}); + return sizes[id]; + }; + + this.appendElement = _appendElement; + + var _hoverSuspended = false; + this.isHoverSuspended = function() { return _hoverSuspended; }; + this.setHoverSuspended = function(s) { _hoverSuspended = s; }; + + this.isCanvasAvailable = function() { return canvasAvailable; }; + this.isSVGAvailable = function() { return svgAvailable; }; + this.isVMLAvailable = vmlAvailable; + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + changeEndpoints - whether not to also hide endpoints on the element. by default this is false. + + Returns: + void + */ + this.hide = function(el, changeEndpoints) { + _setVisible(el, "none", changeEndpoints); + }; + + // exposed for other objects to use to get a unique id. + this.idstamp = _idstamp; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + if (!initialized) { + _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run. + + var bindOne = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && renderMode == jsPlumb.CANVAS) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + var t = c[i].connector[event](e); + if (t) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i].endpoint[event](e)) return; + } + } + } + }); + }; + bindOne("click");bindOne("dblclick");bindOne("mousemove");bindOne("mousedown");bindOne("mouseup");bindOne("contextmenu"); + + initialized = true; + _currentInstance.fire("ready"); + } + }; + + this.log = log; + this.jsPlumbUIComponent = jsPlumbUIComponent; + + /* + * Creates an anchor with the given params. + * + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function() { + if (arguments.length == 0) return null; + var specimen = arguments[0], elementId = arguments[1], jsPlumbInstance = arguments[2], newAnchor = null; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; //TODO hazy here about whether it should be added or is already added somehow. + // is it the name of an anchor type? + else if (typeof specimen == "string") { + newAnchor = jsPlumb.Anchors[arguments[0]]({elementId:elementId, jsPlumbInstance:_currentInstance}); + } + // is it an array? it will be one of: + // an array of [name, params] - this defines a single anchor + // an array of arrays - this defines some dynamic anchors + // an array of numbers - this defines a single anchor. + else if (_isArray(specimen)) { + if (_isArray(specimen[0]) || _isString(specimen[0])) { + if (specimen.length == 2 && _isString(specimen[0]) && _isObject(specimen[1])) { + var pp = jsPlumb.extend({elementId:elementId, jsPlumbInstance:_currentInstance}, specimen[1]); + newAnchor = jsPlumb.Anchors[specimen[0]](pp); + } + else + newAnchor = new DynamicAnchor(specimen, null, elementId); + } + else { + var anchorParams = { + x:specimen[0], y:specimen[1], + orientation : (specimen.length >= 4) ? [ specimen[2], specimen[3] ] : [0,0], + offsets : (specimen.length == 6) ? [ specimen[4], specimen[5] ] : [ 0, 0 ], + elementId:elementId + }; + newAnchor = new Anchor(anchorParams); + newAnchor.clone = function() { return new Anchor(anchorParams); }; + } + } + + if (!newAnchor.id) newAnchor.id = "anchor_" + _idstamp(); + return newAnchor; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types, elementId, jsPlumbInstance) { + var r = []; + for ( var i = 0; i < types.length; i++) { + if (typeof types[i] == "string") + r.push(jsPlumb.Anchors[types[i]]({elementId:elementId, jsPlumbInstance:jsPlumbInstance})); + else if (_isArray(types[i])) + r.push(_currentInstance.makeAnchor(types[i], elementId, jsPlumbInstance)); + } + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /** + * Function: makeTarget + * Makes some DOM element a Connection target, allowing you to drag connections to it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a target. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * scope optional. scope for the drop zone. + * dropOptions optional. same stuff as you would pass to dropOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to true. whether or not to delete + * any Endpoints created by a connection to this target if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * maxConnections optional. Specifies the maximum number of Connections that can be made to this element as a target. Default is no limit. + * onMaxConnections optional. Function to call when user attempts to drop a connection but the limit has been reached. The callback is passed two arguments: a JS object with { element, connection, maxConnection }, and the original event. + * + * + */ + var _targetEndpointDefinitions = {}, + _targetEndpoints = {}, + _targetEndpointsUnique = {}, + _targetMaxConnections = {}, + _setEndpointPaintStylesAndAnchor = function(ep, epIndex) { + ep.paintStyle = ep.paintStyle || + _currentInstance.Defaults.EndpointStyles[epIndex] || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyles[epIndex] || + jsPlumb.Defaults.EndpointStyle; + ep.hoverPaintStyle = ep.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyles[epIndex] || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyles[epIndex] || + jsPlumb.Defaults.EndpointHoverStyle; + + ep.anchor = ep.anchor || + _currentInstance.Defaults.Anchors[epIndex] || + _currentInstance.Defaults.Anchor || + jsPlumb.Defaults.Anchors[epIndex] || + jsPlumb.Defaults.Anchor; + + ep.endpoint = ep.endpoint || + _currentInstance.Defaults.Endpoints[epIndex] || + _currentInstance.Defaults.Endpoint || + jsPlumb.Defaults.Endpoints[epIndex] || + jsPlumb.Defaults.Endpoint; + }; + + this.makeTarget = function(el, params, referenceParams) { + + var p = jsPlumb.extend({_jsPlumb:_currentInstance}, referenceParams); + jsPlumb.extend(p, params); + _setEndpointPaintStylesAndAnchor(p, 1); + var jpcl = jsPlumb.CurrentLibrary, + targetScope = p.scope || _currentInstance.Defaults.Scope, + deleteEndpointsOnDetach = !(p.deleteEndpointsOnDetach === false), + maxConnections = p.maxConnections || -1, + onMaxConnections = p.onMaxConnections; + _doOne = function(_el) { + + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el); + _targetEndpointDefinitions[elid] = p; + _targetEndpointsUnique[elid] = p.uniqueEndpoint, + _targetMaxConnections[elid] = maxConnections, + _targetsEnabled[elid] = true, + proxyComponent = new jsPlumbUIComponent(p); + + var dropOptions = jsPlumb.extend({}, p.dropOptions || {}), + _drop = function() { + + var originalEvent = jsPlumb.CurrentLibrary.getDropEvent(arguments), + targetCount = _currentInstance.select({target:elid}).length; + + + _currentInstance.currentlyDragging = false; + var draggable = _getElementObject(jpcl.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + // restore the original scope if necessary (issue 57) + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id], + source = jpc.endpoints[0], + _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : {}; + + if (!_targetsEnabled[elid] || _targetMaxConnections[elid] > 0 && targetCount >= _targetMaxConnections[elid]){ + console.log("target element " + elid + " is full."); + if (onMaxConnections) { + onMaxConnections({ + element:_el, + connection:jpc + }, originalEvent); + } + return false; + } + + // unlock the source anchor to allow it to refresh its position if necessary + source.anchor.locked = false; + + if (scope) jpcl.setDragScope(draggable, scope); + + // check if drop is allowed here. + //var _continue = jpc.isDropAllowed(jpc.sourceId, _getId(_el), jpc.scope); + var _continue = proxyComponent.isDropAllowed(jpc.sourceId, _getId(_el), jpc.scope, jpc, null); + + // regardless of whether the connection is ok, reconfigure the existing connection to + // point at the current info. we need this to be correct for the detach event that will follow. + // clear the source endpoint from the list to detach. we will detach this connection at this + // point, but we want to keep the source endpoint. the target is a floating endpoint and should + // be removed. TODO need to figure out whether this code can result in endpoints kicking around + // when they shouldnt be. like is this a full detach of a connection? can it be? + if (jpc.endpointsToDeleteOnDetach) { + if (source === jpc.endpointsToDeleteOnDetach[0]) + jpc.endpointsToDeleteOnDetach[0] = null; + else if (source === jpc.endpointsToDeleteOnDetach[1]) + jpc.endpointsToDeleteOnDetach[1] = null; + } + // reinstate any suspended endpoint; this just puts the connection back into + // a state in which it will report sensible values if someone asks it about + // its target. we're going to throw this connection away shortly so it doesnt matter + // if we manipulate it a bit. + if (jpc.suspendedEndpoint) { + jpc.targetId = jpc.suspendedEndpoint.elementId; + jpc.target = jpcl.getElementObject(jpc.suspendedEndpoint.elementId); + jpc.endpoints[1] = jpc.suspendedEndpoint; + } + + if (_continue) { + + // detach this connection from the source. + source.detach(jpc, false, true, false); + + // make a new Endpoint for the target + var newEndpoint = _targetEndpoints[elid] || _currentInstance.addEndpoint(_el, p); + if (p.uniqueEndpoint) _targetEndpoints[elid] = newEndpoint; // may of course just store what it just pulled out. that's ok. + newEndpoint._makeTargetCreator = true; + + // if the anchor has a 'positionFinder' set, then delegate to that function to find + // out where to locate the anchor. + if (newEndpoint.anchor.positionFinder != null) { + var dropPosition = jpcl.getUIPosition(arguments), + elPosition = jpcl.getOffset(_el), + elSize = jpcl.getSize(_el), + ap = newEndpoint.anchor.positionFinder(dropPosition, elPosition, elSize, newEndpoint.anchor.constructorParams); + newEndpoint.anchor.x = ap[0]; + newEndpoint.anchor.y = ap[1]; + // now figure an orientation for it..kind of hard to know what to do actually. probably the best thing i can do is to + // support specifying an orientation in the anchor's spec. if one is not supplied then i will make the orientation + // be what will cause the most natural link to the source: it will be pointing at the source, but it needs to be + // specified in one axis only, and so how to make that choice? i think i will use whichever axis is the one in which + // the target is furthest away from the source. + } + var c = _currentInstance.connect({ + source:source, + target:newEndpoint, + scope:scope, + previousConnection:jpc, + container:jpc.parent, + deleteEndpointsOnDetach:deleteEndpointsOnDetach, + // 'endpointWillMoveAfterConnection' is set by the makeSource function, and it indicates that the + // given endpoint will actually transfer from the element it is currently attached to to some other + // element after a connection has been established. in that case, we do not want to fire the + // connection event, since it will have the wrong data in it; makeSource will do it for us. + // this is controlled by the 'parent' parameter on a makeSource call. + doNotFireConnectionEvent:source.endpointWillMoveAfterConnection + }); + + // delete the original target endpoint. but only want to do this if the endpoint was created + // automatically and has no other connections. + if (jpc.endpoints[1]._makeTargetCreator && jpc.endpoints[1].connections.length < 2) + _currentInstance.deleteEndpoint(jpc.endpoints[1]); + + if (deleteEndpointsOnDetach) + c.endpointsToDeleteOnDetach = [ source, newEndpoint ]; + + c.repaint(); + } + // if not allowed to drop... + else { + // TODO this code is identical (pretty much) to what happens when a connection + // dragged from a normal endpoint is in this situation. refactor. + // is this an existing connection, and will we reattach? + if (jpc.suspendedEndpoint) { + if (source.isReattach) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + _currentInstance.repaint(source.elementId); + } + else + source.detach(jpc, false, true, true, originalEvent); // otherwise, detach the connection and tell everyone about it. + } + + } + }; + + var dropEvent = jpcl.dragEvents['drop']; + dropOptions["scope"] = dropOptions["scope"] || targetScope; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop); + + jpcl.initDroppable(_el, dropOptions, true); + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + + return _currentInstance; + }; + + /* + * Function: unmakeTarget + * Sets the given element to no longer be a connection target. + * Parameters: + * el - either a string id or a selector representing the element. + * Returns: + * The current jsPlumb instance. + */ + this.unmakeTarget = function(el, doNotClearArrays) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + var elid = _getId(el); + + // TODO this is not an exhaustive unmake of a target, since it does not remove the droppable stuff from + // the element. the effect will be to prevent it form behaving as a target, but it's not completely purged. + if (!doNotClearArrays) { + delete _targetEndpointDefinitions[elid]; + delete _targetEndpointsUnique[elid]; + delete _targetMaxConnections[elid]; + delete _targetsEnabled[elid]; + } + + return _currentInstance; + }; + + /** + * helper method to make a list of elements drop targets. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeTargets = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeTarget(els[i], params, referenceParams); + } + }; + + /** + * Function: makeSource + * Makes some DOM element a Connection source, allowing you to drag connections from it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a source. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * parent optional. the element to add Endpoints to when a Connection is established. if you omit this, + * Endpoints will be added to 'el'. + * scope optional. scope for the connections dragged from this element. + * dragOptions optional. same stuff as you would pass to dragOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to false. whether or not to delete + * any Endpoints created by a connection from this source if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * filter - optional function to call when the user presses the mouse button to start a drag. This function is passed the original + * event and the element on which the associated makeSource call was made. If it returns anything other than false, + * the drag begins as usual. But if it returns false (the boolean false, not something falsey), the drag is aborted. + * + * + */ + var _sourceEndpointDefinitions = {}, + _sourceEndpoints = {}, + _sourceEndpointsUnique = {}, + _sourcesEnabled = {}, + _sourceTriggers = {}, + _sourceMaxConnections = {}, + _targetsEnabled = {}; + + this.makeSource = function(el, params, referenceParams) { + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + _setEndpointPaintStylesAndAnchor(p, 0); + var jpcl = jsPlumb.CurrentLibrary, + maxConnections = p.maxConnections || -1, + onMaxConnections = p.onMaxConnections, + _doOne = function(_el) { + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el), + parent = p.parent, + idToRegisterAgainst = parent != null ? _currentInstance.getId(jpcl.getElementObject(parent)) : elid; + + _sourceEndpointDefinitions[idToRegisterAgainst] = p; + _sourceEndpointsUnique[idToRegisterAgainst] = p.uniqueEndpoint; + _sourcesEnabled[idToRegisterAgainst] = true; + + var stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"], + dragOptions = jsPlumb.extend({ }, p.dragOptions || {}), + existingDrag = dragOptions.drag, + existingStop = dragOptions.stop, + ep = null, + endpointAddedButNoDragYet = false; + + _sourceMaxConnections[idToRegisterAgainst] = maxConnections; + + // set scope if its not set in dragOptions but was passed in in params + dragOptions["scope"] = dragOptions["scope"] || p.scope; + + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + if (existingDrag) existingDrag.apply(this, arguments); + endpointAddedButNoDragYet = false; + }); + + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], function() { + if (existingStop) existingStop.apply(this, arguments); + + //_currentlyDown = false; + _currentInstance.currentlyDragging = false; + + if (ep.connections.length == 0) + _currentInstance.deleteEndpoint(ep); + else { + + jpcl.unbind(ep.canvas, "mousedown"); + + // reset the anchor to the anchor that was initially provided. the one we were using to drag + // the connection was just a placeholder that was located at the place the user pressed the + // mouse button to initiate the drag. + var anchorDef = p.anchor || _currentInstance.Defaults.Anchor, + oldAnchor = ep.anchor, + oldConnection = ep.connections[0]; + + ep.anchor = _currentInstance.makeAnchor(anchorDef, elid, _currentInstance); + + if (p.parent) { + var parent = jpcl.getElementObject(p.parent); + if (parent) { + var currentId = ep.elementId; + + var potentialParent = p.container || _currentInstance.Defaults.Container || jsPlumb.Defaults.Container; + + ep.setElement(parent, potentialParent); + ep.endpointWillMoveAfterConnection = false; + _currentInstance.anchorManager.rehomeEndpoint(currentId, parent); + oldConnection.previousConnection = null; + // remove from connectionsByScope + _removeWithFunction(connectionsByScope[oldConnection.scope], function(c) { + return c.id === oldConnection.id; + }); + _currentInstance.anchorManager.connectionDetached({ + sourceId:oldConnection.sourceId, + targetId:oldConnection.targetId, + connection:oldConnection + }); + _finaliseConnection(oldConnection); + } + } + + ep.repaint(); + _currentInstance.repaint(ep.elementId); + _currentInstance.repaint(oldConnection.targetId); + + } + }); + // when the user presses the mouse, add an Endpoint, if we are enabled. + var mouseDownListener = function(e) { + + // if disabled, return. + if (!_sourcesEnabled[idToRegisterAgainst]) return; + + // if maxConnections reached + var sourceCount = _currentInstance.select({source:idToRegisterAgainst}).length + if (_sourceMaxConnections[idToRegisterAgainst] >= 0 && sourceCount >= _sourceMaxConnections[idToRegisterAgainst]) { + if (onMaxConnections) { + onMaxConnections({ + element:_el, + maxConnections:maxConnections + }, e); + } + return false; + } + + // if a filter was given, run it, and return if it says no. + if (params.filter) { + // pass the original event to the user: + var r = params.filter(jpcl.getOriginalEvent(e), _el); + if (r === false) return; + } + + // make sure we have the latest offset for this div + var myOffsetInfo = _updateOffset({elId:elid}); + + var x = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width, + y = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height, + parentX = x, + parentY = y; + + // if there is a parent, the endpoint will actually be added to it now, rather than the div + // that was the source. in that case, we have to adjust the anchor position so it refers to + // the parent. + if (p.parent) { + var pEl = jpcl.getElementObject(p.parent), + pId = _getId(pEl); + myOffsetInfo = _updateOffset({elId:pId}); + parentX = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width, + parentY = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height; + } + + // we need to override the anchor in here, and force 'isSource', but we don't want to mess with + // the params passed in, because after a connection is established we're going to reset the endpoint + // to have the anchor we were given. + var tempEndpointParams = {}; + jsPlumb.extend(tempEndpointParams, p); + tempEndpointParams.isSource = true; + tempEndpointParams.anchor = [x,y,0,0]; + tempEndpointParams.parentAnchor = [ parentX, parentY, 0, 0 ]; + tempEndpointParams.dragOptions = dragOptions; + // if a parent was given we need to turn that into a "container" argument. this is, by default, + // the parent of the element we will move to, so parent of p.parent in this case. however, if + // the user has specified a 'container' on the endpoint definition or on + // the defaults, we should use that. + if (p.parent) { + var potentialParent = tempEndpointParams.container || _currentInstance.Defaults.Container || jsPlumb.Defaults.Container; + if (potentialParent) + tempEndpointParams.container = potentialParent; + else + tempEndpointParams.container = jsPlumb.CurrentLibrary.getParent(p.parent); + } + + ep = _currentInstance.addEndpoint(elid, tempEndpointParams); + + endpointAddedButNoDragYet = true; + // we set this to prevent connections from firing attach events before this function has had a chance + // to move the endpoint. + ep.endpointWillMoveAfterConnection = p.parent != null; + ep.endpointWillMoveTo = p.parent ? jpcl.getElementObject(p.parent) : null; + + var _delTempEndpoint = function() { + // this mouseup event is fired only if no dragging occurred, by jquery and yui, but for mootools + // it is fired even if dragging has occurred, in which case we would blow away a perfectly + // legitimate endpoint, were it not for this check. the flag is set after adding an + // endpoint and cleared in a drag listener we set in the dragOptions above. + if(endpointAddedButNoDragYet) { + _currentInstance.deleteEndpoint(ep); + } + }; + + _currentInstance.registerListener(ep.canvas, "mouseup", _delTempEndpoint); + _currentInstance.registerListener(_el, "mouseup", _delTempEndpoint); + + // and then trigger its mousedown event, which will kick off a drag, which will start dragging + // a new connection from this endpoint. + jpcl.trigger(ep.canvas, "mousedown", e); + }; + + // register this on jsPlumb so that it can be cleared by a reset. + _currentInstance.registerListener(_el, "mousedown", mouseDownListener); + _sourceTriggers[elid] = mouseDownListener; + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + + return _currentInstance; + }; + + /** + * Function: unmakeSource + * Sets the given element to no longer be a connection source. + * Parameters: + * el - either a string id or a selector representing the element. + * Returns: + * The current jsPlumb instance. + */ + this.unmakeSource = function(el, doNotClearArrays) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + var id = _getId(el), + mouseDownListener = _sourceTriggers[id]; + + if (mouseDownListener) + _currentInstance.unregisterListener(_el, "mousedown", mouseDownListener); + + if (!doNotClearArrays) { + delete _sourceEndpointDefinitions[id]; + delete _sourceEndpointsUnique[id]; + delete _sourcesEnabled[id]; + delete _sourceTriggers[id]; + delete _sourceMaxConnections[id]; + } + + return _currentInstance; + }; + + /* + * Function: unmakeEverySource + * Resets all elements in this instance of jsPlumb so that none of them are connection sources. + * Returns: + * The current jsPlumb instance. + */ + this.unmakeEverySource = function() { + for (var i in _sourcesEnabled) + _currentInstance.unmakeSource(i, true); + + _sourceEndpointDefinitions = {}; + _sourceEndpointsUnique = {}; + _sourcesEnabled = {}; + _sourceTriggers = {}; + }; + + /* + * Function: unmakeEveryTarget + * Resets all elements in this instance of jsPlumb so that none of them are connection targets. + * Returns: + * The current jsPlumb instance. + */ + this.unmakeEveryTarget = function() { + for (var i in _targetsEnabled) + _currentInstance.unmakeTarget(i, true); + + _targetEndpointDefinitions = {}; + _targetEndpointsUnique = {}; + _targetMaxConnections = {}; + _targetsEnabled = {}; + + return _currentInstance; + }; + + /* + * Function: makeSources + * Makes all elements in some array or a selector connection sources. + * Parameters: + * els - either an array of ids or a selector + * params - parameters to configure each element as a source with + * referenceParams - extra parameters to configure each element as a source with. + * + * Returns: + * The current jsPlumb instance. + */ + this.makeSources = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeSource(els[i], params, referenceParams); + } + + return _currentInstance; + }; + + // does the work of setting a source enabled or disabled. + var _setEnabled = function(type, el, state, toggle) { + var a = type == "source" ? _sourcesEnabled : _targetsEnabled; + + if (_isString(el)) a[el] = toggle ? !a[el] : state; + else if (el.length) { + el = _convertYUICollection(el); + for (var i = 0; i < el.length; i++) { + var id = _el = jsPlumb.CurrentLibrary.getElementObject(el[i]), id = _getId(_el); + a[id] = toggle ? !a[id] : state; + } + } + return _currentInstance; + }; + + /* + Function: setSourceEnabled + Sets the enabled state of one or more elements that were previously made a connection source with the makeSource + method. + + Parameters: + el - either a string representing some element's id, or an array of ids, or a selector. + state - true to enable the element(s), false to disable it. + + Returns: + The current jsPlumb instance. + */ + this.setSourceEnabled = function(el, state) { + return _setEnabled("source", el, state); + }; + + /* + * Function: toggleSourceEnabled + * Toggles the source enabled state of the given element or elements. + * + * Parameters: + * el - either a string representing some element's id, or an array of ids, or a selector. + * state - true to enable the element(s), false to disable it. + * + * Returns: + * The current enabled state of the source. + */ + this.toggleSourceEnabled = function(el) { + _setEnabled("source", el, null, true); + return _currentInstance.isSourceEnabled(el); + }; + + /* + * Function: isSource + * Returns whether or not the given element is registered as a connection source. + * + * Parameters: + * el - either a string id, or a selector representing a single element. + * + * Returns: + * True if source, false if not. + */ + this.isSource = function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return _sourcesEnabled[_getId(el)] != null; + }; + + /* + * Function: isSourceEnabled + * Returns whether or not the given connection source is enabled. + * + * Parameters: + * el - either a string id, or a selector representing a single element. + * + * Returns: + * True if enabled, false if not. + */ + this.isSourceEnabled = function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return _sourcesEnabled[_getId(el)] === true; + }; + + /* + * Function: setTargetEnabled + * Sets the enabled state of one or more elements that were previously made a connection target with the makeTarget method. + * method. + * + * Parameters: + * el - either a string representing some element's id, or an array of ids, or a selector. + * state - true to enable the element(s), false to disable it. + * + * Returns: + * The current jsPlumb instance. + */ + this.setTargetEnabled = function(el, state) { + return _setEnabled("target", el, state); + }; + + /* + * Function: toggleTargetEnabled + * Toggles the target enabled state of the given element or elements. + * + * Parameters: + * el - either a string representing some element's id, or an array of ids, or a selector. + * state - true to enable the element(s), false to disable it. + * + * Returns: + * The current enabled state of the target. + */ + this.toggleTargetEnabled = function(el) { + return _setEnabled("target", el, null, true); + return _currentInstance.isTargetEnabled(el); + }; + + /* + Function: isTarget + Returns whether or not the given element is registered as a connection target. + + Parameters: + el - either a string id, or a selector representing a single element. + + Returns: + True if source, false if not. + */ + this.isTarget = function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return _targetsEnabled[_getId(el)] != null; + }; + + /* + Function: isTargetEnabled + Returns whether or not the given connection target is enabled. + + Parameters: + el - either a string id, or a selector representing a single element. + + Returns: + True if enabled, false if not. + */ + this.isTargetEnabled = function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return _targetsEnabled[_getId(el)] === true; + }; + + /* + Function: ready + Helper method to bind a function to jsPlumb's ready event. + */ + this.ready = function(fn) { + _currentInstance.bind("ready", fn); + }, + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, null); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"), + ebe = endpointsByElement[elId]; + if (ebe) { + for ( var i = 0; i < ebe.length; i++) + _currentInstance.deleteEndpoint(ebe[i]); + } + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + var _registeredListeners = {}, + _unbindRegisteredListeners = function() { + for (var i in _registeredListeners) { + for (var j = 0; j < _registeredListeners[i].length; j++) { + var info = _registeredListeners[i][j]; + jsPlumb.CurrentLibrary.unbind(info.el, info.event, info.listener); + } + } + _registeredListeners = {}; + }; + + // internal register listener method. gives us a hook to clean things up + // with if the user calls jsPlumb.reset. + this.registerListener = function(el, type, listener) { + jsPlumb.CurrentLibrary.bind(el, type, listener); + _addToList(_registeredListeners, type, {el:el, event:type, listener:listener}); + }; + + this.unregisterListener = function(el, type, listener) { + jsPlumb.CurrentLibrary.unbind(el, type, listener); + _removeWithFunction(_registeredListeners, function(rl) { + return rl.type == type && rl.listener == listener; + }); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + _currentInstance.deleteEveryEndpoint(); + _currentInstance.unbind(); + _targetEndpointDefinitions = {}; + _targetEndpoints = {}; + _targetEndpointsUnique = {}; + _targetMaxConnections = {}; + _sourceEndpointDefinitions = {}; + _sourceEndpoints = {}; + _sourceEndpointsUnique = {}; + _sourceMaxConnections = {}; + _unbindRegisteredListeners(); + _currentInstance.anchorManager.reset(); + _currentInstance.dragManager.reset(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + + Parameters: + value - whether or not to automatically repaint when the window is resized. + + Returns: void + * + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + };*/ + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setId + * Changes the id of some element, adjusting all connections and endpoints + * + * Parameters: + * el - a selector, a DOM element, or a string. + * newId - string. + */ + this.setId = function(el, newId, doNotSetAttribute) { + + var id = el.constructor == String ? el : _currentInstance.getId(el), + sConns = _currentInstance.getConnections({source:id, scope:'*'}, true), + tConns = _currentInstance.getConnections({target:id, scope:'*'}, true); + + newId = "" + newId; + + if (!doNotSetAttribute) { + el = jsPlumb.CurrentLibrary.getElementObject(id); + jsPlumb.CurrentLibrary.setAttribute(el, "id", newId); + } + + el = jsPlumb.CurrentLibrary.getElementObject(newId); + + + endpointsByElement[newId] = endpointsByElement[id] || []; + for (var i = 0; i < endpointsByElement[newId].length; i++) { + endpointsByElement[newId][i].elementId = newId; + endpointsByElement[newId][i].element = el; + endpointsByElement[newId][i].anchor.elementId = newId; + } + delete endpointsByElement[id]; + + _currentInstance.anchorManager.changeId(id, newId); + + var _conns = function(list, epIdx, type) { + for (var i = 0; i < list.length; i++) { + list[i].endpoints[epIdx].elementId = newId; + list[i].endpoints[epIdx].element = el; + list[i][type + "Id"] = newId; + list[i][type] = el; + } + }; + _conns(sConns, 0, "source"); + _conns(tConns, 1, "target"); + }; + + /* + * Function: setIdChanged + * Notify jsPlumb that the element with oldId has had its id changed to newId. + */ + this.setIdChanged = function(oldId, newId) { + _currentInstance.setId(oldId, newId, true); + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction + * Sets the function to fire when the window size has changed and a repaint was fired. + * + * Parameters: + * f - Function to execute. + * + * Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + + var _suspendDrawing = false; + /* + * Function: setSuspendDrawing + * Suspends drawing operations. This can be used when you have a lot of connections to make or endpoints to register; + * it will save you a lot of time. + */ + this.setSuspendDrawing = function(val, repaintAfterwards) { + _suspendDrawing = val; + if (repaintAfterwards) _currentInstance.repaintEverything(); + }; + + /* + * Function: isSuspendDrawing + * Returns whether or not drawing is currently suspended. + */ + this.isSuspendDrawing = function() { + return _suspendDrawing; + }; + + /* + * Constant for use with the setRenderMode method + */ + this.CANVAS = "canvas"; + + /* + * Constant for use with the setRenderMode method + */ + this.SVG = "svg"; + + this.VML = "vml"; + + /* + * Function: setRenderMode + * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that + * what you asked for is not supported (and that VML is). If you asked for VML but the browser does + * not support it, jsPlumb uses SVG. + * + * Returns: + * the render mode that jsPlumb set, which of course may be different from that requested. + */ + this.setRenderMode = function(mode) { + if (mode) + mode = mode.toLowerCase(); + else + return; + if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); + // now test we actually have the capability to do this. + if (mode === jsPlumb.SVG) { + if (svgAvailable) renderMode = jsPlumb.SVG + else if (canvasAvailable) renderMode = jsPlumb.CANVAS + else if (vmlAvailable()) renderMode = jsPlumb.VML + } + else if (mode === jsPlumb.CANVAS && canvasAvailable) renderMode = jsPlumb.CANVAS; + else if (vmlAvailable()) renderMode = jsPlumb.VML; + + return renderMode; + }; + + this.getRenderMode = function() { return renderMode; }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * changeEndpoints - whether or not to also change the visible state of the endpoints on the element. this also has a bearing on + * other connections on those endpoints: if their other endpoint is also visible, the connections are made visible. + * + * Returns: + * void + */ + this.show = function(el, changeEndpoints) { + _setVisible(el, "block", changeEndpoints); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + //findIndex : _findIndex, + getId : _getId, + makeAnchor:self.makeAnchor, + makeDynamicAnchor:self.makeDynamicAnchor + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * changeEndpoints - whether or not to also toggle the endpoints on the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload + * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element. + * + * Returns: + * void + */ + this.unload = function() { + // this used to do something, but it turns out that what it did was nothing. + // now it exists only for backwards compatibility. + }; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + var adjustForParentOffsetAndScroll = function(xy, el) { + + var offsetParent = null, result = xy; + if (el.tagName.toLowerCase() === "svg" && el.parentNode) { + offsetParent = el.parentNode; + } + else if (el.offsetParent) { + offsetParent = el.offsetParent; + } + if (offsetParent != null) { + var po = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(offsetParent), + so = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : {left:offsetParent.scrollLeft, top:offsetParent.scrollTop}; + + + // i thought it might be cool to do this: + // lastReturnValue[0] = lastReturnValue[0] - offsetParent.offsetLeft + offsetParent.scrollLeft; + // lastReturnValue[1] = lastReturnValue[1] - offsetParent.offsetTop + offsetParent.scrollTop; + // but i think it ignores margins. my reasoning was that it's quicker to not hand off to some underlying + // library. + + result[0] = xy[0] - po.left + so.left; + result[1] = xy[1] - po.top + so.top; + } + + return result; + + }; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + this.elementId = params.elementId; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + + if (timestamp && timestamp === self.timestamp) + return lastReturnValue; + + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + + // adjust loc if there is an offsetParent + lastReturnValue = adjustForParentOffsetAndScroll(lastReturnValue, element.canvas); + + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function(_endpoint) { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + * + * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference, + // the canvas this refers to. + refCanvas = params.referenceCanvas, + size = _getSize(_getElementObject(refCanvas)), + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + xDir = 0, yDir = 0, + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + orientation = null, + _lastResult = null; + + // set these to 0 each; they are used by certain types of connectors in the loopback case, + // when the connector is trying to clear the element it is on. but for floating anchor it's not + // very important. + this.x = 0; this.y = 0; + + this.isFloating = true; + + this.compute = function(params) { + var xy = params.xy, element = params.element, + result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + + // adjust loc if there is an offsetParent + result = adjustForParentOffsetAndScroll(result, element.canvas); + + _lastResult = result; + return result; + }; + + this.getOrientation = function(_endpoint) { + if (orientation) return orientation; + else { + var o = ref.getOrientation(_endpoint); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchor is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector, elementId) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = [], self = this, + _convert = function(anchor) { + return anchor.constructor == Anchor ? anchor: _currentInstance.makeAnchor(anchor, elementId, _currentInstance); + }; + for (var i = 0; i < anchors.length; i++) + _anchors[i] = _convert(anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + this.locked = false; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null, + _curIndex = _anchors.length > 0 ? 0 : -1, + self = this, + + // helper method to calculate the distance between the centers of the two elements. + _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }, + + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + self.x = _curAnchor.x; + self.y = _curAnchor.y; + + return _curAnchor.compute(params); + }; + + this.getCurrentLocation = function() { + return _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + }; + + this.getOrientation = function(_endpoint) { return _curAnchor != null ? _curAnchor.getOrientation(_endpoint) : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + manages anchors for all elements. + */ + // "continuous" anchors: anchors that pick their location based on how many connections the given element has. + // this requires looking at a lot more elements than normal - anything that has had a Continuous anchor applied has + // to be recalculated. so this manager is used as a reference point. the first time, with a new timestamp, that + // a continuous anchor is asked to compute, it calls this guy. or maybe, even, this guy gets called somewhere else + // and compute only ever returns pre-computed values. either way, this is the central point, and we want it to + // be called as few times as possible. + var continuousAnchors = {}, + continuousAnchorLocations = {}, + continuousAnchorOrientations = {}, + Orientation = { HORIZONTAL : "horizontal", VERTICAL : "vertical", DIAGONAL : "diagonal", IDENTITY:"identity" }, + + // TODO this functions uses a crude method of determining orientation between two elements. + // 'diagonal' should be chosen when the angle of the line between the two centers is around + // one of 45, 135, 225 and 315 degrees. maybe +- 15 degrees. + calculateOrientation = function(sourceId, targetId, sd, td) { + + if (sourceId === targetId) return { + orientation:Orientation.IDENTITY, + a:["top", "top"] + }; + + var theta = Math.atan2((td.centery - sd.centery) , (td.centerx - sd.centerx)), + theta2 = Math.atan2((sd.centery - td.centery) , (sd.centerx - td.centerx)), + h = ((sd.left <= td.left && sd.right >= td.left) || (sd.left <= td.right && sd.right >= td.right) || + (sd.left <= td.left && sd.right >= td.right) || (td.left <= sd.left && td.right >= sd.right)), + v = ((sd.top <= td.top && sd.bottom >= td.top) || (sd.top <= td.bottom && sd.bottom >= td.bottom) || + (sd.top <= td.top && sd.bottom >= td.bottom) || (td.top <= sd.top && td.bottom >= sd.bottom)); + + if (! (h || v)) { + var a = null, rls = false, rrs = false, sortValue = null; + if (td.left > sd.left && td.top > sd.top) + a = ["right", "top"]; + else if (td.left > sd.left && sd.top > td.top) + a = [ "top", "left"]; + else if (td.left < sd.left && td.top < sd.top) + a = [ "top", "right"]; + else if (td.left < sd.left && td.top > sd.top) + a = ["left", "top" ]; + + return { orientation:Orientation.DIAGONAL, a:a, theta:theta, theta2:theta2 }; + } + else if (h) return { + orientation:Orientation.HORIZONTAL, + a:sd.top < td.top ? ["bottom", "top"] : ["top", "bottom"], + theta:theta, theta2:theta2 + } + else return { + orientation:Orientation.VERTICAL, + a:sd.left < td.left ? ["right", "left"] : ["left", "right"], + theta:theta, theta2:theta2 + } + }, + placeAnchorsOnLine = function(desc, elementDimensions, elementPosition, + connections, horizontal, otherMultiplier, reverse) { + var a = [], step = elementDimensions[horizontal ? 0 : 1] / (connections.length + 1); + + for (var i = 0; i < connections.length; i++) { + var val = (i + 1) * step, other = otherMultiplier * elementDimensions[horizontal ? 1 : 0]; + if (reverse) + val = elementDimensions[horizontal ? 0 : 1] - val; + + var dx = (horizontal ? val : other), x = elementPosition[0] + dx, xp = dx / elementDimensions[0], + dy = (horizontal ? other : val), y = elementPosition[1] + dy, yp = dy / elementDimensions[1]; + + a.push([ x, y, xp, yp, connections[i][1], connections[i][2] ]); + } + + return a; + }, + standardEdgeSort = function(a, b) { return a[0] > b[0] ? 1 : -1 }, + currySort = function(reverseAngles) { + return function(a,b) { + var r = true; + if (reverseAngles) { + if (a[0][0] < b[0][0]) + r = true; + else + r = a[0][1] > b[0][1]; + } + else { + if (a[0][0] > b[0][0]) + r= true; + else + r =a[0][1] > b[0][1]; + } + return r === false ? -1 : 1; + }; + }, + leftSort = function(a,b) { + // first get adjusted values + var p1 = a[0][0] < 0 ? -Math.PI - a[0][0] : Math.PI - a[0][0], + p2 = b[0][0] < 0 ? -Math.PI - b[0][0] : Math.PI - b[0][0]; + if (p1 > p2) return 1; + else return a[0][1] > b[0][1] ? 1 : -1; + }, + edgeSortFunctions = { + "top":standardEdgeSort, + "right":currySort(true), + "bottom":currySort(true), + "left":leftSort + }, + _sortHelper = function(_array, _fn) { + return _array.sort(_fn); + }, + placeAnchors = function(elementId, _anchorLists) { + var sS = sizes[elementId], sO = offsets[elementId], + placeSomeAnchors = function(desc, elementDimensions, elementPosition, unsortedConnections, isHorizontal, otherMultiplier, orientation) { + if (unsortedConnections.length > 0) { + var sc = _sortHelper(unsortedConnections, edgeSortFunctions[desc]), // puts them in order based on the target element's pos on screen + reverse = desc === "right" || desc === "top", + anchors = placeAnchorsOnLine(desc, elementDimensions, + elementPosition, sc, + isHorizontal, otherMultiplier, reverse ); + + // takes a computed anchor position and adjusts it for parent offset and scroll, then stores it. + var _setAnchorLocation = function(endpoint, anchorPos) { + var a = adjustForParentOffsetAndScroll([anchorPos[0], anchorPos[1]], endpoint.canvas); + continuousAnchorLocations[endpoint.id] = [ a[0], a[1], anchorPos[2], anchorPos[3] ]; + continuousAnchorOrientations[endpoint.id] = orientation; + }; + + for (var i = 0; i < anchors.length; i++) { + var c = anchors[i][4], weAreSource = c.endpoints[0].elementId === elementId, weAreTarget = c.endpoints[1].elementId === elementId; + if (weAreSource) + _setAnchorLocation(c.endpoints[0], anchors[i]); + else if (weAreTarget) + _setAnchorLocation(c.endpoints[1], anchors[i]); + } + } + }; + + placeSomeAnchors("bottom", sS, [sO.left,sO.top], _anchorLists.bottom, true, 1, [0,1]); + placeSomeAnchors("top", sS, [sO.left,sO.top], _anchorLists.top, true, 0, [0,-1]); + placeSomeAnchors("left", sS, [sO.left,sO.top], _anchorLists.left, false, 0, [-1,0]); + placeSomeAnchors("right", sS, [sO.left,sO.top], _anchorLists.right, false, 1, [1,0]); + }, + AnchorManager = function() { + var _amEndpoints = {}, + connectionsByElementId = {}, + self = this, + anchorLists = {}; + + this.reset = function() { + _amEndpoints = {}; + connectionsByElementId = {}; + anchorLists = {}; + }; + this.newConnection = function(conn) { + var sourceId = conn.sourceId, targetId = conn.targetId, + ep = conn.endpoints, + doRegisterTarget = true, + registerConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + + if ((sourceId == targetId) && otherAnchor.isContinuous){ + // remove the target endpoint's canvas. we dont need it. + jsPlumb.CurrentLibrary.removeElement(ep[1].canvas); + doRegisterTarget = false; + } + _addToList(connectionsByElementId, elId, [c, otherEndpoint, otherAnchor.constructor == DynamicAnchor]); + }; + + registerConnection(0, ep[0], ep[0].anchor, targetId, conn); + if (doRegisterTarget) + registerConnection(1, ep[1], ep[1].anchor, sourceId, conn); + }; + this.connectionDetached = function(connInfo) { + var connection = connInfo.connection || connInfo; + var sourceId = connection.sourceId, + targetId = connection.targetId, + ep = connection.endpoints, + removeConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + if (otherAnchor.constructor == FloatingAnchor) { + // no-op + } + else { + _removeWithFunction(connectionsByElementId[elId], function(_c) { + return _c[0].id == c.id; + }); + } + }; + + removeConnection(1, ep[1], ep[1].anchor, sourceId, connection); + removeConnection(0, ep[0], ep[0].anchor, targetId, connection); + + // remove from anchorLists + var sEl = connection.sourceId, + tEl = connection.targetId, + sE = connection.endpoints[0].id, + tE = connection.endpoints[1].id, + _remove = function(list, eId) { + if (list) { // transient anchors dont get entries in this list. + var f = function(e) { return e[4] == eId; }; + _removeWithFunction(list["top"], f); + _removeWithFunction(list["left"], f); + _removeWithFunction(list["bottom"], f); + _removeWithFunction(list["right"], f); + } + }; + + _remove(anchorLists[sEl], sE); + _remove(anchorLists[tEl], tE); + self.redraw(sEl); + self.redraw(tEl); + }; + this.add = function(endpoint, elementId) { + _addToList(_amEndpoints, elementId, endpoint); + }; + this.changeId = function(oldId, newId) { + connectionsByElementId[newId] = connectionsByElementId[oldId]; + _amEndpoints[newId] = _amEndpoints[oldId]; + delete connectionsByElementId[oldId]; + delete _amEndpoints[oldId]; + }; + this.getConnectionsFor = function(elementId) { + return connectionsByElementId[elementId] || []; + }; + this.getEndpointsFor = function(elementId) { + return _amEndpoints[elementId] || []; + }; + this.deleteEndpoint = function(endpoint) { + _removeWithFunction(_amEndpoints[endpoint.elementId], function(e) { + return e.id == endpoint.id; + }); + }; + this.clearFor = function(elementId) { + delete _amEndpoints[elementId]; + _amEndpoints[elementId] = []; + }; + // updates the given anchor list by either updating an existing anchor's info, or adding it. this function + // also removes the anchor from its previous list, if the edge it is on has changed. + // all connections found along the way (those that are connected to one of the faces this function + // operates on) are added to the connsToPaint list, as are their endpoints. in this way we know to repaint + // them wthout having to calculate anything else about them. + var _updateAnchorList = function(lists, theta, order, conn, aBoolean, otherElId, idx, reverse, edgeId, elId, connsToPaint, endpointsToPaint) { + // first try to find the exact match, but keep track of the first index of a matching element id along the way.s + var exactIdx = -1, + firstMatchingElIdx = -1, + endpoint = conn.endpoints[idx], + endpointId = endpoint.id, + oIdx = [1,0][idx], + values = [ [ theta, order ], conn, aBoolean, otherElId, endpointId ], + listToAddTo = lists[edgeId], + listToRemoveFrom = endpoint._continuousAnchorEdge ? lists[endpoint._continuousAnchorEdge] : null; + + if (listToRemoveFrom) { + var rIdx = _findWithFunction(listToRemoveFrom, function(e) { return e[4] == endpointId }); + if (rIdx != -1) { + listToRemoveFrom.splice(rIdx, 1); + // get all connections from this list + for (var i = 0; i < listToRemoveFrom.length; i++) { + _addWithFunction(connsToPaint, listToRemoveFrom[i][1], function(c) { return c.id == listToRemoveFrom[i][1].id }); + _addWithFunction(endpointsToPaint, listToRemoveFrom[i][1].endpoints[idx], function(e) { return e.id == listToRemoveFrom[i][1].endpoints[idx].id }); + } + } + } + + for (var i = 0; i < listToAddTo.length; i++) { + if (idx == 1 && listToAddTo[i][3] === otherElId && firstMatchingElIdx == -1) + firstMatchingElIdx = i; + _addWithFunction(connsToPaint, listToAddTo[i][1], function(c) { return c.id == listToAddTo[i][1].id }); + _addWithFunction(endpointsToPaint, listToAddTo[i][1].endpoints[idx], function(e) { return e.id == listToAddTo[i][1].endpoints[idx].id }); + } + if (exactIdx != -1) { + listToAddTo[exactIdx] = values; + } + else { + var insertIdx = reverse ? firstMatchingElIdx != -1 ? firstMatchingElIdx : 0 : listToAddTo.length; // of course we will get this from having looked through the array shortly. + listToAddTo.splice(insertIdx, 0, values); + } + + // store this for next time. + endpoint._continuousAnchorEdge = edgeId; + }; + this.redraw = function(elementId, ui, timestamp, offsetToUI) { + + if (!_suspendDrawing) { + // get all the endpoints for this element + var ep = _amEndpoints[elementId] || [], + endpointConnections = connectionsByElementId[elementId] || [], + connectionsToPaint = [], + endpointsToPaint = [], + anchorsToUpdate = []; + + timestamp = timestamp || _timestamp(); + // offsetToUI are values that would have been calculated in the dragManager when registering + // an endpoint for an element that had a parent (somewhere in the hierarchy) that had been + // registered as draggable. + offsetToUI = offsetToUI || {left:0, top:0}; + if (ui) { + ui = { + left:ui.left + offsetToUI.left, + top:ui.top + offsetToUI.top + } + } + + _updateOffset( { elId : elementId, offset : ui, recalc : false, timestamp : timestamp }); + // valid for one paint cycle. + var myOffset = offsets[elementId], + myWH = sizes[elementId], + orientationCache = {}; + + // actually, first we should compute the orientation of this element to all other elements to which + // this element is connected with a continuous anchor (whether both ends of the connection have + // a continuous anchor or just one) + //for (var i = 0; i < continuousAnchorConnections.length; i++) { + for (var i = 0; i < endpointConnections.length; i++) { + var conn = endpointConnections[i][0], + sourceId = conn.sourceId, + targetId = conn.targetId, + sourceContinuous = conn.endpoints[0].anchor.isContinuous, + targetContinuous = conn.endpoints[1].anchor.isContinuous; + + if (sourceContinuous || targetContinuous) { + var oKey = sourceId + "_" + targetId, + oKey2 = targetId + "_" + sourceId, + o = orientationCache[oKey], + oIdx = conn.sourceId == elementId ? 1 : 0; + + if (sourceContinuous && !anchorLists[sourceId]) anchorLists[sourceId] = { top:[], right:[], bottom:[], left:[] }; + if (targetContinuous && !anchorLists[targetId]) anchorLists[targetId] = { top:[], right:[], bottom:[], left:[] }; + + if (elementId != targetId) _updateOffset( { elId : targetId, timestamp : timestamp }); + if (elementId != sourceId) _updateOffset( { elId : sourceId, timestamp : timestamp }); + + var td = _getCachedData(targetId), + sd = _getCachedData(sourceId); + + if (targetId == sourceId && (sourceContinuous || targetContinuous)) { + // here we may want to improve this by somehow determining the face we'd like + // to put the connector on. ideally, when drawing, the face should be calculated + // by determining which face is closest to the point at which the mouse button + // was released. for now, we're putting it on the top face. + _updateAnchorList(anchorLists[sourceId], -Math.PI / 2, 0, conn, false, targetId, 0, false, "top", sourceId, connectionsToPaint, endpointsToPaint) + } + else { + if (!o) { + o = calculateOrientation(sourceId, targetId, sd.o, td.o); + orientationCache[oKey] = o; + // this would be a performance enhancement, but the computed angles need to be clamped to + //the (-PI/2 -> PI/2) range in order for the sorting to work properly. + /* orientationCache[oKey2] = { + orientation:o.orientation, + a:[o.a[1], o.a[0]], + theta:o.theta + Math.PI, + theta2:o.theta2 + Math.PI + };*/ + } + if (sourceContinuous) _updateAnchorList(anchorLists[sourceId], o.theta, 0, conn, false, targetId, 0, false, o.a[0], sourceId, connectionsToPaint, endpointsToPaint); + if (targetContinuous) _updateAnchorList(anchorLists[targetId], o.theta2, -1, conn, true, sourceId, 1, true, o.a[1], targetId, connectionsToPaint, endpointsToPaint); + } + + if (sourceContinuous) _addWithFunction(anchorsToUpdate, sourceId, function(a) { return a === sourceId; }); + if (targetContinuous) _addWithFunction(anchorsToUpdate, targetId, function(a) { return a === targetId; }); + _addWithFunction(connectionsToPaint, conn, function(c) { return c.id == conn.id; }); + if ((sourceContinuous && oIdx == 0) || (targetContinuous && oIdx == 1)) + _addWithFunction(endpointsToPaint, conn.endpoints[oIdx], function(e) { return e.id == conn.endpoints[oIdx].id; }); + } + } + + // now place all the continuous anchors we need to; + for (var i = 0; i < anchorsToUpdate.length; i++) { + placeAnchors(anchorsToUpdate[i], anchorLists[anchorsToUpdate[i]]); + } + + // now that continuous anchors have been placed, paint all the endpoints for this element + // TODO performance: add the endpoint ids to a temp array, and then when iterating in the next + // loop, check that we didn't just paint that endpoint. we can probably shave off a few more milliseconds this way. + for (var i = 0; i < ep.length; i++) { + ep[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + // ... and any other endpoints we came across as a result of the continuous anchors. + for (var i = 0; i < endpointsToPaint.length; i++) { + endpointsToPaint[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + + // paint all the standard and "dynamic connections", which are connections whose other anchor is + // static and therefore does need to be recomputed; we make sure that happens only one time. + + // TODO we could have compiled a list of these in the first pass through connections; might save some time. + for (var i = 0; i < endpointConnections.length; i++) { + var otherEndpoint = endpointConnections[i][1]; + if (otherEndpoint.anchor.constructor == DynamicAnchor) { + otherEndpoint.paint({ elementWithPrecedence:elementId }); + _addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; }); + // all the connections for the other endpoint now need to be repainted + for (var k = 0; k < otherEndpoint.connections.length; k++) { + if (otherEndpoint.connections[k] !== endpointConnections[i][0]) + _addWithFunction(connectionsToPaint, otherEndpoint.connections[k], function(c) { return c.id == otherEndpoint.connections[k].id; }); + } + } else if (otherEndpoint.anchor.constructor == Anchor) { + _addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; }); + } + } + // paint current floating connection for this element, if there is one. + var fc = floatingConnections[elementId]; + if (fc) + fc.paint({timestamp:timestamp, recalc:false, elId:elementId}); + + // paint all the connections + for (var i = 0; i < connectionsToPaint.length; i++) { + connectionsToPaint[i].paint({elId:elementId, timestamp:timestamp, recalc:false}); + } + } + }; + this.rehomeEndpoint = function(currentId, element) { + var eps = _amEndpoints[currentId] || [], //, + elementId = _currentInstance.getId(element); + for (var i = 0; i < eps.length; i++) { + self.add(eps[i], elementId); + } + eps.splice(0, eps.length); + }; + }; + _currentInstance.anchorManager = new AnchorManager(); + _currentInstance.continuousAnchorFactory = { + get:function(params) { + var existing = continuousAnchors[params.elementId]; + if (!existing) { + existing = { + type:"Continuous", + compute : function(params) { + return continuousAnchorLocations[params.element.id] || [0,0]; + }, + getCurrentLocation : function(endpoint) { + return continuousAnchorLocations[endpoint.id] || [0,0]; + }, + getOrientation : function(endpoint) { + return continuousAnchorOrientations[endpoint.id] || [0,0]; + }, + isDynamic : true, + isContinuous : true + }; + continuousAnchors[params.elementId] = existing; + } + return existing; + } + }; + + /** + Manages dragging for some instance of jsPlumb. + + */ + var DragManager = function() { + + var _draggables = {}, _dlist = [], _delements = {}, _elementsWithEndpoints = {}; + + /** + register some element as draggable. right now the drag init stuff is done elsewhere, and it is + possible that will continue to be the case. + */ + this.register = function(el) { + var jpcl = jsPlumb.CurrentLibrary; + el = jpcl.getElementObject(el); + var id = _currentInstance.getId(el), + domEl = jpcl.getDOMElement(el); + if (!_draggables[id]) { + _draggables[id] = el; + _dlist.push(el); + _delements[id] = {}; + } + + // look for child elements that have endpoints and register them against this draggable. + var _oneLevel = function(p) { + var pEl = jpcl.getElementObject(p), + pOff = jpcl.getOffset(pEl); + + for (var i = 0; i < p.childNodes.length; i++) { + if (p.childNodes[i].nodeType != 3) { + var cEl = jpcl.getElementObject(p.childNodes[i]), + cid = _currentInstance.getId(cEl, null, true); + if (cid && _elementsWithEndpoints[cid] && _elementsWithEndpoints[cid] > 0) { + var cOff = jpcl.getOffset(cEl); + _delements[id][cid] = { + id:cid, + offset:{ + left:cOff.left - pOff.left, + top:cOff.top - pOff.top + } + }; + } + } + } + }; + + _oneLevel(domEl); + }; + + /** + notification that an endpoint was added to the given el. we go up from that el's parent + node, looking for a parent that has been registered as a draggable. if we find one, we add this + el to that parent's list of elements to update on drag (if it is not there already) + */ + this.endpointAdded = function(el) { + var jpcl = jsPlumb.CurrentLibrary, b = document.body, id = _currentInstance.getId(el), c = jpcl.getDOMElement(el), + p = c.parentNode, done = p == b; + + _elementsWithEndpoints[id] = _elementsWithEndpoints[id] ? _elementsWithEndpoints[id] + 1 : 1; + + while (p != b) { + var pid = _currentInstance.getId(p); + if (_draggables[pid]) { + var idx = -1, pEl = jpcl.getElementObject(p), pLoc = jpcl.getOffset(pEl); + + if (_delements[pid][id] == null) { + var cLoc = jsPlumb.CurrentLibrary.getOffset(el); + _delements[pid][id] = { + id:id, + offset:{ + left:cLoc.left - pLoc.left, + top:cLoc.top - pLoc.top + } + }; + } + break; + } + p = p.parentNode; + } + }; + + this.endpointDeleted = function(endpoint) { + if (_elementsWithEndpoints[endpoint.elementId]) { + _elementsWithEndpoints[endpoint.elementId]--; + if (_elementsWithEndpoints[endpoint.elementId] <= 0) { + for (var i in _delements) { + delete _delements[i][endpoint.elementId]; + } + } + } + }; + + this.getElementsForDraggable = function(id) { + return _delements[id]; + }; + + this.reset = function() { + _draggables = {}; + _dlist = []; + _delements = {}; + _elementsWithEndpoints = {}; + }; + + }; + _currentInstance.dragManager = new DragManager(); + + + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. You should not ever create one of these directly. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * container - optional id or selector instructing jsPlumb where to attach all the elements it creates for this connection. you should read the documentation for a full discussion of this. + * detachable - optional, defaults to true. Defines whether or not the connection may be detached using the mouse. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * overlays - Optional array of Overlay definitions to appear on this Connection. + * drawEndpoints - if false, instructs jsPlumb to not draw the endpoints for this Connection. Be careful with this: it only really works when you tell jsPlumb to attach elements to the document body. Read the documentation for a full discussion of this. + * parameters - Optional JS object containing parameters to set on the Connection. These parameters are then available via the getParameter method. + */ + var Connection = function(params) { + var self = this, visible = true; + self.idPrefix = "_jsplumb_c_"; + self.defaultLabelLocation = 0.5; + self.defaultOverlayKeys = ["Overlays", "ConnectionOverlays"]; + this.parent = params.parent; + overlayCapableJsPlumbUIComponent.apply(this, arguments); + // ************** get the source and target and register the connection. ******************* + + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + self[v ? "showOverlays" : "hideOverlays"](); + if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none"; + }; + + /** + Property: source + The source element for this Connection. + */ + this.source = _getElementObject(params.source); + /** + Property:target + The target element for this Connection. + */ + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. but + // source is not overridden if the Endpoint has declared it is not the final target of a connection; + // instead we use the source that the Endpoint declares will be the final source element. + if (params.sourceEndpoint) { + this.source = params.sourceEndpoint.endpointWillMoveTo || params.sourceEndpoint.getElement(); + } + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + + // if a new connection is the result of moving some existing connection, params.previousConnection + // will have that Connection in it. listeners for the jsPlumbConnection event can look for that + // member and take action if they need to. + self.previousConnection = params.previousConnection; + + var _cost = params.cost; + self.getCost = function() { return _cost; }; + self.setCost = function(c) { _cost = c; }; + + var _bidirectional = params.bidirectional === false ? false : true; + self.isBidirectional = function() { return _bidirectional; }; + + /* + * Property: sourceId + * Id of the source element in the connection. + */ + this.sourceId = _getAttribute(this.source, "id"); + /* + * Property: targetId + * Id of the target element in the connection. + */ + this.targetId = _getAttribute(this.target, "id"); + + /** + * implementation of abstract method in jsPlumbUtil.EventGenerator + * @return list of attached elements. in our case, a list of Endpoints. + */ + this.getAttachedElements = function() { + return self.endpoints; + }; + + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams, elementId) { + if (anchorParams) + return _currentInstance.makeAnchor(anchorParams, elementId, _currentInstance); + }, + prepareEndpoint = function(existing, index, params, element, elementId, connectorPaintStyle, connectorHoverPaintStyle) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] + || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint; + + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified. + if (es.fillStyle == null && connectorPaintStyle != null) + es.fillStyle = connectorPaintStyle.strokeStyle; + + // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does: + //* + if (es.outlineColor == null && connectorPaintStyle != null) + es.outlineColor = connectorPaintStyle.outlineColor; + if (es.outlineWidth == null && connectorPaintStyle != null) + es.outlineWidth = connectorPaintStyle.outlineWidth; + //*/ + + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure? + if (connectorHoverPaintStyle != null) { + if (ehs == null) ehs = {}; + if (ehs.fillStyle == null) { + ehs.fillStyle = connectorHoverPaintStyle.strokeStyle; + } + } + var a = params.anchors ? params.anchors[index] : + params.anchor ? params.anchor : + _makeAnchor(_currentInstance.Defaults.Anchors[index], elementId) || + _makeAnchor(jsPlumb.Defaults.Anchors[index], elementId) || + _makeAnchor(_currentInstance.Defaults.Anchor, elementId) || + _makeAnchor(jsPlumb.Defaults.Anchor, elementId), + u = params.uuids ? params.uuids[index] : null, + e = _newEndpoint({ + paintStyle : es, + hoverPaintStyle:ehs, + endpoint : ep, + connections : [ self ], + uuid : u, + anchor : a, + source : element, + scope : params.scope, + container:params.container, + reattach:params.reattach, + detachable:params.detachable + }); + self.endpoints[index] = e; + + + if (params.drawEndpoints === false) e.setVisible(false, true, true); + + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, + 0, + params, + self.source, + self.sourceId, + params.paintStyle, + params.hoverPaintStyle); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + + // if there were no endpoints supplied and the source element is the target element, we will reuse the source + // endpoint that was just created. + var existingTargetEndpoint = ((self.sourceId == self.targetId) && params.targetEndpoint == null) ? eS : params.targetEndpoint, + eT = prepareEndpoint(existingTargetEndpoint, + 1, + params, + self.target, + self.targetId, + params.paintStyle, + params.hoverPaintStyle); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + // if delete endpoints on detach, keep a record of just exactly which endpoints they are. + if (params.deleteEndpointsOnDetach) + self.endpointsToDeleteOnDetach = [eS, eT]; + + var _detachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.detachable === false) _detachable = false; + if(self.endpoints[0].connectionsDetachable === false) _detachable = false; + if(self.endpoints[1].connectionsDetachable === false) _detachable = false; + + // inherit connectin cost if it was set on source endpoint + if (_cost == null) _cost = self.endpoints[0].getConnectionCost(); + // inherit bidirectional flag if set no source endpoint + if (params.bidirectional == null) _bidirectional = self.endpoints[0].areConnectionsBidirectional(); + + /* + Function: isDetachable + Returns whether or not this connection can be detached from its target/source endpoint. by default this + is false; use it in conjunction with the 'reattach' parameter. + */ + this.isDetachable = function() { + return _detachable === true; + }; + + /* + Function: setDetachable + Sets whether or not this connection is detachable. + */ + this.setDetachable = function(detachable) { + _detachable = detachable === true; + }; + + // merge all the parameters objects into the connection. parameters set + // on the connection take precedence; then target endpoint params, then + // finally source endpoint params. + // TODO jsPlumb.extend could be made to take more than two args, and it would + // apply the second through nth args in order. + var _p = jsPlumb.extend({}, this.endpoints[0].getParameters()); + jsPlumb.extend(_p, this.endpoints[1].getParameters()); + jsPlumb.extend(_p, self.getParameters()); + self.setParameters(_p); + + // override setHover to pass it down to the underlying connector + var _sh = self.setHover; + + self.setHover = function(state) { + self.connector.setHover.apply(self.connector, arguments); + _sh.apply(self, arguments); + }; + + var _internalHover = function(state) { + if (_connectionBeingDragged == null) { + self.setHover(state, false); + } + }; + + /* + * Function: setConnector + * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method - the same + * thing that you would set as the 'connector' property on a jsPlumb.connect call. + * + * Parameters: + * connector - Connector definition + */ + this.setConnector = function(connector, doNotRepaint) { + if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent); + var connectorArgs = { + _jsPlumb:self._jsPlumb, + parent:params.parent, + cssClass:params.cssClass, + container:params.container, + tooltip:self.tooltip + }; + if (_isString(connector)) + this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand. + else if (_isArray(connector)) + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs)); + self.canvas = self.connector.canvas; + // binds mouse listeners to the current connector. + _bindListeners(self.connector, self, _internalHover); + if (!doNotRepaint) self.repaint(); + }; + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc) + */ + + self.setConnector(this.endpoints[0].connector || + this.endpoints[1].connector || + params.connector || + _currentInstance.Defaults.Connector || + jsPlumb.Defaults.Connector, true); + + this.setPaintStyle(this.endpoints[0].connectorStyle || + this.endpoints[1].connectorStyle || + params.paintStyle || + _currentInstance.Defaults.PaintStyle || + jsPlumb.Defaults.PaintStyle, true); + + this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || + this.endpoints[1].connectorHoverStyle || + params.hoverPaintStyle || + _currentInstance.Defaults.HoverPaintStyle || + jsPlumb.Defaults.HoverPaintStyle, true); + + this.paintStyleInUse = this.getPaintStyle(); + + + this.moveParent = function(newParent) { + var jpcl = jsPlumb.CurrentLibrary, curParent = jpcl.getParent(self.connector.canvas); + if (self.connector.bgCanvas) { + jpcl.removeElement(self.connector.bgCanvas, curParent); + jpcl.appendElement(self.connector.bgCanvas, newParent); + } + jpcl.removeElement(self.connector.canvas, curParent); + jpcl.appendElement(self.connector.canvas, newParent); + // this only applies for DOMOverlays + for (var i = 0; i < self.overlays.length; i++) { + if (self.overlays[i].isAppendedAtTopLevel) { + jpcl.removeElement(self.overlays[i].canvas, curParent); + jpcl.appendElement(self.overlays[i].canvas, newParent); + if (self.overlays[i].reattachListeners) + self.overlays[i].reattachListeners(self.connector); + } + } + if (self.connector.reattachListeners) // this is for SVG/VML; change an element's parent and you have to reinit its listeners. + self.connector.reattachListeners(); // the Canvas implementation doesn't have to care about this + }; + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Connection. + * + * Parameters: + * event - the event to bind. Available events on a Connection are: + * - *click* : notification that a Connection was clicked. + * - *dblclick* : notification that a Connection was double clicked. + * - *mouseenter* : notification that the mouse is over a Connection. + * - *mouseexit* : notification that the mouse exited a Connection. + * - *contextmenu* : notification that the user right-clicked on the Connection. + * + * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Connection's paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: getPaintStyle + * Gets the Connection's paint style. This is not necessarily the paint style in use at the time; + * this is the paint style for the connection when the mouse it not hovering over it. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Connection will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Connection. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + + /* + * Function: getParameter + * Gets the named parameter; returns null if no parameter with that name is set. Parameter values may have been supplied to a 'connect' or 'addEndpoint' call (connections made with the mouse get a copy of all parameters set on each of their Endpoints), or the parameter value may have been set with setParameter. + * + * Parameters: + * key - Parameter name. + */ + + /* + * Function: setParameter + * Sets the named parameter to the given value. + * + * Parameters: + * key - Parameter name. + * value - Parameter value. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId], + otherOffset = offsets[this.targetId], + otherWH = sizes[this.targetId], + initialTimestamp = _timestamp(), + anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + elementId:this.endpoints[0].elementId, + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1], + timestamp:initialTimestamp + }); + + this.endpoints[0].paint( { anchorLoc : anchorLoc, timestamp:initialTimestamp }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + elementId:this.endpoints[1].elementId, + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0], + timestamp:initialTimestamp + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc, timestamp:initialTimestamp }); + + /* + * Paints the Connection. Not exposed for public usage. + * + * Parameters: + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp, + // if the moving object is not the source we must transpose the two references. + swap = false, + tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId, + tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + + var sourceInfo = _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }), + targetInfo = _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var sE = this.endpoints[sIdx], tE = this.endpoints[tIdx], + sAnchorP = sE.anchor.getCurrentLocation(sE), + tAnchorP = tE.anchor.getCurrentLocation(tE); + + /* paint overlays*/ + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible()) maxSize = Math.max(maxSize, o.computeMaxSize(self.connector)); + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, + this.endpoints[sIdx], this.endpoints[tIdx], + this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, + self.paintStyleInUse.lineWidth, maxSize, + sourceInfo, + targetInfo); + + self.connector.paint(dim, self.paintStyleInUse); + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim); + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function(params) { + params = params || {}; + var recalc = !(params.recalc === false); + this.paint({ elId : this.sourceId, recalc : recalc, timestamp:params.timestamp }); + }; + + }; + +// ENDPOINT HELPER FUNCTIONS + var _makeConnectionDragHandler = function(placeholder) { + var stopped = false; + return { + + drag : function() { + if (stopped) { + stopped = false; + return true; + } + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments), + el = placeholder.element; + if (el) { + jsPlumb.CurrentLibrary.setOffset(el, _ui); + _draw(_getElementObject(el), _ui); + } + }, + stopDrag : function() { + stopped = true; + } + }; + }; + + var _makeFloatingEndpoint = function(paintStyle, referenceAnchor, endpoint, referenceCanvas, sourceElement) { + var floatingAnchor = new FloatingAnchor( { reference : referenceAnchor, referenceCanvas : referenceCanvas }); + + //setting the scope here should not be the way to fix that mootools issue. it should be fixed by not + // adding the floating endpoint as a droppable. that makes more sense anyway! + + return _newEndpoint({ paintStyle : paintStyle, endpoint : endpoint, anchor : floatingAnchor, source : sourceElement, scope:"__floating" }); + }; + + /** + * creates a placeholder div for dragging purposes, adds it to the DOM, and pre-computes its offset. then returns + * both the element id and a selector for the element. + */ + var _makeDraggablePlaceholder = function(placeholder, parent) { + var n = document.createElement("div"); + n.style.position = "absolute"; + var placeholderDragElement = _getElementObject(n); + _appendElement(n, parent); + var id = _getId(placeholderDragElement); + _updateOffset( { elId : id }); + // create and assign an id, and initialize the offset. + placeholder.id = id; + placeholder.element = placeholderDragElement; + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1 + * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't + * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you + * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or + * 'target' Endpoints for Connections. + * + * + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * enabled - optional, defaults to true. Indicates whether or not the Endpoint should be enabled for mouse events (drag/drop). + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * container - optional id or selector instructing jsPlumb where to attach the element it creates for this endpoint. you should read the documentation for a full discussion of this. + * connections - optional list of Connections to configure the Endpoint with. + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * connectionsDetachable - optional, defaults to true. Sets whether connections to/from this Endpoint should be detachable or not. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + * parameters - Optional JS object containing parameters to set on the Endpoint. These parameters are then available via the getParameter method. When a connection is made involving this Endpoint, the parameters from this Endpoint are copied into that Connection. Source Endpoint parameters override target Endpoint parameters if they both have a parameter with the same name. + */ + var Endpoint = function(params) { + var self = this; + self.idPrefix = "_jsplumb_e_"; + self.defaultLabelLocation = [ 0.5, 0.5 ]; + self.defaultOverlayKeys = ["Overlays", "EndpointOverlays"]; + this.parent = params.parent; + overlayCapableJsPlumbUIComponent.apply(this, arguments); + params = params || {}; + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Endpoint. + * + * Parameters: + * event - the event to bind. Available events on an Endpoint are: + * - *click* : notification that a Endpoint was clicked. + * - *dblclick* : notification that a Endpoint was double clicked. + * - *mouseenter* : notification that the mouse is over a Endpoint. + * - *mouseexit* : notification that the mouse exited a Endpoint. + * - *contextmenu* : notification that the user right-clicked on the Endpoint. + * + * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Endpoint's paint style and then repaints the Endpoint. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: getPaintStyle + * Gets the Endpoint's paint style. This is not necessarily the paint style in use at the time; + * this is the paint style for the Endpoint when the mouse it not hovering over it. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Endpoint will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Endpoint. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + + /* + * Function: getParameter + * Gets the named parameter; returns null if no parameter with that name is set. Parameters may have been set on the Endpoint in the 'addEndpoint' call, or they may have been set with the setParameter function. + * + * Parameters: + * key - Parameter name. + */ + + /* + * Function: setParameter + * Sets the named parameter to the given value. + * + * Parameters: + * key - Parameter name. + * value - Parameter value. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + var visible = true, __enabled = !(params.enabled === false); + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + self[v ? "showOverlays" : "hideOverlays"](); + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + + /* + Function: isEnabled + Returns whether or not the Endpoint is enabled for drag/drop connections. + */ + this.isEnabled = function() { return __enabled; }; + + /* + Function: setEnabled + Sets whether or not the Endpoint is enabled for drag/drop connections. + */ + this.setEnabled = function(e) { __enabled = e; }; + + var _element = params.source, _uuid = params.uuid, floatingEndpoint = null, inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + this.element = _element; + + var _connectionCost = params.connectionCost; + this.getConnectionCost = function() { return _connectionCost; }; + this.setConnectionCost = function(c) { + _connectionCost = c; + }; + + var _connectionsBidirectional = params.connectionsBidirectional === false ? false : true; + this.areConnectionsBidirectional = function() { return _connectionsBidirectional; }; + this.setConnectionsBidirectional = function(b) { _connectionsBidirectional = b; }; + + self.anchor = params.anchor ? _currentInstance.makeAnchor(params.anchor, _elementId, _currentInstance) : params.anchors ? _currentInstance.makeAnchor(params.anchors, _elementId, _currentInstance) : _currentInstance.makeAnchor("TopCenter", _elementId, _currentInstance); + + // ANCHOR MANAGER + if (!params._transient) // in place copies, for example, are transient. they will never need to be retrieved during a paint cycle, because they dont move, and then they are deleted. + _currentInstance.anchorManager.add(self, _elementId); + + var _endpoint = null, originalEndpoint = null; + this.setEndpoint = function(ep) { + var endpointArgs = { + _jsPlumb:self._jsPlumb, + parent:params.parent, + container:params.container, + tooltip:params.tooltip, + connectorTooltip:params.connectorTooltip, + endpoint:self + }; + if (_isString(ep)) + _endpoint = new jsPlumb.Endpoints[renderMode][ep](endpointArgs); + else if (_isArray(ep)) { + endpointArgs = jsPlumb.extend(ep[1], endpointArgs); + _endpoint = new jsPlumb.Endpoints[renderMode][ep[0]](endpointArgs); + } + else { + _endpoint = ep.clone(); + } + + // assign a clone function using a copy of endpointArgs. this is used when a drag starts: the endpoint that was dragged is cloned, + // and the clone is left in its place while the original one goes off on a magical journey. + // the copy is to get around a closure problem, in which endpointArgs ends up getting shared by + // the whole world. + var argsForClone = jsPlumb.extend({}, endpointArgs); + _endpoint.clone = function() { + var o = new Object(); + _endpoint.constructor.apply(o, [argsForClone]); + return o; + }; + + self.endpoint = _endpoint; + self.type = self.endpoint.type; + }; + + this.setEndpoint(params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot"); + originalEndpoint = _endpoint; + + // override setHover to pass it down to the underlying endpoint + var _sh = self.setHover; + self.setHover = function() { + self.endpoint.setHover.apply(self.endpoint, arguments); + _sh.apply(self, arguments); + }; + // endpoint delegates to first connection for hover, if there is one. + var internalHover = function(state) { + if (self.connections.length > 0) + self.connections[0].setHover(state, false); + else + self.setHover(state); + }; + + // bind listeners from endpoint to self, with the internal hover function defined above. + _bindListeners(self.endpoint, self, internalHover); + + this.setPaintStyle(params.paintStyle || + params.style || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyle, true); + this.setHoverPaintStyle(params.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyle, true); + this.paintStyleInUse = this.getPaintStyle(); + var originalPaintStyle = this.getPaintStyle(); + this.connectorStyle = params.connectorStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.connectorTooltip = params.connectorTooltip; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + + var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + /* + * Property: canvas + * The Endpoint's Canvas. + */ + this.canvas = this.endpoint.canvas; + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + this.connections = params.connections || []; + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + self.isReattach = params.reattach || false; + self.connectionsDetachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.connectionsDetachable === false || params.detachable === false) + self.connectionsDetachable = false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + if (params.onMaxConnections) + self.bind("maxConnections", params.onMaxConnections); + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget, forceDetach, fireEvent, originalEvent) { + var idx = _findWithFunction(self.connections, function(c) { return c.id == connection.id}), + actuallyDetached = false; + fireEvent = (fireEvent !== false); + if (idx >= 0) { + // 1. does the connection have a before detach (note this also checks jsPlumb's bound + // detach handlers; but then Endpoint's check will, too, hmm.) + if (forceDetach || connection._forceDetach || connection.isDetachable() || connection.isDetachAllowed(connection)) { + // get the target endpoint + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + // it would be nice to check with both endpoints that it is ok to detach. but + // for this we'll have to get a bit fancier: right now if you use the same beforeDetach + // interceptor for two endpoints (which is kind of common, because it's part of the + // endpoint definition), then it gets fired twice. so in fact we need to loop through + // each beforeDetach and see if it returns false, at which point we exit. but if it + // returns true, we have to check the next one. however we need to track which ones + // have already been run, and not run them again. + if (forceDetach || connection._forceDetach || (self.isDetachAllowed(connection) /*&& t.isDetachAllowed(connection)*/)) { + + self.connections.splice(idx, 1); + + // this avoids a circular loop + if (!ignoreTarget) { + + t.detach(connection, true, forceDetach); + // check connection to see if we want to delete the endpoints associated with it. + // we only detach those that have just this connection; this scenario is most + // likely if we got to this bit of code because it is set by the methods that + // create their own endpoints, like .connect or .makeTarget. the user is + // not likely to have interacted with those endpoints. + if (connection.endpointsToDeleteOnDetach){ + for (var i = 0; i < connection.endpointsToDeleteOnDetach.length; i++) { + var cde = connection.endpointsToDeleteOnDetach[i]; + if (cde && cde.connections.length == 0) + _currentInstance.deleteEndpoint(cde); + } + } + } + _removeElements(connection.connector.getDisplayElements(), connection.parent); + _removeWithFunction(connectionsByScope[connection.scope], function(c) { + return c.id == connection.id; + }); + actuallyDetached = true; + var doFireEvent = (!ignoreTarget && fireEvent) + fireDetachEvent(connection, doFireEvent, originalEvent); + } + } + } + return actuallyDetached; + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + * + * Parameters: + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachAll = function(fireEvent, originalEvent) { + while (self.connections.length > 0) { + self.detach(self.connections[0], false, true, fireEvent, originalEvent); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachFrom = function(targetEndpoint, fireEvent, originalEvent) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + if (self.detach(c[i], false, true, fireEvent, originalEvent)) + c[i].setHover(false, false); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findWithFunction(self.connections, function(c) { return c.id == connection.id}); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: setElement + * Sets the DOM element this Endpoint is attached to. + */ + this.setElement = function(el, container) { + + // TODO possibly have this object take charge of moving the UI components into the appropriate + // parent. this is used only by makeSource right now, and that function takes care of + // moving the UI bits and pieces. however it would s + var parentId = _getId(el); + // remove the endpoint from the list for the current endpoint's element + _removeWithFunction(endpointsByElement[self.elementId], function(e) { + return e.id == self.id; + }); + _element = _getElementObject(el); + _elementId = _getId(_element); + self.elementId = _elementId; + // need to get the new parent now + var newParentElement = _getParentFromParams({source:parentId, container:container}), + curParent = jpcl.getParent(self.canvas); + jpcl.removeElement(self.canvas, curParent); + jpcl.appendElement(self.canvas, newParentElement); + + // now move connection(s)...i would expect there to be only one but we will iterate. + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].moveParent(newParentElement); + self.connections[i].sourceId = _elementId; + self.connections[i].source = _element; + } + _addToList(endpointsByElement, parentId, self); + //_currentInstance.repaint(parentId); + + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + var loc = self.anchor.getCurrentLocation(self), + o = self.anchor.getOrientation(self), + inPlaceAnchor = { + compute:function() { return [ loc[0], loc[1] ]}, + getCurrentLocation : function() { return [ loc[0], loc[1] ]}, + getOrientation:function() { return o; } + }; + + return _newEndpoint( { + anchor : inPlaceAnchor, + source : _element, + paintStyle : this.getPaintStyle(), + endpoint : _endpoint, + _transient:true, + scope:self.scope + }); + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + var candidate = self.connections[0]; + if (self.isTarget && candidate) return candidate; + else { + return (self.connections.length < _maxConnections) || _maxConnections == -1 ? null : candidate; + } + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + /* + * Function: setStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to + * setStyle and deprecate it if so. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + * + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + // a helper function that tries to find a connection to the given element, and returns it if so. if elementWithPrecedence is null, + // or no connection to it is found, we return the first connection in our list. + var findConnectionToUseForDynamicAnchor = function(elementWithPrecedence) { + var idx = 0; + if (elementWithPrecedence != null) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].sourceId == elementWithPrecedence || self.connections[i].targetId == elementWithPrecedence) { + idx = i; + break; + } + } + } + + return self.connections[idx]; + }; + + /* + * Function: paint + * Paints the Endpoint, recalculating offset and anchor positions if necessary. This does NOT paint + * any of the Endpoint's connections. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + params = params || {}; + var timestamp = params.timestamp, + recalc = !(params.recalc === false); + if (!timestamp || self.timestamp !== timestamp) { + _updateOffset({ elId:_elementId, timestamp:timestamp, recalc:recalc }); + var xy = params.offset || offsets[_elementId]; + if(xy) { + var ap = params.anchorPoint,connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (recalc && self.anchor.isDynamic && self.connections.length > 0) { + var c = findConnectionToUseForDynamicAnchor(params.elementWithPrecedence), + oIdx = c.endpoints[0] == self ? 1 : 0, + oId = oIdx == 0 ? c.sourceId : c.targetId, + oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + ap = self.anchor.compute(anchorParams); + } + + // switched _endpoint to self here; continuous anchors, for instance, look for the + // endpoint's id, but here "_endpoint" is the renderer, not the actual endpoint. + // TODO need to verify this does not break anything. + //var d = _endpoint.compute(ap, self.anchor.getOrientation(_endpoint), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + var d = _endpoint.compute(ap, self.anchor.getOrientation(self), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + _endpoint.paint(d, self.paintStyleInUse, self.anchor); + self.timestamp = timestamp; + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) self.overlayPlacements[i] = o.draw(self.endpoint, self.paintStyleInUse, d); + } + } + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var placeholderInfo = { id:null, element:null }, + jpc = null, + existingJpc = false, + existingJpcParams = null, + _dragHandler = _makeConnectionDragHandler(placeholderInfo); + + var start = function() { + // drag might have started on an endpoint that is not actually a source, but which has + // one or more connections. + jpc = self.connectorSelector(); + var _continue = true; + // if not enabled, return + if (!self.isEnabled()) _continue = false; + // if no connection and we're not a source, return. + if (jpc == null && !params.isSource) _continue = false; + // otherwise if we're full and not allowed to drag, also return false. + if (params.isSource && self.isFull() && !dragAllowedWhenFull) _continue = false; + // if the connection was setup as not detachable or one of its endpoints + // was setup as connectionsDetachable = false, or Defaults.ConnectionsDetachable + // is set to false... + if (jpc != null && !jpc.isDetachable()) _continue = false; + + if (_continue === false) { + // this is for mootools and yui. returning false from this causes jquery to stop drag. + // the events are wrapped in both mootools and yui anyway, but i don't think returning + // false from the start callback would stop a drag. + if (jsPlumb.CurrentLibrary.stopDrag) jsPlumb.CurrentLibrary.stopDrag(); + _dragHandler.stopDrag(); + return false; + } + + // if we're not full but there was a connection, make it null. we'll create a new one. + if (jpc && !self.isFull() && params.isSource) jpc = null; + + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + _makeDraggablePlaceholder(placeholderInfo, self.parent); + + // set the offset of this div to be where 'inPlaceCopy' is, to start with. + // TODO merge this code with the code in both Anchor and FloatingAnchor, because it + // does the same stuff. + var ipcoel = _getElementObject(inPlaceCopy.canvas), + ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel), + po = adjustForParentOffsetAndScroll([ipco.left, ipco.top], inPlaceCopy.canvas); + jsPlumb.CurrentLibrary.setOffset(placeholderInfo.element, {left:po[0], top:po[1]}); + + // when using makeSource and a parent, we first draw the source anchor on the source element, then + // move it to the parent. note that this happens after drawing the placeholder for the + // first time. + if (self.parentAnchor) self.anchor = _currentInstance.makeAnchor(self.parentAnchor, self.elementId, _currentInstance); + + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", placeholderInfo.id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + + // create a floating endpoint. + // here we test to see if a dragProxy was specified in this endpoint's constructor params, and + // if so, we create that endpoint instead of cloning ourselves. + if (params.proxy) { + /* var floatingAnchor = new FloatingAnchor( { reference : self.anchor, referenceCanvas : self.canvas }); + + floatingEndpoint = _newEndpoint({ + paintStyle : params.proxy.paintStyle, + endpoint : params.proxy.endpoint, + anchor : floatingAnchor, + source : placeholderInfo.element, + scope:"__floating" + }); + + //$(self.canvas).hide(); + */ + self.setPaintStyle(params.proxy.paintStyle); + // if we do this, we have to cleanup the old one. like just remove its display parts + //self.setEndpoint(params.proxy.endpoint); + + } + // else { + floatingEndpoint = _makeFloatingEndpoint(self.getPaintStyle(), self.anchor, _endpoint, self.canvas, placeholderInfo.element); + // } + + if (jpc == null) { + self.anchor.locked = true; + self.setHover(false, false); + // TODO the hover call above does not reset any target endpoint's hover + // states. + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : self.endpointWillMoveTo || _getElementObject(_element), // for makeSource with parent option. ensure source element is represented correctly. + target : placeholderInfo.element, + anchors : [ self.anchor, floatingEndpoint.anchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays + }); + + } else { + existingJpc = true; + jpc.connector.setHover(false, false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas), false, true); + // new anchor idx + var anchorIdx = jpc.endpoints[0].id == self.id ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas), + dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // now we want to get this endpoint's DROP scope, and set it for now: we can only be dropped on drop zones + // that have our drop scope (issue 57). + var dropScope = jsPlumb.CurrentLibrary.getDropScope(c); + jsPlumb.CurrentLibrary.setDragScope(c, dropScope); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = placeholderInfo.element; + jpc.sourceId = placeholderInfo.id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = placeholderInfo.element; + jpc.targetId = placeholderInfo.id; + } + + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.suspendedEndpoint.setHover(false); + jpc.endpoints[anchorIdx] = floatingEndpoint; + + // fire an event that informs that a connection is being dragged + fireConnectionDraggingEvent(jpc); + + } + // register it and register connection on it. + floatingConnections[placeholderInfo.id] = jpc; + floatingEndpoint.addConnection(jpc); + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, placeholderInfo.id, floatingEndpoint); + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var jpcl = jsPlumb.CurrentLibrary, + dragOptions = params.dragOptions || {}, + defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions), + startEvent = jpcl.dragEvents["start"], + stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"]; + + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + // extracted drag handler function so can be used by makeSource + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], _dragHandler.drag); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + var originalEvent = jpcl.getDropEvent(arguments); + _currentInstance.currentlyDragging = false; + _removeWithFunction(endpointsByElement[placeholderInfo.id], function(e) { + return e.id == floatingEndpoint.id; + }); + _removeElements( [ placeholderInfo.element[0], floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + _currentInstance.anchorManager.clearFor(placeholderInfo.id); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + self.setPaintStyle(originalPaintStyle); // reset to original; may have been changed by drag proxy. + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (self.isReattach || jpc._forceDetach || !jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc, false, false, true, originalEvent)) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + _currentInstance.repaint(existingJpcParams[1]); + } + jpc._forceDetach = null; + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElements(jpc.connector.getDisplayElements(), self.parent); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint({recalc:false}); + jpc.setHover(false, false); + + fireConnectionDragStopEvent(jpc); + + jpc = null; + inPlaceCopy = null; + delete endpointsByElement[floatingEndpoint.elementId]; + floatingEndpoint.anchor = null; + floatingEndpoint = null; + _currentInstance.currentlyDragging = false; + + + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions, true); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas, forceInit, isTransient, endpoint) { + if ((params.isTarget || forceInit) && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop'], + overEvent = jsPlumb.CurrentLibrary.dragEvents['over'], + outEvent = jsPlumb.CurrentLibrary.dragEvents['out'], + drop = function() { + + var originalEvent = jsPlumb.CurrentLibrary.getDropEvent(arguments), + draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + elId = _getAttribute(draggable, "elId"), + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id]; + + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + + // restore the original scope if necessary (issue 57) + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + var endpointEnabled = endpoint != null ? endpoint.isEnabled() : true; + + if (self.isFull()) { + self.fire("maxConnections", { + endpoint:self, + connection:jpc, + maxConnections:_maxConnections + }, originalEvent); + } + + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget) && endpointEnabled) { + + var _doContinue = true; + + // the second check here is for the case that the user is dropping it back + // where it came from. + if (jpc.suspendedEndpoint && jpc.suspendedEndpoint.id != self.id) { + if (idx == 0) { + jpc.source = jpc.suspendedEndpoint.element; + jpc.sourceId = jpc.suspendedEndpoint.elementId; + } else { + jpc.target = jpc.suspendedEndpoint.element; + jpc.targetId = jpc.suspendedEndpoint.elementId; + } + + if (!jpc.isDetachAllowed(jpc) || !jpc.endpoints[idx].isDetachAllowed(jpc) || !jpc.suspendedEndpoint.isDetachAllowed(jpc) || !_currentInstance.checkCondition("beforeDetach", jpc)) + _doContinue = false; + } + + // these have to be set before testing for beforeDrop. + if (idx == 0) { + jpc.source = self.element; + jpc.sourceId = self.elementId; + } else { + jpc.target = self.element; + jpc.targetId = self.elementId; + } + + // now check beforeDrop. this will be available only on Endpoints that are setup to + // have a beforeDrop condition (although, secretly, under the hood all Endpoints and + // the Connection have them, because they are on jsPlumbUIComponent. shhh!), because + // it only makes sense to have it on a target endpoint. + _doContinue = _doContinue && self.isDropAllowed(jpc.sourceId, jpc.targetId, jpc.scope, jpc, self); + + if (_doContinue) { + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + + // copy our parameters in to the connection: + var params = self.getParameters(); + for (var aParam in params) + jpc.setParameter(aParam, params[aParam]); + + if (!jpc.suspendedEndpoint) { + //_initDraggableIfNecessary(self.element, params.draggable, {}); + if (params.draggable) + jsPlumb.CurrentLibrary.initDraggable(self.element, dragOptions, true); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + fireDetachEvent({ + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }, true, originalEvent); + } + + // finalise will inform the anchor manager and also add to + // connectionsByScope if necessary. + _finaliseConnection(jpc, null, originalEvent); + } + else { + // otherwise just put it back on the endpoint it was on before the drag. + if (jpc.suspendedEndpoint) { + // self.detachFrom(jpc); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.setHover(false); + jpc._forceDetach = true; + if (idx == 0) { + jpc.source = jpc.suspendedEndpoint.element; + jpc.sourceId = jpc.suspendedEndpoint.elementId; + } else { + jpc.target = jpc.suspendedEndpoint.element; + jpc.targetId = jpc.suspendedEndpoint.elementId;; + } + jpc.suspendedEndpoint.addConnection(jpc); + + jpc.endpoints[0].repaint(); + jpc.repaint(); + _currentInstance.repaint(jpc.source.elementId); + jpc._forceDetach = false; + } + } + + jpc.floatingAnchorIndex = null; + } + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + } + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], function() { + if (self.isTarget) { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + } + } + }); + dropOptions[outEvent] = _wrap(dropOptions[outEvent], function() { + if (self.isTarget) { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + } + } + }); + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions, true, isTransient); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas), true, !(params._transient || self.anchor.isFloating), self); + + return self; + }; + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + j.init(); + return j; + }; + + var _curryAnchor = function(x, y, ox, oy, type, fnInit) { + return function(params) { + params = params || {}; + //var a = jsPlumb.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance); + var a = params.jsPlumbInstance.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance); + a.type = type; + if (fnInit) fnInit(a, params); + return a; + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1, "TopCenter"); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1, "BottomCenter"); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0, "LeftMiddle"); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0, "RightMiddle"); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0, "Center"); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1, "TopRight"); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1, "BottomRight"); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1, "TopLeft"); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1, "BottomLeft"); + + // TODO test that this does not break with the current instance idea + jsPlumb.Defaults.DynamicAnchors = function(params) { + return params.jsPlumbInstance.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"], params.elementId, params.jsPlumbInstance); + }; + jsPlumb.Anchors["AutoDefault"] = function(params) { + var a = params.jsPlumbInstance.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors(params)); + a.type = "AutoDefault"; + return a; + }; + + jsPlumb.Anchors["Assign"] = _curryAnchor(0,0,0,0,"Assign", function(anchor, params) { + // find what to use as the "position finder". the user may have supplied a String which represents + // the id of a position finder in jsPlumb.AnchorPositionFinders, or the user may have supplied the + // position finder as a function. we find out what to use and then set it on the anchor. + var pf = params.position || "Fixed"; + anchor.positionFinder = pf.constructor == String ? params.jsPlumbInstance.AnchorPositionFinders[pf] : pf; + // always set the constructor params; the position finder might need them later (the Grid one does, + // for example) + anchor.constructorParams = params; + }); + + // Continuous anchor is just curried through to the 'get' method of the continuous anchor + // factory. + jsPlumb.Anchors["Continuous"] = function(params) { + return params.jsPlumbInstance.continuousAnchorFactory.get(params); + }; + + // these are the default anchor positions finders, which are used by the makeTarget function. supply + // a position finder argument to that function allows you to specify where the resulting anchor will + // be located + jsPlumb.AnchorPositionFinders = { + "Fixed": function(dp, ep, es, params) { + return [ (dp.left - ep.left) / es[0], (dp.top - ep.top) / es[1] ]; + }, + "Grid":function(dp, ep, es, params) { + var dx = dp.left - ep.left, dy = dp.top - ep.top, + gx = es[0] / (params.grid[0]), gy = es[1] / (params.grid[1]), + mx = Math.floor(dx / gx), my = Math.floor(dy / gy); + return [ ((mx * gx) + (gx / 2)) / es[0], ((my * gy) + (gy / 2)) / es[1] ]; + } + }; +})(); diff --git a/archive/1.3.11/jsPlumb-1.3.11-tests.js b/archive/1.3.11/jsPlumb-1.3.11-tests.js new file mode 100644 index 000000000..3ee2a7abd --- /dev/null +++ b/archive/1.3.11/jsPlumb-1.3.11-tests.js @@ -0,0 +1,4052 @@ +// _jsPlumb qunit tests. + +QUnit.config.reorder = false; + +var _getContextNode = function() { + return $("#container"); +}; + +var assertContextExists = function() { + ok(_getContextNode().length == 1, "context node exists"); +}; + +var assertContextSize = function(elementCount) { + //equals(_getContextNode().children().length - _divs.length, elementCount, 'context has ' + elementCount + ' children'); +}; + +var assertContextEmpty = function() { + equals(_getContextNode().children.length, 0, "context empty"); +}; + +var assertEndpointCount = function(elId, count, _jsPlumb) { + equals(_jsPlumb.getTestHarness().endpointCount(elId), count, elId + " has " + count + (count > 1) ? "endpoints" : "endpoint"); + equals(_jsPlumb.anchorManager.getEndpointsFor(elId).length, count, "anchor manager has " + count + (count > 1) ? "endpoints" : "endpoint"); +}; + +var assertConnectionCount = function(endpoint, count) { + equals(endpoint.connections.length, count, "endpoint has " + count + " connections"); +}; + +var assertConnectionByScopeCount = function(scope, count, _jsPlumb) { + equals(_jsPlumb.getTestHarness().connectionCount(scope), count, 'Scope ' + scope + " has " + count + (count > 1) ? "connections" : "connection"); + +}; + +var _divs = []; +var _addDiv = function(id, parent) { + var d1 = document.createElement("div"); + if (parent) parent.append(d1); else _getContextNode().append(d1); + d1 = jsPlumb.CurrentLibrary.getElementObject(d1); + jsPlumb.CurrentLibrary.setAttribute(d1, "id", id); + _divs.push(id); + return d1; +}; + +var defaults = null, + _cleanup = function(_jsPlumb) { + + _jsPlumb.reset(); + _jsPlumb.Defaults = defaults; + + for (var i in _divs) { + $("#" + _divs[i]).remove(); + } + _divs.splice(0, _divs.length - 1); + + $("#container").empty(); +}; + +var testSuite = function(renderMode, _jsPlumb) { + + + module("jsPlumb", { + teardown: function() { + _cleanup(_jsPlumb); + }, + setup:function() { + defaults = jsPlumb.extend({}, _jsPlumb.Defaults); + } + }); + + // setup the container + var container = document.createElement("div"); + container.id = "container"; + document.body.appendChild(container); + + var jpcl = jsPlumb.CurrentLibrary; + + _jsPlumb.setRenderMode(renderMode); + + test(renderMode + " : getElementObject", function() { + var e = document.createElement("div"); + e.id = "FOO"; + document.body.appendChild(e); + var el = jpcl.getElementObject(e); + equals(jpcl.getAttribute(el, "id"), "FOO"); + }); + + test(renderMode + " : getDOMElement", function() { + var e = document.createElement("div"); + e.id = "FOO"; + document.body.appendChild(e); + var el = jpcl.getElementObject(e); + var e2 = jpcl.getDOMElement(el); + equals(e2.id, "FOO"); + }); + + test(renderMode + ': _jsPlumb setup', function() { + ok(_jsPlumb, "loaded"); + }); + + test(renderMode + ': getId', function() { + var d10 = _addDiv('d10'); + equals(_jsPlumb.getTestHarness().getId(d10), "d10"); + }); + + test(renderMode + ': create a simple endpoint', function() { + var d1 = _addDiv("d1"); + var e = _jsPlumb.addEndpoint($("#d1"), {}); + ok(e, 'endpoint exists'); + assertEndpointCount("d1", 1, _jsPlumb); + ok(e.id != null, "endpoint has had an id assigned"); + }); + + test(renderMode + ': create and remove a simple endpoint', function() { + var d1 = _addDiv("d1"); + var ee = _jsPlumb.addEndpoint(d1, {uuid:"78978597593"}); + ok(ee != null, "endpoint exists"); + var e = _jsPlumb.getEndpoint("78978597593"); + ok(e != null, "the endpoint could be retrieved by UUID"); + ok(e.id != null, "the endpoint has had an id assigned to it"); + assertEndpointCount("d1", 1, _jsPlumb); + assertContextSize(1); // one Endpoint canvas. + _jsPlumb.removeEndpoint(d1, ee); + assertEndpointCount("d1", 0, _jsPlumb); + e = _jsPlumb.getEndpoint("78978597593"); + equals(e, null, "the endpoint has been deleted"); + assertContextSize(0); // no Endpoint canvases. + }); + + test(renderMode + ': create two simple endpoints, registered using a selector', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + d1.addClass("window");d2.addClass("window"); + var endpoints = _jsPlumb.addEndpoint($(".window"), {}); + equals(endpoints.length, 2, "endpoint added to both windows"); + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + }); + + test(renderMode + ': create two simple endpoints, registered using an array of element ids', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + d1.addClass("window");d2.addClass("window"); + var endpoints = _jsPlumb.addEndpoint(["d1", "d2"], {}); + equals(endpoints.length, 2, "endpoint added to both windows"); + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + }); + + test(renderMode + ': draggable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1"); + var e = _jsPlumb.addEndpoint(d1, {isSource:true}); + ok(e, 'endpoint exists'); + }); + + test(renderMode + ': droppable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1") + var e = _jsPlumb.addEndpoint(d1, {isTarget:true}); + ok(e, 'endpoint exists'); + }); + + test(renderMode + ': defaultEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = _jsPlumb.addEndpoint(d3, {isSource:true}); + ok(e3.anchor, 'endpoint 3 has an anchor'); + var e4 = _jsPlumb.addEndpoint(d4, {isSource:true}); + assertEndpointCount("d3", 1, _jsPlumb); + assertEndpointCount("d4", 1, _jsPlumb); + assertContextSize(2); // one canvas for each of our two endpoints. + ok(!e3.isFull(), "endpoint 3 is not full."); + _jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // we have one connection + _jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // should have refused the connection; default max is 1. + assertContextSize(3); // now we have one more canvas, for the Connection that was accepted. + }); + + test(renderMode + ': specifiedEndpointMaxConnections', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = _jsPlumb.addEndpoint(d5, {isSource:true, maxConnections:3}); + ok(e5.anchor, 'endpoint 5 has an anchor'); + var e6 = _jsPlumb.addEndpoint(d6, {isSource:true, maxConnections:2}); // this one has max TWO + assertEndpointCount("d5", 1, _jsPlumb); assertEndpointCount("d6", 1, _jsPlumb); + assertContextSize(2); + ok(!e5.isFull(), "endpoint 5 is not full."); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 1); // we have one connection + assertContextSize(3); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // two connections + assertContextSize(4); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // should have refused; max is 2, for d4. + assertContextSize(4); + }); + + test(renderMode + ': noEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = _jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + var e4 = _jsPlumb.addEndpoint(d4, {isSource:true, maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertContextSize(3); + assertConnectionCount(e3, 1); // we have one connection + _jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 2); // we have two. etc (default was one. this proves max is working). + assertContextSize(4); + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = _jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e4}); + assertConnectionCount(e4, 3); + assertContextSize(6); + }); + + test(renderMode + ': anchors equal', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + ok(a1.equals(a2), "anchors are the same"); + }); + + test(renderMode + ': anchors equal with offsets', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb); + ok(a1.equals(a2), "anchors are the same"); + }); + + test(renderMode + ': anchors not equal', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 0, 1], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + ok(!a1.equals(a2), "anchors are different"); + }); + + test(renderMode + ': anchor not equal with offsets', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + ok(!a1.equals(a2), "anchors are different"); + }); + + test(renderMode + ': detach does not fail when no arguments are provided', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + _jsPlumb.connect({source:d3, target:d4}); + _jsPlumb.detach(); + }); + + // test that detach does not fire an event by default + test(renderMode + ': _jsPlumb.detach should fire detach event by default', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach(conn); + equals(eventCount, 1); + }); + + // test that detach does not fire an event by default + test(renderMode + ': _jsPlumb.detach should fire detach event by default, using params object', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({connection:conn}); + equals(eventCount, 1); + }); + + // test that detach fires an event when instructed to do so + test(renderMode + ': _jsPlumb.detach should not fire detach event when instructed to not do so', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach(conn, {fireEvent:false}); + equals(eventCount, 0); + }); + + // issue 81 + test(renderMode + ': _jsPlumb.detach should fire only one detach event (pass Connection as argument)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach(conn); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': _jsPlumb.detach should fire only one detach event (pass Connection as param in argument)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({connection:conn}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': detach should fire only one detach event (pass source and targets as strings as arguments in params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({source:"d5", target:"d6"}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': detach should fire only one detach event (pass source and targets as divs as arguments in params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({source:d5, target:d6, fireEvent:true}); + equals(eventCount, 1); + }); + + //TODO make sure you run this test with a single detach call, to ensure that + // single detach calls result in the connection being removed. detachEveryConnection can + // just blow away the connectionsByScope array and recreate it. + test(renderMode + ': getConnections (simple case, default scope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + _jsPlumb.connect({source:d5, target:d6}); + assertContextSize(3); + var c = _jsPlumb.getConnections(); // will get all connections in the default scope. + equals(c.length, 1, "there is one connection"); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by element id using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + _jsPlumb.connect({source:d5, target:d6}); + _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach({source:'d5', target:'d6'}); + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by id using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + _jsPlumb.connect({source:d5, target:d6}); + _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach({source:"d5", target:"d6"}); + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by element object using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + _jsPlumb.connect({source:d5, target:d6}); + _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach({source:d5, target:d6}); + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by Connection)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + var c56 = _jsPlumb.connect({source:d5, target:d6}); + var c67 = _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach(c56); + assertContextSize(5); // check that the connection canvas was removed. + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + }); + +// beforeDetach functionality + + test(renderMode + ": detach; beforeDetach on connect call returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { return false; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": detach; beforeDetach on connect call returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { return true; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": detach; beforeDetach on connect call throws an exception; we treat it with the contempt it deserves and pretend it said the detach was ok.", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { throw "i am an example of badly coded beforeDetach, but i don't break jsPlumb "; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": detach; beforeDetach on addEndpoint call to source Endpoint returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return false; } }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": detach; beforeDetach on addEndpoint call to source Endpoint returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return true; } }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + + test(renderMode + ": Endpoint.detach; beforeDetach on addEndpoint call to source Endpoint returns false; Endpoint.detach returns false too (the UI needs it to)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return false; } }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + var success = e1.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + ok(!success, "Endpoint reported detach did not execute"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns false but detach is forced", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c, {forceDetach:true}); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was denied but forced anyway"); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was denied but forced anyway"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return true; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach bound to _jsPlumb returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + _jsPlumb.bind("beforeDetach", function(connection) { + ok(connection.sourceId === "d1", "connection is provided and configured with correct source"); + ok(connection.targetId === "d2", "connection is provided and configured with correct target"); + return false; + }); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach bound to _jsPlumb returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + _jsPlumb.bind("beforeDetach", function(connection) { + ok(connection.sourceId === "d1", "connection is provided and configured with correct source"); + ok(connection.targetId === "d2", "connection is provided and configured with correct target"); + return true; + }); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was denied"); + }); + + test(renderMode + ": _jsPlumb.detachAllConnections ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detachAllConnections(d1); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachAll was called"); + }); + + test(renderMode + ": _jsPlumb.detachEveryConnection ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detachEveryConnection(); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachEveryConnection was called"); + }); + + test(renderMode + ": Endpoint.detachAll ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + e1.detachAll(); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachAllConnections was called"); + }); + +// ******** end of beforeDetach tests ************** + +// detachEveryConnection/detachAllConnections fireEvent overrides tests + + test(renderMode + ": _jsPlumb.detachEveryConnection fires events", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0; + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachEveryConnection(); + equals(connCount, 0, "no connections registered"); + }); + + test(renderMode + ": _jsPlumb.detachEveryConnection doesn't fire events when instructed not to", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0; + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachEveryConnection({fireEvent:false}); + equals(connCount, 2, "two connections registered"); + }); + + test(renderMode + ": _jsPlumb.detachAllConnections fires events", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0, + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachAllConnections("d1"); + equals(connCount, 0, "no connections registered"); + }); + + test(renderMode + ": _jsPlumb.detachAllConnections doesn't fire events when instructed not to", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0, + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachAllConnections("d1", {fireEvent:false}); + equals(connCount, 2, "two connections registered"); + }); + + test(renderMode + ': getConnections (scope testScope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + _jsPlumb.connect({source:d5, target:d6, scope:'testScope'}); + var c = _jsPlumb.getConnections("testScope"); // will get all connections in testScope + equals(c.length, 1, "there is one connection"); + equals(c[0].sourceId, 'd5', "the connection's source is d5"); + equals(c[0].targetId, 'd6', "the connection's source is d6"); + c = _jsPlumb.getConnections(); // will get all connections in default scope; should be none. + equals(c.length, 0, "there are no connections in the default scope"); + }); + + test(renderMode + ': _jsPlumb.getAllConnections (filtered by scope)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + _jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + _jsPlumb.connect({source:d9, target:d10}); // default scope + var c = _jsPlumb.getAllConnections(); // will get all connections + equals(c[_jsPlumb.getDefaultScope()].length, 1); + equals(c['testScope'].length, 1); + // now supply a list of scopes + c = _jsPlumb.getConnections(); + equals(c.length, 1); + c = _jsPlumb.getConnections("testScope"); + equals(c.length, 1, "there is one connection in 'testScope'"); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by scope and sourceId)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + _jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + _jsPlumb.connect({source:d9, target:d8, scope:'testScope'}); + _jsPlumb.connect({source:d9, target:d10}); // default scope + var c = _jsPlumb.getConnections({scope:'testScope', source:'d8'}); // will get all connections with sourceId 'd8' + equals(c.length, 1, "there is one connection in 'testScope' from d8"); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by scope, source id and target id)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope'}); + var c = _jsPlumb.getConnections({scope:'testScope', source:'d11', target:'d13'}); + equals(c.length, 1, "there is one connection from d11 to d13"); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = _jsPlumb.getConnections({scope:['testScope','testScope3']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes and source ids)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = _jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes, source ids and target ids)', function() { + _jsPlumb.detachEveryConnection(); + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'), d14=_addDiv("d14"), d15=_addDiv("d15"); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d11, target:d14, scope:'testScope'}); + _jsPlumb.connect({source:d11, target:d15, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + assertContextSize(18); + var c = _jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11'], target:['d14', 'd15']}); + equals(c['testScope'].length, 2, 'there are two connections in testScope'); + equals(c['testScope3'], null, 'there are no connections in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + var anEntry = c['testScope'][0]; + ok(anEntry.endpoints[0] != null, "Source endpoint is set"); + ok(anEntry.endpoints[1] != null, "Target endpoint is set"); + equals(anEntry.source.attr("id"), "d11", "Source is div d11"); + equals(anEntry.target.attr("id"), "d14", "Target is div d14"); + }); + + test(renderMode + ': getEndpoints, one Endpoint added by addEndpoint, get Endpoints by selector', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + _jsPlumb.addEndpoint(d1); + var e = _jsPlumb.getEndpoints(d1); + equals(e.length, 1, "there is one endpoint for element d1"); + }); + + test(renderMode + ': getEndpoints, one Endpoint added by addEndpoint, get Endpoints by id', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + _jsPlumb.addEndpoint(d1); + var e = _jsPlumb.getEndpoints("d1"); + equals(e.length, 1, "there is one endpoint for element d1"); + }); + + test(renderMode + ': connection event listeners', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null/*, returnedParams2 = null*/; + _jsPlumb.bind("jsPlumbConnection", function(params) { + returnedParams = $.extend({}, params); + }); + var c = _jsPlumb.connect({source:d1, target:d2}); + ok(returnedParams != null, "new connection listener event was fired"); + ok(returnedParams.connection != null, 'connection is set'); + equals(returnedParams.sourceId, "d1", 'sourceid is set'); + equals(returnedParams.targetId, "d2", 'targetid is set'); + equals(returnedParams.source.attr("id"), "d1", 'source is set'); + equals(returnedParams.target.attr("id"), "d2" , 'target is set'); + ok(returnedParams.sourceEndpoint != null, "source endpoint is not null"); + ok(returnedParams.targetEndpoint != null, "target endpoint is not null"); + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.detach(c); + ok(returnedParams.connection != null, 'connection is set'); + }); + + test(renderMode + ': detach event listeners (detach by connection)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + ok(returnedParams.connection != null, "removed connection listener event passed in connection"); + }); + + test(renderMode + ': detach event listeners (detach by element ids)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach({source:"d1",target:"d2"}); + ok(returnedParams != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (detach by elements)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach({source:d1,target:d2}); + ok(returnedParams != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detach method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detachFrom method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachFrom(e2); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detachAll method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachAll(); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via _jsPlumb.deleteEndpoint method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + _jsPlumb.deleteEndpoint(e1); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(1); + }); + + test(renderMode + ': detach event listeners (ensure cleared by _jsPlumb.reset)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach({source:d1,target:d2, fireEvent:true}); + ok(returnedParams != null, "removed connection listener event was fired"); + returnedParams = null; + + _jsPlumb.reset(); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach(d1,d2); + ok(returnedParams == null, "connection listener was cleared by _jsPlumb.reset()"); + }); + + test(renderMode + ': connection events that throw errors', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null, returnedParams2 = null; + _jsPlumb.bind("jsPlumbConnection", function(params) { + returnedParams = $.extend({}, params); + throw "oh no!"; + }); + _jsPlumb.connect({source:d1, target:d2}); + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + _jsPlumb.connect({source:d3, target:d4}); + ok(returnedParams != null, "new connection listener event was fired; we threw an error, _jsPlumb survived."); + }); + + test(renderMode + ': unbinding connection event listeners, connection', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var count = 0; + _jsPlumb.bind("jsPlumbConnection", function(params) { + count++; + }); + var c = _jsPlumb.connect({source:d1, target:d2}); + ok(count == 1, "received one event"); + _jsPlumb.unbind("jsPlumbConnection"); + var c2 = _jsPlumb.connect({source:d1, target:d2}); + ok(count == 1, "still received only one event"); + + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + count--; + }); + _jsPlumb.detach(c); + ok(count == 0, "count of events is now zero"); + }); + + test(renderMode + ': unbinding connection event listeners, detach', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var count = 0; + _jsPlumb.bind("jsPlumbConnection", function(params) { + count++; + }); + var c = _jsPlumb.connect({source:d1, target:d2}); + ok(count == 1, "received one event"); + var c2 = _jsPlumb.connect({source:d1, target:d2}); + ok(count == 2, "received two events"); + + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + count--; + }); + _jsPlumb.detach(c); + ok(count == 1, "count of events is now one"); + _jsPlumb.unbind("jsPlumbConnectionDetached"); + _jsPlumb.detach(c2); + ok(count == 1, "count of events is still one"); + }); + + test(renderMode + ': unbinding connection event listeners, all listeners', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var count = 0; + _jsPlumb.bind("jsPlumbConnection", function(params) { + count++; + }); + var c = _jsPlumb.connect({source:d1, target:d2}), + c2 = _jsPlumb.connect({source:d1, target:d2}), + c3 = _jsPlumb.connect({source:d1, target:d2}); + + ok(count == 3, "received three events"); + + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + count--; + }); + _jsPlumb.detach(c); + ok(count == 2, "count of events is now two"); + + _jsPlumb.unbind(); // unbind everything + + _jsPlumb.detach(c2); + _jsPlumb.detach(c3); + _jsPlumb.connect({source:d1, target:d2}) + _jsPlumb.connect({source:d1, target:d2}) + _jsPlumb.connect({source:d1, target:d2}) + _jsPlumb.connect({source:d1, target:d2}) + + ok(count == 2, "count of events is still two"); + }); + + test(renderMode + ": Endpoint.detachFrom", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + e16.detachFrom(e17); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by _jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": Endpoint.detachFromConnection", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + e16.detachFromConnection(conn); + assertContextSize(3); // all canvases should remain; the connection was not removed. + // but endpoint e16 should have no connections now. + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": Endpoint.detachAll", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + var e16 = _jsPlumb.addEndpoint($("#d16"), {isSource:true,maxConnections:-1}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint($("#d17"), {isSource:true}); + var e18 = _jsPlumb.addEndpoint($("#d18"), {isSource:true}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e18}); + assertConnectionCount(e16, 2); + assertConnectionCount(e17, 1); + assertConnectionCount(e18, 1); + assertContextSize(5); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 2, _jsPlumb); + e16.detachAll(); + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": Endpoint.detach", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + e16.detach(conn); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by _jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": Endpoint.isConnectedTo", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.isConnectedTo(e17), true, "e16 and e17 are connected"); + }); + + asyncTest(renderMode + "jsPlumbUtil.setImage on Endpoint, with supplied onload", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { + endpoint:[ "Image", { + src:"../img/endpointTest1.png", + onload:function(imgEp) { + _jsPlumb.repaint("d1"); + ok(imgEp.img.src.indexOf("endpointTest1.png") != -1, "image source is correct"); + ok(imgEp.img.src.indexOf("endpointTest1.png") != -1, "image elementsource is correct"); + + imgEp.canvas.setAttribute("id", "iwilllookforthis"); + + ok(document.getElementById("iwilllookforthis") != null, "image element is present"); + _jsPlumb.removeAllEndpoints("d1"); + ok(document.getElementById("iwilllookforthis") == null, "image element was removed after remove endpoint"); + } + } ] + }; + start(); + _jsPlumb.addEndpoint(d1, e); + }); + + test(renderMode + ": setting endpoint uuid", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + equals(e16.getUuid(), uuid, "endpoint's uuid was set correctly"); + }); + + test(renderMode + ": _jsPlumb.getEndpoint (by uuid)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by uuid, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + _jsPlumb.deleteEndpoint(uuid); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + var ebe = _jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by uuid, connections too)", function() { + // create two endpoints (one with a uuid), add them to two divs and then connect them. + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + // check that the connection was ok. + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + // delete the endpoint that has a uuid. verify that the endpoint cannot be retrieved and that the connection has been removed, but that + // element d17 still has its Endpoint. + _jsPlumb.deleteEndpoint(uuid); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + var ebe = _jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + ebe = _jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 1, "element d17 still has its Endpoint"); + assertContextSize(1); + + // now delete d17's endpoint and check that it has gone. + _jsPlumb.deleteEndpoint(e17); + f = _jsPlumb.getEndpoint(e17); + equals(f, null, "endpoint has been deleted"); + ebe = _jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 0, "element d17 no longer has any Endpoints"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by reference, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + _jsPlumb.deleteEndpoint(e16); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by reference, connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + _jsPlumb.deleteEndpoint(e16); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + assertContextSize(1); + }); + + test(renderMode + ": _jsPlumb.deleteEveryEndpoint", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + assertContextSize(2); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + _jsPlumb.deleteEveryEndpoint(); + + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = _jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEveryEndpoint (connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + _jsPlumb.deleteEveryEndpoint(); + + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = _jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); // no canvases. so all connection canvases have been cleaned up too. + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchor:[0,0.5,0,-1]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:"TopCenter"}); + assertContextSize(2); + equals(e16.anchor.x, 0); + equals(e16.anchor.y, 0.5); + equals(e17.anchor.x, 0.5); + equals(e17.anchor.y, 0); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (tooltip)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {tooltip:"FOO"}), + e17 = _jsPlumb.addEndpoint(d17, {tooltip:"BAZ"}); + assertContextSize(2); + equals(e16.canvas.getAttribute("title"), "FOO"); + equals(e17.canvas.getAttribute("title"), "BAZ"); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case, dynamic anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + assertContextSize(2); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case, two arg method)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchor:[0,0.5,0,-1]}); + var e17 = _jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchor:"TopCenter"}); + assertContextSize(2); + equals(e16.anchor.x, 0); + equals(e16.anchor.y, 0.5); + equals(false, e16.isTarget); + equals(true, e16.isSource); + equals(e17.anchor.x, 0.5); + equals(e17.anchor.y, 0); + equals(true, e17.isTarget); + equals(false, e17.isSource); + }); + + + test(renderMode + ": _jsPlumb.addEndpoints (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false, anchor:[0,0.5,0,-1] }, { isTarget:true, isSource:false, anchor:"TopCenter" }]); + assertContextSize(2); + equals(e16[0].anchor.x, 0); + equals(e16[0].anchor.y, 0.5); + equals(false, e16[0].isTarget); + equals(true, e16[0].isSource); + equals(e16[1].anchor.x, 0.5); + equals(e16[1].anchor.y, 0); + equals(true, e16[1].isTarget); + equals(false, e16[1].isSource); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (with reference params)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var refParams = {anchor:"RightMiddle"}; + var e16 = _jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false}, { isTarget:true, isSource:false }], refParams); + assertContextSize(2); + equals(e16[0].anchor.x, 1); + equals(e16[0].anchor.y, 0.5); + equals(false, e16[0].isTarget); + equals(true, e16[0].isSource); + equals(e16[1].anchor.x, 1); + equals(e16[1].anchor.y, 0.5); + equals(true, e16[1].isTarget); + equals(false, e16[1].isSource); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case, dynamic anchors, two arg method)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchors:["TopCenter", "BottomCenter"]}); + assertContextSize(2); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (default overlays)", function() { + _jsPlumb.Defaults.Overlays = [ + [ "Label", { id:"label" } ] + ]; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16), + e2 = _jsPlumb.addEndpoint(d17); + + ok(e1.getOverlay("label") != null, "endpoint 1 has overlay from defaults"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (default overlays)", function() { + _jsPlumb.Defaults.Overlays = [ + [ "Label", { id:"label" } ] + ]; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16, { + overlays:[ + ["Label", { id:"label2", location:[ 0.5, 1 ] } ] + ] + }), + e2 = _jsPlumb.addEndpoint(d17); + + ok(e1.getOverlay("label") != null, "endpoint 1 has overlay from defaults"); + ok(e1.getOverlay("label2") != null, "endpoint 1 has overlay from addEndpoint call"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (end point set label)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16), + e2 = _jsPlumb.addEndpoint(d17); + + e1.setLabel("FOO"); + equals(e1.getLabel(), "FOO", "endpoint's label is correct"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (end point set label in constructor, as string)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16, {label:"FOO"}), + e2 = _jsPlumb.addEndpoint(d17); + + equals(e1.getLabel(), "FOO", "endpoint's label is correct"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (end point set label in constructor, as function)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16, {label:function() { return "BAZ"; }, labelLocation:0.1}), + e2 = _jsPlumb.addEndpoint(d17); + + equals(e1.getLabel()(), "BAZ", "endpoint's label is correct"); + equals(e1.getLabelOverlay().getLocation(), 0.1, "endpoint's label's location is correct"); + }); + +// ****************** makeTarget (and associated methods) tests ******************************************** + + test(renderMode + ": _jsPlumb.makeTarget (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.makeTarget (specify two divs in an array)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.makeTarget([d16, d17], { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.makeTarget (specify two divs by id in an array)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.makeTarget(["d16", "d17"], { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.makeTarget (specify divs by selector)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + d16.addClass("FOO");d17.addClass("FOO"); + _jsPlumb.makeTarget($(".FOO"), { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e.length, 1, "d17 has one endpoint"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case, two connect calls)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17"}); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 2, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case, two connect calls, uniqueEndpoint set)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true, anchor:"LeftMiddle", uniqueEndpoint:true, maxConnections:-1 }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17"}); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].connections.length, 2, "endpoint on d17 has two connections"); + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (newConnection:true specified)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17", newConnection:true}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0.5, "anchor is BottomCenter"); //here we should be seeing the default anchor + equals(e[0].anchor.y, 1, "anchor is BottomCenter"); //here we should be seeing the default anchor + }); + +// jsPlumb.connect, after makeSource has been called on some element + test(renderMode + ": _jsPlumb.connect after makeSource (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeSource (simple case, two connect calls)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 2, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case, two connect calls, uniqueEndpoint set)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true, anchor:"LeftMiddle", uniqueEndpoint:true, maxConnections:-1 }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].connections.length, 2, "endpoint on d17 has two connections"); + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (newConnection:true specified)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16, newConnection:true}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0.5, "anchor is BottomCenter"); //here we should be seeing the default anchor + equals(e[0].anchor.y, 1, "anchor is BottomCenter"); //here we should be seeing the default anchor + }); + + test(renderMode + ": _jsPlumb.connect after makeSource on child; wth parent set (parent should be recognised)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18", d17); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d18, { isSource:true,anchor:"LeftMiddle", parent:d17 }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + assertEndpointCount("d18", 0, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + // makeSource, then disable it. should not be able to make a connection from it. + test(renderMode + ": _jsPlumb.connect after makeSource and setSourceEnabled(false) (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.setSourceEnabled(d17, false); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + }); + + // makeSource, then disable it. should not be able to make a connection from it. + test(renderMode + ": _jsPlumb.connect after makeSource and setSourceEnabled(false) (selector as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.setSourceEnabled($("div"), false); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + }); + + // makeSource, then toggle its enabled state. should not be able to make a connection from it. + test(renderMode + ": _jsPlumb.connect after makeSource and toggleSourceEnabled() (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.toggleSourceEnabled(d17); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + + _jsPlumb.toggleSourceEnabled(d17); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + // makeSource, then disable it. should not be able to make a connection from it. + test(renderMode + ": _jsPlumb.connect after makeSource and toggleSourceEnabled() (selector as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.toggleSourceEnabled($("div")); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + _jsPlumb.toggleSourceEnabled($("div")); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + test(renderMode + ": jsPlumb.isSource and jsPlumb.isSourceEnabled", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + ok(_jsPlumb.isSource(d17) == true, "d17 is recognised as connection source"); + ok(_jsPlumb.isSourceEnabled(d17) == true, "d17 is recognised as enabled"); + _jsPlumb.setSourceEnabled(d17, false); + ok(_jsPlumb.isSourceEnabled(d17) == false, "d17 is recognised as disabled"); + }); + + // makeSource, then disable it. should not be able to make a connection to it. + test(renderMode + ": _jsPlumb.connect after makeTarget and setTargetEnabled(false) (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.setTargetEnabled(d17, false); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + }); + + // makeTarget, then disable it. should not be able to make a connection to it. + test(renderMode + ": _jsPlumb.connect after makeTarget and setTargetEnabled(false) (selector as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.setTargetEnabled($("div"), false); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + }); + + // makeTarget, then toggle its enabled state. should not be able to make a connection to it. + test(renderMode + ": _jsPlumb.connect after makeTarget and toggleTargetEnabled() (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.toggleTargetEnabled(d17); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + + _jsPlumb.toggleTargetEnabled(d17); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + // makeTarget, then disable it. should not be able to make a connection to it. + test(renderMode + ": _jsPlumb.connect after makeTarget and toggleTargetEnabled() (selector as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.toggleTargetEnabled($("div")); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + _jsPlumb.toggleTargetEnabled($("div")); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + test(renderMode + ": jsPlumb.isTarget and jsPlumb.isTargetEnabled", function() { + var d17 = _addDiv("d17"); + _jsPlumb.makeTarget(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + ok(_jsPlumb.isTarget(d17) == true, "d17 is recognised as connection target"); + ok(_jsPlumb.isTargetEnabled(d17) == true, "d17 is recognised as enabled"); + _jsPlumb.setTargetEnabled(d17, false); + ok(_jsPlumb.isTargetEnabled(d17) == false, "d17 is recognised as disabled"); + }); + + // makeSource, then unmake it. should not be able to make a connection from it. then connect to it, which should succeed, + // because jsPlumb will just add a new endpoint. + test(renderMode + ": jsPlumb.unmakeSource (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + ok(_jsPlumb.isSource(d17) == true, "d17 is currently a source"); + // unmake source + _jsPlumb.unmakeSource(d17); + ok(_jsPlumb.isSource(d17) == false, "d17 is no longer a source"); + + // this should succeed, because d17 is no longer a source and so jsPlumb will just create and add a new endpoint to d17. + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + // maketarget, then unmake it. should not be able to make a connection to it. + test(renderMode + ": jsPlumb.unmakeTarget (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + ok(_jsPlumb.isTarget(d17) == true, "d17 is currently a target"); + // unmake target + _jsPlumb.unmakeTarget(d17); + ok(_jsPlumb.isTarget(d17) == false, "d17 is no longer a target"); + + // this should succeed, because d17 is no longer a target and so jsPlumb will just create and add a new endpoint to d17. + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + + test(renderMode + ": jsPlumb.removeEverySource and removeEveryTarget (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + _jsPlumb.makeSource(d16).makeTarget(d17).makeSource(d18); + ok(_jsPlumb.isSource(d16) == true, "d16 is a source"); + ok(_jsPlumb.isTarget(d17) == true, "d17 is a target"); + ok(_jsPlumb.isSource(d18) == true, "d18 is a source"); + + _jsPlumb.unmakeEverySource(); + _jsPlumb.unmakeEveryTarget(); + + ok(_jsPlumb.isSource(d16) == false, "d16 is no longer a source"); + ok(_jsPlumb.isTarget(d17) == false, "d17 is no longer a target"); + ok(_jsPlumb.isSource(d18) == false, "d18 is no longer a source"); + }); + +// *********************** end of makeTarget (and associated methods) tests ************************ + + test(renderMode + ': _jsPlumb.connect (between two Endpoints)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1, _jsPlumb); // no new endpoint should have been added + assertEndpointCount("d2", 1, _jsPlumb); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. + ok(c.id != null, "connection has had an id assigned"); + }); + + test(renderMode + ': _jsPlumb.connect (Endpoint connectorTooltip parameter)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1, {connectorTooltip:"FOO"}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + equals(c.connector.canvas.getAttribute("title"), "FOO", "connector canvas has label attribute set"); + }); + + test(renderMode + ': _jsPlumb.connect (tooltip parameter)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2, tooltip:"FOO"}); + equals(c.connector.canvas.getAttribute("title"), "FOO", "connector canvas has label attribute set"); + }); + + test(renderMode + ': _jsPlumb.connect (between two Endpoints, and dont supply any parameters to the Endpoints.)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1, _jsPlumb); // no new endpoint should have been added + assertEndpointCount("d2", 1, _jsPlumb); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. + }); + + test(renderMode + " : _jsPlumb.connect, passing 'anchors' array" ,function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, anchors:["LeftMiddle", "RightMiddle"]}); + + equals(c.endpoints[0].anchor.x, 0, "source anchor is at x=0"); + equals(c.endpoints[0].anchor.y, 0.5, "source anchor is at y=0.5"); + equals(c.endpoints[1].anchor.x, 1, "target anchor is at x=1"); + equals(c.endpoints[1].anchor.y, 0.5, "target anchor is at y=0.5"); + }); + + test(renderMode + ': _jsPlumb.connect (by endpoint)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + }); + + test(renderMode + ': _jsPlumb.connect (cost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17, cost:567}); + assertContextSize(3); + equals(c.getCost(), 567, "connection cost is 567"); + }); + + test(renderMode + ': _jsPlumb.connect (default cost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), undefined, "default connection cost is 1"); + }); + + test(renderMode + ': _jsPlumb.connect (set cost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), undefined, "default connection cost is 1"); + c.setCost(8989); + equals(c.getCost(), 8989, "connection cost is 8989"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (connectionCost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionCost:567}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), 567, "connection cost is 567"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (change connectionCost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionCost:567, maxConnections:-1}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1}); + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), 567, "connection cost is 567"); + e16.setConnectionCost(23); + var c2 = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(c2.getCost(), 23, "connection cost is 23 after change on endpoint"); + }); + + test(renderMode + ': _jsPlumb.connect (default bidirectional)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true}), + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.isBidirectional(), true, "default connection is bidirectional"); + }); + + test(renderMode + ': _jsPlumb.connect (bidirectional false)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true}), + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17, bidirectional:false}); + assertContextSize(3); + equals(c.isBidirectional(), false, "connection is not bidirectional"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (connectionsBidirectional)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionsBidirectional:true, maxConnections:-1}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.isBidirectional(), true, "connection is bidrectional"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (change connectionsBidirectional)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionsBidirectional:true, maxConnections:-1}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1}); + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.isBidirectional(), true, "connection is bidirectional"); + e16.setConnectionsBidirectional(false); + var c2 = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(c2.isBidirectional(), false, "connection is not bidirectional"); + }); + + test(renderMode + ": _jsPlumb.connect (two Endpoints - that have been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = _jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1, uuid:srcEndpointUuid}); + var e2 = _jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1, uuid:dstEndpointUuid}); + _jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ] }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertContextSize(3); + }); + + test(renderMode + ": _jsPlumb.connect (two Endpoints - that have not been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ], source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + var e1 = _jsPlumb.getEndpoint(srcEndpointUuid); + ok(e1 != null, "endpoint with src uuid added"); + ok(e1.canvas != null); + var e2 = _jsPlumb.getEndpoint(dstEndpointUuid); + ok(e2 != null, "endpoint with target uuid added"); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.connect (two Endpoints - that have been already added - by endpoint reference)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = _jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1}); + var e2 = _jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1}); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.connect (two elements)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.connect({ source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, straight)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, bezier, no params)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Bezier" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 150, "Bezier connector chose 150 curviness"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, bezier, curviness as int)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", { curviness:200 }] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Canvas Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 200, "Bezier connector chose 200 curviness"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, bezier, curviness as named option)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", {curviness:300}] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 300, "Bezier connector chose 300 curviness"); + }); + + test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Canvas Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as strings)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:["LeftMiddle", "RightMiddle"] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.5, conn.endpoints[0].anchor.y, "source anchor y"); + equals(1, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.5, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + + test(renderMode + ": _jsPlumb.connect (two argument method in which some data is reused across connections)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"), d19 = _addDiv("d19"); + var sharedData = { connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }; + var conn = _jsPlumb.connect({ source:d16, target:d17}, sharedData); + var conn2 = _jsPlumb.connect({ source:d18, target:d19}, sharedData); + assertContextSize(6); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 2, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(conn2.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + equals(0.3, conn2.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn2.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn2.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn2.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoint test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoints test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Dot, "Dot endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Blank Endpoint specified via 'endpoint' param)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Blank" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Blank Endpoint specified via 'endpoints' param)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Blank", "Blank" ] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Dot, "Dot endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, connector test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {}); + var e17 = _jsPlumb.addEndpoint(d17, {}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {}); + var e17 = _jsPlumb.addEndpoint(d17, {}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, anchors as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = "TopCenter", a17 = "BottomCenter"; + var e16 = _jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, 0.5, "endpoint 16 is at top center");equals(e16.anchor.y, 0, "endpoint 16 is at top center"); + equals(e17.anchor.x, 0.5, "endpoint 17 is at bottom center");equals(e17.anchor.y, 1, "endpoint 17 is at bottom center"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = [0,0.5,0,-1], a17 = [1,0.0,-1,-1]; + var e16 = _jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, a16[0]);equals(e16.anchor.y, a16[1]); + equals(e17.anchor.x, a17[0]);equals(e17.anchor.y, a17[1]); + equals(e16.anchor.getOrientation()[0], a16[2]); equals(e16.anchor.getOrientation()[1], a16[3]); + equals(e17.anchor.getOrientation()[0], a17[2]); equals(e17.anchor.getOrientation()[1], a17[3]); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchor')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchor:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:["TopCenter", "BottomCenter"]}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchors')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, default endpoint, supplied dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var anchors = [ [0.25, 0, 0, -1], [1, 0.25, 1, 0], [0.75, 1, 0, 1], [0, 0.75, -1, 0] ]; + _jsPlumb.connect({source:d1, target:d2, dynamicAnchors:anchors}); // auto connect with default endpoint and provided anchors + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.detach({source:d1, target:d2}); + // this changed in 1.3.5, because auto generated endpoints are now removed by detach. so i added the test below this one + // to check that the deleteEndpointsOnDetach flag is honoured. + assertEndpointCount("d1", 0, _jsPlumb);assertEndpointCount("d2", 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, default endpoint, supplied dynamic anchors, delete on detach false)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var anchors = [ [0.25, 0, 0, -1], [1, 0.25, 1, 0], [0.75, 1, 0, 1], [0, 0.75, -1, 0] ]; + _jsPlumb.connect({ + source:d1, + target:d2, + dynamicAnchors:anchors, + deleteEndpointsOnDetach:false + }); // auto connect with default endpoint and provided anchors + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.detach({source:d1, target:d2}); + // this changed in 1.3.5, because auto generated endpoints are now removed by detach. so i added this test + // to check that the deleteEndpointsOnDetach flag is honoured. + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, supplied endpoint and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = _jsPlumb.addEndpoint(d1, endpoint); + var e2 = _jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(3); + _jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, supplied endpoints using 'source' and 'target' (this test is identical to the one above apart from the param names), and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = _jsPlumb.addEndpoint(d1, endpoint); + var e2 = _jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + _jsPlumb.connect({source:e1, target:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(3); + _jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + " detachable parameter defaults to true on _jsPlumb.connect", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), true, "connections detachable by default"); + }); + + test(renderMode + " detachable parameter set to false on _jsPlumb.connect", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, detachable:false}); + equals(c.isDetachable(), false, "connection detachable"); + }); + + test(renderMode + " setDetachable on initially detachable connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), true, "connection initially detachable"); + c.setDetachable(false); + equals(c.isDetachable(), false, "connection not detachable"); + }); + + test(renderMode + " setDetachable on initially not detachable connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, detachable:false }); + equals(c.isDetachable(), false, "connection not initially detachable"); + c.setDetachable(true); + equals(c.isDetachable(), true, "connection now detachable"); + }); + + test(renderMode + " _jsPlumb.Defaults.ConnectionsDetachable", function() { + _jsPlumb.Defaults.ConnectionsDetachable = false; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), false, "connections not detachable by default (overrode the defaults)"); + _jsPlumb.Defaults.ConnectionsDetachable = true; + }); + + + test(renderMode + ": _jsPlumb.connect (testing for connection event callback)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var connectCallback = null, detachCallback = null; + _jsPlumb.bind("jsPlumbConnection", function(params) { + connectCallback = $.extend({}, params); + }); + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + detachCallback = $.extend({}, params); + }); + _jsPlumb.connect({source:d1, target:d2}); // auto connect with default endpoint and anchor set + ok(connectCallback != null, "connect callback was made"); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.detach({source:d1, target:d2}); + assertContextSize(2); + ok(detachCallback != null, "detach callback was made"); + }); + + test(renderMode + ": _jsPlumb.connect (setting cssClass on Connector)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2,cssClass:"CSS"}); + var has = function(clazz) { + var cn = c.connector.canvas.className, + cns = cn.constructor == String ? cn : cn.baseVal; + + return cns.indexOf(clazz) != -1; + }; + ok(has("CSS"), "custom cssClass set correctly"); + ok(has(_jsPlumb.connectorClass), "basic connector class set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (overlays, long-hand version)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = {width:40,length:40,location:0.7, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"}}; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + }); + + test(renderMode + ": _jsPlumb.connect (overlays, long-hand version, IDs specified)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + equals("aLabel", connection1.overlays[0].id); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + equals("anArrow", connection1.overlays[1].id); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays)", function() { + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays + overlays specified in connect call)", function() { + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, overlays:[ + ["Label", {id:"label"}] + ]}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("label") != null, "Label overlay created from connect call"); + }); + + test(renderMode + ": _jsPlumb.connect (default connection overlays)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + }); + + test(renderMode + ": _jsPlumb.connect (default connection overlays + overlays specified in connect call)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, overlays:[ + ["Label", {id:"label"}] + ]}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("label") != null, "Label overlay created from connect call"); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays + default connection overlays)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow2" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("arrow2") != null, "Arrow overlay created from connection defaults"); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays + default connection overlays)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow2" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, overlays:[ + ["Label", {id:"label"}] + ]}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("arrow2") != null, "Arrow overlay created from connection defaults"); + ok(c.getOverlay("label") != null, "Label overlay created from connect call"); + }); + + test(renderMode + ": _jsPlumb.connect (label overlay set using 'label')", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO" + }); + + equals(c.getLabel(), "FOO", "label is set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction, with string)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2}); + + c.setLabel("FOO"); + equals(c.getLabel(), "FOO", "label is set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction, with function)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2}); + + c.setLabel(function() { return "BAR"; }); + equals(c.getLabel()(), "BAR", "label is set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction, with params object)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2}); + + c.setLabel({ + label:"BAZ", + cssClass:"CLASSY", + location:0.9 + }); + var lo = c.getLabelOverlay(); + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "BAZ", "label overlay has correct value"); + equals(lo.getLocation(), 0.9, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction with existing label set, with params object)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO", + labelLocation:0.2 + }); + + var lo = c.getLabelOverlay(); + equals(lo.getLocation(), 0.2, "label overlay has correct location"); + + c.setLabel({ + label:"BAZ", + cssClass:"CLASSY", + location:0.9 + }); + + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "BAZ", "label overlay has correct value"); + equals(lo.getLocation(), 0.9, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (getLabelOverlay, label on connect call)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO" + }), + lo = c.getLabelOverlay(); + + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "FOO", "label overlay has correct value"); + equals(lo.getLocation(), 0.5, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (getLabelOverlay, label on connect call, location set)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO", + labelLocation:0.2 + }), + lo = c.getLabelOverlay(); + + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "FOO", "label overlay has correct value"); + equals(lo.getLocation(), 0.2, "label overlay has correct location"); + }); + + + test(renderMode + ": _jsPlumb.connect (remove single overlay by id)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + connection1.removeOverlay("aLabel"); + equals(1, connection1.overlays.length); + equals("anArrow", connection1.overlays[0].id); + }); + + test(renderMode + ": _jsPlumb.connect (remove multiple overlays by id)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + connection1.removeOverlays("aLabel", "anArrow"); + equals(0, connection1.overlays.length); + }); + + test(renderMode + ": _jsPlumb.connect (overlays, short-hand version)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var loc = { location:0.7 }; + var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label", {label:"CONNECTION 1", location:0.3}], + ["Arrow", arrowSpec, loc] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + }); + + test(renderMode + ": _jsPlumb.connect (removeAllOverlays)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var loc = { location:0.7 }; + var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label", {label:"CONNECTION 1", location:0.3, cssClass:"PPPP"}], + ["Arrow", arrowSpec, loc] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + + connection1.removeAllOverlays(); + equals(0, connection1.overlays.length); + equals(0, $(".PPPP").length); + }); + + test(renderMode + ": _jsPlumb.connect, specify arrow overlay using string identifier only", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:["Arrow"]}); + equals(jsPlumb.Overlays[renderMode].Arrow, conn.overlays[0].constructor); + }); + + test(renderMode + ": Connection.getOverlay method, existing overlay", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("arrowOverlay"); + ok(overlay != null); + }); + + test(renderMode + ": Connection.getOverlay method, non-existent overlay", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("IDONTEXIST"); + ok(overlay == null); + }); + + test(renderMode + ": Overlay.setVisible method", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("arrowOverlay"); + ok(overlay.isVisible()); + overlay.setVisible(false); + ok(!overlay.isVisible()); + overlay.setVisible(true); + ok(overlay.isVisible()); + }); + + // this test is for the original detach function; it should stay working after i mess with it + // a little. + test(renderMode + ": _jsPlumb.detach (by element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + var e3 = _jsPlumb.addEndpoint(d1); + var e4 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + _jsPlumb.connect({ sourceEndpoint:e3, targetEndpoint:e4 }); // make two connections to be sure this works ;) + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionCount(e3, 1); + assertConnectionCount(e4, 1); + + _jsPlumb.detach({source:"d1", target:"d2"}); + + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionCount(e3, 0); + assertConnectionCount(e4, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + // detach is being made to operate more like connect - by taking one argument with a whole + // bunch of possible params in it. if two args are passed in it will continue working + // in the old way. + test(renderMode + ": _jsPlumb.detach (params object, using element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({source:"d1", target:"d2"}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); +/* + test(renderMode + ": _jsPlumb.detach (params object, using target only)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e1 = _jsPlumb.addEndpoint(d1, {maxConnections:2}), + e2 = _jsPlumb.addEndpoint(d2), + e3 = _jsPlumb.addEndpoint(d3); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e3 }); + assertConnectionCount(e1, 2); + assertConnectionCount(e2, 1); + assertConnectionCount(e3, 1); + _jsPlumb.detach({target:"d2"}); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 0); + assertConnectionCount(e3, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1); + }); +*/ + test(renderMode + ": _jsPlumb.detach (params object, using element objects)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({source:d1, target:d2}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.detach (source and target as endpoint UUIDs)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {uuid:"abcdefg"}); + ok(_jsPlumb.getEndpoint("abcdefg") != null, "e1 exists"); + var e2 = _jsPlumb.addEndpoint(d2, {uuid:"hijklmn"}); + ok(_jsPlumb.getEndpoint("hijklmn") != null, "e2 exists"); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({uuids:["abcdefg", "hijklmn"]}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.detach (sourceEndpoint and targetEndpoint supplied)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.makeDynamicAnchors (longhand)", function() { + var anchors = [_jsPlumb.makeAnchor([0.2, 0, 0, -1], null, _jsPlumb), _jsPlumb.makeAnchor([1, 0.2, 1, 0], null, _jsPlumb), + _jsPlumb.makeAnchor([0.8, 1, 0, 1], null, _jsPlumb), _jsPlumb.makeAnchor([0, 0.8, -1, 0], null, _jsPlumb) ]; + var dynamicAnchor = _jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); + }); + + test(renderMode + ": _jsPlumb.makeDynamicAnchors (shorthand)", function() { + var anchors = [[0.2, 0, 0, -1], [1, 0.2, 1, 0], + [0.8, 1, 0, 1], [0, 0.8, -1, 0] ]; + var dynamicAnchor = _jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); + }); + + test(renderMode + ": Connection.isVisible/setVisible", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c1 = _jsPlumb.connect({source:d1,target:d2}); + equals(true, c1.isVisible(), "Connection is visible after creation."); + c1.setVisible(false); + equals(false, c1.isVisible(), "Connection is not visible after calling setVisible(false)."); + equals($(c1.connector.canvas).css("display"), "none"); + c1.setVisible(true); + equals(true, c1.isVisible(), "Connection is visible after calling setVisible(true)."); + equals($(c1.connector.canvas).css("display"), "block"); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible basic test (no connections)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals($(e1.canvas).css("display"), "none"); + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals($(e1.canvas).css("display"), "block"); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should track changes in the source, because it has only this connection.)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + var c1 = _jsPlumb.connect({source:e1, target:e2}); + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals(false, e2.isVisible(), "other Endpoint is not visible either."); + equals(false, c1.isVisible(), "connection between the two is not visible either."); + + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals(true, e2.isVisible(), "other Endpoint is visible too"); + equals(true, c1.isVisible(), "connection between the two is visible too."); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should not track changes in the source, because it has another connection.)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2, { maxConnections:2 }), e3 = _jsPlumb.addEndpoint(d3); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + var c1 = _jsPlumb.connect({source:e1, target:e2}); + var c2 = _jsPlumb.connect({source:e2, target:e3}); + + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals(true, e2.isVisible(), "other Endpoint should still be visible."); + equals(true, e3.isVisible(), "third Endpoint should still be visible."); + equals(false, c1.isVisible(), "connection between the two is not visible either."); + equals(true, c2.isVisible(), "other connection is visible."); + + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals(true, e2.isVisible(), "other Endpoint is visible too"); + equals(true, c1.isVisible(), "connection between the two is visible too."); + equals(true, c2.isVisible(), "other connection is visible."); + }); + + // tests of the functionality that allows a user to specify that they want elements appended to the document body + test(renderMode + " _jsPlumb.Defaults.Container, specified with a selector", function() { + _jsPlumb.Defaults.Container = $("body"); + equals($("#container")[0].childNodes.length, 0, "container has no nodes"); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals($("#container")[0].childNodes.length, 2, "container has two div elements"); // the divs we added have been added to the 'container' div. + // but we have told _jsPlumb to add its canvas to the body, so this connect call should not add another few elements to the container: + _jsPlumb.connect({source:d1, target:d2}); + equals($("#container")[0].childNodes.length, 2, "container still has two div elements"); + }); + + // tests of the functionality that allows a user to specify that they want elements appended to some specific container. + test(renderMode + " _jsPlumb.Defaults.Container, specified with DOM element", function() { + _jsPlumb.Defaults.Container = document.getElementsByTagName("body")[0]; + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length, "two divs added to the container"); // the divs we added have been added to the 'container' div. + // but we have told _jsPlumb to add its canvas to the body, so this connect call should not add another few elements to the container: + var bodyElementCount = $("body")[0].childNodes.length; + _jsPlumb.connect({source:d1, target:d2}); + equals(2, $("#container")[0].childNodes.length, "still only two children in container; elements were added to the body by _jsPlumb"); + // test to see if 3 elements have been added + equals(bodyElementCount + 3, $("body")[0].childNodes.length, "3 new elements added to the document body"); + }); + + test(renderMode + " container specified to connect call, with a selector", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length); // the divs we added have been added to the 'container' div. + var bodyElementCount = $("body")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to the body, so this connect call should not add another few elements to the container: + _jsPlumb.connect({source:d1, target:d2, container:$("body")}); + equals(2, $("#container")[0].childNodes.length); + equals(bodyElementCount + 3, $("body")[0].childNodes.length, "3 new elements added to the document body"); + }); + + test(renderMode + " container specified to connect call, with a string ID", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + equals(3, $("#container")[0].childNodes.length, "container has divs we added"); // the divs we added have been added to the 'container' div. + var d3ElementCount = $("#d3")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to "d3", so this connect call should not add another few elements to the container: + _jsPlumb.connect({source:d1, target:d2, container:"d3"}); + equals(3, $("#container")[0].childNodes.length, "container still has only the divs we added"); + equals(d3ElementCount + 3, $("#d3")[0].childNodes.length, "3 new elements added to div d3"); + }); + + test(renderMode + " container specified to addEndpoint call, with a selector", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length); // the divs we added have been added to the 'container' div. + var bodyElementCount = $("body")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to the body, so this connect call should not add another few elements to the container: + _jsPlumb.addEndpoint(d1, {container:$("body")}); + equals(2, $("#container")[0].childNodes.length); + equals(bodyElementCount + 1, $("body")[0].childNodes.length, "1 new element added to the document body"); + }); + + test(renderMode + " container specified to addEndpoint call, with a string ID", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + equals(3, $("#container")[0].childNodes.length, "container has divs we added"); // the divs we added have been added to the 'container' div. + var d3ElementCount = $("#d3")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to "d3", so this connect call should not add another few elements to the container: + _jsPlumb.addEndpoint(d1, { container:"d3" }); + equals(3, $("#container")[0].childNodes.length, "container still has only the divs we added"); + equals(d3ElementCount + 1, $("#d3")[0].childNodes.length, "1 new element added to div d3"); + }); + + test(renderMode + " detachable defaults to true when connection made between two endpoints", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2), + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection not detachable"); + }); + + test(renderMode + " connection detachable when target endpoint has connectionsDetachable set to true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2, {connectionsDetachable:true}), + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection detachable because connectionsDetachable was set on target endpoint"); + }); + + test(renderMode + " connection detachable when source endpoint has connectionsDetachable set to true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {connectionsDetachable:true}), e2 = _jsPlumb.addEndpoint(d2), + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection detachable because connectionsDetachable was set on source endpoint"); + }); + + test(renderMode + " Connector has 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.connector.type, "Bezier", "Bezier connector has type set"); + + var c2 = _jsPlumb.connect({source:d1, target:d2, connector:"Straight"}); + equals(c2.connector.type, "Straight", "Straight connector has type set"); + + var c3 = _jsPlumb.connect({source:d1, target:d2, connector:"Flowchart"}); + equals(c3.connector.type, "Flowchart", "Flowchart connector has type set"); + }); + + test(renderMode + " Endpoints have 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.endpoints[0].type, "Dot", "Dot endpoint has type set"); + + var c2 = _jsPlumb.connect({source:d1, target:d2, endpoints:["Rectangle", "Blank"]}); + equals(c2.endpoints[1].type, "Blank", "Blank endpoint has type set"); + equals(c2.endpoints[0].type, "Rectangle", "Rectangle endpoint has type set"); + }); + + test(renderMode + " Overlays have 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = _jsPlumb.connect({ + source:d1, + target:d2, + overlays:[ "Arrow", "Label", "PlainArrow", "Diamond" ] + }); + equals(c.overlays[0].type, "Arrow", "Arrow overlay has type set"); + equals(c.overlays[1].type, "Label", "Label overlay has type set"); + equals(c.overlays[2].type, "PlainArrow", "PlainArrow overlay has type set"); + equals(c.overlays[3].type, "Diamond", "Diamond overlay has type set"); + }); + + test(renderMode + " _jsPlumb.hide, original one-arg version", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + _jsPlumb.hide(d1); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(true, e1.isVisible(), "endpoint 1 is still visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + _jsPlumb.show(d1); + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + }); + + test(renderMode + " _jsPlumb.hide, two-arg version, endpoints should also be hidden", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + _jsPlumb.show(d1); // now show d1, but do not alter the endpoints. e1 should still be hidden + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + test(renderMode + " _jsPlumb.show, two-arg version, endpoints should become visible", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + _jsPlumb.show(d1, true); // now show d1, and alter the endpoints. e1 should be visible. + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(true, e1.isVisible(), "endpoint 1 is visible again."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + test(renderMode + " _jsPlumb.show, two-arg version, endpoints should become visible, but not all connections, because some other endpoints are not visible.", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e11 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + e3 = _jsPlumb.addEndpoint(d3, e), + c1 = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e11, target:e3}); + + // we now have d1 connected to both d3 and d2. we'll hide d1, and everything on d1 should be hidden. + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "connection 1 is no longer visible."); + equals(false, c2.isVisible(), "connection 2 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(false, e11.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + equals(true, e3.isVisible(), "endpoint 3 is still visible."); + + // now, we will also hide d3. making d1 visible again should NOT result in c2 becoming visible, because the other endpoint + // for c2 is e3, which is not visible. + _jsPlumb.hide(d3, true); + equals(false, e3.isVisible(), "endpoint 3 is no longer visible."); + + _jsPlumb.show(d1, true); // now show d1, and alter the endpoints. e1 should be visible, c1 should be visible, but c2 should not. + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(false, c2.isVisible(), "Connection 2 is not visible."); + equals(true, e1.isVisible(), "endpoint 1 is visible again."); + equals(true, e11.isVisible(), "endpoint 11 is visible again."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + equals(false, e3.isVisible(), "endpoint 3 is still not visible."); + }); + + /* + test(renderMode + " _jsPlumb.hide, two-arg version, endpoints should also be hidden", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + /** + * test for issue 132: label leaves its element in the DOM after it has been + * removed from a connection. + */ + test(renderMode + " label cleans itself up properly", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label", cssClass:"foo"}] + ]}); + ok($(".foo").length == 1, "label element exists in DOM"); + c.removeOverlay("label"); + ok($(".foo").length == 0, "label element does not exist in DOM"); + }); + + test(renderMode + " arrow cleans itself up properly", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Arrow", {id:"arrow"}] + ]}); + ok(c.getOverlay("arrow") != null, "arrow overlay exists"); + c.removeOverlay("arrow"); + ok(c.getOverlay("arrow") == null, "arrow overlay has been removed"); + }); + + test(renderMode + " label overlay getElement function", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label"}] + ]}); + ok(c.getOverlay("label").getElement() != null, "label overlay exposes element via getElement method"); + }); + + test(renderMode + " label overlay provides getLabel and setLabel methods", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label", label:"foo"}] + ]}); + var o = c.getOverlay("label"), e = o.getElement(); + ok(e.innerHTML == "foo", "label text is set to original value"); + o.setLabel("baz"); + ok(e.innerHTML == "baz", "label text is set to new value 'baz'"); + ok(o.getLabel() === "baz", "getLabel function works correctly with String"); + // now try functions + var aFunction = function() { return "aFunction"; }; + o.setLabel(aFunction); + ok(e.innerHTML == "aFunction", "label text is set to new value from Function"); + ok(o.getLabel() === aFunction, "getLabel function works correctly with Function"); + }); + + test(renderMode + " parameters object works for Endpoint", function() { + var d1 = _addDiv("d1"), + f = function() { alert("FOO!"); }, + e = _jsPlumb.addEndpoint(d1, { + isSource:true, + parameters:{ + "string":"param1", + "int":4, + "function":f + } + }); + ok(e.getParameter("string") === "param1", "getParameter(String) works correctly"); + ok(e.getParameter("int") === 4, "getParameter(int) works correctly"); + ok(e.getParameter("function") == f, "getParameter(Function) works correctly"); + }); + + test(renderMode + " parameters object works for Connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + f = function() { alert("FOO!"); }; + var c = _jsPlumb.connect({ + source:d1, + target:d2, + parameters:{ + "string":"param1", + "int":4, + "function":f + } + }); + ok(c.getParameter("string") === "param1", "getParameter(String) works correctly"); + ok(c.getParameter("int") === 4, "getParameter(int) works correctly"); + ok(c.getParameter("function") == f, "getParameter(Function) works correctly"); + }); + + test(renderMode + " parameters set on Endpoints and Connections are all merged, and merged correctly at that.", function() { + var d1 = _addDiv("d1"), + d2 = _addDiv("d2"), + e = _jsPlumb.addEndpoint(d1, { + isSource:true, + parameters:{ + "string":"sourceEndpoint", + "int":0, + "function":function() { return "sourceEndpoint"; } + } + }), + e2 = _jsPlumb.addEndpoint(d2, { + isTarget:true, + parameters:{ + "int":1, + "function":function() { return "targetEndpoint"; } + } + }), + c = _jsPlumb.connect({source:e, target:e2, parameters:{ + "function":function() { return "connection"; } + }}); + + ok(c.getParameter("string") === "sourceEndpoint", "getParameter(String) works correctly"); + ok(c.getParameter("int") === 1, "getParameter(int) works correctly"); + ok(c.getParameter("function")() == "connection", "getParameter(Function) works correctly"); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers standard connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2}); + equals(_jsPlumb.anchorManager.getConnectionsFor("d1").length, 1); + equals(_jsPlumb.anchorManager.getEndpointsFor("d1").length, 1); + equals(_jsPlumb.anchorManager.getConnectionsFor("d2").length, 1); + equals(_jsPlumb.anchorManager.getEndpointsFor("d2").length, 1); + var c2 = _jsPlumb.connect({source:d1, target:d2}); + equals(_jsPlumb.anchorManager.getConnectionsFor("d1").length, 2); + equals(_jsPlumb.anchorManager.getConnectionsFor("d2").length, 2); + equals(_jsPlumb.anchorManager.getEndpointsFor("d1").length, 2); + equals(_jsPlumb.anchorManager.getEndpointsFor("d2").length, 2); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers dynamic anchor connection, and removes it.", function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var c = _jsPlumb.connect({source:d3, target:d4, anchors:["AutoDefault", "AutoDefault"]}); + + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 1); + + var c2 = _jsPlumb.connect({source:d3, target:d4}); + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 2); + equals(_jsPlumb.anchorManager.getConnectionsFor("d4").length, 2); + + equals(_jsPlumb.anchorManager.getEndpointsFor("d3").length, 2); + _jsPlumb.detach(c); + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 1); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers continuous anchor connection, and removes it.", function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var c = _jsPlumb.connect({source:d3, target:d4, anchors:["Continuous", "Continuous"]}); + + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 1); + equals(_jsPlumb.anchorManager.getConnectionsFor("d4").length, 1); + + _jsPlumb.detach(c); + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 0); + equals(_jsPlumb.anchorManager.getConnectionsFor("d4").length, 0); + + _jsPlumb.reset(); + equals(_jsPlumb.anchorManager.getEndpointsFor("d4").length, 0); + }); + +// ------------- utility functions - math stuff, mostly -------------------------- + + var tolerance = 0.00000005, withinTolerance = function(v1, v2, msg) { + if (Math.abs(v1 - v2) < tolerance) ok(true, msg + "; expected " + v1 + " and got it"); + else { + ok(false, msg + "; expected " + v1 + " got " + v2); + } + }; + test(renderMode + " jsPlumbUtil.gradient, segment 1", function() { + var p1 = [2,2], p2 = [3,1], m = jsPlumbUtil.gradient(p1,p2); + equals(m, -1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.normal, segment 1", function() { + var p1 = [2,2], p2 = [3,1], m = jsPlumbUtil.normal(p1,p2); + equals(m, 1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.gradient, segment 2", function() { + var p1 = [2,2], p2 = [3,3], m = jsPlumbUtil.gradient(p1,p2); + equals(m, 1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.normal, segment 2", function() { + var p1 = [2,2], p2 = [3,3], m = jsPlumbUtil.normal(p1,p2); + equals(m, -1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.gradient, segment 3", function() { + var p1 = [2,2], p2 = [1,3], m = jsPlumbUtil.gradient(p1,p2); + equals(m, -1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.normal, segment 3", function() { + var p1 = [2,2], p2 = [1,3], m = jsPlumbUtil.normal(p1,p2); + equals(m, 1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.gradient, segment 4", function() { + var p1 = [2,2], p2 = [1,1], m = jsPlumbUtil.gradient(p1,p2); + equals(m, 1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.normal, segment 4", function() { + var p1 = [2,2], p2 = [1,1], m = jsPlumbUtil.normal(p1,p2); + equals(m, -1, "normal calculated correctly for simple case"); + }); + test(renderMode + "jsPlumbUtil.pointOnLine, segment 1", function() { + var p1 = {x:2,y:2}, p2={x:3, y:1}, + target = jsPlumbUtil.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumbUtil.pointOnLine, segment 2", function() { + var p1 = {x:2,y:2}, p2={x:3, y:3}, + target = jsPlumbUtil.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumbUtil.pointOnLine, segment 3", function() { + var p1 = {x:2,y:2}, p2={x:1, y:3}, + target = jsPlumbUtil.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumbUtil.pointOnLine, segment 4", function() { + var p1 = {x:2,y:2}, p2={x:1, y:1}, + target = jsPlumbUtil.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumbUtil.perpendicularLineTo, segment 1", function() { + var p1 = {x:2, y:2}, p2={x:3, y:1}, m = jsPlumbUtil.gradient(p1, p2), + l = jsPlumbUtil.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(4, l[0].x, "point 1 x is correct"); + withinTolerance(2, l[0].y, "point 1 y is correct"); + + withinTolerance(2, l[1].x, "point 2 x is correct"); + withinTolerance(0, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumbUtil.perpendicularLineTo, segment 2", function() { + var p1 = {x:2, y:2}, p2={x:3, y:3}, m = jsPlumbUtil.gradient(p1, p2), + l = jsPlumbUtil.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(4, l[0].x, "point 1 x is correct"); + withinTolerance(2, l[0].y, "point 1 y is correct"); + + withinTolerance(2, l[1].x, "point 2 x is correct"); + withinTolerance(4, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumbUtil.perpendicularLineTo, segment 3", function() { + var p1 = {x:2, y:2}, p2={x:1, y:3}, m = jsPlumbUtil.gradient(p1, p2), + l = jsPlumbUtil.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(2, l[0].x, "point 1 x is correct"); + withinTolerance(4, l[0].y, "point 1 y is correct"); + + withinTolerance(0, l[1].x, "point 2 x is correct"); + withinTolerance(2, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumbUtil.perpendicularLineTo, segment 4", function() { + var p1 = {x:2, y:2}, p2={x:1, y:1}, m = jsPlumbUtil.gradient(p1, p2), + l = jsPlumbUtil.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(2, l[0].x, "point 1 x is correct"); + withinTolerance(0, l[0].y, "point 1 y is correct"); + + withinTolerance(0, l[1].x, "point 2 x is correct"); + withinTolerance(2, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumbUtil.intersects, with intersection", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 3, y:4, w:3, h:3}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, with no intersection", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 13, y:4, w:3, h:3}; + + ok(!jsPlumbUtil.intersects(r1, r2), "r1 and r2 do not intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, with intersection, equal Y", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 1, y:2, w:3, h:6}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, with intersection, equal X", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 2, y:1, w:4, h:6}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, identical rectangles", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 2, y:2, w:4, h:6}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, corners touch (intersection)", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 6, y:8, w:3, h:3}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, one rectangle contained within the other", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 0, y:0, w:23, h:23}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "setImage on Endpoint", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + originalUrl = "../../img/endpointTest1.png", + e = { + endpoint:[ "Image", { src:originalUrl } ] + }, + ep = _jsPlumb.addEndpoint(d1, e); + }); + test(renderMode + "setImage on Endpoint, with supplied onload", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { + endpoint:[ "Image", { + src:"../../img/endpointTest1.png", + onload:function(imgEp) { + equals("../../img/endpointTest1.png", imgEp.img.src); + equals(ep.endpoint.canvas.getAttribute("src"), imgEp.img.src); + } + } ] + }, + ep = _jsPlumb.addEndpoint(d1, e); + ep.setImage("../../img/littledot.png", function(imgEp) { + equals("../../img/littledot.png", imgEp.img.src); + equals(ep.endpoint.canvas.getAttribute("src"), imgEp.img.src); + }); + }); + + test(renderMode + "attach endpoint to table when desired element was td", function() { + var table = document.createElement("table"), + tr = document.createElement("tr"), + td = document.createElement("td"); + table.appendChild(tr); tr.appendChild(td); + document.body.appendChild(table); + var ep = _jsPlumb.addEndpoint(td); + equals(ep.canvas.parentNode.tagName.toLowerCase(), "table"); + }); + +// issue 190 - regressions with getInstance. these tests ensure that generated ids are unique across +// instances. + + test(renderMode + " id clashes between instances", function() { + var d1 = document.createElement("div"), + d2 = document.createElement("div"), + _jsp2 = jsPlumb.getInstance(); + + document.body.appendChild(d1); + document.body.appendChild(d2); + + _jsPlumb.addEndpoint(d1); + _jsp2.addEndpoint(d2); + + var id1 = d1.getAttribute("id"), + id2 = d2.getAttribute("id"); + + var idx = id1.indexOf("_"), idx2 = id1.lastIndexOf("_"), v1 = id1.substring(idx, idx2); + var idx3 = id2.indexOf("_"), idx4 = id2.lastIndexOf("_"), v2 = id2.substring(idx3, idx4); + + ok (v1 != v2, "instance versions are different : " + v1 + " : " + v2); + }); + + test(renderMode + " id clashes between instances", function() { + var d1 = document.createElement("div"), + d2 = document.createElement("div"), + _jsp2 = jsPlumb.getInstance(); + + document.body.appendChild(d1); + document.body.appendChild(d2); + + _jsPlumb.addEndpoint(d1); + _jsPlumb.addEndpoint(d2); + + var id1 = d1.getAttribute("id"), + id2 = d2.getAttribute("id"); + + var idx = id1.indexOf("_"), idx2 = id1.lastIndexOf("_"), v1 = id1.substring(idx, idx2); + var idx3 = id2.indexOf("_"), idx4 = id2.lastIndexOf("_"), v2 = id2.substring(idx3, idx4); + + ok (v1 == v2, "instance versions are the same : " + v1 + " : " + v2); + }); + + test(renderMode + " importDefaults", function() { + _jsPlumb.Defaults.Anchors = ["LeftMiddle", "RightMiddle"]; + var d1 = _addDiv("d1"), + d2 = _addDiv(d2), + c = _jsPlumb.connect({source:d1, target:d2}), + e = c.endpoints[0]; + + equals(e.anchor.x, 0, "left middle anchor"); + equals(e.anchor.y, 0.5, "left middle anchor"); + + _jsPlumb.importDefaults({ + Anchors:["TopLeft", "TopRight"] + }); + + var conn = _jsPlumb.connect({source:d1, target:d2}), + e1 = conn.endpoints[0], e2 = conn.endpoints[1]; + + equals(e1.anchor.x, 0, "top leftanchor"); + equals(e2.anchor.y, 0, "top left anchor"); + equals(e2.anchor.x, 1, "top right anchor"); + equals(e2.anchor.y, 0, "top right anchor"); + + }); + + test(renderMode + " restoreDefaults", function() { + _jsPlumb.importDefaults({ + Anchors:["TopLeft", "TopRight"] + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), conn = _jsPlumb.connect({source:d1, target:d2}), + e1 = conn.endpoints[0], e2 = conn.endpoints[1]; + + equals(e1.anchor.x, 0, "top leftanchor"); + equals(e2.anchor.y, 0, "top left anchor"); + equals(e2.anchor.x, 1, "top right anchor"); + equals(e2.anchor.y, 0, "top right anchor"); + + _jsPlumb.restoreDefaults(); + + var conn2 = _jsPlumb.connect({source:d1, target:d2}), + e3 = conn2.endpoints[0], e4 = conn2.endpoints[1]; + + equals(e3.anchor.x, 0.5, "bottom center anchor"); + equals(e3.anchor.y, 1, "bottom center anchor"); + equals(e4.anchor.x, 0.5, "bottom center anchor"); + equals(e4.anchor.y, 1, "bottom center anchor"); + }); + +// setId function + + test(renderMode + " setId, taking two strings, only default scope", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId("d1", "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a selector and a string, only default scope", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1"), "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a DOM element and a string, only default scope", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1")[0], "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking two strings, mix of scopes", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2, scope:"FOO"}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId("d1", "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a selector and a string, mix of scopes", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2, scope:"FOO"}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1"), "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a DOM element and a string, mix of scopes", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2, scope:"FOO"}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1")[0], "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setIdChanged, ", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + $("#d1").attr("id", "d3"); + + _jsPlumb.setIdChanged("d1", "d3"); + + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " endpoint hide/show should hide/show overlays", function() { + _addDiv("d1"); + var e1 = _jsPlumb.addEndpoint("d1", { + overlays:[ + [ "Label", { id:"label", label:"foo" } ] + ] + }), + o = e1.getOverlay("label"); + + ok(o.isVisible(), "overlay is initially visible"); + _jsPlumb.hide("d1", true); + ok(!o.isVisible(), "overlay is no longer visible"); + }); + + test(renderMode + " connection hide/show should hide/show overlays", function() { + _addDiv("d1");_addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", + overlays:[ + [ "Label", { id:"label", label:"foo" } ] + ] + }), + o = c.getOverlay("label"); + + ok(o.isVisible(), "overlay is initially visible"); + _jsPlumb.hide("d1", true); + ok(!o.isVisible(), "overlay is no longer visible"); + }); + + test(renderMode + " select, basic test", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2"}), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 1, "one connection selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; dont filter on scope.", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 2, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scope", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + s = _jsPlumb.select({source:"d1", scope:"FOO"}); + + equals(s.length, 1, "one connection selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scopes", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + c3 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAZ"}) + s = _jsPlumb.select({source:"d1", scope:["FOO","BAR"]}); + + equals(s.length, 2, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; scope but no scope filter; single source id", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + c3 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAZ"}), + c4 = _jsPlumb.connect({source:"d2", target:"d1", scope:"BOZ"}), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 3, "three connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scopes; single source id", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + c3 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAZ"}), + c4 = _jsPlumb.connect({source:"d2", target:"d1", scope:"BOZ"}), + s = _jsPlumb.select({source:"d1", scope:["FOO","BAR", "BOZ"]}); + + equals(s.length, 2, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scope; dont supply sourceid", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + s = _jsPlumb.select({ scope:"FOO" }); + + equals(s.length, 1, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scope; dont supply sourceid", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d2", target:"d1", scope:"BAR"}), + s = _jsPlumb.select({ scope:"FOO" }); + + equals(s.length, 1, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, two connections, with overlays", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({ + source:"d1", + target:"d2", + overlays:[ + ["Label", {id:"l"}] + ] + }), + c2 = _jsPlumb.connect({ + source:"d1", + target:"d2", + overlays:[ + ["Label", {id:"l"}] + ] + }), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 2, "two connections selected"); + ok(s.get(0).getOverlay("l") != null, "connection has overlay"); + ok(s.get(1).getOverlay("l") != null, "connection has overlay"); + }); + + test(renderMode + " select, chaining with setHover and hideOverlay", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({ + source:"d1", + target:"d2", + overlays:[ + ["Label", {id:"l"}] + ] + }); + s = _jsPlumb.select({source:"d1"}); + + s.setHover(false).hideOverlay("l"); + + ok(!(s.get(0).isHover()), "connection is not hover"); + ok(!(s.get(0).getOverlay("l").isVisible()), "overlay is not visible"); + }); + + test(renderMode + " select, .each function", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10) + }); + } + + var s = _jsPlumb.select(); + equals(s.length, 5, "there are five connections"); + + var t = ""; + s.each(function(connection) { + t += "f"; + }); + equals("fffff", t, ".each is working"); + }); + + test(renderMode + " select, multiple connections + chaining", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + overlays:[ + ["Arrow", {location:0.3}], + ["Arrow", {location:0.7}] + ] + }); + } + + var s = _jsPlumb.select().removeAllOverlays().setParameter("foo", "bar").setHover(false).setLabel("baz"); + equals(s.length, 5, "there are five connections"); + + for (var j = 0; j < 5; j++) { + equals(s.get(j).getOverlays().length, 1, "one overlay: the label"); + equals(s.get(j).getParameter("foo"), "bar", "parameter foo has value 'bar'"); + ok(!(s.get(j).isHover()), "hover is set to false"); + equals(s.get(j).getLabel(), "baz", "label is set to 'baz'"); + } + }); + + test(renderMode + " select, simple getter", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + label:"FOO" + }); + } + + var lbls = _jsPlumb.select().getLabel(); + equals(lbls.length, 5, "there are five labels"); + + for (var j = 0; j < 5; j++) { + equals(lbls[j][0], "FOO", "label has value 'FOO'"); + } + }); + + test(renderMode + " select, getter + chaining", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + label:"FOO" + }); + } + + var params = _jsPlumb.select().removeAllOverlays().setParameter("foo", "bar").getParameter("foo"); + equals(params.length, 5, "there are five params"); + + for (var j = 0; j < 5; j++) { + equals(params[j][0], "bar", "parameter has value 'bar'"); + } + }); + + + test(renderMode + " select, detach method", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + label:"FOO" + }); + } + + var params = _jsPlumb.select().detach(); + + equals(jsPlumb.select().length, 0, "there are no connections"); + }); + + + // selectEndpoints + test(renderMode + " selectEndpoints, basic tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1), + e2 = _jsPlumb.addEndpoint(d1); + + equals(_jsPlumb.selectEndpoints().length, 2, "there are two endpoints"); + equals(_jsPlumb.selectEndpoints({element:"d1"}).length, 2, "there are two endpoints on d1"); + equals(_jsPlumb.selectEndpoints({element:"d2"}).length, 0, "there are 0 endpoints on d2"); + + equals(_jsPlumb.selectEndpoints({source:"d1"}).length, 0, "there are zero source endpoints on d1"); + equals(_jsPlumb.selectEndpoints({target:"d1"}).length, 0, "there are zero target endpoints on d1"); + equals(_jsPlumb.selectEndpoints({source:"d2"}).length, 0, "there are zero source endpoints on d2"); + equals(_jsPlumb.selectEndpoints({target:"d2"}).length, 0, "there are zero target endpoints on d2"); + + equals(_jsPlumb.selectEndpoints({source:"d1", scope:"FOO"}).length, 0, "there are zero source endpoints on d1 with scope FOO"); + + _jsPlumb.addEndpoint("d2", { scope:"FOO", isSource:true }); + equals(_jsPlumb.selectEndpoints({source:"d2", scope:"FOO"}).length, 1, "there is one source endpoint on d2 with scope FOO"); + + equals(_jsPlumb.selectEndpoints({element:["d2", "d1"]}).length, 3, "there are three endpoints between d2 and d1"); + }); + + test(renderMode + " selectEndpoints, basic tests, various input argument formats", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1), + e2 = _jsPlumb.addEndpoint(d1); + + equals(_jsPlumb.selectEndpoints({element:"d1"}).length, 2, "using id, there are two endpoints on d1"); + equals(_jsPlumb.selectEndpoints({element:d1}).length, 2, "using dom element, there are two endpoints on d1"); + equals(_jsPlumb.selectEndpoints({element:$("#d1")}).length, 2, "using selector, there are two endpoints on d1"); + equals(_jsPlumb.selectEndpoints({element:$(d1)}).length, 2, "using selector with dom element, there are two endpoints on d1"); + + }); + + test(renderMode + " selectEndpoints, basic tests, scope", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {scope:"FOO"}), + e2 = _jsPlumb.addEndpoint(d1); + + equals(_jsPlumb.selectEndpoints({element:"d1"}).length, 2, "using id, there are two endpoints on d1"); + equals(_jsPlumb.selectEndpoints({element:"d1", scope:"FOO"}).length, 1, "using id, there is one endpoint on d1 with scope 'FOO'"); + _jsPlumb.addEndpoint(d1, {scope:"BAR"}), + equals(_jsPlumb.selectEndpoints({element:"d1", scope:"FOO"}).length, 1, "using id, there is one endpoint on d1 with scope 'BAR'"); + equals(_jsPlumb.selectEndpoints({element:"d1", scope:["BAR", "FOO"]}).length, 2, "using id, there are two endpoints on d1 with scope 'BAR' or 'FOO'"); + }); + + test(renderMode + " selectEndpoints, isSource tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true}), + e2 = _jsPlumb.addEndpoint(d1), + e3 = _jsPlumb.addEndpoint(d2, {isSource:true}); + + equals(_jsPlumb.selectEndpoints({source:"d1"}).length, 1, "there is one source endpoint on d1"); + equals(_jsPlumb.selectEndpoints({target:"d1"}).length, 0, "there are zero target endpoints on d1"); + + equals(_jsPlumb.selectEndpoints({source:"d2"}).length, 1, "there is one source endpoint on d2"); + + equals(_jsPlumb.selectEndpoints({source:["d2", "d1"]}).length, 2, "there are two source endpoints between d1 and d2"); + }); + + test(renderMode + " selectEndpoints, isTarget tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isTarget:true}), + e2 = _jsPlumb.addEndpoint(d1), + e3 = _jsPlumb.addEndpoint(d2, {isTarget:true}); + + equals(_jsPlumb.selectEndpoints({target:"d1"}).length, 1, "there is one target endpoint on d1"); + equals(_jsPlumb.selectEndpoints({source:"d1"}).length, 0, "there are zero source endpoints on d1"); + + equals(_jsPlumb.selectEndpoints({target:"d2"}).length, 1, "there is one target endpoint on d2"); + + equals(_jsPlumb.selectEndpoints({target:["d2", "d1"]}).length, 2, "there are two target endpoints between d1 and d2"); + }); + + test(renderMode + " selectEndpoints, isSource + isTarget tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true}), + e2 = _jsPlumb.addEndpoint(d1), + e3 = _jsPlumb.addEndpoint(d1, {isSource:true}), + e4 = _jsPlumb.addEndpoint(d1, {isTarget:true}); + + equals(_jsPlumb.selectEndpoints({source:"d1"}).length, 2, "there are two source endpoints on d1"); + equals(_jsPlumb.selectEndpoints({target:"d1"}).length, 2, "there are two target endpoints on d1"); + + equals(_jsPlumb.selectEndpoints({target:"d1", source:"d1"}).length, 1, "there is one source and target endpoint on d1"); + + equals(_jsPlumb.selectEndpoints({element:"d1"}).length, 4, "there are four endpoints on d1"); + + }); + + test(renderMode + " selectEndpoints, delete endpoints", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true}); + + equals(_jsPlumb.selectEndpoints({element:"d1"}).length, 1, "there is one endpoint on d1"); + _jsPlumb.selectEndpoints({source:"d1"}).delete(); + equals(_jsPlumb.selectEndpoints({element:"d1"}).length, 0, "there are zero endpoints on d1"); + }); + + test(renderMode + " selectEndpoints, detach connections", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true}), + e2 = _jsPlumb.addEndpoint(d2, {isSource:true, isTarget:true}); + + _jsPlumb.connect({source:e1, target:e2}); + + equals(e1.connections.length, 1, "there is one connection on d1's endpoint"); + equals(e2.connections.length, 1, "there is one connection on d2's endpoint"); + + _jsPlumb.selectEndpoints({source:"d1"}).detachAll(); + + equals(e1.connections.length, 0, "there are zero connections on d1's endpoint"); + equals(e2.connections.length, 0, "there are zero connections on d2's endpoint"); + }); + + test(renderMode + " selectEndpoints, hover tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true}); + + equals(e1.isHover(), false, "hover not set"); + _jsPlumb.selectEndpoints({source:"d1"}).setHover(true); + equals(e1.isHover(), true, "hover set"); + _jsPlumb.selectEndpoints({source:"d1"}).setHover(false); + equals(e1.isHover(), false, "hover no longer set"); + }); + +// setPaintStyle/getPaintStyle tests + + test(renderMode + " setPaintStyle", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), c = _jsPlumb.connect({source:d1, target:d2}); + c.setPaintStyle({strokeStyle:"FOO", lineWidth:999}); + equals(c.paintStyleInUse.strokeStyle, "FOO", "strokeStyle was set"); + equals(c.paintStyleInUse.lineWidth, 999, "lineWidth was set"); + + c.setHoverPaintStyle({strokeStyle:"BAZ", lineWidth:444}); + c.setHover(true); + equals(c.paintStyleInUse.strokeStyle, "BAZ", "strokeStyle was set"); + equals(c.paintStyleInUse.lineWidth, 444, "lineWidth was set"); + + equals(c.getPaintStyle().strokeStyle, "FOO", "getPaintStyle returns correct value"); + equals(c.getHoverPaintStyle().strokeStyle, "BAZ", "getHoverPaintStyle returns correct value"); + }); + +// ******************* getEndpoints ************************************************ + + test(renderMode + " getEndpoints", function() { + _addDiv("d1");_addDiv("d2"); + + _jsPlumb.addEndpoint("d1"); + _jsPlumb.addEndpoint("d2"); + _jsPlumb.addEndpoint("d1"); + + var e = _jsPlumb.getEndpoints("d1"), + e2 = _jsPlumb.getEndpoints("d2"); + equals(e.length, 2, "two endpoints on d1"); + equals(e2.length, 1, "one endpoint on d2"); + }); + + /** + * leave this test at the bottom! + */ + test(renderMode + ': unload test', function() { + _jsPlumb.unload(); + var contextNode = $(".__jsPlumb_context"); + ok(contextNode.length == 0, 'context node unloaded'); + }); +}; + diff --git a/archive/1.3.11/jsPlumb-connectors-statemachine-1.3.11-RC1.js b/archive/1.3.11/jsPlumb-connectors-statemachine-1.3.11-RC1.js new file mode 100644 index 000000000..c387f24fb --- /dev/null +++ b/archive/1.3.11/jsPlumb-connectors-statemachine-1.3.11-RC1.js @@ -0,0 +1,465 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.11 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the state machine connectors. + * + * Thanks to Brainstorm Mobile Solutions for supporting the development of these. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + var Line = function(x1, y1, x2, y2) { + + this.m = (y2 - y1) / (x2 - x1); + this.b = -1 * ((this.m * x1) - y1); + + this.rectIntersect = function(x,y,w,h) { + var results = []; + + // try top face + // the equation of the top face is y = (0 * x) + b; y = b. + var xInt = (y - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try right face + var yInt = (this.m * (x + w)) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + // bottom face + var xInt = ((y + h) - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try left face + var yInt = (this.m * x) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + if (results.length == 2) { + var midx = (results[0][0] + results[1][0]) / 2, midy = (results[0][1] + results[1][1]) / 2; + results.push([ midx,midy ]); + // now calculate the segment inside the rectangle where the midpoint lies. + var xseg = midx <= x + (w / 2) ? -1 : 1, + yseg = midy <= y + (h / 2) ? -1 : 1; + results.push([xseg, yseg]); + return results; + } + + return null; + + }; + }, + _segment = function(x1, y1, x2, y2) { + if (x1 <= x2 && y2 <= y1) return 1; + else if (x1 <= x2 && y1 <= y2) return 2; + else if (x2 <= x1 && y2 >= y1) return 3; + return 4; + }, + + // the control point we will use depends on the faces to which each end of the connection is assigned, specifically whether or not the + // two faces are parallel or perpendicular. if they are parallel then the control point lies on the midpoint of the axis in which they + // are parellel and varies only in the other axis; this variation is proportional to the distance that the anchor points lie from the + // center of that face. if the two faces are perpendicular then the control point is at some distance from both the midpoints; the amount and + // direction are dependent on the orientation of the two elements. 'seg', passed in to this method, tells you which segment the target element + // lies in with respect to the source: 1 is top right, 2 is bottom right, 3 is bottom left, 4 is top left. + // + // sourcePos and targetPos are arrays of info about where on the source and target each anchor is located. their contents are: + // + // 0 - absolute x + // 1 - absolute y + // 2 - proportional x in element (0 is left edge, 1 is right edge) + // 3 - proportional y in element (0 is top edge, 1 is bottom edge) + // + _findControlPoint = function(midx, midy, segment, sourceEdge, targetEdge, dx, dy, distance, proximityLimit) { + + // TODO (maybe) + // - if anchor pos is 0.5, make the control point take into account the relative position of the elements. + if (distance <= proximityLimit) return [midx, midy]; + + if (segment == 1) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 2) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 3) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 4) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + }; + + /* + Function: StateMachine constructor + + Allowed parameters: + curviness - measure of how "curvy" the connectors will be. this is translated as the distance that the + Bezier curve's control point is from the midpoint of the straight line connecting the two + endpoints, and does not mean that the connector is this wide. The Bezier curve never reaches + its control points; they act as gravitational masses. defaults to 10. + margin - distance from element to start and end connectors, in pixels. defaults to 5. + proximityLimit - sets the distance beneath which the elements are consider too close together to bother with fancy + curves. by default this is 80 pixels. + loopbackRadius - the radius of a loopback connector. optional; defaults to 25. + showLoopback - If set to false this tells the connector that it is ok to paint connections whose source and target is the same element with a connector running through the element. The default value for this is true; the connector always makes a loopback connection loop around the element rather than passing through it. + */ + jsPlumb.Connectors.StateMachine = function(params) { + var self = this, + currentPoints = null, + _sx, _sy, _tx, _ty, _controlPoint = [], + curviness = params.curviness || 10, + margin = params.margin || 5, + proximityLimit = params.proximityLimit || 80, + clockwise = params.orientation && params.orientation == "clockwise", + loopbackRadius = params.loopbackRadius || 25, + isLoopback = false, + showLoopback = params.showLoopback !== false; + + this.type = "StateMachine"; + params = params || {}; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *= 1.9; + //ensure at least one pixel width + lineWidth = lineWidth || 1; + var x = Math.min(sourcePos[0], targetPos[0]) - xo, + y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (!showLoopback || (sourceEndpoint.elementId != targetEndpoint.elementId)) { + + isLoopback = false; + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + + // now adjust for the margin + if (sourcePos[2] == 0) _sx -= margin; + if (sourcePos[2] == 1) _sx += margin; + if (sourcePos[3] == 0) _sy -= margin; + if (sourcePos[3] == 1) _sy += margin; + if (targetPos[2] == 0) _tx -= margin; + if (targetPos[2] == 1) _tx += margin; + if (targetPos[3] == 0) _ty -= margin; + if (targetPos[3] == 1) _ty += margin; + + // + // these connectors are quadratic bezier curves, having a single control point. if both anchors + // are located at 0.5 on their respective faces, the control point is set to the midpoint and you + // get a straight line. this is also the case if the two anchors are within 'proximityLimit', since + // it seems to make good aesthetic sense to do that. outside of that, the control point is positioned + // at 'curviness' pixels away along the normal to the straight line connecting the two anchors. + // + // there may be two improvements to this. firstly, we might actually support the notion of avoiding nodes + // in the UI, or at least making a good effort at doing so. if a connection would pass underneath some node, + // for example, we might increase the distance the control point is away from the midpoint in a bid to + // steer it around that node. this will work within limits, but i think those limits would also be the likely + // limits for, once again, aesthetic good sense in the layout of a chart using these connectors. + // + // the second possible change is actually two possible changes: firstly, it is possible we should gradually + // decrease the 'curviness' as the distance between the anchors decreases; start tailing it off to 0 at some + // point (which should be configurable). secondly, we might slightly increase the 'curviness' for connectors + // with respect to how far their anchor is from the center of its respective face. this could either look cool, + // or stupid, and may indeed work only in a way that is so subtle as to have been a waste of time. + // + + var _midx = (_sx + _tx) / 2, _midy = (_sy + _ty) / 2, + m2 = (-1 * _midx) / _midy, theta2 = Math.atan(m2), + dy = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.sin(theta2)), + dx = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.cos(theta2)), + segment = _segment(_sx, _sy, _tx, _ty), + distance = Math.sqrt(Math.pow(_tx - _sx, 2) + Math.pow(_ty - _sy, 2)); + + // calculate the control point. this code will be where we'll put in a rudimentary element avoidance scheme; it + // will work by extending the control point to force the curve to be, um, curvier. + _controlPoint = _findControlPoint(_midx, + _midy, + segment, + sourcePos, + targetPos, + curviness, curviness, + distance, + proximityLimit); + + + var requiredWidth = Math.max(Math.abs(_controlPoint[0] - _sx) * 3, Math.abs(_controlPoint[0] - _tx) * 3, Math.abs(_tx-_sx), 2 * lineWidth, minWidth), + requiredHeight = Math.max(Math.abs(_controlPoint[1] - _sy) * 3, Math.abs(_controlPoint[1] - _ty) * 3, Math.abs(_ty-_sy), 2 * lineWidth, minWidth); + + if (w < requiredWidth) { + var dw = requiredWidth - w; + x -= (dw / 2); + _sx += (dw / 2); + _tx += (dw / 2); + w = requiredWidth; + _controlPoint[0] += (dw / 2); + } + + if (h < requiredHeight) { + var dh = requiredHeight - h; + y -= (dh / 2); + _sy += (dh / 2); + _ty += (dh / 2); + h = requiredHeight; + _controlPoint[1] += (dh / 2); + } + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty, _controlPoint[0], _controlPoint[1] ]; + } + else { + isLoopback = true; + // a loopback connector. draw an arc from one anchor to the other. + // i guess we'll do this the same way as the others. just the control point will be a fair distance away. + var x1 = sourcePos[0], x2 = sourcePos[0], y1 = sourcePos[1] - margin, y2 = sourcePos[1] - margin, + cx = x1, cy = y1 - loopbackRadius; + + // canvas sizing stuff, to ensure the whole painted area is visible. + w = ((2 * lineWidth) + (4 * loopbackRadius)), h = ((2 * lineWidth) + (4 * loopbackRadius)); + x = cx - loopbackRadius - lineWidth - loopbackRadius, y = cy - loopbackRadius - lineWidth - loopbackRadius; + currentPoints = [ x, y, w, h, cx-x, cy-y, loopbackRadius, clockwise, x1-x, y1-y, x2-x, y2-y]; + } + + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_tx, y:_ty }, + { x:_controlPoint[0], y:_controlPoint[1] }, + { x:_controlPoint[0] + 1, y:_controlPoint[1] + 1}, + { x:_sx, y:_sy } + ]; + }; + + var _translateLocation = function(curve, location, absolute) { + if (absolute) + location = jsBezier.locationAlongCurveFrom(curve, location > 0 ? 0 : 1, location); + + return location; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location, absolute) { + if (isLoopback) { + if (absolute) { + var circumference = Math.PI * 2 * loopbackRadius; + location = location / circumference; + } + + if (location > 0 && location < 1) location = 1 - location; + +// current points are [ x, y, width, height, center x, center y, radius, clockwise, startx, starty, endx, endy ] + // so the path length is the circumference of the circle + //var len = 2 * Math.PI * currentPoints[6], + // map 'location' to an angle. 0 is PI/2 when the connector is on the top face; if we + // support other faces it will have to be calculated for each one. 1 is also PI/2. + // 0.5 is -PI/2. + var startAngle = (location * 2 * Math.PI) + (Math.PI / 2), + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + + } + else { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.pointOnCurve(c, location); + } + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location, absolute) { + if (isLoopback) { + // todo if absolute, location is a proportion of circumference + if (absolute) { + var circumference = Math.PI * 2 * loopbackRadius; + location = location / circumference; + } + + return Math.atan(location * 2 * Math.PI); + } + else { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.gradientAtPoint(c, location); + } + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance, absolute) { + if (isLoopback) { + + if (absolute) { + var circumference = Math.PI * 2 * loopbackRadius; + location = location / circumference; + } + + if (location > 0 && location < 1) location = 1 - location; + + var circumference = 2 * Math.PI * currentPoints[6], + arcSpan = distance / circumference * 2 * Math.PI, + startAngle = (location * 2 * Math.PI) - arcSpan + (Math.PI / 2), + + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + } + else { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.pointAlongCurveFrom(c, location, distance); + } + }; + + }; + + /* + * Canvas state machine renderer. + */ + jsPlumb.Connectors.canvas.StateMachine = function(params) { + params = params || {}; + var self = this, drawGuideline = params.drawGuideline || true, avoidSelector = params.avoidSelector; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.CanvasConnector.apply(this, arguments); + + + this._paint = function(dimensions) { + + if (dimensions.length == 10) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[8], dimensions[9], dimensions[6], dimensions[7]); + self.ctx.stroke(); + } + else { + // a loopback connector + self.ctx.save(); + self.ctx.beginPath(); + var startAngle = 0, // Starting point on circle + endAngle = 2 * Math.PI, // End point on circle + clockwise = dimensions[7]; // clockwise or anticlockwise + self.ctx.arc(dimensions[4],dimensions[5],dimensions[6],0, endAngle, clockwise); + self.ctx.stroke(); + self.ctx.closePath(); + self.ctx.restore(); + } + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + /* + * SVG State Machine renderer + */ + jsPlumb.Connectors.svg.StateMachine = function() { + var self = this; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.SvgConnector.apply(this, arguments); + this.getPath = function(d) { + + if (d.length == 10) + return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[8] + " " + d[9] + " " + d[6] + " " + d[7]; + else { + // loopback + return "M" + (d[8] + 4) + " " + d[9] + " A " + d[6] + " " + d[6] + " 0 1,0 " + (d[8]-4) + " " + d[9]; + } + }; + }; + + /* + * VML state machine renderer + */ + jsPlumb.Connectors.vml.StateMachine = function() { + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.VmlConnector.apply(this, arguments); + var _conv = jsPlumb.vml.convertValue; + this.getPath = function(d) { + if (d.length == 10) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + } + else { + // loopback + var left = _conv(d[8] - d[6]), + top = _conv(d[9] - (2 * d[6])), + right = left + _conv(2 * d[6]), + bottom = top + _conv(2 * d[6]), + posString = left + "," + top + "," + right + "," + bottom; + + var o = "ar " + posString + "," + _conv(d[8]) + "," + + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + " e"; + + return o; + } + }; + }; + +})(); + +/* + // now for a rudimentary avoidance scheme. TODO: how to set this in a cross-library way? + // if (avoidSelector) { + // var testLine = new Line(sourcePos[0] + _sx,sourcePos[1] + _sy,sourcePos[0] + _tx,sourcePos[1] + _ty); + // var sel = jsPlumb.getSelector(avoidSelector); + // for (var i = 0; i < sel.length; i++) { + // var id = jsPlumb.getId(sel[i]); + // if (id != sourceEndpoint.elementId && id != targetEndpoint.elementId) { + // o = jsPlumb.getOffset(id), s = jsPlumb.getSize(id); +// +// if (o && s) { +// var collision = testLine.rectIntersect(o.left,o.top,s[0],s[1]); +// if (collision) { + // set the control point to be a certain distance from the midpoint of the two points that + // the line crosses on the rectangle. + // TODO where will this 75 number come from? + // _controlX = collision[2][0] + (75 * collision[3][0]); + // / _controlY = collision[2][1] + (75 * collision[3][1]); +// } +// } + // } + // } + //} + */ \ No newline at end of file diff --git a/archive/1.3.11/jsPlumb-defaults-1.3.11-RC1.js b/archive/1.3.11/jsPlumb-defaults-1.3.11-RC1.js new file mode 100644 index 000000000..b5dfc0d3a --- /dev/null +++ b/archive/1.3.11/jsPlumb-defaults-1.3.11-RC1.js @@ -0,0 +1,1188 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.11 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the default Connectors, Endpoint and Overlay definitions. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +(function() { + + /** + * + * Helper class to consume unused mouse events by components that are DOM elements and + * are used by all of the different rendering modes. + * + */ + jsPlumb.DOMElementComponent = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + // when render mode is canvas, these functions may be called by the canvas mouse handler. + // this component is safe to pipe this stuff to /dev/null. + this.mousemove = + this.dblclick = + this.click = + this.mousedown = + this.mouseup = function(e) { }; + }; + + /** + * Class: Connectors.Straight + * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters. + */ + jsPlumb.Connectors.Straight = function() { + this.type = "Straight"; + var self = this, + currentPoints = null, + _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty, _segment, _length; + + /** + * Computes the new size and position of the canvas. + */ + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + _dx = _tx - _sx, _dy = _ty - _sy; + //_m = _dy / _dx, _m2 = -1 / _m; + _m = jsPlumbUtil.gradient({x:_sx, y:_sy}, {x:_tx, y:_ty}), _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + //_segment = jsPlumbUtil.segment({x:_sx, y:_sy}, {x:_tx, y:_ty}); + _length = Math.sqrt((_dx * _dx) + (_dy * _dy)); + + return currentPoints; + }; + + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location, absolute) { + if (location == 0 && !absolute) + return { x:_sx, y:_sy }; + else if (location == 1 && !absolute) + return { x:_tx, y:_ty }; + else { + var l = absolute ? location > 0 ? location : _length + location : location * _length; + return jsPlumbUtil.pointOnLine({x:_sx, y:_sy}, {x:_tx, y:_ty}, l); + } + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { + return _m; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + * this hands off to jsPlumbUtil to do the maths, supplying two points and the distance. + */ + this.pointAlongPathFrom = function(location, distance, absolute) { + var p = self.pointOnPath(location, absolute), + farAwayPoint = location == 1 ? { + x:_sx + ((_tx - _sx) * 10), + y:_sy + ((_sy - _ty) * 10) + } : {x:_tx, y:_ty }; + + return jsPlumbUtil.pointOnLine(p, farAwayPoint, distance); + }; + }; + + + /** + * Class:Connectors.Bezier + * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's + * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below. + */ + /** + * Function:Constructor + * + * Parameters: + * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line. + * Optional; defaults to 150. + * stub - optional value for a distance to travel from the connector's endpoint before beginning the Bezier curve. defaults to 0. + * + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + params = params || {}; + this.majorAnchor = params.curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + this.type = "Bezier"; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(sourceEndpoint), + too = targetAnchor.getOrientation(targetEndpoint), + perpendicular = soo[0] != too[0] || soo[1] == too[1], + p = [], + ma = self.majorAnchor, mi = self.minorAnchor; + + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h, _sStubX, _sStubY, _tStubX, _tStubY; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetEndpoint, sourceEndpoint, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx), minx2 = Math.min(_CP[0], _CP2[0]), minx = Math.min(minx1,minx2), + maxx1 = Math.max(_sx,_tx), maxx2 = Math.max(_CP[0], _CP2[0]), maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty), miny2 = Math.min(_CP[1], _CP2[1]), miny = Math.min(miny1,miny2), + maxy1 = Math.max(_sy,_ty), maxy2 = Math.max(_CP[1], _CP2[1]), maxy = Math.max(maxy1,maxy2); + + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, + _sx, _sy, _tx, _ty, + _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + var _translateLocation = function(curve, location, absolute) { + if (absolute) + location = jsBezier.locationAlongCurveFrom(curve, location > 0 ? 0 : 1, location); + + return location; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location, absolute) { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.pointOnCurve(c, location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location, absolute) { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.gradientAtPoint(c, location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance, absolute) { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.pointAlongCurveFrom(c, location, distance); + }; + }; + + + /** + * Class: Connectors.Flowchart + * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments. + */ + /** + * Function: Constructor + * + * Parameters: + * stub - minimum length for the stub at each end of the connector. This can be an integer, giving a value for both ends of the connections, + * or an array of two integers, giving separate values for each end. The default is an integer with value 30 (pixels). + * gap - gap to leave between the end of the connector and the element on which the endpoint resides. if you make this larger than stub then you will see some odd looking behaviour. defaults to 0 pixels. + */ + jsPlumb.Connectors.Flowchart = function(params) { + this.type = "Flowchart"; + params = params || {}; + var self = this, + stub = params.stub || params.minStubLength /* bwds compat. */ || 30, + sourceStub = jsPlumbUtil.isArray(stub) ? stub[0] : stub, + targetStub = jsPlumbUtil.isArray(stub) ? stub[1] : stub, + gap = params.gap || 0, + segments = [], + totalLength = 0, + segmentProportions = [], + segmentProportionalLengths = [], + points = [], + swapX, swapY, + maxX = -Infinity, maxY = -Infinity, + minX = Infinity, minY = Infinity, + /** + * recalculates the points at which the segments begin and end, proportional to the total length travelled + * by all the segments that constitute the connector. we use this to help with pointOnPath calculations. + */ + updateSegmentProportions = function(startX, startY, endX, endY) { + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segments[i][5] / totalLength; + segmentProportions[i] = [curLoc, (curLoc += (segments[i][5] / totalLength)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0], + ly = segments.length == 0 ? sy : segments[segments.length - 1][1], + m = x == lx ? Infinity : 0, + l = Math.abs(x == lx ? y - ly : x - lx); + segments.push([x, y, lx, ly, m, l]); + totalLength += l; + + maxX = Math.max(maxX, x); + maxY = Math.max(maxY, y); + minX = Math.min(minX, x); + minY = Math.min(minY, y); + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment + * that contains the point which is 'location' distance along the entire path, where + * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths + * are made up of a list of segments, each of which contributes some fraction to + * the total length. + * From 1.3.10 this also supports the 'absolute' property, which lets us specify a location + * as the absolute distance in pixels, rather than a proportion of the total path. + */ + findSegmentForLocation = function(location, absolute) { + if (absolute) { + location = location > 0 ? location / totalLength : (totalLength + location) / totalLength; + } + + var idx = segmentProportions.length - 1, inSegmentProportion = 1; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, + sourceAnchor, targetAnchor, lineWidth, minWidth, sourceInfo, targetInfo) { + + segments = []; + segmentProportions = []; + totalLength = 0; + segmentProportionalLengths = []; + maxX = maxY = -Infinity; + minX = minY = Infinity; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + sourceOffx = (lw / 2) + (sourceStub + targetStub), + targetOffx = (lw / 2) + (targetStub + sourceStub), + sourceOffy = (lw / 2) + (sourceStub + targetStub), + targetOffy = (lw / 2) + (targetStub + sourceStub), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(sourceEndpoint), + to = targetAnchor.orientation || targetAnchor.getOrientation(targetEndpoint), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + sourceOffx + targetOffx, + h = Math.abs(targetPos[1] - sourcePos[1]) + sourceOffy + targetOffy; + + // if either anchor does not have an orientation set, we derive one from their relative + // positions. we fix the axis to be the one in which the two elements are further apart, and + // point each anchor at the other element. this is also used when dragging a new connection. + if (so[0] == 0 && so[1] == 0 || to[0] == 0 && to[1] == 0) { + var index = w > h ? 0 : 1, oIndex = [1,0][index]; + so = []; to = []; + so[index] = sourcePos[index] > targetPos[index] ? -1 : 1; + to[index] = sourcePos[index] > targetPos[index] ? 1 : -1; + so[oIndex] = 0; + to[oIndex] = 0; + } + + if (w < minWidth) { + sourceOffx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + sourceOffy += (minWidth - h) / 2; + h = minWidth; + } + + var sx = swapX ? (w - targetOffx) +( gap * so[0]) : sourceOffx + (gap * so[0]), + sy = swapY ? (h - targetOffy) + (gap * so[1]) : sourceOffy + (gap * so[1]), + tx = swapX ? sourceOffx + (gap * to[0]) : (w - targetOffx) + (gap * to[0]), + ty = swapY ? sourceOffy + (gap * to[1]) : (h - targetOffy) + (gap * to[1]), + startStubX = sx + (so[0] * sourceStub), + startStubY = sy + (so[1] * sourceStub), + endStubX = tx + (to[0] * targetStub), + endStubY = ty + (to[1] * targetStub), + isXGreaterThanStubTimes2 = Math.abs(sx - tx) > (sourceStub + targetStub), + isYGreaterThanStubTimes2 = Math.abs(sy - ty) > (sourceStub + targetStub), + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2), + oProduct = ((so[0] * to[0]) + (so[1] * to[1])), + opposite = oProduct == -1, + perpendicular = oProduct == 0, + orthogonal = oProduct == 1; + + x -= sourceOffx; y -= sourceOffy; + points = [x, y, w, h, sx, sy, tx, ty]; + var extraPoints = []; + + var sourceAxis = so[0] == 0 ? "y" : "x", + anchorOrientation = opposite ? "opposite" : orthogonal ? "orthogonal" : "perpendicular", + segment = jsPlumbUtil.segment([sx, sy], [tx, ty]), + flipSourceSegments = so[sourceAxis == "x" ? 0 : 1] == -1, + flipSegments = { + "x":[null, 4, 3, 2, 1], + "y":[null, 2, 1, 4, 3] + } + + if (flipSourceSegments) + segment = flipSegments[sourceAxis][segment]; + + /*if (segment == 1 || segment == 2) { + if (sourceAxis == "x") + addSegment(Math.max(startStubX, endStubX), startStubY, sx, sy, tx, ty); + else + addSegment(startStubX, Math.max(startStubY, endStubY), sx, sy, tx, ty); + } + else { + if (sourceAxis == "x") + addSegment(Math.min(startStubX, endStubX), startStubY, sx, sy, tx, ty); + else + addSegment(startStubX, Math.min(startStubY, endStubY), sx, sy, tx, ty); + }*/ + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + var findClearedLine = function(start, mult, anchorPos, dimension) { + return start + (mult * (( 1 - anchorPos) * dimension) + Math.max(sourceStub, targetStub)); + //mx = so[0] == 0 ? startStubX + ((1 - sourceAnchor.x) * sourceInfo.width) + stub : startStubX, + }, + + lineCalculators = { + oppositex : function() { + if (sourceEndpoint.elementId == targetEndpoint.elementId) { + var _y = startStubY + ((1 - sourceAnchor.y) * sourceInfo.height) + Math.max(sourceStub, targetStub); + return [ [ startStubX, _y ], [ endStubX, _y ]]; + } + else if (isXGreaterThanStubTimes2 && (segment == 1 || segment == 2)) { + return [[ midx, sy ], [ midx, ty ]]; + } + else { + return [[ startStubX, midy ], [endStubX, midy ]]; + } + }, + orthogonalx : function() { + if (segment == 1 || segment == 2) { + return [ [ endStubX, startStubY ]]; + } + else { + return [ [ startStubX, endStubY ]]; + } + }, + perpendicularx : function() { + var my = (ty + sy) / 2; + if ((segment == 1 && to[1] == 1) || (segment == 2 && to[1] == -1)) { + if (Math.abs(tx - sx) > Math.max(sourceStub, targetStub)) + return [ [endStubX, startStubY ]]; + else + return [ [startStubX, startStubY ], [ startStubX, my ], [ endStubX, my ]]; + } + else if ((segment == 3 && to[1] == -1) || (segment == 4 && to[1] == 1)) { + return [ [ startStubX, my ], [ endStubX, my ]]; + } + else if ((segment == 3 && to[1] == 1) || (segment == 4 && to[1] == -1)) { + return [ [ startStubX, endStubY ]]; + } + else if ((segment == 1 && to[1] == -1) || (segment == 2 && to[1] == 1)) { + if (Math.abs(tx - sx) > Math.max(sourceStub, targetStub)) + return [ [ midx, startStubY ], [ midx, endStubY ]]; + else + return [ [ startStubX, endStubY ]]; + } + }, + oppositey : function() { + if (sourceEndpoint.elementId == targetEndpoint.elementId) { + var _x = startStubX + ((1 - sourceAnchor.x) * sourceInfo.width) + Math.max(sourceStub, targetStub); + return [ [ _x, startStubY ], [ _x, endStubY ]]; + } + else if (isYGreaterThanStubTimes2 && (segment == 2 || segment == 3)) { + return [[ sx, midy ], [ tx, midy ]]; + } + else { + return [[ midx, startStubY ], [midx, endStubY ]]; + } + }, + orthogonaly : function() { + if (segment == 2 || segment == 3) { + return [ [ startStubX, endStubY ]]; + } + else { + return [ [ endStubX, startStubY ]]; + } + }, + perpendiculary : function() { + var mx = (tx + sx) / 2; + if ((segment == 2 && to[0] == -1) || (segment == 3 && to[0] == 1)) { + if (Math.abs(tx - sx) > Math.max(sourceStub, targetStub)) + return [ [startStubX, endStubY ]]; + else + return [ [startStubX, midy ], [ endStubX, midy ]]; + } + else if ((segment == 1 && to[0] == -1) || (segment == 4 && to[0] == 1)) { + var mx = (tx + sx) / 2; + return [ [ mx, startStubY ], [ mx, endStubY ]]; + } + else if ((segment == 1 && to[0] == 1) || (segment == 4 && to[0] == -1)) { + return [ [ endStubX, startStubY ]]; + } + else if ((segment == 2 && to[0] == 1) || (segment == 3 && to[0] == -1)) { + if (Math.abs(ty - sy) > Math.max(sourceStub, targetStub)) + return [ [ startStubX, midy ], [ endStubX, midy ]]; + else + return [ [ endStubX, startStubY ]]; + } + } + }; + + var p = lineCalculators[anchorOrientation + sourceAxis](); + if (p) { + for (var i = 0; i < p.length; i++) { + addSegment(p[i][0], p[i][1], sx, sy, tx, ty); + } + } + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentProportions(sx, sy, tx, ty); + + // adjust the max values of the canvas if we have a value that is larger than what we previously set. + // + if (maxY > points[3]) points[3] = maxY + (lineWidth * 2); + if (maxX > points[2]) points[2] = maxX + (lineWidth * 2); + + return points; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location, absolute) { + return self.pointAlongPathFrom(location, 0, absolute); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location, absolute) { + return segments[findSegmentForLocation(location, absolute)["index"]][4]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance, absolute) { + var s = findSegmentForLocation(location, absolute), seg = s.segment, p = s.proportion, sl = segments[s.index][5], m = segments[s.index][4]; + var e = { + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + return e; + }; + }; + + // ********************************* END OF CONNECTOR TYPES ******************************************************************* + + // ********************************* ENDPOINT TYPES ******************************************************************* + + /** + * Class: Endpoints.Dot + * A round endpoint, with default radius 10 pixels. + */ + + /** + * Function: Constructor + * + * Parameters: + * + * radius - radius of the endpoint. defaults to 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + this.type = "Dot"; + var self = this; + params = params || {}; + this.radius = params.radius || 10; + this.defaultOffset = 0.5 * this.radius; + this.defaultInnerRadius = this.radius / 3; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var r = endpointStyle.radius || self.radius, + x = anchorPoint[0] - r, + y = anchorPoint[1] - r; + return [ x, y, r * 2, r * 2, r ]; + }; + }; + + /** + * Class: Endpoints.Rectangle + * A Rectangular Endpoint, with default size 20x20. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the endpoint. defaults to 20 pixels. + * height - height of the endpoint. defaults to 20 pixels. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + this.type = "Rectangle"; + var self = this; + params = params || {}; + this.width = params.width || 20; + this.height = params.height || 20; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height]; + }; + }; + + + var DOMElementEndpoint = function(params) { + jsPlumb.DOMElementComponent.apply(this, arguments); + var self = this; + + var displayElements = [ ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + }; + /** + * Class: Endpoints.Image + * Draws an image as the Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * src - location of the image to use. + */ + jsPlumb.Endpoints.Image = function(params) { + + this.type = "Image"; + DOMElementEndpoint.apply(this, arguments); + + var self = this, + initialized = false, + widthToUse = params.width, + heightToUse = params.height, + _onload = null, + _endpoint = params.endpoint; + + this.img = new Image(); + self.ready = false; + + this.img.onload = function() { + self.ready = true; + widthToUse = widthToUse || self.img.width; + heightToUse = heightToUse || self.img.height; + if (_onload) { + _onload(self); + } + }; + + /* + Function: setImage + Sets the Image to use in this Endpoint. + + Parameters: + img - may be a URL or an Image object + onload - optional; a callback to execute once the image has loaded. + */ + _endpoint.setImage = function(img, onload) { + var s = img.constructor == String ? img : img.src; + _onload = onload; + self.img.src = img; + + if (self.canvas != null) + self.canvas.setAttribute("src", img); + }; + + _endpoint.setImage(params.src || params.url, params.onload); + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + self.anchorPoint = anchorPoint; + if (self.ready) return [anchorPoint[0] - widthToUse / 2, anchorPoint[1] - heightToUse / 2, + widthToUse, heightToUse]; + else return [0,0,0,0]; + }; + + self.canvas = document.createElement("img"), initialized = false; + self.canvas.style["margin"] = 0; + self.canvas.style["padding"] = 0; + self.canvas.style["outline"] = 0; + self.canvas.style["position"] = "absolute"; + var clazz = params.cssClass ? " " + params.cssClass : ""; + self.canvas.className = jsPlumb.endpointClass + clazz; + if (widthToUse) self.canvas.setAttribute("width", widthToUse); + if (heightToUse) self.canvas.setAttribute("height", heightToUse); + jsPlumb.appendElement(self.canvas, params.parent); + self.attachListeners(self.canvas, self); + + var actuallyPaint = function(d, style, anchor) { + if (!initialized) { + self.canvas.setAttribute("src", self.img.src); + self.appendDisplayElement(self.canvas); + initialized = true; + } + var x = self.anchorPoint[0] - (widthToUse / 2), + y = self.anchorPoint[1] - (heightToUse / 2); + jsPlumb.sizeCanvas(self.canvas, x, y, widthToUse, heightToUse); + }; + + this.paint = function(d, style, anchor) { + if (self.ready) { + actuallyPaint(d, style, anchor); + } + else { + window.setTimeout(function() { + self.paint(d, style, anchor); + }, 200); + } + }; + }; + + /** + * Class: Endpoints.Blank + * An Endpoint that paints nothing (visible) on the screen. Supports cssClass and hoverClass parameters like all Endpoints. + */ + jsPlumb.Endpoints.Blank = function(params) { + var self = this; + this.type = "Blank"; + DOMElementEndpoint.apply(this, arguments); + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + return [anchorPoint[0], anchorPoint[1],10,0]; + }; + + self.canvas = document.createElement("div"); + self.canvas.style.display = "block"; + self.canvas.style.width = "1px"; + self.canvas.style.height = "1px"; + self.canvas.style.background = "transparent"; + self.canvas.style.position = "absolute"; + self.canvas.className = self._jsPlumb.endpointClass; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + }; + }; + + /** + * Class: Endpoints.Triangle + * A triangular Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the triangle's base. defaults to 55 pixels. + * height - height of the triangle from base to apex. defaults to 55 pixels. + */ + jsPlumb.Endpoints.Triangle = function(params) { + this.type = "Triangle"; + params = params || { }; + params.width = params.width || 55; + params.height = params.height || 55; + this.width = params.width; + this.height = params.height; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height ]; + }; + }; +// ********************************* END OF ENDPOINT TYPES ******************************************************************* + + +// ********************************* OVERLAY DEFINITIONS *********************************************************************** + + var AbstractOverlay = function(params) { + var visible = true, self = this; + this.isAppendedAtTopLevel = true; + this.component = params.component; + this.loc = params.location == null ? 0.5 : params.location; + this.endpointLoc = params.endpointLocation == null ? [ 0.5, 0.5] : params.endpointLocation; + this.setVisible = function(val) { + visible = val; + self.component.repaint(); + }; + this.isVisible = function() { return visible; }; + this.hide = function() { self.setVisible(false); }; + this.show = function() { self.setVisible(true); }; + + this.incrementLocation = function(amount) { + self.loc += amount; + self.component.repaint(); + }; + this.setLocation = function(l) { + self.loc = l; + self.component.repaint(); + }; + this.getLocation = function() { + return self.loc; + }; + }; + + + /** + * Class: Overlays.Arrow + * + * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length + * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction + * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line + * across the tail. + */ + /** + * Function: Constructor + * + * Parameters: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + this.type = "Arrow"; + AbstractOverlay.apply(this, arguments); + this.isAppendedAtTopLevel = false; + params = params || {}; + var self = this; + + this.length = params.length || 20; + this.width = params.width || 20; + this.id = params.id; + var direction = (params.direction || 1) < 0 ? -1 : 1, + paintStyle = params.paintStyle || { lineWidth:1 }, + // how far along the arrow the lines folding back in come to. default is 62.3%. + foldback = params.foldback || 0.623; + + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.cleanup = function() { }; // nothing to clean up for Arrows + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var hxy, mid, txy, tail, cxy; + if (connector.pointAlongPathFrom) { + + if (jsPlumbUtil.isString(self.loc) || self.loc > 1 || self.loc < 0) { + var l = parseInt(self.loc); + hxy = connector.pointAlongPathFrom(l, direction * self.length / 2, true), + mid = connector.pointOnPath(l, true), + txy = jsPlumbUtil.pointOnLine(hxy, mid, self.length); + } + else if (self.loc == 1) { + hxy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, -1); + txy = jsPlumbUtil.pointOnLine(hxy, mid, self.length); + } + else if (self.loc == 0) { + txy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, 1); + hxy = jsPlumbUtil.pointOnLine(txy, mid, self.length); + } + else { + hxy = connector.pointAlongPathFrom(self.loc, direction * self.length / 2), + mid = connector.pointOnPath(self.loc), + txy = jsPlumbUtil.pointOnLine(hxy, mid, self.length); + } + + tail = jsPlumbUtil.perpendicularLineTo(hxy, txy, self.width); + cxy = jsPlumbUtil.pointOnLine(hxy, txy, foldback * self.length); + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x), + maxx = Math.max(hxy.x, tail[0].x, tail[1].x), + miny = Math.min(hxy.y, tail[0].y, tail[1].y), + maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + var d = { hxy:hxy, tail:tail, cxy:cxy }, + strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle, + fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle, + lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth; + + self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions); + + return [ minx, maxx, miny, maxy]; + } + else return [0,0,0,0]; + }; + }; + + /** + * Class: Overlays.PlainArrow + * + * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "PlainArrow"; + }; + + /** + * Class: Overlays.Diamond + * + * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40, + p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "Diamond"; + }; + + + + /** + * Class: Overlays.Label + * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb + * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter + * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it + * puts on the Label's 'style' attribute, so the end result is the same. + */ + /** + * Function: Constructor + * + * Parameters: + * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes + * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle. + * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * + */ + jsPlumb.Overlays.Label = function(params) { + this.type = "Label"; + jsPlumb.DOMElementComponent.apply(this, arguments); + AbstractOverlay.apply(this, arguments); + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.id = params.id; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var label = params.label || "", + self = this, + initialised = false, + div = document.createElement("div"), + labelText = null; + div.style["position"] = "absolute"; + + var clazz = params["_jsPlumb"].overlayClass + " " + + (self.labelStyle.cssClass ? self.labelStyle.cssClass : + params.cssClass ? params.cssClass : ""); + + div.className = clazz; + + jsPlumb.appendElement(div, params.component.parent); + jsPlumb.getId(div); + self.attachListeners(div, self); + self.canvas = div; + + //override setVisible + var osv = self.setVisible; + self.setVisible = function(state) { + osv(state); // call superclass + div.style.display = state ? "block" : "none"; + }; + + this.getElement = function() { + return div; + }; + + this.cleanup = function() { + if (div != null) jsPlumb.CurrentLibrary.removeElement(div); + }; + + /* + * Function: setLabel + * sets the label's, um, label. you would think i'd call this function + * 'setText', but you can pass either a Function or a String to this, so + * it makes more sense as 'setLabel'. This uses innerHTML on the label div, so keep + * that in mind if you need escaped HTML. + */ + this.setLabel = function(l) { + label = l; + labelText = null; + self.component.repaint(); + }; + + this.getLabel = function() { + return label; + }; + + this.paint = function(component, d, componentDimensions) { + if (!initialised) { + component.appendDisplayElement(div); + self.attachListeners(div, component); + initialised = true; + } + div.style.left = (componentDimensions[0] + d.minx) + "px"; + div.style.top = (componentDimensions[1] + d.miny) + "px"; + }; + + this.getTextDimensions = function() { + if (typeof label == "function") { + var lt = label(self); + div.innerHTML = lt.replace(/\r\n/g, "
"); + } + else { + if (labelText == null) { + labelText = label; + div.innerHTML = labelText.replace(/\r\n/g, "
"); + } + } + var de = jsPlumb.CurrentLibrary.getElementObject(div), + s = jsPlumb.CurrentLibrary.getSize(de); + return {width:s[0], height:s[1]}; + }; + + this.computeMaxSize = function(connector) { + var td = self.getTextDimensions(connector); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + + this.draw = function(component, currentConnectionPaintStyle, componentDimensions) { + var td = self.getTextDimensions(component); + if (td.width != null) { + var cxy = {x:0,y:0}; + if (component.pointOnPath) { + var loc = self.loc, absolute = false; + if (jsPlumbUtil.isString(self.loc) || self.loc < 0 || self.loc > 1) { + loc = parseInt(self.loc); + absolute = true; + } + cxy = component.pointOnPath(loc, absolute); // a connection + } + else { + var locToUse = self.loc.constructor == Array ? self.loc : self.endpointLoc; + cxy = { x:locToUse[0] * componentDimensions[2], + y:locToUse[1] * componentDimensions[3] }; + } + + minx = cxy.x - (td.width / 2), + miny = cxy.y - (td.height / 2); + + self.paint(component, { + minx:minx, + miny:miny, + td:td, + cxy:cxy + }, componentDimensions); + + return [minx, minx+td.width, miny, miny+td.height]; + } + else return [0,0,0,0]; + }; + + this.reattachListeners = function(connector) { + if (div) { + self.reattachListenersForElement(div, self, connector); + } + }; + }; + + // this is really just a test overlay, so its undocumented and doesnt take any parameters. but i was loth to delete it. + jsPlumb.Overlays.GuideLines = function() { + var self = this; + self.length = 50; + self.lineWidth = 5; + this.type = "GuideLines"; + AbstractOverlay.apply(this, arguments); + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var head = connector.pointAlongPathFrom(self.loc, self.length / 2), + mid = connector.pointOnPath(self.loc), + tail = jsPlumbUtil.pointOnLine(head, mid, self.length), + tailLine = jsPlumbUtil.perpendicularLineTo(head, tail, 40), + headLine = jsPlumbUtil.perpendicularLineTo(tail, head, 20); + + self.paint(connector, [head, tail, tailLine, headLine], self.lineWidth, "red", null, connectorDimensions); + + return [Math.min(head.x, tail.x), Math.min(head.y, tail.y), Math.max(head.x, tail.x), Math.max(head.y,tail.y)]; + }; + + this.computeMaxSize = function() { return 50; }; + + this.cleanup = function() { }; // nothing to clean up for GuideLines + }; + + // ********************************* END OF OVERLAY DEFINITIONS *********************************************************************** + + // ********************************* OVERLAY CANVAS RENDERERS*********************************************************************** + + // ********************************* END OF OVERLAY CANVAS RENDERERS *********************************************************************** +})(); \ No newline at end of file diff --git a/archive/1.3.11/jsPlumb-overlays-guidelines-1.3.11-RC1.js b/archive/1.3.11/jsPlumb-overlays-guidelines-1.3.11-RC1.js new file mode 100644 index 000000000..7a4bc8952 --- /dev/null +++ b/archive/1.3.11/jsPlumb-overlays-guidelines-1.3.11-RC1.js @@ -0,0 +1,47 @@ +// a test + jsPlumb.Overlays.svg.GuideLines = function() { + var path = null, self = this, path2 = null, p1_1, p1_2; + jsPlumb.Overlays.GuideLines.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + + p1_1 = _node("path"); + connector.svg.appendChild(p1_1); + self.attachListeners(p1_1, connector); + self.attachListeners(p1_1, self); + + p1_2 = _node("path"); + connector.svg.appendChild(p1_2); + self.attachListeners(p1_2, connector); + self.attachListeners(p1_2, self); + + } + + _attr(path, { + "d" : makePath(d[0], d[1]), + stroke : "red", + fill : null + }); + + _attr(p1_1, { + "d" : makePath(d[2][0], d[2][1]), + stroke : "blue", + fill : null + }); + + _attr(p1_2, { + "d" : makePath(d[3][0], d[3][1]), + stroke : "green", + fill : null + }); + }; + + var makePath = function(d1, d2) { + return "M " + d1.x + "," + d1.y + + " L" + d2.x + "," + d2.y; + }; + }; \ No newline at end of file diff --git a/archive/1.3.11/jsPlumb-renderers-canvas-1.3.11-RC1.js b/archive/1.3.11/jsPlumb-renderers-canvas-1.3.11-RC1.js new file mode 100644 index 000000000..560f549ee --- /dev/null +++ b/archive/1.3.11/jsPlumb-renderers-canvas-1.3.11-RC1.js @@ -0,0 +1,507 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.11 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the HTML5 canvas renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + +// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS ******************************************************************* + + // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. + var _connectionBeingDragged = null, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, + _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }; + + /* + * Class:CanvasMouseAdapter + * Provides support for mouse events on canvases. + */ + var CanvasMouseAdapter = function() { + var self = this; + self.overlayPlacements = []; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + jsPlumbUtil.EventGenerator.apply(this, arguments); + /** + * returns whether or not the given event is ojver a painted area of the canvas. + */ + this._over = function(e) { + var o = _getOffset(_getElementObject(self.canvas)), + pageXY = _pageXY(e), + x = pageXY[0] - o.left, y = pageXY[1] - o.top; + if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + return false; + }; + + var _mouseover = false, _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false, + _nullSafeHasClass = function(el, clazz) { + return el != null && _hasClass(el, clazz); + }; + this.mousemove = function(e) { + var pageXY = _pageXY(e), clientXY = _clientXY(e), + ee = document.elementFromPoint(clientXY[0], clientXY[1]), + eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); + var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); + if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.fire("mouseenter", self, e); + return true; + } + // TODO here there is a remote chance that the overlay the mouse moved onto + // is actually not an overlay for the current component. a more thorough check would + // be to ensure the overlay belonged to the current component. + else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { + _mouseover = false; + self.fire("mouseexit", self, e); + } + self.fire("mousemove", self, e); + }; + + this.click = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); + self.fire("mousedown", self, e); + } + }; + + this.mouseup = function(e) { + _mouseDown = false; + self.fire("mouseup", self, e); + }; + + this.contextmenu = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("contextmenu", self, e); + _mouseWasDown = false; + }; + }; + + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + params["_jsPlumb"].appendElement(canvas, params.parent); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + params["_jsPlumb"].getId(canvas, params.uuid); + if (params.tooltip) canvas.setAttribute("title", params.tooltip); + + return canvas; + }; + + var CanvasComponent = function(params) { + CanvasMouseAdapter.apply(this, arguments); + + var displayElements = [ ]; + this.getDisplayElements = function() { return displayElements; }; + this.appendDisplayElement = function(el) { displayElements.push(el); }; + }; + + /** + * Class:CanvasConnector + * Superclass for Canvas Connector renderers. + */ + var CanvasConnector = jsPlumb.CanvasConnector = function(params) { + + CanvasComponent.apply(this, arguments); + + var _paintOneStyle = function(dim, aStyle) { + self.ctx.save(); + jsPlumb.extend(self.ctx, aStyle); + if (aStyle.gradient) { + var g = self.createGradient(dim, self.ctx); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + self.ctx.strokeStyle = g; + } + self._paint(dim, aStyle); + self.ctx.restore(); + }; + + var self = this, + clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:params.tooltip + }); + self.ctx = self.canvas.getContext("2d"); + + self.appendDisplayElement(self.canvas); + + self.paint = function(dim, style) { + if (style != null) { + jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + _paintOneStyle(dim, outlineStyle); + } + _paintOneStyle(dim, style); + } + }; + }; + + /** + * Class:CanvasEndpoint + * Superclass for Canvas Endpoint renderers. + */ + var CanvasEndpoint = function(params) { + var self = this; + CanvasComponent.apply(this, arguments); + var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""), + canvasParams = { + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:self.tooltip + }; + self.canvas = _newCanvas(canvasParams); + self.ctx = self.canvas.getContext("2d"); + + self.appendDisplayElement(self.canvas); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + } + + self._paint.apply(this, arguments); + }; + }; + + jsPlumb.Endpoints.canvas.Dot = function(params) { + jsPlumb.Endpoints.Dot.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + var self = this, + parseValue = function(value) { + try { return parseInt(value); } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }, + calculateAdjustments = function(gradient) { + var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius; + gradient.offset && (offsetAdjustment = parseValue(gradient.offset)); + gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius)); + return [offsetAdjustment, innerRadius]; + }; + this._paint = function(d, style, anchor) { + if (style != null) { + var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + if (style.gradient) { + var adjustments = calculateAdjustments(style.gradient), + yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0], + xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0], + g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + ctx.beginPath(); + ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + } + }; + }; + + jsPlumb.Endpoints.canvas.Rectangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) { + + var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + + /* canvas gradient */ + if (style.gradient) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, d[2], d[3]); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + jsPlumb.Endpoints.canvas.Triangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Triangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) + { + var width = d[2], height = d[3], x = d[0], y = d[1], + ctx = self.canvas.getContext('2d'), + offsetX = 0, offsetY = 0, angle = 0, + orientation = anchor.getOrientation(self); + + if( orientation[0] == 1 ) { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = style.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + /* + * Canvas Image Endpoint: uses the default version, which creates an tag. + */ + jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image; + + /* + * Blank endpoint in all renderers is just the default Blank endpoint. + */ + jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank; + + /* + * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Bezier = function() { + var self = this; + jsPlumb.Connectors.Bezier.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions, style) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO i doubt this handles the case that source and target are swapped. + this.createGradient = function(dim, ctx, swap) { + return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + /* + * Canvas straight line Connector. Draws a straight line onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Straight = function() { + var self = this, + segmentMultipliers = [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ]; + + jsPlumb.Connectors.Straight.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions, style) { + + self.ctx.beginPath(); + + if (style.dashstyle && style.dashstyle.split(" ").length == 2) { + // only a very simple dashed style is supported - having two values, which define the stroke length + // (as a multiple of the stroke width) and then the space length (also as a multiple of stroke width). + var ds = style.dashstyle.split(" "); + if (ds.length != 2) ds = [2, 2]; + var dss = [ ds[0] * style.lineWidth, ds[1] * style.lineWidth ], + m = (dimensions[6] - dimensions[4]) / (dimensions[7] - dimensions[5]), + s = jsPlumbUtil.segment([dimensions[4], dimensions[5]], [ dimensions[6], dimensions[7] ]), + sm = segmentMultipliers[s], + theta = Math.atan(m), + l = Math.sqrt(Math.pow(dimensions[6] - dimensions[4], 2) + Math.pow(dimensions[7] - dimensions[5], 2)), + repeats = Math.floor(l / (dss[0] + dss[1])), + curPos = [dimensions[4], dimensions[5]]; + + + // TODO: the question here is why could we not support this in all connector types? it's really + // just a case of going along and asking jsPlumb for the next point on the path a few times, until it + // reaches the end. every type of connector supports that method, after all. but right now its only the + // bezier connector that gives you back the new location on the path along with the x,y coordinates, which + // we would need. we'd start out at loc=0 and ask for the point along the path that is dss[0] pixels away. + // we then ask for the point that is (dss[0] + dss[1]) pixels away; and from that one we need not just the + // x,y but the location, cos we're gonna plug that location back in in order to find where that dash ends. + // + // it also strikes me that it should be trivial to support arbitrary dash styles (having more or less than two + // entries). you'd just iterate that array using a step size of 2, and generify the (rss[0] + rss[1]) + // computation to be sum(rss[0]..rss[n]). + + for (var i = 0; i < repeats; i++) { + self.ctx.moveTo(curPos[0], curPos[1]); + + var nextEndX = curPos[0] + (Math.abs(Math.sin(theta) * dss[0]) * sm[0]), + nextEndY = curPos[1] + (Math.abs(Math.cos(theta) * dss[0]) * sm[1]), + nextStartX = curPos[0] + (Math.abs(Math.sin(theta) * (dss[0] + dss[1])) * sm[0]), + nextStartY = curPos[1] + (Math.abs(Math.cos(theta) * (dss[0] + dss[1])) * sm[1]) + + self.ctx.lineTo(nextEndX, nextEndY); + curPos = [nextStartX, nextStartY]; + } + + // now draw the last bit + self.ctx.moveTo(curPos[0], curPos[1]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + + } + else { + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + } + + self.ctx.stroke(); + }; + + // TODO this does not handle the case that src and target are swapped. + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + jsPlumb.Connectors.canvas.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions, style) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + +// ********************************* END OF CANVAS RENDERERS ******************************************************************* + + jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label; + + /** + * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. + */ + var CanvasOverlay = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }; + + var AbstractCanvasArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + CanvasOverlay.apply(this, originalArgs); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + var ctx = connector.ctx; + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(d.hxy.x, d.hxy.y); + ctx.lineTo(d.tail[0].x, d.tail[0].y); + ctx.lineTo(d.cxy.x, d.cxy.y); + ctx.lineTo(d.tail[1].x, d.tail[1].y); + ctx.lineTo(d.hxy.x, d.hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + }; + + jsPlumb.Overlays.canvas.Arrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.canvas.PlainArrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.canvas.Diamond = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.11/jsPlumb-renderers-svg-1.3.11-RC1.js b/archive/1.3.11/jsPlumb-renderers-svg-1.3.11-RC1.js new file mode 100644 index 000000000..a7f43d303 --- /dev/null +++ b/archive/1.3.11/jsPlumb-renderers-svg-1.3.11-RC1.js @@ -0,0 +1,550 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.11 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the SVG renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +/** + * SVG support for jsPlumb. + * + * things to investigate: + * + * gradients: https://developer.mozilla.org/en/svg_in_html_introduction + * css:http://tutorials.jenkov.com/svg/svg-and-css.html + * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath + * pointer events: https://developer.mozilla.org/en/css/pointer-events + * + * IE9 hover jquery: http://forum.jquery.com/topic/1-6-2-broke-svg-hover-events + * + */ +;(function() { + + var svgAttributeMap = { + "joinstyle":"stroke-linejoin", + "stroke-linejoin":"stroke-linejoin", + "stroke-dashoffset":"stroke-dashoffset", + "stroke-linecap":"stroke-linecap" + }, + STROKE_DASHARRAY = "stroke-dasharray", + DASHSTYLE = "dashstyle", + LINEAR_GRADIENT = "linearGradient", + RADIAL_GRADIENT = "radialGradient", + FILL = "fill", + STOP = "stop", + STROKE = "stroke", + STROKE_WIDTH = "stroke-width", + STYLE = "style", + NONE = "none", + JSPLUMB_GRADIENT = "jsplumb_gradient_", + LINE_WIDTH = "lineWidth", + ns = { + svg:"http://www.w3.org/2000/svg", + xhtml:"http://www.w3.org/1999/xhtml" + }, + _attr = function(node, attributes) { + for (var i in attributes) + node.setAttribute(i, "" + attributes[i]); + }, + _node = function(name, attributes) { + var n = document.createElementNS(ns.svg, name); + attributes = attributes || {}; + attributes["version"] = "1.1"; + attributes["xmlns"] = ns.xhtml; + _attr(n, attributes); + return n; + }, + _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; }, + _clearGradient = function(parent) { + for (var i = 0; i < parent.childNodes.length; i++) { + if (parent.childNodes[i].tagName == LINEAR_GRADIENT || parent.childNodes[i].tagName == RADIAL_GRADIENT) + parent.removeChild(parent.childNodes[i]); + } + }, + _updateGradient = function(parent, node, style, dimensions, uiComponent) { + var id = JSPLUMB_GRADIENT + uiComponent._jsPlumb.idstamp(); + // first clear out any existing gradient + _clearGradient(parent); + // this checks for an 'offset' property in the gradient, and in the absence of it, assumes + // we want a linear gradient. if it's there, we create a radial gradient. + // it is possible that a more explicit means of defining the gradient type would be + // better. relying on 'offset' means that we can never have a radial gradient that uses + // some default offset, for instance. + // issue 244 suggested the 'gradientUnits' attribute; without this, straight/flowchart connectors with gradients would + // not show gradients when the line was perfectly horizontal or vertical. + if (!style.gradient.offset) { + var g = _node(LINEAR_GRADIENT, {id:id, gradientUnits:"userSpaceOnUse"}); + parent.appendChild(g); + } + else { + var g = _node(RADIAL_GRADIENT, { + id:id + }); + parent.appendChild(g); + } + + // the svg radial gradient seems to treat stops in the reverse + // order to how canvas does it. so we want to keep all the maths the same, but + // iterate the actual style declarations in reverse order, if the x indexes are not in order. + for (var i = 0; i < style.gradient.stops.length; i++) { + // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of + // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments. + // so, not too concerned about leaving this in for now. + var styleToUse = i; + if (dimensions.length == 8) + styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i; + else + styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i; + var stopColor = jsPlumbUtil.convertStyle(style.gradient.stops[styleToUse][1], true); + var s = _node(STOP, {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor}); + g.appendChild(s); + } + var applyGradientTo = style.strokeStyle ? STROKE : FILL; + node.setAttribute(STYLE, applyGradientTo + ":url(#" + id + ")"); + }, + _applyStyles = function(parent, node, style, dimensions, uiComponent) { + + if (style.gradient) { + _updateGradient(parent, node, style, dimensions, uiComponent); + } + else { + // make sure we clear any existing gradient + _clearGradient(parent); + node.setAttribute(STYLE, ""); + } + + node.setAttribute(FILL, style.fillStyle ? jsPlumbUtil.convertStyle(style.fillStyle, true) : NONE); + node.setAttribute(STROKE, style.strokeStyle ? jsPlumbUtil.convertStyle(style.strokeStyle, true) : NONE); + if (style.lineWidth) { + node.setAttribute(STROKE_WIDTH, style.lineWidth); + } + + // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like + // the syntax in VML but is actually kind of nasty: values are given in the pixel + // coordinate space, whereas in VML they are multiples of the width of the stroked + // line, which makes a lot more sense. for that reason, jsPlumb is supporting both + // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from + // VML, which will be the preferred method. the code below this converts a dashstyle + // attribute given in terms of stroke width into a pixel representation, by using the + // stroke's lineWidth. + if (style[DASHSTYLE] && style[LINE_WIDTH] && !style[STROKE_DASHARRAY]) { + var sep = style[DASHSTYLE].indexOf(",") == -1 ? " " : ",", + parts = style[DASHSTYLE].split(sep), + styleToUse = ""; + parts.forEach(function(p) { + styleToUse += (Math.floor(p * style.lineWidth) + sep); + }); + node.setAttribute(STROKE_DASHARRAY, styleToUse); + } + else if(style[STROKE_DASHARRAY]) { + node.setAttribute(STROKE_DASHARRAY, style[STROKE_DASHARRAY]); + } + + // extra attributes such as join type, dash offset. + for (var i in svgAttributeMap) { + if (style[i]) { + node.setAttribute(svgAttributeMap[i], style[i]); + } + } + }, + _decodeFont = function(f) { + var r = /([0-9].)(p[xt])\s(.*)/; + var bits = f.match(r); + return {size:bits[1] + bits[2], font:bits[3]}; + }, + _classManip = function(el, add, clazz) { + var classesToAddOrRemove = clazz.split(" "), + className = el.className, + curClasses = className.baseVal.split(" "); + + for (var i = 0; i < classesToAddOrRemove.length; i++) { + if (add) { + if (curClasses.indexOf(classesToAddOrRemove[i]) == -1) + curClasses.push(classesToAddOrRemove[i]); + } + else { + var idx = curClasses.indexOf(classesToAddOrRemove[i]); + if (idx != -1) + curClasses.splice(idx, 1); + } + } + + el.className.baseVal = curClasses.join(" "); + }, + _addClass = function(el, clazz) { + _classManip(el, true, clazz); + }, + _removeClass = function(el, clazz) { + _classManip(el, false, clazz); + }; + + /** + utility methods for other objects to use. + */ + jsPlumbUtil.svg = { + addClass:_addClass, + removeClass:_removeClass, + node:_node, + attr:_attr, + pos:_pos + }; + + /* + * Base class for SVG components. + */ + //var SvgComponent = function(cssClass, originalArgs, pointerEventsSpec) { + var SvgComponent = function(params) { + var self = this, + pointerEventsSpec = params.pointerEventsSpec || "all"; + jsPlumb.jsPlumbUIComponent.apply(this, params.originalArgs); + self.canvas = null, self.path = null, self.svg = null; + + var clazz = params.cssClass + " " + (params.originalArgs[0].cssClass || ""), + svgParams = { + "style":"", + "width":0, + "height":0, + "pointer-events":pointerEventsSpec, + "position":"absolute" + }; + if (self.tooltip) svgParams["title"] = self.tooltip; + self.svg = _node("svg", svgParams); + if (params.useDivWrapper) { + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.sizeCanvas(self.canvas,0,0,1,1); + self.canvas.className = clazz; + if (self.tooltip) self.canvas.setAttribute("title", self.tooltip); + } + else { + _attr(self.svg, { "class":clazz }); + self.canvas = self.svg; + } + + params._jsPlumb.appendElement(self.canvas, params.originalArgs[0]["parent"]); + if (params.useDivWrapper) self.canvas.appendChild(self.svg); + + // TODO this displayElement stuff is common between all components, across all + // renderers. would be best moved to jsPlumbUIComponent. + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + this.paint = function(d, style, anchor) { + if (style != null) { + var x = d[0], y = d[1]; + if (params.useDivWrapper) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + x = 0, y = 0; + } + _attr(self.svg, { + "style":_pos([x, y, d[2], d[3]]), + "width": d[2], + "height": d[3] + }); + self._paint.apply(this, arguments); + } + }; + }; + + /* + * Base class for SVG connectors. + */ + var SvgConnector = jsPlumb.SvgConnector = function(params) { + var self = this; + SvgComponent.apply(this, [ { + cssClass:params["_jsPlumb"].connectorClass, + originalArgs:arguments, + pointerEventsSpec:"none", + tooltip:params.tooltip, + _jsPlumb:params["_jsPlumb"] + } ]); + this._paint = function(d, style) { + var p = self.getPath(d), a = { "d":p }, outlineStyle = null; + a["pointer-events"] = "all"; + + // outline style. actually means drawing an svg object underneath the main one. + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = jsPlumb.CurrentLibrary.extend({}, style); + outlineStyle.strokeStyle = jsPlumbUtil.convertStyle(style.outlineColor); + outlineStyle.lineWidth = outlineStrokeWidth; + + if (self.bgPath == null) { + self.bgPath = _node("path", a); + self.svg.appendChild(self.bgPath); + self.attachListeners(self.bgPath, self); + } + else { + _attr(self.bgPath, a); + } + + _applyStyles(self.svg, self.bgPath, outlineStyle, d, self); + } + + + // test - see below + // a["clip-path"]= "url(#testClip)"; + + if (self.path == null) { + self.path = _node("path", a); + self.svg.appendChild(self.path); + self.attachListeners(self.path, self); + + /* + this is a test of a clip path. i'm looking into using one of these to animate a jsplumb connection. + you could do this by walking along the line, stepping along a little at a time, and setting the clip + path to extend as far as that point. + + self.clip = _node("clipPath", {id:"testClip", clipPathUnits:"objectBoundingBox"}); + self.svg.appendChild(self.clip); + self.clip.appendChild(_node("rect", { + x:"0",y:"0",width:"0.5",height:"1" + })); + */ + } + else { + _attr(self.path, a); + } + + _applyStyles(self.svg, self.path, style, d, self); + }; + + this.reattachListeners = function() { + if (self.bgPath) self.reattachListenersForElement(self.bgPath, self); + if (self.path) self.reattachListenersForElement(self.path, self); + }; + + }; + + /* + * SVG Bezier Connector + */ + jsPlumb.Connectors.svg.Bezier = function(params) { + jsPlumb.Connectors.Bezier.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { + var _p = "M " + d[4] + " " + d[5]; + _p += (" C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]); + return _p; + }; + }; + + /* + * SVG straight line Connector + */ + jsPlumb.Connectors.svg.Straight = function(params) { + jsPlumb.Connectors.Straight.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; }; + }; + + jsPlumb.Connectors.svg.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "M " + dimensions[4] + "," + dimensions[5]; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)]; + } + // finally draw a line to the end + p = p + " " + dimensions[6] + "," + dimensions[7]; + return p; + }; + }; + + /* + * Base class for SVG endpoints. + */ + var SvgEndpoint = window.SvgEndpoint = function(params) { + var self = this; + SvgComponent.apply(this, [ { + cssClass:params["_jsPlumb"].endpointClass, + originalArgs:arguments, + pointerEventsSpec:"all", + useDivWrapper:true, + _jsPlumb:params["_jsPlumb"] + } ]); + this._paint = function(d, style) { + var s = jsPlumb.extend({}, style); + if (s.outlineColor) { + s.strokeWidth = s.outlineWidth; + s.strokeStyle = jsPlumbUtil.convertStyle(s.outlineColor, true); + } + + if (self.node == null) { + self.node = self.makeNode(d, s); + self.svg.appendChild(self.node); + self.attachListeners(self.node, self); + } + _applyStyles(self.svg, self.node, s, d, self); + _pos(self.node, d); + }; + + this.reattachListeners = function() { + if (self.node) self.reattachListenersForElement(self.node, self); + }; + }; + + /* + * SVG Dot Endpoint + */ + jsPlumb.Endpoints.svg.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("circle", { + "cx" : d[2] / 2, + "cy" : d[3] / 2, + "r" : d[2] / 2 + }); + }; + }; + + /* + * SVG Rectangle Endpoint + */ + jsPlumb.Endpoints.svg.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("rect", { + "width":d[2], + "height":d[3] + }); + }; + }; + + /* + * SVG Image Endpoint is the default image endpoint. + */ + jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image; + /* + * Blank endpoint in svg renderer is the default Blank endpoint. + */ + jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank; + /* + * Label endpoint in svg renderer is the default Label endpoint. + */ + jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label; + + var AbstractSvgArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + this.isAppendedAtTopLevel = false; + var self = this, path = null; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path", { + "pointer-events":"all" + }); + connector.svg.appendChild(path); + + self.attachListeners(path, connector); + self.attachListeners(path, self); + } + var clazz = originalArgs && (originalArgs.length == 1) ? (originalArgs[0].cssClass || "") : ""; + + _attr(path, { + "d" : makePath(d), + "class" : clazz, + stroke : strokeStyle ? strokeStyle : null, + fill : fillStyle ? fillStyle : null + }); + }; + var makePath = function(d) { + return "M" + d.hxy.x + "," + d.hxy.y + + " L" + d.tail[0].x + "," + d.tail[0].y + + " L" + d.cxy.x + "," + d.cxy.y + + " L" + d.tail[1].x + "," + d.tail[1].y + + " L" + d.hxy.x + "," + d.hxy.y; + }; + this.reattachListeners = function() { + if (path) self.reattachListenersForElement(path, self); + }; + this.cleanup = function() { + if (path != null) jsPlumb.CurrentLibrary.removeElement(path); + }; + }; + + jsPlumb.Overlays.svg.Arrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.svg.PlainArrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.svg.Diamond = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; + + // a test + jsPlumb.Overlays.svg.GuideLines = function() { + var path = null, self = this, path2 = null, p1_1, p1_2; + jsPlumb.Overlays.GuideLines.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + + p1_1 = _node("path"); + connector.svg.appendChild(p1_1); + self.attachListeners(p1_1, connector); + self.attachListeners(p1_1, self); + + p1_2 = _node("path"); + connector.svg.appendChild(p1_2); + self.attachListeners(p1_2, connector); + self.attachListeners(p1_2, self); + + } + + _attr(path, { + "d" : makePath(d[0], d[1]), + stroke : "red", + fill : null + }); + + _attr(p1_1, { + "d" : makePath(d[2][0], d[2][1]), + stroke : "blue", + fill : null + }); + + _attr(p1_2, { + "d" : makePath(d[3][0], d[3][1]), + stroke : "green", + fill : null + }); + }; + + var makePath = function(d1, d2) { + return "M " + d1.x + "," + d1.y + + " L" + d2.x + "," + d2.y; + }; + + }; +})(); \ No newline at end of file diff --git a/archive/1.3.11/jsPlumb-renderers-vml-1.3.11-RC1.js b/archive/1.3.11/jsPlumb-renderers-vml-1.3.11-RC1.js new file mode 100644 index 000000000..defffa704 --- /dev/null +++ b/archive/1.3.11/jsPlumb-renderers-vml-1.3.11-RC1.js @@ -0,0 +1,445 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.11 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the VML renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + // http://ajaxian.com/archives/the-vml-changes-in-ie-8 + // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/ + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + + var vmlAttributeMap = { + "stroke-linejoin":"joinstyle", + "joinstyle":"joinstyle", + "endcap":"endcap", + "miterlimit":"miterlimit" + }, + jsPlumbStylesheet = null; + + if (document.createStyleSheet && document.namespaces) { + + var ruleClasses = [ + ".jsplumb_vml", "jsplumb\\:textbox", "jsplumb\\:oval", "jsplumb\\:rect", + "jsplumb\\:stroke", "jsplumb\\:shape", "jsplumb\\:group" + ], + rule = "behavior:url(#default#VML);position:absolute;"; + + jsPlumbStylesheet = document.createStyleSheet(); + + for (var i = 0; i < ruleClasses.length; i++) + jsPlumbStylesheet.addRule(ruleClasses[i], rule); + + // in this page it is also mentioned that IE requires the extra arg to the namespace + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either. + // var iev = document.documentMode; + //if (!iev || iev < 8) + document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); + //else + // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML"); + } + + jsPlumb.vml = {}; + + var scale = 1000, + + _groupMap = {}, + _getGroup = function(container, connectorClass) { + var id = jsPlumb.getId(container), + g = _groupMap[id]; + if(!g) { + g = _node("group", [0,0,scale, scale], {"class":connectorClass}); + //g.style.position=absolute; + //g["coordsize"] = "1000,1000"; + g.style.backgroundColor="red"; + _groupMap[id] = g; + jsPlumb.appendElement(g, container); // todo if this gets reinstated, remember to use the current jsplumb instance. + //document.body.appendChild(g); + } + return g; + }, + _atts = function(o, atts) { + for (var i in atts) { + // IE8 fix: setattribute does not work after an element has been added to the dom! + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + //o.setAttribute(i, atts[i]); + + /*There is an additional problem when accessing VML elements by using get/setAttribute. The simple solution is following: + + if (document.documentMode==8) { + ele.opacity=1; + } else { + ele.setAttribute(‘opacity’,1); + } + */ + + o[i] = atts[i]; + } + }, + _node = function(name, d, atts, parent, _jsPlumb) { + atts = atts || {}; + var o = document.createElement("jsplumb:" + name); + _jsPlumb.appendElement(o, parent); + o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml"; + _pos(o, d); + _atts(o, atts); + return o; + }, + _pos = function(o,d) { + o.style.left = d[0] + "px"; + o.style.top = d[1] + "px"; + o.style.width= d[2] + "px"; + o.style.height= d[3] + "px"; + o.style.position = "absolute"; + }, + _conv = jsPlumb.vml.convertValue = function(v) { + return Math.floor(v * scale); + }, + // tests if the given style is "transparent" and then sets the appropriate opacity node to 0 if so, + // or 1 if not. TODO in the future, support variable opacity. + _maybeSetOpacity = function(styleToWrite, styleToCheck, type, component) { + if ("transparent" === styleToCheck) + component.setOpacity(type, "0.0"); + else + component.setOpacity(type, "1.0"); + }, + _applyStyles = function(node, style, component, _jsPlumb) { + var styleToWrite = {}; + if (style.strokeStyle) { + styleToWrite["stroked"] = "true"; + var strokeColor = jsPlumbUtil.convertStyle(style.strokeStyle, true); + styleToWrite["strokecolor"] = strokeColor; + _maybeSetOpacity(styleToWrite, strokeColor, "stroke", component); + styleToWrite["strokeweight"] = style.lineWidth + "px"; + } + else styleToWrite["stroked"] = "false"; + + if (style.fillStyle) { + styleToWrite["filled"] = "true"; + var fillColor = jsPlumbUtil.convertStyle(style.fillStyle, true); + styleToWrite["fillcolor"] = fillColor; + _maybeSetOpacity(styleToWrite, fillColor, "fill", component); + } + else styleToWrite["filled"] = "false"; + + if(style["dashstyle"]) { + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }, node, _jsPlumb); + } + else + component.strokeNode.dashstyle = style["dashstyle"]; + } + else if (style["stroke-dasharray"] && style["lineWidth"]) { + var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",", + parts = style["stroke-dasharray"].split(sep), + styleToUse = ""; + for(var i = 0; i < parts.length; i++) { + styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep); + } + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }, node, _jsPlumb); + //node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = styleToUse; + } + + _atts(node, styleToWrite); + }, + /* + * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. + */ + VmlComponent = function() { + var self = this; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.opacityNodes = { + "stroke":null, + "fill":null + }; + this.initOpacityNodes = function(vml) { + self.opacityNodes["stroke"] = _node("stroke", [0,0,1,1], {opacity:"0.0"}, vml, self._jsPlumb); + self.opacityNodes["fill"] = _node("fill", [0,0,1,1], {opacity:"0.0"}, vml, self._jsPlumb); + }; + this.setOpacity = function(type, value) { + var node = self.opacityNodes[type]; + if (node) node["opacity"] = "" + value; + }; + var displayElements = [ ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el, doNotAppendToCanvas) { + if (!doNotAppendToCanvas) self.canvas.parentNode.appendChild(el); + displayElements.push(el); + }; + }, + /* + * Base class for Vml connectors. extends VmlComponent. + */ + VmlConnector = jsPlumb.VmlConnector = function(params) { + var self = this; + self.strokeNode = null; + self.canvas = null; + VmlComponent.apply(this, arguments); + var clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : ""); + this.paint = function(d, style, anchor) { + if (style != null) { + var path = self.getPath(d), p = { "path":path }; + + //* + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle : jsPlumbUtil.convertStyle(style.outlineColor), + lineWidth : outlineStrokeWidth + }; + for (var aa in vmlAttributeMap) outlineStyle[aa] = style[aa]; + + if (self.bgCanvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.bgCanvas = _node("shape", d, p, params.parent, self._jsPlumb); + _pos(self.bgCanvas, d); + self.appendDisplayElement(self.bgCanvas, true); + self.attachListeners(self.bgCanvas, self); + self.initOpacityNodes(self.bgCanvas, ["stroke"]); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.bgCanvas, d); + _atts(self.bgCanvas, p); + } + + _applyStyles(self.bgCanvas, outlineStyle, self); + } + //*/ + + if (self.canvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + if (self.tooltip) p["label"] = self.tooltip; + self.canvas = _node("shape", d, p, params.parent, self._jsPlumb); + //var group = _getGroup(params.parent); // test of append everything to a group + //group.appendChild(self.canvas); // sort of works but not exactly; + //params["_jsPlumb"].appendElement(self.canvas, params.parent); //before introduction of groups + + self.appendDisplayElement(self.canvas, true); + self.attachListeners(self.canvas, self); + self.initOpacityNodes(self.canvas, ["stroke"]); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.canvas, d); + _atts(self.canvas, p); + } + + _applyStyles(self.canvas, style, self, self._jsPlumb); + } + }; + + //self.appendDisplayElement(self.canvas); + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + }, + /* + * + * Base class for Vml Endpoints. extends VmlComponent. + * + */ + VmlEndpoint = window.VmlEndpoint = function(params) { + VmlComponent.apply(this, arguments); + var vml = null, self = this, opacityStrokeNode = null, opacityFillNode = null; + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + + var clazz = self._jsPlumb.endpointClass + (params.cssClass ? (" " + params.cssClass) : ""); + + //var group = _getGroup(params.parent); + //group.appendChild(self.canvas); + params["_jsPlumb"].appendElement(self.canvas, params.parent); + + if (self.tooltip) self.canvas.setAttribute("label", self.tooltip); + + this.paint = function(d, style, anchor) { + var p = { }; + + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (vml == null) { + p["class"] = clazz; + vml = self.getVml([0,0, d[2], d[3]], p, anchor, self.canvas, self._jsPlumb); + self.attachListeners(vml, self); + + self.appendDisplayElement(vml, true); + self.appendDisplayElement(self.canvas, true); + + self.initOpacityNodes(vml, ["fill"]); + } + else { + _pos(vml, [0,0, d[2], d[3]]); + _atts(vml, p); + } + + _applyStyles(vml, style, self); + }; + + this.reattachListeners = function() { + if (vml) self.reattachListenersForElement(vml, self); + }; + }; + + jsPlumb.Connectors.vml.Bezier = function() { + jsPlumb.Connectors.Bezier.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Straight = function() { + jsPlumb.Connectors.Straight.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Flowchart = function() { + jsPlumb.Connectors.Flowchart.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l"; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]); + } + // finally draw a line to the end + p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e"; + return p; + }; + }; + + jsPlumb.Endpoints.vml.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor, parent, _jsPlumb) { return _node("oval", d, atts, parent, _jsPlumb); }; + }; + + jsPlumb.Endpoints.vml.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor, parent, _jsPlumb) { return _node("rect", d, atts, parent, _jsPlumb); }; + }; + + /* + * VML Image Endpoint is the same as the default image endpoint. + */ + jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image; + + /** + * placeholder for Blank endpoint in vml renderer. + */ + jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank; + + /** + * VML Label renderer. uses the default label renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label; + + var AbstractVmlArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + VmlComponent.apply(this, originalArgs); + var self = this, path = null; + self.canvas = null; + self.isAppendedAtTopLevel = true; + var getPath = function(d, connectorDimensions) { + return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) + + " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + + " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + + " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + + " x e"; + }; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) { + var p = {}; + if (strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] = jsPlumbUtil.convertStyle(strokeStyle, true); + } + if (lineWidth) p["strokeweight"] = lineWidth + "px"; + if (fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = fillStyle; + } + var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + w = Math.abs(xmax - xmin), + h = Math.abs(ymax - ymin), + dim = [xmin, ymin, w, h]; + + // for VML, we create overlays using shapes that have the same dimensions and + // coordsize as their connector - overlays calculate themselves relative to the + // connector (it's how it's been done since the original canvas implementation, because + // for canvas that makes sense). + p["path"] = getPath(d, connectorDimensions); + p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale); + + dim[0] = connectorDimensions[0]; + dim[1] = connectorDimensions[1]; + dim[2] = connectorDimensions[2]; + dim[3] = connectorDimensions[3]; + + if (self.canvas == null) { + //p["class"] = jsPlumb.overlayClass; // TODO currentInstance? + self.canvas = _node("shape", dim, p, connector.canvas.parentNode, connector._jsPlumb); + connector.appendDisplayElement(self.canvas, true); + self.attachListeners(self.canvas, connector); + self.attachListeners(self.canvas, self); + } + else { + _pos(self.canvas, dim); + _atts(self.canvas, p); + } + }; + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + + this.cleanup = function() { + if (self.canvas != null) jsPlumb.CurrentLibrary.removeElement(self.canvas); + }; + }; + + jsPlumb.Overlays.vml.Arrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.vml.PlainArrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.vml.Diamond = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.11/jsPlumb-util-1.3.11-RC1.js b/archive/1.3.11/jsPlumb-util-1.3.11-RC1.js new file mode 100644 index 000000000..c87e738f7 --- /dev/null +++ b/archive/1.3.11/jsPlumb-util-1.3.11-RC1.js @@ -0,0 +1,221 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.11 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the util functions + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ +jsPlumbUtil = { + isArray : function(a) { + return Object.prototype.toString.call(a) === "[object Array]"; + }, + isString : function(s) { + return typeof s === "string"; + }, + isObject : function(o) { + return Object.prototype.toString.call(o) === "[object Object]"; + }, + convertStyle : function(s, ignoreAlpha) { + // TODO: jsPlumb should support a separate 'opacity' style member. + if ("transparent" === s) return s; + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + return o; + }, + gradient : function(p1, p2) { + p1 = jsPlumbUtil.isArray(p1) ? p1 : [p1.x, p1.y]; + p2 = jsPlumbUtil.isArray(p2) ? p2 : [p2.x, p2.y]; + return (p2[1] - p1[1]) / (p2[0] - p1[0]); + }, + normal : function(p1, p2) { + return -1 / jsPlumbUtil.gradient(p1,p2); + }, + lineLength : function(p1, p2) { + p1 = jsPlumbUtil.isArray(p1) ? p1 : [p1.x, p1.y]; + p2 = jsPlumbUtil.isArray(p2) ? p2 : [p2.x, p2.y]; + return Math.sqrt(Math.pow(p2[1] - p1[1], 2) + Math.pow(p2[0] - p1[0], 2)); + }, + segment : function(p1, p2) { + p1 = jsPlumbUtil.isArray(p1) ? p1 : [p1.x, p1.y]; + p2 = jsPlumbUtil.isArray(p2) ? p2 : [p2.x, p2.y]; + if (p2[0] > p1[0]) { + return (p2[1] > p1[1]) ? 2 : 1; + } + else { + return (p2[1] > p1[1]) ? 3 : 4; + } + }, + intersects : function(r1, r2) { + var x1 = r1.x, x2 = r1.x + r1.w, y1 = r1.y, y2 = r1.y + r1.h, + a1 = r2.x, a2 = r2.x + r2.w, b1 = r2.y, b2 = r2.y + r2.h; + + return ( (x1 <= a1 && a1 <= x2) && (y1 <= b1 && b1 <= y2) ) || + ( (x1 <= a2 && a2 <= x2) && (y1 <= b1 && b1 <= y2) ) || + ( (x1 <= a1 && a1 <= x2) && (y1 <= b2 && b2 <= y2) ) || + ( (x1 <= a2 && a1 <= x2) && (y1 <= b2 && b2 <= y2) ) || + + ( (a1 <= x1 && x1 <= a2) && (b1 <= y1 && y1 <= b2) ) || + ( (a1 <= x2 && x2 <= a2) && (b1 <= y1 && y1 <= b2) ) || + ( (a1 <= x1 && x1 <= a2) && (b1 <= y2 && y2 <= b2) ) || + ( (a1 <= x2 && x1 <= a2) && (b1 <= y2 && y2 <= b2) ); + }, + segmentMultipliers : [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ], + inverseSegmentMultipliers : [null, [-1, -1], [-1, 1], [1, 1], [1, -1] ], + pointOnLine : function(fromPoint, toPoint, distance) { + var m = jsPlumbUtil.gradient(fromPoint, toPoint), + s = jsPlumbUtil.segment(fromPoint, toPoint), + segmentMultiplier = distance > 0 ? jsPlumbUtil.segmentMultipliers[s] : jsPlumbUtil.inverseSegmentMultipliers[s], + theta = Math.atan(m), + y = Math.abs(distance * Math.sin(theta)) * segmentMultiplier[1], + x = Math.abs(distance * Math.cos(theta)) * segmentMultiplier[0]; + return { x:fromPoint.x + x, y:fromPoint.y + y }; + }, + /** + * calculates a perpendicular to the line fromPoint->toPoint, that passes through toPoint and is 'length' long. + * @param fromPoint + * @param toPoint + * @param length + */ + perpendicularLineTo : function(fromPoint, toPoint, length) { + var m = jsPlumbUtil.gradient(fromPoint, toPoint), + theta2 = Math.atan(-1 / m), + y = length / 2 * Math.sin(theta2), + x = length / 2 * Math.cos(theta2); + return [{x:toPoint.x + x, y:toPoint.y + y}, {x:toPoint.x - x, y:toPoint.y - y}]; + }, + findWithFunction : function(a, f) { + if (a) + for (var i = 0; i < a.length; i++) if (f(a[i])) return i; + return -1; + }, + indexOf : function(l, v) { + return jsPlumbUtil.findWithFunction(l, function(_v) { return _v == v; }); + }, + removeWithFunction : function(a, f) { + var idx = jsPlumbUtil.findWithFunction(a, f); + if (idx > -1) a.splice(idx, 1); + return idx != -1; + }, + remove : function(l, v) { + var idx = jsPlumbUtil.indexOf(l, v); + if (idx > -1) l.splice(idx, 1); + return idx != -1; + }, + // TODO support insert index + addWithFunction : function(list, item, hashFunction) { + if (jsPlumbUtil.findWithFunction(list, hashFunction) == -1) list.push(item); + }, + addToList : function(map, key, value) { + var l = map[key]; + if (l == null) { + l = [], map[key] = l; + } + l.push(value); + return l; + }, + /** + * EventGenerator + * Superclass for objects that generate events - jsPlumb extends this, as does jsPlumbUIComponent, which all the UI elements extend. + */ + EventGenerator : function() { + var _listeners = {}, self = this; + + // this is a list of events that should re-throw any errors that occur during their dispatch. as of 1.3.0 this is private to + // jsPlumb, but it seems feasible that people might want to manipulate this list. the thinking is that we don't want event + // listeners to bring down jsPlumb - or do we. i can't make up my mind about this, but i know i want to hear about it if the "ready" + // event fails, because then my page has most likely not initialised. so i have this halfway-house solution. it will be interesting + // to hear what other people think. + var eventsToDieOn = [ "ready" ]; + + /* + * Binds a listener to an event. + * + * Parameters: + * event - name of the event to bind to. + * listener - function to execute. + */ + this.bind = function(event, listener) { + jsPlumbUtil.addToList(_listeners, event, listener); + return self; + }; + /* + * Fires an update for the given event. + * + * Parameters: + * event - event to fire + * value - value to pass to the event listener(s). + * originalEvent - the original event from the browser + */ + this.fire = function(event, value, originalEvent) { + if (_listeners[event]) { + for ( var i = 0; i < _listeners[event].length; i++) { + // doing it this way rather than catching and then possibly re-throwing means that an error propagated by this + // method will have the whole call stack available in the debugger. + if (jsPlumbUtil.findWithFunction(eventsToDieOn, function(e) { return e === event}) != -1) + _listeners[event][i](value, originalEvent); + else { + // for events we don't want to die on, catch and log. + try { + _listeners[event][i](value, originalEvent); + } catch (e) { + jsPlumbUtil.log("jsPlumb: fire failed for event " + event + " : " + e); + } + } + } + } + return self; + }; + /* + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + this.unbind = function(event) { + if (event) + delete _listeners[event]; + else { + delete _listeners; + _listeners = {}; + } + return self; + }; + + this.getListener = function(forEvent) { + return _listeners[forEvent]; + }; + }, + logEnabled : true, + log : function() { + if (jsPlumbUtil.logEnabled && typeof console != "undefined") { + try { + var msg = arguments[arguments.length - 1]; + console.log(msg); + } + catch (e) {} + } + }, + group : function(g) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.group(g); }, + groupEnd : function(g) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.groupEnd(g); }, + time : function(t) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.time(t); }, + timeEnd : function(t) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.timeEnd(t); } +}; \ No newline at end of file diff --git a/archive/1.3.11/mootools.jsPlumb-1.3.11-RC1.js b/archive/1.3.11/mootools.jsPlumb-1.3.11-RC1.js new file mode 100644 index 000000000..a41efe8cf --- /dev/null +++ b/archive/1.3.11/mootools.jsPlumb-1.3.11-RC1.js @@ -0,0 +1,463 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.11 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the MooTools adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function(now) { + this.parent(now); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event, originalEvent) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr, originalEvent); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, _scope, value) { + var l = list[_scope]; + if (!l) { + l = []; + list[_scope] = l; + } + l.push(value); + }; + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + var _getElementObject = function(el) { + return $(el); + }, + + _removeNonPermanentDroppables = function(drag) { + /* + // remove non-permanent droppables from all arrays + var dbs = _droppables[drag.scope], d = []; + if (dbs) { + var d = []; + for (var i=0; i < dbs.length; i++) { + var isPermanent = dbs[i].getAttribute("_isPermanentDroppable"); + if (isPermanent === "true") d.push(dbs[i]); + } + dbs.splice(0, dbs.length); + _droppables[drag.scope] = d; + } + d = []; + // clear out transient droppables from the drag itself + for(var i = 0; i < drag.droppables.length; i++) { + var isPermanent = drag.droppables[i].getAttribute("_isPermanentDroppable"); + if (isPermanent === "true") d.push(drag.droppables[i]); + } + drag.droppables.splice(0, drag.droppables.length); // release old ones + drag.droppables = d; + //*/ + }; + + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el) + try { + if (el.className.constructor == SVGAnimatedString) { + jsPlumbUtil.svg.addClass(el, clazz); + } + else el.addClass(clazz); + } + catch (e) { + // SVGAnimatedString not supported; no problem. + el.addClass(clazz); + } + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + m.start(properties); + }, + + appendElement : function(child, parent) { + _getElementObject(parent).grab(child); + }, + + bind : function(el, event, callback) { + el = _getElementObject(el); + el.addEvent(event, callback); + }, + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.event.clientX, eventObject.event.clientY]; + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el), + drags = _draggablesById[id]; + return drags[0].scope; + }, + + getDropEvent : function(args) { + return args[2]; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getDOMElement : function(el) { + // MooTools just decorates the DOM elements. so we have either an ID or an Element here. + return typeof(el) == "String" ? document.getElementById(el) : el; + }, + + getElementObject : _getElementObject, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + getOriginalEvent : function(e) { + return e.event; + }, + + getPageXY : function(eventObject) { + return [eventObject.event.pageX, eventObject.event.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).getParent(); + }, + + getScrollLeft : function(el) { + return null; + }, + + getScrollTop : function(el) { + return null; + }, + + getSelector : function(spec) { + return $$(spec); + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e != null ? e.tagName : null; + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[0], + p = jsPlumb.CurrentLibrary.getElementObject(ui).getPosition(); + return { left:p.x, top:p.y }; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options, isPlumbedComponent) { + var id = jsPlumb.getId(el); + var drag = _draggablesById[id]; + if (!drag) { + var originalZIndex = 0, + originalCursor = null, + dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + + options['onStart'] = jsPlumb.wrap(options['onStart'], function() { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + drag.originalZIndex = originalZIndex; + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + + _removeNonPermanentDroppables(drag); + }); + + // DROPPABLES - only relevant if this is a plumbed component, ie. not just the result of the user making some DOM element + // draggable. this is the only library adapter that has to care about this parameter. + var scope = "" + (options["scope"] || jsPlumb.Defaults.Scope), + filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }, + droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + + if (isPlumbedComponent) { + + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr, event) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop', event); + } + }; + } + else + options["droppables"] = []; + + drag = new Drag.Move(el, options); + drag.scope = scope; + drag.originalZIndex = originalZIndex; + _add(_draggablesById, el.get("id"), drag); + // again, only keep a record of this for scope stuff if this is a plumbed component (an endpoint) + if (isPlumbedComponent) { + _add(_draggablesByScope, scope, drag); + } + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + initDroppable : function(el, options, isPlumbedComponent, isPermanent) { + var scope = options["scope"]; + _add(_droppables, scope, el); + var id = jsPlumb.getId(el); + + el.setAttribute("_isPermanentDroppable", isPermanent); // we use this to clear out droppables on drag complete. + _droppableOptions[id] = options; + _droppableScopesById[id] = scope; + var filterFunc = function(entry) { return entry.element != el; }, + draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + isAlreadyDraggable : function(el) { + return _draggablesById[jsPlumb.getId(el)] != null; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + return (typeof Drag != undefined && typeof Drag.Move != undefined); + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el.className.constructor == SVGAnimatedString) { + jsPlumbUtil.svg.removeClass(el, clazz); + } + else el.removeClass(clazz); + } + catch (e) { + // SVGAnimatedString not supported; no problem. + el.removeClass(clazz); + } + }, + + removeElement : function(element, parent) { + var el = _getElementObject(element); + if (el) el.dispose(); // ?? + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + setDragScope : function(el, scope) { + var drag = _draggablesById[el.get("id")]; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + drag[0].droppables = droppables; + }, + + setOffset : function(el, o) { + _getElementObject(el).setPosition({x:o.left, y:o.top}); + }, + + stopDrag : function() { + for (var i in _draggablesById) { + for (var j = 0; j < _draggablesById[i].length; j++) { + var d = _draggablesById[i][j]; + d.stop(); + if (d.originalZIndex != 0) + d.element.setStyle("z-index", d.originalZIndex); + } + } + }, + + /** + * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself. + * the other libraries do not. yui, in fact, cannot even pass an original event. we have to pull out stuff + * from the originalEvent to put in an options object for YUI. + * @param el + * @param event + * @param originalEvent + */ + trigger : function(el, event, originalEvent) { + originalEvent.stopPropagation(); + _getElementObject(el).fireEvent(event, originalEvent); + }, + + unbind : function(el, event, callback) { + el = _getElementObject(el); + el.removeEvent(event, callback); + } + }; + + window.addEvent('domready', jsPlumb.init); +})(); diff --git a/build/1.3.11/tests/android-svg.html b/archive/1.3.11/tests/android-svg.html similarity index 100% rename from build/1.3.11/tests/android-svg.html rename to archive/1.3.11/tests/android-svg.html diff --git a/build/1.3.11/tests/loadTestHarness.html b/archive/1.3.11/tests/loadTestHarness.html similarity index 100% rename from build/1.3.11/tests/loadTestHarness.html rename to archive/1.3.11/tests/loadTestHarness.html diff --git a/build/1.3.11/tests/qunit-all.html b/archive/1.3.11/tests/qunit-all.html similarity index 100% rename from build/1.3.11/tests/qunit-all.html rename to archive/1.3.11/tests/qunit-all.html diff --git a/build/1.3.11/tests/qunit-canvas-jquery-instance.html b/archive/1.3.11/tests/qunit-canvas-jquery-instance.html similarity index 100% rename from build/1.3.11/tests/qunit-canvas-jquery-instance.html rename to archive/1.3.11/tests/qunit-canvas-jquery-instance.html diff --git a/build/1.3.11/tests/qunit-canvas-jquery.html b/archive/1.3.11/tests/qunit-canvas-jquery.html similarity index 100% rename from build/1.3.11/tests/qunit-canvas-jquery.html rename to archive/1.3.11/tests/qunit-canvas-jquery.html diff --git a/build/1.3.11/tests/qunit-canvas-mootools.html b/archive/1.3.11/tests/qunit-canvas-mootools.html similarity index 100% rename from build/1.3.11/tests/qunit-canvas-mootools.html rename to archive/1.3.11/tests/qunit-canvas-mootools.html diff --git a/build/1.3.11/tests/qunit-svg-jquery-instance.html b/archive/1.3.11/tests/qunit-svg-jquery-instance.html similarity index 100% rename from build/1.3.11/tests/qunit-svg-jquery-instance.html rename to archive/1.3.11/tests/qunit-svg-jquery-instance.html diff --git a/build/1.3.11/tests/qunit-svg-jquery.html b/archive/1.3.11/tests/qunit-svg-jquery.html similarity index 100% rename from build/1.3.11/tests/qunit-svg-jquery.html rename to archive/1.3.11/tests/qunit-svg-jquery.html diff --git a/build/1.3.11/tests/qunit-vml-jquery-instance.html b/archive/1.3.11/tests/qunit-vml-jquery-instance.html similarity index 100% rename from build/1.3.11/tests/qunit-vml-jquery-instance.html rename to archive/1.3.11/tests/qunit-vml-jquery-instance.html diff --git a/build/1.3.11/tests/qunit-vml-jquery.html b/archive/1.3.11/tests/qunit-vml-jquery.html similarity index 100% rename from build/1.3.11/tests/qunit-vml-jquery.html rename to archive/1.3.11/tests/qunit-vml-jquery.html diff --git a/build/1.3.11/tests/qunit.css b/archive/1.3.11/tests/qunit.css similarity index 100% rename from build/1.3.11/tests/qunit.css rename to archive/1.3.11/tests/qunit.css diff --git a/archive/1.3.11/yui.jsPlumb-1.3.11-RC1.js b/archive/1.3.11/yui.jsPlumb-1.3.11-RC1.js new file mode 100644 index 000000000..f7e5a1d15 --- /dev/null +++ b/archive/1.3.11/yui.jsPlumb-1.3.11-RC1.js @@ -0,0 +1,384 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.11 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the YUI3 adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +/** + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getOriginalEvent gets the original browser event from some wrapper event. + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + */ +(function() { + + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function( v, b, s ) { + for( var i = +b || 0, l = this.length; i < l; i++ ) { + if( this[i]===v || s && this[i]==v ) { return i; } + } + return -1; + }; + } + + var Y; + + YUI().use('node', 'dd', 'anim', 'node-event-simulate', function(_Y) { + Y = _Y; + Y.on("domready", function() { jsPlumb.init(); }); + }); + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }, + ddEvents = [ "drag:mouseDown", "drag:afterMouseDown", "drag:mouseup", + "drag:align", "drag:removeHandle", "drag:addHandle", "drag:removeInvalid", "drag:addInvalid", + "drag:start", "drag:end", "drag:drag", "drag:over", "drag:enter", + "drag:exit", "drag:drophit", "drag:dropmiss", "drop:over", "drop:enter", "drop:exit", "drop:hit" + ], + animEvents = [ "tween" ], + /** + * helper function to curry callbacks for some element. + */ + _wrapper = function(fn) { + return function() { + try { + return fn.apply(this, arguments); + } + catch (e) { } + }; + }, + /** + * extracts options from the given options object, leaving out event handlers. + */ + _getDDOptions = function(options) { + var o = {}; + for (var i in options) if (ddEvents.indexOf(i) == -1) o[i] = options[i]; + return o; + }, + /** + * attaches all event handlers found in options to the given dragdrop object, and registering + * the given el as the element of interest. + */ + _attachListeners = function(dd, options, eventList) { + for (var ev in options) { + if (eventList.indexOf(ev) != -1) { + var w = _wrapper(options[ev]); + dd.on(ev, w); + } + } + }, + _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}, + _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }, + _lastDragObject = null, + _extend = function(o1, o2) { + for (var i in o2) + o1[i] = o2[i]; + return o1; + }, + _getAttribute = function(el, attributeId) { + return el.getAttribute(attributeId); + }, + _getElementObject = function(el) { + if (el == null) return null; + var eee = null; + eee = typeof el == 'string' ? Y.one('#' + el) : el._node ? el : Y.one(el); + return eee; + }; + + jsPlumb.CurrentLibrary = { + + addClass : function(el, clazz) { + jsPlumb.CurrentLibrary.getElementObject(el).addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + var o = _extend({node:el, to:properties}, options), + id = _getAttribute(el, "id"); + o["tween"] = jsPlumb.wrap(properties["tween"], function() { + // TODO should use a current instance. + jsPlumb.repaint(id); + }); + var a = new Y.Anim(o); + _attachListeners(a, o, animEvents); + a.run(); + }, + + appendElement : function(child, parent) { + _getElementObject (parent).append(child); + }, + + /** + * event binding wrapper. + */ + bind : function(el, event, callback) { + _getElementObject(el).on(event, callback); + }, + + dragEvents : { + "start":"drag:start", "stop":"drag:end", "drag":"drag:drag", "step":"step", + "over":"drop:enter", "out":"drop:exit", "drop":"drop:hit" + }, + + extend : _extend, + + getAttribute : _getAttribute, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + // this is a workaround for the unfortunate fact that in YUI3, the 'drop:exit' event does + // not contain a reference to the drag that just exited. single-threaded js to the + // rescue: we'll just keep it for ourselves. + if (eventArgs[0].drag) _lastDragObject = eventArgs[0].drag.el; + return _lastDragObject; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el), + dd = _draggablesById[id]; + return dd.scope; + }, + + getDropEvent : function(args) { + return args[0]; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getDOMElement : function(el) { + if (typeof(el) == "String") + return document.getElementById(el); + else if (el._node) + return el._node; + else return el; + }, + + getElementObject : _getElementObject, + + getOffset : function(el) { + var o = Y.DOM.getXY(el._node); + return {left:o[0], top:o[1]}; + }, + + getOriginalEvent : function(e) { + return e._event; + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).get("parentNode"); + }, + + getScrollLeft : function(el) { + return 0; + }, + + getScrollTop : function(el) { + return 0; + }, + + getSelector : function(spec) { + var s = Y.all(spec); + return s && s ._nodes ? s._nodes : []; + }, + + getSize : function(el) { + return [ el._node.offsetWidth, el._node.offsetHeight ]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e != null && e._node != null ? e._node.tagName : null; + }, + + getUIPosition : function(args) { + var n = args[0].currentTarget.el._node, + o = Y.DOM.getXY(n); + return {left:o[0], top:o[1]}; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options, isPlumbedComponent) { + var _opts = _getDDOptions(options), + id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drag(_opts); + dd.el = el; + + if (isPlumbedComponent) { + var scope = options['scope'] || jsPlumb.Defaults.Scope; + dd.scope = scope; + _add(_draggablesByScope, scope, dd); + } + + _draggablesById[id] = dd; + + _attachListeners(dd, options, ddEvents); + }, + + initDroppable : function(el, options) { + var _opts = _getDDOptions(options), + id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drop(_opts); + + _droppableOptions[id] = options; + + options = _extend({}, options); + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _droppableScopesById[id] = scope; + + options["drop:enter"] = jsPlumb.wrap(options["drop:enter"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, true); + }, true); + options["drop:exit"] = jsPlumb.wrap(options["drop:exit"], function(e) { + _checkHover(el, false); + }); + options["drop:hit"] = jsPlumb.wrap(options["drop:hit"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, false); + }, true); + + _attachListeners(dd, options, ddEvents); + }, + + isAlreadyDraggable : function(el) { + el = _getElementObject(el); + return el.hasClass("yui3-dd-draggable"); + }, + + isDragSupported : function(el) { return true; }, + isDropSupported : function(el) { return true; }, + removeClass : function(el, clazz) { + jsPlumb.CurrentLibrary.getElementObject(el).removeClass(clazz); + }, + removeElement : function(el) { _getElementObject(el).remove(); }, + + setAttribute : function(el, attributeName, attributeValue) { + el.setAttribute(attributeName, attributeValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + var id = jsPlumb.getId(el), + dd = _draggablesById[id]; + if (dd) dd.set("lock", !draggable); + }, + + setDragScope : function(el, scope) { + var id = jsPlumb.getId(el), + dd = _draggablesById[id]; + if (dd) dd.scope = scope; + }, + + setOffset : function(el, o) { + el = _getElementObject(el); + el.set("top", o.top); + el.set("left", o.left); + }, + + stopDrag : function() { + Y.DD.DDM.stopDrag(); + }, + + trigger : function(el, event, originalEvent) { + originalEvent.stopPropagation(); + _getElementObject(el).simulate(event, { + pageX:originalEvent.pageX, + pageY:originalEvent.pageY, + clientX:originalEvent.clientX, + clientY:originalEvent.clientY + }); + }, + + /** + * event unbinding wrapper. + */ + unbind : function(el, event, callback) { + _getElementObject(el).detach(event, callback); + } + }; +})(); \ No newline at end of file diff --git a/build/1.3.12/demo/apidocs/files/jquery-jsPlumb-1-3-12-all-js.html b/archive/1.3.12/demo/apidocs/files/jquery-jsPlumb-1-3-12-all-js.html similarity index 100% rename from build/1.3.12/demo/apidocs/files/jquery-jsPlumb-1-3-12-all-js.html rename to archive/1.3.12/demo/apidocs/files/jquery-jsPlumb-1-3-12-all-js.html diff --git a/build/1.3.12/demo/apidocs/index.html b/archive/1.3.12/demo/apidocs/index.html similarity index 100% rename from build/1.3.12/demo/apidocs/index.html rename to archive/1.3.12/demo/apidocs/index.html diff --git a/build/1.3.12/demo/apidocs/index/Classes.html b/archive/1.3.12/demo/apidocs/index/Classes.html similarity index 100% rename from build/1.3.12/demo/apidocs/index/Classes.html rename to archive/1.3.12/demo/apidocs/index/Classes.html diff --git a/build/1.3.12/demo/apidocs/index/Files.html b/archive/1.3.12/demo/apidocs/index/Files.html similarity index 100% rename from build/1.3.12/demo/apidocs/index/Files.html rename to archive/1.3.12/demo/apidocs/index/Files.html diff --git a/build/1.3.12/demo/apidocs/index/Functions.html b/archive/1.3.12/demo/apidocs/index/Functions.html similarity index 100% rename from build/1.3.12/demo/apidocs/index/Functions.html rename to archive/1.3.12/demo/apidocs/index/Functions.html diff --git a/build/1.3.12/demo/apidocs/index/Functions2.html b/archive/1.3.12/demo/apidocs/index/Functions2.html similarity index 100% rename from build/1.3.12/demo/apidocs/index/Functions2.html rename to archive/1.3.12/demo/apidocs/index/Functions2.html diff --git a/build/1.3.12/demo/apidocs/index/General.html b/archive/1.3.12/demo/apidocs/index/General.html similarity index 100% rename from build/1.3.12/demo/apidocs/index/General.html rename to archive/1.3.12/demo/apidocs/index/General.html diff --git a/build/1.3.12/demo/apidocs/index/General2.html b/archive/1.3.12/demo/apidocs/index/General2.html similarity index 100% rename from build/1.3.12/demo/apidocs/index/General2.html rename to archive/1.3.12/demo/apidocs/index/General2.html diff --git a/build/1.3.12/demo/apidocs/index/Properties.html b/archive/1.3.12/demo/apidocs/index/Properties.html similarity index 100% rename from build/1.3.12/demo/apidocs/index/Properties.html rename to archive/1.3.12/demo/apidocs/index/Properties.html diff --git a/build/1.3.12/demo/apidocs/javascript/main.js b/archive/1.3.12/demo/apidocs/javascript/main.js similarity index 100% rename from build/1.3.12/demo/apidocs/javascript/main.js rename to archive/1.3.12/demo/apidocs/javascript/main.js diff --git a/build/1.3.12/demo/apidocs/javascript/prettify.js b/archive/1.3.12/demo/apidocs/javascript/prettify.js similarity index 100% rename from build/1.3.12/demo/apidocs/javascript/prettify.js rename to archive/1.3.12/demo/apidocs/javascript/prettify.js diff --git a/build/1.3.12/demo/apidocs/javascript/searchdata.js b/archive/1.3.12/demo/apidocs/javascript/searchdata.js similarity index 100% rename from build/1.3.12/demo/apidocs/javascript/searchdata.js rename to archive/1.3.12/demo/apidocs/javascript/searchdata.js diff --git a/build/1.3.12/demo/apidocs/search/ClassesC.html b/archive/1.3.12/demo/apidocs/search/ClassesC.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/ClassesC.html rename to archive/1.3.12/demo/apidocs/search/ClassesC.html diff --git a/build/1.3.12/demo/apidocs/search/ClassesE.html b/archive/1.3.12/demo/apidocs/search/ClassesE.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/ClassesE.html rename to archive/1.3.12/demo/apidocs/search/ClassesE.html diff --git a/build/1.3.12/demo/apidocs/search/ClassesO.html b/archive/1.3.12/demo/apidocs/search/ClassesO.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/ClassesO.html rename to archive/1.3.12/demo/apidocs/search/ClassesO.html diff --git a/build/1.3.12/demo/apidocs/search/FilesJ.html b/archive/1.3.12/demo/apidocs/search/FilesJ.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/FilesJ.html rename to archive/1.3.12/demo/apidocs/search/FilesJ.html diff --git a/build/1.3.12/demo/apidocs/search/FunctionsA.html b/archive/1.3.12/demo/apidocs/search/FunctionsA.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/FunctionsA.html rename to archive/1.3.12/demo/apidocs/search/FunctionsA.html diff --git a/build/1.3.12/demo/apidocs/search/FunctionsB.html b/archive/1.3.12/demo/apidocs/search/FunctionsB.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/FunctionsB.html rename to archive/1.3.12/demo/apidocs/search/FunctionsB.html diff --git a/build/1.3.12/demo/apidocs/search/FunctionsC.html b/archive/1.3.12/demo/apidocs/search/FunctionsC.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/FunctionsC.html rename to archive/1.3.12/demo/apidocs/search/FunctionsC.html diff --git a/build/1.3.12/demo/apidocs/search/FunctionsD.html b/archive/1.3.12/demo/apidocs/search/FunctionsD.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/FunctionsD.html rename to archive/1.3.12/demo/apidocs/search/FunctionsD.html diff --git a/build/1.3.12/demo/apidocs/search/FunctionsE.html b/archive/1.3.12/demo/apidocs/search/FunctionsE.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/FunctionsE.html rename to archive/1.3.12/demo/apidocs/search/FunctionsE.html diff --git a/build/1.3.12/demo/apidocs/search/FunctionsG.html b/archive/1.3.12/demo/apidocs/search/FunctionsG.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/FunctionsG.html rename to archive/1.3.12/demo/apidocs/search/FunctionsG.html diff --git a/build/1.3.12/demo/apidocs/search/FunctionsH.html b/archive/1.3.12/demo/apidocs/search/FunctionsH.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/FunctionsH.html rename to archive/1.3.12/demo/apidocs/search/FunctionsH.html diff --git a/build/1.3.12/demo/apidocs/search/FunctionsI.html b/archive/1.3.12/demo/apidocs/search/FunctionsI.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/FunctionsI.html rename to archive/1.3.12/demo/apidocs/search/FunctionsI.html diff --git a/build/1.3.12/demo/apidocs/search/FunctionsM.html b/archive/1.3.12/demo/apidocs/search/FunctionsM.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/FunctionsM.html rename to archive/1.3.12/demo/apidocs/search/FunctionsM.html diff --git a/build/1.3.12/demo/apidocs/search/FunctionsP.html b/archive/1.3.12/demo/apidocs/search/FunctionsP.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/FunctionsP.html rename to archive/1.3.12/demo/apidocs/search/FunctionsP.html diff --git a/build/1.3.12/demo/apidocs/search/FunctionsR.html b/archive/1.3.12/demo/apidocs/search/FunctionsR.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/FunctionsR.html rename to archive/1.3.12/demo/apidocs/search/FunctionsR.html diff --git a/build/1.3.12/demo/apidocs/search/FunctionsS.html b/archive/1.3.12/demo/apidocs/search/FunctionsS.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/FunctionsS.html rename to archive/1.3.12/demo/apidocs/search/FunctionsS.html diff --git a/build/1.3.12/demo/apidocs/search/FunctionsT.html b/archive/1.3.12/demo/apidocs/search/FunctionsT.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/FunctionsT.html rename to archive/1.3.12/demo/apidocs/search/FunctionsT.html diff --git a/build/1.3.12/demo/apidocs/search/FunctionsU.html b/archive/1.3.12/demo/apidocs/search/FunctionsU.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/FunctionsU.html rename to archive/1.3.12/demo/apidocs/search/FunctionsU.html diff --git a/build/1.3.12/demo/apidocs/search/GeneralA.html b/archive/1.3.12/demo/apidocs/search/GeneralA.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/GeneralA.html rename to archive/1.3.12/demo/apidocs/search/GeneralA.html diff --git a/build/1.3.12/demo/apidocs/search/GeneralB.html b/archive/1.3.12/demo/apidocs/search/GeneralB.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/GeneralB.html rename to archive/1.3.12/demo/apidocs/search/GeneralB.html diff --git a/build/1.3.12/demo/apidocs/search/GeneralC.html b/archive/1.3.12/demo/apidocs/search/GeneralC.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/GeneralC.html rename to archive/1.3.12/demo/apidocs/search/GeneralC.html diff --git a/build/1.3.12/demo/apidocs/search/GeneralD.html b/archive/1.3.12/demo/apidocs/search/GeneralD.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/GeneralD.html rename to archive/1.3.12/demo/apidocs/search/GeneralD.html diff --git a/build/1.3.12/demo/apidocs/search/GeneralE.html b/archive/1.3.12/demo/apidocs/search/GeneralE.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/GeneralE.html rename to archive/1.3.12/demo/apidocs/search/GeneralE.html diff --git a/build/1.3.12/demo/apidocs/search/GeneralF.html b/archive/1.3.12/demo/apidocs/search/GeneralF.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/GeneralF.html rename to archive/1.3.12/demo/apidocs/search/GeneralF.html diff --git a/build/1.3.12/demo/apidocs/search/GeneralG.html b/archive/1.3.12/demo/apidocs/search/GeneralG.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/GeneralG.html rename to archive/1.3.12/demo/apidocs/search/GeneralG.html diff --git a/build/1.3.12/demo/apidocs/search/GeneralH.html b/archive/1.3.12/demo/apidocs/search/GeneralH.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/GeneralH.html rename to archive/1.3.12/demo/apidocs/search/GeneralH.html diff --git a/build/1.3.12/demo/apidocs/search/GeneralI.html b/archive/1.3.12/demo/apidocs/search/GeneralI.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/GeneralI.html rename to archive/1.3.12/demo/apidocs/search/GeneralI.html diff --git a/build/1.3.12/demo/apidocs/search/GeneralJ.html b/archive/1.3.12/demo/apidocs/search/GeneralJ.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/GeneralJ.html rename to archive/1.3.12/demo/apidocs/search/GeneralJ.html diff --git a/build/1.3.12/demo/apidocs/search/GeneralM.html b/archive/1.3.12/demo/apidocs/search/GeneralM.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/GeneralM.html rename to archive/1.3.12/demo/apidocs/search/GeneralM.html diff --git a/build/1.3.12/demo/apidocs/search/GeneralO.html b/archive/1.3.12/demo/apidocs/search/GeneralO.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/GeneralO.html rename to archive/1.3.12/demo/apidocs/search/GeneralO.html diff --git a/build/1.3.12/demo/apidocs/search/GeneralP.html b/archive/1.3.12/demo/apidocs/search/GeneralP.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/GeneralP.html rename to archive/1.3.12/demo/apidocs/search/GeneralP.html diff --git a/build/1.3.12/demo/apidocs/search/GeneralR.html b/archive/1.3.12/demo/apidocs/search/GeneralR.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/GeneralR.html rename to archive/1.3.12/demo/apidocs/search/GeneralR.html diff --git a/build/1.3.12/demo/apidocs/search/GeneralS.html b/archive/1.3.12/demo/apidocs/search/GeneralS.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/GeneralS.html rename to archive/1.3.12/demo/apidocs/search/GeneralS.html diff --git a/build/1.3.12/demo/apidocs/search/GeneralT.html b/archive/1.3.12/demo/apidocs/search/GeneralT.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/GeneralT.html rename to archive/1.3.12/demo/apidocs/search/GeneralT.html diff --git a/build/1.3.12/demo/apidocs/search/GeneralU.html b/archive/1.3.12/demo/apidocs/search/GeneralU.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/GeneralU.html rename to archive/1.3.12/demo/apidocs/search/GeneralU.html diff --git a/build/1.3.12/demo/apidocs/search/NoResults.html b/archive/1.3.12/demo/apidocs/search/NoResults.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/NoResults.html rename to archive/1.3.12/demo/apidocs/search/NoResults.html diff --git a/build/1.3.12/demo/apidocs/search/PropertiesC.html b/archive/1.3.12/demo/apidocs/search/PropertiesC.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/PropertiesC.html rename to archive/1.3.12/demo/apidocs/search/PropertiesC.html diff --git a/build/1.3.12/demo/apidocs/search/PropertiesD.html b/archive/1.3.12/demo/apidocs/search/PropertiesD.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/PropertiesD.html rename to archive/1.3.12/demo/apidocs/search/PropertiesD.html diff --git a/build/1.3.12/demo/apidocs/search/PropertiesE.html b/archive/1.3.12/demo/apidocs/search/PropertiesE.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/PropertiesE.html rename to archive/1.3.12/demo/apidocs/search/PropertiesE.html diff --git a/build/1.3.12/demo/apidocs/search/PropertiesO.html b/archive/1.3.12/demo/apidocs/search/PropertiesO.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/PropertiesO.html rename to archive/1.3.12/demo/apidocs/search/PropertiesO.html diff --git a/build/1.3.12/demo/apidocs/search/PropertiesS.html b/archive/1.3.12/demo/apidocs/search/PropertiesS.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/PropertiesS.html rename to archive/1.3.12/demo/apidocs/search/PropertiesS.html diff --git a/build/1.3.12/demo/apidocs/search/PropertiesT.html b/archive/1.3.12/demo/apidocs/search/PropertiesT.html similarity index 100% rename from build/1.3.12/demo/apidocs/search/PropertiesT.html rename to archive/1.3.12/demo/apidocs/search/PropertiesT.html diff --git a/build/1.3.12/demo/apidocs/styles/main.css b/archive/1.3.12/demo/apidocs/styles/main.css similarity index 100% rename from build/1.3.12/demo/apidocs/styles/main.css rename to archive/1.3.12/demo/apidocs/styles/main.css diff --git a/build/1.3.12/demo/css/anchorDemo.css b/archive/1.3.12/demo/css/anchorDemo.css similarity index 100% rename from build/1.3.12/demo/css/anchorDemo.css rename to archive/1.3.12/demo/css/anchorDemo.css diff --git a/build/1.3.12/demo/css/chartDemo.css b/archive/1.3.12/demo/css/chartDemo.css similarity index 100% rename from build/1.3.12/demo/css/chartDemo.css rename to archive/1.3.12/demo/css/chartDemo.css diff --git a/build/1.3.12/demo/css/demo.css b/archive/1.3.12/demo/css/demo.css similarity index 100% rename from build/1.3.12/demo/css/demo.css rename to archive/1.3.12/demo/css/demo.css diff --git a/build/1.3.12/demo/css/dragAnimDemo.css b/archive/1.3.12/demo/css/dragAnimDemo.css similarity index 100% rename from build/1.3.12/demo/css/dragAnimDemo.css rename to archive/1.3.12/demo/css/dragAnimDemo.css diff --git a/build/1.3.12/demo/css/draggableConnectorsDemo.css b/archive/1.3.12/demo/css/draggableConnectorsDemo.css similarity index 100% rename from build/1.3.12/demo/css/draggableConnectorsDemo.css rename to archive/1.3.12/demo/css/draggableConnectorsDemo.css diff --git a/build/1.3.12/demo/css/dynamicAnchorsDemo.css b/archive/1.3.12/demo/css/dynamicAnchorsDemo.css similarity index 100% rename from build/1.3.12/demo/css/dynamicAnchorsDemo.css rename to archive/1.3.12/demo/css/dynamicAnchorsDemo.css diff --git a/build/1.3.12/demo/css/flowchartDemo.css b/archive/1.3.12/demo/css/flowchartDemo.css similarity index 100% rename from build/1.3.12/demo/css/flowchartDemo.css rename to archive/1.3.12/demo/css/flowchartDemo.css diff --git a/build/1.3.12/demo/css/jsPlumbDemo.css b/archive/1.3.12/demo/css/jsPlumbDemo.css similarity index 100% rename from build/1.3.12/demo/css/jsPlumbDemo.css rename to archive/1.3.12/demo/css/jsPlumbDemo.css diff --git a/build/1.3.12/demo/css/makeTargetDemo.css b/archive/1.3.12/demo/css/makeTargetDemo.css similarity index 100% rename from build/1.3.12/demo/css/makeTargetDemo.css rename to archive/1.3.12/demo/css/makeTargetDemo.css diff --git a/build/1.3.12/demo/css/multipleJsPlumbDemo.css b/archive/1.3.12/demo/css/multipleJsPlumbDemo.css similarity index 100% rename from build/1.3.12/demo/css/multipleJsPlumbDemo.css rename to archive/1.3.12/demo/css/multipleJsPlumbDemo.css diff --git a/build/1.3.12/demo/css/selectDemo.css b/archive/1.3.12/demo/css/selectDemo.css similarity index 100% rename from build/1.3.12/demo/css/selectDemo.css rename to archive/1.3.12/demo/css/selectDemo.css diff --git a/build/1.3.12/demo/css/stateMachineDemo.css b/archive/1.3.12/demo/css/stateMachineDemo.css similarity index 100% rename from build/1.3.12/demo/css/stateMachineDemo.css rename to archive/1.3.12/demo/css/stateMachineDemo.css diff --git a/build/1.3.12/demo/doc/archive/1.2.6/content.html b/archive/1.3.12/demo/doc/archive/1.2.6/content.html similarity index 100% rename from build/1.3.12/demo/doc/archive/1.2.6/content.html rename to archive/1.3.12/demo/doc/archive/1.2.6/content.html diff --git a/build/1.3.12/demo/doc/archive/1.2.6/index.html b/archive/1.3.12/demo/doc/archive/1.2.6/index.html similarity index 100% rename from build/1.3.12/demo/doc/archive/1.2.6/index.html rename to archive/1.3.12/demo/doc/archive/1.2.6/index.html diff --git a/build/1.3.12/demo/doc/archive/1.2.6/usage.html b/archive/1.3.12/demo/doc/archive/1.2.6/usage.html similarity index 100% rename from build/1.3.12/demo/doc/archive/1.2.6/usage.html rename to archive/1.3.12/demo/doc/archive/1.2.6/usage.html diff --git a/build/1.3.12/demo/doc/archive/1.3.10/content.html b/archive/1.3.12/demo/doc/archive/1.3.10/content.html similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.10/content.html rename to archive/1.3.12/demo/doc/archive/1.3.10/content.html diff --git a/build/1.3.12/demo/doc/archive/1.3.10/index.html b/archive/1.3.12/demo/doc/archive/1.3.10/index.html similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.10/index.html rename to archive/1.3.12/demo/doc/archive/1.3.10/index.html diff --git a/build/1.3.12/demo/doc/archive/1.3.10/jsPlumbDoc.css b/archive/1.3.12/demo/doc/archive/1.3.10/jsPlumbDoc.css similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.10/jsPlumbDoc.css rename to archive/1.3.12/demo/doc/archive/1.3.10/jsPlumbDoc.css diff --git a/build/1.3.12/demo/doc/archive/1.3.10/usage.html b/archive/1.3.12/demo/doc/archive/1.3.10/usage.html similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.10/usage.html rename to archive/1.3.12/demo/doc/archive/1.3.10/usage.html diff --git a/build/1.3.12/demo/doc/archive/1.3.11/content.html b/archive/1.3.12/demo/doc/archive/1.3.11/content.html similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.11/content.html rename to archive/1.3.12/demo/doc/archive/1.3.11/content.html diff --git a/build/1.3.12/demo/doc/archive/1.3.11/index.html b/archive/1.3.12/demo/doc/archive/1.3.11/index.html similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.11/index.html rename to archive/1.3.12/demo/doc/archive/1.3.11/index.html diff --git a/build/1.3.12/demo/doc/archive/1.3.11/jsPlumbDoc.css b/archive/1.3.12/demo/doc/archive/1.3.11/jsPlumbDoc.css similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.11/jsPlumbDoc.css rename to archive/1.3.12/demo/doc/archive/1.3.11/jsPlumbDoc.css diff --git a/build/1.3.12/demo/doc/archive/1.3.11/usage.html b/archive/1.3.12/demo/doc/archive/1.3.11/usage.html similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.11/usage.html rename to archive/1.3.12/demo/doc/archive/1.3.11/usage.html diff --git a/build/1.3.12/demo/doc/archive/1.3.2/content.html b/archive/1.3.12/demo/doc/archive/1.3.2/content.html similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.2/content.html rename to archive/1.3.12/demo/doc/archive/1.3.2/content.html diff --git a/build/1.3.12/demo/doc/archive/1.3.2/index.html b/archive/1.3.12/demo/doc/archive/1.3.2/index.html similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.2/index.html rename to archive/1.3.12/demo/doc/archive/1.3.2/index.html diff --git a/build/1.3.12/demo/doc/archive/1.3.2/usage.html b/archive/1.3.12/demo/doc/archive/1.3.2/usage.html similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.2/usage.html rename to archive/1.3.12/demo/doc/archive/1.3.2/usage.html diff --git a/build/1.3.12/demo/doc/archive/1.3.3/content.html b/archive/1.3.12/demo/doc/archive/1.3.3/content.html similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.3/content.html rename to archive/1.3.12/demo/doc/archive/1.3.3/content.html diff --git a/build/1.3.12/demo/doc/archive/1.3.3/index.html b/archive/1.3.12/demo/doc/archive/1.3.3/index.html similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.3/index.html rename to archive/1.3.12/demo/doc/archive/1.3.3/index.html diff --git a/build/1.3.12/demo/doc/archive/1.3.3/jsPlumbDoc.css b/archive/1.3.12/demo/doc/archive/1.3.3/jsPlumbDoc.css similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.3/jsPlumbDoc.css rename to archive/1.3.12/demo/doc/archive/1.3.3/jsPlumbDoc.css diff --git a/build/1.3.12/demo/doc/archive/1.3.3/usage.html b/archive/1.3.12/demo/doc/archive/1.3.3/usage.html similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.3/usage.html rename to archive/1.3.12/demo/doc/archive/1.3.3/usage.html diff --git a/build/1.3.12/demo/doc/archive/1.3.4/content.html b/archive/1.3.12/demo/doc/archive/1.3.4/content.html similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.4/content.html rename to archive/1.3.12/demo/doc/archive/1.3.4/content.html diff --git a/build/1.3.12/demo/doc/archive/1.3.4/index.html b/archive/1.3.12/demo/doc/archive/1.3.4/index.html similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.4/index.html rename to archive/1.3.12/demo/doc/archive/1.3.4/index.html diff --git a/build/1.3.12/demo/doc/archive/1.3.4/jsPlumbDoc.css b/archive/1.3.12/demo/doc/archive/1.3.4/jsPlumbDoc.css similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.4/jsPlumbDoc.css rename to archive/1.3.12/demo/doc/archive/1.3.4/jsPlumbDoc.css diff --git a/build/1.3.12/demo/doc/archive/1.3.4/usage.html b/archive/1.3.12/demo/doc/archive/1.3.4/usage.html similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.4/usage.html rename to archive/1.3.12/demo/doc/archive/1.3.4/usage.html diff --git a/build/1.3.12/demo/doc/archive/1.3.5/content.html b/archive/1.3.12/demo/doc/archive/1.3.5/content.html similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.5/content.html rename to archive/1.3.12/demo/doc/archive/1.3.5/content.html diff --git a/build/1.3.12/demo/doc/archive/1.3.5/index.html b/archive/1.3.12/demo/doc/archive/1.3.5/index.html similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.5/index.html rename to archive/1.3.12/demo/doc/archive/1.3.5/index.html diff --git a/build/1.3.12/demo/doc/archive/1.3.5/jsPlumbDoc.css b/archive/1.3.12/demo/doc/archive/1.3.5/jsPlumbDoc.css similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.5/jsPlumbDoc.css rename to archive/1.3.12/demo/doc/archive/1.3.5/jsPlumbDoc.css diff --git a/build/1.3.12/demo/doc/archive/1.3.5/usage.html b/archive/1.3.12/demo/doc/archive/1.3.5/usage.html similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.5/usage.html rename to archive/1.3.12/demo/doc/archive/1.3.5/usage.html diff --git a/build/1.3.12/demo/doc/archive/1.3.6/content.html b/archive/1.3.12/demo/doc/archive/1.3.6/content.html similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.6/content.html rename to archive/1.3.12/demo/doc/archive/1.3.6/content.html diff --git a/build/1.3.12/demo/doc/archive/1.3.6/index.html b/archive/1.3.12/demo/doc/archive/1.3.6/index.html similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.6/index.html rename to archive/1.3.12/demo/doc/archive/1.3.6/index.html diff --git a/build/1.3.12/demo/doc/archive/1.3.6/jsPlumbDoc.css b/archive/1.3.12/demo/doc/archive/1.3.6/jsPlumbDoc.css similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.6/jsPlumbDoc.css rename to archive/1.3.12/demo/doc/archive/1.3.6/jsPlumbDoc.css diff --git a/build/1.3.12/demo/doc/archive/1.3.6/usage.html b/archive/1.3.12/demo/doc/archive/1.3.6/usage.html similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.6/usage.html rename to archive/1.3.12/demo/doc/archive/1.3.6/usage.html diff --git a/build/1.3.12/demo/doc/archive/1.3.7/content.html b/archive/1.3.12/demo/doc/archive/1.3.7/content.html similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.7/content.html rename to archive/1.3.12/demo/doc/archive/1.3.7/content.html diff --git a/build/1.3.12/demo/doc/archive/1.3.7/index.html b/archive/1.3.12/demo/doc/archive/1.3.7/index.html similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.7/index.html rename to archive/1.3.12/demo/doc/archive/1.3.7/index.html diff --git a/build/1.3.12/demo/doc/archive/1.3.7/jsPlumbDoc.css b/archive/1.3.12/demo/doc/archive/1.3.7/jsPlumbDoc.css similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.7/jsPlumbDoc.css rename to archive/1.3.12/demo/doc/archive/1.3.7/jsPlumbDoc.css diff --git a/build/1.3.12/demo/doc/archive/1.3.7/usage.html b/archive/1.3.12/demo/doc/archive/1.3.7/usage.html similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.7/usage.html rename to archive/1.3.12/demo/doc/archive/1.3.7/usage.html diff --git a/build/1.3.12/demo/doc/archive/1.3.8/content.html b/archive/1.3.12/demo/doc/archive/1.3.8/content.html similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.8/content.html rename to archive/1.3.12/demo/doc/archive/1.3.8/content.html diff --git a/build/1.3.12/demo/doc/archive/1.3.8/index.html b/archive/1.3.12/demo/doc/archive/1.3.8/index.html similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.8/index.html rename to archive/1.3.12/demo/doc/archive/1.3.8/index.html diff --git a/build/1.3.12/demo/doc/archive/1.3.8/jsPlumbDoc.css b/archive/1.3.12/demo/doc/archive/1.3.8/jsPlumbDoc.css similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.8/jsPlumbDoc.css rename to archive/1.3.12/demo/doc/archive/1.3.8/jsPlumbDoc.css diff --git a/build/1.3.12/demo/doc/archive/1.3.8/usage.html b/archive/1.3.12/demo/doc/archive/1.3.8/usage.html similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.8/usage.html rename to archive/1.3.12/demo/doc/archive/1.3.8/usage.html diff --git a/build/1.3.12/demo/doc/archive/1.3.9/content.html b/archive/1.3.12/demo/doc/archive/1.3.9/content.html similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.9/content.html rename to archive/1.3.12/demo/doc/archive/1.3.9/content.html diff --git a/build/1.3.12/demo/doc/archive/1.3.9/index.html b/archive/1.3.12/demo/doc/archive/1.3.9/index.html similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.9/index.html rename to archive/1.3.12/demo/doc/archive/1.3.9/index.html diff --git a/build/1.3.12/demo/doc/archive/1.3.9/jsPlumbDoc.css b/archive/1.3.12/demo/doc/archive/1.3.9/jsPlumbDoc.css similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.9/jsPlumbDoc.css rename to archive/1.3.12/demo/doc/archive/1.3.9/jsPlumbDoc.css diff --git a/build/1.3.12/demo/doc/archive/1.3.9/usage.html b/archive/1.3.12/demo/doc/archive/1.3.9/usage.html similarity index 100% rename from build/1.3.12/demo/doc/archive/1.3.9/usage.html rename to archive/1.3.12/demo/doc/archive/1.3.9/usage.html diff --git a/build/1.3.12/demo/doc/content.html b/archive/1.3.12/demo/doc/content.html similarity index 100% rename from build/1.3.12/demo/doc/content.html rename to archive/1.3.12/demo/doc/content.html diff --git a/build/1.3.12/demo/doc/index.html b/archive/1.3.12/demo/doc/index.html similarity index 100% rename from build/1.3.12/demo/doc/index.html rename to archive/1.3.12/demo/doc/index.html diff --git a/build/1.3.12/demo/doc/jsPlumbDoc.css b/archive/1.3.12/demo/doc/jsPlumbDoc.css similarity index 100% rename from build/1.3.12/demo/doc/jsPlumbDoc.css rename to archive/1.3.12/demo/doc/jsPlumbDoc.css diff --git a/build/1.3.12/demo/doc/usage.html b/archive/1.3.12/demo/doc/usage.html similarity index 100% rename from build/1.3.12/demo/doc/usage.html rename to archive/1.3.12/demo/doc/usage.html diff --git a/build/1.3.12/demo/img/bigdot.jpg b/archive/1.3.12/demo/img/bigdot.jpg similarity index 100% rename from build/1.3.12/demo/img/bigdot.jpg rename to archive/1.3.12/demo/img/bigdot.jpg diff --git a/build/1.3.12/demo/img/bigdot.png b/archive/1.3.12/demo/img/bigdot.png similarity index 100% rename from build/1.3.12/demo/img/bigdot.png rename to archive/1.3.12/demo/img/bigdot.png diff --git a/build/1.3.12/demo/img/bigdot.xcf b/archive/1.3.12/demo/img/bigdot.xcf similarity index 100% rename from build/1.3.12/demo/img/bigdot.xcf rename to archive/1.3.12/demo/img/bigdot.xcf diff --git a/build/1.3.12/demo/img/dragging_1.jpg b/archive/1.3.12/demo/img/dragging_1.jpg similarity index 100% rename from build/1.3.12/demo/img/dragging_1.jpg rename to archive/1.3.12/demo/img/dragging_1.jpg diff --git a/build/1.3.12/demo/img/dragging_2.jpg b/archive/1.3.12/demo/img/dragging_2.jpg similarity index 100% rename from build/1.3.12/demo/img/dragging_2.jpg rename to archive/1.3.12/demo/img/dragging_2.jpg diff --git a/build/1.3.12/demo/img/dragging_3.jpg b/archive/1.3.12/demo/img/dragging_3.jpg similarity index 100% rename from build/1.3.12/demo/img/dragging_3.jpg rename to archive/1.3.12/demo/img/dragging_3.jpg diff --git a/build/1.3.12/demo/img/dynamicAnchorBg.jpg b/archive/1.3.12/demo/img/dynamicAnchorBg.jpg similarity index 100% rename from build/1.3.12/demo/img/dynamicAnchorBg.jpg rename to archive/1.3.12/demo/img/dynamicAnchorBg.jpg diff --git a/build/1.3.12/demo/img/endpointTest1.png b/archive/1.3.12/demo/img/endpointTest1.png similarity index 100% rename from build/1.3.12/demo/img/endpointTest1.png rename to archive/1.3.12/demo/img/endpointTest1.png diff --git a/build/1.3.12/demo/img/endpointTest1.xcf b/archive/1.3.12/demo/img/endpointTest1.xcf similarity index 100% rename from build/1.3.12/demo/img/endpointTest1.xcf rename to archive/1.3.12/demo/img/endpointTest1.xcf diff --git a/build/1.3.12/demo/img/index-bg.gif b/archive/1.3.12/demo/img/index-bg.gif similarity index 100% rename from build/1.3.12/demo/img/index-bg.gif rename to archive/1.3.12/demo/img/index-bg.gif diff --git a/build/1.3.12/demo/img/issue4_final.jpg b/archive/1.3.12/demo/img/issue4_final.jpg similarity index 100% rename from build/1.3.12/demo/img/issue4_final.jpg rename to archive/1.3.12/demo/img/issue4_final.jpg diff --git a/build/1.3.12/demo/img/littledot.png b/archive/1.3.12/demo/img/littledot.png similarity index 100% rename from build/1.3.12/demo/img/littledot.png rename to archive/1.3.12/demo/img/littledot.png diff --git a/build/1.3.12/demo/img/littledot.xcf b/archive/1.3.12/demo/img/littledot.xcf similarity index 100% rename from build/1.3.12/demo/img/littledot.xcf rename to archive/1.3.12/demo/img/littledot.xcf diff --git a/build/1.3.12/demo/img/pattern.jpg b/archive/1.3.12/demo/img/pattern.jpg similarity index 100% rename from build/1.3.12/demo/img/pattern.jpg rename to archive/1.3.12/demo/img/pattern.jpg diff --git a/build/1.3.12/demo/img/swappedAnchors.jpg b/archive/1.3.12/demo/img/swappedAnchors.jpg similarity index 100% rename from build/1.3.12/demo/img/swappedAnchors.jpg rename to archive/1.3.12/demo/img/swappedAnchors.jpg diff --git a/build/1.3.12/demo/index.html b/archive/1.3.12/demo/index.html similarity index 100% rename from build/1.3.12/demo/index.html rename to archive/1.3.12/demo/index.html diff --git a/build/1.3.12/demo/jquery/anchorDemo.html b/archive/1.3.12/demo/jquery/anchorDemo.html similarity index 100% rename from build/1.3.12/demo/jquery/anchorDemo.html rename to archive/1.3.12/demo/jquery/anchorDemo.html diff --git a/build/1.3.12/demo/jquery/chartDemo.html b/archive/1.3.12/demo/jquery/chartDemo.html similarity index 100% rename from build/1.3.12/demo/jquery/chartDemo.html rename to archive/1.3.12/demo/jquery/chartDemo.html diff --git a/build/1.3.12/demo/jquery/demo.html b/archive/1.3.12/demo/jquery/demo.html similarity index 100% rename from build/1.3.12/demo/jquery/demo.html rename to archive/1.3.12/demo/jquery/demo.html diff --git a/build/1.3.12/demo/jquery/dragAnimDemo.html b/archive/1.3.12/demo/jquery/dragAnimDemo.html similarity index 100% rename from build/1.3.12/demo/jquery/dragAnimDemo.html rename to archive/1.3.12/demo/jquery/dragAnimDemo.html diff --git a/build/1.3.12/demo/jquery/draggableConnectorsDemo.html b/archive/1.3.12/demo/jquery/draggableConnectorsDemo.html similarity index 100% rename from build/1.3.12/demo/jquery/draggableConnectorsDemo.html rename to archive/1.3.12/demo/jquery/draggableConnectorsDemo.html diff --git a/build/1.3.12/demo/jquery/dynamicAnchorsDemo.html b/archive/1.3.12/demo/jquery/dynamicAnchorsDemo.html similarity index 100% rename from build/1.3.12/demo/jquery/dynamicAnchorsDemo.html rename to archive/1.3.12/demo/jquery/dynamicAnchorsDemo.html diff --git a/build/1.3.12/demo/jquery/flowchartConnectorsDemo.html b/archive/1.3.12/demo/jquery/flowchartConnectorsDemo.html similarity index 100% rename from build/1.3.12/demo/jquery/flowchartConnectorsDemo.html rename to archive/1.3.12/demo/jquery/flowchartConnectorsDemo.html diff --git a/build/1.3.12/demo/jquery/loadTest.html b/archive/1.3.12/demo/jquery/loadTest.html similarity index 100% rename from build/1.3.12/demo/jquery/loadTest.html rename to archive/1.3.12/demo/jquery/loadTest.html diff --git a/build/1.3.12/demo/jquery/makeSourceDemo.html b/archive/1.3.12/demo/jquery/makeSourceDemo.html similarity index 100% rename from build/1.3.12/demo/jquery/makeSourceDemo.html rename to archive/1.3.12/demo/jquery/makeSourceDemo.html diff --git a/build/1.3.12/demo/jquery/makeTargetDemo.html b/archive/1.3.12/demo/jquery/makeTargetDemo.html similarity index 100% rename from build/1.3.12/demo/jquery/makeTargetDemo.html rename to archive/1.3.12/demo/jquery/makeTargetDemo.html diff --git a/build/1.3.12/demo/jquery/stateMachineDemo.html b/archive/1.3.12/demo/jquery/stateMachineDemo.html similarity index 100% rename from build/1.3.12/demo/jquery/stateMachineDemo.html rename to archive/1.3.12/demo/jquery/stateMachineDemo.html diff --git a/build/1.3.12/demo/js/anchorDemo-jquery.js b/archive/1.3.12/demo/js/anchorDemo-jquery.js similarity index 100% rename from build/1.3.12/demo/js/anchorDemo-jquery.js rename to archive/1.3.12/demo/js/anchorDemo-jquery.js diff --git a/build/1.3.12/demo/js/anchorDemo-mootools.js b/archive/1.3.12/demo/js/anchorDemo-mootools.js similarity index 100% rename from build/1.3.12/demo/js/anchorDemo-mootools.js rename to archive/1.3.12/demo/js/anchorDemo-mootools.js diff --git a/build/1.3.12/demo/js/anchorDemo-yui3.js b/archive/1.3.12/demo/js/anchorDemo-yui3.js similarity index 100% rename from build/1.3.12/demo/js/anchorDemo-yui3.js rename to archive/1.3.12/demo/js/anchorDemo-yui3.js diff --git a/build/1.3.12/demo/js/anchorDemo.js b/archive/1.3.12/demo/js/anchorDemo.js similarity index 100% rename from build/1.3.12/demo/js/anchorDemo.js rename to archive/1.3.12/demo/js/anchorDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.0/anchorDemo.js b/archive/1.3.12/demo/js/archive/1.3.0/anchorDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.0/anchorDemo.js rename to archive/1.3.12/demo/js/archive/1.3.0/anchorDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.0/chartDemo.js b/archive/1.3.12/demo/js/archive/1.3.0/chartDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.0/chartDemo.js rename to archive/1.3.12/demo/js/archive/1.3.0/chartDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.0/demo-helper-jquery.js b/archive/1.3.12/demo/js/archive/1.3.0/demo-helper-jquery.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.0/demo-helper-jquery.js rename to archive/1.3.12/demo/js/archive/1.3.0/demo-helper-jquery.js diff --git a/build/1.3.12/demo/js/archive/1.3.0/demo-helper-mootools.js b/archive/1.3.12/demo/js/archive/1.3.0/demo-helper-mootools.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.0/demo-helper-mootools.js rename to archive/1.3.12/demo/js/archive/1.3.0/demo-helper-mootools.js diff --git a/build/1.3.12/demo/js/archive/1.3.0/demo-helper-yui3.js b/archive/1.3.12/demo/js/archive/1.3.0/demo-helper-yui3.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.0/demo-helper-yui3.js rename to archive/1.3.12/demo/js/archive/1.3.0/demo-helper-yui3.js diff --git a/build/1.3.12/demo/js/archive/1.3.0/demo.js b/archive/1.3.12/demo/js/archive/1.3.0/demo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.0/demo.js rename to archive/1.3.12/demo/js/archive/1.3.0/demo.js diff --git a/build/1.3.12/demo/js/archive/1.3.0/dragAnimDemo-jquery.js b/archive/1.3.12/demo/js/archive/1.3.0/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.0/dragAnimDemo-jquery.js rename to archive/1.3.12/demo/js/archive/1.3.0/dragAnimDemo-jquery.js diff --git a/build/1.3.12/demo/js/archive/1.3.0/dragAnimDemo-mootools.js b/archive/1.3.12/demo/js/archive/1.3.0/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.0/dragAnimDemo-mootools.js rename to archive/1.3.12/demo/js/archive/1.3.0/dragAnimDemo-mootools.js diff --git a/build/1.3.12/demo/js/archive/1.3.0/dragAnimDemo-yui3.js b/archive/1.3.12/demo/js/archive/1.3.0/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.0/dragAnimDemo-yui3.js rename to archive/1.3.12/demo/js/archive/1.3.0/dragAnimDemo-yui3.js diff --git a/build/1.3.12/demo/js/archive/1.3.0/dragAnimDemo.js b/archive/1.3.12/demo/js/archive/1.3.0/dragAnimDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.0/dragAnimDemo.js rename to archive/1.3.12/demo/js/archive/1.3.0/dragAnimDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js b/archive/1.3.12/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js rename to archive/1.3.12/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.12/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js b/archive/1.3.12/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js rename to archive/1.3.12/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.12/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js b/archive/1.3.12/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js rename to archive/1.3.12/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.12/demo/js/archive/1.3.0/draggableConnectorsDemo.js b/archive/1.3.12/demo/js/archive/1.3.0/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.0/draggableConnectorsDemo.js rename to archive/1.3.12/demo/js/archive/1.3.0/draggableConnectorsDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.0/dynamicAnchorsDemo.js b/archive/1.3.12/demo/js/archive/1.3.0/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.0/dynamicAnchorsDemo.js rename to archive/1.3.12/demo/js/archive/1.3.0/dynamicAnchorsDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.0/flowchartConnectorsDemo.js b/archive/1.3.12/demo/js/archive/1.3.0/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.0/flowchartConnectorsDemo.js rename to archive/1.3.12/demo/js/archive/1.3.0/flowchartConnectorsDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.1/anchorDemo.js b/archive/1.3.12/demo/js/archive/1.3.1/anchorDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.1/anchorDemo.js rename to archive/1.3.12/demo/js/archive/1.3.1/anchorDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.1/chartDemo.js b/archive/1.3.12/demo/js/archive/1.3.1/chartDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.1/chartDemo.js rename to archive/1.3.12/demo/js/archive/1.3.1/chartDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.1/demo-helper-jquery.js b/archive/1.3.12/demo/js/archive/1.3.1/demo-helper-jquery.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.1/demo-helper-jquery.js rename to archive/1.3.12/demo/js/archive/1.3.1/demo-helper-jquery.js diff --git a/build/1.3.12/demo/js/archive/1.3.1/demo-helper-mootools.js b/archive/1.3.12/demo/js/archive/1.3.1/demo-helper-mootools.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.1/demo-helper-mootools.js rename to archive/1.3.12/demo/js/archive/1.3.1/demo-helper-mootools.js diff --git a/build/1.3.12/demo/js/archive/1.3.1/demo-helper-yui3.js b/archive/1.3.12/demo/js/archive/1.3.1/demo-helper-yui3.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.1/demo-helper-yui3.js rename to archive/1.3.12/demo/js/archive/1.3.1/demo-helper-yui3.js diff --git a/build/1.3.12/demo/js/archive/1.3.1/demo.js b/archive/1.3.12/demo/js/archive/1.3.1/demo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.1/demo.js rename to archive/1.3.12/demo/js/archive/1.3.1/demo.js diff --git a/build/1.3.12/demo/js/archive/1.3.1/dragAnimDemo-jquery.js b/archive/1.3.12/demo/js/archive/1.3.1/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.1/dragAnimDemo-jquery.js rename to archive/1.3.12/demo/js/archive/1.3.1/dragAnimDemo-jquery.js diff --git a/build/1.3.12/demo/js/archive/1.3.1/dragAnimDemo-mootools.js b/archive/1.3.12/demo/js/archive/1.3.1/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.1/dragAnimDemo-mootools.js rename to archive/1.3.12/demo/js/archive/1.3.1/dragAnimDemo-mootools.js diff --git a/build/1.3.12/demo/js/archive/1.3.1/dragAnimDemo-yui3.js b/archive/1.3.12/demo/js/archive/1.3.1/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.1/dragAnimDemo-yui3.js rename to archive/1.3.12/demo/js/archive/1.3.1/dragAnimDemo-yui3.js diff --git a/build/1.3.12/demo/js/archive/1.3.1/dragAnimDemo.js b/archive/1.3.12/demo/js/archive/1.3.1/dragAnimDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.1/dragAnimDemo.js rename to archive/1.3.12/demo/js/archive/1.3.1/dragAnimDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js b/archive/1.3.12/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js rename to archive/1.3.12/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.12/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js b/archive/1.3.12/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js rename to archive/1.3.12/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.12/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js b/archive/1.3.12/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js rename to archive/1.3.12/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.12/demo/js/archive/1.3.1/draggableConnectorsDemo.js b/archive/1.3.12/demo/js/archive/1.3.1/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.1/draggableConnectorsDemo.js rename to archive/1.3.12/demo/js/archive/1.3.1/draggableConnectorsDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.1/dynamicAnchorsDemo.js b/archive/1.3.12/demo/js/archive/1.3.1/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.1/dynamicAnchorsDemo.js rename to archive/1.3.12/demo/js/archive/1.3.1/dynamicAnchorsDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.1/flowchartConnectorsDemo.js b/archive/1.3.12/demo/js/archive/1.3.1/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.1/flowchartConnectorsDemo.js rename to archive/1.3.12/demo/js/archive/1.3.1/flowchartConnectorsDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.2/anchorDemo.js b/archive/1.3.12/demo/js/archive/1.3.2/anchorDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.2/anchorDemo.js rename to archive/1.3.12/demo/js/archive/1.3.2/anchorDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.2/chartDemo.js b/archive/1.3.12/demo/js/archive/1.3.2/chartDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.2/chartDemo.js rename to archive/1.3.12/demo/js/archive/1.3.2/chartDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.2/demo-helper-jquery.js b/archive/1.3.12/demo/js/archive/1.3.2/demo-helper-jquery.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.2/demo-helper-jquery.js rename to archive/1.3.12/demo/js/archive/1.3.2/demo-helper-jquery.js diff --git a/build/1.3.12/demo/js/archive/1.3.2/demo-helper-mootools.js b/archive/1.3.12/demo/js/archive/1.3.2/demo-helper-mootools.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.2/demo-helper-mootools.js rename to archive/1.3.12/demo/js/archive/1.3.2/demo-helper-mootools.js diff --git a/build/1.3.12/demo/js/archive/1.3.2/demo-helper-yui3.js b/archive/1.3.12/demo/js/archive/1.3.2/demo-helper-yui3.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.2/demo-helper-yui3.js rename to archive/1.3.12/demo/js/archive/1.3.2/demo-helper-yui3.js diff --git a/build/1.3.12/demo/js/archive/1.3.2/demo.js b/archive/1.3.12/demo/js/archive/1.3.2/demo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.2/demo.js rename to archive/1.3.12/demo/js/archive/1.3.2/demo.js diff --git a/build/1.3.12/demo/js/archive/1.3.2/dragAnimDemo-jquery.js b/archive/1.3.12/demo/js/archive/1.3.2/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.2/dragAnimDemo-jquery.js rename to archive/1.3.12/demo/js/archive/1.3.2/dragAnimDemo-jquery.js diff --git a/build/1.3.12/demo/js/archive/1.3.2/dragAnimDemo-mootools.js b/archive/1.3.12/demo/js/archive/1.3.2/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.2/dragAnimDemo-mootools.js rename to archive/1.3.12/demo/js/archive/1.3.2/dragAnimDemo-mootools.js diff --git a/build/1.3.12/demo/js/archive/1.3.2/dragAnimDemo-yui3.js b/archive/1.3.12/demo/js/archive/1.3.2/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.2/dragAnimDemo-yui3.js rename to archive/1.3.12/demo/js/archive/1.3.2/dragAnimDemo-yui3.js diff --git a/build/1.3.12/demo/js/archive/1.3.2/dragAnimDemo.js b/archive/1.3.12/demo/js/archive/1.3.2/dragAnimDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.2/dragAnimDemo.js rename to archive/1.3.12/demo/js/archive/1.3.2/dragAnimDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js b/archive/1.3.12/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js rename to archive/1.3.12/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.12/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js b/archive/1.3.12/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js rename to archive/1.3.12/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.12/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js b/archive/1.3.12/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js rename to archive/1.3.12/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.12/demo/js/archive/1.3.2/draggableConnectorsDemo.js b/archive/1.3.12/demo/js/archive/1.3.2/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.2/draggableConnectorsDemo.js rename to archive/1.3.12/demo/js/archive/1.3.2/draggableConnectorsDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.2/dynamicAnchorsDemo.js b/archive/1.3.12/demo/js/archive/1.3.2/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.2/dynamicAnchorsDemo.js rename to archive/1.3.12/demo/js/archive/1.3.2/dynamicAnchorsDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.2/flowchartConnectorsDemo.js b/archive/1.3.12/demo/js/archive/1.3.2/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.2/flowchartConnectorsDemo.js rename to archive/1.3.12/demo/js/archive/1.3.2/flowchartConnectorsDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.3/anchorDemo.js b/archive/1.3.12/demo/js/archive/1.3.3/anchorDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.3/anchorDemo.js rename to archive/1.3.12/demo/js/archive/1.3.3/anchorDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.3/chartDemo.js b/archive/1.3.12/demo/js/archive/1.3.3/chartDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.3/chartDemo.js rename to archive/1.3.12/demo/js/archive/1.3.3/chartDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.3/demo-helper-jquery.js b/archive/1.3.12/demo/js/archive/1.3.3/demo-helper-jquery.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.3/demo-helper-jquery.js rename to archive/1.3.12/demo/js/archive/1.3.3/demo-helper-jquery.js diff --git a/build/1.3.12/demo/js/archive/1.3.3/demo-helper-mootools.js b/archive/1.3.12/demo/js/archive/1.3.3/demo-helper-mootools.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.3/demo-helper-mootools.js rename to archive/1.3.12/demo/js/archive/1.3.3/demo-helper-mootools.js diff --git a/build/1.3.12/demo/js/archive/1.3.3/demo-helper-yui3.js b/archive/1.3.12/demo/js/archive/1.3.3/demo-helper-yui3.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.3/demo-helper-yui3.js rename to archive/1.3.12/demo/js/archive/1.3.3/demo-helper-yui3.js diff --git a/build/1.3.12/demo/js/archive/1.3.3/demo.js b/archive/1.3.12/demo/js/archive/1.3.3/demo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.3/demo.js rename to archive/1.3.12/demo/js/archive/1.3.3/demo.js diff --git a/build/1.3.12/demo/js/archive/1.3.3/dragAnimDemo-jquery.js b/archive/1.3.12/demo/js/archive/1.3.3/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.3/dragAnimDemo-jquery.js rename to archive/1.3.12/demo/js/archive/1.3.3/dragAnimDemo-jquery.js diff --git a/build/1.3.12/demo/js/archive/1.3.3/dragAnimDemo-mootools.js b/archive/1.3.12/demo/js/archive/1.3.3/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.3/dragAnimDemo-mootools.js rename to archive/1.3.12/demo/js/archive/1.3.3/dragAnimDemo-mootools.js diff --git a/build/1.3.12/demo/js/archive/1.3.3/dragAnimDemo-yui3.js b/archive/1.3.12/demo/js/archive/1.3.3/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.3/dragAnimDemo-yui3.js rename to archive/1.3.12/demo/js/archive/1.3.3/dragAnimDemo-yui3.js diff --git a/build/1.3.12/demo/js/archive/1.3.3/dragAnimDemo.js b/archive/1.3.12/demo/js/archive/1.3.3/dragAnimDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.3/dragAnimDemo.js rename to archive/1.3.12/demo/js/archive/1.3.3/dragAnimDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js b/archive/1.3.12/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js rename to archive/1.3.12/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.12/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js b/archive/1.3.12/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js rename to archive/1.3.12/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.12/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js b/archive/1.3.12/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js rename to archive/1.3.12/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.12/demo/js/archive/1.3.3/draggableConnectorsDemo.js b/archive/1.3.12/demo/js/archive/1.3.3/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.3/draggableConnectorsDemo.js rename to archive/1.3.12/demo/js/archive/1.3.3/draggableConnectorsDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.3/dynamicAnchorsDemo.js b/archive/1.3.12/demo/js/archive/1.3.3/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.3/dynamicAnchorsDemo.js rename to archive/1.3.12/demo/js/archive/1.3.3/dynamicAnchorsDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.3/flowchartConnectorsDemo.js b/archive/1.3.12/demo/js/archive/1.3.3/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.3/flowchartConnectorsDemo.js rename to archive/1.3.12/demo/js/archive/1.3.3/flowchartConnectorsDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js b/archive/1.3.12/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js rename to archive/1.3.12/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js diff --git a/build/1.3.12/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all.js b/archive/1.3.12/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all.js rename to archive/1.3.12/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all.js diff --git a/build/1.3.12/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js b/archive/1.3.12/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js rename to archive/1.3.12/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js diff --git a/build/1.3.12/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js b/archive/1.3.12/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js rename to archive/1.3.12/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js diff --git a/build/1.3.12/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js b/archive/1.3.12/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js rename to archive/1.3.12/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js diff --git a/build/1.3.12/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js b/archive/1.3.12/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js rename to archive/1.3.12/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js diff --git a/build/1.3.12/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js b/archive/1.3.12/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js rename to archive/1.3.12/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js diff --git a/build/1.3.12/demo/js/archive/1.3.4/anchorDemo.js b/archive/1.3.12/demo/js/archive/1.3.4/anchorDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.4/anchorDemo.js rename to archive/1.3.12/demo/js/archive/1.3.4/anchorDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.4/chartDemo.js b/archive/1.3.12/demo/js/archive/1.3.4/chartDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.4/chartDemo.js rename to archive/1.3.12/demo/js/archive/1.3.4/chartDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.4/demo-helper-jquery.js b/archive/1.3.12/demo/js/archive/1.3.4/demo-helper-jquery.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.4/demo-helper-jquery.js rename to archive/1.3.12/demo/js/archive/1.3.4/demo-helper-jquery.js diff --git a/build/1.3.12/demo/js/archive/1.3.4/demo-helper-mootools.js b/archive/1.3.12/demo/js/archive/1.3.4/demo-helper-mootools.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.4/demo-helper-mootools.js rename to archive/1.3.12/demo/js/archive/1.3.4/demo-helper-mootools.js diff --git a/build/1.3.12/demo/js/archive/1.3.4/demo-helper-yui3.js b/archive/1.3.12/demo/js/archive/1.3.4/demo-helper-yui3.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.4/demo-helper-yui3.js rename to archive/1.3.12/demo/js/archive/1.3.4/demo-helper-yui3.js diff --git a/build/1.3.12/demo/js/archive/1.3.4/demo-list.js b/archive/1.3.12/demo/js/archive/1.3.4/demo-list.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.4/demo-list.js rename to archive/1.3.12/demo/js/archive/1.3.4/demo-list.js diff --git a/build/1.3.12/demo/js/archive/1.3.4/demo.js b/archive/1.3.12/demo/js/archive/1.3.4/demo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.4/demo.js rename to archive/1.3.12/demo/js/archive/1.3.4/demo.js diff --git a/build/1.3.12/demo/js/archive/1.3.4/dragAnimDemo-jquery.js b/archive/1.3.12/demo/js/archive/1.3.4/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.4/dragAnimDemo-jquery.js rename to archive/1.3.12/demo/js/archive/1.3.4/dragAnimDemo-jquery.js diff --git a/build/1.3.12/demo/js/archive/1.3.4/dragAnimDemo-mootools.js b/archive/1.3.12/demo/js/archive/1.3.4/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.4/dragAnimDemo-mootools.js rename to archive/1.3.12/demo/js/archive/1.3.4/dragAnimDemo-mootools.js diff --git a/build/1.3.12/demo/js/archive/1.3.4/dragAnimDemo-yui3.js b/archive/1.3.12/demo/js/archive/1.3.4/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.4/dragAnimDemo-yui3.js rename to archive/1.3.12/demo/js/archive/1.3.4/dragAnimDemo-yui3.js diff --git a/build/1.3.12/demo/js/archive/1.3.4/dragAnimDemo.js b/archive/1.3.12/demo/js/archive/1.3.4/dragAnimDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.4/dragAnimDemo.js rename to archive/1.3.12/demo/js/archive/1.3.4/dragAnimDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.4/draggableConnectorsDemo-jquery.js b/archive/1.3.12/demo/js/archive/1.3.4/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.4/draggableConnectorsDemo-jquery.js rename to archive/1.3.12/demo/js/archive/1.3.4/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.12/demo/js/archive/1.3.4/draggableConnectorsDemo-mootools.js b/archive/1.3.12/demo/js/archive/1.3.4/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.4/draggableConnectorsDemo-mootools.js rename to archive/1.3.12/demo/js/archive/1.3.4/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.12/demo/js/archive/1.3.4/draggableConnectorsDemo-yui3.js b/archive/1.3.12/demo/js/archive/1.3.4/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.4/draggableConnectorsDemo-yui3.js rename to archive/1.3.12/demo/js/archive/1.3.4/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.12/demo/js/archive/1.3.4/draggableConnectorsDemo.js b/archive/1.3.12/demo/js/archive/1.3.4/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.4/draggableConnectorsDemo.js rename to archive/1.3.12/demo/js/archive/1.3.4/draggableConnectorsDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.4/dynamicAnchorsDemo.js b/archive/1.3.12/demo/js/archive/1.3.4/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.4/dynamicAnchorsDemo.js rename to archive/1.3.12/demo/js/archive/1.3.4/dynamicAnchorsDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.4/flowchartConnectorsDemo.js b/archive/1.3.12/demo/js/archive/1.3.4/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.4/flowchartConnectorsDemo.js rename to archive/1.3.12/demo/js/archive/1.3.4/flowchartConnectorsDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.4/makeSourceDemo.js b/archive/1.3.12/demo/js/archive/1.3.4/makeSourceDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.4/makeSourceDemo.js rename to archive/1.3.12/demo/js/archive/1.3.4/makeSourceDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.4/makeTargetDemo.js b/archive/1.3.12/demo/js/archive/1.3.4/makeTargetDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.4/makeTargetDemo.js rename to archive/1.3.12/demo/js/archive/1.3.4/makeTargetDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.4/stateMachineDemo-jquery.js b/archive/1.3.12/demo/js/archive/1.3.4/stateMachineDemo-jquery.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.4/stateMachineDemo-jquery.js rename to archive/1.3.12/demo/js/archive/1.3.4/stateMachineDemo-jquery.js diff --git a/build/1.3.12/demo/js/archive/1.3.4/stateMachineDemo-mootools.js b/archive/1.3.12/demo/js/archive/1.3.4/stateMachineDemo-mootools.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.4/stateMachineDemo-mootools.js rename to archive/1.3.12/demo/js/archive/1.3.4/stateMachineDemo-mootools.js diff --git a/build/1.3.12/demo/js/archive/1.3.4/stateMachineDemo-yui3.js b/archive/1.3.12/demo/js/archive/1.3.4/stateMachineDemo-yui3.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.4/stateMachineDemo-yui3.js rename to archive/1.3.12/demo/js/archive/1.3.4/stateMachineDemo-yui3.js diff --git a/build/1.3.12/demo/js/archive/1.3.4/stateMachineDemo.js b/archive/1.3.12/demo/js/archive/1.3.4/stateMachineDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.4/stateMachineDemo.js rename to archive/1.3.12/demo/js/archive/1.3.4/stateMachineDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.5/anchorDemo.js b/archive/1.3.12/demo/js/archive/1.3.5/anchorDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.5/anchorDemo.js rename to archive/1.3.12/demo/js/archive/1.3.5/anchorDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.5/chartDemo.js b/archive/1.3.12/demo/js/archive/1.3.5/chartDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.5/chartDemo.js rename to archive/1.3.12/demo/js/archive/1.3.5/chartDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.5/demo-helper-jquery.js b/archive/1.3.12/demo/js/archive/1.3.5/demo-helper-jquery.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.5/demo-helper-jquery.js rename to archive/1.3.12/demo/js/archive/1.3.5/demo-helper-jquery.js diff --git a/build/1.3.12/demo/js/archive/1.3.5/demo-helper-mootools.js b/archive/1.3.12/demo/js/archive/1.3.5/demo-helper-mootools.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.5/demo-helper-mootools.js rename to archive/1.3.12/demo/js/archive/1.3.5/demo-helper-mootools.js diff --git a/build/1.3.12/demo/js/archive/1.3.5/demo-helper-yui3.js b/archive/1.3.12/demo/js/archive/1.3.5/demo-helper-yui3.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.5/demo-helper-yui3.js rename to archive/1.3.12/demo/js/archive/1.3.5/demo-helper-yui3.js diff --git a/build/1.3.12/demo/js/archive/1.3.5/demo-list.js b/archive/1.3.12/demo/js/archive/1.3.5/demo-list.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.5/demo-list.js rename to archive/1.3.12/demo/js/archive/1.3.5/demo-list.js diff --git a/build/1.3.12/demo/js/archive/1.3.5/demo.js b/archive/1.3.12/demo/js/archive/1.3.5/demo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.5/demo.js rename to archive/1.3.12/demo/js/archive/1.3.5/demo.js diff --git a/build/1.3.12/demo/js/archive/1.3.5/dragAnimDemo-jquery.js b/archive/1.3.12/demo/js/archive/1.3.5/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.5/dragAnimDemo-jquery.js rename to archive/1.3.12/demo/js/archive/1.3.5/dragAnimDemo-jquery.js diff --git a/build/1.3.12/demo/js/archive/1.3.5/dragAnimDemo-mootools.js b/archive/1.3.12/demo/js/archive/1.3.5/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.5/dragAnimDemo-mootools.js rename to archive/1.3.12/demo/js/archive/1.3.5/dragAnimDemo-mootools.js diff --git a/build/1.3.12/demo/js/archive/1.3.5/dragAnimDemo-yui3.js b/archive/1.3.12/demo/js/archive/1.3.5/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.5/dragAnimDemo-yui3.js rename to archive/1.3.12/demo/js/archive/1.3.5/dragAnimDemo-yui3.js diff --git a/build/1.3.12/demo/js/archive/1.3.5/dragAnimDemo.js b/archive/1.3.12/demo/js/archive/1.3.5/dragAnimDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.5/dragAnimDemo.js rename to archive/1.3.12/demo/js/archive/1.3.5/dragAnimDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.5/draggableConnectorsDemo-jquery.js b/archive/1.3.12/demo/js/archive/1.3.5/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.5/draggableConnectorsDemo-jquery.js rename to archive/1.3.12/demo/js/archive/1.3.5/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.12/demo/js/archive/1.3.5/draggableConnectorsDemo-mootools.js b/archive/1.3.12/demo/js/archive/1.3.5/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.5/draggableConnectorsDemo-mootools.js rename to archive/1.3.12/demo/js/archive/1.3.5/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.12/demo/js/archive/1.3.5/draggableConnectorsDemo-yui3.js b/archive/1.3.12/demo/js/archive/1.3.5/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.5/draggableConnectorsDemo-yui3.js rename to archive/1.3.12/demo/js/archive/1.3.5/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.12/demo/js/archive/1.3.5/draggableConnectorsDemo.js b/archive/1.3.12/demo/js/archive/1.3.5/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.5/draggableConnectorsDemo.js rename to archive/1.3.12/demo/js/archive/1.3.5/draggableConnectorsDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.5/dynamicAnchorsDemo.js b/archive/1.3.12/demo/js/archive/1.3.5/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.5/dynamicAnchorsDemo.js rename to archive/1.3.12/demo/js/archive/1.3.5/dynamicAnchorsDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.5/flowchartConnectorsDemo.js b/archive/1.3.12/demo/js/archive/1.3.5/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.5/flowchartConnectorsDemo.js rename to archive/1.3.12/demo/js/archive/1.3.5/flowchartConnectorsDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.5/loadTest.js b/archive/1.3.12/demo/js/archive/1.3.5/loadTest.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.5/loadTest.js rename to archive/1.3.12/demo/js/archive/1.3.5/loadTest.js diff --git a/build/1.3.12/demo/js/archive/1.3.5/makeSourceDemo.js b/archive/1.3.12/demo/js/archive/1.3.5/makeSourceDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.5/makeSourceDemo.js rename to archive/1.3.12/demo/js/archive/1.3.5/makeSourceDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.5/makeTargetDemo.js b/archive/1.3.12/demo/js/archive/1.3.5/makeTargetDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.5/makeTargetDemo.js rename to archive/1.3.12/demo/js/archive/1.3.5/makeTargetDemo.js diff --git a/build/1.3.12/demo/js/archive/1.3.5/stateMachineDemo-jquery.js b/archive/1.3.12/demo/js/archive/1.3.5/stateMachineDemo-jquery.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.5/stateMachineDemo-jquery.js rename to archive/1.3.12/demo/js/archive/1.3.5/stateMachineDemo-jquery.js diff --git a/build/1.3.12/demo/js/archive/1.3.5/stateMachineDemo-mootools.js b/archive/1.3.12/demo/js/archive/1.3.5/stateMachineDemo-mootools.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.5/stateMachineDemo-mootools.js rename to archive/1.3.12/demo/js/archive/1.3.5/stateMachineDemo-mootools.js diff --git a/build/1.3.12/demo/js/archive/1.3.5/stateMachineDemo-yui3.js b/archive/1.3.12/demo/js/archive/1.3.5/stateMachineDemo-yui3.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.5/stateMachineDemo-yui3.js rename to archive/1.3.12/demo/js/archive/1.3.5/stateMachineDemo-yui3.js diff --git a/build/1.3.12/demo/js/archive/1.3.5/stateMachineDemo.js b/archive/1.3.12/demo/js/archive/1.3.5/stateMachineDemo.js similarity index 100% rename from build/1.3.12/demo/js/archive/1.3.5/stateMachineDemo.js rename to archive/1.3.12/demo/js/archive/1.3.5/stateMachineDemo.js diff --git a/build/1.3.12/demo/js/chartDemo.js b/archive/1.3.12/demo/js/chartDemo.js similarity index 100% rename from build/1.3.12/demo/js/chartDemo.js rename to archive/1.3.12/demo/js/chartDemo.js diff --git a/build/1.3.12/demo/js/demo-helper-jquery.js b/archive/1.3.12/demo/js/demo-helper-jquery.js similarity index 100% rename from build/1.3.12/demo/js/demo-helper-jquery.js rename to archive/1.3.12/demo/js/demo-helper-jquery.js diff --git a/build/1.3.12/demo/js/demo-helper-mootools.js b/archive/1.3.12/demo/js/demo-helper-mootools.js similarity index 100% rename from build/1.3.12/demo/js/demo-helper-mootools.js rename to archive/1.3.12/demo/js/demo-helper-mootools.js diff --git a/build/1.3.12/demo/js/demo-helper-yui3.js b/archive/1.3.12/demo/js/demo-helper-yui3.js similarity index 100% rename from build/1.3.12/demo/js/demo-helper-yui3.js rename to archive/1.3.12/demo/js/demo-helper-yui3.js diff --git a/build/1.3.12/demo/js/demo-list.js b/archive/1.3.12/demo/js/demo-list.js similarity index 100% rename from build/1.3.12/demo/js/demo-list.js rename to archive/1.3.12/demo/js/demo-list.js diff --git a/build/1.3.12/demo/js/demo.js b/archive/1.3.12/demo/js/demo.js similarity index 100% rename from build/1.3.12/demo/js/demo.js rename to archive/1.3.12/demo/js/demo.js diff --git a/build/1.3.12/demo/js/dragAnimDemo-jquery.js b/archive/1.3.12/demo/js/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.12/demo/js/dragAnimDemo-jquery.js rename to archive/1.3.12/demo/js/dragAnimDemo-jquery.js diff --git a/build/1.3.12/demo/js/dragAnimDemo-mootools.js b/archive/1.3.12/demo/js/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.12/demo/js/dragAnimDemo-mootools.js rename to archive/1.3.12/demo/js/dragAnimDemo-mootools.js diff --git a/build/1.3.12/demo/js/dragAnimDemo-yui3.js b/archive/1.3.12/demo/js/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.12/demo/js/dragAnimDemo-yui3.js rename to archive/1.3.12/demo/js/dragAnimDemo-yui3.js diff --git a/build/1.3.12/demo/js/dragAnimDemo.js b/archive/1.3.12/demo/js/dragAnimDemo.js similarity index 100% rename from build/1.3.12/demo/js/dragAnimDemo.js rename to archive/1.3.12/demo/js/dragAnimDemo.js diff --git a/build/1.3.12/demo/js/draggableConnectorsDemo-jquery.js b/archive/1.3.12/demo/js/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.12/demo/js/draggableConnectorsDemo-jquery.js rename to archive/1.3.12/demo/js/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.12/demo/js/draggableConnectorsDemo-mootools.js b/archive/1.3.12/demo/js/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.12/demo/js/draggableConnectorsDemo-mootools.js rename to archive/1.3.12/demo/js/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.12/demo/js/draggableConnectorsDemo-yui3.js b/archive/1.3.12/demo/js/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.12/demo/js/draggableConnectorsDemo-yui3.js rename to archive/1.3.12/demo/js/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.12/demo/js/draggableConnectorsDemo.js b/archive/1.3.12/demo/js/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.12/demo/js/draggableConnectorsDemo.js rename to archive/1.3.12/demo/js/draggableConnectorsDemo.js diff --git a/build/1.3.12/demo/js/dynamicAnchorsDemo.js b/archive/1.3.12/demo/js/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.12/demo/js/dynamicAnchorsDemo.js rename to archive/1.3.12/demo/js/dynamicAnchorsDemo.js diff --git a/build/1.3.12/demo/js/flowchartConnectorsDemo.js b/archive/1.3.12/demo/js/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.12/demo/js/flowchartConnectorsDemo.js rename to archive/1.3.12/demo/js/flowchartConnectorsDemo.js diff --git a/build/1.3.12/demo/js/jquery.jsPlumb-1.3.12-all-min.js b/archive/1.3.12/demo/js/jquery.jsPlumb-1.3.12-all-min.js similarity index 100% rename from build/1.3.12/demo/js/jquery.jsPlumb-1.3.12-all-min.js rename to archive/1.3.12/demo/js/jquery.jsPlumb-1.3.12-all-min.js diff --git a/build/1.3.12/demo/js/jquery.jsPlumb-1.3.12-all.js b/archive/1.3.12/demo/js/jquery.jsPlumb-1.3.12-all.js similarity index 100% rename from build/1.3.12/demo/js/jquery.jsPlumb-1.3.12-all.js rename to archive/1.3.12/demo/js/jquery.jsPlumb-1.3.12-all.js diff --git a/build/1.3.12/demo/js/loadTest.js b/archive/1.3.12/demo/js/loadTest.js similarity index 100% rename from build/1.3.12/demo/js/loadTest.js rename to archive/1.3.12/demo/js/loadTest.js diff --git a/build/1.3.12/demo/js/makeSourceDemo.js b/archive/1.3.12/demo/js/makeSourceDemo.js similarity index 100% rename from build/1.3.12/demo/js/makeSourceDemo.js rename to archive/1.3.12/demo/js/makeSourceDemo.js diff --git a/build/1.3.12/demo/js/makeTargetDemo.js b/archive/1.3.12/demo/js/makeTargetDemo.js similarity index 100% rename from build/1.3.12/demo/js/makeTargetDemo.js rename to archive/1.3.12/demo/js/makeTargetDemo.js diff --git a/build/1.3.12/demo/js/mootools-1.3.2.1-more.js b/archive/1.3.12/demo/js/mootools-1.3.2.1-more.js similarity index 100% rename from build/1.3.12/demo/js/mootools-1.3.2.1-more.js rename to archive/1.3.12/demo/js/mootools-1.3.2.1-more.js diff --git a/build/1.3.12/demo/js/mootools.jsPlumb-1.3.12-all-min.js b/archive/1.3.12/demo/js/mootools.jsPlumb-1.3.12-all-min.js similarity index 100% rename from build/1.3.12/demo/js/mootools.jsPlumb-1.3.12-all-min.js rename to archive/1.3.12/demo/js/mootools.jsPlumb-1.3.12-all-min.js diff --git a/build/1.3.12/demo/js/mootools.jsPlumb-1.3.12-all.js b/archive/1.3.12/demo/js/mootools.jsPlumb-1.3.12-all.js similarity index 100% rename from build/1.3.12/demo/js/mootools.jsPlumb-1.3.12-all.js rename to archive/1.3.12/demo/js/mootools.jsPlumb-1.3.12-all.js diff --git a/build/1.3.12/demo/js/stateMachineDemo-jquery.js b/archive/1.3.12/demo/js/stateMachineDemo-jquery.js similarity index 100% rename from build/1.3.12/demo/js/stateMachineDemo-jquery.js rename to archive/1.3.12/demo/js/stateMachineDemo-jquery.js diff --git a/build/1.3.12/demo/js/stateMachineDemo-mootools.js b/archive/1.3.12/demo/js/stateMachineDemo-mootools.js similarity index 100% rename from build/1.3.12/demo/js/stateMachineDemo-mootools.js rename to archive/1.3.12/demo/js/stateMachineDemo-mootools.js diff --git a/build/1.3.12/demo/js/stateMachineDemo-yui3.js b/archive/1.3.12/demo/js/stateMachineDemo-yui3.js similarity index 100% rename from build/1.3.12/demo/js/stateMachineDemo-yui3.js rename to archive/1.3.12/demo/js/stateMachineDemo-yui3.js diff --git a/build/1.3.12/demo/js/stateMachineDemo.js b/archive/1.3.12/demo/js/stateMachineDemo.js similarity index 100% rename from build/1.3.12/demo/js/stateMachineDemo.js rename to archive/1.3.12/demo/js/stateMachineDemo.js diff --git a/build/1.3.12/demo/js/yui-3.3.0-min.js b/archive/1.3.12/demo/js/yui-3.3.0-min.js similarity index 100% rename from build/1.3.12/demo/js/yui-3.3.0-min.js rename to archive/1.3.12/demo/js/yui-3.3.0-min.js diff --git a/build/1.3.12/demo/js/yui.jsPlumb-1.3.12-all-min.js b/archive/1.3.12/demo/js/yui.jsPlumb-1.3.12-all-min.js similarity index 100% rename from build/1.3.12/demo/js/yui.jsPlumb-1.3.12-all-min.js rename to archive/1.3.12/demo/js/yui.jsPlumb-1.3.12-all-min.js diff --git a/build/1.3.12/demo/js/yui.jsPlumb-1.3.12-all.js b/archive/1.3.12/demo/js/yui.jsPlumb-1.3.12-all.js similarity index 100% rename from build/1.3.12/demo/js/yui.jsPlumb-1.3.12-all.js rename to archive/1.3.12/demo/js/yui.jsPlumb-1.3.12-all.js diff --git a/build/1.3.12/demo/mootools/anchorDemo.html b/archive/1.3.12/demo/mootools/anchorDemo.html similarity index 100% rename from build/1.3.12/demo/mootools/anchorDemo.html rename to archive/1.3.12/demo/mootools/anchorDemo.html diff --git a/build/1.3.12/demo/mootools/chartDemo.html b/archive/1.3.12/demo/mootools/chartDemo.html similarity index 100% rename from build/1.3.12/demo/mootools/chartDemo.html rename to archive/1.3.12/demo/mootools/chartDemo.html diff --git a/build/1.3.12/demo/mootools/demo.html b/archive/1.3.12/demo/mootools/demo.html similarity index 100% rename from build/1.3.12/demo/mootools/demo.html rename to archive/1.3.12/demo/mootools/demo.html diff --git a/build/1.3.12/demo/mootools/dragAnimDemo.html b/archive/1.3.12/demo/mootools/dragAnimDemo.html similarity index 100% rename from build/1.3.12/demo/mootools/dragAnimDemo.html rename to archive/1.3.12/demo/mootools/dragAnimDemo.html diff --git a/build/1.3.12/demo/mootools/draggableConnectorsDemo.html b/archive/1.3.12/demo/mootools/draggableConnectorsDemo.html similarity index 100% rename from build/1.3.12/demo/mootools/draggableConnectorsDemo.html rename to archive/1.3.12/demo/mootools/draggableConnectorsDemo.html diff --git a/build/1.3.12/demo/mootools/dynamicAnchorsDemo.html b/archive/1.3.12/demo/mootools/dynamicAnchorsDemo.html similarity index 100% rename from build/1.3.12/demo/mootools/dynamicAnchorsDemo.html rename to archive/1.3.12/demo/mootools/dynamicAnchorsDemo.html diff --git a/build/1.3.12/demo/mootools/flowchartConnectorsDemo.html b/archive/1.3.12/demo/mootools/flowchartConnectorsDemo.html similarity index 100% rename from build/1.3.12/demo/mootools/flowchartConnectorsDemo.html rename to archive/1.3.12/demo/mootools/flowchartConnectorsDemo.html diff --git a/build/1.3.12/demo/mootools/stateMachineDemo.html b/archive/1.3.12/demo/mootools/stateMachineDemo.html similarity index 100% rename from build/1.3.12/demo/mootools/stateMachineDemo.html rename to archive/1.3.12/demo/mootools/stateMachineDemo.html diff --git a/build/1.3.12/demo/yui3/anchorDemo.html b/archive/1.3.12/demo/yui3/anchorDemo.html similarity index 100% rename from build/1.3.12/demo/yui3/anchorDemo.html rename to archive/1.3.12/demo/yui3/anchorDemo.html diff --git a/build/1.3.12/demo/yui3/chartDemo.html b/archive/1.3.12/demo/yui3/chartDemo.html similarity index 100% rename from build/1.3.12/demo/yui3/chartDemo.html rename to archive/1.3.12/demo/yui3/chartDemo.html diff --git a/build/1.3.12/demo/yui3/demo.html b/archive/1.3.12/demo/yui3/demo.html similarity index 100% rename from build/1.3.12/demo/yui3/demo.html rename to archive/1.3.12/demo/yui3/demo.html diff --git a/build/1.3.12/demo/yui3/dragAnimDemo.html b/archive/1.3.12/demo/yui3/dragAnimDemo.html similarity index 100% rename from build/1.3.12/demo/yui3/dragAnimDemo.html rename to archive/1.3.12/demo/yui3/dragAnimDemo.html diff --git a/build/1.3.12/demo/yui3/draggableConnectorsDemo.html b/archive/1.3.12/demo/yui3/draggableConnectorsDemo.html similarity index 100% rename from build/1.3.12/demo/yui3/draggableConnectorsDemo.html rename to archive/1.3.12/demo/yui3/draggableConnectorsDemo.html diff --git a/build/1.3.12/demo/yui3/dynamicAnchorsDemo.html b/archive/1.3.12/demo/yui3/dynamicAnchorsDemo.html similarity index 100% rename from build/1.3.12/demo/yui3/dynamicAnchorsDemo.html rename to archive/1.3.12/demo/yui3/dynamicAnchorsDemo.html diff --git a/build/1.3.12/demo/yui3/flowchartConnectorsDemo.html b/archive/1.3.12/demo/yui3/flowchartConnectorsDemo.html similarity index 100% rename from build/1.3.12/demo/yui3/flowchartConnectorsDemo.html rename to archive/1.3.12/demo/yui3/flowchartConnectorsDemo.html diff --git a/build/1.3.12/demo/yui3/stateMachineDemo.html b/archive/1.3.12/demo/yui3/stateMachineDemo.html similarity index 100% rename from build/1.3.12/demo/yui3/stateMachineDemo.html rename to archive/1.3.12/demo/yui3/stateMachineDemo.html diff --git a/archive/1.3.12/jquery.jsPlumb-1.3.12-RC1.js b/archive/1.3.12/jquery.jsPlumb-1.3.12-RC1.js new file mode 100644 index 000000000..b8d28b2c9 --- /dev/null +++ b/archive/1.3.12/jquery.jsPlumb-1.3.12-RC1.js @@ -0,0 +1,372 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.12 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the jQuery adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ +/* + * the library specific functions, such as find offset, get id, get attribute, extend etc. + * the full list is: + * + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getDropScope gets the drop scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getOriginalEvent gets the original browser event from some wrapper event + * getPageXY gets the page event's xy location. + * getParent gets the parent of some element. + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * hasClass returns whether or not the given element has the given class. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + * trigger triggers some event on an element. + * unbind unbinds some listener from some element. + */ +(function($) { + + //var getBoundingClientRectSupported = "getBoundingClientRect" in document.documentElement; + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el[0].className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.addClass(el[0], clazz); + } + } + catch (e) { + // SVGAnimatedString not supported; no problem. + } + el.addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * appends the given child to the given parent. + */ + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).append(child); + }, + + /** + * executes an ajax call. + */ + ajax : function(params) { + params = params || {}; + params.type = params.type || "get"; + $.ajax(params); + }, + + /** + * event binding wrapper. it just so happens that jQuery uses 'bind' also. yui3, for example, + * uses 'on'. + */ + bind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.bind(event, callback); + }, + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step', + 'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete' + }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + }, + + getDragScope : function(el) { + return el.draggable("option", "scope"); + }, + + getDropEvent : function(args) { + return args[0]; + }, + + getDropScope : function(el) { + return el.droppable("option", "scope"); + }, + + /** + * gets a DOM element from the given input, which might be a string (in which case we just do document.getElementById), + * a selector (in which case we return el[0]), or a DOM element already (we assume this if it's not either of the other + * two cases). this is the opposite of getElementObject below. + */ + getDOMElement : function(el) { + if (typeof(el) == "string") return document.getElementById(el); + else if (el.context || el.length != null) return el[0]; + else return el; + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + * + */ + getElementObject : function(el) { + return typeof(el) == "string" ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + getOriginalEvent : function(e) { + return e.originalEvent; + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).parent(); + }, + + getScrollLeft : function(el) { + return el.scrollLeft(); + }, + + getScrollTop : function(el) { + return el.scrollTop(); + }, + + getSelector : function(spec) { + return $(spec); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e.length > 0 ? e[0].tagName : null; + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + + // this code is a workaround for the case that the element being dragged has a margin set on it. jquery UI passes + // in the wrong offset if the element has a margin (it doesn't take the margin into account). the getBoundingClientRect + // method, which is in pretty much all browsers now, reports the right numbers. but it introduces a noticeable lag, which + // i don't like. + + /*if ( getBoundingClientRectSupported ) { + var r = eventArgs[1].helper[0].getBoundingClientRect(); + return { left : r.left, top: r.top }; + } else {*/ + if (eventArgs.length == 1) { + ret = { left: eventArgs[0].pageX, top:eventArgs[0].pageY }; + } + else { + var ui = eventArgs[1], _offset = ui.offset; + ret = _offset || ui.absolutePosition; + } + return ret; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options, isPlumbedComponent) { + options = options || {}; + // remove helper directive if present. + options.helper = null; + if (isPlumbedComponent) + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.draggable(options); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.droppable(options); + }, + + isAlreadyDraggable : function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return el.hasClass("ui-draggable"); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el[0].className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.removeClass(el[0], clazz); + } + } + catch (e) { + // SVGAnimatedString not supported; no problem. + } + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).remove(); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * sets the drag scope. probably time for a setDragOption method (roll this and the one above together) + * @param el + * @param scope + */ + setDragScope : function(el, scope) { + el.draggable("option", "scope", scope); + }, + + setOffset : function(el, o) { + jsPlumb.CurrentLibrary.getElementObject(el).offset(o); + }, + + /** + * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself. + * the other libraries do not. yui, in fact, cannot even pass an original event. we have to pull out stuff + * from the originalEvent to put in an options object for YUI. + * @param el + * @param event + * @param originalEvent + */ + trigger : function(el, event, originalEvent) { + //originalEvent.stopPropagation(); + //jsPlumb.CurrentLibrary.getElementObject(el).trigger(originalEvent); + var h = jQuery._data(jsPlumb.CurrentLibrary.getElementObject(el)[0], "handle"); + h(originalEvent); + //originalEvent.stopPropagation(); + }, + + /** + * event unbinding wrapper. it just so happens that jQuery uses 'unbind' also. yui3, for example, + * uses..something else. + */ + unbind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.unbind(event, callback); + } + }; + + $(document).ready(jsPlumb.init); + +})(jQuery); + diff --git a/build/1.3.12/js/jquery.jsPlumb-1.3.12-all-min.js b/archive/1.3.12/js/jquery.jsPlumb-1.3.12-all-min.js similarity index 100% rename from build/1.3.12/js/jquery.jsPlumb-1.3.12-all-min.js rename to archive/1.3.12/js/jquery.jsPlumb-1.3.12-all-min.js diff --git a/build/1.3.12/js/jquery.jsPlumb-1.3.12-all.js b/archive/1.3.12/js/jquery.jsPlumb-1.3.12-all.js similarity index 100% rename from build/1.3.12/js/jquery.jsPlumb-1.3.12-all.js rename to archive/1.3.12/js/jquery.jsPlumb-1.3.12-all.js diff --git a/build/1.3.12/js/jsPlumb-1.3.12-tests.js b/archive/1.3.12/js/jsPlumb-1.3.12-tests.js similarity index 100% rename from build/1.3.12/js/jsPlumb-1.3.12-tests.js rename to archive/1.3.12/js/jsPlumb-1.3.12-tests.js diff --git a/build/1.3.12/js/lib/qunit.js b/archive/1.3.12/js/lib/qunit.js similarity index 100% rename from build/1.3.12/js/lib/qunit.js rename to archive/1.3.12/js/lib/qunit.js diff --git a/build/1.3.12/js/mootools.jsPlumb-1.3.12-all-min.js b/archive/1.3.12/js/mootools.jsPlumb-1.3.12-all-min.js similarity index 100% rename from build/1.3.12/js/mootools.jsPlumb-1.3.12-all-min.js rename to archive/1.3.12/js/mootools.jsPlumb-1.3.12-all-min.js diff --git a/build/1.3.12/js/mootools.jsPlumb-1.3.12-all.js b/archive/1.3.12/js/mootools.jsPlumb-1.3.12-all.js similarity index 100% rename from build/1.3.12/js/mootools.jsPlumb-1.3.12-all.js rename to archive/1.3.12/js/mootools.jsPlumb-1.3.12-all.js diff --git a/build/1.3.12/js/yui.jsPlumb-1.3.12-all-min.js b/archive/1.3.12/js/yui.jsPlumb-1.3.12-all-min.js similarity index 100% rename from build/1.3.12/js/yui.jsPlumb-1.3.12-all-min.js rename to archive/1.3.12/js/yui.jsPlumb-1.3.12-all-min.js diff --git a/build/1.3.12/js/yui.jsPlumb-1.3.12-all.js b/archive/1.3.12/js/yui.jsPlumb-1.3.12-all.js similarity index 100% rename from build/1.3.12/js/yui.jsPlumb-1.3.12-all.js rename to archive/1.3.12/js/yui.jsPlumb-1.3.12-all.js diff --git a/archive/1.3.12/jsPlumb-1.3.12-RC1.js b/archive/1.3.12/jsPlumb-1.3.12-RC1.js new file mode 100644 index 000000000..9f5cee061 --- /dev/null +++ b/archive/1.3.12/jsPlumb-1.3.12-RC1.js @@ -0,0 +1,6052 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.12 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the jsPlumb core code. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + /** + * Class:jsPlumb + * The jsPlumb engine, registered as a static object in the window. This object contains all of the methods you will use to + * create and maintain Connections and Endpoints. + */ + + var _findWithFunction = jsPlumbUtil.findWithFunction, + _indexOf = jsPlumbUtil.indexOf, + _removeWithFunction = jsPlumbUtil.removeWithFunction, + _remove = jsPlumbUtil.remove, + // TODO support insert index + _addWithFunction = jsPlumbUtil.addWithFunction, + _addToList = jsPlumbUtil.addToList, + /** + an isArray function that even works across iframes...see here: + + http://tobyho.com/2011/01/28/checking-types-in-javascript/ + + i was originally using "a.constructor == Array" as a test. + */ + _isArray = jsPlumbUtil.isArray, + _isString = jsPlumbUtil.isString, + _isObject = jsPlumbUtil.isObject; + + var _connectionBeingDragged = null, + _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }, + _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }, + _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }, + _log = jsPlumbUtil.log, + _group = jsPlumbUtil.group, + _groupEnd = jsPlumbUtil.groupEnd, + _time = jsPlumbUtil.time, + _timeEnd = jsPlumbUtil.timeEnd, + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + _timestamp = function() { return "" + (new Date()).getTime(); }, + + /* + * Class:jsPlumbUIComponent + * Abstract superclass for UI components Endpoint and Connection. Provides the abstraction of paintStyle/hoverPaintStyle, + * and also extends jsPlumbUtil.EventGenerator to provide the bind and fire methods. + */ + jsPlumbUIComponent = function(params) { + var self = this, + a = arguments, + _hover = false, + parameters = params.parameters || {}, + idPrefix = self.idPrefix, + id = idPrefix + (new Date()).getTime(), + paintStyle = null, + hoverPaintStyle = null; + + self._jsPlumb = params["_jsPlumb"]; + self.getId = function() { return id; }; + self.tooltip = params.tooltip; + self.hoverClass = params.hoverClass || self._jsPlumb.Defaults.HoverClass || jsPlumb.Defaults.HoverClass; + + // all components can generate events + jsPlumbUtil.EventGenerator.apply(this); + // all components get this clone function. + // TODO issue 116 showed a problem with this - it seems 'a' that is in + // the clone function's scope is shared by all invocations of it, the classic + // JS closure problem. for now, jsPlumb does a version of this inline where + // it used to call clone. but it would be nice to find some time to look + // further at this. + this.clone = function() { + var o = new Object(); + self.constructor.apply(o, a); + return o; + }; + + this.getParameter = function(name) { return parameters[name]; }, + this.getParameters = function() { + return parameters; + }, + this.setParameter = function(name, value) { parameters[name] = value; }, + this.setParameters = function(p) { parameters = p; }, + this.overlayPlacements = []; + + // user can supply a beforeDetach callback, which will be executed before a detach + // is performed; returning false prevents the detach. + var beforeDetach = params.beforeDetach; + this.isDetachAllowed = function(connection) { + var r = self._jsPlumb.checkCondition("beforeDetach", connection ); + if (beforeDetach) { + try { + r = beforeDetach(connection); + } + catch (e) { _log("jsPlumb: beforeDetach callback failed", e); } + } + return r; + }; + + // user can supply a beforeDrop callback, which will be executed before a dropped + // connection is confirmed. user can return false to reject connection. + var beforeDrop = params.beforeDrop; + this.isDropAllowed = function(sourceId, targetId, scope, connection, dropEndpoint) { + var r = self._jsPlumb.checkCondition("beforeDrop", { + sourceId:sourceId, + targetId:targetId, + scope:scope, + connection:connection, + dropEndpoint:dropEndpoint + }); + if (beforeDrop) { + try { + r = beforeDrop({ + sourceId:sourceId, + targetId:targetId, + scope:scope, + connection:connection, + dropEndpoint:dropEndpoint + }); + } + catch (e) { _log("jsPlumb: beforeDrop callback failed", e); } + } + return r; + }; + + // helper method to update the hover style whenever it, or paintStyle, changes. + // we use paintStyle as the foundation and merge hoverPaintStyle over the + // top. + var _updateHoverStyle = function() { + if (paintStyle && hoverPaintStyle) { + var mergedHoverStyle = {}; + jsPlumb.extend(mergedHoverStyle, paintStyle); + jsPlumb.extend(mergedHoverStyle, hoverPaintStyle); + delete self["hoverPaintStyle"]; + // we want the fillStyle of paintStyle to override a gradient, if possible. + if (mergedHoverStyle.gradient && paintStyle.fillStyle) + delete mergedHoverStyle["gradient"]; + hoverPaintStyle = mergedHoverStyle; + } + }; + + /* + * Sets the paint style and then repaints the element. + * + * Parameters: + * style - Style to use. + */ + this.setPaintStyle = function(style, doNotRepaint) { + paintStyle = style; + self.paintStyleInUse = paintStyle; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /** + * Gets the component's paint style. + * + * Returns: + * the component's paint style. if there is no hoverPaintStyle set then this will be the paint style used all the time, otherwise this is the style used when the mouse is not hovering. + */ + this.getPaintStyle = function() { + return paintStyle; + }; + + /* + * Sets the paint style to use when the mouse is hovering over the element. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the component will not be repainted. useful when setting things up initially. + */ + this.setHoverPaintStyle = function(style, doNotRepaint) { + hoverPaintStyle = style; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /** + * Gets the component's hover paint style. + * + * Returns: + * the component's hover paint style. may be null. + */ + this.getHoverPaintStyle = function() { + return hoverPaintStyle; + }; + + /* + * sets/unsets the hover state of this element. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + this.setHover = function(hover, ignoreAttachedElements, timestamp) { + // while dragging, we ignore these events. this keeps the UI from flashing and + // swishing and whatevering. + if (!self._jsPlumb.currentlyDragging && !self._jsPlumb.isHoverSuspended()) { + + _hover = hover; + if (self.hoverClass != null && self.canvas != null) { + if (hover) + jpcl.addClass(self.canvas, self.hoverClass); + else + jpcl.removeClass(self.canvas, self.hoverClass); + } + if (hoverPaintStyle != null) { + self.paintStyleInUse = hover ? hoverPaintStyle : paintStyle; + timestamp = timestamp || _timestamp(); + self.repaint({timestamp:timestamp, recalc:false}); + } + // get the list of other affected elements, if supported by this component. + // for a connection, its the endpoints. for an endpoint, its the connections! surprise. + if (self.getAttachedElements && !ignoreAttachedElements) + _updateAttachedElements(hover, _timestamp(), self); + } + }; + + this.isHover = function() { return _hover; }; + + var zIndex = null; + this.setZIndex = function(v) { zIndex = v; }; + this.getZIndex = function() { return zIndex; }; + + var jpcl = jsPlumb.CurrentLibrary, + events = [ "click", "dblclick", "mouseenter", "mouseout", "mousemove", "mousedown", "mouseup", "contextmenu" ], + eventFilters = { "mouseout":"mouseexit" }, + bindOne = function(o, c, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.bind(o, evt, function(ee) { + c.fire(filteredEvent, c, ee); + }); + }, + unbindOne = function(o, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.unbind(o, evt); + }; + + this.attachListeners = function(o, c) { + for (var i = 0; i < events.length; i++) { + bindOne(o, c, events[i]); + } + }; + + var _updateAttachedElements = function(state, timestamp, sourceElement) { + var affectedElements = self.getAttachedElements(); // implemented in subclasses + if (affectedElements) { + for (var i = 0; i < affectedElements.length; i++) { + if (!sourceElement || sourceElement != affectedElements[i]) + affectedElements[i].setHover(state, true, timestamp); // tell the attached elements not to inform their own attached elements. + } + } + }; + + this.reattachListenersForElement = function(o) { + if (arguments.length > 1) { + for (var i = 0; i < events.length; i++) + unbindOne(o, events[i]); + for (var i = 1; i < arguments.length; i++) + self.attachListeners(o, arguments[i]); + } + }; + + /* + * TYPES + */ + var _types = [], + _splitType = function(t) { return t == null ? null : t.split(" ")}, + _applyTypes = function(doNotRepaint) { + if (self.getDefaultType) { + var td = self.getTypeDescriptor(); + + var o = jsPlumbUtil.merge({}, self.getDefaultType()); + for (var i = 0; i < _types.length; i++) + o = jsPlumbUtil.merge(o, self._jsPlumb.getType(_types[i], td)); + + self.applyType(o); + if (!doNotRepaint) self.repaint(); + } + }; + + self.setType = function(typeId, doNotRepaint) { + _types = _splitType(typeId) || []; + _applyTypes(doNotRepaint); + }; + + /* + * Function : getType + * Gets the 'types' of this component. + */ + self.getType = function() { + return _types; + }; + + self.hasType = function(typeId) { + return jsPlumbUtil.indexOf(_types, typeId) != -1; + }; + + self.addType = function(typeId, doNotRepaint) { + var t = _splitType(typeId), _cont = false; + if (t != null) { + for (var i = 0; i < t.length; i++) { + if (!self.hasType(t[i])) { + _types.push(t[i]); + _cont = true; + } + } + if (_cont) _applyTypes(doNotRepaint); + } + }; + + self.removeType = function(typeId, doNotRepaint) { + var t = _splitType(typeId), _cont = false, _one = function(tt) { + var idx = jsPlumbUtil.indexOf(_types, tt); + if (idx != -1) { + _types.splice(idx, 1); + return true; + } + return false; + }; + + if (t != null) { + for (var i = 0; i < t.length; i++) { + _cont = _one(t[i]) || _cont; + } + if (_cont) _applyTypes(doNotRepaint); + } + }; + + self.toggleType = function(typeId, doNotRepaint) { + var t = _splitType(typeId); + if (t != null) { + for (var i = 0; i < t.length; i++) { + var idx = jsPlumbUtil.indexOf(_types, t[i]); + if (idx != -1) + _types.splice(idx, 1); + else + _types.push(t[i]); + } + + _applyTypes(doNotRepaint); + } + }; + + this.applyType = function(t) { + self.setPaintStyle(t.paintStyle); + self.setHoverPaintStyle(t.hoverPaintStyle); + if (t.parameters){ + for (var i in t.parameters) + self.setParameter(i, t.parameters[i]); + } + }; + + }, + + overlayCapableJsPlumbUIComponent = function(params) { + jsPlumbUIComponent.apply(this, arguments); + var self = this; + this.overlays = []; + + var processOverlay = function(o) { + var _newOverlay = null; + if (_isArray(o)) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0], + // make a copy of the object so as not to mess up anyone else's reference... + p = jsPlumb.extend({component:self, _jsPlumb:self._jsPlumb}, o[1]); + if (o.length == 3) jsPlumb.extend(p, o[2]); + _newOverlay = new jsPlumb.Overlays[self._jsPlumb.getRenderMode()][type](p); + if (p.events) { + for (var evt in p.events) { + _newOverlay.bind(evt, p.events[evt]); + } + } + } else if (o.constructor == String) { + _newOverlay = new jsPlumb.Overlays[self._jsPlumb.getRenderMode()][o]({component:self, _jsPlumb:self._jsPlumb}); + } else { + _newOverlay = o; + } + + self.overlays.push(_newOverlay); + }, + calculateOverlaysToAdd = function(params) { + var defaultKeys = self.defaultOverlayKeys || [], + o = params.overlays, + checkKey = function(k) { + return self._jsPlumb.Defaults[k] || jsPlumb.Defaults[k] || []; + }; + + if (!o) o = []; + + for (var i = 0; i < defaultKeys.length; i++) + o.unshift.apply(o, checkKey(defaultKeys[i])); + + return o; + } + + var _overlays = calculateOverlaysToAdd(params);//params.overlays || self._jsPlumb.Defaults.Overlays; + if (_overlays) { + for (var i = 0; i < _overlays.length; i++) { + processOverlay(_overlays[i]); + } + } + + // overlay finder helper method + var _getOverlayIndex = function(id) { + var idx = -1; + for (var i = 0; i < self.overlays.length; i++) { + if (id === self.overlays[i].id) { + idx = i; + break; + } + } + return idx; + }; + + this.addOverlay = function(overlay, doNotRepaint) { + processOverlay(overlay); + if (!doNotRepaint) self.repaint(); + }; + + this.getOverlay = function(id) { + var idx = _getOverlayIndex(id); + return idx >= 0 ? self.overlays[idx] : null; + }; + + this.getOverlays = function() { + return self.overlays; + }; + + this.hideOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.hide(); + }; + + this.hideOverlays = function() { + for (var i = 0; i < self.overlays.length; i++) + self.overlays[i].hide(); + }; + + this.showOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.show(); + }; + + this.showOverlays = function() { + for (var i = 0; i < self.overlays.length; i++) + self.overlays[i].show(); + }; + + this.removeAllOverlays = function() { + for (var i in self.overlays) + self.overlays[i].cleanup(); + + self.overlays.splice(0, self.overlays.length); + self.repaint(); + }; + + this.removeOverlay = function(overlayId) { + var idx = _getOverlayIndex(overlayId); + if (idx != -1) { + var o = self.overlays[idx]; + o.cleanup(); + self.overlays.splice(idx, 1); + } + }; + + this.removeOverlays = function() { + for (var i = 0; i < arguments.length; i++) + self.removeOverlay(arguments[i]); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + var _internalLabelOverlayId = "__label", + _makeLabelOverlay = function(params) { + + var _params = { + cssClass:params.cssClass, + labelStyle : this.labelStyle, + id:_internalLabelOverlayId, + component:self, + _jsPlumb:self._jsPlumb + }, + mergedParams = jsPlumb.extend(_params, params); + + return new jsPlumb.Overlays[self._jsPlumb.getRenderMode()].Label( mergedParams ); + }; + if (params.label) { + var loc = params.labelLocation || self.defaultLabelLocation || 0.5, + labelStyle = params.labelStyle || self._jsPlumb.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.overlays.push(_makeLabelOverlay({ + label:params.label, + location:loc, + labelStyle:labelStyle + })); + } + + + this.setLabel = function(l) { + var lo = self.getOverlay(_internalLabelOverlayId); + if (!lo) { + var params = l.constructor == String || l.constructor == Function ? { label:l } : l; + lo = _makeLabelOverlay(params); + this.overlays.push(lo); + } + else { + if (l.constructor == String || l.constructor == Function) lo.setLabel(l); + else { + if (l.label) lo.setLabel(l.label); + if (l.location) lo.setLocation(l.location); + } + } + + if (!self._jsPlumb.isSuspendDrawing()) + self.repaint(); + }; + + + this.getLabel = function() { + var lo = self.getOverlay(_internalLabelOverlayId); + return lo != null ? lo.getLabel() : null; + }; + + + this.getLabelOverlay = function() { + return self.getOverlay(_internalLabelOverlayId); + }; + + var superAt = this.applyType; + this.applyType = function(t) { + superAt(t); + self.removeAllOverlays(); + if (t.overlays) { + for (var i = 0; i < t.overlays.length; i++) + self.addOverlay(t.overlays[i], true); + } + }; + }, + + _bindListeners = function(obj, _self, _hoverFunction) { + obj.bind("click", function(ep, e) { _self.fire("click", _self, e); }); + obj.bind("dblclick", function(ep, e) { _self.fire("dblclick", _self, e); }); + obj.bind("contextmenu", function(ep, e) { _self.fire("contextmenu", _self, e); }); + obj.bind("mouseenter", function(ep, e) { + if (!_self.isHover()) { + _hoverFunction(true); + _self.fire("mouseenter", _self, e); + } + }); + obj.bind("mouseexit", function(ep, e) { + if (_self.isHover()) { + _hoverFunction(false); + _self.fire("mouseexit", _self, e); + } + }); + }; + + var _jsPlumbInstanceIndex = 0, + getInstanceIndex = function() { + var i = _jsPlumbInstanceIndex + 1; + _jsPlumbInstanceIndex++; + return i; + }; + + var jsPlumbInstance = function(_defaults) { + + /* + * Property: Defaults + * + * These are the default settings for jsPlumb. They are what will be used if you do not supply specific pieces of information + * to the various API calls. A convenient way to implement your own look and feel can be to override these defaults + * by including a script somewhere after the jsPlumb include, but before you make any calls to jsPlumb. + * + * Properties: + * - *Anchor* The default anchor to use for all connections (both source and target). Default is "BottomCenter". + * - *Anchors* The default anchors to use ([source, target]) for all connections. Defaults are ["BottomCenter", "BottomCenter"]. + * - *ConnectionsDetachable* Whether or not connections are detachable by default (using the mouse). Defaults to true. + * - *ConnectionOverlays* The default overlay definitions for Connections. Defaults to an empty list. + * - *Connector* The default connector definition to use for all connections. Default is "Bezier". + * - *ConnectorZIndex* Optional value for the z-index of Connections that are not in the hover state. If you set this, jsPlumb will set the z-index of all created Connections to be this value, and the z-index of any Connections in the hover state to be this value plus one. This brings hovered connections up on top of others, which is a nice effect in busy UIs. + * - *Container* Optional selector or element id that instructs jsPlumb to append elements it creates to a specific element. + * - *DragOptions* The default drag options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *DropOptions* The default drop options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *Endpoint* The default endpoint definition to use for all connections (both source and target). Default is "Dot". + * - *EndpointOverlays* The default overlay definitions for Endpoints. Defaults to an empty list. + * - *Endpoints* The default endpoint definitions ([ source, target ]) to use for all connections. Defaults are ["Dot", "Dot"]. + * - *EndpointStyle* The default style definition to use for all endpoints. Default is fillStyle:"#456". + * - *EndpointStyles* The default style definitions ([ source, target ]) to use for all endpoints. Defaults are empty. + * - *EndpointHoverStyle* The default hover style definition to use for all endpoints. Default is null. + * - *EndpointHoverStyles* The default hover style definitions ([ source, target ]) to use for all endpoints. Defaults are null. + * - *HoverPaintStyle* The default hover style definition to use for all connections. Defaults are null. + * - *LabelStyle* The default style to use for label overlays on connections. + * - *LogEnabled* Whether or not the jsPlumb log is enabled. defaults to false. + * - *Overlays* The default overlay definitions (for both Connections and Endpoint). Defaults to an empty list. + * - *MaxConnections* The default maximum number of connections for an Endpoint. Defaults to 1. + * - *PaintStyle* The default paint style for a connection. Default is line width of 8 pixels, with color "#456". + * - *RenderMode* What mode to use to paint with. If you're on IE<9, you don't really get to choose this. You'll just get VML. Otherwise, the jsPlumb default is to use SVG. + * - *Scope* The default "scope" to use for connections. Scope lets you assign connections to different categories. + */ + this.Defaults = { + Anchor : "BottomCenter", + Anchors : [ null, null ], + ConnectionsDetachable : true, + ConnectionOverlays : [ ], + Connector : "Bezier", + ConnectorZIndex : null, + Container : null, + DragOptions : { }, + DropOptions : { }, + Endpoint : "Dot", + EndpointOverlays : [ ], + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : "#456" }, + EndpointStyles : [ null, null ], + EndpointHoverStyle : null, + EndpointHoverStyles : [ null, null ], + HoverPaintStyle : null, + LabelStyle : { color : "black" }, + LogEnabled : false, + Overlays : [ ], + MaxConnections : 1, + PaintStyle : { lineWidth : 8, strokeStyle : "#456" }, + //Reattach:false, + RenderMode : "svg", + Scope : "jsPlumb_DefaultScope" + }; + if (_defaults) jsPlumb.extend(this.Defaults, _defaults); + + this.logEnabled = this.Defaults.LogEnabled; + + var _connectionTypes = { }, _endpointTypes = {}; + this.registerConnectionType = function(id, type) { + _connectionTypes[id] = jsPlumb.extend({}, type); + }; + this.registerConnectionTypes = function(types) { + for (var i in types) + _connectionTypes[i] = jsPlumb.extend({}, types[i]); + }; + this.registerEndpointType = function(id, type) { + _endpointTypes[id] = jsPlumb.extend({}, type); + }; + this.registerEndpointTypes = function(types) { + for (var i in types) + _endpointTypes[i] = jsPlumb.extend({}, types[i]); + }; + this.getType = function(id, typeDescriptor) { + return typeDescriptor === "connection" ? _connectionTypes[id] : _endpointTypes[id]; + }; + + jsPlumbUtil.EventGenerator.apply(this); + var _currentInstance = this, + _instanceIndex = getInstanceIndex(), + _bb = _currentInstance.bind, + _initialDefaults = {}; + + for (var i in this.Defaults) + _initialDefaults[i] = this.Defaults[i]; + + this.bind = function(event, fn) { + if ("ready" === event && initialized) fn(); + else _bb.apply(_currentInstance,[event, fn]); + }; + + /* + Function: importDefaults + Imports all the given defaults into this instance of jsPlumb. + */ + _currentInstance.importDefaults = function(d) { + for (var i in d) { + _currentInstance.Defaults[i] = d[i]; + } + }; + + /* + Function:restoreDefaults + Restores the default settings to "factory" values. + */ + _currentInstance.restoreDefaults = function() { + _currentInstance.Defaults = jsPlumb.extend({}, _initialDefaults); + }; + + var log = null, + repaintFunction = function() { + jsPlumb.repaintEverything(); + }, + automaticRepaint = true, + repaintEverything = function() { + if (automaticRepaint) + repaintFunction(); + }, + resizeTimer = null, + initialized = false, + connectionsByScope = {}, + /** + * map of element id -> endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + endpointsByElement = {}, + endpointsByUUID = {}, + offsets = {}, + offsetTimestamps = {}, + floatingConnections = {}, + draggableStates = {}, + canvasList = [], + sizes = [], + //listeners = {}, // a map: keys are event types, values are lists of listeners. + DEFAULT_SCOPE = this.Defaults.Scope, + renderMode = null, // will be set in init() + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }, + + /** + * appends an element to some other element, which is calculated as follows: + * + * 1. if _currentInstance.Defaults.Container exists, use that element. + * 2. if the 'parent' parameter exists, use that. + * 3. otherwise just use the root element (for DOM usage, the document body). + * + */ + _appendElement = function(el, parent) { + if (_currentInstance.Defaults.Container) + jsPlumb.CurrentLibrary.appendElement(el, _currentInstance.Defaults.Container); + else if (!parent) + //document.body.appendChild(el); + jsPlumbAdapter.appendToRoot(el); + else + jsPlumb.CurrentLibrary.appendElement(el, parent); + }, + + _curIdStamp = 1, + _idstamp = function() { return "" + _curIdStamp++; }, + + /** + * YUI, for some reason, put the result of a Y.all call into an object that contains + * a '_nodes' array, instead of handing back an array-like object like the other + * libraries do. + */ + _convertYUICollection = function(c) { + return c._nodes ? c._nodes : c; + }, + + /** + * Draws an endpoint and its connections. this is the main entry point into drawing connections as well + * as endpoints, since jsPlumb is endpoint-centric under the hood. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + _draw = function(element, ui, timestamp) { + + // TOD is it correct to filter by headless at this top level? how would a headless adapter ever repaint? + if (!jsPlumbAdapter.headless && !_suspendDrawing) { + var id = _getAttribute(element, "id"), + repaintEls = _currentInstance.dragManager.getElementsForDraggable(id); + + if (timestamp == null) timestamp = _timestamp(); + + _currentInstance.anchorManager.redraw(id, ui, timestamp); + + if (repaintEls) { + for (var i in repaintEls) { + _currentInstance.anchorManager.redraw(repaintEls[i].id, ui, timestamp, repaintEls[i].offset); + } + } + } + }, + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + _elementProxy = function(element, fn) { + var retVal = null; + if (_isArray(element)) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }, + + /** + * gets an Endpoint by uuid. + */ + _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }, + + /** + * inits a draggable if it's not already initialised. + */ + _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + // TODO move to DragManager? + if (!jsPlumbAdapter.headless) { + var draggable = isDraggable == null ? false : isDraggable, + jpcl = jsPlumb.CurrentLibrary; + if (draggable) { + if (jpcl.isDragSupported(element) && !jpcl.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jpcl.dragEvents["drag"], + stopEvent = jpcl.dragEvents["stop"], + startEvent = jpcl.dragEvents["start"]; + + options[startEvent] = _wrap(options[startEvent], function() { + _currentInstance.setHoverSuspended(true); + }); + + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jpcl.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jpcl.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + _currentInstance.setHoverSuspended(false); + }); + var elId = _getId(element); // need ID + draggableStates[elId] = true; + var draggable = draggableStates[elId]; + options.disabled = draggable == null ? false : !draggable; + jpcl.initDraggable(element, options, false); + _currentInstance.dragManager.register(element); + } + } + } + }, + + /* + * prepares a final params object that can be passed to _newConnection, taking into account defaults, events, etc. + */ + _prepareConnectionParams = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + // hotwire endpoints passed as source or target to sourceEndpoint/targetEndpoint, respectively. + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not full. + // source: + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + // target: + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + // if source endpoint mandates connection type and nothing specified in our params, use it. + if (!_p.type && _p.sourceEndpoint) + _p.type = _p.sourceEndpoint.connectionType; + + // copy in any connectorOverlays that were specified on the source endpoint. + // it doesnt copy target endpoint overlays. i'm not sure if we want it to or not. + if (_p.sourceEndpoint && _p.sourceEndpoint.connectorOverlays) { + _p.overlays = _p.overlays || []; + for (var i = 0; i < _p.sourceEndpoint.connectorOverlays.length; i++) { + _p.overlays.push(_p.sourceEndpoint.connectorOverlays[i]); + } + } + + // tooltip. params.tooltip takes precedence, then sourceEndpoint.connectorTooltip. + _p.tooltip = params.tooltip; + if (!_p.tooltip && _p.sourceEndpoint && _p.sourceEndpoint.connectorTooltip) + _p.tooltip = _p.sourceEndpoint.connectorTooltip; + + // if there's a target specified (which of course there should be), and there is no + // target endpoint specified, and 'newConnection' was not set to true, then we check to + // see if a prior call to makeTarget has provided us with the specs for the target endpoint, and + // we use those if so. additionally, if the makeTarget call was specified with 'uniqueEndpoint' set + // to true, then if that target endpoint has already been created, we re-use it. + if (_p.target && !_p.target.endpoint && !_p.targetEndpoint && !_p.newConnection) { + var tid = _getId(_p.target), + tep =_targetEndpointDefinitions[tid], + existingUniqueEndpoint = _targetEndpoints[tid]; + + if (tep) { + // if target not enabled, return. + if (!_targetsEnabled[tid]) return; + + // check for max connections?? + var newEndpoint = existingUniqueEndpoint != null ? existingUniqueEndpoint : _currentInstance.addEndpoint(_p.target, tep); + if (_targetEndpointsUnique[tid]) _targetEndpoints[tid] = newEndpoint; + _p.targetEndpoint = newEndpoint; + newEndpoint._makeTargetCreator = true; + } + } + + // same thing, but for source. + if (_p.source && !_p.source.endpoint && !_p.sourceEndpoint && !_p.newConnection) { + var tid = _getId(_p.source), + tep = _sourceEndpointDefinitions[tid], + existingUniqueEndpoint = _sourceEndpoints[tid]; + + if (tep) { + // if source not enabled, return. + if (!_sourcesEnabled[tid]) return; + + var newEndpoint = existingUniqueEndpoint != null ? existingUniqueEndpoint : _currentInstance.addEndpoint(_p.source, tep); + if (_sourceEndpointsUnique[tid]) _sourceEndpoints[tid] = newEndpoint; + _p.sourceEndpoint = newEndpoint; + } + } + + return _p; + }, + + _newConnection = function(params) { + var connectionFunc = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + endpointFunc = _currentInstance.Defaults.EndpointType || Endpoint, + parent = jsPlumb.CurrentLibrary.getParent; + + if (params.container) + params["parent"] = params.container; + else { + if (params.sourceEndpoint) + params["parent"] = params.sourceEndpoint.parent; + else if (params.source.constructor == endpointFunc) + params["parent"] = params.source.parent; + else params["parent"] = parent(params.source); + } + + params["_jsPlumb"] = _currentInstance; + var con = new connectionFunc(params); + con.id = "con_" + _idstamp(); + _eventFireProxy("click", "click", con); + _eventFireProxy("dblclick", "dblclick", con); + _eventFireProxy("contextmenu", "contextmenu", con); + return con; + }, + + /** + * adds the connection to the backing model, fires an event if necessary and then redraws + */ + _finaliseConnection = function(jpc, params, originalEvent) { + params = params || {}; + // add to list of connections (by scope). + if (!jpc.suspendedEndpoint) + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + if (!params.doNotFireConnectionEvent && params.fireEvent !== false) { + + var eventArgs = { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }; + + _currentInstance.fire("jsPlumbConnection", eventArgs, originalEvent); + // this is from 1.3.11 onwards. "jsPlumbConnection" always felt so unnecessary, so + // I've added this alias in 1.3.11, with a view to removing "jsPlumbConnection" completely in a future version. be aware, of course, you should only register listeners for one or the other of these events. + _currentInstance.fire("connection", eventArgs, originalEvent); + } + + // always inform the anchor manager + // except that if jpc has a suspended endpoint it's not true to say the + // connection is new; it has just (possibly) moved. the question is whether + // to make that call here or in the anchor manager. i think perhaps here. + _currentInstance.anchorManager.newConnection(jpc); + // force a paint + _draw(jpc.source); + }, + + _eventFireProxy = function(event, proxyEvent, obj) { + obj.bind(event, function(originalObject, originalEvent) { + _currentInstance.fire(proxyEvent, obj, originalEvent); + }); + }, + + /** + * for the given endpoint params, returns an appropriate parent element for the UI elements that will be added. + * this function is used by _newEndpoint (directly below), and also in the makeSource function in jsPlumb. + * + * the logic is to first look for a "container" member of params, and pass that back if found. otherwise we + * handoff to the 'getParent' function in the current library. + */ + _getParentFromParams = function(params) { + if (params.container) + return params.container; + else { + var tag = jsPlumb.CurrentLibrary.getTagName(params.source), + p = jsPlumb.CurrentLibrary.getParent(params.source); + if (tag && tag.toLowerCase() === "td") + return jsPlumb.CurrentLibrary.getParent(p); + else return p; + } + }, + + /** + factory method to prepare a new endpoint. this should always be used instead of creating Endpoints + manually, since this method attaches event listeners and an id. + */ + _newEndpoint = function(params) { + var endpointFunc = _currentInstance.Defaults.EndpointType || Endpoint; + params.parent = _getParentFromParams(params); + params["_jsPlumb"] = _currentInstance; + var ep = new endpointFunc(params); + ep.id = "ep_" + _idstamp(); + _eventFireProxy("click", "endpointClick", ep); + _eventFireProxy("dblclick", "endpointDblClick", ep); + _eventFireProxy("contextmenu", "contextmenu", ep); + return ep; + }, + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + _operation = function(elId, func, endpointFunc) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + if (endpointFunc) endpointFunc(endpoints[i]); + } + } + }, + /** + * perform an operation on all elements. + */ + _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }, + + /** + * helper to remove an element from the DOM. + */ + _removeElement = function(element, parent) { + if (element != null && element.parentNode != null) { + element.parentNode.removeChild(element); + } + }, + /** + * helper to remove a list of elements from the DOM. + */ + _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }, + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }, + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + _setVisible = function(el, state, alsoChangeEndpoints) { + state = state === "block"; + var endpointFunc = null; + if (alsoChangeEndpoints) { + if (state) endpointFunc = function(ep) { + ep.setVisible(true, true, true); + }; + else endpointFunc = function(ep) { + ep.setVisible(false, true, true); + }; + } + var id = _getAttribute(el, "id"); + _operation(id, function(jpc) { + if (state && alsoChangeEndpoints) { + // this test is necessary because this functionality is new, and i wanted to maintain backwards compatibility. + // this block will only set a connection to be visible if the other endpoint in the connection is also visible. + var oidx = jpc.sourceId === id ? 1 : 0; + if (jpc.endpoints[oidx].isVisible()) jpc.setVisible(true); + } + else // the default behaviour for show, and what always happens for hide, is to just set the visibility without getting clever. + jpc.setVisible(state); + }, endpointFunc); + }, + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? false : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }, + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + _toggleVisible = function(elId, changeEndpoints) { + var endpointFunc = null; + if (changeEndpoints) { + endpointFunc = function(ep) { + var state = ep.isVisible(); + ep.setVisible(!state); + }; + } + _operation(elId, function(jpc) { + var state = jpc.isVisible(); + jpc.setVisible(!state); + }, endpointFunc); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }, + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return offsets[elId]; + } + if (recalc || !offset) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + if (sizes[elId] == null) { + var s = _getElementObject(elId); + if (s != null) + sizes[elId] = _getSize(s); + } + } + + if(offsets[elId] && !offsets[elId].right) { + offsets[elId].right = offsets[elId].left + sizes[elId][0]; + offsets[elId].bottom = offsets[elId].top + sizes[elId][1]; + offsets[elId].width = sizes[elId][0]; + offsets[elId].height = sizes[elId][1]; + offsets[elId].centerx = offsets[elId].left + (offsets[elId].width / 2); + offsets[elId].centery = offsets[elId].top + (offsets[elId].height / 2); + } + return offsets[elId]; + }, + + // TODO comparison performance + _getCachedData = function(elId) { + var o = offsets[elId]; + if (!o) o = _updateOffset({elId:elId}); + return {o:o, s:sizes[elId]}; + }, + + /** + * gets an id for the given element, creating and setting one if + * necessary. the id is of the form + * + * jsPlumb__ + * + * where "index in instance" is a monotonically increasing integer that starts at 0, + * for each instance. this method is used not only to assign ids to elements that do not + * have them but also to connections and endpoints. + */ + _getId = function(element, uuid, doNotCreateIfNotFound) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else if (arguments.length == 1 || (arguments.length == 3 && !arguments[2])) + id = "jsPlumb_" + _instanceIndex + "_" + _idstamp(); + _setAttribute(ele, "id", id); + } + return id; + }, + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, "jsPlumb function failed : " + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, "wrapped function failed : " + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = "_jsPlumb_connector"; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = "_jsPlumb_endpoint"; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = "_jsPlumb_overlay"; + + this.Anchors = {}; + + this.Connectors = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Endpoints = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Overlays = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + +// ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***************************************** + /* + * Function: bind + * Bind to an event on jsPlumb. + * + * Parameters: + * event - the event to bind. Available events on jsPlumb are: + * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback. + * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback. + * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback. + * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback. + * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback. + * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback. + * - *beforeDrop* : notification that a Connection is about to be dropped. Returning false from this method cancels the drop. jsPlumb passes { sourceId, targetId, scope, connection, dropEndpoint } to your callback. For more information, refer to the jsPlumb documentation. + * - *beforeDetach* : notification that a Connection is about to be detached. Returning false from this method cancels the detach. jsPlumb passes the Connection to your callback. For more information, refer to the jsPlumb documentation. + * - *connectionDrag* : notification that an existing Connection is being dragged. jsPlumb passes the Connection to your callback function. + * - *connectionDragEnd* : notification that the drag of an existing Connection has ended. jsPlumb passes the Connection to your callback function. + * + * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event. + */ + + /* + * Function: unbind + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + +// *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *********************************************************** + + +// --------------------------- jsPLumbInstance public API --------------------------------------------------------- + + /* + Function: addClass + + Helper method to abstract out differences in setting css classes on the different renderer types. + */ + this.addClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.addClass(el, clazz); + }; + + /* + Function: removeClass + + Helper method to abstract out differences in setting css classes on the different renderer types. + */ + this.removeClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.removeClass(el, clazz); + }; + + /* + Function: hasClass + + Helper method to abstract out differences in testing for css classes on the different renderer types. + */ + this.hasClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.hasClass(el, clazz); + }; + + /* + Function: addEndpoint + + Adds an to a given element or elements. + + Parameters: + + el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + params - Object containing Endpoint constructor arguments. For more information, see . + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some + shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in + this object are anything that 'params' can contain. See . + + Returns: + The newly created , if el referred to a single element. Otherwise, an array of newly created s. + + See Also: + + */ + this.addEndpoint = function(el, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // YUI wrapper + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + var _el = _getElementObject(inputs[i]), id = _getId(_el); + p.source = _el; + _updateOffset({ elId : id }); + var e = _newEndpoint(p); + if (p.parentAnchor) e.parentAnchor = p.parentAnchor; + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + results.push(e); + if (!jsPlumbAdapter.headless) + _currentInstance.dragManager.endpointAdded(_el); + } + + return results.length == 1 ? results[0] : results; + }; + + /* + Function: addEndpoints + Adds a list of s to a given element or elements. + + Parameters: + target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See 's constructor documentation. + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created s, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(el, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams); + if (_isArray(e)) + Array.prototype.push.apply(results, e); + else results.push(e); + } + return results; + }; + + /* + Function: animate + This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating + the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // onComplete repaints, just to make sure everything looks good at the end of the animation. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /** + * checks for a listener for the given condition, executing it if found, passing in the given value. + * condition listeners would have been attached using "bind" (which is, you could argue, now overloaded, since + * firing click events etc is a bit different to what this does). i thought about adding a "bindCondition" + * or something, but decided against it, for the sake of simplicity. jsPlumb will never fire one of these + * condition events anyway. + */ + this.checkCondition = function(conditionName, value) { + var l = _currentInstance.getListener(conditionName); + var r = true; + if (l && l.length > 0) { + try { + for (var i = 0 ; i < l.length; i++) { + r = r && l[i](value); + } + } + catch (e) { + _log(_currentInstance, "cannot check condition [" + conditionName + "]" + e); + } + } + return r; + }; + + /* + Function: connect + Establishes a between two elements (or s, which are themselves registered to elements). + + Parameters: + params - Object containing constructor arguments for the Connection. See 's constructor documentation. + referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of + Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection. + + Returns: + The newly created . + */ + this.connect = function(params, referenceParams) { + // prepare a final set of parameters to create connection with + var _p = _prepareConnectionParams(params, referenceParams), jpc; + // TODO probably a nicer return value if the connection was not made. _prepareConnectionParams + // will return null (and log something) if either endpoint was full. what would be nicer is to + // create a dedicated 'error' object. + if (_p) { + // a connect call will delete its created endpoints on detach, unless otherwise specified. + // this is because the endpoints belong to this connection only, and are no use to + // anyone else, so they hang around like a bad smell. + if (_p.deleteEndpointsOnDetach == null) + _p.deleteEndpointsOnDetach = true; + + // create the connection. it is not yet registered + jpc = _newConnection(_p); + // now add it the model, fire an event, and redraw + _finaliseConnection(jpc, _p); + } + return jpc; + }; + + /* + Function: deleteEndpoint + Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too) + + Parameters: + object - either an object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + if (endpoint.endpoint.cleanup) endpoint.endpoint.cleanup(); + _removeElements(endpoint.endpoint.getDisplayElements()); + _currentInstance.anchorManager.deleteEndpoint(endpoint); + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + if (!jsPlumbAdapter.headless) + _currentInstance.dragManager.endpointDeleted(endpoint); + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every , and their associated s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + _currentInstance.setSuspendDrawing(true); + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + + _currentInstance.setSuspendDrawing(false, true); + }; + + var fireDetachEvent = function(jpc, doFireEvent, originalEvent) { + // may have been given a connection, or in special cases, an object + var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + argIsConnection = jpc.constructor == connType, + params = argIsConnection ? { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + } : jpc; + + if (doFireEvent) { + _currentInstance.fire("jsPlumbConnectionDetached", params, originalEvent); + // introduced in 1.3.11..an alias because the original event name is unwieldy. in future versions this will be the only event and the other will no longer be fired. + _currentInstance.fire("connectionDetached", params, originalEvent); + } + _currentInstance.anchorManager.connectionDetached(params); + }, + /** + fires an event to indicate an existing connection is being dragged. + */ + fireConnectionDraggingEvent = function(jpc) { + _currentInstance.fire("connectionDrag", jpc); + }, + fireConnectionDragStopEvent = function(jpc) { + _currentInstance.fire("connectionDragStop", jpc); + }; + + + /* + Function: detach + Detaches and then removes a . From 1.3.5 this method has been altered to remove support for + specifying Connections by various parameters; you can now pass in a Connection as the first argument and + an optional parameters object as a second argument. If you need the functionality this method provided + before 1.3.5 then you should use the getConnections method to get the list of Connections to detach, and + then iterate through them, calling this for each one. + + Parameters: + connection - the to detach + params - optional parameters to the detach call. valid values here are + fireEvent : defaults to false; indicates you want jsPlumb to fire a connection + detached event. The thinking behind this is that if you made a programmatic + call to detach an event, you probably don't need the callback. + forceDetach : defaults to false. allows you to override any beforeDetach listeners that may be registered. + + Returns: + true if successful, false if not. + */ + this.detach = function() { + + if (arguments.length == 0) return; + var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + firstArgIsConnection = arguments[0].constructor == connType, + params = arguments.length == 2 ? firstArgIsConnection ? (arguments[1] || {}) : arguments[0] : arguments[0], + fireEvent = (params.fireEvent !== false), + forceDetach = params.forceDetach, + connection = firstArgIsConnection ? arguments[0] : params.connection; + + if (connection) { + if (forceDetach || (connection.isDetachAllowed(connection) + && connection.endpoints[0].isDetachAllowed(connection) + && connection.endpoints[1].isDetachAllowed(connection))) { + if (forceDetach || _currentInstance.checkCondition("beforeDetach", connection)) + connection.endpoints[0].detach(connection, false, true, fireEvent); // TODO check this param iscorrect for endpoint's detach method + } + } + else { + var _p = jsPlumb.extend( {}, params); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1]), fireEvent); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source), + targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + if (_currentInstance.checkCondition("beforeDetach", jpc)) { + jpc.endpoints[0].detach(jpc, false, true, fireEvent); + } + } + }); + } + } + }; + + /* + Function: detachAllConnections + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + params - optional parameters. alowed values: + fireEvent : defaults to true, whether or not to fire the detach event. + + Returns: + void + */ + this.detachAllConnections = function(el, params) { + params = params || {}; + el = _getElementObject(el); + var id = _getAttribute(el, "id"), + endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + }; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Parameters: + params - optional params object containing: + fireEvent : whether or not to fire detach events. defaults to true. + + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function(params) { + params = params || {}; + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of your + library's draggable method so that jsPlumb can setup the appropriate callbacks. Your + underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + // helpers for select/selectEndpoints + var _setOperation = function(list, func, args, selector) { + for (var i = 0; i < list.length; i++) { + list[i][func].apply(list[i], args); + } + return selector(list); + }, + _getOperation = function(list, func, args) { + var out = []; + for (var i = 0; i < list.length; i++) { + out.push([ list[i][func].apply(list[i], args), list[i] ]); + } + return out; + }, + setter = function(list, func, selector) { + return function() { + return _setOperation(list, func, arguments, selector); + }; + }, + getter = function(list, func) { + return function() { + return _getOperation(list, func, arguments); + }; + }, + prepareList = function(input, doNotGetIds) { + var r = []; + if (input) { + if (typeof input == 'string') { + if (input === "*") return input; + r.push(input); + } + else { + if (doNotGetIds) r = input; + else { + for (var i = 0; i < input.length; i++) + r.push(_getId(_getElementObject(input[i]))); + } + } + } + return r; + }, + filterList = function(list, value, missingIsFalse) { + if (list === "*") return true; + return list.length > 0 ? _indexOf(list, value) != -1 : !missingIsFalse; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method, + * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the + * default scope). If multiple scopes are passed in, the return value will be a map of { scope -> [ connection... ] }. + * + * Parameters: + * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list + * of connections that are in the given scope. use '*' for all scopes. + * options - if the argument is a JS object, you can specify a finer-grained filter: + * + * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes. Also may have the value '*', indicating any scope. + * - *source* either a string representing an element id, a selector, or an array of ids. Also may have the value '*', indicating any source. Constrains the result to connections having this/these element(s) as source. + * - *target* either a string representing an element id, a selector, or an array of ids. Also may have the value '*', indicating any target. Constrains the result to connections having this/these element(s) as target. + * flat - return results in a flat array (don't return an object whose keys are scopes and whose values are lists per scope). + * + */ + this.getConnections = function(options, flat) { + if (!options) { + options = {}; + } else if (options.constructor == String) { + options = { "scope": options }; + } + var + scope = options.scope || _currentInstance.getDefaultScope(), + scopes = prepareList(scope, true), + sources = prepareList(options.source), + targets = prepareList(options.target), + results = (!flat && scopes.length > 1) ? {} : [], + _addOne = function(scope, obj) { + if (!flat && scopes.length > 1) { + var ss = results[scope]; + if (ss == null) { + ss = []; results[scope] = ss; + } + ss.push(obj); + } else results.push(obj); + }; + for ( var i in connectionsByScope) { + if (filterList(scopes, i)) { + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filterList(sources, c.sourceId) && filterList(targets, c.targetId)) + _addOne(i, c); + } + } + } + return results; + }; + + var _curryEach = function(list, executor) { + return function(f) { + for (var i = 0; i < list.length; i++) { + f(list[i]); + } + return executor(list); + }; + }, + _curryGet = function(list) { + return function(idx) { + return list[idx]; + }; + }; + + var _makeCommonSelectHandler = function(list, executor) { + return { + // setters + setHover:setter(list, "setHover", executor), + removeAllOverlays:setter(list, "removeAllOverlays", executor), + setLabel:setter(list, "setLabel", executor), + addOverlay:setter(list, "addOverlay", executor), + removeOverlay:setter(list, "removeOverlay", executor), + removeOverlays:setter(list, "removeOverlays", executor), + showOverlay:setter(list, "showOverlay", executor), + hideOverlay:setter(list, "hideOverlay", executor), + showOverlays:setter(list, "showOverlays", executor), + hideOverlays:setter(list, "hideOverlays", executor), + setPaintStyle:setter(list, "setPaintStyle", executor), + setHoverPaintStyle:setter(list, "setHoverPaintStyle", executor), + setParameter:setter(list, "setParameter", executor), + setParameters:setter(list, "setParameters", executor), + setVisible:setter(list, "setVisible", executor), + setZIndex:setter(list, "setZIndex", executor), + repaint:setter(list, "repaint", executor), + addType:setter(list, "addType", executor), + toggleType:setter(list, "toggleType", executor), + removeType:setter(list, "removeType", executor), + + // getters + getLabel:getter(list, "getLabel"), + getOverlay:getter(list, "getOverlay"), + isHover:getter(list, "isHover"), + getParameter:getter(list, "getParameter"), + getParameters:getter(list, "getParameters"), + getPaintStyle:getter(list, "getPaintStyle"), + getHoverPaintStyle:getter(list, "getHoverPaintStyle"), + isVisible:getter(list, "isVisible"), + getZIndex:getter(list, "getZIndex"), + hasType:getter(list, "hasType"), + getType:getter(list, "getType"), + + // util + length:list.length, + each:_curryEach(list, executor), + get:_curryGet(list) + }; + + }; + + var _makeConnectionSelectHandler = function(list) { + var common = _makeCommonSelectHandler(list, _makeConnectionSelectHandler); + return jsPlumb.CurrentLibrary.extend(common, { + // setters + setDetachable:setter(list, "setDetachable", _makeConnectionSelectHandler), + setConnector:setter(list, "setConnector", _makeConnectionSelectHandler), + detach:function() { + for (var i = 0; i < list.length; i++) + _currentInstance.detach(list[i]); + }, + // getters + isDetachable:getter(list, "isDetachable") + }); + }; + + var _makeEndpointSelectHandler = function(list) { + var common = _makeCommonSelectHandler(list, _makeEndpointSelectHandler); + return jsPlumb.CurrentLibrary.extend(common, { + setEnabled:setter(list, "setEnabled", _makeEndpointSelectHandler), + isEnabled:getter(list, "isEnabled"), + detachAll:function() { + for (var i = 0; i < list.length; i++) + list[i].detachAll(); + }, + "delete":function() { + for (var i = 0; i < list.length; i++) + _currentInstance.deleteEndpoint(list[i]); + } + }); + }; + + /* + * Function: select + * Selects a set of Connections, using the filter options from the getConnections method, and returns an object + * that allows you to perform an operation on all of the Connections at once. The return value from any of these + * operations is the original list of Connections, allowing operations to be chained (for 'setter' type operations). + * 'getter' type operations return an array of values, where each entry is a [Connection, return value] pair. + * + * Parameters: + * scope - see getConnections + * source - see getConnections + * target - see getConnections + * + * Returns: + * A list of Connections on which operations may be executed. 'Setter' type operations can be chained; 'getter' type operations + * return an array of [Connection, value] pairs, one entry for each Connection in the list returned. The full list of operations + * is as follows (where not specified, the operation's effect or return value is the same as the corresponding method on Connection) : + * - setHover + * - removeAllOverlays + * - setLabel + * - addOverlay + * - removeOverlay + * - removeOverlays + * - showOverlay + * - hideOverlay + * - showOverlays + * - hideOverlays + * - setPaintStyle + * - setHoverPaintStyle + * - setDetachable + * - setConnector + * - setParameter + * - setParameters + * - getLabel + * - getOverlay + * - isHover + * - isDetachable + * - getParameter + * - getParameters + * - getPaintStyle + * - getHoverPaintStyle + * - detach detaches all the connections in the list. not chainable and does not return anything. + * - length : returns the length of the list. + * - get(index) : returns the Connection at 'index' in the list. + * - each(function(connection)...) : allows you to specify your own function to execute; this function is chainable. + */ + this.select = function(params) { + params = params || {}; + params.scope = params.scope || "*"; + var c = _currentInstance.getConnections(params, true); + return _makeConnectionSelectHandler(c); + }; + + /* + * Function: selectEndpoints + * Selects a set of Endpoints and returns an object that allows you to execute various different methods on them at once. The return + * value from any of these operations is the original list of Endpoints, allowing operations to be chained (for 'setter' type + * operations). 'getter' type operations return an array of values, where each entry is an [Endpoint, return value] pair. + * + * Parameters: + * scope - either a string or an array of strings. + * source - either a string, a dom element, or a selector, or an array of any mix of these. limits returned endpoints to those that are declared as a source endpoint on any elements identified. + * target - either a string, a dom element, or a selector, or an array of any mix of these. limits returned endpoints to those that are declared as a target endpoint on any elements identified. + * element - either a string, a dom element, or a selector, or an array of any mix of these. limits returned endpoints to those that are declared as either a source OR a target endpoint on any elements identified. + * + * Returns: + * A list of Endpoints on which operations may be executed. 'Setter' type operations can be chained; 'getter' type operations + * return an array of [Endpoint, value] pairs, one entry for each Endpoint in the list returned. The full list of operations + * is as follows (where not specified, the operation's effect or return value is the same as the corresponding method on Endpoint) : + * - setHover + * - removeAllOverlays + * - setLabel + * - addOverlay + * - removeOverlay + * - removeOverlays + * - showOverlay + * - hideOverlay + * - showOverlays + * - hideOverlays + * - setPaintStyle + * - setHoverPaintStyle + * - setParameter + * - setParameters + * - getLabel + * - getOverlay + * - isHover + * - isDetachable + * - getParameter + * - getParameters + * - getPaintStyle + * - getHoverPaintStyle + * - detachAll Detaches all the Connections from every Endpoint in the list. not chainable and does not return anything. + * - delete Deletes every Endpoint in the list. not chainable and does not return anything. + * - length : returns the length of the list. + * - get(index) : returns the Endpoint at 'index' in the list. + * - each(function(endpoint)...) : allows you to specify your own function to execute; this function is chainable. + */ + this.selectEndpoints = function(params) { + params = params || {}; + params.scope = params.scope || "*"; + var noElementFilters = !params.element && !params.source && !params.target, + elements = noElementFilters ? "*" : prepareList(params.element), + sources = noElementFilters ? "*" : prepareList(params.source), + targets = noElementFilters ? "*" : prepareList(params.target), + scopes = prepareList(params.scope, true); + + var ep = []; + + for (var el in endpointsByElement) { + var either = filterList(elements, el, true), + source = filterList(sources, el, true), + sourceMatchExact = sources != "*", + target = filterList(targets, el, true), + targetMatchExact = targets != "*"; + + // if they requested 'either' then just match scope. otherwise if they requested 'source' (not as a wildcard) then we have to match only endpoints that have isSource set to to true, and the same thing with isTarget. + if ( either || source || target ) { + inner: + for (var i = 0; i < endpointsByElement[el].length; i++) { + var _ep = endpointsByElement[el][i]; + if (filterList(scopes, _ep.scope, true)) { + + var noMatchSource = (sourceMatchExact && sources.length > 0 && !_ep.isSource), + noMatchTarget = (targetMatchExact && targets.length > 0 && !_ep.isTarget); + + if (noMatchSource || noMatchTarget) + continue inner; + + ep.push(_ep); + } + } + } + } + + return _makeEndpointSelectHandler(ep); + }; + + /* + * Function: getAllConnections + * Gets all connections, as a map of { scope -> [ connection... ] }. + */ + this.getAllConnections = function() { + return connectionsByScope; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /** + * Function:getEndpoints + * Gets the list of Endpoints for a given selector, or element id. + * @param el + * @return + */ + this.getEndpoints = function(el) { + return endpointsByElement[_getId(el)]; + }; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + this.getOffset = function(id) { + var o = offsets[id]; + return _updateOffset({elId:id}); + }; + + this.getSelector = function(spec) { + return jsPlumb.CurrentLibrary.getSelector(spec); + }; + + this.getSize = function(id) { + var s = sizes[id]; + if (!s) _updateOffset({elId:id}); + return sizes[id]; + }; + + this.appendElement = _appendElement; + + var _hoverSuspended = false; + this.isHoverSuspended = function() { return _hoverSuspended; }; + this.setHoverSuspended = function(s) { _hoverSuspended = s; }; + + var _isAvailable = function(m) { + return function() { + return jsPlumbAdapter.isRenderModeAvailable(m); + }; + } + this.isCanvasAvailable = _isAvailable("canvas"); + this.isSVGAvailable = _isAvailable("svg"); + this.isVMLAvailable = _isAvailable("vml"); + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + changeEndpoints - whether not to also hide endpoints on the element. by default this is false. + + Returns: + void + */ + this.hide = function(el, changeEndpoints) { + _setVisible(el, "none", changeEndpoints); + }; + + // exposed for other objects to use to get a unique id. + this.idstamp = _idstamp; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + if (!initialized) { + _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run. + + var bindOne = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && renderMode == jsPlumb.CANVAS) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + var t = c[i].connector[event](e); + if (t) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i].endpoint[event](e)) return; + } + } + } + }); + }; + bindOne("click");bindOne("dblclick");bindOne("mousemove");bindOne("mousedown");bindOne("mouseup");bindOne("contextmenu"); + + initialized = true; + _currentInstance.fire("ready"); + } + }; + + this.log = log; + this.jsPlumbUIComponent = jsPlumbUIComponent; + + /* + * Creates an anchor with the given params. + * + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function() { + if (arguments.length == 0) return null; + var specimen = arguments[0], elementId = arguments[1], jsPlumbInstance = arguments[2], newAnchor = null; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; //TODO hazy here about whether it should be added or is already added somehow. + // is it the name of an anchor type? + else if (typeof specimen == "string") { + newAnchor = jsPlumb.Anchors[arguments[0]]({elementId:elementId, jsPlumbInstance:_currentInstance}); + } + // is it an array? it will be one of: + // an array of [name, params] - this defines a single anchor + // an array of arrays - this defines some dynamic anchors + // an array of numbers - this defines a single anchor. + else if (_isArray(specimen)) { + if (_isArray(specimen[0]) || _isString(specimen[0])) { + if (specimen.length == 2 && _isString(specimen[0]) && _isObject(specimen[1])) { + var pp = jsPlumb.extend({elementId:elementId, jsPlumbInstance:_currentInstance}, specimen[1]); + newAnchor = jsPlumb.Anchors[specimen[0]](pp); + } + else + newAnchor = new DynamicAnchor(specimen, null, elementId); + } + else { + var anchorParams = { + x:specimen[0], y:specimen[1], + orientation : (specimen.length >= 4) ? [ specimen[2], specimen[3] ] : [0,0], + offsets : (specimen.length == 6) ? [ specimen[4], specimen[5] ] : [ 0, 0 ], + elementId:elementId + }; + newAnchor = new Anchor(anchorParams); + newAnchor.clone = function() { return new Anchor(anchorParams); }; + } + } + + if (!newAnchor.id) newAnchor.id = "anchor_" + _idstamp(); + return newAnchor; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types, elementId, jsPlumbInstance) { + var r = []; + for ( var i = 0; i < types.length; i++) { + if (typeof types[i] == "string") + r.push(jsPlumb.Anchors[types[i]]({elementId:elementId, jsPlumbInstance:jsPlumbInstance})); + else if (_isArray(types[i])) + r.push(_currentInstance.makeAnchor(types[i], elementId, jsPlumbInstance)); + } + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /** + * Function: makeTarget + * Makes some DOM element a Connection target, allowing you to drag connections to it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a target. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * scope optional. scope for the drop zone. + * dropOptions optional. same stuff as you would pass to dropOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to true. whether or not to delete + * any Endpoints created by a connection to this target if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * maxConnections optional. Specifies the maximum number of Connections that can be made to this element as a target. Default is no limit. + * onMaxConnections optional. Function to call when user attempts to drop a connection but the limit has been reached. The callback is passed two arguments: a JS object with { element, connection, maxConnection }, and the original event. + * + * + */ + var _targetEndpointDefinitions = {}, + _targetEndpoints = {}, + _targetEndpointsUnique = {}, + _targetMaxConnections = {}, + _setEndpointPaintStylesAndAnchor = function(ep, epIndex) { + ep.paintStyle = ep.paintStyle || + _currentInstance.Defaults.EndpointStyles[epIndex] || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyles[epIndex] || + jsPlumb.Defaults.EndpointStyle; + ep.hoverPaintStyle = ep.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyles[epIndex] || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyles[epIndex] || + jsPlumb.Defaults.EndpointHoverStyle; + + ep.anchor = ep.anchor || + _currentInstance.Defaults.Anchors[epIndex] || + _currentInstance.Defaults.Anchor || + jsPlumb.Defaults.Anchors[epIndex] || + jsPlumb.Defaults.Anchor; + + ep.endpoint = ep.endpoint || + _currentInstance.Defaults.Endpoints[epIndex] || + _currentInstance.Defaults.Endpoint || + jsPlumb.Defaults.Endpoints[epIndex] || + jsPlumb.Defaults.Endpoint; + }; + + this.makeTarget = function(el, params, referenceParams) { + + var p = jsPlumb.extend({_jsPlumb:_currentInstance}, referenceParams); + jsPlumb.extend(p, params); + _setEndpointPaintStylesAndAnchor(p, 1); + var jpcl = jsPlumb.CurrentLibrary, + targetScope = p.scope || _currentInstance.Defaults.Scope, + deleteEndpointsOnDetach = !(p.deleteEndpointsOnDetach === false), + maxConnections = p.maxConnections || -1, + onMaxConnections = p.onMaxConnections; + _doOne = function(_el) { + + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el); + _targetEndpointDefinitions[elid] = p; + _targetEndpointsUnique[elid] = p.uniqueEndpoint, + _targetMaxConnections[elid] = maxConnections, + _targetsEnabled[elid] = true, + proxyComponent = new jsPlumbUIComponent(p); + + var dropOptions = jsPlumb.extend({}, p.dropOptions || {}), + _drop = function() { + + var originalEvent = jsPlumb.CurrentLibrary.getDropEvent(arguments), + targetCount = _currentInstance.select({target:elid}).length; + + + _currentInstance.currentlyDragging = false; + var draggable = _getElementObject(jpcl.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + // restore the original scope if necessary (issue 57) + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id], + source = jpc.endpoints[0], + _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : {}; + + if (!_targetsEnabled[elid] || _targetMaxConnections[elid] > 0 && targetCount >= _targetMaxConnections[elid]){ + //console.log("target element " + elid + " is full."); + if (onMaxConnections) { + onMaxConnections({ + element:_el, + connection:jpc + }, originalEvent); + } + return false; + } + + // unlock the source anchor to allow it to refresh its position if necessary + source.anchor.locked = false; + + if (scope) jpcl.setDragScope(draggable, scope); + + // check if drop is allowed here. + //var _continue = jpc.isDropAllowed(jpc.sourceId, _getId(_el), jpc.scope); + var _continue = proxyComponent.isDropAllowed(jpc.sourceId, _getId(_el), jpc.scope, jpc, null); + + // regardless of whether the connection is ok, reconfigure the existing connection to + // point at the current info. we need this to be correct for the detach event that will follow. + // clear the source endpoint from the list to detach. we will detach this connection at this + // point, but we want to keep the source endpoint. the target is a floating endpoint and should + // be removed. TODO need to figure out whether this code can result in endpoints kicking around + // when they shouldnt be. like is this a full detach of a connection? can it be? + if (jpc.endpointsToDeleteOnDetach) { + if (source === jpc.endpointsToDeleteOnDetach[0]) + jpc.endpointsToDeleteOnDetach[0] = null; + else if (source === jpc.endpointsToDeleteOnDetach[1]) + jpc.endpointsToDeleteOnDetach[1] = null; + } + // reinstate any suspended endpoint; this just puts the connection back into + // a state in which it will report sensible values if someone asks it about + // its target. we're going to throw this connection away shortly so it doesnt matter + // if we manipulate it a bit. + if (jpc.suspendedEndpoint) { + jpc.targetId = jpc.suspendedEndpoint.elementId; + jpc.target = jpcl.getElementObject(jpc.suspendedEndpoint.elementId); + jpc.endpoints[1] = jpc.suspendedEndpoint; + } + + if (_continue) { + + // detach this connection from the source. + source.detach(jpc, false, true, false); + + // make a new Endpoint for the target + var newEndpoint = _targetEndpoints[elid] || _currentInstance.addEndpoint(_el, p); + if (p.uniqueEndpoint) _targetEndpoints[elid] = newEndpoint; // may of course just store what it just pulled out. that's ok. + newEndpoint._makeTargetCreator = true; + + // if the anchor has a 'positionFinder' set, then delegate to that function to find + // out where to locate the anchor. + if (newEndpoint.anchor.positionFinder != null) { + var dropPosition = jpcl.getUIPosition(arguments), + elPosition = jpcl.getOffset(_el), + elSize = jpcl.getSize(_el), + ap = newEndpoint.anchor.positionFinder(dropPosition, elPosition, elSize, newEndpoint.anchor.constructorParams); + newEndpoint.anchor.x = ap[0]; + newEndpoint.anchor.y = ap[1]; + // now figure an orientation for it..kind of hard to know what to do actually. probably the best thing i can do is to + // support specifying an orientation in the anchor's spec. if one is not supplied then i will make the orientation + // be what will cause the most natural link to the source: it will be pointing at the source, but it needs to be + // specified in one axis only, and so how to make that choice? i think i will use whichever axis is the one in which + // the target is furthest away from the source. + } + var c = _currentInstance.connect({ + source:source, + target:newEndpoint, + scope:scope, + previousConnection:jpc, + container:jpc.parent, + deleteEndpointsOnDetach:deleteEndpointsOnDetach, + // 'endpointWillMoveAfterConnection' is set by the makeSource function, and it indicates that the + // given endpoint will actually transfer from the element it is currently attached to to some other + // element after a connection has been established. in that case, we do not want to fire the + // connection event, since it will have the wrong data in it; makeSource will do it for us. + // this is controlled by the 'parent' parameter on a makeSource call. + doNotFireConnectionEvent:source.endpointWillMoveAfterConnection + }); + + // delete the original target endpoint. but only want to do this if the endpoint was created + // automatically and has no other connections. + if (jpc.endpoints[1]._makeTargetCreator && jpc.endpoints[1].connections.length < 2) + _currentInstance.deleteEndpoint(jpc.endpoints[1]); + + if (deleteEndpointsOnDetach) + c.endpointsToDeleteOnDetach = [ source, newEndpoint ]; + + c.repaint(); + } + // if not allowed to drop... + else { + // TODO this code is identical (pretty much) to what happens when a connection + // dragged from a normal endpoint is in this situation. refactor. + // is this an existing connection, and will we reattach? + if (jpc.suspendedEndpoint) { + if (source.isReattach) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + _currentInstance.repaint(source.elementId); + } + else + source.detach(jpc, false, true, true, originalEvent); // otherwise, detach the connection and tell everyone about it. + } + + } + }; + + var dropEvent = jpcl.dragEvents['drop']; + dropOptions["scope"] = dropOptions["scope"] || targetScope; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop); + + jpcl.initDroppable(_el, dropOptions, true); + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + + return _currentInstance; + }; + + /* + * Function: unmakeTarget + * Sets the given element to no longer be a connection target. + * Parameters: + * el - either a string id or a selector representing the element. + * Returns: + * The current jsPlumb instance. + */ + this.unmakeTarget = function(el, doNotClearArrays) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + var elid = _getId(el); + + // TODO this is not an exhaustive unmake of a target, since it does not remove the droppable stuff from + // the element. the effect will be to prevent it form behaving as a target, but it's not completely purged. + if (!doNotClearArrays) { + delete _targetEndpointDefinitions[elid]; + delete _targetEndpointsUnique[elid]; + delete _targetMaxConnections[elid]; + delete _targetsEnabled[elid]; + } + + return _currentInstance; + }; + + /** + * helper method to make a list of elements drop targets. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeTargets = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeTarget(els[i], params, referenceParams); + } + }; + + /** + * Function: makeSource + * Makes some DOM element a Connection source, allowing you to drag connections from it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a source. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * parent optional. the element to add Endpoints to when a Connection is established. if you omit this, + * Endpoints will be added to 'el'. + * scope optional. scope for the connections dragged from this element. + * dragOptions optional. same stuff as you would pass to dragOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to false. whether or not to delete + * any Endpoints created by a connection from this source if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * filter - optional function to call when the user presses the mouse button to start a drag. This function is passed the original + * event and the element on which the associated makeSource call was made. If it returns anything other than false, + * the drag begins as usual. But if it returns false (the boolean false, not something falsey), the drag is aborted. + * + * + */ + var _sourceEndpointDefinitions = {}, + _sourceEndpoints = {}, + _sourceEndpointsUnique = {}, + _sourcesEnabled = {}, + _sourceTriggers = {}, + _sourceMaxConnections = {}, + _targetsEnabled = {}; + + this.makeSource = function(el, params, referenceParams) { + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + _setEndpointPaintStylesAndAnchor(p, 0); + var jpcl = jsPlumb.CurrentLibrary, + maxConnections = p.maxConnections || -1, + onMaxConnections = p.onMaxConnections, + _doOne = function(_el) { + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el), + parent = p.parent, + idToRegisterAgainst = parent != null ? _currentInstance.getId(jpcl.getElementObject(parent)) : elid; + + _sourceEndpointDefinitions[idToRegisterAgainst] = p; + _sourceEndpointsUnique[idToRegisterAgainst] = p.uniqueEndpoint; + _sourcesEnabled[idToRegisterAgainst] = true; + + var stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"], + dragOptions = jsPlumb.extend({ }, p.dragOptions || {}), + existingDrag = dragOptions.drag, + existingStop = dragOptions.stop, + ep = null, + endpointAddedButNoDragYet = false; + + _sourceMaxConnections[idToRegisterAgainst] = maxConnections; + + // set scope if its not set in dragOptions but was passed in in params + dragOptions["scope"] = dragOptions["scope"] || p.scope; + + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + if (existingDrag) existingDrag.apply(this, arguments); + endpointAddedButNoDragYet = false; + }); + + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], function() { + if (existingStop) existingStop.apply(this, arguments); + + //_currentlyDown = false; + _currentInstance.currentlyDragging = false; + + if (ep.connections.length == 0) + _currentInstance.deleteEndpoint(ep); + else { + + jpcl.unbind(ep.canvas, "mousedown"); + + // reset the anchor to the anchor that was initially provided. the one we were using to drag + // the connection was just a placeholder that was located at the place the user pressed the + // mouse button to initiate the drag. + var anchorDef = p.anchor || _currentInstance.Defaults.Anchor, + oldAnchor = ep.anchor, + oldConnection = ep.connections[0]; + + ep.anchor = _currentInstance.makeAnchor(anchorDef, elid, _currentInstance); + + if (p.parent) { + var parent = jpcl.getElementObject(p.parent); + if (parent) { + var currentId = ep.elementId; + + var potentialParent = p.container || _currentInstance.Defaults.Container || jsPlumb.Defaults.Container; + + ep.setElement(parent, potentialParent); + ep.endpointWillMoveAfterConnection = false; + _currentInstance.anchorManager.rehomeEndpoint(currentId, parent); + oldConnection.previousConnection = null; + // remove from connectionsByScope + _removeWithFunction(connectionsByScope[oldConnection.scope], function(c) { + return c.id === oldConnection.id; + }); + _currentInstance.anchorManager.connectionDetached({ + sourceId:oldConnection.sourceId, + targetId:oldConnection.targetId, + connection:oldConnection + }); + _finaliseConnection(oldConnection); + } + } + + ep.repaint(); + _currentInstance.repaint(ep.elementId); + _currentInstance.repaint(oldConnection.targetId); + + } + }); + // when the user presses the mouse, add an Endpoint, if we are enabled. + var mouseDownListener = function(e) { + + // if disabled, return. + if (!_sourcesEnabled[idToRegisterAgainst]) return; + + // if maxConnections reached + var sourceCount = _currentInstance.select({source:idToRegisterAgainst}).length + if (_sourceMaxConnections[idToRegisterAgainst] >= 0 && sourceCount >= _sourceMaxConnections[idToRegisterAgainst]) { + if (onMaxConnections) { + onMaxConnections({ + element:_el, + maxConnections:maxConnections + }, e); + } + return false; + } + + // if a filter was given, run it, and return if it says no. + if (params.filter) { + // pass the original event to the user: + var r = params.filter(jpcl.getOriginalEvent(e), _el); + if (r === false) return; + } + + // make sure we have the latest offset for this div + var myOffsetInfo = _updateOffset({elId:elid}); + + var x = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width, + y = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height, + parentX = x, + parentY = y; + + // if there is a parent, the endpoint will actually be added to it now, rather than the div + // that was the source. in that case, we have to adjust the anchor position so it refers to + // the parent. + if (p.parent) { + var pEl = jpcl.getElementObject(p.parent), + pId = _getId(pEl); + myOffsetInfo = _updateOffset({elId:pId}); + parentX = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width, + parentY = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height; + } + + // we need to override the anchor in here, and force 'isSource', but we don't want to mess with + // the params passed in, because after a connection is established we're going to reset the endpoint + // to have the anchor we were given. + var tempEndpointParams = {}; + jsPlumb.extend(tempEndpointParams, p); + tempEndpointParams.isSource = true; + tempEndpointParams.anchor = [x,y,0,0]; + tempEndpointParams.parentAnchor = [ parentX, parentY, 0, 0 ]; + tempEndpointParams.dragOptions = dragOptions; + // if a parent was given we need to turn that into a "container" argument. this is, by default, + // the parent of the element we will move to, so parent of p.parent in this case. however, if + // the user has specified a 'container' on the endpoint definition or on + // the defaults, we should use that. + if (p.parent) { + var potentialParent = tempEndpointParams.container || _currentInstance.Defaults.Container || jsPlumb.Defaults.Container; + if (potentialParent) + tempEndpointParams.container = potentialParent; + else + tempEndpointParams.container = jsPlumb.CurrentLibrary.getParent(p.parent); + } + + ep = _currentInstance.addEndpoint(elid, tempEndpointParams); + + endpointAddedButNoDragYet = true; + // we set this to prevent connections from firing attach events before this function has had a chance + // to move the endpoint. + ep.endpointWillMoveAfterConnection = p.parent != null; + ep.endpointWillMoveTo = p.parent ? jpcl.getElementObject(p.parent) : null; + + var _delTempEndpoint = function() { + // this mouseup event is fired only if no dragging occurred, by jquery and yui, but for mootools + // it is fired even if dragging has occurred, in which case we would blow away a perfectly + // legitimate endpoint, were it not for this check. the flag is set after adding an + // endpoint and cleared in a drag listener we set in the dragOptions above. + if(endpointAddedButNoDragYet) { + _currentInstance.deleteEndpoint(ep); + } + }; + + _currentInstance.registerListener(ep.canvas, "mouseup", _delTempEndpoint); + _currentInstance.registerListener(_el, "mouseup", _delTempEndpoint); + + // and then trigger its mousedown event, which will kick off a drag, which will start dragging + // a new connection from this endpoint. + jpcl.trigger(ep.canvas, "mousedown", e); + }; + + // register this on jsPlumb so that it can be cleared by a reset. + _currentInstance.registerListener(_el, "mousedown", mouseDownListener); + _sourceTriggers[elid] = mouseDownListener; + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + + return _currentInstance; + }; + + /** + * Function: unmakeSource + * Sets the given element to no longer be a connection source. + * Parameters: + * el - either a string id or a selector representing the element. + * Returns: + * The current jsPlumb instance. + */ + this.unmakeSource = function(el, doNotClearArrays) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + var id = _getId(el), + mouseDownListener = _sourceTriggers[id]; + + if (mouseDownListener) + _currentInstance.unregisterListener(_el, "mousedown", mouseDownListener); + + if (!doNotClearArrays) { + delete _sourceEndpointDefinitions[id]; + delete _sourceEndpointsUnique[id]; + delete _sourcesEnabled[id]; + delete _sourceTriggers[id]; + delete _sourceMaxConnections[id]; + } + + return _currentInstance; + }; + + /* + * Function: unmakeEverySource + * Resets all elements in this instance of jsPlumb so that none of them are connection sources. + * Returns: + * The current jsPlumb instance. + */ + this.unmakeEverySource = function() { + for (var i in _sourcesEnabled) + _currentInstance.unmakeSource(i, true); + + _sourceEndpointDefinitions = {}; + _sourceEndpointsUnique = {}; + _sourcesEnabled = {}; + _sourceTriggers = {}; + }; + + /* + * Function: unmakeEveryTarget + * Resets all elements in this instance of jsPlumb so that none of them are connection targets. + * Returns: + * The current jsPlumb instance. + */ + this.unmakeEveryTarget = function() { + for (var i in _targetsEnabled) + _currentInstance.unmakeTarget(i, true); + + _targetEndpointDefinitions = {}; + _targetEndpointsUnique = {}; + _targetMaxConnections = {}; + _targetsEnabled = {}; + + return _currentInstance; + }; + + /* + * Function: makeSources + * Makes all elements in some array or a selector connection sources. + * Parameters: + * els - either an array of ids or a selector + * params - parameters to configure each element as a source with + * referenceParams - extra parameters to configure each element as a source with. + * + * Returns: + * The current jsPlumb instance. + */ + this.makeSources = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeSource(els[i], params, referenceParams); + } + + return _currentInstance; + }; + + // does the work of setting a source enabled or disabled. + var _setEnabled = function(type, el, state, toggle) { + var a = type == "source" ? _sourcesEnabled : _targetsEnabled; + + if (_isString(el)) a[el] = toggle ? !a[el] : state; + else if (el.length) { + el = _convertYUICollection(el); + for (var i = 0; i < el.length; i++) { + var id = _el = jsPlumb.CurrentLibrary.getElementObject(el[i]), id = _getId(_el); + a[id] = toggle ? !a[id] : state; + } + } + return _currentInstance; + }; + + /* + Function: setSourceEnabled + Sets the enabled state of one or more elements that were previously made a connection source with the makeSource + method. + + Parameters: + el - either a string representing some element's id, or an array of ids, or a selector. + state - true to enable the element(s), false to disable it. + + Returns: + The current jsPlumb instance. + */ + this.setSourceEnabled = function(el, state) { + return _setEnabled("source", el, state); + }; + + /* + * Function: toggleSourceEnabled + * Toggles the source enabled state of the given element or elements. + * + * Parameters: + * el - either a string representing some element's id, or an array of ids, or a selector. + * state - true to enable the element(s), false to disable it. + * + * Returns: + * The current enabled state of the source. + */ + this.toggleSourceEnabled = function(el) { + _setEnabled("source", el, null, true); + return _currentInstance.isSourceEnabled(el); + }; + + /* + * Function: isSource + * Returns whether or not the given element is registered as a connection source. + * + * Parameters: + * el - either a string id, or a selector representing a single element. + * + * Returns: + * True if source, false if not. + */ + this.isSource = function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return _sourcesEnabled[_getId(el)] != null; + }; + + /* + * Function: isSourceEnabled + * Returns whether or not the given connection source is enabled. + * + * Parameters: + * el - either a string id, or a selector representing a single element. + * + * Returns: + * True if enabled, false if not. + */ + this.isSourceEnabled = function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return _sourcesEnabled[_getId(el)] === true; + }; + + /* + * Function: setTargetEnabled + * Sets the enabled state of one or more elements that were previously made a connection target with the makeTarget method. + * method. + * + * Parameters: + * el - either a string representing some element's id, or an array of ids, or a selector. + * state - true to enable the element(s), false to disable it. + * + * Returns: + * The current jsPlumb instance. + */ + this.setTargetEnabled = function(el, state) { + return _setEnabled("target", el, state); + }; + + /* + * Function: toggleTargetEnabled + * Toggles the target enabled state of the given element or elements. + * + * Parameters: + * el - either a string representing some element's id, or an array of ids, or a selector. + * state - true to enable the element(s), false to disable it. + * + * Returns: + * The current enabled state of the target. + */ + this.toggleTargetEnabled = function(el) { + return _setEnabled("target", el, null, true); + return _currentInstance.isTargetEnabled(el); + }; + + /* + Function: isTarget + Returns whether or not the given element is registered as a connection target. + + Parameters: + el - either a string id, or a selector representing a single element. + + Returns: + True if source, false if not. + */ + this.isTarget = function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return _targetsEnabled[_getId(el)] != null; + }; + + /* + Function: isTargetEnabled + Returns whether or not the given connection target is enabled. + + Parameters: + el - either a string id, or a selector representing a single element. + + Returns: + True if enabled, false if not. + */ + this.isTargetEnabled = function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return _targetsEnabled[_getId(el)] === true; + }; + + /* + Function: ready + Helper method to bind a function to jsPlumb's ready event. + */ + this.ready = function(fn) { + _currentInstance.bind("ready", fn); + }, + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el, ui, timestamp) { + //var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) { + //_processElement(el[i]); + _draw(_getElementObject(el), ui, timestamp); + } + else // ...and single strings. + //_processElement(el); + _draw(_getElementObject(el), ui, timestamp); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, null); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"), + ebe = endpointsByElement[elId]; + if (ebe) { + for ( var i = 0; i < ebe.length; i++) + _currentInstance.deleteEndpoint(ebe[i]); + } + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + var _registeredListeners = {}, + _unbindRegisteredListeners = function() { + for (var i in _registeredListeners) { + for (var j = 0; j < _registeredListeners[i].length; j++) { + var info = _registeredListeners[i][j]; + jsPlumb.CurrentLibrary.unbind(info.el, info.event, info.listener); + } + } + _registeredListeners = {}; + }; + + // internal register listener method. gives us a hook to clean things up + // with if the user calls jsPlumb.reset. + this.registerListener = function(el, type, listener) { + jsPlumb.CurrentLibrary.bind(el, type, listener); + _addToList(_registeredListeners, type, {el:el, event:type, listener:listener}); + }; + + this.unregisterListener = function(el, type, listener) { + jsPlumb.CurrentLibrary.unbind(el, type, listener); + _removeWithFunction(_registeredListeners, function(rl) { + return rl.type == type && rl.listener == listener; + }); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + _currentInstance.deleteEveryEndpoint(); + _currentInstance.unbind(); + _targetEndpointDefinitions = {}; + _targetEndpoints = {}; + _targetEndpointsUnique = {}; + _targetMaxConnections = {}; + _sourceEndpointDefinitions = {}; + _sourceEndpoints = {}; + _sourceEndpointsUnique = {}; + _sourceMaxConnections = {}; + _unbindRegisteredListeners(); + _currentInstance.anchorManager.reset(); + if (!jsPlumbAdapter.headless) + _currentInstance.dragManager.reset(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + + Parameters: + value - whether or not to automatically repaint when the window is resized. + + Returns: void + * + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + };*/ + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setId + * Changes the id of some element, adjusting all connections and endpoints + * + * Parameters: + * el - a selector, a DOM element, or a string. + * newId - string. + */ + this.setId = function(el, newId, doNotSetAttribute) { + + var id = el.constructor == String ? el : _currentInstance.getId(el), + sConns = _currentInstance.getConnections({source:id, scope:'*'}, true), + tConns = _currentInstance.getConnections({target:id, scope:'*'}, true); + + newId = "" + newId; + + if (!doNotSetAttribute) { + el = jsPlumb.CurrentLibrary.getElementObject(id); + jsPlumb.CurrentLibrary.setAttribute(el, "id", newId); + } + + el = jsPlumb.CurrentLibrary.getElementObject(newId); + + + endpointsByElement[newId] = endpointsByElement[id] || []; + for (var i = 0; i < endpointsByElement[newId].length; i++) { + endpointsByElement[newId][i].elementId = newId; + endpointsByElement[newId][i].element = el; + endpointsByElement[newId][i].anchor.elementId = newId; + } + delete endpointsByElement[id]; + + _currentInstance.anchorManager.changeId(id, newId); + + var _conns = function(list, epIdx, type) { + for (var i = 0; i < list.length; i++) { + list[i].endpoints[epIdx].elementId = newId; + list[i].endpoints[epIdx].element = el; + list[i][type + "Id"] = newId; + list[i][type] = el; + } + }; + _conns(sConns, 0, "source"); + _conns(tConns, 1, "target"); + }; + + /* + * Function: setIdChanged + * Notify jsPlumb that the element with oldId has had its id changed to newId. + */ + this.setIdChanged = function(oldId, newId) { + _currentInstance.setId(oldId, newId, true); + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction + * Sets the function to fire when the window size has changed and a repaint was fired. + * + * Parameters: + * f - Function to execute. + * + * Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + + var _suspendDrawing = false; + /* + * Function: setSuspendDrawing + * Suspends drawing operations. This can be used when you have a lot of connections to make or endpoints to register; + * it will save you a lot of time. + */ + this.setSuspendDrawing = function(val, repaintAfterwards) { + _suspendDrawing = val; + if (repaintAfterwards) _currentInstance.repaintEverything(); + }; + + /* + * Function: isSuspendDrawing + * Returns whether or not drawing is currently suspended. + */ + this.isSuspendDrawing = function() { + return _suspendDrawing; + }; + + /* + * Constant for use with the setRenderMode method + */ + this.CANVAS = "canvas"; + + /* + * Constant for use with the setRenderMode method + */ + this.SVG = "svg"; + + this.VML = "vml"; + + /* + * Function: setRenderMode + * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that + * what you asked for is not supported (and that VML is). If you asked for VML but the browser does + * not support it, jsPlumb uses SVG. + * + * Returns: + * the render mode that jsPlumb set, which of course may be different from that requested. + */ + this.setRenderMode = function(mode) { + /*if (mode) + mode = mode.toLowerCase(); + else + return; + if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); + // now test we actually have the capability to do this. + if (mode === jsPlumb.SVG) { + if (svgAvailable) renderMode = jsPlumb.SVG + else if (canvasAvailable) renderMode = jsPlumb.CANVAS + else if (vmlAvailable()) renderMode = jsPlumb.VML + } + else if (mode === jsPlumb.CANVAS && canvasAvailable) renderMode = jsPlumb.CANVAS; + else if (vmlAvailable()) renderMode = jsPlumb.VML; + + return renderMode;*/ + renderMode = jsPlumbAdapter.setRenderMode(mode); + return renderMode; + }; + + this.getRenderMode = function() { return renderMode; }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * changeEndpoints - whether or not to also change the visible state of the endpoints on the element. this also has a bearing on + * other connections on those endpoints: if their other endpoint is also visible, the connections are made visible. + * + * Returns: + * void + */ + this.show = function(el, changeEndpoints) { + _setVisible(el, "block", changeEndpoints); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + //findIndex : _findIndex, + getId : _getId, + makeAnchor:self.makeAnchor, + makeDynamicAnchor:self.makeDynamicAnchor + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * changeEndpoints - whether or not to also toggle the endpoints on the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload + * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element. + * + * Returns: + * void + */ + this.unload = function() { + // this used to do something, but it turns out that what it did was nothing. + // now it exists only for backwards compatibility. + }; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + var adjustForParentOffsetAndScroll = function(xy, el) { + + var offsetParent = null, result = xy; + if (el.tagName.toLowerCase() === "svg" && el.parentNode) { + offsetParent = el.parentNode; + } + else if (el.offsetParent) { + offsetParent = el.offsetParent; + } + if (offsetParent != null) { + var po = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(offsetParent), + so = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : {left:offsetParent.scrollLeft, top:offsetParent.scrollTop}; + + + // i thought it might be cool to do this: + // lastReturnValue[0] = lastReturnValue[0] - offsetParent.offsetLeft + offsetParent.scrollLeft; + // lastReturnValue[1] = lastReturnValue[1] - offsetParent.offsetTop + offsetParent.scrollTop; + // but i think it ignores margins. my reasoning was that it's quicker to not hand off to some underlying + // library. + + result[0] = xy[0] - po.left + so.left; + result[1] = xy[1] - po.top + so.top; + } + + return result; + + }; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + this.elementId = params.elementId; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + + if (timestamp && timestamp === self.timestamp) + return lastReturnValue; + + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + + // adjust loc if there is an offsetParent + lastReturnValue = adjustForParentOffsetAndScroll(lastReturnValue, element.canvas); + + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function(_endpoint) { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + * + * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference, + // the canvas this refers to. + refCanvas = params.referenceCanvas, + size = _getSize(_getElementObject(refCanvas)), + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + xDir = 0, yDir = 0, + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + orientation = null, + _lastResult = null; + + // set these to 0 each; they are used by certain types of connectors in the loopback case, + // when the connector is trying to clear the element it is on. but for floating anchor it's not + // very important. + this.x = 0; this.y = 0; + + this.isFloating = true; + + this.compute = function(params) { + var xy = params.xy, element = params.element, + result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + + // adjust loc if there is an offsetParent + result = adjustForParentOffsetAndScroll(result, element.canvas); + + _lastResult = result; + return result; + }; + + this.getOrientation = function(_endpoint) { + if (orientation) return orientation; + else { + var o = ref.getOrientation(_endpoint); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchor is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector, elementId) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = [], self = this, + _convert = function(anchor) { + return anchor.constructor == Anchor ? anchor: _currentInstance.makeAnchor(anchor, elementId, _currentInstance); + }; + for (var i = 0; i < anchors.length; i++) + _anchors[i] = _convert(anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + this.locked = false; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null, + _curIndex = _anchors.length > 0 ? 0 : -1, + self = this, + + // helper method to calculate the distance between the centers of the two elements. + _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }, + + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + self.x = _curAnchor.x; + self.y = _curAnchor.y; + + return _curAnchor.compute(params); + }; + + this.getCurrentLocation = function() { + return _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + }; + + this.getOrientation = function(_endpoint) { return _curAnchor != null ? _curAnchor.getOrientation(_endpoint) : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + manages anchors for all elements. + */ + // "continuous" anchors: anchors that pick their location based on how many connections the given element has. + // this requires looking at a lot more elements than normal - anything that has had a Continuous anchor applied has + // to be recalculated. so this manager is used as a reference point. the first time, with a new timestamp, that + // a continuous anchor is asked to compute, it calls this guy. or maybe, even, this guy gets called somewhere else + // and compute only ever returns pre-computed values. either way, this is the central point, and we want it to + // be called as few times as possible. + var continuousAnchors = {}, + continuousAnchorLocations = {}, + continuousAnchorOrientations = {}, + Orientation = { HORIZONTAL : "horizontal", VERTICAL : "vertical", DIAGONAL : "diagonal", IDENTITY:"identity" }, + + // TODO this functions uses a crude method of determining orientation between two elements. + // 'diagonal' should be chosen when the angle of the line between the two centers is around + // one of 45, 135, 225 and 315 degrees. maybe +- 15 degrees. + calculateOrientation = function(sourceId, targetId, sd, td) { + + if (sourceId === targetId) return { + orientation:Orientation.IDENTITY, + a:["top", "top"] + }; + + var theta = Math.atan2((td.centery - sd.centery) , (td.centerx - sd.centerx)), + theta2 = Math.atan2((sd.centery - td.centery) , (sd.centerx - td.centerx)), + h = ((sd.left <= td.left && sd.right >= td.left) || (sd.left <= td.right && sd.right >= td.right) || + (sd.left <= td.left && sd.right >= td.right) || (td.left <= sd.left && td.right >= sd.right)), + v = ((sd.top <= td.top && sd.bottom >= td.top) || (sd.top <= td.bottom && sd.bottom >= td.bottom) || + (sd.top <= td.top && sd.bottom >= td.bottom) || (td.top <= sd.top && td.bottom >= sd.bottom)); + + if (! (h || v)) { + var a = null, rls = false, rrs = false, sortValue = null; + if (td.left > sd.left && td.top > sd.top) + a = ["right", "top"]; + else if (td.left > sd.left && sd.top > td.top) + a = [ "top", "left"]; + else if (td.left < sd.left && td.top < sd.top) + a = [ "top", "right"]; + else if (td.left < sd.left && td.top > sd.top) + a = ["left", "top" ]; + + return { orientation:Orientation.DIAGONAL, a:a, theta:theta, theta2:theta2 }; + } + else if (h) return { + orientation:Orientation.HORIZONTAL, + a:sd.top < td.top ? ["bottom", "top"] : ["top", "bottom"], + theta:theta, theta2:theta2 + } + else return { + orientation:Orientation.VERTICAL, + a:sd.left < td.left ? ["right", "left"] : ["left", "right"], + theta:theta, theta2:theta2 + } + }, + placeAnchorsOnLine = function(desc, elementDimensions, elementPosition, + connections, horizontal, otherMultiplier, reverse) { + var a = [], step = elementDimensions[horizontal ? 0 : 1] / (connections.length + 1); + + for (var i = 0; i < connections.length; i++) { + var val = (i + 1) * step, other = otherMultiplier * elementDimensions[horizontal ? 1 : 0]; + if (reverse) + val = elementDimensions[horizontal ? 0 : 1] - val; + + var dx = (horizontal ? val : other), x = elementPosition[0] + dx, xp = dx / elementDimensions[0], + dy = (horizontal ? other : val), y = elementPosition[1] + dy, yp = dy / elementDimensions[1]; + + a.push([ x, y, xp, yp, connections[i][1], connections[i][2] ]); + } + + return a; + }, + standardEdgeSort = function(a, b) { return a[0] > b[0] ? 1 : -1 }, + currySort = function(reverseAngles) { + return function(a,b) { + var r = true; + if (reverseAngles) { + if (a[0][0] < b[0][0]) + r = true; + else + r = a[0][1] > b[0][1]; + } + else { + if (a[0][0] > b[0][0]) + r= true; + else + r =a[0][1] > b[0][1]; + } + return r === false ? -1 : 1; + }; + }, + leftSort = function(a,b) { + // first get adjusted values + var p1 = a[0][0] < 0 ? -Math.PI - a[0][0] : Math.PI - a[0][0], + p2 = b[0][0] < 0 ? -Math.PI - b[0][0] : Math.PI - b[0][0]; + if (p1 > p2) return 1; + else return a[0][1] > b[0][1] ? 1 : -1; + }, + edgeSortFunctions = { + "top":standardEdgeSort, + "right":currySort(true), + "bottom":currySort(true), + "left":leftSort + }, + _sortHelper = function(_array, _fn) { + return _array.sort(_fn); + }, + placeAnchors = function(elementId, _anchorLists) { + var sS = sizes[elementId], sO = offsets[elementId], + placeSomeAnchors = function(desc, elementDimensions, elementPosition, unsortedConnections, isHorizontal, otherMultiplier, orientation) { + if (unsortedConnections.length > 0) { + var sc = _sortHelper(unsortedConnections, edgeSortFunctions[desc]), // puts them in order based on the target element's pos on screen + reverse = desc === "right" || desc === "top", + anchors = placeAnchorsOnLine(desc, elementDimensions, + elementPosition, sc, + isHorizontal, otherMultiplier, reverse ); + + // takes a computed anchor position and adjusts it for parent offset and scroll, then stores it. + var _setAnchorLocation = function(endpoint, anchorPos) { + var a = adjustForParentOffsetAndScroll([anchorPos[0], anchorPos[1]], endpoint.canvas); + continuousAnchorLocations[endpoint.id] = [ a[0], a[1], anchorPos[2], anchorPos[3] ]; + continuousAnchorOrientations[endpoint.id] = orientation; + }; + + for (var i = 0; i < anchors.length; i++) { + var c = anchors[i][4], weAreSource = c.endpoints[0].elementId === elementId, weAreTarget = c.endpoints[1].elementId === elementId; + if (weAreSource) + _setAnchorLocation(c.endpoints[0], anchors[i]); + else if (weAreTarget) + _setAnchorLocation(c.endpoints[1], anchors[i]); + } + } + }; + + placeSomeAnchors("bottom", sS, [sO.left,sO.top], _anchorLists.bottom, true, 1, [0,1]); + placeSomeAnchors("top", sS, [sO.left,sO.top], _anchorLists.top, true, 0, [0,-1]); + placeSomeAnchors("left", sS, [sO.left,sO.top], _anchorLists.left, false, 0, [-1,0]); + placeSomeAnchors("right", sS, [sO.left,sO.top], _anchorLists.right, false, 1, [1,0]); + }, + AnchorManager = function() { + var _amEndpoints = {}, + connectionsByElementId = {}, + self = this, + anchorLists = {}; + + this.reset = function() { + _amEndpoints = {}; + connectionsByElementId = {}; + anchorLists = {}; + }; + this.newConnection = function(conn) { + var sourceId = conn.sourceId, targetId = conn.targetId, + ep = conn.endpoints, + doRegisterTarget = true, + registerConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + + if ((sourceId == targetId) && otherAnchor.isContinuous){ + // remove the target endpoint's canvas. we dont need it. + jsPlumb.CurrentLibrary.removeElement(ep[1].canvas); + doRegisterTarget = false; + } + _addToList(connectionsByElementId, elId, [c, otherEndpoint, otherAnchor.constructor == DynamicAnchor]); + }; + + registerConnection(0, ep[0], ep[0].anchor, targetId, conn); + if (doRegisterTarget) + registerConnection(1, ep[1], ep[1].anchor, sourceId, conn); + }; + this.connectionDetached = function(connInfo) { + var connection = connInfo.connection || connInfo; + var sourceId = connection.sourceId, + targetId = connection.targetId, + ep = connection.endpoints, + removeConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + if (otherAnchor.constructor == FloatingAnchor) { + // no-op + } + else { + _removeWithFunction(connectionsByElementId[elId], function(_c) { + return _c[0].id == c.id; + }); + } + }; + + removeConnection(1, ep[1], ep[1].anchor, sourceId, connection); + removeConnection(0, ep[0], ep[0].anchor, targetId, connection); + + // remove from anchorLists + var sEl = connection.sourceId, + tEl = connection.targetId, + sE = connection.endpoints[0].id, + tE = connection.endpoints[1].id, + _remove = function(list, eId) { + if (list) { // transient anchors dont get entries in this list. + var f = function(e) { return e[4] == eId; }; + _removeWithFunction(list["top"], f); + _removeWithFunction(list["left"], f); + _removeWithFunction(list["bottom"], f); + _removeWithFunction(list["right"], f); + } + }; + + _remove(anchorLists[sEl], sE); + _remove(anchorLists[tEl], tE); + self.redraw(sEl); + self.redraw(tEl); + }; + this.add = function(endpoint, elementId) { + _addToList(_amEndpoints, elementId, endpoint); + }; + this.changeId = function(oldId, newId) { + connectionsByElementId[newId] = connectionsByElementId[oldId]; + _amEndpoints[newId] = _amEndpoints[oldId]; + delete connectionsByElementId[oldId]; + delete _amEndpoints[oldId]; + }; + this.getConnectionsFor = function(elementId) { + return connectionsByElementId[elementId] || []; + }; + this.getEndpointsFor = function(elementId) { + return _amEndpoints[elementId] || []; + }; + this.deleteEndpoint = function(endpoint) { + _removeWithFunction(_amEndpoints[endpoint.elementId], function(e) { + return e.id == endpoint.id; + }); + }; + this.clearFor = function(elementId) { + delete _amEndpoints[elementId]; + _amEndpoints[elementId] = []; + }; + // updates the given anchor list by either updating an existing anchor's info, or adding it. this function + // also removes the anchor from its previous list, if the edge it is on has changed. + // all connections found along the way (those that are connected to one of the faces this function + // operates on) are added to the connsToPaint list, as are their endpoints. in this way we know to repaint + // them wthout having to calculate anything else about them. + var _updateAnchorList = function(lists, theta, order, conn, aBoolean, otherElId, idx, reverse, edgeId, elId, connsToPaint, endpointsToPaint) { + // first try to find the exact match, but keep track of the first index of a matching element id along the way.s + var exactIdx = -1, + firstMatchingElIdx = -1, + endpoint = conn.endpoints[idx], + endpointId = endpoint.id, + oIdx = [1,0][idx], + values = [ [ theta, order ], conn, aBoolean, otherElId, endpointId ], + listToAddTo = lists[edgeId], + listToRemoveFrom = endpoint._continuousAnchorEdge ? lists[endpoint._continuousAnchorEdge] : null; + + if (listToRemoveFrom) { + var rIdx = _findWithFunction(listToRemoveFrom, function(e) { return e[4] == endpointId }); + if (rIdx != -1) { + listToRemoveFrom.splice(rIdx, 1); + // get all connections from this list + for (var i = 0; i < listToRemoveFrom.length; i++) { + _addWithFunction(connsToPaint, listToRemoveFrom[i][1], function(c) { return c.id == listToRemoveFrom[i][1].id }); + _addWithFunction(endpointsToPaint, listToRemoveFrom[i][1].endpoints[idx], function(e) { return e.id == listToRemoveFrom[i][1].endpoints[idx].id }); + } + } + } + + for (var i = 0; i < listToAddTo.length; i++) { + if (idx == 1 && listToAddTo[i][3] === otherElId && firstMatchingElIdx == -1) + firstMatchingElIdx = i; + _addWithFunction(connsToPaint, listToAddTo[i][1], function(c) { return c.id == listToAddTo[i][1].id }); + _addWithFunction(endpointsToPaint, listToAddTo[i][1].endpoints[idx], function(e) { return e.id == listToAddTo[i][1].endpoints[idx].id }); + } + if (exactIdx != -1) { + listToAddTo[exactIdx] = values; + } + else { + var insertIdx = reverse ? firstMatchingElIdx != -1 ? firstMatchingElIdx : 0 : listToAddTo.length; // of course we will get this from having looked through the array shortly. + listToAddTo.splice(insertIdx, 0, values); + } + + // store this for next time. + endpoint._continuousAnchorEdge = edgeId; + }; + this.redraw = function(elementId, ui, timestamp, offsetToUI) { + + if (!_suspendDrawing) { + // get all the endpoints for this element + var ep = _amEndpoints[elementId] || [], + endpointConnections = connectionsByElementId[elementId] || [], + connectionsToPaint = [], + endpointsToPaint = [], + anchorsToUpdate = []; + + timestamp = timestamp || _timestamp(); + // offsetToUI are values that would have been calculated in the dragManager when registering + // an endpoint for an element that had a parent (somewhere in the hierarchy) that had been + // registered as draggable. + offsetToUI = offsetToUI || {left:0, top:0}; + if (ui) { + ui = { + left:ui.left + offsetToUI.left, + top:ui.top + offsetToUI.top + } + } + + _updateOffset( { elId : elementId, offset : ui, recalc : false, timestamp : timestamp }); + // valid for one paint cycle. + var myOffset = offsets[elementId], + myWH = sizes[elementId], + orientationCache = {}; + + // actually, first we should compute the orientation of this element to all other elements to which + // this element is connected with a continuous anchor (whether both ends of the connection have + // a continuous anchor or just one) + //for (var i = 0; i < continuousAnchorConnections.length; i++) { + for (var i = 0; i < endpointConnections.length; i++) { + var conn = endpointConnections[i][0], + sourceId = conn.sourceId, + targetId = conn.targetId, + sourceContinuous = conn.endpoints[0].anchor.isContinuous, + targetContinuous = conn.endpoints[1].anchor.isContinuous; + + if (sourceContinuous || targetContinuous) { + var oKey = sourceId + "_" + targetId, + oKey2 = targetId + "_" + sourceId, + o = orientationCache[oKey], + oIdx = conn.sourceId == elementId ? 1 : 0; + + if (sourceContinuous && !anchorLists[sourceId]) anchorLists[sourceId] = { top:[], right:[], bottom:[], left:[] }; + if (targetContinuous && !anchorLists[targetId]) anchorLists[targetId] = { top:[], right:[], bottom:[], left:[] }; + + if (elementId != targetId) _updateOffset( { elId : targetId, timestamp : timestamp }); + if (elementId != sourceId) _updateOffset( { elId : sourceId, timestamp : timestamp }); + + var td = _getCachedData(targetId), + sd = _getCachedData(sourceId); + + if (targetId == sourceId && (sourceContinuous || targetContinuous)) { + // here we may want to improve this by somehow determining the face we'd like + // to put the connector on. ideally, when drawing, the face should be calculated + // by determining which face is closest to the point at which the mouse button + // was released. for now, we're putting it on the top face. + _updateAnchorList(anchorLists[sourceId], -Math.PI / 2, 0, conn, false, targetId, 0, false, "top", sourceId, connectionsToPaint, endpointsToPaint) + } + else { + if (!o) { + o = calculateOrientation(sourceId, targetId, sd.o, td.o); + orientationCache[oKey] = o; + // this would be a performance enhancement, but the computed angles need to be clamped to + //the (-PI/2 -> PI/2) range in order for the sorting to work properly. + /* orientationCache[oKey2] = { + orientation:o.orientation, + a:[o.a[1], o.a[0]], + theta:o.theta + Math.PI, + theta2:o.theta2 + Math.PI + };*/ + } + if (sourceContinuous) _updateAnchorList(anchorLists[sourceId], o.theta, 0, conn, false, targetId, 0, false, o.a[0], sourceId, connectionsToPaint, endpointsToPaint); + if (targetContinuous) _updateAnchorList(anchorLists[targetId], o.theta2, -1, conn, true, sourceId, 1, true, o.a[1], targetId, connectionsToPaint, endpointsToPaint); + } + + if (sourceContinuous) _addWithFunction(anchorsToUpdate, sourceId, function(a) { return a === sourceId; }); + if (targetContinuous) _addWithFunction(anchorsToUpdate, targetId, function(a) { return a === targetId; }); + _addWithFunction(connectionsToPaint, conn, function(c) { return c.id == conn.id; }); + if ((sourceContinuous && oIdx == 0) || (targetContinuous && oIdx == 1)) + _addWithFunction(endpointsToPaint, conn.endpoints[oIdx], function(e) { return e.id == conn.endpoints[oIdx].id; }); + } + } + + // place Endpoints whose anchors are continuous but have no Connections + for (var i = 0; i < ep.length; i++) { + if (ep[i].connections.length == 0 && ep[i].anchor.isContinuous) { + if (!anchorLists[elementId]) anchorLists[elementId] = { top:[], right:[], bottom:[], left:[] }; + _updateAnchorList(anchorLists[elementId], -Math.PI / 2, 0, {endpoints:[ep[i], ep[i]], paint:function(){}}, false, elementId, 0, false, "top", elementId, connectionsToPaint, endpointsToPaint) + _addWithFunction(anchorsToUpdate, elementId, function(a) { return a === elementId; }) + } + } + + // now place all the continuous anchors we need to; + for (var i = 0; i < anchorsToUpdate.length; i++) { + placeAnchors(anchorsToUpdate[i], anchorLists[anchorsToUpdate[i]]); + } + + // now that continuous anchors have been placed, paint all the endpoints for this element + // TODO performance: add the endpoint ids to a temp array, and then when iterating in the next + // loop, check that we didn't just paint that endpoint. we can probably shave off a few more milliseconds this way. + for (var i = 0; i < ep.length; i++) { + ep[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + // ... and any other endpoints we came across as a result of the continuous anchors. + for (var i = 0; i < endpointsToPaint.length; i++) { + endpointsToPaint[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + + // paint all the standard and "dynamic connections", which are connections whose other anchor is + // static and therefore does need to be recomputed; we make sure that happens only one time. + + // TODO we could have compiled a list of these in the first pass through connections; might save some time. + for (var i = 0; i < endpointConnections.length; i++) { + var otherEndpoint = endpointConnections[i][1]; + if (otherEndpoint.anchor.constructor == DynamicAnchor) { + otherEndpoint.paint({ elementWithPrecedence:elementId }); + _addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; }); + // all the connections for the other endpoint now need to be repainted + for (var k = 0; k < otherEndpoint.connections.length; k++) { + if (otherEndpoint.connections[k] !== endpointConnections[i][0]) + _addWithFunction(connectionsToPaint, otherEndpoint.connections[k], function(c) { return c.id == otherEndpoint.connections[k].id; }); + } + } else if (otherEndpoint.anchor.constructor == Anchor) { + _addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; }); + } + } + // paint current floating connection for this element, if there is one. + var fc = floatingConnections[elementId]; + if (fc) + fc.paint({timestamp:timestamp, recalc:false, elId:elementId}); + + // paint all the connections + for (var i = 0; i < connectionsToPaint.length; i++) { + connectionsToPaint[i].paint({elId:elementId, timestamp:timestamp, recalc:false}); + } + } + }; + this.rehomeEndpoint = function(currentId, element) { + var eps = _amEndpoints[currentId] || [], //, + elementId = _currentInstance.getId(element); + for (var i = 0; i < eps.length; i++) { + self.add(eps[i], elementId); + } + eps.splice(0, eps.length); + }; + }; + _currentInstance.anchorManager = new AnchorManager(); + _currentInstance.continuousAnchorFactory = { + get:function(params) { + var existing = continuousAnchors[params.elementId]; + if (!existing) { + existing = { + type:"Continuous", + compute : function(params) { + return continuousAnchorLocations[params.element.id] || [0,0]; + }, + getCurrentLocation : function(endpoint) { + return continuousAnchorLocations[endpoint.id] || [0,0]; + }, + getOrientation : function(endpoint) { + return continuousAnchorOrientations[endpoint.id] || [0,0]; + }, + isDynamic : true, + isContinuous : true + }; + continuousAnchors[params.elementId] = existing; + } + return existing; + } + }; + + if (!jsPlumbAdapter.headless) + _currentInstance.dragManager = jsPlumbAdapter.getDragManager(_currentInstance); + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. You should not ever create one of these directly. If you make a call to jsPlumb.connect, all of + * the parameters that you pass in to that function will be passed to the Connection constructor; if your UI + * uses the various Endpoint-centric methods like addEndpoint/makeSource/makeTarget, along with drag and drop, + * then the parameters you set on those functions are translated and passed in to the Connection constructor. So + * you should check the documentation for each of those methods. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * container - optional id or selector instructing jsPlumb where to attach all the elements it creates for this connection. you should read the documentation for a full discussion of this. + * detachable - optional, defaults to true. Defines whether or not the connection may be detached using the mouse. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * cssClass - optional CSS class to set on the display element associated with this Connection. + * hoverClass - optional CSS class to set on the display element associated with this Connection when it is in hover state. + * overlays - Optional array of Overlay definitions to appear on this Connection. + * drawEndpoints - if false, instructs jsPlumb to not draw the endpoints for this Connection. Be careful with this: it only really works when you tell jsPlumb to attach elements to the document body. Read the documentation for a full discussion of this. + * parameters - Optional JS object containing parameters to set on the Connection. These parameters are then available via the getParameter method. + */ + var Connection = function(params) { + var self = this, visible = true, _internalHover, _superClassHover; + self.idPrefix = "_jsplumb_c_"; + self.defaultLabelLocation = 0.5; + self.defaultOverlayKeys = ["Overlays", "ConnectionOverlays"]; + this.parent = params.parent; + overlayCapableJsPlumbUIComponent.apply(this, arguments); + // ************** get the source and target and register the connection. ******************* + +// VISIBILITY + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + self[v ? "showOverlays" : "hideOverlays"](); + if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none"; + self.repaint(); + }; +// END VISIBILITY + +// TYPE + + this.getTypeDescriptor = function() { return "connection"; }; + this.getDefaultType = function() { + return { + parameters:{}, + scope:null, + detachable:self._jsPlumb.Defaults.ConnectionsDetachable, + paintStyle:self._jsPlumb.Defaults.PaintStyle || jsPlumb.Defaults.PaintStyle, + connector:self._jsPlumb.Defaults.Connector || jsPlumb.Defaults.Connector, + hoverPaintStyle:self._jsPlumb.Defaults.HoverPaintStyle || jsPlumb.Defaults.HoverPaintStyle, + overlays:self._jsPlumb.Defaults.ConnectorOverlays || jsPlumb.Defaults.ConnectorOverlays + }; + }; + var superAt = this.applyType; + this.applyType = function(t) { + superAt(t); + if (t.detachable != null) self.setDetachable(t.detachable); + if (t.scope) self.scope = t.scope; + self.setConnector(t.connector); + }; +// END TYPE + +// HOVER + // override setHover to pass it down to the underlying connector + _superClassHover = self.setHover; + self.setHover = function(state) { + var zi = _currentInstance.ConnectorZIndex || jsPlumb.Defaults.ConnectorZIndex; + if (zi) + self.connector.setZIndex(zi + (state ? 1 : 0)); + self.connector.setHover.apply(self.connector, arguments); + _superClassHover.apply(self, arguments); + }; + + _internalHover = function(state) { + if (_connectionBeingDragged == null) { + self.setHover(state, false); + } + }; +// END HOVER + + /* + * Function: setConnector + * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method - the same + * thing that you would set as the 'connector' property on a jsPlumb.connect call. + * + * Parameters: + * connector - Connector definition + */ + this.setConnector = function(connector, doNotRepaint) { + if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent); + var connectorArgs = { _jsPlumb:self._jsPlumb, parent:params.parent, + cssClass:params.cssClass, container:params.container, tooltip:self.tooltip + }; + if (_isString(connector)) + this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand. + else if (_isArray(connector)) { + if (connector.length == 1) + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](connectorArgs); + else + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs)); + } + self.canvas = self.connector.canvas; + // binds mouse listeners to the current connector. + _bindListeners(self.connector, self, _internalHover); + + // set z-index if it was set on Defaults. + var zi = _currentInstance.ConnectorZIndex || jsPlumb.Defaults.ConnectorZIndex; + if (zi) + self.connector.setZIndex(zi); + + if (!doNotRepaint) self.repaint(); + }; + +// INITIALISATION CODE + + this.source = _getElementObject(params.source); + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. but + // source is not overridden if the Endpoint has declared it is not the final target of a connection; + // instead we use the source that the Endpoint declares will be the final source element. + if (params.sourceEndpoint) this.source = params.sourceEndpoint.endpointWillMoveTo || params.sourceEndpoint.getElement(); + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + + // if a new connection is the result of moving some existing connection, params.previousConnection + // will have that Connection in it. listeners for the jsPlumbConnection event can look for that + // member and take action if they need to. + self.previousConnection = params.previousConnection; + + this.sourceId = _getAttribute(this.source, "id"); + this.targetId = _getAttribute(this.target, "id"); + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams, elementId) { + return (anchorParams) ? _currentInstance.makeAnchor(anchorParams, elementId, _currentInstance) : null; + }, + prepareEndpoint = function(existing, index, params, element, elementId, connectorPaintStyle, connectorHoverPaintStyle) { + var e; + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] + || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint; + + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified. + if (es.fillStyle == null && connectorPaintStyle != null) + es.fillStyle = connectorPaintStyle.strokeStyle; + + // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does: + //* + if (es.outlineColor == null && connectorPaintStyle != null) + es.outlineColor = connectorPaintStyle.outlineColor; + if (es.outlineWidth == null && connectorPaintStyle != null) + es.outlineWidth = connectorPaintStyle.outlineWidth; + //*/ + + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure? + if (connectorHoverPaintStyle != null) { + if (ehs == null) ehs = {}; + if (ehs.fillStyle == null) { + ehs.fillStyle = connectorHoverPaintStyle.strokeStyle; + } + } + var a = params.anchors ? params.anchors[index] : + params.anchor ? params.anchor : + _makeAnchor(_currentInstance.Defaults.Anchors[index], elementId) || + _makeAnchor(jsPlumb.Defaults.Anchors[index], elementId) || + _makeAnchor(_currentInstance.Defaults.Anchor, elementId) || + _makeAnchor(jsPlumb.Defaults.Anchor, elementId), + u = params.uuids ? params.uuids[index] : null; + e = _newEndpoint({ + paintStyle : es, hoverPaintStyle:ehs, endpoint : ep, connections : [ self ], + uuid : u, anchor : a, source : element, scope : params.scope, container:params.container, + reattach:params.reattach, detachable:params.detachable + }); + self.endpoints[index] = e; + + if (params.drawEndpoints === false) e.setVisible(false, true, true); + + } + return e; + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source, + self.sourceId, params.paintStyle, params.hoverPaintStyle); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target, + self.targetId, params.paintStyle, params.hoverPaintStyle); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + // if delete endpoints on detach, keep a record of just exactly which endpoints they are. + if (params.deleteEndpointsOnDetach) + self.endpointsToDeleteOnDetach = [eS, eT]; + + self.setConnector(this.endpoints[0].connector || + this.endpoints[1].connector || + params.connector || + _currentInstance.Defaults.Connector || + jsPlumb.Defaults.Connector, true); + + this.setPaintStyle(this.endpoints[0].connectorStyle || + this.endpoints[1].connectorStyle || + params.paintStyle || + _currentInstance.Defaults.PaintStyle || + jsPlumb.Defaults.PaintStyle, true); + + this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || + this.endpoints[1].connectorHoverStyle || + params.hoverPaintStyle || + _currentInstance.Defaults.HoverPaintStyle || + jsPlumb.Defaults.HoverPaintStyle, true); + + this.paintStyleInUse = this.getPaintStyle(); + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId], + otherOffset = offsets[this.targetId], + otherWH = sizes[this.targetId], + initialTimestamp = _timestamp(), + anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + elementId:this.endpoints[0].elementId, + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1], + timestamp:initialTimestamp + }); + + this.endpoints[0].paint( { anchorLoc : anchorLoc, timestamp:initialTimestamp }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + elementId:this.endpoints[1].elementId, + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0], + timestamp:initialTimestamp + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc, timestamp:initialTimestamp }); + +// END INITIALISATION CODE + +// DETACHABLE + var _detachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.detachable === false) _detachable = false; + if(self.endpoints[0].connectionsDetachable === false) _detachable = false; + if(self.endpoints[1].connectionsDetachable === false) _detachable = false; + /* + Function: isDetachable + Returns whether or not this connection can be detached from its target/source endpoint. by default this + is false; use it in conjunction with the 'reattach' parameter. + */ + this.isDetachable = function() { + return _detachable === true; + }; + /* + Function: setDetachable + Sets whether or not this connection is detachable. + */ + this.setDetachable = function(detachable) { + _detachable = detachable === true; + }; +// END DETACHABLE + +// COST + DIRECTIONALITY + // if cost not supplied, try to inherit from source endpoint + var _cost = params.cost || self.endpoints[0].getConnectionCost(); + self.getCost = function() { return _cost; }; + self.setCost = function(c) { _cost = c; }; + var _bidirectional = !(params.bidirectional === false); + // inherit bidirectional flag if set no source endpoint + if (params.bidirectional == null) _bidirectional = self.endpoints[0].areConnectionsBidirectional(); + self.isBidirectional = function() { return _bidirectional; }; +// END COST + DIRECTIONALITY + +// PARAMETERS + // merge all the parameters objects into the connection. parameters set + // on the connection take precedence; then target endpoint params, then + // finally source endpoint params. + // TODO jsPlumb.extend could be made to take more than two args, and it would + // apply the second through nth args in order. + var _p = jsPlumb.extend({}, this.endpoints[0].getParameters()); + jsPlumb.extend(_p, this.endpoints[1].getParameters()); + jsPlumb.extend(_p, self.getParameters()); + self.setParameters(_p); +// END PARAMETERS + +// MISCELLANEOUS + /** + * implementation of abstract method in jsPlumbUtil.EventGenerator + * @return list of attached elements. in our case, a list of Endpoints. + */ + this.getAttachedElements = function() { + return self.endpoints; + }; + + /** + * changes the parent element of this connection to newParent. not exposed for the public API. + */ + this.moveParent = function(newParent) { + var jpcl = jsPlumb.CurrentLibrary, curParent = jpcl.getParent(self.connector.canvas); + if (self.connector.bgCanvas) { + jpcl.removeElement(self.connector.bgCanvas, curParent); + jpcl.appendElement(self.connector.bgCanvas, newParent); + } + jpcl.removeElement(self.connector.canvas, curParent); + jpcl.appendElement(self.connector.canvas, newParent); + // this only applies for DOMOverlays + for (var i = 0; i < self.overlays.length; i++) { + if (self.overlays[i].isAppendedAtTopLevel) { + jpcl.removeElement(self.overlays[i].canvas, curParent); + jpcl.appendElement(self.overlays[i].canvas, newParent); + if (self.overlays[i].reattachListeners) + self.overlays[i].reattachListeners(self.connector); + } + } + if (self.connector.reattachListeners) // this is for SVG/VML; change an element's parent and you have to reinit its listeners. + self.connector.reattachListeners(); // the Canvas implementation doesn't have to care about this + }; + +// END MISCELLANEOUS + +// PAINTING + + /* + * Paints the Connection. Not exposed for public usage. + * + * Parameters: + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + this.paint = function(params) { + if (visible) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp, + // if the moving object is not the source we must transpose the two references. + swap = false, + tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId, + tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + + var sourceInfo = _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }), + targetInfo = _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var sE = this.endpoints[sIdx], tE = this.endpoints[tIdx], + sAnchorP = sE.anchor.getCurrentLocation(sE), + tAnchorP = tE.anchor.getCurrentLocation(tE); + + /* paint overlays*/ + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible()) maxSize = Math.max(maxSize, o.computeMaxSize()); + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, + this.endpoints[sIdx], this.endpoints[tIdx], + this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, + self.paintStyleInUse.lineWidth, maxSize, + sourceInfo, + targetInfo); + + self.connector.paint(dim, self.paintStyleInUse); + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim); + } + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function(params) { + params = params || {}; + var recalc = !(params.recalc === false); + this.paint({ elId : this.sourceId, recalc : recalc, timestamp:params.timestamp }); + }; + + // the very last thing we do is check to see if a 'type' was supplied in the params + var _type = params.type || self.endpoints[0].connectionType || self.endpoints[1].connectionType; + if (_type) + self.setType(_type); + +// END PAINTING + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Connection. + * + * Parameters: + * event - the event to bind. Available events on a Connection are: + * - *click* : notification that a Connection was clicked. + * - *dblclick* : notification that a Connection was double clicked. + * - *mouseenter* : notification that the mouse is over a Connection. + * - *mouseexit* : notification that the mouse exited a Connection. + * - *contextmenu* : notification that the user right-clicked on the Connection. + * + * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Connection's paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: getPaintStyle + * Gets the Connection's paint style. This is not necessarily the paint style in use at the time; + * this is the paint style for the connection when the mouse it not hovering over it. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Connection will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Connection. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + + /* + * Function: getParameter + * Gets the named parameter; returns null if no parameter with that name is set. Parameter values may have been supplied to a 'connect' or 'addEndpoint' call (connections made with the mouse get a copy of all parameters set on each of their Endpoints), or the parameter value may have been set with setParameter. + * + * Parameters: + * key - Parameter name. + */ + + /* + * Function: setParameter + * Sets the named parameter to the given value. + * + * Parameters: + * key - Parameter name. + * value - Parameter value. + */ + + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc) + */ + + /* + * Property: sourceId + * Id of the source element in the connection. + */ + /* + * Property: targetId + * Id of the target element in the connection. + */ + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + /* + * Property: source + * The source element for this Connection. + */ + /* + * Property: target + * The target element for this Connection. + */ + /* + * Property: overlays + * List of Overlays for this component. + */ + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + /* + * Function: getOverlay + * Gets an overlay, by ID. Note: by ID. You would pass an 'id' parameter + * in to the Overlay's constructor arguments, and then use that to retrieve + * it via this method. + */ + /* + * Function: getOverlays + * Gets all the overlays for this component. + */ + /* + * Function: hideOverlay + * Hides the overlay specified by the given id. + */ + /* + * Function: hideOverlays + * Hides all Overlays + */ + /* + * Function: showOverlay + * Shows the overlay specified by the given id. + */ + /* + * Function: showOverlays + * Shows all Overlays + */ + /** + * Function: removeAllOverlays + * Removes all overlays from the Connection, and then repaints. + */ + /** + * Function: removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + /** + * Function: removeOverlays + * Removes a set of overlays by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String, a Function that returns a String, or a params object containing { "label", "labelStyle", "location", "cssClass" }. Note that this uses innerHTML on the label div, so keep + * that in mind if you need escaped HTML. + */ + /* + Function: getLabel + Returns the label text for this Connection (or a function if you are labelling with a function). + This does not return the overlay itself; this is a convenience method which is a pair with + setLabel; together they allow you to add and access a Label Overlay without having to create the + Overlay object itself. For access to the underlying label overlay that jsPlumb has created, + use getLabelOverlay. + */ + /* + Function: getLabelOverlay + Returns the underlying internal label overlay, which will exist if you specified a label on + a connect call, or have called setLabel at any stage. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + + }; // END Connection class + +// ENDPOINT HELPER FUNCTIONS + var _makeConnectionDragHandler = function(placeholder) { + var stopped = false; + return { + + drag : function() { + if (stopped) { + stopped = false; + return true; + } + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments), + el = placeholder.element; + if (el) { + jsPlumb.CurrentLibrary.setOffset(el, _ui); + _draw(_getElementObject(el), _ui); + } + }, + stopDrag : function() { + stopped = true; + } + }; + }; + + var _makeFloatingEndpoint = function(paintStyle, referenceAnchor, endpoint, referenceCanvas, sourceElement) { + var floatingAnchor = new FloatingAnchor( { reference : referenceAnchor, referenceCanvas : referenceCanvas }); + + //setting the scope here should not be the way to fix that mootools issue. it should be fixed by not + // adding the floating endpoint as a droppable. that makes more sense anyway! + + return _newEndpoint({ paintStyle : paintStyle, endpoint : endpoint, anchor : floatingAnchor, source : sourceElement, scope:"__floating" }); + }; + + /** + * creates a placeholder div for dragging purposes, adds it to the DOM, and pre-computes its offset. then returns + * both the element id and a selector for the element. + */ + var _makeDraggablePlaceholder = function(placeholder, parent) { + var n = document.createElement("div"); + n.style.position = "absolute"; + var placeholderDragElement = _getElementObject(n); + _appendElement(n, parent); + var id = _getId(placeholderDragElement); + _updateOffset( { elId : id }); + // create and assign an id, and initialize the offset. + placeholder.id = id; + placeholder.element = placeholderDragElement; + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1 + * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't + * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you + * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or + * 'target' Endpoints for Connections. + * + * + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * enabled - optional, defaults to true. Indicates whether or not the Endpoint should be enabled for mouse events (drag/drop). + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * cssClass - optional CSS class to set on the display element associated with this Endpoint. + * hoverClass - optional CSS class to set on the display element associated with this Endpoint when it is in hover state. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * container - optional id or selector instructing jsPlumb where to attach the element it creates for this endpoint. you should read the documentation for a full discussion of this. + * connections - optional list of Connections to configure the Endpoint with. + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * connectorClass - optional CSS class to set on Connections emanating from this Endpoint. + * connectorHoverClass - optional CSS class to set on to set on Connections emanating from this Endpoint when they are in hover state. + * connectionsDetachable - optional, defaults to true. Sets whether connections to/from this Endpoint should be detachable or not. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + * parameters - Optional JS object containing parameters to set on the Endpoint. These parameters are then available via the getParameter method. When a connection is made involving this Endpoint, the parameters from this Endpoint are copied into that Connection. Source Endpoint parameters override target Endpoint parameters if they both have a parameter with the same name. + */ + var Endpoint = function(params) { + var self = this; + self.idPrefix = "_jsplumb_e_"; + self.defaultLabelLocation = [ 0.5, 0.5 ]; + self.defaultOverlayKeys = ["Overlays", "EndpointOverlays"]; + this.parent = params.parent; + overlayCapableJsPlumbUIComponent.apply(this, arguments); + params = params || {}; + +// TYPE + + this.getTypeDescriptor = function() { return "endpoint"; }; + this.getDefaultType = function() { + return { + parameters:{}, + scope:null, + maxConnections:self._jsPlumb.Defaults.MaxConnections, + paintStyle:self._jsPlumb.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle, + endpoint:self._jsPlumb.Defaults.Endpoint || jsPlumb.Defaults.Endpoint, + hoverPaintStyle:self._jsPlumb.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle, + overlays:self._jsPlumb.Defaults.EndpointOverlays || jsPlumb.Defaults.EndpointOverlays, + connectorStyle:params.connectorStyle, + connectorHoverStyle:params.connectorHoverStyle, + connectorClass:params.connectorClass, + connectorHoverClass:params.connectorHoverClass, + connectorOverlays:params.connectorOverlays, + connector:params.connector, + connectorTooltip:params.connectorTooltip + }; + }; + var superAt = this.applyType; + this.applyType = function(t) { + superAt(t); + if (t.maxConnections != null) _maxConnections = t.maxConnections; + if (t.scope) self.scope = t.scope; + self.connectorStyle = t.connectorStyle; + self.connectorHoverStyle = t.connectorHoverStyle; + self.connectorOverlays = t.connectorOverlays; + self.connector = t.connector; + self.connectorTooltip = t.connectorTooltip; + self.connectionType = t.connectionType; + self.connectorClass = t.connectorClass; + self.connectorHoverClass = t.connectorHoverClass; + }; +// END TYPE + + var visible = true, __enabled = !(params.enabled === false); + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + self[v ? "showOverlays" : "hideOverlays"](); + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + + /* + Function: isEnabled + Returns whether or not the Endpoint is enabled for drag/drop connections. + */ + this.isEnabled = function() { return __enabled; }; + + /* + Function: setEnabled + Sets whether or not the Endpoint is enabled for drag/drop connections. + */ + this.setEnabled = function(e) { __enabled = e; }; + + var _element = params.source, _uuid = params.uuid, floatingEndpoint = null, inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + this.element = _element; + + var _connectionCost = params.connectionCost; + this.getConnectionCost = function() { return _connectionCost; }; + this.setConnectionCost = function(c) { + _connectionCost = c; + }; + + var _connectionsBidirectional = params.connectionsBidirectional === false ? false : true; + this.areConnectionsBidirectional = function() { return _connectionsBidirectional; }; + this.setConnectionsBidirectional = function(b) { _connectionsBidirectional = b; }; + + self.anchor = params.anchor ? _currentInstance.makeAnchor(params.anchor, _elementId, _currentInstance) : params.anchors ? _currentInstance.makeAnchor(params.anchors, _elementId, _currentInstance) : _currentInstance.makeAnchor("TopCenter", _elementId, _currentInstance); + + // ANCHOR MANAGER + if (!params._transient) // in place copies, for example, are transient. they will never need to be retrieved during a paint cycle, because they dont move, and then they are deleted. + _currentInstance.anchorManager.add(self, _elementId); + + var _endpoint = null, originalEndpoint = null; + this.setEndpoint = function(ep) { + var endpointArgs = { + _jsPlumb:self._jsPlumb, + parent:params.parent, + container:params.container, + tooltip:params.tooltip, + connectorTooltip:params.connectorTooltip, + endpoint:self + }; + if (_isString(ep)) + _endpoint = new jsPlumb.Endpoints[renderMode][ep](endpointArgs); + else if (_isArray(ep)) { + endpointArgs = jsPlumb.extend(ep[1], endpointArgs); + _endpoint = new jsPlumb.Endpoints[renderMode][ep[0]](endpointArgs); + } + else { + _endpoint = ep.clone(); + } + + // assign a clone function using a copy of endpointArgs. this is used when a drag starts: the endpoint that was dragged is cloned, + // and the clone is left in its place while the original one goes off on a magical journey. + // the copy is to get around a closure problem, in which endpointArgs ends up getting shared by + // the whole world. + var argsForClone = jsPlumb.extend({}, endpointArgs); + _endpoint.clone = function() { + var o = new Object(); + _endpoint.constructor.apply(o, [argsForClone]); + return o; + }; + + self.endpoint = _endpoint; + self.type = self.endpoint.type; + }; + + this.setEndpoint(params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot"); + originalEndpoint = _endpoint; + + // override setHover to pass it down to the underlying endpoint + var _sh = self.setHover; + self.setHover = function() { + self.endpoint.setHover.apply(self.endpoint, arguments); + _sh.apply(self, arguments); + }; + // endpoint delegates to first connection for hover, if there is one. + var internalHover = function(state) { + if (self.connections.length > 0) + self.connections[0].setHover(state, false); + else + self.setHover(state); + }; + + // bind listeners from endpoint to self, with the internal hover function defined above. + _bindListeners(self.endpoint, self, internalHover); + + this.setPaintStyle(params.paintStyle || + params.style || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyle, true); + this.setHoverPaintStyle(params.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyle, true); + this.paintStyleInUse = this.getPaintStyle(); + var originalPaintStyle = this.getPaintStyle(); + this.connectorStyle = params.connectorStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.connectorTooltip = params.connectorTooltip; + this.connectorClass = params.connectorClass; + this.connectorHoverClass = params.connectorHoverClass; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + + var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + this.canvas = this.endpoint.canvas; + this.connections = params.connections || []; + + this.scope = params.scope || DEFAULT_SCOPE; + this.connectionType = params.connectionType; + this.timestamp = null; + self.isReattach = params.reattach || false; + self.connectionsDetachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.connectionsDetachable === false || params.detachable === false) + self.connectionsDetachable = false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + if (params.onMaxConnections) + self.bind("maxConnections", params.onMaxConnections); + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget, forceDetach, fireEvent, originalEvent) { + var idx = _findWithFunction(self.connections, function(c) { return c.id == connection.id}), + actuallyDetached = false; + fireEvent = (fireEvent !== false); + if (idx >= 0) { + // 1. does the connection have a before detach (note this also checks jsPlumb's bound + // detach handlers; but then Endpoint's check will, too, hmm.) + if (forceDetach || connection._forceDetach || connection.isDetachable() || connection.isDetachAllowed(connection)) { + // get the target endpoint + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + // it would be nice to check with both endpoints that it is ok to detach. but + // for this we'll have to get a bit fancier: right now if you use the same beforeDetach + // interceptor for two endpoints (which is kind of common, because it's part of the + // endpoint definition), then it gets fired twice. so in fact we need to loop through + // each beforeDetach and see if it returns false, at which point we exit. but if it + // returns true, we have to check the next one. however we need to track which ones + // have already been run, and not run them again. + if (forceDetach || connection._forceDetach || (self.isDetachAllowed(connection) /*&& t.isDetachAllowed(connection)*/)) { + + self.connections.splice(idx, 1); + + // this avoids a circular loop + if (!ignoreTarget) { + + t.detach(connection, true, forceDetach); + // check connection to see if we want to delete the endpoints associated with it. + // we only detach those that have just this connection; this scenario is most + // likely if we got to this bit of code because it is set by the methods that + // create their own endpoints, like .connect or .makeTarget. the user is + // not likely to have interacted with those endpoints. + if (connection.endpointsToDeleteOnDetach){ + for (var i = 0; i < connection.endpointsToDeleteOnDetach.length; i++) { + var cde = connection.endpointsToDeleteOnDetach[i]; + if (cde && cde.connections.length == 0) + _currentInstance.deleteEndpoint(cde); + } + } + } + _removeElements(connection.connector.getDisplayElements(), connection.parent); + _removeWithFunction(connectionsByScope[connection.scope], function(c) { + return c.id == connection.id; + }); + actuallyDetached = true; + var doFireEvent = (!ignoreTarget && fireEvent) + fireDetachEvent(connection, doFireEvent, originalEvent); + } + } + } + return actuallyDetached; + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + * + * Parameters: + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachAll = function(fireEvent, originalEvent) { + while (self.connections.length > 0) { + self.detach(self.connections[0], false, true, fireEvent, originalEvent); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachFrom = function(targetEndpoint, fireEvent, originalEvent) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + if (self.detach(c[i], false, true, fireEvent, originalEvent)) + c[i].setHover(false, false); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findWithFunction(self.connections, function(c) { return c.id == connection.id}); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: setElement + * Sets the DOM element this Endpoint is attached to. + */ + this.setElement = function(el, container) { + + // TODO possibly have this object take charge of moving the UI components into the appropriate + // parent. this is used only by makeSource right now, and that function takes care of + // moving the UI bits and pieces. however it would s + var parentId = _getId(el); + // remove the endpoint from the list for the current endpoint's element + _removeWithFunction(endpointsByElement[self.elementId], function(e) { + return e.id == self.id; + }); + _element = _getElementObject(el); + _elementId = _getId(_element); + self.elementId = _elementId; + // need to get the new parent now + var newParentElement = _getParentFromParams({source:parentId, container:container}), + curParent = jpcl.getParent(self.canvas); + jpcl.removeElement(self.canvas, curParent); + jpcl.appendElement(self.canvas, newParentElement); + + // now move connection(s)...i would expect there to be only one but we will iterate. + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].moveParent(newParentElement); + self.connections[i].sourceId = _elementId; + self.connections[i].source = _element; + } + _addToList(endpointsByElement, parentId, self); + //_currentInstance.repaint(parentId); + + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + var loc = self.anchor.getCurrentLocation(self), + o = self.anchor.getOrientation(self), + inPlaceAnchor = { + compute:function() { return [ loc[0], loc[1] ]}, + getCurrentLocation : function() { return [ loc[0], loc[1] ]}, + getOrientation:function() { return o; } + }; + + return _newEndpoint( { + anchor : inPlaceAnchor, + source : _element, + paintStyle : this.getPaintStyle(), + endpoint : _endpoint, + _transient:true, + scope:self.scope + }); + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + var candidate = self.connections[0]; + if (self.isTarget && candidate) return candidate; + else { + return (self.connections.length < _maxConnections) || _maxConnections == -1 ? null : candidate; + } + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + /* + * Function: setStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to + * setStyle and deprecate it if so. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + * + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + // a helper function that tries to find a connection to the given element, and returns it if so. if elementWithPrecedence is null, + // or no connection to it is found, we return the first connection in our list. + var findConnectionToUseForDynamicAnchor = function(elementWithPrecedence) { + var idx = 0; + if (elementWithPrecedence != null) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].sourceId == elementWithPrecedence || self.connections[i].targetId == elementWithPrecedence) { + idx = i; + break; + } + } + } + + return self.connections[idx]; + }; + + /* + * Function: paint + * Paints the Endpoint, recalculating offset and anchor positions if necessary. This does NOT paint + * any of the Endpoint's connections. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + params = params || {}; + var timestamp = params.timestamp, + recalc = !(params.recalc === false); + if (!timestamp || self.timestamp !== timestamp) { + _updateOffset({ elId:_elementId, timestamp:timestamp, recalc:recalc }); + var xy = params.offset || offsets[_elementId]; + if(xy) { + var ap = params.anchorPoint,connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (recalc && self.anchor.isDynamic && self.connections.length > 0) { + var c = findConnectionToUseForDynamicAnchor(params.elementWithPrecedence), + oIdx = c.endpoints[0] == self ? 1 : 0, + oId = oIdx == 0 ? c.sourceId : c.targetId, + oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + ap = self.anchor.compute(anchorParams); + } + + // switched _endpoint to self here; continuous anchors, for instance, look for the + // endpoint's id, but here "_endpoint" is the renderer, not the actual endpoint. + // TODO need to verify this does not break anything. + //var d = _endpoint.compute(ap, self.anchor.getOrientation(_endpoint), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + var d = _endpoint.compute(ap, self.anchor.getOrientation(self), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + _endpoint.paint(d, self.paintStyleInUse, self.anchor); + self.timestamp = timestamp; + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) self.overlayPlacements[i] = o.draw(self.endpoint, self.paintStyleInUse, d); + } + } + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var placeholderInfo = { id:null, element:null }, + jpc = null, + existingJpc = false, + existingJpcParams = null, + _dragHandler = _makeConnectionDragHandler(placeholderInfo); + + var start = function() { + // drag might have started on an endpoint that is not actually a source, but which has + // one or more connections. + jpc = self.connectorSelector(); + var _continue = true; + // if not enabled, return + if (!self.isEnabled()) _continue = false; + // if no connection and we're not a source, return. + if (jpc == null && !params.isSource) _continue = false; + // otherwise if we're full and not allowed to drag, also return false. + if (params.isSource && self.isFull() && !dragAllowedWhenFull) _continue = false; + // if the connection was setup as not detachable or one of its endpoints + // was setup as connectionsDetachable = false, or Defaults.ConnectionsDetachable + // is set to false... + if (jpc != null && !jpc.isDetachable()) _continue = false; + + if (_continue === false) { + // this is for mootools and yui. returning false from this causes jquery to stop drag. + // the events are wrapped in both mootools and yui anyway, but i don't think returning + // false from the start callback would stop a drag. + if (jsPlumb.CurrentLibrary.stopDrag) jsPlumb.CurrentLibrary.stopDrag(); + _dragHandler.stopDrag(); + return false; + } + + // if we're not full but there was a connection, make it null. we'll create a new one. + if (jpc && !self.isFull() && params.isSource) jpc = null; + + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + _makeDraggablePlaceholder(placeholderInfo, self.parent); + + // set the offset of this div to be where 'inPlaceCopy' is, to start with. + // TODO merge this code with the code in both Anchor and FloatingAnchor, because it + // does the same stuff. + var ipcoel = _getElementObject(inPlaceCopy.canvas), + ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel), + po = adjustForParentOffsetAndScroll([ipco.left, ipco.top], inPlaceCopy.canvas); + jsPlumb.CurrentLibrary.setOffset(placeholderInfo.element, {left:po[0], top:po[1]}); + + // when using makeSource and a parent, we first draw the source anchor on the source element, then + // move it to the parent. note that this happens after drawing the placeholder for the + // first time. + if (self.parentAnchor) self.anchor = _currentInstance.makeAnchor(self.parentAnchor, self.elementId, _currentInstance); + + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", placeholderInfo.id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + + // create a floating endpoint. + // here we test to see if a dragProxy was specified in this endpoint's constructor params, and + // if so, we create that endpoint instead of cloning ourselves. + if (params.proxy) { + /* var floatingAnchor = new FloatingAnchor( { reference : self.anchor, referenceCanvas : self.canvas }); + + floatingEndpoint = _newEndpoint({ + paintStyle : params.proxy.paintStyle, + endpoint : params.proxy.endpoint, + anchor : floatingAnchor, + source : placeholderInfo.element, + scope:"__floating" + }); + + //$(self.canvas).hide(); + */ + self.setPaintStyle(params.proxy.paintStyle); + // if we do this, we have to cleanup the old one. like just remove its display parts + //self.setEndpoint(params.proxy.endpoint); + + } + // else { + floatingEndpoint = _makeFloatingEndpoint(self.getPaintStyle(), self.anchor, _endpoint, self.canvas, placeholderInfo.element); + // } + + if (jpc == null) { + self.anchor.locked = true; + self.setHover(false, false); + // TODO the hover call above does not reset any target endpoint's hover + // states. + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : self.endpointWillMoveTo || _getElementObject(_element), // for makeSource with parent option. ensure source element is represented correctly. + target : placeholderInfo.element, + anchors : [ self.anchor, floatingEndpoint.anchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays, + type:self.connectionType, + cssClass:self.connectorClass, + hoverClass:self.connectorHoverClass + }); + + } else { + existingJpc = true; + jpc.connector.setHover(false, false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas), false, true); + // new anchor idx + var anchorIdx = jpc.endpoints[0].id == self.id ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas), + dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // now we want to get this endpoint's DROP scope, and set it for now: we can only be dropped on drop zones + // that have our drop scope (issue 57). + var dropScope = jsPlumb.CurrentLibrary.getDropScope(c); + jsPlumb.CurrentLibrary.setDragScope(c, dropScope); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = placeholderInfo.element; + jpc.sourceId = placeholderInfo.id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = placeholderInfo.element; + jpc.targetId = placeholderInfo.id; + } + + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.suspendedEndpoint.setHover(false); + jpc.endpoints[anchorIdx] = floatingEndpoint; + + // fire an event that informs that a connection is being dragged + fireConnectionDraggingEvent(jpc); + + } + // register it and register connection on it. + floatingConnections[placeholderInfo.id] = jpc; + floatingEndpoint.addConnection(jpc); + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, placeholderInfo.id, floatingEndpoint); + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var jpcl = jsPlumb.CurrentLibrary, + dragOptions = params.dragOptions || {}, + defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions), + startEvent = jpcl.dragEvents["start"], + stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"]; + + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + // extracted drag handler function so can be used by makeSource + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], _dragHandler.drag); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + var originalEvent = jpcl.getDropEvent(arguments); + _currentInstance.currentlyDragging = false; + _removeWithFunction(endpointsByElement[placeholderInfo.id], function(e) { + return e.id == floatingEndpoint.id; + }); + _removeElements( [ placeholderInfo.element[0], floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + _currentInstance.anchorManager.clearFor(placeholderInfo.id); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + self.setPaintStyle(originalPaintStyle); // reset to original; may have been changed by drag proxy. + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (self.isReattach || jpc._forceDetach || !jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc, false, false, true, originalEvent)) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + _currentInstance.repaint(existingJpcParams[1]); + } + jpc._forceDetach = null; + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElements(jpc.connector.getDisplayElements(), self.parent); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint({recalc:false}); + jpc.setHover(false, false); + + fireConnectionDragStopEvent(jpc); + + jpc = null; + inPlaceCopy = null; + delete endpointsByElement[floatingEndpoint.elementId]; + floatingEndpoint.anchor = null; + floatingEndpoint = null; + _currentInstance.currentlyDragging = false; + + + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions, true); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas, forceInit, isTransient, endpoint) { + if ((params.isTarget || forceInit) && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop'], + overEvent = jsPlumb.CurrentLibrary.dragEvents['over'], + outEvent = jsPlumb.CurrentLibrary.dragEvents['out'], + drop = function() { + + var originalEvent = jsPlumb.CurrentLibrary.getDropEvent(arguments), + draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + elId = _getAttribute(draggable, "elId"), + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id]; + + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + + // restore the original scope if necessary (issue 57) + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + var endpointEnabled = endpoint != null ? endpoint.isEnabled() : true; + + if (self.isFull()) { + self.fire("maxConnections", { + endpoint:self, + connection:jpc, + maxConnections:_maxConnections + }, originalEvent); + } + + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget) && endpointEnabled) { + + var _doContinue = true; + + // the second check here is for the case that the user is dropping it back + // where it came from. + if (jpc.suspendedEndpoint && jpc.suspendedEndpoint.id != self.id) { + if (idx == 0) { + jpc.source = jpc.suspendedEndpoint.element; + jpc.sourceId = jpc.suspendedEndpoint.elementId; + } else { + jpc.target = jpc.suspendedEndpoint.element; + jpc.targetId = jpc.suspendedEndpoint.elementId; + } + + if (!jpc.isDetachAllowed(jpc) || !jpc.endpoints[idx].isDetachAllowed(jpc) || !jpc.suspendedEndpoint.isDetachAllowed(jpc) || !_currentInstance.checkCondition("beforeDetach", jpc)) + _doContinue = false; + } + + // these have to be set before testing for beforeDrop. + if (idx == 0) { + jpc.source = self.element; + jpc.sourceId = self.elementId; + } else { + jpc.target = self.element; + jpc.targetId = self.elementId; + } + + // now check beforeDrop. this will be available only on Endpoints that are setup to + // have a beforeDrop condition (although, secretly, under the hood all Endpoints and + // the Connection have them, because they are on jsPlumbUIComponent. shhh!), because + // it only makes sense to have it on a target endpoint. + _doContinue = _doContinue && self.isDropAllowed(jpc.sourceId, jpc.targetId, jpc.scope, jpc, self); + + if (_doContinue) { + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + + // copy our parameters in to the connection: + var params = self.getParameters(); + for (var aParam in params) + jpc.setParameter(aParam, params[aParam]); + + if (!jpc.suspendedEndpoint) { + //_initDraggableIfNecessary(self.element, params.draggable, {}); + if (params.draggable) + jsPlumb.CurrentLibrary.initDraggable(self.element, dragOptions, true); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + fireDetachEvent({ + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }, true, originalEvent); + } + + // finalise will inform the anchor manager and also add to + // connectionsByScope if necessary. + _finaliseConnection(jpc, null, originalEvent); + } + else { + // otherwise just put it back on the endpoint it was on before the drag. + if (jpc.suspendedEndpoint) { + // self.detachFrom(jpc); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.setHover(false); + jpc._forceDetach = true; + if (idx == 0) { + jpc.source = jpc.suspendedEndpoint.element; + jpc.sourceId = jpc.suspendedEndpoint.elementId; + } else { + jpc.target = jpc.suspendedEndpoint.element; + jpc.targetId = jpc.suspendedEndpoint.elementId;; + } + jpc.suspendedEndpoint.addConnection(jpc); + + jpc.endpoints[0].repaint(); + jpc.repaint(); + _currentInstance.repaint(jpc.source.elementId); + jpc._forceDetach = false; + } + } + + jpc.floatingAnchorIndex = null; + } + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + } + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], function() { + if (self.isTarget) { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + } + } + }); + dropOptions[outEvent] = _wrap(dropOptions[outEvent], function() { + if (self.isTarget) { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + } + } + }); + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions, true, isTransient); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas), true, !(params._transient || self.anchor.isFloating), self); + + // finally, set type if it was provided + if (params.type) + self.setType(params.type); + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Endpoint. + * + * Parameters: + * event - the event to bind. Available events on an Endpoint are: + * - *click* : notification that a Endpoint was clicked. + * - *dblclick* : notification that a Endpoint was double clicked. + * - *mouseenter* : notification that the mouse is over a Endpoint. + * - *mouseexit* : notification that the mouse exited a Endpoint. + * - *contextmenu* : notification that the user right-clicked on the Endpoint. + * + * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Endpoint's paint style and then repaints the Endpoint. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: getPaintStyle + * Gets the Endpoint's paint style. This is not necessarily the paint style in use at the time; + * this is the paint style for the Endpoint when the mouse it not hovering over it. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Endpoint will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Endpoint. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + + /* + * Function: getParameter + * Gets the named parameter; returns null if no parameter with that name is set. Parameters may have been set on the Endpoint in the 'addEndpoint' call, or they may have been set with the setParameter function. + * + * Parameters: + * key - Parameter name. + */ + + /* + * Function: setParameter + * Sets the named parameter to the given value. + * + * Parameters: + * key - Parameter name. + * value - Parameter value. + */ + + /* + * Property: canvas + * The Endpoint's drawing area. + */ + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + + /* + * Property: overlays + * List of Overlays for this Endpoint. + */ + /* + * Function: addOverlay + * Adds an Overlay to the Endpoint. + * + * Parameters: + * overlay - Overlay to add. + */ + /* + * Function: getOverlay + * Gets an overlay, by ID. Note: by ID. You would pass an 'id' parameter + * in to the Overlay's constructor arguments, and then use that to retrieve + * it via this method. + */ + /* + * Function: getOverlays + * Gets all the overlays for this component. + */ + /* + * Function: hideOverlay + * Hides the overlay specified by the given id. + */ + /* + * Function: hideOverlays + * Hides all Overlays + */ + /* + * Function: showOverlay + * Shows the overlay specified by the given id. + */ + /* + * Function: showOverlays + * Shows all Overlays + */ + /** + * Function: removeAllOverlays + * Removes all overlays from the Endpoint, and then repaints. + */ + /** + * Function: removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + /** + * Function: removeOverlays + * Removes a set of overlays by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + /* + * Function: setLabel + * Sets the Endpoint's label. + * + * Parameters: + * l - label to set. May be a String, a Function that returns a String, or a params object containing { "label", "labelStyle", "location", "cssClass" }. Note that this uses innerHTML on the label div, so keep + * that in mind if you need escaped HTML. + */ + /* + Function: getLabel + Returns the label text for this Endpoint (or a function if you are labelling with a function). + This does not return the overlay itself; this is a convenience method which is a pair with + setLabel; together they allow you to add and access a Label Overlay without having to create the + Overlay object itself. For access to the underlying label overlay that jsPlumb has created, + use getLabelOverlay. + */ + /* + Function: getLabelOverlay + Returns the underlying internal label overlay, which will exist if you specified a label on + an addEndpoint call, or have called setLabel at any stage. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + + return self; + }; + }; + + var jsPlumb = new jsPlumbInstance(); + if (typeof window != 'undefined') window.jsPlumb = jsPlumb; + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + j.init(); + return j; + }; + + var _curryAnchor = function(x, y, ox, oy, type, fnInit) { + return function(params) { + params = params || {}; + //var a = jsPlumb.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance); + var a = params.jsPlumbInstance.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance); + a.type = type; + if (fnInit) fnInit(a, params); + return a; + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1, "TopCenter"); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1, "BottomCenter"); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0, "LeftMiddle"); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0, "RightMiddle"); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0, "Center"); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1, "TopRight"); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1, "BottomRight"); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1, "TopLeft"); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1, "BottomLeft"); + + // TODO test that this does not break with the current instance idea + jsPlumb.Defaults.DynamicAnchors = function(params) { + return params.jsPlumbInstance.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"], params.elementId, params.jsPlumbInstance); + }; + jsPlumb.Anchors["AutoDefault"] = function(params) { + var a = params.jsPlumbInstance.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors(params)); + a.type = "AutoDefault"; + return a; + }; + + jsPlumb.Anchors["Assign"] = _curryAnchor(0,0,0,0,"Assign", function(anchor, params) { + // find what to use as the "position finder". the user may have supplied a String which represents + // the id of a position finder in jsPlumb.AnchorPositionFinders, or the user may have supplied the + // position finder as a function. we find out what to use and then set it on the anchor. + var pf = params.position || "Fixed"; + anchor.positionFinder = pf.constructor == String ? params.jsPlumbInstance.AnchorPositionFinders[pf] : pf; + // always set the constructor params; the position finder might need them later (the Grid one does, + // for example) + anchor.constructorParams = params; + }); + + // Continuous anchor is just curried through to the 'get' method of the continuous anchor + // factory. + jsPlumb.Anchors["Continuous"] = function(params) { + return params.jsPlumbInstance.continuousAnchorFactory.get(params); + }; + + // these are the default anchor positions finders, which are used by the makeTarget function. supply + // a position finder argument to that function allows you to specify where the resulting anchor will + // be located + jsPlumb.AnchorPositionFinders = { + "Fixed": function(dp, ep, es, params) { + return [ (dp.left - ep.left) / es[0], (dp.top - ep.top) / es[1] ]; + }, + "Grid":function(dp, ep, es, params) { + var dx = dp.left - ep.left, dy = dp.top - ep.top, + gx = es[0] / (params.grid[0]), gy = es[1] / (params.grid[1]), + mx = Math.floor(dx / gx), my = Math.floor(dy / gy); + return [ ((mx * gx) + (gx / 2)) / es[0], ((my * gy) + (gy / 2)) / es[1] ]; + } + }; +})(); diff --git a/build/1.3.13/js/jsPlumb-1.3.13-tests.js b/archive/1.3.12/jsPlumb-1.3.12-tests.js similarity index 100% rename from build/1.3.13/js/jsPlumb-1.3.13-tests.js rename to archive/1.3.12/jsPlumb-1.3.12-tests.js diff --git a/archive/1.3.12/jsPlumb-connectors-statemachine-1.3.12-RC1.js b/archive/1.3.12/jsPlumb-connectors-statemachine-1.3.12-RC1.js new file mode 100644 index 000000000..48edac6a4 --- /dev/null +++ b/archive/1.3.12/jsPlumb-connectors-statemachine-1.3.12-RC1.js @@ -0,0 +1,469 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.12 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the state machine connectors. + * + * Thanks to Brainstorm Mobile Solutions for supporting the development of these. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + var Line = function(x1, y1, x2, y2) { + + this.m = (y2 - y1) / (x2 - x1); + this.b = -1 * ((this.m * x1) - y1); + + this.rectIntersect = function(x,y,w,h) { + var results = []; + + // try top face + // the equation of the top face is y = (0 * x) + b; y = b. + var xInt = (y - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try right face + var yInt = (this.m * (x + w)) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + // bottom face + var xInt = ((y + h) - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try left face + var yInt = (this.m * x) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + if (results.length == 2) { + var midx = (results[0][0] + results[1][0]) / 2, midy = (results[0][1] + results[1][1]) / 2; + results.push([ midx,midy ]); + // now calculate the segment inside the rectangle where the midpoint lies. + var xseg = midx <= x + (w / 2) ? -1 : 1, + yseg = midy <= y + (h / 2) ? -1 : 1; + results.push([xseg, yseg]); + return results; + } + + return null; + + }; + }, + _segment = function(x1, y1, x2, y2) { + if (x1 <= x2 && y2 <= y1) return 1; + else if (x1 <= x2 && y1 <= y2) return 2; + else if (x2 <= x1 && y2 >= y1) return 3; + return 4; + }, + + // the control point we will use depends on the faces to which each end of the connection is assigned, specifically whether or not the + // two faces are parallel or perpendicular. if they are parallel then the control point lies on the midpoint of the axis in which they + // are parellel and varies only in the other axis; this variation is proportional to the distance that the anchor points lie from the + // center of that face. if the two faces are perpendicular then the control point is at some distance from both the midpoints; the amount and + // direction are dependent on the orientation of the two elements. 'seg', passed in to this method, tells you which segment the target element + // lies in with respect to the source: 1 is top right, 2 is bottom right, 3 is bottom left, 4 is top left. + // + // sourcePos and targetPos are arrays of info about where on the source and target each anchor is located. their contents are: + // + // 0 - absolute x + // 1 - absolute y + // 2 - proportional x in element (0 is left edge, 1 is right edge) + // 3 - proportional y in element (0 is top edge, 1 is bottom edge) + // + _findControlPoint = function(midx, midy, segment, sourceEdge, targetEdge, dx, dy, distance, proximityLimit) { + + // TODO (maybe) + // - if anchor pos is 0.5, make the control point take into account the relative position of the elements. + if (distance <= proximityLimit) return [midx, midy]; + + if (segment == 1) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 2) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 3) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 4) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + }; + + /** + * Class: Connectors.StateMachine + * Provides 'state machine' connectors. + */ + /* + * Function: Constructor + * + * Parameters: + * curviness - measure of how "curvy" the connectors will be. this is translated as the distance that the + * Bezier curve's control point is from the midpoint of the straight line connecting the two + * endpoints, and does not mean that the connector is this wide. The Bezier curve never reaches + * its control points; they act as gravitational masses. defaults to 10. + * margin - distance from element to start and end connectors, in pixels. defaults to 5. + * proximityLimit - sets the distance beneath which the elements are consider too close together to bother + * with fancy curves. by default this is 80 pixels. + * loopbackRadius - the radius of a loopback connector. optional; defaults to 25. + * showLoopback - If set to false this tells the connector that it is ok to paint connections whose source and target is the same element with a connector running through the element. The default value for this is true; the connector always makes a loopback connection loop around the element rather than passing through it. + */ + jsPlumb.Connectors.StateMachine = function(params) { + var self = this, + currentPoints = null, + _sx, _sy, _tx, _ty, _controlPoint = [], + curviness = params.curviness || 10, + margin = params.margin || 5, + proximityLimit = params.proximityLimit || 80, + clockwise = params.orientation && params.orientation == "clockwise", + loopbackRadius = params.loopbackRadius || 25, + isLoopback = false, + showLoopback = params.showLoopback !== false; + + this.type = "StateMachine"; + params = params || {}; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *= 1.9; + //ensure at least one pixel width + lineWidth = lineWidth || 1; + var x = Math.min(sourcePos[0], targetPos[0]) - xo, + y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (!showLoopback || (sourceEndpoint.elementId != targetEndpoint.elementId)) { + + isLoopback = false; + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + + // now adjust for the margin + if (sourcePos[2] == 0) _sx -= margin; + if (sourcePos[2] == 1) _sx += margin; + if (sourcePos[3] == 0) _sy -= margin; + if (sourcePos[3] == 1) _sy += margin; + if (targetPos[2] == 0) _tx -= margin; + if (targetPos[2] == 1) _tx += margin; + if (targetPos[3] == 0) _ty -= margin; + if (targetPos[3] == 1) _ty += margin; + + // + // these connectors are quadratic bezier curves, having a single control point. if both anchors + // are located at 0.5 on their respective faces, the control point is set to the midpoint and you + // get a straight line. this is also the case if the two anchors are within 'proximityLimit', since + // it seems to make good aesthetic sense to do that. outside of that, the control point is positioned + // at 'curviness' pixels away along the normal to the straight line connecting the two anchors. + // + // there may be two improvements to this. firstly, we might actually support the notion of avoiding nodes + // in the UI, or at least making a good effort at doing so. if a connection would pass underneath some node, + // for example, we might increase the distance the control point is away from the midpoint in a bid to + // steer it around that node. this will work within limits, but i think those limits would also be the likely + // limits for, once again, aesthetic good sense in the layout of a chart using these connectors. + // + // the second possible change is actually two possible changes: firstly, it is possible we should gradually + // decrease the 'curviness' as the distance between the anchors decreases; start tailing it off to 0 at some + // point (which should be configurable). secondly, we might slightly increase the 'curviness' for connectors + // with respect to how far their anchor is from the center of its respective face. this could either look cool, + // or stupid, and may indeed work only in a way that is so subtle as to have been a waste of time. + // + + var _midx = (_sx + _tx) / 2, _midy = (_sy + _ty) / 2, + m2 = (-1 * _midx) / _midy, theta2 = Math.atan(m2), + dy = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.sin(theta2)), + dx = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.cos(theta2)), + segment = _segment(_sx, _sy, _tx, _ty), + distance = Math.sqrt(Math.pow(_tx - _sx, 2) + Math.pow(_ty - _sy, 2)); + + // calculate the control point. this code will be where we'll put in a rudimentary element avoidance scheme; it + // will work by extending the control point to force the curve to be, um, curvier. + _controlPoint = _findControlPoint(_midx, + _midy, + segment, + sourcePos, + targetPos, + curviness, curviness, + distance, + proximityLimit); + + + var requiredWidth = Math.max(Math.abs(_controlPoint[0] - _sx) * 3, Math.abs(_controlPoint[0] - _tx) * 3, Math.abs(_tx-_sx), 2 * lineWidth, minWidth), + requiredHeight = Math.max(Math.abs(_controlPoint[1] - _sy) * 3, Math.abs(_controlPoint[1] - _ty) * 3, Math.abs(_ty-_sy), 2 * lineWidth, minWidth); + + if (w < requiredWidth) { + var dw = requiredWidth - w; + x -= (dw / 2); + _sx += (dw / 2); + _tx += (dw / 2); + w = requiredWidth; + _controlPoint[0] += (dw / 2); + } + + if (h < requiredHeight) { + var dh = requiredHeight - h; + y -= (dh / 2); + _sy += (dh / 2); + _ty += (dh / 2); + h = requiredHeight; + _controlPoint[1] += (dh / 2); + } + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty, _controlPoint[0], _controlPoint[1] ]; + } + else { + isLoopback = true; + // a loopback connector. draw an arc from one anchor to the other. + // i guess we'll do this the same way as the others. just the control point will be a fair distance away. + var x1 = sourcePos[0], x2 = sourcePos[0], y1 = sourcePos[1] - margin, y2 = sourcePos[1] - margin, + cx = x1, cy = y1 - loopbackRadius; + + // canvas sizing stuff, to ensure the whole painted area is visible. + w = ((2 * lineWidth) + (4 * loopbackRadius)), h = ((2 * lineWidth) + (4 * loopbackRadius)); + x = cx - loopbackRadius - lineWidth - loopbackRadius, y = cy - loopbackRadius - lineWidth - loopbackRadius; + currentPoints = [ x, y, w, h, cx-x, cy-y, loopbackRadius, clockwise, x1-x, y1-y, x2-x, y2-y]; + } + + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_tx, y:_ty }, + { x:_controlPoint[0], y:_controlPoint[1] }, + { x:_controlPoint[0] + 1, y:_controlPoint[1] + 1}, + { x:_sx, y:_sy } + ]; + }; + + var _translateLocation = function(curve, location, absolute) { + if (absolute) + location = jsBezier.locationAlongCurveFrom(curve, location > 0 ? 0 : 1, location); + + return location; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location, absolute) { + if (isLoopback) { + if (absolute) { + var circumference = Math.PI * 2 * loopbackRadius; + location = location / circumference; + } + + if (location > 0 && location < 1) location = 1 - location; + +// current points are [ x, y, width, height, center x, center y, radius, clockwise, startx, starty, endx, endy ] + // so the path length is the circumference of the circle + //var len = 2 * Math.PI * currentPoints[6], + // map 'location' to an angle. 0 is PI/2 when the connector is on the top face; if we + // support other faces it will have to be calculated for each one. 1 is also PI/2. + // 0.5 is -PI/2. + var startAngle = (location * 2 * Math.PI) + (Math.PI / 2), + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + + } + else { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.pointOnCurve(c, location); + } + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location, absolute) { + if (isLoopback) { + // todo if absolute, location is a proportion of circumference + if (absolute) { + var circumference = Math.PI * 2 * loopbackRadius; + location = location / circumference; + } + + return Math.atan(location * 2 * Math.PI); + } + else { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.gradientAtPoint(c, location); + } + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance, absolute) { + if (isLoopback) { + + if (absolute) { + var circumference = Math.PI * 2 * loopbackRadius; + location = location / circumference; + } + + if (location > 0 && location < 1) location = 1 - location; + + var circumference = 2 * Math.PI * currentPoints[6], + arcSpan = distance / circumference * 2 * Math.PI, + startAngle = (location * 2 * Math.PI) - arcSpan + (Math.PI / 2), + + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + } + else { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.pointAlongCurveFrom(c, location, distance); + } + }; + + }; + + /* + * Canvas state machine renderer. + */ + jsPlumb.Connectors.canvas.StateMachine = function(params) { + params = params || {}; + var self = this, drawGuideline = params.drawGuideline || true, avoidSelector = params.avoidSelector; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.CanvasConnector.apply(this, arguments); + + + this._paint = function(dimensions) { + + if (dimensions.length == 10) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[8], dimensions[9], dimensions[6], dimensions[7]); + self.ctx.stroke(); + } + else { + // a loopback connector + self.ctx.save(); + self.ctx.beginPath(); + var startAngle = 0, // Starting point on circle + endAngle = 2 * Math.PI, // End point on circle + clockwise = dimensions[7]; // clockwise or anticlockwise + self.ctx.arc(dimensions[4],dimensions[5],dimensions[6],0, endAngle, clockwise); + self.ctx.stroke(); + self.ctx.closePath(); + self.ctx.restore(); + } + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + /* + * SVG State Machine renderer + */ + jsPlumb.Connectors.svg.StateMachine = function() { + var self = this; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.SvgConnector.apply(this, arguments); + this.getPath = function(d) { + + if (d.length == 10) + return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[8] + " " + d[9] + " " + d[6] + " " + d[7]; + else { + // loopback + return "M" + (d[8] + 4) + " " + d[9] + " A " + d[6] + " " + d[6] + " 0 1,0 " + (d[8]-4) + " " + d[9]; + } + }; + }; + + /* + * VML state machine renderer + */ + jsPlumb.Connectors.vml.StateMachine = function() { + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.VmlConnector.apply(this, arguments); + var _conv = jsPlumb.vml.convertValue; + this.getPath = function(d) { + if (d.length == 10) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + } + else { + // loopback + var left = _conv(d[8] - d[6]), + top = _conv(d[9] - (2 * d[6])), + right = left + _conv(2 * d[6]), + bottom = top + _conv(2 * d[6]), + posString = left + "," + top + "," + right + "," + bottom; + + var o = "ar " + posString + "," + _conv(d[8]) + "," + + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + " e"; + + return o; + } + }; + }; + +})(); + +/* + // now for a rudimentary avoidance scheme. TODO: how to set this in a cross-library way? + // if (avoidSelector) { + // var testLine = new Line(sourcePos[0] + _sx,sourcePos[1] + _sy,sourcePos[0] + _tx,sourcePos[1] + _ty); + // var sel = jsPlumb.getSelector(avoidSelector); + // for (var i = 0; i < sel.length; i++) { + // var id = jsPlumb.getId(sel[i]); + // if (id != sourceEndpoint.elementId && id != targetEndpoint.elementId) { + // o = jsPlumb.getOffset(id), s = jsPlumb.getSize(id); +// +// if (o && s) { +// var collision = testLine.rectIntersect(o.left,o.top,s[0],s[1]); +// if (collision) { + // set the control point to be a certain distance from the midpoint of the two points that + // the line crosses on the rectangle. + // TODO where will this 75 number come from? + // _controlX = collision[2][0] + (75 * collision[3][0]); + // / _controlY = collision[2][1] + (75 * collision[3][1]); +// } +// } + // } + // } + //} + */ \ No newline at end of file diff --git a/archive/1.3.12/jsPlumb-defaults-1.3.12-RC1.js b/archive/1.3.12/jsPlumb-defaults-1.3.12-RC1.js new file mode 100644 index 000000000..03b8a815f --- /dev/null +++ b/archive/1.3.12/jsPlumb-defaults-1.3.12-RC1.js @@ -0,0 +1,1281 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.12 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the default Connectors, Endpoint and Overlay definitions. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +(function() { + + /** + * + * Helper class to consume unused mouse events by components that are DOM elements and + * are used by all of the different rendering modes. + * + */ + jsPlumb.DOMElementComponent = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + // when render mode is canvas, these functions may be called by the canvas mouse handler. + // this component is safe to pipe this stuff to /dev/null. + this.mousemove = + this.dblclick = + this.click = + this.mousedown = + this.mouseup = function(e) { }; + }; + + /** + * Class: Connectors.Straight + * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters. + */ + jsPlumb.Connectors.Straight = function() { + this.type = "Straight"; + var self = this, + currentPoints = null, + _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty, _segment, _length; + + /** + * Computes the new size and position of the canvas. + */ + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + _dx = _tx - _sx, _dy = _ty - _sy; + //_m = _dy / _dx, _m2 = -1 / _m; + _m = jsPlumbUtil.gradient({x:_sx, y:_sy}, {x:_tx, y:_ty}), _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + //_segment = jsPlumbUtil.segment({x:_sx, y:_sy}, {x:_tx, y:_ty}); + _length = Math.sqrt((_dx * _dx) + (_dy * _dy)); + + return currentPoints; + }; + + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location, absolute) { + if (location == 0 && !absolute) + return { x:_sx, y:_sy }; + else if (location == 1 && !absolute) + return { x:_tx, y:_ty }; + else { + var l = absolute ? location > 0 ? location : _length + location : location * _length; + return jsPlumbUtil.pointOnLine({x:_sx, y:_sy}, {x:_tx, y:_ty}, l); + } + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { + return _m; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + * this hands off to jsPlumbUtil to do the maths, supplying two points and the distance. + */ + this.pointAlongPathFrom = function(location, distance, absolute) { + var p = self.pointOnPath(location, absolute), + farAwayPoint = location == 1 ? { + x:_sx + ((_tx - _sx) * 10), + y:_sy + ((_sy - _ty) * 10) + } : {x:_tx, y:_ty }; + + return jsPlumbUtil.pointOnLine(p, farAwayPoint, distance); + }; + }; + + + /** + * Class:Connectors.Bezier + * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's + * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below. + */ + /** + * Function:Constructor + * + * Parameters: + * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line. + * Optional; defaults to 150. + * stub - optional value for a distance to travel from the connector's endpoint before beginning the Bezier curve. defaults to 0. + * + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + params = params || {}; + this.majorAnchor = params.curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + this.type = "Bezier"; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(sourceEndpoint), + too = targetAnchor.getOrientation(targetEndpoint), + perpendicular = soo[0] != too[0] || soo[1] == too[1], + p = [], + ma = self.majorAnchor, mi = self.minorAnchor; + + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h, _sStubX, _sStubY, _tStubX, _tStubY; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetEndpoint, sourceEndpoint, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx), minx2 = Math.min(_CP[0], _CP2[0]), minx = Math.min(minx1,minx2), + maxx1 = Math.max(_sx,_tx), maxx2 = Math.max(_CP[0], _CP2[0]), maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty), miny2 = Math.min(_CP[1], _CP2[1]), miny = Math.min(miny1,miny2), + maxy1 = Math.max(_sy,_ty), maxy2 = Math.max(_CP[1], _CP2[1]), maxy = Math.max(maxy1,maxy2); + + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, + _sx, _sy, _tx, _ty, + _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + var _translateLocation = function(curve, location, absolute) { + if (absolute) + location = jsBezier.locationAlongCurveFrom(curve, location > 0 ? 0 : 1, location); + + return location; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location, absolute) { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.pointOnCurve(c, location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location, absolute) { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.gradientAtPoint(c, location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance, absolute) { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.pointAlongCurveFrom(c, location, distance); + }; + }; + + + /** + * Class: Connectors.Flowchart + * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments. + */ + /** + * Function: Constructor + * + * Parameters: + * stub - minimum length for the stub at each end of the connector. This can be an integer, giving a value for both ends of the connections, + * or an array of two integers, giving separate values for each end. The default is an integer with value 30 (pixels). + * gap - gap to leave between the end of the connector and the element on which the endpoint resides. if you make this larger than stub then you will see some odd looking behaviour. defaults to 0 pixels. + */ + jsPlumb.Connectors.Flowchart = function(params) { + this.type = "Flowchart"; + params = params || {}; + var self = this, + stub = params.stub || params.minStubLength /* bwds compat. */ || 30, + sourceStub = jsPlumbUtil.isArray(stub) ? stub[0] : stub, + targetStub = jsPlumbUtil.isArray(stub) ? stub[1] : stub, + gap = params.gap || 0, + segments = [], + totalLength = 0, + segmentProportions = [], + segmentProportionalLengths = [], + points = [], + swapX, swapY, + maxX = -Infinity, maxY = -Infinity, + minX = Infinity, minY = Infinity, + grid = params.grid, + _gridClamp = function(n, g) { var e = n % g, f = Math.floor(n / g), inc = e > (g / 2) ? 1 : 0; return (f + inc) * g; }, + clampToGrid = function(x, y, dontClampX, dontClampY) { + return [ + dontClampX || grid == null ? x : _gridClamp(x, grid[0]), + dontClampY || grid == null ? y : _gridClamp(y, grid[1]) + ]; + }, + /** + * recalculates the points at which the segments begin and end, proportional to the total length travelled + * by all the segments that constitute the connector. we use this to help with pointOnPath calculations. + */ + updateSegmentProportions = function(startX, startY, endX, endY) { + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segments[i][5] / totalLength; + segmentProportions[i] = [curLoc, (curLoc += (segments[i][5] / totalLength)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty/*, doGridX, doGridY*/) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0], + ly = segments.length == 0 ? sy : segments[segments.length - 1][1], + m = x == lx ? Infinity : 0, + l = Math.abs(x == lx ? y - ly : x - lx);/*, + gridded = clampToGrid(x, y), + doGridX = true, + doGridY = true; + + // grid experiment. TODO: have two more params that indicate whether or not to lock to a grid in each + // axis. the reason for this is that anchor points wont always be located on the grid, so until a connector + // emanating from that anchor has turned a right angle, we can't actually clamp it to a grid for that axis. + // so if a line came out horizontally heading left, then it will probably not be clamped in the y axis, but + // we can choose to clamp its first corner in the x axis. the same principle goes for the target anchor. + //if (segments.length == 0) { + console.log("this is the first segment...if sx == x then do not do grid in X.") + doGridX = !(sx == x) && !(tx == x); + doGridY = !(sy == y) && !(ty == y); + x = doGridX ? gridded[0] : x; + y = doGridY ? gridded[1] : y; + */ + + segments.push([x, y, lx, ly, m, l]); + totalLength += l; + + maxX = Math.max(maxX, x); + maxY = Math.max(maxY, y); + minX = Math.min(minX, x); + minY = Math.min(minY, y); + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment + * that contains the point which is 'location' distance along the entire path, where + * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths + * are made up of a list of segments, each of which contributes some fraction to + * the total length. + * From 1.3.10 this also supports the 'absolute' property, which lets us specify a location + * as the absolute distance in pixels, rather than a proportion of the total path. + */ + findSegmentForLocation = function(location, absolute) { + if (absolute) { + location = location > 0 ? location / totalLength : (totalLength + location) / totalLength; + } + + var idx = segmentProportions.length - 1, inSegmentProportion = 1; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, + sourceAnchor, targetAnchor, lineWidth, minWidth, sourceInfo, targetInfo) { + + segments = []; + segmentProportions = []; + totalLength = 0; + segmentProportionalLengths = []; + maxX = maxY = -Infinity; + minX = minY = Infinity; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + sourceOffx = (lw / 2) + (sourceStub + targetStub), + targetOffx = (lw / 2) + (targetStub + sourceStub), + sourceOffy = (lw / 2) + (sourceStub + targetStub), + targetOffy = (lw / 2) + (targetStub + sourceStub), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(sourceEndpoint), + to = targetAnchor.orientation || targetAnchor.getOrientation(targetEndpoint), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + sourceOffx + targetOffx, + h = Math.abs(targetPos[1] - sourcePos[1]) + sourceOffy + targetOffy; + + // if either anchor does not have an orientation set, we derive one from their relative + // positions. we fix the axis to be the one in which the two elements are further apart, and + // point each anchor at the other element. this is also used when dragging a new connection. + if (so[0] == 0 && so[1] == 0 || to[0] == 0 && to[1] == 0) { + var index = w > h ? 0 : 1, oIndex = [1,0][index]; + so = []; to = []; + so[index] = sourcePos[index] > targetPos[index] ? -1 : 1; + to[index] = sourcePos[index] > targetPos[index] ? 1 : -1; + so[oIndex] = 0; + to[oIndex] = 0; + } + + /* + this code is unexplained and causes paint errors with continuous anchors sometimes. + commenting it out until i can get to the bottom of it. + if (w < minWidth) { + sourceOffx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + sourceOffy += (minWidth - h) / 2; + h = minWidth; + } + */ + + var sx = swapX ? (w - targetOffx) +( gap * so[0]) : sourceOffx + (gap * so[0]), + sy = swapY ? (h - targetOffy) + (gap * so[1]) : sourceOffy + (gap * so[1]), + tx = swapX ? sourceOffx + (gap * to[0]) : (w - targetOffx) + (gap * to[0]), + ty = swapY ? sourceOffy + (gap * to[1]) : (h - targetOffy) + (gap * to[1]), + startStubX = sx + (so[0] * sourceStub), + startStubY = sy + (so[1] * sourceStub), + endStubX = tx + (to[0] * targetStub), + endStubY = ty + (to[1] * targetStub), + isXGreaterThanStubTimes2 = Math.abs(sx - tx) > (sourceStub + targetStub), + isYGreaterThanStubTimes2 = Math.abs(sy - ty) > (sourceStub + targetStub), + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2), + oProduct = ((so[0] * to[0]) + (so[1] * to[1])), + opposite = oProduct == -1, + perpendicular = oProduct == 0, + orthogonal = oProduct == 1; + + x -= sourceOffx; y -= sourceOffy; + points = [x, y, w, h, sx, sy, tx, ty]; + var extraPoints = []; + + var sourceAxis = so[0] == 0 ? "y" : "x", + anchorOrientation = opposite ? "opposite" : orthogonal ? "orthogonal" : "perpendicular", + segment = jsPlumbUtil.segment([sx, sy], [tx, ty]), + flipSourceSegments = so[sourceAxis == "x" ? 0 : 1] == -1, + flipSegments = { + "x":[null, 4, 3, 2, 1], + "y":[null, 2, 1, 4, 3] + } + + if (flipSourceSegments) + segment = flipSegments[sourceAxis][segment]; + + /*if (segment == 1 || segment == 2) { + if (sourceAxis == "x") + addSegment(Math.max(startStubX, endStubX), startStubY, sx, sy, tx, ty); + else + addSegment(startStubX, Math.max(startStubY, endStubY), sx, sy, tx, ty); + } + else { + if (sourceAxis == "x") + addSegment(Math.min(startStubX, endStubX), startStubY, sx, sy, tx, ty); + else + addSegment(startStubX, Math.min(startStubY, endStubY), sx, sy, tx, ty); + }*/ + //addSegment(startStubX, startStubY, sx, sy, tx, ty); + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + var findClearedLine = function(start, mult, anchorPos, dimension) { + return start + (mult * (( 1 - anchorPos) * dimension) + Math.max(sourceStub, targetStub)); + //mx = so[0] == 0 ? startStubX + ((1 - sourceAnchor.x) * sourceInfo.width) + stub : startStubX, + }, + + lineCalculators = { + oppositex : function() { + if (sourceEndpoint.elementId == targetEndpoint.elementId) { + var _y = startStubY + ((1 - sourceAnchor.y) * sourceInfo.height) + Math.max(sourceStub, targetStub); + return [ [ startStubX, _y ], [ endStubX, _y ]]; + } + else if (isXGreaterThanStubTimes2 && (segment == 1 || segment == 2)) { + return [[ midx, sy ], [ midx, ty ]]; + } + else { + return [[ startStubX, midy ], [endStubX, midy ]]; + } + }, + orthogonalx : function() { + if (segment == 1 || segment == 2) { + return [ [ endStubX, startStubY ]]; + } + else { + return [ [ startStubX, endStubY ]]; + } + }, + perpendicularx : function() { + var my = (ty + sy) / 2; + if ((segment == 1 && to[1] == 1) || (segment == 2 && to[1] == -1)) { + if (Math.abs(tx - sx) > Math.max(sourceStub, targetStub)) + return [ [endStubX, startStubY ]]; + else + return [ [startStubX, startStubY ], [ startStubX, my ], [ endStubX, my ]]; + } + else if ((segment == 3 && to[1] == -1) || (segment == 4 && to[1] == 1)) { + return [ [ startStubX, my ], [ endStubX, my ]]; + } + else if ((segment == 3 && to[1] == 1) || (segment == 4 && to[1] == -1)) { + return [ [ startStubX, endStubY ]]; + } + else if ((segment == 1 && to[1] == -1) || (segment == 2 && to[1] == 1)) { + if (Math.abs(tx - sx) > Math.max(sourceStub, targetStub)) + return [ [ midx, startStubY ], [ midx, endStubY ]]; + else + return [ [ startStubX, endStubY ]]; + } + }, + oppositey : function() { + if (sourceEndpoint.elementId == targetEndpoint.elementId) { + var _x = startStubX + ((1 - sourceAnchor.x) * sourceInfo.width) + Math.max(sourceStub, targetStub); + return [ [ _x, startStubY ], [ _x, endStubY ]]; + } + else if (isYGreaterThanStubTimes2 && (segment == 2 || segment == 3)) { + return [[ sx, midy ], [ tx, midy ]]; + } + else { + return [[ midx, startStubY ], [midx, endStubY ]]; + } + }, + orthogonaly : function() { + if (segment == 2 || segment == 3) { + return [ [ startStubX, endStubY ]]; + } + else { + return [ [ endStubX, startStubY ]]; + } + }, + perpendiculary : function() { + var mx = (tx + sx) / 2; + if ((segment == 2 && to[0] == -1) || (segment == 3 && to[0] == 1)) { + if (Math.abs(tx - sx) > Math.max(sourceStub, targetStub)) + return [ [startStubX, endStubY ]]; + else + return [ [startStubX, midy ], [ endStubX, midy ]]; + } + else if ((segment == 1 && to[0] == -1) || (segment == 4 && to[0] == 1)) { + var mx = (tx + sx) / 2; + return [ [ mx, startStubY ], [ mx, endStubY ]]; + } + else if ((segment == 1 && to[0] == 1) || (segment == 4 && to[0] == -1)) { + return [ [ endStubX, startStubY ]]; + } + else if ((segment == 2 && to[0] == 1) || (segment == 3 && to[0] == -1)) { + if (Math.abs(ty - sy) > Math.max(sourceStub, targetStub)) + return [ [ startStubX, midy ], [ endStubX, midy ]]; + else + return [ [ endStubX, startStubY ]]; + } + } + }; + + var p = lineCalculators[anchorOrientation + sourceAxis](); + if (p) { + for (var i = 0; i < p.length; i++) { + addSegment(p[i][0], p[i][1], sx, sy, tx, ty); + } + } + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentProportions(sx, sy, tx, ty); + + // adjust the max values of the canvas if we have a value that is larger than what we previously set. + // + if (maxY > points[3]) points[3] = maxY + (lineWidth * 2); + if (maxX > points[2]) points[2] = maxX + (lineWidth * 2); + + return points; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location, absolute) { + return self.pointAlongPathFrom(location, 0, absolute); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location, absolute) { + return segments[findSegmentForLocation(location, absolute)["index"]][4]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance, absolute) { + var s = findSegmentForLocation(location, absolute), seg = s.segment, p = s.proportion, sl = segments[s.index][5], m = segments[s.index][4]; + var e = { + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + return e; + }; + }; + + // ********************************* END OF CONNECTOR TYPES ******************************************************************* + + // ********************************* ENDPOINT TYPES ******************************************************************* + + /** + * Class: Endpoints.Dot + * A round endpoint, with default radius 10 pixels. + */ + + /** + * Function: Constructor + * + * Parameters: + * + * radius - radius of the endpoint. defaults to 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + this.type = "Dot"; + var self = this; + params = params || {}; + this.radius = params.radius || 10; + this.defaultOffset = 0.5 * this.radius; + this.defaultInnerRadius = this.radius / 3; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var r = endpointStyle.radius || self.radius, + x = anchorPoint[0] - r, + y = anchorPoint[1] - r; + return [ x, y, r * 2, r * 2, r ]; + }; + }; + + /** + * Class: Endpoints.Rectangle + * A Rectangular Endpoint, with default size 20x20. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the endpoint. defaults to 20 pixels. + * height - height of the endpoint. defaults to 20 pixels. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + this.type = "Rectangle"; + var self = this; + params = params || {}; + this.width = params.width || 20; + this.height = params.height || 20; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height]; + }; + }; + + + var DOMElementEndpoint = function(params) { + jsPlumb.DOMElementComponent.apply(this, arguments); + var self = this; + + var displayElements = [ ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + }; + /** + * Class: Endpoints.Image + * Draws an image as the Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * src - location of the image to use. + */ + jsPlumb.Endpoints.Image = function(params) { + + this.type = "Image"; + DOMElementEndpoint.apply(this, arguments); + + var self = this, + initialized = false, + deleted = false, + widthToUse = params.width, + heightToUse = params.height, + _onload = null, + _endpoint = params.endpoint; + + this.img = new Image(); + self.ready = false; + + this.img.onload = function() { + self.ready = true; + widthToUse = widthToUse || self.img.width; + heightToUse = heightToUse || self.img.height; + if (_onload) { + _onload(self); + } + }; + + /* + Function: setImage + Sets the Image to use in this Endpoint. + + Parameters: + img - may be a URL or an Image object + onload - optional; a callback to execute once the image has loaded. + */ + _endpoint.setImage = function(img, onload) { + var s = img.constructor == String ? img : img.src; + _onload = onload; + self.img.src = img; + + if (self.canvas != null) + self.canvas.setAttribute("src", img); + }; + + _endpoint.setImage(params.src || params.url, params.onload); + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + self.anchorPoint = anchorPoint; + if (self.ready) return [anchorPoint[0] - widthToUse / 2, anchorPoint[1] - heightToUse / 2, + widthToUse, heightToUse]; + else return [0,0,0,0]; + }; + + self.canvas = document.createElement("img"), initialized = false; + self.canvas.style["margin"] = 0; + self.canvas.style["padding"] = 0; + self.canvas.style["outline"] = 0; + self.canvas.style["position"] = "absolute"; + var clazz = params.cssClass ? " " + params.cssClass : ""; + self.canvas.className = jsPlumb.endpointClass + clazz; + if (widthToUse) self.canvas.setAttribute("width", widthToUse); + if (heightToUse) self.canvas.setAttribute("height", heightToUse); + jsPlumb.appendElement(self.canvas, params.parent); + self.attachListeners(self.canvas, self); + + self.cleanup = function() { + deleted = true; + }; + + var actuallyPaint = function(d, style, anchor) { + if (!deleted) { + if (!initialized) { + self.canvas.setAttribute("src", self.img.src); + self.appendDisplayElement(self.canvas); + initialized = true; + } + var x = self.anchorPoint[0] - (widthToUse / 2), + y = self.anchorPoint[1] - (heightToUse / 2); + jsPlumb.sizeCanvas(self.canvas, x, y, widthToUse, heightToUse); + } + }; + + this.paint = function(d, style, anchor) { + if (self.ready) { + actuallyPaint(d, style, anchor); + } + else { + window.setTimeout(function() { + self.paint(d, style, anchor); + }, 200); + } + }; + }; + + /** + * Class: Endpoints.Blank + * An Endpoint that paints nothing (visible) on the screen. Supports cssClass and hoverClass parameters like all Endpoints. + */ + jsPlumb.Endpoints.Blank = function(params) { + var self = this; + this.type = "Blank"; + DOMElementEndpoint.apply(this, arguments); + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + return [anchorPoint[0], anchorPoint[1],10,0]; + }; + + self.canvas = document.createElement("div"); + self.canvas.style.display = "block"; + self.canvas.style.width = "1px"; + self.canvas.style.height = "1px"; + self.canvas.style.background = "transparent"; + self.canvas.style.position = "absolute"; + self.canvas.className = self._jsPlumb.endpointClass; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + }; + }; + + /** + * Class: Endpoints.Triangle + * A triangular Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the triangle's base. defaults to 55 pixels. + * height - height of the triangle from base to apex. defaults to 55 pixels. + */ + jsPlumb.Endpoints.Triangle = function(params) { + this.type = "Triangle"; + params = params || { }; + params.width = params.width || 55; + params.height = params.height || 55; + this.width = params.width; + this.height = params.height; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height ]; + }; + }; +// ********************************* END OF ENDPOINT TYPES ******************************************************************* + + +// ********************************* OVERLAY DEFINITIONS *********************************************************************** + + var AbstractOverlay = function(params) { + var visible = true, self = this; + this.isAppendedAtTopLevel = true; + this.component = params.component; + this.loc = params.location == null ? 0.5 : params.location; + this.endpointLoc = params.endpointLocation == null ? [ 0.5, 0.5] : params.endpointLocation; + this.setVisible = function(val) { + visible = val; + self.component.repaint(); + }; + this.isVisible = function() { return visible; }; + this.hide = function() { self.setVisible(false); }; + this.show = function() { self.setVisible(true); }; + + this.incrementLocation = function(amount) { + self.loc += amount; + self.component.repaint(); + }; + this.setLocation = function(l) { + self.loc = l; + self.component.repaint(); + }; + this.getLocation = function() { + return self.loc; + }; + }; + + + /** + * Class: Overlays.Arrow + * + * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length + * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction + * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line + * across the tail. + */ + /** + * Function: Constructor + * + * Parameters: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + this.type = "Arrow"; + AbstractOverlay.apply(this, arguments); + this.isAppendedAtTopLevel = false; + params = params || {}; + var self = this; + + this.length = params.length || 20; + this.width = params.width || 20; + this.id = params.id; + var direction = (params.direction || 1) < 0 ? -1 : 1, + paintStyle = params.paintStyle || { lineWidth:1 }, + // how far along the arrow the lines folding back in come to. default is 62.3%. + foldback = params.foldback || 0.623; + + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.cleanup = function() { }; // nothing to clean up for Arrows + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var hxy, mid, txy, tail, cxy; + if (connector.pointAlongPathFrom) { + + if (jsPlumbUtil.isString(self.loc) || self.loc > 1 || self.loc < 0) { + var l = parseInt(self.loc); + hxy = connector.pointAlongPathFrom(l, direction * self.length / 2, true), + mid = connector.pointOnPath(l, true), + txy = jsPlumbUtil.pointOnLine(hxy, mid, self.length); + } + else if (self.loc == 1) { + hxy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, -1); + txy = jsPlumbUtil.pointOnLine(hxy, mid, self.length); + } + else if (self.loc == 0) { + txy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, 1); + hxy = jsPlumbUtil.pointOnLine(txy, mid, self.length); + } + else { + hxy = connector.pointAlongPathFrom(self.loc, direction * self.length / 2), + mid = connector.pointOnPath(self.loc), + txy = jsPlumbUtil.pointOnLine(hxy, mid, self.length); + } + + tail = jsPlumbUtil.perpendicularLineTo(hxy, txy, self.width); + cxy = jsPlumbUtil.pointOnLine(hxy, txy, foldback * self.length); + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x), + maxx = Math.max(hxy.x, tail[0].x, tail[1].x), + miny = Math.min(hxy.y, tail[0].y, tail[1].y), + maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + var d = { hxy:hxy, tail:tail, cxy:cxy }, + strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle, + fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle, + lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth; + + self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions); + + return [ minx, maxx, miny, maxy]; + } + else return [0,0,0,0]; + }; + }; + + /** + * Class: Overlays.PlainArrow + * + * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "PlainArrow"; + }; + + /** + * Class: Overlays.Diamond + * + * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40, + p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "Diamond"; + }; + + + // abstract superclass for overlays that add an element to the DOM. + var AbstractDOMOverlay = function(params) { + jsPlumb.DOMElementComponent.apply(this, arguments); + AbstractOverlay.apply(this, arguments); + + var self = this, initialised = false; + params = params || {}; + this.id = params.id; + var div; + + var makeDiv = function() { + div = params.create(params.component); + div = jsPlumb.CurrentLibrary.getDOMElement(div); + div.style["position"] = "absolute"; + var clazz = params["_jsPlumb"].overlayClass + " " + + (self.cssClass ? self.cssClass : + params.cssClass ? params.cssClass : ""); + div.className = clazz; + jsPlumb.appendElement(div, params.component.parent); + params["_jsPlumb"].getId(div); + self.attachListeners(div, self); + self.canvas = div; + }; + + this.getElement = function() { + if (div == null) { + makeDiv(); + } + return div; + }; + + this.getDimensions = function() { + return jsPlumb.CurrentLibrary.getSize(jsPlumb.CurrentLibrary.getElementObject(self.getElement())); + }; + + var cachedDimensions = null, + _getDimensions = function(component) { + if (cachedDimensions == null) + cachedDimensions = self.getDimensions(); + return cachedDimensions; + }; + + /* + * Function: clearCachedDimensions + * Clears the cached dimensions for the label. As a performance enhancement, label dimensions are + * cached from 1.3.12 onwards. The cache is cleared when you change the label text, of course, but + * there are other reasons why the text dimensions might change - if you make a change through CSS, for + * example, you might change the font size. in that case you should explicitly call this method. + */ + this.clearCachedDimensions = function() { + cachedDimensions = null; + }; + + this.computeMaxSize = function() { + var td = _getDimensions(); + return Math.max(td[0], td[1]); + }; + + //override setVisible + var osv = self.setVisible; + self.setVisible = function(state) { + osv(state); // call superclass + div.style.display = state ? "block" : "none"; + }; + + this.cleanup = function() { + if (div != null) jsPlumb.CurrentLibrary.removeElement(div); + }; + + this.paint = function(component, d, componentDimensions) { + if (!initialised) { + self.getElement(); + component.appendDisplayElement(div); + self.attachListeners(div, component); + initialised = true; + } + div.style.left = (componentDimensions[0] + d.minx) + "px"; + div.style.top = (componentDimensions[1] + d.miny) + "px"; + }; + + this.draw = function(component, currentConnectionPaintStyle, componentDimensions) { + var td = _getDimensions(); + if (td != null && td.length == 2) { + var cxy = {x:0,y:0}; + if (component.pointOnPath) { + var loc = self.loc, absolute = false; + if (jsPlumbUtil.isString(self.loc) || self.loc < 0 || self.loc > 1) { + loc = parseInt(self.loc); + absolute = true; + } + cxy = component.pointOnPath(loc, absolute); // a connection + } + else { + var locToUse = self.loc.constructor == Array ? self.loc : self.endpointLoc; + cxy = { x:locToUse[0] * componentDimensions[2], + y:locToUse[1] * componentDimensions[3] }; + } + + minx = cxy.x - (td[0] / 2), + miny = cxy.y - (td[1] / 2); + self.paint(component, { minx:minx, miny:miny, td:td, cxy:cxy }, componentDimensions); + return [minx, minx + td[0], miny, miny + td[1]]; + } + else return [0,0,0,0]; + }; + + this.reattachListeners = function(connector) { + if (div) { + self.reattachListenersForElement(div, self, connector); + } + }; + + }; + + /** + * Class: Overlays.Custom + * A Custom overlay. You supply a 'create' function which returns some DOM element, and jsPlumb positions it. + * The 'create' function is passed a Connection or Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * create - function for jsPlumb to call that returns a DOM element. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * id - optional id to use for later retrieval of this overlay. + * + */ + jsPlumb.Overlays.Custom = function(params) { + this.type = "Custom"; + AbstractDOMOverlay.apply(this, arguments); + }; + + /** + * Class: Overlays.Label + * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb + * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter + * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it + * puts on the Label's 'style' attribute, so the end result is the same. + */ + /** + * Function: Constructor + * + * Parameters: + * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes + * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle. + * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * id - optional id to use for later retrieval of this overlay. + * + */ + jsPlumb.Overlays.Label = function(params) { + var self = this; + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.cssClass = this.labelStyle != null ? this.labelStyle.cssClass : null; + params.create = function() { + return document.createElement("div"); + }; + jsPlumb.Overlays.Custom.apply(this, arguments); + this.type = "Label"; + + var label = params.label || "", + self = this, + labelText = null; + + /* + * Function: setLabel + * sets the label's, um, label. you would think i'd call this function + * 'setText', but you can pass either a Function or a String to this, so + * it makes more sense as 'setLabel'. This uses innerHTML on the label div, so keep + * that in mind if you need escaped HTML. + */ + this.setLabel = function(l) { + label = l; + labelText = null; + cachedDimensions = null; + _update(); + self.component.repaint(); + }; + + var _update = function() { + if (typeof label == "function") { + var lt = label(self); + self.getElement().innerHTML = lt.replace(/\r\n/g, "
"); + } + else { + if (labelText == null) { + labelText = label; + self.getElement().innerHTML = labelText.replace(/\r\n/g, "
"); + } + } + }; + + this.getLabel = function() { + return label; + }; + + var superGD = this.getDimensions; + this.getDimensions = function() { + _update(); + return superGD(); + }; + + }; + + // this is really just a test overlay, so its undocumented and doesnt take any parameters. but i was loth to delete it. + jsPlumb.Overlays.GuideLines = function() { + var self = this; + self.length = 50; + self.lineWidth = 5; + this.type = "GuideLines"; + AbstractOverlay.apply(this, arguments); + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var head = connector.pointAlongPathFrom(self.loc, self.length / 2), + mid = connector.pointOnPath(self.loc), + tail = jsPlumbUtil.pointOnLine(head, mid, self.length), + tailLine = jsPlumbUtil.perpendicularLineTo(head, tail, 40), + headLine = jsPlumbUtil.perpendicularLineTo(tail, head, 20); + + self.paint(connector, [head, tail, tailLine, headLine], self.lineWidth, "red", null, connectorDimensions); + + return [Math.min(head.x, tail.x), Math.min(head.y, tail.y), Math.max(head.x, tail.x), Math.max(head.y,tail.y)]; + }; + + this.computeMaxSize = function() { return 50; }; + + this.cleanup = function() { }; // nothing to clean up for GuideLines + }; + + // ********************************* END OF OVERLAY DEFINITIONS *********************************************************************** + +})(); \ No newline at end of file diff --git a/archive/1.3.12/jsPlumb-dom-adapter-1.3.12-RC1.js b/archive/1.3.12/jsPlumb-dom-adapter-1.3.12-RC1.js new file mode 100644 index 000000000..4bb4dbee0 --- /dev/null +++ b/archive/1.3.12/jsPlumb-dom-adapter-1.3.12-RC1.js @@ -0,0 +1,194 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.12 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the base functionality for DOM type adapters. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ +;(function() { + + var canvasAvailable = !!document.createElement('canvas').getContext, + svgAvailable = !!window.SVGAngle || document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"), + // http://stackoverflow.com/questions/654112/how-do-you-detect-support-for-vml-or-svg-in-a-browser + vmlAvailable = function() { + if(vmlAvailable.vml == undefined) { + var a = document.body.appendChild(document.createElement('div')); + a.innerHTML = ''; + var b = a.firstChild; + b.style.behavior = "url(#default#VML)"; + vmlAvailable.vml = b ? typeof b.adj == "object": true; + a.parentNode.removeChild(a); + } + return vmlAvailable.vml; + }; + + /** + Manages dragging for some instance of jsPlumb. + + TODO move to DOM adapter + + */ + var DragManager = function(_currentInstance) { + + var _draggables = {}, _dlist = [], _delements = {}, _elementsWithEndpoints = {}; + + /** + register some element as draggable. right now the drag init stuff is done elsewhere, and it is + possible that will continue to be the case. + */ + this.register = function(el) { + var jpcl = jsPlumb.CurrentLibrary; + el = jpcl.getElementObject(el); + var id = _currentInstance.getId(el), + domEl = jpcl.getDOMElement(el); + if (!_draggables[id]) { + _draggables[id] = el; + _dlist.push(el); + _delements[id] = {}; + } + + // look for child elements that have endpoints and register them against this draggable. + var _oneLevel = function(p) { + if (p) { + var pEl = jpcl.getElementObject(p), + pOff = jpcl.getOffset(pEl); + + for (var i = 0; i < p.childNodes.length; i++) { + if (p.childNodes[i].nodeType != 3) { + var cEl = jpcl.getElementObject(p.childNodes[i]), + cid = _currentInstance.getId(cEl, null, true); + if (cid && _elementsWithEndpoints[cid] && _elementsWithEndpoints[cid] > 0) { + var cOff = jpcl.getOffset(cEl); + _delements[id][cid] = { + id:cid, + offset:{ + left:cOff.left - pOff.left, + top:cOff.top - pOff.top + } + }; + } + } + } + } + }; + + _oneLevel(domEl); + }; + + /** + notification that an endpoint was added to the given el. we go up from that el's parent + node, looking for a parent that has been registered as a draggable. if we find one, we add this + el to that parent's list of elements to update on drag (if it is not there already) + */ + this.endpointAdded = function(el) { + var jpcl = jsPlumb.CurrentLibrary, b = document.body, id = _currentInstance.getId(el), c = jpcl.getDOMElement(el), + p = c.parentNode, done = p == b; + + _elementsWithEndpoints[id] = _elementsWithEndpoints[id] ? _elementsWithEndpoints[id] + 1 : 1; + + while (p != b) { + var pid = _currentInstance.getId(p); + if (_draggables[pid]) { + var idx = -1, pEl = jpcl.getElementObject(p), pLoc = jpcl.getOffset(pEl); + + if (_delements[pid][id] == null) { + var cLoc = jsPlumb.CurrentLibrary.getOffset(el); + _delements[pid][id] = { + id:id, + offset:{ + left:cLoc.left - pLoc.left, + top:cLoc.top - pLoc.top + } + }; + } + break; + } + p = p.parentNode; + } + }; + + this.endpointDeleted = function(endpoint) { + if (_elementsWithEndpoints[endpoint.elementId]) { + _elementsWithEndpoints[endpoint.elementId]--; + if (_elementsWithEndpoints[endpoint.elementId] <= 0) { + for (var i in _delements) { + delete _delements[i][endpoint.elementId]; + } + } + } + }; + + this.getElementsForDraggable = function(id) { + return _delements[id]; + }; + + this.reset = function() { + _draggables = {}; + _dlist = []; + _delements = {}; + _elementsWithEndpoints = {}; + }; + + }; + + // for those browsers that dont have it. they still don't have it! but at least they won't crash. + if (!window.console) + window.console = { time:function(){}, timeEnd:function(){}, group:function(){}, groupEnd:function(){}, log:function(){} }; + + window.jsPlumbAdapter = { + + headless:false, + + appendToRoot : function(node) { + document.body.appendChild(node); + }, + getRenderModes : function() { + return [ "canvas", "svg", "vml" ] + }, + isRenderModeAvailable : function(m) { + return { + "canvas":canvasAvailable, + "svg":svgAvailable, + "vml":vmlAvailable() + }[m]; + }, + getDragManager : function(_jsPlumb) { + return new DragManager(_jsPlumb); + }, + setRenderMode : function(mode) { + var renderMode; + + if (mode) { + mode = mode.toLowerCase(); + + var canvasAvailable = this.isRenderModeAvailable("canvas"), + svgAvailable = this.isRenderModeAvailable("svg"), + vmlAvailable = this.isRenderModeAvailable("vml"); + + //if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); + // now test we actually have the capability to do this. + if (mode === "svg") { + if (svgAvailable) renderMode = "svg" + else if (canvasAvailable) renderMode = "canvas" + else if (vmlAvailable) renderMode = "vml" + } + else if (mode === "canvas" && canvasAvailable) renderMode = "canvas"; + else if (vmlAvailable) renderMode = "vml"; + } + + return renderMode; + } + }; + +})(); \ No newline at end of file diff --git a/archive/1.3.12/jsPlumb-overlays-guidelines-1.3.12-RC1.js b/archive/1.3.12/jsPlumb-overlays-guidelines-1.3.12-RC1.js new file mode 100644 index 000000000..7a4bc8952 --- /dev/null +++ b/archive/1.3.12/jsPlumb-overlays-guidelines-1.3.12-RC1.js @@ -0,0 +1,47 @@ +// a test + jsPlumb.Overlays.svg.GuideLines = function() { + var path = null, self = this, path2 = null, p1_1, p1_2; + jsPlumb.Overlays.GuideLines.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + + p1_1 = _node("path"); + connector.svg.appendChild(p1_1); + self.attachListeners(p1_1, connector); + self.attachListeners(p1_1, self); + + p1_2 = _node("path"); + connector.svg.appendChild(p1_2); + self.attachListeners(p1_2, connector); + self.attachListeners(p1_2, self); + + } + + _attr(path, { + "d" : makePath(d[0], d[1]), + stroke : "red", + fill : null + }); + + _attr(p1_1, { + "d" : makePath(d[2][0], d[2][1]), + stroke : "blue", + fill : null + }); + + _attr(p1_2, { + "d" : makePath(d[3][0], d[3][1]), + stroke : "green", + fill : null + }); + }; + + var makePath = function(d1, d2) { + return "M " + d1.x + "," + d1.y + + " L" + d2.x + "," + d2.y; + }; + }; \ No newline at end of file diff --git a/archive/1.3.12/jsPlumb-renderers-canvas-1.3.12-RC1.js b/archive/1.3.12/jsPlumb-renderers-canvas-1.3.12-RC1.js new file mode 100644 index 000000000..f0d994ec2 --- /dev/null +++ b/archive/1.3.12/jsPlumb-renderers-canvas-1.3.12-RC1.js @@ -0,0 +1,510 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.12 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the HTML5 canvas renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + +// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS ******************************************************************* + + // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. + var _connectionBeingDragged = null, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, + _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }; + + /* + * Class:CanvasMouseAdapter + * Provides support for mouse events on canvases. + */ + var CanvasMouseAdapter = function() { + var self = this; + self.overlayPlacements = []; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + jsPlumbUtil.EventGenerator.apply(this, arguments); + /** + * returns whether or not the given event is ojver a painted area of the canvas. + */ + this._over = function(e) { + var o = _getOffset(_getElementObject(self.canvas)), + pageXY = _pageXY(e), + x = pageXY[0] - o.left, y = pageXY[1] - o.top; + if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + return false; + }; + + var _mouseover = false, _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false, + _nullSafeHasClass = function(el, clazz) { + return el != null && _hasClass(el, clazz); + }; + this.mousemove = function(e) { + var pageXY = _pageXY(e), clientXY = _clientXY(e), + ee = document.elementFromPoint(clientXY[0], clientXY[1]), + eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); + var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); + if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.fire("mouseenter", self, e); + return true; + } + // TODO here there is a remote chance that the overlay the mouse moved onto + // is actually not an overlay for the current component. a more thorough check would + // be to ensure the overlay belonged to the current component. + else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { + _mouseover = false; + self.fire("mouseexit", self, e); + } + self.fire("mousemove", self, e); + }; + + this.click = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); + self.fire("mousedown", self, e); + } + }; + + this.mouseup = function(e) { + _mouseDown = false; + self.fire("mouseup", self, e); + }; + + this.contextmenu = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("contextmenu", self, e); + _mouseWasDown = false; + }; + }; + + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + params["_jsPlumb"].appendElement(canvas, params.parent); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + params["_jsPlumb"].getId(canvas, params.uuid); + if (params.tooltip) canvas.setAttribute("title", params.tooltip); + + return canvas; + }; + + var CanvasComponent = function(params) { + CanvasMouseAdapter.apply(this, arguments); + + var displayElements = [ ]; + this.getDisplayElements = function() { return displayElements; }; + this.appendDisplayElement = function(el) { displayElements.push(el); }; + }; + + /** + * Class:CanvasConnector + * Superclass for Canvas Connector renderers. + */ + var CanvasConnector = jsPlumb.CanvasConnector = function(params) { + + CanvasComponent.apply(this, arguments); + + var _paintOneStyle = function(dim, aStyle) { + self.ctx.save(); + jsPlumb.extend(self.ctx, aStyle); + if (aStyle.gradient) { + var g = self.createGradient(dim, self.ctx); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + self.ctx.strokeStyle = g; + } + self._paint(dim, aStyle); + self.ctx.restore(); + }; + + var self = this, + clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:params.tooltip + }); + self.ctx = self.canvas.getContext("2d"); + + self.appendDisplayElement(self.canvas); + + self.paint = function(dim, style) { + if (style != null) { + jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); + if (self.getZIndex()) + self.canvas.style.zIndex = self.getZIndex(); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + _paintOneStyle(dim, outlineStyle); + } + _paintOneStyle(dim, style); + } + }; + }; + + /** + * Class:CanvasEndpoint + * Superclass for Canvas Endpoint renderers. + */ + var CanvasEndpoint = function(params) { + var self = this; + CanvasComponent.apply(this, arguments); + var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""), + canvasParams = { + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:self.tooltip + }; + self.canvas = _newCanvas(canvasParams); + self.ctx = self.canvas.getContext("2d"); + + self.appendDisplayElement(self.canvas); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + } + + self._paint.apply(this, arguments); + }; + }; + + jsPlumb.Endpoints.canvas.Dot = function(params) { + jsPlumb.Endpoints.Dot.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + var self = this, + parseValue = function(value) { + try { return parseInt(value); } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }, + calculateAdjustments = function(gradient) { + var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius; + gradient.offset && (offsetAdjustment = parseValue(gradient.offset)); + gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius)); + return [offsetAdjustment, innerRadius]; + }; + this._paint = function(d, style, anchor) { + if (style != null) { + var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + if (style.gradient) { + var adjustments = calculateAdjustments(style.gradient), + yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0], + xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0], + g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + ctx.beginPath(); + ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + } + }; + }; + + jsPlumb.Endpoints.canvas.Rectangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) { + + var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + + /* canvas gradient */ + if (style.gradient) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, d[2], d[3]); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + jsPlumb.Endpoints.canvas.Triangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Triangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) + { + var width = d[2], height = d[3], x = d[0], y = d[1], + ctx = self.canvas.getContext('2d'), + offsetX = 0, offsetY = 0, angle = 0, + orientation = anchor.getOrientation(self); + + if( orientation[0] == 1 ) { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = style.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + /* + * Canvas Image Endpoint: uses the default version, which creates an tag. + */ + jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image; + + /* + * Blank endpoint in all renderers is just the default Blank endpoint. + */ + jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank; + + /* + * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Bezier = function() { + var self = this; + jsPlumb.Connectors.Bezier.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions, style) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO i doubt this handles the case that source and target are swapped. + this.createGradient = function(dim, ctx, swap) { + return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + /* + * Canvas straight line Connector. Draws a straight line onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Straight = function() { + var self = this, + segmentMultipliers = [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ]; + + jsPlumb.Connectors.Straight.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions, style) { + + self.ctx.beginPath(); + + if (style.dashstyle && style.dashstyle.split(" ").length == 2) { + // only a very simple dashed style is supported - having two values, which define the stroke length + // (as a multiple of the stroke width) and then the space length (also as a multiple of stroke width). + var ds = style.dashstyle.split(" "); + if (ds.length != 2) ds = [2, 2]; + var dss = [ ds[0] * style.lineWidth, ds[1] * style.lineWidth ], + m = (dimensions[6] - dimensions[4]) / (dimensions[7] - dimensions[5]), + s = jsPlumbUtil.segment([dimensions[4], dimensions[5]], [ dimensions[6], dimensions[7] ]), + sm = segmentMultipliers[s], + theta = Math.atan(m), + l = Math.sqrt(Math.pow(dimensions[6] - dimensions[4], 2) + Math.pow(dimensions[7] - dimensions[5], 2)), + repeats = Math.floor(l / (dss[0] + dss[1])), + curPos = [dimensions[4], dimensions[5]]; + + + // TODO: the question here is why could we not support this in all connector types? it's really + // just a case of going along and asking jsPlumb for the next point on the path a few times, until it + // reaches the end. every type of connector supports that method, after all. but right now its only the + // bezier connector that gives you back the new location on the path along with the x,y coordinates, which + // we would need. we'd start out at loc=0 and ask for the point along the path that is dss[0] pixels away. + // we then ask for the point that is (dss[0] + dss[1]) pixels away; and from that one we need not just the + // x,y but the location, cos we're gonna plug that location back in in order to find where that dash ends. + // + // it also strikes me that it should be trivial to support arbitrary dash styles (having more or less than two + // entries). you'd just iterate that array using a step size of 2, and generify the (rss[0] + rss[1]) + // computation to be sum(rss[0]..rss[n]). + + for (var i = 0; i < repeats; i++) { + self.ctx.moveTo(curPos[0], curPos[1]); + + var nextEndX = curPos[0] + (Math.abs(Math.sin(theta) * dss[0]) * sm[0]), + nextEndY = curPos[1] + (Math.abs(Math.cos(theta) * dss[0]) * sm[1]), + nextStartX = curPos[0] + (Math.abs(Math.sin(theta) * (dss[0] + dss[1])) * sm[0]), + nextStartY = curPos[1] + (Math.abs(Math.cos(theta) * (dss[0] + dss[1])) * sm[1]) + + self.ctx.lineTo(nextEndX, nextEndY); + curPos = [nextStartX, nextStartY]; + } + + // now draw the last bit + self.ctx.moveTo(curPos[0], curPos[1]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + + } + else { + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + } + + self.ctx.stroke(); + }; + + // TODO this does not handle the case that src and target are swapped. + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + jsPlumb.Connectors.canvas.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions, style) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + +// ********************************* END OF CANVAS RENDERERS ******************************************************************* + + jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label; + jsPlumb.Overlays.canvas.Custom = jsPlumb.Overlays.Custom; + + /** + * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. + */ + var CanvasOverlay = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }; + + var AbstractCanvasArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + CanvasOverlay.apply(this, originalArgs); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + var ctx = connector.ctx; + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(d.hxy.x, d.hxy.y); + ctx.lineTo(d.tail[0].x, d.tail[0].y); + ctx.lineTo(d.cxy.x, d.cxy.y); + ctx.lineTo(d.tail[1].x, d.tail[1].y); + ctx.lineTo(d.hxy.x, d.hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + }; + + jsPlumb.Overlays.canvas.Arrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.canvas.PlainArrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.canvas.Diamond = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.12/jsPlumb-renderers-svg-1.3.12-RC1.js b/archive/1.3.12/jsPlumb-renderers-svg-1.3.12-RC1.js new file mode 100644 index 000000000..d8d63a013 --- /dev/null +++ b/archive/1.3.12/jsPlumb-renderers-svg-1.3.12-RC1.js @@ -0,0 +1,555 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.12 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the SVG renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +/** + * SVG support for jsPlumb. + * + * things to investigate: + * + * gradients: https://developer.mozilla.org/en/svg_in_html_introduction + * css:http://tutorials.jenkov.com/svg/svg-and-css.html + * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath + * pointer events: https://developer.mozilla.org/en/css/pointer-events + * + * IE9 hover jquery: http://forum.jquery.com/topic/1-6-2-broke-svg-hover-events + * + */ +;(function() { + + var svgAttributeMap = { + "joinstyle":"stroke-linejoin", + "stroke-linejoin":"stroke-linejoin", + "stroke-dashoffset":"stroke-dashoffset", + "stroke-linecap":"stroke-linecap" + }, + STROKE_DASHARRAY = "stroke-dasharray", + DASHSTYLE = "dashstyle", + LINEAR_GRADIENT = "linearGradient", + RADIAL_GRADIENT = "radialGradient", + FILL = "fill", + STOP = "stop", + STROKE = "stroke", + STROKE_WIDTH = "stroke-width", + STYLE = "style", + NONE = "none", + JSPLUMB_GRADIENT = "jsplumb_gradient_", + LINE_WIDTH = "lineWidth", + ns = { + svg:"http://www.w3.org/2000/svg", + xhtml:"http://www.w3.org/1999/xhtml" + }, + _attr = function(node, attributes) { + for (var i in attributes) + node.setAttribute(i, "" + attributes[i]); + }, + _node = function(name, attributes) { + var n = document.createElementNS(ns.svg, name); + attributes = attributes || {}; + attributes["version"] = "1.1"; + attributes["xmlns"] = ns.xhtml; + _attr(n, attributes); + return n; + }, + _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; }, + _clearGradient = function(parent) { + for (var i = 0; i < parent.childNodes.length; i++) { + if (parent.childNodes[i].tagName == LINEAR_GRADIENT || parent.childNodes[i].tagName == RADIAL_GRADIENT) + parent.removeChild(parent.childNodes[i]); + } + }, + _updateGradient = function(parent, node, style, dimensions, uiComponent) { + var id = JSPLUMB_GRADIENT + uiComponent._jsPlumb.idstamp(); + // first clear out any existing gradient + _clearGradient(parent); + // this checks for an 'offset' property in the gradient, and in the absence of it, assumes + // we want a linear gradient. if it's there, we create a radial gradient. + // it is possible that a more explicit means of defining the gradient type would be + // better. relying on 'offset' means that we can never have a radial gradient that uses + // some default offset, for instance. + // issue 244 suggested the 'gradientUnits' attribute; without this, straight/flowchart connectors with gradients would + // not show gradients when the line was perfectly horizontal or vertical. + if (!style.gradient.offset) { + var g = _node(LINEAR_GRADIENT, {id:id, gradientUnits:"userSpaceOnUse"}); + parent.appendChild(g); + } + else { + var g = _node(RADIAL_GRADIENT, { + id:id + }); + parent.appendChild(g); + } + + // the svg radial gradient seems to treat stops in the reverse + // order to how canvas does it. so we want to keep all the maths the same, but + // iterate the actual style declarations in reverse order, if the x indexes are not in order. + for (var i = 0; i < style.gradient.stops.length; i++) { + // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of + // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments. + // so, not too concerned about leaving this in for now. + var styleToUse = i; + if (dimensions.length == 8) + styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i; + else + styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i; + var stopColor = jsPlumbUtil.convertStyle(style.gradient.stops[styleToUse][1], true); + var s = _node(STOP, {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor}); + g.appendChild(s); + } + var applyGradientTo = style.strokeStyle ? STROKE : FILL; + node.setAttribute(STYLE, applyGradientTo + ":url(#" + id + ")"); + }, + _applyStyles = function(parent, node, style, dimensions, uiComponent) { + + if (style.gradient) { + _updateGradient(parent, node, style, dimensions, uiComponent); + } + else { + // make sure we clear any existing gradient + _clearGradient(parent); + node.setAttribute(STYLE, ""); + } + + node.setAttribute(FILL, style.fillStyle ? jsPlumbUtil.convertStyle(style.fillStyle, true) : NONE); + node.setAttribute(STROKE, style.strokeStyle ? jsPlumbUtil.convertStyle(style.strokeStyle, true) : NONE); + if (style.lineWidth) { + node.setAttribute(STROKE_WIDTH, style.lineWidth); + } + + // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like + // the syntax in VML but is actually kind of nasty: values are given in the pixel + // coordinate space, whereas in VML they are multiples of the width of the stroked + // line, which makes a lot more sense. for that reason, jsPlumb is supporting both + // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from + // VML, which will be the preferred method. the code below this converts a dashstyle + // attribute given in terms of stroke width into a pixel representation, by using the + // stroke's lineWidth. + if (style[DASHSTYLE] && style[LINE_WIDTH] && !style[STROKE_DASHARRAY]) { + var sep = style[DASHSTYLE].indexOf(",") == -1 ? " " : ",", + parts = style[DASHSTYLE].split(sep), + styleToUse = ""; + parts.forEach(function(p) { + styleToUse += (Math.floor(p * style.lineWidth) + sep); + }); + node.setAttribute(STROKE_DASHARRAY, styleToUse); + } + else if(style[STROKE_DASHARRAY]) { + node.setAttribute(STROKE_DASHARRAY, style[STROKE_DASHARRAY]); + } + + // extra attributes such as join type, dash offset. + for (var i in svgAttributeMap) { + if (style[i]) { + node.setAttribute(svgAttributeMap[i], style[i]); + } + } + }, + _decodeFont = function(f) { + var r = /([0-9].)(p[xt])\s(.*)/; + var bits = f.match(r); + return {size:bits[1] + bits[2], font:bits[3]}; + }, + _classManip = function(el, add, clazz) { + var classesToAddOrRemove = clazz.split(" "), + className = el.className, + curClasses = className.baseVal.split(" "); + + for (var i = 0; i < classesToAddOrRemove.length; i++) { + if (add) { + if (curClasses.indexOf(classesToAddOrRemove[i]) == -1) + curClasses.push(classesToAddOrRemove[i]); + } + else { + var idx = curClasses.indexOf(classesToAddOrRemove[i]); + if (idx != -1) + curClasses.splice(idx, 1); + } + } + + el.className.baseVal = curClasses.join(" "); + }, + _addClass = function(el, clazz) { + _classManip(el, true, clazz); + }, + _removeClass = function(el, clazz) { + _classManip(el, false, clazz); + }; + + /** + utility methods for other objects to use. + */ + jsPlumbUtil.svg = { + addClass:_addClass, + removeClass:_removeClass, + node:_node, + attr:_attr, + pos:_pos + }; + + /* + * Base class for SVG components. + */ + var SvgComponent = function(params) { + var self = this, + pointerEventsSpec = params.pointerEventsSpec || "all"; + jsPlumb.jsPlumbUIComponent.apply(this, params.originalArgs); + self.canvas = null, self.path = null, self.svg = null; + + var clazz = params.cssClass + " " + (params.originalArgs[0].cssClass || ""), + svgParams = { + "style":"", + "width":0, + "height":0, + "pointer-events":pointerEventsSpec, + "position":"absolute" + }; + if (self.tooltip) svgParams["title"] = self.tooltip; + self.svg = _node("svg", svgParams); + if (params.useDivWrapper) { + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.sizeCanvas(self.canvas,0,0,1,1); + self.canvas.className = clazz; + if (self.tooltip) self.canvas.setAttribute("title", self.tooltip); + } + else { + _attr(self.svg, { "class":clazz }); + self.canvas = self.svg; + } + + params._jsPlumb.appendElement(self.canvas, params.originalArgs[0]["parent"]); + if (params.useDivWrapper) self.canvas.appendChild(self.svg); + + // TODO this displayElement stuff is common between all components, across all + // renderers. would be best moved to jsPlumbUIComponent. + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + this.paint = function(d, style, anchor) { + if (style != null) { + var x = d[0], y = d[1]; + if (params.useDivWrapper) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + x = 0, y = 0; + } + var p = _pos([x, y, d[2], d[3]]); + if (self.getZIndex()) p += ";z-index:" + self.getZIndex() + ";"; + _attr(self.svg, { + "style":p, + "width": d[2], + "height": d[3] + }); + self._paint.apply(this, arguments); + } + }; + }; + + /* + * Base class for SVG connectors. + */ + var SvgConnector = jsPlumb.SvgConnector = function(params) { + var self = this; + SvgComponent.apply(this, [ { + cssClass:params["_jsPlumb"].connectorClass, + originalArgs:arguments, + pointerEventsSpec:"none", + tooltip:params.tooltip, + _jsPlumb:params["_jsPlumb"] + } ]); + this._paint = function(d, style) { + var p = self.getPath(d), a = { "d":p }, outlineStyle = null; + a["pointer-events"] = "all"; + + // outline style. actually means drawing an svg object underneath the main one. + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = jsPlumb.CurrentLibrary.extend({}, style); + outlineStyle.strokeStyle = jsPlumbUtil.convertStyle(style.outlineColor); + outlineStyle.lineWidth = outlineStrokeWidth; + + if (self.bgPath == null) { + self.bgPath = _node("path", a); + self.svg.appendChild(self.bgPath); + self.attachListeners(self.bgPath, self); + } + else { + _attr(self.bgPath, a); + } + + _applyStyles(self.svg, self.bgPath, outlineStyle, d, self); + } + + + // test - see below + // a["clip-path"]= "url(#testClip)"; + + if (self.path == null) { + self.path = _node("path", a); + self.svg.appendChild(self.path); + self.attachListeners(self.path, self); + + /* + this is a test of a clip path. i'm looking into using one of these to animate a jsplumb connection. + you could do this by walking along the line, stepping along a little at a time, and setting the clip + path to extend as far as that point. + + self.clip = _node("clipPath", {id:"testClip", clipPathUnits:"objectBoundingBox"}); + self.svg.appendChild(self.clip); + self.clip.appendChild(_node("rect", { + x:"0",y:"0",width:"0.5",height:"1" + })); + */ + } + else { + _attr(self.path, a); + } + + _applyStyles(self.svg, self.path, style, d, self); + }; + + this.reattachListeners = function() { + if (self.bgPath) self.reattachListenersForElement(self.bgPath, self); + if (self.path) self.reattachListenersForElement(self.path, self); + }; + + }; + + /* + * SVG Bezier Connector + */ + jsPlumb.Connectors.svg.Bezier = function(params) { + jsPlumb.Connectors.Bezier.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { + var _p = "M " + d[4] + " " + d[5]; + _p += (" C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]); + return _p; + }; + }; + + /* + * SVG straight line Connector + */ + jsPlumb.Connectors.svg.Straight = function(params) { + jsPlumb.Connectors.Straight.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; }; + }; + + jsPlumb.Connectors.svg.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "M " + dimensions[4] + "," + dimensions[5]; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)]; + } + // finally draw a line to the end + p = p + " " + dimensions[6] + "," + dimensions[7]; + return p; + }; + }; + + /* + * Base class for SVG endpoints. + */ + var SvgEndpoint = window.SvgEndpoint = function(params) { + var self = this; + SvgComponent.apply(this, [ { + cssClass:params["_jsPlumb"].endpointClass, + originalArgs:arguments, + pointerEventsSpec:"all", + useDivWrapper:true, + _jsPlumb:params["_jsPlumb"] + } ]); + this._paint = function(d, style) { + var s = jsPlumb.extend({}, style); + if (s.outlineColor) { + s.strokeWidth = s.outlineWidth; + s.strokeStyle = jsPlumbUtil.convertStyle(s.outlineColor, true); + } + + if (self.node == null) { + self.node = self.makeNode(d, s); + self.svg.appendChild(self.node); + self.attachListeners(self.node, self); + } + _applyStyles(self.svg, self.node, s, d, self); + _pos(self.node, d); + }; + + this.reattachListeners = function() { + if (self.node) self.reattachListenersForElement(self.node, self); + }; + }; + + /* + * SVG Dot Endpoint + */ + jsPlumb.Endpoints.svg.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("circle", { + "cx" : d[2] / 2, + "cy" : d[3] / 2, + "r" : d[2] / 2 + }); + }; + }; + + /* + * SVG Rectangle Endpoint + */ + jsPlumb.Endpoints.svg.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("rect", { + "width":d[2], + "height":d[3] + }); + }; + }; + + /* + * SVG Image Endpoint is the default image endpoint. + */ + jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image; + /* + * Blank endpoint in svg renderer is the default Blank endpoint. + */ + jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank; + /* + * Label overlay in svg renderer is the default Label overlay. + */ + jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label; + /* + * Custom overlay in svg renderer is the default Custom overlay. + */ + jsPlumb.Overlays.svg.Custom = jsPlumb.Overlays.Custom; + + var AbstractSvgArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + this.isAppendedAtTopLevel = false; + var self = this, path = null; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path", { + "pointer-events":"all" + }); + connector.svg.appendChild(path); + + self.attachListeners(path, connector); + self.attachListeners(path, self); + } + var clazz = originalArgs && (originalArgs.length == 1) ? (originalArgs[0].cssClass || "") : ""; + + _attr(path, { + "d" : makePath(d), + "class" : clazz, + stroke : strokeStyle ? strokeStyle : null, + fill : fillStyle ? fillStyle : null + }); + }; + var makePath = function(d) { + return "M" + d.hxy.x + "," + d.hxy.y + + " L" + d.tail[0].x + "," + d.tail[0].y + + " L" + d.cxy.x + "," + d.cxy.y + + " L" + d.tail[1].x + "," + d.tail[1].y + + " L" + d.hxy.x + "," + d.hxy.y; + }; + this.reattachListeners = function() { + if (path) self.reattachListenersForElement(path, self); + }; + this.cleanup = function() { + if (path != null) jsPlumb.CurrentLibrary.removeElement(path); + }; + }; + + jsPlumb.Overlays.svg.Arrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.svg.PlainArrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.svg.Diamond = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; + + // a test + jsPlumb.Overlays.svg.GuideLines = function() { + var path = null, self = this, path2 = null, p1_1, p1_2; + jsPlumb.Overlays.GuideLines.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + + p1_1 = _node("path"); + connector.svg.appendChild(p1_1); + self.attachListeners(p1_1, connector); + self.attachListeners(p1_1, self); + + p1_2 = _node("path"); + connector.svg.appendChild(p1_2); + self.attachListeners(p1_2, connector); + self.attachListeners(p1_2, self); + + } + + _attr(path, { + "d" : makePath(d[0], d[1]), + stroke : "red", + fill : null + }); + + _attr(p1_1, { + "d" : makePath(d[2][0], d[2][1]), + stroke : "blue", + fill : null + }); + + _attr(p1_2, { + "d" : makePath(d[3][0], d[3][1]), + stroke : "green", + fill : null + }); + }; + + var makePath = function(d1, d2) { + return "M " + d1.x + "," + d1.y + + " L" + d2.x + "," + d2.y; + }; + + }; +})(); \ No newline at end of file diff --git a/archive/1.3.12/jsPlumb-renderers-vml-1.3.12-RC1.js b/archive/1.3.12/jsPlumb-renderers-vml-1.3.12-RC1.js new file mode 100644 index 000000000..8467da742 --- /dev/null +++ b/archive/1.3.12/jsPlumb-renderers-vml-1.3.12-RC1.js @@ -0,0 +1,454 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.12 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the VML renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + // http://ajaxian.com/archives/the-vml-changes-in-ie-8 + // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/ + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + + var vmlAttributeMap = { + "stroke-linejoin":"joinstyle", + "joinstyle":"joinstyle", + "endcap":"endcap", + "miterlimit":"miterlimit" + }, + jsPlumbStylesheet = null; + + if (document.createStyleSheet && document.namespaces) { + + var ruleClasses = [ + ".jsplumb_vml", "jsplumb\\:textbox", "jsplumb\\:oval", "jsplumb\\:rect", + "jsplumb\\:stroke", "jsplumb\\:shape", "jsplumb\\:group" + ], + rule = "behavior:url(#default#VML);position:absolute;"; + + jsPlumbStylesheet = document.createStyleSheet(); + + for (var i = 0; i < ruleClasses.length; i++) + jsPlumbStylesheet.addRule(ruleClasses[i], rule); + + // in this page it is also mentioned that IE requires the extra arg to the namespace + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either. + // var iev = document.documentMode; + //if (!iev || iev < 8) + document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); + //else + // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML"); + } + + jsPlumb.vml = {}; + + var scale = 1000, + + _groupMap = {}, + _getGroup = function(container, connectorClass) { + var id = jsPlumb.getId(container), + g = _groupMap[id]; + if(!g) { + g = _node("group", [0,0,scale, scale], {"class":connectorClass}); + //g.style.position=absolute; + //g["coordsize"] = "1000,1000"; + g.style.backgroundColor="red"; + _groupMap[id] = g; + jsPlumb.appendElement(g, container); // todo if this gets reinstated, remember to use the current jsplumb instance. + //document.body.appendChild(g); + } + return g; + }, + _atts = function(o, atts) { + for (var i in atts) { + // IE8 fix: setattribute does not work after an element has been added to the dom! + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + //o.setAttribute(i, atts[i]); + + /*There is an additional problem when accessing VML elements by using get/setAttribute. The simple solution is following: + + if (document.documentMode==8) { + ele.opacity=1; + } else { + ele.setAttribute(‘opacity’,1); + } + */ + + o[i] = atts[i]; + } + }, + _node = function(name, d, atts, parent, _jsPlumb, deferToJsPlumbContainer) { + atts = atts || {}; + var o = document.createElement("jsplumb:" + name); + if (deferToJsPlumbContainer) + _jsPlumb.appendElement(o, parent); + else + jsPlumb.CurrentLibrary.appendElement(o, parent); + o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml"; + _pos(o, d); + _atts(o, atts); + return o; + }, + _pos = function(o,d, zIndex) { + o.style.left = d[0] + "px"; + o.style.top = d[1] + "px"; + o.style.width= d[2] + "px"; + o.style.height= d[3] + "px"; + o.style.position = "absolute"; + if (zIndex) + o.style.zIndex = zIndex; + }, + _conv = jsPlumb.vml.convertValue = function(v) { + return Math.floor(v * scale); + }, + // tests if the given style is "transparent" and then sets the appropriate opacity node to 0 if so, + // or 1 if not. TODO in the future, support variable opacity. + _maybeSetOpacity = function(styleToWrite, styleToCheck, type, component) { + if ("transparent" === styleToCheck) + component.setOpacity(type, "0.0"); + else + component.setOpacity(type, "1.0"); + }, + _applyStyles = function(node, style, component, _jsPlumb) { + var styleToWrite = {}; + if (style.strokeStyle) { + styleToWrite["stroked"] = "true"; + var strokeColor = jsPlumbUtil.convertStyle(style.strokeStyle, true); + styleToWrite["strokecolor"] = strokeColor; + _maybeSetOpacity(styleToWrite, strokeColor, "stroke", component); + styleToWrite["strokeweight"] = style.lineWidth + "px"; + } + else styleToWrite["stroked"] = "false"; + + if (style.fillStyle) { + styleToWrite["filled"] = "true"; + var fillColor = jsPlumbUtil.convertStyle(style.fillStyle, true); + styleToWrite["fillcolor"] = fillColor; + _maybeSetOpacity(styleToWrite, fillColor, "fill", component); + } + else styleToWrite["filled"] = "false"; + + if(style["dashstyle"]) { + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }, node, _jsPlumb); + } + else + component.strokeNode.dashstyle = style["dashstyle"]; + } + else if (style["stroke-dasharray"] && style["lineWidth"]) { + var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",", + parts = style["stroke-dasharray"].split(sep), + styleToUse = ""; + for(var i = 0; i < parts.length; i++) { + styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep); + } + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }, node, _jsPlumb); + } + else + component.strokeNode.dashstyle = styleToUse; + } + + _atts(node, styleToWrite); + }, + /* + * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. + */ + VmlComponent = function() { + var self = this; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.opacityNodes = { + "stroke":null, + "fill":null + }; + this.initOpacityNodes = function(vml) { + self.opacityNodes["stroke"] = _node("stroke", [0,0,1,1], {opacity:"0.0"}, vml, self._jsPlumb); + self.opacityNodes["fill"] = _node("fill", [0,0,1,1], {opacity:"0.0"}, vml, self._jsPlumb); + }; + this.setOpacity = function(type, value) { + var node = self.opacityNodes[type]; + if (node) node["opacity"] = "" + value; + }; + var displayElements = [ ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el, doNotAppendToCanvas) { + if (!doNotAppendToCanvas) self.canvas.parentNode.appendChild(el); + displayElements.push(el); + }; + }, + /* + * Base class for Vml connectors. extends VmlComponent. + */ + VmlConnector = jsPlumb.VmlConnector = function(params) { + var self = this; + self.strokeNode = null; + self.canvas = null; + VmlComponent.apply(this, arguments); + var clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : ""); + this.paint = function(d, style, anchor) { + if (style != null) { + var path = self.getPath(d), p = { "path":path }; + + //* + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle : jsPlumbUtil.convertStyle(style.outlineColor), + lineWidth : outlineStrokeWidth + }; + for (var aa in vmlAttributeMap) outlineStyle[aa] = style[aa]; + + if (self.bgCanvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.bgCanvas = _node("shape", d, p, params.parent, self._jsPlumb, true); + _pos(self.bgCanvas, d, self.getZIndex()); + self.appendDisplayElement(self.bgCanvas, true); + self.attachListeners(self.bgCanvas, self); + self.initOpacityNodes(self.bgCanvas, ["stroke"]); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.bgCanvas, d, self.getZIndex()); + _atts(self.bgCanvas, p); + } + + _applyStyles(self.bgCanvas, outlineStyle, self); + } + //*/ + + if (self.canvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + if (self.tooltip) p["label"] = self.tooltip; + self.canvas = _node("shape", d, p, params.parent, self._jsPlumb, true); + //var group = _getGroup(params.parent); // test of append everything to a group + //group.appendChild(self.canvas); // sort of works but not exactly; + //params["_jsPlumb"].appendElement(self.canvas, params.parent); //before introduction of groups + + self.appendDisplayElement(self.canvas, true); + self.attachListeners(self.canvas, self); + self.initOpacityNodes(self.canvas, ["stroke"]); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.canvas, d, self.getZIndex()); + _atts(self.canvas, p); + } + + _applyStyles(self.canvas, style, self, self._jsPlumb); + } + }; + + //self.appendDisplayElement(self.canvas); + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + }, + /* + * + * Base class for Vml Endpoints. extends VmlComponent. + * + */ + VmlEndpoint = window.VmlEndpoint = function(params) { + VmlComponent.apply(this, arguments); + var vml = null, self = this, opacityStrokeNode = null, opacityFillNode = null; + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + + var clazz = self._jsPlumb.endpointClass + (params.cssClass ? (" " + params.cssClass) : ""); + + //var group = _getGroup(params.parent); + //group.appendChild(self.canvas); + params["_jsPlumb"].appendElement(self.canvas, params.parent); + + if (self.tooltip) self.canvas.setAttribute("label", self.tooltip); + + this.paint = function(d, style, anchor) { + var p = { }; + + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (vml == null) { + p["class"] = clazz; + vml = self.getVml([0,0, d[2], d[3]], p, anchor, self.canvas, self._jsPlumb); + self.attachListeners(vml, self); + + self.appendDisplayElement(vml, true); + self.appendDisplayElement(self.canvas, true); + + self.initOpacityNodes(vml, ["fill"]); + } + else { + _pos(vml, [0,0, d[2], d[3]]); + _atts(vml, p); + } + + _applyStyles(vml, style, self); + }; + + this.reattachListeners = function() { + if (vml) self.reattachListenersForElement(vml, self); + }; + }; + + jsPlumb.Connectors.vml.Bezier = function() { + jsPlumb.Connectors.Bezier.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Straight = function() { + jsPlumb.Connectors.Straight.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Flowchart = function() { + jsPlumb.Connectors.Flowchart.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l"; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]); + } + // finally draw a line to the end + p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e"; + return p; + }; + }; + + jsPlumb.Endpoints.vml.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor, parent, _jsPlumb) { return _node("oval", d, atts, parent, _jsPlumb); }; + }; + + jsPlumb.Endpoints.vml.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor, parent, _jsPlumb) { return _node("rect", d, atts, parent, _jsPlumb); }; + }; + + /* + * VML Image Endpoint is the same as the default image endpoint. + */ + jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image; + + /** + * placeholder for Blank endpoint in vml renderer. + */ + jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank; + + /** + * VML Label renderer. uses the default label renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label; + + /** + * VML Custom renderer. uses the default Custom renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Custom = jsPlumb.Overlays.Custom; + + var AbstractVmlArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + VmlComponent.apply(this, originalArgs); + var self = this, path = null; + self.canvas = null; + self.isAppendedAtTopLevel = true; + var getPath = function(d, connectorDimensions) { + return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) + + " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + + " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + + " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + + " x e"; + }; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) { + var p = {}; + if (strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] = jsPlumbUtil.convertStyle(strokeStyle, true); + } + if (lineWidth) p["strokeweight"] = lineWidth + "px"; + if (fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = fillStyle; + } + var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + w = Math.abs(xmax - xmin), + h = Math.abs(ymax - ymin), + dim = [xmin, ymin, w, h]; + + // for VML, we create overlays using shapes that have the same dimensions and + // coordsize as their connector - overlays calculate themselves relative to the + // connector (it's how it's been done since the original canvas implementation, because + // for canvas that makes sense). + p["path"] = getPath(d, connectorDimensions); + p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale); + + dim[0] = connectorDimensions[0]; + dim[1] = connectorDimensions[1]; + dim[2] = connectorDimensions[2]; + dim[3] = connectorDimensions[3]; + + if (self.canvas == null) { + //p["class"] = jsPlumb.overlayClass; // TODO currentInstance? + self.canvas = _node("shape", dim, p, connector.canvas.parentNode, connector._jsPlumb, true); + connector.appendDisplayElement(self.canvas, true); + self.attachListeners(self.canvas, connector); + self.attachListeners(self.canvas, self); + } + else { + _pos(self.canvas, dim); + _atts(self.canvas, p); + } + }; + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + + this.cleanup = function() { + if (self.canvas != null) jsPlumb.CurrentLibrary.removeElement(self.canvas); + }; + }; + + jsPlumb.Overlays.vml.Arrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.vml.PlainArrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.vml.Diamond = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.12/jsPlumb-util-1.3.12-RC1.js b/archive/1.3.12/jsPlumb-util-1.3.12-RC1.js new file mode 100644 index 000000000..eb747bfe9 --- /dev/null +++ b/archive/1.3.12/jsPlumb-util-1.3.12-RC1.js @@ -0,0 +1,243 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.12 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the util functions + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ +jsPlumbUtil = { + isArray : function(a) { + return Object.prototype.toString.call(a) === "[object Array]"; + }, + isString : function(s) { + return typeof s === "string"; + }, + isBoolean: function(s) { + return typeof s === "boolean"; + }, + isObject : function(o) { + return Object.prototype.toString.call(o) === "[object Object]"; + }, + merge : function(a, b) { + var c = jsPlumb.extend({}, a); + for (var i in b) { + if (c[i] == null || this.isString(b[i]) || this.isBoolean(b[i])) + c[i] = b[i]; + else { + if (this.isArray(b[i]) && this.isArray(c[i])) { + var ar = []; + ar.push.apply(ar, c[i]); + ar.push.apply(ar, b[i]); + c[i] = ar; + } + else if(this.isObject(c[i]) && this.isObject(b[i])) { + for (var j in b[i]) + c[i][j] = b[i][j]; + } + } + } + return c; + }, + convertStyle : function(s, ignoreAlpha) { + // TODO: jsPlumb should support a separate 'opacity' style member. + if ("transparent" === s) return s; + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + return o; + }, + gradient : function(p1, p2) { + p1 = jsPlumbUtil.isArray(p1) ? p1 : [p1.x, p1.y]; + p2 = jsPlumbUtil.isArray(p2) ? p2 : [p2.x, p2.y]; + return (p2[1] - p1[1]) / (p2[0] - p1[0]); + }, + normal : function(p1, p2) { + return -1 / jsPlumbUtil.gradient(p1,p2); + }, + lineLength : function(p1, p2) { + p1 = jsPlumbUtil.isArray(p1) ? p1 : [p1.x, p1.y]; + p2 = jsPlumbUtil.isArray(p2) ? p2 : [p2.x, p2.y]; + return Math.sqrt(Math.pow(p2[1] - p1[1], 2) + Math.pow(p2[0] - p1[0], 2)); + }, + segment : function(p1, p2) { + p1 = jsPlumbUtil.isArray(p1) ? p1 : [p1.x, p1.y]; + p2 = jsPlumbUtil.isArray(p2) ? p2 : [p2.x, p2.y]; + if (p2[0] > p1[0]) { + return (p2[1] > p1[1]) ? 2 : 1; + } + else { + return (p2[1] > p1[1]) ? 3 : 4; + } + }, + intersects : function(r1, r2) { + var x1 = r1.x, x2 = r1.x + r1.w, y1 = r1.y, y2 = r1.y + r1.h, + a1 = r2.x, a2 = r2.x + r2.w, b1 = r2.y, b2 = r2.y + r2.h; + + return ( (x1 <= a1 && a1 <= x2) && (y1 <= b1 && b1 <= y2) ) || + ( (x1 <= a2 && a2 <= x2) && (y1 <= b1 && b1 <= y2) ) || + ( (x1 <= a1 && a1 <= x2) && (y1 <= b2 && b2 <= y2) ) || + ( (x1 <= a2 && a1 <= x2) && (y1 <= b2 && b2 <= y2) ) || + + ( (a1 <= x1 && x1 <= a2) && (b1 <= y1 && y1 <= b2) ) || + ( (a1 <= x2 && x2 <= a2) && (b1 <= y1 && y1 <= b2) ) || + ( (a1 <= x1 && x1 <= a2) && (b1 <= y2 && y2 <= b2) ) || + ( (a1 <= x2 && x1 <= a2) && (b1 <= y2 && y2 <= b2) ); + }, + segmentMultipliers : [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ], + inverseSegmentMultipliers : [null, [-1, -1], [-1, 1], [1, 1], [1, -1] ], + pointOnLine : function(fromPoint, toPoint, distance) { + var m = jsPlumbUtil.gradient(fromPoint, toPoint), + s = jsPlumbUtil.segment(fromPoint, toPoint), + segmentMultiplier = distance > 0 ? jsPlumbUtil.segmentMultipliers[s] : jsPlumbUtil.inverseSegmentMultipliers[s], + theta = Math.atan(m), + y = Math.abs(distance * Math.sin(theta)) * segmentMultiplier[1], + x = Math.abs(distance * Math.cos(theta)) * segmentMultiplier[0]; + return { x:fromPoint.x + x, y:fromPoint.y + y }; + }, + /** + * calculates a perpendicular to the line fromPoint->toPoint, that passes through toPoint and is 'length' long. + * @param fromPoint + * @param toPoint + * @param length + */ + perpendicularLineTo : function(fromPoint, toPoint, length) { + var m = jsPlumbUtil.gradient(fromPoint, toPoint), + theta2 = Math.atan(-1 / m), + y = length / 2 * Math.sin(theta2), + x = length / 2 * Math.cos(theta2); + return [{x:toPoint.x + x, y:toPoint.y + y}, {x:toPoint.x - x, y:toPoint.y - y}]; + }, + findWithFunction : function(a, f) { + if (a) + for (var i = 0; i < a.length; i++) if (f(a[i])) return i; + return -1; + }, + indexOf : function(l, v) { + return jsPlumbUtil.findWithFunction(l, function(_v) { return _v == v; }); + }, + removeWithFunction : function(a, f) { + var idx = jsPlumbUtil.findWithFunction(a, f); + if (idx > -1) a.splice(idx, 1); + return idx != -1; + }, + remove : function(l, v) { + var idx = jsPlumbUtil.indexOf(l, v); + if (idx > -1) l.splice(idx, 1); + return idx != -1; + }, + // TODO support insert index + addWithFunction : function(list, item, hashFunction) { + if (jsPlumbUtil.findWithFunction(list, hashFunction) == -1) list.push(item); + }, + addToList : function(map, key, value) { + var l = map[key]; + if (l == null) { + l = [], map[key] = l; + } + l.push(value); + return l; + }, + /** + * EventGenerator + * Superclass for objects that generate events - jsPlumb extends this, as does jsPlumbUIComponent, which all the UI elements extend. + */ + EventGenerator : function() { + var _listeners = {}, self = this; + + // this is a list of events that should re-throw any errors that occur during their dispatch. as of 1.3.0 this is private to + // jsPlumb, but it seems feasible that people might want to manipulate this list. the thinking is that we don't want event + // listeners to bring down jsPlumb - or do we. i can't make up my mind about this, but i know i want to hear about it if the "ready" + // event fails, because then my page has most likely not initialised. so i have this halfway-house solution. it will be interesting + // to hear what other people think. + var eventsToDieOn = [ "ready" ]; + + /* + * Binds a listener to an event. + * + * Parameters: + * event - name of the event to bind to. + * listener - function to execute. + */ + this.bind = function(event, listener) { + jsPlumbUtil.addToList(_listeners, event, listener); + return self; + }; + /* + * Fires an update for the given event. + * + * Parameters: + * event - event to fire + * value - value to pass to the event listener(s). + * originalEvent - the original event from the browser + */ + this.fire = function(event, value, originalEvent) { + if (_listeners[event]) { + for ( var i = 0; i < _listeners[event].length; i++) { + // doing it this way rather than catching and then possibly re-throwing means that an error propagated by this + // method will have the whole call stack available in the debugger. + if (jsPlumbUtil.findWithFunction(eventsToDieOn, function(e) { return e === event}) != -1) + _listeners[event][i](value, originalEvent); + else { + // for events we don't want to die on, catch and log. + try { + _listeners[event][i](value, originalEvent); + } catch (e) { + jsPlumbUtil.log("jsPlumb: fire failed for event " + event + " : " + e); + } + } + } + } + return self; + }; + /* + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + this.unbind = function(event) { + if (event) + delete _listeners[event]; + else { + _listeners = {}; + } + return self; + }; + + this.getListener = function(forEvent) { + return _listeners[forEvent]; + }; + }, + logEnabled : true, + log : function() { + if (jsPlumbUtil.logEnabled && typeof console != "undefined") { + try { + var msg = arguments[arguments.length - 1]; + console.log(msg); + } + catch (e) {} + } + }, + group : function(g) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.group(g); }, + groupEnd : function(g) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.groupEnd(g); }, + time : function(t) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.time(t); }, + timeEnd : function(t) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.timeEnd(t); } +}; \ No newline at end of file diff --git a/archive/1.3.12/mootools.jsPlumb-1.3.12-RC1.js b/archive/1.3.12/mootools.jsPlumb-1.3.12-RC1.js new file mode 100644 index 000000000..151fe955a --- /dev/null +++ b/archive/1.3.12/mootools.jsPlumb-1.3.12-RC1.js @@ -0,0 +1,436 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.12 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the MooTools adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function(now) { + this.parent(now); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event, originalEvent) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr, originalEvent); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, _scope, value) { + var l = list[_scope]; + if (!l) { + l = []; + list[_scope] = l; + } + l.push(value); + }; + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + var _getElementObject = function(el) { + return $(el); + }; + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el) + try { + if (el.className.constructor == SVGAnimatedString) { + jsPlumbUtil.svg.addClass(el, clazz); + } + else el.addClass(clazz); + } + catch (e) { + // SVGAnimatedString not supported; no problem. + el.addClass(clazz); + } + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + m.start(properties); + }, + + appendElement : function(child, parent) { + _getElementObject(parent).grab(child); + }, + + bind : function(el, event, callback) { + el = _getElementObject(el); + el.addEvent(event, callback); + }, + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.event.clientX, eventObject.event.clientY]; + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el), + drags = _draggablesById[id]; + return drags[0].scope; + }, + + getDropEvent : function(args) { + return args[2]; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getDOMElement : function(el) { + // MooTools just decorates the DOM elements. so we have either an ID or an Element here. + return typeof(el) == "String" ? document.getElementById(el) : el; + }, + + getElementObject : _getElementObject, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + getOriginalEvent : function(e) { + return e.event; + }, + + getPageXY : function(eventObject) { + return [eventObject.event.pageX, eventObject.event.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).getParent(); + }, + + getScrollLeft : function(el) { + return null; + }, + + getScrollTop : function(el) { + return null; + }, + + getSelector : function(spec) { + return $$(spec); + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e != null ? e.tagName : null; + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[0], + p = jsPlumb.CurrentLibrary.getElementObject(ui).getPosition(); + return { left:p.x, top:p.y }; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options, isPlumbedComponent) { + var id = jsPlumb.getId(el); + var drag = _draggablesById[id]; + if (!drag) { + var originalZIndex = 0, + originalCursor = null, + dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + + options['onStart'] = jsPlumb.wrap(options['onStart'], function() { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + drag.originalZIndex = originalZIndex; + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + }); + + // DROPPABLES - only relevant if this is a plumbed component, ie. not just the result of the user making some DOM element + // draggable. this is the only library adapter that has to care about this parameter. + var scope = "" + (options["scope"] || jsPlumb.Defaults.Scope), + filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }, + droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + + if (isPlumbedComponent) { + + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr, event) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop', event); + } + }; + } + else + options["droppables"] = []; + + drag = new Drag.Move(el, options); + drag.scope = scope; + drag.originalZIndex = originalZIndex; + _add(_draggablesById, el.get("id"), drag); + // again, only keep a record of this for scope stuff if this is a plumbed component (an endpoint) + if (isPlumbedComponent) { + _add(_draggablesByScope, scope, drag); + } + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + initDroppable : function(el, options, isPlumbedComponent, isPermanent) { + var scope = options["scope"]; + _add(_droppables, scope, el); + var id = jsPlumb.getId(el); + + el.setAttribute("_isPermanentDroppable", isPermanent); // we use this to clear out droppables on drag complete. + _droppableOptions[id] = options; + _droppableScopesById[id] = scope; + var filterFunc = function(entry) { return entry.element != el; }, + draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + isAlreadyDraggable : function(el) { + return _draggablesById[jsPlumb.getId(el)] != null; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + return (typeof Drag != undefined && typeof Drag.Move != undefined); + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el.className.constructor == SVGAnimatedString) { + jsPlumbUtil.svg.removeClass(el, clazz); + } + else el.removeClass(clazz); + } + catch (e) { + // SVGAnimatedString not supported; no problem. + el.removeClass(clazz); + } + }, + + removeElement : function(element, parent) { + var el = _getElementObject(element); + if (el) el.dispose(); // ?? + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + setDragScope : function(el, scope) { + var drag = _draggablesById[el.get("id")]; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + drag[0].droppables = droppables; + }, + + setOffset : function(el, o) { + _getElementObject(el).setPosition({x:o.left, y:o.top}); + }, + + stopDrag : function() { + for (var i in _draggablesById) { + for (var j = 0; j < _draggablesById[i].length; j++) { + var d = _draggablesById[i][j]; + d.stop(); + if (d.originalZIndex != 0) + d.element.setStyle("z-index", d.originalZIndex); + } + } + }, + + /** + * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself. + * the other libraries do not. yui, in fact, cannot even pass an original event. we have to pull out stuff + * from the originalEvent to put in an options object for YUI. + * @param el + * @param event + * @param originalEvent + */ + trigger : function(el, event, originalEvent) { + originalEvent.stopPropagation(); + _getElementObject(el).fireEvent(event, originalEvent); + }, + + unbind : function(el, event, callback) { + el = _getElementObject(el); + el.removeEvent(event, callback); + } + }; + + window.addEvent('domready', jsPlumb.init); +})(); diff --git a/build/1.3.12/tests/android-svg.html b/archive/1.3.12/tests/android-svg.html similarity index 100% rename from build/1.3.12/tests/android-svg.html rename to archive/1.3.12/tests/android-svg.html diff --git a/build/1.3.12/tests/loadTestHarness.html b/archive/1.3.12/tests/loadTestHarness.html similarity index 100% rename from build/1.3.12/tests/loadTestHarness.html rename to archive/1.3.12/tests/loadTestHarness.html diff --git a/build/1.3.12/tests/qunit-all.html b/archive/1.3.12/tests/qunit-all.html similarity index 100% rename from build/1.3.12/tests/qunit-all.html rename to archive/1.3.12/tests/qunit-all.html diff --git a/build/1.3.12/tests/qunit-canvas-jquery-instance.html b/archive/1.3.12/tests/qunit-canvas-jquery-instance.html similarity index 100% rename from build/1.3.12/tests/qunit-canvas-jquery-instance.html rename to archive/1.3.12/tests/qunit-canvas-jquery-instance.html diff --git a/build/1.3.12/tests/qunit-canvas-jquery.html b/archive/1.3.12/tests/qunit-canvas-jquery.html similarity index 100% rename from build/1.3.12/tests/qunit-canvas-jquery.html rename to archive/1.3.12/tests/qunit-canvas-jquery.html diff --git a/build/1.3.12/tests/qunit-canvas-mootools.html b/archive/1.3.12/tests/qunit-canvas-mootools.html similarity index 100% rename from build/1.3.12/tests/qunit-canvas-mootools.html rename to archive/1.3.12/tests/qunit-canvas-mootools.html diff --git a/build/1.3.12/tests/qunit-svg-jquery-instance.html b/archive/1.3.12/tests/qunit-svg-jquery-instance.html similarity index 100% rename from build/1.3.12/tests/qunit-svg-jquery-instance.html rename to archive/1.3.12/tests/qunit-svg-jquery-instance.html diff --git a/build/1.3.12/tests/qunit-svg-jquery.html b/archive/1.3.12/tests/qunit-svg-jquery.html similarity index 100% rename from build/1.3.12/tests/qunit-svg-jquery.html rename to archive/1.3.12/tests/qunit-svg-jquery.html diff --git a/build/1.3.12/tests/qunit-vml-jquery-instance.html b/archive/1.3.12/tests/qunit-vml-jquery-instance.html similarity index 100% rename from build/1.3.12/tests/qunit-vml-jquery-instance.html rename to archive/1.3.12/tests/qunit-vml-jquery-instance.html diff --git a/build/1.3.12/tests/qunit-vml-jquery.html b/archive/1.3.12/tests/qunit-vml-jquery.html similarity index 100% rename from build/1.3.12/tests/qunit-vml-jquery.html rename to archive/1.3.12/tests/qunit-vml-jquery.html diff --git a/build/1.3.12/tests/qunit.css b/archive/1.3.12/tests/qunit.css similarity index 100% rename from build/1.3.12/tests/qunit.css rename to archive/1.3.12/tests/qunit.css diff --git a/archive/1.3.12/yui.jsPlumb-1.3.12-RC1.js b/archive/1.3.12/yui.jsPlumb-1.3.12-RC1.js new file mode 100644 index 000000000..b6e893e86 --- /dev/null +++ b/archive/1.3.12/yui.jsPlumb-1.3.12-RC1.js @@ -0,0 +1,384 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.12 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the YUI3 adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +/** + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getOriginalEvent gets the original browser event from some wrapper event. + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + */ +(function() { + + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function( v, b, s ) { + for( var i = +b || 0, l = this.length; i < l; i++ ) { + if( this[i]===v || s && this[i]==v ) { return i; } + } + return -1; + }; + } + + var Y; + + YUI().use('node', 'dd', 'anim', 'node-event-simulate', function(_Y) { + Y = _Y; + Y.on("domready", function() { jsPlumb.init(); }); + }); + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }, + ddEvents = [ "drag:mouseDown", "drag:afterMouseDown", "drag:mouseup", + "drag:align", "drag:removeHandle", "drag:addHandle", "drag:removeInvalid", "drag:addInvalid", + "drag:start", "drag:end", "drag:drag", "drag:over", "drag:enter", + "drag:exit", "drag:drophit", "drag:dropmiss", "drop:over", "drop:enter", "drop:exit", "drop:hit" + ], + animEvents = [ "tween" ], + /** + * helper function to curry callbacks for some element. + */ + _wrapper = function(fn) { + return function() { + try { + return fn.apply(this, arguments); + } + catch (e) { } + }; + }, + /** + * extracts options from the given options object, leaving out event handlers. + */ + _getDDOptions = function(options) { + var o = {}; + for (var i in options) if (ddEvents.indexOf(i) == -1) o[i] = options[i]; + return o; + }, + /** + * attaches all event handlers found in options to the given dragdrop object, and registering + * the given el as the element of interest. + */ + _attachListeners = function(dd, options, eventList) { + for (var ev in options) { + if (eventList.indexOf(ev) != -1) { + var w = _wrapper(options[ev]); + dd.on(ev, w); + } + } + }, + _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}, + _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }, + _lastDragObject = null, + _extend = function(o1, o2) { + for (var i in o2) + o1[i] = o2[i]; + return o1; + }, + _getAttribute = function(el, attributeId) { + return el.getAttribute(attributeId); + }, + _getElementObject = function(el) { + if (el == null) return null; + var eee = null; + eee = typeof el == 'string' ? Y.one('#' + el) : el._node ? el : Y.one(el); + return eee; + }; + + jsPlumb.CurrentLibrary = { + + addClass : function(el, clazz) { + jsPlumb.CurrentLibrary.getElementObject(el).addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + var o = _extend({node:el, to:properties}, options), + id = _getAttribute(el, "id"); + o["tween"] = jsPlumb.wrap(properties["tween"], function() { + // TODO should use a current instance. + jsPlumb.repaint(id); + }); + var a = new Y.Anim(o); + _attachListeners(a, o, animEvents); + a.run(); + }, + + appendElement : function(child, parent) { + _getElementObject (parent).append(child); + }, + + /** + * event binding wrapper. + */ + bind : function(el, event, callback) { + _getElementObject(el).on(event, callback); + }, + + dragEvents : { + "start":"drag:start", "stop":"drag:end", "drag":"drag:drag", "step":"step", + "over":"drop:enter", "out":"drop:exit", "drop":"drop:hit" + }, + + extend : _extend, + + getAttribute : _getAttribute, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + // this is a workaround for the unfortunate fact that in YUI3, the 'drop:exit' event does + // not contain a reference to the drag that just exited. single-threaded js to the + // rescue: we'll just keep it for ourselves. + if (eventArgs[0].drag) _lastDragObject = eventArgs[0].drag.el; + return _lastDragObject; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el), + dd = _draggablesById[id]; + return dd.scope; + }, + + getDropEvent : function(args) { + return args[0]; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getDOMElement : function(el) { + if (typeof(el) == "String") + return document.getElementById(el); + else if (el._node) + return el._node; + else return el; + }, + + getElementObject : _getElementObject, + + getOffset : function(el) { + var o = Y.DOM.getXY(el._node); + return {left:o[0], top:o[1]}; + }, + + getOriginalEvent : function(e) { + return e._event; + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).get("parentNode"); + }, + + getScrollLeft : function(el) { + return 0; + }, + + getScrollTop : function(el) { + return 0; + }, + + getSelector : function(spec) { + var s = Y.all(spec); + return s && s ._nodes ? s._nodes : []; + }, + + getSize : function(el) { + return [ el._node.offsetWidth, el._node.offsetHeight ]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e != null && e._node != null ? e._node.tagName : null; + }, + + getUIPosition : function(args) { + var n = args[0].currentTarget.el._node, + o = Y.DOM.getXY(n); + return {left:o[0], top:o[1]}; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options, isPlumbedComponent) { + var _opts = _getDDOptions(options), + id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drag(_opts); + dd.el = el; + + if (isPlumbedComponent) { + var scope = options['scope'] || jsPlumb.Defaults.Scope; + dd.scope = scope; + _add(_draggablesByScope, scope, dd); + } + + _draggablesById[id] = dd; + + _attachListeners(dd, options, ddEvents); + }, + + initDroppable : function(el, options) { + var _opts = _getDDOptions(options), + id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drop(_opts); + + _droppableOptions[id] = options; + + options = _extend({}, options); + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _droppableScopesById[id] = scope; + + options["drop:enter"] = jsPlumb.wrap(options["drop:enter"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, true); + }, true); + options["drop:exit"] = jsPlumb.wrap(options["drop:exit"], function(e) { + _checkHover(el, false); + }); + options["drop:hit"] = jsPlumb.wrap(options["drop:hit"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, false); + }, true); + + _attachListeners(dd, options, ddEvents); + }, + + isAlreadyDraggable : function(el) { + el = _getElementObject(el); + return el.hasClass("yui3-dd-draggable"); + }, + + isDragSupported : function(el) { return true; }, + isDropSupported : function(el) { return true; }, + removeClass : function(el, clazz) { + jsPlumb.CurrentLibrary.getElementObject(el).removeClass(clazz); + }, + removeElement : function(el) { _getElementObject(el).remove(); }, + + setAttribute : function(el, attributeName, attributeValue) { + el.setAttribute(attributeName, attributeValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + var id = jsPlumb.getId(el), + dd = _draggablesById[id]; + if (dd) dd.set("lock", !draggable); + }, + + setDragScope : function(el, scope) { + var id = jsPlumb.getId(el), + dd = _draggablesById[id]; + if (dd) dd.scope = scope; + }, + + setOffset : function(el, o) { + el = _getElementObject(el); + el.set("top", o.top); + el.set("left", o.left); + }, + + stopDrag : function() { + Y.DD.DDM.stopDrag(); + }, + + trigger : function(el, event, originalEvent) { + originalEvent.stopPropagation(); + _getElementObject(el).simulate(event, { + pageX:originalEvent.pageX, + pageY:originalEvent.pageY, + clientX:originalEvent.clientX, + clientY:originalEvent.clientY + }); + }, + + /** + * event unbinding wrapper. + */ + unbind : function(el, event, callback) { + _getElementObject(el).detach(event, callback); + } + }; +})(); \ No newline at end of file diff --git a/build/1.3.13/demo/apidocs/files/jquery-jsPlumb-1-3-13-all-js.html b/archive/1.3.13/demo/apidocs/files/jquery-jsPlumb-1-3-13-all-js.html similarity index 100% rename from build/1.3.13/demo/apidocs/files/jquery-jsPlumb-1-3-13-all-js.html rename to archive/1.3.13/demo/apidocs/files/jquery-jsPlumb-1-3-13-all-js.html diff --git a/build/1.3.13/demo/apidocs/index.html b/archive/1.3.13/demo/apidocs/index.html similarity index 100% rename from build/1.3.13/demo/apidocs/index.html rename to archive/1.3.13/demo/apidocs/index.html diff --git a/build/1.3.13/demo/apidocs/index/Classes.html b/archive/1.3.13/demo/apidocs/index/Classes.html similarity index 100% rename from build/1.3.13/demo/apidocs/index/Classes.html rename to archive/1.3.13/demo/apidocs/index/Classes.html diff --git a/build/1.3.13/demo/apidocs/index/Files.html b/archive/1.3.13/demo/apidocs/index/Files.html similarity index 100% rename from build/1.3.13/demo/apidocs/index/Files.html rename to archive/1.3.13/demo/apidocs/index/Files.html diff --git a/build/1.3.13/demo/apidocs/index/Functions.html b/archive/1.3.13/demo/apidocs/index/Functions.html similarity index 100% rename from build/1.3.13/demo/apidocs/index/Functions.html rename to archive/1.3.13/demo/apidocs/index/Functions.html diff --git a/build/1.3.13/demo/apidocs/index/Functions2.html b/archive/1.3.13/demo/apidocs/index/Functions2.html similarity index 100% rename from build/1.3.13/demo/apidocs/index/Functions2.html rename to archive/1.3.13/demo/apidocs/index/Functions2.html diff --git a/build/1.3.13/demo/apidocs/index/General.html b/archive/1.3.13/demo/apidocs/index/General.html similarity index 100% rename from build/1.3.13/demo/apidocs/index/General.html rename to archive/1.3.13/demo/apidocs/index/General.html diff --git a/build/1.3.13/demo/apidocs/index/General2.html b/archive/1.3.13/demo/apidocs/index/General2.html similarity index 100% rename from build/1.3.13/demo/apidocs/index/General2.html rename to archive/1.3.13/demo/apidocs/index/General2.html diff --git a/build/1.3.13/demo/apidocs/index/General3.html b/archive/1.3.13/demo/apidocs/index/General3.html similarity index 100% rename from build/1.3.13/demo/apidocs/index/General3.html rename to archive/1.3.13/demo/apidocs/index/General3.html diff --git a/build/1.3.13/demo/apidocs/index/Properties.html b/archive/1.3.13/demo/apidocs/index/Properties.html similarity index 100% rename from build/1.3.13/demo/apidocs/index/Properties.html rename to archive/1.3.13/demo/apidocs/index/Properties.html diff --git a/build/1.3.13/demo/apidocs/javascript/main.js b/archive/1.3.13/demo/apidocs/javascript/main.js similarity index 100% rename from build/1.3.13/demo/apidocs/javascript/main.js rename to archive/1.3.13/demo/apidocs/javascript/main.js diff --git a/build/1.3.13/demo/apidocs/javascript/prettify.js b/archive/1.3.13/demo/apidocs/javascript/prettify.js similarity index 100% rename from build/1.3.13/demo/apidocs/javascript/prettify.js rename to archive/1.3.13/demo/apidocs/javascript/prettify.js diff --git a/build/1.3.13/demo/apidocs/javascript/searchdata.js b/archive/1.3.13/demo/apidocs/javascript/searchdata.js similarity index 100% rename from build/1.3.13/demo/apidocs/javascript/searchdata.js rename to archive/1.3.13/demo/apidocs/javascript/searchdata.js diff --git a/build/1.3.13/demo/apidocs/search/ClassesC.html b/archive/1.3.13/demo/apidocs/search/ClassesC.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/ClassesC.html rename to archive/1.3.13/demo/apidocs/search/ClassesC.html diff --git a/build/1.3.13/demo/apidocs/search/ClassesE.html b/archive/1.3.13/demo/apidocs/search/ClassesE.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/ClassesE.html rename to archive/1.3.13/demo/apidocs/search/ClassesE.html diff --git a/build/1.3.13/demo/apidocs/search/ClassesO.html b/archive/1.3.13/demo/apidocs/search/ClassesO.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/ClassesO.html rename to archive/1.3.13/demo/apidocs/search/ClassesO.html diff --git a/build/1.3.13/demo/apidocs/search/FilesJ.html b/archive/1.3.13/demo/apidocs/search/FilesJ.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/FilesJ.html rename to archive/1.3.13/demo/apidocs/search/FilesJ.html diff --git a/build/1.3.13/demo/apidocs/search/FunctionsA.html b/archive/1.3.13/demo/apidocs/search/FunctionsA.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/FunctionsA.html rename to archive/1.3.13/demo/apidocs/search/FunctionsA.html diff --git a/build/1.3.13/demo/apidocs/search/FunctionsB.html b/archive/1.3.13/demo/apidocs/search/FunctionsB.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/FunctionsB.html rename to archive/1.3.13/demo/apidocs/search/FunctionsB.html diff --git a/build/1.3.13/demo/apidocs/search/FunctionsC.html b/archive/1.3.13/demo/apidocs/search/FunctionsC.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/FunctionsC.html rename to archive/1.3.13/demo/apidocs/search/FunctionsC.html diff --git a/build/1.3.13/demo/apidocs/search/FunctionsD.html b/archive/1.3.13/demo/apidocs/search/FunctionsD.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/FunctionsD.html rename to archive/1.3.13/demo/apidocs/search/FunctionsD.html diff --git a/build/1.3.13/demo/apidocs/search/FunctionsE.html b/archive/1.3.13/demo/apidocs/search/FunctionsE.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/FunctionsE.html rename to archive/1.3.13/demo/apidocs/search/FunctionsE.html diff --git a/build/1.3.13/demo/apidocs/search/FunctionsG.html b/archive/1.3.13/demo/apidocs/search/FunctionsG.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/FunctionsG.html rename to archive/1.3.13/demo/apidocs/search/FunctionsG.html diff --git a/build/1.3.13/demo/apidocs/search/FunctionsH.html b/archive/1.3.13/demo/apidocs/search/FunctionsH.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/FunctionsH.html rename to archive/1.3.13/demo/apidocs/search/FunctionsH.html diff --git a/build/1.3.13/demo/apidocs/search/FunctionsI.html b/archive/1.3.13/demo/apidocs/search/FunctionsI.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/FunctionsI.html rename to archive/1.3.13/demo/apidocs/search/FunctionsI.html diff --git a/build/1.3.13/demo/apidocs/search/FunctionsM.html b/archive/1.3.13/demo/apidocs/search/FunctionsM.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/FunctionsM.html rename to archive/1.3.13/demo/apidocs/search/FunctionsM.html diff --git a/build/1.3.13/demo/apidocs/search/FunctionsP.html b/archive/1.3.13/demo/apidocs/search/FunctionsP.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/FunctionsP.html rename to archive/1.3.13/demo/apidocs/search/FunctionsP.html diff --git a/build/1.3.13/demo/apidocs/search/FunctionsR.html b/archive/1.3.13/demo/apidocs/search/FunctionsR.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/FunctionsR.html rename to archive/1.3.13/demo/apidocs/search/FunctionsR.html diff --git a/build/1.3.13/demo/apidocs/search/FunctionsS.html b/archive/1.3.13/demo/apidocs/search/FunctionsS.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/FunctionsS.html rename to archive/1.3.13/demo/apidocs/search/FunctionsS.html diff --git a/build/1.3.13/demo/apidocs/search/FunctionsT.html b/archive/1.3.13/demo/apidocs/search/FunctionsT.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/FunctionsT.html rename to archive/1.3.13/demo/apidocs/search/FunctionsT.html diff --git a/build/1.3.13/demo/apidocs/search/FunctionsU.html b/archive/1.3.13/demo/apidocs/search/FunctionsU.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/FunctionsU.html rename to archive/1.3.13/demo/apidocs/search/FunctionsU.html diff --git a/build/1.3.13/demo/apidocs/search/GeneralA.html b/archive/1.3.13/demo/apidocs/search/GeneralA.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/GeneralA.html rename to archive/1.3.13/demo/apidocs/search/GeneralA.html diff --git a/build/1.3.13/demo/apidocs/search/GeneralB.html b/archive/1.3.13/demo/apidocs/search/GeneralB.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/GeneralB.html rename to archive/1.3.13/demo/apidocs/search/GeneralB.html diff --git a/build/1.3.13/demo/apidocs/search/GeneralC.html b/archive/1.3.13/demo/apidocs/search/GeneralC.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/GeneralC.html rename to archive/1.3.13/demo/apidocs/search/GeneralC.html diff --git a/build/1.3.13/demo/apidocs/search/GeneralD.html b/archive/1.3.13/demo/apidocs/search/GeneralD.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/GeneralD.html rename to archive/1.3.13/demo/apidocs/search/GeneralD.html diff --git a/build/1.3.13/demo/apidocs/search/GeneralE.html b/archive/1.3.13/demo/apidocs/search/GeneralE.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/GeneralE.html rename to archive/1.3.13/demo/apidocs/search/GeneralE.html diff --git a/build/1.3.13/demo/apidocs/search/GeneralF.html b/archive/1.3.13/demo/apidocs/search/GeneralF.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/GeneralF.html rename to archive/1.3.13/demo/apidocs/search/GeneralF.html diff --git a/build/1.3.13/demo/apidocs/search/GeneralG.html b/archive/1.3.13/demo/apidocs/search/GeneralG.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/GeneralG.html rename to archive/1.3.13/demo/apidocs/search/GeneralG.html diff --git a/build/1.3.13/demo/apidocs/search/GeneralH.html b/archive/1.3.13/demo/apidocs/search/GeneralH.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/GeneralH.html rename to archive/1.3.13/demo/apidocs/search/GeneralH.html diff --git a/build/1.3.13/demo/apidocs/search/GeneralI.html b/archive/1.3.13/demo/apidocs/search/GeneralI.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/GeneralI.html rename to archive/1.3.13/demo/apidocs/search/GeneralI.html diff --git a/build/1.3.13/demo/apidocs/search/GeneralJ.html b/archive/1.3.13/demo/apidocs/search/GeneralJ.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/GeneralJ.html rename to archive/1.3.13/demo/apidocs/search/GeneralJ.html diff --git a/build/1.3.13/demo/apidocs/search/GeneralL.html b/archive/1.3.13/demo/apidocs/search/GeneralL.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/GeneralL.html rename to archive/1.3.13/demo/apidocs/search/GeneralL.html diff --git a/build/1.3.13/demo/apidocs/search/GeneralM.html b/archive/1.3.13/demo/apidocs/search/GeneralM.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/GeneralM.html rename to archive/1.3.13/demo/apidocs/search/GeneralM.html diff --git a/build/1.3.13/demo/apidocs/search/GeneralO.html b/archive/1.3.13/demo/apidocs/search/GeneralO.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/GeneralO.html rename to archive/1.3.13/demo/apidocs/search/GeneralO.html diff --git a/build/1.3.13/demo/apidocs/search/GeneralP.html b/archive/1.3.13/demo/apidocs/search/GeneralP.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/GeneralP.html rename to archive/1.3.13/demo/apidocs/search/GeneralP.html diff --git a/build/1.3.13/demo/apidocs/search/GeneralR.html b/archive/1.3.13/demo/apidocs/search/GeneralR.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/GeneralR.html rename to archive/1.3.13/demo/apidocs/search/GeneralR.html diff --git a/build/1.3.13/demo/apidocs/search/GeneralS.html b/archive/1.3.13/demo/apidocs/search/GeneralS.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/GeneralS.html rename to archive/1.3.13/demo/apidocs/search/GeneralS.html diff --git a/build/1.3.13/demo/apidocs/search/GeneralT.html b/archive/1.3.13/demo/apidocs/search/GeneralT.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/GeneralT.html rename to archive/1.3.13/demo/apidocs/search/GeneralT.html diff --git a/build/1.3.13/demo/apidocs/search/GeneralU.html b/archive/1.3.13/demo/apidocs/search/GeneralU.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/GeneralU.html rename to archive/1.3.13/demo/apidocs/search/GeneralU.html diff --git a/build/1.3.13/demo/apidocs/search/NoResults.html b/archive/1.3.13/demo/apidocs/search/NoResults.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/NoResults.html rename to archive/1.3.13/demo/apidocs/search/NoResults.html diff --git a/build/1.3.13/demo/apidocs/search/PropertiesA.html b/archive/1.3.13/demo/apidocs/search/PropertiesA.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/PropertiesA.html rename to archive/1.3.13/demo/apidocs/search/PropertiesA.html diff --git a/build/1.3.13/demo/apidocs/search/PropertiesB.html b/archive/1.3.13/demo/apidocs/search/PropertiesB.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/PropertiesB.html rename to archive/1.3.13/demo/apidocs/search/PropertiesB.html diff --git a/build/1.3.13/demo/apidocs/search/PropertiesC.html b/archive/1.3.13/demo/apidocs/search/PropertiesC.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/PropertiesC.html rename to archive/1.3.13/demo/apidocs/search/PropertiesC.html diff --git a/build/1.3.13/demo/apidocs/search/PropertiesD.html b/archive/1.3.13/demo/apidocs/search/PropertiesD.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/PropertiesD.html rename to archive/1.3.13/demo/apidocs/search/PropertiesD.html diff --git a/build/1.3.13/demo/apidocs/search/PropertiesE.html b/archive/1.3.13/demo/apidocs/search/PropertiesE.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/PropertiesE.html rename to archive/1.3.13/demo/apidocs/search/PropertiesE.html diff --git a/build/1.3.13/demo/apidocs/search/PropertiesL.html b/archive/1.3.13/demo/apidocs/search/PropertiesL.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/PropertiesL.html rename to archive/1.3.13/demo/apidocs/search/PropertiesL.html diff --git a/build/1.3.13/demo/apidocs/search/PropertiesO.html b/archive/1.3.13/demo/apidocs/search/PropertiesO.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/PropertiesO.html rename to archive/1.3.13/demo/apidocs/search/PropertiesO.html diff --git a/build/1.3.13/demo/apidocs/search/PropertiesP.html b/archive/1.3.13/demo/apidocs/search/PropertiesP.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/PropertiesP.html rename to archive/1.3.13/demo/apidocs/search/PropertiesP.html diff --git a/build/1.3.13/demo/apidocs/search/PropertiesR.html b/archive/1.3.13/demo/apidocs/search/PropertiesR.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/PropertiesR.html rename to archive/1.3.13/demo/apidocs/search/PropertiesR.html diff --git a/build/1.3.13/demo/apidocs/search/PropertiesS.html b/archive/1.3.13/demo/apidocs/search/PropertiesS.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/PropertiesS.html rename to archive/1.3.13/demo/apidocs/search/PropertiesS.html diff --git a/build/1.3.13/demo/apidocs/search/PropertiesT.html b/archive/1.3.13/demo/apidocs/search/PropertiesT.html similarity index 100% rename from build/1.3.13/demo/apidocs/search/PropertiesT.html rename to archive/1.3.13/demo/apidocs/search/PropertiesT.html diff --git a/build/1.3.13/demo/apidocs/styles/main.css b/archive/1.3.13/demo/apidocs/styles/main.css similarity index 100% rename from build/1.3.13/demo/apidocs/styles/main.css rename to archive/1.3.13/demo/apidocs/styles/main.css diff --git a/build/1.3.13/demo/css/anchorDemo.css b/archive/1.3.13/demo/css/anchorDemo.css similarity index 100% rename from build/1.3.13/demo/css/anchorDemo.css rename to archive/1.3.13/demo/css/anchorDemo.css diff --git a/build/1.3.13/demo/css/chartDemo.css b/archive/1.3.13/demo/css/chartDemo.css similarity index 100% rename from build/1.3.13/demo/css/chartDemo.css rename to archive/1.3.13/demo/css/chartDemo.css diff --git a/build/1.3.13/demo/css/demo.css b/archive/1.3.13/demo/css/demo.css similarity index 100% rename from build/1.3.13/demo/css/demo.css rename to archive/1.3.13/demo/css/demo.css diff --git a/build/1.3.13/demo/css/dragAnimDemo.css b/archive/1.3.13/demo/css/dragAnimDemo.css similarity index 100% rename from build/1.3.13/demo/css/dragAnimDemo.css rename to archive/1.3.13/demo/css/dragAnimDemo.css diff --git a/build/1.3.13/demo/css/draggableConnectorsDemo.css b/archive/1.3.13/demo/css/draggableConnectorsDemo.css similarity index 100% rename from build/1.3.13/demo/css/draggableConnectorsDemo.css rename to archive/1.3.13/demo/css/draggableConnectorsDemo.css diff --git a/build/1.3.13/demo/css/dynamicAnchorsDemo.css b/archive/1.3.13/demo/css/dynamicAnchorsDemo.css similarity index 100% rename from build/1.3.13/demo/css/dynamicAnchorsDemo.css rename to archive/1.3.13/demo/css/dynamicAnchorsDemo.css diff --git a/build/1.3.13/demo/css/flowchartDemo.css b/archive/1.3.13/demo/css/flowchartDemo.css similarity index 100% rename from build/1.3.13/demo/css/flowchartDemo.css rename to archive/1.3.13/demo/css/flowchartDemo.css diff --git a/build/1.3.13/demo/css/jsPlumbDemo.css b/archive/1.3.13/demo/css/jsPlumbDemo.css similarity index 100% rename from build/1.3.13/demo/css/jsPlumbDemo.css rename to archive/1.3.13/demo/css/jsPlumbDemo.css diff --git a/build/1.3.13/demo/css/makeTargetDemo.css b/archive/1.3.13/demo/css/makeTargetDemo.css similarity index 100% rename from build/1.3.13/demo/css/makeTargetDemo.css rename to archive/1.3.13/demo/css/makeTargetDemo.css diff --git a/build/1.3.13/demo/css/multipleJsPlumbDemo.css b/archive/1.3.13/demo/css/multipleJsPlumbDemo.css similarity index 100% rename from build/1.3.13/demo/css/multipleJsPlumbDemo.css rename to archive/1.3.13/demo/css/multipleJsPlumbDemo.css diff --git a/build/1.3.13/demo/css/perimeterAnchorsDemo.css b/archive/1.3.13/demo/css/perimeterAnchorsDemo.css similarity index 100% rename from build/1.3.13/demo/css/perimeterAnchorsDemo.css rename to archive/1.3.13/demo/css/perimeterAnchorsDemo.css diff --git a/build/1.3.13/demo/css/selectDemo.css b/archive/1.3.13/demo/css/selectDemo.css similarity index 100% rename from build/1.3.13/demo/css/selectDemo.css rename to archive/1.3.13/demo/css/selectDemo.css diff --git a/build/1.3.13/demo/css/stateMachineDemo.css b/archive/1.3.13/demo/css/stateMachineDemo.css similarity index 100% rename from build/1.3.13/demo/css/stateMachineDemo.css rename to archive/1.3.13/demo/css/stateMachineDemo.css diff --git a/build/1.3.13/demo/doc/archive/1.2.6/content.html b/archive/1.3.13/demo/doc/archive/1.2.6/content.html similarity index 100% rename from build/1.3.13/demo/doc/archive/1.2.6/content.html rename to archive/1.3.13/demo/doc/archive/1.2.6/content.html diff --git a/build/1.3.13/demo/doc/archive/1.2.6/index.html b/archive/1.3.13/demo/doc/archive/1.2.6/index.html similarity index 100% rename from build/1.3.13/demo/doc/archive/1.2.6/index.html rename to archive/1.3.13/demo/doc/archive/1.2.6/index.html diff --git a/build/1.3.13/demo/doc/archive/1.2.6/usage.html b/archive/1.3.13/demo/doc/archive/1.2.6/usage.html similarity index 100% rename from build/1.3.13/demo/doc/archive/1.2.6/usage.html rename to archive/1.3.13/demo/doc/archive/1.2.6/usage.html diff --git a/build/1.3.13/demo/doc/archive/1.3.10/content.html b/archive/1.3.13/demo/doc/archive/1.3.10/content.html similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.10/content.html rename to archive/1.3.13/demo/doc/archive/1.3.10/content.html diff --git a/build/1.3.13/demo/doc/archive/1.3.10/index.html b/archive/1.3.13/demo/doc/archive/1.3.10/index.html similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.10/index.html rename to archive/1.3.13/demo/doc/archive/1.3.10/index.html diff --git a/build/1.3.13/demo/doc/archive/1.3.10/jsPlumbDoc.css b/archive/1.3.13/demo/doc/archive/1.3.10/jsPlumbDoc.css similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.10/jsPlumbDoc.css rename to archive/1.3.13/demo/doc/archive/1.3.10/jsPlumbDoc.css diff --git a/build/1.3.13/demo/doc/archive/1.3.10/usage.html b/archive/1.3.13/demo/doc/archive/1.3.10/usage.html similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.10/usage.html rename to archive/1.3.13/demo/doc/archive/1.3.10/usage.html diff --git a/build/1.3.13/demo/doc/archive/1.3.11/content.html b/archive/1.3.13/demo/doc/archive/1.3.11/content.html similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.11/content.html rename to archive/1.3.13/demo/doc/archive/1.3.11/content.html diff --git a/build/1.3.13/demo/doc/archive/1.3.11/index.html b/archive/1.3.13/demo/doc/archive/1.3.11/index.html similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.11/index.html rename to archive/1.3.13/demo/doc/archive/1.3.11/index.html diff --git a/build/1.3.13/demo/doc/archive/1.3.11/jsPlumbDoc.css b/archive/1.3.13/demo/doc/archive/1.3.11/jsPlumbDoc.css similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.11/jsPlumbDoc.css rename to archive/1.3.13/demo/doc/archive/1.3.11/jsPlumbDoc.css diff --git a/build/1.3.13/demo/doc/archive/1.3.11/usage.html b/archive/1.3.13/demo/doc/archive/1.3.11/usage.html similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.11/usage.html rename to archive/1.3.13/demo/doc/archive/1.3.11/usage.html diff --git a/build/1.3.13/demo/doc/archive/1.3.12/content.html b/archive/1.3.13/demo/doc/archive/1.3.12/content.html similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.12/content.html rename to archive/1.3.13/demo/doc/archive/1.3.12/content.html diff --git a/build/1.3.13/demo/doc/archive/1.3.12/index.html b/archive/1.3.13/demo/doc/archive/1.3.12/index.html similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.12/index.html rename to archive/1.3.13/demo/doc/archive/1.3.12/index.html diff --git a/build/1.3.13/demo/doc/archive/1.3.12/jsPlumbDoc.css b/archive/1.3.13/demo/doc/archive/1.3.12/jsPlumbDoc.css similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.12/jsPlumbDoc.css rename to archive/1.3.13/demo/doc/archive/1.3.12/jsPlumbDoc.css diff --git a/build/1.3.13/demo/doc/archive/1.3.12/usage.html b/archive/1.3.13/demo/doc/archive/1.3.12/usage.html similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.12/usage.html rename to archive/1.3.13/demo/doc/archive/1.3.12/usage.html diff --git a/build/1.3.13/demo/doc/archive/1.3.2/content.html b/archive/1.3.13/demo/doc/archive/1.3.2/content.html similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.2/content.html rename to archive/1.3.13/demo/doc/archive/1.3.2/content.html diff --git a/build/1.3.13/demo/doc/archive/1.3.2/index.html b/archive/1.3.13/demo/doc/archive/1.3.2/index.html similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.2/index.html rename to archive/1.3.13/demo/doc/archive/1.3.2/index.html diff --git a/build/1.3.13/demo/doc/archive/1.3.2/usage.html b/archive/1.3.13/demo/doc/archive/1.3.2/usage.html similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.2/usage.html rename to archive/1.3.13/demo/doc/archive/1.3.2/usage.html diff --git a/build/1.3.13/demo/doc/archive/1.3.3/content.html b/archive/1.3.13/demo/doc/archive/1.3.3/content.html similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.3/content.html rename to archive/1.3.13/demo/doc/archive/1.3.3/content.html diff --git a/build/1.3.13/demo/doc/archive/1.3.3/index.html b/archive/1.3.13/demo/doc/archive/1.3.3/index.html similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.3/index.html rename to archive/1.3.13/demo/doc/archive/1.3.3/index.html diff --git a/build/1.3.13/demo/doc/archive/1.3.3/jsPlumbDoc.css b/archive/1.3.13/demo/doc/archive/1.3.3/jsPlumbDoc.css similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.3/jsPlumbDoc.css rename to archive/1.3.13/demo/doc/archive/1.3.3/jsPlumbDoc.css diff --git a/build/1.3.13/demo/doc/archive/1.3.3/usage.html b/archive/1.3.13/demo/doc/archive/1.3.3/usage.html similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.3/usage.html rename to archive/1.3.13/demo/doc/archive/1.3.3/usage.html diff --git a/build/1.3.13/demo/doc/archive/1.3.4/content.html b/archive/1.3.13/demo/doc/archive/1.3.4/content.html similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.4/content.html rename to archive/1.3.13/demo/doc/archive/1.3.4/content.html diff --git a/build/1.3.13/demo/doc/archive/1.3.4/index.html b/archive/1.3.13/demo/doc/archive/1.3.4/index.html similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.4/index.html rename to archive/1.3.13/demo/doc/archive/1.3.4/index.html diff --git a/build/1.3.13/demo/doc/archive/1.3.4/jsPlumbDoc.css b/archive/1.3.13/demo/doc/archive/1.3.4/jsPlumbDoc.css similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.4/jsPlumbDoc.css rename to archive/1.3.13/demo/doc/archive/1.3.4/jsPlumbDoc.css diff --git a/build/1.3.13/demo/doc/archive/1.3.4/usage.html b/archive/1.3.13/demo/doc/archive/1.3.4/usage.html similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.4/usage.html rename to archive/1.3.13/demo/doc/archive/1.3.4/usage.html diff --git a/build/1.3.13/demo/doc/archive/1.3.5/content.html b/archive/1.3.13/demo/doc/archive/1.3.5/content.html similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.5/content.html rename to archive/1.3.13/demo/doc/archive/1.3.5/content.html diff --git a/build/1.3.13/demo/doc/archive/1.3.5/index.html b/archive/1.3.13/demo/doc/archive/1.3.5/index.html similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.5/index.html rename to archive/1.3.13/demo/doc/archive/1.3.5/index.html diff --git a/build/1.3.13/demo/doc/archive/1.3.5/jsPlumbDoc.css b/archive/1.3.13/demo/doc/archive/1.3.5/jsPlumbDoc.css similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.5/jsPlumbDoc.css rename to archive/1.3.13/demo/doc/archive/1.3.5/jsPlumbDoc.css diff --git a/build/1.3.13/demo/doc/archive/1.3.5/usage.html b/archive/1.3.13/demo/doc/archive/1.3.5/usage.html similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.5/usage.html rename to archive/1.3.13/demo/doc/archive/1.3.5/usage.html diff --git a/build/1.3.13/demo/doc/archive/1.3.6/content.html b/archive/1.3.13/demo/doc/archive/1.3.6/content.html similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.6/content.html rename to archive/1.3.13/demo/doc/archive/1.3.6/content.html diff --git a/build/1.3.13/demo/doc/archive/1.3.6/index.html b/archive/1.3.13/demo/doc/archive/1.3.6/index.html similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.6/index.html rename to archive/1.3.13/demo/doc/archive/1.3.6/index.html diff --git a/build/1.3.13/demo/doc/archive/1.3.6/jsPlumbDoc.css b/archive/1.3.13/demo/doc/archive/1.3.6/jsPlumbDoc.css similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.6/jsPlumbDoc.css rename to archive/1.3.13/demo/doc/archive/1.3.6/jsPlumbDoc.css diff --git a/build/1.3.13/demo/doc/archive/1.3.6/usage.html b/archive/1.3.13/demo/doc/archive/1.3.6/usage.html similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.6/usage.html rename to archive/1.3.13/demo/doc/archive/1.3.6/usage.html diff --git a/build/1.3.13/demo/doc/archive/1.3.7/content.html b/archive/1.3.13/demo/doc/archive/1.3.7/content.html similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.7/content.html rename to archive/1.3.13/demo/doc/archive/1.3.7/content.html diff --git a/build/1.3.13/demo/doc/archive/1.3.7/index.html b/archive/1.3.13/demo/doc/archive/1.3.7/index.html similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.7/index.html rename to archive/1.3.13/demo/doc/archive/1.3.7/index.html diff --git a/build/1.3.13/demo/doc/archive/1.3.7/jsPlumbDoc.css b/archive/1.3.13/demo/doc/archive/1.3.7/jsPlumbDoc.css similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.7/jsPlumbDoc.css rename to archive/1.3.13/demo/doc/archive/1.3.7/jsPlumbDoc.css diff --git a/build/1.3.13/demo/doc/archive/1.3.7/usage.html b/archive/1.3.13/demo/doc/archive/1.3.7/usage.html similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.7/usage.html rename to archive/1.3.13/demo/doc/archive/1.3.7/usage.html diff --git a/build/1.3.13/demo/doc/archive/1.3.8/content.html b/archive/1.3.13/demo/doc/archive/1.3.8/content.html similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.8/content.html rename to archive/1.3.13/demo/doc/archive/1.3.8/content.html diff --git a/build/1.3.13/demo/doc/archive/1.3.8/index.html b/archive/1.3.13/demo/doc/archive/1.3.8/index.html similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.8/index.html rename to archive/1.3.13/demo/doc/archive/1.3.8/index.html diff --git a/build/1.3.13/demo/doc/archive/1.3.8/jsPlumbDoc.css b/archive/1.3.13/demo/doc/archive/1.3.8/jsPlumbDoc.css similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.8/jsPlumbDoc.css rename to archive/1.3.13/demo/doc/archive/1.3.8/jsPlumbDoc.css diff --git a/build/1.3.13/demo/doc/archive/1.3.8/usage.html b/archive/1.3.13/demo/doc/archive/1.3.8/usage.html similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.8/usage.html rename to archive/1.3.13/demo/doc/archive/1.3.8/usage.html diff --git a/build/1.3.13/demo/doc/archive/1.3.9/content.html b/archive/1.3.13/demo/doc/archive/1.3.9/content.html similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.9/content.html rename to archive/1.3.13/demo/doc/archive/1.3.9/content.html diff --git a/build/1.3.13/demo/doc/archive/1.3.9/index.html b/archive/1.3.13/demo/doc/archive/1.3.9/index.html similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.9/index.html rename to archive/1.3.13/demo/doc/archive/1.3.9/index.html diff --git a/build/1.3.13/demo/doc/archive/1.3.9/jsPlumbDoc.css b/archive/1.3.13/demo/doc/archive/1.3.9/jsPlumbDoc.css similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.9/jsPlumbDoc.css rename to archive/1.3.13/demo/doc/archive/1.3.9/jsPlumbDoc.css diff --git a/build/1.3.13/demo/doc/archive/1.3.9/usage.html b/archive/1.3.13/demo/doc/archive/1.3.9/usage.html similarity index 100% rename from build/1.3.13/demo/doc/archive/1.3.9/usage.html rename to archive/1.3.13/demo/doc/archive/1.3.9/usage.html diff --git a/build/1.3.13/demo/doc/content.html b/archive/1.3.13/demo/doc/content.html similarity index 100% rename from build/1.3.13/demo/doc/content.html rename to archive/1.3.13/demo/doc/content.html diff --git a/build/1.3.13/demo/doc/index.html b/archive/1.3.13/demo/doc/index.html similarity index 100% rename from build/1.3.13/demo/doc/index.html rename to archive/1.3.13/demo/doc/index.html diff --git a/build/1.3.13/demo/doc/jsPlumbDoc.css b/archive/1.3.13/demo/doc/jsPlumbDoc.css similarity index 100% rename from build/1.3.13/demo/doc/jsPlumbDoc.css rename to archive/1.3.13/demo/doc/jsPlumbDoc.css diff --git a/build/1.3.13/demo/doc/usage.html b/archive/1.3.13/demo/doc/usage.html similarity index 100% rename from build/1.3.13/demo/doc/usage.html rename to archive/1.3.13/demo/doc/usage.html diff --git a/build/1.3.13/demo/img/bigdot.jpg b/archive/1.3.13/demo/img/bigdot.jpg similarity index 100% rename from build/1.3.13/demo/img/bigdot.jpg rename to archive/1.3.13/demo/img/bigdot.jpg diff --git a/build/1.3.13/demo/img/bigdot.png b/archive/1.3.13/demo/img/bigdot.png similarity index 100% rename from build/1.3.13/demo/img/bigdot.png rename to archive/1.3.13/demo/img/bigdot.png diff --git a/build/1.3.13/demo/img/bigdot.xcf b/archive/1.3.13/demo/img/bigdot.xcf similarity index 100% rename from build/1.3.13/demo/img/bigdot.xcf rename to archive/1.3.13/demo/img/bigdot.xcf diff --git a/build/1.3.13/demo/img/circle.png b/archive/1.3.13/demo/img/circle.png similarity index 100% rename from build/1.3.13/demo/img/circle.png rename to archive/1.3.13/demo/img/circle.png diff --git a/build/1.3.13/demo/img/diamond.png b/archive/1.3.13/demo/img/diamond.png similarity index 100% rename from build/1.3.13/demo/img/diamond.png rename to archive/1.3.13/demo/img/diamond.png diff --git a/build/1.3.13/demo/img/dragging_1.jpg b/archive/1.3.13/demo/img/dragging_1.jpg similarity index 100% rename from build/1.3.13/demo/img/dragging_1.jpg rename to archive/1.3.13/demo/img/dragging_1.jpg diff --git a/build/1.3.13/demo/img/dragging_2.jpg b/archive/1.3.13/demo/img/dragging_2.jpg similarity index 100% rename from build/1.3.13/demo/img/dragging_2.jpg rename to archive/1.3.13/demo/img/dragging_2.jpg diff --git a/build/1.3.13/demo/img/dragging_3.jpg b/archive/1.3.13/demo/img/dragging_3.jpg similarity index 100% rename from build/1.3.13/demo/img/dragging_3.jpg rename to archive/1.3.13/demo/img/dragging_3.jpg diff --git a/build/1.3.13/demo/img/dynamicAnchorBg.jpg b/archive/1.3.13/demo/img/dynamicAnchorBg.jpg similarity index 100% rename from build/1.3.13/demo/img/dynamicAnchorBg.jpg rename to archive/1.3.13/demo/img/dynamicAnchorBg.jpg diff --git a/build/1.3.13/demo/img/ellipse.png b/archive/1.3.13/demo/img/ellipse.png similarity index 100% rename from build/1.3.13/demo/img/ellipse.png rename to archive/1.3.13/demo/img/ellipse.png diff --git a/build/1.3.13/demo/img/endpointTest1.png b/archive/1.3.13/demo/img/endpointTest1.png similarity index 100% rename from build/1.3.13/demo/img/endpointTest1.png rename to archive/1.3.13/demo/img/endpointTest1.png diff --git a/build/1.3.13/demo/img/endpointTest1.xcf b/archive/1.3.13/demo/img/endpointTest1.xcf similarity index 100% rename from build/1.3.13/demo/img/endpointTest1.xcf rename to archive/1.3.13/demo/img/endpointTest1.xcf diff --git a/build/1.3.13/demo/img/index-bg.gif b/archive/1.3.13/demo/img/index-bg.gif similarity index 100% rename from build/1.3.13/demo/img/index-bg.gif rename to archive/1.3.13/demo/img/index-bg.gif diff --git a/build/1.3.13/demo/img/issue4_final.jpg b/archive/1.3.13/demo/img/issue4_final.jpg similarity index 100% rename from build/1.3.13/demo/img/issue4_final.jpg rename to archive/1.3.13/demo/img/issue4_final.jpg diff --git a/build/1.3.13/demo/img/littledot.png b/archive/1.3.13/demo/img/littledot.png similarity index 100% rename from build/1.3.13/demo/img/littledot.png rename to archive/1.3.13/demo/img/littledot.png diff --git a/build/1.3.13/demo/img/littledot.xcf b/archive/1.3.13/demo/img/littledot.xcf similarity index 100% rename from build/1.3.13/demo/img/littledot.xcf rename to archive/1.3.13/demo/img/littledot.xcf diff --git a/build/1.3.13/demo/img/pattern.jpg b/archive/1.3.13/demo/img/pattern.jpg similarity index 100% rename from build/1.3.13/demo/img/pattern.jpg rename to archive/1.3.13/demo/img/pattern.jpg diff --git a/build/1.3.13/demo/img/rectangle.png b/archive/1.3.13/demo/img/rectangle.png similarity index 100% rename from build/1.3.13/demo/img/rectangle.png rename to archive/1.3.13/demo/img/rectangle.png diff --git a/build/1.3.13/demo/img/square.png b/archive/1.3.13/demo/img/square.png similarity index 100% rename from build/1.3.13/demo/img/square.png rename to archive/1.3.13/demo/img/square.png diff --git a/build/1.3.13/demo/img/swappedAnchors.jpg b/archive/1.3.13/demo/img/swappedAnchors.jpg similarity index 100% rename from build/1.3.13/demo/img/swappedAnchors.jpg rename to archive/1.3.13/demo/img/swappedAnchors.jpg diff --git a/build/1.3.13/demo/img/triangle.png b/archive/1.3.13/demo/img/triangle.png similarity index 100% rename from build/1.3.13/demo/img/triangle.png rename to archive/1.3.13/demo/img/triangle.png diff --git a/build/1.3.13/demo/img/triangle_90.png b/archive/1.3.13/demo/img/triangle_90.png similarity index 100% rename from build/1.3.13/demo/img/triangle_90.png rename to archive/1.3.13/demo/img/triangle_90.png diff --git a/build/1.3.13/demo/index.html b/archive/1.3.13/demo/index.html similarity index 100% rename from build/1.3.13/demo/index.html rename to archive/1.3.13/demo/index.html diff --git a/build/1.3.13/demo/jquery/anchorDemo.html b/archive/1.3.13/demo/jquery/anchorDemo.html similarity index 100% rename from build/1.3.13/demo/jquery/anchorDemo.html rename to archive/1.3.13/demo/jquery/anchorDemo.html diff --git a/build/1.3.13/demo/jquery/chartDemo.html b/archive/1.3.13/demo/jquery/chartDemo.html similarity index 100% rename from build/1.3.13/demo/jquery/chartDemo.html rename to archive/1.3.13/demo/jquery/chartDemo.html diff --git a/build/1.3.13/demo/jquery/demo.html b/archive/1.3.13/demo/jquery/demo.html similarity index 100% rename from build/1.3.13/demo/jquery/demo.html rename to archive/1.3.13/demo/jquery/demo.html diff --git a/build/1.3.13/demo/jquery/dragAnimDemo.html b/archive/1.3.13/demo/jquery/dragAnimDemo.html similarity index 100% rename from build/1.3.13/demo/jquery/dragAnimDemo.html rename to archive/1.3.13/demo/jquery/dragAnimDemo.html diff --git a/build/1.3.13/demo/jquery/draggableConnectorsDemo.html b/archive/1.3.13/demo/jquery/draggableConnectorsDemo.html similarity index 100% rename from build/1.3.13/demo/jquery/draggableConnectorsDemo.html rename to archive/1.3.13/demo/jquery/draggableConnectorsDemo.html diff --git a/build/1.3.13/demo/jquery/dynamicAnchorsDemo.html b/archive/1.3.13/demo/jquery/dynamicAnchorsDemo.html similarity index 100% rename from build/1.3.13/demo/jquery/dynamicAnchorsDemo.html rename to archive/1.3.13/demo/jquery/dynamicAnchorsDemo.html diff --git a/build/1.3.13/demo/jquery/flowchartConnectorsDemo.html b/archive/1.3.13/demo/jquery/flowchartConnectorsDemo.html similarity index 100% rename from build/1.3.13/demo/jquery/flowchartConnectorsDemo.html rename to archive/1.3.13/demo/jquery/flowchartConnectorsDemo.html diff --git a/build/1.3.13/demo/jquery/loadTest.html b/archive/1.3.13/demo/jquery/loadTest.html similarity index 100% rename from build/1.3.13/demo/jquery/loadTest.html rename to archive/1.3.13/demo/jquery/loadTest.html diff --git a/build/1.3.13/demo/jquery/makeSourceDemo.html b/archive/1.3.13/demo/jquery/makeSourceDemo.html similarity index 100% rename from build/1.3.13/demo/jquery/makeSourceDemo.html rename to archive/1.3.13/demo/jquery/makeSourceDemo.html diff --git a/build/1.3.13/demo/jquery/makeTargetDemo.html b/archive/1.3.13/demo/jquery/makeTargetDemo.html similarity index 100% rename from build/1.3.13/demo/jquery/makeTargetDemo.html rename to archive/1.3.13/demo/jquery/makeTargetDemo.html diff --git a/build/1.3.13/demo/jquery/perimeterAnchorsDemo.html b/archive/1.3.13/demo/jquery/perimeterAnchorsDemo.html similarity index 100% rename from build/1.3.13/demo/jquery/perimeterAnchorsDemo.html rename to archive/1.3.13/demo/jquery/perimeterAnchorsDemo.html diff --git a/build/1.3.13/demo/jquery/stateMachineDemo.html b/archive/1.3.13/demo/jquery/stateMachineDemo.html similarity index 100% rename from build/1.3.13/demo/jquery/stateMachineDemo.html rename to archive/1.3.13/demo/jquery/stateMachineDemo.html diff --git a/build/1.3.13/demo/js/anchorDemo-jquery.js b/archive/1.3.13/demo/js/anchorDemo-jquery.js similarity index 100% rename from build/1.3.13/demo/js/anchorDemo-jquery.js rename to archive/1.3.13/demo/js/anchorDemo-jquery.js diff --git a/build/1.3.13/demo/js/anchorDemo-mootools.js b/archive/1.3.13/demo/js/anchorDemo-mootools.js similarity index 100% rename from build/1.3.13/demo/js/anchorDemo-mootools.js rename to archive/1.3.13/demo/js/anchorDemo-mootools.js diff --git a/build/1.3.13/demo/js/anchorDemo-yui3.js b/archive/1.3.13/demo/js/anchorDemo-yui3.js similarity index 100% rename from build/1.3.13/demo/js/anchorDemo-yui3.js rename to archive/1.3.13/demo/js/anchorDemo-yui3.js diff --git a/build/1.3.13/demo/js/anchorDemo.js b/archive/1.3.13/demo/js/anchorDemo.js similarity index 100% rename from build/1.3.13/demo/js/anchorDemo.js rename to archive/1.3.13/demo/js/anchorDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.0/anchorDemo.js b/archive/1.3.13/demo/js/archive/1.3.0/anchorDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.0/anchorDemo.js rename to archive/1.3.13/demo/js/archive/1.3.0/anchorDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.0/chartDemo.js b/archive/1.3.13/demo/js/archive/1.3.0/chartDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.0/chartDemo.js rename to archive/1.3.13/demo/js/archive/1.3.0/chartDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.0/demo-helper-jquery.js b/archive/1.3.13/demo/js/archive/1.3.0/demo-helper-jquery.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.0/demo-helper-jquery.js rename to archive/1.3.13/demo/js/archive/1.3.0/demo-helper-jquery.js diff --git a/build/1.3.13/demo/js/archive/1.3.0/demo-helper-mootools.js b/archive/1.3.13/demo/js/archive/1.3.0/demo-helper-mootools.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.0/demo-helper-mootools.js rename to archive/1.3.13/demo/js/archive/1.3.0/demo-helper-mootools.js diff --git a/build/1.3.13/demo/js/archive/1.3.0/demo-helper-yui3.js b/archive/1.3.13/demo/js/archive/1.3.0/demo-helper-yui3.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.0/demo-helper-yui3.js rename to archive/1.3.13/demo/js/archive/1.3.0/demo-helper-yui3.js diff --git a/build/1.3.13/demo/js/archive/1.3.0/demo.js b/archive/1.3.13/demo/js/archive/1.3.0/demo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.0/demo.js rename to archive/1.3.13/demo/js/archive/1.3.0/demo.js diff --git a/build/1.3.13/demo/js/archive/1.3.0/dragAnimDemo-jquery.js b/archive/1.3.13/demo/js/archive/1.3.0/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.0/dragAnimDemo-jquery.js rename to archive/1.3.13/demo/js/archive/1.3.0/dragAnimDemo-jquery.js diff --git a/build/1.3.13/demo/js/archive/1.3.0/dragAnimDemo-mootools.js b/archive/1.3.13/demo/js/archive/1.3.0/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.0/dragAnimDemo-mootools.js rename to archive/1.3.13/demo/js/archive/1.3.0/dragAnimDemo-mootools.js diff --git a/build/1.3.13/demo/js/archive/1.3.0/dragAnimDemo-yui3.js b/archive/1.3.13/demo/js/archive/1.3.0/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.0/dragAnimDemo-yui3.js rename to archive/1.3.13/demo/js/archive/1.3.0/dragAnimDemo-yui3.js diff --git a/build/1.3.13/demo/js/archive/1.3.0/dragAnimDemo.js b/archive/1.3.13/demo/js/archive/1.3.0/dragAnimDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.0/dragAnimDemo.js rename to archive/1.3.13/demo/js/archive/1.3.0/dragAnimDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js b/archive/1.3.13/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js rename to archive/1.3.13/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.13/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js b/archive/1.3.13/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js rename to archive/1.3.13/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.13/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js b/archive/1.3.13/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js rename to archive/1.3.13/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.13/demo/js/archive/1.3.0/draggableConnectorsDemo.js b/archive/1.3.13/demo/js/archive/1.3.0/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.0/draggableConnectorsDemo.js rename to archive/1.3.13/demo/js/archive/1.3.0/draggableConnectorsDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.0/dynamicAnchorsDemo.js b/archive/1.3.13/demo/js/archive/1.3.0/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.0/dynamicAnchorsDemo.js rename to archive/1.3.13/demo/js/archive/1.3.0/dynamicAnchorsDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.0/flowchartConnectorsDemo.js b/archive/1.3.13/demo/js/archive/1.3.0/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.0/flowchartConnectorsDemo.js rename to archive/1.3.13/demo/js/archive/1.3.0/flowchartConnectorsDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.1/anchorDemo.js b/archive/1.3.13/demo/js/archive/1.3.1/anchorDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.1/anchorDemo.js rename to archive/1.3.13/demo/js/archive/1.3.1/anchorDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.1/chartDemo.js b/archive/1.3.13/demo/js/archive/1.3.1/chartDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.1/chartDemo.js rename to archive/1.3.13/demo/js/archive/1.3.1/chartDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.1/demo-helper-jquery.js b/archive/1.3.13/demo/js/archive/1.3.1/demo-helper-jquery.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.1/demo-helper-jquery.js rename to archive/1.3.13/demo/js/archive/1.3.1/demo-helper-jquery.js diff --git a/build/1.3.13/demo/js/archive/1.3.1/demo-helper-mootools.js b/archive/1.3.13/demo/js/archive/1.3.1/demo-helper-mootools.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.1/demo-helper-mootools.js rename to archive/1.3.13/demo/js/archive/1.3.1/demo-helper-mootools.js diff --git a/build/1.3.13/demo/js/archive/1.3.1/demo-helper-yui3.js b/archive/1.3.13/demo/js/archive/1.3.1/demo-helper-yui3.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.1/demo-helper-yui3.js rename to archive/1.3.13/demo/js/archive/1.3.1/demo-helper-yui3.js diff --git a/build/1.3.13/demo/js/archive/1.3.1/demo.js b/archive/1.3.13/demo/js/archive/1.3.1/demo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.1/demo.js rename to archive/1.3.13/demo/js/archive/1.3.1/demo.js diff --git a/build/1.3.13/demo/js/archive/1.3.1/dragAnimDemo-jquery.js b/archive/1.3.13/demo/js/archive/1.3.1/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.1/dragAnimDemo-jquery.js rename to archive/1.3.13/demo/js/archive/1.3.1/dragAnimDemo-jquery.js diff --git a/build/1.3.13/demo/js/archive/1.3.1/dragAnimDemo-mootools.js b/archive/1.3.13/demo/js/archive/1.3.1/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.1/dragAnimDemo-mootools.js rename to archive/1.3.13/demo/js/archive/1.3.1/dragAnimDemo-mootools.js diff --git a/build/1.3.13/demo/js/archive/1.3.1/dragAnimDemo-yui3.js b/archive/1.3.13/demo/js/archive/1.3.1/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.1/dragAnimDemo-yui3.js rename to archive/1.3.13/demo/js/archive/1.3.1/dragAnimDemo-yui3.js diff --git a/build/1.3.13/demo/js/archive/1.3.1/dragAnimDemo.js b/archive/1.3.13/demo/js/archive/1.3.1/dragAnimDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.1/dragAnimDemo.js rename to archive/1.3.13/demo/js/archive/1.3.1/dragAnimDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js b/archive/1.3.13/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js rename to archive/1.3.13/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.13/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js b/archive/1.3.13/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js rename to archive/1.3.13/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.13/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js b/archive/1.3.13/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js rename to archive/1.3.13/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.13/demo/js/archive/1.3.1/draggableConnectorsDemo.js b/archive/1.3.13/demo/js/archive/1.3.1/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.1/draggableConnectorsDemo.js rename to archive/1.3.13/demo/js/archive/1.3.1/draggableConnectorsDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.1/dynamicAnchorsDemo.js b/archive/1.3.13/demo/js/archive/1.3.1/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.1/dynamicAnchorsDemo.js rename to archive/1.3.13/demo/js/archive/1.3.1/dynamicAnchorsDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.1/flowchartConnectorsDemo.js b/archive/1.3.13/demo/js/archive/1.3.1/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.1/flowchartConnectorsDemo.js rename to archive/1.3.13/demo/js/archive/1.3.1/flowchartConnectorsDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.2/anchorDemo.js b/archive/1.3.13/demo/js/archive/1.3.2/anchorDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.2/anchorDemo.js rename to archive/1.3.13/demo/js/archive/1.3.2/anchorDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.2/chartDemo.js b/archive/1.3.13/demo/js/archive/1.3.2/chartDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.2/chartDemo.js rename to archive/1.3.13/demo/js/archive/1.3.2/chartDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.2/demo-helper-jquery.js b/archive/1.3.13/demo/js/archive/1.3.2/demo-helper-jquery.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.2/demo-helper-jquery.js rename to archive/1.3.13/demo/js/archive/1.3.2/demo-helper-jquery.js diff --git a/build/1.3.13/demo/js/archive/1.3.2/demo-helper-mootools.js b/archive/1.3.13/demo/js/archive/1.3.2/demo-helper-mootools.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.2/demo-helper-mootools.js rename to archive/1.3.13/demo/js/archive/1.3.2/demo-helper-mootools.js diff --git a/build/1.3.13/demo/js/archive/1.3.2/demo-helper-yui3.js b/archive/1.3.13/demo/js/archive/1.3.2/demo-helper-yui3.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.2/demo-helper-yui3.js rename to archive/1.3.13/demo/js/archive/1.3.2/demo-helper-yui3.js diff --git a/build/1.3.13/demo/js/archive/1.3.2/demo.js b/archive/1.3.13/demo/js/archive/1.3.2/demo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.2/demo.js rename to archive/1.3.13/demo/js/archive/1.3.2/demo.js diff --git a/build/1.3.13/demo/js/archive/1.3.2/dragAnimDemo-jquery.js b/archive/1.3.13/demo/js/archive/1.3.2/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.2/dragAnimDemo-jquery.js rename to archive/1.3.13/demo/js/archive/1.3.2/dragAnimDemo-jquery.js diff --git a/build/1.3.13/demo/js/archive/1.3.2/dragAnimDemo-mootools.js b/archive/1.3.13/demo/js/archive/1.3.2/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.2/dragAnimDemo-mootools.js rename to archive/1.3.13/demo/js/archive/1.3.2/dragAnimDemo-mootools.js diff --git a/build/1.3.13/demo/js/archive/1.3.2/dragAnimDemo-yui3.js b/archive/1.3.13/demo/js/archive/1.3.2/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.2/dragAnimDemo-yui3.js rename to archive/1.3.13/demo/js/archive/1.3.2/dragAnimDemo-yui3.js diff --git a/build/1.3.13/demo/js/archive/1.3.2/dragAnimDemo.js b/archive/1.3.13/demo/js/archive/1.3.2/dragAnimDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.2/dragAnimDemo.js rename to archive/1.3.13/demo/js/archive/1.3.2/dragAnimDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js b/archive/1.3.13/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js rename to archive/1.3.13/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.13/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js b/archive/1.3.13/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js rename to archive/1.3.13/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.13/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js b/archive/1.3.13/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js rename to archive/1.3.13/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.13/demo/js/archive/1.3.2/draggableConnectorsDemo.js b/archive/1.3.13/demo/js/archive/1.3.2/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.2/draggableConnectorsDemo.js rename to archive/1.3.13/demo/js/archive/1.3.2/draggableConnectorsDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.2/dynamicAnchorsDemo.js b/archive/1.3.13/demo/js/archive/1.3.2/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.2/dynamicAnchorsDemo.js rename to archive/1.3.13/demo/js/archive/1.3.2/dynamicAnchorsDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.2/flowchartConnectorsDemo.js b/archive/1.3.13/demo/js/archive/1.3.2/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.2/flowchartConnectorsDemo.js rename to archive/1.3.13/demo/js/archive/1.3.2/flowchartConnectorsDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.3/anchorDemo.js b/archive/1.3.13/demo/js/archive/1.3.3/anchorDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.3/anchorDemo.js rename to archive/1.3.13/demo/js/archive/1.3.3/anchorDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.3/chartDemo.js b/archive/1.3.13/demo/js/archive/1.3.3/chartDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.3/chartDemo.js rename to archive/1.3.13/demo/js/archive/1.3.3/chartDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.3/demo-helper-jquery.js b/archive/1.3.13/demo/js/archive/1.3.3/demo-helper-jquery.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.3/demo-helper-jquery.js rename to archive/1.3.13/demo/js/archive/1.3.3/demo-helper-jquery.js diff --git a/build/1.3.13/demo/js/archive/1.3.3/demo-helper-mootools.js b/archive/1.3.13/demo/js/archive/1.3.3/demo-helper-mootools.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.3/demo-helper-mootools.js rename to archive/1.3.13/demo/js/archive/1.3.3/demo-helper-mootools.js diff --git a/build/1.3.13/demo/js/archive/1.3.3/demo-helper-yui3.js b/archive/1.3.13/demo/js/archive/1.3.3/demo-helper-yui3.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.3/demo-helper-yui3.js rename to archive/1.3.13/demo/js/archive/1.3.3/demo-helper-yui3.js diff --git a/build/1.3.13/demo/js/archive/1.3.3/demo.js b/archive/1.3.13/demo/js/archive/1.3.3/demo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.3/demo.js rename to archive/1.3.13/demo/js/archive/1.3.3/demo.js diff --git a/build/1.3.13/demo/js/archive/1.3.3/dragAnimDemo-jquery.js b/archive/1.3.13/demo/js/archive/1.3.3/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.3/dragAnimDemo-jquery.js rename to archive/1.3.13/demo/js/archive/1.3.3/dragAnimDemo-jquery.js diff --git a/build/1.3.13/demo/js/archive/1.3.3/dragAnimDemo-mootools.js b/archive/1.3.13/demo/js/archive/1.3.3/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.3/dragAnimDemo-mootools.js rename to archive/1.3.13/demo/js/archive/1.3.3/dragAnimDemo-mootools.js diff --git a/build/1.3.13/demo/js/archive/1.3.3/dragAnimDemo-yui3.js b/archive/1.3.13/demo/js/archive/1.3.3/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.3/dragAnimDemo-yui3.js rename to archive/1.3.13/demo/js/archive/1.3.3/dragAnimDemo-yui3.js diff --git a/build/1.3.13/demo/js/archive/1.3.3/dragAnimDemo.js b/archive/1.3.13/demo/js/archive/1.3.3/dragAnimDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.3/dragAnimDemo.js rename to archive/1.3.13/demo/js/archive/1.3.3/dragAnimDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js b/archive/1.3.13/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js rename to archive/1.3.13/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.13/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js b/archive/1.3.13/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js rename to archive/1.3.13/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.13/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js b/archive/1.3.13/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js rename to archive/1.3.13/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.13/demo/js/archive/1.3.3/draggableConnectorsDemo.js b/archive/1.3.13/demo/js/archive/1.3.3/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.3/draggableConnectorsDemo.js rename to archive/1.3.13/demo/js/archive/1.3.3/draggableConnectorsDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.3/dynamicAnchorsDemo.js b/archive/1.3.13/demo/js/archive/1.3.3/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.3/dynamicAnchorsDemo.js rename to archive/1.3.13/demo/js/archive/1.3.3/dynamicAnchorsDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.3/flowchartConnectorsDemo.js b/archive/1.3.13/demo/js/archive/1.3.3/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.3/flowchartConnectorsDemo.js rename to archive/1.3.13/demo/js/archive/1.3.3/flowchartConnectorsDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js b/archive/1.3.13/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js rename to archive/1.3.13/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js diff --git a/build/1.3.13/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all.js b/archive/1.3.13/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all.js rename to archive/1.3.13/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all.js diff --git a/build/1.3.13/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js b/archive/1.3.13/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js rename to archive/1.3.13/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js diff --git a/build/1.3.13/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js b/archive/1.3.13/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js rename to archive/1.3.13/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js diff --git a/build/1.3.13/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js b/archive/1.3.13/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js rename to archive/1.3.13/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js diff --git a/build/1.3.13/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js b/archive/1.3.13/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js rename to archive/1.3.13/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js diff --git a/build/1.3.13/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js b/archive/1.3.13/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js rename to archive/1.3.13/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js diff --git a/build/1.3.13/demo/js/archive/1.3.4/anchorDemo.js b/archive/1.3.13/demo/js/archive/1.3.4/anchorDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.4/anchorDemo.js rename to archive/1.3.13/demo/js/archive/1.3.4/anchorDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.4/chartDemo.js b/archive/1.3.13/demo/js/archive/1.3.4/chartDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.4/chartDemo.js rename to archive/1.3.13/demo/js/archive/1.3.4/chartDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.4/demo-helper-jquery.js b/archive/1.3.13/demo/js/archive/1.3.4/demo-helper-jquery.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.4/demo-helper-jquery.js rename to archive/1.3.13/demo/js/archive/1.3.4/demo-helper-jquery.js diff --git a/build/1.3.13/demo/js/archive/1.3.4/demo-helper-mootools.js b/archive/1.3.13/demo/js/archive/1.3.4/demo-helper-mootools.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.4/demo-helper-mootools.js rename to archive/1.3.13/demo/js/archive/1.3.4/demo-helper-mootools.js diff --git a/build/1.3.13/demo/js/archive/1.3.4/demo-helper-yui3.js b/archive/1.3.13/demo/js/archive/1.3.4/demo-helper-yui3.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.4/demo-helper-yui3.js rename to archive/1.3.13/demo/js/archive/1.3.4/demo-helper-yui3.js diff --git a/build/1.3.13/demo/js/archive/1.3.4/demo-list.js b/archive/1.3.13/demo/js/archive/1.3.4/demo-list.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.4/demo-list.js rename to archive/1.3.13/demo/js/archive/1.3.4/demo-list.js diff --git a/build/1.3.13/demo/js/archive/1.3.4/demo.js b/archive/1.3.13/demo/js/archive/1.3.4/demo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.4/demo.js rename to archive/1.3.13/demo/js/archive/1.3.4/demo.js diff --git a/build/1.3.13/demo/js/archive/1.3.4/dragAnimDemo-jquery.js b/archive/1.3.13/demo/js/archive/1.3.4/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.4/dragAnimDemo-jquery.js rename to archive/1.3.13/demo/js/archive/1.3.4/dragAnimDemo-jquery.js diff --git a/build/1.3.13/demo/js/archive/1.3.4/dragAnimDemo-mootools.js b/archive/1.3.13/demo/js/archive/1.3.4/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.4/dragAnimDemo-mootools.js rename to archive/1.3.13/demo/js/archive/1.3.4/dragAnimDemo-mootools.js diff --git a/build/1.3.13/demo/js/archive/1.3.4/dragAnimDemo-yui3.js b/archive/1.3.13/demo/js/archive/1.3.4/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.4/dragAnimDemo-yui3.js rename to archive/1.3.13/demo/js/archive/1.3.4/dragAnimDemo-yui3.js diff --git a/build/1.3.13/demo/js/archive/1.3.4/dragAnimDemo.js b/archive/1.3.13/demo/js/archive/1.3.4/dragAnimDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.4/dragAnimDemo.js rename to archive/1.3.13/demo/js/archive/1.3.4/dragAnimDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.4/draggableConnectorsDemo-jquery.js b/archive/1.3.13/demo/js/archive/1.3.4/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.4/draggableConnectorsDemo-jquery.js rename to archive/1.3.13/demo/js/archive/1.3.4/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.13/demo/js/archive/1.3.4/draggableConnectorsDemo-mootools.js b/archive/1.3.13/demo/js/archive/1.3.4/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.4/draggableConnectorsDemo-mootools.js rename to archive/1.3.13/demo/js/archive/1.3.4/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.13/demo/js/archive/1.3.4/draggableConnectorsDemo-yui3.js b/archive/1.3.13/demo/js/archive/1.3.4/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.4/draggableConnectorsDemo-yui3.js rename to archive/1.3.13/demo/js/archive/1.3.4/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.13/demo/js/archive/1.3.4/draggableConnectorsDemo.js b/archive/1.3.13/demo/js/archive/1.3.4/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.4/draggableConnectorsDemo.js rename to archive/1.3.13/demo/js/archive/1.3.4/draggableConnectorsDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.4/dynamicAnchorsDemo.js b/archive/1.3.13/demo/js/archive/1.3.4/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.4/dynamicAnchorsDemo.js rename to archive/1.3.13/demo/js/archive/1.3.4/dynamicAnchorsDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.4/flowchartConnectorsDemo.js b/archive/1.3.13/demo/js/archive/1.3.4/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.4/flowchartConnectorsDemo.js rename to archive/1.3.13/demo/js/archive/1.3.4/flowchartConnectorsDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.4/makeSourceDemo.js b/archive/1.3.13/demo/js/archive/1.3.4/makeSourceDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.4/makeSourceDemo.js rename to archive/1.3.13/demo/js/archive/1.3.4/makeSourceDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.4/makeTargetDemo.js b/archive/1.3.13/demo/js/archive/1.3.4/makeTargetDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.4/makeTargetDemo.js rename to archive/1.3.13/demo/js/archive/1.3.4/makeTargetDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.4/stateMachineDemo-jquery.js b/archive/1.3.13/demo/js/archive/1.3.4/stateMachineDemo-jquery.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.4/stateMachineDemo-jquery.js rename to archive/1.3.13/demo/js/archive/1.3.4/stateMachineDemo-jquery.js diff --git a/build/1.3.13/demo/js/archive/1.3.4/stateMachineDemo-mootools.js b/archive/1.3.13/demo/js/archive/1.3.4/stateMachineDemo-mootools.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.4/stateMachineDemo-mootools.js rename to archive/1.3.13/demo/js/archive/1.3.4/stateMachineDemo-mootools.js diff --git a/build/1.3.13/demo/js/archive/1.3.4/stateMachineDemo-yui3.js b/archive/1.3.13/demo/js/archive/1.3.4/stateMachineDemo-yui3.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.4/stateMachineDemo-yui3.js rename to archive/1.3.13/demo/js/archive/1.3.4/stateMachineDemo-yui3.js diff --git a/build/1.3.13/demo/js/archive/1.3.4/stateMachineDemo.js b/archive/1.3.13/demo/js/archive/1.3.4/stateMachineDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.4/stateMachineDemo.js rename to archive/1.3.13/demo/js/archive/1.3.4/stateMachineDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.5/anchorDemo.js b/archive/1.3.13/demo/js/archive/1.3.5/anchorDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.5/anchorDemo.js rename to archive/1.3.13/demo/js/archive/1.3.5/anchorDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.5/chartDemo.js b/archive/1.3.13/demo/js/archive/1.3.5/chartDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.5/chartDemo.js rename to archive/1.3.13/demo/js/archive/1.3.5/chartDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.5/demo-helper-jquery.js b/archive/1.3.13/demo/js/archive/1.3.5/demo-helper-jquery.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.5/demo-helper-jquery.js rename to archive/1.3.13/demo/js/archive/1.3.5/demo-helper-jquery.js diff --git a/build/1.3.13/demo/js/archive/1.3.5/demo-helper-mootools.js b/archive/1.3.13/demo/js/archive/1.3.5/demo-helper-mootools.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.5/demo-helper-mootools.js rename to archive/1.3.13/demo/js/archive/1.3.5/demo-helper-mootools.js diff --git a/build/1.3.13/demo/js/archive/1.3.5/demo-helper-yui3.js b/archive/1.3.13/demo/js/archive/1.3.5/demo-helper-yui3.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.5/demo-helper-yui3.js rename to archive/1.3.13/demo/js/archive/1.3.5/demo-helper-yui3.js diff --git a/build/1.3.13/demo/js/archive/1.3.5/demo-list.js b/archive/1.3.13/demo/js/archive/1.3.5/demo-list.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.5/demo-list.js rename to archive/1.3.13/demo/js/archive/1.3.5/demo-list.js diff --git a/build/1.3.13/demo/js/archive/1.3.5/demo.js b/archive/1.3.13/demo/js/archive/1.3.5/demo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.5/demo.js rename to archive/1.3.13/demo/js/archive/1.3.5/demo.js diff --git a/build/1.3.13/demo/js/archive/1.3.5/dragAnimDemo-jquery.js b/archive/1.3.13/demo/js/archive/1.3.5/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.5/dragAnimDemo-jquery.js rename to archive/1.3.13/demo/js/archive/1.3.5/dragAnimDemo-jquery.js diff --git a/build/1.3.13/demo/js/archive/1.3.5/dragAnimDemo-mootools.js b/archive/1.3.13/demo/js/archive/1.3.5/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.5/dragAnimDemo-mootools.js rename to archive/1.3.13/demo/js/archive/1.3.5/dragAnimDemo-mootools.js diff --git a/build/1.3.13/demo/js/archive/1.3.5/dragAnimDemo-yui3.js b/archive/1.3.13/demo/js/archive/1.3.5/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.5/dragAnimDemo-yui3.js rename to archive/1.3.13/demo/js/archive/1.3.5/dragAnimDemo-yui3.js diff --git a/build/1.3.13/demo/js/archive/1.3.5/dragAnimDemo.js b/archive/1.3.13/demo/js/archive/1.3.5/dragAnimDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.5/dragAnimDemo.js rename to archive/1.3.13/demo/js/archive/1.3.5/dragAnimDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.5/draggableConnectorsDemo-jquery.js b/archive/1.3.13/demo/js/archive/1.3.5/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.5/draggableConnectorsDemo-jquery.js rename to archive/1.3.13/demo/js/archive/1.3.5/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.13/demo/js/archive/1.3.5/draggableConnectorsDemo-mootools.js b/archive/1.3.13/demo/js/archive/1.3.5/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.5/draggableConnectorsDemo-mootools.js rename to archive/1.3.13/demo/js/archive/1.3.5/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.13/demo/js/archive/1.3.5/draggableConnectorsDemo-yui3.js b/archive/1.3.13/demo/js/archive/1.3.5/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.5/draggableConnectorsDemo-yui3.js rename to archive/1.3.13/demo/js/archive/1.3.5/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.13/demo/js/archive/1.3.5/draggableConnectorsDemo.js b/archive/1.3.13/demo/js/archive/1.3.5/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.5/draggableConnectorsDemo.js rename to archive/1.3.13/demo/js/archive/1.3.5/draggableConnectorsDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.5/dynamicAnchorsDemo.js b/archive/1.3.13/demo/js/archive/1.3.5/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.5/dynamicAnchorsDemo.js rename to archive/1.3.13/demo/js/archive/1.3.5/dynamicAnchorsDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.5/flowchartConnectorsDemo.js b/archive/1.3.13/demo/js/archive/1.3.5/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.5/flowchartConnectorsDemo.js rename to archive/1.3.13/demo/js/archive/1.3.5/flowchartConnectorsDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.5/loadTest.js b/archive/1.3.13/demo/js/archive/1.3.5/loadTest.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.5/loadTest.js rename to archive/1.3.13/demo/js/archive/1.3.5/loadTest.js diff --git a/build/1.3.13/demo/js/archive/1.3.5/makeSourceDemo.js b/archive/1.3.13/demo/js/archive/1.3.5/makeSourceDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.5/makeSourceDemo.js rename to archive/1.3.13/demo/js/archive/1.3.5/makeSourceDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.5/makeTargetDemo.js b/archive/1.3.13/demo/js/archive/1.3.5/makeTargetDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.5/makeTargetDemo.js rename to archive/1.3.13/demo/js/archive/1.3.5/makeTargetDemo.js diff --git a/build/1.3.13/demo/js/archive/1.3.5/stateMachineDemo-jquery.js b/archive/1.3.13/demo/js/archive/1.3.5/stateMachineDemo-jquery.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.5/stateMachineDemo-jquery.js rename to archive/1.3.13/demo/js/archive/1.3.5/stateMachineDemo-jquery.js diff --git a/build/1.3.13/demo/js/archive/1.3.5/stateMachineDemo-mootools.js b/archive/1.3.13/demo/js/archive/1.3.5/stateMachineDemo-mootools.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.5/stateMachineDemo-mootools.js rename to archive/1.3.13/demo/js/archive/1.3.5/stateMachineDemo-mootools.js diff --git a/build/1.3.13/demo/js/archive/1.3.5/stateMachineDemo-yui3.js b/archive/1.3.13/demo/js/archive/1.3.5/stateMachineDemo-yui3.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.5/stateMachineDemo-yui3.js rename to archive/1.3.13/demo/js/archive/1.3.5/stateMachineDemo-yui3.js diff --git a/build/1.3.13/demo/js/archive/1.3.5/stateMachineDemo.js b/archive/1.3.13/demo/js/archive/1.3.5/stateMachineDemo.js similarity index 100% rename from build/1.3.13/demo/js/archive/1.3.5/stateMachineDemo.js rename to archive/1.3.13/demo/js/archive/1.3.5/stateMachineDemo.js diff --git a/build/1.3.13/demo/js/chartDemo.js b/archive/1.3.13/demo/js/chartDemo.js similarity index 100% rename from build/1.3.13/demo/js/chartDemo.js rename to archive/1.3.13/demo/js/chartDemo.js diff --git a/build/1.3.13/demo/js/demo-helper-jquery.js b/archive/1.3.13/demo/js/demo-helper-jquery.js similarity index 100% rename from build/1.3.13/demo/js/demo-helper-jquery.js rename to archive/1.3.13/demo/js/demo-helper-jquery.js diff --git a/build/1.3.13/demo/js/demo-helper-mootools.js b/archive/1.3.13/demo/js/demo-helper-mootools.js similarity index 100% rename from build/1.3.13/demo/js/demo-helper-mootools.js rename to archive/1.3.13/demo/js/demo-helper-mootools.js diff --git a/build/1.3.13/demo/js/demo-helper-yui3.js b/archive/1.3.13/demo/js/demo-helper-yui3.js similarity index 100% rename from build/1.3.13/demo/js/demo-helper-yui3.js rename to archive/1.3.13/demo/js/demo-helper-yui3.js diff --git a/build/1.3.13/demo/js/demo-list.js b/archive/1.3.13/demo/js/demo-list.js similarity index 100% rename from build/1.3.13/demo/js/demo-list.js rename to archive/1.3.13/demo/js/demo-list.js diff --git a/build/1.3.13/demo/js/demo.js b/archive/1.3.13/demo/js/demo.js similarity index 100% rename from build/1.3.13/demo/js/demo.js rename to archive/1.3.13/demo/js/demo.js diff --git a/build/1.3.13/demo/js/dragAnimDemo-jquery.js b/archive/1.3.13/demo/js/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.13/demo/js/dragAnimDemo-jquery.js rename to archive/1.3.13/demo/js/dragAnimDemo-jquery.js diff --git a/build/1.3.13/demo/js/dragAnimDemo-mootools.js b/archive/1.3.13/demo/js/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.13/demo/js/dragAnimDemo-mootools.js rename to archive/1.3.13/demo/js/dragAnimDemo-mootools.js diff --git a/build/1.3.13/demo/js/dragAnimDemo-yui3.js b/archive/1.3.13/demo/js/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.13/demo/js/dragAnimDemo-yui3.js rename to archive/1.3.13/demo/js/dragAnimDemo-yui3.js diff --git a/build/1.3.13/demo/js/dragAnimDemo.js b/archive/1.3.13/demo/js/dragAnimDemo.js similarity index 100% rename from build/1.3.13/demo/js/dragAnimDemo.js rename to archive/1.3.13/demo/js/dragAnimDemo.js diff --git a/build/1.3.13/demo/js/draggableConnectorsDemo-jquery.js b/archive/1.3.13/demo/js/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.13/demo/js/draggableConnectorsDemo-jquery.js rename to archive/1.3.13/demo/js/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.13/demo/js/draggableConnectorsDemo-mootools.js b/archive/1.3.13/demo/js/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.13/demo/js/draggableConnectorsDemo-mootools.js rename to archive/1.3.13/demo/js/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.13/demo/js/draggableConnectorsDemo-yui3.js b/archive/1.3.13/demo/js/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.13/demo/js/draggableConnectorsDemo-yui3.js rename to archive/1.3.13/demo/js/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.13/demo/js/draggableConnectorsDemo.js b/archive/1.3.13/demo/js/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.13/demo/js/draggableConnectorsDemo.js rename to archive/1.3.13/demo/js/draggableConnectorsDemo.js diff --git a/build/1.3.13/demo/js/dynamicAnchorsDemo.js b/archive/1.3.13/demo/js/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.13/demo/js/dynamicAnchorsDemo.js rename to archive/1.3.13/demo/js/dynamicAnchorsDemo.js diff --git a/build/1.3.13/demo/js/flowchartConnectorsDemo.js b/archive/1.3.13/demo/js/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.13/demo/js/flowchartConnectorsDemo.js rename to archive/1.3.13/demo/js/flowchartConnectorsDemo.js diff --git a/build/1.3.13/demo/js/jquery.jsPlumb-1.3.13-all-min.js b/archive/1.3.13/demo/js/jquery.jsPlumb-1.3.13-all-min.js similarity index 100% rename from build/1.3.13/demo/js/jquery.jsPlumb-1.3.13-all-min.js rename to archive/1.3.13/demo/js/jquery.jsPlumb-1.3.13-all-min.js diff --git a/build/1.3.13/demo/js/jquery.jsPlumb-1.3.13-all.js b/archive/1.3.13/demo/js/jquery.jsPlumb-1.3.13-all.js similarity index 100% rename from build/1.3.13/demo/js/jquery.jsPlumb-1.3.13-all.js rename to archive/1.3.13/demo/js/jquery.jsPlumb-1.3.13-all.js diff --git a/build/1.3.13/demo/js/loadTest.js b/archive/1.3.13/demo/js/loadTest.js similarity index 100% rename from build/1.3.13/demo/js/loadTest.js rename to archive/1.3.13/demo/js/loadTest.js diff --git a/build/1.3.13/demo/js/makeSourceDemo.js b/archive/1.3.13/demo/js/makeSourceDemo.js similarity index 100% rename from build/1.3.13/demo/js/makeSourceDemo.js rename to archive/1.3.13/demo/js/makeSourceDemo.js diff --git a/build/1.3.13/demo/js/makeTargetDemo.js b/archive/1.3.13/demo/js/makeTargetDemo.js similarity index 100% rename from build/1.3.13/demo/js/makeTargetDemo.js rename to archive/1.3.13/demo/js/makeTargetDemo.js diff --git a/build/1.3.13/demo/js/mootools-1.3.2.1-more.js b/archive/1.3.13/demo/js/mootools-1.3.2.1-more.js similarity index 100% rename from build/1.3.13/demo/js/mootools-1.3.2.1-more.js rename to archive/1.3.13/demo/js/mootools-1.3.2.1-more.js diff --git a/build/1.3.13/demo/js/mootools.jsPlumb-1.3.13-all-min.js b/archive/1.3.13/demo/js/mootools.jsPlumb-1.3.13-all-min.js similarity index 100% rename from build/1.3.13/demo/js/mootools.jsPlumb-1.3.13-all-min.js rename to archive/1.3.13/demo/js/mootools.jsPlumb-1.3.13-all-min.js diff --git a/build/1.3.13/demo/js/mootools.jsPlumb-1.3.13-all.js b/archive/1.3.13/demo/js/mootools.jsPlumb-1.3.13-all.js similarity index 100% rename from build/1.3.13/demo/js/mootools.jsPlumb-1.3.13-all.js rename to archive/1.3.13/demo/js/mootools.jsPlumb-1.3.13-all.js diff --git a/build/1.3.13/demo/js/perimeterAnchorsDemo-jquery.js b/archive/1.3.13/demo/js/perimeterAnchorsDemo-jquery.js similarity index 100% rename from build/1.3.13/demo/js/perimeterAnchorsDemo-jquery.js rename to archive/1.3.13/demo/js/perimeterAnchorsDemo-jquery.js diff --git a/build/1.3.13/demo/js/perimeterAnchorsDemo-mootools.js b/archive/1.3.13/demo/js/perimeterAnchorsDemo-mootools.js similarity index 100% rename from build/1.3.13/demo/js/perimeterAnchorsDemo-mootools.js rename to archive/1.3.13/demo/js/perimeterAnchorsDemo-mootools.js diff --git a/build/1.3.13/demo/js/perimeterAnchorsDemo-yui3.js b/archive/1.3.13/demo/js/perimeterAnchorsDemo-yui3.js similarity index 100% rename from build/1.3.13/demo/js/perimeterAnchorsDemo-yui3.js rename to archive/1.3.13/demo/js/perimeterAnchorsDemo-yui3.js diff --git a/build/1.3.13/demo/js/stateMachineDemo-jquery.js b/archive/1.3.13/demo/js/stateMachineDemo-jquery.js similarity index 100% rename from build/1.3.13/demo/js/stateMachineDemo-jquery.js rename to archive/1.3.13/demo/js/stateMachineDemo-jquery.js diff --git a/build/1.3.13/demo/js/stateMachineDemo-mootools.js b/archive/1.3.13/demo/js/stateMachineDemo-mootools.js similarity index 100% rename from build/1.3.13/demo/js/stateMachineDemo-mootools.js rename to archive/1.3.13/demo/js/stateMachineDemo-mootools.js diff --git a/build/1.3.13/demo/js/stateMachineDemo-yui3.js b/archive/1.3.13/demo/js/stateMachineDemo-yui3.js similarity index 100% rename from build/1.3.13/demo/js/stateMachineDemo-yui3.js rename to archive/1.3.13/demo/js/stateMachineDemo-yui3.js diff --git a/build/1.3.13/demo/js/stateMachineDemo.js b/archive/1.3.13/demo/js/stateMachineDemo.js similarity index 100% rename from build/1.3.13/demo/js/stateMachineDemo.js rename to archive/1.3.13/demo/js/stateMachineDemo.js diff --git a/build/1.3.13/demo/js/yui-3.3.0-min.js b/archive/1.3.13/demo/js/yui-3.3.0-min.js similarity index 100% rename from build/1.3.13/demo/js/yui-3.3.0-min.js rename to archive/1.3.13/demo/js/yui-3.3.0-min.js diff --git a/build/1.3.13/demo/js/yui.jsPlumb-1.3.13-all-min.js b/archive/1.3.13/demo/js/yui.jsPlumb-1.3.13-all-min.js similarity index 100% rename from build/1.3.13/demo/js/yui.jsPlumb-1.3.13-all-min.js rename to archive/1.3.13/demo/js/yui.jsPlumb-1.3.13-all-min.js diff --git a/build/1.3.13/demo/js/yui.jsPlumb-1.3.13-all.js b/archive/1.3.13/demo/js/yui.jsPlumb-1.3.13-all.js similarity index 100% rename from build/1.3.13/demo/js/yui.jsPlumb-1.3.13-all.js rename to archive/1.3.13/demo/js/yui.jsPlumb-1.3.13-all.js diff --git a/build/1.3.13/demo/mootools/anchorDemo.html b/archive/1.3.13/demo/mootools/anchorDemo.html similarity index 100% rename from build/1.3.13/demo/mootools/anchorDemo.html rename to archive/1.3.13/demo/mootools/anchorDemo.html diff --git a/build/1.3.13/demo/mootools/chartDemo.html b/archive/1.3.13/demo/mootools/chartDemo.html similarity index 100% rename from build/1.3.13/demo/mootools/chartDemo.html rename to archive/1.3.13/demo/mootools/chartDemo.html diff --git a/build/1.3.13/demo/mootools/demo.html b/archive/1.3.13/demo/mootools/demo.html similarity index 100% rename from build/1.3.13/demo/mootools/demo.html rename to archive/1.3.13/demo/mootools/demo.html diff --git a/build/1.3.13/demo/mootools/dragAnimDemo.html b/archive/1.3.13/demo/mootools/dragAnimDemo.html similarity index 100% rename from build/1.3.13/demo/mootools/dragAnimDemo.html rename to archive/1.3.13/demo/mootools/dragAnimDemo.html diff --git a/build/1.3.13/demo/mootools/draggableConnectorsDemo.html b/archive/1.3.13/demo/mootools/draggableConnectorsDemo.html similarity index 100% rename from build/1.3.13/demo/mootools/draggableConnectorsDemo.html rename to archive/1.3.13/demo/mootools/draggableConnectorsDemo.html diff --git a/build/1.3.13/demo/mootools/dynamicAnchorsDemo.html b/archive/1.3.13/demo/mootools/dynamicAnchorsDemo.html similarity index 100% rename from build/1.3.13/demo/mootools/dynamicAnchorsDemo.html rename to archive/1.3.13/demo/mootools/dynamicAnchorsDemo.html diff --git a/build/1.3.13/demo/mootools/flowchartConnectorsDemo.html b/archive/1.3.13/demo/mootools/flowchartConnectorsDemo.html similarity index 100% rename from build/1.3.13/demo/mootools/flowchartConnectorsDemo.html rename to archive/1.3.13/demo/mootools/flowchartConnectorsDemo.html diff --git a/build/1.3.13/demo/mootools/perimeterAnchorsDemo.html b/archive/1.3.13/demo/mootools/perimeterAnchorsDemo.html similarity index 100% rename from build/1.3.13/demo/mootools/perimeterAnchorsDemo.html rename to archive/1.3.13/demo/mootools/perimeterAnchorsDemo.html diff --git a/build/1.3.13/demo/mootools/stateMachineDemo.html b/archive/1.3.13/demo/mootools/stateMachineDemo.html similarity index 100% rename from build/1.3.13/demo/mootools/stateMachineDemo.html rename to archive/1.3.13/demo/mootools/stateMachineDemo.html diff --git a/build/1.3.13/demo/yui3/anchorDemo.html b/archive/1.3.13/demo/yui3/anchorDemo.html similarity index 100% rename from build/1.3.13/demo/yui3/anchorDemo.html rename to archive/1.3.13/demo/yui3/anchorDemo.html diff --git a/build/1.3.13/demo/yui3/chartDemo.html b/archive/1.3.13/demo/yui3/chartDemo.html similarity index 100% rename from build/1.3.13/demo/yui3/chartDemo.html rename to archive/1.3.13/demo/yui3/chartDemo.html diff --git a/build/1.3.13/demo/yui3/demo.html b/archive/1.3.13/demo/yui3/demo.html similarity index 100% rename from build/1.3.13/demo/yui3/demo.html rename to archive/1.3.13/demo/yui3/demo.html diff --git a/build/1.3.13/demo/yui3/dragAnimDemo.html b/archive/1.3.13/demo/yui3/dragAnimDemo.html similarity index 100% rename from build/1.3.13/demo/yui3/dragAnimDemo.html rename to archive/1.3.13/demo/yui3/dragAnimDemo.html diff --git a/build/1.3.13/demo/yui3/draggableConnectorsDemo.html b/archive/1.3.13/demo/yui3/draggableConnectorsDemo.html similarity index 100% rename from build/1.3.13/demo/yui3/draggableConnectorsDemo.html rename to archive/1.3.13/demo/yui3/draggableConnectorsDemo.html diff --git a/build/1.3.13/demo/yui3/dynamicAnchorsDemo.html b/archive/1.3.13/demo/yui3/dynamicAnchorsDemo.html similarity index 100% rename from build/1.3.13/demo/yui3/dynamicAnchorsDemo.html rename to archive/1.3.13/demo/yui3/dynamicAnchorsDemo.html diff --git a/build/1.3.13/demo/yui3/flowchartConnectorsDemo.html b/archive/1.3.13/demo/yui3/flowchartConnectorsDemo.html similarity index 100% rename from build/1.3.13/demo/yui3/flowchartConnectorsDemo.html rename to archive/1.3.13/demo/yui3/flowchartConnectorsDemo.html diff --git a/build/1.3.13/demo/yui3/perimeterAnchorsDemo.html b/archive/1.3.13/demo/yui3/perimeterAnchorsDemo.html similarity index 100% rename from build/1.3.13/demo/yui3/perimeterAnchorsDemo.html rename to archive/1.3.13/demo/yui3/perimeterAnchorsDemo.html diff --git a/build/1.3.13/demo/yui3/stateMachineDemo.html b/archive/1.3.13/demo/yui3/stateMachineDemo.html similarity index 100% rename from build/1.3.13/demo/yui3/stateMachineDemo.html rename to archive/1.3.13/demo/yui3/stateMachineDemo.html diff --git a/archive/1.3.13/jquery.jsPlumb-1.3.13-RC1.js b/archive/1.3.13/jquery.jsPlumb-1.3.13-RC1.js new file mode 100644 index 000000000..a5992fd68 --- /dev/null +++ b/archive/1.3.13/jquery.jsPlumb-1.3.13-RC1.js @@ -0,0 +1,372 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.13 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the jQuery adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ +/* + * the library specific functions, such as find offset, get id, get attribute, extend etc. + * the full list is: + * + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getDropScope gets the drop scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getOriginalEvent gets the original browser event from some wrapper event + * getPageXY gets the page event's xy location. + * getParent gets the parent of some element. + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * hasClass returns whether or not the given element has the given class. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + * trigger triggers some event on an element. + * unbind unbinds some listener from some element. + */ +(function($) { + + //var getBoundingClientRectSupported = "getBoundingClientRect" in document.documentElement; + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el[0].className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.addClass(el[0], clazz); + } + } + catch (e) { + // SVGAnimatedString not supported; no problem. + } + el.addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * appends the given child to the given parent. + */ + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).append(child); + }, + + /** + * executes an ajax call. + */ + ajax : function(params) { + params = params || {}; + params.type = params.type || "get"; + $.ajax(params); + }, + + /** + * event binding wrapper. it just so happens that jQuery uses 'bind' also. yui3, for example, + * uses 'on'. + */ + bind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.bind(event, callback); + }, + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step', + 'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete' + }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + }, + + getDragScope : function(el) { + return el.draggable("option", "scope"); + }, + + getDropEvent : function(args) { + return args[0]; + }, + + getDropScope : function(el) { + return el.droppable("option", "scope"); + }, + + /** + * gets a DOM element from the given input, which might be a string (in which case we just do document.getElementById), + * a selector (in which case we return el[0]), or a DOM element already (we assume this if it's not either of the other + * two cases). this is the opposite of getElementObject below. + */ + getDOMElement : function(el) { + if (typeof(el) == "string") return document.getElementById(el); + else if (el.context || el.length != null) return el[0]; + else return el; + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + * + */ + getElementObject : function(el) { + return typeof(el) == "string" ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + getOriginalEvent : function(e) { + return e.originalEvent; + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).parent(); + }, + + getScrollLeft : function(el) { + return el.scrollLeft(); + }, + + getScrollTop : function(el) { + return el.scrollTop(); + }, + + getSelector : function(spec) { + return $(spec); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e.length > 0 ? e[0].tagName : null; + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + + // this code is a workaround for the case that the element being dragged has a margin set on it. jquery UI passes + // in the wrong offset if the element has a margin (it doesn't take the margin into account). the getBoundingClientRect + // method, which is in pretty much all browsers now, reports the right numbers. but it introduces a noticeable lag, which + // i don't like. + + /*if ( getBoundingClientRectSupported ) { + var r = eventArgs[1].helper[0].getBoundingClientRect(); + return { left : r.left, top: r.top }; + } else {*/ + if (eventArgs.length == 1) { + ret = { left: eventArgs[0].pageX, top:eventArgs[0].pageY }; + } + else { + var ui = eventArgs[1], _offset = ui.offset; + ret = _offset || ui.absolutePosition; + } + return ret; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options, isPlumbedComponent) { + options = options || {}; + // remove helper directive if present. + options.helper = null; + if (isPlumbedComponent) + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.draggable(options); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.droppable(options); + }, + + isAlreadyDraggable : function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return el.hasClass("ui-draggable"); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el[0].className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.removeClass(el[0], clazz); + } + } + catch (e) { + // SVGAnimatedString not supported; no problem. + } + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).remove(); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * sets the drag scope. probably time for a setDragOption method (roll this and the one above together) + * @param el + * @param scope + */ + setDragScope : function(el, scope) { + el.draggable("option", "scope", scope); + }, + + setOffset : function(el, o) { + jsPlumb.CurrentLibrary.getElementObject(el).offset(o); + }, + + /** + * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself. + * the other libraries do not. yui, in fact, cannot even pass an original event. we have to pull out stuff + * from the originalEvent to put in an options object for YUI. + * @param el + * @param event + * @param originalEvent + */ + trigger : function(el, event, originalEvent) { + //originalEvent.stopPropagation(); + //jsPlumb.CurrentLibrary.getElementObject(el).trigger(originalEvent); + var h = jQuery._data(jsPlumb.CurrentLibrary.getElementObject(el)[0], "handle"); + h(originalEvent); + //originalEvent.stopPropagation(); + }, + + /** + * event unbinding wrapper. it just so happens that jQuery uses 'unbind' also. yui3, for example, + * uses..something else. + */ + unbind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.unbind(event, callback); + } + }; + + $(document).ready(jsPlumb.init); + +})(jQuery); + diff --git a/build/1.3.13/js/jquery.jsPlumb-1.3.13-all-min.js b/archive/1.3.13/js/jquery.jsPlumb-1.3.13-all-min.js similarity index 100% rename from build/1.3.13/js/jquery.jsPlumb-1.3.13-all-min.js rename to archive/1.3.13/js/jquery.jsPlumb-1.3.13-all-min.js diff --git a/build/1.3.13/js/jquery.jsPlumb-1.3.13-all.js b/archive/1.3.13/js/jquery.jsPlumb-1.3.13-all.js similarity index 100% rename from build/1.3.13/js/jquery.jsPlumb-1.3.13-all.js rename to archive/1.3.13/js/jquery.jsPlumb-1.3.13-all.js diff --git a/archive/1.3.13/js/jsPlumb-1.3.13-tests.js b/archive/1.3.13/js/jsPlumb-1.3.13-tests.js new file mode 100644 index 000000000..42080d25d --- /dev/null +++ b/archive/1.3.13/js/jsPlumb-1.3.13-tests.js @@ -0,0 +1,4663 @@ +// _jsPlumb qunit tests. + +QUnit.config.reorder = false; + +var _getContextNode = function() { + return $("#container"); +}; + +var assertContextExists = function() { + ok(_getContextNode().length == 1, "context node exists"); +}; + +var assertContextSize = function(elementCount) { + //equals(_getContextNode().children().length - _divs.length, elementCount, 'context has ' + elementCount + ' children'); +}; + +var assertContextEmpty = function() { + equals(_getContextNode().children.length, 0, "context empty"); +}; + +var assertEndpointCount = function(elId, count, _jsPlumb) { + equals(_jsPlumb.getTestHarness().endpointCount(elId), count, elId + " has " + count + (count > 1) ? "endpoints" : "endpoint"); + equals(_jsPlumb.anchorManager.getEndpointsFor(elId).length, count, "anchor manager has " + count + (count > 1) ? "endpoints" : "endpoint"); +}; + +var assertConnectionCount = function(endpoint, count) { + equals(endpoint.connections.length, count, "endpoint has " + count + " connections"); +}; + +var assertConnectionByScopeCount = function(scope, count, _jsPlumb) { + equals(_jsPlumb.getTestHarness().connectionCount(scope), count, 'Scope ' + scope + " has " + count + (count > 1) ? "connections" : "connection"); + +}; + +var _divs = []; +var _addDiv = function(id, parent) { + var d1 = document.createElement("div"); + if (parent) parent.append(d1); else _getContextNode().append(d1); + d1 = jsPlumb.CurrentLibrary.getElementObject(d1); + jsPlumb.CurrentLibrary.setAttribute(d1, "id", id); + _divs.push(id); + return d1; +}; + +var defaults = null, + _cleanup = function(_jsPlumb) { + + _jsPlumb.reset(); + _jsPlumb.Defaults = defaults; + + for (var i in _divs) { + $("#" + _divs[i]).remove(); + } + _divs.splice(0, _divs.length - 1); + + $("#container").empty(); +}; + +var testSuite = function(renderMode, _jsPlumb) { + + + module("jsPlumb", { + teardown: function() { + _cleanup(_jsPlumb); + }, + setup:function() { + defaults = jsPlumb.extend({}, _jsPlumb.Defaults); + } + }); + + // setup the container + var container = document.createElement("div"); + container.id = "container"; + document.body.appendChild(container); + + var jpcl = jsPlumb.CurrentLibrary; + + _jsPlumb.setRenderMode(renderMode); + + test(renderMode + " : getElementObject", function() { + var e = document.createElement("div"); + e.id = "FOO"; + document.body.appendChild(e); + var el = jpcl.getElementObject(e); + equals(jpcl.getAttribute(el, "id"), "FOO"); + }); + + test(renderMode + " : getDOMElement", function() { + var e = document.createElement("div"); + e.id = "FOO"; + document.body.appendChild(e); + var el = jpcl.getElementObject(e); + var e2 = jpcl.getDOMElement(el); + equals(e2.id, "FOO"); + }); + + test(renderMode + ': _jsPlumb setup', function() { + ok(_jsPlumb, "loaded"); + }); + + test(renderMode + ': getId', function() { + var d10 = _addDiv('d10'); + equals(_jsPlumb.getTestHarness().getId(d10), "d10"); + }); + + test(renderMode + ': create a simple endpoint', function() { + var d1 = _addDiv("d1"); + var e = _jsPlumb.addEndpoint($("#d1"), {}); + ok(e, 'endpoint exists'); + assertEndpointCount("d1", 1, _jsPlumb); + ok(e.id != null, "endpoint has had an id assigned"); + }); + + test(renderMode + ': create and remove a simple endpoint', function() { + var d1 = _addDiv("d1"); + var ee = _jsPlumb.addEndpoint(d1, {uuid:"78978597593"}); + ok(ee != null, "endpoint exists"); + var e = _jsPlumb.getEndpoint("78978597593"); + ok(e != null, "the endpoint could be retrieved by UUID"); + ok(e.id != null, "the endpoint has had an id assigned to it"); + assertEndpointCount("d1", 1, _jsPlumb); + assertContextSize(1); // one Endpoint canvas. + _jsPlumb.removeEndpoint(d1, ee); + assertEndpointCount("d1", 0, _jsPlumb); + e = _jsPlumb.getEndpoint("78978597593"); + equals(e, null, "the endpoint has been deleted"); + assertContextSize(0); // no Endpoint canvases. + }); + + test(renderMode + ': create two simple endpoints, registered using a selector', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + d1.addClass("window");d2.addClass("window"); + var endpoints = _jsPlumb.addEndpoint($(".window"), {}); + equals(endpoints.length, 2, "endpoint added to both windows"); + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + }); + + test(renderMode + ': create two simple endpoints, registered using an array of element ids', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + d1.addClass("window");d2.addClass("window"); + var endpoints = _jsPlumb.addEndpoint(["d1", "d2"], {}); + equals(endpoints.length, 2, "endpoint added to both windows"); + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + }); + + test(renderMode + ': draggable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1"); + var e = _jsPlumb.addEndpoint(d1, {isSource:true}); + ok(e, 'endpoint exists'); + }); + + test(renderMode + ': droppable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1") + var e = _jsPlumb.addEndpoint(d1, {isTarget:true}); + ok(e, 'endpoint exists'); + }); + + test(renderMode + ': defaultEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = _jsPlumb.addEndpoint(d3, {isSource:true}); + ok(e3.anchor, 'endpoint 3 has an anchor'); + var e4 = _jsPlumb.addEndpoint(d4, {isSource:true}); + assertEndpointCount("d3", 1, _jsPlumb); + assertEndpointCount("d4", 1, _jsPlumb); + assertContextSize(2); // one canvas for each of our two endpoints. + ok(!e3.isFull(), "endpoint 3 is not full."); + _jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // we have one connection + _jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // should have refused the connection; default max is 1. + assertContextSize(3); // now we have one more canvas, for the Connection that was accepted. + }); + + test(renderMode + ': specifiedEndpointMaxConnections', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = _jsPlumb.addEndpoint(d5, {isSource:true, maxConnections:3}); + ok(e5.anchor, 'endpoint 5 has an anchor'); + var e6 = _jsPlumb.addEndpoint(d6, {isSource:true, maxConnections:2}); // this one has max TWO + assertEndpointCount("d5", 1, _jsPlumb); assertEndpointCount("d6", 1, _jsPlumb); + assertContextSize(2); + ok(!e5.isFull(), "endpoint 5 is not full."); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 1); // we have one connection + assertContextSize(3); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // two connections + assertContextSize(4); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // should have refused; max is 2, for d4. + assertContextSize(4); + }); + + test(renderMode + ': noEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = _jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + var e4 = _jsPlumb.addEndpoint(d4, {isSource:true, maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertContextSize(3); + assertConnectionCount(e3, 1); // we have one connection + _jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 2); // we have two. etc (default was one. this proves max is working). + assertContextSize(4); + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = _jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e4}); + assertConnectionCount(e4, 3); + assertContextSize(6); + }); + + test(renderMode + ': anchors equal', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + ok(a1.equals(a2), "anchors are the same"); + }); + + test(renderMode + ': anchors equal with offsets', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb); + ok(a1.equals(a2), "anchors are the same"); + }); + + test(renderMode + ': anchors not equal', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 0, 1], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + ok(!a1.equals(a2), "anchors are different"); + }); + + test(renderMode + ': anchor not equal with offsets', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + ok(!a1.equals(a2), "anchors are different"); + }); + + test(renderMode + ': detach does not fail when no arguments are provided', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + _jsPlumb.connect({source:d3, target:d4}); + _jsPlumb.detach(); + }); + + // test that detach does not fire an event by default + test(renderMode + ': _jsPlumb.detach should fire detach event by default', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach(conn); + equals(eventCount, 1); + }); + + // test that detach does not fire an event by default + test(renderMode + ': _jsPlumb.detach should fire detach event by default, using params object', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({connection:conn}); + equals(eventCount, 1); + }); + + // test that detach fires an event when instructed to do so + test(renderMode + ': _jsPlumb.detach should not fire detach event when instructed to not do so', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach(conn, {fireEvent:false}); + equals(eventCount, 0); + }); + + // issue 81 + test(renderMode + ': _jsPlumb.detach should fire only one detach event (pass Connection as argument)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach(conn); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': _jsPlumb.detach should fire only one detach event (pass Connection as param in argument)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({connection:conn}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': detach should fire only one detach event (pass source and targets as strings as arguments in params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({source:"d5", target:"d6"}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': detach should fire only one detach event (pass source and targets as divs as arguments in params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({source:d5, target:d6, fireEvent:true}); + equals(eventCount, 1); + }); + + //TODO make sure you run this test with a single detach call, to ensure that + // single detach calls result in the connection being removed. detachEveryConnection can + // just blow away the connectionsByScope array and recreate it. + test(renderMode + ': getConnections (simple case, default scope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + _jsPlumb.connect({source:d5, target:d6}); + assertContextSize(3); + var c = _jsPlumb.getConnections(); // will get all connections in the default scope. + equals(c.length, 1, "there is one connection"); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by element id using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + _jsPlumb.connect({source:d5, target:d6}); + _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach({source:'d5', target:'d6'}); + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by id using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + _jsPlumb.connect({source:d5, target:d6}); + _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach({source:"d5", target:"d6"}); + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by element object using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + _jsPlumb.connect({source:d5, target:d6}); + _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach({source:d5, target:d6}); + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by Connection)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + var c56 = _jsPlumb.connect({source:d5, target:d6}); + var c67 = _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach(c56); + assertContextSize(5); // check that the connection canvas was removed. + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + }); + +// beforeDetach functionality + + test(renderMode + ": detach; beforeDetach on connect call returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { return false; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": detach; beforeDetach on connect call returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { return true; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": detach; beforeDetach on connect call throws an exception; we treat it with the contempt it deserves and pretend it said the detach was ok.", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { throw "i am an example of badly coded beforeDetach, but i don't break jsPlumb "; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": detach; beforeDetach on addEndpoint call to source Endpoint returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return false; } }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": detach; beforeDetach on addEndpoint call to source Endpoint returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return true; } }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + + test(renderMode + ": Endpoint.detach; beforeDetach on addEndpoint call to source Endpoint returns false; Endpoint.detach returns false too (the UI needs it to)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return false; } }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + var success = e1.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + ok(!success, "Endpoint reported detach did not execute"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns false but detach is forced", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c, {forceDetach:true}); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was denied but forced anyway"); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was denied but forced anyway"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return true; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach bound to _jsPlumb returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + _jsPlumb.bind("beforeDetach", function(connection) { + ok(connection.sourceId === "d1", "connection is provided and configured with correct source"); + ok(connection.targetId === "d2", "connection is provided and configured with correct target"); + return false; + }); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach bound to _jsPlumb returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + _jsPlumb.bind("beforeDetach", function(connection) { + ok(connection.sourceId === "d1", "connection is provided and configured with correct source"); + ok(connection.targetId === "d2", "connection is provided and configured with correct target"); + return true; + }); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was denied"); + }); + + test(renderMode + ": _jsPlumb.detachAllConnections ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detachAllConnections(d1); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachAll was called"); + }); + + test(renderMode + ": _jsPlumb.detachEveryConnection ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detachEveryConnection(); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachEveryConnection was called"); + }); + + test(renderMode + ": Endpoint.detachAll ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + e1.detachAll(); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachAllConnections was called"); + }); + +// ******** end of beforeDetach tests ************** + +// detachEveryConnection/detachAllConnections fireEvent overrides tests + + test(renderMode + ": _jsPlumb.detachEveryConnection fires events", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0; + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachEveryConnection(); + equals(connCount, 0, "no connections registered"); + }); + + test(renderMode + ": _jsPlumb.detachEveryConnection doesn't fire events when instructed not to", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0; + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachEveryConnection({fireEvent:false}); + equals(connCount, 2, "two connections registered"); + }); + + test(renderMode + ": _jsPlumb.detachAllConnections fires events", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0, + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachAllConnections("d1"); + equals(connCount, 0, "no connections registered"); + }); + + test(renderMode + ": _jsPlumb.detachAllConnections doesn't fire events when instructed not to", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0, + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachAllConnections("d1", {fireEvent:false}); + equals(connCount, 2, "two connections registered"); + }); + + test(renderMode + ': getConnections (scope testScope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + _jsPlumb.connect({source:d5, target:d6, scope:'testScope'}); + var c = _jsPlumb.getConnections("testScope"); // will get all connections in testScope + equals(c.length, 1, "there is one connection"); + equals(c[0].sourceId, 'd5', "the connection's source is d5"); + equals(c[0].targetId, 'd6', "the connection's source is d6"); + c = _jsPlumb.getConnections(); // will get all connections in default scope; should be none. + equals(c.length, 0, "there are no connections in the default scope"); + }); + + test(renderMode + ': _jsPlumb.getAllConnections (filtered by scope)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + _jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + _jsPlumb.connect({source:d9, target:d10}); // default scope + var c = _jsPlumb.getAllConnections(); // will get all connections + equals(c[_jsPlumb.getDefaultScope()].length, 1); + equals(c['testScope'].length, 1); + // now supply a list of scopes + c = _jsPlumb.getConnections(); + equals(c.length, 1); + c = _jsPlumb.getConnections("testScope"); + equals(c.length, 1, "there is one connection in 'testScope'"); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by scope and sourceId)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + _jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + _jsPlumb.connect({source:d9, target:d8, scope:'testScope'}); + _jsPlumb.connect({source:d9, target:d10}); // default scope + var c = _jsPlumb.getConnections({scope:'testScope', source:'d8'}); // will get all connections with sourceId 'd8' + equals(c.length, 1, "there is one connection in 'testScope' from d8"); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by scope, source id and target id)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope'}); + var c = _jsPlumb.getConnections({scope:'testScope', source:'d11', target:'d13'}); + equals(c.length, 1, "there is one connection from d11 to d13"); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = _jsPlumb.getConnections({scope:['testScope','testScope3']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes and source ids)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = _jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes, source ids and target ids)', function() { + _jsPlumb.detachEveryConnection(); + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'), d14=_addDiv("d14"), d15=_addDiv("d15"); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d11, target:d14, scope:'testScope'}); + _jsPlumb.connect({source:d11, target:d15, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + assertContextSize(18); + var c = _jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11'], target:['d14', 'd15']}); + equals(c['testScope'].length, 2, 'there are two connections in testScope'); + equals(c['testScope3'], null, 'there are no connections in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + var anEntry = c['testScope'][0]; + ok(anEntry.endpoints[0] != null, "Source endpoint is set"); + ok(anEntry.endpoints[1] != null, "Target endpoint is set"); + equals(anEntry.source.attr("id"), "d11", "Source is div d11"); + equals(anEntry.target.attr("id"), "d14", "Target is div d14"); + }); + + test(renderMode + ': getEndpoints, one Endpoint added by addEndpoint, get Endpoints by selector', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + _jsPlumb.addEndpoint(d1); + var e = _jsPlumb.getEndpoints(d1); + equals(e.length, 1, "there is one endpoint for element d1"); + }); + + test(renderMode + ': getEndpoints, one Endpoint added by addEndpoint, get Endpoints by id', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + _jsPlumb.addEndpoint(d1); + var e = _jsPlumb.getEndpoints("d1"); + equals(e.length, 1, "there is one endpoint for element d1"); + }); + + test(renderMode + ': connection event listeners', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null/*, returnedParams2 = null*/; + _jsPlumb.bind("jsPlumbConnection", function(params) { + returnedParams = $.extend({}, params); + }); + var c = _jsPlumb.connect({source:d1, target:d2}); + ok(returnedParams != null, "new connection listener event was fired"); + ok(returnedParams.connection != null, 'connection is set'); + equals(returnedParams.sourceId, "d1", 'sourceid is set'); + equals(returnedParams.targetId, "d2", 'targetid is set'); + equals(returnedParams.source.attr("id"), "d1", 'source is set'); + equals(returnedParams.target.attr("id"), "d2" , 'target is set'); + ok(returnedParams.sourceEndpoint != null, "source endpoint is not null"); + ok(returnedParams.targetEndpoint != null, "target endpoint is not null"); + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.detach(c); + ok(returnedParams.connection != null, 'connection is set'); + }); + + test(renderMode + ': detach event listeners (detach by connection)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + ok(returnedParams.connection != null, "removed connection listener event passed in connection"); + }); + + test(renderMode + ': detach event listeners (detach by element ids)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach({source:"d1",target:"d2"}); + ok(returnedParams != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (detach by elements)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach({source:d1,target:d2}); + ok(returnedParams != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detach method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detachFrom method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachFrom(e2); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detachAll method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachAll(); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via _jsPlumb.deleteEndpoint method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + _jsPlumb.deleteEndpoint(e1); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(1); + }); + + test(renderMode + ': detach event listeners (ensure cleared by _jsPlumb.reset)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach({source:d1,target:d2, fireEvent:true}); + ok(returnedParams != null, "removed connection listener event was fired"); + returnedParams = null; + + _jsPlumb.reset(); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach(d1,d2); + ok(returnedParams == null, "connection listener was cleared by _jsPlumb.reset()"); + }); + + test(renderMode + ': connection events that throw errors', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null, returnedParams2 = null; + _jsPlumb.bind("jsPlumbConnection", function(params) { + returnedParams = $.extend({}, params); + throw "oh no!"; + }); + _jsPlumb.connect({source:d1, target:d2}); + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + _jsPlumb.connect({source:d3, target:d4}); + ok(returnedParams != null, "new connection listener event was fired; we threw an error, _jsPlumb survived."); + }); + + test(renderMode + ': unbinding connection event listeners, connection', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var count = 0; + _jsPlumb.bind("jsPlumbConnection", function(params) { + count++; + }); + var c = _jsPlumb.connect({source:d1, target:d2}); + ok(count == 1, "received one event"); + _jsPlumb.unbind("jsPlumbConnection"); + var c2 = _jsPlumb.connect({source:d1, target:d2}); + ok(count == 1, "still received only one event"); + + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + count--; + }); + _jsPlumb.detach(c); + ok(count == 0, "count of events is now zero"); + }); + + test(renderMode + ': unbinding connection event listeners, detach', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var count = 0; + _jsPlumb.bind("jsPlumbConnection", function(params) { + count++; + }); + var c = _jsPlumb.connect({source:d1, target:d2}); + ok(count == 1, "received one event"); + var c2 = _jsPlumb.connect({source:d1, target:d2}); + ok(count == 2, "received two events"); + + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + count--; + }); + _jsPlumb.detach(c); + ok(count == 1, "count of events is now one"); + _jsPlumb.unbind("jsPlumbConnectionDetached"); + _jsPlumb.detach(c2); + ok(count == 1, "count of events is still one"); + }); + + test(renderMode + ': unbinding connection event listeners, all listeners', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var count = 0; + _jsPlumb.bind("jsPlumbConnection", function(params) { + count++; + }); + var c = _jsPlumb.connect({source:d1, target:d2}), + c2 = _jsPlumb.connect({source:d1, target:d2}), + c3 = _jsPlumb.connect({source:d1, target:d2}); + + ok(count == 3, "received three events"); + + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + count--; + }); + _jsPlumb.detach(c); + ok(count == 2, "count of events is now two"); + + _jsPlumb.unbind(); // unbind everything + + _jsPlumb.detach(c2); + _jsPlumb.detach(c3); + _jsPlumb.connect({source:d1, target:d2}) + _jsPlumb.connect({source:d1, target:d2}) + _jsPlumb.connect({source:d1, target:d2}) + _jsPlumb.connect({source:d1, target:d2}) + + ok(count == 2, "count of events is still two"); + }); + + test(renderMode + ": Endpoint.detachFrom", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + e16.detachFrom(e17); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by _jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": Endpoint.detachFromConnection", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + e16.detachFromConnection(conn); + assertContextSize(3); // all canvases should remain; the connection was not removed. + // but endpoint e16 should have no connections now. + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": Endpoint.detachAll", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + var e16 = _jsPlumb.addEndpoint($("#d16"), {isSource:true,maxConnections:-1}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint($("#d17"), {isSource:true}); + var e18 = _jsPlumb.addEndpoint($("#d18"), {isSource:true}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e18}); + assertConnectionCount(e16, 2); + assertConnectionCount(e17, 1); + assertConnectionCount(e18, 1); + assertContextSize(5); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 2, _jsPlumb); + e16.detachAll(); + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": Endpoint.detach", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + e16.detach(conn); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by _jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": Endpoint.isConnectedTo", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.isConnectedTo(e17), true, "e16 and e17 are connected"); + }); + + asyncTest(renderMode + "jsPlumbUtil.setImage on Endpoint, with supplied onload", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { + endpoint:[ "Image", { + src:"../img/endpointTest1.png", + onload:function(imgEp) { + _jsPlumb.repaint("d1"); + ok(imgEp.img.src.indexOf("endpointTest1.png") != -1, "image source is correct"); + ok(imgEp.img.src.indexOf("endpointTest1.png") != -1, "image elementsource is correct"); + + imgEp.canvas.setAttribute("id", "iwilllookforthis"); + + _jsPlumb.removeAllEndpoints("d1"); + ok(document.getElementById("iwilllookforthis") == null, "image element was removed after remove endpoint"); + } + } ] + }; + start(); + _jsPlumb.addEndpoint(d1, e); + }); + + test(renderMode + ": setting endpoint uuid", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + equals(e16.getUuid(), uuid, "endpoint's uuid was set correctly"); + }); + + test(renderMode + ": _jsPlumb.getEndpoint (by uuid)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by uuid, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + _jsPlumb.deleteEndpoint(uuid); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + var ebe = _jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by uuid, connections too)", function() { + // create two endpoints (one with a uuid), add them to two divs and then connect them. + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + // check that the connection was ok. + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + // delete the endpoint that has a uuid. verify that the endpoint cannot be retrieved and that the connection has been removed, but that + // element d17 still has its Endpoint. + _jsPlumb.deleteEndpoint(uuid); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + var ebe = _jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + ebe = _jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 1, "element d17 still has its Endpoint"); + assertContextSize(1); + + // now delete d17's endpoint and check that it has gone. + _jsPlumb.deleteEndpoint(e17); + f = _jsPlumb.getEndpoint(e17); + equals(f, null, "endpoint has been deleted"); + ebe = _jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 0, "element d17 no longer has any Endpoints"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by reference, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + _jsPlumb.deleteEndpoint(e16); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by reference, connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + _jsPlumb.deleteEndpoint(e16); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + assertContextSize(1); + }); + + test(renderMode + ": _jsPlumb.deleteEveryEndpoint", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + assertContextSize(2); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + _jsPlumb.deleteEveryEndpoint(); + + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = _jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEveryEndpoint (connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + _jsPlumb.deleteEveryEndpoint(); + + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = _jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); // no canvases. so all connection canvases have been cleaned up too. + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchor:[0,0.5,0,-1]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:"TopCenter"}); + assertContextSize(2); + equals(e16.anchor.x, 0); + equals(e16.anchor.y, 0.5); + equals(e17.anchor.x, 0.5); + equals(e17.anchor.y, 0); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (tooltip)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {tooltip:"FOO"}), + e17 = _jsPlumb.addEndpoint(d17, {tooltip:"BAZ"}); + assertContextSize(2); + equals(e16.canvas.getAttribute("title"), "FOO"); + equals(e17.canvas.getAttribute("title"), "BAZ"); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case, dynamic anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + assertContextSize(2); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case, two arg method)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchor:[0,0.5,0,-1]}); + var e17 = _jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchor:"TopCenter"}); + assertContextSize(2); + equals(e16.anchor.x, 0); + equals(e16.anchor.y, 0.5); + equals(false, e16.isTarget); + equals(true, e16.isSource); + equals(e17.anchor.x, 0.5); + equals(e17.anchor.y, 0); + equals(true, e17.isTarget); + equals(false, e17.isSource); + }); + + + test(renderMode + ": _jsPlumb.addEndpoints (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false, anchor:[0,0.5,0,-1] }, { isTarget:true, isSource:false, anchor:"TopCenter" }]); + assertContextSize(2); + equals(e16[0].anchor.x, 0); + equals(e16[0].anchor.y, 0.5); + equals(false, e16[0].isTarget); + equals(true, e16[0].isSource); + equals(e16[1].anchor.x, 0.5); + equals(e16[1].anchor.y, 0); + equals(true, e16[1].isTarget); + equals(false, e16[1].isSource); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (with reference params)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var refParams = {anchor:"RightMiddle"}; + var e16 = _jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false}, { isTarget:true, isSource:false }], refParams); + assertContextSize(2); + equals(e16[0].anchor.x, 1); + equals(e16[0].anchor.y, 0.5); + equals(false, e16[0].isTarget); + equals(true, e16[0].isSource); + equals(e16[1].anchor.x, 1); + equals(e16[1].anchor.y, 0.5); + equals(true, e16[1].isTarget); + equals(false, e16[1].isSource); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case, dynamic anchors, two arg method)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchors:["TopCenter", "BottomCenter"]}); + assertContextSize(2); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (default overlays)", function() { + _jsPlumb.Defaults.Overlays = [ + [ "Label", { id:"label" } ] + ]; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16), + e2 = _jsPlumb.addEndpoint(d17); + + ok(e1.getOverlay("label") != null, "endpoint 1 has overlay from defaults"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (default overlays)", function() { + _jsPlumb.Defaults.Overlays = [ + [ "Label", { id:"label" } ] + ]; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16, { + overlays:[ + ["Label", { id:"label2", location:[ 0.5, 1 ] } ] + ] + }), + e2 = _jsPlumb.addEndpoint(d17); + + ok(e1.getOverlay("label") != null, "endpoint 1 has overlay from defaults"); + ok(e1.getOverlay("label2") != null, "endpoint 1 has overlay from addEndpoint call"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (end point set label)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16), + e2 = _jsPlumb.addEndpoint(d17); + + e1.setLabel("FOO"); + equals(e1.getLabel(), "FOO", "endpoint's label is correct"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (end point set label in constructor, as string)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16, {label:"FOO"}), + e2 = _jsPlumb.addEndpoint(d17); + + equals(e1.getLabel(), "FOO", "endpoint's label is correct"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (end point set label in constructor, as function)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16, {label:function() { return "BAZ"; }, labelLocation:0.1}), + e2 = _jsPlumb.addEndpoint(d17); + + equals(e1.getLabel()(), "BAZ", "endpoint's label is correct"); + equals(e1.getLabelOverlay().getLocation(), 0.1, "endpoint's label's location is correct"); + }); + +// ****************** makeTarget (and associated methods) tests ******************************************** + + test(renderMode + ": _jsPlumb.makeTarget (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.makeTarget (specify two divs in an array)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.makeTarget([d16, d17], { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.makeTarget (specify two divs by id in an array)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.makeTarget(["d16", "d17"], { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.makeTarget (specify divs by selector)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + d16.addClass("FOO");d17.addClass("FOO"); + _jsPlumb.makeTarget($(".FOO"), { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e.length, 1, "d17 has one endpoint"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case, two connect calls)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17"}); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 2, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case, two connect calls, uniqueEndpoint set)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true, anchor:"LeftMiddle", uniqueEndpoint:true, maxConnections:-1 }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17"}); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].connections.length, 2, "endpoint on d17 has two connections"); + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (newConnection:true specified)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17", newConnection:true}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0.5, "anchor is BottomCenter"); //here we should be seeing the default anchor + equals(e[0].anchor.y, 1, "anchor is BottomCenter"); //here we should be seeing the default anchor + }); + +// jsPlumb.connect, after makeSource has been called on some element + test(renderMode + ": _jsPlumb.connect after makeSource (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeSource (simple case, two connect calls)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 2, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case, two connect calls, uniqueEndpoint set)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true, anchor:"LeftMiddle", uniqueEndpoint:true, maxConnections:-1 }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].connections.length, 2, "endpoint on d17 has two connections"); + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (newConnection:true specified)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16, newConnection:true}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0.5, "anchor is BottomCenter"); //here we should be seeing the default anchor + equals(e[0].anchor.y, 1, "anchor is BottomCenter"); //here we should be seeing the default anchor + }); + + test(renderMode + ": _jsPlumb.connect after makeSource on child; wth parent set (parent should be recognised)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18", d17); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d18, { isSource:true,anchor:"LeftMiddle", parent:d17 }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + assertEndpointCount("d18", 0, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + // makeSource, then disable it. should not be able to make a connection from it. + test(renderMode + ": _jsPlumb.connect after makeSource and setSourceEnabled(false) (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.setSourceEnabled(d17, false); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + }); + + // makeSource, then disable it. should not be able to make a connection from it. + test(renderMode + ": _jsPlumb.connect after makeSource and setSourceEnabled(false) (selector as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.setSourceEnabled($("div"), false); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + }); + + // makeSource, then toggle its enabled state. should not be able to make a connection from it. + test(renderMode + ": _jsPlumb.connect after makeSource and toggleSourceEnabled() (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.toggleSourceEnabled(d17); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + + _jsPlumb.toggleSourceEnabled(d17); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + // makeSource, then disable it. should not be able to make a connection from it. + test(renderMode + ": _jsPlumb.connect after makeSource and toggleSourceEnabled() (selector as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.toggleSourceEnabled($("div")); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + _jsPlumb.toggleSourceEnabled($("div")); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + test(renderMode + ": jsPlumb.isSource and jsPlumb.isSourceEnabled", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + ok(_jsPlumb.isSource(d17) == true, "d17 is recognised as connection source"); + ok(_jsPlumb.isSourceEnabled(d17) == true, "d17 is recognised as enabled"); + _jsPlumb.setSourceEnabled(d17, false); + ok(_jsPlumb.isSourceEnabled(d17) == false, "d17 is recognised as disabled"); + }); + + // makeSource, then disable it. should not be able to make a connection to it. + test(renderMode + ": _jsPlumb.connect after makeTarget and setTargetEnabled(false) (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.setTargetEnabled(d17, false); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + }); + + // makeTarget, then disable it. should not be able to make a connection to it. + test(renderMode + ": _jsPlumb.connect after makeTarget and setTargetEnabled(false) (selector as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.setTargetEnabled($("div"), false); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + }); + + // makeTarget, then toggle its enabled state. should not be able to make a connection to it. + test(renderMode + ": _jsPlumb.connect after makeTarget and toggleTargetEnabled() (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.toggleTargetEnabled(d17); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + + _jsPlumb.toggleTargetEnabled(d17); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + // makeTarget, then disable it. should not be able to make a connection to it. + test(renderMode + ": _jsPlumb.connect after makeTarget and toggleTargetEnabled() (selector as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.toggleTargetEnabled($("div")); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + _jsPlumb.toggleTargetEnabled($("div")); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + test(renderMode + ": jsPlumb.isTarget and jsPlumb.isTargetEnabled", function() { + var d17 = _addDiv("d17"); + _jsPlumb.makeTarget(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + ok(_jsPlumb.isTarget(d17) == true, "d17 is recognised as connection target"); + ok(_jsPlumb.isTargetEnabled(d17) == true, "d17 is recognised as enabled"); + _jsPlumb.setTargetEnabled(d17, false); + ok(_jsPlumb.isTargetEnabled(d17) == false, "d17 is recognised as disabled"); + }); + + // makeSource, then unmake it. should not be able to make a connection from it. then connect to it, which should succeed, + // because jsPlumb will just add a new endpoint. + test(renderMode + ": jsPlumb.unmakeSource (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + ok(_jsPlumb.isSource(d17) == true, "d17 is currently a source"); + // unmake source + _jsPlumb.unmakeSource(d17); + ok(_jsPlumb.isSource(d17) == false, "d17 is no longer a source"); + + // this should succeed, because d17 is no longer a source and so jsPlumb will just create and add a new endpoint to d17. + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + // maketarget, then unmake it. should not be able to make a connection to it. + test(renderMode + ": jsPlumb.unmakeTarget (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + ok(_jsPlumb.isTarget(d17) == true, "d17 is currently a target"); + // unmake target + _jsPlumb.unmakeTarget(d17); + ok(_jsPlumb.isTarget(d17) == false, "d17 is no longer a target"); + + // this should succeed, because d17 is no longer a target and so jsPlumb will just create and add a new endpoint to d17. + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + + test(renderMode + ": jsPlumb.removeEverySource and removeEveryTarget (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + _jsPlumb.makeSource(d16).makeTarget(d17).makeSource(d18); + ok(_jsPlumb.isSource(d16) == true, "d16 is a source"); + ok(_jsPlumb.isTarget(d17) == true, "d17 is a target"); + ok(_jsPlumb.isSource(d18) == true, "d18 is a source"); + + _jsPlumb.unmakeEverySource(); + _jsPlumb.unmakeEveryTarget(); + + ok(_jsPlumb.isSource(d16) == false, "d16 is no longer a source"); + ok(_jsPlumb.isTarget(d17) == false, "d17 is no longer a target"); + ok(_jsPlumb.isSource(d18) == false, "d18 is no longer a source"); + }); + +// *********************** end of makeTarget (and associated methods) tests ************************ + + test(renderMode + ': _jsPlumb.connect (between two Endpoints)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1, _jsPlumb); // no new endpoint should have been added + assertEndpointCount("d2", 1, _jsPlumb); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. + ok(c.id != null, "connection has had an id assigned"); + }); + + test(renderMode + ': _jsPlumb.connect (Endpoint connectorTooltip parameter)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1, {connectorTooltip:"FOO"}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + equals(c.connector.canvas.getAttribute("title"), "FOO", "connector canvas has label attribute set"); + }); + + test(renderMode + ': _jsPlumb.connect (tooltip parameter)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2, tooltip:"FOO"}); + equals(c.connector.canvas.getAttribute("title"), "FOO", "connector canvas has label attribute set"); + }); + + test(renderMode + ': _jsPlumb.connect (between two Endpoints, and dont supply any parameters to the Endpoints.)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1, _jsPlumb); // no new endpoint should have been added + assertEndpointCount("d2", 1, _jsPlumb); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. + }); + + test(renderMode + " : _jsPlumb.connect, passing 'anchors' array" ,function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, anchors:["LeftMiddle", "RightMiddle"]}); + + equals(c.endpoints[0].anchor.x, 0, "source anchor is at x=0"); + equals(c.endpoints[0].anchor.y, 0.5, "source anchor is at y=0.5"); + equals(c.endpoints[1].anchor.x, 1, "target anchor is at x=1"); + equals(c.endpoints[1].anchor.y, 0.5, "target anchor is at y=0.5"); + }); + + test(renderMode + ': _jsPlumb.connect (by endpoint)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + }); + + test(renderMode + ': _jsPlumb.connect (cost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17, cost:567}); + assertContextSize(3); + equals(c.getCost(), 567, "connection cost is 567"); + }); + + test(renderMode + ': _jsPlumb.connect (default cost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), undefined, "default connection cost is 1"); + }); + + test(renderMode + ': _jsPlumb.connect (set cost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), undefined, "default connection cost is 1"); + c.setCost(8989); + equals(c.getCost(), 8989, "connection cost is 8989"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (connectionCost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionCost:567}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), 567, "connection cost is 567"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (change connectionCost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionCost:567, maxConnections:-1}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1}); + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), 567, "connection cost is 567"); + e16.setConnectionCost(23); + var c2 = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(c2.getCost(), 23, "connection cost is 23 after change on endpoint"); + }); + + test(renderMode + ': _jsPlumb.connect (default bidirectional)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true}), + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.isBidirectional(), true, "default connection is bidirectional"); + }); + + test(renderMode + ': _jsPlumb.connect (bidirectional false)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true}), + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17, bidirectional:false}); + assertContextSize(3); + equals(c.isBidirectional(), false, "connection is not bidirectional"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (connectionsBidirectional)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionsBidirectional:true, maxConnections:-1}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.isBidirectional(), true, "connection is bidrectional"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (change connectionsBidirectional)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionsBidirectional:true, maxConnections:-1}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1}); + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.isBidirectional(), true, "connection is bidirectional"); + e16.setConnectionsBidirectional(false); + var c2 = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(c2.isBidirectional(), false, "connection is not bidirectional"); + }); + + test(renderMode + ": _jsPlumb.connect (two Endpoints - that have been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = _jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1, uuid:srcEndpointUuid}); + var e2 = _jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1, uuid:dstEndpointUuid}); + _jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ] }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertContextSize(3); + }); + + test(renderMode + ": _jsPlumb.connect (two Endpoints - that have not been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ], source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + var e1 = _jsPlumb.getEndpoint(srcEndpointUuid); + ok(e1 != null, "endpoint with src uuid added"); + ok(e1.canvas != null); + var e2 = _jsPlumb.getEndpoint(dstEndpointUuid); + ok(e2 != null, "endpoint with target uuid added"); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.connect (two Endpoints - that have been already added - by endpoint reference)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = _jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1}); + var e2 = _jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1}); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.connect (two elements)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.connect({ source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, straight)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, bezier, no params)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Bezier" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 150, "Bezier connector chose 150 curviness"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, bezier, curviness as int)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", { curviness:200 }] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Canvas Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 200, "Bezier connector chose 200 curviness"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, bezier, curviness as named option)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", {curviness:300}] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 300, "Bezier connector chose 300 curviness"); + }); + + test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Canvas Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as strings)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:["LeftMiddle", "RightMiddle"] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.5, conn.endpoints[0].anchor.y, "source anchor y"); + equals(1, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.5, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + + test(renderMode + ": _jsPlumb.connect (two argument method in which some data is reused across connections)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"), d19 = _addDiv("d19"); + var sharedData = { connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }; + var conn = _jsPlumb.connect({ source:d16, target:d17}, sharedData); + var conn2 = _jsPlumb.connect({ source:d18, target:d19}, sharedData); + assertContextSize(6); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 2, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(conn2.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + equals(0.3, conn2.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn2.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn2.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn2.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoint test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoints test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Dot, "Dot endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Blank Endpoint specified via 'endpoint' param)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Blank" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Blank Endpoint specified via 'endpoints' param)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Blank", "Blank" ] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Dot, "Dot endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, connector test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {}); + var e17 = _jsPlumb.addEndpoint(d17, {}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {}); + var e17 = _jsPlumb.addEndpoint(d17, {}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, anchors as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = "TopCenter", a17 = "BottomCenter"; + var e16 = _jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, 0.5, "endpoint 16 is at top center");equals(e16.anchor.y, 0, "endpoint 16 is at top center"); + equals(e17.anchor.x, 0.5, "endpoint 17 is at bottom center");equals(e17.anchor.y, 1, "endpoint 17 is at bottom center"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = [0,0.5,0,-1], a17 = [1,0.0,-1,-1]; + var e16 = _jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, a16[0]);equals(e16.anchor.y, a16[1]); + equals(e17.anchor.x, a17[0]);equals(e17.anchor.y, a17[1]); + equals(e16.anchor.getOrientation()[0], a16[2]); equals(e16.anchor.getOrientation()[1], a16[3]); + equals(e17.anchor.getOrientation()[0], a17[2]); equals(e17.anchor.getOrientation()[1], a17[3]); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchor')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchor:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:["TopCenter", "BottomCenter"]}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchors')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, default endpoint, supplied dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var anchors = [ [0.25, 0, 0, -1], [1, 0.25, 1, 0], [0.75, 1, 0, 1], [0, 0.75, -1, 0] ]; + _jsPlumb.connect({source:d1, target:d2, dynamicAnchors:anchors}); // auto connect with default endpoint and provided anchors + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.detach({source:d1, target:d2}); + // this changed in 1.3.5, because auto generated endpoints are now removed by detach. so i added the test below this one + // to check that the deleteEndpointsOnDetach flag is honoured. + assertEndpointCount("d1", 0, _jsPlumb);assertEndpointCount("d2", 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, default endpoint, supplied dynamic anchors, delete on detach false)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var anchors = [ [0.25, 0, 0, -1], [1, 0.25, 1, 0], [0.75, 1, 0, 1], [0, 0.75, -1, 0] ]; + _jsPlumb.connect({ + source:d1, + target:d2, + dynamicAnchors:anchors, + deleteEndpointsOnDetach:false + }); // auto connect with default endpoint and provided anchors + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.detach({source:d1, target:d2}); + // this changed in 1.3.5, because auto generated endpoints are now removed by detach. so i added this test + // to check that the deleteEndpointsOnDetach flag is honoured. + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, supplied endpoint and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = _jsPlumb.addEndpoint(d1, endpoint); + var e2 = _jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(3); + _jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, supplied endpoints using 'source' and 'target' (this test is identical to the one above apart from the param names), and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = _jsPlumb.addEndpoint(d1, endpoint); + var e2 = _jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + _jsPlumb.connect({source:e1, target:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(3); + _jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + " detachable parameter defaults to true on _jsPlumb.connect", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), true, "connections detachable by default"); + }); + + test(renderMode + " detachable parameter set to false on _jsPlumb.connect", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, detachable:false}); + equals(c.isDetachable(), false, "connection detachable"); + }); + + test(renderMode + " setDetachable on initially detachable connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), true, "connection initially detachable"); + c.setDetachable(false); + equals(c.isDetachable(), false, "connection not detachable"); + }); + + test(renderMode + " setDetachable on initially not detachable connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, detachable:false }); + equals(c.isDetachable(), false, "connection not initially detachable"); + c.setDetachable(true); + equals(c.isDetachable(), true, "connection now detachable"); + }); + + test(renderMode + " _jsPlumb.Defaults.ConnectionsDetachable", function() { + _jsPlumb.Defaults.ConnectionsDetachable = false; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), false, "connections not detachable by default (overrode the defaults)"); + _jsPlumb.Defaults.ConnectionsDetachable = true; + }); + + + test(renderMode + ": _jsPlumb.connect (testing for connection event callback)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var connectCallback = null, detachCallback = null; + _jsPlumb.bind("jsPlumbConnection", function(params) { + connectCallback = $.extend({}, params); + }); + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + detachCallback = $.extend({}, params); + }); + _jsPlumb.connect({source:d1, target:d2}); // auto connect with default endpoint and anchor set + ok(connectCallback != null, "connect callback was made"); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.detach({source:d1, target:d2}); + assertContextSize(2); + ok(detachCallback != null, "detach callback was made"); + }); + + test(renderMode + ": _jsPlumb.connect (setting cssClass on Connector)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2,cssClass:"CSS"}); + var has = function(clazz) { + var cn = c.connector.canvas.className, + cns = cn.constructor == String ? cn : cn.baseVal; + + return cns.indexOf(clazz) != -1; + }; + ok(has("CSS"), "custom cssClass set correctly"); + ok(has(_jsPlumb.connectorClass), "basic connector class set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (overlays, long-hand version)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = {width:40,length:40,location:0.7, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"}}; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + }); + + test(renderMode + ": _jsPlumb.connect (overlays, long-hand version, IDs specified)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + equals("aLabel", connection1.overlays[0].id); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + equals("anArrow", connection1.overlays[1].id); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays)", function() { + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays + overlays specified in connect call)", function() { + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, overlays:[ + ["Label", {id:"label"}] + ]}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("label") != null, "Label overlay created from connect call"); + }); + + test(renderMode + ": _jsPlumb.connect (default connection overlays)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + }); + + test(renderMode + ": _jsPlumb.connect (default connection overlays + overlays specified in connect call)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, overlays:[ + ["Label", {id:"label"}] + ]}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("label") != null, "Label overlay created from connect call"); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays + default connection overlays)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow2" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("arrow2") != null, "Arrow overlay created from connection defaults"); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays + default connection overlays)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow2" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, overlays:[ + ["Label", {id:"label"}] + ]}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("arrow2") != null, "Arrow overlay created from connection defaults"); + ok(c.getOverlay("label") != null, "Label overlay created from connect call"); + }); + + test(renderMode + ": _jsPlumb.connect (label overlay set using 'label')", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO" + }); + + equals(c.getLabel(), "FOO", "label is set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction, with string)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2}); + + c.setLabel("FOO"); + equals(c.getLabel(), "FOO", "label is set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction, with function)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2}); + + c.setLabel(function() { return "BAR"; }); + equals(c.getLabel()(), "BAR", "label is set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction, with params object)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2}); + + c.setLabel({ + label:"BAZ", + cssClass:"CLASSY", + location:0.9 + }); + var lo = c.getLabelOverlay(); + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "BAZ", "label overlay has correct value"); + equals(lo.getLocation(), 0.9, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction with existing label set, with params object)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO", + labelLocation:0.2 + }); + + var lo = c.getLabelOverlay(); + equals(lo.getLocation(), 0.2, "label overlay has correct location"); + + c.setLabel({ + label:"BAZ", + cssClass:"CLASSY", + location:0.9 + }); + + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "BAZ", "label overlay has correct value"); + equals(lo.getLocation(), 0.9, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (getLabelOverlay, label on connect call)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO" + }), + lo = c.getLabelOverlay(); + + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "FOO", "label overlay has correct value"); + equals(lo.getLocation(), 0.5, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (getLabelOverlay, label on connect call, location set)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO", + labelLocation:0.2 + }), + lo = c.getLabelOverlay(); + + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "FOO", "label overlay has correct value"); + equals(lo.getLocation(), 0.2, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (remove single overlay by id)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + connection1.removeOverlay("aLabel"); + equals(1, connection1.overlays.length); + equals("anArrow", connection1.overlays[0].id); + }); + + test(renderMode + ": _jsPlumb.connect (remove multiple overlays by id)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + connection1.removeOverlays("aLabel", "anArrow"); + equals(0, connection1.overlays.length); + }); + + test(renderMode + ": _jsPlumb.connect (overlays, short-hand version)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var loc = { location:0.7 }; + var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label", {label:"CONNECTION 1", location:0.3}], + ["Arrow", arrowSpec, loc] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + }); + + test(renderMode + ": _jsPlumb.connect (removeAllOverlays)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var loc = { location:0.7 }; + var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label", {label:"CONNECTION 1", location:0.3, cssClass:"PPPP"}], + ["Arrow", arrowSpec, loc] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + + connection1.removeAllOverlays(); + equals(0, connection1.overlays.length); + equals(0, $(".PPPP").length); + }); + + test(renderMode + ": _jsPlumb.connect, specify arrow overlay using string identifier only", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:["Arrow"]}); + equals(jsPlumb.Overlays[renderMode].Arrow, conn.overlays[0].constructor); + }); + + test(renderMode + ": Connection.getOverlay method, existing overlay", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("arrowOverlay"); + ok(overlay != null); + }); + + test(renderMode + ": Connection.getOverlay method, non-existent overlay", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("IDONTEXIST"); + ok(overlay == null); + }); + + test(renderMode + ": Overlay.setVisible method", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("arrowOverlay"); + ok(overlay.isVisible()); + overlay.setVisible(false); + ok(!overlay.isVisible()); + overlay.setVisible(true); + ok(overlay.isVisible()); + }); + + test(renderMode + ": _jsPlumb.connect (custom label overlay, set on Defaults, return plain DOM element)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Custom",{ id:"custom", create:function(connection) { + ok(connection != null, "we were passed in a connection"); + var d = document.createElement("div"); + d.setAttribute("custom", "true"); + d.innerHTML = connection.id; + return d; + }}] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + var o = c.getOverlay("custom"); + equals(o.getElement().getAttribute("custom"), "true", "custom overlay created correctly"); + equals(o.getElement().innerHTML, c.id, "custom overlay has correct value"); + }); + + test(renderMode + ": _jsPlumb.connect (custom label overlay, set on Defaults, return selector)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Custom",{ id:"custom", create:function(connection) { + ok(connection != null, "we were passed in a connection"); + return $("
" + connection.id + "
"); + }}] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + var o = c.getOverlay("custom"); + equals(o.getElement().getAttribute("custom"), "true", "custom overlay created correctly"); + equals(o.getElement().innerHTML, c.id, "custom overlay has correct value"); + }); + + // this test is for the original detach function; it should stay working after i mess with it + // a little. + test(renderMode + ": _jsPlumb.detach (by element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + var e3 = _jsPlumb.addEndpoint(d1); + var e4 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + _jsPlumb.connect({ sourceEndpoint:e3, targetEndpoint:e4 }); // make two connections to be sure this works ;) + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionCount(e3, 1); + assertConnectionCount(e4, 1); + + _jsPlumb.detach({source:"d1", target:"d2"}); + + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionCount(e3, 0); + assertConnectionCount(e4, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + // detach is being made to operate more like connect - by taking one argument with a whole + // bunch of possible params in it. if two args are passed in it will continue working + // in the old way. + test(renderMode + ": _jsPlumb.detach (params object, using element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({source:"d1", target:"d2"}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); +/* + test(renderMode + ": _jsPlumb.detach (params object, using target only)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e1 = _jsPlumb.addEndpoint(d1, {maxConnections:2}), + e2 = _jsPlumb.addEndpoint(d2), + e3 = _jsPlumb.addEndpoint(d3); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e3 }); + assertConnectionCount(e1, 2); + assertConnectionCount(e2, 1); + assertConnectionCount(e3, 1); + _jsPlumb.detach({target:"d2"}); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 0); + assertConnectionCount(e3, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1); + }); +*/ + test(renderMode + ": _jsPlumb.detach (params object, using element objects)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({source:d1, target:d2}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.detach (source and target as endpoint UUIDs)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {uuid:"abcdefg"}); + ok(_jsPlumb.getEndpoint("abcdefg") != null, "e1 exists"); + var e2 = _jsPlumb.addEndpoint(d2, {uuid:"hijklmn"}); + ok(_jsPlumb.getEndpoint("hijklmn") != null, "e2 exists"); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({uuids:["abcdefg", "hijklmn"]}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.detach (sourceEndpoint and targetEndpoint supplied)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.makeDynamicAnchors (longhand)", function() { + var anchors = [_jsPlumb.makeAnchor([0.2, 0, 0, -1], null, _jsPlumb), _jsPlumb.makeAnchor([1, 0.2, 1, 0], null, _jsPlumb), + _jsPlumb.makeAnchor([0.8, 1, 0, 1], null, _jsPlumb), _jsPlumb.makeAnchor([0, 0.8, -1, 0], null, _jsPlumb) ]; + var dynamicAnchor = _jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); + }); + + test(renderMode + ": _jsPlumb.makeDynamicAnchors (shorthand)", function() { + var anchors = [[0.2, 0, 0, -1], [1, 0.2, 1, 0], + [0.8, 1, 0, 1], [0, 0.8, -1, 0] ]; + var dynamicAnchor = _jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); + }); + + test(renderMode + ": Connection.isVisible/setVisible", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c1 = _jsPlumb.connect({source:d1,target:d2}); + equals(true, c1.isVisible(), "Connection is visible after creation."); + c1.setVisible(false); + equals(false, c1.isVisible(), "Connection is not visible after calling setVisible(false)."); + equals($(c1.connector.canvas).css("display"), "none"); + c1.setVisible(true); + equals(true, c1.isVisible(), "Connection is visible after calling setVisible(true)."); + equals($(c1.connector.canvas).css("display"), "block"); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible basic test (no connections)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals($(e1.canvas).css("display"), "none"); + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals($(e1.canvas).css("display"), "block"); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should track changes in the source, because it has only this connection.)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + var c1 = _jsPlumb.connect({source:e1, target:e2}); + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals(false, e2.isVisible(), "other Endpoint is not visible either."); + equals(false, c1.isVisible(), "connection between the two is not visible either."); + + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals(true, e2.isVisible(), "other Endpoint is visible too"); + equals(true, c1.isVisible(), "connection between the two is visible too."); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should not track changes in the source, because it has another connection.)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2, { maxConnections:2 }), e3 = _jsPlumb.addEndpoint(d3); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + var c1 = _jsPlumb.connect({source:e1, target:e2}); + var c2 = _jsPlumb.connect({source:e2, target:e3}); + + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals(true, e2.isVisible(), "other Endpoint should still be visible."); + equals(true, e3.isVisible(), "third Endpoint should still be visible."); + equals(false, c1.isVisible(), "connection between the two is not visible either."); + equals(true, c2.isVisible(), "other connection is visible."); + + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals(true, e2.isVisible(), "other Endpoint is visible too"); + equals(true, c1.isVisible(), "connection between the two is visible too."); + equals(true, c2.isVisible(), "other connection is visible."); + }); + + // tests of the functionality that allows a user to specify that they want elements appended to the document body + test(renderMode + " _jsPlumb.Defaults.Container, specified with a selector", function() { + _jsPlumb.Defaults.Container = $("body"); + equals($("#container")[0].childNodes.length, 0, "container has no nodes"); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals($("#container")[0].childNodes.length, 2, "container has two div elements"); // the divs we added have been added to the 'container' div. + // but we have told _jsPlumb to add its canvas to the body, so this connect call should not add another few elements to the container: + _jsPlumb.connect({source:d1, target:d2}); + equals($("#container")[0].childNodes.length, 2, "container still has two div elements"); + }); + + // tests of the functionality that allows a user to specify that they want elements appended to some specific container. + test(renderMode + " _jsPlumb.Defaults.Container, specified with DOM element", function() { + _jsPlumb.Defaults.Container = document.getElementsByTagName("body")[0]; + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length, "two divs added to the container"); // the divs we added have been added to the 'container' div. + // but we have told _jsPlumb to add its canvas to the body, so this connect call should not add another few elements to the container: + var bodyElementCount = $("body")[0].childNodes.length; + _jsPlumb.connect({source:d1, target:d2}); + equals(2, $("#container")[0].childNodes.length, "still only two children in container; elements were added to the body by _jsPlumb"); + // test to see if 3 elements have been added + equals(bodyElementCount + 3, $("body")[0].childNodes.length, "3 new elements added to the document body"); + }); + + test(renderMode + " container specified to connect call, with a selector", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length); // the divs we added have been added to the 'container' div. + var bodyElementCount = $("body")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to the body, so this connect call should not add another few elements to the container: + _jsPlumb.connect({source:d1, target:d2, container:$("body")}); + equals(2, $("#container")[0].childNodes.length); + equals(bodyElementCount + 3, $("body")[0].childNodes.length, "3 new elements added to the document body"); + }); + + test(renderMode + " container specified to connect call, with a string ID", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + equals(3, $("#container")[0].childNodes.length, "container has divs we added"); // the divs we added have been added to the 'container' div. + var d3ElementCount = $("#d3")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to "d3", so this connect call should not add another few elements to the container: + _jsPlumb.connect({source:d1, target:d2, container:"d3"}); + equals(3, $("#container")[0].childNodes.length, "container still has only the divs we added"); + equals(d3ElementCount + 3, $("#d3")[0].childNodes.length, "3 new elements added to div d3"); + }); + + test(renderMode + " container specified to addEndpoint call, with a selector", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length); // the divs we added have been added to the 'container' div. + var bodyElementCount = $("body")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to the body, so this connect call should not add another few elements to the container: + _jsPlumb.addEndpoint(d1, {container:$("body")}); + equals(2, $("#container")[0].childNodes.length); + equals(bodyElementCount + 1, $("body")[0].childNodes.length, "1 new element added to the document body"); + }); + + test(renderMode + " container specified to addEndpoint call, with a string ID", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + equals(3, $("#container")[0].childNodes.length, "container has divs we added"); // the divs we added have been added to the 'container' div. + var d3ElementCount = $("#d3")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to "d3", so this connect call should not add another few elements to the container: + _jsPlumb.addEndpoint(d1, { container:"d3" }); + equals(3, $("#container")[0].childNodes.length, "container still has only the divs we added"); + equals(d3ElementCount + 1, $("#d3")[0].childNodes.length, "1 new element added to div d3"); + }); + + test(renderMode + " detachable defaults to true when connection made between two endpoints", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2), + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection not detachable"); + }); + + test(renderMode + " connection detachable when target endpoint has connectionsDetachable set to true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2, {connectionsDetachable:true}), + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection detachable because connectionsDetachable was set on target endpoint"); + }); + + test(renderMode + " connection detachable when source endpoint has connectionsDetachable set to true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {connectionsDetachable:true}), e2 = _jsPlumb.addEndpoint(d2), + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection detachable because connectionsDetachable was set on source endpoint"); + }); + + test(renderMode + " Connector has 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.connector.type, "Bezier", "Bezier connector has type set"); + + var c2 = _jsPlumb.connect({source:d1, target:d2, connector:"Straight"}); + equals(c2.connector.type, "Straight", "Straight connector has type set"); + + var c3 = _jsPlumb.connect({source:d1, target:d2, connector:"Flowchart"}); + equals(c3.connector.type, "Flowchart", "Flowchart connector has type set"); + }); + + test(renderMode + " Endpoints have 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.endpoints[0].type, "Dot", "Dot endpoint has type set"); + + var c2 = _jsPlumb.connect({source:d1, target:d2, endpoints:["Rectangle", "Blank"]}); + equals(c2.endpoints[1].type, "Blank", "Blank endpoint has type set"); + equals(c2.endpoints[0].type, "Rectangle", "Rectangle endpoint has type set"); + }); + + test(renderMode + " Overlays have 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = _jsPlumb.connect({ + source:d1, + target:d2, + overlays:[ "Arrow", "Label", "PlainArrow", "Diamond" ] + }); + equals(c.overlays[0].type, "Arrow", "Arrow overlay has type set"); + equals(c.overlays[1].type, "Label", "Label overlay has type set"); + equals(c.overlays[2].type, "PlainArrow", "PlainArrow overlay has type set"); + equals(c.overlays[3].type, "Diamond", "Diamond overlay has type set"); + }); + + test(renderMode + " _jsPlumb.hide, original one-arg version", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + _jsPlumb.hide(d1); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(true, e1.isVisible(), "endpoint 1 is still visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + _jsPlumb.show(d1); + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + }); + + test(renderMode + " _jsPlumb.hide, two-arg version, endpoints should also be hidden", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + _jsPlumb.show(d1); // now show d1, but do not alter the endpoints. e1 should still be hidden + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + test(renderMode + " _jsPlumb.show, two-arg version, endpoints should become visible", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + _jsPlumb.show(d1, true); // now show d1, and alter the endpoints. e1 should be visible. + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(true, e1.isVisible(), "endpoint 1 is visible again."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + test(renderMode + " _jsPlumb.show, two-arg version, endpoints should become visible, but not all connections, because some other endpoints are not visible.", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e11 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + e3 = _jsPlumb.addEndpoint(d3, e), + c1 = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e11, target:e3}); + + // we now have d1 connected to both d3 and d2. we'll hide d1, and everything on d1 should be hidden. + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "connection 1 is no longer visible."); + equals(false, c2.isVisible(), "connection 2 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(false, e11.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + equals(true, e3.isVisible(), "endpoint 3 is still visible."); + + // now, we will also hide d3. making d1 visible again should NOT result in c2 becoming visible, because the other endpoint + // for c2 is e3, which is not visible. + _jsPlumb.hide(d3, true); + equals(false, e3.isVisible(), "endpoint 3 is no longer visible."); + + _jsPlumb.show(d1, true); // now show d1, and alter the endpoints. e1 should be visible, c1 should be visible, but c2 should not. + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(false, c2.isVisible(), "Connection 2 is not visible."); + equals(true, e1.isVisible(), "endpoint 1 is visible again."); + equals(true, e11.isVisible(), "endpoint 11 is visible again."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + equals(false, e3.isVisible(), "endpoint 3 is still not visible."); + }); + + /* + test(renderMode + " _jsPlumb.hide, two-arg version, endpoints should also be hidden", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + /** + * test for issue 132: label leaves its element in the DOM after it has been + * removed from a connection. + */ + test(renderMode + " label cleans itself up properly", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label", cssClass:"foo"}] + ]}); + ok($(".foo").length == 1, "label element exists in DOM"); + c.removeOverlay("label"); + ok($(".foo").length == 0, "label element does not exist in DOM"); + }); + + test(renderMode + " arrow cleans itself up properly", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Arrow", {id:"arrow"}] + ]}); + ok(c.getOverlay("arrow") != null, "arrow overlay exists"); + c.removeOverlay("arrow"); + ok(c.getOverlay("arrow") == null, "arrow overlay has been removed"); + }); + + test(renderMode + " label overlay getElement function", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label"}] + ]}); + ok(c.getOverlay("label").getElement() != null, "label overlay exposes element via getElement method"); + }); + + test(renderMode + " label overlay provides getLabel and setLabel methods", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label", label:"foo"}] + ]}); + var o = c.getOverlay("label"), e = o.getElement(); + ok(e.innerHTML == "foo", "label text is set to original value"); + o.setLabel("baz"); + equals(e.innerHTML, "baz", "label text is set to new value 'baz'"); + equals(o.getLabel(), "baz", "getLabel function works correctly with String"); + // now try functions + var aFunction = function() { return "aFunction"; }; + o.setLabel(aFunction); + equals(e.innerHTML, "aFunction", "label text is set to new value from Function"); + equals(o.getLabel(), aFunction, "getLabel function works correctly with Function"); + }); + + test(renderMode + " parameters object works for Endpoint", function() { + var d1 = _addDiv("d1"), + f = function() { alert("FOO!"); }, + e = _jsPlumb.addEndpoint(d1, { + isSource:true, + parameters:{ + "string":"param1", + "int":4, + "function":f + } + }); + ok(e.getParameter("string") === "param1", "getParameter(String) works correctly"); + ok(e.getParameter("int") === 4, "getParameter(int) works correctly"); + ok(e.getParameter("function") == f, "getParameter(Function) works correctly"); + }); + + test(renderMode + " parameters object works for Connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + f = function() { alert("FOO!"); }; + var c = _jsPlumb.connect({ + source:d1, + target:d2, + parameters:{ + "string":"param1", + "int":4, + "function":f + } + }); + ok(c.getParameter("string") === "param1", "getParameter(String) works correctly"); + ok(c.getParameter("int") === 4, "getParameter(int) works correctly"); + ok(c.getParameter("function") == f, "getParameter(Function) works correctly"); + }); + + test(renderMode + " parameters set on Endpoints and Connections are all merged, and merged correctly at that.", function() { + var d1 = _addDiv("d1"), + d2 = _addDiv("d2"), + e = _jsPlumb.addEndpoint(d1, { + isSource:true, + parameters:{ + "string":"sourceEndpoint", + "int":0, + "function":function() { return "sourceEndpoint"; } + } + }), + e2 = _jsPlumb.addEndpoint(d2, { + isTarget:true, + parameters:{ + "int":1, + "function":function() { return "targetEndpoint"; } + } + }), + c = _jsPlumb.connect({source:e, target:e2, parameters:{ + "function":function() { return "connection"; } + }}); + + ok(c.getParameter("string") === "sourceEndpoint", "getParameter(String) works correctly"); + ok(c.getParameter("int") === 1, "getParameter(int) works correctly"); + ok(c.getParameter("function")() == "connection", "getParameter(Function) works correctly"); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers standard connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2}); + equals(_jsPlumb.anchorManager.getConnectionsFor("d1").length, 1); + equals(_jsPlumb.anchorManager.getEndpointsFor("d1").length, 1); + equals(_jsPlumb.anchorManager.getConnectionsFor("d2").length, 1); + equals(_jsPlumb.anchorManager.getEndpointsFor("d2").length, 1); + var c2 = _jsPlumb.connect({source:d1, target:d2}); + equals(_jsPlumb.anchorManager.getConnectionsFor("d1").length, 2); + equals(_jsPlumb.anchorManager.getConnectionsFor("d2").length, 2); + equals(_jsPlumb.anchorManager.getEndpointsFor("d1").length, 2); + equals(_jsPlumb.anchorManager.getEndpointsFor("d2").length, 2); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers dynamic anchor connection, and removes it.", function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var c = _jsPlumb.connect({source:d3, target:d4, anchors:["AutoDefault", "AutoDefault"]}); + + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 1); + + var c2 = _jsPlumb.connect({source:d3, target:d4}); + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 2); + equals(_jsPlumb.anchorManager.getConnectionsFor("d4").length, 2); + + equals(_jsPlumb.anchorManager.getEndpointsFor("d3").length, 2); + _jsPlumb.detach(c); + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 1); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers continuous anchor connection, and removes it.", function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var c = _jsPlumb.connect({source:d3, target:d4, anchors:["Continuous", "Continuous"]}); + + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 1); + equals(_jsPlumb.anchorManager.getConnectionsFor("d4").length, 1); + + _jsPlumb.detach(c); + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 0); + equals(_jsPlumb.anchorManager.getConnectionsFor("d4").length, 0); + + _jsPlumb.reset(); + equals(_jsPlumb.anchorManager.getEndpointsFor("d4").length, 0); + }); + +// ------------- utility functions - math stuff, mostly -------------------------- + + var tolerance = 0.00000005, withinTolerance = function(v1, v2, msg) { + if (Math.abs(v1 - v2) < tolerance) ok(true, msg + "; expected " + v1 + " and got it"); + else { + ok(false, msg + "; expected " + v1 + " got " + v2); + } + }; + test(renderMode + " jsPlumbUtil.gradient, segment 1", function() { + var p1 = [2,2], p2 = [3,1], m = jsPlumbUtil.gradient(p1,p2); + equals(m, -1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.normal, segment 1", function() { + var p1 = [2,2], p2 = [3,1], m = jsPlumbUtil.normal(p1,p2); + equals(m, 1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.gradient, segment 2", function() { + var p1 = [2,2], p2 = [3,3], m = jsPlumbUtil.gradient(p1,p2); + equals(m, 1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.normal, segment 2", function() { + var p1 = [2,2], p2 = [3,3], m = jsPlumbUtil.normal(p1,p2); + equals(m, -1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.gradient, segment 3", function() { + var p1 = [2,2], p2 = [1,3], m = jsPlumbUtil.gradient(p1,p2); + equals(m, -1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.normal, segment 3", function() { + var p1 = [2,2], p2 = [1,3], m = jsPlumbUtil.normal(p1,p2); + equals(m, 1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.gradient, segment 4", function() { + var p1 = [2,2], p2 = [1,1], m = jsPlumbUtil.gradient(p1,p2); + equals(m, 1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.normal, segment 4", function() { + var p1 = [2,2], p2 = [1,1], m = jsPlumbUtil.normal(p1,p2); + equals(m, -1, "normal calculated correctly for simple case"); + }); + test(renderMode + "jsPlumbUtil.pointOnLine, segment 1", function() { + var p1 = {x:2,y:2}, p2={x:3, y:1}, + target = jsPlumbUtil.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumbUtil.pointOnLine, segment 2", function() { + var p1 = {x:2,y:2}, p2={x:3, y:3}, + target = jsPlumbUtil.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumbUtil.pointOnLine, segment 3", function() { + var p1 = {x:2,y:2}, p2={x:1, y:3}, + target = jsPlumbUtil.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumbUtil.pointOnLine, segment 4", function() { + var p1 = {x:2,y:2}, p2={x:1, y:1}, + target = jsPlumbUtil.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumbUtil.perpendicularLineTo, segment 1", function() { + var p1 = {x:2, y:2}, p2={x:3, y:1}, m = jsPlumbUtil.gradient(p1, p2), + l = jsPlumbUtil.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(4, l[0].x, "point 1 x is correct"); + withinTolerance(2, l[0].y, "point 1 y is correct"); + + withinTolerance(2, l[1].x, "point 2 x is correct"); + withinTolerance(0, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumbUtil.perpendicularLineTo, segment 2", function() { + var p1 = {x:2, y:2}, p2={x:3, y:3}, m = jsPlumbUtil.gradient(p1, p2), + l = jsPlumbUtil.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(4, l[0].x, "point 1 x is correct"); + withinTolerance(2, l[0].y, "point 1 y is correct"); + + withinTolerance(2, l[1].x, "point 2 x is correct"); + withinTolerance(4, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumbUtil.perpendicularLineTo, segment 3", function() { + var p1 = {x:2, y:2}, p2={x:1, y:3}, m = jsPlumbUtil.gradient(p1, p2), + l = jsPlumbUtil.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(2, l[0].x, "point 1 x is correct"); + withinTolerance(4, l[0].y, "point 1 y is correct"); + + withinTolerance(0, l[1].x, "point 2 x is correct"); + withinTolerance(2, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumbUtil.perpendicularLineTo, segment 4", function() { + var p1 = {x:2, y:2}, p2={x:1, y:1}, m = jsPlumbUtil.gradient(p1, p2), + l = jsPlumbUtil.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(2, l[0].x, "point 1 x is correct"); + withinTolerance(0, l[0].y, "point 1 y is correct"); + + withinTolerance(0, l[1].x, "point 2 x is correct"); + withinTolerance(2, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumbUtil.intersects, with intersection", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 3, y:4, w:3, h:3}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, with no intersection", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 13, y:4, w:3, h:3}; + + ok(!jsPlumbUtil.intersects(r1, r2), "r1 and r2 do not intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, with intersection, equal Y", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 1, y:2, w:3, h:6}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, with intersection, equal X", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 2, y:1, w:4, h:6}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, identical rectangles", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 2, y:2, w:4, h:6}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, corners touch (intersection)", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 6, y:8, w:3, h:3}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, one rectangle contained within the other", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 0, y:0, w:23, h:23}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "setImage on Endpoint", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + originalUrl = "../../img/endpointTest1.png", + e = { + endpoint:[ "Image", { src:originalUrl } ] + }, + ep = _jsPlumb.addEndpoint(d1, e); + }); + test(renderMode + "setImage on Endpoint, with supplied onload", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { + endpoint:[ "Image", { + src:"../../img/endpointTest1.png", + onload:function(imgEp) { + equals("../../img/endpointTest1.png", imgEp.img.src); + equals(ep.endpoint.canvas.getAttribute("src"), imgEp.img.src); + } + } ] + }, + ep = _jsPlumb.addEndpoint(d1, e); + ep.setImage("../../img/littledot.png", function(imgEp) { + equals("../../img/littledot.png", imgEp.img.src); + equals(ep.endpoint.canvas.getAttribute("src"), imgEp.img.src); + }); + }); + + test(renderMode + "attach endpoint to table when desired element was td", function() { + var table = document.createElement("table"), + tr = document.createElement("tr"), + td = document.createElement("td"); + table.appendChild(tr); tr.appendChild(td); + document.body.appendChild(table); + var ep = _jsPlumb.addEndpoint(td); + equals(ep.canvas.parentNode.tagName.toLowerCase(), "table"); + }); + +// issue 190 - regressions with getInstance. these tests ensure that generated ids are unique across +// instances. + + test(renderMode + " id clashes between instances", function() { + var d1 = document.createElement("div"), + d2 = document.createElement("div"), + _jsp2 = jsPlumb.getInstance(); + + document.body.appendChild(d1); + document.body.appendChild(d2); + + _jsPlumb.addEndpoint(d1); + _jsp2.addEndpoint(d2); + + var id1 = d1.getAttribute("id"), + id2 = d2.getAttribute("id"); + + var idx = id1.indexOf("_"), idx2 = id1.lastIndexOf("_"), v1 = id1.substring(idx, idx2); + var idx3 = id2.indexOf("_"), idx4 = id2.lastIndexOf("_"), v2 = id2.substring(idx3, idx4); + + ok (v1 != v2, "instance versions are different : " + v1 + " : " + v2); + }); + + test(renderMode + " id clashes between instances", function() { + var d1 = document.createElement("div"), + d2 = document.createElement("div"), + _jsp2 = jsPlumb.getInstance(); + + document.body.appendChild(d1); + document.body.appendChild(d2); + + _jsPlumb.addEndpoint(d1); + _jsPlumb.addEndpoint(d2); + + var id1 = d1.getAttribute("id"), + id2 = d2.getAttribute("id"); + + var idx = id1.indexOf("_"), idx2 = id1.lastIndexOf("_"), v1 = id1.substring(idx, idx2); + var idx3 = id2.indexOf("_"), idx4 = id2.lastIndexOf("_"), v2 = id2.substring(idx3, idx4); + + ok (v1 == v2, "instance versions are the same : " + v1 + " : " + v2); + }); + + test(renderMode + " importDefaults", function() { + _jsPlumb.Defaults.Anchors = ["LeftMiddle", "RightMiddle"]; + var d1 = _addDiv("d1"), + d2 = _addDiv(d2), + c = _jsPlumb.connect({source:d1, target:d2}), + e = c.endpoints[0]; + + equals(e.anchor.x, 0, "left middle anchor"); + equals(e.anchor.y, 0.5, "left middle anchor"); + + _jsPlumb.importDefaults({ + Anchors:["TopLeft", "TopRight"] + }); + + var conn = _jsPlumb.connect({source:d1, target:d2}), + e1 = conn.endpoints[0], e2 = conn.endpoints[1]; + + equals(e1.anchor.x, 0, "top leftanchor"); + equals(e2.anchor.y, 0, "top left anchor"); + equals(e2.anchor.x, 1, "top right anchor"); + equals(e2.anchor.y, 0, "top right anchor"); + + }); + + test(renderMode + " restoreDefaults", function() { + _jsPlumb.importDefaults({ + Anchors:["TopLeft", "TopRight"] + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), conn = _jsPlumb.connect({source:d1, target:d2}), + e1 = conn.endpoints[0], e2 = conn.endpoints[1]; + + equals(e1.anchor.x, 0, "top leftanchor"); + equals(e2.anchor.y, 0, "top left anchor"); + equals(e2.anchor.x, 1, "top right anchor"); + equals(e2.anchor.y, 0, "top right anchor"); + + _jsPlumb.restoreDefaults(); + + var conn2 = _jsPlumb.connect({source:d1, target:d2}), + e3 = conn2.endpoints[0], e4 = conn2.endpoints[1]; + + equals(e3.anchor.x, 0.5, "bottom center anchor"); + equals(e3.anchor.y, 1, "bottom center anchor"); + equals(e4.anchor.x, 0.5, "bottom center anchor"); + equals(e4.anchor.y, 1, "bottom center anchor"); + }); + +// setId function + + test(renderMode + " setId, taking two strings, only default scope", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId("d1", "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a selector and a string, only default scope", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1"), "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a DOM element and a string, only default scope", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1")[0], "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking two strings, mix of scopes", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2, scope:"FOO"}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId("d1", "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a selector and a string, mix of scopes", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2, scope:"FOO"}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1"), "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a DOM element and a string, mix of scopes", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2, scope:"FOO"}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1")[0], "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setIdChanged, ", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + $("#d1").attr("id", "d3"); + + _jsPlumb.setIdChanged("d1", "d3"); + + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " endpoint hide/show should hide/show overlays", function() { + _addDiv("d1"); + var e1 = _jsPlumb.addEndpoint("d1", { + overlays:[ + [ "Label", { id:"label", label:"foo" } ] + ] + }), + o = e1.getOverlay("label"); + + ok(o.isVisible(), "overlay is initially visible"); + _jsPlumb.hide("d1", true); + ok(!o.isVisible(), "overlay is no longer visible"); + }); + + test(renderMode + " connection hide/show should hide/show overlays", function() { + _addDiv("d1");_addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", + overlays:[ + [ "Label", { id:"label", label:"foo" } ] + ] + }), + o = c.getOverlay("label"); + + ok(o.isVisible(), "overlay is initially visible"); + _jsPlumb.hide("d1", true); + ok(!o.isVisible(), "overlay is no longer visible"); + }); + + test(renderMode + " select, basic test", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2"}), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 1, "one connection selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; dont filter on scope.", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 2, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scope", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + s = _jsPlumb.select({source:"d1", scope:"FOO"}); + + equals(s.length, 1, "one connection selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scopes", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + c3 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAZ"}) + s = _jsPlumb.select({source:"d1", scope:["FOO","BAR"]}); + + equals(s.length, 2, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; scope but no scope filter; single source id", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + c3 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAZ"}), + c4 = _jsPlumb.connect({source:"d2", target:"d1", scope:"BOZ"}), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 3, "three connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scopes; single source id", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + c3 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAZ"}), + c4 = _jsPlumb.connect({source:"d2", target:"d1", scope:"BOZ"}), + s = _jsPlumb.select({source:"d1", scope:["FOO","BAR", "BOZ"]}); + + equals(s.length, 2, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scope; dont supply sourceid", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + s = _jsPlumb.select({ scope:"FOO" }); + + equals(s.length, 1, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scope; dont supply sourceid", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d2", target:"d1", scope:"BAR"}), + s = _jsPlumb.select({ scope:"FOO" }); + + equals(s.length, 1, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, two connections, with overlays", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({ + source:"d1", + target:"d2", + overlays:[ + ["Label", {id:"l"}] + ] + }), + c2 = _jsPlumb.connect({ + source:"d1", + target:"d2", + overlays:[ + ["Label", {id:"l"}] + ] + }), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 2, "two connections selected"); + ok(s.get(0).getOverlay("l") != null, "connection has overlay"); + ok(s.get(1).getOverlay("l") != null, "connection has overlay"); + }); + + test(renderMode + " select, chaining with setHover and hideOverlay", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({ + source:"d1", + target:"d2", + overlays:[ + ["Label", {id:"l"}] + ] + }); + s = _jsPlumb.select({source:"d1"}); + + s.setHover(false).hideOverlay("l"); + + ok(!(s.get(0).isHover()), "connection is not hover"); + ok(!(s.get(0).getOverlay("l").isVisible()), "overlay is not visible"); + }); + + test(renderMode + " select, .each function", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10) + }); + } + + var s = _jsPlumb.select(); + equals(s.length, 5, "there are five connections"); + + var t = ""; + s.each(function(connection) { + t += "f"; + }); + equals("fffff", t, ".each is working"); + }); + + test(renderMode + " select, multiple connections + chaining", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + overlays:[ + ["Arrow", {location:0.3}], + ["Arrow", {location:0.7}] + ] + }); + } + + var s = _jsPlumb.select().removeAllOverlays().setParameter("foo", "bar").setHover(false).setLabel("baz"); + equals(s.length, 5, "there are five connections"); + + for (var j = 0; j < 5; j++) { + equals(s.get(j).getOverlays().length, 1, "one overlay: the label"); + equals(s.get(j).getParameter("foo"), "bar", "parameter foo has value 'bar'"); + ok(!(s.get(j).isHover()), "hover is set to false"); + equals(s.get(j).getLabel(), "baz", "label is set to 'baz'"); + } + }); + + test(renderMode + " select, simple getter", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + label:"FOO" + }); + } + + var lbls = _jsPlumb.select().getLabel(); + equals(lbls.length, 5, "there are five labels"); + + for (var j = 0; j < 5; j++) { + equals(lbls[j][0], "FOO", "label has value 'FOO'"); + } + }); + + test(renderMode + " select, getter + chaining", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + label:"FOO" + }); + } + + var params = _jsPlumb.select().removeAllOverlays().setParameter("foo", "bar").getParameter("foo"); + equals(params.length, 5, "there are five params"); + + for (var j = 0; j < 5; j++) { + equals(params[j][0], "bar", "parameter has value 'bar'"); + } + }); + + + test(renderMode + " select, detach method", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + label:"FOO" + }); + } + + var params = _jsPlumb.select().detach(); + + equals(_jsPlumb.select().length, 0, "there are no connections"); + }); + + test(renderMode + " select, repaint method", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + label:"FOO" + }); + } + + var len = _jsPlumb.select().repaint().length; + + equals(len, 5, "there are five connections"); + }); + + + // selectEndpoints + test(renderMode + " selectEndpoints, basic tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1), + e2 = _jsPlumb.addEndpoint(d1); + + equals(_jsPlumb.selectEndpoints().length, 2, "there are two endpoints"); + equals(_jsPlumb.selectEndpoints({element:"d1"}).length, 2, "there are two endpoints on d1"); + equals(_jsPlumb.selectEndpoints({element:"d2"}).length, 0, "there are 0 endpoints on d2"); + + equals(_jsPlumb.selectEndpoints({source:"d1"}).length, 0, "there are zero source endpoints on d1"); + equals(_jsPlumb.selectEndpoints({target:"d1"}).length, 0, "there are zero target endpoints on d1"); + equals(_jsPlumb.selectEndpoints({source:"d2"}).length, 0, "there are zero source endpoints on d2"); + equals(_jsPlumb.selectEndpoints({target:"d2"}).length, 0, "there are zero target endpoints on d2"); + + equals(_jsPlumb.selectEndpoints({source:"d1", scope:"FOO"}).length, 0, "there are zero source endpoints on d1 with scope FOO"); + + _jsPlumb.addEndpoint("d2", { scope:"FOO", isSource:true }); + equals(_jsPlumb.selectEndpoints({source:"d2", scope:"FOO"}).length, 1, "there is one source endpoint on d2 with scope FOO"); + + equals(_jsPlumb.selectEndpoints({element:["d2", "d1"]}).length, 3, "there are three endpoints between d2 and d1"); + }); + + test(renderMode + " selectEndpoints, basic tests, various input argument formats", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1), + e2 = _jsPlumb.addEndpoint(d1); + + equals(_jsPlumb.selectEndpoints({element:"d1"}).length, 2, "using id, there are two endpoints on d1"); + equals(_jsPlumb.selectEndpoints({element:d1}).length, 2, "using dom element, there are two endpoints on d1"); + equals(_jsPlumb.selectEndpoints({element:$("#d1")}).length, 2, "using selector, there are two endpoints on d1"); + equals(_jsPlumb.selectEndpoints({element:$(d1)}).length, 2, "using selector with dom element, there are two endpoints on d1"); + + }); + + test(renderMode + " selectEndpoints, basic tests, scope", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {scope:"FOO"}), + e2 = _jsPlumb.addEndpoint(d1); + + equals(_jsPlumb.selectEndpoints({element:"d1"}).length, 2, "using id, there are two endpoints on d1"); + equals(_jsPlumb.selectEndpoints({element:"d1", scope:"FOO"}).length, 1, "using id, there is one endpoint on d1 with scope 'FOO'"); + _jsPlumb.addEndpoint(d1, {scope:"BAR"}), + equals(_jsPlumb.selectEndpoints({element:"d1", scope:"FOO"}).length, 1, "using id, there is one endpoint on d1 with scope 'BAR'"); + equals(_jsPlumb.selectEndpoints({element:"d1", scope:["BAR", "FOO"]}).length, 2, "using id, there are two endpoints on d1 with scope 'BAR' or 'FOO'"); + }); + + test(renderMode + " selectEndpoints, isSource tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true}), + e2 = _jsPlumb.addEndpoint(d1), + e3 = _jsPlumb.addEndpoint(d2, {isSource:true}); + + equals(_jsPlumb.selectEndpoints({source:"d1"}).length, 1, "there is one source endpoint on d1"); + equals(_jsPlumb.selectEndpoints({target:"d1"}).length, 0, "there are zero target endpoints on d1"); + + equals(_jsPlumb.selectEndpoints({source:"d2"}).length, 1, "there is one source endpoint on d2"); + + equals(_jsPlumb.selectEndpoints({source:["d2", "d1"]}).length, 2, "there are two source endpoints between d1 and d2"); + }); + + test(renderMode + " selectEndpoints, isTarget tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isTarget:true}), + e2 = _jsPlumb.addEndpoint(d1), + e3 = _jsPlumb.addEndpoint(d2, {isTarget:true}); + + equals(_jsPlumb.selectEndpoints({target:"d1"}).length, 1, "there is one target endpoint on d1"); + equals(_jsPlumb.selectEndpoints({source:"d1"}).length, 0, "there are zero source endpoints on d1"); + + equals(_jsPlumb.selectEndpoints({target:"d2"}).length, 1, "there is one target endpoint on d2"); + + equals(_jsPlumb.selectEndpoints({target:["d2", "d1"]}).length, 2, "there are two target endpoints between d1 and d2"); + }); + + test(renderMode + " selectEndpoints, isSource + isTarget tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true}), + e2 = _jsPlumb.addEndpoint(d1), + e3 = _jsPlumb.addEndpoint(d1, {isSource:true}), + e4 = _jsPlumb.addEndpoint(d1, {isTarget:true}); + + equals(_jsPlumb.selectEndpoints({source:"d1"}).length, 2, "there are two source endpoints on d1"); + equals(_jsPlumb.selectEndpoints({target:"d1"}).length, 2, "there are two target endpoints on d1"); + + equals(_jsPlumb.selectEndpoints({target:"d1", source:"d1"}).length, 1, "there is one source and target endpoint on d1"); + + equals(_jsPlumb.selectEndpoints({element:"d1"}).length, 4, "there are four endpoints on d1"); + + }); + + test(renderMode + " selectEndpoints, delete endpoints", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true}); + + equals(_jsPlumb.selectEndpoints({element:"d1"}).length, 1, "there is one endpoint on d1"); + _jsPlumb.selectEndpoints({source:"d1"}).delete(); + equals(_jsPlumb.selectEndpoints({element:"d1"}).length, 0, "there are zero endpoints on d1"); + }); + + test(renderMode + " selectEndpoints, detach connections", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true}), + e2 = _jsPlumb.addEndpoint(d2, {isSource:true, isTarget:true}); + + _jsPlumb.connect({source:e1, target:e2}); + + equals(e1.connections.length, 1, "there is one connection on d1's endpoint"); + equals(e2.connections.length, 1, "there is one connection on d2's endpoint"); + + _jsPlumb.selectEndpoints({source:"d1"}).detachAll(); + + equals(e1.connections.length, 0, "there are zero connections on d1's endpoint"); + equals(e2.connections.length, 0, "there are zero connections on d2's endpoint"); + }); + + test(renderMode + " selectEndpoints, hover tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true}); + + equals(e1.isHover(), false, "hover not set"); + _jsPlumb.selectEndpoints({source:"d1"}).setHover(true); + equals(e1.isHover(), true, "hover set"); + _jsPlumb.selectEndpoints({source:"d1"}).setHover(false); + equals(e1.isHover(), false, "hover no longer set"); + }); + + test(renderMode + " selectEndpoints, setEnabled tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true}); + + equals(e1.isEnabled(), true, "endpoint is enabled"); + _jsPlumb.selectEndpoints({source:"d1"}).setEnabled(false); + equals(e1.isEnabled(), false, "endpoint not enabled"); + }); + + test(renderMode + " selectEndpoints, setEnabled tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true}); + + equals(e1.isEnabled(), true, "endpoint is enabled"); + var e = _jsPlumb.selectEndpoints({source:"d1"}).isEnabled(); + equals(e[0][0], true, "endpoint enabled"); + _jsPlumb.selectEndpoints({source:"d1"}).setEnabled(false); + e = _jsPlumb.selectEndpoints({source:"d1"}).isEnabled(); + equals(e[0][0], false, "endpoint not enabled"); + }); + +// setPaintStyle/getPaintStyle tests + + test(renderMode + " setPaintStyle", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), c = _jsPlumb.connect({source:d1, target:d2}); + c.setPaintStyle({strokeStyle:"FOO", lineWidth:999}); + equals(c.paintStyleInUse.strokeStyle, "FOO", "strokeStyle was set"); + equals(c.paintStyleInUse.lineWidth, 999, "lineWidth was set"); + + c.setHoverPaintStyle({strokeStyle:"BAZ", lineWidth:444}); + c.setHover(true); + equals(c.paintStyleInUse.strokeStyle, "BAZ", "strokeStyle was set"); + equals(c.paintStyleInUse.lineWidth, 444, "lineWidth was set"); + + equals(c.getPaintStyle().strokeStyle, "FOO", "getPaintStyle returns correct value"); + equals(c.getHoverPaintStyle().strokeStyle, "BAZ", "getHoverPaintStyle returns correct value"); + }); + +// ******************* getEndpoints ************************************************ + + test(renderMode + " getEndpoints", function() { + _addDiv("d1");_addDiv("d2"); + + _jsPlumb.addEndpoint("d1"); + _jsPlumb.addEndpoint("d2"); + _jsPlumb.addEndpoint("d1"); + + var e = _jsPlumb.getEndpoints("d1"), + e2 = _jsPlumb.getEndpoints("d2"); + equals(e.length, 2, "two endpoints on d1"); + equals(e2.length, 1, "one endpoint on d2"); + }); + +// connection type tests - types, type extension, set types, get types etc. + test(renderMode + " set connection type on existing connection", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" } + }; + + _jsPlumb.registerConnectionType("basic", basicType); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("basic"); + equals(c.getPaintStyle().lineWidth, 4, "paintStyle lineWidth is 4"); + equals(c.getPaintStyle().strokeStyle, "yellow", "paintStyle strokeStyle is yellow"); + equals(c.getHoverPaintStyle().strokeStyle, "blue", "paintStyle strokeStyle is yellow"); + equals(c.getHoverPaintStyle().lineWidth, 4, "hoverPaintStyle linewidth is 6"); + }); + + test(renderMode + " set connection type on existing connection then change type", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" } + }; + var otherType = { + connector:"Bezier", + paintStyle:{ strokeStyle:"red", lineWidth:14 }, + hoverPaintStyle:{ strokeStyle:"green" } + }; + + _jsPlumb.registerConnectionType("basic", basicType); + _jsPlumb.registerConnectionType("other", otherType); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("basic"); + equals(c.getPaintStyle().lineWidth, 4, "paintStyle lineWidth is 4"); + equals(c.getPaintStyle().strokeStyle, "yellow", "paintStyle strokeStyle is yellow"); + equals(c.getHoverPaintStyle().strokeStyle, "blue", "hoverPaintStyle strokeStyle is blue"); + equals(c.getHoverPaintStyle().lineWidth, 4, "hoverPaintStyle linewidth is 6"); + + c.setType("other"); + equals(c.getPaintStyle().lineWidth, 14, "paintStyle lineWidth is 14"); + equals(c.getPaintStyle().strokeStyle, "red", "paintStyle strokeStyle is red"); + equals(c.getHoverPaintStyle().strokeStyle, "green", "hoverPaintStyle strokeStyle is green"); + equals(c.getHoverPaintStyle().lineWidth, 14, "hoverPaintStyle linewidth is 14"); + }); + + test(renderMode + " set connection type on existing connection, overlays should be set", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }; + + _jsPlumb.registerConnectionType("basic", basicType); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("basic"); + equals(c.getOverlays().length, 1, "one overlay"); + }); + + test(renderMode + " set connection type on existing connection, overlays should be removed with second type", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }; + var otherType = { + connector:"Bezier" + }; + _jsPlumb.registerConnectionType("basic", basicType); + _jsPlumb.registerConnectionType("other", otherType); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("basic"); + equals(c.getOverlays().length, 1, "one overlay"); + c.setType("other"); + equals(c.getOverlays().length, 0, "no overlays"); + equals(c.getPaintStyle().lineWidth, _jsPlumb.Defaults.PaintStyle.lineWidth, "paintStyle lineWidth is default"); + }); + + test(renderMode + " set connection type on existing connection, hasType + toggleType", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }; + + _jsPlumb.registerConnectionTypes({ + "basic": basicType + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("basic"); + equals(c.hasType("basic"), true, "connection has 'basic' type"); + c.toggleType("basic"); + equals(c.hasType("basic"), false, "connection does not have 'basic' type"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + c.toggleType("basic"); + equals(c.hasType("basic"), true, "connection has 'basic' type"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + equals(c.getOverlays().length, 1, "one overlay"); + + }); + + test(renderMode + " set connection type on existing connection, merge tests", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }; + // this tests all three merge types: connector should overwrite, linewidth should be inserted into + // basic type's params, and arrow overlay should be added to list to end up with two overlays + var otherType = { + connector:"Bezier", + paintStyle:{ lineWidth:14 }, + overlays:[ + ["Arrow", {location:0.25}] + ] + }; + _jsPlumb.registerConnectionTypes({ + "basic": basicType, + "other": otherType + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("basic"); + equals(c.hasType("basic"), true, "connection has 'basic' type"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + equals(c.getPaintStyle().lineWidth, 4, "connection has linewidth 4"); + equals(c.getOverlays().length, 1, "one overlay"); + + c.addType("other"); + equals(c.hasType("basic"), true, "connection has 'basic' type"); + equals(c.hasType("other"), true, "connection has 'other' type"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + equals(c.getPaintStyle().lineWidth, 14, "connection has linewidth 14"); + equals(c.getOverlays().length, 2, "two overlays"); + + c.removeType("basic"); + equals(c.hasType("basic"), false, "connection does not have 'basic' type"); + equals(c.hasType("other"), true, "connection has 'other' type"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + equals(c.getPaintStyle().lineWidth, 14, "connection has linewidth 14"); + equals(c.getOverlays().length, 1, "one overlay"); + + c.toggleType("other"); + equals(c.hasType("other"), false, "connection does not have 'other' type"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + equals(c.getPaintStyle().lineWidth, _jsPlumb.Defaults.PaintStyle.lineWidth, "connection has default linewidth"); + equals(c.getOverlays().length, 0, "nooverlays"); + }); + + test(renderMode + " connection type tests, space separated arguments", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }; + // this tests all three merge types: connector should overwrite, linewidth should be inserted into + // basic type's params, and arrow overlay should be added to list to end up with two overlays + var otherType = { + connector:"Bezier", + paintStyle:{ lineWidth:14 }, + overlays:[ + ["Arrow", {location:0.25}] + ] + }; + _jsPlumb.registerConnectionTypes({ + "basic": basicType, + "other": otherType + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("basic other"); + equals(c.hasType("basic"), true, "connection has 'basic' type"); + equals(c.hasType("other"), true, "connection has 'other' type"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + equals(c.getPaintStyle().lineWidth, 14, "connection has linewidth 14"); + equals(c.getOverlays().length, 2, "two overlays"); + + c.toggleType("other basic"); + equals(c.hasType("basic"), false, "after toggle, connection does not have 'basic' type"); + equals(c.hasType("other"), false, "after toggle, connection does not have 'other' type"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "after toggle, connection has default stroke style"); + equals(c.getPaintStyle().lineWidth, _jsPlumb.Defaults.PaintStyle.lineWidth, "after toggle, connection has default linewidth"); + equals(c.getOverlays().length, 0, "after toggle, no overlays"); + + c.toggleType("basic other"); + equals(c.hasType("basic"), true, "after toggle again, connection has 'basic' type"); + equals(c.hasType("other"), true, "after toggle again, connection has 'other' type"); + equals(c.getPaintStyle().strokeStyle, "yellow", "after toggle again, connection has yellow stroke style"); + equals(c.getPaintStyle().lineWidth, 14, "after toggle again, connection has linewidth 14"); + equals(c.getOverlays().length, 2, "after toggle again, two overlays"); + + c.removeType("other basic"); + equals(c.hasType("basic"), false, "after remove, connection does not have 'basic' type"); + equals(c.hasType("other"), false, "after remove, connection does not have 'other' type"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "after remove, connection has default stroke style"); + equals(c.getPaintStyle().lineWidth, _jsPlumb.Defaults.PaintStyle.lineWidth, "after remove, connection has default linewidth"); + equals(c.getOverlays().length, 0, "after remove, no overlays"); + + c.addType("other basic"); + equals(c.hasType("basic"), true, "after add, connection has 'basic' type"); + equals(c.hasType("other"), true, "after add, connection has 'other' type"); + equals(c.getPaintStyle().strokeStyle, "yellow", "after add, connection has yellow stroke style"); + // NOTE here we added the types in the other order to before, so lineWidth 4 - from basic - should win. + equals(c.getPaintStyle().lineWidth, 4, "after add, connection has linewidth 4"); + equals(c.getOverlays().length, 2, "after add, two overlays"); + }); + + test(renderMode + " connection type tests, fluid interface", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }; + // this tests all three merge types: connector should overwrite, linewidth should be inserted into + // basic type's params, and arrow overlay should be added to list to end up with two overlays + var otherType = { + connector:"Bezier", + paintStyle:{ lineWidth:14 }, + overlays:[ + ["Arrow", {location:0.25}] + ] + }; + _jsPlumb.registerConnectionTypes({ + "basic": basicType, + "other": otherType + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + c = _jsPlumb.connect({source:d1, target:d2}), + c2 = _jsPlumb.connect({source:d2, target:d3}); + + _jsPlumb.select().addType("basic"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + equals(c2.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + + _jsPlumb.select().toggleType("basic"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + equals(c2.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + + _jsPlumb.select().addType("basic"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + equals(c2.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + + _jsPlumb.select().removeType("basic").addType("other"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + equals(c2.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + + + }); + + test(renderMode + " setType when null", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType(null); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + + }); + + test(renderMode + " setType to unknown type", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("foo"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + + }); + + test(renderMode + " setType to mix of known and unknown types", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + c = _jsPlumb.connect({source:d1, target:d2}); + + _jsPlumb.registerConnectionType("basic", { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }); + + c.setType("basic foo"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has basic type's stroke style"); + + c.toggleType("foo"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has basic type's stroke style"); + + c.removeType("basic baz"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + + c.addType("basic foo bar baz"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has basic type's stroke style"); + + }); + + test(renderMode + " create connection using type parameter", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + + _jsPlumb.Defaults.PaintStyle = {strokeStyle:"blue", lineWidth:34}; + + _jsPlumb.registerConnectionTypes({ + "basic": { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }, + "other":{ + paintStyle:{ lineWidth:14 } + } + }); + + equals(_jsPlumb.Defaults.PaintStyle.strokeStyle, "blue", "default value has not been messed up"); + + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + + c = _jsPlumb.connect({source:d1, target:d2, type:"basic other"}); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has basic type's stroke style"); + equals(c.getPaintStyle().lineWidth, 14, "connection has other type's lineWidth"); + + }); + + test(renderMode + " setType, scope", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + c = _jsPlumb.connect({source:d1, target:d2}); + + _jsPlumb.registerConnectionType("basic", { + connector:"Flowchart", + scope:"BANANA", + detachable:false, + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }); + + _jsPlumb.Defaults.ConnectionsDetachable = true;//just make sure we've setup the test correctly. + + c.setType("basic"); + equals(c.scope, "BANANA", "scope is correct"); + equals(c.isDetachable(), false, "not detachable"); + + }); + + test(renderMode + " setType, parameters", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + + _jsPlumb.registerConnectionType("basic", { + parameters:{ + foo:1, + bar:2, + baz:6785962437582 + } + }); + + _jsPlumb.registerConnectionType("frank", { + parameters:{ + bar:5 + } + }); + + // first try creating one with the parameters + c = _jsPlumb.connect({source:d1, target:d2, type:"basic"}); + + equals(c.getParameter("foo"), 1, "foo param correct"); + equals(c.getParameter("bar"), 2, "bar param correct"); + + c.addType("frank"); + equals(c.getParameter("foo"), 1, "foo param correct"); + equals(c.getParameter("bar"), 5, "bar param correct"); + }); + + test(renderMode + " setType, scope, two types", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + c = _jsPlumb.connect({source:d1, target:d2}); + + _jsPlumb.registerConnectionType("basic", { + connector:"Flowchart", + scope:"BANANA", + detachable:false, + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }); + + _jsPlumb.registerConnectionType("frank", { + scope:"OVERRIDE", + detachable:true + }); + + _jsPlumb.Defaults.ConnectionsDetachable = true;//just make sure we've setup the test correctly. + + c.setType("basic frank"); + equals(c.scope, "OVERRIDE", "scope is correct"); + equals(c.isDetachable(), true, "detachable"); + + }); + + test(renderMode + " create connection from Endpoints - type should be passed through.", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e1 = _jsPlumb.addEndpoint(d1, { + connectionType:"basic" + }), + e2 = _jsPlumb.addEndpoint(d2, { + connectionType:"basic" + }); + + _jsPlumb.registerConnectionTypes({ + "basic": { + connector:"Flowchart", + paintStyle:{ strokeStyle:"blue", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"red" }, + overlays:[ + "Arrow" + ] + }, + "other":{ + paintStyle:{ lineWidth:14 } + } + }); + + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.getPaintStyle().strokeStyle, "blue", "connection has default stroke style"); + equal(c.connector.type, "Flowchart", "connector is flowchart"); + }); + + test(renderMode + " simple Endpoint type tests.", function() { + _jsPlumb.registerEndpointType("basic", { + paintStyle:{fillStyle:"blue"} + }); + + var d = _addDiv('d1'), e = _jsPlumb.addEndpoint(d); + e.setType("basic"); + equals(e.getPaintStyle().fillStyle, "blue", "fill style is correct"); + + var d2 = _addDiv('d2'), e2 = _jsPlumb.addEndpoint(d2, {type:"basic"}); + equals(e2.getPaintStyle().fillStyle, "blue", "fill style is correct"); + }); + + test(renderMode + " create connection from Endpoints - with connector settings in Endpoint type.", function() { + + _jsPlumb.registerEndpointTypes({ + "basic": { + connector:"Flowchart", + connectorOverlays:[ + "Arrow" + ], + connectorStyle:{strokeStyle:"green" }, + connectorHoverStyle:{lineWidth:534 }, + paintStyle:{ fillStyle:"blue" }, + hoverPaintStyle:{ strokeStyle:"red" }, + overlays:[ + "Arrow" + ] + }, + "other":{ + paintStyle:{ fillStyle:"red" } + } + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e1 = _jsPlumb.addEndpoint(d1, { + type:"basic" + }), + e2 = _jsPlumb.addEndpoint(d2); + + c = _jsPlumb.connect({source:e1, target:e2}); + equals(e1.getPaintStyle().fillStyle, "blue", "endpoint has fill style specified in Endpoint type"); + equals(c.getPaintStyle().strokeStyle, "green", "connection has stroke style specified in Endpoint type"); + equals(c.getHoverPaintStyle().lineWidth, 534, "connection has hover style specified in Endpoint type"); + equals(c.connector.type, "Flowchart", "connector is Flowchart"); + equals(c.overlays.length, 1, "connector has one overlay"); + equals(e1.overlays.length, 1, "endpoint has one overlay"); + }); + + test(renderMode + " create connection from Endpoints - type should be passed through.", function() { + + _jsPlumb.registerConnectionTypes({ + "basic": { + connector:"Flowchart", + paintStyle:{ strokeStyle:"bazona", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"red" }, + overlays:[ + "Arrow" + ] + } + }); + + _jsPlumb.registerEndpointType("basic", { + connectionType:"basic", + paintStyle:{fillStyle:"GAZOODA"} + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e1 = _jsPlumb.addEndpoint(d1, { + type:"basic" + }), + e2 = _jsPlumb.addEndpoint(d2); + + c = _jsPlumb.connect({source:e1, target:e2}); + equals(e1.getPaintStyle().fillStyle, "GAZOODA", "endpoint has correct paint style, from type."); + equals(c.getPaintStyle().strokeStyle, "bazona", "connection has paint style from connection type, as specified in endpoint type. sweet!"); + equal(c.connector.type, "Flowchart", "connector is flowchart - this also came from connection type as specified by endpoint type."); + }); + + + /** + * leave this test at the bottom! + */ + test(renderMode + ': unload test', function() { + _jsPlumb.unload(); + var contextNode = $(".__jsPlumb_context"); + ok(contextNode.length == 0, 'context node unloaded'); + }); +}; + diff --git a/build/1.3.13/js/lib/qunit.js b/archive/1.3.13/js/lib/qunit.js similarity index 100% rename from build/1.3.13/js/lib/qunit.js rename to archive/1.3.13/js/lib/qunit.js diff --git a/build/1.3.13/js/mootools.jsPlumb-1.3.13-all-min.js b/archive/1.3.13/js/mootools.jsPlumb-1.3.13-all-min.js similarity index 100% rename from build/1.3.13/js/mootools.jsPlumb-1.3.13-all-min.js rename to archive/1.3.13/js/mootools.jsPlumb-1.3.13-all-min.js diff --git a/build/1.3.13/js/mootools.jsPlumb-1.3.13-all.js b/archive/1.3.13/js/mootools.jsPlumb-1.3.13-all.js similarity index 100% rename from build/1.3.13/js/mootools.jsPlumb-1.3.13-all.js rename to archive/1.3.13/js/mootools.jsPlumb-1.3.13-all.js diff --git a/build/1.3.13/js/yui.jsPlumb-1.3.13-all-min.js b/archive/1.3.13/js/yui.jsPlumb-1.3.13-all-min.js similarity index 100% rename from build/1.3.13/js/yui.jsPlumb-1.3.13-all-min.js rename to archive/1.3.13/js/yui.jsPlumb-1.3.13-all-min.js diff --git a/build/1.3.13/js/yui.jsPlumb-1.3.13-all.js b/archive/1.3.13/js/yui.jsPlumb-1.3.13-all.js similarity index 100% rename from build/1.3.13/js/yui.jsPlumb-1.3.13-all.js rename to archive/1.3.13/js/yui.jsPlumb-1.3.13-all.js diff --git a/archive/1.3.13/jsPlumb-1.3.13-RC1.js b/archive/1.3.13/jsPlumb-1.3.13-RC1.js new file mode 100644 index 000000000..ce5152b9f --- /dev/null +++ b/archive/1.3.13/jsPlumb-1.3.13-RC1.js @@ -0,0 +1,6180 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.13 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the jsPlumb core code. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + /** + * Class:jsPlumb + * The jsPlumb engine, registered as a static object in the window. This object contains all of the methods you will use to + * create and maintain Connections and Endpoints. + */ + + var _findWithFunction = jsPlumbUtil.findWithFunction, + _indexOf = jsPlumbUtil.indexOf, + _removeWithFunction = jsPlumbUtil.removeWithFunction, + _remove = jsPlumbUtil.remove, + // TODO support insert index + _addWithFunction = jsPlumbUtil.addWithFunction, + _addToList = jsPlumbUtil.addToList, + /** + an isArray function that even works across iframes...see here: + + http://tobyho.com/2011/01/28/checking-types-in-javascript/ + + i was originally using "a.constructor == Array" as a test. + */ + _isArray = jsPlumbUtil.isArray, + _isString = jsPlumbUtil.isString, + _isObject = jsPlumbUtil.isObject; + + var _connectionBeingDragged = null, + _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }, + _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }, + _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }, + _log = jsPlumbUtil.log, + _group = jsPlumbUtil.group, + _groupEnd = jsPlumbUtil.groupEnd, + _time = jsPlumbUtil.time, + _timeEnd = jsPlumbUtil.timeEnd, + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + _timestamp = function() { return "" + (new Date()).getTime(); }, + + /* + * Class:jsPlumbUIComponent + * Abstract superclass for UI components Endpoint and Connection. Provides the abstraction of paintStyle/hoverPaintStyle, + * and also extends jsPlumbUtil.EventGenerator to provide the bind and fire methods. + */ + jsPlumbUIComponent = function(params) { + var self = this, + a = arguments, + _hover = false, + parameters = params.parameters || {}, + idPrefix = self.idPrefix, + id = idPrefix + (new Date()).getTime(), + paintStyle = null, + hoverPaintStyle = null; + + self._jsPlumb = params["_jsPlumb"]; + self.getId = function() { return id; }; + self.tooltip = params.tooltip; + self.hoverClass = params.hoverClass || self._jsPlumb.Defaults.HoverClass || jsPlumb.Defaults.HoverClass; + + // all components can generate events + jsPlumbUtil.EventGenerator.apply(this); + // all components get this clone function. + // TODO issue 116 showed a problem with this - it seems 'a' that is in + // the clone function's scope is shared by all invocations of it, the classic + // JS closure problem. for now, jsPlumb does a version of this inline where + // it used to call clone. but it would be nice to find some time to look + // further at this. + this.clone = function() { + var o = new Object(); + self.constructor.apply(o, a); + return o; + }; + + this.getParameter = function(name) { return parameters[name]; }, + this.getParameters = function() { + return parameters; + }, + this.setParameter = function(name, value) { parameters[name] = value; }, + this.setParameters = function(p) { parameters = p; }, + this.overlayPlacements = []; + + // user can supply a beforeDetach callback, which will be executed before a detach + // is performed; returning false prevents the detach. + var beforeDetach = params.beforeDetach; + this.isDetachAllowed = function(connection) { + var r = self._jsPlumb.checkCondition("beforeDetach", connection ); + if (beforeDetach) { + try { + r = beforeDetach(connection); + } + catch (e) { _log("jsPlumb: beforeDetach callback failed", e); } + } + return r; + }; + + // user can supply a beforeDrop callback, which will be executed before a dropped + // connection is confirmed. user can return false to reject connection. + var beforeDrop = params.beforeDrop; + this.isDropAllowed = function(sourceId, targetId, scope, connection, dropEndpoint) { + var r = self._jsPlumb.checkCondition("beforeDrop", { + sourceId:sourceId, + targetId:targetId, + scope:scope, + connection:connection, + dropEndpoint:dropEndpoint + }); + if (beforeDrop) { + try { + r = beforeDrop({ + sourceId:sourceId, + targetId:targetId, + scope:scope, + connection:connection, + dropEndpoint:dropEndpoint + }); + } + catch (e) { _log("jsPlumb: beforeDrop callback failed", e); } + } + return r; + }; + + // helper method to update the hover style whenever it, or paintStyle, changes. + // we use paintStyle as the foundation and merge hoverPaintStyle over the + // top. + var _updateHoverStyle = function() { + if (paintStyle && hoverPaintStyle) { + var mergedHoverStyle = {}; + jsPlumb.extend(mergedHoverStyle, paintStyle); + jsPlumb.extend(mergedHoverStyle, hoverPaintStyle); + delete self["hoverPaintStyle"]; + // we want the fillStyle of paintStyle to override a gradient, if possible. + if (mergedHoverStyle.gradient && paintStyle.fillStyle) + delete mergedHoverStyle["gradient"]; + hoverPaintStyle = mergedHoverStyle; + } + }; + + /* + * Sets the paint style and then repaints the element. + * + * Parameters: + * style - Style to use. + */ + this.setPaintStyle = function(style, doNotRepaint) { + paintStyle = style; + self.paintStyleInUse = paintStyle; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /** + * Gets the component's paint style. + * + * Returns: + * the component's paint style. if there is no hoverPaintStyle set then this will be the paint style used all the time, otherwise this is the style used when the mouse is not hovering. + */ + this.getPaintStyle = function() { + return paintStyle; + }; + + /* + * Sets the paint style to use when the mouse is hovering over the element. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the component will not be repainted. useful when setting things up initially. + */ + this.setHoverPaintStyle = function(style, doNotRepaint) { + hoverPaintStyle = style; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /** + * Gets the component's hover paint style. + * + * Returns: + * the component's hover paint style. may be null. + */ + this.getHoverPaintStyle = function() { + return hoverPaintStyle; + }; + + /* + * sets/unsets the hover state of this element. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + this.setHover = function(hover, ignoreAttachedElements, timestamp) { + // while dragging, we ignore these events. this keeps the UI from flashing and + // swishing and whatevering. + if (!self._jsPlumb.currentlyDragging && !self._jsPlumb.isHoverSuspended()) { + + _hover = hover; + if (self.hoverClass != null && self.canvas != null) { + if (hover) + jpcl.addClass(self.canvas, self.hoverClass); + else + jpcl.removeClass(self.canvas, self.hoverClass); + } + if (hoverPaintStyle != null) { + self.paintStyleInUse = hover ? hoverPaintStyle : paintStyle; + timestamp = timestamp || _timestamp(); + self.repaint({timestamp:timestamp, recalc:false}); + } + // get the list of other affected elements, if supported by this component. + // for a connection, its the endpoints. for an endpoint, its the connections! surprise. + if (self.getAttachedElements && !ignoreAttachedElements) + _updateAttachedElements(hover, _timestamp(), self); + } + }; + + this.isHover = function() { return _hover; }; + + var zIndex = null; + this.setZIndex = function(v) { zIndex = v; }; + this.getZIndex = function() { return zIndex; }; + + var jpcl = jsPlumb.CurrentLibrary, + events = [ "click", "dblclick", "mouseenter", "mouseout", "mousemove", "mousedown", "mouseup", "contextmenu" ], + eventFilters = { "mouseout":"mouseexit" }, + bindOne = function(o, c, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.bind(o, evt, function(ee) { + c.fire(filteredEvent, c, ee); + }); + }, + unbindOne = function(o, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.unbind(o, evt); + }; + + this.attachListeners = function(o, c) { + for (var i = 0; i < events.length; i++) { + bindOne(o, c, events[i]); + } + }; + + var _updateAttachedElements = function(state, timestamp, sourceElement) { + var affectedElements = self.getAttachedElements(); // implemented in subclasses + if (affectedElements) { + for (var i = 0; i < affectedElements.length; i++) { + if (!sourceElement || sourceElement != affectedElements[i]) + affectedElements[i].setHover(state, true, timestamp); // tell the attached elements not to inform their own attached elements. + } + } + }; + + this.reattachListenersForElement = function(o) { + if (arguments.length > 1) { + for (var i = 0; i < events.length; i++) + unbindOne(o, events[i]); + for (var i = 1; i < arguments.length; i++) + self.attachListeners(o, arguments[i]); + } + }; + + /* + * TYPES + */ + var _types = [], + _splitType = function(t) { return t == null ? null : t.split(" ")}, + _applyTypes = function(doNotRepaint) { + if (self.getDefaultType) { + var td = self.getTypeDescriptor(); + + var o = jsPlumbUtil.merge({}, self.getDefaultType()); + for (var i = 0; i < _types.length; i++) + o = jsPlumbUtil.merge(o, self._jsPlumb.getType(_types[i], td)); + + self.applyType(o); + if (!doNotRepaint) self.repaint(); + } + }; + + self.setType = function(typeId, doNotRepaint) { + _types = _splitType(typeId) || []; + _applyTypes(doNotRepaint); + }; + + /* + * Function : getType + * Gets the 'types' of this component. + */ + self.getType = function() { + return _types; + }; + + self.hasType = function(typeId) { + return jsPlumbUtil.indexOf(_types, typeId) != -1; + }; + + self.addType = function(typeId, doNotRepaint) { + var t = _splitType(typeId), _cont = false; + if (t != null) { + for (var i = 0; i < t.length; i++) { + if (!self.hasType(t[i])) { + _types.push(t[i]); + _cont = true; + } + } + if (_cont) _applyTypes(doNotRepaint); + } + }; + + self.removeType = function(typeId, doNotRepaint) { + var t = _splitType(typeId), _cont = false, _one = function(tt) { + var idx = jsPlumbUtil.indexOf(_types, tt); + if (idx != -1) { + _types.splice(idx, 1); + return true; + } + return false; + }; + + if (t != null) { + for (var i = 0; i < t.length; i++) { + _cont = _one(t[i]) || _cont; + } + if (_cont) _applyTypes(doNotRepaint); + } + }; + + self.toggleType = function(typeId, doNotRepaint) { + var t = _splitType(typeId); + if (t != null) { + for (var i = 0; i < t.length; i++) { + var idx = jsPlumbUtil.indexOf(_types, t[i]); + if (idx != -1) + _types.splice(idx, 1); + else + _types.push(t[i]); + } + + _applyTypes(doNotRepaint); + } + }; + + this.applyType = function(t) { + self.setPaintStyle(t.paintStyle); + self.setHoverPaintStyle(t.hoverPaintStyle); + if (t.parameters){ + for (var i in t.parameters) + self.setParameter(i, t.parameters[i]); + } + }; + + }, + + overlayCapableJsPlumbUIComponent = function(params) { + jsPlumbUIComponent.apply(this, arguments); + var self = this; + this.overlays = []; + + var processOverlay = function(o) { + var _newOverlay = null; + if (_isArray(o)) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0], + // make a copy of the object so as not to mess up anyone else's reference... + p = jsPlumb.extend({component:self, _jsPlumb:self._jsPlumb}, o[1]); + if (o.length == 3) jsPlumb.extend(p, o[2]); + _newOverlay = new jsPlumb.Overlays[self._jsPlumb.getRenderMode()][type](p); + if (p.events) { + for (var evt in p.events) { + _newOverlay.bind(evt, p.events[evt]); + } + } + } else if (o.constructor == String) { + _newOverlay = new jsPlumb.Overlays[self._jsPlumb.getRenderMode()][o]({component:self, _jsPlumb:self._jsPlumb}); + } else { + _newOverlay = o; + } + + self.overlays.push(_newOverlay); + }, + calculateOverlaysToAdd = function(params) { + var defaultKeys = self.defaultOverlayKeys || [], + o = params.overlays, + checkKey = function(k) { + return self._jsPlumb.Defaults[k] || jsPlumb.Defaults[k] || []; + }; + + if (!o) o = []; + + for (var i = 0; i < defaultKeys.length; i++) + o.unshift.apply(o, checkKey(defaultKeys[i])); + + return o; + } + + var _overlays = calculateOverlaysToAdd(params);//params.overlays || self._jsPlumb.Defaults.Overlays; + if (_overlays) { + for (var i = 0; i < _overlays.length; i++) { + processOverlay(_overlays[i]); + } + } + + // overlay finder helper method + var _getOverlayIndex = function(id) { + var idx = -1; + for (var i = 0; i < self.overlays.length; i++) { + if (id === self.overlays[i].id) { + idx = i; + break; + } + } + return idx; + }; + + this.addOverlay = function(overlay, doNotRepaint) { + processOverlay(overlay); + if (!doNotRepaint) self.repaint(); + }; + + this.getOverlay = function(id) { + var idx = _getOverlayIndex(id); + return idx >= 0 ? self.overlays[idx] : null; + }; + + this.getOverlays = function() { + return self.overlays; + }; + + this.hideOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.hide(); + }; + + this.hideOverlays = function() { + for (var i = 0; i < self.overlays.length; i++) + self.overlays[i].hide(); + }; + + this.showOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.show(); + }; + + this.showOverlays = function() { + for (var i = 0; i < self.overlays.length; i++) + self.overlays[i].show(); + }; + + this.removeAllOverlays = function() { + for (var i in self.overlays) + self.overlays[i].cleanup(); + + self.overlays.splice(0, self.overlays.length); + self.repaint(); + }; + + this.removeOverlay = function(overlayId) { + var idx = _getOverlayIndex(overlayId); + if (idx != -1) { + var o = self.overlays[idx]; + o.cleanup(); + self.overlays.splice(idx, 1); + } + }; + + this.removeOverlays = function() { + for (var i = 0; i < arguments.length; i++) + self.removeOverlay(arguments[i]); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + var _internalLabelOverlayId = "__label", + _makeLabelOverlay = function(params) { + + var _params = { + cssClass:params.cssClass, + labelStyle : this.labelStyle, + id:_internalLabelOverlayId, + component:self, + _jsPlumb:self._jsPlumb + }, + mergedParams = jsPlumb.extend(_params, params); + + return new jsPlumb.Overlays[self._jsPlumb.getRenderMode()].Label( mergedParams ); + }; + if (params.label) { + var loc = params.labelLocation || self.defaultLabelLocation || 0.5, + labelStyle = params.labelStyle || self._jsPlumb.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.overlays.push(_makeLabelOverlay({ + label:params.label, + location:loc, + labelStyle:labelStyle + })); + } + + + this.setLabel = function(l) { + var lo = self.getOverlay(_internalLabelOverlayId); + if (!lo) { + var params = l.constructor == String || l.constructor == Function ? { label:l } : l; + lo = _makeLabelOverlay(params); + this.overlays.push(lo); + } + else { + if (l.constructor == String || l.constructor == Function) lo.setLabel(l); + else { + if (l.label) lo.setLabel(l.label); + if (l.location) lo.setLocation(l.location); + } + } + + if (!self._jsPlumb.isSuspendDrawing()) + self.repaint(); + }; + + + this.getLabel = function() { + var lo = self.getOverlay(_internalLabelOverlayId); + return lo != null ? lo.getLabel() : null; + }; + + + this.getLabelOverlay = function() { + return self.getOverlay(_internalLabelOverlayId); + }; + + var superAt = this.applyType; + this.applyType = function(t) { + superAt(t); + self.removeAllOverlays(); + if (t.overlays) { + for (var i = 0; i < t.overlays.length; i++) + self.addOverlay(t.overlays[i], true); + } + }; + }, + + _bindListeners = function(obj, _self, _hoverFunction) { + obj.bind("click", function(ep, e) { _self.fire("click", _self, e); }); + obj.bind("dblclick", function(ep, e) { _self.fire("dblclick", _self, e); }); + obj.bind("contextmenu", function(ep, e) { _self.fire("contextmenu", _self, e); }); + obj.bind("mouseenter", function(ep, e) { + if (!_self.isHover()) { + _hoverFunction(true); + _self.fire("mouseenter", _self, e); + } + }); + obj.bind("mouseexit", function(ep, e) { + if (_self.isHover()) { + _hoverFunction(false); + _self.fire("mouseexit", _self, e); + } + }); + }; + + var _jsPlumbInstanceIndex = 0, + getInstanceIndex = function() { + var i = _jsPlumbInstanceIndex + 1; + _jsPlumbInstanceIndex++; + return i; + }; + + var jsPlumbInstance = function(_defaults) { + + /* + * Property: Defaults + * + * These are the default settings for jsPlumb. They are what will be used if you do not supply specific pieces of information + * to the various API calls. A convenient way to implement your own look and feel can be to override these defaults + * by including a script somewhere after the jsPlumb include, but before you make any calls to jsPlumb. + * + * Properties: + * - *Anchor* The default anchor to use for all connections (both source and target). Default is "BottomCenter". + * - *Anchors* The default anchors to use ([source, target]) for all connections. Defaults are ["BottomCenter", "BottomCenter"]. + * - *ConnectionsDetachable* Whether or not connections are detachable by default (using the mouse). Defaults to true. + * - *ConnectionOverlays* The default overlay definitions for Connections. Defaults to an empty list. + * - *Connector* The default connector definition to use for all connections. Default is "Bezier". + * - *ConnectorZIndex* Optional value for the z-index of Connections that are not in the hover state. If you set this, jsPlumb will set the z-index of all created Connections to be this value, and the z-index of any Connections in the hover state to be this value plus one. This brings hovered connections up on top of others, which is a nice effect in busy UIs. + * - *Container* Optional selector or element id that instructs jsPlumb to append elements it creates to a specific element. + * - *DragOptions* The default drag options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *DropOptions* The default drop options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *Endpoint* The default endpoint definition to use for all connections (both source and target). Default is "Dot". + * - *EndpointOverlays* The default overlay definitions for Endpoints. Defaults to an empty list. + * - *Endpoints* The default endpoint definitions ([ source, target ]) to use for all connections. Defaults are ["Dot", "Dot"]. + * - *EndpointStyle* The default style definition to use for all endpoints. Default is fillStyle:"#456". + * - *EndpointStyles* The default style definitions ([ source, target ]) to use for all endpoints. Defaults are empty. + * - *EndpointHoverStyle* The default hover style definition to use for all endpoints. Default is null. + * - *EndpointHoverStyles* The default hover style definitions ([ source, target ]) to use for all endpoints. Defaults are null. + * - *HoverPaintStyle* The default hover style definition to use for all connections. Defaults are null. + * - *LabelStyle* The default style to use for label overlays on connections. + * - *LogEnabled* Whether or not the jsPlumb log is enabled. defaults to false. + * - *Overlays* The default overlay definitions (for both Connections and Endpoint). Defaults to an empty list. + * - *MaxConnections* The default maximum number of connections for an Endpoint. Defaults to 1. + * - *PaintStyle* The default paint style for a connection. Default is line width of 8 pixels, with color "#456". + * - *RenderMode* What mode to use to paint with. If you're on IE<9, you don't really get to choose this. You'll just get VML. Otherwise, the jsPlumb default is to use SVG. + * - *Scope* The default "scope" to use for connections. Scope lets you assign connections to different categories. + */ + this.Defaults = { + Anchor : "BottomCenter", + Anchors : [ null, null ], + ConnectionsDetachable : true, + ConnectionOverlays : [ ], + Connector : "Bezier", + ConnectorZIndex : null, + Container : null, + DragOptions : { }, + DropOptions : { }, + Endpoint : "Dot", + EndpointOverlays : [ ], + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : "#456" }, + EndpointStyles : [ null, null ], + EndpointHoverStyle : null, + EndpointHoverStyles : [ null, null ], + HoverPaintStyle : null, + LabelStyle : { color : "black" }, + LogEnabled : false, + Overlays : [ ], + MaxConnections : 1, + PaintStyle : { lineWidth : 8, strokeStyle : "#456" }, + //Reattach:false, + RenderMode : "svg", + Scope : "jsPlumb_DefaultScope" + }; + if (_defaults) jsPlumb.extend(this.Defaults, _defaults); + + this.logEnabled = this.Defaults.LogEnabled; + + var _connectionTypes = { }, _endpointTypes = {}; + this.registerConnectionType = function(id, type) { + _connectionTypes[id] = jsPlumb.extend({}, type); + }; + this.registerConnectionTypes = function(types) { + for (var i in types) + _connectionTypes[i] = jsPlumb.extend({}, types[i]); + }; + this.registerEndpointType = function(id, type) { + _endpointTypes[id] = jsPlumb.extend({}, type); + }; + this.registerEndpointTypes = function(types) { + for (var i in types) + _endpointTypes[i] = jsPlumb.extend({}, types[i]); + }; + this.getType = function(id, typeDescriptor) { + return typeDescriptor === "connection" ? _connectionTypes[id] : _endpointTypes[id]; + }; + + jsPlumbUtil.EventGenerator.apply(this); + var _currentInstance = this, + _instanceIndex = getInstanceIndex(), + _bb = _currentInstance.bind, + _initialDefaults = {}; + + for (var i in this.Defaults) + _initialDefaults[i] = this.Defaults[i]; + + this.bind = function(event, fn) { + if ("ready" === event && initialized) fn(); + else _bb.apply(_currentInstance,[event, fn]); + }; + + /* + Function: importDefaults + Imports all the given defaults into this instance of jsPlumb. + */ + _currentInstance.importDefaults = function(d) { + for (var i in d) { + _currentInstance.Defaults[i] = d[i]; + } + }; + + /* + Function:restoreDefaults + Restores the default settings to "factory" values. + */ + _currentInstance.restoreDefaults = function() { + _currentInstance.Defaults = jsPlumb.extend({}, _initialDefaults); + }; + + var log = null, + repaintFunction = function() { + jsPlumb.repaintEverything(); + }, + automaticRepaint = true, + repaintEverything = function() { + if (automaticRepaint) + repaintFunction(); + }, + resizeTimer = null, + initialized = false, + connectionsByScope = {}, + /** + * map of element id -> endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + endpointsByElement = {}, + endpointsByUUID = {}, + offsets = {}, + offsetTimestamps = {}, + floatingConnections = {}, + draggableStates = {}, + canvasList = [], + sizes = [], + //listeners = {}, // a map: keys are event types, values are lists of listeners. + DEFAULT_SCOPE = this.Defaults.Scope, + renderMode = null, // will be set in init() + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }, + + /** + * appends an element to some other element, which is calculated as follows: + * + * 1. if _currentInstance.Defaults.Container exists, use that element. + * 2. if the 'parent' parameter exists, use that. + * 3. otherwise just use the root element (for DOM usage, the document body). + * + */ + _appendElement = function(el, parent) { + if (_currentInstance.Defaults.Container) + jsPlumb.CurrentLibrary.appendElement(el, _currentInstance.Defaults.Container); + else if (!parent) + //document.body.appendChild(el); + jsPlumbAdapter.appendToRoot(el); + else + jsPlumb.CurrentLibrary.appendElement(el, parent); + }, + + _curIdStamp = 1, + _idstamp = function() { return "" + _curIdStamp++; }, + + /** + * YUI, for some reason, put the result of a Y.all call into an object that contains + * a '_nodes' array, instead of handing back an array-like object like the other + * libraries do. + */ + _convertYUICollection = function(c) { + return c._nodes ? c._nodes : c; + }, + + /** + * Draws an endpoint and its connections. this is the main entry point into drawing connections as well + * as endpoints, since jsPlumb is endpoint-centric under the hood. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + _draw = function(element, ui, timestamp) { + + // TOD is it correct to filter by headless at this top level? how would a headless adapter ever repaint? + if (!jsPlumbAdapter.headless && !_suspendDrawing) { + var id = _getAttribute(element, "id"), + repaintEls = _currentInstance.dragManager.getElementsForDraggable(id); + + if (timestamp == null) timestamp = _timestamp(); + + _currentInstance.anchorManager.redraw(id, ui, timestamp); + + if (repaintEls) { + for (var i in repaintEls) { + _currentInstance.anchorManager.redraw(repaintEls[i].id, ui, timestamp, repaintEls[i].offset); + } + } + } + }, + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + _elementProxy = function(element, fn) { + var retVal = null; + if (_isArray(element)) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }, + + /** + * gets an Endpoint by uuid. + */ + _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }, + + /** + * inits a draggable if it's not already initialised. + */ + _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + // TODO move to DragManager? + if (!jsPlumbAdapter.headless) { + var draggable = isDraggable == null ? false : isDraggable, + jpcl = jsPlumb.CurrentLibrary; + if (draggable) { + if (jpcl.isDragSupported(element) && !jpcl.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jpcl.dragEvents["drag"], + stopEvent = jpcl.dragEvents["stop"], + startEvent = jpcl.dragEvents["start"]; + + options[startEvent] = _wrap(options[startEvent], function() { + _currentInstance.setHoverSuspended(true); + }); + + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jpcl.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jpcl.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + _currentInstance.setHoverSuspended(false); + }); + var elId = _getId(element); // need ID + draggableStates[elId] = true; + var draggable = draggableStates[elId]; + options.disabled = draggable == null ? false : !draggable; + jpcl.initDraggable(element, options, false); + _currentInstance.dragManager.register(element); + } + } + } + }, + + /* + * prepares a final params object that can be passed to _newConnection, taking into account defaults, events, etc. + */ + _prepareConnectionParams = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + // hotwire endpoints passed as source or target to sourceEndpoint/targetEndpoint, respectively. + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not full. + // source: + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + // target: + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + // if source endpoint mandates connection type and nothing specified in our params, use it. + if (!_p.type && _p.sourceEndpoint) + _p.type = _p.sourceEndpoint.connectionType; + + // copy in any connectorOverlays that were specified on the source endpoint. + // it doesnt copy target endpoint overlays. i'm not sure if we want it to or not. + if (_p.sourceEndpoint && _p.sourceEndpoint.connectorOverlays) { + _p.overlays = _p.overlays || []; + for (var i = 0; i < _p.sourceEndpoint.connectorOverlays.length; i++) { + _p.overlays.push(_p.sourceEndpoint.connectorOverlays[i]); + } + } + + // tooltip. params.tooltip takes precedence, then sourceEndpoint.connectorTooltip. + _p.tooltip = params.tooltip; + if (!_p.tooltip && _p.sourceEndpoint && _p.sourceEndpoint.connectorTooltip) + _p.tooltip = _p.sourceEndpoint.connectorTooltip; + + // if there's a target specified (which of course there should be), and there is no + // target endpoint specified, and 'newConnection' was not set to true, then we check to + // see if a prior call to makeTarget has provided us with the specs for the target endpoint, and + // we use those if so. additionally, if the makeTarget call was specified with 'uniqueEndpoint' set + // to true, then if that target endpoint has already been created, we re-use it. + if (_p.target && !_p.target.endpoint && !_p.targetEndpoint && !_p.newConnection) { + var tid = _getId(_p.target), + tep =_targetEndpointDefinitions[tid], + existingUniqueEndpoint = _targetEndpoints[tid]; + + if (tep) { + // if target not enabled, return. + if (!_targetsEnabled[tid]) return; + + // check for max connections?? + var newEndpoint = existingUniqueEndpoint != null ? existingUniqueEndpoint : _currentInstance.addEndpoint(_p.target, tep); + if (_targetEndpointsUnique[tid]) _targetEndpoints[tid] = newEndpoint; + _p.targetEndpoint = newEndpoint; + newEndpoint._makeTargetCreator = true; + } + } + + // same thing, but for source. + if (_p.source && !_p.source.endpoint && !_p.sourceEndpoint && !_p.newConnection) { + var tid = _getId(_p.source), + tep = _sourceEndpointDefinitions[tid], + existingUniqueEndpoint = _sourceEndpoints[tid]; + + if (tep) { + // if source not enabled, return. + if (!_sourcesEnabled[tid]) return; + + var newEndpoint = existingUniqueEndpoint != null ? existingUniqueEndpoint : _currentInstance.addEndpoint(_p.source, tep); + if (_sourceEndpointsUnique[tid]) _sourceEndpoints[tid] = newEndpoint; + _p.sourceEndpoint = newEndpoint; + } + } + + return _p; + }, + + _newConnection = function(params) { + var connectionFunc = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + endpointFunc = _currentInstance.Defaults.EndpointType || Endpoint, + parent = jsPlumb.CurrentLibrary.getParent; + + if (params.container) + params["parent"] = params.container; + else { + if (params.sourceEndpoint) + params["parent"] = params.sourceEndpoint.parent; + else if (params.source.constructor == endpointFunc) + params["parent"] = params.source.parent; + else params["parent"] = parent(params.source); + } + + params["_jsPlumb"] = _currentInstance; + var con = new connectionFunc(params); + con.id = "con_" + _idstamp(); + _eventFireProxy("click", "click", con); + _eventFireProxy("dblclick", "dblclick", con); + _eventFireProxy("contextmenu", "contextmenu", con); + return con; + }, + + /** + * adds the connection to the backing model, fires an event if necessary and then redraws + */ + _finaliseConnection = function(jpc, params, originalEvent) { + params = params || {}; + // add to list of connections (by scope). + if (!jpc.suspendedEndpoint) + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + if (!params.doNotFireConnectionEvent && params.fireEvent !== false) { + + var eventArgs = { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }; + + _currentInstance.fire("jsPlumbConnection", eventArgs, originalEvent); + // this is from 1.3.11 onwards. "jsPlumbConnection" always felt so unnecessary, so + // I've added this alias in 1.3.11, with a view to removing "jsPlumbConnection" completely in a future version. be aware, of course, you should only register listeners for one or the other of these events. + _currentInstance.fire("connection", eventArgs, originalEvent); + } + + // always inform the anchor manager + // except that if jpc has a suspended endpoint it's not true to say the + // connection is new; it has just (possibly) moved. the question is whether + // to make that call here or in the anchor manager. i think perhaps here. + _currentInstance.anchorManager.newConnection(jpc); + // force a paint + _draw(jpc.source); + }, + + _eventFireProxy = function(event, proxyEvent, obj) { + obj.bind(event, function(originalObject, originalEvent) { + _currentInstance.fire(proxyEvent, obj, originalEvent); + }); + }, + + /** + * for the given endpoint params, returns an appropriate parent element for the UI elements that will be added. + * this function is used by _newEndpoint (directly below), and also in the makeSource function in jsPlumb. + * + * the logic is to first look for a "container" member of params, and pass that back if found. otherwise we + * handoff to the 'getParent' function in the current library. + */ + _getParentFromParams = function(params) { + if (params.container) + return params.container; + else { + var tag = jsPlumb.CurrentLibrary.getTagName(params.source), + p = jsPlumb.CurrentLibrary.getParent(params.source); + if (tag && tag.toLowerCase() === "td") + return jsPlumb.CurrentLibrary.getParent(p); + else return p; + } + }, + + /** + factory method to prepare a new endpoint. this should always be used instead of creating Endpoints + manually, since this method attaches event listeners and an id. + */ + _newEndpoint = function(params) { + var endpointFunc = _currentInstance.Defaults.EndpointType || Endpoint; + params.parent = _getParentFromParams(params); + params["_jsPlumb"] = _currentInstance; + var ep = new endpointFunc(params); + ep.id = "ep_" + _idstamp(); + _eventFireProxy("click", "endpointClick", ep); + _eventFireProxy("dblclick", "endpointDblClick", ep); + _eventFireProxy("contextmenu", "contextmenu", ep); + if (!jsPlumbAdapter.headless) + _currentInstance.dragManager.endpointAdded(params.source); + return ep; + }, + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + _operation = function(elId, func, endpointFunc) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + if (endpointFunc) endpointFunc(endpoints[i]); + } + } + }, + /** + * perform an operation on all elements. + */ + _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }, + + /** + * helper to remove an element from the DOM. + */ + _removeElement = function(element, parent) { + if (element != null && element.parentNode != null) { + element.parentNode.removeChild(element); + } + }, + /** + * helper to remove a list of elements from the DOM. + */ + _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }, + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }, + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + _setVisible = function(el, state, alsoChangeEndpoints) { + state = state === "block"; + var endpointFunc = null; + if (alsoChangeEndpoints) { + if (state) endpointFunc = function(ep) { + ep.setVisible(true, true, true); + }; + else endpointFunc = function(ep) { + ep.setVisible(false, true, true); + }; + } + var id = _getAttribute(el, "id"); + _operation(id, function(jpc) { + if (state && alsoChangeEndpoints) { + // this test is necessary because this functionality is new, and i wanted to maintain backwards compatibility. + // this block will only set a connection to be visible if the other endpoint in the connection is also visible. + var oidx = jpc.sourceId === id ? 1 : 0; + if (jpc.endpoints[oidx].isVisible()) jpc.setVisible(true); + } + else // the default behaviour for show, and what always happens for hide, is to just set the visibility without getting clever. + jpc.setVisible(state); + }, endpointFunc); + }, + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? false : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }, + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + _toggleVisible = function(elId, changeEndpoints) { + var endpointFunc = null; + if (changeEndpoints) { + endpointFunc = function(ep) { + var state = ep.isVisible(); + ep.setVisible(!state); + }; + } + _operation(elId, function(jpc) { + var state = jpc.isVisible(); + jpc.setVisible(!state); + }, endpointFunc); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }, + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (_suspendDrawing && !timestamp) timestamp = _suspendedAt; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return offsets[elId]; + } + if (recalc || !offset) { // if forced repaint or no offset available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + if (sizes[elId] == null) { + var s = _getElementObject(elId); + if (s != null) sizes[elId] = _getSize(s); + } + } + + if(offsets[elId] && !offsets[elId].right) { + offsets[elId].right = offsets[elId].left + sizes[elId][0]; + offsets[elId].bottom = offsets[elId].top + sizes[elId][1]; + offsets[elId].width = sizes[elId][0]; + offsets[elId].height = sizes[elId][1]; + offsets[elId].centerx = offsets[elId].left + (offsets[elId].width / 2); + offsets[elId].centery = offsets[elId].top + (offsets[elId].height / 2); + } + return offsets[elId]; + }, + + // TODO comparison performance + _getCachedData = function(elId) { + var o = offsets[elId]; + if (!o) o = _updateOffset({elId:elId}); + return {o:o, s:sizes[elId]}; + }, + + /** + * gets an id for the given element, creating and setting one if + * necessary. the id is of the form + * + * jsPlumb__ + * + * where "index in instance" is a monotonically increasing integer that starts at 0, + * for each instance. this method is used not only to assign ids to elements that do not + * have them but also to connections and endpoints. + */ + _getId = function(element, uuid, doNotCreateIfNotFound) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else if (arguments.length == 1 || (arguments.length == 3 && !arguments[2])) + id = "jsPlumb_" + _instanceIndex + "_" + _idstamp(); + _setAttribute(ele, "id", id); + } + return id; + }, + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, "jsPlumb function failed : " + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, "wrapped function failed : " + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = "_jsPlumb_connector"; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = "_jsPlumb_endpoint"; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = "_jsPlumb_overlay"; + + this.Anchors = {}; + + this.Connectors = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Endpoints = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Overlays = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + +// ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***************************************** + /* + * Function: bind + * Bind to an event on jsPlumb. + * + * Parameters: + * event - the event to bind. Available events on jsPlumb are: + * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback. + * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback. + * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback. + * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback. + * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback. + * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback. + * - *beforeDrop* : notification that a Connection is about to be dropped. Returning false from this method cancels the drop. jsPlumb passes { sourceId, targetId, scope, connection, dropEndpoint } to your callback. For more information, refer to the jsPlumb documentation. + * - *beforeDetach* : notification that a Connection is about to be detached. Returning false from this method cancels the detach. jsPlumb passes the Connection to your callback. For more information, refer to the jsPlumb documentation. + * - *connectionDrag* : notification that an existing Connection is being dragged. jsPlumb passes the Connection to your callback function. + * - *connectionDragEnd* : notification that the drag of an existing Connection has ended. jsPlumb passes the Connection to your callback function. + * + * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event. + */ + + /* + * Function: unbind + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + +// *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *********************************************************** + + +// --------------------------- jsPLumbInstance public API --------------------------------------------------------- + + /* + Function: addClass + + Helper method to abstract out differences in setting css classes on the different renderer types. + */ + this.addClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.addClass(el, clazz); + }; + + /* + Function: removeClass + + Helper method to abstract out differences in setting css classes on the different renderer types. + */ + this.removeClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.removeClass(el, clazz); + }; + + /* + Function: hasClass + + Helper method to abstract out differences in testing for css classes on the different renderer types. + */ + this.hasClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.hasClass(el, clazz); + }; + + /* + Function: addEndpoint + + Adds an to a given element or elements. + + Parameters: + + el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + params - Object containing Endpoint constructor arguments. For more information, see . + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some + shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in + this object are anything that 'params' can contain. See . + + Returns: + The newly created , if el referred to a single element. Otherwise, an array of newly created s. + + See Also: + + */ + this.addEndpoint = function(el, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // YUI wrapper + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + var _el = _getElementObject(inputs[i]), id = _getId(_el); + p.source = _el; + _updateOffset({ elId : id, timestamp:_suspendedAt }); + var e = _newEndpoint(p); + if (p.parentAnchor) e.parentAnchor = p.parentAnchor; + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e, timestamp:_suspendedAt }); + var endpointPaintParams = { anchorLoc : anchorLoc, timestamp:_suspendedAt }; + if (_suspendDrawing) endpointPaintParams.recalc = false; + e.paint(endpointPaintParams); + results.push(e); + //if (!jsPlumbAdapter.headless) + //_currentInstance.dragManager.endpointAdded(_el); + } + + return results.length == 1 ? results[0] : results; + }; + + /* + Function: addEndpoints + Adds a list of s to a given element or elements. + + Parameters: + target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See 's constructor documentation. + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created s, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(el, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams); + if (_isArray(e)) + Array.prototype.push.apply(results, e); + else results.push(e); + } + return results; + }; + + /* + Function: animate + This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating + the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // onComplete repaints, just to make sure everything looks good at the end of the animation. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /** + * checks for a listener for the given condition, executing it if found, passing in the given value. + * condition listeners would have been attached using "bind" (which is, you could argue, now overloaded, since + * firing click events etc is a bit different to what this does). i thought about adding a "bindCondition" + * or something, but decided against it, for the sake of simplicity. jsPlumb will never fire one of these + * condition events anyway. + */ + this.checkCondition = function(conditionName, value) { + var l = _currentInstance.getListener(conditionName); + var r = true; + if (l && l.length > 0) { + try { + for (var i = 0 ; i < l.length; i++) { + r = r && l[i](value); + } + } + catch (e) { + _log(_currentInstance, "cannot check condition [" + conditionName + "]" + e); + } + } + return r; + }; + + /* + Function: connect + Establishes a between two elements (or s, which are themselves registered to elements). + + Parameters: + params - Object containing constructor arguments for the Connection. See 's constructor documentation. + referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of + Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection. + + Returns: + The newly created . + */ + this.connect = function(params, referenceParams) { + // prepare a final set of parameters to create connection with + var _p = _prepareConnectionParams(params, referenceParams), jpc; + // TODO probably a nicer return value if the connection was not made. _prepareConnectionParams + // will return null (and log something) if either endpoint was full. what would be nicer is to + // create a dedicated 'error' object. + if (_p) { + // a connect call will delete its created endpoints on detach, unless otherwise specified. + // this is because the endpoints belong to this connection only, and are no use to + // anyone else, so they hang around like a bad smell. + if (_p.deleteEndpointsOnDetach == null) + _p.deleteEndpointsOnDetach = true; + + // create the connection. it is not yet registered + jpc = _newConnection(_p); + // now add it the model, fire an event, and redraw + _finaliseConnection(jpc, _p); + } + return jpc; + }; + + /* + Function: deleteEndpoint + Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too) + + Parameters: + object - either an object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + if (endpoint.endpoint.cleanup) endpoint.endpoint.cleanup(); + _removeElements(endpoint.endpoint.getDisplayElements()); + _currentInstance.anchorManager.deleteEndpoint(endpoint); + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + if (!jsPlumbAdapter.headless) + _currentInstance.dragManager.endpointDeleted(endpoint); + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every , and their associated s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + _currentInstance.setSuspendDrawing(true); + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + + _currentInstance.setSuspendDrawing(false, true); + }; + + var fireDetachEvent = function(jpc, doFireEvent, originalEvent) { + // may have been given a connection, or in special cases, an object + var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + argIsConnection = jpc.constructor == connType, + params = argIsConnection ? { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + } : jpc; + + if (doFireEvent) { + _currentInstance.fire("jsPlumbConnectionDetached", params, originalEvent); + // introduced in 1.3.11..an alias because the original event name is unwieldy. in future versions this will be the only event and the other will no longer be fired. + _currentInstance.fire("connectionDetached", params, originalEvent); + } + _currentInstance.anchorManager.connectionDetached(params); + }, + /** + fires an event to indicate an existing connection is being dragged. + */ + fireConnectionDraggingEvent = function(jpc) { + _currentInstance.fire("connectionDrag", jpc); + }, + fireConnectionDragStopEvent = function(jpc) { + _currentInstance.fire("connectionDragStop", jpc); + }; + + + /* + Function: detach + Detaches and then removes a . From 1.3.5 this method has been altered to remove support for + specifying Connections by various parameters; you can now pass in a Connection as the first argument and + an optional parameters object as a second argument. If you need the functionality this method provided + before 1.3.5 then you should use the getConnections method to get the list of Connections to detach, and + then iterate through them, calling this for each one. + + Parameters: + connection - the to detach + params - optional parameters to the detach call. valid values here are + fireEvent : defaults to false; indicates you want jsPlumb to fire a connection + detached event. The thinking behind this is that if you made a programmatic + call to detach an event, you probably don't need the callback. + forceDetach : defaults to false. allows you to override any beforeDetach listeners that may be registered. + + Returns: + true if successful, false if not. + */ + this.detach = function() { + + if (arguments.length == 0) return; + var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + firstArgIsConnection = arguments[0].constructor == connType, + params = arguments.length == 2 ? firstArgIsConnection ? (arguments[1] || {}) : arguments[0] : arguments[0], + fireEvent = (params.fireEvent !== false), + forceDetach = params.forceDetach, + connection = firstArgIsConnection ? arguments[0] : params.connection; + + if (connection) { + if (forceDetach || (connection.isDetachAllowed(connection) + && connection.endpoints[0].isDetachAllowed(connection) + && connection.endpoints[1].isDetachAllowed(connection))) { + if (forceDetach || _currentInstance.checkCondition("beforeDetach", connection)) + connection.endpoints[0].detach(connection, false, true, fireEvent); // TODO check this param iscorrect for endpoint's detach method + } + } + else { + var _p = jsPlumb.extend( {}, params); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1]), fireEvent); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source), + targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + if (_currentInstance.checkCondition("beforeDetach", jpc)) { + jpc.endpoints[0].detach(jpc, false, true, fireEvent); + } + } + }); + } + } + }; + + /* + Function: detachAllConnections + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + params - optional parameters. alowed values: + fireEvent : defaults to true, whether or not to fire the detach event. + + Returns: + void + */ + this.detachAllConnections = function(el, params) { + params = params || {}; + el = _getElementObject(el); + var id = _getAttribute(el, "id"), + endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + }; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Parameters: + params - optional params object containing: + fireEvent : whether or not to fire detach events. defaults to true. + + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function(params) { + params = params || {}; + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of your + library's draggable method so that jsPlumb can setup the appropriate callbacks. Your + underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + // helpers for select/selectEndpoints + var _setOperation = function(list, func, args, selector) { + for (var i = 0; i < list.length; i++) { + list[i][func].apply(list[i], args); + } + return selector(list); + }, + _getOperation = function(list, func, args) { + var out = []; + for (var i = 0; i < list.length; i++) { + out.push([ list[i][func].apply(list[i], args), list[i] ]); + } + return out; + }, + setter = function(list, func, selector) { + return function() { + return _setOperation(list, func, arguments, selector); + }; + }, + getter = function(list, func) { + return function() { + return _getOperation(list, func, arguments); + }; + }, + prepareList = function(input, doNotGetIds) { + var r = []; + if (input) { + if (typeof input == 'string') { + if (input === "*") return input; + r.push(input); + } + else { + if (doNotGetIds) r = input; + else { + for (var i = 0; i < input.length; i++) + r.push(_getId(_getElementObject(input[i]))); + } + } + } + return r; + }, + filterList = function(list, value, missingIsFalse) { + if (list === "*") return true; + return list.length > 0 ? _indexOf(list, value) != -1 : !missingIsFalse; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method, + * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the + * default scope). If multiple scopes are passed in, the return value will be a map of { scope -> [ connection... ] }. + * + * Parameters: + * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list + * of connections that are in the given scope. use '*' for all scopes. + * options - if the argument is a JS object, you can specify a finer-grained filter: + * + * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes. Also may have the value '*', indicating any scope. + * - *source* either a string representing an element id, a selector, or an array of ids. Also may have the value '*', indicating any source. Constrains the result to connections having this/these element(s) as source. + * - *target* either a string representing an element id, a selector, or an array of ids. Also may have the value '*', indicating any target. Constrains the result to connections having this/these element(s) as target. + * flat - return results in a flat array (don't return an object whose keys are scopes and whose values are lists per scope). + * + */ + this.getConnections = function(options, flat) { + if (!options) { + options = {}; + } else if (options.constructor == String) { + options = { "scope": options }; + } + var + scope = options.scope || _currentInstance.getDefaultScope(), + scopes = prepareList(scope, true), + sources = prepareList(options.source), + targets = prepareList(options.target), + results = (!flat && scopes.length > 1) ? {} : [], + _addOne = function(scope, obj) { + if (!flat && scopes.length > 1) { + var ss = results[scope]; + if (ss == null) { + ss = []; results[scope] = ss; + } + ss.push(obj); + } else results.push(obj); + }; + for ( var i in connectionsByScope) { + if (filterList(scopes, i)) { + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filterList(sources, c.sourceId) && filterList(targets, c.targetId)) + _addOne(i, c); + } + } + } + return results; + }; + + var _curryEach = function(list, executor) { + return function(f) { + for (var i = 0; i < list.length; i++) { + f(list[i]); + } + return executor(list); + }; + }, + _curryGet = function(list) { + return function(idx) { + return list[idx]; + }; + }; + + var _makeCommonSelectHandler = function(list, executor) { + return { + // setters + setHover:setter(list, "setHover", executor), + removeAllOverlays:setter(list, "removeAllOverlays", executor), + setLabel:setter(list, "setLabel", executor), + addOverlay:setter(list, "addOverlay", executor), + removeOverlay:setter(list, "removeOverlay", executor), + removeOverlays:setter(list, "removeOverlays", executor), + showOverlay:setter(list, "showOverlay", executor), + hideOverlay:setter(list, "hideOverlay", executor), + showOverlays:setter(list, "showOverlays", executor), + hideOverlays:setter(list, "hideOverlays", executor), + setPaintStyle:setter(list, "setPaintStyle", executor), + setHoverPaintStyle:setter(list, "setHoverPaintStyle", executor), + setParameter:setter(list, "setParameter", executor), + setParameters:setter(list, "setParameters", executor), + setVisible:setter(list, "setVisible", executor), + setZIndex:setter(list, "setZIndex", executor), + repaint:setter(list, "repaint", executor), + addType:setter(list, "addType", executor), + toggleType:setter(list, "toggleType", executor), + removeType:setter(list, "removeType", executor), + + // getters + getLabel:getter(list, "getLabel"), + getOverlay:getter(list, "getOverlay"), + isHover:getter(list, "isHover"), + getParameter:getter(list, "getParameter"), + getParameters:getter(list, "getParameters"), + getPaintStyle:getter(list, "getPaintStyle"), + getHoverPaintStyle:getter(list, "getHoverPaintStyle"), + isVisible:getter(list, "isVisible"), + getZIndex:getter(list, "getZIndex"), + hasType:getter(list, "hasType"), + getType:getter(list, "getType"), + + // util + length:list.length, + each:_curryEach(list, executor), + get:_curryGet(list) + }; + + }; + + var _makeConnectionSelectHandler = function(list) { + var common = _makeCommonSelectHandler(list, _makeConnectionSelectHandler); + return jsPlumb.CurrentLibrary.extend(common, { + // setters + setDetachable:setter(list, "setDetachable", _makeConnectionSelectHandler), + setConnector:setter(list, "setConnector", _makeConnectionSelectHandler), + detach:function() { + for (var i = 0; i < list.length; i++) + _currentInstance.detach(list[i]); + }, + // getters + isDetachable:getter(list, "isDetachable") + }); + }; + + var _makeEndpointSelectHandler = function(list) { + var common = _makeCommonSelectHandler(list, _makeEndpointSelectHandler); + return jsPlumb.CurrentLibrary.extend(common, { + setEnabled:setter(list, "setEnabled", _makeEndpointSelectHandler), + isEnabled:getter(list, "isEnabled"), + detachAll:function() { + for (var i = 0; i < list.length; i++) + list[i].detachAll(); + }, + "delete":function() { + for (var i = 0; i < list.length; i++) + _currentInstance.deleteEndpoint(list[i]); + } + }); + }; + + /* + * Function: select + * Selects a set of Connections, using the filter options from the getConnections method, and returns an object + * that allows you to perform an operation on all of the Connections at once. The return value from any of these + * operations is the original list of Connections, allowing operations to be chained (for 'setter' type operations). + * 'getter' type operations return an array of values, where each entry is a [Connection, return value] pair. + * + * Parameters: + * scope - see getConnections + * source - see getConnections + * target - see getConnections + * + * Returns: + * A list of Connections on which operations may be executed. 'Setter' type operations can be chained; 'getter' type operations + * return an array of [Connection, value] pairs, one entry for each Connection in the list returned. The full list of operations + * is as follows (where not specified, the operation's effect or return value is the same as the corresponding method on Connection) : + * - setHover + * - removeAllOverlays + * - setLabel + * - addOverlay + * - removeOverlay + * - removeOverlays + * - showOverlay + * - hideOverlay + * - showOverlays + * - hideOverlays + * - setPaintStyle + * - setHoverPaintStyle + * - setDetachable + * - setConnector + * - setParameter + * - setParameters + * - getLabel + * - getOverlay + * - isHover + * - isDetachable + * - getParameter + * - getParameters + * - getPaintStyle + * - getHoverPaintStyle + * - detach detaches all the connections in the list. not chainable and does not return anything. + * - length : returns the length of the list. + * - get(index) : returns the Connection at 'index' in the list. + * - each(function(connection)...) : allows you to specify your own function to execute; this function is chainable. + */ + this.select = function(params) { + params = params || {}; + params.scope = params.scope || "*"; + var c = _currentInstance.getConnections(params, true); + return _makeConnectionSelectHandler(c); + }; + + /* + * Function: selectEndpoints + * Selects a set of Endpoints and returns an object that allows you to execute various different methods on them at once. The return + * value from any of these operations is the original list of Endpoints, allowing operations to be chained (for 'setter' type + * operations). 'getter' type operations return an array of values, where each entry is an [Endpoint, return value] pair. + * + * Parameters: + * scope - either a string or an array of strings. + * source - either a string, a dom element, or a selector, or an array of any mix of these. limits returned endpoints to those that are declared as a source endpoint on any elements identified. + * target - either a string, a dom element, or a selector, or an array of any mix of these. limits returned endpoints to those that are declared as a target endpoint on any elements identified. + * element - either a string, a dom element, or a selector, or an array of any mix of these. limits returned endpoints to those that are declared as either a source OR a target endpoint on any elements identified. + * + * Returns: + * A list of Endpoints on which operations may be executed. 'Setter' type operations can be chained; 'getter' type operations + * return an array of [Endpoint, value] pairs, one entry for each Endpoint in the list returned. The full list of operations + * is as follows (where not specified, the operation's effect or return value is the same as the corresponding method on Endpoint) : + * - setHover + * - removeAllOverlays + * - setLabel + * - addOverlay + * - removeOverlay + * - removeOverlays + * - showOverlay + * - hideOverlay + * - showOverlays + * - hideOverlays + * - setPaintStyle + * - setHoverPaintStyle + * - setParameter + * - setParameters + * - getLabel + * - getOverlay + * - isHover + * - isDetachable + * - getParameter + * - getParameters + * - getPaintStyle + * - getHoverPaintStyle + * - detachAll Detaches all the Connections from every Endpoint in the list. not chainable and does not return anything. + * - delete Deletes every Endpoint in the list. not chainable and does not return anything. + * - length : returns the length of the list. + * - get(index) : returns the Endpoint at 'index' in the list. + * - each(function(endpoint)...) : allows you to specify your own function to execute; this function is chainable. + */ + this.selectEndpoints = function(params) { + params = params || {}; + params.scope = params.scope || "*"; + var noElementFilters = !params.element && !params.source && !params.target, + elements = noElementFilters ? "*" : prepareList(params.element), + sources = noElementFilters ? "*" : prepareList(params.source), + targets = noElementFilters ? "*" : prepareList(params.target), + scopes = prepareList(params.scope, true); + + var ep = []; + + for (var el in endpointsByElement) { + var either = filterList(elements, el, true), + source = filterList(sources, el, true), + sourceMatchExact = sources != "*", + target = filterList(targets, el, true), + targetMatchExact = targets != "*"; + + // if they requested 'either' then just match scope. otherwise if they requested 'source' (not as a wildcard) then we have to match only endpoints that have isSource set to to true, and the same thing with isTarget. + if ( either || source || target ) { + inner: + for (var i = 0; i < endpointsByElement[el].length; i++) { + var _ep = endpointsByElement[el][i]; + if (filterList(scopes, _ep.scope, true)) { + + var noMatchSource = (sourceMatchExact && sources.length > 0 && !_ep.isSource), + noMatchTarget = (targetMatchExact && targets.length > 0 && !_ep.isTarget); + + if (noMatchSource || noMatchTarget) + continue inner; + + ep.push(_ep); + } + } + } + } + + return _makeEndpointSelectHandler(ep); + }; + + /* + * Function: getAllConnections + * Gets all connections, as a map of { scope -> [ connection... ] }. + */ + this.getAllConnections = function() { + return connectionsByScope; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /** + * Function:getEndpoints + * Gets the list of Endpoints for a given selector, or element id. + * @param el + * @return + */ + this.getEndpoints = function(el) { + return endpointsByElement[_getId(el)]; + }; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + this.getOffset = function(id) { + var o = offsets[id]; + return _updateOffset({elId:id}); + }; + + this.getSelector = function(spec) { + return jsPlumb.CurrentLibrary.getSelector(spec); + }; + + this.getSize = function(id) { + var s = sizes[id]; + if (!s) _updateOffset({elId:id}); + return sizes[id]; + }; + + this.appendElement = _appendElement; + + var _hoverSuspended = false; + this.isHoverSuspended = function() { return _hoverSuspended; }; + this.setHoverSuspended = function(s) { _hoverSuspended = s; }; + + var _isAvailable = function(m) { + return function() { + return jsPlumbAdapter.isRenderModeAvailable(m); + }; + } + this.isCanvasAvailable = _isAvailable("canvas"); + this.isSVGAvailable = _isAvailable("svg"); + this.isVMLAvailable = _isAvailable("vml"); + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + changeEndpoints - whether not to also hide endpoints on the element. by default this is false. + + Returns: + void + */ + this.hide = function(el, changeEndpoints) { + _setVisible(el, "none", changeEndpoints); + }; + + // exposed for other objects to use to get a unique id. + this.idstamp = _idstamp; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + if (!initialized) { + _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run. + + var bindOne = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && renderMode == jsPlumb.CANVAS) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + var t = c[i].connector[event](e); + if (t) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i].endpoint[event](e)) return; + } + } + } + }); + }; + bindOne("click");bindOne("dblclick");bindOne("mousemove");bindOne("mousedown");bindOne("mouseup");bindOne("contextmenu"); + + initialized = true; + _currentInstance.fire("ready"); + } + }; + + this.log = log; + this.jsPlumbUIComponent = jsPlumbUIComponent; + + /* + * Creates an anchor with the given params. + * + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function() { + if (arguments.length == 0) return null; + var specimen = arguments[0], elementId = arguments[1], jsPlumbInstance = arguments[2], newAnchor = null; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; //TODO hazy here about whether it should be added or is already added somehow. + // is it the name of an anchor type? + else if (typeof specimen == "string") { + newAnchor = jsPlumb.Anchors[arguments[0]]({elementId:elementId, jsPlumbInstance:_currentInstance}); + } + // is it an array? it will be one of: + // an array of [name, params] - this defines a single anchor + // an array of arrays - this defines some dynamic anchors + // an array of numbers - this defines a single anchor. + else if (_isArray(specimen)) { + if (_isArray(specimen[0]) || _isString(specimen[0])) { + if (specimen.length == 2 && _isString(specimen[0]) && _isObject(specimen[1])) { + var pp = jsPlumb.extend({elementId:elementId, jsPlumbInstance:_currentInstance}, specimen[1]); + newAnchor = jsPlumb.Anchors[specimen[0]](pp); + } + else + newAnchor = new DynamicAnchor(specimen, null, elementId); + } + else { + var anchorParams = { + x:specimen[0], y:specimen[1], + orientation : (specimen.length >= 4) ? [ specimen[2], specimen[3] ] : [0,0], + offsets : (specimen.length == 6) ? [ specimen[4], specimen[5] ] : [ 0, 0 ], + elementId:elementId + }; + newAnchor = new Anchor(anchorParams); + newAnchor.clone = function() { return new Anchor(anchorParams); }; + } + } + + if (!newAnchor.id) newAnchor.id = "anchor_" + _idstamp(); + return newAnchor; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types, elementId, jsPlumbInstance) { + var r = []; + for ( var i = 0; i < types.length; i++) { + if (typeof types[i] == "string") + r.push(jsPlumb.Anchors[types[i]]({elementId:elementId, jsPlumbInstance:jsPlumbInstance})); + else if (_isArray(types[i])) + r.push(_currentInstance.makeAnchor(types[i], elementId, jsPlumbInstance)); + } + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /** + * Function: makeTarget + * Makes some DOM element a Connection target, allowing you to drag connections to it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a target. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * scope optional. scope for the drop zone. + * dropOptions optional. same stuff as you would pass to dropOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to true. whether or not to delete + * any Endpoints created by a connection to this target if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * maxConnections optional. Specifies the maximum number of Connections that can be made to this element as a target. Default is no limit. + * onMaxConnections optional. Function to call when user attempts to drop a connection but the limit has been reached. The callback is passed two arguments: a JS object with { element, connection, maxConnection }, and the original event. + * + * + */ + var _targetEndpointDefinitions = {}, + _targetEndpoints = {}, + _targetEndpointsUnique = {}, + _targetMaxConnections = {}, + _setEndpointPaintStylesAndAnchor = function(ep, epIndex) { + ep.paintStyle = ep.paintStyle || + _currentInstance.Defaults.EndpointStyles[epIndex] || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyles[epIndex] || + jsPlumb.Defaults.EndpointStyle; + ep.hoverPaintStyle = ep.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyles[epIndex] || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyles[epIndex] || + jsPlumb.Defaults.EndpointHoverStyle; + + ep.anchor = ep.anchor || + _currentInstance.Defaults.Anchors[epIndex] || + _currentInstance.Defaults.Anchor || + jsPlumb.Defaults.Anchors[epIndex] || + jsPlumb.Defaults.Anchor; + + ep.endpoint = ep.endpoint || + _currentInstance.Defaults.Endpoints[epIndex] || + _currentInstance.Defaults.Endpoint || + jsPlumb.Defaults.Endpoints[epIndex] || + jsPlumb.Defaults.Endpoint; + }; + + this.makeTarget = function(el, params, referenceParams) { + + var p = jsPlumb.extend({_jsPlumb:_currentInstance}, referenceParams); + jsPlumb.extend(p, params); + _setEndpointPaintStylesAndAnchor(p, 1); + var jpcl = jsPlumb.CurrentLibrary, + targetScope = p.scope || _currentInstance.Defaults.Scope, + deleteEndpointsOnDetach = !(p.deleteEndpointsOnDetach === false), + maxConnections = p.maxConnections || -1, + onMaxConnections = p.onMaxConnections; + _doOne = function(_el) { + + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el); + _targetEndpointDefinitions[elid] = p; + _targetEndpointsUnique[elid] = p.uniqueEndpoint, + _targetMaxConnections[elid] = maxConnections, + _targetsEnabled[elid] = true, + proxyComponent = new jsPlumbUIComponent(p); + + var dropOptions = jsPlumb.extend({}, p.dropOptions || {}), + _drop = function() { + + var originalEvent = jsPlumb.CurrentLibrary.getDropEvent(arguments), + targetCount = _currentInstance.select({target:elid}).length; + + + _currentInstance.currentlyDragging = false; + var draggable = _getElementObject(jpcl.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + // restore the original scope if necessary (issue 57) + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id], + source = jpc.endpoints[0], + _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : {}; + + if (!_targetsEnabled[elid] || _targetMaxConnections[elid] > 0 && targetCount >= _targetMaxConnections[elid]){ + //console.log("target element " + elid + " is full."); + if (onMaxConnections) { + onMaxConnections({ + element:_el, + connection:jpc + }, originalEvent); + } + return false; + } + + // unlock the source anchor to allow it to refresh its position if necessary + source.anchor.locked = false; + + if (scope) jpcl.setDragScope(draggable, scope); + + // check if drop is allowed here. + //var _continue = jpc.isDropAllowed(jpc.sourceId, _getId(_el), jpc.scope); + var _continue = proxyComponent.isDropAllowed(jpc.sourceId, _getId(_el), jpc.scope, jpc, null); + + // regardless of whether the connection is ok, reconfigure the existing connection to + // point at the current info. we need this to be correct for the detach event that will follow. + // clear the source endpoint from the list to detach. we will detach this connection at this + // point, but we want to keep the source endpoint. the target is a floating endpoint and should + // be removed. TODO need to figure out whether this code can result in endpoints kicking around + // when they shouldnt be. like is this a full detach of a connection? can it be? + if (jpc.endpointsToDeleteOnDetach) { + if (source === jpc.endpointsToDeleteOnDetach[0]) + jpc.endpointsToDeleteOnDetach[0] = null; + else if (source === jpc.endpointsToDeleteOnDetach[1]) + jpc.endpointsToDeleteOnDetach[1] = null; + } + // reinstate any suspended endpoint; this just puts the connection back into + // a state in which it will report sensible values if someone asks it about + // its target. we're going to throw this connection away shortly so it doesnt matter + // if we manipulate it a bit. + if (jpc.suspendedEndpoint) { + jpc.targetId = jpc.suspendedEndpoint.elementId; + jpc.target = jpcl.getElementObject(jpc.suspendedEndpoint.elementId); + jpc.endpoints[1] = jpc.suspendedEndpoint; + } + + if (_continue) { + + // detach this connection from the source. + source.detach(jpc, false, true, false); + + // make a new Endpoint for the target + var newEndpoint = _targetEndpoints[elid] || _currentInstance.addEndpoint(_el, p); + if (p.uniqueEndpoint) _targetEndpoints[elid] = newEndpoint; // may of course just store what it just pulled out. that's ok. + newEndpoint._makeTargetCreator = true; + + // if the anchor has a 'positionFinder' set, then delegate to that function to find + // out where to locate the anchor. + if (newEndpoint.anchor.positionFinder != null) { + var dropPosition = jpcl.getUIPosition(arguments), + elPosition = jpcl.getOffset(_el), + elSize = jpcl.getSize(_el), + ap = newEndpoint.anchor.positionFinder(dropPosition, elPosition, elSize, newEndpoint.anchor.constructorParams); + newEndpoint.anchor.x = ap[0]; + newEndpoint.anchor.y = ap[1]; + // now figure an orientation for it..kind of hard to know what to do actually. probably the best thing i can do is to + // support specifying an orientation in the anchor's spec. if one is not supplied then i will make the orientation + // be what will cause the most natural link to the source: it will be pointing at the source, but it needs to be + // specified in one axis only, and so how to make that choice? i think i will use whichever axis is the one in which + // the target is furthest away from the source. + } + var c = _currentInstance.connect({ + source:source, + target:newEndpoint, + scope:scope, + previousConnection:jpc, + container:jpc.parent, + deleteEndpointsOnDetach:deleteEndpointsOnDetach, + // 'endpointWillMoveAfterConnection' is set by the makeSource function, and it indicates that the + // given endpoint will actually transfer from the element it is currently attached to to some other + // element after a connection has been established. in that case, we do not want to fire the + // connection event, since it will have the wrong data in it; makeSource will do it for us. + // this is controlled by the 'parent' parameter on a makeSource call. + doNotFireConnectionEvent:source.endpointWillMoveAfterConnection + }); + + // delete the original target endpoint. but only want to do this if the endpoint was created + // automatically and has no other connections. + if (jpc.endpoints[1]._makeTargetCreator && jpc.endpoints[1].connections.length < 2) + _currentInstance.deleteEndpoint(jpc.endpoints[1]); + + if (deleteEndpointsOnDetach) + c.endpointsToDeleteOnDetach = [ source, newEndpoint ]; + + c.repaint(); + } + // if not allowed to drop... + else { + // TODO this code is identical (pretty much) to what happens when a connection + // dragged from a normal endpoint is in this situation. refactor. + // is this an existing connection, and will we reattach? + if (jpc.suspendedEndpoint) { + if (source.isReattach) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + _currentInstance.repaint(source.elementId); + } + else + source.detach(jpc, false, true, true, originalEvent); // otherwise, detach the connection and tell everyone about it. + } + + } + }; + + var dropEvent = jpcl.dragEvents['drop']; + dropOptions["scope"] = dropOptions["scope"] || targetScope; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop); + + jpcl.initDroppable(_el, dropOptions, true); + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + + return _currentInstance; + }; + + /* + * Function: unmakeTarget + * Sets the given element to no longer be a connection target. + * Parameters: + * el - either a string id or a selector representing the element. + * Returns: + * The current jsPlumb instance. + */ + this.unmakeTarget = function(el, doNotClearArrays) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + var elid = _getId(el); + + // TODO this is not an exhaustive unmake of a target, since it does not remove the droppable stuff from + // the element. the effect will be to prevent it form behaving as a target, but it's not completely purged. + if (!doNotClearArrays) { + delete _targetEndpointDefinitions[elid]; + delete _targetEndpointsUnique[elid]; + delete _targetMaxConnections[elid]; + delete _targetsEnabled[elid]; + } + + return _currentInstance; + }; + + /** + * helper method to make a list of elements drop targets. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeTargets = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeTarget(els[i], params, referenceParams); + } + }; + + /** + * Function: makeSource + * Makes some DOM element a Connection source, allowing you to drag connections from it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a source. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * parent optional. the element to add Endpoints to when a Connection is established. if you omit this, + * Endpoints will be added to 'el'. + * scope optional. scope for the connections dragged from this element. + * dragOptions optional. same stuff as you would pass to dragOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to false. whether or not to delete + * any Endpoints created by a connection from this source if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * filter - optional function to call when the user presses the mouse button to start a drag. This function is passed the original + * event and the element on which the associated makeSource call was made. If it returns anything other than false, + * the drag begins as usual. But if it returns false (the boolean false, not something falsey), the drag is aborted. + * + * + */ + var _sourceEndpointDefinitions = {}, + _sourceEndpoints = {}, + _sourceEndpointsUnique = {}, + _sourcesEnabled = {}, + _sourceTriggers = {}, + _sourceMaxConnections = {}, + _targetsEnabled = {}; + + this.makeSource = function(el, params, referenceParams) { + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + _setEndpointPaintStylesAndAnchor(p, 0); + var jpcl = jsPlumb.CurrentLibrary, + maxConnections = p.maxConnections || -1, + onMaxConnections = p.onMaxConnections, + _doOne = function(_el) { + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el), + parent = p.parent, + idToRegisterAgainst = parent != null ? _currentInstance.getId(jpcl.getElementObject(parent)) : elid; + + _sourceEndpointDefinitions[idToRegisterAgainst] = p; + _sourceEndpointsUnique[idToRegisterAgainst] = p.uniqueEndpoint; + _sourcesEnabled[idToRegisterAgainst] = true; + + var stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"], + dragOptions = jsPlumb.extend({ }, p.dragOptions || {}), + existingDrag = dragOptions.drag, + existingStop = dragOptions.stop, + ep = null, + endpointAddedButNoDragYet = false; + + _sourceMaxConnections[idToRegisterAgainst] = maxConnections; + + // set scope if its not set in dragOptions but was passed in in params + dragOptions["scope"] = dragOptions["scope"] || p.scope; + + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + if (existingDrag) existingDrag.apply(this, arguments); + endpointAddedButNoDragYet = false; + }); + + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], function() { + if (existingStop) existingStop.apply(this, arguments); + + //_currentlyDown = false; + _currentInstance.currentlyDragging = false; + + if (ep.connections.length == 0) + _currentInstance.deleteEndpoint(ep); + else { + + jpcl.unbind(ep.canvas, "mousedown"); + + // reset the anchor to the anchor that was initially provided. the one we were using to drag + // the connection was just a placeholder that was located at the place the user pressed the + // mouse button to initiate the drag. + var anchorDef = p.anchor || _currentInstance.Defaults.Anchor, + oldAnchor = ep.anchor, + oldConnection = ep.connections[0]; + + ep.anchor = _currentInstance.makeAnchor(anchorDef, elid, _currentInstance); + + if (p.parent) { + var parent = jpcl.getElementObject(p.parent); + if (parent) { + var currentId = ep.elementId; + + var potentialParent = p.container || _currentInstance.Defaults.Container || jsPlumb.Defaults.Container; + + ep.setElement(parent, potentialParent); + ep.endpointWillMoveAfterConnection = false; + _currentInstance.anchorManager.rehomeEndpoint(currentId, parent); + oldConnection.previousConnection = null; + // remove from connectionsByScope + _removeWithFunction(connectionsByScope[oldConnection.scope], function(c) { + return c.id === oldConnection.id; + }); + _currentInstance.anchorManager.connectionDetached({ + sourceId:oldConnection.sourceId, + targetId:oldConnection.targetId, + connection:oldConnection + }); + _finaliseConnection(oldConnection); + } + } + + ep.repaint(); + _currentInstance.repaint(ep.elementId); + _currentInstance.repaint(oldConnection.targetId); + + } + }); + // when the user presses the mouse, add an Endpoint, if we are enabled. + var mouseDownListener = function(e) { + + // if disabled, return. + if (!_sourcesEnabled[idToRegisterAgainst]) return; + + // if maxConnections reached + var sourceCount = _currentInstance.select({source:idToRegisterAgainst}).length + if (_sourceMaxConnections[idToRegisterAgainst] >= 0 && sourceCount >= _sourceMaxConnections[idToRegisterAgainst]) { + if (onMaxConnections) { + onMaxConnections({ + element:_el, + maxConnections:maxConnections + }, e); + } + return false; + } + + // if a filter was given, run it, and return if it says no. + if (params.filter) { + // pass the original event to the user: + var r = params.filter(jpcl.getOriginalEvent(e), _el); + if (r === false) return; + } + + // make sure we have the latest offset for this div + var myOffsetInfo = _updateOffset({elId:elid}); + + var x = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width, + y = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height, + parentX = x, + parentY = y; + + // if there is a parent, the endpoint will actually be added to it now, rather than the div + // that was the source. in that case, we have to adjust the anchor position so it refers to + // the parent. + if (p.parent) { + var pEl = jpcl.getElementObject(p.parent), + pId = _getId(pEl); + myOffsetInfo = _updateOffset({elId:pId}); + parentX = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width, + parentY = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height; + } + + // we need to override the anchor in here, and force 'isSource', but we don't want to mess with + // the params passed in, because after a connection is established we're going to reset the endpoint + // to have the anchor we were given. + var tempEndpointParams = {}; + jsPlumb.extend(tempEndpointParams, p); + tempEndpointParams.isSource = true; + tempEndpointParams.anchor = [x,y,0,0]; + tempEndpointParams.parentAnchor = [ parentX, parentY, 0, 0 ]; + tempEndpointParams.dragOptions = dragOptions; + // if a parent was given we need to turn that into a "container" argument. this is, by default, + // the parent of the element we will move to, so parent of p.parent in this case. however, if + // the user has specified a 'container' on the endpoint definition or on + // the defaults, we should use that. + if (p.parent) { + var potentialParent = tempEndpointParams.container || _currentInstance.Defaults.Container || jsPlumb.Defaults.Container; + if (potentialParent) + tempEndpointParams.container = potentialParent; + else + tempEndpointParams.container = jsPlumb.CurrentLibrary.getParent(p.parent); + } + + ep = _currentInstance.addEndpoint(elid, tempEndpointParams); + + endpointAddedButNoDragYet = true; + // we set this to prevent connections from firing attach events before this function has had a chance + // to move the endpoint. + ep.endpointWillMoveAfterConnection = p.parent != null; + ep.endpointWillMoveTo = p.parent ? jpcl.getElementObject(p.parent) : null; + + var _delTempEndpoint = function() { + // this mouseup event is fired only if no dragging occurred, by jquery and yui, but for mootools + // it is fired even if dragging has occurred, in which case we would blow away a perfectly + // legitimate endpoint, were it not for this check. the flag is set after adding an + // endpoint and cleared in a drag listener we set in the dragOptions above. + if(endpointAddedButNoDragYet) { + _currentInstance.deleteEndpoint(ep); + } + }; + + _currentInstance.registerListener(ep.canvas, "mouseup", _delTempEndpoint); + _currentInstance.registerListener(_el, "mouseup", _delTempEndpoint); + + // and then trigger its mousedown event, which will kick off a drag, which will start dragging + // a new connection from this endpoint. + jpcl.trigger(ep.canvas, "mousedown", e); + }; + + // register this on jsPlumb so that it can be cleared by a reset. + _currentInstance.registerListener(_el, "mousedown", mouseDownListener); + _sourceTriggers[elid] = mouseDownListener; + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + + return _currentInstance; + }; + + /** + * Function: unmakeSource + * Sets the given element to no longer be a connection source. + * Parameters: + * el - either a string id or a selector representing the element. + * Returns: + * The current jsPlumb instance. + */ + this.unmakeSource = function(el, doNotClearArrays) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + var id = _getId(el), + mouseDownListener = _sourceTriggers[id]; + + if (mouseDownListener) + _currentInstance.unregisterListener(el, "mousedown", mouseDownListener); + + if (!doNotClearArrays) { + delete _sourceEndpointDefinitions[id]; + delete _sourceEndpointsUnique[id]; + delete _sourcesEnabled[id]; + delete _sourceTriggers[id]; + delete _sourceMaxConnections[id]; + } + + return _currentInstance; + }; + + /* + * Function: unmakeEverySource + * Resets all elements in this instance of jsPlumb so that none of them are connection sources. + * Returns: + * The current jsPlumb instance. + */ + this.unmakeEverySource = function() { + for (var i in _sourcesEnabled) + _currentInstance.unmakeSource(i, true); + + _sourceEndpointDefinitions = {}; + _sourceEndpointsUnique = {}; + _sourcesEnabled = {}; + _sourceTriggers = {}; + }; + + /* + * Function: unmakeEveryTarget + * Resets all elements in this instance of jsPlumb so that none of them are connection targets. + * Returns: + * The current jsPlumb instance. + */ + this.unmakeEveryTarget = function() { + for (var i in _targetsEnabled) + _currentInstance.unmakeTarget(i, true); + + _targetEndpointDefinitions = {}; + _targetEndpointsUnique = {}; + _targetMaxConnections = {}; + _targetsEnabled = {}; + + return _currentInstance; + }; + + /* + * Function: makeSources + * Makes all elements in some array or a selector connection sources. + * Parameters: + * els - either an array of ids or a selector + * params - parameters to configure each element as a source with + * referenceParams - extra parameters to configure each element as a source with. + * + * Returns: + * The current jsPlumb instance. + */ + this.makeSources = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeSource(els[i], params, referenceParams); + } + + return _currentInstance; + }; + + // does the work of setting a source enabled or disabled. + var _setEnabled = function(type, el, state, toggle) { + var a = type == "source" ? _sourcesEnabled : _targetsEnabled; + + if (_isString(el)) a[el] = toggle ? !a[el] : state; + else if (el.length) { + el = _convertYUICollection(el); + for (var i = 0; i < el.length; i++) { + var id = _el = jsPlumb.CurrentLibrary.getElementObject(el[i]), id = _getId(_el); + a[id] = toggle ? !a[id] : state; + } + } + return _currentInstance; + }; + + /* + Function: setSourceEnabled + Sets the enabled state of one or more elements that were previously made a connection source with the makeSource + method. + + Parameters: + el - either a string representing some element's id, or an array of ids, or a selector. + state - true to enable the element(s), false to disable it. + + Returns: + The current jsPlumb instance. + */ + this.setSourceEnabled = function(el, state) { + return _setEnabled("source", el, state); + }; + + /* + * Function: toggleSourceEnabled + * Toggles the source enabled state of the given element or elements. + * + * Parameters: + * el - either a string representing some element's id, or an array of ids, or a selector. + * state - true to enable the element(s), false to disable it. + * + * Returns: + * The current enabled state of the source. + */ + this.toggleSourceEnabled = function(el) { + _setEnabled("source", el, null, true); + return _currentInstance.isSourceEnabled(el); + }; + + /* + * Function: isSource + * Returns whether or not the given element is registered as a connection source. + * + * Parameters: + * el - either a string id, or a selector representing a single element. + * + * Returns: + * True if source, false if not. + */ + this.isSource = function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return _sourcesEnabled[_getId(el)] != null; + }; + + /* + * Function: isSourceEnabled + * Returns whether or not the given connection source is enabled. + * + * Parameters: + * el - either a string id, or a selector representing a single element. + * + * Returns: + * True if enabled, false if not. + */ + this.isSourceEnabled = function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return _sourcesEnabled[_getId(el)] === true; + }; + + /* + * Function: setTargetEnabled + * Sets the enabled state of one or more elements that were previously made a connection target with the makeTarget method. + * method. + * + * Parameters: + * el - either a string representing some element's id, or an array of ids, or a selector. + * state - true to enable the element(s), false to disable it. + * + * Returns: + * The current jsPlumb instance. + */ + this.setTargetEnabled = function(el, state) { + return _setEnabled("target", el, state); + }; + + /* + * Function: toggleTargetEnabled + * Toggles the target enabled state of the given element or elements. + * + * Parameters: + * el - either a string representing some element's id, or an array of ids, or a selector. + * state - true to enable the element(s), false to disable it. + * + * Returns: + * The current enabled state of the target. + */ + this.toggleTargetEnabled = function(el) { + return _setEnabled("target", el, null, true); + return _currentInstance.isTargetEnabled(el); + }; + + /* + Function: isTarget + Returns whether or not the given element is registered as a connection target. + + Parameters: + el - either a string id, or a selector representing a single element. + + Returns: + True if source, false if not. + */ + this.isTarget = function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return _targetsEnabled[_getId(el)] != null; + }; + + /* + Function: isTargetEnabled + Returns whether or not the given connection target is enabled. + + Parameters: + el - either a string id, or a selector representing a single element. + + Returns: + True if enabled, false if not. + */ + this.isTargetEnabled = function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return _targetsEnabled[_getId(el)] === true; + }; + + /* + Function: ready + Helper method to bind a function to jsPlumb's ready event. + */ + this.ready = function(fn) { + _currentInstance.bind("ready", fn); + }, + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el, ui, timestamp) { + //var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) { + //_processElement(el[i]); + _draw(_getElementObject(el[i]), ui, timestamp); + } + else // ...and single strings. + //_processElement(el); + _draw(_getElementObject(el), ui, timestamp); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"), + ebe = endpointsByElement[elId]; + if (ebe) { + for ( var i = 0; i < ebe.length; i++) + _currentInstance.deleteEndpoint(ebe[i]); + } + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + var _registeredListeners = {}, + _unbindRegisteredListeners = function() { + for (var i in _registeredListeners) { + for (var j = 0; j < _registeredListeners[i].length; j++) { + var info = _registeredListeners[i][j]; + jsPlumb.CurrentLibrary.unbind(info.el, info.event, info.listener); + } + } + _registeredListeners = {}; + }; + + // internal register listener method. gives us a hook to clean things up + // with if the user calls jsPlumb.reset. + this.registerListener = function(el, type, listener) { + jsPlumb.CurrentLibrary.bind(el, type, listener); + _addToList(_registeredListeners, type, {el:el, event:type, listener:listener}); + }; + + this.unregisterListener = function(el, type, listener) { + jsPlumb.CurrentLibrary.unbind(el, type, listener); + _removeWithFunction(_registeredListeners, function(rl) { + return rl.type == type && rl.listener == listener; + }); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + _currentInstance.deleteEveryEndpoint(); + _currentInstance.unbind(); + _targetEndpointDefinitions = {}; + _targetEndpoints = {}; + _targetEndpointsUnique = {}; + _targetMaxConnections = {}; + _sourceEndpointDefinitions = {}; + _sourceEndpoints = {}; + _sourceEndpointsUnique = {}; + _sourceMaxConnections = {}; + _unbindRegisteredListeners(); + _currentInstance.anchorManager.reset(); + if (!jsPlumbAdapter.headless) + _currentInstance.dragManager.reset(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + + Parameters: + value - whether or not to automatically repaint when the window is resized. + + Returns: void + * + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + };*/ + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setId + * Changes the id of some element, adjusting all connections and endpoints + * + * Parameters: + * el - a selector, a DOM element, or a string. + * newId - string. + */ + this.setId = function(el, newId, doNotSetAttribute) { + + var id = el.constructor == String ? el : _currentInstance.getId(el), + sConns = _currentInstance.getConnections({source:id, scope:'*'}, true), + tConns = _currentInstance.getConnections({target:id, scope:'*'}, true); + + newId = "" + newId; + + if (!doNotSetAttribute) { + el = jsPlumb.CurrentLibrary.getElementObject(id); + jsPlumb.CurrentLibrary.setAttribute(el, "id", newId); + } + + el = jsPlumb.CurrentLibrary.getElementObject(newId); + + + endpointsByElement[newId] = endpointsByElement[id] || []; + for (var i = 0; i < endpointsByElement[newId].length; i++) { + endpointsByElement[newId][i].elementId = newId; + endpointsByElement[newId][i].element = el; + endpointsByElement[newId][i].anchor.elementId = newId; + } + delete endpointsByElement[id]; + + _currentInstance.anchorManager.changeId(id, newId); + + var _conns = function(list, epIdx, type) { + for (var i = 0; i < list.length; i++) { + list[i].endpoints[epIdx].elementId = newId; + list[i].endpoints[epIdx].element = el; + list[i][type + "Id"] = newId; + list[i][type] = el; + } + }; + _conns(sConns, 0, "source"); + _conns(tConns, 1, "target"); + }; + + /* + * Function: setIdChanged + * Notify jsPlumb that the element with oldId has had its id changed to newId. + */ + this.setIdChanged = function(oldId, newId) { + _currentInstance.setId(oldId, newId, true); + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction + * Sets the function to fire when the window size has changed and a repaint was fired. + * + * Parameters: + * f - Function to execute. + * + * Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + + var _suspendDrawing = false, + _suspendedAt = null; + /* + * Function: setSuspendDrawing + * Suspends drawing operations. This can be used when you have a lot of connections to make or endpoints to register; + * it will save you a lot of time. + */ + this.setSuspendDrawing = function(val, repaintAfterwards) { + _suspendDrawing = val; + if (val) _suspendedAt = new Date().getTime(); else _suspendedAt = null; + if (repaintAfterwards) _currentInstance.repaintEverything(); + }; + + /* + * Function: isSuspendDrawing + * Returns whether or not drawing is currently suspended. + */ + this.isSuspendDrawing = function() { + return _suspendDrawing; + }; + + /* + * Constant for use with the setRenderMode method + */ + this.CANVAS = "canvas"; + + /* + * Constant for use with the setRenderMode method + */ + this.SVG = "svg"; + + /* + * Constant for use with the setRenderMode method + */ + this.VML = "vml"; + + /* + * Function: setRenderMode + * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that + * what you asked for is not supported (and that VML is). If you asked for VML but the browser does + * not support it, jsPlumb uses SVG. + * + * Returns: + * the render mode that jsPlumb set, which of course may be different from that requested. + */ + this.setRenderMode = function(mode) { + renderMode = jsPlumbAdapter.setRenderMode(mode); + return renderMode; + }; + + this.getRenderMode = function() { return renderMode; }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * changeEndpoints - whether or not to also change the visible state of the endpoints on the element. this also has a bearing on + * other connections on those endpoints: if their other endpoint is also visible, the connections are made visible. + * + * Returns: + * void + */ + this.show = function(el, changeEndpoints) { + _setVisible(el, "block", changeEndpoints); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + //findIndex : _findIndex, + getId : _getId, + makeAnchor:self.makeAnchor, + makeDynamicAnchor:self.makeDynamicAnchor + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * changeEndpoints - whether or not to also toggle the endpoints on the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload + * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element. + * + * Returns: + * void + */ + this.unload = function() { + // this used to do something, but it turns out that what it did was nothing. + // now it exists only for backwards compatibility. + }; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + var adjustForParentOffsetAndScroll = function(xy, el) { + + var offsetParent = null, result = xy; + if (el.tagName.toLowerCase() === "svg" && el.parentNode) { + offsetParent = el.parentNode; + } + else if (el.offsetParent) { + offsetParent = el.offsetParent; + } + if (offsetParent != null) { + var po = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(offsetParent), + so = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : {left:offsetParent.scrollLeft, top:offsetParent.scrollTop}; + + + // i thought it might be cool to do this: + // lastReturnValue[0] = lastReturnValue[0] - offsetParent.offsetLeft + offsetParent.scrollLeft; + // lastReturnValue[1] = lastReturnValue[1] - offsetParent.offsetTop + offsetParent.scrollTop; + // but i think it ignores margins. my reasoning was that it's quicker to not hand off to some underlying + // library. + + result[0] = xy[0] - po.left + so.left; + result[1] = xy[1] - po.top + so.top; + } + + return result; + + }; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + this.elementId = params.elementId; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + + if (timestamp && timestamp === self.timestamp) + return lastReturnValue; + + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + + // adjust loc if there is an offsetParent + lastReturnValue = adjustForParentOffsetAndScroll(lastReturnValue, element.canvas); + + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function(_endpoint) { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + * + * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference, + // the canvas this refers to. + refCanvas = params.referenceCanvas, + size = _getSize(_getElementObject(refCanvas)), + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + xDir = 0, yDir = 0, + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + orientation = null, + _lastResult = null; + + // set these to 0 each; they are used by certain types of connectors in the loopback case, + // when the connector is trying to clear the element it is on. but for floating anchor it's not + // very important. + this.x = 0; this.y = 0; + + this.isFloating = true; + + this.compute = function(params) { + var xy = params.xy, element = params.element, + result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + + // adjust loc if there is an offsetParent + result = adjustForParentOffsetAndScroll(result, element.canvas); + + _lastResult = result; + return result; + }; + + this.getOrientation = function(_endpoint) { + if (orientation) return orientation; + else { + var o = ref.getOrientation(_endpoint); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchor is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector, elementId) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = [], self = this, + _convert = function(anchor) { + return anchor.constructor == Anchor ? anchor: _currentInstance.makeAnchor(anchor, elementId, _currentInstance); + }; + for (var i = 0; i < anchors.length; i++) + _anchors[i] = _convert(anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + this.locked = false; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null, + _curIndex = _anchors.length > 0 ? 0 : -1, + self = this, + + // helper method to calculate the distance between the centers of the two elements. + _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }, + + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + self.x = _curAnchor.x; + self.y = _curAnchor.y; + + return _curAnchor.compute(params); + }; + + this.getCurrentLocation = function() { + return _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + }; + + this.getOrientation = function(_endpoint) { return _curAnchor != null ? _curAnchor.getOrientation(_endpoint) : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + manages anchors for all elements. + */ + // "continuous" anchors: anchors that pick their location based on how many connections the given element has. + // this requires looking at a lot more elements than normal - anything that has had a Continuous anchor applied has + // to be recalculated. so this manager is used as a reference point. the first time, with a new timestamp, that + // a continuous anchor is asked to compute, it calls this guy. or maybe, even, this guy gets called somewhere else + // and compute only ever returns pre-computed values. either way, this is the central point, and we want it to + // be called as few times as possible. + var continuousAnchors = {}, + continuousAnchorLocations = {}, + continuousAnchorOrientations = {}, + Orientation = { HORIZONTAL : "horizontal", VERTICAL : "vertical", DIAGONAL : "diagonal", IDENTITY:"identity" }, + + // TODO this functions uses a crude method of determining orientation between two elements. + // 'diagonal' should be chosen when the angle of the line between the two centers is around + // one of 45, 135, 225 and 315 degrees. maybe +- 15 degrees. + calculateOrientation = function(sourceId, targetId, sd, td) { + + if (sourceId === targetId) return { + orientation:Orientation.IDENTITY, + a:["top", "top"] + }; + + var theta = Math.atan2((td.centery - sd.centery) , (td.centerx - sd.centerx)), + theta2 = Math.atan2((sd.centery - td.centery) , (sd.centerx - td.centerx)), + h = ((sd.left <= td.left && sd.right >= td.left) || (sd.left <= td.right && sd.right >= td.right) || + (sd.left <= td.left && sd.right >= td.right) || (td.left <= sd.left && td.right >= sd.right)), + v = ((sd.top <= td.top && sd.bottom >= td.top) || (sd.top <= td.bottom && sd.bottom >= td.bottom) || + (sd.top <= td.top && sd.bottom >= td.bottom) || (td.top <= sd.top && td.bottom >= sd.bottom)); + + if (! (h || v)) { + var a = null, rls = false, rrs = false, sortValue = null; + if (td.left > sd.left && td.top > sd.top) + a = ["right", "top"]; + else if (td.left > sd.left && sd.top > td.top) + a = [ "top", "left"]; + else if (td.left < sd.left && td.top < sd.top) + a = [ "top", "right"]; + else if (td.left < sd.left && td.top > sd.top) + a = ["left", "top" ]; + + return { orientation:Orientation.DIAGONAL, a:a, theta:theta, theta2:theta2 }; + } + else if (h) return { + orientation:Orientation.HORIZONTAL, + a:sd.top < td.top ? ["bottom", "top"] : ["top", "bottom"], + theta:theta, theta2:theta2 + } + else return { + orientation:Orientation.VERTICAL, + a:sd.left < td.left ? ["right", "left"] : ["left", "right"], + theta:theta, theta2:theta2 + } + }, + placeAnchorsOnLine = function(desc, elementDimensions, elementPosition, + connections, horizontal, otherMultiplier, reverse) { + var a = [], step = elementDimensions[horizontal ? 0 : 1] / (connections.length + 1); + + for (var i = 0; i < connections.length; i++) { + var val = (i + 1) * step, other = otherMultiplier * elementDimensions[horizontal ? 1 : 0]; + if (reverse) + val = elementDimensions[horizontal ? 0 : 1] - val; + + var dx = (horizontal ? val : other), x = elementPosition[0] + dx, xp = dx / elementDimensions[0], + dy = (horizontal ? other : val), y = elementPosition[1] + dy, yp = dy / elementDimensions[1]; + + a.push([ x, y, xp, yp, connections[i][1], connections[i][2] ]); + } + + return a; + }, + standardEdgeSort = function(a, b) { return a[0] > b[0] ? 1 : -1 }, + currySort = function(reverseAngles) { + return function(a,b) { + var r = true; + if (reverseAngles) { + if (a[0][0] < b[0][0]) + r = true; + else + r = a[0][1] > b[0][1]; + } + else { + if (a[0][0] > b[0][0]) + r= true; + else + r =a[0][1] > b[0][1]; + } + return r === false ? -1 : 1; + }; + }, + leftSort = function(a,b) { + // first get adjusted values + var p1 = a[0][0] < 0 ? -Math.PI - a[0][0] : Math.PI - a[0][0], + p2 = b[0][0] < 0 ? -Math.PI - b[0][0] : Math.PI - b[0][0]; + if (p1 > p2) return 1; + else return a[0][1] > b[0][1] ? 1 : -1; + }, + edgeSortFunctions = { + "top":standardEdgeSort, + "right":currySort(true), + "bottom":currySort(true), + "left":leftSort + }, + _sortHelper = function(_array, _fn) { + return _array.sort(_fn); + }, + placeAnchors = function(elementId, _anchorLists) { + var sS = sizes[elementId], sO = offsets[elementId], + placeSomeAnchors = function(desc, elementDimensions, elementPosition, unsortedConnections, isHorizontal, otherMultiplier, orientation) { + if (unsortedConnections.length > 0) { + var sc = _sortHelper(unsortedConnections, edgeSortFunctions[desc]), // puts them in order based on the target element's pos on screen + reverse = desc === "right" || desc === "top", + anchors = placeAnchorsOnLine(desc, elementDimensions, + elementPosition, sc, + isHorizontal, otherMultiplier, reverse ); + + // takes a computed anchor position and adjusts it for parent offset and scroll, then stores it. + var _setAnchorLocation = function(endpoint, anchorPos) { + var a = adjustForParentOffsetAndScroll([anchorPos[0], anchorPos[1]], endpoint.canvas); + continuousAnchorLocations[endpoint.id] = [ a[0], a[1], anchorPos[2], anchorPos[3] ]; + continuousAnchorOrientations[endpoint.id] = orientation; + }; + + for (var i = 0; i < anchors.length; i++) { + var c = anchors[i][4], weAreSource = c.endpoints[0].elementId === elementId, weAreTarget = c.endpoints[1].elementId === elementId; + if (weAreSource) + _setAnchorLocation(c.endpoints[0], anchors[i]); + else if (weAreTarget) + _setAnchorLocation(c.endpoints[1], anchors[i]); + } + } + }; + + placeSomeAnchors("bottom", sS, [sO.left,sO.top], _anchorLists.bottom, true, 1, [0,1]); + placeSomeAnchors("top", sS, [sO.left,sO.top], _anchorLists.top, true, 0, [0,-1]); + placeSomeAnchors("left", sS, [sO.left,sO.top], _anchorLists.left, false, 0, [-1,0]); + placeSomeAnchors("right", sS, [sO.left,sO.top], _anchorLists.right, false, 1, [1,0]); + }, + AnchorManager = function() { + var _amEndpoints = {}, + connectionsByElementId = {}, + self = this, + anchorLists = {}; + + this.reset = function() { + _amEndpoints = {}; + connectionsByElementId = {}; + anchorLists = {}; + }; + this.newConnection = function(conn) { + var sourceId = conn.sourceId, targetId = conn.targetId, + ep = conn.endpoints, + doRegisterTarget = true, + registerConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + + if ((sourceId == targetId) && otherAnchor.isContinuous){ + // remove the target endpoint's canvas. we dont need it. + jsPlumb.CurrentLibrary.removeElement(ep[1].canvas); + doRegisterTarget = false; + } + _addToList(connectionsByElementId, elId, [c, otherEndpoint, otherAnchor.constructor == DynamicAnchor]); + }; + + registerConnection(0, ep[0], ep[0].anchor, targetId, conn); + if (doRegisterTarget) + registerConnection(1, ep[1], ep[1].anchor, sourceId, conn); + }; + this.connectionDetached = function(connInfo) { + var connection = connInfo.connection || connInfo; + var sourceId = connection.sourceId, + targetId = connection.targetId, + ep = connection.endpoints, + removeConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + if (otherAnchor.constructor == FloatingAnchor) { + // no-op + } + else { + _removeWithFunction(connectionsByElementId[elId], function(_c) { + return _c[0].id == c.id; + }); + } + }; + + removeConnection(1, ep[1], ep[1].anchor, sourceId, connection); + removeConnection(0, ep[0], ep[0].anchor, targetId, connection); + + // remove from anchorLists + var sEl = connection.sourceId, + tEl = connection.targetId, + sE = connection.endpoints[0].id, + tE = connection.endpoints[1].id, + _remove = function(list, eId) { + if (list) { // transient anchors dont get entries in this list. + var f = function(e) { return e[4] == eId; }; + _removeWithFunction(list["top"], f); + _removeWithFunction(list["left"], f); + _removeWithFunction(list["bottom"], f); + _removeWithFunction(list["right"], f); + } + }; + + _remove(anchorLists[sEl], sE); + _remove(anchorLists[tEl], tE); + self.redraw(sEl); + self.redraw(tEl); + }; + this.add = function(endpoint, elementId) { + _addToList(_amEndpoints, elementId, endpoint); + }; + this.changeId = function(oldId, newId) { + connectionsByElementId[newId] = connectionsByElementId[oldId]; + _amEndpoints[newId] = _amEndpoints[oldId]; + delete connectionsByElementId[oldId]; + delete _amEndpoints[oldId]; + }; + this.getConnectionsFor = function(elementId) { + return connectionsByElementId[elementId] || []; + }; + this.getEndpointsFor = function(elementId) { + return _amEndpoints[elementId] || []; + }; + this.deleteEndpoint = function(endpoint) { + _removeWithFunction(_amEndpoints[endpoint.elementId], function(e) { + return e.id == endpoint.id; + }); + }; + this.clearFor = function(elementId) { + delete _amEndpoints[elementId]; + _amEndpoints[elementId] = []; + }; + // updates the given anchor list by either updating an existing anchor's info, or adding it. this function + // also removes the anchor from its previous list, if the edge it is on has changed. + // all connections found along the way (those that are connected to one of the faces this function + // operates on) are added to the connsToPaint list, as are their endpoints. in this way we know to repaint + // them wthout having to calculate anything else about them. + var _updateAnchorList = function(lists, theta, order, conn, aBoolean, otherElId, idx, reverse, edgeId, elId, connsToPaint, endpointsToPaint) { + // first try to find the exact match, but keep track of the first index of a matching element id along the way.s + var exactIdx = -1, + firstMatchingElIdx = -1, + endpoint = conn.endpoints[idx], + endpointId = endpoint.id, + oIdx = [1,0][idx], + values = [ [ theta, order ], conn, aBoolean, otherElId, endpointId ], + listToAddTo = lists[edgeId], + listToRemoveFrom = endpoint._continuousAnchorEdge ? lists[endpoint._continuousAnchorEdge] : null; + + if (listToRemoveFrom) { + var rIdx = _findWithFunction(listToRemoveFrom, function(e) { return e[4] == endpointId }); + if (rIdx != -1) { + listToRemoveFrom.splice(rIdx, 1); + // get all connections from this list + for (var i = 0; i < listToRemoveFrom.length; i++) { + _addWithFunction(connsToPaint, listToRemoveFrom[i][1], function(c) { return c.id == listToRemoveFrom[i][1].id }); + _addWithFunction(endpointsToPaint, listToRemoveFrom[i][1].endpoints[idx], function(e) { return e.id == listToRemoveFrom[i][1].endpoints[idx].id }); + } + } + } + + for (var i = 0; i < listToAddTo.length; i++) { + if (idx == 1 && listToAddTo[i][3] === otherElId && firstMatchingElIdx == -1) + firstMatchingElIdx = i; + _addWithFunction(connsToPaint, listToAddTo[i][1], function(c) { return c.id == listToAddTo[i][1].id }); + _addWithFunction(endpointsToPaint, listToAddTo[i][1].endpoints[idx], function(e) { return e.id == listToAddTo[i][1].endpoints[idx].id }); + } + if (exactIdx != -1) { + listToAddTo[exactIdx] = values; + } + else { + var insertIdx = reverse ? firstMatchingElIdx != -1 ? firstMatchingElIdx : 0 : listToAddTo.length; // of course we will get this from having looked through the array shortly. + listToAddTo.splice(insertIdx, 0, values); + } + + // store this for next time. + endpoint._continuousAnchorEdge = edgeId; + }; + this.redraw = function(elementId, ui, timestamp, offsetToUI) { + + if (!_suspendDrawing) { + // get all the endpoints for this element + var ep = _amEndpoints[elementId] || [], + endpointConnections = connectionsByElementId[elementId] || [], + connectionsToPaint = [], + endpointsToPaint = [], + anchorsToUpdate = []; + + timestamp = timestamp || _timestamp(); + // offsetToUI are values that would have been calculated in the dragManager when registering + // an endpoint for an element that had a parent (somewhere in the hierarchy) that had been + // registered as draggable. + offsetToUI = offsetToUI || {left:0, top:0}; + if (ui) { + ui = { + left:ui.left + offsetToUI.left, + top:ui.top + offsetToUI.top + } + } + + _updateOffset( { elId : elementId, offset : ui, recalc : false, timestamp : timestamp }); + // valid for one paint cycle. + var myOffset = offsets[elementId], + myWH = sizes[elementId], + orientationCache = {}; + + // actually, first we should compute the orientation of this element to all other elements to which + // this element is connected with a continuous anchor (whether both ends of the connection have + // a continuous anchor or just one) + //for (var i = 0; i < continuousAnchorConnections.length; i++) { + for (var i = 0; i < endpointConnections.length; i++) { + var conn = endpointConnections[i][0], + sourceId = conn.sourceId, + targetId = conn.targetId, + sourceContinuous = conn.endpoints[0].anchor.isContinuous, + targetContinuous = conn.endpoints[1].anchor.isContinuous; + + if (sourceContinuous || targetContinuous) { + var oKey = sourceId + "_" + targetId, + oKey2 = targetId + "_" + sourceId, + o = orientationCache[oKey], + oIdx = conn.sourceId == elementId ? 1 : 0; + + if (sourceContinuous && !anchorLists[sourceId]) anchorLists[sourceId] = { top:[], right:[], bottom:[], left:[] }; + if (targetContinuous && !anchorLists[targetId]) anchorLists[targetId] = { top:[], right:[], bottom:[], left:[] }; + + if (elementId != targetId) _updateOffset( { elId : targetId, timestamp : timestamp }); + if (elementId != sourceId) _updateOffset( { elId : sourceId, timestamp : timestamp }); + + var td = _getCachedData(targetId), + sd = _getCachedData(sourceId); + + if (targetId == sourceId && (sourceContinuous || targetContinuous)) { + // here we may want to improve this by somehow determining the face we'd like + // to put the connector on. ideally, when drawing, the face should be calculated + // by determining which face is closest to the point at which the mouse button + // was released. for now, we're putting it on the top face. + _updateAnchorList(anchorLists[sourceId], -Math.PI / 2, 0, conn, false, targetId, 0, false, "top", sourceId, connectionsToPaint, endpointsToPaint) + } + else { + if (!o) { + o = calculateOrientation(sourceId, targetId, sd.o, td.o); + orientationCache[oKey] = o; + // this would be a performance enhancement, but the computed angles need to be clamped to + //the (-PI/2 -> PI/2) range in order for the sorting to work properly. + /* orientationCache[oKey2] = { + orientation:o.orientation, + a:[o.a[1], o.a[0]], + theta:o.theta + Math.PI, + theta2:o.theta2 + Math.PI + };*/ + } + if (sourceContinuous) _updateAnchorList(anchorLists[sourceId], o.theta, 0, conn, false, targetId, 0, false, o.a[0], sourceId, connectionsToPaint, endpointsToPaint); + if (targetContinuous) _updateAnchorList(anchorLists[targetId], o.theta2, -1, conn, true, sourceId, 1, true, o.a[1], targetId, connectionsToPaint, endpointsToPaint); + } + + if (sourceContinuous) _addWithFunction(anchorsToUpdate, sourceId, function(a) { return a === sourceId; }); + if (targetContinuous) _addWithFunction(anchorsToUpdate, targetId, function(a) { return a === targetId; }); + _addWithFunction(connectionsToPaint, conn, function(c) { return c.id == conn.id; }); + if ((sourceContinuous && oIdx == 0) || (targetContinuous && oIdx == 1)) + _addWithFunction(endpointsToPaint, conn.endpoints[oIdx], function(e) { return e.id == conn.endpoints[oIdx].id; }); + } + } + + // place Endpoints whose anchors are continuous but have no Connections + for (var i = 0; i < ep.length; i++) { + if (ep[i].connections.length == 0 && ep[i].anchor.isContinuous) { + if (!anchorLists[elementId]) anchorLists[elementId] = { top:[], right:[], bottom:[], left:[] }; + _updateAnchorList(anchorLists[elementId], -Math.PI / 2, 0, {endpoints:[ep[i], ep[i]], paint:function(){}}, false, elementId, 0, false, "top", elementId, connectionsToPaint, endpointsToPaint) + _addWithFunction(anchorsToUpdate, elementId, function(a) { return a === elementId; }) + } + } + + // now place all the continuous anchors we need to; + for (var i = 0; i < anchorsToUpdate.length; i++) { + placeAnchors(anchorsToUpdate[i], anchorLists[anchorsToUpdate[i]]); + } + + // now that continuous anchors have been placed, paint all the endpoints for this element + // TODO performance: add the endpoint ids to a temp array, and then when iterating in the next + // loop, check that we didn't just paint that endpoint. we can probably shave off a few more milliseconds this way. + for (var i = 0; i < ep.length; i++) { + ep[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + // ... and any other endpoints we came across as a result of the continuous anchors. + for (var i = 0; i < endpointsToPaint.length; i++) { + endpointsToPaint[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + + // paint all the standard and "dynamic connections", which are connections whose other anchor is + // static and therefore does need to be recomputed; we make sure that happens only one time. + + // TODO we could have compiled a list of these in the first pass through connections; might save some time. + for (var i = 0; i < endpointConnections.length; i++) { + var otherEndpoint = endpointConnections[i][1]; + if (otherEndpoint.anchor.constructor == DynamicAnchor) { + otherEndpoint.paint({ elementWithPrecedence:elementId }); + _addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; }); + // all the connections for the other endpoint now need to be repainted + for (var k = 0; k < otherEndpoint.connections.length; k++) { + if (otherEndpoint.connections[k] !== endpointConnections[i][0]) + _addWithFunction(connectionsToPaint, otherEndpoint.connections[k], function(c) { return c.id == otherEndpoint.connections[k].id; }); + } + } else if (otherEndpoint.anchor.constructor == Anchor) { + _addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; }); + } + } + // paint current floating connection for this element, if there is one. + var fc = floatingConnections[elementId]; + if (fc) + fc.paint({timestamp:timestamp, recalc:false, elId:elementId}); + + // paint all the connections + for (var i = 0; i < connectionsToPaint.length; i++) { + connectionsToPaint[i].paint({elId:elementId, timestamp:timestamp, recalc:false}); + } + } + }; + this.rehomeEndpoint = function(currentId, element) { + var eps = _amEndpoints[currentId] || [], //, + elementId = _currentInstance.getId(element); + for (var i = 0; i < eps.length; i++) { + self.add(eps[i], elementId); + } + eps.splice(0, eps.length); + }; + }; + _currentInstance.anchorManager = new AnchorManager(); + _currentInstance.continuousAnchorFactory = { + get:function(params) { + var existing = continuousAnchors[params.elementId]; + if (!existing) { + existing = { + type:"Continuous", + compute : function(params) { + return continuousAnchorLocations[params.element.id] || [0,0]; + }, + getCurrentLocation : function(endpoint) { + return continuousAnchorLocations[endpoint.id] || [0,0]; + }, + getOrientation : function(endpoint) { + return continuousAnchorOrientations[endpoint.id] || [0,0]; + }, + isDynamic : true, + isContinuous : true + }; + continuousAnchors[params.elementId] = existing; + } + return existing; + } + }; + + if (!jsPlumbAdapter.headless) + _currentInstance.dragManager = jsPlumbAdapter.getDragManager(_currentInstance); + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. You should not ever create one of these directly. If you make a call to jsPlumb.connect, all of + * the parameters that you pass in to that function will be passed to the Connection constructor; if your UI + * uses the various Endpoint-centric methods like addEndpoint/makeSource/makeTarget, along with drag and drop, + * then the parameters you set on those functions are translated and passed in to the Connection constructor. So + * you should check the documentation for each of those methods. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * container - optional id or selector instructing jsPlumb where to attach all the elements it creates for this connection. you should read the documentation for a full discussion of this. + * detachable - optional, defaults to true. Defines whether or not the connection may be detached using the mouse. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * cssClass - optional CSS class to set on the display element associated with this Connection. + * hoverClass - optional CSS class to set on the display element associated with this Connection when it is in hover state. + * overlays - Optional array of Overlay definitions to appear on this Connection. + * drawEndpoints - if false, instructs jsPlumb to not draw the endpoints for this Connection. Be careful with this: it only really works when you tell jsPlumb to attach elements to the document body. Read the documentation for a full discussion of this. + * parameters - Optional JS object containing parameters to set on the Connection. These parameters are then available via the getParameter method. + */ + var Connection = function(params) { + var self = this, visible = true, _internalHover, _superClassHover; + self.idPrefix = "_jsplumb_c_"; + self.defaultLabelLocation = 0.5; + self.defaultOverlayKeys = ["Overlays", "ConnectionOverlays"]; + this.parent = params.parent; + overlayCapableJsPlumbUIComponent.apply(this, arguments); + // ************** get the source and target and register the connection. ******************* + +// VISIBILITY + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + self[v ? "showOverlays" : "hideOverlays"](); + if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none"; + self.repaint(); + }; +// END VISIBILITY + +// TYPE + + this.getTypeDescriptor = function() { return "connection"; }; + this.getDefaultType = function() { + return { + parameters:{}, + scope:null, + detachable:self._jsPlumb.Defaults.ConnectionsDetachable, + paintStyle:self._jsPlumb.Defaults.PaintStyle || jsPlumb.Defaults.PaintStyle, + connector:self._jsPlumb.Defaults.Connector || jsPlumb.Defaults.Connector, + hoverPaintStyle:self._jsPlumb.Defaults.HoverPaintStyle || jsPlumb.Defaults.HoverPaintStyle, + overlays:self._jsPlumb.Defaults.ConnectorOverlays || jsPlumb.Defaults.ConnectorOverlays + }; + }; + var superAt = this.applyType; + this.applyType = function(t) { + superAt(t); + if (t.detachable != null) self.setDetachable(t.detachable); + if (t.scope) self.scope = t.scope; + self.setConnector(t.connector); + }; +// END TYPE + +// HOVER + // override setHover to pass it down to the underlying connector + _superClassHover = self.setHover; + self.setHover = function(state) { + var zi = _currentInstance.ConnectorZIndex || jsPlumb.Defaults.ConnectorZIndex; + if (zi) + self.connector.setZIndex(zi + (state ? 1 : 0)); + self.connector.setHover.apply(self.connector, arguments); + _superClassHover.apply(self, arguments); + }; + + _internalHover = function(state) { + if (_connectionBeingDragged == null) { + self.setHover(state, false); + } + }; +// END HOVER + + /* + * Function: setConnector + * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method - the same + * thing that you would set as the 'connector' property on a jsPlumb.connect call. + * + * Parameters: + * connector - Connector definition + */ + this.setConnector = function(connector, doNotRepaint) { + if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent); + var connectorArgs = { _jsPlumb:self._jsPlumb, parent:params.parent, + cssClass:params.cssClass, container:params.container, tooltip:self.tooltip + }; + if (_isString(connector)) + this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand. + else if (_isArray(connector)) { + if (connector.length == 1) + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](connectorArgs); + else + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs)); + } + self.canvas = self.connector.canvas; + // binds mouse listeners to the current connector. + _bindListeners(self.connector, self, _internalHover); + + // set z-index if it was set on Defaults. + var zi = _currentInstance.ConnectorZIndex || jsPlumb.Defaults.ConnectorZIndex; + if (zi) + self.connector.setZIndex(zi); + + if (!doNotRepaint) self.repaint(); + }; + +// INITIALISATION CODE + + this.source = _getElementObject(params.source); + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. but + // source is not overridden if the Endpoint has declared it is not the final target of a connection; + // instead we use the source that the Endpoint declares will be the final source element. + if (params.sourceEndpoint) this.source = params.sourceEndpoint.endpointWillMoveTo || params.sourceEndpoint.getElement(); + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + + // if a new connection is the result of moving some existing connection, params.previousConnection + // will have that Connection in it. listeners for the jsPlumbConnection event can look for that + // member and take action if they need to. + self.previousConnection = params.previousConnection; + + this.sourceId = _getAttribute(this.source, "id"); + this.targetId = _getAttribute(this.target, "id"); + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams, elementId) { + return (anchorParams) ? _currentInstance.makeAnchor(anchorParams, elementId, _currentInstance) : null; + }, + prepareEndpoint = function(existing, index, params, element, elementId, connectorPaintStyle, connectorHoverPaintStyle) { + var e; + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] + || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint; + + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified. + if (es.fillStyle == null && connectorPaintStyle != null) + es.fillStyle = connectorPaintStyle.strokeStyle; + + // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does: + //* + if (es.outlineColor == null && connectorPaintStyle != null) + es.outlineColor = connectorPaintStyle.outlineColor; + if (es.outlineWidth == null && connectorPaintStyle != null) + es.outlineWidth = connectorPaintStyle.outlineWidth; + //*/ + + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure? + if (connectorHoverPaintStyle != null) { + if (ehs == null) ehs = {}; + if (ehs.fillStyle == null) { + ehs.fillStyle = connectorHoverPaintStyle.strokeStyle; + } + } + var a = params.anchors ? params.anchors[index] : + params.anchor ? params.anchor : + _makeAnchor(_currentInstance.Defaults.Anchors[index], elementId) || + _makeAnchor(jsPlumb.Defaults.Anchors[index], elementId) || + _makeAnchor(_currentInstance.Defaults.Anchor, elementId) || + _makeAnchor(jsPlumb.Defaults.Anchor, elementId), + u = params.uuids ? params.uuids[index] : null; + e = _newEndpoint({ + paintStyle : es, hoverPaintStyle:ehs, endpoint : ep, connections : [ self ], + uuid : u, anchor : a, source : element, scope : params.scope, container:params.container, + reattach:params.reattach, detachable:params.detachable + }); + self.endpoints[index] = e; + + if (params.drawEndpoints === false) e.setVisible(false, true, true); + + } + return e; + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source, + self.sourceId, params.paintStyle, params.hoverPaintStyle); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target, + self.targetId, params.paintStyle, params.hoverPaintStyle); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + // if delete endpoints on detach, keep a record of just exactly which endpoints they are. + if (params.deleteEndpointsOnDetach) + self.endpointsToDeleteOnDetach = [eS, eT]; + + self.setConnector(this.endpoints[0].connector || + this.endpoints[1].connector || + params.connector || + _currentInstance.Defaults.Connector || + jsPlumb.Defaults.Connector, true); + + this.setPaintStyle(this.endpoints[0].connectorStyle || + this.endpoints[1].connectorStyle || + params.paintStyle || + _currentInstance.Defaults.PaintStyle || + jsPlumb.Defaults.PaintStyle, true); + + this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || + this.endpoints[1].connectorHoverStyle || + params.hoverPaintStyle || + _currentInstance.Defaults.HoverPaintStyle || + jsPlumb.Defaults.HoverPaintStyle, true); + + this.paintStyleInUse = this.getPaintStyle(); + + _updateOffset( { elId : this.sourceId, timestamp:_suspendedAt }); + _updateOffset( { elId : this.targetId, timestamp:_suspendedAt }); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId], + otherOffset = offsets[this.targetId], + otherWH = sizes[this.targetId], + initialTimestamp = _suspendedAt || _timestamp(), + anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + elementId:this.endpoints[0].elementId, + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1], + timestamp:initialTimestamp + }); + + this.endpoints[0].paint( { anchorLoc : anchorLoc, timestamp:initialTimestamp }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + elementId:this.endpoints[1].elementId, + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0], + timestamp:initialTimestamp + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc, timestamp:initialTimestamp }); + +// END INITIALISATION CODE + +// DETACHABLE + var _detachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.detachable === false) _detachable = false; + if(self.endpoints[0].connectionsDetachable === false) _detachable = false; + if(self.endpoints[1].connectionsDetachable === false) _detachable = false; + /* + Function: isDetachable + Returns whether or not this connection can be detached from its target/source endpoint. by default this + is false; use it in conjunction with the 'reattach' parameter. + */ + this.isDetachable = function() { + return _detachable === true; + }; + /* + Function: setDetachable + Sets whether or not this connection is detachable. + */ + this.setDetachable = function(detachable) { + _detachable = detachable === true; + }; +// END DETACHABLE + +// COST + DIRECTIONALITY + // if cost not supplied, try to inherit from source endpoint + var _cost = params.cost || self.endpoints[0].getConnectionCost(); + self.getCost = function() { return _cost; }; + self.setCost = function(c) { _cost = c; }; + var _bidirectional = !(params.bidirectional === false); + // inherit bidirectional flag if set no source endpoint + if (params.bidirectional == null) _bidirectional = self.endpoints[0].areConnectionsBidirectional(); + self.isBidirectional = function() { return _bidirectional; }; +// END COST + DIRECTIONALITY + +// PARAMETERS + // merge all the parameters objects into the connection. parameters set + // on the connection take precedence; then target endpoint params, then + // finally source endpoint params. + // TODO jsPlumb.extend could be made to take more than two args, and it would + // apply the second through nth args in order. + var _p = jsPlumb.extend({}, this.endpoints[0].getParameters()); + jsPlumb.extend(_p, this.endpoints[1].getParameters()); + jsPlumb.extend(_p, self.getParameters()); + self.setParameters(_p); +// END PARAMETERS + +// MISCELLANEOUS + /** + * implementation of abstract method in jsPlumbUtil.EventGenerator + * @return list of attached elements. in our case, a list of Endpoints. + */ + this.getAttachedElements = function() { + return self.endpoints; + }; + + /** + * changes the parent element of this connection to newParent. not exposed for the public API. + */ + this.moveParent = function(newParent) { + var jpcl = jsPlumb.CurrentLibrary, curParent = jpcl.getParent(self.connector.canvas); + if (self.connector.bgCanvas) { + jpcl.removeElement(self.connector.bgCanvas, curParent); + jpcl.appendElement(self.connector.bgCanvas, newParent); + } + jpcl.removeElement(self.connector.canvas, curParent); + jpcl.appendElement(self.connector.canvas, newParent); + // this only applies for DOMOverlays + for (var i = 0; i < self.overlays.length; i++) { + if (self.overlays[i].isAppendedAtTopLevel) { + jpcl.removeElement(self.overlays[i].canvas, curParent); + jpcl.appendElement(self.overlays[i].canvas, newParent); + if (self.overlays[i].reattachListeners) + self.overlays[i].reattachListeners(self.connector); + } + } + if (self.connector.reattachListeners) // this is for SVG/VML; change an element's parent and you have to reinit its listeners. + self.connector.reattachListeners(); // the Canvas implementation doesn't have to care about this + }; + +// END MISCELLANEOUS + +// PAINTING + + /* + * Paints the Connection. Not exposed for public usage. + * + * Parameters: + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + var lastPaintedAt = null; + this.paint = function(params) { + + if (visible) { + + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp, + // if the moving object is not the source we must transpose the two references. + swap = false, + tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId, + tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + + if (timestamp == null || timestamp != lastPaintedAt) { + var sourceInfo = _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }), + targetInfo = _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var sE = this.endpoints[sIdx], tE = this.endpoints[tIdx], + sAnchorP = sE.anchor.getCurrentLocation(sE), + tAnchorP = tE.anchor.getCurrentLocation(tE); + + /* paint overlays*/ + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible()) maxSize = Math.max(maxSize, o.computeMaxSize()); + } + + var dim = this.connector.compute( + sAnchorP, + tAnchorP, + this.endpoints[sIdx], + this.endpoints[tIdx], + this.endpoints[sIdx].anchor, + this.endpoints[tIdx].anchor, + self.paintStyleInUse.lineWidth, + maxSize, + sourceInfo, + targetInfo ); + + self.connector.paint(dim, self.paintStyleInUse); + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim, timestamp); + } + } + lastPaintedAt = timestamp; + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function(params) { + params = params || {}; + var recalc = !(params.recalc === false); + this.paint({ elId : this.sourceId, recalc : recalc, timestamp:params.timestamp }); + }; + + // the very last thing we do is check to see if a 'type' was supplied in the params + var _type = params.type || self.endpoints[0].connectionType || self.endpoints[1].connectionType; + if (_type) + self.setType(_type); + +// END PAINTING + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Connection. + * + * Parameters: + * event - the event to bind. Available events on a Connection are: + * - *click* : notification that a Connection was clicked. + * - *dblclick* : notification that a Connection was double clicked. + * - *mouseenter* : notification that the mouse is over a Connection. + * - *mouseexit* : notification that the mouse exited a Connection. + * - *contextmenu* : notification that the user right-clicked on the Connection. + * + * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Connection's paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: getPaintStyle + * Gets the Connection's paint style. This is not necessarily the paint style in use at the time; + * this is the paint style for the connection when the mouse it not hovering over it. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Connection will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Connection. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + + /* + * Function: getParameter + * Gets the named parameter; returns null if no parameter with that name is set. Parameter values may have been supplied to a 'connect' or 'addEndpoint' call (connections made with the mouse get a copy of all parameters set on each of their Endpoints), or the parameter value may have been set with setParameter. + * + * Parameters: + * key - Parameter name. + */ + + /* + * Function: setParameter + * Sets the named parameter to the given value. + * + * Parameters: + * key - Parameter name. + * value - Parameter value. + */ + + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc) + */ + + /* + * Property: sourceId + * Id of the source element in the connection. + */ + /* + * Property: targetId + * Id of the target element in the connection. + */ + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + /* + * Property: source + * The source element for this Connection. + */ + /* + * Property: target + * The target element for this Connection. + */ + /* + * Property: overlays + * List of Overlays for this component. + */ + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + /* + * Function: getOverlay + * Gets an overlay, by ID. Note: by ID. You would pass an 'id' parameter + * in to the Overlay's constructor arguments, and then use that to retrieve + * it via this method. + */ + /* + * Function: getOverlays + * Gets all the overlays for this component. + */ + /* + * Function: hideOverlay + * Hides the overlay specified by the given id. + */ + /* + * Function: hideOverlays + * Hides all Overlays + */ + /* + * Function: showOverlay + * Shows the overlay specified by the given id. + */ + /* + * Function: showOverlays + * Shows all Overlays + */ + /** + * Function: removeAllOverlays + * Removes all overlays from the Connection, and then repaints. + */ + /** + * Function: removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + /** + * Function: removeOverlays + * Removes a set of overlays by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String, a Function that returns a String, or a params object containing { "label", "labelStyle", "location", "cssClass" }. Note that this uses innerHTML on the label div, so keep + * that in mind if you need escaped HTML. + */ + /* + Function: getLabel + Returns the label text for this Connection (or a function if you are labelling with a function). + This does not return the overlay itself; this is a convenience method which is a pair with + setLabel; together they allow you to add and access a Label Overlay without having to create the + Overlay object itself. For access to the underlying label overlay that jsPlumb has created, + use getLabelOverlay. + */ + /* + Function: getLabelOverlay + Returns the underlying internal label overlay, which will exist if you specified a label on + a connect call, or have called setLabel at any stage. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + + }; // END Connection class + +// ENDPOINT HELPER FUNCTIONS + var _makeConnectionDragHandler = function(placeholder) { + var stopped = false; + return { + drag : function() { + if (stopped) { + stopped = false; + return true; + } + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments), + el = placeholder.element; + if (el) { + jsPlumb.CurrentLibrary.setOffset(el, _ui); + _draw(_getElementObject(el), _ui); + } + }, + stopDrag : function() { + stopped = true; + } + }; + }; + + var _makeFloatingEndpoint = function(paintStyle, referenceAnchor, endpoint, referenceCanvas, sourceElement) { + var floatingAnchor = new FloatingAnchor( { reference : referenceAnchor, referenceCanvas : referenceCanvas }); + //setting the scope here should not be the way to fix that mootools issue. it should be fixed by not + // adding the floating endpoint as a droppable. that makes more sense anyway! + return _newEndpoint({ paintStyle : paintStyle, endpoint : endpoint, anchor : floatingAnchor, source : sourceElement, scope:"__floating" }); + }; + + /** + * creates a placeholder div for dragging purposes, adds it to the DOM, and pre-computes its offset. + */ + var _makeDraggablePlaceholder = function(placeholder, parent) { + var n = document.createElement("div"); + n.style.position = "absolute"; + var placeholderDragElement = _getElementObject(n); + _appendElement(n, parent); + var id = _getId(placeholderDragElement); + _updateOffset( { elId : id }); + // create and assign an id, and initialize the offset. + placeholder.id = id; + placeholder.element = placeholderDragElement; + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1 + * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't + * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you + * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or + * 'target' Endpoints for Connections. + * + * + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * enabled - optional, defaults to true. Indicates whether or not the Endpoint should be enabled for mouse events (drag/drop). + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * cssClass - optional CSS class to set on the display element associated with this Endpoint. + * hoverClass - optional CSS class to set on the display element associated with this Endpoint when it is in hover state. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * container - optional id or selector instructing jsPlumb where to attach the element it creates for this endpoint. you should read the documentation for a full discussion of this. + * connections - optional list of Connections to configure the Endpoint with. + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * connectorClass - optional CSS class to set on Connections emanating from this Endpoint. + * connectorHoverClass - optional CSS class to set on to set on Connections emanating from this Endpoint when they are in hover state. + * connectionsDetachable - optional, defaults to true. Sets whether connections to/from this Endpoint should be detachable or not. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + * parameters - Optional JS object containing parameters to set on the Endpoint. These parameters are then available via the getParameter method. When a connection is made involving this Endpoint, the parameters from this Endpoint are copied into that Connection. Source Endpoint parameters override target Endpoint parameters if they both have a parameter with the same name. + */ + var Endpoint = function(params) { + var self = this; + self.idPrefix = "_jsplumb_e_"; + self.defaultLabelLocation = [ 0.5, 0.5 ]; + self.defaultOverlayKeys = ["Overlays", "EndpointOverlays"]; + this.parent = params.parent; + overlayCapableJsPlumbUIComponent.apply(this, arguments); + params = params || {}; + +// TYPE + + this.getTypeDescriptor = function() { return "endpoint"; }; + this.getDefaultType = function() { + return { + parameters:{}, + scope:null, + maxConnections:self._jsPlumb.Defaults.MaxConnections, + paintStyle:self._jsPlumb.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle, + endpoint:self._jsPlumb.Defaults.Endpoint || jsPlumb.Defaults.Endpoint, + hoverPaintStyle:self._jsPlumb.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle, + overlays:self._jsPlumb.Defaults.EndpointOverlays || jsPlumb.Defaults.EndpointOverlays, + connectorStyle:params.connectorStyle, + connectorHoverStyle:params.connectorHoverStyle, + connectorClass:params.connectorClass, + connectorHoverClass:params.connectorHoverClass, + connectorOverlays:params.connectorOverlays, + connector:params.connector, + connectorTooltip:params.connectorTooltip + }; + }; + var superAt = this.applyType; + this.applyType = function(t) { + superAt(t); + if (t.maxConnections != null) _maxConnections = t.maxConnections; + if (t.scope) self.scope = t.scope; + self.connectorStyle = t.connectorStyle; + self.connectorHoverStyle = t.connectorHoverStyle; + self.connectorOverlays = t.connectorOverlays; + self.connector = t.connector; + self.connectorTooltip = t.connectorTooltip; + self.connectionType = t.connectionType; + self.connectorClass = t.connectorClass; + self.connectorHoverClass = t.connectorHoverClass; + }; +// END TYPE + + var visible = true, __enabled = !(params.enabled === false); + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + self[v ? "showOverlays" : "hideOverlays"](); + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + + /* + Function: isEnabled + Returns whether or not the Endpoint is enabled for drag/drop connections. + */ + this.isEnabled = function() { return __enabled; }; + + /* + Function: setEnabled + Sets whether or not the Endpoint is enabled for drag/drop connections. + */ + this.setEnabled = function(e) { __enabled = e; }; + + var _element = params.source, _uuid = params.uuid, floatingEndpoint = null, inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + this.element = _element; + + var _connectionCost = params.connectionCost; + this.getConnectionCost = function() { return _connectionCost; }; + this.setConnectionCost = function(c) { + _connectionCost = c; + }; + + var _connectionsBidirectional = params.connectionsBidirectional === false ? false : true; + this.areConnectionsBidirectional = function() { return _connectionsBidirectional; }; + this.setConnectionsBidirectional = function(b) { _connectionsBidirectional = b; }; + + self.anchor = params.anchor ? _currentInstance.makeAnchor(params.anchor, _elementId, _currentInstance) : params.anchors ? _currentInstance.makeAnchor(params.anchors, _elementId, _currentInstance) : _currentInstance.makeAnchor(_currentInstance.Defaults.Anchor || "TopCenter", _elementId, _currentInstance); + + // ANCHOR MANAGER + if (!params._transient) // in place copies, for example, are transient. they will never need to be retrieved during a paint cycle, because they dont move, and then they are deleted. + _currentInstance.anchorManager.add(self, _elementId); + + var _endpoint = null, originalEndpoint = null; + this.setEndpoint = function(ep) { + var endpointArgs = { + _jsPlumb:self._jsPlumb, + parent:params.parent, + container:params.container, + tooltip:params.tooltip, + connectorTooltip:params.connectorTooltip, + endpoint:self + }; + if (_isString(ep)) + _endpoint = new jsPlumb.Endpoints[renderMode][ep](endpointArgs); + else if (_isArray(ep)) { + endpointArgs = jsPlumb.extend(ep[1], endpointArgs); + _endpoint = new jsPlumb.Endpoints[renderMode][ep[0]](endpointArgs); + } + else { + _endpoint = ep.clone(); + } + + // assign a clone function using a copy of endpointArgs. this is used when a drag starts: the endpoint that was dragged is cloned, + // and the clone is left in its place while the original one goes off on a magical journey. + // the copy is to get around a closure problem, in which endpointArgs ends up getting shared by + // the whole world. + var argsForClone = jsPlumb.extend({}, endpointArgs); + _endpoint.clone = function() { + var o = new Object(); + _endpoint.constructor.apply(o, [argsForClone]); + return o; + }; + + self.endpoint = _endpoint; + self.type = self.endpoint.type; + }; + + this.setEndpoint(params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot"); + originalEndpoint = _endpoint; + + // override setHover to pass it down to the underlying endpoint + var _sh = self.setHover; + self.setHover = function() { + self.endpoint.setHover.apply(self.endpoint, arguments); + _sh.apply(self, arguments); + }; + // endpoint delegates to first connection for hover, if there is one. + var internalHover = function(state) { + if (self.connections.length > 0) + self.connections[0].setHover(state, false); + else + self.setHover(state); + }; + + // bind listeners from endpoint to self, with the internal hover function defined above. + _bindListeners(self.endpoint, self, internalHover); + + this.setPaintStyle(params.paintStyle || + params.style || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyle, true); + this.setHoverPaintStyle(params.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyle, true); + this.paintStyleInUse = this.getPaintStyle(); + var originalPaintStyle = this.getPaintStyle(); + this.connectorStyle = params.connectorStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.connectorTooltip = params.connectorTooltip; + this.connectorClass = params.connectorClass; + this.connectorHoverClass = params.connectorHoverClass; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + + var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + this.canvas = this.endpoint.canvas; + this.connections = params.connections || []; + + this.scope = params.scope || DEFAULT_SCOPE; + this.connectionType = params.connectionType; + this.timestamp = null; + self.isReattach = params.reattach || false; + self.connectionsDetachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.connectionsDetachable === false || params.detachable === false) + self.connectionsDetachable = false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + if (params.onMaxConnections) + self.bind("maxConnections", params.onMaxConnections); + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget, forceDetach, fireEvent, originalEvent) { + var idx = _findWithFunction(self.connections, function(c) { return c.id == connection.id}), + actuallyDetached = false; + fireEvent = (fireEvent !== false); + if (idx >= 0) { + // 1. does the connection have a before detach (note this also checks jsPlumb's bound + // detach handlers; but then Endpoint's check will, too, hmm.) + if (forceDetach || connection._forceDetach || connection.isDetachable() || connection.isDetachAllowed(connection)) { + // get the target endpoint + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + // it would be nice to check with both endpoints that it is ok to detach. but + // for this we'll have to get a bit fancier: right now if you use the same beforeDetach + // interceptor for two endpoints (which is kind of common, because it's part of the + // endpoint definition), then it gets fired twice. so in fact we need to loop through + // each beforeDetach and see if it returns false, at which point we exit. but if it + // returns true, we have to check the next one. however we need to track which ones + // have already been run, and not run them again. + if (forceDetach || connection._forceDetach || (self.isDetachAllowed(connection) /*&& t.isDetachAllowed(connection)*/)) { + + self.connections.splice(idx, 1); + + // this avoids a circular loop + if (!ignoreTarget) { + + t.detach(connection, true, forceDetach); + // check connection to see if we want to delete the endpoints associated with it. + // we only detach those that have just this connection; this scenario is most + // likely if we got to this bit of code because it is set by the methods that + // create their own endpoints, like .connect or .makeTarget. the user is + // not likely to have interacted with those endpoints. + if (connection.endpointsToDeleteOnDetach){ + for (var i = 0; i < connection.endpointsToDeleteOnDetach.length; i++) { + var cde = connection.endpointsToDeleteOnDetach[i]; + if (cde && cde.connections.length == 0) + _currentInstance.deleteEndpoint(cde); + } + } + } + _removeElements(connection.connector.getDisplayElements(), connection.parent); + _removeWithFunction(connectionsByScope[connection.scope], function(c) { + return c.id == connection.id; + }); + actuallyDetached = true; + var doFireEvent = (!ignoreTarget && fireEvent) + fireDetachEvent(connection, doFireEvent, originalEvent); + } + } + } + return actuallyDetached; + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + * + * Parameters: + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachAll = function(fireEvent, originalEvent) { + while (self.connections.length > 0) { + self.detach(self.connections[0], false, true, fireEvent, originalEvent); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachFrom = function(targetEndpoint, fireEvent, originalEvent) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + if (self.detach(c[i], false, true, fireEvent, originalEvent)) + c[i].setHover(false, false); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findWithFunction(self.connections, function(c) { return c.id == connection.id}); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: setElement + * Sets the DOM element this Endpoint is attached to. + */ + this.setElement = function(el, container) { + + // TODO possibly have this object take charge of moving the UI components into the appropriate + // parent. this is used only by makeSource right now, and that function takes care of + // moving the UI bits and pieces. however it would s + var parentId = _getId(el); + // remove the endpoint from the list for the current endpoint's element + _removeWithFunction(endpointsByElement[self.elementId], function(e) { + return e.id == self.id; + }); + _element = _getElementObject(el); + _elementId = _getId(_element); + self.elementId = _elementId; + // need to get the new parent now + var newParentElement = _getParentFromParams({source:parentId, container:container}), + curParent = jpcl.getParent(self.canvas); + jpcl.removeElement(self.canvas, curParent); + jpcl.appendElement(self.canvas, newParentElement); + + // now move connection(s)...i would expect there to be only one but we will iterate. + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].moveParent(newParentElement); + self.connections[i].sourceId = _elementId; + self.connections[i].source = _element; + } + _addToList(endpointsByElement, parentId, self); + //_currentInstance.repaint(parentId); + + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + var loc = self.anchor.getCurrentLocation(self), + o = self.anchor.getOrientation(self), + inPlaceAnchor = { + compute:function() { return [ loc[0], loc[1] ]}, + getCurrentLocation : function() { return [ loc[0], loc[1] ]}, + getOrientation:function() { return o; } + }; + + return _newEndpoint( { + anchor : inPlaceAnchor, + source : _element, + paintStyle : this.getPaintStyle(), + endpoint : _endpoint, + _transient:true, + scope:self.scope + }); + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + var candidate = self.connections[0]; + if (self.isTarget && candidate) return candidate; + else { + return (self.connections.length < _maxConnections) || _maxConnections == -1 ? null : candidate; + } + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + /* + * Function: setStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to + * setStyle and deprecate it if so. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + * + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + // a helper function that tries to find a connection to the given element, and returns it if so. if elementWithPrecedence is null, + // or no connection to it is found, we return the first connection in our list. + var findConnectionToUseForDynamicAnchor = function(elementWithPrecedence) { + var idx = 0; + if (elementWithPrecedence != null) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].sourceId == elementWithPrecedence || self.connections[i].targetId == elementWithPrecedence) { + idx = i; + break; + } + } + } + + return self.connections[idx]; + }; + + /* + * Function: paint + * Paints the Endpoint, recalculating offset and anchor positions if necessary. This does NOT paint + * any of the Endpoint's connections. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + params = params || {}; + var timestamp = params.timestamp, recalc = !(params.recalc === false); + //recalc = params.recalc; + if (!timestamp || self.timestamp !== timestamp) { + _updateOffset({ elId:_elementId, timestamp:timestamp, recalc:recalc }); + var xy = params.offset || offsets[_elementId]; + if(xy) { + var ap = params.anchorPoint,connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (recalc && self.anchor.isDynamic && self.connections.length > 0) { + var c = findConnectionToUseForDynamicAnchor(params.elementWithPrecedence), + oIdx = c.endpoints[0] == self ? 1 : 0, + oId = oIdx == 0 ? c.sourceId : c.targetId, + oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + ap = self.anchor.compute(anchorParams); + } + + // switched _endpoint to self here; continuous anchors, for instance, look for the + // endpoint's id, but here "_endpoint" is the renderer, not the actual endpoint. + // TODO need to verify this does not break anything. + //var d = _endpoint.compute(ap, self.anchor.getOrientation(_endpoint), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + var d = _endpoint.compute(ap, self.anchor.getOrientation(self), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + _endpoint.paint(d, self.paintStyleInUse, self.anchor); + self.timestamp = timestamp; + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) self.overlayPlacements[i] = o.draw(self.endpoint, self.paintStyleInUse, d); + } + } + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var placeholderInfo = { id:null, element:null }, + jpc = null, + existingJpc = false, + existingJpcParams = null, + _dragHandler = _makeConnectionDragHandler(placeholderInfo); + + var start = function() { + // drag might have started on an endpoint that is not actually a source, but which has + // one or more connections. + jpc = self.connectorSelector(); + var _continue = true; + // if not enabled, return + if (!self.isEnabled()) _continue = false; + // if no connection and we're not a source, return. + if (jpc == null && !params.isSource) _continue = false; + // otherwise if we're full and not allowed to drag, also return false. + if (params.isSource && self.isFull() && !dragAllowedWhenFull) _continue = false; + // if the connection was setup as not detachable or one of its endpoints + // was setup as connectionsDetachable = false, or Defaults.ConnectionsDetachable + // is set to false... + if (jpc != null && !jpc.isDetachable()) _continue = false; + + if (_continue === false) { + // this is for mootools and yui. returning false from this causes jquery to stop drag. + // the events are wrapped in both mootools and yui anyway, but i don't think returning + // false from the start callback would stop a drag. + if (jsPlumb.CurrentLibrary.stopDrag) jsPlumb.CurrentLibrary.stopDrag(); + _dragHandler.stopDrag(); + return false; + } + + // if we're not full but there was a connection, make it null. we'll create a new one. + if (jpc && !self.isFull() && params.isSource) jpc = null; + + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + _makeDraggablePlaceholder(placeholderInfo, self.parent); + + // set the offset of this div to be where 'inPlaceCopy' is, to start with. + // TODO merge this code with the code in both Anchor and FloatingAnchor, because it + // does the same stuff. + var ipcoel = _getElementObject(inPlaceCopy.canvas), + ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel), + po = adjustForParentOffsetAndScroll([ipco.left, ipco.top], inPlaceCopy.canvas); + jsPlumb.CurrentLibrary.setOffset(placeholderInfo.element, {left:po[0], top:po[1]}); + + // when using makeSource and a parent, we first draw the source anchor on the source element, then + // move it to the parent. note that this happens after drawing the placeholder for the + // first time. + if (self.parentAnchor) self.anchor = _currentInstance.makeAnchor(self.parentAnchor, self.elementId, _currentInstance); + + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", placeholderInfo.id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + + // create a floating endpoint. + // here we test to see if a dragProxy was specified in this endpoint's constructor params, and + // if so, we create that endpoint instead of cloning ourselves. + if (params.proxy) { + /* var floatingAnchor = new FloatingAnchor( { reference : self.anchor, referenceCanvas : self.canvas }); + + floatingEndpoint = _newEndpoint({ + paintStyle : params.proxy.paintStyle, + endpoint : params.proxy.endpoint, + anchor : floatingAnchor, + source : placeholderInfo.element, + scope:"__floating" + }); + + //$(self.canvas).hide(); + */ + self.setPaintStyle(params.proxy.paintStyle); + // if we do this, we have to cleanup the old one. like just remove its display parts + //self.setEndpoint(params.proxy.endpoint); + + } + // else { + floatingEndpoint = _makeFloatingEndpoint(self.getPaintStyle(), self.anchor, _endpoint, self.canvas, placeholderInfo.element); + // } + + if (jpc == null) { + self.anchor.locked = true; + self.setHover(false, false); + // TODO the hover call above does not reset any target endpoint's hover + // states. + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : self.endpointWillMoveTo || _getElementObject(_element), // for makeSource with parent option. ensure source element is represented correctly. + target : placeholderInfo.element, + anchors : [ self.anchor, floatingEndpoint.anchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays, + type:self.connectionType, + cssClass:self.connectorClass, + hoverClass:self.connectorHoverClass + }); + + } else { + existingJpc = true; + jpc.connector.setHover(false, false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas), false, true); + // new anchor idx + var anchorIdx = jpc.endpoints[0].id == self.id ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas), + dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // now we want to get this endpoint's DROP scope, and set it for now: we can only be dropped on drop zones + // that have our drop scope (issue 57). + var dropScope = jsPlumb.CurrentLibrary.getDropScope(c); + jsPlumb.CurrentLibrary.setDragScope(c, dropScope); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = placeholderInfo.element; + jpc.sourceId = placeholderInfo.id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = placeholderInfo.element; + jpc.targetId = placeholderInfo.id; + } + + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.suspendedEndpoint.setHover(false); + jpc.endpoints[anchorIdx] = floatingEndpoint; + + // fire an event that informs that a connection is being dragged + fireConnectionDraggingEvent(jpc); + + } + // register it and register connection on it. + floatingConnections[placeholderInfo.id] = jpc; + floatingEndpoint.addConnection(jpc); + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, placeholderInfo.id, floatingEndpoint); + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var jpcl = jsPlumb.CurrentLibrary, + dragOptions = params.dragOptions || {}, + defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions), + startEvent = jpcl.dragEvents["start"], + stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"]; + + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + // extracted drag handler function so can be used by makeSource + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], _dragHandler.drag); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + var originalEvent = jpcl.getDropEvent(arguments); + _currentInstance.currentlyDragging = false; + _removeWithFunction(endpointsByElement[placeholderInfo.id], function(e) { + return e.id == floatingEndpoint.id; + }); + _removeElements( [ placeholderInfo.element[0], floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + _currentInstance.anchorManager.clearFor(placeholderInfo.id); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + self.setPaintStyle(originalPaintStyle); // reset to original; may have been changed by drag proxy. + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (self.isReattach || jpc._forceDetach || !jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc, false, false, true, originalEvent)) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + _currentInstance.repaint(existingJpcParams[1]); + } + jpc._forceDetach = null; + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElements(jpc.connector.getDisplayElements(), self.parent); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint({recalc:false}); + jpc.setHover(false, false); + + fireConnectionDragStopEvent(jpc); + + jpc = null; + inPlaceCopy = null; + delete endpointsByElement[floatingEndpoint.elementId]; + floatingEndpoint.anchor = null; + floatingEndpoint = null; + _currentInstance.currentlyDragging = false; + + + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions, true); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas, forceInit, isTransient, endpoint) { + if ((params.isTarget || forceInit) && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop'], + overEvent = jsPlumb.CurrentLibrary.dragEvents['over'], + outEvent = jsPlumb.CurrentLibrary.dragEvents['out'], + drop = function() { + + var originalEvent = jsPlumb.CurrentLibrary.getDropEvent(arguments), + draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + elId = _getAttribute(draggable, "elId"), + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id]; + + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + + // restore the original scope if necessary (issue 57) + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + var endpointEnabled = endpoint != null ? endpoint.isEnabled() : true; + + if (self.isFull()) { + self.fire("maxConnections", { + endpoint:self, + connection:jpc, + maxConnections:_maxConnections + }, originalEvent); + } + + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget) && endpointEnabled) { + + var _doContinue = true; + + // the second check here is for the case that the user is dropping it back + // where it came from. + if (jpc.suspendedEndpoint && jpc.suspendedEndpoint.id != self.id) { + if (idx == 0) { + jpc.source = jpc.suspendedEndpoint.element; + jpc.sourceId = jpc.suspendedEndpoint.elementId; + } else { + jpc.target = jpc.suspendedEndpoint.element; + jpc.targetId = jpc.suspendedEndpoint.elementId; + } + + if (!jpc.isDetachAllowed(jpc) || !jpc.endpoints[idx].isDetachAllowed(jpc) || !jpc.suspendedEndpoint.isDetachAllowed(jpc) || !_currentInstance.checkCondition("beforeDetach", jpc)) + _doContinue = false; + } + + // these have to be set before testing for beforeDrop. + if (idx == 0) { + jpc.source = self.element; + jpc.sourceId = self.elementId; + } else { + jpc.target = self.element; + jpc.targetId = self.elementId; + } + + // now check beforeDrop. this will be available only on Endpoints that are setup to + // have a beforeDrop condition (although, secretly, under the hood all Endpoints and + // the Connection have them, because they are on jsPlumbUIComponent. shhh!), because + // it only makes sense to have it on a target endpoint. + _doContinue = _doContinue && self.isDropAllowed(jpc.sourceId, jpc.targetId, jpc.scope, jpc, self); + + if (_doContinue) { + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + + // copy our parameters in to the connection: + var params = self.getParameters(); + for (var aParam in params) + jpc.setParameter(aParam, params[aParam]); + + if (!jpc.suspendedEndpoint) { + //_initDraggableIfNecessary(self.element, params.draggable, {}); + if (params.draggable) + jsPlumb.CurrentLibrary.initDraggable(self.element, dragOptions, true); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + fireDetachEvent({ + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }, true, originalEvent); + } + + // finalise will inform the anchor manager and also add to + // connectionsByScope if necessary. + _finaliseConnection(jpc, null, originalEvent); + } + else { + // otherwise just put it back on the endpoint it was on before the drag. + if (jpc.suspendedEndpoint) { + // self.detachFrom(jpc); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.setHover(false); + jpc._forceDetach = true; + if (idx == 0) { + jpc.source = jpc.suspendedEndpoint.element; + jpc.sourceId = jpc.suspendedEndpoint.elementId; + } else { + jpc.target = jpc.suspendedEndpoint.element; + jpc.targetId = jpc.suspendedEndpoint.elementId;; + } + jpc.suspendedEndpoint.addConnection(jpc); + + jpc.endpoints[0].repaint(); + jpc.repaint(); + _currentInstance.repaint(jpc.source.elementId); + jpc._forceDetach = false; + } + } + + jpc.floatingAnchorIndex = null; + } + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + } + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], function() { + if (self.isTarget) { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + } + } + }); + dropOptions[outEvent] = _wrap(dropOptions[outEvent], function() { + if (self.isTarget) { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + } + } + }); + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions, true, isTransient); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas), true, !(params._transient || self.anchor.isFloating), self); + + // finally, set type if it was provided + if (params.type) + self.setType(params.type); + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Endpoint. + * + * Parameters: + * event - the event to bind. Available events on an Endpoint are: + * - *click* : notification that a Endpoint was clicked. + * - *dblclick* : notification that a Endpoint was double clicked. + * - *mouseenter* : notification that the mouse is over a Endpoint. + * - *mouseexit* : notification that the mouse exited a Endpoint. + * - *contextmenu* : notification that the user right-clicked on the Endpoint. + * + * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Endpoint's paint style and then repaints the Endpoint. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: getPaintStyle + * Gets the Endpoint's paint style. This is not necessarily the paint style in use at the time; + * this is the paint style for the Endpoint when the mouse it not hovering over it. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Endpoint will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Endpoint. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + + /* + * Function: getParameter + * Gets the named parameter; returns null if no parameter with that name is set. Parameters may have been set on the Endpoint in the 'addEndpoint' call, or they may have been set with the setParameter function. + * + * Parameters: + * key - Parameter name. + */ + + /* + * Function: setParameter + * Sets the named parameter to the given value. + * + * Parameters: + * key - Parameter name. + * value - Parameter value. + */ + + /* + * Property: canvas + * The Endpoint's drawing area. + */ + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + + /* + * Property: overlays + * List of Overlays for this Endpoint. + */ + /* + * Function: addOverlay + * Adds an Overlay to the Endpoint. + * + * Parameters: + * overlay - Overlay to add. + */ + /* + * Function: getOverlay + * Gets an overlay, by ID. Note: by ID. You would pass an 'id' parameter + * in to the Overlay's constructor arguments, and then use that to retrieve + * it via this method. + */ + /* + * Function: getOverlays + * Gets all the overlays for this component. + */ + /* + * Function: hideOverlay + * Hides the overlay specified by the given id. + */ + /* + * Function: hideOverlays + * Hides all Overlays + */ + /* + * Function: showOverlay + * Shows the overlay specified by the given id. + */ + /* + * Function: showOverlays + * Shows all Overlays + */ + /** + * Function: removeAllOverlays + * Removes all overlays from the Endpoint, and then repaints. + */ + /** + * Function: removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + /** + * Function: removeOverlays + * Removes a set of overlays by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + /* + * Function: setLabel + * Sets the Endpoint's label. + * + * Parameters: + * l - label to set. May be a String, a Function that returns a String, or a params object containing { "label", "labelStyle", "location", "cssClass" }. Note that this uses innerHTML on the label div, so keep + * that in mind if you need escaped HTML. + */ + /* + Function: getLabel + Returns the label text for this Endpoint (or a function if you are labelling with a function). + This does not return the overlay itself; this is a convenience method which is a pair with + setLabel; together they allow you to add and access a Label Overlay without having to create the + Overlay object itself. For access to the underlying label overlay that jsPlumb has created, + use getLabelOverlay. + */ + /* + Function: getLabelOverlay + Returns the underlying internal label overlay, which will exist if you specified a label on + an addEndpoint call, or have called setLabel at any stage. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + + return self; + }; + }; + + var jsPlumb = new jsPlumbInstance(); + if (typeof window != 'undefined') window.jsPlumb = jsPlumb; + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + j.init(); + return j; + }; + + var _curryAnchor = function(x, y, ox, oy, type, fnInit) { + return function(params) { + params = params || {}; + //var a = jsPlumb.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance); + var a = params.jsPlumbInstance.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance); + a.type = type; + if (fnInit) fnInit(a, params); + return a; + }; + }; + /* + * Property: Anchors.TopCenter + * An Anchor that is located at the top center of the element. + */ + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1, "TopCenter"); + /* + * Property: Anchors.BottomCenter + * An Anchor that is located at the bottom center of the element. + */ + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1, "BottomCenter"); + /* + * Property: Anchors.LeftMiddle + * An Anchor that is located at the left middle of the element. + */ + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0, "LeftMiddle"); + /* + * Property: Anchors.RightMiddle + * An Anchor that is located at the right middle of the element. + */ + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0, "RightMiddle"); + /* + * Property: Anchors.Center + * An Anchor that is located at the center of the element. + */ + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0, "Center"); + /* + * Property: Anchors.TopRight + * An Anchor that is located at the top right corner of the element. + */ + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1, "TopRight"); + /* + * Property: Anchors.BottomRight + * An Anchor that is located at the bottom right corner of the element. + */ + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1, "BottomRight"); + /* + * Property: Anchors.TopLeft + * An Anchor that is located at the top left corner of the element. + */ + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1, "TopLeft"); + /* + * Property: Anchors.BottomLeft + * An Anchor that is located at the bototm left corner of the element. + */ + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1, "BottomLeft"); + + jsPlumb.Defaults.DynamicAnchors = function(params) { + return params.jsPlumbInstance.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"], params.elementId, params.jsPlumbInstance); + }; + /* + * Property: Anchors.AutoDefault + * Default DynamicAnchors - chooses from TopCenter, RightMiddle, BottomCenter, LeftMiddle. + */ + jsPlumb.Anchors["AutoDefault"] = function(params) { + var a = params.jsPlumbInstance.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors(params)); + a.type = "AutoDefault"; + return a; + }; + + /* + * Property: Anchors.Assign + * An Anchor whose location is assigned at connection time, through an AnchorPositionFinder. Used in conjunction + * with the 'makeTarget' function. jsPlumb has two of these - 'Fixed' and 'Grid', and you can also write your own. + */ + jsPlumb.Anchors["Assign"] = _curryAnchor(0,0,0,0,"Assign", function(anchor, params) { + // find what to use as the "position finder". the user may have supplied a String which represents + // the id of a position finder in jsPlumb.AnchorPositionFinders, or the user may have supplied the + // position finder as a function. we find out what to use and then set it on the anchor. + var pf = params.position || "Fixed"; + anchor.positionFinder = pf.constructor == String ? params.jsPlumbInstance.AnchorPositionFinders[pf] : pf; + // always set the constructor params; the position finder might need them later (the Grid one does, + // for example) + anchor.constructorParams = params; + }); + + // Continuous anchor is just curried through to the 'get' method of the continuous anchor + // factory. + /* + * Property: Anchors.Continuous + * An Anchor that is tracks the other element in the connection, choosing the closest face. + */ + jsPlumb.Anchors["Continuous"] = function(params) { + return params.jsPlumbInstance.continuousAnchorFactory.get(params); + }; + + // these are the default anchor positions finders, which are used by the makeTarget function. supply + // a position finder argument to that function allows you to specify where the resulting anchor will + // be located + jsPlumb.AnchorPositionFinders = { + "Fixed": function(dp, ep, es, params) { + return [ (dp.left - ep.left) / es[0], (dp.top - ep.top) / es[1] ]; + }, + "Grid":function(dp, ep, es, params) { + var dx = dp.left - ep.left, dy = dp.top - ep.top, + gx = es[0] / (params.grid[0]), gy = es[1] / (params.grid[1]), + mx = Math.floor(dx / gx), my = Math.floor(dy / gy); + return [ ((mx * gx) + (gx / 2)) / es[0], ((my * gy) + (gy / 2)) / es[1] ]; + } + }; + + /* + * Property: Anchors.Perimeter + * An Anchor that tracks the perimeter of some shape, approximating it with a given number of dynamically + * positioned locations. + */ + jsPlumb.Anchors["Perimeter"] = function(params) { + params = params || {}; + var anchorCount = params.anchorCount || 60, + shape = params.shape; + + if (!shape) throw new Error("no shape supplied to Perimeter Anchor type"); + + var _circle = function() { + var r = 0.5, step = Math.PI * 2 / anchorCount, current = 0, a = []; + for (var i = 0; i < anchorCount; i++) { + var x = r + (r * Math.sin(current)), + y = r + (r * Math.cos(current)); + a.push( [ x, y, 0, 0 ] ); + current += step; + } + return a; + }, + _shape = function(faces) { + var anchorsPerFace = anchorCount / faces.length, a = [], + _computeFace = function(x1, y1, x2, y2) { + var dx = (x2 - x1) / anchorsPerFace, dy = (y2 - y1) / anchorsPerFace; + for (var i = 0; i < anchorsPerFace; i++) { + a.push( [ + x1 + (dx * i), + y1 + (dy * i), + 0, + 0 + ]); + } + }; + + for (var i = 0; i < faces.length; i++) + _computeFace.apply(null, faces[i]); + + return a; + }, + _rectangle = function() { + return _shape([ + [ 0, 0, 1, 0 ], [ 1, 0, 1, 1 ], [ 1, 1, 0, 1 ], [ 0, 1, 0, 0 ] + ]); + }; + + var _shapes = { + "circle":_circle, + "ellipse":_circle, + "diamond":function() { + return _shape([ + [ 0.5, 0, 1, 0.5 ], [ 1, 0.5, 0.5, 1 ], [ 0.5, 1, 0, 0.5 ], [ 0, 0.5, 0.5, 0 ] + ]); + }, + "rectangle":_rectangle, + "square":_rectangle, + "triangle":function() { + return _shape([ + [ 0.5, 0, 1, 1 ], [ 1, 1, 0, 1 ], [ 0, 1, 0.5, 0] + ]); + }/*, + "path":function(points) { + var p = []; + for (var i = 0; i < points.length - 1; i++) { + p.push([points[i][0], points[i][1], points[i+1][0], points[i+1][1]]); + } + return _shape(p); + }*/ + }; + + if (!_shapes[shape]) throw new Error("Shape [" + shape + "] is unknown by Perimeter Anchor type"); + + var da = _shapes[shape](), + a = params.jsPlumbInstance.makeDynamicAnchor(da); + a.type = "Perimeter"; + return a; + }; +})(); diff --git a/archive/1.3.13/jsPlumb-1.3.13-tests.js b/archive/1.3.13/jsPlumb-1.3.13-tests.js new file mode 100644 index 000000000..42080d25d --- /dev/null +++ b/archive/1.3.13/jsPlumb-1.3.13-tests.js @@ -0,0 +1,4663 @@ +// _jsPlumb qunit tests. + +QUnit.config.reorder = false; + +var _getContextNode = function() { + return $("#container"); +}; + +var assertContextExists = function() { + ok(_getContextNode().length == 1, "context node exists"); +}; + +var assertContextSize = function(elementCount) { + //equals(_getContextNode().children().length - _divs.length, elementCount, 'context has ' + elementCount + ' children'); +}; + +var assertContextEmpty = function() { + equals(_getContextNode().children.length, 0, "context empty"); +}; + +var assertEndpointCount = function(elId, count, _jsPlumb) { + equals(_jsPlumb.getTestHarness().endpointCount(elId), count, elId + " has " + count + (count > 1) ? "endpoints" : "endpoint"); + equals(_jsPlumb.anchorManager.getEndpointsFor(elId).length, count, "anchor manager has " + count + (count > 1) ? "endpoints" : "endpoint"); +}; + +var assertConnectionCount = function(endpoint, count) { + equals(endpoint.connections.length, count, "endpoint has " + count + " connections"); +}; + +var assertConnectionByScopeCount = function(scope, count, _jsPlumb) { + equals(_jsPlumb.getTestHarness().connectionCount(scope), count, 'Scope ' + scope + " has " + count + (count > 1) ? "connections" : "connection"); + +}; + +var _divs = []; +var _addDiv = function(id, parent) { + var d1 = document.createElement("div"); + if (parent) parent.append(d1); else _getContextNode().append(d1); + d1 = jsPlumb.CurrentLibrary.getElementObject(d1); + jsPlumb.CurrentLibrary.setAttribute(d1, "id", id); + _divs.push(id); + return d1; +}; + +var defaults = null, + _cleanup = function(_jsPlumb) { + + _jsPlumb.reset(); + _jsPlumb.Defaults = defaults; + + for (var i in _divs) { + $("#" + _divs[i]).remove(); + } + _divs.splice(0, _divs.length - 1); + + $("#container").empty(); +}; + +var testSuite = function(renderMode, _jsPlumb) { + + + module("jsPlumb", { + teardown: function() { + _cleanup(_jsPlumb); + }, + setup:function() { + defaults = jsPlumb.extend({}, _jsPlumb.Defaults); + } + }); + + // setup the container + var container = document.createElement("div"); + container.id = "container"; + document.body.appendChild(container); + + var jpcl = jsPlumb.CurrentLibrary; + + _jsPlumb.setRenderMode(renderMode); + + test(renderMode + " : getElementObject", function() { + var e = document.createElement("div"); + e.id = "FOO"; + document.body.appendChild(e); + var el = jpcl.getElementObject(e); + equals(jpcl.getAttribute(el, "id"), "FOO"); + }); + + test(renderMode + " : getDOMElement", function() { + var e = document.createElement("div"); + e.id = "FOO"; + document.body.appendChild(e); + var el = jpcl.getElementObject(e); + var e2 = jpcl.getDOMElement(el); + equals(e2.id, "FOO"); + }); + + test(renderMode + ': _jsPlumb setup', function() { + ok(_jsPlumb, "loaded"); + }); + + test(renderMode + ': getId', function() { + var d10 = _addDiv('d10'); + equals(_jsPlumb.getTestHarness().getId(d10), "d10"); + }); + + test(renderMode + ': create a simple endpoint', function() { + var d1 = _addDiv("d1"); + var e = _jsPlumb.addEndpoint($("#d1"), {}); + ok(e, 'endpoint exists'); + assertEndpointCount("d1", 1, _jsPlumb); + ok(e.id != null, "endpoint has had an id assigned"); + }); + + test(renderMode + ': create and remove a simple endpoint', function() { + var d1 = _addDiv("d1"); + var ee = _jsPlumb.addEndpoint(d1, {uuid:"78978597593"}); + ok(ee != null, "endpoint exists"); + var e = _jsPlumb.getEndpoint("78978597593"); + ok(e != null, "the endpoint could be retrieved by UUID"); + ok(e.id != null, "the endpoint has had an id assigned to it"); + assertEndpointCount("d1", 1, _jsPlumb); + assertContextSize(1); // one Endpoint canvas. + _jsPlumb.removeEndpoint(d1, ee); + assertEndpointCount("d1", 0, _jsPlumb); + e = _jsPlumb.getEndpoint("78978597593"); + equals(e, null, "the endpoint has been deleted"); + assertContextSize(0); // no Endpoint canvases. + }); + + test(renderMode + ': create two simple endpoints, registered using a selector', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + d1.addClass("window");d2.addClass("window"); + var endpoints = _jsPlumb.addEndpoint($(".window"), {}); + equals(endpoints.length, 2, "endpoint added to both windows"); + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + }); + + test(renderMode + ': create two simple endpoints, registered using an array of element ids', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + d1.addClass("window");d2.addClass("window"); + var endpoints = _jsPlumb.addEndpoint(["d1", "d2"], {}); + equals(endpoints.length, 2, "endpoint added to both windows"); + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + }); + + test(renderMode + ': draggable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1"); + var e = _jsPlumb.addEndpoint(d1, {isSource:true}); + ok(e, 'endpoint exists'); + }); + + test(renderMode + ': droppable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1") + var e = _jsPlumb.addEndpoint(d1, {isTarget:true}); + ok(e, 'endpoint exists'); + }); + + test(renderMode + ': defaultEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = _jsPlumb.addEndpoint(d3, {isSource:true}); + ok(e3.anchor, 'endpoint 3 has an anchor'); + var e4 = _jsPlumb.addEndpoint(d4, {isSource:true}); + assertEndpointCount("d3", 1, _jsPlumb); + assertEndpointCount("d4", 1, _jsPlumb); + assertContextSize(2); // one canvas for each of our two endpoints. + ok(!e3.isFull(), "endpoint 3 is not full."); + _jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // we have one connection + _jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // should have refused the connection; default max is 1. + assertContextSize(3); // now we have one more canvas, for the Connection that was accepted. + }); + + test(renderMode + ': specifiedEndpointMaxConnections', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = _jsPlumb.addEndpoint(d5, {isSource:true, maxConnections:3}); + ok(e5.anchor, 'endpoint 5 has an anchor'); + var e6 = _jsPlumb.addEndpoint(d6, {isSource:true, maxConnections:2}); // this one has max TWO + assertEndpointCount("d5", 1, _jsPlumb); assertEndpointCount("d6", 1, _jsPlumb); + assertContextSize(2); + ok(!e5.isFull(), "endpoint 5 is not full."); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 1); // we have one connection + assertContextSize(3); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // two connections + assertContextSize(4); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // should have refused; max is 2, for d4. + assertContextSize(4); + }); + + test(renderMode + ': noEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = _jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + var e4 = _jsPlumb.addEndpoint(d4, {isSource:true, maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertContextSize(3); + assertConnectionCount(e3, 1); // we have one connection + _jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 2); // we have two. etc (default was one. this proves max is working). + assertContextSize(4); + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = _jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e4}); + assertConnectionCount(e4, 3); + assertContextSize(6); + }); + + test(renderMode + ': anchors equal', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + ok(a1.equals(a2), "anchors are the same"); + }); + + test(renderMode + ': anchors equal with offsets', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb); + ok(a1.equals(a2), "anchors are the same"); + }); + + test(renderMode + ': anchors not equal', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 0, 1], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + ok(!a1.equals(a2), "anchors are different"); + }); + + test(renderMode + ': anchor not equal with offsets', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + ok(!a1.equals(a2), "anchors are different"); + }); + + test(renderMode + ': detach does not fail when no arguments are provided', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + _jsPlumb.connect({source:d3, target:d4}); + _jsPlumb.detach(); + }); + + // test that detach does not fire an event by default + test(renderMode + ': _jsPlumb.detach should fire detach event by default', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach(conn); + equals(eventCount, 1); + }); + + // test that detach does not fire an event by default + test(renderMode + ': _jsPlumb.detach should fire detach event by default, using params object', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({connection:conn}); + equals(eventCount, 1); + }); + + // test that detach fires an event when instructed to do so + test(renderMode + ': _jsPlumb.detach should not fire detach event when instructed to not do so', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach(conn, {fireEvent:false}); + equals(eventCount, 0); + }); + + // issue 81 + test(renderMode + ': _jsPlumb.detach should fire only one detach event (pass Connection as argument)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach(conn); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': _jsPlumb.detach should fire only one detach event (pass Connection as param in argument)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({connection:conn}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': detach should fire only one detach event (pass source and targets as strings as arguments in params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({source:"d5", target:"d6"}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': detach should fire only one detach event (pass source and targets as divs as arguments in params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({source:d5, target:d6, fireEvent:true}); + equals(eventCount, 1); + }); + + //TODO make sure you run this test with a single detach call, to ensure that + // single detach calls result in the connection being removed. detachEveryConnection can + // just blow away the connectionsByScope array and recreate it. + test(renderMode + ': getConnections (simple case, default scope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + _jsPlumb.connect({source:d5, target:d6}); + assertContextSize(3); + var c = _jsPlumb.getConnections(); // will get all connections in the default scope. + equals(c.length, 1, "there is one connection"); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by element id using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + _jsPlumb.connect({source:d5, target:d6}); + _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach({source:'d5', target:'d6'}); + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by id using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + _jsPlumb.connect({source:d5, target:d6}); + _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach({source:"d5", target:"d6"}); + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by element object using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + _jsPlumb.connect({source:d5, target:d6}); + _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach({source:d5, target:d6}); + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by Connection)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + var c56 = _jsPlumb.connect({source:d5, target:d6}); + var c67 = _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach(c56); + assertContextSize(5); // check that the connection canvas was removed. + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + }); + +// beforeDetach functionality + + test(renderMode + ": detach; beforeDetach on connect call returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { return false; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": detach; beforeDetach on connect call returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { return true; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": detach; beforeDetach on connect call throws an exception; we treat it with the contempt it deserves and pretend it said the detach was ok.", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { throw "i am an example of badly coded beforeDetach, but i don't break jsPlumb "; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": detach; beforeDetach on addEndpoint call to source Endpoint returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return false; } }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": detach; beforeDetach on addEndpoint call to source Endpoint returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return true; } }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + + test(renderMode + ": Endpoint.detach; beforeDetach on addEndpoint call to source Endpoint returns false; Endpoint.detach returns false too (the UI needs it to)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return false; } }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + var success = e1.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + ok(!success, "Endpoint reported detach did not execute"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns false but detach is forced", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c, {forceDetach:true}); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was denied but forced anyway"); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was denied but forced anyway"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return true; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach bound to _jsPlumb returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + _jsPlumb.bind("beforeDetach", function(connection) { + ok(connection.sourceId === "d1", "connection is provided and configured with correct source"); + ok(connection.targetId === "d2", "connection is provided and configured with correct target"); + return false; + }); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach bound to _jsPlumb returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + _jsPlumb.bind("beforeDetach", function(connection) { + ok(connection.sourceId === "d1", "connection is provided and configured with correct source"); + ok(connection.targetId === "d2", "connection is provided and configured with correct target"); + return true; + }); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was denied"); + }); + + test(renderMode + ": _jsPlumb.detachAllConnections ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detachAllConnections(d1); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachAll was called"); + }); + + test(renderMode + ": _jsPlumb.detachEveryConnection ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detachEveryConnection(); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachEveryConnection was called"); + }); + + test(renderMode + ": Endpoint.detachAll ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + e1.detachAll(); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachAllConnections was called"); + }); + +// ******** end of beforeDetach tests ************** + +// detachEveryConnection/detachAllConnections fireEvent overrides tests + + test(renderMode + ": _jsPlumb.detachEveryConnection fires events", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0; + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachEveryConnection(); + equals(connCount, 0, "no connections registered"); + }); + + test(renderMode + ": _jsPlumb.detachEveryConnection doesn't fire events when instructed not to", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0; + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachEveryConnection({fireEvent:false}); + equals(connCount, 2, "two connections registered"); + }); + + test(renderMode + ": _jsPlumb.detachAllConnections fires events", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0, + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachAllConnections("d1"); + equals(connCount, 0, "no connections registered"); + }); + + test(renderMode + ": _jsPlumb.detachAllConnections doesn't fire events when instructed not to", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0, + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachAllConnections("d1", {fireEvent:false}); + equals(connCount, 2, "two connections registered"); + }); + + test(renderMode + ': getConnections (scope testScope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + _jsPlumb.connect({source:d5, target:d6, scope:'testScope'}); + var c = _jsPlumb.getConnections("testScope"); // will get all connections in testScope + equals(c.length, 1, "there is one connection"); + equals(c[0].sourceId, 'd5', "the connection's source is d5"); + equals(c[0].targetId, 'd6', "the connection's source is d6"); + c = _jsPlumb.getConnections(); // will get all connections in default scope; should be none. + equals(c.length, 0, "there are no connections in the default scope"); + }); + + test(renderMode + ': _jsPlumb.getAllConnections (filtered by scope)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + _jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + _jsPlumb.connect({source:d9, target:d10}); // default scope + var c = _jsPlumb.getAllConnections(); // will get all connections + equals(c[_jsPlumb.getDefaultScope()].length, 1); + equals(c['testScope'].length, 1); + // now supply a list of scopes + c = _jsPlumb.getConnections(); + equals(c.length, 1); + c = _jsPlumb.getConnections("testScope"); + equals(c.length, 1, "there is one connection in 'testScope'"); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by scope and sourceId)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + _jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + _jsPlumb.connect({source:d9, target:d8, scope:'testScope'}); + _jsPlumb.connect({source:d9, target:d10}); // default scope + var c = _jsPlumb.getConnections({scope:'testScope', source:'d8'}); // will get all connections with sourceId 'd8' + equals(c.length, 1, "there is one connection in 'testScope' from d8"); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by scope, source id and target id)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope'}); + var c = _jsPlumb.getConnections({scope:'testScope', source:'d11', target:'d13'}); + equals(c.length, 1, "there is one connection from d11 to d13"); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = _jsPlumb.getConnections({scope:['testScope','testScope3']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes and source ids)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = _jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes, source ids and target ids)', function() { + _jsPlumb.detachEveryConnection(); + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'), d14=_addDiv("d14"), d15=_addDiv("d15"); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d11, target:d14, scope:'testScope'}); + _jsPlumb.connect({source:d11, target:d15, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + assertContextSize(18); + var c = _jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11'], target:['d14', 'd15']}); + equals(c['testScope'].length, 2, 'there are two connections in testScope'); + equals(c['testScope3'], null, 'there are no connections in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + var anEntry = c['testScope'][0]; + ok(anEntry.endpoints[0] != null, "Source endpoint is set"); + ok(anEntry.endpoints[1] != null, "Target endpoint is set"); + equals(anEntry.source.attr("id"), "d11", "Source is div d11"); + equals(anEntry.target.attr("id"), "d14", "Target is div d14"); + }); + + test(renderMode + ': getEndpoints, one Endpoint added by addEndpoint, get Endpoints by selector', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + _jsPlumb.addEndpoint(d1); + var e = _jsPlumb.getEndpoints(d1); + equals(e.length, 1, "there is one endpoint for element d1"); + }); + + test(renderMode + ': getEndpoints, one Endpoint added by addEndpoint, get Endpoints by id', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + _jsPlumb.addEndpoint(d1); + var e = _jsPlumb.getEndpoints("d1"); + equals(e.length, 1, "there is one endpoint for element d1"); + }); + + test(renderMode + ': connection event listeners', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null/*, returnedParams2 = null*/; + _jsPlumb.bind("jsPlumbConnection", function(params) { + returnedParams = $.extend({}, params); + }); + var c = _jsPlumb.connect({source:d1, target:d2}); + ok(returnedParams != null, "new connection listener event was fired"); + ok(returnedParams.connection != null, 'connection is set'); + equals(returnedParams.sourceId, "d1", 'sourceid is set'); + equals(returnedParams.targetId, "d2", 'targetid is set'); + equals(returnedParams.source.attr("id"), "d1", 'source is set'); + equals(returnedParams.target.attr("id"), "d2" , 'target is set'); + ok(returnedParams.sourceEndpoint != null, "source endpoint is not null"); + ok(returnedParams.targetEndpoint != null, "target endpoint is not null"); + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.detach(c); + ok(returnedParams.connection != null, 'connection is set'); + }); + + test(renderMode + ': detach event listeners (detach by connection)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + ok(returnedParams.connection != null, "removed connection listener event passed in connection"); + }); + + test(renderMode + ': detach event listeners (detach by element ids)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach({source:"d1",target:"d2"}); + ok(returnedParams != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (detach by elements)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach({source:d1,target:d2}); + ok(returnedParams != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detach method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detachFrom method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachFrom(e2); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detachAll method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachAll(); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via _jsPlumb.deleteEndpoint method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + _jsPlumb.deleteEndpoint(e1); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(1); + }); + + test(renderMode + ': detach event listeners (ensure cleared by _jsPlumb.reset)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach({source:d1,target:d2, fireEvent:true}); + ok(returnedParams != null, "removed connection listener event was fired"); + returnedParams = null; + + _jsPlumb.reset(); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach(d1,d2); + ok(returnedParams == null, "connection listener was cleared by _jsPlumb.reset()"); + }); + + test(renderMode + ': connection events that throw errors', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null, returnedParams2 = null; + _jsPlumb.bind("jsPlumbConnection", function(params) { + returnedParams = $.extend({}, params); + throw "oh no!"; + }); + _jsPlumb.connect({source:d1, target:d2}); + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + _jsPlumb.connect({source:d3, target:d4}); + ok(returnedParams != null, "new connection listener event was fired; we threw an error, _jsPlumb survived."); + }); + + test(renderMode + ': unbinding connection event listeners, connection', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var count = 0; + _jsPlumb.bind("jsPlumbConnection", function(params) { + count++; + }); + var c = _jsPlumb.connect({source:d1, target:d2}); + ok(count == 1, "received one event"); + _jsPlumb.unbind("jsPlumbConnection"); + var c2 = _jsPlumb.connect({source:d1, target:d2}); + ok(count == 1, "still received only one event"); + + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + count--; + }); + _jsPlumb.detach(c); + ok(count == 0, "count of events is now zero"); + }); + + test(renderMode + ': unbinding connection event listeners, detach', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var count = 0; + _jsPlumb.bind("jsPlumbConnection", function(params) { + count++; + }); + var c = _jsPlumb.connect({source:d1, target:d2}); + ok(count == 1, "received one event"); + var c2 = _jsPlumb.connect({source:d1, target:d2}); + ok(count == 2, "received two events"); + + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + count--; + }); + _jsPlumb.detach(c); + ok(count == 1, "count of events is now one"); + _jsPlumb.unbind("jsPlumbConnectionDetached"); + _jsPlumb.detach(c2); + ok(count == 1, "count of events is still one"); + }); + + test(renderMode + ': unbinding connection event listeners, all listeners', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var count = 0; + _jsPlumb.bind("jsPlumbConnection", function(params) { + count++; + }); + var c = _jsPlumb.connect({source:d1, target:d2}), + c2 = _jsPlumb.connect({source:d1, target:d2}), + c3 = _jsPlumb.connect({source:d1, target:d2}); + + ok(count == 3, "received three events"); + + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + count--; + }); + _jsPlumb.detach(c); + ok(count == 2, "count of events is now two"); + + _jsPlumb.unbind(); // unbind everything + + _jsPlumb.detach(c2); + _jsPlumb.detach(c3); + _jsPlumb.connect({source:d1, target:d2}) + _jsPlumb.connect({source:d1, target:d2}) + _jsPlumb.connect({source:d1, target:d2}) + _jsPlumb.connect({source:d1, target:d2}) + + ok(count == 2, "count of events is still two"); + }); + + test(renderMode + ": Endpoint.detachFrom", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + e16.detachFrom(e17); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by _jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": Endpoint.detachFromConnection", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + e16.detachFromConnection(conn); + assertContextSize(3); // all canvases should remain; the connection was not removed. + // but endpoint e16 should have no connections now. + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": Endpoint.detachAll", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + var e16 = _jsPlumb.addEndpoint($("#d16"), {isSource:true,maxConnections:-1}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint($("#d17"), {isSource:true}); + var e18 = _jsPlumb.addEndpoint($("#d18"), {isSource:true}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e18}); + assertConnectionCount(e16, 2); + assertConnectionCount(e17, 1); + assertConnectionCount(e18, 1); + assertContextSize(5); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 2, _jsPlumb); + e16.detachAll(); + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": Endpoint.detach", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + e16.detach(conn); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by _jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": Endpoint.isConnectedTo", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.isConnectedTo(e17), true, "e16 and e17 are connected"); + }); + + asyncTest(renderMode + "jsPlumbUtil.setImage on Endpoint, with supplied onload", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { + endpoint:[ "Image", { + src:"../img/endpointTest1.png", + onload:function(imgEp) { + _jsPlumb.repaint("d1"); + ok(imgEp.img.src.indexOf("endpointTest1.png") != -1, "image source is correct"); + ok(imgEp.img.src.indexOf("endpointTest1.png") != -1, "image elementsource is correct"); + + imgEp.canvas.setAttribute("id", "iwilllookforthis"); + + _jsPlumb.removeAllEndpoints("d1"); + ok(document.getElementById("iwilllookforthis") == null, "image element was removed after remove endpoint"); + } + } ] + }; + start(); + _jsPlumb.addEndpoint(d1, e); + }); + + test(renderMode + ": setting endpoint uuid", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + equals(e16.getUuid(), uuid, "endpoint's uuid was set correctly"); + }); + + test(renderMode + ": _jsPlumb.getEndpoint (by uuid)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by uuid, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + _jsPlumb.deleteEndpoint(uuid); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + var ebe = _jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by uuid, connections too)", function() { + // create two endpoints (one with a uuid), add them to two divs and then connect them. + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + // check that the connection was ok. + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + // delete the endpoint that has a uuid. verify that the endpoint cannot be retrieved and that the connection has been removed, but that + // element d17 still has its Endpoint. + _jsPlumb.deleteEndpoint(uuid); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + var ebe = _jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + ebe = _jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 1, "element d17 still has its Endpoint"); + assertContextSize(1); + + // now delete d17's endpoint and check that it has gone. + _jsPlumb.deleteEndpoint(e17); + f = _jsPlumb.getEndpoint(e17); + equals(f, null, "endpoint has been deleted"); + ebe = _jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 0, "element d17 no longer has any Endpoints"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by reference, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + _jsPlumb.deleteEndpoint(e16); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by reference, connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + _jsPlumb.deleteEndpoint(e16); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + assertContextSize(1); + }); + + test(renderMode + ": _jsPlumb.deleteEveryEndpoint", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + assertContextSize(2); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + _jsPlumb.deleteEveryEndpoint(); + + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = _jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEveryEndpoint (connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + _jsPlumb.deleteEveryEndpoint(); + + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = _jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); // no canvases. so all connection canvases have been cleaned up too. + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchor:[0,0.5,0,-1]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:"TopCenter"}); + assertContextSize(2); + equals(e16.anchor.x, 0); + equals(e16.anchor.y, 0.5); + equals(e17.anchor.x, 0.5); + equals(e17.anchor.y, 0); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (tooltip)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {tooltip:"FOO"}), + e17 = _jsPlumb.addEndpoint(d17, {tooltip:"BAZ"}); + assertContextSize(2); + equals(e16.canvas.getAttribute("title"), "FOO"); + equals(e17.canvas.getAttribute("title"), "BAZ"); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case, dynamic anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + assertContextSize(2); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case, two arg method)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchor:[0,0.5,0,-1]}); + var e17 = _jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchor:"TopCenter"}); + assertContextSize(2); + equals(e16.anchor.x, 0); + equals(e16.anchor.y, 0.5); + equals(false, e16.isTarget); + equals(true, e16.isSource); + equals(e17.anchor.x, 0.5); + equals(e17.anchor.y, 0); + equals(true, e17.isTarget); + equals(false, e17.isSource); + }); + + + test(renderMode + ": _jsPlumb.addEndpoints (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false, anchor:[0,0.5,0,-1] }, { isTarget:true, isSource:false, anchor:"TopCenter" }]); + assertContextSize(2); + equals(e16[0].anchor.x, 0); + equals(e16[0].anchor.y, 0.5); + equals(false, e16[0].isTarget); + equals(true, e16[0].isSource); + equals(e16[1].anchor.x, 0.5); + equals(e16[1].anchor.y, 0); + equals(true, e16[1].isTarget); + equals(false, e16[1].isSource); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (with reference params)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var refParams = {anchor:"RightMiddle"}; + var e16 = _jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false}, { isTarget:true, isSource:false }], refParams); + assertContextSize(2); + equals(e16[0].anchor.x, 1); + equals(e16[0].anchor.y, 0.5); + equals(false, e16[0].isTarget); + equals(true, e16[0].isSource); + equals(e16[1].anchor.x, 1); + equals(e16[1].anchor.y, 0.5); + equals(true, e16[1].isTarget); + equals(false, e16[1].isSource); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case, dynamic anchors, two arg method)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchors:["TopCenter", "BottomCenter"]}); + assertContextSize(2); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (default overlays)", function() { + _jsPlumb.Defaults.Overlays = [ + [ "Label", { id:"label" } ] + ]; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16), + e2 = _jsPlumb.addEndpoint(d17); + + ok(e1.getOverlay("label") != null, "endpoint 1 has overlay from defaults"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (default overlays)", function() { + _jsPlumb.Defaults.Overlays = [ + [ "Label", { id:"label" } ] + ]; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16, { + overlays:[ + ["Label", { id:"label2", location:[ 0.5, 1 ] } ] + ] + }), + e2 = _jsPlumb.addEndpoint(d17); + + ok(e1.getOverlay("label") != null, "endpoint 1 has overlay from defaults"); + ok(e1.getOverlay("label2") != null, "endpoint 1 has overlay from addEndpoint call"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (end point set label)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16), + e2 = _jsPlumb.addEndpoint(d17); + + e1.setLabel("FOO"); + equals(e1.getLabel(), "FOO", "endpoint's label is correct"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (end point set label in constructor, as string)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16, {label:"FOO"}), + e2 = _jsPlumb.addEndpoint(d17); + + equals(e1.getLabel(), "FOO", "endpoint's label is correct"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (end point set label in constructor, as function)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16, {label:function() { return "BAZ"; }, labelLocation:0.1}), + e2 = _jsPlumb.addEndpoint(d17); + + equals(e1.getLabel()(), "BAZ", "endpoint's label is correct"); + equals(e1.getLabelOverlay().getLocation(), 0.1, "endpoint's label's location is correct"); + }); + +// ****************** makeTarget (and associated methods) tests ******************************************** + + test(renderMode + ": _jsPlumb.makeTarget (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.makeTarget (specify two divs in an array)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.makeTarget([d16, d17], { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.makeTarget (specify two divs by id in an array)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.makeTarget(["d16", "d17"], { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.makeTarget (specify divs by selector)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + d16.addClass("FOO");d17.addClass("FOO"); + _jsPlumb.makeTarget($(".FOO"), { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e.length, 1, "d17 has one endpoint"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case, two connect calls)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17"}); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 2, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case, two connect calls, uniqueEndpoint set)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true, anchor:"LeftMiddle", uniqueEndpoint:true, maxConnections:-1 }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17"}); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].connections.length, 2, "endpoint on d17 has two connections"); + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (newConnection:true specified)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17", newConnection:true}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0.5, "anchor is BottomCenter"); //here we should be seeing the default anchor + equals(e[0].anchor.y, 1, "anchor is BottomCenter"); //here we should be seeing the default anchor + }); + +// jsPlumb.connect, after makeSource has been called on some element + test(renderMode + ": _jsPlumb.connect after makeSource (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeSource (simple case, two connect calls)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 2, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case, two connect calls, uniqueEndpoint set)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true, anchor:"LeftMiddle", uniqueEndpoint:true, maxConnections:-1 }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].connections.length, 2, "endpoint on d17 has two connections"); + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (newConnection:true specified)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16, newConnection:true}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0.5, "anchor is BottomCenter"); //here we should be seeing the default anchor + equals(e[0].anchor.y, 1, "anchor is BottomCenter"); //here we should be seeing the default anchor + }); + + test(renderMode + ": _jsPlumb.connect after makeSource on child; wth parent set (parent should be recognised)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18", d17); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d18, { isSource:true,anchor:"LeftMiddle", parent:d17 }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + assertEndpointCount("d18", 0, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + // makeSource, then disable it. should not be able to make a connection from it. + test(renderMode + ": _jsPlumb.connect after makeSource and setSourceEnabled(false) (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.setSourceEnabled(d17, false); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + }); + + // makeSource, then disable it. should not be able to make a connection from it. + test(renderMode + ": _jsPlumb.connect after makeSource and setSourceEnabled(false) (selector as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.setSourceEnabled($("div"), false); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + }); + + // makeSource, then toggle its enabled state. should not be able to make a connection from it. + test(renderMode + ": _jsPlumb.connect after makeSource and toggleSourceEnabled() (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.toggleSourceEnabled(d17); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + + _jsPlumb.toggleSourceEnabled(d17); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + // makeSource, then disable it. should not be able to make a connection from it. + test(renderMode + ": _jsPlumb.connect after makeSource and toggleSourceEnabled() (selector as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.toggleSourceEnabled($("div")); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + _jsPlumb.toggleSourceEnabled($("div")); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + test(renderMode + ": jsPlumb.isSource and jsPlumb.isSourceEnabled", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + ok(_jsPlumb.isSource(d17) == true, "d17 is recognised as connection source"); + ok(_jsPlumb.isSourceEnabled(d17) == true, "d17 is recognised as enabled"); + _jsPlumb.setSourceEnabled(d17, false); + ok(_jsPlumb.isSourceEnabled(d17) == false, "d17 is recognised as disabled"); + }); + + // makeSource, then disable it. should not be able to make a connection to it. + test(renderMode + ": _jsPlumb.connect after makeTarget and setTargetEnabled(false) (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.setTargetEnabled(d17, false); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + }); + + // makeTarget, then disable it. should not be able to make a connection to it. + test(renderMode + ": _jsPlumb.connect after makeTarget and setTargetEnabled(false) (selector as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.setTargetEnabled($("div"), false); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + }); + + // makeTarget, then toggle its enabled state. should not be able to make a connection to it. + test(renderMode + ": _jsPlumb.connect after makeTarget and toggleTargetEnabled() (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.toggleTargetEnabled(d17); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + + _jsPlumb.toggleTargetEnabled(d17); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + // makeTarget, then disable it. should not be able to make a connection to it. + test(renderMode + ": _jsPlumb.connect after makeTarget and toggleTargetEnabled() (selector as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.toggleTargetEnabled($("div")); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + _jsPlumb.toggleTargetEnabled($("div")); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + test(renderMode + ": jsPlumb.isTarget and jsPlumb.isTargetEnabled", function() { + var d17 = _addDiv("d17"); + _jsPlumb.makeTarget(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + ok(_jsPlumb.isTarget(d17) == true, "d17 is recognised as connection target"); + ok(_jsPlumb.isTargetEnabled(d17) == true, "d17 is recognised as enabled"); + _jsPlumb.setTargetEnabled(d17, false); + ok(_jsPlumb.isTargetEnabled(d17) == false, "d17 is recognised as disabled"); + }); + + // makeSource, then unmake it. should not be able to make a connection from it. then connect to it, which should succeed, + // because jsPlumb will just add a new endpoint. + test(renderMode + ": jsPlumb.unmakeSource (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + ok(_jsPlumb.isSource(d17) == true, "d17 is currently a source"); + // unmake source + _jsPlumb.unmakeSource(d17); + ok(_jsPlumb.isSource(d17) == false, "d17 is no longer a source"); + + // this should succeed, because d17 is no longer a source and so jsPlumb will just create and add a new endpoint to d17. + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + // maketarget, then unmake it. should not be able to make a connection to it. + test(renderMode + ": jsPlumb.unmakeTarget (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + ok(_jsPlumb.isTarget(d17) == true, "d17 is currently a target"); + // unmake target + _jsPlumb.unmakeTarget(d17); + ok(_jsPlumb.isTarget(d17) == false, "d17 is no longer a target"); + + // this should succeed, because d17 is no longer a target and so jsPlumb will just create and add a new endpoint to d17. + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + + test(renderMode + ": jsPlumb.removeEverySource and removeEveryTarget (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + _jsPlumb.makeSource(d16).makeTarget(d17).makeSource(d18); + ok(_jsPlumb.isSource(d16) == true, "d16 is a source"); + ok(_jsPlumb.isTarget(d17) == true, "d17 is a target"); + ok(_jsPlumb.isSource(d18) == true, "d18 is a source"); + + _jsPlumb.unmakeEverySource(); + _jsPlumb.unmakeEveryTarget(); + + ok(_jsPlumb.isSource(d16) == false, "d16 is no longer a source"); + ok(_jsPlumb.isTarget(d17) == false, "d17 is no longer a target"); + ok(_jsPlumb.isSource(d18) == false, "d18 is no longer a source"); + }); + +// *********************** end of makeTarget (and associated methods) tests ************************ + + test(renderMode + ': _jsPlumb.connect (between two Endpoints)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1, _jsPlumb); // no new endpoint should have been added + assertEndpointCount("d2", 1, _jsPlumb); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. + ok(c.id != null, "connection has had an id assigned"); + }); + + test(renderMode + ': _jsPlumb.connect (Endpoint connectorTooltip parameter)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1, {connectorTooltip:"FOO"}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + equals(c.connector.canvas.getAttribute("title"), "FOO", "connector canvas has label attribute set"); + }); + + test(renderMode + ': _jsPlumb.connect (tooltip parameter)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2, tooltip:"FOO"}); + equals(c.connector.canvas.getAttribute("title"), "FOO", "connector canvas has label attribute set"); + }); + + test(renderMode + ': _jsPlumb.connect (between two Endpoints, and dont supply any parameters to the Endpoints.)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1, _jsPlumb); // no new endpoint should have been added + assertEndpointCount("d2", 1, _jsPlumb); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. + }); + + test(renderMode + " : _jsPlumb.connect, passing 'anchors' array" ,function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, anchors:["LeftMiddle", "RightMiddle"]}); + + equals(c.endpoints[0].anchor.x, 0, "source anchor is at x=0"); + equals(c.endpoints[0].anchor.y, 0.5, "source anchor is at y=0.5"); + equals(c.endpoints[1].anchor.x, 1, "target anchor is at x=1"); + equals(c.endpoints[1].anchor.y, 0.5, "target anchor is at y=0.5"); + }); + + test(renderMode + ': _jsPlumb.connect (by endpoint)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + }); + + test(renderMode + ': _jsPlumb.connect (cost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17, cost:567}); + assertContextSize(3); + equals(c.getCost(), 567, "connection cost is 567"); + }); + + test(renderMode + ': _jsPlumb.connect (default cost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), undefined, "default connection cost is 1"); + }); + + test(renderMode + ': _jsPlumb.connect (set cost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), undefined, "default connection cost is 1"); + c.setCost(8989); + equals(c.getCost(), 8989, "connection cost is 8989"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (connectionCost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionCost:567}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), 567, "connection cost is 567"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (change connectionCost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionCost:567, maxConnections:-1}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1}); + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), 567, "connection cost is 567"); + e16.setConnectionCost(23); + var c2 = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(c2.getCost(), 23, "connection cost is 23 after change on endpoint"); + }); + + test(renderMode + ': _jsPlumb.connect (default bidirectional)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true}), + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.isBidirectional(), true, "default connection is bidirectional"); + }); + + test(renderMode + ': _jsPlumb.connect (bidirectional false)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true}), + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17, bidirectional:false}); + assertContextSize(3); + equals(c.isBidirectional(), false, "connection is not bidirectional"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (connectionsBidirectional)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionsBidirectional:true, maxConnections:-1}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.isBidirectional(), true, "connection is bidrectional"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (change connectionsBidirectional)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionsBidirectional:true, maxConnections:-1}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1}); + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.isBidirectional(), true, "connection is bidirectional"); + e16.setConnectionsBidirectional(false); + var c2 = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(c2.isBidirectional(), false, "connection is not bidirectional"); + }); + + test(renderMode + ": _jsPlumb.connect (two Endpoints - that have been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = _jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1, uuid:srcEndpointUuid}); + var e2 = _jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1, uuid:dstEndpointUuid}); + _jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ] }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertContextSize(3); + }); + + test(renderMode + ": _jsPlumb.connect (two Endpoints - that have not been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ], source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + var e1 = _jsPlumb.getEndpoint(srcEndpointUuid); + ok(e1 != null, "endpoint with src uuid added"); + ok(e1.canvas != null); + var e2 = _jsPlumb.getEndpoint(dstEndpointUuid); + ok(e2 != null, "endpoint with target uuid added"); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.connect (two Endpoints - that have been already added - by endpoint reference)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = _jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1}); + var e2 = _jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1}); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.connect (two elements)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.connect({ source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, straight)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, bezier, no params)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Bezier" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 150, "Bezier connector chose 150 curviness"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, bezier, curviness as int)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", { curviness:200 }] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Canvas Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 200, "Bezier connector chose 200 curviness"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, bezier, curviness as named option)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", {curviness:300}] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 300, "Bezier connector chose 300 curviness"); + }); + + test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Canvas Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as strings)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:["LeftMiddle", "RightMiddle"] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.5, conn.endpoints[0].anchor.y, "source anchor y"); + equals(1, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.5, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + + test(renderMode + ": _jsPlumb.connect (two argument method in which some data is reused across connections)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"), d19 = _addDiv("d19"); + var sharedData = { connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }; + var conn = _jsPlumb.connect({ source:d16, target:d17}, sharedData); + var conn2 = _jsPlumb.connect({ source:d18, target:d19}, sharedData); + assertContextSize(6); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 2, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(conn2.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + equals(0.3, conn2.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn2.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn2.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn2.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoint test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoints test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Dot, "Dot endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Blank Endpoint specified via 'endpoint' param)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Blank" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Blank Endpoint specified via 'endpoints' param)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Blank", "Blank" ] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Dot, "Dot endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, connector test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {}); + var e17 = _jsPlumb.addEndpoint(d17, {}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {}); + var e17 = _jsPlumb.addEndpoint(d17, {}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, anchors as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = "TopCenter", a17 = "BottomCenter"; + var e16 = _jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, 0.5, "endpoint 16 is at top center");equals(e16.anchor.y, 0, "endpoint 16 is at top center"); + equals(e17.anchor.x, 0.5, "endpoint 17 is at bottom center");equals(e17.anchor.y, 1, "endpoint 17 is at bottom center"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = [0,0.5,0,-1], a17 = [1,0.0,-1,-1]; + var e16 = _jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, a16[0]);equals(e16.anchor.y, a16[1]); + equals(e17.anchor.x, a17[0]);equals(e17.anchor.y, a17[1]); + equals(e16.anchor.getOrientation()[0], a16[2]); equals(e16.anchor.getOrientation()[1], a16[3]); + equals(e17.anchor.getOrientation()[0], a17[2]); equals(e17.anchor.getOrientation()[1], a17[3]); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchor')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchor:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:["TopCenter", "BottomCenter"]}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchors')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, default endpoint, supplied dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var anchors = [ [0.25, 0, 0, -1], [1, 0.25, 1, 0], [0.75, 1, 0, 1], [0, 0.75, -1, 0] ]; + _jsPlumb.connect({source:d1, target:d2, dynamicAnchors:anchors}); // auto connect with default endpoint and provided anchors + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.detach({source:d1, target:d2}); + // this changed in 1.3.5, because auto generated endpoints are now removed by detach. so i added the test below this one + // to check that the deleteEndpointsOnDetach flag is honoured. + assertEndpointCount("d1", 0, _jsPlumb);assertEndpointCount("d2", 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, default endpoint, supplied dynamic anchors, delete on detach false)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var anchors = [ [0.25, 0, 0, -1], [1, 0.25, 1, 0], [0.75, 1, 0, 1], [0, 0.75, -1, 0] ]; + _jsPlumb.connect({ + source:d1, + target:d2, + dynamicAnchors:anchors, + deleteEndpointsOnDetach:false + }); // auto connect with default endpoint and provided anchors + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.detach({source:d1, target:d2}); + // this changed in 1.3.5, because auto generated endpoints are now removed by detach. so i added this test + // to check that the deleteEndpointsOnDetach flag is honoured. + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, supplied endpoint and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = _jsPlumb.addEndpoint(d1, endpoint); + var e2 = _jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(3); + _jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, supplied endpoints using 'source' and 'target' (this test is identical to the one above apart from the param names), and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = _jsPlumb.addEndpoint(d1, endpoint); + var e2 = _jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + _jsPlumb.connect({source:e1, target:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(3); + _jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + " detachable parameter defaults to true on _jsPlumb.connect", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), true, "connections detachable by default"); + }); + + test(renderMode + " detachable parameter set to false on _jsPlumb.connect", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, detachable:false}); + equals(c.isDetachable(), false, "connection detachable"); + }); + + test(renderMode + " setDetachable on initially detachable connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), true, "connection initially detachable"); + c.setDetachable(false); + equals(c.isDetachable(), false, "connection not detachable"); + }); + + test(renderMode + " setDetachable on initially not detachable connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, detachable:false }); + equals(c.isDetachable(), false, "connection not initially detachable"); + c.setDetachable(true); + equals(c.isDetachable(), true, "connection now detachable"); + }); + + test(renderMode + " _jsPlumb.Defaults.ConnectionsDetachable", function() { + _jsPlumb.Defaults.ConnectionsDetachable = false; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), false, "connections not detachable by default (overrode the defaults)"); + _jsPlumb.Defaults.ConnectionsDetachable = true; + }); + + + test(renderMode + ": _jsPlumb.connect (testing for connection event callback)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var connectCallback = null, detachCallback = null; + _jsPlumb.bind("jsPlumbConnection", function(params) { + connectCallback = $.extend({}, params); + }); + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + detachCallback = $.extend({}, params); + }); + _jsPlumb.connect({source:d1, target:d2}); // auto connect with default endpoint and anchor set + ok(connectCallback != null, "connect callback was made"); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.detach({source:d1, target:d2}); + assertContextSize(2); + ok(detachCallback != null, "detach callback was made"); + }); + + test(renderMode + ": _jsPlumb.connect (setting cssClass on Connector)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2,cssClass:"CSS"}); + var has = function(clazz) { + var cn = c.connector.canvas.className, + cns = cn.constructor == String ? cn : cn.baseVal; + + return cns.indexOf(clazz) != -1; + }; + ok(has("CSS"), "custom cssClass set correctly"); + ok(has(_jsPlumb.connectorClass), "basic connector class set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (overlays, long-hand version)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = {width:40,length:40,location:0.7, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"}}; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + }); + + test(renderMode + ": _jsPlumb.connect (overlays, long-hand version, IDs specified)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + equals("aLabel", connection1.overlays[0].id); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + equals("anArrow", connection1.overlays[1].id); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays)", function() { + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays + overlays specified in connect call)", function() { + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, overlays:[ + ["Label", {id:"label"}] + ]}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("label") != null, "Label overlay created from connect call"); + }); + + test(renderMode + ": _jsPlumb.connect (default connection overlays)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + }); + + test(renderMode + ": _jsPlumb.connect (default connection overlays + overlays specified in connect call)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, overlays:[ + ["Label", {id:"label"}] + ]}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("label") != null, "Label overlay created from connect call"); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays + default connection overlays)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow2" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("arrow2") != null, "Arrow overlay created from connection defaults"); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays + default connection overlays)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow2" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, overlays:[ + ["Label", {id:"label"}] + ]}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("arrow2") != null, "Arrow overlay created from connection defaults"); + ok(c.getOverlay("label") != null, "Label overlay created from connect call"); + }); + + test(renderMode + ": _jsPlumb.connect (label overlay set using 'label')", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO" + }); + + equals(c.getLabel(), "FOO", "label is set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction, with string)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2}); + + c.setLabel("FOO"); + equals(c.getLabel(), "FOO", "label is set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction, with function)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2}); + + c.setLabel(function() { return "BAR"; }); + equals(c.getLabel()(), "BAR", "label is set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction, with params object)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2}); + + c.setLabel({ + label:"BAZ", + cssClass:"CLASSY", + location:0.9 + }); + var lo = c.getLabelOverlay(); + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "BAZ", "label overlay has correct value"); + equals(lo.getLocation(), 0.9, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction with existing label set, with params object)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO", + labelLocation:0.2 + }); + + var lo = c.getLabelOverlay(); + equals(lo.getLocation(), 0.2, "label overlay has correct location"); + + c.setLabel({ + label:"BAZ", + cssClass:"CLASSY", + location:0.9 + }); + + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "BAZ", "label overlay has correct value"); + equals(lo.getLocation(), 0.9, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (getLabelOverlay, label on connect call)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO" + }), + lo = c.getLabelOverlay(); + + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "FOO", "label overlay has correct value"); + equals(lo.getLocation(), 0.5, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (getLabelOverlay, label on connect call, location set)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO", + labelLocation:0.2 + }), + lo = c.getLabelOverlay(); + + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "FOO", "label overlay has correct value"); + equals(lo.getLocation(), 0.2, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (remove single overlay by id)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + connection1.removeOverlay("aLabel"); + equals(1, connection1.overlays.length); + equals("anArrow", connection1.overlays[0].id); + }); + + test(renderMode + ": _jsPlumb.connect (remove multiple overlays by id)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + connection1.removeOverlays("aLabel", "anArrow"); + equals(0, connection1.overlays.length); + }); + + test(renderMode + ": _jsPlumb.connect (overlays, short-hand version)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var loc = { location:0.7 }; + var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label", {label:"CONNECTION 1", location:0.3}], + ["Arrow", arrowSpec, loc] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + }); + + test(renderMode + ": _jsPlumb.connect (removeAllOverlays)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var loc = { location:0.7 }; + var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label", {label:"CONNECTION 1", location:0.3, cssClass:"PPPP"}], + ["Arrow", arrowSpec, loc] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + + connection1.removeAllOverlays(); + equals(0, connection1.overlays.length); + equals(0, $(".PPPP").length); + }); + + test(renderMode + ": _jsPlumb.connect, specify arrow overlay using string identifier only", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:["Arrow"]}); + equals(jsPlumb.Overlays[renderMode].Arrow, conn.overlays[0].constructor); + }); + + test(renderMode + ": Connection.getOverlay method, existing overlay", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("arrowOverlay"); + ok(overlay != null); + }); + + test(renderMode + ": Connection.getOverlay method, non-existent overlay", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("IDONTEXIST"); + ok(overlay == null); + }); + + test(renderMode + ": Overlay.setVisible method", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("arrowOverlay"); + ok(overlay.isVisible()); + overlay.setVisible(false); + ok(!overlay.isVisible()); + overlay.setVisible(true); + ok(overlay.isVisible()); + }); + + test(renderMode + ": _jsPlumb.connect (custom label overlay, set on Defaults, return plain DOM element)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Custom",{ id:"custom", create:function(connection) { + ok(connection != null, "we were passed in a connection"); + var d = document.createElement("div"); + d.setAttribute("custom", "true"); + d.innerHTML = connection.id; + return d; + }}] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + var o = c.getOverlay("custom"); + equals(o.getElement().getAttribute("custom"), "true", "custom overlay created correctly"); + equals(o.getElement().innerHTML, c.id, "custom overlay has correct value"); + }); + + test(renderMode + ": _jsPlumb.connect (custom label overlay, set on Defaults, return selector)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Custom",{ id:"custom", create:function(connection) { + ok(connection != null, "we were passed in a connection"); + return $("
" + connection.id + "
"); + }}] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + var o = c.getOverlay("custom"); + equals(o.getElement().getAttribute("custom"), "true", "custom overlay created correctly"); + equals(o.getElement().innerHTML, c.id, "custom overlay has correct value"); + }); + + // this test is for the original detach function; it should stay working after i mess with it + // a little. + test(renderMode + ": _jsPlumb.detach (by element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + var e3 = _jsPlumb.addEndpoint(d1); + var e4 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + _jsPlumb.connect({ sourceEndpoint:e3, targetEndpoint:e4 }); // make two connections to be sure this works ;) + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionCount(e3, 1); + assertConnectionCount(e4, 1); + + _jsPlumb.detach({source:"d1", target:"d2"}); + + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionCount(e3, 0); + assertConnectionCount(e4, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + // detach is being made to operate more like connect - by taking one argument with a whole + // bunch of possible params in it. if two args are passed in it will continue working + // in the old way. + test(renderMode + ": _jsPlumb.detach (params object, using element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({source:"d1", target:"d2"}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); +/* + test(renderMode + ": _jsPlumb.detach (params object, using target only)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e1 = _jsPlumb.addEndpoint(d1, {maxConnections:2}), + e2 = _jsPlumb.addEndpoint(d2), + e3 = _jsPlumb.addEndpoint(d3); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e3 }); + assertConnectionCount(e1, 2); + assertConnectionCount(e2, 1); + assertConnectionCount(e3, 1); + _jsPlumb.detach({target:"d2"}); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 0); + assertConnectionCount(e3, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1); + }); +*/ + test(renderMode + ": _jsPlumb.detach (params object, using element objects)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({source:d1, target:d2}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.detach (source and target as endpoint UUIDs)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {uuid:"abcdefg"}); + ok(_jsPlumb.getEndpoint("abcdefg") != null, "e1 exists"); + var e2 = _jsPlumb.addEndpoint(d2, {uuid:"hijklmn"}); + ok(_jsPlumb.getEndpoint("hijklmn") != null, "e2 exists"); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({uuids:["abcdefg", "hijklmn"]}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.detach (sourceEndpoint and targetEndpoint supplied)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.makeDynamicAnchors (longhand)", function() { + var anchors = [_jsPlumb.makeAnchor([0.2, 0, 0, -1], null, _jsPlumb), _jsPlumb.makeAnchor([1, 0.2, 1, 0], null, _jsPlumb), + _jsPlumb.makeAnchor([0.8, 1, 0, 1], null, _jsPlumb), _jsPlumb.makeAnchor([0, 0.8, -1, 0], null, _jsPlumb) ]; + var dynamicAnchor = _jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); + }); + + test(renderMode + ": _jsPlumb.makeDynamicAnchors (shorthand)", function() { + var anchors = [[0.2, 0, 0, -1], [1, 0.2, 1, 0], + [0.8, 1, 0, 1], [0, 0.8, -1, 0] ]; + var dynamicAnchor = _jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); + }); + + test(renderMode + ": Connection.isVisible/setVisible", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c1 = _jsPlumb.connect({source:d1,target:d2}); + equals(true, c1.isVisible(), "Connection is visible after creation."); + c1.setVisible(false); + equals(false, c1.isVisible(), "Connection is not visible after calling setVisible(false)."); + equals($(c1.connector.canvas).css("display"), "none"); + c1.setVisible(true); + equals(true, c1.isVisible(), "Connection is visible after calling setVisible(true)."); + equals($(c1.connector.canvas).css("display"), "block"); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible basic test (no connections)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals($(e1.canvas).css("display"), "none"); + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals($(e1.canvas).css("display"), "block"); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should track changes in the source, because it has only this connection.)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + var c1 = _jsPlumb.connect({source:e1, target:e2}); + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals(false, e2.isVisible(), "other Endpoint is not visible either."); + equals(false, c1.isVisible(), "connection between the two is not visible either."); + + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals(true, e2.isVisible(), "other Endpoint is visible too"); + equals(true, c1.isVisible(), "connection between the two is visible too."); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should not track changes in the source, because it has another connection.)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2, { maxConnections:2 }), e3 = _jsPlumb.addEndpoint(d3); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + var c1 = _jsPlumb.connect({source:e1, target:e2}); + var c2 = _jsPlumb.connect({source:e2, target:e3}); + + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals(true, e2.isVisible(), "other Endpoint should still be visible."); + equals(true, e3.isVisible(), "third Endpoint should still be visible."); + equals(false, c1.isVisible(), "connection between the two is not visible either."); + equals(true, c2.isVisible(), "other connection is visible."); + + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals(true, e2.isVisible(), "other Endpoint is visible too"); + equals(true, c1.isVisible(), "connection between the two is visible too."); + equals(true, c2.isVisible(), "other connection is visible."); + }); + + // tests of the functionality that allows a user to specify that they want elements appended to the document body + test(renderMode + " _jsPlumb.Defaults.Container, specified with a selector", function() { + _jsPlumb.Defaults.Container = $("body"); + equals($("#container")[0].childNodes.length, 0, "container has no nodes"); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals($("#container")[0].childNodes.length, 2, "container has two div elements"); // the divs we added have been added to the 'container' div. + // but we have told _jsPlumb to add its canvas to the body, so this connect call should not add another few elements to the container: + _jsPlumb.connect({source:d1, target:d2}); + equals($("#container")[0].childNodes.length, 2, "container still has two div elements"); + }); + + // tests of the functionality that allows a user to specify that they want elements appended to some specific container. + test(renderMode + " _jsPlumb.Defaults.Container, specified with DOM element", function() { + _jsPlumb.Defaults.Container = document.getElementsByTagName("body")[0]; + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length, "two divs added to the container"); // the divs we added have been added to the 'container' div. + // but we have told _jsPlumb to add its canvas to the body, so this connect call should not add another few elements to the container: + var bodyElementCount = $("body")[0].childNodes.length; + _jsPlumb.connect({source:d1, target:d2}); + equals(2, $("#container")[0].childNodes.length, "still only two children in container; elements were added to the body by _jsPlumb"); + // test to see if 3 elements have been added + equals(bodyElementCount + 3, $("body")[0].childNodes.length, "3 new elements added to the document body"); + }); + + test(renderMode + " container specified to connect call, with a selector", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length); // the divs we added have been added to the 'container' div. + var bodyElementCount = $("body")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to the body, so this connect call should not add another few elements to the container: + _jsPlumb.connect({source:d1, target:d2, container:$("body")}); + equals(2, $("#container")[0].childNodes.length); + equals(bodyElementCount + 3, $("body")[0].childNodes.length, "3 new elements added to the document body"); + }); + + test(renderMode + " container specified to connect call, with a string ID", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + equals(3, $("#container")[0].childNodes.length, "container has divs we added"); // the divs we added have been added to the 'container' div. + var d3ElementCount = $("#d3")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to "d3", so this connect call should not add another few elements to the container: + _jsPlumb.connect({source:d1, target:d2, container:"d3"}); + equals(3, $("#container")[0].childNodes.length, "container still has only the divs we added"); + equals(d3ElementCount + 3, $("#d3")[0].childNodes.length, "3 new elements added to div d3"); + }); + + test(renderMode + " container specified to addEndpoint call, with a selector", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length); // the divs we added have been added to the 'container' div. + var bodyElementCount = $("body")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to the body, so this connect call should not add another few elements to the container: + _jsPlumb.addEndpoint(d1, {container:$("body")}); + equals(2, $("#container")[0].childNodes.length); + equals(bodyElementCount + 1, $("body")[0].childNodes.length, "1 new element added to the document body"); + }); + + test(renderMode + " container specified to addEndpoint call, with a string ID", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + equals(3, $("#container")[0].childNodes.length, "container has divs we added"); // the divs we added have been added to the 'container' div. + var d3ElementCount = $("#d3")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to "d3", so this connect call should not add another few elements to the container: + _jsPlumb.addEndpoint(d1, { container:"d3" }); + equals(3, $("#container")[0].childNodes.length, "container still has only the divs we added"); + equals(d3ElementCount + 1, $("#d3")[0].childNodes.length, "1 new element added to div d3"); + }); + + test(renderMode + " detachable defaults to true when connection made between two endpoints", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2), + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection not detachable"); + }); + + test(renderMode + " connection detachable when target endpoint has connectionsDetachable set to true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2, {connectionsDetachable:true}), + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection detachable because connectionsDetachable was set on target endpoint"); + }); + + test(renderMode + " connection detachable when source endpoint has connectionsDetachable set to true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {connectionsDetachable:true}), e2 = _jsPlumb.addEndpoint(d2), + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection detachable because connectionsDetachable was set on source endpoint"); + }); + + test(renderMode + " Connector has 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.connector.type, "Bezier", "Bezier connector has type set"); + + var c2 = _jsPlumb.connect({source:d1, target:d2, connector:"Straight"}); + equals(c2.connector.type, "Straight", "Straight connector has type set"); + + var c3 = _jsPlumb.connect({source:d1, target:d2, connector:"Flowchart"}); + equals(c3.connector.type, "Flowchart", "Flowchart connector has type set"); + }); + + test(renderMode + " Endpoints have 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.endpoints[0].type, "Dot", "Dot endpoint has type set"); + + var c2 = _jsPlumb.connect({source:d1, target:d2, endpoints:["Rectangle", "Blank"]}); + equals(c2.endpoints[1].type, "Blank", "Blank endpoint has type set"); + equals(c2.endpoints[0].type, "Rectangle", "Rectangle endpoint has type set"); + }); + + test(renderMode + " Overlays have 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = _jsPlumb.connect({ + source:d1, + target:d2, + overlays:[ "Arrow", "Label", "PlainArrow", "Diamond" ] + }); + equals(c.overlays[0].type, "Arrow", "Arrow overlay has type set"); + equals(c.overlays[1].type, "Label", "Label overlay has type set"); + equals(c.overlays[2].type, "PlainArrow", "PlainArrow overlay has type set"); + equals(c.overlays[3].type, "Diamond", "Diamond overlay has type set"); + }); + + test(renderMode + " _jsPlumb.hide, original one-arg version", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + _jsPlumb.hide(d1); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(true, e1.isVisible(), "endpoint 1 is still visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + _jsPlumb.show(d1); + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + }); + + test(renderMode + " _jsPlumb.hide, two-arg version, endpoints should also be hidden", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + _jsPlumb.show(d1); // now show d1, but do not alter the endpoints. e1 should still be hidden + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + test(renderMode + " _jsPlumb.show, two-arg version, endpoints should become visible", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + _jsPlumb.show(d1, true); // now show d1, and alter the endpoints. e1 should be visible. + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(true, e1.isVisible(), "endpoint 1 is visible again."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + test(renderMode + " _jsPlumb.show, two-arg version, endpoints should become visible, but not all connections, because some other endpoints are not visible.", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e11 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + e3 = _jsPlumb.addEndpoint(d3, e), + c1 = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e11, target:e3}); + + // we now have d1 connected to both d3 and d2. we'll hide d1, and everything on d1 should be hidden. + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "connection 1 is no longer visible."); + equals(false, c2.isVisible(), "connection 2 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(false, e11.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + equals(true, e3.isVisible(), "endpoint 3 is still visible."); + + // now, we will also hide d3. making d1 visible again should NOT result in c2 becoming visible, because the other endpoint + // for c2 is e3, which is not visible. + _jsPlumb.hide(d3, true); + equals(false, e3.isVisible(), "endpoint 3 is no longer visible."); + + _jsPlumb.show(d1, true); // now show d1, and alter the endpoints. e1 should be visible, c1 should be visible, but c2 should not. + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(false, c2.isVisible(), "Connection 2 is not visible."); + equals(true, e1.isVisible(), "endpoint 1 is visible again."); + equals(true, e11.isVisible(), "endpoint 11 is visible again."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + equals(false, e3.isVisible(), "endpoint 3 is still not visible."); + }); + + /* + test(renderMode + " _jsPlumb.hide, two-arg version, endpoints should also be hidden", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + /** + * test for issue 132: label leaves its element in the DOM after it has been + * removed from a connection. + */ + test(renderMode + " label cleans itself up properly", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label", cssClass:"foo"}] + ]}); + ok($(".foo").length == 1, "label element exists in DOM"); + c.removeOverlay("label"); + ok($(".foo").length == 0, "label element does not exist in DOM"); + }); + + test(renderMode + " arrow cleans itself up properly", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Arrow", {id:"arrow"}] + ]}); + ok(c.getOverlay("arrow") != null, "arrow overlay exists"); + c.removeOverlay("arrow"); + ok(c.getOverlay("arrow") == null, "arrow overlay has been removed"); + }); + + test(renderMode + " label overlay getElement function", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label"}] + ]}); + ok(c.getOverlay("label").getElement() != null, "label overlay exposes element via getElement method"); + }); + + test(renderMode + " label overlay provides getLabel and setLabel methods", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label", label:"foo"}] + ]}); + var o = c.getOverlay("label"), e = o.getElement(); + ok(e.innerHTML == "foo", "label text is set to original value"); + o.setLabel("baz"); + equals(e.innerHTML, "baz", "label text is set to new value 'baz'"); + equals(o.getLabel(), "baz", "getLabel function works correctly with String"); + // now try functions + var aFunction = function() { return "aFunction"; }; + o.setLabel(aFunction); + equals(e.innerHTML, "aFunction", "label text is set to new value from Function"); + equals(o.getLabel(), aFunction, "getLabel function works correctly with Function"); + }); + + test(renderMode + " parameters object works for Endpoint", function() { + var d1 = _addDiv("d1"), + f = function() { alert("FOO!"); }, + e = _jsPlumb.addEndpoint(d1, { + isSource:true, + parameters:{ + "string":"param1", + "int":4, + "function":f + } + }); + ok(e.getParameter("string") === "param1", "getParameter(String) works correctly"); + ok(e.getParameter("int") === 4, "getParameter(int) works correctly"); + ok(e.getParameter("function") == f, "getParameter(Function) works correctly"); + }); + + test(renderMode + " parameters object works for Connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + f = function() { alert("FOO!"); }; + var c = _jsPlumb.connect({ + source:d1, + target:d2, + parameters:{ + "string":"param1", + "int":4, + "function":f + } + }); + ok(c.getParameter("string") === "param1", "getParameter(String) works correctly"); + ok(c.getParameter("int") === 4, "getParameter(int) works correctly"); + ok(c.getParameter("function") == f, "getParameter(Function) works correctly"); + }); + + test(renderMode + " parameters set on Endpoints and Connections are all merged, and merged correctly at that.", function() { + var d1 = _addDiv("d1"), + d2 = _addDiv("d2"), + e = _jsPlumb.addEndpoint(d1, { + isSource:true, + parameters:{ + "string":"sourceEndpoint", + "int":0, + "function":function() { return "sourceEndpoint"; } + } + }), + e2 = _jsPlumb.addEndpoint(d2, { + isTarget:true, + parameters:{ + "int":1, + "function":function() { return "targetEndpoint"; } + } + }), + c = _jsPlumb.connect({source:e, target:e2, parameters:{ + "function":function() { return "connection"; } + }}); + + ok(c.getParameter("string") === "sourceEndpoint", "getParameter(String) works correctly"); + ok(c.getParameter("int") === 1, "getParameter(int) works correctly"); + ok(c.getParameter("function")() == "connection", "getParameter(Function) works correctly"); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers standard connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2}); + equals(_jsPlumb.anchorManager.getConnectionsFor("d1").length, 1); + equals(_jsPlumb.anchorManager.getEndpointsFor("d1").length, 1); + equals(_jsPlumb.anchorManager.getConnectionsFor("d2").length, 1); + equals(_jsPlumb.anchorManager.getEndpointsFor("d2").length, 1); + var c2 = _jsPlumb.connect({source:d1, target:d2}); + equals(_jsPlumb.anchorManager.getConnectionsFor("d1").length, 2); + equals(_jsPlumb.anchorManager.getConnectionsFor("d2").length, 2); + equals(_jsPlumb.anchorManager.getEndpointsFor("d1").length, 2); + equals(_jsPlumb.anchorManager.getEndpointsFor("d2").length, 2); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers dynamic anchor connection, and removes it.", function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var c = _jsPlumb.connect({source:d3, target:d4, anchors:["AutoDefault", "AutoDefault"]}); + + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 1); + + var c2 = _jsPlumb.connect({source:d3, target:d4}); + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 2); + equals(_jsPlumb.anchorManager.getConnectionsFor("d4").length, 2); + + equals(_jsPlumb.anchorManager.getEndpointsFor("d3").length, 2); + _jsPlumb.detach(c); + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 1); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers continuous anchor connection, and removes it.", function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var c = _jsPlumb.connect({source:d3, target:d4, anchors:["Continuous", "Continuous"]}); + + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 1); + equals(_jsPlumb.anchorManager.getConnectionsFor("d4").length, 1); + + _jsPlumb.detach(c); + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 0); + equals(_jsPlumb.anchorManager.getConnectionsFor("d4").length, 0); + + _jsPlumb.reset(); + equals(_jsPlumb.anchorManager.getEndpointsFor("d4").length, 0); + }); + +// ------------- utility functions - math stuff, mostly -------------------------- + + var tolerance = 0.00000005, withinTolerance = function(v1, v2, msg) { + if (Math.abs(v1 - v2) < tolerance) ok(true, msg + "; expected " + v1 + " and got it"); + else { + ok(false, msg + "; expected " + v1 + " got " + v2); + } + }; + test(renderMode + " jsPlumbUtil.gradient, segment 1", function() { + var p1 = [2,2], p2 = [3,1], m = jsPlumbUtil.gradient(p1,p2); + equals(m, -1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.normal, segment 1", function() { + var p1 = [2,2], p2 = [3,1], m = jsPlumbUtil.normal(p1,p2); + equals(m, 1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.gradient, segment 2", function() { + var p1 = [2,2], p2 = [3,3], m = jsPlumbUtil.gradient(p1,p2); + equals(m, 1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.normal, segment 2", function() { + var p1 = [2,2], p2 = [3,3], m = jsPlumbUtil.normal(p1,p2); + equals(m, -1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.gradient, segment 3", function() { + var p1 = [2,2], p2 = [1,3], m = jsPlumbUtil.gradient(p1,p2); + equals(m, -1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.normal, segment 3", function() { + var p1 = [2,2], p2 = [1,3], m = jsPlumbUtil.normal(p1,p2); + equals(m, 1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.gradient, segment 4", function() { + var p1 = [2,2], p2 = [1,1], m = jsPlumbUtil.gradient(p1,p2); + equals(m, 1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.normal, segment 4", function() { + var p1 = [2,2], p2 = [1,1], m = jsPlumbUtil.normal(p1,p2); + equals(m, -1, "normal calculated correctly for simple case"); + }); + test(renderMode + "jsPlumbUtil.pointOnLine, segment 1", function() { + var p1 = {x:2,y:2}, p2={x:3, y:1}, + target = jsPlumbUtil.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumbUtil.pointOnLine, segment 2", function() { + var p1 = {x:2,y:2}, p2={x:3, y:3}, + target = jsPlumbUtil.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumbUtil.pointOnLine, segment 3", function() { + var p1 = {x:2,y:2}, p2={x:1, y:3}, + target = jsPlumbUtil.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumbUtil.pointOnLine, segment 4", function() { + var p1 = {x:2,y:2}, p2={x:1, y:1}, + target = jsPlumbUtil.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumbUtil.perpendicularLineTo, segment 1", function() { + var p1 = {x:2, y:2}, p2={x:3, y:1}, m = jsPlumbUtil.gradient(p1, p2), + l = jsPlumbUtil.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(4, l[0].x, "point 1 x is correct"); + withinTolerance(2, l[0].y, "point 1 y is correct"); + + withinTolerance(2, l[1].x, "point 2 x is correct"); + withinTolerance(0, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumbUtil.perpendicularLineTo, segment 2", function() { + var p1 = {x:2, y:2}, p2={x:3, y:3}, m = jsPlumbUtil.gradient(p1, p2), + l = jsPlumbUtil.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(4, l[0].x, "point 1 x is correct"); + withinTolerance(2, l[0].y, "point 1 y is correct"); + + withinTolerance(2, l[1].x, "point 2 x is correct"); + withinTolerance(4, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumbUtil.perpendicularLineTo, segment 3", function() { + var p1 = {x:2, y:2}, p2={x:1, y:3}, m = jsPlumbUtil.gradient(p1, p2), + l = jsPlumbUtil.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(2, l[0].x, "point 1 x is correct"); + withinTolerance(4, l[0].y, "point 1 y is correct"); + + withinTolerance(0, l[1].x, "point 2 x is correct"); + withinTolerance(2, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumbUtil.perpendicularLineTo, segment 4", function() { + var p1 = {x:2, y:2}, p2={x:1, y:1}, m = jsPlumbUtil.gradient(p1, p2), + l = jsPlumbUtil.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(2, l[0].x, "point 1 x is correct"); + withinTolerance(0, l[0].y, "point 1 y is correct"); + + withinTolerance(0, l[1].x, "point 2 x is correct"); + withinTolerance(2, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumbUtil.intersects, with intersection", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 3, y:4, w:3, h:3}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, with no intersection", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 13, y:4, w:3, h:3}; + + ok(!jsPlumbUtil.intersects(r1, r2), "r1 and r2 do not intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, with intersection, equal Y", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 1, y:2, w:3, h:6}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, with intersection, equal X", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 2, y:1, w:4, h:6}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, identical rectangles", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 2, y:2, w:4, h:6}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, corners touch (intersection)", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 6, y:8, w:3, h:3}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, one rectangle contained within the other", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 0, y:0, w:23, h:23}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "setImage on Endpoint", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + originalUrl = "../../img/endpointTest1.png", + e = { + endpoint:[ "Image", { src:originalUrl } ] + }, + ep = _jsPlumb.addEndpoint(d1, e); + }); + test(renderMode + "setImage on Endpoint, with supplied onload", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { + endpoint:[ "Image", { + src:"../../img/endpointTest1.png", + onload:function(imgEp) { + equals("../../img/endpointTest1.png", imgEp.img.src); + equals(ep.endpoint.canvas.getAttribute("src"), imgEp.img.src); + } + } ] + }, + ep = _jsPlumb.addEndpoint(d1, e); + ep.setImage("../../img/littledot.png", function(imgEp) { + equals("../../img/littledot.png", imgEp.img.src); + equals(ep.endpoint.canvas.getAttribute("src"), imgEp.img.src); + }); + }); + + test(renderMode + "attach endpoint to table when desired element was td", function() { + var table = document.createElement("table"), + tr = document.createElement("tr"), + td = document.createElement("td"); + table.appendChild(tr); tr.appendChild(td); + document.body.appendChild(table); + var ep = _jsPlumb.addEndpoint(td); + equals(ep.canvas.parentNode.tagName.toLowerCase(), "table"); + }); + +// issue 190 - regressions with getInstance. these tests ensure that generated ids are unique across +// instances. + + test(renderMode + " id clashes between instances", function() { + var d1 = document.createElement("div"), + d2 = document.createElement("div"), + _jsp2 = jsPlumb.getInstance(); + + document.body.appendChild(d1); + document.body.appendChild(d2); + + _jsPlumb.addEndpoint(d1); + _jsp2.addEndpoint(d2); + + var id1 = d1.getAttribute("id"), + id2 = d2.getAttribute("id"); + + var idx = id1.indexOf("_"), idx2 = id1.lastIndexOf("_"), v1 = id1.substring(idx, idx2); + var idx3 = id2.indexOf("_"), idx4 = id2.lastIndexOf("_"), v2 = id2.substring(idx3, idx4); + + ok (v1 != v2, "instance versions are different : " + v1 + " : " + v2); + }); + + test(renderMode + " id clashes between instances", function() { + var d1 = document.createElement("div"), + d2 = document.createElement("div"), + _jsp2 = jsPlumb.getInstance(); + + document.body.appendChild(d1); + document.body.appendChild(d2); + + _jsPlumb.addEndpoint(d1); + _jsPlumb.addEndpoint(d2); + + var id1 = d1.getAttribute("id"), + id2 = d2.getAttribute("id"); + + var idx = id1.indexOf("_"), idx2 = id1.lastIndexOf("_"), v1 = id1.substring(idx, idx2); + var idx3 = id2.indexOf("_"), idx4 = id2.lastIndexOf("_"), v2 = id2.substring(idx3, idx4); + + ok (v1 == v2, "instance versions are the same : " + v1 + " : " + v2); + }); + + test(renderMode + " importDefaults", function() { + _jsPlumb.Defaults.Anchors = ["LeftMiddle", "RightMiddle"]; + var d1 = _addDiv("d1"), + d2 = _addDiv(d2), + c = _jsPlumb.connect({source:d1, target:d2}), + e = c.endpoints[0]; + + equals(e.anchor.x, 0, "left middle anchor"); + equals(e.anchor.y, 0.5, "left middle anchor"); + + _jsPlumb.importDefaults({ + Anchors:["TopLeft", "TopRight"] + }); + + var conn = _jsPlumb.connect({source:d1, target:d2}), + e1 = conn.endpoints[0], e2 = conn.endpoints[1]; + + equals(e1.anchor.x, 0, "top leftanchor"); + equals(e2.anchor.y, 0, "top left anchor"); + equals(e2.anchor.x, 1, "top right anchor"); + equals(e2.anchor.y, 0, "top right anchor"); + + }); + + test(renderMode + " restoreDefaults", function() { + _jsPlumb.importDefaults({ + Anchors:["TopLeft", "TopRight"] + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), conn = _jsPlumb.connect({source:d1, target:d2}), + e1 = conn.endpoints[0], e2 = conn.endpoints[1]; + + equals(e1.anchor.x, 0, "top leftanchor"); + equals(e2.anchor.y, 0, "top left anchor"); + equals(e2.anchor.x, 1, "top right anchor"); + equals(e2.anchor.y, 0, "top right anchor"); + + _jsPlumb.restoreDefaults(); + + var conn2 = _jsPlumb.connect({source:d1, target:d2}), + e3 = conn2.endpoints[0], e4 = conn2.endpoints[1]; + + equals(e3.anchor.x, 0.5, "bottom center anchor"); + equals(e3.anchor.y, 1, "bottom center anchor"); + equals(e4.anchor.x, 0.5, "bottom center anchor"); + equals(e4.anchor.y, 1, "bottom center anchor"); + }); + +// setId function + + test(renderMode + " setId, taking two strings, only default scope", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId("d1", "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a selector and a string, only default scope", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1"), "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a DOM element and a string, only default scope", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1")[0], "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking two strings, mix of scopes", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2, scope:"FOO"}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId("d1", "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a selector and a string, mix of scopes", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2, scope:"FOO"}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1"), "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a DOM element and a string, mix of scopes", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2, scope:"FOO"}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1")[0], "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setIdChanged, ", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + $("#d1").attr("id", "d3"); + + _jsPlumb.setIdChanged("d1", "d3"); + + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " endpoint hide/show should hide/show overlays", function() { + _addDiv("d1"); + var e1 = _jsPlumb.addEndpoint("d1", { + overlays:[ + [ "Label", { id:"label", label:"foo" } ] + ] + }), + o = e1.getOverlay("label"); + + ok(o.isVisible(), "overlay is initially visible"); + _jsPlumb.hide("d1", true); + ok(!o.isVisible(), "overlay is no longer visible"); + }); + + test(renderMode + " connection hide/show should hide/show overlays", function() { + _addDiv("d1");_addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", + overlays:[ + [ "Label", { id:"label", label:"foo" } ] + ] + }), + o = c.getOverlay("label"); + + ok(o.isVisible(), "overlay is initially visible"); + _jsPlumb.hide("d1", true); + ok(!o.isVisible(), "overlay is no longer visible"); + }); + + test(renderMode + " select, basic test", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2"}), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 1, "one connection selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; dont filter on scope.", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 2, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scope", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + s = _jsPlumb.select({source:"d1", scope:"FOO"}); + + equals(s.length, 1, "one connection selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scopes", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + c3 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAZ"}) + s = _jsPlumb.select({source:"d1", scope:["FOO","BAR"]}); + + equals(s.length, 2, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; scope but no scope filter; single source id", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + c3 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAZ"}), + c4 = _jsPlumb.connect({source:"d2", target:"d1", scope:"BOZ"}), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 3, "three connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scopes; single source id", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + c3 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAZ"}), + c4 = _jsPlumb.connect({source:"d2", target:"d1", scope:"BOZ"}), + s = _jsPlumb.select({source:"d1", scope:["FOO","BAR", "BOZ"]}); + + equals(s.length, 2, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scope; dont supply sourceid", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + s = _jsPlumb.select({ scope:"FOO" }); + + equals(s.length, 1, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scope; dont supply sourceid", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d2", target:"d1", scope:"BAR"}), + s = _jsPlumb.select({ scope:"FOO" }); + + equals(s.length, 1, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, two connections, with overlays", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({ + source:"d1", + target:"d2", + overlays:[ + ["Label", {id:"l"}] + ] + }), + c2 = _jsPlumb.connect({ + source:"d1", + target:"d2", + overlays:[ + ["Label", {id:"l"}] + ] + }), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 2, "two connections selected"); + ok(s.get(0).getOverlay("l") != null, "connection has overlay"); + ok(s.get(1).getOverlay("l") != null, "connection has overlay"); + }); + + test(renderMode + " select, chaining with setHover and hideOverlay", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({ + source:"d1", + target:"d2", + overlays:[ + ["Label", {id:"l"}] + ] + }); + s = _jsPlumb.select({source:"d1"}); + + s.setHover(false).hideOverlay("l"); + + ok(!(s.get(0).isHover()), "connection is not hover"); + ok(!(s.get(0).getOverlay("l").isVisible()), "overlay is not visible"); + }); + + test(renderMode + " select, .each function", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10) + }); + } + + var s = _jsPlumb.select(); + equals(s.length, 5, "there are five connections"); + + var t = ""; + s.each(function(connection) { + t += "f"; + }); + equals("fffff", t, ".each is working"); + }); + + test(renderMode + " select, multiple connections + chaining", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + overlays:[ + ["Arrow", {location:0.3}], + ["Arrow", {location:0.7}] + ] + }); + } + + var s = _jsPlumb.select().removeAllOverlays().setParameter("foo", "bar").setHover(false).setLabel("baz"); + equals(s.length, 5, "there are five connections"); + + for (var j = 0; j < 5; j++) { + equals(s.get(j).getOverlays().length, 1, "one overlay: the label"); + equals(s.get(j).getParameter("foo"), "bar", "parameter foo has value 'bar'"); + ok(!(s.get(j).isHover()), "hover is set to false"); + equals(s.get(j).getLabel(), "baz", "label is set to 'baz'"); + } + }); + + test(renderMode + " select, simple getter", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + label:"FOO" + }); + } + + var lbls = _jsPlumb.select().getLabel(); + equals(lbls.length, 5, "there are five labels"); + + for (var j = 0; j < 5; j++) { + equals(lbls[j][0], "FOO", "label has value 'FOO'"); + } + }); + + test(renderMode + " select, getter + chaining", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + label:"FOO" + }); + } + + var params = _jsPlumb.select().removeAllOverlays().setParameter("foo", "bar").getParameter("foo"); + equals(params.length, 5, "there are five params"); + + for (var j = 0; j < 5; j++) { + equals(params[j][0], "bar", "parameter has value 'bar'"); + } + }); + + + test(renderMode + " select, detach method", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + label:"FOO" + }); + } + + var params = _jsPlumb.select().detach(); + + equals(_jsPlumb.select().length, 0, "there are no connections"); + }); + + test(renderMode + " select, repaint method", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + label:"FOO" + }); + } + + var len = _jsPlumb.select().repaint().length; + + equals(len, 5, "there are five connections"); + }); + + + // selectEndpoints + test(renderMode + " selectEndpoints, basic tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1), + e2 = _jsPlumb.addEndpoint(d1); + + equals(_jsPlumb.selectEndpoints().length, 2, "there are two endpoints"); + equals(_jsPlumb.selectEndpoints({element:"d1"}).length, 2, "there are two endpoints on d1"); + equals(_jsPlumb.selectEndpoints({element:"d2"}).length, 0, "there are 0 endpoints on d2"); + + equals(_jsPlumb.selectEndpoints({source:"d1"}).length, 0, "there are zero source endpoints on d1"); + equals(_jsPlumb.selectEndpoints({target:"d1"}).length, 0, "there are zero target endpoints on d1"); + equals(_jsPlumb.selectEndpoints({source:"d2"}).length, 0, "there are zero source endpoints on d2"); + equals(_jsPlumb.selectEndpoints({target:"d2"}).length, 0, "there are zero target endpoints on d2"); + + equals(_jsPlumb.selectEndpoints({source:"d1", scope:"FOO"}).length, 0, "there are zero source endpoints on d1 with scope FOO"); + + _jsPlumb.addEndpoint("d2", { scope:"FOO", isSource:true }); + equals(_jsPlumb.selectEndpoints({source:"d2", scope:"FOO"}).length, 1, "there is one source endpoint on d2 with scope FOO"); + + equals(_jsPlumb.selectEndpoints({element:["d2", "d1"]}).length, 3, "there are three endpoints between d2 and d1"); + }); + + test(renderMode + " selectEndpoints, basic tests, various input argument formats", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1), + e2 = _jsPlumb.addEndpoint(d1); + + equals(_jsPlumb.selectEndpoints({element:"d1"}).length, 2, "using id, there are two endpoints on d1"); + equals(_jsPlumb.selectEndpoints({element:d1}).length, 2, "using dom element, there are two endpoints on d1"); + equals(_jsPlumb.selectEndpoints({element:$("#d1")}).length, 2, "using selector, there are two endpoints on d1"); + equals(_jsPlumb.selectEndpoints({element:$(d1)}).length, 2, "using selector with dom element, there are two endpoints on d1"); + + }); + + test(renderMode + " selectEndpoints, basic tests, scope", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {scope:"FOO"}), + e2 = _jsPlumb.addEndpoint(d1); + + equals(_jsPlumb.selectEndpoints({element:"d1"}).length, 2, "using id, there are two endpoints on d1"); + equals(_jsPlumb.selectEndpoints({element:"d1", scope:"FOO"}).length, 1, "using id, there is one endpoint on d1 with scope 'FOO'"); + _jsPlumb.addEndpoint(d1, {scope:"BAR"}), + equals(_jsPlumb.selectEndpoints({element:"d1", scope:"FOO"}).length, 1, "using id, there is one endpoint on d1 with scope 'BAR'"); + equals(_jsPlumb.selectEndpoints({element:"d1", scope:["BAR", "FOO"]}).length, 2, "using id, there are two endpoints on d1 with scope 'BAR' or 'FOO'"); + }); + + test(renderMode + " selectEndpoints, isSource tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true}), + e2 = _jsPlumb.addEndpoint(d1), + e3 = _jsPlumb.addEndpoint(d2, {isSource:true}); + + equals(_jsPlumb.selectEndpoints({source:"d1"}).length, 1, "there is one source endpoint on d1"); + equals(_jsPlumb.selectEndpoints({target:"d1"}).length, 0, "there are zero target endpoints on d1"); + + equals(_jsPlumb.selectEndpoints({source:"d2"}).length, 1, "there is one source endpoint on d2"); + + equals(_jsPlumb.selectEndpoints({source:["d2", "d1"]}).length, 2, "there are two source endpoints between d1 and d2"); + }); + + test(renderMode + " selectEndpoints, isTarget tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isTarget:true}), + e2 = _jsPlumb.addEndpoint(d1), + e3 = _jsPlumb.addEndpoint(d2, {isTarget:true}); + + equals(_jsPlumb.selectEndpoints({target:"d1"}).length, 1, "there is one target endpoint on d1"); + equals(_jsPlumb.selectEndpoints({source:"d1"}).length, 0, "there are zero source endpoints on d1"); + + equals(_jsPlumb.selectEndpoints({target:"d2"}).length, 1, "there is one target endpoint on d2"); + + equals(_jsPlumb.selectEndpoints({target:["d2", "d1"]}).length, 2, "there are two target endpoints between d1 and d2"); + }); + + test(renderMode + " selectEndpoints, isSource + isTarget tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true}), + e2 = _jsPlumb.addEndpoint(d1), + e3 = _jsPlumb.addEndpoint(d1, {isSource:true}), + e4 = _jsPlumb.addEndpoint(d1, {isTarget:true}); + + equals(_jsPlumb.selectEndpoints({source:"d1"}).length, 2, "there are two source endpoints on d1"); + equals(_jsPlumb.selectEndpoints({target:"d1"}).length, 2, "there are two target endpoints on d1"); + + equals(_jsPlumb.selectEndpoints({target:"d1", source:"d1"}).length, 1, "there is one source and target endpoint on d1"); + + equals(_jsPlumb.selectEndpoints({element:"d1"}).length, 4, "there are four endpoints on d1"); + + }); + + test(renderMode + " selectEndpoints, delete endpoints", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true}); + + equals(_jsPlumb.selectEndpoints({element:"d1"}).length, 1, "there is one endpoint on d1"); + _jsPlumb.selectEndpoints({source:"d1"}).delete(); + equals(_jsPlumb.selectEndpoints({element:"d1"}).length, 0, "there are zero endpoints on d1"); + }); + + test(renderMode + " selectEndpoints, detach connections", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true}), + e2 = _jsPlumb.addEndpoint(d2, {isSource:true, isTarget:true}); + + _jsPlumb.connect({source:e1, target:e2}); + + equals(e1.connections.length, 1, "there is one connection on d1's endpoint"); + equals(e2.connections.length, 1, "there is one connection on d2's endpoint"); + + _jsPlumb.selectEndpoints({source:"d1"}).detachAll(); + + equals(e1.connections.length, 0, "there are zero connections on d1's endpoint"); + equals(e2.connections.length, 0, "there are zero connections on d2's endpoint"); + }); + + test(renderMode + " selectEndpoints, hover tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true}); + + equals(e1.isHover(), false, "hover not set"); + _jsPlumb.selectEndpoints({source:"d1"}).setHover(true); + equals(e1.isHover(), true, "hover set"); + _jsPlumb.selectEndpoints({source:"d1"}).setHover(false); + equals(e1.isHover(), false, "hover no longer set"); + }); + + test(renderMode + " selectEndpoints, setEnabled tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true}); + + equals(e1.isEnabled(), true, "endpoint is enabled"); + _jsPlumb.selectEndpoints({source:"d1"}).setEnabled(false); + equals(e1.isEnabled(), false, "endpoint not enabled"); + }); + + test(renderMode + " selectEndpoints, setEnabled tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true}); + + equals(e1.isEnabled(), true, "endpoint is enabled"); + var e = _jsPlumb.selectEndpoints({source:"d1"}).isEnabled(); + equals(e[0][0], true, "endpoint enabled"); + _jsPlumb.selectEndpoints({source:"d1"}).setEnabled(false); + e = _jsPlumb.selectEndpoints({source:"d1"}).isEnabled(); + equals(e[0][0], false, "endpoint not enabled"); + }); + +// setPaintStyle/getPaintStyle tests + + test(renderMode + " setPaintStyle", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), c = _jsPlumb.connect({source:d1, target:d2}); + c.setPaintStyle({strokeStyle:"FOO", lineWidth:999}); + equals(c.paintStyleInUse.strokeStyle, "FOO", "strokeStyle was set"); + equals(c.paintStyleInUse.lineWidth, 999, "lineWidth was set"); + + c.setHoverPaintStyle({strokeStyle:"BAZ", lineWidth:444}); + c.setHover(true); + equals(c.paintStyleInUse.strokeStyle, "BAZ", "strokeStyle was set"); + equals(c.paintStyleInUse.lineWidth, 444, "lineWidth was set"); + + equals(c.getPaintStyle().strokeStyle, "FOO", "getPaintStyle returns correct value"); + equals(c.getHoverPaintStyle().strokeStyle, "BAZ", "getHoverPaintStyle returns correct value"); + }); + +// ******************* getEndpoints ************************************************ + + test(renderMode + " getEndpoints", function() { + _addDiv("d1");_addDiv("d2"); + + _jsPlumb.addEndpoint("d1"); + _jsPlumb.addEndpoint("d2"); + _jsPlumb.addEndpoint("d1"); + + var e = _jsPlumb.getEndpoints("d1"), + e2 = _jsPlumb.getEndpoints("d2"); + equals(e.length, 2, "two endpoints on d1"); + equals(e2.length, 1, "one endpoint on d2"); + }); + +// connection type tests - types, type extension, set types, get types etc. + test(renderMode + " set connection type on existing connection", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" } + }; + + _jsPlumb.registerConnectionType("basic", basicType); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("basic"); + equals(c.getPaintStyle().lineWidth, 4, "paintStyle lineWidth is 4"); + equals(c.getPaintStyle().strokeStyle, "yellow", "paintStyle strokeStyle is yellow"); + equals(c.getHoverPaintStyle().strokeStyle, "blue", "paintStyle strokeStyle is yellow"); + equals(c.getHoverPaintStyle().lineWidth, 4, "hoverPaintStyle linewidth is 6"); + }); + + test(renderMode + " set connection type on existing connection then change type", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" } + }; + var otherType = { + connector:"Bezier", + paintStyle:{ strokeStyle:"red", lineWidth:14 }, + hoverPaintStyle:{ strokeStyle:"green" } + }; + + _jsPlumb.registerConnectionType("basic", basicType); + _jsPlumb.registerConnectionType("other", otherType); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("basic"); + equals(c.getPaintStyle().lineWidth, 4, "paintStyle lineWidth is 4"); + equals(c.getPaintStyle().strokeStyle, "yellow", "paintStyle strokeStyle is yellow"); + equals(c.getHoverPaintStyle().strokeStyle, "blue", "hoverPaintStyle strokeStyle is blue"); + equals(c.getHoverPaintStyle().lineWidth, 4, "hoverPaintStyle linewidth is 6"); + + c.setType("other"); + equals(c.getPaintStyle().lineWidth, 14, "paintStyle lineWidth is 14"); + equals(c.getPaintStyle().strokeStyle, "red", "paintStyle strokeStyle is red"); + equals(c.getHoverPaintStyle().strokeStyle, "green", "hoverPaintStyle strokeStyle is green"); + equals(c.getHoverPaintStyle().lineWidth, 14, "hoverPaintStyle linewidth is 14"); + }); + + test(renderMode + " set connection type on existing connection, overlays should be set", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }; + + _jsPlumb.registerConnectionType("basic", basicType); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("basic"); + equals(c.getOverlays().length, 1, "one overlay"); + }); + + test(renderMode + " set connection type on existing connection, overlays should be removed with second type", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }; + var otherType = { + connector:"Bezier" + }; + _jsPlumb.registerConnectionType("basic", basicType); + _jsPlumb.registerConnectionType("other", otherType); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("basic"); + equals(c.getOverlays().length, 1, "one overlay"); + c.setType("other"); + equals(c.getOverlays().length, 0, "no overlays"); + equals(c.getPaintStyle().lineWidth, _jsPlumb.Defaults.PaintStyle.lineWidth, "paintStyle lineWidth is default"); + }); + + test(renderMode + " set connection type on existing connection, hasType + toggleType", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }; + + _jsPlumb.registerConnectionTypes({ + "basic": basicType + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("basic"); + equals(c.hasType("basic"), true, "connection has 'basic' type"); + c.toggleType("basic"); + equals(c.hasType("basic"), false, "connection does not have 'basic' type"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + c.toggleType("basic"); + equals(c.hasType("basic"), true, "connection has 'basic' type"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + equals(c.getOverlays().length, 1, "one overlay"); + + }); + + test(renderMode + " set connection type on existing connection, merge tests", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }; + // this tests all three merge types: connector should overwrite, linewidth should be inserted into + // basic type's params, and arrow overlay should be added to list to end up with two overlays + var otherType = { + connector:"Bezier", + paintStyle:{ lineWidth:14 }, + overlays:[ + ["Arrow", {location:0.25}] + ] + }; + _jsPlumb.registerConnectionTypes({ + "basic": basicType, + "other": otherType + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("basic"); + equals(c.hasType("basic"), true, "connection has 'basic' type"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + equals(c.getPaintStyle().lineWidth, 4, "connection has linewidth 4"); + equals(c.getOverlays().length, 1, "one overlay"); + + c.addType("other"); + equals(c.hasType("basic"), true, "connection has 'basic' type"); + equals(c.hasType("other"), true, "connection has 'other' type"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + equals(c.getPaintStyle().lineWidth, 14, "connection has linewidth 14"); + equals(c.getOverlays().length, 2, "two overlays"); + + c.removeType("basic"); + equals(c.hasType("basic"), false, "connection does not have 'basic' type"); + equals(c.hasType("other"), true, "connection has 'other' type"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + equals(c.getPaintStyle().lineWidth, 14, "connection has linewidth 14"); + equals(c.getOverlays().length, 1, "one overlay"); + + c.toggleType("other"); + equals(c.hasType("other"), false, "connection does not have 'other' type"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + equals(c.getPaintStyle().lineWidth, _jsPlumb.Defaults.PaintStyle.lineWidth, "connection has default linewidth"); + equals(c.getOverlays().length, 0, "nooverlays"); + }); + + test(renderMode + " connection type tests, space separated arguments", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }; + // this tests all three merge types: connector should overwrite, linewidth should be inserted into + // basic type's params, and arrow overlay should be added to list to end up with two overlays + var otherType = { + connector:"Bezier", + paintStyle:{ lineWidth:14 }, + overlays:[ + ["Arrow", {location:0.25}] + ] + }; + _jsPlumb.registerConnectionTypes({ + "basic": basicType, + "other": otherType + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("basic other"); + equals(c.hasType("basic"), true, "connection has 'basic' type"); + equals(c.hasType("other"), true, "connection has 'other' type"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + equals(c.getPaintStyle().lineWidth, 14, "connection has linewidth 14"); + equals(c.getOverlays().length, 2, "two overlays"); + + c.toggleType("other basic"); + equals(c.hasType("basic"), false, "after toggle, connection does not have 'basic' type"); + equals(c.hasType("other"), false, "after toggle, connection does not have 'other' type"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "after toggle, connection has default stroke style"); + equals(c.getPaintStyle().lineWidth, _jsPlumb.Defaults.PaintStyle.lineWidth, "after toggle, connection has default linewidth"); + equals(c.getOverlays().length, 0, "after toggle, no overlays"); + + c.toggleType("basic other"); + equals(c.hasType("basic"), true, "after toggle again, connection has 'basic' type"); + equals(c.hasType("other"), true, "after toggle again, connection has 'other' type"); + equals(c.getPaintStyle().strokeStyle, "yellow", "after toggle again, connection has yellow stroke style"); + equals(c.getPaintStyle().lineWidth, 14, "after toggle again, connection has linewidth 14"); + equals(c.getOverlays().length, 2, "after toggle again, two overlays"); + + c.removeType("other basic"); + equals(c.hasType("basic"), false, "after remove, connection does not have 'basic' type"); + equals(c.hasType("other"), false, "after remove, connection does not have 'other' type"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "after remove, connection has default stroke style"); + equals(c.getPaintStyle().lineWidth, _jsPlumb.Defaults.PaintStyle.lineWidth, "after remove, connection has default linewidth"); + equals(c.getOverlays().length, 0, "after remove, no overlays"); + + c.addType("other basic"); + equals(c.hasType("basic"), true, "after add, connection has 'basic' type"); + equals(c.hasType("other"), true, "after add, connection has 'other' type"); + equals(c.getPaintStyle().strokeStyle, "yellow", "after add, connection has yellow stroke style"); + // NOTE here we added the types in the other order to before, so lineWidth 4 - from basic - should win. + equals(c.getPaintStyle().lineWidth, 4, "after add, connection has linewidth 4"); + equals(c.getOverlays().length, 2, "after add, two overlays"); + }); + + test(renderMode + " connection type tests, fluid interface", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }; + // this tests all three merge types: connector should overwrite, linewidth should be inserted into + // basic type's params, and arrow overlay should be added to list to end up with two overlays + var otherType = { + connector:"Bezier", + paintStyle:{ lineWidth:14 }, + overlays:[ + ["Arrow", {location:0.25}] + ] + }; + _jsPlumb.registerConnectionTypes({ + "basic": basicType, + "other": otherType + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + c = _jsPlumb.connect({source:d1, target:d2}), + c2 = _jsPlumb.connect({source:d2, target:d3}); + + _jsPlumb.select().addType("basic"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + equals(c2.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + + _jsPlumb.select().toggleType("basic"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + equals(c2.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + + _jsPlumb.select().addType("basic"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + equals(c2.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + + _jsPlumb.select().removeType("basic").addType("other"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + equals(c2.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + + + }); + + test(renderMode + " setType when null", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType(null); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + + }); + + test(renderMode + " setType to unknown type", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("foo"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + + }); + + test(renderMode + " setType to mix of known and unknown types", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + c = _jsPlumb.connect({source:d1, target:d2}); + + _jsPlumb.registerConnectionType("basic", { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }); + + c.setType("basic foo"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has basic type's stroke style"); + + c.toggleType("foo"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has basic type's stroke style"); + + c.removeType("basic baz"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + + c.addType("basic foo bar baz"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has basic type's stroke style"); + + }); + + test(renderMode + " create connection using type parameter", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + + _jsPlumb.Defaults.PaintStyle = {strokeStyle:"blue", lineWidth:34}; + + _jsPlumb.registerConnectionTypes({ + "basic": { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }, + "other":{ + paintStyle:{ lineWidth:14 } + } + }); + + equals(_jsPlumb.Defaults.PaintStyle.strokeStyle, "blue", "default value has not been messed up"); + + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + + c = _jsPlumb.connect({source:d1, target:d2, type:"basic other"}); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has basic type's stroke style"); + equals(c.getPaintStyle().lineWidth, 14, "connection has other type's lineWidth"); + + }); + + test(renderMode + " setType, scope", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + c = _jsPlumb.connect({source:d1, target:d2}); + + _jsPlumb.registerConnectionType("basic", { + connector:"Flowchart", + scope:"BANANA", + detachable:false, + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }); + + _jsPlumb.Defaults.ConnectionsDetachable = true;//just make sure we've setup the test correctly. + + c.setType("basic"); + equals(c.scope, "BANANA", "scope is correct"); + equals(c.isDetachable(), false, "not detachable"); + + }); + + test(renderMode + " setType, parameters", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + + _jsPlumb.registerConnectionType("basic", { + parameters:{ + foo:1, + bar:2, + baz:6785962437582 + } + }); + + _jsPlumb.registerConnectionType("frank", { + parameters:{ + bar:5 + } + }); + + // first try creating one with the parameters + c = _jsPlumb.connect({source:d1, target:d2, type:"basic"}); + + equals(c.getParameter("foo"), 1, "foo param correct"); + equals(c.getParameter("bar"), 2, "bar param correct"); + + c.addType("frank"); + equals(c.getParameter("foo"), 1, "foo param correct"); + equals(c.getParameter("bar"), 5, "bar param correct"); + }); + + test(renderMode + " setType, scope, two types", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + c = _jsPlumb.connect({source:d1, target:d2}); + + _jsPlumb.registerConnectionType("basic", { + connector:"Flowchart", + scope:"BANANA", + detachable:false, + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }); + + _jsPlumb.registerConnectionType("frank", { + scope:"OVERRIDE", + detachable:true + }); + + _jsPlumb.Defaults.ConnectionsDetachable = true;//just make sure we've setup the test correctly. + + c.setType("basic frank"); + equals(c.scope, "OVERRIDE", "scope is correct"); + equals(c.isDetachable(), true, "detachable"); + + }); + + test(renderMode + " create connection from Endpoints - type should be passed through.", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e1 = _jsPlumb.addEndpoint(d1, { + connectionType:"basic" + }), + e2 = _jsPlumb.addEndpoint(d2, { + connectionType:"basic" + }); + + _jsPlumb.registerConnectionTypes({ + "basic": { + connector:"Flowchart", + paintStyle:{ strokeStyle:"blue", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"red" }, + overlays:[ + "Arrow" + ] + }, + "other":{ + paintStyle:{ lineWidth:14 } + } + }); + + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.getPaintStyle().strokeStyle, "blue", "connection has default stroke style"); + equal(c.connector.type, "Flowchart", "connector is flowchart"); + }); + + test(renderMode + " simple Endpoint type tests.", function() { + _jsPlumb.registerEndpointType("basic", { + paintStyle:{fillStyle:"blue"} + }); + + var d = _addDiv('d1'), e = _jsPlumb.addEndpoint(d); + e.setType("basic"); + equals(e.getPaintStyle().fillStyle, "blue", "fill style is correct"); + + var d2 = _addDiv('d2'), e2 = _jsPlumb.addEndpoint(d2, {type:"basic"}); + equals(e2.getPaintStyle().fillStyle, "blue", "fill style is correct"); + }); + + test(renderMode + " create connection from Endpoints - with connector settings in Endpoint type.", function() { + + _jsPlumb.registerEndpointTypes({ + "basic": { + connector:"Flowchart", + connectorOverlays:[ + "Arrow" + ], + connectorStyle:{strokeStyle:"green" }, + connectorHoverStyle:{lineWidth:534 }, + paintStyle:{ fillStyle:"blue" }, + hoverPaintStyle:{ strokeStyle:"red" }, + overlays:[ + "Arrow" + ] + }, + "other":{ + paintStyle:{ fillStyle:"red" } + } + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e1 = _jsPlumb.addEndpoint(d1, { + type:"basic" + }), + e2 = _jsPlumb.addEndpoint(d2); + + c = _jsPlumb.connect({source:e1, target:e2}); + equals(e1.getPaintStyle().fillStyle, "blue", "endpoint has fill style specified in Endpoint type"); + equals(c.getPaintStyle().strokeStyle, "green", "connection has stroke style specified in Endpoint type"); + equals(c.getHoverPaintStyle().lineWidth, 534, "connection has hover style specified in Endpoint type"); + equals(c.connector.type, "Flowchart", "connector is Flowchart"); + equals(c.overlays.length, 1, "connector has one overlay"); + equals(e1.overlays.length, 1, "endpoint has one overlay"); + }); + + test(renderMode + " create connection from Endpoints - type should be passed through.", function() { + + _jsPlumb.registerConnectionTypes({ + "basic": { + connector:"Flowchart", + paintStyle:{ strokeStyle:"bazona", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"red" }, + overlays:[ + "Arrow" + ] + } + }); + + _jsPlumb.registerEndpointType("basic", { + connectionType:"basic", + paintStyle:{fillStyle:"GAZOODA"} + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e1 = _jsPlumb.addEndpoint(d1, { + type:"basic" + }), + e2 = _jsPlumb.addEndpoint(d2); + + c = _jsPlumb.connect({source:e1, target:e2}); + equals(e1.getPaintStyle().fillStyle, "GAZOODA", "endpoint has correct paint style, from type."); + equals(c.getPaintStyle().strokeStyle, "bazona", "connection has paint style from connection type, as specified in endpoint type. sweet!"); + equal(c.connector.type, "Flowchart", "connector is flowchart - this also came from connection type as specified by endpoint type."); + }); + + + /** + * leave this test at the bottom! + */ + test(renderMode + ': unload test', function() { + _jsPlumb.unload(); + var contextNode = $(".__jsPlumb_context"); + ok(contextNode.length == 0, 'context node unloaded'); + }); +}; + diff --git a/archive/1.3.13/jsPlumb-connectors-statemachine-1.3.13-RC1.js b/archive/1.3.13/jsPlumb-connectors-statemachine-1.3.13-RC1.js new file mode 100644 index 000000000..f086dda3f --- /dev/null +++ b/archive/1.3.13/jsPlumb-connectors-statemachine-1.3.13-RC1.js @@ -0,0 +1,469 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.13 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the state machine connectors. + * + * Thanks to Brainstorm Mobile Solutions for supporting the development of these. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + var Line = function(x1, y1, x2, y2) { + + this.m = (y2 - y1) / (x2 - x1); + this.b = -1 * ((this.m * x1) - y1); + + this.rectIntersect = function(x,y,w,h) { + var results = []; + + // try top face + // the equation of the top face is y = (0 * x) + b; y = b. + var xInt = (y - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try right face + var yInt = (this.m * (x + w)) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + // bottom face + var xInt = ((y + h) - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try left face + var yInt = (this.m * x) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + if (results.length == 2) { + var midx = (results[0][0] + results[1][0]) / 2, midy = (results[0][1] + results[1][1]) / 2; + results.push([ midx,midy ]); + // now calculate the segment inside the rectangle where the midpoint lies. + var xseg = midx <= x + (w / 2) ? -1 : 1, + yseg = midy <= y + (h / 2) ? -1 : 1; + results.push([xseg, yseg]); + return results; + } + + return null; + + }; + }, + _segment = function(x1, y1, x2, y2) { + if (x1 <= x2 && y2 <= y1) return 1; + else if (x1 <= x2 && y1 <= y2) return 2; + else if (x2 <= x1 && y2 >= y1) return 3; + return 4; + }, + + // the control point we will use depends on the faces to which each end of the connection is assigned, specifically whether or not the + // two faces are parallel or perpendicular. if they are parallel then the control point lies on the midpoint of the axis in which they + // are parellel and varies only in the other axis; this variation is proportional to the distance that the anchor points lie from the + // center of that face. if the two faces are perpendicular then the control point is at some distance from both the midpoints; the amount and + // direction are dependent on the orientation of the two elements. 'seg', passed in to this method, tells you which segment the target element + // lies in with respect to the source: 1 is top right, 2 is bottom right, 3 is bottom left, 4 is top left. + // + // sourcePos and targetPos are arrays of info about where on the source and target each anchor is located. their contents are: + // + // 0 - absolute x + // 1 - absolute y + // 2 - proportional x in element (0 is left edge, 1 is right edge) + // 3 - proportional y in element (0 is top edge, 1 is bottom edge) + // + _findControlPoint = function(midx, midy, segment, sourceEdge, targetEdge, dx, dy, distance, proximityLimit) { + + // TODO (maybe) + // - if anchor pos is 0.5, make the control point take into account the relative position of the elements. + if (distance <= proximityLimit) return [midx, midy]; + + if (segment == 1) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 2) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 3) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 4) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + }; + + /** + * Class: Connectors.StateMachine + * Provides 'state machine' connectors. + */ + /* + * Function: Constructor + * + * Parameters: + * curviness - measure of how "curvy" the connectors will be. this is translated as the distance that the + * Bezier curve's control point is from the midpoint of the straight line connecting the two + * endpoints, and does not mean that the connector is this wide. The Bezier curve never reaches + * its control points; they act as gravitational masses. defaults to 10. + * margin - distance from element to start and end connectors, in pixels. defaults to 5. + * proximityLimit - sets the distance beneath which the elements are consider too close together to bother + * with fancy curves. by default this is 80 pixels. + * loopbackRadius - the radius of a loopback connector. optional; defaults to 25. + * showLoopback - If set to false this tells the connector that it is ok to paint connections whose source and target is the same element with a connector running through the element. The default value for this is true; the connector always makes a loopback connection loop around the element rather than passing through it. + */ + jsPlumb.Connectors.StateMachine = function(params) { + var self = this, + currentPoints = null, + _sx, _sy, _tx, _ty, _controlPoint = [], + curviness = params.curviness || 10, + margin = params.margin || 5, + proximityLimit = params.proximityLimit || 80, + clockwise = params.orientation && params.orientation == "clockwise", + loopbackRadius = params.loopbackRadius || 25, + isLoopback = false, + showLoopback = params.showLoopback !== false; + + this.type = "StateMachine"; + params = params || {}; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *= 1.9; + //ensure at least one pixel width + lineWidth = lineWidth || 1; + var x = Math.min(sourcePos[0], targetPos[0]) - xo, + y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (!showLoopback || (sourceEndpoint.elementId != targetEndpoint.elementId)) { + + isLoopback = false; + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + + // now adjust for the margin + if (sourcePos[2] == 0) _sx -= margin; + if (sourcePos[2] == 1) _sx += margin; + if (sourcePos[3] == 0) _sy -= margin; + if (sourcePos[3] == 1) _sy += margin; + if (targetPos[2] == 0) _tx -= margin; + if (targetPos[2] == 1) _tx += margin; + if (targetPos[3] == 0) _ty -= margin; + if (targetPos[3] == 1) _ty += margin; + + // + // these connectors are quadratic bezier curves, having a single control point. if both anchors + // are located at 0.5 on their respective faces, the control point is set to the midpoint and you + // get a straight line. this is also the case if the two anchors are within 'proximityLimit', since + // it seems to make good aesthetic sense to do that. outside of that, the control point is positioned + // at 'curviness' pixels away along the normal to the straight line connecting the two anchors. + // + // there may be two improvements to this. firstly, we might actually support the notion of avoiding nodes + // in the UI, or at least making a good effort at doing so. if a connection would pass underneath some node, + // for example, we might increase the distance the control point is away from the midpoint in a bid to + // steer it around that node. this will work within limits, but i think those limits would also be the likely + // limits for, once again, aesthetic good sense in the layout of a chart using these connectors. + // + // the second possible change is actually two possible changes: firstly, it is possible we should gradually + // decrease the 'curviness' as the distance between the anchors decreases; start tailing it off to 0 at some + // point (which should be configurable). secondly, we might slightly increase the 'curviness' for connectors + // with respect to how far their anchor is from the center of its respective face. this could either look cool, + // or stupid, and may indeed work only in a way that is so subtle as to have been a waste of time. + // + + var _midx = (_sx + _tx) / 2, _midy = (_sy + _ty) / 2, + m2 = (-1 * _midx) / _midy, theta2 = Math.atan(m2), + dy = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.sin(theta2)), + dx = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.cos(theta2)), + segment = _segment(_sx, _sy, _tx, _ty), + distance = Math.sqrt(Math.pow(_tx - _sx, 2) + Math.pow(_ty - _sy, 2)); + + // calculate the control point. this code will be where we'll put in a rudimentary element avoidance scheme; it + // will work by extending the control point to force the curve to be, um, curvier. + _controlPoint = _findControlPoint(_midx, + _midy, + segment, + sourcePos, + targetPos, + curviness, curviness, + distance, + proximityLimit); + + + var requiredWidth = Math.max(Math.abs(_controlPoint[0] - _sx) * 3, Math.abs(_controlPoint[0] - _tx) * 3, Math.abs(_tx-_sx), 2 * lineWidth, minWidth), + requiredHeight = Math.max(Math.abs(_controlPoint[1] - _sy) * 3, Math.abs(_controlPoint[1] - _ty) * 3, Math.abs(_ty-_sy), 2 * lineWidth, minWidth); + + if (w < requiredWidth) { + var dw = requiredWidth - w; + x -= (dw / 2); + _sx += (dw / 2); + _tx += (dw / 2); + w = requiredWidth; + _controlPoint[0] += (dw / 2); + } + + if (h < requiredHeight) { + var dh = requiredHeight - h; + y -= (dh / 2); + _sy += (dh / 2); + _ty += (dh / 2); + h = requiredHeight; + _controlPoint[1] += (dh / 2); + } + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty, _controlPoint[0], _controlPoint[1] ]; + } + else { + isLoopback = true; + // a loopback connector. draw an arc from one anchor to the other. + // i guess we'll do this the same way as the others. just the control point will be a fair distance away. + var x1 = sourcePos[0], x2 = sourcePos[0], y1 = sourcePos[1] - margin, y2 = sourcePos[1] - margin, + cx = x1, cy = y1 - loopbackRadius; + + // canvas sizing stuff, to ensure the whole painted area is visible. + w = ((2 * lineWidth) + (4 * loopbackRadius)), h = ((2 * lineWidth) + (4 * loopbackRadius)); + x = cx - loopbackRadius - lineWidth - loopbackRadius, y = cy - loopbackRadius - lineWidth - loopbackRadius; + currentPoints = [ x, y, w, h, cx-x, cy-y, loopbackRadius, clockwise, x1-x, y1-y, x2-x, y2-y]; + } + + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_tx, y:_ty }, + { x:_controlPoint[0], y:_controlPoint[1] }, + { x:_controlPoint[0] + 1, y:_controlPoint[1] + 1}, + { x:_sx, y:_sy } + ]; + }; + + var _translateLocation = function(curve, location, absolute) { + if (absolute) + location = jsBezier.locationAlongCurveFrom(curve, location > 0 ? 0 : 1, location); + + return location; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location, absolute) { + if (isLoopback) { + if (absolute) { + var circumference = Math.PI * 2 * loopbackRadius; + location = location / circumference; + } + + if (location > 0 && location < 1) location = 1 - location; + +// current points are [ x, y, width, height, center x, center y, radius, clockwise, startx, starty, endx, endy ] + // so the path length is the circumference of the circle + //var len = 2 * Math.PI * currentPoints[6], + // map 'location' to an angle. 0 is PI/2 when the connector is on the top face; if we + // support other faces it will have to be calculated for each one. 1 is also PI/2. + // 0.5 is -PI/2. + var startAngle = (location * 2 * Math.PI) + (Math.PI / 2), + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + + } + else { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.pointOnCurve(c, location); + } + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location, absolute) { + if (isLoopback) { + // todo if absolute, location is a proportion of circumference + if (absolute) { + var circumference = Math.PI * 2 * loopbackRadius; + location = location / circumference; + } + + return Math.atan(location * 2 * Math.PI); + } + else { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.gradientAtPoint(c, location); + } + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance, absolute) { + if (isLoopback) { + + if (absolute) { + var circumference = Math.PI * 2 * loopbackRadius; + location = location / circumference; + } + + if (location > 0 && location < 1) location = 1 - location; + + var circumference = 2 * Math.PI * currentPoints[6], + arcSpan = distance / circumference * 2 * Math.PI, + startAngle = (location * 2 * Math.PI) - arcSpan + (Math.PI / 2), + + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + } + else { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.pointAlongCurveFrom(c, location, distance); + } + }; + + }; + + /* + * Canvas state machine renderer. + */ + jsPlumb.Connectors.canvas.StateMachine = function(params) { + params = params || {}; + var self = this, drawGuideline = params.drawGuideline || true, avoidSelector = params.avoidSelector; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.CanvasConnector.apply(this, arguments); + + + this._paint = function(dimensions) { + + if (dimensions.length == 10) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[8], dimensions[9], dimensions[6], dimensions[7]); + self.ctx.stroke(); + } + else { + // a loopback connector + self.ctx.save(); + self.ctx.beginPath(); + var startAngle = 0, // Starting point on circle + endAngle = 2 * Math.PI, // End point on circle + clockwise = dimensions[7]; // clockwise or anticlockwise + self.ctx.arc(dimensions[4],dimensions[5],dimensions[6],0, endAngle, clockwise); + self.ctx.stroke(); + self.ctx.closePath(); + self.ctx.restore(); + } + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + /* + * SVG State Machine renderer + */ + jsPlumb.Connectors.svg.StateMachine = function() { + var self = this; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.SvgConnector.apply(this, arguments); + this.getPath = function(d) { + + if (d.length == 10) + return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[8] + " " + d[9] + " " + d[6] + " " + d[7]; + else { + // loopback + return "M" + (d[8] + 4) + " " + d[9] + " A " + d[6] + " " + d[6] + " 0 1,0 " + (d[8]-4) + " " + d[9]; + } + }; + }; + + /* + * VML state machine renderer + */ + jsPlumb.Connectors.vml.StateMachine = function() { + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.VmlConnector.apply(this, arguments); + var _conv = jsPlumb.vml.convertValue; + this.getPath = function(d) { + if (d.length == 10) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + } + else { + // loopback + var left = _conv(d[8] - d[6]), + top = _conv(d[9] - (2 * d[6])), + right = left + _conv(2 * d[6]), + bottom = top + _conv(2 * d[6]), + posString = left + "," + top + "," + right + "," + bottom; + + var o = "ar " + posString + "," + _conv(d[8]) + "," + + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + " e"; + + return o; + } + }; + }; + +})(); + +/* + // now for a rudimentary avoidance scheme. TODO: how to set this in a cross-library way? + // if (avoidSelector) { + // var testLine = new Line(sourcePos[0] + _sx,sourcePos[1] + _sy,sourcePos[0] + _tx,sourcePos[1] + _ty); + // var sel = jsPlumb.getSelector(avoidSelector); + // for (var i = 0; i < sel.length; i++) { + // var id = jsPlumb.getId(sel[i]); + // if (id != sourceEndpoint.elementId && id != targetEndpoint.elementId) { + // o = jsPlumb.getOffset(id), s = jsPlumb.getSize(id); +// +// if (o && s) { +// var collision = testLine.rectIntersect(o.left,o.top,s[0],s[1]); +// if (collision) { + // set the control point to be a certain distance from the midpoint of the two points that + // the line crosses on the rectangle. + // TODO where will this 75 number come from? + // _controlX = collision[2][0] + (75 * collision[3][0]); + // / _controlY = collision[2][1] + (75 * collision[3][1]); +// } +// } + // } + // } + //} + */ \ No newline at end of file diff --git a/archive/1.3.13/jsPlumb-defaults-1.3.13-RC1.js b/archive/1.3.13/jsPlumb-defaults-1.3.13-RC1.js new file mode 100644 index 000000000..1a5410128 --- /dev/null +++ b/archive/1.3.13/jsPlumb-defaults-1.3.13-RC1.js @@ -0,0 +1,1256 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.13 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the default Connectors, Endpoint and Overlay definitions. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +(function() { + + /** + * + * Helper class to consume unused mouse events by components that are DOM elements and + * are used by all of the different rendering modes. + * + */ + jsPlumb.DOMElementComponent = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + // when render mode is canvas, these functions may be called by the canvas mouse handler. + // this component is safe to pipe this stuff to /dev/null. + this.mousemove = + this.dblclick = + this.click = + this.mousedown = + this.mouseup = function(e) { }; + }; + + /** + * Class: Connectors.Straight + * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters. + */ + jsPlumb.Connectors.Straight = function() { + this.type = "Straight"; + var self = this, + currentPoints = null, + _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty, _segment, _length; + + /** + * Computes the new size and position of the canvas. + */ + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + _dx = _tx - _sx, _dy = _ty - _sy; + //_m = _dy / _dx, _m2 = -1 / _m; + _m = jsPlumbUtil.gradient({x:_sx, y:_sy}, {x:_tx, y:_ty}), _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + //_segment = jsPlumbUtil.segment({x:_sx, y:_sy}, {x:_tx, y:_ty}); + _length = Math.sqrt((_dx * _dx) + (_dy * _dy)); + + return currentPoints; + }; + + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location, absolute) { + if (location == 0 && !absolute) + return { x:_sx, y:_sy }; + else if (location == 1 && !absolute) + return { x:_tx, y:_ty }; + else { + var l = absolute ? location > 0 ? location : _length + location : location * _length; + return jsPlumbUtil.pointOnLine({x:_sx, y:_sy}, {x:_tx, y:_ty}, l); + } + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { + return _m; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + * this hands off to jsPlumbUtil to do the maths, supplying two points and the distance. + */ + this.pointAlongPathFrom = function(location, distance, absolute) { + var p = self.pointOnPath(location, absolute), + farAwayPoint = location == 1 ? { + x:_sx + ((_tx - _sx) * 10), + y:_sy + ((_sy - _ty) * 10) + } : {x:_tx, y:_ty }; + + return jsPlumbUtil.pointOnLine(p, farAwayPoint, distance); + }; + }; + + + /** + * Class:Connectors.Bezier + * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's + * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below. + */ + /** + * Function:Constructor + * + * Parameters: + * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line. + * Optional; defaults to 150. + * stub - optional value for a distance to travel from the connector's endpoint before beginning the Bezier curve. defaults to 0. + * + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + params = params || {}; + this.majorAnchor = params.curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + this.type = "Bezier"; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(sourceEndpoint), + too = targetAnchor.getOrientation(targetEndpoint), + perpendicular = soo[0] != too[0] || soo[1] == too[1], + p = [], + ma = self.majorAnchor, mi = self.minorAnchor; + + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h, _sStubX, _sStubY, _tStubX, _tStubY; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetEndpoint, sourceEndpoint, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx), minx2 = Math.min(_CP[0], _CP2[0]), minx = Math.min(minx1,minx2), + maxx1 = Math.max(_sx,_tx), maxx2 = Math.max(_CP[0], _CP2[0]), maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty), miny2 = Math.min(_CP[1], _CP2[1]), miny = Math.min(miny1,miny2), + maxy1 = Math.max(_sy,_ty), maxy2 = Math.max(_CP[1], _CP2[1]), maxy = Math.max(maxy1,maxy2); + + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, + _sx, _sy, _tx, _ty, + _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + var _translateLocation = function(curve, location, absolute) { + if (absolute) + location = jsBezier.locationAlongCurveFrom(curve, location > 0 ? 0 : 1, location); + + return location; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location, absolute) { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.pointOnCurve(c, location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location, absolute) { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.gradientAtPoint(c, location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance, absolute) { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.pointAlongCurveFrom(c, location, distance); + }; + }; + + + /** + * Class: Connectors.Flowchart + * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments. + */ + /** + * Function: Constructor + * + * Parameters: + * stub - minimum length for the stub at each end of the connector. This can be an integer, giving a value for both ends of the connections, + * or an array of two integers, giving separate values for each end. The default is an integer with value 30 (pixels). + * gap - gap to leave between the end of the connector and the element on which the endpoint resides. if you make this larger than stub then you will see some odd looking behaviour. defaults to 0 pixels. + */ + jsPlumb.Connectors.Flowchart = function(params) { + this.type = "Flowchart"; + params = params || {}; + var self = this, + stub = params.stub || params.minStubLength /* bwds compat. */ || 30, + sourceStub = jsPlumbUtil.isArray(stub) ? stub[0] : stub, + targetStub = jsPlumbUtil.isArray(stub) ? stub[1] : stub, + gap = params.gap || 0, + segments = [], + totalLength = 0, + segmentProportions = [], + segmentProportionalLengths = [], + points = [], + swapX, swapY, + maxX = -Infinity, maxY = -Infinity, + minX = Infinity, minY = Infinity, + grid = params.grid, + _gridClamp = function(n, g) { var e = n % g, f = Math.floor(n / g), inc = e > (g / 2) ? 1 : 0; return (f + inc) * g; }, + clampToGrid = function(x, y, dontClampX, dontClampY) { + return [ + dontClampX || grid == null ? x : _gridClamp(x, grid[0]), + dontClampY || grid == null ? y : _gridClamp(y, grid[1]) + ]; + }, + /** + * recalculates the points at which the segments begin and end, proportional to the total length travelled + * by all the segments that constitute the connector. we use this to help with pointOnPath calculations. + */ + updateSegmentProportions = function(startX, startY, endX, endY) { + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segments[i][5] / totalLength; + segmentProportions[i] = [curLoc, (curLoc += (segments[i][5] / totalLength)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty/*, doGridX, doGridY*/) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0], + ly = segments.length == 0 ? sy : segments[segments.length - 1][1], + m = x == lx ? Infinity : 0, + l = Math.abs(x == lx ? y - ly : x - lx);/*, + gridded = clampToGrid(x, y), + doGridX = true, + doGridY = true; + + // grid experiment. TODO: have two more params that indicate whether or not to lock to a grid in each + // axis. the reason for this is that anchor points wont always be located on the grid, so until a connector + // emanating from that anchor has turned a right angle, we can't actually clamp it to a grid for that axis. + // so if a line came out horizontally heading left, then it will probably not be clamped in the y axis, but + // we can choose to clamp its first corner in the x axis. the same principle goes for the target anchor. + //if (segments.length == 0) { + console.log("this is the first segment...if sx == x then do not do grid in X.") + doGridX = !(sx == x) && !(tx == x); + doGridY = !(sy == y) && !(ty == y); + x = doGridX ? gridded[0] : x; + y = doGridY ? gridded[1] : y; + */ + + segments.push([x, y, lx, ly, m, l]); + totalLength += l; + + maxX = Math.max(maxX, x); + maxY = Math.max(maxY, y); + minX = Math.min(minX, x); + minY = Math.min(minY, y); + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment + * that contains the point which is 'location' distance along the entire path, where + * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths + * are made up of a list of segments, each of which contributes some fraction to + * the total length. + * From 1.3.10 this also supports the 'absolute' property, which lets us specify a location + * as the absolute distance in pixels, rather than a proportion of the total path. + */ + findSegmentForLocation = function(location, absolute) { + if (absolute) { + location = location > 0 ? location / totalLength : (totalLength + location) / totalLength; + } + + var idx = segmentProportions.length - 1, inSegmentProportion = 1; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, + sourceAnchor, targetAnchor, lineWidth, minWidth, sourceInfo, targetInfo) { + + segments = []; + segmentProportions = []; + totalLength = 0; + segmentProportionalLengths = []; + maxX = maxY = -Infinity; + minX = minY = Infinity; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + sourceOffx = (lw / 2) + (sourceStub + targetStub), + targetOffx = (lw / 2) + (targetStub + sourceStub), + sourceOffy = (lw / 2) + (sourceStub + targetStub), + targetOffy = (lw / 2) + (targetStub + sourceStub), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(sourceEndpoint), + to = targetAnchor.orientation || targetAnchor.getOrientation(targetEndpoint), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + sourceOffx + targetOffx, + h = Math.abs(targetPos[1] - sourcePos[1]) + sourceOffy + targetOffy; + + // if either anchor does not have an orientation set, we derive one from their relative + // positions. we fix the axis to be the one in which the two elements are further apart, and + // point each anchor at the other element. this is also used when dragging a new connection. + if (so[0] == 0 && so[1] == 0 || to[0] == 0 && to[1] == 0) { + var index = w > h ? 0 : 1, oIndex = [1,0][index]; + so = []; to = []; + so[index] = sourcePos[index] > targetPos[index] ? -1 : 1; + to[index] = sourcePos[index] > targetPos[index] ? 1 : -1; + so[oIndex] = 0; + to[oIndex] = 0; + } + + /* + this code is unexplained and causes paint errors with continuous anchors sometimes. + commenting it out until i can get to the bottom of it. + if (w < minWidth) { + sourceOffx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + sourceOffy += (minWidth - h) / 2; + h = minWidth; + } + */ + + var sx = swapX ? (w - targetOffx) +( gap * so[0]) : sourceOffx + (gap * so[0]), + sy = swapY ? (h - targetOffy) + (gap * so[1]) : sourceOffy + (gap * so[1]), + tx = swapX ? sourceOffx + (gap * to[0]) : (w - targetOffx) + (gap * to[0]), + ty = swapY ? sourceOffy + (gap * to[1]) : (h - targetOffy) + (gap * to[1]), + startStubX = sx + (so[0] * sourceStub), + startStubY = sy + (so[1] * sourceStub), + endStubX = tx + (to[0] * targetStub), + endStubY = ty + (to[1] * targetStub), + isXGreaterThanStubTimes2 = Math.abs(sx - tx) > (sourceStub + targetStub), + isYGreaterThanStubTimes2 = Math.abs(sy - ty) > (sourceStub + targetStub), + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2), + oProduct = ((so[0] * to[0]) + (so[1] * to[1])), + opposite = oProduct == -1, + perpendicular = oProduct == 0, + orthogonal = oProduct == 1; + + x -= sourceOffx; y -= sourceOffy; + points = [x, y, w, h, sx, sy, tx, ty]; + var extraPoints = []; + + var sourceAxis = so[0] == 0 ? "y" : "x", + anchorOrientation = opposite ? "opposite" : orthogonal ? "orthogonal" : "perpendicular", + segment = jsPlumbUtil.segment([sx, sy], [tx, ty]), + flipSourceSegments = so[sourceAxis == "x" ? 0 : 1] == -1, + flipSegments = { + "x":[null, 4, 3, 2, 1], + "y":[null, 2, 1, 4, 3] + } + + if (flipSourceSegments) + segment = flipSegments[sourceAxis][segment]; + + /*if (segment == 1 || segment == 2) { + if (sourceAxis == "x") + addSegment(Math.max(startStubX, endStubX), startStubY, sx, sy, tx, ty); + else + addSegment(startStubX, Math.max(startStubY, endStubY), sx, sy, tx, ty); + } + else { + if (sourceAxis == "x") + addSegment(Math.min(startStubX, endStubX), startStubY, sx, sy, tx, ty); + else + addSegment(startStubX, Math.min(startStubY, endStubY), sx, sy, tx, ty); + }*/ + //addSegment(startStubX, startStubY, sx, sy, tx, ty); + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + var findClearedLine = function(start, mult, anchorPos, dimension) { + return start + (mult * (( 1 - anchorPos) * dimension) + Math.max(sourceStub, targetStub)); + //mx = so[0] == 0 ? startStubX + ((1 - sourceAnchor.x) * sourceInfo.width) + stub : startStubX, + }, + + lineCalculators = { + oppositex : function() { + if (sourceEndpoint.elementId == targetEndpoint.elementId) { + var _y = startStubY + ((1 - sourceAnchor.y) * sourceInfo.height) + Math.max(sourceStub, targetStub); + return [ [ startStubX, _y ], [ endStubX, _y ]]; + } + else if (isXGreaterThanStubTimes2 && (segment == 1 || segment == 2)) { + return [[ midx, sy ], [ midx, ty ]]; + } + else { + return [[ startStubX, midy ], [endStubX, midy ]]; + } + }, + orthogonalx : function() { + if (segment == 1 || segment == 2) { + return [ [ endStubX, startStubY ]]; + } + else { + return [ [ startStubX, endStubY ]]; + } + }, + perpendicularx : function() { + var my = (ty + sy) / 2; + if ((segment == 1 && to[1] == 1) || (segment == 2 && to[1] == -1)) { + if (Math.abs(tx - sx) > Math.max(sourceStub, targetStub)) + return [ [endStubX, startStubY ]]; + else + return [ [startStubX, startStubY ], [ startStubX, my ], [ endStubX, my ]]; + } + else if ((segment == 3 && to[1] == -1) || (segment == 4 && to[1] == 1)) { + return [ [ startStubX, my ], [ endStubX, my ]]; + } + else if ((segment == 3 && to[1] == 1) || (segment == 4 && to[1] == -1)) { + return [ [ startStubX, endStubY ]]; + } + else if ((segment == 1 && to[1] == -1) || (segment == 2 && to[1] == 1)) { + if (Math.abs(tx - sx) > Math.max(sourceStub, targetStub)) + return [ [ midx, startStubY ], [ midx, endStubY ]]; + else + return [ [ startStubX, endStubY ]]; + } + }, + oppositey : function() { + if (sourceEndpoint.elementId == targetEndpoint.elementId) { + var _x = startStubX + ((1 - sourceAnchor.x) * sourceInfo.width) + Math.max(sourceStub, targetStub); + return [ [ _x, startStubY ], [ _x, endStubY ]]; + } + else if (isYGreaterThanStubTimes2 && (segment == 2 || segment == 3)) { + return [[ sx, midy ], [ tx, midy ]]; + } + else { + return [[ midx, startStubY ], [midx, endStubY ]]; + } + }, + orthogonaly : function() { + if (segment == 2 || segment == 3) { + return [ [ startStubX, endStubY ]]; + } + else { + return [ [ endStubX, startStubY ]]; + } + }, + perpendiculary : function() { + var mx = (tx + sx) / 2; + if ((segment == 2 && to[0] == -1) || (segment == 3 && to[0] == 1)) { + if (Math.abs(tx - sx) > Math.max(sourceStub, targetStub)) + return [ [startStubX, endStubY ]]; + else + return [ [startStubX, midy ], [ endStubX, midy ]]; + } + else if ((segment == 1 && to[0] == -1) || (segment == 4 && to[0] == 1)) { + var mx = (tx + sx) / 2; + return [ [ mx, startStubY ], [ mx, endStubY ]]; + } + else if ((segment == 1 && to[0] == 1) || (segment == 4 && to[0] == -1)) { + return [ [ endStubX, startStubY ]]; + } + else if ((segment == 2 && to[0] == 1) || (segment == 3 && to[0] == -1)) { + if (Math.abs(ty - sy) > Math.max(sourceStub, targetStub)) + return [ [ startStubX, midy ], [ endStubX, midy ]]; + else + return [ [ endStubX, startStubY ]]; + } + } + }; + + var p = lineCalculators[anchorOrientation + sourceAxis](); + if (p) { + for (var i = 0; i < p.length; i++) { + addSegment(p[i][0], p[i][1], sx, sy, tx, ty); + } + } + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentProportions(sx, sy, tx, ty); + + // adjust the max values of the canvas if we have a value that is larger than what we previously set. + // + if (maxY > points[3]) points[3] = maxY + (lineWidth * 2); + if (maxX > points[2]) points[2] = maxX + (lineWidth * 2); + + return points; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location, absolute) { + return self.pointAlongPathFrom(location, 0, absolute); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location, absolute) { + return segments[findSegmentForLocation(location, absolute)["index"]][4]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance, absolute) { + var s = findSegmentForLocation(location, absolute), seg = s.segment, p = s.proportion, sl = segments[s.index][5], m = segments[s.index][4]; + var e = { + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + return e; + }; + }; + + // ********************************* END OF CONNECTOR TYPES ******************************************************************* + + // ********************************* ENDPOINT TYPES ******************************************************************* + + /** + * Class: Endpoints.Dot + * A round endpoint, with default radius 10 pixels. + */ + + /** + * Function: Constructor + * + * Parameters: + * + * radius - radius of the endpoint. defaults to 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + this.type = "Dot"; + var self = this; + params = params || {}; + this.radius = params.radius || 10; + this.defaultOffset = 0.5 * this.radius; + this.defaultInnerRadius = this.radius / 3; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var r = endpointStyle.radius || self.radius, + x = anchorPoint[0] - r, + y = anchorPoint[1] - r; + return [ x, y, r * 2, r * 2, r ]; + }; + }; + + /** + * Class: Endpoints.Rectangle + * A Rectangular Endpoint, with default size 20x20. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the endpoint. defaults to 20 pixels. + * height - height of the endpoint. defaults to 20 pixels. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + this.type = "Rectangle"; + var self = this; + params = params || {}; + this.width = params.width || 20; + this.height = params.height || 20; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height]; + }; + }; + + + var DOMElementEndpoint = function(params) { + jsPlumb.DOMElementComponent.apply(this, arguments); + var self = this; + + var displayElements = [ ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + }; + /** + * Class: Endpoints.Image + * Draws an image as the Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * src - location of the image to use. + */ + jsPlumb.Endpoints.Image = function(params) { + + this.type = "Image"; + DOMElementEndpoint.apply(this, arguments); + + var self = this, + initialized = false, + deleted = false, + widthToUse = params.width, + heightToUse = params.height, + _onload = null, + _endpoint = params.endpoint; + + this.img = new Image(); + self.ready = false; + + this.img.onload = function() { + self.ready = true; + widthToUse = widthToUse || self.img.width; + heightToUse = heightToUse || self.img.height; + if (_onload) { + _onload(self); + } + }; + + /* + Function: setImage + Sets the Image to use in this Endpoint. + + Parameters: + img - may be a URL or an Image object + onload - optional; a callback to execute once the image has loaded. + */ + _endpoint.setImage = function(img, onload) { + var s = img.constructor == String ? img : img.src; + _onload = onload; + self.img.src = img; + + if (self.canvas != null) + self.canvas.setAttribute("src", img); + }; + + _endpoint.setImage(params.src || params.url, params.onload); + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + self.anchorPoint = anchorPoint; + if (self.ready) return [anchorPoint[0] - widthToUse / 2, anchorPoint[1] - heightToUse / 2, + widthToUse, heightToUse]; + else return [0,0,0,0]; + }; + + self.canvas = document.createElement("img"), initialized = false; + self.canvas.style["margin"] = 0; + self.canvas.style["padding"] = 0; + self.canvas.style["outline"] = 0; + self.canvas.style["position"] = "absolute"; + var clazz = params.cssClass ? " " + params.cssClass : ""; + self.canvas.className = jsPlumb.endpointClass + clazz; + if (widthToUse) self.canvas.setAttribute("width", widthToUse); + if (heightToUse) self.canvas.setAttribute("height", heightToUse); + jsPlumb.appendElement(self.canvas, params.parent); + self.attachListeners(self.canvas, self); + + self.cleanup = function() { + deleted = true; + }; + + var actuallyPaint = function(d, style, anchor) { + if (!deleted) { + if (!initialized) { + self.canvas.setAttribute("src", self.img.src); + self.appendDisplayElement(self.canvas); + initialized = true; + } + var x = self.anchorPoint[0] - (widthToUse / 2), + y = self.anchorPoint[1] - (heightToUse / 2); + jsPlumb.sizeCanvas(self.canvas, x, y, widthToUse, heightToUse); + } + }; + + this.paint = function(d, style, anchor) { + if (self.ready) { + actuallyPaint(d, style, anchor); + } + else { + window.setTimeout(function() { + self.paint(d, style, anchor); + }, 200); + } + }; + }; + + /** + * Class: Endpoints.Blank + * An Endpoint that paints nothing (visible) on the screen. Supports cssClass and hoverClass parameters like all Endpoints. + */ + jsPlumb.Endpoints.Blank = function(params) { + var self = this; + this.type = "Blank"; + DOMElementEndpoint.apply(this, arguments); + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + return [anchorPoint[0], anchorPoint[1],10,0]; + }; + + self.canvas = document.createElement("div"); + self.canvas.style.display = "block"; + self.canvas.style.width = "1px"; + self.canvas.style.height = "1px"; + self.canvas.style.background = "transparent"; + self.canvas.style.position = "absolute"; + self.canvas.className = self._jsPlumb.endpointClass; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + }; + }; + + /** + * Class: Endpoints.Triangle + * A triangular Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the triangle's base. defaults to 55 pixels. + * height - height of the triangle from base to apex. defaults to 55 pixels. + */ + jsPlumb.Endpoints.Triangle = function(params) { + this.type = "Triangle"; + params = params || { }; + params.width = params.width || 55; + params.height = params.height || 55; + this.width = params.width; + this.height = params.height; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height ]; + }; + }; +// ********************************* END OF ENDPOINT TYPES ******************************************************************* + + +// ********************************* OVERLAY DEFINITIONS *********************************************************************** + + var AbstractOverlay = function(params) { + var visible = true, self = this; + this.isAppendedAtTopLevel = true; + this.component = params.component; + this.loc = params.location == null ? 0.5 : params.location; + this.endpointLoc = params.endpointLocation == null ? [ 0.5, 0.5] : params.endpointLocation; + this.setVisible = function(val) { + visible = val; + self.component.repaint(); + }; + this.isVisible = function() { return visible; }; + this.hide = function() { self.setVisible(false); }; + this.show = function() { self.setVisible(true); }; + + this.incrementLocation = function(amount) { + self.loc += amount; + self.component.repaint(); + }; + this.setLocation = function(l) { + self.loc = l; + self.component.repaint(); + }; + this.getLocation = function() { + return self.loc; + }; + }; + + + /** + * Class: Overlays.Arrow + * + * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length + * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction + * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line + * across the tail. + */ + /** + * Function: Constructor + * + * Parameters: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + this.type = "Arrow"; + AbstractOverlay.apply(this, arguments); + this.isAppendedAtTopLevel = false; + params = params || {}; + var self = this; + + this.length = params.length || 20; + this.width = params.width || 20; + this.id = params.id; + var direction = (params.direction || 1) < 0 ? -1 : 1, + paintStyle = params.paintStyle || { lineWidth:1 }, + // how far along the arrow the lines folding back in come to. default is 62.3%. + foldback = params.foldback || 0.623; + + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.cleanup = function() { }; // nothing to clean up for Arrows + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var hxy, mid, txy, tail, cxy; + if (connector.pointAlongPathFrom) { + + if (jsPlumbUtil.isString(self.loc) || self.loc > 1 || self.loc < 0) { + var l = parseInt(self.loc); + hxy = connector.pointAlongPathFrom(l, direction * self.length / 2, true), + mid = connector.pointOnPath(l, true), + txy = jsPlumbUtil.pointOnLine(hxy, mid, self.length); + } + else if (self.loc == 1) { + hxy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, -1); + txy = jsPlumbUtil.pointOnLine(hxy, mid, self.length); + } + else if (self.loc == 0) { + txy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, 1); + hxy = jsPlumbUtil.pointOnLine(txy, mid, self.length); + } + else { + hxy = connector.pointAlongPathFrom(self.loc, direction * self.length / 2), + mid = connector.pointOnPath(self.loc), + txy = jsPlumbUtil.pointOnLine(hxy, mid, self.length); + } + + tail = jsPlumbUtil.perpendicularLineTo(hxy, txy, self.width); + cxy = jsPlumbUtil.pointOnLine(hxy, txy, foldback * self.length); + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x), + maxx = Math.max(hxy.x, tail[0].x, tail[1].x), + miny = Math.min(hxy.y, tail[0].y, tail[1].y), + maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + var d = { hxy:hxy, tail:tail, cxy:cxy }, + strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle, + fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle, + lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth; + + self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions); + + return [ minx, maxx, miny, maxy]; + } + else return [0,0,0,0]; + }; + }; + + /** + * Class: Overlays.PlainArrow + * + * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "PlainArrow"; + }; + + /** + * Class: Overlays.Diamond + * + * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40, + p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "Diamond"; + }; + + + // abstract superclass for overlays that add an element to the DOM. + var AbstractDOMOverlay = function(params) { + jsPlumb.DOMElementComponent.apply(this, arguments); + AbstractOverlay.apply(this, arguments); + + var self = this, initialised = false; + params = params || {}; + this.id = params.id; + var div; + + var makeDiv = function() { + div = params.create(params.component); + div = jsPlumb.CurrentLibrary.getDOMElement(div); + div.style["position"] = "absolute"; + var clazz = params["_jsPlumb"].overlayClass + " " + + (self.cssClass ? self.cssClass : + params.cssClass ? params.cssClass : ""); + div.className = clazz; + jsPlumb.appendElement(div, params.component.parent); + params["_jsPlumb"].getId(div); + self.attachListeners(div, self); + self.canvas = div; + }; + + this.getElement = function() { + if (div == null) { + makeDiv(); + } + return div; + }; + + this.getDimensions = function() { + return jsPlumb.CurrentLibrary.getSize(jsPlumb.CurrentLibrary.getElementObject(self.getElement())); + }; + + var cachedDimensions = null, + _getDimensions = function(component) { + if (cachedDimensions == null) + cachedDimensions = self.getDimensions(); + return cachedDimensions; + }; + + /* + * Function: clearCachedDimensions + * Clears the cached dimensions for the label. As a performance enhancement, label dimensions are + * cached from 1.3.12 onwards. The cache is cleared when you change the label text, of course, but + * there are other reasons why the text dimensions might change - if you make a change through CSS, for + * example, you might change the font size. in that case you should explicitly call this method. + */ + this.clearCachedDimensions = function() { + cachedDimensions = null; + }; + + this.computeMaxSize = function() { + var td = _getDimensions(); + return Math.max(td[0], td[1]); + }; + + //override setVisible + var osv = self.setVisible; + self.setVisible = function(state) { + osv(state); // call superclass + div.style.display = state ? "block" : "none"; + }; + + this.cleanup = function() { + if (div != null) jsPlumb.CurrentLibrary.removeElement(div); + }; + + this.paint = function(component, d, componentDimensions) { + if (!initialised) { + self.getElement(); + component.appendDisplayElement(div); + self.attachListeners(div, component); + initialised = true; + } + div.style.left = (componentDimensions[0] + d.minx) + "px"; + div.style.top = (componentDimensions[1] + d.miny) + "px"; + }; + + this.draw = function(component, currentConnectionPaintStyle, componentDimensions) { + var td = _getDimensions(); + if (td != null && td.length == 2) { + var cxy = {x:0,y:0}; + if (component.pointOnPath) { + var loc = self.loc, absolute = false; + if (jsPlumbUtil.isString(self.loc) || self.loc < 0 || self.loc > 1) { + loc = parseInt(self.loc); + absolute = true; + } + cxy = component.pointOnPath(loc, absolute); // a connection + } + else { + var locToUse = self.loc.constructor == Array ? self.loc : self.endpointLoc; + cxy = { x:locToUse[0] * componentDimensions[2], + y:locToUse[1] * componentDimensions[3] }; + } + + minx = cxy.x - (td[0] / 2), + miny = cxy.y - (td[1] / 2); + self.paint(component, { minx:minx, miny:miny, td:td, cxy:cxy }, componentDimensions); + return [minx, minx + td[0], miny, miny + td[1]]; + } + else return [0,0,0,0]; + }; + + this.reattachListeners = function(connector) { + if (div) { + self.reattachListenersForElement(div, self, connector); + } + }; + + }; + + /** + * Class: Overlays.Custom + * A Custom overlay. You supply a 'create' function which returns some DOM element, and jsPlumb positions it. + * The 'create' function is passed a Connection or Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * create - function for jsPlumb to call that returns a DOM element. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * id - optional id to use for later retrieval of this overlay. + * + */ + jsPlumb.Overlays.Custom = function(params) { + this.type = "Custom"; + AbstractDOMOverlay.apply(this, arguments); + }; + + /** + * Class: Overlays.Label + * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb + * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter + * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it + * puts on the Label's 'style' attribute, so the end result is the same. + */ + /** + * Function: Constructor + * + * Parameters: + * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes + * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle. + * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * id - optional id to use for later retrieval of this overlay. + * + */ + jsPlumb.Overlays.Label = function(params) { + var self = this; + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.cssClass = this.labelStyle != null ? this.labelStyle.cssClass : null; + params.create = function() { + return document.createElement("div"); + }; + jsPlumb.Overlays.Custom.apply(this, arguments); + this.type = "Label"; + + var label = params.label || "", + self = this, + labelText = null; + + /* + * Function: setLabel + * sets the label's, um, label. you would think i'd call this function + * 'setText', but you can pass either a Function or a String to this, so + * it makes more sense as 'setLabel'. This uses innerHTML on the label div, so keep + * that in mind if you need escaped HTML. + */ + this.setLabel = function(l) { + label = l; + labelText = null; + self.clearCachedDimensions(); + _update(); + self.component.repaint(); + }; + + var _update = function() { + if (typeof label == "function") { + var lt = label(self); + self.getElement().innerHTML = lt.replace(/\r\n/g, "
"); + } + else { + if (labelText == null) { + labelText = label; + self.getElement().innerHTML = labelText.replace(/\r\n/g, "
"); + } + } + }; + + this.getLabel = function() { + return label; + }; + + var superGD = this.getDimensions; + this.getDimensions = function() { + _update(); + return superGD(); + }; + + }; + + + // ********************************* END OF OVERLAY DEFINITIONS *********************************************************************** + +})(); \ No newline at end of file diff --git a/archive/1.3.13/jsPlumb-dom-adapter-1.3.13-RC1.js b/archive/1.3.13/jsPlumb-dom-adapter-1.3.13-RC1.js new file mode 100644 index 000000000..9666d486b --- /dev/null +++ b/archive/1.3.13/jsPlumb-dom-adapter-1.3.13-RC1.js @@ -0,0 +1,190 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.13 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the base functionality for DOM type adapters. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ +;(function() { + + var canvasAvailable = !!document.createElement('canvas').getContext, + svgAvailable = !!window.SVGAngle || document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"), + // http://stackoverflow.com/questions/654112/how-do-you-detect-support-for-vml-or-svg-in-a-browser + vmlAvailable = function() { + if (vmlAvailable.vml == undefined) { + var a = document.body.appendChild(document.createElement('div')); + a.innerHTML = ''; + var b = a.firstChild; + b.style.behavior = "url(#default#VML)"; + vmlAvailable.vml = b ? typeof b.adj == "object": true; + a.parentNode.removeChild(a); + } + return vmlAvailable.vml; + }; + + /** + Manages dragging for some instance of jsPlumb. + */ + var DragManager = function(_currentInstance) { + var _draggables = {}, _dlist = [], _delements = {}, _elementsWithEndpoints = {}; + + /** + register some element as draggable. right now the drag init stuff is done elsewhere, and it is + possible that will continue to be the case. + */ + this.register = function(el) { + var jpcl = jsPlumb.CurrentLibrary; + el = jpcl.getElementObject(el); + var id = _currentInstance.getId(el), + domEl = jpcl.getDOMElement(el), + parentOffset = jpcl.getOffset(el); + + if (!_draggables[id]) { + _draggables[id] = el; + _dlist.push(el); + _delements[id] = {}; + } + + // look for child elements that have endpoints and register them against this draggable. + var _oneLevel = function(p, startOffset) { + if (p) { + for (var i = 0; i < p.childNodes.length; i++) { + if (p.childNodes[i].nodeType != 3) { + var cEl = jpcl.getElementObject(p.childNodes[i]), + cid = _currentInstance.getId(cEl, null, true); + if (cid && _elementsWithEndpoints[cid] && _elementsWithEndpoints[cid] > 0) { + var cOff = jpcl.getOffset(cEl); + _delements[id][cid] = { + id:cid, + offset:{ + left:cOff.left - parentOffset.left, + top:cOff.top - parentOffset.top + } + }; + } + _oneLevel(p.childNodes[i]); + } + } + } + }; + + _oneLevel(domEl); + }; + + /** + notification that an endpoint was added to the given el. we go up from that el's parent + node, looking for a parent that has been registered as a draggable. if we find one, we add this + el to that parent's list of elements to update on drag (if it is not there already) + */ + this.endpointAdded = function(el) { + var jpcl = jsPlumb.CurrentLibrary, b = document.body, id = _currentInstance.getId(el), c = jpcl.getDOMElement(el), + p = c.parentNode, done = p == b; + + _elementsWithEndpoints[id] = _elementsWithEndpoints[id] ? _elementsWithEndpoints[id] + 1 : 1; + + while (p != b) { + var pid = _currentInstance.getId(p); + if (_draggables[pid]) { + var idx = -1, pEl = jpcl.getElementObject(p), pLoc = jpcl.getOffset(pEl); + + if (_delements[pid][id] == null) { + var cLoc = jsPlumb.CurrentLibrary.getOffset(el); + _delements[pid][id] = { + id:id, + offset:{ + left:cLoc.left - pLoc.left, + top:cLoc.top - pLoc.top + } + }; + } + break; + } + p = p.parentNode; + } + }; + + this.endpointDeleted = function(endpoint) { + if (_elementsWithEndpoints[endpoint.elementId]) { + _elementsWithEndpoints[endpoint.elementId]--; + if (_elementsWithEndpoints[endpoint.elementId] <= 0) { + for (var i in _delements) { + delete _delements[i][endpoint.elementId]; + } + } + } + }; + + this.getElementsForDraggable = function(id) { + return _delements[id]; + }; + + this.reset = function() { + _draggables = {}; + _dlist = []; + _delements = {}; + _elementsWithEndpoints = {}; + }; + + }; + + // for those browsers that dont have it. they still don't have it! but at least they won't crash. + if (!window.console) + window.console = { time:function(){}, timeEnd:function(){}, group:function(){}, groupEnd:function(){}, log:function(){} }; + + window.jsPlumbAdapter = { + + headless:false, + + appendToRoot : function(node) { + document.body.appendChild(node); + }, + getRenderModes : function() { + return [ "canvas", "svg", "vml" ] + }, + isRenderModeAvailable : function(m) { + return { + "canvas":canvasAvailable, + "svg":svgAvailable, + "vml":vmlAvailable() + }[m]; + }, + getDragManager : function(_jsPlumb) { + return new DragManager(_jsPlumb); + }, + setRenderMode : function(mode) { + var renderMode; + + if (mode) { + mode = mode.toLowerCase(); + + var canvasAvailable = this.isRenderModeAvailable("canvas"), + svgAvailable = this.isRenderModeAvailable("svg"), + vmlAvailable = this.isRenderModeAvailable("vml"); + + //if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); + // now test we actually have the capability to do this. + if (mode === "svg") { + if (svgAvailable) renderMode = "svg" + else if (canvasAvailable) renderMode = "canvas" + else if (vmlAvailable) renderMode = "vml" + } + else if (mode === "canvas" && canvasAvailable) renderMode = "canvas"; + else if (vmlAvailable) renderMode = "vml"; + } + + return renderMode; + } + }; + +})(); \ No newline at end of file diff --git a/archive/1.3.13/jsPlumb-drag-1.3.13-RC1.js b/archive/1.3.13/jsPlumb-drag-1.3.13-RC1.js new file mode 100644 index 000000000..d71db029e --- /dev/null +++ b/archive/1.3.13/jsPlumb-drag-1.3.13-RC1.js @@ -0,0 +1,61 @@ +/* + * this is experimental and probably will not be used. solutions exist for most libraries. but of course if + * i want to support multiple scopes at some stage then i will have to do dragging inside jsPlumb. + */ +;(function() { + + window.jsPlumbDrag = function(_jsPlumb) { + + var ta = new TouchAdapter(); + + this.draggable = function(selector) { + var el, elId, da = [], elo, d = false, + isInSelector = function(el) { + if (typeof selector == "string") + return selector === _jsPlumb.getId(el); + + for (var i = 0; i < selector.length; i++) { + var _sel = jsPlumb.CurrentLibrary.getDOMElement(selector[i]); + if (_sel == el) return true; + } + return false; + }; + + ta.bind(document, "mousedown", function(e) { + var target = e.target || e.srcElement; + if (isInSelector(target)) { + el = jsPlumb.CurrentLibrary.getElementObject(target); + elId = _jsPlumb.getId(el); + elo = jsPlumb.CurrentLibrary.getOffset(el); + da = [e.pageX, e.pageY]; + d = true; + } + }); + + ta.bind(document, "mousemove", function(e) { + if (d) { + var dx = e.pageX - da[0], + dy = e.pageY - da[1]; + + jsPlumb.CurrentLibrary.setOffset(el, { + left:elo.left + dx, + top:elo.top + dy + }); + _jsPlumb.repaint(elId); + e.preventDefault(); + e.stopPropagation(); + } + }); + ta.bind(document, "mouseup", function(e) { + el = null; + d = false; + }); + }; + + var isIOS = ((/iphone|ipad/gi).test(navigator.appVersion)); + if (isIOS) + _jsPlumb.draggable = this.draggable; + + }; + +})(); \ No newline at end of file diff --git a/archive/1.3.13/jsPlumb-overlays-guidelines-1.3.13-RC1.js b/archive/1.3.13/jsPlumb-overlays-guidelines-1.3.13-RC1.js new file mode 100644 index 000000000..72b63b009 --- /dev/null +++ b/archive/1.3.13/jsPlumb-overlays-guidelines-1.3.13-RC1.js @@ -0,0 +1,73 @@ + // this is really just a test overlay, so its undocumented and doesnt take any parameters. but i was loth to delete it. + jsPlumb.Overlays.GuideLines = function() { + var self = this; + self.length = 50; + self.lineWidth = 5; + this.type = "GuideLines"; + AbstractOverlay.apply(this, arguments); + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var head = connector.pointAlongPathFrom(self.loc, self.length / 2), + mid = connector.pointOnPath(self.loc), + tail = jsPlumbUtil.pointOnLine(head, mid, self.length), + tailLine = jsPlumbUtil.perpendicularLineTo(head, tail, 40), + headLine = jsPlumbUtil.perpendicularLineTo(tail, head, 20); + + self.paint(connector, [head, tail, tailLine, headLine], self.lineWidth, "red", null, connectorDimensions); + + return [Math.min(head.x, tail.x), Math.min(head.y, tail.y), Math.max(head.x, tail.x), Math.max(head.y,tail.y)]; + }; + + this.computeMaxSize = function() { return 50; }; + + this.cleanup = function() { }; // nothing to clean up for GuideLines + }; + +// a test + jsPlumb.Overlays.svg.GuideLines = function() { + var path = null, self = this, path2 = null, p1_1, p1_2; + jsPlumb.Overlays.GuideLines.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + + p1_1 = _node("path"); + connector.svg.appendChild(p1_1); + self.attachListeners(p1_1, connector); + self.attachListeners(p1_1, self); + + p1_2 = _node("path"); + connector.svg.appendChild(p1_2); + self.attachListeners(p1_2, connector); + self.attachListeners(p1_2, self); + + } + + _attr(path, { + "d" : makePath(d[0], d[1]), + stroke : "red", + fill : null + }); + + _attr(p1_1, { + "d" : makePath(d[2][0], d[2][1]), + stroke : "blue", + fill : null + }); + + _attr(p1_2, { + "d" : makePath(d[3][0], d[3][1]), + stroke : "green", + fill : null + }); + }; + + var makePath = function(d1, d2) { + return "M " + d1.x + "," + d1.y + + " L" + d2.x + "," + d2.y; + }; + }; \ No newline at end of file diff --git a/archive/1.3.13/jsPlumb-renderers-canvas-1.3.13-RC1.js b/archive/1.3.13/jsPlumb-renderers-canvas-1.3.13-RC1.js new file mode 100644 index 000000000..d11037064 --- /dev/null +++ b/archive/1.3.13/jsPlumb-renderers-canvas-1.3.13-RC1.js @@ -0,0 +1,510 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.13 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the HTML5 canvas renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + +// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS ******************************************************************* + + // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. + var _connectionBeingDragged = null, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, + _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }; + + /* + * Class:CanvasMouseAdapter + * Provides support for mouse events on canvases. + */ + var CanvasMouseAdapter = function() { + var self = this; + self.overlayPlacements = []; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + jsPlumbUtil.EventGenerator.apply(this, arguments); + /** + * returns whether or not the given event is ojver a painted area of the canvas. + */ + this._over = function(e) { + var o = _getOffset(_getElementObject(self.canvas)), + pageXY = _pageXY(e), + x = pageXY[0] - o.left, y = pageXY[1] - o.top; + if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + return false; + }; + + var _mouseover = false, _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false, + _nullSafeHasClass = function(el, clazz) { + return el != null && _hasClass(el, clazz); + }; + this.mousemove = function(e) { + var pageXY = _pageXY(e), clientXY = _clientXY(e), + ee = document.elementFromPoint(clientXY[0], clientXY[1]), + eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); + var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); + if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.fire("mouseenter", self, e); + return true; + } + // TODO here there is a remote chance that the overlay the mouse moved onto + // is actually not an overlay for the current component. a more thorough check would + // be to ensure the overlay belonged to the current component. + else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { + _mouseover = false; + self.fire("mouseexit", self, e); + } + self.fire("mousemove", self, e); + }; + + this.click = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); + self.fire("mousedown", self, e); + } + }; + + this.mouseup = function(e) { + _mouseDown = false; + self.fire("mouseup", self, e); + }; + + this.contextmenu = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("contextmenu", self, e); + _mouseWasDown = false; + }; + }; + + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + params["_jsPlumb"].appendElement(canvas, params.parent); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + params["_jsPlumb"].getId(canvas, params.uuid); + if (params.tooltip) canvas.setAttribute("title", params.tooltip); + + return canvas; + }; + + var CanvasComponent = function(params) { + CanvasMouseAdapter.apply(this, arguments); + + var displayElements = [ ]; + this.getDisplayElements = function() { return displayElements; }; + this.appendDisplayElement = function(el) { displayElements.push(el); }; + }; + + /** + * Class:CanvasConnector + * Superclass for Canvas Connector renderers. + */ + var CanvasConnector = jsPlumb.CanvasConnector = function(params) { + + CanvasComponent.apply(this, arguments); + + var _paintOneStyle = function(dim, aStyle) { + self.ctx.save(); + jsPlumb.extend(self.ctx, aStyle); + if (aStyle.gradient) { + var g = self.createGradient(dim, self.ctx); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + self.ctx.strokeStyle = g; + } + self._paint(dim, aStyle); + self.ctx.restore(); + }; + + var self = this, + clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:params.tooltip + }); + self.ctx = self.canvas.getContext("2d"); + + self.appendDisplayElement(self.canvas); + + self.paint = function(dim, style) { + if (style != null) { + jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); + if (self.getZIndex()) + self.canvas.style.zIndex = self.getZIndex(); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + _paintOneStyle(dim, outlineStyle); + } + _paintOneStyle(dim, style); + } + }; + }; + + /** + * Class:CanvasEndpoint + * Superclass for Canvas Endpoint renderers. + */ + var CanvasEndpoint = function(params) { + var self = this; + CanvasComponent.apply(this, arguments); + var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""), + canvasParams = { + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:self.tooltip + }; + self.canvas = _newCanvas(canvasParams); + self.ctx = self.canvas.getContext("2d"); + + self.appendDisplayElement(self.canvas); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + } + + self._paint.apply(this, arguments); + }; + }; + + jsPlumb.Endpoints.canvas.Dot = function(params) { + jsPlumb.Endpoints.Dot.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + var self = this, + parseValue = function(value) { + try { return parseInt(value); } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }, + calculateAdjustments = function(gradient) { + var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius; + gradient.offset && (offsetAdjustment = parseValue(gradient.offset)); + gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius)); + return [offsetAdjustment, innerRadius]; + }; + this._paint = function(d, style, anchor) { + if (style != null) { + var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + if (style.gradient) { + var adjustments = calculateAdjustments(style.gradient), + yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0], + xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0], + g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + ctx.beginPath(); + ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + } + }; + }; + + jsPlumb.Endpoints.canvas.Rectangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) { + + var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + + /* canvas gradient */ + if (style.gradient) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, d[2], d[3]); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + jsPlumb.Endpoints.canvas.Triangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Triangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) + { + var width = d[2], height = d[3], x = d[0], y = d[1], + ctx = self.canvas.getContext('2d'), + offsetX = 0, offsetY = 0, angle = 0, + orientation = anchor.getOrientation(self); + + if( orientation[0] == 1 ) { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = style.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + /* + * Canvas Image Endpoint: uses the default version, which creates an tag. + */ + jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image; + + /* + * Blank endpoint in all renderers is just the default Blank endpoint. + */ + jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank; + + /* + * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Bezier = function() { + var self = this; + jsPlumb.Connectors.Bezier.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions, style) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO i doubt this handles the case that source and target are swapped. + this.createGradient = function(dim, ctx, swap) { + return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + /* + * Canvas straight line Connector. Draws a straight line onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Straight = function() { + var self = this, + segmentMultipliers = [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ]; + + jsPlumb.Connectors.Straight.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions, style) { + + self.ctx.beginPath(); + + if (style.dashstyle && style.dashstyle.split(" ").length == 2) { + // only a very simple dashed style is supported - having two values, which define the stroke length + // (as a multiple of the stroke width) and then the space length (also as a multiple of stroke width). + var ds = style.dashstyle.split(" "); + if (ds.length != 2) ds = [2, 2]; + var dss = [ ds[0] * style.lineWidth, ds[1] * style.lineWidth ], + m = (dimensions[6] - dimensions[4]) / (dimensions[7] - dimensions[5]), + s = jsPlumbUtil.segment([dimensions[4], dimensions[5]], [ dimensions[6], dimensions[7] ]), + sm = segmentMultipliers[s], + theta = Math.atan(m), + l = Math.sqrt(Math.pow(dimensions[6] - dimensions[4], 2) + Math.pow(dimensions[7] - dimensions[5], 2)), + repeats = Math.floor(l / (dss[0] + dss[1])), + curPos = [dimensions[4], dimensions[5]]; + + + // TODO: the question here is why could we not support this in all connector types? it's really + // just a case of going along and asking jsPlumb for the next point on the path a few times, until it + // reaches the end. every type of connector supports that method, after all. but right now its only the + // bezier connector that gives you back the new location on the path along with the x,y coordinates, which + // we would need. we'd start out at loc=0 and ask for the point along the path that is dss[0] pixels away. + // we then ask for the point that is (dss[0] + dss[1]) pixels away; and from that one we need not just the + // x,y but the location, cos we're gonna plug that location back in in order to find where that dash ends. + // + // it also strikes me that it should be trivial to support arbitrary dash styles (having more or less than two + // entries). you'd just iterate that array using a step size of 2, and generify the (rss[0] + rss[1]) + // computation to be sum(rss[0]..rss[n]). + + for (var i = 0; i < repeats; i++) { + self.ctx.moveTo(curPos[0], curPos[1]); + + var nextEndX = curPos[0] + (Math.abs(Math.sin(theta) * dss[0]) * sm[0]), + nextEndY = curPos[1] + (Math.abs(Math.cos(theta) * dss[0]) * sm[1]), + nextStartX = curPos[0] + (Math.abs(Math.sin(theta) * (dss[0] + dss[1])) * sm[0]), + nextStartY = curPos[1] + (Math.abs(Math.cos(theta) * (dss[0] + dss[1])) * sm[1]) + + self.ctx.lineTo(nextEndX, nextEndY); + curPos = [nextStartX, nextStartY]; + } + + // now draw the last bit + self.ctx.moveTo(curPos[0], curPos[1]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + + } + else { + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + } + + self.ctx.stroke(); + }; + + // TODO this does not handle the case that src and target are swapped. + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + jsPlumb.Connectors.canvas.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions, style) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + +// ********************************* END OF CANVAS RENDERERS ******************************************************************* + + jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label; + jsPlumb.Overlays.canvas.Custom = jsPlumb.Overlays.Custom; + + /** + * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. + */ + var CanvasOverlay = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }; + + var AbstractCanvasArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + CanvasOverlay.apply(this, originalArgs); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + var ctx = connector.ctx; + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(d.hxy.x, d.hxy.y); + ctx.lineTo(d.tail[0].x, d.tail[0].y); + ctx.lineTo(d.cxy.x, d.cxy.y); + ctx.lineTo(d.tail[1].x, d.tail[1].y); + ctx.lineTo(d.hxy.x, d.hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + }; + + jsPlumb.Overlays.canvas.Arrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.canvas.PlainArrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.canvas.Diamond = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.13/jsPlumb-renderers-svg-1.3.13-RC1.js b/archive/1.3.13/jsPlumb-renderers-svg-1.3.13-RC1.js new file mode 100644 index 000000000..ceec7a130 --- /dev/null +++ b/archive/1.3.13/jsPlumb-renderers-svg-1.3.13-RC1.js @@ -0,0 +1,555 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.13 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the SVG renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +/** + * SVG support for jsPlumb. + * + * things to investigate: + * + * gradients: https://developer.mozilla.org/en/svg_in_html_introduction + * css:http://tutorials.jenkov.com/svg/svg-and-css.html + * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath + * pointer events: https://developer.mozilla.org/en/css/pointer-events + * + * IE9 hover jquery: http://forum.jquery.com/topic/1-6-2-broke-svg-hover-events + * + */ +;(function() { + + var svgAttributeMap = { + "joinstyle":"stroke-linejoin", + "stroke-linejoin":"stroke-linejoin", + "stroke-dashoffset":"stroke-dashoffset", + "stroke-linecap":"stroke-linecap" + }, + STROKE_DASHARRAY = "stroke-dasharray", + DASHSTYLE = "dashstyle", + LINEAR_GRADIENT = "linearGradient", + RADIAL_GRADIENT = "radialGradient", + FILL = "fill", + STOP = "stop", + STROKE = "stroke", + STROKE_WIDTH = "stroke-width", + STYLE = "style", + NONE = "none", + JSPLUMB_GRADIENT = "jsplumb_gradient_", + LINE_WIDTH = "lineWidth", + ns = { + svg:"http://www.w3.org/2000/svg", + xhtml:"http://www.w3.org/1999/xhtml" + }, + _attr = function(node, attributes) { + for (var i in attributes) + node.setAttribute(i, "" + attributes[i]); + }, + _node = function(name, attributes) { + var n = document.createElementNS(ns.svg, name); + attributes = attributes || {}; + attributes["version"] = "1.1"; + attributes["xmlns"] = ns.xhtml; + _attr(n, attributes); + return n; + }, + _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; }, + _clearGradient = function(parent) { + for (var i = 0; i < parent.childNodes.length; i++) { + if (parent.childNodes[i].tagName == LINEAR_GRADIENT || parent.childNodes[i].tagName == RADIAL_GRADIENT) + parent.removeChild(parent.childNodes[i]); + } + }, + _updateGradient = function(parent, node, style, dimensions, uiComponent) { + var id = JSPLUMB_GRADIENT + uiComponent._jsPlumb.idstamp(); + // first clear out any existing gradient + _clearGradient(parent); + // this checks for an 'offset' property in the gradient, and in the absence of it, assumes + // we want a linear gradient. if it's there, we create a radial gradient. + // it is possible that a more explicit means of defining the gradient type would be + // better. relying on 'offset' means that we can never have a radial gradient that uses + // some default offset, for instance. + // issue 244 suggested the 'gradientUnits' attribute; without this, straight/flowchart connectors with gradients would + // not show gradients when the line was perfectly horizontal or vertical. + if (!style.gradient.offset) { + var g = _node(LINEAR_GRADIENT, {id:id, gradientUnits:"userSpaceOnUse"}); + parent.appendChild(g); + } + else { + var g = _node(RADIAL_GRADIENT, { + id:id + }); + parent.appendChild(g); + } + + // the svg radial gradient seems to treat stops in the reverse + // order to how canvas does it. so we want to keep all the maths the same, but + // iterate the actual style declarations in reverse order, if the x indexes are not in order. + for (var i = 0; i < style.gradient.stops.length; i++) { + // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of + // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments. + // so, not too concerned about leaving this in for now. + var styleToUse = i; + if (dimensions.length == 8) + styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i; + else + styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i; + var stopColor = jsPlumbUtil.convertStyle(style.gradient.stops[styleToUse][1], true); + var s = _node(STOP, {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor}); + g.appendChild(s); + } + var applyGradientTo = style.strokeStyle ? STROKE : FILL; + node.setAttribute(STYLE, applyGradientTo + ":url(#" + id + ")"); + }, + _applyStyles = function(parent, node, style, dimensions, uiComponent) { + + if (style.gradient) { + _updateGradient(parent, node, style, dimensions, uiComponent); + } + else { + // make sure we clear any existing gradient + _clearGradient(parent); + node.setAttribute(STYLE, ""); + } + + node.setAttribute(FILL, style.fillStyle ? jsPlumbUtil.convertStyle(style.fillStyle, true) : NONE); + node.setAttribute(STROKE, style.strokeStyle ? jsPlumbUtil.convertStyle(style.strokeStyle, true) : NONE); + if (style.lineWidth) { + node.setAttribute(STROKE_WIDTH, style.lineWidth); + } + + // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like + // the syntax in VML but is actually kind of nasty: values are given in the pixel + // coordinate space, whereas in VML they are multiples of the width of the stroked + // line, which makes a lot more sense. for that reason, jsPlumb is supporting both + // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from + // VML, which will be the preferred method. the code below this converts a dashstyle + // attribute given in terms of stroke width into a pixel representation, by using the + // stroke's lineWidth. + if (style[DASHSTYLE] && style[LINE_WIDTH] && !style[STROKE_DASHARRAY]) { + var sep = style[DASHSTYLE].indexOf(",") == -1 ? " " : ",", + parts = style[DASHSTYLE].split(sep), + styleToUse = ""; + parts.forEach(function(p) { + styleToUse += (Math.floor(p * style.lineWidth) + sep); + }); + node.setAttribute(STROKE_DASHARRAY, styleToUse); + } + else if(style[STROKE_DASHARRAY]) { + node.setAttribute(STROKE_DASHARRAY, style[STROKE_DASHARRAY]); + } + + // extra attributes such as join type, dash offset. + for (var i in svgAttributeMap) { + if (style[i]) { + node.setAttribute(svgAttributeMap[i], style[i]); + } + } + }, + _decodeFont = function(f) { + var r = /([0-9].)(p[xt])\s(.*)/; + var bits = f.match(r); + return {size:bits[1] + bits[2], font:bits[3]}; + }, + _classManip = function(el, add, clazz) { + var classesToAddOrRemove = clazz.split(" "), + className = el.className, + curClasses = className.baseVal.split(" "); + + for (var i = 0; i < classesToAddOrRemove.length; i++) { + if (add) { + if (curClasses.indexOf(classesToAddOrRemove[i]) == -1) + curClasses.push(classesToAddOrRemove[i]); + } + else { + var idx = curClasses.indexOf(classesToAddOrRemove[i]); + if (idx != -1) + curClasses.splice(idx, 1); + } + } + + el.className.baseVal = curClasses.join(" "); + }, + _addClass = function(el, clazz) { + _classManip(el, true, clazz); + }, + _removeClass = function(el, clazz) { + _classManip(el, false, clazz); + }; + + /** + utility methods for other objects to use. + */ + jsPlumbUtil.svg = { + addClass:_addClass, + removeClass:_removeClass, + node:_node, + attr:_attr, + pos:_pos + }; + + /* + * Base class for SVG components. + */ + var SvgComponent = function(params) { + var self = this, + pointerEventsSpec = params.pointerEventsSpec || "all"; + jsPlumb.jsPlumbUIComponent.apply(this, params.originalArgs); + self.canvas = null, self.path = null, self.svg = null; + + var clazz = params.cssClass + " " + (params.originalArgs[0].cssClass || ""), + svgParams = { + "style":"", + "width":0, + "height":0, + "pointer-events":pointerEventsSpec, + "position":"absolute" + }; + if (self.tooltip) svgParams["title"] = self.tooltip; + self.svg = _node("svg", svgParams); + if (params.useDivWrapper) { + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.sizeCanvas(self.canvas,0,0,1,1); + self.canvas.className = clazz; + if (self.tooltip) self.canvas.setAttribute("title", self.tooltip); + } + else { + _attr(self.svg, { "class":clazz }); + self.canvas = self.svg; + } + + params._jsPlumb.appendElement(self.canvas, params.originalArgs[0]["parent"]); + if (params.useDivWrapper) self.canvas.appendChild(self.svg); + + // TODO this displayElement stuff is common between all components, across all + // renderers. would be best moved to jsPlumbUIComponent. + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + this.paint = function(d, style, anchor) { + if (style != null) { + var x = d[0], y = d[1]; + if (params.useDivWrapper) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + x = 0, y = 0; + } + var p = _pos([x, y, d[2], d[3]]); + if (self.getZIndex()) p += ";z-index:" + self.getZIndex() + ";"; + _attr(self.svg, { + "style":p, + "width": d[2], + "height": d[3] + }); + self._paint.apply(this, arguments); + } + }; + }; + + /* + * Base class for SVG connectors. + */ + var SvgConnector = jsPlumb.SvgConnector = function(params) { + var self = this; + SvgComponent.apply(this, [ { + cssClass:params["_jsPlumb"].connectorClass, + originalArgs:arguments, + pointerEventsSpec:"none", + tooltip:params.tooltip, + _jsPlumb:params["_jsPlumb"] + } ]); + this._paint = function(d, style) { + var p = self.getPath(d), a = { "d":p }, outlineStyle = null; + a["pointer-events"] = "all"; + + // outline style. actually means drawing an svg object underneath the main one. + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = jsPlumb.CurrentLibrary.extend({}, style); + outlineStyle.strokeStyle = jsPlumbUtil.convertStyle(style.outlineColor); + outlineStyle.lineWidth = outlineStrokeWidth; + + if (self.bgPath == null) { + self.bgPath = _node("path", a); + self.svg.appendChild(self.bgPath); + self.attachListeners(self.bgPath, self); + } + else { + _attr(self.bgPath, a); + } + + _applyStyles(self.svg, self.bgPath, outlineStyle, d, self); + } + + + // test - see below + // a["clip-path"]= "url(#testClip)"; + + if (self.path == null) { + self.path = _node("path", a); + self.svg.appendChild(self.path); + self.attachListeners(self.path, self); + + /* + this is a test of a clip path. i'm looking into using one of these to animate a jsplumb connection. + you could do this by walking along the line, stepping along a little at a time, and setting the clip + path to extend as far as that point. + + self.clip = _node("clipPath", {id:"testClip", clipPathUnits:"objectBoundingBox"}); + self.svg.appendChild(self.clip); + self.clip.appendChild(_node("rect", { + x:"0",y:"0",width:"0.5",height:"1" + })); + */ + } + else { + _attr(self.path, a); + } + + _applyStyles(self.svg, self.path, style, d, self); + }; + + this.reattachListeners = function() { + if (self.bgPath) self.reattachListenersForElement(self.bgPath, self); + if (self.path) self.reattachListenersForElement(self.path, self); + }; + + }; + + /* + * SVG Bezier Connector + */ + jsPlumb.Connectors.svg.Bezier = function(params) { + jsPlumb.Connectors.Bezier.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { + var _p = "M " + d[4] + " " + d[5]; + _p += (" C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]); + return _p; + }; + }; + + /* + * SVG straight line Connector + */ + jsPlumb.Connectors.svg.Straight = function(params) { + jsPlumb.Connectors.Straight.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; }; + }; + + jsPlumb.Connectors.svg.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "M " + dimensions[4] + "," + dimensions[5]; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)]; + } + // finally draw a line to the end + p = p + " " + dimensions[6] + "," + dimensions[7]; + return p; + }; + }; + + /* + * Base class for SVG endpoints. + */ + var SvgEndpoint = window.SvgEndpoint = function(params) { + var self = this; + SvgComponent.apply(this, [ { + cssClass:params["_jsPlumb"].endpointClass, + originalArgs:arguments, + pointerEventsSpec:"all", + useDivWrapper:true, + _jsPlumb:params["_jsPlumb"] + } ]); + this._paint = function(d, style) { + var s = jsPlumb.extend({}, style); + if (s.outlineColor) { + s.strokeWidth = s.outlineWidth; + s.strokeStyle = jsPlumbUtil.convertStyle(s.outlineColor, true); + } + + if (self.node == null) { + self.node = self.makeNode(d, s); + self.svg.appendChild(self.node); + self.attachListeners(self.node, self); + } + _applyStyles(self.svg, self.node, s, d, self); + _pos(self.node, d); + }; + + this.reattachListeners = function() { + if (self.node) self.reattachListenersForElement(self.node, self); + }; + }; + + /* + * SVG Dot Endpoint + */ + jsPlumb.Endpoints.svg.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("circle", { + "cx" : d[2] / 2, + "cy" : d[3] / 2, + "r" : d[2] / 2 + }); + }; + }; + + /* + * SVG Rectangle Endpoint + */ + jsPlumb.Endpoints.svg.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("rect", { + "width":d[2], + "height":d[3] + }); + }; + }; + + /* + * SVG Image Endpoint is the default image endpoint. + */ + jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image; + /* + * Blank endpoint in svg renderer is the default Blank endpoint. + */ + jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank; + /* + * Label overlay in svg renderer is the default Label overlay. + */ + jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label; + /* + * Custom overlay in svg renderer is the default Custom overlay. + */ + jsPlumb.Overlays.svg.Custom = jsPlumb.Overlays.Custom; + + var AbstractSvgArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + this.isAppendedAtTopLevel = false; + var self = this, path = null; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path", { + "pointer-events":"all" + }); + connector.svg.appendChild(path); + + self.attachListeners(path, connector); + self.attachListeners(path, self); + } + var clazz = originalArgs && (originalArgs.length == 1) ? (originalArgs[0].cssClass || "") : ""; + + _attr(path, { + "d" : makePath(d), + "class" : clazz, + stroke : strokeStyle ? strokeStyle : null, + fill : fillStyle ? fillStyle : null + }); + }; + var makePath = function(d) { + return "M" + d.hxy.x + "," + d.hxy.y + + " L" + d.tail[0].x + "," + d.tail[0].y + + " L" + d.cxy.x + "," + d.cxy.y + + " L" + d.tail[1].x + "," + d.tail[1].y + + " L" + d.hxy.x + "," + d.hxy.y; + }; + this.reattachListeners = function() { + if (path) self.reattachListenersForElement(path, self); + }; + this.cleanup = function() { + if (path != null) jsPlumb.CurrentLibrary.removeElement(path); + }; + }; + + jsPlumb.Overlays.svg.Arrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.svg.PlainArrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.svg.Diamond = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; + + // a test + jsPlumb.Overlays.svg.GuideLines = function() { + var path = null, self = this, path2 = null, p1_1, p1_2; + jsPlumb.Overlays.GuideLines.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + + p1_1 = _node("path"); + connector.svg.appendChild(p1_1); + self.attachListeners(p1_1, connector); + self.attachListeners(p1_1, self); + + p1_2 = _node("path"); + connector.svg.appendChild(p1_2); + self.attachListeners(p1_2, connector); + self.attachListeners(p1_2, self); + + } + + _attr(path, { + "d" : makePath(d[0], d[1]), + stroke : "red", + fill : null + }); + + _attr(p1_1, { + "d" : makePath(d[2][0], d[2][1]), + stroke : "blue", + fill : null + }); + + _attr(p1_2, { + "d" : makePath(d[3][0], d[3][1]), + stroke : "green", + fill : null + }); + }; + + var makePath = function(d1, d2) { + return "M " + d1.x + "," + d1.y + + " L" + d2.x + "," + d2.y; + }; + + }; +})(); \ No newline at end of file diff --git a/archive/1.3.13/jsPlumb-renderers-vml-1.3.13-RC1.js b/archive/1.3.13/jsPlumb-renderers-vml-1.3.13-RC1.js new file mode 100644 index 000000000..dbc6298e3 --- /dev/null +++ b/archive/1.3.13/jsPlumb-renderers-vml-1.3.13-RC1.js @@ -0,0 +1,454 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.13 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the VML renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + // http://ajaxian.com/archives/the-vml-changes-in-ie-8 + // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/ + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + + var vmlAttributeMap = { + "stroke-linejoin":"joinstyle", + "joinstyle":"joinstyle", + "endcap":"endcap", + "miterlimit":"miterlimit" + }, + jsPlumbStylesheet = null; + + if (document.createStyleSheet && document.namespaces) { + + var ruleClasses = [ + ".jsplumb_vml", "jsplumb\\:textbox", "jsplumb\\:oval", "jsplumb\\:rect", + "jsplumb\\:stroke", "jsplumb\\:shape", "jsplumb\\:group" + ], + rule = "behavior:url(#default#VML);position:absolute;"; + + jsPlumbStylesheet = document.createStyleSheet(); + + for (var i = 0; i < ruleClasses.length; i++) + jsPlumbStylesheet.addRule(ruleClasses[i], rule); + + // in this page it is also mentioned that IE requires the extra arg to the namespace + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either. + // var iev = document.documentMode; + //if (!iev || iev < 8) + document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); + //else + // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML"); + } + + jsPlumb.vml = {}; + + var scale = 1000, + + _groupMap = {}, + _getGroup = function(container, connectorClass) { + var id = jsPlumb.getId(container), + g = _groupMap[id]; + if(!g) { + g = _node("group", [0,0,scale, scale], {"class":connectorClass}); + //g.style.position=absolute; + //g["coordsize"] = "1000,1000"; + g.style.backgroundColor="red"; + _groupMap[id] = g; + jsPlumb.appendElement(g, container); // todo if this gets reinstated, remember to use the current jsplumb instance. + //document.body.appendChild(g); + } + return g; + }, + _atts = function(o, atts) { + for (var i in atts) { + // IE8 fix: setattribute does not work after an element has been added to the dom! + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + //o.setAttribute(i, atts[i]); + + /*There is an additional problem when accessing VML elements by using get/setAttribute. The simple solution is following: + + if (document.documentMode==8) { + ele.opacity=1; + } else { + ele.setAttribute(‘opacity’,1); + } + */ + + o[i] = atts[i]; + } + }, + _node = function(name, d, atts, parent, _jsPlumb, deferToJsPlumbContainer) { + atts = atts || {}; + var o = document.createElement("jsplumb:" + name); + if (deferToJsPlumbContainer) + _jsPlumb.appendElement(o, parent); + else + jsPlumb.CurrentLibrary.appendElement(o, parent); + o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml"; + _pos(o, d); + _atts(o, atts); + return o; + }, + _pos = function(o,d, zIndex) { + o.style.left = d[0] + "px"; + o.style.top = d[1] + "px"; + o.style.width= d[2] + "px"; + o.style.height= d[3] + "px"; + o.style.position = "absolute"; + if (zIndex) + o.style.zIndex = zIndex; + }, + _conv = jsPlumb.vml.convertValue = function(v) { + return Math.floor(v * scale); + }, + // tests if the given style is "transparent" and then sets the appropriate opacity node to 0 if so, + // or 1 if not. TODO in the future, support variable opacity. + _maybeSetOpacity = function(styleToWrite, styleToCheck, type, component) { + if ("transparent" === styleToCheck) + component.setOpacity(type, "0.0"); + else + component.setOpacity(type, "1.0"); + }, + _applyStyles = function(node, style, component, _jsPlumb) { + var styleToWrite = {}; + if (style.strokeStyle) { + styleToWrite["stroked"] = "true"; + var strokeColor = jsPlumbUtil.convertStyle(style.strokeStyle, true); + styleToWrite["strokecolor"] = strokeColor; + _maybeSetOpacity(styleToWrite, strokeColor, "stroke", component); + styleToWrite["strokeweight"] = style.lineWidth + "px"; + } + else styleToWrite["stroked"] = "false"; + + if (style.fillStyle) { + styleToWrite["filled"] = "true"; + var fillColor = jsPlumbUtil.convertStyle(style.fillStyle, true); + styleToWrite["fillcolor"] = fillColor; + _maybeSetOpacity(styleToWrite, fillColor, "fill", component); + } + else styleToWrite["filled"] = "false"; + + if(style["dashstyle"]) { + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }, node, _jsPlumb); + } + else + component.strokeNode.dashstyle = style["dashstyle"]; + } + else if (style["stroke-dasharray"] && style["lineWidth"]) { + var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",", + parts = style["stroke-dasharray"].split(sep), + styleToUse = ""; + for(var i = 0; i < parts.length; i++) { + styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep); + } + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }, node, _jsPlumb); + } + else + component.strokeNode.dashstyle = styleToUse; + } + + _atts(node, styleToWrite); + }, + /* + * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. + */ + VmlComponent = function() { + var self = this; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.opacityNodes = { + "stroke":null, + "fill":null + }; + this.initOpacityNodes = function(vml) { + self.opacityNodes["stroke"] = _node("stroke", [0,0,1,1], {opacity:"0.0"}, vml, self._jsPlumb); + self.opacityNodes["fill"] = _node("fill", [0,0,1,1], {opacity:"0.0"}, vml, self._jsPlumb); + }; + this.setOpacity = function(type, value) { + var node = self.opacityNodes[type]; + if (node) node["opacity"] = "" + value; + }; + var displayElements = [ ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el, doNotAppendToCanvas) { + if (!doNotAppendToCanvas) self.canvas.parentNode.appendChild(el); + displayElements.push(el); + }; + }, + /* + * Base class for Vml connectors. extends VmlComponent. + */ + VmlConnector = jsPlumb.VmlConnector = function(params) { + var self = this; + self.strokeNode = null; + self.canvas = null; + VmlComponent.apply(this, arguments); + var clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : ""); + this.paint = function(d, style, anchor) { + if (style != null) { + var path = self.getPath(d), p = { "path":path }; + + //* + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle : jsPlumbUtil.convertStyle(style.outlineColor), + lineWidth : outlineStrokeWidth + }; + for (var aa in vmlAttributeMap) outlineStyle[aa] = style[aa]; + + if (self.bgCanvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.bgCanvas = _node("shape", d, p, params.parent, self._jsPlumb, true); + _pos(self.bgCanvas, d, self.getZIndex()); + self.appendDisplayElement(self.bgCanvas, true); + self.attachListeners(self.bgCanvas, self); + self.initOpacityNodes(self.bgCanvas, ["stroke"]); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.bgCanvas, d, self.getZIndex()); + _atts(self.bgCanvas, p); + } + + _applyStyles(self.bgCanvas, outlineStyle, self); + } + //*/ + + if (self.canvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + if (self.tooltip) p["label"] = self.tooltip; + self.canvas = _node("shape", d, p, params.parent, self._jsPlumb, true); + //var group = _getGroup(params.parent); // test of append everything to a group + //group.appendChild(self.canvas); // sort of works but not exactly; + //params["_jsPlumb"].appendElement(self.canvas, params.parent); //before introduction of groups + + self.appendDisplayElement(self.canvas, true); + self.attachListeners(self.canvas, self); + self.initOpacityNodes(self.canvas, ["stroke"]); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.canvas, d, self.getZIndex()); + _atts(self.canvas, p); + } + + _applyStyles(self.canvas, style, self, self._jsPlumb); + } + }; + + //self.appendDisplayElement(self.canvas); + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + }, + /* + * + * Base class for Vml Endpoints. extends VmlComponent. + * + */ + VmlEndpoint = window.VmlEndpoint = function(params) { + VmlComponent.apply(this, arguments); + var vml = null, self = this, opacityStrokeNode = null, opacityFillNode = null; + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + + var clazz = self._jsPlumb.endpointClass + (params.cssClass ? (" " + params.cssClass) : ""); + + //var group = _getGroup(params.parent); + //group.appendChild(self.canvas); + params["_jsPlumb"].appendElement(self.canvas, params.parent); + + if (self.tooltip) self.canvas.setAttribute("label", self.tooltip); + + this.paint = function(d, style, anchor) { + var p = { }; + + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (vml == null) { + p["class"] = clazz; + vml = self.getVml([0,0, d[2], d[3]], p, anchor, self.canvas, self._jsPlumb); + self.attachListeners(vml, self); + + self.appendDisplayElement(vml, true); + self.appendDisplayElement(self.canvas, true); + + self.initOpacityNodes(vml, ["fill"]); + } + else { + _pos(vml, [0,0, d[2], d[3]]); + _atts(vml, p); + } + + _applyStyles(vml, style, self); + }; + + this.reattachListeners = function() { + if (vml) self.reattachListenersForElement(vml, self); + }; + }; + + jsPlumb.Connectors.vml.Bezier = function() { + jsPlumb.Connectors.Bezier.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Straight = function() { + jsPlumb.Connectors.Straight.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Flowchart = function() { + jsPlumb.Connectors.Flowchart.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l"; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]); + } + // finally draw a line to the end + p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e"; + return p; + }; + }; + + jsPlumb.Endpoints.vml.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor, parent, _jsPlumb) { return _node("oval", d, atts, parent, _jsPlumb); }; + }; + + jsPlumb.Endpoints.vml.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor, parent, _jsPlumb) { return _node("rect", d, atts, parent, _jsPlumb); }; + }; + + /* + * VML Image Endpoint is the same as the default image endpoint. + */ + jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image; + + /** + * placeholder for Blank endpoint in vml renderer. + */ + jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank; + + /** + * VML Label renderer. uses the default label renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label; + + /** + * VML Custom renderer. uses the default Custom renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Custom = jsPlumb.Overlays.Custom; + + var AbstractVmlArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + VmlComponent.apply(this, originalArgs); + var self = this, path = null; + self.canvas = null; + self.isAppendedAtTopLevel = true; + var getPath = function(d, connectorDimensions) { + return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) + + " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + + " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + + " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + + " x e"; + }; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) { + var p = {}; + if (strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] = jsPlumbUtil.convertStyle(strokeStyle, true); + } + if (lineWidth) p["strokeweight"] = lineWidth + "px"; + if (fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = fillStyle; + } + var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + w = Math.abs(xmax - xmin), + h = Math.abs(ymax - ymin), + dim = [xmin, ymin, w, h]; + + // for VML, we create overlays using shapes that have the same dimensions and + // coordsize as their connector - overlays calculate themselves relative to the + // connector (it's how it's been done since the original canvas implementation, because + // for canvas that makes sense). + p["path"] = getPath(d, connectorDimensions); + p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale); + + dim[0] = connectorDimensions[0]; + dim[1] = connectorDimensions[1]; + dim[2] = connectorDimensions[2]; + dim[3] = connectorDimensions[3]; + + if (self.canvas == null) { + //p["class"] = jsPlumb.overlayClass; // TODO currentInstance? + self.canvas = _node("shape", dim, p, connector.canvas.parentNode, connector._jsPlumb, true); + connector.appendDisplayElement(self.canvas, true); + self.attachListeners(self.canvas, connector); + self.attachListeners(self.canvas, self); + } + else { + _pos(self.canvas, dim); + _atts(self.canvas, p); + } + }; + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + + this.cleanup = function() { + if (self.canvas != null) jsPlumb.CurrentLibrary.removeElement(self.canvas); + }; + }; + + jsPlumb.Overlays.vml.Arrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.vml.PlainArrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.vml.Diamond = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.13/jsPlumb-util-1.3.13-RC1.js b/archive/1.3.13/jsPlumb-util-1.3.13-RC1.js new file mode 100644 index 000000000..6c7d0a174 --- /dev/null +++ b/archive/1.3.13/jsPlumb-util-1.3.13-RC1.js @@ -0,0 +1,243 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.13 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the util functions + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ +jsPlumbUtil = { + isArray : function(a) { + return Object.prototype.toString.call(a) === "[object Array]"; + }, + isString : function(s) { + return typeof s === "string"; + }, + isBoolean: function(s) { + return typeof s === "boolean"; + }, + isObject : function(o) { + return Object.prototype.toString.call(o) === "[object Object]"; + }, + merge : function(a, b) { + var c = jsPlumb.extend({}, a); + for (var i in b) { + if (c[i] == null || this.isString(b[i]) || this.isBoolean(b[i])) + c[i] = b[i]; + else { + if (this.isArray(b[i]) && this.isArray(c[i])) { + var ar = []; + ar.push.apply(ar, c[i]); + ar.push.apply(ar, b[i]); + c[i] = ar; + } + else if(this.isObject(c[i]) && this.isObject(b[i])) { + for (var j in b[i]) + c[i][j] = b[i][j]; + } + } + } + return c; + }, + convertStyle : function(s, ignoreAlpha) { + // TODO: jsPlumb should support a separate 'opacity' style member. + if ("transparent" === s) return s; + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + return o; + }, + gradient : function(p1, p2) { + p1 = jsPlumbUtil.isArray(p1) ? p1 : [p1.x, p1.y]; + p2 = jsPlumbUtil.isArray(p2) ? p2 : [p2.x, p2.y]; + return (p2[1] - p1[1]) / (p2[0] - p1[0]); + }, + normal : function(p1, p2) { + return -1 / jsPlumbUtil.gradient(p1,p2); + }, + lineLength : function(p1, p2) { + p1 = jsPlumbUtil.isArray(p1) ? p1 : [p1.x, p1.y]; + p2 = jsPlumbUtil.isArray(p2) ? p2 : [p2.x, p2.y]; + return Math.sqrt(Math.pow(p2[1] - p1[1], 2) + Math.pow(p2[0] - p1[0], 2)); + }, + segment : function(p1, p2) { + p1 = jsPlumbUtil.isArray(p1) ? p1 : [p1.x, p1.y]; + p2 = jsPlumbUtil.isArray(p2) ? p2 : [p2.x, p2.y]; + if (p2[0] > p1[0]) { + return (p2[1] > p1[1]) ? 2 : 1; + } + else { + return (p2[1] > p1[1]) ? 3 : 4; + } + }, + intersects : function(r1, r2) { + var x1 = r1.x, x2 = r1.x + r1.w, y1 = r1.y, y2 = r1.y + r1.h, + a1 = r2.x, a2 = r2.x + r2.w, b1 = r2.y, b2 = r2.y + r2.h; + + return ( (x1 <= a1 && a1 <= x2) && (y1 <= b1 && b1 <= y2) ) || + ( (x1 <= a2 && a2 <= x2) && (y1 <= b1 && b1 <= y2) ) || + ( (x1 <= a1 && a1 <= x2) && (y1 <= b2 && b2 <= y2) ) || + ( (x1 <= a2 && a1 <= x2) && (y1 <= b2 && b2 <= y2) ) || + + ( (a1 <= x1 && x1 <= a2) && (b1 <= y1 && y1 <= b2) ) || + ( (a1 <= x2 && x2 <= a2) && (b1 <= y1 && y1 <= b2) ) || + ( (a1 <= x1 && x1 <= a2) && (b1 <= y2 && y2 <= b2) ) || + ( (a1 <= x2 && x1 <= a2) && (b1 <= y2 && y2 <= b2) ); + }, + segmentMultipliers : [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ], + inverseSegmentMultipliers : [null, [-1, -1], [-1, 1], [1, 1], [1, -1] ], + pointOnLine : function(fromPoint, toPoint, distance) { + var m = jsPlumbUtil.gradient(fromPoint, toPoint), + s = jsPlumbUtil.segment(fromPoint, toPoint), + segmentMultiplier = distance > 0 ? jsPlumbUtil.segmentMultipliers[s] : jsPlumbUtil.inverseSegmentMultipliers[s], + theta = Math.atan(m), + y = Math.abs(distance * Math.sin(theta)) * segmentMultiplier[1], + x = Math.abs(distance * Math.cos(theta)) * segmentMultiplier[0]; + return { x:fromPoint.x + x, y:fromPoint.y + y }; + }, + /** + * calculates a perpendicular to the line fromPoint->toPoint, that passes through toPoint and is 'length' long. + * @param fromPoint + * @param toPoint + * @param length + */ + perpendicularLineTo : function(fromPoint, toPoint, length) { + var m = jsPlumbUtil.gradient(fromPoint, toPoint), + theta2 = Math.atan(-1 / m), + y = length / 2 * Math.sin(theta2), + x = length / 2 * Math.cos(theta2); + return [{x:toPoint.x + x, y:toPoint.y + y}, {x:toPoint.x - x, y:toPoint.y - y}]; + }, + findWithFunction : function(a, f) { + if (a) + for (var i = 0; i < a.length; i++) if (f(a[i])) return i; + return -1; + }, + indexOf : function(l, v) { + return jsPlumbUtil.findWithFunction(l, function(_v) { return _v == v; }); + }, + removeWithFunction : function(a, f) { + var idx = jsPlumbUtil.findWithFunction(a, f); + if (idx > -1) a.splice(idx, 1); + return idx != -1; + }, + remove : function(l, v) { + var idx = jsPlumbUtil.indexOf(l, v); + if (idx > -1) l.splice(idx, 1); + return idx != -1; + }, + // TODO support insert index + addWithFunction : function(list, item, hashFunction) { + if (jsPlumbUtil.findWithFunction(list, hashFunction) == -1) list.push(item); + }, + addToList : function(map, key, value) { + var l = map[key]; + if (l == null) { + l = [], map[key] = l; + } + l.push(value); + return l; + }, + /** + * EventGenerator + * Superclass for objects that generate events - jsPlumb extends this, as does jsPlumbUIComponent, which all the UI elements extend. + */ + EventGenerator : function() { + var _listeners = {}, self = this; + + // this is a list of events that should re-throw any errors that occur during their dispatch. as of 1.3.0 this is private to + // jsPlumb, but it seems feasible that people might want to manipulate this list. the thinking is that we don't want event + // listeners to bring down jsPlumb - or do we. i can't make up my mind about this, but i know i want to hear about it if the "ready" + // event fails, because then my page has most likely not initialised. so i have this halfway-house solution. it will be interesting + // to hear what other people think. + var eventsToDieOn = [ "ready" ]; + + /* + * Binds a listener to an event. + * + * Parameters: + * event - name of the event to bind to. + * listener - function to execute. + */ + this.bind = function(event, listener) { + jsPlumbUtil.addToList(_listeners, event, listener); + return self; + }; + /* + * Fires an update for the given event. + * + * Parameters: + * event - event to fire + * value - value to pass to the event listener(s). + * originalEvent - the original event from the browser + */ + this.fire = function(event, value, originalEvent) { + if (_listeners[event]) { + for ( var i = 0; i < _listeners[event].length; i++) { + // doing it this way rather than catching and then possibly re-throwing means that an error propagated by this + // method will have the whole call stack available in the debugger. + if (jsPlumbUtil.findWithFunction(eventsToDieOn, function(e) { return e === event}) != -1) + _listeners[event][i](value, originalEvent); + else { + // for events we don't want to die on, catch and log. + try { + _listeners[event][i](value, originalEvent); + } catch (e) { + jsPlumbUtil.log("jsPlumb: fire failed for event " + event + " : " + e); + } + } + } + } + return self; + }; + /* + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + this.unbind = function(event) { + if (event) + delete _listeners[event]; + else { + _listeners = {}; + } + return self; + }; + + this.getListener = function(forEvent) { + return _listeners[forEvent]; + }; + }, + logEnabled : true, + log : function() { + if (jsPlumbUtil.logEnabled && typeof console != "undefined") { + try { + var msg = arguments[arguments.length - 1]; + console.log(msg); + } + catch (e) {} + } + }, + group : function(g) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.group(g); }, + groupEnd : function(g) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.groupEnd(g); }, + time : function(t) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.time(t); }, + timeEnd : function(t) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.timeEnd(t); } +}; \ No newline at end of file diff --git a/archive/1.3.13/mootools.jsPlumb-1.3.13-RC1.js b/archive/1.3.13/mootools.jsPlumb-1.3.13-RC1.js new file mode 100644 index 000000000..c7d840929 --- /dev/null +++ b/archive/1.3.13/mootools.jsPlumb-1.3.13-RC1.js @@ -0,0 +1,436 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.13 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the MooTools adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function(now) { + this.parent(now); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event, originalEvent) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr, originalEvent); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, _scope, value) { + var l = list[_scope]; + if (!l) { + l = []; + list[_scope] = l; + } + l.push(value); + }; + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + var _getElementObject = function(el) { + return $(el); + }; + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el) + try { + if (el.className.constructor == SVGAnimatedString) { + jsPlumbUtil.svg.addClass(el, clazz); + } + else el.addClass(clazz); + } + catch (e) { + // SVGAnimatedString not supported; no problem. + el.addClass(clazz); + } + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + m.start(properties); + }, + + appendElement : function(child, parent) { + _getElementObject(parent).grab(child); + }, + + bind : function(el, event, callback) { + el = _getElementObject(el); + el.addEvent(event, callback); + }, + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.event.clientX, eventObject.event.clientY]; + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el), + drags = _draggablesById[id]; + return drags[0].scope; + }, + + getDropEvent : function(args) { + return args[2]; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getDOMElement : function(el) { + // MooTools just decorates the DOM elements. so we have either an ID or an Element here. + return typeof(el) == "String" ? document.getElementById(el) : el; + }, + + getElementObject : _getElementObject, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + getOriginalEvent : function(e) { + return e.event; + }, + + getPageXY : function(eventObject) { + return [eventObject.event.pageX, eventObject.event.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).getParent(); + }, + + getScrollLeft : function(el) { + return null; + }, + + getScrollTop : function(el) { + return null; + }, + + getSelector : function(spec) { + return $$(spec); + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e != null ? e.tagName : null; + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[0], + p = jsPlumb.CurrentLibrary.getElementObject(ui).getPosition(); + return { left:p.x, top:p.y }; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options, isPlumbedComponent) { + var id = jsPlumb.getId(el); + var drag = _draggablesById[id]; + if (!drag) { + var originalZIndex = 0, + originalCursor = null, + dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + + options['onStart'] = jsPlumb.wrap(options['onStart'], function() { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + drag.originalZIndex = originalZIndex; + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + }); + + // DROPPABLES - only relevant if this is a plumbed component, ie. not just the result of the user making some DOM element + // draggable. this is the only library adapter that has to care about this parameter. + var scope = "" + (options["scope"] || jsPlumb.Defaults.Scope), + filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }, + droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + + if (isPlumbedComponent) { + + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr, event) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop', event); + } + }; + } + else + options["droppables"] = []; + + drag = new Drag.Move(el, options); + drag.scope = scope; + drag.originalZIndex = originalZIndex; + _add(_draggablesById, el.get("id"), drag); + // again, only keep a record of this for scope stuff if this is a plumbed component (an endpoint) + if (isPlumbedComponent) { + _add(_draggablesByScope, scope, drag); + } + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + initDroppable : function(el, options, isPlumbedComponent, isPermanent) { + var scope = options["scope"]; + _add(_droppables, scope, el); + var id = jsPlumb.getId(el); + + el.setAttribute("_isPermanentDroppable", isPermanent); // we use this to clear out droppables on drag complete. + _droppableOptions[id] = options; + _droppableScopesById[id] = scope; + var filterFunc = function(entry) { return entry.element != el; }, + draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + isAlreadyDraggable : function(el) { + return _draggablesById[jsPlumb.getId(el)] != null; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + return (typeof Drag != undefined && typeof Drag.Move != undefined); + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el.className.constructor == SVGAnimatedString) { + jsPlumbUtil.svg.removeClass(el, clazz); + } + else el.removeClass(clazz); + } + catch (e) { + // SVGAnimatedString not supported; no problem. + el.removeClass(clazz); + } + }, + + removeElement : function(element, parent) { + var el = _getElementObject(element); + if (el) el.dispose(); // ?? + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + setDragScope : function(el, scope) { + var drag = _draggablesById[el.get("id")]; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + drag[0].droppables = droppables; + }, + + setOffset : function(el, o) { + _getElementObject(el).setPosition({x:o.left, y:o.top}); + }, + + stopDrag : function() { + for (var i in _draggablesById) { + for (var j = 0; j < _draggablesById[i].length; j++) { + var d = _draggablesById[i][j]; + d.stop(); + if (d.originalZIndex != 0) + d.element.setStyle("z-index", d.originalZIndex); + } + } + }, + + /** + * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself. + * the other libraries do not. yui, in fact, cannot even pass an original event. we have to pull out stuff + * from the originalEvent to put in an options object for YUI. + * @param el + * @param event + * @param originalEvent + */ + trigger : function(el, event, originalEvent) { + originalEvent.stopPropagation(); + _getElementObject(el).fireEvent(event, originalEvent); + }, + + unbind : function(el, event, callback) { + el = _getElementObject(el); + el.removeEvent(event, callback); + } + }; + + window.addEvent('domready', jsPlumb.init); +})(); diff --git a/build/1.3.13/tests/android-svg.html b/archive/1.3.13/tests/android-svg.html similarity index 100% rename from build/1.3.13/tests/android-svg.html rename to archive/1.3.13/tests/android-svg.html diff --git a/build/1.3.13/tests/loadTestHarness.html b/archive/1.3.13/tests/loadTestHarness.html similarity index 100% rename from build/1.3.13/tests/loadTestHarness.html rename to archive/1.3.13/tests/loadTestHarness.html diff --git a/build/1.3.13/tests/qunit-all.html b/archive/1.3.13/tests/qunit-all.html similarity index 100% rename from build/1.3.13/tests/qunit-all.html rename to archive/1.3.13/tests/qunit-all.html diff --git a/build/1.3.13/tests/qunit-canvas-jquery-instance.html b/archive/1.3.13/tests/qunit-canvas-jquery-instance.html similarity index 100% rename from build/1.3.13/tests/qunit-canvas-jquery-instance.html rename to archive/1.3.13/tests/qunit-canvas-jquery-instance.html diff --git a/build/1.3.13/tests/qunit-canvas-jquery.html b/archive/1.3.13/tests/qunit-canvas-jquery.html similarity index 100% rename from build/1.3.13/tests/qunit-canvas-jquery.html rename to archive/1.3.13/tests/qunit-canvas-jquery.html diff --git a/build/1.3.13/tests/qunit-canvas-mootools.html b/archive/1.3.13/tests/qunit-canvas-mootools.html similarity index 100% rename from build/1.3.13/tests/qunit-canvas-mootools.html rename to archive/1.3.13/tests/qunit-canvas-mootools.html diff --git a/build/1.3.13/tests/qunit-svg-jquery-instance.html b/archive/1.3.13/tests/qunit-svg-jquery-instance.html similarity index 100% rename from build/1.3.13/tests/qunit-svg-jquery-instance.html rename to archive/1.3.13/tests/qunit-svg-jquery-instance.html diff --git a/build/1.3.13/tests/qunit-svg-jquery.html b/archive/1.3.13/tests/qunit-svg-jquery.html similarity index 100% rename from build/1.3.13/tests/qunit-svg-jquery.html rename to archive/1.3.13/tests/qunit-svg-jquery.html diff --git a/build/1.3.13/tests/qunit-vml-jquery-instance.html b/archive/1.3.13/tests/qunit-vml-jquery-instance.html similarity index 100% rename from build/1.3.13/tests/qunit-vml-jquery-instance.html rename to archive/1.3.13/tests/qunit-vml-jquery-instance.html diff --git a/build/1.3.13/tests/qunit-vml-jquery.html b/archive/1.3.13/tests/qunit-vml-jquery.html similarity index 100% rename from build/1.3.13/tests/qunit-vml-jquery.html rename to archive/1.3.13/tests/qunit-vml-jquery.html diff --git a/build/1.3.13/tests/qunit.css b/archive/1.3.13/tests/qunit.css similarity index 100% rename from build/1.3.13/tests/qunit.css rename to archive/1.3.13/tests/qunit.css diff --git a/archive/1.3.13/yui.jsPlumb-1.3.13-RC1.js b/archive/1.3.13/yui.jsPlumb-1.3.13-RC1.js new file mode 100644 index 000000000..1f4483257 --- /dev/null +++ b/archive/1.3.13/yui.jsPlumb-1.3.13-RC1.js @@ -0,0 +1,384 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.13 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the YUI3 adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +/** + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getOriginalEvent gets the original browser event from some wrapper event. + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + */ +(function() { + + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function( v, b, s ) { + for( var i = +b || 0, l = this.length; i < l; i++ ) { + if( this[i]===v || s && this[i]==v ) { return i; } + } + return -1; + }; + } + + var Y; + + YUI().use('node', 'dd', 'anim', 'node-event-simulate', function(_Y) { + Y = _Y; + Y.on("domready", function() { jsPlumb.init(); }); + }); + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }, + ddEvents = [ "drag:mouseDown", "drag:afterMouseDown", "drag:mouseup", + "drag:align", "drag:removeHandle", "drag:addHandle", "drag:removeInvalid", "drag:addInvalid", + "drag:start", "drag:end", "drag:drag", "drag:over", "drag:enter", + "drag:exit", "drag:drophit", "drag:dropmiss", "drop:over", "drop:enter", "drop:exit", "drop:hit" + ], + animEvents = [ "tween" ], + /** + * helper function to curry callbacks for some element. + */ + _wrapper = function(fn) { + return function() { + try { + return fn.apply(this, arguments); + } + catch (e) { } + }; + }, + /** + * extracts options from the given options object, leaving out event handlers. + */ + _getDDOptions = function(options) { + var o = {}; + for (var i in options) if (ddEvents.indexOf(i) == -1) o[i] = options[i]; + return o; + }, + /** + * attaches all event handlers found in options to the given dragdrop object, and registering + * the given el as the element of interest. + */ + _attachListeners = function(dd, options, eventList) { + for (var ev in options) { + if (eventList.indexOf(ev) != -1) { + var w = _wrapper(options[ev]); + dd.on(ev, w); + } + } + }, + _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}, + _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }, + _lastDragObject = null, + _extend = function(o1, o2) { + for (var i in o2) + o1[i] = o2[i]; + return o1; + }, + _getAttribute = function(el, attributeId) { + return el.getAttribute(attributeId); + }, + _getElementObject = function(el) { + if (el == null) return null; + var eee = null; + eee = typeof el == 'string' ? Y.one('#' + el) : el._node ? el : Y.one(el); + return eee; + }; + + jsPlumb.CurrentLibrary = { + + addClass : function(el, clazz) { + jsPlumb.CurrentLibrary.getElementObject(el).addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + var o = _extend({node:el, to:properties}, options), + id = _getAttribute(el, "id"); + o["tween"] = jsPlumb.wrap(properties["tween"], function() { + // TODO should use a current instance. + jsPlumb.repaint(id); + }); + var a = new Y.Anim(o); + _attachListeners(a, o, animEvents); + a.run(); + }, + + appendElement : function(child, parent) { + _getElementObject (parent).append(child); + }, + + /** + * event binding wrapper. + */ + bind : function(el, event, callback) { + _getElementObject(el).on(event, callback); + }, + + dragEvents : { + "start":"drag:start", "stop":"drag:end", "drag":"drag:drag", "step":"step", + "over":"drop:enter", "out":"drop:exit", "drop":"drop:hit" + }, + + extend : _extend, + + getAttribute : _getAttribute, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + // this is a workaround for the unfortunate fact that in YUI3, the 'drop:exit' event does + // not contain a reference to the drag that just exited. single-threaded js to the + // rescue: we'll just keep it for ourselves. + if (eventArgs[0].drag) _lastDragObject = eventArgs[0].drag.el; + return _lastDragObject; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el), + dd = _draggablesById[id]; + return dd.scope; + }, + + getDropEvent : function(args) { + return args[0]; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getDOMElement : function(el) { + if (typeof(el) == "String") + return document.getElementById(el); + else if (el._node) + return el._node; + else return el; + }, + + getElementObject : _getElementObject, + + getOffset : function(el) { + var o = Y.DOM.getXY(el._node); + return {left:o[0], top:o[1]}; + }, + + getOriginalEvent : function(e) { + return e._event; + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).get("parentNode"); + }, + + getScrollLeft : function(el) { + return 0; + }, + + getScrollTop : function(el) { + return 0; + }, + + getSelector : function(spec) { + var s = Y.all(spec); + return s && s ._nodes ? s._nodes : []; + }, + + getSize : function(el) { + return [ el._node.offsetWidth, el._node.offsetHeight ]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e != null && e._node != null ? e._node.tagName : null; + }, + + getUIPosition : function(args) { + var n = args[0].currentTarget.el._node, + o = Y.DOM.getXY(n); + return {left:o[0], top:o[1]}; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options, isPlumbedComponent) { + var _opts = _getDDOptions(options), + id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drag(_opts); + dd.el = el; + + if (isPlumbedComponent) { + var scope = options['scope'] || jsPlumb.Defaults.Scope; + dd.scope = scope; + _add(_draggablesByScope, scope, dd); + } + + _draggablesById[id] = dd; + + _attachListeners(dd, options, ddEvents); + }, + + initDroppable : function(el, options) { + var _opts = _getDDOptions(options), + id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drop(_opts); + + _droppableOptions[id] = options; + + options = _extend({}, options); + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _droppableScopesById[id] = scope; + + options["drop:enter"] = jsPlumb.wrap(options["drop:enter"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, true); + }, true); + options["drop:exit"] = jsPlumb.wrap(options["drop:exit"], function(e) { + _checkHover(el, false); + }); + options["drop:hit"] = jsPlumb.wrap(options["drop:hit"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, false); + }, true); + + _attachListeners(dd, options, ddEvents); + }, + + isAlreadyDraggable : function(el) { + el = _getElementObject(el); + return el.hasClass("yui3-dd-draggable"); + }, + + isDragSupported : function(el) { return true; }, + isDropSupported : function(el) { return true; }, + removeClass : function(el, clazz) { + jsPlumb.CurrentLibrary.getElementObject(el).removeClass(clazz); + }, + removeElement : function(el) { _getElementObject(el).remove(); }, + + setAttribute : function(el, attributeName, attributeValue) { + el.setAttribute(attributeName, attributeValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + var id = jsPlumb.getId(el), + dd = _draggablesById[id]; + if (dd) dd.set("lock", !draggable); + }, + + setDragScope : function(el, scope) { + var id = jsPlumb.getId(el), + dd = _draggablesById[id]; + if (dd) dd.scope = scope; + }, + + setOffset : function(el, o) { + el = _getElementObject(el); + el.set("top", o.top); + el.set("left", o.left); + }, + + stopDrag : function() { + Y.DD.DDM.stopDrag(); + }, + + trigger : function(el, event, originalEvent) { + originalEvent.stopPropagation(); + _getElementObject(el).simulate(event, { + pageX:originalEvent.pageX, + pageY:originalEvent.pageY, + clientX:originalEvent.clientX, + clientY:originalEvent.clientY + }); + }, + + /** + * event unbinding wrapper. + */ + unbind : function(el, event, callback) { + _getElementObject(el).detach(event, callback); + } + }; +})(); \ No newline at end of file diff --git a/build/1.3.14/demo/apidocs/files/jquery-jsPlumb-1-3-14-all-js.html b/archive/1.3.14/demo/apidocs/files/jquery-jsPlumb-1-3-14-all-js.html similarity index 100% rename from build/1.3.14/demo/apidocs/files/jquery-jsPlumb-1-3-14-all-js.html rename to archive/1.3.14/demo/apidocs/files/jquery-jsPlumb-1-3-14-all-js.html diff --git a/build/1.3.14/demo/apidocs/index.html b/archive/1.3.14/demo/apidocs/index.html similarity index 100% rename from build/1.3.14/demo/apidocs/index.html rename to archive/1.3.14/demo/apidocs/index.html diff --git a/build/1.3.14/demo/apidocs/index/Classes.html b/archive/1.3.14/demo/apidocs/index/Classes.html similarity index 100% rename from build/1.3.14/demo/apidocs/index/Classes.html rename to archive/1.3.14/demo/apidocs/index/Classes.html diff --git a/build/1.3.14/demo/apidocs/index/Files.html b/archive/1.3.14/demo/apidocs/index/Files.html similarity index 100% rename from build/1.3.14/demo/apidocs/index/Files.html rename to archive/1.3.14/demo/apidocs/index/Files.html diff --git a/build/1.3.14/demo/apidocs/index/Functions.html b/archive/1.3.14/demo/apidocs/index/Functions.html similarity index 100% rename from build/1.3.14/demo/apidocs/index/Functions.html rename to archive/1.3.14/demo/apidocs/index/Functions.html diff --git a/build/1.3.14/demo/apidocs/index/Functions2.html b/archive/1.3.14/demo/apidocs/index/Functions2.html similarity index 100% rename from build/1.3.14/demo/apidocs/index/Functions2.html rename to archive/1.3.14/demo/apidocs/index/Functions2.html diff --git a/build/1.3.14/demo/apidocs/index/General.html b/archive/1.3.14/demo/apidocs/index/General.html similarity index 100% rename from build/1.3.14/demo/apidocs/index/General.html rename to archive/1.3.14/demo/apidocs/index/General.html diff --git a/build/1.3.14/demo/apidocs/index/General2.html b/archive/1.3.14/demo/apidocs/index/General2.html similarity index 100% rename from build/1.3.14/demo/apidocs/index/General2.html rename to archive/1.3.14/demo/apidocs/index/General2.html diff --git a/build/1.3.14/demo/apidocs/index/General3.html b/archive/1.3.14/demo/apidocs/index/General3.html similarity index 100% rename from build/1.3.14/demo/apidocs/index/General3.html rename to archive/1.3.14/demo/apidocs/index/General3.html diff --git a/build/1.3.14/demo/apidocs/index/Properties.html b/archive/1.3.14/demo/apidocs/index/Properties.html similarity index 100% rename from build/1.3.14/demo/apidocs/index/Properties.html rename to archive/1.3.14/demo/apidocs/index/Properties.html diff --git a/build/1.3.14/demo/apidocs/javascript/main.js b/archive/1.3.14/demo/apidocs/javascript/main.js similarity index 100% rename from build/1.3.14/demo/apidocs/javascript/main.js rename to archive/1.3.14/demo/apidocs/javascript/main.js diff --git a/build/1.3.14/demo/apidocs/javascript/prettify.js b/archive/1.3.14/demo/apidocs/javascript/prettify.js similarity index 100% rename from build/1.3.14/demo/apidocs/javascript/prettify.js rename to archive/1.3.14/demo/apidocs/javascript/prettify.js diff --git a/build/1.3.14/demo/apidocs/javascript/searchdata.js b/archive/1.3.14/demo/apidocs/javascript/searchdata.js similarity index 100% rename from build/1.3.14/demo/apidocs/javascript/searchdata.js rename to archive/1.3.14/demo/apidocs/javascript/searchdata.js diff --git a/build/1.3.14/demo/apidocs/search/ClassesC.html b/archive/1.3.14/demo/apidocs/search/ClassesC.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/ClassesC.html rename to archive/1.3.14/demo/apidocs/search/ClassesC.html diff --git a/build/1.3.14/demo/apidocs/search/ClassesE.html b/archive/1.3.14/demo/apidocs/search/ClassesE.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/ClassesE.html rename to archive/1.3.14/demo/apidocs/search/ClassesE.html diff --git a/build/1.3.14/demo/apidocs/search/ClassesO.html b/archive/1.3.14/demo/apidocs/search/ClassesO.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/ClassesO.html rename to archive/1.3.14/demo/apidocs/search/ClassesO.html diff --git a/build/1.3.14/demo/apidocs/search/FilesJ.html b/archive/1.3.14/demo/apidocs/search/FilesJ.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/FilesJ.html rename to archive/1.3.14/demo/apidocs/search/FilesJ.html diff --git a/build/1.3.14/demo/apidocs/search/FunctionsA.html b/archive/1.3.14/demo/apidocs/search/FunctionsA.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/FunctionsA.html rename to archive/1.3.14/demo/apidocs/search/FunctionsA.html diff --git a/build/1.3.14/demo/apidocs/search/FunctionsB.html b/archive/1.3.14/demo/apidocs/search/FunctionsB.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/FunctionsB.html rename to archive/1.3.14/demo/apidocs/search/FunctionsB.html diff --git a/build/1.3.14/demo/apidocs/search/FunctionsC.html b/archive/1.3.14/demo/apidocs/search/FunctionsC.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/FunctionsC.html rename to archive/1.3.14/demo/apidocs/search/FunctionsC.html diff --git a/build/1.3.14/demo/apidocs/search/FunctionsD.html b/archive/1.3.14/demo/apidocs/search/FunctionsD.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/FunctionsD.html rename to archive/1.3.14/demo/apidocs/search/FunctionsD.html diff --git a/build/1.3.14/demo/apidocs/search/FunctionsE.html b/archive/1.3.14/demo/apidocs/search/FunctionsE.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/FunctionsE.html rename to archive/1.3.14/demo/apidocs/search/FunctionsE.html diff --git a/build/1.3.14/demo/apidocs/search/FunctionsG.html b/archive/1.3.14/demo/apidocs/search/FunctionsG.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/FunctionsG.html rename to archive/1.3.14/demo/apidocs/search/FunctionsG.html diff --git a/build/1.3.14/demo/apidocs/search/FunctionsH.html b/archive/1.3.14/demo/apidocs/search/FunctionsH.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/FunctionsH.html rename to archive/1.3.14/demo/apidocs/search/FunctionsH.html diff --git a/build/1.3.14/demo/apidocs/search/FunctionsI.html b/archive/1.3.14/demo/apidocs/search/FunctionsI.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/FunctionsI.html rename to archive/1.3.14/demo/apidocs/search/FunctionsI.html diff --git a/build/1.3.14/demo/apidocs/search/FunctionsM.html b/archive/1.3.14/demo/apidocs/search/FunctionsM.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/FunctionsM.html rename to archive/1.3.14/demo/apidocs/search/FunctionsM.html diff --git a/build/1.3.14/demo/apidocs/search/FunctionsP.html b/archive/1.3.14/demo/apidocs/search/FunctionsP.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/FunctionsP.html rename to archive/1.3.14/demo/apidocs/search/FunctionsP.html diff --git a/build/1.3.14/demo/apidocs/search/FunctionsR.html b/archive/1.3.14/demo/apidocs/search/FunctionsR.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/FunctionsR.html rename to archive/1.3.14/demo/apidocs/search/FunctionsR.html diff --git a/build/1.3.14/demo/apidocs/search/FunctionsS.html b/archive/1.3.14/demo/apidocs/search/FunctionsS.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/FunctionsS.html rename to archive/1.3.14/demo/apidocs/search/FunctionsS.html diff --git a/build/1.3.14/demo/apidocs/search/FunctionsT.html b/archive/1.3.14/demo/apidocs/search/FunctionsT.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/FunctionsT.html rename to archive/1.3.14/demo/apidocs/search/FunctionsT.html diff --git a/build/1.3.14/demo/apidocs/search/FunctionsU.html b/archive/1.3.14/demo/apidocs/search/FunctionsU.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/FunctionsU.html rename to archive/1.3.14/demo/apidocs/search/FunctionsU.html diff --git a/build/1.3.14/demo/apidocs/search/GeneralA.html b/archive/1.3.14/demo/apidocs/search/GeneralA.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/GeneralA.html rename to archive/1.3.14/demo/apidocs/search/GeneralA.html diff --git a/build/1.3.14/demo/apidocs/search/GeneralB.html b/archive/1.3.14/demo/apidocs/search/GeneralB.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/GeneralB.html rename to archive/1.3.14/demo/apidocs/search/GeneralB.html diff --git a/build/1.3.14/demo/apidocs/search/GeneralC.html b/archive/1.3.14/demo/apidocs/search/GeneralC.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/GeneralC.html rename to archive/1.3.14/demo/apidocs/search/GeneralC.html diff --git a/build/1.3.14/demo/apidocs/search/GeneralD.html b/archive/1.3.14/demo/apidocs/search/GeneralD.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/GeneralD.html rename to archive/1.3.14/demo/apidocs/search/GeneralD.html diff --git a/build/1.3.14/demo/apidocs/search/GeneralE.html b/archive/1.3.14/demo/apidocs/search/GeneralE.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/GeneralE.html rename to archive/1.3.14/demo/apidocs/search/GeneralE.html diff --git a/build/1.3.14/demo/apidocs/search/GeneralF.html b/archive/1.3.14/demo/apidocs/search/GeneralF.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/GeneralF.html rename to archive/1.3.14/demo/apidocs/search/GeneralF.html diff --git a/build/1.3.14/demo/apidocs/search/GeneralG.html b/archive/1.3.14/demo/apidocs/search/GeneralG.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/GeneralG.html rename to archive/1.3.14/demo/apidocs/search/GeneralG.html diff --git a/build/1.3.14/demo/apidocs/search/GeneralH.html b/archive/1.3.14/demo/apidocs/search/GeneralH.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/GeneralH.html rename to archive/1.3.14/demo/apidocs/search/GeneralH.html diff --git a/build/1.3.14/demo/apidocs/search/GeneralI.html b/archive/1.3.14/demo/apidocs/search/GeneralI.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/GeneralI.html rename to archive/1.3.14/demo/apidocs/search/GeneralI.html diff --git a/build/1.3.14/demo/apidocs/search/GeneralJ.html b/archive/1.3.14/demo/apidocs/search/GeneralJ.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/GeneralJ.html rename to archive/1.3.14/demo/apidocs/search/GeneralJ.html diff --git a/build/1.3.14/demo/apidocs/search/GeneralL.html b/archive/1.3.14/demo/apidocs/search/GeneralL.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/GeneralL.html rename to archive/1.3.14/demo/apidocs/search/GeneralL.html diff --git a/build/1.3.14/demo/apidocs/search/GeneralM.html b/archive/1.3.14/demo/apidocs/search/GeneralM.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/GeneralM.html rename to archive/1.3.14/demo/apidocs/search/GeneralM.html diff --git a/build/1.3.14/demo/apidocs/search/GeneralO.html b/archive/1.3.14/demo/apidocs/search/GeneralO.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/GeneralO.html rename to archive/1.3.14/demo/apidocs/search/GeneralO.html diff --git a/build/1.3.14/demo/apidocs/search/GeneralP.html b/archive/1.3.14/demo/apidocs/search/GeneralP.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/GeneralP.html rename to archive/1.3.14/demo/apidocs/search/GeneralP.html diff --git a/build/1.3.14/demo/apidocs/search/GeneralR.html b/archive/1.3.14/demo/apidocs/search/GeneralR.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/GeneralR.html rename to archive/1.3.14/demo/apidocs/search/GeneralR.html diff --git a/build/1.3.14/demo/apidocs/search/GeneralS.html b/archive/1.3.14/demo/apidocs/search/GeneralS.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/GeneralS.html rename to archive/1.3.14/demo/apidocs/search/GeneralS.html diff --git a/build/1.3.14/demo/apidocs/search/GeneralT.html b/archive/1.3.14/demo/apidocs/search/GeneralT.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/GeneralT.html rename to archive/1.3.14/demo/apidocs/search/GeneralT.html diff --git a/build/1.3.14/demo/apidocs/search/GeneralU.html b/archive/1.3.14/demo/apidocs/search/GeneralU.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/GeneralU.html rename to archive/1.3.14/demo/apidocs/search/GeneralU.html diff --git a/build/1.3.14/demo/apidocs/search/NoResults.html b/archive/1.3.14/demo/apidocs/search/NoResults.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/NoResults.html rename to archive/1.3.14/demo/apidocs/search/NoResults.html diff --git a/build/1.3.14/demo/apidocs/search/PropertiesA.html b/archive/1.3.14/demo/apidocs/search/PropertiesA.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/PropertiesA.html rename to archive/1.3.14/demo/apidocs/search/PropertiesA.html diff --git a/build/1.3.14/demo/apidocs/search/PropertiesB.html b/archive/1.3.14/demo/apidocs/search/PropertiesB.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/PropertiesB.html rename to archive/1.3.14/demo/apidocs/search/PropertiesB.html diff --git a/build/1.3.14/demo/apidocs/search/PropertiesC.html b/archive/1.3.14/demo/apidocs/search/PropertiesC.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/PropertiesC.html rename to archive/1.3.14/demo/apidocs/search/PropertiesC.html diff --git a/build/1.3.14/demo/apidocs/search/PropertiesD.html b/archive/1.3.14/demo/apidocs/search/PropertiesD.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/PropertiesD.html rename to archive/1.3.14/demo/apidocs/search/PropertiesD.html diff --git a/build/1.3.14/demo/apidocs/search/PropertiesE.html b/archive/1.3.14/demo/apidocs/search/PropertiesE.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/PropertiesE.html rename to archive/1.3.14/demo/apidocs/search/PropertiesE.html diff --git a/build/1.3.14/demo/apidocs/search/PropertiesL.html b/archive/1.3.14/demo/apidocs/search/PropertiesL.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/PropertiesL.html rename to archive/1.3.14/demo/apidocs/search/PropertiesL.html diff --git a/build/1.3.14/demo/apidocs/search/PropertiesO.html b/archive/1.3.14/demo/apidocs/search/PropertiesO.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/PropertiesO.html rename to archive/1.3.14/demo/apidocs/search/PropertiesO.html diff --git a/build/1.3.14/demo/apidocs/search/PropertiesP.html b/archive/1.3.14/demo/apidocs/search/PropertiesP.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/PropertiesP.html rename to archive/1.3.14/demo/apidocs/search/PropertiesP.html diff --git a/build/1.3.14/demo/apidocs/search/PropertiesR.html b/archive/1.3.14/demo/apidocs/search/PropertiesR.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/PropertiesR.html rename to archive/1.3.14/demo/apidocs/search/PropertiesR.html diff --git a/build/1.3.14/demo/apidocs/search/PropertiesS.html b/archive/1.3.14/demo/apidocs/search/PropertiesS.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/PropertiesS.html rename to archive/1.3.14/demo/apidocs/search/PropertiesS.html diff --git a/build/1.3.14/demo/apidocs/search/PropertiesT.html b/archive/1.3.14/demo/apidocs/search/PropertiesT.html similarity index 100% rename from build/1.3.14/demo/apidocs/search/PropertiesT.html rename to archive/1.3.14/demo/apidocs/search/PropertiesT.html diff --git a/build/1.3.14/demo/apidocs/styles/main.css b/archive/1.3.14/demo/apidocs/styles/main.css similarity index 100% rename from build/1.3.14/demo/apidocs/styles/main.css rename to archive/1.3.14/demo/apidocs/styles/main.css diff --git a/build/1.3.14/demo/css/anchorDemo.css b/archive/1.3.14/demo/css/anchorDemo.css similarity index 100% rename from build/1.3.14/demo/css/anchorDemo.css rename to archive/1.3.14/demo/css/anchorDemo.css diff --git a/build/1.3.14/demo/css/chartDemo.css b/archive/1.3.14/demo/css/chartDemo.css similarity index 100% rename from build/1.3.14/demo/css/chartDemo.css rename to archive/1.3.14/demo/css/chartDemo.css diff --git a/build/1.3.14/demo/css/demo.css b/archive/1.3.14/demo/css/demo.css similarity index 100% rename from build/1.3.14/demo/css/demo.css rename to archive/1.3.14/demo/css/demo.css diff --git a/build/1.3.14/demo/css/dragAnimDemo.css b/archive/1.3.14/demo/css/dragAnimDemo.css similarity index 100% rename from build/1.3.14/demo/css/dragAnimDemo.css rename to archive/1.3.14/demo/css/dragAnimDemo.css diff --git a/build/1.3.14/demo/css/draggableConnectorsDemo.css b/archive/1.3.14/demo/css/draggableConnectorsDemo.css similarity index 100% rename from build/1.3.14/demo/css/draggableConnectorsDemo.css rename to archive/1.3.14/demo/css/draggableConnectorsDemo.css diff --git a/build/1.3.14/demo/css/dynamicAnchorsDemo.css b/archive/1.3.14/demo/css/dynamicAnchorsDemo.css similarity index 100% rename from build/1.3.14/demo/css/dynamicAnchorsDemo.css rename to archive/1.3.14/demo/css/dynamicAnchorsDemo.css diff --git a/build/1.3.14/demo/css/flowchartDemo.css b/archive/1.3.14/demo/css/flowchartDemo.css similarity index 100% rename from build/1.3.14/demo/css/flowchartDemo.css rename to archive/1.3.14/demo/css/flowchartDemo.css diff --git a/build/1.3.14/demo/css/jsPlumbDemo.css b/archive/1.3.14/demo/css/jsPlumbDemo.css similarity index 100% rename from build/1.3.14/demo/css/jsPlumbDemo.css rename to archive/1.3.14/demo/css/jsPlumbDemo.css diff --git a/build/1.3.14/demo/css/makeTargetDemo.css b/archive/1.3.14/demo/css/makeTargetDemo.css similarity index 100% rename from build/1.3.14/demo/css/makeTargetDemo.css rename to archive/1.3.14/demo/css/makeTargetDemo.css diff --git a/build/1.3.14/demo/css/multipleJsPlumbDemo.css b/archive/1.3.14/demo/css/multipleJsPlumbDemo.css similarity index 100% rename from build/1.3.14/demo/css/multipleJsPlumbDemo.css rename to archive/1.3.14/demo/css/multipleJsPlumbDemo.css diff --git a/build/1.3.14/demo/css/perimeterAnchorsDemo.css b/archive/1.3.14/demo/css/perimeterAnchorsDemo.css similarity index 100% rename from build/1.3.14/demo/css/perimeterAnchorsDemo.css rename to archive/1.3.14/demo/css/perimeterAnchorsDemo.css diff --git a/build/1.3.14/demo/css/selectDemo.css b/archive/1.3.14/demo/css/selectDemo.css similarity index 100% rename from build/1.3.14/demo/css/selectDemo.css rename to archive/1.3.14/demo/css/selectDemo.css diff --git a/build/1.3.14/demo/css/stateMachineDemo.css b/archive/1.3.14/demo/css/stateMachineDemo.css similarity index 100% rename from build/1.3.14/demo/css/stateMachineDemo.css rename to archive/1.3.14/demo/css/stateMachineDemo.css diff --git a/build/1.3.14/demo/doc/archive/1.2.6/content.html b/archive/1.3.14/demo/doc/archive/1.2.6/content.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.2.6/content.html rename to archive/1.3.14/demo/doc/archive/1.2.6/content.html diff --git a/build/1.3.14/demo/doc/archive/1.2.6/index.html b/archive/1.3.14/demo/doc/archive/1.2.6/index.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.2.6/index.html rename to archive/1.3.14/demo/doc/archive/1.2.6/index.html diff --git a/build/1.3.14/demo/doc/archive/1.2.6/usage.html b/archive/1.3.14/demo/doc/archive/1.2.6/usage.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.2.6/usage.html rename to archive/1.3.14/demo/doc/archive/1.2.6/usage.html diff --git a/build/1.3.14/demo/doc/archive/1.3.10/content.html b/archive/1.3.14/demo/doc/archive/1.3.10/content.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.10/content.html rename to archive/1.3.14/demo/doc/archive/1.3.10/content.html diff --git a/build/1.3.14/demo/doc/archive/1.3.10/index.html b/archive/1.3.14/demo/doc/archive/1.3.10/index.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.10/index.html rename to archive/1.3.14/demo/doc/archive/1.3.10/index.html diff --git a/build/1.3.14/demo/doc/archive/1.3.10/jsPlumbDoc.css b/archive/1.3.14/demo/doc/archive/1.3.10/jsPlumbDoc.css similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.10/jsPlumbDoc.css rename to archive/1.3.14/demo/doc/archive/1.3.10/jsPlumbDoc.css diff --git a/build/1.3.14/demo/doc/archive/1.3.10/usage.html b/archive/1.3.14/demo/doc/archive/1.3.10/usage.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.10/usage.html rename to archive/1.3.14/demo/doc/archive/1.3.10/usage.html diff --git a/build/1.3.14/demo/doc/archive/1.3.11/content.html b/archive/1.3.14/demo/doc/archive/1.3.11/content.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.11/content.html rename to archive/1.3.14/demo/doc/archive/1.3.11/content.html diff --git a/build/1.3.14/demo/doc/archive/1.3.11/index.html b/archive/1.3.14/demo/doc/archive/1.3.11/index.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.11/index.html rename to archive/1.3.14/demo/doc/archive/1.3.11/index.html diff --git a/build/1.3.14/demo/doc/archive/1.3.11/jsPlumbDoc.css b/archive/1.3.14/demo/doc/archive/1.3.11/jsPlumbDoc.css similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.11/jsPlumbDoc.css rename to archive/1.3.14/demo/doc/archive/1.3.11/jsPlumbDoc.css diff --git a/build/1.3.14/demo/doc/archive/1.3.11/usage.html b/archive/1.3.14/demo/doc/archive/1.3.11/usage.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.11/usage.html rename to archive/1.3.14/demo/doc/archive/1.3.11/usage.html diff --git a/build/1.3.14/demo/doc/archive/1.3.12/content.html b/archive/1.3.14/demo/doc/archive/1.3.12/content.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.12/content.html rename to archive/1.3.14/demo/doc/archive/1.3.12/content.html diff --git a/build/1.3.14/demo/doc/archive/1.3.12/index.html b/archive/1.3.14/demo/doc/archive/1.3.12/index.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.12/index.html rename to archive/1.3.14/demo/doc/archive/1.3.12/index.html diff --git a/build/1.3.14/demo/doc/archive/1.3.12/jsPlumbDoc.css b/archive/1.3.14/demo/doc/archive/1.3.12/jsPlumbDoc.css similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.12/jsPlumbDoc.css rename to archive/1.3.14/demo/doc/archive/1.3.12/jsPlumbDoc.css diff --git a/build/1.3.14/demo/doc/archive/1.3.12/usage.html b/archive/1.3.14/demo/doc/archive/1.3.12/usage.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.12/usage.html rename to archive/1.3.14/demo/doc/archive/1.3.12/usage.html diff --git a/build/1.3.14/demo/doc/archive/1.3.13/content.html b/archive/1.3.14/demo/doc/archive/1.3.13/content.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.13/content.html rename to archive/1.3.14/demo/doc/archive/1.3.13/content.html diff --git a/build/1.3.14/demo/doc/archive/1.3.13/index.html b/archive/1.3.14/demo/doc/archive/1.3.13/index.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.13/index.html rename to archive/1.3.14/demo/doc/archive/1.3.13/index.html diff --git a/build/1.3.14/demo/doc/archive/1.3.13/jsPlumbDoc.css b/archive/1.3.14/demo/doc/archive/1.3.13/jsPlumbDoc.css similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.13/jsPlumbDoc.css rename to archive/1.3.14/demo/doc/archive/1.3.13/jsPlumbDoc.css diff --git a/build/1.3.14/demo/doc/archive/1.3.13/usage.html b/archive/1.3.14/demo/doc/archive/1.3.13/usage.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.13/usage.html rename to archive/1.3.14/demo/doc/archive/1.3.13/usage.html diff --git a/build/1.3.14/demo/doc/archive/1.3.2/content.html b/archive/1.3.14/demo/doc/archive/1.3.2/content.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.2/content.html rename to archive/1.3.14/demo/doc/archive/1.3.2/content.html diff --git a/build/1.3.14/demo/doc/archive/1.3.2/index.html b/archive/1.3.14/demo/doc/archive/1.3.2/index.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.2/index.html rename to archive/1.3.14/demo/doc/archive/1.3.2/index.html diff --git a/build/1.3.14/demo/doc/archive/1.3.2/usage.html b/archive/1.3.14/demo/doc/archive/1.3.2/usage.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.2/usage.html rename to archive/1.3.14/demo/doc/archive/1.3.2/usage.html diff --git a/build/1.3.14/demo/doc/archive/1.3.3/content.html b/archive/1.3.14/demo/doc/archive/1.3.3/content.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.3/content.html rename to archive/1.3.14/demo/doc/archive/1.3.3/content.html diff --git a/build/1.3.14/demo/doc/archive/1.3.3/index.html b/archive/1.3.14/demo/doc/archive/1.3.3/index.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.3/index.html rename to archive/1.3.14/demo/doc/archive/1.3.3/index.html diff --git a/build/1.3.14/demo/doc/archive/1.3.3/jsPlumbDoc.css b/archive/1.3.14/demo/doc/archive/1.3.3/jsPlumbDoc.css similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.3/jsPlumbDoc.css rename to archive/1.3.14/demo/doc/archive/1.3.3/jsPlumbDoc.css diff --git a/build/1.3.14/demo/doc/archive/1.3.3/usage.html b/archive/1.3.14/demo/doc/archive/1.3.3/usage.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.3/usage.html rename to archive/1.3.14/demo/doc/archive/1.3.3/usage.html diff --git a/build/1.3.14/demo/doc/archive/1.3.4/content.html b/archive/1.3.14/demo/doc/archive/1.3.4/content.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.4/content.html rename to archive/1.3.14/demo/doc/archive/1.3.4/content.html diff --git a/build/1.3.14/demo/doc/archive/1.3.4/index.html b/archive/1.3.14/demo/doc/archive/1.3.4/index.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.4/index.html rename to archive/1.3.14/demo/doc/archive/1.3.4/index.html diff --git a/build/1.3.14/demo/doc/archive/1.3.4/jsPlumbDoc.css b/archive/1.3.14/demo/doc/archive/1.3.4/jsPlumbDoc.css similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.4/jsPlumbDoc.css rename to archive/1.3.14/demo/doc/archive/1.3.4/jsPlumbDoc.css diff --git a/build/1.3.14/demo/doc/archive/1.3.4/usage.html b/archive/1.3.14/demo/doc/archive/1.3.4/usage.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.4/usage.html rename to archive/1.3.14/demo/doc/archive/1.3.4/usage.html diff --git a/build/1.3.14/demo/doc/archive/1.3.5/content.html b/archive/1.3.14/demo/doc/archive/1.3.5/content.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.5/content.html rename to archive/1.3.14/demo/doc/archive/1.3.5/content.html diff --git a/build/1.3.14/demo/doc/archive/1.3.5/index.html b/archive/1.3.14/demo/doc/archive/1.3.5/index.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.5/index.html rename to archive/1.3.14/demo/doc/archive/1.3.5/index.html diff --git a/build/1.3.14/demo/doc/archive/1.3.5/jsPlumbDoc.css b/archive/1.3.14/demo/doc/archive/1.3.5/jsPlumbDoc.css similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.5/jsPlumbDoc.css rename to archive/1.3.14/demo/doc/archive/1.3.5/jsPlumbDoc.css diff --git a/build/1.3.14/demo/doc/archive/1.3.5/usage.html b/archive/1.3.14/demo/doc/archive/1.3.5/usage.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.5/usage.html rename to archive/1.3.14/demo/doc/archive/1.3.5/usage.html diff --git a/build/1.3.14/demo/doc/archive/1.3.6/content.html b/archive/1.3.14/demo/doc/archive/1.3.6/content.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.6/content.html rename to archive/1.3.14/demo/doc/archive/1.3.6/content.html diff --git a/build/1.3.14/demo/doc/archive/1.3.6/index.html b/archive/1.3.14/demo/doc/archive/1.3.6/index.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.6/index.html rename to archive/1.3.14/demo/doc/archive/1.3.6/index.html diff --git a/build/1.3.14/demo/doc/archive/1.3.6/jsPlumbDoc.css b/archive/1.3.14/demo/doc/archive/1.3.6/jsPlumbDoc.css similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.6/jsPlumbDoc.css rename to archive/1.3.14/demo/doc/archive/1.3.6/jsPlumbDoc.css diff --git a/build/1.3.14/demo/doc/archive/1.3.6/usage.html b/archive/1.3.14/demo/doc/archive/1.3.6/usage.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.6/usage.html rename to archive/1.3.14/demo/doc/archive/1.3.6/usage.html diff --git a/build/1.3.14/demo/doc/archive/1.3.7/content.html b/archive/1.3.14/demo/doc/archive/1.3.7/content.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.7/content.html rename to archive/1.3.14/demo/doc/archive/1.3.7/content.html diff --git a/build/1.3.14/demo/doc/archive/1.3.7/index.html b/archive/1.3.14/demo/doc/archive/1.3.7/index.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.7/index.html rename to archive/1.3.14/demo/doc/archive/1.3.7/index.html diff --git a/build/1.3.14/demo/doc/archive/1.3.7/jsPlumbDoc.css b/archive/1.3.14/demo/doc/archive/1.3.7/jsPlumbDoc.css similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.7/jsPlumbDoc.css rename to archive/1.3.14/demo/doc/archive/1.3.7/jsPlumbDoc.css diff --git a/build/1.3.14/demo/doc/archive/1.3.7/usage.html b/archive/1.3.14/demo/doc/archive/1.3.7/usage.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.7/usage.html rename to archive/1.3.14/demo/doc/archive/1.3.7/usage.html diff --git a/build/1.3.14/demo/doc/archive/1.3.8/content.html b/archive/1.3.14/demo/doc/archive/1.3.8/content.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.8/content.html rename to archive/1.3.14/demo/doc/archive/1.3.8/content.html diff --git a/build/1.3.14/demo/doc/archive/1.3.8/index.html b/archive/1.3.14/demo/doc/archive/1.3.8/index.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.8/index.html rename to archive/1.3.14/demo/doc/archive/1.3.8/index.html diff --git a/build/1.3.14/demo/doc/archive/1.3.8/jsPlumbDoc.css b/archive/1.3.14/demo/doc/archive/1.3.8/jsPlumbDoc.css similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.8/jsPlumbDoc.css rename to archive/1.3.14/demo/doc/archive/1.3.8/jsPlumbDoc.css diff --git a/build/1.3.14/demo/doc/archive/1.3.8/usage.html b/archive/1.3.14/demo/doc/archive/1.3.8/usage.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.8/usage.html rename to archive/1.3.14/demo/doc/archive/1.3.8/usage.html diff --git a/build/1.3.14/demo/doc/archive/1.3.9/content.html b/archive/1.3.14/demo/doc/archive/1.3.9/content.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.9/content.html rename to archive/1.3.14/demo/doc/archive/1.3.9/content.html diff --git a/build/1.3.14/demo/doc/archive/1.3.9/index.html b/archive/1.3.14/demo/doc/archive/1.3.9/index.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.9/index.html rename to archive/1.3.14/demo/doc/archive/1.3.9/index.html diff --git a/build/1.3.14/demo/doc/archive/1.3.9/jsPlumbDoc.css b/archive/1.3.14/demo/doc/archive/1.3.9/jsPlumbDoc.css similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.9/jsPlumbDoc.css rename to archive/1.3.14/demo/doc/archive/1.3.9/jsPlumbDoc.css diff --git a/build/1.3.14/demo/doc/archive/1.3.9/usage.html b/archive/1.3.14/demo/doc/archive/1.3.9/usage.html similarity index 100% rename from build/1.3.14/demo/doc/archive/1.3.9/usage.html rename to archive/1.3.14/demo/doc/archive/1.3.9/usage.html diff --git a/build/1.3.14/demo/doc/content.html b/archive/1.3.14/demo/doc/content.html similarity index 100% rename from build/1.3.14/demo/doc/content.html rename to archive/1.3.14/demo/doc/content.html diff --git a/build/1.3.14/demo/doc/index.html b/archive/1.3.14/demo/doc/index.html similarity index 100% rename from build/1.3.14/demo/doc/index.html rename to archive/1.3.14/demo/doc/index.html diff --git a/build/1.3.14/demo/doc/jsPlumbDoc.css b/archive/1.3.14/demo/doc/jsPlumbDoc.css similarity index 100% rename from build/1.3.14/demo/doc/jsPlumbDoc.css rename to archive/1.3.14/demo/doc/jsPlumbDoc.css diff --git a/build/1.3.14/demo/doc/usage.html b/archive/1.3.14/demo/doc/usage.html similarity index 100% rename from build/1.3.14/demo/doc/usage.html rename to archive/1.3.14/demo/doc/usage.html diff --git a/build/1.3.14/demo/img/bigdot.jpg b/archive/1.3.14/demo/img/bigdot.jpg similarity index 100% rename from build/1.3.14/demo/img/bigdot.jpg rename to archive/1.3.14/demo/img/bigdot.jpg diff --git a/build/1.3.14/demo/img/bigdot.png b/archive/1.3.14/demo/img/bigdot.png similarity index 100% rename from build/1.3.14/demo/img/bigdot.png rename to archive/1.3.14/demo/img/bigdot.png diff --git a/build/1.3.14/demo/img/bigdot.xcf b/archive/1.3.14/demo/img/bigdot.xcf similarity index 100% rename from build/1.3.14/demo/img/bigdot.xcf rename to archive/1.3.14/demo/img/bigdot.xcf diff --git a/build/1.3.14/demo/img/circle.png b/archive/1.3.14/demo/img/circle.png similarity index 100% rename from build/1.3.14/demo/img/circle.png rename to archive/1.3.14/demo/img/circle.png diff --git a/build/1.3.14/demo/img/diamond.png b/archive/1.3.14/demo/img/diamond.png similarity index 100% rename from build/1.3.14/demo/img/diamond.png rename to archive/1.3.14/demo/img/diamond.png diff --git a/build/1.3.14/demo/img/dragging_1.jpg b/archive/1.3.14/demo/img/dragging_1.jpg similarity index 100% rename from build/1.3.14/demo/img/dragging_1.jpg rename to archive/1.3.14/demo/img/dragging_1.jpg diff --git a/build/1.3.14/demo/img/dragging_2.jpg b/archive/1.3.14/demo/img/dragging_2.jpg similarity index 100% rename from build/1.3.14/demo/img/dragging_2.jpg rename to archive/1.3.14/demo/img/dragging_2.jpg diff --git a/build/1.3.14/demo/img/dragging_3.jpg b/archive/1.3.14/demo/img/dragging_3.jpg similarity index 100% rename from build/1.3.14/demo/img/dragging_3.jpg rename to archive/1.3.14/demo/img/dragging_3.jpg diff --git a/build/1.3.14/demo/img/dynamicAnchorBg.jpg b/archive/1.3.14/demo/img/dynamicAnchorBg.jpg similarity index 100% rename from build/1.3.14/demo/img/dynamicAnchorBg.jpg rename to archive/1.3.14/demo/img/dynamicAnchorBg.jpg diff --git a/build/1.3.14/demo/img/ellipse.png b/archive/1.3.14/demo/img/ellipse.png similarity index 100% rename from build/1.3.14/demo/img/ellipse.png rename to archive/1.3.14/demo/img/ellipse.png diff --git a/build/1.3.14/demo/img/endpointTest1.png b/archive/1.3.14/demo/img/endpointTest1.png similarity index 100% rename from build/1.3.14/demo/img/endpointTest1.png rename to archive/1.3.14/demo/img/endpointTest1.png diff --git a/build/1.3.14/demo/img/endpointTest1.xcf b/archive/1.3.14/demo/img/endpointTest1.xcf similarity index 100% rename from build/1.3.14/demo/img/endpointTest1.xcf rename to archive/1.3.14/demo/img/endpointTest1.xcf diff --git a/build/1.3.14/demo/img/index-bg.gif b/archive/1.3.14/demo/img/index-bg.gif similarity index 100% rename from build/1.3.14/demo/img/index-bg.gif rename to archive/1.3.14/demo/img/index-bg.gif diff --git a/build/1.3.14/demo/img/issue4_final.jpg b/archive/1.3.14/demo/img/issue4_final.jpg similarity index 100% rename from build/1.3.14/demo/img/issue4_final.jpg rename to archive/1.3.14/demo/img/issue4_final.jpg diff --git a/build/1.3.14/demo/img/littledot.png b/archive/1.3.14/demo/img/littledot.png similarity index 100% rename from build/1.3.14/demo/img/littledot.png rename to archive/1.3.14/demo/img/littledot.png diff --git a/build/1.3.14/demo/img/littledot.xcf b/archive/1.3.14/demo/img/littledot.xcf similarity index 100% rename from build/1.3.14/demo/img/littledot.xcf rename to archive/1.3.14/demo/img/littledot.xcf diff --git a/build/1.3.14/demo/img/pattern.jpg b/archive/1.3.14/demo/img/pattern.jpg similarity index 100% rename from build/1.3.14/demo/img/pattern.jpg rename to archive/1.3.14/demo/img/pattern.jpg diff --git a/build/1.3.14/demo/img/rectangle.png b/archive/1.3.14/demo/img/rectangle.png similarity index 100% rename from build/1.3.14/demo/img/rectangle.png rename to archive/1.3.14/demo/img/rectangle.png diff --git a/build/1.3.14/demo/img/square.png b/archive/1.3.14/demo/img/square.png similarity index 100% rename from build/1.3.14/demo/img/square.png rename to archive/1.3.14/demo/img/square.png diff --git a/build/1.3.14/demo/img/swappedAnchors.jpg b/archive/1.3.14/demo/img/swappedAnchors.jpg similarity index 100% rename from build/1.3.14/demo/img/swappedAnchors.jpg rename to archive/1.3.14/demo/img/swappedAnchors.jpg diff --git a/build/1.3.14/demo/img/triangle.png b/archive/1.3.14/demo/img/triangle.png similarity index 100% rename from build/1.3.14/demo/img/triangle.png rename to archive/1.3.14/demo/img/triangle.png diff --git a/build/1.3.14/demo/img/triangle_90.png b/archive/1.3.14/demo/img/triangle_90.png similarity index 100% rename from build/1.3.14/demo/img/triangle_90.png rename to archive/1.3.14/demo/img/triangle_90.png diff --git a/build/1.3.14/demo/index.html b/archive/1.3.14/demo/index.html similarity index 100% rename from build/1.3.14/demo/index.html rename to archive/1.3.14/demo/index.html diff --git a/build/1.3.14/demo/jquery/anchorDemo.html b/archive/1.3.14/demo/jquery/anchorDemo.html similarity index 100% rename from build/1.3.14/demo/jquery/anchorDemo.html rename to archive/1.3.14/demo/jquery/anchorDemo.html diff --git a/build/1.3.14/demo/jquery/chartDemo.html b/archive/1.3.14/demo/jquery/chartDemo.html similarity index 100% rename from build/1.3.14/demo/jquery/chartDemo.html rename to archive/1.3.14/demo/jquery/chartDemo.html diff --git a/build/1.3.14/demo/jquery/demo.html b/archive/1.3.14/demo/jquery/demo.html similarity index 100% rename from build/1.3.14/demo/jquery/demo.html rename to archive/1.3.14/demo/jquery/demo.html diff --git a/build/1.3.14/demo/jquery/dragAnimDemo.html b/archive/1.3.14/demo/jquery/dragAnimDemo.html similarity index 100% rename from build/1.3.14/demo/jquery/dragAnimDemo.html rename to archive/1.3.14/demo/jquery/dragAnimDemo.html diff --git a/build/1.3.14/demo/jquery/draggableConnectorsDemo.html b/archive/1.3.14/demo/jquery/draggableConnectorsDemo.html similarity index 100% rename from build/1.3.14/demo/jquery/draggableConnectorsDemo.html rename to archive/1.3.14/demo/jquery/draggableConnectorsDemo.html diff --git a/build/1.3.14/demo/jquery/dynamicAnchorsDemo.html b/archive/1.3.14/demo/jquery/dynamicAnchorsDemo.html similarity index 100% rename from build/1.3.14/demo/jquery/dynamicAnchorsDemo.html rename to archive/1.3.14/demo/jquery/dynamicAnchorsDemo.html diff --git a/build/1.3.14/demo/jquery/flowchartConnectorsDemo.html b/archive/1.3.14/demo/jquery/flowchartConnectorsDemo.html similarity index 100% rename from build/1.3.14/demo/jquery/flowchartConnectorsDemo.html rename to archive/1.3.14/demo/jquery/flowchartConnectorsDemo.html diff --git a/build/1.3.14/demo/jquery/loadTest.html b/archive/1.3.14/demo/jquery/loadTest.html similarity index 100% rename from build/1.3.14/demo/jquery/loadTest.html rename to archive/1.3.14/demo/jquery/loadTest.html diff --git a/build/1.3.14/demo/jquery/perimeterAnchorsDemo.html b/archive/1.3.14/demo/jquery/perimeterAnchorsDemo.html similarity index 100% rename from build/1.3.14/demo/jquery/perimeterAnchorsDemo.html rename to archive/1.3.14/demo/jquery/perimeterAnchorsDemo.html diff --git a/build/1.3.14/demo/jquery/stateMachineDemo.html b/archive/1.3.14/demo/jquery/stateMachineDemo.html similarity index 100% rename from build/1.3.14/demo/jquery/stateMachineDemo.html rename to archive/1.3.14/demo/jquery/stateMachineDemo.html diff --git a/build/1.3.14/demo/js/anchorDemo-jquery.js b/archive/1.3.14/demo/js/anchorDemo-jquery.js similarity index 100% rename from build/1.3.14/demo/js/anchorDemo-jquery.js rename to archive/1.3.14/demo/js/anchorDemo-jquery.js diff --git a/build/1.3.14/demo/js/anchorDemo-mootools.js b/archive/1.3.14/demo/js/anchorDemo-mootools.js similarity index 100% rename from build/1.3.14/demo/js/anchorDemo-mootools.js rename to archive/1.3.14/demo/js/anchorDemo-mootools.js diff --git a/build/1.3.14/demo/js/anchorDemo-yui3.js b/archive/1.3.14/demo/js/anchorDemo-yui3.js similarity index 100% rename from build/1.3.14/demo/js/anchorDemo-yui3.js rename to archive/1.3.14/demo/js/anchorDemo-yui3.js diff --git a/build/1.3.14/demo/js/anchorDemo.js b/archive/1.3.14/demo/js/anchorDemo.js similarity index 100% rename from build/1.3.14/demo/js/anchorDemo.js rename to archive/1.3.14/demo/js/anchorDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.0/anchorDemo.js b/archive/1.3.14/demo/js/archive/1.3.0/anchorDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.0/anchorDemo.js rename to archive/1.3.14/demo/js/archive/1.3.0/anchorDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.0/chartDemo.js b/archive/1.3.14/demo/js/archive/1.3.0/chartDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.0/chartDemo.js rename to archive/1.3.14/demo/js/archive/1.3.0/chartDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.0/demo-helper-jquery.js b/archive/1.3.14/demo/js/archive/1.3.0/demo-helper-jquery.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.0/demo-helper-jquery.js rename to archive/1.3.14/demo/js/archive/1.3.0/demo-helper-jquery.js diff --git a/build/1.3.14/demo/js/archive/1.3.0/demo-helper-mootools.js b/archive/1.3.14/demo/js/archive/1.3.0/demo-helper-mootools.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.0/demo-helper-mootools.js rename to archive/1.3.14/demo/js/archive/1.3.0/demo-helper-mootools.js diff --git a/build/1.3.14/demo/js/archive/1.3.0/demo-helper-yui3.js b/archive/1.3.14/demo/js/archive/1.3.0/demo-helper-yui3.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.0/demo-helper-yui3.js rename to archive/1.3.14/demo/js/archive/1.3.0/demo-helper-yui3.js diff --git a/build/1.3.14/demo/js/archive/1.3.0/demo.js b/archive/1.3.14/demo/js/archive/1.3.0/demo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.0/demo.js rename to archive/1.3.14/demo/js/archive/1.3.0/demo.js diff --git a/build/1.3.14/demo/js/archive/1.3.0/dragAnimDemo-jquery.js b/archive/1.3.14/demo/js/archive/1.3.0/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.0/dragAnimDemo-jquery.js rename to archive/1.3.14/demo/js/archive/1.3.0/dragAnimDemo-jquery.js diff --git a/build/1.3.14/demo/js/archive/1.3.0/dragAnimDemo-mootools.js b/archive/1.3.14/demo/js/archive/1.3.0/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.0/dragAnimDemo-mootools.js rename to archive/1.3.14/demo/js/archive/1.3.0/dragAnimDemo-mootools.js diff --git a/build/1.3.14/demo/js/archive/1.3.0/dragAnimDemo-yui3.js b/archive/1.3.14/demo/js/archive/1.3.0/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.0/dragAnimDemo-yui3.js rename to archive/1.3.14/demo/js/archive/1.3.0/dragAnimDemo-yui3.js diff --git a/build/1.3.14/demo/js/archive/1.3.0/dragAnimDemo.js b/archive/1.3.14/demo/js/archive/1.3.0/dragAnimDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.0/dragAnimDemo.js rename to archive/1.3.14/demo/js/archive/1.3.0/dragAnimDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js b/archive/1.3.14/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js rename to archive/1.3.14/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.14/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js b/archive/1.3.14/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js rename to archive/1.3.14/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.14/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js b/archive/1.3.14/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js rename to archive/1.3.14/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.14/demo/js/archive/1.3.0/draggableConnectorsDemo.js b/archive/1.3.14/demo/js/archive/1.3.0/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.0/draggableConnectorsDemo.js rename to archive/1.3.14/demo/js/archive/1.3.0/draggableConnectorsDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.0/dynamicAnchorsDemo.js b/archive/1.3.14/demo/js/archive/1.3.0/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.0/dynamicAnchorsDemo.js rename to archive/1.3.14/demo/js/archive/1.3.0/dynamicAnchorsDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.0/flowchartConnectorsDemo.js b/archive/1.3.14/demo/js/archive/1.3.0/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.0/flowchartConnectorsDemo.js rename to archive/1.3.14/demo/js/archive/1.3.0/flowchartConnectorsDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.1/anchorDemo.js b/archive/1.3.14/demo/js/archive/1.3.1/anchorDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.1/anchorDemo.js rename to archive/1.3.14/demo/js/archive/1.3.1/anchorDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.1/chartDemo.js b/archive/1.3.14/demo/js/archive/1.3.1/chartDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.1/chartDemo.js rename to archive/1.3.14/demo/js/archive/1.3.1/chartDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.1/demo-helper-jquery.js b/archive/1.3.14/demo/js/archive/1.3.1/demo-helper-jquery.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.1/demo-helper-jquery.js rename to archive/1.3.14/demo/js/archive/1.3.1/demo-helper-jquery.js diff --git a/build/1.3.14/demo/js/archive/1.3.1/demo-helper-mootools.js b/archive/1.3.14/demo/js/archive/1.3.1/demo-helper-mootools.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.1/demo-helper-mootools.js rename to archive/1.3.14/demo/js/archive/1.3.1/demo-helper-mootools.js diff --git a/build/1.3.14/demo/js/archive/1.3.1/demo-helper-yui3.js b/archive/1.3.14/demo/js/archive/1.3.1/demo-helper-yui3.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.1/demo-helper-yui3.js rename to archive/1.3.14/demo/js/archive/1.3.1/demo-helper-yui3.js diff --git a/build/1.3.14/demo/js/archive/1.3.1/demo.js b/archive/1.3.14/demo/js/archive/1.3.1/demo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.1/demo.js rename to archive/1.3.14/demo/js/archive/1.3.1/demo.js diff --git a/build/1.3.14/demo/js/archive/1.3.1/dragAnimDemo-jquery.js b/archive/1.3.14/demo/js/archive/1.3.1/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.1/dragAnimDemo-jquery.js rename to archive/1.3.14/demo/js/archive/1.3.1/dragAnimDemo-jquery.js diff --git a/build/1.3.14/demo/js/archive/1.3.1/dragAnimDemo-mootools.js b/archive/1.3.14/demo/js/archive/1.3.1/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.1/dragAnimDemo-mootools.js rename to archive/1.3.14/demo/js/archive/1.3.1/dragAnimDemo-mootools.js diff --git a/build/1.3.14/demo/js/archive/1.3.1/dragAnimDemo-yui3.js b/archive/1.3.14/demo/js/archive/1.3.1/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.1/dragAnimDemo-yui3.js rename to archive/1.3.14/demo/js/archive/1.3.1/dragAnimDemo-yui3.js diff --git a/build/1.3.14/demo/js/archive/1.3.1/dragAnimDemo.js b/archive/1.3.14/demo/js/archive/1.3.1/dragAnimDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.1/dragAnimDemo.js rename to archive/1.3.14/demo/js/archive/1.3.1/dragAnimDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js b/archive/1.3.14/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js rename to archive/1.3.14/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.14/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js b/archive/1.3.14/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js rename to archive/1.3.14/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.14/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js b/archive/1.3.14/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js rename to archive/1.3.14/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.14/demo/js/archive/1.3.1/draggableConnectorsDemo.js b/archive/1.3.14/demo/js/archive/1.3.1/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.1/draggableConnectorsDemo.js rename to archive/1.3.14/demo/js/archive/1.3.1/draggableConnectorsDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.1/dynamicAnchorsDemo.js b/archive/1.3.14/demo/js/archive/1.3.1/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.1/dynamicAnchorsDemo.js rename to archive/1.3.14/demo/js/archive/1.3.1/dynamicAnchorsDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.1/flowchartConnectorsDemo.js b/archive/1.3.14/demo/js/archive/1.3.1/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.1/flowchartConnectorsDemo.js rename to archive/1.3.14/demo/js/archive/1.3.1/flowchartConnectorsDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.2/anchorDemo.js b/archive/1.3.14/demo/js/archive/1.3.2/anchorDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.2/anchorDemo.js rename to archive/1.3.14/demo/js/archive/1.3.2/anchorDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.2/chartDemo.js b/archive/1.3.14/demo/js/archive/1.3.2/chartDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.2/chartDemo.js rename to archive/1.3.14/demo/js/archive/1.3.2/chartDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.2/demo-helper-jquery.js b/archive/1.3.14/demo/js/archive/1.3.2/demo-helper-jquery.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.2/demo-helper-jquery.js rename to archive/1.3.14/demo/js/archive/1.3.2/demo-helper-jquery.js diff --git a/build/1.3.14/demo/js/archive/1.3.2/demo-helper-mootools.js b/archive/1.3.14/demo/js/archive/1.3.2/demo-helper-mootools.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.2/demo-helper-mootools.js rename to archive/1.3.14/demo/js/archive/1.3.2/demo-helper-mootools.js diff --git a/build/1.3.14/demo/js/archive/1.3.2/demo-helper-yui3.js b/archive/1.3.14/demo/js/archive/1.3.2/demo-helper-yui3.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.2/demo-helper-yui3.js rename to archive/1.3.14/demo/js/archive/1.3.2/demo-helper-yui3.js diff --git a/build/1.3.14/demo/js/archive/1.3.2/demo.js b/archive/1.3.14/demo/js/archive/1.3.2/demo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.2/demo.js rename to archive/1.3.14/demo/js/archive/1.3.2/demo.js diff --git a/build/1.3.14/demo/js/archive/1.3.2/dragAnimDemo-jquery.js b/archive/1.3.14/demo/js/archive/1.3.2/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.2/dragAnimDemo-jquery.js rename to archive/1.3.14/demo/js/archive/1.3.2/dragAnimDemo-jquery.js diff --git a/build/1.3.14/demo/js/archive/1.3.2/dragAnimDemo-mootools.js b/archive/1.3.14/demo/js/archive/1.3.2/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.2/dragAnimDemo-mootools.js rename to archive/1.3.14/demo/js/archive/1.3.2/dragAnimDemo-mootools.js diff --git a/build/1.3.14/demo/js/archive/1.3.2/dragAnimDemo-yui3.js b/archive/1.3.14/demo/js/archive/1.3.2/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.2/dragAnimDemo-yui3.js rename to archive/1.3.14/demo/js/archive/1.3.2/dragAnimDemo-yui3.js diff --git a/build/1.3.14/demo/js/archive/1.3.2/dragAnimDemo.js b/archive/1.3.14/demo/js/archive/1.3.2/dragAnimDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.2/dragAnimDemo.js rename to archive/1.3.14/demo/js/archive/1.3.2/dragAnimDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js b/archive/1.3.14/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js rename to archive/1.3.14/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.14/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js b/archive/1.3.14/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js rename to archive/1.3.14/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.14/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js b/archive/1.3.14/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js rename to archive/1.3.14/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.14/demo/js/archive/1.3.2/draggableConnectorsDemo.js b/archive/1.3.14/demo/js/archive/1.3.2/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.2/draggableConnectorsDemo.js rename to archive/1.3.14/demo/js/archive/1.3.2/draggableConnectorsDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.2/dynamicAnchorsDemo.js b/archive/1.3.14/demo/js/archive/1.3.2/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.2/dynamicAnchorsDemo.js rename to archive/1.3.14/demo/js/archive/1.3.2/dynamicAnchorsDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.2/flowchartConnectorsDemo.js b/archive/1.3.14/demo/js/archive/1.3.2/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.2/flowchartConnectorsDemo.js rename to archive/1.3.14/demo/js/archive/1.3.2/flowchartConnectorsDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.3/anchorDemo.js b/archive/1.3.14/demo/js/archive/1.3.3/anchorDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.3/anchorDemo.js rename to archive/1.3.14/demo/js/archive/1.3.3/anchorDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.3/chartDemo.js b/archive/1.3.14/demo/js/archive/1.3.3/chartDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.3/chartDemo.js rename to archive/1.3.14/demo/js/archive/1.3.3/chartDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.3/demo-helper-jquery.js b/archive/1.3.14/demo/js/archive/1.3.3/demo-helper-jquery.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.3/demo-helper-jquery.js rename to archive/1.3.14/demo/js/archive/1.3.3/demo-helper-jquery.js diff --git a/build/1.3.14/demo/js/archive/1.3.3/demo-helper-mootools.js b/archive/1.3.14/demo/js/archive/1.3.3/demo-helper-mootools.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.3/demo-helper-mootools.js rename to archive/1.3.14/demo/js/archive/1.3.3/demo-helper-mootools.js diff --git a/build/1.3.14/demo/js/archive/1.3.3/demo-helper-yui3.js b/archive/1.3.14/demo/js/archive/1.3.3/demo-helper-yui3.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.3/demo-helper-yui3.js rename to archive/1.3.14/demo/js/archive/1.3.3/demo-helper-yui3.js diff --git a/build/1.3.14/demo/js/archive/1.3.3/demo.js b/archive/1.3.14/demo/js/archive/1.3.3/demo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.3/demo.js rename to archive/1.3.14/demo/js/archive/1.3.3/demo.js diff --git a/build/1.3.14/demo/js/archive/1.3.3/dragAnimDemo-jquery.js b/archive/1.3.14/demo/js/archive/1.3.3/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.3/dragAnimDemo-jquery.js rename to archive/1.3.14/demo/js/archive/1.3.3/dragAnimDemo-jquery.js diff --git a/build/1.3.14/demo/js/archive/1.3.3/dragAnimDemo-mootools.js b/archive/1.3.14/demo/js/archive/1.3.3/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.3/dragAnimDemo-mootools.js rename to archive/1.3.14/demo/js/archive/1.3.3/dragAnimDemo-mootools.js diff --git a/build/1.3.14/demo/js/archive/1.3.3/dragAnimDemo-yui3.js b/archive/1.3.14/demo/js/archive/1.3.3/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.3/dragAnimDemo-yui3.js rename to archive/1.3.14/demo/js/archive/1.3.3/dragAnimDemo-yui3.js diff --git a/build/1.3.14/demo/js/archive/1.3.3/dragAnimDemo.js b/archive/1.3.14/demo/js/archive/1.3.3/dragAnimDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.3/dragAnimDemo.js rename to archive/1.3.14/demo/js/archive/1.3.3/dragAnimDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js b/archive/1.3.14/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js rename to archive/1.3.14/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.14/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js b/archive/1.3.14/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js rename to archive/1.3.14/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.14/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js b/archive/1.3.14/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js rename to archive/1.3.14/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.14/demo/js/archive/1.3.3/draggableConnectorsDemo.js b/archive/1.3.14/demo/js/archive/1.3.3/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.3/draggableConnectorsDemo.js rename to archive/1.3.14/demo/js/archive/1.3.3/draggableConnectorsDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.3/dynamicAnchorsDemo.js b/archive/1.3.14/demo/js/archive/1.3.3/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.3/dynamicAnchorsDemo.js rename to archive/1.3.14/demo/js/archive/1.3.3/dynamicAnchorsDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.3/flowchartConnectorsDemo.js b/archive/1.3.14/demo/js/archive/1.3.3/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.3/flowchartConnectorsDemo.js rename to archive/1.3.14/demo/js/archive/1.3.3/flowchartConnectorsDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js b/archive/1.3.14/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js rename to archive/1.3.14/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js diff --git a/build/1.3.14/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all.js b/archive/1.3.14/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all.js rename to archive/1.3.14/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all.js diff --git a/build/1.3.14/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js b/archive/1.3.14/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js rename to archive/1.3.14/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js diff --git a/build/1.3.14/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js b/archive/1.3.14/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js rename to archive/1.3.14/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js diff --git a/build/1.3.14/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js b/archive/1.3.14/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js rename to archive/1.3.14/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js diff --git a/build/1.3.14/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js b/archive/1.3.14/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js rename to archive/1.3.14/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js diff --git a/build/1.3.14/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js b/archive/1.3.14/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js rename to archive/1.3.14/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js diff --git a/build/1.3.14/demo/js/archive/1.3.4/anchorDemo.js b/archive/1.3.14/demo/js/archive/1.3.4/anchorDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.4/anchorDemo.js rename to archive/1.3.14/demo/js/archive/1.3.4/anchorDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.4/chartDemo.js b/archive/1.3.14/demo/js/archive/1.3.4/chartDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.4/chartDemo.js rename to archive/1.3.14/demo/js/archive/1.3.4/chartDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.4/demo-helper-jquery.js b/archive/1.3.14/demo/js/archive/1.3.4/demo-helper-jquery.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.4/demo-helper-jquery.js rename to archive/1.3.14/demo/js/archive/1.3.4/demo-helper-jquery.js diff --git a/build/1.3.14/demo/js/archive/1.3.4/demo-helper-mootools.js b/archive/1.3.14/demo/js/archive/1.3.4/demo-helper-mootools.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.4/demo-helper-mootools.js rename to archive/1.3.14/demo/js/archive/1.3.4/demo-helper-mootools.js diff --git a/build/1.3.14/demo/js/archive/1.3.4/demo-helper-yui3.js b/archive/1.3.14/demo/js/archive/1.3.4/demo-helper-yui3.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.4/demo-helper-yui3.js rename to archive/1.3.14/demo/js/archive/1.3.4/demo-helper-yui3.js diff --git a/build/1.3.14/demo/js/archive/1.3.4/demo-list.js b/archive/1.3.14/demo/js/archive/1.3.4/demo-list.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.4/demo-list.js rename to archive/1.3.14/demo/js/archive/1.3.4/demo-list.js diff --git a/build/1.3.14/demo/js/archive/1.3.4/demo.js b/archive/1.3.14/demo/js/archive/1.3.4/demo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.4/demo.js rename to archive/1.3.14/demo/js/archive/1.3.4/demo.js diff --git a/build/1.3.14/demo/js/archive/1.3.4/dragAnimDemo-jquery.js b/archive/1.3.14/demo/js/archive/1.3.4/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.4/dragAnimDemo-jquery.js rename to archive/1.3.14/demo/js/archive/1.3.4/dragAnimDemo-jquery.js diff --git a/build/1.3.14/demo/js/archive/1.3.4/dragAnimDemo-mootools.js b/archive/1.3.14/demo/js/archive/1.3.4/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.4/dragAnimDemo-mootools.js rename to archive/1.3.14/demo/js/archive/1.3.4/dragAnimDemo-mootools.js diff --git a/build/1.3.14/demo/js/archive/1.3.4/dragAnimDemo-yui3.js b/archive/1.3.14/demo/js/archive/1.3.4/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.4/dragAnimDemo-yui3.js rename to archive/1.3.14/demo/js/archive/1.3.4/dragAnimDemo-yui3.js diff --git a/build/1.3.14/demo/js/archive/1.3.4/dragAnimDemo.js b/archive/1.3.14/demo/js/archive/1.3.4/dragAnimDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.4/dragAnimDemo.js rename to archive/1.3.14/demo/js/archive/1.3.4/dragAnimDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.4/draggableConnectorsDemo-jquery.js b/archive/1.3.14/demo/js/archive/1.3.4/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.4/draggableConnectorsDemo-jquery.js rename to archive/1.3.14/demo/js/archive/1.3.4/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.14/demo/js/archive/1.3.4/draggableConnectorsDemo-mootools.js b/archive/1.3.14/demo/js/archive/1.3.4/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.4/draggableConnectorsDemo-mootools.js rename to archive/1.3.14/demo/js/archive/1.3.4/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.14/demo/js/archive/1.3.4/draggableConnectorsDemo-yui3.js b/archive/1.3.14/demo/js/archive/1.3.4/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.4/draggableConnectorsDemo-yui3.js rename to archive/1.3.14/demo/js/archive/1.3.4/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.14/demo/js/archive/1.3.4/draggableConnectorsDemo.js b/archive/1.3.14/demo/js/archive/1.3.4/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.4/draggableConnectorsDemo.js rename to archive/1.3.14/demo/js/archive/1.3.4/draggableConnectorsDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.4/dynamicAnchorsDemo.js b/archive/1.3.14/demo/js/archive/1.3.4/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.4/dynamicAnchorsDemo.js rename to archive/1.3.14/demo/js/archive/1.3.4/dynamicAnchorsDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.4/flowchartConnectorsDemo.js b/archive/1.3.14/demo/js/archive/1.3.4/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.4/flowchartConnectorsDemo.js rename to archive/1.3.14/demo/js/archive/1.3.4/flowchartConnectorsDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.4/makeSourceDemo.js b/archive/1.3.14/demo/js/archive/1.3.4/makeSourceDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.4/makeSourceDemo.js rename to archive/1.3.14/demo/js/archive/1.3.4/makeSourceDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.4/makeTargetDemo.js b/archive/1.3.14/demo/js/archive/1.3.4/makeTargetDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.4/makeTargetDemo.js rename to archive/1.3.14/demo/js/archive/1.3.4/makeTargetDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.4/stateMachineDemo-jquery.js b/archive/1.3.14/demo/js/archive/1.3.4/stateMachineDemo-jquery.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.4/stateMachineDemo-jquery.js rename to archive/1.3.14/demo/js/archive/1.3.4/stateMachineDemo-jquery.js diff --git a/build/1.3.14/demo/js/archive/1.3.4/stateMachineDemo-mootools.js b/archive/1.3.14/demo/js/archive/1.3.4/stateMachineDemo-mootools.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.4/stateMachineDemo-mootools.js rename to archive/1.3.14/demo/js/archive/1.3.4/stateMachineDemo-mootools.js diff --git a/build/1.3.14/demo/js/archive/1.3.4/stateMachineDemo-yui3.js b/archive/1.3.14/demo/js/archive/1.3.4/stateMachineDemo-yui3.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.4/stateMachineDemo-yui3.js rename to archive/1.3.14/demo/js/archive/1.3.4/stateMachineDemo-yui3.js diff --git a/build/1.3.14/demo/js/archive/1.3.4/stateMachineDemo.js b/archive/1.3.14/demo/js/archive/1.3.4/stateMachineDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.4/stateMachineDemo.js rename to archive/1.3.14/demo/js/archive/1.3.4/stateMachineDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.5/anchorDemo.js b/archive/1.3.14/demo/js/archive/1.3.5/anchorDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.5/anchorDemo.js rename to archive/1.3.14/demo/js/archive/1.3.5/anchorDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.5/chartDemo.js b/archive/1.3.14/demo/js/archive/1.3.5/chartDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.5/chartDemo.js rename to archive/1.3.14/demo/js/archive/1.3.5/chartDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.5/demo-helper-jquery.js b/archive/1.3.14/demo/js/archive/1.3.5/demo-helper-jquery.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.5/demo-helper-jquery.js rename to archive/1.3.14/demo/js/archive/1.3.5/demo-helper-jquery.js diff --git a/build/1.3.14/demo/js/archive/1.3.5/demo-helper-mootools.js b/archive/1.3.14/demo/js/archive/1.3.5/demo-helper-mootools.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.5/demo-helper-mootools.js rename to archive/1.3.14/demo/js/archive/1.3.5/demo-helper-mootools.js diff --git a/build/1.3.14/demo/js/archive/1.3.5/demo-helper-yui3.js b/archive/1.3.14/demo/js/archive/1.3.5/demo-helper-yui3.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.5/demo-helper-yui3.js rename to archive/1.3.14/demo/js/archive/1.3.5/demo-helper-yui3.js diff --git a/build/1.3.14/demo/js/archive/1.3.5/demo-list.js b/archive/1.3.14/demo/js/archive/1.3.5/demo-list.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.5/demo-list.js rename to archive/1.3.14/demo/js/archive/1.3.5/demo-list.js diff --git a/build/1.3.14/demo/js/archive/1.3.5/demo.js b/archive/1.3.14/demo/js/archive/1.3.5/demo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.5/demo.js rename to archive/1.3.14/demo/js/archive/1.3.5/demo.js diff --git a/build/1.3.14/demo/js/archive/1.3.5/dragAnimDemo-jquery.js b/archive/1.3.14/demo/js/archive/1.3.5/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.5/dragAnimDemo-jquery.js rename to archive/1.3.14/demo/js/archive/1.3.5/dragAnimDemo-jquery.js diff --git a/build/1.3.14/demo/js/archive/1.3.5/dragAnimDemo-mootools.js b/archive/1.3.14/demo/js/archive/1.3.5/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.5/dragAnimDemo-mootools.js rename to archive/1.3.14/demo/js/archive/1.3.5/dragAnimDemo-mootools.js diff --git a/build/1.3.14/demo/js/archive/1.3.5/dragAnimDemo-yui3.js b/archive/1.3.14/demo/js/archive/1.3.5/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.5/dragAnimDemo-yui3.js rename to archive/1.3.14/demo/js/archive/1.3.5/dragAnimDemo-yui3.js diff --git a/build/1.3.14/demo/js/archive/1.3.5/dragAnimDemo.js b/archive/1.3.14/demo/js/archive/1.3.5/dragAnimDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.5/dragAnimDemo.js rename to archive/1.3.14/demo/js/archive/1.3.5/dragAnimDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.5/draggableConnectorsDemo-jquery.js b/archive/1.3.14/demo/js/archive/1.3.5/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.5/draggableConnectorsDemo-jquery.js rename to archive/1.3.14/demo/js/archive/1.3.5/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.14/demo/js/archive/1.3.5/draggableConnectorsDemo-mootools.js b/archive/1.3.14/demo/js/archive/1.3.5/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.5/draggableConnectorsDemo-mootools.js rename to archive/1.3.14/demo/js/archive/1.3.5/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.14/demo/js/archive/1.3.5/draggableConnectorsDemo-yui3.js b/archive/1.3.14/demo/js/archive/1.3.5/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.5/draggableConnectorsDemo-yui3.js rename to archive/1.3.14/demo/js/archive/1.3.5/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.14/demo/js/archive/1.3.5/draggableConnectorsDemo.js b/archive/1.3.14/demo/js/archive/1.3.5/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.5/draggableConnectorsDemo.js rename to archive/1.3.14/demo/js/archive/1.3.5/draggableConnectorsDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.5/dynamicAnchorsDemo.js b/archive/1.3.14/demo/js/archive/1.3.5/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.5/dynamicAnchorsDemo.js rename to archive/1.3.14/demo/js/archive/1.3.5/dynamicAnchorsDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.5/flowchartConnectorsDemo.js b/archive/1.3.14/demo/js/archive/1.3.5/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.5/flowchartConnectorsDemo.js rename to archive/1.3.14/demo/js/archive/1.3.5/flowchartConnectorsDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.5/loadTest.js b/archive/1.3.14/demo/js/archive/1.3.5/loadTest.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.5/loadTest.js rename to archive/1.3.14/demo/js/archive/1.3.5/loadTest.js diff --git a/build/1.3.14/demo/js/archive/1.3.5/makeSourceDemo.js b/archive/1.3.14/demo/js/archive/1.3.5/makeSourceDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.5/makeSourceDemo.js rename to archive/1.3.14/demo/js/archive/1.3.5/makeSourceDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.5/makeTargetDemo.js b/archive/1.3.14/demo/js/archive/1.3.5/makeTargetDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.5/makeTargetDemo.js rename to archive/1.3.14/demo/js/archive/1.3.5/makeTargetDemo.js diff --git a/build/1.3.14/demo/js/archive/1.3.5/stateMachineDemo-jquery.js b/archive/1.3.14/demo/js/archive/1.3.5/stateMachineDemo-jquery.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.5/stateMachineDemo-jquery.js rename to archive/1.3.14/demo/js/archive/1.3.5/stateMachineDemo-jquery.js diff --git a/build/1.3.14/demo/js/archive/1.3.5/stateMachineDemo-mootools.js b/archive/1.3.14/demo/js/archive/1.3.5/stateMachineDemo-mootools.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.5/stateMachineDemo-mootools.js rename to archive/1.3.14/demo/js/archive/1.3.5/stateMachineDemo-mootools.js diff --git a/build/1.3.14/demo/js/archive/1.3.5/stateMachineDemo-yui3.js b/archive/1.3.14/demo/js/archive/1.3.5/stateMachineDemo-yui3.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.5/stateMachineDemo-yui3.js rename to archive/1.3.14/demo/js/archive/1.3.5/stateMachineDemo-yui3.js diff --git a/build/1.3.14/demo/js/archive/1.3.5/stateMachineDemo.js b/archive/1.3.14/demo/js/archive/1.3.5/stateMachineDemo.js similarity index 100% rename from build/1.3.14/demo/js/archive/1.3.5/stateMachineDemo.js rename to archive/1.3.14/demo/js/archive/1.3.5/stateMachineDemo.js diff --git a/build/1.3.14/demo/js/chartDemo.js b/archive/1.3.14/demo/js/chartDemo.js similarity index 100% rename from build/1.3.14/demo/js/chartDemo.js rename to archive/1.3.14/demo/js/chartDemo.js diff --git a/build/1.3.14/demo/js/demo-helper-jquery.js b/archive/1.3.14/demo/js/demo-helper-jquery.js similarity index 100% rename from build/1.3.14/demo/js/demo-helper-jquery.js rename to archive/1.3.14/demo/js/demo-helper-jquery.js diff --git a/build/1.3.14/demo/js/demo-helper-mootools.js b/archive/1.3.14/demo/js/demo-helper-mootools.js similarity index 100% rename from build/1.3.14/demo/js/demo-helper-mootools.js rename to archive/1.3.14/demo/js/demo-helper-mootools.js diff --git a/build/1.3.14/demo/js/demo-helper-yui3.js b/archive/1.3.14/demo/js/demo-helper-yui3.js similarity index 100% rename from build/1.3.14/demo/js/demo-helper-yui3.js rename to archive/1.3.14/demo/js/demo-helper-yui3.js diff --git a/build/1.3.14/demo/js/demo-list.js b/archive/1.3.14/demo/js/demo-list.js similarity index 100% rename from build/1.3.14/demo/js/demo-list.js rename to archive/1.3.14/demo/js/demo-list.js diff --git a/build/1.3.14/demo/js/demo.js b/archive/1.3.14/demo/js/demo.js similarity index 100% rename from build/1.3.14/demo/js/demo.js rename to archive/1.3.14/demo/js/demo.js diff --git a/build/1.3.14/demo/js/dragAnimDemo-jquery.js b/archive/1.3.14/demo/js/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.14/demo/js/dragAnimDemo-jquery.js rename to archive/1.3.14/demo/js/dragAnimDemo-jquery.js diff --git a/build/1.3.14/demo/js/dragAnimDemo-mootools.js b/archive/1.3.14/demo/js/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.14/demo/js/dragAnimDemo-mootools.js rename to archive/1.3.14/demo/js/dragAnimDemo-mootools.js diff --git a/build/1.3.14/demo/js/dragAnimDemo-yui3.js b/archive/1.3.14/demo/js/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.14/demo/js/dragAnimDemo-yui3.js rename to archive/1.3.14/demo/js/dragAnimDemo-yui3.js diff --git a/build/1.3.14/demo/js/dragAnimDemo.js b/archive/1.3.14/demo/js/dragAnimDemo.js similarity index 100% rename from build/1.3.14/demo/js/dragAnimDemo.js rename to archive/1.3.14/demo/js/dragAnimDemo.js diff --git a/build/1.3.14/demo/js/draggableConnectorsDemo-jquery.js b/archive/1.3.14/demo/js/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.14/demo/js/draggableConnectorsDemo-jquery.js rename to archive/1.3.14/demo/js/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.14/demo/js/draggableConnectorsDemo-mootools.js b/archive/1.3.14/demo/js/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.14/demo/js/draggableConnectorsDemo-mootools.js rename to archive/1.3.14/demo/js/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.14/demo/js/draggableConnectorsDemo-yui3.js b/archive/1.3.14/demo/js/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.14/demo/js/draggableConnectorsDemo-yui3.js rename to archive/1.3.14/demo/js/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.14/demo/js/draggableConnectorsDemo.js b/archive/1.3.14/demo/js/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.14/demo/js/draggableConnectorsDemo.js rename to archive/1.3.14/demo/js/draggableConnectorsDemo.js diff --git a/build/1.3.14/demo/js/dynamicAnchorsDemo.js b/archive/1.3.14/demo/js/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.14/demo/js/dynamicAnchorsDemo.js rename to archive/1.3.14/demo/js/dynamicAnchorsDemo.js diff --git a/build/1.3.14/demo/js/flowchartConnectorsDemo.js b/archive/1.3.14/demo/js/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.14/demo/js/flowchartConnectorsDemo.js rename to archive/1.3.14/demo/js/flowchartConnectorsDemo.js diff --git a/build/1.3.14/demo/js/jquery.jsPlumb-1.3.14-all-min.js b/archive/1.3.14/demo/js/jquery.jsPlumb-1.3.14-all-min.js similarity index 100% rename from build/1.3.14/demo/js/jquery.jsPlumb-1.3.14-all-min.js rename to archive/1.3.14/demo/js/jquery.jsPlumb-1.3.14-all-min.js diff --git a/build/1.3.14/demo/js/jquery.jsPlumb-1.3.14-all.js b/archive/1.3.14/demo/js/jquery.jsPlumb-1.3.14-all.js similarity index 100% rename from build/1.3.14/demo/js/jquery.jsPlumb-1.3.14-all.js rename to archive/1.3.14/demo/js/jquery.jsPlumb-1.3.14-all.js diff --git a/build/1.3.14/demo/js/loadTest.js b/archive/1.3.14/demo/js/loadTest.js similarity index 100% rename from build/1.3.14/demo/js/loadTest.js rename to archive/1.3.14/demo/js/loadTest.js diff --git a/build/1.3.14/demo/js/makeSourceDemo.js b/archive/1.3.14/demo/js/makeSourceDemo.js similarity index 100% rename from build/1.3.14/demo/js/makeSourceDemo.js rename to archive/1.3.14/demo/js/makeSourceDemo.js diff --git a/build/1.3.14/demo/js/makeTargetDemo.js b/archive/1.3.14/demo/js/makeTargetDemo.js similarity index 100% rename from build/1.3.14/demo/js/makeTargetDemo.js rename to archive/1.3.14/demo/js/makeTargetDemo.js diff --git a/build/1.3.14/demo/js/mootools-1.3.2.1-more.js b/archive/1.3.14/demo/js/mootools-1.3.2.1-more.js similarity index 100% rename from build/1.3.14/demo/js/mootools-1.3.2.1-more.js rename to archive/1.3.14/demo/js/mootools-1.3.2.1-more.js diff --git a/build/1.3.14/demo/js/mootools.jsPlumb-1.3.14-all-min.js b/archive/1.3.14/demo/js/mootools.jsPlumb-1.3.14-all-min.js similarity index 100% rename from build/1.3.14/demo/js/mootools.jsPlumb-1.3.14-all-min.js rename to archive/1.3.14/demo/js/mootools.jsPlumb-1.3.14-all-min.js diff --git a/build/1.3.14/demo/js/mootools.jsPlumb-1.3.14-all.js b/archive/1.3.14/demo/js/mootools.jsPlumb-1.3.14-all.js similarity index 100% rename from build/1.3.14/demo/js/mootools.jsPlumb-1.3.14-all.js rename to archive/1.3.14/demo/js/mootools.jsPlumb-1.3.14-all.js diff --git a/build/1.3.14/demo/js/perimeterAnchorsDemo-jquery.js b/archive/1.3.14/demo/js/perimeterAnchorsDemo-jquery.js similarity index 100% rename from build/1.3.14/demo/js/perimeterAnchorsDemo-jquery.js rename to archive/1.3.14/demo/js/perimeterAnchorsDemo-jquery.js diff --git a/build/1.3.14/demo/js/perimeterAnchorsDemo-mootools.js b/archive/1.3.14/demo/js/perimeterAnchorsDemo-mootools.js similarity index 100% rename from build/1.3.14/demo/js/perimeterAnchorsDemo-mootools.js rename to archive/1.3.14/demo/js/perimeterAnchorsDemo-mootools.js diff --git a/build/1.3.14/demo/js/perimeterAnchorsDemo-yui3.js b/archive/1.3.14/demo/js/perimeterAnchorsDemo-yui3.js similarity index 100% rename from build/1.3.14/demo/js/perimeterAnchorsDemo-yui3.js rename to archive/1.3.14/demo/js/perimeterAnchorsDemo-yui3.js diff --git a/build/1.3.14/demo/js/stateMachineDemo-jquery.js b/archive/1.3.14/demo/js/stateMachineDemo-jquery.js similarity index 100% rename from build/1.3.14/demo/js/stateMachineDemo-jquery.js rename to archive/1.3.14/demo/js/stateMachineDemo-jquery.js diff --git a/build/1.3.14/demo/js/stateMachineDemo-mootools.js b/archive/1.3.14/demo/js/stateMachineDemo-mootools.js similarity index 100% rename from build/1.3.14/demo/js/stateMachineDemo-mootools.js rename to archive/1.3.14/demo/js/stateMachineDemo-mootools.js diff --git a/build/1.3.14/demo/js/stateMachineDemo-yui3.js b/archive/1.3.14/demo/js/stateMachineDemo-yui3.js similarity index 100% rename from build/1.3.14/demo/js/stateMachineDemo-yui3.js rename to archive/1.3.14/demo/js/stateMachineDemo-yui3.js diff --git a/build/1.3.14/demo/js/stateMachineDemo.js b/archive/1.3.14/demo/js/stateMachineDemo.js similarity index 100% rename from build/1.3.14/demo/js/stateMachineDemo.js rename to archive/1.3.14/demo/js/stateMachineDemo.js diff --git a/build/1.3.14/demo/js/yui-3.3.0-min.js b/archive/1.3.14/demo/js/yui-3.3.0-min.js similarity index 100% rename from build/1.3.14/demo/js/yui-3.3.0-min.js rename to archive/1.3.14/demo/js/yui-3.3.0-min.js diff --git a/build/1.3.14/demo/js/yui.jsPlumb-1.3.14-all-min.js b/archive/1.3.14/demo/js/yui.jsPlumb-1.3.14-all-min.js similarity index 100% rename from build/1.3.14/demo/js/yui.jsPlumb-1.3.14-all-min.js rename to archive/1.3.14/demo/js/yui.jsPlumb-1.3.14-all-min.js diff --git a/build/1.3.14/demo/js/yui.jsPlumb-1.3.14-all.js b/archive/1.3.14/demo/js/yui.jsPlumb-1.3.14-all.js similarity index 100% rename from build/1.3.14/demo/js/yui.jsPlumb-1.3.14-all.js rename to archive/1.3.14/demo/js/yui.jsPlumb-1.3.14-all.js diff --git a/build/1.3.14/demo/mootools/anchorDemo.html b/archive/1.3.14/demo/mootools/anchorDemo.html similarity index 100% rename from build/1.3.14/demo/mootools/anchorDemo.html rename to archive/1.3.14/demo/mootools/anchorDemo.html diff --git a/build/1.3.14/demo/mootools/chartDemo.html b/archive/1.3.14/demo/mootools/chartDemo.html similarity index 100% rename from build/1.3.14/demo/mootools/chartDemo.html rename to archive/1.3.14/demo/mootools/chartDemo.html diff --git a/build/1.3.14/demo/mootools/demo.html b/archive/1.3.14/demo/mootools/demo.html similarity index 100% rename from build/1.3.14/demo/mootools/demo.html rename to archive/1.3.14/demo/mootools/demo.html diff --git a/build/1.3.14/demo/mootools/dragAnimDemo.html b/archive/1.3.14/demo/mootools/dragAnimDemo.html similarity index 100% rename from build/1.3.14/demo/mootools/dragAnimDemo.html rename to archive/1.3.14/demo/mootools/dragAnimDemo.html diff --git a/build/1.3.14/demo/mootools/draggableConnectorsDemo.html b/archive/1.3.14/demo/mootools/draggableConnectorsDemo.html similarity index 100% rename from build/1.3.14/demo/mootools/draggableConnectorsDemo.html rename to archive/1.3.14/demo/mootools/draggableConnectorsDemo.html diff --git a/build/1.3.14/demo/mootools/dynamicAnchorsDemo.html b/archive/1.3.14/demo/mootools/dynamicAnchorsDemo.html similarity index 100% rename from build/1.3.14/demo/mootools/dynamicAnchorsDemo.html rename to archive/1.3.14/demo/mootools/dynamicAnchorsDemo.html diff --git a/build/1.3.14/demo/mootools/flowchartConnectorsDemo.html b/archive/1.3.14/demo/mootools/flowchartConnectorsDemo.html similarity index 100% rename from build/1.3.14/demo/mootools/flowchartConnectorsDemo.html rename to archive/1.3.14/demo/mootools/flowchartConnectorsDemo.html diff --git a/build/1.3.14/demo/mootools/perimeterAnchorsDemo.html b/archive/1.3.14/demo/mootools/perimeterAnchorsDemo.html similarity index 100% rename from build/1.3.14/demo/mootools/perimeterAnchorsDemo.html rename to archive/1.3.14/demo/mootools/perimeterAnchorsDemo.html diff --git a/build/1.3.14/demo/mootools/stateMachineDemo.html b/archive/1.3.14/demo/mootools/stateMachineDemo.html similarity index 100% rename from build/1.3.14/demo/mootools/stateMachineDemo.html rename to archive/1.3.14/demo/mootools/stateMachineDemo.html diff --git a/build/1.3.14/demo/yui3/anchorDemo.html b/archive/1.3.14/demo/yui3/anchorDemo.html similarity index 100% rename from build/1.3.14/demo/yui3/anchorDemo.html rename to archive/1.3.14/demo/yui3/anchorDemo.html diff --git a/build/1.3.14/demo/yui3/chartDemo.html b/archive/1.3.14/demo/yui3/chartDemo.html similarity index 100% rename from build/1.3.14/demo/yui3/chartDemo.html rename to archive/1.3.14/demo/yui3/chartDemo.html diff --git a/build/1.3.14/demo/yui3/demo.html b/archive/1.3.14/demo/yui3/demo.html similarity index 100% rename from build/1.3.14/demo/yui3/demo.html rename to archive/1.3.14/demo/yui3/demo.html diff --git a/build/1.3.14/demo/yui3/dragAnimDemo.html b/archive/1.3.14/demo/yui3/dragAnimDemo.html similarity index 100% rename from build/1.3.14/demo/yui3/dragAnimDemo.html rename to archive/1.3.14/demo/yui3/dragAnimDemo.html diff --git a/build/1.3.14/demo/yui3/draggableConnectorsDemo.html b/archive/1.3.14/demo/yui3/draggableConnectorsDemo.html similarity index 100% rename from build/1.3.14/demo/yui3/draggableConnectorsDemo.html rename to archive/1.3.14/demo/yui3/draggableConnectorsDemo.html diff --git a/build/1.3.14/demo/yui3/dynamicAnchorsDemo.html b/archive/1.3.14/demo/yui3/dynamicAnchorsDemo.html similarity index 100% rename from build/1.3.14/demo/yui3/dynamicAnchorsDemo.html rename to archive/1.3.14/demo/yui3/dynamicAnchorsDemo.html diff --git a/build/1.3.14/demo/yui3/flowchartConnectorsDemo.html b/archive/1.3.14/demo/yui3/flowchartConnectorsDemo.html similarity index 100% rename from build/1.3.14/demo/yui3/flowchartConnectorsDemo.html rename to archive/1.3.14/demo/yui3/flowchartConnectorsDemo.html diff --git a/build/1.3.14/demo/yui3/perimeterAnchorsDemo.html b/archive/1.3.14/demo/yui3/perimeterAnchorsDemo.html similarity index 100% rename from build/1.3.14/demo/yui3/perimeterAnchorsDemo.html rename to archive/1.3.14/demo/yui3/perimeterAnchorsDemo.html diff --git a/build/1.3.14/demo/yui3/stateMachineDemo.html b/archive/1.3.14/demo/yui3/stateMachineDemo.html similarity index 100% rename from build/1.3.14/demo/yui3/stateMachineDemo.html rename to archive/1.3.14/demo/yui3/stateMachineDemo.html diff --git a/archive/1.3.14/jquery.jsPlumb-1.3.14-RC1.js b/archive/1.3.14/jquery.jsPlumb-1.3.14-RC1.js new file mode 100644 index 000000000..d529da6d7 --- /dev/null +++ b/archive/1.3.14/jquery.jsPlumb-1.3.14-RC1.js @@ -0,0 +1,379 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.14 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the jQuery adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ +/* + * the library specific functions, such as find offset, get id, get attribute, extend etc. + * the full list is: + * + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getDropScope gets the drop scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getOriginalEvent gets the original browser event from some wrapper event + * getPageXY gets the page event's xy location. + * getParent gets the parent of some element. + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * hasClass returns whether or not the given element has the given class. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + * trigger triggers some event on an element. + * unbind unbinds some listener from some element. + */ +(function($) { + + //var getBoundingClientRectSupported = "getBoundingClientRect" in document.documentElement; + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el[0].className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.addClass(el[0], clazz); + } + } + catch (e) { + // SVGAnimatedString not supported; no problem. + } + el.addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * appends the given child to the given parent. + */ + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).append(child); + }, + + /** + * executes an ajax call. + */ + ajax : function(params) { + params = params || {}; + params.type = params.type || "get"; + $.ajax(params); + }, + + /** + * event binding wrapper. it just so happens that jQuery uses 'bind' also. yui3, for example, + * uses 'on'. + */ + bind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.bind(event, callback); + }, + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step', + 'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete' + }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + }, + + getDragScope : function(el) { + return el.draggable("option", "scope"); + }, + + getDropEvent : function(args) { + return args[0]; + }, + + getDropScope : function(el) { + return el.droppable("option", "scope"); + }, + + /** + * gets a DOM element from the given input, which might be a string (in which case we just do document.getElementById), + * a selector (in which case we return el[0]), or a DOM element already (we assume this if it's not either of the other + * two cases). this is the opposite of getElementObject below. + */ + getDOMElement : function(el) { + if (typeof(el) == "string") return document.getElementById(el); + else if (el.context || el.length != null) return el[0]; + else return el; + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + * + */ + getElementObject : function(el) { + return typeof(el) == "string" ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + getOriginalEvent : function(e) { + return e.originalEvent; + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).parent(); + }, + + getScrollLeft : function(el) { + return el.scrollLeft(); + }, + + getScrollTop : function(el) { + return el.scrollTop(); + }, + + getSelector : function(spec) { + return $(spec); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e.length > 0 ? e[0].tagName : null; + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs, zoom) { + + zoom = zoom || 1; + // this code is a workaround for the case that the element being dragged has a margin set on it. jquery UI passes + // in the wrong offset if the element has a margin (it doesn't take the margin into account). the getBoundingClientRect + // method, which is in pretty much all browsers now, reports the right numbers. but it introduces a noticeable lag, which + // i don't like. + + /*if ( getBoundingClientRectSupported ) { + var r = eventArgs[1].helper[0].getBoundingClientRect(); + return { left : r.left, top: r.top }; + } else {*/ + if (eventArgs.length == 1) { + ret = { left: eventArgs[0].pageX, top:eventArgs[0].pageY }; + } + else { + var ui = eventArgs[1], + _offset = ui.offset; + + ret = _offset || ui.absolutePosition; + + // adjust ui position to account for zoom, because jquery ui does not do this. + ui.position.left /= zoom; + ui.position.top /= zoom; + } + return { left:ret.left / zoom, top: ret.top / zoom }; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options, isPlumbedComponent) { + options = options || {}; + // remove helper directive if present. + options.helper = null; + if (isPlumbedComponent) + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.draggable(options); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.droppable(options); + }, + + isAlreadyDraggable : function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return el.hasClass("ui-draggable"); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el[0].className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.removeClass(el[0], clazz); + } + } + catch (e) { + // SVGAnimatedString not supported; no problem. + } + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).remove(); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * sets the drag scope. probably time for a setDragOption method (roll this and the one above together) + * @param el + * @param scope + */ + setDragScope : function(el, scope) { + el.draggable("option", "scope", scope); + }, + + setOffset : function(el, o) { + jsPlumb.CurrentLibrary.getElementObject(el).offset(o); + }, + + /** + * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself. + * the other libraries do not. yui, in fact, cannot even pass an original event. we have to pull out stuff + * from the originalEvent to put in an options object for YUI. + * @param el + * @param event + * @param originalEvent + */ + trigger : function(el, event, originalEvent) { + //originalEvent.stopPropagation(); + //jsPlumb.CurrentLibrary.getElementObject(el).trigger(originalEvent); + var h = jQuery._data(jsPlumb.CurrentLibrary.getElementObject(el)[0], "handle"); + h(originalEvent); + //originalEvent.stopPropagation(); + }, + + /** + * event unbinding wrapper. it just so happens that jQuery uses 'unbind' also. yui3, for example, + * uses..something else. + */ + unbind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.unbind(event, callback); + } + }; + + $(document).ready(jsPlumb.init); + +})(jQuery); + diff --git a/build/1.3.14/js/jquery.jsPlumb-1.3.14-all-min.js b/archive/1.3.14/js/jquery.jsPlumb-1.3.14-all-min.js similarity index 100% rename from build/1.3.14/js/jquery.jsPlumb-1.3.14-all-min.js rename to archive/1.3.14/js/jquery.jsPlumb-1.3.14-all-min.js diff --git a/build/1.3.14/js/jquery.jsPlumb-1.3.14-all.js b/archive/1.3.14/js/jquery.jsPlumb-1.3.14-all.js similarity index 100% rename from build/1.3.14/js/jquery.jsPlumb-1.3.14-all.js rename to archive/1.3.14/js/jquery.jsPlumb-1.3.14-all.js diff --git a/build/1.3.14/js/jsPlumb-1.3.14-tests.js b/archive/1.3.14/js/jsPlumb-1.3.14-tests.js similarity index 100% rename from build/1.3.14/js/jsPlumb-1.3.14-tests.js rename to archive/1.3.14/js/jsPlumb-1.3.14-tests.js diff --git a/build/1.3.14/js/lib/qunit.js b/archive/1.3.14/js/lib/qunit.js similarity index 100% rename from build/1.3.14/js/lib/qunit.js rename to archive/1.3.14/js/lib/qunit.js diff --git a/build/1.3.14/js/mootools.jsPlumb-1.3.14-all-min.js b/archive/1.3.14/js/mootools.jsPlumb-1.3.14-all-min.js similarity index 100% rename from build/1.3.14/js/mootools.jsPlumb-1.3.14-all-min.js rename to archive/1.3.14/js/mootools.jsPlumb-1.3.14-all-min.js diff --git a/build/1.3.14/js/mootools.jsPlumb-1.3.14-all.js b/archive/1.3.14/js/mootools.jsPlumb-1.3.14-all.js similarity index 100% rename from build/1.3.14/js/mootools.jsPlumb-1.3.14-all.js rename to archive/1.3.14/js/mootools.jsPlumb-1.3.14-all.js diff --git a/build/1.3.14/js/yui.jsPlumb-1.3.14-all-min.js b/archive/1.3.14/js/yui.jsPlumb-1.3.14-all-min.js similarity index 100% rename from build/1.3.14/js/yui.jsPlumb-1.3.14-all-min.js rename to archive/1.3.14/js/yui.jsPlumb-1.3.14-all-min.js diff --git a/build/1.3.14/js/yui.jsPlumb-1.3.14-all.js b/archive/1.3.14/js/yui.jsPlumb-1.3.14-all.js similarity index 100% rename from build/1.3.14/js/yui.jsPlumb-1.3.14-all.js rename to archive/1.3.14/js/yui.jsPlumb-1.3.14-all.js diff --git a/archive/1.3.14/jsPlumb-1.3.14-RC1.js b/archive/1.3.14/jsPlumb-1.3.14-RC1.js new file mode 100644 index 000000000..cde9c5582 --- /dev/null +++ b/archive/1.3.14/jsPlumb-1.3.14-RC1.js @@ -0,0 +1,6186 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.14 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the jsPlumb core code. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + /** + * Class:jsPlumb + * The jsPlumb engine, registered as a static object in the window. This object contains all of the methods you will use to + * create and maintain Connections and Endpoints. + */ + + var _findWithFunction = jsPlumbUtil.findWithFunction, + _indexOf = jsPlumbUtil.indexOf, + _removeWithFunction = jsPlumbUtil.removeWithFunction, + _remove = jsPlumbUtil.remove, + // TODO support insert index + _addWithFunction = jsPlumbUtil.addWithFunction, + _addToList = jsPlumbUtil.addToList, + /** + an isArray function that even works across iframes...see here: + + http://tobyho.com/2011/01/28/checking-types-in-javascript/ + + i was originally using "a.constructor == Array" as a test. + */ + _isArray = jsPlumbUtil.isArray, + _isString = jsPlumbUtil.isString, + _isObject = jsPlumbUtil.isObject; + + var _connectionBeingDragged = null, + _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }, + _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }, + _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el, _instance) { + var o = jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); + if (_instance != null) { + var z = _instance.getZoom(); + return {left:o.left / z, top:o.top / z }; + } + else + return o; + }, + _getSize = function(el) { + return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); + }, + _log = jsPlumbUtil.log, + _group = jsPlumbUtil.group, + _groupEnd = jsPlumbUtil.groupEnd, + _time = jsPlumbUtil.time, + _timeEnd = jsPlumbUtil.timeEnd, + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + _timestamp = function() { return "" + (new Date()).getTime(); }, + + /* + * Class:jsPlumbUIComponent + * Abstract superclass for UI components Endpoint and Connection. Provides the abstraction of paintStyle/hoverPaintStyle, + * and also extends jsPlumbUtil.EventGenerator to provide the bind and fire methods. + */ + jsPlumbUIComponent = function(params) { + var self = this, + a = arguments, + _hover = false, + parameters = params.parameters || {}, + idPrefix = self.idPrefix, + id = idPrefix + (new Date()).getTime(), + paintStyle = null, + hoverPaintStyle = null; + + self._jsPlumb = params["_jsPlumb"]; + self.getId = function() { return id; }; + self.tooltip = params.tooltip; + self.hoverClass = params.hoverClass || self._jsPlumb.Defaults.HoverClass || jsPlumb.Defaults.HoverClass; + + // all components can generate events + jsPlumbUtil.EventGenerator.apply(this); + // all components get this clone function. + // TODO issue 116 showed a problem with this - it seems 'a' that is in + // the clone function's scope is shared by all invocations of it, the classic + // JS closure problem. for now, jsPlumb does a version of this inline where + // it used to call clone. but it would be nice to find some time to look + // further at this. + this.clone = function() { + var o = new Object(); + self.constructor.apply(o, a); + return o; + }; + + this.getParameter = function(name) { return parameters[name]; }, + this.getParameters = function() { + return parameters; + }, + this.setParameter = function(name, value) { parameters[name] = value; }, + this.setParameters = function(p) { parameters = p; }, + this.overlayPlacements = []; + + // user can supply a beforeDetach callback, which will be executed before a detach + // is performed; returning false prevents the detach. + var beforeDetach = params.beforeDetach; + this.isDetachAllowed = function(connection) { + var r = self._jsPlumb.checkCondition("beforeDetach", connection ); + if (beforeDetach) { + try { + r = beforeDetach(connection); + } + catch (e) { _log("jsPlumb: beforeDetach callback failed", e); } + } + return r; + }; + + // user can supply a beforeDrop callback, which will be executed before a dropped + // connection is confirmed. user can return false to reject connection. + var beforeDrop = params.beforeDrop; + this.isDropAllowed = function(sourceId, targetId, scope, connection, dropEndpoint) { + var r = self._jsPlumb.checkCondition("beforeDrop", { + sourceId:sourceId, + targetId:targetId, + scope:scope, + connection:connection, + dropEndpoint:dropEndpoint + }); + if (beforeDrop) { + try { + r = beforeDrop({ + sourceId:sourceId, + targetId:targetId, + scope:scope, + connection:connection, + dropEndpoint:dropEndpoint + }); + } + catch (e) { _log("jsPlumb: beforeDrop callback failed", e); } + } + return r; + }; + + // helper method to update the hover style whenever it, or paintStyle, changes. + // we use paintStyle as the foundation and merge hoverPaintStyle over the + // top. + var _updateHoverStyle = function() { + if (paintStyle && hoverPaintStyle) { + var mergedHoverStyle = {}; + jsPlumb.extend(mergedHoverStyle, paintStyle); + jsPlumb.extend(mergedHoverStyle, hoverPaintStyle); + delete self["hoverPaintStyle"]; + // we want the fillStyle of paintStyle to override a gradient, if possible. + if (mergedHoverStyle.gradient && paintStyle.fillStyle) + delete mergedHoverStyle["gradient"]; + hoverPaintStyle = mergedHoverStyle; + } + }; + + /* + * Sets the paint style and then repaints the element. + * + * Parameters: + * style - Style to use. + */ + this.setPaintStyle = function(style, doNotRepaint) { + paintStyle = style; + self.paintStyleInUse = paintStyle; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /** + * Gets the component's paint style. + * + * Returns: + * the component's paint style. if there is no hoverPaintStyle set then this will be the paint style used all the time, otherwise this is the style used when the mouse is not hovering. + */ + this.getPaintStyle = function() { + return paintStyle; + }; + + /* + * Sets the paint style to use when the mouse is hovering over the element. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the component will not be repainted. useful when setting things up initially. + */ + this.setHoverPaintStyle = function(style, doNotRepaint) { + hoverPaintStyle = style; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /** + * Gets the component's hover paint style. + * + * Returns: + * the component's hover paint style. may be null. + */ + this.getHoverPaintStyle = function() { + return hoverPaintStyle; + }; + + /* + * sets/unsets the hover state of this element. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + this.setHover = function(hover, ignoreAttachedElements, timestamp) { + // while dragging, we ignore these events. this keeps the UI from flashing and + // swishing and whatevering. + if (!self._jsPlumb.currentlyDragging && !self._jsPlumb.isHoverSuspended()) { + + _hover = hover; + if (self.hoverClass != null && self.canvas != null) { + if (hover) + jpcl.addClass(self.canvas, self.hoverClass); + else + jpcl.removeClass(self.canvas, self.hoverClass); + } + if (hoverPaintStyle != null) { + self.paintStyleInUse = hover ? hoverPaintStyle : paintStyle; + timestamp = timestamp || _timestamp(); + self.repaint({timestamp:timestamp, recalc:false}); + } + // get the list of other affected elements, if supported by this component. + // for a connection, its the endpoints. for an endpoint, its the connections! surprise. + if (self.getAttachedElements && !ignoreAttachedElements) + _updateAttachedElements(hover, _timestamp(), self); + } + }; + + this.isHover = function() { return _hover; }; + + var zIndex = null; + this.setZIndex = function(v) { zIndex = v; }; + this.getZIndex = function() { return zIndex; }; + + var jpcl = jsPlumb.CurrentLibrary, + events = [ "click", "dblclick", "mouseenter", "mouseout", "mousemove", "mousedown", "mouseup", "contextmenu" ], + eventFilters = { "mouseout":"mouseexit" }, + bindOne = function(o, c, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.bind(o, evt, function(ee) { + c.fire(filteredEvent, c, ee); + }); + }, + unbindOne = function(o, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.unbind(o, evt); + }; + + this.attachListeners = function(o, c) { + for (var i = 0; i < events.length; i++) { + bindOne(o, c, events[i]); + } + }; + + var _updateAttachedElements = function(state, timestamp, sourceElement) { + var affectedElements = self.getAttachedElements(); // implemented in subclasses + if (affectedElements) { + for (var i = 0; i < affectedElements.length; i++) { + if (!sourceElement || sourceElement != affectedElements[i]) + affectedElements[i].setHover(state, true, timestamp); // tell the attached elements not to inform their own attached elements. + } + } + }; + + this.reattachListenersForElement = function(o) { + if (arguments.length > 1) { + for (var i = 0; i < events.length; i++) + unbindOne(o, events[i]); + for (var i = 1; i < arguments.length; i++) + self.attachListeners(o, arguments[i]); + } + }; + + /* + * TYPES + */ + var _types = [], + _splitType = function(t) { return t == null ? null : t.split(" ")}, + _applyTypes = function(doNotRepaint) { + if (self.getDefaultType) { + var td = self.getTypeDescriptor(); + + var o = jsPlumbUtil.merge({}, self.getDefaultType()); + for (var i = 0; i < _types.length; i++) + o = jsPlumbUtil.merge(o, self._jsPlumb.getType(_types[i], td)); + + self.applyType(o); + if (!doNotRepaint) self.repaint(); + } + }; + + self.setType = function(typeId, doNotRepaint) { + _types = _splitType(typeId) || []; + _applyTypes(doNotRepaint); + }; + + /* + * Function : getType + * Gets the 'types' of this component. + */ + self.getType = function() { + return _types; + }; + + self.hasType = function(typeId) { + return jsPlumbUtil.indexOf(_types, typeId) != -1; + }; + + self.addType = function(typeId, doNotRepaint) { + var t = _splitType(typeId), _cont = false; + if (t != null) { + for (var i = 0; i < t.length; i++) { + if (!self.hasType(t[i])) { + _types.push(t[i]); + _cont = true; + } + } + if (_cont) _applyTypes(doNotRepaint); + } + }; + + self.removeType = function(typeId, doNotRepaint) { + var t = _splitType(typeId), _cont = false, _one = function(tt) { + var idx = jsPlumbUtil.indexOf(_types, tt); + if (idx != -1) { + _types.splice(idx, 1); + return true; + } + return false; + }; + + if (t != null) { + for (var i = 0; i < t.length; i++) { + _cont = _one(t[i]) || _cont; + } + if (_cont) _applyTypes(doNotRepaint); + } + }; + + self.toggleType = function(typeId, doNotRepaint) { + var t = _splitType(typeId); + if (t != null) { + for (var i = 0; i < t.length; i++) { + var idx = jsPlumbUtil.indexOf(_types, t[i]); + if (idx != -1) + _types.splice(idx, 1); + else + _types.push(t[i]); + } + + _applyTypes(doNotRepaint); + } + }; + + this.applyType = function(t) { + self.setPaintStyle(t.paintStyle); + self.setHoverPaintStyle(t.hoverPaintStyle); + if (t.parameters){ + for (var i in t.parameters) + self.setParameter(i, t.parameters[i]); + } + }; + + }, + + overlayCapableJsPlumbUIComponent = function(params) { + jsPlumbUIComponent.apply(this, arguments); + var self = this; + this.overlays = []; + + var processOverlay = function(o) { + var _newOverlay = null; + if (_isArray(o)) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0], + // make a copy of the object so as not to mess up anyone else's reference... + p = jsPlumb.extend({component:self, _jsPlumb:self._jsPlumb}, o[1]); + if (o.length == 3) jsPlumb.extend(p, o[2]); + _newOverlay = new jsPlumb.Overlays[self._jsPlumb.getRenderMode()][type](p); + if (p.events) { + for (var evt in p.events) { + _newOverlay.bind(evt, p.events[evt]); + } + } + } else if (o.constructor == String) { + _newOverlay = new jsPlumb.Overlays[self._jsPlumb.getRenderMode()][o]({component:self, _jsPlumb:self._jsPlumb}); + } else { + _newOverlay = o; + } + + self.overlays.push(_newOverlay); + }, + calculateOverlaysToAdd = function(params) { + var defaultKeys = self.defaultOverlayKeys || [], + o = params.overlays, + checkKey = function(k) { + return self._jsPlumb.Defaults[k] || jsPlumb.Defaults[k] || []; + }; + + if (!o) o = []; + + for (var i = 0; i < defaultKeys.length; i++) + o.unshift.apply(o, checkKey(defaultKeys[i])); + + return o; + } + + var _overlays = calculateOverlaysToAdd(params);//params.overlays || self._jsPlumb.Defaults.Overlays; + if (_overlays) { + for (var i = 0; i < _overlays.length; i++) { + processOverlay(_overlays[i]); + } + } + + // overlay finder helper method + var _getOverlayIndex = function(id) { + var idx = -1; + for (var i = 0; i < self.overlays.length; i++) { + if (id === self.overlays[i].id) { + idx = i; + break; + } + } + return idx; + }; + + this.addOverlay = function(overlay, doNotRepaint) { + processOverlay(overlay); + if (!doNotRepaint) self.repaint(); + }; + + this.getOverlay = function(id) { + var idx = _getOverlayIndex(id); + return idx >= 0 ? self.overlays[idx] : null; + }; + + this.getOverlays = function() { + return self.overlays; + }; + + this.hideOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.hide(); + }; + + this.hideOverlays = function() { + for (var i = 0; i < self.overlays.length; i++) + self.overlays[i].hide(); + }; + + this.showOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.show(); + }; + + this.showOverlays = function() { + for (var i = 0; i < self.overlays.length; i++) + self.overlays[i].show(); + }; + + this.removeAllOverlays = function() { + for (var i = 0; i < self.overlays.length; i++) { + if (self.overlays[i].cleanup) self.overlays[i].cleanup(); + } + + self.overlays.splice(0, self.overlays.length); + self.repaint(); + }; + + this.removeOverlay = function(overlayId) { + var idx = _getOverlayIndex(overlayId); + if (idx != -1) { + var o = self.overlays[idx]; + if (o.cleanup) o.cleanup(); + self.overlays.splice(idx, 1); + } + }; + + this.removeOverlays = function() { + for (var i = 0; i < arguments.length; i++) + self.removeOverlay(arguments[i]); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + var _internalLabelOverlayId = "__label", + _makeLabelOverlay = function(params) { + + var _params = { + cssClass:params.cssClass, + labelStyle : this.labelStyle, + id:_internalLabelOverlayId, + component:self, + _jsPlumb:self._jsPlumb + }, + mergedParams = jsPlumb.extend(_params, params); + + return new jsPlumb.Overlays[self._jsPlumb.getRenderMode()].Label( mergedParams ); + }; + if (params.label) { + var loc = params.labelLocation || self.defaultLabelLocation || 0.5, + labelStyle = params.labelStyle || self._jsPlumb.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.overlays.push(_makeLabelOverlay({ + label:params.label, + location:loc, + labelStyle:labelStyle + })); + } + + + this.setLabel = function(l) { + var lo = self.getOverlay(_internalLabelOverlayId); + if (!lo) { + var params = l.constructor == String || l.constructor == Function ? { label:l } : l; + lo = _makeLabelOverlay(params); + this.overlays.push(lo); + } + else { + if (l.constructor == String || l.constructor == Function) lo.setLabel(l); + else { + if (l.label) lo.setLabel(l.label); + if (l.location) lo.setLocation(l.location); + } + } + + if (!self._jsPlumb.isSuspendDrawing()) + self.repaint(); + }; + + + this.getLabel = function() { + var lo = self.getOverlay(_internalLabelOverlayId); + return lo != null ? lo.getLabel() : null; + }; + + + this.getLabelOverlay = function() { + return self.getOverlay(_internalLabelOverlayId); + }; + + var superAt = this.applyType; + this.applyType = function(t) { + superAt(t); + self.removeAllOverlays(); + if (t.overlays) { + for (var i = 0; i < t.overlays.length; i++) + self.addOverlay(t.overlays[i], true); + } + }; + }, + + _bindListeners = function(obj, _self, _hoverFunction) { + obj.bind("click", function(ep, e) { _self.fire("click", _self, e); }); + obj.bind("dblclick", function(ep, e) { _self.fire("dblclick", _self, e); }); + obj.bind("contextmenu", function(ep, e) { _self.fire("contextmenu", _self, e); }); + obj.bind("mouseenter", function(ep, e) { + if (!_self.isHover()) { + _hoverFunction(true); + _self.fire("mouseenter", _self, e); + } + }); + obj.bind("mouseexit", function(ep, e) { + if (_self.isHover()) { + _hoverFunction(false); + _self.fire("mouseexit", _self, e); + } + }); + }; + + var _jsPlumbInstanceIndex = 0, + getInstanceIndex = function() { + var i = _jsPlumbInstanceIndex + 1; + _jsPlumbInstanceIndex++; + return i; + }; + + var jsPlumbInstance = function(_defaults) { + + /* + * Property: Defaults + * + * These are the default settings for jsPlumb. They are what will be used if you do not supply specific pieces of information + * to the various API calls. A convenient way to implement your own look and feel can be to override these defaults + * by including a script somewhere after the jsPlumb include, but before you make any calls to jsPlumb. + * + * Properties: + * - *Anchor* The default anchor to use for all connections (both source and target). Default is "BottomCenter". + * - *Anchors* The default anchors to use ([source, target]) for all connections. Defaults are ["BottomCenter", "BottomCenter"]. + * - *ConnectionsDetachable* Whether or not connections are detachable by default (using the mouse). Defaults to true. + * - *ConnectionOverlays* The default overlay definitions for Connections. Defaults to an empty list. + * - *Connector* The default connector definition to use for all connections. Default is "Bezier". + * - *ConnectorZIndex* Optional value for the z-index of Connections that are not in the hover state. If you set this, jsPlumb will set the z-index of all created Connections to be this value, and the z-index of any Connections in the hover state to be this value plus one. This brings hovered connections up on top of others, which is a nice effect in busy UIs. + * - *Container* Optional selector or element id that instructs jsPlumb to append elements it creates to a specific element. + * - *DragOptions* The default drag options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *DropOptions* The default drop options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *Endpoint* The default endpoint definition to use for all connections (both source and target). Default is "Dot". + * - *EndpointOverlays* The default overlay definitions for Endpoints. Defaults to an empty list. + * - *Endpoints* The default endpoint definitions ([ source, target ]) to use for all connections. Defaults are ["Dot", "Dot"]. + * - *EndpointStyle* The default style definition to use for all endpoints. Default is fillStyle:"#456". + * - *EndpointStyles* The default style definitions ([ source, target ]) to use for all endpoints. Defaults are empty. + * - *EndpointHoverStyle* The default hover style definition to use for all endpoints. Default is null. + * - *EndpointHoverStyles* The default hover style definitions ([ source, target ]) to use for all endpoints. Defaults are null. + * - *HoverPaintStyle* The default hover style definition to use for all connections. Defaults are null. + * - *LabelStyle* The default style to use for label overlays on connections. + * - *LogEnabled* Whether or not the jsPlumb log is enabled. defaults to false. + * - *Overlays* The default overlay definitions (for both Connections and Endpoint). Defaults to an empty list. + * - *MaxConnections* The default maximum number of connections for an Endpoint. Defaults to 1. + * - *PaintStyle* The default paint style for a connection. Default is line width of 8 pixels, with color "#456". + * - *RenderMode* What mode to use to paint with. If you're on IE<9, you don't really get to choose this. You'll just get VML. Otherwise, the jsPlumb default is to use SVG. + * - *Scope* The default "scope" to use for connections. Scope lets you assign connections to different categories. + */ + this.Defaults = { + Anchor : "BottomCenter", + Anchors : [ null, null ], + ConnectionsDetachable : true, + ConnectionOverlays : [ ], + Connector : "Bezier", + ConnectorZIndex : null, + Container : null, + DragOptions : { }, + DropOptions : { }, + Endpoint : "Dot", + EndpointOverlays : [ ], + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : "#456" }, + EndpointStyles : [ null, null ], + EndpointHoverStyle : null, + EndpointHoverStyles : [ null, null ], + HoverPaintStyle : null, + LabelStyle : { color : "black" }, + LogEnabled : false, + Overlays : [ ], + MaxConnections : 1, + PaintStyle : { lineWidth : 8, strokeStyle : "#456" }, + //Reattach:false, + RenderMode : "svg", + Scope : "jsPlumb_DefaultScope" + }; + if (_defaults) jsPlumb.extend(this.Defaults, _defaults); + + this.logEnabled = this.Defaults.LogEnabled; + + var _connectionTypes = { }, _endpointTypes = {}; + this.registerConnectionType = function(id, type) { + _connectionTypes[id] = jsPlumb.extend({}, type); + }; + this.registerConnectionTypes = function(types) { + for (var i in types) + _connectionTypes[i] = jsPlumb.extend({}, types[i]); + }; + this.registerEndpointType = function(id, type) { + _endpointTypes[id] = jsPlumb.extend({}, type); + }; + this.registerEndpointTypes = function(types) { + for (var i in types) + _endpointTypes[i] = jsPlumb.extend({}, types[i]); + }; + this.getType = function(id, typeDescriptor) { + return typeDescriptor === "connection" ? _connectionTypes[id] : _endpointTypes[id]; + }; + + jsPlumbUtil.EventGenerator.apply(this); + var _currentInstance = this, + _instanceIndex = getInstanceIndex(), + _bb = _currentInstance.bind, + _initialDefaults = {}, + _zoom = 1; + + this.setZoom = function(z, repaintEverything) { + _zoom = z; + if (repaintEverything) _currentInstance.repaintEverything(); + }; + this.getZoom = function() { return _zoom; }; + + for (var i in this.Defaults) + _initialDefaults[i] = this.Defaults[i]; + + this.bind = function(event, fn) { + if ("ready" === event && initialized) fn(); + else _bb.apply(_currentInstance,[event, fn]); + }; + + /* + Function: importDefaults + Imports all the given defaults into this instance of jsPlumb. + */ + _currentInstance.importDefaults = function(d) { + for (var i in d) { + _currentInstance.Defaults[i] = d[i]; + } + }; + + /* + Function:restoreDefaults + Restores the default settings to "factory" values. + */ + _currentInstance.restoreDefaults = function() { + _currentInstance.Defaults = jsPlumb.extend({}, _initialDefaults); + }; + + var log = null, + resizeTimer = null, + initialized = false, + connectionsByScope = {}, + /** + * map of element id -> endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + endpointsByElement = {}, + endpointsByUUID = {}, + offsets = {}, + offsetTimestamps = {}, + floatingConnections = {}, + draggableStates = {}, + canvasList = [], + sizes = [], + //listeners = {}, // a map: keys are event types, values are lists of listeners. + DEFAULT_SCOPE = this.Defaults.Scope, + renderMode = null, // will be set in init() + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }, + + /** + * appends an element to some other element, which is calculated as follows: + * + * 1. if _currentInstance.Defaults.Container exists, use that element. + * 2. if the 'parent' parameter exists, use that. + * 3. otherwise just use the root element (for DOM usage, the document body). + * + */ + _appendElement = function(el, parent) { + if (_currentInstance.Defaults.Container) + jsPlumb.CurrentLibrary.appendElement(el, _currentInstance.Defaults.Container); + else if (!parent) + //document.body.appendChild(el); + jsPlumbAdapter.appendToRoot(el); + else + jsPlumb.CurrentLibrary.appendElement(el, parent); + }, + + _curIdStamp = 1, + _idstamp = function() { return "" + _curIdStamp++; }, + + /** + * YUI, for some reason, put the result of a Y.all call into an object that contains + * a '_nodes' array, instead of handing back an array-like object like the other + * libraries do. + */ + _convertYUICollection = function(c) { + return c._nodes ? c._nodes : c; + }, + + /** + * Draws an endpoint and its connections. this is the main entry point into drawing connections as well + * as endpoints, since jsPlumb is endpoint-centric under the hood. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + _draw = function(element, ui, timestamp) { + + // TOD is it correct to filter by headless at this top level? how would a headless adapter ever repaint? + if (!jsPlumbAdapter.headless && !_suspendDrawing) { + var id = _getAttribute(element, "id"), + repaintEls = _currentInstance.dragManager.getElementsForDraggable(id); + + if (timestamp == null) timestamp = _timestamp(); + + _currentInstance.anchorManager.redraw(id, ui, timestamp); + + if (repaintEls) { + for (var i in repaintEls) { + _currentInstance.anchorManager.redraw(repaintEls[i].id, ui, timestamp, repaintEls[i].offset); + } + } + } + }, + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + _elementProxy = function(element, fn) { + var retVal = null; + if (_isArray(element)) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }, + + /** + * gets an Endpoint by uuid. + */ + _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }, + + /** + * inits a draggable if it's not already initialised. + */ + _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + // TODO move to DragManager? + if (!jsPlumbAdapter.headless) { + var draggable = isDraggable == null ? false : isDraggable, + jpcl = jsPlumb.CurrentLibrary; + if (draggable) { + if (jpcl.isDragSupported(element) && !jpcl.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jpcl.dragEvents["drag"], + stopEvent = jpcl.dragEvents["stop"], + startEvent = jpcl.dragEvents["start"]; + + options[startEvent] = _wrap(options[startEvent], function() { + _currentInstance.setHoverSuspended(true); + }); + + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jpcl.getUIPosition(arguments, _currentInstance.getZoom()); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jpcl.getUIPosition(arguments, _currentInstance.getZoom()); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + _currentInstance.setHoverSuspended(false); + }); + var elId = _getId(element); // need ID + draggableStates[elId] = true; + var draggable = draggableStates[elId]; + options.disabled = draggable == null ? false : !draggable; + jpcl.initDraggable(element, options, false); + _currentInstance.dragManager.register(element); + } + } + } + }, + + /* + * prepares a final params object that can be passed to _newConnection, taking into account defaults, events, etc. + */ + _prepareConnectionParams = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + // hotwire endpoints passed as source or target to sourceEndpoint/targetEndpoint, respectively. + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not full. + // source: + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + // target: + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + // if source endpoint mandates connection type and nothing specified in our params, use it. + if (!_p.type && _p.sourceEndpoint) + _p.type = _p.sourceEndpoint.connectionType; + + // copy in any connectorOverlays that were specified on the source endpoint. + // it doesnt copy target endpoint overlays. i'm not sure if we want it to or not. + if (_p.sourceEndpoint && _p.sourceEndpoint.connectorOverlays) { + _p.overlays = _p.overlays || []; + for (var i = 0; i < _p.sourceEndpoint.connectorOverlays.length; i++) { + _p.overlays.push(_p.sourceEndpoint.connectorOverlays[i]); + } + } + + // tooltip. params.tooltip takes precedence, then sourceEndpoint.connectorTooltip. + _p.tooltip = params.tooltip; + if (!_p.tooltip && _p.sourceEndpoint && _p.sourceEndpoint.connectorTooltip) + _p.tooltip = _p.sourceEndpoint.connectorTooltip; + + // if there's a target specified (which of course there should be), and there is no + // target endpoint specified, and 'newConnection' was not set to true, then we check to + // see if a prior call to makeTarget has provided us with the specs for the target endpoint, and + // we use those if so. additionally, if the makeTarget call was specified with 'uniqueEndpoint' set + // to true, then if that target endpoint has already been created, we re-use it. + if (_p.target && !_p.target.endpoint && !_p.targetEndpoint && !_p.newConnection) { + var tid = _getId(_p.target), + tep =_targetEndpointDefinitions[tid], + existingUniqueEndpoint = _targetEndpoints[tid]; + + if (tep) { + // if target not enabled, return. + if (!_targetsEnabled[tid]) return; + + // check for max connections?? + var newEndpoint = existingUniqueEndpoint != null ? existingUniqueEndpoint : _currentInstance.addEndpoint(_p.target, tep); + if (_targetEndpointsUnique[tid]) _targetEndpoints[tid] = newEndpoint; + _p.targetEndpoint = newEndpoint; + newEndpoint._makeTargetCreator = true; + } + } + + // same thing, but for source. + if (_p.source && !_p.source.endpoint && !_p.sourceEndpoint && !_p.newConnection) { + var tid = _getId(_p.source), + tep = _sourceEndpointDefinitions[tid], + existingUniqueEndpoint = _sourceEndpoints[tid]; + + if (tep) { + // if source not enabled, return. + if (!_sourcesEnabled[tid]) return; + + var newEndpoint = existingUniqueEndpoint != null ? existingUniqueEndpoint : _currentInstance.addEndpoint(_p.source, tep); + if (_sourceEndpointsUnique[tid]) _sourceEndpoints[tid] = newEndpoint; + _p.sourceEndpoint = newEndpoint; + } + } + + return _p; + }, + + _newConnection = function(params) { + var connectionFunc = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + endpointFunc = _currentInstance.Defaults.EndpointType || Endpoint, + parent = jsPlumb.CurrentLibrary.getParent; + + if (params.container) + params["parent"] = params.container; + else { + if (params.sourceEndpoint) + params["parent"] = params.sourceEndpoint.parent; + else if (params.source.constructor == endpointFunc) + params["parent"] = params.source.parent; + else params["parent"] = parent(params.source); + } + + params["_jsPlumb"] = _currentInstance; + var con = new connectionFunc(params); + con.id = "con_" + _idstamp(); + _eventFireProxy("click", "click", con); + _eventFireProxy("dblclick", "dblclick", con); + _eventFireProxy("contextmenu", "contextmenu", con); + return con; + }, + + /** + * adds the connection to the backing model, fires an event if necessary and then redraws + */ + _finaliseConnection = function(jpc, params, originalEvent) { + params = params || {}; + // add to list of connections (by scope). + if (!jpc.suspendedEndpoint) + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + if (!params.doNotFireConnectionEvent && params.fireEvent !== false) { + + var eventArgs = { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }; + + _currentInstance.fire("jsPlumbConnection", eventArgs, originalEvent); + // this is from 1.3.11 onwards. "jsPlumbConnection" always felt so unnecessary, so + // I've added this alias in 1.3.11, with a view to removing "jsPlumbConnection" completely in a future version. be aware, of course, you should only register listeners for one or the other of these events. + _currentInstance.fire("connection", eventArgs, originalEvent); + } + + // always inform the anchor manager + // except that if jpc has a suspended endpoint it's not true to say the + // connection is new; it has just (possibly) moved. the question is whether + // to make that call here or in the anchor manager. i think perhaps here. + _currentInstance.anchorManager.newConnection(jpc); + // force a paint + _draw(jpc.source); + }, + + _eventFireProxy = function(event, proxyEvent, obj) { + obj.bind(event, function(originalObject, originalEvent) { + _currentInstance.fire(proxyEvent, obj, originalEvent); + }); + }, + + /** + * for the given endpoint params, returns an appropriate parent element for the UI elements that will be added. + * this function is used by _newEndpoint (directly below), and also in the makeSource function in jsPlumb. + * + * the logic is to first look for a "container" member of params, and pass that back if found. otherwise we + * handoff to the 'getParent' function in the current library. + */ + _getParentFromParams = function(params) { + if (params.container) + return params.container; + else { + var tag = jsPlumb.CurrentLibrary.getTagName(params.source), + p = jsPlumb.CurrentLibrary.getParent(params.source); + if (tag && tag.toLowerCase() === "td") + return jsPlumb.CurrentLibrary.getParent(p); + else return p; + } + }, + + /** + factory method to prepare a new endpoint. this should always be used instead of creating Endpoints + manually, since this method attaches event listeners and an id. + */ + _newEndpoint = function(params) { + var endpointFunc = _currentInstance.Defaults.EndpointType || Endpoint; + params.parent = _getParentFromParams(params); + params["_jsPlumb"] = _currentInstance; + var ep = new endpointFunc(params); + ep.id = "ep_" + _idstamp(); + _eventFireProxy("click", "endpointClick", ep); + _eventFireProxy("dblclick", "endpointDblClick", ep); + _eventFireProxy("contextmenu", "contextmenu", ep); + if (!jsPlumbAdapter.headless) + _currentInstance.dragManager.endpointAdded(params.source); + return ep; + }, + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + _operation = function(elId, func, endpointFunc) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + if (endpointFunc) endpointFunc(endpoints[i]); + } + } + }, + /** + * perform an operation on all elements. + */ + _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }, + + /** + * helper to remove an element from the DOM. + */ + _removeElement = function(element, parent) { + if (element != null && element.parentNode != null) { + element.parentNode.removeChild(element); + } + }, + /** + * helper to remove a list of elements from the DOM. + */ + _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }, + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }, + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + _setVisible = function(el, state, alsoChangeEndpoints) { + state = state === "block"; + var endpointFunc = null; + if (alsoChangeEndpoints) { + if (state) endpointFunc = function(ep) { + ep.setVisible(true, true, true); + }; + else endpointFunc = function(ep) { + ep.setVisible(false, true, true); + }; + } + var id = _getAttribute(el, "id"); + _operation(id, function(jpc) { + if (state && alsoChangeEndpoints) { + // this test is necessary because this functionality is new, and i wanted to maintain backwards compatibility. + // this block will only set a connection to be visible if the other endpoint in the connection is also visible. + var oidx = jpc.sourceId === id ? 1 : 0; + if (jpc.endpoints[oidx].isVisible()) jpc.setVisible(true); + } + else // the default behaviour for show, and what always happens for hide, is to just set the visibility without getting clever. + jpc.setVisible(state); + }, endpointFunc); + }, + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? false : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }, + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + _toggleVisible = function(elId, changeEndpoints) { + var endpointFunc = null; + if (changeEndpoints) { + endpointFunc = function(ep) { + var state = ep.isVisible(); + ep.setVisible(!state); + }; + } + _operation(elId, function(jpc) { + var state = jpc.isVisible(); + jpc.setVisible(!state); + }, endpointFunc); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }, + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (_suspendDrawing && !timestamp) timestamp = _suspendedAt; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return offsets[elId]; + } + if (recalc || !offset) { // if forced repaint or no offset available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s, _currentInstance); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + if (sizes[elId] == null) { + var s = _getElementObject(elId); + if (s != null) sizes[elId] = _getSize(s); + } + } + + if(offsets[elId] && !offsets[elId].right) { + offsets[elId].right = offsets[elId].left + sizes[elId][0]; + offsets[elId].bottom = offsets[elId].top + sizes[elId][1]; + offsets[elId].width = sizes[elId][0]; + offsets[elId].height = sizes[elId][1]; + offsets[elId].centerx = offsets[elId].left + (offsets[elId].width / 2); + offsets[elId].centery = offsets[elId].top + (offsets[elId].height / 2); + } + return offsets[elId]; + }, + + // TODO comparison performance + _getCachedData = function(elId) { + var o = offsets[elId]; + if (!o) o = _updateOffset({elId:elId}); + return {o:o, s:sizes[elId]}; + }, + + /** + * gets an id for the given element, creating and setting one if + * necessary. the id is of the form + * + * jsPlumb__ + * + * where "index in instance" is a monotonically increasing integer that starts at 0, + * for each instance. this method is used not only to assign ids to elements that do not + * have them but also to connections and endpoints. + */ + _getId = function(element, uuid, doNotCreateIfNotFound) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else if (arguments.length == 1 || (arguments.length == 3 && !arguments[2])) + id = "jsPlumb_" + _instanceIndex + "_" + _idstamp(); + + if (!doNotCreateIfNotFound) _setAttribute(ele, "id", id); + } + return id; + }, + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, "jsPlumb function failed : " + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, "wrapped function failed : " + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = "_jsPlumb_connector"; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = "_jsPlumb_endpoint"; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = "_jsPlumb_overlay"; + + this.Anchors = {}; + + this.Connectors = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Endpoints = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Overlays = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + +// ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***************************************** + /* + * Function: bind + * Bind to an event on jsPlumb. + * + * Parameters: + * event - the event to bind. Available events on jsPlumb are: + * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback. + * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback. + * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback. + * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback. + * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback. + * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback. + * - *beforeDrop* : notification that a Connection is about to be dropped. Returning false from this method cancels the drop. jsPlumb passes { sourceId, targetId, scope, connection, dropEndpoint } to your callback. For more information, refer to the jsPlumb documentation. + * - *beforeDetach* : notification that a Connection is about to be detached. Returning false from this method cancels the detach. jsPlumb passes the Connection to your callback. For more information, refer to the jsPlumb documentation. + * - *connectionDrag* : notification that an existing Connection is being dragged. jsPlumb passes the Connection to your callback function. + * - *connectionDragEnd* : notification that the drag of an existing Connection has ended. jsPlumb passes the Connection to your callback function. + * + * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event. + */ + + /* + * Function: unbind + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + +// *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *********************************************************** + + +// --------------------------- jsPLumbInstance public API --------------------------------------------------------- + + /* + Function: addClass + + Helper method to abstract out differences in setting css classes on the different renderer types. + */ + this.addClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.addClass(el, clazz); + }; + + /* + Function: removeClass + + Helper method to abstract out differences in setting css classes on the different renderer types. + */ + this.removeClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.removeClass(el, clazz); + }; + + /* + Function: hasClass + + Helper method to abstract out differences in testing for css classes on the different renderer types. + */ + this.hasClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.hasClass(el, clazz); + }; + + /* + Function: addEndpoint + + Adds an to a given element or elements. + + Parameters: + + el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + params - Object containing Endpoint constructor arguments. For more information, see . + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some + shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in + this object are anything that 'params' can contain. See . + + Returns: + The newly created , if el referred to a single element. Otherwise, an array of newly created s. + + See Also: + + */ + this.addEndpoint = function(el, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // YUI wrapper + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + var _el = _getElementObject(inputs[i]), id = _getId(_el); + p.source = _el; + _updateOffset({ elId : id, timestamp:_suspendedAt }); + var e = _newEndpoint(p); + if (p.parentAnchor) e.parentAnchor = p.parentAnchor; + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e, timestamp:_suspendedAt }); + var endpointPaintParams = { anchorLoc : anchorLoc, timestamp:_suspendedAt }; + if (_suspendDrawing) endpointPaintParams.recalc = false; + e.paint(endpointPaintParams); + results.push(e); + //if (!jsPlumbAdapter.headless) + //_currentInstance.dragManager.endpointAdded(_el); + } + + return results.length == 1 ? results[0] : results; + }; + + /* + Function: addEndpoints + Adds a list of s to a given element or elements. + + Parameters: + target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See 's constructor documentation. + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created s, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(el, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams); + if (_isArray(e)) + Array.prototype.push.apply(results, e); + else results.push(e); + } + return results; + }; + + /* + Function: animate + This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating + the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // onComplete repaints, just to make sure everything looks good at the end of the animation. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /** + * checks for a listener for the given condition, executing it if found, passing in the given value. + * condition listeners would have been attached using "bind" (which is, you could argue, now overloaded, since + * firing click events etc is a bit different to what this does). i thought about adding a "bindCondition" + * or something, but decided against it, for the sake of simplicity. jsPlumb will never fire one of these + * condition events anyway. + */ + this.checkCondition = function(conditionName, value) { + var l = _currentInstance.getListener(conditionName); + var r = true; + if (l && l.length > 0) { + try { + for (var i = 0 ; i < l.length; i++) { + r = r && l[i](value); + } + } + catch (e) { + _log(_currentInstance, "cannot check condition [" + conditionName + "]" + e); + } + } + return r; + }; + + /* + Function: connect + Establishes a between two elements (or s, which are themselves registered to elements). + + Parameters: + params - Object containing constructor arguments for the Connection. See 's constructor documentation. + referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of + Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection. + + Returns: + The newly created . + */ + this.connect = function(params, referenceParams) { + // prepare a final set of parameters to create connection with + var _p = _prepareConnectionParams(params, referenceParams), jpc; + // TODO probably a nicer return value if the connection was not made. _prepareConnectionParams + // will return null (and log something) if either endpoint was full. what would be nicer is to + // create a dedicated 'error' object. + if (_p) { + // a connect call will delete its created endpoints on detach, unless otherwise specified. + // this is because the endpoints belong to this connection only, and are no use to + // anyone else, so they hang around like a bad smell. + if (_p.deleteEndpointsOnDetach == null) + _p.deleteEndpointsOnDetach = true; + + // create the connection. it is not yet registered + jpc = _newConnection(_p); + // now add it the model, fire an event, and redraw + _finaliseConnection(jpc, _p); + } + return jpc; + }; + + /* + Function: deleteEndpoint + Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too) + + Parameters: + object - either an object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + if (endpoint.endpoint.cleanup) endpoint.endpoint.cleanup(); + _removeElements(endpoint.endpoint.getDisplayElements()); + _currentInstance.anchorManager.deleteEndpoint(endpoint); + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + if (!jsPlumbAdapter.headless) + _currentInstance.dragManager.endpointDeleted(endpoint); + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every , and their associated s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + _currentInstance.setSuspendDrawing(true); + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + + _currentInstance.setSuspendDrawing(false, true); + }; + + var fireDetachEvent = function(jpc, doFireEvent, originalEvent) { + // may have been given a connection, or in special cases, an object + var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + argIsConnection = jpc.constructor == connType, + params = argIsConnection ? { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + } : jpc; + + if (doFireEvent) { + _currentInstance.fire("jsPlumbConnectionDetached", params, originalEvent); + // introduced in 1.3.11..an alias because the original event name is unwieldy. in future versions this will be the only event and the other will no longer be fired. + _currentInstance.fire("connectionDetached", params, originalEvent); + } + _currentInstance.anchorManager.connectionDetached(params); + }, + /** + fires an event to indicate an existing connection is being dragged. + */ + fireConnectionDraggingEvent = function(jpc) { + _currentInstance.fire("connectionDrag", jpc); + }, + fireConnectionDragStopEvent = function(jpc) { + _currentInstance.fire("connectionDragStop", jpc); + }; + + + /* + Function: detach + Detaches and then removes a . From 1.3.5 this method has been altered to remove support for + specifying Connections by various parameters; you can now pass in a Connection as the first argument and + an optional parameters object as a second argument. If you need the functionality this method provided + before 1.3.5 then you should use the getConnections method to get the list of Connections to detach, and + then iterate through them, calling this for each one. + + Parameters: + connection - the to detach + params - optional parameters to the detach call. valid values here are + fireEvent : defaults to false; indicates you want jsPlumb to fire a connection + detached event. The thinking behind this is that if you made a programmatic + call to detach an event, you probably don't need the callback. + forceDetach : defaults to false. allows you to override any beforeDetach listeners that may be registered. + + Returns: + true if successful, false if not. + */ + this.detach = function() { + + if (arguments.length == 0) return; + var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + firstArgIsConnection = arguments[0].constructor == connType, + params = arguments.length == 2 ? firstArgIsConnection ? (arguments[1] || {}) : arguments[0] : arguments[0], + fireEvent = (params.fireEvent !== false), + forceDetach = params.forceDetach, + connection = firstArgIsConnection ? arguments[0] : params.connection; + + if (connection) { + if (forceDetach || (connection.isDetachAllowed(connection) + && connection.endpoints[0].isDetachAllowed(connection) + && connection.endpoints[1].isDetachAllowed(connection))) { + if (forceDetach || _currentInstance.checkCondition("beforeDetach", connection)) + connection.endpoints[0].detach(connection, false, true, fireEvent); // TODO check this param iscorrect for endpoint's detach method + } + } + else { + var _p = jsPlumb.extend( {}, params); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1]), fireEvent); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source), + targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + if (_currentInstance.checkCondition("beforeDetach", jpc)) { + jpc.endpoints[0].detach(jpc, false, true, fireEvent); + } + } + }); + } + } + }; + + /* + Function: detachAllConnections + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + params - optional parameters. alowed values: + fireEvent : defaults to true, whether or not to fire the detach event. + + Returns: + void + */ + this.detachAllConnections = function(el, params) { + params = params || {}; + el = _getElementObject(el); + var id = _getAttribute(el, "id"), + endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + }; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Parameters: + params - optional params object containing: + fireEvent : whether or not to fire detach events. defaults to true. + + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function(params) { + params = params || {}; + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of your + library's draggable method so that jsPlumb can setup the appropriate callbacks. Your + underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + // helpers for select/selectEndpoints + var _setOperation = function(list, func, args, selector) { + for (var i = 0; i < list.length; i++) { + list[i][func].apply(list[i], args); + } + return selector(list); + }, + _getOperation = function(list, func, args) { + var out = []; + for (var i = 0; i < list.length; i++) { + out.push([ list[i][func].apply(list[i], args), list[i] ]); + } + return out; + }, + setter = function(list, func, selector) { + return function() { + return _setOperation(list, func, arguments, selector); + }; + }, + getter = function(list, func) { + return function() { + return _getOperation(list, func, arguments); + }; + }, + prepareList = function(input, doNotGetIds) { + var r = []; + if (input) { + if (typeof input == 'string') { + if (input === "*") return input; + r.push(input); + } + else { + if (doNotGetIds) r = input; + else { + for (var i = 0; i < input.length; i++) + r.push(_getId(_getElementObject(input[i]))); + } + } + } + return r; + }, + filterList = function(list, value, missingIsFalse) { + if (list === "*") return true; + return list.length > 0 ? _indexOf(list, value) != -1 : !missingIsFalse; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method, + * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the + * default scope). If multiple scopes are passed in, the return value will be a map of { scope -> [ connection... ] }. + * + * Parameters: + * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list + * of connections that are in the given scope. use '*' for all scopes. + * options - if the argument is a JS object, you can specify a finer-grained filter: + * + * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes. Also may have the value '*', indicating any scope. + * - *source* either a string representing an element id, a selector, or an array of ids. Also may have the value '*', indicating any source. Constrains the result to connections having this/these element(s) as source. + * - *target* either a string representing an element id, a selector, or an array of ids. Also may have the value '*', indicating any target. Constrains the result to connections having this/these element(s) as target. + * flat - return results in a flat array (don't return an object whose keys are scopes and whose values are lists per scope). + * + */ + this.getConnections = function(options, flat) { + if (!options) { + options = {}; + } else if (options.constructor == String) { + options = { "scope": options }; + } + var + scope = options.scope || _currentInstance.getDefaultScope(), + scopes = prepareList(scope, true), + sources = prepareList(options.source), + targets = prepareList(options.target), + results = (!flat && scopes.length > 1) ? {} : [], + _addOne = function(scope, obj) { + if (!flat && scopes.length > 1) { + var ss = results[scope]; + if (ss == null) { + ss = []; results[scope] = ss; + } + ss.push(obj); + } else results.push(obj); + }; + for ( var i in connectionsByScope) { + if (filterList(scopes, i)) { + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filterList(sources, c.sourceId) && filterList(targets, c.targetId)) + _addOne(i, c); + } + } + } + return results; + }; + + var _curryEach = function(list, executor) { + return function(f) { + for (var i = 0; i < list.length; i++) { + f(list[i]); + } + return executor(list); + }; + }, + _curryGet = function(list) { + return function(idx) { + return list[idx]; + }; + }; + + var _makeCommonSelectHandler = function(list, executor) { + return { + // setters + setHover:setter(list, "setHover", executor), + removeAllOverlays:setter(list, "removeAllOverlays", executor), + setLabel:setter(list, "setLabel", executor), + addOverlay:setter(list, "addOverlay", executor), + removeOverlay:setter(list, "removeOverlay", executor), + removeOverlays:setter(list, "removeOverlays", executor), + showOverlay:setter(list, "showOverlay", executor), + hideOverlay:setter(list, "hideOverlay", executor), + showOverlays:setter(list, "showOverlays", executor), + hideOverlays:setter(list, "hideOverlays", executor), + setPaintStyle:setter(list, "setPaintStyle", executor), + setHoverPaintStyle:setter(list, "setHoverPaintStyle", executor), + setParameter:setter(list, "setParameter", executor), + setParameters:setter(list, "setParameters", executor), + setVisible:setter(list, "setVisible", executor), + setZIndex:setter(list, "setZIndex", executor), + repaint:setter(list, "repaint", executor), + addType:setter(list, "addType", executor), + toggleType:setter(list, "toggleType", executor), + removeType:setter(list, "removeType", executor), + + // getters + getLabel:getter(list, "getLabel"), + getOverlay:getter(list, "getOverlay"), + isHover:getter(list, "isHover"), + getParameter:getter(list, "getParameter"), + getParameters:getter(list, "getParameters"), + getPaintStyle:getter(list, "getPaintStyle"), + getHoverPaintStyle:getter(list, "getHoverPaintStyle"), + isVisible:getter(list, "isVisible"), + getZIndex:getter(list, "getZIndex"), + hasType:getter(list, "hasType"), + getType:getter(list, "getType"), + + // util + length:list.length, + each:_curryEach(list, executor), + get:_curryGet(list) + }; + + }; + + var _makeConnectionSelectHandler = function(list) { + var common = _makeCommonSelectHandler(list, _makeConnectionSelectHandler); + return jsPlumb.CurrentLibrary.extend(common, { + // setters + setDetachable:setter(list, "setDetachable", _makeConnectionSelectHandler), + setConnector:setter(list, "setConnector", _makeConnectionSelectHandler), + detach:function() { + for (var i = 0; i < list.length; i++) + _currentInstance.detach(list[i]); + }, + // getters + isDetachable:getter(list, "isDetachable") + }); + }; + + var _makeEndpointSelectHandler = function(list) { + var common = _makeCommonSelectHandler(list, _makeEndpointSelectHandler); + return jsPlumb.CurrentLibrary.extend(common, { + setEnabled:setter(list, "setEnabled", _makeEndpointSelectHandler), + isEnabled:getter(list, "isEnabled"), + detachAll:function() { + for (var i = 0; i < list.length; i++) + list[i].detachAll(); + }, + "delete":function() { + for (var i = 0; i < list.length; i++) + _currentInstance.deleteEndpoint(list[i]); + } + }); + }; + + /* + * Function: select + * Selects a set of Connections, using the filter options from the getConnections method, and returns an object + * that allows you to perform an operation on all of the Connections at once. The return value from any of these + * operations is the original list of Connections, allowing operations to be chained (for 'setter' type operations). + * 'getter' type operations return an array of values, where each entry is a [Connection, return value] pair. + * + * Parameters: + * scope - see getConnections + * source - see getConnections + * target - see getConnections + * + * Returns: + * A list of Connections on which operations may be executed. 'Setter' type operations can be chained; 'getter' type operations + * return an array of [Connection, value] pairs, one entry for each Connection in the list returned. The full list of operations + * is as follows (where not specified, the operation's effect or return value is the same as the corresponding method on Connection) : + * - setHover + * - removeAllOverlays + * - setLabel + * - addOverlay + * - removeOverlay + * - removeOverlays + * - showOverlay + * - hideOverlay + * - showOverlays + * - hideOverlays + * - setPaintStyle + * - setHoverPaintStyle + * - setDetachable + * - setConnector + * - setParameter + * - setParameters + * - getLabel + * - getOverlay + * - isHover + * - isDetachable + * - getParameter + * - getParameters + * - getPaintStyle + * - getHoverPaintStyle + * - detach detaches all the connections in the list. not chainable and does not return anything. + * - length : returns the length of the list. + * - get(index) : returns the Connection at 'index' in the list. + * - each(function(connection)...) : allows you to specify your own function to execute; this function is chainable. + */ + this.select = function(params) { + params = params || {}; + params.scope = params.scope || "*"; + var c = _currentInstance.getConnections(params, true); + return _makeConnectionSelectHandler(c); + }; + + /* + * Function: selectEndpoints + * Selects a set of Endpoints and returns an object that allows you to execute various different methods on them at once. The return + * value from any of these operations is the original list of Endpoints, allowing operations to be chained (for 'setter' type + * operations). 'getter' type operations return an array of values, where each entry is an [Endpoint, return value] pair. + * + * Parameters: + * scope - either a string or an array of strings. + * source - either a string, a dom element, or a selector, or an array of any mix of these. limits returned endpoints to those that are declared as a source endpoint on any elements identified. + * target - either a string, a dom element, or a selector, or an array of any mix of these. limits returned endpoints to those that are declared as a target endpoint on any elements identified. + * element - either a string, a dom element, or a selector, or an array of any mix of these. limits returned endpoints to those that are declared as either a source OR a target endpoint on any elements identified. + * + * Returns: + * A list of Endpoints on which operations may be executed. 'Setter' type operations can be chained; 'getter' type operations + * return an array of [Endpoint, value] pairs, one entry for each Endpoint in the list returned. The full list of operations + * is as follows (where not specified, the operation's effect or return value is the same as the corresponding method on Endpoint) : + * - setHover + * - removeAllOverlays + * - setLabel + * - addOverlay + * - removeOverlay + * - removeOverlays + * - showOverlay + * - hideOverlay + * - showOverlays + * - hideOverlays + * - setPaintStyle + * - setHoverPaintStyle + * - setParameter + * - setParameters + * - getLabel + * - getOverlay + * - isHover + * - isDetachable + * - getParameter + * - getParameters + * - getPaintStyle + * - getHoverPaintStyle + * - detachAll Detaches all the Connections from every Endpoint in the list. not chainable and does not return anything. + * - delete Deletes every Endpoint in the list. not chainable and does not return anything. + * - length : returns the length of the list. + * - get(index) : returns the Endpoint at 'index' in the list. + * - each(function(endpoint)...) : allows you to specify your own function to execute; this function is chainable. + */ + this.selectEndpoints = function(params) { + params = params || {}; + params.scope = params.scope || "*"; + var noElementFilters = !params.element && !params.source && !params.target, + elements = noElementFilters ? "*" : prepareList(params.element), + sources = noElementFilters ? "*" : prepareList(params.source), + targets = noElementFilters ? "*" : prepareList(params.target), + scopes = prepareList(params.scope, true); + + var ep = []; + + for (var el in endpointsByElement) { + var either = filterList(elements, el, true), + source = filterList(sources, el, true), + sourceMatchExact = sources != "*", + target = filterList(targets, el, true), + targetMatchExact = targets != "*"; + + // if they requested 'either' then just match scope. otherwise if they requested 'source' (not as a wildcard) then we have to match only endpoints that have isSource set to to true, and the same thing with isTarget. + if ( either || source || target ) { + inner: + for (var i = 0; i < endpointsByElement[el].length; i++) { + var _ep = endpointsByElement[el][i]; + if (filterList(scopes, _ep.scope, true)) { + + var noMatchSource = (sourceMatchExact && sources.length > 0 && !_ep.isSource), + noMatchTarget = (targetMatchExact && targets.length > 0 && !_ep.isTarget); + + if (noMatchSource || noMatchTarget) + continue inner; + + ep.push(_ep); + } + } + } + } + + return _makeEndpointSelectHandler(ep); + }; + + /* + * Function: getAllConnections + * Gets all connections, as a map of { scope -> [ connection... ] }. + */ + this.getAllConnections = function() { + return connectionsByScope; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /** + * Function:getEndpoints + * Gets the list of Endpoints for a given selector, or element id. + * @param el + * @return + */ + this.getEndpoints = function(el) { + return endpointsByElement[_getId(el)]; + }; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + this.getOffset = function(id) { + var o = offsets[id]; + return _updateOffset({elId:id}); + }; + + this.getSelector = function(spec) { + return jsPlumb.CurrentLibrary.getSelector(spec); + }; + + this.getSize = function(id) { + var s = sizes[id]; + if (!s) _updateOffset({elId:id}); + return sizes[id]; + }; + + this.appendElement = _appendElement; + + var _hoverSuspended = false; + this.isHoverSuspended = function() { return _hoverSuspended; }; + this.setHoverSuspended = function(s) { _hoverSuspended = s; }; + + var _isAvailable = function(m) { + return function() { + return jsPlumbAdapter.isRenderModeAvailable(m); + }; + } + this.isCanvasAvailable = _isAvailable("canvas"); + this.isSVGAvailable = _isAvailable("svg"); + this.isVMLAvailable = _isAvailable("vml"); + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + changeEndpoints - whether not to also hide endpoints on the element. by default this is false. + + Returns: + void + */ + this.hide = function(el, changeEndpoints) { + _setVisible(el, "none", changeEndpoints); + }; + + // exposed for other objects to use to get a unique id. + this.idstamp = _idstamp; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + if (!initialized) { + _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run. + + var bindOne = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && renderMode == jsPlumb.CANVAS) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + var t = c[i].connector[event](e); + if (t) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i].endpoint[event](e)) return; + } + } + } + }); + }; + bindOne("click");bindOne("dblclick");bindOne("mousemove");bindOne("mousedown");bindOne("mouseup");bindOne("contextmenu"); + + initialized = true; + _currentInstance.fire("ready"); + } + }; + + this.log = log; + this.jsPlumbUIComponent = jsPlumbUIComponent; + + /* + * Creates an anchor with the given params. + * + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function() { + if (arguments.length == 0) return null; + var specimen = arguments[0], elementId = arguments[1], jsPlumbInstance = arguments[2], newAnchor = null; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; //TODO hazy here about whether it should be added or is already added somehow. + // is it the name of an anchor type? + else if (typeof specimen == "string") { + newAnchor = jsPlumb.Anchors[arguments[0]]({elementId:elementId, jsPlumbInstance:_currentInstance}); + } + // is it an array? it will be one of: + // an array of [name, params] - this defines a single anchor + // an array of arrays - this defines some dynamic anchors + // an array of numbers - this defines a single anchor. + else if (_isArray(specimen)) { + if (_isArray(specimen[0]) || _isString(specimen[0])) { + if (specimen.length == 2 && _isString(specimen[0]) && _isObject(specimen[1])) { + var pp = jsPlumb.extend({elementId:elementId, jsPlumbInstance:_currentInstance}, specimen[1]); + newAnchor = jsPlumb.Anchors[specimen[0]](pp); + } + else + newAnchor = new DynamicAnchor(specimen, null, elementId); + } + else { + var anchorParams = { + x:specimen[0], y:specimen[1], + orientation : (specimen.length >= 4) ? [ specimen[2], specimen[3] ] : [0,0], + offsets : (specimen.length == 6) ? [ specimen[4], specimen[5] ] : [ 0, 0 ], + elementId:elementId + }; + newAnchor = new Anchor(anchorParams); + newAnchor.clone = function() { return new Anchor(anchorParams); }; + } + } + + if (!newAnchor.id) newAnchor.id = "anchor_" + _idstamp(); + return newAnchor; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types, elementId, jsPlumbInstance) { + var r = []; + for ( var i = 0; i < types.length; i++) { + if (typeof types[i] == "string") + r.push(jsPlumb.Anchors[types[i]]({elementId:elementId, jsPlumbInstance:jsPlumbInstance})); + else if (_isArray(types[i])) + r.push(_currentInstance.makeAnchor(types[i], elementId, jsPlumbInstance)); + } + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /** + * Function: makeTarget + * Makes some DOM element a Connection target, allowing you to drag connections to it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a target. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * scope optional. scope for the drop zone. + * dropOptions optional. same stuff as you would pass to dropOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to true. whether or not to delete + * any Endpoints created by a connection to this target if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * maxConnections optional. Specifies the maximum number of Connections that can be made to this element as a target. Default is no limit. + * onMaxConnections optional. Function to call when user attempts to drop a connection but the limit has been reached. The callback is passed two arguments: a JS object with { element, connection, maxConnection }, and the original event. + * + * + */ + var _targetEndpointDefinitions = {}, + _targetEndpoints = {}, + _targetEndpointsUnique = {}, + _targetMaxConnections = {}, + _setEndpointPaintStylesAndAnchor = function(ep, epIndex) { + ep.paintStyle = ep.paintStyle || + _currentInstance.Defaults.EndpointStyles[epIndex] || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyles[epIndex] || + jsPlumb.Defaults.EndpointStyle; + ep.hoverPaintStyle = ep.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyles[epIndex] || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyles[epIndex] || + jsPlumb.Defaults.EndpointHoverStyle; + + ep.anchor = ep.anchor || + _currentInstance.Defaults.Anchors[epIndex] || + _currentInstance.Defaults.Anchor || + jsPlumb.Defaults.Anchors[epIndex] || + jsPlumb.Defaults.Anchor; + + ep.endpoint = ep.endpoint || + _currentInstance.Defaults.Endpoints[epIndex] || + _currentInstance.Defaults.Endpoint || + jsPlumb.Defaults.Endpoints[epIndex] || + jsPlumb.Defaults.Endpoint; + }; + + this.makeTarget = function(el, params, referenceParams) { + + var p = jsPlumb.extend({_jsPlumb:_currentInstance}, referenceParams); + jsPlumb.extend(p, params); + _setEndpointPaintStylesAndAnchor(p, 1); + var jpcl = jsPlumb.CurrentLibrary, + targetScope = p.scope || _currentInstance.Defaults.Scope, + deleteEndpointsOnDetach = !(p.deleteEndpointsOnDetach === false), + maxConnections = p.maxConnections || -1, + onMaxConnections = p.onMaxConnections; + _doOne = function(_el) { + + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el); + _targetEndpointDefinitions[elid] = p; + _targetEndpointsUnique[elid] = p.uniqueEndpoint, + _targetMaxConnections[elid] = maxConnections, + _targetsEnabled[elid] = true, + proxyComponent = new jsPlumbUIComponent(p); + + var dropOptions = jsPlumb.extend({}, p.dropOptions || {}), + _drop = function() { + + var originalEvent = jsPlumb.CurrentLibrary.getDropEvent(arguments), + targetCount = _currentInstance.select({target:elid}).length; + + + _currentInstance.currentlyDragging = false; + var draggable = _getElementObject(jpcl.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + // restore the original scope if necessary (issue 57) + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id], + source = jpc.endpoints[0], + _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : {}; + + if (!_targetsEnabled[elid] || _targetMaxConnections[elid] > 0 && targetCount >= _targetMaxConnections[elid]){ + //console.log("target element " + elid + " is full."); + if (onMaxConnections) { + onMaxConnections({ + element:_el, + connection:jpc + }, originalEvent); + } + return false; + } + + // unlock the source anchor to allow it to refresh its position if necessary + source.anchor.locked = false; + + if (scope) jpcl.setDragScope(draggable, scope); + + // check if drop is allowed here. + //var _continue = jpc.isDropAllowed(jpc.sourceId, _getId(_el), jpc.scope); + var _continue = proxyComponent.isDropAllowed(jpc.sourceId, _getId(_el), jpc.scope, jpc, null); + + // regardless of whether the connection is ok, reconfigure the existing connection to + // point at the current info. we need this to be correct for the detach event that will follow. + // clear the source endpoint from the list to detach. we will detach this connection at this + // point, but we want to keep the source endpoint. the target is a floating endpoint and should + // be removed. TODO need to figure out whether this code can result in endpoints kicking around + // when they shouldnt be. like is this a full detach of a connection? can it be? + if (jpc.endpointsToDeleteOnDetach) { + if (source === jpc.endpointsToDeleteOnDetach[0]) + jpc.endpointsToDeleteOnDetach[0] = null; + else if (source === jpc.endpointsToDeleteOnDetach[1]) + jpc.endpointsToDeleteOnDetach[1] = null; + } + // reinstate any suspended endpoint; this just puts the connection back into + // a state in which it will report sensible values if someone asks it about + // its target. we're going to throw this connection away shortly so it doesnt matter + // if we manipulate it a bit. + if (jpc.suspendedEndpoint) { + jpc.targetId = jpc.suspendedEndpoint.elementId; + jpc.target = jpcl.getElementObject(jpc.suspendedEndpoint.elementId); + jpc.endpoints[1] = jpc.suspendedEndpoint; + } + + if (_continue) { + + // detach this connection from the source. + source.detach(jpc, false, true, false); + + // make a new Endpoint for the target + var newEndpoint = _targetEndpoints[elid] || _currentInstance.addEndpoint(_el, p); + if (p.uniqueEndpoint) _targetEndpoints[elid] = newEndpoint; // may of course just store what it just pulled out. that's ok. + newEndpoint._makeTargetCreator = true; + + // if the anchor has a 'positionFinder' set, then delegate to that function to find + // out where to locate the anchor. + if (newEndpoint.anchor.positionFinder != null) { + var dropPosition = jpcl.getUIPosition(arguments, _currentInstance.getZoom()), + elPosition = _getOffset(_el, _currentInstance), + elSize = _getSize(_el), + ap = newEndpoint.anchor.positionFinder(dropPosition, elPosition, elSize, newEndpoint.anchor.constructorParams); + newEndpoint.anchor.x = ap[0]; + newEndpoint.anchor.y = ap[1]; + // now figure an orientation for it..kind of hard to know what to do actually. probably the best thing i can do is to + // support specifying an orientation in the anchor's spec. if one is not supplied then i will make the orientation + // be what will cause the most natural link to the source: it will be pointing at the source, but it needs to be + // specified in one axis only, and so how to make that choice? i think i will use whichever axis is the one in which + // the target is furthest away from the source. + } + var c = _currentInstance.connect({ + source:source, + target:newEndpoint, + scope:scope, + previousConnection:jpc, + container:jpc.parent, + deleteEndpointsOnDetach:deleteEndpointsOnDetach, + // 'endpointWillMoveAfterConnection' is set by the makeSource function, and it indicates that the + // given endpoint will actually transfer from the element it is currently attached to to some other + // element after a connection has been established. in that case, we do not want to fire the + // connection event, since it will have the wrong data in it; makeSource will do it for us. + // this is controlled by the 'parent' parameter on a makeSource call. + doNotFireConnectionEvent:source.endpointWillMoveAfterConnection + }); + + // delete the original target endpoint. but only want to do this if the endpoint was created + // automatically and has no other connections. + if (jpc.endpoints[1]._makeTargetCreator && jpc.endpoints[1].connections.length < 2) + _currentInstance.deleteEndpoint(jpc.endpoints[1]); + + if (deleteEndpointsOnDetach) + c.endpointsToDeleteOnDetach = [ source, newEndpoint ]; + + c.repaint(); + } + // if not allowed to drop... + else { + // TODO this code is identical (pretty much) to what happens when a connection + // dragged from a normal endpoint is in this situation. refactor. + // is this an existing connection, and will we reattach? + if (jpc.suspendedEndpoint) { + if (source.isReattach) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + _currentInstance.repaint(source.elementId); + } + else + source.detach(jpc, false, true, true, originalEvent); // otherwise, detach the connection and tell everyone about it. + } + + } + }; + + var dropEvent = jpcl.dragEvents['drop']; + dropOptions["scope"] = dropOptions["scope"] || targetScope; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop); + + jpcl.initDroppable(_el, dropOptions, true); + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + + return _currentInstance; + }; + + /* + * Function: unmakeTarget + * Sets the given element to no longer be a connection target. + * Parameters: + * el - either a string id or a selector representing the element. + * Returns: + * The current jsPlumb instance. + */ + this.unmakeTarget = function(el, doNotClearArrays) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + var elid = _getId(el); + + // TODO this is not an exhaustive unmake of a target, since it does not remove the droppable stuff from + // the element. the effect will be to prevent it form behaving as a target, but it's not completely purged. + if (!doNotClearArrays) { + delete _targetEndpointDefinitions[elid]; + delete _targetEndpointsUnique[elid]; + delete _targetMaxConnections[elid]; + delete _targetsEnabled[elid]; + } + + return _currentInstance; + }; + + /** + * helper method to make a list of elements drop targets. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeTargets = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeTarget(els[i], params, referenceParams); + } + }; + + /** + * Function: makeSource + * Makes some DOM element a Connection source, allowing you to drag connections from it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a source. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * parent optional. the element to add Endpoints to when a Connection is established. if you omit this, + * Endpoints will be added to 'el'. + * scope optional. scope for the connections dragged from this element. + * dragOptions optional. same stuff as you would pass to dragOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to false. whether or not to delete + * any Endpoints created by a connection from this source if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * filter - optional function to call when the user presses the mouse button to start a drag. This function is passed the original + * event and the element on which the associated makeSource call was made. If it returns anything other than false, + * the drag begins as usual. But if it returns false (the boolean false, not something falsey), the drag is aborted. + * + * + */ + var _sourceEndpointDefinitions = {}, + _sourceEndpoints = {}, + _sourceEndpointsUnique = {}, + _sourcesEnabled = {}, + _sourceTriggers = {}, + _sourceMaxConnections = {}, + _targetsEnabled = {}; + + this.makeSource = function(el, params, referenceParams) { + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + _setEndpointPaintStylesAndAnchor(p, 0); + var jpcl = jsPlumb.CurrentLibrary, + maxConnections = p.maxConnections || -1, + onMaxConnections = p.onMaxConnections, + _doOne = function(_el) { + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el), + parent = p.parent, + idToRegisterAgainst = parent != null ? _currentInstance.getId(jpcl.getElementObject(parent)) : elid; + + _sourceEndpointDefinitions[idToRegisterAgainst] = p; + _sourceEndpointsUnique[idToRegisterAgainst] = p.uniqueEndpoint; + _sourcesEnabled[idToRegisterAgainst] = true; + + var stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"], + dragOptions = jsPlumb.extend({ }, p.dragOptions || {}), + existingDrag = dragOptions.drag, + existingStop = dragOptions.stop, + ep = null, + endpointAddedButNoDragYet = false; + + _sourceMaxConnections[idToRegisterAgainst] = maxConnections; + + // set scope if its not set in dragOptions but was passed in in params + dragOptions["scope"] = dragOptions["scope"] || p.scope; + + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + if (existingDrag) existingDrag.apply(this, arguments); + endpointAddedButNoDragYet = false; + }); + + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], function() { + if (existingStop) existingStop.apply(this, arguments); + + //_currentlyDown = false; + _currentInstance.currentlyDragging = false; + + if (ep.connections.length == 0) + _currentInstance.deleteEndpoint(ep); + else { + + jpcl.unbind(ep.canvas, "mousedown"); + + // reset the anchor to the anchor that was initially provided. the one we were using to drag + // the connection was just a placeholder that was located at the place the user pressed the + // mouse button to initiate the drag. + var anchorDef = p.anchor || _currentInstance.Defaults.Anchor, + oldAnchor = ep.anchor, + oldConnection = ep.connections[0]; + + ep.anchor = _currentInstance.makeAnchor(anchorDef, elid, _currentInstance); + + if (p.parent) { + var parent = jpcl.getElementObject(p.parent); + if (parent) { + var currentId = ep.elementId; + + var potentialParent = p.container || _currentInstance.Defaults.Container || jsPlumb.Defaults.Container; + + ep.setElement(parent, potentialParent); + ep.endpointWillMoveAfterConnection = false; + _currentInstance.anchorManager.rehomeEndpoint(currentId, parent); + oldConnection.previousConnection = null; + // remove from connectionsByScope + _removeWithFunction(connectionsByScope[oldConnection.scope], function(c) { + return c.id === oldConnection.id; + }); + _currentInstance.anchorManager.connectionDetached({ + sourceId:oldConnection.sourceId, + targetId:oldConnection.targetId, + connection:oldConnection + }); + _finaliseConnection(oldConnection); + } + } + + ep.repaint(); + _currentInstance.repaint(ep.elementId); + _currentInstance.repaint(oldConnection.targetId); + + } + }); + // when the user presses the mouse, add an Endpoint, if we are enabled. + var mouseDownListener = function(e) { + + // if disabled, return. + if (!_sourcesEnabled[idToRegisterAgainst]) return; + + // if maxConnections reached + var sourceCount = _currentInstance.select({source:idToRegisterAgainst}).length + if (_sourceMaxConnections[idToRegisterAgainst] >= 0 && sourceCount >= _sourceMaxConnections[idToRegisterAgainst]) { + if (onMaxConnections) { + onMaxConnections({ + element:_el, + maxConnections:maxConnections + }, e); + } + return false; + } + + // if a filter was given, run it, and return if it says no. + if (params.filter) { + // pass the original event to the user: + var r = params.filter(jpcl.getOriginalEvent(e), _el); + if (r === false) return; + } + + // make sure we have the latest offset for this div + var myOffsetInfo = _updateOffset({elId:elid}); + + var x = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width, + y = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height, + parentX = x, + parentY = y; + + // if there is a parent, the endpoint will actually be added to it now, rather than the div + // that was the source. in that case, we have to adjust the anchor position so it refers to + // the parent. + if (p.parent) { + var pEl = jpcl.getElementObject(p.parent), + pId = _getId(pEl); + myOffsetInfo = _updateOffset({elId:pId}); + parentX = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width, + parentY = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height; + } + + // we need to override the anchor in here, and force 'isSource', but we don't want to mess with + // the params passed in, because after a connection is established we're going to reset the endpoint + // to have the anchor we were given. + var tempEndpointParams = {}; + jsPlumb.extend(tempEndpointParams, p); + tempEndpointParams.isSource = true; + tempEndpointParams.anchor = [x,y,0,0]; + tempEndpointParams.parentAnchor = [ parentX, parentY, 0, 0 ]; + tempEndpointParams.dragOptions = dragOptions; + // if a parent was given we need to turn that into a "container" argument. this is, by default, + // the parent of the element we will move to, so parent of p.parent in this case. however, if + // the user has specified a 'container' on the endpoint definition or on + // the defaults, we should use that. + if (p.parent) { + var potentialParent = tempEndpointParams.container || _currentInstance.Defaults.Container || jsPlumb.Defaults.Container; + if (potentialParent) + tempEndpointParams.container = potentialParent; + else + tempEndpointParams.container = jsPlumb.CurrentLibrary.getParent(p.parent); + } + + ep = _currentInstance.addEndpoint(elid, tempEndpointParams); + + endpointAddedButNoDragYet = true; + // we set this to prevent connections from firing attach events before this function has had a chance + // to move the endpoint. + ep.endpointWillMoveAfterConnection = p.parent != null; + ep.endpointWillMoveTo = p.parent ? jpcl.getElementObject(p.parent) : null; + + var _delTempEndpoint = function() { + // this mouseup event is fired only if no dragging occurred, by jquery and yui, but for mootools + // it is fired even if dragging has occurred, in which case we would blow away a perfectly + // legitimate endpoint, were it not for this check. the flag is set after adding an + // endpoint and cleared in a drag listener we set in the dragOptions above. + if(endpointAddedButNoDragYet) { + _currentInstance.deleteEndpoint(ep); + } + }; + + _currentInstance.registerListener(ep.canvas, "mouseup", _delTempEndpoint); + _currentInstance.registerListener(_el, "mouseup", _delTempEndpoint); + + // and then trigger its mousedown event, which will kick off a drag, which will start dragging + // a new connection from this endpoint. + jpcl.trigger(ep.canvas, "mousedown", e); + }; + + // register this on jsPlumb so that it can be cleared by a reset. + _currentInstance.registerListener(_el, "mousedown", mouseDownListener); + _sourceTriggers[elid] = mouseDownListener; + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + + return _currentInstance; + }; + + /** + * Function: unmakeSource + * Sets the given element to no longer be a connection source. + * Parameters: + * el - either a string id or a selector representing the element. + * Returns: + * The current jsPlumb instance. + */ + this.unmakeSource = function(el, doNotClearArrays) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + var id = _getId(el), + mouseDownListener = _sourceTriggers[id]; + + if (mouseDownListener) + _currentInstance.unregisterListener(_el, "mousedown", mouseDownListener); + + if (!doNotClearArrays) { + delete _sourceEndpointDefinitions[id]; + delete _sourceEndpointsUnique[id]; + delete _sourcesEnabled[id]; + delete _sourceTriggers[id]; + delete _sourceMaxConnections[id]; + } + + return _currentInstance; + }; + + /* + * Function: unmakeEverySource + * Resets all elements in this instance of jsPlumb so that none of them are connection sources. + * Returns: + * The current jsPlumb instance. + */ + this.unmakeEverySource = function() { + for (var i in _sourcesEnabled) + _currentInstance.unmakeSource(i, true); + + _sourceEndpointDefinitions = {}; + _sourceEndpointsUnique = {}; + _sourcesEnabled = {}; + _sourceTriggers = {}; + }; + + /* + * Function: unmakeEveryTarget + * Resets all elements in this instance of jsPlumb so that none of them are connection targets. + * Returns: + * The current jsPlumb instance. + */ + this.unmakeEveryTarget = function() { + for (var i in _targetsEnabled) + _currentInstance.unmakeTarget(i, true); + + _targetEndpointDefinitions = {}; + _targetEndpointsUnique = {}; + _targetMaxConnections = {}; + _targetsEnabled = {}; + + return _currentInstance; + }; + + /* + * Function: makeSources + * Makes all elements in some array or a selector connection sources. + * Parameters: + * els - either an array of ids or a selector + * params - parameters to configure each element as a source with + * referenceParams - extra parameters to configure each element as a source with. + * + * Returns: + * The current jsPlumb instance. + */ + this.makeSources = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeSource(els[i], params, referenceParams); + } + + return _currentInstance; + }; + + // does the work of setting a source enabled or disabled. + var _setEnabled = function(type, el, state, toggle) { + var a = type == "source" ? _sourcesEnabled : _targetsEnabled; + + if (_isString(el)) a[el] = toggle ? !a[el] : state; + else if (el.length) { + el = _convertYUICollection(el); + for (var i = 0; i < el.length; i++) { + var id = _el = jsPlumb.CurrentLibrary.getElementObject(el[i]), id = _getId(_el); + a[id] = toggle ? !a[id] : state; + } + } + return _currentInstance; + }; + + /* + Function: setSourceEnabled + Sets the enabled state of one or more elements that were previously made a connection source with the makeSource + method. + + Parameters: + el - either a string representing some element's id, or an array of ids, or a selector. + state - true to enable the element(s), false to disable it. + + Returns: + The current jsPlumb instance. + */ + this.setSourceEnabled = function(el, state) { + return _setEnabled("source", el, state); + }; + + /* + * Function: toggleSourceEnabled + * Toggles the source enabled state of the given element or elements. + * + * Parameters: + * el - either a string representing some element's id, or an array of ids, or a selector. + * state - true to enable the element(s), false to disable it. + * + * Returns: + * The current enabled state of the source. + */ + this.toggleSourceEnabled = function(el) { + _setEnabled("source", el, null, true); + return _currentInstance.isSourceEnabled(el); + }; + + /* + * Function: isSource + * Returns whether or not the given element is registered as a connection source. + * + * Parameters: + * el - either a string id, or a selector representing a single element. + * + * Returns: + * True if source, false if not. + */ + this.isSource = function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return _sourcesEnabled[_getId(el)] != null; + }; + + /* + * Function: isSourceEnabled + * Returns whether or not the given connection source is enabled. + * + * Parameters: + * el - either a string id, or a selector representing a single element. + * + * Returns: + * True if enabled, false if not. + */ + this.isSourceEnabled = function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return _sourcesEnabled[_getId(el)] === true; + }; + + /* + * Function: setTargetEnabled + * Sets the enabled state of one or more elements that were previously made a connection target with the makeTarget method. + * method. + * + * Parameters: + * el - either a string representing some element's id, or an array of ids, or a selector. + * state - true to enable the element(s), false to disable it. + * + * Returns: + * The current jsPlumb instance. + */ + this.setTargetEnabled = function(el, state) { + return _setEnabled("target", el, state); + }; + + /* + * Function: toggleTargetEnabled + * Toggles the target enabled state of the given element or elements. + * + * Parameters: + * el - either a string representing some element's id, or an array of ids, or a selector. + * state - true to enable the element(s), false to disable it. + * + * Returns: + * The current enabled state of the target. + */ + this.toggleTargetEnabled = function(el) { + return _setEnabled("target", el, null, true); + return _currentInstance.isTargetEnabled(el); + }; + + /* + Function: isTarget + Returns whether or not the given element is registered as a connection target. + + Parameters: + el - either a string id, or a selector representing a single element. + + Returns: + True if source, false if not. + */ + this.isTarget = function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return _targetsEnabled[_getId(el)] != null; + }; + + /* + Function: isTargetEnabled + Returns whether or not the given connection target is enabled. + + Parameters: + el - either a string id, or a selector representing a single element. + + Returns: + True if enabled, false if not. + */ + this.isTargetEnabled = function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return _targetsEnabled[_getId(el)] === true; + }; + + /* + Function: ready + Helper method to bind a function to jsPlumb's ready event. + */ + this.ready = function(fn) { + _currentInstance.bind("ready", fn); + }, + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el, ui, timestamp) { + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) { + _draw(_getElementObject(el[i]), ui, timestamp); + } + else // ...and single strings. + _draw(_getElementObject(el), ui, timestamp); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId)); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"), + ebe = endpointsByElement[elId]; + if (ebe) { + for ( var i = 0; i < ebe.length; i++) + _currentInstance.deleteEndpoint(ebe[i]); + } + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + var _registeredListeners = {}, + _unbindRegisteredListeners = function() { + for (var i in _registeredListeners) { + for (var j = 0; j < _registeredListeners[i].length; j++) { + var info = _registeredListeners[i][j]; + jsPlumb.CurrentLibrary.unbind(info.el, info.event, info.listener); + } + } + _registeredListeners = {}; + }; + + // internal register listener method. gives us a hook to clean things up + // with if the user calls jsPlumb.reset. + this.registerListener = function(el, type, listener) { + jsPlumb.CurrentLibrary.bind(el, type, listener); + _addToList(_registeredListeners, type, {el:el, event:type, listener:listener}); + }; + + this.unregisterListener = function(el, type, listener) { + jsPlumb.CurrentLibrary.unbind(el, type, listener); + _removeWithFunction(_registeredListeners, function(rl) { + return rl.type == type && rl.listener == listener; + }); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + _currentInstance.deleteEveryEndpoint(); + _currentInstance.unbind(); + _targetEndpointDefinitions = {}; + _targetEndpoints = {}; + _targetEndpointsUnique = {}; + _targetMaxConnections = {}; + _sourceEndpointDefinitions = {}; + _sourceEndpoints = {}; + _sourceEndpointsUnique = {}; + _sourceMaxConnections = {}; + _unbindRegisteredListeners(); + _currentInstance.anchorManager.reset(); + if (!jsPlumbAdapter.headless) + _currentInstance.dragManager.reset(); + }; + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setId + * Changes the id of some element, adjusting all connections and endpoints + * + * Parameters: + * el - a selector, a DOM element, or a string. + * newId - string. + */ + this.setId = function(el, newId, doNotSetAttribute) { + + var id = el.constructor == String ? el : _currentInstance.getId(el), + sConns = _currentInstance.getConnections({source:id, scope:'*'}, true), + tConns = _currentInstance.getConnections({target:id, scope:'*'}, true); + + newId = "" + newId; + + if (!doNotSetAttribute) { + el = jsPlumb.CurrentLibrary.getElementObject(id); + jsPlumb.CurrentLibrary.setAttribute(el, "id", newId); + } + + el = jsPlumb.CurrentLibrary.getElementObject(newId); + + + endpointsByElement[newId] = endpointsByElement[id] || []; + for (var i = 0; i < endpointsByElement[newId].length; i++) { + endpointsByElement[newId][i].elementId = newId; + endpointsByElement[newId][i].element = el; + endpointsByElement[newId][i].anchor.elementId = newId; + } + delete endpointsByElement[id]; + + _currentInstance.anchorManager.changeId(id, newId); + + var _conns = function(list, epIdx, type) { + for (var i = 0; i < list.length; i++) { + list[i].endpoints[epIdx].elementId = newId; + list[i].endpoints[epIdx].element = el; + list[i][type + "Id"] = newId; + list[i][type] = el; + } + }; + _conns(sConns, 0, "source"); + _conns(tConns, 1, "target"); + }; + + /* + * Function: setIdChanged + * Notify jsPlumb that the element with oldId has had its id changed to newId. + */ + this.setIdChanged = function(oldId, newId) { + _currentInstance.setId(oldId, newId, true); + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + + var _suspendDrawing = false, + _suspendedAt = null; + /* + * Function: setSuspendDrawing + * Suspends drawing operations. This can be used when you have a lot of connections to make or endpoints to register; + * it will save you a lot of time. + */ + this.setSuspendDrawing = function(val, repaintAfterwards) { + _suspendDrawing = val; + if (val) _suspendedAt = new Date().getTime(); else _suspendedAt = null; + if (repaintAfterwards) _currentInstance.repaintEverything(); + }; + + /* + * Function: isSuspendDrawing + * Returns whether or not drawing is currently suspended. + */ + this.isSuspendDrawing = function() { + return _suspendDrawing; + }; + + /* + * Constant for use with the setRenderMode method + */ + this.CANVAS = "canvas"; + + /* + * Constant for use with the setRenderMode method + */ + this.SVG = "svg"; + + /* + * Constant for use with the setRenderMode method + */ + this.VML = "vml"; + + /* + * Function: setRenderMode + * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that + * what you asked for is not supported (and that VML is). If you asked for VML but the browser does + * not support it, jsPlumb uses SVG. + * + * Returns: + * the render mode that jsPlumb set, which of course may be different from that requested. + */ + this.setRenderMode = function(mode) { + renderMode = jsPlumbAdapter.setRenderMode(mode); + return renderMode; + }; + + this.getRenderMode = function() { return renderMode; }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * changeEndpoints - whether or not to also change the visible state of the endpoints on the element. this also has a bearing on + * other connections on those endpoints: if their other endpoint is also visible, the connections are made visible. + * + * Returns: + * void + */ + this.show = function(el, changeEndpoints) { + _setVisible(el, "block", changeEndpoints); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + //findIndex : _findIndex, + getId : _getId, + makeAnchor:self.makeAnchor, + makeDynamicAnchor:self.makeDynamicAnchor + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * changeEndpoints - whether or not to also toggle the endpoints on the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + var adjustForParentOffsetAndScroll = function(xy, el) { + + var offsetParent = null, result = xy; + if (el.tagName.toLowerCase() === "svg" && el.parentNode) { + offsetParent = el.parentNode; + } + else if (el.offsetParent) { + offsetParent = el.offsetParent; + } + if (offsetParent != null) { + var po = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(offsetParent, _currentInstance), + so = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : {left:offsetParent.scrollLeft, top:offsetParent.scrollTop}; + + + // i thought it might be cool to do this: + // lastReturnValue[0] = lastReturnValue[0] - offsetParent.offsetLeft + offsetParent.scrollLeft; + // lastReturnValue[1] = lastReturnValue[1] - offsetParent.offsetTop + offsetParent.scrollTop; + // but i think it ignores margins. my reasoning was that it's quicker to not hand off to some underlying + // library. + + result[0] = xy[0] - po.left + so.left; + result[1] = xy[1] - po.top + so.top; + } + + return result; + + }; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + this.elementId = params.elementId; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + + if (timestamp && timestamp === self.timestamp) + return lastReturnValue; + + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + + // adjust loc if there is an offsetParent + lastReturnValue = adjustForParentOffsetAndScroll(lastReturnValue, element.canvas); + + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function(_endpoint) { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + * + * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference, + // the canvas this refers to. + refCanvas = params.referenceCanvas, + size = _getSize(_getElementObject(refCanvas)), + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + xDir = 0, yDir = 0, + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + orientation = null, + _lastResult = null; + + // set these to 0 each; they are used by certain types of connectors in the loopback case, + // when the connector is trying to clear the element it is on. but for floating anchor it's not + // very important. + this.x = 0; this.y = 0; + + this.isFloating = true; + + this.compute = function(params) { + var xy = params.xy, element = params.element, + result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + + // adjust loc if there is an offsetParent + result = adjustForParentOffsetAndScroll(result, element.canvas); + + _lastResult = result; + return result; + }; + + this.getOrientation = function(_endpoint) { + if (orientation) return orientation; + else { + var o = ref.getOrientation(_endpoint); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchor is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector, elementId) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = [], self = this, + _convert = function(anchor) { + return anchor.constructor == Anchor ? anchor: _currentInstance.makeAnchor(anchor, elementId, _currentInstance); + }; + for (var i = 0; i < anchors.length; i++) + _anchors[i] = _convert(anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + this.locked = false; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null, + _curIndex = _anchors.length > 0 ? 0 : -1, + self = this, + + // helper method to calculate the distance between the centers of the two elements. + _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }, + + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + self.x = _curAnchor.x; + self.y = _curAnchor.y; + + return _curAnchor.compute(params); + }; + + this.getCurrentLocation = function() { + return _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + }; + + this.getOrientation = function(_endpoint) { return _curAnchor != null ? _curAnchor.getOrientation(_endpoint) : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + manages anchors for all elements. + */ + // "continuous" anchors: anchors that pick their location based on how many connections the given element has. + // this requires looking at a lot more elements than normal - anything that has had a Continuous anchor applied has + // to be recalculated. so this manager is used as a reference point. the first time, with a new timestamp, that + // a continuous anchor is asked to compute, it calls this guy. or maybe, even, this guy gets called somewhere else + // and compute only ever returns pre-computed values. either way, this is the central point, and we want it to + // be called as few times as possible. + var continuousAnchors = {}, + continuousAnchorLocations = {}, + continuousAnchorOrientations = {}, + Orientation = { HORIZONTAL : "horizontal", VERTICAL : "vertical", DIAGONAL : "diagonal", IDENTITY:"identity" }, + + // TODO this functions uses a crude method of determining orientation between two elements. + // 'diagonal' should be chosen when the angle of the line between the two centers is around + // one of 45, 135, 225 and 315 degrees. maybe +- 15 degrees. + calculateOrientation = function(sourceId, targetId, sd, td) { + + if (sourceId === targetId) return { + orientation:Orientation.IDENTITY, + a:["top", "top"] + }; + + var theta = Math.atan2((td.centery - sd.centery) , (td.centerx - sd.centerx)), + theta2 = Math.atan2((sd.centery - td.centery) , (sd.centerx - td.centerx)), + h = ((sd.left <= td.left && sd.right >= td.left) || (sd.left <= td.right && sd.right >= td.right) || + (sd.left <= td.left && sd.right >= td.right) || (td.left <= sd.left && td.right >= sd.right)), + v = ((sd.top <= td.top && sd.bottom >= td.top) || (sd.top <= td.bottom && sd.bottom >= td.bottom) || + (sd.top <= td.top && sd.bottom >= td.bottom) || (td.top <= sd.top && td.bottom >= sd.bottom)); + + if (! (h || v)) { + var a = null, rls = false, rrs = false, sortValue = null; + if (td.left > sd.left && td.top > sd.top) + a = ["right", "top"]; + else if (td.left > sd.left && sd.top > td.top) + a = [ "top", "left"]; + else if (td.left < sd.left && td.top < sd.top) + a = [ "top", "right"]; + else if (td.left < sd.left && td.top > sd.top) + a = ["left", "top" ]; + + return { orientation:Orientation.DIAGONAL, a:a, theta:theta, theta2:theta2 }; + } + else if (h) return { + orientation:Orientation.HORIZONTAL, + a:sd.top < td.top ? ["bottom", "top"] : ["top", "bottom"], + theta:theta, theta2:theta2 + } + else return { + orientation:Orientation.VERTICAL, + a:sd.left < td.left ? ["right", "left"] : ["left", "right"], + theta:theta, theta2:theta2 + } + }, + placeAnchorsOnLine = function(desc, elementDimensions, elementPosition, + connections, horizontal, otherMultiplier, reverse) { + var a = [], step = elementDimensions[horizontal ? 0 : 1] / (connections.length + 1); + + for (var i = 0; i < connections.length; i++) { + var val = (i + 1) * step, other = otherMultiplier * elementDimensions[horizontal ? 1 : 0]; + if (reverse) + val = elementDimensions[horizontal ? 0 : 1] - val; + + var dx = (horizontal ? val : other), x = elementPosition[0] + dx, xp = dx / elementDimensions[0], + dy = (horizontal ? other : val), y = elementPosition[1] + dy, yp = dy / elementDimensions[1]; + + a.push([ x, y, xp, yp, connections[i][1], connections[i][2] ]); + } + + return a; + }, + standardEdgeSort = function(a, b) { return a[0] > b[0] ? 1 : -1 }, + currySort = function(reverseAngles) { + return function(a,b) { + var r = true; + if (reverseAngles) { + if (a[0][0] < b[0][0]) + r = true; + else + r = a[0][1] > b[0][1]; + } + else { + if (a[0][0] > b[0][0]) + r= true; + else + r =a[0][1] > b[0][1]; + } + return r === false ? -1 : 1; + }; + }, + leftSort = function(a,b) { + // first get adjusted values + var p1 = a[0][0] < 0 ? -Math.PI - a[0][0] : Math.PI - a[0][0], + p2 = b[0][0] < 0 ? -Math.PI - b[0][0] : Math.PI - b[0][0]; + if (p1 > p2) return 1; + else return a[0][1] > b[0][1] ? 1 : -1; + }, + edgeSortFunctions = { + "top":standardEdgeSort, + "right":currySort(true), + "bottom":currySort(true), + "left":leftSort + }, + _sortHelper = function(_array, _fn) { + return _array.sort(_fn); + }, + placeAnchors = function(elementId, _anchorLists) { + var sS = sizes[elementId], sO = offsets[elementId], + placeSomeAnchors = function(desc, elementDimensions, elementPosition, unsortedConnections, isHorizontal, otherMultiplier, orientation) { + if (unsortedConnections.length > 0) { + var sc = _sortHelper(unsortedConnections, edgeSortFunctions[desc]), // puts them in order based on the target element's pos on screen + reverse = desc === "right" || desc === "top", + anchors = placeAnchorsOnLine(desc, elementDimensions, + elementPosition, sc, + isHorizontal, otherMultiplier, reverse ); + + // takes a computed anchor position and adjusts it for parent offset and scroll, then stores it. + var _setAnchorLocation = function(endpoint, anchorPos) { + var a = adjustForParentOffsetAndScroll([anchorPos[0], anchorPos[1]], endpoint.canvas); + continuousAnchorLocations[endpoint.id] = [ a[0], a[1], anchorPos[2], anchorPos[3] ]; + continuousAnchorOrientations[endpoint.id] = orientation; + }; + + for (var i = 0; i < anchors.length; i++) { + var c = anchors[i][4], weAreSource = c.endpoints[0].elementId === elementId, weAreTarget = c.endpoints[1].elementId === elementId; + if (weAreSource) + _setAnchorLocation(c.endpoints[0], anchors[i]); + else if (weAreTarget) + _setAnchorLocation(c.endpoints[1], anchors[i]); + } + } + }; + + placeSomeAnchors("bottom", sS, [sO.left,sO.top], _anchorLists.bottom, true, 1, [0,1]); + placeSomeAnchors("top", sS, [sO.left,sO.top], _anchorLists.top, true, 0, [0,-1]); + placeSomeAnchors("left", sS, [sO.left,sO.top], _anchorLists.left, false, 0, [-1,0]); + placeSomeAnchors("right", sS, [sO.left,sO.top], _anchorLists.right, false, 1, [1,0]); + }, + AnchorManager = function() { + var _amEndpoints = {}, + connectionsByElementId = {}, + self = this, + anchorLists = {}; + + this.reset = function() { + _amEndpoints = {}; + connectionsByElementId = {}; + anchorLists = {}; + }; + this.newConnection = function(conn) { + var sourceId = conn.sourceId, targetId = conn.targetId, + ep = conn.endpoints, + doRegisterTarget = true, + registerConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + + if ((sourceId == targetId) && otherAnchor.isContinuous){ + // remove the target endpoint's canvas. we dont need it. + jsPlumb.CurrentLibrary.removeElement(ep[1].canvas); + doRegisterTarget = false; + } + _addToList(connectionsByElementId, elId, [c, otherEndpoint, otherAnchor.constructor == DynamicAnchor]); + }; + + registerConnection(0, ep[0], ep[0].anchor, targetId, conn); + if (doRegisterTarget) + registerConnection(1, ep[1], ep[1].anchor, sourceId, conn); + }; + this.connectionDetached = function(connInfo) { + var connection = connInfo.connection || connInfo; + var sourceId = connection.sourceId, + targetId = connection.targetId, + ep = connection.endpoints, + removeConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + if (otherAnchor.constructor == FloatingAnchor) { + // no-op + } + else { + _removeWithFunction(connectionsByElementId[elId], function(_c) { + return _c[0].id == c.id; + }); + } + }; + + removeConnection(1, ep[1], ep[1].anchor, sourceId, connection); + removeConnection(0, ep[0], ep[0].anchor, targetId, connection); + + // remove from anchorLists + var sEl = connection.sourceId, + tEl = connection.targetId, + sE = connection.endpoints[0].id, + tE = connection.endpoints[1].id, + _remove = function(list, eId) { + if (list) { // transient anchors dont get entries in this list. + var f = function(e) { return e[4] == eId; }; + _removeWithFunction(list["top"], f); + _removeWithFunction(list["left"], f); + _removeWithFunction(list["bottom"], f); + _removeWithFunction(list["right"], f); + } + }; + + _remove(anchorLists[sEl], sE); + _remove(anchorLists[tEl], tE); + self.redraw(sEl); + self.redraw(tEl); + }; + this.add = function(endpoint, elementId) { + _addToList(_amEndpoints, elementId, endpoint); + }; + this.changeId = function(oldId, newId) { + connectionsByElementId[newId] = connectionsByElementId[oldId]; + _amEndpoints[newId] = _amEndpoints[oldId]; + delete connectionsByElementId[oldId]; + delete _amEndpoints[oldId]; + }; + this.getConnectionsFor = function(elementId) { + return connectionsByElementId[elementId] || []; + }; + this.getEndpointsFor = function(elementId) { + return _amEndpoints[elementId] || []; + }; + this.deleteEndpoint = function(endpoint) { + _removeWithFunction(_amEndpoints[endpoint.elementId], function(e) { + return e.id == endpoint.id; + }); + }; + this.clearFor = function(elementId) { + delete _amEndpoints[elementId]; + _amEndpoints[elementId] = []; + }; + // updates the given anchor list by either updating an existing anchor's info, or adding it. this function + // also removes the anchor from its previous list, if the edge it is on has changed. + // all connections found along the way (those that are connected to one of the faces this function + // operates on) are added to the connsToPaint list, as are their endpoints. in this way we know to repaint + // them wthout having to calculate anything else about them. + var _updateAnchorList = function(lists, theta, order, conn, aBoolean, otherElId, idx, reverse, edgeId, elId, connsToPaint, endpointsToPaint) { + // first try to find the exact match, but keep track of the first index of a matching element id along the way.s + var exactIdx = -1, + firstMatchingElIdx = -1, + endpoint = conn.endpoints[idx], + endpointId = endpoint.id, + oIdx = [1,0][idx], + values = [ [ theta, order ], conn, aBoolean, otherElId, endpointId ], + listToAddTo = lists[edgeId], + listToRemoveFrom = endpoint._continuousAnchorEdge ? lists[endpoint._continuousAnchorEdge] : null; + + if (listToRemoveFrom) { + var rIdx = _findWithFunction(listToRemoveFrom, function(e) { return e[4] == endpointId }); + if (rIdx != -1) { + listToRemoveFrom.splice(rIdx, 1); + // get all connections from this list + for (var i = 0; i < listToRemoveFrom.length; i++) { + _addWithFunction(connsToPaint, listToRemoveFrom[i][1], function(c) { return c.id == listToRemoveFrom[i][1].id }); + _addWithFunction(endpointsToPaint, listToRemoveFrom[i][1].endpoints[idx], function(e) { return e.id == listToRemoveFrom[i][1].endpoints[idx].id }); + } + } + } + + for (var i = 0; i < listToAddTo.length; i++) { + if (idx == 1 && listToAddTo[i][3] === otherElId && firstMatchingElIdx == -1) + firstMatchingElIdx = i; + _addWithFunction(connsToPaint, listToAddTo[i][1], function(c) { return c.id == listToAddTo[i][1].id }); + _addWithFunction(endpointsToPaint, listToAddTo[i][1].endpoints[idx], function(e) { return e.id == listToAddTo[i][1].endpoints[idx].id }); + } + if (exactIdx != -1) { + listToAddTo[exactIdx] = values; + } + else { + var insertIdx = reverse ? firstMatchingElIdx != -1 ? firstMatchingElIdx : 0 : listToAddTo.length; // of course we will get this from having looked through the array shortly. + listToAddTo.splice(insertIdx, 0, values); + } + + // store this for next time. + endpoint._continuousAnchorEdge = edgeId; + }; + this.redraw = function(elementId, ui, timestamp, offsetToUI) { + + if (!_suspendDrawing) { + // get all the endpoints for this element + var ep = _amEndpoints[elementId] || [], + endpointConnections = connectionsByElementId[elementId] || [], + connectionsToPaint = [], + endpointsToPaint = [], + anchorsToUpdate = []; + + timestamp = timestamp || _timestamp(); + // offsetToUI are values that would have been calculated in the dragManager when registering + // an endpoint for an element that had a parent (somewhere in the hierarchy) that had been + // registered as draggable. + offsetToUI = offsetToUI || {left:0, top:0}; + if (ui) { + ui = { + left:ui.left + offsetToUI.left, + top:ui.top + offsetToUI.top + } + } + + _updateOffset( { elId : elementId, offset : ui, recalc : false, timestamp : timestamp }); + // valid for one paint cycle. + var myOffset = offsets[elementId], + myWH = sizes[elementId], + orientationCache = {}; + + // actually, first we should compute the orientation of this element to all other elements to which + // this element is connected with a continuous anchor (whether both ends of the connection have + // a continuous anchor or just one) + //for (var i = 0; i < continuousAnchorConnections.length; i++) { + for (var i = 0; i < endpointConnections.length; i++) { + var conn = endpointConnections[i][0], + sourceId = conn.sourceId, + targetId = conn.targetId, + sourceContinuous = conn.endpoints[0].anchor.isContinuous, + targetContinuous = conn.endpoints[1].anchor.isContinuous; + + if (sourceContinuous || targetContinuous) { + var oKey = sourceId + "_" + targetId, + oKey2 = targetId + "_" + sourceId, + o = orientationCache[oKey], + oIdx = conn.sourceId == elementId ? 1 : 0; + + if (sourceContinuous && !anchorLists[sourceId]) anchorLists[sourceId] = { top:[], right:[], bottom:[], left:[] }; + if (targetContinuous && !anchorLists[targetId]) anchorLists[targetId] = { top:[], right:[], bottom:[], left:[] }; + + if (elementId != targetId) _updateOffset( { elId : targetId, timestamp : timestamp }); + if (elementId != sourceId) _updateOffset( { elId : sourceId, timestamp : timestamp }); + + var td = _getCachedData(targetId), + sd = _getCachedData(sourceId); + + if (targetId == sourceId && (sourceContinuous || targetContinuous)) { + // here we may want to improve this by somehow determining the face we'd like + // to put the connector on. ideally, when drawing, the face should be calculated + // by determining which face is closest to the point at which the mouse button + // was released. for now, we're putting it on the top face. + _updateAnchorList(anchorLists[sourceId], -Math.PI / 2, 0, conn, false, targetId, 0, false, "top", sourceId, connectionsToPaint, endpointsToPaint) + } + else { + if (!o) { + o = calculateOrientation(sourceId, targetId, sd.o, td.o); + orientationCache[oKey] = o; + // this would be a performance enhancement, but the computed angles need to be clamped to + //the (-PI/2 -> PI/2) range in order for the sorting to work properly. + /* orientationCache[oKey2] = { + orientation:o.orientation, + a:[o.a[1], o.a[0]], + theta:o.theta + Math.PI, + theta2:o.theta2 + Math.PI + };*/ + } + if (sourceContinuous) _updateAnchorList(anchorLists[sourceId], o.theta, 0, conn, false, targetId, 0, false, o.a[0], sourceId, connectionsToPaint, endpointsToPaint); + if (targetContinuous) _updateAnchorList(anchorLists[targetId], o.theta2, -1, conn, true, sourceId, 1, true, o.a[1], targetId, connectionsToPaint, endpointsToPaint); + } + + if (sourceContinuous) _addWithFunction(anchorsToUpdate, sourceId, function(a) { return a === sourceId; }); + if (targetContinuous) _addWithFunction(anchorsToUpdate, targetId, function(a) { return a === targetId; }); + _addWithFunction(connectionsToPaint, conn, function(c) { return c.id == conn.id; }); + if ((sourceContinuous && oIdx == 0) || (targetContinuous && oIdx == 1)) + _addWithFunction(endpointsToPaint, conn.endpoints[oIdx], function(e) { return e.id == conn.endpoints[oIdx].id; }); + } + } + + // place Endpoints whose anchors are continuous but have no Connections + for (var i = 0; i < ep.length; i++) { + if (ep[i].connections.length == 0 && ep[i].anchor.isContinuous) { + if (!anchorLists[elementId]) anchorLists[elementId] = { top:[], right:[], bottom:[], left:[] }; + _updateAnchorList(anchorLists[elementId], -Math.PI / 2, 0, {endpoints:[ep[i], ep[i]], paint:function(){}}, false, elementId, 0, false, "top", elementId, connectionsToPaint, endpointsToPaint) + _addWithFunction(anchorsToUpdate, elementId, function(a) { return a === elementId; }) + } + } + + // now place all the continuous anchors we need to; + for (var i = 0; i < anchorsToUpdate.length; i++) { + placeAnchors(anchorsToUpdate[i], anchorLists[anchorsToUpdate[i]]); + } + + // now that continuous anchors have been placed, paint all the endpoints for this element + // TODO performance: add the endpoint ids to a temp array, and then when iterating in the next + // loop, check that we didn't just paint that endpoint. we can probably shave off a few more milliseconds this way. + for (var i = 0; i < ep.length; i++) { + ep[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + // ... and any other endpoints we came across as a result of the continuous anchors. + for (var i = 0; i < endpointsToPaint.length; i++) { + endpointsToPaint[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + + // paint all the standard and "dynamic connections", which are connections whose other anchor is + // static and therefore does need to be recomputed; we make sure that happens only one time. + + // TODO we could have compiled a list of these in the first pass through connections; might save some time. + for (var i = 0; i < endpointConnections.length; i++) { + var otherEndpoint = endpointConnections[i][1]; + if (otherEndpoint.anchor.constructor == DynamicAnchor) { + otherEndpoint.paint({ elementWithPrecedence:elementId }); + _addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; }); + // all the connections for the other endpoint now need to be repainted + for (var k = 0; k < otherEndpoint.connections.length; k++) { + if (otherEndpoint.connections[k] !== endpointConnections[i][0]) + _addWithFunction(connectionsToPaint, otherEndpoint.connections[k], function(c) { return c.id == otherEndpoint.connections[k].id; }); + } + } else if (otherEndpoint.anchor.constructor == Anchor) { + _addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; }); + } + } + // paint current floating connection for this element, if there is one. + var fc = floatingConnections[elementId]; + if (fc) + fc.paint({timestamp:timestamp, recalc:false, elId:elementId}); + + // paint all the connections + for (var i = 0; i < connectionsToPaint.length; i++) { + connectionsToPaint[i].paint({elId:elementId, timestamp:timestamp, recalc:false}); + } + } + }; + this.rehomeEndpoint = function(currentId, element) { + var eps = _amEndpoints[currentId] || [], //, + elementId = _currentInstance.getId(element); + for (var i = 0; i < eps.length; i++) { + self.add(eps[i], elementId); + } + eps.splice(0, eps.length); + }; + }; + _currentInstance.anchorManager = new AnchorManager(); + _currentInstance.continuousAnchorFactory = { + get:function(params) { + var existing = continuousAnchors[params.elementId]; + if (!existing) { + existing = { + type:"Continuous", + compute : function(params) { + return continuousAnchorLocations[params.element.id] || [0,0]; + }, + getCurrentLocation : function(endpoint) { + return continuousAnchorLocations[endpoint.id] || [0,0]; + }, + getOrientation : function(endpoint) { + return continuousAnchorOrientations[endpoint.id] || [0,0]; + }, + isDynamic : true, + isContinuous : true + }; + continuousAnchors[params.elementId] = existing; + } + return existing; + } + }; + + if (!jsPlumbAdapter.headless) + _currentInstance.dragManager = jsPlumbAdapter.getDragManager(_currentInstance); + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. You should not ever create one of these directly. If you make a call to jsPlumb.connect, all of + * the parameters that you pass in to that function will be passed to the Connection constructor; if your UI + * uses the various Endpoint-centric methods like addEndpoint/makeSource/makeTarget, along with drag and drop, + * then the parameters you set on those functions are translated and passed in to the Connection constructor. So + * you should check the documentation for each of those methods. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * container - optional id or selector instructing jsPlumb where to attach all the elements it creates for this connection. you should read the documentation for a full discussion of this. + * detachable - optional, defaults to true. Defines whether or not the connection may be detached using the mouse. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * cssClass - optional CSS class to set on the display element associated with this Connection. + * hoverClass - optional CSS class to set on the display element associated with this Connection when it is in hover state. + * overlays - Optional array of Overlay definitions to appear on this Connection. + * drawEndpoints - if false, instructs jsPlumb to not draw the endpoints for this Connection. Be careful with this: it only really works when you tell jsPlumb to attach elements to the document body. Read the documentation for a full discussion of this. + * parameters - Optional JS object containing parameters to set on the Connection. These parameters are then available via the getParameter method. + */ + var Connection = function(params) { + var self = this, visible = true, _internalHover, _superClassHover; + self.idPrefix = "_jsplumb_c_"; + self.defaultLabelLocation = 0.5; + self.defaultOverlayKeys = ["Overlays", "ConnectionOverlays"]; + this.parent = params.parent; + overlayCapableJsPlumbUIComponent.apply(this, arguments); + // ************** get the source and target and register the connection. ******************* + +// VISIBILITY + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + self[v ? "showOverlays" : "hideOverlays"](); + if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none"; + self.repaint(); + }; +// END VISIBILITY + +// TYPE + + this.getTypeDescriptor = function() { return "connection"; }; + this.getDefaultType = function() { + return { + parameters:{}, + scope:null, + detachable:self._jsPlumb.Defaults.ConnectionsDetachable, + paintStyle:self._jsPlumb.Defaults.PaintStyle || jsPlumb.Defaults.PaintStyle, + connector:self._jsPlumb.Defaults.Connector || jsPlumb.Defaults.Connector, + hoverPaintStyle:self._jsPlumb.Defaults.HoverPaintStyle || jsPlumb.Defaults.HoverPaintStyle, + overlays:self._jsPlumb.Defaults.ConnectorOverlays || jsPlumb.Defaults.ConnectorOverlays + }; + }; + var superAt = this.applyType; + this.applyType = function(t) { + superAt(t); + if (t.detachable != null) self.setDetachable(t.detachable); + if (t.scope) self.scope = t.scope; + self.setConnector(t.connector); + }; +// END TYPE + +// HOVER + // override setHover to pass it down to the underlying connector + _superClassHover = self.setHover; + self.setHover = function(state) { + var zi = _currentInstance.ConnectorZIndex || jsPlumb.Defaults.ConnectorZIndex; + if (zi) + self.connector.setZIndex(zi + (state ? 1 : 0)); + self.connector.setHover.apply(self.connector, arguments); + _superClassHover.apply(self, arguments); + }; + + _internalHover = function(state) { + if (_connectionBeingDragged == null) { + self.setHover(state, false); + } + }; +// END HOVER + + /* + * Function: setConnector + * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method - the same + * thing that you would set as the 'connector' property on a jsPlumb.connect call. + * + * Parameters: + * connector - Connector definition + */ + this.setConnector = function(connector, doNotRepaint) { + if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent); + var connectorArgs = { _jsPlumb:self._jsPlumb, parent:params.parent, + cssClass:params.cssClass, container:params.container, tooltip:self.tooltip + }; + if (_isString(connector)) + this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand. + else if (_isArray(connector)) { + if (connector.length == 1) + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](connectorArgs); + else + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs)); + } + self.canvas = self.connector.canvas; + // binds mouse listeners to the current connector. + _bindListeners(self.connector, self, _internalHover); + + // set z-index if it was set on Defaults. + var zi = _currentInstance.ConnectorZIndex || jsPlumb.Defaults.ConnectorZIndex; + if (zi) + self.connector.setZIndex(zi); + + if (!doNotRepaint) self.repaint(); + }; + +// INITIALISATION CODE + + this.source = _getElementObject(params.source); + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. but + // source is not overridden if the Endpoint has declared it is not the final target of a connection; + // instead we use the source that the Endpoint declares will be the final source element. + if (params.sourceEndpoint) this.source = params.sourceEndpoint.endpointWillMoveTo || params.sourceEndpoint.getElement(); + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + + // if a new connection is the result of moving some existing connection, params.previousConnection + // will have that Connection in it. listeners for the jsPlumbConnection event can look for that + // member and take action if they need to. + self.previousConnection = params.previousConnection; + + this.sourceId = _getAttribute(this.source, "id"); + this.targetId = _getAttribute(this.target, "id"); + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams, elementId) { + return (anchorParams) ? _currentInstance.makeAnchor(anchorParams, elementId, _currentInstance) : null; + }, + prepareEndpoint = function(existing, index, params, element, elementId, connectorPaintStyle, connectorHoverPaintStyle) { + var e; + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] + || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint; + + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified. + if (es.fillStyle == null && connectorPaintStyle != null) + es.fillStyle = connectorPaintStyle.strokeStyle; + + // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does: + //* + if (es.outlineColor == null && connectorPaintStyle != null) + es.outlineColor = connectorPaintStyle.outlineColor; + if (es.outlineWidth == null && connectorPaintStyle != null) + es.outlineWidth = connectorPaintStyle.outlineWidth; + //*/ + + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure? + if (connectorHoverPaintStyle != null) { + if (ehs == null) ehs = {}; + if (ehs.fillStyle == null) { + ehs.fillStyle = connectorHoverPaintStyle.strokeStyle; + } + } + var a = params.anchors ? params.anchors[index] : + params.anchor ? params.anchor : + _makeAnchor(_currentInstance.Defaults.Anchors[index], elementId) || + _makeAnchor(jsPlumb.Defaults.Anchors[index], elementId) || + _makeAnchor(_currentInstance.Defaults.Anchor, elementId) || + _makeAnchor(jsPlumb.Defaults.Anchor, elementId), + u = params.uuids ? params.uuids[index] : null; + e = _newEndpoint({ + paintStyle : es, hoverPaintStyle:ehs, endpoint : ep, connections : [ self ], + uuid : u, anchor : a, source : element, scope : params.scope, container:params.container, + reattach:params.reattach, detachable:params.detachable + }); + self.endpoints[index] = e; + + if (params.drawEndpoints === false) e.setVisible(false, true, true); + + } + return e; + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source, + self.sourceId, params.paintStyle, params.hoverPaintStyle); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target, + self.targetId, params.paintStyle, params.hoverPaintStyle); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + // if delete endpoints on detach, keep a record of just exactly which endpoints they are. + if (params.deleteEndpointsOnDetach) + self.endpointsToDeleteOnDetach = [eS, eT]; + + self.setConnector(this.endpoints[0].connector || + this.endpoints[1].connector || + params.connector || + _currentInstance.Defaults.Connector || + jsPlumb.Defaults.Connector, true); + + this.setPaintStyle(this.endpoints[0].connectorStyle || + this.endpoints[1].connectorStyle || + params.paintStyle || + _currentInstance.Defaults.PaintStyle || + jsPlumb.Defaults.PaintStyle, true); + + this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || + this.endpoints[1].connectorHoverStyle || + params.hoverPaintStyle || + _currentInstance.Defaults.HoverPaintStyle || + jsPlumb.Defaults.HoverPaintStyle, true); + + this.paintStyleInUse = this.getPaintStyle(); + + _updateOffset( { elId : this.sourceId, timestamp:_suspendedAt }); + _updateOffset( { elId : this.targetId, timestamp:_suspendedAt }); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId], + otherOffset = offsets[this.targetId], + otherWH = sizes[this.targetId], + initialTimestamp = _suspendedAt || _timestamp(), + anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + elementId:this.endpoints[0].elementId, + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1], + timestamp:initialTimestamp + }); + + this.endpoints[0].paint( { anchorLoc : anchorLoc, timestamp:initialTimestamp }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + elementId:this.endpoints[1].elementId, + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0], + timestamp:initialTimestamp + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc, timestamp:initialTimestamp }); + +// END INITIALISATION CODE + +// DETACHABLE + var _detachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.detachable === false) _detachable = false; + if(self.endpoints[0].connectionsDetachable === false) _detachable = false; + if(self.endpoints[1].connectionsDetachable === false) _detachable = false; + /* + Function: isDetachable + Returns whether or not this connection can be detached from its target/source endpoint. by default this + is false; use it in conjunction with the 'reattach' parameter. + */ + this.isDetachable = function() { + return _detachable === true; + }; + /* + Function: setDetachable + Sets whether or not this connection is detachable. + */ + this.setDetachable = function(detachable) { + _detachable = detachable === true; + }; +// END DETACHABLE + +// COST + DIRECTIONALITY + // if cost not supplied, try to inherit from source endpoint + var _cost = params.cost || self.endpoints[0].getConnectionCost(); + self.getCost = function() { return _cost; }; + self.setCost = function(c) { _cost = c; }; + var _bidirectional = !(params.bidirectional === false); + // inherit bidirectional flag if set no source endpoint + if (params.bidirectional == null) _bidirectional = self.endpoints[0].areConnectionsBidirectional(); + self.isBidirectional = function() { return _bidirectional; }; +// END COST + DIRECTIONALITY + +// PARAMETERS + // merge all the parameters objects into the connection. parameters set + // on the connection take precedence; then target endpoint params, then + // finally source endpoint params. + // TODO jsPlumb.extend could be made to take more than two args, and it would + // apply the second through nth args in order. + var _p = jsPlumb.extend({}, this.endpoints[0].getParameters()); + jsPlumb.extend(_p, this.endpoints[1].getParameters()); + jsPlumb.extend(_p, self.getParameters()); + self.setParameters(_p); +// END PARAMETERS + +// MISCELLANEOUS + /** + * implementation of abstract method in jsPlumbUtil.EventGenerator + * @return list of attached elements. in our case, a list of Endpoints. + */ + this.getAttachedElements = function() { + return self.endpoints; + }; + + /** + * changes the parent element of this connection to newParent. not exposed for the public API. + */ + this.moveParent = function(newParent) { + var jpcl = jsPlumb.CurrentLibrary, curParent = jpcl.getParent(self.connector.canvas); + if (self.connector.bgCanvas) { + jpcl.removeElement(self.connector.bgCanvas, curParent); + jpcl.appendElement(self.connector.bgCanvas, newParent); + } + jpcl.removeElement(self.connector.canvas, curParent); + jpcl.appendElement(self.connector.canvas, newParent); + // this only applies for DOMOverlays + for (var i = 0; i < self.overlays.length; i++) { + if (self.overlays[i].isAppendedAtTopLevel) { + jpcl.removeElement(self.overlays[i].canvas, curParent); + jpcl.appendElement(self.overlays[i].canvas, newParent); + if (self.overlays[i].reattachListeners) + self.overlays[i].reattachListeners(self.connector); + } + } + if (self.connector.reattachListeners) // this is for SVG/VML; change an element's parent and you have to reinit its listeners. + self.connector.reattachListeners(); // the Canvas implementation doesn't have to care about this + }; + +// END MISCELLANEOUS + +// PAINTING + + /* + * Paints the Connection. Not exposed for public usage. + * + * Parameters: + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + var lastPaintedAt = null; + this.paint = function(params) { + + if (visible) { + + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp, + // if the moving object is not the source we must transpose the two references. + swap = false, + tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId, + tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + + if (timestamp == null || timestamp != lastPaintedAt) { + var sourceInfo = _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }), + targetInfo = _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var sE = this.endpoints[sIdx], tE = this.endpoints[tIdx], + sAnchorP = sE.anchor.getCurrentLocation(sE), + tAnchorP = tE.anchor.getCurrentLocation(tE); + + /* paint overlays*/ + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible()) maxSize = Math.max(maxSize, o.computeMaxSize()); + } + + var dim = this.connector.compute( + sAnchorP, + tAnchorP, + this.endpoints[sIdx], + this.endpoints[tIdx], + this.endpoints[sIdx].anchor, + this.endpoints[tIdx].anchor, + self.paintStyleInUse.lineWidth, + maxSize, + sourceInfo, + targetInfo ); + + self.connector.paint(dim, self.paintStyleInUse); + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim, timestamp); + } + } + lastPaintedAt = timestamp; + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function(params) { + params = params || {}; + var recalc = !(params.recalc === false); + this.paint({ elId : this.sourceId, recalc : recalc, timestamp:params.timestamp }); + }; + + // the very last thing we do is check to see if a 'type' was supplied in the params + var _type = params.type || self.endpoints[0].connectionType || self.endpoints[1].connectionType; + if (_type) + self.addType(_type); + +// END PAINTING + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Connection. + * + * Parameters: + * event - the event to bind. Available events on a Connection are: + * - *click* : notification that a Connection was clicked. + * - *dblclick* : notification that a Connection was double clicked. + * - *mouseenter* : notification that the mouse is over a Connection. + * - *mouseexit* : notification that the mouse exited a Connection. + * - *contextmenu* : notification that the user right-clicked on the Connection. + * + * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Connection's paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: getPaintStyle + * Gets the Connection's paint style. This is not necessarily the paint style in use at the time; + * this is the paint style for the connection when the mouse it not hovering over it. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Connection will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Connection. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + + /* + * Function: getParameter + * Gets the named parameter; returns null if no parameter with that name is set. Parameter values may have been supplied to a 'connect' or 'addEndpoint' call (connections made with the mouse get a copy of all parameters set on each of their Endpoints), or the parameter value may have been set with setParameter. + * + * Parameters: + * key - Parameter name. + */ + + /* + * Function: setParameter + * Sets the named parameter to the given value. + * + * Parameters: + * key - Parameter name. + * value - Parameter value. + */ + + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc) + */ + + /* + * Property: sourceId + * Id of the source element in the connection. + */ + /* + * Property: targetId + * Id of the target element in the connection. + */ + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + /* + * Property: source + * The source element for this Connection. + */ + /* + * Property: target + * The target element for this Connection. + */ + /* + * Property: overlays + * List of Overlays for this component. + */ + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + /* + * Function: getOverlay + * Gets an overlay, by ID. Note: by ID. You would pass an 'id' parameter + * in to the Overlay's constructor arguments, and then use that to retrieve + * it via this method. + */ + /* + * Function: getOverlays + * Gets all the overlays for this component. + */ + /* + * Function: hideOverlay + * Hides the overlay specified by the given id. + */ + /* + * Function: hideOverlays + * Hides all Overlays + */ + /* + * Function: showOverlay + * Shows the overlay specified by the given id. + */ + /* + * Function: showOverlays + * Shows all Overlays + */ + /** + * Function: removeAllOverlays + * Removes all overlays from the Connection, and then repaints. + */ + /** + * Function: removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + /** + * Function: removeOverlays + * Removes a set of overlays by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String, a Function that returns a String, or a params object containing { "label", "labelStyle", "location", "cssClass" }. Note that this uses innerHTML on the label div, so keep + * that in mind if you need escaped HTML. + */ + /* + Function: getLabel + Returns the label text for this Connection (or a function if you are labelling with a function). + This does not return the overlay itself; this is a convenience method which is a pair with + setLabel; together they allow you to add and access a Label Overlay without having to create the + Overlay object itself. For access to the underlying label overlay that jsPlumb has created, + use getLabelOverlay. + */ + /* + Function: getLabelOverlay + Returns the underlying internal label overlay, which will exist if you specified a label on + a connect call, or have called setLabel at any stage. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + + }; // END Connection class + +// ENDPOINT HELPER FUNCTIONS + var _makeConnectionDragHandler = function(placeholder) { + var stopped = false; + return { + drag : function() { + if (stopped) { + stopped = false; + return true; + } + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments, _currentInstance.getZoom()), + el = placeholder.element; + + if (el) { + jsPlumb.CurrentLibrary.setOffset(el, _ui); + _draw(_getElementObject(el), _ui); + } + }, + stopDrag : function() { + stopped = true; + } + }; + }; + + var _makeFloatingEndpoint = function(paintStyle, referenceAnchor, endpoint, referenceCanvas, sourceElement) { + var floatingAnchor = new FloatingAnchor( { reference : referenceAnchor, referenceCanvas : referenceCanvas }); + //setting the scope here should not be the way to fix that mootools issue. it should be fixed by not + // adding the floating endpoint as a droppable. that makes more sense anyway! + return _newEndpoint({ paintStyle : paintStyle, endpoint : endpoint, anchor : floatingAnchor, source : sourceElement, scope:"__floating" }); + }; + + /** + * creates a placeholder div for dragging purposes, adds it to the DOM, and pre-computes its offset. + */ + var _makeDraggablePlaceholder = function(placeholder, parent) { + var n = document.createElement("div"); + n.style.position = "absolute"; + var placeholderDragElement = _getElementObject(n); + _appendElement(n, parent); + var id = _getId(placeholderDragElement); + _updateOffset( { elId : id }); + // create and assign an id, and initialize the offset. + placeholder.id = id; + placeholder.element = placeholderDragElement; + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1 + * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't + * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you + * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or + * 'target' Endpoints for Connections. + * + * + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * enabled - optional, defaults to true. Indicates whether or not the Endpoint should be enabled for mouse events (drag/drop). + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * cssClass - optional CSS class to set on the display element associated with this Endpoint. + * hoverClass - optional CSS class to set on the display element associated with this Endpoint when it is in hover state. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * container - optional id or selector instructing jsPlumb where to attach the element it creates for this endpoint. you should read the documentation for a full discussion of this. + * connections - optional list of Connections to configure the Endpoint with. + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * connectorClass - optional CSS class to set on Connections emanating from this Endpoint. + * connectorHoverClass - optional CSS class to set on to set on Connections emanating from this Endpoint when they are in hover state. + * connectionsDetachable - optional, defaults to true. Sets whether connections to/from this Endpoint should be detachable or not. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + * parameters - Optional JS object containing parameters to set on the Endpoint. These parameters are then available via the getParameter method. When a connection is made involving this Endpoint, the parameters from this Endpoint are copied into that Connection. Source Endpoint parameters override target Endpoint parameters if they both have a parameter with the same name. + */ + var Endpoint = function(params) { + var self = this; + self.idPrefix = "_jsplumb_e_"; + self.defaultLabelLocation = [ 0.5, 0.5 ]; + self.defaultOverlayKeys = ["Overlays", "EndpointOverlays"]; + this.parent = params.parent; + overlayCapableJsPlumbUIComponent.apply(this, arguments); + params = params || {}; + +// TYPE + + this.getTypeDescriptor = function() { return "endpoint"; }; + this.getDefaultType = function() { + return { + parameters:{}, + scope:null, + maxConnections:self._jsPlumb.Defaults.MaxConnections, + paintStyle:self._jsPlumb.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle, + endpoint:self._jsPlumb.Defaults.Endpoint || jsPlumb.Defaults.Endpoint, + hoverPaintStyle:self._jsPlumb.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle, + overlays:self._jsPlumb.Defaults.EndpointOverlays || jsPlumb.Defaults.EndpointOverlays, + connectorStyle:params.connectorStyle, + connectorHoverStyle:params.connectorHoverStyle, + connectorClass:params.connectorClass, + connectorHoverClass:params.connectorHoverClass, + connectorOverlays:params.connectorOverlays, + connector:params.connector, + connectorTooltip:params.connectorTooltip + }; + }; + var superAt = this.applyType; + this.applyType = function(t) { + superAt(t); + if (t.maxConnections != null) _maxConnections = t.maxConnections; + if (t.scope) self.scope = t.scope; + self.connectorStyle = t.connectorStyle; + self.connectorHoverStyle = t.connectorHoverStyle; + self.connectorOverlays = t.connectorOverlays; + self.connector = t.connector; + self.connectorTooltip = t.connectorTooltip; + self.connectionType = t.connectionType; + self.connectorClass = t.connectorClass; + self.connectorHoverClass = t.connectorHoverClass; + }; +// END TYPE + + var visible = true, __enabled = !(params.enabled === false); + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + self[v ? "showOverlays" : "hideOverlays"](); + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + + /* + Function: isEnabled + Returns whether or not the Endpoint is enabled for drag/drop connections. + */ + this.isEnabled = function() { return __enabled; }; + + /* + Function: setEnabled + Sets whether or not the Endpoint is enabled for drag/drop connections. + */ + this.setEnabled = function(e) { __enabled = e; }; + + var _element = params.source, _uuid = params.uuid, floatingEndpoint = null, inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + this.element = _element; + + var _connectionCost = params.connectionCost; + this.getConnectionCost = function() { return _connectionCost; }; + this.setConnectionCost = function(c) { + _connectionCost = c; + }; + + var _connectionsBidirectional = params.connectionsBidirectional === false ? false : true; + this.areConnectionsBidirectional = function() { return _connectionsBidirectional; }; + this.setConnectionsBidirectional = function(b) { _connectionsBidirectional = b; }; + + self.anchor = params.anchor ? _currentInstance.makeAnchor(params.anchor, _elementId, _currentInstance) : params.anchors ? _currentInstance.makeAnchor(params.anchors, _elementId, _currentInstance) : _currentInstance.makeAnchor(_currentInstance.Defaults.Anchor || "TopCenter", _elementId, _currentInstance); + + // ANCHOR MANAGER + if (!params._transient) // in place copies, for example, are transient. they will never need to be retrieved during a paint cycle, because they dont move, and then they are deleted. + _currentInstance.anchorManager.add(self, _elementId); + + var _endpoint = null, originalEndpoint = null; + this.setEndpoint = function(ep) { + var endpointArgs = { + _jsPlumb:self._jsPlumb, + parent:params.parent, + container:params.container, + tooltip:params.tooltip, + connectorTooltip:params.connectorTooltip, + endpoint:self + }; + if (_isString(ep)) + _endpoint = new jsPlumb.Endpoints[renderMode][ep](endpointArgs); + else if (_isArray(ep)) { + endpointArgs = jsPlumb.extend(ep[1], endpointArgs); + _endpoint = new jsPlumb.Endpoints[renderMode][ep[0]](endpointArgs); + } + else { + _endpoint = ep.clone(); + } + + // assign a clone function using a copy of endpointArgs. this is used when a drag starts: the endpoint that was dragged is cloned, + // and the clone is left in its place while the original one goes off on a magical journey. + // the copy is to get around a closure problem, in which endpointArgs ends up getting shared by + // the whole world. + var argsForClone = jsPlumb.extend({}, endpointArgs); + _endpoint.clone = function() { + var o = new Object(); + _endpoint.constructor.apply(o, [argsForClone]); + return o; + }; + + self.endpoint = _endpoint; + self.type = self.endpoint.type; + }; + + this.setEndpoint(params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot"); + originalEndpoint = _endpoint; + + // override setHover to pass it down to the underlying endpoint + var _sh = self.setHover; + self.setHover = function() { + self.endpoint.setHover.apply(self.endpoint, arguments); + _sh.apply(self, arguments); + }; + // endpoint delegates to first connection for hover, if there is one. + var internalHover = function(state) { + if (self.connections.length > 0) + self.connections[0].setHover(state, false); + else + self.setHover(state); + }; + + // bind listeners from endpoint to self, with the internal hover function defined above. + _bindListeners(self.endpoint, self, internalHover); + + this.setPaintStyle(params.paintStyle || + params.style || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyle, true); + this.setHoverPaintStyle(params.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyle, true); + this.paintStyleInUse = this.getPaintStyle(); + var originalPaintStyle = this.getPaintStyle(); + this.connectorStyle = params.connectorStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.connectorTooltip = params.connectorTooltip; + this.connectorClass = params.connectorClass; + this.connectorHoverClass = params.connectorHoverClass; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + + var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + this.canvas = this.endpoint.canvas; + this.connections = params.connections || []; + + this.scope = params.scope || DEFAULT_SCOPE; + this.connectionType = params.connectionType; + this.timestamp = null; + self.isReattach = params.reattach || false; + self.connectionsDetachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.connectionsDetachable === false || params.detachable === false) + self.connectionsDetachable = false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + if (params.onMaxConnections) + self.bind("maxConnections", params.onMaxConnections); + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget, forceDetach, fireEvent, originalEvent) { + var idx = _findWithFunction(self.connections, function(c) { return c.id == connection.id}), + actuallyDetached = false; + fireEvent = (fireEvent !== false); + if (idx >= 0) { + // 1. does the connection have a before detach (note this also checks jsPlumb's bound + // detach handlers; but then Endpoint's check will, too, hmm.) + if (forceDetach || connection._forceDetach || connection.isDetachable() || connection.isDetachAllowed(connection)) { + // get the target endpoint + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + // it would be nice to check with both endpoints that it is ok to detach. but + // for this we'll have to get a bit fancier: right now if you use the same beforeDetach + // interceptor for two endpoints (which is kind of common, because it's part of the + // endpoint definition), then it gets fired twice. so in fact we need to loop through + // each beforeDetach and see if it returns false, at which point we exit. but if it + // returns true, we have to check the next one. however we need to track which ones + // have already been run, and not run them again. + if (forceDetach || connection._forceDetach || (self.isDetachAllowed(connection) /*&& t.isDetachAllowed(connection)*/)) { + + self.connections.splice(idx, 1); + + // this avoids a circular loop + if (!ignoreTarget) { + + t.detach(connection, true, forceDetach); + // check connection to see if we want to delete the endpoints associated with it. + // we only detach those that have just this connection; this scenario is most + // likely if we got to this bit of code because it is set by the methods that + // create their own endpoints, like .connect or .makeTarget. the user is + // not likely to have interacted with those endpoints. + if (connection.endpointsToDeleteOnDetach){ + for (var i = 0; i < connection.endpointsToDeleteOnDetach.length; i++) { + var cde = connection.endpointsToDeleteOnDetach[i]; + if (cde && cde.connections.length == 0) + _currentInstance.deleteEndpoint(cde); + } + } + } + _removeElements(connection.connector.getDisplayElements(), connection.parent); + _removeWithFunction(connectionsByScope[connection.scope], function(c) { + return c.id == connection.id; + }); + actuallyDetached = true; + var doFireEvent = (!ignoreTarget && fireEvent) + fireDetachEvent(connection, doFireEvent, originalEvent); + } + } + } + return actuallyDetached; + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + * + * Parameters: + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachAll = function(fireEvent, originalEvent) { + while (self.connections.length > 0) { + self.detach(self.connections[0], false, true, fireEvent, originalEvent); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachFrom = function(targetEndpoint, fireEvent, originalEvent) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + if (self.detach(c[i], false, true, fireEvent, originalEvent)) + c[i].setHover(false, false); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findWithFunction(self.connections, function(c) { return c.id == connection.id}); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: setElement + * Sets the DOM element this Endpoint is attached to. + */ + this.setElement = function(el, container) { + + // TODO possibly have this object take charge of moving the UI components into the appropriate + // parent. this is used only by makeSource right now, and that function takes care of + // moving the UI bits and pieces. however it would s + var parentId = _getId(el); + // remove the endpoint from the list for the current endpoint's element + _removeWithFunction(endpointsByElement[self.elementId], function(e) { + return e.id == self.id; + }); + _element = _getElementObject(el); + _elementId = _getId(_element); + self.elementId = _elementId; + // need to get the new parent now + var newParentElement = _getParentFromParams({source:parentId, container:container}), + curParent = jpcl.getParent(self.canvas); + jpcl.removeElement(self.canvas, curParent); + jpcl.appendElement(self.canvas, newParentElement); + + // now move connection(s)...i would expect there to be only one but we will iterate. + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].moveParent(newParentElement); + self.connections[i].sourceId = _elementId; + self.connections[i].source = _element; + } + _addToList(endpointsByElement, parentId, self); + //_currentInstance.repaint(parentId); + + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + var loc = self.anchor.getCurrentLocation(self), + o = self.anchor.getOrientation(self), + inPlaceAnchor = { + compute:function() { return [ loc[0], loc[1] ]}, + getCurrentLocation : function() { return [ loc[0], loc[1] ]}, + getOrientation:function() { return o; } + }; + + return _newEndpoint( { + anchor : inPlaceAnchor, + source : _element, + paintStyle : this.getPaintStyle(), + endpoint : _endpoint, + _transient:true, + scope:self.scope + }); + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + var candidate = self.connections[0]; + if (self.isTarget && candidate) return candidate; + else { + return (self.connections.length < _maxConnections) || _maxConnections == -1 ? null : candidate; + } + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + /* + * Function: setStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to + * setStyle and deprecate it if so. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + * + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + // a helper function that tries to find a connection to the given element, and returns it if so. if elementWithPrecedence is null, + // or no connection to it is found, we return the first connection in our list. + var findConnectionToUseForDynamicAnchor = function(elementWithPrecedence) { + var idx = 0; + if (elementWithPrecedence != null) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].sourceId == elementWithPrecedence || self.connections[i].targetId == elementWithPrecedence) { + idx = i; + break; + } + } + } + + return self.connections[idx]; + }; + + /* + * Function: paint + * Paints the Endpoint, recalculating offset and anchor positions if necessary. This does NOT paint + * any of the Endpoint's connections. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + params = params || {}; + var timestamp = params.timestamp, recalc = !(params.recalc === false); + //recalc = params.recalc; + if (!timestamp || self.timestamp !== timestamp) { + _updateOffset({ elId:_elementId, timestamp:timestamp, recalc:recalc }); + var xy = params.offset || offsets[_elementId]; + if(xy) { + var ap = params.anchorPoint,connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (recalc && self.anchor.isDynamic && self.connections.length > 0) { + var c = findConnectionToUseForDynamicAnchor(params.elementWithPrecedence), + oIdx = c.endpoints[0] == self ? 1 : 0, + oId = oIdx == 0 ? c.sourceId : c.targetId, + oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + ap = self.anchor.compute(anchorParams); + } + + // switched _endpoint to self here; continuous anchors, for instance, look for the + // endpoint's id, but here "_endpoint" is the renderer, not the actual endpoint. + // TODO need to verify this does not break anything. + //var d = _endpoint.compute(ap, self.anchor.getOrientation(_endpoint), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + var d = _endpoint.compute(ap, self.anchor.getOrientation(self), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + _endpoint.paint(d, self.paintStyleInUse, self.anchor); + self.timestamp = timestamp; + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) self.overlayPlacements[i] = o.draw(self.endpoint, self.paintStyleInUse, d); + } + } + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var placeholderInfo = { id:null, element:null }, + jpc = null, + existingJpc = false, + existingJpcParams = null, + _dragHandler = _makeConnectionDragHandler(placeholderInfo); + + var start = function() { + // drag might have started on an endpoint that is not actually a source, but which has + // one or more connections. + jpc = self.connectorSelector(); + var _continue = true; + // if not enabled, return + if (!self.isEnabled()) _continue = false; + // if no connection and we're not a source, return. + if (jpc == null && !params.isSource) _continue = false; + // otherwise if we're full and not allowed to drag, also return false. + if (params.isSource && self.isFull() && !dragAllowedWhenFull) _continue = false; + // if the connection was setup as not detachable or one of its endpoints + // was setup as connectionsDetachable = false, or Defaults.ConnectionsDetachable + // is set to false... + if (jpc != null && !jpc.isDetachable()) _continue = false; + + if (_continue === false) { + // this is for mootools and yui. returning false from this causes jquery to stop drag. + // the events are wrapped in both mootools and yui anyway, but i don't think returning + // false from the start callback would stop a drag. + if (jsPlumb.CurrentLibrary.stopDrag) jsPlumb.CurrentLibrary.stopDrag(); + _dragHandler.stopDrag(); + return false; + } + + // if we're not full but there was a connection, make it null. we'll create a new one. + if (jpc && !self.isFull() && params.isSource) jpc = null; + + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + _makeDraggablePlaceholder(placeholderInfo, self.parent); + + // set the offset of this div to be where 'inPlaceCopy' is, to start with. + // TODO merge this code with the code in both Anchor and FloatingAnchor, because it + // does the same stuff. + var ipcoel = _getElementObject(inPlaceCopy.canvas), + ipco = _getOffset(ipcoel, _currentInstance), + po = adjustForParentOffsetAndScroll([ipco.left, ipco.top], inPlaceCopy.canvas); + jsPlumb.CurrentLibrary.setOffset(placeholderInfo.element, {left:po[0], top:po[1]}); + + // when using makeSource and a parent, we first draw the source anchor on the source element, then + // move it to the parent. note that this happens after drawing the placeholder for the + // first time. + if (self.parentAnchor) self.anchor = _currentInstance.makeAnchor(self.parentAnchor, self.elementId, _currentInstance); + + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", placeholderInfo.id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + + // create a floating endpoint. + // here we test to see if a dragProxy was specified in this endpoint's constructor params, and + // if so, we create that endpoint instead of cloning ourselves. + if (params.proxy) { + /* var floatingAnchor = new FloatingAnchor( { reference : self.anchor, referenceCanvas : self.canvas }); + + floatingEndpoint = _newEndpoint({ + paintStyle : params.proxy.paintStyle, + endpoint : params.proxy.endpoint, + anchor : floatingAnchor, + source : placeholderInfo.element, + scope:"__floating" + }); + + //$(self.canvas).hide(); + */ + self.setPaintStyle(params.proxy.paintStyle); + // if we do this, we have to cleanup the old one. like just remove its display parts + //self.setEndpoint(params.proxy.endpoint); + + } + // else { + floatingEndpoint = _makeFloatingEndpoint(self.getPaintStyle(), self.anchor, _endpoint, self.canvas, placeholderInfo.element); + // } + + if (jpc == null) { + self.anchor.locked = true; + self.setHover(false, false); + // TODO the hover call above does not reset any target endpoint's hover + // states. + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : self.endpointWillMoveTo || _getElementObject(_element), // for makeSource with parent option. ensure source element is represented correctly. + target : placeholderInfo.element, + anchors : [ self.anchor, floatingEndpoint.anchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays, + type:self.connectionType, + cssClass:self.connectorClass, + hoverClass:self.connectorHoverClass + }); + + } else { + existingJpc = true; + jpc.connector.setHover(false, false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas), false, true); + // new anchor idx + var anchorIdx = jpc.endpoints[0].id == self.id ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas), + dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // now we want to get this endpoint's DROP scope, and set it for now: we can only be dropped on drop zones + // that have our drop scope (issue 57). + var dropScope = jsPlumb.CurrentLibrary.getDropScope(c); + jsPlumb.CurrentLibrary.setDragScope(c, dropScope); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = placeholderInfo.element; + jpc.sourceId = placeholderInfo.id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = placeholderInfo.element; + jpc.targetId = placeholderInfo.id; + } + + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.suspendedEndpoint.setHover(false); + jpc.endpoints[anchorIdx] = floatingEndpoint; + + // fire an event that informs that a connection is being dragged + fireConnectionDraggingEvent(jpc); + + } + // register it and register connection on it. + floatingConnections[placeholderInfo.id] = jpc; + floatingEndpoint.addConnection(jpc); + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, placeholderInfo.id, floatingEndpoint); + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var jpcl = jsPlumb.CurrentLibrary, + dragOptions = params.dragOptions || {}, + defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions), + startEvent = jpcl.dragEvents["start"], + stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"]; + + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + // extracted drag handler function so can be used by makeSource + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], _dragHandler.drag); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + var originalEvent = jpcl.getDropEvent(arguments); + _currentInstance.currentlyDragging = false; + _removeWithFunction(endpointsByElement[placeholderInfo.id], function(e) { + return e.id == floatingEndpoint.id; + }); + _removeElements( [ placeholderInfo.element[0], floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + _currentInstance.anchorManager.clearFor(placeholderInfo.id); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + self.setPaintStyle(originalPaintStyle); // reset to original; may have been changed by drag proxy. + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (self.isReattach || jpc._forceDetach || !jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc, false, false, true, originalEvent)) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + _currentInstance.repaint(existingJpcParams[1]); + } + jpc._forceDetach = null; + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElements(jpc.connector.getDisplayElements(), self.parent); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint({recalc:false}); + jpc.setHover(false, false); + + fireConnectionDragStopEvent(jpc); + + jpc = null; + inPlaceCopy = null; + delete endpointsByElement[floatingEndpoint.elementId]; + floatingEndpoint.anchor = null; + floatingEndpoint = null; + _currentInstance.currentlyDragging = false; + + + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions, true); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas, forceInit, isTransient, endpoint) { + if ((params.isTarget || forceInit) && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop'], + overEvent = jsPlumb.CurrentLibrary.dragEvents['over'], + outEvent = jsPlumb.CurrentLibrary.dragEvents['out'], + drop = function() { + + var originalEvent = jsPlumb.CurrentLibrary.getDropEvent(arguments), + draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + elId = _getAttribute(draggable, "elId"), + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id]; + + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + + // restore the original scope if necessary (issue 57) + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + var endpointEnabled = endpoint != null ? endpoint.isEnabled() : true; + + if (self.isFull()) { + self.fire("maxConnections", { + endpoint:self, + connection:jpc, + maxConnections:_maxConnections + }, originalEvent); + } + + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget) && endpointEnabled) { + + var _doContinue = true; + + // the second check here is for the case that the user is dropping it back + // where it came from. + if (jpc.suspendedEndpoint && jpc.suspendedEndpoint.id != self.id) { + if (idx == 0) { + jpc.source = jpc.suspendedEndpoint.element; + jpc.sourceId = jpc.suspendedEndpoint.elementId; + } else { + jpc.target = jpc.suspendedEndpoint.element; + jpc.targetId = jpc.suspendedEndpoint.elementId; + } + + if (!jpc.isDetachAllowed(jpc) || !jpc.endpoints[idx].isDetachAllowed(jpc) || !jpc.suspendedEndpoint.isDetachAllowed(jpc) || !_currentInstance.checkCondition("beforeDetach", jpc)) + _doContinue = false; + } + + // these have to be set before testing for beforeDrop. + if (idx == 0) { + jpc.source = self.element; + jpc.sourceId = self.elementId; + } else { + jpc.target = self.element; + jpc.targetId = self.elementId; + } + + // now check beforeDrop. this will be available only on Endpoints that are setup to + // have a beforeDrop condition (although, secretly, under the hood all Endpoints and + // the Connection have them, because they are on jsPlumbUIComponent. shhh!), because + // it only makes sense to have it on a target endpoint. + _doContinue = _doContinue && self.isDropAllowed(jpc.sourceId, jpc.targetId, jpc.scope, jpc, self); + + if (_doContinue) { + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + + // copy our parameters in to the connection: + var params = self.getParameters(); + for (var aParam in params) + jpc.setParameter(aParam, params[aParam]); + + if (!jpc.suspendedEndpoint) { + //_initDraggableIfNecessary(self.element, params.draggable, {}); + if (params.draggable) + jsPlumb.CurrentLibrary.initDraggable(self.element, dragOptions, true); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + fireDetachEvent({ + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }, true, originalEvent); + } + + // finalise will inform the anchor manager and also add to + // connectionsByScope if necessary. + _finaliseConnection(jpc, null, originalEvent); + } + else { + // otherwise just put it back on the endpoint it was on before the drag. + if (jpc.suspendedEndpoint) { + // self.detachFrom(jpc); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.setHover(false); + jpc._forceDetach = true; + if (idx == 0) { + jpc.source = jpc.suspendedEndpoint.element; + jpc.sourceId = jpc.suspendedEndpoint.elementId; + } else { + jpc.target = jpc.suspendedEndpoint.element; + jpc.targetId = jpc.suspendedEndpoint.elementId;; + } + jpc.suspendedEndpoint.addConnection(jpc); + + jpc.endpoints[0].repaint(); + jpc.repaint(); + _currentInstance.repaint(jpc.source.elementId); + jpc._forceDetach = false; + } + } + + jpc.floatingAnchorIndex = null; + } + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + } + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], function() { + if (self.isTarget) { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + } + } + }); + dropOptions[outEvent] = _wrap(dropOptions[outEvent], function() { + if (self.isTarget) { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + } + } + }); + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions, true, isTransient); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas), true, !(params._transient || self.anchor.isFloating), self); + + // finally, set type if it was provided + if (params.type) + self.addType(params.type); + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Endpoint. + * + * Parameters: + * event - the event to bind. Available events on an Endpoint are: + * - *click* : notification that a Endpoint was clicked. + * - *dblclick* : notification that a Endpoint was double clicked. + * - *mouseenter* : notification that the mouse is over a Endpoint. + * - *mouseexit* : notification that the mouse exited a Endpoint. + * - *contextmenu* : notification that the user right-clicked on the Endpoint. + * + * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Endpoint's paint style and then repaints the Endpoint. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: getPaintStyle + * Gets the Endpoint's paint style. This is not necessarily the paint style in use at the time; + * this is the paint style for the Endpoint when the mouse it not hovering over it. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Endpoint will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Endpoint. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + + /* + * Function: getParameter + * Gets the named parameter; returns null if no parameter with that name is set. Parameters may have been set on the Endpoint in the 'addEndpoint' call, or they may have been set with the setParameter function. + * + * Parameters: + * key - Parameter name. + */ + + /* + * Function: setParameter + * Sets the named parameter to the given value. + * + * Parameters: + * key - Parameter name. + * value - Parameter value. + */ + + /* + * Property: canvas + * The Endpoint's drawing area. + */ + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + + /* + * Property: overlays + * List of Overlays for this Endpoint. + */ + /* + * Function: addOverlay + * Adds an Overlay to the Endpoint. + * + * Parameters: + * overlay - Overlay to add. + */ + /* + * Function: getOverlay + * Gets an overlay, by ID. Note: by ID. You would pass an 'id' parameter + * in to the Overlay's constructor arguments, and then use that to retrieve + * it via this method. + */ + /* + * Function: getOverlays + * Gets all the overlays for this component. + */ + /* + * Function: hideOverlay + * Hides the overlay specified by the given id. + */ + /* + * Function: hideOverlays + * Hides all Overlays + */ + /* + * Function: showOverlay + * Shows the overlay specified by the given id. + */ + /* + * Function: showOverlays + * Shows all Overlays + */ + /** + * Function: removeAllOverlays + * Removes all overlays from the Endpoint, and then repaints. + */ + /** + * Function: removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + /** + * Function: removeOverlays + * Removes a set of overlays by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + /* + * Function: setLabel + * Sets the Endpoint's label. + * + * Parameters: + * l - label to set. May be a String, a Function that returns a String, or a params object containing { "label", "labelStyle", "location", "cssClass" }. Note that this uses innerHTML on the label div, so keep + * that in mind if you need escaped HTML. + */ + /* + Function: getLabel + Returns the label text for this Endpoint (or a function if you are labelling with a function). + This does not return the overlay itself; this is a convenience method which is a pair with + setLabel; together they allow you to add and access a Label Overlay without having to create the + Overlay object itself. For access to the underlying label overlay that jsPlumb has created, + use getLabelOverlay. + */ + /* + Function: getLabelOverlay + Returns the underlying internal label overlay, which will exist if you specified a label on + an addEndpoint call, or have called setLabel at any stage. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + + return self; + }; + }; + + var jsPlumb = new jsPlumbInstance(); + if (typeof window != 'undefined') window.jsPlumb = jsPlumb; + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + j.init(); + return j; + }; + + var _curryAnchor = function(x, y, ox, oy, type, fnInit) { + return function(params) { + params = params || {}; + //var a = jsPlumb.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance); + var a = params.jsPlumbInstance.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance); + a.type = type; + if (fnInit) fnInit(a, params); + return a; + }; + }; + /* + * Property: Anchors.TopCenter + * An Anchor that is located at the top center of the element. + */ + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1, "TopCenter"); + /* + * Property: Anchors.BottomCenter + * An Anchor that is located at the bottom center of the element. + */ + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1, "BottomCenter"); + /* + * Property: Anchors.LeftMiddle + * An Anchor that is located at the left middle of the element. + */ + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0, "LeftMiddle"); + /* + * Property: Anchors.RightMiddle + * An Anchor that is located at the right middle of the element. + */ + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0, "RightMiddle"); + /* + * Property: Anchors.Center + * An Anchor that is located at the center of the element. + */ + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0, "Center"); + /* + * Property: Anchors.TopRight + * An Anchor that is located at the top right corner of the element. + */ + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1, "TopRight"); + /* + * Property: Anchors.BottomRight + * An Anchor that is located at the bottom right corner of the element. + */ + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1, "BottomRight"); + /* + * Property: Anchors.TopLeft + * An Anchor that is located at the top left corner of the element. + */ + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1, "TopLeft"); + /* + * Property: Anchors.BottomLeft + * An Anchor that is located at the bototm left corner of the element. + */ + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1, "BottomLeft"); + + jsPlumb.Defaults.DynamicAnchors = function(params) { + return params.jsPlumbInstance.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"], params.elementId, params.jsPlumbInstance); + }; + /* + * Property: Anchors.AutoDefault + * Default DynamicAnchors - chooses from TopCenter, RightMiddle, BottomCenter, LeftMiddle. + */ + jsPlumb.Anchors["AutoDefault"] = function(params) { + var a = params.jsPlumbInstance.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors(params)); + a.type = "AutoDefault"; + return a; + }; + + /* + * Property: Anchors.Assign + * An Anchor whose location is assigned at connection time, through an AnchorPositionFinder. Used in conjunction + * with the 'makeTarget' function. jsPlumb has two of these - 'Fixed' and 'Grid', and you can also write your own. + */ + jsPlumb.Anchors["Assign"] = _curryAnchor(0,0,0,0,"Assign", function(anchor, params) { + // find what to use as the "position finder". the user may have supplied a String which represents + // the id of a position finder in jsPlumb.AnchorPositionFinders, or the user may have supplied the + // position finder as a function. we find out what to use and then set it on the anchor. + var pf = params.position || "Fixed"; + anchor.positionFinder = pf.constructor == String ? params.jsPlumbInstance.AnchorPositionFinders[pf] : pf; + // always set the constructor params; the position finder might need them later (the Grid one does, + // for example) + anchor.constructorParams = params; + }); + + // Continuous anchor is just curried through to the 'get' method of the continuous anchor + // factory. + /* + * Property: Anchors.Continuous + * An Anchor that is tracks the other element in the connection, choosing the closest face. + */ + jsPlumb.Anchors["Continuous"] = function(params) { + return params.jsPlumbInstance.continuousAnchorFactory.get(params); + }; + + // these are the default anchor positions finders, which are used by the makeTarget function. supply + // a position finder argument to that function allows you to specify where the resulting anchor will + // be located + jsPlumb.AnchorPositionFinders = { + "Fixed": function(dp, ep, es, params) { + return [ (dp.left - ep.left) / es[0], (dp.top - ep.top) / es[1] ]; + }, + "Grid":function(dp, ep, es, params) { + var dx = dp.left - ep.left, dy = dp.top - ep.top, + gx = es[0] / (params.grid[0]), gy = es[1] / (params.grid[1]), + mx = Math.floor(dx / gx), my = Math.floor(dy / gy); + return [ ((mx * gx) + (gx / 2)) / es[0], ((my * gy) + (gy / 2)) / es[1] ]; + } + }; + + /* + * Property: Anchors.Perimeter + * An Anchor that tracks the perimeter of some shape, approximating it with a given number of dynamically + * positioned locations. + * + * Parameters: + * + * anchorCount - optional number of anchors to use to approximate the perimeter. default is 60. + * shape - required. the name of the shape. valid values are 'rectangle', 'square', 'ellipse', 'circle', 'triangle' and 'diamond' + * rotation - optional rotation, in degrees, to apply. + */ + jsPlumb.Anchors["Perimeter"] = function(params) { + params = params || {}; + var anchorCount = params.anchorCount || 60, + shape = params.shape; + + if (!shape) throw new Error("no shape supplied to Perimeter Anchor type"); + + var _circle = function() { + var r = 0.5, step = Math.PI * 2 / anchorCount, current = 0, a = []; + for (var i = 0; i < anchorCount; i++) { + var x = r + (r * Math.sin(current)), + y = r + (r * Math.cos(current)); + a.push( [ x, y, 0, 0 ] ); + current += step; + } + return a; + }, + _path = function(segments) { + var anchorsPerFace = anchorCount / segments.length, a = [], + _computeFace = function(x1, y1, x2, y2, fractionalLength) { + anchorsPerFace = anchorCount * fractionalLength; + var dx = (x2 - x1) / anchorsPerFace, dy = (y2 - y1) / anchorsPerFace; + for (var i = 0; i < anchorsPerFace; i++) { + a.push( [ + x1 + (dx * i), + y1 + (dy * i), + 0, + 0 + ]); + } + }; + + for (var i = 0; i < segments.length; i++) + _computeFace.apply(null, segments[i]); + + return a; + }, + _shape = function(faces) { + var s = []; + for (var i = 0; i < faces.length; i++) { + s.push([faces[i][0], faces[i][1], faces[i][2], faces[i][3], 1 / faces.length]); + } + return _path(s); + }, + _rectangle = function() { + return _shape([ + [ 0, 0, 1, 0 ], [ 1, 0, 1, 1 ], [ 1, 1, 0, 1 ], [ 0, 1, 0, 0 ] + ]); + }; + + var _shapes = { + "circle":_circle, + "ellipse":_circle, + "diamond":function() { + return _shape([ + [ 0.5, 0, 1, 0.5 ], [ 1, 0.5, 0.5, 1 ], [ 0.5, 1, 0, 0.5 ], [ 0, 0.5, 0.5, 0 ] + ]); + }, + "rectangle":_rectangle, + "square":_rectangle, + "triangle":function() { + return _shape([ + [ 0.5, 0, 1, 1 ], [ 1, 1, 0, 1 ], [ 0, 1, 0.5, 0] + ]); + }, + "path":function(params) { + var points = params.points; + var p = [], tl = 0; + for (var i = 0; i < points.length - 1; i++) { + var l = Math.sqrt(Math.pow(points[i][2] - points[i][0]) + Math.pow(points[i][3] - points[i][1])); + tl += l; + p.push([points[i][0], points[i][1], points[i+1][0], points[i+1][1], l]); + } + for (var i = 0; i < p.length; i++) { + p[i][4] = p[i][4] / tl; + } + return _path(p); + } + }, + _rotate = function(points, amountInDegrees) { + var o = [], theta = amountInDegrees / 180 * Math.PI ; + for (var i = 0; i < points.length; i++) { + var _x = points[i][0] - 0.5, + _y = points[i][1] - 0.5; + + o.push([ + 0.5 + ((_x * Math.cos(theta)) - (_y * Math.sin(theta))), + 0.5 + ((_x * Math.sin(theta)) + (_y * Math.cos(theta))), + points[i][2], + points[i][3] + ]); + } + return o; + }; + + if (!_shapes[shape]) throw new Error("Shape [" + shape + "] is unknown by Perimeter Anchor type"); + + var da = _shapes[shape](params); + if (params.rotation) da = _rotate(da, params.rotation); + var a = params.jsPlumbInstance.makeDynamicAnchor(da); + a.type = "Perimeter"; + return a; + }; +})(); diff --git a/archive/1.3.14/jsPlumb-1.3.14-tests.js b/archive/1.3.14/jsPlumb-1.3.14-tests.js new file mode 100644 index 000000000..df75dd444 --- /dev/null +++ b/archive/1.3.14/jsPlumb-1.3.14-tests.js @@ -0,0 +1,4748 @@ +// _jsPlumb qunit tests. + +QUnit.config.reorder = false; + +var _getContextNode = function() { + return $("#container"); +}; + +var assertContextExists = function() { + ok(_getContextNode().length == 1, "context node exists"); +}; + +var assertContextSize = function(elementCount) { + //equals(_getContextNode().children().length - _divs.length, elementCount, 'context has ' + elementCount + ' children'); +}; + +var assertContextEmpty = function() { + equals(_getContextNode().children.length, 0, "context empty"); +}; + +var assertEndpointCount = function(elId, count, _jsPlumb) { + equals(_jsPlumb.getTestHarness().endpointCount(elId), count, elId + " has " + count + (count > 1) ? "endpoints" : "endpoint"); + equals(_jsPlumb.anchorManager.getEndpointsFor(elId).length, count, "anchor manager has " + count + (count > 1) ? "endpoints" : "endpoint"); +}; + +var assertConnectionCount = function(endpoint, count) { + equals(endpoint.connections.length, count, "endpoint has " + count + " connections"); +}; + +var assertConnectionByScopeCount = function(scope, count, _jsPlumb) { + equals(_jsPlumb.getTestHarness().connectionCount(scope), count, 'Scope ' + scope + " has " + count + (count > 1) ? "connections" : "connection"); + +}; + +var _divs = []; +var _addDiv = function(id, parent) { + var d1 = document.createElement("div"); + if (parent) parent.append(d1); else _getContextNode().append(d1); + d1 = jsPlumb.CurrentLibrary.getElementObject(d1); + jsPlumb.CurrentLibrary.setAttribute(d1, "id", id); + _divs.push(id); + return d1; +}; + +var defaults = null, + _cleanup = function(_jsPlumb) { + + _jsPlumb.reset(); + _jsPlumb.Defaults = defaults; + + for (var i in _divs) { + $("#" + _divs[i]).remove(); + } + _divs.splice(0, _divs.length - 1); + + $("#container").empty(); +}; + +var testSuite = function(renderMode, _jsPlumb) { + + + module("jsPlumb", { + teardown: function() { + _cleanup(_jsPlumb); + }, + setup:function() { + defaults = jsPlumb.extend({}, _jsPlumb.Defaults); + } + }); + + // setup the container + var container = document.createElement("div"); + container.id = "container"; + document.body.appendChild(container); + + var jpcl = jsPlumb.CurrentLibrary; + + _jsPlumb.setRenderMode(renderMode); + + test(renderMode + " : getElementObject", function() { + var e = document.createElement("div"); + e.id = "FOO"; + document.body.appendChild(e); + var el = jpcl.getElementObject(e); + equals(jpcl.getAttribute(el, "id"), "FOO"); + }); + + test(renderMode + " : getDOMElement", function() { + var e = document.createElement("div"); + e.id = "FOO"; + document.body.appendChild(e); + var el = jpcl.getElementObject(e); + var e2 = jpcl.getDOMElement(el); + equals(e2.id, "FOO"); + }); + + test(renderMode + ': _jsPlumb setup', function() { + ok(_jsPlumb, "loaded"); + }); + + test(renderMode + ': getId', function() { + var d10 = _addDiv('d10'); + equals(_jsPlumb.getTestHarness().getId(d10), "d10"); + }); + + test(renderMode + ': create a simple endpoint', function() { + var d1 = _addDiv("d1"); + var e = _jsPlumb.addEndpoint($("#d1"), {}); + ok(e, 'endpoint exists'); + assertEndpointCount("d1", 1, _jsPlumb); + ok(e.id != null, "endpoint has had an id assigned"); + }); + + test(renderMode + ': create and remove a simple endpoint', function() { + var d1 = _addDiv("d1"); + var ee = _jsPlumb.addEndpoint(d1, {uuid:"78978597593"}); + ok(ee != null, "endpoint exists"); + var e = _jsPlumb.getEndpoint("78978597593"); + ok(e != null, "the endpoint could be retrieved by UUID"); + ok(e.id != null, "the endpoint has had an id assigned to it"); + assertEndpointCount("d1", 1, _jsPlumb); + assertContextSize(1); // one Endpoint canvas. + _jsPlumb.removeEndpoint(d1, ee); + assertEndpointCount("d1", 0, _jsPlumb); + e = _jsPlumb.getEndpoint("78978597593"); + equals(e, null, "the endpoint has been deleted"); + assertContextSize(0); // no Endpoint canvases. + }); + + test(renderMode + ': create two simple endpoints, registered using a selector', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + d1.addClass("window");d2.addClass("window"); + var endpoints = _jsPlumb.addEndpoint($(".window"), {}); + equals(endpoints.length, 2, "endpoint added to both windows"); + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + }); + + test(renderMode + ': create two simple endpoints, registered using an array of element ids', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + d1.addClass("window");d2.addClass("window"); + var endpoints = _jsPlumb.addEndpoint(["d1", "d2"], {}); + equals(endpoints.length, 2, "endpoint added to both windows"); + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + }); + + test(renderMode + ': draggable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1"); + var e = _jsPlumb.addEndpoint(d1, {isSource:true}); + ok(e, 'endpoint exists'); + }); + + test(renderMode + ': droppable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1") + var e = _jsPlumb.addEndpoint(d1, {isTarget:true}); + ok(e, 'endpoint exists'); + }); + + test(renderMode + ': draggable in nested element does not cause extra ids to be created', function() { + var d = _addDiv("d1"); + var d2 = document.createElement("div"); + d2.setAttribute("foo", "ff"); + d.append(d2); + var d3 = document.createElement("div"); + d2.appendChild(d3); + ok(d2.getAttribute("id") == null, "no id on d2"); + _jsPlumb.draggable(d); + _jsPlumb.addEndpoint(d3); + ok(d2.getAttribute("id") == null, "no id on d2"); + ok(d3.getAttribute("id") != null, "id on d3"); + }); + + test(renderMode + ': defaultEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = _jsPlumb.addEndpoint(d3, {isSource:true}); + ok(e3.anchor, 'endpoint 3 has an anchor'); + var e4 = _jsPlumb.addEndpoint(d4, {isSource:true}); + assertEndpointCount("d3", 1, _jsPlumb); + assertEndpointCount("d4", 1, _jsPlumb); + assertContextSize(2); // one canvas for each of our two endpoints. + ok(!e3.isFull(), "endpoint 3 is not full."); + _jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // we have one connection + _jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // should have refused the connection; default max is 1. + assertContextSize(3); // now we have one more canvas, for the Connection that was accepted. + }); + + test(renderMode + ': specifiedEndpointMaxConnections', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = _jsPlumb.addEndpoint(d5, {isSource:true, maxConnections:3}); + ok(e5.anchor, 'endpoint 5 has an anchor'); + var e6 = _jsPlumb.addEndpoint(d6, {isSource:true, maxConnections:2}); // this one has max TWO + assertEndpointCount("d5", 1, _jsPlumb); assertEndpointCount("d6", 1, _jsPlumb); + assertContextSize(2); + ok(!e5.isFull(), "endpoint 5 is not full."); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 1); // we have one connection + assertContextSize(3); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // two connections + assertContextSize(4); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // should have refused; max is 2, for d4. + assertContextSize(4); + }); + + test(renderMode + ': noEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = _jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + var e4 = _jsPlumb.addEndpoint(d4, {isSource:true, maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertContextSize(3); + assertConnectionCount(e3, 1); // we have one connection + _jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 2); // we have two. etc (default was one. this proves max is working). + assertContextSize(4); + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = _jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e4}); + assertConnectionCount(e4, 3); + assertContextSize(6); + }); + + test(renderMode + ': anchors equal', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + ok(a1.equals(a2), "anchors are the same"); + }); + + test(renderMode + ': anchors equal with offsets', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb); + ok(a1.equals(a2), "anchors are the same"); + }); + + test(renderMode + ': anchors not equal', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 0, 1], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + ok(!a1.equals(a2), "anchors are different"); + }); + + test(renderMode + ': anchor not equal with offsets', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + ok(!a1.equals(a2), "anchors are different"); + }); + + test(renderMode + ': detach does not fail when no arguments are provided', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + _jsPlumb.connect({source:d3, target:d4}); + _jsPlumb.detach(); + }); + + // test that detach does not fire an event by default + test(renderMode + ': _jsPlumb.detach should fire detach event by default', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach(conn); + equals(eventCount, 1); + }); + + // test that detach does not fire an event by default + test(renderMode + ': _jsPlumb.detach should fire detach event by default, using params object', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({connection:conn}); + equals(eventCount, 1); + }); + + // test that detach fires an event when instructed to do so + test(renderMode + ': _jsPlumb.detach should not fire detach event when instructed to not do so', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach(conn, {fireEvent:false}); + equals(eventCount, 0); + }); + + // issue 81 + test(renderMode + ': _jsPlumb.detach should fire only one detach event (pass Connection as argument)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach(conn); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': _jsPlumb.detach should fire only one detach event (pass Connection as param in argument)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({connection:conn}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': detach should fire only one detach event (pass source and targets as strings as arguments in params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({source:"d5", target:"d6"}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': detach should fire only one detach event (pass source and targets as divs as arguments in params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({source:d5, target:d6, fireEvent:true}); + equals(eventCount, 1); + }); + + //TODO make sure you run this test with a single detach call, to ensure that + // single detach calls result in the connection being removed. detachEveryConnection can + // just blow away the connectionsByScope array and recreate it. + test(renderMode + ': getConnections (simple case, default scope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + _jsPlumb.connect({source:d5, target:d6}); + assertContextSize(3); + var c = _jsPlumb.getConnections(); // will get all connections in the default scope. + equals(c.length, 1, "there is one connection"); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by element id using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + _jsPlumb.connect({source:d5, target:d6}); + _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach({source:'d5', target:'d6'}); + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by id using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + _jsPlumb.connect({source:d5, target:d6}); + _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach({source:"d5", target:"d6"}); + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by element object using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + _jsPlumb.connect({source:d5, target:d6}); + _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach({source:d5, target:d6}); + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by Connection)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + var c56 = _jsPlumb.connect({source:d5, target:d6}); + var c67 = _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach(c56); + assertContextSize(5); // check that the connection canvas was removed. + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + }); + +// beforeDetach functionality + + test(renderMode + ": detach; beforeDetach on connect call returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { return false; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": detach; beforeDetach on connect call returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { return true; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": detach; beforeDetach on connect call throws an exception; we treat it with the contempt it deserves and pretend it said the detach was ok.", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { throw "i am an example of badly coded beforeDetach, but i don't break jsPlumb "; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": detach; beforeDetach on addEndpoint call to source Endpoint returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return false; } }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": detach; beforeDetach on addEndpoint call to source Endpoint returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return true; } }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + + test(renderMode + ": Endpoint.detach; beforeDetach on addEndpoint call to source Endpoint returns false; Endpoint.detach returns false too (the UI needs it to)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return false; } }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + var success = e1.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + ok(!success, "Endpoint reported detach did not execute"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns false but detach is forced", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c, {forceDetach:true}); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was denied but forced anyway"); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was denied but forced anyway"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return true; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach bound to _jsPlumb returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + _jsPlumb.bind("beforeDetach", function(connection) { + ok(connection.sourceId === "d1", "connection is provided and configured with correct source"); + ok(connection.targetId === "d2", "connection is provided and configured with correct target"); + return false; + }); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach bound to _jsPlumb returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + _jsPlumb.bind("beforeDetach", function(connection) { + ok(connection.sourceId === "d1", "connection is provided and configured with correct source"); + ok(connection.targetId === "d2", "connection is provided and configured with correct target"); + return true; + }); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was denied"); + }); + + test(renderMode + ": _jsPlumb.detachAllConnections ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detachAllConnections(d1); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachAll was called"); + }); + + test(renderMode + ": _jsPlumb.detachEveryConnection ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detachEveryConnection(); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachEveryConnection was called"); + }); + + test(renderMode + ": Endpoint.detachAll ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + e1.detachAll(); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachAllConnections was called"); + }); + +// ******** end of beforeDetach tests ************** + +// detachEveryConnection/detachAllConnections fireEvent overrides tests + + test(renderMode + ": _jsPlumb.detachEveryConnection fires events", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0; + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachEveryConnection(); + equals(connCount, 0, "no connections registered"); + }); + + test(renderMode + ": _jsPlumb.detachEveryConnection doesn't fire events when instructed not to", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0; + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachEveryConnection({fireEvent:false}); + equals(connCount, 2, "two connections registered"); + }); + + test(renderMode + ": _jsPlumb.detachAllConnections fires events", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0, + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachAllConnections("d1"); + equals(connCount, 0, "no connections registered"); + }); + + test(renderMode + ": _jsPlumb.detachAllConnections doesn't fire events when instructed not to", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0, + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachAllConnections("d1", {fireEvent:false}); + equals(connCount, 2, "two connections registered"); + }); + + test(renderMode + ': getConnections (scope testScope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + _jsPlumb.connect({source:d5, target:d6, scope:'testScope'}); + var c = _jsPlumb.getConnections("testScope"); // will get all connections in testScope + equals(c.length, 1, "there is one connection"); + equals(c[0].sourceId, 'd5', "the connection's source is d5"); + equals(c[0].targetId, 'd6', "the connection's source is d6"); + c = _jsPlumb.getConnections(); // will get all connections in default scope; should be none. + equals(c.length, 0, "there are no connections in the default scope"); + }); + + test(renderMode + ': _jsPlumb.getAllConnections (filtered by scope)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + _jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + _jsPlumb.connect({source:d9, target:d10}); // default scope + var c = _jsPlumb.getAllConnections(); // will get all connections + equals(c[_jsPlumb.getDefaultScope()].length, 1); + equals(c['testScope'].length, 1); + // now supply a list of scopes + c = _jsPlumb.getConnections(); + equals(c.length, 1); + c = _jsPlumb.getConnections("testScope"); + equals(c.length, 1, "there is one connection in 'testScope'"); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by scope and sourceId)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + _jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + _jsPlumb.connect({source:d9, target:d8, scope:'testScope'}); + _jsPlumb.connect({source:d9, target:d10}); // default scope + var c = _jsPlumb.getConnections({scope:'testScope', source:'d8'}); // will get all connections with sourceId 'd8' + equals(c.length, 1, "there is one connection in 'testScope' from d8"); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by scope, source id and target id)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope'}); + var c = _jsPlumb.getConnections({scope:'testScope', source:'d11', target:'d13'}); + equals(c.length, 1, "there is one connection from d11 to d13"); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = _jsPlumb.getConnections({scope:['testScope','testScope3']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes and source ids)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = _jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes, source ids and target ids)', function() { + _jsPlumb.detachEveryConnection(); + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'), d14=_addDiv("d14"), d15=_addDiv("d15"); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d11, target:d14, scope:'testScope'}); + _jsPlumb.connect({source:d11, target:d15, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + assertContextSize(18); + var c = _jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11'], target:['d14', 'd15']}); + equals(c['testScope'].length, 2, 'there are two connections in testScope'); + equals(c['testScope3'], null, 'there are no connections in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + var anEntry = c['testScope'][0]; + ok(anEntry.endpoints[0] != null, "Source endpoint is set"); + ok(anEntry.endpoints[1] != null, "Target endpoint is set"); + equals(anEntry.source.attr("id"), "d11", "Source is div d11"); + equals(anEntry.target.attr("id"), "d14", "Target is div d14"); + }); + + test(renderMode + ': getEndpoints, one Endpoint added by addEndpoint, get Endpoints by selector', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + _jsPlumb.addEndpoint(d1); + var e = _jsPlumb.getEndpoints(d1); + equals(e.length, 1, "there is one endpoint for element d1"); + }); + + test(renderMode + ': getEndpoints, one Endpoint added by addEndpoint, get Endpoints by id', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + _jsPlumb.addEndpoint(d1); + var e = _jsPlumb.getEndpoints("d1"); + equals(e.length, 1, "there is one endpoint for element d1"); + }); + + test(renderMode + ': connection event listeners', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null/*, returnedParams2 = null*/; + _jsPlumb.bind("jsPlumbConnection", function(params) { + returnedParams = $.extend({}, params); + }); + var c = _jsPlumb.connect({source:d1, target:d2}); + ok(returnedParams != null, "new connection listener event was fired"); + ok(returnedParams.connection != null, 'connection is set'); + equals(returnedParams.sourceId, "d1", 'sourceid is set'); + equals(returnedParams.targetId, "d2", 'targetid is set'); + equals(returnedParams.source.attr("id"), "d1", 'source is set'); + equals(returnedParams.target.attr("id"), "d2" , 'target is set'); + ok(returnedParams.sourceEndpoint != null, "source endpoint is not null"); + ok(returnedParams.targetEndpoint != null, "target endpoint is not null"); + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.detach(c); + ok(returnedParams.connection != null, 'connection is set'); + }); + + test(renderMode + ': detach event listeners (detach by connection)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + ok(returnedParams.connection != null, "removed connection listener event passed in connection"); + }); + + test(renderMode + ': detach event listeners (detach by element ids)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach({source:"d1",target:"d2"}); + ok(returnedParams != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (detach by elements)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach({source:d1,target:d2}); + ok(returnedParams != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detach method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detachFrom method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachFrom(e2); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detachAll method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachAll(); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via _jsPlumb.deleteEndpoint method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + _jsPlumb.deleteEndpoint(e1); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(1); + }); + + test(renderMode + ': detach event listeners (ensure cleared by _jsPlumb.reset)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach({source:d1,target:d2, fireEvent:true}); + ok(returnedParams != null, "removed connection listener event was fired"); + returnedParams = null; + + _jsPlumb.reset(); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach(d1,d2); + ok(returnedParams == null, "connection listener was cleared by _jsPlumb.reset()"); + }); + + test(renderMode + ': connection events that throw errors', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null, returnedParams2 = null; + _jsPlumb.bind("jsPlumbConnection", function(params) { + returnedParams = $.extend({}, params); + throw "oh no!"; + }); + _jsPlumb.connect({source:d1, target:d2}); + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + _jsPlumb.connect({source:d3, target:d4}); + ok(returnedParams != null, "new connection listener event was fired; we threw an error, _jsPlumb survived."); + }); + + test(renderMode + ': unbinding connection event listeners, connection', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var count = 0; + _jsPlumb.bind("jsPlumbConnection", function(params) { + count++; + }); + var c = _jsPlumb.connect({source:d1, target:d2}); + ok(count == 1, "received one event"); + _jsPlumb.unbind("jsPlumbConnection"); + var c2 = _jsPlumb.connect({source:d1, target:d2}); + ok(count == 1, "still received only one event"); + + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + count--; + }); + _jsPlumb.detach(c); + ok(count == 0, "count of events is now zero"); + }); + + test(renderMode + ': unbinding connection event listeners, detach', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var count = 0; + _jsPlumb.bind("jsPlumbConnection", function(params) { + count++; + }); + var c = _jsPlumb.connect({source:d1, target:d2}); + ok(count == 1, "received one event"); + var c2 = _jsPlumb.connect({source:d1, target:d2}); + ok(count == 2, "received two events"); + + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + count--; + }); + _jsPlumb.detach(c); + ok(count == 1, "count of events is now one"); + _jsPlumb.unbind("jsPlumbConnectionDetached"); + _jsPlumb.detach(c2); + ok(count == 1, "count of events is still one"); + }); + + test(renderMode + ': unbinding connection event listeners, all listeners', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var count = 0; + _jsPlumb.bind("jsPlumbConnection", function(params) { + count++; + }); + var c = _jsPlumb.connect({source:d1, target:d2}), + c2 = _jsPlumb.connect({source:d1, target:d2}), + c3 = _jsPlumb.connect({source:d1, target:d2}); + + ok(count == 3, "received three events"); + + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + count--; + }); + _jsPlumb.detach(c); + ok(count == 2, "count of events is now two"); + + _jsPlumb.unbind(); // unbind everything + + _jsPlumb.detach(c2); + _jsPlumb.detach(c3); + _jsPlumb.connect({source:d1, target:d2}) + _jsPlumb.connect({source:d1, target:d2}) + _jsPlumb.connect({source:d1, target:d2}) + _jsPlumb.connect({source:d1, target:d2}) + + ok(count == 2, "count of events is still two"); + }); + + test(renderMode + ": Endpoint.detachFrom", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + e16.detachFrom(e17); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by _jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": Endpoint.detachFromConnection", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + e16.detachFromConnection(conn); + assertContextSize(3); // all canvases should remain; the connection was not removed. + // but endpoint e16 should have no connections now. + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": Endpoint.detachAll", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + var e16 = _jsPlumb.addEndpoint($("#d16"), {isSource:true,maxConnections:-1}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint($("#d17"), {isSource:true}); + var e18 = _jsPlumb.addEndpoint($("#d18"), {isSource:true}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e18}); + assertConnectionCount(e16, 2); + assertConnectionCount(e17, 1); + assertConnectionCount(e18, 1); + assertContextSize(5); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 2, _jsPlumb); + e16.detachAll(); + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": Endpoint.detach", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + e16.detach(conn); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by _jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": Endpoint.isConnectedTo", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.isConnectedTo(e17), true, "e16 and e17 are connected"); + }); + + asyncTest(renderMode + "jsPlumbUtil.setImage on Endpoint, with supplied onload", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { + endpoint:[ "Image", { + src:"../img/endpointTest1.png", + onload:function(imgEp) { + _jsPlumb.repaint("d1"); + ok(imgEp.img.src.indexOf("endpointTest1.png") != -1, "image source is correct"); + ok(imgEp.img.src.indexOf("endpointTest1.png") != -1, "image elementsource is correct"); + + imgEp.canvas.setAttribute("id", "iwilllookforthis"); + + _jsPlumb.removeAllEndpoints("d1"); + ok(document.getElementById("iwilllookforthis") == null, "image element was removed after remove endpoint"); + } + } ] + }; + start(); + _jsPlumb.addEndpoint(d1, e); + }); + + test(renderMode + ": setting endpoint uuid", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + equals(e16.getUuid(), uuid, "endpoint's uuid was set correctly"); + }); + + test(renderMode + ": _jsPlumb.getEndpoint (by uuid)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by uuid, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + _jsPlumb.deleteEndpoint(uuid); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + var ebe = _jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by uuid, connections too)", function() { + // create two endpoints (one with a uuid), add them to two divs and then connect them. + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + // check that the connection was ok. + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + // delete the endpoint that has a uuid. verify that the endpoint cannot be retrieved and that the connection has been removed, but that + // element d17 still has its Endpoint. + _jsPlumb.deleteEndpoint(uuid); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + var ebe = _jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + ebe = _jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 1, "element d17 still has its Endpoint"); + assertContextSize(1); + + // now delete d17's endpoint and check that it has gone. + _jsPlumb.deleteEndpoint(e17); + f = _jsPlumb.getEndpoint(e17); + equals(f, null, "endpoint has been deleted"); + ebe = _jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 0, "element d17 no longer has any Endpoints"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by reference, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + _jsPlumb.deleteEndpoint(e16); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by reference, connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + _jsPlumb.deleteEndpoint(e16); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + assertContextSize(1); + }); + + test(renderMode + ": _jsPlumb.deleteEveryEndpoint", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + assertContextSize(2); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + _jsPlumb.deleteEveryEndpoint(); + + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = _jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEveryEndpoint (connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + _jsPlumb.deleteEveryEndpoint(); + + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = _jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); // no canvases. so all connection canvases have been cleaned up too. + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchor:[0,0.5,0,-1]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:"TopCenter"}); + assertContextSize(2); + equals(e16.anchor.x, 0); + equals(e16.anchor.y, 0.5); + equals(e17.anchor.x, 0.5); + equals(e17.anchor.y, 0); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (tooltip)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {tooltip:"FOO"}), + e17 = _jsPlumb.addEndpoint(d17, {tooltip:"BAZ"}); + assertContextSize(2); + equals(e16.canvas.getAttribute("title"), "FOO"); + equals(e17.canvas.getAttribute("title"), "BAZ"); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case, dynamic anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + assertContextSize(2); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case, two arg method)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchor:[0,0.5,0,-1]}); + var e17 = _jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchor:"TopCenter"}); + assertContextSize(2); + equals(e16.anchor.x, 0); + equals(e16.anchor.y, 0.5); + equals(false, e16.isTarget); + equals(true, e16.isSource); + equals(e17.anchor.x, 0.5); + equals(e17.anchor.y, 0); + equals(true, e17.isTarget); + equals(false, e17.isSource); + }); + + + test(renderMode + ": _jsPlumb.addEndpoints (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false, anchor:[0,0.5,0,-1] }, { isTarget:true, isSource:false, anchor:"TopCenter" }]); + assertContextSize(2); + equals(e16[0].anchor.x, 0); + equals(e16[0].anchor.y, 0.5); + equals(false, e16[0].isTarget); + equals(true, e16[0].isSource); + equals(e16[1].anchor.x, 0.5); + equals(e16[1].anchor.y, 0); + equals(true, e16[1].isTarget); + equals(false, e16[1].isSource); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (with reference params)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var refParams = {anchor:"RightMiddle"}; + var e16 = _jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false}, { isTarget:true, isSource:false }], refParams); + assertContextSize(2); + equals(e16[0].anchor.x, 1); + equals(e16[0].anchor.y, 0.5); + equals(false, e16[0].isTarget); + equals(true, e16[0].isSource); + equals(e16[1].anchor.x, 1); + equals(e16[1].anchor.y, 0.5); + equals(true, e16[1].isTarget); + equals(false, e16[1].isSource); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case, dynamic anchors, two arg method)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchors:["TopCenter", "BottomCenter"]}); + assertContextSize(2); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (default overlays)", function() { + _jsPlumb.Defaults.Overlays = [ + [ "Label", { id:"label" } ] + ]; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16), + e2 = _jsPlumb.addEndpoint(d17); + + ok(e1.getOverlay("label") != null, "endpoint 1 has overlay from defaults"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (default overlays)", function() { + _jsPlumb.Defaults.Overlays = [ + [ "Label", { id:"label" } ] + ]; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16, { + overlays:[ + ["Label", { id:"label2", location:[ 0.5, 1 ] } ] + ] + }), + e2 = _jsPlumb.addEndpoint(d17); + + ok(e1.getOverlay("label") != null, "endpoint 1 has overlay from defaults"); + ok(e1.getOverlay("label2") != null, "endpoint 1 has overlay from addEndpoint call"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (end point set label)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16), + e2 = _jsPlumb.addEndpoint(d17); + + e1.setLabel("FOO"); + equals(e1.getLabel(), "FOO", "endpoint's label is correct"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (end point set label in constructor, as string)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16, {label:"FOO"}), + e2 = _jsPlumb.addEndpoint(d17); + + equals(e1.getLabel(), "FOO", "endpoint's label is correct"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (end point set label in constructor, as function)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16, {label:function() { return "BAZ"; }, labelLocation:0.1}), + e2 = _jsPlumb.addEndpoint(d17); + + equals(e1.getLabel()(), "BAZ", "endpoint's label is correct"); + equals(e1.getLabelOverlay().getLocation(), 0.1, "endpoint's label's location is correct"); + }); + +// ****************** makeTarget (and associated methods) tests ******************************************** + + test(renderMode + ": _jsPlumb.makeTarget (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.makeTarget (specify two divs in an array)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.makeTarget([d16, d17], { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.makeTarget (specify two divs by id in an array)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.makeTarget(["d16", "d17"], { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.makeTarget (specify divs by selector)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + d16.addClass("FOO");d17.addClass("FOO"); + _jsPlumb.makeTarget($(".FOO"), { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e.length, 1, "d17 has one endpoint"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case, two connect calls)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17"}); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 2, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case, two connect calls, uniqueEndpoint set)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true, anchor:"LeftMiddle", uniqueEndpoint:true, maxConnections:-1 }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17"}); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].connections.length, 2, "endpoint on d17 has two connections"); + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (newConnection:true specified)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17", newConnection:true}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0.5, "anchor is BottomCenter"); //here we should be seeing the default anchor + equals(e[0].anchor.y, 1, "anchor is BottomCenter"); //here we should be seeing the default anchor + }); + +// jsPlumb.connect, after makeSource has been called on some element + test(renderMode + ": _jsPlumb.connect after makeSource (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeSource (simple case, two connect calls)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 2, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case, two connect calls, uniqueEndpoint set)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true, anchor:"LeftMiddle", uniqueEndpoint:true, maxConnections:-1 }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].connections.length, 2, "endpoint on d17 has two connections"); + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (newConnection:true specified)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16, newConnection:true}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0.5, "anchor is BottomCenter"); //here we should be seeing the default anchor + equals(e[0].anchor.y, 1, "anchor is BottomCenter"); //here we should be seeing the default anchor + }); + + test(renderMode + ": _jsPlumb.connect after makeSource on child; wth parent set (parent should be recognised)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18", d17); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d18, { isSource:true,anchor:"LeftMiddle", parent:d17 }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + assertEndpointCount("d18", 0, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + // makeSource, then disable it. should not be able to make a connection from it. + test(renderMode + ": _jsPlumb.connect after makeSource and setSourceEnabled(false) (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.setSourceEnabled(d17, false); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + }); + + // makeSource, then disable it. should not be able to make a connection from it. + test(renderMode + ": _jsPlumb.connect after makeSource and setSourceEnabled(false) (selector as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.setSourceEnabled($("div"), false); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + }); + + // makeSource, then toggle its enabled state. should not be able to make a connection from it. + test(renderMode + ": _jsPlumb.connect after makeSource and toggleSourceEnabled() (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.toggleSourceEnabled(d17); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + + _jsPlumb.toggleSourceEnabled(d17); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + // makeSource, then disable it. should not be able to make a connection from it. + test(renderMode + ": _jsPlumb.connect after makeSource and toggleSourceEnabled() (selector as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.toggleSourceEnabled($("div")); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + _jsPlumb.toggleSourceEnabled($("div")); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + test(renderMode + ": jsPlumb.isSource and jsPlumb.isSourceEnabled", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + ok(_jsPlumb.isSource(d17) == true, "d17 is recognised as connection source"); + ok(_jsPlumb.isSourceEnabled(d17) == true, "d17 is recognised as enabled"); + _jsPlumb.setSourceEnabled(d17, false); + ok(_jsPlumb.isSourceEnabled(d17) == false, "d17 is recognised as disabled"); + }); + + // makeSource, then disable it. should not be able to make a connection to it. + test(renderMode + ": _jsPlumb.connect after makeTarget and setTargetEnabled(false) (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.setTargetEnabled(d17, false); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + }); + + // makeTarget, then disable it. should not be able to make a connection to it. + test(renderMode + ": _jsPlumb.connect after makeTarget and setTargetEnabled(false) (selector as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.setTargetEnabled($("div"), false); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + }); + + // makeTarget, then toggle its enabled state. should not be able to make a connection to it. + test(renderMode + ": _jsPlumb.connect after makeTarget and toggleTargetEnabled() (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.toggleTargetEnabled(d17); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + + _jsPlumb.toggleTargetEnabled(d17); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + // makeTarget, then disable it. should not be able to make a connection to it. + test(renderMode + ": _jsPlumb.connect after makeTarget and toggleTargetEnabled() (selector as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.toggleTargetEnabled($("div")); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + _jsPlumb.toggleTargetEnabled($("div")); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + test(renderMode + ": jsPlumb.isTarget and jsPlumb.isTargetEnabled", function() { + var d17 = _addDiv("d17"); + _jsPlumb.makeTarget(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + ok(_jsPlumb.isTarget(d17) == true, "d17 is recognised as connection target"); + ok(_jsPlumb.isTargetEnabled(d17) == true, "d17 is recognised as enabled"); + _jsPlumb.setTargetEnabled(d17, false); + ok(_jsPlumb.isTargetEnabled(d17) == false, "d17 is recognised as disabled"); + }); + + // makeSource, then unmake it. should not be able to make a connection from it. then connect to it, which should succeed, + // because jsPlumb will just add a new endpoint. + test(renderMode + ": jsPlumb.unmakeSource (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + ok(_jsPlumb.isSource(d17) == true, "d17 is currently a source"); + // unmake source + _jsPlumb.unmakeSource(d17); + ok(_jsPlumb.isSource(d17) == false, "d17 is no longer a source"); + + // this should succeed, because d17 is no longer a source and so jsPlumb will just create and add a new endpoint to d17. + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + // maketarget, then unmake it. should not be able to make a connection to it. + test(renderMode + ": jsPlumb.unmakeTarget (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + ok(_jsPlumb.isTarget(d17) == true, "d17 is currently a target"); + // unmake target + _jsPlumb.unmakeTarget(d17); + ok(_jsPlumb.isTarget(d17) == false, "d17 is no longer a target"); + + // this should succeed, because d17 is no longer a target and so jsPlumb will just create and add a new endpoint to d17. + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + + test(renderMode + ": jsPlumb.removeEverySource and removeEveryTarget (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + _jsPlumb.makeSource(d16).makeTarget(d17).makeSource(d18); + ok(_jsPlumb.isSource(d16) == true, "d16 is a source"); + ok(_jsPlumb.isTarget(d17) == true, "d17 is a target"); + ok(_jsPlumb.isSource(d18) == true, "d18 is a source"); + + _jsPlumb.unmakeEverySource(); + _jsPlumb.unmakeEveryTarget(); + + ok(_jsPlumb.isSource(d16) == false, "d16 is no longer a source"); + ok(_jsPlumb.isTarget(d17) == false, "d17 is no longer a target"); + ok(_jsPlumb.isSource(d18) == false, "d18 is no longer a source"); + }); + +// *********************** end of makeTarget (and associated methods) tests ************************ + + test(renderMode + ': _jsPlumb.connect (between two Endpoints)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1, _jsPlumb); // no new endpoint should have been added + assertEndpointCount("d2", 1, _jsPlumb); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. + ok(c.id != null, "connection has had an id assigned"); + }); + + test(renderMode + ': _jsPlumb.connect (Endpoint connectorTooltip parameter)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1, {connectorTooltip:"FOO"}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + equals(c.connector.canvas.getAttribute("title"), "FOO", "connector canvas has label attribute set"); + }); + + test(renderMode + ': _jsPlumb.connect (tooltip parameter)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2, tooltip:"FOO"}); + equals(c.connector.canvas.getAttribute("title"), "FOO", "connector canvas has label attribute set"); + }); + + test(renderMode + ': _jsPlumb.connect (between two Endpoints, and dont supply any parameters to the Endpoints.)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1, _jsPlumb); // no new endpoint should have been added + assertEndpointCount("d2", 1, _jsPlumb); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. + }); + + test(renderMode + " : _jsPlumb.connect, passing 'anchors' array" ,function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, anchors:["LeftMiddle", "RightMiddle"]}); + + equals(c.endpoints[0].anchor.x, 0, "source anchor is at x=0"); + equals(c.endpoints[0].anchor.y, 0.5, "source anchor is at y=0.5"); + equals(c.endpoints[1].anchor.x, 1, "target anchor is at x=1"); + equals(c.endpoints[1].anchor.y, 0.5, "target anchor is at y=0.5"); + }); + + test(renderMode + ': _jsPlumb.connect (by endpoint)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + }); + + test(renderMode + ': _jsPlumb.connect (cost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17, cost:567}); + assertContextSize(3); + equals(c.getCost(), 567, "connection cost is 567"); + }); + + test(renderMode + ': _jsPlumb.connect (default cost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), undefined, "default connection cost is 1"); + }); + + test(renderMode + ': _jsPlumb.connect (set cost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), undefined, "default connection cost is 1"); + c.setCost(8989); + equals(c.getCost(), 8989, "connection cost is 8989"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (connectionCost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionCost:567}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), 567, "connection cost is 567"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (change connectionCost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionCost:567, maxConnections:-1}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1}); + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), 567, "connection cost is 567"); + e16.setConnectionCost(23); + var c2 = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(c2.getCost(), 23, "connection cost is 23 after change on endpoint"); + }); + + test(renderMode + ': _jsPlumb.connect (default bidirectional)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true}), + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.isBidirectional(), true, "default connection is bidirectional"); + }); + + test(renderMode + ': _jsPlumb.connect (bidirectional false)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true}), + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17, bidirectional:false}); + assertContextSize(3); + equals(c.isBidirectional(), false, "connection is not bidirectional"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (connectionsBidirectional)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionsBidirectional:true, maxConnections:-1}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.isBidirectional(), true, "connection is bidrectional"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (change connectionsBidirectional)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionsBidirectional:true, maxConnections:-1}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1}); + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.isBidirectional(), true, "connection is bidirectional"); + e16.setConnectionsBidirectional(false); + var c2 = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(c2.isBidirectional(), false, "connection is not bidirectional"); + }); + + test(renderMode + ": _jsPlumb.connect (two Endpoints - that have been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = _jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1, uuid:srcEndpointUuid}); + var e2 = _jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1, uuid:dstEndpointUuid}); + _jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ] }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertContextSize(3); + }); + + test(renderMode + ": _jsPlumb.connect (two Endpoints - that have not been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ], source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + var e1 = _jsPlumb.getEndpoint(srcEndpointUuid); + ok(e1 != null, "endpoint with src uuid added"); + ok(e1.canvas != null); + var e2 = _jsPlumb.getEndpoint(dstEndpointUuid); + ok(e2 != null, "endpoint with target uuid added"); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.connect (two Endpoints - that have been already added - by endpoint reference)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = _jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1}); + var e2 = _jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1}); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.connect (two elements)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.connect({ source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, straight)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, bezier, no params)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Bezier" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 150, "Bezier connector chose 150 curviness"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, bezier, curviness as int)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", { curviness:200 }] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Canvas Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 200, "Bezier connector chose 200 curviness"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, bezier, curviness as named option)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", {curviness:300}] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 300, "Bezier connector chose 300 curviness"); + }); + + test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Canvas Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as strings)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:["LeftMiddle", "RightMiddle"] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.5, conn.endpoints[0].anchor.y, "source anchor y"); + equals(1, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.5, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + + test(renderMode + ": _jsPlumb.connect (two argument method in which some data is reused across connections)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"), d19 = _addDiv("d19"); + var sharedData = { connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }; + var conn = _jsPlumb.connect({ source:d16, target:d17}, sharedData); + var conn2 = _jsPlumb.connect({ source:d18, target:d19}, sharedData); + assertContextSize(6); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 2, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(conn2.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + equals(0.3, conn2.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn2.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn2.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn2.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoint test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoints test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Dot, "Dot endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Blank Endpoint specified via 'endpoint' param)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Blank" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Blank Endpoint specified via 'endpoints' param)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Blank", "Blank" ] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Dot, "Dot endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, connector test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {}); + var e17 = _jsPlumb.addEndpoint(d17, {}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {}); + var e17 = _jsPlumb.addEndpoint(d17, {}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, anchors as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = "TopCenter", a17 = "BottomCenter"; + var e16 = _jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, 0.5, "endpoint 16 is at top center");equals(e16.anchor.y, 0, "endpoint 16 is at top center"); + equals(e17.anchor.x, 0.5, "endpoint 17 is at bottom center");equals(e17.anchor.y, 1, "endpoint 17 is at bottom center"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = [0,0.5,0,-1], a17 = [1,0.0,-1,-1]; + var e16 = _jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, a16[0]);equals(e16.anchor.y, a16[1]); + equals(e17.anchor.x, a17[0]);equals(e17.anchor.y, a17[1]); + equals(e16.anchor.getOrientation()[0], a16[2]); equals(e16.anchor.getOrientation()[1], a16[3]); + equals(e17.anchor.getOrientation()[0], a17[2]); equals(e17.anchor.getOrientation()[1], a17[3]); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchor')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchor:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:["TopCenter", "BottomCenter"]}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchors')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, default endpoint, supplied dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var anchors = [ [0.25, 0, 0, -1], [1, 0.25, 1, 0], [0.75, 1, 0, 1], [0, 0.75, -1, 0] ]; + _jsPlumb.connect({source:d1, target:d2, dynamicAnchors:anchors}); // auto connect with default endpoint and provided anchors + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.detach({source:d1, target:d2}); + // this changed in 1.3.5, because auto generated endpoints are now removed by detach. so i added the test below this one + // to check that the deleteEndpointsOnDetach flag is honoured. + assertEndpointCount("d1", 0, _jsPlumb);assertEndpointCount("d2", 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, default endpoint, supplied dynamic anchors, delete on detach false)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var anchors = [ [0.25, 0, 0, -1], [1, 0.25, 1, 0], [0.75, 1, 0, 1], [0, 0.75, -1, 0] ]; + _jsPlumb.connect({ + source:d1, + target:d2, + dynamicAnchors:anchors, + deleteEndpointsOnDetach:false + }); // auto connect with default endpoint and provided anchors + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.detach({source:d1, target:d2}); + // this changed in 1.3.5, because auto generated endpoints are now removed by detach. so i added this test + // to check that the deleteEndpointsOnDetach flag is honoured. + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, supplied endpoint and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = _jsPlumb.addEndpoint(d1, endpoint); + var e2 = _jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(3); + _jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, supplied endpoints using 'source' and 'target' (this test is identical to the one above apart from the param names), and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = _jsPlumb.addEndpoint(d1, endpoint); + var e2 = _jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + _jsPlumb.connect({source:e1, target:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(3); + _jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + " detachable parameter defaults to true on _jsPlumb.connect", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), true, "connections detachable by default"); + }); + + test(renderMode + " detachable parameter set to false on _jsPlumb.connect", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, detachable:false}); + equals(c.isDetachable(), false, "connection detachable"); + }); + + test(renderMode + " setDetachable on initially detachable connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), true, "connection initially detachable"); + c.setDetachable(false); + equals(c.isDetachable(), false, "connection not detachable"); + }); + + test(renderMode + " setDetachable on initially not detachable connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, detachable:false }); + equals(c.isDetachable(), false, "connection not initially detachable"); + c.setDetachable(true); + equals(c.isDetachable(), true, "connection now detachable"); + }); + + test(renderMode + " _jsPlumb.Defaults.ConnectionsDetachable", function() { + _jsPlumb.Defaults.ConnectionsDetachable = false; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), false, "connections not detachable by default (overrode the defaults)"); + _jsPlumb.Defaults.ConnectionsDetachable = true; + }); + + + test(renderMode + ": _jsPlumb.connect (testing for connection event callback)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var connectCallback = null, detachCallback = null; + _jsPlumb.bind("jsPlumbConnection", function(params) { + connectCallback = $.extend({}, params); + }); + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + detachCallback = $.extend({}, params); + }); + _jsPlumb.connect({source:d1, target:d2}); // auto connect with default endpoint and anchor set + ok(connectCallback != null, "connect callback was made"); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.detach({source:d1, target:d2}); + assertContextSize(2); + ok(detachCallback != null, "detach callback was made"); + }); + + test(renderMode + ": _jsPlumb.connect (setting cssClass on Connector)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2,cssClass:"CSS"}); + var has = function(clazz) { + var cn = c.connector.canvas.className, + cns = cn.constructor == String ? cn : cn.baseVal; + + return cns.indexOf(clazz) != -1; + }; + ok(has("CSS"), "custom cssClass set correctly"); + ok(has(_jsPlumb.connectorClass), "basic connector class set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (overlays, long-hand version)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = {width:40,length:40,location:0.7, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"}}; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + }); + + test(renderMode + ": _jsPlumb.connect (overlays, long-hand version, IDs specified)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + equals("aLabel", connection1.overlays[0].id); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + equals("anArrow", connection1.overlays[1].id); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays)", function() { + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays + overlays specified in connect call)", function() { + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, overlays:[ + ["Label", {id:"label"}] + ]}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("label") != null, "Label overlay created from connect call"); + }); + + test(renderMode + ": _jsPlumb.connect (default connection overlays)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + }); + + test(renderMode + ": _jsPlumb.connect (default connection overlays + overlays specified in connect call)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, overlays:[ + ["Label", {id:"label"}] + ]}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("label") != null, "Label overlay created from connect call"); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays + default connection overlays)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow2" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("arrow2") != null, "Arrow overlay created from connection defaults"); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays + default connection overlays)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow2" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, overlays:[ + ["Label", {id:"label"}] + ]}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("arrow2") != null, "Arrow overlay created from connection defaults"); + ok(c.getOverlay("label") != null, "Label overlay created from connect call"); + }); + + test(renderMode + ": _jsPlumb.connect (label overlay set using 'label')", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO" + }); + + equals(c.getLabel(), "FOO", "label is set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction, with string)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2}); + + c.setLabel("FOO"); + equals(c.getLabel(), "FOO", "label is set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction, with function)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2}); + + c.setLabel(function() { return "BAR"; }); + equals(c.getLabel()(), "BAR", "label is set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction, with params object)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2}); + + c.setLabel({ + label:"BAZ", + cssClass:"CLASSY", + location:0.9 + }); + var lo = c.getLabelOverlay(); + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "BAZ", "label overlay has correct value"); + equals(lo.getLocation(), 0.9, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction with existing label set, with params object)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO", + labelLocation:0.2 + }); + + var lo = c.getLabelOverlay(); + equals(lo.getLocation(), 0.2, "label overlay has correct location"); + + c.setLabel({ + label:"BAZ", + cssClass:"CLASSY", + location:0.9 + }); + + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "BAZ", "label overlay has correct value"); + equals(lo.getLocation(), 0.9, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (getLabelOverlay, label on connect call)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO" + }), + lo = c.getLabelOverlay(); + + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "FOO", "label overlay has correct value"); + equals(lo.getLocation(), 0.5, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (getLabelOverlay, label on connect call, location set)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO", + labelLocation:0.2 + }), + lo = c.getLabelOverlay(); + + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "FOO", "label overlay has correct value"); + equals(lo.getLocation(), 0.2, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (remove single overlay by id)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + connection1.removeOverlay("aLabel"); + equals(1, connection1.overlays.length); + equals("anArrow", connection1.overlays[0].id); + }); + + test(renderMode + ": _jsPlumb.connect (remove multiple overlays by id)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + connection1.removeOverlays("aLabel", "anArrow"); + equals(0, connection1.overlays.length); + }); + + test(renderMode + ": _jsPlumb.connect (overlays, short-hand version)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var loc = { location:0.7 }; + var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label", {label:"CONNECTION 1", location:0.3}], + ["Arrow", arrowSpec, loc] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + }); + + test(renderMode + ": _jsPlumb.connect (removeAllOverlays)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var loc = { location:0.7 }; + var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label", {label:"CONNECTION 1", location:0.3, cssClass:"PPPP"}], + ["Arrow", arrowSpec, loc] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + + connection1.removeAllOverlays(); + equals(0, connection1.overlays.length); + equals(0, $(".PPPP").length); + }); + + test(renderMode + ": _jsPlumb.connect, specify arrow overlay using string identifier only", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:["Arrow"]}); + equals(jsPlumb.Overlays[renderMode].Arrow, conn.overlays[0].constructor); + }); + + test(renderMode + ": Connection.getOverlay method, existing overlay", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("arrowOverlay"); + ok(overlay != null); + }); + + test(renderMode + ": Connection.getOverlay method, non-existent overlay", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("IDONTEXIST"); + ok(overlay == null); + }); + + test(renderMode + ": Overlay.setVisible method", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("arrowOverlay"); + ok(overlay.isVisible()); + overlay.setVisible(false); + ok(!overlay.isVisible()); + overlay.setVisible(true); + ok(overlay.isVisible()); + }); + + test(renderMode + ": _jsPlumb.connect (custom label overlay, set on Defaults, return plain DOM element)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Custom",{ id:"custom", create:function(connection) { + ok(connection != null, "we were passed in a connection"); + var d = document.createElement("div"); + d.setAttribute("custom", "true"); + d.innerHTML = connection.id; + return d; + }}] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + var o = c.getOverlay("custom"); + equals(o.getElement().getAttribute("custom"), "true", "custom overlay created correctly"); + equals(o.getElement().innerHTML, c.id, "custom overlay has correct value"); + }); + + test(renderMode + ": _jsPlumb.connect (custom label overlay, set on Defaults, return selector)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Custom",{ id:"custom", create:function(connection) { + ok(connection != null, "we were passed in a connection"); + return $("
" + connection.id + "
"); + }}] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + var o = c.getOverlay("custom"); + equals(o.getElement().getAttribute("custom"), "true", "custom overlay created correctly"); + equals(o.getElement().innerHTML, c.id, "custom overlay has correct value"); + }); + + // this test is for the original detach function; it should stay working after i mess with it + // a little. + test(renderMode + ": _jsPlumb.detach (by element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + var e3 = _jsPlumb.addEndpoint(d1); + var e4 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + _jsPlumb.connect({ sourceEndpoint:e3, targetEndpoint:e4 }); // make two connections to be sure this works ;) + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionCount(e3, 1); + assertConnectionCount(e4, 1); + + _jsPlumb.detach({source:"d1", target:"d2"}); + + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionCount(e3, 0); + assertConnectionCount(e4, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + // detach is being made to operate more like connect - by taking one argument with a whole + // bunch of possible params in it. if two args are passed in it will continue working + // in the old way. + test(renderMode + ": _jsPlumb.detach (params object, using element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({source:"d1", target:"d2"}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); +/* + test(renderMode + ": _jsPlumb.detach (params object, using target only)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e1 = _jsPlumb.addEndpoint(d1, {maxConnections:2}), + e2 = _jsPlumb.addEndpoint(d2), + e3 = _jsPlumb.addEndpoint(d3); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e3 }); + assertConnectionCount(e1, 2); + assertConnectionCount(e2, 1); + assertConnectionCount(e3, 1); + _jsPlumb.detach({target:"d2"}); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 0); + assertConnectionCount(e3, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1); + }); +*/ + test(renderMode + ": _jsPlumb.detach (params object, using element objects)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({source:d1, target:d2}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.detach (source and target as endpoint UUIDs)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {uuid:"abcdefg"}); + ok(_jsPlumb.getEndpoint("abcdefg") != null, "e1 exists"); + var e2 = _jsPlumb.addEndpoint(d2, {uuid:"hijklmn"}); + ok(_jsPlumb.getEndpoint("hijklmn") != null, "e2 exists"); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({uuids:["abcdefg", "hijklmn"]}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.detach (sourceEndpoint and targetEndpoint supplied)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.makeDynamicAnchors (longhand)", function() { + var anchors = [_jsPlumb.makeAnchor([0.2, 0, 0, -1], null, _jsPlumb), _jsPlumb.makeAnchor([1, 0.2, 1, 0], null, _jsPlumb), + _jsPlumb.makeAnchor([0.8, 1, 0, 1], null, _jsPlumb), _jsPlumb.makeAnchor([0, 0.8, -1, 0], null, _jsPlumb) ]; + var dynamicAnchor = _jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); + }); + + test(renderMode + ": _jsPlumb.makeDynamicAnchors (shorthand)", function() { + var anchors = [[0.2, 0, 0, -1], [1, 0.2, 1, 0], + [0.8, 1, 0, 1], [0, 0.8, -1, 0] ]; + var dynamicAnchor = _jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); + }); + + test(renderMode + ": Connection.isVisible/setVisible", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c1 = _jsPlumb.connect({source:d1,target:d2}); + equals(true, c1.isVisible(), "Connection is visible after creation."); + c1.setVisible(false); + equals(false, c1.isVisible(), "Connection is not visible after calling setVisible(false)."); + equals($(c1.connector.canvas).css("display"), "none"); + c1.setVisible(true); + equals(true, c1.isVisible(), "Connection is visible after calling setVisible(true)."); + equals($(c1.connector.canvas).css("display"), "block"); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible basic test (no connections)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals($(e1.canvas).css("display"), "none"); + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals($(e1.canvas).css("display"), "block"); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should track changes in the source, because it has only this connection.)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + var c1 = _jsPlumb.connect({source:e1, target:e2}); + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals(false, e2.isVisible(), "other Endpoint is not visible either."); + equals(false, c1.isVisible(), "connection between the two is not visible either."); + + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals(true, e2.isVisible(), "other Endpoint is visible too"); + equals(true, c1.isVisible(), "connection between the two is visible too."); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should not track changes in the source, because it has another connection.)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2, { maxConnections:2 }), e3 = _jsPlumb.addEndpoint(d3); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + var c1 = _jsPlumb.connect({source:e1, target:e2}); + var c2 = _jsPlumb.connect({source:e2, target:e3}); + + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals(true, e2.isVisible(), "other Endpoint should still be visible."); + equals(true, e3.isVisible(), "third Endpoint should still be visible."); + equals(false, c1.isVisible(), "connection between the two is not visible either."); + equals(true, c2.isVisible(), "other connection is visible."); + + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals(true, e2.isVisible(), "other Endpoint is visible too"); + equals(true, c1.isVisible(), "connection between the two is visible too."); + equals(true, c2.isVisible(), "other connection is visible."); + }); + + // tests of the functionality that allows a user to specify that they want elements appended to the document body + test(renderMode + " _jsPlumb.Defaults.Container, specified with a selector", function() { + _jsPlumb.Defaults.Container = $("body"); + equals($("#container")[0].childNodes.length, 0, "container has no nodes"); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals($("#container")[0].childNodes.length, 2, "container has two div elements"); // the divs we added have been added to the 'container' div. + // but we have told _jsPlumb to add its canvas to the body, so this connect call should not add another few elements to the container: + _jsPlumb.connect({source:d1, target:d2}); + equals($("#container")[0].childNodes.length, 2, "container still has two div elements"); + }); + + // tests of the functionality that allows a user to specify that they want elements appended to some specific container. + test(renderMode + " _jsPlumb.Defaults.Container, specified with DOM element", function() { + _jsPlumb.Defaults.Container = document.getElementsByTagName("body")[0]; + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length, "two divs added to the container"); // the divs we added have been added to the 'container' div. + // but we have told _jsPlumb to add its canvas to the body, so this connect call should not add another few elements to the container: + var bodyElementCount = $("body")[0].childNodes.length; + _jsPlumb.connect({source:d1, target:d2}); + equals(2, $("#container")[0].childNodes.length, "still only two children in container; elements were added to the body by _jsPlumb"); + // test to see if 3 elements have been added + equals(bodyElementCount + 3, $("body")[0].childNodes.length, "3 new elements added to the document body"); + }); + + test(renderMode + " container specified to connect call, with a selector", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length); // the divs we added have been added to the 'container' div. + var bodyElementCount = $("body")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to the body, so this connect call should not add another few elements to the container: + _jsPlumb.connect({source:d1, target:d2, container:$("body")}); + equals(2, $("#container")[0].childNodes.length); + equals(bodyElementCount + 3, $("body")[0].childNodes.length, "3 new elements added to the document body"); + }); + + test(renderMode + " container specified to connect call, with a string ID", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + equals(3, $("#container")[0].childNodes.length, "container has divs we added"); // the divs we added have been added to the 'container' div. + var d3ElementCount = $("#d3")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to "d3", so this connect call should not add another few elements to the container: + _jsPlumb.connect({source:d1, target:d2, container:"d3"}); + equals(3, $("#container")[0].childNodes.length, "container still has only the divs we added"); + equals(d3ElementCount + 3, $("#d3")[0].childNodes.length, "3 new elements added to div d3"); + }); + + test(renderMode + " container specified to addEndpoint call, with a selector", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length); // the divs we added have been added to the 'container' div. + var bodyElementCount = $("body")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to the body, so this connect call should not add another few elements to the container: + _jsPlumb.addEndpoint(d1, {container:$("body")}); + equals(2, $("#container")[0].childNodes.length); + equals(bodyElementCount + 1, $("body")[0].childNodes.length, "1 new element added to the document body"); + }); + + test(renderMode + " container specified to addEndpoint call, with a string ID", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + equals(3, $("#container")[0].childNodes.length, "container has divs we added"); // the divs we added have been added to the 'container' div. + var d3ElementCount = $("#d3")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to "d3", so this connect call should not add another few elements to the container: + _jsPlumb.addEndpoint(d1, { container:"d3" }); + equals(3, $("#container")[0].childNodes.length, "container still has only the divs we added"); + equals(d3ElementCount + 1, $("#d3")[0].childNodes.length, "1 new element added to div d3"); + }); + + test(renderMode + " detachable defaults to true when connection made between two endpoints", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2), + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection not detachable"); + }); + + test(renderMode + " connection detachable when target endpoint has connectionsDetachable set to true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2, {connectionsDetachable:true}), + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection detachable because connectionsDetachable was set on target endpoint"); + }); + + test(renderMode + " connection detachable when source endpoint has connectionsDetachable set to true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {connectionsDetachable:true}), e2 = _jsPlumb.addEndpoint(d2), + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection detachable because connectionsDetachable was set on source endpoint"); + }); + + test(renderMode + " Connector has 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.connector.type, "Bezier", "Bezier connector has type set"); + + var c2 = _jsPlumb.connect({source:d1, target:d2, connector:"Straight"}); + equals(c2.connector.type, "Straight", "Straight connector has type set"); + + var c3 = _jsPlumb.connect({source:d1, target:d2, connector:"Flowchart"}); + equals(c3.connector.type, "Flowchart", "Flowchart connector has type set"); + }); + + test(renderMode + " Endpoints have 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.endpoints[0].type, "Dot", "Dot endpoint has type set"); + + var c2 = _jsPlumb.connect({source:d1, target:d2, endpoints:["Rectangle", "Blank"]}); + equals(c2.endpoints[1].type, "Blank", "Blank endpoint has type set"); + equals(c2.endpoints[0].type, "Rectangle", "Rectangle endpoint has type set"); + }); + + test(renderMode + " Overlays have 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = _jsPlumb.connect({ + source:d1, + target:d2, + overlays:[ "Arrow", "Label", "PlainArrow", "Diamond" ] + }); + equals(c.overlays[0].type, "Arrow", "Arrow overlay has type set"); + equals(c.overlays[1].type, "Label", "Label overlay has type set"); + equals(c.overlays[2].type, "PlainArrow", "PlainArrow overlay has type set"); + equals(c.overlays[3].type, "Diamond", "Diamond overlay has type set"); + }); + + test(renderMode + " _jsPlumb.hide, original one-arg version", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + _jsPlumb.hide(d1); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(true, e1.isVisible(), "endpoint 1 is still visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + _jsPlumb.show(d1); + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + }); + + test(renderMode + " _jsPlumb.hide, two-arg version, endpoints should also be hidden", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + _jsPlumb.show(d1); // now show d1, but do not alter the endpoints. e1 should still be hidden + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + test(renderMode + " _jsPlumb.show, two-arg version, endpoints should become visible", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + _jsPlumb.show(d1, true); // now show d1, and alter the endpoints. e1 should be visible. + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(true, e1.isVisible(), "endpoint 1 is visible again."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + test(renderMode + " _jsPlumb.show, two-arg version, endpoints should become visible, but not all connections, because some other endpoints are not visible.", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e11 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + e3 = _jsPlumb.addEndpoint(d3, e), + c1 = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e11, target:e3}); + + // we now have d1 connected to both d3 and d2. we'll hide d1, and everything on d1 should be hidden. + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "connection 1 is no longer visible."); + equals(false, c2.isVisible(), "connection 2 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(false, e11.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + equals(true, e3.isVisible(), "endpoint 3 is still visible."); + + // now, we will also hide d3. making d1 visible again should NOT result in c2 becoming visible, because the other endpoint + // for c2 is e3, which is not visible. + _jsPlumb.hide(d3, true); + equals(false, e3.isVisible(), "endpoint 3 is no longer visible."); + + _jsPlumb.show(d1, true); // now show d1, and alter the endpoints. e1 should be visible, c1 should be visible, but c2 should not. + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(false, c2.isVisible(), "Connection 2 is not visible."); + equals(true, e1.isVisible(), "endpoint 1 is visible again."); + equals(true, e11.isVisible(), "endpoint 11 is visible again."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + equals(false, e3.isVisible(), "endpoint 3 is still not visible."); + }); + + /* + test(renderMode + " _jsPlumb.hide, two-arg version, endpoints should also be hidden", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + /** + * test for issue 132: label leaves its element in the DOM after it has been + * removed from a connection. + */ + test(renderMode + " label cleans itself up properly", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label", cssClass:"foo"}] + ]}); + ok($(".foo").length == 1, "label element exists in DOM"); + c.removeOverlay("label"); + ok($(".foo").length == 0, "label element does not exist in DOM"); + }); + + test(renderMode + " arrow cleans itself up properly", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Arrow", {id:"arrow"}] + ]}); + ok(c.getOverlay("arrow") != null, "arrow overlay exists"); + c.removeOverlay("arrow"); + ok(c.getOverlay("arrow") == null, "arrow overlay has been removed"); + }); + + test(renderMode + " label overlay getElement function", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label"}] + ]}); + ok(c.getOverlay("label").getElement() != null, "label overlay exposes element via getElement method"); + }); + + test(renderMode + " label overlay provides getLabel and setLabel methods", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label", label:"foo"}] + ]}); + var o = c.getOverlay("label"), e = o.getElement(); + ok(e.innerHTML == "foo", "label text is set to original value"); + o.setLabel("baz"); + equals(e.innerHTML, "baz", "label text is set to new value 'baz'"); + equals(o.getLabel(), "baz", "getLabel function works correctly with String"); + // now try functions + var aFunction = function() { return "aFunction"; }; + o.setLabel(aFunction); + equals(e.innerHTML, "aFunction", "label text is set to new value from Function"); + equals(o.getLabel(), aFunction, "getLabel function works correctly with Function"); + }); + + test(renderMode + " parameters object works for Endpoint", function() { + var d1 = _addDiv("d1"), + f = function() { alert("FOO!"); }, + e = _jsPlumb.addEndpoint(d1, { + isSource:true, + parameters:{ + "string":"param1", + "int":4, + "function":f + } + }); + ok(e.getParameter("string") === "param1", "getParameter(String) works correctly"); + ok(e.getParameter("int") === 4, "getParameter(int) works correctly"); + ok(e.getParameter("function") == f, "getParameter(Function) works correctly"); + }); + + test(renderMode + " parameters object works for Connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + f = function() { alert("FOO!"); }; + var c = _jsPlumb.connect({ + source:d1, + target:d2, + parameters:{ + "string":"param1", + "int":4, + "function":f + } + }); + ok(c.getParameter("string") === "param1", "getParameter(String) works correctly"); + ok(c.getParameter("int") === 4, "getParameter(int) works correctly"); + ok(c.getParameter("function") == f, "getParameter(Function) works correctly"); + }); + + test(renderMode + " parameters set on Endpoints and Connections are all merged, and merged correctly at that.", function() { + var d1 = _addDiv("d1"), + d2 = _addDiv("d2"), + e = _jsPlumb.addEndpoint(d1, { + isSource:true, + parameters:{ + "string":"sourceEndpoint", + "int":0, + "function":function() { return "sourceEndpoint"; } + } + }), + e2 = _jsPlumb.addEndpoint(d2, { + isTarget:true, + parameters:{ + "int":1, + "function":function() { return "targetEndpoint"; } + } + }), + c = _jsPlumb.connect({source:e, target:e2, parameters:{ + "function":function() { return "connection"; } + }}); + + ok(c.getParameter("string") === "sourceEndpoint", "getParameter(String) works correctly"); + ok(c.getParameter("int") === 1, "getParameter(int) works correctly"); + ok(c.getParameter("function")() == "connection", "getParameter(Function) works correctly"); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers standard connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2}); + equals(_jsPlumb.anchorManager.getConnectionsFor("d1").length, 1); + equals(_jsPlumb.anchorManager.getEndpointsFor("d1").length, 1); + equals(_jsPlumb.anchorManager.getConnectionsFor("d2").length, 1); + equals(_jsPlumb.anchorManager.getEndpointsFor("d2").length, 1); + var c2 = _jsPlumb.connect({source:d1, target:d2}); + equals(_jsPlumb.anchorManager.getConnectionsFor("d1").length, 2); + equals(_jsPlumb.anchorManager.getConnectionsFor("d2").length, 2); + equals(_jsPlumb.anchorManager.getEndpointsFor("d1").length, 2); + equals(_jsPlumb.anchorManager.getEndpointsFor("d2").length, 2); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers dynamic anchor connection, and removes it.", function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var c = _jsPlumb.connect({source:d3, target:d4, anchors:["AutoDefault", "AutoDefault"]}); + + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 1); + + var c2 = _jsPlumb.connect({source:d3, target:d4}); + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 2); + equals(_jsPlumb.anchorManager.getConnectionsFor("d4").length, 2); + + equals(_jsPlumb.anchorManager.getEndpointsFor("d3").length, 2); + _jsPlumb.detach(c); + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 1); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers continuous anchor connection, and removes it.", function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var c = _jsPlumb.connect({source:d3, target:d4, anchors:["Continuous", "Continuous"]}); + + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 1); + equals(_jsPlumb.anchorManager.getConnectionsFor("d4").length, 1); + + _jsPlumb.detach(c); + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 0); + equals(_jsPlumb.anchorManager.getConnectionsFor("d4").length, 0); + + _jsPlumb.reset(); + equals(_jsPlumb.anchorManager.getEndpointsFor("d4").length, 0); + }); + +// ------------- utility functions - math stuff, mostly -------------------------- + + var tolerance = 0.00000005, withinTolerance = function(v1, v2, msg) { + if (Math.abs(v1 - v2) < tolerance) ok(true, msg + "; expected " + v1 + " and got it"); + else { + ok(false, msg + "; expected " + v1 + " got " + v2); + } + }; + test(renderMode + " jsPlumbUtil.gradient, segment 1", function() { + var p1 = [2,2], p2 = [3,1], m = jsPlumbUtil.gradient(p1,p2); + equals(m, -1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.normal, segment 1", function() { + var p1 = [2,2], p2 = [3,1], m = jsPlumbUtil.normal(p1,p2); + equals(m, 1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.gradient, segment 2", function() { + var p1 = [2,2], p2 = [3,3], m = jsPlumbUtil.gradient(p1,p2); + equals(m, 1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.normal, segment 2", function() { + var p1 = [2,2], p2 = [3,3], m = jsPlumbUtil.normal(p1,p2); + equals(m, -1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.gradient, segment 3", function() { + var p1 = [2,2], p2 = [1,3], m = jsPlumbUtil.gradient(p1,p2); + equals(m, -1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.normal, segment 3", function() { + var p1 = [2,2], p2 = [1,3], m = jsPlumbUtil.normal(p1,p2); + equals(m, 1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.gradient, segment 4", function() { + var p1 = [2,2], p2 = [1,1], m = jsPlumbUtil.gradient(p1,p2); + equals(m, 1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.normal, segment 4", function() { + var p1 = [2,2], p2 = [1,1], m = jsPlumbUtil.normal(p1,p2); + equals(m, -1, "normal calculated correctly for simple case"); + }); + test(renderMode + "jsPlumbUtil.pointOnLine, segment 1", function() { + var p1 = {x:2,y:2}, p2={x:3, y:1}, + target = jsPlumbUtil.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumbUtil.pointOnLine, segment 2", function() { + var p1 = {x:2,y:2}, p2={x:3, y:3}, + target = jsPlumbUtil.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumbUtil.pointOnLine, segment 3", function() { + var p1 = {x:2,y:2}, p2={x:1, y:3}, + target = jsPlumbUtil.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumbUtil.pointOnLine, segment 4", function() { + var p1 = {x:2,y:2}, p2={x:1, y:1}, + target = jsPlumbUtil.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumbUtil.perpendicularLineTo, segment 1", function() { + var p1 = {x:2, y:2}, p2={x:3, y:1}, m = jsPlumbUtil.gradient(p1, p2), + l = jsPlumbUtil.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(4, l[0].x, "point 1 x is correct"); + withinTolerance(2, l[0].y, "point 1 y is correct"); + + withinTolerance(2, l[1].x, "point 2 x is correct"); + withinTolerance(0, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumbUtil.perpendicularLineTo, segment 2", function() { + var p1 = {x:2, y:2}, p2={x:3, y:3}, m = jsPlumbUtil.gradient(p1, p2), + l = jsPlumbUtil.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(4, l[0].x, "point 1 x is correct"); + withinTolerance(2, l[0].y, "point 1 y is correct"); + + withinTolerance(2, l[1].x, "point 2 x is correct"); + withinTolerance(4, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumbUtil.perpendicularLineTo, segment 3", function() { + var p1 = {x:2, y:2}, p2={x:1, y:3}, m = jsPlumbUtil.gradient(p1, p2), + l = jsPlumbUtil.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(2, l[0].x, "point 1 x is correct"); + withinTolerance(4, l[0].y, "point 1 y is correct"); + + withinTolerance(0, l[1].x, "point 2 x is correct"); + withinTolerance(2, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumbUtil.perpendicularLineTo, segment 4", function() { + var p1 = {x:2, y:2}, p2={x:1, y:1}, m = jsPlumbUtil.gradient(p1, p2), + l = jsPlumbUtil.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(2, l[0].x, "point 1 x is correct"); + withinTolerance(0, l[0].y, "point 1 y is correct"); + + withinTolerance(0, l[1].x, "point 2 x is correct"); + withinTolerance(2, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumbUtil.intersects, with intersection", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 3, y:4, w:3, h:3}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, with no intersection", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 13, y:4, w:3, h:3}; + + ok(!jsPlumbUtil.intersects(r1, r2), "r1 and r2 do not intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, with intersection, equal Y", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 1, y:2, w:3, h:6}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, with intersection, equal X", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 2, y:1, w:4, h:6}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, identical rectangles", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 2, y:2, w:4, h:6}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, corners touch (intersection)", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 6, y:8, w:3, h:3}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, one rectangle contained within the other", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 0, y:0, w:23, h:23}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "setImage on Endpoint", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + originalUrl = "../../img/endpointTest1.png", + e = { + endpoint:[ "Image", { src:originalUrl } ] + }, + ep = _jsPlumb.addEndpoint(d1, e); + }); + test(renderMode + "setImage on Endpoint, with supplied onload", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { + endpoint:[ "Image", { + src:"../../img/endpointTest1.png", + onload:function(imgEp) { + equals("../../img/endpointTest1.png", imgEp.img.src); + equals(ep.endpoint.canvas.getAttribute("src"), imgEp.img.src); + } + } ] + }, + ep = _jsPlumb.addEndpoint(d1, e); + ep.setImage("../../img/littledot.png", function(imgEp) { + equals("../../img/littledot.png", imgEp.img.src); + equals(ep.endpoint.canvas.getAttribute("src"), imgEp.img.src); + }); + }); + + test(renderMode + "attach endpoint to table when desired element was td", function() { + var table = document.createElement("table"), + tr = document.createElement("tr"), + td = document.createElement("td"); + table.appendChild(tr); tr.appendChild(td); + document.body.appendChild(table); + var ep = _jsPlumb.addEndpoint(td); + equals(ep.canvas.parentNode.tagName.toLowerCase(), "table"); + }); + +// issue 190 - regressions with getInstance. these tests ensure that generated ids are unique across +// instances. + + test(renderMode + " id clashes between instances", function() { + var d1 = document.createElement("div"), + d2 = document.createElement("div"), + _jsp2 = jsPlumb.getInstance(); + + document.body.appendChild(d1); + document.body.appendChild(d2); + + _jsPlumb.addEndpoint(d1); + _jsp2.addEndpoint(d2); + + var id1 = d1.getAttribute("id"), + id2 = d2.getAttribute("id"); + + var idx = id1.indexOf("_"), idx2 = id1.lastIndexOf("_"), v1 = id1.substring(idx, idx2); + var idx3 = id2.indexOf("_"), idx4 = id2.lastIndexOf("_"), v2 = id2.substring(idx3, idx4); + + ok (v1 != v2, "instance versions are different : " + v1 + " : " + v2); + }); + + test(renderMode + " id clashes between instances", function() { + var d1 = document.createElement("div"), + d2 = document.createElement("div"), + _jsp2 = jsPlumb.getInstance(); + + document.body.appendChild(d1); + document.body.appendChild(d2); + + _jsPlumb.addEndpoint(d1); + _jsPlumb.addEndpoint(d2); + + var id1 = d1.getAttribute("id"), + id2 = d2.getAttribute("id"); + + var idx = id1.indexOf("_"), idx2 = id1.lastIndexOf("_"), v1 = id1.substring(idx, idx2); + var idx3 = id2.indexOf("_"), idx4 = id2.lastIndexOf("_"), v2 = id2.substring(idx3, idx4); + + ok (v1 == v2, "instance versions are the same : " + v1 + " : " + v2); + }); + + test(renderMode + " importDefaults", function() { + _jsPlumb.Defaults.Anchors = ["LeftMiddle", "RightMiddle"]; + var d1 = _addDiv("d1"), + d2 = _addDiv(d2), + c = _jsPlumb.connect({source:d1, target:d2}), + e = c.endpoints[0]; + + equals(e.anchor.x, 0, "left middle anchor"); + equals(e.anchor.y, 0.5, "left middle anchor"); + + _jsPlumb.importDefaults({ + Anchors:["TopLeft", "TopRight"] + }); + + var conn = _jsPlumb.connect({source:d1, target:d2}), + e1 = conn.endpoints[0], e2 = conn.endpoints[1]; + + equals(e1.anchor.x, 0, "top leftanchor"); + equals(e2.anchor.y, 0, "top left anchor"); + equals(e2.anchor.x, 1, "top right anchor"); + equals(e2.anchor.y, 0, "top right anchor"); + + }); + + test(renderMode + " restoreDefaults", function() { + _jsPlumb.importDefaults({ + Anchors:["TopLeft", "TopRight"] + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), conn = _jsPlumb.connect({source:d1, target:d2}), + e1 = conn.endpoints[0], e2 = conn.endpoints[1]; + + equals(e1.anchor.x, 0, "top leftanchor"); + equals(e2.anchor.y, 0, "top left anchor"); + equals(e2.anchor.x, 1, "top right anchor"); + equals(e2.anchor.y, 0, "top right anchor"); + + _jsPlumb.restoreDefaults(); + + var conn2 = _jsPlumb.connect({source:d1, target:d2}), + e3 = conn2.endpoints[0], e4 = conn2.endpoints[1]; + + equals(e3.anchor.x, 0.5, "bottom center anchor"); + equals(e3.anchor.y, 1, "bottom center anchor"); + equals(e4.anchor.x, 0.5, "bottom center anchor"); + equals(e4.anchor.y, 1, "bottom center anchor"); + }); + +// setId function + + test(renderMode + " setId, taking two strings, only default scope", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId("d1", "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a selector and a string, only default scope", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1"), "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a DOM element and a string, only default scope", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1")[0], "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking two strings, mix of scopes", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2, scope:"FOO"}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId("d1", "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a selector and a string, mix of scopes", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2, scope:"FOO"}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1"), "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a DOM element and a string, mix of scopes", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2, scope:"FOO"}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1")[0], "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setIdChanged, ", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + $("#d1").attr("id", "d3"); + + _jsPlumb.setIdChanged("d1", "d3"); + + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " endpoint hide/show should hide/show overlays", function() { + _addDiv("d1"); + var e1 = _jsPlumb.addEndpoint("d1", { + overlays:[ + [ "Label", { id:"label", label:"foo" } ] + ] + }), + o = e1.getOverlay("label"); + + ok(o.isVisible(), "overlay is initially visible"); + _jsPlumb.hide("d1", true); + ok(!o.isVisible(), "overlay is no longer visible"); + }); + + test(renderMode + " connection hide/show should hide/show overlays", function() { + _addDiv("d1");_addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", + overlays:[ + [ "Label", { id:"label", label:"foo" } ] + ] + }), + o = c.getOverlay("label"); + + ok(o.isVisible(), "overlay is initially visible"); + _jsPlumb.hide("d1", true); + ok(!o.isVisible(), "overlay is no longer visible"); + }); + + test(renderMode + " select, basic test", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2"}), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 1, "one connection selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; dont filter on scope.", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 2, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scope", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + s = _jsPlumb.select({source:"d1", scope:"FOO"}); + + equals(s.length, 1, "one connection selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scopes", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + c3 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAZ"}) + s = _jsPlumb.select({source:"d1", scope:["FOO","BAR"]}); + + equals(s.length, 2, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; scope but no scope filter; single source id", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + c3 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAZ"}), + c4 = _jsPlumb.connect({source:"d2", target:"d1", scope:"BOZ"}), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 3, "three connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scopes; single source id", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + c3 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAZ"}), + c4 = _jsPlumb.connect({source:"d2", target:"d1", scope:"BOZ"}), + s = _jsPlumb.select({source:"d1", scope:["FOO","BAR", "BOZ"]}); + + equals(s.length, 2, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scope; dont supply sourceid", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + s = _jsPlumb.select({ scope:"FOO" }); + + equals(s.length, 1, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scope; dont supply sourceid", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d2", target:"d1", scope:"BAR"}), + s = _jsPlumb.select({ scope:"FOO" }); + + equals(s.length, 1, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, two connections, with overlays", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({ + source:"d1", + target:"d2", + overlays:[ + ["Label", {id:"l"}] + ] + }), + c2 = _jsPlumb.connect({ + source:"d1", + target:"d2", + overlays:[ + ["Label", {id:"l"}] + ] + }), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 2, "two connections selected"); + ok(s.get(0).getOverlay("l") != null, "connection has overlay"); + ok(s.get(1).getOverlay("l") != null, "connection has overlay"); + }); + + test(renderMode + " select, chaining with setHover and hideOverlay", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({ + source:"d1", + target:"d2", + overlays:[ + ["Label", {id:"l"}] + ] + }); + s = _jsPlumb.select({source:"d1"}); + + s.setHover(false).hideOverlay("l"); + + ok(!(s.get(0).isHover()), "connection is not hover"); + ok(!(s.get(0).getOverlay("l").isVisible()), "overlay is not visible"); + }); + + test(renderMode + " select, .each function", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10) + }); + } + + var s = _jsPlumb.select(); + equals(s.length, 5, "there are five connections"); + + var t = ""; + s.each(function(connection) { + t += "f"; + }); + equals("fffff", t, ".each is working"); + }); + + test(renderMode + " select, multiple connections + chaining", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + overlays:[ + ["Arrow", {location:0.3}], + ["Arrow", {location:0.7}] + ] + }); + } + + var s = _jsPlumb.select().removeAllOverlays().setParameter("foo", "bar").setHover(false).setLabel("baz"); + equals(s.length, 5, "there are five connections"); + + for (var j = 0; j < 5; j++) { + equals(s.get(j).getOverlays().length, 1, "one overlay: the label"); + equals(s.get(j).getParameter("foo"), "bar", "parameter foo has value 'bar'"); + ok(!(s.get(j).isHover()), "hover is set to false"); + equals(s.get(j).getLabel(), "baz", "label is set to 'baz'"); + } + }); + + test(renderMode + " select, simple getter", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + label:"FOO" + }); + } + + var lbls = _jsPlumb.select().getLabel(); + equals(lbls.length, 5, "there are five labels"); + + for (var j = 0; j < 5; j++) { + equals(lbls[j][0], "FOO", "label has value 'FOO'"); + } + }); + + test(renderMode + " select, getter + chaining", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + label:"FOO" + }); + } + + var params = _jsPlumb.select().removeAllOverlays().setParameter("foo", "bar").getParameter("foo"); + equals(params.length, 5, "there are five params"); + + for (var j = 0; j < 5; j++) { + equals(params[j][0], "bar", "parameter has value 'bar'"); + } + }); + + + test(renderMode + " select, detach method", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + label:"FOO" + }); + } + + var params = _jsPlumb.select().detach(); + + equals(_jsPlumb.select().length, 0, "there are no connections"); + }); + + test(renderMode + " select, repaint method", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + label:"FOO" + }); + } + + var len = _jsPlumb.select().repaint().length; + + equals(len, 5, "there are five connections"); + }); + + + // selectEndpoints + test(renderMode + " selectEndpoints, basic tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1), + e2 = _jsPlumb.addEndpoint(d1); + + equals(_jsPlumb.selectEndpoints().length, 2, "there are two endpoints"); + equals(_jsPlumb.selectEndpoints({element:"d1"}).length, 2, "there are two endpoints on d1"); + equals(_jsPlumb.selectEndpoints({element:"d2"}).length, 0, "there are 0 endpoints on d2"); + + equals(_jsPlumb.selectEndpoints({source:"d1"}).length, 0, "there are zero source endpoints on d1"); + equals(_jsPlumb.selectEndpoints({target:"d1"}).length, 0, "there are zero target endpoints on d1"); + equals(_jsPlumb.selectEndpoints({source:"d2"}).length, 0, "there are zero source endpoints on d2"); + equals(_jsPlumb.selectEndpoints({target:"d2"}).length, 0, "there are zero target endpoints on d2"); + + equals(_jsPlumb.selectEndpoints({source:"d1", scope:"FOO"}).length, 0, "there are zero source endpoints on d1 with scope FOO"); + + _jsPlumb.addEndpoint("d2", { scope:"FOO", isSource:true }); + equals(_jsPlumb.selectEndpoints({source:"d2", scope:"FOO"}).length, 1, "there is one source endpoint on d2 with scope FOO"); + + equals(_jsPlumb.selectEndpoints({element:["d2", "d1"]}).length, 3, "there are three endpoints between d2 and d1"); + }); + + test(renderMode + " selectEndpoints, basic tests, various input argument formats", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1), + e2 = _jsPlumb.addEndpoint(d1); + + equals(_jsPlumb.selectEndpoints({element:"d1"}).length, 2, "using id, there are two endpoints on d1"); + equals(_jsPlumb.selectEndpoints({element:d1}).length, 2, "using dom element, there are two endpoints on d1"); + equals(_jsPlumb.selectEndpoints({element:$("#d1")}).length, 2, "using selector, there are two endpoints on d1"); + equals(_jsPlumb.selectEndpoints({element:$(d1)}).length, 2, "using selector with dom element, there are two endpoints on d1"); + + }); + + test(renderMode + " selectEndpoints, basic tests, scope", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {scope:"FOO"}), + e2 = _jsPlumb.addEndpoint(d1); + + equals(_jsPlumb.selectEndpoints({element:"d1"}).length, 2, "using id, there are two endpoints on d1"); + equals(_jsPlumb.selectEndpoints({element:"d1", scope:"FOO"}).length, 1, "using id, there is one endpoint on d1 with scope 'FOO'"); + _jsPlumb.addEndpoint(d1, {scope:"BAR"}), + equals(_jsPlumb.selectEndpoints({element:"d1", scope:"FOO"}).length, 1, "using id, there is one endpoint on d1 with scope 'BAR'"); + equals(_jsPlumb.selectEndpoints({element:"d1", scope:["BAR", "FOO"]}).length, 2, "using id, there are two endpoints on d1 with scope 'BAR' or 'FOO'"); + }); + + test(renderMode + " selectEndpoints, isSource tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true}), + e2 = _jsPlumb.addEndpoint(d1), + e3 = _jsPlumb.addEndpoint(d2, {isSource:true}); + + equals(_jsPlumb.selectEndpoints({source:"d1"}).length, 1, "there is one source endpoint on d1"); + equals(_jsPlumb.selectEndpoints({target:"d1"}).length, 0, "there are zero target endpoints on d1"); + + equals(_jsPlumb.selectEndpoints({source:"d2"}).length, 1, "there is one source endpoint on d2"); + + equals(_jsPlumb.selectEndpoints({source:["d2", "d1"]}).length, 2, "there are two source endpoints between d1 and d2"); + }); + + test(renderMode + " selectEndpoints, isTarget tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isTarget:true}), + e2 = _jsPlumb.addEndpoint(d1), + e3 = _jsPlumb.addEndpoint(d2, {isTarget:true}); + + equals(_jsPlumb.selectEndpoints({target:"d1"}).length, 1, "there is one target endpoint on d1"); + equals(_jsPlumb.selectEndpoints({source:"d1"}).length, 0, "there are zero source endpoints on d1"); + + equals(_jsPlumb.selectEndpoints({target:"d2"}).length, 1, "there is one target endpoint on d2"); + + equals(_jsPlumb.selectEndpoints({target:["d2", "d1"]}).length, 2, "there are two target endpoints between d1 and d2"); + }); + + test(renderMode + " selectEndpoints, isSource + isTarget tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true}), + e2 = _jsPlumb.addEndpoint(d1), + e3 = _jsPlumb.addEndpoint(d1, {isSource:true}), + e4 = _jsPlumb.addEndpoint(d1, {isTarget:true}); + + equals(_jsPlumb.selectEndpoints({source:"d1"}).length, 2, "there are two source endpoints on d1"); + equals(_jsPlumb.selectEndpoints({target:"d1"}).length, 2, "there are two target endpoints on d1"); + + equals(_jsPlumb.selectEndpoints({target:"d1", source:"d1"}).length, 1, "there is one source and target endpoint on d1"); + + equals(_jsPlumb.selectEndpoints({element:"d1"}).length, 4, "there are four endpoints on d1"); + + }); + + test(renderMode + " selectEndpoints, delete endpoints", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true}); + + equals(_jsPlumb.selectEndpoints({element:"d1"}).length, 1, "there is one endpoint on d1"); + _jsPlumb.selectEndpoints({source:"d1"}).delete(); + equals(_jsPlumb.selectEndpoints({element:"d1"}).length, 0, "there are zero endpoints on d1"); + }); + + test(renderMode + " selectEndpoints, detach connections", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true}), + e2 = _jsPlumb.addEndpoint(d2, {isSource:true, isTarget:true}); + + _jsPlumb.connect({source:e1, target:e2}); + + equals(e1.connections.length, 1, "there is one connection on d1's endpoint"); + equals(e2.connections.length, 1, "there is one connection on d2's endpoint"); + + _jsPlumb.selectEndpoints({source:"d1"}).detachAll(); + + equals(e1.connections.length, 0, "there are zero connections on d1's endpoint"); + equals(e2.connections.length, 0, "there are zero connections on d2's endpoint"); + }); + + test(renderMode + " selectEndpoints, hover tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true}); + + equals(e1.isHover(), false, "hover not set"); + _jsPlumb.selectEndpoints({source:"d1"}).setHover(true); + equals(e1.isHover(), true, "hover set"); + _jsPlumb.selectEndpoints({source:"d1"}).setHover(false); + equals(e1.isHover(), false, "hover no longer set"); + }); + + test(renderMode + " selectEndpoints, setEnabled tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true}); + + equals(e1.isEnabled(), true, "endpoint is enabled"); + _jsPlumb.selectEndpoints({source:"d1"}).setEnabled(false); + equals(e1.isEnabled(), false, "endpoint not enabled"); + }); + + test(renderMode + " selectEndpoints, setEnabled tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true}); + + equals(e1.isEnabled(), true, "endpoint is enabled"); + var e = _jsPlumb.selectEndpoints({source:"d1"}).isEnabled(); + equals(e[0][0], true, "endpoint enabled"); + _jsPlumb.selectEndpoints({source:"d1"}).setEnabled(false); + e = _jsPlumb.selectEndpoints({source:"d1"}).isEnabled(); + equals(e[0][0], false, "endpoint not enabled"); + }); + +// setPaintStyle/getPaintStyle tests + + test(renderMode + " setPaintStyle", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), c = _jsPlumb.connect({source:d1, target:d2}); + c.setPaintStyle({strokeStyle:"FOO", lineWidth:999}); + equals(c.paintStyleInUse.strokeStyle, "FOO", "strokeStyle was set"); + equals(c.paintStyleInUse.lineWidth, 999, "lineWidth was set"); + + c.setHoverPaintStyle({strokeStyle:"BAZ", lineWidth:444}); + c.setHover(true); + equals(c.paintStyleInUse.strokeStyle, "BAZ", "strokeStyle was set"); + equals(c.paintStyleInUse.lineWidth, 444, "lineWidth was set"); + + equals(c.getPaintStyle().strokeStyle, "FOO", "getPaintStyle returns correct value"); + equals(c.getHoverPaintStyle().strokeStyle, "BAZ", "getHoverPaintStyle returns correct value"); + }); + +// ******************* getEndpoints ************************************************ + + test(renderMode + " getEndpoints", function() { + _addDiv("d1");_addDiv("d2"); + + _jsPlumb.addEndpoint("d1"); + _jsPlumb.addEndpoint("d2"); + _jsPlumb.addEndpoint("d1"); + + var e = _jsPlumb.getEndpoints("d1"), + e2 = _jsPlumb.getEndpoints("d2"); + equals(e.length, 2, "two endpoints on d1"); + equals(e2.length, 1, "one endpoint on d2"); + }); + +// connection type tests - types, type extension, set types, get types etc. + test(renderMode + " set connection type on existing connection", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" } + }; + + _jsPlumb.registerConnectionType("basic", basicType); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("basic"); + equals(c.getPaintStyle().lineWidth, 4, "paintStyle lineWidth is 4"); + equals(c.getPaintStyle().strokeStyle, "yellow", "paintStyle strokeStyle is yellow"); + equals(c.getHoverPaintStyle().strokeStyle, "blue", "paintStyle strokeStyle is yellow"); + equals(c.getHoverPaintStyle().lineWidth, 4, "hoverPaintStyle linewidth is 6"); + }); + + test(renderMode + " set connection type on existing connection then change type", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" } + }; + var otherType = { + connector:"Bezier", + paintStyle:{ strokeStyle:"red", lineWidth:14 }, + hoverPaintStyle:{ strokeStyle:"green" } + }; + + _jsPlumb.registerConnectionType("basic", basicType); + _jsPlumb.registerConnectionType("other", otherType); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("basic"); + equals(c.getPaintStyle().lineWidth, 4, "paintStyle lineWidth is 4"); + equals(c.getPaintStyle().strokeStyle, "yellow", "paintStyle strokeStyle is yellow"); + equals(c.getHoverPaintStyle().strokeStyle, "blue", "hoverPaintStyle strokeStyle is blue"); + equals(c.getHoverPaintStyle().lineWidth, 4, "hoverPaintStyle linewidth is 6"); + + c.setType("other"); + equals(c.getPaintStyle().lineWidth, 14, "paintStyle lineWidth is 14"); + equals(c.getPaintStyle().strokeStyle, "red", "paintStyle strokeStyle is red"); + equals(c.getHoverPaintStyle().strokeStyle, "green", "hoverPaintStyle strokeStyle is green"); + equals(c.getHoverPaintStyle().lineWidth, 14, "hoverPaintStyle linewidth is 14"); + }); + + test(renderMode + " set connection type on existing connection, overlays should be set", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }; + + _jsPlumb.registerConnectionType("basic", basicType); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("basic"); + equals(c.getOverlays().length, 1, "one overlay"); + }); + + test(renderMode + " set connection type on existing connection, overlays should be removed with second type", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }; + var otherType = { + connector:"Bezier" + }; + _jsPlumb.registerConnectionType("basic", basicType); + _jsPlumb.registerConnectionType("other", otherType); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("basic"); + equals(c.getOverlays().length, 1, "one overlay"); + c.setType("other"); + equals(c.getOverlays().length, 0, "no overlays"); + equals(c.getPaintStyle().lineWidth, _jsPlumb.Defaults.PaintStyle.lineWidth, "paintStyle lineWidth is default"); + }); + + test(renderMode + " set connection type on existing connection, hasType + toggleType", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }; + + _jsPlumb.registerConnectionTypes({ + "basic": basicType + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("basic"); + equals(c.hasType("basic"), true, "connection has 'basic' type"); + c.toggleType("basic"); + equals(c.hasType("basic"), false, "connection does not have 'basic' type"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + c.toggleType("basic"); + equals(c.hasType("basic"), true, "connection has 'basic' type"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + equals(c.getOverlays().length, 1, "one overlay"); + + }); + + test(renderMode + " set connection type on existing connection, merge tests", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }; + // this tests all three merge types: connector should overwrite, linewidth should be inserted into + // basic type's params, and arrow overlay should be added to list to end up with two overlays + var otherType = { + connector:"Bezier", + paintStyle:{ lineWidth:14 }, + overlays:[ + ["Arrow", {location:0.25}] + ] + }; + _jsPlumb.registerConnectionTypes({ + "basic": basicType, + "other": otherType + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("basic"); + equals(c.hasType("basic"), true, "connection has 'basic' type"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + equals(c.getPaintStyle().lineWidth, 4, "connection has linewidth 4"); + equals(c.getOverlays().length, 1, "one overlay"); + + c.addType("other"); + equals(c.hasType("basic"), true, "connection has 'basic' type"); + equals(c.hasType("other"), true, "connection has 'other' type"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + equals(c.getPaintStyle().lineWidth, 14, "connection has linewidth 14"); + equals(c.getOverlays().length, 2, "two overlays"); + + c.removeType("basic"); + equals(c.hasType("basic"), false, "connection does not have 'basic' type"); + equals(c.hasType("other"), true, "connection has 'other' type"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + equals(c.getPaintStyle().lineWidth, 14, "connection has linewidth 14"); + equals(c.getOverlays().length, 1, "one overlay"); + + c.toggleType("other"); + equals(c.hasType("other"), false, "connection does not have 'other' type"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + equals(c.getPaintStyle().lineWidth, _jsPlumb.Defaults.PaintStyle.lineWidth, "connection has default linewidth"); + equals(c.getOverlays().length, 0, "nooverlays"); + }); + + test(renderMode + " connection type tests, space separated arguments", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }; + // this tests all three merge types: connector should overwrite, linewidth should be inserted into + // basic type's params, and arrow overlay should be added to list to end up with two overlays + var otherType = { + connector:"Bezier", + paintStyle:{ lineWidth:14 }, + overlays:[ + ["Arrow", {location:0.25}] + ] + }; + _jsPlumb.registerConnectionTypes({ + "basic": basicType, + "other": otherType + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("basic other"); + equals(c.hasType("basic"), true, "connection has 'basic' type"); + equals(c.hasType("other"), true, "connection has 'other' type"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + equals(c.getPaintStyle().lineWidth, 14, "connection has linewidth 14"); + equals(c.getOverlays().length, 2, "two overlays"); + + c.toggleType("other basic"); + equals(c.hasType("basic"), false, "after toggle, connection does not have 'basic' type"); + equals(c.hasType("other"), false, "after toggle, connection does not have 'other' type"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "after toggle, connection has default stroke style"); + equals(c.getPaintStyle().lineWidth, _jsPlumb.Defaults.PaintStyle.lineWidth, "after toggle, connection has default linewidth"); + equals(c.getOverlays().length, 0, "after toggle, no overlays"); + + c.toggleType("basic other"); + equals(c.hasType("basic"), true, "after toggle again, connection has 'basic' type"); + equals(c.hasType("other"), true, "after toggle again, connection has 'other' type"); + equals(c.getPaintStyle().strokeStyle, "yellow", "after toggle again, connection has yellow stroke style"); + equals(c.getPaintStyle().lineWidth, 14, "after toggle again, connection has linewidth 14"); + equals(c.getOverlays().length, 2, "after toggle again, two overlays"); + + c.removeType("other basic"); + equals(c.hasType("basic"), false, "after remove, connection does not have 'basic' type"); + equals(c.hasType("other"), false, "after remove, connection does not have 'other' type"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "after remove, connection has default stroke style"); + equals(c.getPaintStyle().lineWidth, _jsPlumb.Defaults.PaintStyle.lineWidth, "after remove, connection has default linewidth"); + equals(c.getOverlays().length, 0, "after remove, no overlays"); + + c.addType("other basic"); + equals(c.hasType("basic"), true, "after add, connection has 'basic' type"); + equals(c.hasType("other"), true, "after add, connection has 'other' type"); + equals(c.getPaintStyle().strokeStyle, "yellow", "after add, connection has yellow stroke style"); + // NOTE here we added the types in the other order to before, so lineWidth 4 - from basic - should win. + equals(c.getPaintStyle().lineWidth, 4, "after add, connection has linewidth 4"); + equals(c.getOverlays().length, 2, "after add, two overlays"); + }); + + test(renderMode + " connection type tests, fluid interface", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }; + // this tests all three merge types: connector should overwrite, linewidth should be inserted into + // basic type's params, and arrow overlay should be added to list to end up with two overlays + var otherType = { + connector:"Bezier", + paintStyle:{ lineWidth:14 }, + overlays:[ + ["Arrow", {location:0.25}] + ] + }; + _jsPlumb.registerConnectionTypes({ + "basic": basicType, + "other": otherType + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + c = _jsPlumb.connect({source:d1, target:d2}), + c2 = _jsPlumb.connect({source:d2, target:d3}); + + _jsPlumb.select().addType("basic"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + equals(c2.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + + _jsPlumb.select().toggleType("basic"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + equals(c2.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + + _jsPlumb.select().addType("basic"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + equals(c2.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + + _jsPlumb.select().removeType("basic").addType("other"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + equals(c2.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + + + }); + + test(renderMode + " connection type tests, two types, check separation", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 } + }; + // this tests all three merge types: connector should overwrite, linewidth should be inserted into + // basic type's params, and arrow overlay should be added to list to end up with two overlays + var otherType = { + paintStyle:{ strokeStyle:"red", lineWidth:14 } + }; + _jsPlumb.registerConnectionTypes({ + "basic": basicType, + "other": otherType + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + c = _jsPlumb.connect({source:d1, target:d2}), + c2 = _jsPlumb.connect({source:d2, target:d3}); + c.setType("basic"); + equals(c.getPaintStyle().strokeStyle, "yellow", "first connection has yellow stroke style"); + c2.setType("other"); + + equals(c.getPaintStyle().strokeStyle, "yellow", "first connection has yellow stroke style"); + + + }); + + test(renderMode + " setType when null", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType(null); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + + }); + + test(renderMode + " setType to unknown type", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("foo"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + + }); + + test(renderMode + " setType to mix of known and unknown types", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + c = _jsPlumb.connect({source:d1, target:d2}); + + _jsPlumb.registerConnectionType("basic", { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }); + + c.setType("basic foo"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has basic type's stroke style"); + + c.toggleType("foo"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has basic type's stroke style"); + + c.removeType("basic baz"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + + c.addType("basic foo bar baz"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has basic type's stroke style"); + + }); + + test(renderMode + " create connection using type parameter", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + + _jsPlumb.Defaults.PaintStyle = {strokeStyle:"blue", lineWidth:34}; + + _jsPlumb.registerConnectionTypes({ + "basic": { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }, + "other":{ + paintStyle:{ lineWidth:14 } + } + }); + + equals(_jsPlumb.Defaults.PaintStyle.strokeStyle, "blue", "default value has not been messed up"); + + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + + c = _jsPlumb.connect({source:d1, target:d2, type:"basic other"}); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has basic type's stroke style"); + equals(c.getPaintStyle().lineWidth, 14, "connection has other type's lineWidth"); + + }); + + test(renderMode + " setType, scope", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + c = _jsPlumb.connect({source:d1, target:d2}); + + _jsPlumb.registerConnectionType("basic", { + connector:"Flowchart", + scope:"BANANA", + detachable:false, + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }); + + _jsPlumb.Defaults.ConnectionsDetachable = true;//just make sure we've setup the test correctly. + + c.setType("basic"); + equals(c.scope, "BANANA", "scope is correct"); + equals(c.isDetachable(), false, "not detachable"); + + }); + + test(renderMode + " setType, parameters", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + + _jsPlumb.registerConnectionType("basic", { + parameters:{ + foo:1, + bar:2, + baz:6785962437582 + } + }); + + _jsPlumb.registerConnectionType("frank", { + parameters:{ + bar:5 + } + }); + + // first try creating one with the parameters + c = _jsPlumb.connect({source:d1, target:d2, type:"basic"}); + + equals(c.getParameter("foo"), 1, "foo param correct"); + equals(c.getParameter("bar"), 2, "bar param correct"); + + c.addType("frank"); + equals(c.getParameter("foo"), 1, "foo param correct"); + equals(c.getParameter("bar"), 5, "bar param correct"); + }); + + test(renderMode + " setType, scope, two types", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + c = _jsPlumb.connect({source:d1, target:d2}); + + _jsPlumb.registerConnectionType("basic", { + connector:"Flowchart", + scope:"BANANA", + detachable:false, + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }); + + _jsPlumb.registerConnectionType("frank", { + scope:"OVERRIDE", + detachable:true + }); + + _jsPlumb.Defaults.ConnectionsDetachable = true;//just make sure we've setup the test correctly. + + c.setType("basic frank"); + equals(c.scope, "OVERRIDE", "scope is correct"); + equals(c.isDetachable(), true, "detachable"); + + }); + + test(renderMode + " create connection from Endpoints - type should be passed through.", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e1 = _jsPlumb.addEndpoint(d1, { + connectionType:"basic" + }), + e2 = _jsPlumb.addEndpoint(d2, { + connectionType:"basic" + }); + + _jsPlumb.registerConnectionTypes({ + "basic": { + connector:"Flowchart", + paintStyle:{ strokeStyle:"blue", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"red" }, + overlays:[ + "Arrow" + ] + }, + "other":{ + paintStyle:{ lineWidth:14 } + } + }); + + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.getPaintStyle().strokeStyle, "blue", "connection has default stroke style"); + equal(c.connector.type, "Flowchart", "connector is flowchart"); + }); + + test(renderMode + " simple Endpoint type tests.", function() { + _jsPlumb.registerEndpointType("basic", { + paintStyle:{fillStyle:"blue"} + }); + + var d = _addDiv('d1'), e = _jsPlumb.addEndpoint(d); + e.setType("basic"); + equals(e.getPaintStyle().fillStyle, "blue", "fill style is correct"); + + var d2 = _addDiv('d2'), e2 = _jsPlumb.addEndpoint(d2, {type:"basic"}); + equals(e2.getPaintStyle().fillStyle, "blue", "fill style is correct"); + }); + + test(renderMode + " create connection from Endpoints - with connector settings in Endpoint type.", function() { + + _jsPlumb.registerEndpointTypes({ + "basic": { + connector:"Flowchart", + connectorOverlays:[ + "Arrow" + ], + connectorStyle:{strokeStyle:"green" }, + connectorHoverStyle:{lineWidth:534 }, + paintStyle:{ fillStyle:"blue" }, + hoverPaintStyle:{ strokeStyle:"red" }, + overlays:[ + "Arrow" + ] + }, + "other":{ + paintStyle:{ fillStyle:"red" } + } + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e1 = _jsPlumb.addEndpoint(d1, { + type:"basic" + }), + e2 = _jsPlumb.addEndpoint(d2); + + c = _jsPlumb.connect({source:e1, target:e2}); + equals(e1.getPaintStyle().fillStyle, "blue", "endpoint has fill style specified in Endpoint type"); + equals(c.getPaintStyle().strokeStyle, "green", "connection has stroke style specified in Endpoint type"); + equals(c.getHoverPaintStyle().lineWidth, 534, "connection has hover style specified in Endpoint type"); + equals(c.connector.type, "Flowchart", "connector is Flowchart"); + equals(c.overlays.length, 1, "connector has one overlay"); + equals(e1.overlays.length, 1, "endpoint has one overlay"); + }); + + test(renderMode + " create connection from Endpoints - type should be passed through.", function() { + + _jsPlumb.registerConnectionTypes({ + "basic": { + connector:"Flowchart", + paintStyle:{ strokeStyle:"bazona", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"red" }, + overlays:[ + "Arrow" + ] + } + }); + + _jsPlumb.registerEndpointType("basic", { + connectionType:"basic", + paintStyle:{fillStyle:"GAZOODA"} + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e1 = _jsPlumb.addEndpoint(d1, { + type:"basic" + }), + e2 = _jsPlumb.addEndpoint(d2); + + c = _jsPlumb.connect({source:e1, target:e2}); + equals(e1.getPaintStyle().fillStyle, "GAZOODA", "endpoint has correct paint style, from type."); + equals(c.getPaintStyle().strokeStyle, "bazona", "connection has paint style from connection type, as specified in endpoint type. sweet!"); + equal(c.connector.type, "Flowchart", "connector is flowchart - this also came from connection type as specified by endpoint type."); + }); + + test(renderMode + " endpoint type", function() { + _jsPlumb.registerEndpointTypes({"example": {hoverPaintStyle: null}}); + //OR + //jsPlumb.registerEndpointType("example", {hoverPaintStyle: null}); + + var d = _addDiv("d"); + _jsPlumb.addEndpoint(d, {type: "example"}); + _jsPlumb.repaint(d); + }); + + /* + * test the merge function in jsplumb util: it should create an entirely new object + */ + test(renderMode + "jsPlumbUtil.merge", function() { + var a = { + foo:"a_foo", + bar:"a_bar", + nested:{ + foo:"a_foo", + bar:"a_bar" + } + }, + b = { + foo:"b_foo", + nested :{ + foo:"b_foo" + } + }, + c = jsPlumbUtil.merge(a, b); + equals(c.foo, "b_foo", "c has b's foo"); + equals(c.nested.foo, "b_foo", "c has b's nested foo"); + // now change c's foo. b should be unchanged. + c.foo = "c_foo"; + equals(b.foo, "b_foo", "b has b's foo"); + c.nested.foo = "c_foo"; + equals(b.nested.foo, "b_foo", "b has b's nested foo"); + equals(a.nested.foo, "a_foo", "a has a's nested foo"); + }); + + test(renderMode + "jsPlumbUtil.clone", function() { + var a = { + nested:{ + foo:"a_foo" + } + }, + b = jsPlumbUtil.clone(a); + equals(b.nested.foo, "a_foo", "b has a's nested foo"); + equals(a.nested.foo, "a_foo", "a has a's nested foo"); + b.nested.foo="b_foo"; + equals(b.nested.foo, "b_foo", "b has b's nested foo"); + equals(a.nested.foo, "a_foo", "a has a's nested foo"); + }); + +}; + diff --git a/archive/1.3.14/jsPlumb-connectors-statemachine-1.3.14-RC1.js b/archive/1.3.14/jsPlumb-connectors-statemachine-1.3.14-RC1.js new file mode 100644 index 000000000..3bc61e210 --- /dev/null +++ b/archive/1.3.14/jsPlumb-connectors-statemachine-1.3.14-RC1.js @@ -0,0 +1,469 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.14 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the state machine connectors. + * + * Thanks to Brainstorm Mobile Solutions for supporting the development of these. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + var Line = function(x1, y1, x2, y2) { + + this.m = (y2 - y1) / (x2 - x1); + this.b = -1 * ((this.m * x1) - y1); + + this.rectIntersect = function(x,y,w,h) { + var results = []; + + // try top face + // the equation of the top face is y = (0 * x) + b; y = b. + var xInt = (y - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try right face + var yInt = (this.m * (x + w)) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + // bottom face + var xInt = ((y + h) - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try left face + var yInt = (this.m * x) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + if (results.length == 2) { + var midx = (results[0][0] + results[1][0]) / 2, midy = (results[0][1] + results[1][1]) / 2; + results.push([ midx,midy ]); + // now calculate the segment inside the rectangle where the midpoint lies. + var xseg = midx <= x + (w / 2) ? -1 : 1, + yseg = midy <= y + (h / 2) ? -1 : 1; + results.push([xseg, yseg]); + return results; + } + + return null; + + }; + }, + _segment = function(x1, y1, x2, y2) { + if (x1 <= x2 && y2 <= y1) return 1; + else if (x1 <= x2 && y1 <= y2) return 2; + else if (x2 <= x1 && y2 >= y1) return 3; + return 4; + }, + + // the control point we will use depends on the faces to which each end of the connection is assigned, specifically whether or not the + // two faces are parallel or perpendicular. if they are parallel then the control point lies on the midpoint of the axis in which they + // are parellel and varies only in the other axis; this variation is proportional to the distance that the anchor points lie from the + // center of that face. if the two faces are perpendicular then the control point is at some distance from both the midpoints; the amount and + // direction are dependent on the orientation of the two elements. 'seg', passed in to this method, tells you which segment the target element + // lies in with respect to the source: 1 is top right, 2 is bottom right, 3 is bottom left, 4 is top left. + // + // sourcePos and targetPos are arrays of info about where on the source and target each anchor is located. their contents are: + // + // 0 - absolute x + // 1 - absolute y + // 2 - proportional x in element (0 is left edge, 1 is right edge) + // 3 - proportional y in element (0 is top edge, 1 is bottom edge) + // + _findControlPoint = function(midx, midy, segment, sourceEdge, targetEdge, dx, dy, distance, proximityLimit) { + + // TODO (maybe) + // - if anchor pos is 0.5, make the control point take into account the relative position of the elements. + if (distance <= proximityLimit) return [midx, midy]; + + if (segment == 1) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 2) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 3) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 4) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + }; + + /** + * Class: Connectors.StateMachine + * Provides 'state machine' connectors. + */ + /* + * Function: Constructor + * + * Parameters: + * curviness - measure of how "curvy" the connectors will be. this is translated as the distance that the + * Bezier curve's control point is from the midpoint of the straight line connecting the two + * endpoints, and does not mean that the connector is this wide. The Bezier curve never reaches + * its control points; they act as gravitational masses. defaults to 10. + * margin - distance from element to start and end connectors, in pixels. defaults to 5. + * proximityLimit - sets the distance beneath which the elements are consider too close together to bother + * with fancy curves. by default this is 80 pixels. + * loopbackRadius - the radius of a loopback connector. optional; defaults to 25. + * showLoopback - If set to false this tells the connector that it is ok to paint connections whose source and target is the same element with a connector running through the element. The default value for this is true; the connector always makes a loopback connection loop around the element rather than passing through it. + */ + jsPlumb.Connectors.StateMachine = function(params) { + var self = this, + currentPoints = null, + _sx, _sy, _tx, _ty, _controlPoint = [], + curviness = params.curviness || 10, + margin = params.margin || 5, + proximityLimit = params.proximityLimit || 80, + clockwise = params.orientation && params.orientation == "clockwise", + loopbackRadius = params.loopbackRadius || 25, + isLoopback = false, + showLoopback = params.showLoopback !== false; + + this.type = "StateMachine"; + params = params || {}; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *= 1.9; + //ensure at least one pixel width + lineWidth = lineWidth || 1; + var x = Math.min(sourcePos[0], targetPos[0]) - xo, + y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (!showLoopback || (sourceEndpoint.elementId != targetEndpoint.elementId)) { + + isLoopback = false; + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + + // now adjust for the margin + if (sourcePos[2] == 0) _sx -= margin; + if (sourcePos[2] == 1) _sx += margin; + if (sourcePos[3] == 0) _sy -= margin; + if (sourcePos[3] == 1) _sy += margin; + if (targetPos[2] == 0) _tx -= margin; + if (targetPos[2] == 1) _tx += margin; + if (targetPos[3] == 0) _ty -= margin; + if (targetPos[3] == 1) _ty += margin; + + // + // these connectors are quadratic bezier curves, having a single control point. if both anchors + // are located at 0.5 on their respective faces, the control point is set to the midpoint and you + // get a straight line. this is also the case if the two anchors are within 'proximityLimit', since + // it seems to make good aesthetic sense to do that. outside of that, the control point is positioned + // at 'curviness' pixels away along the normal to the straight line connecting the two anchors. + // + // there may be two improvements to this. firstly, we might actually support the notion of avoiding nodes + // in the UI, or at least making a good effort at doing so. if a connection would pass underneath some node, + // for example, we might increase the distance the control point is away from the midpoint in a bid to + // steer it around that node. this will work within limits, but i think those limits would also be the likely + // limits for, once again, aesthetic good sense in the layout of a chart using these connectors. + // + // the second possible change is actually two possible changes: firstly, it is possible we should gradually + // decrease the 'curviness' as the distance between the anchors decreases; start tailing it off to 0 at some + // point (which should be configurable). secondly, we might slightly increase the 'curviness' for connectors + // with respect to how far their anchor is from the center of its respective face. this could either look cool, + // or stupid, and may indeed work only in a way that is so subtle as to have been a waste of time. + // + + var _midx = (_sx + _tx) / 2, _midy = (_sy + _ty) / 2, + m2 = (-1 * _midx) / _midy, theta2 = Math.atan(m2), + dy = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.sin(theta2)), + dx = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.cos(theta2)), + segment = _segment(_sx, _sy, _tx, _ty), + distance = Math.sqrt(Math.pow(_tx - _sx, 2) + Math.pow(_ty - _sy, 2)); + + // calculate the control point. this code will be where we'll put in a rudimentary element avoidance scheme; it + // will work by extending the control point to force the curve to be, um, curvier. + _controlPoint = _findControlPoint(_midx, + _midy, + segment, + sourcePos, + targetPos, + curviness, curviness, + distance, + proximityLimit); + + + var requiredWidth = Math.max(Math.abs(_controlPoint[0] - _sx) * 3, Math.abs(_controlPoint[0] - _tx) * 3, Math.abs(_tx-_sx), 2 * lineWidth, minWidth), + requiredHeight = Math.max(Math.abs(_controlPoint[1] - _sy) * 3, Math.abs(_controlPoint[1] - _ty) * 3, Math.abs(_ty-_sy), 2 * lineWidth, minWidth); + + if (w < requiredWidth) { + var dw = requiredWidth - w; + x -= (dw / 2); + _sx += (dw / 2); + _tx += (dw / 2); + w = requiredWidth; + _controlPoint[0] += (dw / 2); + } + + if (h < requiredHeight) { + var dh = requiredHeight - h; + y -= (dh / 2); + _sy += (dh / 2); + _ty += (dh / 2); + h = requiredHeight; + _controlPoint[1] += (dh / 2); + } + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty, _controlPoint[0], _controlPoint[1] ]; + } + else { + isLoopback = true; + // a loopback connector. draw an arc from one anchor to the other. + // i guess we'll do this the same way as the others. just the control point will be a fair distance away. + var x1 = sourcePos[0], x2 = sourcePos[0], y1 = sourcePos[1] - margin, y2 = sourcePos[1] - margin, + cx = x1, cy = y1 - loopbackRadius; + + // canvas sizing stuff, to ensure the whole painted area is visible. + w = ((2 * lineWidth) + (4 * loopbackRadius)), h = ((2 * lineWidth) + (4 * loopbackRadius)); + x = cx - loopbackRadius - lineWidth - loopbackRadius, y = cy - loopbackRadius - lineWidth - loopbackRadius; + currentPoints = [ x, y, w, h, cx-x, cy-y, loopbackRadius, clockwise, x1-x, y1-y, x2-x, y2-y]; + } + + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_tx, y:_ty }, + { x:_controlPoint[0], y:_controlPoint[1] }, + { x:_controlPoint[0] + 1, y:_controlPoint[1] + 1}, + { x:_sx, y:_sy } + ]; + }; + + var _translateLocation = function(curve, location, absolute) { + if (absolute) + location = jsBezier.locationAlongCurveFrom(curve, location > 0 ? 0 : 1, location); + + return location; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location, absolute) { + if (isLoopback) { + if (absolute) { + var circumference = Math.PI * 2 * loopbackRadius; + location = location / circumference; + } + + if (location > 0 && location < 1) location = 1 - location; + +// current points are [ x, y, width, height, center x, center y, radius, clockwise, startx, starty, endx, endy ] + // so the path length is the circumference of the circle + //var len = 2 * Math.PI * currentPoints[6], + // map 'location' to an angle. 0 is PI/2 when the connector is on the top face; if we + // support other faces it will have to be calculated for each one. 1 is also PI/2. + // 0.5 is -PI/2. + var startAngle = (location * 2 * Math.PI) + (Math.PI / 2), + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + + } + else { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.pointOnCurve(c, location); + } + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location, absolute) { + if (isLoopback) { + // todo if absolute, location is a proportion of circumference + if (absolute) { + var circumference = Math.PI * 2 * loopbackRadius; + location = location / circumference; + } + + return Math.atan(location * 2 * Math.PI); + } + else { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.gradientAtPoint(c, location); + } + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance, absolute) { + if (isLoopback) { + + if (absolute) { + var circumference = Math.PI * 2 * loopbackRadius; + location = location / circumference; + } + + if (location > 0 && location < 1) location = 1 - location; + + var circumference = 2 * Math.PI * currentPoints[6], + arcSpan = distance / circumference * 2 * Math.PI, + startAngle = (location * 2 * Math.PI) - arcSpan + (Math.PI / 2), + + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + } + else { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.pointAlongCurveFrom(c, location, distance); + } + }; + + }; + + /* + * Canvas state machine renderer. + */ + jsPlumb.Connectors.canvas.StateMachine = function(params) { + params = params || {}; + var self = this, drawGuideline = params.drawGuideline || true, avoidSelector = params.avoidSelector; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.CanvasConnector.apply(this, arguments); + + + this._paint = function(dimensions) { + + if (dimensions.length == 10) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[8], dimensions[9], dimensions[6], dimensions[7]); + self.ctx.stroke(); + } + else { + // a loopback connector + self.ctx.save(); + self.ctx.beginPath(); + var startAngle = 0, // Starting point on circle + endAngle = 2 * Math.PI, // End point on circle + clockwise = dimensions[7]; // clockwise or anticlockwise + self.ctx.arc(dimensions[4],dimensions[5],dimensions[6],0, endAngle, clockwise); + self.ctx.stroke(); + self.ctx.closePath(); + self.ctx.restore(); + } + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + /* + * SVG State Machine renderer + */ + jsPlumb.Connectors.svg.StateMachine = function() { + var self = this; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.SvgConnector.apply(this, arguments); + this.getPath = function(d) { + + if (d.length == 10) + return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[8] + " " + d[9] + " " + d[6] + " " + d[7]; + else { + // loopback + return "M" + (d[8] + 4) + " " + d[9] + " A " + d[6] + " " + d[6] + " 0 1,0 " + (d[8]-4) + " " + d[9]; + } + }; + }; + + /* + * VML state machine renderer + */ + jsPlumb.Connectors.vml.StateMachine = function() { + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.VmlConnector.apply(this, arguments); + var _conv = jsPlumb.vml.convertValue; + this.getPath = function(d) { + if (d.length == 10) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + } + else { + // loopback + var left = _conv(d[8] - d[6]), + top = _conv(d[9] - (2 * d[6])), + right = left + _conv(2 * d[6]), + bottom = top + _conv(2 * d[6]), + posString = left + "," + top + "," + right + "," + bottom; + + var o = "ar " + posString + "," + _conv(d[8]) + "," + + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + " e"; + + return o; + } + }; + }; + +})(); + +/* + // now for a rudimentary avoidance scheme. TODO: how to set this in a cross-library way? + // if (avoidSelector) { + // var testLine = new Line(sourcePos[0] + _sx,sourcePos[1] + _sy,sourcePos[0] + _tx,sourcePos[1] + _ty); + // var sel = jsPlumb.getSelector(avoidSelector); + // for (var i = 0; i < sel.length; i++) { + // var id = jsPlumb.getId(sel[i]); + // if (id != sourceEndpoint.elementId && id != targetEndpoint.elementId) { + // o = jsPlumb.getOffset(id), s = jsPlumb.getSize(id); +// +// if (o && s) { +// var collision = testLine.rectIntersect(o.left,o.top,s[0],s[1]); +// if (collision) { + // set the control point to be a certain distance from the midpoint of the two points that + // the line crosses on the rectangle. + // TODO where will this 75 number come from? + // _controlX = collision[2][0] + (75 * collision[3][0]); + // / _controlY = collision[2][1] + (75 * collision[3][1]); +// } +// } + // } + // } + //} + */ \ No newline at end of file diff --git a/archive/1.3.14/jsPlumb-defaults-1.3.14-RC1.js b/archive/1.3.14/jsPlumb-defaults-1.3.14-RC1.js new file mode 100644 index 000000000..013428e81 --- /dev/null +++ b/archive/1.3.14/jsPlumb-defaults-1.3.14-RC1.js @@ -0,0 +1,1256 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.14 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the default Connectors, Endpoint and Overlay definitions. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +(function() { + + /** + * + * Helper class to consume unused mouse events by components that are DOM elements and + * are used by all of the different rendering modes. + * + */ + jsPlumb.DOMElementComponent = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + // when render mode is canvas, these functions may be called by the canvas mouse handler. + // this component is safe to pipe this stuff to /dev/null. + this.mousemove = + this.dblclick = + this.click = + this.mousedown = + this.mouseup = function(e) { }; + }; + + /** + * Class: Connectors.Straight + * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters. + */ + jsPlumb.Connectors.Straight = function() { + this.type = "Straight"; + var self = this, + currentPoints = null, + _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty, _segment, _length; + + /** + * Computes the new size and position of the canvas. + */ + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + _dx = _tx - _sx, _dy = _ty - _sy; + //_m = _dy / _dx, _m2 = -1 / _m; + _m = jsPlumbUtil.gradient({x:_sx, y:_sy}, {x:_tx, y:_ty}), _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + //_segment = jsPlumbUtil.segment({x:_sx, y:_sy}, {x:_tx, y:_ty}); + _length = Math.sqrt((_dx * _dx) + (_dy * _dy)); + + return currentPoints; + }; + + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location, absolute) { + if (location == 0 && !absolute) + return { x:_sx, y:_sy }; + else if (location == 1 && !absolute) + return { x:_tx, y:_ty }; + else { + var l = absolute ? location > 0 ? location : _length + location : location * _length; + return jsPlumbUtil.pointOnLine({x:_sx, y:_sy}, {x:_tx, y:_ty}, l); + } + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { + return _m; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + * this hands off to jsPlumbUtil to do the maths, supplying two points and the distance. + */ + this.pointAlongPathFrom = function(location, distance, absolute) { + var p = self.pointOnPath(location, absolute), + farAwayPoint = location == 1 ? { + x:_sx + ((_tx - _sx) * 10), + y:_sy + ((_sy - _ty) * 10) + } : {x:_tx, y:_ty }; + + return jsPlumbUtil.pointOnLine(p, farAwayPoint, distance); + }; + }; + + + /** + * Class:Connectors.Bezier + * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's + * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below. + */ + /** + * Function:Constructor + * + * Parameters: + * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line. + * Optional; defaults to 150. + * stub - optional value for a distance to travel from the connector's endpoint before beginning the Bezier curve. defaults to 0. + * + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + params = params || {}; + this.majorAnchor = params.curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + this.type = "Bezier"; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(sourceEndpoint), + too = targetAnchor.getOrientation(targetEndpoint), + perpendicular = soo[0] != too[0] || soo[1] == too[1], + p = [], + ma = self.majorAnchor, mi = self.minorAnchor; + + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h, _sStubX, _sStubY, _tStubX, _tStubY; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetEndpoint, sourceEndpoint, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx), minx2 = Math.min(_CP[0], _CP2[0]), minx = Math.min(minx1,minx2), + maxx1 = Math.max(_sx,_tx), maxx2 = Math.max(_CP[0], _CP2[0]), maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty), miny2 = Math.min(_CP[1], _CP2[1]), miny = Math.min(miny1,miny2), + maxy1 = Math.max(_sy,_ty), maxy2 = Math.max(_CP[1], _CP2[1]), maxy = Math.max(maxy1,maxy2); + + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, + _sx, _sy, _tx, _ty, + _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + var _translateLocation = function(curve, location, absolute) { + if (absolute) + location = jsBezier.locationAlongCurveFrom(curve, location > 0 ? 0 : 1, location); + + return location; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location, absolute) { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.pointOnCurve(c, location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location, absolute) { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.gradientAtPoint(c, location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance, absolute) { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.pointAlongCurveFrom(c, location, distance); + }; + }; + + + /** + * Class: Connectors.Flowchart + * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments. + */ + /** + * Function: Constructor + * + * Parameters: + * stub - minimum length for the stub at each end of the connector. This can be an integer, giving a value for both ends of the connections, + * or an array of two integers, giving separate values for each end. The default is an integer with value 30 (pixels). + * gap - gap to leave between the end of the connector and the element on which the endpoint resides. if you make this larger than stub then you will see some odd looking behaviour. defaults to 0 pixels. + */ + jsPlumb.Connectors.Flowchart = function(params) { + this.type = "Flowchart"; + params = params || {}; + var self = this, + stub = params.stub || params.minStubLength /* bwds compat. */ || 30, + sourceStub = jsPlumbUtil.isArray(stub) ? stub[0] : stub, + targetStub = jsPlumbUtil.isArray(stub) ? stub[1] : stub, + gap = params.gap || 0, + segments = [], + totalLength = 0, + segmentProportions = [], + segmentProportionalLengths = [], + points = [], + swapX, swapY, + maxX = -Infinity, maxY = -Infinity, + minX = Infinity, minY = Infinity, + grid = params.grid, + _gridClamp = function(n, g) { var e = n % g, f = Math.floor(n / g), inc = e > (g / 2) ? 1 : 0; return (f + inc) * g; }, + clampToGrid = function(x, y, dontClampX, dontClampY) { + return [ + dontClampX || grid == null ? x : _gridClamp(x, grid[0]), + dontClampY || grid == null ? y : _gridClamp(y, grid[1]) + ]; + }, + /** + * recalculates the points at which the segments begin and end, proportional to the total length travelled + * by all the segments that constitute the connector. we use this to help with pointOnPath calculations. + */ + updateSegmentProportions = function(startX, startY, endX, endY) { + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segments[i][5] / totalLength; + segmentProportions[i] = [curLoc, (curLoc += (segments[i][5] / totalLength)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty/*, doGridX, doGridY*/) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0], + ly = segments.length == 0 ? sy : segments[segments.length - 1][1], + m = x == lx ? Infinity : 0, + l = Math.abs(x == lx ? y - ly : x - lx);/*, + gridded = clampToGrid(x, y), + doGridX = true, + doGridY = true; + + // grid experiment. TODO: have two more params that indicate whether or not to lock to a grid in each + // axis. the reason for this is that anchor points wont always be located on the grid, so until a connector + // emanating from that anchor has turned a right angle, we can't actually clamp it to a grid for that axis. + // so if a line came out horizontally heading left, then it will probably not be clamped in the y axis, but + // we can choose to clamp its first corner in the x axis. the same principle goes for the target anchor. + //if (segments.length == 0) { + console.log("this is the first segment...if sx == x then do not do grid in X.") + doGridX = !(sx == x) && !(tx == x); + doGridY = !(sy == y) && !(ty == y); + x = doGridX ? gridded[0] : x; + y = doGridY ? gridded[1] : y; + */ + + segments.push([x, y, lx, ly, m, l]); + totalLength += l; + + maxX = Math.max(maxX, x); + maxY = Math.max(maxY, y); + minX = Math.min(minX, x); + minY = Math.min(minY, y); + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment + * that contains the point which is 'location' distance along the entire path, where + * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths + * are made up of a list of segments, each of which contributes some fraction to + * the total length. + * From 1.3.10 this also supports the 'absolute' property, which lets us specify a location + * as the absolute distance in pixels, rather than a proportion of the total path. + */ + findSegmentForLocation = function(location, absolute) { + if (absolute) { + location = location > 0 ? location / totalLength : (totalLength + location) / totalLength; + } + + var idx = segmentProportions.length - 1, inSegmentProportion = 1; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, + sourceAnchor, targetAnchor, lineWidth, minWidth, sourceInfo, targetInfo) { + + segments = []; + segmentProportions = []; + totalLength = 0; + segmentProportionalLengths = []; + maxX = maxY = -Infinity; + minX = minY = Infinity; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + sourceOffx = (lw / 2) + (sourceStub + targetStub), + targetOffx = (lw / 2) + (targetStub + sourceStub), + sourceOffy = (lw / 2) + (sourceStub + targetStub), + targetOffy = (lw / 2) + (targetStub + sourceStub), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(sourceEndpoint), + to = targetAnchor.orientation || targetAnchor.getOrientation(targetEndpoint), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + sourceOffx + targetOffx, + h = Math.abs(targetPos[1] - sourcePos[1]) + sourceOffy + targetOffy; + + // if either anchor does not have an orientation set, we derive one from their relative + // positions. we fix the axis to be the one in which the two elements are further apart, and + // point each anchor at the other element. this is also used when dragging a new connection. + if (so[0] == 0 && so[1] == 0 || to[0] == 0 && to[1] == 0) { + var index = w > h ? 0 : 1, oIndex = [1,0][index]; + so = []; to = []; + so[index] = sourcePos[index] > targetPos[index] ? -1 : 1; + to[index] = sourcePos[index] > targetPos[index] ? 1 : -1; + so[oIndex] = 0; + to[oIndex] = 0; + } + + /* + this code is unexplained and causes paint errors with continuous anchors sometimes. + commenting it out until i can get to the bottom of it. + if (w < minWidth) { + sourceOffx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + sourceOffy += (minWidth - h) / 2; + h = minWidth; + } + */ + + var sx = swapX ? (w - targetOffx) +( gap * so[0]) : sourceOffx + (gap * so[0]), + sy = swapY ? (h - targetOffy) + (gap * so[1]) : sourceOffy + (gap * so[1]), + tx = swapX ? sourceOffx + (gap * to[0]) : (w - targetOffx) + (gap * to[0]), + ty = swapY ? sourceOffy + (gap * to[1]) : (h - targetOffy) + (gap * to[1]), + startStubX = sx + (so[0] * sourceStub), + startStubY = sy + (so[1] * sourceStub), + endStubX = tx + (to[0] * targetStub), + endStubY = ty + (to[1] * targetStub), + isXGreaterThanStubTimes2 = Math.abs(sx - tx) > (sourceStub + targetStub), + isYGreaterThanStubTimes2 = Math.abs(sy - ty) > (sourceStub + targetStub), + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2), + oProduct = ((so[0] * to[0]) + (so[1] * to[1])), + opposite = oProduct == -1, + perpendicular = oProduct == 0, + orthogonal = oProduct == 1; + + x -= sourceOffx; y -= sourceOffy; + points = [x, y, w, h, sx, sy, tx, ty]; + var extraPoints = []; + + var sourceAxis = so[0] == 0 ? "y" : "x", + anchorOrientation = opposite ? "opposite" : orthogonal ? "orthogonal" : "perpendicular", + segment = jsPlumbUtil.segment([sx, sy], [tx, ty]), + flipSourceSegments = so[sourceAxis == "x" ? 0 : 1] == -1, + flipSegments = { + "x":[null, 4, 3, 2, 1], + "y":[null, 2, 1, 4, 3] + } + + if (flipSourceSegments) + segment = flipSegments[sourceAxis][segment]; + + /*if (segment == 1 || segment == 2) { + if (sourceAxis == "x") + addSegment(Math.max(startStubX, endStubX), startStubY, sx, sy, tx, ty); + else + addSegment(startStubX, Math.max(startStubY, endStubY), sx, sy, tx, ty); + } + else { + if (sourceAxis == "x") + addSegment(Math.min(startStubX, endStubX), startStubY, sx, sy, tx, ty); + else + addSegment(startStubX, Math.min(startStubY, endStubY), sx, sy, tx, ty); + }*/ + //addSegment(startStubX, startStubY, sx, sy, tx, ty); + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + var findClearedLine = function(start, mult, anchorPos, dimension) { + return start + (mult * (( 1 - anchorPos) * dimension) + Math.max(sourceStub, targetStub)); + //mx = so[0] == 0 ? startStubX + ((1 - sourceAnchor.x) * sourceInfo.width) + stub : startStubX, + }, + + lineCalculators = { + oppositex : function() { + if (sourceEndpoint.elementId == targetEndpoint.elementId) { + var _y = startStubY + ((1 - sourceAnchor.y) * sourceInfo.height) + Math.max(sourceStub, targetStub); + return [ [ startStubX, _y ], [ endStubX, _y ]]; + } + else if (isXGreaterThanStubTimes2 && (segment == 1 || segment == 2)) { + return [[ midx, sy ], [ midx, ty ]]; + } + else { + return [[ startStubX, midy ], [endStubX, midy ]]; + } + }, + orthogonalx : function() { + if (segment == 1 || segment == 2) { + return [ [ endStubX, startStubY ]]; + } + else { + return [ [ startStubX, endStubY ]]; + } + }, + perpendicularx : function() { + var my = (ty + sy) / 2; + if ((segment == 1 && to[1] == 1) || (segment == 2 && to[1] == -1)) { + if (Math.abs(tx - sx) > Math.max(sourceStub, targetStub)) + return [ [endStubX, startStubY ]]; + else + return [ [startStubX, startStubY ], [ startStubX, my ], [ endStubX, my ]]; + } + else if ((segment == 3 && to[1] == -1) || (segment == 4 && to[1] == 1)) { + return [ [ startStubX, my ], [ endStubX, my ]]; + } + else if ((segment == 3 && to[1] == 1) || (segment == 4 && to[1] == -1)) { + return [ [ startStubX, endStubY ]]; + } + else if ((segment == 1 && to[1] == -1) || (segment == 2 && to[1] == 1)) { + if (Math.abs(tx - sx) > Math.max(sourceStub, targetStub)) + return [ [ midx, startStubY ], [ midx, endStubY ]]; + else + return [ [ startStubX, endStubY ]]; + } + }, + oppositey : function() { + if (sourceEndpoint.elementId == targetEndpoint.elementId) { + var _x = startStubX + ((1 - sourceAnchor.x) * sourceInfo.width) + Math.max(sourceStub, targetStub); + return [ [ _x, startStubY ], [ _x, endStubY ]]; + } + else if (isYGreaterThanStubTimes2 && (segment == 2 || segment == 3)) { + return [[ sx, midy ], [ tx, midy ]]; + } + else { + return [[ midx, startStubY ], [midx, endStubY ]]; + } + }, + orthogonaly : function() { + if (segment == 2 || segment == 3) { + return [ [ startStubX, endStubY ]]; + } + else { + return [ [ endStubX, startStubY ]]; + } + }, + perpendiculary : function() { + var mx = (tx + sx) / 2; + if ((segment == 2 && to[0] == -1) || (segment == 3 && to[0] == 1)) { + if (Math.abs(tx - sx) > Math.max(sourceStub, targetStub)) + return [ [startStubX, endStubY ]]; + else + return [ [startStubX, midy ], [ endStubX, midy ]]; + } + else if ((segment == 1 && to[0] == -1) || (segment == 4 && to[0] == 1)) { + var mx = (tx + sx) / 2; + return [ [ mx, startStubY ], [ mx, endStubY ]]; + } + else if ((segment == 1 && to[0] == 1) || (segment == 4 && to[0] == -1)) { + return [ [ endStubX, startStubY ]]; + } + else if ((segment == 2 && to[0] == 1) || (segment == 3 && to[0] == -1)) { + if (Math.abs(ty - sy) > Math.max(sourceStub, targetStub)) + return [ [ startStubX, midy ], [ endStubX, midy ]]; + else + return [ [ endStubX, startStubY ]]; + } + } + }; + + var p = lineCalculators[anchorOrientation + sourceAxis](); + if (p) { + for (var i = 0; i < p.length; i++) { + addSegment(p[i][0], p[i][1], sx, sy, tx, ty); + } + } + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentProportions(sx, sy, tx, ty); + + // adjust the max values of the canvas if we have a value that is larger than what we previously set. + // + if (maxY > points[3]) points[3] = maxY + (lineWidth * 2); + if (maxX > points[2]) points[2] = maxX + (lineWidth * 2); + + return points; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location, absolute) { + return self.pointAlongPathFrom(location, 0, absolute); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location, absolute) { + return segments[findSegmentForLocation(location, absolute)["index"]][4]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance, absolute) { + var s = findSegmentForLocation(location, absolute), seg = s.segment, p = s.proportion, sl = segments[s.index][5], m = segments[s.index][4]; + var e = { + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + return e; + }; + }; + + // ********************************* END OF CONNECTOR TYPES ******************************************************************* + + // ********************************* ENDPOINT TYPES ******************************************************************* + + /** + * Class: Endpoints.Dot + * A round endpoint, with default radius 10 pixels. + */ + + /** + * Function: Constructor + * + * Parameters: + * + * radius - radius of the endpoint. defaults to 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + this.type = "Dot"; + var self = this; + params = params || {}; + this.radius = params.radius || 10; + this.defaultOffset = 0.5 * this.radius; + this.defaultInnerRadius = this.radius / 3; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var r = endpointStyle.radius || self.radius, + x = anchorPoint[0] - r, + y = anchorPoint[1] - r; + return [ x, y, r * 2, r * 2, r ]; + }; + }; + + /** + * Class: Endpoints.Rectangle + * A Rectangular Endpoint, with default size 20x20. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the endpoint. defaults to 20 pixels. + * height - height of the endpoint. defaults to 20 pixels. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + this.type = "Rectangle"; + var self = this; + params = params || {}; + this.width = params.width || 20; + this.height = params.height || 20; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height]; + }; + }; + + + var DOMElementEndpoint = function(params) { + jsPlumb.DOMElementComponent.apply(this, arguments); + var self = this; + + var displayElements = [ ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + }; + /** + * Class: Endpoints.Image + * Draws an image as the Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * src - location of the image to use. + */ + jsPlumb.Endpoints.Image = function(params) { + + this.type = "Image"; + DOMElementEndpoint.apply(this, arguments); + + var self = this, + initialized = false, + deleted = false, + widthToUse = params.width, + heightToUse = params.height, + _onload = null, + _endpoint = params.endpoint; + + this.img = new Image(); + self.ready = false; + + this.img.onload = function() { + self.ready = true; + widthToUse = widthToUse || self.img.width; + heightToUse = heightToUse || self.img.height; + if (_onload) { + _onload(self); + } + }; + + /* + Function: setImage + Sets the Image to use in this Endpoint. + + Parameters: + img - may be a URL or an Image object + onload - optional; a callback to execute once the image has loaded. + */ + _endpoint.setImage = function(img, onload) { + var s = img.constructor == String ? img : img.src; + _onload = onload; + self.img.src = img; + + if (self.canvas != null) + self.canvas.setAttribute("src", img); + }; + + _endpoint.setImage(params.src || params.url, params.onload); + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + self.anchorPoint = anchorPoint; + if (self.ready) return [anchorPoint[0] - widthToUse / 2, anchorPoint[1] - heightToUse / 2, + widthToUse, heightToUse]; + else return [0,0,0,0]; + }; + + self.canvas = document.createElement("img"), initialized = false; + self.canvas.style["margin"] = 0; + self.canvas.style["padding"] = 0; + self.canvas.style["outline"] = 0; + self.canvas.style["position"] = "absolute"; + var clazz = params.cssClass ? " " + params.cssClass : ""; + self.canvas.className = jsPlumb.endpointClass + clazz; + if (widthToUse) self.canvas.setAttribute("width", widthToUse); + if (heightToUse) self.canvas.setAttribute("height", heightToUse); + jsPlumb.appendElement(self.canvas, params.parent); + self.attachListeners(self.canvas, self); + + self.cleanup = function() { + deleted = true; + }; + + var actuallyPaint = function(d, style, anchor) { + if (!deleted) { + if (!initialized) { + self.canvas.setAttribute("src", self.img.src); + self.appendDisplayElement(self.canvas); + initialized = true; + } + var x = self.anchorPoint[0] - (widthToUse / 2), + y = self.anchorPoint[1] - (heightToUse / 2); + jsPlumb.sizeCanvas(self.canvas, x, y, widthToUse, heightToUse); + } + }; + + this.paint = function(d, style, anchor) { + if (self.ready) { + actuallyPaint(d, style, anchor); + } + else { + window.setTimeout(function() { + self.paint(d, style, anchor); + }, 200); + } + }; + }; + + /** + * Class: Endpoints.Blank + * An Endpoint that paints nothing (visible) on the screen. Supports cssClass and hoverClass parameters like all Endpoints. + */ + jsPlumb.Endpoints.Blank = function(params) { + var self = this; + this.type = "Blank"; + DOMElementEndpoint.apply(this, arguments); + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + return [anchorPoint[0], anchorPoint[1],10,0]; + }; + + self.canvas = document.createElement("div"); + self.canvas.style.display = "block"; + self.canvas.style.width = "1px"; + self.canvas.style.height = "1px"; + self.canvas.style.background = "transparent"; + self.canvas.style.position = "absolute"; + self.canvas.className = self._jsPlumb.endpointClass; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + }; + }; + + /** + * Class: Endpoints.Triangle + * A triangular Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the triangle's base. defaults to 55 pixels. + * height - height of the triangle from base to apex. defaults to 55 pixels. + */ + jsPlumb.Endpoints.Triangle = function(params) { + this.type = "Triangle"; + params = params || { }; + params.width = params.width || 55; + params.height = params.height || 55; + this.width = params.width; + this.height = params.height; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height ]; + }; + }; +// ********************************* END OF ENDPOINT TYPES ******************************************************************* + + +// ********************************* OVERLAY DEFINITIONS *********************************************************************** + + var AbstractOverlay = function(params) { + var visible = true, self = this; + this.isAppendedAtTopLevel = true; + this.component = params.component; + this.loc = params.location == null ? 0.5 : params.location; + this.endpointLoc = params.endpointLocation == null ? [ 0.5, 0.5] : params.endpointLocation; + this.setVisible = function(val) { + visible = val; + self.component.repaint(); + }; + this.isVisible = function() { return visible; }; + this.hide = function() { self.setVisible(false); }; + this.show = function() { self.setVisible(true); }; + + this.incrementLocation = function(amount) { + self.loc += amount; + self.component.repaint(); + }; + this.setLocation = function(l) { + self.loc = l; + self.component.repaint(); + }; + this.getLocation = function() { + return self.loc; + }; + }; + + + /** + * Class: Overlays.Arrow + * + * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length + * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction + * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line + * across the tail. + */ + /** + * Function: Constructor + * + * Parameters: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + this.type = "Arrow"; + AbstractOverlay.apply(this, arguments); + this.isAppendedAtTopLevel = false; + params = params || {}; + var self = this; + + this.length = params.length || 20; + this.width = params.width || 20; + this.id = params.id; + var direction = (params.direction || 1) < 0 ? -1 : 1, + paintStyle = params.paintStyle || { lineWidth:1 }, + // how far along the arrow the lines folding back in come to. default is 62.3%. + foldback = params.foldback || 0.623; + + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.cleanup = function() { }; // nothing to clean up for Arrows + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var hxy, mid, txy, tail, cxy; + if (connector.pointAlongPathFrom) { + + if (jsPlumbUtil.isString(self.loc) || self.loc > 1 || self.loc < 0) { + var l = parseInt(self.loc); + hxy = connector.pointAlongPathFrom(l, direction * self.length / 2, true), + mid = connector.pointOnPath(l, true), + txy = jsPlumbUtil.pointOnLine(hxy, mid, self.length); + } + else if (self.loc == 1) { + hxy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, -1); + txy = jsPlumbUtil.pointOnLine(hxy, mid, self.length); + } + else if (self.loc == 0) { + txy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, 1); + hxy = jsPlumbUtil.pointOnLine(txy, mid, self.length); + } + else { + hxy = connector.pointAlongPathFrom(self.loc, direction * self.length / 2), + mid = connector.pointOnPath(self.loc), + txy = jsPlumbUtil.pointOnLine(hxy, mid, self.length); + } + + tail = jsPlumbUtil.perpendicularLineTo(hxy, txy, self.width); + cxy = jsPlumbUtil.pointOnLine(hxy, txy, foldback * self.length); + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x), + maxx = Math.max(hxy.x, tail[0].x, tail[1].x), + miny = Math.min(hxy.y, tail[0].y, tail[1].y), + maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + var d = { hxy:hxy, tail:tail, cxy:cxy }, + strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle, + fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle, + lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth; + + self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions); + + return [ minx, maxx, miny, maxy]; + } + else return [0,0,0,0]; + }; + }; + + /** + * Class: Overlays.PlainArrow + * + * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "PlainArrow"; + }; + + /** + * Class: Overlays.Diamond + * + * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40, + p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "Diamond"; + }; + + + // abstract superclass for overlays that add an element to the DOM. + var AbstractDOMOverlay = function(params) { + jsPlumb.DOMElementComponent.apply(this, arguments); + AbstractOverlay.apply(this, arguments); + + var self = this, initialised = false; + params = params || {}; + this.id = params.id; + var div; + + var makeDiv = function() { + div = params.create(params.component); + div = jsPlumb.CurrentLibrary.getDOMElement(div); + div.style["position"] = "absolute"; + var clazz = params["_jsPlumb"].overlayClass + " " + + (self.cssClass ? self.cssClass : + params.cssClass ? params.cssClass : ""); + div.className = clazz; + jsPlumb.appendElement(div, params.component.parent); + params["_jsPlumb"].getId(div); + self.attachListeners(div, self); + self.canvas = div; + }; + + this.getElement = function() { + if (div == null) { + makeDiv(); + } + return div; + }; + + this.getDimensions = function() { + return jsPlumb.CurrentLibrary.getSize(jsPlumb.CurrentLibrary.getElementObject(self.getElement())); + }; + + var cachedDimensions = null, + _getDimensions = function(component) { + if (cachedDimensions == null) + cachedDimensions = self.getDimensions(); + return cachedDimensions; + }; + + /* + * Function: clearCachedDimensions + * Clears the cached dimensions for the label. As a performance enhancement, label dimensions are + * cached from 1.3.12 onwards. The cache is cleared when you change the label text, of course, but + * there are other reasons why the text dimensions might change - if you make a change through CSS, for + * example, you might change the font size. in that case you should explicitly call this method. + */ + this.clearCachedDimensions = function() { + cachedDimensions = null; + }; + + this.computeMaxSize = function() { + var td = _getDimensions(); + return Math.max(td[0], td[1]); + }; + + //override setVisible + var osv = self.setVisible; + self.setVisible = function(state) { + osv(state); // call superclass + div.style.display = state ? "block" : "none"; + }; + + this.cleanup = function() { + if (div != null) jsPlumb.CurrentLibrary.removeElement(div); + }; + + this.paint = function(component, d, componentDimensions) { + if (!initialised) { + self.getElement(); + component.appendDisplayElement(div); + self.attachListeners(div, component); + initialised = true; + } + div.style.left = (componentDimensions[0] + d.minx) + "px"; + div.style.top = (componentDimensions[1] + d.miny) + "px"; + }; + + this.draw = function(component, currentConnectionPaintStyle, componentDimensions) { + var td = _getDimensions(); + if (td != null && td.length == 2) { + var cxy = {x:0,y:0}; + if (component.pointOnPath) { + var loc = self.loc, absolute = false; + if (jsPlumbUtil.isString(self.loc) || self.loc < 0 || self.loc > 1) { + loc = parseInt(self.loc); + absolute = true; + } + cxy = component.pointOnPath(loc, absolute); // a connection + } + else { + var locToUse = self.loc.constructor == Array ? self.loc : self.endpointLoc; + cxy = { x:locToUse[0] * componentDimensions[2], + y:locToUse[1] * componentDimensions[3] }; + } + + minx = cxy.x - (td[0] / 2), + miny = cxy.y - (td[1] / 2); + self.paint(component, { minx:minx, miny:miny, td:td, cxy:cxy }, componentDimensions); + return [minx, minx + td[0], miny, miny + td[1]]; + } + else return [0,0,0,0]; + }; + + this.reattachListeners = function(connector) { + if (div) { + self.reattachListenersForElement(div, self, connector); + } + }; + + }; + + /** + * Class: Overlays.Custom + * A Custom overlay. You supply a 'create' function which returns some DOM element, and jsPlumb positions it. + * The 'create' function is passed a Connection or Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * create - function for jsPlumb to call that returns a DOM element. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * id - optional id to use for later retrieval of this overlay. + * + */ + jsPlumb.Overlays.Custom = function(params) { + this.type = "Custom"; + AbstractDOMOverlay.apply(this, arguments); + }; + + /** + * Class: Overlays.Label + * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb + * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter + * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it + * puts on the Label's 'style' attribute, so the end result is the same. + */ + /** + * Function: Constructor + * + * Parameters: + * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes + * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle. + * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * id - optional id to use for later retrieval of this overlay. + * + */ + jsPlumb.Overlays.Label = function(params) { + var self = this; + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.cssClass = this.labelStyle != null ? this.labelStyle.cssClass : null; + params.create = function() { + return document.createElement("div"); + }; + jsPlumb.Overlays.Custom.apply(this, arguments); + this.type = "Label"; + + var label = params.label || "", + self = this, + labelText = null; + + /* + * Function: setLabel + * sets the label's, um, label. you would think i'd call this function + * 'setText', but you can pass either a Function or a String to this, so + * it makes more sense as 'setLabel'. This uses innerHTML on the label div, so keep + * that in mind if you need escaped HTML. + */ + this.setLabel = function(l) { + label = l; + labelText = null; + self.clearCachedDimensions(); + _update(); + self.component.repaint(); + }; + + var _update = function() { + if (typeof label == "function") { + var lt = label(self); + self.getElement().innerHTML = lt.replace(/\r\n/g, "
"); + } + else { + if (labelText == null) { + labelText = label; + self.getElement().innerHTML = labelText.replace(/\r\n/g, "
"); + } + } + }; + + this.getLabel = function() { + return label; + }; + + var superGD = this.getDimensions; + this.getDimensions = function() { + _update(); + return superGD(); + }; + + }; + + + // ********************************* END OF OVERLAY DEFINITIONS *********************************************************************** + +})(); \ No newline at end of file diff --git a/archive/1.3.14/jsPlumb-dom-adapter-1.3.14-RC1.js b/archive/1.3.14/jsPlumb-dom-adapter-1.3.14-RC1.js new file mode 100644 index 000000000..a9fe3e8fa --- /dev/null +++ b/archive/1.3.14/jsPlumb-dom-adapter-1.3.14-RC1.js @@ -0,0 +1,190 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.14 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the base functionality for DOM type adapters. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ +;(function() { + + var canvasAvailable = !!document.createElement('canvas').getContext, + svgAvailable = !!window.SVGAngle || document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"), + // http://stackoverflow.com/questions/654112/how-do-you-detect-support-for-vml-or-svg-in-a-browser + vmlAvailable = function() { + if (vmlAvailable.vml == undefined) { + var a = document.body.appendChild(document.createElement('div')); + a.innerHTML = ''; + var b = a.firstChild; + b.style.behavior = "url(#default#VML)"; + vmlAvailable.vml = b ? typeof b.adj == "object": true; + a.parentNode.removeChild(a); + } + return vmlAvailable.vml; + }; + + /** + Manages dragging for some instance of jsPlumb. + */ + var DragManager = function(_currentInstance) { + var _draggables = {}, _dlist = [], _delements = {}, _elementsWithEndpoints = {}; + + /** + register some element as draggable. right now the drag init stuff is done elsewhere, and it is + possible that will continue to be the case. + */ + this.register = function(el) { + var jpcl = jsPlumb.CurrentLibrary; + el = jpcl.getElementObject(el); + var id = _currentInstance.getId(el), + domEl = jpcl.getDOMElement(el), + parentOffset = jpcl.getOffset(el); + + if (!_draggables[id]) { + _draggables[id] = el; + _dlist.push(el); + _delements[id] = {}; + } + + // look for child elements that have endpoints and register them against this draggable. + var _oneLevel = function(p, startOffset) { + if (p) { + for (var i = 0; i < p.childNodes.length; i++) { + if (p.childNodes[i].nodeType != 3) { + var cEl = jpcl.getElementObject(p.childNodes[i]), + cid = _currentInstance.getId(cEl, null, true); + if (cid && _elementsWithEndpoints[cid] && _elementsWithEndpoints[cid] > 0) { + var cOff = jpcl.getOffset(cEl); + _delements[id][cid] = { + id:cid, + offset:{ + left:cOff.left - parentOffset.left, + top:cOff.top - parentOffset.top + } + }; + } + _oneLevel(p.childNodes[i]); + } + } + } + }; + + _oneLevel(domEl); + }; + + /** + notification that an endpoint was added to the given el. we go up from that el's parent + node, looking for a parent that has been registered as a draggable. if we find one, we add this + el to that parent's list of elements to update on drag (if it is not there already) + */ + this.endpointAdded = function(el) { + var jpcl = jsPlumb.CurrentLibrary, b = document.body, id = _currentInstance.getId(el), c = jpcl.getDOMElement(el), + p = c.parentNode, done = p == b; + + _elementsWithEndpoints[id] = _elementsWithEndpoints[id] ? _elementsWithEndpoints[id] + 1 : 1; + + while (p != b) { + var pid = _currentInstance.getId(p, null, true); + if (pid && _draggables[pid]) { + var idx = -1, pEl = jpcl.getElementObject(p), pLoc = jpcl.getOffset(pEl); + + if (_delements[pid][id] == null) { + var cLoc = jsPlumb.CurrentLibrary.getOffset(el); + _delements[pid][id] = { + id:id, + offset:{ + left:cLoc.left - pLoc.left, + top:cLoc.top - pLoc.top + } + }; + } + break; + } + p = p.parentNode; + } + }; + + this.endpointDeleted = function(endpoint) { + if (_elementsWithEndpoints[endpoint.elementId]) { + _elementsWithEndpoints[endpoint.elementId]--; + if (_elementsWithEndpoints[endpoint.elementId] <= 0) { + for (var i in _delements) { + delete _delements[i][endpoint.elementId]; + } + } + } + }; + + this.getElementsForDraggable = function(id) { + return _delements[id]; + }; + + this.reset = function() { + _draggables = {}; + _dlist = []; + _delements = {}; + _elementsWithEndpoints = {}; + }; + + }; + + // for those browsers that dont have it. they still don't have it! but at least they won't crash. + if (!window.console) + window.console = { time:function(){}, timeEnd:function(){}, group:function(){}, groupEnd:function(){}, log:function(){} }; + + window.jsPlumbAdapter = { + + headless:false, + + appendToRoot : function(node) { + document.body.appendChild(node); + }, + getRenderModes : function() { + return [ "canvas", "svg", "vml" ] + }, + isRenderModeAvailable : function(m) { + return { + "canvas":canvasAvailable, + "svg":svgAvailable, + "vml":vmlAvailable() + }[m]; + }, + getDragManager : function(_jsPlumb) { + return new DragManager(_jsPlumb); + }, + setRenderMode : function(mode) { + var renderMode; + + if (mode) { + mode = mode.toLowerCase(); + + var canvasAvailable = this.isRenderModeAvailable("canvas"), + svgAvailable = this.isRenderModeAvailable("svg"), + vmlAvailable = this.isRenderModeAvailable("vml"); + + //if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); + // now test we actually have the capability to do this. + if (mode === "svg") { + if (svgAvailable) renderMode = "svg" + else if (canvasAvailable) renderMode = "canvas" + else if (vmlAvailable) renderMode = "vml" + } + else if (mode === "canvas" && canvasAvailable) renderMode = "canvas"; + else if (vmlAvailable) renderMode = "vml"; + } + + return renderMode; + } + }; + +})(); \ No newline at end of file diff --git a/archive/1.3.14/jsPlumb-drag-1.3.14-RC1.js b/archive/1.3.14/jsPlumb-drag-1.3.14-RC1.js new file mode 100644 index 000000000..d71db029e --- /dev/null +++ b/archive/1.3.14/jsPlumb-drag-1.3.14-RC1.js @@ -0,0 +1,61 @@ +/* + * this is experimental and probably will not be used. solutions exist for most libraries. but of course if + * i want to support multiple scopes at some stage then i will have to do dragging inside jsPlumb. + */ +;(function() { + + window.jsPlumbDrag = function(_jsPlumb) { + + var ta = new TouchAdapter(); + + this.draggable = function(selector) { + var el, elId, da = [], elo, d = false, + isInSelector = function(el) { + if (typeof selector == "string") + return selector === _jsPlumb.getId(el); + + for (var i = 0; i < selector.length; i++) { + var _sel = jsPlumb.CurrentLibrary.getDOMElement(selector[i]); + if (_sel == el) return true; + } + return false; + }; + + ta.bind(document, "mousedown", function(e) { + var target = e.target || e.srcElement; + if (isInSelector(target)) { + el = jsPlumb.CurrentLibrary.getElementObject(target); + elId = _jsPlumb.getId(el); + elo = jsPlumb.CurrentLibrary.getOffset(el); + da = [e.pageX, e.pageY]; + d = true; + } + }); + + ta.bind(document, "mousemove", function(e) { + if (d) { + var dx = e.pageX - da[0], + dy = e.pageY - da[1]; + + jsPlumb.CurrentLibrary.setOffset(el, { + left:elo.left + dx, + top:elo.top + dy + }); + _jsPlumb.repaint(elId); + e.preventDefault(); + e.stopPropagation(); + } + }); + ta.bind(document, "mouseup", function(e) { + el = null; + d = false; + }); + }; + + var isIOS = ((/iphone|ipad/gi).test(navigator.appVersion)); + if (isIOS) + _jsPlumb.draggable = this.draggable; + + }; + +})(); \ No newline at end of file diff --git a/archive/1.3.14/jsPlumb-overlays-guidelines-1.3.14-RC1.js b/archive/1.3.14/jsPlumb-overlays-guidelines-1.3.14-RC1.js new file mode 100644 index 000000000..72b63b009 --- /dev/null +++ b/archive/1.3.14/jsPlumb-overlays-guidelines-1.3.14-RC1.js @@ -0,0 +1,73 @@ + // this is really just a test overlay, so its undocumented and doesnt take any parameters. but i was loth to delete it. + jsPlumb.Overlays.GuideLines = function() { + var self = this; + self.length = 50; + self.lineWidth = 5; + this.type = "GuideLines"; + AbstractOverlay.apply(this, arguments); + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var head = connector.pointAlongPathFrom(self.loc, self.length / 2), + mid = connector.pointOnPath(self.loc), + tail = jsPlumbUtil.pointOnLine(head, mid, self.length), + tailLine = jsPlumbUtil.perpendicularLineTo(head, tail, 40), + headLine = jsPlumbUtil.perpendicularLineTo(tail, head, 20); + + self.paint(connector, [head, tail, tailLine, headLine], self.lineWidth, "red", null, connectorDimensions); + + return [Math.min(head.x, tail.x), Math.min(head.y, tail.y), Math.max(head.x, tail.x), Math.max(head.y,tail.y)]; + }; + + this.computeMaxSize = function() { return 50; }; + + this.cleanup = function() { }; // nothing to clean up for GuideLines + }; + +// a test + jsPlumb.Overlays.svg.GuideLines = function() { + var path = null, self = this, path2 = null, p1_1, p1_2; + jsPlumb.Overlays.GuideLines.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + + p1_1 = _node("path"); + connector.svg.appendChild(p1_1); + self.attachListeners(p1_1, connector); + self.attachListeners(p1_1, self); + + p1_2 = _node("path"); + connector.svg.appendChild(p1_2); + self.attachListeners(p1_2, connector); + self.attachListeners(p1_2, self); + + } + + _attr(path, { + "d" : makePath(d[0], d[1]), + stroke : "red", + fill : null + }); + + _attr(p1_1, { + "d" : makePath(d[2][0], d[2][1]), + stroke : "blue", + fill : null + }); + + _attr(p1_2, { + "d" : makePath(d[3][0], d[3][1]), + stroke : "green", + fill : null + }); + }; + + var makePath = function(d1, d2) { + return "M " + d1.x + "," + d1.y + + " L" + d2.x + "," + d2.y; + }; + }; \ No newline at end of file diff --git a/archive/1.3.14/jsPlumb-renderers-canvas-1.3.14-RC1.js b/archive/1.3.14/jsPlumb-renderers-canvas-1.3.14-RC1.js new file mode 100644 index 000000000..c8df8eba1 --- /dev/null +++ b/archive/1.3.14/jsPlumb-renderers-canvas-1.3.14-RC1.js @@ -0,0 +1,510 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.14 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the HTML5 canvas renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + +// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS ******************************************************************* + + // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. + var _connectionBeingDragged = null, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, + _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }; + + /* + * Class:CanvasMouseAdapter + * Provides support for mouse events on canvases. + */ + var CanvasMouseAdapter = function() { + var self = this; + self.overlayPlacements = []; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + jsPlumbUtil.EventGenerator.apply(this, arguments); + /** + * returns whether or not the given event is ojver a painted area of the canvas. + */ + this._over = function(e) { + var o = _getOffset(_getElementObject(self.canvas)), + pageXY = _pageXY(e), + x = pageXY[0] - o.left, y = pageXY[1] - o.top; + if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + return false; + }; + + var _mouseover = false, _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false, + _nullSafeHasClass = function(el, clazz) { + return el != null && _hasClass(el, clazz); + }; + this.mousemove = function(e) { + var pageXY = _pageXY(e), clientXY = _clientXY(e), + ee = document.elementFromPoint(clientXY[0], clientXY[1]), + eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); + var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); + if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.fire("mouseenter", self, e); + return true; + } + // TODO here there is a remote chance that the overlay the mouse moved onto + // is actually not an overlay for the current component. a more thorough check would + // be to ensure the overlay belonged to the current component. + else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { + _mouseover = false; + self.fire("mouseexit", self, e); + } + self.fire("mousemove", self, e); + }; + + this.click = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); + self.fire("mousedown", self, e); + } + }; + + this.mouseup = function(e) { + _mouseDown = false; + self.fire("mouseup", self, e); + }; + + this.contextmenu = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("contextmenu", self, e); + _mouseWasDown = false; + }; + }; + + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + params["_jsPlumb"].appendElement(canvas, params.parent); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + params["_jsPlumb"].getId(canvas, params.uuid); + if (params.tooltip) canvas.setAttribute("title", params.tooltip); + + return canvas; + }; + + var CanvasComponent = function(params) { + CanvasMouseAdapter.apply(this, arguments); + + var displayElements = [ ]; + this.getDisplayElements = function() { return displayElements; }; + this.appendDisplayElement = function(el) { displayElements.push(el); }; + }; + + /** + * Class:CanvasConnector + * Superclass for Canvas Connector renderers. + */ + var CanvasConnector = jsPlumb.CanvasConnector = function(params) { + + CanvasComponent.apply(this, arguments); + + var _paintOneStyle = function(dim, aStyle) { + self.ctx.save(); + jsPlumb.extend(self.ctx, aStyle); + if (aStyle.gradient) { + var g = self.createGradient(dim, self.ctx); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + self.ctx.strokeStyle = g; + } + self._paint(dim, aStyle); + self.ctx.restore(); + }; + + var self = this, + clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:params.tooltip + }); + self.ctx = self.canvas.getContext("2d"); + + self.appendDisplayElement(self.canvas); + + self.paint = function(dim, style) { + if (style != null) { + jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); + if (self.getZIndex()) + self.canvas.style.zIndex = self.getZIndex(); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + _paintOneStyle(dim, outlineStyle); + } + _paintOneStyle(dim, style); + } + }; + }; + + /** + * Class:CanvasEndpoint + * Superclass for Canvas Endpoint renderers. + */ + var CanvasEndpoint = function(params) { + var self = this; + CanvasComponent.apply(this, arguments); + var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""), + canvasParams = { + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:self.tooltip + }; + self.canvas = _newCanvas(canvasParams); + self.ctx = self.canvas.getContext("2d"); + + self.appendDisplayElement(self.canvas); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + } + + self._paint.apply(this, arguments); + }; + }; + + jsPlumb.Endpoints.canvas.Dot = function(params) { + jsPlumb.Endpoints.Dot.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + var self = this, + parseValue = function(value) { + try { return parseInt(value); } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }, + calculateAdjustments = function(gradient) { + var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius; + gradient.offset && (offsetAdjustment = parseValue(gradient.offset)); + gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius)); + return [offsetAdjustment, innerRadius]; + }; + this._paint = function(d, style, anchor) { + if (style != null) { + var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + if (style.gradient) { + var adjustments = calculateAdjustments(style.gradient), + yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0], + xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0], + g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + ctx.beginPath(); + ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + } + }; + }; + + jsPlumb.Endpoints.canvas.Rectangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) { + + var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + + /* canvas gradient */ + if (style.gradient) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, d[2], d[3]); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + jsPlumb.Endpoints.canvas.Triangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Triangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) + { + var width = d[2], height = d[3], x = d[0], y = d[1], + ctx = self.canvas.getContext('2d'), + offsetX = 0, offsetY = 0, angle = 0, + orientation = anchor.getOrientation(self); + + if( orientation[0] == 1 ) { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = style.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + /* + * Canvas Image Endpoint: uses the default version, which creates an tag. + */ + jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image; + + /* + * Blank endpoint in all renderers is just the default Blank endpoint. + */ + jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank; + + /* + * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Bezier = function() { + var self = this; + jsPlumb.Connectors.Bezier.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions, style) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO i doubt this handles the case that source and target are swapped. + this.createGradient = function(dim, ctx, swap) { + return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + /* + * Canvas straight line Connector. Draws a straight line onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Straight = function() { + var self = this, + segmentMultipliers = [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ]; + + jsPlumb.Connectors.Straight.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions, style) { + + self.ctx.beginPath(); + + if (style.dashstyle && style.dashstyle.split(" ").length == 2) { + // only a very simple dashed style is supported - having two values, which define the stroke length + // (as a multiple of the stroke width) and then the space length (also as a multiple of stroke width). + var ds = style.dashstyle.split(" "); + if (ds.length != 2) ds = [2, 2]; + var dss = [ ds[0] * style.lineWidth, ds[1] * style.lineWidth ], + m = (dimensions[6] - dimensions[4]) / (dimensions[7] - dimensions[5]), + s = jsPlumbUtil.segment([dimensions[4], dimensions[5]], [ dimensions[6], dimensions[7] ]), + sm = segmentMultipliers[s], + theta = Math.atan(m), + l = Math.sqrt(Math.pow(dimensions[6] - dimensions[4], 2) + Math.pow(dimensions[7] - dimensions[5], 2)), + repeats = Math.floor(l / (dss[0] + dss[1])), + curPos = [dimensions[4], dimensions[5]]; + + + // TODO: the question here is why could we not support this in all connector types? it's really + // just a case of going along and asking jsPlumb for the next point on the path a few times, until it + // reaches the end. every type of connector supports that method, after all. but right now its only the + // bezier connector that gives you back the new location on the path along with the x,y coordinates, which + // we would need. we'd start out at loc=0 and ask for the point along the path that is dss[0] pixels away. + // we then ask for the point that is (dss[0] + dss[1]) pixels away; and from that one we need not just the + // x,y but the location, cos we're gonna plug that location back in in order to find where that dash ends. + // + // it also strikes me that it should be trivial to support arbitrary dash styles (having more or less than two + // entries). you'd just iterate that array using a step size of 2, and generify the (rss[0] + rss[1]) + // computation to be sum(rss[0]..rss[n]). + + for (var i = 0; i < repeats; i++) { + self.ctx.moveTo(curPos[0], curPos[1]); + + var nextEndX = curPos[0] + (Math.abs(Math.sin(theta) * dss[0]) * sm[0]), + nextEndY = curPos[1] + (Math.abs(Math.cos(theta) * dss[0]) * sm[1]), + nextStartX = curPos[0] + (Math.abs(Math.sin(theta) * (dss[0] + dss[1])) * sm[0]), + nextStartY = curPos[1] + (Math.abs(Math.cos(theta) * (dss[0] + dss[1])) * sm[1]) + + self.ctx.lineTo(nextEndX, nextEndY); + curPos = [nextStartX, nextStartY]; + } + + // now draw the last bit + self.ctx.moveTo(curPos[0], curPos[1]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + + } + else { + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + } + + self.ctx.stroke(); + }; + + // TODO this does not handle the case that src and target are swapped. + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + jsPlumb.Connectors.canvas.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions, style) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + +// ********************************* END OF CANVAS RENDERERS ******************************************************************* + + jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label; + jsPlumb.Overlays.canvas.Custom = jsPlumb.Overlays.Custom; + + /** + * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. + */ + var CanvasOverlay = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }; + + var AbstractCanvasArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + CanvasOverlay.apply(this, originalArgs); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + var ctx = connector.ctx; + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(d.hxy.x, d.hxy.y); + ctx.lineTo(d.tail[0].x, d.tail[0].y); + ctx.lineTo(d.cxy.x, d.cxy.y); + ctx.lineTo(d.tail[1].x, d.tail[1].y); + ctx.lineTo(d.hxy.x, d.hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + }; + + jsPlumb.Overlays.canvas.Arrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.canvas.PlainArrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.canvas.Diamond = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.14/jsPlumb-renderers-svg-1.3.14-RC1.js b/archive/1.3.14/jsPlumb-renderers-svg-1.3.14-RC1.js new file mode 100644 index 000000000..9b5ebafef --- /dev/null +++ b/archive/1.3.14/jsPlumb-renderers-svg-1.3.14-RC1.js @@ -0,0 +1,555 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.14 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the SVG renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +/** + * SVG support for jsPlumb. + * + * things to investigate: + * + * gradients: https://developer.mozilla.org/en/svg_in_html_introduction + * css:http://tutorials.jenkov.com/svg/svg-and-css.html + * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath + * pointer events: https://developer.mozilla.org/en/css/pointer-events + * + * IE9 hover jquery: http://forum.jquery.com/topic/1-6-2-broke-svg-hover-events + * + */ +;(function() { + + var svgAttributeMap = { + "joinstyle":"stroke-linejoin", + "stroke-linejoin":"stroke-linejoin", + "stroke-dashoffset":"stroke-dashoffset", + "stroke-linecap":"stroke-linecap" + }, + STROKE_DASHARRAY = "stroke-dasharray", + DASHSTYLE = "dashstyle", + LINEAR_GRADIENT = "linearGradient", + RADIAL_GRADIENT = "radialGradient", + FILL = "fill", + STOP = "stop", + STROKE = "stroke", + STROKE_WIDTH = "stroke-width", + STYLE = "style", + NONE = "none", + JSPLUMB_GRADIENT = "jsplumb_gradient_", + LINE_WIDTH = "lineWidth", + ns = { + svg:"http://www.w3.org/2000/svg", + xhtml:"http://www.w3.org/1999/xhtml" + }, + _attr = function(node, attributes) { + for (var i in attributes) + node.setAttribute(i, "" + attributes[i]); + }, + _node = function(name, attributes) { + var n = document.createElementNS(ns.svg, name); + attributes = attributes || {}; + attributes["version"] = "1.1"; + attributes["xmlns"] = ns.xhtml; + _attr(n, attributes); + return n; + }, + _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; }, + _clearGradient = function(parent) { + for (var i = 0; i < parent.childNodes.length; i++) { + if (parent.childNodes[i].tagName == LINEAR_GRADIENT || parent.childNodes[i].tagName == RADIAL_GRADIENT) + parent.removeChild(parent.childNodes[i]); + } + }, + _updateGradient = function(parent, node, style, dimensions, uiComponent) { + var id = JSPLUMB_GRADIENT + uiComponent._jsPlumb.idstamp(); + // first clear out any existing gradient + _clearGradient(parent); + // this checks for an 'offset' property in the gradient, and in the absence of it, assumes + // we want a linear gradient. if it's there, we create a radial gradient. + // it is possible that a more explicit means of defining the gradient type would be + // better. relying on 'offset' means that we can never have a radial gradient that uses + // some default offset, for instance. + // issue 244 suggested the 'gradientUnits' attribute; without this, straight/flowchart connectors with gradients would + // not show gradients when the line was perfectly horizontal or vertical. + if (!style.gradient.offset) { + var g = _node(LINEAR_GRADIENT, {id:id, gradientUnits:"userSpaceOnUse"}); + parent.appendChild(g); + } + else { + var g = _node(RADIAL_GRADIENT, { + id:id + }); + parent.appendChild(g); + } + + // the svg radial gradient seems to treat stops in the reverse + // order to how canvas does it. so we want to keep all the maths the same, but + // iterate the actual style declarations in reverse order, if the x indexes are not in order. + for (var i = 0; i < style.gradient.stops.length; i++) { + // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of + // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments. + // so, not too concerned about leaving this in for now. + var styleToUse = i; + if (dimensions.length == 8) + styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i; + else + styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i; + var stopColor = jsPlumbUtil.convertStyle(style.gradient.stops[styleToUse][1], true); + var s = _node(STOP, {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor}); + g.appendChild(s); + } + var applyGradientTo = style.strokeStyle ? STROKE : FILL; + node.setAttribute(STYLE, applyGradientTo + ":url(#" + id + ")"); + }, + _applyStyles = function(parent, node, style, dimensions, uiComponent) { + + if (style.gradient) { + _updateGradient(parent, node, style, dimensions, uiComponent); + } + else { + // make sure we clear any existing gradient + _clearGradient(parent); + node.setAttribute(STYLE, ""); + } + + node.setAttribute(FILL, style.fillStyle ? jsPlumbUtil.convertStyle(style.fillStyle, true) : NONE); + node.setAttribute(STROKE, style.strokeStyle ? jsPlumbUtil.convertStyle(style.strokeStyle, true) : NONE); + if (style.lineWidth) { + node.setAttribute(STROKE_WIDTH, style.lineWidth); + } + + // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like + // the syntax in VML but is actually kind of nasty: values are given in the pixel + // coordinate space, whereas in VML they are multiples of the width of the stroked + // line, which makes a lot more sense. for that reason, jsPlumb is supporting both + // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from + // VML, which will be the preferred method. the code below this converts a dashstyle + // attribute given in terms of stroke width into a pixel representation, by using the + // stroke's lineWidth. + if (style[DASHSTYLE] && style[LINE_WIDTH] && !style[STROKE_DASHARRAY]) { + var sep = style[DASHSTYLE].indexOf(",") == -1 ? " " : ",", + parts = style[DASHSTYLE].split(sep), + styleToUse = ""; + parts.forEach(function(p) { + styleToUse += (Math.floor(p * style.lineWidth) + sep); + }); + node.setAttribute(STROKE_DASHARRAY, styleToUse); + } + else if(style[STROKE_DASHARRAY]) { + node.setAttribute(STROKE_DASHARRAY, style[STROKE_DASHARRAY]); + } + + // extra attributes such as join type, dash offset. + for (var i in svgAttributeMap) { + if (style[i]) { + node.setAttribute(svgAttributeMap[i], style[i]); + } + } + }, + _decodeFont = function(f) { + var r = /([0-9].)(p[xt])\s(.*)/; + var bits = f.match(r); + return {size:bits[1] + bits[2], font:bits[3]}; + }, + _classManip = function(el, add, clazz) { + var classesToAddOrRemove = clazz.split(" "), + className = el.className, + curClasses = className.baseVal.split(" "); + + for (var i = 0; i < classesToAddOrRemove.length; i++) { + if (add) { + if (curClasses.indexOf(classesToAddOrRemove[i]) == -1) + curClasses.push(classesToAddOrRemove[i]); + } + else { + var idx = curClasses.indexOf(classesToAddOrRemove[i]); + if (idx != -1) + curClasses.splice(idx, 1); + } + } + + el.className.baseVal = curClasses.join(" "); + }, + _addClass = function(el, clazz) { + _classManip(el, true, clazz); + }, + _removeClass = function(el, clazz) { + _classManip(el, false, clazz); + }; + + /** + utility methods for other objects to use. + */ + jsPlumbUtil.svg = { + addClass:_addClass, + removeClass:_removeClass, + node:_node, + attr:_attr, + pos:_pos + }; + + /* + * Base class for SVG components. + */ + var SvgComponent = function(params) { + var self = this, + pointerEventsSpec = params.pointerEventsSpec || "all"; + jsPlumb.jsPlumbUIComponent.apply(this, params.originalArgs); + self.canvas = null, self.path = null, self.svg = null; + + var clazz = params.cssClass + " " + (params.originalArgs[0].cssClass || ""), + svgParams = { + "style":"", + "width":0, + "height":0, + "pointer-events":pointerEventsSpec, + "position":"absolute" + }; + if (self.tooltip) svgParams["title"] = self.tooltip; + self.svg = _node("svg", svgParams); + if (params.useDivWrapper) { + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.sizeCanvas(self.canvas,0,0,1,1); + self.canvas.className = clazz; + if (self.tooltip) self.canvas.setAttribute("title", self.tooltip); + } + else { + _attr(self.svg, { "class":clazz }); + self.canvas = self.svg; + } + + params._jsPlumb.appendElement(self.canvas, params.originalArgs[0]["parent"]); + if (params.useDivWrapper) self.canvas.appendChild(self.svg); + + // TODO this displayElement stuff is common between all components, across all + // renderers. would be best moved to jsPlumbUIComponent. + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + this.paint = function(d, style, anchor) { + if (style != null) { + var x = d[0], y = d[1]; + if (params.useDivWrapper) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + x = 0, y = 0; + } + var p = _pos([x, y, d[2], d[3]]); + if (self.getZIndex()) p += ";z-index:" + self.getZIndex() + ";"; + _attr(self.svg, { + "style":p, + "width": d[2], + "height": d[3] + }); + self._paint.apply(this, arguments); + } + }; + }; + + /* + * Base class for SVG connectors. + */ + var SvgConnector = jsPlumb.SvgConnector = function(params) { + var self = this; + SvgComponent.apply(this, [ { + cssClass:params["_jsPlumb"].connectorClass, + originalArgs:arguments, + pointerEventsSpec:"none", + tooltip:params.tooltip, + _jsPlumb:params["_jsPlumb"] + } ]); + this._paint = function(d, style) { + var p = self.getPath(d), a = { "d":p }, outlineStyle = null; + a["pointer-events"] = "all"; + + // outline style. actually means drawing an svg object underneath the main one. + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = jsPlumb.CurrentLibrary.extend({}, style); + outlineStyle.strokeStyle = jsPlumbUtil.convertStyle(style.outlineColor); + outlineStyle.lineWidth = outlineStrokeWidth; + + if (self.bgPath == null) { + self.bgPath = _node("path", a); + self.svg.appendChild(self.bgPath); + self.attachListeners(self.bgPath, self); + } + else { + _attr(self.bgPath, a); + } + + _applyStyles(self.svg, self.bgPath, outlineStyle, d, self); + } + + + // test - see below + // a["clip-path"]= "url(#testClip)"; + + if (self.path == null) { + self.path = _node("path", a); + self.svg.appendChild(self.path); + self.attachListeners(self.path, self); + + /* + this is a test of a clip path. i'm looking into using one of these to animate a jsplumb connection. + you could do this by walking along the line, stepping along a little at a time, and setting the clip + path to extend as far as that point. + + self.clip = _node("clipPath", {id:"testClip", clipPathUnits:"objectBoundingBox"}); + self.svg.appendChild(self.clip); + self.clip.appendChild(_node("rect", { + x:"0",y:"0",width:"0.5",height:"1" + })); + */ + } + else { + _attr(self.path, a); + } + + _applyStyles(self.svg, self.path, style, d, self); + }; + + this.reattachListeners = function() { + if (self.bgPath) self.reattachListenersForElement(self.bgPath, self); + if (self.path) self.reattachListenersForElement(self.path, self); + }; + + }; + + /* + * SVG Bezier Connector + */ + jsPlumb.Connectors.svg.Bezier = function(params) { + jsPlumb.Connectors.Bezier.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { + var _p = "M " + d[4] + " " + d[5]; + _p += (" C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]); + return _p; + }; + }; + + /* + * SVG straight line Connector + */ + jsPlumb.Connectors.svg.Straight = function(params) { + jsPlumb.Connectors.Straight.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; }; + }; + + jsPlumb.Connectors.svg.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "M " + dimensions[4] + "," + dimensions[5]; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)]; + } + // finally draw a line to the end + p = p + " " + dimensions[6] + "," + dimensions[7]; + return p; + }; + }; + + /* + * Base class for SVG endpoints. + */ + var SvgEndpoint = window.SvgEndpoint = function(params) { + var self = this; + SvgComponent.apply(this, [ { + cssClass:params["_jsPlumb"].endpointClass, + originalArgs:arguments, + pointerEventsSpec:"all", + useDivWrapper:true, + _jsPlumb:params["_jsPlumb"] + } ]); + this._paint = function(d, style) { + var s = jsPlumb.extend({}, style); + if (s.outlineColor) { + s.strokeWidth = s.outlineWidth; + s.strokeStyle = jsPlumbUtil.convertStyle(s.outlineColor, true); + } + + if (self.node == null) { + self.node = self.makeNode(d, s); + self.svg.appendChild(self.node); + self.attachListeners(self.node, self); + } + _applyStyles(self.svg, self.node, s, d, self); + _pos(self.node, d); + }; + + this.reattachListeners = function() { + if (self.node) self.reattachListenersForElement(self.node, self); + }; + }; + + /* + * SVG Dot Endpoint + */ + jsPlumb.Endpoints.svg.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("circle", { + "cx" : d[2] / 2, + "cy" : d[3] / 2, + "r" : d[2] / 2 + }); + }; + }; + + /* + * SVG Rectangle Endpoint + */ + jsPlumb.Endpoints.svg.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("rect", { + "width":d[2], + "height":d[3] + }); + }; + }; + + /* + * SVG Image Endpoint is the default image endpoint. + */ + jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image; + /* + * Blank endpoint in svg renderer is the default Blank endpoint. + */ + jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank; + /* + * Label overlay in svg renderer is the default Label overlay. + */ + jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label; + /* + * Custom overlay in svg renderer is the default Custom overlay. + */ + jsPlumb.Overlays.svg.Custom = jsPlumb.Overlays.Custom; + + var AbstractSvgArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + this.isAppendedAtTopLevel = false; + var self = this, path = null; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path", { + "pointer-events":"all" + }); + connector.svg.appendChild(path); + + self.attachListeners(path, connector); + self.attachListeners(path, self); + } + var clazz = originalArgs && (originalArgs.length == 1) ? (originalArgs[0].cssClass || "") : ""; + + _attr(path, { + "d" : makePath(d), + "class" : clazz, + stroke : strokeStyle ? strokeStyle : null, + fill : fillStyle ? fillStyle : null + }); + }; + var makePath = function(d) { + return "M" + d.hxy.x + "," + d.hxy.y + + " L" + d.tail[0].x + "," + d.tail[0].y + + " L" + d.cxy.x + "," + d.cxy.y + + " L" + d.tail[1].x + "," + d.tail[1].y + + " L" + d.hxy.x + "," + d.hxy.y; + }; + this.reattachListeners = function() { + if (path) self.reattachListenersForElement(path, self); + }; + this.cleanup = function() { + if (path != null) jsPlumb.CurrentLibrary.removeElement(path); + }; + }; + + jsPlumb.Overlays.svg.Arrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.svg.PlainArrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.svg.Diamond = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; + + // a test + jsPlumb.Overlays.svg.GuideLines = function() { + var path = null, self = this, path2 = null, p1_1, p1_2; + jsPlumb.Overlays.GuideLines.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + + p1_1 = _node("path"); + connector.svg.appendChild(p1_1); + self.attachListeners(p1_1, connector); + self.attachListeners(p1_1, self); + + p1_2 = _node("path"); + connector.svg.appendChild(p1_2); + self.attachListeners(p1_2, connector); + self.attachListeners(p1_2, self); + + } + + _attr(path, { + "d" : makePath(d[0], d[1]), + stroke : "red", + fill : null + }); + + _attr(p1_1, { + "d" : makePath(d[2][0], d[2][1]), + stroke : "blue", + fill : null + }); + + _attr(p1_2, { + "d" : makePath(d[3][0], d[3][1]), + stroke : "green", + fill : null + }); + }; + + var makePath = function(d1, d2) { + return "M " + d1.x + "," + d1.y + + " L" + d2.x + "," + d2.y; + }; + + }; +})(); \ No newline at end of file diff --git a/archive/1.3.14/jsPlumb-renderers-vml-1.3.14-RC1.js b/archive/1.3.14/jsPlumb-renderers-vml-1.3.14-RC1.js new file mode 100644 index 000000000..db6731e2d --- /dev/null +++ b/archive/1.3.14/jsPlumb-renderers-vml-1.3.14-RC1.js @@ -0,0 +1,454 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.14 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the VML renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + // http://ajaxian.com/archives/the-vml-changes-in-ie-8 + // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/ + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + + var vmlAttributeMap = { + "stroke-linejoin":"joinstyle", + "joinstyle":"joinstyle", + "endcap":"endcap", + "miterlimit":"miterlimit" + }, + jsPlumbStylesheet = null; + + if (document.createStyleSheet && document.namespaces) { + + var ruleClasses = [ + ".jsplumb_vml", "jsplumb\\:textbox", "jsplumb\\:oval", "jsplumb\\:rect", + "jsplumb\\:stroke", "jsplumb\\:shape", "jsplumb\\:group" + ], + rule = "behavior:url(#default#VML);position:absolute;"; + + jsPlumbStylesheet = document.createStyleSheet(); + + for (var i = 0; i < ruleClasses.length; i++) + jsPlumbStylesheet.addRule(ruleClasses[i], rule); + + // in this page it is also mentioned that IE requires the extra arg to the namespace + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either. + // var iev = document.documentMode; + //if (!iev || iev < 8) + document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); + //else + // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML"); + } + + jsPlumb.vml = {}; + + var scale = 1000, + + _groupMap = {}, + _getGroup = function(container, connectorClass) { + var id = jsPlumb.getId(container), + g = _groupMap[id]; + if(!g) { + g = _node("group", [0,0,scale, scale], {"class":connectorClass}); + //g.style.position=absolute; + //g["coordsize"] = "1000,1000"; + g.style.backgroundColor="red"; + _groupMap[id] = g; + jsPlumb.appendElement(g, container); // todo if this gets reinstated, remember to use the current jsplumb instance. + //document.body.appendChild(g); + } + return g; + }, + _atts = function(o, atts) { + for (var i in atts) { + // IE8 fix: setattribute does not work after an element has been added to the dom! + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + //o.setAttribute(i, atts[i]); + + /*There is an additional problem when accessing VML elements by using get/setAttribute. The simple solution is following: + + if (document.documentMode==8) { + ele.opacity=1; + } else { + ele.setAttribute(‘opacity’,1); + } + */ + + o[i] = atts[i]; + } + }, + _node = function(name, d, atts, parent, _jsPlumb, deferToJsPlumbContainer) { + atts = atts || {}; + var o = document.createElement("jsplumb:" + name); + if (deferToJsPlumbContainer) + _jsPlumb.appendElement(o, parent); + else + jsPlumb.CurrentLibrary.appendElement(o, parent); + o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml"; + _pos(o, d); + _atts(o, atts); + return o; + }, + _pos = function(o,d, zIndex) { + o.style.left = d[0] + "px"; + o.style.top = d[1] + "px"; + o.style.width= d[2] + "px"; + o.style.height= d[3] + "px"; + o.style.position = "absolute"; + if (zIndex) + o.style.zIndex = zIndex; + }, + _conv = jsPlumb.vml.convertValue = function(v) { + return Math.floor(v * scale); + }, + // tests if the given style is "transparent" and then sets the appropriate opacity node to 0 if so, + // or 1 if not. TODO in the future, support variable opacity. + _maybeSetOpacity = function(styleToWrite, styleToCheck, type, component) { + if ("transparent" === styleToCheck) + component.setOpacity(type, "0.0"); + else + component.setOpacity(type, "1.0"); + }, + _applyStyles = function(node, style, component, _jsPlumb) { + var styleToWrite = {}; + if (style.strokeStyle) { + styleToWrite["stroked"] = "true"; + var strokeColor = jsPlumbUtil.convertStyle(style.strokeStyle, true); + styleToWrite["strokecolor"] = strokeColor; + _maybeSetOpacity(styleToWrite, strokeColor, "stroke", component); + styleToWrite["strokeweight"] = style.lineWidth + "px"; + } + else styleToWrite["stroked"] = "false"; + + if (style.fillStyle) { + styleToWrite["filled"] = "true"; + var fillColor = jsPlumbUtil.convertStyle(style.fillStyle, true); + styleToWrite["fillcolor"] = fillColor; + _maybeSetOpacity(styleToWrite, fillColor, "fill", component); + } + else styleToWrite["filled"] = "false"; + + if(style["dashstyle"]) { + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }, node, _jsPlumb); + } + else + component.strokeNode.dashstyle = style["dashstyle"]; + } + else if (style["stroke-dasharray"] && style["lineWidth"]) { + var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",", + parts = style["stroke-dasharray"].split(sep), + styleToUse = ""; + for(var i = 0; i < parts.length; i++) { + styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep); + } + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }, node, _jsPlumb); + } + else + component.strokeNode.dashstyle = styleToUse; + } + + _atts(node, styleToWrite); + }, + /* + * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. + */ + VmlComponent = function() { + var self = this; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.opacityNodes = { + "stroke":null, + "fill":null + }; + this.initOpacityNodes = function(vml) { + self.opacityNodes["stroke"] = _node("stroke", [0,0,1,1], {opacity:"0.0"}, vml, self._jsPlumb); + self.opacityNodes["fill"] = _node("fill", [0,0,1,1], {opacity:"0.0"}, vml, self._jsPlumb); + }; + this.setOpacity = function(type, value) { + var node = self.opacityNodes[type]; + if (node) node["opacity"] = "" + value; + }; + var displayElements = [ ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el, doNotAppendToCanvas) { + if (!doNotAppendToCanvas) self.canvas.parentNode.appendChild(el); + displayElements.push(el); + }; + }, + /* + * Base class for Vml connectors. extends VmlComponent. + */ + VmlConnector = jsPlumb.VmlConnector = function(params) { + var self = this; + self.strokeNode = null; + self.canvas = null; + VmlComponent.apply(this, arguments); + var clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : ""); + this.paint = function(d, style, anchor) { + if (style != null) { + var path = self.getPath(d), p = { "path":path }; + + //* + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle : jsPlumbUtil.convertStyle(style.outlineColor), + lineWidth : outlineStrokeWidth + }; + for (var aa in vmlAttributeMap) outlineStyle[aa] = style[aa]; + + if (self.bgCanvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.bgCanvas = _node("shape", d, p, params.parent, self._jsPlumb, true); + _pos(self.bgCanvas, d, self.getZIndex()); + self.appendDisplayElement(self.bgCanvas, true); + self.attachListeners(self.bgCanvas, self); + self.initOpacityNodes(self.bgCanvas, ["stroke"]); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.bgCanvas, d, self.getZIndex()); + _atts(self.bgCanvas, p); + } + + _applyStyles(self.bgCanvas, outlineStyle, self); + } + //*/ + + if (self.canvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + if (self.tooltip) p["label"] = self.tooltip; + self.canvas = _node("shape", d, p, params.parent, self._jsPlumb, true); + //var group = _getGroup(params.parent); // test of append everything to a group + //group.appendChild(self.canvas); // sort of works but not exactly; + //params["_jsPlumb"].appendElement(self.canvas, params.parent); //before introduction of groups + + self.appendDisplayElement(self.canvas, true); + self.attachListeners(self.canvas, self); + self.initOpacityNodes(self.canvas, ["stroke"]); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.canvas, d, self.getZIndex()); + _atts(self.canvas, p); + } + + _applyStyles(self.canvas, style, self, self._jsPlumb); + } + }; + + //self.appendDisplayElement(self.canvas); + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + }, + /* + * + * Base class for Vml Endpoints. extends VmlComponent. + * + */ + VmlEndpoint = window.VmlEndpoint = function(params) { + VmlComponent.apply(this, arguments); + var vml = null, self = this, opacityStrokeNode = null, opacityFillNode = null; + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + + var clazz = self._jsPlumb.endpointClass + (params.cssClass ? (" " + params.cssClass) : ""); + + //var group = _getGroup(params.parent); + //group.appendChild(self.canvas); + params["_jsPlumb"].appendElement(self.canvas, params.parent); + + if (self.tooltip) self.canvas.setAttribute("label", self.tooltip); + + this.paint = function(d, style, anchor) { + var p = { }; + + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (vml == null) { + p["class"] = clazz; + vml = self.getVml([0,0, d[2], d[3]], p, anchor, self.canvas, self._jsPlumb); + self.attachListeners(vml, self); + + self.appendDisplayElement(vml, true); + self.appendDisplayElement(self.canvas, true); + + self.initOpacityNodes(vml, ["fill"]); + } + else { + _pos(vml, [0,0, d[2], d[3]]); + _atts(vml, p); + } + + _applyStyles(vml, style, self); + }; + + this.reattachListeners = function() { + if (vml) self.reattachListenersForElement(vml, self); + }; + }; + + jsPlumb.Connectors.vml.Bezier = function() { + jsPlumb.Connectors.Bezier.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Straight = function() { + jsPlumb.Connectors.Straight.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Flowchart = function() { + jsPlumb.Connectors.Flowchart.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l"; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]); + } + // finally draw a line to the end + p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e"; + return p; + }; + }; + + jsPlumb.Endpoints.vml.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor, parent, _jsPlumb) { return _node("oval", d, atts, parent, _jsPlumb); }; + }; + + jsPlumb.Endpoints.vml.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor, parent, _jsPlumb) { return _node("rect", d, atts, parent, _jsPlumb); }; + }; + + /* + * VML Image Endpoint is the same as the default image endpoint. + */ + jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image; + + /** + * placeholder for Blank endpoint in vml renderer. + */ + jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank; + + /** + * VML Label renderer. uses the default label renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label; + + /** + * VML Custom renderer. uses the default Custom renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Custom = jsPlumb.Overlays.Custom; + + var AbstractVmlArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + VmlComponent.apply(this, originalArgs); + var self = this, path = null; + self.canvas = null; + self.isAppendedAtTopLevel = true; + var getPath = function(d, connectorDimensions) { + return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) + + " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + + " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + + " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + + " x e"; + }; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) { + var p = {}; + if (strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] = jsPlumbUtil.convertStyle(strokeStyle, true); + } + if (lineWidth) p["strokeweight"] = lineWidth + "px"; + if (fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = fillStyle; + } + var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + w = Math.abs(xmax - xmin), + h = Math.abs(ymax - ymin), + dim = [xmin, ymin, w, h]; + + // for VML, we create overlays using shapes that have the same dimensions and + // coordsize as their connector - overlays calculate themselves relative to the + // connector (it's how it's been done since the original canvas implementation, because + // for canvas that makes sense). + p["path"] = getPath(d, connectorDimensions); + p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale); + + dim[0] = connectorDimensions[0]; + dim[1] = connectorDimensions[1]; + dim[2] = connectorDimensions[2]; + dim[3] = connectorDimensions[3]; + + if (self.canvas == null) { + //p["class"] = jsPlumb.overlayClass; // TODO currentInstance? + self.canvas = _node("shape", dim, p, connector.canvas.parentNode, connector._jsPlumb, true); + connector.appendDisplayElement(self.canvas, true); + self.attachListeners(self.canvas, connector); + self.attachListeners(self.canvas, self); + } + else { + _pos(self.canvas, dim); + _atts(self.canvas, p); + } + }; + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + + this.cleanup = function() { + if (self.canvas != null) jsPlumb.CurrentLibrary.removeElement(self.canvas); + }; + }; + + jsPlumb.Overlays.vml.Arrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.vml.PlainArrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.vml.Diamond = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.14/jsPlumb-util-1.3.14-RC1.js b/archive/1.3.14/jsPlumb-util-1.3.14-RC1.js new file mode 100644 index 000000000..cdb732d23 --- /dev/null +++ b/archive/1.3.14/jsPlumb-util-1.3.14-RC1.js @@ -0,0 +1,268 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.14 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the util functions + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ +jsPlumbUtil = { + isArray : function(a) { + return Object.prototype.toString.call(a) === "[object Array]"; + }, + isString : function(s) { + return typeof s === "string"; + }, + isBoolean: function(s) { + return typeof s === "boolean"; + }, + isObject : function(o) { + return Object.prototype.toString.call(o) === "[object Object]"; + }, + isDate : function(o) { + return Object.prototype.toString.call(o) === "[object Date]"; + }, + isFunction: function(o) { + return Object.prototype.toString.call(o) === "[object Function]"; + }, + clone : function(a) { + if (this.isString(a)) return new String(a); + else if (this.isBoolean(a)) return new Boolean(a); + else if (this.isDate(a)) return new Date(a.getTime()); + else if (this.isFunction(a)) return a; + else if (this.isArray(a)) { + var b = []; + for (var i = 0; i < a.length; i++) + b.push(this.clone(a[i])); + return b; + } + else if (this.isObject(a)) { + var b = {}; + for (var i in a) + b[i] = this.clone(a[i]); + return b; + } + else return a; + }, + merge : function(a, b) { + var c = this.clone(a); + for (var i in b) { + if (c[i] == null || this.isString(b[i]) || this.isBoolean(b[i])) + c[i] = b[i]; + else { + if (this.isArray(b[i]) && this.isArray(c[i])) { + var ar = []; + ar.push.apply(ar, c[i]); + ar.push.apply(ar, b[i]); + c[i] = ar; + } + else if(this.isObject(c[i]) && this.isObject(b[i])) { + for (var j in b[i]) + c[i][j] = b[i][j]; + } + } + } + return c; + }, + convertStyle : function(s, ignoreAlpha) { + // TODO: jsPlumb should support a separate 'opacity' style member. + if ("transparent" === s) return s; + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + return o; + }, + gradient : function(p1, p2) { + p1 = jsPlumbUtil.isArray(p1) ? p1 : [p1.x, p1.y]; + p2 = jsPlumbUtil.isArray(p2) ? p2 : [p2.x, p2.y]; + return (p2[1] - p1[1]) / (p2[0] - p1[0]); + }, + normal : function(p1, p2) { + return -1 / jsPlumbUtil.gradient(p1,p2); + }, + lineLength : function(p1, p2) { + p1 = jsPlumbUtil.isArray(p1) ? p1 : [p1.x, p1.y]; + p2 = jsPlumbUtil.isArray(p2) ? p2 : [p2.x, p2.y]; + return Math.sqrt(Math.pow(p2[1] - p1[1], 2) + Math.pow(p2[0] - p1[0], 2)); + }, + segment : function(p1, p2) { + p1 = jsPlumbUtil.isArray(p1) ? p1 : [p1.x, p1.y]; + p2 = jsPlumbUtil.isArray(p2) ? p2 : [p2.x, p2.y]; + if (p2[0] > p1[0]) { + return (p2[1] > p1[1]) ? 2 : 1; + } + else { + return (p2[1] > p1[1]) ? 3 : 4; + } + }, + intersects : function(r1, r2) { + var x1 = r1.x, x2 = r1.x + r1.w, y1 = r1.y, y2 = r1.y + r1.h, + a1 = r2.x, a2 = r2.x + r2.w, b1 = r2.y, b2 = r2.y + r2.h; + + return ( (x1 <= a1 && a1 <= x2) && (y1 <= b1 && b1 <= y2) ) || + ( (x1 <= a2 && a2 <= x2) && (y1 <= b1 && b1 <= y2) ) || + ( (x1 <= a1 && a1 <= x2) && (y1 <= b2 && b2 <= y2) ) || + ( (x1 <= a2 && a1 <= x2) && (y1 <= b2 && b2 <= y2) ) || + + ( (a1 <= x1 && x1 <= a2) && (b1 <= y1 && y1 <= b2) ) || + ( (a1 <= x2 && x2 <= a2) && (b1 <= y1 && y1 <= b2) ) || + ( (a1 <= x1 && x1 <= a2) && (b1 <= y2 && y2 <= b2) ) || + ( (a1 <= x2 && x1 <= a2) && (b1 <= y2 && y2 <= b2) ); + }, + segmentMultipliers : [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ], + inverseSegmentMultipliers : [null, [-1, -1], [-1, 1], [1, 1], [1, -1] ], + pointOnLine : function(fromPoint, toPoint, distance) { + var m = jsPlumbUtil.gradient(fromPoint, toPoint), + s = jsPlumbUtil.segment(fromPoint, toPoint), + segmentMultiplier = distance > 0 ? jsPlumbUtil.segmentMultipliers[s] : jsPlumbUtil.inverseSegmentMultipliers[s], + theta = Math.atan(m), + y = Math.abs(distance * Math.sin(theta)) * segmentMultiplier[1], + x = Math.abs(distance * Math.cos(theta)) * segmentMultiplier[0]; + return { x:fromPoint.x + x, y:fromPoint.y + y }; + }, + /** + * calculates a perpendicular to the line fromPoint->toPoint, that passes through toPoint and is 'length' long. + * @param fromPoint + * @param toPoint + * @param length + */ + perpendicularLineTo : function(fromPoint, toPoint, length) { + var m = jsPlumbUtil.gradient(fromPoint, toPoint), + theta2 = Math.atan(-1 / m), + y = length / 2 * Math.sin(theta2), + x = length / 2 * Math.cos(theta2); + return [{x:toPoint.x + x, y:toPoint.y + y}, {x:toPoint.x - x, y:toPoint.y - y}]; + }, + findWithFunction : function(a, f) { + if (a) + for (var i = 0; i < a.length; i++) if (f(a[i])) return i; + return -1; + }, + indexOf : function(l, v) { + return jsPlumbUtil.findWithFunction(l, function(_v) { return _v == v; }); + }, + removeWithFunction : function(a, f) { + var idx = jsPlumbUtil.findWithFunction(a, f); + if (idx > -1) a.splice(idx, 1); + return idx != -1; + }, + remove : function(l, v) { + var idx = jsPlumbUtil.indexOf(l, v); + if (idx > -1) l.splice(idx, 1); + return idx != -1; + }, + // TODO support insert index + addWithFunction : function(list, item, hashFunction) { + if (jsPlumbUtil.findWithFunction(list, hashFunction) == -1) list.push(item); + }, + addToList : function(map, key, value) { + var l = map[key]; + if (l == null) { + l = [], map[key] = l; + } + l.push(value); + return l; + }, + /** + * EventGenerator + * Superclass for objects that generate events - jsPlumb extends this, as does jsPlumbUIComponent, which all the UI elements extend. + */ + EventGenerator : function() { + var _listeners = {}, self = this; + + // this is a list of events that should re-throw any errors that occur during their dispatch. as of 1.3.0 this is private to + // jsPlumb, but it seems feasible that people might want to manipulate this list. the thinking is that we don't want event + // listeners to bring down jsPlumb - or do we. i can't make up my mind about this, but i know i want to hear about it if the "ready" + // event fails, because then my page has most likely not initialised. so i have this halfway-house solution. it will be interesting + // to hear what other people think. + var eventsToDieOn = [ "ready" ]; + + /* + * Binds a listener to an event. + * + * Parameters: + * event - name of the event to bind to. + * listener - function to execute. + */ + this.bind = function(event, listener) { + jsPlumbUtil.addToList(_listeners, event, listener); + return self; + }; + /* + * Fires an update for the given event. + * + * Parameters: + * event - event to fire + * value - value to pass to the event listener(s). + * originalEvent - the original event from the browser + */ + this.fire = function(event, value, originalEvent) { + if (_listeners[event]) { + for ( var i = 0; i < _listeners[event].length; i++) { + // doing it this way rather than catching and then possibly re-throwing means that an error propagated by this + // method will have the whole call stack available in the debugger. + if (jsPlumbUtil.findWithFunction(eventsToDieOn, function(e) { return e === event}) != -1) + _listeners[event][i](value, originalEvent); + else { + // for events we don't want to die on, catch and log. + try { + _listeners[event][i](value, originalEvent); + } catch (e) { + jsPlumbUtil.log("jsPlumb: fire failed for event " + event + " : " + e); + } + } + } + } + return self; + }; + /* + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + this.unbind = function(event) { + if (event) + delete _listeners[event]; + else { + _listeners = {}; + } + return self; + }; + + this.getListener = function(forEvent) { + return _listeners[forEvent]; + }; + }, + logEnabled : true, + log : function() { + if (jsPlumbUtil.logEnabled && typeof console != "undefined") { + try { + var msg = arguments[arguments.length - 1]; + console.log(msg); + } + catch (e) {} + } + }, + group : function(g) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.group(g); }, + groupEnd : function(g) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.groupEnd(g); }, + time : function(t) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.time(t); }, + timeEnd : function(t) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.timeEnd(t); } +}; \ No newline at end of file diff --git a/archive/1.3.14/mootools.jsPlumb-1.3.14-RC1.js b/archive/1.3.14/mootools.jsPlumb-1.3.14-RC1.js new file mode 100644 index 000000000..7b9a00f5d --- /dev/null +++ b/archive/1.3.14/mootools.jsPlumb-1.3.14-RC1.js @@ -0,0 +1,440 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.14 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the MooTools adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function(now) { + this.parent(now); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event, originalEvent) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr, originalEvent); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, _scope, value) { + var l = list[_scope]; + if (!l) { + l = []; + list[_scope] = l; + } + l.push(value); + }; + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + var _getElementObject = function(el) { + return $(el); + }; + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el) + try { + if (el.className.constructor == SVGAnimatedString) { + jsPlumbUtil.svg.addClass(el, clazz); + } + else el.addClass(clazz); + } + catch (e) { + // SVGAnimatedString not supported; no problem. + el.addClass(clazz); + } + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + m.start(properties); + }, + + appendElement : function(child, parent) { + _getElementObject(parent).grab(child); + }, + + bind : function(el, event, callback) { + el = _getElementObject(el); + el.addEvent(event, callback); + }, + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.event.clientX, eventObject.event.clientY]; + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el), + drags = _draggablesById[id]; + return drags[0].scope; + }, + + getDropEvent : function(args) { + return args[2]; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getDOMElement : function(el) { + // MooTools just decorates the DOM elements. so we have either an ID or an Element here. + return typeof(el) == "String" ? document.getElementById(el) : el; + }, + + getElementObject : _getElementObject, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + getOriginalEvent : function(e) { + return e.event; + }, + + getPageXY : function(eventObject) { + return [eventObject.event.pageX, eventObject.event.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).getParent(); + }, + + getScrollLeft : function(el) { + return null; + }, + + getScrollTop : function(el) { + return null; + }, + + getSelector : function(spec) { + return $$(spec); + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e != null ? e.tagName : null; + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs, zoom) { + var ui = eventArgs[0], + el = jsPlumb.CurrentLibrary.getElementObject(ui), + p = el.getPosition(); + + zoom = zoom || 1; + + return { left:p.x / zoom, top:p.y / zoom}; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options, isPlumbedComponent) { + var id = jsPlumb.getId(el); + var drag = _draggablesById[id]; + if (!drag) { + var originalZIndex = 0, + originalCursor = null, + dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + + options['onStart'] = jsPlumb.wrap(options['onStart'], function() { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + drag.originalZIndex = originalZIndex; + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + }); + + // DROPPABLES - only relevant if this is a plumbed component, ie. not just the result of the user making some DOM element + // draggable. this is the only library adapter that has to care about this parameter. + var scope = "" + (options["scope"] || jsPlumb.Defaults.Scope), + filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }, + droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + + if (isPlumbedComponent) { + + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr, event) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop', event); + } + }; + } + else + options["droppables"] = []; + + drag = new Drag.Move(el, options); + drag.scope = scope; + drag.originalZIndex = originalZIndex; + _add(_draggablesById, el.get("id"), drag); + // again, only keep a record of this for scope stuff if this is a plumbed component (an endpoint) + if (isPlumbedComponent) { + _add(_draggablesByScope, scope, drag); + } + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + initDroppable : function(el, options, isPlumbedComponent, isPermanent) { + var scope = options["scope"]; + _add(_droppables, scope, el); + var id = jsPlumb.getId(el); + + el.setAttribute("_isPermanentDroppable", isPermanent); // we use this to clear out droppables on drag complete. + _droppableOptions[id] = options; + _droppableScopesById[id] = scope; + var filterFunc = function(entry) { return entry.element != el; }, + draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + isAlreadyDraggable : function(el) { + return _draggablesById[jsPlumb.getId(el)] != null; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + return (typeof Drag != undefined && typeof Drag.Move != undefined); + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el.className.constructor == SVGAnimatedString) { + jsPlumbUtil.svg.removeClass(el, clazz); + } + else el.removeClass(clazz); + } + catch (e) { + // SVGAnimatedString not supported; no problem. + el.removeClass(clazz); + } + }, + + removeElement : function(element, parent) { + var el = _getElementObject(element); + if (el) el.dispose(); // ?? + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + setDragScope : function(el, scope) { + var drag = _draggablesById[el.get("id")]; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + drag[0].droppables = droppables; + }, + + setOffset : function(el, o) { + _getElementObject(el).setPosition({x:o.left, y:o.top}); + }, + + stopDrag : function() { + for (var i in _draggablesById) { + for (var j = 0; j < _draggablesById[i].length; j++) { + var d = _draggablesById[i][j]; + d.stop(); + if (d.originalZIndex != 0) + d.element.setStyle("z-index", d.originalZIndex); + } + } + }, + + /** + * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself. + * the other libraries do not. yui, in fact, cannot even pass an original event. we have to pull out stuff + * from the originalEvent to put in an options object for YUI. + * @param el + * @param event + * @param originalEvent + */ + trigger : function(el, event, originalEvent) { + originalEvent.stopPropagation(); + _getElementObject(el).fireEvent(event, originalEvent); + }, + + unbind : function(el, event, callback) { + el = _getElementObject(el); + el.removeEvent(event, callback); + } + }; + + window.addEvent('domready', jsPlumb.init); +})(); diff --git a/build/1.3.14/tests/android-svg.html b/archive/1.3.14/tests/android-svg.html similarity index 100% rename from build/1.3.14/tests/android-svg.html rename to archive/1.3.14/tests/android-svg.html diff --git a/build/1.3.14/tests/loadTestHarness.html b/archive/1.3.14/tests/loadTestHarness.html similarity index 100% rename from build/1.3.14/tests/loadTestHarness.html rename to archive/1.3.14/tests/loadTestHarness.html diff --git a/build/1.3.14/tests/qunit-all.html b/archive/1.3.14/tests/qunit-all.html similarity index 100% rename from build/1.3.14/tests/qunit-all.html rename to archive/1.3.14/tests/qunit-all.html diff --git a/build/1.3.14/tests/qunit-canvas-jquery-instance.html b/archive/1.3.14/tests/qunit-canvas-jquery-instance.html similarity index 100% rename from build/1.3.14/tests/qunit-canvas-jquery-instance.html rename to archive/1.3.14/tests/qunit-canvas-jquery-instance.html diff --git a/build/1.3.14/tests/qunit-canvas-jquery.html b/archive/1.3.14/tests/qunit-canvas-jquery.html similarity index 100% rename from build/1.3.14/tests/qunit-canvas-jquery.html rename to archive/1.3.14/tests/qunit-canvas-jquery.html diff --git a/build/1.3.14/tests/qunit-canvas-mootools.html b/archive/1.3.14/tests/qunit-canvas-mootools.html similarity index 100% rename from build/1.3.14/tests/qunit-canvas-mootools.html rename to archive/1.3.14/tests/qunit-canvas-mootools.html diff --git a/build/1.3.14/tests/qunit-svg-jquery-instance.html b/archive/1.3.14/tests/qunit-svg-jquery-instance.html similarity index 100% rename from build/1.3.14/tests/qunit-svg-jquery-instance.html rename to archive/1.3.14/tests/qunit-svg-jquery-instance.html diff --git a/build/1.3.14/tests/qunit-svg-jquery.html b/archive/1.3.14/tests/qunit-svg-jquery.html similarity index 100% rename from build/1.3.14/tests/qunit-svg-jquery.html rename to archive/1.3.14/tests/qunit-svg-jquery.html diff --git a/build/1.3.14/tests/qunit-vml-jquery-instance.html b/archive/1.3.14/tests/qunit-vml-jquery-instance.html similarity index 100% rename from build/1.3.14/tests/qunit-vml-jquery-instance.html rename to archive/1.3.14/tests/qunit-vml-jquery-instance.html diff --git a/build/1.3.14/tests/qunit-vml-jquery.html b/archive/1.3.14/tests/qunit-vml-jquery.html similarity index 100% rename from build/1.3.14/tests/qunit-vml-jquery.html rename to archive/1.3.14/tests/qunit-vml-jquery.html diff --git a/build/1.3.14/tests/qunit.css b/archive/1.3.14/tests/qunit.css similarity index 100% rename from build/1.3.14/tests/qunit.css rename to archive/1.3.14/tests/qunit.css diff --git a/archive/1.3.14/yui.jsPlumb-1.3.14-RC1.js b/archive/1.3.14/yui.jsPlumb-1.3.14-RC1.js new file mode 100644 index 000000000..5b758c54e --- /dev/null +++ b/archive/1.3.14/yui.jsPlumb-1.3.14-RC1.js @@ -0,0 +1,385 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.14 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the YUI3 adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +/** + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getOriginalEvent gets the original browser event from some wrapper event. + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + */ +(function() { + + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function( v, b, s ) { + for( var i = +b || 0, l = this.length; i < l; i++ ) { + if( this[i]===v || s && this[i]==v ) { return i; } + } + return -1; + }; + } + + var Y; + + YUI().use('node', 'dd', 'anim', 'node-event-simulate', function(_Y) { + Y = _Y; + Y.on("domready", function() { jsPlumb.init(); }); + }); + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }, + ddEvents = [ "drag:mouseDown", "drag:afterMouseDown", "drag:mouseup", + "drag:align", "drag:removeHandle", "drag:addHandle", "drag:removeInvalid", "drag:addInvalid", + "drag:start", "drag:end", "drag:drag", "drag:over", "drag:enter", + "drag:exit", "drag:drophit", "drag:dropmiss", "drop:over", "drop:enter", "drop:exit", "drop:hit" + ], + animEvents = [ "tween" ], + /** + * helper function to curry callbacks for some element. + */ + _wrapper = function(fn) { + return function() { + try { + return fn.apply(this, arguments); + } + catch (e) { } + }; + }, + /** + * extracts options from the given options object, leaving out event handlers. + */ + _getDDOptions = function(options) { + var o = {}; + for (var i in options) if (ddEvents.indexOf(i) == -1) o[i] = options[i]; + return o; + }, + /** + * attaches all event handlers found in options to the given dragdrop object, and registering + * the given el as the element of interest. + */ + _attachListeners = function(dd, options, eventList) { + for (var ev in options) { + if (eventList.indexOf(ev) != -1) { + var w = _wrapper(options[ev]); + dd.on(ev, w); + } + } + }, + _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}, + _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }, + _lastDragObject = null, + _extend = function(o1, o2) { + for (var i in o2) + o1[i] = o2[i]; + return o1; + }, + _getAttribute = function(el, attributeId) { + return el.getAttribute(attributeId); + }, + _getElementObject = function(el) { + if (el == null) return null; + var eee = null; + eee = typeof el == 'string' ? Y.one('#' + el) : el._node ? el : Y.one(el); + return eee; + }; + + jsPlumb.CurrentLibrary = { + + addClass : function(el, clazz) { + jsPlumb.CurrentLibrary.getElementObject(el).addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + var o = _extend({node:el, to:properties}, options), + id = _getAttribute(el, "id"); + o["tween"] = jsPlumb.wrap(properties["tween"], function() { + // TODO should use a current instance. + jsPlumb.repaint(id); + }); + var a = new Y.Anim(o); + _attachListeners(a, o, animEvents); + a.run(); + }, + + appendElement : function(child, parent) { + _getElementObject (parent).append(child); + }, + + /** + * event binding wrapper. + */ + bind : function(el, event, callback) { + _getElementObject(el).on(event, callback); + }, + + dragEvents : { + "start":"drag:start", "stop":"drag:end", "drag":"drag:drag", "step":"step", + "over":"drop:enter", "out":"drop:exit", "drop":"drop:hit" + }, + + extend : _extend, + + getAttribute : _getAttribute, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + // this is a workaround for the unfortunate fact that in YUI3, the 'drop:exit' event does + // not contain a reference to the drag that just exited. single-threaded js to the + // rescue: we'll just keep it for ourselves. + if (eventArgs[0].drag) _lastDragObject = eventArgs[0].drag.el; + return _lastDragObject; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el), + dd = _draggablesById[id]; + return dd.scope; + }, + + getDropEvent : function(args) { + return args[0]; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getDOMElement : function(el) { + if (typeof(el) == "String") + return document.getElementById(el); + else if (el._node) + return el._node; + else return el; + }, + + getElementObject : _getElementObject, + + getOffset : function(el) { + var o = Y.DOM.getXY(el._node); + return {left:o[0], top:o[1]}; + }, + + getOriginalEvent : function(e) { + return e._event; + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).get("parentNode"); + }, + + getScrollLeft : function(el) { + return 0; + }, + + getScrollTop : function(el) { + return 0; + }, + + getSelector : function(spec) { + var s = Y.all(spec); + return s && s ._nodes ? s._nodes : []; + }, + + getSize : function(el) { + return [ el._node.offsetWidth, el._node.offsetHeight ]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e != null && e._node != null ? e._node.tagName : null; + }, + + getUIPosition : function(args, zoom) { + zoom = zoom || 1; + var n = args[0].currentTarget.el._node, + o = Y.DOM.getXY(n); + return {left:o[0] / zoom, top:o[1] / zoom}; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options, isPlumbedComponent) { + var _opts = _getDDOptions(options), + id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drag(_opts); + dd.el = el; + + if (isPlumbedComponent) { + var scope = options['scope'] || jsPlumb.Defaults.Scope; + dd.scope = scope; + _add(_draggablesByScope, scope, dd); + } + + _draggablesById[id] = dd; + + _attachListeners(dd, options, ddEvents); + }, + + initDroppable : function(el, options) { + var _opts = _getDDOptions(options), + id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drop(_opts); + + _droppableOptions[id] = options; + + options = _extend({}, options); + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _droppableScopesById[id] = scope; + + options["drop:enter"] = jsPlumb.wrap(options["drop:enter"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, true); + }, true); + options["drop:exit"] = jsPlumb.wrap(options["drop:exit"], function(e) { + _checkHover(el, false); + }); + options["drop:hit"] = jsPlumb.wrap(options["drop:hit"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, false); + }, true); + + _attachListeners(dd, options, ddEvents); + }, + + isAlreadyDraggable : function(el) { + el = _getElementObject(el); + return el.hasClass("yui3-dd-draggable"); + }, + + isDragSupported : function(el) { return true; }, + isDropSupported : function(el) { return true; }, + removeClass : function(el, clazz) { + jsPlumb.CurrentLibrary.getElementObject(el).removeClass(clazz); + }, + removeElement : function(el) { _getElementObject(el).remove(); }, + + setAttribute : function(el, attributeName, attributeValue) { + el.setAttribute(attributeName, attributeValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + var id = jsPlumb.getId(el), + dd = _draggablesById[id]; + if (dd) dd.set("lock", !draggable); + }, + + setDragScope : function(el, scope) { + var id = jsPlumb.getId(el), + dd = _draggablesById[id]; + if (dd) dd.scope = scope; + }, + + setOffset : function(el, o) { + el = _getElementObject(el); + el.set("top", o.top); + el.set("left", o.left); + }, + + stopDrag : function() { + Y.DD.DDM.stopDrag(); + }, + + trigger : function(el, event, originalEvent) { + originalEvent.stopPropagation(); + _getElementObject(el).simulate(event, { + pageX:originalEvent.pageX, + pageY:originalEvent.pageY, + clientX:originalEvent.clientX, + clientY:originalEvent.clientY + }); + }, + + /** + * event unbinding wrapper. + */ + unbind : function(el, event, callback) { + _getElementObject(el).detach(event, callback); + } + }; +})(); \ No newline at end of file diff --git a/build/1.3.15/demo/apidocs/files/jquery-jsPlumb-1-3-15-all-js.html b/archive/1.3.15/demo/apidocs/files/jquery-jsPlumb-1-3-15-all-js.html similarity index 100% rename from build/1.3.15/demo/apidocs/files/jquery-jsPlumb-1-3-15-all-js.html rename to archive/1.3.15/demo/apidocs/files/jquery-jsPlumb-1-3-15-all-js.html diff --git a/build/1.3.15/demo/apidocs/index.html b/archive/1.3.15/demo/apidocs/index.html similarity index 100% rename from build/1.3.15/demo/apidocs/index.html rename to archive/1.3.15/demo/apidocs/index.html diff --git a/build/1.3.15/demo/apidocs/index/Classes.html b/archive/1.3.15/demo/apidocs/index/Classes.html similarity index 100% rename from build/1.3.15/demo/apidocs/index/Classes.html rename to archive/1.3.15/demo/apidocs/index/Classes.html diff --git a/build/1.3.15/demo/apidocs/index/Files.html b/archive/1.3.15/demo/apidocs/index/Files.html similarity index 100% rename from build/1.3.15/demo/apidocs/index/Files.html rename to archive/1.3.15/demo/apidocs/index/Files.html diff --git a/build/1.3.15/demo/apidocs/index/Functions.html b/archive/1.3.15/demo/apidocs/index/Functions.html similarity index 100% rename from build/1.3.15/demo/apidocs/index/Functions.html rename to archive/1.3.15/demo/apidocs/index/Functions.html diff --git a/build/1.3.15/demo/apidocs/index/Functions2.html b/archive/1.3.15/demo/apidocs/index/Functions2.html similarity index 100% rename from build/1.3.15/demo/apidocs/index/Functions2.html rename to archive/1.3.15/demo/apidocs/index/Functions2.html diff --git a/build/1.3.15/demo/apidocs/index/General.html b/archive/1.3.15/demo/apidocs/index/General.html similarity index 100% rename from build/1.3.15/demo/apidocs/index/General.html rename to archive/1.3.15/demo/apidocs/index/General.html diff --git a/build/1.3.15/demo/apidocs/index/General2.html b/archive/1.3.15/demo/apidocs/index/General2.html similarity index 100% rename from build/1.3.15/demo/apidocs/index/General2.html rename to archive/1.3.15/demo/apidocs/index/General2.html diff --git a/build/1.3.15/demo/apidocs/index/General3.html b/archive/1.3.15/demo/apidocs/index/General3.html similarity index 100% rename from build/1.3.15/demo/apidocs/index/General3.html rename to archive/1.3.15/demo/apidocs/index/General3.html diff --git a/build/1.3.15/demo/apidocs/index/Properties.html b/archive/1.3.15/demo/apidocs/index/Properties.html similarity index 100% rename from build/1.3.15/demo/apidocs/index/Properties.html rename to archive/1.3.15/demo/apidocs/index/Properties.html diff --git a/build/1.3.15/demo/apidocs/javascript/main.js b/archive/1.3.15/demo/apidocs/javascript/main.js similarity index 100% rename from build/1.3.15/demo/apidocs/javascript/main.js rename to archive/1.3.15/demo/apidocs/javascript/main.js diff --git a/build/1.3.15/demo/apidocs/javascript/prettify.js b/archive/1.3.15/demo/apidocs/javascript/prettify.js similarity index 100% rename from build/1.3.15/demo/apidocs/javascript/prettify.js rename to archive/1.3.15/demo/apidocs/javascript/prettify.js diff --git a/build/1.3.15/demo/apidocs/javascript/searchdata.js b/archive/1.3.15/demo/apidocs/javascript/searchdata.js similarity index 100% rename from build/1.3.15/demo/apidocs/javascript/searchdata.js rename to archive/1.3.15/demo/apidocs/javascript/searchdata.js diff --git a/build/1.3.15/demo/apidocs/search/ClassesC.html b/archive/1.3.15/demo/apidocs/search/ClassesC.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/ClassesC.html rename to archive/1.3.15/demo/apidocs/search/ClassesC.html diff --git a/build/1.3.15/demo/apidocs/search/ClassesE.html b/archive/1.3.15/demo/apidocs/search/ClassesE.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/ClassesE.html rename to archive/1.3.15/demo/apidocs/search/ClassesE.html diff --git a/build/1.3.15/demo/apidocs/search/ClassesO.html b/archive/1.3.15/demo/apidocs/search/ClassesO.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/ClassesO.html rename to archive/1.3.15/demo/apidocs/search/ClassesO.html diff --git a/build/1.3.15/demo/apidocs/search/FilesJ.html b/archive/1.3.15/demo/apidocs/search/FilesJ.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/FilesJ.html rename to archive/1.3.15/demo/apidocs/search/FilesJ.html diff --git a/build/1.3.15/demo/apidocs/search/FunctionsA.html b/archive/1.3.15/demo/apidocs/search/FunctionsA.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/FunctionsA.html rename to archive/1.3.15/demo/apidocs/search/FunctionsA.html diff --git a/build/1.3.15/demo/apidocs/search/FunctionsB.html b/archive/1.3.15/demo/apidocs/search/FunctionsB.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/FunctionsB.html rename to archive/1.3.15/demo/apidocs/search/FunctionsB.html diff --git a/build/1.3.15/demo/apidocs/search/FunctionsC.html b/archive/1.3.15/demo/apidocs/search/FunctionsC.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/FunctionsC.html rename to archive/1.3.15/demo/apidocs/search/FunctionsC.html diff --git a/build/1.3.15/demo/apidocs/search/FunctionsD.html b/archive/1.3.15/demo/apidocs/search/FunctionsD.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/FunctionsD.html rename to archive/1.3.15/demo/apidocs/search/FunctionsD.html diff --git a/build/1.3.15/demo/apidocs/search/FunctionsE.html b/archive/1.3.15/demo/apidocs/search/FunctionsE.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/FunctionsE.html rename to archive/1.3.15/demo/apidocs/search/FunctionsE.html diff --git a/build/1.3.15/demo/apidocs/search/FunctionsG.html b/archive/1.3.15/demo/apidocs/search/FunctionsG.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/FunctionsG.html rename to archive/1.3.15/demo/apidocs/search/FunctionsG.html diff --git a/build/1.3.15/demo/apidocs/search/FunctionsH.html b/archive/1.3.15/demo/apidocs/search/FunctionsH.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/FunctionsH.html rename to archive/1.3.15/demo/apidocs/search/FunctionsH.html diff --git a/build/1.3.15/demo/apidocs/search/FunctionsI.html b/archive/1.3.15/demo/apidocs/search/FunctionsI.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/FunctionsI.html rename to archive/1.3.15/demo/apidocs/search/FunctionsI.html diff --git a/build/1.3.15/demo/apidocs/search/FunctionsM.html b/archive/1.3.15/demo/apidocs/search/FunctionsM.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/FunctionsM.html rename to archive/1.3.15/demo/apidocs/search/FunctionsM.html diff --git a/build/1.3.15/demo/apidocs/search/FunctionsP.html b/archive/1.3.15/demo/apidocs/search/FunctionsP.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/FunctionsP.html rename to archive/1.3.15/demo/apidocs/search/FunctionsP.html diff --git a/build/1.3.15/demo/apidocs/search/FunctionsR.html b/archive/1.3.15/demo/apidocs/search/FunctionsR.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/FunctionsR.html rename to archive/1.3.15/demo/apidocs/search/FunctionsR.html diff --git a/build/1.3.15/demo/apidocs/search/FunctionsS.html b/archive/1.3.15/demo/apidocs/search/FunctionsS.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/FunctionsS.html rename to archive/1.3.15/demo/apidocs/search/FunctionsS.html diff --git a/build/1.3.15/demo/apidocs/search/FunctionsT.html b/archive/1.3.15/demo/apidocs/search/FunctionsT.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/FunctionsT.html rename to archive/1.3.15/demo/apidocs/search/FunctionsT.html diff --git a/build/1.3.15/demo/apidocs/search/FunctionsU.html b/archive/1.3.15/demo/apidocs/search/FunctionsU.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/FunctionsU.html rename to archive/1.3.15/demo/apidocs/search/FunctionsU.html diff --git a/build/1.3.15/demo/apidocs/search/GeneralA.html b/archive/1.3.15/demo/apidocs/search/GeneralA.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/GeneralA.html rename to archive/1.3.15/demo/apidocs/search/GeneralA.html diff --git a/build/1.3.15/demo/apidocs/search/GeneralB.html b/archive/1.3.15/demo/apidocs/search/GeneralB.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/GeneralB.html rename to archive/1.3.15/demo/apidocs/search/GeneralB.html diff --git a/build/1.3.15/demo/apidocs/search/GeneralC.html b/archive/1.3.15/demo/apidocs/search/GeneralC.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/GeneralC.html rename to archive/1.3.15/demo/apidocs/search/GeneralC.html diff --git a/build/1.3.15/demo/apidocs/search/GeneralD.html b/archive/1.3.15/demo/apidocs/search/GeneralD.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/GeneralD.html rename to archive/1.3.15/demo/apidocs/search/GeneralD.html diff --git a/build/1.3.15/demo/apidocs/search/GeneralE.html b/archive/1.3.15/demo/apidocs/search/GeneralE.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/GeneralE.html rename to archive/1.3.15/demo/apidocs/search/GeneralE.html diff --git a/build/1.3.15/demo/apidocs/search/GeneralF.html b/archive/1.3.15/demo/apidocs/search/GeneralF.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/GeneralF.html rename to archive/1.3.15/demo/apidocs/search/GeneralF.html diff --git a/build/1.3.15/demo/apidocs/search/GeneralG.html b/archive/1.3.15/demo/apidocs/search/GeneralG.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/GeneralG.html rename to archive/1.3.15/demo/apidocs/search/GeneralG.html diff --git a/build/1.3.15/demo/apidocs/search/GeneralH.html b/archive/1.3.15/demo/apidocs/search/GeneralH.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/GeneralH.html rename to archive/1.3.15/demo/apidocs/search/GeneralH.html diff --git a/build/1.3.15/demo/apidocs/search/GeneralI.html b/archive/1.3.15/demo/apidocs/search/GeneralI.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/GeneralI.html rename to archive/1.3.15/demo/apidocs/search/GeneralI.html diff --git a/build/1.3.15/demo/apidocs/search/GeneralJ.html b/archive/1.3.15/demo/apidocs/search/GeneralJ.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/GeneralJ.html rename to archive/1.3.15/demo/apidocs/search/GeneralJ.html diff --git a/build/1.3.15/demo/apidocs/search/GeneralL.html b/archive/1.3.15/demo/apidocs/search/GeneralL.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/GeneralL.html rename to archive/1.3.15/demo/apidocs/search/GeneralL.html diff --git a/build/1.3.15/demo/apidocs/search/GeneralM.html b/archive/1.3.15/demo/apidocs/search/GeneralM.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/GeneralM.html rename to archive/1.3.15/demo/apidocs/search/GeneralM.html diff --git a/build/1.3.15/demo/apidocs/search/GeneralO.html b/archive/1.3.15/demo/apidocs/search/GeneralO.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/GeneralO.html rename to archive/1.3.15/demo/apidocs/search/GeneralO.html diff --git a/build/1.3.15/demo/apidocs/search/GeneralP.html b/archive/1.3.15/demo/apidocs/search/GeneralP.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/GeneralP.html rename to archive/1.3.15/demo/apidocs/search/GeneralP.html diff --git a/build/1.3.15/demo/apidocs/search/GeneralR.html b/archive/1.3.15/demo/apidocs/search/GeneralR.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/GeneralR.html rename to archive/1.3.15/demo/apidocs/search/GeneralR.html diff --git a/build/1.3.15/demo/apidocs/search/GeneralS.html b/archive/1.3.15/demo/apidocs/search/GeneralS.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/GeneralS.html rename to archive/1.3.15/demo/apidocs/search/GeneralS.html diff --git a/build/1.3.15/demo/apidocs/search/GeneralT.html b/archive/1.3.15/demo/apidocs/search/GeneralT.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/GeneralT.html rename to archive/1.3.15/demo/apidocs/search/GeneralT.html diff --git a/build/1.3.15/demo/apidocs/search/GeneralU.html b/archive/1.3.15/demo/apidocs/search/GeneralU.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/GeneralU.html rename to archive/1.3.15/demo/apidocs/search/GeneralU.html diff --git a/build/1.3.15/demo/apidocs/search/GeneralV.html b/archive/1.3.15/demo/apidocs/search/GeneralV.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/GeneralV.html rename to archive/1.3.15/demo/apidocs/search/GeneralV.html diff --git a/build/1.3.15/demo/apidocs/search/NoResults.html b/archive/1.3.15/demo/apidocs/search/NoResults.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/NoResults.html rename to archive/1.3.15/demo/apidocs/search/NoResults.html diff --git a/build/1.3.15/demo/apidocs/search/PropertiesA.html b/archive/1.3.15/demo/apidocs/search/PropertiesA.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/PropertiesA.html rename to archive/1.3.15/demo/apidocs/search/PropertiesA.html diff --git a/build/1.3.15/demo/apidocs/search/PropertiesB.html b/archive/1.3.15/demo/apidocs/search/PropertiesB.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/PropertiesB.html rename to archive/1.3.15/demo/apidocs/search/PropertiesB.html diff --git a/build/1.3.15/demo/apidocs/search/PropertiesC.html b/archive/1.3.15/demo/apidocs/search/PropertiesC.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/PropertiesC.html rename to archive/1.3.15/demo/apidocs/search/PropertiesC.html diff --git a/build/1.3.15/demo/apidocs/search/PropertiesD.html b/archive/1.3.15/demo/apidocs/search/PropertiesD.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/PropertiesD.html rename to archive/1.3.15/demo/apidocs/search/PropertiesD.html diff --git a/build/1.3.15/demo/apidocs/search/PropertiesE.html b/archive/1.3.15/demo/apidocs/search/PropertiesE.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/PropertiesE.html rename to archive/1.3.15/demo/apidocs/search/PropertiesE.html diff --git a/build/1.3.15/demo/apidocs/search/PropertiesL.html b/archive/1.3.15/demo/apidocs/search/PropertiesL.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/PropertiesL.html rename to archive/1.3.15/demo/apidocs/search/PropertiesL.html diff --git a/build/1.3.15/demo/apidocs/search/PropertiesO.html b/archive/1.3.15/demo/apidocs/search/PropertiesO.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/PropertiesO.html rename to archive/1.3.15/demo/apidocs/search/PropertiesO.html diff --git a/build/1.3.15/demo/apidocs/search/PropertiesP.html b/archive/1.3.15/demo/apidocs/search/PropertiesP.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/PropertiesP.html rename to archive/1.3.15/demo/apidocs/search/PropertiesP.html diff --git a/build/1.3.15/demo/apidocs/search/PropertiesR.html b/archive/1.3.15/demo/apidocs/search/PropertiesR.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/PropertiesR.html rename to archive/1.3.15/demo/apidocs/search/PropertiesR.html diff --git a/build/1.3.15/demo/apidocs/search/PropertiesS.html b/archive/1.3.15/demo/apidocs/search/PropertiesS.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/PropertiesS.html rename to archive/1.3.15/demo/apidocs/search/PropertiesS.html diff --git a/build/1.3.15/demo/apidocs/search/PropertiesT.html b/archive/1.3.15/demo/apidocs/search/PropertiesT.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/PropertiesT.html rename to archive/1.3.15/demo/apidocs/search/PropertiesT.html diff --git a/build/1.3.15/demo/apidocs/search/PropertiesV.html b/archive/1.3.15/demo/apidocs/search/PropertiesV.html similarity index 100% rename from build/1.3.15/demo/apidocs/search/PropertiesV.html rename to archive/1.3.15/demo/apidocs/search/PropertiesV.html diff --git a/build/1.3.15/demo/apidocs/styles/main.css b/archive/1.3.15/demo/apidocs/styles/main.css similarity index 100% rename from build/1.3.15/demo/apidocs/styles/main.css rename to archive/1.3.15/demo/apidocs/styles/main.css diff --git a/build/1.3.15/demo/css/anchorDemo.css b/archive/1.3.15/demo/css/anchorDemo.css similarity index 100% rename from build/1.3.15/demo/css/anchorDemo.css rename to archive/1.3.15/demo/css/anchorDemo.css diff --git a/build/1.3.15/demo/css/chartDemo.css b/archive/1.3.15/demo/css/chartDemo.css similarity index 100% rename from build/1.3.15/demo/css/chartDemo.css rename to archive/1.3.15/demo/css/chartDemo.css diff --git a/build/1.3.15/demo/css/demo.css b/archive/1.3.15/demo/css/demo.css similarity index 100% rename from build/1.3.15/demo/css/demo.css rename to archive/1.3.15/demo/css/demo.css diff --git a/build/1.3.15/demo/css/dragAnimDemo.css b/archive/1.3.15/demo/css/dragAnimDemo.css similarity index 100% rename from build/1.3.15/demo/css/dragAnimDemo.css rename to archive/1.3.15/demo/css/dragAnimDemo.css diff --git a/build/1.3.15/demo/css/draggableConnectorsDemo.css b/archive/1.3.15/demo/css/draggableConnectorsDemo.css similarity index 100% rename from build/1.3.15/demo/css/draggableConnectorsDemo.css rename to archive/1.3.15/demo/css/draggableConnectorsDemo.css diff --git a/build/1.3.15/demo/css/dynamicAnchorsDemo.css b/archive/1.3.15/demo/css/dynamicAnchorsDemo.css similarity index 100% rename from build/1.3.15/demo/css/dynamicAnchorsDemo.css rename to archive/1.3.15/demo/css/dynamicAnchorsDemo.css diff --git a/build/1.3.15/demo/css/flowchartDemo.css b/archive/1.3.15/demo/css/flowchartDemo.css similarity index 100% rename from build/1.3.15/demo/css/flowchartDemo.css rename to archive/1.3.15/demo/css/flowchartDemo.css diff --git a/build/1.3.15/demo/css/jsPlumbDemo.css b/archive/1.3.15/demo/css/jsPlumbDemo.css similarity index 100% rename from build/1.3.15/demo/css/jsPlumbDemo.css rename to archive/1.3.15/demo/css/jsPlumbDemo.css diff --git a/build/1.3.15/demo/css/makeTargetDemo.css b/archive/1.3.15/demo/css/makeTargetDemo.css similarity index 100% rename from build/1.3.15/demo/css/makeTargetDemo.css rename to archive/1.3.15/demo/css/makeTargetDemo.css diff --git a/build/1.3.15/demo/css/multipleJsPlumbDemo.css b/archive/1.3.15/demo/css/multipleJsPlumbDemo.css similarity index 100% rename from build/1.3.15/demo/css/multipleJsPlumbDemo.css rename to archive/1.3.15/demo/css/multipleJsPlumbDemo.css diff --git a/build/1.3.15/demo/css/perimeterAnchorsDemo.css b/archive/1.3.15/demo/css/perimeterAnchorsDemo.css similarity index 100% rename from build/1.3.15/demo/css/perimeterAnchorsDemo.css rename to archive/1.3.15/demo/css/perimeterAnchorsDemo.css diff --git a/build/1.3.15/demo/css/selectDemo.css b/archive/1.3.15/demo/css/selectDemo.css similarity index 100% rename from build/1.3.15/demo/css/selectDemo.css rename to archive/1.3.15/demo/css/selectDemo.css diff --git a/build/1.3.15/demo/css/stateMachineDemo.css b/archive/1.3.15/demo/css/stateMachineDemo.css similarity index 100% rename from build/1.3.15/demo/css/stateMachineDemo.css rename to archive/1.3.15/demo/css/stateMachineDemo.css diff --git a/build/1.3.15/demo/doc/archive/1.2.6/content.html b/archive/1.3.15/demo/doc/archive/1.2.6/content.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.2.6/content.html rename to archive/1.3.15/demo/doc/archive/1.2.6/content.html diff --git a/build/1.3.15/demo/doc/archive/1.2.6/index.html b/archive/1.3.15/demo/doc/archive/1.2.6/index.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.2.6/index.html rename to archive/1.3.15/demo/doc/archive/1.2.6/index.html diff --git a/build/1.3.15/demo/doc/archive/1.2.6/usage.html b/archive/1.3.15/demo/doc/archive/1.2.6/usage.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.2.6/usage.html rename to archive/1.3.15/demo/doc/archive/1.2.6/usage.html diff --git a/build/1.3.15/demo/doc/archive/1.3.10/content.html b/archive/1.3.15/demo/doc/archive/1.3.10/content.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.10/content.html rename to archive/1.3.15/demo/doc/archive/1.3.10/content.html diff --git a/build/1.3.15/demo/doc/archive/1.3.10/index.html b/archive/1.3.15/demo/doc/archive/1.3.10/index.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.10/index.html rename to archive/1.3.15/demo/doc/archive/1.3.10/index.html diff --git a/build/1.3.15/demo/doc/archive/1.3.10/jsPlumbDoc.css b/archive/1.3.15/demo/doc/archive/1.3.10/jsPlumbDoc.css similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.10/jsPlumbDoc.css rename to archive/1.3.15/demo/doc/archive/1.3.10/jsPlumbDoc.css diff --git a/build/1.3.15/demo/doc/archive/1.3.10/usage.html b/archive/1.3.15/demo/doc/archive/1.3.10/usage.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.10/usage.html rename to archive/1.3.15/demo/doc/archive/1.3.10/usage.html diff --git a/build/1.3.15/demo/doc/archive/1.3.11/content.html b/archive/1.3.15/demo/doc/archive/1.3.11/content.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.11/content.html rename to archive/1.3.15/demo/doc/archive/1.3.11/content.html diff --git a/build/1.3.15/demo/doc/archive/1.3.11/index.html b/archive/1.3.15/demo/doc/archive/1.3.11/index.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.11/index.html rename to archive/1.3.15/demo/doc/archive/1.3.11/index.html diff --git a/build/1.3.15/demo/doc/archive/1.3.11/jsPlumbDoc.css b/archive/1.3.15/demo/doc/archive/1.3.11/jsPlumbDoc.css similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.11/jsPlumbDoc.css rename to archive/1.3.15/demo/doc/archive/1.3.11/jsPlumbDoc.css diff --git a/build/1.3.15/demo/doc/archive/1.3.11/usage.html b/archive/1.3.15/demo/doc/archive/1.3.11/usage.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.11/usage.html rename to archive/1.3.15/demo/doc/archive/1.3.11/usage.html diff --git a/build/1.3.15/demo/doc/archive/1.3.12/content.html b/archive/1.3.15/demo/doc/archive/1.3.12/content.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.12/content.html rename to archive/1.3.15/demo/doc/archive/1.3.12/content.html diff --git a/build/1.3.15/demo/doc/archive/1.3.12/index.html b/archive/1.3.15/demo/doc/archive/1.3.12/index.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.12/index.html rename to archive/1.3.15/demo/doc/archive/1.3.12/index.html diff --git a/build/1.3.15/demo/doc/archive/1.3.12/jsPlumbDoc.css b/archive/1.3.15/demo/doc/archive/1.3.12/jsPlumbDoc.css similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.12/jsPlumbDoc.css rename to archive/1.3.15/demo/doc/archive/1.3.12/jsPlumbDoc.css diff --git a/build/1.3.15/demo/doc/archive/1.3.12/usage.html b/archive/1.3.15/demo/doc/archive/1.3.12/usage.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.12/usage.html rename to archive/1.3.15/demo/doc/archive/1.3.12/usage.html diff --git a/build/1.3.15/demo/doc/archive/1.3.13/content.html b/archive/1.3.15/demo/doc/archive/1.3.13/content.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.13/content.html rename to archive/1.3.15/demo/doc/archive/1.3.13/content.html diff --git a/build/1.3.15/demo/doc/archive/1.3.13/index.html b/archive/1.3.15/demo/doc/archive/1.3.13/index.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.13/index.html rename to archive/1.3.15/demo/doc/archive/1.3.13/index.html diff --git a/build/1.3.15/demo/doc/archive/1.3.13/jsPlumbDoc.css b/archive/1.3.15/demo/doc/archive/1.3.13/jsPlumbDoc.css similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.13/jsPlumbDoc.css rename to archive/1.3.15/demo/doc/archive/1.3.13/jsPlumbDoc.css diff --git a/build/1.3.15/demo/doc/archive/1.3.13/usage.html b/archive/1.3.15/demo/doc/archive/1.3.13/usage.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.13/usage.html rename to archive/1.3.15/demo/doc/archive/1.3.13/usage.html diff --git a/build/1.3.15/demo/doc/archive/1.3.14/content.html b/archive/1.3.15/demo/doc/archive/1.3.14/content.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.14/content.html rename to archive/1.3.15/demo/doc/archive/1.3.14/content.html diff --git a/build/1.3.15/demo/doc/archive/1.3.14/index.html b/archive/1.3.15/demo/doc/archive/1.3.14/index.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.14/index.html rename to archive/1.3.15/demo/doc/archive/1.3.14/index.html diff --git a/build/1.3.15/demo/doc/archive/1.3.14/jsPlumbDoc.css b/archive/1.3.15/demo/doc/archive/1.3.14/jsPlumbDoc.css similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.14/jsPlumbDoc.css rename to archive/1.3.15/demo/doc/archive/1.3.14/jsPlumbDoc.css diff --git a/build/1.3.15/demo/doc/archive/1.3.14/usage.html b/archive/1.3.15/demo/doc/archive/1.3.14/usage.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.14/usage.html rename to archive/1.3.15/demo/doc/archive/1.3.14/usage.html diff --git a/build/1.3.15/demo/doc/archive/1.3.2/content.html b/archive/1.3.15/demo/doc/archive/1.3.2/content.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.2/content.html rename to archive/1.3.15/demo/doc/archive/1.3.2/content.html diff --git a/build/1.3.15/demo/doc/archive/1.3.2/index.html b/archive/1.3.15/demo/doc/archive/1.3.2/index.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.2/index.html rename to archive/1.3.15/demo/doc/archive/1.3.2/index.html diff --git a/build/1.3.15/demo/doc/archive/1.3.2/usage.html b/archive/1.3.15/demo/doc/archive/1.3.2/usage.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.2/usage.html rename to archive/1.3.15/demo/doc/archive/1.3.2/usage.html diff --git a/build/1.3.15/demo/doc/archive/1.3.3/content.html b/archive/1.3.15/demo/doc/archive/1.3.3/content.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.3/content.html rename to archive/1.3.15/demo/doc/archive/1.3.3/content.html diff --git a/build/1.3.15/demo/doc/archive/1.3.3/index.html b/archive/1.3.15/demo/doc/archive/1.3.3/index.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.3/index.html rename to archive/1.3.15/demo/doc/archive/1.3.3/index.html diff --git a/build/1.3.15/demo/doc/archive/1.3.3/jsPlumbDoc.css b/archive/1.3.15/demo/doc/archive/1.3.3/jsPlumbDoc.css similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.3/jsPlumbDoc.css rename to archive/1.3.15/demo/doc/archive/1.3.3/jsPlumbDoc.css diff --git a/build/1.3.15/demo/doc/archive/1.3.3/usage.html b/archive/1.3.15/demo/doc/archive/1.3.3/usage.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.3/usage.html rename to archive/1.3.15/demo/doc/archive/1.3.3/usage.html diff --git a/build/1.3.15/demo/doc/archive/1.3.4/content.html b/archive/1.3.15/demo/doc/archive/1.3.4/content.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.4/content.html rename to archive/1.3.15/demo/doc/archive/1.3.4/content.html diff --git a/build/1.3.15/demo/doc/archive/1.3.4/index.html b/archive/1.3.15/demo/doc/archive/1.3.4/index.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.4/index.html rename to archive/1.3.15/demo/doc/archive/1.3.4/index.html diff --git a/build/1.3.15/demo/doc/archive/1.3.4/jsPlumbDoc.css b/archive/1.3.15/demo/doc/archive/1.3.4/jsPlumbDoc.css similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.4/jsPlumbDoc.css rename to archive/1.3.15/demo/doc/archive/1.3.4/jsPlumbDoc.css diff --git a/build/1.3.15/demo/doc/archive/1.3.4/usage.html b/archive/1.3.15/demo/doc/archive/1.3.4/usage.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.4/usage.html rename to archive/1.3.15/demo/doc/archive/1.3.4/usage.html diff --git a/build/1.3.15/demo/doc/archive/1.3.5/content.html b/archive/1.3.15/demo/doc/archive/1.3.5/content.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.5/content.html rename to archive/1.3.15/demo/doc/archive/1.3.5/content.html diff --git a/build/1.3.15/demo/doc/archive/1.3.5/index.html b/archive/1.3.15/demo/doc/archive/1.3.5/index.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.5/index.html rename to archive/1.3.15/demo/doc/archive/1.3.5/index.html diff --git a/build/1.3.15/demo/doc/archive/1.3.5/jsPlumbDoc.css b/archive/1.3.15/demo/doc/archive/1.3.5/jsPlumbDoc.css similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.5/jsPlumbDoc.css rename to archive/1.3.15/demo/doc/archive/1.3.5/jsPlumbDoc.css diff --git a/build/1.3.15/demo/doc/archive/1.3.5/usage.html b/archive/1.3.15/demo/doc/archive/1.3.5/usage.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.5/usage.html rename to archive/1.3.15/demo/doc/archive/1.3.5/usage.html diff --git a/build/1.3.15/demo/doc/archive/1.3.6/content.html b/archive/1.3.15/demo/doc/archive/1.3.6/content.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.6/content.html rename to archive/1.3.15/demo/doc/archive/1.3.6/content.html diff --git a/build/1.3.15/demo/doc/archive/1.3.6/index.html b/archive/1.3.15/demo/doc/archive/1.3.6/index.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.6/index.html rename to archive/1.3.15/demo/doc/archive/1.3.6/index.html diff --git a/build/1.3.15/demo/doc/archive/1.3.6/jsPlumbDoc.css b/archive/1.3.15/demo/doc/archive/1.3.6/jsPlumbDoc.css similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.6/jsPlumbDoc.css rename to archive/1.3.15/demo/doc/archive/1.3.6/jsPlumbDoc.css diff --git a/build/1.3.15/demo/doc/archive/1.3.6/usage.html b/archive/1.3.15/demo/doc/archive/1.3.6/usage.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.6/usage.html rename to archive/1.3.15/demo/doc/archive/1.3.6/usage.html diff --git a/build/1.3.15/demo/doc/archive/1.3.7/content.html b/archive/1.3.15/demo/doc/archive/1.3.7/content.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.7/content.html rename to archive/1.3.15/demo/doc/archive/1.3.7/content.html diff --git a/build/1.3.15/demo/doc/archive/1.3.7/index.html b/archive/1.3.15/demo/doc/archive/1.3.7/index.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.7/index.html rename to archive/1.3.15/demo/doc/archive/1.3.7/index.html diff --git a/build/1.3.15/demo/doc/archive/1.3.7/jsPlumbDoc.css b/archive/1.3.15/demo/doc/archive/1.3.7/jsPlumbDoc.css similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.7/jsPlumbDoc.css rename to archive/1.3.15/demo/doc/archive/1.3.7/jsPlumbDoc.css diff --git a/build/1.3.15/demo/doc/archive/1.3.7/usage.html b/archive/1.3.15/demo/doc/archive/1.3.7/usage.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.7/usage.html rename to archive/1.3.15/demo/doc/archive/1.3.7/usage.html diff --git a/build/1.3.15/demo/doc/archive/1.3.8/content.html b/archive/1.3.15/demo/doc/archive/1.3.8/content.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.8/content.html rename to archive/1.3.15/demo/doc/archive/1.3.8/content.html diff --git a/build/1.3.15/demo/doc/archive/1.3.8/index.html b/archive/1.3.15/demo/doc/archive/1.3.8/index.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.8/index.html rename to archive/1.3.15/demo/doc/archive/1.3.8/index.html diff --git a/build/1.3.15/demo/doc/archive/1.3.8/jsPlumbDoc.css b/archive/1.3.15/demo/doc/archive/1.3.8/jsPlumbDoc.css similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.8/jsPlumbDoc.css rename to archive/1.3.15/demo/doc/archive/1.3.8/jsPlumbDoc.css diff --git a/build/1.3.15/demo/doc/archive/1.3.8/usage.html b/archive/1.3.15/demo/doc/archive/1.3.8/usage.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.8/usage.html rename to archive/1.3.15/demo/doc/archive/1.3.8/usage.html diff --git a/build/1.3.15/demo/doc/archive/1.3.9/content.html b/archive/1.3.15/demo/doc/archive/1.3.9/content.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.9/content.html rename to archive/1.3.15/demo/doc/archive/1.3.9/content.html diff --git a/build/1.3.15/demo/doc/archive/1.3.9/index.html b/archive/1.3.15/demo/doc/archive/1.3.9/index.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.9/index.html rename to archive/1.3.15/demo/doc/archive/1.3.9/index.html diff --git a/build/1.3.15/demo/doc/archive/1.3.9/jsPlumbDoc.css b/archive/1.3.15/demo/doc/archive/1.3.9/jsPlumbDoc.css similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.9/jsPlumbDoc.css rename to archive/1.3.15/demo/doc/archive/1.3.9/jsPlumbDoc.css diff --git a/build/1.3.15/demo/doc/archive/1.3.9/usage.html b/archive/1.3.15/demo/doc/archive/1.3.9/usage.html similarity index 100% rename from build/1.3.15/demo/doc/archive/1.3.9/usage.html rename to archive/1.3.15/demo/doc/archive/1.3.9/usage.html diff --git a/build/1.3.15/demo/doc/content.html b/archive/1.3.15/demo/doc/content.html similarity index 100% rename from build/1.3.15/demo/doc/content.html rename to archive/1.3.15/demo/doc/content.html diff --git a/build/1.3.15/demo/doc/index.html b/archive/1.3.15/demo/doc/index.html similarity index 100% rename from build/1.3.15/demo/doc/index.html rename to archive/1.3.15/demo/doc/index.html diff --git a/build/1.3.15/demo/doc/jsPlumbDoc.css b/archive/1.3.15/demo/doc/jsPlumbDoc.css similarity index 100% rename from build/1.3.15/demo/doc/jsPlumbDoc.css rename to archive/1.3.15/demo/doc/jsPlumbDoc.css diff --git a/build/1.3.15/demo/doc/usage.html b/archive/1.3.15/demo/doc/usage.html similarity index 100% rename from build/1.3.15/demo/doc/usage.html rename to archive/1.3.15/demo/doc/usage.html diff --git a/build/1.3.15/demo/img/bigdot.jpg b/archive/1.3.15/demo/img/bigdot.jpg similarity index 100% rename from build/1.3.15/demo/img/bigdot.jpg rename to archive/1.3.15/demo/img/bigdot.jpg diff --git a/build/1.3.15/demo/img/bigdot.png b/archive/1.3.15/demo/img/bigdot.png similarity index 100% rename from build/1.3.15/demo/img/bigdot.png rename to archive/1.3.15/demo/img/bigdot.png diff --git a/build/1.3.15/demo/img/bigdot.xcf b/archive/1.3.15/demo/img/bigdot.xcf similarity index 100% rename from build/1.3.15/demo/img/bigdot.xcf rename to archive/1.3.15/demo/img/bigdot.xcf diff --git a/build/1.3.15/demo/img/circle.png b/archive/1.3.15/demo/img/circle.png similarity index 100% rename from build/1.3.15/demo/img/circle.png rename to archive/1.3.15/demo/img/circle.png diff --git a/build/1.3.15/demo/img/diamond.png b/archive/1.3.15/demo/img/diamond.png similarity index 100% rename from build/1.3.15/demo/img/diamond.png rename to archive/1.3.15/demo/img/diamond.png diff --git a/build/1.3.15/demo/img/dragging_1.jpg b/archive/1.3.15/demo/img/dragging_1.jpg similarity index 100% rename from build/1.3.15/demo/img/dragging_1.jpg rename to archive/1.3.15/demo/img/dragging_1.jpg diff --git a/build/1.3.15/demo/img/dragging_2.jpg b/archive/1.3.15/demo/img/dragging_2.jpg similarity index 100% rename from build/1.3.15/demo/img/dragging_2.jpg rename to archive/1.3.15/demo/img/dragging_2.jpg diff --git a/build/1.3.15/demo/img/dragging_3.jpg b/archive/1.3.15/demo/img/dragging_3.jpg similarity index 100% rename from build/1.3.15/demo/img/dragging_3.jpg rename to archive/1.3.15/demo/img/dragging_3.jpg diff --git a/build/1.3.15/demo/img/dynamicAnchorBg.jpg b/archive/1.3.15/demo/img/dynamicAnchorBg.jpg similarity index 100% rename from build/1.3.15/demo/img/dynamicAnchorBg.jpg rename to archive/1.3.15/demo/img/dynamicAnchorBg.jpg diff --git a/build/1.3.15/demo/img/ellipse.png b/archive/1.3.15/demo/img/ellipse.png similarity index 100% rename from build/1.3.15/demo/img/ellipse.png rename to archive/1.3.15/demo/img/ellipse.png diff --git a/build/1.3.15/demo/img/endpointTest1.png b/archive/1.3.15/demo/img/endpointTest1.png similarity index 100% rename from build/1.3.15/demo/img/endpointTest1.png rename to archive/1.3.15/demo/img/endpointTest1.png diff --git a/build/1.3.15/demo/img/endpointTest1.xcf b/archive/1.3.15/demo/img/endpointTest1.xcf similarity index 100% rename from build/1.3.15/demo/img/endpointTest1.xcf rename to archive/1.3.15/demo/img/endpointTest1.xcf diff --git a/build/1.3.15/demo/img/index-bg.gif b/archive/1.3.15/demo/img/index-bg.gif similarity index 100% rename from build/1.3.15/demo/img/index-bg.gif rename to archive/1.3.15/demo/img/index-bg.gif diff --git a/build/1.3.15/demo/img/issue4_final.jpg b/archive/1.3.15/demo/img/issue4_final.jpg similarity index 100% rename from build/1.3.15/demo/img/issue4_final.jpg rename to archive/1.3.15/demo/img/issue4_final.jpg diff --git a/build/1.3.15/demo/img/littledot.png b/archive/1.3.15/demo/img/littledot.png similarity index 100% rename from build/1.3.15/demo/img/littledot.png rename to archive/1.3.15/demo/img/littledot.png diff --git a/build/1.3.15/demo/img/littledot.xcf b/archive/1.3.15/demo/img/littledot.xcf similarity index 100% rename from build/1.3.15/demo/img/littledot.xcf rename to archive/1.3.15/demo/img/littledot.xcf diff --git a/build/1.3.15/demo/img/pattern.jpg b/archive/1.3.15/demo/img/pattern.jpg similarity index 100% rename from build/1.3.15/demo/img/pattern.jpg rename to archive/1.3.15/demo/img/pattern.jpg diff --git a/build/1.3.15/demo/img/rectangle.png b/archive/1.3.15/demo/img/rectangle.png similarity index 100% rename from build/1.3.15/demo/img/rectangle.png rename to archive/1.3.15/demo/img/rectangle.png diff --git a/build/1.3.15/demo/img/square.png b/archive/1.3.15/demo/img/square.png similarity index 100% rename from build/1.3.15/demo/img/square.png rename to archive/1.3.15/demo/img/square.png diff --git a/build/1.3.15/demo/img/swappedAnchors.jpg b/archive/1.3.15/demo/img/swappedAnchors.jpg similarity index 100% rename from build/1.3.15/demo/img/swappedAnchors.jpg rename to archive/1.3.15/demo/img/swappedAnchors.jpg diff --git a/build/1.3.15/demo/img/triangle.png b/archive/1.3.15/demo/img/triangle.png similarity index 100% rename from build/1.3.15/demo/img/triangle.png rename to archive/1.3.15/demo/img/triangle.png diff --git a/build/1.3.15/demo/img/triangle_90.png b/archive/1.3.15/demo/img/triangle_90.png similarity index 100% rename from build/1.3.15/demo/img/triangle_90.png rename to archive/1.3.15/demo/img/triangle_90.png diff --git a/build/1.3.15/demo/index.html b/archive/1.3.15/demo/index.html similarity index 100% rename from build/1.3.15/demo/index.html rename to archive/1.3.15/demo/index.html diff --git a/build/1.3.15/demo/jquery/anchorDemo.html b/archive/1.3.15/demo/jquery/anchorDemo.html similarity index 100% rename from build/1.3.15/demo/jquery/anchorDemo.html rename to archive/1.3.15/demo/jquery/anchorDemo.html diff --git a/build/1.3.15/demo/jquery/chartDemo.html b/archive/1.3.15/demo/jquery/chartDemo.html similarity index 100% rename from build/1.3.15/demo/jquery/chartDemo.html rename to archive/1.3.15/demo/jquery/chartDemo.html diff --git a/build/1.3.15/demo/jquery/demo.html b/archive/1.3.15/demo/jquery/demo.html similarity index 100% rename from build/1.3.15/demo/jquery/demo.html rename to archive/1.3.15/demo/jquery/demo.html diff --git a/build/1.3.15/demo/jquery/dragAnimDemo.html b/archive/1.3.15/demo/jquery/dragAnimDemo.html similarity index 100% rename from build/1.3.15/demo/jquery/dragAnimDemo.html rename to archive/1.3.15/demo/jquery/dragAnimDemo.html diff --git a/build/1.3.15/demo/jquery/draggableConnectorsDemo.html b/archive/1.3.15/demo/jquery/draggableConnectorsDemo.html similarity index 100% rename from build/1.3.15/demo/jquery/draggableConnectorsDemo.html rename to archive/1.3.15/demo/jquery/draggableConnectorsDemo.html diff --git a/build/1.3.15/demo/jquery/dynamicAnchorsDemo.html b/archive/1.3.15/demo/jquery/dynamicAnchorsDemo.html similarity index 100% rename from build/1.3.15/demo/jquery/dynamicAnchorsDemo.html rename to archive/1.3.15/demo/jquery/dynamicAnchorsDemo.html diff --git a/build/1.3.15/demo/jquery/flowchartConnectorsDemo.html b/archive/1.3.15/demo/jquery/flowchartConnectorsDemo.html similarity index 100% rename from build/1.3.15/demo/jquery/flowchartConnectorsDemo.html rename to archive/1.3.15/demo/jquery/flowchartConnectorsDemo.html diff --git a/build/1.3.15/demo/jquery/loadTest.html b/archive/1.3.15/demo/jquery/loadTest.html similarity index 100% rename from build/1.3.15/demo/jquery/loadTest.html rename to archive/1.3.15/demo/jquery/loadTest.html diff --git a/build/1.3.15/demo/jquery/perimeterAnchorsDemo.html b/archive/1.3.15/demo/jquery/perimeterAnchorsDemo.html similarity index 100% rename from build/1.3.15/demo/jquery/perimeterAnchorsDemo.html rename to archive/1.3.15/demo/jquery/perimeterAnchorsDemo.html diff --git a/build/1.3.15/demo/jquery/stateMachineDemo.html b/archive/1.3.15/demo/jquery/stateMachineDemo.html similarity index 100% rename from build/1.3.15/demo/jquery/stateMachineDemo.html rename to archive/1.3.15/demo/jquery/stateMachineDemo.html diff --git a/build/1.3.15/demo/js/anchorDemo-jquery.js b/archive/1.3.15/demo/js/anchorDemo-jquery.js similarity index 100% rename from build/1.3.15/demo/js/anchorDemo-jquery.js rename to archive/1.3.15/demo/js/anchorDemo-jquery.js diff --git a/build/1.3.15/demo/js/anchorDemo-mootools.js b/archive/1.3.15/demo/js/anchorDemo-mootools.js similarity index 100% rename from build/1.3.15/demo/js/anchorDemo-mootools.js rename to archive/1.3.15/demo/js/anchorDemo-mootools.js diff --git a/build/1.3.15/demo/js/anchorDemo-yui3.js b/archive/1.3.15/demo/js/anchorDemo-yui3.js similarity index 100% rename from build/1.3.15/demo/js/anchorDemo-yui3.js rename to archive/1.3.15/demo/js/anchorDemo-yui3.js diff --git a/build/1.3.15/demo/js/anchorDemo.js b/archive/1.3.15/demo/js/anchorDemo.js similarity index 100% rename from build/1.3.15/demo/js/anchorDemo.js rename to archive/1.3.15/demo/js/anchorDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.0/anchorDemo.js b/archive/1.3.15/demo/js/archive/1.3.0/anchorDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.0/anchorDemo.js rename to archive/1.3.15/demo/js/archive/1.3.0/anchorDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.0/chartDemo.js b/archive/1.3.15/demo/js/archive/1.3.0/chartDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.0/chartDemo.js rename to archive/1.3.15/demo/js/archive/1.3.0/chartDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.0/demo-helper-jquery.js b/archive/1.3.15/demo/js/archive/1.3.0/demo-helper-jquery.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.0/demo-helper-jquery.js rename to archive/1.3.15/demo/js/archive/1.3.0/demo-helper-jquery.js diff --git a/build/1.3.15/demo/js/archive/1.3.0/demo-helper-mootools.js b/archive/1.3.15/demo/js/archive/1.3.0/demo-helper-mootools.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.0/demo-helper-mootools.js rename to archive/1.3.15/demo/js/archive/1.3.0/demo-helper-mootools.js diff --git a/build/1.3.15/demo/js/archive/1.3.0/demo-helper-yui3.js b/archive/1.3.15/demo/js/archive/1.3.0/demo-helper-yui3.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.0/demo-helper-yui3.js rename to archive/1.3.15/demo/js/archive/1.3.0/demo-helper-yui3.js diff --git a/build/1.3.15/demo/js/archive/1.3.0/demo.js b/archive/1.3.15/demo/js/archive/1.3.0/demo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.0/demo.js rename to archive/1.3.15/demo/js/archive/1.3.0/demo.js diff --git a/build/1.3.15/demo/js/archive/1.3.0/dragAnimDemo-jquery.js b/archive/1.3.15/demo/js/archive/1.3.0/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.0/dragAnimDemo-jquery.js rename to archive/1.3.15/demo/js/archive/1.3.0/dragAnimDemo-jquery.js diff --git a/build/1.3.15/demo/js/archive/1.3.0/dragAnimDemo-mootools.js b/archive/1.3.15/demo/js/archive/1.3.0/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.0/dragAnimDemo-mootools.js rename to archive/1.3.15/demo/js/archive/1.3.0/dragAnimDemo-mootools.js diff --git a/build/1.3.15/demo/js/archive/1.3.0/dragAnimDemo-yui3.js b/archive/1.3.15/demo/js/archive/1.3.0/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.0/dragAnimDemo-yui3.js rename to archive/1.3.15/demo/js/archive/1.3.0/dragAnimDemo-yui3.js diff --git a/build/1.3.15/demo/js/archive/1.3.0/dragAnimDemo.js b/archive/1.3.15/demo/js/archive/1.3.0/dragAnimDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.0/dragAnimDemo.js rename to archive/1.3.15/demo/js/archive/1.3.0/dragAnimDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js b/archive/1.3.15/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js rename to archive/1.3.15/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.15/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js b/archive/1.3.15/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js rename to archive/1.3.15/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.15/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js b/archive/1.3.15/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js rename to archive/1.3.15/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.15/demo/js/archive/1.3.0/draggableConnectorsDemo.js b/archive/1.3.15/demo/js/archive/1.3.0/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.0/draggableConnectorsDemo.js rename to archive/1.3.15/demo/js/archive/1.3.0/draggableConnectorsDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.0/dynamicAnchorsDemo.js b/archive/1.3.15/demo/js/archive/1.3.0/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.0/dynamicAnchorsDemo.js rename to archive/1.3.15/demo/js/archive/1.3.0/dynamicAnchorsDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.0/flowchartConnectorsDemo.js b/archive/1.3.15/demo/js/archive/1.3.0/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.0/flowchartConnectorsDemo.js rename to archive/1.3.15/demo/js/archive/1.3.0/flowchartConnectorsDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.1/anchorDemo.js b/archive/1.3.15/demo/js/archive/1.3.1/anchorDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.1/anchorDemo.js rename to archive/1.3.15/demo/js/archive/1.3.1/anchorDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.1/chartDemo.js b/archive/1.3.15/demo/js/archive/1.3.1/chartDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.1/chartDemo.js rename to archive/1.3.15/demo/js/archive/1.3.1/chartDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.1/demo-helper-jquery.js b/archive/1.3.15/demo/js/archive/1.3.1/demo-helper-jquery.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.1/demo-helper-jquery.js rename to archive/1.3.15/demo/js/archive/1.3.1/demo-helper-jquery.js diff --git a/build/1.3.15/demo/js/archive/1.3.1/demo-helper-mootools.js b/archive/1.3.15/demo/js/archive/1.3.1/demo-helper-mootools.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.1/demo-helper-mootools.js rename to archive/1.3.15/demo/js/archive/1.3.1/demo-helper-mootools.js diff --git a/build/1.3.15/demo/js/archive/1.3.1/demo-helper-yui3.js b/archive/1.3.15/demo/js/archive/1.3.1/demo-helper-yui3.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.1/demo-helper-yui3.js rename to archive/1.3.15/demo/js/archive/1.3.1/demo-helper-yui3.js diff --git a/build/1.3.15/demo/js/archive/1.3.1/demo.js b/archive/1.3.15/demo/js/archive/1.3.1/demo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.1/demo.js rename to archive/1.3.15/demo/js/archive/1.3.1/demo.js diff --git a/build/1.3.15/demo/js/archive/1.3.1/dragAnimDemo-jquery.js b/archive/1.3.15/demo/js/archive/1.3.1/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.1/dragAnimDemo-jquery.js rename to archive/1.3.15/demo/js/archive/1.3.1/dragAnimDemo-jquery.js diff --git a/build/1.3.15/demo/js/archive/1.3.1/dragAnimDemo-mootools.js b/archive/1.3.15/demo/js/archive/1.3.1/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.1/dragAnimDemo-mootools.js rename to archive/1.3.15/demo/js/archive/1.3.1/dragAnimDemo-mootools.js diff --git a/build/1.3.15/demo/js/archive/1.3.1/dragAnimDemo-yui3.js b/archive/1.3.15/demo/js/archive/1.3.1/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.1/dragAnimDemo-yui3.js rename to archive/1.3.15/demo/js/archive/1.3.1/dragAnimDemo-yui3.js diff --git a/build/1.3.15/demo/js/archive/1.3.1/dragAnimDemo.js b/archive/1.3.15/demo/js/archive/1.3.1/dragAnimDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.1/dragAnimDemo.js rename to archive/1.3.15/demo/js/archive/1.3.1/dragAnimDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js b/archive/1.3.15/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js rename to archive/1.3.15/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.15/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js b/archive/1.3.15/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js rename to archive/1.3.15/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.15/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js b/archive/1.3.15/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js rename to archive/1.3.15/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.15/demo/js/archive/1.3.1/draggableConnectorsDemo.js b/archive/1.3.15/demo/js/archive/1.3.1/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.1/draggableConnectorsDemo.js rename to archive/1.3.15/demo/js/archive/1.3.1/draggableConnectorsDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.1/dynamicAnchorsDemo.js b/archive/1.3.15/demo/js/archive/1.3.1/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.1/dynamicAnchorsDemo.js rename to archive/1.3.15/demo/js/archive/1.3.1/dynamicAnchorsDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.1/flowchartConnectorsDemo.js b/archive/1.3.15/demo/js/archive/1.3.1/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.1/flowchartConnectorsDemo.js rename to archive/1.3.15/demo/js/archive/1.3.1/flowchartConnectorsDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.2/anchorDemo.js b/archive/1.3.15/demo/js/archive/1.3.2/anchorDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.2/anchorDemo.js rename to archive/1.3.15/demo/js/archive/1.3.2/anchorDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.2/chartDemo.js b/archive/1.3.15/demo/js/archive/1.3.2/chartDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.2/chartDemo.js rename to archive/1.3.15/demo/js/archive/1.3.2/chartDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.2/demo-helper-jquery.js b/archive/1.3.15/demo/js/archive/1.3.2/demo-helper-jquery.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.2/demo-helper-jquery.js rename to archive/1.3.15/demo/js/archive/1.3.2/demo-helper-jquery.js diff --git a/build/1.3.15/demo/js/archive/1.3.2/demo-helper-mootools.js b/archive/1.3.15/demo/js/archive/1.3.2/demo-helper-mootools.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.2/demo-helper-mootools.js rename to archive/1.3.15/demo/js/archive/1.3.2/demo-helper-mootools.js diff --git a/build/1.3.15/demo/js/archive/1.3.2/demo-helper-yui3.js b/archive/1.3.15/demo/js/archive/1.3.2/demo-helper-yui3.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.2/demo-helper-yui3.js rename to archive/1.3.15/demo/js/archive/1.3.2/demo-helper-yui3.js diff --git a/build/1.3.15/demo/js/archive/1.3.2/demo.js b/archive/1.3.15/demo/js/archive/1.3.2/demo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.2/demo.js rename to archive/1.3.15/demo/js/archive/1.3.2/demo.js diff --git a/build/1.3.15/demo/js/archive/1.3.2/dragAnimDemo-jquery.js b/archive/1.3.15/demo/js/archive/1.3.2/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.2/dragAnimDemo-jquery.js rename to archive/1.3.15/demo/js/archive/1.3.2/dragAnimDemo-jquery.js diff --git a/build/1.3.15/demo/js/archive/1.3.2/dragAnimDemo-mootools.js b/archive/1.3.15/demo/js/archive/1.3.2/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.2/dragAnimDemo-mootools.js rename to archive/1.3.15/demo/js/archive/1.3.2/dragAnimDemo-mootools.js diff --git a/build/1.3.15/demo/js/archive/1.3.2/dragAnimDemo-yui3.js b/archive/1.3.15/demo/js/archive/1.3.2/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.2/dragAnimDemo-yui3.js rename to archive/1.3.15/demo/js/archive/1.3.2/dragAnimDemo-yui3.js diff --git a/build/1.3.15/demo/js/archive/1.3.2/dragAnimDemo.js b/archive/1.3.15/demo/js/archive/1.3.2/dragAnimDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.2/dragAnimDemo.js rename to archive/1.3.15/demo/js/archive/1.3.2/dragAnimDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js b/archive/1.3.15/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js rename to archive/1.3.15/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.15/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js b/archive/1.3.15/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js rename to archive/1.3.15/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.15/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js b/archive/1.3.15/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js rename to archive/1.3.15/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.15/demo/js/archive/1.3.2/draggableConnectorsDemo.js b/archive/1.3.15/demo/js/archive/1.3.2/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.2/draggableConnectorsDemo.js rename to archive/1.3.15/demo/js/archive/1.3.2/draggableConnectorsDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.2/dynamicAnchorsDemo.js b/archive/1.3.15/demo/js/archive/1.3.2/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.2/dynamicAnchorsDemo.js rename to archive/1.3.15/demo/js/archive/1.3.2/dynamicAnchorsDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.2/flowchartConnectorsDemo.js b/archive/1.3.15/demo/js/archive/1.3.2/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.2/flowchartConnectorsDemo.js rename to archive/1.3.15/demo/js/archive/1.3.2/flowchartConnectorsDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.3/anchorDemo.js b/archive/1.3.15/demo/js/archive/1.3.3/anchorDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.3/anchorDemo.js rename to archive/1.3.15/demo/js/archive/1.3.3/anchorDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.3/chartDemo.js b/archive/1.3.15/demo/js/archive/1.3.3/chartDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.3/chartDemo.js rename to archive/1.3.15/demo/js/archive/1.3.3/chartDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.3/demo-helper-jquery.js b/archive/1.3.15/demo/js/archive/1.3.3/demo-helper-jquery.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.3/demo-helper-jquery.js rename to archive/1.3.15/demo/js/archive/1.3.3/demo-helper-jquery.js diff --git a/build/1.3.15/demo/js/archive/1.3.3/demo-helper-mootools.js b/archive/1.3.15/demo/js/archive/1.3.3/demo-helper-mootools.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.3/demo-helper-mootools.js rename to archive/1.3.15/demo/js/archive/1.3.3/demo-helper-mootools.js diff --git a/build/1.3.15/demo/js/archive/1.3.3/demo-helper-yui3.js b/archive/1.3.15/demo/js/archive/1.3.3/demo-helper-yui3.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.3/demo-helper-yui3.js rename to archive/1.3.15/demo/js/archive/1.3.3/demo-helper-yui3.js diff --git a/build/1.3.15/demo/js/archive/1.3.3/demo.js b/archive/1.3.15/demo/js/archive/1.3.3/demo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.3/demo.js rename to archive/1.3.15/demo/js/archive/1.3.3/demo.js diff --git a/build/1.3.15/demo/js/archive/1.3.3/dragAnimDemo-jquery.js b/archive/1.3.15/demo/js/archive/1.3.3/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.3/dragAnimDemo-jquery.js rename to archive/1.3.15/demo/js/archive/1.3.3/dragAnimDemo-jquery.js diff --git a/build/1.3.15/demo/js/archive/1.3.3/dragAnimDemo-mootools.js b/archive/1.3.15/demo/js/archive/1.3.3/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.3/dragAnimDemo-mootools.js rename to archive/1.3.15/demo/js/archive/1.3.3/dragAnimDemo-mootools.js diff --git a/build/1.3.15/demo/js/archive/1.3.3/dragAnimDemo-yui3.js b/archive/1.3.15/demo/js/archive/1.3.3/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.3/dragAnimDemo-yui3.js rename to archive/1.3.15/demo/js/archive/1.3.3/dragAnimDemo-yui3.js diff --git a/build/1.3.15/demo/js/archive/1.3.3/dragAnimDemo.js b/archive/1.3.15/demo/js/archive/1.3.3/dragAnimDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.3/dragAnimDemo.js rename to archive/1.3.15/demo/js/archive/1.3.3/dragAnimDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js b/archive/1.3.15/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js rename to archive/1.3.15/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.15/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js b/archive/1.3.15/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js rename to archive/1.3.15/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.15/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js b/archive/1.3.15/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js rename to archive/1.3.15/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.15/demo/js/archive/1.3.3/draggableConnectorsDemo.js b/archive/1.3.15/demo/js/archive/1.3.3/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.3/draggableConnectorsDemo.js rename to archive/1.3.15/demo/js/archive/1.3.3/draggableConnectorsDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.3/dynamicAnchorsDemo.js b/archive/1.3.15/demo/js/archive/1.3.3/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.3/dynamicAnchorsDemo.js rename to archive/1.3.15/demo/js/archive/1.3.3/dynamicAnchorsDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.3/flowchartConnectorsDemo.js b/archive/1.3.15/demo/js/archive/1.3.3/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.3/flowchartConnectorsDemo.js rename to archive/1.3.15/demo/js/archive/1.3.3/flowchartConnectorsDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js b/archive/1.3.15/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js rename to archive/1.3.15/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js diff --git a/build/1.3.15/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all.js b/archive/1.3.15/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all.js rename to archive/1.3.15/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all.js diff --git a/build/1.3.15/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js b/archive/1.3.15/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js rename to archive/1.3.15/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js diff --git a/build/1.3.15/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js b/archive/1.3.15/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js rename to archive/1.3.15/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js diff --git a/build/1.3.15/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js b/archive/1.3.15/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js rename to archive/1.3.15/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js diff --git a/build/1.3.15/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js b/archive/1.3.15/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js rename to archive/1.3.15/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js diff --git a/build/1.3.15/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js b/archive/1.3.15/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js rename to archive/1.3.15/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js diff --git a/build/1.3.15/demo/js/archive/1.3.4/anchorDemo.js b/archive/1.3.15/demo/js/archive/1.3.4/anchorDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.4/anchorDemo.js rename to archive/1.3.15/demo/js/archive/1.3.4/anchorDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.4/chartDemo.js b/archive/1.3.15/demo/js/archive/1.3.4/chartDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.4/chartDemo.js rename to archive/1.3.15/demo/js/archive/1.3.4/chartDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.4/demo-helper-jquery.js b/archive/1.3.15/demo/js/archive/1.3.4/demo-helper-jquery.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.4/demo-helper-jquery.js rename to archive/1.3.15/demo/js/archive/1.3.4/demo-helper-jquery.js diff --git a/build/1.3.15/demo/js/archive/1.3.4/demo-helper-mootools.js b/archive/1.3.15/demo/js/archive/1.3.4/demo-helper-mootools.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.4/demo-helper-mootools.js rename to archive/1.3.15/demo/js/archive/1.3.4/demo-helper-mootools.js diff --git a/build/1.3.15/demo/js/archive/1.3.4/demo-helper-yui3.js b/archive/1.3.15/demo/js/archive/1.3.4/demo-helper-yui3.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.4/demo-helper-yui3.js rename to archive/1.3.15/demo/js/archive/1.3.4/demo-helper-yui3.js diff --git a/build/1.3.15/demo/js/archive/1.3.4/demo-list.js b/archive/1.3.15/demo/js/archive/1.3.4/demo-list.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.4/demo-list.js rename to archive/1.3.15/demo/js/archive/1.3.4/demo-list.js diff --git a/build/1.3.15/demo/js/archive/1.3.4/demo.js b/archive/1.3.15/demo/js/archive/1.3.4/demo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.4/demo.js rename to archive/1.3.15/demo/js/archive/1.3.4/demo.js diff --git a/build/1.3.15/demo/js/archive/1.3.4/dragAnimDemo-jquery.js b/archive/1.3.15/demo/js/archive/1.3.4/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.4/dragAnimDemo-jquery.js rename to archive/1.3.15/demo/js/archive/1.3.4/dragAnimDemo-jquery.js diff --git a/build/1.3.15/demo/js/archive/1.3.4/dragAnimDemo-mootools.js b/archive/1.3.15/demo/js/archive/1.3.4/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.4/dragAnimDemo-mootools.js rename to archive/1.3.15/demo/js/archive/1.3.4/dragAnimDemo-mootools.js diff --git a/build/1.3.15/demo/js/archive/1.3.4/dragAnimDemo-yui3.js b/archive/1.3.15/demo/js/archive/1.3.4/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.4/dragAnimDemo-yui3.js rename to archive/1.3.15/demo/js/archive/1.3.4/dragAnimDemo-yui3.js diff --git a/build/1.3.15/demo/js/archive/1.3.4/dragAnimDemo.js b/archive/1.3.15/demo/js/archive/1.3.4/dragAnimDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.4/dragAnimDemo.js rename to archive/1.3.15/demo/js/archive/1.3.4/dragAnimDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.4/draggableConnectorsDemo-jquery.js b/archive/1.3.15/demo/js/archive/1.3.4/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.4/draggableConnectorsDemo-jquery.js rename to archive/1.3.15/demo/js/archive/1.3.4/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.15/demo/js/archive/1.3.4/draggableConnectorsDemo-mootools.js b/archive/1.3.15/demo/js/archive/1.3.4/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.4/draggableConnectorsDemo-mootools.js rename to archive/1.3.15/demo/js/archive/1.3.4/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.15/demo/js/archive/1.3.4/draggableConnectorsDemo-yui3.js b/archive/1.3.15/demo/js/archive/1.3.4/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.4/draggableConnectorsDemo-yui3.js rename to archive/1.3.15/demo/js/archive/1.3.4/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.15/demo/js/archive/1.3.4/draggableConnectorsDemo.js b/archive/1.3.15/demo/js/archive/1.3.4/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.4/draggableConnectorsDemo.js rename to archive/1.3.15/demo/js/archive/1.3.4/draggableConnectorsDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.4/dynamicAnchorsDemo.js b/archive/1.3.15/demo/js/archive/1.3.4/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.4/dynamicAnchorsDemo.js rename to archive/1.3.15/demo/js/archive/1.3.4/dynamicAnchorsDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.4/flowchartConnectorsDemo.js b/archive/1.3.15/demo/js/archive/1.3.4/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.4/flowchartConnectorsDemo.js rename to archive/1.3.15/demo/js/archive/1.3.4/flowchartConnectorsDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.4/makeSourceDemo.js b/archive/1.3.15/demo/js/archive/1.3.4/makeSourceDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.4/makeSourceDemo.js rename to archive/1.3.15/demo/js/archive/1.3.4/makeSourceDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.4/makeTargetDemo.js b/archive/1.3.15/demo/js/archive/1.3.4/makeTargetDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.4/makeTargetDemo.js rename to archive/1.3.15/demo/js/archive/1.3.4/makeTargetDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.4/stateMachineDemo-jquery.js b/archive/1.3.15/demo/js/archive/1.3.4/stateMachineDemo-jquery.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.4/stateMachineDemo-jquery.js rename to archive/1.3.15/demo/js/archive/1.3.4/stateMachineDemo-jquery.js diff --git a/build/1.3.15/demo/js/archive/1.3.4/stateMachineDemo-mootools.js b/archive/1.3.15/demo/js/archive/1.3.4/stateMachineDemo-mootools.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.4/stateMachineDemo-mootools.js rename to archive/1.3.15/demo/js/archive/1.3.4/stateMachineDemo-mootools.js diff --git a/build/1.3.15/demo/js/archive/1.3.4/stateMachineDemo-yui3.js b/archive/1.3.15/demo/js/archive/1.3.4/stateMachineDemo-yui3.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.4/stateMachineDemo-yui3.js rename to archive/1.3.15/demo/js/archive/1.3.4/stateMachineDemo-yui3.js diff --git a/build/1.3.15/demo/js/archive/1.3.4/stateMachineDemo.js b/archive/1.3.15/demo/js/archive/1.3.4/stateMachineDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.4/stateMachineDemo.js rename to archive/1.3.15/demo/js/archive/1.3.4/stateMachineDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.5/anchorDemo.js b/archive/1.3.15/demo/js/archive/1.3.5/anchorDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.5/anchorDemo.js rename to archive/1.3.15/demo/js/archive/1.3.5/anchorDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.5/chartDemo.js b/archive/1.3.15/demo/js/archive/1.3.5/chartDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.5/chartDemo.js rename to archive/1.3.15/demo/js/archive/1.3.5/chartDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.5/demo-helper-jquery.js b/archive/1.3.15/demo/js/archive/1.3.5/demo-helper-jquery.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.5/demo-helper-jquery.js rename to archive/1.3.15/demo/js/archive/1.3.5/demo-helper-jquery.js diff --git a/build/1.3.15/demo/js/archive/1.3.5/demo-helper-mootools.js b/archive/1.3.15/demo/js/archive/1.3.5/demo-helper-mootools.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.5/demo-helper-mootools.js rename to archive/1.3.15/demo/js/archive/1.3.5/demo-helper-mootools.js diff --git a/build/1.3.15/demo/js/archive/1.3.5/demo-helper-yui3.js b/archive/1.3.15/demo/js/archive/1.3.5/demo-helper-yui3.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.5/demo-helper-yui3.js rename to archive/1.3.15/demo/js/archive/1.3.5/demo-helper-yui3.js diff --git a/build/1.3.15/demo/js/archive/1.3.5/demo-list.js b/archive/1.3.15/demo/js/archive/1.3.5/demo-list.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.5/demo-list.js rename to archive/1.3.15/demo/js/archive/1.3.5/demo-list.js diff --git a/build/1.3.15/demo/js/archive/1.3.5/demo.js b/archive/1.3.15/demo/js/archive/1.3.5/demo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.5/demo.js rename to archive/1.3.15/demo/js/archive/1.3.5/demo.js diff --git a/build/1.3.15/demo/js/archive/1.3.5/dragAnimDemo-jquery.js b/archive/1.3.15/demo/js/archive/1.3.5/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.5/dragAnimDemo-jquery.js rename to archive/1.3.15/demo/js/archive/1.3.5/dragAnimDemo-jquery.js diff --git a/build/1.3.15/demo/js/archive/1.3.5/dragAnimDemo-mootools.js b/archive/1.3.15/demo/js/archive/1.3.5/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.5/dragAnimDemo-mootools.js rename to archive/1.3.15/demo/js/archive/1.3.5/dragAnimDemo-mootools.js diff --git a/build/1.3.15/demo/js/archive/1.3.5/dragAnimDemo-yui3.js b/archive/1.3.15/demo/js/archive/1.3.5/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.5/dragAnimDemo-yui3.js rename to archive/1.3.15/demo/js/archive/1.3.5/dragAnimDemo-yui3.js diff --git a/build/1.3.15/demo/js/archive/1.3.5/dragAnimDemo.js b/archive/1.3.15/demo/js/archive/1.3.5/dragAnimDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.5/dragAnimDemo.js rename to archive/1.3.15/demo/js/archive/1.3.5/dragAnimDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.5/draggableConnectorsDemo-jquery.js b/archive/1.3.15/demo/js/archive/1.3.5/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.5/draggableConnectorsDemo-jquery.js rename to archive/1.3.15/demo/js/archive/1.3.5/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.15/demo/js/archive/1.3.5/draggableConnectorsDemo-mootools.js b/archive/1.3.15/demo/js/archive/1.3.5/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.5/draggableConnectorsDemo-mootools.js rename to archive/1.3.15/demo/js/archive/1.3.5/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.15/demo/js/archive/1.3.5/draggableConnectorsDemo-yui3.js b/archive/1.3.15/demo/js/archive/1.3.5/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.5/draggableConnectorsDemo-yui3.js rename to archive/1.3.15/demo/js/archive/1.3.5/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.15/demo/js/archive/1.3.5/draggableConnectorsDemo.js b/archive/1.3.15/demo/js/archive/1.3.5/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.5/draggableConnectorsDemo.js rename to archive/1.3.15/demo/js/archive/1.3.5/draggableConnectorsDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.5/dynamicAnchorsDemo.js b/archive/1.3.15/demo/js/archive/1.3.5/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.5/dynamicAnchorsDemo.js rename to archive/1.3.15/demo/js/archive/1.3.5/dynamicAnchorsDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.5/flowchartConnectorsDemo.js b/archive/1.3.15/demo/js/archive/1.3.5/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.5/flowchartConnectorsDemo.js rename to archive/1.3.15/demo/js/archive/1.3.5/flowchartConnectorsDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.5/loadTest.js b/archive/1.3.15/demo/js/archive/1.3.5/loadTest.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.5/loadTest.js rename to archive/1.3.15/demo/js/archive/1.3.5/loadTest.js diff --git a/build/1.3.15/demo/js/archive/1.3.5/makeSourceDemo.js b/archive/1.3.15/demo/js/archive/1.3.5/makeSourceDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.5/makeSourceDemo.js rename to archive/1.3.15/demo/js/archive/1.3.5/makeSourceDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.5/makeTargetDemo.js b/archive/1.3.15/demo/js/archive/1.3.5/makeTargetDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.5/makeTargetDemo.js rename to archive/1.3.15/demo/js/archive/1.3.5/makeTargetDemo.js diff --git a/build/1.3.15/demo/js/archive/1.3.5/stateMachineDemo-jquery.js b/archive/1.3.15/demo/js/archive/1.3.5/stateMachineDemo-jquery.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.5/stateMachineDemo-jquery.js rename to archive/1.3.15/demo/js/archive/1.3.5/stateMachineDemo-jquery.js diff --git a/build/1.3.15/demo/js/archive/1.3.5/stateMachineDemo-mootools.js b/archive/1.3.15/demo/js/archive/1.3.5/stateMachineDemo-mootools.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.5/stateMachineDemo-mootools.js rename to archive/1.3.15/demo/js/archive/1.3.5/stateMachineDemo-mootools.js diff --git a/build/1.3.15/demo/js/archive/1.3.5/stateMachineDemo-yui3.js b/archive/1.3.15/demo/js/archive/1.3.5/stateMachineDemo-yui3.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.5/stateMachineDemo-yui3.js rename to archive/1.3.15/demo/js/archive/1.3.5/stateMachineDemo-yui3.js diff --git a/build/1.3.15/demo/js/archive/1.3.5/stateMachineDemo.js b/archive/1.3.15/demo/js/archive/1.3.5/stateMachineDemo.js similarity index 100% rename from build/1.3.15/demo/js/archive/1.3.5/stateMachineDemo.js rename to archive/1.3.15/demo/js/archive/1.3.5/stateMachineDemo.js diff --git a/build/1.3.15/demo/js/chartDemo.js b/archive/1.3.15/demo/js/chartDemo.js similarity index 100% rename from build/1.3.15/demo/js/chartDemo.js rename to archive/1.3.15/demo/js/chartDemo.js diff --git a/build/1.3.15/demo/js/demo-helper-jquery.js b/archive/1.3.15/demo/js/demo-helper-jquery.js similarity index 100% rename from build/1.3.15/demo/js/demo-helper-jquery.js rename to archive/1.3.15/demo/js/demo-helper-jquery.js diff --git a/build/1.3.15/demo/js/demo-helper-mootools.js b/archive/1.3.15/demo/js/demo-helper-mootools.js similarity index 100% rename from build/1.3.15/demo/js/demo-helper-mootools.js rename to archive/1.3.15/demo/js/demo-helper-mootools.js diff --git a/build/1.3.15/demo/js/demo-helper-yui3.js b/archive/1.3.15/demo/js/demo-helper-yui3.js similarity index 100% rename from build/1.3.15/demo/js/demo-helper-yui3.js rename to archive/1.3.15/demo/js/demo-helper-yui3.js diff --git a/build/1.3.15/demo/js/demo-list.js b/archive/1.3.15/demo/js/demo-list.js similarity index 100% rename from build/1.3.15/demo/js/demo-list.js rename to archive/1.3.15/demo/js/demo-list.js diff --git a/build/1.3.15/demo/js/demo.js b/archive/1.3.15/demo/js/demo.js similarity index 100% rename from build/1.3.15/demo/js/demo.js rename to archive/1.3.15/demo/js/demo.js diff --git a/build/1.3.15/demo/js/dragAnimDemo-jquery.js b/archive/1.3.15/demo/js/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.15/demo/js/dragAnimDemo-jquery.js rename to archive/1.3.15/demo/js/dragAnimDemo-jquery.js diff --git a/build/1.3.15/demo/js/dragAnimDemo-mootools.js b/archive/1.3.15/demo/js/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.15/demo/js/dragAnimDemo-mootools.js rename to archive/1.3.15/demo/js/dragAnimDemo-mootools.js diff --git a/build/1.3.15/demo/js/dragAnimDemo-yui3.js b/archive/1.3.15/demo/js/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.15/demo/js/dragAnimDemo-yui3.js rename to archive/1.3.15/demo/js/dragAnimDemo-yui3.js diff --git a/build/1.3.15/demo/js/dragAnimDemo.js b/archive/1.3.15/demo/js/dragAnimDemo.js similarity index 100% rename from build/1.3.15/demo/js/dragAnimDemo.js rename to archive/1.3.15/demo/js/dragAnimDemo.js diff --git a/build/1.3.15/demo/js/draggableConnectorsDemo-jquery.js b/archive/1.3.15/demo/js/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.15/demo/js/draggableConnectorsDemo-jquery.js rename to archive/1.3.15/demo/js/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.15/demo/js/draggableConnectorsDemo-mootools.js b/archive/1.3.15/demo/js/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.15/demo/js/draggableConnectorsDemo-mootools.js rename to archive/1.3.15/demo/js/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.15/demo/js/draggableConnectorsDemo-yui3.js b/archive/1.3.15/demo/js/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.15/demo/js/draggableConnectorsDemo-yui3.js rename to archive/1.3.15/demo/js/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.15/demo/js/draggableConnectorsDemo.js b/archive/1.3.15/demo/js/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.15/demo/js/draggableConnectorsDemo.js rename to archive/1.3.15/demo/js/draggableConnectorsDemo.js diff --git a/build/1.3.15/demo/js/dynamicAnchorsDemo.js b/archive/1.3.15/demo/js/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.15/demo/js/dynamicAnchorsDemo.js rename to archive/1.3.15/demo/js/dynamicAnchorsDemo.js diff --git a/build/1.3.15/demo/js/flowchartConnectorsDemo.js b/archive/1.3.15/demo/js/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.15/demo/js/flowchartConnectorsDemo.js rename to archive/1.3.15/demo/js/flowchartConnectorsDemo.js diff --git a/build/1.3.15/demo/js/jquery.jsPlumb-1.3.15-all-min.js b/archive/1.3.15/demo/js/jquery.jsPlumb-1.3.15-all-min.js similarity index 100% rename from build/1.3.15/demo/js/jquery.jsPlumb-1.3.15-all-min.js rename to archive/1.3.15/demo/js/jquery.jsPlumb-1.3.15-all-min.js diff --git a/build/1.3.15/demo/js/jquery.jsPlumb-1.3.15-all.js b/archive/1.3.15/demo/js/jquery.jsPlumb-1.3.15-all.js similarity index 100% rename from build/1.3.15/demo/js/jquery.jsPlumb-1.3.15-all.js rename to archive/1.3.15/demo/js/jquery.jsPlumb-1.3.15-all.js diff --git a/build/1.3.15/demo/js/loadTest.js b/archive/1.3.15/demo/js/loadTest.js similarity index 100% rename from build/1.3.15/demo/js/loadTest.js rename to archive/1.3.15/demo/js/loadTest.js diff --git a/build/1.3.15/demo/js/makeSourceDemo.js b/archive/1.3.15/demo/js/makeSourceDemo.js similarity index 100% rename from build/1.3.15/demo/js/makeSourceDemo.js rename to archive/1.3.15/demo/js/makeSourceDemo.js diff --git a/build/1.3.15/demo/js/makeTargetDemo.js b/archive/1.3.15/demo/js/makeTargetDemo.js similarity index 100% rename from build/1.3.15/demo/js/makeTargetDemo.js rename to archive/1.3.15/demo/js/makeTargetDemo.js diff --git a/build/1.3.15/demo/js/mootools-1.3.2.1-more.js b/archive/1.3.15/demo/js/mootools-1.3.2.1-more.js similarity index 100% rename from build/1.3.15/demo/js/mootools-1.3.2.1-more.js rename to archive/1.3.15/demo/js/mootools-1.3.2.1-more.js diff --git a/build/1.3.15/demo/js/mootools.jsPlumb-1.3.15-all-min.js b/archive/1.3.15/demo/js/mootools.jsPlumb-1.3.15-all-min.js similarity index 100% rename from build/1.3.15/demo/js/mootools.jsPlumb-1.3.15-all-min.js rename to archive/1.3.15/demo/js/mootools.jsPlumb-1.3.15-all-min.js diff --git a/build/1.3.15/demo/js/mootools.jsPlumb-1.3.15-all.js b/archive/1.3.15/demo/js/mootools.jsPlumb-1.3.15-all.js similarity index 100% rename from build/1.3.15/demo/js/mootools.jsPlumb-1.3.15-all.js rename to archive/1.3.15/demo/js/mootools.jsPlumb-1.3.15-all.js diff --git a/build/1.3.15/demo/js/perimeterAnchorsDemo-jquery.js b/archive/1.3.15/demo/js/perimeterAnchorsDemo-jquery.js similarity index 100% rename from build/1.3.15/demo/js/perimeterAnchorsDemo-jquery.js rename to archive/1.3.15/demo/js/perimeterAnchorsDemo-jquery.js diff --git a/build/1.3.15/demo/js/perimeterAnchorsDemo-mootools.js b/archive/1.3.15/demo/js/perimeterAnchorsDemo-mootools.js similarity index 100% rename from build/1.3.15/demo/js/perimeterAnchorsDemo-mootools.js rename to archive/1.3.15/demo/js/perimeterAnchorsDemo-mootools.js diff --git a/build/1.3.15/demo/js/perimeterAnchorsDemo-yui3.js b/archive/1.3.15/demo/js/perimeterAnchorsDemo-yui3.js similarity index 100% rename from build/1.3.15/demo/js/perimeterAnchorsDemo-yui3.js rename to archive/1.3.15/demo/js/perimeterAnchorsDemo-yui3.js diff --git a/build/1.3.15/demo/js/stateMachineDemo-jquery.js b/archive/1.3.15/demo/js/stateMachineDemo-jquery.js similarity index 100% rename from build/1.3.15/demo/js/stateMachineDemo-jquery.js rename to archive/1.3.15/demo/js/stateMachineDemo-jquery.js diff --git a/build/1.3.15/demo/js/stateMachineDemo-mootools.js b/archive/1.3.15/demo/js/stateMachineDemo-mootools.js similarity index 100% rename from build/1.3.15/demo/js/stateMachineDemo-mootools.js rename to archive/1.3.15/demo/js/stateMachineDemo-mootools.js diff --git a/build/1.3.15/demo/js/stateMachineDemo-yui3.js b/archive/1.3.15/demo/js/stateMachineDemo-yui3.js similarity index 100% rename from build/1.3.15/demo/js/stateMachineDemo-yui3.js rename to archive/1.3.15/demo/js/stateMachineDemo-yui3.js diff --git a/build/1.3.15/demo/js/stateMachineDemo.js b/archive/1.3.15/demo/js/stateMachineDemo.js similarity index 100% rename from build/1.3.15/demo/js/stateMachineDemo.js rename to archive/1.3.15/demo/js/stateMachineDemo.js diff --git a/build/1.3.15/demo/js/yui-3.3.0-min.js b/archive/1.3.15/demo/js/yui-3.3.0-min.js similarity index 100% rename from build/1.3.15/demo/js/yui-3.3.0-min.js rename to archive/1.3.15/demo/js/yui-3.3.0-min.js diff --git a/build/1.3.15/demo/js/yui.jsPlumb-1.3.15-all-min.js b/archive/1.3.15/demo/js/yui.jsPlumb-1.3.15-all-min.js similarity index 100% rename from build/1.3.15/demo/js/yui.jsPlumb-1.3.15-all-min.js rename to archive/1.3.15/demo/js/yui.jsPlumb-1.3.15-all-min.js diff --git a/build/1.3.15/demo/js/yui.jsPlumb-1.3.15-all.js b/archive/1.3.15/demo/js/yui.jsPlumb-1.3.15-all.js similarity index 100% rename from build/1.3.15/demo/js/yui.jsPlumb-1.3.15-all.js rename to archive/1.3.15/demo/js/yui.jsPlumb-1.3.15-all.js diff --git a/build/1.3.15/demo/mootools/anchorDemo.html b/archive/1.3.15/demo/mootools/anchorDemo.html similarity index 100% rename from build/1.3.15/demo/mootools/anchorDemo.html rename to archive/1.3.15/demo/mootools/anchorDemo.html diff --git a/build/1.3.15/demo/mootools/chartDemo.html b/archive/1.3.15/demo/mootools/chartDemo.html similarity index 100% rename from build/1.3.15/demo/mootools/chartDemo.html rename to archive/1.3.15/demo/mootools/chartDemo.html diff --git a/build/1.3.15/demo/mootools/demo.html b/archive/1.3.15/demo/mootools/demo.html similarity index 100% rename from build/1.3.15/demo/mootools/demo.html rename to archive/1.3.15/demo/mootools/demo.html diff --git a/build/1.3.15/demo/mootools/dragAnimDemo.html b/archive/1.3.15/demo/mootools/dragAnimDemo.html similarity index 100% rename from build/1.3.15/demo/mootools/dragAnimDemo.html rename to archive/1.3.15/demo/mootools/dragAnimDemo.html diff --git a/build/1.3.15/demo/mootools/draggableConnectorsDemo.html b/archive/1.3.15/demo/mootools/draggableConnectorsDemo.html similarity index 100% rename from build/1.3.15/demo/mootools/draggableConnectorsDemo.html rename to archive/1.3.15/demo/mootools/draggableConnectorsDemo.html diff --git a/build/1.3.15/demo/mootools/dynamicAnchorsDemo.html b/archive/1.3.15/demo/mootools/dynamicAnchorsDemo.html similarity index 100% rename from build/1.3.15/demo/mootools/dynamicAnchorsDemo.html rename to archive/1.3.15/demo/mootools/dynamicAnchorsDemo.html diff --git a/build/1.3.15/demo/mootools/flowchartConnectorsDemo.html b/archive/1.3.15/demo/mootools/flowchartConnectorsDemo.html similarity index 100% rename from build/1.3.15/demo/mootools/flowchartConnectorsDemo.html rename to archive/1.3.15/demo/mootools/flowchartConnectorsDemo.html diff --git a/build/1.3.15/demo/mootools/perimeterAnchorsDemo.html b/archive/1.3.15/demo/mootools/perimeterAnchorsDemo.html similarity index 100% rename from build/1.3.15/demo/mootools/perimeterAnchorsDemo.html rename to archive/1.3.15/demo/mootools/perimeterAnchorsDemo.html diff --git a/build/1.3.15/demo/mootools/stateMachineDemo.html b/archive/1.3.15/demo/mootools/stateMachineDemo.html similarity index 100% rename from build/1.3.15/demo/mootools/stateMachineDemo.html rename to archive/1.3.15/demo/mootools/stateMachineDemo.html diff --git a/build/1.3.15/demo/yui3/anchorDemo.html b/archive/1.3.15/demo/yui3/anchorDemo.html similarity index 100% rename from build/1.3.15/demo/yui3/anchorDemo.html rename to archive/1.3.15/demo/yui3/anchorDemo.html diff --git a/build/1.3.15/demo/yui3/chartDemo.html b/archive/1.3.15/demo/yui3/chartDemo.html similarity index 100% rename from build/1.3.15/demo/yui3/chartDemo.html rename to archive/1.3.15/demo/yui3/chartDemo.html diff --git a/build/1.3.15/demo/yui3/demo.html b/archive/1.3.15/demo/yui3/demo.html similarity index 100% rename from build/1.3.15/demo/yui3/demo.html rename to archive/1.3.15/demo/yui3/demo.html diff --git a/build/1.3.15/demo/yui3/dragAnimDemo.html b/archive/1.3.15/demo/yui3/dragAnimDemo.html similarity index 100% rename from build/1.3.15/demo/yui3/dragAnimDemo.html rename to archive/1.3.15/demo/yui3/dragAnimDemo.html diff --git a/build/1.3.15/demo/yui3/draggableConnectorsDemo.html b/archive/1.3.15/demo/yui3/draggableConnectorsDemo.html similarity index 100% rename from build/1.3.15/demo/yui3/draggableConnectorsDemo.html rename to archive/1.3.15/demo/yui3/draggableConnectorsDemo.html diff --git a/build/1.3.15/demo/yui3/dynamicAnchorsDemo.html b/archive/1.3.15/demo/yui3/dynamicAnchorsDemo.html similarity index 100% rename from build/1.3.15/demo/yui3/dynamicAnchorsDemo.html rename to archive/1.3.15/demo/yui3/dynamicAnchorsDemo.html diff --git a/build/1.3.15/demo/yui3/flowchartConnectorsDemo.html b/archive/1.3.15/demo/yui3/flowchartConnectorsDemo.html similarity index 100% rename from build/1.3.15/demo/yui3/flowchartConnectorsDemo.html rename to archive/1.3.15/demo/yui3/flowchartConnectorsDemo.html diff --git a/build/1.3.15/demo/yui3/perimeterAnchorsDemo.html b/archive/1.3.15/demo/yui3/perimeterAnchorsDemo.html similarity index 100% rename from build/1.3.15/demo/yui3/perimeterAnchorsDemo.html rename to archive/1.3.15/demo/yui3/perimeterAnchorsDemo.html diff --git a/build/1.3.15/demo/yui3/stateMachineDemo.html b/archive/1.3.15/demo/yui3/stateMachineDemo.html similarity index 100% rename from build/1.3.15/demo/yui3/stateMachineDemo.html rename to archive/1.3.15/demo/yui3/stateMachineDemo.html diff --git a/archive/1.3.15/jquery.jsPlumb-1.3.15-RC1.js b/archive/1.3.15/jquery.jsPlumb-1.3.15-RC1.js new file mode 100644 index 000000000..c544a681c --- /dev/null +++ b/archive/1.3.15/jquery.jsPlumb-1.3.15-RC1.js @@ -0,0 +1,379 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.15 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the jQuery adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ +/* + * the library specific functions, such as find offset, get id, get attribute, extend etc. + * the full list is: + * + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getDropScope gets the drop scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getOriginalEvent gets the original browser event from some wrapper event + * getPageXY gets the page event's xy location. + * getParent gets the parent of some element. + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * hasClass returns whether or not the given element has the given class. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + * trigger triggers some event on an element. + * unbind unbinds some listener from some element. + */ +(function($) { + + //var getBoundingClientRectSupported = "getBoundingClientRect" in document.documentElement; + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el[0].className.constructor == SVGAnimatedString) { + jsPlumbUtil.svg.addClass(el[0], clazz); + } + } + catch (e) { + // SVGAnimatedString not supported; no problem. + } + el.addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * appends the given child to the given parent. + */ + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).append(child); + }, + + /** + * executes an ajax call. + */ + ajax : function(params) { + params = params || {}; + params.type = params.type || "get"; + $.ajax(params); + }, + + /** + * event binding wrapper. it just so happens that jQuery uses 'bind' also. yui3, for example, + * uses 'on'. + */ + bind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.bind(event, callback); + }, + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step', + 'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete' + }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + }, + + getDragScope : function(el) { + return el.draggable("option", "scope"); + }, + + getDropEvent : function(args) { + return args[0]; + }, + + getDropScope : function(el) { + return el.droppable("option", "scope"); + }, + + /** + * gets a DOM element from the given input, which might be a string (in which case we just do document.getElementById), + * a selector (in which case we return el[0]), or a DOM element already (we assume this if it's not either of the other + * two cases). this is the opposite of getElementObject below. + */ + getDOMElement : function(el) { + if (typeof(el) == "string") return document.getElementById(el); + else if (el.context || el.length != null) return el[0]; + else return el; + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + * + */ + getElementObject : function(el) { + return typeof(el) == "string" ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + getOriginalEvent : function(e) { + return e.originalEvent; + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).parent(); + }, + + getScrollLeft : function(el) { + return el.scrollLeft(); + }, + + getScrollTop : function(el) { + return el.scrollTop(); + }, + + getSelector : function(spec) { + return $(spec); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e.length > 0 ? e[0].tagName : null; + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs, zoom) { + + zoom = zoom || 1; + // this code is a workaround for the case that the element being dragged has a margin set on it. jquery UI passes + // in the wrong offset if the element has a margin (it doesn't take the margin into account). the getBoundingClientRect + // method, which is in pretty much all browsers now, reports the right numbers. but it introduces a noticeable lag, which + // i don't like. + + /*if ( getBoundingClientRectSupported ) { + var r = eventArgs[1].helper[0].getBoundingClientRect(); + return { left : r.left, top: r.top }; + } else {*/ + if (eventArgs.length == 1) { + ret = { left: eventArgs[0].pageX, top:eventArgs[0].pageY }; + } + else { + var ui = eventArgs[1], + _offset = ui.offset; + + ret = _offset || ui.absolutePosition; + + // adjust ui position to account for zoom, because jquery ui does not do this. + ui.position.left /= zoom; + ui.position.top /= zoom; + } + return { left:ret.left / zoom, top: ret.top / zoom }; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options, isPlumbedComponent) { + options = options || {}; + // remove helper directive if present. + options.helper = null; + if (isPlumbedComponent) + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.draggable(options); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.droppable(options); + }, + + isAlreadyDraggable : function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return el.hasClass("ui-draggable"); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el[0].className.constructor == SVGAnimatedString) { + jsPlumbUtil.svg.removeClass(el[0], clazz); + } + } + catch (e) { + // SVGAnimatedString not supported; no problem. + } + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).remove(); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * sets the drag scope. probably time for a setDragOption method (roll this and the one above together) + * @param el + * @param scope + */ + setDragScope : function(el, scope) { + el.draggable("option", "scope", scope); + }, + + setOffset : function(el, o) { + jsPlumb.CurrentLibrary.getElementObject(el).offset(o); + }, + + /** + * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself. + * the other libraries do not. yui, in fact, cannot even pass an original event. we have to pull out stuff + * from the originalEvent to put in an options object for YUI. + * @param el + * @param event + * @param originalEvent + */ + trigger : function(el, event, originalEvent) { + //originalEvent.stopPropagation(); + //jsPlumb.CurrentLibrary.getElementObject(el).trigger(originalEvent); + var h = jQuery._data(jsPlumb.CurrentLibrary.getElementObject(el)[0], "handle"); + h(originalEvent); + //originalEvent.stopPropagation(); + }, + + /** + * event unbinding wrapper. it just so happens that jQuery uses 'unbind' also. yui3, for example, + * uses..something else. + */ + unbind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.unbind(event, callback); + } + }; + + $(document).ready(jsPlumb.init); + +})(jQuery); + diff --git a/build/1.3.15/js/jquery.jsPlumb-1.3.15-all-min.js b/archive/1.3.15/js/jquery.jsPlumb-1.3.15-all-min.js similarity index 100% rename from build/1.3.15/js/jquery.jsPlumb-1.3.15-all-min.js rename to archive/1.3.15/js/jquery.jsPlumb-1.3.15-all-min.js diff --git a/build/1.3.15/js/jquery.jsPlumb-1.3.15-all.js b/archive/1.3.15/js/jquery.jsPlumb-1.3.15-all.js similarity index 100% rename from build/1.3.15/js/jquery.jsPlumb-1.3.15-all.js rename to archive/1.3.15/js/jquery.jsPlumb-1.3.15-all.js diff --git a/build/1.3.15/js/jsPlumb-1.3.15-tests.js b/archive/1.3.15/js/jsPlumb-1.3.15-tests.js similarity index 100% rename from build/1.3.15/js/jsPlumb-1.3.15-tests.js rename to archive/1.3.15/js/jsPlumb-1.3.15-tests.js diff --git a/build/1.3.15/js/lib/qunit.js b/archive/1.3.15/js/lib/qunit.js similarity index 100% rename from build/1.3.15/js/lib/qunit.js rename to archive/1.3.15/js/lib/qunit.js diff --git a/build/1.3.15/js/mootools.jsPlumb-1.3.15-all-min.js b/archive/1.3.15/js/mootools.jsPlumb-1.3.15-all-min.js similarity index 100% rename from build/1.3.15/js/mootools.jsPlumb-1.3.15-all-min.js rename to archive/1.3.15/js/mootools.jsPlumb-1.3.15-all-min.js diff --git a/build/1.3.15/js/mootools.jsPlumb-1.3.15-all.js b/archive/1.3.15/js/mootools.jsPlumb-1.3.15-all.js similarity index 100% rename from build/1.3.15/js/mootools.jsPlumb-1.3.15-all.js rename to archive/1.3.15/js/mootools.jsPlumb-1.3.15-all.js diff --git a/build/1.3.15/js/yui.jsPlumb-1.3.15-all-min.js b/archive/1.3.15/js/yui.jsPlumb-1.3.15-all-min.js similarity index 100% rename from build/1.3.15/js/yui.jsPlumb-1.3.15-all-min.js rename to archive/1.3.15/js/yui.jsPlumb-1.3.15-all-min.js diff --git a/build/1.3.15/js/yui.jsPlumb-1.3.15-all.js b/archive/1.3.15/js/yui.jsPlumb-1.3.15-all.js similarity index 100% rename from build/1.3.15/js/yui.jsPlumb-1.3.15-all.js rename to archive/1.3.15/js/yui.jsPlumb-1.3.15-all.js diff --git a/archive/1.3.15/jsPlumb-1.3.15-RC1.js b/archive/1.3.15/jsPlumb-1.3.15-RC1.js new file mode 100644 index 000000000..83e6a7f41 --- /dev/null +++ b/archive/1.3.15/jsPlumb-1.3.15-RC1.js @@ -0,0 +1,6355 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.15 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the jsPlumb core code. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + /** + * Class:jsPlumb + * The jsPlumb engine, registered as a static object in the window. This object contains all of the methods you will use to + * create and maintain Connections and Endpoints. + */ + + var _findWithFunction = jsPlumbUtil.findWithFunction, + _indexOf = jsPlumbUtil.indexOf, + _removeWithFunction = jsPlumbUtil.removeWithFunction, + _remove = jsPlumbUtil.remove, + // TODO support insert index + _addWithFunction = jsPlumbUtil.addWithFunction, + _addToList = jsPlumbUtil.addToList, + /** + an isArray function that even works across iframes...see here: + + http://tobyho.com/2011/01/28/checking-types-in-javascript/ + + i was originally using "a.constructor == Array" as a test. + */ + _isArray = jsPlumbUtil.isArray, + _isString = jsPlumbUtil.isString, + _isObject = jsPlumbUtil.isObject; + + var _connectionBeingDragged = null, + _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }, + _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }, + _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el, _instance) { + var o = jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); + if (_instance != null) { + var z = _instance.getZoom(); + return {left:o.left / z, top:o.top / z }; + } + else + return o; + }, + _getSize = function(el) { + return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); + }, + _log = jsPlumbUtil.log, + _group = jsPlumbUtil.group, + _groupEnd = jsPlumbUtil.groupEnd, + _time = jsPlumbUtil.time, + _timeEnd = jsPlumbUtil.timeEnd, + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + _timestamp = function() { return "" + (new Date()).getTime(); }, + + /* + * Class:jsPlumbUIComponent + * Abstract superclass for UI components Endpoint and Connection. Provides the abstraction of paintStyle/hoverPaintStyle, + * and also extends jsPlumbUtil.EventGenerator to provide the bind and fire methods. + */ + jsPlumbUIComponent = function(params) { + var self = this, + a = arguments, + _hover = false, + parameters = params.parameters || {}, + idPrefix = self.idPrefix, + id = idPrefix + (new Date()).getTime(), + paintStyle = null, + hoverPaintStyle = null; + + self._jsPlumb = params["_jsPlumb"]; + self.getId = function() { return id; }; + self.tooltip = params.tooltip; + self.hoverClass = params.hoverClass || self._jsPlumb.Defaults.HoverClass || jsPlumb.Defaults.HoverClass; + + // all components can generate events + jsPlumbUtil.EventGenerator.apply(this); + // all components get this clone function. + // TODO issue 116 showed a problem with this - it seems 'a' that is in + // the clone function's scope is shared by all invocations of it, the classic + // JS closure problem. for now, jsPlumb does a version of this inline where + // it used to call clone. but it would be nice to find some time to look + // further at this. + this.clone = function() { + var o = new Object(); + self.constructor.apply(o, a); + return o; + }; + + this.getParameter = function(name) { return parameters[name]; }, + this.getParameters = function() { + return parameters; + }, + this.setParameter = function(name, value) { parameters[name] = value; }, + this.setParameters = function(p) { parameters = p; }, + this.overlayPlacements = []; + + // user can supply a beforeDetach callback, which will be executed before a detach + // is performed; returning false prevents the detach. + var beforeDetach = params.beforeDetach; + this.isDetachAllowed = function(connection) { + var r = self._jsPlumb.checkCondition("beforeDetach", connection ); + if (beforeDetach) { + try { + r = beforeDetach(connection); + } + catch (e) { _log("jsPlumb: beforeDetach callback failed", e); } + } + return r; + }; + + // user can supply a beforeDrop callback, which will be executed before a dropped + // connection is confirmed. user can return false to reject connection. + var beforeDrop = params.beforeDrop; + this.isDropAllowed = function(sourceId, targetId, scope, connection, dropEndpoint) { + var r = self._jsPlumb.checkCondition("beforeDrop", { + sourceId:sourceId, + targetId:targetId, + scope:scope, + connection:connection, + dropEndpoint:dropEndpoint + }); + if (beforeDrop) { + try { + r = beforeDrop({ + sourceId:sourceId, + targetId:targetId, + scope:scope, + connection:connection, + dropEndpoint:dropEndpoint + }); + } + catch (e) { _log("jsPlumb: beforeDrop callback failed", e); } + } + return r; + }; + + // helper method to update the hover style whenever it, or paintStyle, changes. + // we use paintStyle as the foundation and merge hoverPaintStyle over the + // top. + var _updateHoverStyle = function() { + if (paintStyle && hoverPaintStyle) { + var mergedHoverStyle = {}; + jsPlumb.extend(mergedHoverStyle, paintStyle); + jsPlumb.extend(mergedHoverStyle, hoverPaintStyle); + delete self["hoverPaintStyle"]; + // we want the fillStyle of paintStyle to override a gradient, if possible. + if (mergedHoverStyle.gradient && paintStyle.fillStyle) + delete mergedHoverStyle["gradient"]; + hoverPaintStyle = mergedHoverStyle; + } + }; + + /* + * Sets the paint style and then repaints the element. + * + * Parameters: + * style - Style to use. + */ + this.setPaintStyle = function(style, doNotRepaint) { + paintStyle = style; + self.paintStyleInUse = paintStyle; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /** + * Gets the component's paint style. + * + * Returns: + * the component's paint style. if there is no hoverPaintStyle set then this will be the paint style used all the time, otherwise this is the style used when the mouse is not hovering. + */ + this.getPaintStyle = function() { + return paintStyle; + }; + + /* + * Sets the paint style to use when the mouse is hovering over the element. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the component will not be repainted. useful when setting things up initially. + */ + this.setHoverPaintStyle = function(style, doNotRepaint) { + hoverPaintStyle = style; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /** + * Gets the component's hover paint style. + * + * Returns: + * the component's hover paint style. may be null. + */ + this.getHoverPaintStyle = function() { + return hoverPaintStyle; + }; + + /* + * sets/unsets the hover state of this element. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + this.setHover = function(hover, ignoreAttachedElements, timestamp) { + // while dragging, we ignore these events. this keeps the UI from flashing and + // swishing and whatevering. + if (!self._jsPlumb.currentlyDragging && !self._jsPlumb.isHoverSuspended()) { + + _hover = hover; + if (self.hoverClass != null && self.canvas != null) { + if (hover) + jpcl.addClass(self.canvas, self.hoverClass); + else + jpcl.removeClass(self.canvas, self.hoverClass); + } + if (hoverPaintStyle != null) { + self.paintStyleInUse = hover ? hoverPaintStyle : paintStyle; + timestamp = timestamp || _timestamp(); + self.repaint({timestamp:timestamp, recalc:false}); + } + // get the list of other affected elements, if supported by this component. + // for a connection, its the endpoints. for an endpoint, its the connections! surprise. + if (self.getAttachedElements && !ignoreAttachedElements) + _updateAttachedElements(hover, _timestamp(), self); + } + }; + + this.isHover = function() { return _hover; }; + + var zIndex = null; + this.setZIndex = function(v) { zIndex = v; }; + this.getZIndex = function() { return zIndex; }; + + var jpcl = jsPlumb.CurrentLibrary, + events = [ "click", "dblclick", "mouseenter", "mouseout", "mousemove", "mousedown", "mouseup", "contextmenu" ], + eventFilters = { "mouseout":"mouseexit" }, + bindOne = function(o, c, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.bind(o, evt, function(ee) { + c.fire(filteredEvent, c, ee); + }); + }, + unbindOne = function(o, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.unbind(o, evt); + }; + + this.attachListeners = function(o, c) { + for (var i = 0; i < events.length; i++) { + bindOne(o, c, events[i]); + } + }; + + var _updateAttachedElements = function(state, timestamp, sourceElement) { + var affectedElements = self.getAttachedElements(); // implemented in subclasses + if (affectedElements) { + for (var i = 0; i < affectedElements.length; i++) { + if (!sourceElement || sourceElement != affectedElements[i]) + affectedElements[i].setHover(state, true, timestamp); // tell the attached elements not to inform their own attached elements. + } + } + }; + + this.reattachListenersForElement = function(o) { + if (arguments.length > 1) { + for (var i = 0; i < events.length; i++) + unbindOne(o, events[i]); + for (var i = 1; i < arguments.length; i++) + self.attachListeners(o, arguments[i]); + } + }; + + /* + * TYPES + */ + var _types = [], + _splitType = function(t) { return t == null ? null : t.split(" ")}, + _applyTypes = function(doNotRepaint) { + if (self.getDefaultType) { + var td = self.getTypeDescriptor(); + + var o = jsPlumbUtil.merge({}, self.getDefaultType()); + for (var i = 0; i < _types.length; i++) + o = jsPlumbUtil.merge(o, self._jsPlumb.getType(_types[i], td)); + + self.applyType(o); + if (!doNotRepaint) self.repaint(); + } + }; + + self.setType = function(typeId, doNotRepaint) { + _types = _splitType(typeId) || []; + _applyTypes(doNotRepaint); + }; + + /* + * Function : getType + * Gets the 'types' of this component. + */ + self.getType = function() { + return _types; + }; + + self.hasType = function(typeId) { + return jsPlumbUtil.indexOf(_types, typeId) != -1; + }; + + self.addType = function(typeId, doNotRepaint) { + var t = _splitType(typeId), _cont = false; + if (t != null) { + for (var i = 0; i < t.length; i++) { + if (!self.hasType(t[i])) { + _types.push(t[i]); + _cont = true; + } + } + if (_cont) _applyTypes(doNotRepaint); + } + }; + + self.removeType = function(typeId, doNotRepaint) { + var t = _splitType(typeId), _cont = false, _one = function(tt) { + var idx = jsPlumbUtil.indexOf(_types, tt); + if (idx != -1) { + _types.splice(idx, 1); + return true; + } + return false; + }; + + if (t != null) { + for (var i = 0; i < t.length; i++) { + _cont = _one(t[i]) || _cont; + } + if (_cont) _applyTypes(doNotRepaint); + } + }; + + self.toggleType = function(typeId, doNotRepaint) { + var t = _splitType(typeId); + if (t != null) { + for (var i = 0; i < t.length; i++) { + var idx = jsPlumbUtil.indexOf(_types, t[i]); + if (idx != -1) + _types.splice(idx, 1); + else + _types.push(t[i]); + } + + _applyTypes(doNotRepaint); + } + }; + + this.applyType = function(t) { + self.setPaintStyle(t.paintStyle); + self.setHoverPaintStyle(t.hoverPaintStyle); + if (t.parameters){ + for (var i in t.parameters) + self.setParameter(i, t.parameters[i]); + } + }; + + }, + + overlayCapableJsPlumbUIComponent = function(params) { + jsPlumbUIComponent.apply(this, arguments); + var self = this; + this.overlays = []; + + var processOverlay = function(o) { + var _newOverlay = null; + if (_isArray(o)) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0], + // make a copy of the object so as not to mess up anyone else's reference... + p = jsPlumb.extend({component:self, _jsPlumb:self._jsPlumb}, o[1]); + if (o.length == 3) jsPlumb.extend(p, o[2]); + _newOverlay = new jsPlumb.Overlays[self._jsPlumb.getRenderMode()][type](p); + if (p.events) { + for (var evt in p.events) { + _newOverlay.bind(evt, p.events[evt]); + } + } + } else if (o.constructor == String) { + _newOverlay = new jsPlumb.Overlays[self._jsPlumb.getRenderMode()][o]({component:self, _jsPlumb:self._jsPlumb}); + } else { + _newOverlay = o; + } + + self.overlays.push(_newOverlay); + }, + calculateOverlaysToAdd = function(params) { + var defaultKeys = self.defaultOverlayKeys || [], + o = params.overlays, + checkKey = function(k) { + return self._jsPlumb.Defaults[k] || jsPlumb.Defaults[k] || []; + }; + + if (!o) o = []; + + for (var i = 0; i < defaultKeys.length; i++) + o.unshift.apply(o, checkKey(defaultKeys[i])); + + return o; + } + + var _overlays = calculateOverlaysToAdd(params);//params.overlays || self._jsPlumb.Defaults.Overlays; + if (_overlays) { + for (var i = 0; i < _overlays.length; i++) { + processOverlay(_overlays[i]); + } + } + + // overlay finder helper method + var _getOverlayIndex = function(id) { + var idx = -1; + for (var i = 0; i < self.overlays.length; i++) { + if (id === self.overlays[i].id) { + idx = i; + break; + } + } + return idx; + }; + + this.addOverlay = function(overlay, doNotRepaint) { + processOverlay(overlay); + if (!doNotRepaint) self.repaint(); + }; + + this.getOverlay = function(id) { + var idx = _getOverlayIndex(id); + return idx >= 0 ? self.overlays[idx] : null; + }; + + this.getOverlays = function() { + return self.overlays; + }; + + this.hideOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.hide(); + }; + + this.hideOverlays = function() { + for (var i = 0; i < self.overlays.length; i++) + self.overlays[i].hide(); + }; + + this.showOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.show(); + }; + + this.showOverlays = function() { + for (var i = 0; i < self.overlays.length; i++) + self.overlays[i].show(); + }; + + this.removeAllOverlays = function() { + for (var i = 0; i < self.overlays.length; i++) { + if (self.overlays[i].cleanup) self.overlays[i].cleanup(); + } + + self.overlays.splice(0, self.overlays.length); + self.repaint(); + }; + + this.removeOverlay = function(overlayId) { + var idx = _getOverlayIndex(overlayId); + if (idx != -1) { + var o = self.overlays[idx]; + if (o.cleanup) o.cleanup(); + self.overlays.splice(idx, 1); + } + }; + + this.removeOverlays = function() { + for (var i = 0; i < arguments.length; i++) + self.removeOverlay(arguments[i]); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + var _internalLabelOverlayId = "__label", + _makeLabelOverlay = function(params) { + + var _params = { + cssClass:params.cssClass, + labelStyle : this.labelStyle, + id:_internalLabelOverlayId, + component:self, + _jsPlumb:self._jsPlumb + }, + mergedParams = jsPlumb.extend(_params, params); + + return new jsPlumb.Overlays[self._jsPlumb.getRenderMode()].Label( mergedParams ); + }; + if (params.label) { + var loc = params.labelLocation || self.defaultLabelLocation || 0.5, + labelStyle = params.labelStyle || self._jsPlumb.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.overlays.push(_makeLabelOverlay({ + label:params.label, + location:loc, + labelStyle:labelStyle + })); + } + + + this.setLabel = function(l) { + var lo = self.getOverlay(_internalLabelOverlayId); + if (!lo) { + var params = l.constructor == String || l.constructor == Function ? { label:l } : l; + lo = _makeLabelOverlay(params); + this.overlays.push(lo); + } + else { + if (l.constructor == String || l.constructor == Function) lo.setLabel(l); + else { + if (l.label) lo.setLabel(l.label); + if (l.location) lo.setLocation(l.location); + } + } + + if (!self._jsPlumb.isSuspendDrawing()) + self.repaint(); + }; + + + this.getLabel = function() { + var lo = self.getOverlay(_internalLabelOverlayId); + return lo != null ? lo.getLabel() : null; + }; + + + this.getLabelOverlay = function() { + return self.getOverlay(_internalLabelOverlayId); + }; + + var superAt = this.applyType; + this.applyType = function(t) { + superAt(t); + self.removeAllOverlays(); + if (t.overlays) { + for (var i = 0; i < t.overlays.length; i++) + self.addOverlay(t.overlays[i], true); + } + }; + }, + + _bindListeners = function(obj, _self, _hoverFunction) { + obj.bind("click", function(ep, e) { _self.fire("click", _self, e); }); + obj.bind("dblclick", function(ep, e) { _self.fire("dblclick", _self, e); }); + obj.bind("contextmenu", function(ep, e) { _self.fire("contextmenu", _self, e); }); + obj.bind("mouseenter", function(ep, e) { + if (!_self.isHover()) { + _hoverFunction(true); + _self.fire("mouseenter", _self, e); + } + }); + obj.bind("mouseexit", function(ep, e) { + if (_self.isHover()) { + _hoverFunction(false); + _self.fire("mouseexit", _self, e); + } + }); + }; + + var _jsPlumbInstanceIndex = 0, + getInstanceIndex = function() { + var i = _jsPlumbInstanceIndex + 1; + _jsPlumbInstanceIndex++; + return i; + }; + + var jsPlumbInstance = function(_defaults) { + + /* + * Property: Defaults + * + * These are the default settings for jsPlumb. They are what will be used if you do not supply specific pieces of information + * to the various API calls. A convenient way to implement your own look and feel can be to override these defaults + * by including a script somewhere after the jsPlumb include, but before you make any calls to jsPlumb. + * + * Properties: + * - *Anchor* The default anchor to use for all connections (both source and target). Default is "BottomCenter". + * - *Anchors* The default anchors to use ([source, target]) for all connections. Defaults are ["BottomCenter", "BottomCenter"]. + * - *ConnectionsDetachable* Whether or not connections are detachable by default (using the mouse). Defaults to true. + * - *ConnectionOverlays* The default overlay definitions for Connections. Defaults to an empty list. + * - *Connector* The default connector definition to use for all connections. Default is "Bezier". + * - *ConnectorZIndex* Optional value for the z-index of Connections that are not in the hover state. If you set this, jsPlumb will set the z-index of all created Connections to be this value, and the z-index of any Connections in the hover state to be this value plus one. This brings hovered connections up on top of others, which is a nice effect in busy UIs. + * - *Container* Optional selector or element id that instructs jsPlumb to append elements it creates to a specific element. + * - *DragOptions* The default drag options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *DropOptions* The default drop options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *Endpoint* The default endpoint definition to use for all connections (both source and target). Default is "Dot". + * - *EndpointOverlays* The default overlay definitions for Endpoints. Defaults to an empty list. + * - *Endpoints* The default endpoint definitions ([ source, target ]) to use for all connections. Defaults are ["Dot", "Dot"]. + * - *EndpointStyle* The default style definition to use for all endpoints. Default is fillStyle:"#456". + * - *EndpointStyles* The default style definitions ([ source, target ]) to use for all endpoints. Defaults are empty. + * - *EndpointHoverStyle* The default hover style definition to use for all endpoints. Default is null. + * - *EndpointHoverStyles* The default hover style definitions ([ source, target ]) to use for all endpoints. Defaults are null. + * - *HoverPaintStyle* The default hover style definition to use for all connections. Defaults are null. + * - *LabelStyle* The default style to use for label overlays on connections. + * - *LogEnabled* Whether or not the jsPlumb log is enabled. defaults to false. + * - *Overlays* The default overlay definitions (for both Connections and Endpoint). Defaults to an empty list. + * - *MaxConnections* The default maximum number of connections for an Endpoint. Defaults to 1. + * - *PaintStyle* The default paint style for a connection. Default is line width of 8 pixels, with color "#456". + * - *ReattachConnections* Whether or not to reattach Connections that a user has detached with the mouse and then dropped. Default is false. + * - *RenderMode* What mode to use to paint with. If you're on IE<9, you don't really get to choose this. You'll just get VML. Otherwise, the jsPlumb default is to use SVG. + * - *Scope* The default "scope" to use for connections. Scope lets you assign connections to different categories. + */ + this.Defaults = { + Anchor : "BottomCenter", + Anchors : [ null, null ], + ConnectionsDetachable : true, + ConnectionOverlays : [ ], + Connector : "Bezier", + ConnectorZIndex : null, + Container : null, + DragOptions : { }, + DropOptions : { }, + Endpoint : "Dot", + EndpointOverlays : [ ], + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : "#456" }, + EndpointStyles : [ null, null ], + EndpointHoverStyle : null, + EndpointHoverStyles : [ null, null ], + HoverPaintStyle : null, + LabelStyle : { color : "black" }, + LogEnabled : false, + Overlays : [ ], + MaxConnections : 1, + PaintStyle : { lineWidth : 8, strokeStyle : "#456" }, + ReattachConnections:false, + RenderMode : "svg", + Scope : "jsPlumb_DefaultScope" + }; + if (_defaults) jsPlumb.extend(this.Defaults, _defaults); + + this.logEnabled = this.Defaults.LogEnabled; + + var _connectionTypes = { }, _endpointTypes = {}; + this.registerConnectionType = function(id, type) { + _connectionTypes[id] = jsPlumb.extend({}, type); + }; + this.registerConnectionTypes = function(types) { + for (var i in types) + _connectionTypes[i] = jsPlumb.extend({}, types[i]); + }; + this.registerEndpointType = function(id, type) { + _endpointTypes[id] = jsPlumb.extend({}, type); + }; + this.registerEndpointTypes = function(types) { + for (var i in types) + _endpointTypes[i] = jsPlumb.extend({}, types[i]); + }; + this.getType = function(id, typeDescriptor) { + return typeDescriptor === "connection" ? _connectionTypes[id] : _endpointTypes[id]; + }; + + jsPlumbUtil.EventGenerator.apply(this); + var _currentInstance = this, + _instanceIndex = getInstanceIndex(), + _bb = _currentInstance.bind, + _initialDefaults = {}, + _zoom = 1; + + this.setZoom = function(z, repaintEverything) { + _zoom = z; + if (repaintEverything) _currentInstance.repaintEverything(); + }; + this.getZoom = function() { return _zoom; }; + + for (var i in this.Defaults) + _initialDefaults[i] = this.Defaults[i]; + + this.bind = function(event, fn) { + if ("ready" === event && initialized) fn(); + else _bb.apply(_currentInstance,[event, fn]); + }; + + _currentInstance.importDefaults = function(d) { + for (var i in d) { + _currentInstance.Defaults[i] = d[i]; + } + }; + + _currentInstance.restoreDefaults = function() { + _currentInstance.Defaults = jsPlumb.extend({}, _initialDefaults); + }; + + var log = null, + resizeTimer = null, + initialized = false, + connectionsByScope = {}, + /** + * map of element id -> endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + endpointsByElement = {}, + endpointsByUUID = {}, + offsets = {}, + offsetTimestamps = {}, + floatingConnections = {}, + draggableStates = {}, + canvasList = [], + sizes = [], + //listeners = {}, // a map: keys are event types, values are lists of listeners. + DEFAULT_SCOPE = this.Defaults.Scope, + renderMode = null, // will be set in init() + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }, + + /** + * appends an element to some other element, which is calculated as follows: + * + * 1. if _currentInstance.Defaults.Container exists, use that element. + * 2. if the 'parent' parameter exists, use that. + * 3. otherwise just use the root element (for DOM usage, the document body). + * + */ + _appendElement = function(el, parent) { + if (_currentInstance.Defaults.Container) + jsPlumb.CurrentLibrary.appendElement(el, _currentInstance.Defaults.Container); + else if (!parent) + //document.body.appendChild(el); + jsPlumbAdapter.appendToRoot(el); + else + jsPlumb.CurrentLibrary.appendElement(el, parent); + }, + + _curIdStamp = 1, + _idstamp = function() { return "" + _curIdStamp++; }, + + /** + * YUI, for some reason, put the result of a Y.all call into an object that contains + * a '_nodes' array, instead of handing back an array-like object like the other + * libraries do. + */ + _convertYUICollection = function(c) { + return c._nodes ? c._nodes : c; + }, + + /** + * Draws an endpoint and its connections. this is the main entry point into drawing connections as well + * as endpoints, since jsPlumb is endpoint-centric under the hood. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + _draw = function(element, ui, timestamp) { + + // TOD is it correct to filter by headless at this top level? how would a headless adapter ever repaint? + if (!jsPlumbAdapter.headless && !_suspendDrawing) { + var id = _getAttribute(element, "id"), + repaintEls = _currentInstance.dragManager.getElementsForDraggable(id); + + if (timestamp == null) timestamp = _timestamp(); + + _currentInstance.anchorManager.redraw(id, ui, timestamp); + + if (repaintEls) { + for (var i in repaintEls) { + _currentInstance.anchorManager.redraw(repaintEls[i].id, ui, timestamp, repaintEls[i].offset); + } + } + } + }, + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + _elementProxy = function(element, fn) { + var retVal = null; + if (_isArray(element)) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }, + + /** + * gets an Endpoint by uuid. + */ + _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }, + + /** + * inits a draggable if it's not already initialised. + */ + _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + // TODO move to DragManager? + if (!jsPlumbAdapter.headless) { + var draggable = isDraggable == null ? false : isDraggable, + jpcl = jsPlumb.CurrentLibrary; + if (draggable) { + if (jpcl.isDragSupported(element) && !jpcl.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jpcl.dragEvents["drag"], + stopEvent = jpcl.dragEvents["stop"], + startEvent = jpcl.dragEvents["start"]; + + options[startEvent] = _wrap(options[startEvent], function() { + _currentInstance.setHoverSuspended(true); + }); + + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jpcl.getUIPosition(arguments, _currentInstance.getZoom()); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jpcl.getUIPosition(arguments, _currentInstance.getZoom()); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + _currentInstance.setHoverSuspended(false); + }); + var elId = _getId(element); // need ID + draggableStates[elId] = true; + var draggable = draggableStates[elId]; + options.disabled = draggable == null ? false : !draggable; + jpcl.initDraggable(element, options, false); + _currentInstance.dragManager.register(element); + } + } + } + }, + + /* + * prepares a final params object that can be passed to _newConnection, taking into account defaults, events, etc. + */ + _prepareConnectionParams = function(params, referenceParams) { + var _p = jsPlumb.extend( { + sourceIsNew:true, + targetIsNew:true + }, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + // hotwire endpoints passed as source or target to sourceEndpoint/targetEndpoint, respectively. + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not full. + // source: + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + // target: + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + // at this point, if we have source or target Endpoints, they were not new and we should mark the + // flag to reflect that. this is for use later with the deleteEndpointsOnDetach flag. + if (_p.sourceEndpoint) _p.sourceIsNew = false; + if (_p.targetEndpoint) _p.targetIsNew = false; + + // if source endpoint mandates connection type and nothing specified in our params, use it. + if (!_p.type && _p.sourceEndpoint) + _p.type = _p.sourceEndpoint.connectionType; + + // copy in any connectorOverlays that were specified on the source endpoint. + // it doesnt copy target endpoint overlays. i'm not sure if we want it to or not. + if (_p.sourceEndpoint && _p.sourceEndpoint.connectorOverlays) { + _p.overlays = _p.overlays || []; + for (var i = 0; i < _p.sourceEndpoint.connectorOverlays.length; i++) { + _p.overlays.push(_p.sourceEndpoint.connectorOverlays[i]); + } + } + + // tooltip. params.tooltip takes precedence, then sourceEndpoint.connectorTooltip. + _p.tooltip = params.tooltip; + if (!_p.tooltip && _p.sourceEndpoint && _p.sourceEndpoint.connectorTooltip) + _p.tooltip = _p.sourceEndpoint.connectorTooltip; + + // if there's a target specified (which of course there should be), and there is no + // target endpoint specified, and 'newConnection' was not set to true, then we check to + // see if a prior call to makeTarget has provided us with the specs for the target endpoint, and + // we use those if so. additionally, if the makeTarget call was specified with 'uniqueEndpoint' set + // to true, then if that target endpoint has already been created, we re-use it. + if (_p.target && !_p.target.endpoint && !_p.targetEndpoint && !_p.newConnection) { + var tid = _getId(_p.target), + tep =_targetEndpointDefinitions[tid], + existingUniqueEndpoint = _targetEndpoints[tid]; + + if (tep) { + // if target not enabled, return. + if (!_targetsEnabled[tid]) return; + + // check for max connections?? + var newEndpoint = existingUniqueEndpoint != null ? existingUniqueEndpoint : _currentInstance.addEndpoint(_p.target, tep); + if (_targetEndpointsUnique[tid]) _targetEndpoints[tid] = newEndpoint; + _p.targetEndpoint = newEndpoint; + newEndpoint._makeTargetCreator = true; + _p.targetIsNew = true; + } + } + + // same thing, but for source. + if (_p.source && !_p.source.endpoint && !_p.sourceEndpoint && !_p.newConnection) { + var tid = _getId(_p.source), + tep = _sourceEndpointDefinitions[tid], + existingUniqueEndpoint = _sourceEndpoints[tid]; + + if (tep) { + // if source not enabled, return. + if (!_sourcesEnabled[tid]) return; + + var newEndpoint = existingUniqueEndpoint != null ? existingUniqueEndpoint : _currentInstance.addEndpoint(_p.source, tep); + if (_sourceEndpointsUnique[tid]) _sourceEndpoints[tid] = newEndpoint; + _p.sourceEndpoint = newEndpoint; + _p.sourceIsNew = true; + } + } + + return _p; + }, + + _newConnection = function(params) { + var connectionFunc = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + endpointFunc = _currentInstance.Defaults.EndpointType || Endpoint, + parent = jsPlumb.CurrentLibrary.getParent; + + if (params.container) + params["parent"] = params.container; + else { + if (params.sourceEndpoint) + params["parent"] = params.sourceEndpoint.parent; + else if (params.source.constructor == endpointFunc) + params["parent"] = params.source.parent; + else params["parent"] = parent(params.source); + } + + params["_jsPlumb"] = _currentInstance; + var con = new connectionFunc(params); + con.id = "con_" + _idstamp(); + _eventFireProxy("click", "click", con); + _eventFireProxy("dblclick", "dblclick", con); + _eventFireProxy("contextmenu", "contextmenu", con); + return con; + }, + + /** + * adds the connection to the backing model, fires an event if necessary and then redraws + */ + _finaliseConnection = function(jpc, params, originalEvent) { + params = params || {}; + // add to list of connections (by scope). + if (!jpc.suspendedEndpoint) + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + if (!params.doNotFireConnectionEvent && params.fireEvent !== false) { + + var eventArgs = { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }; + + _currentInstance.fire("jsPlumbConnection", eventArgs, originalEvent); + // this is from 1.3.11 onwards. "jsPlumbConnection" always felt so unnecessary, so + // I've added this alias in 1.3.11, with a view to removing "jsPlumbConnection" completely in a future version. be aware, of course, you should only register listeners for one or the other of these events. + _currentInstance.fire("connection", eventArgs, originalEvent); + } + + // always inform the anchor manager + // except that if jpc has a suspended endpoint it's not true to say the + // connection is new; it has just (possibly) moved. the question is whether + // to make that call here or in the anchor manager. i think perhaps here. + _currentInstance.anchorManager.newConnection(jpc); + // force a paint + _draw(jpc.source); + }, + + _eventFireProxy = function(event, proxyEvent, obj) { + obj.bind(event, function(originalObject, originalEvent) { + _currentInstance.fire(proxyEvent, obj, originalEvent); + }); + }, + + /** + * for the given endpoint params, returns an appropriate parent element for the UI elements that will be added. + * this function is used by _newEndpoint (directly below), and also in the makeSource function in jsPlumb. + * + * the logic is to first look for a "container" member of params, and pass that back if found. otherwise we + * handoff to the 'getParent' function in the current library. + */ + _getParentFromParams = function(params) { + if (params.container) + return params.container; + else { + var tag = jsPlumb.CurrentLibrary.getTagName(params.source), + p = jsPlumb.CurrentLibrary.getParent(params.source); + if (tag && tag.toLowerCase() === "td") + return jsPlumb.CurrentLibrary.getParent(p); + else return p; + } + }, + + /** + factory method to prepare a new endpoint. this should always be used instead of creating Endpoints + manually, since this method attaches event listeners and an id. + */ + _newEndpoint = function(params) { + var endpointFunc = _currentInstance.Defaults.EndpointType || Endpoint; + params.parent = _getParentFromParams(params); + params["_jsPlumb"] = _currentInstance; + var ep = new endpointFunc(params); + ep.id = "ep_" + _idstamp(); + _eventFireProxy("click", "endpointClick", ep); + _eventFireProxy("dblclick", "endpointDblClick", ep); + _eventFireProxy("contextmenu", "contextmenu", ep); + if (!jsPlumbAdapter.headless) + _currentInstance.dragManager.endpointAdded(params.source); + return ep; + }, + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + _operation = function(elId, func, endpointFunc) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + if (endpointFunc) endpointFunc(endpoints[i]); + } + } + }, + /** + * perform an operation on all elements. + */ + _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }, + + /** + * helper to remove an element from the DOM. + */ + _removeElement = function(element, parent) { + if (element != null && element.parentNode != null) { + element.parentNode.removeChild(element); + } + }, + /** + * helper to remove a list of elements from the DOM. + */ + _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }, + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }, + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + _setVisible = function(el, state, alsoChangeEndpoints) { + state = state === "block"; + var endpointFunc = null; + if (alsoChangeEndpoints) { + if (state) endpointFunc = function(ep) { + ep.setVisible(true, true, true); + }; + else endpointFunc = function(ep) { + ep.setVisible(false, true, true); + }; + } + var id = _getAttribute(el, "id"); + _operation(id, function(jpc) { + if (state && alsoChangeEndpoints) { + // this test is necessary because this functionality is new, and i wanted to maintain backwards compatibility. + // this block will only set a connection to be visible if the other endpoint in the connection is also visible. + var oidx = jpc.sourceId === id ? 1 : 0; + if (jpc.endpoints[oidx].isVisible()) jpc.setVisible(true); + } + else // the default behaviour for show, and what always happens for hide, is to just set the visibility without getting clever. + jpc.setVisible(state); + }, endpointFunc); + }, + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? false : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }, + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + _toggleVisible = function(elId, changeEndpoints) { + var endpointFunc = null; + if (changeEndpoints) { + endpointFunc = function(ep) { + var state = ep.isVisible(); + ep.setVisible(!state); + }; + } + _operation(elId, function(jpc) { + var state = jpc.isVisible(); + jpc.setVisible(!state); + }, endpointFunc); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }, + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (_suspendDrawing && !timestamp) timestamp = _suspendedAt; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return offsets[elId]; + } + if (recalc || !offset) { // if forced repaint or no offset available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s, _currentInstance); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + if (sizes[elId] == null) { + var s = _getElementObject(elId); + if (s != null) sizes[elId] = _getSize(s); + } + } + + if(offsets[elId] && !offsets[elId].right) { + offsets[elId].right = offsets[elId].left + sizes[elId][0]; + offsets[elId].bottom = offsets[elId].top + sizes[elId][1]; + offsets[elId].width = sizes[elId][0]; + offsets[elId].height = sizes[elId][1]; + offsets[elId].centerx = offsets[elId].left + (offsets[elId].width / 2); + offsets[elId].centery = offsets[elId].top + (offsets[elId].height / 2); + } + return offsets[elId]; + }, + + // TODO comparison performance + _getCachedData = function(elId) { + var o = offsets[elId]; + if (!o) o = _updateOffset({elId:elId}); + return {o:o, s:sizes[elId]}; + }, + + /** + * gets an id for the given element, creating and setting one if + * necessary. the id is of the form + * + * jsPlumb__ + * + * where "index in instance" is a monotonically increasing integer that starts at 0, + * for each instance. this method is used not only to assign ids to elements that do not + * have them but also to connections and endpoints. + */ + _getId = function(element, uuid, doNotCreateIfNotFound) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else if (arguments.length == 1 || (arguments.length == 3 && !arguments[2])) + id = "jsPlumb_" + _instanceIndex + "_" + _idstamp(); + + if (!doNotCreateIfNotFound) _setAttribute(ele, "id", id); + } + return id; + }, + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, "jsPlumb function failed : " + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, "wrapped function failed : " + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = "_jsPlumb_connector"; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = "_jsPlumb_endpoint"; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = "_jsPlumb_overlay"; + + this.Anchors = {}; + + this.Connectors = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Endpoints = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Overlays = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + +// ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***************************************** + + /* + * Function: importDefaults + * Imports all the given defaults into this instance of jsPlumb. + */ + + /* + * Function: restoreDefaults + * Restores the default settings to "factory" values. + */ + + /* + * Function: bind + * Bind to an event on jsPlumb. + * + * Parameters: + * event - the event to bind. Available events on jsPlumb are: + * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback. + * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback. + * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback. + * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback. + * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback. + * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback. + * - *beforeDrop* : notification that a Connection is about to be dropped. Returning false from this method cancels the drop. jsPlumb passes { sourceId, targetId, scope, connection, dropEndpoint } to your callback. For more information, refer to the jsPlumb documentation. + * - *beforeDetach* : notification that a Connection is about to be detached. Returning false from this method cancels the detach. jsPlumb passes the Connection to your callback. For more information, refer to the jsPlumb documentation. + * - *connectionDrag* : notification that an existing Connection is being dragged. jsPlumb passes the Connection to your callback function. + * - *connectionDragEnd* : notification that the drag of an existing Connection has ended. jsPlumb passes the Connection to your callback function. + * + * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event. + */ + + /* + * Function: unbind + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + + /* + * Function: addClass + * Add class(es) to some element(s). + * + * Parameters: + * el - element id, dom element, or selector representing the element(s) to add class(es) to. + * clazz - string representing the class(es) to add. may contain multiple classes separated by spaces. + */ + + /* + * Function: removeClass + * Remove class from some selement(s). + * + * Parameters: + * el - element id, dom element, or selector representing the element(s) to remove a class from. + * clazz - string representing the class to remove. + */ + + /* + * Function: hasClass + * Checks if an element has some class. + * + * Parameters: + * el - element id, dom element, or selector representing the element to test. If the selector matches multiple elements, we return the test for the first element in the selector only. + * clazz - string representing the class to test for. + */ + +// *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *********************************************************** + + +// --------------------------- jsPLumbInstance public API --------------------------------------------------------- + + this.addClass = function(el, clazz) { return jsPlumb.CurrentLibrary.addClass(el, clazz); }; + this.removeClass = function(el, clazz) { return jsPlumb.CurrentLibrary.removeClass(el, clazz); }; + this.hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(el, clazz); }; + + /* + Function: addEndpoint + + Adds an to a given element or elements. + + Parameters: + + el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + params - Object containing Endpoint constructor arguments. For more information, see . + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some + shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in + this object are anything that 'params' can contain. See . + + Returns: + The newly created , if el referred to a single element. Otherwise, an array of newly created s. + + See Also: + + */ + this.addEndpoint = function(el, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // YUI wrapper + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + var _el = _getElementObject(inputs[i]), id = _getId(_el); + p.source = _el; + _updateOffset({ elId : id, timestamp:_suspendedAt }); + var e = _newEndpoint(p); + if (p.parentAnchor) e.parentAnchor = p.parentAnchor; + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e, timestamp:_suspendedAt }); + var endpointPaintParams = { anchorLoc : anchorLoc, timestamp:_suspendedAt }; + if (_suspendDrawing) endpointPaintParams.recalc = false; + e.paint(endpointPaintParams); + results.push(e); + //if (!jsPlumbAdapter.headless) + //_currentInstance.dragManager.endpointAdded(_el); + } + + return results.length == 1 ? results[0] : results; + }; + + /* + Function: addEndpoints + Adds a list of s to a given element or elements. + + Parameters: + target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See 's constructor documentation. + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created s, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(el, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams); + if (_isArray(e)) + Array.prototype.push.apply(results, e); + else results.push(e); + } + return results; + }; + + /* + Function: animate + This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating + the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // onComplete repaints, just to make sure everything looks good at the end of the animation. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /** + * checks for a listener for the given condition, executing it if found, passing in the given value. + * condition listeners would have been attached using "bind" (which is, you could argue, now overloaded, since + * firing click events etc is a bit different to what this does). i thought about adding a "bindCondition" + * or something, but decided against it, for the sake of simplicity. jsPlumb will never fire one of these + * condition events anyway. + */ + this.checkCondition = function(conditionName, value) { + var l = _currentInstance.getListener(conditionName); + var r = true; + if (l && l.length > 0) { + try { + for (var i = 0 ; i < l.length; i++) { + r = r && l[i](value); + } + } + catch (e) { + _log(_currentInstance, "cannot check condition [" + conditionName + "]" + e); + } + } + return r; + }; + + /* + Function: connect + Establishes a between two elements (or s, which are themselves registered to elements). + + Parameters: + params - Object containing constructor arguments for the Connection. See 's constructor documentation. + referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of + Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection. + + Returns: + The newly created . + */ + this.connect = function(params, referenceParams) { + // prepare a final set of parameters to create connection with + var _p = _prepareConnectionParams(params, referenceParams), jpc; + // TODO probably a nicer return value if the connection was not made. _prepareConnectionParams + // will return null (and log something) if either endpoint was full. what would be nicer is to + // create a dedicated 'error' object. + if (_p) { + // a connect call will delete its created endpoints on detach, unless otherwise specified. + // this is because the endpoints belong to this connection only, and are no use to + // anyone else, so they hang around like a bad smell. + if (_p.deleteEndpointsOnDetach == null) + _p.deleteEndpointsOnDetach = true; + + // create the connection. it is not yet registered + jpc = _newConnection(_p); + // now add it the model, fire an event, and redraw + _finaliseConnection(jpc, _p); + } + return jpc; + }; + + /* + Function: deleteEndpoint + Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too) + + Parameters: + object - either an object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + if (endpoint.endpoint.cleanup) endpoint.endpoint.cleanup(); + _removeElements(endpoint.endpoint.getDisplayElements()); + _currentInstance.anchorManager.deleteEndpoint(endpoint); + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + if (!jsPlumbAdapter.headless) + _currentInstance.dragManager.endpointDeleted(endpoint); + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every , and their associated s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + _currentInstance.setSuspendDrawing(true); + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + endpointsByElement = {}; + endpointsByUUID = {}; + + _currentInstance.setSuspendDrawing(false, true); + }; + + var fireDetachEvent = function(jpc, doFireEvent, originalEvent) { + // may have been given a connection, or in special cases, an object + var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + argIsConnection = jpc.constructor == connType, + params = argIsConnection ? { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + } : jpc; + + if (doFireEvent) { + _currentInstance.fire("jsPlumbConnectionDetached", params, originalEvent); + // introduced in 1.3.11..an alias because the original event name is unwieldy. in future versions this will be the only event and the other will no longer be fired. + _currentInstance.fire("connectionDetached", params, originalEvent); + } + _currentInstance.anchorManager.connectionDetached(params); + }, + /** + fires an event to indicate an existing connection is being dragged. + */ + fireConnectionDraggingEvent = function(jpc) { + _currentInstance.fire("connectionDrag", jpc); + }, + fireConnectionDragStopEvent = function(jpc) { + _currentInstance.fire("connectionDragStop", jpc); + }; + + /* + Function: detach + Detaches and then removes a . + + Parameters: + connection - the to detach + params - optional parameters to the detach call. valid values here are + fireEvent : defaults to false; indicates you want jsPlumb to fire a connection + detached event. The thinking behind this is that if you made a programmatic + call to detach an event, you probably don't need the callback. + forceDetach : defaults to false. allows you to override any beforeDetach listeners that may be registered. + + Returns: + true if successful, false if not. + */ + this.detach = function() { + + if (arguments.length == 0) return; + var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + firstArgIsConnection = arguments[0].constructor == connType, + params = arguments.length == 2 ? firstArgIsConnection ? (arguments[1] || {}) : arguments[0] : arguments[0], + fireEvent = (params.fireEvent !== false), + forceDetach = params.forceDetach, + connection = firstArgIsConnection ? arguments[0] : params.connection; + + if (connection) { + if (forceDetach || (connection.isDetachAllowed(connection) + && connection.endpoints[0].isDetachAllowed(connection) + && connection.endpoints[1].isDetachAllowed(connection))) { + if (forceDetach || _currentInstance.checkCondition("beforeDetach", connection)) + connection.endpoints[0].detach(connection, false, true, fireEvent); // TODO check this param iscorrect for endpoint's detach method + } + } + else { + var _p = jsPlumb.extend( {}, params); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1]), fireEvent); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source), + targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + if (_currentInstance.checkCondition("beforeDetach", jpc)) { + jpc.endpoints[0].detach(jpc, false, true, fireEvent); + } + } + }); + } + } + }; + + /* + Function: detachAllConnections + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + params - optional parameters. alowed values: + fireEvent : defaults to true, whether or not to fire the detach event. + + Returns: + void + */ + this.detachAllConnections = function(el, params) { + params = params || {}; + el = _getElementObject(el); + var id = _getAttribute(el, "id"), + endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + }; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Parameters: + params - optional params object containing: + fireEvent : whether or not to fire detach events. defaults to true. + + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function(params) { + params = params || {}; + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + } + connectionsByScope = {}; + }; + + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of your + library's draggable method so that jsPlumb can setup the appropriate callbacks. Your + underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * + * : jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * + * : jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + // helpers for select/selectEndpoints + var _setOperation = function(list, func, args, selector) { + for (var i = 0; i < list.length; i++) { + list[i][func].apply(list[i], args); + } + return selector(list); + }, + _getOperation = function(list, func, args) { + var out = []; + for (var i = 0; i < list.length; i++) { + out.push([ list[i][func].apply(list[i], args), list[i] ]); + } + return out; + }, + setter = function(list, func, selector) { + return function() { + return _setOperation(list, func, arguments, selector); + }; + }, + getter = function(list, func) { + return function() { + return _getOperation(list, func, arguments); + }; + }, + prepareList = function(input, doNotGetIds) { + var r = []; + if (input) { + if (typeof input == 'string') { + if (input === "*") return input; + r.push(input); + } + else { + if (doNotGetIds) r = input; + else { + for (var i = 0; i < input.length; i++) + r.push(_getId(_getElementObject(input[i]))); + } + } + } + return r; + }, + filterList = function(list, value, missingIsFalse) { + if (list === "*") return true; + return list.length > 0 ? _indexOf(list, value) != -1 : !missingIsFalse; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method, + * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the + * default scope). + * + * If multiple scopes are passed in, the return value will be a map of + * + * : { scope -> [ connection... ] } + * + * Parameters: + * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list + * of connections that are in the given scope. use '*' for all scopes. + * options - if the argument is a JS object, you can specify a finer-grained filter: + * + * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes. Also may have the value '*', indicating any scope. + * - *source* either a string representing an element id, a selector, or an array of ids. Also may have the value '*', indicating any source. Constrains the result to connections having this/these element(s) as source. + * - *target* either a string representing an element id, a selector, or an array of ids. Also may have the value '*', indicating any target. Constrains the result to connections having this/these element(s) as target. + * flat - return results in a flat array (don't return an object whose keys are scopes and whose values are lists per scope). + * + */ + this.getConnections = function(options, flat) { + if (!options) { + options = {}; + } else if (options.constructor == String) { + options = { "scope": options }; + } + var + scope = options.scope || _currentInstance.getDefaultScope(), + scopes = prepareList(scope, true), + sources = prepareList(options.source), + targets = prepareList(options.target), + results = (!flat && scopes.length > 1) ? {} : [], + _addOne = function(scope, obj) { + if (!flat && scopes.length > 1) { + var ss = results[scope]; + if (ss == null) { + ss = []; results[scope] = ss; + } + ss.push(obj); + } else results.push(obj); + }; + for ( var i in connectionsByScope) { + if (filterList(scopes, i)) { + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filterList(sources, c.sourceId) && filterList(targets, c.targetId)) + _addOne(i, c); + } + } + } + return results; + }; + + var _curryEach = function(list, executor) { + return function(f) { + for (var i = 0; i < list.length; i++) { + f(list[i]); + } + return executor(list); + }; + }, + _curryGet = function(list) { + return function(idx) { + return list[idx]; + }; + }; + + var _makeCommonSelectHandler = function(list, executor) { + return { + // setters + setHover:setter(list, "setHover", executor), + removeAllOverlays:setter(list, "removeAllOverlays", executor), + setLabel:setter(list, "setLabel", executor), + addOverlay:setter(list, "addOverlay", executor), + removeOverlay:setter(list, "removeOverlay", executor), + removeOverlays:setter(list, "removeOverlays", executor), + showOverlay:setter(list, "showOverlay", executor), + hideOverlay:setter(list, "hideOverlay", executor), + showOverlays:setter(list, "showOverlays", executor), + hideOverlays:setter(list, "hideOverlays", executor), + setPaintStyle:setter(list, "setPaintStyle", executor), + setHoverPaintStyle:setter(list, "setHoverPaintStyle", executor), + setParameter:setter(list, "setParameter", executor), + setParameters:setter(list, "setParameters", executor), + setVisible:setter(list, "setVisible", executor), + setZIndex:setter(list, "setZIndex", executor), + repaint:setter(list, "repaint", executor), + addType:setter(list, "addType", executor), + toggleType:setter(list, "toggleType", executor), + removeType:setter(list, "removeType", executor), + + // getters + getLabel:getter(list, "getLabel"), + getOverlay:getter(list, "getOverlay"), + isHover:getter(list, "isHover"), + getParameter:getter(list, "getParameter"), + getParameters:getter(list, "getParameters"), + getPaintStyle:getter(list, "getPaintStyle"), + getHoverPaintStyle:getter(list, "getHoverPaintStyle"), + isVisible:getter(list, "isVisible"), + getZIndex:getter(list, "getZIndex"), + hasType:getter(list, "hasType"), + getType:getter(list, "getType"), + + // util + length:list.length, + each:_curryEach(list, executor), + get:_curryGet(list) + }; + + }; + + var _makeConnectionSelectHandler = function(list) { + var common = _makeCommonSelectHandler(list, _makeConnectionSelectHandler); + return jsPlumb.CurrentLibrary.extend(common, { + // setters + setDetachable:setter(list, "setDetachable", _makeConnectionSelectHandler), + setReattach:setter(list, "setReattach", _makeConnectionSelectHandler), + setConnector:setter(list, "setConnector", _makeConnectionSelectHandler), + detach:function() { + for (var i = 0; i < list.length; i++) + _currentInstance.detach(list[i]); + }, + // getters + isDetachable:getter(list, "isDetachable"), + isReattach:getter(list, "isReattach") + }); + }; + + var _makeEndpointSelectHandler = function(list) { + var common = _makeCommonSelectHandler(list, _makeEndpointSelectHandler); + return jsPlumb.CurrentLibrary.extend(common, { + setEnabled:setter(list, "setEnabled", _makeEndpointSelectHandler), + isEnabled:getter(list, "isEnabled"), + detachAll:function() { + for (var i = 0; i < list.length; i++) + list[i].detachAll(); + }, + "delete":function() { + for (var i = 0; i < list.length; i++) + _currentInstance.deleteEndpoint(list[i]); + } + }); + }; + + /* + * Function: select + * Selects a set of Connections, using the filter options from the getConnections method, and returns an object + * that allows you to perform an operation on all of the Connections at once. + * + * The return value from any of these operations is the original list of Connections, allowing operations to be + * chained (for 'setter' type operations). 'getter' type operations return an array of values, where each entry is + * of the form: + * + * : [ Connection, return value ] + * + * Parameters: + * scope - see getConnections + * source - see getConnections + * target - see getConnections + * + * Returns: + * A list of Connections on which operations may be executed. 'Setter' type operations can be chained; 'getter' type operations + * return an array of [Connection, value] pairs, one entry for each Connection in the list returned. The full list of operations + * is as follows (where not specified, the operation's effect or return value is the same as the corresponding method on Connection) : + * + * - setHover + * - removeAllOverlays + * - setLabel + * - addOverlay + * - removeOverlay + * - removeOverlays + * - showOverlay + * - hideOverlay + * - showOverlays + * - hideOverlays + * - setPaintStyle + * - setHoverPaintStyle + * - setDetachable + * - setReattach + * - setConnector + * - setParameter + * - setParameters + * - getLabel + * - getOverlay + * - isHover + * - isDetachable + * - isReattach + * - getParameter + * - getParameters + * - getPaintStyle + * - getHoverPaintStyle + * - detach : detaches all the connections in the list. not chainable and does not return anything. + * - length : returns the length of the list. + * - get(index) : returns the Connection at 'index' in the list. + * - each(function(connection)...) : allows you to specify your own function to execute; this function is chainable. + */ + this.select = function(params) { + params = params || {}; + params.scope = params.scope || "*"; + var c = _currentInstance.getConnections(params, true); + return _makeConnectionSelectHandler(c); + }; + + /* + * Function: selectEndpoints + * Selects a set of Endpoints and returns an object that allows you to execute various different methods on them at once. The return + * value from any of these operations is the original list of Endpoints, allowing operations to be chained (for 'setter' type + * operations). 'getter' type operations return an array of values, where each entry is of the form: + * + * : [ Endpoint, return value ] + * + * Parameters: + * scope - either a string or an array of strings. + * source - either a string, a dom element, or a selector, or an array of any mix of these. limits returned endpoints to those that are declared as a source endpoint on any elements identified. + * target - either a string, a dom element, or a selector, or an array of any mix of these. limits returned endpoints to those that are declared as a target endpoint on any elements identified. + * element - either a string, a dom element, or a selector, or an array of any mix of these. limits returned endpoints to those that are declared as either a source OR a target endpoint on any elements identified. + * + * Returns: + * A list of Endpoints on which operations may be executed. 'Setter' type operations can be chained; 'getter' type operations + * return an array of [Endpoint, value] pairs, one entry for each Endpoint in the list returned. + * + * The full list of operations is as follows (where not specified, the operation's effect or return value is the + * same as the corresponding method on Endpoint) : + * + * - setHover + * - removeAllOverlays + * - setLabel + * - addOverlay + * - removeOverlay + * - removeOverlays + * - showOverlay + * - hideOverlay + * - showOverlays + * - hideOverlays + * - setPaintStyle + * - setHoverPaintStyle + * - setParameter + * - setParameters + * - getLabel + * - getOverlay + * - isHover + * - isDetachable + * - getParameter + * - getParameters + * - getPaintStyle + * - getHoverPaintStyle + * - detachAll : Detaches all the Connections from every Endpoint in the list. not chainable and does not return anything. + * - delete : Deletes every Endpoint in the list. not chainable and does not return anything. + * - length : returns the length of the list. + * - get(index) : returns the Endpoint at 'index' in the list. + * - each(function(endpoint)...) : allows you to specify your own function to execute; this function is chainable. + */ + this.selectEndpoints = function(params) { + params = params || {}; + params.scope = params.scope || "*"; + var noElementFilters = !params.element && !params.source && !params.target, + elements = noElementFilters ? "*" : prepareList(params.element), + sources = noElementFilters ? "*" : prepareList(params.source), + targets = noElementFilters ? "*" : prepareList(params.target), + scopes = prepareList(params.scope, true); + + var ep = []; + + for (var el in endpointsByElement) { + var either = filterList(elements, el, true), + source = filterList(sources, el, true), + sourceMatchExact = sources != "*", + target = filterList(targets, el, true), + targetMatchExact = targets != "*"; + + // if they requested 'either' then just match scope. otherwise if they requested 'source' (not as a wildcard) then we have to match only endpoints that have isSource set to to true, and the same thing with isTarget. + if ( either || source || target ) { + inner: + for (var i = 0; i < endpointsByElement[el].length; i++) { + var _ep = endpointsByElement[el][i]; + if (filterList(scopes, _ep.scope, true)) { + + var noMatchSource = (sourceMatchExact && sources.length > 0 && !_ep.isSource), + noMatchTarget = (targetMatchExact && targets.length > 0 && !_ep.isTarget); + + if (noMatchSource || noMatchTarget) + continue inner; + + ep.push(_ep); + } + } + } + } + + return _makeEndpointSelectHandler(ep); + }; + + /* + * Function: getAllConnections + * Gets all connections, as a map of the form: + * + * : { scope -> [ connection... ] } + */ + this.getAllConnections = function() { + return connectionsByScope; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. + * + * A scope defines a type of endpoint/connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /** + * Function: getEndpoints + * Gets the list of Endpoints for a given element. + * + * Parameters: + * el - element id, dom element, or selector. + * + * Returns: + * An array of Endpoints for the specified element. + */ + this.getEndpoints = function(el) { + return endpointsByElement[_getId(el)]; + }; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + this.getOffset = function(id) { + var o = offsets[id]; + return _updateOffset({elId:id}); + }; + + /* + * Function: getSelector + * This method takes the given selector spec and, using the current underlying library, turns it into + * a selector from that library. This method exists really as a helper function for those applications + * where you're writing jsPlumb code that will target more than one library (such as in the case of the + * jsPlumb demo pages). + * + * Parameters: + * spec - a valid selector string. + */ + this.getSelector = function(spec) { + return jsPlumb.CurrentLibrary.getSelector(spec); + }; + + // get the size of the element with the given id, perhaps from cache. + this.getSize = function(id) { + var s = sizes[id]; + if (!s) _updateOffset({elId:id}); + return sizes[id]; + }; + + this.appendElement = _appendElement; + + var _hoverSuspended = false; + this.isHoverSuspended = function() { return _hoverSuspended; }; + this.setHoverSuspended = function(s) { _hoverSuspended = s; }; + + var _isAvailable = function(m) { + return function() { + return jsPlumbAdapter.isRenderModeAvailable(m); + }; + } + this.isCanvasAvailable = _isAvailable("canvas"); + this.isSVGAvailable = _isAvailable("svg"); + this.isVMLAvailable = _isAvailable("vml"); + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + changeEndpoints - whether not to also hide endpoints on the element. by default this is false. + + Returns: + void + */ + this.hide = function(el, changeEndpoints) { + _setVisible(el, "none", changeEndpoints); + }; + + // exposed for other objects to use to get a unique id. + this.idstamp = _idstamp; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + */ + this.init = function() { + if (!initialized) { + _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run. + + var bindOne = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && renderMode == jsPlumb.CANVAS) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + var t = c[i].connector[event](e); + if (t) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i].endpoint[event](e)) return; + } + } + } + }); + }; + bindOne("click");bindOne("dblclick");bindOne("mousemove");bindOne("mousedown");bindOne("mouseup");bindOne("contextmenu"); + + initialized = true; + _currentInstance.fire("ready"); + } + }; + + this.log = log; + this.jsPlumbUIComponent = jsPlumbUIComponent; + + /* + * Creates an anchor with the given params. + * + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function() { + if (arguments.length == 0) return null; + var specimen = arguments[0], elementId = arguments[1], jsPlumbInstance = arguments[2], newAnchor = null; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; //TODO hazy here about whether it should be added or is already added somehow. + // is it the name of an anchor type? + else if (typeof specimen == "string") { + newAnchor = jsPlumb.Anchors[arguments[0]]({elementId:elementId, jsPlumbInstance:_currentInstance}); + } + // is it an array? it will be one of: + // an array of [name, params] - this defines a single anchor + // an array of arrays - this defines some dynamic anchors + // an array of numbers - this defines a single anchor. + else if (_isArray(specimen)) { + if (_isArray(specimen[0]) || _isString(specimen[0])) { + if (specimen.length == 2 && _isString(specimen[0]) && _isObject(specimen[1])) { + var pp = jsPlumb.extend({elementId:elementId, jsPlumbInstance:_currentInstance}, specimen[1]); + newAnchor = jsPlumb.Anchors[specimen[0]](pp); + } + else + newAnchor = new DynamicAnchor(specimen, null, elementId); + } + else { + var anchorParams = { + x:specimen[0], y:specimen[1], + orientation : (specimen.length >= 4) ? [ specimen[2], specimen[3] ] : [0,0], + offsets : (specimen.length == 6) ? [ specimen[4], specimen[5] ] : [ 0, 0 ], + elementId:elementId + }; + newAnchor = new Anchor(anchorParams); + newAnchor.clone = function() { return new Anchor(anchorParams); }; + } + } + + if (!newAnchor.id) newAnchor.id = "anchor_" + _idstamp(); + return newAnchor; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types, elementId, jsPlumbInstance) { + var r = []; + for ( var i = 0; i < types.length; i++) { + if (typeof types[i] == "string") + r.push(jsPlumb.Anchors[types[i]]({elementId:elementId, jsPlumbInstance:jsPlumbInstance})); + else if (_isArray(types[i])) + r.push(_currentInstance.makeAnchor(types[i], elementId, jsPlumbInstance)); + } + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /** + * Function: makeTarget + * Makes some DOM element a Connection target, allowing you to drag connections to it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a target. + * params - JS object containing parameters: + * - *endpoint* optional. specification of an Endpoint to create when a Connection is established. + * - *scope* optional. scope for the drop zone. + * - *dropOptions* optional. same stuff as you would pass to dropOptions of an Endpoint definition. + * - *deleteEndpointsOnDetach* optional, defaults to true. whether or not to delete + * any Endpoints created by a connection to this target if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * - *maxConnections* optional. Specifies the maximum number of Connections that can be made to this element as a target. Default is no limit. + * - *onMaxConnections* optional. Function to call when user attempts to drop a connection but the limit has been reached. + * The callback is passed two arguments: a JS object with: + * : { element, connection, maxConnection } + * ...and the original event. + */ + var _targetEndpointDefinitions = {}, + _targetEndpoints = {}, + _targetEndpointsUnique = {}, + _targetMaxConnections = {}, + _setEndpointPaintStylesAndAnchor = function(ep, epIndex) { + ep.paintStyle = ep.paintStyle || + _currentInstance.Defaults.EndpointStyles[epIndex] || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyles[epIndex] || + jsPlumb.Defaults.EndpointStyle; + ep.hoverPaintStyle = ep.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyles[epIndex] || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyles[epIndex] || + jsPlumb.Defaults.EndpointHoverStyle; + + ep.anchor = ep.anchor || + _currentInstance.Defaults.Anchors[epIndex] || + _currentInstance.Defaults.Anchor || + jsPlumb.Defaults.Anchors[epIndex] || + jsPlumb.Defaults.Anchor; + + ep.endpoint = ep.endpoint || + _currentInstance.Defaults.Endpoints[epIndex] || + _currentInstance.Defaults.Endpoint || + jsPlumb.Defaults.Endpoints[epIndex] || + jsPlumb.Defaults.Endpoint; + }; + + this.makeTarget = function(el, params, referenceParams) { + + var p = jsPlumb.extend({_jsPlumb:_currentInstance}, referenceParams); + jsPlumb.extend(p, params); + _setEndpointPaintStylesAndAnchor(p, 1); + var jpcl = jsPlumb.CurrentLibrary, + targetScope = p.scope || _currentInstance.Defaults.Scope, + deleteEndpointsOnDetach = !(p.deleteEndpointsOnDetach === false), + maxConnections = p.maxConnections || -1, + onMaxConnections = p.onMaxConnections; + _doOne = function(_el) { + + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el); + _targetEndpointDefinitions[elid] = p; + _targetEndpointsUnique[elid] = p.uniqueEndpoint, + _targetMaxConnections[elid] = maxConnections, + _targetsEnabled[elid] = true, + proxyComponent = new jsPlumbUIComponent(p); + + var dropOptions = jsPlumb.extend({}, p.dropOptions || {}), + _drop = function() { + + var originalEvent = jsPlumb.CurrentLibrary.getDropEvent(arguments), + targetCount = _currentInstance.select({target:elid}).length; + + _currentInstance.currentlyDragging = false; + var draggable = _getElementObject(jpcl.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + // restore the original scope if necessary (issue 57) + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id], + source = jpc.endpoints[0], + _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : {}; + + if (!_targetsEnabled[elid] || _targetMaxConnections[elid] > 0 && targetCount >= _targetMaxConnections[elid]){ + if (onMaxConnections) { + onMaxConnections({ + element:_el, + connection:jpc + }, originalEvent); + } + return false; + } + + // unlock the source anchor to allow it to refresh its position if necessary + source.anchor.locked = false; + + if (scope) jpcl.setDragScope(draggable, scope); + + // check if drop is allowed here. + //var _continue = jpc.isDropAllowed(jpc.sourceId, _getId(_el), jpc.scope); + var _continue = proxyComponent.isDropAllowed(jpc.sourceId, _getId(_el), jpc.scope, jpc, null); + + // regardless of whether the connection is ok, reconfigure the existing connection to + // point at the current info. we need this to be correct for the detach event that will follow. + // clear the source endpoint from the list to detach. we will detach this connection at this + // point, but we want to keep the source endpoint. the target is a floating endpoint and should + // be removed. TODO need to figure out whether this code can result in endpoints kicking around + // when they shouldnt be. like is this a full detach of a connection? can it be? + if (jpc.endpointsToDeleteOnDetach) { + if (source === jpc.endpointsToDeleteOnDetach[0]) + jpc.endpointsToDeleteOnDetach[0] = null; + else if (source === jpc.endpointsToDeleteOnDetach[1]) + jpc.endpointsToDeleteOnDetach[1] = null; + } + // reinstate any suspended endpoint; this just puts the connection back into + // a state in which it will report sensible values if someone asks it about + // its target. we're going to throw this connection away shortly so it doesnt matter + // if we manipulate it a bit. + if (jpc.suspendedEndpoint) { + jpc.targetId = jpc.suspendedEndpoint.elementId; + jpc.target = jpcl.getElementObject(jpc.suspendedEndpoint.elementId); + jpc.endpoints[1] = jpc.suspendedEndpoint; + } + + if (_continue) { + + // detach this connection from the source. + source.detach(jpc, false, true, false); + + // make a new Endpoint for the target + var newEndpoint = _targetEndpoints[elid] || _currentInstance.addEndpoint(_el, p); + if (p.uniqueEndpoint) _targetEndpoints[elid] = newEndpoint; // may of course just store what it just pulled out. that's ok. + newEndpoint._makeTargetCreator = true; + + // if the anchor has a 'positionFinder' set, then delegate to that function to find + // out where to locate the anchor. + if (newEndpoint.anchor.positionFinder != null) { + var dropPosition = jpcl.getUIPosition(arguments, _currentInstance.getZoom()), + elPosition = _getOffset(_el, _currentInstance), + elSize = _getSize(_el), + ap = newEndpoint.anchor.positionFinder(dropPosition, elPosition, elSize, newEndpoint.anchor.constructorParams); + newEndpoint.anchor.x = ap[0]; + newEndpoint.anchor.y = ap[1]; + // now figure an orientation for it..kind of hard to know what to do actually. probably the best thing i can do is to + // support specifying an orientation in the anchor's spec. if one is not supplied then i will make the orientation + // be what will cause the most natural link to the source: it will be pointing at the source, but it needs to be + // specified in one axis only, and so how to make that choice? i think i will use whichever axis is the one in which + // the target is furthest away from the source. + } + var c = _currentInstance.connect({ + source:source, + target:newEndpoint, + scope:scope, + previousConnection:jpc, + container:jpc.parent, + deleteEndpointsOnDetach:deleteEndpointsOnDetach, + // 'endpointWillMoveAfterConnection' is set by the makeSource function, and it indicates that the + // given endpoint will actually transfer from the element it is currently attached to to some other + // element after a connection has been established. in that case, we do not want to fire the + // connection event, since it will have the wrong data in it; makeSource will do it for us. + // this is controlled by the 'parent' parameter on a makeSource call. + doNotFireConnectionEvent:source.endpointWillMoveAfterConnection + }); + + // delete the original target endpoint. but only want to do this if the endpoint was created + // automatically and has no other connections. + if (jpc.endpoints[1]._makeTargetCreator && jpc.endpoints[1].connections.length < 2) + _currentInstance.deleteEndpoint(jpc.endpoints[1]); + + if (deleteEndpointsOnDetach) + c.endpointsToDeleteOnDetach = [ source, newEndpoint ]; + + c.repaint(); + } + // if not allowed to drop... + else { + // TODO this code is identical (pretty much) to what happens when a connection + // dragged from a normal endpoint is in this situation. refactor. + // is this an existing connection, and will we reattach? + if (jpc.suspendedEndpoint) { + //if (source.isReattach) { + if (jpc.isReattach()) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + _currentInstance.repaint(source.elementId); + } + else + source.detach(jpc, false, true, true, originalEvent); // otherwise, detach the connection and tell everyone about it. + } + + } + }; + + var dropEvent = jpcl.dragEvents['drop']; + dropOptions["scope"] = dropOptions["scope"] || targetScope; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop); + + jpcl.initDroppable(_el, dropOptions, true); + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + + return _currentInstance; + }; + + /* + * Function: unmakeTarget + * Sets the given element to no longer be a connection target. + * + * Parameters: + * el - a string id, a dom element, or a selector representing the element. + * + * Returns: + * The current jsPlumb instance. + */ + this.unmakeTarget = function(el, doNotClearArrays) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + var elid = _getId(el); + + // TODO this is not an exhaustive unmake of a target, since it does not remove the droppable stuff from + // the element. the effect will be to prevent it form behaving as a target, but it's not completely purged. + if (!doNotClearArrays) { + delete _targetEndpointDefinitions[elid]; + delete _targetEndpointsUnique[elid]; + delete _targetMaxConnections[elid]; + delete _targetsEnabled[elid]; + } + + return _currentInstance; + }; + + /* + * Function: makeTargets + * Makes all elements in some array or a selector connection targets. + * + * Parameters: + * els - either an array of ids or a selector + * params - parameters to configure each element as a target with + * referenceParams - extra parameters to configure each element as a taretsource with. + * + * Returns: + * The current jsPlumb instance. + */ + this.makeTargets = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeTarget(els[i], params, referenceParams); + } + }; + + /** + * Function: makeSource + * Makes some DOM element a Connection source, allowing you to drag connections from it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a source. + * params - JS object containing parameters: + * - *endpoint* optional. specification of an endpoint to create when a connection is created. + * - *parent* optional. the element to add Endpoints to when a Connection is established. if you omit this, + * Endpoints will be added to 'el'. + * - *scope* optional. scope for the connections dragged from this element. + * - *dragOptions* optional. same stuff as you would pass to dragOptions of an Endpoint definition. + * - *deleteEndpointsOnDetach* optional, defaults to false. whether or not to delete + * any Endpoints created by a connection from this source if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * - *filter* - optional function to call when the user presses the mouse button to start a drag. This function is passed the original + * event and the element on which the associated makeSource call was made. If it returns anything other than false, + * the drag begins as usual. But if it returns false (the boolean false, not just something falsey), the drag is aborted. + * + * + */ + var _sourceEndpointDefinitions = {}, + _sourceEndpoints = {}, + _sourceEndpointsUnique = {}, + _sourcesEnabled = {}, + _sourceTriggers = {}, + _sourceMaxConnections = {}, + _targetsEnabled = {}; + + this.makeSource = function(el, params, referenceParams) { + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + _setEndpointPaintStylesAndAnchor(p, 0); + var jpcl = jsPlumb.CurrentLibrary, + maxConnections = p.maxConnections || -1, + onMaxConnections = p.onMaxConnections, + _doOne = function(_el) { + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el), + parent = p.parent, + idToRegisterAgainst = parent != null ? _currentInstance.getId(jpcl.getElementObject(parent)) : elid; + + _sourceEndpointDefinitions[idToRegisterAgainst] = p; + _sourceEndpointsUnique[idToRegisterAgainst] = p.uniqueEndpoint; + _sourcesEnabled[idToRegisterAgainst] = true; + + var stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"], + dragOptions = jsPlumb.extend({ }, p.dragOptions || {}), + existingDrag = dragOptions.drag, + existingStop = dragOptions.stop, + ep = null, + endpointAddedButNoDragYet = false; + + _sourceMaxConnections[idToRegisterAgainst] = maxConnections; + + // set scope if its not set in dragOptions but was passed in in params + dragOptions["scope"] = dragOptions["scope"] || p.scope; + + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + if (existingDrag) existingDrag.apply(this, arguments); + endpointAddedButNoDragYet = false; + }); + + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], function() { + if (existingStop) existingStop.apply(this, arguments); + + //_currentlyDown = false; + _currentInstance.currentlyDragging = false; + + if (ep.connections.length == 0) + _currentInstance.deleteEndpoint(ep); + else { + + jpcl.unbind(ep.canvas, "mousedown"); + + // reset the anchor to the anchor that was initially provided. the one we were using to drag + // the connection was just a placeholder that was located at the place the user pressed the + // mouse button to initiate the drag. + var anchorDef = p.anchor || _currentInstance.Defaults.Anchor, + oldAnchor = ep.anchor, + oldConnection = ep.connections[0]; + + ep.anchor = _currentInstance.makeAnchor(anchorDef, elid, _currentInstance); + + if (p.parent) { + var parent = jpcl.getElementObject(p.parent); + if (parent) { + var currentId = ep.elementId; + + var potentialParent = p.container || _currentInstance.Defaults.Container || jsPlumb.Defaults.Container; + + ep.setElement(parent, potentialParent); + ep.endpointWillMoveAfterConnection = false; + _currentInstance.anchorManager.rehomeEndpoint(currentId, parent); + oldConnection.previousConnection = null; + // remove from connectionsByScope + _removeWithFunction(connectionsByScope[oldConnection.scope], function(c) { + return c.id === oldConnection.id; + }); + _currentInstance.anchorManager.connectionDetached({ + sourceId:oldConnection.sourceId, + targetId:oldConnection.targetId, + connection:oldConnection + }); + _finaliseConnection(oldConnection); + } + } + + ep.repaint(); + _currentInstance.repaint(ep.elementId); + _currentInstance.repaint(oldConnection.targetId); + + } + }); + // when the user presses the mouse, add an Endpoint, if we are enabled. + var mouseDownListener = function(e) { + + // if disabled, return. + if (!_sourcesEnabled[idToRegisterAgainst]) return; + + // if maxConnections reached + var sourceCount = _currentInstance.select({source:idToRegisterAgainst}).length + if (_sourceMaxConnections[idToRegisterAgainst] >= 0 && sourceCount >= _sourceMaxConnections[idToRegisterAgainst]) { + if (onMaxConnections) { + onMaxConnections({ + element:_el, + maxConnections:maxConnections + }, e); + } + return false; + } + + // if a filter was given, run it, and return if it says no. + if (params.filter) { + // pass the original event to the user: + var r = params.filter(jpcl.getOriginalEvent(e), _el); + if (r === false) return; + } + + // make sure we have the latest offset for this div + var myOffsetInfo = _updateOffset({elId:elid}); + + var x = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width, + y = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height, + parentX = x, + parentY = y; + + // if there is a parent, the endpoint will actually be added to it now, rather than the div + // that was the source. in that case, we have to adjust the anchor position so it refers to + // the parent. + if (p.parent) { + var pEl = jpcl.getElementObject(p.parent), + pId = _getId(pEl); + myOffsetInfo = _updateOffset({elId:pId}); + parentX = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width, + parentY = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height; + } + + // we need to override the anchor in here, and force 'isSource', but we don't want to mess with + // the params passed in, because after a connection is established we're going to reset the endpoint + // to have the anchor we were given. + var tempEndpointParams = {}; + jsPlumb.extend(tempEndpointParams, p); + tempEndpointParams.isSource = true; + tempEndpointParams.anchor = [x,y,0,0]; + tempEndpointParams.parentAnchor = [ parentX, parentY, 0, 0 ]; + tempEndpointParams.dragOptions = dragOptions; + // if a parent was given we need to turn that into a "container" argument. this is, by default, + // the parent of the element we will move to, so parent of p.parent in this case. however, if + // the user has specified a 'container' on the endpoint definition or on + // the defaults, we should use that. + if (p.parent) { + var potentialParent = tempEndpointParams.container || _currentInstance.Defaults.Container || jsPlumb.Defaults.Container; + if (potentialParent) + tempEndpointParams.container = potentialParent; + else + tempEndpointParams.container = jsPlumb.CurrentLibrary.getParent(p.parent); + } + + ep = _currentInstance.addEndpoint(elid, tempEndpointParams); + + endpointAddedButNoDragYet = true; + // we set this to prevent connections from firing attach events before this function has had a chance + // to move the endpoint. + ep.endpointWillMoveAfterConnection = p.parent != null; + ep.endpointWillMoveTo = p.parent ? jpcl.getElementObject(p.parent) : null; + + var _delTempEndpoint = function() { + // this mouseup event is fired only if no dragging occurred, by jquery and yui, but for mootools + // it is fired even if dragging has occurred, in which case we would blow away a perfectly + // legitimate endpoint, were it not for this check. the flag is set after adding an + // endpoint and cleared in a drag listener we set in the dragOptions above. + if(endpointAddedButNoDragYet) { + _currentInstance.deleteEndpoint(ep); + } + }; + + _currentInstance.registerListener(ep.canvas, "mouseup", _delTempEndpoint); + _currentInstance.registerListener(_el, "mouseup", _delTempEndpoint); + + // and then trigger its mousedown event, which will kick off a drag, which will start dragging + // a new connection from this endpoint. + jpcl.trigger(ep.canvas, "mousedown", e); + }; + + // register this on jsPlumb so that it can be cleared by a reset. + _currentInstance.registerListener(_el, "mousedown", mouseDownListener); + _sourceTriggers[elid] = mouseDownListener; + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + + return _currentInstance; + }; + + /** + * Function: unmakeSource + * Sets the given element to no longer be a connection source. + * + * Parameters: + * el - a string id, a dom element, or a selector representing the element. + * + * Returns: + * The current jsPlumb instance. + */ + this.unmakeSource = function(el, doNotClearArrays) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + var id = _getId(el), + mouseDownListener = _sourceTriggers[id]; + + if (mouseDownListener) + _currentInstance.unregisterListener(_el, "mousedown", mouseDownListener); + + if (!doNotClearArrays) { + delete _sourceEndpointDefinitions[id]; + delete _sourceEndpointsUnique[id]; + delete _sourcesEnabled[id]; + delete _sourceTriggers[id]; + delete _sourceMaxConnections[id]; + } + + return _currentInstance; + }; + + /* + * Function: unmakeEverySource + * Resets all elements in this instance of jsPlumb so that none of them are connection sources. + * + * Returns: + * The current jsPlumb instance. + */ + this.unmakeEverySource = function() { + for (var i in _sourcesEnabled) + _currentInstance.unmakeSource(i, true); + + _sourceEndpointDefinitions = {}; + _sourceEndpointsUnique = {}; + _sourcesEnabled = {}; + _sourceTriggers = {}; + }; + + /* + * Function: unmakeEveryTarget + * Resets all elements in this instance of jsPlumb so that none of them are connection targets. + * + * Returns: + * The current jsPlumb instance. + */ + this.unmakeEveryTarget = function() { + for (var i in _targetsEnabled) + _currentInstance.unmakeTarget(i, true); + + _targetEndpointDefinitions = {}; + _targetEndpointsUnique = {}; + _targetMaxConnections = {}; + _targetsEnabled = {}; + + return _currentInstance; + }; + + /* + * Function: makeSources + * Makes all elements in some array or a selector connection sources. + * + * Parameters: + * els - either an array of ids or a selector + * params - parameters to configure each element as a source with + * referenceParams - extra parameters to configure each element as a source with. + * + * Returns: + * The current jsPlumb instance. + */ + this.makeSources = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeSource(els[i], params, referenceParams); + } + + return _currentInstance; + }; + + // does the work of setting a source enabled or disabled. + var _setEnabled = function(type, el, state, toggle) { + var a = type == "source" ? _sourcesEnabled : _targetsEnabled; + + if (_isString(el)) a[el] = toggle ? !a[el] : state; + else if (el.length) { + el = _convertYUICollection(el); + for (var i = 0; i < el.length; i++) { + var id = _el = jsPlumb.CurrentLibrary.getElementObject(el[i]), id = _getId(_el); + a[id] = toggle ? !a[id] : state; + } + } + return _currentInstance; + }; + + /* + * Function: setSourceEnabled + * Sets the enabled state of one or more elements that were previously made a connection source with the makeSource + * method. + * + * Parameters: + * el - either a string representing some element's id, or an array of ids, or a selector. + * state - true to enable the element(s), false to disable it. + * + * Returns: + * The current jsPlumb instance. + */ + this.setSourceEnabled = function(el, state) { + return _setEnabled("source", el, state); + }; + + /* + * Function: toggleSourceEnabled + * Toggles the source enabled state of the given element or elements. + * + * Parameters: + * el - either a string representing some element's id, or an array of ids, or a selector. + * state - true to enable the element(s), false to disable it. + * + * Returns: + * The current enabled state of the source. + */ + this.toggleSourceEnabled = function(el) { + _setEnabled("source", el, null, true); + return _currentInstance.isSourceEnabled(el); + }; + + /* + * Function: isSource + * Returns whether or not the given element is registered as a connection source. + * + * Parameters: + * el - a string id, a dom element, or a selector representing a single element. + * + * Returns: + * True if source, false if not. + */ + this.isSource = function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return _sourcesEnabled[_getId(el)] != null; + }; + + /* + * Function: isSourceEnabled + * Returns whether or not the given connection source is enabled. + * + * Parameters: + * el - a string id, a dom element, or a selector representing a single element. + * + * Returns: + * True if enabled, false if not. + */ + this.isSourceEnabled = function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return _sourcesEnabled[_getId(el)] === true; + }; + + /* + * Function: setTargetEnabled + * Sets the enabled state of one or more elements that were previously made a connection target with the makeTarget method. + * method. + * + * Parameters: + * el - either a string representing some element's id, or an array of ids, or a selector. + * state - true to enable the element(s), false to disable it. + * + * Returns: + * The current jsPlumb instance. + */ + this.setTargetEnabled = function(el, state) { + return _setEnabled("target", el, state); + }; + + /* + * Function: toggleTargetEnabled + * Toggles the target enabled state of the given element or elements. + * + * Parameters: + * el - either a string representing some element's id, or an array of ids, or a selector. + * state - true to enable the element(s), false to disable it. + * + * Returns: + * The current enabled state of the target. + */ + this.toggleTargetEnabled = function(el) { + return _setEnabled("target", el, null, true); + return _currentInstance.isTargetEnabled(el); + }; + + /* + Function: isTarget + Returns whether or not the given element is registered as a connection target. + + Parameters: + el - a string id, a dom element, or a selector representing a single element. + + Returns: + True if source, false if not. + */ + this.isTarget = function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return _targetsEnabled[_getId(el)] != null; + }; + + /* + Function: isTargetEnabled + Returns whether or not the given connection target is enabled. + + Parameters: + el - a string id, a dom element, or a selector representing a single element. + + Returns: + True if enabled, false if not. + */ + this.isTargetEnabled = function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return _targetsEnabled[_getId(el)] === true; + }; + + /* + * Function: ready + * Helper method to bind a function to jsPlumb's ready event. You should use this method instead of your + * library's equivalent, to ensure that jsPlumb has loaded properly before you start to use it. This is + * particularly true in the case of YUI, because of the asynchronous nature of the module loading process. + */ + this.ready = function(fn) { + _currentInstance.bind("ready", fn); + }; + + /* + * Function: repaint + * Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + * + * Parameters: + * el - id of the element, a dom element, or a selector representing the element. + * + * Returns: + * The current jsPlumb instance. + * + * See Also: + * + */ + this.repaint = function(el, ui, timestamp) { + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) { + _draw(_getElementObject(el[i]), ui, timestamp); + } + else // ...and single strings. + _draw(_getElementObject(el), ui, timestamp); + + return _currentInstance; + }; + + /* + * Function: repaintEverything + * Repaints all connections. + * + * Returns: + * The current jsPlumb instance. + * + * See Also: + * + */ + this.repaintEverything = function() { + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId)); + } + return _currentInstance; + }; + + /* + * Function: removeAllEndpoints + * Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + * + * Parameters: + * el - either an element id, or a selector for an element. + * + * Returns: + * The current jsPlumb instance. + * + * See Also: + * + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"), + ebe = endpointsByElement[elId]; + if (ebe) { + for ( var i = 0; i < ebe.length; i++) + _currentInstance.deleteEndpoint(ebe[i]); + } + endpointsByElement[elId] = []; + return _currentInstance; + }; + + + var _registeredListeners = {}, + _unbindRegisteredListeners = function() { + for (var i in _registeredListeners) { + for (var j = 0; j < _registeredListeners[i].length; j++) { + var info = _registeredListeners[i][j]; + jsPlumb.CurrentLibrary.unbind(info.el, info.event, info.listener); + } + } + _registeredListeners = {}; + }; + + // internal register listener method. gives us a hook to clean things up + // with if the user calls jsPlumb.reset. + this.registerListener = function(el, type, listener) { + jsPlumb.CurrentLibrary.bind(el, type, listener); + _addToList(_registeredListeners, type, {el:el, event:type, listener:listener}); + }; + + this.unregisterListener = function(el, type, listener) { + jsPlumb.CurrentLibrary.unbind(el, type, listener); + _removeWithFunction(_registeredListeners, function(rl) { + return rl.type == type && rl.listener == listener; + }); + }; + + /* + * Function:reset + * Removes all endpoints and connections and clears the listener list. To keep listeners call + * : jsPlumb.deleteEveryEndpoint() + * instead of this. + */ + this.reset = function() { + _currentInstance.deleteEveryEndpoint(); + _currentInstance.unbind(); + _targetEndpointDefinitions = {}; + _targetEndpoints = {}; + _targetEndpointsUnique = {}; + _targetMaxConnections = {}; + _sourceEndpointDefinitions = {}; + _sourceEndpoints = {}; + _sourceEndpointsUnique = {}; + _sourceMaxConnections = {}; + _unbindRegisteredListeners(); + _currentInstance.anchorManager.reset(); + if (!jsPlumbAdapter.headless) + _currentInstance.dragManager.reset(); + }; + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + * + * Returns: + * The current jsPlumb instance. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + return _currentInstance; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * draggable - whether or not the element should be draggable. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setId + * Changes the id of some element, adjusting all connections and endpoints + * + * Parameters: + * el - a selector, a DOM element, or a string. + * newId - string. + */ + this.setId = function(el, newId, doNotSetAttribute) { + + var id = el.constructor == String ? el : _currentInstance.getId(el), + sConns = _currentInstance.getConnections({source:id, scope:'*'}, true), + tConns = _currentInstance.getConnections({target:id, scope:'*'}, true); + + newId = "" + newId; + + if (!doNotSetAttribute) { + el = jsPlumb.CurrentLibrary.getElementObject(id); + jsPlumb.CurrentLibrary.setAttribute(el, "id", newId); + } + + el = jsPlumb.CurrentLibrary.getElementObject(newId); + + + endpointsByElement[newId] = endpointsByElement[id] || []; + for (var i = 0; i < endpointsByElement[newId].length; i++) { + endpointsByElement[newId][i].elementId = newId; + endpointsByElement[newId][i].element = el; + endpointsByElement[newId][i].anchor.elementId = newId; + } + delete endpointsByElement[id]; + + _currentInstance.anchorManager.changeId(id, newId); + + var _conns = function(list, epIdx, type) { + for (var i = 0; i < list.length; i++) { + list[i].endpoints[epIdx].elementId = newId; + list[i].endpoints[epIdx].element = el; + list[i][type + "Id"] = newId; + list[i][type] = el; + } + }; + _conns(sConns, 0, "source"); + _conns(tConns, 1, "target"); + }; + + /* + * Function: setIdChanged + * Notify jsPlumb that the element with oldId has had its id changed to newId. + * + * This method is equivalent to what jsPlumb does itself in the second step of the setId method above. + * + * Parameters: + * oldId - previous element id + * newId - element's new id + * + * See Also: + * + */ + this.setIdChanged = function(oldId, newId) { + _currentInstance.setId(oldId, newId, true); + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + + var _suspendDrawing = false, + _suspendedAt = null; + /* + * Function: setSuspendDrawing + * Suspends drawing operations. This can (and should!) be used when you have a lot of connections to make or endpoints to register; + * it will save you a lot of time. + * + * Parameters: + * val - boolean indicating whether to suspend or not + * repaintAfterwards - optional boolean instructing jsPlumb to do a full repaint after changing the suspension + * state. defaults to false. + */ + this.setSuspendDrawing = function(val, repaintAfterwards) { + _suspendDrawing = val; + if (val) _suspendedAt = new Date().getTime(); else _suspendedAt = null; + if (repaintAfterwards) _currentInstance.repaintEverything(); + }; + + /* + * Function: isSuspendDrawing + * Returns whether or not drawing is currently suspended. + */ + this.isSuspendDrawing = function() { + return _suspendDrawing; + }; + + /* + * Property: CANVAS + * Constant for use with the setRenderMode method + */ + this.CANVAS = "canvas"; + + /* + * Property: SVG + * Constant for use with the setRenderMode method + */ + this.SVG = "svg"; + + /* + * Property: VML + * Constant for use with the setRenderMode method + */ + this.VML = "vml"; + + /* + * Function: setRenderMode + * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that + * what you asked for is not supported (and that VML is). If you asked for VML but the browser does + * not support it, jsPlumb uses SVG. + * + * Parameters: + * mode - a string representing the mode. Use one of the jsPlumb render mode constants as discussed above. + * + * Returns: + * The render mode that jsPlumb set, which of course may be different from that requested. + */ + this.setRenderMode = function(mode) { + renderMode = jsPlumbAdapter.setRenderMode(mode); + return renderMode; + }; + + /* + * Function: getRenderMode + * + * Returns: + * The current render mode. + */ + this.getRenderMode = function() { return renderMode; }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * changeEndpoints - whether or not to also change the visible state of the endpoints on the element. this also has a bearing on + * other connections on those endpoints: if their other endpoint is also visible, the connections are made visible. + * + * Returns: + * The current jsPlumb instance. + */ + this.show = function(el, changeEndpoints) { + _setVisible(el, "block", changeEndpoints); + return _currentInstance; + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * The current jsPlumb instance + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + return _currentInstance; + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + //findIndex : _findIndex, + getId : _getId, + makeAnchor:self.makeAnchor, + makeDynamicAnchor:self.makeDynamicAnchor + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * changeEndpoints - whether or not to also toggle the endpoints on the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + var adjustForParentOffsetAndScroll = function(xy, el) { + + var offsetParent = null, result = xy; + if (el.tagName.toLowerCase() === "svg" && el.parentNode) { + offsetParent = el.parentNode; + } + else if (el.offsetParent) { + offsetParent = el.offsetParent; + } + if (offsetParent != null) { + var po = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(offsetParent, _currentInstance), + so = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : {left:offsetParent.scrollLeft, top:offsetParent.scrollTop}; + + + // i thought it might be cool to do this: + // lastReturnValue[0] = lastReturnValue[0] - offsetParent.offsetLeft + offsetParent.scrollLeft; + // lastReturnValue[1] = lastReturnValue[1] - offsetParent.offsetTop + offsetParent.scrollTop; + // but i think it ignores margins. my reasoning was that it's quicker to not hand off to some underlying + // library. + + result[0] = xy[0] - po.left + so.left; + result[1] = xy[1] - po.top + so.top; + } + + return result; + + }; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + this.elementId = params.elementId; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + + if (timestamp && timestamp === self.timestamp) + return lastReturnValue; + + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + + // adjust loc if there is an offsetParent + lastReturnValue = adjustForParentOffsetAndScroll(lastReturnValue, element.canvas); + + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function(_endpoint) { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + * + * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference, + // the canvas this refers to. + refCanvas = params.referenceCanvas, + size = _getSize(_getElementObject(refCanvas)), + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + xDir = 0, yDir = 0, + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + orientation = null, + _lastResult = null; + + // set these to 0 each; they are used by certain types of connectors in the loopback case, + // when the connector is trying to clear the element it is on. but for floating anchor it's not + // very important. + this.x = 0; this.y = 0; + + this.isFloating = true; + + this.compute = function(params) { + var xy = params.xy, element = params.element, + result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + + // adjust loc if there is an offsetParent + result = adjustForParentOffsetAndScroll(result, element.canvas); + + _lastResult = result; + return result; + }; + + this.getOrientation = function(_endpoint) { + if (orientation) return orientation; + else { + var o = ref.getOrientation(_endpoint); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchor is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector, elementId) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = [], self = this, + _convert = function(anchor) { + return anchor.constructor == Anchor ? anchor: _currentInstance.makeAnchor(anchor, elementId, _currentInstance); + }; + for (var i = 0; i < anchors.length; i++) + _anchors[i] = _convert(anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + this.locked = false; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null, + _curIndex = _anchors.length > 0 ? 0 : -1, + self = this, + + // helper method to calculate the distance between the centers of the two elements. + _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }, + + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + self.x = _curAnchor.x; + self.y = _curAnchor.y; + + return _curAnchor.compute(params); + }; + + this.getCurrentLocation = function() { + return _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + }; + + this.getOrientation = function(_endpoint) { return _curAnchor != null ? _curAnchor.getOrientation(_endpoint) : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + manages anchors for all elements. + */ + // "continuous" anchors: anchors that pick their location based on how many connections the given element has. + // this requires looking at a lot more elements than normal - anything that has had a Continuous anchor applied has + // to be recalculated. so this manager is used as a reference point. the first time, with a new timestamp, that + // a continuous anchor is asked to compute, it calls this guy. or maybe, even, this guy gets called somewhere else + // and compute only ever returns pre-computed values. either way, this is the central point, and we want it to + // be called as few times as possible. + var continuousAnchors = {}, + continuousAnchorLocations = {}, + continuousAnchorOrientations = {}, + Orientation = { HORIZONTAL : "horizontal", VERTICAL : "vertical", DIAGONAL : "diagonal", IDENTITY:"identity" }, + + // TODO this functions uses a crude method of determining orientation between two elements. + // 'diagonal' should be chosen when the angle of the line between the two centers is around + // one of 45, 135, 225 and 315 degrees. maybe +- 15 degrees. + calculateOrientation = function(sourceId, targetId, sd, td) { + + if (sourceId === targetId) return { + orientation:Orientation.IDENTITY, + a:["top", "top"] + }; + + var theta = Math.atan2((td.centery - sd.centery) , (td.centerx - sd.centerx)), + theta2 = Math.atan2((sd.centery - td.centery) , (sd.centerx - td.centerx)), + h = ((sd.left <= td.left && sd.right >= td.left) || (sd.left <= td.right && sd.right >= td.right) || + (sd.left <= td.left && sd.right >= td.right) || (td.left <= sd.left && td.right >= sd.right)), + v = ((sd.top <= td.top && sd.bottom >= td.top) || (sd.top <= td.bottom && sd.bottom >= td.bottom) || + (sd.top <= td.top && sd.bottom >= td.bottom) || (td.top <= sd.top && td.bottom >= sd.bottom)); + + if (! (h || v)) { + var a = null, rls = false, rrs = false, sortValue = null; + if (td.left > sd.left && td.top > sd.top) + a = ["right", "top"]; + else if (td.left > sd.left && sd.top > td.top) + a = [ "top", "left"]; + else if (td.left < sd.left && td.top < sd.top) + a = [ "top", "right"]; + else if (td.left < sd.left && td.top > sd.top) + a = ["left", "top" ]; + + return { orientation:Orientation.DIAGONAL, a:a, theta:theta, theta2:theta2 }; + } + else if (h) return { + orientation:Orientation.HORIZONTAL, + a:sd.top < td.top ? ["bottom", "top"] : ["top", "bottom"], + theta:theta, theta2:theta2 + } + else return { + orientation:Orientation.VERTICAL, + a:sd.left < td.left ? ["right", "left"] : ["left", "right"], + theta:theta, theta2:theta2 + } + }, + placeAnchorsOnLine = function(desc, elementDimensions, elementPosition, + connections, horizontal, otherMultiplier, reverse) { + var a = [], step = elementDimensions[horizontal ? 0 : 1] / (connections.length + 1); + + for (var i = 0; i < connections.length; i++) { + var val = (i + 1) * step, other = otherMultiplier * elementDimensions[horizontal ? 1 : 0]; + if (reverse) + val = elementDimensions[horizontal ? 0 : 1] - val; + + var dx = (horizontal ? val : other), x = elementPosition[0] + dx, xp = dx / elementDimensions[0], + dy = (horizontal ? other : val), y = elementPosition[1] + dy, yp = dy / elementDimensions[1]; + + a.push([ x, y, xp, yp, connections[i][1], connections[i][2] ]); + } + + return a; + }, + standardEdgeSort = function(a, b) { return a[0] > b[0] ? 1 : -1 }, + currySort = function(reverseAngles) { + return function(a,b) { + var r = true; + if (reverseAngles) { + if (a[0][0] < b[0][0]) + r = true; + else + r = a[0][1] > b[0][1]; + } + else { + if (a[0][0] > b[0][0]) + r= true; + else + r =a[0][1] > b[0][1]; + } + return r === false ? -1 : 1; + }; + }, + leftSort = function(a,b) { + // first get adjusted values + var p1 = a[0][0] < 0 ? -Math.PI - a[0][0] : Math.PI - a[0][0], + p2 = b[0][0] < 0 ? -Math.PI - b[0][0] : Math.PI - b[0][0]; + if (p1 > p2) return 1; + else return a[0][1] > b[0][1] ? 1 : -1; + }, + edgeSortFunctions = { + "top":standardEdgeSort, + "right":currySort(true), + "bottom":currySort(true), + "left":leftSort + }, + _sortHelper = function(_array, _fn) { + return _array.sort(_fn); + }, + placeAnchors = function(elementId, _anchorLists) { + var sS = sizes[elementId], sO = offsets[elementId], + placeSomeAnchors = function(desc, elementDimensions, elementPosition, unsortedConnections, isHorizontal, otherMultiplier, orientation) { + if (unsortedConnections.length > 0) { + var sc = _sortHelper(unsortedConnections, edgeSortFunctions[desc]), // puts them in order based on the target element's pos on screen + reverse = desc === "right" || desc === "top", + anchors = placeAnchorsOnLine(desc, elementDimensions, + elementPosition, sc, + isHorizontal, otherMultiplier, reverse ); + + // takes a computed anchor position and adjusts it for parent offset and scroll, then stores it. + var _setAnchorLocation = function(endpoint, anchorPos) { + var a = adjustForParentOffsetAndScroll([anchorPos[0], anchorPos[1]], endpoint.canvas); + continuousAnchorLocations[endpoint.id] = [ a[0], a[1], anchorPos[2], anchorPos[3] ]; + continuousAnchorOrientations[endpoint.id] = orientation; + }; + + for (var i = 0; i < anchors.length; i++) { + var c = anchors[i][4], weAreSource = c.endpoints[0].elementId === elementId, weAreTarget = c.endpoints[1].elementId === elementId; + if (weAreSource) + _setAnchorLocation(c.endpoints[0], anchors[i]); + else if (weAreTarget) + _setAnchorLocation(c.endpoints[1], anchors[i]); + } + } + }; + + placeSomeAnchors("bottom", sS, [sO.left,sO.top], _anchorLists.bottom, true, 1, [0,1]); + placeSomeAnchors("top", sS, [sO.left,sO.top], _anchorLists.top, true, 0, [0,-1]); + placeSomeAnchors("left", sS, [sO.left,sO.top], _anchorLists.left, false, 0, [-1,0]); + placeSomeAnchors("right", sS, [sO.left,sO.top], _anchorLists.right, false, 1, [1,0]); + }, + AnchorManager = function() { + var _amEndpoints = {}, + connectionsByElementId = {}, + self = this, + anchorLists = {}; + + this.reset = function() { + _amEndpoints = {}; + connectionsByElementId = {}; + anchorLists = {}; + }; + this.newConnection = function(conn) { + var sourceId = conn.sourceId, targetId = conn.targetId, + ep = conn.endpoints, + doRegisterTarget = true, + registerConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + + if ((sourceId == targetId) && otherAnchor.isContinuous){ + // remove the target endpoint's canvas. we dont need it. + jsPlumb.CurrentLibrary.removeElement(ep[1].canvas); + doRegisterTarget = false; + } + _addToList(connectionsByElementId, elId, [c, otherEndpoint, otherAnchor.constructor == DynamicAnchor]); + }; + + registerConnection(0, ep[0], ep[0].anchor, targetId, conn); + if (doRegisterTarget) + registerConnection(1, ep[1], ep[1].anchor, sourceId, conn); + }; + this.connectionDetached = function(connInfo) { + var connection = connInfo.connection || connInfo; + var sourceId = connection.sourceId, + targetId = connection.targetId, + ep = connection.endpoints, + removeConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + if (otherAnchor.constructor == FloatingAnchor) { + // no-op + } + else { + _removeWithFunction(connectionsByElementId[elId], function(_c) { + return _c[0].id == c.id; + }); + } + }; + + removeConnection(1, ep[1], ep[1].anchor, sourceId, connection); + removeConnection(0, ep[0], ep[0].anchor, targetId, connection); + + // remove from anchorLists + var sEl = connection.sourceId, + tEl = connection.targetId, + sE = connection.endpoints[0].id, + tE = connection.endpoints[1].id, + _remove = function(list, eId) { + if (list) { // transient anchors dont get entries in this list. + var f = function(e) { return e[4] == eId; }; + _removeWithFunction(list["top"], f); + _removeWithFunction(list["left"], f); + _removeWithFunction(list["bottom"], f); + _removeWithFunction(list["right"], f); + } + }; + + _remove(anchorLists[sEl], sE); + _remove(anchorLists[tEl], tE); + self.redraw(sEl); + self.redraw(tEl); + }; + this.add = function(endpoint, elementId) { + _addToList(_amEndpoints, elementId, endpoint); + }; + this.changeId = function(oldId, newId) { + connectionsByElementId[newId] = connectionsByElementId[oldId]; + _amEndpoints[newId] = _amEndpoints[oldId]; + delete connectionsByElementId[oldId]; + delete _amEndpoints[oldId]; + }; + this.getConnectionsFor = function(elementId) { + return connectionsByElementId[elementId] || []; + }; + this.getEndpointsFor = function(elementId) { + return _amEndpoints[elementId] || []; + }; + this.deleteEndpoint = function(endpoint) { + _removeWithFunction(_amEndpoints[endpoint.elementId], function(e) { + return e.id == endpoint.id; + }); + }; + this.clearFor = function(elementId) { + delete _amEndpoints[elementId]; + _amEndpoints[elementId] = []; + }; + // updates the given anchor list by either updating an existing anchor's info, or adding it. this function + // also removes the anchor from its previous list, if the edge it is on has changed. + // all connections found along the way (those that are connected to one of the faces this function + // operates on) are added to the connsToPaint list, as are their endpoints. in this way we know to repaint + // them wthout having to calculate anything else about them. + var _updateAnchorList = function(lists, theta, order, conn, aBoolean, otherElId, idx, reverse, edgeId, elId, connsToPaint, endpointsToPaint) { + // first try to find the exact match, but keep track of the first index of a matching element id along the way.s + var exactIdx = -1, + firstMatchingElIdx = -1, + endpoint = conn.endpoints[idx], + endpointId = endpoint.id, + oIdx = [1,0][idx], + values = [ [ theta, order ], conn, aBoolean, otherElId, endpointId ], + listToAddTo = lists[edgeId], + listToRemoveFrom = endpoint._continuousAnchorEdge ? lists[endpoint._continuousAnchorEdge] : null; + + if (listToRemoveFrom) { + var rIdx = _findWithFunction(listToRemoveFrom, function(e) { return e[4] == endpointId }); + if (rIdx != -1) { + listToRemoveFrom.splice(rIdx, 1); + // get all connections from this list + for (var i = 0; i < listToRemoveFrom.length; i++) { + _addWithFunction(connsToPaint, listToRemoveFrom[i][1], function(c) { return c.id == listToRemoveFrom[i][1].id }); + _addWithFunction(endpointsToPaint, listToRemoveFrom[i][1].endpoints[idx], function(e) { return e.id == listToRemoveFrom[i][1].endpoints[idx].id }); + } + } + } + + for (var i = 0; i < listToAddTo.length; i++) { + if (idx == 1 && listToAddTo[i][3] === otherElId && firstMatchingElIdx == -1) + firstMatchingElIdx = i; + _addWithFunction(connsToPaint, listToAddTo[i][1], function(c) { return c.id == listToAddTo[i][1].id }); + _addWithFunction(endpointsToPaint, listToAddTo[i][1].endpoints[idx], function(e) { return e.id == listToAddTo[i][1].endpoints[idx].id }); + } + if (exactIdx != -1) { + listToAddTo[exactIdx] = values; + } + else { + var insertIdx = reverse ? firstMatchingElIdx != -1 ? firstMatchingElIdx : 0 : listToAddTo.length; // of course we will get this from having looked through the array shortly. + listToAddTo.splice(insertIdx, 0, values); + } + + // store this for next time. + endpoint._continuousAnchorEdge = edgeId; + }; + this.redraw = function(elementId, ui, timestamp, offsetToUI) { + + if (!_suspendDrawing) { + // get all the endpoints for this element + var ep = _amEndpoints[elementId] || [], + endpointConnections = connectionsByElementId[elementId] || [], + connectionsToPaint = [], + endpointsToPaint = [], + anchorsToUpdate = []; + + timestamp = timestamp || _timestamp(); + // offsetToUI are values that would have been calculated in the dragManager when registering + // an endpoint for an element that had a parent (somewhere in the hierarchy) that had been + // registered as draggable. + offsetToUI = offsetToUI || {left:0, top:0}; + if (ui) { + ui = { + left:ui.left + offsetToUI.left, + top:ui.top + offsetToUI.top + } + } + + _updateOffset( { elId : elementId, offset : ui, recalc : false, timestamp : timestamp }); + // valid for one paint cycle. + var myOffset = offsets[elementId], + myWH = sizes[elementId], + orientationCache = {}; + + // actually, first we should compute the orientation of this element to all other elements to which + // this element is connected with a continuous anchor (whether both ends of the connection have + // a continuous anchor or just one) + //for (var i = 0; i < continuousAnchorConnections.length; i++) { + for (var i = 0; i < endpointConnections.length; i++) { + var conn = endpointConnections[i][0], + sourceId = conn.sourceId, + targetId = conn.targetId, + sourceContinuous = conn.endpoints[0].anchor.isContinuous, + targetContinuous = conn.endpoints[1].anchor.isContinuous; + + if (sourceContinuous || targetContinuous) { + var oKey = sourceId + "_" + targetId, + oKey2 = targetId + "_" + sourceId, + o = orientationCache[oKey], + oIdx = conn.sourceId == elementId ? 1 : 0; + + if (sourceContinuous && !anchorLists[sourceId]) anchorLists[sourceId] = { top:[], right:[], bottom:[], left:[] }; + if (targetContinuous && !anchorLists[targetId]) anchorLists[targetId] = { top:[], right:[], bottom:[], left:[] }; + + if (elementId != targetId) _updateOffset( { elId : targetId, timestamp : timestamp }); + if (elementId != sourceId) _updateOffset( { elId : sourceId, timestamp : timestamp }); + + var td = _getCachedData(targetId), + sd = _getCachedData(sourceId); + + if (targetId == sourceId && (sourceContinuous || targetContinuous)) { + // here we may want to improve this by somehow determining the face we'd like + // to put the connector on. ideally, when drawing, the face should be calculated + // by determining which face is closest to the point at which the mouse button + // was released. for now, we're putting it on the top face. + _updateAnchorList(anchorLists[sourceId], -Math.PI / 2, 0, conn, false, targetId, 0, false, "top", sourceId, connectionsToPaint, endpointsToPaint) + } + else { + if (!o) { + o = calculateOrientation(sourceId, targetId, sd.o, td.o); + orientationCache[oKey] = o; + // this would be a performance enhancement, but the computed angles need to be clamped to + //the (-PI/2 -> PI/2) range in order for the sorting to work properly. + /* orientationCache[oKey2] = { + orientation:o.orientation, + a:[o.a[1], o.a[0]], + theta:o.theta + Math.PI, + theta2:o.theta2 + Math.PI + };*/ + } + if (sourceContinuous) _updateAnchorList(anchorLists[sourceId], o.theta, 0, conn, false, targetId, 0, false, o.a[0], sourceId, connectionsToPaint, endpointsToPaint); + if (targetContinuous) _updateAnchorList(anchorLists[targetId], o.theta2, -1, conn, true, sourceId, 1, true, o.a[1], targetId, connectionsToPaint, endpointsToPaint); + } + + if (sourceContinuous) _addWithFunction(anchorsToUpdate, sourceId, function(a) { return a === sourceId; }); + if (targetContinuous) _addWithFunction(anchorsToUpdate, targetId, function(a) { return a === targetId; }); + _addWithFunction(connectionsToPaint, conn, function(c) { return c.id == conn.id; }); + if ((sourceContinuous && oIdx == 0) || (targetContinuous && oIdx == 1)) + _addWithFunction(endpointsToPaint, conn.endpoints[oIdx], function(e) { return e.id == conn.endpoints[oIdx].id; }); + } + } + + // place Endpoints whose anchors are continuous but have no Connections + for (var i = 0; i < ep.length; i++) { + if (ep[i].connections.length == 0 && ep[i].anchor.isContinuous) { + if (!anchorLists[elementId]) anchorLists[elementId] = { top:[], right:[], bottom:[], left:[] }; + _updateAnchorList(anchorLists[elementId], -Math.PI / 2, 0, {endpoints:[ep[i], ep[i]], paint:function(){}}, false, elementId, 0, false, "top", elementId, connectionsToPaint, endpointsToPaint) + _addWithFunction(anchorsToUpdate, elementId, function(a) { return a === elementId; }) + } + } + + // now place all the continuous anchors we need to; + for (var i = 0; i < anchorsToUpdate.length; i++) { + placeAnchors(anchorsToUpdate[i], anchorLists[anchorsToUpdate[i]]); + } + + // now that continuous anchors have been placed, paint all the endpoints for this element + // TODO performance: add the endpoint ids to a temp array, and then when iterating in the next + // loop, check that we didn't just paint that endpoint. we can probably shave off a few more milliseconds this way. + for (var i = 0; i < ep.length; i++) { + ep[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + // ... and any other endpoints we came across as a result of the continuous anchors. + for (var i = 0; i < endpointsToPaint.length; i++) { + endpointsToPaint[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + + // paint all the standard and "dynamic connections", which are connections whose other anchor is + // static and therefore does need to be recomputed; we make sure that happens only one time. + + // TODO we could have compiled a list of these in the first pass through connections; might save some time. + for (var i = 0; i < endpointConnections.length; i++) { + var otherEndpoint = endpointConnections[i][1]; + if (otherEndpoint.anchor.constructor == DynamicAnchor) { + otherEndpoint.paint({ elementWithPrecedence:elementId }); + _addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; }); + // all the connections for the other endpoint now need to be repainted + for (var k = 0; k < otherEndpoint.connections.length; k++) { + if (otherEndpoint.connections[k] !== endpointConnections[i][0]) + _addWithFunction(connectionsToPaint, otherEndpoint.connections[k], function(c) { return c.id == otherEndpoint.connections[k].id; }); + } + } else if (otherEndpoint.anchor.constructor == Anchor) { + _addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; }); + } + } + // paint current floating connection for this element, if there is one. + var fc = floatingConnections[elementId]; + if (fc) + fc.paint({timestamp:timestamp, recalc:false, elId:elementId}); + + // paint all the connections + for (var i = 0; i < connectionsToPaint.length; i++) { + connectionsToPaint[i].paint({elId:elementId, timestamp:timestamp, recalc:false}); + } + } + }; + this.rehomeEndpoint = function(currentId, element) { + var eps = _amEndpoints[currentId] || [], //, + elementId = _currentInstance.getId(element); + if (elementId !== currentId) { + for (var i = 0; i < eps.length; i++) { + self.add(eps[i], elementId); + } + eps.splice(0, eps.length); + } + }; + }; + _currentInstance.anchorManager = new AnchorManager(); + _currentInstance.continuousAnchorFactory = { + get:function(params) { + var existing = continuousAnchors[params.elementId]; + if (!existing) { + existing = { + type:"Continuous", + compute : function(params) { + return continuousAnchorLocations[params.element.id] || [0,0]; + }, + getCurrentLocation : function(endpoint) { + return continuousAnchorLocations[endpoint.id] || [0,0]; + }, + getOrientation : function(endpoint) { + return continuousAnchorOrientations[endpoint.id] || [0,0]; + }, + isDynamic : true, + isContinuous : true + }; + continuousAnchors[params.elementId] = existing; + } + return existing; + } + }; + + if (!jsPlumbAdapter.headless) + _currentInstance.dragManager = jsPlumbAdapter.getDragManager(_currentInstance); + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. You should not ever create one of these directly. If you make a call to jsPlumb.connect, all of + * the parameters that you pass in to that function will be passed to the Connection constructor; if your UI + * uses the various Endpoint-centric methods like addEndpoint/makeSource/makeTarget, along with drag and drop, + * then the parameters you set on those functions are translated and passed in to the Connection constructor. So + * you should check the documentation for each of those methods. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * container - optional id or selector instructing jsPlumb where to attach all the elements it creates for this connection. you should read the documentation for a full discussion of this. + * detachable - optional, defaults to true. Defines whether or not the connection may be detached using the mouse. + * reattach - optional, defaults to false. Defines whether not the connection should be retached if it was dragged off an Endpoint and then dropped in whitespace. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * cssClass - optional CSS class to set on the display element associated with this Connection. + * hoverClass - optional CSS class to set on the display element associated with this Connection when it is in hover state. + * overlays - Optional array of Overlay definitions to appear on this Connection. + * drawEndpoints - if false, instructs jsPlumb to not draw the endpoints for this Connection. Be careful with this: it only really works when you tell jsPlumb to attach elements to the document body. Read the documentation for a full discussion of this. + * parameters - Optional JS object containing parameters to set on the Connection. These parameters are then available via the getParameter method. + */ + var Connection = function(params) { + var self = this, visible = true, _internalHover, _superClassHover; + self.idPrefix = "_jsplumb_c_"; + self.defaultLabelLocation = 0.5; + self.defaultOverlayKeys = ["Overlays", "ConnectionOverlays"]; + this.parent = params.parent; + overlayCapableJsPlumbUIComponent.apply(this, arguments); + // ************** get the source and target and register the connection. ******************* + +// VISIBILITY + /* + * Function: isVisible + * Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + * Function: setVisible + * Sets whether or not the Connection should be visible. + * + * Parameters: + * visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + self[v ? "showOverlays" : "hideOverlays"](); + if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none"; + self.repaint(); + }; +// END VISIBILITY + +// TYPE + + this.getTypeDescriptor = function() { return "connection"; }; + this.getDefaultType = function() { + return { + parameters:{}, + scope:null, + detachable:self._jsPlumb.Defaults.ConnectionsDetachable, + rettach:self._jsPlumb.Defaults.ReattachConnections, + paintStyle:self._jsPlumb.Defaults.PaintStyle || jsPlumb.Defaults.PaintStyle, + connector:self._jsPlumb.Defaults.Connector || jsPlumb.Defaults.Connector, + hoverPaintStyle:self._jsPlumb.Defaults.HoverPaintStyle || jsPlumb.Defaults.HoverPaintStyle, + overlays:self._jsPlumb.Defaults.ConnectorOverlays || jsPlumb.Defaults.ConnectorOverlays + }; + }; + var superAt = this.applyType; + this.applyType = function(t) { + superAt(t); + if (t.detachable != null) self.setDetachable(t.detachable); + if (t.reattach != null) self.setReattach(t.reattach); + if (t.scope) self.scope = t.scope; + self.setConnector(t.connector); + }; +// END TYPE + +// HOVER + // override setHover to pass it down to the underlying connector + _superClassHover = self.setHover; + self.setHover = function(state) { + var zi = _currentInstance.ConnectorZIndex || jsPlumb.Defaults.ConnectorZIndex; + if (zi) + self.connector.setZIndex(zi + (state ? 1 : 0)); + self.connector.setHover.apply(self.connector, arguments); + _superClassHover.apply(self, arguments); + }; + + _internalHover = function(state) { + if (_connectionBeingDragged == null) { + self.setHover(state, false); + } + }; +// END HOVER + + /* + * Function: setConnector + * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method, the same + * thing that you would set as the 'connector' property on a jsPlumb.connect call. + * + * Parameters: + * connector - Connector definition + */ + this.setConnector = function(connector, doNotRepaint) { + if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent); + var connectorArgs = { _jsPlumb:self._jsPlumb, parent:params.parent, + cssClass:params.cssClass, container:params.container, tooltip:self.tooltip + }; + if (_isString(connector)) + this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand. + else if (_isArray(connector)) { + if (connector.length == 1) + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](connectorArgs); + else + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs)); + } + self.canvas = self.connector.canvas; + // binds mouse listeners to the current connector. + _bindListeners(self.connector, self, _internalHover); + + // set z-index if it was set on Defaults. + var zi = _currentInstance.ConnectorZIndex || jsPlumb.Defaults.ConnectorZIndex; + if (zi) + self.connector.setZIndex(zi); + + if (!doNotRepaint) self.repaint(); + }; + +// INITIALISATION CODE + + this.source = _getElementObject(params.source); + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. but + // source is not overridden if the Endpoint has declared it is not the final target of a connection; + // instead we use the source that the Endpoint declares will be the final source element. + if (params.sourceEndpoint) this.source = params.sourceEndpoint.endpointWillMoveTo || params.sourceEndpoint.getElement(); + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + + // if a new connection is the result of moving some existing connection, params.previousConnection + // will have that Connection in it. listeners for the jsPlumbConnection event can look for that + // member and take action if they need to. + self.previousConnection = params.previousConnection; + + this.sourceId = _getAttribute(this.source, "id"); + this.targetId = _getAttribute(this.target, "id"); + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams, elementId) { + return (anchorParams) ? _currentInstance.makeAnchor(anchorParams, elementId, _currentInstance) : null; + }, + prepareEndpoint = function(existing, index, params, element, elementId, connectorPaintStyle, connectorHoverPaintStyle) { + var e; + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] + || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint; + + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified. + if (es.fillStyle == null && connectorPaintStyle != null) + es.fillStyle = connectorPaintStyle.strokeStyle; + + // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does: + //* + if (es.outlineColor == null && connectorPaintStyle != null) + es.outlineColor = connectorPaintStyle.outlineColor; + if (es.outlineWidth == null && connectorPaintStyle != null) + es.outlineWidth = connectorPaintStyle.outlineWidth; + //*/ + + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure? + if (connectorHoverPaintStyle != null) { + if (ehs == null) ehs = {}; + if (ehs.fillStyle == null) { + ehs.fillStyle = connectorHoverPaintStyle.strokeStyle; + } + } + var a = params.anchors ? params.anchors[index] : + params.anchor ? params.anchor : + _makeAnchor(_currentInstance.Defaults.Anchors[index], elementId) || + _makeAnchor(jsPlumb.Defaults.Anchors[index], elementId) || + _makeAnchor(_currentInstance.Defaults.Anchor, elementId) || + _makeAnchor(jsPlumb.Defaults.Anchor, elementId), + u = params.uuids ? params.uuids[index] : null; + e = _newEndpoint({ + paintStyle : es, hoverPaintStyle:ehs, endpoint : ep, connections : [ self ], + uuid : u, anchor : a, source : element, scope : params.scope, container:params.container, + reattach:params.reattach || _currentInstance.Defaults.ReattachConnections, + detachable:params.detachable || _currentInstance.Defaults.ConnectionsDetachable + }); + self.endpoints[index] = e; + + if (params.drawEndpoints === false) e.setVisible(false, true, true); + + } + return e; + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source, + self.sourceId, params.paintStyle, params.hoverPaintStyle); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target, + self.targetId, params.paintStyle, params.hoverPaintStyle); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + // if delete endpoints on detach, keep a record of just exactly which endpoints they are. + self.endpointsToDeleteOnDetach = [null, null]; + if (params.deleteEndpointsOnDetach) { + if (params.sourceIsNew) self.endpointsToDeleteOnDetach[0] = self.endpoints[0]; + if (params.targetIsNew) self.endpointsToDeleteOnDetach[1] = self.endpoints[1]; + } + + self.setConnector(this.endpoints[0].connector || + this.endpoints[1].connector || + params.connector || + _currentInstance.Defaults.Connector || + jsPlumb.Defaults.Connector, true); + + this.setPaintStyle(this.endpoints[0].connectorStyle || + this.endpoints[1].connectorStyle || + params.paintStyle || + _currentInstance.Defaults.PaintStyle || + jsPlumb.Defaults.PaintStyle, true); + + this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || + this.endpoints[1].connectorHoverStyle || + params.hoverPaintStyle || + _currentInstance.Defaults.HoverPaintStyle || + jsPlumb.Defaults.HoverPaintStyle, true); + + this.paintStyleInUse = this.getPaintStyle(); + + _updateOffset( { elId : this.sourceId, timestamp:_suspendedAt }); + _updateOffset( { elId : this.targetId, timestamp:_suspendedAt }); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId], + otherOffset = offsets[this.targetId], + otherWH = sizes[this.targetId], + initialTimestamp = _suspendedAt || _timestamp(), + anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + elementId:this.endpoints[0].elementId, + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1], + timestamp:initialTimestamp + }); + + this.endpoints[0].paint( { anchorLoc : anchorLoc, timestamp:initialTimestamp }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + elementId:this.endpoints[1].elementId, + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0], + timestamp:initialTimestamp + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc, timestamp:initialTimestamp }); + +// END INITIALISATION CODE + +// DETACHABLE + var _detachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.detachable === false) _detachable = false; + if(self.endpoints[0].connectionsDetachable === false) _detachable = false; + if(self.endpoints[1].connectionsDetachable === false) _detachable = false; + /* + * Function: isDetachable + * Returns whether or not this connection can be detached from its target/source endpoint. by default this + * is false; use it in conjunction with the 'reattach' parameter. + */ + this.isDetachable = function() { + return _detachable === true; + }; + /* + * Function: setDetachable + * Sets whether or not this connection is detachable. + * + * Parameters: + * detachable - whether or not to set the Connection to be detachable. + */ + this.setDetachable = function(detachable) { + _detachable = detachable === true; + }; +// END DETACHABLE + +// REATTACH + var _reattach = params.reattach || + self.endpoints[0].reattachConnections || + self.endpoints[1].reattachConnections || + _currentInstance.Defaults.ReattachConnections; + /* + * Function: isReattach + * Returns whether or not this connection will be reattached after having been detached via the mouse and dropped. by default this + * is false; use it in conjunction with the 'detachable' parameter. + */ + this.isReattach = function() { + return _reattach === true; + }; + /* + * Function: setReattach + * Sets whether or not this connection will reattach after having been detached via the mouse and dropped. + * + * Parameters: + * reattach - whether or not to set the Connection to reattach after drop in whitespace. + */ + this.setReattach = function(reattach) { + _reattach = reattach === true; + }; + +// END REATTACH + +// COST + DIRECTIONALITY + // if cost not supplied, try to inherit from source endpoint + var _cost = params.cost || self.endpoints[0].getConnectionCost(); + self.getCost = function() { return _cost; }; + self.setCost = function(c) { _cost = c; }; + var _bidirectional = !(params.bidirectional === false); + // inherit bidirectional flag if set no source endpoint + if (params.bidirectional == null) _bidirectional = self.endpoints[0].areConnectionsBidirectional(); + self.isBidirectional = function() { return _bidirectional; }; +// END COST + DIRECTIONALITY + +// PARAMETERS + // merge all the parameters objects into the connection. parameters set + // on the connection take precedence; then target endpoint params, then + // finally source endpoint params. + // TODO jsPlumb.extend could be made to take more than two args, and it would + // apply the second through nth args in order. + var _p = jsPlumb.extend({}, this.endpoints[0].getParameters()); + jsPlumb.extend(_p, this.endpoints[1].getParameters()); + jsPlumb.extend(_p, self.getParameters()); + self.setParameters(_p); +// END PARAMETERS + +// MISCELLANEOUS + /** + * implementation of abstract method in jsPlumbUtil.EventGenerator + * @return list of attached elements. in our case, a list of Endpoints. + */ + this.getAttachedElements = function() { + return self.endpoints; + }; + + /** + * changes the parent element of this connection to newParent. not exposed for the public API. + */ + this.moveParent = function(newParent) { + var jpcl = jsPlumb.CurrentLibrary, curParent = jpcl.getParent(self.connector.canvas); + if (self.connector.bgCanvas) { + jpcl.removeElement(self.connector.bgCanvas, curParent); + jpcl.appendElement(self.connector.bgCanvas, newParent); + } + jpcl.removeElement(self.connector.canvas, curParent); + jpcl.appendElement(self.connector.canvas, newParent); + // this only applies for DOMOverlays + for (var i = 0; i < self.overlays.length; i++) { + if (self.overlays[i].isAppendedAtTopLevel) { + jpcl.removeElement(self.overlays[i].canvas, curParent); + jpcl.appendElement(self.overlays[i].canvas, newParent); + if (self.overlays[i].reattachListeners) + self.overlays[i].reattachListeners(self.connector); + } + } + if (self.connector.reattachListeners) // this is for SVG/VML; change an element's parent and you have to reinit its listeners. + self.connector.reattachListeners(); // the Canvas implementation doesn't have to care about this + }; + +// END MISCELLANEOUS + +// PAINTING + + /* + * Paints the Connection. Not exposed for public usage. + * + * Parameters: + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + var lastPaintedAt = null; + this.paint = function(params) { + + if (visible) { + + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp, + // if the moving object is not the source we must transpose the two references. + swap = false, + tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId, + tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + + if (timestamp == null || timestamp != lastPaintedAt) { + var sourceInfo = _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }), + targetInfo = _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var sE = this.endpoints[sIdx], tE = this.endpoints[tIdx], + sAnchorP = sE.anchor.getCurrentLocation(sE), + tAnchorP = tE.anchor.getCurrentLocation(tE); + + /* paint overlays*/ + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible()) maxSize = Math.max(maxSize, o.computeMaxSize()); + } + + var dim = this.connector.compute( + sAnchorP, + tAnchorP, + this.endpoints[sIdx], + this.endpoints[tIdx], + this.endpoints[sIdx].anchor, + this.endpoints[tIdx].anchor, + self.paintStyleInUse.lineWidth, + maxSize, + sourceInfo, + targetInfo ); + + self.connector.paint(dim, self.paintStyleInUse); + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim, timestamp); + } + } + lastPaintedAt = timestamp; + } + }; + + /* + * Function: repaint + * Repaints the Connection. No parameters exposed to public API. + */ + this.repaint = function(params) { + params = params || {}; + var recalc = !(params.recalc === false); + this.paint({ elId : this.sourceId, recalc : recalc, timestamp:params.timestamp }); + }; + + // the very last thing we do is check to see if a 'type' was supplied in the params + var _type = params.type || self.endpoints[0].connectionType || self.endpoints[1].connectionType; + if (_type) + self.addType(_type); + +// END PAINTING + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Connection. + * + * Parameters: + * event - the event to bind. Available events on a Connection are: + * - *click* : notification that a Connection was clicked. + * - *dblclick* : notification that a Connection was double clicked. + * - *mouseenter* : notification that the mouse is over a Connection. + * - *mouseexit* : notification that the mouse exited a Connection. + * - *contextmenu* : notification that the user right-clicked on the Connection. + * + * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Connection's paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: getPaintStyle + * Gets the Connection's paint style. This is not necessarily the paint style in use at the time; + * this is the paint style for the connection when the mouse it not hovering over it. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Connection will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Connection. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + + /* + * Function: getParameter + * Gets the named parameter; returns null if no parameter with that name is set. Parameter values may have been supplied to a 'connect' or 'addEndpoint' call (connections made with the mouse get a copy of all parameters set on each of their Endpoints), or the parameter value may have been set with setParameter. + * + * Parameters: + * key - Parameter name. + */ + + /* + * Function: setParameter + * Sets the named parameter to the given value. + * + * Parameters: + * key - Parameter name. + * value - Parameter value. + */ + + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc) + */ + + /* + * Property: sourceId + * Id of the source element in the connection. + */ + /* + * Property: targetId + * Id of the target element in the connection. + */ + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + /* + * Property: source + * The source element for this Connection. + */ + /* + * Property: target + * The target element for this Connection. + */ + /* + * Property: overlays + * List of Overlays for this component. + */ + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + /* + * Function: getOverlay + * Gets an overlay, by ID. Note: by ID. You would pass an 'id' parameter + * in to the Overlay's constructor arguments, and then use that to retrieve + * it via this method. + * + * Parameters: + * overlayId - id of the overlay to retrieve. + */ + /* + * Function: getOverlays + * Gets all the overlays for this component. + */ + /* + * Function: hideOverlay + * Hides the overlay specified by the given id. + * + * Parameters: + * overlayId - id of the overlay to hide. + */ + /* + * Function: hideOverlays + * Hides all Overlays + */ + /* + * Function: showOverlay + * Shows the overlay specified by the given id. + * + * Parameters: + * overlayId - id of the overlay to show. + */ + /* + * Function: showOverlays + * Shows all Overlays + */ + /** + * Function: removeAllOverlays + * Removes all overlays from the Connection, and then repaints. + */ + /** + * Function: removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * + * Parameters: + * overlayId - id of the overlay to remove. + */ + /** + * Function: removeOverlays + * Removes a set of overlays by ID. Note: by ID. this is a string you set in the overlay spec. + * + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String, a Function that returns a String, or a params object containing { "label", "labelStyle", "location", "cssClass" }. Note that this uses innerHTML on the label div, so keep + * that in mind if you need escaped HTML. + */ + /* + * Function: getLabel + * Returns the label text for this Connection (or a function if you are labelling with a function). + * + * This does not return the overlay itself; this is a convenience method which is a pair with + * setLabel; together they allow you to add and access a Label Overlay without having to create the + * Overlay object itself. For access to the underlying label overlay that jsPlumb has created, + * use getLabelOverlay. + * + * See Also: + * + */ + /* + * Function: getLabelOverlay + * Returns the underlying internal label overlay, which will exist if you specified a label on + * a connect call, or have called setLabel at any stage. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + + }; // END Connection class + +// ENDPOINT HELPER FUNCTIONS + var _makeConnectionDragHandler = function(placeholder) { + var stopped = false; + return { + drag : function() { + if (stopped) { + stopped = false; + return true; + } + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments, _currentInstance.getZoom()), + el = placeholder.element; + + if (el) { + jsPlumb.CurrentLibrary.setOffset(el, _ui); + _draw(_getElementObject(el), _ui); + } + }, + stopDrag : function() { + stopped = true; + } + }; + }; + + var _makeFloatingEndpoint = function(paintStyle, referenceAnchor, endpoint, referenceCanvas, sourceElement) { + var floatingAnchor = new FloatingAnchor( { reference : referenceAnchor, referenceCanvas : referenceCanvas }); + //setting the scope here should not be the way to fix that mootools issue. it should be fixed by not + // adding the floating endpoint as a droppable. that makes more sense anyway! + return _newEndpoint({ paintStyle : paintStyle, endpoint : endpoint, anchor : floatingAnchor, source : sourceElement, scope:"__floating" }); + }; + + /** + * creates a placeholder div for dragging purposes, adds it to the DOM, and pre-computes its offset. + */ + var _makeDraggablePlaceholder = function(placeholder, parent) { + var n = document.createElement("div"); + n.style.position = "absolute"; + var placeholderDragElement = _getElementObject(n); + _appendElement(n, parent); + var id = _getId(placeholderDragElement); + _updateOffset( { elId : id }); + // create and assign an id, and initialize the offset. + placeholder.id = id; + placeholder.element = placeholderDragElement; + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1 + * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't + * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you + * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or + * 'target' Endpoints for Connections. + * + * + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * enabled - optional, defaults to true. Indicates whether or not the Endpoint should be enabled for mouse events (drag/drop). + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * cssClass - optional CSS class to set on the display element associated with this Endpoint. + * hoverClass - optional CSS class to set on the display element associated with this Endpoint when it is in hover state. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * container - optional id or selector instructing jsPlumb where to attach the element it creates for this endpoint. you should read the documentation for a full discussion of this. + * connections - optional list of Connections to configure the Endpoint with. + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * connectorClass - optional CSS class to set on Connections emanating from this Endpoint. + * connectorHoverClass - optional CSS class to set on to set on Connections emanating from this Endpoint when they are in hover state. + * connectionsDetachable - optional, defaults to true. Sets whether connections to/from this Endpoint should be detachable or not. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + * parameters - Optional JS object containing parameters to set on the Endpoint. These parameters are then available via the getParameter method. When a connection is made involving this Endpoint, the parameters from this Endpoint are copied into that Connection. Source Endpoint parameters override target Endpoint parameters if they both have a parameter with the same name. + */ + var Endpoint = function(params) { + var self = this; + self.idPrefix = "_jsplumb_e_"; + self.defaultLabelLocation = [ 0.5, 0.5 ]; + self.defaultOverlayKeys = ["Overlays", "EndpointOverlays"]; + this.parent = params.parent; + overlayCapableJsPlumbUIComponent.apply(this, arguments); + params = params || {}; + +// TYPE + + this.getTypeDescriptor = function() { return "endpoint"; }; + this.getDefaultType = function() { + return { + parameters:{}, + scope:null, + maxConnections:self._jsPlumb.Defaults.MaxConnections, + paintStyle:self._jsPlumb.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle, + endpoint:self._jsPlumb.Defaults.Endpoint || jsPlumb.Defaults.Endpoint, + hoverPaintStyle:self._jsPlumb.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle, + overlays:self._jsPlumb.Defaults.EndpointOverlays || jsPlumb.Defaults.EndpointOverlays, + connectorStyle:params.connectorStyle, + connectorHoverStyle:params.connectorHoverStyle, + connectorClass:params.connectorClass, + connectorHoverClass:params.connectorHoverClass, + connectorOverlays:params.connectorOverlays, + connector:params.connector, + connectorTooltip:params.connectorTooltip + }; + }; + var superAt = this.applyType; + this.applyType = function(t) { + superAt(t); + if (t.maxConnections != null) _maxConnections = t.maxConnections; + if (t.scope) self.scope = t.scope; + self.connectorStyle = t.connectorStyle; + self.connectorHoverStyle = t.connectorHoverStyle; + self.connectorOverlays = t.connectorOverlays; + self.connector = t.connector; + self.connectorTooltip = t.connectorTooltip; + self.connectionType = t.connectionType; + self.connectorClass = t.connectorClass; + self.connectorHoverClass = t.connectorHoverClass; + }; +// END TYPE + + var visible = true, __enabled = !(params.enabled === false); + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + self[v ? "showOverlays" : "hideOverlays"](); + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + + /* + Function: isEnabled + Returns whether or not the Endpoint is enabled for drag/drop connections. + */ + this.isEnabled = function() { return __enabled; }; + + /* + Function: setEnabled + Sets whether or not the Endpoint is enabled for drag/drop connections. + + Parameters: + enabled - whether or not the Endpoint is enabled. + */ + this.setEnabled = function(e) { __enabled = e; }; + + var _element = params.source, _uuid = params.uuid, floatingEndpoint = null, inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + this.element = _element; + + var _connectionCost = params.connectionCost; + this.getConnectionCost = function() { return _connectionCost; }; + this.setConnectionCost = function(c) { + _connectionCost = c; + }; + + var _connectionsBidirectional = params.connectionsBidirectional === false ? false : true; + this.areConnectionsBidirectional = function() { return _connectionsBidirectional; }; + this.setConnectionsBidirectional = function(b) { _connectionsBidirectional = b; }; + + self.anchor = params.anchor ? _currentInstance.makeAnchor(params.anchor, _elementId, _currentInstance) : params.anchors ? _currentInstance.makeAnchor(params.anchors, _elementId, _currentInstance) : _currentInstance.makeAnchor(_currentInstance.Defaults.Anchor || "TopCenter", _elementId, _currentInstance); + + // ANCHOR MANAGER + if (!params._transient) // in place copies, for example, are transient. they will never need to be retrieved during a paint cycle, because they dont move, and then they are deleted. + _currentInstance.anchorManager.add(self, _elementId); + + var _endpoint = null, originalEndpoint = null; + this.setEndpoint = function(ep) { + var endpointArgs = { + _jsPlumb:self._jsPlumb, + parent:params.parent, + container:params.container, + tooltip:params.tooltip, + connectorTooltip:params.connectorTooltip, + endpoint:self + }; + if (_isString(ep)) + _endpoint = new jsPlumb.Endpoints[renderMode][ep](endpointArgs); + else if (_isArray(ep)) { + endpointArgs = jsPlumb.extend(ep[1], endpointArgs); + _endpoint = new jsPlumb.Endpoints[renderMode][ep[0]](endpointArgs); + } + else { + _endpoint = ep.clone(); + } + + // assign a clone function using a copy of endpointArgs. this is used when a drag starts: the endpoint that was dragged is cloned, + // and the clone is left in its place while the original one goes off on a magical journey. + // the copy is to get around a closure problem, in which endpointArgs ends up getting shared by + // the whole world. + var argsForClone = jsPlumb.extend({}, endpointArgs); + _endpoint.clone = function() { + var o = new Object(); + _endpoint.constructor.apply(o, [argsForClone]); + return o; + }; + + self.endpoint = _endpoint; + self.type = self.endpoint.type; + }; + + this.setEndpoint(params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot"); + originalEndpoint = _endpoint; + + // override setHover to pass it down to the underlying endpoint + var _sh = self.setHover; + self.setHover = function() { + self.endpoint.setHover.apply(self.endpoint, arguments); + _sh.apply(self, arguments); + }; + // endpoint delegates to first connection for hover, if there is one. + var internalHover = function(state) { + if (self.connections.length > 0) + self.connections[0].setHover(state, false); + else + self.setHover(state); + }; + + // bind listeners from endpoint to self, with the internal hover function defined above. + _bindListeners(self.endpoint, self, internalHover); + + this.setPaintStyle(params.paintStyle || + params.style || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyle, true); + this.setHoverPaintStyle(params.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyle, true); + this.paintStyleInUse = this.getPaintStyle(); + var originalPaintStyle = this.getPaintStyle(); + this.connectorStyle = params.connectorStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.connectorTooltip = params.connectorTooltip; + this.connectorClass = params.connectorClass; + this.connectorHoverClass = params.connectorHoverClass; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + + var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + this.canvas = this.endpoint.canvas; + this.connections = params.connections || []; + + this.scope = params.scope || DEFAULT_SCOPE; + this.connectionType = params.connectionType; + this.timestamp = null; + //self.isReattach = params.reattach || false; + self.reattachConnections = params.reattach || _currentInstance.Defaults.ReattachConnections; + //self.isReattach = params.reattach || false; + self.connectionsDetachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.connectionsDetachable === false || params.detachable === false) + self.connectionsDetachable = false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + if (params.onMaxConnections) + self.bind("maxConnections", params.onMaxConnections); + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget, forceDetach, fireEvent, originalEvent) { + var idx = _findWithFunction(self.connections, function(c) { return c.id == connection.id}), + actuallyDetached = false; + fireEvent = (fireEvent !== false); + if (idx >= 0) { + // 1. does the connection have a before detach (note this also checks jsPlumb's bound + // detach handlers; but then Endpoint's check will, too, hmm.) + if (forceDetach || connection._forceDetach || connection.isDetachable() || connection.isDetachAllowed(connection)) { + // get the target endpoint + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + // it would be nice to check with both endpoints that it is ok to detach. but + // for this we'll have to get a bit fancier: right now if you use the same beforeDetach + // interceptor for two endpoints (which is kind of common, because it's part of the + // endpoint definition), then it gets fired twice. so in fact we need to loop through + // each beforeDetach and see if it returns false, at which point we exit. but if it + // returns true, we have to check the next one. however we need to track which ones + // have already been run, and not run them again. + if (forceDetach || connection._forceDetach || (self.isDetachAllowed(connection) /*&& t.isDetachAllowed(connection)*/)) { + + self.connections.splice(idx, 1); + + // this avoids a circular loop + if (!ignoreTarget) { + + t.detach(connection, true, forceDetach); + // check connection to see if we want to delete the endpoints associated with it. + // we only detach those that have just this connection; this scenario is most + // likely if we got to this bit of code because it is set by the methods that + // create their own endpoints, like .connect or .makeTarget. the user is + // not likely to have interacted with those endpoints. + if (connection.endpointsToDeleteOnDetach){ + for (var i = 0; i < connection.endpointsToDeleteOnDetach.length; i++) { + var cde = connection.endpointsToDeleteOnDetach[i]; + if (cde && cde.connections.length == 0) + _currentInstance.deleteEndpoint(cde); + } + } + } + _removeElements(connection.connector.getDisplayElements(), connection.parent); + _removeWithFunction(connectionsByScope[connection.scope], function(c) { + return c.id == connection.id; + }); + actuallyDetached = true; + var doFireEvent = (!ignoreTarget && fireEvent) + fireDetachEvent(connection, doFireEvent, originalEvent); + } + } + } + return actuallyDetached; + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + * + * Parameters: + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachAll = function(fireEvent, originalEvent) { + while (self.connections.length > 0) { + self.detach(self.connections[0], false, true, fireEvent, originalEvent); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachFrom = function(targetEndpoint, fireEvent, originalEvent) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + if (self.detach(c[i], false, true, fireEvent, originalEvent)) + c[i].setHover(false, false); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findWithFunction(self.connections, function(c) { return c.id == connection.id}); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * + * Returns: + * The DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: setElement + * Sets the DOM element this Endpoint is attached to. + * + * Parameters: + * el - dom element or selector + * container - optional, specifies the actual parent element to use as the parent for this Endpoint's visual representation. + * See the jsPlumb documentation for a discussion about this. + */ + this.setElement = function(el, container) { + + // TODO possibly have this object take charge of moving the UI components into the appropriate + // parent. this is used only by makeSource right now, and that function takes care of + // moving the UI bits and pieces. however it would s + var parentId = _getId(el); + // remove the endpoint from the list for the current endpoint's element + _removeWithFunction(endpointsByElement[self.elementId], function(e) { + return e.id == self.id; + }); + _element = _getElementObject(el); + _elementId = _getId(_element); + self.elementId = _elementId; + // need to get the new parent now + var newParentElement = _getParentFromParams({source:parentId, container:container}), + curParent = jpcl.getParent(self.canvas); + jpcl.removeElement(self.canvas, curParent); + jpcl.appendElement(self.canvas, newParentElement); + + // now move connection(s)...i would expect there to be only one but we will iterate. + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].moveParent(newParentElement); + self.connections[i].sourceId = _elementId; + self.connections[i].source = _element; + } + _addToList(endpointsByElement, parentId, self); + //_currentInstance.repaint(parentId); + + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + var loc = self.anchor.getCurrentLocation(self), + o = self.anchor.getOrientation(self), + inPlaceAnchor = { + compute:function() { return [ loc[0], loc[1] ]}, + getCurrentLocation : function() { return [ loc[0], loc[1] ]}, + getOrientation:function() { return o; } + }; + + return _newEndpoint( { + anchor : inPlaceAnchor, + source : _element, + paintStyle : this.getPaintStyle(), + endpoint : _endpoint, + _transient:true, + scope:self.scope + }); + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + var candidate = self.connections[0]; + if (self.isTarget && candidate) return candidate; + else { + return (self.connections.length < _maxConnections) || _maxConnections == -1 ? null : candidate; + } + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + /* + * Function: setStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to + * setStyle and deprecate it if so. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + * + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + // a helper function that tries to find a connection to the given element, and returns it if so. if elementWithPrecedence is null, + // or no connection to it is found, we return the first connection in our list. + var findConnectionToUseForDynamicAnchor = function(elementWithPrecedence) { + var idx = 0; + if (elementWithPrecedence != null) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].sourceId == elementWithPrecedence || self.connections[i].targetId == elementWithPrecedence) { + idx = i; + break; + } + } + } + + return self.connections[idx]; + }; + + /* + * Function: paint + * Paints the Endpoint, recalculating offset and anchor positions if necessary. This does NOT paint + * any of the Endpoint's connections. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + params = params || {}; + var timestamp = params.timestamp, recalc = !(params.recalc === false); + //recalc = params.recalc; + if (!timestamp || self.timestamp !== timestamp) { + _updateOffset({ elId:_elementId, timestamp:timestamp, recalc:recalc }); + var xy = params.offset || offsets[_elementId]; + if(xy) { + var ap = params.anchorPoint,connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (recalc && self.anchor.isDynamic && self.connections.length > 0) { + var c = findConnectionToUseForDynamicAnchor(params.elementWithPrecedence), + oIdx = c.endpoints[0] == self ? 1 : 0, + oId = oIdx == 0 ? c.sourceId : c.targetId, + oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + ap = self.anchor.compute(anchorParams); + } + + // switched _endpoint to self here; continuous anchors, for instance, look for the + // endpoint's id, but here "_endpoint" is the renderer, not the actual endpoint. + // TODO need to verify this does not break anything. + //var d = _endpoint.compute(ap, self.anchor.getOrientation(_endpoint), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + var d = _endpoint.compute(ap, self.anchor.getOrientation(self), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + _endpoint.paint(d, self.paintStyleInUse, self.anchor); + self.timestamp = timestamp; + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) self.overlayPlacements[i] = o.draw(self.endpoint, self.paintStyleInUse, d); + } + } + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var placeholderInfo = { id:null, element:null }, + jpc = null, + existingJpc = false, + existingJpcParams = null, + _dragHandler = _makeConnectionDragHandler(placeholderInfo); + + var start = function() { + // drag might have started on an endpoint that is not actually a source, but which has + // one or more connections. + jpc = self.connectorSelector(); + var _continue = true; + // if not enabled, return + if (!self.isEnabled()) _continue = false; + // if no connection and we're not a source, return. + if (jpc == null && !params.isSource) _continue = false; + // otherwise if we're full and not allowed to drag, also return false. + if (params.isSource && self.isFull() && !dragAllowedWhenFull) _continue = false; + // if the connection was setup as not detachable or one of its endpoints + // was setup as connectionsDetachable = false, or Defaults.ConnectionsDetachable + // is set to false... + if (jpc != null && !jpc.isDetachable()) _continue = false; + + if (_continue === false) { + // this is for mootools and yui. returning false from this causes jquery to stop drag. + // the events are wrapped in both mootools and yui anyway, but i don't think returning + // false from the start callback would stop a drag. + if (jsPlumb.CurrentLibrary.stopDrag) jsPlumb.CurrentLibrary.stopDrag(); + _dragHandler.stopDrag(); + return false; + } + + // if we're not full but there was a connection, make it null. we'll create a new one. + if (jpc && !self.isFull() && params.isSource) jpc = null; + + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.referenceEndpoint = self; + inPlaceCopy.paint(); + + _makeDraggablePlaceholder(placeholderInfo, self.parent); + + // set the offset of this div to be where 'inPlaceCopy' is, to start with. + // TODO merge this code with the code in both Anchor and FloatingAnchor, because it + // does the same stuff. + var ipcoel = _getElementObject(inPlaceCopy.canvas), + ipco = _getOffset(ipcoel, _currentInstance), + po = adjustForParentOffsetAndScroll([ipco.left, ipco.top], inPlaceCopy.canvas); + jsPlumb.CurrentLibrary.setOffset(placeholderInfo.element, {left:po[0], top:po[1]}); + + // when using makeSource and a parent, we first draw the source anchor on the source element, then + // move it to the parent. note that this happens after drawing the placeholder for the + // first time. + if (self.parentAnchor) self.anchor = _currentInstance.makeAnchor(self.parentAnchor, self.elementId, _currentInstance); + + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", placeholderInfo.id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + + // create a floating endpoint. + // here we test to see if a dragProxy was specified in this endpoint's constructor params, and + // if so, we create that endpoint instead of cloning ourselves. + if (params.proxy) { + /* var floatingAnchor = new FloatingAnchor( { reference : self.anchor, referenceCanvas : self.canvas }); + + floatingEndpoint = _newEndpoint({ + paintStyle : params.proxy.paintStyle, + endpoint : params.proxy.endpoint, + anchor : floatingAnchor, + source : placeholderInfo.element, + scope:"__floating" + }); + + //$(self.canvas).hide(); + */ + self.setPaintStyle(params.proxy.paintStyle); + // if we do this, we have to cleanup the old one. like just remove its display parts + //self.setEndpoint(params.proxy.endpoint); + + } + // else { + floatingEndpoint = _makeFloatingEndpoint(self.getPaintStyle(), self.anchor, _endpoint, self.canvas, placeholderInfo.element); + // } + + if (jpc == null) { + self.anchor.locked = true; + self.setHover(false, false); + // TODO the hover call above does not reset any target endpoint's hover + // states. + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : self.endpointWillMoveTo || _getElementObject(_element), // for makeSource with parent option. ensure source element is represented correctly. + target : placeholderInfo.element, + anchors : [ self.anchor, floatingEndpoint.anchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays, + type:self.connectionType, + cssClass:self.connectorClass, + hoverClass:self.connectorHoverClass + }); + + } else { + existingJpc = true; + jpc.connector.setHover(false, false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas), false, true); + // new anchor idx + var anchorIdx = jpc.endpoints[0].id == self.id ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas), + dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // now we want to get this endpoint's DROP scope, and set it for now: we can only be dropped on drop zones + // that have our drop scope (issue 57). + var dropScope = jsPlumb.CurrentLibrary.getDropScope(c); + jsPlumb.CurrentLibrary.setDragScope(c, dropScope); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = placeholderInfo.element; + jpc.sourceId = placeholderInfo.id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = placeholderInfo.element; + jpc.targetId = placeholderInfo.id; + } + + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.suspendedEndpoint.setHover(false); + floatingEndpoint.referenceEndpoint = jpc.suspendedEndpoint; + jpc.endpoints[anchorIdx] = floatingEndpoint; + + // fire an event that informs that a connection is being dragged + fireConnectionDraggingEvent(jpc); + + } + // register it and register connection on it. + floatingConnections[placeholderInfo.id] = jpc; + floatingEndpoint.addConnection(jpc); + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, placeholderInfo.id, floatingEndpoint); + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var jpcl = jsPlumb.CurrentLibrary, + dragOptions = params.dragOptions || {}, + defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions), + startEvent = jpcl.dragEvents["start"], + stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"]; + + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + // extracted drag handler function so can be used by makeSource + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], _dragHandler.drag); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + var originalEvent = jpcl.getDropEvent(arguments); + _currentInstance.currentlyDragging = false; + _removeWithFunction(endpointsByElement[placeholderInfo.id], function(e) { + return e.id == floatingEndpoint.id; + }); + _removeElements( [ placeholderInfo.element[0], floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + _currentInstance.anchorManager.clearFor(placeholderInfo.id); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + self.setPaintStyle(originalPaintStyle); // reset to original; may have been changed by drag proxy. + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + //if (self.isReattach || jpc._forceDetach || !jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc, false, false, true, originalEvent)) { + if (jpc.isReattach() || jpc._forceReattach || jpc._forceDetach || !jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc, false, false, true, originalEvent)) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + _currentInstance.repaint(existingJpcParams[1]); + } + jpc._forceDetach = null; + jpc._forceReattach = null; + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElements(jpc.connector.getDisplayElements(), self.parent); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint({recalc:false}); + jpc.setHover(false, false); + + fireConnectionDragStopEvent(jpc); + + jpc = null; + inPlaceCopy = null; + delete endpointsByElement[floatingEndpoint.elementId]; + floatingEndpoint.anchor = null; + floatingEndpoint = null; + _currentInstance.currentlyDragging = false; + + + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions, true); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas, forceInit, isTransient, endpoint) { + if ((params.isTarget || forceInit) && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop'], + overEvent = jsPlumb.CurrentLibrary.dragEvents['over'], + outEvent = jsPlumb.CurrentLibrary.dragEvents['out'], + drop = function() { + + var originalEvent = jsPlumb.CurrentLibrary.getDropEvent(arguments), + draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + elId = _getAttribute(draggable, "elId"), + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id]; + + // if this is a drop back where the connection came from, mark it force rettach and + // return; the stop handler will reattach. without firing an event. + var redrop = jpc.suspendedEndpoint && (jpc.suspendedEndpoint.id == self.id || + self.referenceEndpoint && jpc.suspendedEndpoint.id == self.referenceEndpoint.id) ; + if (redrop) { + jpc._forceReattach = true; + return; + } + + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + + // restore the original scope if necessary (issue 57) + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + var endpointEnabled = endpoint != null ? endpoint.isEnabled() : true; + + if (self.isFull()) { + self.fire("maxConnections", { + endpoint:self, + connection:jpc, + maxConnections:_maxConnections + }, originalEvent); + } + + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget) && endpointEnabled) { + var _doContinue = true; + + // the second check here is for the case that the user is dropping it back + // where it came from. + if (jpc.suspendedEndpoint && jpc.suspendedEndpoint.id != self.id) { + if (idx == 0) { + jpc.source = jpc.suspendedEndpoint.element; + jpc.sourceId = jpc.suspendedEndpoint.elementId; + } else { + jpc.target = jpc.suspendedEndpoint.element; + jpc.targetId = jpc.suspendedEndpoint.elementId; + } + + if (!jpc.isDetachAllowed(jpc) || !jpc.endpoints[idx].isDetachAllowed(jpc) || !jpc.suspendedEndpoint.isDetachAllowed(jpc) || !_currentInstance.checkCondition("beforeDetach", jpc)) + _doContinue = false; + } + + // these have to be set before testing for beforeDrop. + if (idx == 0) { + jpc.source = self.element; + jpc.sourceId = self.elementId; + } else { + jpc.target = self.element; + jpc.targetId = self.elementId; + } + + // now check beforeDrop. this will be available only on Endpoints that are setup to + // have a beforeDrop condition (although, secretly, under the hood all Endpoints and + // the Connection have them, because they are on jsPlumbUIComponent. shhh!), because + // it only makes sense to have it on a target endpoint. + _doContinue = _doContinue && self.isDropAllowed(jpc.sourceId, jpc.targetId, jpc.scope, jpc, self); + + if (_doContinue) { + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + + // copy our parameters in to the connection: + var params = self.getParameters(); + for (var aParam in params) + jpc.setParameter(aParam, params[aParam]); + + if (!jpc.suspendedEndpoint) { + if (params.draggable) + jsPlumb.CurrentLibrary.initDraggable(self.element, dragOptions, true); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + fireDetachEvent({ + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }, true, originalEvent); + } + + // finalise will inform the anchor manager and also add to + // connectionsByScope if necessary. + _finaliseConnection(jpc, null, originalEvent); + } + else { + // otherwise just put it back on the endpoint it was on before the drag. + if (jpc.suspendedEndpoint) { + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.setHover(false); + jpc._forceDetach = true; + if (idx == 0) { + jpc.source = jpc.suspendedEndpoint.element; + jpc.sourceId = jpc.suspendedEndpoint.elementId; + } else { + jpc.target = jpc.suspendedEndpoint.element; + jpc.targetId = jpc.suspendedEndpoint.elementId;; + } + jpc.suspendedEndpoint.addConnection(jpc); + + jpc.endpoints[0].repaint(); + jpc.repaint(); + _currentInstance.repaint(jpc.source.elementId); + jpc._forceDetach = false; + } + } + + jpc.floatingAnchorIndex = null; + } + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + jpc.suspendedEndpoint = null; + } + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + _jpc = floatingConnections[id]; + + if (_jpc != null) { + var idx = _jpc.floatingAnchorIndex == null ? 1 : _jpc.floatingAnchorIndex; + // here we should fire the 'over' event if we are a target and this is a new connection, + // or we are the same as the floating endpoint. + var _cont = (self.isTarget && _jpc.floatingAnchorIndex != 0) || (_jpc.suspendedEndpoint && self.referenceEndpoint && self.referenceEndpoint.id == _jpc.suspendedEndpoint.id); + if (_cont) + _jpc.endpoints[idx].anchor.over(self.anchor); + } + }); + dropOptions[outEvent] = _wrap(dropOptions[outEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + _jpc = floatingConnections[id]; + + if (_jpc != null) { + var idx = _jpc.floatingAnchorIndex == null ? 1 : _jpc.floatingAnchorIndex; + var _cont = (self.isTarget && _jpc.floatingAnchorIndex != 0) || (_jpc.suspendedEndpoint && self.referenceEndpoint && self.referenceEndpoint.id == _jpc.suspendedEndpoint.id); + if (_cont) + _jpc.endpoints[idx].anchor.out(); + } + }); + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions, true, isTransient); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas), true, !(params._transient || self.anchor.isFloating), self); + + // finally, set type if it was provided + if (params.type) + self.addType(params.type); + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Endpoint. + * + * Parameters: + * event - the event to bind. Available events on an Endpoint are: + * - *click* : notification that a Endpoint was clicked. + * - *dblclick* : notification that a Endpoint was double clicked. + * - *mouseenter* : notification that the mouse is over a Endpoint. + * - *mouseexit* : notification that the mouse exited a Endpoint. + * - *contextmenu* : notification that the user right-clicked on the Endpoint. + * + * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Endpoint's paint style and then repaints the Endpoint. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: getPaintStyle + * Gets the Endpoint's paint style. This is not necessarily the paint style in use at the time; + * this is the paint style for the Endpoint when the mouse it not hovering over it. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Endpoint will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Endpoint. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + + /* + * Function: getParameter + * Gets the named parameter; returns null if no parameter with that name is set. Parameters may have been set on the Endpoint in the 'addEndpoint' call, or they may have been set with the setParameter function. + * + * Parameters: + * key - Parameter name. + */ + + /* + * Function: setParameter + * Sets the named parameter to the given value. + * + * Parameters: + * key - Parameter name. + * value - Parameter value. + */ + + /* + * Property: canvas + * The Endpoint's drawing area. + */ + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + + /* + * Property: overlays + * List of Overlays for this Endpoint. + */ + /* + * Function: addOverlay + * Adds an Overlay to the Endpoint. + * + * Parameters: + * overlay - Overlay to add. + */ + /* + * Function: getOverlay + * Gets an overlay, by ID. Note: by ID. You would pass an 'id' parameter + * in to the Overlay's constructor arguments, and then use that to retrieve + * it via this method. + */ + /* + * Function: getOverlays + * Gets all the overlays for this component. + */ + /* + * Function: hideOverlay + * Hides the overlay specified by the given id. + */ + /* + * Function: hideOverlays + * Hides all Overlays + */ + /* + * Function: showOverlay + * Shows the overlay specified by the given id. + */ + /* + * Function: showOverlays + * Shows all Overlays + */ + /** + * Function: removeAllOverlays + * Removes all overlays from the Endpoint, and then repaints. + */ + /** + * Function: removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + /** + * Function: removeOverlays + * Removes a set of overlays by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + /* + * Function: setLabel + * Sets the Endpoint's label. + * + * Parameters: + * l - label to set. May be a String, a Function that returns a String, or a params object containing { "label", "labelStyle", "location", "cssClass" }. Note that this uses innerHTML on the label div, so keep + * that in mind if you need escaped HTML. + */ + /* + Function: getLabel + Returns the label text for this Endpoint (or a function if you are labelling with a function). + This does not return the overlay itself; this is a convenience method which is a pair with + setLabel; together they allow you to add and access a Label Overlay without having to create the + Overlay object itself. For access to the underlying label overlay that jsPlumb has created, + use getLabelOverlay. + */ + /* + Function: getLabelOverlay + Returns the underlying internal label overlay, which will exist if you specified a label on + an addEndpoint call, or have called setLabel at any stage. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + + return self; + }; + }; + + var jsPlumb = new jsPlumbInstance(); + if (typeof window != 'undefined') window.jsPlumb = jsPlumb; + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + j.init(); + return j; + }; + + var _curryAnchor = function(x, y, ox, oy, type, fnInit) { + return function(params) { + params = params || {}; + //var a = jsPlumb.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance); + var a = params.jsPlumbInstance.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance); + a.type = type; + if (fnInit) fnInit(a, params); + return a; + }; + }; + /* + * Property: Anchors.TopCenter + * An Anchor that is located at the top center of the element. + */ + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1, "TopCenter"); + /* + * Property: Anchors.BottomCenter + * An Anchor that is located at the bottom center of the element. + */ + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1, "BottomCenter"); + /* + * Property: Anchors.LeftMiddle + * An Anchor that is located at the left middle of the element. + */ + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0, "LeftMiddle"); + /* + * Property: Anchors.RightMiddle + * An Anchor that is located at the right middle of the element. + */ + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0, "RightMiddle"); + /* + * Property: Anchors.Center + * An Anchor that is located at the center of the element. + */ + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0, "Center"); + /* + * Property: Anchors.TopRight + * An Anchor that is located at the top right corner of the element. + */ + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1, "TopRight"); + /* + * Property: Anchors.BottomRight + * An Anchor that is located at the bottom right corner of the element. + */ + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1, "BottomRight"); + /* + * Property: Anchors.TopLeft + * An Anchor that is located at the top left corner of the element. + */ + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1, "TopLeft"); + /* + * Property: Anchors.BottomLeft + * An Anchor that is located at the bototm left corner of the element. + */ + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1, "BottomLeft"); + + jsPlumb.Defaults.DynamicAnchors = function(params) { + return params.jsPlumbInstance.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"], params.elementId, params.jsPlumbInstance); + }; + /* + * Property: Anchors.AutoDefault + * Default DynamicAnchors - chooses from TopCenter, RightMiddle, BottomCenter, LeftMiddle. + */ + jsPlumb.Anchors["AutoDefault"] = function(params) { + var a = params.jsPlumbInstance.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors(params)); + a.type = "AutoDefault"; + return a; + }; + + /* + * Property: Anchors.Assign + * An Anchor whose location is assigned at connection time, through an AnchorPositionFinder. Used in conjunction + * with the 'makeTarget' function. jsPlumb has two of these - 'Fixed' and 'Grid', and you can also write your own. + */ + jsPlumb.Anchors["Assign"] = _curryAnchor(0,0,0,0,"Assign", function(anchor, params) { + // find what to use as the "position finder". the user may have supplied a String which represents + // the id of a position finder in jsPlumb.AnchorPositionFinders, or the user may have supplied the + // position finder as a function. we find out what to use and then set it on the anchor. + var pf = params.position || "Fixed"; + anchor.positionFinder = pf.constructor == String ? params.jsPlumbInstance.AnchorPositionFinders[pf] : pf; + // always set the constructor params; the position finder might need them later (the Grid one does, + // for example) + anchor.constructorParams = params; + }); + + // Continuous anchor is just curried through to the 'get' method of the continuous anchor + // factory. + /* + * Property: Anchors.Continuous + * An Anchor that is tracks the other element in the connection, choosing the closest face. + */ + jsPlumb.Anchors["Continuous"] = function(params) { + return params.jsPlumbInstance.continuousAnchorFactory.get(params); + }; + + // these are the default anchor positions finders, which are used by the makeTarget function. supply + // a position finder argument to that function allows you to specify where the resulting anchor will + // be located + jsPlumb.AnchorPositionFinders = { + "Fixed": function(dp, ep, es, params) { + return [ (dp.left - ep.left) / es[0], (dp.top - ep.top) / es[1] ]; + }, + "Grid":function(dp, ep, es, params) { + var dx = dp.left - ep.left, dy = dp.top - ep.top, + gx = es[0] / (params.grid[0]), gy = es[1] / (params.grid[1]), + mx = Math.floor(dx / gx), my = Math.floor(dy / gy); + return [ ((mx * gx) + (gx / 2)) / es[0], ((my * gy) + (gy / 2)) / es[1] ]; + } + }; + + /* + * Property: Anchors.Perimeter + * An Anchor that tracks the perimeter of some shape, approximating it with a given number of dynamically + * positioned locations. + * + * Parameters: + * + * anchorCount - optional number of anchors to use to approximate the perimeter. default is 60. + * shape - required. the name of the shape. valid values are 'rectangle', 'square', 'ellipse', 'circle', 'triangle' and 'diamond' + * rotation - optional rotation, in degrees, to apply. + */ + jsPlumb.Anchors["Perimeter"] = function(params) { + params = params || {}; + var anchorCount = params.anchorCount || 60, + shape = params.shape; + + if (!shape) throw new Error("no shape supplied to Perimeter Anchor type"); + + var _circle = function() { + var r = 0.5, step = Math.PI * 2 / anchorCount, current = 0, a = []; + for (var i = 0; i < anchorCount; i++) { + var x = r + (r * Math.sin(current)), + y = r + (r * Math.cos(current)); + a.push( [ x, y, 0, 0 ] ); + current += step; + } + return a; + }, + _path = function(segments) { + var anchorsPerFace = anchorCount / segments.length, a = [], + _computeFace = function(x1, y1, x2, y2, fractionalLength) { + anchorsPerFace = anchorCount * fractionalLength; + var dx = (x2 - x1) / anchorsPerFace, dy = (y2 - y1) / anchorsPerFace; + for (var i = 0; i < anchorsPerFace; i++) { + a.push( [ + x1 + (dx * i), + y1 + (dy * i), + 0, + 0 + ]); + } + }; + + for (var i = 0; i < segments.length; i++) + _computeFace.apply(null, segments[i]); + + return a; + }, + _shape = function(faces) { + var s = []; + for (var i = 0; i < faces.length; i++) { + s.push([faces[i][0], faces[i][1], faces[i][2], faces[i][3], 1 / faces.length]); + } + return _path(s); + }, + _rectangle = function() { + return _shape([ + [ 0, 0, 1, 0 ], [ 1, 0, 1, 1 ], [ 1, 1, 0, 1 ], [ 0, 1, 0, 0 ] + ]); + }; + + var _shapes = { + "circle":_circle, + "ellipse":_circle, + "diamond":function() { + return _shape([ + [ 0.5, 0, 1, 0.5 ], [ 1, 0.5, 0.5, 1 ], [ 0.5, 1, 0, 0.5 ], [ 0, 0.5, 0.5, 0 ] + ]); + }, + "rectangle":_rectangle, + "square":_rectangle, + "triangle":function() { + return _shape([ + [ 0.5, 0, 1, 1 ], [ 1, 1, 0, 1 ], [ 0, 1, 0.5, 0] + ]); + }, + "path":function(params) { + var points = params.points; + var p = [], tl = 0; + for (var i = 0; i < points.length - 1; i++) { + var l = Math.sqrt(Math.pow(points[i][2] - points[i][0]) + Math.pow(points[i][3] - points[i][1])); + tl += l; + p.push([points[i][0], points[i][1], points[i+1][0], points[i+1][1], l]); + } + for (var i = 0; i < p.length; i++) { + p[i][4] = p[i][4] / tl; + } + return _path(p); + } + }, + _rotate = function(points, amountInDegrees) { + var o = [], theta = amountInDegrees / 180 * Math.PI ; + for (var i = 0; i < points.length; i++) { + var _x = points[i][0] - 0.5, + _y = points[i][1] - 0.5; + + o.push([ + 0.5 + ((_x * Math.cos(theta)) - (_y * Math.sin(theta))), + 0.5 + ((_x * Math.sin(theta)) + (_y * Math.cos(theta))), + points[i][2], + points[i][3] + ]); + } + return o; + }; + + if (!_shapes[shape]) throw new Error("Shape [" + shape + "] is unknown by Perimeter Anchor type"); + + var da = _shapes[shape](params); + if (params.rotation) da = _rotate(da, params.rotation); + var a = params.jsPlumbInstance.makeDynamicAnchor(da); + a.type = "Perimeter"; + return a; + }; +})(); diff --git a/build/1.3.16/js/jsPlumb-1.3.16-tests.js b/archive/1.3.15/jsPlumb-1.3.15-tests.js similarity index 100% rename from build/1.3.16/js/jsPlumb-1.3.16-tests.js rename to archive/1.3.15/jsPlumb-1.3.15-tests.js diff --git a/archive/1.3.15/jsPlumb-connectors-statemachine-1.3.15-RC1.js b/archive/1.3.15/jsPlumb-connectors-statemachine-1.3.15-RC1.js new file mode 100644 index 000000000..f79ce5b5d --- /dev/null +++ b/archive/1.3.15/jsPlumb-connectors-statemachine-1.3.15-RC1.js @@ -0,0 +1,469 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.15 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the state machine connectors. + * + * Thanks to Brainstorm Mobile Solutions for supporting the development of these. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + var Line = function(x1, y1, x2, y2) { + + this.m = (y2 - y1) / (x2 - x1); + this.b = -1 * ((this.m * x1) - y1); + + this.rectIntersect = function(x,y,w,h) { + var results = []; + + // try top face + // the equation of the top face is y = (0 * x) + b; y = b. + var xInt = (y - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try right face + var yInt = (this.m * (x + w)) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + // bottom face + var xInt = ((y + h) - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try left face + var yInt = (this.m * x) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + if (results.length == 2) { + var midx = (results[0][0] + results[1][0]) / 2, midy = (results[0][1] + results[1][1]) / 2; + results.push([ midx,midy ]); + // now calculate the segment inside the rectangle where the midpoint lies. + var xseg = midx <= x + (w / 2) ? -1 : 1, + yseg = midy <= y + (h / 2) ? -1 : 1; + results.push([xseg, yseg]); + return results; + } + + return null; + + }; + }, + _segment = function(x1, y1, x2, y2) { + if (x1 <= x2 && y2 <= y1) return 1; + else if (x1 <= x2 && y1 <= y2) return 2; + else if (x2 <= x1 && y2 >= y1) return 3; + return 4; + }, + + // the control point we will use depends on the faces to which each end of the connection is assigned, specifically whether or not the + // two faces are parallel or perpendicular. if they are parallel then the control point lies on the midpoint of the axis in which they + // are parellel and varies only in the other axis; this variation is proportional to the distance that the anchor points lie from the + // center of that face. if the two faces are perpendicular then the control point is at some distance from both the midpoints; the amount and + // direction are dependent on the orientation of the two elements. 'seg', passed in to this method, tells you which segment the target element + // lies in with respect to the source: 1 is top right, 2 is bottom right, 3 is bottom left, 4 is top left. + // + // sourcePos and targetPos are arrays of info about where on the source and target each anchor is located. their contents are: + // + // 0 - absolute x + // 1 - absolute y + // 2 - proportional x in element (0 is left edge, 1 is right edge) + // 3 - proportional y in element (0 is top edge, 1 is bottom edge) + // + _findControlPoint = function(midx, midy, segment, sourceEdge, targetEdge, dx, dy, distance, proximityLimit) { + + // TODO (maybe) + // - if anchor pos is 0.5, make the control point take into account the relative position of the elements. + if (distance <= proximityLimit) return [midx, midy]; + + if (segment == 1) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 2) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 3) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 4) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + }; + + /** + * Class: Connectors.StateMachine + * Provides 'state machine' connectors. + */ + /* + * Function: Constructor + * + * Parameters: + * curviness - measure of how "curvy" the connectors will be. this is translated as the distance that the + * Bezier curve's control point is from the midpoint of the straight line connecting the two + * endpoints, and does not mean that the connector is this wide. The Bezier curve never reaches + * its control points; they act as gravitational masses. defaults to 10. + * margin - distance from element to start and end connectors, in pixels. defaults to 5. + * proximityLimit - sets the distance beneath which the elements are consider too close together to bother + * with fancy curves. by default this is 80 pixels. + * loopbackRadius - the radius of a loopback connector. optional; defaults to 25. + * showLoopback - If set to false this tells the connector that it is ok to paint connections whose source and target is the same element with a connector running through the element. The default value for this is true; the connector always makes a loopback connection loop around the element rather than passing through it. + */ + jsPlumb.Connectors.StateMachine = function(params) { + var self = this, + currentPoints = null, + _sx, _sy, _tx, _ty, _controlPoint = [], + curviness = params.curviness || 10, + margin = params.margin || 5, + proximityLimit = params.proximityLimit || 80, + clockwise = params.orientation && params.orientation == "clockwise", + loopbackRadius = params.loopbackRadius || 25, + isLoopback = false, + showLoopback = params.showLoopback !== false; + + this.type = "StateMachine"; + params = params || {}; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *= 1.9; + //ensure at least one pixel width + lineWidth = lineWidth || 1; + var x = Math.min(sourcePos[0], targetPos[0]) - xo, + y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (!showLoopback || (sourceEndpoint.elementId != targetEndpoint.elementId)) { + + isLoopback = false; + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + + // now adjust for the margin + if (sourcePos[2] == 0) _sx -= margin; + if (sourcePos[2] == 1) _sx += margin; + if (sourcePos[3] == 0) _sy -= margin; + if (sourcePos[3] == 1) _sy += margin; + if (targetPos[2] == 0) _tx -= margin; + if (targetPos[2] == 1) _tx += margin; + if (targetPos[3] == 0) _ty -= margin; + if (targetPos[3] == 1) _ty += margin; + + // + // these connectors are quadratic bezier curves, having a single control point. if both anchors + // are located at 0.5 on their respective faces, the control point is set to the midpoint and you + // get a straight line. this is also the case if the two anchors are within 'proximityLimit', since + // it seems to make good aesthetic sense to do that. outside of that, the control point is positioned + // at 'curviness' pixels away along the normal to the straight line connecting the two anchors. + // + // there may be two improvements to this. firstly, we might actually support the notion of avoiding nodes + // in the UI, or at least making a good effort at doing so. if a connection would pass underneath some node, + // for example, we might increase the distance the control point is away from the midpoint in a bid to + // steer it around that node. this will work within limits, but i think those limits would also be the likely + // limits for, once again, aesthetic good sense in the layout of a chart using these connectors. + // + // the second possible change is actually two possible changes: firstly, it is possible we should gradually + // decrease the 'curviness' as the distance between the anchors decreases; start tailing it off to 0 at some + // point (which should be configurable). secondly, we might slightly increase the 'curviness' for connectors + // with respect to how far their anchor is from the center of its respective face. this could either look cool, + // or stupid, and may indeed work only in a way that is so subtle as to have been a waste of time. + // + + var _midx = (_sx + _tx) / 2, _midy = (_sy + _ty) / 2, + m2 = (-1 * _midx) / _midy, theta2 = Math.atan(m2), + dy = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.sin(theta2)), + dx = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.cos(theta2)), + segment = _segment(_sx, _sy, _tx, _ty), + distance = Math.sqrt(Math.pow(_tx - _sx, 2) + Math.pow(_ty - _sy, 2)); + + // calculate the control point. this code will be where we'll put in a rudimentary element avoidance scheme; it + // will work by extending the control point to force the curve to be, um, curvier. + _controlPoint = _findControlPoint(_midx, + _midy, + segment, + sourcePos, + targetPos, + curviness, curviness, + distance, + proximityLimit); + + + var requiredWidth = Math.max(Math.abs(_controlPoint[0] - _sx) * 3, Math.abs(_controlPoint[0] - _tx) * 3, Math.abs(_tx-_sx), 2 * lineWidth, minWidth), + requiredHeight = Math.max(Math.abs(_controlPoint[1] - _sy) * 3, Math.abs(_controlPoint[1] - _ty) * 3, Math.abs(_ty-_sy), 2 * lineWidth, minWidth); + + if (w < requiredWidth) { + var dw = requiredWidth - w; + x -= (dw / 2); + _sx += (dw / 2); + _tx += (dw / 2); + w = requiredWidth; + _controlPoint[0] += (dw / 2); + } + + if (h < requiredHeight) { + var dh = requiredHeight - h; + y -= (dh / 2); + _sy += (dh / 2); + _ty += (dh / 2); + h = requiredHeight; + _controlPoint[1] += (dh / 2); + } + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty, _controlPoint[0], _controlPoint[1] ]; + } + else { + isLoopback = true; + // a loopback connector. draw an arc from one anchor to the other. + // i guess we'll do this the same way as the others. just the control point will be a fair distance away. + var x1 = sourcePos[0], x2 = sourcePos[0], y1 = sourcePos[1] - margin, y2 = sourcePos[1] - margin, + cx = x1, cy = y1 - loopbackRadius; + + // canvas sizing stuff, to ensure the whole painted area is visible. + w = ((2 * lineWidth) + (4 * loopbackRadius)), h = ((2 * lineWidth) + (4 * loopbackRadius)); + x = cx - loopbackRadius - lineWidth - loopbackRadius, y = cy - loopbackRadius - lineWidth - loopbackRadius; + currentPoints = [ x, y, w, h, cx-x, cy-y, loopbackRadius, clockwise, x1-x, y1-y, x2-x, y2-y]; + } + + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_tx, y:_ty }, + { x:_controlPoint[0], y:_controlPoint[1] }, + { x:_controlPoint[0] + 1, y:_controlPoint[1] + 1}, + { x:_sx, y:_sy } + ]; + }; + + var _translateLocation = function(curve, location, absolute) { + if (absolute) + location = jsBezier.locationAlongCurveFrom(curve, location > 0 ? 0 : 1, location); + + return location; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location, absolute) { + if (isLoopback) { + if (absolute) { + var circumference = Math.PI * 2 * loopbackRadius; + location = location / circumference; + } + + if (location > 0 && location < 1) location = 1 - location; + +// current points are [ x, y, width, height, center x, center y, radius, clockwise, startx, starty, endx, endy ] + // so the path length is the circumference of the circle + //var len = 2 * Math.PI * currentPoints[6], + // map 'location' to an angle. 0 is PI/2 when the connector is on the top face; if we + // support other faces it will have to be calculated for each one. 1 is also PI/2. + // 0.5 is -PI/2. + var startAngle = (location * 2 * Math.PI) + (Math.PI / 2), + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + + } + else { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.pointOnCurve(c, location); + } + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location, absolute) { + if (isLoopback) { + // todo if absolute, location is a proportion of circumference + if (absolute) { + var circumference = Math.PI * 2 * loopbackRadius; + location = location / circumference; + } + + return Math.atan(location * 2 * Math.PI); + } + else { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.gradientAtPoint(c, location); + } + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance, absolute) { + if (isLoopback) { + + if (absolute) { + var circumference = Math.PI * 2 * loopbackRadius; + location = location / circumference; + } + + if (location > 0 && location < 1) location = 1 - location; + + var circumference = 2 * Math.PI * currentPoints[6], + arcSpan = distance / circumference * 2 * Math.PI, + startAngle = (location * 2 * Math.PI) - arcSpan + (Math.PI / 2), + + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + } + else { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.pointAlongCurveFrom(c, location, distance); + } + }; + + }; + + /* + * Canvas state machine renderer. + */ + jsPlumb.Connectors.canvas.StateMachine = function(params) { + params = params || {}; + var self = this, drawGuideline = params.drawGuideline || true, avoidSelector = params.avoidSelector; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.CanvasConnector.apply(this, arguments); + + + this._paint = function(dimensions) { + + if (dimensions.length == 10) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[8], dimensions[9], dimensions[6], dimensions[7]); + self.ctx.stroke(); + } + else { + // a loopback connector + self.ctx.save(); + self.ctx.beginPath(); + var startAngle = 0, // Starting point on circle + endAngle = 2 * Math.PI, // End point on circle + clockwise = dimensions[7]; // clockwise or anticlockwise + self.ctx.arc(dimensions[4],dimensions[5],dimensions[6],0, endAngle, clockwise); + self.ctx.stroke(); + self.ctx.closePath(); + self.ctx.restore(); + } + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + /* + * SVG State Machine renderer + */ + jsPlumb.Connectors.svg.StateMachine = function() { + var self = this; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.SvgConnector.apply(this, arguments); + this.getPath = function(d) { + + if (d.length == 10) + return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[8] + " " + d[9] + " " + d[6] + " " + d[7]; + else { + // loopback + return "M" + (d[8] + 4) + " " + d[9] + " A " + d[6] + " " + d[6] + " 0 1,0 " + (d[8]-4) + " " + d[9]; + } + }; + }; + + /* + * VML state machine renderer + */ + jsPlumb.Connectors.vml.StateMachine = function() { + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.VmlConnector.apply(this, arguments); + var _conv = jsPlumb.vml.convertValue; + this.getPath = function(d) { + if (d.length == 10) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + } + else { + // loopback + var left = _conv(d[8] - d[6]), + top = _conv(d[9] - (2 * d[6])), + right = left + _conv(2 * d[6]), + bottom = top + _conv(2 * d[6]), + posString = left + "," + top + "," + right + "," + bottom; + + var o = "ar " + posString + "," + _conv(d[8]) + "," + + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + " e"; + + return o; + } + }; + }; + +})(); + +/* + // now for a rudimentary avoidance scheme. TODO: how to set this in a cross-library way? + // if (avoidSelector) { + // var testLine = new Line(sourcePos[0] + _sx,sourcePos[1] + _sy,sourcePos[0] + _tx,sourcePos[1] + _ty); + // var sel = jsPlumb.getSelector(avoidSelector); + // for (var i = 0; i < sel.length; i++) { + // var id = jsPlumb.getId(sel[i]); + // if (id != sourceEndpoint.elementId && id != targetEndpoint.elementId) { + // o = jsPlumb.getOffset(id), s = jsPlumb.getSize(id); +// +// if (o && s) { +// var collision = testLine.rectIntersect(o.left,o.top,s[0],s[1]); +// if (collision) { + // set the control point to be a certain distance from the midpoint of the two points that + // the line crosses on the rectangle. + // TODO where will this 75 number come from? + // _controlX = collision[2][0] + (75 * collision[3][0]); + // / _controlY = collision[2][1] + (75 * collision[3][1]); +// } +// } + // } + // } + //} + */ \ No newline at end of file diff --git a/archive/1.3.15/jsPlumb-defaults-1.3.15-RC1.js b/archive/1.3.15/jsPlumb-defaults-1.3.15-RC1.js new file mode 100644 index 000000000..31a1ba9b3 --- /dev/null +++ b/archive/1.3.15/jsPlumb-defaults-1.3.15-RC1.js @@ -0,0 +1,1246 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.15 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the default Connectors, Endpoint and Overlay definitions. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +(function() { + + /** + * + * Helper class to consume unused mouse events by components that are DOM elements and + * are used by all of the different rendering modes. + * + */ + jsPlumb.DOMElementComponent = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + // when render mode is canvas, these functions may be called by the canvas mouse handler. + // this component is safe to pipe this stuff to /dev/null. + this.mousemove = + this.dblclick = + this.click = + this.mousedown = + this.mouseup = function(e) { }; + }; + + /** + * Class: Connectors.Straight + * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters. + */ + jsPlumb.Connectors.Straight = function() { + this.type = "Straight"; + var self = this, + currentPoints = null, + _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty, _segment, _length; + + /** + * Computes the new size and position of the canvas. + */ + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + _dx = _tx - _sx, _dy = _ty - _sy; + //_m = _dy / _dx, _m2 = -1 / _m; + _m = jsPlumbUtil.gradient({x:_sx, y:_sy}, {x:_tx, y:_ty}), _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + //_segment = jsPlumbUtil.segment({x:_sx, y:_sy}, {x:_tx, y:_ty}); + _length = Math.sqrt((_dx * _dx) + (_dy * _dy)); + + return currentPoints; + }; + + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location, absolute) { + if (location == 0 && !absolute) + return { x:_sx, y:_sy }; + else if (location == 1 && !absolute) + return { x:_tx, y:_ty }; + else { + var l = absolute ? location > 0 ? location : _length + location : location * _length; + return jsPlumbUtil.pointOnLine({x:_sx, y:_sy}, {x:_tx, y:_ty}, l); + } + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { + return _m; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + * this hands off to jsPlumbUtil to do the maths, supplying two points and the distance. + */ + this.pointAlongPathFrom = function(location, distance, absolute) { + var p = self.pointOnPath(location, absolute), + farAwayPoint = location == 1 ? { + x:_sx + ((_tx - _sx) * 10), + y:_sy + ((_sy - _ty) * 10) + } : {x:_tx, y:_ty }; + + return jsPlumbUtil.pointOnLine(p, farAwayPoint, distance); + }; + }; + + + /** + * Class:Connectors.Bezier + * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's + * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below. + */ + /** + * Function:Constructor + * + * Parameters: + * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line. + * Optional; defaults to 150. + * stub - optional value for a distance to travel from the connector's endpoint before beginning the Bezier curve. defaults to 0. + * + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + params = params || {}; + this.majorAnchor = params.curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + this.type = "Bezier"; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(sourceEndpoint), + too = targetAnchor.getOrientation(targetEndpoint), + perpendicular = soo[0] != too[0] || soo[1] == too[1], + p = [], + ma = self.majorAnchor, mi = self.minorAnchor; + + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h, _sStubX, _sStubY, _tStubX, _tStubY; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetEndpoint, sourceEndpoint, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx), minx2 = Math.min(_CP[0], _CP2[0]), minx = Math.min(minx1,minx2), + maxx1 = Math.max(_sx,_tx), maxx2 = Math.max(_CP[0], _CP2[0]), maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty), miny2 = Math.min(_CP[1], _CP2[1]), miny = Math.min(miny1,miny2), + maxy1 = Math.max(_sy,_ty), maxy2 = Math.max(_CP[1], _CP2[1]), maxy = Math.max(maxy1,maxy2); + + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, + _sx, _sy, _tx, _ty, + _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + var _translateLocation = function(curve, location, absolute) { + if (absolute) + location = jsBezier.locationAlongCurveFrom(curve, location > 0 ? 0 : 1, location); + + return location; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location, absolute) { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.pointOnCurve(c, location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location, absolute) { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.gradientAtPoint(c, location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance, absolute) { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.pointAlongCurveFrom(c, location, distance); + }; + }; + + + /** + * Class: Connectors.Flowchart + * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments. + */ + /** + * Function: Constructor + * + * Parameters: + * stub - minimum length for the stub at each end of the connector. This can be an integer, giving a value for both ends of the connections, + * or an array of two integers, giving separate values for each end. The default is an integer with value 30 (pixels). + * gap - gap to leave between the end of the connector and the element on which the endpoint resides. if you make this larger than stub then you will see some odd looking behaviour. defaults to 0 pixels. + */ + jsPlumb.Connectors.Flowchart = function(params) { + this.type = "Flowchart"; + params = params || {}; + var self = this, + stub = params.stub || params.minStubLength /* bwds compat. */ || 30, + sourceStub = jsPlumbUtil.isArray(stub) ? stub[0] : stub, + targetStub = jsPlumbUtil.isArray(stub) ? stub[1] : stub, + gap = params.gap || 0, + segments = [], + totalLength = 0, + segmentProportions = [], + segmentProportionalLengths = [], + points = [], + swapX, swapY, + maxX = -Infinity, maxY = -Infinity, + minX = Infinity, minY = Infinity, + grid = params.grid, + _gridClamp = function(n, g) { var e = n % g, f = Math.floor(n / g), inc = e > (g / 2) ? 1 : 0; return (f + inc) * g; }, + clampToGrid = function(x, y, dontClampX, dontClampY) { + return [ + dontClampX || grid == null ? x : _gridClamp(x, grid[0]), + dontClampY || grid == null ? y : _gridClamp(y, grid[1]) + ]; + }, + /** + * recalculates the points at which the segments begin and end, proportional to the total length travelled + * by all the segments that constitute the connector. we use this to help with pointOnPath calculations. + */ + updateSegmentProportions = function(startX, startY, endX, endY) { + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segments[i][5] / totalLength; + segmentProportions[i] = [curLoc, (curLoc += (segments[i][5] / totalLength)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty/*, doGridX, doGridY*/) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0], + ly = segments.length == 0 ? sy : segments[segments.length - 1][1], + m = x == lx ? Infinity : 0; + /*, + gridded = clampToGrid(x, y), + doGridX = true, + doGridY = true; + + // grid experiment. TODO: have two more params that indicate whether or not to lock to a grid in each + // axis. the reason for this is that anchor points wont always be located on the grid, so until a connector + // emanating from that anchor has turned a right angle, we can't actually clamp it to a grid for that axis. + // so if a line came out horizontally heading left, then it will probably not be clamped in the y axis, but + // we can choose to clamp its first corner in the x axis. the same principle goes for the target anchor. + //if (segments.length == 0) { + console.log("this is the first segment...if sx == x then do not do grid in X.") + doGridX = !(sx == x) && !(tx == x); + doGridY = !(sy == y) && !(ty == y); + x = doGridX ? gridded[0] : x; + y = doGridY ? gridded[1] : y; + */ + + var l = Math.abs(x == lx ? y - ly : x - lx); + + segments.push([x, y, lx, ly, m, l]); + totalLength += l; + + maxX = Math.max(maxX, x); + maxY = Math.max(maxY, y); + minX = Math.min(minX, x); + minY = Math.min(minY, y); + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment + * that contains the point which is 'location' distance along the entire path, where + * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths + * are made up of a list of segments, each of which contributes some fraction to + * the total length. + * From 1.3.10 this also supports the 'absolute' property, which lets us specify a location + * as the absolute distance in pixels, rather than a proportion of the total path. + */ + findSegmentForLocation = function(location, absolute) { + if (absolute) { + location = location > 0 ? location / totalLength : (totalLength + location) / totalLength; + } + + var idx = segmentProportions.length - 1, inSegmentProportion = 1; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, + sourceAnchor, targetAnchor, lineWidth, minWidth, sourceInfo, targetInfo) { + segments = []; + segmentProportions = []; + totalLength = 0; + segmentProportionalLengths = []; + maxX = maxY = -Infinity; + minX = minY = Infinity; + + self.lineWidth = lineWidth; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + sourceOffx = (lw / 2) + (sourceStub + targetStub), + targetOffx = (lw / 2) + (targetStub + sourceStub), + sourceOffy = (lw / 2) + (sourceStub + targetStub), + targetOffy = (lw / 2) + (targetStub + sourceStub), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(sourceEndpoint), + to = targetAnchor.orientation || targetAnchor.getOrientation(targetEndpoint), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + sourceOffx + targetOffx, + h = Math.abs(targetPos[1] - sourcePos[1]) + sourceOffy + targetOffy; + + // if either anchor does not have an orientation set, we derive one from their relative + // positions. we fix the axis to be the one in which the two elements are further apart, and + // point each anchor at the other element. this is also used when dragging a new connection. + if (so[0] == 0 && so[1] == 0 || to[0] == 0 && to[1] == 0) { + var index = w > h ? 0 : 1, oIndex = [1,0][index]; + so = []; to = []; + so[index] = sourcePos[index] > targetPos[index] ? -1 : 1; + to[index] = sourcePos[index] > targetPos[index] ? 1 : -1; + so[oIndex] = 0; + to[oIndex] = 0; + } + + /* + this code is unexplained and causes paint errors with continuous anchors sometimes. + commenting it out until i can get to the bottom of it. + if (w < minWidth) { + sourceOffx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + sourceOffy += (minWidth - h) / 2; + h = minWidth; + } + */ + + var sx = swapX ? (w - targetOffx) +( gap * so[0]) : sourceOffx + (gap * so[0]), + sy = swapY ? (h - targetOffy) + (gap * so[1]) : sourceOffy + (gap * so[1]), + tx = swapX ? sourceOffx + (gap * to[0]) : (w - targetOffx) + (gap * to[0]), + ty = swapY ? sourceOffy + (gap * to[1]) : (h - targetOffy) + (gap * to[1]), + startStubX = sx + (so[0] * sourceStub), + startStubY = sy + (so[1] * sourceStub), + endStubX = tx + (to[0] * targetStub), + endStubY = ty + (to[1] * targetStub), + isXGreaterThanStubTimes2 = Math.abs(sx - tx) > (sourceStub + targetStub), + isYGreaterThanStubTimes2 = Math.abs(sy - ty) > (sourceStub + targetStub), + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2), + oProduct = ((so[0] * to[0]) + (so[1] * to[1])), + opposite = oProduct == -1, + perpendicular = oProduct == 0, + orthogonal = oProduct == 1; + + x -= sourceOffx; y -= sourceOffy; + points = [x, y, w, h, sx, sy, tx, ty]; + var extraPoints = []; + + var sourceAxis = so[0] == 0 ? "y" : "x", + anchorOrientation = opposite ? "opposite" : orthogonal ? "orthogonal" : "perpendicular", + segment = jsPlumbUtil.segment([sx, sy], [tx, ty]), + flipSourceSegments = so[sourceAxis == "x" ? 0 : 1] == -1, + flipSegments = { + "x":[null, 4, 3, 2, 1], + "y":[null, 2, 1, 4, 3] + } + + if (flipSourceSegments) + segment = flipSegments[sourceAxis][segment]; + + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + var findClearedLine = function(start, mult, anchorPos, dimension) { + return start + (mult * (( 1 - anchorPos) * dimension) + Math.max(sourceStub, targetStub)); + //mx = so[0] == 0 ? startStubX + ((1 - sourceAnchor.x) * sourceInfo.width) + stub : startStubX, + }, + + lineCalculators = { + oppositex : function() { + if (sourceEndpoint.elementId == targetEndpoint.elementId) { + var _y = startStubY + ((1 - sourceAnchor.y) * sourceInfo.height) + Math.max(sourceStub, targetStub); + return [ [ startStubX, _y ], [ endStubX, _y ]]; + } + else if (isXGreaterThanStubTimes2 && (segment == 1 || segment == 2)) { + return [[ midx, sy ], [ midx, ty ]]; + } + else { + return [[ startStubX, midy ], [endStubX, midy ]]; + } + }, + orthogonalx : function() { + if (segment == 1 || segment == 2) { + return [ [ endStubX, startStubY ]]; + } + else { + return [ [ startStubX, endStubY ]]; + } + }, + perpendicularx : function() { + var my = (ty + sy) / 2; + if ((segment == 1 && to[1] == 1) || (segment == 2 && to[1] == -1)) { + if (Math.abs(tx - sx) > Math.max(sourceStub, targetStub)) + return [ [endStubX, startStubY ]]; + else + return [ [startStubX, startStubY ], [ startStubX, my ], [ endStubX, my ]]; + } + else if ((segment == 3 && to[1] == -1) || (segment == 4 && to[1] == 1)) { + return [ [ startStubX, my ], [ endStubX, my ]]; + } + else if ((segment == 3 && to[1] == 1) || (segment == 4 && to[1] == -1)) { + return [ [ startStubX, endStubY ]]; + } + else if ((segment == 1 && to[1] == -1) || (segment == 2 && to[1] == 1)) { + if (Math.abs(tx - sx) > Math.max(sourceStub, targetStub)) + return [ [ midx, startStubY ], [ midx, endStubY ]]; + else + return [ [ startStubX, endStubY ]]; + } + }, + oppositey : function() { + if (sourceEndpoint.elementId == targetEndpoint.elementId) { + var _x = startStubX + ((1 - sourceAnchor.x) * sourceInfo.width) + Math.max(sourceStub, targetStub); + return [ [ _x, startStubY ], [ _x, endStubY ]]; + } + else if (isYGreaterThanStubTimes2 && (segment == 2 || segment == 3)) { + return [[ sx, midy ], [ tx, midy ]]; + } + else { + return [[ midx, startStubY ], [midx, endStubY ]]; + } + }, + orthogonaly : function() { + if (segment == 2 || segment == 3) { + return [ [ startStubX, endStubY ]]; + } + else { + return [ [ endStubX, startStubY ]]; + } + }, + perpendiculary : function() { + var mx = (tx + sx) / 2; + if ((segment == 2 && to[0] == -1) || (segment == 3 && to[0] == 1)) { + if (Math.abs(tx - sx) > Math.max(sourceStub, targetStub)) + return [ [startStubX, endStubY ]]; + else + return [ [startStubX, midy ], [ endStubX, midy ]]; + } + else if ((segment == 1 && to[0] == -1) || (segment == 4 && to[0] == 1)) { + var mx = (tx + sx) / 2; + return [ [ mx, startStubY ], [ mx, endStubY ]]; + } + else if ((segment == 1 && to[0] == 1) || (segment == 4 && to[0] == -1)) { + return [ [ endStubX, startStubY ]]; + } + else if ((segment == 2 && to[0] == 1) || (segment == 3 && to[0] == -1)) { + if (Math.abs(ty - sy) > Math.max(sourceStub, targetStub)) + return [ [ startStubX, midy ], [ endStubX, midy ]]; + else + return [ [ endStubX, startStubY ]]; + } + } + }; + + var p = lineCalculators[anchorOrientation + sourceAxis](); + if (p) { + for (var i = 0; i < p.length; i++) { + addSegment(p[i][0], p[i][1], sx, sy, tx, ty); + } + } + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentProportions(sx, sy, tx, ty); + + // adjust the max values of the canvas if we have a value that is larger than what we previously set. + // + if (maxY > points[3]) points[3] = maxY + (lineWidth * 2); + if (maxX > points[2]) points[2] = maxX + (lineWidth * 2); + + return points; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location, absolute) { + return self.pointAlongPathFrom(location, 0, absolute); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location, absolute) { + return segments[findSegmentForLocation(location, absolute)["index"]][4]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance, absolute) { + var s = findSegmentForLocation(location, absolute), seg = s.segment, p = s.proportion, sl = segments[s.index][5], m = segments[s.index][4]; + var e = { + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + return e; + }; + }; + + // ********************************* END OF CONNECTOR TYPES ******************************************************************* + + // ********************************* ENDPOINT TYPES ******************************************************************* + + /** + * Class: Endpoints.Dot + * A round endpoint, with default radius 10 pixels. + */ + + /** + * Function: Constructor + * + * Parameters: + * + * radius - radius of the endpoint. defaults to 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + this.type = "Dot"; + var self = this; + params = params || {}; + this.radius = params.radius || 10; + this.defaultOffset = 0.5 * this.radius; + this.defaultInnerRadius = this.radius / 3; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var r = endpointStyle.radius || self.radius, + x = anchorPoint[0] - r, + y = anchorPoint[1] - r; + return [ x, y, r * 2, r * 2, r ]; + }; + }; + + /** + * Class: Endpoints.Rectangle + * A Rectangular Endpoint, with default size 20x20. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the endpoint. defaults to 20 pixels. + * height - height of the endpoint. defaults to 20 pixels. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + this.type = "Rectangle"; + var self = this; + params = params || {}; + this.width = params.width || 20; + this.height = params.height || 20; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height]; + }; + }; + + + var DOMElementEndpoint = function(params) { + jsPlumb.DOMElementComponent.apply(this, arguments); + var self = this; + + var displayElements = [ ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + }; + /** + * Class: Endpoints.Image + * Draws an image as the Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * src - location of the image to use. + */ + jsPlumb.Endpoints.Image = function(params) { + + this.type = "Image"; + DOMElementEndpoint.apply(this, arguments); + + var self = this, + initialized = false, + deleted = false, + widthToUse = params.width, + heightToUse = params.height, + _onload = null, + _endpoint = params.endpoint; + + this.img = new Image(); + self.ready = false; + + this.img.onload = function() { + self.ready = true; + widthToUse = widthToUse || self.img.width; + heightToUse = heightToUse || self.img.height; + if (_onload) { + _onload(self); + } + }; + + /* + Function: setImage + Sets the Image to use in this Endpoint. + + Parameters: + img - may be a URL or an Image object + onload - optional; a callback to execute once the image has loaded. + */ + _endpoint.setImage = function(img, onload) { + var s = img.constructor == String ? img : img.src; + _onload = onload; + self.img.src = img; + + if (self.canvas != null) + self.canvas.setAttribute("src", img); + }; + + _endpoint.setImage(params.src || params.url, params.onload); + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + self.anchorPoint = anchorPoint; + if (self.ready) return [anchorPoint[0] - widthToUse / 2, anchorPoint[1] - heightToUse / 2, + widthToUse, heightToUse]; + else return [0,0,0,0]; + }; + + self.canvas = document.createElement("img"), initialized = false; + self.canvas.style["margin"] = 0; + self.canvas.style["padding"] = 0; + self.canvas.style["outline"] = 0; + self.canvas.style["position"] = "absolute"; + var clazz = params.cssClass ? " " + params.cssClass : ""; + self.canvas.className = jsPlumb.endpointClass + clazz; + if (widthToUse) self.canvas.setAttribute("width", widthToUse); + if (heightToUse) self.canvas.setAttribute("height", heightToUse); + jsPlumb.appendElement(self.canvas, params.parent); + self.attachListeners(self.canvas, self); + + self.cleanup = function() { + deleted = true; + }; + + var actuallyPaint = function(d, style, anchor) { + if (!deleted) { + if (!initialized) { + self.canvas.setAttribute("src", self.img.src); + self.appendDisplayElement(self.canvas); + initialized = true; + } + var x = self.anchorPoint[0] - (widthToUse / 2), + y = self.anchorPoint[1] - (heightToUse / 2); + jsPlumb.sizeCanvas(self.canvas, x, y, widthToUse, heightToUse); + } + }; + + this.paint = function(d, style, anchor) { + if (self.ready) { + actuallyPaint(d, style, anchor); + } + else { + window.setTimeout(function() { + self.paint(d, style, anchor); + }, 200); + } + }; + }; + + /** + * Class: Endpoints.Blank + * An Endpoint that paints nothing (visible) on the screen. Supports cssClass and hoverClass parameters like all Endpoints. + */ + jsPlumb.Endpoints.Blank = function(params) { + var self = this; + this.type = "Blank"; + DOMElementEndpoint.apply(this, arguments); + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + return [anchorPoint[0], anchorPoint[1],10,0]; + }; + + self.canvas = document.createElement("div"); + self.canvas.style.display = "block"; + self.canvas.style.width = "1px"; + self.canvas.style.height = "1px"; + self.canvas.style.background = "transparent"; + self.canvas.style.position = "absolute"; + self.canvas.className = self._jsPlumb.endpointClass; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + }; + }; + + /** + * Class: Endpoints.Triangle + * A triangular Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the triangle's base. defaults to 55 pixels. + * height - height of the triangle from base to apex. defaults to 55 pixels. + */ + jsPlumb.Endpoints.Triangle = function(params) { + this.type = "Triangle"; + params = params || { }; + params.width = params.width || 55; + params.height = params.height || 55; + this.width = params.width; + this.height = params.height; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height ]; + }; + }; +// ********************************* END OF ENDPOINT TYPES ******************************************************************* + + +// ********************************* OVERLAY DEFINITIONS *********************************************************************** + + var AbstractOverlay = function(params) { + var visible = true, self = this; + this.isAppendedAtTopLevel = true; + this.component = params.component; + this.loc = params.location == null ? 0.5 : params.location; + this.endpointLoc = params.endpointLocation == null ? [ 0.5, 0.5] : params.endpointLocation; + this.setVisible = function(val) { + visible = val; + self.component.repaint(); + }; + this.isVisible = function() { return visible; }; + this.hide = function() { self.setVisible(false); }; + this.show = function() { self.setVisible(true); }; + + this.incrementLocation = function(amount) { + self.loc += amount; + self.component.repaint(); + }; + this.setLocation = function(l) { + self.loc = l; + self.component.repaint(); + }; + this.getLocation = function() { + return self.loc; + }; + }; + + + /** + * Class: Overlays.Arrow + * + * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length + * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction + * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line + * across the tail. + */ + /** + * Function: Constructor + * + * Parameters: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + this.type = "Arrow"; + AbstractOverlay.apply(this, arguments); + this.isAppendedAtTopLevel = false; + params = params || {}; + var self = this; + + this.length = params.length || 20; + this.width = params.width || 20; + this.id = params.id; + var direction = (params.direction || 1) < 0 ? -1 : 1, + paintStyle = params.paintStyle || { lineWidth:1 }, + // how far along the arrow the lines folding back in come to. default is 62.3%. + foldback = params.foldback || 0.623; + + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.cleanup = function() { }; // nothing to clean up for Arrows + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var hxy, mid, txy, tail, cxy; + if (connector.pointAlongPathFrom) { + + if (jsPlumbUtil.isString(self.loc) || self.loc > 1 || self.loc < 0) { + var l = parseInt(self.loc); + hxy = connector.pointAlongPathFrom(l, direction * self.length / 2, true), + mid = connector.pointOnPath(l, true), + txy = jsPlumbUtil.pointOnLine(hxy, mid, self.length); + } + else if (self.loc == 1) { + hxy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, -1); + txy = jsPlumbUtil.pointOnLine(hxy, mid, self.length); + } + else if (self.loc == 0) { + txy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, 1); + hxy = jsPlumbUtil.pointOnLine(txy, mid, self.length); + } + else { + hxy = connector.pointAlongPathFrom(self.loc, direction * self.length / 2), + mid = connector.pointOnPath(self.loc), + txy = jsPlumbUtil.pointOnLine(hxy, mid, self.length); + } + + tail = jsPlumbUtil.perpendicularLineTo(hxy, txy, self.width); + cxy = jsPlumbUtil.pointOnLine(hxy, txy, foldback * self.length); + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x), + maxx = Math.max(hxy.x, tail[0].x, tail[1].x), + miny = Math.min(hxy.y, tail[0].y, tail[1].y), + maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + var d = { hxy:hxy, tail:tail, cxy:cxy }, + strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle, + fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle, + lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth; + + self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions); + + return [ minx, maxx, miny, maxy]; + } + else return [0,0,0,0]; + }; + }; + + /** + * Class: Overlays.PlainArrow + * + * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "PlainArrow"; + }; + + /** + * Class: Overlays.Diamond + * + * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40, + p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "Diamond"; + }; + + + // abstract superclass for overlays that add an element to the DOM. + var AbstractDOMOverlay = function(params) { + jsPlumb.DOMElementComponent.apply(this, arguments); + AbstractOverlay.apply(this, arguments); + + var self = this, initialised = false; + params = params || {}; + this.id = params.id; + var div; + + var makeDiv = function() { + div = params.create(params.component); + div = jsPlumb.CurrentLibrary.getDOMElement(div); + div.style["position"] = "absolute"; + var clazz = params["_jsPlumb"].overlayClass + " " + + (self.cssClass ? self.cssClass : + params.cssClass ? params.cssClass : ""); + div.className = clazz; + jsPlumb.appendElement(div, params.component.parent); + params["_jsPlumb"].getId(div); + self.attachListeners(div, self); + self.canvas = div; + }; + + this.getElement = function() { + if (div == null) { + makeDiv(); + } + return div; + }; + + this.getDimensions = function() { + return jsPlumb.CurrentLibrary.getSize(jsPlumb.CurrentLibrary.getElementObject(self.getElement())); + }; + + var cachedDimensions = null, + _getDimensions = function(component) { + if (cachedDimensions == null) + cachedDimensions = self.getDimensions(); + return cachedDimensions; + }; + + /* + * Function: clearCachedDimensions + * Clears the cached dimensions for the label. As a performance enhancement, label dimensions are + * cached from 1.3.12 onwards. The cache is cleared when you change the label text, of course, but + * there are other reasons why the text dimensions might change - if you make a change through CSS, for + * example, you might change the font size. in that case you should explicitly call this method. + */ + this.clearCachedDimensions = function() { + cachedDimensions = null; + }; + + this.computeMaxSize = function() { + var td = _getDimensions(); + return Math.max(td[0], td[1]); + }; + + //override setVisible + var osv = self.setVisible; + self.setVisible = function(state) { + osv(state); // call superclass + div.style.display = state ? "block" : "none"; + }; + + this.cleanup = function() { + if (div != null) jsPlumb.CurrentLibrary.removeElement(div); + }; + + this.paint = function(component, d, componentDimensions) { + if (!initialised) { + self.getElement(); + component.appendDisplayElement(div); + self.attachListeners(div, component); + initialised = true; + } + div.style.left = (componentDimensions[0] + d.minx) + "px"; + div.style.top = (componentDimensions[1] + d.miny) + "px"; + }; + + this.draw = function(component, currentConnectionPaintStyle, componentDimensions) { + var td = _getDimensions(); + if (td != null && td.length == 2) { + var cxy = {x:0,y:0}; + if (component.pointOnPath) { + var loc = self.loc, absolute = false; + if (jsPlumbUtil.isString(self.loc) || self.loc < 0 || self.loc > 1) { + loc = parseInt(self.loc); + absolute = true; + } + cxy = component.pointOnPath(loc, absolute); // a connection + } + else { + var locToUse = self.loc.constructor == Array ? self.loc : self.endpointLoc; + cxy = { x:locToUse[0] * componentDimensions[2], + y:locToUse[1] * componentDimensions[3] }; + } + + minx = cxy.x - (td[0] / 2), + miny = cxy.y - (td[1] / 2); + self.paint(component, { minx:minx, miny:miny, td:td, cxy:cxy }, componentDimensions); + return [minx, minx + td[0], miny, miny + td[1]]; + } + else return [0,0,0,0]; + }; + + this.reattachListeners = function(connector) { + if (div) { + self.reattachListenersForElement(div, self, connector); + } + }; + + }; + + /** + * Class: Overlays.Custom + * A Custom overlay. You supply a 'create' function which returns some DOM element, and jsPlumb positions it. + * The 'create' function is passed a Connection or Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * create - function for jsPlumb to call that returns a DOM element. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * id - optional id to use for later retrieval of this overlay. + * + */ + jsPlumb.Overlays.Custom = function(params) { + this.type = "Custom"; + AbstractDOMOverlay.apply(this, arguments); + }; + + /** + * Class: Overlays.Label + * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb + * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter + * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it + * puts on the Label's 'style' attribute, so the end result is the same. + */ + /** + * Function: Constructor + * + * Parameters: + * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes + * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle. + * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * id - optional id to use for later retrieval of this overlay. + * + */ + jsPlumb.Overlays.Label = function(params) { + var self = this; + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.cssClass = this.labelStyle != null ? this.labelStyle.cssClass : null; + params.create = function() { + return document.createElement("div"); + }; + jsPlumb.Overlays.Custom.apply(this, arguments); + this.type = "Label"; + + var label = params.label || "", + self = this, + labelText = null; + + /* + * Function: setLabel + * sets the label's, um, label. you would think i'd call this function + * 'setText', but you can pass either a Function or a String to this, so + * it makes more sense as 'setLabel'. This uses innerHTML on the label div, so keep + * that in mind if you need escaped HTML. + */ + this.setLabel = function(l) { + label = l; + labelText = null; + self.clearCachedDimensions(); + _update(); + self.component.repaint(); + }; + + var _update = function() { + if (typeof label == "function") { + var lt = label(self); + self.getElement().innerHTML = lt.replace(/\r\n/g, "
"); + } + else { + if (labelText == null) { + labelText = label; + self.getElement().innerHTML = labelText.replace(/\r\n/g, "
"); + } + } + }; + + this.getLabel = function() { + return label; + }; + + var superGD = this.getDimensions; + this.getDimensions = function() { + _update(); + return superGD(); + }; + + }; + + + // ********************************* END OF OVERLAY DEFINITIONS *********************************************************************** + +})(); \ No newline at end of file diff --git a/archive/1.3.15/jsPlumb-dom-adapter-1.3.15-RC1.js b/archive/1.3.15/jsPlumb-dom-adapter-1.3.15-RC1.js new file mode 100644 index 000000000..738fed10f --- /dev/null +++ b/archive/1.3.15/jsPlumb-dom-adapter-1.3.15-RC1.js @@ -0,0 +1,190 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.15 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the base functionality for DOM type adapters. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ +;(function() { + + var canvasAvailable = !!document.createElement('canvas').getContext, + svgAvailable = !!window.SVGAngle || document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"), + // http://stackoverflow.com/questions/654112/how-do-you-detect-support-for-vml-or-svg-in-a-browser + vmlAvailable = function() { + if (vmlAvailable.vml == undefined) { + var a = document.body.appendChild(document.createElement('div')); + a.innerHTML = ''; + var b = a.firstChild; + b.style.behavior = "url(#default#VML)"; + vmlAvailable.vml = b ? typeof b.adj == "object": true; + a.parentNode.removeChild(a); + } + return vmlAvailable.vml; + }; + + /** + Manages dragging for some instance of jsPlumb. + */ + var DragManager = function(_currentInstance) { + var _draggables = {}, _dlist = [], _delements = {}, _elementsWithEndpoints = {}; + + /** + register some element as draggable. right now the drag init stuff is done elsewhere, and it is + possible that will continue to be the case. + */ + this.register = function(el) { + var jpcl = jsPlumb.CurrentLibrary; + el = jpcl.getElementObject(el); + var id = _currentInstance.getId(el), + domEl = jpcl.getDOMElement(el), + parentOffset = jpcl.getOffset(el); + + if (!_draggables[id]) { + _draggables[id] = el; + _dlist.push(el); + _delements[id] = {}; + } + + // look for child elements that have endpoints and register them against this draggable. + var _oneLevel = function(p, startOffset) { + if (p) { + for (var i = 0; i < p.childNodes.length; i++) { + if (p.childNodes[i].nodeType != 3) { + var cEl = jpcl.getElementObject(p.childNodes[i]), + cid = _currentInstance.getId(cEl, null, true); + if (cid && _elementsWithEndpoints[cid] && _elementsWithEndpoints[cid] > 0) { + var cOff = jpcl.getOffset(cEl); + _delements[id][cid] = { + id:cid, + offset:{ + left:cOff.left - parentOffset.left, + top:cOff.top - parentOffset.top + } + }; + } + _oneLevel(p.childNodes[i]); + } + } + } + }; + + _oneLevel(domEl); + }; + + /** + notification that an endpoint was added to the given el. we go up from that el's parent + node, looking for a parent that has been registered as a draggable. if we find one, we add this + el to that parent's list of elements to update on drag (if it is not there already) + */ + this.endpointAdded = function(el) { + var jpcl = jsPlumb.CurrentLibrary, b = document.body, id = _currentInstance.getId(el), c = jpcl.getDOMElement(el), + p = c.parentNode, done = p == b; + + _elementsWithEndpoints[id] = _elementsWithEndpoints[id] ? _elementsWithEndpoints[id] + 1 : 1; + + while (p != b) { + var pid = _currentInstance.getId(p, null, true); + if (pid && _draggables[pid]) { + var idx = -1, pEl = jpcl.getElementObject(p), pLoc = jpcl.getOffset(pEl); + + if (_delements[pid][id] == null) { + var cLoc = jsPlumb.CurrentLibrary.getOffset(el); + _delements[pid][id] = { + id:id, + offset:{ + left:cLoc.left - pLoc.left, + top:cLoc.top - pLoc.top + } + }; + } + break; + } + p = p.parentNode; + } + }; + + this.endpointDeleted = function(endpoint) { + if (_elementsWithEndpoints[endpoint.elementId]) { + _elementsWithEndpoints[endpoint.elementId]--; + if (_elementsWithEndpoints[endpoint.elementId] <= 0) { + for (var i in _delements) { + delete _delements[i][endpoint.elementId]; + } + } + } + }; + + this.getElementsForDraggable = function(id) { + return _delements[id]; + }; + + this.reset = function() { + _draggables = {}; + _dlist = []; + _delements = {}; + _elementsWithEndpoints = {}; + }; + + }; + + // for those browsers that dont have it. they still don't have it! but at least they won't crash. + if (!window.console) + window.console = { time:function(){}, timeEnd:function(){}, group:function(){}, groupEnd:function(){}, log:function(){} }; + + window.jsPlumbAdapter = { + + headless:false, + + appendToRoot : function(node) { + document.body.appendChild(node); + }, + getRenderModes : function() { + return [ "canvas", "svg", "vml" ] + }, + isRenderModeAvailable : function(m) { + return { + "canvas":canvasAvailable, + "svg":svgAvailable, + "vml":vmlAvailable() + }[m]; + }, + getDragManager : function(_jsPlumb) { + return new DragManager(_jsPlumb); + }, + setRenderMode : function(mode) { + var renderMode; + + if (mode) { + mode = mode.toLowerCase(); + + var canvasAvailable = this.isRenderModeAvailable("canvas"), + svgAvailable = this.isRenderModeAvailable("svg"), + vmlAvailable = this.isRenderModeAvailable("vml"); + + //if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); + // now test we actually have the capability to do this. + if (mode === "svg") { + if (svgAvailable) renderMode = "svg" + else if (canvasAvailable) renderMode = "canvas" + else if (vmlAvailable) renderMode = "vml" + } + else if (mode === "canvas" && canvasAvailable) renderMode = "canvas"; + else if (vmlAvailable) renderMode = "vml"; + } + + return renderMode; + } + }; + +})(); \ No newline at end of file diff --git a/archive/1.3.15/jsPlumb-drag-1.3.15-RC1.js b/archive/1.3.15/jsPlumb-drag-1.3.15-RC1.js new file mode 100644 index 000000000..d71db029e --- /dev/null +++ b/archive/1.3.15/jsPlumb-drag-1.3.15-RC1.js @@ -0,0 +1,61 @@ +/* + * this is experimental and probably will not be used. solutions exist for most libraries. but of course if + * i want to support multiple scopes at some stage then i will have to do dragging inside jsPlumb. + */ +;(function() { + + window.jsPlumbDrag = function(_jsPlumb) { + + var ta = new TouchAdapter(); + + this.draggable = function(selector) { + var el, elId, da = [], elo, d = false, + isInSelector = function(el) { + if (typeof selector == "string") + return selector === _jsPlumb.getId(el); + + for (var i = 0; i < selector.length; i++) { + var _sel = jsPlumb.CurrentLibrary.getDOMElement(selector[i]); + if (_sel == el) return true; + } + return false; + }; + + ta.bind(document, "mousedown", function(e) { + var target = e.target || e.srcElement; + if (isInSelector(target)) { + el = jsPlumb.CurrentLibrary.getElementObject(target); + elId = _jsPlumb.getId(el); + elo = jsPlumb.CurrentLibrary.getOffset(el); + da = [e.pageX, e.pageY]; + d = true; + } + }); + + ta.bind(document, "mousemove", function(e) { + if (d) { + var dx = e.pageX - da[0], + dy = e.pageY - da[1]; + + jsPlumb.CurrentLibrary.setOffset(el, { + left:elo.left + dx, + top:elo.top + dy + }); + _jsPlumb.repaint(elId); + e.preventDefault(); + e.stopPropagation(); + } + }); + ta.bind(document, "mouseup", function(e) { + el = null; + d = false; + }); + }; + + var isIOS = ((/iphone|ipad/gi).test(navigator.appVersion)); + if (isIOS) + _jsPlumb.draggable = this.draggable; + + }; + +})(); \ No newline at end of file diff --git a/archive/1.3.15/jsPlumb-overlays-guidelines-1.3.15-RC1.js b/archive/1.3.15/jsPlumb-overlays-guidelines-1.3.15-RC1.js new file mode 100644 index 000000000..72b63b009 --- /dev/null +++ b/archive/1.3.15/jsPlumb-overlays-guidelines-1.3.15-RC1.js @@ -0,0 +1,73 @@ + // this is really just a test overlay, so its undocumented and doesnt take any parameters. but i was loth to delete it. + jsPlumb.Overlays.GuideLines = function() { + var self = this; + self.length = 50; + self.lineWidth = 5; + this.type = "GuideLines"; + AbstractOverlay.apply(this, arguments); + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var head = connector.pointAlongPathFrom(self.loc, self.length / 2), + mid = connector.pointOnPath(self.loc), + tail = jsPlumbUtil.pointOnLine(head, mid, self.length), + tailLine = jsPlumbUtil.perpendicularLineTo(head, tail, 40), + headLine = jsPlumbUtil.perpendicularLineTo(tail, head, 20); + + self.paint(connector, [head, tail, tailLine, headLine], self.lineWidth, "red", null, connectorDimensions); + + return [Math.min(head.x, tail.x), Math.min(head.y, tail.y), Math.max(head.x, tail.x), Math.max(head.y,tail.y)]; + }; + + this.computeMaxSize = function() { return 50; }; + + this.cleanup = function() { }; // nothing to clean up for GuideLines + }; + +// a test + jsPlumb.Overlays.svg.GuideLines = function() { + var path = null, self = this, path2 = null, p1_1, p1_2; + jsPlumb.Overlays.GuideLines.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + + p1_1 = _node("path"); + connector.svg.appendChild(p1_1); + self.attachListeners(p1_1, connector); + self.attachListeners(p1_1, self); + + p1_2 = _node("path"); + connector.svg.appendChild(p1_2); + self.attachListeners(p1_2, connector); + self.attachListeners(p1_2, self); + + } + + _attr(path, { + "d" : makePath(d[0], d[1]), + stroke : "red", + fill : null + }); + + _attr(p1_1, { + "d" : makePath(d[2][0], d[2][1]), + stroke : "blue", + fill : null + }); + + _attr(p1_2, { + "d" : makePath(d[3][0], d[3][1]), + stroke : "green", + fill : null + }); + }; + + var makePath = function(d1, d2) { + return "M " + d1.x + "," + d1.y + + " L" + d2.x + "," + d2.y; + }; + }; \ No newline at end of file diff --git a/archive/1.3.15/jsPlumb-renderers-canvas-1.3.15-RC1.js b/archive/1.3.15/jsPlumb-renderers-canvas-1.3.15-RC1.js new file mode 100644 index 000000000..63c57668f --- /dev/null +++ b/archive/1.3.15/jsPlumb-renderers-canvas-1.3.15-RC1.js @@ -0,0 +1,510 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.15 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the HTML5 canvas renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + +// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS ******************************************************************* + + // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. + var _connectionBeingDragged = null, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, + _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }; + + /* + * Class:CanvasMouseAdapter + * Provides support for mouse events on canvases. + */ + var CanvasMouseAdapter = function() { + var self = this; + self.overlayPlacements = []; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + jsPlumbUtil.EventGenerator.apply(this, arguments); + /** + * returns whether or not the given event is ojver a painted area of the canvas. + */ + this._over = function(e) { + var o = _getOffset(_getElementObject(self.canvas)), + pageXY = _pageXY(e), + x = pageXY[0] - o.left, y = pageXY[1] - o.top; + if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + return false; + }; + + var _mouseover = false, _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false, + _nullSafeHasClass = function(el, clazz) { + return el != null && _hasClass(el, clazz); + }; + this.mousemove = function(e) { + var pageXY = _pageXY(e), clientXY = _clientXY(e), + ee = document.elementFromPoint(clientXY[0], clientXY[1]), + eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); + var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); + if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.fire("mouseenter", self, e); + return true; + } + // TODO here there is a remote chance that the overlay the mouse moved onto + // is actually not an overlay for the current component. a more thorough check would + // be to ensure the overlay belonged to the current component. + else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { + _mouseover = false; + self.fire("mouseexit", self, e); + } + self.fire("mousemove", self, e); + }; + + this.click = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); + self.fire("mousedown", self, e); + } + }; + + this.mouseup = function(e) { + _mouseDown = false; + self.fire("mouseup", self, e); + }; + + this.contextmenu = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("contextmenu", self, e); + _mouseWasDown = false; + }; + }; + + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + params["_jsPlumb"].appendElement(canvas, params.parent); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + params["_jsPlumb"].getId(canvas, params.uuid); + if (params.tooltip) canvas.setAttribute("title", params.tooltip); + + return canvas; + }; + + var CanvasComponent = function(params) { + CanvasMouseAdapter.apply(this, arguments); + + var displayElements = [ ]; + this.getDisplayElements = function() { return displayElements; }; + this.appendDisplayElement = function(el) { displayElements.push(el); }; + }; + + /** + * Class:CanvasConnector + * Superclass for Canvas Connector renderers. + */ + var CanvasConnector = jsPlumb.CanvasConnector = function(params) { + + CanvasComponent.apply(this, arguments); + + var _paintOneStyle = function(dim, aStyle) { + self.ctx.save(); + jsPlumb.extend(self.ctx, aStyle); + if (aStyle.gradient) { + var g = self.createGradient(dim, self.ctx); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + self.ctx.strokeStyle = g; + } + self._paint(dim, aStyle); + self.ctx.restore(); + }; + + var self = this, + clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:params.tooltip + }); + self.ctx = self.canvas.getContext("2d"); + + self.appendDisplayElement(self.canvas); + + self.paint = function(dim, style) { + if (style != null) { + jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); + if (self.getZIndex()) + self.canvas.style.zIndex = self.getZIndex(); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + _paintOneStyle(dim, outlineStyle); + } + _paintOneStyle(dim, style); + } + }; + }; + + /** + * Class:CanvasEndpoint + * Superclass for Canvas Endpoint renderers. + */ + var CanvasEndpoint = function(params) { + var self = this; + CanvasComponent.apply(this, arguments); + var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""), + canvasParams = { + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:self.tooltip + }; + self.canvas = _newCanvas(canvasParams); + self.ctx = self.canvas.getContext("2d"); + + self.appendDisplayElement(self.canvas); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + } + + self._paint.apply(this, arguments); + }; + }; + + jsPlumb.Endpoints.canvas.Dot = function(params) { + jsPlumb.Endpoints.Dot.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + var self = this, + parseValue = function(value) { + try { return parseInt(value); } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }, + calculateAdjustments = function(gradient) { + var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius; + gradient.offset && (offsetAdjustment = parseValue(gradient.offset)); + gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius)); + return [offsetAdjustment, innerRadius]; + }; + this._paint = function(d, style, anchor) { + if (style != null) { + var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + if (style.gradient) { + var adjustments = calculateAdjustments(style.gradient), + yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0], + xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0], + g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + ctx.beginPath(); + ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + } + }; + }; + + jsPlumb.Endpoints.canvas.Rectangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) { + + var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + + /* canvas gradient */ + if (style.gradient) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, d[2], d[3]); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + jsPlumb.Endpoints.canvas.Triangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Triangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) + { + var width = d[2], height = d[3], x = d[0], y = d[1], + ctx = self.canvas.getContext('2d'), + offsetX = 0, offsetY = 0, angle = 0, + orientation = anchor.getOrientation(self); + + if( orientation[0] == 1 ) { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = style.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + /* + * Canvas Image Endpoint: uses the default version, which creates an tag. + */ + jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image; + + /* + * Blank endpoint in all renderers is just the default Blank endpoint. + */ + jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank; + + /* + * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Bezier = function() { + var self = this; + jsPlumb.Connectors.Bezier.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions, style) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO i doubt this handles the case that source and target are swapped. + this.createGradient = function(dim, ctx, swap) { + return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + /* + * Canvas straight line Connector. Draws a straight line onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Straight = function() { + var self = this, + segmentMultipliers = [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ]; + + jsPlumb.Connectors.Straight.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions, style) { + + self.ctx.beginPath(); + + if (style.dashstyle && style.dashstyle.split(" ").length == 2) { + // only a very simple dashed style is supported - having two values, which define the stroke length + // (as a multiple of the stroke width) and then the space length (also as a multiple of stroke width). + var ds = style.dashstyle.split(" "); + if (ds.length != 2) ds = [2, 2]; + var dss = [ ds[0] * style.lineWidth, ds[1] * style.lineWidth ], + m = (dimensions[6] - dimensions[4]) / (dimensions[7] - dimensions[5]), + s = jsPlumbUtil.segment([dimensions[4], dimensions[5]], [ dimensions[6], dimensions[7] ]), + sm = segmentMultipliers[s], + theta = Math.atan(m), + l = Math.sqrt(Math.pow(dimensions[6] - dimensions[4], 2) + Math.pow(dimensions[7] - dimensions[5], 2)), + repeats = Math.floor(l / (dss[0] + dss[1])), + curPos = [dimensions[4], dimensions[5]]; + + + // TODO: the question here is why could we not support this in all connector types? it's really + // just a case of going along and asking jsPlumb for the next point on the path a few times, until it + // reaches the end. every type of connector supports that method, after all. but right now its only the + // bezier connector that gives you back the new location on the path along with the x,y coordinates, which + // we would need. we'd start out at loc=0 and ask for the point along the path that is dss[0] pixels away. + // we then ask for the point that is (dss[0] + dss[1]) pixels away; and from that one we need not just the + // x,y but the location, cos we're gonna plug that location back in in order to find where that dash ends. + // + // it also strikes me that it should be trivial to support arbitrary dash styles (having more or less than two + // entries). you'd just iterate that array using a step size of 2, and generify the (rss[0] + rss[1]) + // computation to be sum(rss[0]..rss[n]). + + for (var i = 0; i < repeats; i++) { + self.ctx.moveTo(curPos[0], curPos[1]); + + var nextEndX = curPos[0] + (Math.abs(Math.sin(theta) * dss[0]) * sm[0]), + nextEndY = curPos[1] + (Math.abs(Math.cos(theta) * dss[0]) * sm[1]), + nextStartX = curPos[0] + (Math.abs(Math.sin(theta) * (dss[0] + dss[1])) * sm[0]), + nextStartY = curPos[1] + (Math.abs(Math.cos(theta) * (dss[0] + dss[1])) * sm[1]) + + self.ctx.lineTo(nextEndX, nextEndY); + curPos = [nextStartX, nextStartY]; + } + + // now draw the last bit + self.ctx.moveTo(curPos[0], curPos[1]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + + } + else { + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + } + + self.ctx.stroke(); + }; + + // TODO this does not handle the case that src and target are swapped. + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + jsPlumb.Connectors.canvas.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions, style) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + +// ********************************* END OF CANVAS RENDERERS ******************************************************************* + + jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label; + jsPlumb.Overlays.canvas.Custom = jsPlumb.Overlays.Custom; + + /** + * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. + */ + var CanvasOverlay = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }; + + var AbstractCanvasArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + CanvasOverlay.apply(this, originalArgs); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + var ctx = connector.ctx; + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(d.hxy.x, d.hxy.y); + ctx.lineTo(d.tail[0].x, d.tail[0].y); + ctx.lineTo(d.cxy.x, d.cxy.y); + ctx.lineTo(d.tail[1].x, d.tail[1].y); + ctx.lineTo(d.hxy.x, d.hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + }; + + jsPlumb.Overlays.canvas.Arrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.canvas.PlainArrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.canvas.Diamond = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.15/jsPlumb-renderers-svg-1.3.15-RC1.js b/archive/1.3.15/jsPlumb-renderers-svg-1.3.15-RC1.js new file mode 100644 index 000000000..3ee10dbf4 --- /dev/null +++ b/archive/1.3.15/jsPlumb-renderers-svg-1.3.15-RC1.js @@ -0,0 +1,577 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.15 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the SVG renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +/** + * SVG support for jsPlumb. + * + * things to investigate: + * + * gradients: https://developer.mozilla.org/en/svg_in_html_introduction + * css:http://tutorials.jenkov.com/svg/svg-and-css.html + * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath + * pointer events: https://developer.mozilla.org/en/css/pointer-events + * + * IE9 hover jquery: http://forum.jquery.com/topic/1-6-2-broke-svg-hover-events + * + */ +;(function() { + + var svgAttributeMap = { + "joinstyle":"stroke-linejoin", + "stroke-linejoin":"stroke-linejoin", + "stroke-dashoffset":"stroke-dashoffset", + "stroke-linecap":"stroke-linecap" + }, + STROKE_DASHARRAY = "stroke-dasharray", + DASHSTYLE = "dashstyle", + LINEAR_GRADIENT = "linearGradient", + RADIAL_GRADIENT = "radialGradient", + FILL = "fill", + STOP = "stop", + STROKE = "stroke", + STROKE_WIDTH = "stroke-width", + STYLE = "style", + NONE = "none", + JSPLUMB_GRADIENT = "jsplumb_gradient_", + LINE_WIDTH = "lineWidth", + ns = { + svg:"http://www.w3.org/2000/svg", + xhtml:"http://www.w3.org/1999/xhtml" + }, + _attr = function(node, attributes) { + for (var i in attributes) + node.setAttribute(i, "" + attributes[i]); + }, + _node = function(name, attributes) { + var n = document.createElementNS(ns.svg, name); + attributes = attributes || {}; + attributes["version"] = "1.1"; + attributes["xmlns"] = ns.xhtml; + _attr(n, attributes); + return n; + }, + _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; }, + _clearGradient = function(parent) { + for (var i = 0; i < parent.childNodes.length; i++) { + if (parent.childNodes[i].tagName == LINEAR_GRADIENT || parent.childNodes[i].tagName == RADIAL_GRADIENT) + parent.removeChild(parent.childNodes[i]); + } + }, + _updateGradient = function(parent, node, style, dimensions, uiComponent) { + var id = JSPLUMB_GRADIENT + uiComponent._jsPlumb.idstamp(); + // first clear out any existing gradient + _clearGradient(parent); + // this checks for an 'offset' property in the gradient, and in the absence of it, assumes + // we want a linear gradient. if it's there, we create a radial gradient. + // it is possible that a more explicit means of defining the gradient type would be + // better. relying on 'offset' means that we can never have a radial gradient that uses + // some default offset, for instance. + // issue 244 suggested the 'gradientUnits' attribute; without this, straight/flowchart connectors with gradients would + // not show gradients when the line was perfectly horizontal or vertical. + if (!style.gradient.offset) { + var g = _node(LINEAR_GRADIENT, {id:id, gradientUnits:"userSpaceOnUse"}); + parent.appendChild(g); + } + else { + var g = _node(RADIAL_GRADIENT, { + id:id + }); + parent.appendChild(g); + } + + // the svg radial gradient seems to treat stops in the reverse + // order to how canvas does it. so we want to keep all the maths the same, but + // iterate the actual style declarations in reverse order, if the x indexes are not in order. + for (var i = 0; i < style.gradient.stops.length; i++) { + // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of + // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments. + // so, not too concerned about leaving this in for now. + var styleToUse = i; + if (dimensions.length == 8) + styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i; + else + styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i; + var stopColor = jsPlumbUtil.convertStyle(style.gradient.stops[styleToUse][1], true); + var s = _node(STOP, {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor}); + g.appendChild(s); + } + var applyGradientTo = style.strokeStyle ? STROKE : FILL; + node.setAttribute(STYLE, applyGradientTo + ":url(#" + id + ")"); + }, + _applyStyles = function(parent, node, style, dimensions, uiComponent) { + + if (style.gradient) { + _updateGradient(parent, node, style, dimensions, uiComponent); + } + else { + // make sure we clear any existing gradient + _clearGradient(parent); + node.setAttribute(STYLE, ""); + } + + node.setAttribute(FILL, style.fillStyle ? jsPlumbUtil.convertStyle(style.fillStyle, true) : NONE); + node.setAttribute(STROKE, style.strokeStyle ? jsPlumbUtil.convertStyle(style.strokeStyle, true) : NONE); + if (style.lineWidth) { + node.setAttribute(STROKE_WIDTH, style.lineWidth); + } + + // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like + // the syntax in VML but is actually kind of nasty: values are given in the pixel + // coordinate space, whereas in VML they are multiples of the width of the stroked + // line, which makes a lot more sense. for that reason, jsPlumb is supporting both + // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from + // VML, which will be the preferred method. the code below this converts a dashstyle + // attribute given in terms of stroke width into a pixel representation, by using the + // stroke's lineWidth. + if (style[DASHSTYLE] && style[LINE_WIDTH] && !style[STROKE_DASHARRAY]) { + var sep = style[DASHSTYLE].indexOf(",") == -1 ? " " : ",", + parts = style[DASHSTYLE].split(sep), + styleToUse = ""; + parts.forEach(function(p) { + styleToUse += (Math.floor(p * style.lineWidth) + sep); + }); + node.setAttribute(STROKE_DASHARRAY, styleToUse); + } + else if(style[STROKE_DASHARRAY]) { + node.setAttribute(STROKE_DASHARRAY, style[STROKE_DASHARRAY]); + } + + // extra attributes such as join type, dash offset. + for (var i in svgAttributeMap) { + if (style[i]) { + node.setAttribute(svgAttributeMap[i], style[i]); + } + } + }, + _decodeFont = function(f) { + var r = /([0-9].)(p[xt])\s(.*)/; + var bits = f.match(r); + return {size:bits[1] + bits[2], font:bits[3]}; + }, + _classManip = function(el, add, clazz) { + var classesToAddOrRemove = clazz.split(" "), + className = el.className, + curClasses = className.baseVal.split(" "); + + for (var i = 0; i < classesToAddOrRemove.length; i++) { + if (add) { + if (curClasses.indexOf(classesToAddOrRemove[i]) == -1) + curClasses.push(classesToAddOrRemove[i]); + } + else { + var idx = curClasses.indexOf(classesToAddOrRemove[i]); + if (idx != -1) + curClasses.splice(idx, 1); + } + } + + el.className.baseVal = curClasses.join(" "); + }, + _addClass = function(el, clazz) { + _classManip(el, true, clazz); + }, + _removeClass = function(el, clazz) { + _classManip(el, false, clazz); + }; + + /** + utility methods for other objects to use. + */ + jsPlumbUtil.svg = { + addClass:_addClass, + removeClass:_removeClass, + node:_node, + attr:_attr, + pos:_pos + }; + + /* + * Base class for SVG components. + */ + var SvgComponent = function(params) { + var self = this, + pointerEventsSpec = params.pointerEventsSpec || "all"; + jsPlumb.jsPlumbUIComponent.apply(this, params.originalArgs); + self.canvas = null, self.path = null, self.svg = null; + + var clazz = params.cssClass + " " + (params.originalArgs[0].cssClass || ""), + svgParams = { + "style":"", + "width":0, + "height":0, + "pointer-events":pointerEventsSpec, + "position":"absolute" + }; + if (self.tooltip) svgParams["title"] = self.tooltip; + self.svg = _node("svg", svgParams); + if (params.useDivWrapper) { + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.sizeCanvas(self.canvas,0,0,1,1); + self.canvas.className = clazz; + if (self.tooltip) self.canvas.setAttribute("title", self.tooltip); + } + else { + _attr(self.svg, { "class":clazz }); + self.canvas = self.svg; + } + + params._jsPlumb.appendElement(self.canvas, params.originalArgs[0]["parent"]); + if (params.useDivWrapper) self.canvas.appendChild(self.svg); + + // TODO this displayElement stuff is common between all components, across all + // renderers. would be best moved to jsPlumbUIComponent. + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + this.paint = function(d, style, anchor) { + if (style != null) { + var x = d[0], y = d[1]; + if (params.useDivWrapper) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + x = 0, y = 0; + } + var p = _pos([x, y, d[2], d[3]]); + if (self.getZIndex()) p += ";z-index:" + self.getZIndex() + ";"; + _attr(self.svg, { + "style":p, + "width": d[2], + "height": d[3] + }); + self._paint.apply(this, arguments); + } + }; + }; + + /* + * Base class for SVG connectors. + */ + var SvgConnector = jsPlumb.SvgConnector = function(params) { + var self = this; + SvgComponent.apply(this, [ { + cssClass:params["_jsPlumb"].connectorClass, + originalArgs:arguments, + pointerEventsSpec:"none", + tooltip:params.tooltip, + _jsPlumb:params["_jsPlumb"] + } ]); + this._paint = function(d, style) { + var p = self.getPath(d), a = { "d":p }, outlineStyle = null; + a["pointer-events"] = "all"; + + // outline style. actually means drawing an svg object underneath the main one. + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = jsPlumb.CurrentLibrary.extend({}, style); + outlineStyle.strokeStyle = jsPlumbUtil.convertStyle(style.outlineColor); + outlineStyle.lineWidth = outlineStrokeWidth; + + if (self.bgPath == null) { + self.bgPath = _node("path", a); + self.svg.appendChild(self.bgPath); + self.attachListeners(self.bgPath, self); + } + else { + _attr(self.bgPath, a); + } + + _applyStyles(self.svg, self.bgPath, outlineStyle, d, self); + } + + + // test - see below + // a["clip-path"]= "url(#testClip)"; + + if (self.path == null) { + self.path = _node("path", a); + self.svg.appendChild(self.path); + self.attachListeners(self.path, self); + + /* + this is a test of a clip path. i'm looking into using one of these to animate a jsplumb connection. + you could do this by walking along the line, stepping along a little at a time, and setting the clip + path to extend as far as that point. + + self.clip = _node("clipPath", {id:"testClip", clipPathUnits:"objectBoundingBox"}); + self.svg.appendChild(self.clip); + self.clip.appendChild(_node("rect", { + x:"0",y:"0",width:"0.5",height:"1" + })); + */ + } + else { + _attr(self.path, a); + } + + _applyStyles(self.svg, self.path, style, d, self); + }; + + this.reattachListeners = function() { + if (self.bgPath) self.reattachListenersForElement(self.bgPath, self); + if (self.path) self.reattachListenersForElement(self.path, self); + }; + + }; + + /* + * SVG Bezier Connector + */ + jsPlumb.Connectors.svg.Bezier = function(params) { + jsPlumb.Connectors.Bezier.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { + var _p = "M " + d[4] + " " + d[5]; + _p += (" C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]); + return _p; + }; + }; + + /* + * SVG straight line Connector + */ + jsPlumb.Connectors.svg.Straight = function(params) { + jsPlumb.Connectors.Straight.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; }; + }; + + jsPlumb.Connectors.svg.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "M " + dimensions[4] + "," + dimensions[5], + lx = dimensions[4], + ly = dimensions[5]; + + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + + var x1 = dimensions[9 + (i*2)], y1 = dimensions[10 + (i*2)], + x2 = dimensions[9 + ((i + 1) * 2)], y2 = dimensions[10 + ((i + 1) * 2)], + horiz = (x1 != lx) && (y1 == ly), + vert = (x1 == lx) && (y1 != ly), + multX = horiz ? x1 > x2 ? 1 : -1 : 0, + multY = vert ? y1 > y2 ? 1 : -1 : 0, + halfStroke = self.lineWidth / 2; + + // previously: + //p = p + " L " + x1 + " " + y1; + //p = p + " M " + x1 + " " + y1; + + // now, with support for painting an extra bit at the end each line: + p = p + " L " + x1 + " " + y1; + p = p + " L " + (x1 + (multX * halfStroke)) + " " + (y1 + (multY * halfStroke)); + + lx = x1; + ly = y1; + p = p + " M " + x1 + " " + y1; + } + // finally draw a line to the end + p = p + " L " + dimensions[6] + "," + dimensions[7]; + return p; + }; + }; + + /* + * Base class for SVG endpoints. + */ + var SvgEndpoint = window.SvgEndpoint = function(params) { + var self = this; + SvgComponent.apply(this, [ { + cssClass:params["_jsPlumb"].endpointClass, + originalArgs:arguments, + pointerEventsSpec:"all", + useDivWrapper:true, + _jsPlumb:params["_jsPlumb"] + } ]); + this._paint = function(d, style) { + var s = jsPlumb.extend({}, style); + if (s.outlineColor) { + s.strokeWidth = s.outlineWidth; + s.strokeStyle = jsPlumbUtil.convertStyle(s.outlineColor, true); + } + + if (self.node == null) { + self.node = self.makeNode(d, s); + self.svg.appendChild(self.node); + self.attachListeners(self.node, self); + } + _applyStyles(self.svg, self.node, s, d, self); + _pos(self.node, d); + }; + + this.reattachListeners = function() { + if (self.node) self.reattachListenersForElement(self.node, self); + }; + }; + + /* + * SVG Dot Endpoint + */ + jsPlumb.Endpoints.svg.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("circle", { + "cx" : d[2] / 2, + "cy" : d[3] / 2, + "r" : d[2] / 2 + }); + }; + }; + + /* + * SVG Rectangle Endpoint + */ + jsPlumb.Endpoints.svg.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("rect", { + "width":d[2], + "height":d[3] + }); + }; + }; + + /* + * SVG Image Endpoint is the default image endpoint. + */ + jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image; + /* + * Blank endpoint in svg renderer is the default Blank endpoint. + */ + jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank; + /* + * Label overlay in svg renderer is the default Label overlay. + */ + jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label; + /* + * Custom overlay in svg renderer is the default Custom overlay. + */ + jsPlumb.Overlays.svg.Custom = jsPlumb.Overlays.Custom; + + var AbstractSvgArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + this.isAppendedAtTopLevel = false; + var self = this, path = null; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path", { + "pointer-events":"all" + }); + connector.svg.appendChild(path); + + self.attachListeners(path, connector); + self.attachListeners(path, self); + } + var clazz = originalArgs && (originalArgs.length == 1) ? (originalArgs[0].cssClass || "") : ""; + + _attr(path, { + "d" : makePath(d), + "class" : clazz, + stroke : strokeStyle ? strokeStyle : null, + fill : fillStyle ? fillStyle : null + }); + }; + var makePath = function(d) { + return "M" + d.hxy.x + "," + d.hxy.y + + " L" + d.tail[0].x + "," + d.tail[0].y + + " L" + d.cxy.x + "," + d.cxy.y + + " L" + d.tail[1].x + "," + d.tail[1].y + + " L" + d.hxy.x + "," + d.hxy.y; + }; + this.reattachListeners = function() { + if (path) self.reattachListenersForElement(path, self); + }; + this.cleanup = function() { + if (path != null) jsPlumb.CurrentLibrary.removeElement(path); + }; + }; + + jsPlumb.Overlays.svg.Arrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.svg.PlainArrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.svg.Diamond = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; + + // a test + jsPlumb.Overlays.svg.GuideLines = function() { + var path = null, self = this, path2 = null, p1_1, p1_2; + jsPlumb.Overlays.GuideLines.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + + p1_1 = _node("path"); + connector.svg.appendChild(p1_1); + self.attachListeners(p1_1, connector); + self.attachListeners(p1_1, self); + + p1_2 = _node("path"); + connector.svg.appendChild(p1_2); + self.attachListeners(p1_2, connector); + self.attachListeners(p1_2, self); + + } + + _attr(path, { + "d" : makePath(d[0], d[1]), + stroke : "red", + fill : null + }); + + _attr(p1_1, { + "d" : makePath(d[2][0], d[2][1]), + stroke : "blue", + fill : null + }); + + _attr(p1_2, { + "d" : makePath(d[3][0], d[3][1]), + stroke : "green", + fill : null + }); + }; + + var makePath = function(d1, d2) { + return "M " + d1.x + "," + d1.y + + " L" + d2.x + "," + d2.y; + }; + + }; +})(); \ No newline at end of file diff --git a/archive/1.3.15/jsPlumb-renderers-vml-1.3.15-RC1.js b/archive/1.3.15/jsPlumb-renderers-vml-1.3.15-RC1.js new file mode 100644 index 000000000..bdb67189c --- /dev/null +++ b/archive/1.3.15/jsPlumb-renderers-vml-1.3.15-RC1.js @@ -0,0 +1,454 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.15 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the VML renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + // http://ajaxian.com/archives/the-vml-changes-in-ie-8 + // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/ + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + + var vmlAttributeMap = { + "stroke-linejoin":"joinstyle", + "joinstyle":"joinstyle", + "endcap":"endcap", + "miterlimit":"miterlimit" + }, + jsPlumbStylesheet = null; + + if (document.createStyleSheet && document.namespaces) { + + var ruleClasses = [ + ".jsplumb_vml", "jsplumb\\:textbox", "jsplumb\\:oval", "jsplumb\\:rect", + "jsplumb\\:stroke", "jsplumb\\:shape", "jsplumb\\:group" + ], + rule = "behavior:url(#default#VML);position:absolute;"; + + jsPlumbStylesheet = document.createStyleSheet(); + + for (var i = 0; i < ruleClasses.length; i++) + jsPlumbStylesheet.addRule(ruleClasses[i], rule); + + // in this page it is also mentioned that IE requires the extra arg to the namespace + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either. + // var iev = document.documentMode; + //if (!iev || iev < 8) + document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); + //else + // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML"); + } + + jsPlumb.vml = {}; + + var scale = 1000, + + _groupMap = {}, + _getGroup = function(container, connectorClass) { + var id = jsPlumb.getId(container), + g = _groupMap[id]; + if(!g) { + g = _node("group", [0,0,scale, scale], {"class":connectorClass}); + //g.style.position=absolute; + //g["coordsize"] = "1000,1000"; + g.style.backgroundColor="red"; + _groupMap[id] = g; + jsPlumb.appendElement(g, container); // todo if this gets reinstated, remember to use the current jsplumb instance. + //document.body.appendChild(g); + } + return g; + }, + _atts = function(o, atts) { + for (var i in atts) { + // IE8 fix: setattribute does not work after an element has been added to the dom! + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + //o.setAttribute(i, atts[i]); + + /*There is an additional problem when accessing VML elements by using get/setAttribute. The simple solution is following: + + if (document.documentMode==8) { + ele.opacity=1; + } else { + ele.setAttribute(‘opacity’,1); + } + */ + + o[i] = atts[i]; + } + }, + _node = function(name, d, atts, parent, _jsPlumb, deferToJsPlumbContainer) { + atts = atts || {}; + var o = document.createElement("jsplumb:" + name); + if (deferToJsPlumbContainer) + _jsPlumb.appendElement(o, parent); + else + jsPlumb.CurrentLibrary.appendElement(o, parent); + o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml"; + _pos(o, d); + _atts(o, atts); + return o; + }, + _pos = function(o,d, zIndex) { + o.style.left = d[0] + "px"; + o.style.top = d[1] + "px"; + o.style.width= d[2] + "px"; + o.style.height= d[3] + "px"; + o.style.position = "absolute"; + if (zIndex) + o.style.zIndex = zIndex; + }, + _conv = jsPlumb.vml.convertValue = function(v) { + return Math.floor(v * scale); + }, + // tests if the given style is "transparent" and then sets the appropriate opacity node to 0 if so, + // or 1 if not. TODO in the future, support variable opacity. + _maybeSetOpacity = function(styleToWrite, styleToCheck, type, component) { + if ("transparent" === styleToCheck) + component.setOpacity(type, "0.0"); + else + component.setOpacity(type, "1.0"); + }, + _applyStyles = function(node, style, component, _jsPlumb) { + var styleToWrite = {}; + if (style.strokeStyle) { + styleToWrite["stroked"] = "true"; + var strokeColor = jsPlumbUtil.convertStyle(style.strokeStyle, true); + styleToWrite["strokecolor"] = strokeColor; + _maybeSetOpacity(styleToWrite, strokeColor, "stroke", component); + styleToWrite["strokeweight"] = style.lineWidth + "px"; + } + else styleToWrite["stroked"] = "false"; + + if (style.fillStyle) { + styleToWrite["filled"] = "true"; + var fillColor = jsPlumbUtil.convertStyle(style.fillStyle, true); + styleToWrite["fillcolor"] = fillColor; + _maybeSetOpacity(styleToWrite, fillColor, "fill", component); + } + else styleToWrite["filled"] = "false"; + + if(style["dashstyle"]) { + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }, node, _jsPlumb); + } + else + component.strokeNode.dashstyle = style["dashstyle"]; + } + else if (style["stroke-dasharray"] && style["lineWidth"]) { + var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",", + parts = style["stroke-dasharray"].split(sep), + styleToUse = ""; + for(var i = 0; i < parts.length; i++) { + styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep); + } + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }, node, _jsPlumb); + } + else + component.strokeNode.dashstyle = styleToUse; + } + + _atts(node, styleToWrite); + }, + /* + * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. + */ + VmlComponent = function() { + var self = this; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.opacityNodes = { + "stroke":null, + "fill":null + }; + this.initOpacityNodes = function(vml) { + self.opacityNodes["stroke"] = _node("stroke", [0,0,1,1], {opacity:"0.0"}, vml, self._jsPlumb); + self.opacityNodes["fill"] = _node("fill", [0,0,1,1], {opacity:"0.0"}, vml, self._jsPlumb); + }; + this.setOpacity = function(type, value) { + var node = self.opacityNodes[type]; + if (node) node["opacity"] = "" + value; + }; + var displayElements = [ ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el, doNotAppendToCanvas) { + if (!doNotAppendToCanvas) self.canvas.parentNode.appendChild(el); + displayElements.push(el); + }; + }, + /* + * Base class for Vml connectors. extends VmlComponent. + */ + VmlConnector = jsPlumb.VmlConnector = function(params) { + var self = this; + self.strokeNode = null; + self.canvas = null; + VmlComponent.apply(this, arguments); + var clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : ""); + this.paint = function(d, style, anchor) { + if (style != null) { + var path = self.getPath(d), p = { "path":path }; + + //* + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle : jsPlumbUtil.convertStyle(style.outlineColor), + lineWidth : outlineStrokeWidth + }; + for (var aa in vmlAttributeMap) outlineStyle[aa] = style[aa]; + + if (self.bgCanvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.bgCanvas = _node("shape", d, p, params.parent, self._jsPlumb, true); + _pos(self.bgCanvas, d, self.getZIndex()); + self.appendDisplayElement(self.bgCanvas, true); + self.attachListeners(self.bgCanvas, self); + self.initOpacityNodes(self.bgCanvas, ["stroke"]); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.bgCanvas, d, self.getZIndex()); + _atts(self.bgCanvas, p); + } + + _applyStyles(self.bgCanvas, outlineStyle, self); + } + //*/ + + if (self.canvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + if (self.tooltip) p["label"] = self.tooltip; + self.canvas = _node("shape", d, p, params.parent, self._jsPlumb, true); + //var group = _getGroup(params.parent); // test of append everything to a group + //group.appendChild(self.canvas); // sort of works but not exactly; + //params["_jsPlumb"].appendElement(self.canvas, params.parent); //before introduction of groups + + self.appendDisplayElement(self.canvas, true); + self.attachListeners(self.canvas, self); + self.initOpacityNodes(self.canvas, ["stroke"]); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.canvas, d, self.getZIndex()); + _atts(self.canvas, p); + } + + _applyStyles(self.canvas, style, self, self._jsPlumb); + } + }; + + //self.appendDisplayElement(self.canvas); + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + }, + /* + * + * Base class for Vml Endpoints. extends VmlComponent. + * + */ + VmlEndpoint = window.VmlEndpoint = function(params) { + VmlComponent.apply(this, arguments); + var vml = null, self = this, opacityStrokeNode = null, opacityFillNode = null; + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + + var clazz = self._jsPlumb.endpointClass + (params.cssClass ? (" " + params.cssClass) : ""); + + //var group = _getGroup(params.parent); + //group.appendChild(self.canvas); + params["_jsPlumb"].appendElement(self.canvas, params.parent); + + if (self.tooltip) self.canvas.setAttribute("label", self.tooltip); + + this.paint = function(d, style, anchor) { + var p = { }; + + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (vml == null) { + p["class"] = clazz; + vml = self.getVml([0,0, d[2], d[3]], p, anchor, self.canvas, self._jsPlumb); + self.attachListeners(vml, self); + + self.appendDisplayElement(vml, true); + self.appendDisplayElement(self.canvas, true); + + self.initOpacityNodes(vml, ["fill"]); + } + else { + _pos(vml, [0,0, d[2], d[3]]); + _atts(vml, p); + } + + _applyStyles(vml, style, self); + }; + + this.reattachListeners = function() { + if (vml) self.reattachListenersForElement(vml, self); + }; + }; + + jsPlumb.Connectors.vml.Bezier = function() { + jsPlumb.Connectors.Bezier.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Straight = function() { + jsPlumb.Connectors.Straight.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Flowchart = function() { + jsPlumb.Connectors.Flowchart.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l"; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]); + } + // finally draw a line to the end + p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e"; + return p; + }; + }; + + jsPlumb.Endpoints.vml.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor, parent, _jsPlumb) { return _node("oval", d, atts, parent, _jsPlumb); }; + }; + + jsPlumb.Endpoints.vml.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor, parent, _jsPlumb) { return _node("rect", d, atts, parent, _jsPlumb); }; + }; + + /* + * VML Image Endpoint is the same as the default image endpoint. + */ + jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image; + + /** + * placeholder for Blank endpoint in vml renderer. + */ + jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank; + + /** + * VML Label renderer. uses the default label renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label; + + /** + * VML Custom renderer. uses the default Custom renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Custom = jsPlumb.Overlays.Custom; + + var AbstractVmlArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + VmlComponent.apply(this, originalArgs); + var self = this, path = null; + self.canvas = null; + self.isAppendedAtTopLevel = true; + var getPath = function(d, connectorDimensions) { + return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) + + " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + + " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + + " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + + " x e"; + }; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) { + var p = {}; + if (strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] = jsPlumbUtil.convertStyle(strokeStyle, true); + } + if (lineWidth) p["strokeweight"] = lineWidth + "px"; + if (fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = fillStyle; + } + var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + w = Math.abs(xmax - xmin), + h = Math.abs(ymax - ymin), + dim = [xmin, ymin, w, h]; + + // for VML, we create overlays using shapes that have the same dimensions and + // coordsize as their connector - overlays calculate themselves relative to the + // connector (it's how it's been done since the original canvas implementation, because + // for canvas that makes sense). + p["path"] = getPath(d, connectorDimensions); + p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale); + + dim[0] = connectorDimensions[0]; + dim[1] = connectorDimensions[1]; + dim[2] = connectorDimensions[2]; + dim[3] = connectorDimensions[3]; + + if (self.canvas == null) { + //p["class"] = jsPlumb.overlayClass; // TODO currentInstance? + self.canvas = _node("shape", dim, p, connector.canvas.parentNode, connector._jsPlumb, true); + connector.appendDisplayElement(self.canvas, true); + self.attachListeners(self.canvas, connector); + self.attachListeners(self.canvas, self); + } + else { + _pos(self.canvas, dim); + _atts(self.canvas, p); + } + }; + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + + this.cleanup = function() { + if (self.canvas != null) jsPlumb.CurrentLibrary.removeElement(self.canvas); + }; + }; + + jsPlumb.Overlays.vml.Arrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.vml.PlainArrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.vml.Diamond = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.15/jsPlumb-util-1.3.15-RC1.js b/archive/1.3.15/jsPlumb-util-1.3.15-RC1.js new file mode 100644 index 000000000..540f3804c --- /dev/null +++ b/archive/1.3.15/jsPlumb-util-1.3.15-RC1.js @@ -0,0 +1,268 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.15 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the util functions + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ +jsPlumbUtil = { + isArray : function(a) { + return Object.prototype.toString.call(a) === "[object Array]"; + }, + isString : function(s) { + return typeof s === "string"; + }, + isBoolean: function(s) { + return typeof s === "boolean"; + }, + isObject : function(o) { + return Object.prototype.toString.call(o) === "[object Object]"; + }, + isDate : function(o) { + return Object.prototype.toString.call(o) === "[object Date]"; + }, + isFunction: function(o) { + return Object.prototype.toString.call(o) === "[object Function]"; + }, + clone : function(a) { + if (this.isString(a)) return new String(a); + else if (this.isBoolean(a)) return new Boolean(a); + else if (this.isDate(a)) return new Date(a.getTime()); + else if (this.isFunction(a)) return a; + else if (this.isArray(a)) { + var b = []; + for (var i = 0; i < a.length; i++) + b.push(this.clone(a[i])); + return b; + } + else if (this.isObject(a)) { + var b = {}; + for (var i in a) + b[i] = this.clone(a[i]); + return b; + } + else return a; + }, + merge : function(a, b) { + var c = this.clone(a); + for (var i in b) { + if (c[i] == null || this.isString(b[i]) || this.isBoolean(b[i])) + c[i] = b[i]; + else { + if (this.isArray(b[i]) && this.isArray(c[i])) { + var ar = []; + ar.push.apply(ar, c[i]); + ar.push.apply(ar, b[i]); + c[i] = ar; + } + else if(this.isObject(c[i]) && this.isObject(b[i])) { + for (var j in b[i]) + c[i][j] = b[i][j]; + } + } + } + return c; + }, + convertStyle : function(s, ignoreAlpha) { + // TODO: jsPlumb should support a separate 'opacity' style member. + if ("transparent" === s) return s; + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + return o; + }, + gradient : function(p1, p2) { + p1 = jsPlumbUtil.isArray(p1) ? p1 : [p1.x, p1.y]; + p2 = jsPlumbUtil.isArray(p2) ? p2 : [p2.x, p2.y]; + return (p2[1] - p1[1]) / (p2[0] - p1[0]); + }, + normal : function(p1, p2) { + return -1 / jsPlumbUtil.gradient(p1,p2); + }, + lineLength : function(p1, p2) { + p1 = jsPlumbUtil.isArray(p1) ? p1 : [p1.x, p1.y]; + p2 = jsPlumbUtil.isArray(p2) ? p2 : [p2.x, p2.y]; + return Math.sqrt(Math.pow(p2[1] - p1[1], 2) + Math.pow(p2[0] - p1[0], 2)); + }, + segment : function(p1, p2) { + p1 = jsPlumbUtil.isArray(p1) ? p1 : [p1.x, p1.y]; + p2 = jsPlumbUtil.isArray(p2) ? p2 : [p2.x, p2.y]; + if (p2[0] > p1[0]) { + return (p2[1] > p1[1]) ? 2 : 1; + } + else { + return (p2[1] > p1[1]) ? 3 : 4; + } + }, + intersects : function(r1, r2) { + var x1 = r1.x, x2 = r1.x + r1.w, y1 = r1.y, y2 = r1.y + r1.h, + a1 = r2.x, a2 = r2.x + r2.w, b1 = r2.y, b2 = r2.y + r2.h; + + return ( (x1 <= a1 && a1 <= x2) && (y1 <= b1 && b1 <= y2) ) || + ( (x1 <= a2 && a2 <= x2) && (y1 <= b1 && b1 <= y2) ) || + ( (x1 <= a1 && a1 <= x2) && (y1 <= b2 && b2 <= y2) ) || + ( (x1 <= a2 && a1 <= x2) && (y1 <= b2 && b2 <= y2) ) || + + ( (a1 <= x1 && x1 <= a2) && (b1 <= y1 && y1 <= b2) ) || + ( (a1 <= x2 && x2 <= a2) && (b1 <= y1 && y1 <= b2) ) || + ( (a1 <= x1 && x1 <= a2) && (b1 <= y2 && y2 <= b2) ) || + ( (a1 <= x2 && x1 <= a2) && (b1 <= y2 && y2 <= b2) ); + }, + segmentMultipliers : [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ], + inverseSegmentMultipliers : [null, [-1, -1], [-1, 1], [1, 1], [1, -1] ], + pointOnLine : function(fromPoint, toPoint, distance) { + var m = jsPlumbUtil.gradient(fromPoint, toPoint), + s = jsPlumbUtil.segment(fromPoint, toPoint), + segmentMultiplier = distance > 0 ? jsPlumbUtil.segmentMultipliers[s] : jsPlumbUtil.inverseSegmentMultipliers[s], + theta = Math.atan(m), + y = Math.abs(distance * Math.sin(theta)) * segmentMultiplier[1], + x = Math.abs(distance * Math.cos(theta)) * segmentMultiplier[0]; + return { x:fromPoint.x + x, y:fromPoint.y + y }; + }, + /** + * calculates a perpendicular to the line fromPoint->toPoint, that passes through toPoint and is 'length' long. + * @param fromPoint + * @param toPoint + * @param length + */ + perpendicularLineTo : function(fromPoint, toPoint, length) { + var m = jsPlumbUtil.gradient(fromPoint, toPoint), + theta2 = Math.atan(-1 / m), + y = length / 2 * Math.sin(theta2), + x = length / 2 * Math.cos(theta2); + return [{x:toPoint.x + x, y:toPoint.y + y}, {x:toPoint.x - x, y:toPoint.y - y}]; + }, + findWithFunction : function(a, f) { + if (a) + for (var i = 0; i < a.length; i++) if (f(a[i])) return i; + return -1; + }, + indexOf : function(l, v) { + return jsPlumbUtil.findWithFunction(l, function(_v) { return _v == v; }); + }, + removeWithFunction : function(a, f) { + var idx = jsPlumbUtil.findWithFunction(a, f); + if (idx > -1) a.splice(idx, 1); + return idx != -1; + }, + remove : function(l, v) { + var idx = jsPlumbUtil.indexOf(l, v); + if (idx > -1) l.splice(idx, 1); + return idx != -1; + }, + // TODO support insert index + addWithFunction : function(list, item, hashFunction) { + if (jsPlumbUtil.findWithFunction(list, hashFunction) == -1) list.push(item); + }, + addToList : function(map, key, value) { + var l = map[key]; + if (l == null) { + l = [], map[key] = l; + } + l.push(value); + return l; + }, + /** + * EventGenerator + * Superclass for objects that generate events - jsPlumb extends this, as does jsPlumbUIComponent, which all the UI elements extend. + */ + EventGenerator : function() { + var _listeners = {}, self = this; + + // this is a list of events that should re-throw any errors that occur during their dispatch. as of 1.3.0 this is private to + // jsPlumb, but it seems feasible that people might want to manipulate this list. the thinking is that we don't want event + // listeners to bring down jsPlumb - or do we. i can't make up my mind about this, but i know i want to hear about it if the "ready" + // event fails, because then my page has most likely not initialised. so i have this halfway-house solution. it will be interesting + // to hear what other people think. + var eventsToDieOn = [ "ready" ]; + + /* + * Binds a listener to an event. + * + * Parameters: + * event - name of the event to bind to. + * listener - function to execute. + */ + this.bind = function(event, listener) { + jsPlumbUtil.addToList(_listeners, event, listener); + return self; + }; + /* + * Fires an update for the given event. + * + * Parameters: + * event - event to fire + * value - value to pass to the event listener(s). + * originalEvent - the original event from the browser + */ + this.fire = function(event, value, originalEvent) { + if (_listeners[event]) { + for ( var i = 0; i < _listeners[event].length; i++) { + // doing it this way rather than catching and then possibly re-throwing means that an error propagated by this + // method will have the whole call stack available in the debugger. + if (jsPlumbUtil.findWithFunction(eventsToDieOn, function(e) { return e === event}) != -1) + _listeners[event][i](value, originalEvent); + else { + // for events we don't want to die on, catch and log. + try { + _listeners[event][i](value, originalEvent); + } catch (e) { + jsPlumbUtil.log("jsPlumb: fire failed for event " + event + " : " + e); + } + } + } + } + return self; + }; + /* + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + this.unbind = function(event) { + if (event) + delete _listeners[event]; + else { + _listeners = {}; + } + return self; + }; + + this.getListener = function(forEvent) { + return _listeners[forEvent]; + }; + }, + logEnabled : true, + log : function() { + if (jsPlumbUtil.logEnabled && typeof console != "undefined") { + try { + var msg = arguments[arguments.length - 1]; + console.log(msg); + } + catch (e) {} + } + }, + group : function(g) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.group(g); }, + groupEnd : function(g) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.groupEnd(g); }, + time : function(t) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.time(t); }, + timeEnd : function(t) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.timeEnd(t); } +}; \ No newline at end of file diff --git a/archive/1.3.15/mootools.jsPlumb-1.3.15-RC1.js b/archive/1.3.15/mootools.jsPlumb-1.3.15-RC1.js new file mode 100644 index 000000000..381704313 --- /dev/null +++ b/archive/1.3.15/mootools.jsPlumb-1.3.15-RC1.js @@ -0,0 +1,440 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.15 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the MooTools adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function(now) { + this.parent(now); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event, originalEvent) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr, originalEvent); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, _scope, value) { + var l = list[_scope]; + if (!l) { + l = []; + list[_scope] = l; + } + l.push(value); + }; + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + var _getElementObject = function(el) { + return $(el); + }; + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el) + try { + if (el.className.constructor == SVGAnimatedString) { + jsPlumbUtil.svg.addClass(el, clazz); + } + else el.addClass(clazz); + } + catch (e) { + // SVGAnimatedString not supported; no problem. + el.addClass(clazz); + } + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + m.start(properties); + }, + + appendElement : function(child, parent) { + _getElementObject(parent).grab(child); + }, + + bind : function(el, event, callback) { + el = _getElementObject(el); + el.addEvent(event, callback); + }, + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.event.clientX, eventObject.event.clientY]; + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el), + drags = _draggablesById[id]; + return drags[0].scope; + }, + + getDropEvent : function(args) { + return args[2]; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getDOMElement : function(el) { + // MooTools just decorates the DOM elements. so we have either an ID or an Element here. + return typeof(el) == "String" ? document.getElementById(el) : el; + }, + + getElementObject : _getElementObject, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + getOriginalEvent : function(e) { + return e.event; + }, + + getPageXY : function(eventObject) { + return [eventObject.event.pageX, eventObject.event.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).getParent(); + }, + + getScrollLeft : function(el) { + return null; + }, + + getScrollTop : function(el) { + return null; + }, + + getSelector : function(spec) { + return $$(spec); + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e != null ? e.tagName : null; + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs, zoom) { + var ui = eventArgs[0], + el = jsPlumb.CurrentLibrary.getElementObject(ui), + p = el.getPosition(); + + zoom = zoom || 1; + + return { left:p.x / zoom, top:p.y / zoom}; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options, isPlumbedComponent) { + var id = jsPlumb.getId(el); + var drag = _draggablesById[id]; + if (!drag) { + var originalZIndex = 0, + originalCursor = null, + dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + + options['onStart'] = jsPlumb.wrap(options['onStart'], function() { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + drag.originalZIndex = originalZIndex; + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + }); + + // DROPPABLES - only relevant if this is a plumbed component, ie. not just the result of the user making some DOM element + // draggable. this is the only library adapter that has to care about this parameter. + var scope = "" + (options["scope"] || jsPlumb.Defaults.Scope), + filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }, + droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + + if (isPlumbedComponent) { + + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr, event) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop', event); + } + }; + } + else + options["droppables"] = []; + + drag = new Drag.Move(el, options); + drag.scope = scope; + drag.originalZIndex = originalZIndex; + _add(_draggablesById, el.get("id"), drag); + // again, only keep a record of this for scope stuff if this is a plumbed component (an endpoint) + if (isPlumbedComponent) { + _add(_draggablesByScope, scope, drag); + } + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + initDroppable : function(el, options, isPlumbedComponent, isPermanent) { + var scope = options["scope"]; + _add(_droppables, scope, el); + var id = jsPlumb.getId(el); + + el.setAttribute("_isPermanentDroppable", isPermanent); // we use this to clear out droppables on drag complete. + _droppableOptions[id] = options; + _droppableScopesById[id] = scope; + var filterFunc = function(entry) { return entry.element != el; }, + draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + isAlreadyDraggable : function(el) { + return _draggablesById[jsPlumb.getId(el)] != null; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + return (typeof Drag != undefined && typeof Drag.Move != undefined); + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el.className.constructor == SVGAnimatedString) { + jsPlumbUtil.svg.removeClass(el, clazz); + } + else el.removeClass(clazz); + } + catch (e) { + // SVGAnimatedString not supported; no problem. + el.removeClass(clazz); + } + }, + + removeElement : function(element, parent) { + var el = _getElementObject(element); + if (el) el.dispose(); // ?? + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + setDragScope : function(el, scope) { + var drag = _draggablesById[el.get("id")]; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + drag[0].droppables = droppables; + }, + + setOffset : function(el, o) { + _getElementObject(el).setPosition({x:o.left, y:o.top}); + }, + + stopDrag : function() { + for (var i in _draggablesById) { + for (var j = 0; j < _draggablesById[i].length; j++) { + var d = _draggablesById[i][j]; + d.stop(); + if (d.originalZIndex != 0) + d.element.setStyle("z-index", d.originalZIndex); + } + } + }, + + /** + * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself. + * the other libraries do not. yui, in fact, cannot even pass an original event. we have to pull out stuff + * from the originalEvent to put in an options object for YUI. + * @param el + * @param event + * @param originalEvent + */ + trigger : function(el, event, originalEvent) { + originalEvent.stopPropagation(); + _getElementObject(el).fireEvent(event, originalEvent); + }, + + unbind : function(el, event, callback) { + el = _getElementObject(el); + el.removeEvent(event, callback); + } + }; + + window.addEvent('domready', jsPlumb.init); +})(); diff --git a/build/1.3.15/tests/android-svg.html b/archive/1.3.15/tests/android-svg.html similarity index 100% rename from build/1.3.15/tests/android-svg.html rename to archive/1.3.15/tests/android-svg.html diff --git a/build/1.3.15/tests/loadTestHarness.html b/archive/1.3.15/tests/loadTestHarness.html similarity index 100% rename from build/1.3.15/tests/loadTestHarness.html rename to archive/1.3.15/tests/loadTestHarness.html diff --git a/build/1.3.15/tests/qunit-all.html b/archive/1.3.15/tests/qunit-all.html similarity index 100% rename from build/1.3.15/tests/qunit-all.html rename to archive/1.3.15/tests/qunit-all.html diff --git a/build/1.3.15/tests/qunit-canvas-jquery-instance.html b/archive/1.3.15/tests/qunit-canvas-jquery-instance.html similarity index 100% rename from build/1.3.15/tests/qunit-canvas-jquery-instance.html rename to archive/1.3.15/tests/qunit-canvas-jquery-instance.html diff --git a/build/1.3.15/tests/qunit-canvas-jquery.html b/archive/1.3.15/tests/qunit-canvas-jquery.html similarity index 100% rename from build/1.3.15/tests/qunit-canvas-jquery.html rename to archive/1.3.15/tests/qunit-canvas-jquery.html diff --git a/build/1.3.15/tests/qunit-canvas-mootools.html b/archive/1.3.15/tests/qunit-canvas-mootools.html similarity index 100% rename from build/1.3.15/tests/qunit-canvas-mootools.html rename to archive/1.3.15/tests/qunit-canvas-mootools.html diff --git a/build/1.3.15/tests/qunit-svg-jquery-instance.html b/archive/1.3.15/tests/qunit-svg-jquery-instance.html similarity index 100% rename from build/1.3.15/tests/qunit-svg-jquery-instance.html rename to archive/1.3.15/tests/qunit-svg-jquery-instance.html diff --git a/build/1.3.15/tests/qunit-svg-jquery.html b/archive/1.3.15/tests/qunit-svg-jquery.html similarity index 100% rename from build/1.3.15/tests/qunit-svg-jquery.html rename to archive/1.3.15/tests/qunit-svg-jquery.html diff --git a/build/1.3.15/tests/qunit-vml-jquery-instance.html b/archive/1.3.15/tests/qunit-vml-jquery-instance.html similarity index 100% rename from build/1.3.15/tests/qunit-vml-jquery-instance.html rename to archive/1.3.15/tests/qunit-vml-jquery-instance.html diff --git a/build/1.3.15/tests/qunit-vml-jquery.html b/archive/1.3.15/tests/qunit-vml-jquery.html similarity index 100% rename from build/1.3.15/tests/qunit-vml-jquery.html rename to archive/1.3.15/tests/qunit-vml-jquery.html diff --git a/build/1.3.15/tests/qunit.css b/archive/1.3.15/tests/qunit.css similarity index 100% rename from build/1.3.15/tests/qunit.css rename to archive/1.3.15/tests/qunit.css diff --git a/archive/1.3.15/yui.jsPlumb-1.3.15-RC1.js b/archive/1.3.15/yui.jsPlumb-1.3.15-RC1.js new file mode 100644 index 000000000..6ba343724 --- /dev/null +++ b/archive/1.3.15/yui.jsPlumb-1.3.15-RC1.js @@ -0,0 +1,385 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.15 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the YUI3 adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +/** + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getOriginalEvent gets the original browser event from some wrapper event. + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + */ +(function() { + + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function( v, b, s ) { + for( var i = +b || 0, l = this.length; i < l; i++ ) { + if( this[i]===v || s && this[i]==v ) { return i; } + } + return -1; + }; + } + + var Y; + + YUI().use('node', 'dd', 'anim', 'node-event-simulate', function(_Y) { + Y = _Y; + Y.on("domready", function() { jsPlumb.init(); }); + }); + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }, + ddEvents = [ "drag:mouseDown", "drag:afterMouseDown", "drag:mouseup", + "drag:align", "drag:removeHandle", "drag:addHandle", "drag:removeInvalid", "drag:addInvalid", + "drag:start", "drag:end", "drag:drag", "drag:over", "drag:enter", + "drag:exit", "drag:drophit", "drag:dropmiss", "drop:over", "drop:enter", "drop:exit", "drop:hit" + ], + animEvents = [ "tween" ], + /** + * helper function to curry callbacks for some element. + */ + _wrapper = function(fn) { + return function() { + try { + return fn.apply(this, arguments); + } + catch (e) { } + }; + }, + /** + * extracts options from the given options object, leaving out event handlers. + */ + _getDDOptions = function(options) { + var o = {}; + for (var i in options) if (ddEvents.indexOf(i) == -1) o[i] = options[i]; + return o; + }, + /** + * attaches all event handlers found in options to the given dragdrop object, and registering + * the given el as the element of interest. + */ + _attachListeners = function(dd, options, eventList) { + for (var ev in options) { + if (eventList.indexOf(ev) != -1) { + var w = _wrapper(options[ev]); + dd.on(ev, w); + } + } + }, + _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}, + _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }, + _lastDragObject = null, + _extend = function(o1, o2) { + for (var i in o2) + o1[i] = o2[i]; + return o1; + }, + _getAttribute = function(el, attributeId) { + return el.getAttribute(attributeId); + }, + _getElementObject = function(el) { + if (el == null) return null; + var eee = null; + eee = typeof el == 'string' ? Y.one('#' + el) : el._node ? el : Y.one(el); + return eee; + }; + + jsPlumb.CurrentLibrary = { + + addClass : function(el, clazz) { + jsPlumb.CurrentLibrary.getElementObject(el).addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + var o = _extend({node:el, to:properties}, options), + id = _getAttribute(el, "id"); + o["tween"] = jsPlumb.wrap(properties["tween"], function() { + // TODO should use a current instance. + jsPlumb.repaint(id); + }); + var a = new Y.Anim(o); + _attachListeners(a, o, animEvents); + a.run(); + }, + + appendElement : function(child, parent) { + _getElementObject (parent).append(child); + }, + + /** + * event binding wrapper. + */ + bind : function(el, event, callback) { + _getElementObject(el).on(event, callback); + }, + + dragEvents : { + "start":"drag:start", "stop":"drag:end", "drag":"drag:drag", "step":"step", + "over":"drop:enter", "out":"drop:exit", "drop":"drop:hit" + }, + + extend : _extend, + + getAttribute : _getAttribute, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + // this is a workaround for the unfortunate fact that in YUI3, the 'drop:exit' event does + // not contain a reference to the drag that just exited. single-threaded js to the + // rescue: we'll just keep it for ourselves. + if (eventArgs[0].drag) _lastDragObject = eventArgs[0].drag.el; + return _lastDragObject; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el), + dd = _draggablesById[id]; + return dd.scope; + }, + + getDropEvent : function(args) { + return args[0]; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getDOMElement : function(el) { + if (typeof(el) == "String") + return document.getElementById(el); + else if (el._node) + return el._node; + else return el; + }, + + getElementObject : _getElementObject, + + getOffset : function(el) { + var o = Y.DOM.getXY(el._node); + return {left:o[0], top:o[1]}; + }, + + getOriginalEvent : function(e) { + return e._event; + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).get("parentNode"); + }, + + getScrollLeft : function(el) { + return 0; + }, + + getScrollTop : function(el) { + return 0; + }, + + getSelector : function(spec) { + var s = Y.all(spec); + return s && s ._nodes ? s._nodes : []; + }, + + getSize : function(el) { + return [ el._node.offsetWidth, el._node.offsetHeight ]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e != null && e._node != null ? e._node.tagName : null; + }, + + getUIPosition : function(args, zoom) { + zoom = zoom || 1; + var n = args[0].currentTarget.el._node, + o = Y.DOM.getXY(n); + return {left:o[0] / zoom, top:o[1] / zoom}; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options, isPlumbedComponent) { + var _opts = _getDDOptions(options), + id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drag(_opts); + dd.el = el; + + if (isPlumbedComponent) { + var scope = options['scope'] || jsPlumb.Defaults.Scope; + dd.scope = scope; + _add(_draggablesByScope, scope, dd); + } + + _draggablesById[id] = dd; + + _attachListeners(dd, options, ddEvents); + }, + + initDroppable : function(el, options) { + var _opts = _getDDOptions(options), + id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drop(_opts); + + _droppableOptions[id] = options; + + options = _extend({}, options); + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _droppableScopesById[id] = scope; + + options["drop:enter"] = jsPlumb.wrap(options["drop:enter"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, true); + }, true); + options["drop:exit"] = jsPlumb.wrap(options["drop:exit"], function(e) { + _checkHover(el, false); + }); + options["drop:hit"] = jsPlumb.wrap(options["drop:hit"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, false); + }, true); + + _attachListeners(dd, options, ddEvents); + }, + + isAlreadyDraggable : function(el) { + el = _getElementObject(el); + return el.hasClass("yui3-dd-draggable"); + }, + + isDragSupported : function(el) { return true; }, + isDropSupported : function(el) { return true; }, + removeClass : function(el, clazz) { + jsPlumb.CurrentLibrary.getElementObject(el).removeClass(clazz); + }, + removeElement : function(el) { _getElementObject(el).remove(); }, + + setAttribute : function(el, attributeName, attributeValue) { + el.setAttribute(attributeName, attributeValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + var id = jsPlumb.getId(el), + dd = _draggablesById[id]; + if (dd) dd.set("lock", !draggable); + }, + + setDragScope : function(el, scope) { + var id = jsPlumb.getId(el), + dd = _draggablesById[id]; + if (dd) dd.scope = scope; + }, + + setOffset : function(el, o) { + el = _getElementObject(el); + el.set("top", o.top); + el.set("left", o.left); + }, + + stopDrag : function() { + Y.DD.DDM.stopDrag(); + }, + + trigger : function(el, event, originalEvent) { + originalEvent.stopPropagation(); + _getElementObject(el).simulate(event, { + pageX:originalEvent.pageX, + pageY:originalEvent.pageY, + clientX:originalEvent.clientX, + clientY:originalEvent.clientY + }); + }, + + /** + * event unbinding wrapper. + */ + unbind : function(el, event, callback) { + _getElementObject(el).detach(event, callback); + } + }; +})(); \ No newline at end of file diff --git a/build/1.3.16/demo/apidocs/files/jquery-jsPlumb-1-3-16-all-js.html b/archive/1.3.16/demo/apidocs/files/jquery-jsPlumb-1-3-16-all-js.html similarity index 100% rename from build/1.3.16/demo/apidocs/files/jquery-jsPlumb-1-3-16-all-js.html rename to archive/1.3.16/demo/apidocs/files/jquery-jsPlumb-1-3-16-all-js.html diff --git a/build/1.3.16/demo/apidocs/index.html b/archive/1.3.16/demo/apidocs/index.html similarity index 100% rename from build/1.3.16/demo/apidocs/index.html rename to archive/1.3.16/demo/apidocs/index.html diff --git a/build/1.3.16/demo/apidocs/index/Classes.html b/archive/1.3.16/demo/apidocs/index/Classes.html similarity index 100% rename from build/1.3.16/demo/apidocs/index/Classes.html rename to archive/1.3.16/demo/apidocs/index/Classes.html diff --git a/build/1.3.16/demo/apidocs/index/Files.html b/archive/1.3.16/demo/apidocs/index/Files.html similarity index 100% rename from build/1.3.16/demo/apidocs/index/Files.html rename to archive/1.3.16/demo/apidocs/index/Files.html diff --git a/build/1.3.16/demo/apidocs/index/Functions.html b/archive/1.3.16/demo/apidocs/index/Functions.html similarity index 100% rename from build/1.3.16/demo/apidocs/index/Functions.html rename to archive/1.3.16/demo/apidocs/index/Functions.html diff --git a/build/1.3.16/demo/apidocs/index/Functions2.html b/archive/1.3.16/demo/apidocs/index/Functions2.html similarity index 100% rename from build/1.3.16/demo/apidocs/index/Functions2.html rename to archive/1.3.16/demo/apidocs/index/Functions2.html diff --git a/build/1.3.16/demo/apidocs/index/General.html b/archive/1.3.16/demo/apidocs/index/General.html similarity index 100% rename from build/1.3.16/demo/apidocs/index/General.html rename to archive/1.3.16/demo/apidocs/index/General.html diff --git a/build/1.3.16/demo/apidocs/index/General2.html b/archive/1.3.16/demo/apidocs/index/General2.html similarity index 100% rename from build/1.3.16/demo/apidocs/index/General2.html rename to archive/1.3.16/demo/apidocs/index/General2.html diff --git a/build/1.3.16/demo/apidocs/index/General3.html b/archive/1.3.16/demo/apidocs/index/General3.html similarity index 100% rename from build/1.3.16/demo/apidocs/index/General3.html rename to archive/1.3.16/demo/apidocs/index/General3.html diff --git a/build/1.3.16/demo/apidocs/index/Properties.html b/archive/1.3.16/demo/apidocs/index/Properties.html similarity index 100% rename from build/1.3.16/demo/apidocs/index/Properties.html rename to archive/1.3.16/demo/apidocs/index/Properties.html diff --git a/build/1.3.16/demo/apidocs/javascript/main.js b/archive/1.3.16/demo/apidocs/javascript/main.js similarity index 100% rename from build/1.3.16/demo/apidocs/javascript/main.js rename to archive/1.3.16/demo/apidocs/javascript/main.js diff --git a/build/1.3.16/demo/apidocs/javascript/prettify.js b/archive/1.3.16/demo/apidocs/javascript/prettify.js similarity index 100% rename from build/1.3.16/demo/apidocs/javascript/prettify.js rename to archive/1.3.16/demo/apidocs/javascript/prettify.js diff --git a/build/1.3.16/demo/apidocs/javascript/searchdata.js b/archive/1.3.16/demo/apidocs/javascript/searchdata.js similarity index 100% rename from build/1.3.16/demo/apidocs/javascript/searchdata.js rename to archive/1.3.16/demo/apidocs/javascript/searchdata.js diff --git a/build/1.3.16/demo/apidocs/search/ClassesC.html b/archive/1.3.16/demo/apidocs/search/ClassesC.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/ClassesC.html rename to archive/1.3.16/demo/apidocs/search/ClassesC.html diff --git a/build/1.3.16/demo/apidocs/search/ClassesE.html b/archive/1.3.16/demo/apidocs/search/ClassesE.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/ClassesE.html rename to archive/1.3.16/demo/apidocs/search/ClassesE.html diff --git a/build/1.3.16/demo/apidocs/search/ClassesO.html b/archive/1.3.16/demo/apidocs/search/ClassesO.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/ClassesO.html rename to archive/1.3.16/demo/apidocs/search/ClassesO.html diff --git a/build/1.3.16/demo/apidocs/search/FilesJ.html b/archive/1.3.16/demo/apidocs/search/FilesJ.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/FilesJ.html rename to archive/1.3.16/demo/apidocs/search/FilesJ.html diff --git a/build/1.3.16/demo/apidocs/search/FunctionsA.html b/archive/1.3.16/demo/apidocs/search/FunctionsA.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/FunctionsA.html rename to archive/1.3.16/demo/apidocs/search/FunctionsA.html diff --git a/build/1.3.16/demo/apidocs/search/FunctionsB.html b/archive/1.3.16/demo/apidocs/search/FunctionsB.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/FunctionsB.html rename to archive/1.3.16/demo/apidocs/search/FunctionsB.html diff --git a/build/1.3.16/demo/apidocs/search/FunctionsC.html b/archive/1.3.16/demo/apidocs/search/FunctionsC.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/FunctionsC.html rename to archive/1.3.16/demo/apidocs/search/FunctionsC.html diff --git a/build/1.3.16/demo/apidocs/search/FunctionsD.html b/archive/1.3.16/demo/apidocs/search/FunctionsD.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/FunctionsD.html rename to archive/1.3.16/demo/apidocs/search/FunctionsD.html diff --git a/build/1.3.16/demo/apidocs/search/FunctionsE.html b/archive/1.3.16/demo/apidocs/search/FunctionsE.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/FunctionsE.html rename to archive/1.3.16/demo/apidocs/search/FunctionsE.html diff --git a/build/1.3.16/demo/apidocs/search/FunctionsG.html b/archive/1.3.16/demo/apidocs/search/FunctionsG.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/FunctionsG.html rename to archive/1.3.16/demo/apidocs/search/FunctionsG.html diff --git a/build/1.3.16/demo/apidocs/search/FunctionsH.html b/archive/1.3.16/demo/apidocs/search/FunctionsH.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/FunctionsH.html rename to archive/1.3.16/demo/apidocs/search/FunctionsH.html diff --git a/build/1.3.16/demo/apidocs/search/FunctionsI.html b/archive/1.3.16/demo/apidocs/search/FunctionsI.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/FunctionsI.html rename to archive/1.3.16/demo/apidocs/search/FunctionsI.html diff --git a/build/1.3.16/demo/apidocs/search/FunctionsM.html b/archive/1.3.16/demo/apidocs/search/FunctionsM.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/FunctionsM.html rename to archive/1.3.16/demo/apidocs/search/FunctionsM.html diff --git a/build/1.3.16/demo/apidocs/search/FunctionsP.html b/archive/1.3.16/demo/apidocs/search/FunctionsP.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/FunctionsP.html rename to archive/1.3.16/demo/apidocs/search/FunctionsP.html diff --git a/build/1.3.16/demo/apidocs/search/FunctionsR.html b/archive/1.3.16/demo/apidocs/search/FunctionsR.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/FunctionsR.html rename to archive/1.3.16/demo/apidocs/search/FunctionsR.html diff --git a/build/1.3.16/demo/apidocs/search/FunctionsS.html b/archive/1.3.16/demo/apidocs/search/FunctionsS.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/FunctionsS.html rename to archive/1.3.16/demo/apidocs/search/FunctionsS.html diff --git a/build/1.3.16/demo/apidocs/search/FunctionsT.html b/archive/1.3.16/demo/apidocs/search/FunctionsT.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/FunctionsT.html rename to archive/1.3.16/demo/apidocs/search/FunctionsT.html diff --git a/build/1.3.16/demo/apidocs/search/FunctionsU.html b/archive/1.3.16/demo/apidocs/search/FunctionsU.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/FunctionsU.html rename to archive/1.3.16/demo/apidocs/search/FunctionsU.html diff --git a/build/1.3.16/demo/apidocs/search/GeneralA.html b/archive/1.3.16/demo/apidocs/search/GeneralA.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/GeneralA.html rename to archive/1.3.16/demo/apidocs/search/GeneralA.html diff --git a/build/1.3.16/demo/apidocs/search/GeneralB.html b/archive/1.3.16/demo/apidocs/search/GeneralB.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/GeneralB.html rename to archive/1.3.16/demo/apidocs/search/GeneralB.html diff --git a/build/1.3.16/demo/apidocs/search/GeneralC.html b/archive/1.3.16/demo/apidocs/search/GeneralC.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/GeneralC.html rename to archive/1.3.16/demo/apidocs/search/GeneralC.html diff --git a/build/1.3.16/demo/apidocs/search/GeneralD.html b/archive/1.3.16/demo/apidocs/search/GeneralD.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/GeneralD.html rename to archive/1.3.16/demo/apidocs/search/GeneralD.html diff --git a/build/1.3.16/demo/apidocs/search/GeneralE.html b/archive/1.3.16/demo/apidocs/search/GeneralE.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/GeneralE.html rename to archive/1.3.16/demo/apidocs/search/GeneralE.html diff --git a/build/1.3.16/demo/apidocs/search/GeneralF.html b/archive/1.3.16/demo/apidocs/search/GeneralF.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/GeneralF.html rename to archive/1.3.16/demo/apidocs/search/GeneralF.html diff --git a/build/1.3.16/demo/apidocs/search/GeneralG.html b/archive/1.3.16/demo/apidocs/search/GeneralG.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/GeneralG.html rename to archive/1.3.16/demo/apidocs/search/GeneralG.html diff --git a/build/1.3.16/demo/apidocs/search/GeneralH.html b/archive/1.3.16/demo/apidocs/search/GeneralH.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/GeneralH.html rename to archive/1.3.16/demo/apidocs/search/GeneralH.html diff --git a/build/1.3.16/demo/apidocs/search/GeneralI.html b/archive/1.3.16/demo/apidocs/search/GeneralI.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/GeneralI.html rename to archive/1.3.16/demo/apidocs/search/GeneralI.html diff --git a/build/1.3.16/demo/apidocs/search/GeneralJ.html b/archive/1.3.16/demo/apidocs/search/GeneralJ.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/GeneralJ.html rename to archive/1.3.16/demo/apidocs/search/GeneralJ.html diff --git a/build/1.3.16/demo/apidocs/search/GeneralL.html b/archive/1.3.16/demo/apidocs/search/GeneralL.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/GeneralL.html rename to archive/1.3.16/demo/apidocs/search/GeneralL.html diff --git a/build/1.3.16/demo/apidocs/search/GeneralM.html b/archive/1.3.16/demo/apidocs/search/GeneralM.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/GeneralM.html rename to archive/1.3.16/demo/apidocs/search/GeneralM.html diff --git a/build/1.3.16/demo/apidocs/search/GeneralO.html b/archive/1.3.16/demo/apidocs/search/GeneralO.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/GeneralO.html rename to archive/1.3.16/demo/apidocs/search/GeneralO.html diff --git a/build/1.3.16/demo/apidocs/search/GeneralP.html b/archive/1.3.16/demo/apidocs/search/GeneralP.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/GeneralP.html rename to archive/1.3.16/demo/apidocs/search/GeneralP.html diff --git a/build/1.3.16/demo/apidocs/search/GeneralR.html b/archive/1.3.16/demo/apidocs/search/GeneralR.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/GeneralR.html rename to archive/1.3.16/demo/apidocs/search/GeneralR.html diff --git a/build/1.3.16/demo/apidocs/search/GeneralS.html b/archive/1.3.16/demo/apidocs/search/GeneralS.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/GeneralS.html rename to archive/1.3.16/demo/apidocs/search/GeneralS.html diff --git a/build/1.3.16/demo/apidocs/search/GeneralT.html b/archive/1.3.16/demo/apidocs/search/GeneralT.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/GeneralT.html rename to archive/1.3.16/demo/apidocs/search/GeneralT.html diff --git a/build/1.3.16/demo/apidocs/search/GeneralU.html b/archive/1.3.16/demo/apidocs/search/GeneralU.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/GeneralU.html rename to archive/1.3.16/demo/apidocs/search/GeneralU.html diff --git a/build/1.3.16/demo/apidocs/search/GeneralV.html b/archive/1.3.16/demo/apidocs/search/GeneralV.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/GeneralV.html rename to archive/1.3.16/demo/apidocs/search/GeneralV.html diff --git a/build/1.3.16/demo/apidocs/search/NoResults.html b/archive/1.3.16/demo/apidocs/search/NoResults.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/NoResults.html rename to archive/1.3.16/demo/apidocs/search/NoResults.html diff --git a/build/1.3.16/demo/apidocs/search/PropertiesA.html b/archive/1.3.16/demo/apidocs/search/PropertiesA.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/PropertiesA.html rename to archive/1.3.16/demo/apidocs/search/PropertiesA.html diff --git a/build/1.3.16/demo/apidocs/search/PropertiesB.html b/archive/1.3.16/demo/apidocs/search/PropertiesB.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/PropertiesB.html rename to archive/1.3.16/demo/apidocs/search/PropertiesB.html diff --git a/build/1.3.16/demo/apidocs/search/PropertiesC.html b/archive/1.3.16/demo/apidocs/search/PropertiesC.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/PropertiesC.html rename to archive/1.3.16/demo/apidocs/search/PropertiesC.html diff --git a/build/1.3.16/demo/apidocs/search/PropertiesD.html b/archive/1.3.16/demo/apidocs/search/PropertiesD.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/PropertiesD.html rename to archive/1.3.16/demo/apidocs/search/PropertiesD.html diff --git a/build/1.3.16/demo/apidocs/search/PropertiesE.html b/archive/1.3.16/demo/apidocs/search/PropertiesE.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/PropertiesE.html rename to archive/1.3.16/demo/apidocs/search/PropertiesE.html diff --git a/build/1.3.16/demo/apidocs/search/PropertiesL.html b/archive/1.3.16/demo/apidocs/search/PropertiesL.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/PropertiesL.html rename to archive/1.3.16/demo/apidocs/search/PropertiesL.html diff --git a/build/1.3.16/demo/apidocs/search/PropertiesO.html b/archive/1.3.16/demo/apidocs/search/PropertiesO.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/PropertiesO.html rename to archive/1.3.16/demo/apidocs/search/PropertiesO.html diff --git a/build/1.3.16/demo/apidocs/search/PropertiesP.html b/archive/1.3.16/demo/apidocs/search/PropertiesP.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/PropertiesP.html rename to archive/1.3.16/demo/apidocs/search/PropertiesP.html diff --git a/build/1.3.16/demo/apidocs/search/PropertiesR.html b/archive/1.3.16/demo/apidocs/search/PropertiesR.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/PropertiesR.html rename to archive/1.3.16/demo/apidocs/search/PropertiesR.html diff --git a/build/1.3.16/demo/apidocs/search/PropertiesS.html b/archive/1.3.16/demo/apidocs/search/PropertiesS.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/PropertiesS.html rename to archive/1.3.16/demo/apidocs/search/PropertiesS.html diff --git a/build/1.3.16/demo/apidocs/search/PropertiesT.html b/archive/1.3.16/demo/apidocs/search/PropertiesT.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/PropertiesT.html rename to archive/1.3.16/demo/apidocs/search/PropertiesT.html diff --git a/build/1.3.16/demo/apidocs/search/PropertiesV.html b/archive/1.3.16/demo/apidocs/search/PropertiesV.html similarity index 100% rename from build/1.3.16/demo/apidocs/search/PropertiesV.html rename to archive/1.3.16/demo/apidocs/search/PropertiesV.html diff --git a/build/1.3.16/demo/apidocs/styles/main.css b/archive/1.3.16/demo/apidocs/styles/main.css similarity index 100% rename from build/1.3.16/demo/apidocs/styles/main.css rename to archive/1.3.16/demo/apidocs/styles/main.css diff --git a/build/1.3.16/demo/css/anchorDemo.css b/archive/1.3.16/demo/css/anchorDemo.css similarity index 100% rename from build/1.3.16/demo/css/anchorDemo.css rename to archive/1.3.16/demo/css/anchorDemo.css diff --git a/build/1.3.16/demo/css/chartDemo.css b/archive/1.3.16/demo/css/chartDemo.css similarity index 100% rename from build/1.3.16/demo/css/chartDemo.css rename to archive/1.3.16/demo/css/chartDemo.css diff --git a/build/1.3.16/demo/css/demo.css b/archive/1.3.16/demo/css/demo.css similarity index 100% rename from build/1.3.16/demo/css/demo.css rename to archive/1.3.16/demo/css/demo.css diff --git a/build/1.3.16/demo/css/dragAnimDemo.css b/archive/1.3.16/demo/css/dragAnimDemo.css similarity index 100% rename from build/1.3.16/demo/css/dragAnimDemo.css rename to archive/1.3.16/demo/css/dragAnimDemo.css diff --git a/build/1.3.16/demo/css/draggableConnectorsDemo.css b/archive/1.3.16/demo/css/draggableConnectorsDemo.css similarity index 100% rename from build/1.3.16/demo/css/draggableConnectorsDemo.css rename to archive/1.3.16/demo/css/draggableConnectorsDemo.css diff --git a/build/1.3.16/demo/css/dynamicAnchorsDemo.css b/archive/1.3.16/demo/css/dynamicAnchorsDemo.css similarity index 100% rename from build/1.3.16/demo/css/dynamicAnchorsDemo.css rename to archive/1.3.16/demo/css/dynamicAnchorsDemo.css diff --git a/build/1.3.16/demo/css/flowchartDemo.css b/archive/1.3.16/demo/css/flowchartDemo.css similarity index 100% rename from build/1.3.16/demo/css/flowchartDemo.css rename to archive/1.3.16/demo/css/flowchartDemo.css diff --git a/build/1.3.16/demo/css/jsPlumbDemo.css b/archive/1.3.16/demo/css/jsPlumbDemo.css similarity index 100% rename from build/1.3.16/demo/css/jsPlumbDemo.css rename to archive/1.3.16/demo/css/jsPlumbDemo.css diff --git a/build/1.3.16/demo/css/makeTargetDemo.css b/archive/1.3.16/demo/css/makeTargetDemo.css similarity index 100% rename from build/1.3.16/demo/css/makeTargetDemo.css rename to archive/1.3.16/demo/css/makeTargetDemo.css diff --git a/build/1.3.16/demo/css/multipleJsPlumbDemo.css b/archive/1.3.16/demo/css/multipleJsPlumbDemo.css similarity index 100% rename from build/1.3.16/demo/css/multipleJsPlumbDemo.css rename to archive/1.3.16/demo/css/multipleJsPlumbDemo.css diff --git a/build/1.3.16/demo/css/perimeterAnchorsDemo.css b/archive/1.3.16/demo/css/perimeterAnchorsDemo.css similarity index 100% rename from build/1.3.16/demo/css/perimeterAnchorsDemo.css rename to archive/1.3.16/demo/css/perimeterAnchorsDemo.css diff --git a/build/1.3.16/demo/css/selectDemo.css b/archive/1.3.16/demo/css/selectDemo.css similarity index 100% rename from build/1.3.16/demo/css/selectDemo.css rename to archive/1.3.16/demo/css/selectDemo.css diff --git a/build/1.3.16/demo/css/stateMachineDemo.css b/archive/1.3.16/demo/css/stateMachineDemo.css similarity index 100% rename from build/1.3.16/demo/css/stateMachineDemo.css rename to archive/1.3.16/demo/css/stateMachineDemo.css diff --git a/build/1.3.16/demo/doc/archive/1.2.6/content.html b/archive/1.3.16/demo/doc/archive/1.2.6/content.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.2.6/content.html rename to archive/1.3.16/demo/doc/archive/1.2.6/content.html diff --git a/build/1.3.16/demo/doc/archive/1.2.6/index.html b/archive/1.3.16/demo/doc/archive/1.2.6/index.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.2.6/index.html rename to archive/1.3.16/demo/doc/archive/1.2.6/index.html diff --git a/build/1.3.16/demo/doc/archive/1.2.6/usage.html b/archive/1.3.16/demo/doc/archive/1.2.6/usage.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.2.6/usage.html rename to archive/1.3.16/demo/doc/archive/1.2.6/usage.html diff --git a/build/1.3.16/demo/doc/archive/1.3.10/content.html b/archive/1.3.16/demo/doc/archive/1.3.10/content.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.10/content.html rename to archive/1.3.16/demo/doc/archive/1.3.10/content.html diff --git a/build/1.3.16/demo/doc/archive/1.3.10/index.html b/archive/1.3.16/demo/doc/archive/1.3.10/index.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.10/index.html rename to archive/1.3.16/demo/doc/archive/1.3.10/index.html diff --git a/build/1.3.16/demo/doc/archive/1.3.10/jsPlumbDoc.css b/archive/1.3.16/demo/doc/archive/1.3.10/jsPlumbDoc.css similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.10/jsPlumbDoc.css rename to archive/1.3.16/demo/doc/archive/1.3.10/jsPlumbDoc.css diff --git a/build/1.3.16/demo/doc/archive/1.3.10/usage.html b/archive/1.3.16/demo/doc/archive/1.3.10/usage.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.10/usage.html rename to archive/1.3.16/demo/doc/archive/1.3.10/usage.html diff --git a/build/1.3.16/demo/doc/archive/1.3.11/content.html b/archive/1.3.16/demo/doc/archive/1.3.11/content.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.11/content.html rename to archive/1.3.16/demo/doc/archive/1.3.11/content.html diff --git a/build/1.3.16/demo/doc/archive/1.3.11/index.html b/archive/1.3.16/demo/doc/archive/1.3.11/index.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.11/index.html rename to archive/1.3.16/demo/doc/archive/1.3.11/index.html diff --git a/build/1.3.16/demo/doc/archive/1.3.11/jsPlumbDoc.css b/archive/1.3.16/demo/doc/archive/1.3.11/jsPlumbDoc.css similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.11/jsPlumbDoc.css rename to archive/1.3.16/demo/doc/archive/1.3.11/jsPlumbDoc.css diff --git a/build/1.3.16/demo/doc/archive/1.3.11/usage.html b/archive/1.3.16/demo/doc/archive/1.3.11/usage.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.11/usage.html rename to archive/1.3.16/demo/doc/archive/1.3.11/usage.html diff --git a/build/1.3.16/demo/doc/archive/1.3.12/content.html b/archive/1.3.16/demo/doc/archive/1.3.12/content.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.12/content.html rename to archive/1.3.16/demo/doc/archive/1.3.12/content.html diff --git a/build/1.3.16/demo/doc/archive/1.3.12/index.html b/archive/1.3.16/demo/doc/archive/1.3.12/index.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.12/index.html rename to archive/1.3.16/demo/doc/archive/1.3.12/index.html diff --git a/build/1.3.16/demo/doc/archive/1.3.12/jsPlumbDoc.css b/archive/1.3.16/demo/doc/archive/1.3.12/jsPlumbDoc.css similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.12/jsPlumbDoc.css rename to archive/1.3.16/demo/doc/archive/1.3.12/jsPlumbDoc.css diff --git a/build/1.3.16/demo/doc/archive/1.3.12/usage.html b/archive/1.3.16/demo/doc/archive/1.3.12/usage.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.12/usage.html rename to archive/1.3.16/demo/doc/archive/1.3.12/usage.html diff --git a/build/1.3.16/demo/doc/archive/1.3.13/content.html b/archive/1.3.16/demo/doc/archive/1.3.13/content.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.13/content.html rename to archive/1.3.16/demo/doc/archive/1.3.13/content.html diff --git a/build/1.3.16/demo/doc/archive/1.3.13/index.html b/archive/1.3.16/demo/doc/archive/1.3.13/index.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.13/index.html rename to archive/1.3.16/demo/doc/archive/1.3.13/index.html diff --git a/build/1.3.16/demo/doc/archive/1.3.13/jsPlumbDoc.css b/archive/1.3.16/demo/doc/archive/1.3.13/jsPlumbDoc.css similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.13/jsPlumbDoc.css rename to archive/1.3.16/demo/doc/archive/1.3.13/jsPlumbDoc.css diff --git a/build/1.3.16/demo/doc/archive/1.3.13/usage.html b/archive/1.3.16/demo/doc/archive/1.3.13/usage.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.13/usage.html rename to archive/1.3.16/demo/doc/archive/1.3.13/usage.html diff --git a/build/1.3.16/demo/doc/archive/1.3.14/content.html b/archive/1.3.16/demo/doc/archive/1.3.14/content.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.14/content.html rename to archive/1.3.16/demo/doc/archive/1.3.14/content.html diff --git a/build/1.3.16/demo/doc/archive/1.3.14/index.html b/archive/1.3.16/demo/doc/archive/1.3.14/index.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.14/index.html rename to archive/1.3.16/demo/doc/archive/1.3.14/index.html diff --git a/build/1.3.16/demo/doc/archive/1.3.14/jsPlumbDoc.css b/archive/1.3.16/demo/doc/archive/1.3.14/jsPlumbDoc.css similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.14/jsPlumbDoc.css rename to archive/1.3.16/demo/doc/archive/1.3.14/jsPlumbDoc.css diff --git a/build/1.3.16/demo/doc/archive/1.3.14/usage.html b/archive/1.3.16/demo/doc/archive/1.3.14/usage.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.14/usage.html rename to archive/1.3.16/demo/doc/archive/1.3.14/usage.html diff --git a/build/1.3.16/demo/doc/archive/1.3.15/content.html b/archive/1.3.16/demo/doc/archive/1.3.15/content.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.15/content.html rename to archive/1.3.16/demo/doc/archive/1.3.15/content.html diff --git a/build/1.3.16/demo/doc/archive/1.3.15/index.html b/archive/1.3.16/demo/doc/archive/1.3.15/index.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.15/index.html rename to archive/1.3.16/demo/doc/archive/1.3.15/index.html diff --git a/build/1.3.16/demo/doc/archive/1.3.15/jsPlumbDoc.css b/archive/1.3.16/demo/doc/archive/1.3.15/jsPlumbDoc.css similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.15/jsPlumbDoc.css rename to archive/1.3.16/demo/doc/archive/1.3.15/jsPlumbDoc.css diff --git a/build/1.3.16/demo/doc/archive/1.3.15/usage.html b/archive/1.3.16/demo/doc/archive/1.3.15/usage.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.15/usage.html rename to archive/1.3.16/demo/doc/archive/1.3.15/usage.html diff --git a/build/1.3.16/demo/doc/archive/1.3.2/content.html b/archive/1.3.16/demo/doc/archive/1.3.2/content.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.2/content.html rename to archive/1.3.16/demo/doc/archive/1.3.2/content.html diff --git a/build/1.3.16/demo/doc/archive/1.3.2/index.html b/archive/1.3.16/demo/doc/archive/1.3.2/index.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.2/index.html rename to archive/1.3.16/demo/doc/archive/1.3.2/index.html diff --git a/build/1.3.16/demo/doc/archive/1.3.2/usage.html b/archive/1.3.16/demo/doc/archive/1.3.2/usage.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.2/usage.html rename to archive/1.3.16/demo/doc/archive/1.3.2/usage.html diff --git a/build/1.3.16/demo/doc/archive/1.3.3/content.html b/archive/1.3.16/demo/doc/archive/1.3.3/content.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.3/content.html rename to archive/1.3.16/demo/doc/archive/1.3.3/content.html diff --git a/build/1.3.16/demo/doc/archive/1.3.3/index.html b/archive/1.3.16/demo/doc/archive/1.3.3/index.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.3/index.html rename to archive/1.3.16/demo/doc/archive/1.3.3/index.html diff --git a/build/1.3.16/demo/doc/archive/1.3.3/jsPlumbDoc.css b/archive/1.3.16/demo/doc/archive/1.3.3/jsPlumbDoc.css similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.3/jsPlumbDoc.css rename to archive/1.3.16/demo/doc/archive/1.3.3/jsPlumbDoc.css diff --git a/build/1.3.16/demo/doc/archive/1.3.3/usage.html b/archive/1.3.16/demo/doc/archive/1.3.3/usage.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.3/usage.html rename to archive/1.3.16/demo/doc/archive/1.3.3/usage.html diff --git a/build/1.3.16/demo/doc/archive/1.3.4/content.html b/archive/1.3.16/demo/doc/archive/1.3.4/content.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.4/content.html rename to archive/1.3.16/demo/doc/archive/1.3.4/content.html diff --git a/build/1.3.16/demo/doc/archive/1.3.4/index.html b/archive/1.3.16/demo/doc/archive/1.3.4/index.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.4/index.html rename to archive/1.3.16/demo/doc/archive/1.3.4/index.html diff --git a/build/1.3.16/demo/doc/archive/1.3.4/jsPlumbDoc.css b/archive/1.3.16/demo/doc/archive/1.3.4/jsPlumbDoc.css similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.4/jsPlumbDoc.css rename to archive/1.3.16/demo/doc/archive/1.3.4/jsPlumbDoc.css diff --git a/build/1.3.16/demo/doc/archive/1.3.4/usage.html b/archive/1.3.16/demo/doc/archive/1.3.4/usage.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.4/usage.html rename to archive/1.3.16/demo/doc/archive/1.3.4/usage.html diff --git a/build/1.3.16/demo/doc/archive/1.3.5/content.html b/archive/1.3.16/demo/doc/archive/1.3.5/content.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.5/content.html rename to archive/1.3.16/demo/doc/archive/1.3.5/content.html diff --git a/build/1.3.16/demo/doc/archive/1.3.5/index.html b/archive/1.3.16/demo/doc/archive/1.3.5/index.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.5/index.html rename to archive/1.3.16/demo/doc/archive/1.3.5/index.html diff --git a/build/1.3.16/demo/doc/archive/1.3.5/jsPlumbDoc.css b/archive/1.3.16/demo/doc/archive/1.3.5/jsPlumbDoc.css similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.5/jsPlumbDoc.css rename to archive/1.3.16/demo/doc/archive/1.3.5/jsPlumbDoc.css diff --git a/build/1.3.16/demo/doc/archive/1.3.5/usage.html b/archive/1.3.16/demo/doc/archive/1.3.5/usage.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.5/usage.html rename to archive/1.3.16/demo/doc/archive/1.3.5/usage.html diff --git a/build/1.3.16/demo/doc/archive/1.3.6/content.html b/archive/1.3.16/demo/doc/archive/1.3.6/content.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.6/content.html rename to archive/1.3.16/demo/doc/archive/1.3.6/content.html diff --git a/build/1.3.16/demo/doc/archive/1.3.6/index.html b/archive/1.3.16/demo/doc/archive/1.3.6/index.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.6/index.html rename to archive/1.3.16/demo/doc/archive/1.3.6/index.html diff --git a/build/1.3.16/demo/doc/archive/1.3.6/jsPlumbDoc.css b/archive/1.3.16/demo/doc/archive/1.3.6/jsPlumbDoc.css similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.6/jsPlumbDoc.css rename to archive/1.3.16/demo/doc/archive/1.3.6/jsPlumbDoc.css diff --git a/build/1.3.16/demo/doc/archive/1.3.6/usage.html b/archive/1.3.16/demo/doc/archive/1.3.6/usage.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.6/usage.html rename to archive/1.3.16/demo/doc/archive/1.3.6/usage.html diff --git a/build/1.3.16/demo/doc/archive/1.3.7/content.html b/archive/1.3.16/demo/doc/archive/1.3.7/content.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.7/content.html rename to archive/1.3.16/demo/doc/archive/1.3.7/content.html diff --git a/build/1.3.16/demo/doc/archive/1.3.7/index.html b/archive/1.3.16/demo/doc/archive/1.3.7/index.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.7/index.html rename to archive/1.3.16/demo/doc/archive/1.3.7/index.html diff --git a/build/1.3.16/demo/doc/archive/1.3.7/jsPlumbDoc.css b/archive/1.3.16/demo/doc/archive/1.3.7/jsPlumbDoc.css similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.7/jsPlumbDoc.css rename to archive/1.3.16/demo/doc/archive/1.3.7/jsPlumbDoc.css diff --git a/build/1.3.16/demo/doc/archive/1.3.7/usage.html b/archive/1.3.16/demo/doc/archive/1.3.7/usage.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.7/usage.html rename to archive/1.3.16/demo/doc/archive/1.3.7/usage.html diff --git a/build/1.3.16/demo/doc/archive/1.3.8/content.html b/archive/1.3.16/demo/doc/archive/1.3.8/content.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.8/content.html rename to archive/1.3.16/demo/doc/archive/1.3.8/content.html diff --git a/build/1.3.16/demo/doc/archive/1.3.8/index.html b/archive/1.3.16/demo/doc/archive/1.3.8/index.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.8/index.html rename to archive/1.3.16/demo/doc/archive/1.3.8/index.html diff --git a/build/1.3.16/demo/doc/archive/1.3.8/jsPlumbDoc.css b/archive/1.3.16/demo/doc/archive/1.3.8/jsPlumbDoc.css similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.8/jsPlumbDoc.css rename to archive/1.3.16/demo/doc/archive/1.3.8/jsPlumbDoc.css diff --git a/build/1.3.16/demo/doc/archive/1.3.8/usage.html b/archive/1.3.16/demo/doc/archive/1.3.8/usage.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.8/usage.html rename to archive/1.3.16/demo/doc/archive/1.3.8/usage.html diff --git a/build/1.3.16/demo/doc/archive/1.3.9/content.html b/archive/1.3.16/demo/doc/archive/1.3.9/content.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.9/content.html rename to archive/1.3.16/demo/doc/archive/1.3.9/content.html diff --git a/build/1.3.16/demo/doc/archive/1.3.9/index.html b/archive/1.3.16/demo/doc/archive/1.3.9/index.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.9/index.html rename to archive/1.3.16/demo/doc/archive/1.3.9/index.html diff --git a/build/1.3.16/demo/doc/archive/1.3.9/jsPlumbDoc.css b/archive/1.3.16/demo/doc/archive/1.3.9/jsPlumbDoc.css similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.9/jsPlumbDoc.css rename to archive/1.3.16/demo/doc/archive/1.3.9/jsPlumbDoc.css diff --git a/build/1.3.16/demo/doc/archive/1.3.9/usage.html b/archive/1.3.16/demo/doc/archive/1.3.9/usage.html similarity index 100% rename from build/1.3.16/demo/doc/archive/1.3.9/usage.html rename to archive/1.3.16/demo/doc/archive/1.3.9/usage.html diff --git a/build/1.3.16/demo/doc/content.html b/archive/1.3.16/demo/doc/content.html similarity index 100% rename from build/1.3.16/demo/doc/content.html rename to archive/1.3.16/demo/doc/content.html diff --git a/build/1.3.16/demo/doc/index.html b/archive/1.3.16/demo/doc/index.html similarity index 100% rename from build/1.3.16/demo/doc/index.html rename to archive/1.3.16/demo/doc/index.html diff --git a/build/1.3.16/demo/doc/jsPlumbDoc.css b/archive/1.3.16/demo/doc/jsPlumbDoc.css similarity index 100% rename from build/1.3.16/demo/doc/jsPlumbDoc.css rename to archive/1.3.16/demo/doc/jsPlumbDoc.css diff --git a/build/1.3.16/demo/doc/usage.html b/archive/1.3.16/demo/doc/usage.html similarity index 100% rename from build/1.3.16/demo/doc/usage.html rename to archive/1.3.16/demo/doc/usage.html diff --git a/build/1.3.16/demo/img/bigdot.jpg b/archive/1.3.16/demo/img/bigdot.jpg similarity index 100% rename from build/1.3.16/demo/img/bigdot.jpg rename to archive/1.3.16/demo/img/bigdot.jpg diff --git a/build/1.3.16/demo/img/bigdot.png b/archive/1.3.16/demo/img/bigdot.png similarity index 100% rename from build/1.3.16/demo/img/bigdot.png rename to archive/1.3.16/demo/img/bigdot.png diff --git a/build/1.3.16/demo/img/bigdot.xcf b/archive/1.3.16/demo/img/bigdot.xcf similarity index 100% rename from build/1.3.16/demo/img/bigdot.xcf rename to archive/1.3.16/demo/img/bigdot.xcf diff --git a/build/1.3.16/demo/img/circle.png b/archive/1.3.16/demo/img/circle.png similarity index 100% rename from build/1.3.16/demo/img/circle.png rename to archive/1.3.16/demo/img/circle.png diff --git a/build/1.3.16/demo/img/diamond.png b/archive/1.3.16/demo/img/diamond.png similarity index 100% rename from build/1.3.16/demo/img/diamond.png rename to archive/1.3.16/demo/img/diamond.png diff --git a/build/1.3.16/demo/img/dragging_1.jpg b/archive/1.3.16/demo/img/dragging_1.jpg similarity index 100% rename from build/1.3.16/demo/img/dragging_1.jpg rename to archive/1.3.16/demo/img/dragging_1.jpg diff --git a/build/1.3.16/demo/img/dragging_2.jpg b/archive/1.3.16/demo/img/dragging_2.jpg similarity index 100% rename from build/1.3.16/demo/img/dragging_2.jpg rename to archive/1.3.16/demo/img/dragging_2.jpg diff --git a/build/1.3.16/demo/img/dragging_3.jpg b/archive/1.3.16/demo/img/dragging_3.jpg similarity index 100% rename from build/1.3.16/demo/img/dragging_3.jpg rename to archive/1.3.16/demo/img/dragging_3.jpg diff --git a/build/1.3.16/demo/img/dynamicAnchorBg.jpg b/archive/1.3.16/demo/img/dynamicAnchorBg.jpg similarity index 100% rename from build/1.3.16/demo/img/dynamicAnchorBg.jpg rename to archive/1.3.16/demo/img/dynamicAnchorBg.jpg diff --git a/build/1.3.16/demo/img/ellipse.png b/archive/1.3.16/demo/img/ellipse.png similarity index 100% rename from build/1.3.16/demo/img/ellipse.png rename to archive/1.3.16/demo/img/ellipse.png diff --git a/build/1.3.16/demo/img/endpointTest1.png b/archive/1.3.16/demo/img/endpointTest1.png similarity index 100% rename from build/1.3.16/demo/img/endpointTest1.png rename to archive/1.3.16/demo/img/endpointTest1.png diff --git a/build/1.3.16/demo/img/endpointTest1.xcf b/archive/1.3.16/demo/img/endpointTest1.xcf similarity index 100% rename from build/1.3.16/demo/img/endpointTest1.xcf rename to archive/1.3.16/demo/img/endpointTest1.xcf diff --git a/build/1.3.16/demo/img/index-bg.gif b/archive/1.3.16/demo/img/index-bg.gif similarity index 100% rename from build/1.3.16/demo/img/index-bg.gif rename to archive/1.3.16/demo/img/index-bg.gif diff --git a/build/1.3.16/demo/img/issue4_final.jpg b/archive/1.3.16/demo/img/issue4_final.jpg similarity index 100% rename from build/1.3.16/demo/img/issue4_final.jpg rename to archive/1.3.16/demo/img/issue4_final.jpg diff --git a/build/1.3.16/demo/img/littledot.png b/archive/1.3.16/demo/img/littledot.png similarity index 100% rename from build/1.3.16/demo/img/littledot.png rename to archive/1.3.16/demo/img/littledot.png diff --git a/build/1.3.16/demo/img/littledot.xcf b/archive/1.3.16/demo/img/littledot.xcf similarity index 100% rename from build/1.3.16/demo/img/littledot.xcf rename to archive/1.3.16/demo/img/littledot.xcf diff --git a/build/1.3.16/demo/img/pattern.jpg b/archive/1.3.16/demo/img/pattern.jpg similarity index 100% rename from build/1.3.16/demo/img/pattern.jpg rename to archive/1.3.16/demo/img/pattern.jpg diff --git a/build/1.3.16/demo/img/rectangle.png b/archive/1.3.16/demo/img/rectangle.png similarity index 100% rename from build/1.3.16/demo/img/rectangle.png rename to archive/1.3.16/demo/img/rectangle.png diff --git a/build/1.3.16/demo/img/square.png b/archive/1.3.16/demo/img/square.png similarity index 100% rename from build/1.3.16/demo/img/square.png rename to archive/1.3.16/demo/img/square.png diff --git a/build/1.3.16/demo/img/swappedAnchors.jpg b/archive/1.3.16/demo/img/swappedAnchors.jpg similarity index 100% rename from build/1.3.16/demo/img/swappedAnchors.jpg rename to archive/1.3.16/demo/img/swappedAnchors.jpg diff --git a/build/1.3.16/demo/img/triangle.png b/archive/1.3.16/demo/img/triangle.png similarity index 100% rename from build/1.3.16/demo/img/triangle.png rename to archive/1.3.16/demo/img/triangle.png diff --git a/build/1.3.16/demo/img/triangle_90.png b/archive/1.3.16/demo/img/triangle_90.png similarity index 100% rename from build/1.3.16/demo/img/triangle_90.png rename to archive/1.3.16/demo/img/triangle_90.png diff --git a/build/1.3.16/demo/index.html b/archive/1.3.16/demo/index.html similarity index 100% rename from build/1.3.16/demo/index.html rename to archive/1.3.16/demo/index.html diff --git a/build/1.3.16/demo/jquery/anchorDemo.html b/archive/1.3.16/demo/jquery/anchorDemo.html similarity index 100% rename from build/1.3.16/demo/jquery/anchorDemo.html rename to archive/1.3.16/demo/jquery/anchorDemo.html diff --git a/build/1.3.16/demo/jquery/chartDemo.html b/archive/1.3.16/demo/jquery/chartDemo.html similarity index 100% rename from build/1.3.16/demo/jquery/chartDemo.html rename to archive/1.3.16/demo/jquery/chartDemo.html diff --git a/build/1.3.16/demo/jquery/demo.html b/archive/1.3.16/demo/jquery/demo.html similarity index 100% rename from build/1.3.16/demo/jquery/demo.html rename to archive/1.3.16/demo/jquery/demo.html diff --git a/build/1.3.16/demo/jquery/dragAnimDemo.html b/archive/1.3.16/demo/jquery/dragAnimDemo.html similarity index 100% rename from build/1.3.16/demo/jquery/dragAnimDemo.html rename to archive/1.3.16/demo/jquery/dragAnimDemo.html diff --git a/build/1.3.16/demo/jquery/draggableConnectorsDemo.html b/archive/1.3.16/demo/jquery/draggableConnectorsDemo.html similarity index 100% rename from build/1.3.16/demo/jquery/draggableConnectorsDemo.html rename to archive/1.3.16/demo/jquery/draggableConnectorsDemo.html diff --git a/build/1.3.16/demo/jquery/dynamicAnchorsDemo.html b/archive/1.3.16/demo/jquery/dynamicAnchorsDemo.html similarity index 100% rename from build/1.3.16/demo/jquery/dynamicAnchorsDemo.html rename to archive/1.3.16/demo/jquery/dynamicAnchorsDemo.html diff --git a/build/1.3.16/demo/jquery/flowchartConnectorsDemo.html b/archive/1.3.16/demo/jquery/flowchartConnectorsDemo.html similarity index 100% rename from build/1.3.16/demo/jquery/flowchartConnectorsDemo.html rename to archive/1.3.16/demo/jquery/flowchartConnectorsDemo.html diff --git a/build/1.3.16/demo/jquery/loadTest.html b/archive/1.3.16/demo/jquery/loadTest.html similarity index 100% rename from build/1.3.16/demo/jquery/loadTest.html rename to archive/1.3.16/demo/jquery/loadTest.html diff --git a/build/1.3.16/demo/jquery/perimeterAnchorsDemo.html b/archive/1.3.16/demo/jquery/perimeterAnchorsDemo.html similarity index 100% rename from build/1.3.16/demo/jquery/perimeterAnchorsDemo.html rename to archive/1.3.16/demo/jquery/perimeterAnchorsDemo.html diff --git a/build/1.3.16/demo/jquery/stateMachineDemo.html b/archive/1.3.16/demo/jquery/stateMachineDemo.html similarity index 100% rename from build/1.3.16/demo/jquery/stateMachineDemo.html rename to archive/1.3.16/demo/jquery/stateMachineDemo.html diff --git a/build/1.3.16/demo/js/anchorDemo-jquery.js b/archive/1.3.16/demo/js/anchorDemo-jquery.js similarity index 100% rename from build/1.3.16/demo/js/anchorDemo-jquery.js rename to archive/1.3.16/demo/js/anchorDemo-jquery.js diff --git a/build/1.3.16/demo/js/anchorDemo-mootools.js b/archive/1.3.16/demo/js/anchorDemo-mootools.js similarity index 100% rename from build/1.3.16/demo/js/anchorDemo-mootools.js rename to archive/1.3.16/demo/js/anchorDemo-mootools.js diff --git a/build/1.3.16/demo/js/anchorDemo-yui3.js b/archive/1.3.16/demo/js/anchorDemo-yui3.js similarity index 100% rename from build/1.3.16/demo/js/anchorDemo-yui3.js rename to archive/1.3.16/demo/js/anchorDemo-yui3.js diff --git a/build/1.3.16/demo/js/anchorDemo.js b/archive/1.3.16/demo/js/anchorDemo.js similarity index 100% rename from build/1.3.16/demo/js/anchorDemo.js rename to archive/1.3.16/demo/js/anchorDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.0/anchorDemo.js b/archive/1.3.16/demo/js/archive/1.3.0/anchorDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.0/anchorDemo.js rename to archive/1.3.16/demo/js/archive/1.3.0/anchorDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.0/chartDemo.js b/archive/1.3.16/demo/js/archive/1.3.0/chartDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.0/chartDemo.js rename to archive/1.3.16/demo/js/archive/1.3.0/chartDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.0/demo-helper-jquery.js b/archive/1.3.16/demo/js/archive/1.3.0/demo-helper-jquery.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.0/demo-helper-jquery.js rename to archive/1.3.16/demo/js/archive/1.3.0/demo-helper-jquery.js diff --git a/build/1.3.16/demo/js/archive/1.3.0/demo-helper-mootools.js b/archive/1.3.16/demo/js/archive/1.3.0/demo-helper-mootools.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.0/demo-helper-mootools.js rename to archive/1.3.16/demo/js/archive/1.3.0/demo-helper-mootools.js diff --git a/build/1.3.16/demo/js/archive/1.3.0/demo-helper-yui3.js b/archive/1.3.16/demo/js/archive/1.3.0/demo-helper-yui3.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.0/demo-helper-yui3.js rename to archive/1.3.16/demo/js/archive/1.3.0/demo-helper-yui3.js diff --git a/build/1.3.16/demo/js/archive/1.3.0/demo.js b/archive/1.3.16/demo/js/archive/1.3.0/demo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.0/demo.js rename to archive/1.3.16/demo/js/archive/1.3.0/demo.js diff --git a/build/1.3.16/demo/js/archive/1.3.0/dragAnimDemo-jquery.js b/archive/1.3.16/demo/js/archive/1.3.0/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.0/dragAnimDemo-jquery.js rename to archive/1.3.16/demo/js/archive/1.3.0/dragAnimDemo-jquery.js diff --git a/build/1.3.16/demo/js/archive/1.3.0/dragAnimDemo-mootools.js b/archive/1.3.16/demo/js/archive/1.3.0/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.0/dragAnimDemo-mootools.js rename to archive/1.3.16/demo/js/archive/1.3.0/dragAnimDemo-mootools.js diff --git a/build/1.3.16/demo/js/archive/1.3.0/dragAnimDemo-yui3.js b/archive/1.3.16/demo/js/archive/1.3.0/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.0/dragAnimDemo-yui3.js rename to archive/1.3.16/demo/js/archive/1.3.0/dragAnimDemo-yui3.js diff --git a/build/1.3.16/demo/js/archive/1.3.0/dragAnimDemo.js b/archive/1.3.16/demo/js/archive/1.3.0/dragAnimDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.0/dragAnimDemo.js rename to archive/1.3.16/demo/js/archive/1.3.0/dragAnimDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js b/archive/1.3.16/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js rename to archive/1.3.16/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.16/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js b/archive/1.3.16/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js rename to archive/1.3.16/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.16/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js b/archive/1.3.16/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js rename to archive/1.3.16/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.16/demo/js/archive/1.3.0/draggableConnectorsDemo.js b/archive/1.3.16/demo/js/archive/1.3.0/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.0/draggableConnectorsDemo.js rename to archive/1.3.16/demo/js/archive/1.3.0/draggableConnectorsDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.0/dynamicAnchorsDemo.js b/archive/1.3.16/demo/js/archive/1.3.0/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.0/dynamicAnchorsDemo.js rename to archive/1.3.16/demo/js/archive/1.3.0/dynamicAnchorsDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.0/flowchartConnectorsDemo.js b/archive/1.3.16/demo/js/archive/1.3.0/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.0/flowchartConnectorsDemo.js rename to archive/1.3.16/demo/js/archive/1.3.0/flowchartConnectorsDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.1/anchorDemo.js b/archive/1.3.16/demo/js/archive/1.3.1/anchorDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.1/anchorDemo.js rename to archive/1.3.16/demo/js/archive/1.3.1/anchorDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.1/chartDemo.js b/archive/1.3.16/demo/js/archive/1.3.1/chartDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.1/chartDemo.js rename to archive/1.3.16/demo/js/archive/1.3.1/chartDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.1/demo-helper-jquery.js b/archive/1.3.16/demo/js/archive/1.3.1/demo-helper-jquery.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.1/demo-helper-jquery.js rename to archive/1.3.16/demo/js/archive/1.3.1/demo-helper-jquery.js diff --git a/build/1.3.16/demo/js/archive/1.3.1/demo-helper-mootools.js b/archive/1.3.16/demo/js/archive/1.3.1/demo-helper-mootools.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.1/demo-helper-mootools.js rename to archive/1.3.16/demo/js/archive/1.3.1/demo-helper-mootools.js diff --git a/build/1.3.16/demo/js/archive/1.3.1/demo-helper-yui3.js b/archive/1.3.16/demo/js/archive/1.3.1/demo-helper-yui3.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.1/demo-helper-yui3.js rename to archive/1.3.16/demo/js/archive/1.3.1/demo-helper-yui3.js diff --git a/build/1.3.16/demo/js/archive/1.3.1/demo.js b/archive/1.3.16/demo/js/archive/1.3.1/demo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.1/demo.js rename to archive/1.3.16/demo/js/archive/1.3.1/demo.js diff --git a/build/1.3.16/demo/js/archive/1.3.1/dragAnimDemo-jquery.js b/archive/1.3.16/demo/js/archive/1.3.1/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.1/dragAnimDemo-jquery.js rename to archive/1.3.16/demo/js/archive/1.3.1/dragAnimDemo-jquery.js diff --git a/build/1.3.16/demo/js/archive/1.3.1/dragAnimDemo-mootools.js b/archive/1.3.16/demo/js/archive/1.3.1/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.1/dragAnimDemo-mootools.js rename to archive/1.3.16/demo/js/archive/1.3.1/dragAnimDemo-mootools.js diff --git a/build/1.3.16/demo/js/archive/1.3.1/dragAnimDemo-yui3.js b/archive/1.3.16/demo/js/archive/1.3.1/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.1/dragAnimDemo-yui3.js rename to archive/1.3.16/demo/js/archive/1.3.1/dragAnimDemo-yui3.js diff --git a/build/1.3.16/demo/js/archive/1.3.1/dragAnimDemo.js b/archive/1.3.16/demo/js/archive/1.3.1/dragAnimDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.1/dragAnimDemo.js rename to archive/1.3.16/demo/js/archive/1.3.1/dragAnimDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js b/archive/1.3.16/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js rename to archive/1.3.16/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.16/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js b/archive/1.3.16/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js rename to archive/1.3.16/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.16/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js b/archive/1.3.16/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js rename to archive/1.3.16/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.16/demo/js/archive/1.3.1/draggableConnectorsDemo.js b/archive/1.3.16/demo/js/archive/1.3.1/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.1/draggableConnectorsDemo.js rename to archive/1.3.16/demo/js/archive/1.3.1/draggableConnectorsDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.1/dynamicAnchorsDemo.js b/archive/1.3.16/demo/js/archive/1.3.1/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.1/dynamicAnchorsDemo.js rename to archive/1.3.16/demo/js/archive/1.3.1/dynamicAnchorsDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.1/flowchartConnectorsDemo.js b/archive/1.3.16/demo/js/archive/1.3.1/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.1/flowchartConnectorsDemo.js rename to archive/1.3.16/demo/js/archive/1.3.1/flowchartConnectorsDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.2/anchorDemo.js b/archive/1.3.16/demo/js/archive/1.3.2/anchorDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.2/anchorDemo.js rename to archive/1.3.16/demo/js/archive/1.3.2/anchorDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.2/chartDemo.js b/archive/1.3.16/demo/js/archive/1.3.2/chartDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.2/chartDemo.js rename to archive/1.3.16/demo/js/archive/1.3.2/chartDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.2/demo-helper-jquery.js b/archive/1.3.16/demo/js/archive/1.3.2/demo-helper-jquery.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.2/demo-helper-jquery.js rename to archive/1.3.16/demo/js/archive/1.3.2/demo-helper-jquery.js diff --git a/build/1.3.16/demo/js/archive/1.3.2/demo-helper-mootools.js b/archive/1.3.16/demo/js/archive/1.3.2/demo-helper-mootools.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.2/demo-helper-mootools.js rename to archive/1.3.16/demo/js/archive/1.3.2/demo-helper-mootools.js diff --git a/build/1.3.16/demo/js/archive/1.3.2/demo-helper-yui3.js b/archive/1.3.16/demo/js/archive/1.3.2/demo-helper-yui3.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.2/demo-helper-yui3.js rename to archive/1.3.16/demo/js/archive/1.3.2/demo-helper-yui3.js diff --git a/build/1.3.16/demo/js/archive/1.3.2/demo.js b/archive/1.3.16/demo/js/archive/1.3.2/demo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.2/demo.js rename to archive/1.3.16/demo/js/archive/1.3.2/demo.js diff --git a/build/1.3.16/demo/js/archive/1.3.2/dragAnimDemo-jquery.js b/archive/1.3.16/demo/js/archive/1.3.2/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.2/dragAnimDemo-jquery.js rename to archive/1.3.16/demo/js/archive/1.3.2/dragAnimDemo-jquery.js diff --git a/build/1.3.16/demo/js/archive/1.3.2/dragAnimDemo-mootools.js b/archive/1.3.16/demo/js/archive/1.3.2/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.2/dragAnimDemo-mootools.js rename to archive/1.3.16/demo/js/archive/1.3.2/dragAnimDemo-mootools.js diff --git a/build/1.3.16/demo/js/archive/1.3.2/dragAnimDemo-yui3.js b/archive/1.3.16/demo/js/archive/1.3.2/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.2/dragAnimDemo-yui3.js rename to archive/1.3.16/demo/js/archive/1.3.2/dragAnimDemo-yui3.js diff --git a/build/1.3.16/demo/js/archive/1.3.2/dragAnimDemo.js b/archive/1.3.16/demo/js/archive/1.3.2/dragAnimDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.2/dragAnimDemo.js rename to archive/1.3.16/demo/js/archive/1.3.2/dragAnimDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js b/archive/1.3.16/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js rename to archive/1.3.16/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.16/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js b/archive/1.3.16/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js rename to archive/1.3.16/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.16/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js b/archive/1.3.16/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js rename to archive/1.3.16/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.16/demo/js/archive/1.3.2/draggableConnectorsDemo.js b/archive/1.3.16/demo/js/archive/1.3.2/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.2/draggableConnectorsDemo.js rename to archive/1.3.16/demo/js/archive/1.3.2/draggableConnectorsDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.2/dynamicAnchorsDemo.js b/archive/1.3.16/demo/js/archive/1.3.2/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.2/dynamicAnchorsDemo.js rename to archive/1.3.16/demo/js/archive/1.3.2/dynamicAnchorsDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.2/flowchartConnectorsDemo.js b/archive/1.3.16/demo/js/archive/1.3.2/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.2/flowchartConnectorsDemo.js rename to archive/1.3.16/demo/js/archive/1.3.2/flowchartConnectorsDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.3/anchorDemo.js b/archive/1.3.16/demo/js/archive/1.3.3/anchorDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.3/anchorDemo.js rename to archive/1.3.16/demo/js/archive/1.3.3/anchorDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.3/chartDemo.js b/archive/1.3.16/demo/js/archive/1.3.3/chartDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.3/chartDemo.js rename to archive/1.3.16/demo/js/archive/1.3.3/chartDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.3/demo-helper-jquery.js b/archive/1.3.16/demo/js/archive/1.3.3/demo-helper-jquery.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.3/demo-helper-jquery.js rename to archive/1.3.16/demo/js/archive/1.3.3/demo-helper-jquery.js diff --git a/build/1.3.16/demo/js/archive/1.3.3/demo-helper-mootools.js b/archive/1.3.16/demo/js/archive/1.3.3/demo-helper-mootools.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.3/demo-helper-mootools.js rename to archive/1.3.16/demo/js/archive/1.3.3/demo-helper-mootools.js diff --git a/build/1.3.16/demo/js/archive/1.3.3/demo-helper-yui3.js b/archive/1.3.16/demo/js/archive/1.3.3/demo-helper-yui3.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.3/demo-helper-yui3.js rename to archive/1.3.16/demo/js/archive/1.3.3/demo-helper-yui3.js diff --git a/build/1.3.16/demo/js/archive/1.3.3/demo.js b/archive/1.3.16/demo/js/archive/1.3.3/demo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.3/demo.js rename to archive/1.3.16/demo/js/archive/1.3.3/demo.js diff --git a/build/1.3.16/demo/js/archive/1.3.3/dragAnimDemo-jquery.js b/archive/1.3.16/demo/js/archive/1.3.3/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.3/dragAnimDemo-jquery.js rename to archive/1.3.16/demo/js/archive/1.3.3/dragAnimDemo-jquery.js diff --git a/build/1.3.16/demo/js/archive/1.3.3/dragAnimDemo-mootools.js b/archive/1.3.16/demo/js/archive/1.3.3/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.3/dragAnimDemo-mootools.js rename to archive/1.3.16/demo/js/archive/1.3.3/dragAnimDemo-mootools.js diff --git a/build/1.3.16/demo/js/archive/1.3.3/dragAnimDemo-yui3.js b/archive/1.3.16/demo/js/archive/1.3.3/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.3/dragAnimDemo-yui3.js rename to archive/1.3.16/demo/js/archive/1.3.3/dragAnimDemo-yui3.js diff --git a/build/1.3.16/demo/js/archive/1.3.3/dragAnimDemo.js b/archive/1.3.16/demo/js/archive/1.3.3/dragAnimDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.3/dragAnimDemo.js rename to archive/1.3.16/demo/js/archive/1.3.3/dragAnimDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js b/archive/1.3.16/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js rename to archive/1.3.16/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.16/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js b/archive/1.3.16/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js rename to archive/1.3.16/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.16/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js b/archive/1.3.16/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js rename to archive/1.3.16/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.16/demo/js/archive/1.3.3/draggableConnectorsDemo.js b/archive/1.3.16/demo/js/archive/1.3.3/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.3/draggableConnectorsDemo.js rename to archive/1.3.16/demo/js/archive/1.3.3/draggableConnectorsDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.3/dynamicAnchorsDemo.js b/archive/1.3.16/demo/js/archive/1.3.3/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.3/dynamicAnchorsDemo.js rename to archive/1.3.16/demo/js/archive/1.3.3/dynamicAnchorsDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.3/flowchartConnectorsDemo.js b/archive/1.3.16/demo/js/archive/1.3.3/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.3/flowchartConnectorsDemo.js rename to archive/1.3.16/demo/js/archive/1.3.3/flowchartConnectorsDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js b/archive/1.3.16/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js rename to archive/1.3.16/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js diff --git a/build/1.3.16/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all.js b/archive/1.3.16/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all.js rename to archive/1.3.16/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all.js diff --git a/build/1.3.16/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js b/archive/1.3.16/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js rename to archive/1.3.16/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js diff --git a/build/1.3.16/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js b/archive/1.3.16/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js rename to archive/1.3.16/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js diff --git a/build/1.3.16/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js b/archive/1.3.16/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js rename to archive/1.3.16/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js diff --git a/build/1.3.16/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js b/archive/1.3.16/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js rename to archive/1.3.16/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js diff --git a/build/1.3.16/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js b/archive/1.3.16/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js rename to archive/1.3.16/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js diff --git a/build/1.3.16/demo/js/archive/1.3.4/anchorDemo.js b/archive/1.3.16/demo/js/archive/1.3.4/anchorDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.4/anchorDemo.js rename to archive/1.3.16/demo/js/archive/1.3.4/anchorDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.4/chartDemo.js b/archive/1.3.16/demo/js/archive/1.3.4/chartDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.4/chartDemo.js rename to archive/1.3.16/demo/js/archive/1.3.4/chartDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.4/demo-helper-jquery.js b/archive/1.3.16/demo/js/archive/1.3.4/demo-helper-jquery.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.4/demo-helper-jquery.js rename to archive/1.3.16/demo/js/archive/1.3.4/demo-helper-jquery.js diff --git a/build/1.3.16/demo/js/archive/1.3.4/demo-helper-mootools.js b/archive/1.3.16/demo/js/archive/1.3.4/demo-helper-mootools.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.4/demo-helper-mootools.js rename to archive/1.3.16/demo/js/archive/1.3.4/demo-helper-mootools.js diff --git a/build/1.3.16/demo/js/archive/1.3.4/demo-helper-yui3.js b/archive/1.3.16/demo/js/archive/1.3.4/demo-helper-yui3.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.4/demo-helper-yui3.js rename to archive/1.3.16/demo/js/archive/1.3.4/demo-helper-yui3.js diff --git a/build/1.3.16/demo/js/archive/1.3.4/demo-list.js b/archive/1.3.16/demo/js/archive/1.3.4/demo-list.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.4/demo-list.js rename to archive/1.3.16/demo/js/archive/1.3.4/demo-list.js diff --git a/build/1.3.16/demo/js/archive/1.3.4/demo.js b/archive/1.3.16/demo/js/archive/1.3.4/demo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.4/demo.js rename to archive/1.3.16/demo/js/archive/1.3.4/demo.js diff --git a/build/1.3.16/demo/js/archive/1.3.4/dragAnimDemo-jquery.js b/archive/1.3.16/demo/js/archive/1.3.4/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.4/dragAnimDemo-jquery.js rename to archive/1.3.16/demo/js/archive/1.3.4/dragAnimDemo-jquery.js diff --git a/build/1.3.16/demo/js/archive/1.3.4/dragAnimDemo-mootools.js b/archive/1.3.16/demo/js/archive/1.3.4/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.4/dragAnimDemo-mootools.js rename to archive/1.3.16/demo/js/archive/1.3.4/dragAnimDemo-mootools.js diff --git a/build/1.3.16/demo/js/archive/1.3.4/dragAnimDemo-yui3.js b/archive/1.3.16/demo/js/archive/1.3.4/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.4/dragAnimDemo-yui3.js rename to archive/1.3.16/demo/js/archive/1.3.4/dragAnimDemo-yui3.js diff --git a/build/1.3.16/demo/js/archive/1.3.4/dragAnimDemo.js b/archive/1.3.16/demo/js/archive/1.3.4/dragAnimDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.4/dragAnimDemo.js rename to archive/1.3.16/demo/js/archive/1.3.4/dragAnimDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.4/draggableConnectorsDemo-jquery.js b/archive/1.3.16/demo/js/archive/1.3.4/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.4/draggableConnectorsDemo-jquery.js rename to archive/1.3.16/demo/js/archive/1.3.4/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.16/demo/js/archive/1.3.4/draggableConnectorsDemo-mootools.js b/archive/1.3.16/demo/js/archive/1.3.4/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.4/draggableConnectorsDemo-mootools.js rename to archive/1.3.16/demo/js/archive/1.3.4/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.16/demo/js/archive/1.3.4/draggableConnectorsDemo-yui3.js b/archive/1.3.16/demo/js/archive/1.3.4/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.4/draggableConnectorsDemo-yui3.js rename to archive/1.3.16/demo/js/archive/1.3.4/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.16/demo/js/archive/1.3.4/draggableConnectorsDemo.js b/archive/1.3.16/demo/js/archive/1.3.4/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.4/draggableConnectorsDemo.js rename to archive/1.3.16/demo/js/archive/1.3.4/draggableConnectorsDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.4/dynamicAnchorsDemo.js b/archive/1.3.16/demo/js/archive/1.3.4/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.4/dynamicAnchorsDemo.js rename to archive/1.3.16/demo/js/archive/1.3.4/dynamicAnchorsDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.4/flowchartConnectorsDemo.js b/archive/1.3.16/demo/js/archive/1.3.4/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.4/flowchartConnectorsDemo.js rename to archive/1.3.16/demo/js/archive/1.3.4/flowchartConnectorsDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.4/makeSourceDemo.js b/archive/1.3.16/demo/js/archive/1.3.4/makeSourceDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.4/makeSourceDemo.js rename to archive/1.3.16/demo/js/archive/1.3.4/makeSourceDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.4/makeTargetDemo.js b/archive/1.3.16/demo/js/archive/1.3.4/makeTargetDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.4/makeTargetDemo.js rename to archive/1.3.16/demo/js/archive/1.3.4/makeTargetDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.4/stateMachineDemo-jquery.js b/archive/1.3.16/demo/js/archive/1.3.4/stateMachineDemo-jquery.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.4/stateMachineDemo-jquery.js rename to archive/1.3.16/demo/js/archive/1.3.4/stateMachineDemo-jquery.js diff --git a/build/1.3.16/demo/js/archive/1.3.4/stateMachineDemo-mootools.js b/archive/1.3.16/demo/js/archive/1.3.4/stateMachineDemo-mootools.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.4/stateMachineDemo-mootools.js rename to archive/1.3.16/demo/js/archive/1.3.4/stateMachineDemo-mootools.js diff --git a/build/1.3.16/demo/js/archive/1.3.4/stateMachineDemo-yui3.js b/archive/1.3.16/demo/js/archive/1.3.4/stateMachineDemo-yui3.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.4/stateMachineDemo-yui3.js rename to archive/1.3.16/demo/js/archive/1.3.4/stateMachineDemo-yui3.js diff --git a/build/1.3.16/demo/js/archive/1.3.4/stateMachineDemo.js b/archive/1.3.16/demo/js/archive/1.3.4/stateMachineDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.4/stateMachineDemo.js rename to archive/1.3.16/demo/js/archive/1.3.4/stateMachineDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.5/anchorDemo.js b/archive/1.3.16/demo/js/archive/1.3.5/anchorDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.5/anchorDemo.js rename to archive/1.3.16/demo/js/archive/1.3.5/anchorDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.5/chartDemo.js b/archive/1.3.16/demo/js/archive/1.3.5/chartDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.5/chartDemo.js rename to archive/1.3.16/demo/js/archive/1.3.5/chartDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.5/demo-helper-jquery.js b/archive/1.3.16/demo/js/archive/1.3.5/demo-helper-jquery.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.5/demo-helper-jquery.js rename to archive/1.3.16/demo/js/archive/1.3.5/demo-helper-jquery.js diff --git a/build/1.3.16/demo/js/archive/1.3.5/demo-helper-mootools.js b/archive/1.3.16/demo/js/archive/1.3.5/demo-helper-mootools.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.5/demo-helper-mootools.js rename to archive/1.3.16/demo/js/archive/1.3.5/demo-helper-mootools.js diff --git a/build/1.3.16/demo/js/archive/1.3.5/demo-helper-yui3.js b/archive/1.3.16/demo/js/archive/1.3.5/demo-helper-yui3.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.5/demo-helper-yui3.js rename to archive/1.3.16/demo/js/archive/1.3.5/demo-helper-yui3.js diff --git a/build/1.3.16/demo/js/archive/1.3.5/demo-list.js b/archive/1.3.16/demo/js/archive/1.3.5/demo-list.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.5/demo-list.js rename to archive/1.3.16/demo/js/archive/1.3.5/demo-list.js diff --git a/build/1.3.16/demo/js/archive/1.3.5/demo.js b/archive/1.3.16/demo/js/archive/1.3.5/demo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.5/demo.js rename to archive/1.3.16/demo/js/archive/1.3.5/demo.js diff --git a/build/1.3.16/demo/js/archive/1.3.5/dragAnimDemo-jquery.js b/archive/1.3.16/demo/js/archive/1.3.5/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.5/dragAnimDemo-jquery.js rename to archive/1.3.16/demo/js/archive/1.3.5/dragAnimDemo-jquery.js diff --git a/build/1.3.16/demo/js/archive/1.3.5/dragAnimDemo-mootools.js b/archive/1.3.16/demo/js/archive/1.3.5/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.5/dragAnimDemo-mootools.js rename to archive/1.3.16/demo/js/archive/1.3.5/dragAnimDemo-mootools.js diff --git a/build/1.3.16/demo/js/archive/1.3.5/dragAnimDemo-yui3.js b/archive/1.3.16/demo/js/archive/1.3.5/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.5/dragAnimDemo-yui3.js rename to archive/1.3.16/demo/js/archive/1.3.5/dragAnimDemo-yui3.js diff --git a/build/1.3.16/demo/js/archive/1.3.5/dragAnimDemo.js b/archive/1.3.16/demo/js/archive/1.3.5/dragAnimDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.5/dragAnimDemo.js rename to archive/1.3.16/demo/js/archive/1.3.5/dragAnimDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.5/draggableConnectorsDemo-jquery.js b/archive/1.3.16/demo/js/archive/1.3.5/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.5/draggableConnectorsDemo-jquery.js rename to archive/1.3.16/demo/js/archive/1.3.5/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.16/demo/js/archive/1.3.5/draggableConnectorsDemo-mootools.js b/archive/1.3.16/demo/js/archive/1.3.5/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.5/draggableConnectorsDemo-mootools.js rename to archive/1.3.16/demo/js/archive/1.3.5/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.16/demo/js/archive/1.3.5/draggableConnectorsDemo-yui3.js b/archive/1.3.16/demo/js/archive/1.3.5/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.5/draggableConnectorsDemo-yui3.js rename to archive/1.3.16/demo/js/archive/1.3.5/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.16/demo/js/archive/1.3.5/draggableConnectorsDemo.js b/archive/1.3.16/demo/js/archive/1.3.5/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.5/draggableConnectorsDemo.js rename to archive/1.3.16/demo/js/archive/1.3.5/draggableConnectorsDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.5/dynamicAnchorsDemo.js b/archive/1.3.16/demo/js/archive/1.3.5/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.5/dynamicAnchorsDemo.js rename to archive/1.3.16/demo/js/archive/1.3.5/dynamicAnchorsDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.5/flowchartConnectorsDemo.js b/archive/1.3.16/demo/js/archive/1.3.5/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.5/flowchartConnectorsDemo.js rename to archive/1.3.16/demo/js/archive/1.3.5/flowchartConnectorsDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.5/loadTest.js b/archive/1.3.16/demo/js/archive/1.3.5/loadTest.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.5/loadTest.js rename to archive/1.3.16/demo/js/archive/1.3.5/loadTest.js diff --git a/build/1.3.16/demo/js/archive/1.3.5/makeSourceDemo.js b/archive/1.3.16/demo/js/archive/1.3.5/makeSourceDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.5/makeSourceDemo.js rename to archive/1.3.16/demo/js/archive/1.3.5/makeSourceDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.5/makeTargetDemo.js b/archive/1.3.16/demo/js/archive/1.3.5/makeTargetDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.5/makeTargetDemo.js rename to archive/1.3.16/demo/js/archive/1.3.5/makeTargetDemo.js diff --git a/build/1.3.16/demo/js/archive/1.3.5/stateMachineDemo-jquery.js b/archive/1.3.16/demo/js/archive/1.3.5/stateMachineDemo-jquery.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.5/stateMachineDemo-jquery.js rename to archive/1.3.16/demo/js/archive/1.3.5/stateMachineDemo-jquery.js diff --git a/build/1.3.16/demo/js/archive/1.3.5/stateMachineDemo-mootools.js b/archive/1.3.16/demo/js/archive/1.3.5/stateMachineDemo-mootools.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.5/stateMachineDemo-mootools.js rename to archive/1.3.16/demo/js/archive/1.3.5/stateMachineDemo-mootools.js diff --git a/build/1.3.16/demo/js/archive/1.3.5/stateMachineDemo-yui3.js b/archive/1.3.16/demo/js/archive/1.3.5/stateMachineDemo-yui3.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.5/stateMachineDemo-yui3.js rename to archive/1.3.16/demo/js/archive/1.3.5/stateMachineDemo-yui3.js diff --git a/build/1.3.16/demo/js/archive/1.3.5/stateMachineDemo.js b/archive/1.3.16/demo/js/archive/1.3.5/stateMachineDemo.js similarity index 100% rename from build/1.3.16/demo/js/archive/1.3.5/stateMachineDemo.js rename to archive/1.3.16/demo/js/archive/1.3.5/stateMachineDemo.js diff --git a/build/1.3.16/demo/js/chartDemo.js b/archive/1.3.16/demo/js/chartDemo.js similarity index 100% rename from build/1.3.16/demo/js/chartDemo.js rename to archive/1.3.16/demo/js/chartDemo.js diff --git a/build/1.3.16/demo/js/demo-helper-jquery.js b/archive/1.3.16/demo/js/demo-helper-jquery.js similarity index 100% rename from build/1.3.16/demo/js/demo-helper-jquery.js rename to archive/1.3.16/demo/js/demo-helper-jquery.js diff --git a/build/1.3.16/demo/js/demo-helper-mootools.js b/archive/1.3.16/demo/js/demo-helper-mootools.js similarity index 100% rename from build/1.3.16/demo/js/demo-helper-mootools.js rename to archive/1.3.16/demo/js/demo-helper-mootools.js diff --git a/build/1.3.16/demo/js/demo-helper-yui3.js b/archive/1.3.16/demo/js/demo-helper-yui3.js similarity index 100% rename from build/1.3.16/demo/js/demo-helper-yui3.js rename to archive/1.3.16/demo/js/demo-helper-yui3.js diff --git a/build/1.3.16/demo/js/demo-list.js b/archive/1.3.16/demo/js/demo-list.js similarity index 100% rename from build/1.3.16/demo/js/demo-list.js rename to archive/1.3.16/demo/js/demo-list.js diff --git a/build/1.3.16/demo/js/demo.js b/archive/1.3.16/demo/js/demo.js similarity index 100% rename from build/1.3.16/demo/js/demo.js rename to archive/1.3.16/demo/js/demo.js diff --git a/build/1.3.16/demo/js/dragAnimDemo-jquery.js b/archive/1.3.16/demo/js/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.16/demo/js/dragAnimDemo-jquery.js rename to archive/1.3.16/demo/js/dragAnimDemo-jquery.js diff --git a/build/1.3.16/demo/js/dragAnimDemo-mootools.js b/archive/1.3.16/demo/js/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.16/demo/js/dragAnimDemo-mootools.js rename to archive/1.3.16/demo/js/dragAnimDemo-mootools.js diff --git a/build/1.3.16/demo/js/dragAnimDemo-yui3.js b/archive/1.3.16/demo/js/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.16/demo/js/dragAnimDemo-yui3.js rename to archive/1.3.16/demo/js/dragAnimDemo-yui3.js diff --git a/build/1.3.16/demo/js/dragAnimDemo.js b/archive/1.3.16/demo/js/dragAnimDemo.js similarity index 100% rename from build/1.3.16/demo/js/dragAnimDemo.js rename to archive/1.3.16/demo/js/dragAnimDemo.js diff --git a/build/1.3.16/demo/js/draggableConnectorsDemo-jquery.js b/archive/1.3.16/demo/js/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.16/demo/js/draggableConnectorsDemo-jquery.js rename to archive/1.3.16/demo/js/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.16/demo/js/draggableConnectorsDemo-mootools.js b/archive/1.3.16/demo/js/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.16/demo/js/draggableConnectorsDemo-mootools.js rename to archive/1.3.16/demo/js/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.16/demo/js/draggableConnectorsDemo-yui3.js b/archive/1.3.16/demo/js/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.16/demo/js/draggableConnectorsDemo-yui3.js rename to archive/1.3.16/demo/js/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.16/demo/js/draggableConnectorsDemo.js b/archive/1.3.16/demo/js/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.16/demo/js/draggableConnectorsDemo.js rename to archive/1.3.16/demo/js/draggableConnectorsDemo.js diff --git a/build/1.3.16/demo/js/dynamicAnchorsDemo.js b/archive/1.3.16/demo/js/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.16/demo/js/dynamicAnchorsDemo.js rename to archive/1.3.16/demo/js/dynamicAnchorsDemo.js diff --git a/build/1.3.16/demo/js/flowchartConnectorsDemo.js b/archive/1.3.16/demo/js/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.16/demo/js/flowchartConnectorsDemo.js rename to archive/1.3.16/demo/js/flowchartConnectorsDemo.js diff --git a/build/1.3.16/demo/js/jquery.jsPlumb-1.3.16-all-min.js b/archive/1.3.16/demo/js/jquery.jsPlumb-1.3.16-all-min.js similarity index 100% rename from build/1.3.16/demo/js/jquery.jsPlumb-1.3.16-all-min.js rename to archive/1.3.16/demo/js/jquery.jsPlumb-1.3.16-all-min.js diff --git a/build/1.3.16/demo/js/jquery.jsPlumb-1.3.16-all.js b/archive/1.3.16/demo/js/jquery.jsPlumb-1.3.16-all.js similarity index 100% rename from build/1.3.16/demo/js/jquery.jsPlumb-1.3.16-all.js rename to archive/1.3.16/demo/js/jquery.jsPlumb-1.3.16-all.js diff --git a/build/1.3.16/demo/js/jquery.ui.touch-punch.min.js b/archive/1.3.16/demo/js/jquery.ui.touch-punch.min.js similarity index 100% rename from build/1.3.16/demo/js/jquery.ui.touch-punch.min.js rename to archive/1.3.16/demo/js/jquery.ui.touch-punch.min.js diff --git a/build/1.3.16/demo/js/loadTest.js b/archive/1.3.16/demo/js/loadTest.js similarity index 100% rename from build/1.3.16/demo/js/loadTest.js rename to archive/1.3.16/demo/js/loadTest.js diff --git a/build/1.3.16/demo/js/makeSourceDemo.js b/archive/1.3.16/demo/js/makeSourceDemo.js similarity index 100% rename from build/1.3.16/demo/js/makeSourceDemo.js rename to archive/1.3.16/demo/js/makeSourceDemo.js diff --git a/build/1.3.16/demo/js/makeTargetDemo.js b/archive/1.3.16/demo/js/makeTargetDemo.js similarity index 100% rename from build/1.3.16/demo/js/makeTargetDemo.js rename to archive/1.3.16/demo/js/makeTargetDemo.js diff --git a/build/1.3.16/demo/js/mootools-1.3.2.1-more.js b/archive/1.3.16/demo/js/mootools-1.3.2.1-more.js similarity index 100% rename from build/1.3.16/demo/js/mootools-1.3.2.1-more.js rename to archive/1.3.16/demo/js/mootools-1.3.2.1-more.js diff --git a/build/1.3.16/demo/js/mootools.jsPlumb-1.3.16-all-min.js b/archive/1.3.16/demo/js/mootools.jsPlumb-1.3.16-all-min.js similarity index 100% rename from build/1.3.16/demo/js/mootools.jsPlumb-1.3.16-all-min.js rename to archive/1.3.16/demo/js/mootools.jsPlumb-1.3.16-all-min.js diff --git a/build/1.3.16/demo/js/mootools.jsPlumb-1.3.16-all.js b/archive/1.3.16/demo/js/mootools.jsPlumb-1.3.16-all.js similarity index 100% rename from build/1.3.16/demo/js/mootools.jsPlumb-1.3.16-all.js rename to archive/1.3.16/demo/js/mootools.jsPlumb-1.3.16-all.js diff --git a/build/1.3.16/demo/js/perimeterAnchorsDemo-jquery.js b/archive/1.3.16/demo/js/perimeterAnchorsDemo-jquery.js similarity index 100% rename from build/1.3.16/demo/js/perimeterAnchorsDemo-jquery.js rename to archive/1.3.16/demo/js/perimeterAnchorsDemo-jquery.js diff --git a/build/1.3.16/demo/js/perimeterAnchorsDemo-mootools.js b/archive/1.3.16/demo/js/perimeterAnchorsDemo-mootools.js similarity index 100% rename from build/1.3.16/demo/js/perimeterAnchorsDemo-mootools.js rename to archive/1.3.16/demo/js/perimeterAnchorsDemo-mootools.js diff --git a/build/1.3.16/demo/js/perimeterAnchorsDemo-yui3.js b/archive/1.3.16/demo/js/perimeterAnchorsDemo-yui3.js similarity index 100% rename from build/1.3.16/demo/js/perimeterAnchorsDemo-yui3.js rename to archive/1.3.16/demo/js/perimeterAnchorsDemo-yui3.js diff --git a/build/1.3.16/demo/js/stateMachineDemo-jquery.js b/archive/1.3.16/demo/js/stateMachineDemo-jquery.js similarity index 100% rename from build/1.3.16/demo/js/stateMachineDemo-jquery.js rename to archive/1.3.16/demo/js/stateMachineDemo-jquery.js diff --git a/build/1.3.16/demo/js/stateMachineDemo-mootools.js b/archive/1.3.16/demo/js/stateMachineDemo-mootools.js similarity index 100% rename from build/1.3.16/demo/js/stateMachineDemo-mootools.js rename to archive/1.3.16/demo/js/stateMachineDemo-mootools.js diff --git a/build/1.3.16/demo/js/stateMachineDemo-yui3.js b/archive/1.3.16/demo/js/stateMachineDemo-yui3.js similarity index 100% rename from build/1.3.16/demo/js/stateMachineDemo-yui3.js rename to archive/1.3.16/demo/js/stateMachineDemo-yui3.js diff --git a/build/1.3.16/demo/js/stateMachineDemo.js b/archive/1.3.16/demo/js/stateMachineDemo.js similarity index 100% rename from build/1.3.16/demo/js/stateMachineDemo.js rename to archive/1.3.16/demo/js/stateMachineDemo.js diff --git a/build/1.3.16/demo/js/yui-3.3.0-min.js b/archive/1.3.16/demo/js/yui-3.3.0-min.js similarity index 100% rename from build/1.3.16/demo/js/yui-3.3.0-min.js rename to archive/1.3.16/demo/js/yui-3.3.0-min.js diff --git a/build/1.3.16/demo/js/yui.jsPlumb-1.3.16-all-min.js b/archive/1.3.16/demo/js/yui.jsPlumb-1.3.16-all-min.js similarity index 100% rename from build/1.3.16/demo/js/yui.jsPlumb-1.3.16-all-min.js rename to archive/1.3.16/demo/js/yui.jsPlumb-1.3.16-all-min.js diff --git a/build/1.3.16/demo/js/yui.jsPlumb-1.3.16-all.js b/archive/1.3.16/demo/js/yui.jsPlumb-1.3.16-all.js similarity index 100% rename from build/1.3.16/demo/js/yui.jsPlumb-1.3.16-all.js rename to archive/1.3.16/demo/js/yui.jsPlumb-1.3.16-all.js diff --git a/build/1.3.16/demo/mootools/anchorDemo.html b/archive/1.3.16/demo/mootools/anchorDemo.html similarity index 100% rename from build/1.3.16/demo/mootools/anchorDemo.html rename to archive/1.3.16/demo/mootools/anchorDemo.html diff --git a/build/1.3.16/demo/mootools/chartDemo.html b/archive/1.3.16/demo/mootools/chartDemo.html similarity index 100% rename from build/1.3.16/demo/mootools/chartDemo.html rename to archive/1.3.16/demo/mootools/chartDemo.html diff --git a/build/1.3.16/demo/mootools/demo.html b/archive/1.3.16/demo/mootools/demo.html similarity index 100% rename from build/1.3.16/demo/mootools/demo.html rename to archive/1.3.16/demo/mootools/demo.html diff --git a/build/1.3.16/demo/mootools/dragAnimDemo.html b/archive/1.3.16/demo/mootools/dragAnimDemo.html similarity index 100% rename from build/1.3.16/demo/mootools/dragAnimDemo.html rename to archive/1.3.16/demo/mootools/dragAnimDemo.html diff --git a/build/1.3.16/demo/mootools/draggableConnectorsDemo.html b/archive/1.3.16/demo/mootools/draggableConnectorsDemo.html similarity index 100% rename from build/1.3.16/demo/mootools/draggableConnectorsDemo.html rename to archive/1.3.16/demo/mootools/draggableConnectorsDemo.html diff --git a/build/1.3.16/demo/mootools/dynamicAnchorsDemo.html b/archive/1.3.16/demo/mootools/dynamicAnchorsDemo.html similarity index 100% rename from build/1.3.16/demo/mootools/dynamicAnchorsDemo.html rename to archive/1.3.16/demo/mootools/dynamicAnchorsDemo.html diff --git a/build/1.3.16/demo/mootools/flowchartConnectorsDemo.html b/archive/1.3.16/demo/mootools/flowchartConnectorsDemo.html similarity index 100% rename from build/1.3.16/demo/mootools/flowchartConnectorsDemo.html rename to archive/1.3.16/demo/mootools/flowchartConnectorsDemo.html diff --git a/build/1.3.16/demo/mootools/perimeterAnchorsDemo.html b/archive/1.3.16/demo/mootools/perimeterAnchorsDemo.html similarity index 100% rename from build/1.3.16/demo/mootools/perimeterAnchorsDemo.html rename to archive/1.3.16/demo/mootools/perimeterAnchorsDemo.html diff --git a/build/1.3.16/demo/mootools/stateMachineDemo.html b/archive/1.3.16/demo/mootools/stateMachineDemo.html similarity index 100% rename from build/1.3.16/demo/mootools/stateMachineDemo.html rename to archive/1.3.16/demo/mootools/stateMachineDemo.html diff --git a/build/1.3.16/demo/yui3/anchorDemo.html b/archive/1.3.16/demo/yui3/anchorDemo.html similarity index 100% rename from build/1.3.16/demo/yui3/anchorDemo.html rename to archive/1.3.16/demo/yui3/anchorDemo.html diff --git a/build/1.3.16/demo/yui3/chartDemo.html b/archive/1.3.16/demo/yui3/chartDemo.html similarity index 100% rename from build/1.3.16/demo/yui3/chartDemo.html rename to archive/1.3.16/demo/yui3/chartDemo.html diff --git a/build/1.3.16/demo/yui3/demo.html b/archive/1.3.16/demo/yui3/demo.html similarity index 100% rename from build/1.3.16/demo/yui3/demo.html rename to archive/1.3.16/demo/yui3/demo.html diff --git a/build/1.3.16/demo/yui3/dragAnimDemo.html b/archive/1.3.16/demo/yui3/dragAnimDemo.html similarity index 100% rename from build/1.3.16/demo/yui3/dragAnimDemo.html rename to archive/1.3.16/demo/yui3/dragAnimDemo.html diff --git a/build/1.3.16/demo/yui3/draggableConnectorsDemo.html b/archive/1.3.16/demo/yui3/draggableConnectorsDemo.html similarity index 100% rename from build/1.3.16/demo/yui3/draggableConnectorsDemo.html rename to archive/1.3.16/demo/yui3/draggableConnectorsDemo.html diff --git a/build/1.3.16/demo/yui3/dynamicAnchorsDemo.html b/archive/1.3.16/demo/yui3/dynamicAnchorsDemo.html similarity index 100% rename from build/1.3.16/demo/yui3/dynamicAnchorsDemo.html rename to archive/1.3.16/demo/yui3/dynamicAnchorsDemo.html diff --git a/build/1.3.16/demo/yui3/flowchartConnectorsDemo.html b/archive/1.3.16/demo/yui3/flowchartConnectorsDemo.html similarity index 100% rename from build/1.3.16/demo/yui3/flowchartConnectorsDemo.html rename to archive/1.3.16/demo/yui3/flowchartConnectorsDemo.html diff --git a/build/1.3.16/demo/yui3/perimeterAnchorsDemo.html b/archive/1.3.16/demo/yui3/perimeterAnchorsDemo.html similarity index 100% rename from build/1.3.16/demo/yui3/perimeterAnchorsDemo.html rename to archive/1.3.16/demo/yui3/perimeterAnchorsDemo.html diff --git a/build/1.3.16/demo/yui3/stateMachineDemo.html b/archive/1.3.16/demo/yui3/stateMachineDemo.html similarity index 100% rename from build/1.3.16/demo/yui3/stateMachineDemo.html rename to archive/1.3.16/demo/yui3/stateMachineDemo.html diff --git a/archive/1.3.16/jquery.jsPlumb-1.3.16-RC1.js b/archive/1.3.16/jquery.jsPlumb-1.3.16-RC1.js new file mode 100644 index 000000000..a230cdb9d --- /dev/null +++ b/archive/1.3.16/jquery.jsPlumb-1.3.16-RC1.js @@ -0,0 +1,379 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.16 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the jQuery adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ +/* + * the library specific functions, such as find offset, get id, get attribute, extend etc. + * the full list is: + * + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getDropScope gets the drop scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getOriginalEvent gets the original browser event from some wrapper event + * getPageXY gets the page event's xy location. + * getParent gets the parent of some element. + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * hasClass returns whether or not the given element has the given class. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + * trigger triggers some event on an element. + * unbind unbinds some listener from some element. + */ +(function($) { + + //var getBoundingClientRectSupported = "getBoundingClientRect" in document.documentElement; + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el[0].className.constructor == SVGAnimatedString) { + jsPlumbUtil.svg.addClass(el[0], clazz); + } + } + catch (e) { + // SVGAnimatedString not supported; no problem. + } + el.addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * appends the given child to the given parent. + */ + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).append(child); + }, + + /** + * executes an ajax call. + */ + ajax : function(params) { + params = params || {}; + params.type = params.type || "get"; + $.ajax(params); + }, + + /** + * event binding wrapper. it just so happens that jQuery uses 'bind' also. yui3, for example, + * uses 'on'. + */ + bind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.bind(event, callback); + }, + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step', + 'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete' + }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + }, + + getDragScope : function(el) { + return el.draggable("option", "scope"); + }, + + getDropEvent : function(args) { + return args[0]; + }, + + getDropScope : function(el) { + return el.droppable("option", "scope"); + }, + + /** + * gets a DOM element from the given input, which might be a string (in which case we just do document.getElementById), + * a selector (in which case we return el[0]), or a DOM element already (we assume this if it's not either of the other + * two cases). this is the opposite of getElementObject below. + */ + getDOMElement : function(el) { + if (typeof(el) == "string") return document.getElementById(el); + else if (el.context || el.length != null) return el[0]; + else return el; + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + * + */ + getElementObject : function(el) { + return typeof(el) == "string" ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + getOriginalEvent : function(e) { + return e.originalEvent; + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).parent(); + }, + + getScrollLeft : function(el) { + return el.scrollLeft(); + }, + + getScrollTop : function(el) { + return el.scrollTop(); + }, + + getSelector : function(spec) { + return $(spec); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e.length > 0 ? e[0].tagName : null; + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs, zoom) { + + zoom = zoom || 1; + // this code is a workaround for the case that the element being dragged has a margin set on it. jquery UI passes + // in the wrong offset if the element has a margin (it doesn't take the margin into account). the getBoundingClientRect + // method, which is in pretty much all browsers now, reports the right numbers. but it introduces a noticeable lag, which + // i don't like. + + /*if ( getBoundingClientRectSupported ) { + var r = eventArgs[1].helper[0].getBoundingClientRect(); + return { left : r.left, top: r.top }; + } else {*/ + if (eventArgs.length == 1) { + ret = { left: eventArgs[0].pageX, top:eventArgs[0].pageY }; + } + else { + var ui = eventArgs[1], + _offset = ui.offset; + + ret = _offset || ui.absolutePosition; + + // adjust ui position to account for zoom, because jquery ui does not do this. + ui.position.left /= zoom; + ui.position.top /= zoom; + } + return { left:ret.left / zoom, top: ret.top / zoom }; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options, isPlumbedComponent) { + options = options || {}; + // remove helper directive if present. + options.helper = null; + if (isPlumbedComponent) + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.draggable(options); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.droppable(options); + }, + + isAlreadyDraggable : function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return el.hasClass("ui-draggable"); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el[0].className.constructor == SVGAnimatedString) { + jsPlumbUtil.svg.removeClass(el[0], clazz); + } + } + catch (e) { + // SVGAnimatedString not supported; no problem. + } + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).remove(); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * sets the drag scope. probably time for a setDragOption method (roll this and the one above together) + * @param el + * @param scope + */ + setDragScope : function(el, scope) { + el.draggable("option", "scope", scope); + }, + + setOffset : function(el, o) { + jsPlumb.CurrentLibrary.getElementObject(el).offset(o); + }, + + /** + * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself. + * the other libraries do not. yui, in fact, cannot even pass an original event. we have to pull out stuff + * from the originalEvent to put in an options object for YUI. + * @param el + * @param event + * @param originalEvent + */ + trigger : function(el, event, originalEvent) { + //originalEvent.stopPropagation(); + //jsPlumb.CurrentLibrary.getElementObject(el).trigger(originalEvent); + var h = jQuery._data(jsPlumb.CurrentLibrary.getElementObject(el)[0], "handle"); + h(originalEvent); + //originalEvent.stopPropagation(); + }, + + /** + * event unbinding wrapper. it just so happens that jQuery uses 'unbind' also. yui3, for example, + * uses..something else. + */ + unbind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.unbind(event, callback); + } + }; + + $(document).ready(jsPlumb.init); + +})(jQuery); + diff --git a/build/1.3.16/js/jquery.jsPlumb-1.3.16-all-min.js b/archive/1.3.16/js/jquery.jsPlumb-1.3.16-all-min.js similarity index 100% rename from build/1.3.16/js/jquery.jsPlumb-1.3.16-all-min.js rename to archive/1.3.16/js/jquery.jsPlumb-1.3.16-all-min.js diff --git a/build/1.3.16/js/jquery.jsPlumb-1.3.16-all.js b/archive/1.3.16/js/jquery.jsPlumb-1.3.16-all.js similarity index 100% rename from build/1.3.16/js/jquery.jsPlumb-1.3.16-all.js rename to archive/1.3.16/js/jquery.jsPlumb-1.3.16-all.js diff --git a/archive/1.3.16/js/jsPlumb-1.3.16-tests.js b/archive/1.3.16/js/jsPlumb-1.3.16-tests.js new file mode 100644 index 000000000..9262bc0ec --- /dev/null +++ b/archive/1.3.16/js/jsPlumb-1.3.16-tests.js @@ -0,0 +1,4748 @@ +// _jsPlumb qunit tests. + +QUnit.config.reorder = false; + +var _getContextNode = function() { + return $("#container"); +}; + +var assertContextExists = function() { + ok(_getContextNode().length == 1, "context node exists"); +}; + +var assertContextSize = function(elementCount) { + //equals(_getContextNode().children().length - _divs.length, elementCount, 'context has ' + elementCount + ' children'); +}; + +var assertContextEmpty = function() { + equals(_getContextNode().children.length, 0, "context empty"); +}; + +var assertEndpointCount = function(elId, count, _jsPlumb) { + equals(_jsPlumb.getTestHarness().endpointCount(elId), count, elId + " has " + count + (count > 1) ? "endpoints" : "endpoint"); + equals(_jsPlumb.anchorManager.getEndpointsFor(elId).length, count, "anchor manager has " + count + (count > 1) ? "endpoints" : "endpoint"); +}; + +var assertConnectionCount = function(endpoint, count) { + equals(endpoint.connections.length, count, "endpoint has " + count + " connections"); +}; + +var assertConnectionByScopeCount = function(scope, count, _jsPlumb) { + equals(_jsPlumb.getTestHarness().connectionCount(scope), count, 'Scope ' + scope + " has " + count + (count > 1) ? "connections" : "connection"); + +}; + +var _divs = []; +var _addDiv = function(id, parent) { + var d1 = document.createElement("div"); + if (parent) parent.append(d1); else _getContextNode().append(d1); + d1 = jsPlumb.CurrentLibrary.getElementObject(d1); + jsPlumb.CurrentLibrary.setAttribute(d1, "id", id); + _divs.push(id); + return d1; +}; + +var defaults = null, + _cleanup = function(_jsPlumb) { + + _jsPlumb.reset(); + _jsPlumb.Defaults = defaults; + + for (var i in _divs) { + $("#" + _divs[i]).remove(); + } + _divs.splice(0, _divs.length - 1); + + $("#container").empty(); +}; + +var testSuite = function(renderMode, _jsPlumb) { + + + module("jsPlumb", { + teardown: function() { + _cleanup(_jsPlumb); + }, + setup:function() { + defaults = jsPlumb.extend({}, _jsPlumb.Defaults); + } + }); + + // setup the container + var container = document.createElement("div"); + container.id = "container"; + document.body.appendChild(container); + + var jpcl = jsPlumb.CurrentLibrary; + + _jsPlumb.setRenderMode(renderMode); + + test(renderMode + " : getElementObject", function() { + var e = document.createElement("div"); + e.id = "FOO"; + document.body.appendChild(e); + var el = jpcl.getElementObject(e); + equals(jpcl.getAttribute(el, "id"), "FOO"); + }); + + test(renderMode + " : getDOMElement", function() { + var e = document.createElement("div"); + e.id = "FOO"; + document.body.appendChild(e); + var el = jpcl.getElementObject(e); + var e2 = jpcl.getDOMElement(el); + equals(e2.id, "FOO"); + }); + + test(renderMode + ': _jsPlumb setup', function() { + ok(_jsPlumb, "loaded"); + }); + + test(renderMode + ': getId', function() { + var d10 = _addDiv('d10'); + equals(_jsPlumb.getTestHarness().getId(d10), "d10"); + }); + + test(renderMode + ': create a simple endpoint', function() { + var d1 = _addDiv("d1"); + var e = _jsPlumb.addEndpoint($("#d1"), {}); + ok(e, 'endpoint exists'); + assertEndpointCount("d1", 1, _jsPlumb); + ok(e.id != null, "endpoint has had an id assigned"); + }); + + test(renderMode + ': create and remove a simple endpoint', function() { + var d1 = _addDiv("d1"); + var ee = _jsPlumb.addEndpoint(d1, {uuid:"78978597593"}); + ok(ee != null, "endpoint exists"); + var e = _jsPlumb.getEndpoint("78978597593"); + ok(e != null, "the endpoint could be retrieved by UUID"); + ok(e.id != null, "the endpoint has had an id assigned to it"); + assertEndpointCount("d1", 1, _jsPlumb); + assertContextSize(1); // one Endpoint canvas. + _jsPlumb.deleteEndpoint(ee); + assertEndpointCount("d1", 0, _jsPlumb); + e = _jsPlumb.getEndpoint("78978597593"); + equals(e, null, "the endpoint has been deleted"); + assertContextSize(0); // no Endpoint canvases. + }); + + test(renderMode + ': create two simple endpoints, registered using a selector', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + d1.addClass("window");d2.addClass("window"); + var endpoints = _jsPlumb.addEndpoint($(".window"), {}); + equals(endpoints.length, 2, "endpoint added to both windows"); + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + }); + + test(renderMode + ': create two simple endpoints, registered using an array of element ids', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + d1.addClass("window");d2.addClass("window"); + var endpoints = _jsPlumb.addEndpoint(["d1", "d2"], {}); + equals(endpoints.length, 2, "endpoint added to both windows"); + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + }); + + test(renderMode + ': draggable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1"); + var e = _jsPlumb.addEndpoint(d1, {isSource:true}); + ok(e, 'endpoint exists'); + }); + + test(renderMode + ': droppable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1") + var e = _jsPlumb.addEndpoint(d1, {isTarget:true}); + ok(e, 'endpoint exists'); + }); + + test(renderMode + ': draggable in nested element does not cause extra ids to be created', function() { + var d = _addDiv("d1"); + var d2 = document.createElement("div"); + d2.setAttribute("foo", "ff"); + d.append(d2); + var d3 = document.createElement("div"); + d2.appendChild(d3); + ok(d2.getAttribute("id") == null, "no id on d2"); + _jsPlumb.draggable(d); + _jsPlumb.addEndpoint(d3); + ok(d2.getAttribute("id") == null, "no id on d2"); + ok(d3.getAttribute("id") != null, "id on d3"); + }); + + test(renderMode + ': defaultEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = _jsPlumb.addEndpoint(d3, {isSource:true}); + ok(e3.anchor, 'endpoint 3 has an anchor'); + var e4 = _jsPlumb.addEndpoint(d4, {isSource:true}); + assertEndpointCount("d3", 1, _jsPlumb); + assertEndpointCount("d4", 1, _jsPlumb); + assertContextSize(2); // one canvas for each of our two endpoints. + ok(!e3.isFull(), "endpoint 3 is not full."); + _jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // we have one connection + _jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // should have refused the connection; default max is 1. + assertContextSize(3); // now we have one more canvas, for the Connection that was accepted. + }); + + test(renderMode + ': specifiedEndpointMaxConnections', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = _jsPlumb.addEndpoint(d5, {isSource:true, maxConnections:3}); + ok(e5.anchor, 'endpoint 5 has an anchor'); + var e6 = _jsPlumb.addEndpoint(d6, {isSource:true, maxConnections:2}); // this one has max TWO + assertEndpointCount("d5", 1, _jsPlumb); assertEndpointCount("d6", 1, _jsPlumb); + assertContextSize(2); + ok(!e5.isFull(), "endpoint 5 is not full."); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 1); // we have one connection + assertContextSize(3); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // two connections + assertContextSize(4); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // should have refused; max is 2, for d4. + assertContextSize(4); + }); + + test(renderMode + ': noEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = _jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + var e4 = _jsPlumb.addEndpoint(d4, {isSource:true, maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertContextSize(3); + assertConnectionCount(e3, 1); // we have one connection + _jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 2); // we have two. etc (default was one. this proves max is working). + assertContextSize(4); + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = _jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e4}); + assertConnectionCount(e4, 3); + assertContextSize(6); + }); + + test(renderMode + ': anchors equal', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + ok(a1.equals(a2), "anchors are the same"); + }); + + test(renderMode + ': anchors equal with offsets', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb); + ok(a1.equals(a2), "anchors are the same"); + }); + + test(renderMode + ': anchors not equal', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 0, 1], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + ok(!a1.equals(a2), "anchors are different"); + }); + + test(renderMode + ': anchor not equal with offsets', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + ok(!a1.equals(a2), "anchors are different"); + }); + + test(renderMode + ': detach does not fail when no arguments are provided', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + _jsPlumb.connect({source:d3, target:d4}); + _jsPlumb.detach(); + }); + + // test that detach does not fire an event by default + test(renderMode + ': _jsPlumb.detach should fire detach event by default', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach(conn); + equals(eventCount, 1); + }); + + // test that detach does not fire an event by default + test(renderMode + ': _jsPlumb.detach should fire detach event by default, using params object', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({connection:conn}); + equals(eventCount, 1); + }); + + // test that detach fires an event when instructed to do so + test(renderMode + ': _jsPlumb.detach should not fire detach event when instructed to not do so', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach(conn, {fireEvent:false}); + equals(eventCount, 0); + }); + + // issue 81 + test(renderMode + ': _jsPlumb.detach should fire only one detach event (pass Connection as argument)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach(conn); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': _jsPlumb.detach should fire only one detach event (pass Connection as param in argument)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({connection:conn}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': detach should fire only one detach event (pass source and targets as strings as arguments in params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({source:"d5", target:"d6"}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': detach should fire only one detach event (pass source and targets as divs as arguments in params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({source:d5, target:d6, fireEvent:true}); + equals(eventCount, 1); + }); + + //TODO make sure you run this test with a single detach call, to ensure that + // single detach calls result in the connection being removed. detachEveryConnection can + // just blow away the connectionsByScope array and recreate it. + test(renderMode + ': getConnections (simple case, default scope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + _jsPlumb.connect({source:d5, target:d6}); + assertContextSize(3); + var c = _jsPlumb.getConnections(); // will get all connections in the default scope. + equals(c.length, 1, "there is one connection"); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by element id using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + _jsPlumb.connect({source:d5, target:d6}); + _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach({source:'d5', target:'d6'}); + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by id using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + _jsPlumb.connect({source:d5, target:d6}); + _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach({source:"d5", target:"d6"}); + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by element object using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + _jsPlumb.connect({source:d5, target:d6}); + _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach({source:d5, target:d6}); + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by Connection)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + var c56 = _jsPlumb.connect({source:d5, target:d6}); + var c67 = _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach(c56); + assertContextSize(5); // check that the connection canvas was removed. + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + }); + +// beforeDetach functionality + + test(renderMode + ": detach; beforeDetach on connect call returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { return false; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": detach; beforeDetach on connect call returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { return true; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": detach; beforeDetach on connect call throws an exception; we treat it with the contempt it deserves and pretend it said the detach was ok.", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { throw "i am an example of badly coded beforeDetach, but i don't break jsPlumb "; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": detach; beforeDetach on addEndpoint call to source Endpoint returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return false; } }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": detach; beforeDetach on addEndpoint call to source Endpoint returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return true; } }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + + test(renderMode + ": Endpoint.detach; beforeDetach on addEndpoint call to source Endpoint returns false; Endpoint.detach returns false too (the UI needs it to)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return false; } }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + var success = e1.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + ok(!success, "Endpoint reported detach did not execute"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns false but detach is forced", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c, {forceDetach:true}); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was denied but forced anyway"); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was denied but forced anyway"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return true; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach bound to _jsPlumb returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + _jsPlumb.bind("beforeDetach", function(connection) { + ok(connection.sourceId === "d1", "connection is provided and configured with correct source"); + ok(connection.targetId === "d2", "connection is provided and configured with correct target"); + return false; + }); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach bound to _jsPlumb returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + _jsPlumb.bind("beforeDetach", function(connection) { + ok(connection.sourceId === "d1", "connection is provided and configured with correct source"); + ok(connection.targetId === "d2", "connection is provided and configured with correct target"); + return true; + }); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was denied"); + }); + + test(renderMode + ": _jsPlumb.detachAllConnections ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detachAllConnections(d1); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachAll was called"); + }); + + test(renderMode + ": _jsPlumb.detachEveryConnection ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detachEveryConnection(); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachEveryConnection was called"); + }); + + test(renderMode + ": Endpoint.detachAll ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + e1.detachAll(); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachAllConnections was called"); + }); + +// ******** end of beforeDetach tests ************** + +// detachEveryConnection/detachAllConnections fireEvent overrides tests + + test(renderMode + ": _jsPlumb.detachEveryConnection fires events", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0; + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachEveryConnection(); + equals(connCount, 0, "no connections registered"); + }); + + test(renderMode + ": _jsPlumb.detachEveryConnection doesn't fire events when instructed not to", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0; + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachEveryConnection({fireEvent:false}); + equals(connCount, 2, "two connections registered"); + }); + + test(renderMode + ": _jsPlumb.detachAllConnections fires events", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0, + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachAllConnections("d1"); + equals(connCount, 0, "no connections registered"); + }); + + test(renderMode + ": _jsPlumb.detachAllConnections doesn't fire events when instructed not to", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0, + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachAllConnections("d1", {fireEvent:false}); + equals(connCount, 2, "two connections registered"); + }); + + test(renderMode + ': getConnections (scope testScope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + _jsPlumb.connect({source:d5, target:d6, scope:'testScope'}); + var c = _jsPlumb.getConnections("testScope"); // will get all connections in testScope + equals(c.length, 1, "there is one connection"); + equals(c[0].sourceId, 'd5', "the connection's source is d5"); + equals(c[0].targetId, 'd6', "the connection's source is d6"); + c = _jsPlumb.getConnections(); // will get all connections in default scope; should be none. + equals(c.length, 0, "there are no connections in the default scope"); + }); + + test(renderMode + ': _jsPlumb.getAllConnections (filtered by scope)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + _jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + _jsPlumb.connect({source:d9, target:d10}); // default scope + var c = _jsPlumb.getAllConnections(); // will get all connections + equals(c[_jsPlumb.getDefaultScope()].length, 1); + equals(c['testScope'].length, 1); + // now supply a list of scopes + c = _jsPlumb.getConnections(); + equals(c.length, 1); + c = _jsPlumb.getConnections("testScope"); + equals(c.length, 1, "there is one connection in 'testScope'"); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by scope and sourceId)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + _jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + _jsPlumb.connect({source:d9, target:d8, scope:'testScope'}); + _jsPlumb.connect({source:d9, target:d10}); // default scope + var c = _jsPlumb.getConnections({scope:'testScope', source:'d8'}); // will get all connections with sourceId 'd8' + equals(c.length, 1, "there is one connection in 'testScope' from d8"); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by scope, source id and target id)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope'}); + var c = _jsPlumb.getConnections({scope:'testScope', source:'d11', target:'d13'}); + equals(c.length, 1, "there is one connection from d11 to d13"); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = _jsPlumb.getConnections({scope:['testScope','testScope3']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes and source ids)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = _jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes, source ids and target ids)', function() { + _jsPlumb.detachEveryConnection(); + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'), d14=_addDiv("d14"), d15=_addDiv("d15"); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d11, target:d14, scope:'testScope'}); + _jsPlumb.connect({source:d11, target:d15, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + assertContextSize(18); + var c = _jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11'], target:['d14', 'd15']}); + equals(c['testScope'].length, 2, 'there are two connections in testScope'); + equals(c['testScope3'], null, 'there are no connections in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + var anEntry = c['testScope'][0]; + ok(anEntry.endpoints[0] != null, "Source endpoint is set"); + ok(anEntry.endpoints[1] != null, "Target endpoint is set"); + equals(anEntry.source.attr("id"), "d11", "Source is div d11"); + equals(anEntry.target.attr("id"), "d14", "Target is div d14"); + }); + + test(renderMode + ': getEndpoints, one Endpoint added by addEndpoint, get Endpoints by selector', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + _jsPlumb.addEndpoint(d1); + var e = _jsPlumb.getEndpoints(d1); + equals(e.length, 1, "there is one endpoint for element d1"); + }); + + test(renderMode + ': getEndpoints, one Endpoint added by addEndpoint, get Endpoints by id', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + _jsPlumb.addEndpoint(d1); + var e = _jsPlumb.getEndpoints("d1"); + equals(e.length, 1, "there is one endpoint for element d1"); + }); + + test(renderMode + ': connection event listeners', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null/*, returnedParams2 = null*/; + _jsPlumb.bind("jsPlumbConnection", function(params) { + returnedParams = $.extend({}, params); + }); + var c = _jsPlumb.connect({source:d1, target:d2}); + ok(returnedParams != null, "new connection listener event was fired"); + ok(returnedParams.connection != null, 'connection is set'); + equals(returnedParams.sourceId, "d1", 'sourceid is set'); + equals(returnedParams.targetId, "d2", 'targetid is set'); + equals(returnedParams.source.attr("id"), "d1", 'source is set'); + equals(returnedParams.target.attr("id"), "d2" , 'target is set'); + ok(returnedParams.sourceEndpoint != null, "source endpoint is not null"); + ok(returnedParams.targetEndpoint != null, "target endpoint is not null"); + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.detach(c); + ok(returnedParams.connection != null, 'connection is set'); + }); + + test(renderMode + ': detach event listeners (detach by connection)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + ok(returnedParams.connection != null, "removed connection listener event passed in connection"); + }); + + test(renderMode + ': detach event listeners (detach by element ids)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach({source:"d1",target:"d2"}); + ok(returnedParams != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (detach by elements)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach({source:d1,target:d2}); + ok(returnedParams != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detach method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detachFrom method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachFrom(e2); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detachAll method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachAll(); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via _jsPlumb.deleteEndpoint method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + _jsPlumb.deleteEndpoint(e1); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(1); + }); + + test(renderMode + ': detach event listeners (ensure cleared by _jsPlumb.reset)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach({source:d1,target:d2, fireEvent:true}); + ok(returnedParams != null, "removed connection listener event was fired"); + returnedParams = null; + + _jsPlumb.reset(); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach(d1,d2); + ok(returnedParams == null, "connection listener was cleared by _jsPlumb.reset()"); + }); + + test(renderMode + ': connection events that throw errors', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null, returnedParams2 = null; + _jsPlumb.bind("jsPlumbConnection", function(params) { + returnedParams = $.extend({}, params); + throw "oh no!"; + }); + _jsPlumb.connect({source:d1, target:d2}); + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + _jsPlumb.connect({source:d3, target:d4}); + ok(returnedParams != null, "new connection listener event was fired; we threw an error, _jsPlumb survived."); + }); + + test(renderMode + ': unbinding connection event listeners, connection', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var count = 0; + _jsPlumb.bind("jsPlumbConnection", function(params) { + count++; + }); + var c = _jsPlumb.connect({source:d1, target:d2}); + ok(count == 1, "received one event"); + _jsPlumb.unbind("jsPlumbConnection"); + var c2 = _jsPlumb.connect({source:d1, target:d2}); + ok(count == 1, "still received only one event"); + + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + count--; + }); + _jsPlumb.detach(c); + ok(count == 0, "count of events is now zero"); + }); + + test(renderMode + ': unbinding connection event listeners, detach', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var count = 0; + _jsPlumb.bind("jsPlumbConnection", function(params) { + count++; + }); + var c = _jsPlumb.connect({source:d1, target:d2}); + ok(count == 1, "received one event"); + var c2 = _jsPlumb.connect({source:d1, target:d2}); + ok(count == 2, "received two events"); + + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + count--; + }); + _jsPlumb.detach(c); + ok(count == 1, "count of events is now one"); + _jsPlumb.unbind("jsPlumbConnectionDetached"); + _jsPlumb.detach(c2); + ok(count == 1, "count of events is still one"); + }); + + test(renderMode + ': unbinding connection event listeners, all listeners', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var count = 0; + _jsPlumb.bind("jsPlumbConnection", function(params) { + count++; + }); + var c = _jsPlumb.connect({source:d1, target:d2}), + c2 = _jsPlumb.connect({source:d1, target:d2}), + c3 = _jsPlumb.connect({source:d1, target:d2}); + + ok(count == 3, "received three events"); + + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + count--; + }); + _jsPlumb.detach(c); + ok(count == 2, "count of events is now two"); + + _jsPlumb.unbind(); // unbind everything + + _jsPlumb.detach(c2); + _jsPlumb.detach(c3); + _jsPlumb.connect({source:d1, target:d2}) + _jsPlumb.connect({source:d1, target:d2}) + _jsPlumb.connect({source:d1, target:d2}) + _jsPlumb.connect({source:d1, target:d2}) + + ok(count == 2, "count of events is still two"); + }); + + test(renderMode + ": Endpoint.detachFrom", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + e16.detachFrom(e17); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by _jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": Endpoint.detachFromConnection", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + e16.detachFromConnection(conn); + assertContextSize(3); // all canvases should remain; the connection was not removed. + // but endpoint e16 should have no connections now. + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": Endpoint.detachAll", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + var e16 = _jsPlumb.addEndpoint($("#d16"), {isSource:true,maxConnections:-1}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint($("#d17"), {isSource:true}); + var e18 = _jsPlumb.addEndpoint($("#d18"), {isSource:true}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e18}); + assertConnectionCount(e16, 2); + assertConnectionCount(e17, 1); + assertConnectionCount(e18, 1); + assertContextSize(5); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 2, _jsPlumb); + e16.detachAll(); + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": Endpoint.detach", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + e16.detach(conn); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by _jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": Endpoint.isConnectedTo", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.isConnectedTo(e17), true, "e16 and e17 are connected"); + }); + + asyncTest(renderMode + "jsPlumbUtil.setImage on Endpoint, with supplied onload", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { + endpoint:[ "Image", { + src:"../img/endpointTest1.png", + onload:function(imgEp) { + _jsPlumb.repaint("d1"); + ok(imgEp.img.src.indexOf("endpointTest1.png") != -1, "image source is correct"); + ok(imgEp.img.src.indexOf("endpointTest1.png") != -1, "image elementsource is correct"); + + imgEp.canvas.setAttribute("id", "iwilllookforthis"); + + _jsPlumb.removeAllEndpoints("d1"); + ok(document.getElementById("iwilllookforthis") == null, "image element was removed after remove endpoint"); + } + } ] + }; + start(); + _jsPlumb.addEndpoint(d1, e); + }); + + test(renderMode + ": setting endpoint uuid", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + equals(e16.getUuid(), uuid, "endpoint's uuid was set correctly"); + }); + + test(renderMode + ": _jsPlumb.getEndpoint (by uuid)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by uuid, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + _jsPlumb.deleteEndpoint(uuid); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + var ebe = _jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by uuid, connections too)", function() { + // create two endpoints (one with a uuid), add them to two divs and then connect them. + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + // check that the connection was ok. + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + // delete the endpoint that has a uuid. verify that the endpoint cannot be retrieved and that the connection has been removed, but that + // element d17 still has its Endpoint. + _jsPlumb.deleteEndpoint(uuid); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + var ebe = _jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + ebe = _jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 1, "element d17 still has its Endpoint"); + assertContextSize(1); + + // now delete d17's endpoint and check that it has gone. + _jsPlumb.deleteEndpoint(e17); + f = _jsPlumb.getEndpoint(e17); + equals(f, null, "endpoint has been deleted"); + ebe = _jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 0, "element d17 no longer has any Endpoints"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by reference, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + _jsPlumb.deleteEndpoint(e16); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by reference, connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + _jsPlumb.deleteEndpoint(e16); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + assertContextSize(1); + }); + + test(renderMode + ": _jsPlumb.deleteEveryEndpoint", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + assertContextSize(2); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + _jsPlumb.deleteEveryEndpoint(); + + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = _jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEveryEndpoint (connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + _jsPlumb.deleteEveryEndpoint(); + + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = _jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); // no canvases. so all connection canvases have been cleaned up too. + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchor:[0,0.5,0,-1]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:"TopCenter"}); + assertContextSize(2); + equals(e16.anchor.x, 0); + equals(e16.anchor.y, 0.5); + equals(e17.anchor.x, 0.5); + equals(e17.anchor.y, 0); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (tooltip)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {tooltip:"FOO"}), + e17 = _jsPlumb.addEndpoint(d17, {tooltip:"BAZ"}); + assertContextSize(2); + equals(e16.canvas.getAttribute("title"), "FOO"); + equals(e17.canvas.getAttribute("title"), "BAZ"); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case, dynamic anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + assertContextSize(2); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case, two arg method)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchor:[0,0.5,0,-1]}); + var e17 = _jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchor:"TopCenter"}); + assertContextSize(2); + equals(e16.anchor.x, 0); + equals(e16.anchor.y, 0.5); + equals(false, e16.isTarget); + equals(true, e16.isSource); + equals(e17.anchor.x, 0.5); + equals(e17.anchor.y, 0); + equals(true, e17.isTarget); + equals(false, e17.isSource); + }); + + + test(renderMode + ": _jsPlumb.addEndpoints (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false, anchor:[0,0.5,0,-1] }, { isTarget:true, isSource:false, anchor:"TopCenter" }]); + assertContextSize(2); + equals(e16[0].anchor.x, 0); + equals(e16[0].anchor.y, 0.5); + equals(false, e16[0].isTarget); + equals(true, e16[0].isSource); + equals(e16[1].anchor.x, 0.5); + equals(e16[1].anchor.y, 0); + equals(true, e16[1].isTarget); + equals(false, e16[1].isSource); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (with reference params)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var refParams = {anchor:"RightMiddle"}; + var e16 = _jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false}, { isTarget:true, isSource:false }], refParams); + assertContextSize(2); + equals(e16[0].anchor.x, 1); + equals(e16[0].anchor.y, 0.5); + equals(false, e16[0].isTarget); + equals(true, e16[0].isSource); + equals(e16[1].anchor.x, 1); + equals(e16[1].anchor.y, 0.5); + equals(true, e16[1].isTarget); + equals(false, e16[1].isSource); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case, dynamic anchors, two arg method)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchors:["TopCenter", "BottomCenter"]}); + assertContextSize(2); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (default overlays)", function() { + _jsPlumb.Defaults.Overlays = [ + [ "Label", { id:"label" } ] + ]; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16), + e2 = _jsPlumb.addEndpoint(d17); + + ok(e1.getOverlay("label") != null, "endpoint 1 has overlay from defaults"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (default overlays)", function() { + _jsPlumb.Defaults.Overlays = [ + [ "Label", { id:"label" } ] + ]; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16, { + overlays:[ + ["Label", { id:"label2", location:[ 0.5, 1 ] } ] + ] + }), + e2 = _jsPlumb.addEndpoint(d17); + + ok(e1.getOverlay("label") != null, "endpoint 1 has overlay from defaults"); + ok(e1.getOverlay("label2") != null, "endpoint 1 has overlay from addEndpoint call"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (end point set label)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16), + e2 = _jsPlumb.addEndpoint(d17); + + e1.setLabel("FOO"); + equals(e1.getLabel(), "FOO", "endpoint's label is correct"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (end point set label in constructor, as string)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16, {label:"FOO"}), + e2 = _jsPlumb.addEndpoint(d17); + + equals(e1.getLabel(), "FOO", "endpoint's label is correct"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (end point set label in constructor, as function)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16, {label:function() { return "BAZ"; }, labelLocation:0.1}), + e2 = _jsPlumb.addEndpoint(d17); + + equals(e1.getLabel()(), "BAZ", "endpoint's label is correct"); + equals(e1.getLabelOverlay().getLocation(), 0.1, "endpoint's label's location is correct"); + }); + +// ****************** makeTarget (and associated methods) tests ******************************************** + + test(renderMode + ": _jsPlumb.makeTarget (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.makeTarget (specify two divs in an array)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.makeTarget([d16, d17], { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.makeTarget (specify two divs by id in an array)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.makeTarget(["d16", "d17"], { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.makeTarget (specify divs by selector)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + d16.addClass("FOO");d17.addClass("FOO"); + _jsPlumb.makeTarget($(".FOO"), { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e.length, 1, "d17 has one endpoint"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case, two connect calls)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17"}); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 2, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case, two connect calls, uniqueEndpoint set)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true, anchor:"LeftMiddle", uniqueEndpoint:true, maxConnections:-1 }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17"}); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].connections.length, 2, "endpoint on d17 has two connections"); + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (newConnection:true specified)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17", newConnection:true}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0.5, "anchor is BottomCenter"); //here we should be seeing the default anchor + equals(e[0].anchor.y, 1, "anchor is BottomCenter"); //here we should be seeing the default anchor + }); + +// jsPlumb.connect, after makeSource has been called on some element + test(renderMode + ": _jsPlumb.connect after makeSource (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeSource (simple case, two connect calls)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 2, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case, two connect calls, uniqueEndpoint set)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true, anchor:"LeftMiddle", uniqueEndpoint:true, maxConnections:-1 }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].connections.length, 2, "endpoint on d17 has two connections"); + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (newConnection:true specified)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16, newConnection:true}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0.5, "anchor is BottomCenter"); //here we should be seeing the default anchor + equals(e[0].anchor.y, 1, "anchor is BottomCenter"); //here we should be seeing the default anchor + }); + + test(renderMode + ": _jsPlumb.connect after makeSource on child; wth parent set (parent should be recognised)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18", d17); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d18, { isSource:true,anchor:"LeftMiddle", parent:d17 }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + assertEndpointCount("d18", 0, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + // makeSource, then disable it. should not be able to make a connection from it. + test(renderMode + ": _jsPlumb.connect after makeSource and setSourceEnabled(false) (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.setSourceEnabled(d17, false); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + }); + + // makeSource, then disable it. should not be able to make a connection from it. + test(renderMode + ": _jsPlumb.connect after makeSource and setSourceEnabled(false) (selector as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.setSourceEnabled($("div"), false); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + }); + + // makeSource, then toggle its enabled state. should not be able to make a connection from it. + test(renderMode + ": _jsPlumb.connect after makeSource and toggleSourceEnabled() (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.toggleSourceEnabled(d17); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + + _jsPlumb.toggleSourceEnabled(d17); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + // makeSource, then disable it. should not be able to make a connection from it. + test(renderMode + ": _jsPlumb.connect after makeSource and toggleSourceEnabled() (selector as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.toggleSourceEnabled($("div")); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + _jsPlumb.toggleSourceEnabled($("div")); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + test(renderMode + ": jsPlumb.isSource and jsPlumb.isSourceEnabled", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + ok(_jsPlumb.isSource(d17) == true, "d17 is recognised as connection source"); + ok(_jsPlumb.isSourceEnabled(d17) == true, "d17 is recognised as enabled"); + _jsPlumb.setSourceEnabled(d17, false); + ok(_jsPlumb.isSourceEnabled(d17) == false, "d17 is recognised as disabled"); + }); + + // makeSource, then disable it. should not be able to make a connection to it. + test(renderMode + ": _jsPlumb.connect after makeTarget and setTargetEnabled(false) (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.setTargetEnabled(d17, false); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + }); + + // makeTarget, then disable it. should not be able to make a connection to it. + test(renderMode + ": _jsPlumb.connect after makeTarget and setTargetEnabled(false) (selector as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.setTargetEnabled($("div"), false); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + }); + + // makeTarget, then toggle its enabled state. should not be able to make a connection to it. + test(renderMode + ": _jsPlumb.connect after makeTarget and toggleTargetEnabled() (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.toggleTargetEnabled(d17); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + + _jsPlumb.toggleTargetEnabled(d17); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + // makeTarget, then disable it. should not be able to make a connection to it. + test(renderMode + ": _jsPlumb.connect after makeTarget and toggleTargetEnabled() (selector as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.toggleTargetEnabled($("div")); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + _jsPlumb.toggleTargetEnabled($("div")); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + test(renderMode + ": jsPlumb.isTarget and jsPlumb.isTargetEnabled", function() { + var d17 = _addDiv("d17"); + _jsPlumb.makeTarget(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + ok(_jsPlumb.isTarget(d17) == true, "d17 is recognised as connection target"); + ok(_jsPlumb.isTargetEnabled(d17) == true, "d17 is recognised as enabled"); + _jsPlumb.setTargetEnabled(d17, false); + ok(_jsPlumb.isTargetEnabled(d17) == false, "d17 is recognised as disabled"); + }); + + // makeSource, then unmake it. should not be able to make a connection from it. then connect to it, which should succeed, + // because jsPlumb will just add a new endpoint. + test(renderMode + ": jsPlumb.unmakeSource (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + ok(_jsPlumb.isSource(d17) == true, "d17 is currently a source"); + // unmake source + _jsPlumb.unmakeSource(d17); + ok(_jsPlumb.isSource(d17) == false, "d17 is no longer a source"); + + // this should succeed, because d17 is no longer a source and so jsPlumb will just create and add a new endpoint to d17. + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + // maketarget, then unmake it. should not be able to make a connection to it. + test(renderMode + ": jsPlumb.unmakeTarget (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + ok(_jsPlumb.isTarget(d17) == true, "d17 is currently a target"); + // unmake target + _jsPlumb.unmakeTarget(d17); + ok(_jsPlumb.isTarget(d17) == false, "d17 is no longer a target"); + + // this should succeed, because d17 is no longer a target and so jsPlumb will just create and add a new endpoint to d17. + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + + test(renderMode + ": jsPlumb.removeEverySource and removeEveryTarget (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + _jsPlumb.makeSource(d16).makeTarget(d17).makeSource(d18); + ok(_jsPlumb.isSource(d16) == true, "d16 is a source"); + ok(_jsPlumb.isTarget(d17) == true, "d17 is a target"); + ok(_jsPlumb.isSource(d18) == true, "d18 is a source"); + + _jsPlumb.unmakeEverySource(); + _jsPlumb.unmakeEveryTarget(); + + ok(_jsPlumb.isSource(d16) == false, "d16 is no longer a source"); + ok(_jsPlumb.isTarget(d17) == false, "d17 is no longer a target"); + ok(_jsPlumb.isSource(d18) == false, "d18 is no longer a source"); + }); + +// *********************** end of makeTarget (and associated methods) tests ************************ + + test(renderMode + ': _jsPlumb.connect (between two Endpoints)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1, _jsPlumb); // no new endpoint should have been added + assertEndpointCount("d2", 1, _jsPlumb); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. + ok(c.id != null, "connection has had an id assigned"); + }); + + test(renderMode + ': _jsPlumb.connect (Endpoint connectorTooltip parameter)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1, {connectorTooltip:"FOO"}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + equals(c.connector.canvas.getAttribute("title"), "FOO", "connector canvas has label attribute set"); + }); + + test(renderMode + ': _jsPlumb.connect (tooltip parameter)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2, tooltip:"FOO"}); + equals(c.connector.canvas.getAttribute("title"), "FOO", "connector canvas has label attribute set"); + }); + + test(renderMode + ': _jsPlumb.connect (between two Endpoints, and dont supply any parameters to the Endpoints.)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1, _jsPlumb); // no new endpoint should have been added + assertEndpointCount("d2", 1, _jsPlumb); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. + }); + + test(renderMode + " : _jsPlumb.connect, passing 'anchors' array" ,function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, anchors:["LeftMiddle", "RightMiddle"]}); + + equals(c.endpoints[0].anchor.x, 0, "source anchor is at x=0"); + equals(c.endpoints[0].anchor.y, 0.5, "source anchor is at y=0.5"); + equals(c.endpoints[1].anchor.x, 1, "target anchor is at x=1"); + equals(c.endpoints[1].anchor.y, 0.5, "target anchor is at y=0.5"); + }); + + test(renderMode + ': _jsPlumb.connect (by endpoint)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + }); + + test(renderMode + ': _jsPlumb.connect (cost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17, cost:567}); + assertContextSize(3); + equals(c.getCost(), 567, "connection cost is 567"); + }); + + test(renderMode + ': _jsPlumb.connect (default cost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), undefined, "default connection cost is 1"); + }); + + test(renderMode + ': _jsPlumb.connect (set cost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), undefined, "default connection cost is 1"); + c.setCost(8989); + equals(c.getCost(), 8989, "connection cost is 8989"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (connectionCost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionCost:567}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), 567, "connection cost is 567"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (change connectionCost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionCost:567, maxConnections:-1}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1}); + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), 567, "connection cost is 567"); + e16.setConnectionCost(23); + var c2 = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(c2.getCost(), 23, "connection cost is 23 after change on endpoint"); + }); + + test(renderMode + ': _jsPlumb.connect (default bidirectional)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true}), + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.isBidirectional(), true, "default connection is bidirectional"); + }); + + test(renderMode + ': _jsPlumb.connect (bidirectional false)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true}), + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17, bidirectional:false}); + assertContextSize(3); + equals(c.isBidirectional(), false, "connection is not bidirectional"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (connectionsBidirectional)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionsBidirectional:true, maxConnections:-1}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.isBidirectional(), true, "connection is bidrectional"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (change connectionsBidirectional)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionsBidirectional:true, maxConnections:-1}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1}); + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.isBidirectional(), true, "connection is bidirectional"); + e16.setConnectionsBidirectional(false); + var c2 = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(c2.isBidirectional(), false, "connection is not bidirectional"); + }); + + test(renderMode + ": _jsPlumb.connect (two Endpoints - that have been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = _jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1, uuid:srcEndpointUuid}); + var e2 = _jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1, uuid:dstEndpointUuid}); + _jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ] }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertContextSize(3); + }); + + test(renderMode + ": _jsPlumb.connect (two Endpoints - that have not been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ], source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + var e1 = _jsPlumb.getEndpoint(srcEndpointUuid); + ok(e1 != null, "endpoint with src uuid added"); + ok(e1.canvas != null); + var e2 = _jsPlumb.getEndpoint(dstEndpointUuid); + ok(e2 != null, "endpoint with target uuid added"); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.connect (two Endpoints - that have been already added - by endpoint reference)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = _jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1}); + var e2 = _jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1}); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.connect (two elements)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.connect({ source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, straight)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, bezier, no params)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Bezier" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 150, "Bezier connector chose 150 curviness"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, bezier, curviness as int)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", { curviness:200 }] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Canvas Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 200, "Bezier connector chose 200 curviness"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, bezier, curviness as named option)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", {curviness:300}] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 300, "Bezier connector chose 300 curviness"); + }); + + test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Canvas Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as strings)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:["LeftMiddle", "RightMiddle"] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.5, conn.endpoints[0].anchor.y, "source anchor y"); + equals(1, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.5, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + + test(renderMode + ": _jsPlumb.connect (two argument method in which some data is reused across connections)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"), d19 = _addDiv("d19"); + var sharedData = { connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }; + var conn = _jsPlumb.connect({ source:d16, target:d17}, sharedData); + var conn2 = _jsPlumb.connect({ source:d18, target:d19}, sharedData); + assertContextSize(6); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 2, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(conn2.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + equals(0.3, conn2.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn2.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn2.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn2.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoint test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoints test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Dot, "Dot endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Blank Endpoint specified via 'endpoint' param)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Blank" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Blank Endpoint specified via 'endpoints' param)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Blank", "Blank" ] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Dot, "Dot endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, connector test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {}); + var e17 = _jsPlumb.addEndpoint(d17, {}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {}); + var e17 = _jsPlumb.addEndpoint(d17, {}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, anchors as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = "TopCenter", a17 = "BottomCenter"; + var e16 = _jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, 0.5, "endpoint 16 is at top center");equals(e16.anchor.y, 0, "endpoint 16 is at top center"); + equals(e17.anchor.x, 0.5, "endpoint 17 is at bottom center");equals(e17.anchor.y, 1, "endpoint 17 is at bottom center"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = [0,0.5,0,-1], a17 = [1,0.0,-1,-1]; + var e16 = _jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, a16[0]);equals(e16.anchor.y, a16[1]); + equals(e17.anchor.x, a17[0]);equals(e17.anchor.y, a17[1]); + equals(e16.anchor.getOrientation()[0], a16[2]); equals(e16.anchor.getOrientation()[1], a16[3]); + equals(e17.anchor.getOrientation()[0], a17[2]); equals(e17.anchor.getOrientation()[1], a17[3]); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchor')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchor:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:["TopCenter", "BottomCenter"]}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchors')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, default endpoint, supplied dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var anchors = [ [0.25, 0, 0, -1], [1, 0.25, 1, 0], [0.75, 1, 0, 1], [0, 0.75, -1, 0] ]; + _jsPlumb.connect({source:d1, target:d2, dynamicAnchors:anchors}); // auto connect with default endpoint and provided anchors + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.detach({source:d1, target:d2}); + // this changed in 1.3.5, because auto generated endpoints are now removed by detach. so i added the test below this one + // to check that the deleteEndpointsOnDetach flag is honoured. + assertEndpointCount("d1", 0, _jsPlumb);assertEndpointCount("d2", 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, default endpoint, supplied dynamic anchors, delete on detach false)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var anchors = [ [0.25, 0, 0, -1], [1, 0.25, 1, 0], [0.75, 1, 0, 1], [0, 0.75, -1, 0] ]; + _jsPlumb.connect({ + source:d1, + target:d2, + dynamicAnchors:anchors, + deleteEndpointsOnDetach:false + }); // auto connect with default endpoint and provided anchors + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.detach({source:d1, target:d2}); + // this changed in 1.3.5, because auto generated endpoints are now removed by detach. so i added this test + // to check that the deleteEndpointsOnDetach flag is honoured. + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, supplied endpoint and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = _jsPlumb.addEndpoint(d1, endpoint); + var e2 = _jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(3); + _jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, supplied endpoints using 'source' and 'target' (this test is identical to the one above apart from the param names), and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = _jsPlumb.addEndpoint(d1, endpoint); + var e2 = _jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + _jsPlumb.connect({source:e1, target:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(3); + _jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + " detachable parameter defaults to true on _jsPlumb.connect", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), true, "connections detachable by default"); + }); + + test(renderMode + " detachable parameter set to false on _jsPlumb.connect", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, detachable:false}); + equals(c.isDetachable(), false, "connection detachable"); + }); + + test(renderMode + " setDetachable on initially detachable connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), true, "connection initially detachable"); + c.setDetachable(false); + equals(c.isDetachable(), false, "connection not detachable"); + }); + + test(renderMode + " setDetachable on initially not detachable connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, detachable:false }); + equals(c.isDetachable(), false, "connection not initially detachable"); + c.setDetachable(true); + equals(c.isDetachable(), true, "connection now detachable"); + }); + + test(renderMode + " _jsPlumb.Defaults.ConnectionsDetachable", function() { + _jsPlumb.Defaults.ConnectionsDetachable = false; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), false, "connections not detachable by default (overrode the defaults)"); + _jsPlumb.Defaults.ConnectionsDetachable = true; + }); + + + test(renderMode + ": _jsPlumb.connect (testing for connection event callback)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var connectCallback = null, detachCallback = null; + _jsPlumb.bind("jsPlumbConnection", function(params) { + connectCallback = $.extend({}, params); + }); + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + detachCallback = $.extend({}, params); + }); + _jsPlumb.connect({source:d1, target:d2}); // auto connect with default endpoint and anchor set + ok(connectCallback != null, "connect callback was made"); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.detach({source:d1, target:d2}); + assertContextSize(2); + ok(detachCallback != null, "detach callback was made"); + }); + + test(renderMode + ": _jsPlumb.connect (setting cssClass on Connector)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2,cssClass:"CSS"}); + var has = function(clazz) { + var cn = c.connector.canvas.className, + cns = cn.constructor == String ? cn : cn.baseVal; + + return cns.indexOf(clazz) != -1; + }; + ok(has("CSS"), "custom cssClass set correctly"); + ok(has(_jsPlumb.connectorClass), "basic connector class set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (overlays, long-hand version)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = {width:40,length:40,location:0.7, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"}}; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + }); + + test(renderMode + ": _jsPlumb.connect (overlays, long-hand version, IDs specified)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + equals("aLabel", connection1.overlays[0].id); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + equals("anArrow", connection1.overlays[1].id); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays)", function() { + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays + overlays specified in connect call)", function() { + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, overlays:[ + ["Label", {id:"label"}] + ]}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("label") != null, "Label overlay created from connect call"); + }); + + test(renderMode + ": _jsPlumb.connect (default connection overlays)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + }); + + test(renderMode + ": _jsPlumb.connect (default connection overlays + overlays specified in connect call)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, overlays:[ + ["Label", {id:"label"}] + ]}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("label") != null, "Label overlay created from connect call"); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays + default connection overlays)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow2" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("arrow2") != null, "Arrow overlay created from connection defaults"); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays + default connection overlays)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow2" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, overlays:[ + ["Label", {id:"label"}] + ]}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("arrow2") != null, "Arrow overlay created from connection defaults"); + ok(c.getOverlay("label") != null, "Label overlay created from connect call"); + }); + + test(renderMode + ": _jsPlumb.connect (label overlay set using 'label')", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO" + }); + + equals(c.getLabel(), "FOO", "label is set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction, with string)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2}); + + c.setLabel("FOO"); + equals(c.getLabel(), "FOO", "label is set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction, with function)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2}); + + c.setLabel(function() { return "BAR"; }); + equals(c.getLabel()(), "BAR", "label is set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction, with params object)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2}); + + c.setLabel({ + label:"BAZ", + cssClass:"CLASSY", + location:0.9 + }); + var lo = c.getLabelOverlay(); + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "BAZ", "label overlay has correct value"); + equals(lo.getLocation(), 0.9, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction with existing label set, with params object)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO", + labelLocation:0.2 + }); + + var lo = c.getLabelOverlay(); + equals(lo.getLocation(), 0.2, "label overlay has correct location"); + + c.setLabel({ + label:"BAZ", + cssClass:"CLASSY", + location:0.9 + }); + + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "BAZ", "label overlay has correct value"); + equals(lo.getLocation(), 0.9, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (getLabelOverlay, label on connect call)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO" + }), + lo = c.getLabelOverlay(); + + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "FOO", "label overlay has correct value"); + equals(lo.getLocation(), 0.5, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (getLabelOverlay, label on connect call, location set)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO", + labelLocation:0.2 + }), + lo = c.getLabelOverlay(); + + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "FOO", "label overlay has correct value"); + equals(lo.getLocation(), 0.2, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (remove single overlay by id)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + connection1.removeOverlay("aLabel"); + equals(1, connection1.overlays.length); + equals("anArrow", connection1.overlays[0].id); + }); + + test(renderMode + ": _jsPlumb.connect (remove multiple overlays by id)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + connection1.removeOverlays("aLabel", "anArrow"); + equals(0, connection1.overlays.length); + }); + + test(renderMode + ": _jsPlumb.connect (overlays, short-hand version)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var loc = { location:0.7 }; + var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label", {label:"CONNECTION 1", location:0.3}], + ["Arrow", arrowSpec, loc] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + }); + + test(renderMode + ": _jsPlumb.connect (removeAllOverlays)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var loc = { location:0.7 }; + var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label", {label:"CONNECTION 1", location:0.3, cssClass:"PPPP"}], + ["Arrow", arrowSpec, loc] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + + connection1.removeAllOverlays(); + equals(0, connection1.overlays.length); + equals(0, $(".PPPP").length); + }); + + test(renderMode + ": _jsPlumb.connect, specify arrow overlay using string identifier only", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:["Arrow"]}); + equals(jsPlumb.Overlays[renderMode].Arrow, conn.overlays[0].constructor); + }); + + test(renderMode + ": Connection.getOverlay method, existing overlay", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("arrowOverlay"); + ok(overlay != null); + }); + + test(renderMode + ": Connection.getOverlay method, non-existent overlay", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("IDONTEXIST"); + ok(overlay == null); + }); + + test(renderMode + ": Overlay.setVisible method", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("arrowOverlay"); + ok(overlay.isVisible()); + overlay.setVisible(false); + ok(!overlay.isVisible()); + overlay.setVisible(true); + ok(overlay.isVisible()); + }); + + test(renderMode + ": _jsPlumb.connect (custom label overlay, set on Defaults, return plain DOM element)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Custom",{ id:"custom", create:function(connection) { + ok(connection != null, "we were passed in a connection"); + var d = document.createElement("div"); + d.setAttribute("custom", "true"); + d.innerHTML = connection.id; + return d; + }}] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + var o = c.getOverlay("custom"); + equals(o.getElement().getAttribute("custom"), "true", "custom overlay created correctly"); + equals(o.getElement().innerHTML, c.id, "custom overlay has correct value"); + }); + + test(renderMode + ": _jsPlumb.connect (custom label overlay, set on Defaults, return selector)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Custom",{ id:"custom", create:function(connection) { + ok(connection != null, "we were passed in a connection"); + return $("
" + connection.id + "
"); + }}] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + var o = c.getOverlay("custom"); + equals(o.getElement().getAttribute("custom"), "true", "custom overlay created correctly"); + equals(o.getElement().innerHTML, c.id, "custom overlay has correct value"); + }); + + // this test is for the original detach function; it should stay working after i mess with it + // a little. + test(renderMode + ": _jsPlumb.detach (by element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + var e3 = _jsPlumb.addEndpoint(d1); + var e4 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + _jsPlumb.connect({ sourceEndpoint:e3, targetEndpoint:e4 }); // make two connections to be sure this works ;) + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionCount(e3, 1); + assertConnectionCount(e4, 1); + + _jsPlumb.detach({source:"d1", target:"d2"}); + + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionCount(e3, 0); + assertConnectionCount(e4, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + // detach is being made to operate more like connect - by taking one argument with a whole + // bunch of possible params in it. if two args are passed in it will continue working + // in the old way. + test(renderMode + ": _jsPlumb.detach (params object, using element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({source:"d1", target:"d2"}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); +/* + test(renderMode + ": _jsPlumb.detach (params object, using target only)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e1 = _jsPlumb.addEndpoint(d1, {maxConnections:2}), + e2 = _jsPlumb.addEndpoint(d2), + e3 = _jsPlumb.addEndpoint(d3); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e3 }); + assertConnectionCount(e1, 2); + assertConnectionCount(e2, 1); + assertConnectionCount(e3, 1); + _jsPlumb.detach({target:"d2"}); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 0); + assertConnectionCount(e3, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1); + }); +*/ + test(renderMode + ": _jsPlumb.detach (params object, using element objects)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({source:d1, target:d2}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.detach (source and target as endpoint UUIDs)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {uuid:"abcdefg"}); + ok(_jsPlumb.getEndpoint("abcdefg") != null, "e1 exists"); + var e2 = _jsPlumb.addEndpoint(d2, {uuid:"hijklmn"}); + ok(_jsPlumb.getEndpoint("hijklmn") != null, "e2 exists"); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({uuids:["abcdefg", "hijklmn"]}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.detach (sourceEndpoint and targetEndpoint supplied)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.makeDynamicAnchors (longhand)", function() { + var anchors = [_jsPlumb.makeAnchor([0.2, 0, 0, -1], null, _jsPlumb), _jsPlumb.makeAnchor([1, 0.2, 1, 0], null, _jsPlumb), + _jsPlumb.makeAnchor([0.8, 1, 0, 1], null, _jsPlumb), _jsPlumb.makeAnchor([0, 0.8, -1, 0], null, _jsPlumb) ]; + var dynamicAnchor = _jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); + }); + + test(renderMode + ": _jsPlumb.makeDynamicAnchors (shorthand)", function() { + var anchors = [[0.2, 0, 0, -1], [1, 0.2, 1, 0], + [0.8, 1, 0, 1], [0, 0.8, -1, 0] ]; + var dynamicAnchor = _jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); + }); + + test(renderMode + ": Connection.isVisible/setVisible", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c1 = _jsPlumb.connect({source:d1,target:d2}); + equals(true, c1.isVisible(), "Connection is visible after creation."); + c1.setVisible(false); + equals(false, c1.isVisible(), "Connection is not visible after calling setVisible(false)."); + equals($(c1.connector.canvas).css("display"), "none"); + c1.setVisible(true); + equals(true, c1.isVisible(), "Connection is visible after calling setVisible(true)."); + equals($(c1.connector.canvas).css("display"), "block"); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible basic test (no connections)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals($(e1.canvas).css("display"), "none"); + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals($(e1.canvas).css("display"), "block"); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should track changes in the source, because it has only this connection.)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + var c1 = _jsPlumb.connect({source:e1, target:e2}); + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals(false, e2.isVisible(), "other Endpoint is not visible either."); + equals(false, c1.isVisible(), "connection between the two is not visible either."); + + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals(true, e2.isVisible(), "other Endpoint is visible too"); + equals(true, c1.isVisible(), "connection between the two is visible too."); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should not track changes in the source, because it has another connection.)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2, { maxConnections:2 }), e3 = _jsPlumb.addEndpoint(d3); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + var c1 = _jsPlumb.connect({source:e1, target:e2}); + var c2 = _jsPlumb.connect({source:e2, target:e3}); + + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals(true, e2.isVisible(), "other Endpoint should still be visible."); + equals(true, e3.isVisible(), "third Endpoint should still be visible."); + equals(false, c1.isVisible(), "connection between the two is not visible either."); + equals(true, c2.isVisible(), "other connection is visible."); + + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals(true, e2.isVisible(), "other Endpoint is visible too"); + equals(true, c1.isVisible(), "connection between the two is visible too."); + equals(true, c2.isVisible(), "other connection is visible."); + }); + + // tests of the functionality that allows a user to specify that they want elements appended to the document body + test(renderMode + " _jsPlumb.Defaults.Container, specified with a selector", function() { + _jsPlumb.Defaults.Container = $("body"); + equals($("#container")[0].childNodes.length, 0, "container has no nodes"); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals($("#container")[0].childNodes.length, 2, "container has two div elements"); // the divs we added have been added to the 'container' div. + // but we have told _jsPlumb to add its canvas to the body, so this connect call should not add another few elements to the container: + _jsPlumb.connect({source:d1, target:d2}); + equals($("#container")[0].childNodes.length, 2, "container still has two div elements"); + }); + + // tests of the functionality that allows a user to specify that they want elements appended to some specific container. + test(renderMode + " _jsPlumb.Defaults.Container, specified with DOM element", function() { + _jsPlumb.Defaults.Container = document.getElementsByTagName("body")[0]; + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length, "two divs added to the container"); // the divs we added have been added to the 'container' div. + // but we have told _jsPlumb to add its canvas to the body, so this connect call should not add another few elements to the container: + var bodyElementCount = $("body")[0].childNodes.length; + _jsPlumb.connect({source:d1, target:d2}); + equals(2, $("#container")[0].childNodes.length, "still only two children in container; elements were added to the body by _jsPlumb"); + // test to see if 3 elements have been added + equals(bodyElementCount + 3, $("body")[0].childNodes.length, "3 new elements added to the document body"); + }); + + test(renderMode + " container specified to connect call, with a selector", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length); // the divs we added have been added to the 'container' div. + var bodyElementCount = $("body")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to the body, so this connect call should not add another few elements to the container: + _jsPlumb.connect({source:d1, target:d2, container:$("body")}); + equals(2, $("#container")[0].childNodes.length); + equals(bodyElementCount + 3, $("body")[0].childNodes.length, "3 new elements added to the document body"); + }); + + test(renderMode + " container specified to connect call, with a string ID", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + equals(3, $("#container")[0].childNodes.length, "container has divs we added"); // the divs we added have been added to the 'container' div. + var d3ElementCount = $("#d3")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to "d3", so this connect call should not add another few elements to the container: + _jsPlumb.connect({source:d1, target:d2, container:"d3"}); + equals(3, $("#container")[0].childNodes.length, "container still has only the divs we added"); + equals(d3ElementCount + 3, $("#d3")[0].childNodes.length, "3 new elements added to div d3"); + }); + + test(renderMode + " container specified to addEndpoint call, with a selector", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length); // the divs we added have been added to the 'container' div. + var bodyElementCount = $("body")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to the body, so this connect call should not add another few elements to the container: + _jsPlumb.addEndpoint(d1, {container:$("body")}); + equals(2, $("#container")[0].childNodes.length); + equals(bodyElementCount + 1, $("body")[0].childNodes.length, "1 new element added to the document body"); + }); + + test(renderMode + " container specified to addEndpoint call, with a string ID", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + equals(3, $("#container")[0].childNodes.length, "container has divs we added"); // the divs we added have been added to the 'container' div. + var d3ElementCount = $("#d3")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to "d3", so this connect call should not add another few elements to the container: + _jsPlumb.addEndpoint(d1, { container:"d3" }); + equals(3, $("#container")[0].childNodes.length, "container still has only the divs we added"); + equals(d3ElementCount + 1, $("#d3")[0].childNodes.length, "1 new element added to div d3"); + }); + + test(renderMode + " detachable defaults to true when connection made between two endpoints", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2), + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection not detachable"); + }); + + test(renderMode + " connection detachable when target endpoint has connectionsDetachable set to true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2, {connectionsDetachable:true}), + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection detachable because connectionsDetachable was set on target endpoint"); + }); + + test(renderMode + " connection detachable when source endpoint has connectionsDetachable set to true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {connectionsDetachable:true}), e2 = _jsPlumb.addEndpoint(d2), + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection detachable because connectionsDetachable was set on source endpoint"); + }); + + test(renderMode + " Connector has 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.connector.type, "Bezier", "Bezier connector has type set"); + + var c2 = _jsPlumb.connect({source:d1, target:d2, connector:"Straight"}); + equals(c2.connector.type, "Straight", "Straight connector has type set"); + + var c3 = _jsPlumb.connect({source:d1, target:d2, connector:"Flowchart"}); + equals(c3.connector.type, "Flowchart", "Flowchart connector has type set"); + }); + + test(renderMode + " Endpoints have 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.endpoints[0].type, "Dot", "Dot endpoint has type set"); + + var c2 = _jsPlumb.connect({source:d1, target:d2, endpoints:["Rectangle", "Blank"]}); + equals(c2.endpoints[1].type, "Blank", "Blank endpoint has type set"); + equals(c2.endpoints[0].type, "Rectangle", "Rectangle endpoint has type set"); + }); + + test(renderMode + " Overlays have 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = _jsPlumb.connect({ + source:d1, + target:d2, + overlays:[ "Arrow", "Label", "PlainArrow", "Diamond" ] + }); + equals(c.overlays[0].type, "Arrow", "Arrow overlay has type set"); + equals(c.overlays[1].type, "Label", "Label overlay has type set"); + equals(c.overlays[2].type, "PlainArrow", "PlainArrow overlay has type set"); + equals(c.overlays[3].type, "Diamond", "Diamond overlay has type set"); + }); + + test(renderMode + " _jsPlumb.hide, original one-arg version", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + _jsPlumb.hide(d1); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(true, e1.isVisible(), "endpoint 1 is still visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + _jsPlumb.show(d1); + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + }); + + test(renderMode + " _jsPlumb.hide, two-arg version, endpoints should also be hidden", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + _jsPlumb.show(d1); // now show d1, but do not alter the endpoints. e1 should still be hidden + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + test(renderMode + " _jsPlumb.show, two-arg version, endpoints should become visible", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + _jsPlumb.show(d1, true); // now show d1, and alter the endpoints. e1 should be visible. + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(true, e1.isVisible(), "endpoint 1 is visible again."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + test(renderMode + " _jsPlumb.show, two-arg version, endpoints should become visible, but not all connections, because some other endpoints are not visible.", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e11 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + e3 = _jsPlumb.addEndpoint(d3, e), + c1 = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e11, target:e3}); + + // we now have d1 connected to both d3 and d2. we'll hide d1, and everything on d1 should be hidden. + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "connection 1 is no longer visible."); + equals(false, c2.isVisible(), "connection 2 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(false, e11.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + equals(true, e3.isVisible(), "endpoint 3 is still visible."); + + // now, we will also hide d3. making d1 visible again should NOT result in c2 becoming visible, because the other endpoint + // for c2 is e3, which is not visible. + _jsPlumb.hide(d3, true); + equals(false, e3.isVisible(), "endpoint 3 is no longer visible."); + + _jsPlumb.show(d1, true); // now show d1, and alter the endpoints. e1 should be visible, c1 should be visible, but c2 should not. + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(false, c2.isVisible(), "Connection 2 is not visible."); + equals(true, e1.isVisible(), "endpoint 1 is visible again."); + equals(true, e11.isVisible(), "endpoint 11 is visible again."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + equals(false, e3.isVisible(), "endpoint 3 is still not visible."); + }); + + /* + test(renderMode + " _jsPlumb.hide, two-arg version, endpoints should also be hidden", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + /** + * test for issue 132: label leaves its element in the DOM after it has been + * removed from a connection. + */ + test(renderMode + " label cleans itself up properly", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label", cssClass:"foo"}] + ]}); + ok($(".foo").length == 1, "label element exists in DOM"); + c.removeOverlay("label"); + ok($(".foo").length == 0, "label element does not exist in DOM"); + }); + + test(renderMode + " arrow cleans itself up properly", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Arrow", {id:"arrow"}] + ]}); + ok(c.getOverlay("arrow") != null, "arrow overlay exists"); + c.removeOverlay("arrow"); + ok(c.getOverlay("arrow") == null, "arrow overlay has been removed"); + }); + + test(renderMode + " label overlay getElement function", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label"}] + ]}); + ok(c.getOverlay("label").getElement() != null, "label overlay exposes element via getElement method"); + }); + + test(renderMode + " label overlay provides getLabel and setLabel methods", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label", label:"foo"}] + ]}); + var o = c.getOverlay("label"), e = o.getElement(); + ok(e.innerHTML == "foo", "label text is set to original value"); + o.setLabel("baz"); + equals(e.innerHTML, "baz", "label text is set to new value 'baz'"); + equals(o.getLabel(), "baz", "getLabel function works correctly with String"); + // now try functions + var aFunction = function() { return "aFunction"; }; + o.setLabel(aFunction); + equals(e.innerHTML, "aFunction", "label text is set to new value from Function"); + equals(o.getLabel(), aFunction, "getLabel function works correctly with Function"); + }); + + test(renderMode + " parameters object works for Endpoint", function() { + var d1 = _addDiv("d1"), + f = function() { alert("FOO!"); }, + e = _jsPlumb.addEndpoint(d1, { + isSource:true, + parameters:{ + "string":"param1", + "int":4, + "function":f + } + }); + ok(e.getParameter("string") === "param1", "getParameter(String) works correctly"); + ok(e.getParameter("int") === 4, "getParameter(int) works correctly"); + ok(e.getParameter("function") == f, "getParameter(Function) works correctly"); + }); + + test(renderMode + " parameters object works for Connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + f = function() { alert("FOO!"); }; + var c = _jsPlumb.connect({ + source:d1, + target:d2, + parameters:{ + "string":"param1", + "int":4, + "function":f + } + }); + ok(c.getParameter("string") === "param1", "getParameter(String) works correctly"); + ok(c.getParameter("int") === 4, "getParameter(int) works correctly"); + ok(c.getParameter("function") == f, "getParameter(Function) works correctly"); + }); + + test(renderMode + " parameters set on Endpoints and Connections are all merged, and merged correctly at that.", function() { + var d1 = _addDiv("d1"), + d2 = _addDiv("d2"), + e = _jsPlumb.addEndpoint(d1, { + isSource:true, + parameters:{ + "string":"sourceEndpoint", + "int":0, + "function":function() { return "sourceEndpoint"; } + } + }), + e2 = _jsPlumb.addEndpoint(d2, { + isTarget:true, + parameters:{ + "int":1, + "function":function() { return "targetEndpoint"; } + } + }), + c = _jsPlumb.connect({source:e, target:e2, parameters:{ + "function":function() { return "connection"; } + }}); + + ok(c.getParameter("string") === "sourceEndpoint", "getParameter(String) works correctly"); + ok(c.getParameter("int") === 1, "getParameter(int) works correctly"); + ok(c.getParameter("function")() == "connection", "getParameter(Function) works correctly"); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers standard connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2}); + equals(_jsPlumb.anchorManager.getConnectionsFor("d1").length, 1); + equals(_jsPlumb.anchorManager.getEndpointsFor("d1").length, 1); + equals(_jsPlumb.anchorManager.getConnectionsFor("d2").length, 1); + equals(_jsPlumb.anchorManager.getEndpointsFor("d2").length, 1); + var c2 = _jsPlumb.connect({source:d1, target:d2}); + equals(_jsPlumb.anchorManager.getConnectionsFor("d1").length, 2); + equals(_jsPlumb.anchorManager.getConnectionsFor("d2").length, 2); + equals(_jsPlumb.anchorManager.getEndpointsFor("d1").length, 2); + equals(_jsPlumb.anchorManager.getEndpointsFor("d2").length, 2); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers dynamic anchor connection, and removes it.", function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var c = _jsPlumb.connect({source:d3, target:d4, anchors:["AutoDefault", "AutoDefault"]}); + + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 1); + + var c2 = _jsPlumb.connect({source:d3, target:d4}); + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 2); + equals(_jsPlumb.anchorManager.getConnectionsFor("d4").length, 2); + + equals(_jsPlumb.anchorManager.getEndpointsFor("d3").length, 2); + _jsPlumb.detach(c); + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 1); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers continuous anchor connection, and removes it.", function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var c = _jsPlumb.connect({source:d3, target:d4, anchors:["Continuous", "Continuous"]}); + + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 1); + equals(_jsPlumb.anchorManager.getConnectionsFor("d4").length, 1); + + _jsPlumb.detach(c); + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 0); + equals(_jsPlumb.anchorManager.getConnectionsFor("d4").length, 0); + + _jsPlumb.reset(); + equals(_jsPlumb.anchorManager.getEndpointsFor("d4").length, 0); + }); + +// ------------- utility functions - math stuff, mostly -------------------------- + + var tolerance = 0.00000005, withinTolerance = function(v1, v2, msg) { + if (Math.abs(v1 - v2) < tolerance) ok(true, msg + "; expected " + v1 + " and got it"); + else { + ok(false, msg + "; expected " + v1 + " got " + v2); + } + }; + test(renderMode + " jsPlumbUtil.gradient, segment 1", function() { + var p1 = [2,2], p2 = [3,1], m = jsPlumbUtil.gradient(p1,p2); + equals(m, -1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.normal, segment 1", function() { + var p1 = [2,2], p2 = [3,1], m = jsPlumbUtil.normal(p1,p2); + equals(m, 1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.gradient, segment 2", function() { + var p1 = [2,2], p2 = [3,3], m = jsPlumbUtil.gradient(p1,p2); + equals(m, 1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.normal, segment 2", function() { + var p1 = [2,2], p2 = [3,3], m = jsPlumbUtil.normal(p1,p2); + equals(m, -1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.gradient, segment 3", function() { + var p1 = [2,2], p2 = [1,3], m = jsPlumbUtil.gradient(p1,p2); + equals(m, -1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.normal, segment 3", function() { + var p1 = [2,2], p2 = [1,3], m = jsPlumbUtil.normal(p1,p2); + equals(m, 1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.gradient, segment 4", function() { + var p1 = [2,2], p2 = [1,1], m = jsPlumbUtil.gradient(p1,p2); + equals(m, 1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.normal, segment 4", function() { + var p1 = [2,2], p2 = [1,1], m = jsPlumbUtil.normal(p1,p2); + equals(m, -1, "normal calculated correctly for simple case"); + }); + test(renderMode + "jsPlumbUtil.pointOnLine, segment 1", function() { + var p1 = {x:2,y:2}, p2={x:3, y:1}, + target = jsPlumbUtil.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumbUtil.pointOnLine, segment 2", function() { + var p1 = {x:2,y:2}, p2={x:3, y:3}, + target = jsPlumbUtil.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumbUtil.pointOnLine, segment 3", function() { + var p1 = {x:2,y:2}, p2={x:1, y:3}, + target = jsPlumbUtil.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumbUtil.pointOnLine, segment 4", function() { + var p1 = {x:2,y:2}, p2={x:1, y:1}, + target = jsPlumbUtil.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumbUtil.perpendicularLineTo, segment 1", function() { + var p1 = {x:2, y:2}, p2={x:3, y:1}, m = jsPlumbUtil.gradient(p1, p2), + l = jsPlumbUtil.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(4, l[0].x, "point 1 x is correct"); + withinTolerance(2, l[0].y, "point 1 y is correct"); + + withinTolerance(2, l[1].x, "point 2 x is correct"); + withinTolerance(0, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumbUtil.perpendicularLineTo, segment 2", function() { + var p1 = {x:2, y:2}, p2={x:3, y:3}, m = jsPlumbUtil.gradient(p1, p2), + l = jsPlumbUtil.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(4, l[0].x, "point 1 x is correct"); + withinTolerance(2, l[0].y, "point 1 y is correct"); + + withinTolerance(2, l[1].x, "point 2 x is correct"); + withinTolerance(4, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumbUtil.perpendicularLineTo, segment 3", function() { + var p1 = {x:2, y:2}, p2={x:1, y:3}, m = jsPlumbUtil.gradient(p1, p2), + l = jsPlumbUtil.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(2, l[0].x, "point 1 x is correct"); + withinTolerance(4, l[0].y, "point 1 y is correct"); + + withinTolerance(0, l[1].x, "point 2 x is correct"); + withinTolerance(2, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumbUtil.perpendicularLineTo, segment 4", function() { + var p1 = {x:2, y:2}, p2={x:1, y:1}, m = jsPlumbUtil.gradient(p1, p2), + l = jsPlumbUtil.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(2, l[0].x, "point 1 x is correct"); + withinTolerance(0, l[0].y, "point 1 y is correct"); + + withinTolerance(0, l[1].x, "point 2 x is correct"); + withinTolerance(2, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumbUtil.intersects, with intersection", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 3, y:4, w:3, h:3}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, with no intersection", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 13, y:4, w:3, h:3}; + + ok(!jsPlumbUtil.intersects(r1, r2), "r1 and r2 do not intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, with intersection, equal Y", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 1, y:2, w:3, h:6}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, with intersection, equal X", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 2, y:1, w:4, h:6}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, identical rectangles", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 2, y:2, w:4, h:6}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, corners touch (intersection)", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 6, y:8, w:3, h:3}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, one rectangle contained within the other", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 0, y:0, w:23, h:23}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "setImage on Endpoint", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + originalUrl = "../../img/endpointTest1.png", + e = { + endpoint:[ "Image", { src:originalUrl } ] + }, + ep = _jsPlumb.addEndpoint(d1, e); + }); + test(renderMode + "setImage on Endpoint, with supplied onload", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { + endpoint:[ "Image", { + src:"../../img/endpointTest1.png", + onload:function(imgEp) { + equals("../../img/endpointTest1.png", imgEp.img.src); + equals(ep.endpoint.canvas.getAttribute("src"), imgEp.img.src); + } + } ] + }, + ep = _jsPlumb.addEndpoint(d1, e); + ep.setImage("../../img/littledot.png", function(imgEp) { + equals("../../img/littledot.png", imgEp.img.src); + equals(ep.endpoint.canvas.getAttribute("src"), imgEp.img.src); + }); + }); + + test(renderMode + "attach endpoint to table when desired element was td", function() { + var table = document.createElement("table"), + tr = document.createElement("tr"), + td = document.createElement("td"); + table.appendChild(tr); tr.appendChild(td); + document.body.appendChild(table); + var ep = _jsPlumb.addEndpoint(td); + equals(ep.canvas.parentNode.tagName.toLowerCase(), "table"); + }); + +// issue 190 - regressions with getInstance. these tests ensure that generated ids are unique across +// instances. + + test(renderMode + " id clashes between instances", function() { + var d1 = document.createElement("div"), + d2 = document.createElement("div"), + _jsp2 = jsPlumb.getInstance(); + + document.body.appendChild(d1); + document.body.appendChild(d2); + + _jsPlumb.addEndpoint(d1); + _jsp2.addEndpoint(d2); + + var id1 = d1.getAttribute("id"), + id2 = d2.getAttribute("id"); + + var idx = id1.indexOf("_"), idx2 = id1.lastIndexOf("_"), v1 = id1.substring(idx, idx2); + var idx3 = id2.indexOf("_"), idx4 = id2.lastIndexOf("_"), v2 = id2.substring(idx3, idx4); + + ok (v1 != v2, "instance versions are different : " + v1 + " : " + v2); + }); + + test(renderMode + " id clashes between instances", function() { + var d1 = document.createElement("div"), + d2 = document.createElement("div"), + _jsp2 = jsPlumb.getInstance(); + + document.body.appendChild(d1); + document.body.appendChild(d2); + + _jsPlumb.addEndpoint(d1); + _jsPlumb.addEndpoint(d2); + + var id1 = d1.getAttribute("id"), + id2 = d2.getAttribute("id"); + + var idx = id1.indexOf("_"), idx2 = id1.lastIndexOf("_"), v1 = id1.substring(idx, idx2); + var idx3 = id2.indexOf("_"), idx4 = id2.lastIndexOf("_"), v2 = id2.substring(idx3, idx4); + + ok (v1 == v2, "instance versions are the same : " + v1 + " : " + v2); + }); + + test(renderMode + " importDefaults", function() { + _jsPlumb.Defaults.Anchors = ["LeftMiddle", "RightMiddle"]; + var d1 = _addDiv("d1"), + d2 = _addDiv(d2), + c = _jsPlumb.connect({source:d1, target:d2}), + e = c.endpoints[0]; + + equals(e.anchor.x, 0, "left middle anchor"); + equals(e.anchor.y, 0.5, "left middle anchor"); + + _jsPlumb.importDefaults({ + Anchors:["TopLeft", "TopRight"] + }); + + var conn = _jsPlumb.connect({source:d1, target:d2}), + e1 = conn.endpoints[0], e2 = conn.endpoints[1]; + + equals(e1.anchor.x, 0, "top leftanchor"); + equals(e2.anchor.y, 0, "top left anchor"); + equals(e2.anchor.x, 1, "top right anchor"); + equals(e2.anchor.y, 0, "top right anchor"); + + }); + + test(renderMode + " restoreDefaults", function() { + _jsPlumb.importDefaults({ + Anchors:["TopLeft", "TopRight"] + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), conn = _jsPlumb.connect({source:d1, target:d2}), + e1 = conn.endpoints[0], e2 = conn.endpoints[1]; + + equals(e1.anchor.x, 0, "top leftanchor"); + equals(e2.anchor.y, 0, "top left anchor"); + equals(e2.anchor.x, 1, "top right anchor"); + equals(e2.anchor.y, 0, "top right anchor"); + + _jsPlumb.restoreDefaults(); + + var conn2 = _jsPlumb.connect({source:d1, target:d2}), + e3 = conn2.endpoints[0], e4 = conn2.endpoints[1]; + + equals(e3.anchor.x, 0.5, "bottom center anchor"); + equals(e3.anchor.y, 1, "bottom center anchor"); + equals(e4.anchor.x, 0.5, "bottom center anchor"); + equals(e4.anchor.y, 1, "bottom center anchor"); + }); + +// setId function + + test(renderMode + " setId, taking two strings, only default scope", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId("d1", "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a selector and a string, only default scope", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1"), "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a DOM element and a string, only default scope", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1")[0], "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking two strings, mix of scopes", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2, scope:"FOO"}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId("d1", "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a selector and a string, mix of scopes", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2, scope:"FOO"}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1"), "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a DOM element and a string, mix of scopes", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2, scope:"FOO"}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1")[0], "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setIdChanged, ", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + $("#d1").attr("id", "d3"); + + _jsPlumb.setIdChanged("d1", "d3"); + + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " endpoint hide/show should hide/show overlays", function() { + _addDiv("d1"); + var e1 = _jsPlumb.addEndpoint("d1", { + overlays:[ + [ "Label", { id:"label", label:"foo" } ] + ] + }), + o = e1.getOverlay("label"); + + ok(o.isVisible(), "overlay is initially visible"); + _jsPlumb.hide("d1", true); + ok(!o.isVisible(), "overlay is no longer visible"); + }); + + test(renderMode + " connection hide/show should hide/show overlays", function() { + _addDiv("d1");_addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", + overlays:[ + [ "Label", { id:"label", label:"foo" } ] + ] + }), + o = c.getOverlay("label"); + + ok(o.isVisible(), "overlay is initially visible"); + _jsPlumb.hide("d1", true); + ok(!o.isVisible(), "overlay is no longer visible"); + }); + + test(renderMode + " select, basic test", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2"}), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 1, "one connection selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; dont filter on scope.", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 2, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scope", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + s = _jsPlumb.select({source:"d1", scope:"FOO"}); + + equals(s.length, 1, "one connection selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scopes", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + c3 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAZ"}) + s = _jsPlumb.select({source:"d1", scope:["FOO","BAR"]}); + + equals(s.length, 2, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; scope but no scope filter; single source id", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + c3 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAZ"}), + c4 = _jsPlumb.connect({source:"d2", target:"d1", scope:"BOZ"}), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 3, "three connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scopes; single source id", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + c3 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAZ"}), + c4 = _jsPlumb.connect({source:"d2", target:"d1", scope:"BOZ"}), + s = _jsPlumb.select({source:"d1", scope:["FOO","BAR", "BOZ"]}); + + equals(s.length, 2, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scope; dont supply sourceid", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + s = _jsPlumb.select({ scope:"FOO" }); + + equals(s.length, 1, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scope; dont supply sourceid", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d2", target:"d1", scope:"BAR"}), + s = _jsPlumb.select({ scope:"FOO" }); + + equals(s.length, 1, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, two connections, with overlays", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({ + source:"d1", + target:"d2", + overlays:[ + ["Label", {id:"l"}] + ] + }), + c2 = _jsPlumb.connect({ + source:"d1", + target:"d2", + overlays:[ + ["Label", {id:"l"}] + ] + }), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 2, "two connections selected"); + ok(s.get(0).getOverlay("l") != null, "connection has overlay"); + ok(s.get(1).getOverlay("l") != null, "connection has overlay"); + }); + + test(renderMode + " select, chaining with setHover and hideOverlay", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({ + source:"d1", + target:"d2", + overlays:[ + ["Label", {id:"l"}] + ] + }); + s = _jsPlumb.select({source:"d1"}); + + s.setHover(false).hideOverlay("l"); + + ok(!(s.get(0).isHover()), "connection is not hover"); + ok(!(s.get(0).getOverlay("l").isVisible()), "overlay is not visible"); + }); + + test(renderMode + " select, .each function", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10) + }); + } + + var s = _jsPlumb.select(); + equals(s.length, 5, "there are five connections"); + + var t = ""; + s.each(function(connection) { + t += "f"; + }); + equals("fffff", t, ".each is working"); + }); + + test(renderMode + " select, multiple connections + chaining", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + overlays:[ + ["Arrow", {location:0.3}], + ["Arrow", {location:0.7}] + ] + }); + } + + var s = _jsPlumb.select().removeAllOverlays().setParameter("foo", "bar").setHover(false).setLabel("baz"); + equals(s.length, 5, "there are five connections"); + + for (var j = 0; j < 5; j++) { + equals(s.get(j).getOverlays().length, 1, "one overlay: the label"); + equals(s.get(j).getParameter("foo"), "bar", "parameter foo has value 'bar'"); + ok(!(s.get(j).isHover()), "hover is set to false"); + equals(s.get(j).getLabel(), "baz", "label is set to 'baz'"); + } + }); + + test(renderMode + " select, simple getter", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + label:"FOO" + }); + } + + var lbls = _jsPlumb.select().getLabel(); + equals(lbls.length, 5, "there are five labels"); + + for (var j = 0; j < 5; j++) { + equals(lbls[j][0], "FOO", "label has value 'FOO'"); + } + }); + + test(renderMode + " select, getter + chaining", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + label:"FOO" + }); + } + + var params = _jsPlumb.select().removeAllOverlays().setParameter("foo", "bar").getParameter("foo"); + equals(params.length, 5, "there are five params"); + + for (var j = 0; j < 5; j++) { + equals(params[j][0], "bar", "parameter has value 'bar'"); + } + }); + + + test(renderMode + " select, detach method", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + label:"FOO" + }); + } + + var params = _jsPlumb.select().detach(); + + equals(_jsPlumb.select().length, 0, "there are no connections"); + }); + + test(renderMode + " select, repaint method", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + label:"FOO" + }); + } + + var len = _jsPlumb.select().repaint().length; + + equals(len, 5, "there are five connections"); + }); + + + // selectEndpoints + test(renderMode + " selectEndpoints, basic tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1), + e2 = _jsPlumb.addEndpoint(d1); + + equals(_jsPlumb.selectEndpoints().length, 2, "there are two endpoints"); + equals(_jsPlumb.selectEndpoints({element:"d1"}).length, 2, "there are two endpoints on d1"); + equals(_jsPlumb.selectEndpoints({element:"d2"}).length, 0, "there are 0 endpoints on d2"); + + equals(_jsPlumb.selectEndpoints({source:"d1"}).length, 0, "there are zero source endpoints on d1"); + equals(_jsPlumb.selectEndpoints({target:"d1"}).length, 0, "there are zero target endpoints on d1"); + equals(_jsPlumb.selectEndpoints({source:"d2"}).length, 0, "there are zero source endpoints on d2"); + equals(_jsPlumb.selectEndpoints({target:"d2"}).length, 0, "there are zero target endpoints on d2"); + + equals(_jsPlumb.selectEndpoints({source:"d1", scope:"FOO"}).length, 0, "there are zero source endpoints on d1 with scope FOO"); + + _jsPlumb.addEndpoint("d2", { scope:"FOO", isSource:true }); + equals(_jsPlumb.selectEndpoints({source:"d2", scope:"FOO"}).length, 1, "there is one source endpoint on d2 with scope FOO"); + + equals(_jsPlumb.selectEndpoints({element:["d2", "d1"]}).length, 3, "there are three endpoints between d2 and d1"); + }); + + test(renderMode + " selectEndpoints, basic tests, various input argument formats", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1), + e2 = _jsPlumb.addEndpoint(d1); + + equals(_jsPlumb.selectEndpoints({element:"d1"}).length, 2, "using id, there are two endpoints on d1"); + equals(_jsPlumb.selectEndpoints({element:d1}).length, 2, "using dom element, there are two endpoints on d1"); + equals(_jsPlumb.selectEndpoints({element:$("#d1")}).length, 2, "using selector, there are two endpoints on d1"); + equals(_jsPlumb.selectEndpoints({element:$(d1)}).length, 2, "using selector with dom element, there are two endpoints on d1"); + + }); + + test(renderMode + " selectEndpoints, basic tests, scope", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {scope:"FOO"}), + e2 = _jsPlumb.addEndpoint(d1); + + equals(_jsPlumb.selectEndpoints({element:"d1"}).length, 2, "using id, there are two endpoints on d1"); + equals(_jsPlumb.selectEndpoints({element:"d1", scope:"FOO"}).length, 1, "using id, there is one endpoint on d1 with scope 'FOO'"); + _jsPlumb.addEndpoint(d1, {scope:"BAR"}), + equals(_jsPlumb.selectEndpoints({element:"d1", scope:"FOO"}).length, 1, "using id, there is one endpoint on d1 with scope 'BAR'"); + equals(_jsPlumb.selectEndpoints({element:"d1", scope:["BAR", "FOO"]}).length, 2, "using id, there are two endpoints on d1 with scope 'BAR' or 'FOO'"); + }); + + test(renderMode + " selectEndpoints, isSource tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true}), + e2 = _jsPlumb.addEndpoint(d1), + e3 = _jsPlumb.addEndpoint(d2, {isSource:true}); + + equals(_jsPlumb.selectEndpoints({source:"d1"}).length, 1, "there is one source endpoint on d1"); + equals(_jsPlumb.selectEndpoints({target:"d1"}).length, 0, "there are zero target endpoints on d1"); + + equals(_jsPlumb.selectEndpoints({source:"d2"}).length, 1, "there is one source endpoint on d2"); + + equals(_jsPlumb.selectEndpoints({source:["d2", "d1"]}).length, 2, "there are two source endpoints between d1 and d2"); + }); + + test(renderMode + " selectEndpoints, isTarget tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isTarget:true}), + e2 = _jsPlumb.addEndpoint(d1), + e3 = _jsPlumb.addEndpoint(d2, {isTarget:true}); + + equals(_jsPlumb.selectEndpoints({target:"d1"}).length, 1, "there is one target endpoint on d1"); + equals(_jsPlumb.selectEndpoints({source:"d1"}).length, 0, "there are zero source endpoints on d1"); + + equals(_jsPlumb.selectEndpoints({target:"d2"}).length, 1, "there is one target endpoint on d2"); + + equals(_jsPlumb.selectEndpoints({target:["d2", "d1"]}).length, 2, "there are two target endpoints between d1 and d2"); + }); + + test(renderMode + " selectEndpoints, isSource + isTarget tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true}), + e2 = _jsPlumb.addEndpoint(d1), + e3 = _jsPlumb.addEndpoint(d1, {isSource:true}), + e4 = _jsPlumb.addEndpoint(d1, {isTarget:true}); + + equals(_jsPlumb.selectEndpoints({source:"d1"}).length, 2, "there are two source endpoints on d1"); + equals(_jsPlumb.selectEndpoints({target:"d1"}).length, 2, "there are two target endpoints on d1"); + + equals(_jsPlumb.selectEndpoints({target:"d1", source:"d1"}).length, 1, "there is one source and target endpoint on d1"); + + equals(_jsPlumb.selectEndpoints({element:"d1"}).length, 4, "there are four endpoints on d1"); + + }); + + test(renderMode + " selectEndpoints, delete endpoints", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true}); + + equals(_jsPlumb.selectEndpoints({element:"d1"}).length, 1, "there is one endpoint on d1"); + _jsPlumb.selectEndpoints({source:"d1"}).delete(); + equals(_jsPlumb.selectEndpoints({element:"d1"}).length, 0, "there are zero endpoints on d1"); + }); + + test(renderMode + " selectEndpoints, detach connections", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true}), + e2 = _jsPlumb.addEndpoint(d2, {isSource:true, isTarget:true}); + + _jsPlumb.connect({source:e1, target:e2}); + + equals(e1.connections.length, 1, "there is one connection on d1's endpoint"); + equals(e2.connections.length, 1, "there is one connection on d2's endpoint"); + + _jsPlumb.selectEndpoints({source:"d1"}).detachAll(); + + equals(e1.connections.length, 0, "there are zero connections on d1's endpoint"); + equals(e2.connections.length, 0, "there are zero connections on d2's endpoint"); + }); + + test(renderMode + " selectEndpoints, hover tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true}); + + equals(e1.isHover(), false, "hover not set"); + _jsPlumb.selectEndpoints({source:"d1"}).setHover(true); + equals(e1.isHover(), true, "hover set"); + _jsPlumb.selectEndpoints({source:"d1"}).setHover(false); + equals(e1.isHover(), false, "hover no longer set"); + }); + + test(renderMode + " selectEndpoints, setEnabled tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true}); + + equals(e1.isEnabled(), true, "endpoint is enabled"); + _jsPlumb.selectEndpoints({source:"d1"}).setEnabled(false); + equals(e1.isEnabled(), false, "endpoint not enabled"); + }); + + test(renderMode + " selectEndpoints, setEnabled tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true}); + + equals(e1.isEnabled(), true, "endpoint is enabled"); + var e = _jsPlumb.selectEndpoints({source:"d1"}).isEnabled(); + equals(e[0][0], true, "endpoint enabled"); + _jsPlumb.selectEndpoints({source:"d1"}).setEnabled(false); + e = _jsPlumb.selectEndpoints({source:"d1"}).isEnabled(); + equals(e[0][0], false, "endpoint not enabled"); + }); + +// setPaintStyle/getPaintStyle tests + + test(renderMode + " setPaintStyle", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), c = _jsPlumb.connect({source:d1, target:d2}); + c.setPaintStyle({strokeStyle:"FOO", lineWidth:999}); + equals(c.paintStyleInUse.strokeStyle, "FOO", "strokeStyle was set"); + equals(c.paintStyleInUse.lineWidth, 999, "lineWidth was set"); + + c.setHoverPaintStyle({strokeStyle:"BAZ", lineWidth:444}); + c.setHover(true); + equals(c.paintStyleInUse.strokeStyle, "BAZ", "strokeStyle was set"); + equals(c.paintStyleInUse.lineWidth, 444, "lineWidth was set"); + + equals(c.getPaintStyle().strokeStyle, "FOO", "getPaintStyle returns correct value"); + equals(c.getHoverPaintStyle().strokeStyle, "BAZ", "getHoverPaintStyle returns correct value"); + }); + +// ******************* getEndpoints ************************************************ + + test(renderMode + " getEndpoints", function() { + _addDiv("d1");_addDiv("d2"); + + _jsPlumb.addEndpoint("d1"); + _jsPlumb.addEndpoint("d2"); + _jsPlumb.addEndpoint("d1"); + + var e = _jsPlumb.getEndpoints("d1"), + e2 = _jsPlumb.getEndpoints("d2"); + equals(e.length, 2, "two endpoints on d1"); + equals(e2.length, 1, "one endpoint on d2"); + }); + +// connection type tests - types, type extension, set types, get types etc. + test(renderMode + " set connection type on existing connection", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" } + }; + + _jsPlumb.registerConnectionType("basic", basicType); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("basic"); + equals(c.getPaintStyle().lineWidth, 4, "paintStyle lineWidth is 4"); + equals(c.getPaintStyle().strokeStyle, "yellow", "paintStyle strokeStyle is yellow"); + equals(c.getHoverPaintStyle().strokeStyle, "blue", "paintStyle strokeStyle is yellow"); + equals(c.getHoverPaintStyle().lineWidth, 4, "hoverPaintStyle linewidth is 6"); + }); + + test(renderMode + " set connection type on existing connection then change type", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" } + }; + var otherType = { + connector:"Bezier", + paintStyle:{ strokeStyle:"red", lineWidth:14 }, + hoverPaintStyle:{ strokeStyle:"green" } + }; + + _jsPlumb.registerConnectionType("basic", basicType); + _jsPlumb.registerConnectionType("other", otherType); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("basic"); + equals(c.getPaintStyle().lineWidth, 4, "paintStyle lineWidth is 4"); + equals(c.getPaintStyle().strokeStyle, "yellow", "paintStyle strokeStyle is yellow"); + equals(c.getHoverPaintStyle().strokeStyle, "blue", "hoverPaintStyle strokeStyle is blue"); + equals(c.getHoverPaintStyle().lineWidth, 4, "hoverPaintStyle linewidth is 6"); + + c.setType("other"); + equals(c.getPaintStyle().lineWidth, 14, "paintStyle lineWidth is 14"); + equals(c.getPaintStyle().strokeStyle, "red", "paintStyle strokeStyle is red"); + equals(c.getHoverPaintStyle().strokeStyle, "green", "hoverPaintStyle strokeStyle is green"); + equals(c.getHoverPaintStyle().lineWidth, 14, "hoverPaintStyle linewidth is 14"); + }); + + test(renderMode + " set connection type on existing connection, overlays should be set", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }; + + _jsPlumb.registerConnectionType("basic", basicType); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("basic"); + equals(c.getOverlays().length, 1, "one overlay"); + }); + + test(renderMode + " set connection type on existing connection, overlays should be removed with second type", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }; + var otherType = { + connector:"Bezier" + }; + _jsPlumb.registerConnectionType("basic", basicType); + _jsPlumb.registerConnectionType("other", otherType); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("basic"); + equals(c.getOverlays().length, 1, "one overlay"); + c.setType("other"); + equals(c.getOverlays().length, 0, "no overlays"); + equals(c.getPaintStyle().lineWidth, _jsPlumb.Defaults.PaintStyle.lineWidth, "paintStyle lineWidth is default"); + }); + + test(renderMode + " set connection type on existing connection, hasType + toggleType", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }; + + _jsPlumb.registerConnectionTypes({ + "basic": basicType + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("basic"); + equals(c.hasType("basic"), true, "connection has 'basic' type"); + c.toggleType("basic"); + equals(c.hasType("basic"), false, "connection does not have 'basic' type"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + c.toggleType("basic"); + equals(c.hasType("basic"), true, "connection has 'basic' type"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + equals(c.getOverlays().length, 1, "one overlay"); + + }); + + test(renderMode + " set connection type on existing connection, merge tests", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }; + // this tests all three merge types: connector should overwrite, linewidth should be inserted into + // basic type's params, and arrow overlay should be added to list to end up with two overlays + var otherType = { + connector:"Bezier", + paintStyle:{ lineWidth:14 }, + overlays:[ + ["Arrow", {location:0.25}] + ] + }; + _jsPlumb.registerConnectionTypes({ + "basic": basicType, + "other": otherType + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("basic"); + equals(c.hasType("basic"), true, "connection has 'basic' type"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + equals(c.getPaintStyle().lineWidth, 4, "connection has linewidth 4"); + equals(c.getOverlays().length, 1, "one overlay"); + + c.addType("other"); + equals(c.hasType("basic"), true, "connection has 'basic' type"); + equals(c.hasType("other"), true, "connection has 'other' type"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + equals(c.getPaintStyle().lineWidth, 14, "connection has linewidth 14"); + equals(c.getOverlays().length, 2, "two overlays"); + + c.removeType("basic"); + equals(c.hasType("basic"), false, "connection does not have 'basic' type"); + equals(c.hasType("other"), true, "connection has 'other' type"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + equals(c.getPaintStyle().lineWidth, 14, "connection has linewidth 14"); + equals(c.getOverlays().length, 1, "one overlay"); + + c.toggleType("other"); + equals(c.hasType("other"), false, "connection does not have 'other' type"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + equals(c.getPaintStyle().lineWidth, _jsPlumb.Defaults.PaintStyle.lineWidth, "connection has default linewidth"); + equals(c.getOverlays().length, 0, "nooverlays"); + }); + + test(renderMode + " connection type tests, space separated arguments", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }; + // this tests all three merge types: connector should overwrite, linewidth should be inserted into + // basic type's params, and arrow overlay should be added to list to end up with two overlays + var otherType = { + connector:"Bezier", + paintStyle:{ lineWidth:14 }, + overlays:[ + ["Arrow", {location:0.25}] + ] + }; + _jsPlumb.registerConnectionTypes({ + "basic": basicType, + "other": otherType + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("basic other"); + equals(c.hasType("basic"), true, "connection has 'basic' type"); + equals(c.hasType("other"), true, "connection has 'other' type"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + equals(c.getPaintStyle().lineWidth, 14, "connection has linewidth 14"); + equals(c.getOverlays().length, 2, "two overlays"); + + c.toggleType("other basic"); + equals(c.hasType("basic"), false, "after toggle, connection does not have 'basic' type"); + equals(c.hasType("other"), false, "after toggle, connection does not have 'other' type"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "after toggle, connection has default stroke style"); + equals(c.getPaintStyle().lineWidth, _jsPlumb.Defaults.PaintStyle.lineWidth, "after toggle, connection has default linewidth"); + equals(c.getOverlays().length, 0, "after toggle, no overlays"); + + c.toggleType("basic other"); + equals(c.hasType("basic"), true, "after toggle again, connection has 'basic' type"); + equals(c.hasType("other"), true, "after toggle again, connection has 'other' type"); + equals(c.getPaintStyle().strokeStyle, "yellow", "after toggle again, connection has yellow stroke style"); + equals(c.getPaintStyle().lineWidth, 14, "after toggle again, connection has linewidth 14"); + equals(c.getOverlays().length, 2, "after toggle again, two overlays"); + + c.removeType("other basic"); + equals(c.hasType("basic"), false, "after remove, connection does not have 'basic' type"); + equals(c.hasType("other"), false, "after remove, connection does not have 'other' type"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "after remove, connection has default stroke style"); + equals(c.getPaintStyle().lineWidth, _jsPlumb.Defaults.PaintStyle.lineWidth, "after remove, connection has default linewidth"); + equals(c.getOverlays().length, 0, "after remove, no overlays"); + + c.addType("other basic"); + equals(c.hasType("basic"), true, "after add, connection has 'basic' type"); + equals(c.hasType("other"), true, "after add, connection has 'other' type"); + equals(c.getPaintStyle().strokeStyle, "yellow", "after add, connection has yellow stroke style"); + // NOTE here we added the types in the other order to before, so lineWidth 4 - from basic - should win. + equals(c.getPaintStyle().lineWidth, 4, "after add, connection has linewidth 4"); + equals(c.getOverlays().length, 2, "after add, two overlays"); + }); + + test(renderMode + " connection type tests, fluid interface", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }; + // this tests all three merge types: connector should overwrite, linewidth should be inserted into + // basic type's params, and arrow overlay should be added to list to end up with two overlays + var otherType = { + connector:"Bezier", + paintStyle:{ lineWidth:14 }, + overlays:[ + ["Arrow", {location:0.25}] + ] + }; + _jsPlumb.registerConnectionTypes({ + "basic": basicType, + "other": otherType + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + c = _jsPlumb.connect({source:d1, target:d2}), + c2 = _jsPlumb.connect({source:d2, target:d3}); + + _jsPlumb.select().addType("basic"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + equals(c2.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + + _jsPlumb.select().toggleType("basic"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + equals(c2.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + + _jsPlumb.select().addType("basic"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + equals(c2.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + + _jsPlumb.select().removeType("basic").addType("other"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + equals(c2.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + + + }); + + test(renderMode + " connection type tests, two types, check separation", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 } + }; + // this tests all three merge types: connector should overwrite, linewidth should be inserted into + // basic type's params, and arrow overlay should be added to list to end up with two overlays + var otherType = { + paintStyle:{ strokeStyle:"red", lineWidth:14 } + }; + _jsPlumb.registerConnectionTypes({ + "basic": basicType, + "other": otherType + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + c = _jsPlumb.connect({source:d1, target:d2}), + c2 = _jsPlumb.connect({source:d2, target:d3}); + c.setType("basic"); + equals(c.getPaintStyle().strokeStyle, "yellow", "first connection has yellow stroke style"); + c2.setType("other"); + + equals(c.getPaintStyle().strokeStyle, "yellow", "first connection has yellow stroke style"); + + + }); + + test(renderMode + " setType when null", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType(null); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + + }); + + test(renderMode + " setType to unknown type", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("foo"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + + }); + + test(renderMode + " setType to mix of known and unknown types", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + c = _jsPlumb.connect({source:d1, target:d2}); + + _jsPlumb.registerConnectionType("basic", { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }); + + c.setType("basic foo"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has basic type's stroke style"); + + c.toggleType("foo"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has basic type's stroke style"); + + c.removeType("basic baz"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + + c.addType("basic foo bar baz"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has basic type's stroke style"); + + }); + + test(renderMode + " create connection using type parameter", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + + _jsPlumb.Defaults.PaintStyle = {strokeStyle:"blue", lineWidth:34}; + + _jsPlumb.registerConnectionTypes({ + "basic": { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }, + "other":{ + paintStyle:{ lineWidth:14 } + } + }); + + equals(_jsPlumb.Defaults.PaintStyle.strokeStyle, "blue", "default value has not been messed up"); + + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + + c = _jsPlumb.connect({source:d1, target:d2, type:"basic other"}); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has basic type's stroke style"); + equals(c.getPaintStyle().lineWidth, 14, "connection has other type's lineWidth"); + + }); + + test(renderMode + " setType, scope", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + c = _jsPlumb.connect({source:d1, target:d2}); + + _jsPlumb.registerConnectionType("basic", { + connector:"Flowchart", + scope:"BANANA", + detachable:false, + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }); + + _jsPlumb.Defaults.ConnectionsDetachable = true;//just make sure we've setup the test correctly. + + c.setType("basic"); + equals(c.scope, "BANANA", "scope is correct"); + equals(c.isDetachable(), false, "not detachable"); + + }); + + test(renderMode + " setType, parameters", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + + _jsPlumb.registerConnectionType("basic", { + parameters:{ + foo:1, + bar:2, + baz:6785962437582 + } + }); + + _jsPlumb.registerConnectionType("frank", { + parameters:{ + bar:5 + } + }); + + // first try creating one with the parameters + c = _jsPlumb.connect({source:d1, target:d2, type:"basic"}); + + equals(c.getParameter("foo"), 1, "foo param correct"); + equals(c.getParameter("bar"), 2, "bar param correct"); + + c.addType("frank"); + equals(c.getParameter("foo"), 1, "foo param correct"); + equals(c.getParameter("bar"), 5, "bar param correct"); + }); + + test(renderMode + " setType, scope, two types", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + c = _jsPlumb.connect({source:d1, target:d2}); + + _jsPlumb.registerConnectionType("basic", { + connector:"Flowchart", + scope:"BANANA", + detachable:false, + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }); + + _jsPlumb.registerConnectionType("frank", { + scope:"OVERRIDE", + detachable:true + }); + + _jsPlumb.Defaults.ConnectionsDetachable = true;//just make sure we've setup the test correctly. + + c.setType("basic frank"); + equals(c.scope, "OVERRIDE", "scope is correct"); + equals(c.isDetachable(), true, "detachable"); + + }); + + test(renderMode + " create connection from Endpoints - type should be passed through.", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e1 = _jsPlumb.addEndpoint(d1, { + connectionType:"basic" + }), + e2 = _jsPlumb.addEndpoint(d2, { + connectionType:"basic" + }); + + _jsPlumb.registerConnectionTypes({ + "basic": { + connector:"Flowchart", + paintStyle:{ strokeStyle:"blue", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"red" }, + overlays:[ + "Arrow" + ] + }, + "other":{ + paintStyle:{ lineWidth:14 } + } + }); + + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.getPaintStyle().strokeStyle, "blue", "connection has default stroke style"); + equal(c.connector.type, "Flowchart", "connector is flowchart"); + }); + + test(renderMode + " simple Endpoint type tests.", function() { + _jsPlumb.registerEndpointType("basic", { + paintStyle:{fillStyle:"blue"} + }); + + var d = _addDiv('d1'), e = _jsPlumb.addEndpoint(d); + e.setType("basic"); + equals(e.getPaintStyle().fillStyle, "blue", "fill style is correct"); + + var d2 = _addDiv('d2'), e2 = _jsPlumb.addEndpoint(d2, {type:"basic"}); + equals(e2.getPaintStyle().fillStyle, "blue", "fill style is correct"); + }); + + test(renderMode + " create connection from Endpoints - with connector settings in Endpoint type.", function() { + + _jsPlumb.registerEndpointTypes({ + "basic": { + connector:"Flowchart", + connectorOverlays:[ + "Arrow" + ], + connectorStyle:{strokeStyle:"green" }, + connectorHoverStyle:{lineWidth:534 }, + paintStyle:{ fillStyle:"blue" }, + hoverPaintStyle:{ strokeStyle:"red" }, + overlays:[ + "Arrow" + ] + }, + "other":{ + paintStyle:{ fillStyle:"red" } + } + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e1 = _jsPlumb.addEndpoint(d1, { + type:"basic" + }), + e2 = _jsPlumb.addEndpoint(d2); + + c = _jsPlumb.connect({source:e1, target:e2}); + equals(e1.getPaintStyle().fillStyle, "blue", "endpoint has fill style specified in Endpoint type"); + equals(c.getPaintStyle().strokeStyle, "green", "connection has stroke style specified in Endpoint type"); + equals(c.getHoverPaintStyle().lineWidth, 534, "connection has hover style specified in Endpoint type"); + equals(c.connector.type, "Flowchart", "connector is Flowchart"); + equals(c.overlays.length, 1, "connector has one overlay"); + equals(e1.overlays.length, 1, "endpoint has one overlay"); + }); + + test(renderMode + " create connection from Endpoints - type should be passed through.", function() { + + _jsPlumb.registerConnectionTypes({ + "basic": { + connector:"Flowchart", + paintStyle:{ strokeStyle:"bazona", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"red" }, + overlays:[ + "Arrow" + ] + } + }); + + _jsPlumb.registerEndpointType("basic", { + connectionType:"basic", + paintStyle:{fillStyle:"GAZOODA"} + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e1 = _jsPlumb.addEndpoint(d1, { + type:"basic" + }), + e2 = _jsPlumb.addEndpoint(d2); + + c = _jsPlumb.connect({source:e1, target:e2}); + equals(e1.getPaintStyle().fillStyle, "GAZOODA", "endpoint has correct paint style, from type."); + equals(c.getPaintStyle().strokeStyle, "bazona", "connection has paint style from connection type, as specified in endpoint type. sweet!"); + equal(c.connector.type, "Flowchart", "connector is flowchart - this also came from connection type as specified by endpoint type."); + }); + + test(renderMode + " endpoint type", function() { + _jsPlumb.registerEndpointTypes({"example": {hoverPaintStyle: null}}); + //OR + //jsPlumb.registerEndpointType("example", {hoverPaintStyle: null}); + + var d = _addDiv("d"); + _jsPlumb.addEndpoint(d, {type: "example"}); + _jsPlumb.repaint(d); + }); + + /* + * test the merge function in jsplumb util: it should create an entirely new object + */ + test(renderMode + "jsPlumbUtil.merge", function() { + var a = { + foo:"a_foo", + bar:"a_bar", + nested:{ + foo:"a_foo", + bar:"a_bar" + } + }, + b = { + foo:"b_foo", + nested :{ + foo:"b_foo" + } + }, + c = jsPlumbUtil.merge(a, b); + equals(c.foo, "b_foo", "c has b's foo"); + equals(c.nested.foo, "b_foo", "c has b's nested foo"); + // now change c's foo. b should be unchanged. + c.foo = "c_foo"; + equals(b.foo, "b_foo", "b has b's foo"); + c.nested.foo = "c_foo"; + equals(b.nested.foo, "b_foo", "b has b's nested foo"); + equals(a.nested.foo, "a_foo", "a has a's nested foo"); + }); + + test(renderMode + "jsPlumbUtil.clone", function() { + var a = { + nested:{ + foo:"a_foo" + } + }, + b = jsPlumbUtil.clone(a); + equals(b.nested.foo, "a_foo", "b has a's nested foo"); + equals(a.nested.foo, "a_foo", "a has a's nested foo"); + b.nested.foo="b_foo"; + equals(b.nested.foo, "b_foo", "b has b's nested foo"); + equals(a.nested.foo, "a_foo", "a has a's nested foo"); + }); + +}; + diff --git a/build/1.3.16/js/lib/qunit.js b/archive/1.3.16/js/lib/qunit.js similarity index 100% rename from build/1.3.16/js/lib/qunit.js rename to archive/1.3.16/js/lib/qunit.js diff --git a/build/1.3.16/js/mootools.jsPlumb-1.3.16-all-min.js b/archive/1.3.16/js/mootools.jsPlumb-1.3.16-all-min.js similarity index 100% rename from build/1.3.16/js/mootools.jsPlumb-1.3.16-all-min.js rename to archive/1.3.16/js/mootools.jsPlumb-1.3.16-all-min.js diff --git a/build/1.3.16/js/mootools.jsPlumb-1.3.16-all.js b/archive/1.3.16/js/mootools.jsPlumb-1.3.16-all.js similarity index 100% rename from build/1.3.16/js/mootools.jsPlumb-1.3.16-all.js rename to archive/1.3.16/js/mootools.jsPlumb-1.3.16-all.js diff --git a/build/1.3.16/js/yui.jsPlumb-1.3.16-all-min.js b/archive/1.3.16/js/yui.jsPlumb-1.3.16-all-min.js similarity index 100% rename from build/1.3.16/js/yui.jsPlumb-1.3.16-all-min.js rename to archive/1.3.16/js/yui.jsPlumb-1.3.16-all-min.js diff --git a/build/1.3.16/js/yui.jsPlumb-1.3.16-all.js b/archive/1.3.16/js/yui.jsPlumb-1.3.16-all.js similarity index 100% rename from build/1.3.16/js/yui.jsPlumb-1.3.16-all.js rename to archive/1.3.16/js/yui.jsPlumb-1.3.16-all.js diff --git a/archive/1.3.16/jsPlumb-1.3.16-RC1.js b/archive/1.3.16/jsPlumb-1.3.16-RC1.js new file mode 100644 index 000000000..7889d9eb4 --- /dev/null +++ b/archive/1.3.16/jsPlumb-1.3.16-RC1.js @@ -0,0 +1,6427 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.16 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the jsPlumb core code. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + /** + * Class:jsPlumb + * The jsPlumb engine, registered as a static object in the window. This object contains all of the methods you will use to + * create and maintain Connections and Endpoints. + */ + + var _findWithFunction = jsPlumbUtil.findWithFunction, + _indexOf = jsPlumbUtil.indexOf, + _removeWithFunction = jsPlumbUtil.removeWithFunction, + _remove = jsPlumbUtil.remove, + // TODO support insert index + _addWithFunction = jsPlumbUtil.addWithFunction, + _addToList = jsPlumbUtil.addToList, + /** + an isArray function that even works across iframes...see here: + + http://tobyho.com/2011/01/28/checking-types-in-javascript/ + + i was originally using "a.constructor == Array" as a test. + */ + _isArray = jsPlumbUtil.isArray, + _isString = jsPlumbUtil.isString, + _isObject = jsPlumbUtil.isObject; + + var _connectionBeingDragged = null, + _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }, + _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }, + _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el, _instance) { + var o = jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); + if (_instance != null) { + var z = _instance.getZoom(); + return {left:o.left / z, top:o.top / z }; + } + else + return o; + }, + _getSize = function(el) { + return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); + }, + _log = jsPlumbUtil.log, + _group = jsPlumbUtil.group, + _groupEnd = jsPlumbUtil.groupEnd, + _time = jsPlumbUtil.time, + _timeEnd = jsPlumbUtil.timeEnd, + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + _timestamp = function() { return "" + (new Date()).getTime(); }, + + /* + * Class:jsPlumbUIComponent + * Abstract superclass for UI components Endpoint and Connection. Provides the abstraction of paintStyle/hoverPaintStyle, + * and also extends jsPlumbUtil.EventGenerator to provide the bind and fire methods. + */ + jsPlumbUIComponent = function(params) { + var self = this, + a = arguments, + _hover = false, + parameters = params.parameters || {}, + idPrefix = self.idPrefix, + id = idPrefix + (new Date()).getTime(), + paintStyle = null, + hoverPaintStyle = null; + + self._jsPlumb = params["_jsPlumb"]; + self.getId = function() { return id; }; + self.tooltip = params.tooltip; + self.hoverClass = params.hoverClass || self._jsPlumb.Defaults.HoverClass || jsPlumb.Defaults.HoverClass; + + // all components can generate events + jsPlumbUtil.EventGenerator.apply(this); + // all components get this clone function. + // TODO issue 116 showed a problem with this - it seems 'a' that is in + // the clone function's scope is shared by all invocations of it, the classic + // JS closure problem. for now, jsPlumb does a version of this inline where + // it used to call clone. but it would be nice to find some time to look + // further at this. + this.clone = function() { + var o = new Object(); + self.constructor.apply(o, a); + return o; + }; + + this.getParameter = function(name) { return parameters[name]; }, + this.getParameters = function() { + return parameters; + }, + this.setParameter = function(name, value) { parameters[name] = value; }, + this.setParameters = function(p) { parameters = p; }, + this.overlayPlacements = []; + + // user can supply a beforeDetach callback, which will be executed before a detach + // is performed; returning false prevents the detach. + var beforeDetach = params.beforeDetach; + this.isDetachAllowed = function(connection) { + var r = self._jsPlumb.checkCondition("beforeDetach", connection ); + if (beforeDetach) { + try { + r = beforeDetach(connection); + } + catch (e) { _log("jsPlumb: beforeDetach callback failed", e); } + } + return r; + }; + + // user can supply a beforeDrop callback, which will be executed before a dropped + // connection is confirmed. user can return false to reject connection. + var beforeDrop = params.beforeDrop; + this.isDropAllowed = function(sourceId, targetId, scope, connection, dropEndpoint) { + var r = self._jsPlumb.checkCondition("beforeDrop", { + sourceId:sourceId, + targetId:targetId, + scope:scope, + connection:connection, + dropEndpoint:dropEndpoint + }); + if (beforeDrop) { + try { + r = beforeDrop({ + sourceId:sourceId, + targetId:targetId, + scope:scope, + connection:connection, + dropEndpoint:dropEndpoint + }); + } + catch (e) { _log("jsPlumb: beforeDrop callback failed", e); } + } + return r; + }; + + // helper method to update the hover style whenever it, or paintStyle, changes. + // we use paintStyle as the foundation and merge hoverPaintStyle over the + // top. + var _updateHoverStyle = function() { + if (paintStyle && hoverPaintStyle) { + var mergedHoverStyle = {}; + jsPlumb.extend(mergedHoverStyle, paintStyle); + jsPlumb.extend(mergedHoverStyle, hoverPaintStyle); + delete self["hoverPaintStyle"]; + // we want the fillStyle of paintStyle to override a gradient, if possible. + if (mergedHoverStyle.gradient && paintStyle.fillStyle) + delete mergedHoverStyle["gradient"]; + hoverPaintStyle = mergedHoverStyle; + } + }; + + /* + * Sets the paint style and then repaints the element. + * + * Parameters: + * style - Style to use. + */ + this.setPaintStyle = function(style, doNotRepaint) { + paintStyle = style; + self.paintStyleInUse = paintStyle; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /** + * Gets the component's paint style. + * + * Returns: + * the component's paint style. if there is no hoverPaintStyle set then this will be the paint style used all the time, otherwise this is the style used when the mouse is not hovering. + */ + this.getPaintStyle = function() { + return paintStyle; + }; + + /* + * Sets the paint style to use when the mouse is hovering over the element. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the component will not be repainted. useful when setting things up initially. + */ + this.setHoverPaintStyle = function(style, doNotRepaint) { + hoverPaintStyle = style; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /** + * Gets the component's hover paint style. + * + * Returns: + * the component's hover paint style. may be null. + */ + this.getHoverPaintStyle = function() { + return hoverPaintStyle; + }; + + /* + * sets/unsets the hover state of this element. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + this.setHover = function(hover, ignoreAttachedElements, timestamp) { + // while dragging, we ignore these events. this keeps the UI from flashing and + // swishing and whatevering. + if (!self._jsPlumb.currentlyDragging && !self._jsPlumb.isHoverSuspended()) { + + _hover = hover; + if (self.hoverClass != null && self.canvas != null) { + if (hover) + jpcl.addClass(self.canvas, self.hoverClass); + else + jpcl.removeClass(self.canvas, self.hoverClass); + } + if (hoverPaintStyle != null) { + self.paintStyleInUse = hover ? hoverPaintStyle : paintStyle; + timestamp = timestamp || _timestamp(); + self.repaint({timestamp:timestamp, recalc:false}); + } + // get the list of other affected elements, if supported by this component. + // for a connection, its the endpoints. for an endpoint, its the connections! surprise. + if (self.getAttachedElements && !ignoreAttachedElements) + _updateAttachedElements(hover, _timestamp(), self); + } + }; + + this.isHover = function() { return _hover; }; + + var zIndex = null; + this.setZIndex = function(v) { zIndex = v; }; + this.getZIndex = function() { return zIndex; }; + + var jpcl = jsPlumb.CurrentLibrary, + events = [ "click", "dblclick", "mouseenter", "mouseout", "mousemove", "mousedown", "mouseup", "contextmenu" ], + eventFilters = { "mouseout":"mouseexit" }, + bindOne = function(o, c, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.bind(o, evt, function(ee) { + c.fire(filteredEvent, c, ee); + }); + }, + unbindOne = function(o, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.unbind(o, evt); + }; + + this.attachListeners = function(o, c) { + for (var i = 0; i < events.length; i++) { + bindOne(o, c, events[i]); + } + }; + + var _updateAttachedElements = function(state, timestamp, sourceElement) { + var affectedElements = self.getAttachedElements(); // implemented in subclasses + if (affectedElements) { + for (var i = 0; i < affectedElements.length; i++) { + if (!sourceElement || sourceElement != affectedElements[i]) + affectedElements[i].setHover(state, true, timestamp); // tell the attached elements not to inform their own attached elements. + } + } + }; + + this.reattachListenersForElement = function(o) { + if (arguments.length > 1) { + for (var i = 0; i < events.length; i++) + unbindOne(o, events[i]); + for (var i = 1; i < arguments.length; i++) + self.attachListeners(o, arguments[i]); + } + }; + + /* + * TYPES + */ + var _types = [], + _splitType = function(t) { return t == null ? null : t.split(" ")}, + _applyTypes = function(doNotRepaint) { + if (self.getDefaultType) { + var td = self.getTypeDescriptor(); + + var o = jsPlumbUtil.merge({}, self.getDefaultType()); + for (var i = 0; i < _types.length; i++) + o = jsPlumbUtil.merge(o, self._jsPlumb.getType(_types[i], td)); + + self.applyType(o); + if (!doNotRepaint) self.repaint(); + } + }; + + self.setType = function(typeId, doNotRepaint) { + _types = _splitType(typeId) || []; + _applyTypes(doNotRepaint); + }; + + /* + * Function : getType + * Gets the 'types' of this component. + */ + self.getType = function() { + return _types; + }; + + self.hasType = function(typeId) { + return jsPlumbUtil.indexOf(_types, typeId) != -1; + }; + + self.addType = function(typeId, doNotRepaint) { + var t = _splitType(typeId), _cont = false; + if (t != null) { + for (var i = 0; i < t.length; i++) { + if (!self.hasType(t[i])) { + _types.push(t[i]); + _cont = true; + } + } + if (_cont) _applyTypes(doNotRepaint); + } + }; + + self.removeType = function(typeId, doNotRepaint) { + var t = _splitType(typeId), _cont = false, _one = function(tt) { + var idx = jsPlumbUtil.indexOf(_types, tt); + if (idx != -1) { + _types.splice(idx, 1); + return true; + } + return false; + }; + + if (t != null) { + for (var i = 0; i < t.length; i++) { + _cont = _one(t[i]) || _cont; + } + if (_cont) _applyTypes(doNotRepaint); + } + }; + + self.toggleType = function(typeId, doNotRepaint) { + var t = _splitType(typeId); + if (t != null) { + for (var i = 0; i < t.length; i++) { + var idx = jsPlumbUtil.indexOf(_types, t[i]); + if (idx != -1) + _types.splice(idx, 1); + else + _types.push(t[i]); + } + + _applyTypes(doNotRepaint); + } + }; + + this.applyType = function(t) { + self.setPaintStyle(t.paintStyle); + self.setHoverPaintStyle(t.hoverPaintStyle); + if (t.parameters){ + for (var i in t.parameters) + self.setParameter(i, t.parameters[i]); + } + }; + + }, + + overlayCapableJsPlumbUIComponent = function(params) { + jsPlumbUIComponent.apply(this, arguments); + var self = this; + this.overlays = []; + + var processOverlay = function(o) { + var _newOverlay = null; + if (_isArray(o)) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0], + // make a copy of the object so as not to mess up anyone else's reference... + p = jsPlumb.extend({component:self, _jsPlumb:self._jsPlumb}, o[1]); + if (o.length == 3) jsPlumb.extend(p, o[2]); + _newOverlay = new jsPlumb.Overlays[self._jsPlumb.getRenderMode()][type](p); + if (p.events) { + for (var evt in p.events) { + _newOverlay.bind(evt, p.events[evt]); + } + } + } else if (o.constructor == String) { + _newOverlay = new jsPlumb.Overlays[self._jsPlumb.getRenderMode()][o]({component:self, _jsPlumb:self._jsPlumb}); + } else { + _newOverlay = o; + } + + self.overlays.push(_newOverlay); + }, + calculateOverlaysToAdd = function(params) { + var defaultKeys = self.defaultOverlayKeys || [], + o = params.overlays, + checkKey = function(k) { + return self._jsPlumb.Defaults[k] || jsPlumb.Defaults[k] || []; + }; + + if (!o) o = []; + + for (var i = 0; i < defaultKeys.length; i++) + o.unshift.apply(o, checkKey(defaultKeys[i])); + + return o; + } + + var _overlays = calculateOverlaysToAdd(params);//params.overlays || self._jsPlumb.Defaults.Overlays; + if (_overlays) { + for (var i = 0; i < _overlays.length; i++) { + processOverlay(_overlays[i]); + } + } + + // overlay finder helper method + var _getOverlayIndex = function(id) { + var idx = -1; + for (var i = 0; i < self.overlays.length; i++) { + if (id === self.overlays[i].id) { + idx = i; + break; + } + } + return idx; + }; + + this.addOverlay = function(overlay, doNotRepaint) { + processOverlay(overlay); + if (!doNotRepaint) self.repaint(); + }; + + this.getOverlay = function(id) { + var idx = _getOverlayIndex(id); + return idx >= 0 ? self.overlays[idx] : null; + }; + + this.getOverlays = function() { + return self.overlays; + }; + + this.hideOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.hide(); + }; + + this.hideOverlays = function() { + for (var i = 0; i < self.overlays.length; i++) + self.overlays[i].hide(); + }; + + this.showOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.show(); + }; + + this.showOverlays = function() { + for (var i = 0; i < self.overlays.length; i++) + self.overlays[i].show(); + }; + + this.removeAllOverlays = function() { + for (var i = 0; i < self.overlays.length; i++) { + if (self.overlays[i].cleanup) self.overlays[i].cleanup(); + } + + self.overlays.splice(0, self.overlays.length); + self.repaint(); + }; + + this.removeOverlay = function(overlayId) { + var idx = _getOverlayIndex(overlayId); + if (idx != -1) { + var o = self.overlays[idx]; + if (o.cleanup) o.cleanup(); + self.overlays.splice(idx, 1); + } + }; + + this.removeOverlays = function() { + for (var i = 0; i < arguments.length; i++) + self.removeOverlay(arguments[i]); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + var _internalLabelOverlayId = "__label", + _makeLabelOverlay = function(params) { + + var _params = { + cssClass:params.cssClass, + labelStyle : this.labelStyle, + id:_internalLabelOverlayId, + component:self, + _jsPlumb:self._jsPlumb + }, + mergedParams = jsPlumb.extend(_params, params); + + return new jsPlumb.Overlays[self._jsPlumb.getRenderMode()].Label( mergedParams ); + }; + if (params.label) { + var loc = params.labelLocation || self.defaultLabelLocation || 0.5, + labelStyle = params.labelStyle || self._jsPlumb.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.overlays.push(_makeLabelOverlay({ + label:params.label, + location:loc, + labelStyle:labelStyle + })); + } + + + this.setLabel = function(l) { + var lo = self.getOverlay(_internalLabelOverlayId); + if (!lo) { + var params = l.constructor == String || l.constructor == Function ? { label:l } : l; + lo = _makeLabelOverlay(params); + this.overlays.push(lo); + } + else { + if (l.constructor == String || l.constructor == Function) lo.setLabel(l); + else { + if (l.label) lo.setLabel(l.label); + if (l.location) lo.setLocation(l.location); + } + } + + if (!self._jsPlumb.isSuspendDrawing()) + self.repaint(); + }; + + + this.getLabel = function() { + var lo = self.getOverlay(_internalLabelOverlayId); + return lo != null ? lo.getLabel() : null; + }; + + + this.getLabelOverlay = function() { + return self.getOverlay(_internalLabelOverlayId); + }; + + var superAt = this.applyType; + this.applyType = function(t) { + superAt(t); + self.removeAllOverlays(); + if (t.overlays) { + for (var i = 0; i < t.overlays.length; i++) + self.addOverlay(t.overlays[i], true); + } + }; + }, + + _bindListeners = function(obj, _self, _hoverFunction) { + obj.bind("click", function(ep, e) { _self.fire("click", _self, e); }); + obj.bind("dblclick", function(ep, e) { _self.fire("dblclick", _self, e); }); + obj.bind("contextmenu", function(ep, e) { _self.fire("contextmenu", _self, e); }); + obj.bind("mouseenter", function(ep, e) { + if (!_self.isHover()) { + _hoverFunction(true); + _self.fire("mouseenter", _self, e); + } + }); + obj.bind("mouseexit", function(ep, e) { + if (_self.isHover()) { + _hoverFunction(false); + _self.fire("mouseexit", _self, e); + } + }); + }; + + var _jsPlumbInstanceIndex = 0, + getInstanceIndex = function() { + var i = _jsPlumbInstanceIndex + 1; + _jsPlumbInstanceIndex++; + return i; + }; + + var jsPlumbInstance = function(_defaults) { + + /* + * Property: Defaults + * + * These are the default settings for jsPlumb. They are what will be used if you do not supply specific pieces of information + * to the various API calls. A convenient way to implement your own look and feel can be to override these defaults + * by including a script somewhere after the jsPlumb include, but before you make any calls to jsPlumb. + * + * Properties: + * - *Anchor* The default anchor to use for all connections (both source and target). Default is "BottomCenter". + * - *Anchors* The default anchors to use ([source, target]) for all connections. Defaults are ["BottomCenter", "BottomCenter"]. + * - *ConnectionsDetachable* Whether or not connections are detachable by default (using the mouse). Defaults to true. + * - *ConnectionOverlays* The default overlay definitions for Connections. Defaults to an empty list. + * - *Connector* The default connector definition to use for all connections. Default is "Bezier". + * - *ConnectorZIndex* Optional value for the z-index of Connections that are not in the hover state. If you set this, jsPlumb will set the z-index of all created Connections to be this value, and the z-index of any Connections in the hover state to be this value plus one. This brings hovered connections up on top of others, which is a nice effect in busy UIs. + * - *Container* Optional selector or element id that instructs jsPlumb to append elements it creates to a specific element. + * - *DragOptions* The default drag options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *DropOptions* The default drop options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *Endpoint* The default endpoint definition to use for all connections (both source and target). Default is "Dot". + * - *EndpointOverlays* The default overlay definitions for Endpoints. Defaults to an empty list. + * - *Endpoints* The default endpoint definitions ([ source, target ]) to use for all connections. Defaults are ["Dot", "Dot"]. + * - *EndpointStyle* The default style definition to use for all endpoints. Default is fillStyle:"#456". + * - *EndpointStyles* The default style definitions ([ source, target ]) to use for all endpoints. Defaults are empty. + * - *EndpointHoverStyle* The default hover style definition to use for all endpoints. Default is null. + * - *EndpointHoverStyles* The default hover style definitions ([ source, target ]) to use for all endpoints. Defaults are null. + * - *HoverPaintStyle* The default hover style definition to use for all connections. Defaults are null. + * - *LabelStyle* The default style to use for label overlays on connections. + * - *LogEnabled* Whether or not the jsPlumb log is enabled. defaults to false. + * - *Overlays* The default overlay definitions (for both Connections and Endpoint). Defaults to an empty list. + * - *MaxConnections* The default maximum number of connections for an Endpoint. Defaults to 1. + * - *PaintStyle* The default paint style for a connection. Default is line width of 8 pixels, with color "#456". + * - *ReattachConnections* Whether or not to reattach Connections that a user has detached with the mouse and then dropped. Default is false. + * - *RenderMode* What mode to use to paint with. If you're on IE<9, you don't really get to choose this. You'll just get VML. Otherwise, the jsPlumb default is to use SVG. + * - *Scope* The default "scope" to use for connections. Scope lets you assign connections to different categories. + */ + this.Defaults = { + Anchor : "BottomCenter", + Anchors : [ null, null ], + ConnectionsDetachable : true, + ConnectionOverlays : [ ], + Connector : "Bezier", + ConnectorZIndex : null, + Container : null, + DragOptions : { }, + DropOptions : { }, + Endpoint : "Dot", + EndpointOverlays : [ ], + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : "#456" }, + EndpointStyles : [ null, null ], + EndpointHoverStyle : null, + EndpointHoverStyles : [ null, null ], + HoverPaintStyle : null, + LabelStyle : { color : "black" }, + LogEnabled : false, + Overlays : [ ], + MaxConnections : 1, + PaintStyle : { lineWidth : 8, strokeStyle : "#456" }, + ReattachConnections:false, + RenderMode : "svg", + Scope : "jsPlumb_DefaultScope" + }; + if (_defaults) jsPlumb.extend(this.Defaults, _defaults); + + this.logEnabled = this.Defaults.LogEnabled; + + var _connectionTypes = { }, _endpointTypes = {}; + this.registerConnectionType = function(id, type) { + _connectionTypes[id] = jsPlumb.extend({}, type); + }; + this.registerConnectionTypes = function(types) { + for (var i in types) + _connectionTypes[i] = jsPlumb.extend({}, types[i]); + }; + this.registerEndpointType = function(id, type) { + _endpointTypes[id] = jsPlumb.extend({}, type); + }; + this.registerEndpointTypes = function(types) { + for (var i in types) + _endpointTypes[i] = jsPlumb.extend({}, types[i]); + }; + this.getType = function(id, typeDescriptor) { + return typeDescriptor === "connection" ? _connectionTypes[id] : _endpointTypes[id]; + }; + + jsPlumbUtil.EventGenerator.apply(this); + var _currentInstance = this, + _instanceIndex = getInstanceIndex(), + _bb = _currentInstance.bind, + _initialDefaults = {}, + _zoom = 1; + + this.setZoom = function(z, repaintEverything) { + _zoom = z; + if (repaintEverything) _currentInstance.repaintEverything(); + }; + this.getZoom = function() { return _zoom; }; + + for (var i in this.Defaults) + _initialDefaults[i] = this.Defaults[i]; + + this.bind = function(event, fn) { + if ("ready" === event && initialized) fn(); + else _bb.apply(_currentInstance,[event, fn]); + }; + + _currentInstance.importDefaults = function(d) { + for (var i in d) { + _currentInstance.Defaults[i] = d[i]; + } + }; + + _currentInstance.restoreDefaults = function() { + _currentInstance.Defaults = jsPlumb.extend({}, _initialDefaults); + }; + + var log = null, + resizeTimer = null, + initialized = false, + connectionsByScope = {}, + /** + * map of element id -> endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + endpointsByElement = {}, + endpointsByUUID = {}, + offsets = {}, + offsetTimestamps = {}, + floatingConnections = {}, + draggableStates = {}, + canvasList = [], + sizes = [], + //listeners = {}, // a map: keys are event types, values are lists of listeners. + DEFAULT_SCOPE = this.Defaults.Scope, + renderMode = null, // will be set in init() + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }, + + /** + * appends an element to some other element, which is calculated as follows: + * + * 1. if _currentInstance.Defaults.Container exists, use that element. + * 2. if the 'parent' parameter exists, use that. + * 3. otherwise just use the root element (for DOM usage, the document body). + * + */ + _appendElement = function(el, parent) { + if (_currentInstance.Defaults.Container) + jsPlumb.CurrentLibrary.appendElement(el, _currentInstance.Defaults.Container); + else if (!parent) + //document.body.appendChild(el); + jsPlumbAdapter.appendToRoot(el); + else + jsPlumb.CurrentLibrary.appendElement(el, parent); + }, + + _curIdStamp = 1, + _idstamp = function() { return "" + _curIdStamp++; }, + + /** + * YUI, for some reason, put the result of a Y.all call into an object that contains + * a '_nodes' array, instead of handing back an array-like object like the other + * libraries do. + */ + _convertYUICollection = function(c) { + return c._nodes ? c._nodes : c; + }, + + /** + * Draws an endpoint and its connections. this is the main entry point into drawing connections as well + * as endpoints, since jsPlumb is endpoint-centric under the hood. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + _draw = function(element, ui, timestamp) { + + // TOD is it correct to filter by headless at this top level? how would a headless adapter ever repaint? + if (!jsPlumbAdapter.headless && !_suspendDrawing) { + var id = _getAttribute(element, "id"), + repaintEls = _currentInstance.dragManager.getElementsForDraggable(id); + + if (timestamp == null) timestamp = _timestamp(); + + _currentInstance.anchorManager.redraw(id, ui, timestamp); + + if (repaintEls) { + for (var i in repaintEls) { + _currentInstance.anchorManager.redraw(repaintEls[i].id, ui, timestamp, repaintEls[i].offset); + } + } + } + }, + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + _elementProxy = function(element, fn) { + var retVal = null; + if (_isArray(element)) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }, + + /** + * gets an Endpoint by uuid. + */ + _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }, + + /** + * inits a draggable if it's not already initialised. + */ + _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + // TODO move to DragManager? + if (!jsPlumbAdapter.headless) { + var draggable = isDraggable == null ? false : isDraggable, + jpcl = jsPlumb.CurrentLibrary; + if (draggable) { + if (jpcl.isDragSupported(element) && !jpcl.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jpcl.dragEvents["drag"], + stopEvent = jpcl.dragEvents["stop"], + startEvent = jpcl.dragEvents["start"]; + + options[startEvent] = _wrap(options[startEvent], function() { + _currentInstance.setHoverSuspended(true); + }); + + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jpcl.getUIPosition(arguments, _currentInstance.getZoom()); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jpcl.getUIPosition(arguments, _currentInstance.getZoom()); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + _currentInstance.setHoverSuspended(false); + }); + var elId = _getId(element); // need ID + draggableStates[elId] = true; + var draggable = draggableStates[elId]; + options.disabled = draggable == null ? false : !draggable; + jpcl.initDraggable(element, options, false); + _currentInstance.dragManager.register(element); + } + } + } + }, + + /* + * prepares a final params object that can be passed to _newConnection, taking into account defaults, events, etc. + */ + _prepareConnectionParams = function(params, referenceParams) { + var _p = jsPlumb.extend( { + sourceIsNew:true, + targetIsNew:true + }, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + // hotwire endpoints passed as source or target to sourceEndpoint/targetEndpoint, respectively. + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not full. + // source: + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + // target: + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + // at this point, if we have source or target Endpoints, they were not new and we should mark the + // flag to reflect that. this is for use later with the deleteEndpointsOnDetach flag. + if (_p.sourceEndpoint) _p.sourceIsNew = false; + if (_p.targetEndpoint) _p.targetIsNew = false; + + // if source endpoint mandates connection type and nothing specified in our params, use it. + if (!_p.type && _p.sourceEndpoint) + _p.type = _p.sourceEndpoint.connectionType; + + // copy in any connectorOverlays that were specified on the source endpoint. + // it doesnt copy target endpoint overlays. i'm not sure if we want it to or not. + if (_p.sourceEndpoint && _p.sourceEndpoint.connectorOverlays) { + _p.overlays = _p.overlays || []; + for (var i = 0; i < _p.sourceEndpoint.connectorOverlays.length; i++) { + _p.overlays.push(_p.sourceEndpoint.connectorOverlays[i]); + } + } + + // tooltip. params.tooltip takes precedence, then sourceEndpoint.connectorTooltip. + _p.tooltip = params.tooltip; + if (!_p.tooltip && _p.sourceEndpoint && _p.sourceEndpoint.connectorTooltip) + _p.tooltip = _p.sourceEndpoint.connectorTooltip; + + // if there's a target specified (which of course there should be), and there is no + // target endpoint specified, and 'newConnection' was not set to true, then we check to + // see if a prior call to makeTarget has provided us with the specs for the target endpoint, and + // we use those if so. additionally, if the makeTarget call was specified with 'uniqueEndpoint' set + // to true, then if that target endpoint has already been created, we re-use it. + if (_p.target && !_p.target.endpoint && !_p.targetEndpoint && !_p.newConnection) { + var tid = _getId(_p.target), + tep =_targetEndpointDefinitions[tid], + existingUniqueEndpoint = _targetEndpoints[tid]; + + if (tep) { + // if target not enabled, return. + if (!_targetsEnabled[tid]) return; + + // check for max connections?? + var newEndpoint = existingUniqueEndpoint != null ? existingUniqueEndpoint : _currentInstance.addEndpoint(_p.target, tep); + if (_targetEndpointsUnique[tid]) _targetEndpoints[tid] = newEndpoint; + _p.targetEndpoint = newEndpoint; + newEndpoint._makeTargetCreator = true; + _p.targetIsNew = true; + } + } + + // same thing, but for source. + if (_p.source && !_p.source.endpoint && !_p.sourceEndpoint && !_p.newConnection) { + var tid = _getId(_p.source), + tep = _sourceEndpointDefinitions[tid], + existingUniqueEndpoint = _sourceEndpoints[tid]; + + if (tep) { + // if source not enabled, return. + if (!_sourcesEnabled[tid]) return; + + var newEndpoint = existingUniqueEndpoint != null ? existingUniqueEndpoint : _currentInstance.addEndpoint(_p.source, tep); + if (_sourceEndpointsUnique[tid]) _sourceEndpoints[tid] = newEndpoint; + _p.sourceEndpoint = newEndpoint; + _p.sourceIsNew = true; + } + } + + return _p; + }, + + _newConnection = function(params) { + var connectionFunc = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + endpointFunc = _currentInstance.Defaults.EndpointType || Endpoint, + parent = jsPlumb.CurrentLibrary.getParent; + + if (params.container) + params["parent"] = params.container; + else { + if (params.sourceEndpoint) + params["parent"] = params.sourceEndpoint.parent; + else if (params.source.constructor == endpointFunc) + params["parent"] = params.source.parent; + else params["parent"] = parent(params.source); + } + + params["_jsPlumb"] = _currentInstance; + var con = new connectionFunc(params); + con.id = "con_" + _idstamp(); + _eventFireProxy("click", "click", con); + _eventFireProxy("dblclick", "dblclick", con); + _eventFireProxy("contextmenu", "contextmenu", con); + return con; + }, + + /** + * adds the connection to the backing model, fires an event if necessary and then redraws + */ + _finaliseConnection = function(jpc, params, originalEvent) { + params = params || {}; + // add to list of connections (by scope). + if (!jpc.suspendedEndpoint) + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + if (!params.doNotFireConnectionEvent && params.fireEvent !== false) { + + var eventArgs = { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }; + + _currentInstance.fire("jsPlumbConnection", eventArgs, originalEvent); + // this is from 1.3.11 onwards. "jsPlumbConnection" always felt so unnecessary, so + // I've added this alias in 1.3.11, with a view to removing "jsPlumbConnection" completely in a future version. be aware, of course, you should only register listeners for one or the other of these events. + _currentInstance.fire("connection", eventArgs, originalEvent); + } + + // always inform the anchor manager + // except that if jpc has a suspended endpoint it's not true to say the + // connection is new; it has just (possibly) moved. the question is whether + // to make that call here or in the anchor manager. i think perhaps here. + _currentInstance.anchorManager.newConnection(jpc); + // force a paint + _draw(jpc.source); + }, + + _eventFireProxy = function(event, proxyEvent, obj) { + obj.bind(event, function(originalObject, originalEvent) { + _currentInstance.fire(proxyEvent, obj, originalEvent); + }); + }, + + /** + * for the given endpoint params, returns an appropriate parent element for the UI elements that will be added. + * this function is used by _newEndpoint (directly below), and also in the makeSource function in jsPlumb. + * + * the logic is to first look for a "container" member of params, and pass that back if found. otherwise we + * handoff to the 'getParent' function in the current library. + */ + _getParentFromParams = function(params) { + if (params.container) + return params.container; + else { + var tag = jsPlumb.CurrentLibrary.getTagName(params.source), + p = jsPlumb.CurrentLibrary.getParent(params.source); + if (tag && tag.toLowerCase() === "td") + return jsPlumb.CurrentLibrary.getParent(p); + else return p; + } + }, + + /** + factory method to prepare a new endpoint. this should always be used instead of creating Endpoints + manually, since this method attaches event listeners and an id. + */ + _newEndpoint = function(params) { + var endpointFunc = _currentInstance.Defaults.EndpointType || Endpoint; + params.parent = _getParentFromParams(params); + params["_jsPlumb"] = _currentInstance; + var ep = new endpointFunc(params); + ep.id = "ep_" + _idstamp(); + _eventFireProxy("click", "endpointClick", ep); + _eventFireProxy("dblclick", "endpointDblClick", ep); + _eventFireProxy("contextmenu", "contextmenu", ep); + if (!jsPlumbAdapter.headless) + _currentInstance.dragManager.endpointAdded(params.source); + return ep; + }, + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + _operation = function(elId, func, endpointFunc) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + if (endpointFunc) endpointFunc(endpoints[i]); + } + } + }, + /** + * perform an operation on all elements. + */ + _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }, + + /** + * helper to remove an element from the DOM. + */ + _removeElement = function(element, parent) { + if (element != null && element.parentNode != null) { + element.parentNode.removeChild(element); + } + }, + /** + * helper to remove a list of elements from the DOM. + */ + _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }, + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }, + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + _setVisible = function(el, state, alsoChangeEndpoints) { + state = state === "block"; + var endpointFunc = null; + if (alsoChangeEndpoints) { + if (state) endpointFunc = function(ep) { + ep.setVisible(true, true, true); + }; + else endpointFunc = function(ep) { + ep.setVisible(false, true, true); + }; + } + var id = _getAttribute(el, "id"); + _operation(id, function(jpc) { + if (state && alsoChangeEndpoints) { + // this test is necessary because this functionality is new, and i wanted to maintain backwards compatibility. + // this block will only set a connection to be visible if the other endpoint in the connection is also visible. + var oidx = jpc.sourceId === id ? 1 : 0; + if (jpc.endpoints[oidx].isVisible()) jpc.setVisible(true); + } + else // the default behaviour for show, and what always happens for hide, is to just set the visibility without getting clever. + jpc.setVisible(state); + }, endpointFunc); + }, + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? false : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }, + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + _toggleVisible = function(elId, changeEndpoints) { + var endpointFunc = null; + if (changeEndpoints) { + endpointFunc = function(ep) { + var state = ep.isVisible(); + ep.setVisible(!state); + }; + } + _operation(elId, function(jpc) { + var state = jpc.isVisible(); + jpc.setVisible(!state); + }, endpointFunc); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }, + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (_suspendDrawing && !timestamp) timestamp = _suspendedAt; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return offsets[elId]; + } + if (recalc || !offset) { // if forced repaint or no offset available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s, _currentInstance); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + if (sizes[elId] == null) { + var s = _getElementObject(elId); + if (s != null) sizes[elId] = _getSize(s); + } + } + + if(offsets[elId] && !offsets[elId].right) { + offsets[elId].right = offsets[elId].left + sizes[elId][0]; + offsets[elId].bottom = offsets[elId].top + sizes[elId][1]; + offsets[elId].width = sizes[elId][0]; + offsets[elId].height = sizes[elId][1]; + offsets[elId].centerx = offsets[elId].left + (offsets[elId].width / 2); + offsets[elId].centery = offsets[elId].top + (offsets[elId].height / 2); + } + return offsets[elId]; + }, + + // TODO comparison performance + _getCachedData = function(elId) { + var o = offsets[elId]; + if (!o) o = _updateOffset({elId:elId}); + return {o:o, s:sizes[elId]}; + }, + + /** + * gets an id for the given element, creating and setting one if + * necessary. the id is of the form + * + * jsPlumb__ + * + * where "index in instance" is a monotonically increasing integer that starts at 0, + * for each instance. this method is used not only to assign ids to elements that do not + * have them but also to connections and endpoints. + */ + _getId = function(element, uuid, doNotCreateIfNotFound) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else if (arguments.length == 1 || (arguments.length == 3 && !arguments[2])) + id = "jsPlumb_" + _instanceIndex + "_" + _idstamp(); + + if (!doNotCreateIfNotFound) _setAttribute(ele, "id", id); + } + return id; + }, + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, "jsPlumb function failed : " + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, "wrapped function failed : " + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = "_jsPlumb_connector"; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = "_jsPlumb_endpoint"; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = "_jsPlumb_overlay"; + + this.Anchors = {}; + + this.Connectors = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Endpoints = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Overlays = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + +// ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***************************************** + + /* + * Function: importDefaults + * Imports all the given defaults into this instance of jsPlumb. + */ + + /* + * Function: restoreDefaults + * Restores the default settings to "factory" values. + */ + + /* + * Function: bind + * Bind to an event on jsPlumb. + * + * Parameters: + * event - the event to bind. Available events on jsPlumb are: + * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback. + * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback. + * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback. + * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback. + * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback. + * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback. + * - *beforeDrop* : notification that a Connection is about to be dropped. Returning false from this method cancels the drop. jsPlumb passes { sourceId, targetId, scope, connection, dropEndpoint } to your callback. For more information, refer to the jsPlumb documentation. + * - *beforeDetach* : notification that a Connection is about to be detached. Returning false from this method cancels the detach. jsPlumb passes the Connection to your callback. For more information, refer to the jsPlumb documentation. + * - *connectionDrag* : notification that an existing Connection is being dragged. jsPlumb passes the Connection to your callback function. + * - *connectionDragEnd* : notification that the drag of an existing Connection has ended. jsPlumb passes the Connection to your callback function. + * + * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event. + */ + + /* + * Function: unbind + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + + /* + * Function: addClass + * Add class(es) to some element(s). + * + * Parameters: + * el - element id, dom element, or selector representing the element(s) to add class(es) to. + * clazz - string representing the class(es) to add. may contain multiple classes separated by spaces. + */ + + /* + * Function: removeClass + * Remove class from some selement(s). + * + * Parameters: + * el - element id, dom element, or selector representing the element(s) to remove a class from. + * clazz - string representing the class to remove. + */ + + /* + * Function: hasClass + * Checks if an element has some class. + * + * Parameters: + * el - element id, dom element, or selector representing the element to test. If the selector matches multiple elements, we return the test for the first element in the selector only. + * clazz - string representing the class to test for. + */ + +// *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *********************************************************** + + +// --------------------------- jsPLumbInstance public API --------------------------------------------------------- + + this.addClass = function(el, clazz) { return jsPlumb.CurrentLibrary.addClass(el, clazz); }; + this.removeClass = function(el, clazz) { return jsPlumb.CurrentLibrary.removeClass(el, clazz); }; + this.hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(el, clazz); }; + + /* + Function: addEndpoint + + Adds an to a given element or elements. + + Parameters: + + el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + params - Object containing Endpoint constructor arguments. For more information, see . + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some + shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in + this object are anything that 'params' can contain. See . + + Returns: + The newly created , if el referred to a single element. Otherwise, an array of newly created s. + + See Also: + + */ + this.addEndpoint = function(el, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // YUI wrapper + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + var _el = _getElementObject(inputs[i]), id = _getId(_el); + p.source = _el; + _updateOffset({ elId : id, timestamp:_suspendedAt }); + var e = _newEndpoint(p); + if (p.parentAnchor) e.parentAnchor = p.parentAnchor; + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e, timestamp:_suspendedAt }); + var endpointPaintParams = { anchorLoc : anchorLoc, timestamp:_suspendedAt }; + if (_suspendDrawing) endpointPaintParams.recalc = false; + e.paint(endpointPaintParams); + results.push(e); + //if (!jsPlumbAdapter.headless) + //_currentInstance.dragManager.endpointAdded(_el); + } + + return results.length == 1 ? results[0] : results; + }; + + /* + Function: addEndpoints + Adds a list of s to a given element or elements. + + Parameters: + target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See 's constructor documentation. + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created s, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(el, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams); + if (_isArray(e)) + Array.prototype.push.apply(results, e); + else results.push(e); + } + return results; + }; + + /* + Function: animate + This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating + the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // onComplete repaints, just to make sure everything looks good at the end of the animation. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /** + * checks for a listener for the given condition, executing it if found, passing in the given value. + * condition listeners would have been attached using "bind" (which is, you could argue, now overloaded, since + * firing click events etc is a bit different to what this does). i thought about adding a "bindCondition" + * or something, but decided against it, for the sake of simplicity. jsPlumb will never fire one of these + * condition events anyway. + */ + this.checkCondition = function(conditionName, value) { + var l = _currentInstance.getListener(conditionName), + r = true; + + if (l && l.length > 0) { + try { + for (var i = 0 ; i < l.length; i++) { + r = r && l[i](value); + } + } + catch (e) { + _log(_currentInstance, "cannot check condition [" + conditionName + "]" + e); + } + } + return r; + }; + + /** + * checks a condition asynchronously: fires the event handler and passes the handler + * a 'proceed' function and a 'stop' function. The handler MUST execute one or other + * of these once it has made up its mind. + * + * Note that although this reads the listener list for the given condition, it + * does not loop through and hit each listener, because that, with asynchronous + * callbacks, would be messy. so it uses only the first listener registered. + */ + this.checkASyncCondition = function(conditionName, value, proceed, stop) { + var l = _currentInstance.getListener(conditionName); + + if (l && l.length > 0) { + try { + l[0](value, proceed, stop); + } + catch (e) { + _log(_currentInstance, "cannot asynchronously check condition [" + conditionName + "]" + e); + } + } + }; + + /* + Function: connect + Establishes a between two elements (or s, which are themselves registered to elements). + + Parameters: + params - Object containing constructor arguments for the Connection. See 's constructor documentation. + referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of + Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection. + + Returns: + The newly created . + */ + this.connect = function(params, referenceParams) { + // prepare a final set of parameters to create connection with + var _p = _prepareConnectionParams(params, referenceParams), jpc; + // TODO probably a nicer return value if the connection was not made. _prepareConnectionParams + // will return null (and log something) if either endpoint was full. what would be nicer is to + // create a dedicated 'error' object. + if (_p) { + // a connect call will delete its created endpoints on detach, unless otherwise specified. + // this is because the endpoints belong to this connection only, and are no use to + // anyone else, so they hang around like a bad smell. + if (_p.deleteEndpointsOnDetach == null) + _p.deleteEndpointsOnDetach = true; + + // create the connection. it is not yet registered + jpc = _newConnection(_p); + // now add it the model, fire an event, and redraw + _finaliseConnection(jpc, _p); + } + return jpc; + }; + + /* + Function: deleteEndpoint + Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too) + + Parameters: + object - either an object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + if (endpoint.endpoint.cleanup) endpoint.endpoint.cleanup(); + _removeElements(endpoint.endpoint.getDisplayElements()); + _currentInstance.anchorManager.deleteEndpoint(endpoint); + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + if (!jsPlumbAdapter.headless) + _currentInstance.dragManager.endpointDeleted(endpoint); + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every , and their associated s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + _currentInstance.setSuspendDrawing(true); + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + endpointsByElement = {}; + endpointsByUUID = {}; + + _currentInstance.setSuspendDrawing(false, true); + }; + + var fireDetachEvent = function(jpc, doFireEvent, originalEvent) { + // may have been given a connection, or in special cases, an object + var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + argIsConnection = jpc.constructor == connType, + params = argIsConnection ? { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + } : jpc; + + if (doFireEvent) { + _currentInstance.fire("jsPlumbConnectionDetached", params, originalEvent); + // introduced in 1.3.11..an alias because the original event name is unwieldy. in future versions this will be the only event and the other will no longer be fired. + _currentInstance.fire("connectionDetached", params, originalEvent); + } + _currentInstance.anchorManager.connectionDetached(params); + }, + /** + fires an event to indicate an existing connection is being dragged. + */ + fireConnectionDraggingEvent = function(jpc) { + _currentInstance.fire("connectionDrag", jpc); + }, + fireConnectionDragStopEvent = function(jpc) { + _currentInstance.fire("connectionDragStop", jpc); + }; + + /* + Function: detach + Detaches and then removes a . + + Parameters: + connection - the to detach + params - optional parameters to the detach call. valid values here are + fireEvent : defaults to false; indicates you want jsPlumb to fire a connection + detached event. The thinking behind this is that if you made a programmatic + call to detach an event, you probably don't need the callback. + forceDetach : defaults to false. allows you to override any beforeDetach listeners that may be registered. + + Returns: + true if successful, false if not. + */ + this.detach = function() { + + if (arguments.length == 0) return; + var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + firstArgIsConnection = arguments[0].constructor == connType, + params = arguments.length == 2 ? firstArgIsConnection ? (arguments[1] || {}) : arguments[0] : arguments[0], + fireEvent = (params.fireEvent !== false), + forceDetach = params.forceDetach, + connection = firstArgIsConnection ? arguments[0] : params.connection; + + if (connection) { + if (forceDetach || (connection.isDetachAllowed(connection) + && connection.endpoints[0].isDetachAllowed(connection) + && connection.endpoints[1].isDetachAllowed(connection))) { + if (forceDetach || _currentInstance.checkCondition("beforeDetach", connection)) + connection.endpoints[0].detach(connection, false, true, fireEvent); // TODO check this param iscorrect for endpoint's detach method + } + } + else { + var _p = jsPlumb.extend( {}, params); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1]), fireEvent); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source), + targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + if (_currentInstance.checkCondition("beforeDetach", jpc)) { + jpc.endpoints[0].detach(jpc, false, true, fireEvent); + } + } + }); + } + } + }; + + /* + Function: detachAllConnections + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + params - optional parameters. alowed values: + fireEvent : defaults to true, whether or not to fire the detach event. + + Returns: + void + */ + this.detachAllConnections = function(el, params) { + params = params || {}; + el = _getElementObject(el); + var id = _getAttribute(el, "id"), + endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + }; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Parameters: + params - optional params object containing: + fireEvent : whether or not to fire detach events. defaults to true. + + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function(params) { + params = params || {}; + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + } + connectionsByScope = {}; + }; + + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of your + library's draggable method so that jsPlumb can setup the appropriate callbacks. Your + underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * + * : jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * + * : jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + // helpers for select/selectEndpoints + var _setOperation = function(list, func, args, selector) { + for (var i = 0; i < list.length; i++) { + list[i][func].apply(list[i], args); + } + return selector(list); + }, + _getOperation = function(list, func, args) { + var out = []; + for (var i = 0; i < list.length; i++) { + out.push([ list[i][func].apply(list[i], args), list[i] ]); + } + return out; + }, + setter = function(list, func, selector) { + return function() { + return _setOperation(list, func, arguments, selector); + }; + }, + getter = function(list, func) { + return function() { + return _getOperation(list, func, arguments); + }; + }, + prepareList = function(input, doNotGetIds) { + var r = []; + if (input) { + if (typeof input == 'string') { + if (input === "*") return input; + r.push(input); + } + else { + if (doNotGetIds) r = input; + else { + for (var i = 0; i < input.length; i++) + r.push(_getId(_getElementObject(input[i]))); + } + } + } + return r; + }, + filterList = function(list, value, missingIsFalse) { + if (list === "*") return true; + return list.length > 0 ? _indexOf(list, value) != -1 : !missingIsFalse; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method, + * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the + * default scope). + * + * If multiple scopes are passed in, the return value will be a map of + * + * : { scope -> [ connection... ] } + * + * Parameters: + * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list + * of connections that are in the given scope. use '*' for all scopes. + * options - if the argument is a JS object, you can specify a finer-grained filter: + * + * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes. Also may have the value '*', indicating any scope. + * - *source* either a string representing an element id, a selector, or an array of ids. Also may have the value '*', indicating any source. Constrains the result to connections having this/these element(s) as source. + * - *target* either a string representing an element id, a selector, or an array of ids. Also may have the value '*', indicating any target. Constrains the result to connections having this/these element(s) as target. + * flat - return results in a flat array (don't return an object whose keys are scopes and whose values are lists per scope). + * + */ + this.getConnections = function(options, flat) { + if (!options) { + options = {}; + } else if (options.constructor == String) { + options = { "scope": options }; + } + var + scope = options.scope || _currentInstance.getDefaultScope(), + scopes = prepareList(scope, true), + sources = prepareList(options.source), + targets = prepareList(options.target), + results = (!flat && scopes.length > 1) ? {} : [], + _addOne = function(scope, obj) { + if (!flat && scopes.length > 1) { + var ss = results[scope]; + if (ss == null) { + ss = []; results[scope] = ss; + } + ss.push(obj); + } else results.push(obj); + }; + for ( var i in connectionsByScope) { + if (filterList(scopes, i)) { + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filterList(sources, c.sourceId) && filterList(targets, c.targetId)) + _addOne(i, c); + } + } + } + return results; + }; + + var _curryEach = function(list, executor) { + return function(f) { + for (var i = 0; i < list.length; i++) { + f(list[i]); + } + return executor(list); + }; + }, + _curryGet = function(list) { + return function(idx) { + return list[idx]; + }; + }; + + var _makeCommonSelectHandler = function(list, executor) { + return { + // setters + setHover:setter(list, "setHover", executor), + removeAllOverlays:setter(list, "removeAllOverlays", executor), + setLabel:setter(list, "setLabel", executor), + addOverlay:setter(list, "addOverlay", executor), + removeOverlay:setter(list, "removeOverlay", executor), + removeOverlays:setter(list, "removeOverlays", executor), + showOverlay:setter(list, "showOverlay", executor), + hideOverlay:setter(list, "hideOverlay", executor), + showOverlays:setter(list, "showOverlays", executor), + hideOverlays:setter(list, "hideOverlays", executor), + setPaintStyle:setter(list, "setPaintStyle", executor), + setHoverPaintStyle:setter(list, "setHoverPaintStyle", executor), + setParameter:setter(list, "setParameter", executor), + setParameters:setter(list, "setParameters", executor), + setVisible:setter(list, "setVisible", executor), + setZIndex:setter(list, "setZIndex", executor), + repaint:setter(list, "repaint", executor), + addType:setter(list, "addType", executor), + toggleType:setter(list, "toggleType", executor), + removeType:setter(list, "removeType", executor), + + // getters + getLabel:getter(list, "getLabel"), + getOverlay:getter(list, "getOverlay"), + isHover:getter(list, "isHover"), + getParameter:getter(list, "getParameter"), + getParameters:getter(list, "getParameters"), + getPaintStyle:getter(list, "getPaintStyle"), + getHoverPaintStyle:getter(list, "getHoverPaintStyle"), + isVisible:getter(list, "isVisible"), + getZIndex:getter(list, "getZIndex"), + hasType:getter(list, "hasType"), + getType:getter(list, "getType"), + + // util + length:list.length, + each:_curryEach(list, executor), + get:_curryGet(list) + }; + + }; + + var _makeConnectionSelectHandler = function(list) { + var common = _makeCommonSelectHandler(list, _makeConnectionSelectHandler); + return jsPlumb.CurrentLibrary.extend(common, { + // setters + setDetachable:setter(list, "setDetachable", _makeConnectionSelectHandler), + setReattach:setter(list, "setReattach", _makeConnectionSelectHandler), + setConnector:setter(list, "setConnector", _makeConnectionSelectHandler), + detach:function() { + for (var i = 0; i < list.length; i++) + _currentInstance.detach(list[i]); + }, + // getters + isDetachable:getter(list, "isDetachable"), + isReattach:getter(list, "isReattach") + }); + }; + + var _makeEndpointSelectHandler = function(list) { + var common = _makeCommonSelectHandler(list, _makeEndpointSelectHandler); + return jsPlumb.CurrentLibrary.extend(common, { + setEnabled:setter(list, "setEnabled", _makeEndpointSelectHandler), + isEnabled:getter(list, "isEnabled"), + detachAll:function() { + for (var i = 0; i < list.length; i++) + list[i].detachAll(); + }, + "delete":function() { + for (var i = 0; i < list.length; i++) + _currentInstance.deleteEndpoint(list[i]); + } + }); + }; + + /* + * Function: select + * Selects a set of Connections, using the filter options from the getConnections method, and returns an object + * that allows you to perform an operation on all of the Connections at once. + * + * The return value from any of these operations is the original list of Connections, allowing operations to be + * chained (for 'setter' type operations). 'getter' type operations return an array of values, where each entry is + * of the form: + * + * : [ Connection, return value ] + * + * Parameters: + * scope - see getConnections + * source - see getConnections + * target - see getConnections + * + * Returns: + * A list of Connections on which operations may be executed. 'Setter' type operations can be chained; 'getter' type operations + * return an array of [Connection, value] pairs, one entry for each Connection in the list returned. The full list of operations + * is as follows (where not specified, the operation's effect or return value is the same as the corresponding method on Connection) : + * + * - setHover + * - removeAllOverlays + * - setLabel + * - addOverlay + * - removeOverlay + * - removeOverlays + * - showOverlay + * - hideOverlay + * - showOverlays + * - hideOverlays + * - setPaintStyle + * - setHoverPaintStyle + * - setDetachable + * - setReattach + * - setConnector + * - setParameter + * - setParameters + * - getLabel + * - getOverlay + * - isHover + * - isDetachable + * - isReattach + * - getParameter + * - getParameters + * - getPaintStyle + * - getHoverPaintStyle + * - detach : detaches all the connections in the list. not chainable and does not return anything. + * - length : returns the length of the list. + * - get(index) : returns the Connection at 'index' in the list. + * - each(function(connection)...) : allows you to specify your own function to execute; this function is chainable. + */ + this.select = function(params) { + params = params || {}; + params.scope = params.scope || "*"; + var c = _currentInstance.getConnections(params, true); + return _makeConnectionSelectHandler(c); + }; + + /* + * Function: selectEndpoints + * Selects a set of Endpoints and returns an object that allows you to execute various different methods on them at once. The return + * value from any of these operations is the original list of Endpoints, allowing operations to be chained (for 'setter' type + * operations). 'getter' type operations return an array of values, where each entry is of the form: + * + * : [ Endpoint, return value ] + * + * Parameters: + * scope - either a string or an array of strings. + * source - either a string, a dom element, or a selector, or an array of any mix of these. limits returned endpoints to those that are declared as a source endpoint on any elements identified. + * target - either a string, a dom element, or a selector, or an array of any mix of these. limits returned endpoints to those that are declared as a target endpoint on any elements identified. + * element - either a string, a dom element, or a selector, or an array of any mix of these. limits returned endpoints to those that are declared as either a source OR a target endpoint on any elements identified. + * + * Returns: + * A list of Endpoints on which operations may be executed. 'Setter' type operations can be chained; 'getter' type operations + * return an array of [Endpoint, value] pairs, one entry for each Endpoint in the list returned. + * + * The full list of operations is as follows (where not specified, the operation's effect or return value is the + * same as the corresponding method on Endpoint) : + * + * - setHover + * - removeAllOverlays + * - setLabel + * - addOverlay + * - removeOverlay + * - removeOverlays + * - showOverlay + * - hideOverlay + * - showOverlays + * - hideOverlays + * - setPaintStyle + * - setHoverPaintStyle + * - setParameter + * - setParameters + * - getLabel + * - getOverlay + * - isHover + * - isDetachable + * - getParameter + * - getParameters + * - getPaintStyle + * - getHoverPaintStyle + * - detachAll : Detaches all the Connections from every Endpoint in the list. not chainable and does not return anything. + * - delete : Deletes every Endpoint in the list. not chainable and does not return anything. + * - length : returns the length of the list. + * - get(index) : returns the Endpoint at 'index' in the list. + * - each(function(endpoint)...) : allows you to specify your own function to execute; this function is chainable. + */ + this.selectEndpoints = function(params) { + params = params || {}; + params.scope = params.scope || "*"; + var noElementFilters = !params.element && !params.source && !params.target, + elements = noElementFilters ? "*" : prepareList(params.element), + sources = noElementFilters ? "*" : prepareList(params.source), + targets = noElementFilters ? "*" : prepareList(params.target), + scopes = prepareList(params.scope, true); + + var ep = []; + + for (var el in endpointsByElement) { + var either = filterList(elements, el, true), + source = filterList(sources, el, true), + sourceMatchExact = sources != "*", + target = filterList(targets, el, true), + targetMatchExact = targets != "*"; + + // if they requested 'either' then just match scope. otherwise if they requested 'source' (not as a wildcard) then we have to match only endpoints that have isSource set to to true, and the same thing with isTarget. + if ( either || source || target ) { + inner: + for (var i = 0; i < endpointsByElement[el].length; i++) { + var _ep = endpointsByElement[el][i]; + if (filterList(scopes, _ep.scope, true)) { + + var noMatchSource = (sourceMatchExact && sources.length > 0 && !_ep.isSource), + noMatchTarget = (targetMatchExact && targets.length > 0 && !_ep.isTarget); + + if (noMatchSource || noMatchTarget) + continue inner; + + ep.push(_ep); + } + } + } + } + + return _makeEndpointSelectHandler(ep); + }; + + /* + * Function: getAllConnections + * Gets all connections, as a map of the form: + * + * : { scope -> [ connection... ] } + */ + this.getAllConnections = function() { + return connectionsByScope; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. + * + * A scope defines a type of endpoint/connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /** + * Function: getEndpoints + * Gets the list of Endpoints for a given element. + * + * Parameters: + * el - element id, dom element, or selector. + * + * Returns: + * An array of Endpoints for the specified element. + */ + this.getEndpoints = function(el) { + return endpointsByElement[_getId(el)]; + }; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + this.getOffset = function(id) { + var o = offsets[id]; + return _updateOffset({elId:id}); + }; + + /* + * Function: getSelector + * This method takes the given selector spec and, using the current underlying library, turns it into + * a selector from that library. This method exists really as a helper function for those applications + * where you're writing jsPlumb code that will target more than one library (such as in the case of the + * jsPlumb demo pages). + * + * Parameters: + * spec - a valid selector string. + */ + this.getSelector = function(spec) { + return jsPlumb.CurrentLibrary.getSelector(spec); + }; + + // get the size of the element with the given id, perhaps from cache. + this.getSize = function(id) { + var s = sizes[id]; + if (!s) _updateOffset({elId:id}); + return sizes[id]; + }; + + this.appendElement = _appendElement; + + var _hoverSuspended = false; + this.isHoverSuspended = function() { return _hoverSuspended; }; + this.setHoverSuspended = function(s) { _hoverSuspended = s; }; + + var _isAvailable = function(m) { + return function() { + return jsPlumbAdapter.isRenderModeAvailable(m); + }; + } + this.isCanvasAvailable = _isAvailable("canvas"); + this.isSVGAvailable = _isAvailable("svg"); + this.isVMLAvailable = _isAvailable("vml"); + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + changeEndpoints - whether not to also hide endpoints on the element. by default this is false. + + Returns: + void + */ + this.hide = function(el, changeEndpoints) { + _setVisible(el, "none", changeEndpoints); + }; + + // exposed for other objects to use to get a unique id. + this.idstamp = _idstamp; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + */ + this.init = function() { + if (!initialized) { + _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run. + + var bindOne = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && renderMode == jsPlumb.CANVAS) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + var t = c[i].connector[event](e); + if (t) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i].endpoint[event](e)) return; + } + } + } + }); + }; + bindOne("click");bindOne("dblclick");bindOne("mousemove");bindOne("mousedown");bindOne("mouseup");bindOne("contextmenu"); + + initialized = true; + _currentInstance.fire("ready"); + } + }; + + this.log = log; + this.jsPlumbUIComponent = jsPlumbUIComponent; + + /* + * Creates an anchor with the given params. + * + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function() { + if (arguments.length == 0) return null; + var specimen = arguments[0], elementId = arguments[1], jsPlumbInstance = arguments[2], newAnchor = null; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; //TODO hazy here about whether it should be added or is already added somehow. + // is it the name of an anchor type? + else if (typeof specimen == "string") { + newAnchor = jsPlumb.Anchors[arguments[0]]({elementId:elementId, jsPlumbInstance:_currentInstance}); + } + // is it an array? it will be one of: + // an array of [name, params] - this defines a single anchor + // an array of arrays - this defines some dynamic anchors + // an array of numbers - this defines a single anchor. + else if (_isArray(specimen)) { + if (_isArray(specimen[0]) || _isString(specimen[0])) { + if (specimen.length == 2 && _isString(specimen[0]) && _isObject(specimen[1])) { + var pp = jsPlumb.extend({elementId:elementId, jsPlumbInstance:_currentInstance}, specimen[1]); + newAnchor = jsPlumb.Anchors[specimen[0]](pp); + } + else + newAnchor = new DynamicAnchor(specimen, null, elementId); + } + else { + var anchorParams = { + x:specimen[0], y:specimen[1], + orientation : (specimen.length >= 4) ? [ specimen[2], specimen[3] ] : [0,0], + offsets : (specimen.length == 6) ? [ specimen[4], specimen[5] ] : [ 0, 0 ], + elementId:elementId + }; + newAnchor = new Anchor(anchorParams); + newAnchor.clone = function() { return new Anchor(anchorParams); }; + } + } + + if (!newAnchor.id) newAnchor.id = "anchor_" + _idstamp(); + return newAnchor; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types, elementId, jsPlumbInstance) { + var r = []; + for ( var i = 0; i < types.length; i++) { + if (typeof types[i] == "string") + r.push(jsPlumb.Anchors[types[i]]({elementId:elementId, jsPlumbInstance:jsPlumbInstance})); + else if (_isArray(types[i])) + r.push(_currentInstance.makeAnchor(types[i], elementId, jsPlumbInstance)); + } + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /** + * Function: makeTarget + * Makes some DOM element a Connection target, allowing you to drag connections to it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a target. + * params - JS object containing parameters: + * - *endpoint* optional. specification of an Endpoint to create when a Connection is established. + * - *scope* optional. scope for the drop zone. + * - *dropOptions* optional. same stuff as you would pass to dropOptions of an Endpoint definition. + * - *deleteEndpointsOnDetach* optional, defaults to true. whether or not to delete + * any Endpoints created by a connection to this target if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * - *maxConnections* optional. Specifies the maximum number of Connections that can be made to this element as a target. Default is no limit. + * - *onMaxConnections* optional. Function to call when user attempts to drop a connection but the limit has been reached. + * The callback is passed two arguments: a JS object with: + * : { element, connection, maxConnection } + * ...and the original event. + */ + var _targetEndpointDefinitions = {}, + _targetEndpoints = {}, + _targetEndpointsUnique = {}, + _targetMaxConnections = {}, + _setEndpointPaintStylesAndAnchor = function(ep, epIndex) { + ep.paintStyle = ep.paintStyle || + _currentInstance.Defaults.EndpointStyles[epIndex] || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyles[epIndex] || + jsPlumb.Defaults.EndpointStyle; + ep.hoverPaintStyle = ep.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyles[epIndex] || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyles[epIndex] || + jsPlumb.Defaults.EndpointHoverStyle; + + ep.anchor = ep.anchor || + _currentInstance.Defaults.Anchors[epIndex] || + _currentInstance.Defaults.Anchor || + jsPlumb.Defaults.Anchors[epIndex] || + jsPlumb.Defaults.Anchor; + + ep.endpoint = ep.endpoint || + _currentInstance.Defaults.Endpoints[epIndex] || + _currentInstance.Defaults.Endpoint || + jsPlumb.Defaults.Endpoints[epIndex] || + jsPlumb.Defaults.Endpoint; + }; + + this.makeTarget = function(el, params, referenceParams) { + + var p = jsPlumb.extend({_jsPlumb:_currentInstance}, referenceParams); + jsPlumb.extend(p, params); + _setEndpointPaintStylesAndAnchor(p, 1); + var jpcl = jsPlumb.CurrentLibrary, + targetScope = p.scope || _currentInstance.Defaults.Scope, + deleteEndpointsOnDetach = !(p.deleteEndpointsOnDetach === false), + maxConnections = p.maxConnections || -1, + onMaxConnections = p.onMaxConnections; + _doOne = function(_el) { + + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el); + _targetEndpointDefinitions[elid] = p; + _targetEndpointsUnique[elid] = p.uniqueEndpoint, + _targetMaxConnections[elid] = maxConnections, + _targetsEnabled[elid] = true, + proxyComponent = new jsPlumbUIComponent(p); + + var dropOptions = jsPlumb.extend({}, p.dropOptions || {}), + _drop = function() { + + var originalEvent = jsPlumb.CurrentLibrary.getDropEvent(arguments), + targetCount = _currentInstance.select({target:elid}).length; + + _currentInstance.currentlyDragging = false; + var draggable = _getElementObject(jpcl.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + // restore the original scope if necessary (issue 57) + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id], + source = jpc.endpoints[0], + _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : {}; + + if (!_targetsEnabled[elid] || _targetMaxConnections[elid] > 0 && targetCount >= _targetMaxConnections[elid]){ + if (onMaxConnections) { + onMaxConnections({ + element:_el, + connection:jpc + }, originalEvent); + } + return false; + } + + // unlock the source anchor to allow it to refresh its position if necessary + source.anchor.locked = false; + + if (scope) jpcl.setDragScope(draggable, scope); + + // check if drop is allowed here. + //var _continue = jpc.isDropAllowed(jpc.sourceId, _getId(_el), jpc.scope); + var _continue = proxyComponent.isDropAllowed(jpc.sourceId, _getId(_el), jpc.scope, jpc, null); + + // regardless of whether the connection is ok, reconfigure the existing connection to + // point at the current info. we need this to be correct for the detach event that will follow. + // clear the source endpoint from the list to detach. we will detach this connection at this + // point, but we want to keep the source endpoint. the target is a floating endpoint and should + // be removed. TODO need to figure out whether this code can result in endpoints kicking around + // when they shouldnt be. like is this a full detach of a connection? can it be? + if (jpc.endpointsToDeleteOnDetach) { + if (source === jpc.endpointsToDeleteOnDetach[0]) + jpc.endpointsToDeleteOnDetach[0] = null; + else if (source === jpc.endpointsToDeleteOnDetach[1]) + jpc.endpointsToDeleteOnDetach[1] = null; + } + // reinstate any suspended endpoint; this just puts the connection back into + // a state in which it will report sensible values if someone asks it about + // its target. we're going to throw this connection away shortly so it doesnt matter + // if we manipulate it a bit. + if (jpc.suspendedEndpoint) { + jpc.targetId = jpc.suspendedEndpoint.elementId; + jpc.target = jpcl.getElementObject(jpc.suspendedEndpoint.elementId); + jpc.endpoints[1] = jpc.suspendedEndpoint; + } + + if (_continue) { + + // detach this connection from the source. + source.detach(jpc, false, true, false); + + // make a new Endpoint for the target + var newEndpoint = _targetEndpoints[elid] || _currentInstance.addEndpoint(_el, p); + if (p.uniqueEndpoint) _targetEndpoints[elid] = newEndpoint; // may of course just store what it just pulled out. that's ok. + newEndpoint._makeTargetCreator = true; + + // if the anchor has a 'positionFinder' set, then delegate to that function to find + // out where to locate the anchor. + if (newEndpoint.anchor.positionFinder != null) { + var dropPosition = jpcl.getUIPosition(arguments, _currentInstance.getZoom()), + elPosition = _getOffset(_el, _currentInstance), + elSize = _getSize(_el), + ap = newEndpoint.anchor.positionFinder(dropPosition, elPosition, elSize, newEndpoint.anchor.constructorParams); + newEndpoint.anchor.x = ap[0]; + newEndpoint.anchor.y = ap[1]; + // now figure an orientation for it..kind of hard to know what to do actually. probably the best thing i can do is to + // support specifying an orientation in the anchor's spec. if one is not supplied then i will make the orientation + // be what will cause the most natural link to the source: it will be pointing at the source, but it needs to be + // specified in one axis only, and so how to make that choice? i think i will use whichever axis is the one in which + // the target is furthest away from the source. + } + var c = _currentInstance.connect({ + source:source, + target:newEndpoint, + scope:scope, + previousConnection:jpc, + container:jpc.parent, + deleteEndpointsOnDetach:deleteEndpointsOnDetach, + // 'endpointWillMoveAfterConnection' is set by the makeSource function, and it indicates that the + // given endpoint will actually transfer from the element it is currently attached to to some other + // element after a connection has been established. in that case, we do not want to fire the + // connection event, since it will have the wrong data in it; makeSource will do it for us. + // this is controlled by the 'parent' parameter on a makeSource call. + doNotFireConnectionEvent:source.endpointWillMoveAfterConnection + }); + + // delete the original target endpoint. but only want to do this if the endpoint was created + // automatically and has no other connections. + if (jpc.endpoints[1]._makeTargetCreator && jpc.endpoints[1].connections.length < 2) + _currentInstance.deleteEndpoint(jpc.endpoints[1]); + + if (deleteEndpointsOnDetach) + c.endpointsToDeleteOnDetach = [ source, newEndpoint ]; + + c.repaint(); + } + // if not allowed to drop... + else { + // TODO this code is identical (pretty much) to what happens when a connection + // dragged from a normal endpoint is in this situation. refactor. + // is this an existing connection, and will we reattach? + if (jpc.suspendedEndpoint) { + //if (source.isReattach) { + if (jpc.isReattach()) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + _currentInstance.repaint(source.elementId); + } + else + source.detach(jpc, false, true, true, originalEvent); // otherwise, detach the connection and tell everyone about it. + } + + } + }; + + var dropEvent = jpcl.dragEvents['drop']; + dropOptions["scope"] = dropOptions["scope"] || targetScope; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop); + + jpcl.initDroppable(_el, dropOptions, true); + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + + return _currentInstance; + }; + + /* + * Function: unmakeTarget + * Sets the given element to no longer be a connection target. + * + * Parameters: + * el - a string id, a dom element, or a selector representing the element. + * + * Returns: + * The current jsPlumb instance. + */ + this.unmakeTarget = function(el, doNotClearArrays) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + var elid = _getId(el); + + // TODO this is not an exhaustive unmake of a target, since it does not remove the droppable stuff from + // the element. the effect will be to prevent it form behaving as a target, but it's not completely purged. + if (!doNotClearArrays) { + delete _targetEndpointDefinitions[elid]; + delete _targetEndpointsUnique[elid]; + delete _targetMaxConnections[elid]; + delete _targetsEnabled[elid]; + } + + return _currentInstance; + }; + + /* + * Function: makeTargets + * Makes all elements in some array or a selector connection targets. + * + * Parameters: + * els - either an array of ids or a selector + * params - parameters to configure each element as a target with + * referenceParams - extra parameters to configure each element as a taretsource with. + * + * Returns: + * The current jsPlumb instance. + */ + this.makeTargets = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeTarget(els[i], params, referenceParams); + } + }; + + /** + * Function: makeSource + * Makes some DOM element a Connection source, allowing you to drag connections from it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a source. + * params - JS object containing parameters: + * - *endpoint* optional. specification of an endpoint to create when a connection is created. + * - *parent* optional. the element to add Endpoints to when a Connection is established. if you omit this, + * Endpoints will be added to 'el'. + * - *scope* optional. scope for the connections dragged from this element. + * - *dragOptions* optional. same stuff as you would pass to dragOptions of an Endpoint definition. + * - *deleteEndpointsOnDetach* optional, defaults to false. whether or not to delete + * any Endpoints created by a connection from this source if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * - *filter* - optional function to call when the user presses the mouse button to start a drag. This function is passed the original + * event and the element on which the associated makeSource call was made. If it returns anything other than false, + * the drag begins as usual. But if it returns false (the boolean false, not just something falsey), the drag is aborted. + * + * + */ + var _sourceEndpointDefinitions = {}, + _sourceEndpoints = {}, + _sourceEndpointsUnique = {}, + _sourcesEnabled = {}, + _sourceTriggers = {}, + _sourceMaxConnections = {}, + _targetsEnabled = {}; + + this.makeSource = function(el, params, referenceParams) { + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + _setEndpointPaintStylesAndAnchor(p, 0); + var jpcl = jsPlumb.CurrentLibrary, + maxConnections = p.maxConnections || -1, + onMaxConnections = p.onMaxConnections, + _doOne = function(_el) { + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el), + parent = p.parent, + idToRegisterAgainst = parent != null ? _currentInstance.getId(jpcl.getElementObject(parent)) : elid; + + _sourceEndpointDefinitions[idToRegisterAgainst] = p; + _sourceEndpointsUnique[idToRegisterAgainst] = p.uniqueEndpoint; + _sourcesEnabled[idToRegisterAgainst] = true; + + var stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"], + dragOptions = jsPlumb.extend({ }, p.dragOptions || {}), + existingDrag = dragOptions.drag, + existingStop = dragOptions.stop, + ep = null, + endpointAddedButNoDragYet = false; + + _sourceMaxConnections[idToRegisterAgainst] = maxConnections; + + // set scope if its not set in dragOptions but was passed in in params + dragOptions["scope"] = dragOptions["scope"] || p.scope; + + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + if (existingDrag) existingDrag.apply(this, arguments); + endpointAddedButNoDragYet = false; + }); + + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], function() { + if (existingStop) existingStop.apply(this, arguments); + + //_currentlyDown = false; + _currentInstance.currentlyDragging = false; + + if (ep.connections.length == 0) + _currentInstance.deleteEndpoint(ep); + else { + + jpcl.unbind(ep.canvas, "mousedown"); + + // reset the anchor to the anchor that was initially provided. the one we were using to drag + // the connection was just a placeholder that was located at the place the user pressed the + // mouse button to initiate the drag. + var anchorDef = p.anchor || _currentInstance.Defaults.Anchor, + oldAnchor = ep.anchor, + oldConnection = ep.connections[0]; + + ep.anchor = _currentInstance.makeAnchor(anchorDef, elid, _currentInstance); + + if (p.parent) { + var parent = jpcl.getElementObject(p.parent); + if (parent) { + var currentId = ep.elementId; + + var potentialParent = p.container || _currentInstance.Defaults.Container || jsPlumb.Defaults.Container; + + ep.setElement(parent, potentialParent); + ep.endpointWillMoveAfterConnection = false; + _currentInstance.anchorManager.rehomeEndpoint(currentId, parent); + oldConnection.previousConnection = null; + // remove from connectionsByScope + _removeWithFunction(connectionsByScope[oldConnection.scope], function(c) { + return c.id === oldConnection.id; + }); + _currentInstance.anchorManager.connectionDetached({ + sourceId:oldConnection.sourceId, + targetId:oldConnection.targetId, + connection:oldConnection + }); + _finaliseConnection(oldConnection); + } + } + + ep.repaint(); + _currentInstance.repaint(ep.elementId); + _currentInstance.repaint(oldConnection.targetId); + + } + }); + // when the user presses the mouse, add an Endpoint, if we are enabled. + var mouseDownListener = function(e) { + + // if disabled, return. + if (!_sourcesEnabled[idToRegisterAgainst]) return; + + // if maxConnections reached + var sourceCount = _currentInstance.select({source:idToRegisterAgainst}).length + if (_sourceMaxConnections[idToRegisterAgainst] >= 0 && sourceCount >= _sourceMaxConnections[idToRegisterAgainst]) { + if (onMaxConnections) { + onMaxConnections({ + element:_el, + maxConnections:maxConnections + }, e); + } + return false; + } + + // if a filter was given, run it, and return if it says no. + if (params.filter) { + // pass the original event to the user: + var r = params.filter(jpcl.getOriginalEvent(e), _el); + if (r === false) return; + } + + // make sure we have the latest offset for this div + var myOffsetInfo = _updateOffset({elId:elid}); + + var x = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width, + y = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height, + parentX = x, + parentY = y; + + // if there is a parent, the endpoint will actually be added to it now, rather than the div + // that was the source. in that case, we have to adjust the anchor position so it refers to + // the parent. + if (p.parent) { + var pEl = jpcl.getElementObject(p.parent), + pId = _getId(pEl); + myOffsetInfo = _updateOffset({elId:pId}); + parentX = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width, + parentY = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height; + } + + // we need to override the anchor in here, and force 'isSource', but we don't want to mess with + // the params passed in, because after a connection is established we're going to reset the endpoint + // to have the anchor we were given. + var tempEndpointParams = {}; + jsPlumb.extend(tempEndpointParams, p); + tempEndpointParams.isSource = true; + tempEndpointParams.anchor = [x,y,0,0]; + tempEndpointParams.parentAnchor = [ parentX, parentY, 0, 0 ]; + tempEndpointParams.dragOptions = dragOptions; + // if a parent was given we need to turn that into a "container" argument. this is, by default, + // the parent of the element we will move to, so parent of p.parent in this case. however, if + // the user has specified a 'container' on the endpoint definition or on + // the defaults, we should use that. + if (p.parent) { + var potentialParent = tempEndpointParams.container || _currentInstance.Defaults.Container || jsPlumb.Defaults.Container; + if (potentialParent) + tempEndpointParams.container = potentialParent; + else + tempEndpointParams.container = jsPlumb.CurrentLibrary.getParent(p.parent); + } + + ep = _currentInstance.addEndpoint(elid, tempEndpointParams); + + endpointAddedButNoDragYet = true; + // we set this to prevent connections from firing attach events before this function has had a chance + // to move the endpoint. + ep.endpointWillMoveAfterConnection = p.parent != null; + ep.endpointWillMoveTo = p.parent ? jpcl.getElementObject(p.parent) : null; + + var _delTempEndpoint = function() { + // this mouseup event is fired only if no dragging occurred, by jquery and yui, but for mootools + // it is fired even if dragging has occurred, in which case we would blow away a perfectly + // legitimate endpoint, were it not for this check. the flag is set after adding an + // endpoint and cleared in a drag listener we set in the dragOptions above. + if(endpointAddedButNoDragYet) { + _currentInstance.deleteEndpoint(ep); + } + }; + + _currentInstance.registerListener(ep.canvas, "mouseup", _delTempEndpoint); + _currentInstance.registerListener(_el, "mouseup", _delTempEndpoint); + + // and then trigger its mousedown event, which will kick off a drag, which will start dragging + // a new connection from this endpoint. + jpcl.trigger(ep.canvas, "mousedown", e); + }; + + // register this on jsPlumb so that it can be cleared by a reset. + _currentInstance.registerListener(_el, "mousedown", mouseDownListener); + _sourceTriggers[elid] = mouseDownListener; + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + + return _currentInstance; + }; + + /** + * Function: unmakeSource + * Sets the given element to no longer be a connection source. + * + * Parameters: + * el - a string id, a dom element, or a selector representing the element. + * + * Returns: + * The current jsPlumb instance. + */ + this.unmakeSource = function(el, doNotClearArrays) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + var id = _getId(el), + mouseDownListener = _sourceTriggers[id]; + + if (mouseDownListener) + _currentInstance.unregisterListener(_el, "mousedown", mouseDownListener); + + if (!doNotClearArrays) { + delete _sourceEndpointDefinitions[id]; + delete _sourceEndpointsUnique[id]; + delete _sourcesEnabled[id]; + delete _sourceTriggers[id]; + delete _sourceMaxConnections[id]; + } + + return _currentInstance; + }; + + /* + * Function: unmakeEverySource + * Resets all elements in this instance of jsPlumb so that none of them are connection sources. + * + * Returns: + * The current jsPlumb instance. + */ + this.unmakeEverySource = function() { + for (var i in _sourcesEnabled) + _currentInstance.unmakeSource(i, true); + + _sourceEndpointDefinitions = {}; + _sourceEndpointsUnique = {}; + _sourcesEnabled = {}; + _sourceTriggers = {}; + }; + + /* + * Function: unmakeEveryTarget + * Resets all elements in this instance of jsPlumb so that none of them are connection targets. + * + * Returns: + * The current jsPlumb instance. + */ + this.unmakeEveryTarget = function() { + for (var i in _targetsEnabled) + _currentInstance.unmakeTarget(i, true); + + _targetEndpointDefinitions = {}; + _targetEndpointsUnique = {}; + _targetMaxConnections = {}; + _targetsEnabled = {}; + + return _currentInstance; + }; + + /* + * Function: makeSources + * Makes all elements in some array or a selector connection sources. + * + * Parameters: + * els - either an array of ids or a selector + * params - parameters to configure each element as a source with + * referenceParams - extra parameters to configure each element as a source with. + * + * Returns: + * The current jsPlumb instance. + */ + this.makeSources = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeSource(els[i], params, referenceParams); + } + + return _currentInstance; + }; + + // does the work of setting a source enabled or disabled. + var _setEnabled = function(type, el, state, toggle) { + var a = type == "source" ? _sourcesEnabled : _targetsEnabled; + + if (_isString(el)) a[el] = toggle ? !a[el] : state; + else if (el.length) { + el = _convertYUICollection(el); + for (var i = 0; i < el.length; i++) { + var id = _el = jsPlumb.CurrentLibrary.getElementObject(el[i]), id = _getId(_el); + a[id] = toggle ? !a[id] : state; + } + } + return _currentInstance; + }; + + /* + * Function: setSourceEnabled + * Sets the enabled state of one or more elements that were previously made a connection source with the makeSource + * method. + * + * Parameters: + * el - either a string representing some element's id, or an array of ids, or a selector. + * state - true to enable the element(s), false to disable it. + * + * Returns: + * The current jsPlumb instance. + */ + this.setSourceEnabled = function(el, state) { + return _setEnabled("source", el, state); + }; + + /* + * Function: toggleSourceEnabled + * Toggles the source enabled state of the given element or elements. + * + * Parameters: + * el - either a string representing some element's id, or an array of ids, or a selector. + * state - true to enable the element(s), false to disable it. + * + * Returns: + * The current enabled state of the source. + */ + this.toggleSourceEnabled = function(el) { + _setEnabled("source", el, null, true); + return _currentInstance.isSourceEnabled(el); + }; + + /* + * Function: isSource + * Returns whether or not the given element is registered as a connection source. + * + * Parameters: + * el - a string id, a dom element, or a selector representing a single element. + * + * Returns: + * True if source, false if not. + */ + this.isSource = function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return _sourcesEnabled[_getId(el)] != null; + }; + + /* + * Function: isSourceEnabled + * Returns whether or not the given connection source is enabled. + * + * Parameters: + * el - a string id, a dom element, or a selector representing a single element. + * + * Returns: + * True if enabled, false if not. + */ + this.isSourceEnabled = function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return _sourcesEnabled[_getId(el)] === true; + }; + + /* + * Function: setTargetEnabled + * Sets the enabled state of one or more elements that were previously made a connection target with the makeTarget method. + * method. + * + * Parameters: + * el - either a string representing some element's id, or an array of ids, or a selector. + * state - true to enable the element(s), false to disable it. + * + * Returns: + * The current jsPlumb instance. + */ + this.setTargetEnabled = function(el, state) { + return _setEnabled("target", el, state); + }; + + /* + * Function: toggleTargetEnabled + * Toggles the target enabled state of the given element or elements. + * + * Parameters: + * el - either a string representing some element's id, or an array of ids, or a selector. + * state - true to enable the element(s), false to disable it. + * + * Returns: + * The current enabled state of the target. + */ + this.toggleTargetEnabled = function(el) { + return _setEnabled("target", el, null, true); + return _currentInstance.isTargetEnabled(el); + }; + + /* + Function: isTarget + Returns whether or not the given element is registered as a connection target. + + Parameters: + el - a string id, a dom element, or a selector representing a single element. + + Returns: + True if source, false if not. + */ + this.isTarget = function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return _targetsEnabled[_getId(el)] != null; + }; + + /* + Function: isTargetEnabled + Returns whether or not the given connection target is enabled. + + Parameters: + el - a string id, a dom element, or a selector representing a single element. + + Returns: + True if enabled, false if not. + */ + this.isTargetEnabled = function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return _targetsEnabled[_getId(el)] === true; + }; + + /* + * Function: ready + * Helper method to bind a function to jsPlumb's ready event. You should use this method instead of your + * library's equivalent, to ensure that jsPlumb has loaded properly before you start to use it. This is + * particularly true in the case of YUI, because of the asynchronous nature of the module loading process. + */ + this.ready = function(fn) { + _currentInstance.bind("ready", fn); + }; + + /* + * Function: repaint + * Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + * + * Parameters: + * el - id of the element, a dom element, or a selector representing the element. + * + * Returns: + * The current jsPlumb instance. + * + * See Also: + * + */ + this.repaint = function(el, ui, timestamp) { + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) { + _draw(_getElementObject(el[i]), ui, timestamp); + } + else // ...and single strings. + _draw(_getElementObject(el), ui, timestamp); + + return _currentInstance; + }; + + /* + * Function: repaintEverything + * Repaints all connections. + * + * Returns: + * The current jsPlumb instance. + * + * See Also: + * + */ + this.repaintEverything = function() { + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId)); + } + return _currentInstance; + }; + + /* + * Function: removeAllEndpoints + * Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + * + * Parameters: + * el - either an element id, or a selector for an element. + * + * Returns: + * The current jsPlumb instance. + * + * See Also: + * + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"), + ebe = endpointsByElement[elId]; + if (ebe) { + for ( var i = 0; i < ebe.length; i++) + _currentInstance.deleteEndpoint(ebe[i]); + } + endpointsByElement[elId] = []; + return _currentInstance; + }; + + + var _registeredListeners = {}, + _unbindRegisteredListeners = function() { + for (var i in _registeredListeners) { + for (var j = 0; j < _registeredListeners[i].length; j++) { + var info = _registeredListeners[i][j]; + jsPlumb.CurrentLibrary.unbind(info.el, info.event, info.listener); + } + } + _registeredListeners = {}; + }; + + // internal register listener method. gives us a hook to clean things up + // with if the user calls jsPlumb.reset. + this.registerListener = function(el, type, listener) { + jsPlumb.CurrentLibrary.bind(el, type, listener); + _addToList(_registeredListeners, type, {el:el, event:type, listener:listener}); + }; + + this.unregisterListener = function(el, type, listener) { + jsPlumb.CurrentLibrary.unbind(el, type, listener); + _removeWithFunction(_registeredListeners, function(rl) { + return rl.type == type && rl.listener == listener; + }); + }; + + /* + * Function:reset + * Removes all endpoints and connections and clears the listener list. To keep listeners call + * : jsPlumb.deleteEveryEndpoint() + * instead of this. + */ + this.reset = function() { + _currentInstance.deleteEveryEndpoint(); + _currentInstance.unbind(); + _targetEndpointDefinitions = {}; + _targetEndpoints = {}; + _targetEndpointsUnique = {}; + _targetMaxConnections = {}; + _sourceEndpointDefinitions = {}; + _sourceEndpoints = {}; + _sourceEndpointsUnique = {}; + _sourceMaxConnections = {}; + _unbindRegisteredListeners(); + _currentInstance.anchorManager.reset(); + if (!jsPlumbAdapter.headless) + _currentInstance.dragManager.reset(); + }; + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + * + * Returns: + * The current jsPlumb instance. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + return _currentInstance; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * draggable - whether or not the element should be draggable. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setId + * Changes the id of some element, adjusting all connections and endpoints + * + * Parameters: + * el - a selector, a DOM element, or a string. + * newId - string. + */ + this.setId = function(el, newId, doNotSetAttribute) { + + var id = el.constructor == String ? el : _currentInstance.getId(el), + sConns = _currentInstance.getConnections({source:id, scope:'*'}, true), + tConns = _currentInstance.getConnections({target:id, scope:'*'}, true); + + newId = "" + newId; + + if (!doNotSetAttribute) { + el = jsPlumb.CurrentLibrary.getElementObject(id); + jsPlumb.CurrentLibrary.setAttribute(el, "id", newId); + } + + el = jsPlumb.CurrentLibrary.getElementObject(newId); + + + endpointsByElement[newId] = endpointsByElement[id] || []; + for (var i = 0; i < endpointsByElement[newId].length; i++) { + endpointsByElement[newId][i].elementId = newId; + endpointsByElement[newId][i].element = el; + endpointsByElement[newId][i].anchor.elementId = newId; + } + delete endpointsByElement[id]; + + _currentInstance.anchorManager.changeId(id, newId); + + var _conns = function(list, epIdx, type) { + for (var i = 0; i < list.length; i++) { + list[i].endpoints[epIdx].elementId = newId; + list[i].endpoints[epIdx].element = el; + list[i][type + "Id"] = newId; + list[i][type] = el; + } + }; + _conns(sConns, 0, "source"); + _conns(tConns, 1, "target"); + }; + + /* + * Function: setIdChanged + * Notify jsPlumb that the element with oldId has had its id changed to newId. + * + * This method is equivalent to what jsPlumb does itself in the second step of the setId method above. + * + * Parameters: + * oldId - previous element id + * newId - element's new id + * + * See Also: + * + */ + this.setIdChanged = function(oldId, newId) { + _currentInstance.setId(oldId, newId, true); + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + + var _suspendDrawing = false, + _suspendedAt = null; + /* + * Function: setSuspendDrawing + * Suspends drawing operations. This can (and should!) be used when you have a lot of connections to make or endpoints to register; + * it will save you a lot of time. + * + * Parameters: + * val - boolean indicating whether to suspend or not + * repaintAfterwards - optional boolean instructing jsPlumb to do a full repaint after changing the suspension + * state. defaults to false. + */ + this.setSuspendDrawing = function(val, repaintAfterwards) { + _suspendDrawing = val; + if (val) _suspendedAt = new Date().getTime(); else _suspendedAt = null; + if (repaintAfterwards) _currentInstance.repaintEverything(); + }; + + /* + * Function: isSuspendDrawing + * Returns whether or not drawing is currently suspended. + */ + this.isSuspendDrawing = function() { + return _suspendDrawing; + }; + + /* + * Property: CANVAS + * Constant for use with the setRenderMode method + */ + this.CANVAS = "canvas"; + + /* + * Property: SVG + * Constant for use with the setRenderMode method + */ + this.SVG = "svg"; + + /* + * Property: VML + * Constant for use with the setRenderMode method + */ + this.VML = "vml"; + + /* + * Function: setRenderMode + * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that + * what you asked for is not supported (and that VML is). If you asked for VML but the browser does + * not support it, jsPlumb uses SVG. + * + * Parameters: + * mode - a string representing the mode. Use one of the jsPlumb render mode constants as discussed above. + * + * Returns: + * The render mode that jsPlumb set, which of course may be different from that requested. + */ + this.setRenderMode = function(mode) { + renderMode = jsPlumbAdapter.setRenderMode(mode); + return renderMode; + }; + + /* + * Function: getRenderMode + * + * Returns: + * The current render mode. + */ + this.getRenderMode = function() { return renderMode; }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * changeEndpoints - whether or not to also change the visible state of the endpoints on the element. this also has a bearing on + * other connections on those endpoints: if their other endpoint is also visible, the connections are made visible. + * + * Returns: + * The current jsPlumb instance. + */ + this.show = function(el, changeEndpoints) { + _setVisible(el, "block", changeEndpoints); + return _currentInstance; + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * The current jsPlumb instance + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + return _currentInstance; + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + //findIndex : _findIndex, + getId : _getId, + makeAnchor:self.makeAnchor, + makeDynamicAnchor:self.makeDynamicAnchor + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * changeEndpoints - whether or not to also toggle the endpoints on the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + var adjustForParentOffsetAndScroll = function(xy, el) { + + var offsetParent = null, result = xy; + if (el.tagName.toLowerCase() === "svg" && el.parentNode) { + offsetParent = el.parentNode; + } + else if (el.offsetParent) { + offsetParent = el.offsetParent; + } + if (offsetParent != null) { + var po = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(offsetParent, _currentInstance), + so = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : {left:offsetParent.scrollLeft, top:offsetParent.scrollTop}; + + + // i thought it might be cool to do this: + // lastReturnValue[0] = lastReturnValue[0] - offsetParent.offsetLeft + offsetParent.scrollLeft; + // lastReturnValue[1] = lastReturnValue[1] - offsetParent.offsetTop + offsetParent.scrollTop; + // but i think it ignores margins. my reasoning was that it's quicker to not hand off to some underlying + // library. + + result[0] = xy[0] - po.left + so.left; + result[1] = xy[1] - po.top + so.top; + } + + return result; + + }; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + this.elementId = params.elementId; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + + if (timestamp && timestamp === self.timestamp) + return lastReturnValue; + + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + + // adjust loc if there is an offsetParent + lastReturnValue = adjustForParentOffsetAndScroll(lastReturnValue, element.canvas); + + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function(_endpoint) { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + * + * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference, + // the canvas this refers to. + refCanvas = params.referenceCanvas, + size = _getSize(_getElementObject(refCanvas)), + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + xDir = 0, yDir = 0, + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + orientation = null, + _lastResult = null; + + // set these to 0 each; they are used by certain types of connectors in the loopback case, + // when the connector is trying to clear the element it is on. but for floating anchor it's not + // very important. + this.x = 0; this.y = 0; + + this.isFloating = true; + + this.compute = function(params) { + var xy = params.xy, element = params.element, + result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + + // adjust loc if there is an offsetParent + result = adjustForParentOffsetAndScroll(result, element.canvas); + + _lastResult = result; + return result; + }; + + this.getOrientation = function(_endpoint) { + if (orientation) return orientation; + else { + var o = ref.getOrientation(_endpoint); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchor is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector, elementId) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = [], self = this, + _convert = function(anchor) { + return anchor.constructor == Anchor ? anchor: _currentInstance.makeAnchor(anchor, elementId, _currentInstance); + }; + for (var i = 0; i < anchors.length; i++) + _anchors[i] = _convert(anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + this.locked = false; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null, + _curIndex = _anchors.length > 0 ? 0 : -1, + self = this, + + // helper method to calculate the distance between the centers of the two elements. + _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]), + acx = xy[0] + (wh[0] / 2), acy = xy[1] + (wh[1] / 2); + return (Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)) + + Math.sqrt(Math.pow(acx - ax, 2) + Math.pow(acy - ay, 2))); + }, + + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + self.x = _curAnchor.x; + self.y = _curAnchor.y; + + return _curAnchor.compute(params); + }; + + this.getCurrentLocation = function() { + return _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + }; + + this.getOrientation = function(_endpoint) { return _curAnchor != null ? _curAnchor.getOrientation(_endpoint) : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + manages anchors for all elements. + */ + // "continuous" anchors: anchors that pick their location based on how many connections the given element has. + // this requires looking at a lot more elements than normal - anything that has had a Continuous anchor applied has + // to be recalculated. so this manager is used as a reference point. the first time, with a new timestamp, that + // a continuous anchor is asked to compute, it calls this guy. or maybe, even, this guy gets called somewhere else + // and compute only ever returns pre-computed values. either way, this is the central point, and we want it to + // be called as few times as possible. + var continuousAnchors = {}, + continuousAnchorLocations = {}, + continuousAnchorOrientations = {}, + Orientation = { HORIZONTAL : "horizontal", VERTICAL : "vertical", DIAGONAL : "diagonal", IDENTITY:"identity" }, + + // TODO this functions uses a crude method of determining orientation between two elements. + // 'diagonal' should be chosen when the angle of the line between the two centers is around + // one of 45, 135, 225 and 315 degrees. maybe +- 15 degrees. + calculateOrientation = function(sourceId, targetId, sd, td) { + + if (sourceId === targetId) return { + orientation:Orientation.IDENTITY, + a:["top", "top"] + }; + + var theta = Math.atan2((td.centery - sd.centery) , (td.centerx - sd.centerx)), + theta2 = Math.atan2((sd.centery - td.centery) , (sd.centerx - td.centerx)), + h = ((sd.left <= td.left && sd.right >= td.left) || (sd.left <= td.right && sd.right >= td.right) || + (sd.left <= td.left && sd.right >= td.right) || (td.left <= sd.left && td.right >= sd.right)), + v = ((sd.top <= td.top && sd.bottom >= td.top) || (sd.top <= td.bottom && sd.bottom >= td.bottom) || + (sd.top <= td.top && sd.bottom >= td.bottom) || (td.top <= sd.top && td.bottom >= sd.bottom)); + + if (! (h || v)) { + var a = null, rls = false, rrs = false, sortValue = null; + if (td.left > sd.left && td.top > sd.top) + a = ["right", "top"]; + else if (td.left > sd.left && sd.top > td.top) + a = [ "top", "left"]; + else if (td.left < sd.left && td.top < sd.top) + a = [ "top", "right"]; + else if (td.left < sd.left && td.top > sd.top) + a = ["left", "top" ]; + + return { orientation:Orientation.DIAGONAL, a:a, theta:theta, theta2:theta2 }; + } + else if (h) return { + orientation:Orientation.HORIZONTAL, + a:sd.top < td.top ? ["bottom", "top"] : ["top", "bottom"], + theta:theta, theta2:theta2 + } + else return { + orientation:Orientation.VERTICAL, + a:sd.left < td.left ? ["right", "left"] : ["left", "right"], + theta:theta, theta2:theta2 + } + }, + placeAnchorsOnLine = function(desc, elementDimensions, elementPosition, + connections, horizontal, otherMultiplier, reverse) { + var a = [], step = elementDimensions[horizontal ? 0 : 1] / (connections.length + 1); + + for (var i = 0; i < connections.length; i++) { + var val = (i + 1) * step, other = otherMultiplier * elementDimensions[horizontal ? 1 : 0]; + if (reverse) + val = elementDimensions[horizontal ? 0 : 1] - val; + + var dx = (horizontal ? val : other), x = elementPosition[0] + dx, xp = dx / elementDimensions[0], + dy = (horizontal ? other : val), y = elementPosition[1] + dy, yp = dy / elementDimensions[1]; + + a.push([ x, y, xp, yp, connections[i][1], connections[i][2] ]); + } + + return a; + }, + standardEdgeSort = function(a, b) { return a[0] > b[0] ? 1 : -1 }, + currySort = function(reverseAngles) { + return function(a,b) { + var r = true; + if (reverseAngles) { + if (a[0][0] < b[0][0]) + r = true; + else + r = a[0][1] > b[0][1]; + } + else { + if (a[0][0] > b[0][0]) + r= true; + else + r =a[0][1] > b[0][1]; + } + return r === false ? -1 : 1; + }; + }, + leftSort = function(a,b) { + // first get adjusted values + var p1 = a[0][0] < 0 ? -Math.PI - a[0][0] : Math.PI - a[0][0], + p2 = b[0][0] < 0 ? -Math.PI - b[0][0] : Math.PI - b[0][0]; + if (p1 > p2) return 1; + else return a[0][1] > b[0][1] ? 1 : -1; + }, + edgeSortFunctions = { + "top":standardEdgeSort, + "right":currySort(true), + "bottom":currySort(true), + "left":leftSort + }, + _sortHelper = function(_array, _fn) { + return _array.sort(_fn); + }, + placeAnchors = function(elementId, _anchorLists) { + var sS = sizes[elementId], sO = offsets[elementId], + placeSomeAnchors = function(desc, elementDimensions, elementPosition, unsortedConnections, isHorizontal, otherMultiplier, orientation) { + if (unsortedConnections.length > 0) { + var sc = _sortHelper(unsortedConnections, edgeSortFunctions[desc]), // puts them in order based on the target element's pos on screen + reverse = desc === "right" || desc === "top", + anchors = placeAnchorsOnLine(desc, elementDimensions, + elementPosition, sc, + isHorizontal, otherMultiplier, reverse ); + + // takes a computed anchor position and adjusts it for parent offset and scroll, then stores it. + var _setAnchorLocation = function(endpoint, anchorPos) { + var a = adjustForParentOffsetAndScroll([anchorPos[0], anchorPos[1]], endpoint.canvas); + continuousAnchorLocations[endpoint.id] = [ a[0], a[1], anchorPos[2], anchorPos[3] ]; + continuousAnchorOrientations[endpoint.id] = orientation; + }; + + for (var i = 0; i < anchors.length; i++) { + var c = anchors[i][4], weAreSource = c.endpoints[0].elementId === elementId, weAreTarget = c.endpoints[1].elementId === elementId; + if (weAreSource) + _setAnchorLocation(c.endpoints[0], anchors[i]); + else if (weAreTarget) + _setAnchorLocation(c.endpoints[1], anchors[i]); + } + } + }; + + placeSomeAnchors("bottom", sS, [sO.left,sO.top], _anchorLists.bottom, true, 1, [0,1]); + placeSomeAnchors("top", sS, [sO.left,sO.top], _anchorLists.top, true, 0, [0,-1]); + placeSomeAnchors("left", sS, [sO.left,sO.top], _anchorLists.left, false, 0, [-1,0]); + placeSomeAnchors("right", sS, [sO.left,sO.top], _anchorLists.right, false, 1, [1,0]); + }, + AnchorManager = function() { + var _amEndpoints = {}, + connectionsByElementId = {}, + self = this, + anchorLists = {}; + + this.reset = function() { + _amEndpoints = {}; + connectionsByElementId = {}; + anchorLists = {}; + }; + this.newConnection = function(conn) { + var sourceId = conn.sourceId, targetId = conn.targetId, + ep = conn.endpoints, + doRegisterTarget = true, + registerConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + + if ((sourceId == targetId) && otherAnchor.isContinuous){ + // remove the target endpoint's canvas. we dont need it. + jsPlumb.CurrentLibrary.removeElement(ep[1].canvas); + doRegisterTarget = false; + } + _addToList(connectionsByElementId, elId, [c, otherEndpoint, otherAnchor.constructor == DynamicAnchor]); + }; + + registerConnection(0, ep[0], ep[0].anchor, targetId, conn); + if (doRegisterTarget) + registerConnection(1, ep[1], ep[1].anchor, sourceId, conn); + }; + this.connectionDetached = function(connInfo) { + var connection = connInfo.connection || connInfo; + var sourceId = connection.sourceId, + targetId = connection.targetId, + ep = connection.endpoints, + removeConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + if (otherAnchor.constructor == FloatingAnchor) { + // no-op + } + else { + _removeWithFunction(connectionsByElementId[elId], function(_c) { + return _c[0].id == c.id; + }); + } + }; + + removeConnection(1, ep[1], ep[1].anchor, sourceId, connection); + removeConnection(0, ep[0], ep[0].anchor, targetId, connection); + + // remove from anchorLists + var sEl = connection.sourceId, + tEl = connection.targetId, + sE = connection.endpoints[0].id, + tE = connection.endpoints[1].id, + _remove = function(list, eId) { + if (list) { // transient anchors dont get entries in this list. + var f = function(e) { return e[4] == eId; }; + _removeWithFunction(list["top"], f); + _removeWithFunction(list["left"], f); + _removeWithFunction(list["bottom"], f); + _removeWithFunction(list["right"], f); + } + }; + + _remove(anchorLists[sEl], sE); + _remove(anchorLists[tEl], tE); + self.redraw(sEl); + self.redraw(tEl); + }; + this.add = function(endpoint, elementId) { + _addToList(_amEndpoints, elementId, endpoint); + }; + this.changeId = function(oldId, newId) { + connectionsByElementId[newId] = connectionsByElementId[oldId]; + _amEndpoints[newId] = _amEndpoints[oldId]; + delete connectionsByElementId[oldId]; + delete _amEndpoints[oldId]; + }; + this.getConnectionsFor = function(elementId) { + return connectionsByElementId[elementId] || []; + }; + this.getEndpointsFor = function(elementId) { + return _amEndpoints[elementId] || []; + }; + this.deleteEndpoint = function(endpoint) { + _removeWithFunction(_amEndpoints[endpoint.elementId], function(e) { + return e.id == endpoint.id; + }); + }; + this.clearFor = function(elementId) { + delete _amEndpoints[elementId]; + _amEndpoints[elementId] = []; + }; + // updates the given anchor list by either updating an existing anchor's info, or adding it. this function + // also removes the anchor from its previous list, if the edge it is on has changed. + // all connections found along the way (those that are connected to one of the faces this function + // operates on) are added to the connsToPaint list, as are their endpoints. in this way we know to repaint + // them wthout having to calculate anything else about them. + var _updateAnchorList = function(lists, theta, order, conn, aBoolean, otherElId, idx, reverse, edgeId, elId, connsToPaint, endpointsToPaint) { + // first try to find the exact match, but keep track of the first index of a matching element id along the way.s + var exactIdx = -1, + firstMatchingElIdx = -1, + endpoint = conn.endpoints[idx], + endpointId = endpoint.id, + oIdx = [1,0][idx], + values = [ [ theta, order ], conn, aBoolean, otherElId, endpointId ], + listToAddTo = lists[edgeId], + listToRemoveFrom = endpoint._continuousAnchorEdge ? lists[endpoint._continuousAnchorEdge] : null; + + if (listToRemoveFrom) { + var rIdx = _findWithFunction(listToRemoveFrom, function(e) { return e[4] == endpointId }); + if (rIdx != -1) { + listToRemoveFrom.splice(rIdx, 1); + // get all connections from this list + for (var i = 0; i < listToRemoveFrom.length; i++) { + _addWithFunction(connsToPaint, listToRemoveFrom[i][1], function(c) { return c.id == listToRemoveFrom[i][1].id }); + _addWithFunction(endpointsToPaint, listToRemoveFrom[i][1].endpoints[idx], function(e) { return e.id == listToRemoveFrom[i][1].endpoints[idx].id }); + } + } + } + + for (var i = 0; i < listToAddTo.length; i++) { + if (idx == 1 && listToAddTo[i][3] === otherElId && firstMatchingElIdx == -1) + firstMatchingElIdx = i; + _addWithFunction(connsToPaint, listToAddTo[i][1], function(c) { return c.id == listToAddTo[i][1].id }); + _addWithFunction(endpointsToPaint, listToAddTo[i][1].endpoints[idx], function(e) { return e.id == listToAddTo[i][1].endpoints[idx].id }); + } + if (exactIdx != -1) { + listToAddTo[exactIdx] = values; + } + else { + var insertIdx = reverse ? firstMatchingElIdx != -1 ? firstMatchingElIdx : 0 : listToAddTo.length; // of course we will get this from having looked through the array shortly. + listToAddTo.splice(insertIdx, 0, values); + } + + // store this for next time. + endpoint._continuousAnchorEdge = edgeId; + }; + this.redraw = function(elementId, ui, timestamp, offsetToUI) { + + if (!_suspendDrawing) { + // get all the endpoints for this element + var ep = _amEndpoints[elementId] || [], + endpointConnections = connectionsByElementId[elementId] || [], + connectionsToPaint = [], + endpointsToPaint = [], + anchorsToUpdate = []; + + timestamp = timestamp || _timestamp(); + // offsetToUI are values that would have been calculated in the dragManager when registering + // an endpoint for an element that had a parent (somewhere in the hierarchy) that had been + // registered as draggable. + offsetToUI = offsetToUI || {left:0, top:0}; + if (ui) { + ui = { + left:ui.left + offsetToUI.left, + top:ui.top + offsetToUI.top + } + } + + _updateOffset( { elId : elementId, offset : ui, recalc : false, timestamp : timestamp }); + // valid for one paint cycle. + var myOffset = offsets[elementId], + myWH = sizes[elementId], + orientationCache = {}; + + // actually, first we should compute the orientation of this element to all other elements to which + // this element is connected with a continuous anchor (whether both ends of the connection have + // a continuous anchor or just one) + //for (var i = 0; i < continuousAnchorConnections.length; i++) { + for (var i = 0; i < endpointConnections.length; i++) { + var conn = endpointConnections[i][0], + sourceId = conn.sourceId, + targetId = conn.targetId, + sourceContinuous = conn.endpoints[0].anchor.isContinuous, + targetContinuous = conn.endpoints[1].anchor.isContinuous; + + if (sourceContinuous || targetContinuous) { + var oKey = sourceId + "_" + targetId, + oKey2 = targetId + "_" + sourceId, + o = orientationCache[oKey], + oIdx = conn.sourceId == elementId ? 1 : 0; + + if (sourceContinuous && !anchorLists[sourceId]) anchorLists[sourceId] = { top:[], right:[], bottom:[], left:[] }; + if (targetContinuous && !anchorLists[targetId]) anchorLists[targetId] = { top:[], right:[], bottom:[], left:[] }; + + if (elementId != targetId) _updateOffset( { elId : targetId, timestamp : timestamp }); + if (elementId != sourceId) _updateOffset( { elId : sourceId, timestamp : timestamp }); + + var td = _getCachedData(targetId), + sd = _getCachedData(sourceId); + + if (targetId == sourceId && (sourceContinuous || targetContinuous)) { + // here we may want to improve this by somehow determining the face we'd like + // to put the connector on. ideally, when drawing, the face should be calculated + // by determining which face is closest to the point at which the mouse button + // was released. for now, we're putting it on the top face. + _updateAnchorList(anchorLists[sourceId], -Math.PI / 2, 0, conn, false, targetId, 0, false, "top", sourceId, connectionsToPaint, endpointsToPaint) + } + else { + if (!o) { + o = calculateOrientation(sourceId, targetId, sd.o, td.o); + orientationCache[oKey] = o; + // this would be a performance enhancement, but the computed angles need to be clamped to + //the (-PI/2 -> PI/2) range in order for the sorting to work properly. + /* orientationCache[oKey2] = { + orientation:o.orientation, + a:[o.a[1], o.a[0]], + theta:o.theta + Math.PI, + theta2:o.theta2 + Math.PI + };*/ + } + if (sourceContinuous) _updateAnchorList(anchorLists[sourceId], o.theta, 0, conn, false, targetId, 0, false, o.a[0], sourceId, connectionsToPaint, endpointsToPaint); + if (targetContinuous) _updateAnchorList(anchorLists[targetId], o.theta2, -1, conn, true, sourceId, 1, true, o.a[1], targetId, connectionsToPaint, endpointsToPaint); + } + + if (sourceContinuous) _addWithFunction(anchorsToUpdate, sourceId, function(a) { return a === sourceId; }); + if (targetContinuous) _addWithFunction(anchorsToUpdate, targetId, function(a) { return a === targetId; }); + _addWithFunction(connectionsToPaint, conn, function(c) { return c.id == conn.id; }); + if ((sourceContinuous && oIdx == 0) || (targetContinuous && oIdx == 1)) + _addWithFunction(endpointsToPaint, conn.endpoints[oIdx], function(e) { return e.id == conn.endpoints[oIdx].id; }); + } + } + + // place Endpoints whose anchors are continuous but have no Connections + for (var i = 0; i < ep.length; i++) { + if (ep[i].connections.length == 0 && ep[i].anchor.isContinuous) { + if (!anchorLists[elementId]) anchorLists[elementId] = { top:[], right:[], bottom:[], left:[] }; + _updateAnchorList(anchorLists[elementId], -Math.PI / 2, 0, {endpoints:[ep[i], ep[i]], paint:function(){}}, false, elementId, 0, false, "top", elementId, connectionsToPaint, endpointsToPaint) + _addWithFunction(anchorsToUpdate, elementId, function(a) { return a === elementId; }) + } + } + + // now place all the continuous anchors we need to; + for (var i = 0; i < anchorsToUpdate.length; i++) { + placeAnchors(anchorsToUpdate[i], anchorLists[anchorsToUpdate[i]]); + } + + // now that continuous anchors have been placed, paint all the endpoints for this element + // TODO performance: add the endpoint ids to a temp array, and then when iterating in the next + // loop, check that we didn't just paint that endpoint. we can probably shave off a few more milliseconds this way. + for (var i = 0; i < ep.length; i++) { + ep[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + // ... and any other endpoints we came across as a result of the continuous anchors. + for (var i = 0; i < endpointsToPaint.length; i++) { + endpointsToPaint[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + + // paint all the standard and "dynamic connections", which are connections whose other anchor is + // static and therefore does need to be recomputed; we make sure that happens only one time. + + // TODO we could have compiled a list of these in the first pass through connections; might save some time. + for (var i = 0; i < endpointConnections.length; i++) { + var otherEndpoint = endpointConnections[i][1]; + if (otherEndpoint.anchor.constructor == DynamicAnchor) { + otherEndpoint.paint({ elementWithPrecedence:elementId }); + _addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; }); + // all the connections for the other endpoint now need to be repainted + for (var k = 0; k < otherEndpoint.connections.length; k++) { + if (otherEndpoint.connections[k] !== endpointConnections[i][0]) + _addWithFunction(connectionsToPaint, otherEndpoint.connections[k], function(c) { return c.id == otherEndpoint.connections[k].id; }); + } + } else if (otherEndpoint.anchor.constructor == Anchor) { + _addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; }); + } + } + // paint current floating connection for this element, if there is one. + var fc = floatingConnections[elementId]; + if (fc) + fc.paint({timestamp:timestamp, recalc:false, elId:elementId}); + + // paint all the connections + for (var i = 0; i < connectionsToPaint.length; i++) { + connectionsToPaint[i].paint({elId:elementId, timestamp:timestamp, recalc:false}); + } + } + }; + this.rehomeEndpoint = function(currentId, element) { + var eps = _amEndpoints[currentId] || [], //, + elementId = _currentInstance.getId(element); + if (elementId !== currentId) { + for (var i = 0; i < eps.length; i++) { + self.add(eps[i], elementId); + } + eps.splice(0, eps.length); + } + }; + }; + _currentInstance.anchorManager = new AnchorManager(); + _currentInstance.continuousAnchorFactory = { + get:function(params) { + var existing = continuousAnchors[params.elementId]; + if (!existing) { + existing = { + type:"Continuous", + compute : function(params) { + return continuousAnchorLocations[params.element.id] || [0,0]; + }, + getCurrentLocation : function(endpoint) { + return continuousAnchorLocations[endpoint.id] || [0,0]; + }, + getOrientation : function(endpoint) { + return continuousAnchorOrientations[endpoint.id] || [0,0]; + }, + isDynamic : true, + isContinuous : true + }; + continuousAnchors[params.elementId] = existing; + } + return existing; + } + }; + + if (!jsPlumbAdapter.headless) + _currentInstance.dragManager = jsPlumbAdapter.getDragManager(_currentInstance); + _currentInstance.recalculateOffsets = _currentInstance.dragManager.updateOffsets; + + /* + * Function: recalculateOffsets + * Recalculates the offsets of all child elements of some element. If you have Endpoints registered on the + * descendants of some element and you make changes to that element's markup, it is possible that the location + * of each Endpooint relative to the origin of the element may have changed. So you call this to tell jsPlumb to + * recalculate. You need to do this because, for performance reasons, jsplumb won't calculate these offsets on + * the fly. + * Parameters: + * el - either a string id, or a selector. + */ + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. You should not ever create one of these directly. If you make a call to jsPlumb.connect, all of + * the parameters that you pass in to that function will be passed to the Connection constructor; if your UI + * uses the various Endpoint-centric methods like addEndpoint/makeSource/makeTarget, along with drag and drop, + * then the parameters you set on those functions are translated and passed in to the Connection constructor. So + * you should check the documentation for each of those methods. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * container - optional id or selector instructing jsPlumb where to attach all the elements it creates for this connection. you should read the documentation for a full discussion of this. + * detachable - optional, defaults to true. Defines whether or not the connection may be detached using the mouse. + * reattach - optional, defaults to false. Defines whether not the connection should be retached if it was dragged off an Endpoint and then dropped in whitespace. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * cssClass - optional CSS class to set on the display element associated with this Connection. + * hoverClass - optional CSS class to set on the display element associated with this Connection when it is in hover state. + * overlays - Optional array of Overlay definitions to appear on this Connection. + * drawEndpoints - if false, instructs jsPlumb to not draw the endpoints for this Connection. Be careful with this: it only really works when you tell jsPlumb to attach elements to the document body. Read the documentation for a full discussion of this. + * parameters - Optional JS object containing parameters to set on the Connection. These parameters are then available via the getParameter method. + */ + var Connection = function(params) { + var self = this, visible = true, _internalHover, _superClassHover; + self.idPrefix = "_jsplumb_c_"; + self.defaultLabelLocation = 0.5; + self.defaultOverlayKeys = ["Overlays", "ConnectionOverlays"]; + this.parent = params.parent; + overlayCapableJsPlumbUIComponent.apply(this, arguments); + // ************** get the source and target and register the connection. ******************* + +// VISIBILITY + /* + * Function: isVisible + * Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + * Function: setVisible + * Sets whether or not the Connection should be visible. + * + * Parameters: + * visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + self[v ? "showOverlays" : "hideOverlays"](); + if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none"; + self.repaint(); + }; +// END VISIBILITY + +// TYPE + + this.getTypeDescriptor = function() { return "connection"; }; + this.getDefaultType = function() { + return { + parameters:{}, + scope:null, + detachable:self._jsPlumb.Defaults.ConnectionsDetachable, + rettach:self._jsPlumb.Defaults.ReattachConnections, + paintStyle:self._jsPlumb.Defaults.PaintStyle || jsPlumb.Defaults.PaintStyle, + connector:self._jsPlumb.Defaults.Connector || jsPlumb.Defaults.Connector, + hoverPaintStyle:self._jsPlumb.Defaults.HoverPaintStyle || jsPlumb.Defaults.HoverPaintStyle, + overlays:self._jsPlumb.Defaults.ConnectorOverlays || jsPlumb.Defaults.ConnectorOverlays + }; + }; + var superAt = this.applyType; + this.applyType = function(t) { + superAt(t); + if (t.detachable != null) self.setDetachable(t.detachable); + if (t.reattach != null) self.setReattach(t.reattach); + if (t.scope) self.scope = t.scope; + self.setConnector(t.connector); + }; +// END TYPE + +// HOVER + // override setHover to pass it down to the underlying connector + _superClassHover = self.setHover; + self.setHover = function(state) { + var zi = _currentInstance.ConnectorZIndex || jsPlumb.Defaults.ConnectorZIndex; + if (zi) + self.connector.setZIndex(zi + (state ? 1 : 0)); + self.connector.setHover.apply(self.connector, arguments); + _superClassHover.apply(self, arguments); + }; + + _internalHover = function(state) { + if (_connectionBeingDragged == null) { + self.setHover(state, false); + } + }; +// END HOVER + + /* + * Function: setConnector + * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method, the same + * thing that you would set as the 'connector' property on a jsPlumb.connect call. + * + * Parameters: + * connector - Connector definition + */ + this.setConnector = function(connector, doNotRepaint) { + if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent); + var connectorArgs = { _jsPlumb:self._jsPlumb, parent:params.parent, + cssClass:params.cssClass, container:params.container, tooltip:self.tooltip + }; + if (_isString(connector)) + this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand. + else if (_isArray(connector)) { + if (connector.length == 1) + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](connectorArgs); + else + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs)); + } + self.canvas = self.connector.canvas; + // binds mouse listeners to the current connector. + _bindListeners(self.connector, self, _internalHover); + + // set z-index if it was set on Defaults. + var zi = _currentInstance.ConnectorZIndex || jsPlumb.Defaults.ConnectorZIndex; + if (zi) + self.connector.setZIndex(zi); + + if (!doNotRepaint) self.repaint(); + }; + +// INITIALISATION CODE + + this.source = _getElementObject(params.source); + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. but + // source is not overridden if the Endpoint has declared it is not the final target of a connection; + // instead we use the source that the Endpoint declares will be the final source element. + if (params.sourceEndpoint) this.source = params.sourceEndpoint.endpointWillMoveTo || params.sourceEndpoint.getElement(); + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + + // if a new connection is the result of moving some existing connection, params.previousConnection + // will have that Connection in it. listeners for the jsPlumbConnection event can look for that + // member and take action if they need to. + self.previousConnection = params.previousConnection; + + this.sourceId = _getAttribute(this.source, "id"); + this.targetId = _getAttribute(this.target, "id"); + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams, elementId) { + return (anchorParams) ? _currentInstance.makeAnchor(anchorParams, elementId, _currentInstance) : null; + }, + prepareEndpoint = function(existing, index, params, element, elementId, connectorPaintStyle, connectorHoverPaintStyle) { + var e; + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] + || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint; + + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified. + if (es.fillStyle == null && connectorPaintStyle != null) + es.fillStyle = connectorPaintStyle.strokeStyle; + + // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does: + //* + if (es.outlineColor == null && connectorPaintStyle != null) + es.outlineColor = connectorPaintStyle.outlineColor; + if (es.outlineWidth == null && connectorPaintStyle != null) + es.outlineWidth = connectorPaintStyle.outlineWidth; + //*/ + + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure? + if (connectorHoverPaintStyle != null) { + if (ehs == null) ehs = {}; + if (ehs.fillStyle == null) { + ehs.fillStyle = connectorHoverPaintStyle.strokeStyle; + } + } + var a = params.anchors ? params.anchors[index] : + params.anchor ? params.anchor : + _makeAnchor(_currentInstance.Defaults.Anchors[index], elementId) || + _makeAnchor(jsPlumb.Defaults.Anchors[index], elementId) || + _makeAnchor(_currentInstance.Defaults.Anchor, elementId) || + _makeAnchor(jsPlumb.Defaults.Anchor, elementId), + u = params.uuids ? params.uuids[index] : null; + e = _newEndpoint({ + paintStyle : es, hoverPaintStyle:ehs, endpoint : ep, connections : [ self ], + uuid : u, anchor : a, source : element, scope : params.scope, container:params.container, + reattach:params.reattach || _currentInstance.Defaults.ReattachConnections, + detachable:params.detachable || _currentInstance.Defaults.ConnectionsDetachable + }); + self.endpoints[index] = e; + + if (params.drawEndpoints === false) e.setVisible(false, true, true); + + } + return e; + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source, + self.sourceId, params.paintStyle, params.hoverPaintStyle); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target, + self.targetId, params.paintStyle, params.hoverPaintStyle); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + // if delete endpoints on detach, keep a record of just exactly which endpoints they are. + self.endpointsToDeleteOnDetach = [null, null]; + if (params.deleteEndpointsOnDetach) { + if (params.sourceIsNew) self.endpointsToDeleteOnDetach[0] = self.endpoints[0]; + if (params.targetIsNew) self.endpointsToDeleteOnDetach[1] = self.endpoints[1]; + } + + self.setConnector(this.endpoints[0].connector || + this.endpoints[1].connector || + params.connector || + _currentInstance.Defaults.Connector || + jsPlumb.Defaults.Connector, true); + + this.setPaintStyle(this.endpoints[0].connectorStyle || + this.endpoints[1].connectorStyle || + params.paintStyle || + _currentInstance.Defaults.PaintStyle || + jsPlumb.Defaults.PaintStyle, true); + + this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || + this.endpoints[1].connectorHoverStyle || + params.hoverPaintStyle || + _currentInstance.Defaults.HoverPaintStyle || + jsPlumb.Defaults.HoverPaintStyle, true); + + this.paintStyleInUse = this.getPaintStyle(); + + _updateOffset( { elId : this.sourceId, timestamp:_suspendedAt }); + _updateOffset( { elId : this.targetId, timestamp:_suspendedAt }); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId], + otherOffset = offsets[this.targetId], + otherWH = sizes[this.targetId], + initialTimestamp = _suspendedAt || _timestamp(), + anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + elementId:this.endpoints[0].elementId, + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1], + timestamp:initialTimestamp + }); + + this.endpoints[0].paint( { anchorLoc : anchorLoc, timestamp:initialTimestamp }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + elementId:this.endpoints[1].elementId, + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0], + timestamp:initialTimestamp + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc, timestamp:initialTimestamp }); + +// END INITIALISATION CODE + +// DETACHABLE + var _detachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.detachable === false) _detachable = false; + if(self.endpoints[0].connectionsDetachable === false) _detachable = false; + if(self.endpoints[1].connectionsDetachable === false) _detachable = false; + /* + * Function: isDetachable + * Returns whether or not this connection can be detached from its target/source endpoint. by default this + * is false; use it in conjunction with the 'reattach' parameter. + */ + this.isDetachable = function() { + return _detachable === true; + }; + /* + * Function: setDetachable + * Sets whether or not this connection is detachable. + * + * Parameters: + * detachable - whether or not to set the Connection to be detachable. + */ + this.setDetachable = function(detachable) { + _detachable = detachable === true; + }; +// END DETACHABLE + +// REATTACH + var _reattach = params.reattach || + self.endpoints[0].reattachConnections || + self.endpoints[1].reattachConnections || + _currentInstance.Defaults.ReattachConnections; + /* + * Function: isReattach + * Returns whether or not this connection will be reattached after having been detached via the mouse and dropped. by default this + * is false; use it in conjunction with the 'detachable' parameter. + */ + this.isReattach = function() { + return _reattach === true; + }; + /* + * Function: setReattach + * Sets whether or not this connection will reattach after having been detached via the mouse and dropped. + * + * Parameters: + * reattach - whether or not to set the Connection to reattach after drop in whitespace. + */ + this.setReattach = function(reattach) { + _reattach = reattach === true; + }; + +// END REATTACH + +// COST + DIRECTIONALITY + // if cost not supplied, try to inherit from source endpoint + var _cost = params.cost || self.endpoints[0].getConnectionCost(); + self.getCost = function() { return _cost; }; + self.setCost = function(c) { _cost = c; }; + var _bidirectional = !(params.bidirectional === false); + // inherit bidirectional flag if set no source endpoint + if (params.bidirectional == null) _bidirectional = self.endpoints[0].areConnectionsBidirectional(); + self.isBidirectional = function() { return _bidirectional; }; +// END COST + DIRECTIONALITY + +// PARAMETERS + // merge all the parameters objects into the connection. parameters set + // on the connection take precedence; then target endpoint params, then + // finally source endpoint params. + // TODO jsPlumb.extend could be made to take more than two args, and it would + // apply the second through nth args in order. + var _p = jsPlumb.extend({}, this.endpoints[0].getParameters()); + jsPlumb.extend(_p, this.endpoints[1].getParameters()); + jsPlumb.extend(_p, self.getParameters()); + self.setParameters(_p); +// END PARAMETERS + +// MISCELLANEOUS + /** + * implementation of abstract method in jsPlumbUtil.EventGenerator + * @return list of attached elements. in our case, a list of Endpoints. + */ + this.getAttachedElements = function() { + return self.endpoints; + }; + + /** + * changes the parent element of this connection to newParent. not exposed for the public API. + */ + this.moveParent = function(newParent) { + var jpcl = jsPlumb.CurrentLibrary, curParent = jpcl.getParent(self.connector.canvas); + if (self.connector.bgCanvas) { + jpcl.removeElement(self.connector.bgCanvas, curParent); + jpcl.appendElement(self.connector.bgCanvas, newParent); + } + jpcl.removeElement(self.connector.canvas, curParent); + jpcl.appendElement(self.connector.canvas, newParent); + // this only applies for DOMOverlays + for (var i = 0; i < self.overlays.length; i++) { + if (self.overlays[i].isAppendedAtTopLevel) { + jpcl.removeElement(self.overlays[i].canvas, curParent); + jpcl.appendElement(self.overlays[i].canvas, newParent); + if (self.overlays[i].reattachListeners) + self.overlays[i].reattachListeners(self.connector); + } + } + if (self.connector.reattachListeners) // this is for SVG/VML; change an element's parent and you have to reinit its listeners. + self.connector.reattachListeners(); // the Canvas implementation doesn't have to care about this + }; + +// END MISCELLANEOUS + +// PAINTING + + /* + * Paints the Connection. Not exposed for public usage. + * + * Parameters: + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + var lastPaintedAt = null; + this.paint = function(params) { + + if (visible) { + + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp, + // if the moving object is not the source we must transpose the two references. + swap = false, + tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId, + tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + + if (timestamp == null || timestamp != lastPaintedAt) { + var sourceInfo = _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }), + targetInfo = _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var sE = this.endpoints[sIdx], tE = this.endpoints[tIdx], + sAnchorP = sE.anchor.getCurrentLocation(sE), + tAnchorP = tE.anchor.getCurrentLocation(tE); + + // find largest overlay; we use it to ensure sufficient padding in the connector canvas. + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible()) maxSize = Math.max(maxSize, o.computeMaxSize()); + } + + var dim = this.connector.compute( + sAnchorP, + tAnchorP, + this.endpoints[sIdx], + this.endpoints[tIdx], + this.endpoints[sIdx].anchor, + this.endpoints[tIdx].anchor, + self.paintStyleInUse.lineWidth, + maxSize, + sourceInfo, + targetInfo ); + + self.connector.paint(dim, self.paintStyleInUse); + + // paint overlays + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) { + self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim); + } + } + + } + lastPaintedAt = timestamp; + } + }; + + /* + * Function: repaint + * Repaints the Connection. No parameters exposed to public API. + */ + this.repaint = function(params) { + params = params || {}; + var recalc = !(params.recalc === false); + this.paint({ elId : this.sourceId, recalc : recalc, timestamp:params.timestamp }); + }; + + // the very last thing we do is check to see if a 'type' was supplied in the params + var _type = params.type || self.endpoints[0].connectionType || self.endpoints[1].connectionType; + if (_type) + self.addType(_type); + +// END PAINTING + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Connection. + * + * Parameters: + * event - the event to bind. Available events on a Connection are: + * - *click* : notification that a Connection was clicked. + * - *dblclick* : notification that a Connection was double clicked. + * - *mouseenter* : notification that the mouse is over a Connection. + * - *mouseexit* : notification that the mouse exited a Connection. + * - *contextmenu* : notification that the user right-clicked on the Connection. + * + * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Connection's paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: getPaintStyle + * Gets the Connection's paint style. This is not necessarily the paint style in use at the time; + * this is the paint style for the connection when the mouse it not hovering over it. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Connection will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Connection. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + + /* + * Function: getParameter + * Gets the named parameter; returns null if no parameter with that name is set. Parameter values may have been supplied to a 'connect' or 'addEndpoint' call (connections made with the mouse get a copy of all parameters set on each of their Endpoints), or the parameter value may have been set with setParameter. + * + * Parameters: + * key - Parameter name. + */ + + /* + * Function: setParameter + * Sets the named parameter to the given value. + * + * Parameters: + * key - Parameter name. + * value - Parameter value. + */ + + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc) + */ + + /* + * Property: sourceId + * Id of the source element in the connection. + */ + /* + * Property: targetId + * Id of the target element in the connection. + */ + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + /* + * Property: source + * The source element for this Connection. + */ + /* + * Property: target + * The target element for this Connection. + */ + /* + * Property: overlays + * List of Overlays for this component. + */ + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + /* + * Function: getOverlay + * Gets an overlay, by ID. Note: by ID. You would pass an 'id' parameter + * in to the Overlay's constructor arguments, and then use that to retrieve + * it via this method. + * + * Parameters: + * overlayId - id of the overlay to retrieve. + */ + /* + * Function: getOverlays + * Gets all the overlays for this component. + */ + /* + * Function: hideOverlay + * Hides the overlay specified by the given id. + * + * Parameters: + * overlayId - id of the overlay to hide. + */ + /* + * Function: hideOverlays + * Hides all Overlays + */ + /* + * Function: showOverlay + * Shows the overlay specified by the given id. + * + * Parameters: + * overlayId - id of the overlay to show. + */ + /* + * Function: showOverlays + * Shows all Overlays + */ + /** + * Function: removeAllOverlays + * Removes all overlays from the Connection, and then repaints. + */ + /** + * Function: removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * + * Parameters: + * overlayId - id of the overlay to remove. + */ + /** + * Function: removeOverlays + * Removes a set of overlays by ID. Note: by ID. this is a string you set in the overlay spec. + * + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String, a Function that returns a String, or a params object containing { "label", "labelStyle", "location", "cssClass" }. Note that this uses innerHTML on the label div, so keep + * that in mind if you need escaped HTML. + */ + /* + * Function: getLabel + * Returns the label text for this Connection (or a function if you are labelling with a function). + * + * This does not return the overlay itself; this is a convenience method which is a pair with + * setLabel; together they allow you to add and access a Label Overlay without having to create the + * Overlay object itself. For access to the underlying label overlay that jsPlumb has created, + * use getLabelOverlay. + * + * See Also: + * + */ + /* + * Function: getLabelOverlay + * Returns the underlying internal label overlay, which will exist if you specified a label on + * a connect call, or have called setLabel at any stage. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + + }; // END Connection class + +// ENDPOINT HELPER FUNCTIONS + var _makeConnectionDragHandler = function(placeholder) { + var stopped = false; + return { + drag : function() { + if (stopped) { + stopped = false; + return true; + } + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments, _currentInstance.getZoom()), + el = placeholder.element; + + if (el) { + jsPlumb.CurrentLibrary.setOffset(el, _ui); + _draw(_getElementObject(el), _ui); + } + }, + stopDrag : function() { + stopped = true; + } + }; + }; + + var _makeFloatingEndpoint = function(paintStyle, referenceAnchor, endpoint, referenceCanvas, sourceElement) { + var floatingAnchor = new FloatingAnchor( { reference : referenceAnchor, referenceCanvas : referenceCanvas }); + //setting the scope here should not be the way to fix that mootools issue. it should be fixed by not + // adding the floating endpoint as a droppable. that makes more sense anyway! + return _newEndpoint({ paintStyle : paintStyle, endpoint : endpoint, anchor : floatingAnchor, source : sourceElement, scope:"__floating" }); + }; + + /** + * creates a placeholder div for dragging purposes, adds it to the DOM, and pre-computes its offset. + */ + var _makeDraggablePlaceholder = function(placeholder, parent) { + var n = document.createElement("div"); + n.style.position = "absolute"; + var placeholderDragElement = _getElementObject(n); + _appendElement(n, parent); + var id = _getId(placeholderDragElement); + _updateOffset( { elId : id }); + // create and assign an id, and initialize the offset. + placeholder.id = id; + placeholder.element = placeholderDragElement; + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1 + * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't + * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you + * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or + * 'target' Endpoints for Connections. + * + * + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * enabled - optional, defaults to true. Indicates whether or not the Endpoint should be enabled for mouse events (drag/drop). + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * cssClass - optional CSS class to set on the display element associated with this Endpoint. + * hoverClass - optional CSS class to set on the display element associated with this Endpoint when it is in hover state. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * container - optional id or selector instructing jsPlumb where to attach the element it creates for this endpoint. you should read the documentation for a full discussion of this. + * connections - optional list of Connections to configure the Endpoint with. + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * connectorClass - optional CSS class to set on Connections emanating from this Endpoint. + * connectorHoverClass - optional CSS class to set on to set on Connections emanating from this Endpoint when they are in hover state. + * connectionsDetachable - optional, defaults to true. Sets whether connections to/from this Endpoint should be detachable or not. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + * parameters - Optional JS object containing parameters to set on the Endpoint. These parameters are then available via the getParameter method. When a connection is made involving this Endpoint, the parameters from this Endpoint are copied into that Connection. Source Endpoint parameters override target Endpoint parameters if they both have a parameter with the same name. + */ + var Endpoint = function(params) { + var self = this; + self.idPrefix = "_jsplumb_e_"; + self.defaultLabelLocation = [ 0.5, 0.5 ]; + self.defaultOverlayKeys = ["Overlays", "EndpointOverlays"]; + this.parent = params.parent; + overlayCapableJsPlumbUIComponent.apply(this, arguments); + params = params || {}; + +// TYPE + + this.getTypeDescriptor = function() { return "endpoint"; }; + this.getDefaultType = function() { + return { + parameters:{}, + scope:null, + maxConnections:self._jsPlumb.Defaults.MaxConnections, + paintStyle:self._jsPlumb.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle, + endpoint:self._jsPlumb.Defaults.Endpoint || jsPlumb.Defaults.Endpoint, + hoverPaintStyle:self._jsPlumb.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle, + overlays:self._jsPlumb.Defaults.EndpointOverlays || jsPlumb.Defaults.EndpointOverlays, + connectorStyle:params.connectorStyle, + connectorHoverStyle:params.connectorHoverStyle, + connectorClass:params.connectorClass, + connectorHoverClass:params.connectorHoverClass, + connectorOverlays:params.connectorOverlays, + connector:params.connector, + connectorTooltip:params.connectorTooltip + }; + }; + var superAt = this.applyType; + this.applyType = function(t) { + superAt(t); + if (t.maxConnections != null) _maxConnections = t.maxConnections; + if (t.scope) self.scope = t.scope; + self.connectorStyle = t.connectorStyle; + self.connectorHoverStyle = t.connectorHoverStyle; + self.connectorOverlays = t.connectorOverlays; + self.connector = t.connector; + self.connectorTooltip = t.connectorTooltip; + self.connectionType = t.connectionType; + self.connectorClass = t.connectorClass; + self.connectorHoverClass = t.connectorHoverClass; + }; +// END TYPE + + var visible = true, __enabled = !(params.enabled === false); + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + self[v ? "showOverlays" : "hideOverlays"](); + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + + /* + Function: isEnabled + Returns whether or not the Endpoint is enabled for drag/drop connections. + */ + this.isEnabled = function() { return __enabled; }; + + /* + Function: setEnabled + Sets whether or not the Endpoint is enabled for drag/drop connections. + + Parameters: + enabled - whether or not the Endpoint is enabled. + */ + this.setEnabled = function(e) { __enabled = e; }; + + var _element = params.source, _uuid = params.uuid, floatingEndpoint = null, inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + this.element = _element; + + var _connectionCost = params.connectionCost; + this.getConnectionCost = function() { return _connectionCost; }; + this.setConnectionCost = function(c) { + _connectionCost = c; + }; + + var _connectionsBidirectional = params.connectionsBidirectional === false ? false : true; + this.areConnectionsBidirectional = function() { return _connectionsBidirectional; }; + this.setConnectionsBidirectional = function(b) { _connectionsBidirectional = b; }; + + self.anchor = params.anchor ? _currentInstance.makeAnchor(params.anchor, _elementId, _currentInstance) : params.anchors ? _currentInstance.makeAnchor(params.anchors, _elementId, _currentInstance) : _currentInstance.makeAnchor(_currentInstance.Defaults.Anchor || "TopCenter", _elementId, _currentInstance); + + // ANCHOR MANAGER + if (!params._transient) // in place copies, for example, are transient. they will never need to be retrieved during a paint cycle, because they dont move, and then they are deleted. + _currentInstance.anchorManager.add(self, _elementId); + + var _endpoint = null, originalEndpoint = null; + this.setEndpoint = function(ep) { + var endpointArgs = { + _jsPlumb:self._jsPlumb, + parent:params.parent, + container:params.container, + tooltip:params.tooltip, + connectorTooltip:params.connectorTooltip, + endpoint:self + }; + if (_isString(ep)) + _endpoint = new jsPlumb.Endpoints[renderMode][ep](endpointArgs); + else if (_isArray(ep)) { + endpointArgs = jsPlumb.extend(ep[1], endpointArgs); + _endpoint = new jsPlumb.Endpoints[renderMode][ep[0]](endpointArgs); + } + else { + _endpoint = ep.clone(); + } + + // assign a clone function using a copy of endpointArgs. this is used when a drag starts: the endpoint that was dragged is cloned, + // and the clone is left in its place while the original one goes off on a magical journey. + // the copy is to get around a closure problem, in which endpointArgs ends up getting shared by + // the whole world. + var argsForClone = jsPlumb.extend({}, endpointArgs); + _endpoint.clone = function() { + var o = new Object(); + _endpoint.constructor.apply(o, [argsForClone]); + return o; + }; + + self.endpoint = _endpoint; + self.type = self.endpoint.type; + }; + + this.setEndpoint(params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot"); + originalEndpoint = _endpoint; + + // override setHover to pass it down to the underlying endpoint + var _sh = self.setHover; + self.setHover = function() { + self.endpoint.setHover.apply(self.endpoint, arguments); + _sh.apply(self, arguments); + }; + // endpoint delegates to first connection for hover, if there is one. + var internalHover = function(state) { + if (self.connections.length > 0) + self.connections[0].setHover(state, false); + else + self.setHover(state); + }; + + // bind listeners from endpoint to self, with the internal hover function defined above. + _bindListeners(self.endpoint, self, internalHover); + + this.setPaintStyle(params.paintStyle || + params.style || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyle, true); + this.setHoverPaintStyle(params.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyle, true); + this.paintStyleInUse = this.getPaintStyle(); + var originalPaintStyle = this.getPaintStyle(); + this.connectorStyle = params.connectorStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.connectorTooltip = params.connectorTooltip; + this.connectorClass = params.connectorClass; + this.connectorHoverClass = params.connectorHoverClass; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + + var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + this.canvas = this.endpoint.canvas; + this.connections = params.connections || []; + + this.scope = params.scope || DEFAULT_SCOPE; + this.connectionType = params.connectionType; + this.timestamp = null; + //self.isReattach = params.reattach || false; + self.reattachConnections = params.reattach || _currentInstance.Defaults.ReattachConnections; + //self.isReattach = params.reattach || false; + self.connectionsDetachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.connectionsDetachable === false || params.detachable === false) + self.connectionsDetachable = false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + if (params.onMaxConnections) + self.bind("maxConnections", params.onMaxConnections); + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget, forceDetach, fireEvent, originalEvent) { + var idx = _findWithFunction(self.connections, function(c) { return c.id == connection.id}), + actuallyDetached = false; + fireEvent = (fireEvent !== false); + if (idx >= 0) { + // 1. does the connection have a before detach (note this also checks jsPlumb's bound + // detach handlers; but then Endpoint's check will, too, hmm.) + if (forceDetach || connection._forceDetach || connection.isDetachable() || connection.isDetachAllowed(connection)) { + // get the target endpoint + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + // it would be nice to check with both endpoints that it is ok to detach. but + // for this we'll have to get a bit fancier: right now if you use the same beforeDetach + // interceptor for two endpoints (which is kind of common, because it's part of the + // endpoint definition), then it gets fired twice. so in fact we need to loop through + // each beforeDetach and see if it returns false, at which point we exit. but if it + // returns true, we have to check the next one. however we need to track which ones + // have already been run, and not run them again. + if (forceDetach || connection._forceDetach || (self.isDetachAllowed(connection) /*&& t.isDetachAllowed(connection)*/)) { + + self.connections.splice(idx, 1); + + // this avoids a circular loop + if (!ignoreTarget) { + + t.detach(connection, true, forceDetach); + // check connection to see if we want to delete the endpoints associated with it. + // we only detach those that have just this connection; this scenario is most + // likely if we got to this bit of code because it is set by the methods that + // create their own endpoints, like .connect or .makeTarget. the user is + // not likely to have interacted with those endpoints. + if (connection.endpointsToDeleteOnDetach){ + for (var i = 0; i < connection.endpointsToDeleteOnDetach.length; i++) { + var cde = connection.endpointsToDeleteOnDetach[i]; + if (cde && cde.connections.length == 0) + _currentInstance.deleteEndpoint(cde); + } + } + } + _removeElements(connection.connector.getDisplayElements(), connection.parent); + _removeWithFunction(connectionsByScope[connection.scope], function(c) { + return c.id == connection.id; + }); + actuallyDetached = true; + var doFireEvent = (!ignoreTarget && fireEvent) + fireDetachEvent(connection, doFireEvent, originalEvent); + } + } + } + return actuallyDetached; + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + * + * Parameters: + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachAll = function(fireEvent, originalEvent) { + while (self.connections.length > 0) { + self.detach(self.connections[0], false, true, fireEvent, originalEvent); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachFrom = function(targetEndpoint, fireEvent, originalEvent) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + if (self.detach(c[i], false, true, fireEvent, originalEvent)) + c[i].setHover(false, false); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findWithFunction(self.connections, function(c) { return c.id == connection.id}); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * + * Returns: + * The DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: setElement + * Sets the DOM element this Endpoint is attached to. + * + * Parameters: + * el - dom element or selector + * container - optional, specifies the actual parent element to use as the parent for this Endpoint's visual representation. + * See the jsPlumb documentation for a discussion about this. + */ + this.setElement = function(el, container) { + + // TODO possibly have this object take charge of moving the UI components into the appropriate + // parent. this is used only by makeSource right now, and that function takes care of + // moving the UI bits and pieces. however it would s + var parentId = _getId(el); + // remove the endpoint from the list for the current endpoint's element + _removeWithFunction(endpointsByElement[self.elementId], function(e) { + return e.id == self.id; + }); + _element = _getElementObject(el); + _elementId = _getId(_element); + self.elementId = _elementId; + // need to get the new parent now + var newParentElement = _getParentFromParams({source:parentId, container:container}), + curParent = jpcl.getParent(self.canvas); + jpcl.removeElement(self.canvas, curParent); + jpcl.appendElement(self.canvas, newParentElement); + + // now move connection(s)...i would expect there to be only one but we will iterate. + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].moveParent(newParentElement); + self.connections[i].sourceId = _elementId; + self.connections[i].source = _element; + } + _addToList(endpointsByElement, parentId, self); + //_currentInstance.repaint(parentId); + + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + var loc = self.anchor.getCurrentLocation(self), + o = self.anchor.getOrientation(self), + inPlaceAnchor = { + compute:function() { return [ loc[0], loc[1] ]}, + getCurrentLocation : function() { return [ loc[0], loc[1] ]}, + getOrientation:function() { return o; } + }; + + return _newEndpoint( { + anchor : inPlaceAnchor, + source : _element, + paintStyle : this.getPaintStyle(), + endpoint : _endpoint, + _transient:true, + scope:self.scope + }); + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + var candidate = self.connections[0]; + if (self.isTarget && candidate) return candidate; + else { + return (self.connections.length < _maxConnections) || _maxConnections == -1 ? null : candidate; + } + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + /* + * Function: setStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to + * setStyle and deprecate it if so. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + * + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + // a helper function that tries to find a connection to the given element, and returns it if so. if elementWithPrecedence is null, + // or no connection to it is found, we return the first connection in our list. + var findConnectionToUseForDynamicAnchor = function(elementWithPrecedence) { + var idx = 0; + if (elementWithPrecedence != null) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].sourceId == elementWithPrecedence || self.connections[i].targetId == elementWithPrecedence) { + idx = i; + break; + } + } + } + + return self.connections[idx]; + }; + + /* + * Function: paint + * Paints the Endpoint, recalculating offset and anchor positions if necessary. This does NOT paint + * any of the Endpoint's connections. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + params = params || {}; + var timestamp = params.timestamp, recalc = !(params.recalc === false); + //recalc = params.recalc; + if (!timestamp || self.timestamp !== timestamp) { + _updateOffset({ elId:_elementId, timestamp:timestamp, recalc:recalc }); + var xy = params.offset || offsets[_elementId]; + if(xy) { + var ap = params.anchorPoint,connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (recalc && self.anchor.isDynamic && self.connections.length > 0) { + var c = findConnectionToUseForDynamicAnchor(params.elementWithPrecedence), + oIdx = c.endpoints[0] == self ? 1 : 0, + oId = oIdx == 0 ? c.sourceId : c.targetId, + oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + ap = self.anchor.compute(anchorParams); + } + + // switched _endpoint to self here; continuous anchors, for instance, look for the + // endpoint's id, but here "_endpoint" is the renderer, not the actual endpoint. + // TODO need to verify this does not break anything. + //var d = _endpoint.compute(ap, self.anchor.getOrientation(_endpoint), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + var d = _endpoint.compute(ap, self.anchor.getOrientation(self), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + _endpoint.paint(d, self.paintStyleInUse, self.anchor); + self.timestamp = timestamp; + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) self.overlayPlacements[i] = o.draw(self.endpoint, self.paintStyleInUse, d); + } + } + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var placeholderInfo = { id:null, element:null }, + jpc = null, + existingJpc = false, + existingJpcParams = null, + _dragHandler = _makeConnectionDragHandler(placeholderInfo); + + var start = function() { + // drag might have started on an endpoint that is not actually a source, but which has + // one or more connections. + jpc = self.connectorSelector(); + var _continue = true; + // if not enabled, return + if (!self.isEnabled()) _continue = false; + // if no connection and we're not a source, return. + if (jpc == null && !params.isSource) _continue = false; + // otherwise if we're full and not allowed to drag, also return false. + if (params.isSource && self.isFull() && !dragAllowedWhenFull) _continue = false; + // if the connection was setup as not detachable or one of its endpoints + // was setup as connectionsDetachable = false, or Defaults.ConnectionsDetachable + // is set to false... + if (jpc != null && !jpc.isDetachable()) _continue = false; + + if (_continue === false) { + // this is for mootools and yui. returning false from this causes jquery to stop drag. + // the events are wrapped in both mootools and yui anyway, but i don't think returning + // false from the start callback would stop a drag. + if (jsPlumb.CurrentLibrary.stopDrag) jsPlumb.CurrentLibrary.stopDrag(); + _dragHandler.stopDrag(); + return false; + } + + // if we're not full but there was a connection, make it null. we'll create a new one. + if (jpc && !self.isFull() && params.isSource) jpc = null; + + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.referenceEndpoint = self; + inPlaceCopy.paint(); + + _makeDraggablePlaceholder(placeholderInfo, self.parent); + + // set the offset of this div to be where 'inPlaceCopy' is, to start with. + // TODO merge this code with the code in both Anchor and FloatingAnchor, because it + // does the same stuff. + var ipcoel = _getElementObject(inPlaceCopy.canvas), + ipco = _getOffset(ipcoel, _currentInstance), + po = adjustForParentOffsetAndScroll([ipco.left, ipco.top], inPlaceCopy.canvas); + jsPlumb.CurrentLibrary.setOffset(placeholderInfo.element, {left:po[0], top:po[1]}); + + // when using makeSource and a parent, we first draw the source anchor on the source element, then + // move it to the parent. note that this happens after drawing the placeholder for the + // first time. + if (self.parentAnchor) self.anchor = _currentInstance.makeAnchor(self.parentAnchor, self.elementId, _currentInstance); + + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", placeholderInfo.id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + + // create a floating endpoint. + // here we test to see if a dragProxy was specified in this endpoint's constructor params, and + // if so, we create that endpoint instead of cloning ourselves. + if (params.proxy) { + /* var floatingAnchor = new FloatingAnchor( { reference : self.anchor, referenceCanvas : self.canvas }); + + floatingEndpoint = _newEndpoint({ + paintStyle : params.proxy.paintStyle, + endpoint : params.proxy.endpoint, + anchor : floatingAnchor, + source : placeholderInfo.element, + scope:"__floating" + }); + + //$(self.canvas).hide(); + */ + self.setPaintStyle(params.proxy.paintStyle); + // if we do this, we have to cleanup the old one. like just remove its display parts + //self.setEndpoint(params.proxy.endpoint); + + } + // else { + floatingEndpoint = _makeFloatingEndpoint(self.getPaintStyle(), self.anchor, _endpoint, self.canvas, placeholderInfo.element); + // } + + if (jpc == null) { + self.anchor.locked = true; + self.setHover(false, false); + // TODO the hover call above does not reset any target endpoint's hover + // states. + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : self.endpointWillMoveTo || _getElementObject(_element), // for makeSource with parent option. ensure source element is represented correctly. + target : placeholderInfo.element, + anchors : [ self.anchor, floatingEndpoint.anchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays, + type:self.connectionType, + cssClass:self.connectorClass, + hoverClass:self.connectorHoverClass + }); + + } else { + existingJpc = true; + jpc.setHover(false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas), false, true); + // new anchor idx + var anchorIdx = jpc.endpoints[0].id == self.id ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas), + dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // now we want to get this endpoint's DROP scope, and set it for now: we can only be dropped on drop zones + // that have our drop scope (issue 57). + var dropScope = jsPlumb.CurrentLibrary.getDropScope(c); + jsPlumb.CurrentLibrary.setDragScope(c, dropScope); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = placeholderInfo.element; + jpc.sourceId = placeholderInfo.id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = placeholderInfo.element; + jpc.targetId = placeholderInfo.id; + } + + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.suspendedEndpoint.setHover(false); + floatingEndpoint.referenceEndpoint = jpc.suspendedEndpoint; + jpc.endpoints[anchorIdx] = floatingEndpoint; + + // fire an event that informs that a connection is being dragged + fireConnectionDraggingEvent(jpc); + + } + // register it and register connection on it. + floatingConnections[placeholderInfo.id] = jpc; + floatingEndpoint.addConnection(jpc); + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, placeholderInfo.id, floatingEndpoint); + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var jpcl = jsPlumb.CurrentLibrary, + dragOptions = params.dragOptions || {}, + defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions), + startEvent = jpcl.dragEvents["start"], + stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"]; + + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + // extracted drag handler function so can be used by makeSource + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], _dragHandler.drag); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + var originalEvent = jpcl.getDropEvent(arguments); + _currentInstance.currentlyDragging = false; + _removeWithFunction(endpointsByElement[placeholderInfo.id], function(e) { + return e.id == floatingEndpoint.id; + }); + _removeElements( [ placeholderInfo.element[0], floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + _currentInstance.anchorManager.clearFor(placeholderInfo.id); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + self.setPaintStyle(originalPaintStyle); // reset to original; may have been changed by drag proxy. + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + //if (self.isReattach || jpc._forceDetach || !jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc, false, false, true, originalEvent)) { + if (jpc.isReattach() || jpc._forceReattach || jpc._forceDetach || !jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc, false, false, true, originalEvent)) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + _currentInstance.repaint(existingJpcParams[1]); + } + jpc._forceDetach = null; + jpc._forceReattach = null; + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElements(jpc.connector.getDisplayElements(), self.parent); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint({recalc:false}); + //jpc.setHover(false, false); + + fireConnectionDragStopEvent(jpc); + + jpc = null; + inPlaceCopy = null; + delete endpointsByElement[floatingEndpoint.elementId]; + floatingEndpoint.anchor = null; + floatingEndpoint = null; + _currentInstance.currentlyDragging = false; + + + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions, true); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas, forceInit, isTransient, endpoint) { + if ((params.isTarget || forceInit) && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop'], + overEvent = jsPlumb.CurrentLibrary.dragEvents['over'], + outEvent = jsPlumb.CurrentLibrary.dragEvents['out'], + drop = function() { + + var originalEvent = jsPlumb.CurrentLibrary.getDropEvent(arguments), + draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + elId = _getAttribute(draggable, "elId"), + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id]; + + // if this is a drop back where the connection came from, mark it force rettach and + // return; the stop handler will reattach. without firing an event. + var redrop = jpc.suspendedEndpoint && (jpc.suspendedEndpoint.id == self.id || + self.referenceEndpoint && jpc.suspendedEndpoint.id == self.referenceEndpoint.id) ; + if (redrop) { + jpc._forceReattach = true; + return; + } + + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + + // restore the original scope if necessary (issue 57) + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + var endpointEnabled = endpoint != null ? endpoint.isEnabled() : true; + + if (self.isFull()) { + self.fire("maxConnections", { + endpoint:self, + connection:jpc, + maxConnections:_maxConnections + }, originalEvent); + } + + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget) && endpointEnabled) { + var _doContinue = true; + + // the second check here is for the case that the user is dropping it back + // where it came from. + if (jpc.suspendedEndpoint && jpc.suspendedEndpoint.id != self.id) { + if (idx == 0) { + jpc.source = jpc.suspendedEndpoint.element; + jpc.sourceId = jpc.suspendedEndpoint.elementId; + } else { + jpc.target = jpc.suspendedEndpoint.element; + jpc.targetId = jpc.suspendedEndpoint.elementId; + } + + if (!jpc.isDetachAllowed(jpc) || !jpc.endpoints[idx].isDetachAllowed(jpc) || !jpc.suspendedEndpoint.isDetachAllowed(jpc) || !_currentInstance.checkCondition("beforeDetach", jpc)) + _doContinue = false; + } + + // these have to be set before testing for beforeDrop. + if (idx == 0) { + jpc.source = self.element; + jpc.sourceId = self.elementId; + } else { + jpc.target = self.element; + jpc.targetId = self.elementId; + } + +// ------------ wrap the execution path in a function so we can support asynchronous beforeDrop + + // we want to execute this regardless. + var commonFunction = function() { + jpc.floatingAnchorIndex = null; + }; + + var continueFunction = function() { + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + + // copy our parameters in to the connection: + var params = self.getParameters(); + for (var aParam in params) + jpc.setParameter(aParam, params[aParam]); + + if (!jpc.suspendedEndpoint) { + if (params.draggable) + jsPlumb.CurrentLibrary.initDraggable(self.element, dragOptions, true); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + fireDetachEvent({ + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }, true, originalEvent); + } + + // finalise will inform the anchor manager and also add to + // connectionsByScope if necessary. + _finaliseConnection(jpc, null, originalEvent); + + commonFunction(); + }; + + var dontContinueFunction = function() { + // otherwise just put it back on the endpoint it was on before the drag. + if (jpc.suspendedEndpoint) { + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.setHover(false); + jpc._forceDetach = true; + if (idx == 0) { + jpc.source = jpc.suspendedEndpoint.element; + jpc.sourceId = jpc.suspendedEndpoint.elementId; + } else { + jpc.target = jpc.suspendedEndpoint.element; + jpc.targetId = jpc.suspendedEndpoint.elementId;; + } + jpc.suspendedEndpoint.addConnection(jpc); + + jpc.endpoints[0].repaint(); + jpc.repaint(); + _currentInstance.repaint(jpc.source.elementId); + jpc._forceDetach = false; + } + + commonFunction(); + }; + +// -------------------------------------- + // now check beforeDrop. this will be available only on Endpoints that are setup to + // have a beforeDrop condition (although, secretly, under the hood all Endpoints and + // the Connection have them, because they are on jsPlumbUIComponent. shhh!), because + // it only makes sense to have it on a target endpoint. + _doContinue = _doContinue && self.isDropAllowed(jpc.sourceId, jpc.targetId, jpc.scope, jpc, self); + + if (_doContinue) { + continueFunction(); + } + else { + dontContinueFunction(); + } + + //commonFunction(); + } + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + jpc.suspendedEndpoint = null; + } + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + _jpc = floatingConnections[id]; + + if (_jpc != null) { + var idx = _jpc.floatingAnchorIndex == null ? 1 : _jpc.floatingAnchorIndex; + // here we should fire the 'over' event if we are a target and this is a new connection, + // or we are the same as the floating endpoint. + var _cont = (self.isTarget && _jpc.floatingAnchorIndex != 0) || (_jpc.suspendedEndpoint && self.referenceEndpoint && self.referenceEndpoint.id == _jpc.suspendedEndpoint.id); + if (_cont) + _jpc.endpoints[idx].anchor.over(self.anchor); + } + }); + dropOptions[outEvent] = _wrap(dropOptions[outEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + _jpc = floatingConnections[id]; + + if (_jpc != null) { + var idx = _jpc.floatingAnchorIndex == null ? 1 : _jpc.floatingAnchorIndex; + var _cont = (self.isTarget && _jpc.floatingAnchorIndex != 0) || (_jpc.suspendedEndpoint && self.referenceEndpoint && self.referenceEndpoint.id == _jpc.suspendedEndpoint.id); + if (_cont) + _jpc.endpoints[idx].anchor.out(); + } + }); + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions, true, isTransient); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas), true, !(params._transient || self.anchor.isFloating), self); + + // finally, set type if it was provided + if (params.type) + self.addType(params.type); + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Endpoint. + * + * Parameters: + * event - the event to bind. Available events on an Endpoint are: + * - *click* : notification that a Endpoint was clicked. + * - *dblclick* : notification that a Endpoint was double clicked. + * - *mouseenter* : notification that the mouse is over a Endpoint. + * - *mouseexit* : notification that the mouse exited a Endpoint. + * - *contextmenu* : notification that the user right-clicked on the Endpoint. + * + * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Endpoint's paint style and then repaints the Endpoint. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: getPaintStyle + * Gets the Endpoint's paint style. This is not necessarily the paint style in use at the time; + * this is the paint style for the Endpoint when the mouse it not hovering over it. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Endpoint will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Endpoint. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + + /* + * Function: getParameter + * Gets the named parameter; returns null if no parameter with that name is set. Parameters may have been set on the Endpoint in the 'addEndpoint' call, or they may have been set with the setParameter function. + * + * Parameters: + * key - Parameter name. + */ + + /* + * Function: setParameter + * Sets the named parameter to the given value. + * + * Parameters: + * key - Parameter name. + * value - Parameter value. + */ + + /* + * Property: canvas + * The Endpoint's drawing area. + */ + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + + /* + * Property: overlays + * List of Overlays for this Endpoint. + */ + /* + * Function: addOverlay + * Adds an Overlay to the Endpoint. + * + * Parameters: + * overlay - Overlay to add. + */ + /* + * Function: getOverlay + * Gets an overlay, by ID. Note: by ID. You would pass an 'id' parameter + * in to the Overlay's constructor arguments, and then use that to retrieve + * it via this method. + */ + /* + * Function: getOverlays + * Gets all the overlays for this component. + */ + /* + * Function: hideOverlay + * Hides the overlay specified by the given id. + */ + /* + * Function: hideOverlays + * Hides all Overlays + */ + /* + * Function: showOverlay + * Shows the overlay specified by the given id. + */ + /* + * Function: showOverlays + * Shows all Overlays + */ + /** + * Function: removeAllOverlays + * Removes all overlays from the Endpoint, and then repaints. + */ + /** + * Function: removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + /** + * Function: removeOverlays + * Removes a set of overlays by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + /* + * Function: setLabel + * Sets the Endpoint's label. + * + * Parameters: + * l - label to set. May be a String, a Function that returns a String, or a params object containing { "label", "labelStyle", "location", "cssClass" }. Note that this uses innerHTML on the label div, so keep + * that in mind if you need escaped HTML. + */ + /* + Function: getLabel + Returns the label text for this Endpoint (or a function if you are labelling with a function). + This does not return the overlay itself; this is a convenience method which is a pair with + setLabel; together they allow you to add and access a Label Overlay without having to create the + Overlay object itself. For access to the underlying label overlay that jsPlumb has created, + use getLabelOverlay. + */ + /* + Function: getLabelOverlay + Returns the underlying internal label overlay, which will exist if you specified a label on + an addEndpoint call, or have called setLabel at any stage. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + + return self; + }; + }; + +// --------------------- static instance + AMD registration ------------------------------------------- + +// create static instance and assign to window if window exists. + var jsPlumb = new jsPlumbInstance(); + if (typeof window != 'undefined') window.jsPlumb = jsPlumb; +// add 'getInstance' method to static instance + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + j.init(); + return j; + }; +// maybe register static instance as an AMD module + if ( typeof define === "function" && define.amd && define.amd.jsPlumb) { + define( "jsplumb", [], function () { return jsPlumb; } ); + } + +// --------------------- end static instance + AMD registration ------------------------------------------- + +// --------------------- anchors (would like to move these out of here ------------------------------------------- + + var _curryAnchor = function(x, y, ox, oy, type, fnInit) { + return function(params) { + params = params || {}; + //var a = jsPlumb.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance); + var a = params.jsPlumbInstance.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance); + a.type = type; + if (fnInit) fnInit(a, params); + return a; + }; + }; + /* + * Property: Anchors.TopCenter + * An Anchor that is located at the top center of the element. + */ + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1, "TopCenter"); + /* + * Property: Anchors.BottomCenter + * An Anchor that is located at the bottom center of the element. + */ + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1, "BottomCenter"); + /* + * Property: Anchors.LeftMiddle + * An Anchor that is located at the left middle of the element. + */ + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0, "LeftMiddle"); + /* + * Property: Anchors.RightMiddle + * An Anchor that is located at the right middle of the element. + */ + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0, "RightMiddle"); + /* + * Property: Anchors.Center + * An Anchor that is located at the center of the element. + */ + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0, "Center"); + /* + * Property: Anchors.TopRight + * An Anchor that is located at the top right corner of the element. + */ + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1, "TopRight"); + /* + * Property: Anchors.BottomRight + * An Anchor that is located at the bottom right corner of the element. + */ + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1, "BottomRight"); + /* + * Property: Anchors.TopLeft + * An Anchor that is located at the top left corner of the element. + */ + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1, "TopLeft"); + /* + * Property: Anchors.BottomLeft + * An Anchor that is located at the bototm left corner of the element. + */ + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1, "BottomLeft"); + + jsPlumb.Defaults.DynamicAnchors = function(params) { + return params.jsPlumbInstance.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"], params.elementId, params.jsPlumbInstance); + }; + /* + * Property: Anchors.AutoDefault + * Default DynamicAnchors - chooses from TopCenter, RightMiddle, BottomCenter, LeftMiddle. + */ + jsPlumb.Anchors["AutoDefault"] = function(params) { + var a = params.jsPlumbInstance.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors(params)); + a.type = "AutoDefault"; + return a; + }; + + /* + * Property: Anchors.Assign + * An Anchor whose location is assigned at connection time, through an AnchorPositionFinder. Used in conjunction + * with the 'makeTarget' function. jsPlumb has two of these - 'Fixed' and 'Grid', and you can also write your own. + */ + jsPlumb.Anchors["Assign"] = _curryAnchor(0,0,0,0,"Assign", function(anchor, params) { + // find what to use as the "position finder". the user may have supplied a String which represents + // the id of a position finder in jsPlumb.AnchorPositionFinders, or the user may have supplied the + // position finder as a function. we find out what to use and then set it on the anchor. + var pf = params.position || "Fixed"; + anchor.positionFinder = pf.constructor == String ? params.jsPlumbInstance.AnchorPositionFinders[pf] : pf; + // always set the constructor params; the position finder might need them later (the Grid one does, + // for example) + anchor.constructorParams = params; + }); + + // Continuous anchor is just curried through to the 'get' method of the continuous anchor + // factory. + /* + * Property: Anchors.Continuous + * An Anchor that is tracks the other element in the connection, choosing the closest face. + */ + jsPlumb.Anchors["Continuous"] = function(params) { + return params.jsPlumbInstance.continuousAnchorFactory.get(params); + }; + + // these are the default anchor positions finders, which are used by the makeTarget function. supply + // a position finder argument to that function allows you to specify where the resulting anchor will + // be located + jsPlumb.AnchorPositionFinders = { + "Fixed": function(dp, ep, es, params) { + return [ (dp.left - ep.left) / es[0], (dp.top - ep.top) / es[1] ]; + }, + "Grid":function(dp, ep, es, params) { + var dx = dp.left - ep.left, dy = dp.top - ep.top, + gx = es[0] / (params.grid[0]), gy = es[1] / (params.grid[1]), + mx = Math.floor(dx / gx), my = Math.floor(dy / gy); + return [ ((mx * gx) + (gx / 2)) / es[0], ((my * gy) + (gy / 2)) / es[1] ]; + } + }; + + /* + * Property: Anchors.Perimeter + * An Anchor that tracks the perimeter of some shape, approximating it with a given number of dynamically + * positioned locations. + * + * Parameters: + * + * anchorCount - optional number of anchors to use to approximate the perimeter. default is 60. + * shape - required. the name of the shape. valid values are 'rectangle', 'square', 'ellipse', 'circle', 'triangle' and 'diamond' + * rotation - optional rotation, in degrees, to apply. + */ + jsPlumb.Anchors["Perimeter"] = function(params) { + params = params || {}; + var anchorCount = params.anchorCount || 60, + shape = params.shape; + + if (!shape) throw new Error("no shape supplied to Perimeter Anchor type"); + + var _circle = function() { + var r = 0.5, step = Math.PI * 2 / anchorCount, current = 0, a = []; + for (var i = 0; i < anchorCount; i++) { + var x = r + (r * Math.sin(current)), + y = r + (r * Math.cos(current)); + a.push( [ x, y, 0, 0 ] ); + current += step; + } + return a; + }, + _path = function(segments) { + var anchorsPerFace = anchorCount / segments.length, a = [], + _computeFace = function(x1, y1, x2, y2, fractionalLength) { + anchorsPerFace = anchorCount * fractionalLength; + var dx = (x2 - x1) / anchorsPerFace, dy = (y2 - y1) / anchorsPerFace; + for (var i = 0; i < anchorsPerFace; i++) { + a.push( [ + x1 + (dx * i), + y1 + (dy * i), + 0, + 0 + ]); + } + }; + + for (var i = 0; i < segments.length; i++) + _computeFace.apply(null, segments[i]); + + return a; + }, + _shape = function(faces) { + var s = []; + for (var i = 0; i < faces.length; i++) { + s.push([faces[i][0], faces[i][1], faces[i][2], faces[i][3], 1 / faces.length]); + } + return _path(s); + }, + _rectangle = function() { + return _shape([ + [ 0, 0, 1, 0 ], [ 1, 0, 1, 1 ], [ 1, 1, 0, 1 ], [ 0, 1, 0, 0 ] + ]); + }; + + var _shapes = { + "circle":_circle, + "ellipse":_circle, + "diamond":function() { + return _shape([ + [ 0.5, 0, 1, 0.5 ], [ 1, 0.5, 0.5, 1 ], [ 0.5, 1, 0, 0.5 ], [ 0, 0.5, 0.5, 0 ] + ]); + }, + "rectangle":_rectangle, + "square":_rectangle, + "triangle":function() { + return _shape([ + [ 0.5, 0, 1, 1 ], [ 1, 1, 0, 1 ], [ 0, 1, 0.5, 0] + ]); + }, + "path":function(params) { + var points = params.points; + var p = [], tl = 0; + for (var i = 0; i < points.length - 1; i++) { + var l = Math.sqrt(Math.pow(points[i][2] - points[i][0]) + Math.pow(points[i][3] - points[i][1])); + tl += l; + p.push([points[i][0], points[i][1], points[i+1][0], points[i+1][1], l]); + } + for (var i = 0; i < p.length; i++) { + p[i][4] = p[i][4] / tl; + } + return _path(p); + } + }, + _rotate = function(points, amountInDegrees) { + var o = [], theta = amountInDegrees / 180 * Math.PI ; + for (var i = 0; i < points.length; i++) { + var _x = points[i][0] - 0.5, + _y = points[i][1] - 0.5; + + o.push([ + 0.5 + ((_x * Math.cos(theta)) - (_y * Math.sin(theta))), + 0.5 + ((_x * Math.sin(theta)) + (_y * Math.cos(theta))), + points[i][2], + points[i][3] + ]); + } + return o; + }; + + if (!_shapes[shape]) throw new Error("Shape [" + shape + "] is unknown by Perimeter Anchor type"); + + var da = _shapes[shape](params); + if (params.rotation) da = _rotate(da, params.rotation); + var a = params.jsPlumbInstance.makeDynamicAnchor(da); + a.type = "Perimeter"; + return a; + }; +})(); diff --git a/archive/1.3.16/jsPlumb-1.3.16-tests.js b/archive/1.3.16/jsPlumb-1.3.16-tests.js new file mode 100644 index 000000000..9262bc0ec --- /dev/null +++ b/archive/1.3.16/jsPlumb-1.3.16-tests.js @@ -0,0 +1,4748 @@ +// _jsPlumb qunit tests. + +QUnit.config.reorder = false; + +var _getContextNode = function() { + return $("#container"); +}; + +var assertContextExists = function() { + ok(_getContextNode().length == 1, "context node exists"); +}; + +var assertContextSize = function(elementCount) { + //equals(_getContextNode().children().length - _divs.length, elementCount, 'context has ' + elementCount + ' children'); +}; + +var assertContextEmpty = function() { + equals(_getContextNode().children.length, 0, "context empty"); +}; + +var assertEndpointCount = function(elId, count, _jsPlumb) { + equals(_jsPlumb.getTestHarness().endpointCount(elId), count, elId + " has " + count + (count > 1) ? "endpoints" : "endpoint"); + equals(_jsPlumb.anchorManager.getEndpointsFor(elId).length, count, "anchor manager has " + count + (count > 1) ? "endpoints" : "endpoint"); +}; + +var assertConnectionCount = function(endpoint, count) { + equals(endpoint.connections.length, count, "endpoint has " + count + " connections"); +}; + +var assertConnectionByScopeCount = function(scope, count, _jsPlumb) { + equals(_jsPlumb.getTestHarness().connectionCount(scope), count, 'Scope ' + scope + " has " + count + (count > 1) ? "connections" : "connection"); + +}; + +var _divs = []; +var _addDiv = function(id, parent) { + var d1 = document.createElement("div"); + if (parent) parent.append(d1); else _getContextNode().append(d1); + d1 = jsPlumb.CurrentLibrary.getElementObject(d1); + jsPlumb.CurrentLibrary.setAttribute(d1, "id", id); + _divs.push(id); + return d1; +}; + +var defaults = null, + _cleanup = function(_jsPlumb) { + + _jsPlumb.reset(); + _jsPlumb.Defaults = defaults; + + for (var i in _divs) { + $("#" + _divs[i]).remove(); + } + _divs.splice(0, _divs.length - 1); + + $("#container").empty(); +}; + +var testSuite = function(renderMode, _jsPlumb) { + + + module("jsPlumb", { + teardown: function() { + _cleanup(_jsPlumb); + }, + setup:function() { + defaults = jsPlumb.extend({}, _jsPlumb.Defaults); + } + }); + + // setup the container + var container = document.createElement("div"); + container.id = "container"; + document.body.appendChild(container); + + var jpcl = jsPlumb.CurrentLibrary; + + _jsPlumb.setRenderMode(renderMode); + + test(renderMode + " : getElementObject", function() { + var e = document.createElement("div"); + e.id = "FOO"; + document.body.appendChild(e); + var el = jpcl.getElementObject(e); + equals(jpcl.getAttribute(el, "id"), "FOO"); + }); + + test(renderMode + " : getDOMElement", function() { + var e = document.createElement("div"); + e.id = "FOO"; + document.body.appendChild(e); + var el = jpcl.getElementObject(e); + var e2 = jpcl.getDOMElement(el); + equals(e2.id, "FOO"); + }); + + test(renderMode + ': _jsPlumb setup', function() { + ok(_jsPlumb, "loaded"); + }); + + test(renderMode + ': getId', function() { + var d10 = _addDiv('d10'); + equals(_jsPlumb.getTestHarness().getId(d10), "d10"); + }); + + test(renderMode + ': create a simple endpoint', function() { + var d1 = _addDiv("d1"); + var e = _jsPlumb.addEndpoint($("#d1"), {}); + ok(e, 'endpoint exists'); + assertEndpointCount("d1", 1, _jsPlumb); + ok(e.id != null, "endpoint has had an id assigned"); + }); + + test(renderMode + ': create and remove a simple endpoint', function() { + var d1 = _addDiv("d1"); + var ee = _jsPlumb.addEndpoint(d1, {uuid:"78978597593"}); + ok(ee != null, "endpoint exists"); + var e = _jsPlumb.getEndpoint("78978597593"); + ok(e != null, "the endpoint could be retrieved by UUID"); + ok(e.id != null, "the endpoint has had an id assigned to it"); + assertEndpointCount("d1", 1, _jsPlumb); + assertContextSize(1); // one Endpoint canvas. + _jsPlumb.deleteEndpoint(ee); + assertEndpointCount("d1", 0, _jsPlumb); + e = _jsPlumb.getEndpoint("78978597593"); + equals(e, null, "the endpoint has been deleted"); + assertContextSize(0); // no Endpoint canvases. + }); + + test(renderMode + ': create two simple endpoints, registered using a selector', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + d1.addClass("window");d2.addClass("window"); + var endpoints = _jsPlumb.addEndpoint($(".window"), {}); + equals(endpoints.length, 2, "endpoint added to both windows"); + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + }); + + test(renderMode + ': create two simple endpoints, registered using an array of element ids', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + d1.addClass("window");d2.addClass("window"); + var endpoints = _jsPlumb.addEndpoint(["d1", "d2"], {}); + equals(endpoints.length, 2, "endpoint added to both windows"); + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + }); + + test(renderMode + ': draggable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1"); + var e = _jsPlumb.addEndpoint(d1, {isSource:true}); + ok(e, 'endpoint exists'); + }); + + test(renderMode + ': droppable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1") + var e = _jsPlumb.addEndpoint(d1, {isTarget:true}); + ok(e, 'endpoint exists'); + }); + + test(renderMode + ': draggable in nested element does not cause extra ids to be created', function() { + var d = _addDiv("d1"); + var d2 = document.createElement("div"); + d2.setAttribute("foo", "ff"); + d.append(d2); + var d3 = document.createElement("div"); + d2.appendChild(d3); + ok(d2.getAttribute("id") == null, "no id on d2"); + _jsPlumb.draggable(d); + _jsPlumb.addEndpoint(d3); + ok(d2.getAttribute("id") == null, "no id on d2"); + ok(d3.getAttribute("id") != null, "id on d3"); + }); + + test(renderMode + ': defaultEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = _jsPlumb.addEndpoint(d3, {isSource:true}); + ok(e3.anchor, 'endpoint 3 has an anchor'); + var e4 = _jsPlumb.addEndpoint(d4, {isSource:true}); + assertEndpointCount("d3", 1, _jsPlumb); + assertEndpointCount("d4", 1, _jsPlumb); + assertContextSize(2); // one canvas for each of our two endpoints. + ok(!e3.isFull(), "endpoint 3 is not full."); + _jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // we have one connection + _jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // should have refused the connection; default max is 1. + assertContextSize(3); // now we have one more canvas, for the Connection that was accepted. + }); + + test(renderMode + ': specifiedEndpointMaxConnections', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = _jsPlumb.addEndpoint(d5, {isSource:true, maxConnections:3}); + ok(e5.anchor, 'endpoint 5 has an anchor'); + var e6 = _jsPlumb.addEndpoint(d6, {isSource:true, maxConnections:2}); // this one has max TWO + assertEndpointCount("d5", 1, _jsPlumb); assertEndpointCount("d6", 1, _jsPlumb); + assertContextSize(2); + ok(!e5.isFull(), "endpoint 5 is not full."); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 1); // we have one connection + assertContextSize(3); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // two connections + assertContextSize(4); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // should have refused; max is 2, for d4. + assertContextSize(4); + }); + + test(renderMode + ': noEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = _jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + var e4 = _jsPlumb.addEndpoint(d4, {isSource:true, maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertContextSize(3); + assertConnectionCount(e3, 1); // we have one connection + _jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 2); // we have two. etc (default was one. this proves max is working). + assertContextSize(4); + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = _jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e4}); + assertConnectionCount(e4, 3); + assertContextSize(6); + }); + + test(renderMode + ': anchors equal', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + ok(a1.equals(a2), "anchors are the same"); + }); + + test(renderMode + ': anchors equal with offsets', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb); + ok(a1.equals(a2), "anchors are the same"); + }); + + test(renderMode + ': anchors not equal', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 0, 1], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + ok(!a1.equals(a2), "anchors are different"); + }); + + test(renderMode + ': anchor not equal with offsets', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + ok(!a1.equals(a2), "anchors are different"); + }); + + test(renderMode + ': detach does not fail when no arguments are provided', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + _jsPlumb.connect({source:d3, target:d4}); + _jsPlumb.detach(); + }); + + // test that detach does not fire an event by default + test(renderMode + ': _jsPlumb.detach should fire detach event by default', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach(conn); + equals(eventCount, 1); + }); + + // test that detach does not fire an event by default + test(renderMode + ': _jsPlumb.detach should fire detach event by default, using params object', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({connection:conn}); + equals(eventCount, 1); + }); + + // test that detach fires an event when instructed to do so + test(renderMode + ': _jsPlumb.detach should not fire detach event when instructed to not do so', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach(conn, {fireEvent:false}); + equals(eventCount, 0); + }); + + // issue 81 + test(renderMode + ': _jsPlumb.detach should fire only one detach event (pass Connection as argument)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach(conn); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': _jsPlumb.detach should fire only one detach event (pass Connection as param in argument)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({connection:conn}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': detach should fire only one detach event (pass source and targets as strings as arguments in params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({source:"d5", target:"d6"}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': detach should fire only one detach event (pass source and targets as divs as arguments in params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({source:d5, target:d6, fireEvent:true}); + equals(eventCount, 1); + }); + + //TODO make sure you run this test with a single detach call, to ensure that + // single detach calls result in the connection being removed. detachEveryConnection can + // just blow away the connectionsByScope array and recreate it. + test(renderMode + ': getConnections (simple case, default scope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + _jsPlumb.connect({source:d5, target:d6}); + assertContextSize(3); + var c = _jsPlumb.getConnections(); // will get all connections in the default scope. + equals(c.length, 1, "there is one connection"); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by element id using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + _jsPlumb.connect({source:d5, target:d6}); + _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach({source:'d5', target:'d6'}); + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by id using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + _jsPlumb.connect({source:d5, target:d6}); + _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach({source:"d5", target:"d6"}); + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by element object using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + _jsPlumb.connect({source:d5, target:d6}); + _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach({source:d5, target:d6}); + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by Connection)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + var c56 = _jsPlumb.connect({source:d5, target:d6}); + var c67 = _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach(c56); + assertContextSize(5); // check that the connection canvas was removed. + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + }); + +// beforeDetach functionality + + test(renderMode + ": detach; beforeDetach on connect call returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { return false; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": detach; beforeDetach on connect call returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { return true; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": detach; beforeDetach on connect call throws an exception; we treat it with the contempt it deserves and pretend it said the detach was ok.", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { throw "i am an example of badly coded beforeDetach, but i don't break jsPlumb "; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": detach; beforeDetach on addEndpoint call to source Endpoint returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return false; } }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": detach; beforeDetach on addEndpoint call to source Endpoint returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return true; } }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + + test(renderMode + ": Endpoint.detach; beforeDetach on addEndpoint call to source Endpoint returns false; Endpoint.detach returns false too (the UI needs it to)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return false; } }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + var success = e1.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + ok(!success, "Endpoint reported detach did not execute"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns false but detach is forced", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c, {forceDetach:true}); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was denied but forced anyway"); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was denied but forced anyway"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return true; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach bound to _jsPlumb returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + _jsPlumb.bind("beforeDetach", function(connection) { + ok(connection.sourceId === "d1", "connection is provided and configured with correct source"); + ok(connection.targetId === "d2", "connection is provided and configured with correct target"); + return false; + }); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach bound to _jsPlumb returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + _jsPlumb.bind("beforeDetach", function(connection) { + ok(connection.sourceId === "d1", "connection is provided and configured with correct source"); + ok(connection.targetId === "d2", "connection is provided and configured with correct target"); + return true; + }); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was denied"); + }); + + test(renderMode + ": _jsPlumb.detachAllConnections ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detachAllConnections(d1); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachAll was called"); + }); + + test(renderMode + ": _jsPlumb.detachEveryConnection ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detachEveryConnection(); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachEveryConnection was called"); + }); + + test(renderMode + ": Endpoint.detachAll ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + e1.detachAll(); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachAllConnections was called"); + }); + +// ******** end of beforeDetach tests ************** + +// detachEveryConnection/detachAllConnections fireEvent overrides tests + + test(renderMode + ": _jsPlumb.detachEveryConnection fires events", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0; + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachEveryConnection(); + equals(connCount, 0, "no connections registered"); + }); + + test(renderMode + ": _jsPlumb.detachEveryConnection doesn't fire events when instructed not to", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0; + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachEveryConnection({fireEvent:false}); + equals(connCount, 2, "two connections registered"); + }); + + test(renderMode + ": _jsPlumb.detachAllConnections fires events", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0, + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachAllConnections("d1"); + equals(connCount, 0, "no connections registered"); + }); + + test(renderMode + ": _jsPlumb.detachAllConnections doesn't fire events when instructed not to", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0, + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachAllConnections("d1", {fireEvent:false}); + equals(connCount, 2, "two connections registered"); + }); + + test(renderMode + ': getConnections (scope testScope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + _jsPlumb.connect({source:d5, target:d6, scope:'testScope'}); + var c = _jsPlumb.getConnections("testScope"); // will get all connections in testScope + equals(c.length, 1, "there is one connection"); + equals(c[0].sourceId, 'd5', "the connection's source is d5"); + equals(c[0].targetId, 'd6', "the connection's source is d6"); + c = _jsPlumb.getConnections(); // will get all connections in default scope; should be none. + equals(c.length, 0, "there are no connections in the default scope"); + }); + + test(renderMode + ': _jsPlumb.getAllConnections (filtered by scope)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + _jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + _jsPlumb.connect({source:d9, target:d10}); // default scope + var c = _jsPlumb.getAllConnections(); // will get all connections + equals(c[_jsPlumb.getDefaultScope()].length, 1); + equals(c['testScope'].length, 1); + // now supply a list of scopes + c = _jsPlumb.getConnections(); + equals(c.length, 1); + c = _jsPlumb.getConnections("testScope"); + equals(c.length, 1, "there is one connection in 'testScope'"); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by scope and sourceId)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + _jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + _jsPlumb.connect({source:d9, target:d8, scope:'testScope'}); + _jsPlumb.connect({source:d9, target:d10}); // default scope + var c = _jsPlumb.getConnections({scope:'testScope', source:'d8'}); // will get all connections with sourceId 'd8' + equals(c.length, 1, "there is one connection in 'testScope' from d8"); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by scope, source id and target id)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope'}); + var c = _jsPlumb.getConnections({scope:'testScope', source:'d11', target:'d13'}); + equals(c.length, 1, "there is one connection from d11 to d13"); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = _jsPlumb.getConnections({scope:['testScope','testScope3']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes and source ids)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = _jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes, source ids and target ids)', function() { + _jsPlumb.detachEveryConnection(); + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'), d14=_addDiv("d14"), d15=_addDiv("d15"); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d11, target:d14, scope:'testScope'}); + _jsPlumb.connect({source:d11, target:d15, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + assertContextSize(18); + var c = _jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11'], target:['d14', 'd15']}); + equals(c['testScope'].length, 2, 'there are two connections in testScope'); + equals(c['testScope3'], null, 'there are no connections in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + var anEntry = c['testScope'][0]; + ok(anEntry.endpoints[0] != null, "Source endpoint is set"); + ok(anEntry.endpoints[1] != null, "Target endpoint is set"); + equals(anEntry.source.attr("id"), "d11", "Source is div d11"); + equals(anEntry.target.attr("id"), "d14", "Target is div d14"); + }); + + test(renderMode + ': getEndpoints, one Endpoint added by addEndpoint, get Endpoints by selector', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + _jsPlumb.addEndpoint(d1); + var e = _jsPlumb.getEndpoints(d1); + equals(e.length, 1, "there is one endpoint for element d1"); + }); + + test(renderMode + ': getEndpoints, one Endpoint added by addEndpoint, get Endpoints by id', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + _jsPlumb.addEndpoint(d1); + var e = _jsPlumb.getEndpoints("d1"); + equals(e.length, 1, "there is one endpoint for element d1"); + }); + + test(renderMode + ': connection event listeners', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null/*, returnedParams2 = null*/; + _jsPlumb.bind("jsPlumbConnection", function(params) { + returnedParams = $.extend({}, params); + }); + var c = _jsPlumb.connect({source:d1, target:d2}); + ok(returnedParams != null, "new connection listener event was fired"); + ok(returnedParams.connection != null, 'connection is set'); + equals(returnedParams.sourceId, "d1", 'sourceid is set'); + equals(returnedParams.targetId, "d2", 'targetid is set'); + equals(returnedParams.source.attr("id"), "d1", 'source is set'); + equals(returnedParams.target.attr("id"), "d2" , 'target is set'); + ok(returnedParams.sourceEndpoint != null, "source endpoint is not null"); + ok(returnedParams.targetEndpoint != null, "target endpoint is not null"); + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.detach(c); + ok(returnedParams.connection != null, 'connection is set'); + }); + + test(renderMode + ': detach event listeners (detach by connection)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + ok(returnedParams.connection != null, "removed connection listener event passed in connection"); + }); + + test(renderMode + ': detach event listeners (detach by element ids)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach({source:"d1",target:"d2"}); + ok(returnedParams != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (detach by elements)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach({source:d1,target:d2}); + ok(returnedParams != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detach method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detachFrom method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachFrom(e2); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detachAll method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachAll(); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via _jsPlumb.deleteEndpoint method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + _jsPlumb.deleteEndpoint(e1); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(1); + }); + + test(renderMode + ': detach event listeners (ensure cleared by _jsPlumb.reset)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach({source:d1,target:d2, fireEvent:true}); + ok(returnedParams != null, "removed connection listener event was fired"); + returnedParams = null; + + _jsPlumb.reset(); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach(d1,d2); + ok(returnedParams == null, "connection listener was cleared by _jsPlumb.reset()"); + }); + + test(renderMode + ': connection events that throw errors', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null, returnedParams2 = null; + _jsPlumb.bind("jsPlumbConnection", function(params) { + returnedParams = $.extend({}, params); + throw "oh no!"; + }); + _jsPlumb.connect({source:d1, target:d2}); + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + _jsPlumb.connect({source:d3, target:d4}); + ok(returnedParams != null, "new connection listener event was fired; we threw an error, _jsPlumb survived."); + }); + + test(renderMode + ': unbinding connection event listeners, connection', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var count = 0; + _jsPlumb.bind("jsPlumbConnection", function(params) { + count++; + }); + var c = _jsPlumb.connect({source:d1, target:d2}); + ok(count == 1, "received one event"); + _jsPlumb.unbind("jsPlumbConnection"); + var c2 = _jsPlumb.connect({source:d1, target:d2}); + ok(count == 1, "still received only one event"); + + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + count--; + }); + _jsPlumb.detach(c); + ok(count == 0, "count of events is now zero"); + }); + + test(renderMode + ': unbinding connection event listeners, detach', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var count = 0; + _jsPlumb.bind("jsPlumbConnection", function(params) { + count++; + }); + var c = _jsPlumb.connect({source:d1, target:d2}); + ok(count == 1, "received one event"); + var c2 = _jsPlumb.connect({source:d1, target:d2}); + ok(count == 2, "received two events"); + + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + count--; + }); + _jsPlumb.detach(c); + ok(count == 1, "count of events is now one"); + _jsPlumb.unbind("jsPlumbConnectionDetached"); + _jsPlumb.detach(c2); + ok(count == 1, "count of events is still one"); + }); + + test(renderMode + ': unbinding connection event listeners, all listeners', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var count = 0; + _jsPlumb.bind("jsPlumbConnection", function(params) { + count++; + }); + var c = _jsPlumb.connect({source:d1, target:d2}), + c2 = _jsPlumb.connect({source:d1, target:d2}), + c3 = _jsPlumb.connect({source:d1, target:d2}); + + ok(count == 3, "received three events"); + + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + count--; + }); + _jsPlumb.detach(c); + ok(count == 2, "count of events is now two"); + + _jsPlumb.unbind(); // unbind everything + + _jsPlumb.detach(c2); + _jsPlumb.detach(c3); + _jsPlumb.connect({source:d1, target:d2}) + _jsPlumb.connect({source:d1, target:d2}) + _jsPlumb.connect({source:d1, target:d2}) + _jsPlumb.connect({source:d1, target:d2}) + + ok(count == 2, "count of events is still two"); + }); + + test(renderMode + ": Endpoint.detachFrom", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + e16.detachFrom(e17); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by _jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": Endpoint.detachFromConnection", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + e16.detachFromConnection(conn); + assertContextSize(3); // all canvases should remain; the connection was not removed. + // but endpoint e16 should have no connections now. + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": Endpoint.detachAll", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + var e16 = _jsPlumb.addEndpoint($("#d16"), {isSource:true,maxConnections:-1}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint($("#d17"), {isSource:true}); + var e18 = _jsPlumb.addEndpoint($("#d18"), {isSource:true}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e18}); + assertConnectionCount(e16, 2); + assertConnectionCount(e17, 1); + assertConnectionCount(e18, 1); + assertContextSize(5); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 2, _jsPlumb); + e16.detachAll(); + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": Endpoint.detach", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + e16.detach(conn); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by _jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": Endpoint.isConnectedTo", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.isConnectedTo(e17), true, "e16 and e17 are connected"); + }); + + asyncTest(renderMode + "jsPlumbUtil.setImage on Endpoint, with supplied onload", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { + endpoint:[ "Image", { + src:"../img/endpointTest1.png", + onload:function(imgEp) { + _jsPlumb.repaint("d1"); + ok(imgEp.img.src.indexOf("endpointTest1.png") != -1, "image source is correct"); + ok(imgEp.img.src.indexOf("endpointTest1.png") != -1, "image elementsource is correct"); + + imgEp.canvas.setAttribute("id", "iwilllookforthis"); + + _jsPlumb.removeAllEndpoints("d1"); + ok(document.getElementById("iwilllookforthis") == null, "image element was removed after remove endpoint"); + } + } ] + }; + start(); + _jsPlumb.addEndpoint(d1, e); + }); + + test(renderMode + ": setting endpoint uuid", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + equals(e16.getUuid(), uuid, "endpoint's uuid was set correctly"); + }); + + test(renderMode + ": _jsPlumb.getEndpoint (by uuid)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by uuid, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + _jsPlumb.deleteEndpoint(uuid); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + var ebe = _jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by uuid, connections too)", function() { + // create two endpoints (one with a uuid), add them to two divs and then connect them. + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + // check that the connection was ok. + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + // delete the endpoint that has a uuid. verify that the endpoint cannot be retrieved and that the connection has been removed, but that + // element d17 still has its Endpoint. + _jsPlumb.deleteEndpoint(uuid); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + var ebe = _jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + ebe = _jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 1, "element d17 still has its Endpoint"); + assertContextSize(1); + + // now delete d17's endpoint and check that it has gone. + _jsPlumb.deleteEndpoint(e17); + f = _jsPlumb.getEndpoint(e17); + equals(f, null, "endpoint has been deleted"); + ebe = _jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 0, "element d17 no longer has any Endpoints"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by reference, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + _jsPlumb.deleteEndpoint(e16); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by reference, connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + _jsPlumb.deleteEndpoint(e16); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + assertContextSize(1); + }); + + test(renderMode + ": _jsPlumb.deleteEveryEndpoint", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + assertContextSize(2); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + _jsPlumb.deleteEveryEndpoint(); + + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = _jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEveryEndpoint (connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + _jsPlumb.deleteEveryEndpoint(); + + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = _jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); // no canvases. so all connection canvases have been cleaned up too. + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchor:[0,0.5,0,-1]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:"TopCenter"}); + assertContextSize(2); + equals(e16.anchor.x, 0); + equals(e16.anchor.y, 0.5); + equals(e17.anchor.x, 0.5); + equals(e17.anchor.y, 0); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (tooltip)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {tooltip:"FOO"}), + e17 = _jsPlumb.addEndpoint(d17, {tooltip:"BAZ"}); + assertContextSize(2); + equals(e16.canvas.getAttribute("title"), "FOO"); + equals(e17.canvas.getAttribute("title"), "BAZ"); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case, dynamic anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + assertContextSize(2); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case, two arg method)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchor:[0,0.5,0,-1]}); + var e17 = _jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchor:"TopCenter"}); + assertContextSize(2); + equals(e16.anchor.x, 0); + equals(e16.anchor.y, 0.5); + equals(false, e16.isTarget); + equals(true, e16.isSource); + equals(e17.anchor.x, 0.5); + equals(e17.anchor.y, 0); + equals(true, e17.isTarget); + equals(false, e17.isSource); + }); + + + test(renderMode + ": _jsPlumb.addEndpoints (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false, anchor:[0,0.5,0,-1] }, { isTarget:true, isSource:false, anchor:"TopCenter" }]); + assertContextSize(2); + equals(e16[0].anchor.x, 0); + equals(e16[0].anchor.y, 0.5); + equals(false, e16[0].isTarget); + equals(true, e16[0].isSource); + equals(e16[1].anchor.x, 0.5); + equals(e16[1].anchor.y, 0); + equals(true, e16[1].isTarget); + equals(false, e16[1].isSource); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (with reference params)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var refParams = {anchor:"RightMiddle"}; + var e16 = _jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false}, { isTarget:true, isSource:false }], refParams); + assertContextSize(2); + equals(e16[0].anchor.x, 1); + equals(e16[0].anchor.y, 0.5); + equals(false, e16[0].isTarget); + equals(true, e16[0].isSource); + equals(e16[1].anchor.x, 1); + equals(e16[1].anchor.y, 0.5); + equals(true, e16[1].isTarget); + equals(false, e16[1].isSource); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case, dynamic anchors, two arg method)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchors:["TopCenter", "BottomCenter"]}); + assertContextSize(2); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (default overlays)", function() { + _jsPlumb.Defaults.Overlays = [ + [ "Label", { id:"label" } ] + ]; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16), + e2 = _jsPlumb.addEndpoint(d17); + + ok(e1.getOverlay("label") != null, "endpoint 1 has overlay from defaults"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (default overlays)", function() { + _jsPlumb.Defaults.Overlays = [ + [ "Label", { id:"label" } ] + ]; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16, { + overlays:[ + ["Label", { id:"label2", location:[ 0.5, 1 ] } ] + ] + }), + e2 = _jsPlumb.addEndpoint(d17); + + ok(e1.getOverlay("label") != null, "endpoint 1 has overlay from defaults"); + ok(e1.getOverlay("label2") != null, "endpoint 1 has overlay from addEndpoint call"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (end point set label)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16), + e2 = _jsPlumb.addEndpoint(d17); + + e1.setLabel("FOO"); + equals(e1.getLabel(), "FOO", "endpoint's label is correct"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (end point set label in constructor, as string)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16, {label:"FOO"}), + e2 = _jsPlumb.addEndpoint(d17); + + equals(e1.getLabel(), "FOO", "endpoint's label is correct"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (end point set label in constructor, as function)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16, {label:function() { return "BAZ"; }, labelLocation:0.1}), + e2 = _jsPlumb.addEndpoint(d17); + + equals(e1.getLabel()(), "BAZ", "endpoint's label is correct"); + equals(e1.getLabelOverlay().getLocation(), 0.1, "endpoint's label's location is correct"); + }); + +// ****************** makeTarget (and associated methods) tests ******************************************** + + test(renderMode + ": _jsPlumb.makeTarget (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.makeTarget (specify two divs in an array)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.makeTarget([d16, d17], { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.makeTarget (specify two divs by id in an array)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.makeTarget(["d16", "d17"], { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.makeTarget (specify divs by selector)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + d16.addClass("FOO");d17.addClass("FOO"); + _jsPlumb.makeTarget($(".FOO"), { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e.length, 1, "d17 has one endpoint"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case, two connect calls)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17"}); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 2, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case, two connect calls, uniqueEndpoint set)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true, anchor:"LeftMiddle", uniqueEndpoint:true, maxConnections:-1 }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17"}); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].connections.length, 2, "endpoint on d17 has two connections"); + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (newConnection:true specified)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17", newConnection:true}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0.5, "anchor is BottomCenter"); //here we should be seeing the default anchor + equals(e[0].anchor.y, 1, "anchor is BottomCenter"); //here we should be seeing the default anchor + }); + +// jsPlumb.connect, after makeSource has been called on some element + test(renderMode + ": _jsPlumb.connect after makeSource (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeSource (simple case, two connect calls)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 2, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case, two connect calls, uniqueEndpoint set)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true, anchor:"LeftMiddle", uniqueEndpoint:true, maxConnections:-1 }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].connections.length, 2, "endpoint on d17 has two connections"); + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (newConnection:true specified)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16, newConnection:true}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0.5, "anchor is BottomCenter"); //here we should be seeing the default anchor + equals(e[0].anchor.y, 1, "anchor is BottomCenter"); //here we should be seeing the default anchor + }); + + test(renderMode + ": _jsPlumb.connect after makeSource on child; wth parent set (parent should be recognised)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18", d17); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d18, { isSource:true,anchor:"LeftMiddle", parent:d17 }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + assertEndpointCount("d18", 0, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + // makeSource, then disable it. should not be able to make a connection from it. + test(renderMode + ": _jsPlumb.connect after makeSource and setSourceEnabled(false) (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.setSourceEnabled(d17, false); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + }); + + // makeSource, then disable it. should not be able to make a connection from it. + test(renderMode + ": _jsPlumb.connect after makeSource and setSourceEnabled(false) (selector as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.setSourceEnabled($("div"), false); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + }); + + // makeSource, then toggle its enabled state. should not be able to make a connection from it. + test(renderMode + ": _jsPlumb.connect after makeSource and toggleSourceEnabled() (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.toggleSourceEnabled(d17); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + + _jsPlumb.toggleSourceEnabled(d17); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + // makeSource, then disable it. should not be able to make a connection from it. + test(renderMode + ": _jsPlumb.connect after makeSource and toggleSourceEnabled() (selector as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.toggleSourceEnabled($("div")); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + _jsPlumb.toggleSourceEnabled($("div")); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + test(renderMode + ": jsPlumb.isSource and jsPlumb.isSourceEnabled", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + ok(_jsPlumb.isSource(d17) == true, "d17 is recognised as connection source"); + ok(_jsPlumb.isSourceEnabled(d17) == true, "d17 is recognised as enabled"); + _jsPlumb.setSourceEnabled(d17, false); + ok(_jsPlumb.isSourceEnabled(d17) == false, "d17 is recognised as disabled"); + }); + + // makeSource, then disable it. should not be able to make a connection to it. + test(renderMode + ": _jsPlumb.connect after makeTarget and setTargetEnabled(false) (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.setTargetEnabled(d17, false); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + }); + + // makeTarget, then disable it. should not be able to make a connection to it. + test(renderMode + ": _jsPlumb.connect after makeTarget and setTargetEnabled(false) (selector as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.setTargetEnabled($("div"), false); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + }); + + // makeTarget, then toggle its enabled state. should not be able to make a connection to it. + test(renderMode + ": _jsPlumb.connect after makeTarget and toggleTargetEnabled() (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.toggleTargetEnabled(d17); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + + _jsPlumb.toggleTargetEnabled(d17); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + // makeTarget, then disable it. should not be able to make a connection to it. + test(renderMode + ": _jsPlumb.connect after makeTarget and toggleTargetEnabled() (selector as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.toggleTargetEnabled($("div")); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + _jsPlumb.toggleTargetEnabled($("div")); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + test(renderMode + ": jsPlumb.isTarget and jsPlumb.isTargetEnabled", function() { + var d17 = _addDiv("d17"); + _jsPlumb.makeTarget(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + ok(_jsPlumb.isTarget(d17) == true, "d17 is recognised as connection target"); + ok(_jsPlumb.isTargetEnabled(d17) == true, "d17 is recognised as enabled"); + _jsPlumb.setTargetEnabled(d17, false); + ok(_jsPlumb.isTargetEnabled(d17) == false, "d17 is recognised as disabled"); + }); + + // makeSource, then unmake it. should not be able to make a connection from it. then connect to it, which should succeed, + // because jsPlumb will just add a new endpoint. + test(renderMode + ": jsPlumb.unmakeSource (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + ok(_jsPlumb.isSource(d17) == true, "d17 is currently a source"); + // unmake source + _jsPlumb.unmakeSource(d17); + ok(_jsPlumb.isSource(d17) == false, "d17 is no longer a source"); + + // this should succeed, because d17 is no longer a source and so jsPlumb will just create and add a new endpoint to d17. + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + // maketarget, then unmake it. should not be able to make a connection to it. + test(renderMode + ": jsPlumb.unmakeTarget (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + ok(_jsPlumb.isTarget(d17) == true, "d17 is currently a target"); + // unmake target + _jsPlumb.unmakeTarget(d17); + ok(_jsPlumb.isTarget(d17) == false, "d17 is no longer a target"); + + // this should succeed, because d17 is no longer a target and so jsPlumb will just create and add a new endpoint to d17. + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + + test(renderMode + ": jsPlumb.removeEverySource and removeEveryTarget (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + _jsPlumb.makeSource(d16).makeTarget(d17).makeSource(d18); + ok(_jsPlumb.isSource(d16) == true, "d16 is a source"); + ok(_jsPlumb.isTarget(d17) == true, "d17 is a target"); + ok(_jsPlumb.isSource(d18) == true, "d18 is a source"); + + _jsPlumb.unmakeEverySource(); + _jsPlumb.unmakeEveryTarget(); + + ok(_jsPlumb.isSource(d16) == false, "d16 is no longer a source"); + ok(_jsPlumb.isTarget(d17) == false, "d17 is no longer a target"); + ok(_jsPlumb.isSource(d18) == false, "d18 is no longer a source"); + }); + +// *********************** end of makeTarget (and associated methods) tests ************************ + + test(renderMode + ': _jsPlumb.connect (between two Endpoints)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1, _jsPlumb); // no new endpoint should have been added + assertEndpointCount("d2", 1, _jsPlumb); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. + ok(c.id != null, "connection has had an id assigned"); + }); + + test(renderMode + ': _jsPlumb.connect (Endpoint connectorTooltip parameter)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1, {connectorTooltip:"FOO"}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + equals(c.connector.canvas.getAttribute("title"), "FOO", "connector canvas has label attribute set"); + }); + + test(renderMode + ': _jsPlumb.connect (tooltip parameter)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2, tooltip:"FOO"}); + equals(c.connector.canvas.getAttribute("title"), "FOO", "connector canvas has label attribute set"); + }); + + test(renderMode + ': _jsPlumb.connect (between two Endpoints, and dont supply any parameters to the Endpoints.)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1, _jsPlumb); // no new endpoint should have been added + assertEndpointCount("d2", 1, _jsPlumb); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. + }); + + test(renderMode + " : _jsPlumb.connect, passing 'anchors' array" ,function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, anchors:["LeftMiddle", "RightMiddle"]}); + + equals(c.endpoints[0].anchor.x, 0, "source anchor is at x=0"); + equals(c.endpoints[0].anchor.y, 0.5, "source anchor is at y=0.5"); + equals(c.endpoints[1].anchor.x, 1, "target anchor is at x=1"); + equals(c.endpoints[1].anchor.y, 0.5, "target anchor is at y=0.5"); + }); + + test(renderMode + ': _jsPlumb.connect (by endpoint)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + }); + + test(renderMode + ': _jsPlumb.connect (cost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17, cost:567}); + assertContextSize(3); + equals(c.getCost(), 567, "connection cost is 567"); + }); + + test(renderMode + ': _jsPlumb.connect (default cost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), undefined, "default connection cost is 1"); + }); + + test(renderMode + ': _jsPlumb.connect (set cost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), undefined, "default connection cost is 1"); + c.setCost(8989); + equals(c.getCost(), 8989, "connection cost is 8989"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (connectionCost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionCost:567}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), 567, "connection cost is 567"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (change connectionCost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionCost:567, maxConnections:-1}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1}); + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), 567, "connection cost is 567"); + e16.setConnectionCost(23); + var c2 = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(c2.getCost(), 23, "connection cost is 23 after change on endpoint"); + }); + + test(renderMode + ': _jsPlumb.connect (default bidirectional)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true}), + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.isBidirectional(), true, "default connection is bidirectional"); + }); + + test(renderMode + ': _jsPlumb.connect (bidirectional false)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true}), + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17, bidirectional:false}); + assertContextSize(3); + equals(c.isBidirectional(), false, "connection is not bidirectional"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (connectionsBidirectional)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionsBidirectional:true, maxConnections:-1}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.isBidirectional(), true, "connection is bidrectional"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (change connectionsBidirectional)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionsBidirectional:true, maxConnections:-1}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1}); + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.isBidirectional(), true, "connection is bidirectional"); + e16.setConnectionsBidirectional(false); + var c2 = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(c2.isBidirectional(), false, "connection is not bidirectional"); + }); + + test(renderMode + ": _jsPlumb.connect (two Endpoints - that have been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = _jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1, uuid:srcEndpointUuid}); + var e2 = _jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1, uuid:dstEndpointUuid}); + _jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ] }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertContextSize(3); + }); + + test(renderMode + ": _jsPlumb.connect (two Endpoints - that have not been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ], source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + var e1 = _jsPlumb.getEndpoint(srcEndpointUuid); + ok(e1 != null, "endpoint with src uuid added"); + ok(e1.canvas != null); + var e2 = _jsPlumb.getEndpoint(dstEndpointUuid); + ok(e2 != null, "endpoint with target uuid added"); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.connect (two Endpoints - that have been already added - by endpoint reference)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = _jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1}); + var e2 = _jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1}); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.connect (two elements)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.connect({ source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, straight)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, bezier, no params)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Bezier" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 150, "Bezier connector chose 150 curviness"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, bezier, curviness as int)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", { curviness:200 }] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Canvas Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 200, "Bezier connector chose 200 curviness"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, bezier, curviness as named option)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", {curviness:300}] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 300, "Bezier connector chose 300 curviness"); + }); + + test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Canvas Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as strings)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:["LeftMiddle", "RightMiddle"] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.5, conn.endpoints[0].anchor.y, "source anchor y"); + equals(1, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.5, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + + test(renderMode + ": _jsPlumb.connect (two argument method in which some data is reused across connections)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"), d19 = _addDiv("d19"); + var sharedData = { connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }; + var conn = _jsPlumb.connect({ source:d16, target:d17}, sharedData); + var conn2 = _jsPlumb.connect({ source:d18, target:d19}, sharedData); + assertContextSize(6); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 2, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(conn2.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + equals(0.3, conn2.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn2.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn2.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn2.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoint test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoints test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Dot, "Dot endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Blank Endpoint specified via 'endpoint' param)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Blank" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Blank Endpoint specified via 'endpoints' param)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Blank", "Blank" ] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Dot, "Dot endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, connector test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {}); + var e17 = _jsPlumb.addEndpoint(d17, {}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {}); + var e17 = _jsPlumb.addEndpoint(d17, {}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, anchors as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = "TopCenter", a17 = "BottomCenter"; + var e16 = _jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, 0.5, "endpoint 16 is at top center");equals(e16.anchor.y, 0, "endpoint 16 is at top center"); + equals(e17.anchor.x, 0.5, "endpoint 17 is at bottom center");equals(e17.anchor.y, 1, "endpoint 17 is at bottom center"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = [0,0.5,0,-1], a17 = [1,0.0,-1,-1]; + var e16 = _jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, a16[0]);equals(e16.anchor.y, a16[1]); + equals(e17.anchor.x, a17[0]);equals(e17.anchor.y, a17[1]); + equals(e16.anchor.getOrientation()[0], a16[2]); equals(e16.anchor.getOrientation()[1], a16[3]); + equals(e17.anchor.getOrientation()[0], a17[2]); equals(e17.anchor.getOrientation()[1], a17[3]); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchor')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchor:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:["TopCenter", "BottomCenter"]}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchors')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, default endpoint, supplied dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var anchors = [ [0.25, 0, 0, -1], [1, 0.25, 1, 0], [0.75, 1, 0, 1], [0, 0.75, -1, 0] ]; + _jsPlumb.connect({source:d1, target:d2, dynamicAnchors:anchors}); // auto connect with default endpoint and provided anchors + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.detach({source:d1, target:d2}); + // this changed in 1.3.5, because auto generated endpoints are now removed by detach. so i added the test below this one + // to check that the deleteEndpointsOnDetach flag is honoured. + assertEndpointCount("d1", 0, _jsPlumb);assertEndpointCount("d2", 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, default endpoint, supplied dynamic anchors, delete on detach false)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var anchors = [ [0.25, 0, 0, -1], [1, 0.25, 1, 0], [0.75, 1, 0, 1], [0, 0.75, -1, 0] ]; + _jsPlumb.connect({ + source:d1, + target:d2, + dynamicAnchors:anchors, + deleteEndpointsOnDetach:false + }); // auto connect with default endpoint and provided anchors + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.detach({source:d1, target:d2}); + // this changed in 1.3.5, because auto generated endpoints are now removed by detach. so i added this test + // to check that the deleteEndpointsOnDetach flag is honoured. + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, supplied endpoint and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = _jsPlumb.addEndpoint(d1, endpoint); + var e2 = _jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(3); + _jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, supplied endpoints using 'source' and 'target' (this test is identical to the one above apart from the param names), and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = _jsPlumb.addEndpoint(d1, endpoint); + var e2 = _jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + _jsPlumb.connect({source:e1, target:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(3); + _jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + " detachable parameter defaults to true on _jsPlumb.connect", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), true, "connections detachable by default"); + }); + + test(renderMode + " detachable parameter set to false on _jsPlumb.connect", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, detachable:false}); + equals(c.isDetachable(), false, "connection detachable"); + }); + + test(renderMode + " setDetachable on initially detachable connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), true, "connection initially detachable"); + c.setDetachable(false); + equals(c.isDetachable(), false, "connection not detachable"); + }); + + test(renderMode + " setDetachable on initially not detachable connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, detachable:false }); + equals(c.isDetachable(), false, "connection not initially detachable"); + c.setDetachable(true); + equals(c.isDetachable(), true, "connection now detachable"); + }); + + test(renderMode + " _jsPlumb.Defaults.ConnectionsDetachable", function() { + _jsPlumb.Defaults.ConnectionsDetachable = false; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), false, "connections not detachable by default (overrode the defaults)"); + _jsPlumb.Defaults.ConnectionsDetachable = true; + }); + + + test(renderMode + ": _jsPlumb.connect (testing for connection event callback)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var connectCallback = null, detachCallback = null; + _jsPlumb.bind("jsPlumbConnection", function(params) { + connectCallback = $.extend({}, params); + }); + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + detachCallback = $.extend({}, params); + }); + _jsPlumb.connect({source:d1, target:d2}); // auto connect with default endpoint and anchor set + ok(connectCallback != null, "connect callback was made"); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.detach({source:d1, target:d2}); + assertContextSize(2); + ok(detachCallback != null, "detach callback was made"); + }); + + test(renderMode + ": _jsPlumb.connect (setting cssClass on Connector)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2,cssClass:"CSS"}); + var has = function(clazz) { + var cn = c.connector.canvas.className, + cns = cn.constructor == String ? cn : cn.baseVal; + + return cns.indexOf(clazz) != -1; + }; + ok(has("CSS"), "custom cssClass set correctly"); + ok(has(_jsPlumb.connectorClass), "basic connector class set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (overlays, long-hand version)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = {width:40,length:40,location:0.7, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"}}; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + }); + + test(renderMode + ": _jsPlumb.connect (overlays, long-hand version, IDs specified)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + equals("aLabel", connection1.overlays[0].id); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + equals("anArrow", connection1.overlays[1].id); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays)", function() { + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays + overlays specified in connect call)", function() { + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, overlays:[ + ["Label", {id:"label"}] + ]}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("label") != null, "Label overlay created from connect call"); + }); + + test(renderMode + ": _jsPlumb.connect (default connection overlays)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + }); + + test(renderMode + ": _jsPlumb.connect (default connection overlays + overlays specified in connect call)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, overlays:[ + ["Label", {id:"label"}] + ]}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("label") != null, "Label overlay created from connect call"); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays + default connection overlays)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow2" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("arrow2") != null, "Arrow overlay created from connection defaults"); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays + default connection overlays)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow2" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, overlays:[ + ["Label", {id:"label"}] + ]}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("arrow2") != null, "Arrow overlay created from connection defaults"); + ok(c.getOverlay("label") != null, "Label overlay created from connect call"); + }); + + test(renderMode + ": _jsPlumb.connect (label overlay set using 'label')", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO" + }); + + equals(c.getLabel(), "FOO", "label is set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction, with string)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2}); + + c.setLabel("FOO"); + equals(c.getLabel(), "FOO", "label is set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction, with function)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2}); + + c.setLabel(function() { return "BAR"; }); + equals(c.getLabel()(), "BAR", "label is set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction, with params object)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2}); + + c.setLabel({ + label:"BAZ", + cssClass:"CLASSY", + location:0.9 + }); + var lo = c.getLabelOverlay(); + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "BAZ", "label overlay has correct value"); + equals(lo.getLocation(), 0.9, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction with existing label set, with params object)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO", + labelLocation:0.2 + }); + + var lo = c.getLabelOverlay(); + equals(lo.getLocation(), 0.2, "label overlay has correct location"); + + c.setLabel({ + label:"BAZ", + cssClass:"CLASSY", + location:0.9 + }); + + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "BAZ", "label overlay has correct value"); + equals(lo.getLocation(), 0.9, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (getLabelOverlay, label on connect call)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO" + }), + lo = c.getLabelOverlay(); + + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "FOO", "label overlay has correct value"); + equals(lo.getLocation(), 0.5, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (getLabelOverlay, label on connect call, location set)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO", + labelLocation:0.2 + }), + lo = c.getLabelOverlay(); + + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "FOO", "label overlay has correct value"); + equals(lo.getLocation(), 0.2, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (remove single overlay by id)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + connection1.removeOverlay("aLabel"); + equals(1, connection1.overlays.length); + equals("anArrow", connection1.overlays[0].id); + }); + + test(renderMode + ": _jsPlumb.connect (remove multiple overlays by id)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + connection1.removeOverlays("aLabel", "anArrow"); + equals(0, connection1.overlays.length); + }); + + test(renderMode + ": _jsPlumb.connect (overlays, short-hand version)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var loc = { location:0.7 }; + var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label", {label:"CONNECTION 1", location:0.3}], + ["Arrow", arrowSpec, loc] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + }); + + test(renderMode + ": _jsPlumb.connect (removeAllOverlays)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var loc = { location:0.7 }; + var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label", {label:"CONNECTION 1", location:0.3, cssClass:"PPPP"}], + ["Arrow", arrowSpec, loc] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + + connection1.removeAllOverlays(); + equals(0, connection1.overlays.length); + equals(0, $(".PPPP").length); + }); + + test(renderMode + ": _jsPlumb.connect, specify arrow overlay using string identifier only", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:["Arrow"]}); + equals(jsPlumb.Overlays[renderMode].Arrow, conn.overlays[0].constructor); + }); + + test(renderMode + ": Connection.getOverlay method, existing overlay", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("arrowOverlay"); + ok(overlay != null); + }); + + test(renderMode + ": Connection.getOverlay method, non-existent overlay", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("IDONTEXIST"); + ok(overlay == null); + }); + + test(renderMode + ": Overlay.setVisible method", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("arrowOverlay"); + ok(overlay.isVisible()); + overlay.setVisible(false); + ok(!overlay.isVisible()); + overlay.setVisible(true); + ok(overlay.isVisible()); + }); + + test(renderMode + ": _jsPlumb.connect (custom label overlay, set on Defaults, return plain DOM element)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Custom",{ id:"custom", create:function(connection) { + ok(connection != null, "we were passed in a connection"); + var d = document.createElement("div"); + d.setAttribute("custom", "true"); + d.innerHTML = connection.id; + return d; + }}] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + var o = c.getOverlay("custom"); + equals(o.getElement().getAttribute("custom"), "true", "custom overlay created correctly"); + equals(o.getElement().innerHTML, c.id, "custom overlay has correct value"); + }); + + test(renderMode + ": _jsPlumb.connect (custom label overlay, set on Defaults, return selector)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Custom",{ id:"custom", create:function(connection) { + ok(connection != null, "we were passed in a connection"); + return $("
" + connection.id + "
"); + }}] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + var o = c.getOverlay("custom"); + equals(o.getElement().getAttribute("custom"), "true", "custom overlay created correctly"); + equals(o.getElement().innerHTML, c.id, "custom overlay has correct value"); + }); + + // this test is for the original detach function; it should stay working after i mess with it + // a little. + test(renderMode + ": _jsPlumb.detach (by element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + var e3 = _jsPlumb.addEndpoint(d1); + var e4 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + _jsPlumb.connect({ sourceEndpoint:e3, targetEndpoint:e4 }); // make two connections to be sure this works ;) + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionCount(e3, 1); + assertConnectionCount(e4, 1); + + _jsPlumb.detach({source:"d1", target:"d2"}); + + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionCount(e3, 0); + assertConnectionCount(e4, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + // detach is being made to operate more like connect - by taking one argument with a whole + // bunch of possible params in it. if two args are passed in it will continue working + // in the old way. + test(renderMode + ": _jsPlumb.detach (params object, using element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({source:"d1", target:"d2"}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); +/* + test(renderMode + ": _jsPlumb.detach (params object, using target only)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e1 = _jsPlumb.addEndpoint(d1, {maxConnections:2}), + e2 = _jsPlumb.addEndpoint(d2), + e3 = _jsPlumb.addEndpoint(d3); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e3 }); + assertConnectionCount(e1, 2); + assertConnectionCount(e2, 1); + assertConnectionCount(e3, 1); + _jsPlumb.detach({target:"d2"}); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 0); + assertConnectionCount(e3, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1); + }); +*/ + test(renderMode + ": _jsPlumb.detach (params object, using element objects)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({source:d1, target:d2}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.detach (source and target as endpoint UUIDs)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {uuid:"abcdefg"}); + ok(_jsPlumb.getEndpoint("abcdefg") != null, "e1 exists"); + var e2 = _jsPlumb.addEndpoint(d2, {uuid:"hijklmn"}); + ok(_jsPlumb.getEndpoint("hijklmn") != null, "e2 exists"); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({uuids:["abcdefg", "hijklmn"]}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.detach (sourceEndpoint and targetEndpoint supplied)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.makeDynamicAnchors (longhand)", function() { + var anchors = [_jsPlumb.makeAnchor([0.2, 0, 0, -1], null, _jsPlumb), _jsPlumb.makeAnchor([1, 0.2, 1, 0], null, _jsPlumb), + _jsPlumb.makeAnchor([0.8, 1, 0, 1], null, _jsPlumb), _jsPlumb.makeAnchor([0, 0.8, -1, 0], null, _jsPlumb) ]; + var dynamicAnchor = _jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); + }); + + test(renderMode + ": _jsPlumb.makeDynamicAnchors (shorthand)", function() { + var anchors = [[0.2, 0, 0, -1], [1, 0.2, 1, 0], + [0.8, 1, 0, 1], [0, 0.8, -1, 0] ]; + var dynamicAnchor = _jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); + }); + + test(renderMode + ": Connection.isVisible/setVisible", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c1 = _jsPlumb.connect({source:d1,target:d2}); + equals(true, c1.isVisible(), "Connection is visible after creation."); + c1.setVisible(false); + equals(false, c1.isVisible(), "Connection is not visible after calling setVisible(false)."); + equals($(c1.connector.canvas).css("display"), "none"); + c1.setVisible(true); + equals(true, c1.isVisible(), "Connection is visible after calling setVisible(true)."); + equals($(c1.connector.canvas).css("display"), "block"); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible basic test (no connections)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals($(e1.canvas).css("display"), "none"); + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals($(e1.canvas).css("display"), "block"); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should track changes in the source, because it has only this connection.)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + var c1 = _jsPlumb.connect({source:e1, target:e2}); + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals(false, e2.isVisible(), "other Endpoint is not visible either."); + equals(false, c1.isVisible(), "connection between the two is not visible either."); + + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals(true, e2.isVisible(), "other Endpoint is visible too"); + equals(true, c1.isVisible(), "connection between the two is visible too."); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should not track changes in the source, because it has another connection.)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2, { maxConnections:2 }), e3 = _jsPlumb.addEndpoint(d3); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + var c1 = _jsPlumb.connect({source:e1, target:e2}); + var c2 = _jsPlumb.connect({source:e2, target:e3}); + + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals(true, e2.isVisible(), "other Endpoint should still be visible."); + equals(true, e3.isVisible(), "third Endpoint should still be visible."); + equals(false, c1.isVisible(), "connection between the two is not visible either."); + equals(true, c2.isVisible(), "other connection is visible."); + + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals(true, e2.isVisible(), "other Endpoint is visible too"); + equals(true, c1.isVisible(), "connection between the two is visible too."); + equals(true, c2.isVisible(), "other connection is visible."); + }); + + // tests of the functionality that allows a user to specify that they want elements appended to the document body + test(renderMode + " _jsPlumb.Defaults.Container, specified with a selector", function() { + _jsPlumb.Defaults.Container = $("body"); + equals($("#container")[0].childNodes.length, 0, "container has no nodes"); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals($("#container")[0].childNodes.length, 2, "container has two div elements"); // the divs we added have been added to the 'container' div. + // but we have told _jsPlumb to add its canvas to the body, so this connect call should not add another few elements to the container: + _jsPlumb.connect({source:d1, target:d2}); + equals($("#container")[0].childNodes.length, 2, "container still has two div elements"); + }); + + // tests of the functionality that allows a user to specify that they want elements appended to some specific container. + test(renderMode + " _jsPlumb.Defaults.Container, specified with DOM element", function() { + _jsPlumb.Defaults.Container = document.getElementsByTagName("body")[0]; + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length, "two divs added to the container"); // the divs we added have been added to the 'container' div. + // but we have told _jsPlumb to add its canvas to the body, so this connect call should not add another few elements to the container: + var bodyElementCount = $("body")[0].childNodes.length; + _jsPlumb.connect({source:d1, target:d2}); + equals(2, $("#container")[0].childNodes.length, "still only two children in container; elements were added to the body by _jsPlumb"); + // test to see if 3 elements have been added + equals(bodyElementCount + 3, $("body")[0].childNodes.length, "3 new elements added to the document body"); + }); + + test(renderMode + " container specified to connect call, with a selector", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length); // the divs we added have been added to the 'container' div. + var bodyElementCount = $("body")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to the body, so this connect call should not add another few elements to the container: + _jsPlumb.connect({source:d1, target:d2, container:$("body")}); + equals(2, $("#container")[0].childNodes.length); + equals(bodyElementCount + 3, $("body")[0].childNodes.length, "3 new elements added to the document body"); + }); + + test(renderMode + " container specified to connect call, with a string ID", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + equals(3, $("#container")[0].childNodes.length, "container has divs we added"); // the divs we added have been added to the 'container' div. + var d3ElementCount = $("#d3")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to "d3", so this connect call should not add another few elements to the container: + _jsPlumb.connect({source:d1, target:d2, container:"d3"}); + equals(3, $("#container")[0].childNodes.length, "container still has only the divs we added"); + equals(d3ElementCount + 3, $("#d3")[0].childNodes.length, "3 new elements added to div d3"); + }); + + test(renderMode + " container specified to addEndpoint call, with a selector", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length); // the divs we added have been added to the 'container' div. + var bodyElementCount = $("body")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to the body, so this connect call should not add another few elements to the container: + _jsPlumb.addEndpoint(d1, {container:$("body")}); + equals(2, $("#container")[0].childNodes.length); + equals(bodyElementCount + 1, $("body")[0].childNodes.length, "1 new element added to the document body"); + }); + + test(renderMode + " container specified to addEndpoint call, with a string ID", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + equals(3, $("#container")[0].childNodes.length, "container has divs we added"); // the divs we added have been added to the 'container' div. + var d3ElementCount = $("#d3")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to "d3", so this connect call should not add another few elements to the container: + _jsPlumb.addEndpoint(d1, { container:"d3" }); + equals(3, $("#container")[0].childNodes.length, "container still has only the divs we added"); + equals(d3ElementCount + 1, $("#d3")[0].childNodes.length, "1 new element added to div d3"); + }); + + test(renderMode + " detachable defaults to true when connection made between two endpoints", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2), + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection not detachable"); + }); + + test(renderMode + " connection detachable when target endpoint has connectionsDetachable set to true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2, {connectionsDetachable:true}), + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection detachable because connectionsDetachable was set on target endpoint"); + }); + + test(renderMode + " connection detachable when source endpoint has connectionsDetachable set to true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {connectionsDetachable:true}), e2 = _jsPlumb.addEndpoint(d2), + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection detachable because connectionsDetachable was set on source endpoint"); + }); + + test(renderMode + " Connector has 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.connector.type, "Bezier", "Bezier connector has type set"); + + var c2 = _jsPlumb.connect({source:d1, target:d2, connector:"Straight"}); + equals(c2.connector.type, "Straight", "Straight connector has type set"); + + var c3 = _jsPlumb.connect({source:d1, target:d2, connector:"Flowchart"}); + equals(c3.connector.type, "Flowchart", "Flowchart connector has type set"); + }); + + test(renderMode + " Endpoints have 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.endpoints[0].type, "Dot", "Dot endpoint has type set"); + + var c2 = _jsPlumb.connect({source:d1, target:d2, endpoints:["Rectangle", "Blank"]}); + equals(c2.endpoints[1].type, "Blank", "Blank endpoint has type set"); + equals(c2.endpoints[0].type, "Rectangle", "Rectangle endpoint has type set"); + }); + + test(renderMode + " Overlays have 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = _jsPlumb.connect({ + source:d1, + target:d2, + overlays:[ "Arrow", "Label", "PlainArrow", "Diamond" ] + }); + equals(c.overlays[0].type, "Arrow", "Arrow overlay has type set"); + equals(c.overlays[1].type, "Label", "Label overlay has type set"); + equals(c.overlays[2].type, "PlainArrow", "PlainArrow overlay has type set"); + equals(c.overlays[3].type, "Diamond", "Diamond overlay has type set"); + }); + + test(renderMode + " _jsPlumb.hide, original one-arg version", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + _jsPlumb.hide(d1); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(true, e1.isVisible(), "endpoint 1 is still visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + _jsPlumb.show(d1); + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + }); + + test(renderMode + " _jsPlumb.hide, two-arg version, endpoints should also be hidden", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + _jsPlumb.show(d1); // now show d1, but do not alter the endpoints. e1 should still be hidden + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + test(renderMode + " _jsPlumb.show, two-arg version, endpoints should become visible", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + _jsPlumb.show(d1, true); // now show d1, and alter the endpoints. e1 should be visible. + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(true, e1.isVisible(), "endpoint 1 is visible again."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + test(renderMode + " _jsPlumb.show, two-arg version, endpoints should become visible, but not all connections, because some other endpoints are not visible.", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e11 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + e3 = _jsPlumb.addEndpoint(d3, e), + c1 = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e11, target:e3}); + + // we now have d1 connected to both d3 and d2. we'll hide d1, and everything on d1 should be hidden. + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "connection 1 is no longer visible."); + equals(false, c2.isVisible(), "connection 2 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(false, e11.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + equals(true, e3.isVisible(), "endpoint 3 is still visible."); + + // now, we will also hide d3. making d1 visible again should NOT result in c2 becoming visible, because the other endpoint + // for c2 is e3, which is not visible. + _jsPlumb.hide(d3, true); + equals(false, e3.isVisible(), "endpoint 3 is no longer visible."); + + _jsPlumb.show(d1, true); // now show d1, and alter the endpoints. e1 should be visible, c1 should be visible, but c2 should not. + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(false, c2.isVisible(), "Connection 2 is not visible."); + equals(true, e1.isVisible(), "endpoint 1 is visible again."); + equals(true, e11.isVisible(), "endpoint 11 is visible again."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + equals(false, e3.isVisible(), "endpoint 3 is still not visible."); + }); + + /* + test(renderMode + " _jsPlumb.hide, two-arg version, endpoints should also be hidden", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + /** + * test for issue 132: label leaves its element in the DOM after it has been + * removed from a connection. + */ + test(renderMode + " label cleans itself up properly", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label", cssClass:"foo"}] + ]}); + ok($(".foo").length == 1, "label element exists in DOM"); + c.removeOverlay("label"); + ok($(".foo").length == 0, "label element does not exist in DOM"); + }); + + test(renderMode + " arrow cleans itself up properly", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Arrow", {id:"arrow"}] + ]}); + ok(c.getOverlay("arrow") != null, "arrow overlay exists"); + c.removeOverlay("arrow"); + ok(c.getOverlay("arrow") == null, "arrow overlay has been removed"); + }); + + test(renderMode + " label overlay getElement function", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label"}] + ]}); + ok(c.getOverlay("label").getElement() != null, "label overlay exposes element via getElement method"); + }); + + test(renderMode + " label overlay provides getLabel and setLabel methods", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label", label:"foo"}] + ]}); + var o = c.getOverlay("label"), e = o.getElement(); + ok(e.innerHTML == "foo", "label text is set to original value"); + o.setLabel("baz"); + equals(e.innerHTML, "baz", "label text is set to new value 'baz'"); + equals(o.getLabel(), "baz", "getLabel function works correctly with String"); + // now try functions + var aFunction = function() { return "aFunction"; }; + o.setLabel(aFunction); + equals(e.innerHTML, "aFunction", "label text is set to new value from Function"); + equals(o.getLabel(), aFunction, "getLabel function works correctly with Function"); + }); + + test(renderMode + " parameters object works for Endpoint", function() { + var d1 = _addDiv("d1"), + f = function() { alert("FOO!"); }, + e = _jsPlumb.addEndpoint(d1, { + isSource:true, + parameters:{ + "string":"param1", + "int":4, + "function":f + } + }); + ok(e.getParameter("string") === "param1", "getParameter(String) works correctly"); + ok(e.getParameter("int") === 4, "getParameter(int) works correctly"); + ok(e.getParameter("function") == f, "getParameter(Function) works correctly"); + }); + + test(renderMode + " parameters object works for Connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + f = function() { alert("FOO!"); }; + var c = _jsPlumb.connect({ + source:d1, + target:d2, + parameters:{ + "string":"param1", + "int":4, + "function":f + } + }); + ok(c.getParameter("string") === "param1", "getParameter(String) works correctly"); + ok(c.getParameter("int") === 4, "getParameter(int) works correctly"); + ok(c.getParameter("function") == f, "getParameter(Function) works correctly"); + }); + + test(renderMode + " parameters set on Endpoints and Connections are all merged, and merged correctly at that.", function() { + var d1 = _addDiv("d1"), + d2 = _addDiv("d2"), + e = _jsPlumb.addEndpoint(d1, { + isSource:true, + parameters:{ + "string":"sourceEndpoint", + "int":0, + "function":function() { return "sourceEndpoint"; } + } + }), + e2 = _jsPlumb.addEndpoint(d2, { + isTarget:true, + parameters:{ + "int":1, + "function":function() { return "targetEndpoint"; } + } + }), + c = _jsPlumb.connect({source:e, target:e2, parameters:{ + "function":function() { return "connection"; } + }}); + + ok(c.getParameter("string") === "sourceEndpoint", "getParameter(String) works correctly"); + ok(c.getParameter("int") === 1, "getParameter(int) works correctly"); + ok(c.getParameter("function")() == "connection", "getParameter(Function) works correctly"); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers standard connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2}); + equals(_jsPlumb.anchorManager.getConnectionsFor("d1").length, 1); + equals(_jsPlumb.anchorManager.getEndpointsFor("d1").length, 1); + equals(_jsPlumb.anchorManager.getConnectionsFor("d2").length, 1); + equals(_jsPlumb.anchorManager.getEndpointsFor("d2").length, 1); + var c2 = _jsPlumb.connect({source:d1, target:d2}); + equals(_jsPlumb.anchorManager.getConnectionsFor("d1").length, 2); + equals(_jsPlumb.anchorManager.getConnectionsFor("d2").length, 2); + equals(_jsPlumb.anchorManager.getEndpointsFor("d1").length, 2); + equals(_jsPlumb.anchorManager.getEndpointsFor("d2").length, 2); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers dynamic anchor connection, and removes it.", function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var c = _jsPlumb.connect({source:d3, target:d4, anchors:["AutoDefault", "AutoDefault"]}); + + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 1); + + var c2 = _jsPlumb.connect({source:d3, target:d4}); + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 2); + equals(_jsPlumb.anchorManager.getConnectionsFor("d4").length, 2); + + equals(_jsPlumb.anchorManager.getEndpointsFor("d3").length, 2); + _jsPlumb.detach(c); + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 1); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers continuous anchor connection, and removes it.", function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var c = _jsPlumb.connect({source:d3, target:d4, anchors:["Continuous", "Continuous"]}); + + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 1); + equals(_jsPlumb.anchorManager.getConnectionsFor("d4").length, 1); + + _jsPlumb.detach(c); + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 0); + equals(_jsPlumb.anchorManager.getConnectionsFor("d4").length, 0); + + _jsPlumb.reset(); + equals(_jsPlumb.anchorManager.getEndpointsFor("d4").length, 0); + }); + +// ------------- utility functions - math stuff, mostly -------------------------- + + var tolerance = 0.00000005, withinTolerance = function(v1, v2, msg) { + if (Math.abs(v1 - v2) < tolerance) ok(true, msg + "; expected " + v1 + " and got it"); + else { + ok(false, msg + "; expected " + v1 + " got " + v2); + } + }; + test(renderMode + " jsPlumbUtil.gradient, segment 1", function() { + var p1 = [2,2], p2 = [3,1], m = jsPlumbUtil.gradient(p1,p2); + equals(m, -1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.normal, segment 1", function() { + var p1 = [2,2], p2 = [3,1], m = jsPlumbUtil.normal(p1,p2); + equals(m, 1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.gradient, segment 2", function() { + var p1 = [2,2], p2 = [3,3], m = jsPlumbUtil.gradient(p1,p2); + equals(m, 1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.normal, segment 2", function() { + var p1 = [2,2], p2 = [3,3], m = jsPlumbUtil.normal(p1,p2); + equals(m, -1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.gradient, segment 3", function() { + var p1 = [2,2], p2 = [1,3], m = jsPlumbUtil.gradient(p1,p2); + equals(m, -1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.normal, segment 3", function() { + var p1 = [2,2], p2 = [1,3], m = jsPlumbUtil.normal(p1,p2); + equals(m, 1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.gradient, segment 4", function() { + var p1 = [2,2], p2 = [1,1], m = jsPlumbUtil.gradient(p1,p2); + equals(m, 1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.normal, segment 4", function() { + var p1 = [2,2], p2 = [1,1], m = jsPlumbUtil.normal(p1,p2); + equals(m, -1, "normal calculated correctly for simple case"); + }); + test(renderMode + "jsPlumbUtil.pointOnLine, segment 1", function() { + var p1 = {x:2,y:2}, p2={x:3, y:1}, + target = jsPlumbUtil.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumbUtil.pointOnLine, segment 2", function() { + var p1 = {x:2,y:2}, p2={x:3, y:3}, + target = jsPlumbUtil.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumbUtil.pointOnLine, segment 3", function() { + var p1 = {x:2,y:2}, p2={x:1, y:3}, + target = jsPlumbUtil.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumbUtil.pointOnLine, segment 4", function() { + var p1 = {x:2,y:2}, p2={x:1, y:1}, + target = jsPlumbUtil.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumbUtil.perpendicularLineTo, segment 1", function() { + var p1 = {x:2, y:2}, p2={x:3, y:1}, m = jsPlumbUtil.gradient(p1, p2), + l = jsPlumbUtil.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(4, l[0].x, "point 1 x is correct"); + withinTolerance(2, l[0].y, "point 1 y is correct"); + + withinTolerance(2, l[1].x, "point 2 x is correct"); + withinTolerance(0, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumbUtil.perpendicularLineTo, segment 2", function() { + var p1 = {x:2, y:2}, p2={x:3, y:3}, m = jsPlumbUtil.gradient(p1, p2), + l = jsPlumbUtil.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(4, l[0].x, "point 1 x is correct"); + withinTolerance(2, l[0].y, "point 1 y is correct"); + + withinTolerance(2, l[1].x, "point 2 x is correct"); + withinTolerance(4, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumbUtil.perpendicularLineTo, segment 3", function() { + var p1 = {x:2, y:2}, p2={x:1, y:3}, m = jsPlumbUtil.gradient(p1, p2), + l = jsPlumbUtil.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(2, l[0].x, "point 1 x is correct"); + withinTolerance(4, l[0].y, "point 1 y is correct"); + + withinTolerance(0, l[1].x, "point 2 x is correct"); + withinTolerance(2, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumbUtil.perpendicularLineTo, segment 4", function() { + var p1 = {x:2, y:2}, p2={x:1, y:1}, m = jsPlumbUtil.gradient(p1, p2), + l = jsPlumbUtil.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(2, l[0].x, "point 1 x is correct"); + withinTolerance(0, l[0].y, "point 1 y is correct"); + + withinTolerance(0, l[1].x, "point 2 x is correct"); + withinTolerance(2, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumbUtil.intersects, with intersection", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 3, y:4, w:3, h:3}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, with no intersection", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 13, y:4, w:3, h:3}; + + ok(!jsPlumbUtil.intersects(r1, r2), "r1 and r2 do not intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, with intersection, equal Y", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 1, y:2, w:3, h:6}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, with intersection, equal X", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 2, y:1, w:4, h:6}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, identical rectangles", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 2, y:2, w:4, h:6}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, corners touch (intersection)", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 6, y:8, w:3, h:3}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, one rectangle contained within the other", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 0, y:0, w:23, h:23}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "setImage on Endpoint", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + originalUrl = "../../img/endpointTest1.png", + e = { + endpoint:[ "Image", { src:originalUrl } ] + }, + ep = _jsPlumb.addEndpoint(d1, e); + }); + test(renderMode + "setImage on Endpoint, with supplied onload", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { + endpoint:[ "Image", { + src:"../../img/endpointTest1.png", + onload:function(imgEp) { + equals("../../img/endpointTest1.png", imgEp.img.src); + equals(ep.endpoint.canvas.getAttribute("src"), imgEp.img.src); + } + } ] + }, + ep = _jsPlumb.addEndpoint(d1, e); + ep.setImage("../../img/littledot.png", function(imgEp) { + equals("../../img/littledot.png", imgEp.img.src); + equals(ep.endpoint.canvas.getAttribute("src"), imgEp.img.src); + }); + }); + + test(renderMode + "attach endpoint to table when desired element was td", function() { + var table = document.createElement("table"), + tr = document.createElement("tr"), + td = document.createElement("td"); + table.appendChild(tr); tr.appendChild(td); + document.body.appendChild(table); + var ep = _jsPlumb.addEndpoint(td); + equals(ep.canvas.parentNode.tagName.toLowerCase(), "table"); + }); + +// issue 190 - regressions with getInstance. these tests ensure that generated ids are unique across +// instances. + + test(renderMode + " id clashes between instances", function() { + var d1 = document.createElement("div"), + d2 = document.createElement("div"), + _jsp2 = jsPlumb.getInstance(); + + document.body.appendChild(d1); + document.body.appendChild(d2); + + _jsPlumb.addEndpoint(d1); + _jsp2.addEndpoint(d2); + + var id1 = d1.getAttribute("id"), + id2 = d2.getAttribute("id"); + + var idx = id1.indexOf("_"), idx2 = id1.lastIndexOf("_"), v1 = id1.substring(idx, idx2); + var idx3 = id2.indexOf("_"), idx4 = id2.lastIndexOf("_"), v2 = id2.substring(idx3, idx4); + + ok (v1 != v2, "instance versions are different : " + v1 + " : " + v2); + }); + + test(renderMode + " id clashes between instances", function() { + var d1 = document.createElement("div"), + d2 = document.createElement("div"), + _jsp2 = jsPlumb.getInstance(); + + document.body.appendChild(d1); + document.body.appendChild(d2); + + _jsPlumb.addEndpoint(d1); + _jsPlumb.addEndpoint(d2); + + var id1 = d1.getAttribute("id"), + id2 = d2.getAttribute("id"); + + var idx = id1.indexOf("_"), idx2 = id1.lastIndexOf("_"), v1 = id1.substring(idx, idx2); + var idx3 = id2.indexOf("_"), idx4 = id2.lastIndexOf("_"), v2 = id2.substring(idx3, idx4); + + ok (v1 == v2, "instance versions are the same : " + v1 + " : " + v2); + }); + + test(renderMode + " importDefaults", function() { + _jsPlumb.Defaults.Anchors = ["LeftMiddle", "RightMiddle"]; + var d1 = _addDiv("d1"), + d2 = _addDiv(d2), + c = _jsPlumb.connect({source:d1, target:d2}), + e = c.endpoints[0]; + + equals(e.anchor.x, 0, "left middle anchor"); + equals(e.anchor.y, 0.5, "left middle anchor"); + + _jsPlumb.importDefaults({ + Anchors:["TopLeft", "TopRight"] + }); + + var conn = _jsPlumb.connect({source:d1, target:d2}), + e1 = conn.endpoints[0], e2 = conn.endpoints[1]; + + equals(e1.anchor.x, 0, "top leftanchor"); + equals(e2.anchor.y, 0, "top left anchor"); + equals(e2.anchor.x, 1, "top right anchor"); + equals(e2.anchor.y, 0, "top right anchor"); + + }); + + test(renderMode + " restoreDefaults", function() { + _jsPlumb.importDefaults({ + Anchors:["TopLeft", "TopRight"] + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), conn = _jsPlumb.connect({source:d1, target:d2}), + e1 = conn.endpoints[0], e2 = conn.endpoints[1]; + + equals(e1.anchor.x, 0, "top leftanchor"); + equals(e2.anchor.y, 0, "top left anchor"); + equals(e2.anchor.x, 1, "top right anchor"); + equals(e2.anchor.y, 0, "top right anchor"); + + _jsPlumb.restoreDefaults(); + + var conn2 = _jsPlumb.connect({source:d1, target:d2}), + e3 = conn2.endpoints[0], e4 = conn2.endpoints[1]; + + equals(e3.anchor.x, 0.5, "bottom center anchor"); + equals(e3.anchor.y, 1, "bottom center anchor"); + equals(e4.anchor.x, 0.5, "bottom center anchor"); + equals(e4.anchor.y, 1, "bottom center anchor"); + }); + +// setId function + + test(renderMode + " setId, taking two strings, only default scope", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId("d1", "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a selector and a string, only default scope", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1"), "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a DOM element and a string, only default scope", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1")[0], "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking two strings, mix of scopes", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2, scope:"FOO"}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId("d1", "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a selector and a string, mix of scopes", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2, scope:"FOO"}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1"), "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a DOM element and a string, mix of scopes", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2, scope:"FOO"}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1")[0], "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setIdChanged, ", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + $("#d1").attr("id", "d3"); + + _jsPlumb.setIdChanged("d1", "d3"); + + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " endpoint hide/show should hide/show overlays", function() { + _addDiv("d1"); + var e1 = _jsPlumb.addEndpoint("d1", { + overlays:[ + [ "Label", { id:"label", label:"foo" } ] + ] + }), + o = e1.getOverlay("label"); + + ok(o.isVisible(), "overlay is initially visible"); + _jsPlumb.hide("d1", true); + ok(!o.isVisible(), "overlay is no longer visible"); + }); + + test(renderMode + " connection hide/show should hide/show overlays", function() { + _addDiv("d1");_addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", + overlays:[ + [ "Label", { id:"label", label:"foo" } ] + ] + }), + o = c.getOverlay("label"); + + ok(o.isVisible(), "overlay is initially visible"); + _jsPlumb.hide("d1", true); + ok(!o.isVisible(), "overlay is no longer visible"); + }); + + test(renderMode + " select, basic test", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2"}), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 1, "one connection selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; dont filter on scope.", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 2, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scope", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + s = _jsPlumb.select({source:"d1", scope:"FOO"}); + + equals(s.length, 1, "one connection selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scopes", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + c3 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAZ"}) + s = _jsPlumb.select({source:"d1", scope:["FOO","BAR"]}); + + equals(s.length, 2, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; scope but no scope filter; single source id", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + c3 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAZ"}), + c4 = _jsPlumb.connect({source:"d2", target:"d1", scope:"BOZ"}), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 3, "three connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scopes; single source id", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + c3 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAZ"}), + c4 = _jsPlumb.connect({source:"d2", target:"d1", scope:"BOZ"}), + s = _jsPlumb.select({source:"d1", scope:["FOO","BAR", "BOZ"]}); + + equals(s.length, 2, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scope; dont supply sourceid", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + s = _jsPlumb.select({ scope:"FOO" }); + + equals(s.length, 1, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scope; dont supply sourceid", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d2", target:"d1", scope:"BAR"}), + s = _jsPlumb.select({ scope:"FOO" }); + + equals(s.length, 1, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, two connections, with overlays", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({ + source:"d1", + target:"d2", + overlays:[ + ["Label", {id:"l"}] + ] + }), + c2 = _jsPlumb.connect({ + source:"d1", + target:"d2", + overlays:[ + ["Label", {id:"l"}] + ] + }), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 2, "two connections selected"); + ok(s.get(0).getOverlay("l") != null, "connection has overlay"); + ok(s.get(1).getOverlay("l") != null, "connection has overlay"); + }); + + test(renderMode + " select, chaining with setHover and hideOverlay", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({ + source:"d1", + target:"d2", + overlays:[ + ["Label", {id:"l"}] + ] + }); + s = _jsPlumb.select({source:"d1"}); + + s.setHover(false).hideOverlay("l"); + + ok(!(s.get(0).isHover()), "connection is not hover"); + ok(!(s.get(0).getOverlay("l").isVisible()), "overlay is not visible"); + }); + + test(renderMode + " select, .each function", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10) + }); + } + + var s = _jsPlumb.select(); + equals(s.length, 5, "there are five connections"); + + var t = ""; + s.each(function(connection) { + t += "f"; + }); + equals("fffff", t, ".each is working"); + }); + + test(renderMode + " select, multiple connections + chaining", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + overlays:[ + ["Arrow", {location:0.3}], + ["Arrow", {location:0.7}] + ] + }); + } + + var s = _jsPlumb.select().removeAllOverlays().setParameter("foo", "bar").setHover(false).setLabel("baz"); + equals(s.length, 5, "there are five connections"); + + for (var j = 0; j < 5; j++) { + equals(s.get(j).getOverlays().length, 1, "one overlay: the label"); + equals(s.get(j).getParameter("foo"), "bar", "parameter foo has value 'bar'"); + ok(!(s.get(j).isHover()), "hover is set to false"); + equals(s.get(j).getLabel(), "baz", "label is set to 'baz'"); + } + }); + + test(renderMode + " select, simple getter", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + label:"FOO" + }); + } + + var lbls = _jsPlumb.select().getLabel(); + equals(lbls.length, 5, "there are five labels"); + + for (var j = 0; j < 5; j++) { + equals(lbls[j][0], "FOO", "label has value 'FOO'"); + } + }); + + test(renderMode + " select, getter + chaining", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + label:"FOO" + }); + } + + var params = _jsPlumb.select().removeAllOverlays().setParameter("foo", "bar").getParameter("foo"); + equals(params.length, 5, "there are five params"); + + for (var j = 0; j < 5; j++) { + equals(params[j][0], "bar", "parameter has value 'bar'"); + } + }); + + + test(renderMode + " select, detach method", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + label:"FOO" + }); + } + + var params = _jsPlumb.select().detach(); + + equals(_jsPlumb.select().length, 0, "there are no connections"); + }); + + test(renderMode + " select, repaint method", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + label:"FOO" + }); + } + + var len = _jsPlumb.select().repaint().length; + + equals(len, 5, "there are five connections"); + }); + + + // selectEndpoints + test(renderMode + " selectEndpoints, basic tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1), + e2 = _jsPlumb.addEndpoint(d1); + + equals(_jsPlumb.selectEndpoints().length, 2, "there are two endpoints"); + equals(_jsPlumb.selectEndpoints({element:"d1"}).length, 2, "there are two endpoints on d1"); + equals(_jsPlumb.selectEndpoints({element:"d2"}).length, 0, "there are 0 endpoints on d2"); + + equals(_jsPlumb.selectEndpoints({source:"d1"}).length, 0, "there are zero source endpoints on d1"); + equals(_jsPlumb.selectEndpoints({target:"d1"}).length, 0, "there are zero target endpoints on d1"); + equals(_jsPlumb.selectEndpoints({source:"d2"}).length, 0, "there are zero source endpoints on d2"); + equals(_jsPlumb.selectEndpoints({target:"d2"}).length, 0, "there are zero target endpoints on d2"); + + equals(_jsPlumb.selectEndpoints({source:"d1", scope:"FOO"}).length, 0, "there are zero source endpoints on d1 with scope FOO"); + + _jsPlumb.addEndpoint("d2", { scope:"FOO", isSource:true }); + equals(_jsPlumb.selectEndpoints({source:"d2", scope:"FOO"}).length, 1, "there is one source endpoint on d2 with scope FOO"); + + equals(_jsPlumb.selectEndpoints({element:["d2", "d1"]}).length, 3, "there are three endpoints between d2 and d1"); + }); + + test(renderMode + " selectEndpoints, basic tests, various input argument formats", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1), + e2 = _jsPlumb.addEndpoint(d1); + + equals(_jsPlumb.selectEndpoints({element:"d1"}).length, 2, "using id, there are two endpoints on d1"); + equals(_jsPlumb.selectEndpoints({element:d1}).length, 2, "using dom element, there are two endpoints on d1"); + equals(_jsPlumb.selectEndpoints({element:$("#d1")}).length, 2, "using selector, there are two endpoints on d1"); + equals(_jsPlumb.selectEndpoints({element:$(d1)}).length, 2, "using selector with dom element, there are two endpoints on d1"); + + }); + + test(renderMode + " selectEndpoints, basic tests, scope", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {scope:"FOO"}), + e2 = _jsPlumb.addEndpoint(d1); + + equals(_jsPlumb.selectEndpoints({element:"d1"}).length, 2, "using id, there are two endpoints on d1"); + equals(_jsPlumb.selectEndpoints({element:"d1", scope:"FOO"}).length, 1, "using id, there is one endpoint on d1 with scope 'FOO'"); + _jsPlumb.addEndpoint(d1, {scope:"BAR"}), + equals(_jsPlumb.selectEndpoints({element:"d1", scope:"FOO"}).length, 1, "using id, there is one endpoint on d1 with scope 'BAR'"); + equals(_jsPlumb.selectEndpoints({element:"d1", scope:["BAR", "FOO"]}).length, 2, "using id, there are two endpoints on d1 with scope 'BAR' or 'FOO'"); + }); + + test(renderMode + " selectEndpoints, isSource tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true}), + e2 = _jsPlumb.addEndpoint(d1), + e3 = _jsPlumb.addEndpoint(d2, {isSource:true}); + + equals(_jsPlumb.selectEndpoints({source:"d1"}).length, 1, "there is one source endpoint on d1"); + equals(_jsPlumb.selectEndpoints({target:"d1"}).length, 0, "there are zero target endpoints on d1"); + + equals(_jsPlumb.selectEndpoints({source:"d2"}).length, 1, "there is one source endpoint on d2"); + + equals(_jsPlumb.selectEndpoints({source:["d2", "d1"]}).length, 2, "there are two source endpoints between d1 and d2"); + }); + + test(renderMode + " selectEndpoints, isTarget tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isTarget:true}), + e2 = _jsPlumb.addEndpoint(d1), + e3 = _jsPlumb.addEndpoint(d2, {isTarget:true}); + + equals(_jsPlumb.selectEndpoints({target:"d1"}).length, 1, "there is one target endpoint on d1"); + equals(_jsPlumb.selectEndpoints({source:"d1"}).length, 0, "there are zero source endpoints on d1"); + + equals(_jsPlumb.selectEndpoints({target:"d2"}).length, 1, "there is one target endpoint on d2"); + + equals(_jsPlumb.selectEndpoints({target:["d2", "d1"]}).length, 2, "there are two target endpoints between d1 and d2"); + }); + + test(renderMode + " selectEndpoints, isSource + isTarget tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true}), + e2 = _jsPlumb.addEndpoint(d1), + e3 = _jsPlumb.addEndpoint(d1, {isSource:true}), + e4 = _jsPlumb.addEndpoint(d1, {isTarget:true}); + + equals(_jsPlumb.selectEndpoints({source:"d1"}).length, 2, "there are two source endpoints on d1"); + equals(_jsPlumb.selectEndpoints({target:"d1"}).length, 2, "there are two target endpoints on d1"); + + equals(_jsPlumb.selectEndpoints({target:"d1", source:"d1"}).length, 1, "there is one source and target endpoint on d1"); + + equals(_jsPlumb.selectEndpoints({element:"d1"}).length, 4, "there are four endpoints on d1"); + + }); + + test(renderMode + " selectEndpoints, delete endpoints", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true}); + + equals(_jsPlumb.selectEndpoints({element:"d1"}).length, 1, "there is one endpoint on d1"); + _jsPlumb.selectEndpoints({source:"d1"}).delete(); + equals(_jsPlumb.selectEndpoints({element:"d1"}).length, 0, "there are zero endpoints on d1"); + }); + + test(renderMode + " selectEndpoints, detach connections", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true}), + e2 = _jsPlumb.addEndpoint(d2, {isSource:true, isTarget:true}); + + _jsPlumb.connect({source:e1, target:e2}); + + equals(e1.connections.length, 1, "there is one connection on d1's endpoint"); + equals(e2.connections.length, 1, "there is one connection on d2's endpoint"); + + _jsPlumb.selectEndpoints({source:"d1"}).detachAll(); + + equals(e1.connections.length, 0, "there are zero connections on d1's endpoint"); + equals(e2.connections.length, 0, "there are zero connections on d2's endpoint"); + }); + + test(renderMode + " selectEndpoints, hover tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true}); + + equals(e1.isHover(), false, "hover not set"); + _jsPlumb.selectEndpoints({source:"d1"}).setHover(true); + equals(e1.isHover(), true, "hover set"); + _jsPlumb.selectEndpoints({source:"d1"}).setHover(false); + equals(e1.isHover(), false, "hover no longer set"); + }); + + test(renderMode + " selectEndpoints, setEnabled tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true}); + + equals(e1.isEnabled(), true, "endpoint is enabled"); + _jsPlumb.selectEndpoints({source:"d1"}).setEnabled(false); + equals(e1.isEnabled(), false, "endpoint not enabled"); + }); + + test(renderMode + " selectEndpoints, setEnabled tests", function() { + var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true}); + + equals(e1.isEnabled(), true, "endpoint is enabled"); + var e = _jsPlumb.selectEndpoints({source:"d1"}).isEnabled(); + equals(e[0][0], true, "endpoint enabled"); + _jsPlumb.selectEndpoints({source:"d1"}).setEnabled(false); + e = _jsPlumb.selectEndpoints({source:"d1"}).isEnabled(); + equals(e[0][0], false, "endpoint not enabled"); + }); + +// setPaintStyle/getPaintStyle tests + + test(renderMode + " setPaintStyle", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), c = _jsPlumb.connect({source:d1, target:d2}); + c.setPaintStyle({strokeStyle:"FOO", lineWidth:999}); + equals(c.paintStyleInUse.strokeStyle, "FOO", "strokeStyle was set"); + equals(c.paintStyleInUse.lineWidth, 999, "lineWidth was set"); + + c.setHoverPaintStyle({strokeStyle:"BAZ", lineWidth:444}); + c.setHover(true); + equals(c.paintStyleInUse.strokeStyle, "BAZ", "strokeStyle was set"); + equals(c.paintStyleInUse.lineWidth, 444, "lineWidth was set"); + + equals(c.getPaintStyle().strokeStyle, "FOO", "getPaintStyle returns correct value"); + equals(c.getHoverPaintStyle().strokeStyle, "BAZ", "getHoverPaintStyle returns correct value"); + }); + +// ******************* getEndpoints ************************************************ + + test(renderMode + " getEndpoints", function() { + _addDiv("d1");_addDiv("d2"); + + _jsPlumb.addEndpoint("d1"); + _jsPlumb.addEndpoint("d2"); + _jsPlumb.addEndpoint("d1"); + + var e = _jsPlumb.getEndpoints("d1"), + e2 = _jsPlumb.getEndpoints("d2"); + equals(e.length, 2, "two endpoints on d1"); + equals(e2.length, 1, "one endpoint on d2"); + }); + +// connection type tests - types, type extension, set types, get types etc. + test(renderMode + " set connection type on existing connection", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" } + }; + + _jsPlumb.registerConnectionType("basic", basicType); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("basic"); + equals(c.getPaintStyle().lineWidth, 4, "paintStyle lineWidth is 4"); + equals(c.getPaintStyle().strokeStyle, "yellow", "paintStyle strokeStyle is yellow"); + equals(c.getHoverPaintStyle().strokeStyle, "blue", "paintStyle strokeStyle is yellow"); + equals(c.getHoverPaintStyle().lineWidth, 4, "hoverPaintStyle linewidth is 6"); + }); + + test(renderMode + " set connection type on existing connection then change type", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" } + }; + var otherType = { + connector:"Bezier", + paintStyle:{ strokeStyle:"red", lineWidth:14 }, + hoverPaintStyle:{ strokeStyle:"green" } + }; + + _jsPlumb.registerConnectionType("basic", basicType); + _jsPlumb.registerConnectionType("other", otherType); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("basic"); + equals(c.getPaintStyle().lineWidth, 4, "paintStyle lineWidth is 4"); + equals(c.getPaintStyle().strokeStyle, "yellow", "paintStyle strokeStyle is yellow"); + equals(c.getHoverPaintStyle().strokeStyle, "blue", "hoverPaintStyle strokeStyle is blue"); + equals(c.getHoverPaintStyle().lineWidth, 4, "hoverPaintStyle linewidth is 6"); + + c.setType("other"); + equals(c.getPaintStyle().lineWidth, 14, "paintStyle lineWidth is 14"); + equals(c.getPaintStyle().strokeStyle, "red", "paintStyle strokeStyle is red"); + equals(c.getHoverPaintStyle().strokeStyle, "green", "hoverPaintStyle strokeStyle is green"); + equals(c.getHoverPaintStyle().lineWidth, 14, "hoverPaintStyle linewidth is 14"); + }); + + test(renderMode + " set connection type on existing connection, overlays should be set", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }; + + _jsPlumb.registerConnectionType("basic", basicType); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("basic"); + equals(c.getOverlays().length, 1, "one overlay"); + }); + + test(renderMode + " set connection type on existing connection, overlays should be removed with second type", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }; + var otherType = { + connector:"Bezier" + }; + _jsPlumb.registerConnectionType("basic", basicType); + _jsPlumb.registerConnectionType("other", otherType); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("basic"); + equals(c.getOverlays().length, 1, "one overlay"); + c.setType("other"); + equals(c.getOverlays().length, 0, "no overlays"); + equals(c.getPaintStyle().lineWidth, _jsPlumb.Defaults.PaintStyle.lineWidth, "paintStyle lineWidth is default"); + }); + + test(renderMode + " set connection type on existing connection, hasType + toggleType", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }; + + _jsPlumb.registerConnectionTypes({ + "basic": basicType + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("basic"); + equals(c.hasType("basic"), true, "connection has 'basic' type"); + c.toggleType("basic"); + equals(c.hasType("basic"), false, "connection does not have 'basic' type"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + c.toggleType("basic"); + equals(c.hasType("basic"), true, "connection has 'basic' type"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + equals(c.getOverlays().length, 1, "one overlay"); + + }); + + test(renderMode + " set connection type on existing connection, merge tests", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }; + // this tests all three merge types: connector should overwrite, linewidth should be inserted into + // basic type's params, and arrow overlay should be added to list to end up with two overlays + var otherType = { + connector:"Bezier", + paintStyle:{ lineWidth:14 }, + overlays:[ + ["Arrow", {location:0.25}] + ] + }; + _jsPlumb.registerConnectionTypes({ + "basic": basicType, + "other": otherType + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("basic"); + equals(c.hasType("basic"), true, "connection has 'basic' type"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + equals(c.getPaintStyle().lineWidth, 4, "connection has linewidth 4"); + equals(c.getOverlays().length, 1, "one overlay"); + + c.addType("other"); + equals(c.hasType("basic"), true, "connection has 'basic' type"); + equals(c.hasType("other"), true, "connection has 'other' type"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + equals(c.getPaintStyle().lineWidth, 14, "connection has linewidth 14"); + equals(c.getOverlays().length, 2, "two overlays"); + + c.removeType("basic"); + equals(c.hasType("basic"), false, "connection does not have 'basic' type"); + equals(c.hasType("other"), true, "connection has 'other' type"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + equals(c.getPaintStyle().lineWidth, 14, "connection has linewidth 14"); + equals(c.getOverlays().length, 1, "one overlay"); + + c.toggleType("other"); + equals(c.hasType("other"), false, "connection does not have 'other' type"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + equals(c.getPaintStyle().lineWidth, _jsPlumb.Defaults.PaintStyle.lineWidth, "connection has default linewidth"); + equals(c.getOverlays().length, 0, "nooverlays"); + }); + + test(renderMode + " connection type tests, space separated arguments", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }; + // this tests all three merge types: connector should overwrite, linewidth should be inserted into + // basic type's params, and arrow overlay should be added to list to end up with two overlays + var otherType = { + connector:"Bezier", + paintStyle:{ lineWidth:14 }, + overlays:[ + ["Arrow", {location:0.25}] + ] + }; + _jsPlumb.registerConnectionTypes({ + "basic": basicType, + "other": otherType + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("basic other"); + equals(c.hasType("basic"), true, "connection has 'basic' type"); + equals(c.hasType("other"), true, "connection has 'other' type"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + equals(c.getPaintStyle().lineWidth, 14, "connection has linewidth 14"); + equals(c.getOverlays().length, 2, "two overlays"); + + c.toggleType("other basic"); + equals(c.hasType("basic"), false, "after toggle, connection does not have 'basic' type"); + equals(c.hasType("other"), false, "after toggle, connection does not have 'other' type"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "after toggle, connection has default stroke style"); + equals(c.getPaintStyle().lineWidth, _jsPlumb.Defaults.PaintStyle.lineWidth, "after toggle, connection has default linewidth"); + equals(c.getOverlays().length, 0, "after toggle, no overlays"); + + c.toggleType("basic other"); + equals(c.hasType("basic"), true, "after toggle again, connection has 'basic' type"); + equals(c.hasType("other"), true, "after toggle again, connection has 'other' type"); + equals(c.getPaintStyle().strokeStyle, "yellow", "after toggle again, connection has yellow stroke style"); + equals(c.getPaintStyle().lineWidth, 14, "after toggle again, connection has linewidth 14"); + equals(c.getOverlays().length, 2, "after toggle again, two overlays"); + + c.removeType("other basic"); + equals(c.hasType("basic"), false, "after remove, connection does not have 'basic' type"); + equals(c.hasType("other"), false, "after remove, connection does not have 'other' type"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "after remove, connection has default stroke style"); + equals(c.getPaintStyle().lineWidth, _jsPlumb.Defaults.PaintStyle.lineWidth, "after remove, connection has default linewidth"); + equals(c.getOverlays().length, 0, "after remove, no overlays"); + + c.addType("other basic"); + equals(c.hasType("basic"), true, "after add, connection has 'basic' type"); + equals(c.hasType("other"), true, "after add, connection has 'other' type"); + equals(c.getPaintStyle().strokeStyle, "yellow", "after add, connection has yellow stroke style"); + // NOTE here we added the types in the other order to before, so lineWidth 4 - from basic - should win. + equals(c.getPaintStyle().lineWidth, 4, "after add, connection has linewidth 4"); + equals(c.getOverlays().length, 2, "after add, two overlays"); + }); + + test(renderMode + " connection type tests, fluid interface", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }; + // this tests all three merge types: connector should overwrite, linewidth should be inserted into + // basic type's params, and arrow overlay should be added to list to end up with two overlays + var otherType = { + connector:"Bezier", + paintStyle:{ lineWidth:14 }, + overlays:[ + ["Arrow", {location:0.25}] + ] + }; + _jsPlumb.registerConnectionTypes({ + "basic": basicType, + "other": otherType + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + c = _jsPlumb.connect({source:d1, target:d2}), + c2 = _jsPlumb.connect({source:d2, target:d3}); + + _jsPlumb.select().addType("basic"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + equals(c2.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + + _jsPlumb.select().toggleType("basic"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + equals(c2.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + + _jsPlumb.select().addType("basic"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + equals(c2.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style"); + + _jsPlumb.select().removeType("basic").addType("other"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + equals(c2.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + + + }); + + test(renderMode + " connection type tests, two types, check separation", function() { + var basicType = { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 } + }; + // this tests all three merge types: connector should overwrite, linewidth should be inserted into + // basic type's params, and arrow overlay should be added to list to end up with two overlays + var otherType = { + paintStyle:{ strokeStyle:"red", lineWidth:14 } + }; + _jsPlumb.registerConnectionTypes({ + "basic": basicType, + "other": otherType + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + c = _jsPlumb.connect({source:d1, target:d2}), + c2 = _jsPlumb.connect({source:d2, target:d3}); + c.setType("basic"); + equals(c.getPaintStyle().strokeStyle, "yellow", "first connection has yellow stroke style"); + c2.setType("other"); + + equals(c.getPaintStyle().strokeStyle, "yellow", "first connection has yellow stroke style"); + + + }); + + test(renderMode + " setType when null", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType(null); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + + }); + + test(renderMode + " setType to unknown type", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + c = _jsPlumb.connect({source:d1, target:d2}); + + c.setType("foo"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + + }); + + test(renderMode + " setType to mix of known and unknown types", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + c = _jsPlumb.connect({source:d1, target:d2}); + + _jsPlumb.registerConnectionType("basic", { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }); + + c.setType("basic foo"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has basic type's stroke style"); + + c.toggleType("foo"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has basic type's stroke style"); + + c.removeType("basic baz"); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + + c.addType("basic foo bar baz"); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has basic type's stroke style"); + + }); + + test(renderMode + " create connection using type parameter", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + + _jsPlumb.Defaults.PaintStyle = {strokeStyle:"blue", lineWidth:34}; + + _jsPlumb.registerConnectionTypes({ + "basic": { + connector:"Flowchart", + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }, + "other":{ + paintStyle:{ lineWidth:14 } + } + }); + + equals(_jsPlumb.Defaults.PaintStyle.strokeStyle, "blue", "default value has not been messed up"); + + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style"); + + c = _jsPlumb.connect({source:d1, target:d2, type:"basic other"}); + equals(c.getPaintStyle().strokeStyle, "yellow", "connection has basic type's stroke style"); + equals(c.getPaintStyle().lineWidth, 14, "connection has other type's lineWidth"); + + }); + + test(renderMode + " setType, scope", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + c = _jsPlumb.connect({source:d1, target:d2}); + + _jsPlumb.registerConnectionType("basic", { + connector:"Flowchart", + scope:"BANANA", + detachable:false, + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }); + + _jsPlumb.Defaults.ConnectionsDetachable = true;//just make sure we've setup the test correctly. + + c.setType("basic"); + equals(c.scope, "BANANA", "scope is correct"); + equals(c.isDetachable(), false, "not detachable"); + + }); + + test(renderMode + " setType, parameters", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + + _jsPlumb.registerConnectionType("basic", { + parameters:{ + foo:1, + bar:2, + baz:6785962437582 + } + }); + + _jsPlumb.registerConnectionType("frank", { + parameters:{ + bar:5 + } + }); + + // first try creating one with the parameters + c = _jsPlumb.connect({source:d1, target:d2, type:"basic"}); + + equals(c.getParameter("foo"), 1, "foo param correct"); + equals(c.getParameter("bar"), 2, "bar param correct"); + + c.addType("frank"); + equals(c.getParameter("foo"), 1, "foo param correct"); + equals(c.getParameter("bar"), 5, "bar param correct"); + }); + + test(renderMode + " setType, scope, two types", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + c = _jsPlumb.connect({source:d1, target:d2}); + + _jsPlumb.registerConnectionType("basic", { + connector:"Flowchart", + scope:"BANANA", + detachable:false, + paintStyle:{ strokeStyle:"yellow", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"blue" }, + overlays:[ + "Arrow" + ] + }); + + _jsPlumb.registerConnectionType("frank", { + scope:"OVERRIDE", + detachable:true + }); + + _jsPlumb.Defaults.ConnectionsDetachable = true;//just make sure we've setup the test correctly. + + c.setType("basic frank"); + equals(c.scope, "OVERRIDE", "scope is correct"); + equals(c.isDetachable(), true, "detachable"); + + }); + + test(renderMode + " create connection from Endpoints - type should be passed through.", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e1 = _jsPlumb.addEndpoint(d1, { + connectionType:"basic" + }), + e2 = _jsPlumb.addEndpoint(d2, { + connectionType:"basic" + }); + + _jsPlumb.registerConnectionTypes({ + "basic": { + connector:"Flowchart", + paintStyle:{ strokeStyle:"blue", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"red" }, + overlays:[ + "Arrow" + ] + }, + "other":{ + paintStyle:{ lineWidth:14 } + } + }); + + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.getPaintStyle().strokeStyle, "blue", "connection has default stroke style"); + equal(c.connector.type, "Flowchart", "connector is flowchart"); + }); + + test(renderMode + " simple Endpoint type tests.", function() { + _jsPlumb.registerEndpointType("basic", { + paintStyle:{fillStyle:"blue"} + }); + + var d = _addDiv('d1'), e = _jsPlumb.addEndpoint(d); + e.setType("basic"); + equals(e.getPaintStyle().fillStyle, "blue", "fill style is correct"); + + var d2 = _addDiv('d2'), e2 = _jsPlumb.addEndpoint(d2, {type:"basic"}); + equals(e2.getPaintStyle().fillStyle, "blue", "fill style is correct"); + }); + + test(renderMode + " create connection from Endpoints - with connector settings in Endpoint type.", function() { + + _jsPlumb.registerEndpointTypes({ + "basic": { + connector:"Flowchart", + connectorOverlays:[ + "Arrow" + ], + connectorStyle:{strokeStyle:"green" }, + connectorHoverStyle:{lineWidth:534 }, + paintStyle:{ fillStyle:"blue" }, + hoverPaintStyle:{ strokeStyle:"red" }, + overlays:[ + "Arrow" + ] + }, + "other":{ + paintStyle:{ fillStyle:"red" } + } + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e1 = _jsPlumb.addEndpoint(d1, { + type:"basic" + }), + e2 = _jsPlumb.addEndpoint(d2); + + c = _jsPlumb.connect({source:e1, target:e2}); + equals(e1.getPaintStyle().fillStyle, "blue", "endpoint has fill style specified in Endpoint type"); + equals(c.getPaintStyle().strokeStyle, "green", "connection has stroke style specified in Endpoint type"); + equals(c.getHoverPaintStyle().lineWidth, 534, "connection has hover style specified in Endpoint type"); + equals(c.connector.type, "Flowchart", "connector is Flowchart"); + equals(c.overlays.length, 1, "connector has one overlay"); + equals(e1.overlays.length, 1, "endpoint has one overlay"); + }); + + test(renderMode + " create connection from Endpoints - type should be passed through.", function() { + + _jsPlumb.registerConnectionTypes({ + "basic": { + connector:"Flowchart", + paintStyle:{ strokeStyle:"bazona", lineWidth:4 }, + hoverPaintStyle:{ strokeStyle:"red" }, + overlays:[ + "Arrow" + ] + } + }); + + _jsPlumb.registerEndpointType("basic", { + connectionType:"basic", + paintStyle:{fillStyle:"GAZOODA"} + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e1 = _jsPlumb.addEndpoint(d1, { + type:"basic" + }), + e2 = _jsPlumb.addEndpoint(d2); + + c = _jsPlumb.connect({source:e1, target:e2}); + equals(e1.getPaintStyle().fillStyle, "GAZOODA", "endpoint has correct paint style, from type."); + equals(c.getPaintStyle().strokeStyle, "bazona", "connection has paint style from connection type, as specified in endpoint type. sweet!"); + equal(c.connector.type, "Flowchart", "connector is flowchart - this also came from connection type as specified by endpoint type."); + }); + + test(renderMode + " endpoint type", function() { + _jsPlumb.registerEndpointTypes({"example": {hoverPaintStyle: null}}); + //OR + //jsPlumb.registerEndpointType("example", {hoverPaintStyle: null}); + + var d = _addDiv("d"); + _jsPlumb.addEndpoint(d, {type: "example"}); + _jsPlumb.repaint(d); + }); + + /* + * test the merge function in jsplumb util: it should create an entirely new object + */ + test(renderMode + "jsPlumbUtil.merge", function() { + var a = { + foo:"a_foo", + bar:"a_bar", + nested:{ + foo:"a_foo", + bar:"a_bar" + } + }, + b = { + foo:"b_foo", + nested :{ + foo:"b_foo" + } + }, + c = jsPlumbUtil.merge(a, b); + equals(c.foo, "b_foo", "c has b's foo"); + equals(c.nested.foo, "b_foo", "c has b's nested foo"); + // now change c's foo. b should be unchanged. + c.foo = "c_foo"; + equals(b.foo, "b_foo", "b has b's foo"); + c.nested.foo = "c_foo"; + equals(b.nested.foo, "b_foo", "b has b's nested foo"); + equals(a.nested.foo, "a_foo", "a has a's nested foo"); + }); + + test(renderMode + "jsPlumbUtil.clone", function() { + var a = { + nested:{ + foo:"a_foo" + } + }, + b = jsPlumbUtil.clone(a); + equals(b.nested.foo, "a_foo", "b has a's nested foo"); + equals(a.nested.foo, "a_foo", "a has a's nested foo"); + b.nested.foo="b_foo"; + equals(b.nested.foo, "b_foo", "b has b's nested foo"); + equals(a.nested.foo, "a_foo", "a has a's nested foo"); + }); + +}; + diff --git a/archive/1.3.16/jsPlumb-connectors-statemachine-1.3.16-RC1.js b/archive/1.3.16/jsPlumb-connectors-statemachine-1.3.16-RC1.js new file mode 100644 index 000000000..8d378b441 --- /dev/null +++ b/archive/1.3.16/jsPlumb-connectors-statemachine-1.3.16-RC1.js @@ -0,0 +1,469 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.16 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the state machine connectors. + * + * Thanks to Brainstorm Mobile Solutions for supporting the development of these. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + var Line = function(x1, y1, x2, y2) { + + this.m = (y2 - y1) / (x2 - x1); + this.b = -1 * ((this.m * x1) - y1); + + this.rectIntersect = function(x,y,w,h) { + var results = []; + + // try top face + // the equation of the top face is y = (0 * x) + b; y = b. + var xInt = (y - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try right face + var yInt = (this.m * (x + w)) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + // bottom face + var xInt = ((y + h) - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try left face + var yInt = (this.m * x) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + if (results.length == 2) { + var midx = (results[0][0] + results[1][0]) / 2, midy = (results[0][1] + results[1][1]) / 2; + results.push([ midx,midy ]); + // now calculate the segment inside the rectangle where the midpoint lies. + var xseg = midx <= x + (w / 2) ? -1 : 1, + yseg = midy <= y + (h / 2) ? -1 : 1; + results.push([xseg, yseg]); + return results; + } + + return null; + + }; + }, + _segment = function(x1, y1, x2, y2) { + if (x1 <= x2 && y2 <= y1) return 1; + else if (x1 <= x2 && y1 <= y2) return 2; + else if (x2 <= x1 && y2 >= y1) return 3; + return 4; + }, + + // the control point we will use depends on the faces to which each end of the connection is assigned, specifically whether or not the + // two faces are parallel or perpendicular. if they are parallel then the control point lies on the midpoint of the axis in which they + // are parellel and varies only in the other axis; this variation is proportional to the distance that the anchor points lie from the + // center of that face. if the two faces are perpendicular then the control point is at some distance from both the midpoints; the amount and + // direction are dependent on the orientation of the two elements. 'seg', passed in to this method, tells you which segment the target element + // lies in with respect to the source: 1 is top right, 2 is bottom right, 3 is bottom left, 4 is top left. + // + // sourcePos and targetPos are arrays of info about where on the source and target each anchor is located. their contents are: + // + // 0 - absolute x + // 1 - absolute y + // 2 - proportional x in element (0 is left edge, 1 is right edge) + // 3 - proportional y in element (0 is top edge, 1 is bottom edge) + // + _findControlPoint = function(midx, midy, segment, sourceEdge, targetEdge, dx, dy, distance, proximityLimit) { + + // TODO (maybe) + // - if anchor pos is 0.5, make the control point take into account the relative position of the elements. + if (distance <= proximityLimit) return [midx, midy]; + + if (segment == 1) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 2) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 3) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 4) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + }; + + /** + * Class: Connectors.StateMachine + * Provides 'state machine' connectors. + */ + /* + * Function: Constructor + * + * Parameters: + * curviness - measure of how "curvy" the connectors will be. this is translated as the distance that the + * Bezier curve's control point is from the midpoint of the straight line connecting the two + * endpoints, and does not mean that the connector is this wide. The Bezier curve never reaches + * its control points; they act as gravitational masses. defaults to 10. + * margin - distance from element to start and end connectors, in pixels. defaults to 5. + * proximityLimit - sets the distance beneath which the elements are consider too close together to bother + * with fancy curves. by default this is 80 pixels. + * loopbackRadius - the radius of a loopback connector. optional; defaults to 25. + * showLoopback - If set to false this tells the connector that it is ok to paint connections whose source and target is the same element with a connector running through the element. The default value for this is true; the connector always makes a loopback connection loop around the element rather than passing through it. + */ + jsPlumb.Connectors.StateMachine = function(params) { + var self = this, + currentPoints = null, + _sx, _sy, _tx, _ty, _controlPoint = [], + curviness = params.curviness || 10, + margin = params.margin || 5, + proximityLimit = params.proximityLimit || 80, + clockwise = params.orientation && params.orientation == "clockwise", + loopbackRadius = params.loopbackRadius || 25, + isLoopback = false, + showLoopback = params.showLoopback !== false; + + this.type = "StateMachine"; + params = params || {}; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *= 1.9; + //ensure at least one pixel width + lineWidth = lineWidth || 1; + var x = Math.min(sourcePos[0], targetPos[0]) - xo, + y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (!showLoopback || (sourceEndpoint.elementId != targetEndpoint.elementId)) { + + isLoopback = false; + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + + // now adjust for the margin + if (sourcePos[2] == 0) _sx -= margin; + if (sourcePos[2] == 1) _sx += margin; + if (sourcePos[3] == 0) _sy -= margin; + if (sourcePos[3] == 1) _sy += margin; + if (targetPos[2] == 0) _tx -= margin; + if (targetPos[2] == 1) _tx += margin; + if (targetPos[3] == 0) _ty -= margin; + if (targetPos[3] == 1) _ty += margin; + + // + // these connectors are quadratic bezier curves, having a single control point. if both anchors + // are located at 0.5 on their respective faces, the control point is set to the midpoint and you + // get a straight line. this is also the case if the two anchors are within 'proximityLimit', since + // it seems to make good aesthetic sense to do that. outside of that, the control point is positioned + // at 'curviness' pixels away along the normal to the straight line connecting the two anchors. + // + // there may be two improvements to this. firstly, we might actually support the notion of avoiding nodes + // in the UI, or at least making a good effort at doing so. if a connection would pass underneath some node, + // for example, we might increase the distance the control point is away from the midpoint in a bid to + // steer it around that node. this will work within limits, but i think those limits would also be the likely + // limits for, once again, aesthetic good sense in the layout of a chart using these connectors. + // + // the second possible change is actually two possible changes: firstly, it is possible we should gradually + // decrease the 'curviness' as the distance between the anchors decreases; start tailing it off to 0 at some + // point (which should be configurable). secondly, we might slightly increase the 'curviness' for connectors + // with respect to how far their anchor is from the center of its respective face. this could either look cool, + // or stupid, and may indeed work only in a way that is so subtle as to have been a waste of time. + // + + var _midx = (_sx + _tx) / 2, _midy = (_sy + _ty) / 2, + m2 = (-1 * _midx) / _midy, theta2 = Math.atan(m2), + dy = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.sin(theta2)), + dx = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.cos(theta2)), + segment = _segment(_sx, _sy, _tx, _ty), + distance = Math.sqrt(Math.pow(_tx - _sx, 2) + Math.pow(_ty - _sy, 2)); + + // calculate the control point. this code will be where we'll put in a rudimentary element avoidance scheme; it + // will work by extending the control point to force the curve to be, um, curvier. + _controlPoint = _findControlPoint(_midx, + _midy, + segment, + sourcePos, + targetPos, + curviness, curviness, + distance, + proximityLimit); + + + var requiredWidth = Math.max(Math.abs(_controlPoint[0] - _sx) * 3, Math.abs(_controlPoint[0] - _tx) * 3, Math.abs(_tx-_sx), 2 * lineWidth, minWidth), + requiredHeight = Math.max(Math.abs(_controlPoint[1] - _sy) * 3, Math.abs(_controlPoint[1] - _ty) * 3, Math.abs(_ty-_sy), 2 * lineWidth, minWidth); + + if (w < requiredWidth) { + var dw = requiredWidth - w; + x -= (dw / 2); + _sx += (dw / 2); + _tx += (dw / 2); + w = requiredWidth; + _controlPoint[0] += (dw / 2); + } + + if (h < requiredHeight) { + var dh = requiredHeight - h; + y -= (dh / 2); + _sy += (dh / 2); + _ty += (dh / 2); + h = requiredHeight; + _controlPoint[1] += (dh / 2); + } + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty, _controlPoint[0], _controlPoint[1] ]; + } + else { + isLoopback = true; + // a loopback connector. draw an arc from one anchor to the other. + // i guess we'll do this the same way as the others. just the control point will be a fair distance away. + var x1 = sourcePos[0], x2 = sourcePos[0], y1 = sourcePos[1] - margin, y2 = sourcePos[1] - margin, + cx = x1, cy = y1 - loopbackRadius; + + // canvas sizing stuff, to ensure the whole painted area is visible. + w = ((2 * lineWidth) + (4 * loopbackRadius)), h = ((2 * lineWidth) + (4 * loopbackRadius)); + x = cx - loopbackRadius - lineWidth - loopbackRadius, y = cy - loopbackRadius - lineWidth - loopbackRadius; + currentPoints = [ x, y, w, h, cx-x, cy-y, loopbackRadius, clockwise, x1-x, y1-y, x2-x, y2-y]; + } + + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_tx, y:_ty }, + { x:_controlPoint[0], y:_controlPoint[1] }, + { x:_controlPoint[0] + 1, y:_controlPoint[1] + 1}, + { x:_sx, y:_sy } + ]; + }; + + var _translateLocation = function(curve, location, absolute) { + if (absolute) + location = jsBezier.locationAlongCurveFrom(curve, location > 0 ? 0 : 1, location); + + return location; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location, absolute) { + if (isLoopback) { + if (absolute) { + var circumference = Math.PI * 2 * loopbackRadius; + location = location / circumference; + } + + if (location > 0 && location < 1) location = 1 - location; + +// current points are [ x, y, width, height, center x, center y, radius, clockwise, startx, starty, endx, endy ] + // so the path length is the circumference of the circle + //var len = 2 * Math.PI * currentPoints[6], + // map 'location' to an angle. 0 is PI/2 when the connector is on the top face; if we + // support other faces it will have to be calculated for each one. 1 is also PI/2. + // 0.5 is -PI/2. + var startAngle = (location * 2 * Math.PI) + (Math.PI / 2), + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + + } + else { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.pointOnCurve(c, location); + } + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location, absolute) { + if (isLoopback) { + // todo if absolute, location is a proportion of circumference + if (absolute) { + var circumference = Math.PI * 2 * loopbackRadius; + location = location / circumference; + } + + return Math.atan(location * 2 * Math.PI); + } + else { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.gradientAtPoint(c, location); + } + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance, absolute) { + if (isLoopback) { + + if (absolute) { + var circumference = Math.PI * 2 * loopbackRadius; + location = location / circumference; + } + + if (location > 0 && location < 1) location = 1 - location; + + var circumference = 2 * Math.PI * currentPoints[6], + arcSpan = distance / circumference * 2 * Math.PI, + startAngle = (location * 2 * Math.PI) - arcSpan + (Math.PI / 2), + + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + } + else { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.pointAlongCurveFrom(c, location, distance); + } + }; + + }; + + /* + * Canvas state machine renderer. + */ + jsPlumb.Connectors.canvas.StateMachine = function(params) { + params = params || {}; + var self = this, drawGuideline = params.drawGuideline || true, avoidSelector = params.avoidSelector; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.CanvasConnector.apply(this, arguments); + + + this._paint = function(dimensions) { + + if (dimensions.length == 10) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[8], dimensions[9], dimensions[6], dimensions[7]); + self.ctx.stroke(); + } + else { + // a loopback connector + self.ctx.save(); + self.ctx.beginPath(); + var startAngle = 0, // Starting point on circle + endAngle = 2 * Math.PI, // End point on circle + clockwise = dimensions[7]; // clockwise or anticlockwise + self.ctx.arc(dimensions[4],dimensions[5],dimensions[6],0, endAngle, clockwise); + self.ctx.stroke(); + self.ctx.closePath(); + self.ctx.restore(); + } + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + /* + * SVG State Machine renderer + */ + jsPlumb.Connectors.svg.StateMachine = function() { + var self = this; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.SvgConnector.apply(this, arguments); + this.getPath = function(d) { + + if (d.length == 10) + return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[8] + " " + d[9] + " " + d[6] + " " + d[7]; + else { + // loopback + return "M" + (d[8] + 4) + " " + d[9] + " A " + d[6] + " " + d[6] + " 0 1,0 " + (d[8]-4) + " " + d[9]; + } + }; + }; + + /* + * VML state machine renderer + */ + jsPlumb.Connectors.vml.StateMachine = function() { + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.VmlConnector.apply(this, arguments); + var _conv = jsPlumb.vml.convertValue; + this.getPath = function(d) { + if (d.length == 10) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + } + else { + // loopback + var left = _conv(d[8] - d[6]), + top = _conv(d[9] - (2 * d[6])), + right = left + _conv(2 * d[6]), + bottom = top + _conv(2 * d[6]), + posString = left + "," + top + "," + right + "," + bottom; + + var o = "ar " + posString + "," + _conv(d[8]) + "," + + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + " e"; + + return o; + } + }; + }; + +})(); + +/* + // now for a rudimentary avoidance scheme. TODO: how to set this in a cross-library way? + // if (avoidSelector) { + // var testLine = new Line(sourcePos[0] + _sx,sourcePos[1] + _sy,sourcePos[0] + _tx,sourcePos[1] + _ty); + // var sel = jsPlumb.getSelector(avoidSelector); + // for (var i = 0; i < sel.length; i++) { + // var id = jsPlumb.getId(sel[i]); + // if (id != sourceEndpoint.elementId && id != targetEndpoint.elementId) { + // o = jsPlumb.getOffset(id), s = jsPlumb.getSize(id); +// +// if (o && s) { +// var collision = testLine.rectIntersect(o.left,o.top,s[0],s[1]); +// if (collision) { + // set the control point to be a certain distance from the midpoint of the two points that + // the line crosses on the rectangle. + // TODO where will this 75 number come from? + // _controlX = collision[2][0] + (75 * collision[3][0]); + // / _controlY = collision[2][1] + (75 * collision[3][1]); +// } +// } + // } + // } + //} + */ \ No newline at end of file diff --git a/archive/1.3.16/jsPlumb-defaults-1.3.16-RC1.js b/archive/1.3.16/jsPlumb-defaults-1.3.16-RC1.js new file mode 100644 index 000000000..1f111d41e --- /dev/null +++ b/archive/1.3.16/jsPlumb-defaults-1.3.16-RC1.js @@ -0,0 +1,1262 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.16 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the default Connectors, Endpoint and Overlay definitions. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +(function() { + + /** + * + * Helper class to consume unused mouse events by components that are DOM elements and + * are used by all of the different rendering modes. + * + */ + jsPlumb.DOMElementComponent = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + // when render mode is canvas, these functions may be called by the canvas mouse handler. + // this component is safe to pipe this stuff to /dev/null. + this.mousemove = + this.dblclick = + this.click = + this.mousedown = + this.mouseup = function(e) { }; + }; + + /** + * Class: Connectors.Straight + * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters. + */ + jsPlumb.Connectors.Straight = function() { + this.type = "Straight"; + var self = this, + currentPoints = null, + _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty, _segment, _length; + + /** + * Computes the new size and position of the canvas. + */ + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + _dx = _tx - _sx, _dy = _ty - _sy; + //_m = _dy / _dx, _m2 = -1 / _m; + _m = jsPlumbUtil.gradient({x:_sx, y:_sy}, {x:_tx, y:_ty}), _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + //_segment = jsPlumbUtil.segment({x:_sx, y:_sy}, {x:_tx, y:_ty}); + _length = Math.sqrt((_dx * _dx) + (_dy * _dy)); + + return currentPoints; + }; + + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location, absolute) { + if (location == 0 && !absolute) + return { x:_sx, y:_sy }; + else if (location == 1 && !absolute) + return { x:_tx, y:_ty }; + else { + var l = absolute ? location > 0 ? location : _length + location : location * _length; + return jsPlumbUtil.pointOnLine({x:_sx, y:_sy}, {x:_tx, y:_ty}, l); + } + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { + return _m; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + * this hands off to jsPlumbUtil to do the maths, supplying two points and the distance. + */ + this.pointAlongPathFrom = function(location, distance, absolute) { + var p = self.pointOnPath(location, absolute), + farAwayPoint = location == 1 ? { + x:_sx + ((_tx - _sx) * 10), + y:_sy + ((_sy - _ty) * 10) + } : distance <= 0 ? {x:_sx, y:_sy} : {x:_tx, y:_ty }; + + if (distance <= 0 && Math.abs(distance) > 1) distance *= -1; + + return jsPlumbUtil.pointOnLine(p, farAwayPoint, distance); + }; + }; + + + /** + * Class:Connectors.Bezier + * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's + * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below. + */ + /** + * Function:Constructor + * + * Parameters: + * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line. + * Optional; defaults to 150. + * stub - optional value for a distance to travel from the connector's endpoint before beginning the Bezier curve. defaults to 0. + * + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + params = params || {}; + this.majorAnchor = params.curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + this.type = "Bezier"; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(sourceEndpoint), + too = targetAnchor.getOrientation(targetEndpoint), + perpendicular = soo[0] != too[0] || soo[1] == too[1], + p = [], + ma = self.majorAnchor, mi = self.minorAnchor; + + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h, _sStubX, _sStubY, _tStubX, _tStubY; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + // this calculation gives us what the minimum distance between either start or end + // should be from the edge of the canvas. + lineWidth = Math.max(minWidth, (lineWidth || 0)); + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetEndpoint, sourceEndpoint, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx), minx2 = Math.min(_CP[0], _CP2[0]), minx = Math.min(minx1,minx2), + maxx1 = Math.max(_sx,_tx), maxx2 = Math.max(_CP[0], _CP2[0]), maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty), miny2 = Math.min(_CP[1], _CP2[1]), miny = Math.min(miny1,miny2), + maxy1 = Math.max(_sy,_ty), maxy2 = Math.max(_CP[1], _CP2[1]), maxy = Math.max(maxy1,maxy2); + + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, + _sx, _sy, _tx, _ty, + _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + var _translateLocation = function(curve, location, absolute) { + if (absolute) + location = jsBezier.locationAlongCurveFrom(curve, location > 0 ? 0 : 1, location); + + return location; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location, absolute) { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.pointOnCurve(c, location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location, absolute) { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.gradientAtPoint(c, location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance, absolute) { + var c = _makeCurve(); + location = _translateLocation(c, location, absolute); + return jsBezier.pointAlongCurveFrom(c, location, distance); + }; + }; + + + /** + * Class: Connectors.Flowchart + * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments. + */ + /** + * Function: Constructor + * + * Parameters: + * stub - minimum length for the stub at each end of the connector. This can be an integer, giving a value for both ends of the connections, + * or an array of two integers, giving separate values for each end. The default is an integer with value 30 (pixels). + * gap - gap to leave between the end of the connector and the element on which the endpoint resides. if you make this larger than stub then you will see some odd looking behaviour. defaults to 0 pixels. + */ + jsPlumb.Connectors.Flowchart = function(params) { + this.type = "Flowchart"; + params = params || {}; + var self = this, + stub = params.stub || params.minStubLength /* bwds compat. */ || 30, + sourceStub = jsPlumbUtil.isArray(stub) ? stub[0] : stub, + targetStub = jsPlumbUtil.isArray(stub) ? stub[1] : stub, + gap = params.gap || 0, + midpoint = params.midpoint || 0.5, + segments = [], + totalLength = 0, + segmentProportions = [], + segmentProportionalLengths = [], + points = [], + swapX, swapY, + maxX = -Infinity, maxY = -Infinity, + minX = Infinity, minY = Infinity, + grid = params.grid, + _gridClamp = function(n, g) { var e = n % g, f = Math.floor(n / g), inc = e > (g / 2) ? 1 : 0; return (f + inc) * g; }, + clampToGrid = function(x, y, dontClampX, dontClampY) { + return [ + dontClampX || grid == null ? x : _gridClamp(x, grid[0]), + dontClampY || grid == null ? y : _gridClamp(y, grid[1]) + ]; + }, + /** + * recalculates the points at which the segments begin and end, proportional to the total length travelled + * by all the segments that constitute the connector. we use this to help with pointOnPath calculations. + */ + updateSegmentProportions = function(startX, startY, endX, endY) { + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segments[i][5] / totalLength; + segmentProportions[i] = [curLoc, (curLoc += (segments[i][5] / totalLength)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty/*, doGridX, doGridY*/) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0], + ly = segments.length == 0 ? sy : segments[segments.length - 1][1], + m = x == lx ? Infinity : 0; + /*, + gridded = clampToGrid(x, y), + doGridX = true, + doGridY = true; + + // grid experiment. TODO: have two more params that indicate whether or not to lock to a grid in each + // axis. the reason for this is that anchor points wont always be located on the grid, so until a connector + // emanating from that anchor has turned a right angle, we can't actually clamp it to a grid for that axis. + // so if a line came out horizontally heading left, then it will probably not be clamped in the y axis, but + // we can choose to clamp its first corner in the x axis. the same principle goes for the target anchor. + //if (segments.length == 0) { + console.log("this is the first segment...if sx == x then do not do grid in X.") + doGridX = !(sx == x) && !(tx == x); + doGridY = !(sy == y) && !(ty == y); + x = doGridX ? gridded[0] : x; + y = doGridY ? gridded[1] : y; + */ + + var l = Math.abs(x == lx ? y - ly : x - lx); + + segments.push([x, y, lx, ly, m, l]); + totalLength += l; + + maxX = Math.max(maxX, x); + maxY = Math.max(maxY, y); + minX = Math.min(minX, x); + minY = Math.min(minY, y); + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment + * that contains the point which is 'location' distance along the entire path, where + * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths + * are made up of a list of segments, each of which contributes some fraction to + * the total length. + * From 1.3.10 this also supports the 'absolute' property, which lets us specify a location + * as the absolute distance in pixels, rather than a proportion of the total path. + */ + findSegmentForLocation = function(location, absolute) { + if (absolute) { + location = location > 0 ? location / totalLength : (totalLength + location) / totalLength; + } + + var idx = segmentProportions.length - 1, inSegmentProportion = 1; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, + sourceAnchor, targetAnchor, lineWidth, minWidth, sourceInfo, targetInfo) { + segments = []; + segmentProportions = []; + totalLength = 0; + segmentProportionalLengths = []; + maxX = maxY = -Infinity; + minX = minY = Infinity; + + self.lineWidth = lineWidth; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + sourceOffx = (lw / 2) + (sourceStub + targetStub), + targetOffx = (lw / 2) + (targetStub + sourceStub), + sourceOffy = (lw / 2) + (sourceStub + targetStub), + targetOffy = (lw / 2) + (targetStub + sourceStub), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(sourceEndpoint), + to = targetAnchor.orientation || targetAnchor.getOrientation(targetEndpoint), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + sourceOffx + targetOffx, + h = Math.abs(targetPos[1] - sourcePos[1]) + sourceOffy + targetOffy; + + // if either anchor does not have an orientation set, we derive one from their relative + // positions. we fix the axis to be the one in which the two elements are further apart, and + // point each anchor at the other element. this is also used when dragging a new connection. + if (so[0] == 0 && so[1] == 0 || to[0] == 0 && to[1] == 0) { + var index = w > h ? 0 : 1, oIndex = [1,0][index]; + so = []; to = []; + so[index] = sourcePos[index] > targetPos[index] ? -1 : 1; + to[index] = sourcePos[index] > targetPos[index] ? 1 : -1; + so[oIndex] = 0; + to[oIndex] = 0; + } + + /* + this code is unexplained and causes paint errors with continuous anchors sometimes. + commenting it out until i can get to the bottom of it. + if (w < minWidth) { + sourceOffx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + sourceOffy += (minWidth - h) / 2; + h = minWidth; + } + */ + + var sx = swapX ? (w - targetOffx) +( gap * so[0]) : sourceOffx + (gap * so[0]), + sy = swapY ? (h - targetOffy) + (gap * so[1]) : sourceOffy + (gap * so[1]), + tx = swapX ? sourceOffx + (gap * to[0]) : (w - targetOffx) + (gap * to[0]), + ty = swapY ? sourceOffy + (gap * to[1]) : (h - targetOffy) + (gap * to[1]), + startStubX = sx + (so[0] * sourceStub), + startStubY = sy + (so[1] * sourceStub), + endStubX = tx + (to[0] * targetStub), + endStubY = ty + (to[1] * targetStub), + isXGreaterThanStubTimes2 = Math.abs(sx - tx) > (sourceStub + targetStub), + isYGreaterThanStubTimes2 = Math.abs(sy - ty) > (sourceStub + targetStub), + midx = startStubX + ((endStubX - startStubX) * midpoint), + midy = startStubY + ((endStubY - startStubY) * midpoint), + oProduct = ((so[0] * to[0]) + (so[1] * to[1])), + opposite = oProduct == -1, + perpendicular = oProduct == 0, + orthogonal = oProduct == 1; + + x -= sourceOffx; y -= sourceOffy; + points = [x, y, w, h, sx, sy, tx, ty]; + var extraPoints = []; + + var sourceAxis = so[0] == 0 ? "y" : "x", + anchorOrientation = opposite ? "opposite" : orthogonal ? "orthogonal" : "perpendicular", + segment = jsPlumbUtil.segment([sx, sy], [tx, ty]), + flipSourceSegments = so[sourceAxis == "x" ? 0 : 1] == -1, + flipSegments = { + "x":[null, 4, 3, 2, 1], + "y":[null, 2, 1, 4, 3] + } + + if (flipSourceSegments) + segment = flipSegments[sourceAxis][segment]; + + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + var findClearedLine = function(start, mult, anchorPos, dimension) { + return start + (mult * (( 1 - anchorPos) * dimension) + Math.max(sourceStub, targetStub)); + //mx = so[0] == 0 ? startStubX + ((1 - sourceAnchor.x) * sourceInfo.width) + stub : startStubX, + }, + + lineCalculators = { + oppositex : function() { + if (sourceEndpoint.elementId == targetEndpoint.elementId) { + var _y = startStubY + ((1 - sourceAnchor.y) * sourceInfo.height) + Math.max(sourceStub, targetStub); + return [ [ startStubX, _y ], [ endStubX, _y ]]; + } + else if (isXGreaterThanStubTimes2 && (segment == 1 || segment == 2)) { + return [[ midx, sy ], [ midx, ty ]]; + } + else { + return [[ startStubX, midy ], [endStubX, midy ]]; + } + }, + orthogonalx : function() { + if (segment == 1 || segment == 2) { + return [ [ endStubX, startStubY ]]; + } + else { + return [ [ startStubX, endStubY ]]; + } + }, + perpendicularx : function() { + var my = (ty + sy) / 2; + if ((segment == 1 && to[1] == 1) || (segment == 2 && to[1] == -1)) { + if (Math.abs(tx - sx) > Math.max(sourceStub, targetStub)) + return [ [endStubX, startStubY ]]; + else + return [ [startStubX, startStubY ], [ startStubX, my ], [ endStubX, my ]]; + } + else if ((segment == 3 && to[1] == -1) || (segment == 4 && to[1] == 1)) { + return [ [ startStubX, my ], [ endStubX, my ]]; + } + else if ((segment == 3 && to[1] == 1) || (segment == 4 && to[1] == -1)) { + return [ [ startStubX, endStubY ]]; + } + else if ((segment == 1 && to[1] == -1) || (segment == 2 && to[1] == 1)) { + if (Math.abs(tx - sx) > Math.max(sourceStub, targetStub)) + return [ [ midx, startStubY ], [ midx, endStubY ]]; + else + return [ [ startStubX, endStubY ]]; + } + }, + oppositey : function() { + if (sourceEndpoint.elementId == targetEndpoint.elementId) { + var _x = startStubX + ((1 - sourceAnchor.x) * sourceInfo.width) + Math.max(sourceStub, targetStub); + return [ [ _x, startStubY ], [ _x, endStubY ]]; + } + else if (isYGreaterThanStubTimes2 && (segment == 2 || segment == 3)) { + return [[ sx, midy ], [ tx, midy ]]; + } + else { + return [[ midx, startStubY ], [midx, endStubY ]]; + } + }, + orthogonaly : function() { + if (segment == 2 || segment == 3) { + return [ [ startStubX, endStubY ]]; + } + else { + return [ [ endStubX, startStubY ]]; + } + }, + perpendiculary : function() { + var mx = (tx + sx) / 2; + if ((segment == 2 && to[0] == -1) || (segment == 3 && to[0] == 1)) { + if (Math.abs(tx - sx) > Math.max(sourceStub, targetStub)) + return [ [startStubX, endStubY ]]; + else + return [ [startStubX, midy ], [ endStubX, midy ]]; + } + else if ((segment == 1 && to[0] == -1) || (segment == 4 && to[0] == 1)) { + var mx = (tx + sx) / 2; + return [ [ mx, startStubY ], [ mx, endStubY ]]; + } + else if ((segment == 1 && to[0] == 1) || (segment == 4 && to[0] == -1)) { + return [ [ endStubX, startStubY ]]; + } + else if ((segment == 2 && to[0] == 1) || (segment == 3 && to[0] == -1)) { + if (Math.abs(ty - sy) > Math.max(sourceStub, targetStub)) + return [ [ startStubX, midy ], [ endStubX, midy ]]; + else + return [ [ endStubX, startStubY ]]; + } + } + }; + + var p = lineCalculators[anchorOrientation + sourceAxis](); + if (p) { + for (var i = 0; i < p.length; i++) { + addSegment(p[i][0], p[i][1], sx, sy, tx, ty); + } + } + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentProportions(sx, sy, tx, ty); + + // adjust the max values of the canvas if we have a value that is larger than what we previously set. + // + if (maxY > points[3]) points[3] = maxY + (lineWidth * 2); + if (maxX > points[2]) points[2] = maxX + (lineWidth * 2); + + return points; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location, absolute) { + return self.pointAlongPathFrom(location, 0, absolute); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location, absolute) { + return segments[findSegmentForLocation(location, absolute)["index"]][4]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance, absolute) { + var s = findSegmentForLocation(location, absolute), seg = s.segment, p = s.proportion, sl = segments[s.index][5], m = segments[s.index][4]; + var e = { + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + return e; + }; + }; + + // ********************************* END OF CONNECTOR TYPES ******************************************************************* + + // ********************************* ENDPOINT TYPES ******************************************************************* + + /** + * Class: Endpoints.Dot + * A round endpoint, with default radius 10 pixels. + */ + + /** + * Function: Constructor + * + * Parameters: + * + * radius - radius of the endpoint. defaults to 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + this.type = "Dot"; + var self = this; + params = params || {}; + this.radius = params.radius || 10; + this.defaultOffset = 0.5 * this.radius; + this.defaultInnerRadius = this.radius / 3; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var r = endpointStyle.radius || self.radius, + x = anchorPoint[0] - r, + y = anchorPoint[1] - r; + return [ x, y, r * 2, r * 2, r ]; + }; + }; + + /** + * Class: Endpoints.Rectangle + * A Rectangular Endpoint, with default size 20x20. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the endpoint. defaults to 20 pixels. + * height - height of the endpoint. defaults to 20 pixels. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + this.type = "Rectangle"; + var self = this; + params = params || {}; + this.width = params.width || 20; + this.height = params.height || 20; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height]; + }; + }; + + + var DOMElementEndpoint = function(params) { + jsPlumb.DOMElementComponent.apply(this, arguments); + var self = this; + + var displayElements = [ ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + }; + /** + * Class: Endpoints.Image + * Draws an image as the Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * src - location of the image to use. + */ + jsPlumb.Endpoints.Image = function(params) { + + this.type = "Image"; + DOMElementEndpoint.apply(this, arguments); + + var self = this, + initialized = false, + deleted = false, + widthToUse = params.width, + heightToUse = params.height, + _onload = null, + _endpoint = params.endpoint; + + this.img = new Image(); + self.ready = false; + + this.img.onload = function() { + self.ready = true; + widthToUse = widthToUse || self.img.width; + heightToUse = heightToUse || self.img.height; + if (_onload) { + _onload(self); + } + }; + + /* + Function: setImage + Sets the Image to use in this Endpoint. + + Parameters: + img - may be a URL or an Image object + onload - optional; a callback to execute once the image has loaded. + */ + _endpoint.setImage = function(img, onload) { + var s = img.constructor == String ? img : img.src; + _onload = onload; + self.img.src = img; + + if (self.canvas != null) + self.canvas.setAttribute("src", img); + }; + + _endpoint.setImage(params.src || params.url, params.onload); + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + self.anchorPoint = anchorPoint; + if (self.ready) return [anchorPoint[0] - widthToUse / 2, anchorPoint[1] - heightToUse / 2, + widthToUse, heightToUse]; + else return [0,0,0,0]; + }; + + self.canvas = document.createElement("img"), initialized = false; + self.canvas.style["margin"] = 0; + self.canvas.style["padding"] = 0; + self.canvas.style["outline"] = 0; + self.canvas.style["position"] = "absolute"; + var clazz = params.cssClass ? " " + params.cssClass : ""; + self.canvas.className = jsPlumb.endpointClass + clazz; + if (widthToUse) self.canvas.setAttribute("width", widthToUse); + if (heightToUse) self.canvas.setAttribute("height", heightToUse); + jsPlumb.appendElement(self.canvas, params.parent); + self.attachListeners(self.canvas, self); + + self.cleanup = function() { + deleted = true; + }; + + var actuallyPaint = function(d, style, anchor) { + if (!deleted) { + if (!initialized) { + self.canvas.setAttribute("src", self.img.src); + self.appendDisplayElement(self.canvas); + initialized = true; + } + var x = self.anchorPoint[0] - (widthToUse / 2), + y = self.anchorPoint[1] - (heightToUse / 2); + jsPlumb.sizeCanvas(self.canvas, x, y, widthToUse, heightToUse); + } + }; + + this.paint = function(d, style, anchor) { + if (self.ready) { + actuallyPaint(d, style, anchor); + } + else { + window.setTimeout(function() { + self.paint(d, style, anchor); + }, 200); + } + }; + }; + + /** + * Class: Endpoints.Blank + * An Endpoint that paints nothing (visible) on the screen. Supports cssClass and hoverClass parameters like all Endpoints. + */ + jsPlumb.Endpoints.Blank = function(params) { + var self = this; + this.type = "Blank"; + DOMElementEndpoint.apply(this, arguments); + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + return [anchorPoint[0], anchorPoint[1],10,0]; + }; + + self.canvas = document.createElement("div"); + self.canvas.style.display = "block"; + self.canvas.style.width = "1px"; + self.canvas.style.height = "1px"; + self.canvas.style.background = "transparent"; + self.canvas.style.position = "absolute"; + self.canvas.className = self._jsPlumb.endpointClass; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + }; + }; + + /** + * Class: Endpoints.Triangle + * A triangular Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the triangle's base. defaults to 55 pixels. + * height - height of the triangle from base to apex. defaults to 55 pixels. + */ + jsPlumb.Endpoints.Triangle = function(params) { + this.type = "Triangle"; + params = params || { }; + params.width = params.width || 55; + params.height = params.height || 55; + this.width = params.width; + this.height = params.height; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height ]; + }; + }; +// ********************************* END OF ENDPOINT TYPES ******************************************************************* + + +// ********************************* OVERLAY DEFINITIONS *********************************************************************** + + var AbstractOverlay = function(params) { + var visible = true, self = this; + this.isAppendedAtTopLevel = true; + this.component = params.component; + this.loc = params.location == null ? 0.5 : params.location; + this.endpointLoc = params.endpointLocation == null ? [ 0.5, 0.5] : params.endpointLocation; + this.setVisible = function(val) { + visible = val; + self.component.repaint(); + }; + this.isVisible = function() { return visible; }; + this.hide = function() { self.setVisible(false); }; + this.show = function() { self.setVisible(true); }; + + this.incrementLocation = function(amount) { + self.loc += amount; + self.component.repaint(); + }; + this.setLocation = function(l) { + self.loc = l; + self.component.repaint(); + }; + this.getLocation = function() { + return self.loc; + }; + }; + + + /** + * Class: Overlays.Arrow + * + * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length + * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction + * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line + * across the tail. + */ + /** + * Function: Constructor + * + * Parameters: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + this.type = "Arrow"; + AbstractOverlay.apply(this, arguments); + this.isAppendedAtTopLevel = false; + params = params || {}; + var self = this; + + this.length = params.length || 20; + this.width = params.width || 20; + this.id = params.id; + var direction = (params.direction || 1) < 0 ? -1 : 1, + paintStyle = params.paintStyle || { lineWidth:1 }, + // how far along the arrow the lines folding back in come to. default is 62.3%. + foldback = params.foldback || 0.623; + + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.cleanup = function() { }; // nothing to clean up for Arrows + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var hxy, mid, txy, tail, cxy; + if (connector.pointAlongPathFrom) { + + if (jsPlumbUtil.isString(self.loc) || self.loc > 1 || self.loc < 0) { + var l = parseInt(self.loc); + hxy = connector.pointAlongPathFrom(l, direction * self.length / 2, true), + mid = connector.pointOnPath(l, true), + txy = jsPlumbUtil.pointOnLine(hxy, mid, self.length); + } + else if (self.loc == 1) { + hxy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, -1); + txy = jsPlumbUtil.pointOnLine(hxy, mid, self.length); + + if (direction == -1) { + var _ = txy; + txy = hxy; + hxy = _; + } + } + else if (self.loc == 0) { + txy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, 1); + hxy = jsPlumbUtil.pointOnLine(txy, mid, self.length); + if (direction == -1) { + var _ = txy; + txy = hxy; + hxy = _; + } + } + else { + hxy = connector.pointAlongPathFrom(self.loc, direction * self.length / 2), + mid = connector.pointOnPath(self.loc), + txy = jsPlumbUtil.pointOnLine(hxy, mid, self.length); + } + + tail = jsPlumbUtil.perpendicularLineTo(hxy, txy, self.width); + cxy = jsPlumbUtil.pointOnLine(hxy, txy, foldback * self.length); + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x), + maxx = Math.max(hxy.x, tail[0].x, tail[1].x), + miny = Math.min(hxy.y, tail[0].y, tail[1].y), + maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + var d = { hxy:hxy, tail:tail, cxy:cxy }, + strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle, + fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle, + lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth; + + self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions); + + return [ minx, maxx, miny, maxy]; + } + else return [0,0,0,0]; + }; + }; + + /** + * Class: Overlays.PlainArrow + * + * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "PlainArrow"; + }; + + /** + * Class: Overlays.Diamond + * + * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40, + p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "Diamond"; + }; + + + // abstract superclass for overlays that add an element to the DOM. + var AbstractDOMOverlay = function(params) { + jsPlumb.DOMElementComponent.apply(this, arguments); + AbstractOverlay.apply(this, arguments); + + var self = this, initialised = false; + params = params || {}; + this.id = params.id; + var div; + + var makeDiv = function() { + div = params.create(params.component); + div = jsPlumb.CurrentLibrary.getDOMElement(div); + div.style["position"] = "absolute"; + var clazz = params["_jsPlumb"].overlayClass + " " + + (self.cssClass ? self.cssClass : + params.cssClass ? params.cssClass : ""); + div.className = clazz; + jsPlumb.appendElement(div, params.component.parent); + params["_jsPlumb"].getId(div); + self.attachListeners(div, self); + self.canvas = div; + }; + + this.getElement = function() { + if (div == null) { + makeDiv(); + } + return div; + }; + + this.getDimensions = function() { + return jsPlumb.CurrentLibrary.getSize(jsPlumb.CurrentLibrary.getElementObject(self.getElement())); + }; + + var cachedDimensions = null, + _getDimensions = function(component) { + if (cachedDimensions == null) + cachedDimensions = self.getDimensions(); + return cachedDimensions; + }; + + /* + * Function: clearCachedDimensions + * Clears the cached dimensions for the label. As a performance enhancement, label dimensions are + * cached from 1.3.12 onwards. The cache is cleared when you change the label text, of course, but + * there are other reasons why the text dimensions might change - if you make a change through CSS, for + * example, you might change the font size. in that case you should explicitly call this method. + */ + this.clearCachedDimensions = function() { + cachedDimensions = null; + }; + + this.computeMaxSize = function() { + var td = _getDimensions(); + return Math.max(td[0], td[1]); + }; + + //override setVisible + var osv = self.setVisible; + self.setVisible = function(state) { + osv(state); // call superclass + div.style.display = state ? "block" : "none"; + }; + + this.cleanup = function() { + if (div != null) jsPlumb.CurrentLibrary.removeElement(div); + }; + + this.paint = function(component, d, componentDimensions) { + if (!initialised) { + self.getElement(); + component.appendDisplayElement(div); + self.attachListeners(div, component); + initialised = true; + } + div.style.left = (componentDimensions[0] + d.minx) + "px"; + div.style.top = (componentDimensions[1] + d.miny) + "px"; + }; + + this.draw = function(component, currentConnectionPaintStyle, componentDimensions) { + var td = _getDimensions(); + if (td != null && td.length == 2) { + var cxy = {x:0,y:0}; + if (component.pointOnPath) { + var loc = self.loc, absolute = false; + if (jsPlumbUtil.isString(self.loc) || self.loc < 0 || self.loc > 1) { + loc = parseInt(self.loc); + absolute = true; + } + cxy = component.pointOnPath(loc, absolute); // a connection + } + else { + var locToUse = self.loc.constructor == Array ? self.loc : self.endpointLoc; + cxy = { x:locToUse[0] * componentDimensions[2], + y:locToUse[1] * componentDimensions[3] }; + } + + minx = cxy.x - (td[0] / 2), + miny = cxy.y - (td[1] / 2); + self.paint(component, { minx:minx, miny:miny, td:td, cxy:cxy }, componentDimensions); + return [minx, minx + td[0], miny, miny + td[1]]; + } + else return [0,0,0,0]; + }; + + this.reattachListeners = function(connector) { + if (div) { + self.reattachListenersForElement(div, self, connector); + } + }; + + }; + + /** + * Class: Overlays.Custom + * A Custom overlay. You supply a 'create' function which returns some DOM element, and jsPlumb positions it. + * The 'create' function is passed a Connection or Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * create - function for jsPlumb to call that returns a DOM element. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * id - optional id to use for later retrieval of this overlay. + * + */ + jsPlumb.Overlays.Custom = function(params) { + this.type = "Custom"; + AbstractDOMOverlay.apply(this, arguments); + }; + + /** + * Class: Overlays.Label + * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb + * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter + * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it + * puts on the Label's 'style' attribute, so the end result is the same. + */ + /** + * Function: Constructor + * + * Parameters: + * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes + * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle. + * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * id - optional id to use for later retrieval of this overlay. + * + */ + jsPlumb.Overlays.Label = function(params) { + var self = this; + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.cssClass = this.labelStyle != null ? this.labelStyle.cssClass : null; + params.create = function() { + return document.createElement("div"); + }; + jsPlumb.Overlays.Custom.apply(this, arguments); + this.type = "Label"; + + var label = params.label || "", + self = this, + labelText = null; + + /* + * Function: setLabel + * sets the label's, um, label. you would think i'd call this function + * 'setText', but you can pass either a Function or a String to this, so + * it makes more sense as 'setLabel'. This uses innerHTML on the label div, so keep + * that in mind if you need escaped HTML. + */ + this.setLabel = function(l) { + label = l; + labelText = null; + self.clearCachedDimensions(); + _update(); + self.component.repaint(); + }; + + var _update = function() { + if (typeof label == "function") { + var lt = label(self); + self.getElement().innerHTML = lt.replace(/\r\n/g, "
"); + } + else { + if (labelText == null) { + labelText = label; + self.getElement().innerHTML = labelText.replace(/\r\n/g, "
"); + } + } + }; + + this.getLabel = function() { + return label; + }; + + var superGD = this.getDimensions; + this.getDimensions = function() { + _update(); + return superGD(); + }; + + }; + + + // ********************************* END OF OVERLAY DEFINITIONS *********************************************************************** + +})(); \ No newline at end of file diff --git a/archive/1.3.16/jsPlumb-dom-adapter-1.3.16-RC1.js b/archive/1.3.16/jsPlumb-dom-adapter-1.3.16-RC1.js new file mode 100644 index 000000000..4d4c6add8 --- /dev/null +++ b/archive/1.3.16/jsPlumb-dom-adapter-1.3.16-RC1.js @@ -0,0 +1,214 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.16 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the base functionality for DOM type adapters. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ +;(function() { + + var canvasAvailable = !!document.createElement('canvas').getContext, + svgAvailable = !!window.SVGAngle || document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"), + // http://stackoverflow.com/questions/654112/how-do-you-detect-support-for-vml-or-svg-in-a-browser + vmlAvailable = function() { + if (vmlAvailable.vml == undefined) { + var a = document.body.appendChild(document.createElement('div')); + a.innerHTML = ''; + var b = a.firstChild; + b.style.behavior = "url(#default#VML)"; + vmlAvailable.vml = b ? typeof b.adj == "object": true; + a.parentNode.removeChild(a); + } + return vmlAvailable.vml; + }; + + /** + Manages dragging for some instance of jsPlumb. + */ + var DragManager = function(_currentInstance) { + var _draggables = {}, _dlist = [], _delements = {}, _elementsWithEndpoints = {}; + + /** + register some element as draggable. right now the drag init stuff is done elsewhere, and it is + possible that will continue to be the case. + */ + this.register = function(el) { + var jpcl = jsPlumb.CurrentLibrary; + el = jpcl.getElementObject(el); + var id = _currentInstance.getId(el), + domEl = jpcl.getDOMElement(el), + parentOffset = jpcl.getOffset(el); + + if (!_draggables[id]) { + _draggables[id] = el; + _dlist.push(el); + _delements[id] = {}; + } + + // look for child elements that have endpoints and register them against this draggable. + var _oneLevel = function(p, startOffset) { + if (p) { + for (var i = 0; i < p.childNodes.length; i++) { + if (p.childNodes[i].nodeType != 3) { + var cEl = jpcl.getElementObject(p.childNodes[i]), + cid = _currentInstance.getId(cEl, null, true); + if (cid && _elementsWithEndpoints[cid] && _elementsWithEndpoints[cid] > 0) { + var cOff = jpcl.getOffset(cEl); + _delements[id][cid] = { + id:cid, + offset:{ + left:cOff.left - parentOffset.left, + top:cOff.top - parentOffset.top + } + }; + } + _oneLevel(p.childNodes[i]); + } + } + } + }; + + _oneLevel(domEl); + }; + + // refresh the offsets for child elements of this element. + this.updateOffsets = function(elId) { + var jpcl = jsPlumb.CurrentLibrary, + el = jpcl.getElementObject(elId), + id = _currentInstance.getId(el), + children = _delements[id], + parentOffset = jpcl.getOffset(el); + + if (children) { + for (var i in children) { + var cel = jpcl.getElementObject(i), + cOff = jpcl.getOffset(cel); + + _delements[id][i] = { + id:i, + offset:{ + left:cOff.left - parentOffset.left, + top:cOff.top - parentOffset.top + } + }; + } + } + }; + + /** + notification that an endpoint was added to the given el. we go up from that el's parent + node, looking for a parent that has been registered as a draggable. if we find one, we add this + el to that parent's list of elements to update on drag (if it is not there already) + */ + this.endpointAdded = function(el) { + var jpcl = jsPlumb.CurrentLibrary, b = document.body, id = _currentInstance.getId(el), c = jpcl.getDOMElement(el), + p = c.parentNode, done = p == b; + + _elementsWithEndpoints[id] = _elementsWithEndpoints[id] ? _elementsWithEndpoints[id] + 1 : 1; + + while (p != b) { + var pid = _currentInstance.getId(p, null, true); + if (pid && _draggables[pid]) { + var idx = -1, pEl = jpcl.getElementObject(p), pLoc = jpcl.getOffset(pEl); + + if (_delements[pid][id] == null) { + var cLoc = jsPlumb.CurrentLibrary.getOffset(el); + _delements[pid][id] = { + id:id, + offset:{ + left:cLoc.left - pLoc.left, + top:cLoc.top - pLoc.top + } + }; + } + break; + } + p = p.parentNode; + } + }; + + this.endpointDeleted = function(endpoint) { + if (_elementsWithEndpoints[endpoint.elementId]) { + _elementsWithEndpoints[endpoint.elementId]--; + if (_elementsWithEndpoints[endpoint.elementId] <= 0) { + for (var i in _delements) { + delete _delements[i][endpoint.elementId]; + } + } + } + }; + + this.getElementsForDraggable = function(id) { + return _delements[id]; + }; + + this.reset = function() { + _draggables = {}; + _dlist = []; + _delements = {}; + _elementsWithEndpoints = {}; + }; + + }; + + // for those browsers that dont have it. they still don't have it! but at least they won't crash. + if (!window.console) + window.console = { time:function(){}, timeEnd:function(){}, group:function(){}, groupEnd:function(){}, log:function(){} }; + + window.jsPlumbAdapter = { + + headless:false, + + appendToRoot : function(node) { + document.body.appendChild(node); + }, + getRenderModes : function() { + return [ "canvas", "svg", "vml" ] + }, + isRenderModeAvailable : function(m) { + return { + "canvas":canvasAvailable, + "svg":svgAvailable, + "vml":vmlAvailable() + }[m]; + }, + getDragManager : function(_jsPlumb) { + return new DragManager(_jsPlumb); + }, + setRenderMode : function(mode) { + var renderMode; + + if (mode) { + mode = mode.toLowerCase(); + + var canvasAvailable = this.isRenderModeAvailable("canvas"), + svgAvailable = this.isRenderModeAvailable("svg"), + vmlAvailable = this.isRenderModeAvailable("vml"); + + //if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); + // now test we actually have the capability to do this. + if (mode === "svg") { + if (svgAvailable) renderMode = "svg" + else if (canvasAvailable) renderMode = "canvas" + else if (vmlAvailable) renderMode = "vml" + } + else if (mode === "canvas" && canvasAvailable) renderMode = "canvas"; + else if (vmlAvailable) renderMode = "vml"; + } + + return renderMode; + } + }; + +})(); \ No newline at end of file diff --git a/archive/1.3.16/jsPlumb-drag-1.3.16-RC1.js b/archive/1.3.16/jsPlumb-drag-1.3.16-RC1.js new file mode 100644 index 000000000..d71db029e --- /dev/null +++ b/archive/1.3.16/jsPlumb-drag-1.3.16-RC1.js @@ -0,0 +1,61 @@ +/* + * this is experimental and probably will not be used. solutions exist for most libraries. but of course if + * i want to support multiple scopes at some stage then i will have to do dragging inside jsPlumb. + */ +;(function() { + + window.jsPlumbDrag = function(_jsPlumb) { + + var ta = new TouchAdapter(); + + this.draggable = function(selector) { + var el, elId, da = [], elo, d = false, + isInSelector = function(el) { + if (typeof selector == "string") + return selector === _jsPlumb.getId(el); + + for (var i = 0; i < selector.length; i++) { + var _sel = jsPlumb.CurrentLibrary.getDOMElement(selector[i]); + if (_sel == el) return true; + } + return false; + }; + + ta.bind(document, "mousedown", function(e) { + var target = e.target || e.srcElement; + if (isInSelector(target)) { + el = jsPlumb.CurrentLibrary.getElementObject(target); + elId = _jsPlumb.getId(el); + elo = jsPlumb.CurrentLibrary.getOffset(el); + da = [e.pageX, e.pageY]; + d = true; + } + }); + + ta.bind(document, "mousemove", function(e) { + if (d) { + var dx = e.pageX - da[0], + dy = e.pageY - da[1]; + + jsPlumb.CurrentLibrary.setOffset(el, { + left:elo.left + dx, + top:elo.top + dy + }); + _jsPlumb.repaint(elId); + e.preventDefault(); + e.stopPropagation(); + } + }); + ta.bind(document, "mouseup", function(e) { + el = null; + d = false; + }); + }; + + var isIOS = ((/iphone|ipad/gi).test(navigator.appVersion)); + if (isIOS) + _jsPlumb.draggable = this.draggable; + + }; + +})(); \ No newline at end of file diff --git a/archive/1.3.16/jsPlumb-overlays-guidelines-1.3.16-RC1.js b/archive/1.3.16/jsPlumb-overlays-guidelines-1.3.16-RC1.js new file mode 100644 index 000000000..72b63b009 --- /dev/null +++ b/archive/1.3.16/jsPlumb-overlays-guidelines-1.3.16-RC1.js @@ -0,0 +1,73 @@ + // this is really just a test overlay, so its undocumented and doesnt take any parameters. but i was loth to delete it. + jsPlumb.Overlays.GuideLines = function() { + var self = this; + self.length = 50; + self.lineWidth = 5; + this.type = "GuideLines"; + AbstractOverlay.apply(this, arguments); + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var head = connector.pointAlongPathFrom(self.loc, self.length / 2), + mid = connector.pointOnPath(self.loc), + tail = jsPlumbUtil.pointOnLine(head, mid, self.length), + tailLine = jsPlumbUtil.perpendicularLineTo(head, tail, 40), + headLine = jsPlumbUtil.perpendicularLineTo(tail, head, 20); + + self.paint(connector, [head, tail, tailLine, headLine], self.lineWidth, "red", null, connectorDimensions); + + return [Math.min(head.x, tail.x), Math.min(head.y, tail.y), Math.max(head.x, tail.x), Math.max(head.y,tail.y)]; + }; + + this.computeMaxSize = function() { return 50; }; + + this.cleanup = function() { }; // nothing to clean up for GuideLines + }; + +// a test + jsPlumb.Overlays.svg.GuideLines = function() { + var path = null, self = this, path2 = null, p1_1, p1_2; + jsPlumb.Overlays.GuideLines.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + + p1_1 = _node("path"); + connector.svg.appendChild(p1_1); + self.attachListeners(p1_1, connector); + self.attachListeners(p1_1, self); + + p1_2 = _node("path"); + connector.svg.appendChild(p1_2); + self.attachListeners(p1_2, connector); + self.attachListeners(p1_2, self); + + } + + _attr(path, { + "d" : makePath(d[0], d[1]), + stroke : "red", + fill : null + }); + + _attr(p1_1, { + "d" : makePath(d[2][0], d[2][1]), + stroke : "blue", + fill : null + }); + + _attr(p1_2, { + "d" : makePath(d[3][0], d[3][1]), + stroke : "green", + fill : null + }); + }; + + var makePath = function(d1, d2) { + return "M " + d1.x + "," + d1.y + + " L" + d2.x + "," + d2.y; + }; + }; \ No newline at end of file diff --git a/archive/1.3.16/jsPlumb-renderers-canvas-1.3.16-RC1.js b/archive/1.3.16/jsPlumb-renderers-canvas-1.3.16-RC1.js new file mode 100644 index 000000000..dc881c918 --- /dev/null +++ b/archive/1.3.16/jsPlumb-renderers-canvas-1.3.16-RC1.js @@ -0,0 +1,510 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.16 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the HTML5 canvas renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + +// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS ******************************************************************* + + // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. + var _connectionBeingDragged = null, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, + _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }; + + /* + * Class:CanvasMouseAdapter + * Provides support for mouse events on canvases. + */ + var CanvasMouseAdapter = function() { + var self = this; + self.overlayPlacements = []; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + jsPlumbUtil.EventGenerator.apply(this, arguments); + /** + * returns whether or not the given event is ojver a painted area of the canvas. + */ + this._over = function(e) { + var o = _getOffset(_getElementObject(self.canvas)), + pageXY = _pageXY(e), + x = pageXY[0] - o.left, y = pageXY[1] - o.top; + if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + return false; + }; + + var _mouseover = false, _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false, + _nullSafeHasClass = function(el, clazz) { + return el != null && _hasClass(el, clazz); + }; + this.mousemove = function(e) { + var pageXY = _pageXY(e), clientXY = _clientXY(e), + ee = document.elementFromPoint(clientXY[0], clientXY[1]), + eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); + var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); + if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.fire("mouseenter", self, e); + return true; + } + // TODO here there is a remote chance that the overlay the mouse moved onto + // is actually not an overlay for the current component. a more thorough check would + // be to ensure the overlay belonged to the current component. + else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { + _mouseover = false; + self.fire("mouseexit", self, e); + } + self.fire("mousemove", self, e); + }; + + this.click = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); + self.fire("mousedown", self, e); + } + }; + + this.mouseup = function(e) { + _mouseDown = false; + self.fire("mouseup", self, e); + }; + + this.contextmenu = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("contextmenu", self, e); + _mouseWasDown = false; + }; + }; + + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + params["_jsPlumb"].appendElement(canvas, params.parent); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + params["_jsPlumb"].getId(canvas, params.uuid); + if (params.tooltip) canvas.setAttribute("title", params.tooltip); + + return canvas; + }; + + var CanvasComponent = function(params) { + CanvasMouseAdapter.apply(this, arguments); + + var displayElements = [ ]; + this.getDisplayElements = function() { return displayElements; }; + this.appendDisplayElement = function(el) { displayElements.push(el); }; + }; + + /** + * Class:CanvasConnector + * Superclass for Canvas Connector renderers. + */ + var CanvasConnector = jsPlumb.CanvasConnector = function(params) { + + CanvasComponent.apply(this, arguments); + + var _paintOneStyle = function(dim, aStyle) { + self.ctx.save(); + jsPlumb.extend(self.ctx, aStyle); + if (aStyle.gradient) { + var g = self.createGradient(dim, self.ctx); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + self.ctx.strokeStyle = g; + } + self._paint(dim, aStyle); + self.ctx.restore(); + }; + + var self = this, + clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:params.tooltip + }); + self.ctx = self.canvas.getContext("2d"); + + self.appendDisplayElement(self.canvas); + + self.paint = function(dim, style) { + if (style != null) { + jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); + if (self.getZIndex()) + self.canvas.style.zIndex = self.getZIndex(); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + _paintOneStyle(dim, outlineStyle); + } + _paintOneStyle(dim, style); + } + }; + }; + + /** + * Class:CanvasEndpoint + * Superclass for Canvas Endpoint renderers. + */ + var CanvasEndpoint = function(params) { + var self = this; + CanvasComponent.apply(this, arguments); + var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""), + canvasParams = { + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:self.tooltip + }; + self.canvas = _newCanvas(canvasParams); + self.ctx = self.canvas.getContext("2d"); + + self.appendDisplayElement(self.canvas); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + } + + self._paint.apply(this, arguments); + }; + }; + + jsPlumb.Endpoints.canvas.Dot = function(params) { + jsPlumb.Endpoints.Dot.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + var self = this, + parseValue = function(value) { + try { return parseInt(value); } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }, + calculateAdjustments = function(gradient) { + var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius; + gradient.offset && (offsetAdjustment = parseValue(gradient.offset)); + gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius)); + return [offsetAdjustment, innerRadius]; + }; + this._paint = function(d, style, anchor) { + if (style != null) { + var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + if (style.gradient) { + var adjustments = calculateAdjustments(style.gradient), + yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0], + xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0], + g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + ctx.beginPath(); + ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + } + }; + }; + + jsPlumb.Endpoints.canvas.Rectangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) { + + var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + + /* canvas gradient */ + if (style.gradient) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, d[2], d[3]); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + jsPlumb.Endpoints.canvas.Triangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Triangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) + { + var width = d[2], height = d[3], x = d[0], y = d[1], + ctx = self.canvas.getContext('2d'), + offsetX = 0, offsetY = 0, angle = 0, + orientation = anchor.getOrientation(self); + + if( orientation[0] == 1 ) { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = style.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + /* + * Canvas Image Endpoint: uses the default version, which creates an tag. + */ + jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image; + + /* + * Blank endpoint in all renderers is just the default Blank endpoint. + */ + jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank; + + /* + * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Bezier = function() { + var self = this; + jsPlumb.Connectors.Bezier.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions, style) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO i doubt this handles the case that source and target are swapped. + this.createGradient = function(dim, ctx, swap) { + return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + /* + * Canvas straight line Connector. Draws a straight line onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Straight = function() { + var self = this, + segmentMultipliers = [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ]; + + jsPlumb.Connectors.Straight.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions, style) { + + self.ctx.beginPath(); + + if (style.dashstyle && style.dashstyle.split(" ").length == 2) { + // only a very simple dashed style is supported - having two values, which define the stroke length + // (as a multiple of the stroke width) and then the space length (also as a multiple of stroke width). + var ds = style.dashstyle.split(" "); + if (ds.length != 2) ds = [2, 2]; + var dss = [ ds[0] * style.lineWidth, ds[1] * style.lineWidth ], + m = (dimensions[6] - dimensions[4]) / (dimensions[7] - dimensions[5]), + s = jsPlumbUtil.segment([dimensions[4], dimensions[5]], [ dimensions[6], dimensions[7] ]), + sm = segmentMultipliers[s], + theta = Math.atan(m), + l = Math.sqrt(Math.pow(dimensions[6] - dimensions[4], 2) + Math.pow(dimensions[7] - dimensions[5], 2)), + repeats = Math.floor(l / (dss[0] + dss[1])), + curPos = [dimensions[4], dimensions[5]]; + + + // TODO: the question here is why could we not support this in all connector types? it's really + // just a case of going along and asking jsPlumb for the next point on the path a few times, until it + // reaches the end. every type of connector supports that method, after all. but right now its only the + // bezier connector that gives you back the new location on the path along with the x,y coordinates, which + // we would need. we'd start out at loc=0 and ask for the point along the path that is dss[0] pixels away. + // we then ask for the point that is (dss[0] + dss[1]) pixels away; and from that one we need not just the + // x,y but the location, cos we're gonna plug that location back in in order to find where that dash ends. + // + // it also strikes me that it should be trivial to support arbitrary dash styles (having more or less than two + // entries). you'd just iterate that array using a step size of 2, and generify the (rss[0] + rss[1]) + // computation to be sum(rss[0]..rss[n]). + + for (var i = 0; i < repeats; i++) { + self.ctx.moveTo(curPos[0], curPos[1]); + + var nextEndX = curPos[0] + (Math.abs(Math.sin(theta) * dss[0]) * sm[0]), + nextEndY = curPos[1] + (Math.abs(Math.cos(theta) * dss[0]) * sm[1]), + nextStartX = curPos[0] + (Math.abs(Math.sin(theta) * (dss[0] + dss[1])) * sm[0]), + nextStartY = curPos[1] + (Math.abs(Math.cos(theta) * (dss[0] + dss[1])) * sm[1]) + + self.ctx.lineTo(nextEndX, nextEndY); + curPos = [nextStartX, nextStartY]; + } + + // now draw the last bit + self.ctx.moveTo(curPos[0], curPos[1]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + + } + else { + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + } + + self.ctx.stroke(); + }; + + // TODO this does not handle the case that src and target are swapped. + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + jsPlumb.Connectors.canvas.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions, style) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + +// ********************************* END OF CANVAS RENDERERS ******************************************************************* + + jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label; + jsPlumb.Overlays.canvas.Custom = jsPlumb.Overlays.Custom; + + /** + * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. + */ + var CanvasOverlay = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }; + + var AbstractCanvasArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + CanvasOverlay.apply(this, originalArgs); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + var ctx = connector.ctx; + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(d.hxy.x, d.hxy.y); + ctx.lineTo(d.tail[0].x, d.tail[0].y); + ctx.lineTo(d.cxy.x, d.cxy.y); + ctx.lineTo(d.tail[1].x, d.tail[1].y); + ctx.lineTo(d.hxy.x, d.hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + }; + + jsPlumb.Overlays.canvas.Arrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.canvas.PlainArrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.canvas.Diamond = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.16/jsPlumb-renderers-svg-1.3.16-RC1.js b/archive/1.3.16/jsPlumb-renderers-svg-1.3.16-RC1.js new file mode 100644 index 000000000..0cef719af --- /dev/null +++ b/archive/1.3.16/jsPlumb-renderers-svg-1.3.16-RC1.js @@ -0,0 +1,577 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.16 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the SVG renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +/** + * SVG support for jsPlumb. + * + * things to investigate: + * + * gradients: https://developer.mozilla.org/en/svg_in_html_introduction + * css:http://tutorials.jenkov.com/svg/svg-and-css.html + * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath + * pointer events: https://developer.mozilla.org/en/css/pointer-events + * + * IE9 hover jquery: http://forum.jquery.com/topic/1-6-2-broke-svg-hover-events + * + */ +;(function() { + + var svgAttributeMap = { + "joinstyle":"stroke-linejoin", + "stroke-linejoin":"stroke-linejoin", + "stroke-dashoffset":"stroke-dashoffset", + "stroke-linecap":"stroke-linecap" + }, + STROKE_DASHARRAY = "stroke-dasharray", + DASHSTYLE = "dashstyle", + LINEAR_GRADIENT = "linearGradient", + RADIAL_GRADIENT = "radialGradient", + FILL = "fill", + STOP = "stop", + STROKE = "stroke", + STROKE_WIDTH = "stroke-width", + STYLE = "style", + NONE = "none", + JSPLUMB_GRADIENT = "jsplumb_gradient_", + LINE_WIDTH = "lineWidth", + ns = { + svg:"http://www.w3.org/2000/svg", + xhtml:"http://www.w3.org/1999/xhtml" + }, + _attr = function(node, attributes) { + for (var i in attributes) + node.setAttribute(i, "" + attributes[i]); + }, + _node = function(name, attributes) { + var n = document.createElementNS(ns.svg, name); + attributes = attributes || {}; + attributes["version"] = "1.1"; + attributes["xmlns"] = ns.xhtml; + _attr(n, attributes); + return n; + }, + _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; }, + _clearGradient = function(parent) { + for (var i = 0; i < parent.childNodes.length; i++) { + if (parent.childNodes[i].tagName == LINEAR_GRADIENT || parent.childNodes[i].tagName == RADIAL_GRADIENT) + parent.removeChild(parent.childNodes[i]); + } + }, + _updateGradient = function(parent, node, style, dimensions, uiComponent) { + var id = JSPLUMB_GRADIENT + uiComponent._jsPlumb.idstamp(); + // first clear out any existing gradient + _clearGradient(parent); + // this checks for an 'offset' property in the gradient, and in the absence of it, assumes + // we want a linear gradient. if it's there, we create a radial gradient. + // it is possible that a more explicit means of defining the gradient type would be + // better. relying on 'offset' means that we can never have a radial gradient that uses + // some default offset, for instance. + // issue 244 suggested the 'gradientUnits' attribute; without this, straight/flowchart connectors with gradients would + // not show gradients when the line was perfectly horizontal or vertical. + if (!style.gradient.offset) { + var g = _node(LINEAR_GRADIENT, {id:id, gradientUnits:"userSpaceOnUse"}); + parent.appendChild(g); + } + else { + var g = _node(RADIAL_GRADIENT, { + id:id + }); + parent.appendChild(g); + } + + // the svg radial gradient seems to treat stops in the reverse + // order to how canvas does it. so we want to keep all the maths the same, but + // iterate the actual style declarations in reverse order, if the x indexes are not in order. + for (var i = 0; i < style.gradient.stops.length; i++) { + // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of + // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments. + // so, not too concerned about leaving this in for now. + var styleToUse = i; + if (dimensions.length == 8) + styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i; + else + styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i; + var stopColor = jsPlumbUtil.convertStyle(style.gradient.stops[styleToUse][1], true); + var s = _node(STOP, {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor}); + g.appendChild(s); + } + var applyGradientTo = style.strokeStyle ? STROKE : FILL; + node.setAttribute(STYLE, applyGradientTo + ":url(#" + id + ")"); + }, + _applyStyles = function(parent, node, style, dimensions, uiComponent) { + + if (style.gradient) { + _updateGradient(parent, node, style, dimensions, uiComponent); + } + else { + // make sure we clear any existing gradient + _clearGradient(parent); + node.setAttribute(STYLE, ""); + } + + node.setAttribute(FILL, style.fillStyle ? jsPlumbUtil.convertStyle(style.fillStyle, true) : NONE); + node.setAttribute(STROKE, style.strokeStyle ? jsPlumbUtil.convertStyle(style.strokeStyle, true) : NONE); + if (style.lineWidth) { + node.setAttribute(STROKE_WIDTH, style.lineWidth); + } + + // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like + // the syntax in VML but is actually kind of nasty: values are given in the pixel + // coordinate space, whereas in VML they are multiples of the width of the stroked + // line, which makes a lot more sense. for that reason, jsPlumb is supporting both + // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from + // VML, which will be the preferred method. the code below this converts a dashstyle + // attribute given in terms of stroke width into a pixel representation, by using the + // stroke's lineWidth. + if (style[DASHSTYLE] && style[LINE_WIDTH] && !style[STROKE_DASHARRAY]) { + var sep = style[DASHSTYLE].indexOf(",") == -1 ? " " : ",", + parts = style[DASHSTYLE].split(sep), + styleToUse = ""; + parts.forEach(function(p) { + styleToUse += (Math.floor(p * style.lineWidth) + sep); + }); + node.setAttribute(STROKE_DASHARRAY, styleToUse); + } + else if(style[STROKE_DASHARRAY]) { + node.setAttribute(STROKE_DASHARRAY, style[STROKE_DASHARRAY]); + } + + // extra attributes such as join type, dash offset. + for (var i in svgAttributeMap) { + if (style[i]) { + node.setAttribute(svgAttributeMap[i], style[i]); + } + } + }, + _decodeFont = function(f) { + var r = /([0-9].)(p[xt])\s(.*)/; + var bits = f.match(r); + return {size:bits[1] + bits[2], font:bits[3]}; + }, + _classManip = function(el, add, clazz) { + var classesToAddOrRemove = clazz.split(" "), + className = el.className, + curClasses = className.baseVal.split(" "); + + for (var i = 0; i < classesToAddOrRemove.length; i++) { + if (add) { + if (curClasses.indexOf(classesToAddOrRemove[i]) == -1) + curClasses.push(classesToAddOrRemove[i]); + } + else { + var idx = curClasses.indexOf(classesToAddOrRemove[i]); + if (idx != -1) + curClasses.splice(idx, 1); + } + } + + el.className.baseVal = curClasses.join(" "); + }, + _addClass = function(el, clazz) { + _classManip(el, true, clazz); + }, + _removeClass = function(el, clazz) { + _classManip(el, false, clazz); + }; + + /** + utility methods for other objects to use. + */ + jsPlumbUtil.svg = { + addClass:_addClass, + removeClass:_removeClass, + node:_node, + attr:_attr, + pos:_pos + }; + + /* + * Base class for SVG components. + */ + var SvgComponent = function(params) { + var self = this, + pointerEventsSpec = params.pointerEventsSpec || "all"; + jsPlumb.jsPlumbUIComponent.apply(this, params.originalArgs); + self.canvas = null, self.path = null, self.svg = null; + + var clazz = params.cssClass + " " + (params.originalArgs[0].cssClass || ""), + svgParams = { + "style":"", + "width":0, + "height":0, + "pointer-events":pointerEventsSpec, + "position":"absolute" + }; + if (self.tooltip) svgParams["title"] = self.tooltip; + self.svg = _node("svg", svgParams); + if (params.useDivWrapper) { + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.sizeCanvas(self.canvas,0,0,1,1); + self.canvas.className = clazz; + if (self.tooltip) self.canvas.setAttribute("title", self.tooltip); + } + else { + _attr(self.svg, { "class":clazz }); + self.canvas = self.svg; + } + + params._jsPlumb.appendElement(self.canvas, params.originalArgs[0]["parent"]); + if (params.useDivWrapper) self.canvas.appendChild(self.svg); + + // TODO this displayElement stuff is common between all components, across all + // renderers. would be best moved to jsPlumbUIComponent. + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + this.paint = function(d, style, anchor) { + if (style != null) { + var x = d[0], y = d[1]; + if (params.useDivWrapper) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + x = 0, y = 0; + } + var p = _pos([x, y, d[2], d[3]]); + if (self.getZIndex()) p += ";z-index:" + self.getZIndex() + ";"; + _attr(self.svg, { + "style":p, + "width": d[2], + "height": d[3] + }); + self._paint.apply(this, arguments); + } + }; + }; + + /* + * Base class for SVG connectors. + */ + var SvgConnector = jsPlumb.SvgConnector = function(params) { + var self = this; + SvgComponent.apply(this, [ { + cssClass:params["_jsPlumb"].connectorClass, + originalArgs:arguments, + pointerEventsSpec:"none", + tooltip:params.tooltip, + _jsPlumb:params["_jsPlumb"] + } ]); + this._paint = function(d, style) { + var p = self.getPath(d), a = { "d":p }, outlineStyle = null; + a["pointer-events"] = "all"; + + // outline style. actually means drawing an svg object underneath the main one. + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = jsPlumb.CurrentLibrary.extend({}, style); + outlineStyle.strokeStyle = jsPlumbUtil.convertStyle(style.outlineColor); + outlineStyle.lineWidth = outlineStrokeWidth; + + if (self.bgPath == null) { + self.bgPath = _node("path", a); + self.svg.appendChild(self.bgPath); + self.attachListeners(self.bgPath, self); + } + else { + _attr(self.bgPath, a); + } + + _applyStyles(self.svg, self.bgPath, outlineStyle, d, self); + } + + + // test - see below + // a["clip-path"]= "url(#testClip)"; + + if (self.path == null) { + self.path = _node("path", a); + self.svg.appendChild(self.path); + self.attachListeners(self.path, self); + + /* + this is a test of a clip path. i'm looking into using one of these to animate a jsplumb connection. + you could do this by walking along the line, stepping along a little at a time, and setting the clip + path to extend as far as that point. + + self.clip = _node("clipPath", {id:"testClip", clipPathUnits:"objectBoundingBox"}); + self.svg.appendChild(self.clip); + self.clip.appendChild(_node("rect", { + x:"0",y:"0",width:"0.5",height:"1" + })); + */ + } + else { + _attr(self.path, a); + } + + _applyStyles(self.svg, self.path, style, d, self); + }; + + this.reattachListeners = function() { + if (self.bgPath) self.reattachListenersForElement(self.bgPath, self); + if (self.path) self.reattachListenersForElement(self.path, self); + }; + + }; + + /* + * SVG Bezier Connector + */ + jsPlumb.Connectors.svg.Bezier = function(params) { + jsPlumb.Connectors.Bezier.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { + var _p = "M " + d[4] + " " + d[5]; + _p += (" C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]); + return _p; + }; + }; + + /* + * SVG straight line Connector + */ + jsPlumb.Connectors.svg.Straight = function(params) { + jsPlumb.Connectors.Straight.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; }; + }; + + jsPlumb.Connectors.svg.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "M " + dimensions[4] + "," + dimensions[5], + lx = dimensions[4], + ly = dimensions[5]; + + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + + var x1 = dimensions[9 + (i*2)], y1 = dimensions[10 + (i*2)], + x2 = dimensions[9 + ((i + 1) * 2)], y2 = dimensions[10 + ((i + 1) * 2)], + horiz = (x1 != lx) && (y1 == ly), + vert = (x1 == lx) && (y1 != ly), + multX = horiz ? x1 > x2 ? 1 : -1 : 0, + multY = vert ? y1 > y2 ? 1 : -1 : 0, + halfStroke = self.lineWidth / 2; + + // previously: + //p = p + " L " + x1 + " " + y1; + //p = p + " M " + x1 + " " + y1; + + // now, with support for painting an extra bit at the end each line: + p = p + " L " + x1 + " " + y1; + p = p + " L " + (x1 + (multX * halfStroke)) + " " + (y1 + (multY * halfStroke)); + + lx = x1; + ly = y1; + p = p + " M " + x1 + " " + y1; + } + // finally draw a line to the end + p = p + " L " + dimensions[6] + "," + dimensions[7]; + return p; + }; + }; + + /* + * Base class for SVG endpoints. + */ + var SvgEndpoint = window.SvgEndpoint = function(params) { + var self = this; + SvgComponent.apply(this, [ { + cssClass:params["_jsPlumb"].endpointClass, + originalArgs:arguments, + pointerEventsSpec:"all", + useDivWrapper:true, + _jsPlumb:params["_jsPlumb"] + } ]); + this._paint = function(d, style) { + var s = jsPlumb.extend({}, style); + if (s.outlineColor) { + s.strokeWidth = s.outlineWidth; + s.strokeStyle = jsPlumbUtil.convertStyle(s.outlineColor, true); + } + + if (self.node == null) { + self.node = self.makeNode(d, s); + self.svg.appendChild(self.node); + self.attachListeners(self.node, self); + } + _applyStyles(self.svg, self.node, s, d, self); + _pos(self.node, d); + }; + + this.reattachListeners = function() { + if (self.node) self.reattachListenersForElement(self.node, self); + }; + }; + + /* + * SVG Dot Endpoint + */ + jsPlumb.Endpoints.svg.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("circle", { + "cx" : d[2] / 2, + "cy" : d[3] / 2, + "r" : d[2] / 2 + }); + }; + }; + + /* + * SVG Rectangle Endpoint + */ + jsPlumb.Endpoints.svg.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("rect", { + "width":d[2], + "height":d[3] + }); + }; + }; + + /* + * SVG Image Endpoint is the default image endpoint. + */ + jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image; + /* + * Blank endpoint in svg renderer is the default Blank endpoint. + */ + jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank; + /* + * Label overlay in svg renderer is the default Label overlay. + */ + jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label; + /* + * Custom overlay in svg renderer is the default Custom overlay. + */ + jsPlumb.Overlays.svg.Custom = jsPlumb.Overlays.Custom; + + var AbstractSvgArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + this.isAppendedAtTopLevel = false; + var self = this, path = null; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path", { + "pointer-events":"all" + }); + connector.svg.appendChild(path); + + self.attachListeners(path, connector); + self.attachListeners(path, self); + } + var clazz = originalArgs && (originalArgs.length == 1) ? (originalArgs[0].cssClass || "") : ""; + + _attr(path, { + "d" : makePath(d), + "class" : clazz, + stroke : strokeStyle ? strokeStyle : null, + fill : fillStyle ? fillStyle : null + }); + }; + var makePath = function(d) { + return "M" + d.hxy.x + "," + d.hxy.y + + " L" + d.tail[0].x + "," + d.tail[0].y + + " L" + d.cxy.x + "," + d.cxy.y + + " L" + d.tail[1].x + "," + d.tail[1].y + + " L" + d.hxy.x + "," + d.hxy.y; + }; + this.reattachListeners = function() { + if (path) self.reattachListenersForElement(path, self); + }; + this.cleanup = function() { + if (path != null) jsPlumb.CurrentLibrary.removeElement(path); + }; + }; + + jsPlumb.Overlays.svg.Arrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.svg.PlainArrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.svg.Diamond = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; + + // a test + jsPlumb.Overlays.svg.GuideLines = function() { + var path = null, self = this, path2 = null, p1_1, p1_2; + jsPlumb.Overlays.GuideLines.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + + p1_1 = _node("path"); + connector.svg.appendChild(p1_1); + self.attachListeners(p1_1, connector); + self.attachListeners(p1_1, self); + + p1_2 = _node("path"); + connector.svg.appendChild(p1_2); + self.attachListeners(p1_2, connector); + self.attachListeners(p1_2, self); + + } + + _attr(path, { + "d" : makePath(d[0], d[1]), + stroke : "red", + fill : null + }); + + _attr(p1_1, { + "d" : makePath(d[2][0], d[2][1]), + stroke : "blue", + fill : null + }); + + _attr(p1_2, { + "d" : makePath(d[3][0], d[3][1]), + stroke : "green", + fill : null + }); + }; + + var makePath = function(d1, d2) { + return "M " + d1.x + "," + d1.y + + " L" + d2.x + "," + d2.y; + }; + + }; +})(); \ No newline at end of file diff --git a/archive/1.3.16/jsPlumb-renderers-vml-1.3.16-RC1.js b/archive/1.3.16/jsPlumb-renderers-vml-1.3.16-RC1.js new file mode 100644 index 000000000..963c570cf --- /dev/null +++ b/archive/1.3.16/jsPlumb-renderers-vml-1.3.16-RC1.js @@ -0,0 +1,454 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.16 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the VML renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + // http://ajaxian.com/archives/the-vml-changes-in-ie-8 + // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/ + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + + var vmlAttributeMap = { + "stroke-linejoin":"joinstyle", + "joinstyle":"joinstyle", + "endcap":"endcap", + "miterlimit":"miterlimit" + }, + jsPlumbStylesheet = null; + + if (document.createStyleSheet && document.namespaces) { + + var ruleClasses = [ + ".jsplumb_vml", "jsplumb\\:textbox", "jsplumb\\:oval", "jsplumb\\:rect", + "jsplumb\\:stroke", "jsplumb\\:shape", "jsplumb\\:group" + ], + rule = "behavior:url(#default#VML);position:absolute;"; + + jsPlumbStylesheet = document.createStyleSheet(); + + for (var i = 0; i < ruleClasses.length; i++) + jsPlumbStylesheet.addRule(ruleClasses[i], rule); + + // in this page it is also mentioned that IE requires the extra arg to the namespace + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either. + // var iev = document.documentMode; + //if (!iev || iev < 8) + document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); + //else + // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML"); + } + + jsPlumb.vml = {}; + + var scale = 1000, + + _groupMap = {}, + _getGroup = function(container, connectorClass) { + var id = jsPlumb.getId(container), + g = _groupMap[id]; + if(!g) { + g = _node("group", [0,0,scale, scale], {"class":connectorClass}); + //g.style.position=absolute; + //g["coordsize"] = "1000,1000"; + g.style.backgroundColor="red"; + _groupMap[id] = g; + jsPlumb.appendElement(g, container); // todo if this gets reinstated, remember to use the current jsplumb instance. + //document.body.appendChild(g); + } + return g; + }, + _atts = function(o, atts) { + for (var i in atts) { + // IE8 fix: setattribute does not work after an element has been added to the dom! + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + //o.setAttribute(i, atts[i]); + + /*There is an additional problem when accessing VML elements by using get/setAttribute. The simple solution is following: + + if (document.documentMode==8) { + ele.opacity=1; + } else { + ele.setAttribute(‘opacity’,1); + } + */ + + o[i] = atts[i]; + } + }, + _node = function(name, d, atts, parent, _jsPlumb, deferToJsPlumbContainer) { + atts = atts || {}; + var o = document.createElement("jsplumb:" + name); + if (deferToJsPlumbContainer) + _jsPlumb.appendElement(o, parent); + else + jsPlumb.CurrentLibrary.appendElement(o, parent); + o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml"; + _pos(o, d); + _atts(o, atts); + return o; + }, + _pos = function(o,d, zIndex) { + o.style.left = d[0] + "px"; + o.style.top = d[1] + "px"; + o.style.width= d[2] + "px"; + o.style.height= d[3] + "px"; + o.style.position = "absolute"; + if (zIndex) + o.style.zIndex = zIndex; + }, + _conv = jsPlumb.vml.convertValue = function(v) { + return Math.floor(v * scale); + }, + // tests if the given style is "transparent" and then sets the appropriate opacity node to 0 if so, + // or 1 if not. TODO in the future, support variable opacity. + _maybeSetOpacity = function(styleToWrite, styleToCheck, type, component) { + if ("transparent" === styleToCheck) + component.setOpacity(type, "0.0"); + else + component.setOpacity(type, "1.0"); + }, + _applyStyles = function(node, style, component, _jsPlumb) { + var styleToWrite = {}; + if (style.strokeStyle) { + styleToWrite["stroked"] = "true"; + var strokeColor = jsPlumbUtil.convertStyle(style.strokeStyle, true); + styleToWrite["strokecolor"] = strokeColor; + _maybeSetOpacity(styleToWrite, strokeColor, "stroke", component); + styleToWrite["strokeweight"] = style.lineWidth + "px"; + } + else styleToWrite["stroked"] = "false"; + + if (style.fillStyle) { + styleToWrite["filled"] = "true"; + var fillColor = jsPlumbUtil.convertStyle(style.fillStyle, true); + styleToWrite["fillcolor"] = fillColor; + _maybeSetOpacity(styleToWrite, fillColor, "fill", component); + } + else styleToWrite["filled"] = "false"; + + if(style["dashstyle"]) { + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }, node, _jsPlumb); + } + else + component.strokeNode.dashstyle = style["dashstyle"]; + } + else if (style["stroke-dasharray"] && style["lineWidth"]) { + var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",", + parts = style["stroke-dasharray"].split(sep), + styleToUse = ""; + for(var i = 0; i < parts.length; i++) { + styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep); + } + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }, node, _jsPlumb); + } + else + component.strokeNode.dashstyle = styleToUse; + } + + _atts(node, styleToWrite); + }, + /* + * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. + */ + VmlComponent = function() { + var self = this; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.opacityNodes = { + "stroke":null, + "fill":null + }; + this.initOpacityNodes = function(vml) { + self.opacityNodes["stroke"] = _node("stroke", [0,0,1,1], {opacity:"0.0"}, vml, self._jsPlumb); + self.opacityNodes["fill"] = _node("fill", [0,0,1,1], {opacity:"0.0"}, vml, self._jsPlumb); + }; + this.setOpacity = function(type, value) { + var node = self.opacityNodes[type]; + if (node) node["opacity"] = "" + value; + }; + var displayElements = [ ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el, doNotAppendToCanvas) { + if (!doNotAppendToCanvas) self.canvas.parentNode.appendChild(el); + displayElements.push(el); + }; + }, + /* + * Base class for Vml connectors. extends VmlComponent. + */ + VmlConnector = jsPlumb.VmlConnector = function(params) { + var self = this; + self.strokeNode = null; + self.canvas = null; + VmlComponent.apply(this, arguments); + var clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : ""); + this.paint = function(d, style, anchor) { + if (style != null) { + var path = self.getPath(d), p = { "path":path }; + + //* + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle : jsPlumbUtil.convertStyle(style.outlineColor), + lineWidth : outlineStrokeWidth + }; + for (var aa in vmlAttributeMap) outlineStyle[aa] = style[aa]; + + if (self.bgCanvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.bgCanvas = _node("shape", d, p, params.parent, self._jsPlumb, true); + _pos(self.bgCanvas, d, self.getZIndex()); + self.appendDisplayElement(self.bgCanvas, true); + self.attachListeners(self.bgCanvas, self); + self.initOpacityNodes(self.bgCanvas, ["stroke"]); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.bgCanvas, d, self.getZIndex()); + _atts(self.bgCanvas, p); + } + + _applyStyles(self.bgCanvas, outlineStyle, self); + } + //*/ + + if (self.canvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + if (self.tooltip) p["label"] = self.tooltip; + self.canvas = _node("shape", d, p, params.parent, self._jsPlumb, true); + //var group = _getGroup(params.parent); // test of append everything to a group + //group.appendChild(self.canvas); // sort of works but not exactly; + //params["_jsPlumb"].appendElement(self.canvas, params.parent); //before introduction of groups + + self.appendDisplayElement(self.canvas, true); + self.attachListeners(self.canvas, self); + self.initOpacityNodes(self.canvas, ["stroke"]); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.canvas, d, self.getZIndex()); + _atts(self.canvas, p); + } + + _applyStyles(self.canvas, style, self, self._jsPlumb); + } + }; + + //self.appendDisplayElement(self.canvas); + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + }, + /* + * + * Base class for Vml Endpoints. extends VmlComponent. + * + */ + VmlEndpoint = window.VmlEndpoint = function(params) { + VmlComponent.apply(this, arguments); + var vml = null, self = this, opacityStrokeNode = null, opacityFillNode = null; + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + + var clazz = self._jsPlumb.endpointClass + (params.cssClass ? (" " + params.cssClass) : ""); + + //var group = _getGroup(params.parent); + //group.appendChild(self.canvas); + params["_jsPlumb"].appendElement(self.canvas, params.parent); + + if (self.tooltip) self.canvas.setAttribute("label", self.tooltip); + + this.paint = function(d, style, anchor) { + var p = { }; + + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (vml == null) { + p["class"] = clazz; + vml = self.getVml([0,0, d[2], d[3]], p, anchor, self.canvas, self._jsPlumb); + self.attachListeners(vml, self); + + self.appendDisplayElement(vml, true); + self.appendDisplayElement(self.canvas, true); + + self.initOpacityNodes(vml, ["fill"]); + } + else { + _pos(vml, [0,0, d[2], d[3]]); + _atts(vml, p); + } + + _applyStyles(vml, style, self); + }; + + this.reattachListeners = function() { + if (vml) self.reattachListenersForElement(vml, self); + }; + }; + + jsPlumb.Connectors.vml.Bezier = function() { + jsPlumb.Connectors.Bezier.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Straight = function() { + jsPlumb.Connectors.Straight.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Flowchart = function() { + jsPlumb.Connectors.Flowchart.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l"; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]); + } + // finally draw a line to the end + p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e"; + return p; + }; + }; + + jsPlumb.Endpoints.vml.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor, parent, _jsPlumb) { return _node("oval", d, atts, parent, _jsPlumb); }; + }; + + jsPlumb.Endpoints.vml.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor, parent, _jsPlumb) { return _node("rect", d, atts, parent, _jsPlumb); }; + }; + + /* + * VML Image Endpoint is the same as the default image endpoint. + */ + jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image; + + /** + * placeholder for Blank endpoint in vml renderer. + */ + jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank; + + /** + * VML Label renderer. uses the default label renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label; + + /** + * VML Custom renderer. uses the default Custom renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Custom = jsPlumb.Overlays.Custom; + + var AbstractVmlArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + VmlComponent.apply(this, originalArgs); + var self = this, path = null; + self.canvas = null; + self.isAppendedAtTopLevel = true; + var getPath = function(d, connectorDimensions) { + return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) + + " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + + " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + + " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + + " x e"; + }; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) { + var p = {}; + if (strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] = jsPlumbUtil.convertStyle(strokeStyle, true); + } + if (lineWidth) p["strokeweight"] = lineWidth + "px"; + if (fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = fillStyle; + } + var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + w = Math.abs(xmax - xmin), + h = Math.abs(ymax - ymin), + dim = [xmin, ymin, w, h]; + + // for VML, we create overlays using shapes that have the same dimensions and + // coordsize as their connector - overlays calculate themselves relative to the + // connector (it's how it's been done since the original canvas implementation, because + // for canvas that makes sense). + p["path"] = getPath(d, connectorDimensions); + p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale); + + dim[0] = connectorDimensions[0]; + dim[1] = connectorDimensions[1]; + dim[2] = connectorDimensions[2]; + dim[3] = connectorDimensions[3]; + + if (self.canvas == null) { + //p["class"] = jsPlumb.overlayClass; // TODO currentInstance? + self.canvas = _node("shape", dim, p, connector.canvas.parentNode, connector._jsPlumb, true); + connector.appendDisplayElement(self.canvas, true); + self.attachListeners(self.canvas, connector); + self.attachListeners(self.canvas, self); + } + else { + _pos(self.canvas, dim); + _atts(self.canvas, p); + } + }; + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + + this.cleanup = function() { + if (self.canvas != null) jsPlumb.CurrentLibrary.removeElement(self.canvas); + }; + }; + + jsPlumb.Overlays.vml.Arrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.vml.PlainArrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.vml.Diamond = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.16/jsPlumb-util-1.3.16-RC1.js b/archive/1.3.16/jsPlumb-util-1.3.16-RC1.js new file mode 100644 index 000000000..640281e29 --- /dev/null +++ b/archive/1.3.16/jsPlumb-util-1.3.16-RC1.js @@ -0,0 +1,268 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.16 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the util functions + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ +jsPlumbUtil = { + isArray : function(a) { + return Object.prototype.toString.call(a) === "[object Array]"; + }, + isString : function(s) { + return typeof s === "string"; + }, + isBoolean: function(s) { + return typeof s === "boolean"; + }, + isObject : function(o) { + return Object.prototype.toString.call(o) === "[object Object]"; + }, + isDate : function(o) { + return Object.prototype.toString.call(o) === "[object Date]"; + }, + isFunction: function(o) { + return Object.prototype.toString.call(o) === "[object Function]"; + }, + clone : function(a) { + if (this.isString(a)) return new String(a); + else if (this.isBoolean(a)) return new Boolean(a); + else if (this.isDate(a)) return new Date(a.getTime()); + else if (this.isFunction(a)) return a; + else if (this.isArray(a)) { + var b = []; + for (var i = 0; i < a.length; i++) + b.push(this.clone(a[i])); + return b; + } + else if (this.isObject(a)) { + var b = {}; + for (var i in a) + b[i] = this.clone(a[i]); + return b; + } + else return a; + }, + merge : function(a, b) { + var c = this.clone(a); + for (var i in b) { + if (c[i] == null || this.isString(b[i]) || this.isBoolean(b[i])) + c[i] = b[i]; + else { + if (this.isArray(b[i]) && this.isArray(c[i])) { + var ar = []; + ar.push.apply(ar, c[i]); + ar.push.apply(ar, b[i]); + c[i] = ar; + } + else if(this.isObject(c[i]) && this.isObject(b[i])) { + for (var j in b[i]) + c[i][j] = b[i][j]; + } + } + } + return c; + }, + convertStyle : function(s, ignoreAlpha) { + // TODO: jsPlumb should support a separate 'opacity' style member. + if ("transparent" === s) return s; + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + return o; + }, + gradient : function(p1, p2) { + p1 = jsPlumbUtil.isArray(p1) ? p1 : [p1.x, p1.y]; + p2 = jsPlumbUtil.isArray(p2) ? p2 : [p2.x, p2.y]; + return (p2[1] - p1[1]) / (p2[0] - p1[0]); + }, + normal : function(p1, p2) { + return -1 / jsPlumbUtil.gradient(p1,p2); + }, + lineLength : function(p1, p2) { + p1 = jsPlumbUtil.isArray(p1) ? p1 : [p1.x, p1.y]; + p2 = jsPlumbUtil.isArray(p2) ? p2 : [p2.x, p2.y]; + return Math.sqrt(Math.pow(p2[1] - p1[1], 2) + Math.pow(p2[0] - p1[0], 2)); + }, + segment : function(p1, p2) { + p1 = jsPlumbUtil.isArray(p1) ? p1 : [p1.x, p1.y]; + p2 = jsPlumbUtil.isArray(p2) ? p2 : [p2.x, p2.y]; + if (p2[0] > p1[0]) { + return (p2[1] > p1[1]) ? 2 : 1; + } + else { + return (p2[1] > p1[1]) ? 3 : 4; + } + }, + intersects : function(r1, r2) { + var x1 = r1.x, x2 = r1.x + r1.w, y1 = r1.y, y2 = r1.y + r1.h, + a1 = r2.x, a2 = r2.x + r2.w, b1 = r2.y, b2 = r2.y + r2.h; + + return ( (x1 <= a1 && a1 <= x2) && (y1 <= b1 && b1 <= y2) ) || + ( (x1 <= a2 && a2 <= x2) && (y1 <= b1 && b1 <= y2) ) || + ( (x1 <= a1 && a1 <= x2) && (y1 <= b2 && b2 <= y2) ) || + ( (x1 <= a2 && a1 <= x2) && (y1 <= b2 && b2 <= y2) ) || + + ( (a1 <= x1 && x1 <= a2) && (b1 <= y1 && y1 <= b2) ) || + ( (a1 <= x2 && x2 <= a2) && (b1 <= y1 && y1 <= b2) ) || + ( (a1 <= x1 && x1 <= a2) && (b1 <= y2 && y2 <= b2) ) || + ( (a1 <= x2 && x1 <= a2) && (b1 <= y2 && y2 <= b2) ); + }, + segmentMultipliers : [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ], + inverseSegmentMultipliers : [null, [-1, -1], [-1, 1], [1, 1], [1, -1] ], + pointOnLine : function(fromPoint, toPoint, distance) { + var m = jsPlumbUtil.gradient(fromPoint, toPoint), + s = jsPlumbUtil.segment(fromPoint, toPoint), + segmentMultiplier = distance > 0 ? jsPlumbUtil.segmentMultipliers[s] : jsPlumbUtil.inverseSegmentMultipliers[s], + theta = Math.atan(m), + y = Math.abs(distance * Math.sin(theta)) * segmentMultiplier[1], + x = Math.abs(distance * Math.cos(theta)) * segmentMultiplier[0]; + return { x:fromPoint.x + x, y:fromPoint.y + y }; + }, + /** + * calculates a perpendicular to the line fromPoint->toPoint, that passes through toPoint and is 'length' long. + * @param fromPoint + * @param toPoint + * @param length + */ + perpendicularLineTo : function(fromPoint, toPoint, length) { + var m = jsPlumbUtil.gradient(fromPoint, toPoint), + theta2 = Math.atan(-1 / m), + y = length / 2 * Math.sin(theta2), + x = length / 2 * Math.cos(theta2); + return [{x:toPoint.x + x, y:toPoint.y + y}, {x:toPoint.x - x, y:toPoint.y - y}]; + }, + findWithFunction : function(a, f) { + if (a) + for (var i = 0; i < a.length; i++) if (f(a[i])) return i; + return -1; + }, + indexOf : function(l, v) { + return jsPlumbUtil.findWithFunction(l, function(_v) { return _v == v; }); + }, + removeWithFunction : function(a, f) { + var idx = jsPlumbUtil.findWithFunction(a, f); + if (idx > -1) a.splice(idx, 1); + return idx != -1; + }, + remove : function(l, v) { + var idx = jsPlumbUtil.indexOf(l, v); + if (idx > -1) l.splice(idx, 1); + return idx != -1; + }, + // TODO support insert index + addWithFunction : function(list, item, hashFunction) { + if (jsPlumbUtil.findWithFunction(list, hashFunction) == -1) list.push(item); + }, + addToList : function(map, key, value) { + var l = map[key]; + if (l == null) { + l = [], map[key] = l; + } + l.push(value); + return l; + }, + /** + * EventGenerator + * Superclass for objects that generate events - jsPlumb extends this, as does jsPlumbUIComponent, which all the UI elements extend. + */ + EventGenerator : function() { + var _listeners = {}, self = this; + + // this is a list of events that should re-throw any errors that occur during their dispatch. as of 1.3.0 this is private to + // jsPlumb, but it seems feasible that people might want to manipulate this list. the thinking is that we don't want event + // listeners to bring down jsPlumb - or do we. i can't make up my mind about this, but i know i want to hear about it if the "ready" + // event fails, because then my page has most likely not initialised. so i have this halfway-house solution. it will be interesting + // to hear what other people think. + var eventsToDieOn = [ "ready" ]; + + /* + * Binds a listener to an event. + * + * Parameters: + * event - name of the event to bind to. + * listener - function to execute. + */ + this.bind = function(event, listener) { + jsPlumbUtil.addToList(_listeners, event, listener); + return self; + }; + /* + * Fires an update for the given event. + * + * Parameters: + * event - event to fire + * value - value to pass to the event listener(s). + * originalEvent - the original event from the browser + */ + this.fire = function(event, value, originalEvent) { + if (_listeners[event]) { + for ( var i = 0; i < _listeners[event].length; i++) { + // doing it this way rather than catching and then possibly re-throwing means that an error propagated by this + // method will have the whole call stack available in the debugger. + if (jsPlumbUtil.findWithFunction(eventsToDieOn, function(e) { return e === event}) != -1) + _listeners[event][i](value, originalEvent); + else { + // for events we don't want to die on, catch and log. + try { + _listeners[event][i](value, originalEvent); + } catch (e) { + jsPlumbUtil.log("jsPlumb: fire failed for event " + event + " : " + e); + } + } + } + } + return self; + }; + /* + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + this.unbind = function(event) { + if (event) + delete _listeners[event]; + else { + _listeners = {}; + } + return self; + }; + + this.getListener = function(forEvent) { + return _listeners[forEvent]; + }; + }, + logEnabled : true, + log : function() { + if (jsPlumbUtil.logEnabled && typeof console != "undefined") { + try { + var msg = arguments[arguments.length - 1]; + console.log(msg); + } + catch (e) {} + } + }, + group : function(g) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.group(g); }, + groupEnd : function(g) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.groupEnd(g); }, + time : function(t) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.time(t); }, + timeEnd : function(t) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.timeEnd(t); } +}; \ No newline at end of file diff --git a/archive/1.3.16/mootools.jsPlumb-1.3.16-RC1.js b/archive/1.3.16/mootools.jsPlumb-1.3.16-RC1.js new file mode 100644 index 000000000..0698fadd4 --- /dev/null +++ b/archive/1.3.16/mootools.jsPlumb-1.3.16-RC1.js @@ -0,0 +1,440 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.16 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the MooTools adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function(now) { + this.parent(now); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event, originalEvent) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr, originalEvent); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, _scope, value) { + var l = list[_scope]; + if (!l) { + l = []; + list[_scope] = l; + } + l.push(value); + }; + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + var _getElementObject = function(el) { + return $(el); + }; + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el) + try { + if (el.className.constructor == SVGAnimatedString) { + jsPlumbUtil.svg.addClass(el, clazz); + } + else el.addClass(clazz); + } + catch (e) { + // SVGAnimatedString not supported; no problem. + el.addClass(clazz); + } + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + m.start(properties); + }, + + appendElement : function(child, parent) { + _getElementObject(parent).grab(child); + }, + + bind : function(el, event, callback) { + el = _getElementObject(el); + el.addEvent(event, callback); + }, + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.event.clientX, eventObject.event.clientY]; + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el), + drags = _draggablesById[id]; + return drags[0].scope; + }, + + getDropEvent : function(args) { + return args[2]; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getDOMElement : function(el) { + // MooTools just decorates the DOM elements. so we have either an ID or an Element here. + return typeof(el) == "String" ? document.getElementById(el) : el; + }, + + getElementObject : _getElementObject, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + getOriginalEvent : function(e) { + return e.event; + }, + + getPageXY : function(eventObject) { + return [eventObject.event.pageX, eventObject.event.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).getParent(); + }, + + getScrollLeft : function(el) { + return null; + }, + + getScrollTop : function(el) { + return null; + }, + + getSelector : function(spec) { + return $$(spec); + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e != null ? e.tagName : null; + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs, zoom) { + var ui = eventArgs[0], + el = jsPlumb.CurrentLibrary.getElementObject(ui), + p = el.getPosition(); + + zoom = zoom || 1; + + return { left:p.x / zoom, top:p.y / zoom}; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options, isPlumbedComponent) { + var id = jsPlumb.getId(el); + var drag = _draggablesById[id]; + if (!drag) { + var originalZIndex = 0, + originalCursor = null, + dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + + options['onStart'] = jsPlumb.wrap(options['onStart'], function() { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + drag.originalZIndex = originalZIndex; + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + }); + + // DROPPABLES - only relevant if this is a plumbed component, ie. not just the result of the user making some DOM element + // draggable. this is the only library adapter that has to care about this parameter. + var scope = "" + (options["scope"] || jsPlumb.Defaults.Scope), + filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }, + droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + + if (isPlumbedComponent) { + + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr, event) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop', event); + } + }; + } + else + options["droppables"] = []; + + drag = new Drag.Move(el, options); + drag.scope = scope; + drag.originalZIndex = originalZIndex; + _add(_draggablesById, el.get("id"), drag); + // again, only keep a record of this for scope stuff if this is a plumbed component (an endpoint) + if (isPlumbedComponent) { + _add(_draggablesByScope, scope, drag); + } + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + initDroppable : function(el, options, isPlumbedComponent, isPermanent) { + var scope = options["scope"]; + _add(_droppables, scope, el); + var id = jsPlumb.getId(el); + + el.setAttribute("_isPermanentDroppable", isPermanent); // we use this to clear out droppables on drag complete. + _droppableOptions[id] = options; + _droppableScopesById[id] = scope; + var filterFunc = function(entry) { return entry.element != el; }, + draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + isAlreadyDraggable : function(el) { + return _draggablesById[jsPlumb.getId(el)] != null; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + return (typeof Drag != undefined && typeof Drag.Move != undefined); + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el.className.constructor == SVGAnimatedString) { + jsPlumbUtil.svg.removeClass(el, clazz); + } + else el.removeClass(clazz); + } + catch (e) { + // SVGAnimatedString not supported; no problem. + el.removeClass(clazz); + } + }, + + removeElement : function(element, parent) { + var el = _getElementObject(element); + if (el) el.dispose(); // ?? + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + setDragScope : function(el, scope) { + var drag = _draggablesById[el.get("id")]; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + drag[0].droppables = droppables; + }, + + setOffset : function(el, o) { + _getElementObject(el).setPosition({x:o.left, y:o.top}); + }, + + stopDrag : function() { + for (var i in _draggablesById) { + for (var j = 0; j < _draggablesById[i].length; j++) { + var d = _draggablesById[i][j]; + d.stop(); + if (d.originalZIndex != 0) + d.element.setStyle("z-index", d.originalZIndex); + } + } + }, + + /** + * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself. + * the other libraries do not. yui, in fact, cannot even pass an original event. we have to pull out stuff + * from the originalEvent to put in an options object for YUI. + * @param el + * @param event + * @param originalEvent + */ + trigger : function(el, event, originalEvent) { + originalEvent.stopPropagation(); + _getElementObject(el).fireEvent(event, originalEvent); + }, + + unbind : function(el, event, callback) { + el = _getElementObject(el); + el.removeEvent(event, callback); + } + }; + + window.addEvent('domready', jsPlumb.init); +})(); diff --git a/build/1.3.16/tests/android-svg.html b/archive/1.3.16/tests/android-svg.html similarity index 100% rename from build/1.3.16/tests/android-svg.html rename to archive/1.3.16/tests/android-svg.html diff --git a/build/1.3.16/tests/loadTestHarness.html b/archive/1.3.16/tests/loadTestHarness.html similarity index 100% rename from build/1.3.16/tests/loadTestHarness.html rename to archive/1.3.16/tests/loadTestHarness.html diff --git a/build/1.3.16/tests/qunit-all.html b/archive/1.3.16/tests/qunit-all.html similarity index 100% rename from build/1.3.16/tests/qunit-all.html rename to archive/1.3.16/tests/qunit-all.html diff --git a/build/1.3.16/tests/qunit-canvas-jquery-instance.html b/archive/1.3.16/tests/qunit-canvas-jquery-instance.html similarity index 100% rename from build/1.3.16/tests/qunit-canvas-jquery-instance.html rename to archive/1.3.16/tests/qunit-canvas-jquery-instance.html diff --git a/build/1.3.16/tests/qunit-canvas-jquery.html b/archive/1.3.16/tests/qunit-canvas-jquery.html similarity index 100% rename from build/1.3.16/tests/qunit-canvas-jquery.html rename to archive/1.3.16/tests/qunit-canvas-jquery.html diff --git a/build/1.3.16/tests/qunit-canvas-mootools.html b/archive/1.3.16/tests/qunit-canvas-mootools.html similarity index 100% rename from build/1.3.16/tests/qunit-canvas-mootools.html rename to archive/1.3.16/tests/qunit-canvas-mootools.html diff --git a/build/1.3.16/tests/qunit-svg-jquery-instance.html b/archive/1.3.16/tests/qunit-svg-jquery-instance.html similarity index 100% rename from build/1.3.16/tests/qunit-svg-jquery-instance.html rename to archive/1.3.16/tests/qunit-svg-jquery-instance.html diff --git a/build/1.3.16/tests/qunit-svg-jquery.html b/archive/1.3.16/tests/qunit-svg-jquery.html similarity index 100% rename from build/1.3.16/tests/qunit-svg-jquery.html rename to archive/1.3.16/tests/qunit-svg-jquery.html diff --git a/build/1.3.16/tests/qunit-vml-jquery-instance.html b/archive/1.3.16/tests/qunit-vml-jquery-instance.html similarity index 100% rename from build/1.3.16/tests/qunit-vml-jquery-instance.html rename to archive/1.3.16/tests/qunit-vml-jquery-instance.html diff --git a/build/1.3.16/tests/qunit-vml-jquery.html b/archive/1.3.16/tests/qunit-vml-jquery.html similarity index 100% rename from build/1.3.16/tests/qunit-vml-jquery.html rename to archive/1.3.16/tests/qunit-vml-jquery.html diff --git a/build/1.3.16/tests/qunit.css b/archive/1.3.16/tests/qunit.css similarity index 100% rename from build/1.3.16/tests/qunit.css rename to archive/1.3.16/tests/qunit.css diff --git a/archive/1.3.16/yui.jsPlumb-1.3.16-RC1.js b/archive/1.3.16/yui.jsPlumb-1.3.16-RC1.js new file mode 100644 index 000000000..df46219a5 --- /dev/null +++ b/archive/1.3.16/yui.jsPlumb-1.3.16-RC1.js @@ -0,0 +1,385 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.16 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the YUI3 adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +/** + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getOriginalEvent gets the original browser event from some wrapper event. + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + */ +(function() { + + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function( v, b, s ) { + for( var i = +b || 0, l = this.length; i < l; i++ ) { + if( this[i]===v || s && this[i]==v ) { return i; } + } + return -1; + }; + } + + var Y; + + YUI().use('node', 'dd', 'anim', 'node-event-simulate', function(_Y) { + Y = _Y; + Y.on("domready", function() { jsPlumb.init(); }); + }); + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }, + ddEvents = [ "drag:mouseDown", "drag:afterMouseDown", "drag:mouseup", + "drag:align", "drag:removeHandle", "drag:addHandle", "drag:removeInvalid", "drag:addInvalid", + "drag:start", "drag:end", "drag:drag", "drag:over", "drag:enter", + "drag:exit", "drag:drophit", "drag:dropmiss", "drop:over", "drop:enter", "drop:exit", "drop:hit" + ], + animEvents = [ "tween" ], + /** + * helper function to curry callbacks for some element. + */ + _wrapper = function(fn) { + return function() { + try { + return fn.apply(this, arguments); + } + catch (e) { } + }; + }, + /** + * extracts options from the given options object, leaving out event handlers. + */ + _getDDOptions = function(options) { + var o = {}; + for (var i in options) if (ddEvents.indexOf(i) == -1) o[i] = options[i]; + return o; + }, + /** + * attaches all event handlers found in options to the given dragdrop object, and registering + * the given el as the element of interest. + */ + _attachListeners = function(dd, options, eventList) { + for (var ev in options) { + if (eventList.indexOf(ev) != -1) { + var w = _wrapper(options[ev]); + dd.on(ev, w); + } + } + }, + _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}, + _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }, + _lastDragObject = null, + _extend = function(o1, o2) { + for (var i in o2) + o1[i] = o2[i]; + return o1; + }, + _getAttribute = function(el, attributeId) { + return el.getAttribute(attributeId); + }, + _getElementObject = function(el) { + if (el == null) return null; + var eee = null; + eee = typeof el == 'string' ? Y.one('#' + el) : el._node ? el : Y.one(el); + return eee; + }; + + jsPlumb.CurrentLibrary = { + + addClass : function(el, clazz) { + jsPlumb.CurrentLibrary.getElementObject(el).addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + var o = _extend({node:el, to:properties}, options), + id = _getAttribute(el, "id"); + o["tween"] = jsPlumb.wrap(properties["tween"], function() { + // TODO should use a current instance. + jsPlumb.repaint(id); + }); + var a = new Y.Anim(o); + _attachListeners(a, o, animEvents); + a.run(); + }, + + appendElement : function(child, parent) { + _getElementObject (parent).append(child); + }, + + /** + * event binding wrapper. + */ + bind : function(el, event, callback) { + _getElementObject(el).on(event, callback); + }, + + dragEvents : { + "start":"drag:start", "stop":"drag:end", "drag":"drag:drag", "step":"step", + "over":"drop:enter", "out":"drop:exit", "drop":"drop:hit" + }, + + extend : _extend, + + getAttribute : _getAttribute, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + // this is a workaround for the unfortunate fact that in YUI3, the 'drop:exit' event does + // not contain a reference to the drag that just exited. single-threaded js to the + // rescue: we'll just keep it for ourselves. + if (eventArgs[0].drag) _lastDragObject = eventArgs[0].drag.el; + return _lastDragObject; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el), + dd = _draggablesById[id]; + return dd.scope; + }, + + getDropEvent : function(args) { + return args[0]; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getDOMElement : function(el) { + if (typeof(el) == "String") + return document.getElementById(el); + else if (el._node) + return el._node; + else return el; + }, + + getElementObject : _getElementObject, + + getOffset : function(el) { + var o = Y.DOM.getXY(el._node); + return {left:o[0], top:o[1]}; + }, + + getOriginalEvent : function(e) { + return e._event; + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).get("parentNode"); + }, + + getScrollLeft : function(el) { + return 0; + }, + + getScrollTop : function(el) { + return 0; + }, + + getSelector : function(spec) { + var s = Y.all(spec); + return s && s ._nodes ? s._nodes : []; + }, + + getSize : function(el) { + return [ el._node.offsetWidth, el._node.offsetHeight ]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e != null && e._node != null ? e._node.tagName : null; + }, + + getUIPosition : function(args, zoom) { + zoom = zoom || 1; + var n = args[0].currentTarget.el._node, + o = Y.DOM.getXY(n); + return {left:o[0] / zoom, top:o[1] / zoom}; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options, isPlumbedComponent) { + var _opts = _getDDOptions(options), + id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drag(_opts); + dd.el = el; + + if (isPlumbedComponent) { + var scope = options['scope'] || jsPlumb.Defaults.Scope; + dd.scope = scope; + _add(_draggablesByScope, scope, dd); + } + + _draggablesById[id] = dd; + + _attachListeners(dd, options, ddEvents); + }, + + initDroppable : function(el, options) { + var _opts = _getDDOptions(options), + id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drop(_opts); + + _droppableOptions[id] = options; + + options = _extend({}, options); + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _droppableScopesById[id] = scope; + + options["drop:enter"] = jsPlumb.wrap(options["drop:enter"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, true); + }, true); + options["drop:exit"] = jsPlumb.wrap(options["drop:exit"], function(e) { + _checkHover(el, false); + }); + options["drop:hit"] = jsPlumb.wrap(options["drop:hit"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, false); + }, true); + + _attachListeners(dd, options, ddEvents); + }, + + isAlreadyDraggable : function(el) { + el = _getElementObject(el); + return el.hasClass("yui3-dd-draggable"); + }, + + isDragSupported : function(el) { return true; }, + isDropSupported : function(el) { return true; }, + removeClass : function(el, clazz) { + jsPlumb.CurrentLibrary.getElementObject(el).removeClass(clazz); + }, + removeElement : function(el) { _getElementObject(el).remove(); }, + + setAttribute : function(el, attributeName, attributeValue) { + el.setAttribute(attributeName, attributeValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + var id = jsPlumb.getId(el), + dd = _draggablesById[id]; + if (dd) dd.set("lock", !draggable); + }, + + setDragScope : function(el, scope) { + var id = jsPlumb.getId(el), + dd = _draggablesById[id]; + if (dd) dd.scope = scope; + }, + + setOffset : function(el, o) { + el = _getElementObject(el); + el.set("top", o.top); + el.set("left", o.left); + }, + + stopDrag : function() { + Y.DD.DDM.stopDrag(); + }, + + trigger : function(el, event, originalEvent) { + originalEvent.stopPropagation(); + _getElementObject(el).simulate(event, { + pageX:originalEvent.pageX, + pageY:originalEvent.pageY, + clientX:originalEvent.clientX, + clientY:originalEvent.clientY + }); + }, + + /** + * event unbinding wrapper. + */ + unbind : function(el, event, callback) { + _getElementObject(el).detach(event, callback); + } + }; +})(); \ No newline at end of file diff --git a/archive/1.3.2/jquery.jsPlumb-1.3.2-RC1.js b/archive/1.3.2/jquery.jsPlumb-1.3.2-RC1.js new file mode 100644 index 000000000..23153f809 --- /dev/null +++ b/archive/1.3.2/jquery.jsPlumb-1.3.2-RC1.js @@ -0,0 +1,282 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the jQuery adapter. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ +/* + * the library specific functions, such as find offset, get id, get attribute, extend etc. + * the full list is: + * + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getDropScope gets the drop scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getPageXY gets the page event's xy location. + * getParent gets the parent of some element. + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * hasClass returns whether or not the given element has the given class. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + */ +(function($) { + + //var getBoundingClientRectSupported = "getBoundingClientRect" in document.documentElement; + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * appends the given child to the given parent. + */ + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).append(child); + }, + + /** + * event binding wrapper. it just so happens that jQuery uses 'bind' also. yui3, for example, + * uses 'on'. + */ + bind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.bind(event, callback); + }, + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step', + 'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete' + }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + getDocumentElement : function() { return document; }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + }, + + getDragScope : function(el) { + return el.draggable("option", "scope"); + }, + + getDropScope : function(el) { + return el.droppable("option", "scope"); + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + * + */ + getElementObject : function(el) { + return typeof(el)=='string' ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).parent(); + }, + + getScrollLeft : function(el) { + return el.scrollLeft(); + }, + + getScrollTop : function(el) { + return el.scrollTop(); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + + // this code is a workaround for the case that the element being dragged has a margin set on it. jquery UI passes + // in the wrong offset if the element has a margin (it doesn't take the margin into account). the getBoundingClientRect + // method, which is in pretty much all browsers now, reports the right numbers. but it introduces a noticeable lag, which + // i don't like. + + /*if ( getBoundingClientRectSupported ) { + var r = eventArgs[1].helper[0].getBoundingClientRect(); + return { left : r.left, top: r.top }; + } else {*/ + var ui = eventArgs[1], _offset = ui.offset; + return _offset || ui.absolutePosition; + //} + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options) { + // remove helper directive if present. + options.helper = null; + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.draggable(options); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.droppable(options); + }, + + isAlreadyDraggable : function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return el.hasClass("ui-draggable"); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).remove(); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * sets the drag scope. probably time for a setDragOption method (roll this and the one above together) + * @param el + * @param scope + */ + setDragScope : function(el, scope) { + el.draggable("option", "scope", scope); + }, + + setOffset : function(el, o) { + jsPlumb.CurrentLibrary.getElementObject(el).offset(o); + } + }; + + $(document).ready(jsPlumb.init); + +})(jQuery); diff --git a/archive/1.3.2/jquery.jsPlumb-1.3.2-all-min.js b/archive/1.3.2/jquery.jsPlumb-1.3.2-all-min.js new file mode 100644 index 000000000..1b6346fbf --- /dev/null +++ b/archive/1.3.2/jquery.jsPlumb-1.3.2-all-min.js @@ -0,0 +1 @@ +(function(){var r=!!document.createElement("canvas").getContext;var d=!!window.SVGAngle||document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1");var a=!(r|d);var l=function(y,z,w,C){var B=function(E,D){if(E===D){return true}else{if(typeof E=="object"&&typeof D=="object"){var F=true;for(var v in E){if(!B(E[v],D[v])){F=false;break}}for(var v in D){if(!B(D[v],E[v])){F=false;break}}return F}}};for(var A=+w||0,x=y.length;A=0){delete (ax[ay]);ax.splice(ay,1);return true}}}return false},v=function(ay,ax){return I(ay,function(az,aA){ag[aA]=ax;if(i.CurrentLibrary.isDragSupported(az)){i.CurrentLibrary.setDraggable(az,ax)}})},ao=function(az,aA,ax){aA=aA==="block";var ay=null;if(ax){if(aA){ay=function(aC){aC.setVisible(true,true,true)}}else{ay=function(aC){aC.setVisible(false,true,true)}}}var aB=c(az,"id");U(aB,function(aD){if(aA&&ax){var aC=aD.sourceId===aB?1:0;if(aD.endpoints[aC].isVisible()){aD.setVisible(true)}}else{aD.setVisible(aA)}},ay)},N=function(ax){return I(ax,function(az,ay){var aA=ag[ay]==null?Y:ag[ay];aA=!aA;ag[ay]=aA;i.CurrentLibrary.setDraggable(az,aA);return aA})},x=function(ax,az){var ay=null;if(az){ay=function(aA){var aB=aA.isVisible();aA.setVisible(!aB)}}U(ax,function(aB){var aA=aB.isVisible();aB.setVisible(!aA)},ay)},E=function(aC){var aA=aC.timestamp,ax=aC.recalc,aB=aC.offset,ay=aC.elId;if(!ax){if(aA&&aA===C[ay]){return V[ay]}}if(ax||aB==null){var az=u(ay);if(az!=null){P[ay]=b(az);V[ay]=n(az);C[ay]=aA}}else{V[ay]=aB}return V[ay]},aw=function(ax,ay){var az=u(ax);var aA=c(az,"id");if(!aA||aA=="undefined"){if(arguments.length==2&&arguments[1]!=undefined){aA=ay}else{aA="jsPlumb_"+ar()}e(az,"id",aA)}return aA},am=function(az,ax,ay){az=az||function(){};ax=ax||function(){};return function(){var aA=null;try{aA=ax.apply(this,arguments)}catch(aB){k(D,"jsPlumb function failed : "+aB)}if(ay==null||(aA!==ay)){try{az.apply(this,arguments)}catch(aB){k(D,"wrapped function failed : "+aB)}}return aA}};this.connectorClass="_jsPlumb_connector";this.endpointClass="_jsPlumb_endpoint";this.overlayClass="_jsPlumb_overlay";this.Anchors={};this.Connectors={canvas:{},svg:{},vml:{}};this.Endpoints={canvas:{},svg:{},vml:{}};this.Overlays={canvas:{},svg:{},vml:{}};this.addEndpoint=function(az,aA,aJ){aJ=aJ||{};var ay=i.extend({},aJ);i.extend(ay,aA);ay.endpoint=ay.endpoint||D.Defaults.Endpoint||i.Defaults.Endpoint;ay.paintStyle=ay.paintStyle||D.Defaults.EndpointStyle||i.Defaults.EndpointStyle;az=w(az);var aB=[],aE=az.length&&az.constructor!=String?az:[az];for(var aC=0;aC0?l(aK,aJ)!=-1:true},aB=aE.length>1?{}:[],aH=function(aK,aL){if(aE.length>1){var aJ=aB[aK];if(aJ==null){aJ=[];aB[aK]=aJ}aJ.push(aL)}else{aB.push(aL)}};for(var aA in L){if(ay(aE,aA)){for(var az=0;az=4){ay.orientation=[arguments[2],arguments[3]]}if(arguments.length==6){ay.offsets=[arguments[4],arguments[5]]}}var aD=new ac(ay);aD.clone=function(){return new ac(ay)};return aD};this.makeAnchors=function(ay){var az=[];for(var ax=0;ax0?aG[0]:null,aC=aG.length>0?0:-1,aF=this,aB=function(aJ,aH,aN,aM,aI){var aL=aM[0]+(aJ.x*aI[0]),aK=aM[1]+(aJ.y*aI[1]);return Math.sqrt(Math.pow(aH-aL,2)+Math.pow(aN-aK,2))},ax=ay||function(aR,aI,aJ,aK,aH){var aM=aJ[0]+(aK[0]/2),aL=aJ[1]+(aK[1]/2);var aO=-1,aQ=Infinity;for(var aN=0;aNaX){aX=a4}}var a8=this.connector.compute(aZ,ba,this.endpoints[be].anchor,this.endpoints[aW].anchor,aI.paintStyleInUse.lineWidth,aX);aI.connector.paint(a8,aI.paintStyleInUse);for(var a9=0;a9=0){aN.connections.splice(aZ,1);if(!a2){var a1=a0.endpoints[0]==aN?a0.endpoints[1]:a0.endpoints[0];a1.detach(a0,true);if(a0.endpointToDeleteOnDetach&&a0.endpointToDeleteOnDetach.connections.length==0){i.deleteEndpoint(a0.endpointToDeleteOnDetach)}}Q(a0.connector.getDisplayElements(),a0.parent);M(L,a0.scope,a0);if(!a2){ad(a0)}}};this.detachAll=function(){while(aN.connections.length>0){aN.detach(aN.connections[0])}};this.detachFrom=function(a0){var a1=[];for(var aZ=0;aZ=0){aN.connections.splice(aZ,1)}};this.getElement=function(){return aM};this.getUuid=function(){return aH};this.makeInPlaceCopy=function(){return an({anchor:aN.anchor,source:aM,paintStyle:this.paintStyle,endpoint:aL})};this.isConnectedTo=function(a1){var a0=false;if(a1){for(var aZ=0;aZ0){var a9=aK(a2.elementWithPrecedence);var bb=a9.endpoints[0]==aN?1:0;var a4=bb==0?a9.sourceId:a9.targetId;var a8=V[a4],ba=P[a4];a0.txy=[a8.left,a8.top];a0.twh=ba;a0.tElement=a9.endpoints[bb]}}a5=aN.anchor.compute(a0)}var a7=aL.compute(a5,aN.anchor.getOrientation(),aN.paintStyleInUse,a3||aN.paintStyleInUse);aL.paint(a7,aN.paintStyleInUse,aN.anchor);aN.timestamp=a6}};this.repaint=this.paint;this.removeConnection=this.detach;if(aY.isSource&&i.CurrentLibrary.isDragSupported(aM)){var aS=null,aO=null,aR=null,ax=false,aA=null;var aC=function(){aR=aN.connectorSelector();if(aN.isFull()&&!aI){return false}E({elId:aE});aB=aN.makeInPlaceCopy();aB.paint();aS=document.createElement("div");aS.style.position="absolute";var a6=u(aS);R(aS,aN.parent);var a0=aw(a6);var a7=u(aB.canvas),a5=i.CurrentLibrary.getOffset(a7),a2=aB.canvas.offsetParent!=null?aB.canvas.offsetParent.tagName.toLowerCase()==="body"?{left:0,top:0}:n(aB.canvas.offsetParent):{left:0,top:0};i.CurrentLibrary.setOffset(aS,{left:a5.left-a2.left,top:a5.top-a2.top});E({elId:a0});e(u(aN.canvas),"dragId",a0);e(u(aN.canvas),"elId",aE);var a8=new H({reference:aN.anchor,referenceCanvas:aN.canvas});aW=an({paintStyle:aN.paintStyle,endpoint:aL,anchor:a8,source:a6});if(aR==null){aN.anchor.locked=true;aR=au({sourceEndpoint:aN,targetEndpoint:aW,source:u(aM),target:u(aS),anchors:[aN.anchor,a8],paintStyle:aY.connectorStyle,hoverPaintStyle:aY.connectorHoverStyle,connector:aY.connector,overlays:aY.connectorOverlays});aR.connector.setHover(false)}else{ax=true;aR.connector.setHover(false);aD(u(aB.canvas));var a1=aR.sourceId==aE?0:1;aR.floatingAnchorIndex=a1;aN.detachFromConnection(aR);var a4=u(aN.canvas);var a3=i.CurrentLibrary.getDragScope(a4);e(a4,"originalScope",a3);var aZ=i.CurrentLibrary.getDropScope(a4);i.CurrentLibrary.setDragScope(a4,aZ);if(a1==0){aA=[aR.source,aR.sourceId,aV,a3];aR.source=u(aS);aR.sourceId=a0}else{aA=[aR.target,aR.targetId,aV,a3];aR.target=u(aS);aR.targetId=a0}aR.endpoints[a1==0?1:0].anchor.locked=true;aR.suspendedEndpoint=aR.endpoints[a1];aR.endpoints[a1]=aW}Z[a0]=aR;aW.addConnection(aR);S(ah,a0,aW);D.currentlyDragging=true};var ay=i.CurrentLibrary,aU=aY.dragOptions||{},aP=i.extend({},ay.defaultDragOptions),aQ=ay.dragEvents.start,aX=ay.dragEvents.stop,aG=ay.dragEvents.drag;aU=i.extend(aP,aU);aU.scope=aU.scope||aN.scope;aU[aQ]=am(aU[aQ],aC);aU[aG]=am(aU[aG],function(){var aZ=i.CurrentLibrary.getUIPosition(arguments);i.CurrentLibrary.setOffset(aS,aZ);at(u(aS),aZ)});aU[aX]=am(aU[aX],function(){M(ah,aO,aW);Q([aS,aW.canvas],aM);ae(aB.canvas,aM);var aZ=aR.floatingAnchorIndex==null?1:aR.floatingAnchorIndex;aR.endpoints[aZ==0?1:0].anchor.locked=false;if(aR.endpoints[aZ]==aW){if(ax&&aR.suspendedEndpoint){if(aZ==0){aR.source=aA[0];aR.sourceId=aA[1]}else{aR.target=aA[0];aR.targetId=aA[1]}i.CurrentLibrary.setDragScope(aA[2],aA[3]);aR.endpoints[aZ]=aR.suspendedEndpoint;if(aJ){aR.floatingAnchorIndex=null;aR.suspendedEndpoint.addConnection(aR);i.repaint(aA[1])}else{aR.endpoints[aZ==0?1:0].detach(aR)}}else{Q(aR.connector.getDisplayElements(),aN.parent);aN.detachFromConnection(aR)}}aN.anchor.locked=false;aN.paint();aR.setHover(false);aR.repaint();aR=null;delete aB;delete ah[aW.elementId];aW=null;delete aW;D.currentlyDragging=false});var aV=u(aN.canvas);i.CurrentLibrary.initDraggable(aV,aU)}var aD=function(a2){if(aY.isTarget&&i.CurrentLibrary.isDropSupported(aM)){var aZ=aY.dropOptions||D.Defaults.DropOptions||i.Defaults.DropOptions;aZ=i.extend({},aZ);aZ.scope=aZ.scope||aN.scope;var a5=null;var a3=i.CurrentLibrary.dragEvents.drop;var a4=i.CurrentLibrary.dragEvents.over;var a0=i.CurrentLibrary.dragEvents.out;var a1=function(){var be=u(i.CurrentLibrary.getDragObject(arguments));var a6=c(be,"dragId");var a8=c(be,"elId");var bd=c(be,"originalScope");if(bd){i.CurrentLibrary.setDragScope(be,bd)}var ba=Z[a6];var bb=ba.floatingAnchorIndex==null?1:ba.floatingAnchorIndex,bc=bb==0?1:0;if(!aN.isFull()&&!(bb==0&&!aN.isSource)&&!(bb==1&&!aN.isTarget)){if(bb==0){ba.source=aM;ba.sourceId=aE}else{ba.target=aM;ba.targetId=aE}ba.endpoints[bb].detachFromConnection(ba);if(ba.suspendedEndpoint){ba.suspendedEndpoint.detachFromConnection(ba)}ba.endpoints[bb]=aN;aN.addConnection(ba);if(!ba.suspendedEndpoint){S(L,ba.scope,ba);O(aM,aY.draggable,{})}else{var a9=ba.suspendedEndpoint.getElement(),a7=ba.suspendedEndpoint.elementId;D.fire("jsPlumbConnectionDetached",{source:bb==0?a9:ba.source,target:bb==1?a9:ba.target,sourceId:bb==0?a7:ba.sourceId,targetId:bb==1?a7:ba.targetId,sourceEndpoint:bb==0?ba.suspendedEndpoint:ba.endpoints[0],targetEndpoint:bb==1?ba.suspendedEndpoint:ba.endpoints[1],connection:ba})}i.repaint(a8);D.fire("jsPlumbConnection",{source:ba.source,target:ba.target,sourceId:ba.sourceId,targetId:ba.targetId,sourceEndpoint:ba.endpoints[0],targetEndpoint:ba.endpoints[1],connection:ba})}D.currentlyDragging=false;delete Z[a6]};aZ[a3]=am(aZ[a3],a1);aZ[a4]=am(aZ[a4],function(){var a7=i.CurrentLibrary.getDragObject(arguments);var a9=c(u(a7),"dragId");var a8=Z[a9];if(a8!=null){var a6=a8.floatingAnchorIndex==null?1:a8.floatingAnchorIndex;a8.endpoints[a6].anchor.over(aN.anchor)}});aZ[a0]=am(aZ[a0],function(){var a7=i.CurrentLibrary.getDragObject(arguments),a9=c(u(a7),"dragId"),a8=Z[a9];if(a8!=null){var a6=a8.floatingAnchorIndex==null?1:a8.floatingAnchorIndex;a8.endpoints[a6].anchor.out()}});i.CurrentLibrary.initDroppable(a2,aZ)}};aD(u(aN.canvas));return aN}};var i=window.jsPlumb=new q();i.getInstance=function(w){var v=new q(w);v.init();return v};var m=function(v,A,z,w){return function(){return i.makeAnchor(v,A,z,w)}};i.Anchors.TopCenter=m(0.5,0,0,-1);i.Anchors.BottomCenter=m(0.5,1,0,1);i.Anchors.LeftMiddle=m(0,0.5,-1,0);i.Anchors.RightMiddle=m(1,0.5,1,0);i.Anchors.Center=m(0.5,0.5,0,0);i.Anchors.TopRight=m(1,0,0,-1);i.Anchors.BottomRight=m(1,1,0,1);i.Anchors.TopLeft=m(0,0,0,-1);i.Anchors.BottomLeft=m(0,1,0,1);i.Defaults.DynamicAnchors=function(){return i.makeAnchors(["TopCenter","RightMiddle","BottomCenter","LeftMiddle"])};i.Anchors.AutoDefault=function(){return i.makeDynamicAnchor(i.Defaults.DynamicAnchors())}})();(function(){jsPlumb.DOMElementComponent=function(a){jsPlumb.jsPlumbUIComponent.apply(this,arguments);this.mousemove=this.dblclick=this.click=this.mousedown=this.mouseup=function(b){}};jsPlumb.Connectors.Straight=function(){this.type="Straight";var n=this;var g=null;var c,h,l,k,i,d,m,f,e,b,a;this.compute=function(r,F,B,o,v,q){var E=Math.abs(r[0]-F[0]);var u=Math.abs(r[1]-F[1]);var z=false,s=false;var t=0.45*E,p=0.45*u;E*=1.9;u*=1.9;var C=Math.min(r[0],F[0])-t;var A=Math.min(r[1],F[1])-p;var D=Math.max(2*v,q);if(E0?1:-1;var t=Math.abs(u*Math.sin(d));if(e>a){t=t*-1}var o=Math.abs(u*Math.cos(d));if(f>b){o=o*-1}return{x:s.x+(r*o),y:s.y+(r*t)}};this.perpendicularToPathAt=function(s,t,w){var u=n.pointAlongPathFrom(s,w);var r=n.gradientAtPoint(u.location);var q=Math.atan(-1/r);var v=t/2*Math.sin(q);var o=t/2*Math.cos(q);return[{x:u.x+o,y:u.y+v},{x:u.x-o,y:u.y-v}]}};jsPlumb.Connectors.Bezier=function(e){var o=this;e=e||{};this.majorAnchor=e.curviness||150;this.minorAnchor=10;var h=null;this.type="Bezier";this._findControlPoint=function(z,q,u,x,r){var w=x.getOrientation(),y=r.getOrientation();var t=w[0]!=y[0]||w[1]==y[1];var s=[];var A=o.majorAnchor,v=o.minorAnchor;if(!t){if(w[0]==0){s.push(q[0]l){l=u}if(x<0){d+=x;var y=Math.abs(x);l+=y;n[0]+=y;i+=y;b+=y;m[0]+=y}var G=Math.min(f,a);var E=Math.min(n[1],m[1]);var t=Math.min(G,E);var z=Math.max(f,a);var w=Math.max(n[1],m[1]);var r=Math.max(z,w);if(r>g){g=r}if(t<0){c+=t;var v=Math.abs(t);g+=v;n[1]+=v;f+=v;a+=v;m[1]+=v}if(D&&l=s){q=t;r=(s-l[t][0])/a[t];break}}return{segment:i[q],proportion:r,index:q}};this.compute=function(Q,v,P,r,q,H){i=[];h=[];a=[];g=[];segmentProportionals=[];d=v[0]t[0]?t[0]+((1-x)*r)-y:t[2]+(x*r)+y,y:q==0?t[3]:t[3]>t[1]?t[1]+((1-x)*r)-y:t[3]+(x*r)+y,segmentInfo:v};return w};this.perpendicularToPathAt=function(t,u,z){var v=n.pointAlongPathFrom(t,z);var s=h[v.segmentInfo.index];var r=Math.atan(-1/s);var w=u/2*Math.sin(r);var q=u/2*Math.cos(r);return[{x:v.x+q,y:v.y+w},{x:v.x-q,y:v.y-w}]}};jsPlumb.Endpoints.Dot=function(b){this.type="Dot";var a=this;b=b||{};this.radius=b.radius||10;this.defaultOffset=0.5*this.radius;this.defaultInnerRadius=this.radius/3;this.compute=function(g,d,i,f){var e=i.radius||a.radius;var c=g[0]-e;var h=g[1]-e;return[c,h,e*2,e*2,e]}};jsPlumb.Endpoints.Rectangle=function(b){this.type="Rectangle";var a=this;b=b||{};this.width=b.width||20;this.height=b.height||20;this.compute=function(h,e,k,g){var f=k.width||a.width;var d=k.height||a.height;var c=h[0]-(f/2);var i=h[1]-(d/2);return[c,i,f,d]}};jsPlumb.Endpoints.Image=function(d){this.type="Image";jsPlumb.DOMElementComponent.apply(this,arguments);var a=this,c=false;this.img=new Image();a.ready=false;this.img.onload=function(){a.ready=true};this.img.src=d.src||d.url;this.compute=function(g,e,h,f){a.anchorPoint=g;if(a.ready){return[g[0]-a.img.width/2,g[1]-a.img.height/2,a.img.width,a.img.height]}else{return[0,0,0,0]}};a.canvas=document.createElement("img"),c=false;a.canvas.style.margin=0;a.canvas.style.padding=0;a.canvas.style.outline=0;a.canvas.style.position="absolute";a.canvas.className=jsPlumb.endpointClass;jsPlumb.appendElement(a.canvas,d.parent);a.attachListeners(a.canvas,a);var b=function(k,i,g){if(!c){a.canvas.setAttribute("src",a.img.src);c=true}var h=a.img.width,f=a.img.height,e=a.anchorPoint[0]-(h/2),l=a.anchorPoint[1]-(f/2);jsPlumb.sizeCanvas(a.canvas,e,l,h,f)};this.paint=function(g,f,e){if(a.ready){b(g,f,e)}else{window.setTimeout(function(){a.paint(g,f,e)},200)}}};jsPlumb.Endpoints.Blank=function(b){var a=this;this.type="Blank";jsPlumb.DOMElementComponent.apply(this,arguments);this.compute=function(){return[0,0,10,0]};a.canvas=document.createElement("div");a.canvas.style.display="block";a.canvas.style.width="1px";a.canvas.style.height="1px";a.canvas.style.background="transparent";a.canvas.style.position="absolute";jsPlumb.appendElement(a.canvas,b.parent);this.paint=function(){}};jsPlumb.Endpoints.Triangle=function(a){this.type="Triangle";a=a||{};a.width=a.width||55;param.height=a.height||55;this.width=a.width;this.height=a.height;this.compute=function(g,d,i,f){var e=i.width||self.width;var c=i.height||self.height;var b=g[0]-(e/2);var h=g[1]-(c/2);return[b,h,e,c]}};jsPlumb.Overlays.Arrow=function(f){this.type="Arrow";f=f||{};var b=this;this.length=f.length||20;this.width=f.width||20;this.id=f.id;this.connection=f.connection;var e=(f.direction||1)<0?-1:1;var c=f.paintStyle||{lineWidth:1};this.loc=f.location==null?0.5:f.location;var a=f.foldback||0.623;var d=function(g,i){if(a==0.5){return g.pointOnPath(i)}else{var h=0.5-a;return g.pointAlongPathFrom(i,e*b.length*h)}};this.computeMaxSize=function(){return b.width*1.5};this.draw=function(k,q,w){var y=k.pointAlongPathFrom(b.loc,e*(b.length/2));var t=k.pointAlongPathFrom(b.loc,-1*e*(b.length/2)),B=t.x,A=t.y;var r=k.perpendicularToPathAt(b.loc,b.width,-1*e*(b.length/2));var i=d(k,b.loc);if(b.loc==1){var h=k.pointOnPath(b.loc);var v=(h.x-y.x)*e,u=(h.y-y.y)*e;i.x+=v;i.y+=u;t.x+=v;t.y+=u;r[0].x+=v;r[0].y+=u;r[1].x+=v;r[1].y+=u;y.x+=v;y.y+=u}if(b.loc==0){var h=k.pointOnPath(b.loc);var s=a>1?i:{x:r[0].x+((r[1].x-r[0].x)/2),y:r[0].y+((r[1].y-r[0].y)/2)};var v=(h.x-s.x)*e,u=(h.y-s.y)*e;i.x+=v;i.y+=u;t.x+=v;t.y+=u;r[0].x+=v;r[0].y+=u;r[1].x+=v;r[1].y+=u;y.x+=v;y.y+=u}var o=Math.min(y.x,r[0].x,r[1].x);var n=Math.max(y.x,r[0].x,r[1].x);var m=Math.min(y.y,r[0].y,r[1].y);var l=Math.max(y.y,r[0].y,r[1].y);var z={hxy:y,tail:r,cxy:i},x=c.strokeStyle||q.strokeStyle,p=c.fillStyle||q.strokeStyle,g=c.lineWidth||q.lineWidth;b.paint(k,z,g,x,p,w);return[o,n,m,l]}};jsPlumb.Overlays.PlainArrow=function(b){b=b||{};var a=jsPlumb.extend(b,{foldback:1});jsPlumb.Overlays.Arrow.call(this,a);this.type="PlainArrow"};jsPlumb.Overlays.Diamond=function(c){c=c||{};var a=c.length||40;var b=jsPlumb.extend(c,{length:a/2,foldback:2});jsPlumb.Overlays.Arrow.call(this,b);this.type="Diamond"};jsPlumb.Overlays.Label=function(d){this.type="Label";jsPlumb.DOMElementComponent.apply(this,arguments);this.labelStyle=d.labelStyle||jsPlumb.Defaults.LabelStyle;this.labelStyle.font=this.labelStyle.font||"12px sans-serif";this.label=d.label||"banana";this.connection=d.connection;this.id=d.id;var k=this;var h=null,e=null,c=null,b=null;this.location=d.location||0.5;this.cachedDimensions=null;var i=false,c=null,a=document.createElement("div");a.style.position="absolute";a.style.font=k.labelStyle.font;a.style.color=k.labelStyle.color||"black";if(k.labelStyle.fillStyle){a.style.background=k.labelStyle.fillStyle}if(k.labelStyle.borderWidth>0){var g=k.labelStyle.borderStyle?k.labelStyle.borderStyle:"black";a.style.border=k.labelStyle.borderWidth+"px solid "+g}if(k.labelStyle.padding){a.style.padding=k.labelStyle.padding}var f=d._jsPlumb.overlayClass+" "+(k.labelStyle.cssClass?k.labelStyle.cssClass:d.cssClass?d.cssClass:"");a.className=f;jsPlumb.appendElement(a,d.connection.parent);jsPlumb.getId(a);k.attachListeners(a,k);this.paint=function(l,n,m){if(!i){l.appendDisplayElement(a);k.attachListeners(a,l);i=true}a.style.left=(m[0]+n.minx)+"px";a.style.top=(m[1]+n.miny)+"px"};this.getTextDimensions=function(l){c=typeof k.label=="function"?k.label(k):k.label;a.innerHTML=c.replace(/\r\n/g,"
");var n=jsPlumb.CurrentLibrary.getElementObject(a),m=jsPlumb.CurrentLibrary.getSize(n);return{width:m[0],height:m[1]}};this.computeMaxSize=function(l){var m=k.getTextDimensions(l);return m.width?Math.max(m.width,m.height)*1.5:0};this.draw=function(n,p,o){var r=k.getTextDimensions(n);if(r.width!=null){var q=n.pointOnPath(k.location);var m=q.x-(r.width/2);var l=q.y-(r.height/2);k.paint(n,{minx:m,miny:l,td:r,cxy:q},o);return[m,m+r.width,l,l+r.height]}else{return[0,0,0,0]}}}})();(function(){var h={"stroke-linejoin":"joinstyle",joinstyle:"joinstyle",endcap:"endcap",miterlimit:"miterlimit"};if(document.createStyleSheet){document.createStyleSheet().addRule(".jsplumb_vml","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:textbox","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:oval","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:rect","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:stroke","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:shape","behavior:url(#default#VML);position:absolute;");document.namespaces.add("jsplumb","urn:schemas-microsoft-com:vml")}var c=1000,d=function(p,q){for(var n in q){p[n]=q[n]}},m=function(n,q,r){r=r||{};var p=document.createElement("jsplumb:"+n);p.className=(r["class"]?r["class"]+" ":"")+"jsplumb_vml";l(p,q);d(p,r);return p},l=function(p,n){p.style.left=n[0]+"px";p.style.top=n[1]+"px";p.style.width=n[2]+"px";p.style.height=n[3]+"px";p.style.position="absolute"},g=function(n){return Math.floor(n*c)},a=function(p,n){var v=p,u=function(o){return o.length==1?"0"+o:o},q=function(o){return u(Number(o).toString(16))},r=/(rgb[a]?\()(.*)(\))/;if(p.match(r)){var t=p.match(r)[2].split(",");v="#"+q(t[0])+q(t[1])+q(t[2]);if(!n&&t.length==4){v=v+q(t[3])}}return v},f=function(s,r,p){var u={};if(r.strokeStyle){u.stroked="true";u.strokecolor=a(r.strokeStyle,true);u.strokeweight=r.lineWidth+"px"}else{u.stroked="false"}if(r.fillStyle){u.filled="true";u.fillcolor=a(r.fillStyle,true)}else{u.filled="false"}if(r.dashstyle){if(p.strokeNode==null){p.strokeNode=m("stroke",[0,0,0,0],{dashstyle:r.dashstyle});s.appendChild(p.strokeNode)}else{p.strokeNode.dashstyle=r.dashstyle}}else{if(r["stroke-dasharray"]&&r.lineWidth){var o=r["stroke-dasharray"].indexOf(",")==-1?" ":",",t=r["stroke-dasharray"].split(o),n="";for(var q=0;q0&&F>0&&z=z&&B[2]<=F&&B[3]>=F)){return true}}var D=v.canvas.getContext("2d").getImageData(parseInt(z),parseInt(F),1,1);return D.data[0]!=0||D.data[1]!=0||D.data[2]!=0||D.data[3]!=0}return false};var u=false;var t=false,y=null,x=false;var w=function(A,z){return A!=null&&f(A,z)};this.mousemove=function(C){var E=e(C),B=i(C),A=document.elementFromPoint(B[0],B[1]),D=w(A,"_jsPlumb_overlay");var z=o==null&&(w(A,"_jsPlumb_endpoint")||w(A,"_jsPlumb_connector"));if(!u&&z&&v._over(C)){u=true;v.fire("mouseenter",v,C);return true}else{if(u&&(!v._over(C)||!z)&&!D){u=false;v.fire("mouseexit",v,C)}}v.fire("mousemove",v,C)};this.click=function(z){if(u&&v._over(z)&&!x){v.fire("click",v,z)}x=false};this.dblclick=function(z){if(u&&v._over(z)&&!x){v.fire("dblclick",v,z)}x=false};this.mousedown=function(z){if(v._over(z)&&!t){t=true;y=m(s(v.canvas));v.fire("mousedown",v,z)}};this.mouseup=function(z){t=false;v.fire("mouseup",v,z)}};var p=function(u){var t=document.createElement("canvas");jsPlumb.appendElement(t,u.parent);t.style.position="absolute";if(u["class"]){t.className=u["class"]}u._jsPlumb.getId(t,u.uuid);return t};var d=jsPlumb.CanvasConnector=function(x){n.apply(this,arguments);var t=function(B,z){u.ctx.save();jsPlumb.extend(u.ctx,z);if(z.gradient){var A=u.createGradient(B,u.ctx);for(var y=0;y0?1:-1}}var b={subtract:function(m,l){return{x:m.x-l.x,y:m.y-l.y}},dotProduct:function(m,l){return m.x*l.x+m.y*l.y},square:function(l){return Math.sqrt(l.x*l.x+l.y*l.y)},scale:function(m,l){return{x:m.x*l,y:m.y*l}}},d=Math.pow(2,-65),h=function(y,x){for(var s=[],v=x.length-1,r=2*v-1,t=[],w=[],o=[],p=[],q=[[1,0.6,0.3,0.1],[0.4,0.6,0.6,0.4],[0.1,0.3,0.6,1]],u=0;u<=v;u++){t[u]=b.subtract(x[u],y)}for(u=0;u<=v-1;u++){w[u]=b.subtract(x[u+1],x[u]);w[u]=b.scale(w[u],3)}for(u=0;u<=v-1;u++){for(var n=0;n<=v;n++){o[u]||(o[u]=[]);o[u][n]=b.dotProduct(w[u],t[n])}}for(u=0;u<=r;u++){p[u]||(p[u]=[]);p[u].y=0;p[u].x=parseFloat(u)/r}r=v-1;for(t=0;t<=v+r;t++){w=Math.min(t,v);for(u=Math.max(0,t-r);u<=w;u++){j=t-u;p[u+j].y+=o[j][u]*q[j][u]}}v=x.length-1;p=k(p,2*v-1,s,0);r=b.subtract(y,x[0]);o=b.square(r);for(u=q=0;u=64){y[0]=(E[0].x+E[D].x)/2;return 1}var s,r,p;v=E[0].y-E[D].y;w=E[D].x-E[0].x;A=E[0].x*E[D].y-E[D].x*E[0].y;t=max_distance_below=0;for(r=1;rt){t=p}else{if(p0?1:-1,r=null;n endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + endpointsByElement = {}, + endpointsByUUID = {}, + offsets = {}, + offsetTimestamps = {}, + floatingConnections = {}, + draggableStates = {}, + _mouseEventsEnabled = this.Defaults.MouseEventsEnabled, + _draggableByDefault = true, + canvasList = [], + sizes = [], + listeners = {}, // a map: keys are event types, values are lists of listeners. + DEFAULT_SCOPE = this.Defaults.Scope, + renderMode = null, // will be set in init() + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }, + + /** + * appends an element to some other element, which is calculated as follows: + * + * 1. if jsPlumb.Defaults.Container exists, use that element. + * 2. if the 'parent' parameter exists, use that. + * 3. otherwise just use the document body. + * + */ + _appendElement = function(el, parent) { + if (_currentInstance.Defaults.Container) + jsPlumb.CurrentLibrary.appendElement(el, _currentInstance.Defaults.Container); + else if (!parent) + document.body.appendChild(el); + else + jsPlumb.CurrentLibrary.appendElement(el, parent); + }, + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + _timestamp = function() { return "" + (new Date()).getTime(); }, + + /** + * YUI, for some reason, put the result of a Y.all call into an object that contains + * a '_nodes' array, instead of handing back an array-like object like the other + * libraries do. + */ + _convertYUICollection = function(c) { + return c._nodes ? c._nodes : c; + }, + + /** + * Draws an endpoint and its connections. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + _draw = function(element, ui, timestamp) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (!timestamp) timestamp = _timestamp(); + if (endpoints) { + _updateOffset( { elId : id, offset : ui, recalc : false, timestamp : timestamp }); // timestamp is checked against last update cache; it is + // valid for one paint cycle. + var myOffset = offsets[id], myWH = sizes[id]; + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + var l = endpoints[i].connections; + for ( var j = 0; j < l.length; j++) { + l[j].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); // ...paint each connection. + // then, check for dynamic endpoint; need to repaint it. + var oIdx = l[j].endpoints[0] == endpoints[i] ? 1 : 0, + otherEndpoint = l[j].endpoints[oIdx]; + if (otherEndpoint.anchor.isDynamic && !otherEndpoint.isFloating()) { + otherEndpoint.paint({ elementWithPrecedence:id }); + // all the connections for the other endpoint now need to be repainted + for (var k = 0; k < otherEndpoint.connections.length; k++) { + if (otherEndpoint.connections[k] !== l) + otherEndpoint.connections[k].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); + } + } + } + } + } + }, + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }, + + /** + * gets an Endpoint by uuid. + */ + _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }, + + /** + * inits a draggable if it's not already initialised. + */ + _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element) && !jsPlumb.CurrentLibrary.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + } + } + }, + + _newConnection = function(params) { + var connectionFunc = jsPlumb.Defaults.ConnectionType || Connection, + endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint, + parent = jsPlumb.CurrentLibrary.getParent; + + if (params.container) + params["parent"] = params.container; + else { + if (params.sourceEndpoint) + params["parent"] = params.sourceEndpoint.parent; + else if (params.source.constructor == endpointFunc) + params["parent"] = params.source.parent; + else params["parent"] = parent(params.source); + } + + params["_jsPlumb"] = _currentInstance; + var con = new connectionFunc(params); + _eventFireProxy("click", "click", con); + _eventFireProxy("dblclick", "dblclick", con); + return con; + }, + + _eventFireProxy = function(event, proxyEvent, obj) { + obj.bind(event, function(e) { + _currentInstance.fire(proxyEvent, obj, e); + }); + }, + + _newEndpoint = function(params) { + var endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint; + if (params.container) + params.parent = params.container; + else + params["parent"] = jsPlumb.CurrentLibrary.getParent(params.source); + params["_jsPlumb"] = _currentInstance, + ep = new endpointFunc(params); + _eventFireProxy("click", "endpointClick", ep); + _eventFireProxy("dblclick", "endpointDblClick", ep); + return ep; + }, + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + _operation = function(elId, func, endpointFunc) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + if (endpointFunc) endpointFunc(endpoints[i]); + } + } + }, + /** + * perform an operation on all elements. + */ + _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }, + + /** + * helper to remove an element from the DOM. + */ + _removeElement = function(element, parent) { + if (element != null && element.parentNode != null) { + element.parentNode.removeChild(element); + } + }, + /** + * helper to remove a list of elements from the DOM. + */ + _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }, + /** + * helper method to remove an item from a list. + */ + _removeFromList = function(map, key, value) { + if (key != null) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete (l[i]); + l.splice(i, 1); + return true; + } + } + } + return false; + }, + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }, + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + _setVisible = function(el, state, alsoChangeEndpoints) { + state = state === "block"; + var endpointFunc = null; + if (alsoChangeEndpoints) { + if (state) endpointFunc = function(ep) { + ep.setVisible(true, true, true); + }; + else endpointFunc = function(ep) { + ep.setVisible(false, true, true); + }; + } + var id = _getAttribute(el, "id"); + _operation(id, function(jpc) { + if (state && alsoChangeEndpoints) { + // this test is necessary because this functionality is new, and i wanted to maintain backwards compatibility. + // this block will only set a connection to be visible if the other endpoint in the connection is also visible. + var oidx = jpc.sourceId === id ? 1 : 0; + if (jpc.endpoints[oidx].isVisible()) jpc.setVisible(true); + } + else // the default behaviour for show, and what always happens for hide, is to just set the visibility without getting clever. + jpc.setVisible(state); + }, endpointFunc); + }, + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }, + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + _toggleVisible = function(elId, changeEndpoints) { + var endpointFunc = null; + if (changeEndpoints) { + endpointFunc = function(ep) { + var state = ep.isVisible(); + ep.setVisible(!state); + }; + } + _operation(elId, function(jpc) { + var state = jpc.isVisible(); + jpc.setVisible(!state); + }, endpointFunc); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }, + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return offsets[elId]; + } + if (recalc || offset == null) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + } + return offsets[elId]; + }, + +/** + * gets an id for the given element, creating and setting one if + * necessary. + */ + _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else + id = "jsPlumb_" + _timestamp(); + _setAttribute(ele, "id", id); + } + return id; + }, + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'jsPlumb function failed : ' + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'wrapped function failed : ' + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = "_jsPlumb_connector"; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = "_jsPlumb_endpoint"; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = "_jsPlumb_overlay"; + + this.Anchors = {}; + + this.Connectors = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Endpoints = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Overlays = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + +// ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***************************************** + /* + * Function: bind + * Bind to an event on jsPlumb. + * + * Parameters: + * event - the event to bind. Available events on jsPlumb are: + * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback. + * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback. + * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback. + * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback. + * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback. + * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback. + * + * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event. + */ + + /* + * Function: clearListeners + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + +// *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *********************************************************** + + /* + Function: addEndpoint + + Adds an to a given element or elements. + + Parameters: + + el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + params - Object containing Endpoint constructor arguments. For more information, see . + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some + shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in + this object are anything that 'params' can contain. See . + + Returns: + The newly created , if el referred to a single element. Otherwise, an array of newly created s. + + See Also: + + */ + this.addEndpoint = function(el, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + + // YUI wrapper + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + var _el = _getElementObject(inputs[i]), id = _getId(_el); + p.source = _el; + _updateOffset({ elId : id }); + var e = _newEndpoint(p); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + results.push(e); + } + + return results.length == 1 ? results[0] : results; + }; + + /* + Function: addEndpoints + Adds a list of s to a given element or elements. + + Parameters: + target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See 's constructor documentation. + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created s, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(el, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams); + if (e.constructor == Array) + Array.prototype.push.apply(results, e); + else results.push(e); + } + return results; + }; + + /* + Function: animate + This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating + the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // onComplete repaints, just to make sure everything looks good at the end of the animation. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /* + Function: connect + Establishes a between two elements (or s, which are themselves registered to elements). + + Parameters: + params - Object containing constructor arguments for the Connection. See 's constructor documentation. + referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of + Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection. + + Returns: + The newly created . + */ + this.connect = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + if (_p.target && !_p.target.endpoint) { + var tid = _getId(_p.target), + tep =_targetEndpointDefinitions[tid]; + if (tep) { + if (_p.endpoints) _p.endpoints[1] = tep; + else if (_p.endpoint) { + _p.endpoints = [ _p.endpoint, tep ]; + _p.endpoint = null; + } + else _p.endpoints = [ null, "Rectangle" ]; + } + } + + // dynamic anchors. backwards compatibility here: from 1.2.6 onwards you don't need to specify "dynamicAnchors". the fact that some anchor consists + // of multiple definitions is enough to tell jsPlumb you want it to be dynamic. + if (_p.dynamicAnchors) { + // these can either be an array of anchor coords, which we will use for both source and target, or an object with {source:[anchors], target:[anchors]}, in which + // case we will use a different set for each element. + var a = _p.dynamicAnchors.constructor == Array; + var sa = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.source)); + var ta = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.target)); + _p.anchors = [sa,ta]; + } + + var jpc = _newConnection(_p); + // add to list of connections (by scope). + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + _currentInstance.fire("jsPlumbConnection", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + // force a paint + _draw(jpc.source); + + return jpc; + }; + + /* + Function: deleteEndpoint + Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too) + + Parameters: + object - either an object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElement(endpoint.canvas, endpoint.parent); + // remove from endpointsbyElement + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + delete endpoint; + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every , and their associated s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc) { + _currentInstance.fire("jsPlumbConnectionDetached", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + }; + + /* + Function: detach + Detaches and then removes a . Takes either (source, target) (the old way, maintained for backwards compatibility), or a params + object with various possible values. + + Parameters: + source - id or element object of the first element in the Connection. + target - id or element object of the second element in the Connection. + params - a JS object containing the same parameters as you pass to jsPlumb.connect. If this is present then neither source nor + target should be present; it should be the only argument to the method. See the docs for 's constructor for information +about the parameters allowed in the params object. + Returns: + true if successful, false if not. + */ + this.detach = function(source, target) { + if (arguments.length == 2) { + var s = _getElementObject(source), sId = _getId(s); + var t = _getElementObject(target), tId = _getId(t); + _operation(sId, function(jpc) { + if ((jpc.sourceId == sId && jpc.targetId == tId) || (jpc.targetId == sId && jpc.sourceId == tId)) { + _removeElements(jpc.connector.getDisplayElements(), jpc.parent); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + // this is the new version of the method, taking a JS object like + // the connect method does. + else if (arguments.length == 1) { + // TODO investigate whether or not this code still works when a user has supplied their own subclass of Connection. i suspect it may not. + if (arguments[0].constructor == Connection) { + arguments[0].endpoints[0].detachFrom(arguments[0].endpoints[1]); + } + else if (arguments[0].connection) { + arguments[0].connection.endpoints[0].detachFrom(arguments[0].connection.endpoints[1]); + } + else { + var _p = jsPlumb.extend( {}, source); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1])); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source); + var targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElements(jpc.connector.getDisplayElements(), jpc.parent); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + } + } + }; + + /* + Function: detachAll + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + + Returns: + void + */ + this.detachAllConnections = function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + }; + + /** + * @deprecated Use detachAllConnections instead. this will be removed in jsPlumb 1.3. + */ + this.detachAll = this.detachAllConnections; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + /** + * @deprecated use detachEveryConnection instead. this will be removed in jsPlumb 1.3. + */ + this.detachEverything = this.detachEveryConnection; + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of you library's draggable method so that jsPlumb can setup the appropriate callbacks. Your underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method, + * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the + * default scope). If multiple scopes are passed in, the return value will be a map of { scope -> [ connection... ] }. + * + * Parameters + * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list + * of connections that are in the given scope. + * options - if the argument is a JS object, you can specify a finer-grained filter: + * + * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes. + * - *source* either a string representing an element id, or a selector. constrains the result to connections having this source. + * - *target* either a string representing an element id, or a selector. constrains the result to connections having this target. + * + */ + this.getConnections = function(options) { + if (!options) { + options = {}; + } else if (options.constructor == String) { + options = { "scope": options }; + } + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') + r.push(input); + else + r = input; + } + return r; + }; + var scope = options.scope || jsPlumb.getDefaultScope(), + scopes = prepareList(scope), + sources = prepareList(options.source), + targets = prepareList(options.target), + filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }, + results = scopes.length > 1 ? {} : [], + _addOne = function(scope, obj) { + if (scopes.length > 1) { + var ss = results[scope]; + if (ss == null) { + ss = []; results[scope] = ss; + } + ss.push(obj); + } else results.push(obj); + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + _addOne(i, c); + } + } + } + return results; + }; + + /* + * Function: getAllConnections + * Gets all connections, as a map of { scope -> [ connection... ] }. + */ + this.getAllConnections = function() { + return connectionsByScope; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /** + * Function:getEndpoints + * Gets the list of Endpoints for a given selector, or element id. + * @param el + * @return + */ + this.getEndpoints = function(el) { + return endpointsByElement[_getId(el)]; + }; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + + this.appendElement = _appendElement; + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + changeEndpoints - whether not to also hide endpoints on the element. by default this is false. + + Returns: + void + */ + this.hide = function(el, changeEndpoints) { + _setVisible(el, "none", changeEndpoints); + }; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + if (!initialized) { + _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run. + + var _bind = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && _mouseEventsEnabled && renderMode == jsPlumb.CANVAS) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + var t = c[i].connector[event](e); + if (t) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i].endpoint[event](e)) return; + } + } + } + }); + }; + _bind("click"); + _bind("dblclick"); + _bind("mousemove"); + _bind("mousedown"); + _bind("mouseup"); + + initialized = true; + _currentInstance.fire("ready"); + } + }; + + this.jsPlumbUIComponent = jsPlumbUIComponent; + this.EventGenerator = EventGenerator; + + /* + * Creates an anchor with the given params. + * + * You do not need to use this method. It is exposed because of the way jsPlumb is + * split into three scripts; this will change in the future. + * + * x - the x location of the anchor as a fraction of the + * total width. y - the y location of the anchor as a fraction of the + * total height. xOrientation - value indicating the general direction a + * connection from the anchor should go in, in the x direction. + * yOrientation - value indicating the general direction a connection + * from the anchor should go in, in the y direction. xOffset - a fixed + * offset that should be applied in the x direction that should be + * applied after the x position has been figured out. optional. defaults + * to 0. yOffset - a fixed offset that should be applied in the y + * direction that should be applied after the y position has been + * figured out. optional. defaults to 0. + * -- OR -- + * + * params - {x:..., y:..., xOrientation etc } + * -- OR FROM 1.2.4 --- + * + * name - the name of some Anchor in the _currentInstance.Anchors array. + * -- OR FROM 1.2.4 --- + * + * coords - a list of coords for the anchor, like you would pass to + * jsPlumb.makeAnchor (eg [0.5,0.5,0,-1] - an anchor in the center of + * some element, oriented towards the top of the screen) + * -- OR FROM 1.2.4 --- + * + * anchor - an existing anchor. just gets passed back. it's handy + * internally to have this functionality. + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed + // in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards + // compatibility if we are given only one value we assume it's a + // call in the old form. + if (arguments.length == 0) return null; + var params = {}; + if (arguments.length == 1) { + var specimen = arguments[0]; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; + // is it the name of an anchor type? + else if (typeof specimen == "string") return _currentInstance.Anchors[arguments[0]](); + // is it an array of coordinates? + else if (specimen.constructor == Array) { + if (specimen[0].constructor == Array || specimen[0].constructor == String) + return new DynamicAnchor(specimen); + else + return jsPlumb.makeAnchor.apply(this, specimen); + } + // last we try the backwards compatibility stuff. + else if (typeof arguments[0] == "object") jsPlumb.extend(params, x); + } else { + params = { x : x, y : y }; + if (arguments.length >= 4) params.orientation = [ arguments[2], arguments[3] ]; + if (arguments.length == 6) params.offsets = [ arguments[4], arguments[5] ]; + } + var a = new Anchor(params); + a.clone = function() { + return new Anchor(params); + }; + return a; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types) { + var r = []; + for ( var i = 0; i < types.length; i++) + if (typeof types[i] == "string") + r.push(_currentInstance.Anchors[types[i]]()); + else if (types[i].constructor == Array) + r.push(jsPlumb.makeAnchor(types[i])); + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /** + * Function: makeTarget + * Makes some DOM element a Connection target, allowing you to drag connections to it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a target. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * scope optional. scope for the drop zone. + * dropOptions optional. same stuff as you would pass to dropOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to false. whether or not to delete + * any Endpoints created by a connection to this target if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + var _targetEndpointDefinitions = {}; + this.makeTarget = function(el, params, referenceParams) { + + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + var jpcl = jsPlumb.CurrentLibrary, + scope = p.scope || _currentInstance.Defaults.Scope, + deleteEndpointsOnDetach = p.deleteEndpointsOnDetach || false, + _doOne = function(_el) { + + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el); + _targetEndpointDefinitions[elid] = p.endpoint; + + var dropOptions = jsPlumb.extend({}, p.dropOptions || {}); + var _drop = function() { + var draggable = _getElementObject(jpcl.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + // restore the original scope if necessary (issue 57) + scope = _getAttribute(draggable, "originalScope"); + + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + // get the connection, to then get its endpoint + var jpc = floatingConnections[id], + source = jpc.endpoints[0], + _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : null, + // make a new Endpoint + newEndpoint = jsPlumb.addEndpoint(_el, _endpoint); + + var c = jsPlumb.connect({ + source:source, + target:newEndpoint, + scope:scope + }); + if (deleteEndpointsOnDetach) + c.endpointToDeleteOnDetach = newEndpoint; + }; + + var dropEvent = jpcl.dragEvents['drop']; + dropOptions["scope"] = dropOptions["scope"] || scope; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop); + + jpcl.initDroppable(_el, dropOptions); + }; + + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements drop targets. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeTargets = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeTarget(els[i], params, referenceParams); + } + }; + + /* + Function: ready + Helper method to bind a function to jsPlumb's ready event. + */ + this.ready = function(fn) { + _currentInstance.bind("ready", fn); + }, + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + for ( var i in ebe) + _currentInstance.deleteEndpoint(ebe[i]); + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + this.deleteEveryEndpoint(); + this.clearListeners(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + + Parameters: + value - whether or not to automatically repaint when the window is resized. + + Returns: void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setDraggableByDefault + * Sets whether or not elements are draggable by default. Default for this is true. + * + * Parameters: + * draggable - value to set + * + * Returns: + * void + */ + this.setDraggableByDefault = function(draggable) { + _draggableByDefault = draggable; + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction + * Sets the function to fire when the window size has changed and a repaint was fired. + * + * Parameters: + * f - Function to execute. + * + * Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: setMouseEventsEnabled + * Sets whether or not mouse events are enabled. Default is true. + * + * Parameters: + * enabled - whether or not mouse events should be enabled. + * + * Returns: + * void + */ + this.setMouseEventsEnabled = function(enabled) { + _mouseEventsEnabled = enabled; + }; + + /* + * Constant for use with the setRenderMode method + */ + this.CANVAS = "canvas"; + + /* + * Constant for use with the setRenderMode method + */ + this.SVG = "svg"; + + this.VML = "vml"; + + /* + * Function: setRenderMode + * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that + * what you asked for is not supported (and that VML is). If you asked for VML but the browser does + * not support it, jsPlumb uses SVG. + * + * Returns: + * the render mode that jsPlumb set, which of course may be different from that requested. + */ + this.setRenderMode = function(mode) { + if (mode) + mode = mode.toLowerCase(); + else + return; + if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); + // now test we actually have the capability to do this. + if (mode === jsPlumb.CANVAS && canvasAvailable) + renderMode = jsPlumb.CANVAS; + else if (mode === jsPlumb.SVG && svgAvailable) + renderMode = jsPlumb.SVG; + else if (vmlAvailable) + renderMode = jsPlumb.VML; + + return renderMode; + }; + + this.getRenderMode = function() { return renderMode; }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * changeEndpoints - whether or not to also change the visible state of the endpoints on the element. this also has a bearing on + * other connections on those endpoints: if their other endpoint is also visible, the connections are made visible. + * + * Returns: + * void + */ + this.show = function(el, changeEndpoints) { + _setVisible(el, "block", changeEndpoints); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId, + makeAnchor:self.makeAnchor, + makeDynamicAnchor:self.makeDynamicAnchor + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * changeEndpoints - whether or not to also toggle the endpoints on the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload + * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element. + * + * Returns: + * void + */ + this.unload = function() { + delete endpointsByElement; + delete endpointsByUUID; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + delete canvasList; + }; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + if (timestamp && timestamp === self.timestamp) { + return lastReturnValue; + } + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + // adjust loc if there is an offsetParent + if (element.canvas && element.canvas.offsetParent) { + var po = element.canvas.offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(element.canvas.offsetParent); + lastReturnValue[0] = lastReturnValue[0] - po.left; + lastReturnValue[1] = lastReturnValue[1] - po.top; + } + + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + * + * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(_getElementObject(refCanvas)); + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + var orientation = null; + var _lastResult = null; + + this.compute = function(params) { + var xy = params.xy, element = params.element; + var result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + + // adjust loc if there is an offsetParent + if (element.canvas && element.canvas.offsetParent) { + var po = element.canvas.offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(element.canvas.offsetParent); + result[0] = result[0] - po.left; + result[1] = result[1] - po.top; + } + + _lastResult = result; + return result; + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { orientation = anchor.getOrientation(); }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchors is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = [], + _convert = function(anchor) { return anchor.constructor == Anchor ? anchor: jsPlumb.makeAnchor(anchor); }; + for (var i = 0; i < anchors.length; i++) _anchors[i] = _convert(anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + this.locked = false; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null, + _curIndex = _anchors.length > 0 ? 0 : -1, + self = this, + + // helper method to calculate the distance between the centers of the two elements. + _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }, + + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + + var pos = _curAnchor.compute(params); + return pos; + }; + + this.getCurrentLocation = function() { + var cl = _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + return cl; + }; + + this.getOrientation = function() { return _curAnchor != null ? _curAnchor.getOrientation() : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * container - optional id or selector instructing jsPlumb where to attach all the elements it creates for this connection. you should read the documentation for a full discussion of this. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * overlays - Optional array of Overlay definitions to appear on this Connection. + * drawEndpoints - if false, instructs jsPlumb to not draw the endpoints for this Connection. Be careful with this: it only really works when you tell jsPlumb to attach elements to the document body. Read the documentation for a full discussion of this. + */ + var Connection = function(params) { + + jsPlumbUIComponent.apply(this, arguments); + // ************** get the source and target and register the connection. ******************* + var self = this; + var visible = true; + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none"; + }; + var id = new String('_jsplumb_c_' + (new Date()).getTime()); + this.getId = function() { return id; }; + this.parent = params.parent; + /** + Property: source + The source element for this Connection. + */ + this.source = _getElementObject(params.source); + /** + Property:target + The target element for this Connection. + */ + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. + if (params.sourceEndpoint) this.source = params.sourceEndpoint.getElement(); + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + /* + * Property: sourceId + * Id of the source element in the connection. + */ + this.sourceId = _getAttribute(this.source, "id"); + /* + * Property: targetId + * Id of the target element in the connection. + */ + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + + /** + * implementation of abstract method in EventGenerator + * @return list of attached elements. in our case, a list of Endpoints. + */ + this.getAttachedElements = function() { + return self.endpoints; + }; + + /** + * implementation of abstract method in EventGenerator + */ + var srcWhenMouseDown = null, targetWhenMouseDown = null; + this.savePosition = function() { + srcWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.source)); + targetWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.target)); + }; + + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams) { + if (anchorParams) + return jsPlumb.makeAnchor(anchorParams); + }; + var prepareEndpoint = function(existing, index, params, element, connectorPaintStyle, connectorHoverPaintStyle) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] + || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint; + + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified. + if (es.fillStyle == null && connectorPaintStyle != null) + es.fillStyle = connectorPaintStyle.strokeStyle; + + // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does: + //* + if (es.outlineColor == null && connectorPaintStyle != null) + es.outlineColor = connectorPaintStyle.outlineColor; + if (es.outlineWidth == null && connectorPaintStyle != null) + es.outlineWidth = connectorPaintStyle.outlineWidth; + //*/ + + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure? + if (connectorHoverPaintStyle != null) { + if (ehs == null) ehs = {}; + if (ehs.fillStyle == null) { + ehs.fillStyle = connectorHoverPaintStyle.strokeStyle; + } + } + var a = params.anchors ? params.anchors[index] : _makeAnchor(_currentInstance.Defaults.Anchors[index]) || _makeAnchor(jsPlumb.Defaults.Anchors[index]) || _makeAnchor(_currentInstance.Defaults.Anchor) || _makeAnchor(jsPlumb.Defaults.Anchor); + var u = params.uuids ? params.uuids[index] : null; + var e = _newEndpoint({ + paintStyle : es, + hoverPaintStyle:ehs, + endpoint : ep, + connections : [ self ], + uuid : u, + anchor : a, + source : element, + container:params.container + }); + self.endpoints[index] = e; + + if (params.drawEndpoints === false) e.setVisible(false, true, true); + + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source, params.paintStyle, params.hoverPaintStyle); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target, params.paintStyle, params.hoverPaintStyle); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + /* + * Function: setConnector + * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method - the same + * thing that you would set as the 'connector' property on a jsPlumb.connect call. + * + * Parameters: + * connector - Connector definition + */ + this.setConnector = function(connector, doNotRepaint) { + if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent); + var connectorArgs = { _jsPlumb:self._jsPlumb, parent:params.parent, cssClass:params.cssClass, container:params.container }; + if (connector.constructor == String) + this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand. + else if (connector.constructor == Array) + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs)); + this.canvas = this.connector.canvas; + var _mouseDown = false, _mouseWasDown = false, _mouseDownAt = null; + // add mouse events + this.connector.bind("click", function(con, e) { + _mouseWasDown = false; + self.fire("click", self, e); + }); + this.connector.bind("dblclick", function(con, e) { _mouseWasDown = false;self.fire("dblclick", self, e); }); + this.connector.bind("mouseenter", function(con, e) { + if (!self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(true); + } + self.fire("mouseenter", self, e); + } + }); + this.connector.bind("mouseexit", function(con, e) { + if (self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(false); + } + self.fire("mouseexit", self, e); + } + }); + this.connector.bind("mousedown", function(con, e) { + _mouseDown = true; + _mouseDownAt = jsPlumb.CurrentLibrary.getPageXY(e); + self.savePosition(); + }); + this.connector.bind("mouseup", function(con, e) { + _mouseDown = false; + if (self.connector == _connectionBeingDragged) _connectionBeingDragged = null; + }); + + if (!doNotRepaint) self.repaint(); + }; + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc) + */ + + self.setConnector(this.endpoints[0].connector || + this.endpoints[1].connector || + params.connector || + _currentInstance.Defaults.Connector || + jsPlumb.Defaults.Connector, true); + + this.setPaintStyle(this.endpoints[0].connectorStyle || + this.endpoints[1].connectorStyle || + params.paintStyle || + _currentInstance.Defaults.PaintStyle || + jsPlumb.Defaults.PaintStyle, true); + + this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || + this.endpoints[1].connectorHoverStyle || + params.hoverPaintStyle || + _currentInstance.Defaults.HoverPaintStyle || + jsPlumb.Defaults.HoverPaintStyle, true); + + this.paintStyleInUse = this.paintStyle; + + /* + * Property: overlays + * List of Overlays for this Connection. + */ + this.overlays = []; + var _overlays = params.overlays || _currentInstance.Defaults.Overlays; + if (_overlays) { + for (var i = 0; i < _overlays.length; i++) { + var o = _overlays[i], _newOverlay = null, _overlayEvents = null; + if (o.constructor == Array) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0]; + var p = jsPlumb.CurrentLibrary.extend({connection:self, _jsPlumb:_currentInstance}, o[1]); // make a copy of the object so as not to mess up anyone else's reference... + if (o.length == 3) jsPlumb.CurrentLibrary.extend(p, o[2]); + _newOverlay = new jsPlumb.Overlays[renderMode][type](p); + if (p.events) { + for (var evt in p.events) { + _newOverlay.bind(evt, p.events[evt]); + } + } + } else if (o.constructor == String) { + _newOverlay = new jsPlumb.Overlays[renderMode][o]({connection:self, _jsPlumb:_currentInstance}); + } else { + _newOverlay = o; + } + + + + this.overlays.push(_newOverlay); + } + } + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Connection. + * + * Parameters: + * event - the event to bind. Available events on a Connection are: + * - *click* : notification that a Connection was clicked. + * - *dblclick* : notification that a Connection was double clicked. + * - *mouseenter* : notification that the mouse is over a Connection. + * - *mouseexit* : notification that the mouse exited a Connection. + * + * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Connection's paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Connection will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Connection. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + this.addOverlay = function(overlay) { self.overlays.push(overlay); }; + + /** + * Function: removeAllOverlays + * Removes all overlays from the Connection, and then repaints. + */ + this.removeAllOverlays = function() { + self.overlays.splice(0, self.overlays.length); + self.repaint(); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + this.removeOverlay = function(overlayId) { + var idx = -1; + for (var i = 0; i < self.overlays.length; i++) { + if (overlayId === self.overlays[i].id) { + idx = i; + break; + } + } + if (idx != -1) self.overlays.splice(idx, 1); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + this.removeOverlays = function() { + for (var i = 0; i < arguments.length; i++) + self.removeOverlay(arguments[i]); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + this.labelStyle = params.labelStyle || _currentInstance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + if (this.label) { + this.overlays.push(new jsPlumb.Overlays[renderMode].Label( { + cssClass:params.cssClass, + labelStyle : this.labelStyle, + label : this.label, + connection:self, + _jsPlumb:_currentInstance + })); + } + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String or a Function that returns a String. + */ + this.setLabel = function(l) { + self.label = l; + _currentInstance.repaint(self.source); + }; + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var otherOffset = offsets[this.targetId]; + var otherWH = sizes[this.targetId]; + var anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1] + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0] + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc }); + + /* + * Paints the Connection. Not exposed for public usage. + * + * Parameters: + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp; + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = false; + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }); + _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + var sAnchorP = this.endpoints[sIdx].anchor.getCurrentLocation(); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.getCurrentLocation(); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + + /* paint overlays*/ + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + var s = o.computeMaxSize(self.connector); + if (s > maxSize) + maxSize = s; + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, self.paintStyleInUse.lineWidth, maxSize); + + self.connector.paint(dim, self.paintStyleInUse); + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim); + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function() { + this.paint({ elId : this.sourceId, recalc : true }); + }; + + _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide + // whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + + // just to make sure the UI gets initialised fully on all browsers. + self.repaint(); + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1 + * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't + * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you + * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or + * 'target' Endpoints for Connections. + * + * + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * container - optional id or selector instructing jsPlumb where to attach the element it creates for this endpoint. you should read the documentation for a full discussion of this. + * connections - optional list of Connections to configure the Endpoint with. + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + params = params || {}; + var self = this; +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Endpoint. + * + * Parameters: + * event - the event to bind. Available events on an Endpoint are: + * - *click* : notification that a Endpoint was clicked. + * - *dblclick* : notification that a Endpoint was double clicked. + * - *mouseenter* : notification that the mouse is over a Endpoint. + * - *mouseexit* : notification that the mouse exited a Endpoint. + * + * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Endpoint's paint style and then repaints the Endpoint. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Endpoint will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Endpoint. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + var visible = true; + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + var id = new String('_jsplumb_e_' + (new Date()).getTime()); + this.getId = function() { return id; }; + if (params.dynamicAnchors) + self.anchor = new DynamicAnchor(jsPlumb.makeAnchors(params.dynamicAnchors)); + else + self.anchor = params.anchor ? jsPlumb.makeAnchor(params.anchor) : params.anchors ? jsPlumb.makeAnchor(params.anchors) : jsPlumb.makeAnchor("TopCenter"); + var _endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot", + endpointArgs = { _jsPlumb:self._jsPlumb, parent:params.parent, container:params.container }; + if (_endpoint.constructor == String) + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint](endpointArgs); + else if (_endpoint.constructor == Array) { + endpointArgs = jsPlumb.extend(_endpoint[1], endpointArgs); + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint[0]](endpointArgs); + } + else + _endpoint = _endpoint.clone(); + + // assign a clone function using our derived endpointArgs. this is used when a drag starts: the endpoint that was dragged is cloned, + // and the clone is left in its place while the original one goes off on a magical journey. + this.clone = function() { + var o = new Object(); + _endpoint.constructor.apply(o, [endpointArgs]); + return o; + }; + + self.endpoint = _endpoint; + self.type = self.endpoint.type; + + // TODO this event listener registration code is identical to what Connection does: it should be refactored. + this.endpoint.bind("click", function(e) { self.fire("click", self, e); }); + this.endpoint.bind("dblclick", function(e) { self.fire("dblclick", self, e); }); + this.endpoint.bind("mouseenter", function(con, e) { + if (!self.isHover()) { + self.setHover(true); + self.fire("mouseenter", self, e); + } + }); + this.endpoint.bind("mouseexit", function(con, e) { + if (self.isHover()) { + self.setHover(false); + self.fire("mouseexit", self, e); + } + }); + // TODO this event listener registration code above is identical to what Connection does: it should be refactored. + + this.setPaintStyle(params.paintStyle || + params.style || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyle, true); + this.setHoverPaintStyle(params.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyle, true); + this.paintStyleInUse = this.paintStyle; + this.connectorStyle = params.connectorStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.parent = params.parent; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + var _element = params.source, + _uuid = params.uuid, + floatingEndpoint = null, + inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + this.element = _element; + var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + /* + * Property: canvas + * The Endpoint's Canvas. + */ + this.canvas = this.endpoint.canvas; + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + this.connections = params.connections || []; + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + var _reattach = params.reattach || false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + + // this avoids a circular loop + if (!ignoreTarget) { + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + t.detach(connection, true); + // check connection to see if we want to delete the other endpoint. + // if the user uses makeTarget to make some element a target for connections, + // it is possible that they will have set 'endpointToDeleteOnDetach': when + // you make a connection to an element that acts as a target (note: NOT an + // Endpoint; just some div as a target), Endpoints are created for that + // connection. so if you then delete that Connection, it is feasible you + // will want these auto-generated endpoints to be removed. + if (connection.endpointToDeleteOnDetach && connection.endpointToDeleteOnDetach.connections.length == 0) + jsPlumb.deleteEndpoint(connection.endpointToDeleteOnDetach); + } + _removeElements(connection.connector.getDisplayElements(), connection.parent); + _removeFromList(connectionsByScope, connection.scope, connection); + if(!ignoreTarget) fireDetachEvent(connection); + } + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + */ + this.detachAll = function() { + while (self.connections.length > 0) { + self.detach(self.connections[0]); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + */ + this.detachFrom = function(targetEndpoint) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + c[i].setHover(false); + self.detach(c[i]); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + return _newEndpoint( { anchor : self.anchor, source : _element, paintStyle : this.paintStyle, endpoint : _endpoint }); + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + return (self.connections.length < _maxConnections) || _maxConnections == -1 ? null : self.connections[0]; + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + /* + * Function: setStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to + * setStyle and deprecate it if so. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + * + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + // a helper function that tries to find a connection to the given element, and returns it if so. if elementWithPrecedence is null, + // or no connection to it is found, we return the first connection in our list. + var findConnectionToUseForDynamicAnchor = function(elementWithPrecedence) { + var idx = 0; + if (elementWithPrecedence != null) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].sourceId == elementWithPrecedence || self.connections[i].targetId == elementWithPrecedence) { + idx = i; + break; + } + } + } + + return self.connections[idx]; + }; + + /* + * Function: paint + * Paints the Endpoint, recalculating offset and anchor positions if necessary. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + + params = params || {}; + var timestamp = params.timestamp; + if (!timestamp || self.timestamp !== timestamp) { + var ap = params.anchorPoint, canvas = params.canvas, connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + var xy = params.offset || offsets[_elementId]; + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (self.anchor.isDynamic) { + if (self.connections.length > 0) { + //var c = self.connections[0]; + var c = findConnectionToUseForDynamicAnchor(params.elementWithPrecedence); + var oIdx = c.endpoints[0] == self ? 1 : 0; + var oId = oIdx == 0 ? c.sourceId : c.targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + } + ap = self.anchor.compute(anchorParams); + } + + var d = _endpoint.compute(ap, self.anchor.getOrientation(), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + _endpoint.paint(d, self.paintStyleInUse, self.anchor); + + self.timestamp = timestamp; + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + var start = function() { + jpc = self.connectorSelector(); + if (self.isFull() && !dragAllowedWhenFull) return false; + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + n.style.position = "absolute"; + var nE = _getElementObject(n); + _appendElement(n, self.parent); + // create and assign an id, and initialize the offset. + var id = _getId(nE); + + // set the offset of this div to be where 'inPlaceCopy' is, to start with. + var ipcoel = _getElementObject(inPlaceCopy.canvas), + ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel), + po = inPlaceCopy.canvas.offsetParent != null ? + inPlaceCopy.canvas.offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(inPlaceCopy.canvas.offsetParent) + : { left:0, top: 0}; + jsPlumb.CurrentLibrary.setOffset(n, {left:ipco.left - po.left, top:ipco.top-po.top}); + + _updateOffset( { elId : id }); + + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor( { reference : self.anchor, referenceCanvas : self.canvas }); + floatingEndpoint = _newEndpoint({ paintStyle : self.paintStyle, endpoint : _endpoint, anchor : floatingAnchor, source : nE }); + + if (jpc == null) { + self.anchor.locked = true; + // create a connection. one end is this endpoint, the + // other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : _getElementObject(_element), + target : _getElementObject(n), + anchors : [ self.anchor, floatingAnchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays + }); + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.connector.setHover(false); + } else { + existingJpc = true; + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.connector.setHover(false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas)); + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; // are we the source or the target? + + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas); + var dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // now we want to get this endpoint's DROP scope, and set it for now: we can only be dropped on drop zones + // that have our drop scope (issue 57). + var dropScope = jsPlumb.CurrentLibrary.getDropScope(c);//jpc.endpoints[anchorIdx == 0 ? 1 : 0].getDropScope(); + jsPlumb.CurrentLibrary.setDragScope(c, dropScope); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it and register connection on it. + floatingConnections[id] = jpc; + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var jpcl = jsPlumb.CurrentLibrary, + dragOptions = params.dragOptions || {}, + defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions), + startEvent = jpcl.dragEvents['start'], + stopEvent = jpcl.dragEvents['stop'], + dragEvent = jpcl.dragEvents['drag']; + + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], + function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + jsPlumb.CurrentLibrary.setOffset(n, _ui); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements( [ n, floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (_reattach) { + + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } else { + jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc); // the main endpoint will inform the floating endpoint + // to disconnect, and also post the detached event. + } + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElements(jpc.connector.getDisplayElements(), self.parent); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint(); + jpc.setHover(false); + jpc.repaint(); + jpc = null; + delete inPlaceCopy; + delete endpointsByElement[floatingEndpoint.elementId]; + floatingEndpoint = null; + delete floatingEndpoint; + + _currentInstance.currentlyDragging = false; + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas) { + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + var drop = function() { + var draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)); + var id = _getAttribute(draggable, "dragId"); + var elId = _getAttribute(draggable, "elId"); + + // restore the original scope if necessary (issue 57) + var scope = _getAttribute(draggable, "originalScope"); + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + var jpc = floatingConnections[id]; + + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // todo test that the target is not full. + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + if (!jpc.suspendedEndpoint) { + _addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + _currentInstance.fire("jsPlumbConnectionDetached", { + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }); + } + + jsPlumb.repaint(elId); + + _currentInstance.fire("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], + targetEndpoint : jpc.endpoints[1], + connection:jpc + }); + } + + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute( _getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + } + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute(_getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + } + }); + + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas)); + + return self; + }; + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + j.init(); + return j; + }; + + var _curryAnchor = function(x,y,ox,oy) { + return function() { + return jsPlumb.makeAnchor(x,y,ox,oy); + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1); + + jsPlumb.Defaults.DynamicAnchors = function() { + return jsPlumb.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"]); + }; + jsPlumb.Anchors["AutoDefault"] = function() { return jsPlumb.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors()); }; + + +})(); +/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the default Connectors, Endpoint and Overlay definitions. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +(function() { + + /** + * + * Helper class to consume unused mouse events by components that are DOM elements and + * are used by all of the different rendering modes. + * + */ + jsPlumb.DOMElementComponent = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + // when render mode is canvas, these functions may be called by the canvas mouse handler. + // this component is safe to pipe this stuff to /dev/null. + this.mousemove = + this.dblclick = + this.click = + this.mousedown = + this.mouseup = function(e) { }; + }; + + /** + * Class: Connectors.Straight + * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters. + */ + jsPlumb.Connectors.Straight = function() { + this.type = "Straight"; + var self = this; + var currentPoints = null; + var _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + _dx = _tx - _sx, _dy = (_ty - _sy); + _m = _dy / _dx, _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + + return currentPoints; + }; + + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + var xp = _sx + (location * _dx); + var yp = (_m == Infinity || _m == -Infinity) ? _sy + (location * (_ty - _sy)) : (_m * xp) + _b; + return {x:xp, y:yp}; + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { return _m; }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + var orientation = distance > 0 ? 1 : -1; + var y = Math.abs(distance * Math.sin(_theta)); + if (_sy > _ty) y = y * -1; + var x = Math.abs(distance * Math.cos(_theta)); + if (_sx > _tx) x = x * -1; + return {x:p.x + (orientation * x), y:p.y + (orientation * y)}; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = self.gradientAtPoint(p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + }; + }; + + + /** + * Class:Connectors.Bezier + * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's + * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below. + */ + /** + * Function:Constructor + * + * Parameters: + * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line. + * Optional; defaults to 150. + * + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + params = params || {}; + this.majorAnchor = params.curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + this.type = "Bezier"; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h; + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) + { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx); var minx2 = Math.min(_CP[0], _CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(_sx,_tx); var maxx2 = Math.max(_CP[0], _CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty); var miny2 = Math.min(_CP[1], _CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(_sy,_ty); var maxy2 = Math.max(_CP[1], _CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + return jsBezier.perpendicularToCurveAt(_makeCurve(), location, length, distance); + }; + + }; + + + /** + * Class: Connectors.Flowchart + * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments. + */ + /** + * Function: Constructor + * + * Parameters: + * stub - minimum length for the stub at each end of the connector. defaults to 30 pixels. + */ + jsPlumb.Connectors.Flowchart = function(params) { + this.type = "Flowchart"; + params = params || {}; + var self = this, + minStubLength = params.stub || params.minStubLength /* bwds compat. */ || 30, + segments = [], + segmentGradients = [], + segmentProportions = [], + segmentLengths = [], + segmentProportionalLengths = [], + points = [], + swapX, + swapY, + /** + * recalculates the gradients of each segment, and the points at which the segments begin, proportional to the total length travelled + * by all the segments that constitute the connector. + */ + updateSegmentGradientsAndProportions = function(startX, startY, endX, endY) { + var total = 0; + for (var i = 0; i < segments.length; i++) { + var sx = i == 0 ? startX : segments[i][2], + sy = i == 0 ? startY : segments[i][3], + ex = segments[i][0], + ey = segments[i][1]; + + segmentGradients[i] = sx == ex ? Infinity : 0; + segmentLengths[i] = Math.abs(sx == ex ? ey - sy : ex - sx); + total += segmentLengths[i]; + } + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segmentLengths[i] / total; + segmentProportions[i] = [curLoc, (curLoc += (segmentLengths[i] / total)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0]; + var ly = segments.length == 0 ? sy : segments[segments.length - 1][1]; + segments.push([x, y, lx, ly]); + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment + * that contains the point which is 'location' distance along the entire path, where + * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths + * are made up of a list of segments, each of which contributes some fraction to + * the total length. + */ + findSegmentForLocation = function(location) { + var idx = segmentProportions.length - 1, inSegmentProportion = 0; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + segments = []; + segmentGradients = []; + segmentProportionalLengths = []; + segmentLengths = []; + segmentProportionals = []; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + offx = (lw / 2) + (minStubLength * 2), + offy = (lw / 2) + (minStubLength * 2), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(), + to = targetAnchor.orientation || targetAnchor.getOrientation(), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx, + h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy; + if (w < minWidth) { + offx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + offy += (minWidth - h) / 2; + h = minWidth; + } + var sx = swapX ? w-offx : offx, + sy = swapY ? h-offy : offy, + tx = swapX ? offx : w-offx , + ty = swapY ? offy : h-offy, + startStubX = sx + (so[0] * minStubLength), + startStubY = sy + (so[1] * minStubLength), + endStubX = tx + (to[0] * minStubLength), + endStubY = ty + (to[1] * minStubLength), + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2); + + x -= offx; y -= offy; + points = [x, y, w, h, sx, sy, tx, ty], extraPoints = []; + + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + if (so[0] == 0) { + var startStubIsBeforeEndStub = startStubY < endStubY; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } + } + else { + var startStubIsBeforeEndStub = startStubX < endStubX; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } + } + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentGradientsAndProportions(sx, sy, tx, ty); + + return points; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location) { + return self.pointAlongPathFrom(location, 0); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location) { + return segmentGradients[findSegmentForLocation(location)["index"]]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance) { + var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segmentLengths[s.index], m = segmentGradients[s.index]; + var e = { + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + return e; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = segmentGradients[p.segmentInfo.index]; + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + + }; + + }; + + // ********************************* END OF CONNECTOR TYPES ******************************************************************* + + // ********************************* ENDPOINT TYPES ******************************************************************* + + /** + * Class: Endpoints.Dot + * A round endpoint, with default radius 10 pixels. + */ + + /** + * Function: Constructor + * + * Parameters: + * + * radius - radius of the endpoint. defaults to 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + this.type = "Dot"; + var self = this; + params = params || {}; + this.radius = params.radius || 10; + this.defaultOffset = 0.5 * this.radius; + this.defaultInnerRadius = this.radius / 3; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var r = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - r; + var y = anchorPoint[1] - r; + return [ x, y, r * 2, r * 2, r ]; + }; + }; + + /** + * Class: Endpoints.Rectangle + * A Rectangular Endpoint, with default size 20x20. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the endpoint. defaults to 20 pixels. + * height - height of the endpoint. defaults to 20 pixels. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + this.type = "Rectangle"; + var self = this; + params = params || {}; + this.width = params.width || 20; + this.height = params.height || 20; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + return [ x, y, width, height]; + }; + }; + + /** + * Class: Endpoints.Image + * Draws an image as the Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * src - location of the image to use. + */ + jsPlumb.Endpoints.Image = function(params) { + + this.type = "Image"; + jsPlumb.DOMElementComponent.apply(this, arguments); + + var self = this, initialized = false; + this.img = new Image(); + self.ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.src || params.url; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + self.anchorPoint = anchorPoint; + if (self.ready) return [anchorPoint[0] - self.img.width / 2, anchorPoint[1] - self.img.height/ 2, self.img.width, self.img.height]; + else return [0,0,0,0]; + }; + + self.canvas = document.createElement("img"), initialized = false; + self.canvas.style["margin"] = 0; + self.canvas.style["padding"] = 0; + self.canvas.style["outline"] = 0; + self.canvas.style["position"] = "absolute"; + self.canvas.className = jsPlumb.endpointClass; + jsPlumb.appendElement(self.canvas, params.parent); + self.attachListeners(self.canvas, self); + + var actuallyPaint = function(d, style, anchor) { + if (!initialized) { + self.canvas.setAttribute("src", self.img.src); + initialized = true; + } + var width = self.img.width, + height = self.img.height, + x = self.anchorPoint[0] - (width/2), + y = self.anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(self.canvas, x, y, width, height); + }; + + this.paint = function(d, style, anchor) { + if (self.ready) { + actuallyPaint(d, style, anchor); + } + else { + window.setTimeout(function() { + self.paint(d, style, anchor); + }, 200); + } + }; + }; + + /** + * Class: Endpoints.Blank + * An Endpoint that paints nothing on the screen, and cannot be interacted with using the mouse. There are no constructor parameters for this Endpoint. + */ + jsPlumb.Endpoints.Blank = function(params) { + var self = this; + this.type = "Blank"; + jsPlumb.DOMElementComponent.apply(this, arguments); + this.compute = function() { + return [0,0,10,0]; + }; + + self.canvas = document.createElement("div"); + self.canvas.style.display = "block"; + self.canvas.style.width = "1px"; + self.canvas.style.height = "1px"; + self.canvas.style.background = "transparent"; + self.canvas.style.position = "absolute"; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function() { }; + }; + + /** + * Class: Endpoints.Triangle + * A triangular Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the triangle's base. defaults to 55 pixels. + * height - height of the triangle from base to apex. defaults to 55 pixels. + */ + jsPlumb.Endpoints.Triangle = function(params) { + this.type = "Triangle"; + params = params || { }; + params.width = params.width || 55; + param.height = params.height || 55; + this.width = params.width; + this.height = params.height; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + return [ x, y, width, height ]; + }; + }; +// ********************************* END OF ENDPOINT TYPES ******************************************************************* + + +// ********************************* OVERLAY DEFINITIONS *********************************************************************** + + /** + * Class: Overlays.Arrow + * + * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length + * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction + * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line + * across the tail. + */ + /** + * Function: Constructor + * + * Parameters: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + this.type = "Arrow"; + params = params || {}; + var self = this; + this.length = params.length || 20; + this.width = params.width || 20; + this.id = params.id; + this.connection = params.connection; + var direction = (params.direction || 1) < 0 ? -1 : 1; + var paintStyle = params.paintStyle || { lineWidth:1 }; + this.loc = params.location == null ? 0.5 : params.location; + // how far along the arrow the lines folding back in come to. default is 62.3%. + var foldback = params.foldback || 0.623; + var _getFoldBackPoint = function(connector, loc) { + if (foldback == 0.5) return connector.pointOnPath(loc); + else { + var adj = 0.5 - foldback; // we calculate relative to the center + return connector.pointAlongPathFrom(loc, direction * self.length * adj); + } + }; + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + // this is the arrow head position + var hxy = connector.pointAlongPathFrom(self.loc, direction * (self.length / 2)); + // this is the center of the tail + var txy = connector.pointAlongPathFrom(self.loc, -1 * direction * (self.length / 2)), tx = txy.x, ty = txy.y; + // this is the tail vector + var tail = connector.perpendicularToPathAt(self.loc, self.width, -1 * direction * (self.length / 2)); + // this is the point the tail goes in to + var cxy = _getFoldBackPoint(connector, self.loc); + + // if loc = 1, then hxy should be flush with the element, or if direction == -1, the tail midpoint. + if (self.loc == 1) { + var lxy = connector.pointOnPath(self.loc); + // TODO determine why the 1.2.6 released version does not + // use 'direction' in the two equations below, yet both + // that and 1.3.0 still paint the arrows correctly. + var dx = (lxy.x - hxy.x) * direction, dy = (lxy.y - hxy.y) * direction; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + // if loc = 0, then tail midpoint should be flush with the element, or, if direction == -1, hxy should be. + if (self.loc == 0) { + var lxy = connector.pointOnPath(self.loc); + var tailMid = foldback > 1 ? cxy : { + x:tail[0].x + ((tail[1].x - tail[0].x) / 2), + y:tail[0].y + ((tail[1].y - tail[0].y) / 2) + }; + var dx = (lxy.x - tailMid.x) * direction, dy = (lxy.y - tailMid.y) * direction; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x); + var maxx = Math.max(hxy.x, tail[0].x, tail[1].x); + var miny = Math.min(hxy.y, tail[0].y, tail[1].y); + var maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + var d = { hxy:hxy, tail:tail, cxy:cxy }, + strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle, + fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle, + lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth; + + self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions); + + return [ minx, maxx, miny, maxy]; + }; + }; + + /** + * Class: Overlays.PlainArrow + * + * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "PlainArrow"; + }; + + /** + * Class: Overlays.Diamond + * + * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40; + var p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "Diamond"; + }; + + + + /** + * Class: Overlays.Label + * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb + * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter + * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it + * puts on the Label's 'style' attribute, so the end result is the same. + */ + /** + * Function: Constructor + * + * Parameters: + * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes + * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle. + * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * labelStyle - (deprecated) js object containing style instructions for the label. defaults to jsPlumb.Defaults.LabelStyle. + * borderWidth - (deprecated) width of a border to paint. defaults to zero. + * borderStyle - (deprecated) strokeStyle to use when painting the border, if necessary. + * + */ + jsPlumb.Overlays.Label = function(params) { + this.type = "Label"; + jsPlumb.DOMElementComponent.apply(this, arguments); + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.labelStyle.font = this.labelStyle.font || "12px sans-serif"; + this.label = params.label || "banana"; + this.connection = params.connection; + this.id = params.id; + var self = this; + var labelWidth = null, labelHeight = null, labelText = null, labelPadding = null; + this.location = params.location || 0.5; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var initialised = false, + labelText = null, + div = document.createElement("div"); + div.style["position"] = "absolute"; + div.style["font"] = self.labelStyle.font; + div.style["color"] = self.labelStyle.color || "black"; + if (self.labelStyle.fillStyle) div.style["background"] = self.labelStyle.fillStyle;//_convertStyle(self.labelStyle.fillStyle, true); + if (self.labelStyle.borderWidth > 0) { + var dStyle = self.labelStyle.borderStyle ? self.labelStyle.borderStyle/*_convertStyle(self.labelStyle.borderStyle, true)*/ : "black"; + div.style["border"] = self.labelStyle.borderWidth + "px solid " + dStyle; + } + if (self.labelStyle.padding) div.style["padding"] = self.labelStyle.padding; + + var clazz = params["_jsPlumb"].overlayClass + " " + + (self.labelStyle.cssClass ? self.labelStyle.cssClass : + params.cssClass ? params.cssClass : ""); + + div.className = clazz; + + jsPlumb.appendElement(div, params.connection.parent); + jsPlumb.getId(div); + self.attachListeners(div, self); + this.paint = function(connector, d, connectorDimensions) { + if (!initialised) { + connector.appendDisplayElement(div); + self.attachListeners(div, connector); + initialised = true; + } + div.style.left = (connectorDimensions[0] + d.minx) + "px"; + div.style.top = (connectorDimensions[1] + d.miny) + "px"; + }; + + this.getTextDimensions = function(connector) { + labelText = typeof self.label == 'function' ? self.label(self) : self.label; + div.innerHTML = labelText.replace(/\r\n/g, "
"); + var de = jsPlumb.CurrentLibrary.getElementObject(div), + s = jsPlumb.CurrentLibrary.getSize(de); + return {width:s[0], height:s[1]}; + }; + + this.computeMaxSize = function(connector) { + var td = self.getTextDimensions(connector); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + var td = self.getTextDimensions(connector); + if (td.width != null) { + var cxy = connector.pointOnPath(self.location); + + var minx = cxy.x - (td.width / 2); + var miny = cxy.y - (td.height / 2); + + self.paint(connector, { + minx:minx, + miny:miny, + td:td, + cxy:cxy + }, connectorDimensions); + + return [minx, minx+td.width, miny, miny+td.height]; + } + else return [0,0,0,0]; + }; + }; + + // ********************************* END OF OVERLAY DEFINITIONS *********************************************************************** + + // ********************************* OVERLAY CANVAS RENDERERS*********************************************************************** + + // ********************************* END OF OVERLAY CANVAS RENDERERS *********************************************************************** +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the VML renderers. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +;(function() { + + // http://ajaxian.com/archives/the-vml-changes-in-ie-8 + // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/ + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + + var vmlAttributeMap = { + "stroke-linejoin":"joinstyle", + "joinstyle":"joinstyle", + "endcap":"endcap", + "miterlimit":"miterlimit" + }; + + if (document.createStyleSheet) { + + // this is the style rule for IE7/6: it uses a CSS class, tidy. + document.createStyleSheet().addRule(".jsplumb_vml", "behavior:url(#default#VML);position:absolute;"); + + // these are for VML in IE8. you have to explicitly call out which elements + // you're going to expect to support VML! + // + // try to avoid IE8. it is recommended you set X-UA-Compatible="IE=7" if you can. + // + document.createStyleSheet().addRule("jsplumb\\:textbox", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:oval", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:rect", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:stroke", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:shape", "behavior:url(#default#VML);position:absolute;"); + + // in this page it is also mentioned that IE requires the extra arg to the namespace + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either. + // var iev = document.documentMode; + //if (!iev || iev < 8) + document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); + //else + // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML"); + } + + var scale = 1000, + _atts = function(o, atts) { + for (var i in atts) { + // IE8 fix: setattribute does not work after an element has been added to the dom! + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + //o.setAttribute(i, atts[i]); + o[i] = atts[i]; + } + }, + _node = function(name, d, atts) { + atts = atts || {}; + var o = document.createElement("jsplumb:" + name); + o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml"; + _pos(o, d); + _atts(o, atts); + return o; + }, + _pos = function(o,d) { + o.style.left = d[0] + "px"; + o.style.top = d[1] + "px"; + o.style.width= d[2] + "px"; + o.style.height= d[3] + "px"; + o.style.position = "absolute"; + }, + _conv = function(v) { + return Math.floor(v * scale); + }, + _convertStyle = function(s, ignoreAlpha) { + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + + return o; + }, + _applyStyles = function(node, style, component) { + var styleToWrite = {}; + if (style.strokeStyle) { + styleToWrite["stroked"] = "true"; + styleToWrite["strokecolor"] =_convertStyle(style.strokeStyle, true); + styleToWrite["strokeweight"] = style.lineWidth + "px"; + } + else styleToWrite["stroked"] = "false"; + + if (style.fillStyle) { + styleToWrite["filled"] = "true"; + styleToWrite["fillcolor"] = _convertStyle(style.fillStyle, true); + } + else styleToWrite["filled"] = "false"; + + if(style["dashstyle"]) { + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = style["dashstyle"]; + } + else if (style["stroke-dasharray"] && style["lineWidth"]) { + var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",", + parts = style["stroke-dasharray"].split(sep), + styleToUse = ""; + for(var i = 0; i < parts.length; i++) { + styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep); + } + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = styleToUse; + } + + _atts(node, styleToWrite); + }, + /* + * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. + */ + VmlComponent = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }, + /* + * Base class for Vml connectors. extends VmlComponent. + */ + VmlConnector = function(params) { + var self = this; + self.strokeNode = null; + self.canvas = null; + VmlComponent.apply(this, arguments); + clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : ""); + this.paint = function(d, style, anchor) { + if (style != null) { + var path = self.getPath(d), p = { "path":path }; + + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + outlineStyle = { + strokeStyle:_convertStyle(style.outlineColor), + lineWidth:outlineStrokeWidth + }; + + if (self.bgCanvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.bgCanvas = _node("shape", d, p); + jsPlumb.appendElement(self.bgCanvas, params.parent); + _pos(self.bgCanvas, d); + displayElements.push(self.bgCanvas); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.bgCanvas, d); + _atts(self.bgCanvas, p); + } + + _applyStyles(self.bgCanvas, outlineStyle, self); + } + + if (self.canvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.canvas = _node("shape", d, p); + jsPlumb.appendElement(self.canvas, params.parent); + displayElements.push(self.canvas); + + self.attachListeners(self.canvas, self); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.canvas, d); + _atts(self.canvas, p); + } + + _applyStyles(self.canvas, style, self); + } + }; + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + self.canvas.parentNode.appendChild(el); + displayElements.push(el); + }; + }, + /* + * + * Base class for Vml Endpoints. extends VmlComponent. + * + */ + VmlEndpoint = function(params) { + VmlComponent.apply(this, arguments); + var vml = null, self = this; + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function(d, style, anchor) { + var p = { }; + + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (vml == null) { + p["class"] = jsPlumb.endpointClass; + vml = self.getVml([0,0, d[2], d[3]], p, anchor); + self.canvas.appendChild(vml); + self.attachListeners(vml, self); + } + else { + //p["coordsize"] = "1,1";//(d[2] * scale) + "," + (d[3] * scale); again, unsure. + _pos(vml, [0,0, d[2], d[3]]); + _atts(vml, p); + } + + _applyStyles(vml, style); + }; + }; + + jsPlumb.Connectors.vml.Bezier = function() { + jsPlumb.Connectors.Bezier.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Straight = function() { + jsPlumb.Connectors.Straight.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Flowchart = function() { + jsPlumb.Connectors.Flowchart.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l"; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]); + } + // finally draw a line to the end + p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e"; + return p; + }; + }; + + jsPlumb.Endpoints.vml.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("oval", d, atts); }; + }; + + jsPlumb.Endpoints.vml.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("rect", d, atts); }; + }; + + /* + * VML Image Endpoint is the same as the default image endpoint. + */ + jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image; + + /** + * placeholder for Blank endpoint in vml renderer. + */ + jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank; + + /** + * VML Label renderer. uses the default label renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label; + + var AbstractVmlArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + VmlComponent.apply(this, arguments); + var self = this, canvas = null, path =null; + var getPath = function(d, connectorDimensions) { + return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) + + " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + + " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + + " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + + " x e"; + }; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) { + var p = {}; + if (strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] =_convertStyle(strokeStyle, true); + } + if (lineWidth) p["strokeweight"] = lineWidth + "px"; + if (fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = fillStyle; + } + var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + w = Math.abs(xmax - xmin), + h = Math.abs(ymax - ymin), + dim = [xmin, ymin, w, h]; + + // for VML, we create overlays using shapes that have the same dimensions and + // coordsize as their connector - overlays calculate themselves relative to the + // connector (it's how it's been done since the original canvas implementation, because + // for canvas that makes sense). + p["path"] = getPath(d, connectorDimensions); + p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale); + + dim[0] = connectorDimensions[0]; + dim[1] = connectorDimensions[1]; + dim[2] = connectorDimensions[2]; + dim[3] = connectorDimensions[3]; + + if (canvas == null) { + //p["class"] = jsPlumb.overlayClass; // TODO currentInstance? + canvas = _node("shape", dim, p); + connector.appendDisplayElement(canvas); + self.attachListeners(canvas, connector); + } + else { + _pos(canvas, dim); + _atts(canvas, p); + } + }; + }; + + jsPlumb.Overlays.vml.Arrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.vml.PlainArrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.vml.Diamond = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the SVG renderers. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +/** + * SVG support for jsPlumb. + * + * things to investigate: + * + * gradients: https://developer.mozilla.org/en/svg_in_html_introduction + * css:http://tutorials.jenkov.com/svg/svg-and-css.html + * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath + * pointer events: https://developer.mozilla.org/en/css/pointer-events + * + */ +;(function() { + + var svgAttributeMap = { + "stroke-linejoin":"stroke-linejoin", + "joinstyle":"stroke-linejoin", + "stroke-dashoffset":"stroke-dashoffset" + }; + + var ns = { + svg:"http://www.w3.org/2000/svg", + xhtml:"http://www.w3.org/1999/xhtml" + }, + _attr = function(node, attributes) { + for (var i in attributes) + node.setAttribute(i, "" + attributes[i]); + }, + _node = function(name, attributes) { + var n = document.createElementNS(ns.svg, name); + attributes = attributes || {}; + attributes["version"] = "1.1"; + attributes["xmnls"] = ns.xhtml; + _attr(n, attributes); + return n; + }, + _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; }, + _convertStyle = function(s, ignoreAlpha) { + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + + return o; + }, + _clearGradient = function(parent) { + for (var i = 0; i < parent.childNodes.length; i++) { + if (parent.childNodes[i].tagName == "linearGradient" || parent.childNodes[i].tagName == "radialGradient") + parent.removeChild(parent.childNodes[i]); + } + }, + _updateGradient = function(parent, node, style, dimensions) { + var id = "jsplumb_gradient_" + (new Date()).getTime(); + // first clear out any existing gradient + _clearGradient(parent); + // this checks for an 'offset' property in the gradient, and in the absence of it, assumes + // we want a linear gradient. if it's there, we create a radial gradient. + // it is possible that a more explicit means of defining the gradient type would be + // better. relying on 'offset' means that we can never have a radial gradient that uses + // some default offset, for instance. + if (!style.gradient.offset) { + var g = _node("linearGradient", {id:id}); + parent.appendChild(g); + } + else { + var g = _node("radialGradient", { + id:id + }); + parent.appendChild(g); + } + + // the svg radial gradient seems to treat stops in the reverse + // order to how canvas does it. so we want to keep all the maths the same, but + // iterate the actual style declarations in reverse order, if the x indexes are not in order. + for (var i = 0; i < style.gradient.stops.length; i++) { + // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of + // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments. + // so, not too concerned about leaving this in for now. + var styleToUse = i; + if (dimensions.length == 8) + styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i; + else + styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i; + var stopColor = _convertStyle(style.gradient.stops[styleToUse][1], true); + var s = _node("stop", {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor}); + g.appendChild(s); + } + var applyGradientTo = style.strokeStyle ? "stroke" : "fill"; + node.setAttribute("style", applyGradientTo + ":url(#" + id + ")"); + }, + _applyStyles = function(parent, node, style, dimensions) { + + if (style.gradient) { + _updateGradient(parent, node, style, dimensions); + } + else { + // make sure we clear any existing gradient + _clearGradient(parent); + node.setAttribute("style", ""); + } + + node.setAttribute("fill", style.fillStyle ? _convertStyle(style.fillStyle, true) : "none"); + node.setAttribute("stroke", style.strokeStyle ? _convertStyle(style.strokeStyle, true) : "none"); + if (style.lineWidth) { + node.setAttribute("stroke-width", style.lineWidth); + } + + // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like + // the syntax in VML but is actually kind of nasty: values are given in the pixel + // coordinate space, whereas in VML they are multiples of the width of the stroked + // line, which makes a lot more sense. for that reason, jsPlumb is supporting both + // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from + // VML, which will be the preferred method. the code below this converts a dashstyle + // attribute given in terms of stroke width into a pixel representation, by using the + // stroke's lineWidth. + if(style["stroke-dasharray"]) { + node.setAttribute("stroke-dasharray", style["stroke-dasharray"]); + } + if (style["dashstyle"] && style["lineWidth"]) { + var sep = style["dashstyle"].indexOf(",") == -1 ? " " : ",", + parts = style["dashstyle"].split(sep), + styleToUse = ""; + parts.forEach(function(p) { + styleToUse += (Math.floor(p * style.lineWidth) + sep); + }); + node.setAttribute("stroke-dasharray", styleToUse); + } + + // extra attributes such as join type, dash offset. + for (var i in svgAttributeMap) { + if (style[i]) { + node.setAttribute(svgAttributeMap[i], style[i]); + } + } + }, + _decodeFont = function(f) { + var r = /([0-9].)(p[xt])\s(.*)/; + var bits = f.match(r); + return {size:bits[1] + bits[2], font:bits[3]}; + }; + + /* + * Base class for SVG components. + */ + var SvgComponent = function(cssClass, originalArgs, pointerEventsSpec) { + var self = this; + pointerEventsSpec = pointerEventsSpec || "all"; + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + self.canvas = null, self.path = null, self.svg = null; + + this.setHover = function() { }; + + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.sizeCanvas(self.canvas,0,0,1,1); + + var clazz = cssClass + " " + (originalArgs[0].cssClass || ""); + self.canvas.className = clazz; + + self.svg = _node("svg", { + "style":"", + "width":0, + "height":0, + "pointer-events":pointerEventsSpec/*, + "class": clazz*/ + }); + + jsPlumb.appendElement(self.canvas, originalArgs[0]["parent"]); + self.canvas.appendChild(self.svg); + + // TODO this displayElement stuff is common between all components, across all + // renderers. would be best moved to jsPlumbUIComponent. + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + this.paint = function(d, style, anchor) { + if (style != null) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + _attr(self.svg, { + "style":_pos([0,0,d[2], d[3]]), + "width": d[2], + "height": d[3] + }); + self._paint.apply(this, arguments); + } + }; + }; + + /* + * Base class for SVG connectors. + */ + var SvgConnector = function(params) { + var self = this; + SvgComponent.apply(this, [ params["_jsPlumb"].connectorClass, arguments, "none" ]); + this._paint = function(d, style) { + var p = self.getPath(d), a = { "d":p }, outlineStyle = null; + a["pointer-events"] = "all"; + + // outline style. actually means drawing an svg object underneath the main one. + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + outlineStyle = { + strokeStyle:_convertStyle(style.outlineColor), + lineWidth:outlineStrokeWidth + }; + + if (self.bgPath == null) { + self.bgPath = _node("path", a); + self.svg.appendChild(self.bgPath); + self.attachListeners(self.bgPath, self); + } + else { + _attr(self.bgPath, a); + } + + _applyStyles(self.svg, self.bgPath, outlineStyle, d); + } + + + if (self.path == null) { + self.path = _node("path", a); + self.svg.appendChild(self.path); + self.attachListeners(self.path, self); + } + else { + _attr(self.path, a); + } + + _applyStyles(self.svg, self.path, style, d); + }; + }; + + /* + * SVG Bezier Connector + */ + jsPlumb.Connectors.svg.Bezier = function(params) { + jsPlumb.Connectors.Bezier.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]; }; + }; + + /* + * SVG straight line Connector + */ + jsPlumb.Connectors.svg.Straight = function(params) { + jsPlumb.Connectors.Straight.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; }; + }; + + jsPlumb.Connectors.svg.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "M " + dimensions[4] + "," + dimensions[5]; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)]; + } + // finally draw a line to the end + p = p + " " + dimensions[6] + "," + dimensions[7]; + return p; + }; + }; + + /* + * Base class for SVG endpoints. + */ + var SvgEndpoint = function(params) { + var self = this; + SvgComponent.apply(this, [ params["_jsPlumb"].endpointClass, arguments, "all" ]); + this._paint = function(d, style) { + var s = jsPlumb.extend({}, style); + if (s.outlineColor) { + s.strokeWidth = s.outlineWidth; + s.strokeStyle = _convertStyle(s.outlineColor, true); + } + + if (self.node == null) { + self.node = self.makeNode(d, s); + self.svg.appendChild(self.node); + self.attachListeners(self.node, self); + } + _applyStyles(self.svg, self.node, s, d); + _pos(self.node, d); + }; + }; + + /* + * SVG Dot Endpoint + */ + jsPlumb.Endpoints.svg.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("circle", { + "cx" : d[2] / 2, + "cy" : d[3] / 2, + "r" : d[2] / 2 + }); + }; + }; + + /* + * SVG Rectangle Endpoint + */ + jsPlumb.Endpoints.svg.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("rect", { + "width":d[2], + "height":d[3] + }); + }; + }; + + /* + * SVG Image Endpoint is the default image endpoint. + */ + jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image; + /* + * Blank endpoint in svg renderer is the default Blank endpoint. + */ + jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank; + /* + * Label endpoint in svg renderer is the default Label endpoint. + */ + jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label; + + + var AbstractSvgArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + var self = this, path =null; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + } + + _attr(path, { + "d" : makePath(d), + stroke : strokeStyle ? strokeStyle : null, + fill : fillStyle ? fillStyle : null + }); + }; + var makePath = function(d) { + return "M" + d.hxy.x + "," + d.hxy.y + + " L" + d.tail[0].x + "," + d.tail[0].y + + " L" + d.cxy.x + "," + d.cxy.y + + " L" + d.tail[1].x + "," + d.tail[1].y + + " L" + d.hxy.x + "," + d.hxy.y; + }; + }; + + jsPlumb.Overlays.svg.Arrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.svg.PlainArrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.svg.Diamond = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the HTML5 canvas renderers. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +;(function() { + +// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS ******************************************************************* + + // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. + var _connectionBeingDragged = null, + _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }, + _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }, + _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }, + _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, + _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }, + _setOffset = function(el, o) { jsPlumb.CurrentLibrary.setOffset(el, o); }; + + /* + * Class:CanvasMouseAdapter + * Provides support for mouse events on canvases. + */ + var CanvasMouseAdapter = function() { + var self = this; + self.overlayPlacements = []; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + jsPlumb.EventGenerator.apply(this, arguments); + /** + * returns whether or not the given event is ojver a painted area of the canvas. + */ + this._over = function(e) { + var o = _getOffset(_getElementObject(self.canvas)), + pageXY = _pageXY(e), + x = pageXY[0] - o.left, y = pageXY[1] - o.top; + if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + return false; + }; + + var _mouseover = false; + var _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false; + var _nullSafeHasClass = function(el, clazz) { + return el != null && _hasClass(el, clazz); + }; + this.mousemove = function(e) { + var pageXY = _pageXY(e), clientXY = _clientXY(e), + ee = document.elementFromPoint(clientXY[0], clientXY[1]), + eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); + var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); + if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.fire("mouseenter", self, e); + return true; + } + // TODO here there is a remote chance that the overlay the mouse moved onto + // is actually not an overlay for the current component. a more thorough check would + // be to ensure the overlay belonged to the current component. + else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { + _mouseover = false; + self.fire("mouseexit", self, e); + } + self.fire("mousemove", self, e); + }; + + this.click = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); + self.fire("mousedown", self, e); + } + }; + + this.mouseup = function(e) { + //if (self == _connectionBeingDragged) _connectionBeingDragged = null; + _mouseDown = false; + self.fire("mouseup", self, e); + }; + }; + + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + jsPlumb.appendElement(canvas, params.parent); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + params["_jsPlumb"].getId(canvas, params.uuid); + + return canvas; + }; + + /** + * Class:CanvasConnector + * Superclass for Canvas Connector renderers. + */ + var CanvasConnector = jsPlumb.CanvasConnector = function(params) { + + CanvasMouseAdapter.apply(this, arguments); + + var _paintOneStyle = function(dim, aStyle) { + self.ctx.save(); + jsPlumb.extend(self.ctx, aStyle); + if (aStyle.gradient) { + var g = self.createGradient(dim, self.ctx); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + self.ctx.strokeStyle = g; + } + self._paint(dim); + self.ctx.restore(); + }; + + var self = this, + clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent + }); + self.ctx = self.canvas.getContext("2d"); + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + self.paint = function(dim, style) { + if (style != null) { + + jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); + + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + _paintOneStyle(dim, outlineStyle); + } + _paintOneStyle(dim, style); + } + }; + }; + + /** + * Class:CanvasEndpoint + * Superclass for Canvas Endpoint renderers. + */ + var CanvasEndpoint = function(params) { + var self = this; + CanvasMouseAdapter.apply(this, arguments); + var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent + }); + self.ctx = self.canvas.getContext("2d"); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + // _paintOneStyle(d, outlineStyle); + } + + self._paint.apply(this, arguments); + }; + }; + + jsPlumb.Endpoints.canvas.Dot = function(params) { + var self = this; + jsPlumb.Endpoints.Dot.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }; + var calculateAdjustments = function(gradient) { + var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius; + gradient.offset && (offsetAdjustment = parseValue(gradient.offset)); + gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius)); + return [offsetAdjustment, innerRadius]; + }; + this._paint = function(d, style, anchor) { + if (style != null) { + var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(); + jsPlumb.extend(ctx, style); + if (style.gradient) { + var adjustments = calculateAdjustments(style.gradient), + yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0], + xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0], + g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + ctx.beginPath(); + ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + } + }; + }; + + jsPlumb.Endpoints.canvas.Rectangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) { + + var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(); + jsPlumb.extend(ctx, style); + + /* canvas gradient */ + if (style.gradient) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, d[2], d[3]); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + jsPlumb.Endpoints.canvas.Triangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Triangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) + { + var width = d[2], height = d[3], x = d[0], y = d[1]; + + var ctx = self.canvas.getContext('2d'); + var offsetX = 0, offsetY = 0, angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = style.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + /* + * Canvas Image Endpoint: uses the default version, which creates an tag. + */ + jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image; + + /* + * Blank endpoint in all renderers is just the default Blank endpoint. + */ + jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank; + + /* + * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Bezier = function() { + var self = this; + jsPlumb.Connectors.Bezier.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO i doubt this handles the case that source and target are swapped. + this.createGradient = function(dim, ctx, swap) { + return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + /* + * Canvas straight line Connector. Draws a straight line onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Straight = function() { + var self = this; + jsPlumb.Connectors.Straight.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO this does not handle the case that src and target are swapped. + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + jsPlumb.Connectors.canvas.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + +// ********************************* END OF CANVAS RENDERERS ******************************************************************* + + jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label; + + /** + * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. + */ + var CanvasOverlay = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }; + + var AbstractCanvasArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + CanvasOverlay.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + var ctx = connector.ctx; + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(d.hxy.x, d.hxy.y); + ctx.lineTo(d.tail[0].x, d.tail[0].y); + ctx.lineTo(d.cxy.x, d.cxy.y); + ctx.lineTo(d.tail[1].x, d.tail[1].y); + ctx.lineTo(d.hxy.x, d.hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + }; + + jsPlumb.Overlays.canvas.Arrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.canvas.PlainArrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.canvas.Diamond = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the jQuery adapter. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ +/* + * the library specific functions, such as find offset, get id, get attribute, extend etc. + * the full list is: + * + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getDropScope gets the drop scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getPageXY gets the page event's xy location. + * getParent gets the parent of some element. + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * hasClass returns whether or not the given element has the given class. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + */ +(function($) { + + //var getBoundingClientRectSupported = "getBoundingClientRect" in document.documentElement; + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * appends the given child to the given parent. + */ + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).append(child); + }, + + /** + * event binding wrapper. it just so happens that jQuery uses 'bind' also. yui3, for example, + * uses 'on'. + */ + bind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.bind(event, callback); + }, + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step', + 'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete' + }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + getDocumentElement : function() { return document; }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + }, + + getDragScope : function(el) { + return el.draggable("option", "scope"); + }, + + getDropScope : function(el) { + return el.droppable("option", "scope"); + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + * + */ + getElementObject : function(el) { + return typeof(el)=='string' ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).parent(); + }, + + getScrollLeft : function(el) { + return el.scrollLeft(); + }, + + getScrollTop : function(el) { + return el.scrollTop(); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + + // this code is a workaround for the case that the element being dragged has a margin set on it. jquery UI passes + // in the wrong offset if the element has a margin (it doesn't take the margin into account). the getBoundingClientRect + // method, which is in pretty much all browsers now, reports the right numbers. but it introduces a noticeable lag, which + // i don't like. + + /*if ( getBoundingClientRectSupported ) { + var r = eventArgs[1].helper[0].getBoundingClientRect(); + return { left : r.left, top: r.top }; + } else {*/ + var ui = eventArgs[1], _offset = ui.offset; + return _offset || ui.absolutePosition; + //} + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options) { + // remove helper directive if present. + options.helper = null; + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.draggable(options); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.droppable(options); + }, + + isAlreadyDraggable : function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return el.hasClass("ui-draggable"); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).remove(); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * sets the drag scope. probably time for a setDragOption method (roll this and the one above together) + * @param el + * @param scope + */ + setDragScope : function(el, scope) { + el.draggable("option", "scope", scope); + }, + + setOffset : function(el, o) { + jsPlumb.CurrentLibrary.getElementObject(el).offset(o); + } + }; + + $(document).ready(jsPlumb.init); + +})(jQuery); +(function(){if(typeof Math.sgn=="undefined")Math.sgn=function(a){return a==0?0:a>0?1:-1};var p={subtract:function(a,b){return{x:a.x-b.x,y:a.y-b.y}},dotProduct:function(a,b){return a.x*b.x+a.y*b.y},square:function(a){return Math.sqrt(a.x*a.x+a.y*a.y)},scale:function(a,b){return{x:a.x*b,y:a.y*b}}},y=Math.pow(2,-65),u=function(a,b){for(var g=[],d=b.length-1,h=2*d-1,f=[],c=[],l=[],k=[],i=[[1,0.6,0.3,0.1],[0.4,0.6,0.6,0.4],[0.1,0.3,0.6,1]],e=0;e<=d;e++)f[e]=p.subtract(b[e],a);for(e=0;e<=d-1;e++){c[e]= +p.subtract(b[e+1],b[e]);c[e]=p.scale(c[e],3)}for(e=0;e<=d-1;e++)for(var m=0;m<=d;m++){l[e]||(l[e]=[]);l[e][m]=p.dotProduct(c[e],f[m])}for(e=0;e<=h;e++){k[e]||(k[e]=[]);k[e].y=0;k[e].x=parseFloat(e)/h}h=d-1;for(f=0;f<=d+h;f++){c=Math.min(f,d);for(e=Math.max(0,f-h);e<=c;e++){j=f-e;k[e+j].y+=l[j][e]*i[j][e]}}d=b.length-1;k=s(k,2*d-1,g,0);h=p.subtract(a,b[0]);l=p.square(h);for(e=i=0;e=64){g[0]=(a[0].x+a[b].x)/2;return 1}var n,o,q;k=a[0].y-a[b].y;i=a[b].x-a[0].x;e=a[0].x*a[b].y-a[b].x*a[0].y;m=max_distance_below=0;for(o=1;om)m=q;else if(q0?1:-1,c=null;h endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + endpointsByElement = {}, + endpointsByUUID = {}, + offsets = {}, + offsetTimestamps = {}, + floatingConnections = {}, + draggableStates = {}, + _mouseEventsEnabled = this.Defaults.MouseEventsEnabled, + _draggableByDefault = true, + canvasList = [], + sizes = [], + listeners = {}, // a map: keys are event types, values are lists of listeners. + DEFAULT_SCOPE = this.Defaults.Scope, + renderMode = null, // will be set in init() + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }, + + /** + * appends an element to some other element, which is calculated as follows: + * + * 1. if jsPlumb.Defaults.Container exists, use that element. + * 2. if the 'parent' parameter exists, use that. + * 3. otherwise just use the document body. + * + */ + _appendElement = function(el, parent) { + if (_currentInstance.Defaults.Container) + jsPlumb.CurrentLibrary.appendElement(el, _currentInstance.Defaults.Container); + else if (!parent) + document.body.appendChild(el); + else + jsPlumb.CurrentLibrary.appendElement(el, parent); + }, + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + _timestamp = function() { return "" + (new Date()).getTime(); }, + + /** + * YUI, for some reason, put the result of a Y.all call into an object that contains + * a '_nodes' array, instead of handing back an array-like object like the other + * libraries do. + */ + _convertYUICollection = function(c) { + return c._nodes ? c._nodes : c; + }, + + /** + * Draws an endpoint and its connections. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + _draw = function(element, ui, timestamp) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (!timestamp) timestamp = _timestamp(); + if (endpoints) { + _updateOffset( { elId : id, offset : ui, recalc : false, timestamp : timestamp }); // timestamp is checked against last update cache; it is + // valid for one paint cycle. + var myOffset = offsets[id], myWH = sizes[id]; + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + var l = endpoints[i].connections; + for ( var j = 0; j < l.length; j++) { + l[j].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); // ...paint each connection. + // then, check for dynamic endpoint; need to repaint it. + var oIdx = l[j].endpoints[0] == endpoints[i] ? 1 : 0, + otherEndpoint = l[j].endpoints[oIdx]; + if (otherEndpoint.anchor.isDynamic && !otherEndpoint.isFloating()) { + otherEndpoint.paint({ elementWithPrecedence:id }); + // all the connections for the other endpoint now need to be repainted + for (var k = 0; k < otherEndpoint.connections.length; k++) { + if (otherEndpoint.connections[k] !== l) + otherEndpoint.connections[k].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); + } + } + } + } + } + }, + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }, + + /** + * gets an Endpoint by uuid. + */ + _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }, + + /** + * inits a draggable if it's not already initialised. + */ + _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element) && !jsPlumb.CurrentLibrary.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + } + } + }, + + _newConnection = function(params) { + var connectionFunc = jsPlumb.Defaults.ConnectionType || Connection, + endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint, + parent = jsPlumb.CurrentLibrary.getParent; + + if (params.container) + params["parent"] = params.container; + else { + if (params.sourceEndpoint) + params["parent"] = params.sourceEndpoint.parent; + else if (params.source.constructor == endpointFunc) + params["parent"] = params.source.parent; + else params["parent"] = parent(params.source); + } + + params["_jsPlumb"] = _currentInstance; + var con = new connectionFunc(params); + _eventFireProxy("click", "click", con); + _eventFireProxy("dblclick", "dblclick", con); + return con; + }, + + _eventFireProxy = function(event, proxyEvent, obj) { + obj.bind(event, function(e) { + _currentInstance.fire(proxyEvent, obj, e); + }); + }, + + _newEndpoint = function(params) { + var endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint; + if (params.container) + params.parent = params.container; + else + params["parent"] = jsPlumb.CurrentLibrary.getParent(params.source); + params["_jsPlumb"] = _currentInstance, + ep = new endpointFunc(params); + _eventFireProxy("click", "endpointClick", ep); + _eventFireProxy("dblclick", "endpointDblClick", ep); + return ep; + }, + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + _operation = function(elId, func, endpointFunc) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + if (endpointFunc) endpointFunc(endpoints[i]); + } + } + }, + /** + * perform an operation on all elements. + */ + _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }, + + /** + * helper to remove an element from the DOM. + */ + _removeElement = function(element, parent) { + if (element != null && element.parentNode != null) { + element.parentNode.removeChild(element); + } + }, + /** + * helper to remove a list of elements from the DOM. + */ + _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }, + /** + * helper method to remove an item from a list. + */ + _removeFromList = function(map, key, value) { + if (key != null) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete (l[i]); + l.splice(i, 1); + return true; + } + } + } + return false; + }, + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }, + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + _setVisible = function(el, state, alsoChangeEndpoints) { + state = state === "block"; + var endpointFunc = null; + if (alsoChangeEndpoints) { + if (state) endpointFunc = function(ep) { + ep.setVisible(true, true, true); + }; + else endpointFunc = function(ep) { + ep.setVisible(false, true, true); + }; + } + var id = _getAttribute(el, "id"); + _operation(id, function(jpc) { + if (state && alsoChangeEndpoints) { + // this test is necessary because this functionality is new, and i wanted to maintain backwards compatibility. + // this block will only set a connection to be visible if the other endpoint in the connection is also visible. + var oidx = jpc.sourceId === id ? 1 : 0; + if (jpc.endpoints[oidx].isVisible()) jpc.setVisible(true); + } + else // the default behaviour for show, and what always happens for hide, is to just set the visibility without getting clever. + jpc.setVisible(state); + }, endpointFunc); + }, + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }, + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + _toggleVisible = function(elId, changeEndpoints) { + var endpointFunc = null; + if (changeEndpoints) { + endpointFunc = function(ep) { + var state = ep.isVisible(); + ep.setVisible(!state); + }; + } + _operation(elId, function(jpc) { + var state = jpc.isVisible(); + jpc.setVisible(!state); + }, endpointFunc); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }, + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return offsets[elId]; + } + if (recalc || offset == null) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + } + return offsets[elId]; + }, + +/** + * gets an id for the given element, creating and setting one if + * necessary. + */ + _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else + id = "jsPlumb_" + _timestamp(); + _setAttribute(ele, "id", id); + } + return id; + }, + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'jsPlumb function failed : ' + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'wrapped function failed : ' + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = "_jsPlumb_connector"; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = "_jsPlumb_endpoint"; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = "_jsPlumb_overlay"; + + this.Anchors = {}; + + this.Connectors = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Endpoints = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Overlays = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + +// ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***************************************** + /* + * Function: bind + * Bind to an event on jsPlumb. + * + * Parameters: + * event - the event to bind. Available events on jsPlumb are: + * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback. + * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback. + * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback. + * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback. + * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback. + * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback. + * + * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event. + */ + + /* + * Function: clearListeners + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + +// *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *********************************************************** + + /* + Function: addEndpoint + + Adds an to a given element or elements. + + Parameters: + + el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + params - Object containing Endpoint constructor arguments. For more information, see . + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some + shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in + this object are anything that 'params' can contain. See . + + Returns: + The newly created , if el referred to a single element. Otherwise, an array of newly created s. + + See Also: + + */ + this.addEndpoint = function(el, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + + // YUI wrapper + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + var _el = _getElementObject(inputs[i]), id = _getId(_el); + p.source = _el; + _updateOffset({ elId : id }); + var e = _newEndpoint(p); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + results.push(e); + } + + return results.length == 1 ? results[0] : results; + }; + + /* + Function: addEndpoints + Adds a list of s to a given element or elements. + + Parameters: + target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See 's constructor documentation. + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created s, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(el, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams); + if (e.constructor == Array) + Array.prototype.push.apply(results, e); + else results.push(e); + } + return results; + }; + + /* + Function: animate + This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating + the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // onComplete repaints, just to make sure everything looks good at the end of the animation. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /* + Function: connect + Establishes a between two elements (or s, which are themselves registered to elements). + + Parameters: + params - Object containing constructor arguments for the Connection. See 's constructor documentation. + referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of + Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection. + + Returns: + The newly created . + */ + this.connect = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + if (_p.target && !_p.target.endpoint) { + var tid = _getId(_p.target), + tep =_targetEndpointDefinitions[tid]; + if (tep) { + if (_p.endpoints) _p.endpoints[1] = tep; + else if (_p.endpoint) { + _p.endpoints = [ _p.endpoint, tep ]; + _p.endpoint = null; + } + else _p.endpoints = [ null, "Rectangle" ]; + } + } + + // dynamic anchors. backwards compatibility here: from 1.2.6 onwards you don't need to specify "dynamicAnchors". the fact that some anchor consists + // of multiple definitions is enough to tell jsPlumb you want it to be dynamic. + if (_p.dynamicAnchors) { + // these can either be an array of anchor coords, which we will use for both source and target, or an object with {source:[anchors], target:[anchors]}, in which + // case we will use a different set for each element. + var a = _p.dynamicAnchors.constructor == Array; + var sa = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.source)); + var ta = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.target)); + _p.anchors = [sa,ta]; + } + + var jpc = _newConnection(_p); + // add to list of connections (by scope). + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + _currentInstance.fire("jsPlumbConnection", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + // force a paint + _draw(jpc.source); + + return jpc; + }; + + /* + Function: deleteEndpoint + Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too) + + Parameters: + object - either an object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElement(endpoint.canvas, endpoint.parent); + // remove from endpointsbyElement + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + delete endpoint; + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every , and their associated s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc) { + _currentInstance.fire("jsPlumbConnectionDetached", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + }; + + /* + Function: detach + Detaches and then removes a . Takes either (source, target) (the old way, maintained for backwards compatibility), or a params + object with various possible values. + + Parameters: + source - id or element object of the first element in the Connection. + target - id or element object of the second element in the Connection. + params - a JS object containing the same parameters as you pass to jsPlumb.connect. If this is present then neither source nor + target should be present; it should be the only argument to the method. See the docs for 's constructor for information +about the parameters allowed in the params object. + Returns: + true if successful, false if not. + */ + this.detach = function(source, target) { + if (arguments.length == 2) { + var s = _getElementObject(source), sId = _getId(s); + var t = _getElementObject(target), tId = _getId(t); + _operation(sId, function(jpc) { + if ((jpc.sourceId == sId && jpc.targetId == tId) || (jpc.targetId == sId && jpc.sourceId == tId)) { + _removeElements(jpc.connector.getDisplayElements(), jpc.parent); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + // this is the new version of the method, taking a JS object like + // the connect method does. + else if (arguments.length == 1) { + // TODO investigate whether or not this code still works when a user has supplied their own subclass of Connection. i suspect it may not. + if (arguments[0].constructor == Connection) { + arguments[0].endpoints[0].detachFrom(arguments[0].endpoints[1]); + } + else if (arguments[0].connection) { + arguments[0].connection.endpoints[0].detachFrom(arguments[0].connection.endpoints[1]); + } + else { + var _p = jsPlumb.extend( {}, source); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1])); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source); + var targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElements(jpc.connector.getDisplayElements(), jpc.parent); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + } + } + }; + + /* + Function: detachAll + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + + Returns: + void + */ + this.detachAllConnections = function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + }; + + /** + * @deprecated Use detachAllConnections instead. this will be removed in jsPlumb 1.3. + */ + this.detachAll = this.detachAllConnections; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + /** + * @deprecated use detachEveryConnection instead. this will be removed in jsPlumb 1.3. + */ + this.detachEverything = this.detachEveryConnection; + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of you library's draggable method so that jsPlumb can setup the appropriate callbacks. Your underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method, + * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the + * default scope). If multiple scopes are passed in, the return value will be a map of { scope -> [ connection... ] }. + * + * Parameters + * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list + * of connections that are in the given scope. + * options - if the argument is a JS object, you can specify a finer-grained filter: + * + * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes. + * - *source* either a string representing an element id, or a selector. constrains the result to connections having this source. + * - *target* either a string representing an element id, or a selector. constrains the result to connections having this target. + * + */ + this.getConnections = function(options) { + if (!options) { + options = {}; + } else if (options.constructor == String) { + options = { "scope": options }; + } + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') + r.push(input); + else + r = input; + } + return r; + }; + var scope = options.scope || jsPlumb.getDefaultScope(), + scopes = prepareList(scope), + sources = prepareList(options.source), + targets = prepareList(options.target), + filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }, + results = scopes.length > 1 ? {} : [], + _addOne = function(scope, obj) { + if (scopes.length > 1) { + var ss = results[scope]; + if (ss == null) { + ss = []; results[scope] = ss; + } + ss.push(obj); + } else results.push(obj); + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + _addOne(i, c); + } + } + } + return results; + }; + + /* + * Function: getAllConnections + * Gets all connections, as a map of { scope -> [ connection... ] }. + */ + this.getAllConnections = function() { + return connectionsByScope; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /** + * Function:getEndpoints + * Gets the list of Endpoints for a given selector, or element id. + * @param el + * @return + */ + this.getEndpoints = function(el) { + return endpointsByElement[_getId(el)]; + }; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + + this.appendElement = _appendElement; + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + changeEndpoints - whether not to also hide endpoints on the element. by default this is false. + + Returns: + void + */ + this.hide = function(el, changeEndpoints) { + _setVisible(el, "none", changeEndpoints); + }; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + if (!initialized) { + _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run. + + var _bind = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && _mouseEventsEnabled && renderMode == jsPlumb.CANVAS) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + var t = c[i].connector[event](e); + if (t) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i].endpoint[event](e)) return; + } + } + } + }); + }; + _bind("click"); + _bind("dblclick"); + _bind("mousemove"); + _bind("mousedown"); + _bind("mouseup"); + + initialized = true; + _currentInstance.fire("ready"); + } + }; + + this.jsPlumbUIComponent = jsPlumbUIComponent; + this.EventGenerator = EventGenerator; + + /* + * Creates an anchor with the given params. + * + * You do not need to use this method. It is exposed because of the way jsPlumb is + * split into three scripts; this will change in the future. + * + * x - the x location of the anchor as a fraction of the + * total width. y - the y location of the anchor as a fraction of the + * total height. xOrientation - value indicating the general direction a + * connection from the anchor should go in, in the x direction. + * yOrientation - value indicating the general direction a connection + * from the anchor should go in, in the y direction. xOffset - a fixed + * offset that should be applied in the x direction that should be + * applied after the x position has been figured out. optional. defaults + * to 0. yOffset - a fixed offset that should be applied in the y + * direction that should be applied after the y position has been + * figured out. optional. defaults to 0. + * -- OR -- + * + * params - {x:..., y:..., xOrientation etc } + * -- OR FROM 1.2.4 --- + * + * name - the name of some Anchor in the _currentInstance.Anchors array. + * -- OR FROM 1.2.4 --- + * + * coords - a list of coords for the anchor, like you would pass to + * jsPlumb.makeAnchor (eg [0.5,0.5,0,-1] - an anchor in the center of + * some element, oriented towards the top of the screen) + * -- OR FROM 1.2.4 --- + * + * anchor - an existing anchor. just gets passed back. it's handy + * internally to have this functionality. + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed + // in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards + // compatibility if we are given only one value we assume it's a + // call in the old form. + if (arguments.length == 0) return null; + var params = {}; + if (arguments.length == 1) { + var specimen = arguments[0]; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; + // is it the name of an anchor type? + else if (typeof specimen == "string") return _currentInstance.Anchors[arguments[0]](); + // is it an array of coordinates? + else if (specimen.constructor == Array) { + if (specimen[0].constructor == Array || specimen[0].constructor == String) + return new DynamicAnchor(specimen); + else + return jsPlumb.makeAnchor.apply(this, specimen); + } + // last we try the backwards compatibility stuff. + else if (typeof arguments[0] == "object") jsPlumb.extend(params, x); + } else { + params = { x : x, y : y }; + if (arguments.length >= 4) params.orientation = [ arguments[2], arguments[3] ]; + if (arguments.length == 6) params.offsets = [ arguments[4], arguments[5] ]; + } + var a = new Anchor(params); + a.clone = function() { + return new Anchor(params); + }; + return a; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types) { + var r = []; + for ( var i = 0; i < types.length; i++) + if (typeof types[i] == "string") + r.push(_currentInstance.Anchors[types[i]]()); + else if (types[i].constructor == Array) + r.push(jsPlumb.makeAnchor(types[i])); + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /** + * Function: makeTarget + * Makes some DOM element a Connection target, allowing you to drag connections to it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a target. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * scope optional. scope for the drop zone. + * dropOptions optional. same stuff as you would pass to dropOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to false. whether or not to delete + * any Endpoints created by a connection to this target if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + var _targetEndpointDefinitions = {}; + this.makeTarget = function(el, params, referenceParams) { + + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + var jpcl = jsPlumb.CurrentLibrary, + scope = p.scope || _currentInstance.Defaults.Scope, + deleteEndpointsOnDetach = p.deleteEndpointsOnDetach || false, + _doOne = function(_el) { + + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el); + _targetEndpointDefinitions[elid] = p.endpoint; + + var dropOptions = jsPlumb.extend({}, p.dropOptions || {}); + var _drop = function() { + var draggable = _getElementObject(jpcl.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + // restore the original scope if necessary (issue 57) + scope = _getAttribute(draggable, "originalScope"); + + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + // get the connection, to then get its endpoint + var jpc = floatingConnections[id], + source = jpc.endpoints[0], + _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : null, + // make a new Endpoint + newEndpoint = jsPlumb.addEndpoint(_el, _endpoint); + + var c = jsPlumb.connect({ + source:source, + target:newEndpoint, + scope:scope + }); + if (deleteEndpointsOnDetach) + c.endpointToDeleteOnDetach = newEndpoint; + }; + + var dropEvent = jpcl.dragEvents['drop']; + dropOptions["scope"] = dropOptions["scope"] || scope; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop); + + jpcl.initDroppable(_el, dropOptions); + }; + + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements drop targets. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeTargets = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeTarget(els[i], params, referenceParams); + } + }; + + /* + Function: ready + Helper method to bind a function to jsPlumb's ready event. + */ + this.ready = function(fn) { + _currentInstance.bind("ready", fn); + }, + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + for ( var i in ebe) + _currentInstance.deleteEndpoint(ebe[i]); + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + this.deleteEveryEndpoint(); + this.clearListeners(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + + Parameters: + value - whether or not to automatically repaint when the window is resized. + + Returns: void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setDraggableByDefault + * Sets whether or not elements are draggable by default. Default for this is true. + * + * Parameters: + * draggable - value to set + * + * Returns: + * void + */ + this.setDraggableByDefault = function(draggable) { + _draggableByDefault = draggable; + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction + * Sets the function to fire when the window size has changed and a repaint was fired. + * + * Parameters: + * f - Function to execute. + * + * Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: setMouseEventsEnabled + * Sets whether or not mouse events are enabled. Default is true. + * + * Parameters: + * enabled - whether or not mouse events should be enabled. + * + * Returns: + * void + */ + this.setMouseEventsEnabled = function(enabled) { + _mouseEventsEnabled = enabled; + }; + + /* + * Constant for use with the setRenderMode method + */ + this.CANVAS = "canvas"; + + /* + * Constant for use with the setRenderMode method + */ + this.SVG = "svg"; + + this.VML = "vml"; + + /* + * Function: setRenderMode + * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that + * what you asked for is not supported (and that VML is). If you asked for VML but the browser does + * not support it, jsPlumb uses SVG. + * + * Returns: + * the render mode that jsPlumb set, which of course may be different from that requested. + */ + this.setRenderMode = function(mode) { + if (mode) + mode = mode.toLowerCase(); + else + return; + if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); + // now test we actually have the capability to do this. + if (mode === jsPlumb.CANVAS && canvasAvailable) + renderMode = jsPlumb.CANVAS; + else if (mode === jsPlumb.SVG && svgAvailable) + renderMode = jsPlumb.SVG; + else if (vmlAvailable) + renderMode = jsPlumb.VML; + + return renderMode; + }; + + this.getRenderMode = function() { return renderMode; }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * changeEndpoints - whether or not to also change the visible state of the endpoints on the element. this also has a bearing on + * other connections on those endpoints: if their other endpoint is also visible, the connections are made visible. + * + * Returns: + * void + */ + this.show = function(el, changeEndpoints) { + _setVisible(el, "block", changeEndpoints); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId, + makeAnchor:self.makeAnchor, + makeDynamicAnchor:self.makeDynamicAnchor + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * changeEndpoints - whether or not to also toggle the endpoints on the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload + * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element. + * + * Returns: + * void + */ + this.unload = function() { + delete endpointsByElement; + delete endpointsByUUID; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + delete canvasList; + }; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + if (timestamp && timestamp === self.timestamp) { + return lastReturnValue; + } + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + // adjust loc if there is an offsetParent + if (element.canvas && element.canvas.offsetParent) { + var po = element.canvas.offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(element.canvas.offsetParent); + lastReturnValue[0] = lastReturnValue[0] - po.left; + lastReturnValue[1] = lastReturnValue[1] - po.top; + } + + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + * + * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(_getElementObject(refCanvas)); + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + var orientation = null; + var _lastResult = null; + + this.compute = function(params) { + var xy = params.xy, element = params.element; + var result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + + // adjust loc if there is an offsetParent + if (element.canvas && element.canvas.offsetParent) { + var po = element.canvas.offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(element.canvas.offsetParent); + result[0] = result[0] - po.left; + result[1] = result[1] - po.top; + } + + _lastResult = result; + return result; + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { orientation = anchor.getOrientation(); }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchors is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = [], + _convert = function(anchor) { return anchor.constructor == Anchor ? anchor: jsPlumb.makeAnchor(anchor); }; + for (var i = 0; i < anchors.length; i++) _anchors[i] = _convert(anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + this.locked = false; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null, + _curIndex = _anchors.length > 0 ? 0 : -1, + self = this, + + // helper method to calculate the distance between the centers of the two elements. + _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }, + + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + + var pos = _curAnchor.compute(params); + return pos; + }; + + this.getCurrentLocation = function() { + var cl = _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + return cl; + }; + + this.getOrientation = function() { return _curAnchor != null ? _curAnchor.getOrientation() : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * container - optional id or selector instructing jsPlumb where to attach all the elements it creates for this connection. you should read the documentation for a full discussion of this. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * overlays - Optional array of Overlay definitions to appear on this Connection. + * drawEndpoints - if false, instructs jsPlumb to not draw the endpoints for this Connection. Be careful with this: it only really works when you tell jsPlumb to attach elements to the document body. Read the documentation for a full discussion of this. + */ + var Connection = function(params) { + + jsPlumbUIComponent.apply(this, arguments); + // ************** get the source and target and register the connection. ******************* + var self = this; + var visible = true; + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none"; + }; + var id = new String('_jsplumb_c_' + (new Date()).getTime()); + this.getId = function() { return id; }; + this.parent = params.parent; + /** + Property: source + The source element for this Connection. + */ + this.source = _getElementObject(params.source); + /** + Property:target + The target element for this Connection. + */ + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. + if (params.sourceEndpoint) this.source = params.sourceEndpoint.getElement(); + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + /* + * Property: sourceId + * Id of the source element in the connection. + */ + this.sourceId = _getAttribute(this.source, "id"); + /* + * Property: targetId + * Id of the target element in the connection. + */ + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + + /** + * implementation of abstract method in EventGenerator + * @return list of attached elements. in our case, a list of Endpoints. + */ + this.getAttachedElements = function() { + return self.endpoints; + }; + + /** + * implementation of abstract method in EventGenerator + */ + var srcWhenMouseDown = null, targetWhenMouseDown = null; + this.savePosition = function() { + srcWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.source)); + targetWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.target)); + }; + + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams) { + if (anchorParams) + return jsPlumb.makeAnchor(anchorParams); + }; + var prepareEndpoint = function(existing, index, params, element, connectorPaintStyle, connectorHoverPaintStyle) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] + || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint; + + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified. + if (es.fillStyle == null && connectorPaintStyle != null) + es.fillStyle = connectorPaintStyle.strokeStyle; + + // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does: + //* + if (es.outlineColor == null && connectorPaintStyle != null) + es.outlineColor = connectorPaintStyle.outlineColor; + if (es.outlineWidth == null && connectorPaintStyle != null) + es.outlineWidth = connectorPaintStyle.outlineWidth; + //*/ + + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure? + if (connectorHoverPaintStyle != null) { + if (ehs == null) ehs = {}; + if (ehs.fillStyle == null) { + ehs.fillStyle = connectorHoverPaintStyle.strokeStyle; + } + } + var a = params.anchors ? params.anchors[index] : _makeAnchor(_currentInstance.Defaults.Anchors[index]) || _makeAnchor(jsPlumb.Defaults.Anchors[index]) || _makeAnchor(_currentInstance.Defaults.Anchor) || _makeAnchor(jsPlumb.Defaults.Anchor); + var u = params.uuids ? params.uuids[index] : null; + var e = _newEndpoint({ + paintStyle : es, + hoverPaintStyle:ehs, + endpoint : ep, + connections : [ self ], + uuid : u, + anchor : a, + source : element, + container:params.container + }); + self.endpoints[index] = e; + + if (params.drawEndpoints === false) e.setVisible(false, true, true); + + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source, params.paintStyle, params.hoverPaintStyle); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target, params.paintStyle, params.hoverPaintStyle); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + /* + * Function: setConnector + * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method - the same + * thing that you would set as the 'connector' property on a jsPlumb.connect call. + * + * Parameters: + * connector - Connector definition + */ + this.setConnector = function(connector, doNotRepaint) { + if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent); + var connectorArgs = { _jsPlumb:self._jsPlumb, parent:params.parent, cssClass:params.cssClass, container:params.container }; + if (connector.constructor == String) + this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand. + else if (connector.constructor == Array) + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs)); + this.canvas = this.connector.canvas; + var _mouseDown = false, _mouseWasDown = false, _mouseDownAt = null; + // add mouse events + this.connector.bind("click", function(con, e) { + _mouseWasDown = false; + self.fire("click", self, e); + }); + this.connector.bind("dblclick", function(con, e) { _mouseWasDown = false;self.fire("dblclick", self, e); }); + this.connector.bind("mouseenter", function(con, e) { + if (!self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(true); + } + self.fire("mouseenter", self, e); + } + }); + this.connector.bind("mouseexit", function(con, e) { + if (self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(false); + } + self.fire("mouseexit", self, e); + } + }); + this.connector.bind("mousedown", function(con, e) { + _mouseDown = true; + _mouseDownAt = jsPlumb.CurrentLibrary.getPageXY(e); + self.savePosition(); + }); + this.connector.bind("mouseup", function(con, e) { + _mouseDown = false; + if (self.connector == _connectionBeingDragged) _connectionBeingDragged = null; + }); + + if (!doNotRepaint) self.repaint(); + }; + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc) + */ + + self.setConnector(this.endpoints[0].connector || + this.endpoints[1].connector || + params.connector || + _currentInstance.Defaults.Connector || + jsPlumb.Defaults.Connector, true); + + this.setPaintStyle(this.endpoints[0].connectorStyle || + this.endpoints[1].connectorStyle || + params.paintStyle || + _currentInstance.Defaults.PaintStyle || + jsPlumb.Defaults.PaintStyle, true); + + this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || + this.endpoints[1].connectorHoverStyle || + params.hoverPaintStyle || + _currentInstance.Defaults.HoverPaintStyle || + jsPlumb.Defaults.HoverPaintStyle, true); + + this.paintStyleInUse = this.paintStyle; + + /* + * Property: overlays + * List of Overlays for this Connection. + */ + this.overlays = []; + var _overlays = params.overlays || _currentInstance.Defaults.Overlays; + if (_overlays) { + for (var i = 0; i < _overlays.length; i++) { + var o = _overlays[i], _newOverlay = null, _overlayEvents = null; + if (o.constructor == Array) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0]; + var p = jsPlumb.CurrentLibrary.extend({connection:self, _jsPlumb:_currentInstance}, o[1]); // make a copy of the object so as not to mess up anyone else's reference... + if (o.length == 3) jsPlumb.CurrentLibrary.extend(p, o[2]); + _newOverlay = new jsPlumb.Overlays[renderMode][type](p); + if (p.events) { + for (var evt in p.events) { + _newOverlay.bind(evt, p.events[evt]); + } + } + } else if (o.constructor == String) { + _newOverlay = new jsPlumb.Overlays[renderMode][o]({connection:self, _jsPlumb:_currentInstance}); + } else { + _newOverlay = o; + } + + + + this.overlays.push(_newOverlay); + } + } + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Connection. + * + * Parameters: + * event - the event to bind. Available events on a Connection are: + * - *click* : notification that a Connection was clicked. + * - *dblclick* : notification that a Connection was double clicked. + * - *mouseenter* : notification that the mouse is over a Connection. + * - *mouseexit* : notification that the mouse exited a Connection. + * + * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Connection's paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Connection will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Connection. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + this.addOverlay = function(overlay) { self.overlays.push(overlay); }; + + /** + * Function: removeAllOverlays + * Removes all overlays from the Connection, and then repaints. + */ + this.removeAllOverlays = function() { + self.overlays.splice(0, self.overlays.length); + self.repaint(); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + this.removeOverlay = function(overlayId) { + var idx = -1; + for (var i = 0; i < self.overlays.length; i++) { + if (overlayId === self.overlays[i].id) { + idx = i; + break; + } + } + if (idx != -1) self.overlays.splice(idx, 1); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + this.removeOverlays = function() { + for (var i = 0; i < arguments.length; i++) + self.removeOverlay(arguments[i]); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + this.labelStyle = params.labelStyle || _currentInstance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + if (this.label) { + this.overlays.push(new jsPlumb.Overlays[renderMode].Label( { + cssClass:params.cssClass, + labelStyle : this.labelStyle, + label : this.label, + connection:self, + _jsPlumb:_currentInstance + })); + } + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String or a Function that returns a String. + */ + this.setLabel = function(l) { + self.label = l; + _currentInstance.repaint(self.source); + }; + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var otherOffset = offsets[this.targetId]; + var otherWH = sizes[this.targetId]; + var anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1] + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0] + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc }); + + /* + * Paints the Connection. Not exposed for public usage. + * + * Parameters: + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp; + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = false; + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }); + _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + var sAnchorP = this.endpoints[sIdx].anchor.getCurrentLocation(); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.getCurrentLocation(); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + + /* paint overlays*/ + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + var s = o.computeMaxSize(self.connector); + if (s > maxSize) + maxSize = s; + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, self.paintStyleInUse.lineWidth, maxSize); + + self.connector.paint(dim, self.paintStyleInUse); + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim); + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function() { + this.paint({ elId : this.sourceId, recalc : true }); + }; + + _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide + // whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + + // just to make sure the UI gets initialised fully on all browsers. + self.repaint(); + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1 + * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't + * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you + * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or + * 'target' Endpoints for Connections. + * + * + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * container - optional id or selector instructing jsPlumb where to attach the element it creates for this endpoint. you should read the documentation for a full discussion of this. + * connections - optional list of Connections to configure the Endpoint with. + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + params = params || {}; + var self = this; +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Endpoint. + * + * Parameters: + * event - the event to bind. Available events on an Endpoint are: + * - *click* : notification that a Endpoint was clicked. + * - *dblclick* : notification that a Endpoint was double clicked. + * - *mouseenter* : notification that the mouse is over a Endpoint. + * - *mouseexit* : notification that the mouse exited a Endpoint. + * + * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Endpoint's paint style and then repaints the Endpoint. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Endpoint will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Endpoint. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + var visible = true; + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + var id = new String('_jsplumb_e_' + (new Date()).getTime()); + this.getId = function() { return id; }; + if (params.dynamicAnchors) + self.anchor = new DynamicAnchor(jsPlumb.makeAnchors(params.dynamicAnchors)); + else + self.anchor = params.anchor ? jsPlumb.makeAnchor(params.anchor) : params.anchors ? jsPlumb.makeAnchor(params.anchors) : jsPlumb.makeAnchor("TopCenter"); + var _endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot", + endpointArgs = { _jsPlumb:self._jsPlumb, parent:params.parent, container:params.container }; + if (_endpoint.constructor == String) + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint](endpointArgs); + else if (_endpoint.constructor == Array) { + endpointArgs = jsPlumb.extend(_endpoint[1], endpointArgs); + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint[0]](endpointArgs); + } + else + _endpoint = _endpoint.clone(); + + // assign a clone function using our derived endpointArgs. this is used when a drag starts: the endpoint that was dragged is cloned, + // and the clone is left in its place while the original one goes off on a magical journey. + this.clone = function() { + var o = new Object(); + _endpoint.constructor.apply(o, [endpointArgs]); + return o; + }; + + self.endpoint = _endpoint; + self.type = self.endpoint.type; + + // TODO this event listener registration code is identical to what Connection does: it should be refactored. + this.endpoint.bind("click", function(e) { self.fire("click", self, e); }); + this.endpoint.bind("dblclick", function(e) { self.fire("dblclick", self, e); }); + this.endpoint.bind("mouseenter", function(con, e) { + if (!self.isHover()) { + self.setHover(true); + self.fire("mouseenter", self, e); + } + }); + this.endpoint.bind("mouseexit", function(con, e) { + if (self.isHover()) { + self.setHover(false); + self.fire("mouseexit", self, e); + } + }); + // TODO this event listener registration code above is identical to what Connection does: it should be refactored. + + this.setPaintStyle(params.paintStyle || + params.style || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyle, true); + this.setHoverPaintStyle(params.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyle, true); + this.paintStyleInUse = this.paintStyle; + this.connectorStyle = params.connectorStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.parent = params.parent; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + var _element = params.source, + _uuid = params.uuid, + floatingEndpoint = null, + inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + this.element = _element; + var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + /* + * Property: canvas + * The Endpoint's Canvas. + */ + this.canvas = this.endpoint.canvas; + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + this.connections = params.connections || []; + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + var _reattach = params.reattach || false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + + // this avoids a circular loop + if (!ignoreTarget) { + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + t.detach(connection, true); + // check connection to see if we want to delete the other endpoint. + // if the user uses makeTarget to make some element a target for connections, + // it is possible that they will have set 'endpointToDeleteOnDetach': when + // you make a connection to an element that acts as a target (note: NOT an + // Endpoint; just some div as a target), Endpoints are created for that + // connection. so if you then delete that Connection, it is feasible you + // will want these auto-generated endpoints to be removed. + if (connection.endpointToDeleteOnDetach && connection.endpointToDeleteOnDetach.connections.length == 0) + jsPlumb.deleteEndpoint(connection.endpointToDeleteOnDetach); + } + _removeElements(connection.connector.getDisplayElements(), connection.parent); + _removeFromList(connectionsByScope, connection.scope, connection); + if(!ignoreTarget) fireDetachEvent(connection); + } + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + */ + this.detachAll = function() { + while (self.connections.length > 0) { + self.detach(self.connections[0]); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + */ + this.detachFrom = function(targetEndpoint) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + c[i].setHover(false); + self.detach(c[i]); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + return _newEndpoint( { anchor : self.anchor, source : _element, paintStyle : this.paintStyle, endpoint : _endpoint }); + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + return (self.connections.length < _maxConnections) || _maxConnections == -1 ? null : self.connections[0]; + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + /* + * Function: setStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to + * setStyle and deprecate it if so. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + * + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + // a helper function that tries to find a connection to the given element, and returns it if so. if elementWithPrecedence is null, + // or no connection to it is found, we return the first connection in our list. + var findConnectionToUseForDynamicAnchor = function(elementWithPrecedence) { + var idx = 0; + if (elementWithPrecedence != null) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].sourceId == elementWithPrecedence || self.connections[i].targetId == elementWithPrecedence) { + idx = i; + break; + } + } + } + + return self.connections[idx]; + }; + + /* + * Function: paint + * Paints the Endpoint, recalculating offset and anchor positions if necessary. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + + params = params || {}; + var timestamp = params.timestamp; + if (!timestamp || self.timestamp !== timestamp) { + var ap = params.anchorPoint, canvas = params.canvas, connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + var xy = params.offset || offsets[_elementId]; + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (self.anchor.isDynamic) { + if (self.connections.length > 0) { + //var c = self.connections[0]; + var c = findConnectionToUseForDynamicAnchor(params.elementWithPrecedence); + var oIdx = c.endpoints[0] == self ? 1 : 0; + var oId = oIdx == 0 ? c.sourceId : c.targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + } + ap = self.anchor.compute(anchorParams); + } + + var d = _endpoint.compute(ap, self.anchor.getOrientation(), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + _endpoint.paint(d, self.paintStyleInUse, self.anchor); + + self.timestamp = timestamp; + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + var start = function() { + jpc = self.connectorSelector(); + if (self.isFull() && !dragAllowedWhenFull) return false; + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + n.style.position = "absolute"; + var nE = _getElementObject(n); + _appendElement(n, self.parent); + // create and assign an id, and initialize the offset. + var id = _getId(nE); + + // set the offset of this div to be where 'inPlaceCopy' is, to start with. + var ipcoel = _getElementObject(inPlaceCopy.canvas), + ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel), + po = inPlaceCopy.canvas.offsetParent != null ? + inPlaceCopy.canvas.offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(inPlaceCopy.canvas.offsetParent) + : { left:0, top: 0}; + jsPlumb.CurrentLibrary.setOffset(n, {left:ipco.left - po.left, top:ipco.top-po.top}); + + _updateOffset( { elId : id }); + + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor( { reference : self.anchor, referenceCanvas : self.canvas }); + floatingEndpoint = _newEndpoint({ paintStyle : self.paintStyle, endpoint : _endpoint, anchor : floatingAnchor, source : nE }); + + if (jpc == null) { + self.anchor.locked = true; + // create a connection. one end is this endpoint, the + // other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : _getElementObject(_element), + target : _getElementObject(n), + anchors : [ self.anchor, floatingAnchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays + }); + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.connector.setHover(false); + } else { + existingJpc = true; + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.connector.setHover(false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas)); + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; // are we the source or the target? + + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas); + var dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // now we want to get this endpoint's DROP scope, and set it for now: we can only be dropped on drop zones + // that have our drop scope (issue 57). + var dropScope = jsPlumb.CurrentLibrary.getDropScope(c);//jpc.endpoints[anchorIdx == 0 ? 1 : 0].getDropScope(); + jsPlumb.CurrentLibrary.setDragScope(c, dropScope); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it and register connection on it. + floatingConnections[id] = jpc; + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var jpcl = jsPlumb.CurrentLibrary, + dragOptions = params.dragOptions || {}, + defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions), + startEvent = jpcl.dragEvents['start'], + stopEvent = jpcl.dragEvents['stop'], + dragEvent = jpcl.dragEvents['drag']; + + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], + function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + jsPlumb.CurrentLibrary.setOffset(n, _ui); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements( [ n, floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (_reattach) { + + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } else { + jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc); // the main endpoint will inform the floating endpoint + // to disconnect, and also post the detached event. + } + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElements(jpc.connector.getDisplayElements(), self.parent); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint(); + jpc.setHover(false); + jpc.repaint(); + jpc = null; + delete inPlaceCopy; + delete endpointsByElement[floatingEndpoint.elementId]; + floatingEndpoint = null; + delete floatingEndpoint; + + _currentInstance.currentlyDragging = false; + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas) { + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + var drop = function() { + var draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)); + var id = _getAttribute(draggable, "dragId"); + var elId = _getAttribute(draggable, "elId"); + + // restore the original scope if necessary (issue 57) + var scope = _getAttribute(draggable, "originalScope"); + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + var jpc = floatingConnections[id]; + + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // todo test that the target is not full. + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + if (!jpc.suspendedEndpoint) { + _addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + _currentInstance.fire("jsPlumbConnectionDetached", { + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }); + } + + jsPlumb.repaint(elId); + + _currentInstance.fire("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], + targetEndpoint : jpc.endpoints[1], + connection:jpc + }); + } + + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute( _getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + } + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute(_getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + } + }); + + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas)); + + return self; + }; + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + j.init(); + return j; + }; + + var _curryAnchor = function(x,y,ox,oy) { + return function() { + return jsPlumb.makeAnchor(x,y,ox,oy); + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1); + + jsPlumb.Defaults.DynamicAnchors = function() { + return jsPlumb.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"]); + }; + jsPlumb.Anchors["AutoDefault"] = function() { return jsPlumb.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors()); }; + + +})(); diff --git a/archive/1.3.2/jsPlumb-1.3.2-tests.js b/archive/1.3.2/jsPlumb-1.3.2-tests.js new file mode 100644 index 000000000..2de9ff459 --- /dev/null +++ b/archive/1.3.2/jsPlumb-1.3.2-tests.js @@ -0,0 +1,1850 @@ + +var _getContextNode = function() { + return $("#container"); +}; + +var assertContextExists = function() { + ok(_getContextNode().length == 1, "context node exists"); +}; + +var assertContextSize = function(elementCount) { + //equals(_getContextNode().children().length - _divs.length, elementCount, 'context has ' + elementCount + ' children'); +}; + +var assertContextEmpty = function() { + equals(_getContextNode().children.length, 0, "context empty"); +}; + +var assertEndpointCount = function(elId, count) { + equals(jsPlumb.getTestHarness().endpointCount(elId), count, elId + " has " + count + (count > 1) ? "endpoints" : "endpoint"); +}; + +var assertConnectionCount = function(endpoint, count) { + equals(endpoint.connections.length, count, "endpoint has " + count + " connections"); +}; + +var assertConnectionByScopeCount = function(scope, count) { + equals(jsPlumb.getTestHarness().connectionCount(scope), count, 'Scope ' + scope + " has " + count + (count > 1) ? "connections" : "connection"); + +}; + +var _divs = []; +var _addDiv = function(id) { + var d1 = document.createElement("div"); + _getContextNode().append(d1); + d1 = jsPlumb.CurrentLibrary.getElementObject(d1); + jsPlumb.CurrentLibrary.setAttribute(d1, "id", id); + _divs.push(id); + return d1; +}; + +var _cleanup = function() { + + jsPlumb.reset(); + jsPlumb.Defaults.Container = null; + + for (var i in _divs) { + $("#" + _divs[i]).remove(); + } + _divs.splice(0, _divs.length - 1); +}; + +var testSuite = function(renderMode) { + + module("jsPlumb", {teardown: _cleanup}); + + // setup the container + var container = document.createElement("div"); + container.id = "container"; + document.body.appendChild(container); + + jsPlumb.setRenderMode(renderMode); + + test(renderMode + ': findIndex method', function() { + var array = [ 1,2,3, "test", "a string", { 'foo':'bar', 'baz':1 }, { 'ding':'dong' } ]; + equals(jsPlumb.getTestHarness().findIndex(array, 1), 0, "find works for integers"); + equals(jsPlumb.getTestHarness().findIndex(array, "test"), 3, "find works for strings"); + equals(jsPlumb.getTestHarness().findIndex(array, { 'foo':'bar', 'baz':1 }), 5, "find works for objects"); + equals(jsPlumb.getTestHarness().findIndex(array, { 'ding':'dong', 'baz':1 }), -1, "find works properly for objects (objects have different length but some same properties)"); + }); + + test(renderMode + ': jsPlumb setup', function() { + ok(jsPlumb, "loaded"); + }); + + test(renderMode + ': getId', function() { + var d10 = _addDiv('d10'); + equals(jsPlumb.getTestHarness().getId(d10), "d10"); + }); + + test(renderMode + ': create a simple endpoint', function() { + var d1 = _addDiv("d1"); + var e = jsPlumb.addEndpoint($("#d1"), {}); + ok(e, 'endpoint exists'); + assertEndpointCount("d1", 1); + }); + + test(renderMode + ': create and remove a simple endpoint', function() { + var d1 = _addDiv("d1"); + var ee = jsPlumb.addEndpoint(d1, {uuid:"78978597593"}); + ok(ee != null, "endpoint exists"); + var e = jsPlumb.getEndpoint("78978597593"); + ok(e != null, "the endpoint could be retrieved by UUID"); + assertEndpointCount("d1", 1); + assertContextSize(1); // one Endpoint canvas. + jsPlumb.removeEndpoint(d1, ee); + assertEndpointCount("d1", 0); + e = jsPlumb.getEndpoint("78978597593"); + equals(e, null, "the endpoint has been deleted"); + assertContextSize(0); // no Endpoint canvases. + }); + + test(renderMode + ': create two simple endpoints, registered using a selector', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + d1.addClass("window");d2.addClass("window"); + var endpoints = jsPlumb.addEndpoint($(".window"), {}); + equals(endpoints.length, 2, "endpoint added to both windows"); + assertEndpointCount("d1", 1); + assertEndpointCount("d2", 1); + }); + + test(renderMode + ': create two simple endpoints, registered using an array of element ids', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + d1.addClass("window");d2.addClass("window"); + var endpoints = jsPlumb.addEndpoint(["d1", "d2"], {}); + equals(endpoints.length, 2, "endpoint added to both windows"); + assertEndpointCount("d1", 1); + assertEndpointCount("d2", 1); + }); + + test(renderMode + ': draggable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1"); + var e = jsPlumb.addEndpoint(d1, {isSource:true}); + ok(e, 'endpoint exists'); + }); + + test(renderMode + ': droppable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1") + var e = jsPlumb.addEndpoint(d1, {isTarget:true}); + ok(e, 'endpoint exists'); + }); + + test(renderMode + ': defaultEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = jsPlumb.addEndpoint(d3, {isSource:true}); + ok(e3.anchor, 'endpoint 3 has an anchor'); + var e4 = jsPlumb.addEndpoint(d4, {isSource:true}); + assertEndpointCount("d3", 1); + assertEndpointCount("d4", 1); + assertContextSize(2); // one canvas for each of our two endpoints. + ok(!e3.isFull(), "endpoint 3 is not full."); + jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // we have one connection + jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // should have refused the connection; default max is 1. + assertContextSize(3); // now we have one more canvas, for the Connection that was accepted. + }); + + test(renderMode + ': specifiedEndpointMaxConnections', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = jsPlumb.addEndpoint(d5, {isSource:true, maxConnections:3}); + ok(e5.anchor, 'endpoint 5 has an anchor'); + var e6 = jsPlumb.addEndpoint(d6, {isSource:true, maxConnections:2}); // this one has max TWO + assertEndpointCount("d5", 1); assertEndpointCount("d6", 1); + assertContextSize(2); + ok(!e5.isFull(), "endpoint 5 is not full."); + jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 1); // we have one connection + assertContextSize(3); + jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // two connections + assertContextSize(4); + jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // should have refused; max is 2, for d4. + assertContextSize(4); + }); + + test(renderMode + ': noEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + var e4 = jsPlumb.addEndpoint(d4, {isSource:true, maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertContextSize(3); + assertConnectionCount(e3, 1); // we have one connection + jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 2); // we have two. etc (default was one. this proves max is working). + assertContextSize(4); + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e4}); + assertConnectionCount(e4, 3); + assertContextSize(6); + }); + + test(renderMode + ': anchors equal', function() { + var a1 = jsPlumb.makeAnchor(0, 1, 1, 1); + var a2 = jsPlumb.makeAnchor(0, 1, 1, 1); + ok(a1.equals(a2), "anchors are the same"); + }); + + test(renderMode + ': anchors equal with offsets', function() { + var a1 = jsPlumb.makeAnchor(0, 1, 1, 1, 10, 13); + var a2 = jsPlumb.makeAnchor(0, 1, 1, 1, 10, 13); + ok(a1.equals(a2), "anchors are the same"); + }); + + test(renderMode + ': anchors not equal', function() { + var a1 = jsPlumb.makeAnchor(0, 1, 0, 1); + var a2 = jsPlumb.makeAnchor(0, 1, 1, 1); + ok(!a1.equals(a2), "anchors are different"); + }); + + test(renderMode + ': anchor not equal with offsets', function() { + var a1 = jsPlumb.makeAnchor(0, 1, 1, 1, 10, 13); + var a2 = jsPlumb.makeAnchor(0, 1, 1, 1); + ok(!a1.equals(a2), "anchors are different"); + }); + + test(renderMode + ': detach plays nice when no target given', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + jsPlumb.connect({source:d3, target:d4}); + jsPlumb.detach(); + }); + + // issue 81 + test(renderMode + ': jsPlumb.detach should fire only one detach event (pass Connection as argument)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + jsPlumb.detach(conn); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': jsPlumb.detach should fire only one detach event (pass Connection as param in argument)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + jsPlumb.detach({connection:conn}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': jsPlumb.detach should fire only one detach event (pass source and targets as strings as arguments in params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + jsPlumb.detach({source:"d5", target:"d6"}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': jsPlumb.detach should fire only one detach event (pass source and targets as divs as arguments in params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + jsPlumb.detach({source:d5, target:d6}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': jsPlumb.detach should fire only one detach event (pass source and targets as divs as arguments)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + jsPlumb.detach(d5, d6); + equals(eventCount, 1); + }); + + //TODO make sure you run this test with a single detach call, to ensure that + // single detach calls result in the connection being removed. detachEverything can + // just blow away the connectionsByScope array and recreate it. + test(renderMode + ': jsPlumb.getConnections (simple case, default scope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + jsPlumb.connect({source:d5, target:d6}); + assertContextSize(3); + var c = jsPlumb.getConnections(); // will get all connections in the default scope. + equals(c.length, 1, "there is one connection"); + }); + + test(renderMode + ': jsPlumb.getConnections (simple case, default scope; detach by element id)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + jsPlumb.connect({source:d5, target:d6}); + jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections in the default scope + equals(c.length, 2, "there are two connections initially"); + jsPlumb.detach('d5', 'd6'); + c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': jsPlumb.getConnections (simple case, default scope; detach by element id using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + jsPlumb.connect({source:d5, target:d6}); + jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + jsPlumb.detach({source:'d5', target:'d6'}); + c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': jsPlumb.getConnections (simple case, default scope; detach by element object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + jsPlumb.connect({source:d5, target:d6}); + jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + jsPlumb.detach(d5, d6); + c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': jsPlumb.getConnections (simple case, default scope; detach by element object using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + jsPlumb.connect({source:d5, target:d6}); + jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + jsPlumb.detach({source:d5, target:d6}); + c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': jsPlumb.getConnections (simple case, default scope; detach by Connection)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + var c56 = jsPlumb.connect({source:d5, target:d6}); + var c67 = jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + jsPlumb.detach(c56); + assertContextSize(5); // check that the connection canvas was removed. + c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + + }); + + test(renderMode + ': jsPlumb.getConnections (scope testScope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + jsPlumb.connect({source:d5, target:d6, scope:'testScope'}); + var c = jsPlumb.getConnections("testScope"); // will get all connections in testScope + equals(c.length, 1, "there is one connection"); + equals(c[0].sourceId, 'd5', "the connection's source is d5"); + equals(c[0].targetId, 'd6', "the connection's source is d6"); + c = jsPlumb.getConnections(); // will get all connections in default scope; should be none. + equals(c.length, 0, "there are no connections in the default scope"); + }); + + test(renderMode + ': jsPlumb.getAllConnections (filtered by scope)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + jsPlumb.connect({source:d9, target:d10}); // default scope + var c = jsPlumb.getAllConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 1); + equals(c['testScope'].length, 1); + // now supply a list of scopes + c = jsPlumb.getConnections(); + equals(c.length, 1); + c = jsPlumb.getConnections("testScope"); + equals(c.length, 1, "there is one connection in 'testScope'"); + }); + + test(renderMode + ': jsPlumb.getConnections (filtered by scope and sourceId)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + jsPlumb.connect({source:d9, target:d8, scope:'testScope'}); + jsPlumb.connect({source:d9, target:d10}); // default scope + var c = jsPlumb.getConnections({scope:'testScope', source:'d8'}); // will get all connections with sourceId 'd8' + equals(c.length, 1, "there is one connection in 'testScope' from d8"); + }); + + test(renderMode + ': jsPlumb.getConnections (filtered by scope, source id and target id)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d12, target:d13, scope:'testScope'}); + jsPlumb.connect({source:d11, target:d13, scope:'testScope'}); + var c = jsPlumb.getConnections({scope:'testScope', source:'d11', target:'d13'}); + equals(c.length, 1, "there is one connection from d11 to d13"); + }); + + test(renderMode + ': jsPlumb.getConnections (filtered by a list of scopes)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = jsPlumb.getConnections({scope:['testScope','testScope3']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + }); + + test(renderMode + ': jsPlumb.getConnections (filtered by a list of scopes and source ids)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + }); + + test(renderMode + ': jsPlumb.getConnections (filtered by a list of scopes, source ids and target ids)', function() { + jsPlumb.detachEverything(); + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'), d14=_addDiv("d14"), d15=_addDiv("d15"); + jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d11, target:d14, scope:'testScope'}); + jsPlumb.connect({source:d11, target:d15, scope:'testScope'}); + jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + assertContextSize(18); + var c = jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11'], target:['d14', 'd15']}); + equals(c['testScope'].length, 2, 'there are two connections in testScope'); + equals(c['testScope3'], null, 'there are no connections in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + var anEntry = c['testScope'][0]; + ok(anEntry.endpoints[0] != null, "Source endpoint is set"); + ok(anEntry.endpoints[1] != null, "Target endpoint is set"); + equals(anEntry.source.attr("id"), "d11", "Source is div d11"); + equals(anEntry.target.attr("id"), "d14", "Target is div d14"); + }); + + test(renderMode + ': getEndpoints, one Endpoint added by addEndpoint, get Endpoints by selector', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + jsPlumb.addEndpoint(d1); + var e = jsPlumb.getEndpoints(d1); + equals(e.length, 1, "there is one endpoint for element d1"); + }); + + test(renderMode + ': getEndpoints, one Endpoint added by addEndpoint, get Endpoints by id', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + jsPlumb.addEndpoint(d1); + var e = jsPlumb.getEndpoints("d1"); + equals(e.length, 1, "there is one endpoint for element d1"); + }); + + test(renderMode + ': connection event listeners', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null/*, returnedParams2 = null*/; + jsPlumb.bind("jsPlumbConnection", function(params) { + returnedParams = $.extend({}, params); + }); + var c = jsPlumb.connect({source:d1, target:d2}); + ok(returnedParams != null, "new connection listener event was fired"); + ok(returnedParams.connection != null, 'connection is set'); + equals(returnedParams.sourceId, "d1", 'sourceid is set'); + equals(returnedParams.targetId, "d2", 'targetid is set'); + equals(returnedParams.source.attr("id"), "d1", 'source is set'); + equals(returnedParams.target.attr("id"), "d2" , 'target is set'); + ok(returnedParams.sourceEndpoint != null, "source endpoint is not null"); + ok(returnedParams.targetEndpoint != null, "target endpoint is not null"); + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + jsPlumb.detach(c); + ok(returnedParams.connection != null, 'connection is set'); + //jsPlumb.detachAll("d1"); + //ok(returnedParams2 != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (detach by connection)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + ok(returnedParams.connection != null, "removed connection listener event passed in connection"); + }); + + test(renderMode + ': detach event listeners (detach by element ids)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach("d1","d2"); + ok(returnedParams != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (detach by elements)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach(d1,d2); + ok(returnedParams != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detach method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detachFrom method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachFrom(e2); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detachAll method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachAll(); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via jsPlumb.deleteEndpoint method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + jsPlumb.deleteEndpoint(e1); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(1); + }); + + test(renderMode + ': detach event listeners (ensure cleared by jsPlumb.reset)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach(d1,d2); + ok(returnedParams != null, "removed connection listener event was fired"); + returnedParams = null; + + jsPlumb.reset(); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach(d1,d2); + ok(returnedParams == null, "connection listener was cleared by jsPlumb.reset()"); + }); + + test(renderMode + ': connection events that throw errors', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null, returnedParams2 = null; + jsPlumb.bind("jsPlumbConnection", function(params) { + returnedParams = $.extend({}, params); + throw "oh no!"; + }); + jsPlumb.connect({source:d1, target:d2}); + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + jsPlumb.connect({source:d3, target:d4}); + ok(returnedParams != null, "new connection listener event was fired; we threw an error, jsPlumb survived."); + }); + + test(renderMode + ": Endpoint.detachFrom", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + e16.detachFrom(e17); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + }); + + test(renderMode + ": Endpoint.detachFromConnection", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + e16.detachFromConnection(conn); + assertContextSize(3); // all canvases should remain; the connection was not removed. + // but endpoint e16 should have no connections now. + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 1); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + }); + + test(renderMode + ": Endpoint.detachAll", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + var e16 = jsPlumb.addEndpoint($("#d16"), {isSource:true,maxConnections:-1}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint($("#d17"), {isSource:true}); + var e18 = jsPlumb.addEndpoint($("#d18"), {isSource:true}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e18}); + assertConnectionCount(e16, 2); + assertConnectionCount(e17, 1); + assertConnectionCount(e18, 1); + assertContextSize(5); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 2); + e16.detachAll(); + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + }); + + test(renderMode + ": Endpoint.detach", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + e16.detach(conn); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + }); + + test(renderMode + ": Endpoint.isConnectedTo", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.isConnectedTo(e17), true, "e16 and e17 are connected"); + }); + + test(renderMode + ": setting endpoint uuid", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + equals(e16.getUuid(), uuid, "endpoint's uuid was set correctly"); + }); + + test(renderMode + ": jsPlumb.getEndpoint (by uuid)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + }); + + test(renderMode + ": jsPlumb.deleteEndpoint (by uuid, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + jsPlumb.deleteEndpoint(uuid); + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + var ebe = jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + assertContextSize(0); + }); + + test(renderMode + ": jsPlumb.deleteEndpoint (by uuid, connections too)", function() { + // create two endpoints (one with a uuid), add them to two divs and then connect them. + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + // check that the connection was ok. + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + // delete the endpoint that has a uuid. verify that the endpoint cannot be retrieved and that the connection has been removed, but that + // element d17 still has its Endpoint. + jsPlumb.deleteEndpoint(uuid); + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + var ebe = jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + ebe = jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 1, "element d17 still has its Endpoint"); + assertContextSize(1); + + // now delete d17's endpoint and check that it has gone. + jsPlumb.deleteEndpoint(e17); + f = jsPlumb.getEndpoint(e17); + equals(f, null, "endpoint has been deleted"); + ebe = jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 0, "element d17 no longer has any Endpoints"); + assertContextSize(0); + }); + + test(renderMode + ": jsPlumb.deleteEndpoint (by reference, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + jsPlumb.deleteEndpoint(e16); + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + assertContextSize(0); + }); + + test(renderMode + ": jsPlumb.deleteEndpoint (by reference, connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + jsPlumb.deleteEndpoint(e16); + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + assertContextSize(1); + }); + + test(renderMode + ": jsPlumb.deleteEveryEndpoint", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + assertContextSize(2); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + jsPlumb.deleteEveryEndpoint(); + + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); + }); + + test(renderMode + ": jsPlumb.deleteEveryEndpoint (connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertContextSize(3); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + jsPlumb.deleteEveryEndpoint(); + + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); // no canvases. so all connection canvases have been cleaned up too. + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + }); + + test(renderMode + ": jsPlumb.addEndpoint (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {anchor:[0,0.5,0,-1]}); + var e17 = jsPlumb.addEndpoint(d17, {anchor:"TopCenter"}); + assertContextSize(2); + equals(e16.anchor.x, 0); + equals(e16.anchor.y, 0.5); + equals(e17.anchor.x, 0.5); + equals(e17.anchor.y, 0); + }); + + test(renderMode + ": jsPlumb.addEndpoint (simple case, dynamic anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + assertContextSize(2); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": jsPlumb.addEndpoint (simple case, two arg method)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchor:[0,0.5,0,-1]}); + var e17 = jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchor:"TopCenter"}); + assertContextSize(2); + equals(e16.anchor.x, 0); + equals(e16.anchor.y, 0.5); + equals(false, e16.isTarget); + equals(true, e16.isSource); + equals(e17.anchor.x, 0.5); + equals(e17.anchor.y, 0); + equals(true, e17.isTarget); + equals(false, e17.isSource); + }); + + + test(renderMode + ": jsPlumb.addEndpoints (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false, anchor:[0,0.5,0,-1] }, { isTarget:true, isSource:false, anchor:"TopCenter" }]); + assertContextSize(2); + equals(e16[0].anchor.x, 0); + equals(e16[0].anchor.y, 0.5); + equals(false, e16[0].isTarget); + equals(true, e16[0].isSource); + equals(e16[1].anchor.x, 0.5); + equals(e16[1].anchor.y, 0); + equals(true, e16[1].isTarget); + equals(false, e16[1].isSource); + }); + + test(renderMode + ": jsPlumb.addEndpoints (with reference params)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var refParams = {anchor:"RightMiddle"}; + var e16 = jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false}, { isTarget:true, isSource:false }], refParams); + assertContextSize(2); + equals(e16[0].anchor.x, 1); + equals(e16[0].anchor.y, 0.5); + equals(false, e16[0].isTarget); + equals(true, e16[0].isSource); + equals(e16[1].anchor.x, 1); + equals(e16[1].anchor.y, 0.5); + equals(true, e16[1].isTarget); + equals(false, e16[1].isSource); + }); + + test(renderMode + ": jsPlumb.addEndpoint (simple case, dynamic anchors, two arg method)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchors:["TopCenter", "BottomCenter"]}); + assertContextSize(2); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": jsPlumb.makeTarget (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + jsPlumb.makeTarget(d17, { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": jsPlumb.makeTarget (specify two divs in an array)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + jsPlumb.makeTarget([d16, d17], { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": jsPlumb.makeTarget (specify two divs by id in an array)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + jsPlumb.makeTarget(["d16", "d17"], { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": jsPlumb.makeTarget (specify divs by selector)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + d16.addClass("FOO");d17.addClass("FOO"); + jsPlumb.makeTarget($(".FOO"), { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ': jsPlumb.connect (between two Endpoints)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1); + assertEndpointCount("d2", 1); + jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1); // no new endpoint should have been added + assertEndpointCount("d2", 1); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. + }); + + test(renderMode + ': jsPlumb.connect (between two Endpoints, and dont supply any parameters to the Endpoints.)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1); + assertEndpointCount("d2", 1); + jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1); // no new endpoint should have been added + assertEndpointCount("d2", 1); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. + }); + + test(renderMode + ': jsPlumb.connect (by endpoint)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + }); + + test(renderMode + ": jsPlumb.connect (two Endpoints - that have been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1, uuid:srcEndpointUuid}); + var e2 = jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1, uuid:dstEndpointUuid}); + jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ] }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertContextSize(3); + }); + + test(renderMode + ": jsPlumb.connect (two Endpoints - that have not been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ], source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + var e1 = jsPlumb.getEndpoint(srcEndpointUuid); + ok(e1 != null, "endpoint with src uuid added"); + ok(e1.canvas != null); + var e2 = jsPlumb.getEndpoint(dstEndpointUuid); + ok(e2 != null, "endpoint with target uuid added"); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + }); + + test(renderMode + ": jsPlumb.connect (two Endpoints - that have been already added - by endpoint reference)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1}); + var e2 = jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1}); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + }); + + test(renderMode + ": jsPlumb.connect (two elements)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + jsPlumb.connect({ source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + }); + + test(renderMode + ": jsPlumb.connect (Connector test, straight)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": jsPlumb.connect (Connector test, bezier, no params)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Bezier" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 150, "Bezier connector chose 150 curviness"); + }); + + test(renderMode + ": jsPlumb.connect (Connector test, bezier, curviness as int)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", { curviness:200 }] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Canvas Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 200, "Bezier connector chose 200 curviness"); + }); + + /*jsPlumb.setRenderMode(jsPlumb.SVG); + test(renderMode + ": jsPlumb.connect (Connector test, bezier, curviness as int)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", 200] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors.svg.Bezier, "SVG Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 200, "Bezier connector chose 200 curviness"); + });*/ + + test(renderMode + ": jsPlumb.connect (Connector test, bezier, curviness as named option)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", {curviness:300}] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 300, "Bezier connector chose 300 curviness"); + }); + + test(renderMode + ": jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Canvas Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": jsPlumb.connect (anchors registered correctly; source and target anchors given, as strings)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:["LeftMiddle", "RightMiddle"] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.5, conn.endpoints[0].anchor.y, "source anchor y"); + equals(1, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.5, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + + test(renderMode + ": jsPlumb.connect (two argument method in which some data is reused across connections)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"), d19 = _addDiv("d19"); + var sharedData = { connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }; + var conn = jsPlumb.connect({ source:d16, target:d17}, sharedData); + var conn2 = jsPlumb.connect({ source:d18, target:d19}, sharedData); + assertContextSize(6); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 2); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(conn2.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + equals(0.3, conn2.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn2.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn2.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn2.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": jsPlumb.connect (Connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": jsPlumb.connect (Endpoint test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection target"); + }); + + test(renderMode + ": jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection target"); + }); + + test(renderMode + ": jsPlumb.connect (Endpoints test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Dot, "Dot endpoint chosen for connection target"); + }); + + test(renderMode + ": jsPlumb.connect (Blank Endpoint specified via 'endpoint' param)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoint:"Blank" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection target"); + }); + + test(renderMode + ": jsPlumb.connect (Blank Endpoint specified via 'endpoints' param)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoints:["Blank", "Blank" ] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection target"); + }); + + test(renderMode + ": jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Dot, "Dot endpoint chosen for connection target"); + }); + + test(renderMode + ": jsPlumb.connect (by Endpoints, connector test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {}); + var e17 = jsPlumb.addEndpoint(d17, {}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": jsPlumb.connect (by Endpoints, connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {}); + var e17 = jsPlumb.addEndpoint(d17, {}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": jsPlumb.connect (by Endpoints, anchors as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = "TopCenter", a17 = "BottomCenter"; + var e16 = jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, 0.5, "endpoint 16 is at top center");equals(e16.anchor.y, 0, "endpoint 16 is at top center"); + equals(e17.anchor.x, 0.5, "endpoint 17 is at bottom center");equals(e17.anchor.y, 1, "endpoint 17 is at bottom center"); + }); + + test(renderMode + ": jsPlumb.connect (by Endpoints, endpoints create anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = [0,0.5,0,-1], a17 = [1,0.0,-1,-1]; + var e16 = jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, a16[0]);equals(e16.anchor.y, a16[1]); + equals(e17.anchor.x, a17[0]);equals(e17.anchor.y, a17[1]); + equals(e16.anchor.getOrientation()[0], a16[2]); equals(e16.anchor.getOrientation()[1], a16[3]); + equals(e17.anchor.getOrientation()[0], a17[2]); equals(e17.anchor.getOrientation()[1], a17[3]); + }); + + test(renderMode + ": jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchor')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {anchor:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = jsPlumb.addEndpoint(d17, {anchor:["TopCenter", "BottomCenter"]}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchors')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": jsPlumb.connect (connect by element, default endpoint, supplied dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var anchors = [ [0.25, 0, 0, -1], [1, 0.25, 1, 0], [0.75, 1, 0, 1], [0, 0.75, -1, 0] ]; + jsPlumb.connect({source:d1, target:d2, dynamicAnchors:anchors}); // auto connect with default endpoint and provided anchors + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + assertContextSize(2); + }); + + test(renderMode + ": jsPlumb.connect (connect by element, supplied endpoint and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = jsPlumb.addEndpoint(d1, endpoint); + var e2 = jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + assertContextSize(3); + jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + assertContextSize(2); + }); + + test(renderMode + ": jsPlumb.connect (connect by element, supplied endpoints using 'source' and 'target' (this test is identical to the one above apart from the param names), and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = jsPlumb.addEndpoint(d1, endpoint); + var e2 = jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + jsPlumb.connect({source:e1, target:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + assertContextSize(3); + jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + assertContextSize(2); + }); + + + test(renderMode + ": jsPlumb.connect (testing for connection event callback)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var connectCallback = null, detachCallback = null; + jsPlumb.bind("jsPlumbConnection", function(params) { + connectCallback = $.extend({}, params); + }); + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + detachCallback = $.extend({}, params); + }); + jsPlumb.connect({source:d1, target:d2}); // auto connect with default endpoint and anchor set + ok(connectCallback != null, "connect callback was made"); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + jsPlumb.detach({source:d1, target:d2}); + assertContextSize(2); + ok(detachCallback != null, "detach callback was made"); + }); + + test(renderMode + ": jsPlumb.connect (setting cssClass on Connector)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = jsPlumb.connect({source:d1,target:d2,cssClass:"CSS"}); + var has = function(clazz) { return $(c.connector.canvas).hasClass(clazz); }; + ok(has("CSS"), "custom cssClass set correctly"); + ok(has(jsPlumb.connectorClass), "basic connector class set correctly"); + }); + + test(renderMode + ": jsPlumb.connect (overlays, long-hand version)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = {width:40,length:40,location:0.7, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"}}; + var connection1 = jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + }); + + test(renderMode + ": jsPlumb.connect (overlays, long-hand version, IDs specified)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + equals("aLabel", connection1.overlays[0].id); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + equals("anArrow", connection1.overlays[1].id); + }); + + test(renderMode + ": jsPlumb.connect (remover single overlay by id)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + connection1.removeOverlay("aLabel"); + equals(1, connection1.overlays.length); + equals("anArrow", connection1.overlays[0].id); + }); + + test(renderMode + ": jsPlumb.connect (remove multiple overlays by id)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + connection1.removeOverlays("aLabel", "anArrow"); + equals(0, connection1.overlays.length); + }); + + test(renderMode + ": jsPlumb.connect (overlays, short-hand version)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var loc = { location:0.7 }; + var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} }; + var connection1 = jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label", {label:"CONNECTION 1", location:0.3}], + ["Arrow", arrowSpec, loc] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + }); + + test(renderMode + ": jsPlumb.connect (removeAllOverlays)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var loc = { location:0.7 }; + var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} }; + var connection1 = jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label", {label:"CONNECTION 1", location:0.3}], + ["Arrow", arrowSpec, loc] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + + connection1.removeAllOverlays(); + equals(0, connection1.overlays.length); + }); + + test(renderMode + ": jsPlumb.connect, specify arrow overlay using string identifier only", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var conn = jsPlumb.connect({source:d1,target:d2,overlays:["Arrow"]}); + equals(jsPlumb.Overlays[renderMode].Arrow, conn.overlays[0].constructor); + }); + + // this test is for the original detach function; it should stay working after i mess with it + // a little. + test(renderMode + ": jsPlumb.detach (by element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + var e3 = jsPlumb.addEndpoint(d1); + var e4 = jsPlumb.addEndpoint(d2); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + jsPlumb.connect({ sourceEndpoint:e3, targetEndpoint:e4 }); // make two connections to be sure this works ;) + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionCount(e3, 1); + assertConnectionCount(e4, 1); + + jsPlumb.detach("d1", "d2"); + + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionCount(e3, 0); + assertConnectionCount(e4, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + }); + + // detach is being made to operate more like connect - by taking one argument with a whole + // bunch of possible params in it. if two args are passed in it will continue working + // in the old way. + test(renderMode + ": jsPlumb.detach (params object, using element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + jsPlumb.detach({source:"d1", target:"d2"}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + }); + + test(renderMode + ": jsPlumb.detach (params object, using element objects)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + jsPlumb.detach({source:d1, target:d2}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + assertContextSize(2); + }); + + test(renderMode + ": jsPlumb.detach (source and target as endpoint UUIDs)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {uuid:"abcdefg"}); + ok(jsPlumb.getEndpoint("abcdefg") != null, "e1 exists"); + var e2 = jsPlumb.addEndpoint(d2, {uuid:"hijklmn"}); + ok(jsPlumb.getEndpoint("hijklmn") != null, "e2 exists"); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + jsPlumb.detach({uuids:["abcdefg", "hijklmn"]}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + assertContextSize(2); + }); + + test(renderMode + ": jsPlumb.detach (sourceEndpoint and targetEndpoint supplied)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + jsPlumb.detach({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + assertContextSize(2); + }); + + test(renderMode + ": jsPlumb.makeDynamicAnchors (longhand)", function() { + var anchors = [jsPlumb.makeAnchor(0.2, 0, 0, -1), jsPlumb.makeAnchor(1, 0.2, 1, 0), + jsPlumb.makeAnchor(0.8, 1, 0, 1), jsPlumb.makeAnchor(0, 0.8, -1, 0) ]; + var dynamicAnchor = jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); + }); + + test(renderMode + ": jsPlumb.makeDynamicAnchors (shorthand)", function() { + var anchors = [[0.2, 0, 0, -1], [1, 0.2, 1, 0], + [0.8, 1, 0, 1], [0, 0.8, -1, 0] ]; + var dynamicAnchor = jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); + }); + + test(renderMode + ": Connection.isVisible/setVisible", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c1 = jsPlumb.connect({source:d1,target:d2}); + equals(true, c1.isVisible(), "Connection is visible after creation."); + c1.setVisible(false); + equals(false, c1.isVisible(), "Connection is not visible after calling setVisible(false)."); + equals($(c1.connector.canvas).css("display"), "none"); + c1.setVisible(true); + equals(true, c1.isVisible(), "Connection is visible after calling setVisible(true)."); + equals($(c1.connector.canvas).css("display"), "block"); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible basic test (no connections)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals($(e1.canvas).css("display"), "none"); + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals($(e1.canvas).css("display"), "block"); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should track changes in the source, because it has only this connection.)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1), e2 = jsPlumb.addEndpoint(d2); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + var c1 = jsPlumb.connect({source:e1, target:e2}); + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals(false, e2.isVisible(), "other Endpoint is not visible either."); + equals(false, c1.isVisible(), "connection between the two is not visible either."); + + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals(true, e2.isVisible(), "other Endpoint is visible too"); + equals(true, c1.isVisible(), "connection between the two is visible too."); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should not track changes in the source, because it has another connection.)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var e1 = jsPlumb.addEndpoint(d1), e2 = jsPlumb.addEndpoint(d2, { maxConnections:2 }), e3 = jsPlumb.addEndpoint(d3); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + var c1 = jsPlumb.connect({source:e1, target:e2}); + var c2 = jsPlumb.connect({source:e2, target:e3}); + + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals(true, e2.isVisible(), "other Endpoint should still be visible."); + equals(true, e3.isVisible(), "third Endpoint should still be visible."); + equals(false, c1.isVisible(), "connection between the two is not visible either."); + equals(true, c2.isVisible(), "other connection is visible."); + + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals(true, e2.isVisible(), "other Endpoint is visible too"); + equals(true, c1.isVisible(), "connection between the two is visible too."); + equals(true, c2.isVisible(), "other connection is visible."); + }); + + // tests of the functionality that allows a user to specify that they want elements appended to the document body + test(renderMode + " jsPlumb.Defaults.Container, specified with a selector", function() { + jsPlumb.Defaults.Container = $("body"); + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length); // the divs we added have been added to the 'container' div. + // but we have told jsPlumb to add its canvas to the body, so this connect call should not add another few elements to the container: + jsPlumb.connect({source:d1, target:d2}); + equals(2, $("#container")[0].childNodes.length); + }); + + // tests of the functionality that allows a user to specify that they want elements appended to some specific container. + test(renderMode + " jsPlumb.Defaults.Container, specified with DOM element", function() { + jsPlumb.Defaults.Container = document.getElementsByTagName("body")[0]; + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length, "two divs added to the container"); // the divs we added have been added to the 'container' div. + // but we have told jsPlumb to add its canvas to the body, so this connect call should not add another few elements to the container: + var bodyElementCount = $("body")[0].childNodes.length; + jsPlumb.connect({source:d1, target:d2}); + equals(2, $("#container")[0].childNodes.length, "still only two children in container; elements were added to the body by jsPlumb"); + // test to see if 3 elements have been added + equals(bodyElementCount + 3, $("body")[0].childNodes.length, "3 new elements added to the document body"); + }); + + test(renderMode + " container specified to connect call, with a selector", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length); // the divs we added have been added to the 'container' div. + var bodyElementCount = $("body")[0].childNodes.length; + // but here we tell jsPlumb to add its elements to the body, so this connect call should not add another few elements to the container: + jsPlumb.connect({source:d1, target:d2, container:$("body")}); + equals(2, $("#container")[0].childNodes.length); + equals(bodyElementCount + 3, $("body")[0].childNodes.length, "3 new elements added to the document body"); + }); + + test(renderMode + " container specified to connect call, with a string ID", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + equals(3, $("#container")[0].childNodes.length, "container has divs we added"); // the divs we added have been added to the 'container' div. + var d3ElementCount = $("#d3")[0].childNodes.length; + // but here we tell jsPlumb to add its elements to "d3", so this connect call should not add another few elements to the container: + jsPlumb.connect({source:d1, target:d2, container:"d3"}); + equals(3, $("#container")[0].childNodes.length, "container still has only the divs we added"); + equals(d3ElementCount + 3, $("#d3")[0].childNodes.length, "3 new elements added to div d3"); + }); + + test(renderMode + " container specified to addEndpoint call, with a selector", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length); // the divs we added have been added to the 'container' div. + var bodyElementCount = $("body")[0].childNodes.length; + // but here we tell jsPlumb to add its elements to the body, so this connect call should not add another few elements to the container: + jsPlumb.addEndpoint(d1, {container:$("body")}); + equals(2, $("#container")[0].childNodes.length); + equals(bodyElementCount + 1, $("body")[0].childNodes.length, "1 new element added to the document body"); + }); + + test(renderMode + " container specified to addEndpoint call, with a string ID", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + equals(3, $("#container")[0].childNodes.length, "container has divs we added"); // the divs we added have been added to the 'container' div. + var d3ElementCount = $("#d3")[0].childNodes.length; + // but here we tell jsPlumb to add its elements to "d3", so this connect call should not add another few elements to the container: + jsPlumb.addEndpoint(d1, { container:"d3" }); + equals(3, $("#container")[0].childNodes.length, "container still has only the divs we added"); + equals(d3ElementCount + 1, $("#d3")[0].childNodes.length, "1 new element added to div d3"); + }); + + test(renderMode + " Connector has 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = jsPlumb.connect({source:d1, target:d2}); + equals(c.connector.type, "Bezier", "Bezier connector has type set"); + + var c2 = jsPlumb.connect({source:d1, target:d2, connector:"Straight"}); + equals(c2.connector.type, "Straight", "Straight connector has type set"); + + var c3 = jsPlumb.connect({source:d1, target:d2, connector:"Flowchart"}); + equals(c3.connector.type, "Flowchart", "Flowchart connector has type set"); + }); + + test(renderMode + " Endpoints have 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = jsPlumb.connect({source:d1, target:d2}); + equals(c.endpoints[0].type, "Dot", "Dot endpoint has type set"); + + var c2 = jsPlumb.connect({source:d1, target:d2, endpoints:["Rectangle", "Blank"]}); + equals(c2.endpoints[1].type, "Blank", "Blank endpoint has type set"); + equals(c2.endpoints[0].type, "Rectangle", "Rectangle endpoint has type set"); + }); + + test(renderMode + " Overlays have 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = jsPlumb.connect({ + source:d1, + target:d2, + overlays:[ "Arrow", "Label", "PlainArrow", "Diamond" ] + }); + equals(c.overlays[0].type, "Arrow", "Arrow overlay has type set"); + equals(c.overlays[1].type, "Label", "Label overlay has type set"); + equals(c.overlays[2].type, "PlainArrow", "PlainArrow overlay has type set"); + equals(c.overlays[3].type, "Diamond", "Diamond overlay has type set"); + }); + + test(renderMode + " jsPlumb.hide, original one-arg version", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = jsPlumb.addEndpoint(d1, e), + e2 = jsPlumb.addEndpoint(d2, e), + c1 = jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + jsPlumb.hide(d1); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(true, e1.isVisible(), "endpoint 1 is still visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + jsPlumb.show(d1); + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + }); + + test(renderMode + " jsPlumb.hide, two-arg version, endpoints should also be hidden", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = jsPlumb.addEndpoint(d1, e), + e2 = jsPlumb.addEndpoint(d2, e), + c1 = jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + jsPlumb.show(d1); // now show d1, but do not alter the endpoints. e1 should still be hidden + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + test(renderMode + " jsPlumb.show, two-arg version, endpoints should become visible", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = jsPlumb.addEndpoint(d1, e), + e2 = jsPlumb.addEndpoint(d2, e), + c1 = jsPlumb.connect({source:e1, target:e2}); + + jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + jsPlumb.show(d1, true); // now show d1, and alter the endpoints. e1 should be visible. + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(true, e1.isVisible(), "endpoint 1 is visible again."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + test(renderMode + " jsPlumb.show, two-arg version, endpoints should become visible, but not all connections, because some other endpoints are not visible.", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = jsPlumb.addEndpoint(d1, e), + e11 = jsPlumb.addEndpoint(d1, e), + e2 = jsPlumb.addEndpoint(d2, e), + e3 = jsPlumb.addEndpoint(d3, e), + c1 = jsPlumb.connect({source:e1, target:e2}); + c2 = jsPlumb.connect({source:e11, target:e3}); + + // we now have d1 connected to both d3 and d2. we'll hide d1, and everything on d1 should be hidden. + + jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "connection 1 is no longer visible."); + equals(false, c2.isVisible(), "connection 2 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(false, e11.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + equals(true, e3.isVisible(), "endpoint 3 is still visible."); + + // now, we will also hide d3. making d1 visible again should NOT result in c2 becoming visible, because the other endpoint + // for c2 is e3, which is not visible. + jsPlumb.hide(d3, true); + equals(false, e3.isVisible(), "endpoint 3 is no longer visible."); + + jsPlumb.show(d1, true); // now show d1, and alter the endpoints. e1 should be visible, c1 should be visible, but c2 should not. + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(false, c2.isVisible(), "Connection 2 is not visible."); + equals(true, e1.isVisible(), "endpoint 1 is visible again."); + equals(true, e11.isVisible(), "endpoint 11 is visible again."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + equals(false, e3.isVisible(), "endpoint 3 is still not visible."); + }); + + /* + test(renderMode + " jsPlumb.hide, two-arg version, endpoints should also be hidden", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = jsPlumb.addEndpoint(d1, e), + e2 = jsPlumb.addEndpoint(d2, e), + c1 = jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + /** + * leave this test at the bottom! + */ + test(renderMode + ': unload test', function() { + jsPlumb.unload(); + var contextNode = $("._jsPlumb_context"); + ok(contextNode.length == 0, 'context node unloaded'); + }); +}; + diff --git a/archive/1.3.2/jsPlumb-defaults-1.3.2-RC1.js b/archive/1.3.2/jsPlumb-defaults-1.3.2-RC1.js new file mode 100644 index 000000000..2f7d336ae --- /dev/null +++ b/archive/1.3.2/jsPlumb-defaults-1.3.2-RC1.js @@ -0,0 +1,932 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the default Connectors, Endpoint and Overlay definitions. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +(function() { + + /** + * + * Helper class to consume unused mouse events by components that are DOM elements and + * are used by all of the different rendering modes. + * + */ + jsPlumb.DOMElementComponent = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + // when render mode is canvas, these functions may be called by the canvas mouse handler. + // this component is safe to pipe this stuff to /dev/null. + this.mousemove = + this.dblclick = + this.click = + this.mousedown = + this.mouseup = function(e) { }; + }; + + /** + * Class: Connectors.Straight + * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters. + */ + jsPlumb.Connectors.Straight = function() { + this.type = "Straight"; + var self = this; + var currentPoints = null; + var _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + _dx = _tx - _sx, _dy = (_ty - _sy); + _m = _dy / _dx, _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + + return currentPoints; + }; + + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + var xp = _sx + (location * _dx); + var yp = (_m == Infinity || _m == -Infinity) ? _sy + (location * (_ty - _sy)) : (_m * xp) + _b; + return {x:xp, y:yp}; + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { return _m; }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + var orientation = distance > 0 ? 1 : -1; + var y = Math.abs(distance * Math.sin(_theta)); + if (_sy > _ty) y = y * -1; + var x = Math.abs(distance * Math.cos(_theta)); + if (_sx > _tx) x = x * -1; + return {x:p.x + (orientation * x), y:p.y + (orientation * y)}; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = self.gradientAtPoint(p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + }; + }; + + + /** + * Class:Connectors.Bezier + * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's + * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below. + */ + /** + * Function:Constructor + * + * Parameters: + * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line. + * Optional; defaults to 150. + * + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + params = params || {}; + this.majorAnchor = params.curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + this.type = "Bezier"; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h; + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) + { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx); var minx2 = Math.min(_CP[0], _CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(_sx,_tx); var maxx2 = Math.max(_CP[0], _CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty); var miny2 = Math.min(_CP[1], _CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(_sy,_ty); var maxy2 = Math.max(_CP[1], _CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + return jsBezier.perpendicularToCurveAt(_makeCurve(), location, length, distance); + }; + + }; + + + /** + * Class: Connectors.Flowchart + * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments. + */ + /** + * Function: Constructor + * + * Parameters: + * stub - minimum length for the stub at each end of the connector. defaults to 30 pixels. + */ + jsPlumb.Connectors.Flowchart = function(params) { + this.type = "Flowchart"; + params = params || {}; + var self = this, + minStubLength = params.stub || params.minStubLength /* bwds compat. */ || 30, + segments = [], + segmentGradients = [], + segmentProportions = [], + segmentLengths = [], + segmentProportionalLengths = [], + points = [], + swapX, + swapY, + /** + * recalculates the gradients of each segment, and the points at which the segments begin, proportional to the total length travelled + * by all the segments that constitute the connector. + */ + updateSegmentGradientsAndProportions = function(startX, startY, endX, endY) { + var total = 0; + for (var i = 0; i < segments.length; i++) { + var sx = i == 0 ? startX : segments[i][2], + sy = i == 0 ? startY : segments[i][3], + ex = segments[i][0], + ey = segments[i][1]; + + segmentGradients[i] = sx == ex ? Infinity : 0; + segmentLengths[i] = Math.abs(sx == ex ? ey - sy : ex - sx); + total += segmentLengths[i]; + } + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segmentLengths[i] / total; + segmentProportions[i] = [curLoc, (curLoc += (segmentLengths[i] / total)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0]; + var ly = segments.length == 0 ? sy : segments[segments.length - 1][1]; + segments.push([x, y, lx, ly]); + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment + * that contains the point which is 'location' distance along the entire path, where + * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths + * are made up of a list of segments, each of which contributes some fraction to + * the total length. + */ + findSegmentForLocation = function(location) { + var idx = segmentProportions.length - 1, inSegmentProportion = 0; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + segments = []; + segmentGradients = []; + segmentProportionalLengths = []; + segmentLengths = []; + segmentProportionals = []; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + offx = (lw / 2) + (minStubLength * 2), + offy = (lw / 2) + (minStubLength * 2), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(), + to = targetAnchor.orientation || targetAnchor.getOrientation(), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx, + h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy; + if (w < minWidth) { + offx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + offy += (minWidth - h) / 2; + h = minWidth; + } + var sx = swapX ? w-offx : offx, + sy = swapY ? h-offy : offy, + tx = swapX ? offx : w-offx , + ty = swapY ? offy : h-offy, + startStubX = sx + (so[0] * minStubLength), + startStubY = sy + (so[1] * minStubLength), + endStubX = tx + (to[0] * minStubLength), + endStubY = ty + (to[1] * minStubLength), + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2); + + x -= offx; y -= offy; + points = [x, y, w, h, sx, sy, tx, ty], extraPoints = []; + + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + if (so[0] == 0) { + var startStubIsBeforeEndStub = startStubY < endStubY; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } + } + else { + var startStubIsBeforeEndStub = startStubX < endStubX; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } + } + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentGradientsAndProportions(sx, sy, tx, ty); + + return points; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location) { + return self.pointAlongPathFrom(location, 0); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location) { + return segmentGradients[findSegmentForLocation(location)["index"]]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance) { + var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segmentLengths[s.index], m = segmentGradients[s.index]; + var e = { + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + return e; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = segmentGradients[p.segmentInfo.index]; + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + + }; + + }; + + // ********************************* END OF CONNECTOR TYPES ******************************************************************* + + // ********************************* ENDPOINT TYPES ******************************************************************* + + /** + * Class: Endpoints.Dot + * A round endpoint, with default radius 10 pixels. + */ + + /** + * Function: Constructor + * + * Parameters: + * + * radius - radius of the endpoint. defaults to 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + this.type = "Dot"; + var self = this; + params = params || {}; + this.radius = params.radius || 10; + this.defaultOffset = 0.5 * this.radius; + this.defaultInnerRadius = this.radius / 3; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var r = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - r; + var y = anchorPoint[1] - r; + return [ x, y, r * 2, r * 2, r ]; + }; + }; + + /** + * Class: Endpoints.Rectangle + * A Rectangular Endpoint, with default size 20x20. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the endpoint. defaults to 20 pixels. + * height - height of the endpoint. defaults to 20 pixels. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + this.type = "Rectangle"; + var self = this; + params = params || {}; + this.width = params.width || 20; + this.height = params.height || 20; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + return [ x, y, width, height]; + }; + }; + + /** + * Class: Endpoints.Image + * Draws an image as the Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * src - location of the image to use. + */ + jsPlumb.Endpoints.Image = function(params) { + + this.type = "Image"; + jsPlumb.DOMElementComponent.apply(this, arguments); + + var self = this, initialized = false; + this.img = new Image(); + self.ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.src || params.url; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + self.anchorPoint = anchorPoint; + if (self.ready) return [anchorPoint[0] - self.img.width / 2, anchorPoint[1] - self.img.height/ 2, self.img.width, self.img.height]; + else return [0,0,0,0]; + }; + + self.canvas = document.createElement("img"), initialized = false; + self.canvas.style["margin"] = 0; + self.canvas.style["padding"] = 0; + self.canvas.style["outline"] = 0; + self.canvas.style["position"] = "absolute"; + self.canvas.className = jsPlumb.endpointClass; + jsPlumb.appendElement(self.canvas, params.parent); + self.attachListeners(self.canvas, self); + + var actuallyPaint = function(d, style, anchor) { + if (!initialized) { + self.canvas.setAttribute("src", self.img.src); + initialized = true; + } + var width = self.img.width, + height = self.img.height, + x = self.anchorPoint[0] - (width/2), + y = self.anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(self.canvas, x, y, width, height); + }; + + this.paint = function(d, style, anchor) { + if (self.ready) { + actuallyPaint(d, style, anchor); + } + else { + window.setTimeout(function() { + self.paint(d, style, anchor); + }, 200); + } + }; + }; + + /** + * Class: Endpoints.Blank + * An Endpoint that paints nothing on the screen, and cannot be interacted with using the mouse. There are no constructor parameters for this Endpoint. + */ + jsPlumb.Endpoints.Blank = function(params) { + var self = this; + this.type = "Blank"; + jsPlumb.DOMElementComponent.apply(this, arguments); + this.compute = function() { + return [0,0,10,0]; + }; + + self.canvas = document.createElement("div"); + self.canvas.style.display = "block"; + self.canvas.style.width = "1px"; + self.canvas.style.height = "1px"; + self.canvas.style.background = "transparent"; + self.canvas.style.position = "absolute"; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function() { }; + }; + + /** + * Class: Endpoints.Triangle + * A triangular Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the triangle's base. defaults to 55 pixels. + * height - height of the triangle from base to apex. defaults to 55 pixels. + */ + jsPlumb.Endpoints.Triangle = function(params) { + this.type = "Triangle"; + params = params || { }; + params.width = params.width || 55; + param.height = params.height || 55; + this.width = params.width; + this.height = params.height; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + return [ x, y, width, height ]; + }; + }; +// ********************************* END OF ENDPOINT TYPES ******************************************************************* + + +// ********************************* OVERLAY DEFINITIONS *********************************************************************** + + /** + * Class: Overlays.Arrow + * + * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length + * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction + * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line + * across the tail. + */ + /** + * Function: Constructor + * + * Parameters: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + this.type = "Arrow"; + params = params || {}; + var self = this; + this.length = params.length || 20; + this.width = params.width || 20; + this.id = params.id; + this.connection = params.connection; + var direction = (params.direction || 1) < 0 ? -1 : 1; + var paintStyle = params.paintStyle || { lineWidth:1 }; + this.loc = params.location == null ? 0.5 : params.location; + // how far along the arrow the lines folding back in come to. default is 62.3%. + var foldback = params.foldback || 0.623; + var _getFoldBackPoint = function(connector, loc) { + if (foldback == 0.5) return connector.pointOnPath(loc); + else { + var adj = 0.5 - foldback; // we calculate relative to the center + return connector.pointAlongPathFrom(loc, direction * self.length * adj); + } + }; + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + // this is the arrow head position + var hxy = connector.pointAlongPathFrom(self.loc, direction * (self.length / 2)); + // this is the center of the tail + var txy = connector.pointAlongPathFrom(self.loc, -1 * direction * (self.length / 2)), tx = txy.x, ty = txy.y; + // this is the tail vector + var tail = connector.perpendicularToPathAt(self.loc, self.width, -1 * direction * (self.length / 2)); + // this is the point the tail goes in to + var cxy = _getFoldBackPoint(connector, self.loc); + + // if loc = 1, then hxy should be flush with the element, or if direction == -1, the tail midpoint. + if (self.loc == 1) { + var lxy = connector.pointOnPath(self.loc); + // TODO determine why the 1.2.6 released version does not + // use 'direction' in the two equations below, yet both + // that and 1.3.0 still paint the arrows correctly. + var dx = (lxy.x - hxy.x) * direction, dy = (lxy.y - hxy.y) * direction; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + // if loc = 0, then tail midpoint should be flush with the element, or, if direction == -1, hxy should be. + if (self.loc == 0) { + var lxy = connector.pointOnPath(self.loc); + var tailMid = foldback > 1 ? cxy : { + x:tail[0].x + ((tail[1].x - tail[0].x) / 2), + y:tail[0].y + ((tail[1].y - tail[0].y) / 2) + }; + var dx = (lxy.x - tailMid.x) * direction, dy = (lxy.y - tailMid.y) * direction; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x); + var maxx = Math.max(hxy.x, tail[0].x, tail[1].x); + var miny = Math.min(hxy.y, tail[0].y, tail[1].y); + var maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + var d = { hxy:hxy, tail:tail, cxy:cxy }, + strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle, + fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle, + lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth; + + self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions); + + return [ minx, maxx, miny, maxy]; + }; + }; + + /** + * Class: Overlays.PlainArrow + * + * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "PlainArrow"; + }; + + /** + * Class: Overlays.Diamond + * + * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40; + var p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "Diamond"; + }; + + + + /** + * Class: Overlays.Label + * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb + * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter + * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it + * puts on the Label's 'style' attribute, so the end result is the same. + */ + /** + * Function: Constructor + * + * Parameters: + * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes + * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle. + * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * labelStyle - (deprecated) js object containing style instructions for the label. defaults to jsPlumb.Defaults.LabelStyle. + * borderWidth - (deprecated) width of a border to paint. defaults to zero. + * borderStyle - (deprecated) strokeStyle to use when painting the border, if necessary. + * + */ + jsPlumb.Overlays.Label = function(params) { + this.type = "Label"; + jsPlumb.DOMElementComponent.apply(this, arguments); + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.labelStyle.font = this.labelStyle.font || "12px sans-serif"; + this.label = params.label || "banana"; + this.connection = params.connection; + this.id = params.id; + var self = this; + var labelWidth = null, labelHeight = null, labelText = null, labelPadding = null; + this.location = params.location || 0.5; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var initialised = false, + labelText = null, + div = document.createElement("div"); + div.style["position"] = "absolute"; + div.style["font"] = self.labelStyle.font; + div.style["color"] = self.labelStyle.color || "black"; + if (self.labelStyle.fillStyle) div.style["background"] = self.labelStyle.fillStyle;//_convertStyle(self.labelStyle.fillStyle, true); + if (self.labelStyle.borderWidth > 0) { + var dStyle = self.labelStyle.borderStyle ? self.labelStyle.borderStyle/*_convertStyle(self.labelStyle.borderStyle, true)*/ : "black"; + div.style["border"] = self.labelStyle.borderWidth + "px solid " + dStyle; + } + if (self.labelStyle.padding) div.style["padding"] = self.labelStyle.padding; + + var clazz = params["_jsPlumb"].overlayClass + " " + + (self.labelStyle.cssClass ? self.labelStyle.cssClass : + params.cssClass ? params.cssClass : ""); + + div.className = clazz; + + jsPlumb.appendElement(div, params.connection.parent); + jsPlumb.getId(div); + self.attachListeners(div, self); + this.paint = function(connector, d, connectorDimensions) { + if (!initialised) { + connector.appendDisplayElement(div); + self.attachListeners(div, connector); + initialised = true; + } + div.style.left = (connectorDimensions[0] + d.minx) + "px"; + div.style.top = (connectorDimensions[1] + d.miny) + "px"; + }; + + this.getTextDimensions = function(connector) { + labelText = typeof self.label == 'function' ? self.label(self) : self.label; + div.innerHTML = labelText.replace(/\r\n/g, "
"); + var de = jsPlumb.CurrentLibrary.getElementObject(div), + s = jsPlumb.CurrentLibrary.getSize(de); + return {width:s[0], height:s[1]}; + }; + + this.computeMaxSize = function(connector) { + var td = self.getTextDimensions(connector); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + var td = self.getTextDimensions(connector); + if (td.width != null) { + var cxy = connector.pointOnPath(self.location); + + var minx = cxy.x - (td.width / 2); + var miny = cxy.y - (td.height / 2); + + self.paint(connector, { + minx:minx, + miny:miny, + td:td, + cxy:cxy + }, connectorDimensions); + + return [minx, minx+td.width, miny, miny+td.height]; + } + else return [0,0,0,0]; + }; + }; + + // ********************************* END OF OVERLAY DEFINITIONS *********************************************************************** + + // ********************************* OVERLAY CANVAS RENDERERS*********************************************************************** + + // ********************************* END OF OVERLAY CANVAS RENDERERS *********************************************************************** +})(); \ No newline at end of file diff --git a/archive/1.3.2/jsPlumb-renderers-canvas-1.3.2-RC1.js b/archive/1.3.2/jsPlumb-renderers-canvas-1.3.2-RC1.js new file mode 100644 index 000000000..f9e45bd00 --- /dev/null +++ b/archive/1.3.2/jsPlumb-renderers-canvas-1.3.2-RC1.js @@ -0,0 +1,459 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the HTML5 canvas renderers. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +;(function() { + +// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS ******************************************************************* + + // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. + var _connectionBeingDragged = null, + _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }, + _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }, + _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }, + _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, + _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }, + _setOffset = function(el, o) { jsPlumb.CurrentLibrary.setOffset(el, o); }; + + /* + * Class:CanvasMouseAdapter + * Provides support for mouse events on canvases. + */ + var CanvasMouseAdapter = function() { + var self = this; + self.overlayPlacements = []; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + jsPlumb.EventGenerator.apply(this, arguments); + /** + * returns whether or not the given event is ojver a painted area of the canvas. + */ + this._over = function(e) { + var o = _getOffset(_getElementObject(self.canvas)), + pageXY = _pageXY(e), + x = pageXY[0] - o.left, y = pageXY[1] - o.top; + if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + return false; + }; + + var _mouseover = false; + var _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false; + var _nullSafeHasClass = function(el, clazz) { + return el != null && _hasClass(el, clazz); + }; + this.mousemove = function(e) { + var pageXY = _pageXY(e), clientXY = _clientXY(e), + ee = document.elementFromPoint(clientXY[0], clientXY[1]), + eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); + var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); + if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.fire("mouseenter", self, e); + return true; + } + // TODO here there is a remote chance that the overlay the mouse moved onto + // is actually not an overlay for the current component. a more thorough check would + // be to ensure the overlay belonged to the current component. + else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { + _mouseover = false; + self.fire("mouseexit", self, e); + } + self.fire("mousemove", self, e); + }; + + this.click = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); + self.fire("mousedown", self, e); + } + }; + + this.mouseup = function(e) { + //if (self == _connectionBeingDragged) _connectionBeingDragged = null; + _mouseDown = false; + self.fire("mouseup", self, e); + }; + }; + + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + jsPlumb.appendElement(canvas, params.parent); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + params["_jsPlumb"].getId(canvas, params.uuid); + + return canvas; + }; + + /** + * Class:CanvasConnector + * Superclass for Canvas Connector renderers. + */ + var CanvasConnector = jsPlumb.CanvasConnector = function(params) { + + CanvasMouseAdapter.apply(this, arguments); + + var _paintOneStyle = function(dim, aStyle) { + self.ctx.save(); + jsPlumb.extend(self.ctx, aStyle); + if (aStyle.gradient) { + var g = self.createGradient(dim, self.ctx); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + self.ctx.strokeStyle = g; + } + self._paint(dim); + self.ctx.restore(); + }; + + var self = this, + clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent + }); + self.ctx = self.canvas.getContext("2d"); + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + self.paint = function(dim, style) { + if (style != null) { + + jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); + + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + _paintOneStyle(dim, outlineStyle); + } + _paintOneStyle(dim, style); + } + }; + }; + + /** + * Class:CanvasEndpoint + * Superclass for Canvas Endpoint renderers. + */ + var CanvasEndpoint = function(params) { + var self = this; + CanvasMouseAdapter.apply(this, arguments); + var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent + }); + self.ctx = self.canvas.getContext("2d"); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + // _paintOneStyle(d, outlineStyle); + } + + self._paint.apply(this, arguments); + }; + }; + + jsPlumb.Endpoints.canvas.Dot = function(params) { + var self = this; + jsPlumb.Endpoints.Dot.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }; + var calculateAdjustments = function(gradient) { + var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius; + gradient.offset && (offsetAdjustment = parseValue(gradient.offset)); + gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius)); + return [offsetAdjustment, innerRadius]; + }; + this._paint = function(d, style, anchor) { + if (style != null) { + var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(); + jsPlumb.extend(ctx, style); + if (style.gradient) { + var adjustments = calculateAdjustments(style.gradient), + yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0], + xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0], + g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + ctx.beginPath(); + ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + } + }; + }; + + jsPlumb.Endpoints.canvas.Rectangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) { + + var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(); + jsPlumb.extend(ctx, style); + + /* canvas gradient */ + if (style.gradient) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, d[2], d[3]); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + jsPlumb.Endpoints.canvas.Triangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Triangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) + { + var width = d[2], height = d[3], x = d[0], y = d[1]; + + var ctx = self.canvas.getContext('2d'); + var offsetX = 0, offsetY = 0, angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = style.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + /* + * Canvas Image Endpoint: uses the default version, which creates an tag. + */ + jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image; + + /* + * Blank endpoint in all renderers is just the default Blank endpoint. + */ + jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank; + + /* + * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Bezier = function() { + var self = this; + jsPlumb.Connectors.Bezier.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO i doubt this handles the case that source and target are swapped. + this.createGradient = function(dim, ctx, swap) { + return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + /* + * Canvas straight line Connector. Draws a straight line onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Straight = function() { + var self = this; + jsPlumb.Connectors.Straight.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO this does not handle the case that src and target are swapped. + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + jsPlumb.Connectors.canvas.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + +// ********************************* END OF CANVAS RENDERERS ******************************************************************* + + jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label; + + /** + * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. + */ + var CanvasOverlay = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }; + + var AbstractCanvasArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + CanvasOverlay.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + var ctx = connector.ctx; + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(d.hxy.x, d.hxy.y); + ctx.lineTo(d.tail[0].x, d.tail[0].y); + ctx.lineTo(d.cxy.x, d.cxy.y); + ctx.lineTo(d.tail[1].x, d.tail[1].y); + ctx.lineTo(d.hxy.x, d.hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + }; + + jsPlumb.Overlays.canvas.Arrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.canvas.PlainArrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.canvas.Diamond = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.2/jsPlumb-renderers-svg-1.3.2-RC1.js b/archive/1.3.2/jsPlumb-renderers-svg-1.3.2-RC1.js new file mode 100644 index 000000000..6208ce723 --- /dev/null +++ b/archive/1.3.2/jsPlumb-renderers-svg-1.3.2-RC1.js @@ -0,0 +1,401 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the SVG renderers. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +/** + * SVG support for jsPlumb. + * + * things to investigate: + * + * gradients: https://developer.mozilla.org/en/svg_in_html_introduction + * css:http://tutorials.jenkov.com/svg/svg-and-css.html + * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath + * pointer events: https://developer.mozilla.org/en/css/pointer-events + * + */ +;(function() { + + var svgAttributeMap = { + "stroke-linejoin":"stroke-linejoin", + "joinstyle":"stroke-linejoin", + "stroke-dashoffset":"stroke-dashoffset" + }; + + var ns = { + svg:"http://www.w3.org/2000/svg", + xhtml:"http://www.w3.org/1999/xhtml" + }, + _attr = function(node, attributes) { + for (var i in attributes) + node.setAttribute(i, "" + attributes[i]); + }, + _node = function(name, attributes) { + var n = document.createElementNS(ns.svg, name); + attributes = attributes || {}; + attributes["version"] = "1.1"; + attributes["xmnls"] = ns.xhtml; + _attr(n, attributes); + return n; + }, + _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; }, + _convertStyle = function(s, ignoreAlpha) { + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + + return o; + }, + _clearGradient = function(parent) { + for (var i = 0; i < parent.childNodes.length; i++) { + if (parent.childNodes[i].tagName == "linearGradient" || parent.childNodes[i].tagName == "radialGradient") + parent.removeChild(parent.childNodes[i]); + } + }, + _updateGradient = function(parent, node, style, dimensions) { + var id = "jsplumb_gradient_" + (new Date()).getTime(); + // first clear out any existing gradient + _clearGradient(parent); + // this checks for an 'offset' property in the gradient, and in the absence of it, assumes + // we want a linear gradient. if it's there, we create a radial gradient. + // it is possible that a more explicit means of defining the gradient type would be + // better. relying on 'offset' means that we can never have a radial gradient that uses + // some default offset, for instance. + if (!style.gradient.offset) { + var g = _node("linearGradient", {id:id}); + parent.appendChild(g); + } + else { + var g = _node("radialGradient", { + id:id + }); + parent.appendChild(g); + } + + // the svg radial gradient seems to treat stops in the reverse + // order to how canvas does it. so we want to keep all the maths the same, but + // iterate the actual style declarations in reverse order, if the x indexes are not in order. + for (var i = 0; i < style.gradient.stops.length; i++) { + // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of + // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments. + // so, not too concerned about leaving this in for now. + var styleToUse = i; + if (dimensions.length == 8) + styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i; + else + styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i; + var stopColor = _convertStyle(style.gradient.stops[styleToUse][1], true); + var s = _node("stop", {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor}); + g.appendChild(s); + } + var applyGradientTo = style.strokeStyle ? "stroke" : "fill"; + node.setAttribute("style", applyGradientTo + ":url(#" + id + ")"); + }, + _applyStyles = function(parent, node, style, dimensions) { + + if (style.gradient) { + _updateGradient(parent, node, style, dimensions); + } + else { + // make sure we clear any existing gradient + _clearGradient(parent); + node.setAttribute("style", ""); + } + + node.setAttribute("fill", style.fillStyle ? _convertStyle(style.fillStyle, true) : "none"); + node.setAttribute("stroke", style.strokeStyle ? _convertStyle(style.strokeStyle, true) : "none"); + if (style.lineWidth) { + node.setAttribute("stroke-width", style.lineWidth); + } + + // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like + // the syntax in VML but is actually kind of nasty: values are given in the pixel + // coordinate space, whereas in VML they are multiples of the width of the stroked + // line, which makes a lot more sense. for that reason, jsPlumb is supporting both + // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from + // VML, which will be the preferred method. the code below this converts a dashstyle + // attribute given in terms of stroke width into a pixel representation, by using the + // stroke's lineWidth. + if(style["stroke-dasharray"]) { + node.setAttribute("stroke-dasharray", style["stroke-dasharray"]); + } + if (style["dashstyle"] && style["lineWidth"]) { + var sep = style["dashstyle"].indexOf(",") == -1 ? " " : ",", + parts = style["dashstyle"].split(sep), + styleToUse = ""; + parts.forEach(function(p) { + styleToUse += (Math.floor(p * style.lineWidth) + sep); + }); + node.setAttribute("stroke-dasharray", styleToUse); + } + + // extra attributes such as join type, dash offset. + for (var i in svgAttributeMap) { + if (style[i]) { + node.setAttribute(svgAttributeMap[i], style[i]); + } + } + }, + _decodeFont = function(f) { + var r = /([0-9].)(p[xt])\s(.*)/; + var bits = f.match(r); + return {size:bits[1] + bits[2], font:bits[3]}; + }; + + /* + * Base class for SVG components. + */ + var SvgComponent = function(cssClass, originalArgs, pointerEventsSpec) { + var self = this; + pointerEventsSpec = pointerEventsSpec || "all"; + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + self.canvas = null, self.path = null, self.svg = null; + + this.setHover = function() { }; + + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.sizeCanvas(self.canvas,0,0,1,1); + + var clazz = cssClass + " " + (originalArgs[0].cssClass || ""); + self.canvas.className = clazz; + + self.svg = _node("svg", { + "style":"", + "width":0, + "height":0, + "pointer-events":pointerEventsSpec/*, + "class": clazz*/ + }); + + jsPlumb.appendElement(self.canvas, originalArgs[0]["parent"]); + self.canvas.appendChild(self.svg); + + // TODO this displayElement stuff is common between all components, across all + // renderers. would be best moved to jsPlumbUIComponent. + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + this.paint = function(d, style, anchor) { + if (style != null) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + _attr(self.svg, { + "style":_pos([0,0,d[2], d[3]]), + "width": d[2], + "height": d[3] + }); + self._paint.apply(this, arguments); + } + }; + }; + + /* + * Base class for SVG connectors. + */ + var SvgConnector = function(params) { + var self = this; + SvgComponent.apply(this, [ params["_jsPlumb"].connectorClass, arguments, "none" ]); + this._paint = function(d, style) { + var p = self.getPath(d), a = { "d":p }, outlineStyle = null; + a["pointer-events"] = "all"; + + // outline style. actually means drawing an svg object underneath the main one. + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + outlineStyle = { + strokeStyle:_convertStyle(style.outlineColor), + lineWidth:outlineStrokeWidth + }; + + if (self.bgPath == null) { + self.bgPath = _node("path", a); + self.svg.appendChild(self.bgPath); + self.attachListeners(self.bgPath, self); + } + else { + _attr(self.bgPath, a); + } + + _applyStyles(self.svg, self.bgPath, outlineStyle, d); + } + + + if (self.path == null) { + self.path = _node("path", a); + self.svg.appendChild(self.path); + self.attachListeners(self.path, self); + } + else { + _attr(self.path, a); + } + + _applyStyles(self.svg, self.path, style, d); + }; + }; + + /* + * SVG Bezier Connector + */ + jsPlumb.Connectors.svg.Bezier = function(params) { + jsPlumb.Connectors.Bezier.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]; }; + }; + + /* + * SVG straight line Connector + */ + jsPlumb.Connectors.svg.Straight = function(params) { + jsPlumb.Connectors.Straight.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; }; + }; + + jsPlumb.Connectors.svg.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "M " + dimensions[4] + "," + dimensions[5]; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)]; + } + // finally draw a line to the end + p = p + " " + dimensions[6] + "," + dimensions[7]; + return p; + }; + }; + + /* + * Base class for SVG endpoints. + */ + var SvgEndpoint = function(params) { + var self = this; + SvgComponent.apply(this, [ params["_jsPlumb"].endpointClass, arguments, "all" ]); + this._paint = function(d, style) { + var s = jsPlumb.extend({}, style); + if (s.outlineColor) { + s.strokeWidth = s.outlineWidth; + s.strokeStyle = _convertStyle(s.outlineColor, true); + } + + if (self.node == null) { + self.node = self.makeNode(d, s); + self.svg.appendChild(self.node); + self.attachListeners(self.node, self); + } + _applyStyles(self.svg, self.node, s, d); + _pos(self.node, d); + }; + }; + + /* + * SVG Dot Endpoint + */ + jsPlumb.Endpoints.svg.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("circle", { + "cx" : d[2] / 2, + "cy" : d[3] / 2, + "r" : d[2] / 2 + }); + }; + }; + + /* + * SVG Rectangle Endpoint + */ + jsPlumb.Endpoints.svg.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("rect", { + "width":d[2], + "height":d[3] + }); + }; + }; + + /* + * SVG Image Endpoint is the default image endpoint. + */ + jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image; + /* + * Blank endpoint in svg renderer is the default Blank endpoint. + */ + jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank; + /* + * Label endpoint in svg renderer is the default Label endpoint. + */ + jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label; + + + var AbstractSvgArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + var self = this, path =null; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + } + + _attr(path, { + "d" : makePath(d), + stroke : strokeStyle ? strokeStyle : null, + fill : fillStyle ? fillStyle : null + }); + }; + var makePath = function(d) { + return "M" + d.hxy.x + "," + d.hxy.y + + " L" + d.tail[0].x + "," + d.tail[0].y + + " L" + d.cxy.x + "," + d.cxy.y + + " L" + d.tail[1].x + "," + d.tail[1].y + + " L" + d.hxy.x + "," + d.hxy.y; + }; + }; + + jsPlumb.Overlays.svg.Arrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.svg.PlainArrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.svg.Diamond = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.2/jsPlumb-renderers-vml-1.3.2-RC1.js b/archive/1.3.2/jsPlumb-renderers-vml-1.3.2-RC1.js new file mode 100644 index 000000000..115b599c8 --- /dev/null +++ b/archive/1.3.2/jsPlumb-renderers-vml-1.3.2-RC1.js @@ -0,0 +1,369 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the VML renderers. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +;(function() { + + // http://ajaxian.com/archives/the-vml-changes-in-ie-8 + // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/ + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + + var vmlAttributeMap = { + "stroke-linejoin":"joinstyle", + "joinstyle":"joinstyle", + "endcap":"endcap", + "miterlimit":"miterlimit" + }; + + if (document.createStyleSheet) { + + // this is the style rule for IE7/6: it uses a CSS class, tidy. + document.createStyleSheet().addRule(".jsplumb_vml", "behavior:url(#default#VML);position:absolute;"); + + // these are for VML in IE8. you have to explicitly call out which elements + // you're going to expect to support VML! + // + // try to avoid IE8. it is recommended you set X-UA-Compatible="IE=7" if you can. + // + document.createStyleSheet().addRule("jsplumb\\:textbox", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:oval", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:rect", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:stroke", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:shape", "behavior:url(#default#VML);position:absolute;"); + + // in this page it is also mentioned that IE requires the extra arg to the namespace + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either. + // var iev = document.documentMode; + //if (!iev || iev < 8) + document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); + //else + // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML"); + } + + var scale = 1000, + _atts = function(o, atts) { + for (var i in atts) { + // IE8 fix: setattribute does not work after an element has been added to the dom! + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + //o.setAttribute(i, atts[i]); + o[i] = atts[i]; + } + }, + _node = function(name, d, atts) { + atts = atts || {}; + var o = document.createElement("jsplumb:" + name); + o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml"; + _pos(o, d); + _atts(o, atts); + return o; + }, + _pos = function(o,d) { + o.style.left = d[0] + "px"; + o.style.top = d[1] + "px"; + o.style.width= d[2] + "px"; + o.style.height= d[3] + "px"; + o.style.position = "absolute"; + }, + _conv = function(v) { + return Math.floor(v * scale); + }, + _convertStyle = function(s, ignoreAlpha) { + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + + return o; + }, + _applyStyles = function(node, style, component) { + var styleToWrite = {}; + if (style.strokeStyle) { + styleToWrite["stroked"] = "true"; + styleToWrite["strokecolor"] =_convertStyle(style.strokeStyle, true); + styleToWrite["strokeweight"] = style.lineWidth + "px"; + } + else styleToWrite["stroked"] = "false"; + + if (style.fillStyle) { + styleToWrite["filled"] = "true"; + styleToWrite["fillcolor"] = _convertStyle(style.fillStyle, true); + } + else styleToWrite["filled"] = "false"; + + if(style["dashstyle"]) { + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = style["dashstyle"]; + } + else if (style["stroke-dasharray"] && style["lineWidth"]) { + var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",", + parts = style["stroke-dasharray"].split(sep), + styleToUse = ""; + for(var i = 0; i < parts.length; i++) { + styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep); + } + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = styleToUse; + } + + _atts(node, styleToWrite); + }, + /* + * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. + */ + VmlComponent = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }, + /* + * Base class for Vml connectors. extends VmlComponent. + */ + VmlConnector = function(params) { + var self = this; + self.strokeNode = null; + self.canvas = null; + VmlComponent.apply(this, arguments); + clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : ""); + this.paint = function(d, style, anchor) { + if (style != null) { + var path = self.getPath(d), p = { "path":path }; + + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + outlineStyle = { + strokeStyle:_convertStyle(style.outlineColor), + lineWidth:outlineStrokeWidth + }; + + if (self.bgCanvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.bgCanvas = _node("shape", d, p); + jsPlumb.appendElement(self.bgCanvas, params.parent); + _pos(self.bgCanvas, d); + displayElements.push(self.bgCanvas); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.bgCanvas, d); + _atts(self.bgCanvas, p); + } + + _applyStyles(self.bgCanvas, outlineStyle, self); + } + + if (self.canvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.canvas = _node("shape", d, p); + jsPlumb.appendElement(self.canvas, params.parent); + displayElements.push(self.canvas); + + self.attachListeners(self.canvas, self); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.canvas, d); + _atts(self.canvas, p); + } + + _applyStyles(self.canvas, style, self); + } + }; + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + self.canvas.parentNode.appendChild(el); + displayElements.push(el); + }; + }, + /* + * + * Base class for Vml Endpoints. extends VmlComponent. + * + */ + VmlEndpoint = function(params) { + VmlComponent.apply(this, arguments); + var vml = null, self = this; + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function(d, style, anchor) { + var p = { }; + + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (vml == null) { + p["class"] = jsPlumb.endpointClass; + vml = self.getVml([0,0, d[2], d[3]], p, anchor); + self.canvas.appendChild(vml); + self.attachListeners(vml, self); + } + else { + //p["coordsize"] = "1,1";//(d[2] * scale) + "," + (d[3] * scale); again, unsure. + _pos(vml, [0,0, d[2], d[3]]); + _atts(vml, p); + } + + _applyStyles(vml, style); + }; + }; + + jsPlumb.Connectors.vml.Bezier = function() { + jsPlumb.Connectors.Bezier.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Straight = function() { + jsPlumb.Connectors.Straight.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Flowchart = function() { + jsPlumb.Connectors.Flowchart.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l"; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]); + } + // finally draw a line to the end + p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e"; + return p; + }; + }; + + jsPlumb.Endpoints.vml.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("oval", d, atts); }; + }; + + jsPlumb.Endpoints.vml.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("rect", d, atts); }; + }; + + /* + * VML Image Endpoint is the same as the default image endpoint. + */ + jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image; + + /** + * placeholder for Blank endpoint in vml renderer. + */ + jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank; + + /** + * VML Label renderer. uses the default label renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label; + + var AbstractVmlArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + VmlComponent.apply(this, arguments); + var self = this, canvas = null, path =null; + var getPath = function(d, connectorDimensions) { + return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) + + " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + + " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + + " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + + " x e"; + }; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) { + var p = {}; + if (strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] =_convertStyle(strokeStyle, true); + } + if (lineWidth) p["strokeweight"] = lineWidth + "px"; + if (fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = fillStyle; + } + var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + w = Math.abs(xmax - xmin), + h = Math.abs(ymax - ymin), + dim = [xmin, ymin, w, h]; + + // for VML, we create overlays using shapes that have the same dimensions and + // coordsize as their connector - overlays calculate themselves relative to the + // connector (it's how it's been done since the original canvas implementation, because + // for canvas that makes sense). + p["path"] = getPath(d, connectorDimensions); + p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale); + + dim[0] = connectorDimensions[0]; + dim[1] = connectorDimensions[1]; + dim[2] = connectorDimensions[2]; + dim[3] = connectorDimensions[3]; + + if (canvas == null) { + //p["class"] = jsPlumb.overlayClass; // TODO currentInstance? + canvas = _node("shape", dim, p); + connector.appendDisplayElement(canvas); + self.attachListeners(canvas, connector); + } + else { + _pos(canvas, dim); + _atts(canvas, p); + } + }; + }; + + jsPlumb.Overlays.vml.Arrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.vml.PlainArrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.vml.Diamond = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.2/mootools.jsPlumb-1.3.2-RC1.js b/archive/1.3.2/mootools.jsPlumb-1.3.2-RC1.js new file mode 100644 index 000000000..2250fbc38 --- /dev/null +++ b/archive/1.3.2/mootools.jsPlumb-1.3.2-RC1.js @@ -0,0 +1,351 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the MooTools adapter. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +;(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function(now) { + this.parent(now); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }; + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + var _getElementObject = function(el) { + return $(el); + }; + + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + m.start(properties); + }, + + appendElement : function(child, parent) { + _getElementObject(parent).grab(child); + }, + + bind : function(el, event, callback) { + el = _getElementObject(el); + el.addEvent(event, callback); + }, + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.event.clientX, eventObject.event.clientY]; + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el); + var drags = _draggablesById[id]; + return drags[0].scope; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getElementObject : _getElementObject, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + getPageXY : function(eventObject) { + return [eventObject.event.pageX, eventObject.event.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).getParent(); + }, + + getScrollLeft : function(el) { + return null; + }, + + getScrollTop : function(el) { + return null; + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[0], + p = jsPlumb.CurrentLibrary.getElementObject(ui).getPosition(); + return { left:p.x, top:p.y }; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options) { + var id = jsPlumb.getId(el); + var drag = _draggablesById[id]; + if (!drag) { + var originalZIndex = 0, originalCursor = null; + var dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + options['onStart'] = jsPlumb.wrap(options['onStart'], function() + { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() + { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + }); + + // DROPPABLES: + var scope = options['scope'] || jsPlumb.Defaults.Scope; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop'); + } + }; + + drag = new Drag.Move(el, options); + drag.scope = scope; + //console.log("drag scope initialized to ", scope); + _add(_draggablesByScope, scope, drag); + _add(_draggablesById, el.get("id"), drag); + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + initDroppable : function(el, options) { + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _add(_droppables, scope, el); + var id = jsPlumb.getId(el); + _droppableOptions[id] = options; + _droppableScopesById[id] = scope; + var filterFunc = function(entry) { return entry.element != el; }; + var draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + isAlreadyDraggable : function(el) { + return _draggablesById[jsPlumb.getId(el)] != null; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + return (typeof Drag != undefined && typeof Drag.Move != undefined); + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + _getElementObject(element).dispose(); // ?? + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + setDragScope : function(el, scope) { + var drag = _draggablesById[el.get("id")]; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + drag[0].droppables = droppables; + }, + + setOffset : function(el, o) { + _getElementObject(el).setPosition({x:o.left, y:o.top}); + } + }; + + window.addEvent('domready', jsPlumb.init); +})(); diff --git a/archive/1.3.2/mootools.jsPlumb-1.3.2-all-min.js b/archive/1.3.2/mootools.jsPlumb-1.3.2-all-min.js new file mode 100644 index 000000000..1b79a4a26 --- /dev/null +++ b/archive/1.3.2/mootools.jsPlumb-1.3.2-all-min.js @@ -0,0 +1 @@ +(function(){var r=!!document.createElement("canvas").getContext;var d=!!window.SVGAngle||document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1");var a=!(r|d);var l=function(y,z,w,C){var B=function(E,D){if(E===D){return true}else{if(typeof E=="object"&&typeof D=="object"){var F=true;for(var v in E){if(!B(E[v],D[v])){F=false;break}}for(var v in D){if(!B(D[v],E[v])){F=false;break}}return F}}};for(var A=+w||0,x=y.length;A=0){delete (ax[ay]);ax.splice(ay,1);return true}}}return false},v=function(ay,ax){return I(ay,function(az,aA){ag[aA]=ax;if(i.CurrentLibrary.isDragSupported(az)){i.CurrentLibrary.setDraggable(az,ax)}})},ao=function(az,aA,ax){aA=aA==="block";var ay=null;if(ax){if(aA){ay=function(aC){aC.setVisible(true,true,true)}}else{ay=function(aC){aC.setVisible(false,true,true)}}}var aB=c(az,"id");U(aB,function(aD){if(aA&&ax){var aC=aD.sourceId===aB?1:0;if(aD.endpoints[aC].isVisible()){aD.setVisible(true)}}else{aD.setVisible(aA)}},ay)},N=function(ax){return I(ax,function(az,ay){var aA=ag[ay]==null?Y:ag[ay];aA=!aA;ag[ay]=aA;i.CurrentLibrary.setDraggable(az,aA);return aA})},x=function(ax,az){var ay=null;if(az){ay=function(aA){var aB=aA.isVisible();aA.setVisible(!aB)}}U(ax,function(aB){var aA=aB.isVisible();aB.setVisible(!aA)},ay)},E=function(aC){var aA=aC.timestamp,ax=aC.recalc,aB=aC.offset,ay=aC.elId;if(!ax){if(aA&&aA===C[ay]){return V[ay]}}if(ax||aB==null){var az=u(ay);if(az!=null){P[ay]=b(az);V[ay]=n(az);C[ay]=aA}}else{V[ay]=aB}return V[ay]},aw=function(ax,ay){var az=u(ax);var aA=c(az,"id");if(!aA||aA=="undefined"){if(arguments.length==2&&arguments[1]!=undefined){aA=ay}else{aA="jsPlumb_"+ar()}e(az,"id",aA)}return aA},am=function(az,ax,ay){az=az||function(){};ax=ax||function(){};return function(){var aA=null;try{aA=ax.apply(this,arguments)}catch(aB){k(D,"jsPlumb function failed : "+aB)}if(ay==null||(aA!==ay)){try{az.apply(this,arguments)}catch(aB){k(D,"wrapped function failed : "+aB)}}return aA}};this.connectorClass="_jsPlumb_connector";this.endpointClass="_jsPlumb_endpoint";this.overlayClass="_jsPlumb_overlay";this.Anchors={};this.Connectors={canvas:{},svg:{},vml:{}};this.Endpoints={canvas:{},svg:{},vml:{}};this.Overlays={canvas:{},svg:{},vml:{}};this.addEndpoint=function(az,aA,aJ){aJ=aJ||{};var ay=i.extend({},aJ);i.extend(ay,aA);ay.endpoint=ay.endpoint||D.Defaults.Endpoint||i.Defaults.Endpoint;ay.paintStyle=ay.paintStyle||D.Defaults.EndpointStyle||i.Defaults.EndpointStyle;az=w(az);var aB=[],aE=az.length&&az.constructor!=String?az:[az];for(var aC=0;aC0?l(aK,aJ)!=-1:true},aB=aE.length>1?{}:[],aH=function(aK,aL){if(aE.length>1){var aJ=aB[aK];if(aJ==null){aJ=[];aB[aK]=aJ}aJ.push(aL)}else{aB.push(aL)}};for(var aA in L){if(ay(aE,aA)){for(var az=0;az=4){ay.orientation=[arguments[2],arguments[3]]}if(arguments.length==6){ay.offsets=[arguments[4],arguments[5]]}}var aD=new ac(ay);aD.clone=function(){return new ac(ay)};return aD};this.makeAnchors=function(ay){var az=[];for(var ax=0;ax0?aG[0]:null,aC=aG.length>0?0:-1,aF=this,aB=function(aJ,aH,aN,aM,aI){var aL=aM[0]+(aJ.x*aI[0]),aK=aM[1]+(aJ.y*aI[1]);return Math.sqrt(Math.pow(aH-aL,2)+Math.pow(aN-aK,2))},ax=ay||function(aR,aI,aJ,aK,aH){var aM=aJ[0]+(aK[0]/2),aL=aJ[1]+(aK[1]/2);var aO=-1,aQ=Infinity;for(var aN=0;aNaX){aX=a4}}var a8=this.connector.compute(aZ,ba,this.endpoints[be].anchor,this.endpoints[aW].anchor,aI.paintStyleInUse.lineWidth,aX);aI.connector.paint(a8,aI.paintStyleInUse);for(var a9=0;a9=0){aN.connections.splice(aZ,1);if(!a2){var a1=a0.endpoints[0]==aN?a0.endpoints[1]:a0.endpoints[0];a1.detach(a0,true);if(a0.endpointToDeleteOnDetach&&a0.endpointToDeleteOnDetach.connections.length==0){i.deleteEndpoint(a0.endpointToDeleteOnDetach)}}Q(a0.connector.getDisplayElements(),a0.parent);M(L,a0.scope,a0);if(!a2){ad(a0)}}};this.detachAll=function(){while(aN.connections.length>0){aN.detach(aN.connections[0])}};this.detachFrom=function(a0){var a1=[];for(var aZ=0;aZ=0){aN.connections.splice(aZ,1)}};this.getElement=function(){return aM};this.getUuid=function(){return aH};this.makeInPlaceCopy=function(){return an({anchor:aN.anchor,source:aM,paintStyle:this.paintStyle,endpoint:aL})};this.isConnectedTo=function(a1){var a0=false;if(a1){for(var aZ=0;aZ0){var a9=aK(a2.elementWithPrecedence);var bb=a9.endpoints[0]==aN?1:0;var a4=bb==0?a9.sourceId:a9.targetId;var a8=V[a4],ba=P[a4];a0.txy=[a8.left,a8.top];a0.twh=ba;a0.tElement=a9.endpoints[bb]}}a5=aN.anchor.compute(a0)}var a7=aL.compute(a5,aN.anchor.getOrientation(),aN.paintStyleInUse,a3||aN.paintStyleInUse);aL.paint(a7,aN.paintStyleInUse,aN.anchor);aN.timestamp=a6}};this.repaint=this.paint;this.removeConnection=this.detach;if(aY.isSource&&i.CurrentLibrary.isDragSupported(aM)){var aS=null,aO=null,aR=null,ax=false,aA=null;var aC=function(){aR=aN.connectorSelector();if(aN.isFull()&&!aI){return false}E({elId:aE});aB=aN.makeInPlaceCopy();aB.paint();aS=document.createElement("div");aS.style.position="absolute";var a6=u(aS);R(aS,aN.parent);var a0=aw(a6);var a7=u(aB.canvas),a5=i.CurrentLibrary.getOffset(a7),a2=aB.canvas.offsetParent!=null?aB.canvas.offsetParent.tagName.toLowerCase()==="body"?{left:0,top:0}:n(aB.canvas.offsetParent):{left:0,top:0};i.CurrentLibrary.setOffset(aS,{left:a5.left-a2.left,top:a5.top-a2.top});E({elId:a0});e(u(aN.canvas),"dragId",a0);e(u(aN.canvas),"elId",aE);var a8=new H({reference:aN.anchor,referenceCanvas:aN.canvas});aW=an({paintStyle:aN.paintStyle,endpoint:aL,anchor:a8,source:a6});if(aR==null){aN.anchor.locked=true;aR=au({sourceEndpoint:aN,targetEndpoint:aW,source:u(aM),target:u(aS),anchors:[aN.anchor,a8],paintStyle:aY.connectorStyle,hoverPaintStyle:aY.connectorHoverStyle,connector:aY.connector,overlays:aY.connectorOverlays});aR.connector.setHover(false)}else{ax=true;aR.connector.setHover(false);aD(u(aB.canvas));var a1=aR.sourceId==aE?0:1;aR.floatingAnchorIndex=a1;aN.detachFromConnection(aR);var a4=u(aN.canvas);var a3=i.CurrentLibrary.getDragScope(a4);e(a4,"originalScope",a3);var aZ=i.CurrentLibrary.getDropScope(a4);i.CurrentLibrary.setDragScope(a4,aZ);if(a1==0){aA=[aR.source,aR.sourceId,aV,a3];aR.source=u(aS);aR.sourceId=a0}else{aA=[aR.target,aR.targetId,aV,a3];aR.target=u(aS);aR.targetId=a0}aR.endpoints[a1==0?1:0].anchor.locked=true;aR.suspendedEndpoint=aR.endpoints[a1];aR.endpoints[a1]=aW}Z[a0]=aR;aW.addConnection(aR);S(ah,a0,aW);D.currentlyDragging=true};var ay=i.CurrentLibrary,aU=aY.dragOptions||{},aP=i.extend({},ay.defaultDragOptions),aQ=ay.dragEvents.start,aX=ay.dragEvents.stop,aG=ay.dragEvents.drag;aU=i.extend(aP,aU);aU.scope=aU.scope||aN.scope;aU[aQ]=am(aU[aQ],aC);aU[aG]=am(aU[aG],function(){var aZ=i.CurrentLibrary.getUIPosition(arguments);i.CurrentLibrary.setOffset(aS,aZ);at(u(aS),aZ)});aU[aX]=am(aU[aX],function(){M(ah,aO,aW);Q([aS,aW.canvas],aM);ae(aB.canvas,aM);var aZ=aR.floatingAnchorIndex==null?1:aR.floatingAnchorIndex;aR.endpoints[aZ==0?1:0].anchor.locked=false;if(aR.endpoints[aZ]==aW){if(ax&&aR.suspendedEndpoint){if(aZ==0){aR.source=aA[0];aR.sourceId=aA[1]}else{aR.target=aA[0];aR.targetId=aA[1]}i.CurrentLibrary.setDragScope(aA[2],aA[3]);aR.endpoints[aZ]=aR.suspendedEndpoint;if(aJ){aR.floatingAnchorIndex=null;aR.suspendedEndpoint.addConnection(aR);i.repaint(aA[1])}else{aR.endpoints[aZ==0?1:0].detach(aR)}}else{Q(aR.connector.getDisplayElements(),aN.parent);aN.detachFromConnection(aR)}}aN.anchor.locked=false;aN.paint();aR.setHover(false);aR.repaint();aR=null;delete aB;delete ah[aW.elementId];aW=null;delete aW;D.currentlyDragging=false});var aV=u(aN.canvas);i.CurrentLibrary.initDraggable(aV,aU)}var aD=function(a2){if(aY.isTarget&&i.CurrentLibrary.isDropSupported(aM)){var aZ=aY.dropOptions||D.Defaults.DropOptions||i.Defaults.DropOptions;aZ=i.extend({},aZ);aZ.scope=aZ.scope||aN.scope;var a5=null;var a3=i.CurrentLibrary.dragEvents.drop;var a4=i.CurrentLibrary.dragEvents.over;var a0=i.CurrentLibrary.dragEvents.out;var a1=function(){var be=u(i.CurrentLibrary.getDragObject(arguments));var a6=c(be,"dragId");var a8=c(be,"elId");var bd=c(be,"originalScope");if(bd){i.CurrentLibrary.setDragScope(be,bd)}var ba=Z[a6];var bb=ba.floatingAnchorIndex==null?1:ba.floatingAnchorIndex,bc=bb==0?1:0;if(!aN.isFull()&&!(bb==0&&!aN.isSource)&&!(bb==1&&!aN.isTarget)){if(bb==0){ba.source=aM;ba.sourceId=aE}else{ba.target=aM;ba.targetId=aE}ba.endpoints[bb].detachFromConnection(ba);if(ba.suspendedEndpoint){ba.suspendedEndpoint.detachFromConnection(ba)}ba.endpoints[bb]=aN;aN.addConnection(ba);if(!ba.suspendedEndpoint){S(L,ba.scope,ba);O(aM,aY.draggable,{})}else{var a9=ba.suspendedEndpoint.getElement(),a7=ba.suspendedEndpoint.elementId;D.fire("jsPlumbConnectionDetached",{source:bb==0?a9:ba.source,target:bb==1?a9:ba.target,sourceId:bb==0?a7:ba.sourceId,targetId:bb==1?a7:ba.targetId,sourceEndpoint:bb==0?ba.suspendedEndpoint:ba.endpoints[0],targetEndpoint:bb==1?ba.suspendedEndpoint:ba.endpoints[1],connection:ba})}i.repaint(a8);D.fire("jsPlumbConnection",{source:ba.source,target:ba.target,sourceId:ba.sourceId,targetId:ba.targetId,sourceEndpoint:ba.endpoints[0],targetEndpoint:ba.endpoints[1],connection:ba})}D.currentlyDragging=false;delete Z[a6]};aZ[a3]=am(aZ[a3],a1);aZ[a4]=am(aZ[a4],function(){var a7=i.CurrentLibrary.getDragObject(arguments);var a9=c(u(a7),"dragId");var a8=Z[a9];if(a8!=null){var a6=a8.floatingAnchorIndex==null?1:a8.floatingAnchorIndex;a8.endpoints[a6].anchor.over(aN.anchor)}});aZ[a0]=am(aZ[a0],function(){var a7=i.CurrentLibrary.getDragObject(arguments),a9=c(u(a7),"dragId"),a8=Z[a9];if(a8!=null){var a6=a8.floatingAnchorIndex==null?1:a8.floatingAnchorIndex;a8.endpoints[a6].anchor.out()}});i.CurrentLibrary.initDroppable(a2,aZ)}};aD(u(aN.canvas));return aN}};var i=window.jsPlumb=new q();i.getInstance=function(w){var v=new q(w);v.init();return v};var m=function(v,A,z,w){return function(){return i.makeAnchor(v,A,z,w)}};i.Anchors.TopCenter=m(0.5,0,0,-1);i.Anchors.BottomCenter=m(0.5,1,0,1);i.Anchors.LeftMiddle=m(0,0.5,-1,0);i.Anchors.RightMiddle=m(1,0.5,1,0);i.Anchors.Center=m(0.5,0.5,0,0);i.Anchors.TopRight=m(1,0,0,-1);i.Anchors.BottomRight=m(1,1,0,1);i.Anchors.TopLeft=m(0,0,0,-1);i.Anchors.BottomLeft=m(0,1,0,1);i.Defaults.DynamicAnchors=function(){return i.makeAnchors(["TopCenter","RightMiddle","BottomCenter","LeftMiddle"])};i.Anchors.AutoDefault=function(){return i.makeDynamicAnchor(i.Defaults.DynamicAnchors())}})();(function(){jsPlumb.DOMElementComponent=function(a){jsPlumb.jsPlumbUIComponent.apply(this,arguments);this.mousemove=this.dblclick=this.click=this.mousedown=this.mouseup=function(b){}};jsPlumb.Connectors.Straight=function(){this.type="Straight";var n=this;var g=null;var c,h,l,k,i,d,m,f,e,b,a;this.compute=function(r,F,B,o,v,q){var E=Math.abs(r[0]-F[0]);var u=Math.abs(r[1]-F[1]);var z=false,s=false;var t=0.45*E,p=0.45*u;E*=1.9;u*=1.9;var C=Math.min(r[0],F[0])-t;var A=Math.min(r[1],F[1])-p;var D=Math.max(2*v,q);if(E0?1:-1;var t=Math.abs(u*Math.sin(d));if(e>a){t=t*-1}var o=Math.abs(u*Math.cos(d));if(f>b){o=o*-1}return{x:s.x+(r*o),y:s.y+(r*t)}};this.perpendicularToPathAt=function(s,t,w){var u=n.pointAlongPathFrom(s,w);var r=n.gradientAtPoint(u.location);var q=Math.atan(-1/r);var v=t/2*Math.sin(q);var o=t/2*Math.cos(q);return[{x:u.x+o,y:u.y+v},{x:u.x-o,y:u.y-v}]}};jsPlumb.Connectors.Bezier=function(e){var o=this;e=e||{};this.majorAnchor=e.curviness||150;this.minorAnchor=10;var h=null;this.type="Bezier";this._findControlPoint=function(z,q,u,x,r){var w=x.getOrientation(),y=r.getOrientation();var t=w[0]!=y[0]||w[1]==y[1];var s=[];var A=o.majorAnchor,v=o.minorAnchor;if(!t){if(w[0]==0){s.push(q[0]l){l=u}if(x<0){d+=x;var y=Math.abs(x);l+=y;n[0]+=y;i+=y;b+=y;m[0]+=y}var G=Math.min(f,a);var E=Math.min(n[1],m[1]);var t=Math.min(G,E);var z=Math.max(f,a);var w=Math.max(n[1],m[1]);var r=Math.max(z,w);if(r>g){g=r}if(t<0){c+=t;var v=Math.abs(t);g+=v;n[1]+=v;f+=v;a+=v;m[1]+=v}if(D&&l=s){q=t;r=(s-l[t][0])/a[t];break}}return{segment:i[q],proportion:r,index:q}};this.compute=function(Q,v,P,r,q,H){i=[];h=[];a=[];g=[];segmentProportionals=[];d=v[0]t[0]?t[0]+((1-x)*r)-y:t[2]+(x*r)+y,y:q==0?t[3]:t[3]>t[1]?t[1]+((1-x)*r)-y:t[3]+(x*r)+y,segmentInfo:v};return w};this.perpendicularToPathAt=function(t,u,z){var v=n.pointAlongPathFrom(t,z);var s=h[v.segmentInfo.index];var r=Math.atan(-1/s);var w=u/2*Math.sin(r);var q=u/2*Math.cos(r);return[{x:v.x+q,y:v.y+w},{x:v.x-q,y:v.y-w}]}};jsPlumb.Endpoints.Dot=function(b){this.type="Dot";var a=this;b=b||{};this.radius=b.radius||10;this.defaultOffset=0.5*this.radius;this.defaultInnerRadius=this.radius/3;this.compute=function(g,d,i,f){var e=i.radius||a.radius;var c=g[0]-e;var h=g[1]-e;return[c,h,e*2,e*2,e]}};jsPlumb.Endpoints.Rectangle=function(b){this.type="Rectangle";var a=this;b=b||{};this.width=b.width||20;this.height=b.height||20;this.compute=function(h,e,k,g){var f=k.width||a.width;var d=k.height||a.height;var c=h[0]-(f/2);var i=h[1]-(d/2);return[c,i,f,d]}};jsPlumb.Endpoints.Image=function(d){this.type="Image";jsPlumb.DOMElementComponent.apply(this,arguments);var a=this,c=false;this.img=new Image();a.ready=false;this.img.onload=function(){a.ready=true};this.img.src=d.src||d.url;this.compute=function(g,e,h,f){a.anchorPoint=g;if(a.ready){return[g[0]-a.img.width/2,g[1]-a.img.height/2,a.img.width,a.img.height]}else{return[0,0,0,0]}};a.canvas=document.createElement("img"),c=false;a.canvas.style.margin=0;a.canvas.style.padding=0;a.canvas.style.outline=0;a.canvas.style.position="absolute";a.canvas.className=jsPlumb.endpointClass;jsPlumb.appendElement(a.canvas,d.parent);a.attachListeners(a.canvas,a);var b=function(k,i,g){if(!c){a.canvas.setAttribute("src",a.img.src);c=true}var h=a.img.width,f=a.img.height,e=a.anchorPoint[0]-(h/2),l=a.anchorPoint[1]-(f/2);jsPlumb.sizeCanvas(a.canvas,e,l,h,f)};this.paint=function(g,f,e){if(a.ready){b(g,f,e)}else{window.setTimeout(function(){a.paint(g,f,e)},200)}}};jsPlumb.Endpoints.Blank=function(b){var a=this;this.type="Blank";jsPlumb.DOMElementComponent.apply(this,arguments);this.compute=function(){return[0,0,10,0]};a.canvas=document.createElement("div");a.canvas.style.display="block";a.canvas.style.width="1px";a.canvas.style.height="1px";a.canvas.style.background="transparent";a.canvas.style.position="absolute";jsPlumb.appendElement(a.canvas,b.parent);this.paint=function(){}};jsPlumb.Endpoints.Triangle=function(a){this.type="Triangle";a=a||{};a.width=a.width||55;param.height=a.height||55;this.width=a.width;this.height=a.height;this.compute=function(g,d,i,f){var e=i.width||self.width;var c=i.height||self.height;var b=g[0]-(e/2);var h=g[1]-(c/2);return[b,h,e,c]}};jsPlumb.Overlays.Arrow=function(f){this.type="Arrow";f=f||{};var b=this;this.length=f.length||20;this.width=f.width||20;this.id=f.id;this.connection=f.connection;var e=(f.direction||1)<0?-1:1;var c=f.paintStyle||{lineWidth:1};this.loc=f.location==null?0.5:f.location;var a=f.foldback||0.623;var d=function(g,i){if(a==0.5){return g.pointOnPath(i)}else{var h=0.5-a;return g.pointAlongPathFrom(i,e*b.length*h)}};this.computeMaxSize=function(){return b.width*1.5};this.draw=function(k,q,w){var y=k.pointAlongPathFrom(b.loc,e*(b.length/2));var t=k.pointAlongPathFrom(b.loc,-1*e*(b.length/2)),B=t.x,A=t.y;var r=k.perpendicularToPathAt(b.loc,b.width,-1*e*(b.length/2));var i=d(k,b.loc);if(b.loc==1){var h=k.pointOnPath(b.loc);var v=(h.x-y.x)*e,u=(h.y-y.y)*e;i.x+=v;i.y+=u;t.x+=v;t.y+=u;r[0].x+=v;r[0].y+=u;r[1].x+=v;r[1].y+=u;y.x+=v;y.y+=u}if(b.loc==0){var h=k.pointOnPath(b.loc);var s=a>1?i:{x:r[0].x+((r[1].x-r[0].x)/2),y:r[0].y+((r[1].y-r[0].y)/2)};var v=(h.x-s.x)*e,u=(h.y-s.y)*e;i.x+=v;i.y+=u;t.x+=v;t.y+=u;r[0].x+=v;r[0].y+=u;r[1].x+=v;r[1].y+=u;y.x+=v;y.y+=u}var o=Math.min(y.x,r[0].x,r[1].x);var n=Math.max(y.x,r[0].x,r[1].x);var m=Math.min(y.y,r[0].y,r[1].y);var l=Math.max(y.y,r[0].y,r[1].y);var z={hxy:y,tail:r,cxy:i},x=c.strokeStyle||q.strokeStyle,p=c.fillStyle||q.strokeStyle,g=c.lineWidth||q.lineWidth;b.paint(k,z,g,x,p,w);return[o,n,m,l]}};jsPlumb.Overlays.PlainArrow=function(b){b=b||{};var a=jsPlumb.extend(b,{foldback:1});jsPlumb.Overlays.Arrow.call(this,a);this.type="PlainArrow"};jsPlumb.Overlays.Diamond=function(c){c=c||{};var a=c.length||40;var b=jsPlumb.extend(c,{length:a/2,foldback:2});jsPlumb.Overlays.Arrow.call(this,b);this.type="Diamond"};jsPlumb.Overlays.Label=function(d){this.type="Label";jsPlumb.DOMElementComponent.apply(this,arguments);this.labelStyle=d.labelStyle||jsPlumb.Defaults.LabelStyle;this.labelStyle.font=this.labelStyle.font||"12px sans-serif";this.label=d.label||"banana";this.connection=d.connection;this.id=d.id;var k=this;var h=null,e=null,c=null,b=null;this.location=d.location||0.5;this.cachedDimensions=null;var i=false,c=null,a=document.createElement("div");a.style.position="absolute";a.style.font=k.labelStyle.font;a.style.color=k.labelStyle.color||"black";if(k.labelStyle.fillStyle){a.style.background=k.labelStyle.fillStyle}if(k.labelStyle.borderWidth>0){var g=k.labelStyle.borderStyle?k.labelStyle.borderStyle:"black";a.style.border=k.labelStyle.borderWidth+"px solid "+g}if(k.labelStyle.padding){a.style.padding=k.labelStyle.padding}var f=d._jsPlumb.overlayClass+" "+(k.labelStyle.cssClass?k.labelStyle.cssClass:d.cssClass?d.cssClass:"");a.className=f;jsPlumb.appendElement(a,d.connection.parent);jsPlumb.getId(a);k.attachListeners(a,k);this.paint=function(l,n,m){if(!i){l.appendDisplayElement(a);k.attachListeners(a,l);i=true}a.style.left=(m[0]+n.minx)+"px";a.style.top=(m[1]+n.miny)+"px"};this.getTextDimensions=function(l){c=typeof k.label=="function"?k.label(k):k.label;a.innerHTML=c.replace(/\r\n/g,"
");var n=jsPlumb.CurrentLibrary.getElementObject(a),m=jsPlumb.CurrentLibrary.getSize(n);return{width:m[0],height:m[1]}};this.computeMaxSize=function(l){var m=k.getTextDimensions(l);return m.width?Math.max(m.width,m.height)*1.5:0};this.draw=function(n,p,o){var r=k.getTextDimensions(n);if(r.width!=null){var q=n.pointOnPath(k.location);var m=q.x-(r.width/2);var l=q.y-(r.height/2);k.paint(n,{minx:m,miny:l,td:r,cxy:q},o);return[m,m+r.width,l,l+r.height]}else{return[0,0,0,0]}}}})();(function(){var h={"stroke-linejoin":"joinstyle",joinstyle:"joinstyle",endcap:"endcap",miterlimit:"miterlimit"};if(document.createStyleSheet){document.createStyleSheet().addRule(".jsplumb_vml","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:textbox","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:oval","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:rect","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:stroke","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:shape","behavior:url(#default#VML);position:absolute;");document.namespaces.add("jsplumb","urn:schemas-microsoft-com:vml")}var c=1000,d=function(p,q){for(var n in q){p[n]=q[n]}},m=function(n,q,r){r=r||{};var p=document.createElement("jsplumb:"+n);p.className=(r["class"]?r["class"]+" ":"")+"jsplumb_vml";l(p,q);d(p,r);return p},l=function(p,n){p.style.left=n[0]+"px";p.style.top=n[1]+"px";p.style.width=n[2]+"px";p.style.height=n[3]+"px";p.style.position="absolute"},g=function(n){return Math.floor(n*c)},a=function(p,n){var v=p,u=function(o){return o.length==1?"0"+o:o},q=function(o){return u(Number(o).toString(16))},r=/(rgb[a]?\()(.*)(\))/;if(p.match(r)){var t=p.match(r)[2].split(",");v="#"+q(t[0])+q(t[1])+q(t[2]);if(!n&&t.length==4){v=v+q(t[3])}}return v},f=function(s,r,p){var u={};if(r.strokeStyle){u.stroked="true";u.strokecolor=a(r.strokeStyle,true);u.strokeweight=r.lineWidth+"px"}else{u.stroked="false"}if(r.fillStyle){u.filled="true";u.fillcolor=a(r.fillStyle,true)}else{u.filled="false"}if(r.dashstyle){if(p.strokeNode==null){p.strokeNode=m("stroke",[0,0,0,0],{dashstyle:r.dashstyle});s.appendChild(p.strokeNode)}else{p.strokeNode.dashstyle=r.dashstyle}}else{if(r["stroke-dasharray"]&&r.lineWidth){var o=r["stroke-dasharray"].indexOf(",")==-1?" ":",",t=r["stroke-dasharray"].split(o),n="";for(var q=0;q0&&F>0&&z=z&&B[2]<=F&&B[3]>=F)){return true}}var D=v.canvas.getContext("2d").getImageData(parseInt(z),parseInt(F),1,1);return D.data[0]!=0||D.data[1]!=0||D.data[2]!=0||D.data[3]!=0}return false};var u=false;var t=false,y=null,x=false;var w=function(A,z){return A!=null&&f(A,z)};this.mousemove=function(C){var E=e(C),B=i(C),A=document.elementFromPoint(B[0],B[1]),D=w(A,"_jsPlumb_overlay");var z=o==null&&(w(A,"_jsPlumb_endpoint")||w(A,"_jsPlumb_connector"));if(!u&&z&&v._over(C)){u=true;v.fire("mouseenter",v,C);return true}else{if(u&&(!v._over(C)||!z)&&!D){u=false;v.fire("mouseexit",v,C)}}v.fire("mousemove",v,C)};this.click=function(z){if(u&&v._over(z)&&!x){v.fire("click",v,z)}x=false};this.dblclick=function(z){if(u&&v._over(z)&&!x){v.fire("dblclick",v,z)}x=false};this.mousedown=function(z){if(v._over(z)&&!t){t=true;y=m(s(v.canvas));v.fire("mousedown",v,z)}};this.mouseup=function(z){t=false;v.fire("mouseup",v,z)}};var p=function(u){var t=document.createElement("canvas");jsPlumb.appendElement(t,u.parent);t.style.position="absolute";if(u["class"]){t.className=u["class"]}u._jsPlumb.getId(t,u.uuid);return t};var d=jsPlumb.CanvasConnector=function(x){n.apply(this,arguments);var t=function(B,z){u.ctx.save();jsPlumb.extend(u.ctx,z);if(z.gradient){var A=u.createGradient(B,u.ctx);for(var y=0;y0?1:-1}}var b={subtract:function(m,l){return{x:m.x-l.x,y:m.y-l.y}},dotProduct:function(m,l){return m.x*l.x+m.y*l.y},square:function(l){return Math.sqrt(l.x*l.x+l.y*l.y)},scale:function(m,l){return{x:m.x*l,y:m.y*l}}},d=Math.pow(2,-65),h=function(y,x){for(var s=[],v=x.length-1,r=2*v-1,t=[],w=[],o=[],p=[],q=[[1,0.6,0.3,0.1],[0.4,0.6,0.6,0.4],[0.1,0.3,0.6,1]],u=0;u<=v;u++){t[u]=b.subtract(x[u],y)}for(u=0;u<=v-1;u++){w[u]=b.subtract(x[u+1],x[u]);w[u]=b.scale(w[u],3)}for(u=0;u<=v-1;u++){for(var n=0;n<=v;n++){o[u]||(o[u]=[]);o[u][n]=b.dotProduct(w[u],t[n])}}for(u=0;u<=r;u++){p[u]||(p[u]=[]);p[u].y=0;p[u].x=parseFloat(u)/r}r=v-1;for(t=0;t<=v+r;t++){w=Math.min(t,v);for(u=Math.max(0,t-r);u<=w;u++){j=t-u;p[u+j].y+=o[j][u]*q[j][u]}}v=x.length-1;p=k(p,2*v-1,s,0);r=b.subtract(y,x[0]);o=b.square(r);for(u=q=0;u=64){y[0]=(E[0].x+E[D].x)/2;return 1}var s,r,p;v=E[0].y-E[D].y;w=E[D].x-E[0].x;A=E[0].x*E[D].y-E[D].x*E[0].y;t=max_distance_below=0;for(r=1;rt){t=p}else{if(p0?1:-1,r=null;n endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + endpointsByElement = {}, + endpointsByUUID = {}, + offsets = {}, + offsetTimestamps = {}, + floatingConnections = {}, + draggableStates = {}, + _mouseEventsEnabled = this.Defaults.MouseEventsEnabled, + _draggableByDefault = true, + canvasList = [], + sizes = [], + listeners = {}, // a map: keys are event types, values are lists of listeners. + DEFAULT_SCOPE = this.Defaults.Scope, + renderMode = null, // will be set in init() + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }, + + /** + * appends an element to some other element, which is calculated as follows: + * + * 1. if jsPlumb.Defaults.Container exists, use that element. + * 2. if the 'parent' parameter exists, use that. + * 3. otherwise just use the document body. + * + */ + _appendElement = function(el, parent) { + if (_currentInstance.Defaults.Container) + jsPlumb.CurrentLibrary.appendElement(el, _currentInstance.Defaults.Container); + else if (!parent) + document.body.appendChild(el); + else + jsPlumb.CurrentLibrary.appendElement(el, parent); + }, + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + _timestamp = function() { return "" + (new Date()).getTime(); }, + + /** + * YUI, for some reason, put the result of a Y.all call into an object that contains + * a '_nodes' array, instead of handing back an array-like object like the other + * libraries do. + */ + _convertYUICollection = function(c) { + return c._nodes ? c._nodes : c; + }, + + /** + * Draws an endpoint and its connections. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + _draw = function(element, ui, timestamp) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (!timestamp) timestamp = _timestamp(); + if (endpoints) { + _updateOffset( { elId : id, offset : ui, recalc : false, timestamp : timestamp }); // timestamp is checked against last update cache; it is + // valid for one paint cycle. + var myOffset = offsets[id], myWH = sizes[id]; + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + var l = endpoints[i].connections; + for ( var j = 0; j < l.length; j++) { + l[j].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); // ...paint each connection. + // then, check for dynamic endpoint; need to repaint it. + var oIdx = l[j].endpoints[0] == endpoints[i] ? 1 : 0, + otherEndpoint = l[j].endpoints[oIdx]; + if (otherEndpoint.anchor.isDynamic && !otherEndpoint.isFloating()) { + otherEndpoint.paint({ elementWithPrecedence:id }); + // all the connections for the other endpoint now need to be repainted + for (var k = 0; k < otherEndpoint.connections.length; k++) { + if (otherEndpoint.connections[k] !== l) + otherEndpoint.connections[k].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); + } + } + } + } + } + }, + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }, + + /** + * gets an Endpoint by uuid. + */ + _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }, + + /** + * inits a draggable if it's not already initialised. + */ + _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element) && !jsPlumb.CurrentLibrary.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + } + } + }, + + _newConnection = function(params) { + var connectionFunc = jsPlumb.Defaults.ConnectionType || Connection, + endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint, + parent = jsPlumb.CurrentLibrary.getParent; + + if (params.container) + params["parent"] = params.container; + else { + if (params.sourceEndpoint) + params["parent"] = params.sourceEndpoint.parent; + else if (params.source.constructor == endpointFunc) + params["parent"] = params.source.parent; + else params["parent"] = parent(params.source); + } + + params["_jsPlumb"] = _currentInstance; + var con = new connectionFunc(params); + _eventFireProxy("click", "click", con); + _eventFireProxy("dblclick", "dblclick", con); + return con; + }, + + _eventFireProxy = function(event, proxyEvent, obj) { + obj.bind(event, function(e) { + _currentInstance.fire(proxyEvent, obj, e); + }); + }, + + _newEndpoint = function(params) { + var endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint; + if (params.container) + params.parent = params.container; + else + params["parent"] = jsPlumb.CurrentLibrary.getParent(params.source); + params["_jsPlumb"] = _currentInstance, + ep = new endpointFunc(params); + _eventFireProxy("click", "endpointClick", ep); + _eventFireProxy("dblclick", "endpointDblClick", ep); + return ep; + }, + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + _operation = function(elId, func, endpointFunc) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + if (endpointFunc) endpointFunc(endpoints[i]); + } + } + }, + /** + * perform an operation on all elements. + */ + _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }, + + /** + * helper to remove an element from the DOM. + */ + _removeElement = function(element, parent) { + if (element != null && element.parentNode != null) { + element.parentNode.removeChild(element); + } + }, + /** + * helper to remove a list of elements from the DOM. + */ + _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }, + /** + * helper method to remove an item from a list. + */ + _removeFromList = function(map, key, value) { + if (key != null) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete (l[i]); + l.splice(i, 1); + return true; + } + } + } + return false; + }, + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }, + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + _setVisible = function(el, state, alsoChangeEndpoints) { + state = state === "block"; + var endpointFunc = null; + if (alsoChangeEndpoints) { + if (state) endpointFunc = function(ep) { + ep.setVisible(true, true, true); + }; + else endpointFunc = function(ep) { + ep.setVisible(false, true, true); + }; + } + var id = _getAttribute(el, "id"); + _operation(id, function(jpc) { + if (state && alsoChangeEndpoints) { + // this test is necessary because this functionality is new, and i wanted to maintain backwards compatibility. + // this block will only set a connection to be visible if the other endpoint in the connection is also visible. + var oidx = jpc.sourceId === id ? 1 : 0; + if (jpc.endpoints[oidx].isVisible()) jpc.setVisible(true); + } + else // the default behaviour for show, and what always happens for hide, is to just set the visibility without getting clever. + jpc.setVisible(state); + }, endpointFunc); + }, + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }, + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + _toggleVisible = function(elId, changeEndpoints) { + var endpointFunc = null; + if (changeEndpoints) { + endpointFunc = function(ep) { + var state = ep.isVisible(); + ep.setVisible(!state); + }; + } + _operation(elId, function(jpc) { + var state = jpc.isVisible(); + jpc.setVisible(!state); + }, endpointFunc); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }, + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return offsets[elId]; + } + if (recalc || offset == null) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + } + return offsets[elId]; + }, + +/** + * gets an id for the given element, creating and setting one if + * necessary. + */ + _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else + id = "jsPlumb_" + _timestamp(); + _setAttribute(ele, "id", id); + } + return id; + }, + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'jsPlumb function failed : ' + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'wrapped function failed : ' + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = "_jsPlumb_connector"; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = "_jsPlumb_endpoint"; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = "_jsPlumb_overlay"; + + this.Anchors = {}; + + this.Connectors = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Endpoints = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Overlays = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + +// ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***************************************** + /* + * Function: bind + * Bind to an event on jsPlumb. + * + * Parameters: + * event - the event to bind. Available events on jsPlumb are: + * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback. + * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback. + * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback. + * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback. + * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback. + * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback. + * + * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event. + */ + + /* + * Function: clearListeners + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + +// *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *********************************************************** + + /* + Function: addEndpoint + + Adds an to a given element or elements. + + Parameters: + + el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + params - Object containing Endpoint constructor arguments. For more information, see . + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some + shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in + this object are anything that 'params' can contain. See . + + Returns: + The newly created , if el referred to a single element. Otherwise, an array of newly created s. + + See Also: + + */ + this.addEndpoint = function(el, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + + // YUI wrapper + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + var _el = _getElementObject(inputs[i]), id = _getId(_el); + p.source = _el; + _updateOffset({ elId : id }); + var e = _newEndpoint(p); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + results.push(e); + } + + return results.length == 1 ? results[0] : results; + }; + + /* + Function: addEndpoints + Adds a list of s to a given element or elements. + + Parameters: + target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See 's constructor documentation. + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created s, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(el, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams); + if (e.constructor == Array) + Array.prototype.push.apply(results, e); + else results.push(e); + } + return results; + }; + + /* + Function: animate + This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating + the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // onComplete repaints, just to make sure everything looks good at the end of the animation. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /* + Function: connect + Establishes a between two elements (or s, which are themselves registered to elements). + + Parameters: + params - Object containing constructor arguments for the Connection. See 's constructor documentation. + referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of + Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection. + + Returns: + The newly created . + */ + this.connect = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + if (_p.target && !_p.target.endpoint) { + var tid = _getId(_p.target), + tep =_targetEndpointDefinitions[tid]; + if (tep) { + if (_p.endpoints) _p.endpoints[1] = tep; + else if (_p.endpoint) { + _p.endpoints = [ _p.endpoint, tep ]; + _p.endpoint = null; + } + else _p.endpoints = [ null, "Rectangle" ]; + } + } + + // dynamic anchors. backwards compatibility here: from 1.2.6 onwards you don't need to specify "dynamicAnchors". the fact that some anchor consists + // of multiple definitions is enough to tell jsPlumb you want it to be dynamic. + if (_p.dynamicAnchors) { + // these can either be an array of anchor coords, which we will use for both source and target, or an object with {source:[anchors], target:[anchors]}, in which + // case we will use a different set for each element. + var a = _p.dynamicAnchors.constructor == Array; + var sa = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.source)); + var ta = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.target)); + _p.anchors = [sa,ta]; + } + + var jpc = _newConnection(_p); + // add to list of connections (by scope). + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + _currentInstance.fire("jsPlumbConnection", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + // force a paint + _draw(jpc.source); + + return jpc; + }; + + /* + Function: deleteEndpoint + Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too) + + Parameters: + object - either an object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElement(endpoint.canvas, endpoint.parent); + // remove from endpointsbyElement + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + delete endpoint; + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every , and their associated s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc) { + _currentInstance.fire("jsPlumbConnectionDetached", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + }; + + /* + Function: detach + Detaches and then removes a . Takes either (source, target) (the old way, maintained for backwards compatibility), or a params + object with various possible values. + + Parameters: + source - id or element object of the first element in the Connection. + target - id or element object of the second element in the Connection. + params - a JS object containing the same parameters as you pass to jsPlumb.connect. If this is present then neither source nor + target should be present; it should be the only argument to the method. See the docs for 's constructor for information +about the parameters allowed in the params object. + Returns: + true if successful, false if not. + */ + this.detach = function(source, target) { + if (arguments.length == 2) { + var s = _getElementObject(source), sId = _getId(s); + var t = _getElementObject(target), tId = _getId(t); + _operation(sId, function(jpc) { + if ((jpc.sourceId == sId && jpc.targetId == tId) || (jpc.targetId == sId && jpc.sourceId == tId)) { + _removeElements(jpc.connector.getDisplayElements(), jpc.parent); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + // this is the new version of the method, taking a JS object like + // the connect method does. + else if (arguments.length == 1) { + // TODO investigate whether or not this code still works when a user has supplied their own subclass of Connection. i suspect it may not. + if (arguments[0].constructor == Connection) { + arguments[0].endpoints[0].detachFrom(arguments[0].endpoints[1]); + } + else if (arguments[0].connection) { + arguments[0].connection.endpoints[0].detachFrom(arguments[0].connection.endpoints[1]); + } + else { + var _p = jsPlumb.extend( {}, source); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1])); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source); + var targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElements(jpc.connector.getDisplayElements(), jpc.parent); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + } + } + }; + + /* + Function: detachAll + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + + Returns: + void + */ + this.detachAllConnections = function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + }; + + /** + * @deprecated Use detachAllConnections instead. this will be removed in jsPlumb 1.3. + */ + this.detachAll = this.detachAllConnections; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + /** + * @deprecated use detachEveryConnection instead. this will be removed in jsPlumb 1.3. + */ + this.detachEverything = this.detachEveryConnection; + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of you library's draggable method so that jsPlumb can setup the appropriate callbacks. Your underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method, + * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the + * default scope). If multiple scopes are passed in, the return value will be a map of { scope -> [ connection... ] }. + * + * Parameters + * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list + * of connections that are in the given scope. + * options - if the argument is a JS object, you can specify a finer-grained filter: + * + * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes. + * - *source* either a string representing an element id, or a selector. constrains the result to connections having this source. + * - *target* either a string representing an element id, or a selector. constrains the result to connections having this target. + * + */ + this.getConnections = function(options) { + if (!options) { + options = {}; + } else if (options.constructor == String) { + options = { "scope": options }; + } + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') + r.push(input); + else + r = input; + } + return r; + }; + var scope = options.scope || jsPlumb.getDefaultScope(), + scopes = prepareList(scope), + sources = prepareList(options.source), + targets = prepareList(options.target), + filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }, + results = scopes.length > 1 ? {} : [], + _addOne = function(scope, obj) { + if (scopes.length > 1) { + var ss = results[scope]; + if (ss == null) { + ss = []; results[scope] = ss; + } + ss.push(obj); + } else results.push(obj); + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + _addOne(i, c); + } + } + } + return results; + }; + + /* + * Function: getAllConnections + * Gets all connections, as a map of { scope -> [ connection... ] }. + */ + this.getAllConnections = function() { + return connectionsByScope; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /** + * Function:getEndpoints + * Gets the list of Endpoints for a given selector, or element id. + * @param el + * @return + */ + this.getEndpoints = function(el) { + return endpointsByElement[_getId(el)]; + }; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + + this.appendElement = _appendElement; + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + changeEndpoints - whether not to also hide endpoints on the element. by default this is false. + + Returns: + void + */ + this.hide = function(el, changeEndpoints) { + _setVisible(el, "none", changeEndpoints); + }; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + if (!initialized) { + _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run. + + var _bind = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && _mouseEventsEnabled && renderMode == jsPlumb.CANVAS) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + var t = c[i].connector[event](e); + if (t) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i].endpoint[event](e)) return; + } + } + } + }); + }; + _bind("click"); + _bind("dblclick"); + _bind("mousemove"); + _bind("mousedown"); + _bind("mouseup"); + + initialized = true; + _currentInstance.fire("ready"); + } + }; + + this.jsPlumbUIComponent = jsPlumbUIComponent; + this.EventGenerator = EventGenerator; + + /* + * Creates an anchor with the given params. + * + * You do not need to use this method. It is exposed because of the way jsPlumb is + * split into three scripts; this will change in the future. + * + * x - the x location of the anchor as a fraction of the + * total width. y - the y location of the anchor as a fraction of the + * total height. xOrientation - value indicating the general direction a + * connection from the anchor should go in, in the x direction. + * yOrientation - value indicating the general direction a connection + * from the anchor should go in, in the y direction. xOffset - a fixed + * offset that should be applied in the x direction that should be + * applied after the x position has been figured out. optional. defaults + * to 0. yOffset - a fixed offset that should be applied in the y + * direction that should be applied after the y position has been + * figured out. optional. defaults to 0. + * -- OR -- + * + * params - {x:..., y:..., xOrientation etc } + * -- OR FROM 1.2.4 --- + * + * name - the name of some Anchor in the _currentInstance.Anchors array. + * -- OR FROM 1.2.4 --- + * + * coords - a list of coords for the anchor, like you would pass to + * jsPlumb.makeAnchor (eg [0.5,0.5,0,-1] - an anchor in the center of + * some element, oriented towards the top of the screen) + * -- OR FROM 1.2.4 --- + * + * anchor - an existing anchor. just gets passed back. it's handy + * internally to have this functionality. + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed + // in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards + // compatibility if we are given only one value we assume it's a + // call in the old form. + if (arguments.length == 0) return null; + var params = {}; + if (arguments.length == 1) { + var specimen = arguments[0]; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; + // is it the name of an anchor type? + else if (typeof specimen == "string") return _currentInstance.Anchors[arguments[0]](); + // is it an array of coordinates? + else if (specimen.constructor == Array) { + if (specimen[0].constructor == Array || specimen[0].constructor == String) + return new DynamicAnchor(specimen); + else + return jsPlumb.makeAnchor.apply(this, specimen); + } + // last we try the backwards compatibility stuff. + else if (typeof arguments[0] == "object") jsPlumb.extend(params, x); + } else { + params = { x : x, y : y }; + if (arguments.length >= 4) params.orientation = [ arguments[2], arguments[3] ]; + if (arguments.length == 6) params.offsets = [ arguments[4], arguments[5] ]; + } + var a = new Anchor(params); + a.clone = function() { + return new Anchor(params); + }; + return a; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types) { + var r = []; + for ( var i = 0; i < types.length; i++) + if (typeof types[i] == "string") + r.push(_currentInstance.Anchors[types[i]]()); + else if (types[i].constructor == Array) + r.push(jsPlumb.makeAnchor(types[i])); + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /** + * Function: makeTarget + * Makes some DOM element a Connection target, allowing you to drag connections to it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a target. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * scope optional. scope for the drop zone. + * dropOptions optional. same stuff as you would pass to dropOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to false. whether or not to delete + * any Endpoints created by a connection to this target if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + var _targetEndpointDefinitions = {}; + this.makeTarget = function(el, params, referenceParams) { + + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + var jpcl = jsPlumb.CurrentLibrary, + scope = p.scope || _currentInstance.Defaults.Scope, + deleteEndpointsOnDetach = p.deleteEndpointsOnDetach || false, + _doOne = function(_el) { + + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el); + _targetEndpointDefinitions[elid] = p.endpoint; + + var dropOptions = jsPlumb.extend({}, p.dropOptions || {}); + var _drop = function() { + var draggable = _getElementObject(jpcl.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + // restore the original scope if necessary (issue 57) + scope = _getAttribute(draggable, "originalScope"); + + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + // get the connection, to then get its endpoint + var jpc = floatingConnections[id], + source = jpc.endpoints[0], + _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : null, + // make a new Endpoint + newEndpoint = jsPlumb.addEndpoint(_el, _endpoint); + + var c = jsPlumb.connect({ + source:source, + target:newEndpoint, + scope:scope + }); + if (deleteEndpointsOnDetach) + c.endpointToDeleteOnDetach = newEndpoint; + }; + + var dropEvent = jpcl.dragEvents['drop']; + dropOptions["scope"] = dropOptions["scope"] || scope; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop); + + jpcl.initDroppable(_el, dropOptions); + }; + + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements drop targets. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeTargets = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeTarget(els[i], params, referenceParams); + } + }; + + /* + Function: ready + Helper method to bind a function to jsPlumb's ready event. + */ + this.ready = function(fn) { + _currentInstance.bind("ready", fn); + }, + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + for ( var i in ebe) + _currentInstance.deleteEndpoint(ebe[i]); + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + this.deleteEveryEndpoint(); + this.clearListeners(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + + Parameters: + value - whether or not to automatically repaint when the window is resized. + + Returns: void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setDraggableByDefault + * Sets whether or not elements are draggable by default. Default for this is true. + * + * Parameters: + * draggable - value to set + * + * Returns: + * void + */ + this.setDraggableByDefault = function(draggable) { + _draggableByDefault = draggable; + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction + * Sets the function to fire when the window size has changed and a repaint was fired. + * + * Parameters: + * f - Function to execute. + * + * Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: setMouseEventsEnabled + * Sets whether or not mouse events are enabled. Default is true. + * + * Parameters: + * enabled - whether or not mouse events should be enabled. + * + * Returns: + * void + */ + this.setMouseEventsEnabled = function(enabled) { + _mouseEventsEnabled = enabled; + }; + + /* + * Constant for use with the setRenderMode method + */ + this.CANVAS = "canvas"; + + /* + * Constant for use with the setRenderMode method + */ + this.SVG = "svg"; + + this.VML = "vml"; + + /* + * Function: setRenderMode + * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that + * what you asked for is not supported (and that VML is). If you asked for VML but the browser does + * not support it, jsPlumb uses SVG. + * + * Returns: + * the render mode that jsPlumb set, which of course may be different from that requested. + */ + this.setRenderMode = function(mode) { + if (mode) + mode = mode.toLowerCase(); + else + return; + if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); + // now test we actually have the capability to do this. + if (mode === jsPlumb.CANVAS && canvasAvailable) + renderMode = jsPlumb.CANVAS; + else if (mode === jsPlumb.SVG && svgAvailable) + renderMode = jsPlumb.SVG; + else if (vmlAvailable) + renderMode = jsPlumb.VML; + + return renderMode; + }; + + this.getRenderMode = function() { return renderMode; }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * changeEndpoints - whether or not to also change the visible state of the endpoints on the element. this also has a bearing on + * other connections on those endpoints: if their other endpoint is also visible, the connections are made visible. + * + * Returns: + * void + */ + this.show = function(el, changeEndpoints) { + _setVisible(el, "block", changeEndpoints); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId, + makeAnchor:self.makeAnchor, + makeDynamicAnchor:self.makeDynamicAnchor + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * changeEndpoints - whether or not to also toggle the endpoints on the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload + * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element. + * + * Returns: + * void + */ + this.unload = function() { + delete endpointsByElement; + delete endpointsByUUID; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + delete canvasList; + }; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + if (timestamp && timestamp === self.timestamp) { + return lastReturnValue; + } + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + // adjust loc if there is an offsetParent + if (element.canvas && element.canvas.offsetParent) { + var po = element.canvas.offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(element.canvas.offsetParent); + lastReturnValue[0] = lastReturnValue[0] - po.left; + lastReturnValue[1] = lastReturnValue[1] - po.top; + } + + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + * + * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(_getElementObject(refCanvas)); + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + var orientation = null; + var _lastResult = null; + + this.compute = function(params) { + var xy = params.xy, element = params.element; + var result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + + // adjust loc if there is an offsetParent + if (element.canvas && element.canvas.offsetParent) { + var po = element.canvas.offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(element.canvas.offsetParent); + result[0] = result[0] - po.left; + result[1] = result[1] - po.top; + } + + _lastResult = result; + return result; + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { orientation = anchor.getOrientation(); }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchors is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = [], + _convert = function(anchor) { return anchor.constructor == Anchor ? anchor: jsPlumb.makeAnchor(anchor); }; + for (var i = 0; i < anchors.length; i++) _anchors[i] = _convert(anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + this.locked = false; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null, + _curIndex = _anchors.length > 0 ? 0 : -1, + self = this, + + // helper method to calculate the distance between the centers of the two elements. + _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }, + + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + + var pos = _curAnchor.compute(params); + return pos; + }; + + this.getCurrentLocation = function() { + var cl = _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + return cl; + }; + + this.getOrientation = function() { return _curAnchor != null ? _curAnchor.getOrientation() : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * container - optional id or selector instructing jsPlumb where to attach all the elements it creates for this connection. you should read the documentation for a full discussion of this. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * overlays - Optional array of Overlay definitions to appear on this Connection. + * drawEndpoints - if false, instructs jsPlumb to not draw the endpoints for this Connection. Be careful with this: it only really works when you tell jsPlumb to attach elements to the document body. Read the documentation for a full discussion of this. + */ + var Connection = function(params) { + + jsPlumbUIComponent.apply(this, arguments); + // ************** get the source and target and register the connection. ******************* + var self = this; + var visible = true; + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none"; + }; + var id = new String('_jsplumb_c_' + (new Date()).getTime()); + this.getId = function() { return id; }; + this.parent = params.parent; + /** + Property: source + The source element for this Connection. + */ + this.source = _getElementObject(params.source); + /** + Property:target + The target element for this Connection. + */ + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. + if (params.sourceEndpoint) this.source = params.sourceEndpoint.getElement(); + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + /* + * Property: sourceId + * Id of the source element in the connection. + */ + this.sourceId = _getAttribute(this.source, "id"); + /* + * Property: targetId + * Id of the target element in the connection. + */ + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + + /** + * implementation of abstract method in EventGenerator + * @return list of attached elements. in our case, a list of Endpoints. + */ + this.getAttachedElements = function() { + return self.endpoints; + }; + + /** + * implementation of abstract method in EventGenerator + */ + var srcWhenMouseDown = null, targetWhenMouseDown = null; + this.savePosition = function() { + srcWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.source)); + targetWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.target)); + }; + + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams) { + if (anchorParams) + return jsPlumb.makeAnchor(anchorParams); + }; + var prepareEndpoint = function(existing, index, params, element, connectorPaintStyle, connectorHoverPaintStyle) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] + || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint; + + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified. + if (es.fillStyle == null && connectorPaintStyle != null) + es.fillStyle = connectorPaintStyle.strokeStyle; + + // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does: + //* + if (es.outlineColor == null && connectorPaintStyle != null) + es.outlineColor = connectorPaintStyle.outlineColor; + if (es.outlineWidth == null && connectorPaintStyle != null) + es.outlineWidth = connectorPaintStyle.outlineWidth; + //*/ + + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure? + if (connectorHoverPaintStyle != null) { + if (ehs == null) ehs = {}; + if (ehs.fillStyle == null) { + ehs.fillStyle = connectorHoverPaintStyle.strokeStyle; + } + } + var a = params.anchors ? params.anchors[index] : _makeAnchor(_currentInstance.Defaults.Anchors[index]) || _makeAnchor(jsPlumb.Defaults.Anchors[index]) || _makeAnchor(_currentInstance.Defaults.Anchor) || _makeAnchor(jsPlumb.Defaults.Anchor); + var u = params.uuids ? params.uuids[index] : null; + var e = _newEndpoint({ + paintStyle : es, + hoverPaintStyle:ehs, + endpoint : ep, + connections : [ self ], + uuid : u, + anchor : a, + source : element, + container:params.container + }); + self.endpoints[index] = e; + + if (params.drawEndpoints === false) e.setVisible(false, true, true); + + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source, params.paintStyle, params.hoverPaintStyle); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target, params.paintStyle, params.hoverPaintStyle); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + /* + * Function: setConnector + * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method - the same + * thing that you would set as the 'connector' property on a jsPlumb.connect call. + * + * Parameters: + * connector - Connector definition + */ + this.setConnector = function(connector, doNotRepaint) { + if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent); + var connectorArgs = { _jsPlumb:self._jsPlumb, parent:params.parent, cssClass:params.cssClass, container:params.container }; + if (connector.constructor == String) + this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand. + else if (connector.constructor == Array) + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs)); + this.canvas = this.connector.canvas; + var _mouseDown = false, _mouseWasDown = false, _mouseDownAt = null; + // add mouse events + this.connector.bind("click", function(con, e) { + _mouseWasDown = false; + self.fire("click", self, e); + }); + this.connector.bind("dblclick", function(con, e) { _mouseWasDown = false;self.fire("dblclick", self, e); }); + this.connector.bind("mouseenter", function(con, e) { + if (!self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(true); + } + self.fire("mouseenter", self, e); + } + }); + this.connector.bind("mouseexit", function(con, e) { + if (self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(false); + } + self.fire("mouseexit", self, e); + } + }); + this.connector.bind("mousedown", function(con, e) { + _mouseDown = true; + _mouseDownAt = jsPlumb.CurrentLibrary.getPageXY(e); + self.savePosition(); + }); + this.connector.bind("mouseup", function(con, e) { + _mouseDown = false; + if (self.connector == _connectionBeingDragged) _connectionBeingDragged = null; + }); + + if (!doNotRepaint) self.repaint(); + }; + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc) + */ + + self.setConnector(this.endpoints[0].connector || + this.endpoints[1].connector || + params.connector || + _currentInstance.Defaults.Connector || + jsPlumb.Defaults.Connector, true); + + this.setPaintStyle(this.endpoints[0].connectorStyle || + this.endpoints[1].connectorStyle || + params.paintStyle || + _currentInstance.Defaults.PaintStyle || + jsPlumb.Defaults.PaintStyle, true); + + this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || + this.endpoints[1].connectorHoverStyle || + params.hoverPaintStyle || + _currentInstance.Defaults.HoverPaintStyle || + jsPlumb.Defaults.HoverPaintStyle, true); + + this.paintStyleInUse = this.paintStyle; + + /* + * Property: overlays + * List of Overlays for this Connection. + */ + this.overlays = []; + var _overlays = params.overlays || _currentInstance.Defaults.Overlays; + if (_overlays) { + for (var i = 0; i < _overlays.length; i++) { + var o = _overlays[i], _newOverlay = null, _overlayEvents = null; + if (o.constructor == Array) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0]; + var p = jsPlumb.CurrentLibrary.extend({connection:self, _jsPlumb:_currentInstance}, o[1]); // make a copy of the object so as not to mess up anyone else's reference... + if (o.length == 3) jsPlumb.CurrentLibrary.extend(p, o[2]); + _newOverlay = new jsPlumb.Overlays[renderMode][type](p); + if (p.events) { + for (var evt in p.events) { + _newOverlay.bind(evt, p.events[evt]); + } + } + } else if (o.constructor == String) { + _newOverlay = new jsPlumb.Overlays[renderMode][o]({connection:self, _jsPlumb:_currentInstance}); + } else { + _newOverlay = o; + } + + + + this.overlays.push(_newOverlay); + } + } + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Connection. + * + * Parameters: + * event - the event to bind. Available events on a Connection are: + * - *click* : notification that a Connection was clicked. + * - *dblclick* : notification that a Connection was double clicked. + * - *mouseenter* : notification that the mouse is over a Connection. + * - *mouseexit* : notification that the mouse exited a Connection. + * + * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Connection's paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Connection will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Connection. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + this.addOverlay = function(overlay) { self.overlays.push(overlay); }; + + /** + * Function: removeAllOverlays + * Removes all overlays from the Connection, and then repaints. + */ + this.removeAllOverlays = function() { + self.overlays.splice(0, self.overlays.length); + self.repaint(); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + this.removeOverlay = function(overlayId) { + var idx = -1; + for (var i = 0; i < self.overlays.length; i++) { + if (overlayId === self.overlays[i].id) { + idx = i; + break; + } + } + if (idx != -1) self.overlays.splice(idx, 1); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + this.removeOverlays = function() { + for (var i = 0; i < arguments.length; i++) + self.removeOverlay(arguments[i]); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + this.labelStyle = params.labelStyle || _currentInstance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + if (this.label) { + this.overlays.push(new jsPlumb.Overlays[renderMode].Label( { + cssClass:params.cssClass, + labelStyle : this.labelStyle, + label : this.label, + connection:self, + _jsPlumb:_currentInstance + })); + } + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String or a Function that returns a String. + */ + this.setLabel = function(l) { + self.label = l; + _currentInstance.repaint(self.source); + }; + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var otherOffset = offsets[this.targetId]; + var otherWH = sizes[this.targetId]; + var anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1] + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0] + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc }); + + /* + * Paints the Connection. Not exposed for public usage. + * + * Parameters: + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp; + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = false; + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }); + _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + var sAnchorP = this.endpoints[sIdx].anchor.getCurrentLocation(); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.getCurrentLocation(); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + + /* paint overlays*/ + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + var s = o.computeMaxSize(self.connector); + if (s > maxSize) + maxSize = s; + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, self.paintStyleInUse.lineWidth, maxSize); + + self.connector.paint(dim, self.paintStyleInUse); + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim); + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function() { + this.paint({ elId : this.sourceId, recalc : true }); + }; + + _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide + // whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + + // just to make sure the UI gets initialised fully on all browsers. + self.repaint(); + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1 + * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't + * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you + * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or + * 'target' Endpoints for Connections. + * + * + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * container - optional id or selector instructing jsPlumb where to attach the element it creates for this endpoint. you should read the documentation for a full discussion of this. + * connections - optional list of Connections to configure the Endpoint with. + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + params = params || {}; + var self = this; +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Endpoint. + * + * Parameters: + * event - the event to bind. Available events on an Endpoint are: + * - *click* : notification that a Endpoint was clicked. + * - *dblclick* : notification that a Endpoint was double clicked. + * - *mouseenter* : notification that the mouse is over a Endpoint. + * - *mouseexit* : notification that the mouse exited a Endpoint. + * + * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Endpoint's paint style and then repaints the Endpoint. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Endpoint will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Endpoint. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + var visible = true; + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + var id = new String('_jsplumb_e_' + (new Date()).getTime()); + this.getId = function() { return id; }; + if (params.dynamicAnchors) + self.anchor = new DynamicAnchor(jsPlumb.makeAnchors(params.dynamicAnchors)); + else + self.anchor = params.anchor ? jsPlumb.makeAnchor(params.anchor) : params.anchors ? jsPlumb.makeAnchor(params.anchors) : jsPlumb.makeAnchor("TopCenter"); + var _endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot", + endpointArgs = { _jsPlumb:self._jsPlumb, parent:params.parent, container:params.container }; + if (_endpoint.constructor == String) + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint](endpointArgs); + else if (_endpoint.constructor == Array) { + endpointArgs = jsPlumb.extend(_endpoint[1], endpointArgs); + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint[0]](endpointArgs); + } + else + _endpoint = _endpoint.clone(); + + // assign a clone function using our derived endpointArgs. this is used when a drag starts: the endpoint that was dragged is cloned, + // and the clone is left in its place while the original one goes off on a magical journey. + this.clone = function() { + var o = new Object(); + _endpoint.constructor.apply(o, [endpointArgs]); + return o; + }; + + self.endpoint = _endpoint; + self.type = self.endpoint.type; + + // TODO this event listener registration code is identical to what Connection does: it should be refactored. + this.endpoint.bind("click", function(e) { self.fire("click", self, e); }); + this.endpoint.bind("dblclick", function(e) { self.fire("dblclick", self, e); }); + this.endpoint.bind("mouseenter", function(con, e) { + if (!self.isHover()) { + self.setHover(true); + self.fire("mouseenter", self, e); + } + }); + this.endpoint.bind("mouseexit", function(con, e) { + if (self.isHover()) { + self.setHover(false); + self.fire("mouseexit", self, e); + } + }); + // TODO this event listener registration code above is identical to what Connection does: it should be refactored. + + this.setPaintStyle(params.paintStyle || + params.style || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyle, true); + this.setHoverPaintStyle(params.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyle, true); + this.paintStyleInUse = this.paintStyle; + this.connectorStyle = params.connectorStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.parent = params.parent; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + var _element = params.source, + _uuid = params.uuid, + floatingEndpoint = null, + inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + this.element = _element; + var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + /* + * Property: canvas + * The Endpoint's Canvas. + */ + this.canvas = this.endpoint.canvas; + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + this.connections = params.connections || []; + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + var _reattach = params.reattach || false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + + // this avoids a circular loop + if (!ignoreTarget) { + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + t.detach(connection, true); + // check connection to see if we want to delete the other endpoint. + // if the user uses makeTarget to make some element a target for connections, + // it is possible that they will have set 'endpointToDeleteOnDetach': when + // you make a connection to an element that acts as a target (note: NOT an + // Endpoint; just some div as a target), Endpoints are created for that + // connection. so if you then delete that Connection, it is feasible you + // will want these auto-generated endpoints to be removed. + if (connection.endpointToDeleteOnDetach && connection.endpointToDeleteOnDetach.connections.length == 0) + jsPlumb.deleteEndpoint(connection.endpointToDeleteOnDetach); + } + _removeElements(connection.connector.getDisplayElements(), connection.parent); + _removeFromList(connectionsByScope, connection.scope, connection); + if(!ignoreTarget) fireDetachEvent(connection); + } + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + */ + this.detachAll = function() { + while (self.connections.length > 0) { + self.detach(self.connections[0]); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + */ + this.detachFrom = function(targetEndpoint) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + c[i].setHover(false); + self.detach(c[i]); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + return _newEndpoint( { anchor : self.anchor, source : _element, paintStyle : this.paintStyle, endpoint : _endpoint }); + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + return (self.connections.length < _maxConnections) || _maxConnections == -1 ? null : self.connections[0]; + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + /* + * Function: setStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to + * setStyle and deprecate it if so. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + * + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + // a helper function that tries to find a connection to the given element, and returns it if so. if elementWithPrecedence is null, + // or no connection to it is found, we return the first connection in our list. + var findConnectionToUseForDynamicAnchor = function(elementWithPrecedence) { + var idx = 0; + if (elementWithPrecedence != null) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].sourceId == elementWithPrecedence || self.connections[i].targetId == elementWithPrecedence) { + idx = i; + break; + } + } + } + + return self.connections[idx]; + }; + + /* + * Function: paint + * Paints the Endpoint, recalculating offset and anchor positions if necessary. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + + params = params || {}; + var timestamp = params.timestamp; + if (!timestamp || self.timestamp !== timestamp) { + var ap = params.anchorPoint, canvas = params.canvas, connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + var xy = params.offset || offsets[_elementId]; + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (self.anchor.isDynamic) { + if (self.connections.length > 0) { + //var c = self.connections[0]; + var c = findConnectionToUseForDynamicAnchor(params.elementWithPrecedence); + var oIdx = c.endpoints[0] == self ? 1 : 0; + var oId = oIdx == 0 ? c.sourceId : c.targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + } + ap = self.anchor.compute(anchorParams); + } + + var d = _endpoint.compute(ap, self.anchor.getOrientation(), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + _endpoint.paint(d, self.paintStyleInUse, self.anchor); + + self.timestamp = timestamp; + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + var start = function() { + jpc = self.connectorSelector(); + if (self.isFull() && !dragAllowedWhenFull) return false; + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + n.style.position = "absolute"; + var nE = _getElementObject(n); + _appendElement(n, self.parent); + // create and assign an id, and initialize the offset. + var id = _getId(nE); + + // set the offset of this div to be where 'inPlaceCopy' is, to start with. + var ipcoel = _getElementObject(inPlaceCopy.canvas), + ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel), + po = inPlaceCopy.canvas.offsetParent != null ? + inPlaceCopy.canvas.offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(inPlaceCopy.canvas.offsetParent) + : { left:0, top: 0}; + jsPlumb.CurrentLibrary.setOffset(n, {left:ipco.left - po.left, top:ipco.top-po.top}); + + _updateOffset( { elId : id }); + + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor( { reference : self.anchor, referenceCanvas : self.canvas }); + floatingEndpoint = _newEndpoint({ paintStyle : self.paintStyle, endpoint : _endpoint, anchor : floatingAnchor, source : nE }); + + if (jpc == null) { + self.anchor.locked = true; + // create a connection. one end is this endpoint, the + // other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : _getElementObject(_element), + target : _getElementObject(n), + anchors : [ self.anchor, floatingAnchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays + }); + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.connector.setHover(false); + } else { + existingJpc = true; + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.connector.setHover(false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas)); + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; // are we the source or the target? + + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas); + var dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // now we want to get this endpoint's DROP scope, and set it for now: we can only be dropped on drop zones + // that have our drop scope (issue 57). + var dropScope = jsPlumb.CurrentLibrary.getDropScope(c);//jpc.endpoints[anchorIdx == 0 ? 1 : 0].getDropScope(); + jsPlumb.CurrentLibrary.setDragScope(c, dropScope); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it and register connection on it. + floatingConnections[id] = jpc; + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var jpcl = jsPlumb.CurrentLibrary, + dragOptions = params.dragOptions || {}, + defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions), + startEvent = jpcl.dragEvents['start'], + stopEvent = jpcl.dragEvents['stop'], + dragEvent = jpcl.dragEvents['drag']; + + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], + function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + jsPlumb.CurrentLibrary.setOffset(n, _ui); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements( [ n, floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (_reattach) { + + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } else { + jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc); // the main endpoint will inform the floating endpoint + // to disconnect, and also post the detached event. + } + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElements(jpc.connector.getDisplayElements(), self.parent); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint(); + jpc.setHover(false); + jpc.repaint(); + jpc = null; + delete inPlaceCopy; + delete endpointsByElement[floatingEndpoint.elementId]; + floatingEndpoint = null; + delete floatingEndpoint; + + _currentInstance.currentlyDragging = false; + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas) { + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + var drop = function() { + var draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)); + var id = _getAttribute(draggable, "dragId"); + var elId = _getAttribute(draggable, "elId"); + + // restore the original scope if necessary (issue 57) + var scope = _getAttribute(draggable, "originalScope"); + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + var jpc = floatingConnections[id]; + + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // todo test that the target is not full. + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + if (!jpc.suspendedEndpoint) { + _addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + _currentInstance.fire("jsPlumbConnectionDetached", { + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }); + } + + jsPlumb.repaint(elId); + + _currentInstance.fire("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], + targetEndpoint : jpc.endpoints[1], + connection:jpc + }); + } + + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute( _getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + } + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute(_getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + } + }); + + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas)); + + return self; + }; + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + j.init(); + return j; + }; + + var _curryAnchor = function(x,y,ox,oy) { + return function() { + return jsPlumb.makeAnchor(x,y,ox,oy); + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1); + + jsPlumb.Defaults.DynamicAnchors = function() { + return jsPlumb.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"]); + }; + jsPlumb.Anchors["AutoDefault"] = function() { return jsPlumb.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors()); }; + + +})(); +/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the default Connectors, Endpoint and Overlay definitions. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +(function() { + + /** + * + * Helper class to consume unused mouse events by components that are DOM elements and + * are used by all of the different rendering modes. + * + */ + jsPlumb.DOMElementComponent = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + // when render mode is canvas, these functions may be called by the canvas mouse handler. + // this component is safe to pipe this stuff to /dev/null. + this.mousemove = + this.dblclick = + this.click = + this.mousedown = + this.mouseup = function(e) { }; + }; + + /** + * Class: Connectors.Straight + * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters. + */ + jsPlumb.Connectors.Straight = function() { + this.type = "Straight"; + var self = this; + var currentPoints = null; + var _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + _dx = _tx - _sx, _dy = (_ty - _sy); + _m = _dy / _dx, _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + + return currentPoints; + }; + + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + var xp = _sx + (location * _dx); + var yp = (_m == Infinity || _m == -Infinity) ? _sy + (location * (_ty - _sy)) : (_m * xp) + _b; + return {x:xp, y:yp}; + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { return _m; }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + var orientation = distance > 0 ? 1 : -1; + var y = Math.abs(distance * Math.sin(_theta)); + if (_sy > _ty) y = y * -1; + var x = Math.abs(distance * Math.cos(_theta)); + if (_sx > _tx) x = x * -1; + return {x:p.x + (orientation * x), y:p.y + (orientation * y)}; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = self.gradientAtPoint(p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + }; + }; + + + /** + * Class:Connectors.Bezier + * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's + * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below. + */ + /** + * Function:Constructor + * + * Parameters: + * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line. + * Optional; defaults to 150. + * + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + params = params || {}; + this.majorAnchor = params.curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + this.type = "Bezier"; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h; + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) + { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx); var minx2 = Math.min(_CP[0], _CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(_sx,_tx); var maxx2 = Math.max(_CP[0], _CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty); var miny2 = Math.min(_CP[1], _CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(_sy,_ty); var maxy2 = Math.max(_CP[1], _CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + return jsBezier.perpendicularToCurveAt(_makeCurve(), location, length, distance); + }; + + }; + + + /** + * Class: Connectors.Flowchart + * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments. + */ + /** + * Function: Constructor + * + * Parameters: + * stub - minimum length for the stub at each end of the connector. defaults to 30 pixels. + */ + jsPlumb.Connectors.Flowchart = function(params) { + this.type = "Flowchart"; + params = params || {}; + var self = this, + minStubLength = params.stub || params.minStubLength /* bwds compat. */ || 30, + segments = [], + segmentGradients = [], + segmentProportions = [], + segmentLengths = [], + segmentProportionalLengths = [], + points = [], + swapX, + swapY, + /** + * recalculates the gradients of each segment, and the points at which the segments begin, proportional to the total length travelled + * by all the segments that constitute the connector. + */ + updateSegmentGradientsAndProportions = function(startX, startY, endX, endY) { + var total = 0; + for (var i = 0; i < segments.length; i++) { + var sx = i == 0 ? startX : segments[i][2], + sy = i == 0 ? startY : segments[i][3], + ex = segments[i][0], + ey = segments[i][1]; + + segmentGradients[i] = sx == ex ? Infinity : 0; + segmentLengths[i] = Math.abs(sx == ex ? ey - sy : ex - sx); + total += segmentLengths[i]; + } + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segmentLengths[i] / total; + segmentProportions[i] = [curLoc, (curLoc += (segmentLengths[i] / total)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0]; + var ly = segments.length == 0 ? sy : segments[segments.length - 1][1]; + segments.push([x, y, lx, ly]); + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment + * that contains the point which is 'location' distance along the entire path, where + * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths + * are made up of a list of segments, each of which contributes some fraction to + * the total length. + */ + findSegmentForLocation = function(location) { + var idx = segmentProportions.length - 1, inSegmentProportion = 0; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + segments = []; + segmentGradients = []; + segmentProportionalLengths = []; + segmentLengths = []; + segmentProportionals = []; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + offx = (lw / 2) + (minStubLength * 2), + offy = (lw / 2) + (minStubLength * 2), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(), + to = targetAnchor.orientation || targetAnchor.getOrientation(), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx, + h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy; + if (w < minWidth) { + offx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + offy += (minWidth - h) / 2; + h = minWidth; + } + var sx = swapX ? w-offx : offx, + sy = swapY ? h-offy : offy, + tx = swapX ? offx : w-offx , + ty = swapY ? offy : h-offy, + startStubX = sx + (so[0] * minStubLength), + startStubY = sy + (so[1] * minStubLength), + endStubX = tx + (to[0] * minStubLength), + endStubY = ty + (to[1] * minStubLength), + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2); + + x -= offx; y -= offy; + points = [x, y, w, h, sx, sy, tx, ty], extraPoints = []; + + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + if (so[0] == 0) { + var startStubIsBeforeEndStub = startStubY < endStubY; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } + } + else { + var startStubIsBeforeEndStub = startStubX < endStubX; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } + } + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentGradientsAndProportions(sx, sy, tx, ty); + + return points; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location) { + return self.pointAlongPathFrom(location, 0); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location) { + return segmentGradients[findSegmentForLocation(location)["index"]]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance) { + var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segmentLengths[s.index], m = segmentGradients[s.index]; + var e = { + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + return e; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = segmentGradients[p.segmentInfo.index]; + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + + }; + + }; + + // ********************************* END OF CONNECTOR TYPES ******************************************************************* + + // ********************************* ENDPOINT TYPES ******************************************************************* + + /** + * Class: Endpoints.Dot + * A round endpoint, with default radius 10 pixels. + */ + + /** + * Function: Constructor + * + * Parameters: + * + * radius - radius of the endpoint. defaults to 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + this.type = "Dot"; + var self = this; + params = params || {}; + this.radius = params.radius || 10; + this.defaultOffset = 0.5 * this.radius; + this.defaultInnerRadius = this.radius / 3; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var r = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - r; + var y = anchorPoint[1] - r; + return [ x, y, r * 2, r * 2, r ]; + }; + }; + + /** + * Class: Endpoints.Rectangle + * A Rectangular Endpoint, with default size 20x20. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the endpoint. defaults to 20 pixels. + * height - height of the endpoint. defaults to 20 pixels. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + this.type = "Rectangle"; + var self = this; + params = params || {}; + this.width = params.width || 20; + this.height = params.height || 20; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + return [ x, y, width, height]; + }; + }; + + /** + * Class: Endpoints.Image + * Draws an image as the Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * src - location of the image to use. + */ + jsPlumb.Endpoints.Image = function(params) { + + this.type = "Image"; + jsPlumb.DOMElementComponent.apply(this, arguments); + + var self = this, initialized = false; + this.img = new Image(); + self.ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.src || params.url; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + self.anchorPoint = anchorPoint; + if (self.ready) return [anchorPoint[0] - self.img.width / 2, anchorPoint[1] - self.img.height/ 2, self.img.width, self.img.height]; + else return [0,0,0,0]; + }; + + self.canvas = document.createElement("img"), initialized = false; + self.canvas.style["margin"] = 0; + self.canvas.style["padding"] = 0; + self.canvas.style["outline"] = 0; + self.canvas.style["position"] = "absolute"; + self.canvas.className = jsPlumb.endpointClass; + jsPlumb.appendElement(self.canvas, params.parent); + self.attachListeners(self.canvas, self); + + var actuallyPaint = function(d, style, anchor) { + if (!initialized) { + self.canvas.setAttribute("src", self.img.src); + initialized = true; + } + var width = self.img.width, + height = self.img.height, + x = self.anchorPoint[0] - (width/2), + y = self.anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(self.canvas, x, y, width, height); + }; + + this.paint = function(d, style, anchor) { + if (self.ready) { + actuallyPaint(d, style, anchor); + } + else { + window.setTimeout(function() { + self.paint(d, style, anchor); + }, 200); + } + }; + }; + + /** + * Class: Endpoints.Blank + * An Endpoint that paints nothing on the screen, and cannot be interacted with using the mouse. There are no constructor parameters for this Endpoint. + */ + jsPlumb.Endpoints.Blank = function(params) { + var self = this; + this.type = "Blank"; + jsPlumb.DOMElementComponent.apply(this, arguments); + this.compute = function() { + return [0,0,10,0]; + }; + + self.canvas = document.createElement("div"); + self.canvas.style.display = "block"; + self.canvas.style.width = "1px"; + self.canvas.style.height = "1px"; + self.canvas.style.background = "transparent"; + self.canvas.style.position = "absolute"; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function() { }; + }; + + /** + * Class: Endpoints.Triangle + * A triangular Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the triangle's base. defaults to 55 pixels. + * height - height of the triangle from base to apex. defaults to 55 pixels. + */ + jsPlumb.Endpoints.Triangle = function(params) { + this.type = "Triangle"; + params = params || { }; + params.width = params.width || 55; + param.height = params.height || 55; + this.width = params.width; + this.height = params.height; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + return [ x, y, width, height ]; + }; + }; +// ********************************* END OF ENDPOINT TYPES ******************************************************************* + + +// ********************************* OVERLAY DEFINITIONS *********************************************************************** + + /** + * Class: Overlays.Arrow + * + * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length + * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction + * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line + * across the tail. + */ + /** + * Function: Constructor + * + * Parameters: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + this.type = "Arrow"; + params = params || {}; + var self = this; + this.length = params.length || 20; + this.width = params.width || 20; + this.id = params.id; + this.connection = params.connection; + var direction = (params.direction || 1) < 0 ? -1 : 1; + var paintStyle = params.paintStyle || { lineWidth:1 }; + this.loc = params.location == null ? 0.5 : params.location; + // how far along the arrow the lines folding back in come to. default is 62.3%. + var foldback = params.foldback || 0.623; + var _getFoldBackPoint = function(connector, loc) { + if (foldback == 0.5) return connector.pointOnPath(loc); + else { + var adj = 0.5 - foldback; // we calculate relative to the center + return connector.pointAlongPathFrom(loc, direction * self.length * adj); + } + }; + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + // this is the arrow head position + var hxy = connector.pointAlongPathFrom(self.loc, direction * (self.length / 2)); + // this is the center of the tail + var txy = connector.pointAlongPathFrom(self.loc, -1 * direction * (self.length / 2)), tx = txy.x, ty = txy.y; + // this is the tail vector + var tail = connector.perpendicularToPathAt(self.loc, self.width, -1 * direction * (self.length / 2)); + // this is the point the tail goes in to + var cxy = _getFoldBackPoint(connector, self.loc); + + // if loc = 1, then hxy should be flush with the element, or if direction == -1, the tail midpoint. + if (self.loc == 1) { + var lxy = connector.pointOnPath(self.loc); + // TODO determine why the 1.2.6 released version does not + // use 'direction' in the two equations below, yet both + // that and 1.3.0 still paint the arrows correctly. + var dx = (lxy.x - hxy.x) * direction, dy = (lxy.y - hxy.y) * direction; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + // if loc = 0, then tail midpoint should be flush with the element, or, if direction == -1, hxy should be. + if (self.loc == 0) { + var lxy = connector.pointOnPath(self.loc); + var tailMid = foldback > 1 ? cxy : { + x:tail[0].x + ((tail[1].x - tail[0].x) / 2), + y:tail[0].y + ((tail[1].y - tail[0].y) / 2) + }; + var dx = (lxy.x - tailMid.x) * direction, dy = (lxy.y - tailMid.y) * direction; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x); + var maxx = Math.max(hxy.x, tail[0].x, tail[1].x); + var miny = Math.min(hxy.y, tail[0].y, tail[1].y); + var maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + var d = { hxy:hxy, tail:tail, cxy:cxy }, + strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle, + fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle, + lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth; + + self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions); + + return [ minx, maxx, miny, maxy]; + }; + }; + + /** + * Class: Overlays.PlainArrow + * + * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "PlainArrow"; + }; + + /** + * Class: Overlays.Diamond + * + * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40; + var p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "Diamond"; + }; + + + + /** + * Class: Overlays.Label + * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb + * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter + * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it + * puts on the Label's 'style' attribute, so the end result is the same. + */ + /** + * Function: Constructor + * + * Parameters: + * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes + * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle. + * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * labelStyle - (deprecated) js object containing style instructions for the label. defaults to jsPlumb.Defaults.LabelStyle. + * borderWidth - (deprecated) width of a border to paint. defaults to zero. + * borderStyle - (deprecated) strokeStyle to use when painting the border, if necessary. + * + */ + jsPlumb.Overlays.Label = function(params) { + this.type = "Label"; + jsPlumb.DOMElementComponent.apply(this, arguments); + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.labelStyle.font = this.labelStyle.font || "12px sans-serif"; + this.label = params.label || "banana"; + this.connection = params.connection; + this.id = params.id; + var self = this; + var labelWidth = null, labelHeight = null, labelText = null, labelPadding = null; + this.location = params.location || 0.5; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var initialised = false, + labelText = null, + div = document.createElement("div"); + div.style["position"] = "absolute"; + div.style["font"] = self.labelStyle.font; + div.style["color"] = self.labelStyle.color || "black"; + if (self.labelStyle.fillStyle) div.style["background"] = self.labelStyle.fillStyle;//_convertStyle(self.labelStyle.fillStyle, true); + if (self.labelStyle.borderWidth > 0) { + var dStyle = self.labelStyle.borderStyle ? self.labelStyle.borderStyle/*_convertStyle(self.labelStyle.borderStyle, true)*/ : "black"; + div.style["border"] = self.labelStyle.borderWidth + "px solid " + dStyle; + } + if (self.labelStyle.padding) div.style["padding"] = self.labelStyle.padding; + + var clazz = params["_jsPlumb"].overlayClass + " " + + (self.labelStyle.cssClass ? self.labelStyle.cssClass : + params.cssClass ? params.cssClass : ""); + + div.className = clazz; + + jsPlumb.appendElement(div, params.connection.parent); + jsPlumb.getId(div); + self.attachListeners(div, self); + this.paint = function(connector, d, connectorDimensions) { + if (!initialised) { + connector.appendDisplayElement(div); + self.attachListeners(div, connector); + initialised = true; + } + div.style.left = (connectorDimensions[0] + d.minx) + "px"; + div.style.top = (connectorDimensions[1] + d.miny) + "px"; + }; + + this.getTextDimensions = function(connector) { + labelText = typeof self.label == 'function' ? self.label(self) : self.label; + div.innerHTML = labelText.replace(/\r\n/g, "
"); + var de = jsPlumb.CurrentLibrary.getElementObject(div), + s = jsPlumb.CurrentLibrary.getSize(de); + return {width:s[0], height:s[1]}; + }; + + this.computeMaxSize = function(connector) { + var td = self.getTextDimensions(connector); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + var td = self.getTextDimensions(connector); + if (td.width != null) { + var cxy = connector.pointOnPath(self.location); + + var minx = cxy.x - (td.width / 2); + var miny = cxy.y - (td.height / 2); + + self.paint(connector, { + minx:minx, + miny:miny, + td:td, + cxy:cxy + }, connectorDimensions); + + return [minx, minx+td.width, miny, miny+td.height]; + } + else return [0,0,0,0]; + }; + }; + + // ********************************* END OF OVERLAY DEFINITIONS *********************************************************************** + + // ********************************* OVERLAY CANVAS RENDERERS*********************************************************************** + + // ********************************* END OF OVERLAY CANVAS RENDERERS *********************************************************************** +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the VML renderers. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +;(function() { + + // http://ajaxian.com/archives/the-vml-changes-in-ie-8 + // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/ + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + + var vmlAttributeMap = { + "stroke-linejoin":"joinstyle", + "joinstyle":"joinstyle", + "endcap":"endcap", + "miterlimit":"miterlimit" + }; + + if (document.createStyleSheet) { + + // this is the style rule for IE7/6: it uses a CSS class, tidy. + document.createStyleSheet().addRule(".jsplumb_vml", "behavior:url(#default#VML);position:absolute;"); + + // these are for VML in IE8. you have to explicitly call out which elements + // you're going to expect to support VML! + // + // try to avoid IE8. it is recommended you set X-UA-Compatible="IE=7" if you can. + // + document.createStyleSheet().addRule("jsplumb\\:textbox", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:oval", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:rect", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:stroke", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:shape", "behavior:url(#default#VML);position:absolute;"); + + // in this page it is also mentioned that IE requires the extra arg to the namespace + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either. + // var iev = document.documentMode; + //if (!iev || iev < 8) + document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); + //else + // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML"); + } + + var scale = 1000, + _atts = function(o, atts) { + for (var i in atts) { + // IE8 fix: setattribute does not work after an element has been added to the dom! + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + //o.setAttribute(i, atts[i]); + o[i] = atts[i]; + } + }, + _node = function(name, d, atts) { + atts = atts || {}; + var o = document.createElement("jsplumb:" + name); + o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml"; + _pos(o, d); + _atts(o, atts); + return o; + }, + _pos = function(o,d) { + o.style.left = d[0] + "px"; + o.style.top = d[1] + "px"; + o.style.width= d[2] + "px"; + o.style.height= d[3] + "px"; + o.style.position = "absolute"; + }, + _conv = function(v) { + return Math.floor(v * scale); + }, + _convertStyle = function(s, ignoreAlpha) { + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + + return o; + }, + _applyStyles = function(node, style, component) { + var styleToWrite = {}; + if (style.strokeStyle) { + styleToWrite["stroked"] = "true"; + styleToWrite["strokecolor"] =_convertStyle(style.strokeStyle, true); + styleToWrite["strokeweight"] = style.lineWidth + "px"; + } + else styleToWrite["stroked"] = "false"; + + if (style.fillStyle) { + styleToWrite["filled"] = "true"; + styleToWrite["fillcolor"] = _convertStyle(style.fillStyle, true); + } + else styleToWrite["filled"] = "false"; + + if(style["dashstyle"]) { + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = style["dashstyle"]; + } + else if (style["stroke-dasharray"] && style["lineWidth"]) { + var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",", + parts = style["stroke-dasharray"].split(sep), + styleToUse = ""; + for(var i = 0; i < parts.length; i++) { + styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep); + } + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = styleToUse; + } + + _atts(node, styleToWrite); + }, + /* + * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. + */ + VmlComponent = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }, + /* + * Base class for Vml connectors. extends VmlComponent. + */ + VmlConnector = function(params) { + var self = this; + self.strokeNode = null; + self.canvas = null; + VmlComponent.apply(this, arguments); + clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : ""); + this.paint = function(d, style, anchor) { + if (style != null) { + var path = self.getPath(d), p = { "path":path }; + + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + outlineStyle = { + strokeStyle:_convertStyle(style.outlineColor), + lineWidth:outlineStrokeWidth + }; + + if (self.bgCanvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.bgCanvas = _node("shape", d, p); + jsPlumb.appendElement(self.bgCanvas, params.parent); + _pos(self.bgCanvas, d); + displayElements.push(self.bgCanvas); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.bgCanvas, d); + _atts(self.bgCanvas, p); + } + + _applyStyles(self.bgCanvas, outlineStyle, self); + } + + if (self.canvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.canvas = _node("shape", d, p); + jsPlumb.appendElement(self.canvas, params.parent); + displayElements.push(self.canvas); + + self.attachListeners(self.canvas, self); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.canvas, d); + _atts(self.canvas, p); + } + + _applyStyles(self.canvas, style, self); + } + }; + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + self.canvas.parentNode.appendChild(el); + displayElements.push(el); + }; + }, + /* + * + * Base class for Vml Endpoints. extends VmlComponent. + * + */ + VmlEndpoint = function(params) { + VmlComponent.apply(this, arguments); + var vml = null, self = this; + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function(d, style, anchor) { + var p = { }; + + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (vml == null) { + p["class"] = jsPlumb.endpointClass; + vml = self.getVml([0,0, d[2], d[3]], p, anchor); + self.canvas.appendChild(vml); + self.attachListeners(vml, self); + } + else { + //p["coordsize"] = "1,1";//(d[2] * scale) + "," + (d[3] * scale); again, unsure. + _pos(vml, [0,0, d[2], d[3]]); + _atts(vml, p); + } + + _applyStyles(vml, style); + }; + }; + + jsPlumb.Connectors.vml.Bezier = function() { + jsPlumb.Connectors.Bezier.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Straight = function() { + jsPlumb.Connectors.Straight.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Flowchart = function() { + jsPlumb.Connectors.Flowchart.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l"; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]); + } + // finally draw a line to the end + p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e"; + return p; + }; + }; + + jsPlumb.Endpoints.vml.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("oval", d, atts); }; + }; + + jsPlumb.Endpoints.vml.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("rect", d, atts); }; + }; + + /* + * VML Image Endpoint is the same as the default image endpoint. + */ + jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image; + + /** + * placeholder for Blank endpoint in vml renderer. + */ + jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank; + + /** + * VML Label renderer. uses the default label renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label; + + var AbstractVmlArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + VmlComponent.apply(this, arguments); + var self = this, canvas = null, path =null; + var getPath = function(d, connectorDimensions) { + return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) + + " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + + " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + + " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + + " x e"; + }; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) { + var p = {}; + if (strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] =_convertStyle(strokeStyle, true); + } + if (lineWidth) p["strokeweight"] = lineWidth + "px"; + if (fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = fillStyle; + } + var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + w = Math.abs(xmax - xmin), + h = Math.abs(ymax - ymin), + dim = [xmin, ymin, w, h]; + + // for VML, we create overlays using shapes that have the same dimensions and + // coordsize as their connector - overlays calculate themselves relative to the + // connector (it's how it's been done since the original canvas implementation, because + // for canvas that makes sense). + p["path"] = getPath(d, connectorDimensions); + p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale); + + dim[0] = connectorDimensions[0]; + dim[1] = connectorDimensions[1]; + dim[2] = connectorDimensions[2]; + dim[3] = connectorDimensions[3]; + + if (canvas == null) { + //p["class"] = jsPlumb.overlayClass; // TODO currentInstance? + canvas = _node("shape", dim, p); + connector.appendDisplayElement(canvas); + self.attachListeners(canvas, connector); + } + else { + _pos(canvas, dim); + _atts(canvas, p); + } + }; + }; + + jsPlumb.Overlays.vml.Arrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.vml.PlainArrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.vml.Diamond = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the SVG renderers. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +/** + * SVG support for jsPlumb. + * + * things to investigate: + * + * gradients: https://developer.mozilla.org/en/svg_in_html_introduction + * css:http://tutorials.jenkov.com/svg/svg-and-css.html + * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath + * pointer events: https://developer.mozilla.org/en/css/pointer-events + * + */ +;(function() { + + var svgAttributeMap = { + "stroke-linejoin":"stroke-linejoin", + "joinstyle":"stroke-linejoin", + "stroke-dashoffset":"stroke-dashoffset" + }; + + var ns = { + svg:"http://www.w3.org/2000/svg", + xhtml:"http://www.w3.org/1999/xhtml" + }, + _attr = function(node, attributes) { + for (var i in attributes) + node.setAttribute(i, "" + attributes[i]); + }, + _node = function(name, attributes) { + var n = document.createElementNS(ns.svg, name); + attributes = attributes || {}; + attributes["version"] = "1.1"; + attributes["xmnls"] = ns.xhtml; + _attr(n, attributes); + return n; + }, + _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; }, + _convertStyle = function(s, ignoreAlpha) { + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + + return o; + }, + _clearGradient = function(parent) { + for (var i = 0; i < parent.childNodes.length; i++) { + if (parent.childNodes[i].tagName == "linearGradient" || parent.childNodes[i].tagName == "radialGradient") + parent.removeChild(parent.childNodes[i]); + } + }, + _updateGradient = function(parent, node, style, dimensions) { + var id = "jsplumb_gradient_" + (new Date()).getTime(); + // first clear out any existing gradient + _clearGradient(parent); + // this checks for an 'offset' property in the gradient, and in the absence of it, assumes + // we want a linear gradient. if it's there, we create a radial gradient. + // it is possible that a more explicit means of defining the gradient type would be + // better. relying on 'offset' means that we can never have a radial gradient that uses + // some default offset, for instance. + if (!style.gradient.offset) { + var g = _node("linearGradient", {id:id}); + parent.appendChild(g); + } + else { + var g = _node("radialGradient", { + id:id + }); + parent.appendChild(g); + } + + // the svg radial gradient seems to treat stops in the reverse + // order to how canvas does it. so we want to keep all the maths the same, but + // iterate the actual style declarations in reverse order, if the x indexes are not in order. + for (var i = 0; i < style.gradient.stops.length; i++) { + // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of + // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments. + // so, not too concerned about leaving this in for now. + var styleToUse = i; + if (dimensions.length == 8) + styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i; + else + styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i; + var stopColor = _convertStyle(style.gradient.stops[styleToUse][1], true); + var s = _node("stop", {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor}); + g.appendChild(s); + } + var applyGradientTo = style.strokeStyle ? "stroke" : "fill"; + node.setAttribute("style", applyGradientTo + ":url(#" + id + ")"); + }, + _applyStyles = function(parent, node, style, dimensions) { + + if (style.gradient) { + _updateGradient(parent, node, style, dimensions); + } + else { + // make sure we clear any existing gradient + _clearGradient(parent); + node.setAttribute("style", ""); + } + + node.setAttribute("fill", style.fillStyle ? _convertStyle(style.fillStyle, true) : "none"); + node.setAttribute("stroke", style.strokeStyle ? _convertStyle(style.strokeStyle, true) : "none"); + if (style.lineWidth) { + node.setAttribute("stroke-width", style.lineWidth); + } + + // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like + // the syntax in VML but is actually kind of nasty: values are given in the pixel + // coordinate space, whereas in VML they are multiples of the width of the stroked + // line, which makes a lot more sense. for that reason, jsPlumb is supporting both + // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from + // VML, which will be the preferred method. the code below this converts a dashstyle + // attribute given in terms of stroke width into a pixel representation, by using the + // stroke's lineWidth. + if(style["stroke-dasharray"]) { + node.setAttribute("stroke-dasharray", style["stroke-dasharray"]); + } + if (style["dashstyle"] && style["lineWidth"]) { + var sep = style["dashstyle"].indexOf(",") == -1 ? " " : ",", + parts = style["dashstyle"].split(sep), + styleToUse = ""; + parts.forEach(function(p) { + styleToUse += (Math.floor(p * style.lineWidth) + sep); + }); + node.setAttribute("stroke-dasharray", styleToUse); + } + + // extra attributes such as join type, dash offset. + for (var i in svgAttributeMap) { + if (style[i]) { + node.setAttribute(svgAttributeMap[i], style[i]); + } + } + }, + _decodeFont = function(f) { + var r = /([0-9].)(p[xt])\s(.*)/; + var bits = f.match(r); + return {size:bits[1] + bits[2], font:bits[3]}; + }; + + /* + * Base class for SVG components. + */ + var SvgComponent = function(cssClass, originalArgs, pointerEventsSpec) { + var self = this; + pointerEventsSpec = pointerEventsSpec || "all"; + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + self.canvas = null, self.path = null, self.svg = null; + + this.setHover = function() { }; + + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.sizeCanvas(self.canvas,0,0,1,1); + + var clazz = cssClass + " " + (originalArgs[0].cssClass || ""); + self.canvas.className = clazz; + + self.svg = _node("svg", { + "style":"", + "width":0, + "height":0, + "pointer-events":pointerEventsSpec/*, + "class": clazz*/ + }); + + jsPlumb.appendElement(self.canvas, originalArgs[0]["parent"]); + self.canvas.appendChild(self.svg); + + // TODO this displayElement stuff is common between all components, across all + // renderers. would be best moved to jsPlumbUIComponent. + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + this.paint = function(d, style, anchor) { + if (style != null) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + _attr(self.svg, { + "style":_pos([0,0,d[2], d[3]]), + "width": d[2], + "height": d[3] + }); + self._paint.apply(this, arguments); + } + }; + }; + + /* + * Base class for SVG connectors. + */ + var SvgConnector = function(params) { + var self = this; + SvgComponent.apply(this, [ params["_jsPlumb"].connectorClass, arguments, "none" ]); + this._paint = function(d, style) { + var p = self.getPath(d), a = { "d":p }, outlineStyle = null; + a["pointer-events"] = "all"; + + // outline style. actually means drawing an svg object underneath the main one. + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + outlineStyle = { + strokeStyle:_convertStyle(style.outlineColor), + lineWidth:outlineStrokeWidth + }; + + if (self.bgPath == null) { + self.bgPath = _node("path", a); + self.svg.appendChild(self.bgPath); + self.attachListeners(self.bgPath, self); + } + else { + _attr(self.bgPath, a); + } + + _applyStyles(self.svg, self.bgPath, outlineStyle, d); + } + + + if (self.path == null) { + self.path = _node("path", a); + self.svg.appendChild(self.path); + self.attachListeners(self.path, self); + } + else { + _attr(self.path, a); + } + + _applyStyles(self.svg, self.path, style, d); + }; + }; + + /* + * SVG Bezier Connector + */ + jsPlumb.Connectors.svg.Bezier = function(params) { + jsPlumb.Connectors.Bezier.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]; }; + }; + + /* + * SVG straight line Connector + */ + jsPlumb.Connectors.svg.Straight = function(params) { + jsPlumb.Connectors.Straight.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; }; + }; + + jsPlumb.Connectors.svg.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "M " + dimensions[4] + "," + dimensions[5]; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)]; + } + // finally draw a line to the end + p = p + " " + dimensions[6] + "," + dimensions[7]; + return p; + }; + }; + + /* + * Base class for SVG endpoints. + */ + var SvgEndpoint = function(params) { + var self = this; + SvgComponent.apply(this, [ params["_jsPlumb"].endpointClass, arguments, "all" ]); + this._paint = function(d, style) { + var s = jsPlumb.extend({}, style); + if (s.outlineColor) { + s.strokeWidth = s.outlineWidth; + s.strokeStyle = _convertStyle(s.outlineColor, true); + } + + if (self.node == null) { + self.node = self.makeNode(d, s); + self.svg.appendChild(self.node); + self.attachListeners(self.node, self); + } + _applyStyles(self.svg, self.node, s, d); + _pos(self.node, d); + }; + }; + + /* + * SVG Dot Endpoint + */ + jsPlumb.Endpoints.svg.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("circle", { + "cx" : d[2] / 2, + "cy" : d[3] / 2, + "r" : d[2] / 2 + }); + }; + }; + + /* + * SVG Rectangle Endpoint + */ + jsPlumb.Endpoints.svg.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("rect", { + "width":d[2], + "height":d[3] + }); + }; + }; + + /* + * SVG Image Endpoint is the default image endpoint. + */ + jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image; + /* + * Blank endpoint in svg renderer is the default Blank endpoint. + */ + jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank; + /* + * Label endpoint in svg renderer is the default Label endpoint. + */ + jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label; + + + var AbstractSvgArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + var self = this, path =null; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + } + + _attr(path, { + "d" : makePath(d), + stroke : strokeStyle ? strokeStyle : null, + fill : fillStyle ? fillStyle : null + }); + }; + var makePath = function(d) { + return "M" + d.hxy.x + "," + d.hxy.y + + " L" + d.tail[0].x + "," + d.tail[0].y + + " L" + d.cxy.x + "," + d.cxy.y + + " L" + d.tail[1].x + "," + d.tail[1].y + + " L" + d.hxy.x + "," + d.hxy.y; + }; + }; + + jsPlumb.Overlays.svg.Arrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.svg.PlainArrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.svg.Diamond = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the HTML5 canvas renderers. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +;(function() { + +// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS ******************************************************************* + + // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. + var _connectionBeingDragged = null, + _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }, + _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }, + _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }, + _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, + _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }, + _setOffset = function(el, o) { jsPlumb.CurrentLibrary.setOffset(el, o); }; + + /* + * Class:CanvasMouseAdapter + * Provides support for mouse events on canvases. + */ + var CanvasMouseAdapter = function() { + var self = this; + self.overlayPlacements = []; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + jsPlumb.EventGenerator.apply(this, arguments); + /** + * returns whether or not the given event is ojver a painted area of the canvas. + */ + this._over = function(e) { + var o = _getOffset(_getElementObject(self.canvas)), + pageXY = _pageXY(e), + x = pageXY[0] - o.left, y = pageXY[1] - o.top; + if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + return false; + }; + + var _mouseover = false; + var _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false; + var _nullSafeHasClass = function(el, clazz) { + return el != null && _hasClass(el, clazz); + }; + this.mousemove = function(e) { + var pageXY = _pageXY(e), clientXY = _clientXY(e), + ee = document.elementFromPoint(clientXY[0], clientXY[1]), + eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); + var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); + if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.fire("mouseenter", self, e); + return true; + } + // TODO here there is a remote chance that the overlay the mouse moved onto + // is actually not an overlay for the current component. a more thorough check would + // be to ensure the overlay belonged to the current component. + else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { + _mouseover = false; + self.fire("mouseexit", self, e); + } + self.fire("mousemove", self, e); + }; + + this.click = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); + self.fire("mousedown", self, e); + } + }; + + this.mouseup = function(e) { + //if (self == _connectionBeingDragged) _connectionBeingDragged = null; + _mouseDown = false; + self.fire("mouseup", self, e); + }; + }; + + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + jsPlumb.appendElement(canvas, params.parent); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + params["_jsPlumb"].getId(canvas, params.uuid); + + return canvas; + }; + + /** + * Class:CanvasConnector + * Superclass for Canvas Connector renderers. + */ + var CanvasConnector = jsPlumb.CanvasConnector = function(params) { + + CanvasMouseAdapter.apply(this, arguments); + + var _paintOneStyle = function(dim, aStyle) { + self.ctx.save(); + jsPlumb.extend(self.ctx, aStyle); + if (aStyle.gradient) { + var g = self.createGradient(dim, self.ctx); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + self.ctx.strokeStyle = g; + } + self._paint(dim); + self.ctx.restore(); + }; + + var self = this, + clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent + }); + self.ctx = self.canvas.getContext("2d"); + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + self.paint = function(dim, style) { + if (style != null) { + + jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); + + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + _paintOneStyle(dim, outlineStyle); + } + _paintOneStyle(dim, style); + } + }; + }; + + /** + * Class:CanvasEndpoint + * Superclass for Canvas Endpoint renderers. + */ + var CanvasEndpoint = function(params) { + var self = this; + CanvasMouseAdapter.apply(this, arguments); + var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent + }); + self.ctx = self.canvas.getContext("2d"); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + // _paintOneStyle(d, outlineStyle); + } + + self._paint.apply(this, arguments); + }; + }; + + jsPlumb.Endpoints.canvas.Dot = function(params) { + var self = this; + jsPlumb.Endpoints.Dot.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }; + var calculateAdjustments = function(gradient) { + var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius; + gradient.offset && (offsetAdjustment = parseValue(gradient.offset)); + gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius)); + return [offsetAdjustment, innerRadius]; + }; + this._paint = function(d, style, anchor) { + if (style != null) { + var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(); + jsPlumb.extend(ctx, style); + if (style.gradient) { + var adjustments = calculateAdjustments(style.gradient), + yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0], + xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0], + g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + ctx.beginPath(); + ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + } + }; + }; + + jsPlumb.Endpoints.canvas.Rectangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) { + + var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(); + jsPlumb.extend(ctx, style); + + /* canvas gradient */ + if (style.gradient) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, d[2], d[3]); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + jsPlumb.Endpoints.canvas.Triangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Triangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) + { + var width = d[2], height = d[3], x = d[0], y = d[1]; + + var ctx = self.canvas.getContext('2d'); + var offsetX = 0, offsetY = 0, angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = style.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + /* + * Canvas Image Endpoint: uses the default version, which creates an tag. + */ + jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image; + + /* + * Blank endpoint in all renderers is just the default Blank endpoint. + */ + jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank; + + /* + * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Bezier = function() { + var self = this; + jsPlumb.Connectors.Bezier.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO i doubt this handles the case that source and target are swapped. + this.createGradient = function(dim, ctx, swap) { + return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + /* + * Canvas straight line Connector. Draws a straight line onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Straight = function() { + var self = this; + jsPlumb.Connectors.Straight.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO this does not handle the case that src and target are swapped. + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + jsPlumb.Connectors.canvas.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + +// ********************************* END OF CANVAS RENDERERS ******************************************************************* + + jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label; + + /** + * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. + */ + var CanvasOverlay = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }; + + var AbstractCanvasArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + CanvasOverlay.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + var ctx = connector.ctx; + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(d.hxy.x, d.hxy.y); + ctx.lineTo(d.tail[0].x, d.tail[0].y); + ctx.lineTo(d.cxy.x, d.cxy.y); + ctx.lineTo(d.tail[1].x, d.tail[1].y); + ctx.lineTo(d.hxy.x, d.hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + }; + + jsPlumb.Overlays.canvas.Arrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.canvas.PlainArrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.canvas.Diamond = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the MooTools adapter. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +;(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function(now) { + this.parent(now); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }; + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + var _getElementObject = function(el) { + return $(el); + }; + + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + m.start(properties); + }, + + appendElement : function(child, parent) { + _getElementObject(parent).grab(child); + }, + + bind : function(el, event, callback) { + el = _getElementObject(el); + el.addEvent(event, callback); + }, + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.event.clientX, eventObject.event.clientY]; + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el); + var drags = _draggablesById[id]; + return drags[0].scope; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getElementObject : _getElementObject, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + getPageXY : function(eventObject) { + return [eventObject.event.pageX, eventObject.event.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).getParent(); + }, + + getScrollLeft : function(el) { + return null; + }, + + getScrollTop : function(el) { + return null; + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[0], + p = jsPlumb.CurrentLibrary.getElementObject(ui).getPosition(); + return { left:p.x, top:p.y }; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options) { + var id = jsPlumb.getId(el); + var drag = _draggablesById[id]; + if (!drag) { + var originalZIndex = 0, originalCursor = null; + var dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + options['onStart'] = jsPlumb.wrap(options['onStart'], function() + { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() + { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + }); + + // DROPPABLES: + var scope = options['scope'] || jsPlumb.Defaults.Scope; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop'); + } + }; + + drag = new Drag.Move(el, options); + drag.scope = scope; + //console.log("drag scope initialized to ", scope); + _add(_draggablesByScope, scope, drag); + _add(_draggablesById, el.get("id"), drag); + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + initDroppable : function(el, options) { + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _add(_droppables, scope, el); + var id = jsPlumb.getId(el); + _droppableOptions[id] = options; + _droppableScopesById[id] = scope; + var filterFunc = function(entry) { return entry.element != el; }; + var draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + isAlreadyDraggable : function(el) { + return _draggablesById[jsPlumb.getId(el)] != null; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + return (typeof Drag != undefined && typeof Drag.Move != undefined); + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + _getElementObject(element).dispose(); // ?? + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + setDragScope : function(el, scope) { + var drag = _draggablesById[el.get("id")]; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + drag[0].droppables = droppables; + }, + + setOffset : function(el, o) { + _getElementObject(el).setPosition({x:o.left, y:o.top}); + } + }; + + window.addEvent('domready', jsPlumb.init); +})(); +(function(){if(typeof Math.sgn=="undefined")Math.sgn=function(a){return a==0?0:a>0?1:-1};var p={subtract:function(a,b){return{x:a.x-b.x,y:a.y-b.y}},dotProduct:function(a,b){return a.x*b.x+a.y*b.y},square:function(a){return Math.sqrt(a.x*a.x+a.y*a.y)},scale:function(a,b){return{x:a.x*b,y:a.y*b}}},y=Math.pow(2,-65),u=function(a,b){for(var g=[],d=b.length-1,h=2*d-1,f=[],c=[],l=[],k=[],i=[[1,0.6,0.3,0.1],[0.4,0.6,0.6,0.4],[0.1,0.3,0.6,1]],e=0;e<=d;e++)f[e]=p.subtract(b[e],a);for(e=0;e<=d-1;e++){c[e]= +p.subtract(b[e+1],b[e]);c[e]=p.scale(c[e],3)}for(e=0;e<=d-1;e++)for(var m=0;m<=d;m++){l[e]||(l[e]=[]);l[e][m]=p.dotProduct(c[e],f[m])}for(e=0;e<=h;e++){k[e]||(k[e]=[]);k[e].y=0;k[e].x=parseFloat(e)/h}h=d-1;for(f=0;f<=d+h;f++){c=Math.min(f,d);for(e=Math.max(0,f-h);e<=c;e++){j=f-e;k[e+j].y+=l[j][e]*i[j][e]}}d=b.length-1;k=s(k,2*d-1,g,0);h=p.subtract(a,b[0]);l=p.square(h);for(e=i=0;e=64){g[0]=(a[0].x+a[b].x)/2;return 1}var n,o,q;k=a[0].y-a[b].y;i=a[b].x-a[0].x;e=a[0].x*a[b].y-a[b].x*a[0].y;m=max_distance_below=0;for(o=1;om)m=q;else if(q0?1:-1,c=null;h=0){delete (ax[ay]);ax.splice(ay,1);return true}}}return false},v=function(ay,ax){return I(ay,function(az,aA){ag[aA]=ax;if(i.CurrentLibrary.isDragSupported(az)){i.CurrentLibrary.setDraggable(az,ax)}})},ao=function(az,aA,ax){aA=aA==="block";var ay=null;if(ax){if(aA){ay=function(aC){aC.setVisible(true,true,true)}}else{ay=function(aC){aC.setVisible(false,true,true)}}}var aB=c(az,"id");U(aB,function(aD){if(aA&&ax){var aC=aD.sourceId===aB?1:0;if(aD.endpoints[aC].isVisible()){aD.setVisible(true)}}else{aD.setVisible(aA)}},ay)},N=function(ax){return I(ax,function(az,ay){var aA=ag[ay]==null?Y:ag[ay];aA=!aA;ag[ay]=aA;i.CurrentLibrary.setDraggable(az,aA);return aA})},x=function(ax,az){var ay=null;if(az){ay=function(aA){var aB=aA.isVisible();aA.setVisible(!aB)}}U(ax,function(aB){var aA=aB.isVisible();aB.setVisible(!aA)},ay)},E=function(aC){var aA=aC.timestamp,ax=aC.recalc,aB=aC.offset,ay=aC.elId;if(!ax){if(aA&&aA===C[ay]){return V[ay]}}if(ax||aB==null){var az=u(ay);if(az!=null){P[ay]=b(az);V[ay]=n(az);C[ay]=aA}}else{V[ay]=aB}return V[ay]},aw=function(ax,ay){var az=u(ax);var aA=c(az,"id");if(!aA||aA=="undefined"){if(arguments.length==2&&arguments[1]!=undefined){aA=ay}else{aA="jsPlumb_"+ar()}e(az,"id",aA)}return aA},am=function(az,ax,ay){az=az||function(){};ax=ax||function(){};return function(){var aA=null;try{aA=ax.apply(this,arguments)}catch(aB){k(D,"jsPlumb function failed : "+aB)}if(ay==null||(aA!==ay)){try{az.apply(this,arguments)}catch(aB){k(D,"wrapped function failed : "+aB)}}return aA}};this.connectorClass="_jsPlumb_connector";this.endpointClass="_jsPlumb_endpoint";this.overlayClass="_jsPlumb_overlay";this.Anchors={};this.Connectors={canvas:{},svg:{},vml:{}};this.Endpoints={canvas:{},svg:{},vml:{}};this.Overlays={canvas:{},svg:{},vml:{}};this.addEndpoint=function(az,aA,aJ){aJ=aJ||{};var ay=i.extend({},aJ);i.extend(ay,aA);ay.endpoint=ay.endpoint||D.Defaults.Endpoint||i.Defaults.Endpoint;ay.paintStyle=ay.paintStyle||D.Defaults.EndpointStyle||i.Defaults.EndpointStyle;az=w(az);var aB=[],aE=az.length&&az.constructor!=String?az:[az];for(var aC=0;aC0?l(aK,aJ)!=-1:true},aB=aE.length>1?{}:[],aH=function(aK,aL){if(aE.length>1){var aJ=aB[aK];if(aJ==null){aJ=[];aB[aK]=aJ}aJ.push(aL)}else{aB.push(aL)}};for(var aA in L){if(ay(aE,aA)){for(var az=0;az=4){ay.orientation=[arguments[2],arguments[3]]}if(arguments.length==6){ay.offsets=[arguments[4],arguments[5]]}}var aD=new ac(ay);aD.clone=function(){return new ac(ay)};return aD};this.makeAnchors=function(ay){var az=[];for(var ax=0;ax0?aG[0]:null,aC=aG.length>0?0:-1,aF=this,aB=function(aJ,aH,aN,aM,aI){var aL=aM[0]+(aJ.x*aI[0]),aK=aM[1]+(aJ.y*aI[1]);return Math.sqrt(Math.pow(aH-aL,2)+Math.pow(aN-aK,2))},ax=ay||function(aR,aI,aJ,aK,aH){var aM=aJ[0]+(aK[0]/2),aL=aJ[1]+(aK[1]/2);var aO=-1,aQ=Infinity;for(var aN=0;aNaX){aX=a4}}var a8=this.connector.compute(aZ,ba,this.endpoints[be].anchor,this.endpoints[aW].anchor,aI.paintStyleInUse.lineWidth,aX);aI.connector.paint(a8,aI.paintStyleInUse);for(var a9=0;a9=0){aN.connections.splice(aZ,1);if(!a2){var a1=a0.endpoints[0]==aN?a0.endpoints[1]:a0.endpoints[0];a1.detach(a0,true);if(a0.endpointToDeleteOnDetach&&a0.endpointToDeleteOnDetach.connections.length==0){i.deleteEndpoint(a0.endpointToDeleteOnDetach)}}Q(a0.connector.getDisplayElements(),a0.parent);M(L,a0.scope,a0);if(!a2){ad(a0)}}};this.detachAll=function(){while(aN.connections.length>0){aN.detach(aN.connections[0])}};this.detachFrom=function(a0){var a1=[];for(var aZ=0;aZ=0){aN.connections.splice(aZ,1)}};this.getElement=function(){return aM};this.getUuid=function(){return aH};this.makeInPlaceCopy=function(){return an({anchor:aN.anchor,source:aM,paintStyle:this.paintStyle,endpoint:aL})};this.isConnectedTo=function(a1){var a0=false;if(a1){for(var aZ=0;aZ0){var a9=aK(a2.elementWithPrecedence);var bb=a9.endpoints[0]==aN?1:0;var a4=bb==0?a9.sourceId:a9.targetId;var a8=V[a4],ba=P[a4];a0.txy=[a8.left,a8.top];a0.twh=ba;a0.tElement=a9.endpoints[bb]}}a5=aN.anchor.compute(a0)}var a7=aL.compute(a5,aN.anchor.getOrientation(),aN.paintStyleInUse,a3||aN.paintStyleInUse);aL.paint(a7,aN.paintStyleInUse,aN.anchor);aN.timestamp=a6}};this.repaint=this.paint;this.removeConnection=this.detach;if(aY.isSource&&i.CurrentLibrary.isDragSupported(aM)){var aS=null,aO=null,aR=null,ax=false,aA=null;var aC=function(){aR=aN.connectorSelector();if(aN.isFull()&&!aI){return false}E({elId:aE});aB=aN.makeInPlaceCopy();aB.paint();aS=document.createElement("div");aS.style.position="absolute";var a6=u(aS);R(aS,aN.parent);var a0=aw(a6);var a7=u(aB.canvas),a5=i.CurrentLibrary.getOffset(a7),a2=aB.canvas.offsetParent!=null?aB.canvas.offsetParent.tagName.toLowerCase()==="body"?{left:0,top:0}:n(aB.canvas.offsetParent):{left:0,top:0};i.CurrentLibrary.setOffset(aS,{left:a5.left-a2.left,top:a5.top-a2.top});E({elId:a0});e(u(aN.canvas),"dragId",a0);e(u(aN.canvas),"elId",aE);var a8=new H({reference:aN.anchor,referenceCanvas:aN.canvas});aW=an({paintStyle:aN.paintStyle,endpoint:aL,anchor:a8,source:a6});if(aR==null){aN.anchor.locked=true;aR=au({sourceEndpoint:aN,targetEndpoint:aW,source:u(aM),target:u(aS),anchors:[aN.anchor,a8],paintStyle:aY.connectorStyle,hoverPaintStyle:aY.connectorHoverStyle,connector:aY.connector,overlays:aY.connectorOverlays});aR.connector.setHover(false)}else{ax=true;aR.connector.setHover(false);aD(u(aB.canvas));var a1=aR.sourceId==aE?0:1;aR.floatingAnchorIndex=a1;aN.detachFromConnection(aR);var a4=u(aN.canvas);var a3=i.CurrentLibrary.getDragScope(a4);e(a4,"originalScope",a3);var aZ=i.CurrentLibrary.getDropScope(a4);i.CurrentLibrary.setDragScope(a4,aZ);if(a1==0){aA=[aR.source,aR.sourceId,aV,a3];aR.source=u(aS);aR.sourceId=a0}else{aA=[aR.target,aR.targetId,aV,a3];aR.target=u(aS);aR.targetId=a0}aR.endpoints[a1==0?1:0].anchor.locked=true;aR.suspendedEndpoint=aR.endpoints[a1];aR.endpoints[a1]=aW}Z[a0]=aR;aW.addConnection(aR);S(ah,a0,aW);D.currentlyDragging=true};var ay=i.CurrentLibrary,aU=aY.dragOptions||{},aP=i.extend({},ay.defaultDragOptions),aQ=ay.dragEvents.start,aX=ay.dragEvents.stop,aG=ay.dragEvents.drag;aU=i.extend(aP,aU);aU.scope=aU.scope||aN.scope;aU[aQ]=am(aU[aQ],aC);aU[aG]=am(aU[aG],function(){var aZ=i.CurrentLibrary.getUIPosition(arguments);i.CurrentLibrary.setOffset(aS,aZ);at(u(aS),aZ)});aU[aX]=am(aU[aX],function(){M(ah,aO,aW);Q([aS,aW.canvas],aM);ae(aB.canvas,aM);var aZ=aR.floatingAnchorIndex==null?1:aR.floatingAnchorIndex;aR.endpoints[aZ==0?1:0].anchor.locked=false;if(aR.endpoints[aZ]==aW){if(ax&&aR.suspendedEndpoint){if(aZ==0){aR.source=aA[0];aR.sourceId=aA[1]}else{aR.target=aA[0];aR.targetId=aA[1]}i.CurrentLibrary.setDragScope(aA[2],aA[3]);aR.endpoints[aZ]=aR.suspendedEndpoint;if(aJ){aR.floatingAnchorIndex=null;aR.suspendedEndpoint.addConnection(aR);i.repaint(aA[1])}else{aR.endpoints[aZ==0?1:0].detach(aR)}}else{Q(aR.connector.getDisplayElements(),aN.parent);aN.detachFromConnection(aR)}}aN.anchor.locked=false;aN.paint();aR.setHover(false);aR.repaint();aR=null;delete aB;delete ah[aW.elementId];aW=null;delete aW;D.currentlyDragging=false});var aV=u(aN.canvas);i.CurrentLibrary.initDraggable(aV,aU)}var aD=function(a2){if(aY.isTarget&&i.CurrentLibrary.isDropSupported(aM)){var aZ=aY.dropOptions||D.Defaults.DropOptions||i.Defaults.DropOptions;aZ=i.extend({},aZ);aZ.scope=aZ.scope||aN.scope;var a5=null;var a3=i.CurrentLibrary.dragEvents.drop;var a4=i.CurrentLibrary.dragEvents.over;var a0=i.CurrentLibrary.dragEvents.out;var a1=function(){var be=u(i.CurrentLibrary.getDragObject(arguments));var a6=c(be,"dragId");var a8=c(be,"elId");var bd=c(be,"originalScope");if(bd){i.CurrentLibrary.setDragScope(be,bd)}var ba=Z[a6];var bb=ba.floatingAnchorIndex==null?1:ba.floatingAnchorIndex,bc=bb==0?1:0;if(!aN.isFull()&&!(bb==0&&!aN.isSource)&&!(bb==1&&!aN.isTarget)){if(bb==0){ba.source=aM;ba.sourceId=aE}else{ba.target=aM;ba.targetId=aE}ba.endpoints[bb].detachFromConnection(ba);if(ba.suspendedEndpoint){ba.suspendedEndpoint.detachFromConnection(ba)}ba.endpoints[bb]=aN;aN.addConnection(ba);if(!ba.suspendedEndpoint){S(L,ba.scope,ba);O(aM,aY.draggable,{})}else{var a9=ba.suspendedEndpoint.getElement(),a7=ba.suspendedEndpoint.elementId;D.fire("jsPlumbConnectionDetached",{source:bb==0?a9:ba.source,target:bb==1?a9:ba.target,sourceId:bb==0?a7:ba.sourceId,targetId:bb==1?a7:ba.targetId,sourceEndpoint:bb==0?ba.suspendedEndpoint:ba.endpoints[0],targetEndpoint:bb==1?ba.suspendedEndpoint:ba.endpoints[1],connection:ba})}i.repaint(a8);D.fire("jsPlumbConnection",{source:ba.source,target:ba.target,sourceId:ba.sourceId,targetId:ba.targetId,sourceEndpoint:ba.endpoints[0],targetEndpoint:ba.endpoints[1],connection:ba})}D.currentlyDragging=false;delete Z[a6]};aZ[a3]=am(aZ[a3],a1);aZ[a4]=am(aZ[a4],function(){var a7=i.CurrentLibrary.getDragObject(arguments);var a9=c(u(a7),"dragId");var a8=Z[a9];if(a8!=null){var a6=a8.floatingAnchorIndex==null?1:a8.floatingAnchorIndex;a8.endpoints[a6].anchor.over(aN.anchor)}});aZ[a0]=am(aZ[a0],function(){var a7=i.CurrentLibrary.getDragObject(arguments),a9=c(u(a7),"dragId"),a8=Z[a9];if(a8!=null){var a6=a8.floatingAnchorIndex==null?1:a8.floatingAnchorIndex;a8.endpoints[a6].anchor.out()}});i.CurrentLibrary.initDroppable(a2,aZ)}};aD(u(aN.canvas));return aN}};var i=window.jsPlumb=new q();i.getInstance=function(w){var v=new q(w);v.init();return v};var m=function(v,A,z,w){return function(){return i.makeAnchor(v,A,z,w)}};i.Anchors.TopCenter=m(0.5,0,0,-1);i.Anchors.BottomCenter=m(0.5,1,0,1);i.Anchors.LeftMiddle=m(0,0.5,-1,0);i.Anchors.RightMiddle=m(1,0.5,1,0);i.Anchors.Center=m(0.5,0.5,0,0);i.Anchors.TopRight=m(1,0,0,-1);i.Anchors.BottomRight=m(1,1,0,1);i.Anchors.TopLeft=m(0,0,0,-1);i.Anchors.BottomLeft=m(0,1,0,1);i.Defaults.DynamicAnchors=function(){return i.makeAnchors(["TopCenter","RightMiddle","BottomCenter","LeftMiddle"])};i.Anchors.AutoDefault=function(){return i.makeDynamicAnchor(i.Defaults.DynamicAnchors())}})();(function(){jsPlumb.DOMElementComponent=function(a){jsPlumb.jsPlumbUIComponent.apply(this,arguments);this.mousemove=this.dblclick=this.click=this.mousedown=this.mouseup=function(b){}};jsPlumb.Connectors.Straight=function(){this.type="Straight";var n=this;var g=null;var c,h,l,k,i,d,m,f,e,b,a;this.compute=function(r,F,B,o,v,q){var E=Math.abs(r[0]-F[0]);var u=Math.abs(r[1]-F[1]);var z=false,s=false;var t=0.45*E,p=0.45*u;E*=1.9;u*=1.9;var C=Math.min(r[0],F[0])-t;var A=Math.min(r[1],F[1])-p;var D=Math.max(2*v,q);if(E0?1:-1;var t=Math.abs(u*Math.sin(d));if(e>a){t=t*-1}var o=Math.abs(u*Math.cos(d));if(f>b){o=o*-1}return{x:s.x+(r*o),y:s.y+(r*t)}};this.perpendicularToPathAt=function(s,t,w){var u=n.pointAlongPathFrom(s,w);var r=n.gradientAtPoint(u.location);var q=Math.atan(-1/r);var v=t/2*Math.sin(q);var o=t/2*Math.cos(q);return[{x:u.x+o,y:u.y+v},{x:u.x-o,y:u.y-v}]}};jsPlumb.Connectors.Bezier=function(e){var o=this;e=e||{};this.majorAnchor=e.curviness||150;this.minorAnchor=10;var h=null;this.type="Bezier";this._findControlPoint=function(z,q,u,x,r){var w=x.getOrientation(),y=r.getOrientation();var t=w[0]!=y[0]||w[1]==y[1];var s=[];var A=o.majorAnchor,v=o.minorAnchor;if(!t){if(w[0]==0){s.push(q[0]l){l=u}if(x<0){d+=x;var y=Math.abs(x);l+=y;n[0]+=y;i+=y;b+=y;m[0]+=y}var G=Math.min(f,a);var E=Math.min(n[1],m[1]);var t=Math.min(G,E);var z=Math.max(f,a);var w=Math.max(n[1],m[1]);var r=Math.max(z,w);if(r>g){g=r}if(t<0){c+=t;var v=Math.abs(t);g+=v;n[1]+=v;f+=v;a+=v;m[1]+=v}if(D&&l=s){q=t;r=(s-l[t][0])/a[t];break}}return{segment:i[q],proportion:r,index:q}};this.compute=function(Q,v,P,r,q,H){i=[];h=[];a=[];g=[];segmentProportionals=[];d=v[0]t[0]?t[0]+((1-x)*r)-y:t[2]+(x*r)+y,y:q==0?t[3]:t[3]>t[1]?t[1]+((1-x)*r)-y:t[3]+(x*r)+y,segmentInfo:v};return w};this.perpendicularToPathAt=function(t,u,z){var v=n.pointAlongPathFrom(t,z);var s=h[v.segmentInfo.index];var r=Math.atan(-1/s);var w=u/2*Math.sin(r);var q=u/2*Math.cos(r);return[{x:v.x+q,y:v.y+w},{x:v.x-q,y:v.y-w}]}};jsPlumb.Endpoints.Dot=function(b){this.type="Dot";var a=this;b=b||{};this.radius=b.radius||10;this.defaultOffset=0.5*this.radius;this.defaultInnerRadius=this.radius/3;this.compute=function(g,d,i,f){var e=i.radius||a.radius;var c=g[0]-e;var h=g[1]-e;return[c,h,e*2,e*2,e]}};jsPlumb.Endpoints.Rectangle=function(b){this.type="Rectangle";var a=this;b=b||{};this.width=b.width||20;this.height=b.height||20;this.compute=function(h,e,k,g){var f=k.width||a.width;var d=k.height||a.height;var c=h[0]-(f/2);var i=h[1]-(d/2);return[c,i,f,d]}};jsPlumb.Endpoints.Image=function(d){this.type="Image";jsPlumb.DOMElementComponent.apply(this,arguments);var a=this,c=false;this.img=new Image();a.ready=false;this.img.onload=function(){a.ready=true};this.img.src=d.src||d.url;this.compute=function(g,e,h,f){a.anchorPoint=g;if(a.ready){return[g[0]-a.img.width/2,g[1]-a.img.height/2,a.img.width,a.img.height]}else{return[0,0,0,0]}};a.canvas=document.createElement("img"),c=false;a.canvas.style.margin=0;a.canvas.style.padding=0;a.canvas.style.outline=0;a.canvas.style.position="absolute";a.canvas.className=jsPlumb.endpointClass;jsPlumb.appendElement(a.canvas,d.parent);a.attachListeners(a.canvas,a);var b=function(k,i,g){if(!c){a.canvas.setAttribute("src",a.img.src);c=true}var h=a.img.width,f=a.img.height,e=a.anchorPoint[0]-(h/2),l=a.anchorPoint[1]-(f/2);jsPlumb.sizeCanvas(a.canvas,e,l,h,f)};this.paint=function(g,f,e){if(a.ready){b(g,f,e)}else{window.setTimeout(function(){a.paint(g,f,e)},200)}}};jsPlumb.Endpoints.Blank=function(b){var a=this;this.type="Blank";jsPlumb.DOMElementComponent.apply(this,arguments);this.compute=function(){return[0,0,10,0]};a.canvas=document.createElement("div");a.canvas.style.display="block";a.canvas.style.width="1px";a.canvas.style.height="1px";a.canvas.style.background="transparent";a.canvas.style.position="absolute";jsPlumb.appendElement(a.canvas,b.parent);this.paint=function(){}};jsPlumb.Endpoints.Triangle=function(a){this.type="Triangle";a=a||{};a.width=a.width||55;param.height=a.height||55;this.width=a.width;this.height=a.height;this.compute=function(g,d,i,f){var e=i.width||self.width;var c=i.height||self.height;var b=g[0]-(e/2);var h=g[1]-(c/2);return[b,h,e,c]}};jsPlumb.Overlays.Arrow=function(f){this.type="Arrow";f=f||{};var b=this;this.length=f.length||20;this.width=f.width||20;this.id=f.id;this.connection=f.connection;var e=(f.direction||1)<0?-1:1;var c=f.paintStyle||{lineWidth:1};this.loc=f.location==null?0.5:f.location;var a=f.foldback||0.623;var d=function(g,i){if(a==0.5){return g.pointOnPath(i)}else{var h=0.5-a;return g.pointAlongPathFrom(i,e*b.length*h)}};this.computeMaxSize=function(){return b.width*1.5};this.draw=function(k,q,w){var y=k.pointAlongPathFrom(b.loc,e*(b.length/2));var t=k.pointAlongPathFrom(b.loc,-1*e*(b.length/2)),B=t.x,A=t.y;var r=k.perpendicularToPathAt(b.loc,b.width,-1*e*(b.length/2));var i=d(k,b.loc);if(b.loc==1){var h=k.pointOnPath(b.loc);var v=(h.x-y.x)*e,u=(h.y-y.y)*e;i.x+=v;i.y+=u;t.x+=v;t.y+=u;r[0].x+=v;r[0].y+=u;r[1].x+=v;r[1].y+=u;y.x+=v;y.y+=u}if(b.loc==0){var h=k.pointOnPath(b.loc);var s=a>1?i:{x:r[0].x+((r[1].x-r[0].x)/2),y:r[0].y+((r[1].y-r[0].y)/2)};var v=(h.x-s.x)*e,u=(h.y-s.y)*e;i.x+=v;i.y+=u;t.x+=v;t.y+=u;r[0].x+=v;r[0].y+=u;r[1].x+=v;r[1].y+=u;y.x+=v;y.y+=u}var o=Math.min(y.x,r[0].x,r[1].x);var n=Math.max(y.x,r[0].x,r[1].x);var m=Math.min(y.y,r[0].y,r[1].y);var l=Math.max(y.y,r[0].y,r[1].y);var z={hxy:y,tail:r,cxy:i},x=c.strokeStyle||q.strokeStyle,p=c.fillStyle||q.strokeStyle,g=c.lineWidth||q.lineWidth;b.paint(k,z,g,x,p,w);return[o,n,m,l]}};jsPlumb.Overlays.PlainArrow=function(b){b=b||{};var a=jsPlumb.extend(b,{foldback:1});jsPlumb.Overlays.Arrow.call(this,a);this.type="PlainArrow"};jsPlumb.Overlays.Diamond=function(c){c=c||{};var a=c.length||40;var b=jsPlumb.extend(c,{length:a/2,foldback:2});jsPlumb.Overlays.Arrow.call(this,b);this.type="Diamond"};jsPlumb.Overlays.Label=function(d){this.type="Label";jsPlumb.DOMElementComponent.apply(this,arguments);this.labelStyle=d.labelStyle||jsPlumb.Defaults.LabelStyle;this.labelStyle.font=this.labelStyle.font||"12px sans-serif";this.label=d.label||"banana";this.connection=d.connection;this.id=d.id;var k=this;var h=null,e=null,c=null,b=null;this.location=d.location||0.5;this.cachedDimensions=null;var i=false,c=null,a=document.createElement("div");a.style.position="absolute";a.style.font=k.labelStyle.font;a.style.color=k.labelStyle.color||"black";if(k.labelStyle.fillStyle){a.style.background=k.labelStyle.fillStyle}if(k.labelStyle.borderWidth>0){var g=k.labelStyle.borderStyle?k.labelStyle.borderStyle:"black";a.style.border=k.labelStyle.borderWidth+"px solid "+g}if(k.labelStyle.padding){a.style.padding=k.labelStyle.padding}var f=d._jsPlumb.overlayClass+" "+(k.labelStyle.cssClass?k.labelStyle.cssClass:d.cssClass?d.cssClass:"");a.className=f;jsPlumb.appendElement(a,d.connection.parent);jsPlumb.getId(a);k.attachListeners(a,k);this.paint=function(l,n,m){if(!i){l.appendDisplayElement(a);k.attachListeners(a,l);i=true}a.style.left=(m[0]+n.minx)+"px";a.style.top=(m[1]+n.miny)+"px"};this.getTextDimensions=function(l){c=typeof k.label=="function"?k.label(k):k.label;a.innerHTML=c.replace(/\r\n/g,"
");var n=jsPlumb.CurrentLibrary.getElementObject(a),m=jsPlumb.CurrentLibrary.getSize(n);return{width:m[0],height:m[1]}};this.computeMaxSize=function(l){var m=k.getTextDimensions(l);return m.width?Math.max(m.width,m.height)*1.5:0};this.draw=function(n,p,o){var r=k.getTextDimensions(n);if(r.width!=null){var q=n.pointOnPath(k.location);var m=q.x-(r.width/2);var l=q.y-(r.height/2);k.paint(n,{minx:m,miny:l,td:r,cxy:q},o);return[m,m+r.width,l,l+r.height]}else{return[0,0,0,0]}}}})();(function(){var h={"stroke-linejoin":"joinstyle",joinstyle:"joinstyle",endcap:"endcap",miterlimit:"miterlimit"};if(document.createStyleSheet){document.createStyleSheet().addRule(".jsplumb_vml","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:textbox","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:oval","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:rect","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:stroke","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:shape","behavior:url(#default#VML);position:absolute;");document.namespaces.add("jsplumb","urn:schemas-microsoft-com:vml")}var c=1000,d=function(p,q){for(var n in q){p[n]=q[n]}},m=function(n,q,r){r=r||{};var p=document.createElement("jsplumb:"+n);p.className=(r["class"]?r["class"]+" ":"")+"jsplumb_vml";l(p,q);d(p,r);return p},l=function(p,n){p.style.left=n[0]+"px";p.style.top=n[1]+"px";p.style.width=n[2]+"px";p.style.height=n[3]+"px";p.style.position="absolute"},g=function(n){return Math.floor(n*c)},a=function(p,n){var v=p,u=function(o){return o.length==1?"0"+o:o},q=function(o){return u(Number(o).toString(16))},r=/(rgb[a]?\()(.*)(\))/;if(p.match(r)){var t=p.match(r)[2].split(",");v="#"+q(t[0])+q(t[1])+q(t[2]);if(!n&&t.length==4){v=v+q(t[3])}}return v},f=function(s,r,p){var u={};if(r.strokeStyle){u.stroked="true";u.strokecolor=a(r.strokeStyle,true);u.strokeweight=r.lineWidth+"px"}else{u.stroked="false"}if(r.fillStyle){u.filled="true";u.fillcolor=a(r.fillStyle,true)}else{u.filled="false"}if(r.dashstyle){if(p.strokeNode==null){p.strokeNode=m("stroke",[0,0,0,0],{dashstyle:r.dashstyle});s.appendChild(p.strokeNode)}else{p.strokeNode.dashstyle=r.dashstyle}}else{if(r["stroke-dasharray"]&&r.lineWidth){var o=r["stroke-dasharray"].indexOf(",")==-1?" ":",",t=r["stroke-dasharray"].split(o),n="";for(var q=0;q0&&F>0&&z=z&&B[2]<=F&&B[3]>=F)){return true}}var D=v.canvas.getContext("2d").getImageData(parseInt(z),parseInt(F),1,1);return D.data[0]!=0||D.data[1]!=0||D.data[2]!=0||D.data[3]!=0}return false};var u=false;var t=false,y=null,x=false;var w=function(A,z){return A!=null&&f(A,z)};this.mousemove=function(C){var E=e(C),B=i(C),A=document.elementFromPoint(B[0],B[1]),D=w(A,"_jsPlumb_overlay");var z=o==null&&(w(A,"_jsPlumb_endpoint")||w(A,"_jsPlumb_connector"));if(!u&&z&&v._over(C)){u=true;v.fire("mouseenter",v,C);return true}else{if(u&&(!v._over(C)||!z)&&!D){u=false;v.fire("mouseexit",v,C)}}v.fire("mousemove",v,C)};this.click=function(z){if(u&&v._over(z)&&!x){v.fire("click",v,z)}x=false};this.dblclick=function(z){if(u&&v._over(z)&&!x){v.fire("dblclick",v,z)}x=false};this.mousedown=function(z){if(v._over(z)&&!t){t=true;y=m(s(v.canvas));v.fire("mousedown",v,z)}};this.mouseup=function(z){t=false;v.fire("mouseup",v,z)}};var p=function(u){var t=document.createElement("canvas");jsPlumb.appendElement(t,u.parent);t.style.position="absolute";if(u["class"]){t.className=u["class"]}u._jsPlumb.getId(t,u.uuid);return t};var d=jsPlumb.CanvasConnector=function(x){n.apply(this,arguments);var t=function(B,z){u.ctx.save();jsPlumb.extend(u.ctx,z);if(z.gradient){var A=u.createGradient(B,u.ctx);for(var y=0;y0?1:-1}}var b={subtract:function(m,l){return{x:m.x-l.x,y:m.y-l.y}},dotProduct:function(m,l){return m.x*l.x+m.y*l.y},square:function(l){return Math.sqrt(l.x*l.x+l.y*l.y)},scale:function(m,l){return{x:m.x*l,y:m.y*l}}},d=Math.pow(2,-65),h=function(y,x){for(var s=[],v=x.length-1,r=2*v-1,t=[],w=[],o=[],p=[],q=[[1,0.6,0.3,0.1],[0.4,0.6,0.6,0.4],[0.1,0.3,0.6,1]],u=0;u<=v;u++){t[u]=b.subtract(x[u],y)}for(u=0;u<=v-1;u++){w[u]=b.subtract(x[u+1],x[u]);w[u]=b.scale(w[u],3)}for(u=0;u<=v-1;u++){for(var n=0;n<=v;n++){o[u]||(o[u]=[]);o[u][n]=b.dotProduct(w[u],t[n])}}for(u=0;u<=r;u++){p[u]||(p[u]=[]);p[u].y=0;p[u].x=parseFloat(u)/r}r=v-1;for(t=0;t<=v+r;t++){w=Math.min(t,v);for(u=Math.max(0,t-r);u<=w;u++){j=t-u;p[u+j].y+=o[j][u]*q[j][u]}}v=x.length-1;p=k(p,2*v-1,s,0);r=b.subtract(y,x[0]);o=b.square(r);for(u=q=0;u=64){y[0]=(E[0].x+E[D].x)/2;return 1}var s,r,p;v=E[0].y-E[D].y;w=E[D].x-E[0].x;A=E[0].x*E[D].y-E[D].x*E[0].y;t=max_distance_below=0;for(r=1;rt){t=p}else{if(p0?1:-1,r=null;n endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + endpointsByElement = {}, + endpointsByUUID = {}, + offsets = {}, + offsetTimestamps = {}, + floatingConnections = {}, + draggableStates = {}, + _mouseEventsEnabled = this.Defaults.MouseEventsEnabled, + _draggableByDefault = true, + canvasList = [], + sizes = [], + listeners = {}, // a map: keys are event types, values are lists of listeners. + DEFAULT_SCOPE = this.Defaults.Scope, + renderMode = null, // will be set in init() + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }, + + /** + * appends an element to some other element, which is calculated as follows: + * + * 1. if jsPlumb.Defaults.Container exists, use that element. + * 2. if the 'parent' parameter exists, use that. + * 3. otherwise just use the document body. + * + */ + _appendElement = function(el, parent) { + if (_currentInstance.Defaults.Container) + jsPlumb.CurrentLibrary.appendElement(el, _currentInstance.Defaults.Container); + else if (!parent) + document.body.appendChild(el); + else + jsPlumb.CurrentLibrary.appendElement(el, parent); + }, + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + _timestamp = function() { return "" + (new Date()).getTime(); }, + + /** + * YUI, for some reason, put the result of a Y.all call into an object that contains + * a '_nodes' array, instead of handing back an array-like object like the other + * libraries do. + */ + _convertYUICollection = function(c) { + return c._nodes ? c._nodes : c; + }, + + /** + * Draws an endpoint and its connections. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + _draw = function(element, ui, timestamp) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (!timestamp) timestamp = _timestamp(); + if (endpoints) { + _updateOffset( { elId : id, offset : ui, recalc : false, timestamp : timestamp }); // timestamp is checked against last update cache; it is + // valid for one paint cycle. + var myOffset = offsets[id], myWH = sizes[id]; + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + var l = endpoints[i].connections; + for ( var j = 0; j < l.length; j++) { + l[j].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); // ...paint each connection. + // then, check for dynamic endpoint; need to repaint it. + var oIdx = l[j].endpoints[0] == endpoints[i] ? 1 : 0, + otherEndpoint = l[j].endpoints[oIdx]; + if (otherEndpoint.anchor.isDynamic && !otherEndpoint.isFloating()) { + otherEndpoint.paint({ elementWithPrecedence:id }); + // all the connections for the other endpoint now need to be repainted + for (var k = 0; k < otherEndpoint.connections.length; k++) { + if (otherEndpoint.connections[k] !== l) + otherEndpoint.connections[k].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); + } + } + } + } + } + }, + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }, + + /** + * gets an Endpoint by uuid. + */ + _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }, + + /** + * inits a draggable if it's not already initialised. + */ + _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element) && !jsPlumb.CurrentLibrary.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + } + } + }, + + _newConnection = function(params) { + var connectionFunc = jsPlumb.Defaults.ConnectionType || Connection, + endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint, + parent = jsPlumb.CurrentLibrary.getParent; + + if (params.container) + params["parent"] = params.container; + else { + if (params.sourceEndpoint) + params["parent"] = params.sourceEndpoint.parent; + else if (params.source.constructor == endpointFunc) + params["parent"] = params.source.parent; + else params["parent"] = parent(params.source); + } + + params["_jsPlumb"] = _currentInstance; + var con = new connectionFunc(params); + _eventFireProxy("click", "click", con); + _eventFireProxy("dblclick", "dblclick", con); + return con; + }, + + _eventFireProxy = function(event, proxyEvent, obj) { + obj.bind(event, function(e) { + _currentInstance.fire(proxyEvent, obj, e); + }); + }, + + _newEndpoint = function(params) { + var endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint; + if (params.container) + params.parent = params.container; + else + params["parent"] = jsPlumb.CurrentLibrary.getParent(params.source); + params["_jsPlumb"] = _currentInstance, + ep = new endpointFunc(params); + _eventFireProxy("click", "endpointClick", ep); + _eventFireProxy("dblclick", "endpointDblClick", ep); + return ep; + }, + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + _operation = function(elId, func, endpointFunc) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + if (endpointFunc) endpointFunc(endpoints[i]); + } + } + }, + /** + * perform an operation on all elements. + */ + _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }, + + /** + * helper to remove an element from the DOM. + */ + _removeElement = function(element, parent) { + if (element != null && element.parentNode != null) { + element.parentNode.removeChild(element); + } + }, + /** + * helper to remove a list of elements from the DOM. + */ + _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }, + /** + * helper method to remove an item from a list. + */ + _removeFromList = function(map, key, value) { + if (key != null) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete (l[i]); + l.splice(i, 1); + return true; + } + } + } + return false; + }, + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }, + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + _setVisible = function(el, state, alsoChangeEndpoints) { + state = state === "block"; + var endpointFunc = null; + if (alsoChangeEndpoints) { + if (state) endpointFunc = function(ep) { + ep.setVisible(true, true, true); + }; + else endpointFunc = function(ep) { + ep.setVisible(false, true, true); + }; + } + var id = _getAttribute(el, "id"); + _operation(id, function(jpc) { + if (state && alsoChangeEndpoints) { + // this test is necessary because this functionality is new, and i wanted to maintain backwards compatibility. + // this block will only set a connection to be visible if the other endpoint in the connection is also visible. + var oidx = jpc.sourceId === id ? 1 : 0; + if (jpc.endpoints[oidx].isVisible()) jpc.setVisible(true); + } + else // the default behaviour for show, and what always happens for hide, is to just set the visibility without getting clever. + jpc.setVisible(state); + }, endpointFunc); + }, + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }, + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + _toggleVisible = function(elId, changeEndpoints) { + var endpointFunc = null; + if (changeEndpoints) { + endpointFunc = function(ep) { + var state = ep.isVisible(); + ep.setVisible(!state); + }; + } + _operation(elId, function(jpc) { + var state = jpc.isVisible(); + jpc.setVisible(!state); + }, endpointFunc); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }, + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return offsets[elId]; + } + if (recalc || offset == null) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + } + return offsets[elId]; + }, + +/** + * gets an id for the given element, creating and setting one if + * necessary. + */ + _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else + id = "jsPlumb_" + _timestamp(); + _setAttribute(ele, "id", id); + } + return id; + }, + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'jsPlumb function failed : ' + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'wrapped function failed : ' + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = "_jsPlumb_connector"; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = "_jsPlumb_endpoint"; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = "_jsPlumb_overlay"; + + this.Anchors = {}; + + this.Connectors = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Endpoints = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Overlays = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + +// ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***************************************** + /* + * Function: bind + * Bind to an event on jsPlumb. + * + * Parameters: + * event - the event to bind. Available events on jsPlumb are: + * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback. + * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback. + * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback. + * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback. + * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback. + * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback. + * + * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event. + */ + + /* + * Function: clearListeners + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + +// *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *********************************************************** + + /* + Function: addEndpoint + + Adds an to a given element or elements. + + Parameters: + + el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + params - Object containing Endpoint constructor arguments. For more information, see . + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some + shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in + this object are anything that 'params' can contain. See . + + Returns: + The newly created , if el referred to a single element. Otherwise, an array of newly created s. + + See Also: + + */ + this.addEndpoint = function(el, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + + // YUI wrapper + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + var _el = _getElementObject(inputs[i]), id = _getId(_el); + p.source = _el; + _updateOffset({ elId : id }); + var e = _newEndpoint(p); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + results.push(e); + } + + return results.length == 1 ? results[0] : results; + }; + + /* + Function: addEndpoints + Adds a list of s to a given element or elements. + + Parameters: + target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See 's constructor documentation. + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created s, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(el, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams); + if (e.constructor == Array) + Array.prototype.push.apply(results, e); + else results.push(e); + } + return results; + }; + + /* + Function: animate + This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating + the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // onComplete repaints, just to make sure everything looks good at the end of the animation. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /* + Function: connect + Establishes a between two elements (or s, which are themselves registered to elements). + + Parameters: + params - Object containing constructor arguments for the Connection. See 's constructor documentation. + referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of + Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection. + + Returns: + The newly created . + */ + this.connect = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + if (_p.target && !_p.target.endpoint) { + var tid = _getId(_p.target), + tep =_targetEndpointDefinitions[tid]; + if (tep) { + if (_p.endpoints) _p.endpoints[1] = tep; + else if (_p.endpoint) { + _p.endpoints = [ _p.endpoint, tep ]; + _p.endpoint = null; + } + else _p.endpoints = [ null, "Rectangle" ]; + } + } + + // dynamic anchors. backwards compatibility here: from 1.2.6 onwards you don't need to specify "dynamicAnchors". the fact that some anchor consists + // of multiple definitions is enough to tell jsPlumb you want it to be dynamic. + if (_p.dynamicAnchors) { + // these can either be an array of anchor coords, which we will use for both source and target, or an object with {source:[anchors], target:[anchors]}, in which + // case we will use a different set for each element. + var a = _p.dynamicAnchors.constructor == Array; + var sa = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.source)); + var ta = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.target)); + _p.anchors = [sa,ta]; + } + + var jpc = _newConnection(_p); + // add to list of connections (by scope). + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + _currentInstance.fire("jsPlumbConnection", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + // force a paint + _draw(jpc.source); + + return jpc; + }; + + /* + Function: deleteEndpoint + Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too) + + Parameters: + object - either an object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElement(endpoint.canvas, endpoint.parent); + // remove from endpointsbyElement + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + delete endpoint; + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every , and their associated s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc) { + _currentInstance.fire("jsPlumbConnectionDetached", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + }; + + /* + Function: detach + Detaches and then removes a . Takes either (source, target) (the old way, maintained for backwards compatibility), or a params + object with various possible values. + + Parameters: + source - id or element object of the first element in the Connection. + target - id or element object of the second element in the Connection. + params - a JS object containing the same parameters as you pass to jsPlumb.connect. If this is present then neither source nor + target should be present; it should be the only argument to the method. See the docs for 's constructor for information +about the parameters allowed in the params object. + Returns: + true if successful, false if not. + */ + this.detach = function(source, target) { + if (arguments.length == 2) { + var s = _getElementObject(source), sId = _getId(s); + var t = _getElementObject(target), tId = _getId(t); + _operation(sId, function(jpc) { + if ((jpc.sourceId == sId && jpc.targetId == tId) || (jpc.targetId == sId && jpc.sourceId == tId)) { + _removeElements(jpc.connector.getDisplayElements(), jpc.parent); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + // this is the new version of the method, taking a JS object like + // the connect method does. + else if (arguments.length == 1) { + // TODO investigate whether or not this code still works when a user has supplied their own subclass of Connection. i suspect it may not. + if (arguments[0].constructor == Connection) { + arguments[0].endpoints[0].detachFrom(arguments[0].endpoints[1]); + } + else if (arguments[0].connection) { + arguments[0].connection.endpoints[0].detachFrom(arguments[0].connection.endpoints[1]); + } + else { + var _p = jsPlumb.extend( {}, source); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1])); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source); + var targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElements(jpc.connector.getDisplayElements(), jpc.parent); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + } + } + }; + + /* + Function: detachAll + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + + Returns: + void + */ + this.detachAllConnections = function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + }; + + /** + * @deprecated Use detachAllConnections instead. this will be removed in jsPlumb 1.3. + */ + this.detachAll = this.detachAllConnections; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + /** + * @deprecated use detachEveryConnection instead. this will be removed in jsPlumb 1.3. + */ + this.detachEverything = this.detachEveryConnection; + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of you library's draggable method so that jsPlumb can setup the appropriate callbacks. Your underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method, + * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the + * default scope). If multiple scopes are passed in, the return value will be a map of { scope -> [ connection... ] }. + * + * Parameters + * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list + * of connections that are in the given scope. + * options - if the argument is a JS object, you can specify a finer-grained filter: + * + * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes. + * - *source* either a string representing an element id, or a selector. constrains the result to connections having this source. + * - *target* either a string representing an element id, or a selector. constrains the result to connections having this target. + * + */ + this.getConnections = function(options) { + if (!options) { + options = {}; + } else if (options.constructor == String) { + options = { "scope": options }; + } + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') + r.push(input); + else + r = input; + } + return r; + }; + var scope = options.scope || jsPlumb.getDefaultScope(), + scopes = prepareList(scope), + sources = prepareList(options.source), + targets = prepareList(options.target), + filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }, + results = scopes.length > 1 ? {} : [], + _addOne = function(scope, obj) { + if (scopes.length > 1) { + var ss = results[scope]; + if (ss == null) { + ss = []; results[scope] = ss; + } + ss.push(obj); + } else results.push(obj); + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + _addOne(i, c); + } + } + } + return results; + }; + + /* + * Function: getAllConnections + * Gets all connections, as a map of { scope -> [ connection... ] }. + */ + this.getAllConnections = function() { + return connectionsByScope; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /** + * Function:getEndpoints + * Gets the list of Endpoints for a given selector, or element id. + * @param el + * @return + */ + this.getEndpoints = function(el) { + return endpointsByElement[_getId(el)]; + }; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + + this.appendElement = _appendElement; + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + changeEndpoints - whether not to also hide endpoints on the element. by default this is false. + + Returns: + void + */ + this.hide = function(el, changeEndpoints) { + _setVisible(el, "none", changeEndpoints); + }; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + if (!initialized) { + _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run. + + var _bind = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && _mouseEventsEnabled && renderMode == jsPlumb.CANVAS) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + var t = c[i].connector[event](e); + if (t) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i].endpoint[event](e)) return; + } + } + } + }); + }; + _bind("click"); + _bind("dblclick"); + _bind("mousemove"); + _bind("mousedown"); + _bind("mouseup"); + + initialized = true; + _currentInstance.fire("ready"); + } + }; + + this.jsPlumbUIComponent = jsPlumbUIComponent; + this.EventGenerator = EventGenerator; + + /* + * Creates an anchor with the given params. + * + * You do not need to use this method. It is exposed because of the way jsPlumb is + * split into three scripts; this will change in the future. + * + * x - the x location of the anchor as a fraction of the + * total width. y - the y location of the anchor as a fraction of the + * total height. xOrientation - value indicating the general direction a + * connection from the anchor should go in, in the x direction. + * yOrientation - value indicating the general direction a connection + * from the anchor should go in, in the y direction. xOffset - a fixed + * offset that should be applied in the x direction that should be + * applied after the x position has been figured out. optional. defaults + * to 0. yOffset - a fixed offset that should be applied in the y + * direction that should be applied after the y position has been + * figured out. optional. defaults to 0. + * -- OR -- + * + * params - {x:..., y:..., xOrientation etc } + * -- OR FROM 1.2.4 --- + * + * name - the name of some Anchor in the _currentInstance.Anchors array. + * -- OR FROM 1.2.4 --- + * + * coords - a list of coords for the anchor, like you would pass to + * jsPlumb.makeAnchor (eg [0.5,0.5,0,-1] - an anchor in the center of + * some element, oriented towards the top of the screen) + * -- OR FROM 1.2.4 --- + * + * anchor - an existing anchor. just gets passed back. it's handy + * internally to have this functionality. + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed + // in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards + // compatibility if we are given only one value we assume it's a + // call in the old form. + if (arguments.length == 0) return null; + var params = {}; + if (arguments.length == 1) { + var specimen = arguments[0]; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; + // is it the name of an anchor type? + else if (typeof specimen == "string") return _currentInstance.Anchors[arguments[0]](); + // is it an array of coordinates? + else if (specimen.constructor == Array) { + if (specimen[0].constructor == Array || specimen[0].constructor == String) + return new DynamicAnchor(specimen); + else + return jsPlumb.makeAnchor.apply(this, specimen); + } + // last we try the backwards compatibility stuff. + else if (typeof arguments[0] == "object") jsPlumb.extend(params, x); + } else { + params = { x : x, y : y }; + if (arguments.length >= 4) params.orientation = [ arguments[2], arguments[3] ]; + if (arguments.length == 6) params.offsets = [ arguments[4], arguments[5] ]; + } + var a = new Anchor(params); + a.clone = function() { + return new Anchor(params); + }; + return a; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types) { + var r = []; + for ( var i = 0; i < types.length; i++) + if (typeof types[i] == "string") + r.push(_currentInstance.Anchors[types[i]]()); + else if (types[i].constructor == Array) + r.push(jsPlumb.makeAnchor(types[i])); + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /** + * Function: makeTarget + * Makes some DOM element a Connection target, allowing you to drag connections to it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a target. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * scope optional. scope for the drop zone. + * dropOptions optional. same stuff as you would pass to dropOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to false. whether or not to delete + * any Endpoints created by a connection to this target if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + var _targetEndpointDefinitions = {}; + this.makeTarget = function(el, params, referenceParams) { + + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + var jpcl = jsPlumb.CurrentLibrary, + scope = p.scope || _currentInstance.Defaults.Scope, + deleteEndpointsOnDetach = p.deleteEndpointsOnDetach || false, + _doOne = function(_el) { + + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el); + _targetEndpointDefinitions[elid] = p.endpoint; + + var dropOptions = jsPlumb.extend({}, p.dropOptions || {}); + var _drop = function() { + var draggable = _getElementObject(jpcl.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + // restore the original scope if necessary (issue 57) + scope = _getAttribute(draggable, "originalScope"); + + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + // get the connection, to then get its endpoint + var jpc = floatingConnections[id], + source = jpc.endpoints[0], + _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : null, + // make a new Endpoint + newEndpoint = jsPlumb.addEndpoint(_el, _endpoint); + + var c = jsPlumb.connect({ + source:source, + target:newEndpoint, + scope:scope + }); + if (deleteEndpointsOnDetach) + c.endpointToDeleteOnDetach = newEndpoint; + }; + + var dropEvent = jpcl.dragEvents['drop']; + dropOptions["scope"] = dropOptions["scope"] || scope; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop); + + jpcl.initDroppable(_el, dropOptions); + }; + + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements drop targets. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeTargets = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeTarget(els[i], params, referenceParams); + } + }; + + /* + Function: ready + Helper method to bind a function to jsPlumb's ready event. + */ + this.ready = function(fn) { + _currentInstance.bind("ready", fn); + }, + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + for ( var i in ebe) + _currentInstance.deleteEndpoint(ebe[i]); + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + this.deleteEveryEndpoint(); + this.clearListeners(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + + Parameters: + value - whether or not to automatically repaint when the window is resized. + + Returns: void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setDraggableByDefault + * Sets whether or not elements are draggable by default. Default for this is true. + * + * Parameters: + * draggable - value to set + * + * Returns: + * void + */ + this.setDraggableByDefault = function(draggable) { + _draggableByDefault = draggable; + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction + * Sets the function to fire when the window size has changed and a repaint was fired. + * + * Parameters: + * f - Function to execute. + * + * Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: setMouseEventsEnabled + * Sets whether or not mouse events are enabled. Default is true. + * + * Parameters: + * enabled - whether or not mouse events should be enabled. + * + * Returns: + * void + */ + this.setMouseEventsEnabled = function(enabled) { + _mouseEventsEnabled = enabled; + }; + + /* + * Constant for use with the setRenderMode method + */ + this.CANVAS = "canvas"; + + /* + * Constant for use with the setRenderMode method + */ + this.SVG = "svg"; + + this.VML = "vml"; + + /* + * Function: setRenderMode + * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that + * what you asked for is not supported (and that VML is). If you asked for VML but the browser does + * not support it, jsPlumb uses SVG. + * + * Returns: + * the render mode that jsPlumb set, which of course may be different from that requested. + */ + this.setRenderMode = function(mode) { + if (mode) + mode = mode.toLowerCase(); + else + return; + if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); + // now test we actually have the capability to do this. + if (mode === jsPlumb.CANVAS && canvasAvailable) + renderMode = jsPlumb.CANVAS; + else if (mode === jsPlumb.SVG && svgAvailable) + renderMode = jsPlumb.SVG; + else if (vmlAvailable) + renderMode = jsPlumb.VML; + + return renderMode; + }; + + this.getRenderMode = function() { return renderMode; }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * changeEndpoints - whether or not to also change the visible state of the endpoints on the element. this also has a bearing on + * other connections on those endpoints: if their other endpoint is also visible, the connections are made visible. + * + * Returns: + * void + */ + this.show = function(el, changeEndpoints) { + _setVisible(el, "block", changeEndpoints); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId, + makeAnchor:self.makeAnchor, + makeDynamicAnchor:self.makeDynamicAnchor + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * changeEndpoints - whether or not to also toggle the endpoints on the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload + * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element. + * + * Returns: + * void + */ + this.unload = function() { + delete endpointsByElement; + delete endpointsByUUID; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + delete canvasList; + }; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + if (timestamp && timestamp === self.timestamp) { + return lastReturnValue; + } + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + // adjust loc if there is an offsetParent + if (element.canvas && element.canvas.offsetParent) { + var po = element.canvas.offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(element.canvas.offsetParent); + lastReturnValue[0] = lastReturnValue[0] - po.left; + lastReturnValue[1] = lastReturnValue[1] - po.top; + } + + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + * + * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(_getElementObject(refCanvas)); + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + var orientation = null; + var _lastResult = null; + + this.compute = function(params) { + var xy = params.xy, element = params.element; + var result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + + // adjust loc if there is an offsetParent + if (element.canvas && element.canvas.offsetParent) { + var po = element.canvas.offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(element.canvas.offsetParent); + result[0] = result[0] - po.left; + result[1] = result[1] - po.top; + } + + _lastResult = result; + return result; + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { orientation = anchor.getOrientation(); }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchors is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = [], + _convert = function(anchor) { return anchor.constructor == Anchor ? anchor: jsPlumb.makeAnchor(anchor); }; + for (var i = 0; i < anchors.length; i++) _anchors[i] = _convert(anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + this.locked = false; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null, + _curIndex = _anchors.length > 0 ? 0 : -1, + self = this, + + // helper method to calculate the distance between the centers of the two elements. + _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }, + + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + + var pos = _curAnchor.compute(params); + return pos; + }; + + this.getCurrentLocation = function() { + var cl = _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + return cl; + }; + + this.getOrientation = function() { return _curAnchor != null ? _curAnchor.getOrientation() : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * container - optional id or selector instructing jsPlumb where to attach all the elements it creates for this connection. you should read the documentation for a full discussion of this. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * overlays - Optional array of Overlay definitions to appear on this Connection. + * drawEndpoints - if false, instructs jsPlumb to not draw the endpoints for this Connection. Be careful with this: it only really works when you tell jsPlumb to attach elements to the document body. Read the documentation for a full discussion of this. + */ + var Connection = function(params) { + + jsPlumbUIComponent.apply(this, arguments); + // ************** get the source and target and register the connection. ******************* + var self = this; + var visible = true; + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none"; + }; + var id = new String('_jsplumb_c_' + (new Date()).getTime()); + this.getId = function() { return id; }; + this.parent = params.parent; + /** + Property: source + The source element for this Connection. + */ + this.source = _getElementObject(params.source); + /** + Property:target + The target element for this Connection. + */ + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. + if (params.sourceEndpoint) this.source = params.sourceEndpoint.getElement(); + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + /* + * Property: sourceId + * Id of the source element in the connection. + */ + this.sourceId = _getAttribute(this.source, "id"); + /* + * Property: targetId + * Id of the target element in the connection. + */ + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + + /** + * implementation of abstract method in EventGenerator + * @return list of attached elements. in our case, a list of Endpoints. + */ + this.getAttachedElements = function() { + return self.endpoints; + }; + + /** + * implementation of abstract method in EventGenerator + */ + var srcWhenMouseDown = null, targetWhenMouseDown = null; + this.savePosition = function() { + srcWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.source)); + targetWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.target)); + }; + + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams) { + if (anchorParams) + return jsPlumb.makeAnchor(anchorParams); + }; + var prepareEndpoint = function(existing, index, params, element, connectorPaintStyle, connectorHoverPaintStyle) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] + || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint; + + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified. + if (es.fillStyle == null && connectorPaintStyle != null) + es.fillStyle = connectorPaintStyle.strokeStyle; + + // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does: + //* + if (es.outlineColor == null && connectorPaintStyle != null) + es.outlineColor = connectorPaintStyle.outlineColor; + if (es.outlineWidth == null && connectorPaintStyle != null) + es.outlineWidth = connectorPaintStyle.outlineWidth; + //*/ + + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure? + if (connectorHoverPaintStyle != null) { + if (ehs == null) ehs = {}; + if (ehs.fillStyle == null) { + ehs.fillStyle = connectorHoverPaintStyle.strokeStyle; + } + } + var a = params.anchors ? params.anchors[index] : _makeAnchor(_currentInstance.Defaults.Anchors[index]) || _makeAnchor(jsPlumb.Defaults.Anchors[index]) || _makeAnchor(_currentInstance.Defaults.Anchor) || _makeAnchor(jsPlumb.Defaults.Anchor); + var u = params.uuids ? params.uuids[index] : null; + var e = _newEndpoint({ + paintStyle : es, + hoverPaintStyle:ehs, + endpoint : ep, + connections : [ self ], + uuid : u, + anchor : a, + source : element, + container:params.container + }); + self.endpoints[index] = e; + + if (params.drawEndpoints === false) e.setVisible(false, true, true); + + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source, params.paintStyle, params.hoverPaintStyle); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target, params.paintStyle, params.hoverPaintStyle); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + /* + * Function: setConnector + * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method - the same + * thing that you would set as the 'connector' property on a jsPlumb.connect call. + * + * Parameters: + * connector - Connector definition + */ + this.setConnector = function(connector, doNotRepaint) { + if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent); + var connectorArgs = { _jsPlumb:self._jsPlumb, parent:params.parent, cssClass:params.cssClass, container:params.container }; + if (connector.constructor == String) + this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand. + else if (connector.constructor == Array) + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs)); + this.canvas = this.connector.canvas; + var _mouseDown = false, _mouseWasDown = false, _mouseDownAt = null; + // add mouse events + this.connector.bind("click", function(con, e) { + _mouseWasDown = false; + self.fire("click", self, e); + }); + this.connector.bind("dblclick", function(con, e) { _mouseWasDown = false;self.fire("dblclick", self, e); }); + this.connector.bind("mouseenter", function(con, e) { + if (!self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(true); + } + self.fire("mouseenter", self, e); + } + }); + this.connector.bind("mouseexit", function(con, e) { + if (self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(false); + } + self.fire("mouseexit", self, e); + } + }); + this.connector.bind("mousedown", function(con, e) { + _mouseDown = true; + _mouseDownAt = jsPlumb.CurrentLibrary.getPageXY(e); + self.savePosition(); + }); + this.connector.bind("mouseup", function(con, e) { + _mouseDown = false; + if (self.connector == _connectionBeingDragged) _connectionBeingDragged = null; + }); + + if (!doNotRepaint) self.repaint(); + }; + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc) + */ + + self.setConnector(this.endpoints[0].connector || + this.endpoints[1].connector || + params.connector || + _currentInstance.Defaults.Connector || + jsPlumb.Defaults.Connector, true); + + this.setPaintStyle(this.endpoints[0].connectorStyle || + this.endpoints[1].connectorStyle || + params.paintStyle || + _currentInstance.Defaults.PaintStyle || + jsPlumb.Defaults.PaintStyle, true); + + this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || + this.endpoints[1].connectorHoverStyle || + params.hoverPaintStyle || + _currentInstance.Defaults.HoverPaintStyle || + jsPlumb.Defaults.HoverPaintStyle, true); + + this.paintStyleInUse = this.paintStyle; + + /* + * Property: overlays + * List of Overlays for this Connection. + */ + this.overlays = []; + var _overlays = params.overlays || _currentInstance.Defaults.Overlays; + if (_overlays) { + for (var i = 0; i < _overlays.length; i++) { + var o = _overlays[i], _newOverlay = null, _overlayEvents = null; + if (o.constructor == Array) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0]; + var p = jsPlumb.CurrentLibrary.extend({connection:self, _jsPlumb:_currentInstance}, o[1]); // make a copy of the object so as not to mess up anyone else's reference... + if (o.length == 3) jsPlumb.CurrentLibrary.extend(p, o[2]); + _newOverlay = new jsPlumb.Overlays[renderMode][type](p); + if (p.events) { + for (var evt in p.events) { + _newOverlay.bind(evt, p.events[evt]); + } + } + } else if (o.constructor == String) { + _newOverlay = new jsPlumb.Overlays[renderMode][o]({connection:self, _jsPlumb:_currentInstance}); + } else { + _newOverlay = o; + } + + + + this.overlays.push(_newOverlay); + } + } + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Connection. + * + * Parameters: + * event - the event to bind. Available events on a Connection are: + * - *click* : notification that a Connection was clicked. + * - *dblclick* : notification that a Connection was double clicked. + * - *mouseenter* : notification that the mouse is over a Connection. + * - *mouseexit* : notification that the mouse exited a Connection. + * + * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Connection's paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Connection will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Connection. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + this.addOverlay = function(overlay) { self.overlays.push(overlay); }; + + /** + * Function: removeAllOverlays + * Removes all overlays from the Connection, and then repaints. + */ + this.removeAllOverlays = function() { + self.overlays.splice(0, self.overlays.length); + self.repaint(); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + this.removeOverlay = function(overlayId) { + var idx = -1; + for (var i = 0; i < self.overlays.length; i++) { + if (overlayId === self.overlays[i].id) { + idx = i; + break; + } + } + if (idx != -1) self.overlays.splice(idx, 1); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + this.removeOverlays = function() { + for (var i = 0; i < arguments.length; i++) + self.removeOverlay(arguments[i]); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + this.labelStyle = params.labelStyle || _currentInstance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + if (this.label) { + this.overlays.push(new jsPlumb.Overlays[renderMode].Label( { + cssClass:params.cssClass, + labelStyle : this.labelStyle, + label : this.label, + connection:self, + _jsPlumb:_currentInstance + })); + } + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String or a Function that returns a String. + */ + this.setLabel = function(l) { + self.label = l; + _currentInstance.repaint(self.source); + }; + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var otherOffset = offsets[this.targetId]; + var otherWH = sizes[this.targetId]; + var anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1] + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0] + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc }); + + /* + * Paints the Connection. Not exposed for public usage. + * + * Parameters: + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp; + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = false; + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }); + _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + var sAnchorP = this.endpoints[sIdx].anchor.getCurrentLocation(); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.getCurrentLocation(); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + + /* paint overlays*/ + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + var s = o.computeMaxSize(self.connector); + if (s > maxSize) + maxSize = s; + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, self.paintStyleInUse.lineWidth, maxSize); + + self.connector.paint(dim, self.paintStyleInUse); + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim); + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function() { + this.paint({ elId : this.sourceId, recalc : true }); + }; + + _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide + // whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + + // just to make sure the UI gets initialised fully on all browsers. + self.repaint(); + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1 + * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't + * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you + * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or + * 'target' Endpoints for Connections. + * + * + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * container - optional id or selector instructing jsPlumb where to attach the element it creates for this endpoint. you should read the documentation for a full discussion of this. + * connections - optional list of Connections to configure the Endpoint with. + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + params = params || {}; + var self = this; +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Endpoint. + * + * Parameters: + * event - the event to bind. Available events on an Endpoint are: + * - *click* : notification that a Endpoint was clicked. + * - *dblclick* : notification that a Endpoint was double clicked. + * - *mouseenter* : notification that the mouse is over a Endpoint. + * - *mouseexit* : notification that the mouse exited a Endpoint. + * + * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Endpoint's paint style and then repaints the Endpoint. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Endpoint will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Endpoint. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + var visible = true; + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + var id = new String('_jsplumb_e_' + (new Date()).getTime()); + this.getId = function() { return id; }; + if (params.dynamicAnchors) + self.anchor = new DynamicAnchor(jsPlumb.makeAnchors(params.dynamicAnchors)); + else + self.anchor = params.anchor ? jsPlumb.makeAnchor(params.anchor) : params.anchors ? jsPlumb.makeAnchor(params.anchors) : jsPlumb.makeAnchor("TopCenter"); + var _endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot", + endpointArgs = { _jsPlumb:self._jsPlumb, parent:params.parent, container:params.container }; + if (_endpoint.constructor == String) + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint](endpointArgs); + else if (_endpoint.constructor == Array) { + endpointArgs = jsPlumb.extend(_endpoint[1], endpointArgs); + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint[0]](endpointArgs); + } + else + _endpoint = _endpoint.clone(); + + // assign a clone function using our derived endpointArgs. this is used when a drag starts: the endpoint that was dragged is cloned, + // and the clone is left in its place while the original one goes off on a magical journey. + this.clone = function() { + var o = new Object(); + _endpoint.constructor.apply(o, [endpointArgs]); + return o; + }; + + self.endpoint = _endpoint; + self.type = self.endpoint.type; + + // TODO this event listener registration code is identical to what Connection does: it should be refactored. + this.endpoint.bind("click", function(e) { self.fire("click", self, e); }); + this.endpoint.bind("dblclick", function(e) { self.fire("dblclick", self, e); }); + this.endpoint.bind("mouseenter", function(con, e) { + if (!self.isHover()) { + self.setHover(true); + self.fire("mouseenter", self, e); + } + }); + this.endpoint.bind("mouseexit", function(con, e) { + if (self.isHover()) { + self.setHover(false); + self.fire("mouseexit", self, e); + } + }); + // TODO this event listener registration code above is identical to what Connection does: it should be refactored. + + this.setPaintStyle(params.paintStyle || + params.style || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyle, true); + this.setHoverPaintStyle(params.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyle, true); + this.paintStyleInUse = this.paintStyle; + this.connectorStyle = params.connectorStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.parent = params.parent; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + var _element = params.source, + _uuid = params.uuid, + floatingEndpoint = null, + inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + this.element = _element; + var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + /* + * Property: canvas + * The Endpoint's Canvas. + */ + this.canvas = this.endpoint.canvas; + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + this.connections = params.connections || []; + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + var _reattach = params.reattach || false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + + // this avoids a circular loop + if (!ignoreTarget) { + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + t.detach(connection, true); + // check connection to see if we want to delete the other endpoint. + // if the user uses makeTarget to make some element a target for connections, + // it is possible that they will have set 'endpointToDeleteOnDetach': when + // you make a connection to an element that acts as a target (note: NOT an + // Endpoint; just some div as a target), Endpoints are created for that + // connection. so if you then delete that Connection, it is feasible you + // will want these auto-generated endpoints to be removed. + if (connection.endpointToDeleteOnDetach && connection.endpointToDeleteOnDetach.connections.length == 0) + jsPlumb.deleteEndpoint(connection.endpointToDeleteOnDetach); + } + _removeElements(connection.connector.getDisplayElements(), connection.parent); + _removeFromList(connectionsByScope, connection.scope, connection); + if(!ignoreTarget) fireDetachEvent(connection); + } + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + */ + this.detachAll = function() { + while (self.connections.length > 0) { + self.detach(self.connections[0]); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + */ + this.detachFrom = function(targetEndpoint) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + c[i].setHover(false); + self.detach(c[i]); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + return _newEndpoint( { anchor : self.anchor, source : _element, paintStyle : this.paintStyle, endpoint : _endpoint }); + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + return (self.connections.length < _maxConnections) || _maxConnections == -1 ? null : self.connections[0]; + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + /* + * Function: setStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to + * setStyle and deprecate it if so. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + * + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + // a helper function that tries to find a connection to the given element, and returns it if so. if elementWithPrecedence is null, + // or no connection to it is found, we return the first connection in our list. + var findConnectionToUseForDynamicAnchor = function(elementWithPrecedence) { + var idx = 0; + if (elementWithPrecedence != null) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].sourceId == elementWithPrecedence || self.connections[i].targetId == elementWithPrecedence) { + idx = i; + break; + } + } + } + + return self.connections[idx]; + }; + + /* + * Function: paint + * Paints the Endpoint, recalculating offset and anchor positions if necessary. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + + params = params || {}; + var timestamp = params.timestamp; + if (!timestamp || self.timestamp !== timestamp) { + var ap = params.anchorPoint, canvas = params.canvas, connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + var xy = params.offset || offsets[_elementId]; + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (self.anchor.isDynamic) { + if (self.connections.length > 0) { + //var c = self.connections[0]; + var c = findConnectionToUseForDynamicAnchor(params.elementWithPrecedence); + var oIdx = c.endpoints[0] == self ? 1 : 0; + var oId = oIdx == 0 ? c.sourceId : c.targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + } + ap = self.anchor.compute(anchorParams); + } + + var d = _endpoint.compute(ap, self.anchor.getOrientation(), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + _endpoint.paint(d, self.paintStyleInUse, self.anchor); + + self.timestamp = timestamp; + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + var start = function() { + jpc = self.connectorSelector(); + if (self.isFull() && !dragAllowedWhenFull) return false; + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + n.style.position = "absolute"; + var nE = _getElementObject(n); + _appendElement(n, self.parent); + // create and assign an id, and initialize the offset. + var id = _getId(nE); + + // set the offset of this div to be where 'inPlaceCopy' is, to start with. + var ipcoel = _getElementObject(inPlaceCopy.canvas), + ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel), + po = inPlaceCopy.canvas.offsetParent != null ? + inPlaceCopy.canvas.offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(inPlaceCopy.canvas.offsetParent) + : { left:0, top: 0}; + jsPlumb.CurrentLibrary.setOffset(n, {left:ipco.left - po.left, top:ipco.top-po.top}); + + _updateOffset( { elId : id }); + + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor( { reference : self.anchor, referenceCanvas : self.canvas }); + floatingEndpoint = _newEndpoint({ paintStyle : self.paintStyle, endpoint : _endpoint, anchor : floatingAnchor, source : nE }); + + if (jpc == null) { + self.anchor.locked = true; + // create a connection. one end is this endpoint, the + // other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : _getElementObject(_element), + target : _getElementObject(n), + anchors : [ self.anchor, floatingAnchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays + }); + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.connector.setHover(false); + } else { + existingJpc = true; + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.connector.setHover(false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas)); + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; // are we the source or the target? + + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas); + var dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // now we want to get this endpoint's DROP scope, and set it for now: we can only be dropped on drop zones + // that have our drop scope (issue 57). + var dropScope = jsPlumb.CurrentLibrary.getDropScope(c);//jpc.endpoints[anchorIdx == 0 ? 1 : 0].getDropScope(); + jsPlumb.CurrentLibrary.setDragScope(c, dropScope); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it and register connection on it. + floatingConnections[id] = jpc; + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var jpcl = jsPlumb.CurrentLibrary, + dragOptions = params.dragOptions || {}, + defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions), + startEvent = jpcl.dragEvents['start'], + stopEvent = jpcl.dragEvents['stop'], + dragEvent = jpcl.dragEvents['drag']; + + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], + function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + jsPlumb.CurrentLibrary.setOffset(n, _ui); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements( [ n, floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (_reattach) { + + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } else { + jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc); // the main endpoint will inform the floating endpoint + // to disconnect, and also post the detached event. + } + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElements(jpc.connector.getDisplayElements(), self.parent); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint(); + jpc.setHover(false); + jpc.repaint(); + jpc = null; + delete inPlaceCopy; + delete endpointsByElement[floatingEndpoint.elementId]; + floatingEndpoint = null; + delete floatingEndpoint; + + _currentInstance.currentlyDragging = false; + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas) { + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + var drop = function() { + var draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)); + var id = _getAttribute(draggable, "dragId"); + var elId = _getAttribute(draggable, "elId"); + + // restore the original scope if necessary (issue 57) + var scope = _getAttribute(draggable, "originalScope"); + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + var jpc = floatingConnections[id]; + + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // todo test that the target is not full. + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + if (!jpc.suspendedEndpoint) { + _addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + _currentInstance.fire("jsPlumbConnectionDetached", { + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }); + } + + jsPlumb.repaint(elId); + + _currentInstance.fire("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], + targetEndpoint : jpc.endpoints[1], + connection:jpc + }); + } + + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute( _getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + } + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute(_getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + } + }); + + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas)); + + return self; + }; + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + j.init(); + return j; + }; + + var _curryAnchor = function(x,y,ox,oy) { + return function() { + return jsPlumb.makeAnchor(x,y,ox,oy); + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1); + + jsPlumb.Defaults.DynamicAnchors = function() { + return jsPlumb.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"]); + }; + jsPlumb.Anchors["AutoDefault"] = function() { return jsPlumb.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors()); }; + + +})(); +/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the default Connectors, Endpoint and Overlay definitions. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +(function() { + + /** + * + * Helper class to consume unused mouse events by components that are DOM elements and + * are used by all of the different rendering modes. + * + */ + jsPlumb.DOMElementComponent = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + // when render mode is canvas, these functions may be called by the canvas mouse handler. + // this component is safe to pipe this stuff to /dev/null. + this.mousemove = + this.dblclick = + this.click = + this.mousedown = + this.mouseup = function(e) { }; + }; + + /** + * Class: Connectors.Straight + * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters. + */ + jsPlumb.Connectors.Straight = function() { + this.type = "Straight"; + var self = this; + var currentPoints = null; + var _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + _dx = _tx - _sx, _dy = (_ty - _sy); + _m = _dy / _dx, _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + + return currentPoints; + }; + + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + var xp = _sx + (location * _dx); + var yp = (_m == Infinity || _m == -Infinity) ? _sy + (location * (_ty - _sy)) : (_m * xp) + _b; + return {x:xp, y:yp}; + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { return _m; }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + var orientation = distance > 0 ? 1 : -1; + var y = Math.abs(distance * Math.sin(_theta)); + if (_sy > _ty) y = y * -1; + var x = Math.abs(distance * Math.cos(_theta)); + if (_sx > _tx) x = x * -1; + return {x:p.x + (orientation * x), y:p.y + (orientation * y)}; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = self.gradientAtPoint(p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + }; + }; + + + /** + * Class:Connectors.Bezier + * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's + * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below. + */ + /** + * Function:Constructor + * + * Parameters: + * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line. + * Optional; defaults to 150. + * + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + params = params || {}; + this.majorAnchor = params.curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + this.type = "Bezier"; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h; + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) + { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx); var minx2 = Math.min(_CP[0], _CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(_sx,_tx); var maxx2 = Math.max(_CP[0], _CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty); var miny2 = Math.min(_CP[1], _CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(_sy,_ty); var maxy2 = Math.max(_CP[1], _CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + return jsBezier.perpendicularToCurveAt(_makeCurve(), location, length, distance); + }; + + }; + + + /** + * Class: Connectors.Flowchart + * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments. + */ + /** + * Function: Constructor + * + * Parameters: + * stub - minimum length for the stub at each end of the connector. defaults to 30 pixels. + */ + jsPlumb.Connectors.Flowchart = function(params) { + this.type = "Flowchart"; + params = params || {}; + var self = this, + minStubLength = params.stub || params.minStubLength /* bwds compat. */ || 30, + segments = [], + segmentGradients = [], + segmentProportions = [], + segmentLengths = [], + segmentProportionalLengths = [], + points = [], + swapX, + swapY, + /** + * recalculates the gradients of each segment, and the points at which the segments begin, proportional to the total length travelled + * by all the segments that constitute the connector. + */ + updateSegmentGradientsAndProportions = function(startX, startY, endX, endY) { + var total = 0; + for (var i = 0; i < segments.length; i++) { + var sx = i == 0 ? startX : segments[i][2], + sy = i == 0 ? startY : segments[i][3], + ex = segments[i][0], + ey = segments[i][1]; + + segmentGradients[i] = sx == ex ? Infinity : 0; + segmentLengths[i] = Math.abs(sx == ex ? ey - sy : ex - sx); + total += segmentLengths[i]; + } + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segmentLengths[i] / total; + segmentProportions[i] = [curLoc, (curLoc += (segmentLengths[i] / total)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0]; + var ly = segments.length == 0 ? sy : segments[segments.length - 1][1]; + segments.push([x, y, lx, ly]); + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment + * that contains the point which is 'location' distance along the entire path, where + * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths + * are made up of a list of segments, each of which contributes some fraction to + * the total length. + */ + findSegmentForLocation = function(location) { + var idx = segmentProportions.length - 1, inSegmentProportion = 0; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + segments = []; + segmentGradients = []; + segmentProportionalLengths = []; + segmentLengths = []; + segmentProportionals = []; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + offx = (lw / 2) + (minStubLength * 2), + offy = (lw / 2) + (minStubLength * 2), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(), + to = targetAnchor.orientation || targetAnchor.getOrientation(), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx, + h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy; + if (w < minWidth) { + offx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + offy += (minWidth - h) / 2; + h = minWidth; + } + var sx = swapX ? w-offx : offx, + sy = swapY ? h-offy : offy, + tx = swapX ? offx : w-offx , + ty = swapY ? offy : h-offy, + startStubX = sx + (so[0] * minStubLength), + startStubY = sy + (so[1] * minStubLength), + endStubX = tx + (to[0] * minStubLength), + endStubY = ty + (to[1] * minStubLength), + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2); + + x -= offx; y -= offy; + points = [x, y, w, h, sx, sy, tx, ty], extraPoints = []; + + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + if (so[0] == 0) { + var startStubIsBeforeEndStub = startStubY < endStubY; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } + } + else { + var startStubIsBeforeEndStub = startStubX < endStubX; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } + } + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentGradientsAndProportions(sx, sy, tx, ty); + + return points; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location) { + return self.pointAlongPathFrom(location, 0); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location) { + return segmentGradients[findSegmentForLocation(location)["index"]]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance) { + var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segmentLengths[s.index], m = segmentGradients[s.index]; + var e = { + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + return e; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = segmentGradients[p.segmentInfo.index]; + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + + }; + + }; + + // ********************************* END OF CONNECTOR TYPES ******************************************************************* + + // ********************************* ENDPOINT TYPES ******************************************************************* + + /** + * Class: Endpoints.Dot + * A round endpoint, with default radius 10 pixels. + */ + + /** + * Function: Constructor + * + * Parameters: + * + * radius - radius of the endpoint. defaults to 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + this.type = "Dot"; + var self = this; + params = params || {}; + this.radius = params.radius || 10; + this.defaultOffset = 0.5 * this.radius; + this.defaultInnerRadius = this.radius / 3; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var r = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - r; + var y = anchorPoint[1] - r; + return [ x, y, r * 2, r * 2, r ]; + }; + }; + + /** + * Class: Endpoints.Rectangle + * A Rectangular Endpoint, with default size 20x20. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the endpoint. defaults to 20 pixels. + * height - height of the endpoint. defaults to 20 pixels. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + this.type = "Rectangle"; + var self = this; + params = params || {}; + this.width = params.width || 20; + this.height = params.height || 20; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + return [ x, y, width, height]; + }; + }; + + /** + * Class: Endpoints.Image + * Draws an image as the Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * src - location of the image to use. + */ + jsPlumb.Endpoints.Image = function(params) { + + this.type = "Image"; + jsPlumb.DOMElementComponent.apply(this, arguments); + + var self = this, initialized = false; + this.img = new Image(); + self.ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.src || params.url; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + self.anchorPoint = anchorPoint; + if (self.ready) return [anchorPoint[0] - self.img.width / 2, anchorPoint[1] - self.img.height/ 2, self.img.width, self.img.height]; + else return [0,0,0,0]; + }; + + self.canvas = document.createElement("img"), initialized = false; + self.canvas.style["margin"] = 0; + self.canvas.style["padding"] = 0; + self.canvas.style["outline"] = 0; + self.canvas.style["position"] = "absolute"; + self.canvas.className = jsPlumb.endpointClass; + jsPlumb.appendElement(self.canvas, params.parent); + self.attachListeners(self.canvas, self); + + var actuallyPaint = function(d, style, anchor) { + if (!initialized) { + self.canvas.setAttribute("src", self.img.src); + initialized = true; + } + var width = self.img.width, + height = self.img.height, + x = self.anchorPoint[0] - (width/2), + y = self.anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(self.canvas, x, y, width, height); + }; + + this.paint = function(d, style, anchor) { + if (self.ready) { + actuallyPaint(d, style, anchor); + } + else { + window.setTimeout(function() { + self.paint(d, style, anchor); + }, 200); + } + }; + }; + + /** + * Class: Endpoints.Blank + * An Endpoint that paints nothing on the screen, and cannot be interacted with using the mouse. There are no constructor parameters for this Endpoint. + */ + jsPlumb.Endpoints.Blank = function(params) { + var self = this; + this.type = "Blank"; + jsPlumb.DOMElementComponent.apply(this, arguments); + this.compute = function() { + return [0,0,10,0]; + }; + + self.canvas = document.createElement("div"); + self.canvas.style.display = "block"; + self.canvas.style.width = "1px"; + self.canvas.style.height = "1px"; + self.canvas.style.background = "transparent"; + self.canvas.style.position = "absolute"; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function() { }; + }; + + /** + * Class: Endpoints.Triangle + * A triangular Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the triangle's base. defaults to 55 pixels. + * height - height of the triangle from base to apex. defaults to 55 pixels. + */ + jsPlumb.Endpoints.Triangle = function(params) { + this.type = "Triangle"; + params = params || { }; + params.width = params.width || 55; + param.height = params.height || 55; + this.width = params.width; + this.height = params.height; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + return [ x, y, width, height ]; + }; + }; +// ********************************* END OF ENDPOINT TYPES ******************************************************************* + + +// ********************************* OVERLAY DEFINITIONS *********************************************************************** + + /** + * Class: Overlays.Arrow + * + * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length + * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction + * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line + * across the tail. + */ + /** + * Function: Constructor + * + * Parameters: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + this.type = "Arrow"; + params = params || {}; + var self = this; + this.length = params.length || 20; + this.width = params.width || 20; + this.id = params.id; + this.connection = params.connection; + var direction = (params.direction || 1) < 0 ? -1 : 1; + var paintStyle = params.paintStyle || { lineWidth:1 }; + this.loc = params.location == null ? 0.5 : params.location; + // how far along the arrow the lines folding back in come to. default is 62.3%. + var foldback = params.foldback || 0.623; + var _getFoldBackPoint = function(connector, loc) { + if (foldback == 0.5) return connector.pointOnPath(loc); + else { + var adj = 0.5 - foldback; // we calculate relative to the center + return connector.pointAlongPathFrom(loc, direction * self.length * adj); + } + }; + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + // this is the arrow head position + var hxy = connector.pointAlongPathFrom(self.loc, direction * (self.length / 2)); + // this is the center of the tail + var txy = connector.pointAlongPathFrom(self.loc, -1 * direction * (self.length / 2)), tx = txy.x, ty = txy.y; + // this is the tail vector + var tail = connector.perpendicularToPathAt(self.loc, self.width, -1 * direction * (self.length / 2)); + // this is the point the tail goes in to + var cxy = _getFoldBackPoint(connector, self.loc); + + // if loc = 1, then hxy should be flush with the element, or if direction == -1, the tail midpoint. + if (self.loc == 1) { + var lxy = connector.pointOnPath(self.loc); + // TODO determine why the 1.2.6 released version does not + // use 'direction' in the two equations below, yet both + // that and 1.3.0 still paint the arrows correctly. + var dx = (lxy.x - hxy.x) * direction, dy = (lxy.y - hxy.y) * direction; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + // if loc = 0, then tail midpoint should be flush with the element, or, if direction == -1, hxy should be. + if (self.loc == 0) { + var lxy = connector.pointOnPath(self.loc); + var tailMid = foldback > 1 ? cxy : { + x:tail[0].x + ((tail[1].x - tail[0].x) / 2), + y:tail[0].y + ((tail[1].y - tail[0].y) / 2) + }; + var dx = (lxy.x - tailMid.x) * direction, dy = (lxy.y - tailMid.y) * direction; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x); + var maxx = Math.max(hxy.x, tail[0].x, tail[1].x); + var miny = Math.min(hxy.y, tail[0].y, tail[1].y); + var maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + var d = { hxy:hxy, tail:tail, cxy:cxy }, + strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle, + fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle, + lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth; + + self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions); + + return [ minx, maxx, miny, maxy]; + }; + }; + + /** + * Class: Overlays.PlainArrow + * + * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "PlainArrow"; + }; + + /** + * Class: Overlays.Diamond + * + * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40; + var p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "Diamond"; + }; + + + + /** + * Class: Overlays.Label + * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb + * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter + * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it + * puts on the Label's 'style' attribute, so the end result is the same. + */ + /** + * Function: Constructor + * + * Parameters: + * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes + * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle. + * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * labelStyle - (deprecated) js object containing style instructions for the label. defaults to jsPlumb.Defaults.LabelStyle. + * borderWidth - (deprecated) width of a border to paint. defaults to zero. + * borderStyle - (deprecated) strokeStyle to use when painting the border, if necessary. + * + */ + jsPlumb.Overlays.Label = function(params) { + this.type = "Label"; + jsPlumb.DOMElementComponent.apply(this, arguments); + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.labelStyle.font = this.labelStyle.font || "12px sans-serif"; + this.label = params.label || "banana"; + this.connection = params.connection; + this.id = params.id; + var self = this; + var labelWidth = null, labelHeight = null, labelText = null, labelPadding = null; + this.location = params.location || 0.5; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var initialised = false, + labelText = null, + div = document.createElement("div"); + div.style["position"] = "absolute"; + div.style["font"] = self.labelStyle.font; + div.style["color"] = self.labelStyle.color || "black"; + if (self.labelStyle.fillStyle) div.style["background"] = self.labelStyle.fillStyle;//_convertStyle(self.labelStyle.fillStyle, true); + if (self.labelStyle.borderWidth > 0) { + var dStyle = self.labelStyle.borderStyle ? self.labelStyle.borderStyle/*_convertStyle(self.labelStyle.borderStyle, true)*/ : "black"; + div.style["border"] = self.labelStyle.borderWidth + "px solid " + dStyle; + } + if (self.labelStyle.padding) div.style["padding"] = self.labelStyle.padding; + + var clazz = params["_jsPlumb"].overlayClass + " " + + (self.labelStyle.cssClass ? self.labelStyle.cssClass : + params.cssClass ? params.cssClass : ""); + + div.className = clazz; + + jsPlumb.appendElement(div, params.connection.parent); + jsPlumb.getId(div); + self.attachListeners(div, self); + this.paint = function(connector, d, connectorDimensions) { + if (!initialised) { + connector.appendDisplayElement(div); + self.attachListeners(div, connector); + initialised = true; + } + div.style.left = (connectorDimensions[0] + d.minx) + "px"; + div.style.top = (connectorDimensions[1] + d.miny) + "px"; + }; + + this.getTextDimensions = function(connector) { + labelText = typeof self.label == 'function' ? self.label(self) : self.label; + div.innerHTML = labelText.replace(/\r\n/g, "
"); + var de = jsPlumb.CurrentLibrary.getElementObject(div), + s = jsPlumb.CurrentLibrary.getSize(de); + return {width:s[0], height:s[1]}; + }; + + this.computeMaxSize = function(connector) { + var td = self.getTextDimensions(connector); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + var td = self.getTextDimensions(connector); + if (td.width != null) { + var cxy = connector.pointOnPath(self.location); + + var minx = cxy.x - (td.width / 2); + var miny = cxy.y - (td.height / 2); + + self.paint(connector, { + minx:minx, + miny:miny, + td:td, + cxy:cxy + }, connectorDimensions); + + return [minx, minx+td.width, miny, miny+td.height]; + } + else return [0,0,0,0]; + }; + }; + + // ********************************* END OF OVERLAY DEFINITIONS *********************************************************************** + + // ********************************* OVERLAY CANVAS RENDERERS*********************************************************************** + + // ********************************* END OF OVERLAY CANVAS RENDERERS *********************************************************************** +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the VML renderers. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +;(function() { + + // http://ajaxian.com/archives/the-vml-changes-in-ie-8 + // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/ + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + + var vmlAttributeMap = { + "stroke-linejoin":"joinstyle", + "joinstyle":"joinstyle", + "endcap":"endcap", + "miterlimit":"miterlimit" + }; + + if (document.createStyleSheet) { + + // this is the style rule for IE7/6: it uses a CSS class, tidy. + document.createStyleSheet().addRule(".jsplumb_vml", "behavior:url(#default#VML);position:absolute;"); + + // these are for VML in IE8. you have to explicitly call out which elements + // you're going to expect to support VML! + // + // try to avoid IE8. it is recommended you set X-UA-Compatible="IE=7" if you can. + // + document.createStyleSheet().addRule("jsplumb\\:textbox", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:oval", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:rect", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:stroke", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:shape", "behavior:url(#default#VML);position:absolute;"); + + // in this page it is also mentioned that IE requires the extra arg to the namespace + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either. + // var iev = document.documentMode; + //if (!iev || iev < 8) + document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); + //else + // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML"); + } + + var scale = 1000, + _atts = function(o, atts) { + for (var i in atts) { + // IE8 fix: setattribute does not work after an element has been added to the dom! + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + //o.setAttribute(i, atts[i]); + o[i] = atts[i]; + } + }, + _node = function(name, d, atts) { + atts = atts || {}; + var o = document.createElement("jsplumb:" + name); + o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml"; + _pos(o, d); + _atts(o, atts); + return o; + }, + _pos = function(o,d) { + o.style.left = d[0] + "px"; + o.style.top = d[1] + "px"; + o.style.width= d[2] + "px"; + o.style.height= d[3] + "px"; + o.style.position = "absolute"; + }, + _conv = function(v) { + return Math.floor(v * scale); + }, + _convertStyle = function(s, ignoreAlpha) { + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + + return o; + }, + _applyStyles = function(node, style, component) { + var styleToWrite = {}; + if (style.strokeStyle) { + styleToWrite["stroked"] = "true"; + styleToWrite["strokecolor"] =_convertStyle(style.strokeStyle, true); + styleToWrite["strokeweight"] = style.lineWidth + "px"; + } + else styleToWrite["stroked"] = "false"; + + if (style.fillStyle) { + styleToWrite["filled"] = "true"; + styleToWrite["fillcolor"] = _convertStyle(style.fillStyle, true); + } + else styleToWrite["filled"] = "false"; + + if(style["dashstyle"]) { + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = style["dashstyle"]; + } + else if (style["stroke-dasharray"] && style["lineWidth"]) { + var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",", + parts = style["stroke-dasharray"].split(sep), + styleToUse = ""; + for(var i = 0; i < parts.length; i++) { + styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep); + } + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = styleToUse; + } + + _atts(node, styleToWrite); + }, + /* + * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. + */ + VmlComponent = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }, + /* + * Base class for Vml connectors. extends VmlComponent. + */ + VmlConnector = function(params) { + var self = this; + self.strokeNode = null; + self.canvas = null; + VmlComponent.apply(this, arguments); + clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : ""); + this.paint = function(d, style, anchor) { + if (style != null) { + var path = self.getPath(d), p = { "path":path }; + + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + outlineStyle = { + strokeStyle:_convertStyle(style.outlineColor), + lineWidth:outlineStrokeWidth + }; + + if (self.bgCanvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.bgCanvas = _node("shape", d, p); + jsPlumb.appendElement(self.bgCanvas, params.parent); + _pos(self.bgCanvas, d); + displayElements.push(self.bgCanvas); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.bgCanvas, d); + _atts(self.bgCanvas, p); + } + + _applyStyles(self.bgCanvas, outlineStyle, self); + } + + if (self.canvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.canvas = _node("shape", d, p); + jsPlumb.appendElement(self.canvas, params.parent); + displayElements.push(self.canvas); + + self.attachListeners(self.canvas, self); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.canvas, d); + _atts(self.canvas, p); + } + + _applyStyles(self.canvas, style, self); + } + }; + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + self.canvas.parentNode.appendChild(el); + displayElements.push(el); + }; + }, + /* + * + * Base class for Vml Endpoints. extends VmlComponent. + * + */ + VmlEndpoint = function(params) { + VmlComponent.apply(this, arguments); + var vml = null, self = this; + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function(d, style, anchor) { + var p = { }; + + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (vml == null) { + p["class"] = jsPlumb.endpointClass; + vml = self.getVml([0,0, d[2], d[3]], p, anchor); + self.canvas.appendChild(vml); + self.attachListeners(vml, self); + } + else { + //p["coordsize"] = "1,1";//(d[2] * scale) + "," + (d[3] * scale); again, unsure. + _pos(vml, [0,0, d[2], d[3]]); + _atts(vml, p); + } + + _applyStyles(vml, style); + }; + }; + + jsPlumb.Connectors.vml.Bezier = function() { + jsPlumb.Connectors.Bezier.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Straight = function() { + jsPlumb.Connectors.Straight.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Flowchart = function() { + jsPlumb.Connectors.Flowchart.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l"; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]); + } + // finally draw a line to the end + p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e"; + return p; + }; + }; + + jsPlumb.Endpoints.vml.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("oval", d, atts); }; + }; + + jsPlumb.Endpoints.vml.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("rect", d, atts); }; + }; + + /* + * VML Image Endpoint is the same as the default image endpoint. + */ + jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image; + + /** + * placeholder for Blank endpoint in vml renderer. + */ + jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank; + + /** + * VML Label renderer. uses the default label renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label; + + var AbstractVmlArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + VmlComponent.apply(this, arguments); + var self = this, canvas = null, path =null; + var getPath = function(d, connectorDimensions) { + return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) + + " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + + " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + + " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + + " x e"; + }; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) { + var p = {}; + if (strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] =_convertStyle(strokeStyle, true); + } + if (lineWidth) p["strokeweight"] = lineWidth + "px"; + if (fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = fillStyle; + } + var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + w = Math.abs(xmax - xmin), + h = Math.abs(ymax - ymin), + dim = [xmin, ymin, w, h]; + + // for VML, we create overlays using shapes that have the same dimensions and + // coordsize as their connector - overlays calculate themselves relative to the + // connector (it's how it's been done since the original canvas implementation, because + // for canvas that makes sense). + p["path"] = getPath(d, connectorDimensions); + p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale); + + dim[0] = connectorDimensions[0]; + dim[1] = connectorDimensions[1]; + dim[2] = connectorDimensions[2]; + dim[3] = connectorDimensions[3]; + + if (canvas == null) { + //p["class"] = jsPlumb.overlayClass; // TODO currentInstance? + canvas = _node("shape", dim, p); + connector.appendDisplayElement(canvas); + self.attachListeners(canvas, connector); + } + else { + _pos(canvas, dim); + _atts(canvas, p); + } + }; + }; + + jsPlumb.Overlays.vml.Arrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.vml.PlainArrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.vml.Diamond = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the SVG renderers. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +/** + * SVG support for jsPlumb. + * + * things to investigate: + * + * gradients: https://developer.mozilla.org/en/svg_in_html_introduction + * css:http://tutorials.jenkov.com/svg/svg-and-css.html + * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath + * pointer events: https://developer.mozilla.org/en/css/pointer-events + * + */ +;(function() { + + var svgAttributeMap = { + "stroke-linejoin":"stroke-linejoin", + "joinstyle":"stroke-linejoin", + "stroke-dashoffset":"stroke-dashoffset" + }; + + var ns = { + svg:"http://www.w3.org/2000/svg", + xhtml:"http://www.w3.org/1999/xhtml" + }, + _attr = function(node, attributes) { + for (var i in attributes) + node.setAttribute(i, "" + attributes[i]); + }, + _node = function(name, attributes) { + var n = document.createElementNS(ns.svg, name); + attributes = attributes || {}; + attributes["version"] = "1.1"; + attributes["xmnls"] = ns.xhtml; + _attr(n, attributes); + return n; + }, + _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; }, + _convertStyle = function(s, ignoreAlpha) { + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + + return o; + }, + _clearGradient = function(parent) { + for (var i = 0; i < parent.childNodes.length; i++) { + if (parent.childNodes[i].tagName == "linearGradient" || parent.childNodes[i].tagName == "radialGradient") + parent.removeChild(parent.childNodes[i]); + } + }, + _updateGradient = function(parent, node, style, dimensions) { + var id = "jsplumb_gradient_" + (new Date()).getTime(); + // first clear out any existing gradient + _clearGradient(parent); + // this checks for an 'offset' property in the gradient, and in the absence of it, assumes + // we want a linear gradient. if it's there, we create a radial gradient. + // it is possible that a more explicit means of defining the gradient type would be + // better. relying on 'offset' means that we can never have a radial gradient that uses + // some default offset, for instance. + if (!style.gradient.offset) { + var g = _node("linearGradient", {id:id}); + parent.appendChild(g); + } + else { + var g = _node("radialGradient", { + id:id + }); + parent.appendChild(g); + } + + // the svg radial gradient seems to treat stops in the reverse + // order to how canvas does it. so we want to keep all the maths the same, but + // iterate the actual style declarations in reverse order, if the x indexes are not in order. + for (var i = 0; i < style.gradient.stops.length; i++) { + // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of + // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments. + // so, not too concerned about leaving this in for now. + var styleToUse = i; + if (dimensions.length == 8) + styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i; + else + styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i; + var stopColor = _convertStyle(style.gradient.stops[styleToUse][1], true); + var s = _node("stop", {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor}); + g.appendChild(s); + } + var applyGradientTo = style.strokeStyle ? "stroke" : "fill"; + node.setAttribute("style", applyGradientTo + ":url(#" + id + ")"); + }, + _applyStyles = function(parent, node, style, dimensions) { + + if (style.gradient) { + _updateGradient(parent, node, style, dimensions); + } + else { + // make sure we clear any existing gradient + _clearGradient(parent); + node.setAttribute("style", ""); + } + + node.setAttribute("fill", style.fillStyle ? _convertStyle(style.fillStyle, true) : "none"); + node.setAttribute("stroke", style.strokeStyle ? _convertStyle(style.strokeStyle, true) : "none"); + if (style.lineWidth) { + node.setAttribute("stroke-width", style.lineWidth); + } + + // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like + // the syntax in VML but is actually kind of nasty: values are given in the pixel + // coordinate space, whereas in VML they are multiples of the width of the stroked + // line, which makes a lot more sense. for that reason, jsPlumb is supporting both + // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from + // VML, which will be the preferred method. the code below this converts a dashstyle + // attribute given in terms of stroke width into a pixel representation, by using the + // stroke's lineWidth. + if(style["stroke-dasharray"]) { + node.setAttribute("stroke-dasharray", style["stroke-dasharray"]); + } + if (style["dashstyle"] && style["lineWidth"]) { + var sep = style["dashstyle"].indexOf(",") == -1 ? " " : ",", + parts = style["dashstyle"].split(sep), + styleToUse = ""; + parts.forEach(function(p) { + styleToUse += (Math.floor(p * style.lineWidth) + sep); + }); + node.setAttribute("stroke-dasharray", styleToUse); + } + + // extra attributes such as join type, dash offset. + for (var i in svgAttributeMap) { + if (style[i]) { + node.setAttribute(svgAttributeMap[i], style[i]); + } + } + }, + _decodeFont = function(f) { + var r = /([0-9].)(p[xt])\s(.*)/; + var bits = f.match(r); + return {size:bits[1] + bits[2], font:bits[3]}; + }; + + /* + * Base class for SVG components. + */ + var SvgComponent = function(cssClass, originalArgs, pointerEventsSpec) { + var self = this; + pointerEventsSpec = pointerEventsSpec || "all"; + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + self.canvas = null, self.path = null, self.svg = null; + + this.setHover = function() { }; + + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.sizeCanvas(self.canvas,0,0,1,1); + + var clazz = cssClass + " " + (originalArgs[0].cssClass || ""); + self.canvas.className = clazz; + + self.svg = _node("svg", { + "style":"", + "width":0, + "height":0, + "pointer-events":pointerEventsSpec/*, + "class": clazz*/ + }); + + jsPlumb.appendElement(self.canvas, originalArgs[0]["parent"]); + self.canvas.appendChild(self.svg); + + // TODO this displayElement stuff is common between all components, across all + // renderers. would be best moved to jsPlumbUIComponent. + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + this.paint = function(d, style, anchor) { + if (style != null) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + _attr(self.svg, { + "style":_pos([0,0,d[2], d[3]]), + "width": d[2], + "height": d[3] + }); + self._paint.apply(this, arguments); + } + }; + }; + + /* + * Base class for SVG connectors. + */ + var SvgConnector = function(params) { + var self = this; + SvgComponent.apply(this, [ params["_jsPlumb"].connectorClass, arguments, "none" ]); + this._paint = function(d, style) { + var p = self.getPath(d), a = { "d":p }, outlineStyle = null; + a["pointer-events"] = "all"; + + // outline style. actually means drawing an svg object underneath the main one. + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + outlineStyle = { + strokeStyle:_convertStyle(style.outlineColor), + lineWidth:outlineStrokeWidth + }; + + if (self.bgPath == null) { + self.bgPath = _node("path", a); + self.svg.appendChild(self.bgPath); + self.attachListeners(self.bgPath, self); + } + else { + _attr(self.bgPath, a); + } + + _applyStyles(self.svg, self.bgPath, outlineStyle, d); + } + + + if (self.path == null) { + self.path = _node("path", a); + self.svg.appendChild(self.path); + self.attachListeners(self.path, self); + } + else { + _attr(self.path, a); + } + + _applyStyles(self.svg, self.path, style, d); + }; + }; + + /* + * SVG Bezier Connector + */ + jsPlumb.Connectors.svg.Bezier = function(params) { + jsPlumb.Connectors.Bezier.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]; }; + }; + + /* + * SVG straight line Connector + */ + jsPlumb.Connectors.svg.Straight = function(params) { + jsPlumb.Connectors.Straight.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; }; + }; + + jsPlumb.Connectors.svg.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "M " + dimensions[4] + "," + dimensions[5]; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)]; + } + // finally draw a line to the end + p = p + " " + dimensions[6] + "," + dimensions[7]; + return p; + }; + }; + + /* + * Base class for SVG endpoints. + */ + var SvgEndpoint = function(params) { + var self = this; + SvgComponent.apply(this, [ params["_jsPlumb"].endpointClass, arguments, "all" ]); + this._paint = function(d, style) { + var s = jsPlumb.extend({}, style); + if (s.outlineColor) { + s.strokeWidth = s.outlineWidth; + s.strokeStyle = _convertStyle(s.outlineColor, true); + } + + if (self.node == null) { + self.node = self.makeNode(d, s); + self.svg.appendChild(self.node); + self.attachListeners(self.node, self); + } + _applyStyles(self.svg, self.node, s, d); + _pos(self.node, d); + }; + }; + + /* + * SVG Dot Endpoint + */ + jsPlumb.Endpoints.svg.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("circle", { + "cx" : d[2] / 2, + "cy" : d[3] / 2, + "r" : d[2] / 2 + }); + }; + }; + + /* + * SVG Rectangle Endpoint + */ + jsPlumb.Endpoints.svg.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("rect", { + "width":d[2], + "height":d[3] + }); + }; + }; + + /* + * SVG Image Endpoint is the default image endpoint. + */ + jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image; + /* + * Blank endpoint in svg renderer is the default Blank endpoint. + */ + jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank; + /* + * Label endpoint in svg renderer is the default Label endpoint. + */ + jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label; + + + var AbstractSvgArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + var self = this, path =null; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + } + + _attr(path, { + "d" : makePath(d), + stroke : strokeStyle ? strokeStyle : null, + fill : fillStyle ? fillStyle : null + }); + }; + var makePath = function(d) { + return "M" + d.hxy.x + "," + d.hxy.y + + " L" + d.tail[0].x + "," + d.tail[0].y + + " L" + d.cxy.x + "," + d.cxy.y + + " L" + d.tail[1].x + "," + d.tail[1].y + + " L" + d.hxy.x + "," + d.hxy.y; + }; + }; + + jsPlumb.Overlays.svg.Arrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.svg.PlainArrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.svg.Diamond = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the HTML5 canvas renderers. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +;(function() { + +// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS ******************************************************************* + + // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. + var _connectionBeingDragged = null, + _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }, + _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }, + _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }, + _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, + _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }, + _setOffset = function(el, o) { jsPlumb.CurrentLibrary.setOffset(el, o); }; + + /* + * Class:CanvasMouseAdapter + * Provides support for mouse events on canvases. + */ + var CanvasMouseAdapter = function() { + var self = this; + self.overlayPlacements = []; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + jsPlumb.EventGenerator.apply(this, arguments); + /** + * returns whether or not the given event is ojver a painted area of the canvas. + */ + this._over = function(e) { + var o = _getOffset(_getElementObject(self.canvas)), + pageXY = _pageXY(e), + x = pageXY[0] - o.left, y = pageXY[1] - o.top; + if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + return false; + }; + + var _mouseover = false; + var _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false; + var _nullSafeHasClass = function(el, clazz) { + return el != null && _hasClass(el, clazz); + }; + this.mousemove = function(e) { + var pageXY = _pageXY(e), clientXY = _clientXY(e), + ee = document.elementFromPoint(clientXY[0], clientXY[1]), + eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); + var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); + if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.fire("mouseenter", self, e); + return true; + } + // TODO here there is a remote chance that the overlay the mouse moved onto + // is actually not an overlay for the current component. a more thorough check would + // be to ensure the overlay belonged to the current component. + else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { + _mouseover = false; + self.fire("mouseexit", self, e); + } + self.fire("mousemove", self, e); + }; + + this.click = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); + self.fire("mousedown", self, e); + } + }; + + this.mouseup = function(e) { + //if (self == _connectionBeingDragged) _connectionBeingDragged = null; + _mouseDown = false; + self.fire("mouseup", self, e); + }; + }; + + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + jsPlumb.appendElement(canvas, params.parent); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + params["_jsPlumb"].getId(canvas, params.uuid); + + return canvas; + }; + + /** + * Class:CanvasConnector + * Superclass for Canvas Connector renderers. + */ + var CanvasConnector = jsPlumb.CanvasConnector = function(params) { + + CanvasMouseAdapter.apply(this, arguments); + + var _paintOneStyle = function(dim, aStyle) { + self.ctx.save(); + jsPlumb.extend(self.ctx, aStyle); + if (aStyle.gradient) { + var g = self.createGradient(dim, self.ctx); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + self.ctx.strokeStyle = g; + } + self._paint(dim); + self.ctx.restore(); + }; + + var self = this, + clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent + }); + self.ctx = self.canvas.getContext("2d"); + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + self.paint = function(dim, style) { + if (style != null) { + + jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); + + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + _paintOneStyle(dim, outlineStyle); + } + _paintOneStyle(dim, style); + } + }; + }; + + /** + * Class:CanvasEndpoint + * Superclass for Canvas Endpoint renderers. + */ + var CanvasEndpoint = function(params) { + var self = this; + CanvasMouseAdapter.apply(this, arguments); + var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent + }); + self.ctx = self.canvas.getContext("2d"); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + // _paintOneStyle(d, outlineStyle); + } + + self._paint.apply(this, arguments); + }; + }; + + jsPlumb.Endpoints.canvas.Dot = function(params) { + var self = this; + jsPlumb.Endpoints.Dot.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }; + var calculateAdjustments = function(gradient) { + var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius; + gradient.offset && (offsetAdjustment = parseValue(gradient.offset)); + gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius)); + return [offsetAdjustment, innerRadius]; + }; + this._paint = function(d, style, anchor) { + if (style != null) { + var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(); + jsPlumb.extend(ctx, style); + if (style.gradient) { + var adjustments = calculateAdjustments(style.gradient), + yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0], + xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0], + g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + ctx.beginPath(); + ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + } + }; + }; + + jsPlumb.Endpoints.canvas.Rectangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) { + + var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(); + jsPlumb.extend(ctx, style); + + /* canvas gradient */ + if (style.gradient) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, d[2], d[3]); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + jsPlumb.Endpoints.canvas.Triangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Triangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) + { + var width = d[2], height = d[3], x = d[0], y = d[1]; + + var ctx = self.canvas.getContext('2d'); + var offsetX = 0, offsetY = 0, angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = style.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + /* + * Canvas Image Endpoint: uses the default version, which creates an tag. + */ + jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image; + + /* + * Blank endpoint in all renderers is just the default Blank endpoint. + */ + jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank; + + /* + * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Bezier = function() { + var self = this; + jsPlumb.Connectors.Bezier.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO i doubt this handles the case that source and target are swapped. + this.createGradient = function(dim, ctx, swap) { + return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + /* + * Canvas straight line Connector. Draws a straight line onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Straight = function() { + var self = this; + jsPlumb.Connectors.Straight.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO this does not handle the case that src and target are swapped. + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + jsPlumb.Connectors.canvas.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + +// ********************************* END OF CANVAS RENDERERS ******************************************************************* + + jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label; + + /** + * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. + */ + var CanvasOverlay = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }; + + var AbstractCanvasArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + CanvasOverlay.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + var ctx = connector.ctx; + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(d.hxy.x, d.hxy.y); + ctx.lineTo(d.tail[0].x, d.tail[0].y); + ctx.lineTo(d.cxy.x, d.cxy.y); + ctx.lineTo(d.tail[1].x, d.tail[1].y); + ctx.lineTo(d.hxy.x, d.hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + }; + + jsPlumb.Overlays.canvas.Arrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.canvas.PlainArrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.canvas.Diamond = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the YUI3 adapter. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +/** + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + */ +(function() { + + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function( v, b, s ) { + for( var i = +b || 0, l = this.length; i < l; i++ ) { + if( this[i]===v || s && this[i]==v ) { return i; } + } + return -1; + }; + } + + var Y; + + YUI().use('node', 'dd', 'anim', function(_Y) { + Y = _Y; + Y.on("domready", function() { jsPlumb.init(); }); + }); + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }, + ddEvents = [ "drag:mouseDown", "drag:afterMouseDown", "drag:mouseup", + "drag:align", "drag:removeHandle", "drag:addHandle", "drag:removeInvalid", "drag:addInvalid", + "drag:start", "drag:end", "drag:drag", "drag:over", "drag:enter", + "drag:exit", "drag:drophit", "drag:dropmiss", "drop:over", "drop:enter", "drop:exit", "drop:hit" + ], + animEvents = [ "tween" ], + /** + * helper function to curry callbacks for some element. + */ + _wrapper = function(fn) { + return function() { + try { + fn.apply(this, arguments); + } + catch (e) { + console.log("wrap fail", e); + } + }; + }, + /** + * extracts options from the given options object, leaving out event handlers. + */ + _getDDOptions = function(options) { + var o = {}; + for (var i in options) if (ddEvents.indexOf(i) == -1) o[i] = options[i]; + return o; + }, + /** + * attaches all event handlers found in options to the given dragdrop object, and registering + * the given el as the element of interest. + */ + _attachListeners = function(dd, options, eventList) { + for (var ev in options) { + if (eventList.indexOf(ev) != -1) { + var w = _wrapper(options[ev]); + dd.on(ev, w); + } + } + }, + _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}, + _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }, + _lastDragObject = null, + _extend = function(o1, o2) { + for (var i in o2) + o1[i] = o2[i]; + return o1; + }, + _getAttribute = function(el, attributeId) { + return el.getAttribute(attributeId); + }, + _getElementObject = function(el) { + return typeof el == 'string' ? Y.one('#' + el) : Y.one(el); + }; + + jsPlumb.CurrentLibrary = { + + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + var o = _extend({node:el, to:properties}, options); + var id = _getAttribute(el, "id"); + o["tween"] = jsPlumb.wrap(properties["tween"], function() { + jsPlumb.repaint(id); + }); + var a = new Y.Anim(o); + _attachListeners(a, o, animEvents); + a.run(); + }, + + appendElement : function(child, parent) { + _getElementObject (parent).append(child); + }, + + /** + * event binding wrapper. + */ + bind : function(el, event, callback) { + _getElementObject(el).on(event, callback); + }, + + dragEvents : { + "start":"drag:start", "stop":"drag:end", "drag":"drag:drag", "step":"step", + "over":"drop:enter", "out":"drop:exit", "drop":"drop:hit" + }, + + extend : _extend, + + getAttribute : _getAttribute, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + // this is a workaround for the unfortunate fact that in YUI3, the 'drop:exit' event does + // not contain a reference to the drag that just exited. single-threaded js to the + // rescue: we'll just keep it for ourselves. + if (eventArgs[0].drag) _lastDragObject = eventArgs[0].drag.el; + return _lastDragObject; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + return dd.scope; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getElementObject : _getElementObject, + + getOffset : function(el) { + var o = Y.DOM.getXY(el._node); + return {left:o[0], top:o[1]}; + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).get("parentNode"); + }, + + getScrollLeft : function(el) { + return 0; + }, + + getScrollTop : function(el) { + return 0; + }, + + getSize : function(el) { + return [ el._node.offsetWidth, el._node.offsetHeight ]; + }, + + getUIPosition : function(args) { + var n = args[0].currentTarget.el._node, + o = Y.DOM.getXY(n); + return {left:o[0], top:o[1]}; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options) { + var _opts = _getDDOptions(options); + var id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drag(_opts); + dd.el = el; + + var scope = options['scope'] || jsPlumb.Defaults.Scope; + dd.scope = scope; + + _draggablesById[id] = dd; + _add(_draggablesByScope, scope, dd); + + _attachListeners(dd, options, ddEvents); + }, + + initDroppable : function(el, options) { + var _opts = _getDDOptions(options); + var id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drop(_opts); + + _droppableOptions[id] = options; + + options = _extend({}, options); + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _droppableScopesById[id] = scope; + + options["drop:enter"] = jsPlumb.wrap(options["drop:enter"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, true); + }, true); + options["drop:exit"] = jsPlumb.wrap(options["drop:exit"], function(e) { + _checkHover(el, false); + }); + options["drop:hit"] = jsPlumb.wrap(options["drop:hit"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, false); + }, true); + + _attachListeners(dd, options, ddEvents); + }, + + isAlreadyDraggable : function(el) { + el = _getElementObject(el); + return el.hasClass("yui3-dd-draggable"); + }, + + isDragSupported : function(el) { return true; }, + isDropSupported : function(el) { return true; }, + removeClass : function(el, clazz) { el.removeClass(clazz); }, + removeElement : function(el) { _getElementObject(el).remove(); }, + + setAttribute : function(el, attributeName, attributeValue) { + el.setAttribute(attributeName, attributeValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + if (dd) dd.set("lock", !draggable); + }, + + setDragScope : function(el, scope) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + if (dd) dd.scope = scope; + }, + + setOffset : function(el, o) { + el = _getElementObject(el); + el.set("top", o.top); + el.set("left", o.left); + } + }; +})();(function(){if(typeof Math.sgn=="undefined")Math.sgn=function(a){return a==0?0:a>0?1:-1};var p={subtract:function(a,b){return{x:a.x-b.x,y:a.y-b.y}},dotProduct:function(a,b){return a.x*b.x+a.y*b.y},square:function(a){return Math.sqrt(a.x*a.x+a.y*a.y)},scale:function(a,b){return{x:a.x*b,y:a.y*b}}},y=Math.pow(2,-65),u=function(a,b){for(var g=[],d=b.length-1,h=2*d-1,f=[],c=[],l=[],k=[],i=[[1,0.6,0.3,0.1],[0.4,0.6,0.6,0.4],[0.1,0.3,0.6,1]],e=0;e<=d;e++)f[e]=p.subtract(b[e],a);for(e=0;e<=d-1;e++){c[e]= +p.subtract(b[e+1],b[e]);c[e]=p.scale(c[e],3)}for(e=0;e<=d-1;e++)for(var m=0;m<=d;m++){l[e]||(l[e]=[]);l[e][m]=p.dotProduct(c[e],f[m])}for(e=0;e<=h;e++){k[e]||(k[e]=[]);k[e].y=0;k[e].x=parseFloat(e)/h}h=d-1;for(f=0;f<=d+h;f++){c=Math.min(f,d);for(e=Math.max(0,f-h);e<=c;e++){j=f-e;k[e+j].y+=l[j][e]*i[j][e]}}d=b.length-1;k=s(k,2*d-1,g,0);h=p.subtract(a,b[0]);l=p.square(h);for(e=i=0;e=64){g[0]=(a[0].x+a[b].x)/2;return 1}var n,o,q;k=a[0].y-a[b].y;i=a[b].x-a[0].x;e=a[0].x*a[b].y-a[b].x*a[0].y;m=max_distance_below=0;for(o=1;om)m=q;else if(q0?1:-1,c=null;h endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + endpointsByElement = {}, + endpointsByUUID = {}, + offsets = {}, + offsetTimestamps = {}, + floatingConnections = {}, + draggableStates = {}, + _mouseEventsEnabled = this.Defaults.MouseEventsEnabled, + _draggableByDefault = true, + canvasList = [], + sizes = [], + listeners = {}, // a map: keys are event types, values are lists of listeners. + DEFAULT_SCOPE = this.Defaults.Scope, + renderMode = null, // will be set in init() + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }, + + /** + * appends an element to some other element, which is calculated as follows: + * + * 1. if jsPlumb.Defaults.Container exists, use that element. + * 2. if the 'parent' parameter exists, use that. + * 3. otherwise just use the document body. + * + */ + _appendElement = function(el, parent) { + if (_currentInstance.Defaults.Container) + jsPlumb.CurrentLibrary.appendElement(el, _currentInstance.Defaults.Container); + else if (!parent) + document.body.appendChild(el); + else + jsPlumb.CurrentLibrary.appendElement(el, parent); + }, + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + _timestamp = function() { return "" + (new Date()).getTime(); }, + + /** + * YUI, for some reason, put the result of a Y.all call into an object that contains + * a '_nodes' array, instead of handing back an array-like object like the other + * libraries do. + */ + _convertYUICollection = function(c) { + return c._nodes ? c._nodes : c; + }, + + /** + * Draws an endpoint and its connections. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + _draw = function(element, ui, timestamp) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (!timestamp) timestamp = _timestamp(); + if (endpoints) { + _updateOffset( { elId : id, offset : ui, recalc : false, timestamp : timestamp }); // timestamp is checked against last update cache; it is + // valid for one paint cycle. + var myOffset = offsets[id], myWH = sizes[id]; + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + var l = endpoints[i].connections; + for ( var j = 0; j < l.length; j++) { + l[j].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); // ...paint each connection. + // then, check for dynamic endpoint; need to repaint it. + var oIdx = l[j].endpoints[0] == endpoints[i] ? 1 : 0, + otherEndpoint = l[j].endpoints[oIdx]; + if (otherEndpoint.anchor.isDynamic && !otherEndpoint.isFloating()) { + _updateOffset( { elId : otherEndpoint.elementId, timestamp : timestamp }); + otherEndpoint.paint({ elementWithPrecedence:id }); + // all the connections for the other endpoint now need to be repainted + for (var k = 0; k < otherEndpoint.connections.length; k++) { + if (otherEndpoint.connections[k] !== l) + otherEndpoint.connections[k].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); + } + } + } + } + } + }, + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }, + + /** + * gets an Endpoint by uuid. + */ + _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }, + + /** + * inits a draggable if it's not already initialised. + */ + _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element) && !jsPlumb.CurrentLibrary.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + } + } + }, + + _newConnection = function(params) { + var connectionFunc = jsPlumb.Defaults.ConnectionType || Connection, + endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint, + parent = jsPlumb.CurrentLibrary.getParent; + + if (params.container) + params["parent"] = params.container; + else { + if (params.sourceEndpoint) + params["parent"] = params.sourceEndpoint.parent; + else if (params.source.constructor == endpointFunc) + params["parent"] = params.source.parent; + else params["parent"] = parent(params.source); + } + + params["_jsPlumb"] = _currentInstance; + var con = new connectionFunc(params); + _eventFireProxy("click", "click", con); + _eventFireProxy("dblclick", "dblclick", con); + return con; + }, + + _eventFireProxy = function(event, proxyEvent, obj) { + obj.bind(event, function(originalObject, originalEvent) { + _currentInstance.fire(proxyEvent, obj, originalEvent); + }); + }, + + _newEndpoint = function(params) { + var endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint; + if (params.container) + params.parent = params.container; + else + params["parent"] = jsPlumb.CurrentLibrary.getParent(params.source); + params["_jsPlumb"] = _currentInstance, + ep = new endpointFunc(params); + _eventFireProxy("click", "endpointClick", ep); + _eventFireProxy("dblclick", "endpointDblClick", ep); + return ep; + }, + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + _operation = function(elId, func, endpointFunc) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + if (endpointFunc) endpointFunc(endpoints[i]); + } + } + }, + /** + * perform an operation on all elements. + */ + _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }, + + /** + * helper to remove an element from the DOM. + */ + _removeElement = function(element, parent) { + if (element != null && element.parentNode != null) { + element.parentNode.removeChild(element); + } + }, + /** + * helper to remove a list of elements from the DOM. + */ + _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }, + /** + * helper method to remove an item from a list. + */ + _removeFromList = function(map, key, value) { + if (key != null) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete (l[i]); + l.splice(i, 1); + return true; + } + } + } + return false; + }, + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }, + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + _setVisible = function(el, state, alsoChangeEndpoints) { + state = state === "block"; + var endpointFunc = null; + if (alsoChangeEndpoints) { + if (state) endpointFunc = function(ep) { + ep.setVisible(true, true, true); + }; + else endpointFunc = function(ep) { + ep.setVisible(false, true, true); + }; + } + var id = _getAttribute(el, "id"); + _operation(id, function(jpc) { + if (state && alsoChangeEndpoints) { + // this test is necessary because this functionality is new, and i wanted to maintain backwards compatibility. + // this block will only set a connection to be visible if the other endpoint in the connection is also visible. + var oidx = jpc.sourceId === id ? 1 : 0; + if (jpc.endpoints[oidx].isVisible()) jpc.setVisible(true); + } + else // the default behaviour for show, and what always happens for hide, is to just set the visibility without getting clever. + jpc.setVisible(state); + }, endpointFunc); + }, + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }, + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + _toggleVisible = function(elId, changeEndpoints) { + var endpointFunc = null; + if (changeEndpoints) { + endpointFunc = function(ep) { + var state = ep.isVisible(); + ep.setVisible(!state); + }; + } + _operation(elId, function(jpc) { + var state = jpc.isVisible(); + jpc.setVisible(!state); + }, endpointFunc); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }, + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return offsets[elId]; + } + if (recalc || offset == null) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + } + return offsets[elId]; + }, + +/** + * gets an id for the given element, creating and setting one if + * necessary. + */ + _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else + id = "jsPlumb_" + _timestamp(); + _setAttribute(ele, "id", id); + } + return id; + }, + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'jsPlumb function failed : ' + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'wrapped function failed : ' + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = "_jsPlumb_connector"; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = "_jsPlumb_endpoint"; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = "_jsPlumb_overlay"; + + this.Anchors = {}; + + this.Connectors = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Endpoints = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Overlays = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + +// ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***************************************** + /* + * Function: bind + * Bind to an event on jsPlumb. + * + * Parameters: + * event - the event to bind. Available events on jsPlumb are: + * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback. + * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback. + * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback. + * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback. + * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback. + * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback. + * + * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event. + */ + + /* + * Function: clearListeners + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + +// *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *********************************************************** + + /* + Function: addEndpoint + + Adds an to a given element or elements. + + Parameters: + + el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + params - Object containing Endpoint constructor arguments. For more information, see . + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some + shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in + this object are anything that 'params' can contain. See . + + Returns: + The newly created , if el referred to a single element. Otherwise, an array of newly created s. + + See Also: + + */ + this.addEndpoint = function(el, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + + // YUI wrapper + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + var _el = _getElementObject(inputs[i]), id = _getId(_el); + p.source = _el; + _updateOffset({ elId : id }); + var e = _newEndpoint(p); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + results.push(e); + } + + return results.length == 1 ? results[0] : results; + }; + + /* + Function: addEndpoints + Adds a list of s to a given element or elements. + + Parameters: + target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See 's constructor documentation. + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created s, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(el, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams); + if (e.constructor == Array) + Array.prototype.push.apply(results, e); + else results.push(e); + } + return results; + }; + + /* + Function: animate + This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating + the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // onComplete repaints, just to make sure everything looks good at the end of the animation. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /* + Function: connect + Establishes a between two elements (or s, which are themselves registered to elements). + + Parameters: + params - Object containing constructor arguments for the Connection. See 's constructor documentation. + referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of + Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection. + + Returns: + The newly created . + */ + this.connect = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + if (_p.target && !_p.target.endpoint) { + var tid = _getId(_p.target), + tep =_targetEndpointDefinitions[tid]; + + var overrideOne = function(singlePropertyName, pluralPropertyName, tepProperty, tep) { + if (tep[tepProperty]) { + if (_p[pluralPropertyName]) _p[pluralPropertyName][1] = tep[tepProperty]; + else if (_p[singlePropertyName]) { + _p[pluralPropertyName] = [ _p[singlePropertyName], tep[tepProperty] ]; + _p[singlePropertyName] = null; + } + else _p[pluralPropertyName] = [ null, tep[tepProperty] ]; + } + }; + + if (tep) { + overrideOne("endpoint", "endpoints", "endpoint", tep); + overrideOne("endpointStyle", "endpointStyles", "paintStyle", tep); + overrideOne("endpointHoverStyle", "endpointHoverStyles", "hoverPaintStyle", tep); + } + } + + // dynamic anchors. backwards compatibility here: from 1.2.6 onwards you don't need to specify "dynamicAnchors". the fact that some anchor consists + // of multiple definitions is enough to tell jsPlumb you want it to be dynamic. + if (_p.dynamicAnchors) { + // these can either be an array of anchor coords, which we will use for both source and target, or an object with {source:[anchors], target:[anchors]}, in which + // case we will use a different set for each element. + var a = _p.dynamicAnchors.constructor == Array; + var sa = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.source)); + var ta = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.target)); + _p.anchors = [sa,ta]; + } + + var jpc = _newConnection(_p); + // add to list of connections (by scope). + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + _currentInstance.fire("jsPlumbConnection", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + // force a paint + _draw(jpc.source); + + return jpc; + }; + + /* + Function: deleteEndpoint + Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too) + + Parameters: + object - either an object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElement(endpoint.canvas, endpoint.parent); + // remove from endpointsbyElement + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + delete endpoint; + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every , and their associated s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc) { + _currentInstance.fire("jsPlumbConnectionDetached", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + }; + + /* + Function: detach + Detaches and then removes a . Takes either (source, target) (the old way, maintained for backwards compatibility), or a params + object with various possible values. + + Parameters: + source - id or element object of the first element in the Connection. + target - id or element object of the second element in the Connection. + params - a JS object containing the same parameters as you pass to jsPlumb.connect. If this is present then neither source nor + target should be present; it should be the only argument to the method. See the docs for 's constructor for information +about the parameters allowed in the params object. + Returns: + true if successful, false if not. + */ + this.detach = function(source, target) { + if (arguments.length == 2) { + var s = _getElementObject(source), sId = _getId(s); + var t = _getElementObject(target), tId = _getId(t); + _operation(sId, function(jpc) { + if ((jpc.sourceId == sId && jpc.targetId == tId) || (jpc.targetId == sId && jpc.sourceId == tId)) { + _removeElements(jpc.connector.getDisplayElements(), jpc.parent); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + // this is the new version of the method, taking a JS object like + // the connect method does. + else if (arguments.length == 1) { + // TODO investigate whether or not this code still works when a user has supplied their own subclass of Connection. i suspect it may not. + if (arguments[0].constructor == Connection) { + arguments[0].endpoints[0].detachFrom(arguments[0].endpoints[1]); + } + else if (arguments[0].connection) { + arguments[0].connection.endpoints[0].detachFrom(arguments[0].connection.endpoints[1]); + } + else { + var _p = jsPlumb.extend( {}, source); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1])); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source); + var targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElements(jpc.connector.getDisplayElements(), jpc.parent); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + } + } + }; + + /* + Function: detachAll + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + + Returns: + void + */ + this.detachAllConnections = function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + }; + + /** + * @deprecated Use detachAllConnections instead. this will be removed in jsPlumb 1.3. + */ + this.detachAll = this.detachAllConnections; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + /** + * @deprecated use detachEveryConnection instead. this will be removed in jsPlumb 1.3. + */ + this.detachEverything = this.detachEveryConnection; + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of you library's draggable method so that jsPlumb can setup the appropriate callbacks. Your underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method, + * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the + * default scope). If multiple scopes are passed in, the return value will be a map of { scope -> [ connection... ] }. + * + * Parameters + * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list + * of connections that are in the given scope. + * options - if the argument is a JS object, you can specify a finer-grained filter: + * + * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes. + * - *source* either a string representing an element id, or a selector. constrains the result to connections having this source. + * - *target* either a string representing an element id, or a selector. constrains the result to connections having this target. + * + */ + this.getConnections = function(options) { + if (!options) { + options = {}; + } else if (options.constructor == String) { + options = { "scope": options }; + } + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') + r.push(input); + else + r = input; + } + return r; + }; + var scope = options.scope || jsPlumb.getDefaultScope(), + scopes = prepareList(scope), + sources = prepareList(options.source), + targets = prepareList(options.target), + filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }, + results = scopes.length > 1 ? {} : [], + _addOne = function(scope, obj) { + if (scopes.length > 1) { + var ss = results[scope]; + if (ss == null) { + ss = []; results[scope] = ss; + } + ss.push(obj); + } else results.push(obj); + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + _addOne(i, c); + } + } + } + return results; + }; + + /* + * Function: getAllConnections + * Gets all connections, as a map of { scope -> [ connection... ] }. + */ + this.getAllConnections = function() { + return connectionsByScope; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /** + * Function:getEndpoints + * Gets the list of Endpoints for a given selector, or element id. + * @param el + * @return + */ + this.getEndpoints = function(el) { + return endpointsByElement[_getId(el)]; + }; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + + this.appendElement = _appendElement; + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + changeEndpoints - whether not to also hide endpoints on the element. by default this is false. + + Returns: + void + */ + this.hide = function(el, changeEndpoints) { + _setVisible(el, "none", changeEndpoints); + }; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + if (!initialized) { + _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run. + + var _bind = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && _mouseEventsEnabled && renderMode == jsPlumb.CANVAS) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + var t = c[i].connector[event](e); + if (t) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i].endpoint[event](e)) return; + } + } + } + }); + }; + _bind("click"); + _bind("dblclick"); + _bind("mousemove"); + _bind("mousedown"); + _bind("mouseup"); + + initialized = true; + _currentInstance.fire("ready"); + } + }; + + this.jsPlumbUIComponent = jsPlumbUIComponent; + this.EventGenerator = EventGenerator; + + /* + * Creates an anchor with the given params. + * + * You do not need to use this method. It is exposed because of the way jsPlumb is + * split into three scripts; this will change in the future. + * + * x - the x location of the anchor as a fraction of the + * total width. y - the y location of the anchor as a fraction of the + * total height. xOrientation - value indicating the general direction a + * connection from the anchor should go in, in the x direction. + * yOrientation - value indicating the general direction a connection + * from the anchor should go in, in the y direction. xOffset - a fixed + * offset that should be applied in the x direction that should be + * applied after the x position has been figured out. optional. defaults + * to 0. yOffset - a fixed offset that should be applied in the y + * direction that should be applied after the y position has been + * figured out. optional. defaults to 0. + * -- OR -- + * + * params - {x:..., y:..., xOrientation etc } + * -- OR FROM 1.2.4 --- + * + * name - the name of some Anchor in the _currentInstance.Anchors array. + * -- OR FROM 1.2.4 --- + * + * coords - a list of coords for the anchor, like you would pass to + * jsPlumb.makeAnchor (eg [0.5,0.5,0,-1] - an anchor in the center of + * some element, oriented towards the top of the screen) + * -- OR FROM 1.2.4 --- + * + * anchor - an existing anchor. just gets passed back. it's handy + * internally to have this functionality. + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed + // in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards + // compatibility if we are given only one value we assume it's a + // call in the old form. + if (arguments.length == 0) return null; + var params = {}; + if (arguments.length == 1) { + var specimen = arguments[0]; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; + // is it the name of an anchor type? + else if (typeof specimen == "string") return _currentInstance.Anchors[arguments[0]](); + // is it an array of coordinates? + else if (specimen.constructor == Array) { + if (specimen[0].constructor == Array || specimen[0].constructor == String) + return new DynamicAnchor(specimen); + else + return jsPlumb.makeAnchor.apply(this, specimen); + } + // last we try the backwards compatibility stuff. + else if (typeof arguments[0] == "object") jsPlumb.extend(params, x); + } else { + params = { x : x, y : y }; + if (arguments.length >= 4) params.orientation = [ arguments[2], arguments[3] ]; + if (arguments.length == 6) params.offsets = [ arguments[4], arguments[5] ]; + } + var a = new Anchor(params); + a.clone = function() { + return new Anchor(params); + }; + return a; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types) { + var r = []; + for ( var i = 0; i < types.length; i++) + if (typeof types[i] == "string") + r.push(_currentInstance.Anchors[types[i]]()); + else if (types[i].constructor == Array) + r.push(jsPlumb.makeAnchor(types[i])); + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /** + * Function: makeTarget + * Makes some DOM element a Connection target, allowing you to drag connections to it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a target. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * scope optional. scope for the drop zone. + * dropOptions optional. same stuff as you would pass to dropOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to false. whether or not to delete + * any Endpoints created by a connection to this target if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + var _targetEndpointDefinitions = {}; + this.makeTarget = function(el, params, referenceParams) { + + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + var jpcl = jsPlumb.CurrentLibrary, + scope = p.scope || _currentInstance.Defaults.Scope, + deleteEndpointsOnDetach = p.deleteEndpointsOnDetach || false, + _doOne = function(_el) { + + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el); + _targetEndpointDefinitions[elid] = p.endpoint; + + var dropOptions = jsPlumb.extend({}, p.dropOptions || {}); + var _drop = function() { + var draggable = _getElementObject(jpcl.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + // restore the original scope if necessary (issue 57) + scope = _getAttribute(draggable, "originalScope"); + + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + // get the connection, to then get its endpoint + var jpc = floatingConnections[id], + source = jpc.endpoints[0], + _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : null, + // make a new Endpoint + newEndpoint = jsPlumb.addEndpoint(_el, _endpoint); + + var c = jsPlumb.connect({ + source:source, + target:newEndpoint, + scope:scope + }); + if (deleteEndpointsOnDetach) + c.endpointToDeleteOnDetach = newEndpoint; + }; + + var dropEvent = jpcl.dragEvents['drop']; + dropOptions["scope"] = dropOptions["scope"] || scope; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop); + + jpcl.initDroppable(_el, dropOptions); + }; + + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements drop targets. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeTargets = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeTarget(els[i], params, referenceParams); + } + }; + + /* + Function: ready + Helper method to bind a function to jsPlumb's ready event. + */ + this.ready = function(fn) { + _currentInstance.bind("ready", fn); + }, + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + for ( var i in ebe) + _currentInstance.deleteEndpoint(ebe[i]); + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + this.deleteEveryEndpoint(); + this.clearListeners(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + + Parameters: + value - whether or not to automatically repaint when the window is resized. + + Returns: void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setDraggableByDefault + * Sets whether or not elements are draggable by default. Default for this is true. + * + * Parameters: + * draggable - value to set + * + * Returns: + * void + */ + this.setDraggableByDefault = function(draggable) { + _draggableByDefault = draggable; + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction + * Sets the function to fire when the window size has changed and a repaint was fired. + * + * Parameters: + * f - Function to execute. + * + * Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: setMouseEventsEnabled + * Sets whether or not mouse events are enabled. Default is true. + * + * Parameters: + * enabled - whether or not mouse events should be enabled. + * + * Returns: + * void + */ + this.setMouseEventsEnabled = function(enabled) { + _mouseEventsEnabled = enabled; + }; + + /* + * Constant for use with the setRenderMode method + */ + this.CANVAS = "canvas"; + + /* + * Constant for use with the setRenderMode method + */ + this.SVG = "svg"; + + this.VML = "vml"; + + /* + * Function: setRenderMode + * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that + * what you asked for is not supported (and that VML is). If you asked for VML but the browser does + * not support it, jsPlumb uses SVG. + * + * Returns: + * the render mode that jsPlumb set, which of course may be different from that requested. + */ + this.setRenderMode = function(mode) { + if (mode) + mode = mode.toLowerCase(); + else + return; + if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); + // now test we actually have the capability to do this. + if (mode === jsPlumb.CANVAS && canvasAvailable) + renderMode = jsPlumb.CANVAS; + else if (mode === jsPlumb.SVG && svgAvailable) + renderMode = jsPlumb.SVG; + else if (vmlAvailable) + renderMode = jsPlumb.VML; + + return renderMode; + }; + + this.getRenderMode = function() { return renderMode; }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * changeEndpoints - whether or not to also change the visible state of the endpoints on the element. this also has a bearing on + * other connections on those endpoints: if their other endpoint is also visible, the connections are made visible. + * + * Returns: + * void + */ + this.show = function(el, changeEndpoints) { + _setVisible(el, "block", changeEndpoints); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId, + makeAnchor:self.makeAnchor, + makeDynamicAnchor:self.makeDynamicAnchor + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * changeEndpoints - whether or not to also toggle the endpoints on the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload + * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element. + * + * Returns: + * void + */ + this.unload = function() { + delete endpointsByElement; + delete endpointsByUUID; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + delete canvasList; + }; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + if (timestamp && timestamp === self.timestamp) { + return lastReturnValue; + } + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + // adjust loc if there is an offsetParent + if (element.canvas && element.canvas.offsetParent) { + var po = element.canvas.offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(element.canvas.offsetParent); + lastReturnValue[0] = lastReturnValue[0] - po.left; + lastReturnValue[1] = lastReturnValue[1] - po.top; + } + + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + * + * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(_getElementObject(refCanvas)); + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + var orientation = null; + var _lastResult = null; + + this.compute = function(params) { + var xy = params.xy, element = params.element; + var result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + + // adjust loc if there is an offsetParent + if (element.canvas && element.canvas.offsetParent) { + var po = element.canvas.offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(element.canvas.offsetParent); + result[0] = result[0] - po.left; + result[1] = result[1] - po.top; + } + + _lastResult = result; + return result; + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { orientation = anchor.getOrientation(); }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchors is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = [], + _convert = function(anchor) { return anchor.constructor == Anchor ? anchor: jsPlumb.makeAnchor(anchor); }; + for (var i = 0; i < anchors.length; i++) _anchors[i] = _convert(anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + this.locked = false; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null, + _curIndex = _anchors.length > 0 ? 0 : -1, + self = this, + + // helper method to calculate the distance between the centers of the two elements. + _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }, + + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + + var pos = _curAnchor.compute(params); + return pos; + }; + + this.getCurrentLocation = function() { + var cl = _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + return cl; + }; + + this.getOrientation = function() { return _curAnchor != null ? _curAnchor.getOrientation() : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * container - optional id or selector instructing jsPlumb where to attach all the elements it creates for this connection. you should read the documentation for a full discussion of this. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * overlays - Optional array of Overlay definitions to appear on this Connection. + * drawEndpoints - if false, instructs jsPlumb to not draw the endpoints for this Connection. Be careful with this: it only really works when you tell jsPlumb to attach elements to the document body. Read the documentation for a full discussion of this. + */ + var Connection = function(params) { + + jsPlumbUIComponent.apply(this, arguments); + // ************** get the source and target and register the connection. ******************* + var self = this; + var visible = true; + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none"; + }; + var id = new String('_jsplumb_c_' + (new Date()).getTime()); + this.getId = function() { return id; }; + this.parent = params.parent; + /** + Property: source + The source element for this Connection. + */ + this.source = _getElementObject(params.source); + /** + Property:target + The target element for this Connection. + */ + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. + if (params.sourceEndpoint) this.source = params.sourceEndpoint.getElement(); + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + /* + * Property: sourceId + * Id of the source element in the connection. + */ + this.sourceId = _getAttribute(this.source, "id"); + /* + * Property: targetId + * Id of the target element in the connection. + */ + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + + /** + * implementation of abstract method in EventGenerator + * @return list of attached elements. in our case, a list of Endpoints. + */ + this.getAttachedElements = function() { + return self.endpoints; + }; + + /** + * implementation of abstract method in EventGenerator + */ + this.savePosition = function() { + srcWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.source)); + targetWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.target)); + }; + + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams) { + if (anchorParams) + return jsPlumb.makeAnchor(anchorParams); + }; + var prepareEndpoint = function(existing, index, params, element, connectorPaintStyle, connectorHoverPaintStyle) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] + || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint; + + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified. + if (es.fillStyle == null && connectorPaintStyle != null) + es.fillStyle = connectorPaintStyle.strokeStyle; + + // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does: + //* + if (es.outlineColor == null && connectorPaintStyle != null) + es.outlineColor = connectorPaintStyle.outlineColor; + if (es.outlineWidth == null && connectorPaintStyle != null) + es.outlineWidth = connectorPaintStyle.outlineWidth; + //*/ + + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure? + if (connectorHoverPaintStyle != null) { + if (ehs == null) ehs = {}; + if (ehs.fillStyle == null) { + ehs.fillStyle = connectorHoverPaintStyle.strokeStyle; + } + } + var a = params.anchors ? params.anchors[index] : _makeAnchor(_currentInstance.Defaults.Anchors[index]) || _makeAnchor(jsPlumb.Defaults.Anchors[index]) || _makeAnchor(_currentInstance.Defaults.Anchor) || _makeAnchor(jsPlumb.Defaults.Anchor), + u = params.uuids ? params.uuids[index] : null, + e = _newEndpoint({ + paintStyle : es, + hoverPaintStyle:ehs, + endpoint : ep, + connections : [ self ], + uuid : u, + anchor : a, + source : element, + container:params.container + }); + self.endpoints[index] = e; + + if (params.drawEndpoints === false) e.setVisible(false, true, true); + + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source, params.paintStyle, params.hoverPaintStyle); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target, params.paintStyle, params.hoverPaintStyle); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + /* + * Function: setConnector + * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method - the same + * thing that you would set as the 'connector' property on a jsPlumb.connect call. + * + * Parameters: + * connector - Connector definition + */ + this.setConnector = function(connector, doNotRepaint) { + if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent); + var connectorArgs = { _jsPlumb:self._jsPlumb, parent:params.parent, cssClass:params.cssClass, container:params.container }; + if (connector.constructor == String) + this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand. + else if (connector.constructor == Array) + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs)); + this.canvas = this.connector.canvas; + // add mouse events + this.connector.bind("click", function(con, e) { + _mouseWasDown = false; + self.fire("click", self, e); + }); + this.connector.bind("dblclick", function(con, e) { _mouseWasDown = false;self.fire("dblclick", self, e); }); + this.connector.bind("mouseenter", function(con, e) { + if (!self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(true); + } + self.fire("mouseenter", self, e); + } + }); + this.connector.bind("mouseexit", function(con, e) { + if (self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(false); + } + self.fire("mouseexit", self, e); + } + }); + this.connector.bind("mousedown", function(con, e) { + _mouseDown = true; + _mouseDownAt = jsPlumb.CurrentLibrary.getPageXY(e); + self.savePosition(); + }); + this.connector.bind("mouseup", function(con, e) { + _mouseDown = false; + if (self.connector == _connectionBeingDragged) _connectionBeingDragged = null; + }); + + if (!doNotRepaint) self.repaint(); + }; + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc) + */ + + self.setConnector(this.endpoints[0].connector || + this.endpoints[1].connector || + params.connector || + _currentInstance.Defaults.Connector || + jsPlumb.Defaults.Connector, true); + + this.setPaintStyle(this.endpoints[0].connectorStyle || + this.endpoints[1].connectorStyle || + params.paintStyle || + _currentInstance.Defaults.PaintStyle || + jsPlumb.Defaults.PaintStyle, true); + + this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || + this.endpoints[1].connectorHoverStyle || + params.hoverPaintStyle || + _currentInstance.Defaults.HoverPaintStyle || + jsPlumb.Defaults.HoverPaintStyle, true); + + this.paintStyleInUse = this.paintStyle; + + /* + * Property: overlays + * List of Overlays for this Connection. + */ + this.overlays = []; + var _overlays = params.overlays || _currentInstance.Defaults.Overlays; + if (_overlays) { + for (var i = 0; i < _overlays.length; i++) { + var o = _overlays[i], _newOverlay = null; + if (o.constructor == Array) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0]; + var p = jsPlumb.CurrentLibrary.extend({connection:self, _jsPlumb:_currentInstance}, o[1]); // make a copy of the object so as not to mess up anyone else's reference... + if (o.length == 3) jsPlumb.CurrentLibrary.extend(p, o[2]); + _newOverlay = new jsPlumb.Overlays[renderMode][type](p); + if (p.events) { + for (var evt in p.events) { + _newOverlay.bind(evt, p.events[evt]); + } + } + } else if (o.constructor == String) { + _newOverlay = new jsPlumb.Overlays[renderMode][o]({connection:self, _jsPlumb:_currentInstance}); + } else { + _newOverlay = o; + } + + + + this.overlays.push(_newOverlay); + } + } + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Connection. + * + * Parameters: + * event - the event to bind. Available events on a Connection are: + * - *click* : notification that a Connection was clicked. + * - *dblclick* : notification that a Connection was double clicked. + * - *mouseenter* : notification that the mouse is over a Connection. + * - *mouseexit* : notification that the mouse exited a Connection. + * + * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Connection's paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Connection will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Connection. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + // overlay finder helper method + var _getOverlayIndex = function(id) { + var idx = -1; + for (var i = 0; i < self.overlays.length; i++) { + if (id === self.overlays[i].id) { + idx = i; + break; + } + } + return idx; + }; + + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + this.addOverlay = function(overlay) { self.overlays.push(overlay); }; + + /* + * Function: getOverlay + * Gets an overlay, by ID. Note: by ID. You would pass an 'id' parameter + * in to the Overlay's constructor arguments, and then use that to retrieve + * it via this method. + */ + this.getOverlay = function(id) { + var idx = _getOverlayIndex(id); + return idx >= 0 ? self.overlays[idx] : null; + }; + + /* + * Function: hideOverlay + * Hides the overlay specified by the given id. + */ + this.hideOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.hide(); + }; + + /* + * Function: showOverlay + * Shows the overlay specified by the given id. + */ + this.showOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.show(); + }; + + /** + * Function: removeAllOverlays + * Removes all overlays from the Connection, and then repaints. + */ + this.removeAllOverlays = function() { + self.overlays.splice(0, self.overlays.length); + self.repaint(); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + this.removeOverlay = function(overlayId) { + var idx = _getOverlayIndex(overlayId); + if (idx != -1) self.overlays.splice(idx, 1); + }; + + /** + * Function:removeOverlays + * Removes a set of overlays by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + this.removeOverlays = function() { + for (var i = 0; i < arguments.length; i++) + self.removeOverlay(arguments[i]); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + this.labelStyle = params.labelStyle || _currentInstance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + if (this.label) { + this.overlays.push(new jsPlumb.Overlays[renderMode].Label( { + cssClass:params.cssClass, + labelStyle : this.labelStyle, + label : this.label, + connection:self, + _jsPlumb:_currentInstance + })); + } + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String or a Function that returns a String. + */ + this.setLabel = function(l) { + self.label = l; + _currentInstance.repaint(self.source); + }; + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var otherOffset = offsets[this.targetId]; + var otherWH = sizes[this.targetId]; + var anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1] + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0] + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc }); + + /* + * Paints the Connection. Not exposed for public usage. + * + * Parameters: + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp, + // if the moving object is not the source we must transpose the two references. + swap = false, + tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId, + tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + + _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }); + _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var sAnchorP = this.endpoints[sIdx].anchor.getCurrentLocation(), + tAnchorP = this.endpoints[tIdx].anchor.getCurrentLocation(); + + /* paint overlays*/ + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible()) { + var s = o.computeMaxSize(self.connector); + if (s > maxSize) + maxSize = s; + } + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, self.paintStyleInUse.lineWidth, maxSize); + + self.connector.paint(dim, self.paintStyleInUse); + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) + self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim); + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function() { + this.paint({ elId : this.sourceId, recalc : true }); + }; + + _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide + // whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + + // just to make sure the UI gets initialised fully on all browsers. + self.repaint(); + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1 + * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't + * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you + * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or + * 'target' Endpoints for Connections. + * + * + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * container - optional id or selector instructing jsPlumb where to attach the element it creates for this endpoint. you should read the documentation for a full discussion of this. + * connections - optional list of Connections to configure the Endpoint with. + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + params = params || {}; + var self = this; +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Endpoint. + * + * Parameters: + * event - the event to bind. Available events on an Endpoint are: + * - *click* : notification that a Endpoint was clicked. + * - *dblclick* : notification that a Endpoint was double clicked. + * - *mouseenter* : notification that the mouse is over a Endpoint. + * - *mouseexit* : notification that the mouse exited a Endpoint. + * + * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Endpoint's paint style and then repaints the Endpoint. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Endpoint will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Endpoint. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + var visible = true; + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + var id = new String('_jsplumb_e_' + (new Date()).getTime()); + this.getId = function() { return id; }; + if (params.dynamicAnchors) + self.anchor = new DynamicAnchor(jsPlumb.makeAnchors(params.dynamicAnchors)); + else + self.anchor = params.anchor ? jsPlumb.makeAnchor(params.anchor) : params.anchors ? jsPlumb.makeAnchor(params.anchors) : jsPlumb.makeAnchor("TopCenter"); + var _endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot", + endpointArgs = { _jsPlumb:self._jsPlumb, parent:params.parent, container:params.container }; + if (_endpoint.constructor == String) + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint](endpointArgs); + else if (_endpoint.constructor == Array) { + endpointArgs = jsPlumb.extend(_endpoint[1], endpointArgs); + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint[0]](endpointArgs); + } + else + _endpoint = _endpoint.clone(); + + // assign a clone function using our derived endpointArgs. this is used when a drag starts: the endpoint that was dragged is cloned, + // and the clone is left in its place while the original one goes off on a magical journey. + this.clone = function() { + var o = new Object(); + _endpoint.constructor.apply(o, [endpointArgs]); + return o; + }; + + self.endpoint = _endpoint; + self.type = self.endpoint.type; + + // TODO this event listener registration code is identical to what Connection does: it should be refactored. + this.endpoint.bind("click", function(e) { self.fire("click", self, e); }); + this.endpoint.bind("dblclick", function(e) { self.fire("dblclick", self, e); }); + this.endpoint.bind("mouseenter", function(con, e) { + if (!self.isHover()) { + self.setHover(true); + self.fire("mouseenter", self, e); + } + }); + this.endpoint.bind("mouseexit", function(con, e) { + if (self.isHover()) { + self.setHover(false); + self.fire("mouseexit", self, e); + } + }); + // TODO this event listener registration code above is identical to what Connection does: it should be refactored. + + this.setPaintStyle(params.paintStyle || + params.style || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyle, true); + this.setHoverPaintStyle(params.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyle, true); + this.paintStyleInUse = this.paintStyle; + this.connectorStyle = params.connectorStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.parent = params.parent; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + var _element = params.source, + _uuid = params.uuid, + floatingEndpoint = null, + inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + this.element = _element; + var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + /* + * Property: canvas + * The Endpoint's Canvas. + */ + this.canvas = this.endpoint.canvas; + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + this.connections = params.connections || []; + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + var _reattach = params.reattach || false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + + // this avoids a circular loop + if (!ignoreTarget) { + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + t.detach(connection, true); + // check connection to see if we want to delete the other endpoint. + // if the user uses makeTarget to make some element a target for connections, + // it is possible that they will have set 'endpointToDeleteOnDetach': when + // you make a connection to an element that acts as a target (note: NOT an + // Endpoint; just some div as a target), Endpoints are created for that + // connection. so if you then delete that Connection, it is feasible you + // will want these auto-generated endpoints to be removed. + if (connection.endpointToDeleteOnDetach && connection.endpointToDeleteOnDetach.connections.length == 0) + jsPlumb.deleteEndpoint(connection.endpointToDeleteOnDetach); + } + _removeElements(connection.connector.getDisplayElements(), connection.parent); + _removeFromList(connectionsByScope, connection.scope, connection); + if(!ignoreTarget) fireDetachEvent(connection); + } + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + */ + this.detachAll = function() { + while (self.connections.length > 0) { + self.detach(self.connections[0]); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + */ + this.detachFrom = function(targetEndpoint) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + c[i].setHover(false); + self.detach(c[i]); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + return _newEndpoint( { anchor : self.anchor, source : _element, paintStyle : this.paintStyle, endpoint : _endpoint }); + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + return (self.connections.length < _maxConnections) || _maxConnections == -1 ? null : self.connections[0]; + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + /* + * Function: setStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to + * setStyle and deprecate it if so. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + * + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + // a helper function that tries to find a connection to the given element, and returns it if so. if elementWithPrecedence is null, + // or no connection to it is found, we return the first connection in our list. + var findConnectionToUseForDynamicAnchor = function(elementWithPrecedence) { + var idx = 0; + if (elementWithPrecedence != null) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].sourceId == elementWithPrecedence || self.connections[i].targetId == elementWithPrecedence) { + idx = i; + break; + } + } + } + + return self.connections[idx]; + }; + + /* + * Function: paint + * Paints the Endpoint, recalculating offset and anchor positions if necessary. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + + params = params || {}; + var timestamp = params.timestamp; + if (!timestamp || self.timestamp !== timestamp) { + var ap = params.anchorPoint, canvas = params.canvas, connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + var xy = params.offset || offsets[_elementId]; + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (self.anchor.isDynamic) { + if (self.connections.length > 0) { + //var c = self.connections[0]; + var c = findConnectionToUseForDynamicAnchor(params.elementWithPrecedence); + var oIdx = c.endpoints[0] == self ? 1 : 0; + var oId = oIdx == 0 ? c.sourceId : c.targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + } + ap = self.anchor.compute(anchorParams); + } + + var d = _endpoint.compute(ap, self.anchor.getOrientation(), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + _endpoint.paint(d, self.paintStyleInUse, self.anchor); + + self.timestamp = timestamp; + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + var start = function() { + jpc = self.connectorSelector(); + if (self.isFull() && !dragAllowedWhenFull) return false; + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + n.style.position = "absolute"; + var nE = _getElementObject(n); + _appendElement(n, self.parent); + // create and assign an id, and initialize the offset. + var id = _getId(nE); + + // set the offset of this div to be where 'inPlaceCopy' is, to start with. + var ipcoel = _getElementObject(inPlaceCopy.canvas), + ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel), + po = inPlaceCopy.canvas.offsetParent != null ? + inPlaceCopy.canvas.offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(inPlaceCopy.canvas.offsetParent) + : { left:0, top: 0}; + jsPlumb.CurrentLibrary.setOffset(n, {left:ipco.left - po.left, top:ipco.top-po.top}); + + _updateOffset( { elId : id }); + + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor( { reference : self.anchor, referenceCanvas : self.canvas }); + floatingEndpoint = _newEndpoint({ paintStyle : self.paintStyle, endpoint : _endpoint, anchor : floatingAnchor, source : nE }); + + if (jpc == null) { + self.anchor.locked = true; + // create a connection. one end is this endpoint, the + // other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : _getElementObject(_element), + target : _getElementObject(n), + anchors : [ self.anchor, floatingAnchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays + }); + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.connector.setHover(false); + } else { + existingJpc = true; + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.connector.setHover(false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas)); + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; // are we the source or the target? + + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas); + var dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // now we want to get this endpoint's DROP scope, and set it for now: we can only be dropped on drop zones + // that have our drop scope (issue 57). + var dropScope = jsPlumb.CurrentLibrary.getDropScope(c);//jpc.endpoints[anchorIdx == 0 ? 1 : 0].getDropScope(); + jsPlumb.CurrentLibrary.setDragScope(c, dropScope); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it and register connection on it. + floatingConnections[id] = jpc; + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var jpcl = jsPlumb.CurrentLibrary, + dragOptions = params.dragOptions || {}, + defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions), + startEvent = jpcl.dragEvents['start'], + stopEvent = jpcl.dragEvents['stop'], + dragEvent = jpcl.dragEvents['drag']; + + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], + function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + jsPlumb.CurrentLibrary.setOffset(n, _ui); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements( [ n, floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (_reattach) { + + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } else { + jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc); // the main endpoint will inform the floating endpoint + // to disconnect, and also post the detached event. + } + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElements(jpc.connector.getDisplayElements(), self.parent); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint(); + jpc.setHover(false); + jpc.repaint(); + jpc = null; + delete inPlaceCopy; + delete endpointsByElement[floatingEndpoint.elementId]; + floatingEndpoint = null; + delete floatingEndpoint; + + _currentInstance.currentlyDragging = false; + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas) { + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + var drop = function() { + var draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)); + var id = _getAttribute(draggable, "dragId"); + var elId = _getAttribute(draggable, "elId"); + + // restore the original scope if necessary (issue 57) + var scope = _getAttribute(draggable, "originalScope"); + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + var jpc = floatingConnections[id]; + + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // todo test that the target is not full. + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + if (!jpc.suspendedEndpoint) { + _addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + _currentInstance.fire("jsPlumbConnectionDetached", { + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }); + } + + jsPlumb.repaint(elId); + + _currentInstance.fire("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], + targetEndpoint : jpc.endpoints[1], + connection:jpc + }); + } + + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute( _getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + } + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute(_getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + } + }); + + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas)); + + return self; + }; + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + j.init(); + return j; + }; + + var _curryAnchor = function(x,y,ox,oy) { + return function() { + return jsPlumb.makeAnchor(x,y,ox,oy); + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1); + + jsPlumb.Defaults.DynamicAnchors = function() { + return jsPlumb.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"]); + }; + jsPlumb.Anchors["AutoDefault"] = function() { return jsPlumb.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors()); }; + + +})(); diff --git a/build/1.3.6/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js b/archive/1.3.3/jsPlumb-1.3.3-tests.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js rename to archive/1.3.3/jsPlumb-1.3.3-tests.js diff --git a/archive/1.3.3/jsPlumb-defaults-1.3.3-RC1.js b/archive/1.3.3/jsPlumb-defaults-1.3.3-RC1.js new file mode 100644 index 000000000..107372d8e --- /dev/null +++ b/archive/1.3.3/jsPlumb-defaults-1.3.3-RC1.js @@ -0,0 +1,955 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the default Connectors, Endpoint and Overlay definitions. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +(function() { + + /** + * + * Helper class to consume unused mouse events by components that are DOM elements and + * are used by all of the different rendering modes. + * + */ + jsPlumb.DOMElementComponent = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + // when render mode is canvas, these functions may be called by the canvas mouse handler. + // this component is safe to pipe this stuff to /dev/null. + this.mousemove = + this.dblclick = + this.click = + this.mousedown = + this.mouseup = function(e) { }; + }; + + /** + * Class: Connectors.Straight + * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters. + */ + jsPlumb.Connectors.Straight = function() { + this.type = "Straight"; + var self = this; + var currentPoints = null; + var _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + _dx = _tx - _sx, _dy = (_ty - _sy); + _m = _dy / _dx, _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + + return currentPoints; + }; + + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + var xp = _sx + (location * _dx); + var yp = (_m == Infinity || _m == -Infinity) ? _sy + (location * (_ty - _sy)) : (_m * xp) + _b; + return {x:xp, y:yp}; + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { return _m; }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + var orientation = distance > 0 ? 1 : -1; + var y = Math.abs(distance * Math.sin(_theta)); + if (_sy > _ty) y = y * -1; + var x = Math.abs(distance * Math.cos(_theta)); + if (_sx > _tx) x = x * -1; + return {x:p.x + (orientation * x), y:p.y + (orientation * y)}; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = self.gradientAtPoint(p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + }; + }; + + + /** + * Class:Connectors.Bezier + * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's + * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below. + */ + /** + * Function:Constructor + * + * Parameters: + * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line. + * Optional; defaults to 150. + * + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + params = params || {}; + this.majorAnchor = params.curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + this.type = "Bezier"; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h; + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) + { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx); var minx2 = Math.min(_CP[0], _CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(_sx,_tx); var maxx2 = Math.max(_CP[0], _CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty); var miny2 = Math.min(_CP[1], _CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(_sy,_ty); var maxy2 = Math.max(_CP[1], _CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + return jsBezier.perpendicularToCurveAt(_makeCurve(), location, length, distance); + }; + + }; + + + /** + * Class: Connectors.Flowchart + * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments. + */ + /** + * Function: Constructor + * + * Parameters: + * stub - minimum length for the stub at each end of the connector. defaults to 30 pixels. + */ + jsPlumb.Connectors.Flowchart = function(params) { + this.type = "Flowchart"; + params = params || {}; + var self = this, + minStubLength = params.stub || params.minStubLength /* bwds compat. */ || 30, + segments = [], + segmentGradients = [], + segmentProportions = [], + segmentLengths = [], + segmentProportionalLengths = [], + points = [], + swapX, + swapY, + /** + * recalculates the gradients of each segment, and the points at which the segments begin, proportional to the total length travelled + * by all the segments that constitute the connector. + */ + updateSegmentGradientsAndProportions = function(startX, startY, endX, endY) { + var total = 0; + for (var i = 0; i < segments.length; i++) { + var sx = i == 0 ? startX : segments[i][2], + sy = i == 0 ? startY : segments[i][3], + ex = segments[i][0], + ey = segments[i][1]; + + segmentGradients[i] = sx == ex ? Infinity : 0; + segmentLengths[i] = Math.abs(sx == ex ? ey - sy : ex - sx); + total += segmentLengths[i]; + } + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segmentLengths[i] / total; + segmentProportions[i] = [curLoc, (curLoc += (segmentLengths[i] / total)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0]; + var ly = segments.length == 0 ? sy : segments[segments.length - 1][1]; + segments.push([x, y, lx, ly]); + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment + * that contains the point which is 'location' distance along the entire path, where + * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths + * are made up of a list of segments, each of which contributes some fraction to + * the total length. + */ + findSegmentForLocation = function(location) { + var idx = segmentProportions.length - 1, inSegmentProportion = 0; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + segments = []; + segmentGradients = []; + segmentProportionalLengths = []; + segmentLengths = []; + segmentProportionals = []; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + offx = (lw / 2) + (minStubLength * 2), + offy = (lw / 2) + (minStubLength * 2), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(), + to = targetAnchor.orientation || targetAnchor.getOrientation(), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx, + h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy; + if (w < minWidth) { + offx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + offy += (minWidth - h) / 2; + h = minWidth; + } + var sx = swapX ? w-offx : offx, + sy = swapY ? h-offy : offy, + tx = swapX ? offx : w-offx , + ty = swapY ? offy : h-offy, + startStubX = sx + (so[0] * minStubLength), + startStubY = sy + (so[1] * minStubLength), + endStubX = tx + (to[0] * minStubLength), + endStubY = ty + (to[1] * minStubLength), + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2); + + x -= offx; y -= offy; + points = [x, y, w, h, sx, sy, tx, ty], extraPoints = []; + + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + if (so[0] == 0) { + var startStubIsBeforeEndStub = startStubY < endStubY; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } + } + else { + var startStubIsBeforeEndStub = startStubX < endStubX; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } + } + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentGradientsAndProportions(sx, sy, tx, ty); + + return points; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location) { + return self.pointAlongPathFrom(location, 0); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location) { + return segmentGradients[findSegmentForLocation(location)["index"]]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance) { + var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segmentLengths[s.index], m = segmentGradients[s.index]; + var e = { + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + return e; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = segmentGradients[p.segmentInfo.index]; + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + + }; + + }; + + // ********************************* END OF CONNECTOR TYPES ******************************************************************* + + // ********************************* ENDPOINT TYPES ******************************************************************* + + /** + * Class: Endpoints.Dot + * A round endpoint, with default radius 10 pixels. + */ + + /** + * Function: Constructor + * + * Parameters: + * + * radius - radius of the endpoint. defaults to 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + this.type = "Dot"; + var self = this; + params = params || {}; + this.radius = params.radius || 10; + this.defaultOffset = 0.5 * this.radius; + this.defaultInnerRadius = this.radius / 3; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var r = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - r; + var y = anchorPoint[1] - r; + return [ x, y, r * 2, r * 2, r ]; + }; + }; + + /** + * Class: Endpoints.Rectangle + * A Rectangular Endpoint, with default size 20x20. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the endpoint. defaults to 20 pixels. + * height - height of the endpoint. defaults to 20 pixels. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + this.type = "Rectangle"; + var self = this; + params = params || {}; + this.width = params.width || 20; + this.height = params.height || 20; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + return [ x, y, width, height]; + }; + }; + + /** + * Class: Endpoints.Image + * Draws an image as the Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * src - location of the image to use. + */ + jsPlumb.Endpoints.Image = function(params) { + + this.type = "Image"; + jsPlumb.DOMElementComponent.apply(this, arguments); + + var self = this, initialized = false; + this.img = new Image(); + self.ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.src || params.url; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + self.anchorPoint = anchorPoint; + if (self.ready) return [anchorPoint[0] - self.img.width / 2, anchorPoint[1] - self.img.height/ 2, self.img.width, self.img.height]; + else return [0,0,0,0]; + }; + + self.canvas = document.createElement("img"), initialized = false; + self.canvas.style["margin"] = 0; + self.canvas.style["padding"] = 0; + self.canvas.style["outline"] = 0; + self.canvas.style["position"] = "absolute"; + self.canvas.className = jsPlumb.endpointClass; + jsPlumb.appendElement(self.canvas, params.parent); + self.attachListeners(self.canvas, self); + + var actuallyPaint = function(d, style, anchor) { + if (!initialized) { + self.canvas.setAttribute("src", self.img.src); + initialized = true; + } + var width = self.img.width, + height = self.img.height, + x = self.anchorPoint[0] - (width/2), + y = self.anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(self.canvas, x, y, width, height); + }; + + this.paint = function(d, style, anchor) { + if (self.ready) { + actuallyPaint(d, style, anchor); + } + else { + window.setTimeout(function() { + self.paint(d, style, anchor); + }, 200); + } + }; + }; + + /** + * Class: Endpoints.Blank + * An Endpoint that paints nothing on the screen, and cannot be interacted with using the mouse. There are no constructor parameters for this Endpoint. + */ + jsPlumb.Endpoints.Blank = function(params) { + var self = this; + this.type = "Blank"; + jsPlumb.DOMElementComponent.apply(this, arguments); + this.compute = function() { + return [0,0,10,0]; + }; + + self.canvas = document.createElement("div"); + self.canvas.style.display = "block"; + self.canvas.style.width = "1px"; + self.canvas.style.height = "1px"; + self.canvas.style.background = "transparent"; + self.canvas.style.position = "absolute"; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function() { }; + }; + + /** + * Class: Endpoints.Triangle + * A triangular Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the triangle's base. defaults to 55 pixels. + * height - height of the triangle from base to apex. defaults to 55 pixels. + */ + jsPlumb.Endpoints.Triangle = function(params) { + this.type = "Triangle"; + params = params || { }; + params.width = params.width || 55; + param.height = params.height || 55; + this.width = params.width; + this.height = params.height; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + return [ x, y, width, height ]; + }; + }; +// ********************************* END OF ENDPOINT TYPES ******************************************************************* + + +// ********************************* OVERLAY DEFINITIONS *********************************************************************** + + var AbstractOverlay = function() { + var visible = true, self = this; + this.setVisible = function(val) { + visible = val; + self.connection.repaint(); + }; + this.isVisible = function() { return visible; }; + this.hide = function() { self.setVisible(false); }; + this.show = function() { self.setVisible(true); }; + }; + + + /** + * Class: Overlays.Arrow + * + * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length + * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction + * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line + * across the tail. + */ + /** + * Function: Constructor + * + * Parameters: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + this.type = "Arrow"; + AbstractOverlay.apply(this); + params = params || {}; + var self = this; + + this.length = params.length || 20; + this.width = params.width || 20; + this.id = params.id; + this.connection = params.connection; + var direction = (params.direction || 1) < 0 ? -1 : 1; + var paintStyle = params.paintStyle || { lineWidth:1 }; + this.loc = params.location == null ? 0.5 : params.location; + // how far along the arrow the lines folding back in come to. default is 62.3%. + var foldback = params.foldback || 0.623; + var _getFoldBackPoint = function(connector, loc) { + if (foldback == 0.5) return connector.pointOnPath(loc); + else { + var adj = 0.5 - foldback; // we calculate relative to the center + return connector.pointAlongPathFrom(loc, direction * self.length * adj); + } + }; + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + // this is the arrow head position + var hxy = connector.pointAlongPathFrom(self.loc, direction * (self.length / 2)); + // this is the center of the tail + var txy = connector.pointAlongPathFrom(self.loc, -1 * direction * (self.length / 2)), tx = txy.x, ty = txy.y; + // this is the tail vector + var tail = connector.perpendicularToPathAt(self.loc, self.width, -1 * direction * (self.length / 2)); + // this is the point the tail goes in to + var cxy = _getFoldBackPoint(connector, self.loc); + + // if loc = 1, then hxy should be flush with the element, or if direction == -1, the tail midpoint. + if (self.loc == 1) { + var lxy = connector.pointOnPath(self.loc); + // TODO determine why the 1.2.6 released version does not + // use 'direction' in the two equations below, yet both + // that and 1.3.0 still paint the arrows correctly. + var dx = (lxy.x - hxy.x) * direction, dy = (lxy.y - hxy.y) * direction; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + // if loc = 0, then tail midpoint should be flush with the element, or, if direction == -1, hxy should be. + if (self.loc == 0) { + var lxy = connector.pointOnPath(self.loc); + var tailMid = foldback > 1 ? cxy : { + x:tail[0].x + ((tail[1].x - tail[0].x) / 2), + y:tail[0].y + ((tail[1].y - tail[0].y) / 2) + }; + var dx = (lxy.x - tailMid.x) * direction, dy = (lxy.y - tailMid.y) * direction; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x); + var maxx = Math.max(hxy.x, tail[0].x, tail[1].x); + var miny = Math.min(hxy.y, tail[0].y, tail[1].y); + var maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + var d = { hxy:hxy, tail:tail, cxy:cxy }, + strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle, + fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle, + lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth; + + self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions); + + return [ minx, maxx, miny, maxy]; + }; + }; + + /** + * Class: Overlays.PlainArrow + * + * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "PlainArrow"; + }; + + /** + * Class: Overlays.Diamond + * + * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40; + var p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "Diamond"; + }; + + + + /** + * Class: Overlays.Label + * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb + * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter + * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it + * puts on the Label's 'style' attribute, so the end result is the same. + */ + /** + * Function: Constructor + * + * Parameters: + * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes + * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle. + * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * labelStyle - (deprecated) js object containing style instructions for the label. defaults to jsPlumb.Defaults.LabelStyle. + * borderWidth - (deprecated) width of a border to paint. defaults to zero. + * borderStyle - (deprecated) strokeStyle to use when painting the border, if necessary. + * + */ + jsPlumb.Overlays.Label = function(params) { + this.type = "Label"; + jsPlumb.DOMElementComponent.apply(this, arguments); + AbstractOverlay.apply(this); + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.labelStyle.font = this.labelStyle.font || "12px sans-serif"; + this.label = params.label || "banana"; + this.connection = params.connection; + this.id = params.id; + var self = this; + var labelWidth = null, labelHeight = null, labelText = null, labelPadding = null; + this.location = params.location || 0.5; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var initialised = false, + labelText = null, + div = document.createElement("div"); + div.style["position"] = "absolute"; + div.style["font"] = self.labelStyle.font; + div.style["color"] = self.labelStyle.color || "black"; + if (self.labelStyle.fillStyle) div.style["background"] = self.labelStyle.fillStyle;//_convertStyle(self.labelStyle.fillStyle, true); + if (self.labelStyle.borderWidth > 0) { + var dStyle = self.labelStyle.borderStyle ? self.labelStyle.borderStyle/*_convertStyle(self.labelStyle.borderStyle, true)*/ : "black"; + div.style["border"] = self.labelStyle.borderWidth + "px solid " + dStyle; + } + if (self.labelStyle.padding) div.style["padding"] = self.labelStyle.padding; + + var clazz = params["_jsPlumb"].overlayClass + " " + + (self.labelStyle.cssClass ? self.labelStyle.cssClass : + params.cssClass ? params.cssClass : ""); + + div.className = clazz; + + jsPlumb.appendElement(div, params.connection.parent); + jsPlumb.getId(div); + self.attachListeners(div, self); + + //override setVisible + var osv = self.setVisible; + self.setVisible = function(state) { + osv(state); // call superclass + div.style.display = state ? "block" : "none"; + }; + + this.paint = function(connector, d, connectorDimensions) { + if (!initialised) { + connector.appendDisplayElement(div); + self.attachListeners(div, connector); + initialised = true; + } + div.style.left = (connectorDimensions[0] + d.minx) + "px"; + div.style.top = (connectorDimensions[1] + d.miny) + "px"; + }; + + this.getTextDimensions = function(connector) { + labelText = typeof self.label == 'function' ? self.label(self) : self.label; + div.innerHTML = labelText.replace(/\r\n/g, "
"); + var de = jsPlumb.CurrentLibrary.getElementObject(div), + s = jsPlumb.CurrentLibrary.getSize(de); + return {width:s[0], height:s[1]}; + }; + + this.computeMaxSize = function(connector) { + var td = self.getTextDimensions(connector); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + var td = self.getTextDimensions(connector); + if (td.width != null) { + var cxy = connector.pointOnPath(self.location); + + var minx = cxy.x - (td.width / 2); + var miny = cxy.y - (td.height / 2); + + self.paint(connector, { + minx:minx, + miny:miny, + td:td, + cxy:cxy + }, connectorDimensions); + + return [minx, minx+td.width, miny, miny+td.height]; + } + else return [0,0,0,0]; + }; + }; + + // ********************************* END OF OVERLAY DEFINITIONS *********************************************************************** + + // ********************************* OVERLAY CANVAS RENDERERS*********************************************************************** + + // ********************************* END OF OVERLAY CANVAS RENDERERS *********************************************************************** +})(); \ No newline at end of file diff --git a/archive/1.3.3/jsPlumb-renderers-canvas-1.3.3-RC1.js b/archive/1.3.3/jsPlumb-renderers-canvas-1.3.3-RC1.js new file mode 100644 index 000000000..f9e45bd00 --- /dev/null +++ b/archive/1.3.3/jsPlumb-renderers-canvas-1.3.3-RC1.js @@ -0,0 +1,459 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the HTML5 canvas renderers. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +;(function() { + +// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS ******************************************************************* + + // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. + var _connectionBeingDragged = null, + _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }, + _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }, + _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }, + _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, + _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }, + _setOffset = function(el, o) { jsPlumb.CurrentLibrary.setOffset(el, o); }; + + /* + * Class:CanvasMouseAdapter + * Provides support for mouse events on canvases. + */ + var CanvasMouseAdapter = function() { + var self = this; + self.overlayPlacements = []; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + jsPlumb.EventGenerator.apply(this, arguments); + /** + * returns whether or not the given event is ojver a painted area of the canvas. + */ + this._over = function(e) { + var o = _getOffset(_getElementObject(self.canvas)), + pageXY = _pageXY(e), + x = pageXY[0] - o.left, y = pageXY[1] - o.top; + if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + return false; + }; + + var _mouseover = false; + var _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false; + var _nullSafeHasClass = function(el, clazz) { + return el != null && _hasClass(el, clazz); + }; + this.mousemove = function(e) { + var pageXY = _pageXY(e), clientXY = _clientXY(e), + ee = document.elementFromPoint(clientXY[0], clientXY[1]), + eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); + var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); + if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.fire("mouseenter", self, e); + return true; + } + // TODO here there is a remote chance that the overlay the mouse moved onto + // is actually not an overlay for the current component. a more thorough check would + // be to ensure the overlay belonged to the current component. + else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { + _mouseover = false; + self.fire("mouseexit", self, e); + } + self.fire("mousemove", self, e); + }; + + this.click = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); + self.fire("mousedown", self, e); + } + }; + + this.mouseup = function(e) { + //if (self == _connectionBeingDragged) _connectionBeingDragged = null; + _mouseDown = false; + self.fire("mouseup", self, e); + }; + }; + + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + jsPlumb.appendElement(canvas, params.parent); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + params["_jsPlumb"].getId(canvas, params.uuid); + + return canvas; + }; + + /** + * Class:CanvasConnector + * Superclass for Canvas Connector renderers. + */ + var CanvasConnector = jsPlumb.CanvasConnector = function(params) { + + CanvasMouseAdapter.apply(this, arguments); + + var _paintOneStyle = function(dim, aStyle) { + self.ctx.save(); + jsPlumb.extend(self.ctx, aStyle); + if (aStyle.gradient) { + var g = self.createGradient(dim, self.ctx); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + self.ctx.strokeStyle = g; + } + self._paint(dim); + self.ctx.restore(); + }; + + var self = this, + clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent + }); + self.ctx = self.canvas.getContext("2d"); + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + self.paint = function(dim, style) { + if (style != null) { + + jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); + + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + _paintOneStyle(dim, outlineStyle); + } + _paintOneStyle(dim, style); + } + }; + }; + + /** + * Class:CanvasEndpoint + * Superclass for Canvas Endpoint renderers. + */ + var CanvasEndpoint = function(params) { + var self = this; + CanvasMouseAdapter.apply(this, arguments); + var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent + }); + self.ctx = self.canvas.getContext("2d"); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + // _paintOneStyle(d, outlineStyle); + } + + self._paint.apply(this, arguments); + }; + }; + + jsPlumb.Endpoints.canvas.Dot = function(params) { + var self = this; + jsPlumb.Endpoints.Dot.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }; + var calculateAdjustments = function(gradient) { + var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius; + gradient.offset && (offsetAdjustment = parseValue(gradient.offset)); + gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius)); + return [offsetAdjustment, innerRadius]; + }; + this._paint = function(d, style, anchor) { + if (style != null) { + var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(); + jsPlumb.extend(ctx, style); + if (style.gradient) { + var adjustments = calculateAdjustments(style.gradient), + yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0], + xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0], + g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + ctx.beginPath(); + ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + } + }; + }; + + jsPlumb.Endpoints.canvas.Rectangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) { + + var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(); + jsPlumb.extend(ctx, style); + + /* canvas gradient */ + if (style.gradient) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, d[2], d[3]); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + jsPlumb.Endpoints.canvas.Triangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Triangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) + { + var width = d[2], height = d[3], x = d[0], y = d[1]; + + var ctx = self.canvas.getContext('2d'); + var offsetX = 0, offsetY = 0, angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = style.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + /* + * Canvas Image Endpoint: uses the default version, which creates an tag. + */ + jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image; + + /* + * Blank endpoint in all renderers is just the default Blank endpoint. + */ + jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank; + + /* + * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Bezier = function() { + var self = this; + jsPlumb.Connectors.Bezier.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO i doubt this handles the case that source and target are swapped. + this.createGradient = function(dim, ctx, swap) { + return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + /* + * Canvas straight line Connector. Draws a straight line onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Straight = function() { + var self = this; + jsPlumb.Connectors.Straight.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO this does not handle the case that src and target are swapped. + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + jsPlumb.Connectors.canvas.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + +// ********************************* END OF CANVAS RENDERERS ******************************************************************* + + jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label; + + /** + * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. + */ + var CanvasOverlay = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }; + + var AbstractCanvasArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + CanvasOverlay.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + var ctx = connector.ctx; + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(d.hxy.x, d.hxy.y); + ctx.lineTo(d.tail[0].x, d.tail[0].y); + ctx.lineTo(d.cxy.x, d.cxy.y); + ctx.lineTo(d.tail[1].x, d.tail[1].y); + ctx.lineTo(d.hxy.x, d.hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + }; + + jsPlumb.Overlays.canvas.Arrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.canvas.PlainArrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.canvas.Diamond = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.3/jsPlumb-renderers-svg-1.3.3-RC1.js b/archive/1.3.3/jsPlumb-renderers-svg-1.3.3-RC1.js new file mode 100644 index 000000000..6208ce723 --- /dev/null +++ b/archive/1.3.3/jsPlumb-renderers-svg-1.3.3-RC1.js @@ -0,0 +1,401 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the SVG renderers. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +/** + * SVG support for jsPlumb. + * + * things to investigate: + * + * gradients: https://developer.mozilla.org/en/svg_in_html_introduction + * css:http://tutorials.jenkov.com/svg/svg-and-css.html + * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath + * pointer events: https://developer.mozilla.org/en/css/pointer-events + * + */ +;(function() { + + var svgAttributeMap = { + "stroke-linejoin":"stroke-linejoin", + "joinstyle":"stroke-linejoin", + "stroke-dashoffset":"stroke-dashoffset" + }; + + var ns = { + svg:"http://www.w3.org/2000/svg", + xhtml:"http://www.w3.org/1999/xhtml" + }, + _attr = function(node, attributes) { + for (var i in attributes) + node.setAttribute(i, "" + attributes[i]); + }, + _node = function(name, attributes) { + var n = document.createElementNS(ns.svg, name); + attributes = attributes || {}; + attributes["version"] = "1.1"; + attributes["xmnls"] = ns.xhtml; + _attr(n, attributes); + return n; + }, + _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; }, + _convertStyle = function(s, ignoreAlpha) { + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + + return o; + }, + _clearGradient = function(parent) { + for (var i = 0; i < parent.childNodes.length; i++) { + if (parent.childNodes[i].tagName == "linearGradient" || parent.childNodes[i].tagName == "radialGradient") + parent.removeChild(parent.childNodes[i]); + } + }, + _updateGradient = function(parent, node, style, dimensions) { + var id = "jsplumb_gradient_" + (new Date()).getTime(); + // first clear out any existing gradient + _clearGradient(parent); + // this checks for an 'offset' property in the gradient, and in the absence of it, assumes + // we want a linear gradient. if it's there, we create a radial gradient. + // it is possible that a more explicit means of defining the gradient type would be + // better. relying on 'offset' means that we can never have a radial gradient that uses + // some default offset, for instance. + if (!style.gradient.offset) { + var g = _node("linearGradient", {id:id}); + parent.appendChild(g); + } + else { + var g = _node("radialGradient", { + id:id + }); + parent.appendChild(g); + } + + // the svg radial gradient seems to treat stops in the reverse + // order to how canvas does it. so we want to keep all the maths the same, but + // iterate the actual style declarations in reverse order, if the x indexes are not in order. + for (var i = 0; i < style.gradient.stops.length; i++) { + // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of + // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments. + // so, not too concerned about leaving this in for now. + var styleToUse = i; + if (dimensions.length == 8) + styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i; + else + styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i; + var stopColor = _convertStyle(style.gradient.stops[styleToUse][1], true); + var s = _node("stop", {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor}); + g.appendChild(s); + } + var applyGradientTo = style.strokeStyle ? "stroke" : "fill"; + node.setAttribute("style", applyGradientTo + ":url(#" + id + ")"); + }, + _applyStyles = function(parent, node, style, dimensions) { + + if (style.gradient) { + _updateGradient(parent, node, style, dimensions); + } + else { + // make sure we clear any existing gradient + _clearGradient(parent); + node.setAttribute("style", ""); + } + + node.setAttribute("fill", style.fillStyle ? _convertStyle(style.fillStyle, true) : "none"); + node.setAttribute("stroke", style.strokeStyle ? _convertStyle(style.strokeStyle, true) : "none"); + if (style.lineWidth) { + node.setAttribute("stroke-width", style.lineWidth); + } + + // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like + // the syntax in VML but is actually kind of nasty: values are given in the pixel + // coordinate space, whereas in VML they are multiples of the width of the stroked + // line, which makes a lot more sense. for that reason, jsPlumb is supporting both + // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from + // VML, which will be the preferred method. the code below this converts a dashstyle + // attribute given in terms of stroke width into a pixel representation, by using the + // stroke's lineWidth. + if(style["stroke-dasharray"]) { + node.setAttribute("stroke-dasharray", style["stroke-dasharray"]); + } + if (style["dashstyle"] && style["lineWidth"]) { + var sep = style["dashstyle"].indexOf(",") == -1 ? " " : ",", + parts = style["dashstyle"].split(sep), + styleToUse = ""; + parts.forEach(function(p) { + styleToUse += (Math.floor(p * style.lineWidth) + sep); + }); + node.setAttribute("stroke-dasharray", styleToUse); + } + + // extra attributes such as join type, dash offset. + for (var i in svgAttributeMap) { + if (style[i]) { + node.setAttribute(svgAttributeMap[i], style[i]); + } + } + }, + _decodeFont = function(f) { + var r = /([0-9].)(p[xt])\s(.*)/; + var bits = f.match(r); + return {size:bits[1] + bits[2], font:bits[3]}; + }; + + /* + * Base class for SVG components. + */ + var SvgComponent = function(cssClass, originalArgs, pointerEventsSpec) { + var self = this; + pointerEventsSpec = pointerEventsSpec || "all"; + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + self.canvas = null, self.path = null, self.svg = null; + + this.setHover = function() { }; + + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.sizeCanvas(self.canvas,0,0,1,1); + + var clazz = cssClass + " " + (originalArgs[0].cssClass || ""); + self.canvas.className = clazz; + + self.svg = _node("svg", { + "style":"", + "width":0, + "height":0, + "pointer-events":pointerEventsSpec/*, + "class": clazz*/ + }); + + jsPlumb.appendElement(self.canvas, originalArgs[0]["parent"]); + self.canvas.appendChild(self.svg); + + // TODO this displayElement stuff is common between all components, across all + // renderers. would be best moved to jsPlumbUIComponent. + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + this.paint = function(d, style, anchor) { + if (style != null) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + _attr(self.svg, { + "style":_pos([0,0,d[2], d[3]]), + "width": d[2], + "height": d[3] + }); + self._paint.apply(this, arguments); + } + }; + }; + + /* + * Base class for SVG connectors. + */ + var SvgConnector = function(params) { + var self = this; + SvgComponent.apply(this, [ params["_jsPlumb"].connectorClass, arguments, "none" ]); + this._paint = function(d, style) { + var p = self.getPath(d), a = { "d":p }, outlineStyle = null; + a["pointer-events"] = "all"; + + // outline style. actually means drawing an svg object underneath the main one. + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + outlineStyle = { + strokeStyle:_convertStyle(style.outlineColor), + lineWidth:outlineStrokeWidth + }; + + if (self.bgPath == null) { + self.bgPath = _node("path", a); + self.svg.appendChild(self.bgPath); + self.attachListeners(self.bgPath, self); + } + else { + _attr(self.bgPath, a); + } + + _applyStyles(self.svg, self.bgPath, outlineStyle, d); + } + + + if (self.path == null) { + self.path = _node("path", a); + self.svg.appendChild(self.path); + self.attachListeners(self.path, self); + } + else { + _attr(self.path, a); + } + + _applyStyles(self.svg, self.path, style, d); + }; + }; + + /* + * SVG Bezier Connector + */ + jsPlumb.Connectors.svg.Bezier = function(params) { + jsPlumb.Connectors.Bezier.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]; }; + }; + + /* + * SVG straight line Connector + */ + jsPlumb.Connectors.svg.Straight = function(params) { + jsPlumb.Connectors.Straight.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; }; + }; + + jsPlumb.Connectors.svg.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "M " + dimensions[4] + "," + dimensions[5]; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)]; + } + // finally draw a line to the end + p = p + " " + dimensions[6] + "," + dimensions[7]; + return p; + }; + }; + + /* + * Base class for SVG endpoints. + */ + var SvgEndpoint = function(params) { + var self = this; + SvgComponent.apply(this, [ params["_jsPlumb"].endpointClass, arguments, "all" ]); + this._paint = function(d, style) { + var s = jsPlumb.extend({}, style); + if (s.outlineColor) { + s.strokeWidth = s.outlineWidth; + s.strokeStyle = _convertStyle(s.outlineColor, true); + } + + if (self.node == null) { + self.node = self.makeNode(d, s); + self.svg.appendChild(self.node); + self.attachListeners(self.node, self); + } + _applyStyles(self.svg, self.node, s, d); + _pos(self.node, d); + }; + }; + + /* + * SVG Dot Endpoint + */ + jsPlumb.Endpoints.svg.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("circle", { + "cx" : d[2] / 2, + "cy" : d[3] / 2, + "r" : d[2] / 2 + }); + }; + }; + + /* + * SVG Rectangle Endpoint + */ + jsPlumb.Endpoints.svg.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("rect", { + "width":d[2], + "height":d[3] + }); + }; + }; + + /* + * SVG Image Endpoint is the default image endpoint. + */ + jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image; + /* + * Blank endpoint in svg renderer is the default Blank endpoint. + */ + jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank; + /* + * Label endpoint in svg renderer is the default Label endpoint. + */ + jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label; + + + var AbstractSvgArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + var self = this, path =null; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + } + + _attr(path, { + "d" : makePath(d), + stroke : strokeStyle ? strokeStyle : null, + fill : fillStyle ? fillStyle : null + }); + }; + var makePath = function(d) { + return "M" + d.hxy.x + "," + d.hxy.y + + " L" + d.tail[0].x + "," + d.tail[0].y + + " L" + d.cxy.x + "," + d.cxy.y + + " L" + d.tail[1].x + "," + d.tail[1].y + + " L" + d.hxy.x + "," + d.hxy.y; + }; + }; + + jsPlumb.Overlays.svg.Arrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.svg.PlainArrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.svg.Diamond = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.3/jsPlumb-renderers-vml-1.3.3-RC1.js b/archive/1.3.3/jsPlumb-renderers-vml-1.3.3-RC1.js new file mode 100644 index 000000000..115b599c8 --- /dev/null +++ b/archive/1.3.3/jsPlumb-renderers-vml-1.3.3-RC1.js @@ -0,0 +1,369 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the VML renderers. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +;(function() { + + // http://ajaxian.com/archives/the-vml-changes-in-ie-8 + // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/ + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + + var vmlAttributeMap = { + "stroke-linejoin":"joinstyle", + "joinstyle":"joinstyle", + "endcap":"endcap", + "miterlimit":"miterlimit" + }; + + if (document.createStyleSheet) { + + // this is the style rule for IE7/6: it uses a CSS class, tidy. + document.createStyleSheet().addRule(".jsplumb_vml", "behavior:url(#default#VML);position:absolute;"); + + // these are for VML in IE8. you have to explicitly call out which elements + // you're going to expect to support VML! + // + // try to avoid IE8. it is recommended you set X-UA-Compatible="IE=7" if you can. + // + document.createStyleSheet().addRule("jsplumb\\:textbox", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:oval", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:rect", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:stroke", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:shape", "behavior:url(#default#VML);position:absolute;"); + + // in this page it is also mentioned that IE requires the extra arg to the namespace + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either. + // var iev = document.documentMode; + //if (!iev || iev < 8) + document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); + //else + // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML"); + } + + var scale = 1000, + _atts = function(o, atts) { + for (var i in atts) { + // IE8 fix: setattribute does not work after an element has been added to the dom! + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + //o.setAttribute(i, atts[i]); + o[i] = atts[i]; + } + }, + _node = function(name, d, atts) { + atts = atts || {}; + var o = document.createElement("jsplumb:" + name); + o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml"; + _pos(o, d); + _atts(o, atts); + return o; + }, + _pos = function(o,d) { + o.style.left = d[0] + "px"; + o.style.top = d[1] + "px"; + o.style.width= d[2] + "px"; + o.style.height= d[3] + "px"; + o.style.position = "absolute"; + }, + _conv = function(v) { + return Math.floor(v * scale); + }, + _convertStyle = function(s, ignoreAlpha) { + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + + return o; + }, + _applyStyles = function(node, style, component) { + var styleToWrite = {}; + if (style.strokeStyle) { + styleToWrite["stroked"] = "true"; + styleToWrite["strokecolor"] =_convertStyle(style.strokeStyle, true); + styleToWrite["strokeweight"] = style.lineWidth + "px"; + } + else styleToWrite["stroked"] = "false"; + + if (style.fillStyle) { + styleToWrite["filled"] = "true"; + styleToWrite["fillcolor"] = _convertStyle(style.fillStyle, true); + } + else styleToWrite["filled"] = "false"; + + if(style["dashstyle"]) { + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = style["dashstyle"]; + } + else if (style["stroke-dasharray"] && style["lineWidth"]) { + var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",", + parts = style["stroke-dasharray"].split(sep), + styleToUse = ""; + for(var i = 0; i < parts.length; i++) { + styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep); + } + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = styleToUse; + } + + _atts(node, styleToWrite); + }, + /* + * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. + */ + VmlComponent = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }, + /* + * Base class for Vml connectors. extends VmlComponent. + */ + VmlConnector = function(params) { + var self = this; + self.strokeNode = null; + self.canvas = null; + VmlComponent.apply(this, arguments); + clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : ""); + this.paint = function(d, style, anchor) { + if (style != null) { + var path = self.getPath(d), p = { "path":path }; + + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + outlineStyle = { + strokeStyle:_convertStyle(style.outlineColor), + lineWidth:outlineStrokeWidth + }; + + if (self.bgCanvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.bgCanvas = _node("shape", d, p); + jsPlumb.appendElement(self.bgCanvas, params.parent); + _pos(self.bgCanvas, d); + displayElements.push(self.bgCanvas); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.bgCanvas, d); + _atts(self.bgCanvas, p); + } + + _applyStyles(self.bgCanvas, outlineStyle, self); + } + + if (self.canvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.canvas = _node("shape", d, p); + jsPlumb.appendElement(self.canvas, params.parent); + displayElements.push(self.canvas); + + self.attachListeners(self.canvas, self); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.canvas, d); + _atts(self.canvas, p); + } + + _applyStyles(self.canvas, style, self); + } + }; + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + self.canvas.parentNode.appendChild(el); + displayElements.push(el); + }; + }, + /* + * + * Base class for Vml Endpoints. extends VmlComponent. + * + */ + VmlEndpoint = function(params) { + VmlComponent.apply(this, arguments); + var vml = null, self = this; + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function(d, style, anchor) { + var p = { }; + + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (vml == null) { + p["class"] = jsPlumb.endpointClass; + vml = self.getVml([0,0, d[2], d[3]], p, anchor); + self.canvas.appendChild(vml); + self.attachListeners(vml, self); + } + else { + //p["coordsize"] = "1,1";//(d[2] * scale) + "," + (d[3] * scale); again, unsure. + _pos(vml, [0,0, d[2], d[3]]); + _atts(vml, p); + } + + _applyStyles(vml, style); + }; + }; + + jsPlumb.Connectors.vml.Bezier = function() { + jsPlumb.Connectors.Bezier.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Straight = function() { + jsPlumb.Connectors.Straight.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Flowchart = function() { + jsPlumb.Connectors.Flowchart.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l"; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]); + } + // finally draw a line to the end + p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e"; + return p; + }; + }; + + jsPlumb.Endpoints.vml.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("oval", d, atts); }; + }; + + jsPlumb.Endpoints.vml.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("rect", d, atts); }; + }; + + /* + * VML Image Endpoint is the same as the default image endpoint. + */ + jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image; + + /** + * placeholder for Blank endpoint in vml renderer. + */ + jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank; + + /** + * VML Label renderer. uses the default label renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label; + + var AbstractVmlArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + VmlComponent.apply(this, arguments); + var self = this, canvas = null, path =null; + var getPath = function(d, connectorDimensions) { + return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) + + " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + + " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + + " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + + " x e"; + }; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) { + var p = {}; + if (strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] =_convertStyle(strokeStyle, true); + } + if (lineWidth) p["strokeweight"] = lineWidth + "px"; + if (fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = fillStyle; + } + var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + w = Math.abs(xmax - xmin), + h = Math.abs(ymax - ymin), + dim = [xmin, ymin, w, h]; + + // for VML, we create overlays using shapes that have the same dimensions and + // coordsize as their connector - overlays calculate themselves relative to the + // connector (it's how it's been done since the original canvas implementation, because + // for canvas that makes sense). + p["path"] = getPath(d, connectorDimensions); + p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale); + + dim[0] = connectorDimensions[0]; + dim[1] = connectorDimensions[1]; + dim[2] = connectorDimensions[2]; + dim[3] = connectorDimensions[3]; + + if (canvas == null) { + //p["class"] = jsPlumb.overlayClass; // TODO currentInstance? + canvas = _node("shape", dim, p); + connector.appendDisplayElement(canvas); + self.attachListeners(canvas, connector); + } + else { + _pos(canvas, dim); + _atts(canvas, p); + } + }; + }; + + jsPlumb.Overlays.vml.Arrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.vml.PlainArrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.vml.Diamond = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.3/mootools.jsPlumb-1.3.3-RC1.js b/archive/1.3.3/mootools.jsPlumb-1.3.3-RC1.js new file mode 100644 index 000000000..2250fbc38 --- /dev/null +++ b/archive/1.3.3/mootools.jsPlumb-1.3.3-RC1.js @@ -0,0 +1,351 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the MooTools adapter. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +;(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function(now) { + this.parent(now); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }; + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + var _getElementObject = function(el) { + return $(el); + }; + + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + m.start(properties); + }, + + appendElement : function(child, parent) { + _getElementObject(parent).grab(child); + }, + + bind : function(el, event, callback) { + el = _getElementObject(el); + el.addEvent(event, callback); + }, + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.event.clientX, eventObject.event.clientY]; + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el); + var drags = _draggablesById[id]; + return drags[0].scope; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getElementObject : _getElementObject, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + getPageXY : function(eventObject) { + return [eventObject.event.pageX, eventObject.event.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).getParent(); + }, + + getScrollLeft : function(el) { + return null; + }, + + getScrollTop : function(el) { + return null; + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[0], + p = jsPlumb.CurrentLibrary.getElementObject(ui).getPosition(); + return { left:p.x, top:p.y }; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options) { + var id = jsPlumb.getId(el); + var drag = _draggablesById[id]; + if (!drag) { + var originalZIndex = 0, originalCursor = null; + var dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + options['onStart'] = jsPlumb.wrap(options['onStart'], function() + { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() + { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + }); + + // DROPPABLES: + var scope = options['scope'] || jsPlumb.Defaults.Scope; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop'); + } + }; + + drag = new Drag.Move(el, options); + drag.scope = scope; + //console.log("drag scope initialized to ", scope); + _add(_draggablesByScope, scope, drag); + _add(_draggablesById, el.get("id"), drag); + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + initDroppable : function(el, options) { + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _add(_droppables, scope, el); + var id = jsPlumb.getId(el); + _droppableOptions[id] = options; + _droppableScopesById[id] = scope; + var filterFunc = function(entry) { return entry.element != el; }; + var draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + isAlreadyDraggable : function(el) { + return _draggablesById[jsPlumb.getId(el)] != null; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + return (typeof Drag != undefined && typeof Drag.Move != undefined); + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + _getElementObject(element).dispose(); // ?? + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + setDragScope : function(el, scope) { + var drag = _draggablesById[el.get("id")]; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + drag[0].droppables = droppables; + }, + + setOffset : function(el, o) { + _getElementObject(el).setPosition({x:o.left, y:o.top}); + } + }; + + window.addEvent('domready', jsPlumb.init); +})(); diff --git a/build/1.3.6/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js b/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js rename to archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js diff --git a/build/1.3.6/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js b/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js rename to archive/1.3.3/mootools.jsPlumb-1.3.3-all.js diff --git a/archive/1.3.3/yui.jsPlumb-1.3.3-RC1.js b/archive/1.3.3/yui.jsPlumb-1.3.3-RC1.js new file mode 100644 index 000000000..46dc203b9 --- /dev/null +++ b/archive/1.3.3/yui.jsPlumb-1.3.3-RC1.js @@ -0,0 +1,329 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the YUI3 adapter. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +/** + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + */ +(function() { + + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function( v, b, s ) { + for( var i = +b || 0, l = this.length; i < l; i++ ) { + if( this[i]===v || s && this[i]==v ) { return i; } + } + return -1; + }; + } + + var Y; + + YUI().use('node', 'dd', 'anim', function(_Y) { + Y = _Y; + Y.on("domready", function() { jsPlumb.init(); }); + }); + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }, + ddEvents = [ "drag:mouseDown", "drag:afterMouseDown", "drag:mouseup", + "drag:align", "drag:removeHandle", "drag:addHandle", "drag:removeInvalid", "drag:addInvalid", + "drag:start", "drag:end", "drag:drag", "drag:over", "drag:enter", + "drag:exit", "drag:drophit", "drag:dropmiss", "drop:over", "drop:enter", "drop:exit", "drop:hit" + ], + animEvents = [ "tween" ], + /** + * helper function to curry callbacks for some element. + */ + _wrapper = function(fn) { + return function() { + try { + fn.apply(this, arguments); + } + catch (e) { + console.log("wrap fail", e); + } + }; + }, + /** + * extracts options from the given options object, leaving out event handlers. + */ + _getDDOptions = function(options) { + var o = {}; + for (var i in options) if (ddEvents.indexOf(i) == -1) o[i] = options[i]; + return o; + }, + /** + * attaches all event handlers found in options to the given dragdrop object, and registering + * the given el as the element of interest. + */ + _attachListeners = function(dd, options, eventList) { + for (var ev in options) { + if (eventList.indexOf(ev) != -1) { + var w = _wrapper(options[ev]); + dd.on(ev, w); + } + } + }, + _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}, + _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }, + _lastDragObject = null, + _extend = function(o1, o2) { + for (var i in o2) + o1[i] = o2[i]; + return o1; + }, + _getAttribute = function(el, attributeId) { + return el.getAttribute(attributeId); + }, + _getElementObject = function(el) { + return typeof el == 'string' ? Y.one('#' + el) : Y.one(el); + }; + + jsPlumb.CurrentLibrary = { + + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + var o = _extend({node:el, to:properties}, options); + var id = _getAttribute(el, "id"); + o["tween"] = jsPlumb.wrap(properties["tween"], function() { + jsPlumb.repaint(id); + }); + var a = new Y.Anim(o); + _attachListeners(a, o, animEvents); + a.run(); + }, + + appendElement : function(child, parent) { + _getElementObject (parent).append(child); + }, + + /** + * event binding wrapper. + */ + bind : function(el, event, callback) { + _getElementObject(el).on(event, callback); + }, + + dragEvents : { + "start":"drag:start", "stop":"drag:end", "drag":"drag:drag", "step":"step", + "over":"drop:enter", "out":"drop:exit", "drop":"drop:hit" + }, + + extend : _extend, + + getAttribute : _getAttribute, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + // this is a workaround for the unfortunate fact that in YUI3, the 'drop:exit' event does + // not contain a reference to the drag that just exited. single-threaded js to the + // rescue: we'll just keep it for ourselves. + if (eventArgs[0].drag) _lastDragObject = eventArgs[0].drag.el; + return _lastDragObject; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + return dd.scope; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getElementObject : _getElementObject, + + getOffset : function(el) { + var o = Y.DOM.getXY(el._node); + return {left:o[0], top:o[1]}; + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).get("parentNode"); + }, + + getScrollLeft : function(el) { + return 0; + }, + + getScrollTop : function(el) { + return 0; + }, + + getSize : function(el) { + return [ el._node.offsetWidth, el._node.offsetHeight ]; + }, + + getUIPosition : function(args) { + var n = args[0].currentTarget.el._node, + o = Y.DOM.getXY(n); + return {left:o[0], top:o[1]}; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options) { + var _opts = _getDDOptions(options); + var id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drag(_opts); + dd.el = el; + + var scope = options['scope'] || jsPlumb.Defaults.Scope; + dd.scope = scope; + + _draggablesById[id] = dd; + _add(_draggablesByScope, scope, dd); + + _attachListeners(dd, options, ddEvents); + }, + + initDroppable : function(el, options) { + var _opts = _getDDOptions(options); + var id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drop(_opts); + + _droppableOptions[id] = options; + + options = _extend({}, options); + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _droppableScopesById[id] = scope; + + options["drop:enter"] = jsPlumb.wrap(options["drop:enter"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, true); + }, true); + options["drop:exit"] = jsPlumb.wrap(options["drop:exit"], function(e) { + _checkHover(el, false); + }); + options["drop:hit"] = jsPlumb.wrap(options["drop:hit"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, false); + }, true); + + _attachListeners(dd, options, ddEvents); + }, + + isAlreadyDraggable : function(el) { + el = _getElementObject(el); + return el.hasClass("yui3-dd-draggable"); + }, + + isDragSupported : function(el) { return true; }, + isDropSupported : function(el) { return true; }, + removeClass : function(el, clazz) { el.removeClass(clazz); }, + removeElement : function(el) { _getElementObject(el).remove(); }, + + setAttribute : function(el, attributeName, attributeValue) { + el.setAttribute(attributeName, attributeValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + if (dd) dd.set("lock", !draggable); + }, + + setDragScope : function(el, scope) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + if (dd) dd.scope = scope; + }, + + setOffset : function(el, o) { + el = _getElementObject(el); + el.set("top", o.top); + el.set("left", o.left); + } + }; +})(); \ No newline at end of file diff --git a/build/1.3.6/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js b/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js rename to archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js diff --git a/build/1.3.6/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js b/archive/1.3.3/yui.jsPlumb-1.3.3-all.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js rename to archive/1.3.3/yui.jsPlumb-1.3.3-all.js diff --git a/archive/1.3.4/jquery.jsPlumb-1.3.4-RC1.js b/archive/1.3.4/jquery.jsPlumb-1.3.4-RC1.js new file mode 100644 index 000000000..021e7b4b6 --- /dev/null +++ b/archive/1.3.4/jquery.jsPlumb-1.3.4-RC1.js @@ -0,0 +1,342 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the jQuery adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ +/* + * the library specific functions, such as find offset, get id, get attribute, extend etc. + * the full list is: + * + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getDropScope gets the drop scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getPageXY gets the page event's xy location. + * getParent gets the parent of some element. + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * hasClass returns whether or not the given element has the given class. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + * trigger triggers some event on an element. + * unbind unbinds some listener from some element. + */ +(function($) { + + //var getBoundingClientRectSupported = "getBoundingClientRect" in document.documentElement; + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el[0].className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.addClass(el[0], clazz); + } + } + catch (e) { + // SVGAnimatedString not supported; no problem. + } + el.addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * appends the given child to the given parent. + */ + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).append(child); + }, + + /** + * event binding wrapper. it just so happens that jQuery uses 'bind' also. yui3, for example, + * uses 'on'. + */ + bind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.bind(event, callback); + }, + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step', + 'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete' + }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + getDocumentElement : function() { return document; }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + }, + + getDragScope : function(el) { + return el.draggable("option", "scope"); + }, + + getDropScope : function(el) { + return el.droppable("option", "scope"); + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + * + */ + getElementObject : function(el) { + return typeof(el)=='string' ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).parent(); + }, + + getScrollLeft : function(el) { + return el.scrollLeft(); + }, + + getScrollTop : function(el) { + return el.scrollTop(); + }, + + getSelector : function(spec) { + return $(spec); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e.length > 0 ? e[0].tagName : null; + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + + // this code is a workaround for the case that the element being dragged has a margin set on it. jquery UI passes + // in the wrong offset if the element has a margin (it doesn't take the margin into account). the getBoundingClientRect + // method, which is in pretty much all browsers now, reports the right numbers. but it introduces a noticeable lag, which + // i don't like. + + /*if ( getBoundingClientRectSupported ) { + var r = eventArgs[1].helper[0].getBoundingClientRect(); + return { left : r.left, top: r.top }; + } else {*/ + if (eventArgs.length == 1) { + return { left: eventArgs[0].pageX, top:eventArgs[0].pageY }; + } + else { + var ui = eventArgs[1], _offset = ui.offset; + return _offset || ui.absolutePosition; + } + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options) { + options = options || {}; + // remove helper directive if present. + options.helper = null; + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.draggable(options); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.droppable(options); + }, + + isAlreadyDraggable : function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return el.hasClass("ui-draggable"); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el[0].className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.removeClass(el[0], clazz); + } + } + catch (e) { + // SVGAnimatedString not supported; no problem. + } + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).remove(); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * sets the drag scope. probably time for a setDragOption method (roll this and the one above together) + * @param el + * @param scope + */ + setDragScope : function(el, scope) { + el.draggable("option", "scope", scope); + }, + + setOffset : function(el, o) { + jsPlumb.CurrentLibrary.getElementObject(el).offset(o); + }, + + /** + * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself. + * the other libraries do not. yui, in fact, cannot even pass an original event. we have to pull out stuff + * from the originalEvent to put in an options object for YUI. + * @param el + * @param event + * @param originalEvent + */ + trigger : function(el, event, originalEvent) { + //originalEvent.stopPropagation(); + //jsPlumb.CurrentLibrary.getElementObject(el).trigger(originalEvent); + var h = jQuery._data(jsPlumb.CurrentLibrary.getElementObject(el)[0], "handle"); + h(originalEvent); + //originalEvent.stopPropagation(); + }, + + /** + * event unbinding wrapper. it just so happens that jQuery uses 'unbind' also. yui3, for example, + * uses..something else. + */ + unbind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.unbind(event, callback); + } + }; + + $(document).ready(jsPlumb.init); + +})(jQuery); diff --git a/archive/1.3.4/jquery.jsPlumb-1.3.4-all-min.js b/archive/1.3.4/jquery.jsPlumb-1.3.4-all-min.js new file mode 100644 index 000000000..2ab226d37 --- /dev/null +++ b/archive/1.3.4/jquery.jsPlumb-1.3.4-all-min.js @@ -0,0 +1 @@ +(function(){var t=!!document.createElement("canvas").getContext,d=!!window.SVGAngle||document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1"),a=!(t|d);var n=function(C,D,A,G){var F=function(J,I){if(J===I){return true}else{if(typeof J=="object"&&typeof I=="object"){var K=true;for(var H in J){if(!F(J[H],I[H])){K=false;break}}for(var H in I){if(!F(I[H],J[H])){K=false;break}}return K}}};for(var E=+A||0,B=C.length;E-1){B.splice(A,1)}return A!=-1};if(!window.console){window.console={time:function(){},timeEnd:function(){},group:function(){},groupEnd:function(){},log:function(){}}}var i=function(D,B,C){var A=D[B];if(A==null){A=[],D[B]=A}A.push(C);return A},r=null,c=function(A,B){return l.CurrentLibrary.getAttribute(x(A),B)},e=function(B,C,A){l.CurrentLibrary.setAttribute(x(B),C,A)},u=function(B,A){l.CurrentLibrary.addClass(x(B),A)},h=function(B,A){return l.CurrentLibrary.hasClass(x(B),A)},k=function(B,A){l.CurrentLibrary.removeClass(x(B),A)},x=function(A){return l.CurrentLibrary.getElementObject(A)},p=function(A){return l.CurrentLibrary.getOffset(x(A))},b=function(A){return l.CurrentLibrary.getSize(x(A))},w=true,m=function(){if(w&&typeof console!="undefined"){try{var B=arguments[arguments.length-1];console.log(B)}catch(A){}}},z=function(A){if(w&&typeof console!="undefined"){console.group(A)}},f=function(A){if(w&&typeof console!="undefined"){console.groupEnd(A)}},y=function(A){if(w&&typeof console!="undefined"){console.time(A)}},q=function(A){if(w&&typeof console!="undefined"){console.timeEnd(A)}};EventGenerator=function(){var C={},B=this;var A=["ready"];this.bind=function(D,E){i(C,D,E)};this.fire=function(F,G,D){if(C[F]){for(var E=0;E=0){delete (aZ[a0]);aZ.splice(a0,1);return true}}}return false},aO=function(a0,aZ){return am(a0,function(a1,a2){aR[a2]=aZ;if(l.CurrentLibrary.isDragSupported(a1)){l.CurrentLibrary.setDraggable(a1,aZ)}})},aC=function(a1,a2,aZ){a2=a2==="block";var a0=null;if(aZ){if(a2){a0=function(a4){a4.setVisible(true,true,true)}}else{a0=function(a4){a4.setVisible(false,true,true)}}}var a3=c(a1,"id");I(a3,function(a5){if(a2&&aZ){var a4=a5.sourceId===a3?1:0;if(a5.endpoints[a4].isVisible()){a5.setVisible(true)}}else{a5.setVisible(a2)}},a0)},aM=function(aZ){return am(aZ,function(a1,a0){var a2=aR[a0]==null?false:aR[a0];a2=!a2;aR[a0]=a2;l.CurrentLibrary.setDraggable(a1,a2);return a2})},at=function(aZ,a1){var a0=null;if(a1){a0=function(a2){var a3=a2.isVisible();a2.setVisible(!a3)}}I(aZ,function(a3){var a2=a3.isVisible();a3.setVisible(!a2)},a0)},K=function(a4){var a2=a4.timestamp,aZ=a4.recalc,a3=a4.offset,a0=a4.elId;if(!aZ){if(a2&&a2===aV[a0]){return U[a0]}}if(aZ||!a3){var a1=x(a0);if(a1!=null){Q[a0]=b(a1);U[a0]=p(a1);aV[a0]=a2}}else{U[a0]=a3;if(Q[a0]==null){var a1=x(a0);if(a1!=null){Q[a0]=b(a1)}}}if(U[a0]&&!U[a0].right){U[a0].right=U[a0].left+Q[a0][0];U[a0].bottom=U[a0].top+Q[a0][1];U[a0].width=Q[a0][0];U[a0].height=Q[a0][1];U[a0].centerx=U[a0].left+(U[a0].width/2);U[a0].centery=U[a0].top+(U[a0].height/2)}return U[a0]},ap=function(aZ){var a0=U[aZ];if(!a0){a0=K(aZ)}return{o:a0,s:Q[aZ]}},A=function(aZ,a0){var a1=x(aZ);var a2=c(a1,"id");if(!a2||a2=="undefined"){if(arguments.length==2&&arguments[1]!=undefined){a2=a0}else{a2="jsPlumb_"+W()}e(a1,"id",a2)}return a2},Y=function(a1,aZ,a0){a1=a1||function(){};aZ=aZ||function(){};return function(){var a2=null;try{a2=aZ.apply(this,arguments)}catch(a3){m(aS,"jsPlumb function failed : "+a3)}if(a0==null||(a2!==a0)){try{a1.apply(this,arguments)}catch(a3){m(aS,"wrapped function failed : "+a3)}}return a2}};this.connectorClass="_jsPlumb_connector";this.endpointClass="_jsPlumb_endpoint";this.overlayClass="_jsPlumb_overlay";this.Anchors={};this.Connectors={canvas:{},svg:{},vml:{}};this.Endpoints={canvas:{},svg:{},vml:{}};this.Overlays={canvas:{},svg:{},vml:{}};this.addClass=function(a0,aZ){return l.CurrentLibrary.addClass(a0,aZ)};this.removeClass=function(a0,aZ){return l.CurrentLibrary.removeClass(a0,aZ)};this.hasClass=function(a0,aZ){return l.CurrentLibrary.hasClass(a0,aZ)};this.addEndpoint=function(a1,a2,bb){bb=bb||{};var a0=l.extend({},bb);l.extend(a0,a2);a0.endpoint=a0.endpoint||aS.Defaults.Endpoint||l.Defaults.Endpoint;a0.paintStyle=a0.paintStyle||aS.Defaults.EndpointStyle||l.Defaults.EndpointStyle;a1=an(a1);var a3=[],a6=a1.length&&a1.constructor!=String?a1:[a1];for(var a4=0;a40){try{for(var a0=0;a00?n(bc,bb)!=-1:true},a3=a6.length>1?{}:[],a9=function(bc,bd){if(a6.length>1){var bb=a3[bc];if(bb==null){bb=[];a3[bc]=bb}bb.push(bd)}else{a3.push(bd)}};for(var a2 in ay){if(a0(a6,a2)){for(var a1=0;a1=4)?[a4[2],a4[3]]:[0,0],offsets:(a4.length==6)?[a4[4],a4[5]]:[0,0],elementId:a1};a2=new N(a3);a2.clone=function(){return new N(a3)}}}}}if(!a2.id){a2.id="anchor_"+W()}return a2};this.makeAnchors=function(a2,a0,aZ){var a3=[];for(var a1=0;a10?a9[0]:null,a4=a9.length>0?0:-1,a8=this,a3=function(bc,ba,bg,bf,bb){var be=bf[0]+(bc.x*bb[0]),bd=bf[1]+(bc.y*bb[1]);return Math.sqrt(Math.pow(ba-be,2)+Math.pow(bg-bd,2))},aZ=a0||function(bk,bb,bc,bd,ba){var bf=bc[0]+(bd[0]/2),be=bc[1]+(bd[1]/2);var bh=-1,bj=Infinity;for(var bg=0;bg=a2.left)||(a5.left<=a2.right&&a5.right>=a2.right)||(a5.left<=a2.left&&a5.right>=a2.right)||(a2.left<=a5.left&&a2.right>=a5.right)),ba=((a5.top<=a2.top&&a5.bottom>=a2.top)||(a5.top<=a2.bottom&&a5.bottom>=a2.bottom)||(a5.top<=a2.top&&a5.bottom>=a2.bottom)||(a2.top<=a5.top&&a2.bottom>=a5.bottom));if(!(a4||ba)){var a7=null,a1=false,aZ=false,a6=null;if(a2.left>a5.left&&a2.top>a5.top){a7=["right","top"]}else{if(a2.left>a5.left&&a5.top>a2.top){a7=["top","left"]}else{if(a2.lefta5.top){a7=["left","top"]}}}}return{orientation:H.DIAGONAL,a:a7,theta:a0,theta2:a3}}else{if(a4){return{orientation:H.HORIZONTAL,a:a5.topaZ[0]?1:-1},O=function(aZ){return function(a1,a0){var a2=true;if(aZ){if(a1[0][0]a0[0][1]}}else{if(a1[0][0]>a0[0][0]){a2=true}else{a2=a1[0][1]>a0[0][1]}}return a2===false?-1:1}},D=function(a0,aZ){var a2=a0[0][0]<0?-Math.PI-a0[0][0]:Math.PI-a0[0][0],a1=aZ[0][0]<0?-Math.PI-aZ[0][0]:Math.PI-aZ[0][0];if(a2>a1){return 1}else{return a0[0][1]>aZ[0][1]?1:-1}},aA={top:aF,right:O(true),bottom:O(true),left:D},Z=function(aZ,a0){return aZ.sort(a0)},X=function(a0,aZ){var a2=Q[a0],a3=U[a0],a1=function(a9,bg,a5,a8,be,bd){if(a8.length>0){var bc=Z(a8,aA[a9]),ba=a9==="right"||a9==="top",a4=aD(a9,bg,a5,bc,be,bd,ba);var bh=function(bk,bj){var bi=aT([bj[0],bj[1]],bk.canvas);V[bk.id]=[bi[0],bi[1],bj[2],bj[3]]};for(var a6=0;a6-1){a5.splice(a6,1)}else{R(aZ,a7.elementId,a7)}};this.clearFor=function(a6){delete aZ[a6];aZ[a6]=[]};var a3=function(bq,bd,bl,ba,bg,bh,bj,bf,bs,bi,a9,bp){var bn=-1,a8=-1,bb=ba.endpoints[bj],bk=bb.id,be=[1,0][bj],a6=[[bd,bl],ba,bg,bh,bk],a7=bq[bs],br=bb._continuousAnchorEdge?bq[bb._continuousAnchorEdge]:null;if(br){var bo=g(br,function(bt){return bt[4]==bk});if(bo!=-1){br.splice(bo,1);for(var bm=0;bm=0?ba.overlays[bl]:null};this.hideOverlay=function(bm){var bl=ba.getOverlay(bm);if(bl){bl.hide()}};this.showOverlay=function(bm){var bl=ba.getOverlay(bm);if(bl){bl.show()}};this.removeAllOverlays=function(){ba.overlays.splice(0,ba.overlays.length);ba.repaint()};this.removeOverlay=function(bm){var bl=bh(bm);if(bl!=-1){var bn=ba.overlays[bl];bn.cleanup();ba.overlays.splice(bl,1)}};this.removeOverlays=function(){for(var bl=0;bl0){bg.connections[0].setHover(bs,false)}else{bg.setHover(bs)}};this.endpoint.bind("click",function(bs){bg.fire("click",bg,bs)});this.endpoint.bind("dblclick",function(bs){bg.fire("dblclick",bg,bs)});this.endpoint.bind("mouseenter",function(bs,bt){if(!bg.isHover()){br(true);bg.fire("mouseenter",bg,bt)}});this.endpoint.bind("mouseexit",function(bs,bt){if(bg.isHover()){br(false);bg.fire("mouseexit",bg,bt)}});this.setPaintStyle(bq.paintStyle||bq.style||aS.Defaults.EndpointStyle||l.Defaults.EndpointStyle,true);this.setHoverPaintStyle(bq.hoverPaintStyle||aS.Defaults.EndpointHoverStyle||l.Defaults.EndpointHoverStyle,true);this.paintStyleInUse=this.paintStyle;this.connectorStyle=bq.connectorStyle;this.connectorHoverStyle=bq.connectorHoverStyle;this.connectorOverlays=bq.connectorOverlays;this.connector=bq.connector;this.connectorTooltip=bq.connectorTooltip;this.parent=bq.parent;this.isSource=bq.isSource||false;this.isTarget=bq.isTarget||false;var bl=bq.maxConnections||aS.Defaults.MaxConnections;this.getAttachedElements=function(){return bg.connections};this.canvas=this.endpoint.canvas;this.connections=bq.connections||[];this.scope=bq.scope||F;this.timestamp=null;bg.isReattach=bq.reattach||false;bg.connectionsDetachable=aS.Defaults.ConnectionsDetachable;if(bq.connectionsDetachable===false||bq.detachable===false){bg.connectionsDetachable=false}var bb=bq.dragAllowedWhenFull||true;this.computeAnchor=function(bs){return bg.anchor.compute(bs)};this.addConnection=function(bs){bg.connections.push(bs)};this.detach=function(bs,bw,bt,bA){var bz=n(bg.connections,bs),by=false;bA=(bA!==false);if(bz>=0){if(bt||bs._forceDetach||bs.isDetachable()||bs.isDetachAllowed(bs)){var bB=bs.endpoints[0]==bg?bs.endpoints[1]:bs.endpoints[0];if(bt||bs._forceDetach||(bg.isDetachAllowed(bs))){bg.connections.splice(bz,1);if(!bw){bB.detach(bs,true,bt);if(bs.endpointsToDeleteOnDetach){for(var bx=0;bx0){bg.detach(bg.connections[0],false,true,bs)}};this.detachFrom=function(bu,bt){var bv=[];for(var bs=0;bs=0){bg.connections.splice(bs,1)}};this.getElement=function(){return bf};this.setElement=function(bu){var bw=A(bu);R(au,a7,bg);bf=x(bu);a7=A(bf);bg.elementId=a7;var bv=af({source:bw}),bt=a1.getParent(bg.canvas);a1.removeElement(bg.canvas,bt);a1.appendElement(bg.canvas,bv);for(var bs=0;bs0){var bC=bc(bu.elementWithPrecedence),bE=bC.endpoints[0]==bg?1:0,bw=bE==0?bC.sourceId:bC.targetId,bB=U[bw],bD=Q[bw];bt.txy=[bB.left,bB.top];bt.twh=bD;bt.tElement=bC.endpoints[bE]}bx=bg.anchor.compute(bt)}var bA=bd.compute(bx,bg.anchor.getOrientation(bd),bg.paintStyleInUse,bv||bg.paintStyleInUse);bd.paint(bA,bg.paintStyleInUse,bg.anchor);bg.timestamp=bz}};this.repaint=this.paint;this.removeConnection=this.detach;if(l.CurrentLibrary.isDragSupported(bf)){var bk={id:null,element:null},bj=null,a0=false,a3=null,aZ=aI(bk);var a5=function(){bj=bg.connectorSelector();var bs=true;if(bj==null&&!bq.isSource){bs=false}if(bq.isSource&&bg.isFull()&&!bb){bs=false}if(bj!=null&&!bj.isDetachable()){bs=false}if(bs===false){if(l.CurrentLibrary.stopDrag){l.CurrentLibrary.stopDrag()}aZ.stopDrag();return false}if(bj&&!bg.isFull()&&bq.isSource){bj=null}K({elId:a7});a4=bg.makeInPlaceCopy();a4.paint();G(bk,bg.parent);var by=x(a4.canvas),bw=l.CurrentLibrary.getOffset(by),bt=aT([bw.left,bw.top],a4.canvas);l.CurrentLibrary.setOffset(bk.element,{left:bt[0],top:bt[1]});e(x(bg.canvas),"dragId",bk.id);e(x(bg.canvas),"elId",a7);bo=ac(bg.paintStyle,bg.anchor,bd,bg.canvas,bk.element);if(bj==null){bg.anchor.locked=true;bg.setHover(false,false);bj=P({sourceEndpoint:bg,targetEndpoint:bo,source:bg.endpointWillMoveTo||x(bf),target:bk.element,anchors:[bg.anchor,bo.anchor],paintStyle:bq.connectorStyle,hoverPaintStyle:bq.connectorHoverStyle,connector:bq.connector,overlays:bq.connectorOverlays})}else{a0=true;bj.connector.setHover(false,false);a6(x(a4.canvas),false,true);var bv=bj.endpoints[0].id==bg.id?0:1;bj.floatingAnchorIndex=bv;bg.detachFromConnection(bj);var bz=x(bg.canvas),bx=l.CurrentLibrary.getDragScope(bz);e(bz,"originalScope",bx);var bu=l.CurrentLibrary.getDropScope(bz);l.CurrentLibrary.setDragScope(bz,bu);if(bv==0){a3=[bj.source,bj.sourceId,bn,bx];bj.source=bk.element;bj.sourceId=bk.id}else{a3=[bj.target,bj.targetId,bn,bx];bj.target=bk.element;bj.targetId=bk.id}bj.endpoints[bv==0?1:0].anchor.locked=true;bj.suspendedEndpoint=bj.endpoints[bv];bj.suspendedEndpoint.setHover(false);bj.endpoints[bv]=bo}aL[bk.id]=bj;bo.addConnection(bj);J(au,bk.id,bo);aS.currentlyDragging=true};var a1=l.CurrentLibrary,bm=bq.dragOptions||{},bh=l.extend({},a1.defaultDragOptions),bi=a1.dragEvents.start,bp=a1.dragEvents.stop,a9=a1.dragEvents.drag;bm=l.extend(bh,bm);bm.scope=bm.scope||bg.scope;bm[bi]=Y(bm[bi],a5);bm[a9]=Y(bm[a9],aZ);bm[bp]=Y(bm[bp],function(){aS.currentlyDragging=false;R(au,bk.id,bo);ax([bk.element[0],bo.canvas],bf);ad(a4.canvas,bf);aS.anchorManager.clearFor(bk.id);var bs=bj.floatingAnchorIndex==null?1:bj.floatingAnchorIndex;bj.endpoints[bs==0?1:0].anchor.locked=false;if(bj.endpoints[bs]==bo){if(a0&&bj.suspendedEndpoint){if(bs==0){bj.source=a3[0];bj.sourceId=a3[1]}else{bj.target=a3[0];bj.targetId=a3[1]}l.CurrentLibrary.setDragScope(a3[2],a3[3]);bj.endpoints[bs]=bj.suspendedEndpoint;if(bg.isReattach||bj._forceDetach||!bj.endpoints[bs==0?1:0].detach(bj)){bj.setHover(false);bj.floatingAnchorIndex=null;bj.suspendedEndpoint.addConnection(bj);l.repaint(a3[1])}bj._forceDetach=null}else{ax(bj.connector.getDisplayElements(),bg.parent);bg.detachFromConnection(bj)}}bg.anchor.locked=false;bg.paint({recalc:false});bj.setHover(false,false);bj=null;a4=null;delete au[bo.elementId];bo.anchor=null;bo=null;aS.currentlyDragging=false});var bn=x(bg.canvas);l.CurrentLibrary.initDraggable(bn,bm,true)}var a6=function(bw,bz,bu){if((bq.isTarget||bz)&&l.CurrentLibrary.isDropSupported(bf)){var bs=bq.dropOptions||aS.Defaults.DropOptions||l.Defaults.DropOptions;bs=l.extend({},bs);bs.scope=bs.scope||bg.scope;var bx=l.CurrentLibrary.dragEvents.drop,by=l.CurrentLibrary.dragEvents.over,bt=l.CurrentLibrary.dragEvents.out,bv=function(){var bJ=x(l.CurrentLibrary.getDragObject(arguments)),bA=c(bJ,"dragId"),bC=c(bJ,"elId"),bI=c(bJ,"originalScope"),bF=aL[bA],bG=bF.floatingAnchorIndex==null?1:bF.floatingAnchorIndex,bH=bG==0?1:0;if(bI){l.CurrentLibrary.setDragScope(bJ,bI)}if(!bg.isFull()&&!(bG==0&&!bg.isSource)&&!(bG==1&&!bg.isTarget)){var bD=true;if(bF.suspendedEndpoint&&bF.suspendedEndpoint.id!=bg.id){if(!bF.isDetachAllowed(bF)||!bF.endpoints[bG].isDetachAllowed(bF)||!bF.suspendedEndpoint.isDetachAllowed(bF)||!aS.checkCondition("beforeDetach",bF)){bD=false}}if(bG==0){bF.source=bf;bF.sourceId=a7}else{bF.target=bf;bF.targetId=a7}bD=bD&&bg.isDropAllowed(bF.sourceId,bF.targetId,bF.scope);if(bD){bF.endpoints[bG].detachFromConnection(bF);if(bF.suspendedEndpoint){bF.suspendedEndpoint.detachFromConnection(bF)}bF.endpoints[bG]=bg;bg.addConnection(bF);if(!bF.suspendedEndpoint){aH(bf,bq.draggable,{})}else{var bE=bF.suspendedEndpoint.getElement(),bB=bF.suspendedEndpoint.elementId;aE({source:bG==0?bE:bF.source,target:bG==1?bE:bF.target,sourceId:bG==0?bB:bF.sourceId,targetId:bG==1?bB:bF.targetId,sourceEndpoint:bG==0?bF.suspendedEndpoint:bF.endpoints[0],targetEndpoint:bG==1?bF.suspendedEndpoint:bF.endpoints[1],connection:bF},true)}aX(bF)}else{if(bF.suspendedEndpoint){bF.endpoints[bG]=bF.suspendedEndpoint;bF.setHover(false);bF._forceDetach=true;if(bG==0){bF.source=bF.suspendedEndpoint.element;bF.sourceId=bF.suspendedEndpoint.elementId}else{bF.target=bF.suspendedEndpoint.element;bF.targetId=bF.suspendedEndpoint.elementId}bF.suspendedEndpoint.addConnection(bF);bF.endpoints[0].repaint();bF.repaint();l.repaint(bF.source.elementId);bF._forceDetach=false}}bF.floatingAnchorIndex=null}aS.currentlyDragging=false;delete aL[bA]};bs[bx]=Y(bs[bx],bv);bs[by]=Y(bs[by],function(){var bB=l.CurrentLibrary.getDragObject(arguments),bD=c(x(bB),"dragId"),bC=aL[bD];if(bC!=null){var bA=bC.floatingAnchorIndex==null?1:bC.floatingAnchorIndex;bC.endpoints[bA].anchor.over(bg.anchor)}});bs[bt]=Y(bs[bt],function(){var bB=l.CurrentLibrary.getDragObject(arguments),bD=c(x(bB),"dragId"),bC=aL[bD];if(bC!=null){var bA=bC.floatingAnchorIndex==null?1:bC.floatingAnchorIndex;bC.endpoints[bA].anchor.out()}});l.CurrentLibrary.initDroppable(bw,bs,true,bu)}};a6(x(bg.canvas),true,!(bq._transient||bg.anchor.isFloating));return bg}};var l=window.jsPlumb=new s();l.getInstance=function(B){var A=new s(B);A.init();return A};l.util={convertStyle:function(B,A){if("transparent"===B){return B}var G=B,F=function(H){return H.length==1?"0"+H:H},C=function(H){return F(Number(H).toString(16))},D=/(rgb[a]?\()(.*)(\))/;if(B.match(D)){var E=B.match(D)[2].split(",");G="#"+C(E[0])+C(E[1])+C(E[2]);if(!A&&E.length==4){G=G+C(E[3])}}return G},gradient:function(B,A){B=B.constructor==Array?B:[B.x,B.y];A=A.constructor==Array?A:[A.x,A.y];return(A[1]-B[1])/(A[0]-B[0])},normal:function(B,A){return -1/l.util.gradient(B,A)},segment:function(B,A){B=B.constructor==Array?B:[B.x,B.y];A=A.constructor==Array?A:[A.x,A.y];if(A[0]>B[0]){return(A[1]>B[1])?2:1}else{return(A[1]>B[1])?3:4}},segmentMultipliers:[null,[1,-1],[1,1],[-1,1],[-1,-1]],inverseSegmentMultipliers:[null,[-1,-1],[-1,1],[1,1],[1,-1]],pointOnLine:function(A,E,B){var D=l.util.gradient(A,E),I=l.util.segment(A,E),H=B>0?l.util.segmentMultipliers[I]:l.util.inverseSegmentMultipliers[I],C=Math.atan(D),F=Math.abs(B*Math.sin(C))*H[1],G=Math.abs(B*Math.cos(C))*H[0];return{x:A.x+G,y:A.y+F}},perpendicularLineTo:function(C,D,E){var B=l.util.gradient(C,D),F=Math.atan(-1/B),G=E/2*Math.sin(F),A=E/2*Math.cos(F);return[{x:D.x+A,y:D.y+G},{x:D.x-A,y:D.y-G}]}};var o=function(A,F,C,B,E,D){return function(H){H=H||{};var G=l.makeAnchor([A,F,C,B,0,0],H.elementId,H.jsPlumbInstance);G.type=E;if(D){D(G,H)}return G}};l.Anchors.TopCenter=o(0.5,0,0,-1,"TopCenter");l.Anchors.BottomCenter=o(0.5,1,0,1,"BottomCenter");l.Anchors.LeftMiddle=o(0,0.5,-1,0,"LeftMiddle");l.Anchors.RightMiddle=o(1,0.5,1,0,"RightMiddle");l.Anchors.Center=o(0.5,0.5,0,0,"Center");l.Anchors.TopRight=o(1,0,0,-1,"TopRight");l.Anchors.BottomRight=o(1,1,0,1,"BottomRight");l.Anchors.TopLeft=o(0,0,0,-1,"TopLeft");l.Anchors.BottomLeft=o(0,1,0,1,"BottomLeft");l.Defaults.DynamicAnchors=function(A){return l.makeAnchors(["TopCenter","RightMiddle","BottomCenter","LeftMiddle"],A.elementId,A.jsPlumbInstance)};l.Anchors.AutoDefault=function(B){var A=l.makeDynamicAnchor(l.Defaults.DynamicAnchors(B));A.type="AutoDefault";return A};l.Anchors.Assign=o(0,0,0,0,"Assign",function(B,C){var A=C.position||"Fixed";B.positionFinder=A.constructor==String?l.AnchorPositionFinders[A]:A;B.constructorParams=C});l.Anchors.Continuous=function(A){return A.jsPlumbInstance.continuousAnchorFactory.get(A)};l.AnchorPositionFinders={Fixed:function(D,B,C,A){return[(D.left-B.left)/C[0],(D.top-B.top)/C[1]]},Grid:function(A,J,E,B){var I=A.left-J.left,H=A.top-J.top,G=E[0]/(B.constructorParams.grid[0]),F=E[1]/(B.constructorParams.grid[1]),D=Math.floor(I/G),C=Math.floor(H/F);return[((D*G)+(G/2))/E[0],((C*F)+(F/2))/E[1]]}}})();(function(){jsPlumb.DOMElementComponent=function(b){jsPlumb.jsPlumbUIComponent.apply(this,arguments);this.mousemove=this.dblclick=this.click=this.mousedown=this.mouseup=function(c){}};jsPlumb.Connectors.Straight=function(){this.type="Straight";var q=this,h=null,d,i,o,m,k,e,p,g,f,c,b,n,l;this.compute=function(z,I,r,v,E,s,C,u){var H=Math.abs(z[0]-I[0]),B=Math.abs(z[1]-I[1]),A=0.45*H,t=0.45*B;H*=1.9;B*=1.9;var F=Math.min(z[0],I[0])-A;var D=Math.min(z[1],I[1])-t;var G=Math.max(2*C,u);if(Hm){m=w}if(z<0){e+=z;var B=Math.abs(z);m+=B;o[0]+=B;k+=B;c+=B;n[0]+=B}var J=Math.min(g,b),H=Math.min(o[1],n[1]),v=Math.min(J,H),A=Math.max(g,b),y=Math.max(o[1],n[1]),s=Math.max(A,y);if(s>h){h=s}if(v<0){d+=v;var x=Math.abs(v);h+=x;o[1]+=x;g+=x;b+=x;n[1]+=x}if(F&&m=s){q=t;r=(s-l[t][0])/b[t];break}}return{segment:i[q],proportion:r,index:q}};this.compute=function(T,v,L,z,S,r,q,I){i=[];n=0;b=[];e=v[0]t[0]?t[0]+((1-x)*r)-y:t[2]+(x*r)+y,y:q==0?t[3]:t[3]>t[1]?t[1]+((1-x)*r)-y:t[3]+(x*r)+y,segmentInfo:v};return w}};jsPlumb.Endpoints.Dot=function(c){this.type="Dot";var b=this;c=c||{};this.radius=c.radius||10;this.defaultOffset=0.5*this.radius;this.defaultInnerRadius=this.radius/3;this.compute=function(h,e,k,g){var f=k.radius||b.radius,d=h[0]-f,i=h[1]-f;return[d,i,f*2,f*2,f]}};jsPlumb.Endpoints.Rectangle=function(c){this.type="Rectangle";var b=this;c=c||{};this.width=c.width||20;this.height=c.height||20;this.compute=function(i,f,l,h){var g=l.width||b.width,e=l.height||b.height,d=i[0]-(g/2),k=i[1]-(e/2);return[d,k,g,e]}};jsPlumb.Endpoints.Image=function(f){this.type="Image";jsPlumb.DOMElementComponent.apply(this,arguments);var k=this,e=false,d=f.width,c=f.height,h=null,b=f.endpoint;this.img=new Image();k.ready=false;this.img.onload=function(){k.ready=true;d=d||k.img.width;c=c||k.img.height;if(h){h(this)}};b.setImage=function(l,n){var m=l.constructor==String?l:l.src;h=n;k.img.src=l};b.setImage(f.src||f.url,f.onload);this.compute=function(n,l,o,m){k.anchorPoint=n;if(k.ready){return[n[0]-d/2,n[1]-c/2,d,c]}else{return[0,0,0,0]}};k.canvas=document.createElement("img"),e=false;k.canvas.style.margin=0;k.canvas.style.padding=0;k.canvas.style.outline=0;k.canvas.style.position="absolute";var g=f.cssClass?" "+f.cssClass:"";k.canvas.className=jsPlumb.endpointClass+g;if(d){k.canvas.setAttribute("width",d)}if(c){k.canvas.setAttribute("height",c)}jsPlumb.appendElement(k.canvas,f.parent);k.attachListeners(k.canvas,k);var i=function(o,n,m){if(!e){k.canvas.setAttribute("src",k.img.src);e=true}var l=k.anchorPoint[0]-(d/2),p=k.anchorPoint[1]-(c/2);jsPlumb.sizeCanvas(k.canvas,l,p,d,c)};this.paint=function(n,m,l){if(k.ready){i(n,m,l)}else{window.setTimeout(function(){k.paint(n,m,l)},200)}}};jsPlumb.Endpoints.Blank=function(c){var b=this;this.type="Blank";jsPlumb.DOMElementComponent.apply(this,arguments);this.compute=function(f,d,g,e){return[f[0],f[1],10,0]};b.canvas=document.createElement("div");b.canvas.style.display="block";b.canvas.style.width="1px";b.canvas.style.height="1px";b.canvas.style.background="transparent";b.canvas.style.position="absolute";b.canvas.className=b._jsPlumb.endpointClass;jsPlumb.appendElement(b.canvas,c.parent);this.paint=function(g,f,e){jsPlumb.sizeCanvas(b.canvas,g[0],g[1],g[2],g[3])}};jsPlumb.Endpoints.Triangle=function(b){this.type="Triangle";b=b||{};b.width=b.width||55;b.height=b.height||55;this.width=b.width;this.height=b.height;this.compute=function(h,e,k,g){var f=k.width||self.width,d=k.height||self.height,c=h[0]-(f/2),i=h[1]-(d/2);return[c,i,f,d]}};var a=function(d){var c=true,b=this;this.isAppendedAtTopLevel=true;this.connection=d.connection;this.loc=d.location==null?0.5:d.location;this.setVisible=function(e){c=e;b.connection.repaint()};this.isVisible=function(){return c};this.hide=function(){b.setVisible(false)};this.show=function(){b.setVisible(true)};this.incrementLocation=function(e){b.loc+=e;b.connection.repaint()};this.setLocation=function(e){b.loc=e;b.connection.repaint()};this.getLocation=function(){return b.loc}};jsPlumb.Overlays.Arrow=function(f){this.type="Arrow";a.apply(this,arguments);this.isAppendedAtTopLevel=false;f=f||{};var c=this;this.length=f.length||20;this.width=f.width||20;this.id=f.id;var e=(f.direction||1)<0?-1:1,d=f.paintStyle||{lineWidth:1},b=f.foldback||0.623;this.computeMaxSize=function(){return c.width*1.5};this.cleanup=function(){};this.draw=function(h,w,r){var l,s,g,m,k;if(c.loc==1){l=h.pointOnPath(c.loc);s=h.pointAlongPathFrom(c.loc,-1);g=jsPlumb.util.pointOnLine(l,s,c.length)}else{if(c.loc==0){g=h.pointOnPath(c.loc);s=h.pointAlongPathFrom(c.loc,1);l=jsPlumb.util.pointOnLine(g,s,c.length)}else{l=h.pointAlongPathFrom(c.loc,e*c.length/2),s=h.pointOnPath(c.loc),g=jsPlumb.util.pointOnLine(l,s,c.length)}}m=jsPlumb.util.perpendicularLineTo(l,g,c.width);k=jsPlumb.util.pointOnLine(l,g,b*c.length);var v=Math.min(l.x,m[0].x,m[1].x),p=Math.max(l.x,m[0].x,m[1].x),u=Math.min(l.y,m[0].y,m[1].y),o=Math.max(l.y,m[0].y,m[1].y);var n={hxy:l,tail:m,cxy:k},q=d.strokeStyle||w.strokeStyle,t=d.fillStyle||w.strokeStyle,i=d.lineWidth||w.lineWidth;c.paint(h,n,i,q,t,r);return[v,p,u,o]}};jsPlumb.Overlays.PlainArrow=function(c){c=c||{};var b=jsPlumb.extend(c,{foldback:1});jsPlumb.Overlays.Arrow.call(this,b);this.type="PlainArrow"};jsPlumb.Overlays.Diamond=function(d){d=d||{};var b=d.length||40,c=jsPlumb.extend(d,{length:b/2,foldback:2});jsPlumb.Overlays.Arrow.call(this,c);this.type="Diamond"};jsPlumb.Overlays.Label=function(h){this.type="Label";jsPlumb.DOMElementComponent.apply(this,arguments);a.apply(this,arguments);this.labelStyle=h.labelStyle||jsPlumb.Defaults.LabelStyle;this.id=h.id;this.cachedDimensions=null;var d=h.label||"",b=this,e=false,i=document.createElement("div"),f=null;i.style.position="absolute";var c=h._jsPlumb.overlayClass+" "+(b.labelStyle.cssClass?b.labelStyle.cssClass:h.cssClass?h.cssClass:"");i.className=c;jsPlumb.appendElement(i,h.connection.parent);jsPlumb.getId(i);b.attachListeners(i,b);b.canvas=i;var g=b.setVisible;b.setVisible=function(k){g(k);i.style.display=k?"block":"none"};this.getElement=function(){return i};this.cleanup=function(){if(i!=null){jsPlumb.CurrentLibrary.removeElement(i)}};this.setLabel=function(k){d=k;f=null;b.connection.repaint()};this.getLabel=function(){return d};this.paint=function(k,m,l){if(!e){k.appendDisplayElement(i);b.attachListeners(i,k);e=true}i.style.left=(l[0]+m.minx)+"px";i.style.top=(l[1]+m.miny)+"px"};this.getTextDimensions=function(l){if(typeof d=="function"){var k=d(b);i.innerHTML=k.replace(/\r\n/g,"
")}else{if(f==null){f=d;i.innerHTML=f.replace(/\r\n/g,"
")}}var n=jsPlumb.CurrentLibrary.getElementObject(i),m=jsPlumb.CurrentLibrary.getSize(n);return{width:m[0],height:m[1]}};this.computeMaxSize=function(k){var l=b.getTextDimensions(k);return l.width?Math.max(l.width,l.height)*1.5:0};this.draw=function(m,o,n){var q=b.getTextDimensions(m);if(q.width!=null){var p=m.pointOnPath(b.loc),l=p.x-(q.width/2),k=p.y-(q.height/2);b.paint(m,{minx:l,miny:k,td:q,cxy:p},n);return[l,l+q.width,k,k+q.height]}else{return[0,0,0,0]}};this.reattachListeners=function(){if(i){b.reattachListenersForElement(i,b)}}};jsPlumb.Overlays.GuideLines=function(){var b=this;b.length=50;b.lineWidth=5;this.type="GuideLines";a.apply(this,arguments);jsPlumb.jsPlumbUIComponent.apply(this,arguments);this.draw=function(d,k,i){var h=d.pointAlongPathFrom(b.loc,b.length/2),g=d.pointOnPath(b.loc),f=jsPlumb.util.pointOnLine(h,g,b.length),e=jsPlumb.util.perpendicularLineTo(h,f,40),c=jsPlumb.util.perpendicularLineTo(f,h,20);b.paint(d,[h,f,e,c],b.lineWidth,"red",null,i);return[Math.min(h.x,f.x),Math.min(h.y,f.y),Math.max(h.x,f.x),Math.max(h.y,f.y)]};this.computeMaxSize=function(){return 50};this.cleanup=function(){}}})();(function(){var c=function(e,g,d,f){this.m=(f-g)/(d-e);this.b=-1*((this.m*e)-g);this.rectIntersect=function(q,p,s,o){var n=[];var k=(p-this.b)/this.m;if(k>=q&&k<=(q+s)){n.push([k,(this.m*k)+this.b])}var t=(this.m*(q+s))+this.b;if(t>=p&&t<=(p+o)){n.push([(t-this.b)/this.m,t])}var k=((p+o)-this.b)/this.m;if(k>=q&&k<=(q+s)){n.push([k,(this.m*k)+this.b])}var t=(this.m*q)+this.b;if(t>=p&&t<=(p+o)){n.push([(t-this.b)/this.m,t])}if(n.length==2){var m=(n[0][0]+n[1][0])/2,l=(n[0][1]+n[1][1])/2;n.push([m,l]);var i=m<=q+(s/2)?-1:1,r=l<=p+(o/2)?-1:1;n.push([i,r]);return n}return null}},a=function(e,g,d,f){if(e<=d&&f<=g){return 1}else{if(e<=d&&g<=f){return 2}else{if(d<=e&&f>=g){return 3}}}return 4},b=function(g,f,i,e,h,m,l,d,k){if(d<=k){return[g,f]}if(i==1){if(e[3]<=0&&h[3]>=1){return[g+(e[2]<0.5?-1*m:m),f]}else{if(e[2]>=1&&h[2]<=0){return[g,f+(e[3]<0.5?-1*l:l)]}else{return[g+(-1*m),f+(-1*l)]}}}else{if(i==2){if(e[3]>=1&&h[3]<=0){return[g+(e[2]<0.5?-1*m:m),f]}else{if(e[2]>=1&&h[2]<=0){return[g,f+(e[3]<0.5?-1*l:l)]}else{return[g+(1*m),f+(-1*l)]}}}else{if(i==3){if(e[3]>=1&&h[3]<=0){return[g+(e[2]<0.5?-1*m:m),f]}else{if(e[2]<=0&&h[2]>=1){return[g,f+(e[3]<0.5?-1*l:l)]}else{return[g+(-1*m),f+(-1*l)]}}}else{if(i==4){if(e[3]<=0&&h[3]>=1){return[g+(e[2]<0.5?-1*m:m),f]}else{if(e[2]<=0&&h[2]>=1){return[g,f+(e[3]<0.5?-1*l:l)]}else{return[g+(1*m),f+(-1*l)]}}}}}}};jsPlumb.Connectors.StateMachine=function(l){var s=this,n=null,o,m,g,e,p=[],d=l.curviness||10,k=l.margin||5,q=l.proximityLimit||80,f=l.orientation&&l.orientation=="clockwise",i=l.loopbackRadius||25,h=false;this.type="StateMachine";l=l||{};this.compute=function(ab,F,U,G,aa,u,t,S){var O=Math.abs(ab[0]-F[0]),W=Math.abs(ab[1]-F[1]),Q=0.45*O,Z=0.45*W;O*=1.9;W*=1.9;t=t||1;var M=Math.min(ab[0],F[0])-Q,K=Math.min(ab[1],F[1])-Z;if(U.elementId!=G.elementId){h=false;o=ab[0]0&&v<1){v=1-v}var w=(v*2*Math.PI)+(Math.PI/2),u=n[4]+(n[6]*Math.cos(w)),t=n[5]+(n[6]*Math.sin(w));return{x:u,y:t}}else{return jsBezier.pointOnCurve(r(),v)}};this.gradientAtPoint=function(t){if(h){return Math.atan(t*2*Math.PI)}else{return jsBezier.gradientAtPoint(r(),t)}};this.pointAlongPathFrom=function(v,z){if(h){if(v>0&&v<1){v=1-v}var w=2*Math.PI*n[6],y=z/w*2*Math.PI,x=(v*2*Math.PI)-y+(Math.PI/2),u=n[4]+(n[6]*Math.cos(x)),t=n[5]+(n[6]*Math.sin(x));return{x:u,y:t}}return jsBezier.pointAlongCurveFrom(r(),v,z)}};jsPlumb.Connectors.canvas.StateMachine=function(f){f=f||{};var d=this,g=f.drawGuideline||true,e=f.avoidSelector;jsPlumb.Connectors.StateMachine.apply(this,arguments);jsPlumb.CanvasConnector.apply(this,arguments);this._paint=function(l){if(l.length==10){d.ctx.beginPath();d.ctx.moveTo(l[4],l[5]);d.ctx.quadraticCurveTo(l[8],l[9],l[6],l[7]);d.ctx.stroke()}else{d.ctx.save();d.ctx.beginPath();var k=0,i=2*Math.PI,h=l[7];d.ctx.arc(l[4],l[5],l[6],0,i,h);d.ctx.stroke();d.ctx.closePath();d.ctx.restore()}};this.createGradient=function(i,h){return h.createLinearGradient(i[4],i[5],i[6],i[7])}};jsPlumb.Connectors.svg.StateMachine=function(){var d=this;jsPlumb.Connectors.StateMachine.apply(this,arguments);jsPlumb.SvgConnector.apply(this,arguments);this.getPath=function(e){if(e.length==10){return"M "+e[4]+" "+e[5]+" C "+e[8]+" "+e[9]+" "+e[8]+" "+e[9]+" "+e[6]+" "+e[7]}else{return"M"+(e[8]+4)+" "+e[9]+" A "+e[6]+" "+e[6]+" 0 1,0 "+(e[8]-4)+" "+e[9]}}};jsPlumb.Connectors.vml.StateMachine=function(){jsPlumb.Connectors.StateMachine.apply(this,arguments);jsPlumb.VmlConnector.apply(this,arguments);var d=jsPlumb.vml.convertValue;this.getPath=function(k){if(k.length==10){return"m"+d(k[4])+","+d(k[5])+" c"+d(k[8])+","+d(k[9])+","+d(k[8])+","+d(k[9])+","+d(k[6])+","+d(k[7])+" e"}else{var h=d(k[8]-k[6]),g=d(k[9]-(2*k[6])),f=h+d(2*k[6]),e=g+d(2*k[6]),l=h+","+g+","+f+","+e;var i="ar "+l+","+d(k[8])+","+d(k[9])+","+d(k[8])+","+d(k[9])+" e";return i}}}})();(function(){var h={"stroke-linejoin":"joinstyle",joinstyle:"joinstyle",endcap:"endcap",miterlimit:"miterlimit"};if(document.createStyleSheet){document.createStyleSheet().addRule(".jsplumb_vml","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:textbox","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:oval","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:rect","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:stroke","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:shape","behavior:url(#default#VML);position:absolute;");document.namespaces.add("jsplumb","urn:schemas-microsoft-com:vml")}jsPlumb.vml={};var b=1000,c=function(p,q){for(var n in q){p[n]=q[n]}},m=function(n,q,r){r=r||{};var p=document.createElement("jsplumb:"+n);p.className=(r["class"]?r["class"]+" ":"")+"jsplumb_vml";l(p,q);c(p,r);return p},l=function(p,n){p.style.left=n[0]+"px";p.style.top=n[1]+"px";p.style.width=n[2]+"px";p.style.height=n[3]+"px";p.style.position="absolute"},g=jsPlumb.vml.convertValue=function(n){return Math.floor(n*b)},d=function(q,o,p,n){if("transparent"===o){n.setOpacity(p,"0.0")}else{n.setOpacity(p,"1.0")}},f=function(r,n,u){var q={};if(n.strokeStyle){q.stroked="true";var v=jsPlumb.util.convertStyle(n.strokeStyle,true);q.strokecolor=v;d(q,v,"stroke",u);q.strokeweight=n.lineWidth+"px"}else{q.stroked="false"}if(n.fillStyle){q.filled="true";var o=jsPlumb.util.convertStyle(n.fillStyle,true);q.fillcolor=o;d(q,o,"fill",u)}else{q.filled="false"}if(n.dashstyle){if(u.strokeNode==null){u.strokeNode=m("stroke",[0,0,0,0],{dashstyle:n.dashstyle});r.appendChild(u.strokeNode)}else{u.strokeNode.dashstyle=n.dashstyle}}else{if(n["stroke-dasharray"]&&n.lineWidth){var w=n["stroke-dasharray"].indexOf(",")==-1?" ":",",s=n["stroke-dasharray"].split(w),p="";for(var t=0;t0&&B>0&&t=t&&v[2]<=B&&v[3]>=B)){return true}}var z=p.canvas.getContext("2d").getImageData(parseInt(t),parseInt(B),1,1);return z.data[0]!=0||z.data[1]!=0||z.data[2]!=0||z.data[3]!=0}return false};var o=false,n=false,s=null,r=false,q=function(u,t){return u!=null&&i(u,t)};this.mousemove=function(w){var y=m(w),v=f(w),u=document.elementFromPoint(v[0],v[1]),x=q(u,"_jsPlumb_overlay");var t=d==null&&(q(u,"_jsPlumb_endpoint")||q(u,"_jsPlumb_connector"));if(!o&&t&&p._over(w)){o=true;p.fire("mouseenter",p,w);return true}else{if(o&&(!p._over(w)||!t)&&!x){o=false;p.fire("mouseexit",p,w)}}p.fire("mousemove",p,w)};this.click=function(t){if(o&&p._over(t)&&!r){p.fire("click",p,t)}r=false};this.dblclick=function(t){if(o&&p._over(t)&&!r){p.fire("dblclick",p,t)}r=false};this.mousedown=function(t){if(p._over(t)&&!n){n=true;s=l(a(p.canvas));p.fire("mousedown",p,t)}};this.mouseup=function(t){n=false;p.fire("mouseup",p,t)}};var c=function(o){var n=document.createElement("canvas");jsPlumb.appendElement(n,o.parent);n.style.position="absolute";if(o["class"]){n.className=o["class"]}o._jsPlumb.getId(n,o.uuid);if(o.tooltip){n.setAttribute("label",o.tooltip)}return n};var h=jsPlumb.CanvasConnector=function(r){k.apply(this,arguments);var n=function(v,t){o.ctx.save();jsPlumb.extend(o.ctx,t);if(t.gradient){var u=o.createGradient(v,o.ctx);for(var s=0;s0?c[0].tagName:null},getUIPosition:function(c){if(c.length==1){return{left:c[0].pageX,top:c[0].pageY}}else{var d=c[1],b=d.offset;return b||d.absolutePosition}},hasClass:function(c,b){return c.hasClass(b)},initDraggable:function(c,b){b=b||{};b.helper=null;b.scope=b.scope||jsPlumb.Defaults.Scope;c.draggable(b)},initDroppable:function(c,b){b.scope=b.scope||jsPlumb.Defaults.Scope;c.droppable(b)},isAlreadyDraggable:function(b){b=jsPlumb.CurrentLibrary.getElementObject(b);return b.hasClass("ui-draggable")},isDragSupported:function(c,b){return c.draggable},isDropSupported:function(c,b){return c.droppable},removeClass:function(c,b){c=jsPlumb.CurrentLibrary.getElementObject(c);try{if(c[0].className.constructor==SVGAnimatedString){jsPlumb.util.svg.removeClass(c[0],b)}}catch(d){}c.removeClass(b)},removeElement:function(b,c){jsPlumb.CurrentLibrary.getElementObject(b).remove()},setAttribute:function(c,d,b){c.attr(d,b)},setDraggable:function(c,b){c.draggable("option","disabled",!b)},setDragScope:function(c,b){c.draggable("option","scope",b)},setOffset:function(b,c){jsPlumb.CurrentLibrary.getElementObject(b).offset(c)},trigger:function(d,e,b){var c=jQuery._data(jsPlumb.CurrentLibrary.getElementObject(d)[0],"handle");c(b)},unbind:function(b,c,d){b=jsPlumb.CurrentLibrary.getElementObject(b);b.unbind(c,d)}};a(document).ready(jsPlumb.init)})(jQuery);(function(){if("undefined"==typeof Math.sgn){Math.sgn=function(l){return 0==l?0:0q?q=s:sl.location){l.location=0}return i(m,l.location)},nearestPointOnCurve:function(m,l){var n=h(m,l);return{point:a(l,l.length-1,n.location,null,null),location:n.location}},pointOnCurve:c,pointAlongCurveFrom:function(m,l,n){return k(m,l,n).point},perpendicularToCurveAt:function(m,l,n,o){l=k(m,l,null==o?0:o);m=i(m,l.location);o=Math.atan(-1/m);m=n/2*Math.sin(o);n=n/2*Math.cos(o);return[{x:l.point.x+n,y:l.point.y+m},{x:l.point.x-n,y:l.point.y-m}]}}})(); \ No newline at end of file diff --git a/archive/1.3.4/jquery.jsPlumb-1.3.4-all.js b/archive/1.3.4/jquery.jsPlumb-1.3.4-all.js new file mode 100644 index 000000000..8fdd394b3 --- /dev/null +++ b/archive/1.3.4/jquery.jsPlumb-1.3.4-all.js @@ -0,0 +1,7957 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the jsPlumb core code. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + /** + * Class:jsPlumb + * The jsPlumb engine, registered as a static object in the window. This object contains all of the methods you will use to + * create and maintain Connections and Endpoints. + */ + + var canvasAvailable = !!document.createElement('canvas').getContext, + svgAvailable = !!window.SVGAngle || document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"), + // TODO what is a good test for VML availability? aside from just assuming its there because nothing else is. + vmlAvailable = !(canvasAvailable | svgAvailable); + + // finds the index of some value in an array, using a deep equals test. + var _findIndex = function(a, v, b, s) { + var _eq = function(o1, o2) { + if (o1 === o2) return true; + else if (typeof o1 == "object" && typeof o2 == "object") { + var same = true; + for ( var propertyName in o1) { + if (!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for ( var propertyName in o2) { + if (!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for ( var i = +b || 0, l = a.length; i < l; i++) { + if (_eq(a[i], v)) + return i; + } + return -1; + }, + _findWithFunction = function(a, f) { + for (var i = 0; i < a.length; i++) if (f(a[i])) return i; + return -1; + }, + _removeWithFunction = function(a, f) { + var idx = _findWithFunction(a, f); + if (idx > -1) a.splice(idx, 1); + return idx != -1; + }; + + + // for those browsers that dont have it. they still don't have it! but at least they won't crash. + if (!window.console) + window.console = { time:function(){}, timeEnd:function(){}, group:function(){}, groupEnd:function(){}, log:function(){} }; + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = [], map[key] = l; + } + l.push(value); + return l; + }, + _connectionBeingDragged = null, + _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }, + _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }, + _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }, + _logEnabled = true, + _log = function() { + if (_logEnabled && typeof console != "undefined") { + try { + var msg = arguments[arguments.length - 1]; + console.log(msg); + } + catch (e) {} + } + }, + _group = function(g) { + if (_logEnabled && typeof console != "undefined") console.group(g); + }, + _groupEnd = function(g) { + if (_logEnabled && typeof console != "undefined") console.groupEnd(g); + }, + _time = function(t) { + if (_logEnabled && typeof console != "undefined") console.time(t); + }, + _timeEnd = function(t) { + if (_logEnabled && typeof console != "undefined") console.timeEnd(t); + }; + + /** + * EventGenerator + * Superclass for objects that generate events - jsPlumb extends this, as does jsPlumbUIComponent, which all the UI elements extend. + */ + EventGenerator = function() { + var _listeners = {}, self = this; + + // this is a list of events that should re-throw any errors that occur during their dispatch. as of 1.3.0 this is private to + // jsPlumb, but it seems feasible that people might want to manipulate this list. the thinking is that we don't want event + // listeners to bring down jsPlumb - or do we. i can't make up my mind about this, but i know i want to hear about it if the "ready" + // event fails, because then my page has most likely not initialised. so i have this halfway-house solution. it will be interesting + // to hear what other people think. + var eventsToDieOn = [ "ready" ]; + + /* + * Binds a listener to an event. + * + * Parameters: + * event - name of the event to bind to. + * listener - function to execute. + */ + this.bind = function(event, listener) { + _addToList(_listeners, event, listener); + }; + /* + * Fires an update for the given event. + * + * Parameters: + * event - event to fire + * value - value to pass to the event listener(s). + * o riginalEvent - the original event from the browser + */ + this.fire = function(event, value, originalEvent) { + if (_listeners[event]) { + for ( var i = 0; i < _listeners[event].length; i++) { + // doing it this way rather than catching and then possibly re-throwing means that an error propagated by this + // method will have the whole call stack available in the debugger. + if (_findIndex(eventsToDieOn, event) != -1) + _listeners[event][i](value, originalEvent); + else { + // for events we don't want to die on, catch and log. + try { + _listeners[event][i](value, originalEvent); + } catch (e) { + _log("jsPlumb: fire failed for event " + event + " : " + e); + } + } + } + } + }; + /* + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + this.clearListeners = function(event) { + if (event) + delete _listeners[event]; + else { + delete _listeners; + _listeners = {}; + } + }; + + this.getListener = function(forEvent) { + return _listeners[forEvent]; + }; + }, + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + _timestamp = function() { return "" + (new Date()).getTime(); }, + + /* + * Class:jsPlumbUIComponent + * Abstract superclass for UI components Endpoint and Connection. Provides the abstraction of paintStyle/hoverPaintStyle, + * and also extends EventGenerator to provide the bind and fire methods. + */ + jsPlumbUIComponent = function(params) { + var self = this, a = arguments, _hover = false, parameters = params.parameters || {}, idPrefix = self.idPrefix, + id = idPrefix + (new Date()).getTime(); + self._jsPlumb = params["_jsPlumb"]; + self.getId = function() { return id; }; + self.tooltip = params.tooltip; + self.hoverClass = params.hoverClass; + + // all components can generate events + EventGenerator.apply(this); + // all components get this clone function. + // TODO issue 116 showed a problem with this - it seems 'a' that is in + // the clone function's scope is shared by all invocations of it, the classic + // JS closure problem. for now, jsPlumb does a version of this inline where + // it used to call clone. but it would be nice to find some time to look + // further at this. + this.clone = function() { + var o = new Object(); + self.constructor.apply(o, a); + return o; + }; + + this.getParameter = function(name) { return parameters[name]; }, + this.getParameters = function() { return parameters; }, + this.setParameter = function(name, value) { parameters[name] = value; }, + this.setParameters = function(p) { parameters = p; }, + this.overlayPlacements = [], + this.paintStyle = null, + this.hoverPaintStyle = null; + + // user can supply a beforeDetach callback, which will be executed before a detach + // is performed; returning false prevents the detach. + var beforeDetach = params.beforeDetach; + this.isDetachAllowed = function(connection) { + var r = true; + if (beforeDetach) { + try { + r = beforeDetach(connection); + } + catch (e) { _log("jsPlumb: beforeDetach callback failed", e); } + } + return r; + }; + + // user can supply a beforeDrop callback, which will be executed before a dropped + // connection is confirmed. user can return false to reject connection. + var beforeDrop = params.beforeDrop; + this.isDropAllowed = function(sourceId, targetId, scope) { + var r = self._jsPlumb.checkCondition("beforeDrop", { sourceId:sourceId, targetId:targetId, scope:scope }); + if (beforeDrop) { + try { + r = beforeDrop({ sourceId:sourceId, targetId:targetId, scope:scope }); + } + catch (e) { _log("jsPlumb: beforeDrop callback failed", e); } + } + return r; + }; + + // helper method to update the hover style whenever it, or paintStyle, changes. + // we use paintStyle as the foundation and merge hoverPaintStyle over the + // top. + var _updateHoverStyle = function() { + if (self.paintStyle && self.hoverPaintStyle) { + var mergedHoverStyle = {}; + jsPlumb.extend(mergedHoverStyle, self.paintStyle); + jsPlumb.extend(mergedHoverStyle, self.hoverPaintStyle); + delete self["hoverPaintStyle"]; + // we want the fillStyle of paintStyle to override a gradient, if possible. + if (mergedHoverStyle.gradient && self.paintStyle.fillStyle) + delete mergedHoverStyle["gradient"]; + self.hoverPaintStyle = mergedHoverStyle; + } + }; + + /* + * Sets the paint style and then repaints the element. + * + * Parameters: + * style - Style to use. + */ + this.setPaintStyle = function(style, doNotRepaint) { + self.paintStyle = style; + self.paintStyleInUse = self.paintStyle; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /* + * Sets the paint style to use when the mouse is hovering over the element. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the component will not be repainted. useful when setting things up initially. + */ + this.setHoverPaintStyle = function(style, doNotRepaint) { + self.hoverPaintStyle = style; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /* + * sets/unsets the hover state of this element. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + this.setHover = function(hover, ignoreAttachedElements, timestamp) { + // while dragging, we ignore these events. this keeps the UI from flashing and + // swishing and whatevering. + if (!self._jsPlumb.currentlyDragging) { + + _hover = hover; + if (self.hoverClass != null && self.canvas != null) { + if (hover) + jpcl.addClass(self.canvas, self.hoverClass); + else + jpcl.removeClass(self.canvas, self.hoverClass); + } + if (self.hoverPaintStyle != null) { + self.paintStyleInUse = hover ? self.hoverPaintStyle : self.paintStyle; + timestamp = timestamp || _timestamp(); + self.repaint({timestamp:timestamp, recalc:false}); + } + // get the list of other affected elements, if supported by this component. + // for a connection, its the endpoints. for an endpoint, its the connections! surprise. + if (self.getAttachedElements && !ignoreAttachedElements) + _updateAttachedElements(hover, _timestamp(), self); + } + }; + + this.isHover = function() { return _hover; }; + + var jpcl = jsPlumb.CurrentLibrary, + events = [ "click", "dblclick", "mouseenter", "mouseout", "mousemove", "mousedown", "mouseup" ], + eventFilters = { "mouseout":"mouseexit" }, + bindOne = function(o, c, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.bind(o, evt, function(ee) { + c.fire(filteredEvent, c, ee); + }); + }, + unbindOne = function(o, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.unbind(o, evt); + }; + + this.attachListeners = function(o, c) { + for (var i = 0; i < events.length; i++) { + bindOne(o, c, events[i]); + } + }; + + var _updateAttachedElements = function(state, timestamp, sourceElement) { + var affectedElements = self.getAttachedElements(); // implemented in subclasses + if (affectedElements) { + for (var i = 0; i < affectedElements.length; i++) { + if (!sourceElement || sourceElement != affectedElements[i]) + affectedElements[i].setHover(state, true, timestamp); // tell the attached elements not to inform their own attached elements. + } + } + }; + + this.reattachListenersForElement = function(o, c) { + for (var i = 0; i < events.length; i++) + unbindOne(o, events[i]); + self.attachListeners(o, c); + }; + }; + + var jsPlumbInstance = function(_defaults) { + + /* + * Property: Defaults + * + * These are the default settings for jsPlumb. They are what will be used if you do not supply specific pieces of information + * to the various API calls. A convenient way to implement your own look and feel can be to override these defaults + * by including a script somewhere after the jsPlumb include, but before you make any calls to jsPlumb. + * + * Properties: + * - *Anchor* The default anchor to use for all connections (both source and target). Default is "BottomCenter". + * - *Anchors* The default anchors to use ([source, target]) for all connections. Defaults are ["BottomCenter", "BottomCenter"]. + * - *Connector* The default connector definition to use for all connections. Default is "Bezier". + * - *Container* Optional selector or element id that instructs jsPlumb to append elements it creates to a specific element. + * - *DragOptions* The default drag options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *DropOptions* The default drop options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *Endpoint* The default endpoint definition to use for all connections (both source and target). Default is "Dot". + * - *Endpoints* The default endpoint definitions ([ source, target ]) to use for all connections. Defaults are ["Dot", "Dot"]. + * - *EndpointStyle* The default style definition to use for all endpoints. Default is fillStyle:"#456". + * - *EndpointStyles* The default style definitions ([ source, target ]) to use for all endpoints. Defaults are empty. + * - *EndpointHoverStyle* The default hover style definition to use for all endpoints. Default is null. + * - *EndpointHoverStyles* The default hover style definitions ([ source, target ]) to use for all endpoints. Defaults are null. + * - *HoverPaintStyle* The default hover style definition to use for all connections. Defaults are null. + * - *LabelStyle* The default style to use for label overlays on connections. + * - *LogEnabled* Whether or not the jsPlumb log is enabled. defaults to false. + * - *Overlays* The default overlay definitions. Defaults to an empty list. + * - *MaxConnections* The default maximum number of connections for an Endpoint. Defaults to 1. + * - *MouseEventsEnabled* Whether or not mouse events are enabled when using the canvas renderer. Defaults to true. + * The idea of this is just to give people a way to prevent all the mouse listeners from activating if they know they won't need mouse events. + * - *PaintStyle* The default paint style for a connection. Default is line width of 8 pixels, with color "#456". + * - *RenderMode* What mode to use to paint with. If you're on IE<9, you don't really get to choose this. You'll just get VML. Otherwise, the jsPlumb default is to use Canvas elements. + * - *Scope* The default "scope" to use for connections. Scope lets you assign connections to different categories. + */ + this.Defaults = { + Anchor : "BottomCenter", + Anchors : [ null, null ], + ConnectionsDetachable : true, + Connector : "Bezier", + Container : null, + DragOptions : { }, + DropOptions : { }, + Endpoint : "Dot", + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : "#456" }, + EndpointStyles : [ null, null ], + EndpointHoverStyle : null, + EndpointHoverStyles : [ null, null ], + HoverPaintStyle : null, + LabelStyle : { color : "black" }, + LogEnabled : false, + Overlays : [ ], + MaxConnections : 1, + MouseEventsEnabled : true, + PaintStyle : { lineWidth : 8, strokeStyle : "#456" }, + //Reattach:false, + RenderMode : "svg", + Scope : "jsPlumb_DefaultScope" + }; + if (_defaults) jsPlumb.extend(this.Defaults, _defaults); + + this.logEnabled = this.Defaults.LogEnabled; + + EventGenerator.apply(this); + var _bb = this.bind; + this.bind = function(event, fn) { + if ("ready" === event && initialized) fn(); + else _bb(event, fn); + }; + var _currentInstance = this, + log = null, + repaintFunction = function() { + jsPlumb.repaintEverything(); + }, + automaticRepaint = true, + repaintEverything = function() { + if (automaticRepaint) + repaintFunction(); + }, + resizeTimer = null, + initialized = false, + connectionsByScope = {}, + /** + * map of element id -> endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + endpointsByElement = {}, + endpointsByUUID = {}, + offsets = {}, + offsetTimestamps = {}, + floatingConnections = {}, + draggableStates = {}, + _mouseEventsEnabled = this.Defaults.MouseEventsEnabled, + canvasList = [], + sizes = [], + //listeners = {}, // a map: keys are event types, values are lists of listeners. + DEFAULT_SCOPE = this.Defaults.Scope, + renderMode = null, // will be set in init() + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }, + + /** + * appends an element to some other element, which is calculated as follows: + * + * 1. if _currentInstance.Defaults.Container exists, use that element. + * 2. if the 'parent' parameter exists, use that. + * 3. otherwise just use the document body. + * + */ + _appendElement = function(el, parent) { + if (_currentInstance.Defaults.Container) + jsPlumb.CurrentLibrary.appendElement(el, _currentInstance.Defaults.Container); + else if (!parent) + document.body.appendChild(el); + else + jsPlumb.CurrentLibrary.appendElement(el, parent); + }, + + _curIdStamp = 1, + _idstamp = function() { return "" + _curIdStamp++; }, + + /** + * YUI, for some reason, put the result of a Y.all call into an object that contains + * a '_nodes' array, instead of handing back an array-like object like the other + * libraries do. + */ + _convertYUICollection = function(c) { + return c._nodes ? c._nodes : c; + }, + + /** + * Draws an endpoint and its connections. this is the main entry point into drawing connections as well + * as endpoints, since jsPlumb is endpoint-centric under the hood. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + _draw = function(element, ui, timestamp) { + var id = _getAttribute(element, "id"); + _currentInstance.anchorManager.redraw(id, ui, timestamp); + }, + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }, + + /** + * gets an Endpoint by uuid. + */ + _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }, + + /** + * inits a draggable if it's not already initialised. + */ + _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? false : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element) && !jsPlumb.CurrentLibrary.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + draggableStates[_getId(element)] = true; + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options, false); + } + } + }, + + /* + * prepares a final params object that can be passed to _newConnection, taking into account defaults, events, etc. + */ + _prepareConnectionParams = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + // copy in any connectorOverlays that were specified on the source endpoint. + // it doesnt copy target endpoint overlays. i'm not sure if we want it to or not. + if (_p.sourceEndpoint && _p.sourceEndpoint.connectorOverlays) { + _p.overlays = _p.overlays || []; + for (var i = 0; i < _p.sourceEndpoint.connectorOverlays.length; i++) { + _p.overlays.push(_p.sourceEndpoint.connectorOverlays[i]); + } + } + + // tooltip. params.tooltip takes precedence, then sourceEndpoint.connectorTooltip. + _p.tooltip = params.tooltip; + if (!_p.tooltip && _p.sourceEndpoint && _p.sourceEndpoint.connectorTooltip) + _p.tooltip = _p.sourceEndpoint.connectorTooltip; + + if (_p.target && !_p.target.endpoint) { + var tid = _getId(_p.target), + tep =_targetEndpointDefinitions[tid]; + + var overrideOne = function(singlePropertyName, pluralPropertyName, tepProperty, tep) { + if (tep[tepProperty]) { + if (_p[pluralPropertyName]) _p[pluralPropertyName][1] = tep[tepProperty]; + else if (_p[singlePropertyName]) { + _p[pluralPropertyName] = [ _p[singlePropertyName], tep[tepProperty] ]; + _p[singlePropertyName] = null; + } + else _p[pluralPropertyName] = [ null, tep[tepProperty] ]; + } + }; + + if (tep) { + overrideOne("endpoint", "endpoints", "endpoint", tep); + overrideOne("endpointStyle", "endpointStyles", "paintStyle", tep); + overrideOne("endpointHoverStyle", "endpointHoverStyles", "hoverPaintStyle", tep); + } + } + + // dynamic anchors. backwards compatibility here: from 1.2.6 onwards you don't need to specify "dynamicAnchors". the fact that some anchor consists + // of multiple definitions is enough to tell jsPlumb you want it to be dynamic. + if (_p.dynamicAnchors) { + // these can either be an array of anchor coords, which we will use for both source and target, or an object with {source:[anchors], target:[anchors]}, in which + // case we will use a different set for each element. + var a = _p.dynamicAnchors.constructor == Array, + sourceId = _p.sourceEndpoint ? _p.sourceEndpoint.elementId : _getId(_p.source), + targetId = _p.targetEndpoint ? _p.targetEndpoint.elementId : _getId(_p.target), + sa = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors, sourceId, _currentInstance)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.source, sourceId, _currentInstance)), + ta = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors, targetId, _currentInstance)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.target, targetId, _currentInstance)); + _p.anchors = [sa,ta]; + } + return _p; + }, + + _newConnection = function(params) { + var connectionFunc = jsPlumb.Defaults.ConnectionType || jsPlumb.getDefaultConnectionType(), + endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint, + parent = jsPlumb.CurrentLibrary.getParent; + + if (params.container) + params["parent"] = params.container; + else { + if (params.sourceEndpoint) + params["parent"] = params.sourceEndpoint.parent; + else if (params.source.constructor == endpointFunc) + params["parent"] = params.source.parent; + else params["parent"] = parent(params.source); + } + + params["_jsPlumb"] = _currentInstance; + var con = new connectionFunc(params); + con.id = "con_" + _idstamp(); + _eventFireProxy("click", "click", con); + _eventFireProxy("dblclick", "dblclick", con); + return con; + }, + + /** + * adds the connection to the backing model, fires an event if necessary and then redraws + */ + _finaliseConnection = function(jpc, params) { + params = params || {}; + // add to list of connections (by scope). + if (!jpc.suspendedEndpoint) + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + if (!params.doNotFireConnectionEvent && params.fireEvent !== false) { + _currentInstance.fire("jsPlumbConnection", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + } + // always inform the anchor manager + // except that if jpc has a suspended endpoint it's not true to say the + // connection is new; it has just (possibly) moved. the question is whether + // to make that call here or in the anchor manager. i think perhaps here. + _currentInstance.anchorManager.newConnection(jpc); + // force a paint + _draw(jpc.source); + }, + + _eventFireProxy = function(event, proxyEvent, obj) { + obj.bind(event, function(originalObject, originalEvent) { + _currentInstance.fire(proxyEvent, obj, originalEvent); + }); + }, + + /** + * for the given endpoint params, returns an appropriate parent element for the UI elements that will be added. + * this function is used by _newEndpoint (directly below), and also in the makeSource function in jsPlumb. + * + * the logic is to first look for a "container" member of params, and pass that back if found. otherwise we + * handoff to the 'getParent' function in the current library. + */ + _getParentFromParams = function(params) { + if (params.container) + return params.container; + else { + var tag = jsPlumb.CurrentLibrary.getTagName(params.source), + p = jsPlumb.CurrentLibrary.getParent(params.source); + if (tag.toLowerCase() === "td") + return jsPlumb.CurrentLibrary.getParent(p); + else return p; + } + }, + + /** + factory method to prepare a new endpoint. this should always be used instead of creating Endpoints + manually, since this method attaches event listeners and an id. + */ + _newEndpoint = function(params) { + var endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint; + params.parent = _getParentFromParams(params); + params["_jsPlumb"] = _currentInstance; + var ep = new endpointFunc(params); + ep.id = "ep_" + _idstamp(); + _eventFireProxy("click", "endpointClick", ep); + _eventFireProxy("dblclick", "endpointDblClick", ep); + return ep; + }, + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + _operation = function(elId, func, endpointFunc) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + if (endpointFunc) endpointFunc(endpoints[i]); + } + } + }, + /** + * perform an operation on all elements. + */ + _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }, + + /** + * helper to remove an element from the DOM. + */ + _removeElement = function(element, parent) { + if (element != null && element.parentNode != null) { + element.parentNode.removeChild(element); + } + }, + /** + * helper to remove a list of elements from the DOM. + */ + _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }, + /** + * helper method to remove an item from a list. + */ + _removeFromList = function(map, key, value) { + if (key != null) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete (l[i]); + l.splice(i, 1); + return true; + } + } + } + return false; + }, + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }, + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + _setVisible = function(el, state, alsoChangeEndpoints) { + state = state === "block"; + var endpointFunc = null; + if (alsoChangeEndpoints) { + if (state) endpointFunc = function(ep) { + ep.setVisible(true, true, true); + }; + else endpointFunc = function(ep) { + ep.setVisible(false, true, true); + }; + } + var id = _getAttribute(el, "id"); + _operation(id, function(jpc) { + if (state && alsoChangeEndpoints) { + // this test is necessary because this functionality is new, and i wanted to maintain backwards compatibility. + // this block will only set a connection to be visible if the other endpoint in the connection is also visible. + var oidx = jpc.sourceId === id ? 1 : 0; + if (jpc.endpoints[oidx].isVisible()) jpc.setVisible(true); + } + else // the default behaviour for show, and what always happens for hide, is to just set the visibility without getting clever. + jpc.setVisible(state); + }, endpointFunc); + }, + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? false : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }, + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + _toggleVisible = function(elId, changeEndpoints) { + var endpointFunc = null; + if (changeEndpoints) { + endpointFunc = function(ep) { + var state = ep.isVisible(); + ep.setVisible(!state); + }; + } + _operation(elId, function(jpc) { + var state = jpc.isVisible(); + jpc.setVisible(!state); + }, endpointFunc); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }, + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return offsets[elId]; + } + if (recalc || !offset) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + if (sizes[elId] == null) { + var s = _getElementObject(elId); + if (s != null) + sizes[elId] = _getSize(s); + } + } + + if(offsets[elId] && !offsets[elId].right) { + offsets[elId].right = offsets[elId].left + sizes[elId][0]; + offsets[elId].bottom = offsets[elId].top + sizes[elId][1]; + offsets[elId].width = sizes[elId][0]; + offsets[elId].height = sizes[elId][1]; + offsets[elId].centerx = offsets[elId].left + (offsets[elId].width / 2); + offsets[elId].centery = offsets[elId].top + (offsets[elId].height / 2); + } + return offsets[elId]; + }, + + _getCachedData = function(elId) { + var o = offsets[elId]; + if (!o) o = _updateOffset(elId); + return {o:o, s:sizes[elId]}; + }, + +/** + * gets an id for the given element, creating and setting one if + * necessary. + */ + _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else + id = "jsPlumb_" + _idstamp(); + _setAttribute(ele, "id", id); + } + return id; + }, + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'jsPlumb function failed : ' + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'wrapped function failed : ' + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = "_jsPlumb_connector"; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = "_jsPlumb_endpoint"; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = "_jsPlumb_overlay"; + + this.Anchors = {}; + + this.Connectors = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Endpoints = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Overlays = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + +// ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***************************************** + /* + * Function: bind + * Bind to an event on jsPlumb. + * + * Parameters: + * event - the event to bind. Available events on jsPlumb are: + * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback. + * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback. + * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback. + * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback. + * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback. + * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback. + * + * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event. + */ + + /* + * Function: clearListeners + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + +// *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *********************************************************** + + /* + Function: addClass + + Helper method to abstract out differences in setting css classes on the different renderer types. + */ + this.addClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.addClass(el, clazz); + }; + + /* + Function: removeClass + + Helper method to abstract out differences in setting css classes on the different renderer types. + */ + this.removeClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.removeClass(el, clazz); + }; + + /* + Function: hasClass + + Helper method to abstract out differences in testing for css classes on the different renderer types. + */ + this.hasClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.hasClass(el, clazz); + }; + + /* + Function: addEndpoint + + Adds an to a given element or elements. + + Parameters: + + el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + params - Object containing Endpoint constructor arguments. For more information, see . + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some + shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in + this object are anything that 'params' can contain. See . + + Returns: + The newly created , if el referred to a single element. Otherwise, an array of newly created s. + + See Also: + + */ + this.addEndpoint = function(el, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + //p.scope="_jsPlumb_DefaultScope"; + //p.scope="fff"; + // YUI wrapper + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + var _el = _getElementObject(inputs[i]), id = _getId(_el); + p.source = _el; + //p.scope="f"; + _updateOffset({ elId : id }); + var e = _newEndpoint(p); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + results.push(e); + } + + return results.length == 1 ? results[0] : results; + }; + + /* + Function: addEndpoints + Adds a list of s to a given element or elements. + + Parameters: + target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See 's constructor documentation. + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created s, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(el, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams); + if (e.constructor == Array) + Array.prototype.push.apply(results, e); + else results.push(e); + } + return results; + }; + + /* + Function: animate + This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating + the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // onComplete repaints, just to make sure everything looks good at the end of the animation. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /** + * checks for a listener for the given condition, executing it if found, passing in the given value. + * condition listeners would have been attached using "bind" (which is, you could argue, now overloaded, since + * firing click events etc is a bit different to what this does). i thought about adding a "bindCondition" + * or something, but decided against it, for the sake of simplicity. jsPlumb will never fire one of these + * condition events anyway. + */ + this.checkCondition = function(conditionName, value) { + var l = _currentInstance.getListener(conditionName); + var r = true; + if (l && l.length > 0) { + try { + for (var i = 0 ; i < l.length; i++) { + r = r && l[i](value); + } + } + catch (e) { + _log(_currentInstance, "cannot check condition [" + conditionName + "]" + e); + } + } + return r; + }; + + /* + Function: connect + Establishes a between two elements (or s, which are themselves registered to elements). + + Parameters: + params - Object containing constructor arguments for the Connection. See 's constructor documentation. + referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of + Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection. + + Returns: + The newly created . + */ + this.connect = function(params, referenceParams) { + // prepare a final set of parameters to create connection with + var _p = _prepareConnectionParams(params, referenceParams); + // TODO probably a nicer return value if the connection was not made. _prepareConnectionParams + // will return null (and log something) if either endpoint was full. what would be nicer is to + // create a dedicated 'error' object. + if (_p) { + // a connect call will delete its created endpoints on detach, unless otherwise specified. + // this is because the endpoints belong to this connection only, and are no use to + // anyone else, so they hang around like a bad smell. + if (_p.deleteEndpointsOnDetach == null) + _p.deleteEndpointsOnDetach = true; + + // create the connection. it is not yet registered + var jpc = _newConnection(_p); + // now add it the model, fire an event, and redraw + _finaliseConnection(jpc, _p); + return jpc; + } + }; + + /* + Function: deleteEndpoint + Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too) + + Parameters: + object - either an object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElement(endpoint.canvas, endpoint.parent); + // remove from endpointsbyElement + // DEPRECATED. SHOULD NOT BE NECESSARY ONCE THE ANCHOR MANAGER IS WORKING PROPERLY. + _currentInstance.anchorManager.deleteEndpoint(endpoint); + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + delete endpoint; + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every , and their associated s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc, doFireEvent) { + // may have been given a connection, or in special cases, an object + var connType = jsPlumb.Defaults.ConnectionType || jsPlumb.getDefaultConnectionType(), + argIsConnection = jpc.constructor == connType, + params = argIsConnection ? { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + } : jpc; + + if (doFireEvent) _currentInstance.fire("jsPlumbConnectionDetached", params); + _currentInstance.anchorManager.connectionDetached(params); + }; + + /* + Function: detach + Detaches and then removes a . From 1.3.5 this method has been altered to remove support for + specifying Connections by various parameters; you can now pass in a Connection as the first argument and + an optional parameters object as a second argument. If you need the functionality this method provided + before 1.3.5 then you should use the getConnections method to get the list of Connections to detach, and + then iterate through them, calling this for each one. + + Parameters: + connection - the to detach + params - optional parameters to the detach call. valid values here are + fireEvent : defaults to false; indicates you want jsPlumb to fire a connection + detached event. The thinking behind this is that if you made a programmatic + call to detach an event, you probably don't need the callback. + forceDetach : defaults to false. allows you to override any beforeDetach listeners that may be registered. + + Returns: + true if successful, false if not. + */ + this.detach = function() { + + if (arguments.length == 0) return; + var connType = jsPlumb.Defaults.ConnectionType || jsPlumb.getDefaultConnectionType(), + firstArgIsConnection = arguments[0].constructor == connType, + params = arguments.length == 2 ? firstArgIsConnection ? (arguments[1] || {}) : arguments[0] : arguments[0], + fireEvent = (params.fireEvent !== false), + forceDetach = params.forceDetach, + connection = firstArgIsConnection ? arguments[0] : params.connection; + + if (connection) { + if (forceDetach || (connection.isDetachAllowed(connection) + && connection.endpoints[0].isDetachAllowed(connection) + && connection.endpoints[1].isDetachAllowed(connection))) { + if (forceDetach || _currentInstance.checkCondition("beforeDetach", connection)) + connection.endpoints[0].detach(connection, false, true, fireEvent); // TODO check this param iscorrect for endpoint's detach method + } + } + else { + var _p = jsPlumb.extend( {}, params); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1]), fireEvent); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source), + targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + if (_currentInstance.checkCondition("beforeDetach", jpc)) { + jpc.endpoints[0].detach(jpc, false, true, fireEvent); + } + } + }); + } + } + }; + + /* + Function: detachAllConnections + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + params - optional parameters. alowed values: + fireEvent : defaults to true, whether or not to fire the detach event. + + Returns: + void + */ + this.detachAllConnections = function(el, params) { + params = params || {}; + el = _getElementObject(el); + var id = _getAttribute(el, "id"), + endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + }; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Parameters: + params - optional params object containing: + fireEvent : whether or not to fire detach events. defaults to true. + + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function(params) { + params = params || {}; + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of y + our library's draggable method so that jsPlumb can setup the appropriate callbacks. Your + underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + // TODO it would be nice if this supported a selector string, instead of an id. + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method, + * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the + * default scope). If multiple scopes are passed in, the return value will be a map of { scope -> [ connection... ] }. + * + * Parameters + * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list + * of connections that are in the given scope. + * options - if the argument is a JS object, you can specify a finer-grained filter: + * + * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes. + * - *source* either a string representing an element id, or a selector. constrains the result to connections having this source. + * - *target* either a string representing an element id, or a selector. constrains the result to connections having this target. + * + */ + this.getConnections = function(options) { + if (!options) { + options = {}; + } else if (options.constructor == String) { + options = { "scope": options }; + } + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') + r.push(input); + else + r = input; + } + return r; + }; + var scope = options.scope || jsPlumb.getDefaultScope(), + scopes = prepareList(scope), + sources = prepareList(options.source), + targets = prepareList(options.target), + filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }, + results = scopes.length > 1 ? {} : [], + _addOne = function(scope, obj) { + if (scopes.length > 1) { + var ss = results[scope]; + if (ss == null) { + ss = []; results[scope] = ss; + } + ss.push(obj); + } else results.push(obj); + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + _addOne(i, c); + } + } + } + return results; + }; + + /* + * Function: getAllConnections + * Gets all connections, as a map of { scope -> [ connection... ] }. + */ + this.getAllConnections = function() { + return connectionsByScope; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /** + * Function:getEndpoints + * Gets the list of Endpoints for a given selector, or element id. + * @param el + * @return + */ + this.getEndpoints = function(el) { + return endpointsByElement[_getId(el)]; + }; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + this.getOffset = function(id) { + var o = offsets[id]; + return _updateOffset({elId:id}); + }; + + this.getSelector = function(spec) { + return jsPlumb.CurrentLibrary.getSelector(spec); + }; + + this.getSize = function(id) { + var s = sizes[id]; + if (!s) _updateOffset({elId:id}); + return sizes[id]; + }; + + this.appendElement = _appendElement; + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + changeEndpoints - whether not to also hide endpoints on the element. by default this is false. + + Returns: + void + */ + this.hide = function(el, changeEndpoints) { + _setVisible(el, "none", changeEndpoints); + }; + + // exposed for other objects to use to get a unique id. + this.idstamp = _idstamp; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + if (!initialized) { + _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run. + + var bindOne = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && _mouseEventsEnabled && renderMode == jsPlumb.CANVAS) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + var t = c[i].connector[event](e); + if (t) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i].endpoint[event](e)) return; + } + } + } + }); + }; + bindOne("click");bindOne("dblclick");bindOne("mousemove");bindOne("mousedown");bindOne("mouseup"); + + initialized = true; + _currentInstance.fire("ready"); + } + }; + + this.log = log; + this.jsPlumbUIComponent = jsPlumbUIComponent; + this.EventGenerator = EventGenerator; + + /* + * Creates an anchor with the given params. + * + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function() { + if (arguments.length == 0) return null; + var specimen = arguments[0], elementId = arguments[1], jsPlumbInstance = arguments[2], newAnchor = null; + if (!jsPlumbInstance) + throw "NO JSPLUMB SET"; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; //TODO hazy here about whether it should be added or is already added somehow. + // is it the name of an anchor type? + else if (typeof specimen == "string") { + newAnchor = _currentInstance.Anchors[arguments[0]]({elementId:elementId, jsPlumbInstance:_currentInstance}); + } + // is it an array? it will be one of: + // an array of [name, params] - this defines a single anchor + // an array of arrays - this defines some dynamic anchors + // an array of numbers - this defines a single anchor. + else if (specimen.constructor == Array) { + if (specimen[0].constructor == Array || specimen[0].constructor == String) { + if (specimen.length == 2 && specimen[0].constructor == String && specimen[1].constructor == Object) { + var pp = jsPlumb.extend({elementId:elementId, jsPlumbInstance:_currentInstance}, specimen[1]); + newAnchor = _currentInstance.Anchors[specimen[0]](pp); + } + else + newAnchor = new DynamicAnchor(specimen, null, elementId); + } + else { + var anchorParams = { + x:specimen[0], y:specimen[1], + orientation : (specimen.length >= 4) ? [ specimen[2], specimen[3] ] : [0,0], + offsets : (specimen.length == 6) ? [ specimen[4], specimen[5] ] : [ 0, 0 ], + elementId:elementId + }; + newAnchor = new Anchor(anchorParams); + newAnchor.clone = function() { return new Anchor(anchorParams); }; + } + } + + if (!newAnchor.id) newAnchor.id = "anchor_" + _idstamp(); + return newAnchor; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types, elementId, jsPlumbInstance) { + var r = []; + for ( var i = 0; i < types.length; i++) { + if (typeof types[i] == "string") + r.push(_currentInstance.Anchors[types[i]]({elementId:elementId, jsPlumbInstance:jsPlumbInstance})); + else if (types[i].constructor == Array) + r.push(jsPlumb.makeAnchor(types[i], elementId, jsPlumbInstance)); + } + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /** + * Function: makeTarget + * Makes some DOM element a Connection target, allowing you to drag connections to it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a target. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * scope optional. scope for the drop zone. + * dropOptions optional. same stuff as you would pass to dropOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to true. whether or not to delete + * any Endpoints created by a connection to this target if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + var _targetEndpointDefinitions = {}; + this.makeTarget = function(el, params, referenceParams) { + + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + var jpcl = jsPlumb.CurrentLibrary, + targetScope = p.scope || _currentInstance.Defaults.Scope, + deleteEndpointsOnDetach = p.deleteEndpointsOnDetach || true, + _doOne = function(_el) { + + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el); + _targetEndpointDefinitions[elid] = p.endpoint; + + var dropOptions = jsPlumb.extend({}, p.dropOptions || {}), + _drop = function() { + _currentInstance.currentlyDragging = false; + var draggable = _getElementObject(jpcl.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + // restore the original scope if necessary (issue 57) + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id], + source = jpc.endpoints[0], + _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : {}; + + // use defaults for endpoint style, if not present..this either uses EndpointStyle, or EndpointStyles[1], + // if it is present, since this is a target endpoint. + // TODO - this should be a helper method. makeTarget should use it too. give it an endpoint + _endpoint.paintStyle = _endpoint.paintStyle || + _currentInstance.Defaults.EndpointStyles[1] || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyles[1] || + jsPlumb.Defaults.EndpointStyle; + + _endpoint.hoverPaintStyle = _endpoint.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyles[1] || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyles[1] || + jsPlumb.Defaults.EndpointHoverStyle; + + _endpoint.anchor = _endpoint.anchor || + _currentInstance.Defaults.Anchors[1] || + _currentInstance.Defaults.Anchor || + jsPlumb.Defaults.Anchors[1] || + jsPlumb.Defaults.Anchor; + + // unlock the source anchor to allow it to refresh its position if necessary + source.anchor.locked = false; + + // if (!jpcl.hasClass(draggable, _currentInstance.endpointClass)) return; + + if (scope) jpcl.setDragScope(draggable, scope); + + // check if drop is allowed here. + var _continue = jpc.isDropAllowed(jpc.sourceId, _getId(_el), jpc.scope); + + // regardless of whether the connection is ok, reconfigure the existing connection to + // point at the current info. we need this to be correct for the detach event that will follow. + // clear the source endpoint from the list to detach. we will detach this connection at this + // point, but we want to keep the source endpoint. the target is a floating endpoint and should + // be removed. TODO need to figure out whether this code can result in endpoints kicking around + // when they shouldnt be. like is this a full detach of a connection? can it be? + if (jpc.endpointsToDeleteOnDetach) { + if (source === jpc.endpointsToDeleteOnDetach[0]) + jpc.endpointsToDeleteOnDetach[0] = null; + else if (source === jpc.endpointsToDeleteOnDetach[1]) + jpc.endpointsToDeleteOnDetach[1] = null; + } + // reinstate any suspended endpoint; this just puts the connection back into + // a state in which it will report sensible values if someone asks it about + // its target. we're going to throw this connection away shortly so it doesnt matter + // if we manipulate it a bit. + if (jpc.suspendedEndpoint) { + jpc.targetId = jpc.suspendedEndpoint.elementId; + jpc.target = jpcl.getElementObject(jpc.suspendedEndpoint.elementId); + jpc.endpoints[1] = jpc.suspendedEndpoint; + } + + if (_continue) { + + // detach this connection from the source. + source.detach(jpc, false, true, true);//source.endpointWillMoveAfterConnection); + + // make a new Endpoint for the target + var newEndpoint = jsPlumb.addEndpoint(_el, _endpoint); + + // if the anchor has a 'positionFinder' set, then delegate to that function to find + // out where to locate the anchor. + if (newEndpoint.anchor.positionFinder != null) { + var dropPosition = jpcl.getUIPosition(arguments), + elPosition = jpcl.getOffset(_el), + elSize = jpcl.getSize(_el), + ap = newEndpoint.anchor.positionFinder(dropPosition, elPosition, elSize, newEndpoint.anchor); + newEndpoint.anchor.x = ap[0]; + newEndpoint.anchor.y = ap[1]; + // now figure an orientation for it..kind of hard to know what to do actually. probably the best thing i can do is to + // support specifying an orientation in the anchor's spec. if one is not supplied then i will make the orientation + // be what will cause the most natural link to the source: it will be pointing at the source, but it needs to be + // specified in one axis only, and so how to make that choice? i think i will use whichever axis is the one in which + // the target is furthest away from the source. + } + var c = jsPlumb.connect({ + source:source, + target:newEndpoint, + scope:scope, + previousConnection:jpc, + container:jpc.parent, + // 'endpointWillMoveAfterConnection' is set by the makeSource function, and it indicates that the + // given endpoint will actually transfer from the element it is currently attached to to some other + // element after a connection has been established. in that case, we do not want to fire the + // connection event, since it will have the wrong data in it; makeSource will do it for us. + // this is controlled by the 'parent' parameter on a makeSource call. + doNotFireConnectionEvent:source.endpointWillMoveAfterConnection + }); + if (deleteEndpointsOnDetach) + c.endpointsToDeleteOnDetach = [ source, newEndpoint ]; + } + // if not allowed to drop... + else { + // TODO this code is identical (pretty much) to what happens when a connection + // dragged from a normal endpoint is in this situation. refactor. + // is this an existing connection, and will we reattach? + if (jpc.suspendedEndpoint) { + if (source.isReattach) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(source.elementId); + } + else + source.detach(jpc, false, true, true); // otherwise, detach the connection and tell everyone about it. + } + + } + }; + + var dropEvent = jpcl.dragEvents['drop']; + dropOptions["scope"] = dropOptions["scope"] || targetScope; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop); + + jpcl.initDroppable(_el, dropOptions, true); + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements drop targets. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeTargets = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeTarget(els[i], params, referenceParams); + } + }; + + /** + * Function: makeSource + * Makes some DOM element a Connection source, allowing you to drag connections from it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a source. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * parent optional. the element to add Endpoints to when a Connection is established. if you omit this, + * Endpoints will be added to 'el'. + * scope optional. scope for the connections dragged from this element. + * dragOptions optional. same stuff as you would pass to dragOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to false. whether or not to delete + * any Endpoints created by a connection from this source if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + var _sourceEndpointDefinitions = {}; + this.makeSource = function(el, params, referenceParams) { + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + var jpcl = jsPlumb.CurrentLibrary, + _doOne = function(_el) { + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el); + _sourceEndpointDefinitions[elid] = p.endpoint || {}; + var stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"], + dragOptions = jsPlumb.extend({ }, _sourceEndpointDefinitions[elid].dragOptions || {}), + ep = null, + endpointAddedButNoDragYet = false; + + // make sure this method honours whatever defaults have been set for Endpoint. + _sourceEndpointDefinitions[elid].endpoint = _sourceEndpointDefinitions[elid].endpoint || _currentInstance.Defaults.Endpoints[0] || _currentInstance.Defaults.Endpoint; + + // TODO this, and the makeTarget equivalent, and probably elsewhere, should all be handed off to + // some helper method that can make this decision for us. + _sourceEndpointDefinitions[elid].paintStyle = _sourceEndpointDefinitions[elid].paintStyle || + _currentInstance.Defaults.EndpointStyles[0] || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyles[0] || + jsPlumb.Defaults.EndpointStyle; + + _sourceEndpointDefinitions[elid].hoverPaintStyle = _sourceEndpointDefinitions[elid].hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyles[0] || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyles[0] || + jsPlumb.Defaults.EndpointHoverStyle; + + _sourceEndpointDefinitions[elid].anchor = _sourceEndpointDefinitions[elid].anchor || + _currentInstance.Defaults.Anchors[0] || + _currentInstance.Defaults.Anchor || + jsPlumb.Defaults.Anchors[0] || + jsPlumb.Defaults.Anchor; + + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + endpointAddedButNoDragYet = false; + }); + + dragOptions[stopEvent] = function() { + // here we need to do a couple of things; + // first determine whether or not a connection was dragged. if not, just delete this endpoint. + // ...if so, though, then we need to check to see if a 'parent' was specified in the + // options to makeSource. a 'parent' is a reference to an element other than the one from + // which the connection is dragged, and it indicates that after a successful connection, the + // endpoint should be moved off of this element and onto 'parent', using all of the + // options passed in to the makeSource call. + // + // one thing that occurs to me right now is that we dont really want the first + // connection to have fired a connection event. but how can we prevent it from doing so? + // + // + + //_currentlyDown = false; + _currentInstance.currentlyDragging = false; + + if (ep.connections.length == 0) + jsPlumb.deleteEndpoint(ep); + else { + + jpcl.unbind(ep.canvas, "mousedown"); + + // reset the anchor to the anchor that was initially provided. the one we were using to drag + // the connection was just a placeholder that was located at the place the user pressed the + // mouse button to initiate the drag. + var anchorDef = _sourceEndpointDefinitions[elid].anchor || _currentInstance.Defaults.Anchor; + ep.anchor = jsPlumb.makeAnchor(anchorDef, elid, _currentInstance); + + if (p.parent) { + var parent = jpcl.getElementObject(p.parent); + if (parent) { + var currentId = ep.elementId; + ep.setElement(parent); + ep.endpointWillMoveAfterConnection = false; + _currentInstance.anchorManager.rehomeEndpoint(currentId, parent); + var jpc = ep.connections[0]; // TODO will this always be correct? + _finaliseConnection(jpc); + _currentInstance.repaintEverything(); + } + } + _currentInstance.repaint(elid); + } + }; + // when the user presses the mouse, add an Endpoint + var mouseDownListener = function(e) { + // make sure we have the latest offset for this div + _updateOffset({elId:elid}); + // and get it, and the div's size + var myOffset = offsets[elid], myWH = sizes[elid]; + // if there is a parent, the endpoint will actually be added to it now, rather than the div + // that was the source. in that case, we have to adjust the anchor position so it refers to + // the parent. + if (p.parent) { + var pEl = jsPlumb.CurrentLibrary.getElementObject(p.parent), + pId = _getId(pEl); + _updateOffset({elId:pId}); + myOffset = offsets[pId]; + myWH = sizes[pId]; + } + + var x = ((e.pageX || e.page.x) - myOffset.left) / myWH[0], + y = ((e.pageY || e.page.y) - myOffset.top) / myWH[1]; + + // we need to override the anchor in here, and force 'isSource', but we don't want to mess with + // the params passed in, because after a connection is established we're going to reset the endpoint + // to have the anchor we were given. + var tempEndpointParams = {}; + jsPlumb.extend(tempEndpointParams, _sourceEndpointDefinitions[elid]); + tempEndpointParams.isSource = true; + tempEndpointParams.anchor = [x,y,0,0]; + tempEndpointParams.dragOptions = dragOptions; + // if a parent was given we need to turn that into a "container" argument. this is, by default, + // the parent of the element we will move to, so parent of p.parent in this case. however, if + // the user has specified a 'container' on the endpoint definition or on + // the defaults, we should use that. + if (p.parent) { + var potentialParent = tempEndpointParams.container || jsPlumb.Defaults.Container; + if (potentialParent) + tempEndpointParams.container = potentialParent; + else + tempEndpointParams.container = jsPlumb.CurrentLibrary.getParent(p.parent); + } + + ep = jsPlumb.addEndpoint(elid, tempEndpointParams); + + endpointAddedButNoDragYet = true; + // we set this to prevent connections from firing attach events before this function has had a chance + // to move the endpoint. + ep.endpointWillMoveAfterConnection = p.parent != null; + ep.endpointWillMoveTo = p.parent ? jpcl.getElementObject(p.parent) : null; + + var _delTempEndpoint = function() { + // this mouseup event is fired only if no dragging occurred, by jquery and yui, but for mootools + // it is fired even if dragging has occurred, in which case we would blow away a perfectly + // legitimate endpoint, were it not for this check. the flag is set after adding an + // endpoint and cleared in a drag listener we set in the dragOptions above. + if(endpointAddedButNoDragYet) { + jsPlumb.deleteEndpoint(ep); + } + }; + + _currentInstance.registerListener(ep.canvas, "mouseup", _delTempEndpoint); + _currentInstance.registerListener(_el, "mouseup", _delTempEndpoint); + + // and then trigger its mousedown event, which will kick off a drag, which will start dragging + // a new connection from this endpoint. + jpcl.trigger(ep.canvas, "mousedown", e); + }; + + // jpcl.bind(_el, "mousedown", mouseDownListener); + // register this on jsPlumb so that it can be cleared by a reset. + _currentInstance.registerListener(_el, "mousedown", mouseDownListener); + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements connection sources. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeSources = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeSource(els[i], params, referenceParams); + } + }; + + /* + Function: ready + Helper method to bind a function to jsPlumb's ready event. + */ + this.ready = function(fn) { + _currentInstance.bind("ready", fn); + }, + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"), + ebe = endpointsByElement[elId]; + if (ebe) { + for ( var i = 0; i < ebe.length; i++) + _currentInstance.deleteEndpoint(ebe[i]); + } + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + var _registeredListeners = {}, + _unbindRegisteredListeners = function() { + for (var i in _registeredListeners) { + for (var j = 0; j < _registeredListeners[i].length; j++) { + var info = _registeredListeners[i][j]; + jsPlumb.CurrentLibrary.unbind(info.el, info.event, info.listener); + } + } + _registeredListeners = {}; + }; + + // internal register listener method. gives us a hook to clean things up + // with if the user calls jsPlumb.reset. + this.registerListener = function(el, type, listener) { + jsPlumb.CurrentLibrary.bind(el, type, listener); + _addToList(_registeredListeners, type, {el:el, event:type, listener:listener}); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + this.deleteEveryEndpoint(); + this.clearListeners(); + _unbindRegisteredListeners(); + this.anchorManager.reset(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + + Parameters: + value - whether or not to automatically repaint when the window is resized. + + Returns: void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction + * Sets the function to fire when the window size has changed and a repaint was fired. + * + * Parameters: + * f - Function to execute. + * + * Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: setMouseEventsEnabled + * Sets whether or not mouse events are enabled. Default is true. + * + * Parameters: + * enabled - whether or not mouse events should be enabled. + * + * Returns: + * void + */ + this.setMouseEventsEnabled = function(enabled) { + _mouseEventsEnabled = enabled; + }; + + /* + * Constant for use with the setRenderMode method + */ + this.CANVAS = "canvas"; + + /* + * Constant for use with the setRenderMode method + */ + this.SVG = "svg"; + + this.VML = "vml"; + + /* + * Function: setRenderMode + * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that + * what you asked for is not supported (and that VML is). If you asked for VML but the browser does + * not support it, jsPlumb uses SVG. + * + * Returns: + * the render mode that jsPlumb set, which of course may be different from that requested. + */ + this.setRenderMode = function(mode) { + if (mode) + mode = mode.toLowerCase(); + else + return; + if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); + // now test we actually have the capability to do this. + if (mode === jsPlumb.CANVAS && canvasAvailable) + renderMode = jsPlumb.CANVAS; + else if (mode === jsPlumb.SVG && svgAvailable) + renderMode = jsPlumb.SVG; + else if (vmlAvailable) + renderMode = jsPlumb.VML; + + return renderMode; + }; + + this.getRenderMode = function() { return renderMode; }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * changeEndpoints - whether or not to also change the visible state of the endpoints on the element. this also has a bearing on + * other connections on those endpoints: if their other endpoint is also visible, the connections are made visible. + * + * Returns: + * void + */ + this.show = function(el, changeEndpoints) { + _setVisible(el, "block", changeEndpoints); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId, + makeAnchor:self.makeAnchor, + makeDynamicAnchor:self.makeDynamicAnchor + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * changeEndpoints - whether or not to also toggle the endpoints on the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload + * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element. + * + * Returns: + * void + */ + this.unload = function() { + delete endpointsByElement; + delete endpointsByUUID; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + delete canvasList; + }; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + var adjustForParentOffsetAndScroll = function(xy, el) { + var offsetParent = null, result = xy; + if (el.tagName.toLowerCase() === "svg" && el.parentNode) { + offsetParent = el.parentNode; + } + else if (el.offsetParent) { + offsetParent = el.offsetParent; + } + if (offsetParent != null) { + var po = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(offsetParent), + so = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : {left:offsetParent.scrollLeft, top:offsetParent.scrollTop}; + + // i thought it might be cool to do this: + // lastReturnValue[0] = lastReturnValue[0] - offsetParent.offsetLeft + offsetParent.scrollLeft; + // lastReturnValue[1] = lastReturnValue[1] - offsetParent.offsetTop + offsetParent.scrollTop; + // but i think it ignores margins. my reasoning was that it's quicker to not hand off to some underlying + // library. + + result[0] = xy[0] - po.left + so.left; + result[1] = xy[1] - po.top + so.top; + } + + return result; + }; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + this.elementId = params.elementId; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + + if (timestamp && timestamp === self.timestamp) + return lastReturnValue; + + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + + // adjust loc if there is an offsetParent + lastReturnValue = adjustForParentOffsetAndScroll(lastReturnValue, element.canvas); + + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + * + * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference, + // the canvas this refers to. + refCanvas = params.referenceCanvas, + size = _getSize(_getElementObject(refCanvas)), + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + xDir = 0, yDir = 0, + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + orientation = null, + _lastResult = null; + + this.compute = function(params) { + var xy = params.xy, element = params.element, + result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + + // adjust loc if there is an offsetParent + result = adjustForParentOffsetAndScroll(result, element.canvas); + + _lastResult = result; + return result; + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { orientation = anchor.getOrientation(); }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchor is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector, elementId) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = [], + _convert = function(anchor) { return anchor.constructor == Anchor ? anchor: jsPlumb.makeAnchor(anchor, elementId, _currentInstance); }; + for (var i = 0; i < anchors.length; i++) _anchors[i] = _convert(anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + this.locked = false; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null, + _curIndex = _anchors.length > 0 ? 0 : -1, + self = this, + + // helper method to calculate the distance between the centers of the two elements. + _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }, + + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + + return _curAnchor.compute(params); + }; + + this.getCurrentLocation = function() { + return _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + }; + + this.getOrientation = function() { return _curAnchor != null ? _curAnchor.getOrientation() : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + manages anchors for all elements. + */ + // "continuous" anchors: anchors that pick their location based on how many connections the given element has. + // this requires looking at a lot more elements than normal - anything that has had a Continuous anchor applied has + // to be recalculated. so this manager is used as a reference point. the first time, with a new timestamp, that + // a continuous anchor is asked to compute, it calls this guy. or maybe, even, this guy gets called somewhere else + // and compute only ever returns pre-computed values. either way, this is the central point, and we want it to + // be called as few times as possible. + var continuousAnchors = {}, + continuousAnchorLocations = {}, + continuousAnchorOrientations = {}, + Orientation = { HORIZONTAL : "horizontal", VERTICAL : "vertical", DIAGONAL : "diagonal", IDENTITY:"identity" }, + + // TODO this functions uses a crude method of determining orientation between two elements. + // 'diagonal' should be chosen when the angle of the line between the two centers is around + // one of 45, 135, 225 and 315 degrees. maybe +- 15 degrees. + calculateOrientation = function(sourceId, targetId, sd, td) { + + if (sourceId === targetId) return { + orientation:Orientation.IDENTITY, + a:["top", "top"] + }; + + var theta = Math.atan2((td.centery - sd.centery) , (td.centerx - sd.centerx)), + theta2 = Math.atan2((sd.centery - td.centery) , (sd.centerx - td.centerx)), + h = ((sd.left <= td.left && sd.right >= td.left) || (sd.left <= td.right && sd.right >= td.right) || + (sd.left <= td.left && sd.right >= td.right) || (td.left <= sd.left && td.right >= sd.right)), + v = ((sd.top <= td.top && sd.bottom >= td.top) || (sd.top <= td.bottom && sd.bottom >= td.bottom) || + (sd.top <= td.top && sd.bottom >= td.bottom) || (td.top <= sd.top && td.bottom >= sd.bottom)); + + if (! (h || v)) { + var a = null, rls = false, rrs = false, sortValue = null; + if (td.left > sd.left && td.top > sd.top) + a = ["right", "top"]; + else if (td.left > sd.left && sd.top > td.top) + a = [ "top", "left"]; + else if (td.left < sd.left && td.top < sd.top) + a = [ "top", "right"]; + else if (td.left < sd.left && td.top > sd.top) + a = ["left", "top" ]; + + return { orientation:Orientation.DIAGONAL, a:a, theta:theta, theta2:theta2 }; + } + else if (h) return { + orientation:Orientation.HORIZONTAL, + a:sd.top < td.top ? ["bottom", "top"] : ["top", "bottom"], + theta:theta, theta2:theta2 + } + else return { + orientation:Orientation.VERTICAL, + a:sd.left < td.left ? ["right", "left"] : ["left", "right"], + theta:theta, theta2:theta2 + } + }, + placeAnchorsOnLine = function(desc, elementDimensions, elementPosition, + connections, horizontal, otherMultiplier, reverse) { + var a = [], step = elementDimensions[horizontal ? 0 : 1] / (connections.length + 1); + + for (var i = 0; i < connections.length; i++) { + var val = (i + 1) * step, other = otherMultiplier * elementDimensions[horizontal ? 1 : 0]; + if (reverse) + val = elementDimensions[horizontal ? 0 : 1] - val; + + var dx = (horizontal ? val : other), x = elementPosition[0] + dx, xp = dx / elementDimensions[0], + dy = (horizontal ? other : val), y = elementPosition[1] + dy, yp = dy / elementDimensions[1]; + + a.push([ x, y, xp, yp, connections[i][1], connections[i][2] ]); + } + + return a; + }, + standardEdgeSort = function(a, b) { return a[0] > b[0] ? 1 : -1 }, + currySort = function(reverseAngles) { + return function(a,b) { + var r = true; + if (reverseAngles) { + if (a[0][0] < b[0][0]) + r = true; + else + r = a[0][1] > b[0][1]; + } + else { + if (a[0][0] > b[0][0]) + r= true; + else + r =a[0][1] > b[0][1]; + } + return r === false ? -1 : 1; + }; + }, + leftSort = function(a,b) { + // first get adjusted values + var p1 = a[0][0] < 0 ? -Math.PI - a[0][0] : Math.PI - a[0][0], + p2 = b[0][0] < 0 ? -Math.PI - b[0][0] : Math.PI - b[0][0]; + if (p1 > p2) return 1; + else return a[0][1] > b[0][1] ? 1 : -1; + }, + edgeSortFunctions = { + "top":standardEdgeSort, + "right":currySort(true), + "bottom":currySort(true), + "left":leftSort + }, + _sortHelper = function(_array, _fn) { + return _array.sort(_fn); + }, + placeAnchors = function(elementId, _anchorLists) { + var sS = sizes[elementId], sO = offsets[elementId], + placeSomeAnchors = function(desc, elementDimensions, elementPosition, unsortedConnections, isHorizontal, otherMultiplier) { + if (unsortedConnections.length > 0) { + var sc = _sortHelper(unsortedConnections, edgeSortFunctions[desc]), // puts them in order based on the target element's pos on screen + //sc = unsortedConnections.sort(edgeSortFunctions[desc]), // puts them in order based on the target element's pos on screen + reverse = desc === "right" || desc === "top", + anchors = placeAnchorsOnLine(desc, elementDimensions, + elementPosition, sc, + isHorizontal, otherMultiplier, reverse ); + + // takes a computed anchor position and adjusts it for parent offset and scroll, then stores it. + var _setAnchorLocation = function(endpoint, anchorPos) { + var a = adjustForParentOffsetAndScroll([anchorPos[0], anchorPos[1]], endpoint.canvas); + continuousAnchorLocations[endpoint.id] = [ a[0], a[1], anchorPos[2], anchorPos[3] ]; + }; + + for (var i = 0; i < anchors.length; i++) { + var c = anchors[i][4], weAreSource = c.endpoints[0].elementId === elementId, weAreTarget = c.endpoints[1].elementId === elementId; + if (weAreSource) + _setAnchorLocation(c.endpoints[0], anchors[i]); + else if (weAreTarget) + _setAnchorLocation(c.endpoints[1], anchors[i]); + } + } + }; + + placeSomeAnchors("bottom", sS, [sO.left,sO.top], _anchorLists.bottom, true, 1); + placeSomeAnchors("top", sS, [sO.left,sO.top], _anchorLists.top, true, 0); + placeSomeAnchors("left", sS, [sO.left,sO.top], _anchorLists.left, false, 0); + placeSomeAnchors("right", sS, [sO.left,sO.top], _anchorLists.right, false, 1); + }, + AnchorManager = function() { + var _amEndpoints = {}, + endpointConnectionsByElementId = {}, + continuousAnchorConnectionsByElementId = {}, + continuousAnchorEndpoints = [], + self = this, + anchorLists = {}; + + this.reset = function() { + endpointConnectionsByElementId = {}; + continuousAnchorConnectionsByElementId = {}; + continuousAnchorEndpoints = []; + anchorLists = {}; + }; + + this.newConnection = function(conn) { + var sourceId = conn.sourceId, targetId = conn.targetId, + ep = conn.endpoints, + doRegisterTarget = true, + registerConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + if (otherAnchor.constructor == DynamicAnchor || otherAnchor.constructor == Anchor) { + _addToList(endpointConnectionsByElementId, elId, [c, otherEndpoint, otherAnchor.constructor == DynamicAnchor]); + } + else { + // continuous. if they are the same element, just assign the same anchor + // to both. + if (sourceId == targetId) { + // remove the target endpoint's canvas. we dont need it. + jsPlumb.CurrentLibrary.removeElement(ep[1].canvas); + doRegisterTarget = false; + } + _addToList(continuousAnchorConnectionsByElementId, elId, c); + } + }; + registerConnection(0, ep[0], ep[0].anchor, targetId, conn); + if (doRegisterTarget) + registerConnection(1, ep[1], ep[1].anchor, sourceId, conn); + }; + this.connectionDetached = function(connInfo) { + var sourceId = connInfo.sourceId, + targetId = connInfo.targetId, + ep = connInfo.connection.endpoints, + removeConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + if (otherAnchor.constructor == FloatingAnchor) { + // no-op + } + else if (otherAnchor.constructor == DynamicAnchor || otherAnchor.constructor == Anchor) { + var _conns = endpointConnectionsByElementId[elId]; + if (_conns) { + _removeWithFunction(_conns, function(e) { return e[0].id == c.id; }); + } + } + else // continuous. + _removeFromList(continuousAnchorConnectionsByElementId, elId, c); + }; + + removeConnection(1, ep[1], ep[1].anchor, sourceId, connInfo.connection); + removeConnection(0, ep[0], ep[0].anchor, targetId, connInfo.connection); + + // remove from anchorLists + var sEl = connInfo.connection.sourceId, + tEl = connInfo.connection.targetId, + sE = connInfo.connection.endpoints[0].id, + tE = connInfo.connection.endpoints[1].id, + _remove = function(list, eId) { + if (list) { // transient anchors dont get entries in this list. + var f = function(e) { return e[4] == eId; }; + _removeWithFunction(list["top"], f); + _removeWithFunction(list["left"], f); + _removeWithFunction(list["bottom"], f); + _removeWithFunction(list["right"], f); + } + }; + + _remove(anchorLists[sEl], sE); + _remove(anchorLists[tEl], tE); + self.redraw(sEl); + self.redraw(tEl); + }; + this.add = function(endpoint, elementId) { + _addToList(_amEndpoints, elementId, endpoint); + }; + this.get = function(elementId) { + return { + "standard":endpointConnectionsByElementId[elementId] || [], + "continuous":continuousAnchorConnectionsByElementId[elementId] || [], + "endpoints":_amEndpoints[elementId], + "continuousAnchorEndpoints":continuousAnchorEndpoints + }; + }; + this.deleteEndpoint = function(endpoint) { + var cIdx = _findIndex(continuousAnchorEndpoints, endpoint); + if (cIdx > -1) + continuousAnchorEndpoints.splice(cIdx, 1); + else + _removeFromList(_amEndpoints, endpoint.elementId, endpoint); + }; + this.clearFor = function(elementId) { + delete _amEndpoints[elementId]; + _amEndpoints[elementId] = []; + }; + // updates the given anchor list by either updating an existing anchor's info, or adding it. this function + // also removes the anchor from its previous list, if the edge it is on has changed. + // all connections found along the way (those that are connected to one of the faces this function + // operates on) are added to the connsToPaint list, as are their endpoints. in this way we know to repaint + // them wthout having to calculate anything else about them. + var _updateAnchorList = function(lists, theta, order, conn, aBoolean, otherElId, idx, reverse, edgeId, elId, connsToPaint, endpointsToPaint) { + // first try to find the exact match, but keep track of the first index of a matching element id along the way.s + var exactIdx = -1, + firstMatchingElIdx = -1, + endpoint = conn.endpoints[idx], + endpointId = endpoint.id, + oIdx = [1,0][idx], + values = [ [ theta, order ], conn, aBoolean, otherElId, endpointId ], + listToAddTo = lists[edgeId], + listToRemoveFrom = endpoint._continuousAnchorEdge ? lists[endpoint._continuousAnchorEdge] : null; + + if (listToRemoveFrom) { + var rIdx = _findWithFunction(listToRemoveFrom, function(e) { return e[4] == endpointId }); + if (rIdx != -1) { + listToRemoveFrom.splice(rIdx, 1); + // get all connections from this list + for (var i = 0; i < listToRemoveFrom.length; i++) { + connsToPaint.push(listToRemoveFrom[i][1]); + endpointsToPaint.push(listToRemoveFrom[i][1].endpoints[idx]); + } + } + } + + for (var i = 0; i < listToAddTo.length; i++) { + if (idx == 1 && listToAddTo[i][3] === otherElId && firstMatchingElIdx == -1) + firstMatchingElIdx = i; + connsToPaint.push(listToAddTo[i][1]); + endpointsToPaint.push(listToAddTo[i][1].endpoints[idx]); + } + if (exactIdx != -1) { + listToAddTo[exactIdx] = values; + } + else { + var insertIdx = reverse ? firstMatchingElIdx != -1 ? firstMatchingElIdx : 0 : listToAddTo.length; // of course we will get this from having looked through the array shortly. + listToAddTo.splice(insertIdx, 0, values); + } + + // store this for next time. + endpoint._continuousAnchorEdge = edgeId; + }; + this.redraw = function(elementId, ui, timestamp) { + // get all the endpoints for this element + var ep = _amEndpoints[elementId] || [], + endpointConnections = endpointConnectionsByElementId[elementId] || [], + continuousAnchorConnections = continuousAnchorConnectionsByElementId[elementId] || [], + connectionsToPaint = [], + endpointsToPaint = []; + + timestamp = timestamp || _timestamp(); + + _updateOffset( { elId : elementId, offset : ui, recalc : false, timestamp : timestamp }); + // valid for one paint cycle. + var myOffset = offsets[elementId], + myWH = sizes[elementId], + orientationCache = {}; + + // actually, first we should compute the orientation of this element to all other elements to which + // this element is connected with a continuous anchor (whether both ends of the connection have + // a continuous anchor or just one) + for (var i = 0; i < continuousAnchorConnections.length; i++) { + var conn = continuousAnchorConnections[i], + sourceId = conn.sourceId, + targetId = conn.targetId, + oKey = sourceId + "_" + targetId, + oKey2 = targetId + "_" + sourceId, + o = orientationCache[oKey], + td = _getCachedData(targetId), + sd = _getCachedData(sourceId), + oIdx = conn.sourceId == elementId ? 1 : 0; + + if (!anchorLists[sourceId]) anchorLists[sourceId] = { top:[], right:[], bottom:[], left:[] }; + if (!anchorLists[targetId]) anchorLists[targetId] = { top:[], right:[], bottom:[], left:[] }; + + if (targetId == sourceId) { + // here we may want to improve this by somehow determining the face we'd like + // to put the connector on. ideally, when drawing, the face should be calculated + // by determining which face is closest to the point at which the mouse button + // was released. for now, we're putting it on the top face. + _updateAnchorList(anchorLists[sourceId], -Math.PI / 2, 0, conn, false, targetId, 0, false, "top", sourceId, connectionsToPaint, endpointsToPaint) + } + else { + if (!o) { + o = calculateOrientation(sourceId, targetId, sd.o, td.o); + orientationCache[oKey] = o; + // this would be a performance enhancement, but the computed angles need to be clamped to + //the (-PI/2 -> PI/2) range in order for the sorting to work properly. + /* orientationCache[oKey2] = { + orientation:o.orientation, + a:[o.a[1], o.a[0]], + theta:o.theta + Math.PI, + theta2:o.theta2 + Math.PI + };*/ + } + _updateAnchorList(anchorLists[sourceId], o.theta, 0, conn, false, targetId, 0, false, o.a[0], sourceId, connectionsToPaint, endpointsToPaint); + _updateAnchorList(anchorLists[targetId], o.theta2, -1, conn, true, sourceId, 1, true, o.a[1], targetId, connectionsToPaint, endpointsToPaint); + } + + connectionsToPaint.push(conn); + endpointsToPaint.push(conn.endpoints[oIdx]); + } + + // now place all the continuous anchors; + for (var anElement in anchorLists) { + placeAnchors(anElement, anchorLists[anElement]); + } + + // now that continuous anchors have been placed, paint all the endpoints for this element + for (var i = 0; i < ep.length; i++) { + ep[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + // ... and any other endpoints we came across as a result of the continuous anchors. + for (var i = 0; i < endpointsToPaint.length; i++) { + endpointsToPaint[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + + // paint all the standard and "dynamic connections", which are connections whose other anchor is + // static and therefore does need to be recomputed; we make sure that happens only one time. + for (var i = 0; i < endpointConnections.length; i++) { + var otherEndpoint = endpointConnections[i][1]; + if (otherEndpoint.anchor.constructor == DynamicAnchor) { + // _updateOffset( { elId : otherEndpoint.elementId, timestamp : timestamp }); + otherEndpoint.paint({ elementWithPrecedence:elementId }); + connectionsToPaint.push(endpointConnections[i][0]); + // all the connections for the other endpoint now need to be repainted + for (var k = 0; k < otherEndpoint.connections.length; k++) { + if (otherEndpoint.connections[k] !== endpointConnections[i][0]) + connectionsToPaint.push(otherEndpoint.connections[k]); + } + } else if (otherEndpoint.anchor.constructor == Anchor) { + connectionsToPaint.push(endpointConnections[i][0]); + } + } + // paint current floating connection for this element, if there is one. + var fc = floatingConnections[elementId]; + if (fc) + fc.paint({timestamp:timestamp, recalc:false, elId:elementId}); + + // paint all the connections + for (var i = 0; i < connectionsToPaint.length; i++) { + connectionsToPaint[i].paint({elId:elementId, timestamp:timestamp, recalc:false}); + } + }; + this.rehomeEndpoint = function(currentId, element) { + var eps = _amEndpoints[currentId] || [], //, + elementId = _currentInstance.getId(element); + for (var i = 0; i < eps.length; i++) { + self.add(eps[i], elementId); + } + eps.splice(0, eps.length); + }; + }; + _currentInstance.anchorManager = new AnchorManager(); + //_currentInstance.bind("jsPlumbConnection", _currentInstance.anchorManager.connectionListener); + //_currentInstance.bind("jsPlumbConnectionDetached", _currentInstance.anchorManager.connectionDetachedListener); + + _currentInstance.continuousAnchorFactory = { + get:function(params) { + var existing = continuousAnchors[params.elementId]; + if (!existing) { + existing = { + type:"Continuous", + compute : function(params) { + return continuousAnchorLocations[params.element.id] || [0,0]; + }, + getCurrentLocation : function(endpoint) { + return continuousAnchorLocations[endpoint.id] || [0,0]; + }, + getOrientation : function(endpoint) { + return continuousAnchorOrientations[endpoint.id] || [0,0]; + }, + isDynamic : true, + isContinuous : true + }; + continuousAnchors[params.elementId] = existing; + } + return existing; + } + }; + + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * container - optional id or selector instructing jsPlumb where to attach all the elements it creates for this connection. you should read the documentation for a full discussion of this. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * overlays - Optional array of Overlay definitions to appear on this Connection. + * drawEndpoints - if false, instructs jsPlumb to not draw the endpoints for this Connection. Be careful with this: it only really works when you tell jsPlumb to attach elements to the document body. Read the documentation for a full discussion of this. + */ + var Connection = function(params) { + var self = this, visible = true; + self.idPrefix = "_jsplumb_c_"; + jsPlumbUIComponent.apply(this, arguments); + // ************** get the source and target and register the connection. ******************* + + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none"; + }; + this.parent = params.parent; + /** + Property: source + The source element for this Connection. + */ + this.source = _getElementObject(params.source); + /** + Property:target + The target element for this Connection. + */ + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. but + // source is not overridden if the Endpoint has declared it is not the final target of a connection; + // instead we use the source that the Endpoint declares will be the final source element. + if (params.sourceEndpoint) { + this.source = params.sourceEndpoint.endpointWillMoveTo || params.sourceEndpoint.getElement(); + } + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + + // if a new connection is the result of moving some existing connection, params.previousConnection + // will have that Connection in it. listeners for the jsPlumbConnection event can look for that + // member and take action if they need to. + self.previousConnection = params.previousConnection; + + /* + * Property: sourceId + * Id of the source element in the connection. + */ + this.sourceId = _getAttribute(this.source, "id"); + /* + * Property: targetId + * Id of the target element in the connection. + */ + this.targetId = _getAttribute(this.target, "id"); + + /** + * implementation of abstract method in EventGenerator + * @return list of attached elements. in our case, a list of Endpoints. + */ + this.getAttachedElements = function() { + return self.endpoints; + }; + + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams, elementId) { + if (anchorParams) + return jsPlumb.makeAnchor(anchorParams, elementId, _currentInstance); + }, + prepareEndpoint = function(existing, index, params, element, elementId, connectorPaintStyle, connectorHoverPaintStyle) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] + || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint; + + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified. + if (es.fillStyle == null && connectorPaintStyle != null) + es.fillStyle = connectorPaintStyle.strokeStyle; + + // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does: + //* + if (es.outlineColor == null && connectorPaintStyle != null) + es.outlineColor = connectorPaintStyle.outlineColor; + if (es.outlineWidth == null && connectorPaintStyle != null) + es.outlineWidth = connectorPaintStyle.outlineWidth; + //*/ + + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure? + if (connectorHoverPaintStyle != null) { + if (ehs == null) ehs = {}; + if (ehs.fillStyle == null) { + ehs.fillStyle = connectorHoverPaintStyle.strokeStyle; + } + } + var a = params.anchors ? params.anchors[index] : + params.anchor ? params.anchor : + _makeAnchor(_currentInstance.Defaults.Anchors[index], elementId) || + _makeAnchor(jsPlumb.Defaults.Anchors[index], elementId) || + _makeAnchor(_currentInstance.Defaults.Anchor, elementId) || + _makeAnchor(jsPlumb.Defaults.Anchor, elementId), + u = params.uuids ? params.uuids[index] : null, + e = _newEndpoint({ + paintStyle : es, + hoverPaintStyle:ehs, + endpoint : ep, + connections : [ self ], + uuid : u, + anchor : a, + source : element, + container:params.container, + reattach:params.reattach, + detachable:params.detachable + }); + self.endpoints[index] = e; + + + if (params.drawEndpoints === false) e.setVisible(false, true, true); + + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, + 0, + params, + self.source, + self.sourceId, + params.paintStyle, + params.hoverPaintStyle); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + + // if there were no endpoints supplied and the source element is the target element, we will reuse the source + // endpoint that was just created. + var existingTargetEndpoint = ((self.sourceId == self.targetId) && params.targetEndpoint == null) ? eS : params.targetEndpoint, + eT = prepareEndpoint(existingTargetEndpoint, + 1, + params, + self.target, + self.targetId, + params.paintStyle, + params.hoverPaintStyle); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + // if delete endpoints on detach, keep a record of just exactly which endpoints they are. + if (params.deleteEndpointsOnDetach) + self.endpointsToDeleteOnDetach = [eS, eT]; + + var _detachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.detachable === false) _detachable = false; + if(self.endpoints[0].connectionsDetachable === false) _detachable = false; + if(self.endpoints[1].connectionsDetachable === false) _detachable = false; + + /* + Function: isDetachable + Returns whether or not this connection can be detached from its target/source endpoint. by default this + is false; use it in conjunction with the 'reattach' parameter. + */ + this.isDetachable = function() { + return _detachable === true; + }; + + /* + Function: setDetachable + Sets whether or not this connection is detachable. + */ + this.setDetachable = function(detachable) { + _detachable = detachable === true; + }; + + // merge all the parameters objects into the connection. parameters set + // on the connection take precedence; then target endpoint params, then + // finally source endpoint params. + var _p = jsPlumb.CurrentLibrary.extend({}, this.endpoints[0].getParameters()); + jsPlumb.CurrentLibrary.extend(_p, this.endpoints[1].getParameters()); + jsPlumb.CurrentLibrary.extend(_p, self.getParameters()); + self.setParameters(_p); + + var _bindConnectorEvents = function() { + // add mouse events + self.connector.bind("click", function(con, e) { + self.fire("click", self, e); + }); + self.connector.bind("dblclick", function(con, e) { + self.fire("dblclick", self, e); }); + self.connector.bind("mouseenter", function(con, e) { + if (!self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(true, false); + } + self.fire("mouseenter", self, e); + } + }); + self.connector.bind("mouseexit", function(con, e) { + if (self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(false, false); + } + self.fire("mouseexit", self, e); + } + }); + + }; + + /* + * Function: setConnector + * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method - the same + * thing that you would set as the 'connector' property on a jsPlumb.connect call. + * + * Parameters: + * connector - Connector definition + */ + this.setConnector = function(connector, doNotRepaint) { + if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent); + var connectorArgs = { + _jsPlumb:self._jsPlumb, + parent:params.parent, + cssClass:params.cssClass, + container:params.container, + tooltip:self.tooltip + }; + if (connector.constructor == String) + this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand. + else if (connector.constructor == Array) + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs)); + self.canvas = self.connector.canvas; + _bindConnectorEvents(); + if (!doNotRepaint) self.repaint(); + }; + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc) + */ + + self.setConnector(this.endpoints[0].connector || + this.endpoints[1].connector || + params.connector || + _currentInstance.Defaults.Connector || + jsPlumb.Defaults.Connector, true); + + + // override setHover to pass it down to the underlying connector + var _sh = self.setHover; + self.setHover = function() { + self.connector.setHover.apply(self.connector, arguments); + _sh.apply(self, arguments); + }; + + this.setPaintStyle(this.endpoints[0].connectorStyle || + this.endpoints[1].connectorStyle || + params.paintStyle || + _currentInstance.Defaults.PaintStyle || + jsPlumb.Defaults.PaintStyle, true); + + this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || + this.endpoints[1].connectorHoverStyle || + params.hoverPaintStyle || + _currentInstance.Defaults.HoverPaintStyle || + jsPlumb.Defaults.HoverPaintStyle, true); + + this.paintStyleInUse = this.paintStyle; + + /* + * Property: overlays + * List of Overlays for this Connection. + */ + this.overlays = []; + var processOverlay = function(o) { + var _newOverlay = null; + if (o.constructor == Array) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0], + // make a copy of the object so as not to mess up anyone else's reference... + p = jsPlumb.CurrentLibrary.extend({connection:self, _jsPlumb:_currentInstance}, o[1]); + if (o.length == 3) jsPlumb.CurrentLibrary.extend(p, o[2]); + _newOverlay = new jsPlumb.Overlays[renderMode][type](p); + if (p.events) { + for (var evt in p.events) { + _newOverlay.bind(evt, p.events[evt]); + } + } + } else if (o.constructor == String) { + _newOverlay = new jsPlumb.Overlays[renderMode][o]({connection:self, _jsPlumb:_currentInstance}); + } else { + _newOverlay = o; + } + + self.overlays.push(_newOverlay); + }; + var _overlays = params.overlays || _currentInstance.Defaults.Overlays; + if (_overlays) { + for (var i = 0; i < _overlays.length; i++) { + processOverlay(_overlays[i]); + } + } + + this.moveParent = function(newParent) { + var jpcl = jsPlumb.CurrentLibrary, curParent = jpcl.getParent(self.connector.canvas); + jpcl.removeElement(self.connector.canvas, curParent); + jpcl.appendElement(self.connector.canvas, newParent); + if (self.connector.bgCanvas) { + jpcl.removeElement(self.connector.bgCanvas, curParent); + jpcl.appendElement(self.connector.bgCanvas, newParent); + } + // this only applies for DOMOverlays + for (var i = 0; i < self.overlays.length; i++) { + if (self.overlays[i].isAppendedAtTopLevel) { + jpcl.removeElement(self.overlays[i].canvas, curParent); + jpcl.appendElement(self.overlays[i].canvas, newParent); + if (self.overlays[i].reattachListeners) self.overlays[i].reattachListeners(); + } + } + if (self.connector.reattachListeners) // this is for SVG/VML; change an element's parent and you have to reinit its listeners. + self.connector.reattachListeners(); // the Canvas implementation doesn't have to care about this + }; + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Connection. + * + * Parameters: + * event - the event to bind. Available events on a Connection are: + * - *click* : notification that a Connection was clicked. + * - *dblclick* : notification that a Connection was double clicked. + * - *mouseenter* : notification that the mouse is over a Connection. + * - *mouseexit* : notification that the mouse exited a Connection. + * + * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Connection's paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Connection will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Connection. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + // overlay finder helper method + var _getOverlayIndex = function(id) { + var idx = -1; + for (var i = 0; i < self.overlays.length; i++) { + if (id === self.overlays[i].id) { + idx = i; + break; + } + } + return idx; + }; + + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + this.addOverlay = function(overlay) { + processOverlay(overlay); + self.repaint(); + }; + + /* + * Function: getOverlay + * Gets an overlay, by ID. Note: by ID. You would pass an 'id' parameter + * in to the Overlay's constructor arguments, and then use that to retrieve + * it via this method. + */ + this.getOverlay = function(id) { + var idx = _getOverlayIndex(id); + return idx >= 0 ? self.overlays[idx] : null; + }; + + /* + * Function: hideOverlay + * Hides the overlay specified by the given id. + */ + this.hideOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.hide(); + }; + + /* + * Function: showOverlay + * Shows the overlay specified by the given id. + */ + this.showOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.show(); + }; + + /** + * Function: removeAllOverlays + * Removes all overlays from the Connection, and then repaints. + */ + this.removeAllOverlays = function() { + self.overlays.splice(0, self.overlays.length); + self.repaint(); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + this.removeOverlay = function(overlayId) { + var idx = _getOverlayIndex(overlayId); + if (idx != -1) { + var o = self.overlays[idx]; + o.cleanup(); + self.overlays.splice(idx, 1); + } + }; + + /** + * Function:removeOverlays + * Removes a set of overlays by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + this.removeOverlays = function() { + for (var i = 0; i < arguments.length; i++) + self.removeOverlay(arguments[i]); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + this.labelStyle = params.labelStyle || _currentInstance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + if (this.label) { + this.overlays.push(new jsPlumb.Overlays[renderMode].Label( { + cssClass:params.cssClass, + labelStyle : this.labelStyle, + label : this.label, + connection:self, + _jsPlumb:_currentInstance + })); + } + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String or a Function that returns a String. + */ + this.setLabel = function(l) { + self.label = l; + _currentInstance.repaint(self.source); + }; + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId], + otherOffset = offsets[this.targetId], + otherWH = sizes[this.targetId], + initialTimestamp = _timestamp(), + anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + elementId:this.endpoints[0].elementId, + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1], + timestamp:initialTimestamp + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc, timestamp:initialTimestamp }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + elementId:this.endpoints[1].elementId, + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0], + timestamp:initialTimestamp + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc, timestamp:initialTimestamp }); + + /* + * Paints the Connection. Not exposed for public usage. + * + * Parameters: + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp, + // if the moving object is not the source we must transpose the two references. + swap = false, + tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId, + tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + + _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }); + _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var sE = this.endpoints[sIdx], tE = this.endpoints[tIdx], + sAnchorP = sE.anchor.getCurrentLocation(sE), + tAnchorP = tE.anchor.getCurrentLocation(tE); + + /* paint overlays*/ + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible()) maxSize = Math.max(maxSize, o.computeMaxSize(self.connector)); + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, + this.endpoints[sIdx], this.endpoints[tIdx], + this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, + self.paintStyleInUse.lineWidth, maxSize); + + self.connector.paint(dim, self.paintStyleInUse); + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim); + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function(params) { + params = params || {}; + var recalc = !(params.recalc === false); + this.paint({ elId : this.sourceId, recalc : recalc, timestamp:params.timestamp }); + }; + // resizing (using the jquery.ba-resize plugin). todo: decide + // whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + + // just to make sure the UI gets initialised fully on all browsers. + self.repaint(); + }; + +// ENDPOINT HELPER FUNCTIONS + var _makeConnectionDragHandler = function(placeholder) { + var stopped = false; + return function() { + if (stopped) return true; + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments), + el = placeholder.element; + if (el) { + jsPlumb.CurrentLibrary.setOffset(el, _ui); + _draw(_getElementObject(el), _ui); + } + + this.stopDrag = function() { + stopped = true; + }; + }; + }; + + var _makeFloatingEndpoint = function(paintStyle, referenceAnchor, endpoint, referenceCanvas, sourceElement) { + var floatingAnchor = new FloatingAnchor( { reference : referenceAnchor, referenceCanvas : referenceCanvas }); + + //setting the scope here should not be the way to fix that mootools issue. it should be fixed by not + // adding the floating endpoint as a droppable. that makes more sense anyway! + + return _newEndpoint({ paintStyle : paintStyle, endpoint : endpoint, anchor : floatingAnchor, source : sourceElement, scope:"__floating" }); + }; + + /** + * creates a placeholder div for dragging purposes, adds it to the DOM, and pre-computes its offset. then returns + * both the element id and a selector for the element. + */ + var _makeDraggablePlaceholder = function(placeholder, parent) { + var n = document.createElement("div"); + n.style.position = "absolute"; + var placeholderDragElement = _getElementObject(n); + _appendElement(n, parent); + var id = _getId(placeholderDragElement); + _updateOffset( { elId : id }); + // create and assign an id, and initialize the offset. + placeholder.id = id; + placeholder.element = placeholderDragElement; + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1 + * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't + * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you + * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or + * 'target' Endpoints for Connections. + * + * + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * container - optional id or selector instructing jsPlumb where to attach the element it creates for this endpoint. you should read the documentation for a full discussion of this. + * connections - optional list of Connections to configure the Endpoint with. + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + var self = this; + self.idPrefix = "_jsplumb_e_"; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + params = params || {}; + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Endpoint. + * + * Parameters: + * event - the event to bind. Available events on an Endpoint are: + * - *click* : notification that a Endpoint was clicked. + * - *dblclick* : notification that a Endpoint was double clicked. + * - *mouseenter* : notification that the mouse is over a Endpoint. + * - *mouseexit* : notification that the mouse exited a Endpoint. + * + * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Endpoint's paint style and then repaints the Endpoint. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Endpoint will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Endpoint. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + var visible = true; + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + var _element = params.source, _uuid = params.uuid, floatingEndpoint = null, inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + this.element = _element; + + if (params.dynamicAnchors) + self.anchor = new DynamicAnchor(jsPlumb.makeAnchors(params.dynamicAnchors)); + else + self.anchor = params.anchor ? jsPlumb.makeAnchor(params.anchor, _elementId, _currentInstance) : params.anchors ? jsPlumb.makeAnchor(params.anchors, _elementId, _currentInstance) : jsPlumb.makeAnchor("TopCenter", _elementId, _currentInstance); + + // ANCHOR MANAGER + if (!params._transient) // in place copies, for example, are transient. they will never need to be retrieved during a paint cycle, because they dont move, and then they are deleted. + _currentInstance.anchorManager.add(self, _elementId); + + var _endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot", + endpointArgs = { + _jsPlumb:self._jsPlumb, + parent:params.parent, + container:params.container, + tooltip:params.tooltip, + connectorTooltip:params.connectorTooltip, + endpoint:self + }; + if (_endpoint.constructor == String) + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint](endpointArgs); + else if (_endpoint.constructor == Array) { + endpointArgs = jsPlumb.extend(_endpoint[1], endpointArgs); + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint[0]](endpointArgs); + } + else + _endpoint = _endpoint.clone(); + + // assign a clone function using our derived endpointArgs. this is used when a drag starts: the endpoint that was dragged is cloned, + // and the clone is left in its place while the original one goes off on a magical journey. + this.clone = function() { + var o = new Object(); + _endpoint.constructor.apply(o, [endpointArgs]); + return o; + }; + + self.endpoint = _endpoint; + self.type = self.endpoint.type; + // override setHover to pass it down to the underlying endpoint + var _sh = self.setHover; + self.setHover = function() { + self.endpoint.setHover.apply(self.endpoint, arguments); + _sh.apply(self, arguments); + }; + // endpoint delegates to first connection for hover, if there is one. + var internalHover = function(state) { + if (self.connections.length > 0) + self.connections[0].setHover(state, false); + else + self.setHover(state); + }; + + // TODO this event listener registration code is identical to what Connection does: it should be refactored. + this.endpoint.bind("click", function(e) { self.fire("click", self, e); }); + this.endpoint.bind("dblclick", function(e) { self.fire("dblclick", self, e); }); + // this method is different; endpoint delegates hover to the first connection if there is one. + // that allows the connection to notify all its endpoint and avoids a circular loop + this.endpoint.bind("mouseenter", function(con, e) { + if (!self.isHover()) { + internalHover(true); + self.fire("mouseenter", self, e); + } + }); + // this method is different; endpoint delegates hover to the first connection if there is one. + // that allows the connection to notify all its endpoint and avoids a circular loop + this.endpoint.bind("mouseexit", function(con, e) { + if (self.isHover()) { + internalHover(false); + self.fire("mouseexit", self, e); + } + }); + + this.setPaintStyle(params.paintStyle || + params.style || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyle, true); + this.setHoverPaintStyle(params.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyle, true); + this.paintStyleInUse = this.paintStyle; + this.connectorStyle = params.connectorStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.connectorTooltip = params.connectorTooltip; + this.parent = params.parent; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + + var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + /* + * Property: canvas + * The Endpoint's Canvas. + */ + this.canvas = this.endpoint.canvas; + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + this.connections = params.connections || []; + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + self.isReattach = params.reattach || false; + self.connectionsDetachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.connectionsDetachable === false || params.detachable === false) + self.connectionsDetachable = false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget, forceDetach, fireEvent) { + var idx = _findIndex(self.connections, connection), actuallyDetached = false; + fireEvent = (fireEvent !== false); + if (idx >= 0) { + // 1. does the connection have a before detach (note this also checks jsPlumb's bound + // detach handlers; but then Endpoint's check will, too, hmm.) + if (forceDetach || connection._forceDetach || connection.isDetachable() || connection.isDetachAllowed(connection)) { + // get the target endpoint + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + // it would be nice to check with both endpoints that it is ok to detach. but + // for this we'll have to get a bit fancier: right now if you use the same beforeDetach + // interceptor for two endpoints (which is kind of common, because it's part of the + // endpoint definition), then it gets fired twice. so in fact we need to loop through + // each beforeDetach and see if it returns false, at which point we exit. but if it + // returns true, we have to check the next one. however we need to track which ones + // have already been run, and not run them again. + if (forceDetach || connection._forceDetach || (self.isDetachAllowed(connection) /*&& t.isDetachAllowed(connection)*/)) { + + self.connections.splice(idx, 1); + + // this avoids a circular loop + if (!ignoreTarget) { + + t.detach(connection, true, forceDetach); + // check connection to see if we want to delete the endpoints associated with it. + // we only detach those that have just this connection; this scenario is most + // likely if we got to this bit of code because it is set by the methods that + // create their own endpoints, like .connect or .makeTarget. the user is + // not likely to have interacted with those endpoints. + if (connection.endpointsToDeleteOnDetach){ + for (var i = 0; i < connection.endpointsToDeleteOnDetach.length; i++) { + var cde = connection.endpointsToDeleteOnDetach[i]; + if (cde && cde.connections.length == 0) + jsPlumb.deleteEndpoint(cde); + } + } + } + _removeElements(connection.connector.getDisplayElements(), connection.parent); + _removeFromList(connectionsByScope, connection.scope, connection); + actuallyDetached = true; + var doFireEvent = (!ignoreTarget && fireEvent) + fireDetachEvent(connection, doFireEvent); + } + } + } + return actuallyDetached; + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + * + * Parameters: + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachAll = function(fireEvent) { + while (self.connections.length > 0) { + self.detach(self.connections[0], false, true, fireEvent); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachFrom = function(targetEndpoint, fireEvent) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + if (self.detach(c[i], false, true, fireEvent)) + c[i].setHover(false, false); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: setElement + * Sets the DOM element this Endpoint is attached to. + */ + this.setElement = function(el) { + // TODO possibly have this object take charge of moving the UI components into the appropriate + // parent. this is used only by makeSource right now, and that function takes care of + // moving the UI bits and pieces. however it would s + var parentId = _getId(el); + // remove the endpoint from the list for the current endpoint's element + _removeFromList(endpointsByElement, _elementId, self); + _element = _getElementObject(el); + _elementId = _getId(_element); + self.elementId = _elementId; + + // need to get the new parent now + var newParentElement = _getParentFromParams({source:parentId}), + curParent = jpcl.getParent(self.canvas); + jpcl.removeElement(self.canvas, curParent); + jpcl.appendElement(self.canvas, newParentElement); + + // now move connection(s)...i would expect there to be only one but we will iterate. + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].moveParent(newParentElement); + self.connections[i].sourceId = _elementId; + self.connections[i].source = _element; + } + + _addToList(endpointsByElement, parentId, self); + _currentInstance.repaint(parentId); + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + return _newEndpoint( { + anchor : self.anchor, + source : _element, + paintStyle : this.paintStyle, + endpoint : _endpoint, + _transient:true, + scope:self.scope + }); + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + var candidate = self.connections[0]; + if (self.isTarget && candidate) return candidate; + else { + return (self.connections.length < _maxConnections) || _maxConnections == -1 ? null : candidate; + } + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + /* + * Function: setStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to + * setStyle and deprecate it if so. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + * + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + // a helper function that tries to find a connection to the given element, and returns it if so. if elementWithPrecedence is null, + // or no connection to it is found, we return the first connection in our list. + var findConnectionToUseForDynamicAnchor = function(elementWithPrecedence) { + var idx = 0; + if (elementWithPrecedence != null) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].sourceId == elementWithPrecedence || self.connections[i].targetId == elementWithPrecedence) { + idx = i; + break; + } + } + } + + return self.connections[idx]; + }; + + /* + * Function: paint + * Paints the Endpoint, recalculating offset and anchor positions if necessary. This does NOT paint + * any of the Endpoint's connections. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + params = params || {}; + var timestamp = params.timestamp, + recalc = !(params.recalc === false); + if (!timestamp || self.timestamp !== timestamp) { + _updateOffset({ elId:_elementId, timestamp:timestamp, recalc:recalc }); + var ap = params.anchorPoint, connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + var xy = params.offset || offsets[_elementId], + wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (recalc && self.anchor.isDynamic && self.connections.length > 0) { + var c = findConnectionToUseForDynamicAnchor(params.elementWithPrecedence), + oIdx = c.endpoints[0] == self ? 1 : 0, + oId = oIdx == 0 ? c.sourceId : c.targetId, + oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + ap = self.anchor.compute(anchorParams); + } + + var d = _endpoint.compute(ap, self.anchor.getOrientation(_endpoint), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + _endpoint.paint(d, self.paintStyleInUse, self.anchor); + self.timestamp = timestamp; + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var placeholderInfo = { + id:null, + element:null + }, + jpc = null, + existingJpc = false, + existingJpcParams = null, + _dragHandler = _makeConnectionDragHandler(placeholderInfo); + + var start = function() { + // drag might have started on an endpoint that is not actually a source, but which has + // one or more connections. + jpc = self.connectorSelector(); + var _continue = true; + // if no connection and we're not a source, return. + if (jpc == null && !params.isSource) _continue = false; + // otherwise if we're full and not allowed to drag, also return false. + if (params.isSource && self.isFull() && !dragAllowedWhenFull) _continue = false; + + // if the connection was setup as not detachable (or one of its endpoints + // was setup as connectionsDetachable = false, or Defaults.ConnectionsDetachable + // is set to false... + if (jpc != null && !jpc.isDetachable()) _continue = false; + + if (_continue === false) { + // this is for mootools and yui. returning false from this causes jquery to stop drag. + // the events are wrapped in both mootools and yui anyway, but i don't think returning + // false from the start callback would stop a drag. + if (jsPlumb.CurrentLibrary.stopDrag) jsPlumb.CurrentLibrary.stopDrag(); + _dragHandler.stopDrag(); + return false; + } + + // if we're not full but there was a connection, make it null. we'll create a new one. + if (jpc && !self.isFull() && params.isSource) jpc = null; + + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + _makeDraggablePlaceholder(placeholderInfo, self.parent); + + // set the offset of this div to be where 'inPlaceCopy' is, to start with. + // TODO merge this code with the code in both Anchor and FloatingAnchor, because it + // does the same stuff. + var ipcoel = _getElementObject(inPlaceCopy.canvas), + ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel), + po = adjustForParentOffsetAndScroll([ipco.left, ipco.top], inPlaceCopy.canvas); + jsPlumb.CurrentLibrary.setOffset(placeholderInfo.element, {left:po[0], top:po[1]}); + + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", placeholderInfo.id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + floatingEndpoint = _makeFloatingEndpoint(self.paintStyle, self.anchor, _endpoint, self.canvas, placeholderInfo.element); + + if (jpc == null) { + self.anchor.locked = true; + self.setHover(false, false); + // TODO the hover call above does not reset any target endpoint's hover + // states. + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : self.endpointWillMoveTo || _getElementObject(_element), // for makeSource with parent option. ensure source element is represented correctly. + target : placeholderInfo.element, + anchors : [ self.anchor, floatingEndpoint.anchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays + }); + + } else { + existingJpc = true; + jpc.connector.setHover(false, false); + // jpc.endpoints[0].setHover(false, false); + //jpc.endpoints[1].setHover(false, false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas), false, true); + //var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; // are we the source or the target? + // new anchor idx + var anchorIdx = jpc.endpoints[0].id == self.id ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas), + dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // now we want to get this endpoint's DROP scope, and set it for now: we can only be dropped on drop zones + // that have our drop scope (issue 57). + var dropScope = jsPlumb.CurrentLibrary.getDropScope(c); + jsPlumb.CurrentLibrary.setDragScope(c, dropScope); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = placeholderInfo.element; + jpc.sourceId = placeholderInfo.id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = placeholderInfo.element; + jpc.targetId = placeholderInfo.id; + } + + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.suspendedEndpoint.setHover(false); + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + // register it and register connection on it. + floatingConnections[placeholderInfo.id] = jpc; + floatingEndpoint.addConnection(jpc); + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, placeholderInfo.id, floatingEndpoint); + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var jpcl = jsPlumb.CurrentLibrary, + dragOptions = params.dragOptions || {}, + defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions), + startEvent = jpcl.dragEvents["start"], + stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"]; + + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + // extracted drag handler function so can be used by makeSource + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], _dragHandler); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _currentInstance.currentlyDragging = false; + _removeFromList(endpointsByElement, placeholderInfo.id, floatingEndpoint); + _removeElements( [ placeholderInfo.element[0], floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + _currentInstance.anchorManager.clearFor(placeholderInfo.id); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (self.isReattach || jpc._forceDetach || !jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc)) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } + jpc._forceDetach = null; + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElements(jpc.connector.getDisplayElements(), self.parent); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint({recalc:false}); + jpc.setHover(false, false); + jpc = null; + inPlaceCopy = null; + delete endpointsByElement[floatingEndpoint.elementId]; + floatingEndpoint.anchor = null; + floatingEndpoint = null; + _currentInstance.currentlyDragging = false; + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions, true); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas, forceInit, isTransient) { + if ((params.isTarget || forceInit) && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop'], + overEvent = jsPlumb.CurrentLibrary.dragEvents['over'], + outEvent = jsPlumb.CurrentLibrary.dragEvents['out'], + drop = function() { + var draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + elId = _getAttribute(draggable, "elId"), + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id], + idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + + // restore the original scope if necessary (issue 57) + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + + var _doContinue = true; + // the second check here is for the case that the user is dropping it back + // where it came from. + if (jpc.suspendedEndpoint && jpc.suspendedEndpoint.id != self.id) { + if (!jpc.isDetachAllowed(jpc) || !jpc.endpoints[idx].isDetachAllowed(jpc) || !jpc.suspendedEndpoint.isDetachAllowed(jpc) || !_currentInstance.checkCondition("beforeDetach", jpc)) + _doContinue = false; + } + + // these have to be set before testing for beforeDrop. + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + + // now check beforeDrop. this will be available only on Endpoints that are setup to + // have a beforeDrop condition (although, secretly, under the hood all Endpoints and + // the Connection have them, because they are on jsPlumbUIComponent. shhh!), because + // it only makes sense to have it on a target endpoint. + _doContinue = _doContinue && self.isDropAllowed(jpc.sourceId, jpc.targetId, jpc.scope); + + if (_doContinue) { + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + if (!jpc.suspendedEndpoint) { + //_addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + fireDetachEvent({ + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }, true); + } + + // finalise will inform the anchor manager and also add to + // connectionsByScope if necessary. + _finaliseConnection(jpc); + } + else { + // otherwise just put it back on the endpoint it was on before the drag. + if (jpc.suspendedEndpoint) { + // self.detachFrom(jpc); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.setHover(false); + jpc._forceDetach = true; + if (idx == 0) { + jpc.source = jpc.suspendedEndpoint.element; + jpc.sourceId = jpc.suspendedEndpoint.elementId; + } else { + jpc.target = jpc.suspendedEndpoint.element; + jpc.targetId = jpc.suspendedEndpoint.elementId;; + } + jpc.suspendedEndpoint.addConnection(jpc); + + jpc.endpoints[0].repaint(); + jpc.repaint(); + jsPlumb.repaint(jpc.source.elementId); + jpc._forceDetach = false; + } + } + + jpc.floatingAnchorIndex = null; + } + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + } + }); + dropOptions[outEvent] = _wrap(dropOptions[outEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + } + }); + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions, true, isTransient); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas), true, !(params._transient || self.anchor.isFloating)); + + return self; + }; + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + j.init(); + return j; + }; + jsPlumb.util = { + convertStyle : function(s, ignoreAlpha) { + // TODO: jsPlumb should support a separate 'opacity' style member. + if ("transparent" === s) return s; + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + return o; + }, + gradient : function(p1, p2) { + p1 = p1.constructor == Array ? p1 : [p1.x, p1.y]; + p2 = p2.constructor == Array ? p2 : [p2.x, p2.y]; + return (p2[1] - p1[1]) / (p2[0] - p1[0]); + }, + normal : function(p1, p2) { + return -1 / jsPlumb.util.gradient(p1,p2); + }, + segment : function(p1, p2) { + p1 = p1.constructor == Array ? p1 : [p1.x, p1.y]; + p2 = p2.constructor == Array ? p2 : [p2.x, p2.y]; + if (p2[0] > p1[0]) { + return (p2[1] > p1[1]) ? 2 : 1; + } + else { + return (p2[1] > p1[1]) ? 3 : 4; + } + }, + segmentMultipliers : [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ], + inverseSegmentMultipliers : [null, [-1, -1], [-1, 1], [1, 1], [1, -1] ], + pointOnLine : function(fromPoint, toPoint, distance) { + var m = jsPlumb.util.gradient(fromPoint, toPoint), + s = jsPlumb.util.segment(fromPoint, toPoint), + segmentMultiplier = distance > 0 ? jsPlumb.util.segmentMultipliers[s] : jsPlumb.util.inverseSegmentMultipliers[s], + theta = Math.atan(m), + y = Math.abs(distance * Math.sin(theta)) * segmentMultiplier[1], + x = Math.abs(distance * Math.cos(theta)) * segmentMultiplier[0]; + return { x:fromPoint.x + x, y:fromPoint.y + y }; + }, + /** + * calculates a perpendicular to the line fromPoint->toPoint, that passes through toPoint and is 'length' long. + * @param fromPoint + * @param toPoint + * @param length + */ + perpendicularLineTo : function(fromPoint, toPoint, length) { + var m = jsPlumb.util.gradient(fromPoint, toPoint), + theta2 = Math.atan(-1 / m), + y = length / 2 * Math.sin(theta2), + x = length / 2 * Math.cos(theta2); + return [{x:toPoint.x + x, y:toPoint.y + y}, {x:toPoint.x - x, y:toPoint.y - y}]; + } + }; + + var _curryAnchor = function(x, y, ox, oy, type, fnInit) { + return function(params) { + params = params || {}; + var a = jsPlumb.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance); + a.type = type; + if (fnInit) fnInit(a, params); + return a; + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1, "TopCenter"); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1, "BottomCenter"); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0, "LeftMiddle"); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0, "RightMiddle"); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0, "Center"); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1, "TopRight"); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1, "BottomRight"); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1, "TopLeft"); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1, "BottomLeft"); + + jsPlumb.Defaults.DynamicAnchors = function(params) { + return jsPlumb.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"], params.elementId, params.jsPlumbInstance); + }; + jsPlumb.Anchors["AutoDefault"] = function(params) { + var a = jsPlumb.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors(params)); + a.type = "AutoDefault"; + return a; + }; + + jsPlumb.Anchors["Assign"] = _curryAnchor(0,0,0,0,"Assign", function(anchor, params) { + // find what to use as the "position finder". the user may have supplied a String which represents + // the id of a position finder in jsPlumb.AnchorPositionFinders, or the user may have supplied the + // position finder as a function. we find out what to use and then set it on the anchor. + var pf = params.position || "Fixed"; + anchor.positionFinder = pf.constructor == String ? jsPlumb.AnchorPositionFinders[pf] : pf; + // always set the constructor params; the position finder might need them later (the Grid one does, + // for example) + anchor.constructorParams = params; + }); + + // Continuous anchor is just curried through to the 'get' method of the continuous anchor + // factory. + jsPlumb.Anchors["Continuous"] = function(params) { + return params.jsPlumbInstance.continuousAnchorFactory.get(params); + }; + + // these are the default anchor positions finders, which are used by the makeTarget function. supply + // a position finder argument to that function allows you to specify where the resulting anchor will + // be located + jsPlumb.AnchorPositionFinders = { + "Fixed": function(dp, ep, es, a) { + return [ (dp.left - ep.left) / es[0], (dp.top - ep.top) / es[1] ]; + }, + "Grid":function(dp, ep, es, a) { + var dx = dp.left - ep.left, dy = dp.top - ep.top, + gx = es[0] / (a.constructorParams.grid[0]), gy = es[1] / (a.constructorParams.grid[1]), + mx = Math.floor(dx / gx), my = Math.floor(dy / gy); + return [ ((mx * gx) + (gx / 2)) / es[0], ((my * gy) + (gy / 2)) / es[1] ]; + } + }; +})(); +/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the default Connectors, Endpoint and Overlay definitions. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +(function() { + + /** + * + * Helper class to consume unused mouse events by components that are DOM elements and + * are used by all of the different rendering modes. + * + */ + jsPlumb.DOMElementComponent = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + // when render mode is canvas, these functions may be called by the canvas mouse handler. + // this component is safe to pipe this stuff to /dev/null. + this.mousemove = + this.dblclick = + this.click = + this.mousedown = + this.mouseup = function(e) { }; + }; + + /** + * Class: Connectors.Straight + * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters. + */ + jsPlumb.Connectors.Straight = function() { + this.type = "Straight"; + var self = this, + currentPoints = null, + _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty, _segment, _length; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + _dx = _tx - _sx, _dy = _ty - _sy; + //_m = _dy / _dx, _m2 = -1 / _m; + _m = jsPlumb.util.gradient({x:_sx, y:_sy}, {x:_tx, y:_ty}), _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + //_segment = jsPlumb.util.segment({x:_sx, y:_sy}, {x:_tx, y:_ty}); + _length = Math.sqrt((_dx * _dx) + (_dy * _dy)); + + return currentPoints; + }; + + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + if (location == 0) + return { x:_sx, y:_sy }; + else if (location == 1) + return { x:_tx, y:_ty }; + else + return jsPlumb.util.pointOnLine({x:_sx, y:_sy}, {x:_tx, y:_ty}, location * _length); + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { + return _m; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + * this hands off to jsPlumb.util to do the maths, supplying our gradient and position and whether or + * not the coords should be flipped + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + return jsPlumb.util.pointOnLine(p, {x:_tx,y:_ty}, distance); + }; + }; + + + /** + * Class:Connectors.Bezier + * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's + * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below. + */ + /** + * Function:Constructor + * + * Parameters: + * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line. + * Optional; defaults to 150. + * + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + params = params || {}; + this.majorAnchor = params.curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + this.type = "Bezier"; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(sourceEndpoint), + too = targetAnchor.getOrientation(targetEndpoint), + perpendicular = soo[0] != too[0] || soo[1] == too[1], + p = [], + ma = self.majorAnchor, mi = self.minorAnchor; + + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + +// this.isFlipX = function() { return + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h; + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) + { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, sourceEndpoint, targetEndpoint, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx), minx2 = Math.min(_CP[0], _CP2[0]), minx = Math.min(minx1,minx2), + maxx1 = Math.max(_sx,_tx), maxx2 = Math.max(_CP[0], _CP2[0]), maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty), miny2 = Math.min(_CP[1], _CP2[1]), miny = Math.min(miny1,miny2), + maxy1 = Math.max(_sy,_ty), maxy2 = Math.max(_CP[1], _CP2[1]), maxy = Math.max(maxy1,maxy2); + + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + }; + + + /** + * Class: Connectors.Flowchart + * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments. + */ + /** + * Function: Constructor + * + * Parameters: + * stub - minimum length for the stub at each end of the connector. defaults to 30 pixels. + */ + jsPlumb.Connectors.Flowchart = function(params) { + this.type = "Flowchart"; + params = params || {}; + var self = this, + minStubLength = params.stub || params.minStubLength /* bwds compat. */ || 30, + segments = [], + totalLength = 0, + segmentProportions = [], + segmentProportionalLengths = [], + points = [], + swapX, + swapY, + /** + * recalculates the points at which the segments begin and end, proportional to the total length travelled + * by all the segments that constitute the connector. we use this to help with pointOnPath calculations. + */ + updateSegmentProportions = function(startX, startY, endX, endY) { + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segments[i][5] / totalLength; + segmentProportions[i] = [curLoc, (curLoc += (segments[i][5] / totalLength)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0], + ly = segments.length == 0 ? sy : segments[segments.length - 1][1], + m = x == lx ? Infinity : 0, + l = Math.abs(x == lx ? y - ly : x - lx); + segments.push([x, y, lx, ly, m, l]); + totalLength += l; + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment + * that contains the point which is 'location' distance along the entire path, where + * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths + * are made up of a list of segments, each of which contributes some fraction to + * the total length. + */ + findSegmentForLocation = function(location) { + var idx = segmentProportions.length - 1, inSegmentProportion = 0; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + segments = []; + totalLength = 0; + segmentProportionalLengths = []; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + offx = (lw / 2) + (minStubLength * 2), + offy = (lw / 2) + (minStubLength * 2), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(sourceEndpoint), + to = targetAnchor.orientation || targetAnchor.getOrientation(targetEndpoint), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx, + h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy; + if (w < minWidth) { + offx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + offy += (minWidth - h) / 2; + h = minWidth; + } + var sx = swapX ? w-offx : offx, + sy = swapY ? h-offy : offy, + tx = swapX ? offx : w-offx , + ty = swapY ? offy : h-offy, + startStubX = sx + (so[0] * minStubLength), + startStubY = sy + (so[1] * minStubLength), + endStubX = tx + (to[0] * minStubLength), + endStubY = ty + (to[1] * minStubLength), + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2); + + x -= offx; y -= offy; + points = [x, y, w, h, sx, sy, tx, ty]; + var extraPoints = []; + + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + if (so[0] == 0) { + var startStubIsBeforeEndStub = startStubY < endStubY; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } + } + else { + var startStubIsBeforeEndStub = startStubX < endStubX; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } + } + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentProportions(sx, sy, tx, ty); + + return points; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location) { + return self.pointAlongPathFrom(location, 0); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location) { + return segments[findSegmentForLocation(location)["index"]][4]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance) { + var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segments[s.index][5], m = segments[s.index][4]; + var e = { + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + return e; + }; + }; + + // ********************************* END OF CONNECTOR TYPES ******************************************************************* + + // ********************************* ENDPOINT TYPES ******************************************************************* + + /** + * Class: Endpoints.Dot + * A round endpoint, with default radius 10 pixels. + */ + + /** + * Function: Constructor + * + * Parameters: + * + * radius - radius of the endpoint. defaults to 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + this.type = "Dot"; + var self = this; + params = params || {}; + this.radius = params.radius || 10; + this.defaultOffset = 0.5 * this.radius; + this.defaultInnerRadius = this.radius / 3; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var r = endpointStyle.radius || self.radius, + x = anchorPoint[0] - r, + y = anchorPoint[1] - r; + return [ x, y, r * 2, r * 2, r ]; + }; + }; + + /** + * Class: Endpoints.Rectangle + * A Rectangular Endpoint, with default size 20x20. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the endpoint. defaults to 20 pixels. + * height - height of the endpoint. defaults to 20 pixels. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + this.type = "Rectangle"; + var self = this; + params = params || {}; + this.width = params.width || 20; + this.height = params.height || 20; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height]; + }; + }; + + /** + * Class: Endpoints.Image + * Draws an image as the Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * src - location of the image to use. + */ + jsPlumb.Endpoints.Image = function(params) { + + this.type = "Image"; + jsPlumb.DOMElementComponent.apply(this, arguments); + + var self = this, + initialized = false, + widthToUse = params.width, + heightToUse = params.height, + _onload = null, + _endpoint = params.endpoint; + + this.img = new Image(); + self.ready = false; + + this.img.onload = function() { + self.ready = true; + widthToUse = widthToUse || self.img.width; + heightToUse = heightToUse || self.img.height; + if (_onload) { + _onload(this); + } + }; + + _endpoint.setImage = function(img, onload) { + var s = img.constructor == String ? img : img.src; + _onload = onload; + self.img.src = img; + }; + + _endpoint.setImage(params.src || params.url, params.onload); + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + self.anchorPoint = anchorPoint; + if (self.ready) return [anchorPoint[0] - widthToUse / 2, anchorPoint[1] - heightToUse / 2, + widthToUse, heightToUse]; + else return [0,0,0,0]; + }; + + self.canvas = document.createElement("img"), initialized = false; + self.canvas.style["margin"] = 0; + self.canvas.style["padding"] = 0; + self.canvas.style["outline"] = 0; + self.canvas.style["position"] = "absolute"; + var clazz = params.cssClass ? " " + params.cssClass : ""; + self.canvas.className = jsPlumb.endpointClass + clazz; + if (widthToUse) self.canvas.setAttribute("width", widthToUse); + if (heightToUse) self.canvas.setAttribute("height", heightToUse); + jsPlumb.appendElement(self.canvas, params.parent); + self.attachListeners(self.canvas, self); + + var actuallyPaint = function(d, style, anchor) { + if (!initialized) { + self.canvas.setAttribute("src", self.img.src); + initialized = true; + } + var x = self.anchorPoint[0] - (widthToUse / 2), + y = self.anchorPoint[1] - (heightToUse / 2); + jsPlumb.sizeCanvas(self.canvas, x, y, widthToUse, heightToUse); + }; + + this.paint = function(d, style, anchor) { + if (self.ready) { + actuallyPaint(d, style, anchor); + } + else { + window.setTimeout(function() { + self.paint(d, style, anchor); + }, 200); + } + }; + }; + + /** + * Class: Endpoints.Blank + * An Endpoint that paints nothing (visible) on the screen. Supports cssClass and hoverClass parameters like all Endpoints. + */ + jsPlumb.Endpoints.Blank = function(params) { + var self = this; + this.type = "Blank"; + jsPlumb.DOMElementComponent.apply(this, arguments); + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + return [anchorPoint[0], anchorPoint[1],10,0]; + }; + + self.canvas = document.createElement("div"); + self.canvas.style.display = "block"; + self.canvas.style.width = "1px"; + self.canvas.style.height = "1px"; + self.canvas.style.background = "transparent"; + self.canvas.style.position = "absolute"; + self.canvas.className = self._jsPlumb.endpointClass; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + }; + }; + + /** + * Class: Endpoints.Triangle + * A triangular Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the triangle's base. defaults to 55 pixels. + * height - height of the triangle from base to apex. defaults to 55 pixels. + */ + jsPlumb.Endpoints.Triangle = function(params) { + this.type = "Triangle"; + params = params || { }; + params.width = params.width || 55; + params.height = params.height || 55; + this.width = params.width; + this.height = params.height; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height ]; + }; + }; +// ********************************* END OF ENDPOINT TYPES ******************************************************************* + + +// ********************************* OVERLAY DEFINITIONS *********************************************************************** + + var AbstractOverlay = function(params) { + var visible = true, self = this; + this.isAppendedAtTopLevel = true; + this.connection = params.connection; + this.loc = params.location == null ? 0.5 : params.location; + this.setVisible = function(val) { + visible = val; + self.connection.repaint(); + }; + this.isVisible = function() { return visible; }; + this.hide = function() { self.setVisible(false); }; + this.show = function() { self.setVisible(true); }; + + this.incrementLocation = function(amount) { + self.loc += amount; + self.connection.repaint(); + }; + this.setLocation = function(l) { + self.loc = l; + self.connection.repaint(); + }; + this.getLocation = function() { + return self.loc; + }; + }; + + + /** + * Class: Overlays.Arrow + * + * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length + * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction + * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line + * across the tail. + */ + /** + * Function: Constructor + * + * Parameters: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + this.type = "Arrow"; + AbstractOverlay.apply(this, arguments); + this.isAppendedAtTopLevel = false; + params = params || {}; + var self = this; + + this.length = params.length || 20; + this.width = params.width || 20; + this.id = params.id; + var direction = (params.direction || 1) < 0 ? -1 : 1, + paintStyle = params.paintStyle || { lineWidth:1 }, + // how far along the arrow the lines folding back in come to. default is 62.3%. + foldback = params.foldback || 0.623; + + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.cleanup = function() { }; // nothing to clean up for Arrows + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var hxy, mid, txy, tail, cxy; + + if (self.loc == 1) { + hxy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, -1); + txy = jsPlumb.util.pointOnLine(hxy, mid, self.length); + } + else if (self.loc == 0) { + txy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, 1); + hxy = jsPlumb.util.pointOnLine(txy, mid, self.length); + } + else { + hxy = connector.pointAlongPathFrom(self.loc, direction * self.length / 2), + mid = connector.pointOnPath(self.loc), + txy = jsPlumb.util.pointOnLine(hxy, mid, self.length); + } + + tail = jsPlumb.util.perpendicularLineTo(hxy, txy, self.width); + cxy = jsPlumb.util.pointOnLine(hxy, txy, foldback * self.length); + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x), + maxx = Math.max(hxy.x, tail[0].x, tail[1].x), + miny = Math.min(hxy.y, tail[0].y, tail[1].y), + maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + var d = { hxy:hxy, tail:tail, cxy:cxy }, + strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle, + fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle, + lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth; + + self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions); + + return [ minx, maxx, miny, maxy]; + }; + }; + + /** + * Class: Overlays.PlainArrow + * + * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "PlainArrow"; + }; + + /** + * Class: Overlays.Diamond + * + * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40, + p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "Diamond"; + }; + + + + /** + * Class: Overlays.Label + * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb + * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter + * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it + * puts on the Label's 'style' attribute, so the end result is the same. + */ + /** + * Function: Constructor + * + * Parameters: + * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes + * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle. + * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * + */ + jsPlumb.Overlays.Label = function(params) { + this.type = "Label"; + jsPlumb.DOMElementComponent.apply(this, arguments); + AbstractOverlay.apply(this, arguments); + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.id = params.id; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var label = params.label || "", + self = this, + initialised = false, + div = document.createElement("div"), + labelText = null; + div.style["position"] = "absolute"; + + var clazz = params["_jsPlumb"].overlayClass + " " + + (self.labelStyle.cssClass ? self.labelStyle.cssClass : + params.cssClass ? params.cssClass : ""); + + div.className = clazz; + + jsPlumb.appendElement(div, params.connection.parent); + jsPlumb.getId(div); + self.attachListeners(div, self); + self.canvas = div; + + //override setVisible + var osv = self.setVisible; + self.setVisible = function(state) { + osv(state); // call superclass + div.style.display = state ? "block" : "none"; + }; + + this.getElement = function() { + return div; + }; + + this.cleanup = function() { + if (div != null) jsPlumb.CurrentLibrary.removeElement(div); + }; + + /* + * Function: setLabel + * sets the label's, um, label. you would think i'd call this function + * 'setText', but you can pass either a Function or a String to this, so + * it makes more sense as 'setLabel'. + */ + this.setLabel = function(l) { + label = l; + labelText = null; + self.connection.repaint(); + }; + + this.getLabel = function() { + return label; + }; + + this.paint = function(connector, d, connectorDimensions) { + if (!initialised) { + connector.appendDisplayElement(div); + self.attachListeners(div, connector); + initialised = true; + } + div.style.left = (connectorDimensions[0] + d.minx) + "px"; + div.style.top = (connectorDimensions[1] + d.miny) + "px"; + }; + + this.getTextDimensions = function(connector) { + if (typeof label == "function") { + var lt = label(self); + div.innerHTML = lt.replace(/\r\n/g, "
"); + } + else { + if (labelText == null) { + labelText = label; + div.innerHTML = labelText.replace(/\r\n/g, "
"); + } + } + var de = jsPlumb.CurrentLibrary.getElementObject(div), + s = jsPlumb.CurrentLibrary.getSize(de); + return {width:s[0], height:s[1]}; + }; + + this.computeMaxSize = function(connector) { + var td = self.getTextDimensions(connector); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + var td = self.getTextDimensions(connector); + if (td.width != null) { + var cxy = connector.pointOnPath(self.loc), + minx = cxy.x - (td.width / 2), + miny = cxy.y - (td.height / 2); + + self.paint(connector, { + minx:minx, + miny:miny, + td:td, + cxy:cxy + }, connectorDimensions); + + return [minx, minx+td.width, miny, miny+td.height]; + } + else return [0,0,0,0]; + }; + + this.reattachListeners = function() { + if (div) self.reattachListenersForElement(div, self); + }; + }; + + // this is really just a test overlay, so its undocumented and doesnt take any parameters. but i was loth to delete it. + jsPlumb.Overlays.GuideLines = function() { + var self = this; + self.length = 50; + self.lineWidth = 5; + this.type = "GuideLines"; + AbstractOverlay.apply(this, arguments); + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var head = connector.pointAlongPathFrom(self.loc, self.length / 2), + mid = connector.pointOnPath(self.loc), + tail = jsPlumb.util.pointOnLine(head, mid, self.length), + tailLine = jsPlumb.util.perpendicularLineTo(head, tail, 40), + headLine = jsPlumb.util.perpendicularLineTo(tail, head, 20); + + self.paint(connector, [head, tail, tailLine, headLine], self.lineWidth, "red", null, connectorDimensions); + + return [Math.min(head.x, tail.x), Math.min(head.y, tail.y), Math.max(head.x, tail.x), Math.max(head.y,tail.y)]; + }; + + this.computeMaxSize = function() { return 50; }; + + this.cleanup = function() { }; // nothing to clean up for GuideLines + }; + + // ********************************* END OF OVERLAY DEFINITIONS *********************************************************************** + + // ********************************* OVERLAY CANVAS RENDERERS*********************************************************************** + + // ********************************* END OF OVERLAY CANVAS RENDERERS *********************************************************************** +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the state machine connectors. + * + * Thanks to Brainstorm Mobile Solutions for supporting the development of these. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + var Line = function(x1, y1, x2, y2) { + + this.m = (y2 - y1) / (x2 - x1); + this.b = -1 * ((this.m * x1) - y1); + + this.rectIntersect = function(x,y,w,h) { + var results = []; + + // try top face + // the equation of the top face is y = (0 * x) + b; y = b. + var xInt = (y - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try right face + var yInt = (this.m * (x + w)) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + // bottom face + var xInt = ((y + h) - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try left face + var yInt = (this.m * x) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + if (results.length == 2) { + var midx = (results[0][0] + results[1][0]) / 2, midy = (results[0][1] + results[1][1]) / 2; + results.push([ midx,midy ]); + // now calculate the segment inside the rectangle where the midpoint lies. + var xseg = midx <= x + (w / 2) ? -1 : 1, + yseg = midy <= y + (h / 2) ? -1 : 1; + results.push([xseg, yseg]); + return results; + } + + return null; + + }; + }, + _segment = function(x1, y1, x2, y2) { + if (x1 <= x2 && y2 <= y1) return 1; + else if (x1 <= x2 && y1 <= y2) return 2; + else if (x2 <= x1 && y2 >= y1) return 3; + return 4; + }, + + // the control point we will use depends on the faces to which each end of the connection is assigned, specifically whether or not the + // two faces are parallel or perpendicular. if they are parallel then the control point lies on the midpoint of the axis in which they + // are parellel and varies only in the other axis; this variation is proportional to the distance that the anchor points lie from the + // center of that face. if the two faces are perpendicular then the control point is at some distance from both the midpoints; the amount and + // direction are dependent on the orientation of the two elements. 'seg', passed in to this method, tells you which segment the target element + // lies in with respect to the source: 1 is top right, 2 is bottom right, 3 is bottom left, 4 is top left. + // + // sourcePos and targetPos are arrays of info about where on the source and target each anchor is located. their contents are: + // + // 0 - absolute x + // 1 - absolute y + // 2 - proportional x in element (0 is left edge, 1 is right edge) + // 3 - proportional y in element (0 is top edge, 1 is bottom edge) + // + _findControlPoint = function(midx, midy, segment, sourceEdge, targetEdge, dx, dy, distance, proximityLimit) { + + // TODO (maybe) + // - if anchor pos is 0.5, make the control point take into account the relative position of the elements. + if (distance <= proximityLimit) return [midx, midy]; + + if (segment == 1) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 2) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 3) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 4) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + }; + + /* + Function: StateMachine constructor + + Allowed parameters: + curviness - measure of how "curvy" the connectors will be. this is translated as the distance that the + Bezier curve's control point is from the midpoint of the straight line connecting the two + endpoints, and does not mean that the connector is this wide. The Bezier curve never reaches + its control points; they act as gravitational masses. defaults to 10. + margin - distance from element to start and end connectors, in pixels. defaults to 5. + proximityLimit - sets the distance beneath which the elements are consider too close together to bother with fancy + curves. by default this is 80 pixels. + loopbackRadius - the radius of a loopback connector. optional; defaults to 25. + */ + jsPlumb.Connectors.StateMachine = function(params) { + var self = this, + currentPoints = null, + _sx, _sy, _tx, _ty, _controlPoint = [], + curviness = params.curviness || 10, + margin = params.margin || 5, + proximityLimit = params.proximityLimit || 80, + clockwise = params.orientation && params.orientation == "clockwise", + loopbackRadius = params.loopbackRadius || 25, + isLoopback = false; + + this.type = "StateMachine"; + params = params || {}; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *= 1.9; + //ensure at least one pixel width + lineWidth = lineWidth || 1; + var x = Math.min(sourcePos[0], targetPos[0]) - xo, + y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (sourceEndpoint.elementId != targetEndpoint.elementId) { + + isLoopback = false; + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + + // now adjust for the margin + if (sourcePos[2] == 0) _sx -= margin; + if (sourcePos[2] == 1) _sx += margin; + if (sourcePos[3] == 0) _sy -= margin; + if (sourcePos[3] == 1) _sy += margin; + if (targetPos[2] == 0) _tx -= margin; + if (targetPos[2] == 1) _tx += margin; + if (targetPos[3] == 0) _ty -= margin; + if (targetPos[3] == 1) _ty += margin; + + // + // these connectors are quadratic bezier curves, having a single control point. if both anchors + // are located at 0.5 on their respective faces, the control point is set to the midpoint and you + // get a straight line. this is also the case if the two anchors are within 'proximityLimit', since + // it seems to make good aesthetic sense to do that. outside of that, the control point is positioned + // at 'curviness' pixels away along the normal to the straight line connecting the two anchors. + // + // there may be two improvements to this. firstly, we might actually support the notion of avoiding nodes + // in the UI, or at least making a good effort at doing so. if a connection would pass underneath some node, + // for example, we might increase the distance the control point is away from the midpoint in a bid to + // steer it around that node. this will work within limits, but i think those limits would also be the likely + // limits for, once again, aesthetic good sense in the layout of a chart using these connectors. + // + // the second possible change is actually two possible changes: firstly, it is possible we should gradually + // decrease the 'curviness' as the distance between the anchors decreases; start tailing it off to 0 at some + // point (which should be configurable). secondly, we might slightly increase the 'curviness' for connectors + // with respect to how far their anchor is from the center of its respective face. this could either look cool, + // or stupid, and may indeed work only in a way that is so subtle as to have been a waste of time. + // + + var _midx = (_sx + _tx) / 2, _midy = (_sy + _ty) / 2, + m2 = (-1 * _midx) / _midy, theta2 = Math.atan(m2), + dy = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.sin(theta2)), + dx = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.cos(theta2)), + segment = _segment(_sx, _sy, _tx, _ty), + distance = Math.sqrt(Math.pow(_tx - _sx, 2) + Math.pow(_ty - _sy, 2)); + + // calculate the control point. this code will be where we'll put in a rudimentary element avoidance scheme; it + // will work by extending the control point to force the curve to be, um, curvier. + _controlPoint = _findControlPoint(_midx, + _midy, + segment, + sourcePos, + targetPos, + curviness, curviness, + distance, + proximityLimit); + + + var requiredWidth = Math.max(Math.abs(_controlPoint[0] - _sx) * 3, Math.abs(_controlPoint[0] - _tx) * 3, Math.abs(_tx-_sx), 2 * lineWidth, minWidth), + requiredHeight = Math.max(Math.abs(_controlPoint[1] - _sy) * 3, Math.abs(_controlPoint[1] - _ty) * 3, Math.abs(_ty-_sy), 2 * lineWidth, minWidth); + + if (w < requiredWidth) { + var dw = requiredWidth - w; + x -= (dw / 2); + _sx += (dw / 2); + _tx += (dw / 2); + w = requiredWidth; + _controlPoint[0] += (dw / 2); + } + + if (h < requiredHeight) { + var dh = requiredHeight - h; + y -= (dh / 2); + _sy += (dh / 2); + _ty += (dh / 2); + h = requiredHeight; + _controlPoint[1] += (dh / 2); + } + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty, _controlPoint[0], _controlPoint[1] ]; + } + else { + isLoopback = true; + // a loopback connector. draw an arc from one anchor to the other. + // i guess we'll do this the same way as the others. just the control point will be a fair distance away. + var x1 = sourcePos[0], x2 = sourcePos[0], y1 = sourcePos[1] - margin, y2 = sourcePos[1] - margin, + cx = x1, cy = y1 - loopbackRadius; + + // canvas sizing stuff, to ensure the whole painted area is visible. + w = ((2 * lineWidth) + (4 * loopbackRadius)), h = ((2 * lineWidth) + (4 * loopbackRadius)); + x = cx - loopbackRadius - lineWidth - loopbackRadius, y = cy - loopbackRadius - lineWidth - loopbackRadius; + currentPoints = [ x, y, w, h, cx-x, cy-y, loopbackRadius, clockwise, x1-x, y1-y, x2-x, y2-y]; + } + + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_tx, y:_ty }, + { x:_controlPoint[0], y:_controlPoint[1] }, + { x:_controlPoint[0] + 1, y:_controlPoint[1] + 1}, + { x:_sx, y:_sy } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + if (isLoopback) { + + if (location > 0 && location < 1) location = 1- location; + +// current points are [ x, y, width, height, center x, center y, radius, clockwise, startx, starty, endx, endy ] + // so the path length is the circumference of the circle + //var len = 2 * Math.PI * currentPoints[6], + // map 'location' to an angle. 0 is PI/2 when the connector is on the top face; if we + // support other faces it will have to be calculated for each one. 1 is also PI/2. + // 0.5 is -PI/2. + var startAngle = (location * 2 * Math.PI) + (Math.PI / 2), + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + + } + else return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + if (isLoopback) + return Math.atan(location * 2 * Math.PI); + else + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + if (isLoopback) { + + if (location > 0 && location < 1) location = 1- location; + + var circumference = 2 * Math.PI * currentPoints[6], + arcSpan = distance / circumference * 2 * Math.PI, + startAngle = (location * 2 * Math.PI) - arcSpan + (Math.PI / 2), + + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + } + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + }; + + /* + * Canvas state machine renderer. + */ + jsPlumb.Connectors.canvas.StateMachine = function(params) { + params = params || {}; + var self = this, drawGuideline = params.drawGuideline || true, avoidSelector = params.avoidSelector; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.CanvasConnector.apply(this, arguments); + + + this._paint = function(dimensions) { + + if (dimensions.length == 10) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.quadraticCurveTo(dimensions[8], dimensions[9], dimensions[6], dimensions[7]); + self.ctx.stroke(); + + /*/ draw the guideline + if (drawGuideline) { + self.ctx.save(); + self.ctx.beginPath(); + self.ctx.strokeStyle = "silver"; + self.ctx.lineWidth = 1; + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + self.ctx.restore(); + } + //*/ + } + else { + // a loopback connector + self.ctx.save(); + self.ctx.beginPath(); + var startAngle = 0, // Starting point on circle + endAngle = 2 * Math.PI, // End point on circle + clockwise = dimensions[7]; // clockwise or anticlockwise + self.ctx.arc(dimensions[4],dimensions[5],dimensions[6],0, endAngle, clockwise); + self.ctx.stroke(); + self.ctx.closePath(); + self.ctx.restore(); + } + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + /* + * SVG State Machine renderer + */ + jsPlumb.Connectors.svg.StateMachine = function() { + var self = this; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.SvgConnector.apply(this, arguments); + this.getPath = function(d) { + + if (d.length == 10) + return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[8] + " " + d[9] + " " + d[6] + " " + d[7]; + else { + // loopback + return "M" + (d[8] + 4) + " " + d[9] + " A " + d[6] + " " + d[6] + " 0 1,0 " + (d[8]-4) + " " + d[9]; + } + }; + }; + + /* + * VML state machine renderer + */ + jsPlumb.Connectors.vml.StateMachine = function() { + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.VmlConnector.apply(this, arguments); + var _conv = jsPlumb.vml.convertValue; + this.getPath = function(d) { + if (d.length == 10) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + } + else { + // loopback + var left = _conv(d[8] - d[6]), + top = _conv(d[9] - (2 * d[6])), + right = left + _conv(2 * d[6]), + bottom = top + _conv(2 * d[6]), + posString = left + "," + top + "," + right + "," + bottom; + + var o = "ar " + posString + "," + _conv(d[8]) + "," + + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + " e"; + + return o; + } + }; + }; + +})(); + +/* + // now for a rudimentary avoidance scheme. TODO: how to set this in a cross-library way? + // if (avoidSelector) { + // var testLine = new Line(sourcePos[0] + _sx,sourcePos[1] + _sy,sourcePos[0] + _tx,sourcePos[1] + _ty); + // var sel = jsPlumb.getSelector(avoidSelector); + // for (var i = 0; i < sel.length; i++) { + // var id = jsPlumb.getId(sel[i]); + // if (id != sourceEndpoint.elementId && id != targetEndpoint.elementId) { + // o = jsPlumb.getOffset(id), s = jsPlumb.getSize(id); +// +// if (o && s) { +// var collision = testLine.rectIntersect(o.left,o.top,s[0],s[1]); +// if (collision) { + // set the control point to be a certain distance from the midpoint of the two points that + // the line crosses on the rectangle. + // TODO where will this 75 number come from? + // _controlX = collision[2][0] + (75 * collision[3][0]); + // / _controlY = collision[2][1] + (75 * collision[3][1]); +// } +// } + // } + // } + //} + *//* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the VML renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + // http://ajaxian.com/archives/the-vml-changes-in-ie-8 + // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/ + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + + var vmlAttributeMap = { + "stroke-linejoin":"joinstyle", + "joinstyle":"joinstyle", + "endcap":"endcap", + "miterlimit":"miterlimit" + }; + + if (document.createStyleSheet) { + + // this is the style rule for IE7/6: it uses a CSS class, tidy. + document.createStyleSheet().addRule(".jsplumb_vml", "behavior:url(#default#VML);position:absolute;"); + + // these are for VML in IE8. you have to explicitly call out which elements + // you're going to expect to support VML! + // + document.createStyleSheet().addRule("jsplumb\\:textbox", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:oval", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:rect", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:stroke", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:shape", "behavior:url(#default#VML);position:absolute;"); + + // in this page it is also mentioned that IE requires the extra arg to the namespace + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either. + // var iev = document.documentMode; + //if (!iev || iev < 8) + document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); + //else + // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML"); + } + + jsPlumb.vml = {}; + + var scale = 1000, + _atts = function(o, atts) { + for (var i in atts) { + // IE8 fix: setattribute does not work after an element has been added to the dom! + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + //o.setAttribute(i, atts[i]); + + o[i] = atts[i]; + } + }, + _node = function(name, d, atts) { + atts = atts || {}; + var o = document.createElement("jsplumb:" + name); + o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml"; + _pos(o, d); + _atts(o, atts); + return o; + }, + _pos = function(o,d) { + o.style.left = d[0] + "px"; + o.style.top = d[1] + "px"; + o.style.width= d[2] + "px"; + o.style.height= d[3] + "px"; + o.style.position = "absolute"; + }, + _conv = jsPlumb.vml.convertValue = function(v) { + return Math.floor(v * scale); + }, + // tests if the given style is "transparent" and then sets the appropriate opacity node to 0 if so, + // or 1 if not. TODO in the future, support variable opacity. + _maybeSetOpacity = function(styleToWrite, styleToCheck, type, component) { + if ("transparent" === styleToCheck) + component.setOpacity(type, "0.0"); + else + component.setOpacity(type, "1.0"); + }, + _applyStyles = function(node, style, component) { + var styleToWrite = {}; + if (style.strokeStyle) { + styleToWrite["stroked"] = "true"; + var strokeColor = jsPlumb.util.convertStyle(style.strokeStyle, true); + styleToWrite["strokecolor"] = strokeColor; + _maybeSetOpacity(styleToWrite, strokeColor, "stroke", component); + styleToWrite["strokeweight"] = style.lineWidth + "px"; + } + else styleToWrite["stroked"] = "false"; + + if (style.fillStyle) { + styleToWrite["filled"] = "true"; + var fillColor = jsPlumb.util.convertStyle(style.fillStyle, true); + styleToWrite["fillcolor"] = fillColor; + _maybeSetOpacity(styleToWrite, fillColor, "fill", component); + } + else styleToWrite["filled"] = "false"; + + if(style["dashstyle"]) { + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = style["dashstyle"]; + } + else if (style["stroke-dasharray"] && style["lineWidth"]) { + var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",", + parts = style["stroke-dasharray"].split(sep), + styleToUse = ""; + for(var i = 0; i < parts.length; i++) { + styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep); + } + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = styleToUse; + } + + _atts(node, styleToWrite); + }, + /* + * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. + */ + VmlComponent = function() { + var self = this; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.opacityNodes = { + "stroke":null, + "fill":null + }; + this.initOpacityNodes = function(vml) { + self.opacityNodes["stroke"] = _node("stroke", [0,0,1,1], {opacity:"0.0"}); + self.opacityNodes["fill"] = _node("fill", [0,0,1,1], {opacity:"0.0"}); + vml.appendChild(self.opacityNodes["stroke"]); + vml.appendChild(self.opacityNodes["fill"]); + }; + this.setOpacity = function(type, value) { + var node = self.opacityNodes[type]; + if (node) node["opacity"] = "" + value; + }; + }, + /* + * Base class for Vml connectors. extends VmlComponent. + */ + VmlConnector = jsPlumb.VmlConnector = function(params) { + var self = this; + self.strokeNode = null; + self.canvas = null; + VmlComponent.apply(this, arguments); + var clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : ""); + this.paint = function(d, style, anchor) { + if (style != null) { + var path = self.getPath(d), p = { "path":path }; + + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle : jsPlumb.util.convertStyle(style.outlineColor), + lineWidth : outlineStrokeWidth + }; + for (var aa in vmlAttributeMap) outlineStyle[aa] = style[aa]; + + if (self.bgCanvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.bgCanvas = _node("shape", d, p); + jsPlumb.appendElement(self.bgCanvas, params.parent); + _pos(self.bgCanvas, d); + displayElements.push(self.bgCanvas); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.bgCanvas, d); + _atts(self.bgCanvas, p); + } + + _applyStyles(self.bgCanvas, outlineStyle, self); + } + + if (self.canvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + if (self.tooltip) p["label"] = self.tooltip; + self.canvas = _node("shape", d, p); + jsPlumb.appendElement(self.canvas, params.parent); + displayElements.push(self.canvas); + + self.attachListeners(self.canvas, self); + + self.initOpacityNodes(self.canvas, ["stroke"]); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.canvas, d); + _atts(self.canvas, p); + } + + _applyStyles(self.canvas, style, self); + } + }; + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + self.canvas.parentNode.appendChild(el); + displayElements.push(el); + }; + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + }, + /* + * + * Base class for Vml Endpoints. extends VmlComponent. + * + */ + VmlEndpoint = function(params) { + VmlComponent.apply(this, arguments); + var vml = null, self = this, opacityStrokeNode = null, opacityFillNode = null; + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.appendElement(self.canvas, params.parent); + if (self.tooltip) self.canvas.setAttribute("label", self.tooltip); + + this.paint = function(d, style, anchor) { + var p = { }; + + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (vml == null) { + p["class"] = jsPlumb.endpointClass; + vml = self.getVml([0,0, d[2], d[3]], p, anchor); + self.canvas.appendChild(vml); + self.attachListeners(vml, self); + + self.initOpacityNodes(vml, ["fill"]); + } + else { + //p["coordsize"] = "1,1";//(d[2] * scale) + "," + (d[3] * scale); again, unsure. + _pos(vml, [0,0, d[2], d[3]]); + _atts(vml, p); + } + + _applyStyles(vml, style, self); + }; + + this.reattachListeners = function() { + if (vml) self.reattachListenersForElement(vml, self); + }; + }; + + jsPlumb.Connectors.vml.Bezier = function() { + jsPlumb.Connectors.Bezier.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Straight = function() { + jsPlumb.Connectors.Straight.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Flowchart = function() { + jsPlumb.Connectors.Flowchart.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l"; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]); + } + // finally draw a line to the end + p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e"; + return p; + }; + }; + + jsPlumb.Endpoints.vml.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("oval", d, atts); }; + }; + + jsPlumb.Endpoints.vml.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("rect", d, atts); }; + }; + + /* + * VML Image Endpoint is the same as the default image endpoint. + */ + jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image; + + /** + * placeholder for Blank endpoint in vml renderer. + */ + jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank; + + /** + * VML Label renderer. uses the default label renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label; + + var AbstractVmlArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + VmlComponent.apply(this, arguments); + var self = this, path = null; + self.canvas = null; + var getPath = function(d, connectorDimensions) { + return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) + + " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + + " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + + " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + + " x e"; + }; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) { + var p = {}; + if (strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] = jsPlumb.util.convertStyle(strokeStyle, true); + } + if (lineWidth) p["strokeweight"] = lineWidth + "px"; + if (fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = fillStyle; + } + var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + w = Math.abs(xmax - xmin), + h = Math.abs(ymax - ymin), + dim = [xmin, ymin, w, h]; + + // for VML, we create overlays using shapes that have the same dimensions and + // coordsize as their connector - overlays calculate themselves relative to the + // connector (it's how it's been done since the original canvas implementation, because + // for canvas that makes sense). + p["path"] = getPath(d, connectorDimensions); + p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale); + + dim[0] = connectorDimensions[0]; + dim[1] = connectorDimensions[1]; + dim[2] = connectorDimensions[2]; + dim[3] = connectorDimensions[3]; + + if (self.canvas == null) { + //p["class"] = jsPlumb.overlayClass; // TODO currentInstance? + self.canvas = _node("shape", dim, p); + connector.appendDisplayElement(self.canvas); + self.attachListeners(self.canvas, connector); + } + else { + _pos(self.canvas, dim); + _atts(self.canvas, p); + } + }; + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + }; + + jsPlumb.Overlays.vml.Arrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.vml.PlainArrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.vml.Diamond = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the SVG renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +/** + * SVG support for jsPlumb. + * + * things to investigate: + * + * gradients: https://developer.mozilla.org/en/svg_in_html_introduction + * css:http://tutorials.jenkov.com/svg/svg-and-css.html + * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath + * pointer events: https://developer.mozilla.org/en/css/pointer-events + * + * IE9 hover jquery: http://forum.jquery.com/topic/1-6-2-broke-svg-hover-events + * + */ +;(function() { + + var svgAttributeMap = { + "joinstyle":"stroke-linejoin", + "stroke-linejoin":"stroke-linejoin", + "stroke-dashoffset":"stroke-dashoffset", + "stroke-linecap":"stroke-linecap" + }, + STROKE_DASHARRAY = "stroke-dasharray", + DASHSTYLE = "dashstyle", + LINEAR_GRADIENT = "linearGradient", + RADIAL_GRADIENT = "radialGradient", + FILL = "fill", + STOP = "stop", + STROKE = "stroke", + STROKE_WIDTH = "stroke-width", + STYLE = "style", + NONE = "none", + JSPLUMB_GRADIENT = "jsplumb_gradient_", + LINE_WIDTH = "lineWidth", + ns = { + svg:"http://www.w3.org/2000/svg", + xhtml:"http://www.w3.org/1999/xhtml" + }, + _attr = function(node, attributes) { + for (var i in attributes) + node.setAttribute(i, "" + attributes[i]); + }, + _node = function(name, attributes) { + var n = document.createElementNS(ns.svg, name); + attributes = attributes || {}; + attributes["version"] = "1.1"; + attributes["xmnls"] = ns.xhtml; + _attr(n, attributes); + return n; + }, + _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; }, + _clearGradient = function(parent) { + for (var i = 0; i < parent.childNodes.length; i++) { + if (parent.childNodes[i].tagName == LINEAR_GRADIENT || parent.childNodes[i].tagName == RADIAL_GRADIENT) + parent.removeChild(parent.childNodes[i]); + } + }, + _updateGradient = function(parent, node, style, dimensions, uiComponent) { + var id = JSPLUMB_GRADIENT + uiComponent._jsPlumb.idstamp(); + // first clear out any existing gradient + _clearGradient(parent); + // this checks for an 'offset' property in the gradient, and in the absence of it, assumes + // we want a linear gradient. if it's there, we create a radial gradient. + // it is possible that a more explicit means of defining the gradient type would be + // better. relying on 'offset' means that we can never have a radial gradient that uses + // some default offset, for instance. + if (!style.gradient.offset) { + var g = _node(LINEAR_GRADIENT, {id:id}); + parent.appendChild(g); + } + else { + var g = _node(RADIAL_GRADIENT, { + id:id + }); + parent.appendChild(g); + } + + // the svg radial gradient seems to treat stops in the reverse + // order to how canvas does it. so we want to keep all the maths the same, but + // iterate the actual style declarations in reverse order, if the x indexes are not in order. + for (var i = 0; i < style.gradient.stops.length; i++) { + // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of + // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments. + // so, not too concerned about leaving this in for now. + var styleToUse = i; + if (dimensions.length == 8) + styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i; + else + styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i; + var stopColor = jsPlumb.util.convertStyle(style.gradient.stops[styleToUse][1], true); + var s = _node(STOP, {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor}); + g.appendChild(s); + } + var applyGradientTo = style.strokeStyle ? STROKE : FILL; + node.setAttribute(STYLE, applyGradientTo + ":url(#" + id + ")"); + }, + _applyStyles = function(parent, node, style, dimensions, uiComponent) { + + if (style.gradient) { + _updateGradient(parent, node, style, dimensions, uiComponent); + } + else { + // make sure we clear any existing gradient + _clearGradient(parent); + node.setAttribute(STYLE, ""); + } + + node.setAttribute(FILL, style.fillStyle ? jsPlumb.util.convertStyle(style.fillStyle, true) : NONE); + node.setAttribute(STROKE, style.strokeStyle ? jsPlumb.util.convertStyle(style.strokeStyle, true) : NONE); + if (style.lineWidth) { + node.setAttribute(STROKE_WIDTH, style.lineWidth); + } + + // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like + // the syntax in VML but is actually kind of nasty: values are given in the pixel + // coordinate space, whereas in VML they are multiples of the width of the stroked + // line, which makes a lot more sense. for that reason, jsPlumb is supporting both + // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from + // VML, which will be the preferred method. the code below this converts a dashstyle + // attribute given in terms of stroke width into a pixel representation, by using the + // stroke's lineWidth. + if (style[DASHSTYLE] && style[LINE_WIDTH] && !style[STROKE_DASHARRAY]) { + var sep = style[DASHSTYLE].indexOf(",") == -1 ? " " : ",", + parts = style[DASHSTYLE].split(sep), + styleToUse = ""; + parts.forEach(function(p) { + styleToUse += (Math.floor(p * style.lineWidth) + sep); + }); + node.setAttribute(STROKE_DASHARRAY, styleToUse); + } + else if(style[STROKE_DASHARRAY]) { + node.setAttribute(STROKE_DASHARRAY, style[STROKE_DASHARRAY]); + } + + // extra attributes such as join type, dash offset. + for (var i in svgAttributeMap) { + if (style[i]) { + node.setAttribute(svgAttributeMap[i], style[i]); + } + } + }, + _decodeFont = function(f) { + var r = /([0-9].)(p[xt])\s(.*)/; + var bits = f.match(r); + return {size:bits[1] + bits[2], font:bits[3]}; + }, + _classManip = function(el, add, clazz) { + var classesToAddOrRemove = clazz.split(" "), + className = el.className, + curClasses = className.baseVal.split(" "); + + for (var i = 0; i < classesToAddOrRemove.length; i++) { + if (add) { + if (curClasses.indexOf(classesToAddOrRemove[i]) == -1) + curClasses.push(classesToAddOrRemove[i]); + } + else { + var idx = curClasses.indexOf(classesToAddOrRemove[i]); + if (idx != -1) + curClasses.splice(idx, 1); + } + } + + el.className.baseVal = curClasses.join(" "); + }, + _addClass = function(el, clazz) { + _classManip(el, true, clazz); + }, + _removeClass = function(el, clazz) { + _classManip(el, false, clazz); + }; + + /** + utility methods for other objects to use. + */ + jsPlumb.util.svg = { + addClass:_addClass, + removeClass:_removeClass + }; + + /* + * Base class for SVG components. + */ + //var SvgComponent = function(cssClass, originalArgs, pointerEventsSpec) { + var SvgComponent = function(params) { + var self = this, + pointerEventsSpec = params.pointerEventsSpec || "all"; + jsPlumb.jsPlumbUIComponent.apply(this, params.originalArgs); + self.canvas = null, self.path = null, self.svg = null; + + var clazz = params.cssClass + " " + (params.originalArgs[0].cssClass || ""), + svgParams = { + "style":"", + "width":0, + "height":0, + "pointer-events":pointerEventsSpec, + "position":"absolute" + }; + if (self.tooltip) svgParams["label"] = self.tooltip; + self.svg = _node("svg", svgParams); + if (params.useDivWrapper) { + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.sizeCanvas(self.canvas,0,0,1,1); + self.canvas.className = clazz; + if (self.tooltip) self.canvas.setAttribute("label", self.tooltip); + } + else { + _attr(self.svg, { "class":clazz }); + self.canvas = self.svg; + } + + jsPlumb.appendElement(self.canvas, params.originalArgs[0]["parent"]); + if (params.useDivWrapper) self.canvas.appendChild(self.svg); + + // TODO this displayElement stuff is common between all components, across all + // renderers. would be best moved to jsPlumbUIComponent. + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + this.paint = function(d, style, anchor) { + if (style != null) { + var x = d[0], y = d[1]; + if (params.useDivWrapper) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + x = 0, y = 0; + } + _attr(self.svg, { + "style":_pos([x, y, d[2], d[3]]), + "width": d[2], + "height": d[3] + }); + self._paint.apply(this, arguments); + } + }; + }; + + /* + * Base class for SVG connectors. + */ + var SvgConnector = jsPlumb.SvgConnector = function(params) { + var self = this; + SvgComponent.apply(this, [ { cssClass:params["_jsPlumb"].connectorClass, originalArgs:arguments, pointerEventsSpec:"none", tooltip:params.tooltip } ]); + this._paint = function(d, style) { + var p = self.getPath(d), a = { "d":p }, outlineStyle = null; + a["pointer-events"] = "all"; + + // outline style. actually means drawing an svg object underneath the main one. + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = jsPlumb.CurrentLibrary.extend({}, style); + outlineStyle.strokeStyle = jsPlumb.util.convertStyle(style.outlineColor); + outlineStyle.lineWidth = outlineStrokeWidth; + + if (self.bgPath == null) { + self.bgPath = _node("path", a); + self.svg.appendChild(self.bgPath); + self.attachListeners(self.bgPath, self); + } + else { + _attr(self.bgPath, a); + } + + _applyStyles(self.svg, self.bgPath, outlineStyle, d, self); + } + + + // test - see below + // a["clip-path"]= "url(#testClip)"; + + if (self.path == null) { + self.path = _node("path", a); + self.svg.appendChild(self.path); + self.attachListeners(self.path, self); + + /* + this is a test of a clip path. i'm looking into using one of these to animate a jsplumb connection. + you could do this by walking along the line, stepping along a little at a time, and setting the clip + path to extend as far as that point. + + self.clip = _node("clipPath", {id:"testClip", clipPathUnits:"objectBoundingBox"}); + self.svg.appendChild(self.clip); + self.clip.appendChild(_node("rect", { + x:"0",y:"0",width:"0.5",height:"1" + })); + */ + } + else { + _attr(self.path, a); + } + + _applyStyles(self.svg, self.path, style, d, self); + }; + + this.reattachListeners = function() { + if (self.bgPath) self.reattachListenersForElement(self.bgPath, self); + if (self.path) self.reattachListenersForElement(self.path, self); + }; + + }; + + /* + * SVG Bezier Connector + */ + jsPlumb.Connectors.svg.Bezier = function(params) { + jsPlumb.Connectors.Bezier.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]; }; + }; + + /* + * SVG straight line Connector + */ + jsPlumb.Connectors.svg.Straight = function(params) { + jsPlumb.Connectors.Straight.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; }; + }; + + jsPlumb.Connectors.svg.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "M " + dimensions[4] + "," + dimensions[5]; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)]; + } + // finally draw a line to the end + p = p + " " + dimensions[6] + "," + dimensions[7]; + return p; + }; + }; + + /* + * Base class for SVG endpoints. + */ + var SvgEndpoint = function(params) { + var self = this; + SvgComponent.apply(this, [ { + cssClass:params["_jsPlumb"].endpointClass, + originalArgs:arguments, + pointerEventsSpec:"all", + useDivWrapper:true + } ]); + this._paint = function(d, style) { + var s = jsPlumb.extend({}, style); + if (s.outlineColor) { + s.strokeWidth = s.outlineWidth; + s.strokeStyle = jsPlumb.util.convertStyle(s.outlineColor, true); + } + + if (self.node == null) { + self.node = self.makeNode(d, s); + self.svg.appendChild(self.node); + self.attachListeners(self.node, self); + } + _applyStyles(self.svg, self.node, s, d, self); + _pos(self.node, d); + }; + + this.reattachListeners = function() { + if (self.node) self.reattachListenersForElement(self.node, self); + }; + }; + + /* + * SVG Dot Endpoint + */ + jsPlumb.Endpoints.svg.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("circle", { + "cx" : d[2] / 2, + "cy" : d[3] / 2, + "r" : d[2] / 2 + }); + }; + }; + + /* + * SVG Rectangle Endpoint + */ + jsPlumb.Endpoints.svg.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("rect", { + "width":d[2], + "height":d[3] + }); + }; + }; + + /* + * SVG Image Endpoint is the default image endpoint. + */ + jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image; + /* + * Blank endpoint in svg renderer is the default Blank endpoint. + */ + jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank; + /* + * Label endpoint in svg renderer is the default Label endpoint. + */ + jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label; + + var AbstractSvgArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + this.isAppendedAtTopLevel = false; + var self = this, path =null; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + } + var clazz = originalArgs && (originalArgs.length == 1) ? (originalArgs[0].cssClass || "") : ""; + + _attr(path, { + "d" : makePath(d), + "class" : clazz, + stroke : strokeStyle ? strokeStyle : null, + fill : fillStyle ? fillStyle : null + }); + }; + var makePath = function(d) { + return "M" + d.hxy.x + "," + d.hxy.y + + " L" + d.tail[0].x + "," + d.tail[0].y + + " L" + d.cxy.x + "," + d.cxy.y + + " L" + d.tail[1].x + "," + d.tail[1].y + + " L" + d.hxy.x + "," + d.hxy.y; + }; + this.reattachListeners = function() { + if (path) self.reattachListenersForElement(path, self); + }; + }; + + jsPlumb.Overlays.svg.Arrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.svg.PlainArrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.svg.Diamond = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; + + // a test + jsPlumb.Overlays.svg.GuideLines = function() { + var path = null, self = this, path2 = null, p1_1, p1_2; + jsPlumb.Overlays.GuideLines.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + + p1_1 = _node("path"); + connector.svg.appendChild(p1_1); + self.attachListeners(p1_1, connector); + self.attachListeners(p1_1, self); + + p1_2 = _node("path"); + connector.svg.appendChild(p1_2); + self.attachListeners(p1_2, connector); + self.attachListeners(p1_2, self); + + } + + _attr(path, { + "d" : makePath(d[0], d[1]), + stroke : "red", + fill : null + }); + + _attr(p1_1, { + "d" : makePath(d[2][0], d[2][1]), + stroke : "blue", + fill : null + }); + + _attr(p1_2, { + "d" : makePath(d[3][0], d[3][1]), + stroke : "green", + fill : null + }); + }; + + var makePath = function(d1, d2) { + return "M " + d1.x + "," + d1.y + + " L" + d2.x + "," + d2.y; + }; + + }; +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the HTML5 canvas renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + +// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS ******************************************************************* + + // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. + var _connectionBeingDragged = null, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, + _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }; + + /* + * Class:CanvasMouseAdapter + * Provides support for mouse events on canvases. + */ + var CanvasMouseAdapter = function() { + var self = this; + self.overlayPlacements = []; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + jsPlumb.EventGenerator.apply(this, arguments); + /** + * returns whether or not the given event is ojver a painted area of the canvas. + */ + this._over = function(e) { + var o = _getOffset(_getElementObject(self.canvas)), + pageXY = _pageXY(e), + x = pageXY[0] - o.left, y = pageXY[1] - o.top; + if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + return false; + }; + + var _mouseover = false, _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false, + _nullSafeHasClass = function(el, clazz) { + return el != null && _hasClass(el, clazz); + }; + this.mousemove = function(e) { + var pageXY = _pageXY(e), clientXY = _clientXY(e), + ee = document.elementFromPoint(clientXY[0], clientXY[1]), + eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); + var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); + if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.fire("mouseenter", self, e); + return true; + } + // TODO here there is a remote chance that the overlay the mouse moved onto + // is actually not an overlay for the current component. a more thorough check would + // be to ensure the overlay belonged to the current component. + else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { + _mouseover = false; + self.fire("mouseexit", self, e); + } + self.fire("mousemove", self, e); + }; + + this.click = function(e) { + // to filter right click in FF, i could compare e.button. 0 means left mouse button; 1 middle, 2 right. + //console.log(e.button); + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); + self.fire("mousedown", self, e); + } + }; + + this.mouseup = function(e) { + _mouseDown = false; + self.fire("mouseup", self, e); + }; + }; + + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + jsPlumb.appendElement(canvas, params.parent); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + params["_jsPlumb"].getId(canvas, params.uuid); + if (params.tooltip) canvas.setAttribute("label", params.tooltip); + + return canvas; + }; + + /** + * Class:CanvasConnector + * Superclass for Canvas Connector renderers. + */ + var CanvasConnector = jsPlumb.CanvasConnector = function(params) { + + CanvasMouseAdapter.apply(this, arguments); + + var _paintOneStyle = function(dim, aStyle) { + self.ctx.save(); + jsPlumb.extend(self.ctx, aStyle); + if (aStyle.gradient) { + var g = self.createGradient(dim, self.ctx); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + self.ctx.strokeStyle = g; + } + self._paint(dim); + self.ctx.restore(); + }; + + var self = this, + clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:params.tooltip + }); + self.ctx = self.canvas.getContext("2d"); + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { return displayElements; }; + this.appendDisplayElement = function(el) { displayElements.push(el); }; + + self.paint = function(dim, style) { + if (style != null) { + jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + _paintOneStyle(dim, outlineStyle); + } + _paintOneStyle(dim, style); + } + }; + }; + + /** + * Class:CanvasEndpoint + * Superclass for Canvas Endpoint renderers. + */ + var CanvasEndpoint = function(params) { + var self = this; + CanvasMouseAdapter.apply(this, arguments); + var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""), + canvasParams = { + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:self.tooltip + }; + self.canvas = _newCanvas(canvasParams); + self.ctx = self.canvas.getContext("2d"); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + } + + self._paint.apply(this, arguments); + }; + }; + + jsPlumb.Endpoints.canvas.Dot = function(params) { + jsPlumb.Endpoints.Dot.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + var self = this, + parseValue = function(value) { + try { return parseInt(value); } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }, + calculateAdjustments = function(gradient) { + var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius; + gradient.offset && (offsetAdjustment = parseValue(gradient.offset)); + gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius)); + return [offsetAdjustment, innerRadius]; + }; + this._paint = function(d, style, anchor) { + if (style != null) { + var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + if (style.gradient) { + var adjustments = calculateAdjustments(style.gradient), + yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0], + xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0], + g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + ctx.beginPath(); + ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + } + }; + }; + + jsPlumb.Endpoints.canvas.Rectangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) { + + var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + + /* canvas gradient */ + if (style.gradient) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, d[2], d[3]); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + jsPlumb.Endpoints.canvas.Triangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Triangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) + { + var width = d[2], height = d[3], x = d[0], y = d[1], + ctx = self.canvas.getContext('2d'), + offsetX = 0, offsetY = 0, angle = 0, + orientation = anchor.getOrientation(self); + + if( orientation[0] == 1 ) { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = style.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + /* + * Canvas Image Endpoint: uses the default version, which creates an tag. + */ + jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image; + + /* + * Blank endpoint in all renderers is just the default Blank endpoint. + */ + jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank; + + /* + * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Bezier = function() { + var self = this; + jsPlumb.Connectors.Bezier.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO i doubt this handles the case that source and target are swapped. + this.createGradient = function(dim, ctx, swap) { + return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + /* + * Canvas straight line Connector. Draws a straight line onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Straight = function() { + var self = this; + jsPlumb.Connectors.Straight.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO this does not handle the case that src and target are swapped. + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + jsPlumb.Connectors.canvas.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + +// ********************************* END OF CANVAS RENDERERS ******************************************************************* + + jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label; + + /** + * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. + */ + var CanvasOverlay = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }; + + var AbstractCanvasArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + CanvasOverlay.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + var ctx = connector.ctx; + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(d.hxy.x, d.hxy.y); + ctx.lineTo(d.tail[0].x, d.tail[0].y); + ctx.lineTo(d.cxy.x, d.cxy.y); + ctx.lineTo(d.tail[1].x, d.tail[1].y); + ctx.lineTo(d.hxy.x, d.hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + }; + + jsPlumb.Overlays.canvas.Arrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.canvas.PlainArrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.canvas.Diamond = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the jQuery adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ +/* + * the library specific functions, such as find offset, get id, get attribute, extend etc. + * the full list is: + * + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getDropScope gets the drop scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getPageXY gets the page event's xy location. + * getParent gets the parent of some element. + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * hasClass returns whether or not the given element has the given class. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + * trigger triggers some event on an element. + * unbind unbinds some listener from some element. + */ +(function($) { + + //var getBoundingClientRectSupported = "getBoundingClientRect" in document.documentElement; + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el[0].className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.addClass(el[0], clazz); + } + } + catch (e) { + // SVGAnimatedString not supported; no problem. + } + el.addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * appends the given child to the given parent. + */ + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).append(child); + }, + + /** + * event binding wrapper. it just so happens that jQuery uses 'bind' also. yui3, for example, + * uses 'on'. + */ + bind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.bind(event, callback); + }, + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step', + 'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete' + }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + getDocumentElement : function() { return document; }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + }, + + getDragScope : function(el) { + return el.draggable("option", "scope"); + }, + + getDropScope : function(el) { + return el.droppable("option", "scope"); + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + * + */ + getElementObject : function(el) { + return typeof(el)=='string' ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).parent(); + }, + + getScrollLeft : function(el) { + return el.scrollLeft(); + }, + + getScrollTop : function(el) { + return el.scrollTop(); + }, + + getSelector : function(spec) { + return $(spec); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e.length > 0 ? e[0].tagName : null; + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + + // this code is a workaround for the case that the element being dragged has a margin set on it. jquery UI passes + // in the wrong offset if the element has a margin (it doesn't take the margin into account). the getBoundingClientRect + // method, which is in pretty much all browsers now, reports the right numbers. but it introduces a noticeable lag, which + // i don't like. + + /*if ( getBoundingClientRectSupported ) { + var r = eventArgs[1].helper[0].getBoundingClientRect(); + return { left : r.left, top: r.top }; + } else {*/ + if (eventArgs.length == 1) { + return { left: eventArgs[0].pageX, top:eventArgs[0].pageY }; + } + else { + var ui = eventArgs[1], _offset = ui.offset; + return _offset || ui.absolutePosition; + } + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options) { + options = options || {}; + // remove helper directive if present. + options.helper = null; + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.draggable(options); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.droppable(options); + }, + + isAlreadyDraggable : function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return el.hasClass("ui-draggable"); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el[0].className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.removeClass(el[0], clazz); + } + } + catch (e) { + // SVGAnimatedString not supported; no problem. + } + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).remove(); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * sets the drag scope. probably time for a setDragOption method (roll this and the one above together) + * @param el + * @param scope + */ + setDragScope : function(el, scope) { + el.draggable("option", "scope", scope); + }, + + setOffset : function(el, o) { + jsPlumb.CurrentLibrary.getElementObject(el).offset(o); + }, + + /** + * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself. + * the other libraries do not. yui, in fact, cannot even pass an original event. we have to pull out stuff + * from the originalEvent to put in an options object for YUI. + * @param el + * @param event + * @param originalEvent + */ + trigger : function(el, event, originalEvent) { + //originalEvent.stopPropagation(); + //jsPlumb.CurrentLibrary.getElementObject(el).trigger(originalEvent); + var h = jQuery._data(jsPlumb.CurrentLibrary.getElementObject(el)[0], "handle"); + h(originalEvent); + //originalEvent.stopPropagation(); + }, + + /** + * event unbinding wrapper. it just so happens that jQuery uses 'unbind' also. yui3, for example, + * uses..something else. + */ + unbind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.unbind(event, callback); + } + }; + + $(document).ready(jsPlumb.init); + +})(jQuery); +(function(){if("undefined"==typeof Math.sgn)Math.sgn=function(a){return 0==a?0:0n?n=l:lb.location)b.location=0;return t(a,b.location)},nearestPointOnCurve:function(a,b){var f=u(a,b);return{point:r(b,b.length-1,f.location,null,null),location:f.location}},pointOnCurve:p,pointAlongCurveFrom:function(a,b,f){return s(a,b,f).point},perpendicularToCurveAt:function(a,b,f,d){b=s(a,b,null==d?0:d);a=t(a,b.location);d=Math.atan(-1/a);a=f/2*Math.sin(d);f=f/2*Math.cos(d);return[{x:b.point.x+f,y:b.point.y+a},{x:b.point.x-f,y:b.point.y-a}]}}})(); \ No newline at end of file diff --git a/archive/1.3.4/jsPlumb-1.3.4-RC1.js b/archive/1.3.4/jsPlumb-1.3.4-RC1.js new file mode 100644 index 000000000..827b6c963 --- /dev/null +++ b/archive/1.3.4/jsPlumb-1.3.4-RC1.js @@ -0,0 +1,4811 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the jsPlumb core code. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + /** + * Class:jsPlumb + * The jsPlumb engine, registered as a static object in the window. This object contains all of the methods you will use to + * create and maintain Connections and Endpoints. + */ + + var canvasAvailable = !!document.createElement('canvas').getContext, + svgAvailable = !!window.SVGAngle || document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"), + // TODO what is a good test for VML availability? aside from just assuming its there because nothing else is. + vmlAvailable = !(canvasAvailable | svgAvailable); + + // finds the index of some value in an array, using a deep equals test. + var _findIndex = function(a, v, b, s) { + var _eq = function(o1, o2) { + if (o1 === o2) return true; + else if (typeof o1 == "object" && typeof o2 == "object") { + var same = true; + for ( var propertyName in o1) { + if (!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for ( var propertyName in o2) { + if (!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for ( var i = +b || 0, l = a.length; i < l; i++) { + if (_eq(a[i], v)) + return i; + } + return -1; + }, + _findWithFunction = function(a, f) { + for (var i = 0; i < a.length; i++) if (f(a[i])) return i; + return -1; + }, + _removeWithFunction = function(a, f) { + var idx = _findWithFunction(a, f); + if (idx > -1) a.splice(idx, 1); + return idx != -1; + }; + + + // for those browsers that dont have it. they still don't have it! but at least they won't crash. + if (!window.console) + window.console = { time:function(){}, timeEnd:function(){}, group:function(){}, groupEnd:function(){}, log:function(){} }; + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = [], map[key] = l; + } + l.push(value); + return l; + }, + _connectionBeingDragged = null, + _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }, + _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }, + _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }, + _logEnabled = true, + _log = function() { + if (_logEnabled && typeof console != "undefined") { + try { + var msg = arguments[arguments.length - 1]; + console.log(msg); + } + catch (e) {} + } + }, + _group = function(g) { + if (_logEnabled && typeof console != "undefined") console.group(g); + }, + _groupEnd = function(g) { + if (_logEnabled && typeof console != "undefined") console.groupEnd(g); + }, + _time = function(t) { + if (_logEnabled && typeof console != "undefined") console.time(t); + }, + _timeEnd = function(t) { + if (_logEnabled && typeof console != "undefined") console.timeEnd(t); + }; + + /** + * EventGenerator + * Superclass for objects that generate events - jsPlumb extends this, as does jsPlumbUIComponent, which all the UI elements extend. + */ + EventGenerator = function() { + var _listeners = {}, self = this; + + // this is a list of events that should re-throw any errors that occur during their dispatch. as of 1.3.0 this is private to + // jsPlumb, but it seems feasible that people might want to manipulate this list. the thinking is that we don't want event + // listeners to bring down jsPlumb - or do we. i can't make up my mind about this, but i know i want to hear about it if the "ready" + // event fails, because then my page has most likely not initialised. so i have this halfway-house solution. it will be interesting + // to hear what other people think. + var eventsToDieOn = [ "ready" ]; + + /* + * Binds a listener to an event. + * + * Parameters: + * event - name of the event to bind to. + * listener - function to execute. + */ + this.bind = function(event, listener) { + _addToList(_listeners, event, listener); + }; + /* + * Fires an update for the given event. + * + * Parameters: + * event - event to fire + * value - value to pass to the event listener(s). + * o riginalEvent - the original event from the browser + */ + this.fire = function(event, value, originalEvent) { + if (_listeners[event]) { + for ( var i = 0; i < _listeners[event].length; i++) { + // doing it this way rather than catching and then possibly re-throwing means that an error propagated by this + // method will have the whole call stack available in the debugger. + if (_findIndex(eventsToDieOn, event) != -1) + _listeners[event][i](value, originalEvent); + else { + // for events we don't want to die on, catch and log. + try { + _listeners[event][i](value, originalEvent); + } catch (e) { + _log("jsPlumb: fire failed for event " + event + " : " + e); + } + } + } + } + }; + /* + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + this.clearListeners = function(event) { + if (event) + delete _listeners[event]; + else { + delete _listeners; + _listeners = {}; + } + }; + + this.getListener = function(forEvent) { + return _listeners[forEvent]; + }; + }, + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + _timestamp = function() { return "" + (new Date()).getTime(); }, + + /* + * Class:jsPlumbUIComponent + * Abstract superclass for UI components Endpoint and Connection. Provides the abstraction of paintStyle/hoverPaintStyle, + * and also extends EventGenerator to provide the bind and fire methods. + */ + jsPlumbUIComponent = function(params) { + var self = this, a = arguments, _hover = false, parameters = params.parameters || {}, idPrefix = self.idPrefix, + id = idPrefix + (new Date()).getTime(); + self._jsPlumb = params["_jsPlumb"]; + self.getId = function() { return id; }; + self.tooltip = params.tooltip; + self.hoverClass = params.hoverClass; + + // all components can generate events + EventGenerator.apply(this); + // all components get this clone function. + // TODO issue 116 showed a problem with this - it seems 'a' that is in + // the clone function's scope is shared by all invocations of it, the classic + // JS closure problem. for now, jsPlumb does a version of this inline where + // it used to call clone. but it would be nice to find some time to look + // further at this. + this.clone = function() { + var o = new Object(); + self.constructor.apply(o, a); + return o; + }; + + this.getParameter = function(name) { return parameters[name]; }, + this.getParameters = function() { return parameters; }, + this.setParameter = function(name, value) { parameters[name] = value; }, + this.setParameters = function(p) { parameters = p; }, + this.overlayPlacements = [], + this.paintStyle = null, + this.hoverPaintStyle = null; + + // user can supply a beforeDetach callback, which will be executed before a detach + // is performed; returning false prevents the detach. + var beforeDetach = params.beforeDetach; + this.isDetachAllowed = function(connection) { + var r = true; + if (beforeDetach) { + try { + r = beforeDetach(connection); + } + catch (e) { _log("jsPlumb: beforeDetach callback failed", e); } + } + return r; + }; + + // user can supply a beforeDrop callback, which will be executed before a dropped + // connection is confirmed. user can return false to reject connection. + var beforeDrop = params.beforeDrop; + this.isDropAllowed = function(sourceId, targetId, scope) { + var r = self._jsPlumb.checkCondition("beforeDrop", { sourceId:sourceId, targetId:targetId, scope:scope }); + if (beforeDrop) { + try { + r = beforeDrop({ sourceId:sourceId, targetId:targetId, scope:scope }); + } + catch (e) { _log("jsPlumb: beforeDrop callback failed", e); } + } + return r; + }; + + // helper method to update the hover style whenever it, or paintStyle, changes. + // we use paintStyle as the foundation and merge hoverPaintStyle over the + // top. + var _updateHoverStyle = function() { + if (self.paintStyle && self.hoverPaintStyle) { + var mergedHoverStyle = {}; + jsPlumb.extend(mergedHoverStyle, self.paintStyle); + jsPlumb.extend(mergedHoverStyle, self.hoverPaintStyle); + delete self["hoverPaintStyle"]; + // we want the fillStyle of paintStyle to override a gradient, if possible. + if (mergedHoverStyle.gradient && self.paintStyle.fillStyle) + delete mergedHoverStyle["gradient"]; + self.hoverPaintStyle = mergedHoverStyle; + } + }; + + /* + * Sets the paint style and then repaints the element. + * + * Parameters: + * style - Style to use. + */ + this.setPaintStyle = function(style, doNotRepaint) { + self.paintStyle = style; + self.paintStyleInUse = self.paintStyle; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /* + * Sets the paint style to use when the mouse is hovering over the element. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the component will not be repainted. useful when setting things up initially. + */ + this.setHoverPaintStyle = function(style, doNotRepaint) { + self.hoverPaintStyle = style; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /* + * sets/unsets the hover state of this element. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + this.setHover = function(hover, ignoreAttachedElements, timestamp) { + // while dragging, we ignore these events. this keeps the UI from flashing and + // swishing and whatevering. + if (!self._jsPlumb.currentlyDragging) { + + _hover = hover; + if (self.hoverClass != null && self.canvas != null) { + if (hover) + jpcl.addClass(self.canvas, self.hoverClass); + else + jpcl.removeClass(self.canvas, self.hoverClass); + } + if (self.hoverPaintStyle != null) { + self.paintStyleInUse = hover ? self.hoverPaintStyle : self.paintStyle; + timestamp = timestamp || _timestamp(); + self.repaint({timestamp:timestamp, recalc:false}); + } + // get the list of other affected elements, if supported by this component. + // for a connection, its the endpoints. for an endpoint, its the connections! surprise. + if (self.getAttachedElements && !ignoreAttachedElements) + _updateAttachedElements(hover, _timestamp(), self); + } + }; + + this.isHover = function() { return _hover; }; + + var jpcl = jsPlumb.CurrentLibrary, + events = [ "click", "dblclick", "mouseenter", "mouseout", "mousemove", "mousedown", "mouseup" ], + eventFilters = { "mouseout":"mouseexit" }, + bindOne = function(o, c, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.bind(o, evt, function(ee) { + c.fire(filteredEvent, c, ee); + }); + }, + unbindOne = function(o, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.unbind(o, evt); + }; + + this.attachListeners = function(o, c) { + for (var i = 0; i < events.length; i++) { + bindOne(o, c, events[i]); + } + }; + + var _updateAttachedElements = function(state, timestamp, sourceElement) { + var affectedElements = self.getAttachedElements(); // implemented in subclasses + if (affectedElements) { + for (var i = 0; i < affectedElements.length; i++) { + if (!sourceElement || sourceElement != affectedElements[i]) + affectedElements[i].setHover(state, true, timestamp); // tell the attached elements not to inform their own attached elements. + } + } + }; + + this.reattachListenersForElement = function(o, c) { + for (var i = 0; i < events.length; i++) + unbindOne(o, events[i]); + self.attachListeners(o, c); + }; + }; + + var jsPlumbInstance = function(_defaults) { + + /* + * Property: Defaults + * + * These are the default settings for jsPlumb. They are what will be used if you do not supply specific pieces of information + * to the various API calls. A convenient way to implement your own look and feel can be to override these defaults + * by including a script somewhere after the jsPlumb include, but before you make any calls to jsPlumb. + * + * Properties: + * - *Anchor* The default anchor to use for all connections (both source and target). Default is "BottomCenter". + * - *Anchors* The default anchors to use ([source, target]) for all connections. Defaults are ["BottomCenter", "BottomCenter"]. + * - *Connector* The default connector definition to use for all connections. Default is "Bezier". + * - *Container* Optional selector or element id that instructs jsPlumb to append elements it creates to a specific element. + * - *DragOptions* The default drag options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *DropOptions* The default drop options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *Endpoint* The default endpoint definition to use for all connections (both source and target). Default is "Dot". + * - *Endpoints* The default endpoint definitions ([ source, target ]) to use for all connections. Defaults are ["Dot", "Dot"]. + * - *EndpointStyle* The default style definition to use for all endpoints. Default is fillStyle:"#456". + * - *EndpointStyles* The default style definitions ([ source, target ]) to use for all endpoints. Defaults are empty. + * - *EndpointHoverStyle* The default hover style definition to use for all endpoints. Default is null. + * - *EndpointHoverStyles* The default hover style definitions ([ source, target ]) to use for all endpoints. Defaults are null. + * - *HoverPaintStyle* The default hover style definition to use for all connections. Defaults are null. + * - *LabelStyle* The default style to use for label overlays on connections. + * - *LogEnabled* Whether or not the jsPlumb log is enabled. defaults to false. + * - *Overlays* The default overlay definitions. Defaults to an empty list. + * - *MaxConnections* The default maximum number of connections for an Endpoint. Defaults to 1. + * - *MouseEventsEnabled* Whether or not mouse events are enabled when using the canvas renderer. Defaults to true. + * The idea of this is just to give people a way to prevent all the mouse listeners from activating if they know they won't need mouse events. + * - *PaintStyle* The default paint style for a connection. Default is line width of 8 pixels, with color "#456". + * - *RenderMode* What mode to use to paint with. If you're on IE<9, you don't really get to choose this. You'll just get VML. Otherwise, the jsPlumb default is to use Canvas elements. + * - *Scope* The default "scope" to use for connections. Scope lets you assign connections to different categories. + */ + this.Defaults = { + Anchor : "BottomCenter", + Anchors : [ null, null ], + ConnectionsDetachable : true, + Connector : "Bezier", + Container : null, + DragOptions : { }, + DropOptions : { }, + Endpoint : "Dot", + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : "#456" }, + EndpointStyles : [ null, null ], + EndpointHoverStyle : null, + EndpointHoverStyles : [ null, null ], + HoverPaintStyle : null, + LabelStyle : { color : "black" }, + LogEnabled : false, + Overlays : [ ], + MaxConnections : 1, + MouseEventsEnabled : true, + PaintStyle : { lineWidth : 8, strokeStyle : "#456" }, + //Reattach:false, + RenderMode : "svg", + Scope : "jsPlumb_DefaultScope" + }; + if (_defaults) jsPlumb.extend(this.Defaults, _defaults); + + this.logEnabled = this.Defaults.LogEnabled; + + EventGenerator.apply(this); + var _bb = this.bind; + this.bind = function(event, fn) { + if ("ready" === event && initialized) fn(); + else _bb(event, fn); + }; + var _currentInstance = this, + log = null, + repaintFunction = function() { + jsPlumb.repaintEverything(); + }, + automaticRepaint = true, + repaintEverything = function() { + if (automaticRepaint) + repaintFunction(); + }, + resizeTimer = null, + initialized = false, + connectionsByScope = {}, + /** + * map of element id -> endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + endpointsByElement = {}, + endpointsByUUID = {}, + offsets = {}, + offsetTimestamps = {}, + floatingConnections = {}, + draggableStates = {}, + _mouseEventsEnabled = this.Defaults.MouseEventsEnabled, + canvasList = [], + sizes = [], + //listeners = {}, // a map: keys are event types, values are lists of listeners. + DEFAULT_SCOPE = this.Defaults.Scope, + renderMode = null, // will be set in init() + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }, + + /** + * appends an element to some other element, which is calculated as follows: + * + * 1. if _currentInstance.Defaults.Container exists, use that element. + * 2. if the 'parent' parameter exists, use that. + * 3. otherwise just use the document body. + * + */ + _appendElement = function(el, parent) { + if (_currentInstance.Defaults.Container) + jsPlumb.CurrentLibrary.appendElement(el, _currentInstance.Defaults.Container); + else if (!parent) + document.body.appendChild(el); + else + jsPlumb.CurrentLibrary.appendElement(el, parent); + }, + + _curIdStamp = 1, + _idstamp = function() { return "" + _curIdStamp++; }, + + /** + * YUI, for some reason, put the result of a Y.all call into an object that contains + * a '_nodes' array, instead of handing back an array-like object like the other + * libraries do. + */ + _convertYUICollection = function(c) { + return c._nodes ? c._nodes : c; + }, + + /** + * Draws an endpoint and its connections. this is the main entry point into drawing connections as well + * as endpoints, since jsPlumb is endpoint-centric under the hood. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + _draw = function(element, ui, timestamp) { + var id = _getAttribute(element, "id"); + _currentInstance.anchorManager.redraw(id, ui, timestamp); + }, + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }, + + /** + * gets an Endpoint by uuid. + */ + _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }, + + /** + * inits a draggable if it's not already initialised. + */ + _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? false : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element) && !jsPlumb.CurrentLibrary.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + draggableStates[_getId(element)] = true; + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options, false); + } + } + }, + + /* + * prepares a final params object that can be passed to _newConnection, taking into account defaults, events, etc. + */ + _prepareConnectionParams = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + // copy in any connectorOverlays that were specified on the source endpoint. + // it doesnt copy target endpoint overlays. i'm not sure if we want it to or not. + if (_p.sourceEndpoint && _p.sourceEndpoint.connectorOverlays) { + _p.overlays = _p.overlays || []; + for (var i = 0; i < _p.sourceEndpoint.connectorOverlays.length; i++) { + _p.overlays.push(_p.sourceEndpoint.connectorOverlays[i]); + } + } + + // tooltip. params.tooltip takes precedence, then sourceEndpoint.connectorTooltip. + _p.tooltip = params.tooltip; + if (!_p.tooltip && _p.sourceEndpoint && _p.sourceEndpoint.connectorTooltip) + _p.tooltip = _p.sourceEndpoint.connectorTooltip; + + if (_p.target && !_p.target.endpoint) { + var tid = _getId(_p.target), + tep =_targetEndpointDefinitions[tid]; + + var overrideOne = function(singlePropertyName, pluralPropertyName, tepProperty, tep) { + if (tep[tepProperty]) { + if (_p[pluralPropertyName]) _p[pluralPropertyName][1] = tep[tepProperty]; + else if (_p[singlePropertyName]) { + _p[pluralPropertyName] = [ _p[singlePropertyName], tep[tepProperty] ]; + _p[singlePropertyName] = null; + } + else _p[pluralPropertyName] = [ null, tep[tepProperty] ]; + } + }; + + if (tep) { + overrideOne("endpoint", "endpoints", "endpoint", tep); + overrideOne("endpointStyle", "endpointStyles", "paintStyle", tep); + overrideOne("endpointHoverStyle", "endpointHoverStyles", "hoverPaintStyle", tep); + } + } + + // dynamic anchors. backwards compatibility here: from 1.2.6 onwards you don't need to specify "dynamicAnchors". the fact that some anchor consists + // of multiple definitions is enough to tell jsPlumb you want it to be dynamic. + if (_p.dynamicAnchors) { + // these can either be an array of anchor coords, which we will use for both source and target, or an object with {source:[anchors], target:[anchors]}, in which + // case we will use a different set for each element. + var a = _p.dynamicAnchors.constructor == Array, + sourceId = _p.sourceEndpoint ? _p.sourceEndpoint.elementId : _getId(_p.source), + targetId = _p.targetEndpoint ? _p.targetEndpoint.elementId : _getId(_p.target), + sa = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors, sourceId, _currentInstance)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.source, sourceId, _currentInstance)), + ta = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors, targetId, _currentInstance)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.target, targetId, _currentInstance)); + _p.anchors = [sa,ta]; + } + return _p; + }, + + _newConnection = function(params) { + var connectionFunc = jsPlumb.Defaults.ConnectionType || jsPlumb.getDefaultConnectionType(), + endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint, + parent = jsPlumb.CurrentLibrary.getParent; + + if (params.container) + params["parent"] = params.container; + else { + if (params.sourceEndpoint) + params["parent"] = params.sourceEndpoint.parent; + else if (params.source.constructor == endpointFunc) + params["parent"] = params.source.parent; + else params["parent"] = parent(params.source); + } + + params["_jsPlumb"] = _currentInstance; + var con = new connectionFunc(params); + con.id = "con_" + _idstamp(); + _eventFireProxy("click", "click", con); + _eventFireProxy("dblclick", "dblclick", con); + return con; + }, + + /** + * adds the connection to the backing model, fires an event if necessary and then redraws + */ + _finaliseConnection = function(jpc, params) { + params = params || {}; + // add to list of connections (by scope). + if (!jpc.suspendedEndpoint) + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + if (!params.doNotFireConnectionEvent && params.fireEvent !== false) { + _currentInstance.fire("jsPlumbConnection", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + } + // always inform the anchor manager + // except that if jpc has a suspended endpoint it's not true to say the + // connection is new; it has just (possibly) moved. the question is whether + // to make that call here or in the anchor manager. i think perhaps here. + _currentInstance.anchorManager.newConnection(jpc); + // force a paint + _draw(jpc.source); + }, + + _eventFireProxy = function(event, proxyEvent, obj) { + obj.bind(event, function(originalObject, originalEvent) { + _currentInstance.fire(proxyEvent, obj, originalEvent); + }); + }, + + /** + * for the given endpoint params, returns an appropriate parent element for the UI elements that will be added. + * this function is used by _newEndpoint (directly below), and also in the makeSource function in jsPlumb. + * + * the logic is to first look for a "container" member of params, and pass that back if found. otherwise we + * handoff to the 'getParent' function in the current library. + */ + _getParentFromParams = function(params) { + if (params.container) + return params.container; + else { + var tag = jsPlumb.CurrentLibrary.getTagName(params.source), + p = jsPlumb.CurrentLibrary.getParent(params.source); + if (tag.toLowerCase() === "td") + return jsPlumb.CurrentLibrary.getParent(p); + else return p; + } + }, + + /** + factory method to prepare a new endpoint. this should always be used instead of creating Endpoints + manually, since this method attaches event listeners and an id. + */ + _newEndpoint = function(params) { + var endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint; + params.parent = _getParentFromParams(params); + params["_jsPlumb"] = _currentInstance; + var ep = new endpointFunc(params); + ep.id = "ep_" + _idstamp(); + _eventFireProxy("click", "endpointClick", ep); + _eventFireProxy("dblclick", "endpointDblClick", ep); + return ep; + }, + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + _operation = function(elId, func, endpointFunc) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + if (endpointFunc) endpointFunc(endpoints[i]); + } + } + }, + /** + * perform an operation on all elements. + */ + _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }, + + /** + * helper to remove an element from the DOM. + */ + _removeElement = function(element, parent) { + if (element != null && element.parentNode != null) { + element.parentNode.removeChild(element); + } + }, + /** + * helper to remove a list of elements from the DOM. + */ + _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }, + /** + * helper method to remove an item from a list. + */ + _removeFromList = function(map, key, value) { + if (key != null) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete (l[i]); + l.splice(i, 1); + return true; + } + } + } + return false; + }, + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }, + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + _setVisible = function(el, state, alsoChangeEndpoints) { + state = state === "block"; + var endpointFunc = null; + if (alsoChangeEndpoints) { + if (state) endpointFunc = function(ep) { + ep.setVisible(true, true, true); + }; + else endpointFunc = function(ep) { + ep.setVisible(false, true, true); + }; + } + var id = _getAttribute(el, "id"); + _operation(id, function(jpc) { + if (state && alsoChangeEndpoints) { + // this test is necessary because this functionality is new, and i wanted to maintain backwards compatibility. + // this block will only set a connection to be visible if the other endpoint in the connection is also visible. + var oidx = jpc.sourceId === id ? 1 : 0; + if (jpc.endpoints[oidx].isVisible()) jpc.setVisible(true); + } + else // the default behaviour for show, and what always happens for hide, is to just set the visibility without getting clever. + jpc.setVisible(state); + }, endpointFunc); + }, + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? false : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }, + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + _toggleVisible = function(elId, changeEndpoints) { + var endpointFunc = null; + if (changeEndpoints) { + endpointFunc = function(ep) { + var state = ep.isVisible(); + ep.setVisible(!state); + }; + } + _operation(elId, function(jpc) { + var state = jpc.isVisible(); + jpc.setVisible(!state); + }, endpointFunc); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }, + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return offsets[elId]; + } + if (recalc || !offset) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + if (sizes[elId] == null) { + var s = _getElementObject(elId); + if (s != null) + sizes[elId] = _getSize(s); + } + } + + if(offsets[elId] && !offsets[elId].right) { + offsets[elId].right = offsets[elId].left + sizes[elId][0]; + offsets[elId].bottom = offsets[elId].top + sizes[elId][1]; + offsets[elId].width = sizes[elId][0]; + offsets[elId].height = sizes[elId][1]; + offsets[elId].centerx = offsets[elId].left + (offsets[elId].width / 2); + offsets[elId].centery = offsets[elId].top + (offsets[elId].height / 2); + } + return offsets[elId]; + }, + + _getCachedData = function(elId) { + var o = offsets[elId]; + if (!o) o = _updateOffset(elId); + return {o:o, s:sizes[elId]}; + }, + +/** + * gets an id for the given element, creating and setting one if + * necessary. + */ + _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else + id = "jsPlumb_" + _idstamp(); + _setAttribute(ele, "id", id); + } + return id; + }, + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'jsPlumb function failed : ' + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'wrapped function failed : ' + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = "_jsPlumb_connector"; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = "_jsPlumb_endpoint"; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = "_jsPlumb_overlay"; + + this.Anchors = {}; + + this.Connectors = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Endpoints = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Overlays = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + +// ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***************************************** + /* + * Function: bind + * Bind to an event on jsPlumb. + * + * Parameters: + * event - the event to bind. Available events on jsPlumb are: + * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback. + * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback. + * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback. + * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback. + * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback. + * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback. + * + * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event. + */ + + /* + * Function: clearListeners + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + +// *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *********************************************************** + + /* + Function: addClass + + Helper method to abstract out differences in setting css classes on the different renderer types. + */ + this.addClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.addClass(el, clazz); + }; + + /* + Function: removeClass + + Helper method to abstract out differences in setting css classes on the different renderer types. + */ + this.removeClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.removeClass(el, clazz); + }; + + /* + Function: hasClass + + Helper method to abstract out differences in testing for css classes on the different renderer types. + */ + this.hasClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.hasClass(el, clazz); + }; + + /* + Function: addEndpoint + + Adds an to a given element or elements. + + Parameters: + + el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + params - Object containing Endpoint constructor arguments. For more information, see . + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some + shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in + this object are anything that 'params' can contain. See . + + Returns: + The newly created , if el referred to a single element. Otherwise, an array of newly created s. + + See Also: + + */ + this.addEndpoint = function(el, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + //p.scope="_jsPlumb_DefaultScope"; + //p.scope="fff"; + // YUI wrapper + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + var _el = _getElementObject(inputs[i]), id = _getId(_el); + p.source = _el; + //p.scope="f"; + _updateOffset({ elId : id }); + var e = _newEndpoint(p); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + results.push(e); + } + + return results.length == 1 ? results[0] : results; + }; + + /* + Function: addEndpoints + Adds a list of s to a given element or elements. + + Parameters: + target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See 's constructor documentation. + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created s, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(el, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams); + if (e.constructor == Array) + Array.prototype.push.apply(results, e); + else results.push(e); + } + return results; + }; + + /* + Function: animate + This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating + the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // onComplete repaints, just to make sure everything looks good at the end of the animation. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /** + * checks for a listener for the given condition, executing it if found, passing in the given value. + * condition listeners would have been attached using "bind" (which is, you could argue, now overloaded, since + * firing click events etc is a bit different to what this does). i thought about adding a "bindCondition" + * or something, but decided against it, for the sake of simplicity. jsPlumb will never fire one of these + * condition events anyway. + */ + this.checkCondition = function(conditionName, value) { + var l = _currentInstance.getListener(conditionName); + var r = true; + if (l && l.length > 0) { + try { + for (var i = 0 ; i < l.length; i++) { + r = r && l[i](value); + } + } + catch (e) { + _log(_currentInstance, "cannot check condition [" + conditionName + "]" + e); + } + } + return r; + }; + + /* + Function: connect + Establishes a between two elements (or s, which are themselves registered to elements). + + Parameters: + params - Object containing constructor arguments for the Connection. See 's constructor documentation. + referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of + Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection. + + Returns: + The newly created . + */ + this.connect = function(params, referenceParams) { + // prepare a final set of parameters to create connection with + var _p = _prepareConnectionParams(params, referenceParams); + // TODO probably a nicer return value if the connection was not made. _prepareConnectionParams + // will return null (and log something) if either endpoint was full. what would be nicer is to + // create a dedicated 'error' object. + if (_p) { + // a connect call will delete its created endpoints on detach, unless otherwise specified. + // this is because the endpoints belong to this connection only, and are no use to + // anyone else, so they hang around like a bad smell. + if (_p.deleteEndpointsOnDetach == null) + _p.deleteEndpointsOnDetach = true; + + // create the connection. it is not yet registered + var jpc = _newConnection(_p); + // now add it the model, fire an event, and redraw + _finaliseConnection(jpc, _p); + return jpc; + } + }; + + /* + Function: deleteEndpoint + Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too) + + Parameters: + object - either an object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElement(endpoint.canvas, endpoint.parent); + // remove from endpointsbyElement + // DEPRECATED. SHOULD NOT BE NECESSARY ONCE THE ANCHOR MANAGER IS WORKING PROPERLY. + _currentInstance.anchorManager.deleteEndpoint(endpoint); + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + delete endpoint; + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every , and their associated s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc, doFireEvent) { + // may have been given a connection, or in special cases, an object + var connType = jsPlumb.Defaults.ConnectionType || jsPlumb.getDefaultConnectionType(), + argIsConnection = jpc.constructor == connType, + params = argIsConnection ? { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + } : jpc; + + if (doFireEvent) _currentInstance.fire("jsPlumbConnectionDetached", params); + _currentInstance.anchorManager.connectionDetached(params); + }; + + /* + Function: detach + Detaches and then removes a . From 1.3.5 this method has been altered to remove support for + specifying Connections by various parameters; you can now pass in a Connection as the first argument and + an optional parameters object as a second argument. If you need the functionality this method provided + before 1.3.5 then you should use the getConnections method to get the list of Connections to detach, and + then iterate through them, calling this for each one. + + Parameters: + connection - the to detach + params - optional parameters to the detach call. valid values here are + fireEvent : defaults to false; indicates you want jsPlumb to fire a connection + detached event. The thinking behind this is that if you made a programmatic + call to detach an event, you probably don't need the callback. + forceDetach : defaults to false. allows you to override any beforeDetach listeners that may be registered. + + Returns: + true if successful, false if not. + */ + this.detach = function() { + + if (arguments.length == 0) return; + var connType = jsPlumb.Defaults.ConnectionType || jsPlumb.getDefaultConnectionType(), + firstArgIsConnection = arguments[0].constructor == connType, + params = arguments.length == 2 ? firstArgIsConnection ? (arguments[1] || {}) : arguments[0] : arguments[0], + fireEvent = (params.fireEvent !== false), + forceDetach = params.forceDetach, + connection = firstArgIsConnection ? arguments[0] : params.connection; + + if (connection) { + if (forceDetach || (connection.isDetachAllowed(connection) + && connection.endpoints[0].isDetachAllowed(connection) + && connection.endpoints[1].isDetachAllowed(connection))) { + if (forceDetach || _currentInstance.checkCondition("beforeDetach", connection)) + connection.endpoints[0].detach(connection, false, true, fireEvent); // TODO check this param iscorrect for endpoint's detach method + } + } + else { + var _p = jsPlumb.extend( {}, params); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1]), fireEvent); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source), + targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + if (_currentInstance.checkCondition("beforeDetach", jpc)) { + jpc.endpoints[0].detach(jpc, false, true, fireEvent); + } + } + }); + } + } + }; + + /* + Function: detachAllConnections + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + params - optional parameters. alowed values: + fireEvent : defaults to true, whether or not to fire the detach event. + + Returns: + void + */ + this.detachAllConnections = function(el, params) { + params = params || {}; + el = _getElementObject(el); + var id = _getAttribute(el, "id"), + endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + }; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Parameters: + params - optional params object containing: + fireEvent : whether or not to fire detach events. defaults to true. + + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function(params) { + params = params || {}; + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of y + our library's draggable method so that jsPlumb can setup the appropriate callbacks. Your + underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + // TODO it would be nice if this supported a selector string, instead of an id. + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method, + * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the + * default scope). If multiple scopes are passed in, the return value will be a map of { scope -> [ connection... ] }. + * + * Parameters + * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list + * of connections that are in the given scope. + * options - if the argument is a JS object, you can specify a finer-grained filter: + * + * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes. + * - *source* either a string representing an element id, or a selector. constrains the result to connections having this source. + * - *target* either a string representing an element id, or a selector. constrains the result to connections having this target. + * + */ + this.getConnections = function(options) { + if (!options) { + options = {}; + } else if (options.constructor == String) { + options = { "scope": options }; + } + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') + r.push(input); + else + r = input; + } + return r; + }; + var scope = options.scope || jsPlumb.getDefaultScope(), + scopes = prepareList(scope), + sources = prepareList(options.source), + targets = prepareList(options.target), + filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }, + results = scopes.length > 1 ? {} : [], + _addOne = function(scope, obj) { + if (scopes.length > 1) { + var ss = results[scope]; + if (ss == null) { + ss = []; results[scope] = ss; + } + ss.push(obj); + } else results.push(obj); + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + _addOne(i, c); + } + } + } + return results; + }; + + /* + * Function: getAllConnections + * Gets all connections, as a map of { scope -> [ connection... ] }. + */ + this.getAllConnections = function() { + return connectionsByScope; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /** + * Function:getEndpoints + * Gets the list of Endpoints for a given selector, or element id. + * @param el + * @return + */ + this.getEndpoints = function(el) { + return endpointsByElement[_getId(el)]; + }; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + this.getOffset = function(id) { + var o = offsets[id]; + return _updateOffset({elId:id}); + }; + + this.getSelector = function(spec) { + return jsPlumb.CurrentLibrary.getSelector(spec); + }; + + this.getSize = function(id) { + var s = sizes[id]; + if (!s) _updateOffset({elId:id}); + return sizes[id]; + }; + + this.appendElement = _appendElement; + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + changeEndpoints - whether not to also hide endpoints on the element. by default this is false. + + Returns: + void + */ + this.hide = function(el, changeEndpoints) { + _setVisible(el, "none", changeEndpoints); + }; + + // exposed for other objects to use to get a unique id. + this.idstamp = _idstamp; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + if (!initialized) { + _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run. + + var bindOne = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && _mouseEventsEnabled && renderMode == jsPlumb.CANVAS) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + var t = c[i].connector[event](e); + if (t) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i].endpoint[event](e)) return; + } + } + } + }); + }; + bindOne("click");bindOne("dblclick");bindOne("mousemove");bindOne("mousedown");bindOne("mouseup"); + + initialized = true; + _currentInstance.fire("ready"); + } + }; + + this.log = log; + this.jsPlumbUIComponent = jsPlumbUIComponent; + this.EventGenerator = EventGenerator; + + /* + * Creates an anchor with the given params. + * + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function() { + if (arguments.length == 0) return null; + var specimen = arguments[0], elementId = arguments[1], jsPlumbInstance = arguments[2], newAnchor = null; + if (!jsPlumbInstance) + throw "NO JSPLUMB SET"; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; //TODO hazy here about whether it should be added or is already added somehow. + // is it the name of an anchor type? + else if (typeof specimen == "string") { + newAnchor = _currentInstance.Anchors[arguments[0]]({elementId:elementId, jsPlumbInstance:_currentInstance}); + } + // is it an array? it will be one of: + // an array of [name, params] - this defines a single anchor + // an array of arrays - this defines some dynamic anchors + // an array of numbers - this defines a single anchor. + else if (specimen.constructor == Array) { + if (specimen[0].constructor == Array || specimen[0].constructor == String) { + if (specimen.length == 2 && specimen[0].constructor == String && specimen[1].constructor == Object) { + var pp = jsPlumb.extend({elementId:elementId, jsPlumbInstance:_currentInstance}, specimen[1]); + newAnchor = _currentInstance.Anchors[specimen[0]](pp); + } + else + newAnchor = new DynamicAnchor(specimen, null, elementId); + } + else { + var anchorParams = { + x:specimen[0], y:specimen[1], + orientation : (specimen.length >= 4) ? [ specimen[2], specimen[3] ] : [0,0], + offsets : (specimen.length == 6) ? [ specimen[4], specimen[5] ] : [ 0, 0 ], + elementId:elementId + }; + newAnchor = new Anchor(anchorParams); + newAnchor.clone = function() { return new Anchor(anchorParams); }; + } + } + + if (!newAnchor.id) newAnchor.id = "anchor_" + _idstamp(); + return newAnchor; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types, elementId, jsPlumbInstance) { + var r = []; + for ( var i = 0; i < types.length; i++) { + if (typeof types[i] == "string") + r.push(_currentInstance.Anchors[types[i]]({elementId:elementId, jsPlumbInstance:jsPlumbInstance})); + else if (types[i].constructor == Array) + r.push(jsPlumb.makeAnchor(types[i], elementId, jsPlumbInstance)); + } + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /** + * Function: makeTarget + * Makes some DOM element a Connection target, allowing you to drag connections to it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a target. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * scope optional. scope for the drop zone. + * dropOptions optional. same stuff as you would pass to dropOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to true. whether or not to delete + * any Endpoints created by a connection to this target if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + var _targetEndpointDefinitions = {}; + this.makeTarget = function(el, params, referenceParams) { + + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + var jpcl = jsPlumb.CurrentLibrary, + targetScope = p.scope || _currentInstance.Defaults.Scope, + deleteEndpointsOnDetach = p.deleteEndpointsOnDetach || true, + _doOne = function(_el) { + + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el); + _targetEndpointDefinitions[elid] = p.endpoint; + + var dropOptions = jsPlumb.extend({}, p.dropOptions || {}), + _drop = function() { + _currentInstance.currentlyDragging = false; + var draggable = _getElementObject(jpcl.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + // restore the original scope if necessary (issue 57) + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id], + source = jpc.endpoints[0], + _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : {}; + + // use defaults for endpoint style, if not present..this either uses EndpointStyle, or EndpointStyles[1], + // if it is present, since this is a target endpoint. + // TODO - this should be a helper method. makeTarget should use it too. give it an endpoint + _endpoint.paintStyle = _endpoint.paintStyle || + _currentInstance.Defaults.EndpointStyles[1] || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyles[1] || + jsPlumb.Defaults.EndpointStyle; + + _endpoint.hoverPaintStyle = _endpoint.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyles[1] || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyles[1] || + jsPlumb.Defaults.EndpointHoverStyle; + + _endpoint.anchor = _endpoint.anchor || + _currentInstance.Defaults.Anchors[1] || + _currentInstance.Defaults.Anchor || + jsPlumb.Defaults.Anchors[1] || + jsPlumb.Defaults.Anchor; + + // unlock the source anchor to allow it to refresh its position if necessary + source.anchor.locked = false; + + // if (!jpcl.hasClass(draggable, _currentInstance.endpointClass)) return; + + if (scope) jpcl.setDragScope(draggable, scope); + + // check if drop is allowed here. + var _continue = jpc.isDropAllowed(jpc.sourceId, _getId(_el), jpc.scope); + + // regardless of whether the connection is ok, reconfigure the existing connection to + // point at the current info. we need this to be correct for the detach event that will follow. + // clear the source endpoint from the list to detach. we will detach this connection at this + // point, but we want to keep the source endpoint. the target is a floating endpoint and should + // be removed. TODO need to figure out whether this code can result in endpoints kicking around + // when they shouldnt be. like is this a full detach of a connection? can it be? + if (jpc.endpointsToDeleteOnDetach) { + if (source === jpc.endpointsToDeleteOnDetach[0]) + jpc.endpointsToDeleteOnDetach[0] = null; + else if (source === jpc.endpointsToDeleteOnDetach[1]) + jpc.endpointsToDeleteOnDetach[1] = null; + } + // reinstate any suspended endpoint; this just puts the connection back into + // a state in which it will report sensible values if someone asks it about + // its target. we're going to throw this connection away shortly so it doesnt matter + // if we manipulate it a bit. + if (jpc.suspendedEndpoint) { + jpc.targetId = jpc.suspendedEndpoint.elementId; + jpc.target = jpcl.getElementObject(jpc.suspendedEndpoint.elementId); + jpc.endpoints[1] = jpc.suspendedEndpoint; + } + + if (_continue) { + + // detach this connection from the source. + source.detach(jpc, false, true, true);//source.endpointWillMoveAfterConnection); + + // make a new Endpoint for the target + var newEndpoint = jsPlumb.addEndpoint(_el, _endpoint); + + // if the anchor has a 'positionFinder' set, then delegate to that function to find + // out where to locate the anchor. + if (newEndpoint.anchor.positionFinder != null) { + var dropPosition = jpcl.getUIPosition(arguments), + elPosition = jpcl.getOffset(_el), + elSize = jpcl.getSize(_el), + ap = newEndpoint.anchor.positionFinder(dropPosition, elPosition, elSize, newEndpoint.anchor); + newEndpoint.anchor.x = ap[0]; + newEndpoint.anchor.y = ap[1]; + // now figure an orientation for it..kind of hard to know what to do actually. probably the best thing i can do is to + // support specifying an orientation in the anchor's spec. if one is not supplied then i will make the orientation + // be what will cause the most natural link to the source: it will be pointing at the source, but it needs to be + // specified in one axis only, and so how to make that choice? i think i will use whichever axis is the one in which + // the target is furthest away from the source. + } + var c = jsPlumb.connect({ + source:source, + target:newEndpoint, + scope:scope, + previousConnection:jpc, + container:jpc.parent, + // 'endpointWillMoveAfterConnection' is set by the makeSource function, and it indicates that the + // given endpoint will actually transfer from the element it is currently attached to to some other + // element after a connection has been established. in that case, we do not want to fire the + // connection event, since it will have the wrong data in it; makeSource will do it for us. + // this is controlled by the 'parent' parameter on a makeSource call. + doNotFireConnectionEvent:source.endpointWillMoveAfterConnection + }); + if (deleteEndpointsOnDetach) + c.endpointsToDeleteOnDetach = [ source, newEndpoint ]; + } + // if not allowed to drop... + else { + // TODO this code is identical (pretty much) to what happens when a connection + // dragged from a normal endpoint is in this situation. refactor. + // is this an existing connection, and will we reattach? + if (jpc.suspendedEndpoint) { + if (source.isReattach) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(source.elementId); + } + else + source.detach(jpc, false, true, true); // otherwise, detach the connection and tell everyone about it. + } + + } + }; + + var dropEvent = jpcl.dragEvents['drop']; + dropOptions["scope"] = dropOptions["scope"] || targetScope; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop); + + jpcl.initDroppable(_el, dropOptions, true); + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements drop targets. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeTargets = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeTarget(els[i], params, referenceParams); + } + }; + + /** + * Function: makeSource + * Makes some DOM element a Connection source, allowing you to drag connections from it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a source. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * parent optional. the element to add Endpoints to when a Connection is established. if you omit this, + * Endpoints will be added to 'el'. + * scope optional. scope for the connections dragged from this element. + * dragOptions optional. same stuff as you would pass to dragOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to false. whether or not to delete + * any Endpoints created by a connection from this source if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + var _sourceEndpointDefinitions = {}; + this.makeSource = function(el, params, referenceParams) { + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + var jpcl = jsPlumb.CurrentLibrary, + _doOne = function(_el) { + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el); + _sourceEndpointDefinitions[elid] = p.endpoint || {}; + var stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"], + dragOptions = jsPlumb.extend({ }, _sourceEndpointDefinitions[elid].dragOptions || {}), + ep = null, + endpointAddedButNoDragYet = false; + + // make sure this method honours whatever defaults have been set for Endpoint. + _sourceEndpointDefinitions[elid].endpoint = _sourceEndpointDefinitions[elid].endpoint || _currentInstance.Defaults.Endpoints[0] || _currentInstance.Defaults.Endpoint; + + // TODO this, and the makeTarget equivalent, and probably elsewhere, should all be handed off to + // some helper method that can make this decision for us. + _sourceEndpointDefinitions[elid].paintStyle = _sourceEndpointDefinitions[elid].paintStyle || + _currentInstance.Defaults.EndpointStyles[0] || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyles[0] || + jsPlumb.Defaults.EndpointStyle; + + _sourceEndpointDefinitions[elid].hoverPaintStyle = _sourceEndpointDefinitions[elid].hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyles[0] || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyles[0] || + jsPlumb.Defaults.EndpointHoverStyle; + + _sourceEndpointDefinitions[elid].anchor = _sourceEndpointDefinitions[elid].anchor || + _currentInstance.Defaults.Anchors[0] || + _currentInstance.Defaults.Anchor || + jsPlumb.Defaults.Anchors[0] || + jsPlumb.Defaults.Anchor; + + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + endpointAddedButNoDragYet = false; + }); + + dragOptions[stopEvent] = function() { + // here we need to do a couple of things; + // first determine whether or not a connection was dragged. if not, just delete this endpoint. + // ...if so, though, then we need to check to see if a 'parent' was specified in the + // options to makeSource. a 'parent' is a reference to an element other than the one from + // which the connection is dragged, and it indicates that after a successful connection, the + // endpoint should be moved off of this element and onto 'parent', using all of the + // options passed in to the makeSource call. + // + // one thing that occurs to me right now is that we dont really want the first + // connection to have fired a connection event. but how can we prevent it from doing so? + // + // + + //_currentlyDown = false; + _currentInstance.currentlyDragging = false; + + if (ep.connections.length == 0) + jsPlumb.deleteEndpoint(ep); + else { + + jpcl.unbind(ep.canvas, "mousedown"); + + // reset the anchor to the anchor that was initially provided. the one we were using to drag + // the connection was just a placeholder that was located at the place the user pressed the + // mouse button to initiate the drag. + var anchorDef = _sourceEndpointDefinitions[elid].anchor || _currentInstance.Defaults.Anchor; + ep.anchor = jsPlumb.makeAnchor(anchorDef, elid, _currentInstance); + + if (p.parent) { + var parent = jpcl.getElementObject(p.parent); + if (parent) { + var currentId = ep.elementId; + ep.setElement(parent); + ep.endpointWillMoveAfterConnection = false; + _currentInstance.anchorManager.rehomeEndpoint(currentId, parent); + var jpc = ep.connections[0]; // TODO will this always be correct? + _finaliseConnection(jpc); + _currentInstance.repaintEverything(); + } + } + _currentInstance.repaint(elid); + } + }; + // when the user presses the mouse, add an Endpoint + var mouseDownListener = function(e) { + // make sure we have the latest offset for this div + _updateOffset({elId:elid}); + // and get it, and the div's size + var myOffset = offsets[elid], myWH = sizes[elid]; + // if there is a parent, the endpoint will actually be added to it now, rather than the div + // that was the source. in that case, we have to adjust the anchor position so it refers to + // the parent. + if (p.parent) { + var pEl = jsPlumb.CurrentLibrary.getElementObject(p.parent), + pId = _getId(pEl); + _updateOffset({elId:pId}); + myOffset = offsets[pId]; + myWH = sizes[pId]; + } + + var x = ((e.pageX || e.page.x) - myOffset.left) / myWH[0], + y = ((e.pageY || e.page.y) - myOffset.top) / myWH[1]; + + // we need to override the anchor in here, and force 'isSource', but we don't want to mess with + // the params passed in, because after a connection is established we're going to reset the endpoint + // to have the anchor we were given. + var tempEndpointParams = {}; + jsPlumb.extend(tempEndpointParams, _sourceEndpointDefinitions[elid]); + tempEndpointParams.isSource = true; + tempEndpointParams.anchor = [x,y,0,0]; + tempEndpointParams.dragOptions = dragOptions; + // if a parent was given we need to turn that into a "container" argument. this is, by default, + // the parent of the element we will move to, so parent of p.parent in this case. however, if + // the user has specified a 'container' on the endpoint definition or on + // the defaults, we should use that. + if (p.parent) { + var potentialParent = tempEndpointParams.container || jsPlumb.Defaults.Container; + if (potentialParent) + tempEndpointParams.container = potentialParent; + else + tempEndpointParams.container = jsPlumb.CurrentLibrary.getParent(p.parent); + } + + ep = jsPlumb.addEndpoint(elid, tempEndpointParams); + + endpointAddedButNoDragYet = true; + // we set this to prevent connections from firing attach events before this function has had a chance + // to move the endpoint. + ep.endpointWillMoveAfterConnection = p.parent != null; + ep.endpointWillMoveTo = p.parent ? jpcl.getElementObject(p.parent) : null; + + var _delTempEndpoint = function() { + // this mouseup event is fired only if no dragging occurred, by jquery and yui, but for mootools + // it is fired even if dragging has occurred, in which case we would blow away a perfectly + // legitimate endpoint, were it not for this check. the flag is set after adding an + // endpoint and cleared in a drag listener we set in the dragOptions above. + if(endpointAddedButNoDragYet) { + jsPlumb.deleteEndpoint(ep); + } + }; + + _currentInstance.registerListener(ep.canvas, "mouseup", _delTempEndpoint); + _currentInstance.registerListener(_el, "mouseup", _delTempEndpoint); + + // and then trigger its mousedown event, which will kick off a drag, which will start dragging + // a new connection from this endpoint. + jpcl.trigger(ep.canvas, "mousedown", e); + }; + + // jpcl.bind(_el, "mousedown", mouseDownListener); + // register this on jsPlumb so that it can be cleared by a reset. + _currentInstance.registerListener(_el, "mousedown", mouseDownListener); + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements connection sources. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeSources = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeSource(els[i], params, referenceParams); + } + }; + + /* + Function: ready + Helper method to bind a function to jsPlumb's ready event. + */ + this.ready = function(fn) { + _currentInstance.bind("ready", fn); + }, + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"), + ebe = endpointsByElement[elId]; + if (ebe) { + for ( var i = 0; i < ebe.length; i++) + _currentInstance.deleteEndpoint(ebe[i]); + } + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + var _registeredListeners = {}, + _unbindRegisteredListeners = function() { + for (var i in _registeredListeners) { + for (var j = 0; j < _registeredListeners[i].length; j++) { + var info = _registeredListeners[i][j]; + jsPlumb.CurrentLibrary.unbind(info.el, info.event, info.listener); + } + } + _registeredListeners = {}; + }; + + // internal register listener method. gives us a hook to clean things up + // with if the user calls jsPlumb.reset. + this.registerListener = function(el, type, listener) { + jsPlumb.CurrentLibrary.bind(el, type, listener); + _addToList(_registeredListeners, type, {el:el, event:type, listener:listener}); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + this.deleteEveryEndpoint(); + this.clearListeners(); + _unbindRegisteredListeners(); + this.anchorManager.reset(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + + Parameters: + value - whether or not to automatically repaint when the window is resized. + + Returns: void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction + * Sets the function to fire when the window size has changed and a repaint was fired. + * + * Parameters: + * f - Function to execute. + * + * Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: setMouseEventsEnabled + * Sets whether or not mouse events are enabled. Default is true. + * + * Parameters: + * enabled - whether or not mouse events should be enabled. + * + * Returns: + * void + */ + this.setMouseEventsEnabled = function(enabled) { + _mouseEventsEnabled = enabled; + }; + + /* + * Constant for use with the setRenderMode method + */ + this.CANVAS = "canvas"; + + /* + * Constant for use with the setRenderMode method + */ + this.SVG = "svg"; + + this.VML = "vml"; + + /* + * Function: setRenderMode + * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that + * what you asked for is not supported (and that VML is). If you asked for VML but the browser does + * not support it, jsPlumb uses SVG. + * + * Returns: + * the render mode that jsPlumb set, which of course may be different from that requested. + */ + this.setRenderMode = function(mode) { + if (mode) + mode = mode.toLowerCase(); + else + return; + if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); + // now test we actually have the capability to do this. + if (mode === jsPlumb.CANVAS && canvasAvailable) + renderMode = jsPlumb.CANVAS; + else if (mode === jsPlumb.SVG && svgAvailable) + renderMode = jsPlumb.SVG; + else if (vmlAvailable) + renderMode = jsPlumb.VML; + + return renderMode; + }; + + this.getRenderMode = function() { return renderMode; }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * changeEndpoints - whether or not to also change the visible state of the endpoints on the element. this also has a bearing on + * other connections on those endpoints: if their other endpoint is also visible, the connections are made visible. + * + * Returns: + * void + */ + this.show = function(el, changeEndpoints) { + _setVisible(el, "block", changeEndpoints); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId, + makeAnchor:self.makeAnchor, + makeDynamicAnchor:self.makeDynamicAnchor + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * changeEndpoints - whether or not to also toggle the endpoints on the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload + * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element. + * + * Returns: + * void + */ + this.unload = function() { + delete endpointsByElement; + delete endpointsByUUID; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + delete canvasList; + }; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + var adjustForParentOffsetAndScroll = function(xy, el) { + var offsetParent = null, result = xy; + if (el.tagName.toLowerCase() === "svg" && el.parentNode) { + offsetParent = el.parentNode; + } + else if (el.offsetParent) { + offsetParent = el.offsetParent; + } + if (offsetParent != null) { + var po = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(offsetParent), + so = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : {left:offsetParent.scrollLeft, top:offsetParent.scrollTop}; + + // i thought it might be cool to do this: + // lastReturnValue[0] = lastReturnValue[0] - offsetParent.offsetLeft + offsetParent.scrollLeft; + // lastReturnValue[1] = lastReturnValue[1] - offsetParent.offsetTop + offsetParent.scrollTop; + // but i think it ignores margins. my reasoning was that it's quicker to not hand off to some underlying + // library. + + result[0] = xy[0] - po.left + so.left; + result[1] = xy[1] - po.top + so.top; + } + + return result; + }; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + this.elementId = params.elementId; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + + if (timestamp && timestamp === self.timestamp) + return lastReturnValue; + + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + + // adjust loc if there is an offsetParent + lastReturnValue = adjustForParentOffsetAndScroll(lastReturnValue, element.canvas); + + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + * + * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference, + // the canvas this refers to. + refCanvas = params.referenceCanvas, + size = _getSize(_getElementObject(refCanvas)), + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + xDir = 0, yDir = 0, + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + orientation = null, + _lastResult = null; + + this.compute = function(params) { + var xy = params.xy, element = params.element, + result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + + // adjust loc if there is an offsetParent + result = adjustForParentOffsetAndScroll(result, element.canvas); + + _lastResult = result; + return result; + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { orientation = anchor.getOrientation(); }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchor is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector, elementId) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = [], + _convert = function(anchor) { return anchor.constructor == Anchor ? anchor: jsPlumb.makeAnchor(anchor, elementId, _currentInstance); }; + for (var i = 0; i < anchors.length; i++) _anchors[i] = _convert(anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + this.locked = false; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null, + _curIndex = _anchors.length > 0 ? 0 : -1, + self = this, + + // helper method to calculate the distance between the centers of the two elements. + _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }, + + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + + return _curAnchor.compute(params); + }; + + this.getCurrentLocation = function() { + return _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + }; + + this.getOrientation = function() { return _curAnchor != null ? _curAnchor.getOrientation() : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + manages anchors for all elements. + */ + // "continuous" anchors: anchors that pick their location based on how many connections the given element has. + // this requires looking at a lot more elements than normal - anything that has had a Continuous anchor applied has + // to be recalculated. so this manager is used as a reference point. the first time, with a new timestamp, that + // a continuous anchor is asked to compute, it calls this guy. or maybe, even, this guy gets called somewhere else + // and compute only ever returns pre-computed values. either way, this is the central point, and we want it to + // be called as few times as possible. + var continuousAnchors = {}, + continuousAnchorLocations = {}, + continuousAnchorOrientations = {}, + Orientation = { HORIZONTAL : "horizontal", VERTICAL : "vertical", DIAGONAL : "diagonal", IDENTITY:"identity" }, + + // TODO this functions uses a crude method of determining orientation between two elements. + // 'diagonal' should be chosen when the angle of the line between the two centers is around + // one of 45, 135, 225 and 315 degrees. maybe +- 15 degrees. + calculateOrientation = function(sourceId, targetId, sd, td) { + + if (sourceId === targetId) return { + orientation:Orientation.IDENTITY, + a:["top", "top"] + }; + + var theta = Math.atan2((td.centery - sd.centery) , (td.centerx - sd.centerx)), + theta2 = Math.atan2((sd.centery - td.centery) , (sd.centerx - td.centerx)), + h = ((sd.left <= td.left && sd.right >= td.left) || (sd.left <= td.right && sd.right >= td.right) || + (sd.left <= td.left && sd.right >= td.right) || (td.left <= sd.left && td.right >= sd.right)), + v = ((sd.top <= td.top && sd.bottom >= td.top) || (sd.top <= td.bottom && sd.bottom >= td.bottom) || + (sd.top <= td.top && sd.bottom >= td.bottom) || (td.top <= sd.top && td.bottom >= sd.bottom)); + + if (! (h || v)) { + var a = null, rls = false, rrs = false, sortValue = null; + if (td.left > sd.left && td.top > sd.top) + a = ["right", "top"]; + else if (td.left > sd.left && sd.top > td.top) + a = [ "top", "left"]; + else if (td.left < sd.left && td.top < sd.top) + a = [ "top", "right"]; + else if (td.left < sd.left && td.top > sd.top) + a = ["left", "top" ]; + + return { orientation:Orientation.DIAGONAL, a:a, theta:theta, theta2:theta2 }; + } + else if (h) return { + orientation:Orientation.HORIZONTAL, + a:sd.top < td.top ? ["bottom", "top"] : ["top", "bottom"], + theta:theta, theta2:theta2 + } + else return { + orientation:Orientation.VERTICAL, + a:sd.left < td.left ? ["right", "left"] : ["left", "right"], + theta:theta, theta2:theta2 + } + }, + placeAnchorsOnLine = function(desc, elementDimensions, elementPosition, + connections, horizontal, otherMultiplier, reverse) { + var a = [], step = elementDimensions[horizontal ? 0 : 1] / (connections.length + 1); + + for (var i = 0; i < connections.length; i++) { + var val = (i + 1) * step, other = otherMultiplier * elementDimensions[horizontal ? 1 : 0]; + if (reverse) + val = elementDimensions[horizontal ? 0 : 1] - val; + + var dx = (horizontal ? val : other), x = elementPosition[0] + dx, xp = dx / elementDimensions[0], + dy = (horizontal ? other : val), y = elementPosition[1] + dy, yp = dy / elementDimensions[1]; + + a.push([ x, y, xp, yp, connections[i][1], connections[i][2] ]); + } + + return a; + }, + standardEdgeSort = function(a, b) { return a[0] > b[0] ? 1 : -1 }, + currySort = function(reverseAngles) { + return function(a,b) { + var r = true; + if (reverseAngles) { + if (a[0][0] < b[0][0]) + r = true; + else + r = a[0][1] > b[0][1]; + } + else { + if (a[0][0] > b[0][0]) + r= true; + else + r =a[0][1] > b[0][1]; + } + return r === false ? -1 : 1; + }; + }, + leftSort = function(a,b) { + // first get adjusted values + var p1 = a[0][0] < 0 ? -Math.PI - a[0][0] : Math.PI - a[0][0], + p2 = b[0][0] < 0 ? -Math.PI - b[0][0] : Math.PI - b[0][0]; + if (p1 > p2) return 1; + else return a[0][1] > b[0][1] ? 1 : -1; + }, + edgeSortFunctions = { + "top":standardEdgeSort, + "right":currySort(true), + "bottom":currySort(true), + "left":leftSort + }, + _sortHelper = function(_array, _fn) { + return _array.sort(_fn); + }, + placeAnchors = function(elementId, _anchorLists) { + var sS = sizes[elementId], sO = offsets[elementId], + placeSomeAnchors = function(desc, elementDimensions, elementPosition, unsortedConnections, isHorizontal, otherMultiplier) { + if (unsortedConnections.length > 0) { + var sc = _sortHelper(unsortedConnections, edgeSortFunctions[desc]), // puts them in order based on the target element's pos on screen + //sc = unsortedConnections.sort(edgeSortFunctions[desc]), // puts them in order based on the target element's pos on screen + reverse = desc === "right" || desc === "top", + anchors = placeAnchorsOnLine(desc, elementDimensions, + elementPosition, sc, + isHorizontal, otherMultiplier, reverse ); + + // takes a computed anchor position and adjusts it for parent offset and scroll, then stores it. + var _setAnchorLocation = function(endpoint, anchorPos) { + var a = adjustForParentOffsetAndScroll([anchorPos[0], anchorPos[1]], endpoint.canvas); + continuousAnchorLocations[endpoint.id] = [ a[0], a[1], anchorPos[2], anchorPos[3] ]; + }; + + for (var i = 0; i < anchors.length; i++) { + var c = anchors[i][4], weAreSource = c.endpoints[0].elementId === elementId, weAreTarget = c.endpoints[1].elementId === elementId; + if (weAreSource) + _setAnchorLocation(c.endpoints[0], anchors[i]); + else if (weAreTarget) + _setAnchorLocation(c.endpoints[1], anchors[i]); + } + } + }; + + placeSomeAnchors("bottom", sS, [sO.left,sO.top], _anchorLists.bottom, true, 1); + placeSomeAnchors("top", sS, [sO.left,sO.top], _anchorLists.top, true, 0); + placeSomeAnchors("left", sS, [sO.left,sO.top], _anchorLists.left, false, 0); + placeSomeAnchors("right", sS, [sO.left,sO.top], _anchorLists.right, false, 1); + }, + AnchorManager = function() { + var _amEndpoints = {}, + endpointConnectionsByElementId = {}, + continuousAnchorConnectionsByElementId = {}, + continuousAnchorEndpoints = [], + self = this, + anchorLists = {}; + + this.reset = function() { + endpointConnectionsByElementId = {}; + continuousAnchorConnectionsByElementId = {}; + continuousAnchorEndpoints = []; + anchorLists = {}; + }; + + this.newConnection = function(conn) { + var sourceId = conn.sourceId, targetId = conn.targetId, + ep = conn.endpoints, + doRegisterTarget = true, + registerConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + if (otherAnchor.constructor == DynamicAnchor || otherAnchor.constructor == Anchor) { + _addToList(endpointConnectionsByElementId, elId, [c, otherEndpoint, otherAnchor.constructor == DynamicAnchor]); + } + else { + // continuous. if they are the same element, just assign the same anchor + // to both. + if (sourceId == targetId) { + // remove the target endpoint's canvas. we dont need it. + jsPlumb.CurrentLibrary.removeElement(ep[1].canvas); + doRegisterTarget = false; + } + _addToList(continuousAnchorConnectionsByElementId, elId, c); + } + }; + registerConnection(0, ep[0], ep[0].anchor, targetId, conn); + if (doRegisterTarget) + registerConnection(1, ep[1], ep[1].anchor, sourceId, conn); + }; + this.connectionDetached = function(connInfo) { + var sourceId = connInfo.sourceId, + targetId = connInfo.targetId, + ep = connInfo.connection.endpoints, + removeConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + if (otherAnchor.constructor == FloatingAnchor) { + // no-op + } + else if (otherAnchor.constructor == DynamicAnchor || otherAnchor.constructor == Anchor) { + var _conns = endpointConnectionsByElementId[elId]; + if (_conns) { + _removeWithFunction(_conns, function(e) { return e[0].id == c.id; }); + } + } + else // continuous. + _removeFromList(continuousAnchorConnectionsByElementId, elId, c); + }; + + removeConnection(1, ep[1], ep[1].anchor, sourceId, connInfo.connection); + removeConnection(0, ep[0], ep[0].anchor, targetId, connInfo.connection); + + // remove from anchorLists + var sEl = connInfo.connection.sourceId, + tEl = connInfo.connection.targetId, + sE = connInfo.connection.endpoints[0].id, + tE = connInfo.connection.endpoints[1].id, + _remove = function(list, eId) { + if (list) { // transient anchors dont get entries in this list. + var f = function(e) { return e[4] == eId; }; + _removeWithFunction(list["top"], f); + _removeWithFunction(list["left"], f); + _removeWithFunction(list["bottom"], f); + _removeWithFunction(list["right"], f); + } + }; + + _remove(anchorLists[sEl], sE); + _remove(anchorLists[tEl], tE); + self.redraw(sEl); + self.redraw(tEl); + }; + this.add = function(endpoint, elementId) { + _addToList(_amEndpoints, elementId, endpoint); + }; + this.get = function(elementId) { + return { + "standard":endpointConnectionsByElementId[elementId] || [], + "continuous":continuousAnchorConnectionsByElementId[elementId] || [], + "endpoints":_amEndpoints[elementId], + "continuousAnchorEndpoints":continuousAnchorEndpoints + }; + }; + this.deleteEndpoint = function(endpoint) { + var cIdx = _findIndex(continuousAnchorEndpoints, endpoint); + if (cIdx > -1) + continuousAnchorEndpoints.splice(cIdx, 1); + else + _removeFromList(_amEndpoints, endpoint.elementId, endpoint); + }; + this.clearFor = function(elementId) { + delete _amEndpoints[elementId]; + _amEndpoints[elementId] = []; + }; + // updates the given anchor list by either updating an existing anchor's info, or adding it. this function + // also removes the anchor from its previous list, if the edge it is on has changed. + // all connections found along the way (those that are connected to one of the faces this function + // operates on) are added to the connsToPaint list, as are their endpoints. in this way we know to repaint + // them wthout having to calculate anything else about them. + var _updateAnchorList = function(lists, theta, order, conn, aBoolean, otherElId, idx, reverse, edgeId, elId, connsToPaint, endpointsToPaint) { + // first try to find the exact match, but keep track of the first index of a matching element id along the way.s + var exactIdx = -1, + firstMatchingElIdx = -1, + endpoint = conn.endpoints[idx], + endpointId = endpoint.id, + oIdx = [1,0][idx], + values = [ [ theta, order ], conn, aBoolean, otherElId, endpointId ], + listToAddTo = lists[edgeId], + listToRemoveFrom = endpoint._continuousAnchorEdge ? lists[endpoint._continuousAnchorEdge] : null; + + if (listToRemoveFrom) { + var rIdx = _findWithFunction(listToRemoveFrom, function(e) { return e[4] == endpointId }); + if (rIdx != -1) { + listToRemoveFrom.splice(rIdx, 1); + // get all connections from this list + for (var i = 0; i < listToRemoveFrom.length; i++) { + connsToPaint.push(listToRemoveFrom[i][1]); + endpointsToPaint.push(listToRemoveFrom[i][1].endpoints[idx]); + } + } + } + + for (var i = 0; i < listToAddTo.length; i++) { + if (idx == 1 && listToAddTo[i][3] === otherElId && firstMatchingElIdx == -1) + firstMatchingElIdx = i; + connsToPaint.push(listToAddTo[i][1]); + endpointsToPaint.push(listToAddTo[i][1].endpoints[idx]); + } + if (exactIdx != -1) { + listToAddTo[exactIdx] = values; + } + else { + var insertIdx = reverse ? firstMatchingElIdx != -1 ? firstMatchingElIdx : 0 : listToAddTo.length; // of course we will get this from having looked through the array shortly. + listToAddTo.splice(insertIdx, 0, values); + } + + // store this for next time. + endpoint._continuousAnchorEdge = edgeId; + }; + this.redraw = function(elementId, ui, timestamp) { + // get all the endpoints for this element + var ep = _amEndpoints[elementId] || [], + endpointConnections = endpointConnectionsByElementId[elementId] || [], + continuousAnchorConnections = continuousAnchorConnectionsByElementId[elementId] || [], + connectionsToPaint = [], + endpointsToPaint = []; + + timestamp = timestamp || _timestamp(); + + _updateOffset( { elId : elementId, offset : ui, recalc : false, timestamp : timestamp }); + // valid for one paint cycle. + var myOffset = offsets[elementId], + myWH = sizes[elementId], + orientationCache = {}; + + // actually, first we should compute the orientation of this element to all other elements to which + // this element is connected with a continuous anchor (whether both ends of the connection have + // a continuous anchor or just one) + for (var i = 0; i < continuousAnchorConnections.length; i++) { + var conn = continuousAnchorConnections[i], + sourceId = conn.sourceId, + targetId = conn.targetId, + oKey = sourceId + "_" + targetId, + oKey2 = targetId + "_" + sourceId, + o = orientationCache[oKey], + td = _getCachedData(targetId), + sd = _getCachedData(sourceId), + oIdx = conn.sourceId == elementId ? 1 : 0; + + if (!anchorLists[sourceId]) anchorLists[sourceId] = { top:[], right:[], bottom:[], left:[] }; + if (!anchorLists[targetId]) anchorLists[targetId] = { top:[], right:[], bottom:[], left:[] }; + + if (targetId == sourceId) { + // here we may want to improve this by somehow determining the face we'd like + // to put the connector on. ideally, when drawing, the face should be calculated + // by determining which face is closest to the point at which the mouse button + // was released. for now, we're putting it on the top face. + _updateAnchorList(anchorLists[sourceId], -Math.PI / 2, 0, conn, false, targetId, 0, false, "top", sourceId, connectionsToPaint, endpointsToPaint) + } + else { + if (!o) { + o = calculateOrientation(sourceId, targetId, sd.o, td.o); + orientationCache[oKey] = o; + // this would be a performance enhancement, but the computed angles need to be clamped to + //the (-PI/2 -> PI/2) range in order for the sorting to work properly. + /* orientationCache[oKey2] = { + orientation:o.orientation, + a:[o.a[1], o.a[0]], + theta:o.theta + Math.PI, + theta2:o.theta2 + Math.PI + };*/ + } + _updateAnchorList(anchorLists[sourceId], o.theta, 0, conn, false, targetId, 0, false, o.a[0], sourceId, connectionsToPaint, endpointsToPaint); + _updateAnchorList(anchorLists[targetId], o.theta2, -1, conn, true, sourceId, 1, true, o.a[1], targetId, connectionsToPaint, endpointsToPaint); + } + + connectionsToPaint.push(conn); + endpointsToPaint.push(conn.endpoints[oIdx]); + } + + // now place all the continuous anchors; + for (var anElement in anchorLists) { + placeAnchors(anElement, anchorLists[anElement]); + } + + // now that continuous anchors have been placed, paint all the endpoints for this element + for (var i = 0; i < ep.length; i++) { + ep[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + // ... and any other endpoints we came across as a result of the continuous anchors. + for (var i = 0; i < endpointsToPaint.length; i++) { + endpointsToPaint[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + + // paint all the standard and "dynamic connections", which are connections whose other anchor is + // static and therefore does need to be recomputed; we make sure that happens only one time. + for (var i = 0; i < endpointConnections.length; i++) { + var otherEndpoint = endpointConnections[i][1]; + if (otherEndpoint.anchor.constructor == DynamicAnchor) { + // _updateOffset( { elId : otherEndpoint.elementId, timestamp : timestamp }); + otherEndpoint.paint({ elementWithPrecedence:elementId }); + connectionsToPaint.push(endpointConnections[i][0]); + // all the connections for the other endpoint now need to be repainted + for (var k = 0; k < otherEndpoint.connections.length; k++) { + if (otherEndpoint.connections[k] !== endpointConnections[i][0]) + connectionsToPaint.push(otherEndpoint.connections[k]); + } + } else if (otherEndpoint.anchor.constructor == Anchor) { + connectionsToPaint.push(endpointConnections[i][0]); + } + } + // paint current floating connection for this element, if there is one. + var fc = floatingConnections[elementId]; + if (fc) + fc.paint({timestamp:timestamp, recalc:false, elId:elementId}); + + // paint all the connections + for (var i = 0; i < connectionsToPaint.length; i++) { + connectionsToPaint[i].paint({elId:elementId, timestamp:timestamp, recalc:false}); + } + }; + this.rehomeEndpoint = function(currentId, element) { + var eps = _amEndpoints[currentId] || [], //, + elementId = _currentInstance.getId(element); + for (var i = 0; i < eps.length; i++) { + self.add(eps[i], elementId); + } + eps.splice(0, eps.length); + }; + }; + _currentInstance.anchorManager = new AnchorManager(); + //_currentInstance.bind("jsPlumbConnection", _currentInstance.anchorManager.connectionListener); + //_currentInstance.bind("jsPlumbConnectionDetached", _currentInstance.anchorManager.connectionDetachedListener); + + _currentInstance.continuousAnchorFactory = { + get:function(params) { + var existing = continuousAnchors[params.elementId]; + if (!existing) { + existing = { + type:"Continuous", + compute : function(params) { + return continuousAnchorLocations[params.element.id] || [0,0]; + }, + getCurrentLocation : function(endpoint) { + return continuousAnchorLocations[endpoint.id] || [0,0]; + }, + getOrientation : function(endpoint) { + return continuousAnchorOrientations[endpoint.id] || [0,0]; + }, + isDynamic : true, + isContinuous : true + }; + continuousAnchors[params.elementId] = existing; + } + return existing; + } + }; + + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * container - optional id or selector instructing jsPlumb where to attach all the elements it creates for this connection. you should read the documentation for a full discussion of this. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * overlays - Optional array of Overlay definitions to appear on this Connection. + * drawEndpoints - if false, instructs jsPlumb to not draw the endpoints for this Connection. Be careful with this: it only really works when you tell jsPlumb to attach elements to the document body. Read the documentation for a full discussion of this. + */ + var Connection = function(params) { + var self = this, visible = true; + self.idPrefix = "_jsplumb_c_"; + jsPlumbUIComponent.apply(this, arguments); + // ************** get the source and target and register the connection. ******************* + + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none"; + }; + this.parent = params.parent; + /** + Property: source + The source element for this Connection. + */ + this.source = _getElementObject(params.source); + /** + Property:target + The target element for this Connection. + */ + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. but + // source is not overridden if the Endpoint has declared it is not the final target of a connection; + // instead we use the source that the Endpoint declares will be the final source element. + if (params.sourceEndpoint) { + this.source = params.sourceEndpoint.endpointWillMoveTo || params.sourceEndpoint.getElement(); + } + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + + // if a new connection is the result of moving some existing connection, params.previousConnection + // will have that Connection in it. listeners for the jsPlumbConnection event can look for that + // member and take action if they need to. + self.previousConnection = params.previousConnection; + + /* + * Property: sourceId + * Id of the source element in the connection. + */ + this.sourceId = _getAttribute(this.source, "id"); + /* + * Property: targetId + * Id of the target element in the connection. + */ + this.targetId = _getAttribute(this.target, "id"); + + /** + * implementation of abstract method in EventGenerator + * @return list of attached elements. in our case, a list of Endpoints. + */ + this.getAttachedElements = function() { + return self.endpoints; + }; + + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams, elementId) { + if (anchorParams) + return jsPlumb.makeAnchor(anchorParams, elementId, _currentInstance); + }, + prepareEndpoint = function(existing, index, params, element, elementId, connectorPaintStyle, connectorHoverPaintStyle) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] + || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint; + + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified. + if (es.fillStyle == null && connectorPaintStyle != null) + es.fillStyle = connectorPaintStyle.strokeStyle; + + // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does: + //* + if (es.outlineColor == null && connectorPaintStyle != null) + es.outlineColor = connectorPaintStyle.outlineColor; + if (es.outlineWidth == null && connectorPaintStyle != null) + es.outlineWidth = connectorPaintStyle.outlineWidth; + //*/ + + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure? + if (connectorHoverPaintStyle != null) { + if (ehs == null) ehs = {}; + if (ehs.fillStyle == null) { + ehs.fillStyle = connectorHoverPaintStyle.strokeStyle; + } + } + var a = params.anchors ? params.anchors[index] : + params.anchor ? params.anchor : + _makeAnchor(_currentInstance.Defaults.Anchors[index], elementId) || + _makeAnchor(jsPlumb.Defaults.Anchors[index], elementId) || + _makeAnchor(_currentInstance.Defaults.Anchor, elementId) || + _makeAnchor(jsPlumb.Defaults.Anchor, elementId), + u = params.uuids ? params.uuids[index] : null, + e = _newEndpoint({ + paintStyle : es, + hoverPaintStyle:ehs, + endpoint : ep, + connections : [ self ], + uuid : u, + anchor : a, + source : element, + container:params.container, + reattach:params.reattach, + detachable:params.detachable + }); + self.endpoints[index] = e; + + + if (params.drawEndpoints === false) e.setVisible(false, true, true); + + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, + 0, + params, + self.source, + self.sourceId, + params.paintStyle, + params.hoverPaintStyle); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + + // if there were no endpoints supplied and the source element is the target element, we will reuse the source + // endpoint that was just created. + var existingTargetEndpoint = ((self.sourceId == self.targetId) && params.targetEndpoint == null) ? eS : params.targetEndpoint, + eT = prepareEndpoint(existingTargetEndpoint, + 1, + params, + self.target, + self.targetId, + params.paintStyle, + params.hoverPaintStyle); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + // if delete endpoints on detach, keep a record of just exactly which endpoints they are. + if (params.deleteEndpointsOnDetach) + self.endpointsToDeleteOnDetach = [eS, eT]; + + var _detachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.detachable === false) _detachable = false; + if(self.endpoints[0].connectionsDetachable === false) _detachable = false; + if(self.endpoints[1].connectionsDetachable === false) _detachable = false; + + /* + Function: isDetachable + Returns whether or not this connection can be detached from its target/source endpoint. by default this + is false; use it in conjunction with the 'reattach' parameter. + */ + this.isDetachable = function() { + return _detachable === true; + }; + + /* + Function: setDetachable + Sets whether or not this connection is detachable. + */ + this.setDetachable = function(detachable) { + _detachable = detachable === true; + }; + + // merge all the parameters objects into the connection. parameters set + // on the connection take precedence; then target endpoint params, then + // finally source endpoint params. + var _p = jsPlumb.CurrentLibrary.extend({}, this.endpoints[0].getParameters()); + jsPlumb.CurrentLibrary.extend(_p, this.endpoints[1].getParameters()); + jsPlumb.CurrentLibrary.extend(_p, self.getParameters()); + self.setParameters(_p); + + var _bindConnectorEvents = function() { + // add mouse events + self.connector.bind("click", function(con, e) { + self.fire("click", self, e); + }); + self.connector.bind("dblclick", function(con, e) { + self.fire("dblclick", self, e); }); + self.connector.bind("mouseenter", function(con, e) { + if (!self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(true, false); + } + self.fire("mouseenter", self, e); + } + }); + self.connector.bind("mouseexit", function(con, e) { + if (self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(false, false); + } + self.fire("mouseexit", self, e); + } + }); + + }; + + /* + * Function: setConnector + * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method - the same + * thing that you would set as the 'connector' property on a jsPlumb.connect call. + * + * Parameters: + * connector - Connector definition + */ + this.setConnector = function(connector, doNotRepaint) { + if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent); + var connectorArgs = { + _jsPlumb:self._jsPlumb, + parent:params.parent, + cssClass:params.cssClass, + container:params.container, + tooltip:self.tooltip + }; + if (connector.constructor == String) + this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand. + else if (connector.constructor == Array) + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs)); + self.canvas = self.connector.canvas; + _bindConnectorEvents(); + if (!doNotRepaint) self.repaint(); + }; + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc) + */ + + self.setConnector(this.endpoints[0].connector || + this.endpoints[1].connector || + params.connector || + _currentInstance.Defaults.Connector || + jsPlumb.Defaults.Connector, true); + + + // override setHover to pass it down to the underlying connector + var _sh = self.setHover; + self.setHover = function() { + self.connector.setHover.apply(self.connector, arguments); + _sh.apply(self, arguments); + }; + + this.setPaintStyle(this.endpoints[0].connectorStyle || + this.endpoints[1].connectorStyle || + params.paintStyle || + _currentInstance.Defaults.PaintStyle || + jsPlumb.Defaults.PaintStyle, true); + + this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || + this.endpoints[1].connectorHoverStyle || + params.hoverPaintStyle || + _currentInstance.Defaults.HoverPaintStyle || + jsPlumb.Defaults.HoverPaintStyle, true); + + this.paintStyleInUse = this.paintStyle; + + /* + * Property: overlays + * List of Overlays for this Connection. + */ + this.overlays = []; + var processOverlay = function(o) { + var _newOverlay = null; + if (o.constructor == Array) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0], + // make a copy of the object so as not to mess up anyone else's reference... + p = jsPlumb.CurrentLibrary.extend({connection:self, _jsPlumb:_currentInstance}, o[1]); + if (o.length == 3) jsPlumb.CurrentLibrary.extend(p, o[2]); + _newOverlay = new jsPlumb.Overlays[renderMode][type](p); + if (p.events) { + for (var evt in p.events) { + _newOverlay.bind(evt, p.events[evt]); + } + } + } else if (o.constructor == String) { + _newOverlay = new jsPlumb.Overlays[renderMode][o]({connection:self, _jsPlumb:_currentInstance}); + } else { + _newOverlay = o; + } + + self.overlays.push(_newOverlay); + }; + var _overlays = params.overlays || _currentInstance.Defaults.Overlays; + if (_overlays) { + for (var i = 0; i < _overlays.length; i++) { + processOverlay(_overlays[i]); + } + } + + this.moveParent = function(newParent) { + var jpcl = jsPlumb.CurrentLibrary, curParent = jpcl.getParent(self.connector.canvas); + jpcl.removeElement(self.connector.canvas, curParent); + jpcl.appendElement(self.connector.canvas, newParent); + if (self.connector.bgCanvas) { + jpcl.removeElement(self.connector.bgCanvas, curParent); + jpcl.appendElement(self.connector.bgCanvas, newParent); + } + // this only applies for DOMOverlays + for (var i = 0; i < self.overlays.length; i++) { + if (self.overlays[i].isAppendedAtTopLevel) { + jpcl.removeElement(self.overlays[i].canvas, curParent); + jpcl.appendElement(self.overlays[i].canvas, newParent); + if (self.overlays[i].reattachListeners) self.overlays[i].reattachListeners(); + } + } + if (self.connector.reattachListeners) // this is for SVG/VML; change an element's parent and you have to reinit its listeners. + self.connector.reattachListeners(); // the Canvas implementation doesn't have to care about this + }; + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Connection. + * + * Parameters: + * event - the event to bind. Available events on a Connection are: + * - *click* : notification that a Connection was clicked. + * - *dblclick* : notification that a Connection was double clicked. + * - *mouseenter* : notification that the mouse is over a Connection. + * - *mouseexit* : notification that the mouse exited a Connection. + * + * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Connection's paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Connection will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Connection. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + // overlay finder helper method + var _getOverlayIndex = function(id) { + var idx = -1; + for (var i = 0; i < self.overlays.length; i++) { + if (id === self.overlays[i].id) { + idx = i; + break; + } + } + return idx; + }; + + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + this.addOverlay = function(overlay) { + processOverlay(overlay); + self.repaint(); + }; + + /* + * Function: getOverlay + * Gets an overlay, by ID. Note: by ID. You would pass an 'id' parameter + * in to the Overlay's constructor arguments, and then use that to retrieve + * it via this method. + */ + this.getOverlay = function(id) { + var idx = _getOverlayIndex(id); + return idx >= 0 ? self.overlays[idx] : null; + }; + + /* + * Function: hideOverlay + * Hides the overlay specified by the given id. + */ + this.hideOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.hide(); + }; + + /* + * Function: showOverlay + * Shows the overlay specified by the given id. + */ + this.showOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.show(); + }; + + /** + * Function: removeAllOverlays + * Removes all overlays from the Connection, and then repaints. + */ + this.removeAllOverlays = function() { + self.overlays.splice(0, self.overlays.length); + self.repaint(); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + this.removeOverlay = function(overlayId) { + var idx = _getOverlayIndex(overlayId); + if (idx != -1) { + var o = self.overlays[idx]; + o.cleanup(); + self.overlays.splice(idx, 1); + } + }; + + /** + * Function:removeOverlays + * Removes a set of overlays by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + this.removeOverlays = function() { + for (var i = 0; i < arguments.length; i++) + self.removeOverlay(arguments[i]); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + this.labelStyle = params.labelStyle || _currentInstance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + if (this.label) { + this.overlays.push(new jsPlumb.Overlays[renderMode].Label( { + cssClass:params.cssClass, + labelStyle : this.labelStyle, + label : this.label, + connection:self, + _jsPlumb:_currentInstance + })); + } + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String or a Function that returns a String. + */ + this.setLabel = function(l) { + self.label = l; + _currentInstance.repaint(self.source); + }; + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId], + otherOffset = offsets[this.targetId], + otherWH = sizes[this.targetId], + initialTimestamp = _timestamp(), + anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + elementId:this.endpoints[0].elementId, + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1], + timestamp:initialTimestamp + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc, timestamp:initialTimestamp }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + elementId:this.endpoints[1].elementId, + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0], + timestamp:initialTimestamp + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc, timestamp:initialTimestamp }); + + /* + * Paints the Connection. Not exposed for public usage. + * + * Parameters: + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp, + // if the moving object is not the source we must transpose the two references. + swap = false, + tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId, + tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + + _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }); + _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var sE = this.endpoints[sIdx], tE = this.endpoints[tIdx], + sAnchorP = sE.anchor.getCurrentLocation(sE), + tAnchorP = tE.anchor.getCurrentLocation(tE); + + /* paint overlays*/ + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible()) maxSize = Math.max(maxSize, o.computeMaxSize(self.connector)); + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, + this.endpoints[sIdx], this.endpoints[tIdx], + this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, + self.paintStyleInUse.lineWidth, maxSize); + + self.connector.paint(dim, self.paintStyleInUse); + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim); + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function(params) { + params = params || {}; + var recalc = !(params.recalc === false); + this.paint({ elId : this.sourceId, recalc : recalc, timestamp:params.timestamp }); + }; + // resizing (using the jquery.ba-resize plugin). todo: decide + // whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + + // just to make sure the UI gets initialised fully on all browsers. + self.repaint(); + }; + +// ENDPOINT HELPER FUNCTIONS + var _makeConnectionDragHandler = function(placeholder) { + var stopped = false; + return function() { + if (stopped) return true; + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments), + el = placeholder.element; + if (el) { + jsPlumb.CurrentLibrary.setOffset(el, _ui); + _draw(_getElementObject(el), _ui); + } + + this.stopDrag = function() { + stopped = true; + }; + }; + }; + + var _makeFloatingEndpoint = function(paintStyle, referenceAnchor, endpoint, referenceCanvas, sourceElement) { + var floatingAnchor = new FloatingAnchor( { reference : referenceAnchor, referenceCanvas : referenceCanvas }); + + //setting the scope here should not be the way to fix that mootools issue. it should be fixed by not + // adding the floating endpoint as a droppable. that makes more sense anyway! + + return _newEndpoint({ paintStyle : paintStyle, endpoint : endpoint, anchor : floatingAnchor, source : sourceElement, scope:"__floating" }); + }; + + /** + * creates a placeholder div for dragging purposes, adds it to the DOM, and pre-computes its offset. then returns + * both the element id and a selector for the element. + */ + var _makeDraggablePlaceholder = function(placeholder, parent) { + var n = document.createElement("div"); + n.style.position = "absolute"; + var placeholderDragElement = _getElementObject(n); + _appendElement(n, parent); + var id = _getId(placeholderDragElement); + _updateOffset( { elId : id }); + // create and assign an id, and initialize the offset. + placeholder.id = id; + placeholder.element = placeholderDragElement; + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1 + * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't + * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you + * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or + * 'target' Endpoints for Connections. + * + * + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * container - optional id or selector instructing jsPlumb where to attach the element it creates for this endpoint. you should read the documentation for a full discussion of this. + * connections - optional list of Connections to configure the Endpoint with. + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + var self = this; + self.idPrefix = "_jsplumb_e_"; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + params = params || {}; + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Endpoint. + * + * Parameters: + * event - the event to bind. Available events on an Endpoint are: + * - *click* : notification that a Endpoint was clicked. + * - *dblclick* : notification that a Endpoint was double clicked. + * - *mouseenter* : notification that the mouse is over a Endpoint. + * - *mouseexit* : notification that the mouse exited a Endpoint. + * + * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Endpoint's paint style and then repaints the Endpoint. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Endpoint will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Endpoint. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + var visible = true; + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + var _element = params.source, _uuid = params.uuid, floatingEndpoint = null, inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + this.element = _element; + + if (params.dynamicAnchors) + self.anchor = new DynamicAnchor(jsPlumb.makeAnchors(params.dynamicAnchors)); + else + self.anchor = params.anchor ? jsPlumb.makeAnchor(params.anchor, _elementId, _currentInstance) : params.anchors ? jsPlumb.makeAnchor(params.anchors, _elementId, _currentInstance) : jsPlumb.makeAnchor("TopCenter", _elementId, _currentInstance); + + // ANCHOR MANAGER + if (!params._transient) // in place copies, for example, are transient. they will never need to be retrieved during a paint cycle, because they dont move, and then they are deleted. + _currentInstance.anchorManager.add(self, _elementId); + + var _endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot", + endpointArgs = { + _jsPlumb:self._jsPlumb, + parent:params.parent, + container:params.container, + tooltip:params.tooltip, + connectorTooltip:params.connectorTooltip, + endpoint:self + }; + if (_endpoint.constructor == String) + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint](endpointArgs); + else if (_endpoint.constructor == Array) { + endpointArgs = jsPlumb.extend(_endpoint[1], endpointArgs); + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint[0]](endpointArgs); + } + else + _endpoint = _endpoint.clone(); + + // assign a clone function using our derived endpointArgs. this is used when a drag starts: the endpoint that was dragged is cloned, + // and the clone is left in its place while the original one goes off on a magical journey. + this.clone = function() { + var o = new Object(); + _endpoint.constructor.apply(o, [endpointArgs]); + return o; + }; + + self.endpoint = _endpoint; + self.type = self.endpoint.type; + // override setHover to pass it down to the underlying endpoint + var _sh = self.setHover; + self.setHover = function() { + self.endpoint.setHover.apply(self.endpoint, arguments); + _sh.apply(self, arguments); + }; + // endpoint delegates to first connection for hover, if there is one. + var internalHover = function(state) { + if (self.connections.length > 0) + self.connections[0].setHover(state, false); + else + self.setHover(state); + }; + + // TODO this event listener registration code is identical to what Connection does: it should be refactored. + this.endpoint.bind("click", function(e) { self.fire("click", self, e); }); + this.endpoint.bind("dblclick", function(e) { self.fire("dblclick", self, e); }); + // this method is different; endpoint delegates hover to the first connection if there is one. + // that allows the connection to notify all its endpoint and avoids a circular loop + this.endpoint.bind("mouseenter", function(con, e) { + if (!self.isHover()) { + internalHover(true); + self.fire("mouseenter", self, e); + } + }); + // this method is different; endpoint delegates hover to the first connection if there is one. + // that allows the connection to notify all its endpoint and avoids a circular loop + this.endpoint.bind("mouseexit", function(con, e) { + if (self.isHover()) { + internalHover(false); + self.fire("mouseexit", self, e); + } + }); + + this.setPaintStyle(params.paintStyle || + params.style || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyle, true); + this.setHoverPaintStyle(params.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyle, true); + this.paintStyleInUse = this.paintStyle; + this.connectorStyle = params.connectorStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.connectorTooltip = params.connectorTooltip; + this.parent = params.parent; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + + var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + /* + * Property: canvas + * The Endpoint's Canvas. + */ + this.canvas = this.endpoint.canvas; + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + this.connections = params.connections || []; + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + self.isReattach = params.reattach || false; + self.connectionsDetachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.connectionsDetachable === false || params.detachable === false) + self.connectionsDetachable = false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget, forceDetach, fireEvent) { + var idx = _findIndex(self.connections, connection), actuallyDetached = false; + fireEvent = (fireEvent !== false); + if (idx >= 0) { + // 1. does the connection have a before detach (note this also checks jsPlumb's bound + // detach handlers; but then Endpoint's check will, too, hmm.) + if (forceDetach || connection._forceDetach || connection.isDetachable() || connection.isDetachAllowed(connection)) { + // get the target endpoint + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + // it would be nice to check with both endpoints that it is ok to detach. but + // for this we'll have to get a bit fancier: right now if you use the same beforeDetach + // interceptor for two endpoints (which is kind of common, because it's part of the + // endpoint definition), then it gets fired twice. so in fact we need to loop through + // each beforeDetach and see if it returns false, at which point we exit. but if it + // returns true, we have to check the next one. however we need to track which ones + // have already been run, and not run them again. + if (forceDetach || connection._forceDetach || (self.isDetachAllowed(connection) /*&& t.isDetachAllowed(connection)*/)) { + + self.connections.splice(idx, 1); + + // this avoids a circular loop + if (!ignoreTarget) { + + t.detach(connection, true, forceDetach); + // check connection to see if we want to delete the endpoints associated with it. + // we only detach those that have just this connection; this scenario is most + // likely if we got to this bit of code because it is set by the methods that + // create their own endpoints, like .connect or .makeTarget. the user is + // not likely to have interacted with those endpoints. + if (connection.endpointsToDeleteOnDetach){ + for (var i = 0; i < connection.endpointsToDeleteOnDetach.length; i++) { + var cde = connection.endpointsToDeleteOnDetach[i]; + if (cde && cde.connections.length == 0) + jsPlumb.deleteEndpoint(cde); + } + } + } + _removeElements(connection.connector.getDisplayElements(), connection.parent); + _removeFromList(connectionsByScope, connection.scope, connection); + actuallyDetached = true; + var doFireEvent = (!ignoreTarget && fireEvent) + fireDetachEvent(connection, doFireEvent); + } + } + } + return actuallyDetached; + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + * + * Parameters: + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachAll = function(fireEvent) { + while (self.connections.length > 0) { + self.detach(self.connections[0], false, true, fireEvent); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachFrom = function(targetEndpoint, fireEvent) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + if (self.detach(c[i], false, true, fireEvent)) + c[i].setHover(false, false); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: setElement + * Sets the DOM element this Endpoint is attached to. + */ + this.setElement = function(el) { + // TODO possibly have this object take charge of moving the UI components into the appropriate + // parent. this is used only by makeSource right now, and that function takes care of + // moving the UI bits and pieces. however it would s + var parentId = _getId(el); + // remove the endpoint from the list for the current endpoint's element + _removeFromList(endpointsByElement, _elementId, self); + _element = _getElementObject(el); + _elementId = _getId(_element); + self.elementId = _elementId; + + // need to get the new parent now + var newParentElement = _getParentFromParams({source:parentId}), + curParent = jpcl.getParent(self.canvas); + jpcl.removeElement(self.canvas, curParent); + jpcl.appendElement(self.canvas, newParentElement); + + // now move connection(s)...i would expect there to be only one but we will iterate. + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].moveParent(newParentElement); + self.connections[i].sourceId = _elementId; + self.connections[i].source = _element; + } + + _addToList(endpointsByElement, parentId, self); + _currentInstance.repaint(parentId); + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + return _newEndpoint( { + anchor : self.anchor, + source : _element, + paintStyle : this.paintStyle, + endpoint : _endpoint, + _transient:true, + scope:self.scope + }); + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + var candidate = self.connections[0]; + if (self.isTarget && candidate) return candidate; + else { + return (self.connections.length < _maxConnections) || _maxConnections == -1 ? null : candidate; + } + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + /* + * Function: setStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to + * setStyle and deprecate it if so. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + * + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + // a helper function that tries to find a connection to the given element, and returns it if so. if elementWithPrecedence is null, + // or no connection to it is found, we return the first connection in our list. + var findConnectionToUseForDynamicAnchor = function(elementWithPrecedence) { + var idx = 0; + if (elementWithPrecedence != null) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].sourceId == elementWithPrecedence || self.connections[i].targetId == elementWithPrecedence) { + idx = i; + break; + } + } + } + + return self.connections[idx]; + }; + + /* + * Function: paint + * Paints the Endpoint, recalculating offset and anchor positions if necessary. This does NOT paint + * any of the Endpoint's connections. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + params = params || {}; + var timestamp = params.timestamp, + recalc = !(params.recalc === false); + if (!timestamp || self.timestamp !== timestamp) { + _updateOffset({ elId:_elementId, timestamp:timestamp, recalc:recalc }); + var ap = params.anchorPoint, connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + var xy = params.offset || offsets[_elementId], + wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (recalc && self.anchor.isDynamic && self.connections.length > 0) { + var c = findConnectionToUseForDynamicAnchor(params.elementWithPrecedence), + oIdx = c.endpoints[0] == self ? 1 : 0, + oId = oIdx == 0 ? c.sourceId : c.targetId, + oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + ap = self.anchor.compute(anchorParams); + } + + var d = _endpoint.compute(ap, self.anchor.getOrientation(_endpoint), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + _endpoint.paint(d, self.paintStyleInUse, self.anchor); + self.timestamp = timestamp; + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var placeholderInfo = { + id:null, + element:null + }, + jpc = null, + existingJpc = false, + existingJpcParams = null, + _dragHandler = _makeConnectionDragHandler(placeholderInfo); + + var start = function() { + // drag might have started on an endpoint that is not actually a source, but which has + // one or more connections. + jpc = self.connectorSelector(); + var _continue = true; + // if no connection and we're not a source, return. + if (jpc == null && !params.isSource) _continue = false; + // otherwise if we're full and not allowed to drag, also return false. + if (params.isSource && self.isFull() && !dragAllowedWhenFull) _continue = false; + + // if the connection was setup as not detachable (or one of its endpoints + // was setup as connectionsDetachable = false, or Defaults.ConnectionsDetachable + // is set to false... + if (jpc != null && !jpc.isDetachable()) _continue = false; + + if (_continue === false) { + // this is for mootools and yui. returning false from this causes jquery to stop drag. + // the events are wrapped in both mootools and yui anyway, but i don't think returning + // false from the start callback would stop a drag. + if (jsPlumb.CurrentLibrary.stopDrag) jsPlumb.CurrentLibrary.stopDrag(); + _dragHandler.stopDrag(); + return false; + } + + // if we're not full but there was a connection, make it null. we'll create a new one. + if (jpc && !self.isFull() && params.isSource) jpc = null; + + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + _makeDraggablePlaceholder(placeholderInfo, self.parent); + + // set the offset of this div to be where 'inPlaceCopy' is, to start with. + // TODO merge this code with the code in both Anchor and FloatingAnchor, because it + // does the same stuff. + var ipcoel = _getElementObject(inPlaceCopy.canvas), + ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel), + po = adjustForParentOffsetAndScroll([ipco.left, ipco.top], inPlaceCopy.canvas); + jsPlumb.CurrentLibrary.setOffset(placeholderInfo.element, {left:po[0], top:po[1]}); + + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", placeholderInfo.id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + floatingEndpoint = _makeFloatingEndpoint(self.paintStyle, self.anchor, _endpoint, self.canvas, placeholderInfo.element); + + if (jpc == null) { + self.anchor.locked = true; + self.setHover(false, false); + // TODO the hover call above does not reset any target endpoint's hover + // states. + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : self.endpointWillMoveTo || _getElementObject(_element), // for makeSource with parent option. ensure source element is represented correctly. + target : placeholderInfo.element, + anchors : [ self.anchor, floatingEndpoint.anchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays + }); + + } else { + existingJpc = true; + jpc.connector.setHover(false, false); + // jpc.endpoints[0].setHover(false, false); + //jpc.endpoints[1].setHover(false, false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas), false, true); + //var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; // are we the source or the target? + // new anchor idx + var anchorIdx = jpc.endpoints[0].id == self.id ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas), + dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // now we want to get this endpoint's DROP scope, and set it for now: we can only be dropped on drop zones + // that have our drop scope (issue 57). + var dropScope = jsPlumb.CurrentLibrary.getDropScope(c); + jsPlumb.CurrentLibrary.setDragScope(c, dropScope); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = placeholderInfo.element; + jpc.sourceId = placeholderInfo.id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = placeholderInfo.element; + jpc.targetId = placeholderInfo.id; + } + + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.suspendedEndpoint.setHover(false); + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + // register it and register connection on it. + floatingConnections[placeholderInfo.id] = jpc; + floatingEndpoint.addConnection(jpc); + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, placeholderInfo.id, floatingEndpoint); + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var jpcl = jsPlumb.CurrentLibrary, + dragOptions = params.dragOptions || {}, + defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions), + startEvent = jpcl.dragEvents["start"], + stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"]; + + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + // extracted drag handler function so can be used by makeSource + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], _dragHandler); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _currentInstance.currentlyDragging = false; + _removeFromList(endpointsByElement, placeholderInfo.id, floatingEndpoint); + _removeElements( [ placeholderInfo.element[0], floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + _currentInstance.anchorManager.clearFor(placeholderInfo.id); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (self.isReattach || jpc._forceDetach || !jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc)) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } + jpc._forceDetach = null; + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElements(jpc.connector.getDisplayElements(), self.parent); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint({recalc:false}); + jpc.setHover(false, false); + jpc = null; + inPlaceCopy = null; + delete endpointsByElement[floatingEndpoint.elementId]; + floatingEndpoint.anchor = null; + floatingEndpoint = null; + _currentInstance.currentlyDragging = false; + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions, true); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas, forceInit, isTransient) { + if ((params.isTarget || forceInit) && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop'], + overEvent = jsPlumb.CurrentLibrary.dragEvents['over'], + outEvent = jsPlumb.CurrentLibrary.dragEvents['out'], + drop = function() { + var draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + elId = _getAttribute(draggable, "elId"), + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id], + idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + + // restore the original scope if necessary (issue 57) + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + + var _doContinue = true; + // the second check here is for the case that the user is dropping it back + // where it came from. + if (jpc.suspendedEndpoint && jpc.suspendedEndpoint.id != self.id) { + if (!jpc.isDetachAllowed(jpc) || !jpc.endpoints[idx].isDetachAllowed(jpc) || !jpc.suspendedEndpoint.isDetachAllowed(jpc) || !_currentInstance.checkCondition("beforeDetach", jpc)) + _doContinue = false; + } + + // these have to be set before testing for beforeDrop. + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + + // now check beforeDrop. this will be available only on Endpoints that are setup to + // have a beforeDrop condition (although, secretly, under the hood all Endpoints and + // the Connection have them, because they are on jsPlumbUIComponent. shhh!), because + // it only makes sense to have it on a target endpoint. + _doContinue = _doContinue && self.isDropAllowed(jpc.sourceId, jpc.targetId, jpc.scope); + + if (_doContinue) { + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + if (!jpc.suspendedEndpoint) { + //_addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + fireDetachEvent({ + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }, true); + } + + // finalise will inform the anchor manager and also add to + // connectionsByScope if necessary. + _finaliseConnection(jpc); + } + else { + // otherwise just put it back on the endpoint it was on before the drag. + if (jpc.suspendedEndpoint) { + // self.detachFrom(jpc); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.setHover(false); + jpc._forceDetach = true; + if (idx == 0) { + jpc.source = jpc.suspendedEndpoint.element; + jpc.sourceId = jpc.suspendedEndpoint.elementId; + } else { + jpc.target = jpc.suspendedEndpoint.element; + jpc.targetId = jpc.suspendedEndpoint.elementId;; + } + jpc.suspendedEndpoint.addConnection(jpc); + + jpc.endpoints[0].repaint(); + jpc.repaint(); + jsPlumb.repaint(jpc.source.elementId); + jpc._forceDetach = false; + } + } + + jpc.floatingAnchorIndex = null; + } + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + } + }); + dropOptions[outEvent] = _wrap(dropOptions[outEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + } + }); + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions, true, isTransient); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas), true, !(params._transient || self.anchor.isFloating)); + + return self; + }; + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + j.init(); + return j; + }; + jsPlumb.util = { + convertStyle : function(s, ignoreAlpha) { + // TODO: jsPlumb should support a separate 'opacity' style member. + if ("transparent" === s) return s; + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + return o; + }, + gradient : function(p1, p2) { + p1 = p1.constructor == Array ? p1 : [p1.x, p1.y]; + p2 = p2.constructor == Array ? p2 : [p2.x, p2.y]; + return (p2[1] - p1[1]) / (p2[0] - p1[0]); + }, + normal : function(p1, p2) { + return -1 / jsPlumb.util.gradient(p1,p2); + }, + segment : function(p1, p2) { + p1 = p1.constructor == Array ? p1 : [p1.x, p1.y]; + p2 = p2.constructor == Array ? p2 : [p2.x, p2.y]; + if (p2[0] > p1[0]) { + return (p2[1] > p1[1]) ? 2 : 1; + } + else { + return (p2[1] > p1[1]) ? 3 : 4; + } + }, + segmentMultipliers : [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ], + inverseSegmentMultipliers : [null, [-1, -1], [-1, 1], [1, 1], [1, -1] ], + pointOnLine : function(fromPoint, toPoint, distance) { + var m = jsPlumb.util.gradient(fromPoint, toPoint), + s = jsPlumb.util.segment(fromPoint, toPoint), + segmentMultiplier = distance > 0 ? jsPlumb.util.segmentMultipliers[s] : jsPlumb.util.inverseSegmentMultipliers[s], + theta = Math.atan(m), + y = Math.abs(distance * Math.sin(theta)) * segmentMultiplier[1], + x = Math.abs(distance * Math.cos(theta)) * segmentMultiplier[0]; + return { x:fromPoint.x + x, y:fromPoint.y + y }; + }, + /** + * calculates a perpendicular to the line fromPoint->toPoint, that passes through toPoint and is 'length' long. + * @param fromPoint + * @param toPoint + * @param length + */ + perpendicularLineTo : function(fromPoint, toPoint, length) { + var m = jsPlumb.util.gradient(fromPoint, toPoint), + theta2 = Math.atan(-1 / m), + y = length / 2 * Math.sin(theta2), + x = length / 2 * Math.cos(theta2); + return [{x:toPoint.x + x, y:toPoint.y + y}, {x:toPoint.x - x, y:toPoint.y - y}]; + } + }; + + var _curryAnchor = function(x, y, ox, oy, type, fnInit) { + return function(params) { + params = params || {}; + var a = jsPlumb.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance); + a.type = type; + if (fnInit) fnInit(a, params); + return a; + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1, "TopCenter"); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1, "BottomCenter"); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0, "LeftMiddle"); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0, "RightMiddle"); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0, "Center"); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1, "TopRight"); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1, "BottomRight"); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1, "TopLeft"); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1, "BottomLeft"); + + jsPlumb.Defaults.DynamicAnchors = function(params) { + return jsPlumb.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"], params.elementId, params.jsPlumbInstance); + }; + jsPlumb.Anchors["AutoDefault"] = function(params) { + var a = jsPlumb.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors(params)); + a.type = "AutoDefault"; + return a; + }; + + jsPlumb.Anchors["Assign"] = _curryAnchor(0,0,0,0,"Assign", function(anchor, params) { + // find what to use as the "position finder". the user may have supplied a String which represents + // the id of a position finder in jsPlumb.AnchorPositionFinders, or the user may have supplied the + // position finder as a function. we find out what to use and then set it on the anchor. + var pf = params.position || "Fixed"; + anchor.positionFinder = pf.constructor == String ? jsPlumb.AnchorPositionFinders[pf] : pf; + // always set the constructor params; the position finder might need them later (the Grid one does, + // for example) + anchor.constructorParams = params; + }); + + // Continuous anchor is just curried through to the 'get' method of the continuous anchor + // factory. + jsPlumb.Anchors["Continuous"] = function(params) { + return params.jsPlumbInstance.continuousAnchorFactory.get(params); + }; + + // these are the default anchor positions finders, which are used by the makeTarget function. supply + // a position finder argument to that function allows you to specify where the resulting anchor will + // be located + jsPlumb.AnchorPositionFinders = { + "Fixed": function(dp, ep, es, a) { + return [ (dp.left - ep.left) / es[0], (dp.top - ep.top) / es[1] ]; + }, + "Grid":function(dp, ep, es, a) { + var dx = dp.left - ep.left, dy = dp.top - ep.top, + gx = es[0] / (a.constructorParams.grid[0]), gy = es[1] / (a.constructorParams.grid[1]), + mx = Math.floor(dx / gx), my = Math.floor(dy / gy); + return [ ((mx * gx) + (gx / 2)) / es[0], ((my * gy) + (gy / 2)) / es[1] ]; + } + }; +})(); diff --git a/archive/1.3.4/jsPlumb-1.3.4-tests.js b/archive/1.3.4/jsPlumb-1.3.4-tests.js new file mode 100644 index 000000000..04a14c160 --- /dev/null +++ b/archive/1.3.4/jsPlumb-1.3.4-tests.js @@ -0,0 +1,2479 @@ +// jsplumb qunit tests. + +var _getContextNode = function() { + return $("#container"); +}; + +var assertContextExists = function() { + ok(_getContextNode().length == 1, "context node exists"); +}; + +var assertContextSize = function(elementCount) { + //equals(_getContextNode().children().length - _divs.length, elementCount, 'context has ' + elementCount + ' children'); +}; + +var assertContextEmpty = function() { + equals(_getContextNode().children.length, 0, "context empty"); +}; + +var assertEndpointCount = function(elId, count) { + equals(jsPlumb.getTestHarness().endpointCount(elId), count, elId + " has " + count + (count > 1) ? "endpoints" : "endpoint"); +}; + +var assertConnectionCount = function(endpoint, count) { + equals(endpoint.connections.length, count, "endpoint has " + count + " connections"); +}; + +var assertConnectionByScopeCount = function(scope, count) { + equals(jsPlumb.getTestHarness().connectionCount(scope), count, 'Scope ' + scope + " has " + count + (count > 1) ? "connections" : "connection"); + +}; + +var _divs = []; +var _addDiv = function(id) { + var d1 = document.createElement("div"); + _getContextNode().append(d1); + d1 = jsPlumb.CurrentLibrary.getElementObject(d1); + jsPlumb.CurrentLibrary.setAttribute(d1, "id", id); + _divs.push(id); + return d1; +}; + +var _cleanup = function() { + + jsPlumb.reset(); + jsPlumb.Defaults.Container = null; + jsPlumb.Defaults.ConnectionsDetachable = true; + + for (var i in _divs) { + $("#" + _divs[i]).remove(); + } + _divs.splice(0, _divs.length - 1); +}; + +var testSuite = function(renderMode) { + + module("jsPlumb", {teardown: _cleanup}); + + // setup the container + var container = document.createElement("div"); + container.id = "container"; + document.body.appendChild(container); + + jsPlumb.setRenderMode(renderMode); + + test(renderMode + ': findIndex method', function() { + var array = [ 1,2,3, "test", "a string", { 'foo':'bar', 'baz':1 }, { 'ding':'dong' } ]; + equals(jsPlumb.getTestHarness().findIndex(array, 1), 0, "find works for integers"); + equals(jsPlumb.getTestHarness().findIndex(array, "test"), 3, "find works for strings"); + equals(jsPlumb.getTestHarness().findIndex(array, { 'foo':'bar', 'baz':1 }), 5, "find works for objects"); + equals(jsPlumb.getTestHarness().findIndex(array, { 'ding':'dong', 'baz':1 }), -1, "find works properly for objects (objects have different length but some same properties)"); + }); + + test(renderMode + ': jsPlumb setup', function() { + ok(jsPlumb, "loaded"); + }); + + test(renderMode + ': getId', function() { + var d10 = _addDiv('d10'); + equals(jsPlumb.getTestHarness().getId(d10), "d10"); + }); + + test(renderMode + ': create a simple endpoint', function() { + var d1 = _addDiv("d1"); + var e = jsPlumb.addEndpoint($("#d1"), {}); + ok(e, 'endpoint exists'); + assertEndpointCount("d1", 1); + ok(e.id != null, "endpoint has had an id assigned"); + }); + + test(renderMode + ': create and remove a simple endpoint', function() { + var d1 = _addDiv("d1"); + var ee = jsPlumb.addEndpoint(d1, {uuid:"78978597593"}); + ok(ee != null, "endpoint exists"); + var e = jsPlumb.getEndpoint("78978597593"); + ok(e != null, "the endpoint could be retrieved by UUID"); + ok(e.id != null, "the endpoint has had an id assigned to it"); + assertEndpointCount("d1", 1); + assertContextSize(1); // one Endpoint canvas. + jsPlumb.removeEndpoint(d1, ee); + assertEndpointCount("d1", 0); + e = jsPlumb.getEndpoint("78978597593"); + equals(e, null, "the endpoint has been deleted"); + assertContextSize(0); // no Endpoint canvases. + }); + + test(renderMode + ': create two simple endpoints, registered using a selector', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + d1.addClass("window");d2.addClass("window"); + var endpoints = jsPlumb.addEndpoint($(".window"), {}); + equals(endpoints.length, 2, "endpoint added to both windows"); + assertEndpointCount("d1", 1); + assertEndpointCount("d2", 1); + }); + + test(renderMode + ': create two simple endpoints, registered using an array of element ids', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + d1.addClass("window");d2.addClass("window"); + var endpoints = jsPlumb.addEndpoint(["d1", "d2"], {}); + equals(endpoints.length, 2, "endpoint added to both windows"); + assertEndpointCount("d1", 1); + assertEndpointCount("d2", 1); + }); + + test(renderMode + ': draggable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1"); + var e = jsPlumb.addEndpoint(d1, {isSource:true}); + ok(e, 'endpoint exists'); + }); + + test(renderMode + ': droppable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1") + var e = jsPlumb.addEndpoint(d1, {isTarget:true}); + ok(e, 'endpoint exists'); + }); + + test(renderMode + ': defaultEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = jsPlumb.addEndpoint(d3, {isSource:true}); + ok(e3.anchor, 'endpoint 3 has an anchor'); + var e4 = jsPlumb.addEndpoint(d4, {isSource:true}); + assertEndpointCount("d3", 1); + assertEndpointCount("d4", 1); + assertContextSize(2); // one canvas for each of our two endpoints. + ok(!e3.isFull(), "endpoint 3 is not full."); + jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // we have one connection + jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // should have refused the connection; default max is 1. + assertContextSize(3); // now we have one more canvas, for the Connection that was accepted. + }); + + test(renderMode + ': specifiedEndpointMaxConnections', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = jsPlumb.addEndpoint(d5, {isSource:true, maxConnections:3}); + ok(e5.anchor, 'endpoint 5 has an anchor'); + var e6 = jsPlumb.addEndpoint(d6, {isSource:true, maxConnections:2}); // this one has max TWO + assertEndpointCount("d5", 1); assertEndpointCount("d6", 1); + assertContextSize(2); + ok(!e5.isFull(), "endpoint 5 is not full."); + jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 1); // we have one connection + assertContextSize(3); + jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // two connections + assertContextSize(4); + jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // should have refused; max is 2, for d4. + assertContextSize(4); + }); + + test(renderMode + ': noEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + var e4 = jsPlumb.addEndpoint(d4, {isSource:true, maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertContextSize(3); + assertConnectionCount(e3, 1); // we have one connection + jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 2); // we have two. etc (default was one. this proves max is working). + assertContextSize(4); + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e4}); + assertConnectionCount(e4, 3); + assertContextSize(6); + }); + + test(renderMode + ': anchors equal', function() { + var a1 = jsPlumb.makeAnchor([0, 1, 1, 1], null, jsPlumb); + var a2 = jsPlumb.makeAnchor([0, 1, 1, 1], null, jsPlumb); + ok(a1.equals(a2), "anchors are the same"); + }); + + test(renderMode + ': anchors equal with offsets', function() { + var a1 = jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, jsPlumb); + var a2 = jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, jsPlumb); + ok(a1.equals(a2), "anchors are the same"); + }); + + test(renderMode + ': anchors not equal', function() { + var a1 = jsPlumb.makeAnchor([0, 1, 0, 1], null, jsPlumb); + var a2 = jsPlumb.makeAnchor([0, 1, 1, 1], null, jsPlumb); + ok(!a1.equals(a2), "anchors are different"); + }); + + test(renderMode + ': anchor not equal with offsets', function() { + var a1 = jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, jsPlumb); + var a2 = jsPlumb.makeAnchor([0, 1, 1, 1], null, jsPlumb); + ok(!a1.equals(a2), "anchors are different"); + }); + + test(renderMode + ': detach does not fail when no arguments are provided', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + jsPlumb.connect({source:d3, target:d4}); + jsPlumb.detach(); + }); + + // test that detach does not fire an event by default + test(renderMode + ': jsPlumb.detach should fire detach event by default', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + jsPlumb.detach(conn); + equals(eventCount, 1); + }); + + // test that detach does not fire an event by default + test(renderMode + ': jsPlumb.detach should fire detach event by default, using params object', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + jsPlumb.detach({connection:conn}); + equals(eventCount, 1); + }); + + // test that detach fires an event when instructed to do so + test(renderMode + ': jsPlumb.detach should not fire detach event when instructed to not do so', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + jsPlumb.detach(conn, {fireEvent:false}); + equals(eventCount, 0); + }); + + // issue 81 + test(renderMode + ': jsPlumb.detach should fire only one detach event (pass Connection as argument)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + jsPlumb.detach(conn); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': jsPlumb.detach should fire only one detach event (pass Connection as param in argument)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + jsPlumb.detach({connection:conn}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': jsPlumb.detach should fire only one detach event (pass source and targets as strings as arguments in params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + jsPlumb.detach({source:"d5", target:"d6"}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': jsPlumb.detach should fire only one detach event (pass source and targets as divs as arguments in params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + jsPlumb.detach({source:d5, target:d6, fireEvent:true}); + equals(eventCount, 1); + }); + + //TODO make sure you run this test with a single detach call, to ensure that + // single detach calls result in the connection being removed. detachEveryConnection can + // just blow away the connectionsByScope array and recreate it. + test(renderMode + ': jsPlumb.getConnections (simple case, default scope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + jsPlumb.connect({source:d5, target:d6}); + assertContextSize(3); + var c = jsPlumb.getConnections(); // will get all connections in the default scope. + equals(c.length, 1, "there is one connection"); + }); + + test(renderMode + ': jsPlumb.getConnections (simple case, default scope; detach by element id using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + jsPlumb.connect({source:d5, target:d6}); + jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + jsPlumb.detach({source:'d5', target:'d6'}); + c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': jsPlumb.getConnections (simple case, default scope; detach by id using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + jsPlumb.connect({source:d5, target:d6}); + jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + jsPlumb.detach({source:"d5", target:"d6"}); + c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': jsPlumb.getConnections (simple case, default scope; detach by element object using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + jsPlumb.connect({source:d5, target:d6}); + jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + jsPlumb.detach({source:d5, target:d6}); + c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': jsPlumb.getConnections (simple case, default scope; detach by Connection)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + var c56 = jsPlumb.connect({source:d5, target:d6}); + var c67 = jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + jsPlumb.detach(c56); + assertContextSize(5); // check that the connection canvas was removed. + c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + }); + +// beforeDetach functionality + + test(renderMode + ": jsPlumb.detach; beforeDetach on connect call returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { return false; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": jsPlumb.detach; beforeDetach on connect call returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { return true; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": jsPlumb.detach; beforeDetach on connect call throws an exception; we treat it with the contempt it deserves and pretend it said the detach was ok.", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { throw "i am badly coded"; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": jsPlumb.detach; beforeDetach on addEndpoint call to source Endpoint returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return false; } }), + e2 = jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": jsPlumb.detach; beforeDetach on addEndpoint call to source Endpoint returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return true; } }), + e2 = jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + + test(renderMode + ": Endpoint.detach; beforeDetach on addEndpoint call to source Endpoint returns false; Endpoint.detach returns false too (the UI needs it to)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return false; } }), + e2 = jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + var success = e1.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + ok(!success, "Endpoint reported detach did not execute"); + }); + + test(renderMode + ": jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns false but detach is forced", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + jsPlumb.detach(c, {forceDetach:true}); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was denied but forced anyway"); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was denied but forced anyway"); + }); + + test(renderMode + ": jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return true; } }); + var c = jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": jsPlumb.detach; beforeDetach bound to jsPlumb returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = jsPlumb.connect({source:e1,target:e2}); + jsPlumb.bind("beforeDetach", function(connection) { + ok(connection.sourceId === "d1", "connection is provided and configured with correct source"); + ok(connection.targetId === "d2", "connection is provided and configured with correct target"); + return false; + }); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": jsPlumb.detach; beforeDetach bound to jsPlumb returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = jsPlumb.connect({source:e1,target:e2}); + jsPlumb.bind("beforeDetach", function(connection) { + ok(connection.sourceId === "d1", "connection is provided and configured with correct source"); + ok(connection.targetId === "d2", "connection is provided and configured with correct target"); + return true; + }); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was denied"); + }); + + test(renderMode + ": jsPlumb.detachAllConnections ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + jsPlumb.detachAllConnections(d1); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachAll was called"); + }); + + test(renderMode + ": jsPlumb.detachEveryConnection ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + jsPlumb.detachEveryConnection(); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachEveryConnection was called"); + }); + + test(renderMode + ": Endpoint.detachAll ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + e1.detachAll(); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachAllConnections was called"); + }); + +// ******** end of beforeDetach tests ************** + +// detachEveryConnection/detachAllConnections fireEvent overrides tests + + test(renderMode + ": jsPlumb.detachEveryConnection fires events", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0; + jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + jsPlumb.connect({source:d1, target:d2}); + jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + jsPlumb.detachEveryConnection(); + equals(connCount, 0, "no connections registered"); + }); + + test(renderMode + ": jsPlumb.detachEveryConnection doesn't fire events when instructed not to", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0; + jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + jsPlumb.connect({source:d1, target:d2}); + jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + jsPlumb.detachEveryConnection({fireEvent:false}); + equals(connCount, 2, "two connections registered"); + }); + + test(renderMode + ": jsPlumb.detachAllConnections fires events", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0, + e1 = jsPlumb.addEndpoint(d1), e2 = jsPlumb.addEndpoint(d2); + jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + jsPlumb.connect({source:d1, target:d2}); + jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + jsPlumb.detachAllConnections("d1"); + equals(connCount, 0, "no connections registered"); + }); + + test(renderMode + ": jsPlumb.detachAllConnections doesn't fire events when instructed not to", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0, + e1 = jsPlumb.addEndpoint(d1), e2 = jsPlumb.addEndpoint(d2); + jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + jsPlumb.connect({source:d1, target:d2}); + jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + jsPlumb.detachAllConnections("d1", {fireEvent:false}); + equals(connCount, 2, "two connections registered"); + }); + + test(renderMode + ': jsPlumb.getConnections (scope testScope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + jsPlumb.connect({source:d5, target:d6, scope:'testScope'}); + var c = jsPlumb.getConnections("testScope"); // will get all connections in testScope + equals(c.length, 1, "there is one connection"); + equals(c[0].sourceId, 'd5', "the connection's source is d5"); + equals(c[0].targetId, 'd6', "the connection's source is d6"); + c = jsPlumb.getConnections(); // will get all connections in default scope; should be none. + equals(c.length, 0, "there are no connections in the default scope"); + }); + + test(renderMode + ': jsPlumb.getAllConnections (filtered by scope)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + jsPlumb.connect({source:d9, target:d10}); // default scope + var c = jsPlumb.getAllConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 1); + equals(c['testScope'].length, 1); + // now supply a list of scopes + c = jsPlumb.getConnections(); + equals(c.length, 1); + c = jsPlumb.getConnections("testScope"); + equals(c.length, 1, "there is one connection in 'testScope'"); + }); + + test(renderMode + ': jsPlumb.getConnections (filtered by scope and sourceId)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + jsPlumb.connect({source:d9, target:d8, scope:'testScope'}); + jsPlumb.connect({source:d9, target:d10}); // default scope + var c = jsPlumb.getConnections({scope:'testScope', source:'d8'}); // will get all connections with sourceId 'd8' + equals(c.length, 1, "there is one connection in 'testScope' from d8"); + }); + + test(renderMode + ': jsPlumb.getConnections (filtered by scope, source id and target id)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d12, target:d13, scope:'testScope'}); + jsPlumb.connect({source:d11, target:d13, scope:'testScope'}); + var c = jsPlumb.getConnections({scope:'testScope', source:'d11', target:'d13'}); + equals(c.length, 1, "there is one connection from d11 to d13"); + }); + + test(renderMode + ': jsPlumb.getConnections (filtered by a list of scopes)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = jsPlumb.getConnections({scope:['testScope','testScope3']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + }); + + test(renderMode + ': jsPlumb.getConnections (filtered by a list of scopes and source ids)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + }); + + test(renderMode + ': jsPlumb.getConnections (filtered by a list of scopes, source ids and target ids)', function() { + jsPlumb.detachEveryConnection(); + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'), d14=_addDiv("d14"), d15=_addDiv("d15"); + jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d11, target:d14, scope:'testScope'}); + jsPlumb.connect({source:d11, target:d15, scope:'testScope'}); + jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + assertContextSize(18); + var c = jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11'], target:['d14', 'd15']}); + equals(c['testScope'].length, 2, 'there are two connections in testScope'); + equals(c['testScope3'], null, 'there are no connections in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + var anEntry = c['testScope'][0]; + ok(anEntry.endpoints[0] != null, "Source endpoint is set"); + ok(anEntry.endpoints[1] != null, "Target endpoint is set"); + equals(anEntry.source.attr("id"), "d11", "Source is div d11"); + equals(anEntry.target.attr("id"), "d14", "Target is div d14"); + }); + + test(renderMode + ': getEndpoints, one Endpoint added by addEndpoint, get Endpoints by selector', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + jsPlumb.addEndpoint(d1); + var e = jsPlumb.getEndpoints(d1); + equals(e.length, 1, "there is one endpoint for element d1"); + }); + + test(renderMode + ': getEndpoints, one Endpoint added by addEndpoint, get Endpoints by id', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + jsPlumb.addEndpoint(d1); + var e = jsPlumb.getEndpoints("d1"); + equals(e.length, 1, "there is one endpoint for element d1"); + }); + + test(renderMode + ': connection event listeners', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null/*, returnedParams2 = null*/; + jsPlumb.bind("jsPlumbConnection", function(params) { + returnedParams = $.extend({}, params); + }); + var c = jsPlumb.connect({source:d1, target:d2}); + ok(returnedParams != null, "new connection listener event was fired"); + ok(returnedParams.connection != null, 'connection is set'); + equals(returnedParams.sourceId, "d1", 'sourceid is set'); + equals(returnedParams.targetId, "d2", 'targetid is set'); + equals(returnedParams.source.attr("id"), "d1", 'source is set'); + equals(returnedParams.target.attr("id"), "d2" , 'target is set'); + ok(returnedParams.sourceEndpoint != null, "source endpoint is not null"); + ok(returnedParams.targetEndpoint != null, "target endpoint is not null"); + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + jsPlumb.detach(c); + ok(returnedParams.connection != null, 'connection is set'); + //jsPlumb.detachAll("d1"); + //ok(returnedParams2 != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (detach by connection)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + ok(returnedParams.connection != null, "removed connection listener event passed in connection"); + }); + + test(renderMode + ': detach event listeners (detach by element ids)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach({source:"d1",target:"d2"}); + ok(returnedParams != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (detach by elements)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach({source:d1,target:d2}); + ok(returnedParams != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detach method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detachFrom method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachFrom(e2); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detachAll method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachAll(); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via jsPlumb.deleteEndpoint method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + jsPlumb.deleteEndpoint(e1); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(1); + }); + + test(renderMode + ': detach event listeners (ensure cleared by jsPlumb.reset)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach({source:d1,target:d2, fireEvent:true}); + ok(returnedParams != null, "removed connection listener event was fired"); + returnedParams = null; + + jsPlumb.reset(); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach(d1,d2); + ok(returnedParams == null, "connection listener was cleared by jsPlumb.reset()"); + }); + + test(renderMode + ': connection events that throw errors', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null, returnedParams2 = null; + jsPlumb.bind("jsPlumbConnection", function(params) { + returnedParams = $.extend({}, params); + throw "oh no!"; + }); + jsPlumb.connect({source:d1, target:d2}); + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + jsPlumb.connect({source:d3, target:d4}); + ok(returnedParams != null, "new connection listener event was fired; we threw an error, jsPlumb survived."); + }); + + test(renderMode + ": Endpoint.detachFrom", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + e16.detachFrom(e17); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + }); + + test(renderMode + ": Endpoint.detachFromConnection", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + e16.detachFromConnection(conn); + assertContextSize(3); // all canvases should remain; the connection was not removed. + // but endpoint e16 should have no connections now. + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 1); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + }); + + test(renderMode + ": Endpoint.detachAll", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + var e16 = jsPlumb.addEndpoint($("#d16"), {isSource:true,maxConnections:-1}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint($("#d17"), {isSource:true}); + var e18 = jsPlumb.addEndpoint($("#d18"), {isSource:true}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e18}); + assertConnectionCount(e16, 2); + assertConnectionCount(e17, 1); + assertConnectionCount(e18, 1); + assertContextSize(5); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 2); + e16.detachAll(); + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + }); + + test(renderMode + ": Endpoint.detach", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + e16.detach(conn); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + }); + + test(renderMode + ": Endpoint.isConnectedTo", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.isConnectedTo(e17), true, "e16 and e17 are connected"); + }); + + test(renderMode + ": setting endpoint uuid", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + equals(e16.getUuid(), uuid, "endpoint's uuid was set correctly"); + }); + + test(renderMode + ": jsPlumb.getEndpoint (by uuid)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + }); + + test(renderMode + ": jsPlumb.deleteEndpoint (by uuid, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + jsPlumb.deleteEndpoint(uuid); + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + var ebe = jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + assertContextSize(0); + }); + + test(renderMode + ": jsPlumb.deleteEndpoint (by uuid, connections too)", function() { + // create two endpoints (one with a uuid), add them to two divs and then connect them. + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + // check that the connection was ok. + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + // delete the endpoint that has a uuid. verify that the endpoint cannot be retrieved and that the connection has been removed, but that + // element d17 still has its Endpoint. + jsPlumb.deleteEndpoint(uuid); + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + var ebe = jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + ebe = jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 1, "element d17 still has its Endpoint"); + assertContextSize(1); + + // now delete d17's endpoint and check that it has gone. + jsPlumb.deleteEndpoint(e17); + f = jsPlumb.getEndpoint(e17); + equals(f, null, "endpoint has been deleted"); + ebe = jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 0, "element d17 no longer has any Endpoints"); + assertContextSize(0); + }); + + test(renderMode + ": jsPlumb.deleteEndpoint (by reference, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + jsPlumb.deleteEndpoint(e16); + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + assertContextSize(0); + }); + + test(renderMode + ": jsPlumb.deleteEndpoint (by reference, connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + jsPlumb.deleteEndpoint(e16); + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + assertContextSize(1); + }); + + test(renderMode + ": jsPlumb.deleteEveryEndpoint", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + assertContextSize(2); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + jsPlumb.deleteEveryEndpoint(); + + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); + }); + + test(renderMode + ": jsPlumb.deleteEveryEndpoint (connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertContextSize(3); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + jsPlumb.deleteEveryEndpoint(); + + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); // no canvases. so all connection canvases have been cleaned up too. + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + }); + + test(renderMode + ": jsPlumb.addEndpoint (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {anchor:[0,0.5,0,-1]}); + var e17 = jsPlumb.addEndpoint(d17, {anchor:"TopCenter"}); + assertContextSize(2); + equals(e16.anchor.x, 0); + equals(e16.anchor.y, 0.5); + equals(e17.anchor.x, 0.5); + equals(e17.anchor.y, 0); + }); + + test(renderMode + ": jsPlumb.addEndpoint (tooltip)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = jsPlumb.addEndpoint(d16, {tooltip:"FOO"}), + e17 = jsPlumb.addEndpoint(d17, {tooltip:"BAZ"}); + assertContextSize(2); + equals(e16.canvas.getAttribute("label"), "FOO"); + equals(e17.canvas.getAttribute("label"), "BAZ"); + }); + + test(renderMode + ": jsPlumb.addEndpoint (simple case, dynamic anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + assertContextSize(2); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": jsPlumb.addEndpoint (simple case, two arg method)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchor:[0,0.5,0,-1]}); + var e17 = jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchor:"TopCenter"}); + assertContextSize(2); + equals(e16.anchor.x, 0); + equals(e16.anchor.y, 0.5); + equals(false, e16.isTarget); + equals(true, e16.isSource); + equals(e17.anchor.x, 0.5); + equals(e17.anchor.y, 0); + equals(true, e17.isTarget); + equals(false, e17.isSource); + }); + + + test(renderMode + ": jsPlumb.addEndpoints (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false, anchor:[0,0.5,0,-1] }, { isTarget:true, isSource:false, anchor:"TopCenter" }]); + assertContextSize(2); + equals(e16[0].anchor.x, 0); + equals(e16[0].anchor.y, 0.5); + equals(false, e16[0].isTarget); + equals(true, e16[0].isSource); + equals(e16[1].anchor.x, 0.5); + equals(e16[1].anchor.y, 0); + equals(true, e16[1].isTarget); + equals(false, e16[1].isSource); + }); + + test(renderMode + ": jsPlumb.addEndpoints (with reference params)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var refParams = {anchor:"RightMiddle"}; + var e16 = jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false}, { isTarget:true, isSource:false }], refParams); + assertContextSize(2); + equals(e16[0].anchor.x, 1); + equals(e16[0].anchor.y, 0.5); + equals(false, e16[0].isTarget); + equals(true, e16[0].isSource); + equals(e16[1].anchor.x, 1); + equals(e16[1].anchor.y, 0.5); + equals(true, e16[1].isTarget); + equals(false, e16[1].isSource); + }); + + test(renderMode + ": jsPlumb.addEndpoint (simple case, dynamic anchors, two arg method)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchors:["TopCenter", "BottomCenter"]}); + assertContextSize(2); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": jsPlumb.makeTarget (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + jsPlumb.makeTarget(d17, { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": jsPlumb.makeTarget (specify two divs in an array)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + jsPlumb.makeTarget([d16, d17], { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": jsPlumb.makeTarget (specify two divs by id in an array)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + jsPlumb.makeTarget(["d16", "d17"], { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": jsPlumb.makeTarget (specify divs by selector)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + d16.addClass("FOO");d17.addClass("FOO"); + jsPlumb.makeTarget($(".FOO"), { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ': jsPlumb.connect (between two Endpoints)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1); + assertEndpointCount("d2", 1); + var c = jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1); // no new endpoint should have been added + assertEndpointCount("d2", 1); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. + ok(c.id != null, "connection has had an id assigned"); + }); + + test(renderMode + ': jsPlumb.connect (Endpoint connectorTooltip parameter)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = jsPlumb.addEndpoint(d1, {connectorTooltip:"FOO"}); + var e2 = jsPlumb.addEndpoint(d2, {}); + var c = jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + equals(c.connector.canvas.getAttribute("label"), "FOO", "connector canvas has label attribute set"); + }); + + test(renderMode + ': jsPlumb.connect (tooltip parameter)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + var c = jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2, tooltip:"FOO"}); + equals(c.connector.canvas.getAttribute("label"), "FOO", "connector canvas has label attribute set"); + }); + + test(renderMode + ': jsPlumb.connect (between two Endpoints, and dont supply any parameters to the Endpoints.)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1); + assertEndpointCount("d2", 1); + jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1); // no new endpoint should have been added + assertEndpointCount("d2", 1); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. + }); + + test(renderMode + ': jsPlumb.connect (by endpoint)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + }); + + test(renderMode + ": jsPlumb.connect (two Endpoints - that have been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1, uuid:srcEndpointUuid}); + var e2 = jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1, uuid:dstEndpointUuid}); + jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ] }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertContextSize(3); + }); + + test(renderMode + ": jsPlumb.connect (two Endpoints - that have not been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ], source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + var e1 = jsPlumb.getEndpoint(srcEndpointUuid); + ok(e1 != null, "endpoint with src uuid added"); + ok(e1.canvas != null); + var e2 = jsPlumb.getEndpoint(dstEndpointUuid); + ok(e2 != null, "endpoint with target uuid added"); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + }); + + test(renderMode + ": jsPlumb.connect (two Endpoints - that have been already added - by endpoint reference)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1}); + var e2 = jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1}); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + }); + + test(renderMode + ": jsPlumb.connect (two elements)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + jsPlumb.connect({ source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + }); + + test(renderMode + ": jsPlumb.connect (Connector test, straight)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": jsPlumb.connect (Connector test, bezier, no params)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Bezier" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 150, "Bezier connector chose 150 curviness"); + }); + + test(renderMode + ": jsPlumb.connect (Connector test, bezier, curviness as int)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", { curviness:200 }] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Canvas Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 200, "Bezier connector chose 200 curviness"); + }); + + test(renderMode + ": jsPlumb.connect (Connector test, bezier, curviness as named option)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", {curviness:300}] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 300, "Bezier connector chose 300 curviness"); + }); + + test(renderMode + ": jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Canvas Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": jsPlumb.connect (anchors registered correctly; source and target anchors given, as strings)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:["LeftMiddle", "RightMiddle"] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.5, conn.endpoints[0].anchor.y, "source anchor y"); + equals(1, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.5, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + + test(renderMode + ": jsPlumb.connect (two argument method in which some data is reused across connections)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"), d19 = _addDiv("d19"); + var sharedData = { connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }; + var conn = jsPlumb.connect({ source:d16, target:d17}, sharedData); + var conn2 = jsPlumb.connect({ source:d18, target:d19}, sharedData); + assertContextSize(6); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 2); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(conn2.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + equals(0.3, conn2.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn2.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn2.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn2.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": jsPlumb.connect (Connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": jsPlumb.connect (Endpoint test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection target"); + }); + + test(renderMode + ": jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection target"); + }); + + test(renderMode + ": jsPlumb.connect (Endpoints test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Dot, "Dot endpoint chosen for connection target"); + }); + + test(renderMode + ": jsPlumb.connect (Blank Endpoint specified via 'endpoint' param)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoint:"Blank" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection target"); + }); + + test(renderMode + ": jsPlumb.connect (Blank Endpoint specified via 'endpoints' param)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoints:["Blank", "Blank" ] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection target"); + }); + + test(renderMode + ": jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Dot, "Dot endpoint chosen for connection target"); + }); + + test(renderMode + ": jsPlumb.connect (by Endpoints, connector test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {}); + var e17 = jsPlumb.addEndpoint(d17, {}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": jsPlumb.connect (by Endpoints, connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {}); + var e17 = jsPlumb.addEndpoint(d17, {}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": jsPlumb.connect (by Endpoints, anchors as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = "TopCenter", a17 = "BottomCenter"; + var e16 = jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, 0.5, "endpoint 16 is at top center");equals(e16.anchor.y, 0, "endpoint 16 is at top center"); + equals(e17.anchor.x, 0.5, "endpoint 17 is at bottom center");equals(e17.anchor.y, 1, "endpoint 17 is at bottom center"); + }); + + test(renderMode + ": jsPlumb.connect (by Endpoints, endpoints create anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = [0,0.5,0,-1], a17 = [1,0.0,-1,-1]; + var e16 = jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, a16[0]);equals(e16.anchor.y, a16[1]); + equals(e17.anchor.x, a17[0]);equals(e17.anchor.y, a17[1]); + equals(e16.anchor.getOrientation()[0], a16[2]); equals(e16.anchor.getOrientation()[1], a16[3]); + equals(e17.anchor.getOrientation()[0], a17[2]); equals(e17.anchor.getOrientation()[1], a17[3]); + }); + + test(renderMode + ": jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchor')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {anchor:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = jsPlumb.addEndpoint(d17, {anchor:["TopCenter", "BottomCenter"]}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchors')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": jsPlumb.connect (connect by element, default endpoint, supplied dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var anchors = [ [0.25, 0, 0, -1], [1, 0.25, 1, 0], [0.75, 1, 0, 1], [0, 0.75, -1, 0] ]; + jsPlumb.connect({source:d1, target:d2, dynamicAnchors:anchors}); // auto connect with default endpoint and provided anchors + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + jsPlumb.detach({source:d1, target:d2}); + // this changed in 1.3.5, because auto generated endpoints are now removed by detach. so i added the test below this one + // to check that the deleteEndpointsOnDetach flag is honoured. + assertEndpointCount("d1", 0);assertEndpointCount("d2", 0); + assertContextSize(2); + }); + + test(renderMode + ": jsPlumb.connect (connect by element, default endpoint, supplied dynamic anchors, delete on detach false)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var anchors = [ [0.25, 0, 0, -1], [1, 0.25, 1, 0], [0.75, 1, 0, 1], [0, 0.75, -1, 0] ]; + jsPlumb.connect({ + source:d1, + target:d2, + dynamicAnchors:anchors, + deleteEndpointsOnDetach:false + }); // auto connect with default endpoint and provided anchors + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + jsPlumb.detach({source:d1, target:d2}); + // this changed in 1.3.5, because auto generated endpoints are now removed by detach. so i added this test + // to check that the deleteEndpointsOnDetach flag is honoured. + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + assertContextSize(2); + }); + + test(renderMode + ": jsPlumb.connect (connect by element, supplied endpoint and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = jsPlumb.addEndpoint(d1, endpoint); + var e2 = jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + assertContextSize(3); + jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + assertContextSize(2); + }); + + test(renderMode + ": jsPlumb.connect (connect by element, supplied endpoints using 'source' and 'target' (this test is identical to the one above apart from the param names), and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = jsPlumb.addEndpoint(d1, endpoint); + var e2 = jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + jsPlumb.connect({source:e1, target:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + assertContextSize(3); + jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + assertContextSize(2); + }); + + test(renderMode + " detachable parameter defaults to true on jsPlumb.connect", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), true, "connections detachable by default"); + }); + + test(renderMode + " detachable parameter set to false on jsPlumb.connect", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = jsPlumb.connect({source:d1, target:d2, detachable:false}); + equals(c.isDetachable(), false, "connection detachable"); + }); + + test(renderMode + " setDetachable on initially detachable connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), true, "connection initially detachable"); + c.setDetachable(false); + equals(c.isDetachable(), false, "connection not detachable"); + }); + + test(renderMode + " setDetachable on initially not detachable connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = jsPlumb.connect({source:d1, target:d2, detachable:false }); + equals(c.isDetachable(), false, "connection not initially detachable"); + c.setDetachable(true); + equals(c.isDetachable(), true, "connection now detachable"); + }); + + test(renderMode + " jsPlumb.Defaults.ConnectionsDetachable", function() { + jsPlumb.Defaults.ConnectionsDetachable = false; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), false, "connections not detachable by default (overrode the defaults)"); + jsPlumb.Defaults.ConnectionsDetachable = true; + }); + + + test(renderMode + ": jsPlumb.connect (testing for connection event callback)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var connectCallback = null, detachCallback = null; + jsPlumb.bind("jsPlumbConnection", function(params) { + connectCallback = $.extend({}, params); + }); + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + detachCallback = $.extend({}, params); + }); + jsPlumb.connect({source:d1, target:d2}); // auto connect with default endpoint and anchor set + ok(connectCallback != null, "connect callback was made"); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + jsPlumb.detach({source:d1, target:d2}); + assertContextSize(2); + ok(detachCallback != null, "detach callback was made"); + }); + + test(renderMode + ": jsPlumb.connect (setting cssClass on Connector)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = jsPlumb.connect({source:d1,target:d2,cssClass:"CSS"}); + var has = function(clazz) { + var cn = c.connector.canvas.className, + cns = cn.constructor == String ? cn : cn.baseVal; + + return cns.indexOf(clazz) != -1; + }; + ok(has("CSS"), "custom cssClass set correctly"); + ok(has(jsPlumb.connectorClass), "basic connector class set correctly"); + }); + + test(renderMode + ": jsPlumb.connect (overlays, long-hand version)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = {width:40,length:40,location:0.7, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"}}; + var connection1 = jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + }); + + test(renderMode + ": jsPlumb.connect (overlays, long-hand version, IDs specified)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + equals("aLabel", connection1.overlays[0].id); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + equals("anArrow", connection1.overlays[1].id); + }); + + test(renderMode + ": jsPlumb.connect (remove single overlay by id)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + connection1.removeOverlay("aLabel"); + equals(1, connection1.overlays.length); + equals("anArrow", connection1.overlays[0].id); + }); + + test(renderMode + ": jsPlumb.connect (remove multiple overlays by id)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + connection1.removeOverlays("aLabel", "anArrow"); + equals(0, connection1.overlays.length); + }); + + test(renderMode + ": jsPlumb.connect (overlays, short-hand version)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var loc = { location:0.7 }; + var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} }; + var connection1 = jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label", {label:"CONNECTION 1", location:0.3}], + ["Arrow", arrowSpec, loc] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + }); + + test(renderMode + ": jsPlumb.connect (removeAllOverlays)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var loc = { location:0.7 }; + var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} }; + var connection1 = jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label", {label:"CONNECTION 1", location:0.3}], + ["Arrow", arrowSpec, loc] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + + connection1.removeAllOverlays(); + equals(0, connection1.overlays.length); + }); + + test(renderMode + ": jsPlumb.connect, specify arrow overlay using string identifier only", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var conn = jsPlumb.connect({source:d1,target:d2,overlays:["Arrow"]}); + equals(jsPlumb.Overlays[renderMode].Arrow, conn.overlays[0].constructor); + }); + + test(renderMode + ": Connection.getOverlay method, existing overlay", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("arrowOverlay"); + ok(overlay != null); + }); + + test(renderMode + ": Connection.getOverlay method, non-existent overlay", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("IDONTEXIST"); + ok(overlay == null); + }); + + test(renderMode + ": Overlay.setVisible method", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("arrowOverlay"); + ok(overlay.isVisible()); + overlay.setVisible(false); + ok(!overlay.isVisible()); + overlay.setVisible(true); + ok(overlay.isVisible()); + }); + + // this test is for the original detach function; it should stay working after i mess with it + // a little. + test(renderMode + ": jsPlumb.detach (by element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + var e3 = jsPlumb.addEndpoint(d1); + var e4 = jsPlumb.addEndpoint(d2); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + jsPlumb.connect({ sourceEndpoint:e3, targetEndpoint:e4 }); // make two connections to be sure this works ;) + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionCount(e3, 1); + assertConnectionCount(e4, 1); + + jsPlumb.detach({source:"d1", target:"d2"}); + + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionCount(e3, 0); + assertConnectionCount(e4, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + }); + + // detach is being made to operate more like connect - by taking one argument with a whole + // bunch of possible params in it. if two args are passed in it will continue working + // in the old way. + test(renderMode + ": jsPlumb.detach (params object, using element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + jsPlumb.detach({source:"d1", target:"d2"}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + }); + + test(renderMode + ": jsPlumb.detach (params object, using element objects)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + jsPlumb.detach({source:d1, target:d2}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + assertContextSize(2); + }); + + test(renderMode + ": jsPlumb.detach (source and target as endpoint UUIDs)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {uuid:"abcdefg"}); + ok(jsPlumb.getEndpoint("abcdefg") != null, "e1 exists"); + var e2 = jsPlumb.addEndpoint(d2, {uuid:"hijklmn"}); + ok(jsPlumb.getEndpoint("hijklmn") != null, "e2 exists"); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + jsPlumb.detach({uuids:["abcdefg", "hijklmn"]}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + assertContextSize(2); + }); + + test(renderMode + ": jsPlumb.detach (sourceEndpoint and targetEndpoint supplied)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + jsPlumb.detach({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + assertContextSize(2); + }); + + test(renderMode + ": jsPlumb.makeDynamicAnchors (longhand)", function() { + var anchors = [jsPlumb.makeAnchor([0.2, 0, 0, -1], null, jsPlumb), jsPlumb.makeAnchor([1, 0.2, 1, 0], null, jsPlumb), + jsPlumb.makeAnchor([0.8, 1, 0, 1], null, jsPlumb), jsPlumb.makeAnchor([0, 0.8, -1, 0], null, jsPlumb) ]; + var dynamicAnchor = jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); + }); + + test(renderMode + ": jsPlumb.makeDynamicAnchors (shorthand)", function() { + var anchors = [[0.2, 0, 0, -1], [1, 0.2, 1, 0], + [0.8, 1, 0, 1], [0, 0.8, -1, 0] ]; + var dynamicAnchor = jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); + }); + + test(renderMode + ": Connection.isVisible/setVisible", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c1 = jsPlumb.connect({source:d1,target:d2}); + equals(true, c1.isVisible(), "Connection is visible after creation."); + c1.setVisible(false); + equals(false, c1.isVisible(), "Connection is not visible after calling setVisible(false)."); + equals($(c1.connector.canvas).css("display"), "none"); + c1.setVisible(true); + equals(true, c1.isVisible(), "Connection is visible after calling setVisible(true)."); + equals($(c1.connector.canvas).css("display"), "block"); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible basic test (no connections)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals($(e1.canvas).css("display"), "none"); + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals($(e1.canvas).css("display"), "block"); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should track changes in the source, because it has only this connection.)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1), e2 = jsPlumb.addEndpoint(d2); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + var c1 = jsPlumb.connect({source:e1, target:e2}); + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals(false, e2.isVisible(), "other Endpoint is not visible either."); + equals(false, c1.isVisible(), "connection between the two is not visible either."); + + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals(true, e2.isVisible(), "other Endpoint is visible too"); + equals(true, c1.isVisible(), "connection between the two is visible too."); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should not track changes in the source, because it has another connection.)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var e1 = jsPlumb.addEndpoint(d1), e2 = jsPlumb.addEndpoint(d2, { maxConnections:2 }), e3 = jsPlumb.addEndpoint(d3); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + var c1 = jsPlumb.connect({source:e1, target:e2}); + var c2 = jsPlumb.connect({source:e2, target:e3}); + + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals(true, e2.isVisible(), "other Endpoint should still be visible."); + equals(true, e3.isVisible(), "third Endpoint should still be visible."); + equals(false, c1.isVisible(), "connection between the two is not visible either."); + equals(true, c2.isVisible(), "other connection is visible."); + + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals(true, e2.isVisible(), "other Endpoint is visible too"); + equals(true, c1.isVisible(), "connection between the two is visible too."); + equals(true, c2.isVisible(), "other connection is visible."); + }); + + // tests of the functionality that allows a user to specify that they want elements appended to the document body + test(renderMode + " jsPlumb.Defaults.Container, specified with a selector", function() { + jsPlumb.Defaults.Container = $("body"); + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length); // the divs we added have been added to the 'container' div. + // but we have told jsPlumb to add its canvas to the body, so this connect call should not add another few elements to the container: + jsPlumb.connect({source:d1, target:d2}); + equals(2, $("#container")[0].childNodes.length); + }); + + // tests of the functionality that allows a user to specify that they want elements appended to some specific container. + test(renderMode + " jsPlumb.Defaults.Container, specified with DOM element", function() { + jsPlumb.Defaults.Container = document.getElementsByTagName("body")[0]; + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length, "two divs added to the container"); // the divs we added have been added to the 'container' div. + // but we have told jsPlumb to add its canvas to the body, so this connect call should not add another few elements to the container: + var bodyElementCount = $("body")[0].childNodes.length; + jsPlumb.connect({source:d1, target:d2}); + equals(2, $("#container")[0].childNodes.length, "still only two children in container; elements were added to the body by jsPlumb"); + // test to see if 3 elements have been added + equals(bodyElementCount + 3, $("body")[0].childNodes.length, "3 new elements added to the document body"); + }); + + test(renderMode + " container specified to connect call, with a selector", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length); // the divs we added have been added to the 'container' div. + var bodyElementCount = $("body")[0].childNodes.length; + // but here we tell jsPlumb to add its elements to the body, so this connect call should not add another few elements to the container: + jsPlumb.connect({source:d1, target:d2, container:$("body")}); + equals(2, $("#container")[0].childNodes.length); + equals(bodyElementCount + 3, $("body")[0].childNodes.length, "3 new elements added to the document body"); + }); + + test(renderMode + " container specified to connect call, with a string ID", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + equals(3, $("#container")[0].childNodes.length, "container has divs we added"); // the divs we added have been added to the 'container' div. + var d3ElementCount = $("#d3")[0].childNodes.length; + // but here we tell jsPlumb to add its elements to "d3", so this connect call should not add another few elements to the container: + jsPlumb.connect({source:d1, target:d2, container:"d3"}); + equals(3, $("#container")[0].childNodes.length, "container still has only the divs we added"); + equals(d3ElementCount + 3, $("#d3")[0].childNodes.length, "3 new elements added to div d3"); + }); + + test(renderMode + " container specified to addEndpoint call, with a selector", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length); // the divs we added have been added to the 'container' div. + var bodyElementCount = $("body")[0].childNodes.length; + // but here we tell jsPlumb to add its elements to the body, so this connect call should not add another few elements to the container: + jsPlumb.addEndpoint(d1, {container:$("body")}); + equals(2, $("#container")[0].childNodes.length); + equals(bodyElementCount + 1, $("body")[0].childNodes.length, "1 new element added to the document body"); + }); + + test(renderMode + " container specified to addEndpoint call, with a string ID", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + equals(3, $("#container")[0].childNodes.length, "container has divs we added"); // the divs we added have been added to the 'container' div. + var d3ElementCount = $("#d3")[0].childNodes.length; + // but here we tell jsPlumb to add its elements to "d3", so this connect call should not add another few elements to the container: + jsPlumb.addEndpoint(d1, { container:"d3" }); + equals(3, $("#container")[0].childNodes.length, "container still has only the divs we added"); + equals(d3ElementCount + 1, $("#d3")[0].childNodes.length, "1 new element added to div d3"); + }); + + test(renderMode + " detachable defaults to true when connection made between two endpoints", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = jsPlumb.addEndpoint(d1), e2 = jsPlumb.addEndpoint(d2), + c = jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection not detachable"); + }); + + test(renderMode + " connection detachable when target endpoint has connectionsDetachable set to true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = jsPlumb.addEndpoint(d1), e2 = jsPlumb.addEndpoint(d2, {connectionsDetachable:true}), + c = jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection detachable because connectionsDetachable was set on target endpoint"); + }); + + test(renderMode + " connection detachable when source endpoint has connectionsDetachable set to true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = jsPlumb.addEndpoint(d1, {connectionsDetachable:true}), e2 = jsPlumb.addEndpoint(d2), + c = jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection detachable because connectionsDetachable was set on source endpoint"); + }); + + test(renderMode + " Connector has 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = jsPlumb.connect({source:d1, target:d2}); + equals(c.connector.type, "Bezier", "Bezier connector has type set"); + + var c2 = jsPlumb.connect({source:d1, target:d2, connector:"Straight"}); + equals(c2.connector.type, "Straight", "Straight connector has type set"); + + var c3 = jsPlumb.connect({source:d1, target:d2, connector:"Flowchart"}); + equals(c3.connector.type, "Flowchart", "Flowchart connector has type set"); + }); + + test(renderMode + " Endpoints have 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = jsPlumb.connect({source:d1, target:d2}); + equals(c.endpoints[0].type, "Dot", "Dot endpoint has type set"); + + var c2 = jsPlumb.connect({source:d1, target:d2, endpoints:["Rectangle", "Blank"]}); + equals(c2.endpoints[1].type, "Blank", "Blank endpoint has type set"); + equals(c2.endpoints[0].type, "Rectangle", "Rectangle endpoint has type set"); + }); + + test(renderMode + " Overlays have 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = jsPlumb.connect({ + source:d1, + target:d2, + overlays:[ "Arrow", "Label", "PlainArrow", "Diamond" ] + }); + equals(c.overlays[0].type, "Arrow", "Arrow overlay has type set"); + equals(c.overlays[1].type, "Label", "Label overlay has type set"); + equals(c.overlays[2].type, "PlainArrow", "PlainArrow overlay has type set"); + equals(c.overlays[3].type, "Diamond", "Diamond overlay has type set"); + }); + + test(renderMode + " jsPlumb.hide, original one-arg version", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = jsPlumb.addEndpoint(d1, e), + e2 = jsPlumb.addEndpoint(d2, e), + c1 = jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + jsPlumb.hide(d1); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(true, e1.isVisible(), "endpoint 1 is still visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + jsPlumb.show(d1); + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + }); + + test(renderMode + " jsPlumb.hide, two-arg version, endpoints should also be hidden", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = jsPlumb.addEndpoint(d1, e), + e2 = jsPlumb.addEndpoint(d2, e), + c1 = jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + jsPlumb.show(d1); // now show d1, but do not alter the endpoints. e1 should still be hidden + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + test(renderMode + " jsPlumb.show, two-arg version, endpoints should become visible", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = jsPlumb.addEndpoint(d1, e), + e2 = jsPlumb.addEndpoint(d2, e), + c1 = jsPlumb.connect({source:e1, target:e2}); + + jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + jsPlumb.show(d1, true); // now show d1, and alter the endpoints. e1 should be visible. + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(true, e1.isVisible(), "endpoint 1 is visible again."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + test(renderMode + " jsPlumb.show, two-arg version, endpoints should become visible, but not all connections, because some other endpoints are not visible.", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = jsPlumb.addEndpoint(d1, e), + e11 = jsPlumb.addEndpoint(d1, e), + e2 = jsPlumb.addEndpoint(d2, e), + e3 = jsPlumb.addEndpoint(d3, e), + c1 = jsPlumb.connect({source:e1, target:e2}), + c2 = jsPlumb.connect({source:e11, target:e3}); + + // we now have d1 connected to both d3 and d2. we'll hide d1, and everything on d1 should be hidden. + + jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "connection 1 is no longer visible."); + equals(false, c2.isVisible(), "connection 2 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(false, e11.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + equals(true, e3.isVisible(), "endpoint 3 is still visible."); + + // now, we will also hide d3. making d1 visible again should NOT result in c2 becoming visible, because the other endpoint + // for c2 is e3, which is not visible. + jsPlumb.hide(d3, true); + equals(false, e3.isVisible(), "endpoint 3 is no longer visible."); + + jsPlumb.show(d1, true); // now show d1, and alter the endpoints. e1 should be visible, c1 should be visible, but c2 should not. + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(false, c2.isVisible(), "Connection 2 is not visible."); + equals(true, e1.isVisible(), "endpoint 1 is visible again."); + equals(true, e11.isVisible(), "endpoint 11 is visible again."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + equals(false, e3.isVisible(), "endpoint 3 is still not visible."); + }); + + /* + test(renderMode + " jsPlumb.hide, two-arg version, endpoints should also be hidden", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = jsPlumb.addEndpoint(d1, e), + e2 = jsPlumb.addEndpoint(d2, e), + c1 = jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + /** + * test for issue 132: label leaves its element in the DOM after it has been + * removed from a connection. + */ + test(renderMode + " label cleans itself up properly", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label", cssClass:"foo"}] + ]}); + ok($(".foo").length == 1, "label element exists in DOM"); + c.removeOverlay("label"); + ok($(".foo").length == 0, "label element does not exist in DOM"); + }); + + test(renderMode + " arrow cleans itself up properly", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Arrow", {id:"arrow"}] + ]}); + ok(c.getOverlay("arrow") != null, "arrow overlay exists"); + c.removeOverlay("arrow"); + ok(c.getOverlay("arrow") == null, "arrow overlay has been removed"); + }); + + test(renderMode + " label overlay getElement function", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label"}] + ]}); + ok(c.getOverlay("label").getElement() != null, "label overlay exposes element via getElement method"); + }); + + test(renderMode + " label overlay provides getLabel and setLabel methods", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label", label:"foo"}] + ]}); + var o = c.getOverlay("label"), e = o.getElement(); + ok(e.innerHTML == "foo", "label text is set to original value"); + o.setLabel("baz"); + ok(e.innerHTML == "baz", "label text is set to new value 'baz'"); + ok(o.getLabel() === "baz", "getLabel function works correctly with String"); + // now try functions + var aFunction = function() { return "aFunction"; }; + o.setLabel(aFunction); + ok(e.innerHTML == "aFunction", "label text is set to new value from Function"); + ok(o.getLabel() === aFunction, "getLabel function works correctly with Function"); + }); + + test(renderMode + " parameters object works for Endpoint", function() { + var d1 = _addDiv("d1"), + f = function() { alert("FOO!"); }, + e = jsPlumb.addEndpoint(d1, { + isSource:true, + parameters:{ + "string":"param1", + "int":4, + "function":f + } + }); + ok(e.getParameter("string") === "param1", "getParameter(String) works correctly"); + ok(e.getParameter("int") === 4, "getParameter(int) works correctly"); + ok(e.getParameter("function") == f, "getParameter(Function) works correctly"); + }); + + test(renderMode + " parameters object works for Connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + f = function() { alert("FOO!"); }; + var c = jsPlumb.connect({ + source:d1, + target:d2, + parameters:{ + "string":"param1", + "int":4, + "function":f + } + }); + ok(c.getParameter("string") === "param1", "getParameter(String) works correctly"); + ok(c.getParameter("int") === 4, "getParameter(int) works correctly"); + ok(c.getParameter("function") == f, "getParameter(Function) works correctly"); + }); + + test(renderMode + " parameters set on Endpoints and Connections are all merged, and merged correctly at that.", function() { + var d1 = _addDiv("d1"), + d2 = _addDiv("d2"), + e = jsPlumb.addEndpoint(d1, { + isSource:true, + parameters:{ + "string":"sourceEndpoint", + "int":0, + "function":function() { return "sourceEndpoint"; } + } + }), + e2 = jsPlumb.addEndpoint(d2, { + isTarget:true, + parameters:{ + "int":1, + "function":function() { return "targetEndpoint"; } + } + }), + c = jsPlumb.connect({source:e, target:e2, parameters:{ + "function":function() { return "connection"; } + }}); + + ok(c.getParameter("string") === "sourceEndpoint", "getParameter(String) works correctly"); + ok(c.getParameter("int") === 1, "getParameter(int) works correctly"); + ok(c.getParameter("function")() == "connection", "getParameter(Function) works correctly"); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers standard connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = jsPlumb.connect({source:d1, target:d2}); + equals(jsPlumb.anchorManager.get("d1")["standard"].length, 1); + equals(jsPlumb.anchorManager.get("d1")["endpoints"].length, 1); + equals(jsPlumb.anchorManager.get("d2")["standard"].length, 1); + equals(jsPlumb.anchorManager.get("d2")["endpoints"].length, 1); + var c2 = jsPlumb.connect({source:d1, target:d2}); + equals(jsPlumb.anchorManager.get("d1")["standard"].length, 2); + equals(jsPlumb.anchorManager.get("d2")["standard"].length, 2); + equals(jsPlumb.anchorManager.get("d1")["endpoints"].length, 2); + equals(jsPlumb.anchorManager.get("d2")["endpoints"].length, 2); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers dynamic anchor connection, and removes it.", function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var c = jsPlumb.connect({source:d3, target:d4, anchors:["AutoDefault", "AutoDefault"]}); + + equals(jsPlumb.anchorManager.get("d3")["standard"].length, 1); + + var c2 = jsPlumb.connect({source:d3, target:d4}); + equals(jsPlumb.anchorManager.get("d3")["standard"].length, 2); + equals(jsPlumb.anchorManager.get("d4")["standard"].length, 2); + + equals(jsPlumb.anchorManager.get("d3")["endpoints"].length, 2); + jsPlumb.detach(c); + equals(jsPlumb.anchorManager.get("d3")["standard"].length, 1); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers continuous anchor connection, and removes it.", function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var c = jsPlumb.connect({source:d3, target:d4, anchors:["Continuous", "Continuous"]}); + + equals(jsPlumb.anchorManager.get("d3")["standard"].length, 0); + equals(jsPlumb.anchorManager.get("d3")["continuous"].length, 1); + equals(jsPlumb.anchorManager.get("d4")["standard"].length, 0); + equals(jsPlumb.anchorManager.get("d4")["continuous"].length, 1); + + jsPlumb.detach(c); + equals(jsPlumb.anchorManager.get("d3")["continuous"].length, 0); + equals(jsPlumb.anchorManager.get("d4")["continuous"].length, 0); + + jsPlumb.reset(); + equals(jsPlumb.anchorManager.get("d4")["continuousAnchorEndpoints"].length, 0); + }); + +// ------------- utility functions - math stuff, mostly -------------------------- + + var tolerance = 0.00000005, withinTolerance = function(v1, v2, msg) { + if (Math.abs(v1 - v2) < tolerance) ok(true, msg + "; expected " + v1 + " and got it"); + else { + ok(false, msg + "; expected " + v1 + " got " + v2); + } + }; + test(renderMode + " jsPlumb.util.gradient, segment 1", function() { + var p1 = [2,2], p2 = [3,1], m = jsPlumb.util.gradient(p1,p2); + equals(m, -1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumb.util.normal, segment 1", function() { + var p1 = [2,2], p2 = [3,1], m = jsPlumb.util.normal(p1,p2); + equals(m, 1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumb.util.gradient, segment 2", function() { + var p1 = [2,2], p2 = [3,3], m = jsPlumb.util.gradient(p1,p2); + equals(m, 1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumb.util.normal, segment 2", function() { + var p1 = [2,2], p2 = [3,3], m = jsPlumb.util.normal(p1,p2); + equals(m, -1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumb.util.gradient, segment 3", function() { + var p1 = [2,2], p2 = [1,3], m = jsPlumb.util.gradient(p1,p2); + equals(m, -1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumb.util.normal, segment 3", function() { + var p1 = [2,2], p2 = [1,3], m = jsPlumb.util.normal(p1,p2); + equals(m, 1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumb.util.gradient, segment 4", function() { + var p1 = [2,2], p2 = [1,1], m = jsPlumb.util.gradient(p1,p2); + equals(m, 1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumb.util.normal, segment 4", function() { + var p1 = [2,2], p2 = [1,1], m = jsPlumb.util.normal(p1,p2); + equals(m, -1, "normal calculated correctly for simple case"); + }); + test(renderMode + "jsPlumb.util.pointOnLine, segment 1", function() { + var p1 = {x:2,y:2}, p2={x:3, y:1}, + target = jsPlumb.util.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumb.util.pointOnLine, segment 2", function() { + var p1 = {x:2,y:2}, p2={x:3, y:3}, + target = jsPlumb.util.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumb.util.pointOnLine, segment 3", function() { + var p1 = {x:2,y:2}, p2={x:1, y:3}, + target = jsPlumb.util.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumb.util.pointOnLine, segment 4", function() { + var p1 = {x:2,y:2}, p2={x:1, y:1}, + target = jsPlumb.util.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumb.util.perpendicularLineTo, segment 1", function() { + var p1 = {x:2, y:2}, p2={x:3, y:1}, m = jsPlumb.util.gradient(p1, p2), + l = jsPlumb.util.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(4, l[0].x, "point 1 x is correct"); + withinTolerance(2, l[0].y, "point 1 y is correct"); + + withinTolerance(2, l[1].x, "point 2 x is correct"); + withinTolerance(0, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumb.util.perpendicularLineTo, segment 2", function() { + var p1 = {x:2, y:2}, p2={x:3, y:3}, m = jsPlumb.util.gradient(p1, p2), + l = jsPlumb.util.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(4, l[0].x, "point 1 x is correct"); + withinTolerance(2, l[0].y, "point 1 y is correct"); + + withinTolerance(2, l[1].x, "point 2 x is correct"); + withinTolerance(4, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumb.util.perpendicularLineTo, segment 3", function() { + var p1 = {x:2, y:2}, p2={x:1, y:3}, m = jsPlumb.util.gradient(p1, p2), + l = jsPlumb.util.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(2, l[0].x, "point 1 x is correct"); + withinTolerance(4, l[0].y, "point 1 y is correct"); + + withinTolerance(0, l[1].x, "point 2 x is correct"); + withinTolerance(2, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumb.util.perpendicularLineTo, segment 4", function() { + var p1 = {x:2, y:2}, p2={x:1, y:1}, m = jsPlumb.util.gradient(p1, p2), + l = jsPlumb.util.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(2, l[0].x, "point 1 x is correct"); + withinTolerance(0, l[0].y, "point 1 y is correct"); + + withinTolerance(0, l[1].x, "point 2 x is correct"); + withinTolerance(2, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumb.util.setImage on Endpoint", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { + endpoint:[ "Image", { src:"../../img/endpointTest1.png" } ] + }, + ep = jsPlumb.addEndpoint(d1, e); + ep.setImage("../../img/littledot.png"); + }); + test(renderMode + "jsPlumb.util.setImage on Endpoint, with supplied onload", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { + endpoint:[ "Image", { + src:"../../img/endpointTest1.png", + onload:function(imgEp) { + equals("../../img/endpointTest1.png", imgEp.img.src); + } + } ] + }, + ep = jsPlumb.addEndpoint(d1, e); + ep.setImage("../../img/littledot.png", function(imgEp) { + equals("../../img/littledot.png", imgEp.img.src); + }); + }); + + test(renderMode + "attach endpoint to table when desired element was td", function() { + var table = document.createElement("table"), + tr = document.createElement("tr"), + td = document.createElement("td"); + table.appendChild(tr); tr.appendChild(td); + document.body.appendChild(table); + var ep = jsPlumb.addEndpoint(td); + equals(ep.canvas.parentNode.tagName.toLowerCase(), "table"); + }); + + + + /** + * leave this test at the bottom! + */ + test(renderMode + ': unload test', function() { + jsPlumb.unload(); + var contextNode = $("._jsPlumb_context"); + ok(contextNode.length == 0, 'context node unloaded'); + }); +}; + diff --git a/archive/1.3.4/jsPlumb-connectors-statemachine-1.3.4-RC1.js b/archive/1.3.4/jsPlumb-connectors-statemachine-1.3.4-RC1.js new file mode 100644 index 000000000..381925a33 --- /dev/null +++ b/archive/1.3.4/jsPlumb-connectors-statemachine-1.3.4-RC1.js @@ -0,0 +1,442 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the state machine connectors. + * + * Thanks to Brainstorm Mobile Solutions for supporting the development of these. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + var Line = function(x1, y1, x2, y2) { + + this.m = (y2 - y1) / (x2 - x1); + this.b = -1 * ((this.m * x1) - y1); + + this.rectIntersect = function(x,y,w,h) { + var results = []; + + // try top face + // the equation of the top face is y = (0 * x) + b; y = b. + var xInt = (y - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try right face + var yInt = (this.m * (x + w)) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + // bottom face + var xInt = ((y + h) - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try left face + var yInt = (this.m * x) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + if (results.length == 2) { + var midx = (results[0][0] + results[1][0]) / 2, midy = (results[0][1] + results[1][1]) / 2; + results.push([ midx,midy ]); + // now calculate the segment inside the rectangle where the midpoint lies. + var xseg = midx <= x + (w / 2) ? -1 : 1, + yseg = midy <= y + (h / 2) ? -1 : 1; + results.push([xseg, yseg]); + return results; + } + + return null; + + }; + }, + _segment = function(x1, y1, x2, y2) { + if (x1 <= x2 && y2 <= y1) return 1; + else if (x1 <= x2 && y1 <= y2) return 2; + else if (x2 <= x1 && y2 >= y1) return 3; + return 4; + }, + + // the control point we will use depends on the faces to which each end of the connection is assigned, specifically whether or not the + // two faces are parallel or perpendicular. if they are parallel then the control point lies on the midpoint of the axis in which they + // are parellel and varies only in the other axis; this variation is proportional to the distance that the anchor points lie from the + // center of that face. if the two faces are perpendicular then the control point is at some distance from both the midpoints; the amount and + // direction are dependent on the orientation of the two elements. 'seg', passed in to this method, tells you which segment the target element + // lies in with respect to the source: 1 is top right, 2 is bottom right, 3 is bottom left, 4 is top left. + // + // sourcePos and targetPos are arrays of info about where on the source and target each anchor is located. their contents are: + // + // 0 - absolute x + // 1 - absolute y + // 2 - proportional x in element (0 is left edge, 1 is right edge) + // 3 - proportional y in element (0 is top edge, 1 is bottom edge) + // + _findControlPoint = function(midx, midy, segment, sourceEdge, targetEdge, dx, dy, distance, proximityLimit) { + + // TODO (maybe) + // - if anchor pos is 0.5, make the control point take into account the relative position of the elements. + if (distance <= proximityLimit) return [midx, midy]; + + if (segment == 1) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 2) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 3) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 4) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + }; + + /* + Function: StateMachine constructor + + Allowed parameters: + curviness - measure of how "curvy" the connectors will be. this is translated as the distance that the + Bezier curve's control point is from the midpoint of the straight line connecting the two + endpoints, and does not mean that the connector is this wide. The Bezier curve never reaches + its control points; they act as gravitational masses. defaults to 10. + margin - distance from element to start and end connectors, in pixels. defaults to 5. + proximityLimit - sets the distance beneath which the elements are consider too close together to bother with fancy + curves. by default this is 80 pixels. + loopbackRadius - the radius of a loopback connector. optional; defaults to 25. + */ + jsPlumb.Connectors.StateMachine = function(params) { + var self = this, + currentPoints = null, + _sx, _sy, _tx, _ty, _controlPoint = [], + curviness = params.curviness || 10, + margin = params.margin || 5, + proximityLimit = params.proximityLimit || 80, + clockwise = params.orientation && params.orientation == "clockwise", + loopbackRadius = params.loopbackRadius || 25, + isLoopback = false; + + this.type = "StateMachine"; + params = params || {}; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *= 1.9; + //ensure at least one pixel width + lineWidth = lineWidth || 1; + var x = Math.min(sourcePos[0], targetPos[0]) - xo, + y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (sourceEndpoint.elementId != targetEndpoint.elementId) { + + isLoopback = false; + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + + // now adjust for the margin + if (sourcePos[2] == 0) _sx -= margin; + if (sourcePos[2] == 1) _sx += margin; + if (sourcePos[3] == 0) _sy -= margin; + if (sourcePos[3] == 1) _sy += margin; + if (targetPos[2] == 0) _tx -= margin; + if (targetPos[2] == 1) _tx += margin; + if (targetPos[3] == 0) _ty -= margin; + if (targetPos[3] == 1) _ty += margin; + + // + // these connectors are quadratic bezier curves, having a single control point. if both anchors + // are located at 0.5 on their respective faces, the control point is set to the midpoint and you + // get a straight line. this is also the case if the two anchors are within 'proximityLimit', since + // it seems to make good aesthetic sense to do that. outside of that, the control point is positioned + // at 'curviness' pixels away along the normal to the straight line connecting the two anchors. + // + // there may be two improvements to this. firstly, we might actually support the notion of avoiding nodes + // in the UI, or at least making a good effort at doing so. if a connection would pass underneath some node, + // for example, we might increase the distance the control point is away from the midpoint in a bid to + // steer it around that node. this will work within limits, but i think those limits would also be the likely + // limits for, once again, aesthetic good sense in the layout of a chart using these connectors. + // + // the second possible change is actually two possible changes: firstly, it is possible we should gradually + // decrease the 'curviness' as the distance between the anchors decreases; start tailing it off to 0 at some + // point (which should be configurable). secondly, we might slightly increase the 'curviness' for connectors + // with respect to how far their anchor is from the center of its respective face. this could either look cool, + // or stupid, and may indeed work only in a way that is so subtle as to have been a waste of time. + // + + var _midx = (_sx + _tx) / 2, _midy = (_sy + _ty) / 2, + m2 = (-1 * _midx) / _midy, theta2 = Math.atan(m2), + dy = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.sin(theta2)), + dx = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.cos(theta2)), + segment = _segment(_sx, _sy, _tx, _ty), + distance = Math.sqrt(Math.pow(_tx - _sx, 2) + Math.pow(_ty - _sy, 2)); + + // calculate the control point. this code will be where we'll put in a rudimentary element avoidance scheme; it + // will work by extending the control point to force the curve to be, um, curvier. + _controlPoint = _findControlPoint(_midx, + _midy, + segment, + sourcePos, + targetPos, + curviness, curviness, + distance, + proximityLimit); + + + var requiredWidth = Math.max(Math.abs(_controlPoint[0] - _sx) * 3, Math.abs(_controlPoint[0] - _tx) * 3, Math.abs(_tx-_sx), 2 * lineWidth, minWidth), + requiredHeight = Math.max(Math.abs(_controlPoint[1] - _sy) * 3, Math.abs(_controlPoint[1] - _ty) * 3, Math.abs(_ty-_sy), 2 * lineWidth, minWidth); + + if (w < requiredWidth) { + var dw = requiredWidth - w; + x -= (dw / 2); + _sx += (dw / 2); + _tx += (dw / 2); + w = requiredWidth; + _controlPoint[0] += (dw / 2); + } + + if (h < requiredHeight) { + var dh = requiredHeight - h; + y -= (dh / 2); + _sy += (dh / 2); + _ty += (dh / 2); + h = requiredHeight; + _controlPoint[1] += (dh / 2); + } + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty, _controlPoint[0], _controlPoint[1] ]; + } + else { + isLoopback = true; + // a loopback connector. draw an arc from one anchor to the other. + // i guess we'll do this the same way as the others. just the control point will be a fair distance away. + var x1 = sourcePos[0], x2 = sourcePos[0], y1 = sourcePos[1] - margin, y2 = sourcePos[1] - margin, + cx = x1, cy = y1 - loopbackRadius; + + // canvas sizing stuff, to ensure the whole painted area is visible. + w = ((2 * lineWidth) + (4 * loopbackRadius)), h = ((2 * lineWidth) + (4 * loopbackRadius)); + x = cx - loopbackRadius - lineWidth - loopbackRadius, y = cy - loopbackRadius - lineWidth - loopbackRadius; + currentPoints = [ x, y, w, h, cx-x, cy-y, loopbackRadius, clockwise, x1-x, y1-y, x2-x, y2-y]; + } + + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_tx, y:_ty }, + { x:_controlPoint[0], y:_controlPoint[1] }, + { x:_controlPoint[0] + 1, y:_controlPoint[1] + 1}, + { x:_sx, y:_sy } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + if (isLoopback) { + + if (location > 0 && location < 1) location = 1- location; + +// current points are [ x, y, width, height, center x, center y, radius, clockwise, startx, starty, endx, endy ] + // so the path length is the circumference of the circle + //var len = 2 * Math.PI * currentPoints[6], + // map 'location' to an angle. 0 is PI/2 when the connector is on the top face; if we + // support other faces it will have to be calculated for each one. 1 is also PI/2. + // 0.5 is -PI/2. + var startAngle = (location * 2 * Math.PI) + (Math.PI / 2), + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + + } + else return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + if (isLoopback) + return Math.atan(location * 2 * Math.PI); + else + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + if (isLoopback) { + + if (location > 0 && location < 1) location = 1- location; + + var circumference = 2 * Math.PI * currentPoints[6], + arcSpan = distance / circumference * 2 * Math.PI, + startAngle = (location * 2 * Math.PI) - arcSpan + (Math.PI / 2), + + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + } + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + }; + + /* + * Canvas state machine renderer. + */ + jsPlumb.Connectors.canvas.StateMachine = function(params) { + params = params || {}; + var self = this, drawGuideline = params.drawGuideline || true, avoidSelector = params.avoidSelector; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.CanvasConnector.apply(this, arguments); + + + this._paint = function(dimensions) { + + if (dimensions.length == 10) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.quadraticCurveTo(dimensions[8], dimensions[9], dimensions[6], dimensions[7]); + self.ctx.stroke(); + + /*/ draw the guideline + if (drawGuideline) { + self.ctx.save(); + self.ctx.beginPath(); + self.ctx.strokeStyle = "silver"; + self.ctx.lineWidth = 1; + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + self.ctx.restore(); + } + //*/ + } + else { + // a loopback connector + self.ctx.save(); + self.ctx.beginPath(); + var startAngle = 0, // Starting point on circle + endAngle = 2 * Math.PI, // End point on circle + clockwise = dimensions[7]; // clockwise or anticlockwise + self.ctx.arc(dimensions[4],dimensions[5],dimensions[6],0, endAngle, clockwise); + self.ctx.stroke(); + self.ctx.closePath(); + self.ctx.restore(); + } + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + /* + * SVG State Machine renderer + */ + jsPlumb.Connectors.svg.StateMachine = function() { + var self = this; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.SvgConnector.apply(this, arguments); + this.getPath = function(d) { + + if (d.length == 10) + return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[8] + " " + d[9] + " " + d[6] + " " + d[7]; + else { + // loopback + return "M" + (d[8] + 4) + " " + d[9] + " A " + d[6] + " " + d[6] + " 0 1,0 " + (d[8]-4) + " " + d[9]; + } + }; + }; + + /* + * VML state machine renderer + */ + jsPlumb.Connectors.vml.StateMachine = function() { + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.VmlConnector.apply(this, arguments); + var _conv = jsPlumb.vml.convertValue; + this.getPath = function(d) { + if (d.length == 10) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + } + else { + // loopback + var left = _conv(d[8] - d[6]), + top = _conv(d[9] - (2 * d[6])), + right = left + _conv(2 * d[6]), + bottom = top + _conv(2 * d[6]), + posString = left + "," + top + "," + right + "," + bottom; + + var o = "ar " + posString + "," + _conv(d[8]) + "," + + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + " e"; + + return o; + } + }; + }; + +})(); + +/* + // now for a rudimentary avoidance scheme. TODO: how to set this in a cross-library way? + // if (avoidSelector) { + // var testLine = new Line(sourcePos[0] + _sx,sourcePos[1] + _sy,sourcePos[0] + _tx,sourcePos[1] + _ty); + // var sel = jsPlumb.getSelector(avoidSelector); + // for (var i = 0; i < sel.length; i++) { + // var id = jsPlumb.getId(sel[i]); + // if (id != sourceEndpoint.elementId && id != targetEndpoint.elementId) { + // o = jsPlumb.getOffset(id), s = jsPlumb.getSize(id); +// +// if (o && s) { +// var collision = testLine.rectIntersect(o.left,o.top,s[0],s[1]); +// if (collision) { + // set the control point to be a certain distance from the midpoint of the two points that + // the line crosses on the rectangle. + // TODO where will this 75 number come from? + // _controlX = collision[2][0] + (75 * collision[3][0]); + // / _controlY = collision[2][1] + (75 * collision[3][1]); +// } +// } + // } + // } + //} + */ \ No newline at end of file diff --git a/archive/1.3.4/jsPlumb-defaults-1.3.4-RC1.js b/archive/1.3.4/jsPlumb-defaults-1.3.4-RC1.js new file mode 100644 index 000000000..3b68d3450 --- /dev/null +++ b/archive/1.3.4/jsPlumb-defaults-1.3.4-RC1.js @@ -0,0 +1,984 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the default Connectors, Endpoint and Overlay definitions. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +(function() { + + /** + * + * Helper class to consume unused mouse events by components that are DOM elements and + * are used by all of the different rendering modes. + * + */ + jsPlumb.DOMElementComponent = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + // when render mode is canvas, these functions may be called by the canvas mouse handler. + // this component is safe to pipe this stuff to /dev/null. + this.mousemove = + this.dblclick = + this.click = + this.mousedown = + this.mouseup = function(e) { }; + }; + + /** + * Class: Connectors.Straight + * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters. + */ + jsPlumb.Connectors.Straight = function() { + this.type = "Straight"; + var self = this, + currentPoints = null, + _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty, _segment, _length; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + _dx = _tx - _sx, _dy = _ty - _sy; + //_m = _dy / _dx, _m2 = -1 / _m; + _m = jsPlumb.util.gradient({x:_sx, y:_sy}, {x:_tx, y:_ty}), _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + //_segment = jsPlumb.util.segment({x:_sx, y:_sy}, {x:_tx, y:_ty}); + _length = Math.sqrt((_dx * _dx) + (_dy * _dy)); + + return currentPoints; + }; + + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + if (location == 0) + return { x:_sx, y:_sy }; + else if (location == 1) + return { x:_tx, y:_ty }; + else + return jsPlumb.util.pointOnLine({x:_sx, y:_sy}, {x:_tx, y:_ty}, location * _length); + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { + return _m; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + * this hands off to jsPlumb.util to do the maths, supplying our gradient and position and whether or + * not the coords should be flipped + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + return jsPlumb.util.pointOnLine(p, {x:_tx,y:_ty}, distance); + }; + }; + + + /** + * Class:Connectors.Bezier + * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's + * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below. + */ + /** + * Function:Constructor + * + * Parameters: + * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line. + * Optional; defaults to 150. + * + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + params = params || {}; + this.majorAnchor = params.curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + this.type = "Bezier"; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(sourceEndpoint), + too = targetAnchor.getOrientation(targetEndpoint), + perpendicular = soo[0] != too[0] || soo[1] == too[1], + p = [], + ma = self.majorAnchor, mi = self.minorAnchor; + + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + +// this.isFlipX = function() { return + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h; + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) + { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, sourceEndpoint, targetEndpoint, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx), minx2 = Math.min(_CP[0], _CP2[0]), minx = Math.min(minx1,minx2), + maxx1 = Math.max(_sx,_tx), maxx2 = Math.max(_CP[0], _CP2[0]), maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty), miny2 = Math.min(_CP[1], _CP2[1]), miny = Math.min(miny1,miny2), + maxy1 = Math.max(_sy,_ty), maxy2 = Math.max(_CP[1], _CP2[1]), maxy = Math.max(maxy1,maxy2); + + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + }; + + + /** + * Class: Connectors.Flowchart + * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments. + */ + /** + * Function: Constructor + * + * Parameters: + * stub - minimum length for the stub at each end of the connector. defaults to 30 pixels. + */ + jsPlumb.Connectors.Flowchart = function(params) { + this.type = "Flowchart"; + params = params || {}; + var self = this, + minStubLength = params.stub || params.minStubLength /* bwds compat. */ || 30, + segments = [], + totalLength = 0, + segmentProportions = [], + segmentProportionalLengths = [], + points = [], + swapX, + swapY, + /** + * recalculates the points at which the segments begin and end, proportional to the total length travelled + * by all the segments that constitute the connector. we use this to help with pointOnPath calculations. + */ + updateSegmentProportions = function(startX, startY, endX, endY) { + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segments[i][5] / totalLength; + segmentProportions[i] = [curLoc, (curLoc += (segments[i][5] / totalLength)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0], + ly = segments.length == 0 ? sy : segments[segments.length - 1][1], + m = x == lx ? Infinity : 0, + l = Math.abs(x == lx ? y - ly : x - lx); + segments.push([x, y, lx, ly, m, l]); + totalLength += l; + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment + * that contains the point which is 'location' distance along the entire path, where + * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths + * are made up of a list of segments, each of which contributes some fraction to + * the total length. + */ + findSegmentForLocation = function(location) { + var idx = segmentProportions.length - 1, inSegmentProportion = 0; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + segments = []; + totalLength = 0; + segmentProportionalLengths = []; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + offx = (lw / 2) + (minStubLength * 2), + offy = (lw / 2) + (minStubLength * 2), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(sourceEndpoint), + to = targetAnchor.orientation || targetAnchor.getOrientation(targetEndpoint), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx, + h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy; + if (w < minWidth) { + offx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + offy += (minWidth - h) / 2; + h = minWidth; + } + var sx = swapX ? w-offx : offx, + sy = swapY ? h-offy : offy, + tx = swapX ? offx : w-offx , + ty = swapY ? offy : h-offy, + startStubX = sx + (so[0] * minStubLength), + startStubY = sy + (so[1] * minStubLength), + endStubX = tx + (to[0] * minStubLength), + endStubY = ty + (to[1] * minStubLength), + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2); + + x -= offx; y -= offy; + points = [x, y, w, h, sx, sy, tx, ty]; + var extraPoints = []; + + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + if (so[0] == 0) { + var startStubIsBeforeEndStub = startStubY < endStubY; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } + } + else { + var startStubIsBeforeEndStub = startStubX < endStubX; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } + } + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentProportions(sx, sy, tx, ty); + + return points; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location) { + return self.pointAlongPathFrom(location, 0); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location) { + return segments[findSegmentForLocation(location)["index"]][4]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance) { + var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segments[s.index][5], m = segments[s.index][4]; + var e = { + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + return e; + }; + }; + + // ********************************* END OF CONNECTOR TYPES ******************************************************************* + + // ********************************* ENDPOINT TYPES ******************************************************************* + + /** + * Class: Endpoints.Dot + * A round endpoint, with default radius 10 pixels. + */ + + /** + * Function: Constructor + * + * Parameters: + * + * radius - radius of the endpoint. defaults to 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + this.type = "Dot"; + var self = this; + params = params || {}; + this.radius = params.radius || 10; + this.defaultOffset = 0.5 * this.radius; + this.defaultInnerRadius = this.radius / 3; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var r = endpointStyle.radius || self.radius, + x = anchorPoint[0] - r, + y = anchorPoint[1] - r; + return [ x, y, r * 2, r * 2, r ]; + }; + }; + + /** + * Class: Endpoints.Rectangle + * A Rectangular Endpoint, with default size 20x20. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the endpoint. defaults to 20 pixels. + * height - height of the endpoint. defaults to 20 pixels. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + this.type = "Rectangle"; + var self = this; + params = params || {}; + this.width = params.width || 20; + this.height = params.height || 20; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height]; + }; + }; + + /** + * Class: Endpoints.Image + * Draws an image as the Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * src - location of the image to use. + */ + jsPlumb.Endpoints.Image = function(params) { + + this.type = "Image"; + jsPlumb.DOMElementComponent.apply(this, arguments); + + var self = this, + initialized = false, + widthToUse = params.width, + heightToUse = params.height, + _onload = null, + _endpoint = params.endpoint; + + this.img = new Image(); + self.ready = false; + + this.img.onload = function() { + self.ready = true; + widthToUse = widthToUse || self.img.width; + heightToUse = heightToUse || self.img.height; + if (_onload) { + _onload(this); + } + }; + + _endpoint.setImage = function(img, onload) { + var s = img.constructor == String ? img : img.src; + _onload = onload; + self.img.src = img; + }; + + _endpoint.setImage(params.src || params.url, params.onload); + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + self.anchorPoint = anchorPoint; + if (self.ready) return [anchorPoint[0] - widthToUse / 2, anchorPoint[1] - heightToUse / 2, + widthToUse, heightToUse]; + else return [0,0,0,0]; + }; + + self.canvas = document.createElement("img"), initialized = false; + self.canvas.style["margin"] = 0; + self.canvas.style["padding"] = 0; + self.canvas.style["outline"] = 0; + self.canvas.style["position"] = "absolute"; + var clazz = params.cssClass ? " " + params.cssClass : ""; + self.canvas.className = jsPlumb.endpointClass + clazz; + if (widthToUse) self.canvas.setAttribute("width", widthToUse); + if (heightToUse) self.canvas.setAttribute("height", heightToUse); + jsPlumb.appendElement(self.canvas, params.parent); + self.attachListeners(self.canvas, self); + + var actuallyPaint = function(d, style, anchor) { + if (!initialized) { + self.canvas.setAttribute("src", self.img.src); + initialized = true; + } + var x = self.anchorPoint[0] - (widthToUse / 2), + y = self.anchorPoint[1] - (heightToUse / 2); + jsPlumb.sizeCanvas(self.canvas, x, y, widthToUse, heightToUse); + }; + + this.paint = function(d, style, anchor) { + if (self.ready) { + actuallyPaint(d, style, anchor); + } + else { + window.setTimeout(function() { + self.paint(d, style, anchor); + }, 200); + } + }; + }; + + /** + * Class: Endpoints.Blank + * An Endpoint that paints nothing (visible) on the screen. Supports cssClass and hoverClass parameters like all Endpoints. + */ + jsPlumb.Endpoints.Blank = function(params) { + var self = this; + this.type = "Blank"; + jsPlumb.DOMElementComponent.apply(this, arguments); + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + return [anchorPoint[0], anchorPoint[1],10,0]; + }; + + self.canvas = document.createElement("div"); + self.canvas.style.display = "block"; + self.canvas.style.width = "1px"; + self.canvas.style.height = "1px"; + self.canvas.style.background = "transparent"; + self.canvas.style.position = "absolute"; + self.canvas.className = self._jsPlumb.endpointClass; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + }; + }; + + /** + * Class: Endpoints.Triangle + * A triangular Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the triangle's base. defaults to 55 pixels. + * height - height of the triangle from base to apex. defaults to 55 pixels. + */ + jsPlumb.Endpoints.Triangle = function(params) { + this.type = "Triangle"; + params = params || { }; + params.width = params.width || 55; + params.height = params.height || 55; + this.width = params.width; + this.height = params.height; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height ]; + }; + }; +// ********************************* END OF ENDPOINT TYPES ******************************************************************* + + +// ********************************* OVERLAY DEFINITIONS *********************************************************************** + + var AbstractOverlay = function(params) { + var visible = true, self = this; + this.isAppendedAtTopLevel = true; + this.connection = params.connection; + this.loc = params.location == null ? 0.5 : params.location; + this.setVisible = function(val) { + visible = val; + self.connection.repaint(); + }; + this.isVisible = function() { return visible; }; + this.hide = function() { self.setVisible(false); }; + this.show = function() { self.setVisible(true); }; + + this.incrementLocation = function(amount) { + self.loc += amount; + self.connection.repaint(); + }; + this.setLocation = function(l) { + self.loc = l; + self.connection.repaint(); + }; + this.getLocation = function() { + return self.loc; + }; + }; + + + /** + * Class: Overlays.Arrow + * + * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length + * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction + * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line + * across the tail. + */ + /** + * Function: Constructor + * + * Parameters: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + this.type = "Arrow"; + AbstractOverlay.apply(this, arguments); + this.isAppendedAtTopLevel = false; + params = params || {}; + var self = this; + + this.length = params.length || 20; + this.width = params.width || 20; + this.id = params.id; + var direction = (params.direction || 1) < 0 ? -1 : 1, + paintStyle = params.paintStyle || { lineWidth:1 }, + // how far along the arrow the lines folding back in come to. default is 62.3%. + foldback = params.foldback || 0.623; + + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.cleanup = function() { }; // nothing to clean up for Arrows + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var hxy, mid, txy, tail, cxy; + + if (self.loc == 1) { + hxy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, -1); + txy = jsPlumb.util.pointOnLine(hxy, mid, self.length); + } + else if (self.loc == 0) { + txy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, 1); + hxy = jsPlumb.util.pointOnLine(txy, mid, self.length); + } + else { + hxy = connector.pointAlongPathFrom(self.loc, direction * self.length / 2), + mid = connector.pointOnPath(self.loc), + txy = jsPlumb.util.pointOnLine(hxy, mid, self.length); + } + + tail = jsPlumb.util.perpendicularLineTo(hxy, txy, self.width); + cxy = jsPlumb.util.pointOnLine(hxy, txy, foldback * self.length); + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x), + maxx = Math.max(hxy.x, tail[0].x, tail[1].x), + miny = Math.min(hxy.y, tail[0].y, tail[1].y), + maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + var d = { hxy:hxy, tail:tail, cxy:cxy }, + strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle, + fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle, + lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth; + + self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions); + + return [ minx, maxx, miny, maxy]; + }; + }; + + /** + * Class: Overlays.PlainArrow + * + * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "PlainArrow"; + }; + + /** + * Class: Overlays.Diamond + * + * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40, + p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "Diamond"; + }; + + + + /** + * Class: Overlays.Label + * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb + * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter + * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it + * puts on the Label's 'style' attribute, so the end result is the same. + */ + /** + * Function: Constructor + * + * Parameters: + * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes + * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle. + * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * + */ + jsPlumb.Overlays.Label = function(params) { + this.type = "Label"; + jsPlumb.DOMElementComponent.apply(this, arguments); + AbstractOverlay.apply(this, arguments); + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.id = params.id; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var label = params.label || "", + self = this, + initialised = false, + div = document.createElement("div"), + labelText = null; + div.style["position"] = "absolute"; + + var clazz = params["_jsPlumb"].overlayClass + " " + + (self.labelStyle.cssClass ? self.labelStyle.cssClass : + params.cssClass ? params.cssClass : ""); + + div.className = clazz; + + jsPlumb.appendElement(div, params.connection.parent); + jsPlumb.getId(div); + self.attachListeners(div, self); + self.canvas = div; + + //override setVisible + var osv = self.setVisible; + self.setVisible = function(state) { + osv(state); // call superclass + div.style.display = state ? "block" : "none"; + }; + + this.getElement = function() { + return div; + }; + + this.cleanup = function() { + if (div != null) jsPlumb.CurrentLibrary.removeElement(div); + }; + + /* + * Function: setLabel + * sets the label's, um, label. you would think i'd call this function + * 'setText', but you can pass either a Function or a String to this, so + * it makes more sense as 'setLabel'. + */ + this.setLabel = function(l) { + label = l; + labelText = null; + self.connection.repaint(); + }; + + this.getLabel = function() { + return label; + }; + + this.paint = function(connector, d, connectorDimensions) { + if (!initialised) { + connector.appendDisplayElement(div); + self.attachListeners(div, connector); + initialised = true; + } + div.style.left = (connectorDimensions[0] + d.minx) + "px"; + div.style.top = (connectorDimensions[1] + d.miny) + "px"; + }; + + this.getTextDimensions = function(connector) { + if (typeof label == "function") { + var lt = label(self); + div.innerHTML = lt.replace(/\r\n/g, "
"); + } + else { + if (labelText == null) { + labelText = label; + div.innerHTML = labelText.replace(/\r\n/g, "
"); + } + } + var de = jsPlumb.CurrentLibrary.getElementObject(div), + s = jsPlumb.CurrentLibrary.getSize(de); + return {width:s[0], height:s[1]}; + }; + + this.computeMaxSize = function(connector) { + var td = self.getTextDimensions(connector); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + var td = self.getTextDimensions(connector); + if (td.width != null) { + var cxy = connector.pointOnPath(self.loc), + minx = cxy.x - (td.width / 2), + miny = cxy.y - (td.height / 2); + + self.paint(connector, { + minx:minx, + miny:miny, + td:td, + cxy:cxy + }, connectorDimensions); + + return [minx, minx+td.width, miny, miny+td.height]; + } + else return [0,0,0,0]; + }; + + this.reattachListeners = function() { + if (div) self.reattachListenersForElement(div, self); + }; + }; + + // this is really just a test overlay, so its undocumented and doesnt take any parameters. but i was loth to delete it. + jsPlumb.Overlays.GuideLines = function() { + var self = this; + self.length = 50; + self.lineWidth = 5; + this.type = "GuideLines"; + AbstractOverlay.apply(this, arguments); + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var head = connector.pointAlongPathFrom(self.loc, self.length / 2), + mid = connector.pointOnPath(self.loc), + tail = jsPlumb.util.pointOnLine(head, mid, self.length), + tailLine = jsPlumb.util.perpendicularLineTo(head, tail, 40), + headLine = jsPlumb.util.perpendicularLineTo(tail, head, 20); + + self.paint(connector, [head, tail, tailLine, headLine], self.lineWidth, "red", null, connectorDimensions); + + return [Math.min(head.x, tail.x), Math.min(head.y, tail.y), Math.max(head.x, tail.x), Math.max(head.y,tail.y)]; + }; + + this.computeMaxSize = function() { return 50; }; + + this.cleanup = function() { }; // nothing to clean up for GuideLines + }; + + // ********************************* END OF OVERLAY DEFINITIONS *********************************************************************** + + // ********************************* OVERLAY CANVAS RENDERERS*********************************************************************** + + // ********************************* END OF OVERLAY CANVAS RENDERERS *********************************************************************** +})(); \ No newline at end of file diff --git a/archive/1.3.4/jsPlumb-renderers-canvas-1.3.4-RC1.js b/archive/1.3.4/jsPlumb-renderers-canvas-1.3.4-RC1.js new file mode 100644 index 000000000..69a588aeb --- /dev/null +++ b/archive/1.3.4/jsPlumb-renderers-canvas-1.3.4-RC1.js @@ -0,0 +1,444 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the HTML5 canvas renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + +// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS ******************************************************************* + + // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. + var _connectionBeingDragged = null, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, + _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }; + + /* + * Class:CanvasMouseAdapter + * Provides support for mouse events on canvases. + */ + var CanvasMouseAdapter = function() { + var self = this; + self.overlayPlacements = []; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + jsPlumb.EventGenerator.apply(this, arguments); + /** + * returns whether or not the given event is ojver a painted area of the canvas. + */ + this._over = function(e) { + var o = _getOffset(_getElementObject(self.canvas)), + pageXY = _pageXY(e), + x = pageXY[0] - o.left, y = pageXY[1] - o.top; + if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + return false; + }; + + var _mouseover = false, _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false, + _nullSafeHasClass = function(el, clazz) { + return el != null && _hasClass(el, clazz); + }; + this.mousemove = function(e) { + var pageXY = _pageXY(e), clientXY = _clientXY(e), + ee = document.elementFromPoint(clientXY[0], clientXY[1]), + eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); + var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); + if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.fire("mouseenter", self, e); + return true; + } + // TODO here there is a remote chance that the overlay the mouse moved onto + // is actually not an overlay for the current component. a more thorough check would + // be to ensure the overlay belonged to the current component. + else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { + _mouseover = false; + self.fire("mouseexit", self, e); + } + self.fire("mousemove", self, e); + }; + + this.click = function(e) { + // to filter right click in FF, i could compare e.button. 0 means left mouse button; 1 middle, 2 right. + //console.log(e.button); + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); + self.fire("mousedown", self, e); + } + }; + + this.mouseup = function(e) { + _mouseDown = false; + self.fire("mouseup", self, e); + }; + }; + + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + jsPlumb.appendElement(canvas, params.parent); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + params["_jsPlumb"].getId(canvas, params.uuid); + if (params.tooltip) canvas.setAttribute("label", params.tooltip); + + return canvas; + }; + + /** + * Class:CanvasConnector + * Superclass for Canvas Connector renderers. + */ + var CanvasConnector = jsPlumb.CanvasConnector = function(params) { + + CanvasMouseAdapter.apply(this, arguments); + + var _paintOneStyle = function(dim, aStyle) { + self.ctx.save(); + jsPlumb.extend(self.ctx, aStyle); + if (aStyle.gradient) { + var g = self.createGradient(dim, self.ctx); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + self.ctx.strokeStyle = g; + } + self._paint(dim); + self.ctx.restore(); + }; + + var self = this, + clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:params.tooltip + }); + self.ctx = self.canvas.getContext("2d"); + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { return displayElements; }; + this.appendDisplayElement = function(el) { displayElements.push(el); }; + + self.paint = function(dim, style) { + if (style != null) { + jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + _paintOneStyle(dim, outlineStyle); + } + _paintOneStyle(dim, style); + } + }; + }; + + /** + * Class:CanvasEndpoint + * Superclass for Canvas Endpoint renderers. + */ + var CanvasEndpoint = function(params) { + var self = this; + CanvasMouseAdapter.apply(this, arguments); + var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""), + canvasParams = { + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:self.tooltip + }; + self.canvas = _newCanvas(canvasParams); + self.ctx = self.canvas.getContext("2d"); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + } + + self._paint.apply(this, arguments); + }; + }; + + jsPlumb.Endpoints.canvas.Dot = function(params) { + jsPlumb.Endpoints.Dot.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + var self = this, + parseValue = function(value) { + try { return parseInt(value); } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }, + calculateAdjustments = function(gradient) { + var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius; + gradient.offset && (offsetAdjustment = parseValue(gradient.offset)); + gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius)); + return [offsetAdjustment, innerRadius]; + }; + this._paint = function(d, style, anchor) { + if (style != null) { + var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + if (style.gradient) { + var adjustments = calculateAdjustments(style.gradient), + yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0], + xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0], + g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + ctx.beginPath(); + ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + } + }; + }; + + jsPlumb.Endpoints.canvas.Rectangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) { + + var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + + /* canvas gradient */ + if (style.gradient) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, d[2], d[3]); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + jsPlumb.Endpoints.canvas.Triangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Triangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) + { + var width = d[2], height = d[3], x = d[0], y = d[1], + ctx = self.canvas.getContext('2d'), + offsetX = 0, offsetY = 0, angle = 0, + orientation = anchor.getOrientation(self); + + if( orientation[0] == 1 ) { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = style.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + /* + * Canvas Image Endpoint: uses the default version, which creates an tag. + */ + jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image; + + /* + * Blank endpoint in all renderers is just the default Blank endpoint. + */ + jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank; + + /* + * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Bezier = function() { + var self = this; + jsPlumb.Connectors.Bezier.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO i doubt this handles the case that source and target are swapped. + this.createGradient = function(dim, ctx, swap) { + return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + /* + * Canvas straight line Connector. Draws a straight line onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Straight = function() { + var self = this; + jsPlumb.Connectors.Straight.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO this does not handle the case that src and target are swapped. + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + jsPlumb.Connectors.canvas.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + +// ********************************* END OF CANVAS RENDERERS ******************************************************************* + + jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label; + + /** + * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. + */ + var CanvasOverlay = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }; + + var AbstractCanvasArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + CanvasOverlay.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + var ctx = connector.ctx; + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(d.hxy.x, d.hxy.y); + ctx.lineTo(d.tail[0].x, d.tail[0].y); + ctx.lineTo(d.cxy.x, d.cxy.y); + ctx.lineTo(d.tail[1].x, d.tail[1].y); + ctx.lineTo(d.hxy.x, d.hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + }; + + jsPlumb.Overlays.canvas.Arrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.canvas.PlainArrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.canvas.Diamond = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.4/jsPlumb-renderers-svg-1.3.4-RC1.js b/archive/1.3.4/jsPlumb-renderers-svg-1.3.4-RC1.js new file mode 100644 index 000000000..8f9c7c483 --- /dev/null +++ b/archive/1.3.4/jsPlumb-renderers-svg-1.3.4-RC1.js @@ -0,0 +1,528 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the SVG renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +/** + * SVG support for jsPlumb. + * + * things to investigate: + * + * gradients: https://developer.mozilla.org/en/svg_in_html_introduction + * css:http://tutorials.jenkov.com/svg/svg-and-css.html + * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath + * pointer events: https://developer.mozilla.org/en/css/pointer-events + * + * IE9 hover jquery: http://forum.jquery.com/topic/1-6-2-broke-svg-hover-events + * + */ +;(function() { + + var svgAttributeMap = { + "joinstyle":"stroke-linejoin", + "stroke-linejoin":"stroke-linejoin", + "stroke-dashoffset":"stroke-dashoffset", + "stroke-linecap":"stroke-linecap" + }, + STROKE_DASHARRAY = "stroke-dasharray", + DASHSTYLE = "dashstyle", + LINEAR_GRADIENT = "linearGradient", + RADIAL_GRADIENT = "radialGradient", + FILL = "fill", + STOP = "stop", + STROKE = "stroke", + STROKE_WIDTH = "stroke-width", + STYLE = "style", + NONE = "none", + JSPLUMB_GRADIENT = "jsplumb_gradient_", + LINE_WIDTH = "lineWidth", + ns = { + svg:"http://www.w3.org/2000/svg", + xhtml:"http://www.w3.org/1999/xhtml" + }, + _attr = function(node, attributes) { + for (var i in attributes) + node.setAttribute(i, "" + attributes[i]); + }, + _node = function(name, attributes) { + var n = document.createElementNS(ns.svg, name); + attributes = attributes || {}; + attributes["version"] = "1.1"; + attributes["xmnls"] = ns.xhtml; + _attr(n, attributes); + return n; + }, + _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; }, + _clearGradient = function(parent) { + for (var i = 0; i < parent.childNodes.length; i++) { + if (parent.childNodes[i].tagName == LINEAR_GRADIENT || parent.childNodes[i].tagName == RADIAL_GRADIENT) + parent.removeChild(parent.childNodes[i]); + } + }, + _updateGradient = function(parent, node, style, dimensions, uiComponent) { + var id = JSPLUMB_GRADIENT + uiComponent._jsPlumb.idstamp(); + // first clear out any existing gradient + _clearGradient(parent); + // this checks for an 'offset' property in the gradient, and in the absence of it, assumes + // we want a linear gradient. if it's there, we create a radial gradient. + // it is possible that a more explicit means of defining the gradient type would be + // better. relying on 'offset' means that we can never have a radial gradient that uses + // some default offset, for instance. + if (!style.gradient.offset) { + var g = _node(LINEAR_GRADIENT, {id:id}); + parent.appendChild(g); + } + else { + var g = _node(RADIAL_GRADIENT, { + id:id + }); + parent.appendChild(g); + } + + // the svg radial gradient seems to treat stops in the reverse + // order to how canvas does it. so we want to keep all the maths the same, but + // iterate the actual style declarations in reverse order, if the x indexes are not in order. + for (var i = 0; i < style.gradient.stops.length; i++) { + // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of + // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments. + // so, not too concerned about leaving this in for now. + var styleToUse = i; + if (dimensions.length == 8) + styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i; + else + styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i; + var stopColor = jsPlumb.util.convertStyle(style.gradient.stops[styleToUse][1], true); + var s = _node(STOP, {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor}); + g.appendChild(s); + } + var applyGradientTo = style.strokeStyle ? STROKE : FILL; + node.setAttribute(STYLE, applyGradientTo + ":url(#" + id + ")"); + }, + _applyStyles = function(parent, node, style, dimensions, uiComponent) { + + if (style.gradient) { + _updateGradient(parent, node, style, dimensions, uiComponent); + } + else { + // make sure we clear any existing gradient + _clearGradient(parent); + node.setAttribute(STYLE, ""); + } + + node.setAttribute(FILL, style.fillStyle ? jsPlumb.util.convertStyle(style.fillStyle, true) : NONE); + node.setAttribute(STROKE, style.strokeStyle ? jsPlumb.util.convertStyle(style.strokeStyle, true) : NONE); + if (style.lineWidth) { + node.setAttribute(STROKE_WIDTH, style.lineWidth); + } + + // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like + // the syntax in VML but is actually kind of nasty: values are given in the pixel + // coordinate space, whereas in VML they are multiples of the width of the stroked + // line, which makes a lot more sense. for that reason, jsPlumb is supporting both + // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from + // VML, which will be the preferred method. the code below this converts a dashstyle + // attribute given in terms of stroke width into a pixel representation, by using the + // stroke's lineWidth. + if (style[DASHSTYLE] && style[LINE_WIDTH] && !style[STROKE_DASHARRAY]) { + var sep = style[DASHSTYLE].indexOf(",") == -1 ? " " : ",", + parts = style[DASHSTYLE].split(sep), + styleToUse = ""; + parts.forEach(function(p) { + styleToUse += (Math.floor(p * style.lineWidth) + sep); + }); + node.setAttribute(STROKE_DASHARRAY, styleToUse); + } + else if(style[STROKE_DASHARRAY]) { + node.setAttribute(STROKE_DASHARRAY, style[STROKE_DASHARRAY]); + } + + // extra attributes such as join type, dash offset. + for (var i in svgAttributeMap) { + if (style[i]) { + node.setAttribute(svgAttributeMap[i], style[i]); + } + } + }, + _decodeFont = function(f) { + var r = /([0-9].)(p[xt])\s(.*)/; + var bits = f.match(r); + return {size:bits[1] + bits[2], font:bits[3]}; + }, + _classManip = function(el, add, clazz) { + var classesToAddOrRemove = clazz.split(" "), + className = el.className, + curClasses = className.baseVal.split(" "); + + for (var i = 0; i < classesToAddOrRemove.length; i++) { + if (add) { + if (curClasses.indexOf(classesToAddOrRemove[i]) == -1) + curClasses.push(classesToAddOrRemove[i]); + } + else { + var idx = curClasses.indexOf(classesToAddOrRemove[i]); + if (idx != -1) + curClasses.splice(idx, 1); + } + } + + el.className.baseVal = curClasses.join(" "); + }, + _addClass = function(el, clazz) { + _classManip(el, true, clazz); + }, + _removeClass = function(el, clazz) { + _classManip(el, false, clazz); + }; + + /** + utility methods for other objects to use. + */ + jsPlumb.util.svg = { + addClass:_addClass, + removeClass:_removeClass + }; + + /* + * Base class for SVG components. + */ + //var SvgComponent = function(cssClass, originalArgs, pointerEventsSpec) { + var SvgComponent = function(params) { + var self = this, + pointerEventsSpec = params.pointerEventsSpec || "all"; + jsPlumb.jsPlumbUIComponent.apply(this, params.originalArgs); + self.canvas = null, self.path = null, self.svg = null; + + var clazz = params.cssClass + " " + (params.originalArgs[0].cssClass || ""), + svgParams = { + "style":"", + "width":0, + "height":0, + "pointer-events":pointerEventsSpec, + "position":"absolute" + }; + if (self.tooltip) svgParams["label"] = self.tooltip; + self.svg = _node("svg", svgParams); + if (params.useDivWrapper) { + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.sizeCanvas(self.canvas,0,0,1,1); + self.canvas.className = clazz; + if (self.tooltip) self.canvas.setAttribute("label", self.tooltip); + } + else { + _attr(self.svg, { "class":clazz }); + self.canvas = self.svg; + } + + jsPlumb.appendElement(self.canvas, params.originalArgs[0]["parent"]); + if (params.useDivWrapper) self.canvas.appendChild(self.svg); + + // TODO this displayElement stuff is common between all components, across all + // renderers. would be best moved to jsPlumbUIComponent. + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + this.paint = function(d, style, anchor) { + if (style != null) { + var x = d[0], y = d[1]; + if (params.useDivWrapper) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + x = 0, y = 0; + } + _attr(self.svg, { + "style":_pos([x, y, d[2], d[3]]), + "width": d[2], + "height": d[3] + }); + self._paint.apply(this, arguments); + } + }; + }; + + /* + * Base class for SVG connectors. + */ + var SvgConnector = jsPlumb.SvgConnector = function(params) { + var self = this; + SvgComponent.apply(this, [ { cssClass:params["_jsPlumb"].connectorClass, originalArgs:arguments, pointerEventsSpec:"none", tooltip:params.tooltip } ]); + this._paint = function(d, style) { + var p = self.getPath(d), a = { "d":p }, outlineStyle = null; + a["pointer-events"] = "all"; + + // outline style. actually means drawing an svg object underneath the main one. + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = jsPlumb.CurrentLibrary.extend({}, style); + outlineStyle.strokeStyle = jsPlumb.util.convertStyle(style.outlineColor); + outlineStyle.lineWidth = outlineStrokeWidth; + + if (self.bgPath == null) { + self.bgPath = _node("path", a); + self.svg.appendChild(self.bgPath); + self.attachListeners(self.bgPath, self); + } + else { + _attr(self.bgPath, a); + } + + _applyStyles(self.svg, self.bgPath, outlineStyle, d, self); + } + + + // test - see below + // a["clip-path"]= "url(#testClip)"; + + if (self.path == null) { + self.path = _node("path", a); + self.svg.appendChild(self.path); + self.attachListeners(self.path, self); + + /* + this is a test of a clip path. i'm looking into using one of these to animate a jsplumb connection. + you could do this by walking along the line, stepping along a little at a time, and setting the clip + path to extend as far as that point. + + self.clip = _node("clipPath", {id:"testClip", clipPathUnits:"objectBoundingBox"}); + self.svg.appendChild(self.clip); + self.clip.appendChild(_node("rect", { + x:"0",y:"0",width:"0.5",height:"1" + })); + */ + } + else { + _attr(self.path, a); + } + + _applyStyles(self.svg, self.path, style, d, self); + }; + + this.reattachListeners = function() { + if (self.bgPath) self.reattachListenersForElement(self.bgPath, self); + if (self.path) self.reattachListenersForElement(self.path, self); + }; + + }; + + /* + * SVG Bezier Connector + */ + jsPlumb.Connectors.svg.Bezier = function(params) { + jsPlumb.Connectors.Bezier.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]; }; + }; + + /* + * SVG straight line Connector + */ + jsPlumb.Connectors.svg.Straight = function(params) { + jsPlumb.Connectors.Straight.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; }; + }; + + jsPlumb.Connectors.svg.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "M " + dimensions[4] + "," + dimensions[5]; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)]; + } + // finally draw a line to the end + p = p + " " + dimensions[6] + "," + dimensions[7]; + return p; + }; + }; + + /* + * Base class for SVG endpoints. + */ + var SvgEndpoint = function(params) { + var self = this; + SvgComponent.apply(this, [ { + cssClass:params["_jsPlumb"].endpointClass, + originalArgs:arguments, + pointerEventsSpec:"all", + useDivWrapper:true + } ]); + this._paint = function(d, style) { + var s = jsPlumb.extend({}, style); + if (s.outlineColor) { + s.strokeWidth = s.outlineWidth; + s.strokeStyle = jsPlumb.util.convertStyle(s.outlineColor, true); + } + + if (self.node == null) { + self.node = self.makeNode(d, s); + self.svg.appendChild(self.node); + self.attachListeners(self.node, self); + } + _applyStyles(self.svg, self.node, s, d, self); + _pos(self.node, d); + }; + + this.reattachListeners = function() { + if (self.node) self.reattachListenersForElement(self.node, self); + }; + }; + + /* + * SVG Dot Endpoint + */ + jsPlumb.Endpoints.svg.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("circle", { + "cx" : d[2] / 2, + "cy" : d[3] / 2, + "r" : d[2] / 2 + }); + }; + }; + + /* + * SVG Rectangle Endpoint + */ + jsPlumb.Endpoints.svg.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("rect", { + "width":d[2], + "height":d[3] + }); + }; + }; + + /* + * SVG Image Endpoint is the default image endpoint. + */ + jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image; + /* + * Blank endpoint in svg renderer is the default Blank endpoint. + */ + jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank; + /* + * Label endpoint in svg renderer is the default Label endpoint. + */ + jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label; + + var AbstractSvgArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + this.isAppendedAtTopLevel = false; + var self = this, path =null; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + } + var clazz = originalArgs && (originalArgs.length == 1) ? (originalArgs[0].cssClass || "") : ""; + + _attr(path, { + "d" : makePath(d), + "class" : clazz, + stroke : strokeStyle ? strokeStyle : null, + fill : fillStyle ? fillStyle : null + }); + }; + var makePath = function(d) { + return "M" + d.hxy.x + "," + d.hxy.y + + " L" + d.tail[0].x + "," + d.tail[0].y + + " L" + d.cxy.x + "," + d.cxy.y + + " L" + d.tail[1].x + "," + d.tail[1].y + + " L" + d.hxy.x + "," + d.hxy.y; + }; + this.reattachListeners = function() { + if (path) self.reattachListenersForElement(path, self); + }; + }; + + jsPlumb.Overlays.svg.Arrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.svg.PlainArrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.svg.Diamond = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; + + // a test + jsPlumb.Overlays.svg.GuideLines = function() { + var path = null, self = this, path2 = null, p1_1, p1_2; + jsPlumb.Overlays.GuideLines.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + + p1_1 = _node("path"); + connector.svg.appendChild(p1_1); + self.attachListeners(p1_1, connector); + self.attachListeners(p1_1, self); + + p1_2 = _node("path"); + connector.svg.appendChild(p1_2); + self.attachListeners(p1_2, connector); + self.attachListeners(p1_2, self); + + } + + _attr(path, { + "d" : makePath(d[0], d[1]), + stroke : "red", + fill : null + }); + + _attr(p1_1, { + "d" : makePath(d[2][0], d[2][1]), + stroke : "blue", + fill : null + }); + + _attr(p1_2, { + "d" : makePath(d[3][0], d[3][1]), + stroke : "green", + fill : null + }); + }; + + var makePath = function(d1, d2) { + return "M " + d1.x + "," + d1.y + + " L" + d2.x + "," + d2.y; + }; + + }; +})(); \ No newline at end of file diff --git a/archive/1.3.4/jsPlumb-renderers-vml-1.3.4-RC1.js b/archive/1.3.4/jsPlumb-renderers-vml-1.3.4-RC1.js new file mode 100644 index 000000000..7c8529690 --- /dev/null +++ b/archive/1.3.4/jsPlumb-renderers-vml-1.3.4-RC1.js @@ -0,0 +1,404 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the VML renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + // http://ajaxian.com/archives/the-vml-changes-in-ie-8 + // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/ + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + + var vmlAttributeMap = { + "stroke-linejoin":"joinstyle", + "joinstyle":"joinstyle", + "endcap":"endcap", + "miterlimit":"miterlimit" + }; + + if (document.createStyleSheet) { + + // this is the style rule for IE7/6: it uses a CSS class, tidy. + document.createStyleSheet().addRule(".jsplumb_vml", "behavior:url(#default#VML);position:absolute;"); + + // these are for VML in IE8. you have to explicitly call out which elements + // you're going to expect to support VML! + // + document.createStyleSheet().addRule("jsplumb\\:textbox", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:oval", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:rect", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:stroke", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:shape", "behavior:url(#default#VML);position:absolute;"); + + // in this page it is also mentioned that IE requires the extra arg to the namespace + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either. + // var iev = document.documentMode; + //if (!iev || iev < 8) + document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); + //else + // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML"); + } + + jsPlumb.vml = {}; + + var scale = 1000, + _atts = function(o, atts) { + for (var i in atts) { + // IE8 fix: setattribute does not work after an element has been added to the dom! + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + //o.setAttribute(i, atts[i]); + + o[i] = atts[i]; + } + }, + _node = function(name, d, atts) { + atts = atts || {}; + var o = document.createElement("jsplumb:" + name); + o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml"; + _pos(o, d); + _atts(o, atts); + return o; + }, + _pos = function(o,d) { + o.style.left = d[0] + "px"; + o.style.top = d[1] + "px"; + o.style.width= d[2] + "px"; + o.style.height= d[3] + "px"; + o.style.position = "absolute"; + }, + _conv = jsPlumb.vml.convertValue = function(v) { + return Math.floor(v * scale); + }, + // tests if the given style is "transparent" and then sets the appropriate opacity node to 0 if so, + // or 1 if not. TODO in the future, support variable opacity. + _maybeSetOpacity = function(styleToWrite, styleToCheck, type, component) { + if ("transparent" === styleToCheck) + component.setOpacity(type, "0.0"); + else + component.setOpacity(type, "1.0"); + }, + _applyStyles = function(node, style, component) { + var styleToWrite = {}; + if (style.strokeStyle) { + styleToWrite["stroked"] = "true"; + var strokeColor = jsPlumb.util.convertStyle(style.strokeStyle, true); + styleToWrite["strokecolor"] = strokeColor; + _maybeSetOpacity(styleToWrite, strokeColor, "stroke", component); + styleToWrite["strokeweight"] = style.lineWidth + "px"; + } + else styleToWrite["stroked"] = "false"; + + if (style.fillStyle) { + styleToWrite["filled"] = "true"; + var fillColor = jsPlumb.util.convertStyle(style.fillStyle, true); + styleToWrite["fillcolor"] = fillColor; + _maybeSetOpacity(styleToWrite, fillColor, "fill", component); + } + else styleToWrite["filled"] = "false"; + + if(style["dashstyle"]) { + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = style["dashstyle"]; + } + else if (style["stroke-dasharray"] && style["lineWidth"]) { + var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",", + parts = style["stroke-dasharray"].split(sep), + styleToUse = ""; + for(var i = 0; i < parts.length; i++) { + styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep); + } + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = styleToUse; + } + + _atts(node, styleToWrite); + }, + /* + * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. + */ + VmlComponent = function() { + var self = this; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.opacityNodes = { + "stroke":null, + "fill":null + }; + this.initOpacityNodes = function(vml) { + self.opacityNodes["stroke"] = _node("stroke", [0,0,1,1], {opacity:"0.0"}); + self.opacityNodes["fill"] = _node("fill", [0,0,1,1], {opacity:"0.0"}); + vml.appendChild(self.opacityNodes["stroke"]); + vml.appendChild(self.opacityNodes["fill"]); + }; + this.setOpacity = function(type, value) { + var node = self.opacityNodes[type]; + if (node) node["opacity"] = "" + value; + }; + }, + /* + * Base class for Vml connectors. extends VmlComponent. + */ + VmlConnector = jsPlumb.VmlConnector = function(params) { + var self = this; + self.strokeNode = null; + self.canvas = null; + VmlComponent.apply(this, arguments); + var clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : ""); + this.paint = function(d, style, anchor) { + if (style != null) { + var path = self.getPath(d), p = { "path":path }; + + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle : jsPlumb.util.convertStyle(style.outlineColor), + lineWidth : outlineStrokeWidth + }; + for (var aa in vmlAttributeMap) outlineStyle[aa] = style[aa]; + + if (self.bgCanvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.bgCanvas = _node("shape", d, p); + jsPlumb.appendElement(self.bgCanvas, params.parent); + _pos(self.bgCanvas, d); + displayElements.push(self.bgCanvas); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.bgCanvas, d); + _atts(self.bgCanvas, p); + } + + _applyStyles(self.bgCanvas, outlineStyle, self); + } + + if (self.canvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + if (self.tooltip) p["label"] = self.tooltip; + self.canvas = _node("shape", d, p); + jsPlumb.appendElement(self.canvas, params.parent); + displayElements.push(self.canvas); + + self.attachListeners(self.canvas, self); + + self.initOpacityNodes(self.canvas, ["stroke"]); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.canvas, d); + _atts(self.canvas, p); + } + + _applyStyles(self.canvas, style, self); + } + }; + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + self.canvas.parentNode.appendChild(el); + displayElements.push(el); + }; + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + }, + /* + * + * Base class for Vml Endpoints. extends VmlComponent. + * + */ + VmlEndpoint = function(params) { + VmlComponent.apply(this, arguments); + var vml = null, self = this, opacityStrokeNode = null, opacityFillNode = null; + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.appendElement(self.canvas, params.parent); + if (self.tooltip) self.canvas.setAttribute("label", self.tooltip); + + this.paint = function(d, style, anchor) { + var p = { }; + + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (vml == null) { + p["class"] = jsPlumb.endpointClass; + vml = self.getVml([0,0, d[2], d[3]], p, anchor); + self.canvas.appendChild(vml); + self.attachListeners(vml, self); + + self.initOpacityNodes(vml, ["fill"]); + } + else { + //p["coordsize"] = "1,1";//(d[2] * scale) + "," + (d[3] * scale); again, unsure. + _pos(vml, [0,0, d[2], d[3]]); + _atts(vml, p); + } + + _applyStyles(vml, style, self); + }; + + this.reattachListeners = function() { + if (vml) self.reattachListenersForElement(vml, self); + }; + }; + + jsPlumb.Connectors.vml.Bezier = function() { + jsPlumb.Connectors.Bezier.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Straight = function() { + jsPlumb.Connectors.Straight.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Flowchart = function() { + jsPlumb.Connectors.Flowchart.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l"; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]); + } + // finally draw a line to the end + p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e"; + return p; + }; + }; + + jsPlumb.Endpoints.vml.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("oval", d, atts); }; + }; + + jsPlumb.Endpoints.vml.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("rect", d, atts); }; + }; + + /* + * VML Image Endpoint is the same as the default image endpoint. + */ + jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image; + + /** + * placeholder for Blank endpoint in vml renderer. + */ + jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank; + + /** + * VML Label renderer. uses the default label renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label; + + var AbstractVmlArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + VmlComponent.apply(this, arguments); + var self = this, path = null; + self.canvas = null; + var getPath = function(d, connectorDimensions) { + return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) + + " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + + " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + + " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + + " x e"; + }; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) { + var p = {}; + if (strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] = jsPlumb.util.convertStyle(strokeStyle, true); + } + if (lineWidth) p["strokeweight"] = lineWidth + "px"; + if (fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = fillStyle; + } + var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + w = Math.abs(xmax - xmin), + h = Math.abs(ymax - ymin), + dim = [xmin, ymin, w, h]; + + // for VML, we create overlays using shapes that have the same dimensions and + // coordsize as their connector - overlays calculate themselves relative to the + // connector (it's how it's been done since the original canvas implementation, because + // for canvas that makes sense). + p["path"] = getPath(d, connectorDimensions); + p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale); + + dim[0] = connectorDimensions[0]; + dim[1] = connectorDimensions[1]; + dim[2] = connectorDimensions[2]; + dim[3] = connectorDimensions[3]; + + if (self.canvas == null) { + //p["class"] = jsPlumb.overlayClass; // TODO currentInstance? + self.canvas = _node("shape", dim, p); + connector.appendDisplayElement(self.canvas); + self.attachListeners(self.canvas, connector); + } + else { + _pos(self.canvas, dim); + _atts(self.canvas, p); + } + }; + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + }; + + jsPlumb.Overlays.vml.Arrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.vml.PlainArrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.vml.Diamond = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.4/mootools.jsPlumb-1.3.4-RC1.js b/archive/1.3.4/mootools.jsPlumb-1.3.4-RC1.js new file mode 100644 index 000000000..531632880 --- /dev/null +++ b/archive/1.3.4/mootools.jsPlumb-1.3.4-RC1.js @@ -0,0 +1,450 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the MooTools adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function(now) { + this.parent(now); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, _scope, value) { + var l = list[_scope]; + if (!l) { + l = []; + list[_scope] = l; + } + l.push(value); + }; + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + var _getElementObject = function(el) { + return $(el); + }, + + _removeNonPermanentDroppables = function(drag) { + /* + // remove non-permanent droppables from all arrays + var dbs = _droppables[drag.scope], d = []; + if (dbs) { + var d = []; + for (var i=0; i < dbs.length; i++) { + var isPermanent = dbs[i].getAttribute("_isPermanentDroppable"); + if (isPermanent === "true") d.push(dbs[i]); + } + dbs.splice(0, dbs.length); + _droppables[drag.scope] = d; + } + d = []; + // clear out transient droppables from the drag itself + for(var i = 0; i < drag.droppables.length; i++) { + var isPermanent = drag.droppables[i].getAttribute("_isPermanentDroppable"); + if (isPermanent === "true") d.push(drag.droppables[i]); + } + drag.droppables.splice(0, drag.droppables.length); // release old ones + drag.droppables = d; + //*/ + }; + + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el) + try { + if (el.className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.addClass(el, clazz); + } + else el.addClass(clazz); + } + catch (e) { + // SVGAnimatedString not supported; no problem. + el.addClass(clazz); + } + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + m.start(properties); + }, + + appendElement : function(child, parent) { + _getElementObject(parent).grab(child); + }, + + bind : function(el, event, callback) { + el = _getElementObject(el); + el.addEvent(event, callback); + }, + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.event.clientX, eventObject.event.clientY]; + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el), + drags = _draggablesById[id]; + return drags[0].scope; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getElementObject : _getElementObject, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + getPageXY : function(eventObject) { + return [eventObject.event.pageX, eventObject.event.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).getParent(); + }, + + getScrollLeft : function(el) { + return null; + }, + + getScrollTop : function(el) { + return null; + }, + + getSelector : function(spec) { + return $$(spec); + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e != null ? e.tagName : null; + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[0], + p = jsPlumb.CurrentLibrary.getElementObject(ui).getPosition(); + return { left:p.x, top:p.y }; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options, isPlumbedComponent) { + var id = jsPlumb.getId(el); + var drag = _draggablesById[id]; + if (!drag) { + var originalZIndex = 0, + originalCursor = null, + dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + + options['onStart'] = jsPlumb.wrap(options['onStart'], function() { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + drag.originalZIndex = originalZIndex; + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + + _removeNonPermanentDroppables(drag); + }); + + // DROPPABLES - only relevant if this is a plumbed component, ie. not just the result of the user making some DOM element + // draggable. this is the only library adapter that has to care about this parameter. + var scope = "" + (options["scope"] || jsPlumb.Defaults.Scope), + filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }, + droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + + if (isPlumbedComponent) { + + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop'); + } + }; + } + else + options["droppables"] = []; + + drag = new Drag.Move(el, options); + drag.scope = scope; + drag.originalZIndex = originalZIndex; + _add(_draggablesById, el.get("id"), drag); + // again, only keep a record of this for scope stuff if this is a plumbed component (an endpoint) + if (isPlumbedComponent) { + _add(_draggablesByScope, scope, drag); + } + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + initDroppable : function(el, options, isPlumbedComponent, isPermanent) { + var scope = options["scope"]; + _add(_droppables, scope, el); + var id = jsPlumb.getId(el); + + el.setAttribute("_isPermanentDroppable", isPermanent); // we use this to clear out droppables on drag complete. + _droppableOptions[id] = options; + _droppableScopesById[id] = scope; + var filterFunc = function(entry) { return entry.element != el; }, + draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + isAlreadyDraggable : function(el) { + return _draggablesById[jsPlumb.getId(el)] != null; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + return (typeof Drag != undefined && typeof Drag.Move != undefined); + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el.className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.removeClass(el, clazz); + } + else el.removeClass(clazz); + } + catch (e) { + // SVGAnimatedString not supported; no problem. + el.removeClass(clazz); + } + }, + + removeElement : function(element, parent) { + var el = _getElementObject(element); + if (el) el.dispose(); // ?? + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + setDragScope : function(el, scope) { + var drag = _draggablesById[el.get("id")]; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + drag[0].droppables = droppables; + }, + + setOffset : function(el, o) { + _getElementObject(el).setPosition({x:o.left, y:o.top}); + }, + + stopDrag : function() { + for (var i in _draggablesById) { + for (var j = 0; j < _draggablesById[i].length; j++) { + var d = _draggablesById[i][j]; + d.stop(); + if (d.originalZIndex != 0) + d.element.setStyle("z-index", d.originalZIndex); + } + } + }, + + /** + * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself. + * the other libraries do not. yui, in fact, cannot even pass an original event. we have to pull out stuff + * from the originalEvent to put in an options object for YUI. + * @param el + * @param event + * @param originalEvent + */ + trigger : function(el, event, originalEvent) { + originalEvent.stopPropagation(); + _getElementObject(el).fireEvent(event, originalEvent); + }, + + unbind : function(el, event, callback) { + el = _getElementObject(el); + el.removeEvent(event, callback); + } + }; + + window.addEvent('domready', jsPlumb.init); +})(); diff --git a/archive/1.3.4/mootools.jsPlumb-1.3.4-all-min.js b/archive/1.3.4/mootools.jsPlumb-1.3.4-all-min.js new file mode 100644 index 000000000..f3da520f7 --- /dev/null +++ b/archive/1.3.4/mootools.jsPlumb-1.3.4-all-min.js @@ -0,0 +1 @@ +(function(){var t=!!document.createElement("canvas").getContext,d=!!window.SVGAngle||document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1"),a=!(t|d);var n=function(C,D,A,G){var F=function(J,I){if(J===I){return true}else{if(typeof J=="object"&&typeof I=="object"){var K=true;for(var H in J){if(!F(J[H],I[H])){K=false;break}}for(var H in I){if(!F(I[H],J[H])){K=false;break}}return K}}};for(var E=+A||0,B=C.length;E-1){B.splice(A,1)}return A!=-1};if(!window.console){window.console={time:function(){},timeEnd:function(){},group:function(){},groupEnd:function(){},log:function(){}}}var i=function(D,B,C){var A=D[B];if(A==null){A=[],D[B]=A}A.push(C);return A},r=null,c=function(A,B){return l.CurrentLibrary.getAttribute(x(A),B)},e=function(B,C,A){l.CurrentLibrary.setAttribute(x(B),C,A)},u=function(B,A){l.CurrentLibrary.addClass(x(B),A)},h=function(B,A){return l.CurrentLibrary.hasClass(x(B),A)},k=function(B,A){l.CurrentLibrary.removeClass(x(B),A)},x=function(A){return l.CurrentLibrary.getElementObject(A)},p=function(A){return l.CurrentLibrary.getOffset(x(A))},b=function(A){return l.CurrentLibrary.getSize(x(A))},w=true,m=function(){if(w&&typeof console!="undefined"){try{var B=arguments[arguments.length-1];console.log(B)}catch(A){}}},z=function(A){if(w&&typeof console!="undefined"){console.group(A)}},f=function(A){if(w&&typeof console!="undefined"){console.groupEnd(A)}},y=function(A){if(w&&typeof console!="undefined"){console.time(A)}},q=function(A){if(w&&typeof console!="undefined"){console.timeEnd(A)}};EventGenerator=function(){var C={},B=this;var A=["ready"];this.bind=function(D,E){i(C,D,E)};this.fire=function(F,G,D){if(C[F]){for(var E=0;E=0){delete (aZ[a0]);aZ.splice(a0,1);return true}}}return false},aO=function(a0,aZ){return am(a0,function(a1,a2){aR[a2]=aZ;if(l.CurrentLibrary.isDragSupported(a1)){l.CurrentLibrary.setDraggable(a1,aZ)}})},aC=function(a1,a2,aZ){a2=a2==="block";var a0=null;if(aZ){if(a2){a0=function(a4){a4.setVisible(true,true,true)}}else{a0=function(a4){a4.setVisible(false,true,true)}}}var a3=c(a1,"id");I(a3,function(a5){if(a2&&aZ){var a4=a5.sourceId===a3?1:0;if(a5.endpoints[a4].isVisible()){a5.setVisible(true)}}else{a5.setVisible(a2)}},a0)},aM=function(aZ){return am(aZ,function(a1,a0){var a2=aR[a0]==null?false:aR[a0];a2=!a2;aR[a0]=a2;l.CurrentLibrary.setDraggable(a1,a2);return a2})},at=function(aZ,a1){var a0=null;if(a1){a0=function(a2){var a3=a2.isVisible();a2.setVisible(!a3)}}I(aZ,function(a3){var a2=a3.isVisible();a3.setVisible(!a2)},a0)},K=function(a4){var a2=a4.timestamp,aZ=a4.recalc,a3=a4.offset,a0=a4.elId;if(!aZ){if(a2&&a2===aV[a0]){return U[a0]}}if(aZ||!a3){var a1=x(a0);if(a1!=null){Q[a0]=b(a1);U[a0]=p(a1);aV[a0]=a2}}else{U[a0]=a3;if(Q[a0]==null){var a1=x(a0);if(a1!=null){Q[a0]=b(a1)}}}if(U[a0]&&!U[a0].right){U[a0].right=U[a0].left+Q[a0][0];U[a0].bottom=U[a0].top+Q[a0][1];U[a0].width=Q[a0][0];U[a0].height=Q[a0][1];U[a0].centerx=U[a0].left+(U[a0].width/2);U[a0].centery=U[a0].top+(U[a0].height/2)}return U[a0]},ap=function(aZ){var a0=U[aZ];if(!a0){a0=K(aZ)}return{o:a0,s:Q[aZ]}},A=function(aZ,a0){var a1=x(aZ);var a2=c(a1,"id");if(!a2||a2=="undefined"){if(arguments.length==2&&arguments[1]!=undefined){a2=a0}else{a2="jsPlumb_"+W()}e(a1,"id",a2)}return a2},Y=function(a1,aZ,a0){a1=a1||function(){};aZ=aZ||function(){};return function(){var a2=null;try{a2=aZ.apply(this,arguments)}catch(a3){m(aS,"jsPlumb function failed : "+a3)}if(a0==null||(a2!==a0)){try{a1.apply(this,arguments)}catch(a3){m(aS,"wrapped function failed : "+a3)}}return a2}};this.connectorClass="_jsPlumb_connector";this.endpointClass="_jsPlumb_endpoint";this.overlayClass="_jsPlumb_overlay";this.Anchors={};this.Connectors={canvas:{},svg:{},vml:{}};this.Endpoints={canvas:{},svg:{},vml:{}};this.Overlays={canvas:{},svg:{},vml:{}};this.addClass=function(a0,aZ){return l.CurrentLibrary.addClass(a0,aZ)};this.removeClass=function(a0,aZ){return l.CurrentLibrary.removeClass(a0,aZ)};this.hasClass=function(a0,aZ){return l.CurrentLibrary.hasClass(a0,aZ)};this.addEndpoint=function(a1,a2,bb){bb=bb||{};var a0=l.extend({},bb);l.extend(a0,a2);a0.endpoint=a0.endpoint||aS.Defaults.Endpoint||l.Defaults.Endpoint;a0.paintStyle=a0.paintStyle||aS.Defaults.EndpointStyle||l.Defaults.EndpointStyle;a1=an(a1);var a3=[],a6=a1.length&&a1.constructor!=String?a1:[a1];for(var a4=0;a40){try{for(var a0=0;a00?n(bc,bb)!=-1:true},a3=a6.length>1?{}:[],a9=function(bc,bd){if(a6.length>1){var bb=a3[bc];if(bb==null){bb=[];a3[bc]=bb}bb.push(bd)}else{a3.push(bd)}};for(var a2 in ay){if(a0(a6,a2)){for(var a1=0;a1=4)?[a4[2],a4[3]]:[0,0],offsets:(a4.length==6)?[a4[4],a4[5]]:[0,0],elementId:a1};a2=new N(a3);a2.clone=function(){return new N(a3)}}}}}if(!a2.id){a2.id="anchor_"+W()}return a2};this.makeAnchors=function(a2,a0,aZ){var a3=[];for(var a1=0;a10?a9[0]:null,a4=a9.length>0?0:-1,a8=this,a3=function(bc,ba,bg,bf,bb){var be=bf[0]+(bc.x*bb[0]),bd=bf[1]+(bc.y*bb[1]);return Math.sqrt(Math.pow(ba-be,2)+Math.pow(bg-bd,2))},aZ=a0||function(bk,bb,bc,bd,ba){var bf=bc[0]+(bd[0]/2),be=bc[1]+(bd[1]/2);var bh=-1,bj=Infinity;for(var bg=0;bg=a2.left)||(a5.left<=a2.right&&a5.right>=a2.right)||(a5.left<=a2.left&&a5.right>=a2.right)||(a2.left<=a5.left&&a2.right>=a5.right)),ba=((a5.top<=a2.top&&a5.bottom>=a2.top)||(a5.top<=a2.bottom&&a5.bottom>=a2.bottom)||(a5.top<=a2.top&&a5.bottom>=a2.bottom)||(a2.top<=a5.top&&a2.bottom>=a5.bottom));if(!(a4||ba)){var a7=null,a1=false,aZ=false,a6=null;if(a2.left>a5.left&&a2.top>a5.top){a7=["right","top"]}else{if(a2.left>a5.left&&a5.top>a2.top){a7=["top","left"]}else{if(a2.lefta5.top){a7=["left","top"]}}}}return{orientation:H.DIAGONAL,a:a7,theta:a0,theta2:a3}}else{if(a4){return{orientation:H.HORIZONTAL,a:a5.topaZ[0]?1:-1},O=function(aZ){return function(a1,a0){var a2=true;if(aZ){if(a1[0][0]a0[0][1]}}else{if(a1[0][0]>a0[0][0]){a2=true}else{a2=a1[0][1]>a0[0][1]}}return a2===false?-1:1}},D=function(a0,aZ){var a2=a0[0][0]<0?-Math.PI-a0[0][0]:Math.PI-a0[0][0],a1=aZ[0][0]<0?-Math.PI-aZ[0][0]:Math.PI-aZ[0][0];if(a2>a1){return 1}else{return a0[0][1]>aZ[0][1]?1:-1}},aA={top:aF,right:O(true),bottom:O(true),left:D},Z=function(aZ,a0){return aZ.sort(a0)},X=function(a0,aZ){var a2=Q[a0],a3=U[a0],a1=function(a9,bg,a5,a8,be,bd){if(a8.length>0){var bc=Z(a8,aA[a9]),ba=a9==="right"||a9==="top",a4=aD(a9,bg,a5,bc,be,bd,ba);var bh=function(bk,bj){var bi=aT([bj[0],bj[1]],bk.canvas);V[bk.id]=[bi[0],bi[1],bj[2],bj[3]]};for(var a6=0;a6-1){a5.splice(a6,1)}else{R(aZ,a7.elementId,a7)}};this.clearFor=function(a6){delete aZ[a6];aZ[a6]=[]};var a3=function(bq,bd,bl,ba,bg,bh,bj,bf,bs,bi,a9,bp){var bn=-1,a8=-1,bb=ba.endpoints[bj],bk=bb.id,be=[1,0][bj],a6=[[bd,bl],ba,bg,bh,bk],a7=bq[bs],br=bb._continuousAnchorEdge?bq[bb._continuousAnchorEdge]:null;if(br){var bo=g(br,function(bt){return bt[4]==bk});if(bo!=-1){br.splice(bo,1);for(var bm=0;bm=0?ba.overlays[bl]:null};this.hideOverlay=function(bm){var bl=ba.getOverlay(bm);if(bl){bl.hide()}};this.showOverlay=function(bm){var bl=ba.getOverlay(bm);if(bl){bl.show()}};this.removeAllOverlays=function(){ba.overlays.splice(0,ba.overlays.length);ba.repaint()};this.removeOverlay=function(bm){var bl=bh(bm);if(bl!=-1){var bn=ba.overlays[bl];bn.cleanup();ba.overlays.splice(bl,1)}};this.removeOverlays=function(){for(var bl=0;bl0){bg.connections[0].setHover(bs,false)}else{bg.setHover(bs)}};this.endpoint.bind("click",function(bs){bg.fire("click",bg,bs)});this.endpoint.bind("dblclick",function(bs){bg.fire("dblclick",bg,bs)});this.endpoint.bind("mouseenter",function(bs,bt){if(!bg.isHover()){br(true);bg.fire("mouseenter",bg,bt)}});this.endpoint.bind("mouseexit",function(bs,bt){if(bg.isHover()){br(false);bg.fire("mouseexit",bg,bt)}});this.setPaintStyle(bq.paintStyle||bq.style||aS.Defaults.EndpointStyle||l.Defaults.EndpointStyle,true);this.setHoverPaintStyle(bq.hoverPaintStyle||aS.Defaults.EndpointHoverStyle||l.Defaults.EndpointHoverStyle,true);this.paintStyleInUse=this.paintStyle;this.connectorStyle=bq.connectorStyle;this.connectorHoverStyle=bq.connectorHoverStyle;this.connectorOverlays=bq.connectorOverlays;this.connector=bq.connector;this.connectorTooltip=bq.connectorTooltip;this.parent=bq.parent;this.isSource=bq.isSource||false;this.isTarget=bq.isTarget||false;var bl=bq.maxConnections||aS.Defaults.MaxConnections;this.getAttachedElements=function(){return bg.connections};this.canvas=this.endpoint.canvas;this.connections=bq.connections||[];this.scope=bq.scope||F;this.timestamp=null;bg.isReattach=bq.reattach||false;bg.connectionsDetachable=aS.Defaults.ConnectionsDetachable;if(bq.connectionsDetachable===false||bq.detachable===false){bg.connectionsDetachable=false}var bb=bq.dragAllowedWhenFull||true;this.computeAnchor=function(bs){return bg.anchor.compute(bs)};this.addConnection=function(bs){bg.connections.push(bs)};this.detach=function(bs,bw,bt,bA){var bz=n(bg.connections,bs),by=false;bA=(bA!==false);if(bz>=0){if(bt||bs._forceDetach||bs.isDetachable()||bs.isDetachAllowed(bs)){var bB=bs.endpoints[0]==bg?bs.endpoints[1]:bs.endpoints[0];if(bt||bs._forceDetach||(bg.isDetachAllowed(bs))){bg.connections.splice(bz,1);if(!bw){bB.detach(bs,true,bt);if(bs.endpointsToDeleteOnDetach){for(var bx=0;bx0){bg.detach(bg.connections[0],false,true,bs)}};this.detachFrom=function(bu,bt){var bv=[];for(var bs=0;bs=0){bg.connections.splice(bs,1)}};this.getElement=function(){return bf};this.setElement=function(bu){var bw=A(bu);R(au,a7,bg);bf=x(bu);a7=A(bf);bg.elementId=a7;var bv=af({source:bw}),bt=a1.getParent(bg.canvas);a1.removeElement(bg.canvas,bt);a1.appendElement(bg.canvas,bv);for(var bs=0;bs0){var bC=bc(bu.elementWithPrecedence),bE=bC.endpoints[0]==bg?1:0,bw=bE==0?bC.sourceId:bC.targetId,bB=U[bw],bD=Q[bw];bt.txy=[bB.left,bB.top];bt.twh=bD;bt.tElement=bC.endpoints[bE]}bx=bg.anchor.compute(bt)}var bA=bd.compute(bx,bg.anchor.getOrientation(bd),bg.paintStyleInUse,bv||bg.paintStyleInUse);bd.paint(bA,bg.paintStyleInUse,bg.anchor);bg.timestamp=bz}};this.repaint=this.paint;this.removeConnection=this.detach;if(l.CurrentLibrary.isDragSupported(bf)){var bk={id:null,element:null},bj=null,a0=false,a3=null,aZ=aI(bk);var a5=function(){bj=bg.connectorSelector();var bs=true;if(bj==null&&!bq.isSource){bs=false}if(bq.isSource&&bg.isFull()&&!bb){bs=false}if(bj!=null&&!bj.isDetachable()){bs=false}if(bs===false){if(l.CurrentLibrary.stopDrag){l.CurrentLibrary.stopDrag()}aZ.stopDrag();return false}if(bj&&!bg.isFull()&&bq.isSource){bj=null}K({elId:a7});a4=bg.makeInPlaceCopy();a4.paint();G(bk,bg.parent);var by=x(a4.canvas),bw=l.CurrentLibrary.getOffset(by),bt=aT([bw.left,bw.top],a4.canvas);l.CurrentLibrary.setOffset(bk.element,{left:bt[0],top:bt[1]});e(x(bg.canvas),"dragId",bk.id);e(x(bg.canvas),"elId",a7);bo=ac(bg.paintStyle,bg.anchor,bd,bg.canvas,bk.element);if(bj==null){bg.anchor.locked=true;bg.setHover(false,false);bj=P({sourceEndpoint:bg,targetEndpoint:bo,source:bg.endpointWillMoveTo||x(bf),target:bk.element,anchors:[bg.anchor,bo.anchor],paintStyle:bq.connectorStyle,hoverPaintStyle:bq.connectorHoverStyle,connector:bq.connector,overlays:bq.connectorOverlays})}else{a0=true;bj.connector.setHover(false,false);a6(x(a4.canvas),false,true);var bv=bj.endpoints[0].id==bg.id?0:1;bj.floatingAnchorIndex=bv;bg.detachFromConnection(bj);var bz=x(bg.canvas),bx=l.CurrentLibrary.getDragScope(bz);e(bz,"originalScope",bx);var bu=l.CurrentLibrary.getDropScope(bz);l.CurrentLibrary.setDragScope(bz,bu);if(bv==0){a3=[bj.source,bj.sourceId,bn,bx];bj.source=bk.element;bj.sourceId=bk.id}else{a3=[bj.target,bj.targetId,bn,bx];bj.target=bk.element;bj.targetId=bk.id}bj.endpoints[bv==0?1:0].anchor.locked=true;bj.suspendedEndpoint=bj.endpoints[bv];bj.suspendedEndpoint.setHover(false);bj.endpoints[bv]=bo}aL[bk.id]=bj;bo.addConnection(bj);J(au,bk.id,bo);aS.currentlyDragging=true};var a1=l.CurrentLibrary,bm=bq.dragOptions||{},bh=l.extend({},a1.defaultDragOptions),bi=a1.dragEvents.start,bp=a1.dragEvents.stop,a9=a1.dragEvents.drag;bm=l.extend(bh,bm);bm.scope=bm.scope||bg.scope;bm[bi]=Y(bm[bi],a5);bm[a9]=Y(bm[a9],aZ);bm[bp]=Y(bm[bp],function(){aS.currentlyDragging=false;R(au,bk.id,bo);ax([bk.element[0],bo.canvas],bf);ad(a4.canvas,bf);aS.anchorManager.clearFor(bk.id);var bs=bj.floatingAnchorIndex==null?1:bj.floatingAnchorIndex;bj.endpoints[bs==0?1:0].anchor.locked=false;if(bj.endpoints[bs]==bo){if(a0&&bj.suspendedEndpoint){if(bs==0){bj.source=a3[0];bj.sourceId=a3[1]}else{bj.target=a3[0];bj.targetId=a3[1]}l.CurrentLibrary.setDragScope(a3[2],a3[3]);bj.endpoints[bs]=bj.suspendedEndpoint;if(bg.isReattach||bj._forceDetach||!bj.endpoints[bs==0?1:0].detach(bj)){bj.setHover(false);bj.floatingAnchorIndex=null;bj.suspendedEndpoint.addConnection(bj);l.repaint(a3[1])}bj._forceDetach=null}else{ax(bj.connector.getDisplayElements(),bg.parent);bg.detachFromConnection(bj)}}bg.anchor.locked=false;bg.paint({recalc:false});bj.setHover(false,false);bj=null;a4=null;delete au[bo.elementId];bo.anchor=null;bo=null;aS.currentlyDragging=false});var bn=x(bg.canvas);l.CurrentLibrary.initDraggable(bn,bm,true)}var a6=function(bw,bz,bu){if((bq.isTarget||bz)&&l.CurrentLibrary.isDropSupported(bf)){var bs=bq.dropOptions||aS.Defaults.DropOptions||l.Defaults.DropOptions;bs=l.extend({},bs);bs.scope=bs.scope||bg.scope;var bx=l.CurrentLibrary.dragEvents.drop,by=l.CurrentLibrary.dragEvents.over,bt=l.CurrentLibrary.dragEvents.out,bv=function(){var bJ=x(l.CurrentLibrary.getDragObject(arguments)),bA=c(bJ,"dragId"),bC=c(bJ,"elId"),bI=c(bJ,"originalScope"),bF=aL[bA],bG=bF.floatingAnchorIndex==null?1:bF.floatingAnchorIndex,bH=bG==0?1:0;if(bI){l.CurrentLibrary.setDragScope(bJ,bI)}if(!bg.isFull()&&!(bG==0&&!bg.isSource)&&!(bG==1&&!bg.isTarget)){var bD=true;if(bF.suspendedEndpoint&&bF.suspendedEndpoint.id!=bg.id){if(!bF.isDetachAllowed(bF)||!bF.endpoints[bG].isDetachAllowed(bF)||!bF.suspendedEndpoint.isDetachAllowed(bF)||!aS.checkCondition("beforeDetach",bF)){bD=false}}if(bG==0){bF.source=bf;bF.sourceId=a7}else{bF.target=bf;bF.targetId=a7}bD=bD&&bg.isDropAllowed(bF.sourceId,bF.targetId,bF.scope);if(bD){bF.endpoints[bG].detachFromConnection(bF);if(bF.suspendedEndpoint){bF.suspendedEndpoint.detachFromConnection(bF)}bF.endpoints[bG]=bg;bg.addConnection(bF);if(!bF.suspendedEndpoint){aH(bf,bq.draggable,{})}else{var bE=bF.suspendedEndpoint.getElement(),bB=bF.suspendedEndpoint.elementId;aE({source:bG==0?bE:bF.source,target:bG==1?bE:bF.target,sourceId:bG==0?bB:bF.sourceId,targetId:bG==1?bB:bF.targetId,sourceEndpoint:bG==0?bF.suspendedEndpoint:bF.endpoints[0],targetEndpoint:bG==1?bF.suspendedEndpoint:bF.endpoints[1],connection:bF},true)}aX(bF)}else{if(bF.suspendedEndpoint){bF.endpoints[bG]=bF.suspendedEndpoint;bF.setHover(false);bF._forceDetach=true;if(bG==0){bF.source=bF.suspendedEndpoint.element;bF.sourceId=bF.suspendedEndpoint.elementId}else{bF.target=bF.suspendedEndpoint.element;bF.targetId=bF.suspendedEndpoint.elementId}bF.suspendedEndpoint.addConnection(bF);bF.endpoints[0].repaint();bF.repaint();l.repaint(bF.source.elementId);bF._forceDetach=false}}bF.floatingAnchorIndex=null}aS.currentlyDragging=false;delete aL[bA]};bs[bx]=Y(bs[bx],bv);bs[by]=Y(bs[by],function(){var bB=l.CurrentLibrary.getDragObject(arguments),bD=c(x(bB),"dragId"),bC=aL[bD];if(bC!=null){var bA=bC.floatingAnchorIndex==null?1:bC.floatingAnchorIndex;bC.endpoints[bA].anchor.over(bg.anchor)}});bs[bt]=Y(bs[bt],function(){var bB=l.CurrentLibrary.getDragObject(arguments),bD=c(x(bB),"dragId"),bC=aL[bD];if(bC!=null){var bA=bC.floatingAnchorIndex==null?1:bC.floatingAnchorIndex;bC.endpoints[bA].anchor.out()}});l.CurrentLibrary.initDroppable(bw,bs,true,bu)}};a6(x(bg.canvas),true,!(bq._transient||bg.anchor.isFloating));return bg}};var l=window.jsPlumb=new s();l.getInstance=function(B){var A=new s(B);A.init();return A};l.util={convertStyle:function(B,A){if("transparent"===B){return B}var G=B,F=function(H){return H.length==1?"0"+H:H},C=function(H){return F(Number(H).toString(16))},D=/(rgb[a]?\()(.*)(\))/;if(B.match(D)){var E=B.match(D)[2].split(",");G="#"+C(E[0])+C(E[1])+C(E[2]);if(!A&&E.length==4){G=G+C(E[3])}}return G},gradient:function(B,A){B=B.constructor==Array?B:[B.x,B.y];A=A.constructor==Array?A:[A.x,A.y];return(A[1]-B[1])/(A[0]-B[0])},normal:function(B,A){return -1/l.util.gradient(B,A)},segment:function(B,A){B=B.constructor==Array?B:[B.x,B.y];A=A.constructor==Array?A:[A.x,A.y];if(A[0]>B[0]){return(A[1]>B[1])?2:1}else{return(A[1]>B[1])?3:4}},segmentMultipliers:[null,[1,-1],[1,1],[-1,1],[-1,-1]],inverseSegmentMultipliers:[null,[-1,-1],[-1,1],[1,1],[1,-1]],pointOnLine:function(A,E,B){var D=l.util.gradient(A,E),I=l.util.segment(A,E),H=B>0?l.util.segmentMultipliers[I]:l.util.inverseSegmentMultipliers[I],C=Math.atan(D),F=Math.abs(B*Math.sin(C))*H[1],G=Math.abs(B*Math.cos(C))*H[0];return{x:A.x+G,y:A.y+F}},perpendicularLineTo:function(C,D,E){var B=l.util.gradient(C,D),F=Math.atan(-1/B),G=E/2*Math.sin(F),A=E/2*Math.cos(F);return[{x:D.x+A,y:D.y+G},{x:D.x-A,y:D.y-G}]}};var o=function(A,F,C,B,E,D){return function(H){H=H||{};var G=l.makeAnchor([A,F,C,B,0,0],H.elementId,H.jsPlumbInstance);G.type=E;if(D){D(G,H)}return G}};l.Anchors.TopCenter=o(0.5,0,0,-1,"TopCenter");l.Anchors.BottomCenter=o(0.5,1,0,1,"BottomCenter");l.Anchors.LeftMiddle=o(0,0.5,-1,0,"LeftMiddle");l.Anchors.RightMiddle=o(1,0.5,1,0,"RightMiddle");l.Anchors.Center=o(0.5,0.5,0,0,"Center");l.Anchors.TopRight=o(1,0,0,-1,"TopRight");l.Anchors.BottomRight=o(1,1,0,1,"BottomRight");l.Anchors.TopLeft=o(0,0,0,-1,"TopLeft");l.Anchors.BottomLeft=o(0,1,0,1,"BottomLeft");l.Defaults.DynamicAnchors=function(A){return l.makeAnchors(["TopCenter","RightMiddle","BottomCenter","LeftMiddle"],A.elementId,A.jsPlumbInstance)};l.Anchors.AutoDefault=function(B){var A=l.makeDynamicAnchor(l.Defaults.DynamicAnchors(B));A.type="AutoDefault";return A};l.Anchors.Assign=o(0,0,0,0,"Assign",function(B,C){var A=C.position||"Fixed";B.positionFinder=A.constructor==String?l.AnchorPositionFinders[A]:A;B.constructorParams=C});l.Anchors.Continuous=function(A){return A.jsPlumbInstance.continuousAnchorFactory.get(A)};l.AnchorPositionFinders={Fixed:function(D,B,C,A){return[(D.left-B.left)/C[0],(D.top-B.top)/C[1]]},Grid:function(A,J,E,B){var I=A.left-J.left,H=A.top-J.top,G=E[0]/(B.constructorParams.grid[0]),F=E[1]/(B.constructorParams.grid[1]),D=Math.floor(I/G),C=Math.floor(H/F);return[((D*G)+(G/2))/E[0],((C*F)+(F/2))/E[1]]}}})();(function(){jsPlumb.DOMElementComponent=function(b){jsPlumb.jsPlumbUIComponent.apply(this,arguments);this.mousemove=this.dblclick=this.click=this.mousedown=this.mouseup=function(c){}};jsPlumb.Connectors.Straight=function(){this.type="Straight";var q=this,h=null,d,i,o,m,k,e,p,g,f,c,b,n,l;this.compute=function(z,I,r,v,E,s,C,u){var H=Math.abs(z[0]-I[0]),B=Math.abs(z[1]-I[1]),A=0.45*H,t=0.45*B;H*=1.9;B*=1.9;var F=Math.min(z[0],I[0])-A;var D=Math.min(z[1],I[1])-t;var G=Math.max(2*C,u);if(Hm){m=w}if(z<0){e+=z;var B=Math.abs(z);m+=B;o[0]+=B;k+=B;c+=B;n[0]+=B}var J=Math.min(g,b),H=Math.min(o[1],n[1]),v=Math.min(J,H),A=Math.max(g,b),y=Math.max(o[1],n[1]),s=Math.max(A,y);if(s>h){h=s}if(v<0){d+=v;var x=Math.abs(v);h+=x;o[1]+=x;g+=x;b+=x;n[1]+=x}if(F&&m=s){q=t;r=(s-l[t][0])/b[t];break}}return{segment:i[q],proportion:r,index:q}};this.compute=function(T,v,L,z,S,r,q,I){i=[];n=0;b=[];e=v[0]t[0]?t[0]+((1-x)*r)-y:t[2]+(x*r)+y,y:q==0?t[3]:t[3]>t[1]?t[1]+((1-x)*r)-y:t[3]+(x*r)+y,segmentInfo:v};return w}};jsPlumb.Endpoints.Dot=function(c){this.type="Dot";var b=this;c=c||{};this.radius=c.radius||10;this.defaultOffset=0.5*this.radius;this.defaultInnerRadius=this.radius/3;this.compute=function(h,e,k,g){var f=k.radius||b.radius,d=h[0]-f,i=h[1]-f;return[d,i,f*2,f*2,f]}};jsPlumb.Endpoints.Rectangle=function(c){this.type="Rectangle";var b=this;c=c||{};this.width=c.width||20;this.height=c.height||20;this.compute=function(i,f,l,h){var g=l.width||b.width,e=l.height||b.height,d=i[0]-(g/2),k=i[1]-(e/2);return[d,k,g,e]}};jsPlumb.Endpoints.Image=function(f){this.type="Image";jsPlumb.DOMElementComponent.apply(this,arguments);var k=this,e=false,d=f.width,c=f.height,h=null,b=f.endpoint;this.img=new Image();k.ready=false;this.img.onload=function(){k.ready=true;d=d||k.img.width;c=c||k.img.height;if(h){h(this)}};b.setImage=function(l,n){var m=l.constructor==String?l:l.src;h=n;k.img.src=l};b.setImage(f.src||f.url,f.onload);this.compute=function(n,l,o,m){k.anchorPoint=n;if(k.ready){return[n[0]-d/2,n[1]-c/2,d,c]}else{return[0,0,0,0]}};k.canvas=document.createElement("img"),e=false;k.canvas.style.margin=0;k.canvas.style.padding=0;k.canvas.style.outline=0;k.canvas.style.position="absolute";var g=f.cssClass?" "+f.cssClass:"";k.canvas.className=jsPlumb.endpointClass+g;if(d){k.canvas.setAttribute("width",d)}if(c){k.canvas.setAttribute("height",c)}jsPlumb.appendElement(k.canvas,f.parent);k.attachListeners(k.canvas,k);var i=function(o,n,m){if(!e){k.canvas.setAttribute("src",k.img.src);e=true}var l=k.anchorPoint[0]-(d/2),p=k.anchorPoint[1]-(c/2);jsPlumb.sizeCanvas(k.canvas,l,p,d,c)};this.paint=function(n,m,l){if(k.ready){i(n,m,l)}else{window.setTimeout(function(){k.paint(n,m,l)},200)}}};jsPlumb.Endpoints.Blank=function(c){var b=this;this.type="Blank";jsPlumb.DOMElementComponent.apply(this,arguments);this.compute=function(f,d,g,e){return[f[0],f[1],10,0]};b.canvas=document.createElement("div");b.canvas.style.display="block";b.canvas.style.width="1px";b.canvas.style.height="1px";b.canvas.style.background="transparent";b.canvas.style.position="absolute";b.canvas.className=b._jsPlumb.endpointClass;jsPlumb.appendElement(b.canvas,c.parent);this.paint=function(g,f,e){jsPlumb.sizeCanvas(b.canvas,g[0],g[1],g[2],g[3])}};jsPlumb.Endpoints.Triangle=function(b){this.type="Triangle";b=b||{};b.width=b.width||55;b.height=b.height||55;this.width=b.width;this.height=b.height;this.compute=function(h,e,k,g){var f=k.width||self.width,d=k.height||self.height,c=h[0]-(f/2),i=h[1]-(d/2);return[c,i,f,d]}};var a=function(d){var c=true,b=this;this.isAppendedAtTopLevel=true;this.connection=d.connection;this.loc=d.location==null?0.5:d.location;this.setVisible=function(e){c=e;b.connection.repaint()};this.isVisible=function(){return c};this.hide=function(){b.setVisible(false)};this.show=function(){b.setVisible(true)};this.incrementLocation=function(e){b.loc+=e;b.connection.repaint()};this.setLocation=function(e){b.loc=e;b.connection.repaint()};this.getLocation=function(){return b.loc}};jsPlumb.Overlays.Arrow=function(f){this.type="Arrow";a.apply(this,arguments);this.isAppendedAtTopLevel=false;f=f||{};var c=this;this.length=f.length||20;this.width=f.width||20;this.id=f.id;var e=(f.direction||1)<0?-1:1,d=f.paintStyle||{lineWidth:1},b=f.foldback||0.623;this.computeMaxSize=function(){return c.width*1.5};this.cleanup=function(){};this.draw=function(h,w,r){var l,s,g,m,k;if(c.loc==1){l=h.pointOnPath(c.loc);s=h.pointAlongPathFrom(c.loc,-1);g=jsPlumb.util.pointOnLine(l,s,c.length)}else{if(c.loc==0){g=h.pointOnPath(c.loc);s=h.pointAlongPathFrom(c.loc,1);l=jsPlumb.util.pointOnLine(g,s,c.length)}else{l=h.pointAlongPathFrom(c.loc,e*c.length/2),s=h.pointOnPath(c.loc),g=jsPlumb.util.pointOnLine(l,s,c.length)}}m=jsPlumb.util.perpendicularLineTo(l,g,c.width);k=jsPlumb.util.pointOnLine(l,g,b*c.length);var v=Math.min(l.x,m[0].x,m[1].x),p=Math.max(l.x,m[0].x,m[1].x),u=Math.min(l.y,m[0].y,m[1].y),o=Math.max(l.y,m[0].y,m[1].y);var n={hxy:l,tail:m,cxy:k},q=d.strokeStyle||w.strokeStyle,t=d.fillStyle||w.strokeStyle,i=d.lineWidth||w.lineWidth;c.paint(h,n,i,q,t,r);return[v,p,u,o]}};jsPlumb.Overlays.PlainArrow=function(c){c=c||{};var b=jsPlumb.extend(c,{foldback:1});jsPlumb.Overlays.Arrow.call(this,b);this.type="PlainArrow"};jsPlumb.Overlays.Diamond=function(d){d=d||{};var b=d.length||40,c=jsPlumb.extend(d,{length:b/2,foldback:2});jsPlumb.Overlays.Arrow.call(this,c);this.type="Diamond"};jsPlumb.Overlays.Label=function(h){this.type="Label";jsPlumb.DOMElementComponent.apply(this,arguments);a.apply(this,arguments);this.labelStyle=h.labelStyle||jsPlumb.Defaults.LabelStyle;this.id=h.id;this.cachedDimensions=null;var d=h.label||"",b=this,e=false,i=document.createElement("div"),f=null;i.style.position="absolute";var c=h._jsPlumb.overlayClass+" "+(b.labelStyle.cssClass?b.labelStyle.cssClass:h.cssClass?h.cssClass:"");i.className=c;jsPlumb.appendElement(i,h.connection.parent);jsPlumb.getId(i);b.attachListeners(i,b);b.canvas=i;var g=b.setVisible;b.setVisible=function(k){g(k);i.style.display=k?"block":"none"};this.getElement=function(){return i};this.cleanup=function(){if(i!=null){jsPlumb.CurrentLibrary.removeElement(i)}};this.setLabel=function(k){d=k;f=null;b.connection.repaint()};this.getLabel=function(){return d};this.paint=function(k,m,l){if(!e){k.appendDisplayElement(i);b.attachListeners(i,k);e=true}i.style.left=(l[0]+m.minx)+"px";i.style.top=(l[1]+m.miny)+"px"};this.getTextDimensions=function(l){if(typeof d=="function"){var k=d(b);i.innerHTML=k.replace(/\r\n/g,"
")}else{if(f==null){f=d;i.innerHTML=f.replace(/\r\n/g,"
")}}var n=jsPlumb.CurrentLibrary.getElementObject(i),m=jsPlumb.CurrentLibrary.getSize(n);return{width:m[0],height:m[1]}};this.computeMaxSize=function(k){var l=b.getTextDimensions(k);return l.width?Math.max(l.width,l.height)*1.5:0};this.draw=function(m,o,n){var q=b.getTextDimensions(m);if(q.width!=null){var p=m.pointOnPath(b.loc),l=p.x-(q.width/2),k=p.y-(q.height/2);b.paint(m,{minx:l,miny:k,td:q,cxy:p},n);return[l,l+q.width,k,k+q.height]}else{return[0,0,0,0]}};this.reattachListeners=function(){if(i){b.reattachListenersForElement(i,b)}}};jsPlumb.Overlays.GuideLines=function(){var b=this;b.length=50;b.lineWidth=5;this.type="GuideLines";a.apply(this,arguments);jsPlumb.jsPlumbUIComponent.apply(this,arguments);this.draw=function(d,k,i){var h=d.pointAlongPathFrom(b.loc,b.length/2),g=d.pointOnPath(b.loc),f=jsPlumb.util.pointOnLine(h,g,b.length),e=jsPlumb.util.perpendicularLineTo(h,f,40),c=jsPlumb.util.perpendicularLineTo(f,h,20);b.paint(d,[h,f,e,c],b.lineWidth,"red",null,i);return[Math.min(h.x,f.x),Math.min(h.y,f.y),Math.max(h.x,f.x),Math.max(h.y,f.y)]};this.computeMaxSize=function(){return 50};this.cleanup=function(){}}})();(function(){var c=function(e,g,d,f){this.m=(f-g)/(d-e);this.b=-1*((this.m*e)-g);this.rectIntersect=function(q,p,s,o){var n=[];var k=(p-this.b)/this.m;if(k>=q&&k<=(q+s)){n.push([k,(this.m*k)+this.b])}var t=(this.m*(q+s))+this.b;if(t>=p&&t<=(p+o)){n.push([(t-this.b)/this.m,t])}var k=((p+o)-this.b)/this.m;if(k>=q&&k<=(q+s)){n.push([k,(this.m*k)+this.b])}var t=(this.m*q)+this.b;if(t>=p&&t<=(p+o)){n.push([(t-this.b)/this.m,t])}if(n.length==2){var m=(n[0][0]+n[1][0])/2,l=(n[0][1]+n[1][1])/2;n.push([m,l]);var i=m<=q+(s/2)?-1:1,r=l<=p+(o/2)?-1:1;n.push([i,r]);return n}return null}},a=function(e,g,d,f){if(e<=d&&f<=g){return 1}else{if(e<=d&&g<=f){return 2}else{if(d<=e&&f>=g){return 3}}}return 4},b=function(g,f,i,e,h,m,l,d,k){if(d<=k){return[g,f]}if(i==1){if(e[3]<=0&&h[3]>=1){return[g+(e[2]<0.5?-1*m:m),f]}else{if(e[2]>=1&&h[2]<=0){return[g,f+(e[3]<0.5?-1*l:l)]}else{return[g+(-1*m),f+(-1*l)]}}}else{if(i==2){if(e[3]>=1&&h[3]<=0){return[g+(e[2]<0.5?-1*m:m),f]}else{if(e[2]>=1&&h[2]<=0){return[g,f+(e[3]<0.5?-1*l:l)]}else{return[g+(1*m),f+(-1*l)]}}}else{if(i==3){if(e[3]>=1&&h[3]<=0){return[g+(e[2]<0.5?-1*m:m),f]}else{if(e[2]<=0&&h[2]>=1){return[g,f+(e[3]<0.5?-1*l:l)]}else{return[g+(-1*m),f+(-1*l)]}}}else{if(i==4){if(e[3]<=0&&h[3]>=1){return[g+(e[2]<0.5?-1*m:m),f]}else{if(e[2]<=0&&h[2]>=1){return[g,f+(e[3]<0.5?-1*l:l)]}else{return[g+(1*m),f+(-1*l)]}}}}}}};jsPlumb.Connectors.StateMachine=function(l){var s=this,n=null,o,m,g,e,p=[],d=l.curviness||10,k=l.margin||5,q=l.proximityLimit||80,f=l.orientation&&l.orientation=="clockwise",i=l.loopbackRadius||25,h=false;this.type="StateMachine";l=l||{};this.compute=function(ab,F,U,G,aa,u,t,S){var O=Math.abs(ab[0]-F[0]),W=Math.abs(ab[1]-F[1]),Q=0.45*O,Z=0.45*W;O*=1.9;W*=1.9;t=t||1;var M=Math.min(ab[0],F[0])-Q,K=Math.min(ab[1],F[1])-Z;if(U.elementId!=G.elementId){h=false;o=ab[0]0&&v<1){v=1-v}var w=(v*2*Math.PI)+(Math.PI/2),u=n[4]+(n[6]*Math.cos(w)),t=n[5]+(n[6]*Math.sin(w));return{x:u,y:t}}else{return jsBezier.pointOnCurve(r(),v)}};this.gradientAtPoint=function(t){if(h){return Math.atan(t*2*Math.PI)}else{return jsBezier.gradientAtPoint(r(),t)}};this.pointAlongPathFrom=function(v,z){if(h){if(v>0&&v<1){v=1-v}var w=2*Math.PI*n[6],y=z/w*2*Math.PI,x=(v*2*Math.PI)-y+(Math.PI/2),u=n[4]+(n[6]*Math.cos(x)),t=n[5]+(n[6]*Math.sin(x));return{x:u,y:t}}return jsBezier.pointAlongCurveFrom(r(),v,z)}};jsPlumb.Connectors.canvas.StateMachine=function(f){f=f||{};var d=this,g=f.drawGuideline||true,e=f.avoidSelector;jsPlumb.Connectors.StateMachine.apply(this,arguments);jsPlumb.CanvasConnector.apply(this,arguments);this._paint=function(l){if(l.length==10){d.ctx.beginPath();d.ctx.moveTo(l[4],l[5]);d.ctx.quadraticCurveTo(l[8],l[9],l[6],l[7]);d.ctx.stroke()}else{d.ctx.save();d.ctx.beginPath();var k=0,i=2*Math.PI,h=l[7];d.ctx.arc(l[4],l[5],l[6],0,i,h);d.ctx.stroke();d.ctx.closePath();d.ctx.restore()}};this.createGradient=function(i,h){return h.createLinearGradient(i[4],i[5],i[6],i[7])}};jsPlumb.Connectors.svg.StateMachine=function(){var d=this;jsPlumb.Connectors.StateMachine.apply(this,arguments);jsPlumb.SvgConnector.apply(this,arguments);this.getPath=function(e){if(e.length==10){return"M "+e[4]+" "+e[5]+" C "+e[8]+" "+e[9]+" "+e[8]+" "+e[9]+" "+e[6]+" "+e[7]}else{return"M"+(e[8]+4)+" "+e[9]+" A "+e[6]+" "+e[6]+" 0 1,0 "+(e[8]-4)+" "+e[9]}}};jsPlumb.Connectors.vml.StateMachine=function(){jsPlumb.Connectors.StateMachine.apply(this,arguments);jsPlumb.VmlConnector.apply(this,arguments);var d=jsPlumb.vml.convertValue;this.getPath=function(k){if(k.length==10){return"m"+d(k[4])+","+d(k[5])+" c"+d(k[8])+","+d(k[9])+","+d(k[8])+","+d(k[9])+","+d(k[6])+","+d(k[7])+" e"}else{var h=d(k[8]-k[6]),g=d(k[9]-(2*k[6])),f=h+d(2*k[6]),e=g+d(2*k[6]),l=h+","+g+","+f+","+e;var i="ar "+l+","+d(k[8])+","+d(k[9])+","+d(k[8])+","+d(k[9])+" e";return i}}}})();(function(){var h={"stroke-linejoin":"joinstyle",joinstyle:"joinstyle",endcap:"endcap",miterlimit:"miterlimit"};if(document.createStyleSheet){document.createStyleSheet().addRule(".jsplumb_vml","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:textbox","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:oval","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:rect","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:stroke","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:shape","behavior:url(#default#VML);position:absolute;");document.namespaces.add("jsplumb","urn:schemas-microsoft-com:vml")}jsPlumb.vml={};var b=1000,c=function(p,q){for(var n in q){p[n]=q[n]}},m=function(n,q,r){r=r||{};var p=document.createElement("jsplumb:"+n);p.className=(r["class"]?r["class"]+" ":"")+"jsplumb_vml";l(p,q);c(p,r);return p},l=function(p,n){p.style.left=n[0]+"px";p.style.top=n[1]+"px";p.style.width=n[2]+"px";p.style.height=n[3]+"px";p.style.position="absolute"},g=jsPlumb.vml.convertValue=function(n){return Math.floor(n*b)},d=function(q,o,p,n){if("transparent"===o){n.setOpacity(p,"0.0")}else{n.setOpacity(p,"1.0")}},f=function(r,n,u){var q={};if(n.strokeStyle){q.stroked="true";var v=jsPlumb.util.convertStyle(n.strokeStyle,true);q.strokecolor=v;d(q,v,"stroke",u);q.strokeweight=n.lineWidth+"px"}else{q.stroked="false"}if(n.fillStyle){q.filled="true";var o=jsPlumb.util.convertStyle(n.fillStyle,true);q.fillcolor=o;d(q,o,"fill",u)}else{q.filled="false"}if(n.dashstyle){if(u.strokeNode==null){u.strokeNode=m("stroke",[0,0,0,0],{dashstyle:n.dashstyle});r.appendChild(u.strokeNode)}else{u.strokeNode.dashstyle=n.dashstyle}}else{if(n["stroke-dasharray"]&&n.lineWidth){var w=n["stroke-dasharray"].indexOf(",")==-1?" ":",",s=n["stroke-dasharray"].split(w),p="";for(var t=0;t0&&B>0&&t=t&&v[2]<=B&&v[3]>=B)){return true}}var z=p.canvas.getContext("2d").getImageData(parseInt(t),parseInt(B),1,1);return z.data[0]!=0||z.data[1]!=0||z.data[2]!=0||z.data[3]!=0}return false};var o=false,n=false,s=null,r=false,q=function(u,t){return u!=null&&i(u,t)};this.mousemove=function(w){var y=m(w),v=f(w),u=document.elementFromPoint(v[0],v[1]),x=q(u,"_jsPlumb_overlay");var t=d==null&&(q(u,"_jsPlumb_endpoint")||q(u,"_jsPlumb_connector"));if(!o&&t&&p._over(w)){o=true;p.fire("mouseenter",p,w);return true}else{if(o&&(!p._over(w)||!t)&&!x){o=false;p.fire("mouseexit",p,w)}}p.fire("mousemove",p,w)};this.click=function(t){if(o&&p._over(t)&&!r){p.fire("click",p,t)}r=false};this.dblclick=function(t){if(o&&p._over(t)&&!r){p.fire("dblclick",p,t)}r=false};this.mousedown=function(t){if(p._over(t)&&!n){n=true;s=l(a(p.canvas));p.fire("mousedown",p,t)}};this.mouseup=function(t){n=false;p.fire("mouseup",p,t)}};var c=function(o){var n=document.createElement("canvas");jsPlumb.appendElement(n,o.parent);n.style.position="absolute";if(o["class"]){n.className=o["class"]}o._jsPlumb.getId(n,o.uuid);if(o.tooltip){n.setAttribute("label",o.tooltip)}return n};var h=jsPlumb.CanvasConnector=function(r){k.apply(this,arguments);var n=function(v,t){o.ctx.save();jsPlumb.extend(o.ctx,t);if(t.gradient){var u=o.createGradient(v,o.ctx);for(var s=0;sq?q=s:sl.location){l.location=0}return i(m,l.location)},nearestPointOnCurve:function(m,l){var n=h(m,l);return{point:a(l,l.length-1,n.location,null,null),location:n.location}},pointOnCurve:c,pointAlongCurveFrom:function(m,l,n){return k(m,l,n).point},perpendicularToCurveAt:function(m,l,n,o){l=k(m,l,null==o?0:o);m=i(m,l.location);o=Math.atan(-1/m);m=n/2*Math.sin(o);n=n/2*Math.cos(o);return[{x:l.point.x+n,y:l.point.y+m},{x:l.point.x-n,y:l.point.y-m}]}}})(); \ No newline at end of file diff --git a/archive/1.3.4/mootools.jsPlumb-1.3.4-all.js b/archive/1.3.4/mootools.jsPlumb-1.3.4-all.js new file mode 100644 index 000000000..b97c3a79e --- /dev/null +++ b/archive/1.3.4/mootools.jsPlumb-1.3.4-all.js @@ -0,0 +1,8065 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the jsPlumb core code. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + /** + * Class:jsPlumb + * The jsPlumb engine, registered as a static object in the window. This object contains all of the methods you will use to + * create and maintain Connections and Endpoints. + */ + + var canvasAvailable = !!document.createElement('canvas').getContext, + svgAvailable = !!window.SVGAngle || document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"), + // TODO what is a good test for VML availability? aside from just assuming its there because nothing else is. + vmlAvailable = !(canvasAvailable | svgAvailable); + + // finds the index of some value in an array, using a deep equals test. + var _findIndex = function(a, v, b, s) { + var _eq = function(o1, o2) { + if (o1 === o2) return true; + else if (typeof o1 == "object" && typeof o2 == "object") { + var same = true; + for ( var propertyName in o1) { + if (!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for ( var propertyName in o2) { + if (!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for ( var i = +b || 0, l = a.length; i < l; i++) { + if (_eq(a[i], v)) + return i; + } + return -1; + }, + _findWithFunction = function(a, f) { + for (var i = 0; i < a.length; i++) if (f(a[i])) return i; + return -1; + }, + _removeWithFunction = function(a, f) { + var idx = _findWithFunction(a, f); + if (idx > -1) a.splice(idx, 1); + return idx != -1; + }; + + + // for those browsers that dont have it. they still don't have it! but at least they won't crash. + if (!window.console) + window.console = { time:function(){}, timeEnd:function(){}, group:function(){}, groupEnd:function(){}, log:function(){} }; + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = [], map[key] = l; + } + l.push(value); + return l; + }, + _connectionBeingDragged = null, + _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }, + _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }, + _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }, + _logEnabled = true, + _log = function() { + if (_logEnabled && typeof console != "undefined") { + try { + var msg = arguments[arguments.length - 1]; + console.log(msg); + } + catch (e) {} + } + }, + _group = function(g) { + if (_logEnabled && typeof console != "undefined") console.group(g); + }, + _groupEnd = function(g) { + if (_logEnabled && typeof console != "undefined") console.groupEnd(g); + }, + _time = function(t) { + if (_logEnabled && typeof console != "undefined") console.time(t); + }, + _timeEnd = function(t) { + if (_logEnabled && typeof console != "undefined") console.timeEnd(t); + }; + + /** + * EventGenerator + * Superclass for objects that generate events - jsPlumb extends this, as does jsPlumbUIComponent, which all the UI elements extend. + */ + EventGenerator = function() { + var _listeners = {}, self = this; + + // this is a list of events that should re-throw any errors that occur during their dispatch. as of 1.3.0 this is private to + // jsPlumb, but it seems feasible that people might want to manipulate this list. the thinking is that we don't want event + // listeners to bring down jsPlumb - or do we. i can't make up my mind about this, but i know i want to hear about it if the "ready" + // event fails, because then my page has most likely not initialised. so i have this halfway-house solution. it will be interesting + // to hear what other people think. + var eventsToDieOn = [ "ready" ]; + + /* + * Binds a listener to an event. + * + * Parameters: + * event - name of the event to bind to. + * listener - function to execute. + */ + this.bind = function(event, listener) { + _addToList(_listeners, event, listener); + }; + /* + * Fires an update for the given event. + * + * Parameters: + * event - event to fire + * value - value to pass to the event listener(s). + * o riginalEvent - the original event from the browser + */ + this.fire = function(event, value, originalEvent) { + if (_listeners[event]) { + for ( var i = 0; i < _listeners[event].length; i++) { + // doing it this way rather than catching and then possibly re-throwing means that an error propagated by this + // method will have the whole call stack available in the debugger. + if (_findIndex(eventsToDieOn, event) != -1) + _listeners[event][i](value, originalEvent); + else { + // for events we don't want to die on, catch and log. + try { + _listeners[event][i](value, originalEvent); + } catch (e) { + _log("jsPlumb: fire failed for event " + event + " : " + e); + } + } + } + } + }; + /* + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + this.clearListeners = function(event) { + if (event) + delete _listeners[event]; + else { + delete _listeners; + _listeners = {}; + } + }; + + this.getListener = function(forEvent) { + return _listeners[forEvent]; + }; + }, + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + _timestamp = function() { return "" + (new Date()).getTime(); }, + + /* + * Class:jsPlumbUIComponent + * Abstract superclass for UI components Endpoint and Connection. Provides the abstraction of paintStyle/hoverPaintStyle, + * and also extends EventGenerator to provide the bind and fire methods. + */ + jsPlumbUIComponent = function(params) { + var self = this, a = arguments, _hover = false, parameters = params.parameters || {}, idPrefix = self.idPrefix, + id = idPrefix + (new Date()).getTime(); + self._jsPlumb = params["_jsPlumb"]; + self.getId = function() { return id; }; + self.tooltip = params.tooltip; + self.hoverClass = params.hoverClass; + + // all components can generate events + EventGenerator.apply(this); + // all components get this clone function. + // TODO issue 116 showed a problem with this - it seems 'a' that is in + // the clone function's scope is shared by all invocations of it, the classic + // JS closure problem. for now, jsPlumb does a version of this inline where + // it used to call clone. but it would be nice to find some time to look + // further at this. + this.clone = function() { + var o = new Object(); + self.constructor.apply(o, a); + return o; + }; + + this.getParameter = function(name) { return parameters[name]; }, + this.getParameters = function() { return parameters; }, + this.setParameter = function(name, value) { parameters[name] = value; }, + this.setParameters = function(p) { parameters = p; }, + this.overlayPlacements = [], + this.paintStyle = null, + this.hoverPaintStyle = null; + + // user can supply a beforeDetach callback, which will be executed before a detach + // is performed; returning false prevents the detach. + var beforeDetach = params.beforeDetach; + this.isDetachAllowed = function(connection) { + var r = true; + if (beforeDetach) { + try { + r = beforeDetach(connection); + } + catch (e) { _log("jsPlumb: beforeDetach callback failed", e); } + } + return r; + }; + + // user can supply a beforeDrop callback, which will be executed before a dropped + // connection is confirmed. user can return false to reject connection. + var beforeDrop = params.beforeDrop; + this.isDropAllowed = function(sourceId, targetId, scope) { + var r = self._jsPlumb.checkCondition("beforeDrop", { sourceId:sourceId, targetId:targetId, scope:scope }); + if (beforeDrop) { + try { + r = beforeDrop({ sourceId:sourceId, targetId:targetId, scope:scope }); + } + catch (e) { _log("jsPlumb: beforeDrop callback failed", e); } + } + return r; + }; + + // helper method to update the hover style whenever it, or paintStyle, changes. + // we use paintStyle as the foundation and merge hoverPaintStyle over the + // top. + var _updateHoverStyle = function() { + if (self.paintStyle && self.hoverPaintStyle) { + var mergedHoverStyle = {}; + jsPlumb.extend(mergedHoverStyle, self.paintStyle); + jsPlumb.extend(mergedHoverStyle, self.hoverPaintStyle); + delete self["hoverPaintStyle"]; + // we want the fillStyle of paintStyle to override a gradient, if possible. + if (mergedHoverStyle.gradient && self.paintStyle.fillStyle) + delete mergedHoverStyle["gradient"]; + self.hoverPaintStyle = mergedHoverStyle; + } + }; + + /* + * Sets the paint style and then repaints the element. + * + * Parameters: + * style - Style to use. + */ + this.setPaintStyle = function(style, doNotRepaint) { + self.paintStyle = style; + self.paintStyleInUse = self.paintStyle; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /* + * Sets the paint style to use when the mouse is hovering over the element. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the component will not be repainted. useful when setting things up initially. + */ + this.setHoverPaintStyle = function(style, doNotRepaint) { + self.hoverPaintStyle = style; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /* + * sets/unsets the hover state of this element. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + this.setHover = function(hover, ignoreAttachedElements, timestamp) { + // while dragging, we ignore these events. this keeps the UI from flashing and + // swishing and whatevering. + if (!self._jsPlumb.currentlyDragging) { + + _hover = hover; + if (self.hoverClass != null && self.canvas != null) { + if (hover) + jpcl.addClass(self.canvas, self.hoverClass); + else + jpcl.removeClass(self.canvas, self.hoverClass); + } + if (self.hoverPaintStyle != null) { + self.paintStyleInUse = hover ? self.hoverPaintStyle : self.paintStyle; + timestamp = timestamp || _timestamp(); + self.repaint({timestamp:timestamp, recalc:false}); + } + // get the list of other affected elements, if supported by this component. + // for a connection, its the endpoints. for an endpoint, its the connections! surprise. + if (self.getAttachedElements && !ignoreAttachedElements) + _updateAttachedElements(hover, _timestamp(), self); + } + }; + + this.isHover = function() { return _hover; }; + + var jpcl = jsPlumb.CurrentLibrary, + events = [ "click", "dblclick", "mouseenter", "mouseout", "mousemove", "mousedown", "mouseup" ], + eventFilters = { "mouseout":"mouseexit" }, + bindOne = function(o, c, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.bind(o, evt, function(ee) { + c.fire(filteredEvent, c, ee); + }); + }, + unbindOne = function(o, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.unbind(o, evt); + }; + + this.attachListeners = function(o, c) { + for (var i = 0; i < events.length; i++) { + bindOne(o, c, events[i]); + } + }; + + var _updateAttachedElements = function(state, timestamp, sourceElement) { + var affectedElements = self.getAttachedElements(); // implemented in subclasses + if (affectedElements) { + for (var i = 0; i < affectedElements.length; i++) { + if (!sourceElement || sourceElement != affectedElements[i]) + affectedElements[i].setHover(state, true, timestamp); // tell the attached elements not to inform their own attached elements. + } + } + }; + + this.reattachListenersForElement = function(o, c) { + for (var i = 0; i < events.length; i++) + unbindOne(o, events[i]); + self.attachListeners(o, c); + }; + }; + + var jsPlumbInstance = function(_defaults) { + + /* + * Property: Defaults + * + * These are the default settings for jsPlumb. They are what will be used if you do not supply specific pieces of information + * to the various API calls. A convenient way to implement your own look and feel can be to override these defaults + * by including a script somewhere after the jsPlumb include, but before you make any calls to jsPlumb. + * + * Properties: + * - *Anchor* The default anchor to use for all connections (both source and target). Default is "BottomCenter". + * - *Anchors* The default anchors to use ([source, target]) for all connections. Defaults are ["BottomCenter", "BottomCenter"]. + * - *Connector* The default connector definition to use for all connections. Default is "Bezier". + * - *Container* Optional selector or element id that instructs jsPlumb to append elements it creates to a specific element. + * - *DragOptions* The default drag options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *DropOptions* The default drop options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *Endpoint* The default endpoint definition to use for all connections (both source and target). Default is "Dot". + * - *Endpoints* The default endpoint definitions ([ source, target ]) to use for all connections. Defaults are ["Dot", "Dot"]. + * - *EndpointStyle* The default style definition to use for all endpoints. Default is fillStyle:"#456". + * - *EndpointStyles* The default style definitions ([ source, target ]) to use for all endpoints. Defaults are empty. + * - *EndpointHoverStyle* The default hover style definition to use for all endpoints. Default is null. + * - *EndpointHoverStyles* The default hover style definitions ([ source, target ]) to use for all endpoints. Defaults are null. + * - *HoverPaintStyle* The default hover style definition to use for all connections. Defaults are null. + * - *LabelStyle* The default style to use for label overlays on connections. + * - *LogEnabled* Whether or not the jsPlumb log is enabled. defaults to false. + * - *Overlays* The default overlay definitions. Defaults to an empty list. + * - *MaxConnections* The default maximum number of connections for an Endpoint. Defaults to 1. + * - *MouseEventsEnabled* Whether or not mouse events are enabled when using the canvas renderer. Defaults to true. + * The idea of this is just to give people a way to prevent all the mouse listeners from activating if they know they won't need mouse events. + * - *PaintStyle* The default paint style for a connection. Default is line width of 8 pixels, with color "#456". + * - *RenderMode* What mode to use to paint with. If you're on IE<9, you don't really get to choose this. You'll just get VML. Otherwise, the jsPlumb default is to use Canvas elements. + * - *Scope* The default "scope" to use for connections. Scope lets you assign connections to different categories. + */ + this.Defaults = { + Anchor : "BottomCenter", + Anchors : [ null, null ], + ConnectionsDetachable : true, + Connector : "Bezier", + Container : null, + DragOptions : { }, + DropOptions : { }, + Endpoint : "Dot", + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : "#456" }, + EndpointStyles : [ null, null ], + EndpointHoverStyle : null, + EndpointHoverStyles : [ null, null ], + HoverPaintStyle : null, + LabelStyle : { color : "black" }, + LogEnabled : false, + Overlays : [ ], + MaxConnections : 1, + MouseEventsEnabled : true, + PaintStyle : { lineWidth : 8, strokeStyle : "#456" }, + //Reattach:false, + RenderMode : "svg", + Scope : "jsPlumb_DefaultScope" + }; + if (_defaults) jsPlumb.extend(this.Defaults, _defaults); + + this.logEnabled = this.Defaults.LogEnabled; + + EventGenerator.apply(this); + var _bb = this.bind; + this.bind = function(event, fn) { + if ("ready" === event && initialized) fn(); + else _bb(event, fn); + }; + var _currentInstance = this, + log = null, + repaintFunction = function() { + jsPlumb.repaintEverything(); + }, + automaticRepaint = true, + repaintEverything = function() { + if (automaticRepaint) + repaintFunction(); + }, + resizeTimer = null, + initialized = false, + connectionsByScope = {}, + /** + * map of element id -> endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + endpointsByElement = {}, + endpointsByUUID = {}, + offsets = {}, + offsetTimestamps = {}, + floatingConnections = {}, + draggableStates = {}, + _mouseEventsEnabled = this.Defaults.MouseEventsEnabled, + canvasList = [], + sizes = [], + //listeners = {}, // a map: keys are event types, values are lists of listeners. + DEFAULT_SCOPE = this.Defaults.Scope, + renderMode = null, // will be set in init() + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }, + + /** + * appends an element to some other element, which is calculated as follows: + * + * 1. if _currentInstance.Defaults.Container exists, use that element. + * 2. if the 'parent' parameter exists, use that. + * 3. otherwise just use the document body. + * + */ + _appendElement = function(el, parent) { + if (_currentInstance.Defaults.Container) + jsPlumb.CurrentLibrary.appendElement(el, _currentInstance.Defaults.Container); + else if (!parent) + document.body.appendChild(el); + else + jsPlumb.CurrentLibrary.appendElement(el, parent); + }, + + _curIdStamp = 1, + _idstamp = function() { return "" + _curIdStamp++; }, + + /** + * YUI, for some reason, put the result of a Y.all call into an object that contains + * a '_nodes' array, instead of handing back an array-like object like the other + * libraries do. + */ + _convertYUICollection = function(c) { + return c._nodes ? c._nodes : c; + }, + + /** + * Draws an endpoint and its connections. this is the main entry point into drawing connections as well + * as endpoints, since jsPlumb is endpoint-centric under the hood. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + _draw = function(element, ui, timestamp) { + var id = _getAttribute(element, "id"); + _currentInstance.anchorManager.redraw(id, ui, timestamp); + }, + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }, + + /** + * gets an Endpoint by uuid. + */ + _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }, + + /** + * inits a draggable if it's not already initialised. + */ + _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? false : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element) && !jsPlumb.CurrentLibrary.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + draggableStates[_getId(element)] = true; + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options, false); + } + } + }, + + /* + * prepares a final params object that can be passed to _newConnection, taking into account defaults, events, etc. + */ + _prepareConnectionParams = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + // copy in any connectorOverlays that were specified on the source endpoint. + // it doesnt copy target endpoint overlays. i'm not sure if we want it to or not. + if (_p.sourceEndpoint && _p.sourceEndpoint.connectorOverlays) { + _p.overlays = _p.overlays || []; + for (var i = 0; i < _p.sourceEndpoint.connectorOverlays.length; i++) { + _p.overlays.push(_p.sourceEndpoint.connectorOverlays[i]); + } + } + + // tooltip. params.tooltip takes precedence, then sourceEndpoint.connectorTooltip. + _p.tooltip = params.tooltip; + if (!_p.tooltip && _p.sourceEndpoint && _p.sourceEndpoint.connectorTooltip) + _p.tooltip = _p.sourceEndpoint.connectorTooltip; + + if (_p.target && !_p.target.endpoint) { + var tid = _getId(_p.target), + tep =_targetEndpointDefinitions[tid]; + + var overrideOne = function(singlePropertyName, pluralPropertyName, tepProperty, tep) { + if (tep[tepProperty]) { + if (_p[pluralPropertyName]) _p[pluralPropertyName][1] = tep[tepProperty]; + else if (_p[singlePropertyName]) { + _p[pluralPropertyName] = [ _p[singlePropertyName], tep[tepProperty] ]; + _p[singlePropertyName] = null; + } + else _p[pluralPropertyName] = [ null, tep[tepProperty] ]; + } + }; + + if (tep) { + overrideOne("endpoint", "endpoints", "endpoint", tep); + overrideOne("endpointStyle", "endpointStyles", "paintStyle", tep); + overrideOne("endpointHoverStyle", "endpointHoverStyles", "hoverPaintStyle", tep); + } + } + + // dynamic anchors. backwards compatibility here: from 1.2.6 onwards you don't need to specify "dynamicAnchors". the fact that some anchor consists + // of multiple definitions is enough to tell jsPlumb you want it to be dynamic. + if (_p.dynamicAnchors) { + // these can either be an array of anchor coords, which we will use for both source and target, or an object with {source:[anchors], target:[anchors]}, in which + // case we will use a different set for each element. + var a = _p.dynamicAnchors.constructor == Array, + sourceId = _p.sourceEndpoint ? _p.sourceEndpoint.elementId : _getId(_p.source), + targetId = _p.targetEndpoint ? _p.targetEndpoint.elementId : _getId(_p.target), + sa = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors, sourceId, _currentInstance)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.source, sourceId, _currentInstance)), + ta = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors, targetId, _currentInstance)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.target, targetId, _currentInstance)); + _p.anchors = [sa,ta]; + } + return _p; + }, + + _newConnection = function(params) { + var connectionFunc = jsPlumb.Defaults.ConnectionType || jsPlumb.getDefaultConnectionType(), + endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint, + parent = jsPlumb.CurrentLibrary.getParent; + + if (params.container) + params["parent"] = params.container; + else { + if (params.sourceEndpoint) + params["parent"] = params.sourceEndpoint.parent; + else if (params.source.constructor == endpointFunc) + params["parent"] = params.source.parent; + else params["parent"] = parent(params.source); + } + + params["_jsPlumb"] = _currentInstance; + var con = new connectionFunc(params); + con.id = "con_" + _idstamp(); + _eventFireProxy("click", "click", con); + _eventFireProxy("dblclick", "dblclick", con); + return con; + }, + + /** + * adds the connection to the backing model, fires an event if necessary and then redraws + */ + _finaliseConnection = function(jpc, params) { + params = params || {}; + // add to list of connections (by scope). + if (!jpc.suspendedEndpoint) + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + if (!params.doNotFireConnectionEvent && params.fireEvent !== false) { + _currentInstance.fire("jsPlumbConnection", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + } + // always inform the anchor manager + // except that if jpc has a suspended endpoint it's not true to say the + // connection is new; it has just (possibly) moved. the question is whether + // to make that call here or in the anchor manager. i think perhaps here. + _currentInstance.anchorManager.newConnection(jpc); + // force a paint + _draw(jpc.source); + }, + + _eventFireProxy = function(event, proxyEvent, obj) { + obj.bind(event, function(originalObject, originalEvent) { + _currentInstance.fire(proxyEvent, obj, originalEvent); + }); + }, + + /** + * for the given endpoint params, returns an appropriate parent element for the UI elements that will be added. + * this function is used by _newEndpoint (directly below), and also in the makeSource function in jsPlumb. + * + * the logic is to first look for a "container" member of params, and pass that back if found. otherwise we + * handoff to the 'getParent' function in the current library. + */ + _getParentFromParams = function(params) { + if (params.container) + return params.container; + else { + var tag = jsPlumb.CurrentLibrary.getTagName(params.source), + p = jsPlumb.CurrentLibrary.getParent(params.source); + if (tag.toLowerCase() === "td") + return jsPlumb.CurrentLibrary.getParent(p); + else return p; + } + }, + + /** + factory method to prepare a new endpoint. this should always be used instead of creating Endpoints + manually, since this method attaches event listeners and an id. + */ + _newEndpoint = function(params) { + var endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint; + params.parent = _getParentFromParams(params); + params["_jsPlumb"] = _currentInstance; + var ep = new endpointFunc(params); + ep.id = "ep_" + _idstamp(); + _eventFireProxy("click", "endpointClick", ep); + _eventFireProxy("dblclick", "endpointDblClick", ep); + return ep; + }, + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + _operation = function(elId, func, endpointFunc) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + if (endpointFunc) endpointFunc(endpoints[i]); + } + } + }, + /** + * perform an operation on all elements. + */ + _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }, + + /** + * helper to remove an element from the DOM. + */ + _removeElement = function(element, parent) { + if (element != null && element.parentNode != null) { + element.parentNode.removeChild(element); + } + }, + /** + * helper to remove a list of elements from the DOM. + */ + _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }, + /** + * helper method to remove an item from a list. + */ + _removeFromList = function(map, key, value) { + if (key != null) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete (l[i]); + l.splice(i, 1); + return true; + } + } + } + return false; + }, + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }, + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + _setVisible = function(el, state, alsoChangeEndpoints) { + state = state === "block"; + var endpointFunc = null; + if (alsoChangeEndpoints) { + if (state) endpointFunc = function(ep) { + ep.setVisible(true, true, true); + }; + else endpointFunc = function(ep) { + ep.setVisible(false, true, true); + }; + } + var id = _getAttribute(el, "id"); + _operation(id, function(jpc) { + if (state && alsoChangeEndpoints) { + // this test is necessary because this functionality is new, and i wanted to maintain backwards compatibility. + // this block will only set a connection to be visible if the other endpoint in the connection is also visible. + var oidx = jpc.sourceId === id ? 1 : 0; + if (jpc.endpoints[oidx].isVisible()) jpc.setVisible(true); + } + else // the default behaviour for show, and what always happens for hide, is to just set the visibility without getting clever. + jpc.setVisible(state); + }, endpointFunc); + }, + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? false : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }, + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + _toggleVisible = function(elId, changeEndpoints) { + var endpointFunc = null; + if (changeEndpoints) { + endpointFunc = function(ep) { + var state = ep.isVisible(); + ep.setVisible(!state); + }; + } + _operation(elId, function(jpc) { + var state = jpc.isVisible(); + jpc.setVisible(!state); + }, endpointFunc); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }, + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return offsets[elId]; + } + if (recalc || !offset) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + if (sizes[elId] == null) { + var s = _getElementObject(elId); + if (s != null) + sizes[elId] = _getSize(s); + } + } + + if(offsets[elId] && !offsets[elId].right) { + offsets[elId].right = offsets[elId].left + sizes[elId][0]; + offsets[elId].bottom = offsets[elId].top + sizes[elId][1]; + offsets[elId].width = sizes[elId][0]; + offsets[elId].height = sizes[elId][1]; + offsets[elId].centerx = offsets[elId].left + (offsets[elId].width / 2); + offsets[elId].centery = offsets[elId].top + (offsets[elId].height / 2); + } + return offsets[elId]; + }, + + _getCachedData = function(elId) { + var o = offsets[elId]; + if (!o) o = _updateOffset(elId); + return {o:o, s:sizes[elId]}; + }, + +/** + * gets an id for the given element, creating and setting one if + * necessary. + */ + _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else + id = "jsPlumb_" + _idstamp(); + _setAttribute(ele, "id", id); + } + return id; + }, + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'jsPlumb function failed : ' + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'wrapped function failed : ' + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = "_jsPlumb_connector"; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = "_jsPlumb_endpoint"; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = "_jsPlumb_overlay"; + + this.Anchors = {}; + + this.Connectors = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Endpoints = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Overlays = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + +// ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***************************************** + /* + * Function: bind + * Bind to an event on jsPlumb. + * + * Parameters: + * event - the event to bind. Available events on jsPlumb are: + * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback. + * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback. + * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback. + * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback. + * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback. + * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback. + * + * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event. + */ + + /* + * Function: clearListeners + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + +// *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *********************************************************** + + /* + Function: addClass + + Helper method to abstract out differences in setting css classes on the different renderer types. + */ + this.addClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.addClass(el, clazz); + }; + + /* + Function: removeClass + + Helper method to abstract out differences in setting css classes on the different renderer types. + */ + this.removeClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.removeClass(el, clazz); + }; + + /* + Function: hasClass + + Helper method to abstract out differences in testing for css classes on the different renderer types. + */ + this.hasClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.hasClass(el, clazz); + }; + + /* + Function: addEndpoint + + Adds an to a given element or elements. + + Parameters: + + el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + params - Object containing Endpoint constructor arguments. For more information, see . + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some + shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in + this object are anything that 'params' can contain. See . + + Returns: + The newly created , if el referred to a single element. Otherwise, an array of newly created s. + + See Also: + + */ + this.addEndpoint = function(el, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + //p.scope="_jsPlumb_DefaultScope"; + //p.scope="fff"; + // YUI wrapper + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + var _el = _getElementObject(inputs[i]), id = _getId(_el); + p.source = _el; + //p.scope="f"; + _updateOffset({ elId : id }); + var e = _newEndpoint(p); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + results.push(e); + } + + return results.length == 1 ? results[0] : results; + }; + + /* + Function: addEndpoints + Adds a list of s to a given element or elements. + + Parameters: + target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See 's constructor documentation. + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created s, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(el, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams); + if (e.constructor == Array) + Array.prototype.push.apply(results, e); + else results.push(e); + } + return results; + }; + + /* + Function: animate + This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating + the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // onComplete repaints, just to make sure everything looks good at the end of the animation. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /** + * checks for a listener for the given condition, executing it if found, passing in the given value. + * condition listeners would have been attached using "bind" (which is, you could argue, now overloaded, since + * firing click events etc is a bit different to what this does). i thought about adding a "bindCondition" + * or something, but decided against it, for the sake of simplicity. jsPlumb will never fire one of these + * condition events anyway. + */ + this.checkCondition = function(conditionName, value) { + var l = _currentInstance.getListener(conditionName); + var r = true; + if (l && l.length > 0) { + try { + for (var i = 0 ; i < l.length; i++) { + r = r && l[i](value); + } + } + catch (e) { + _log(_currentInstance, "cannot check condition [" + conditionName + "]" + e); + } + } + return r; + }; + + /* + Function: connect + Establishes a between two elements (or s, which are themselves registered to elements). + + Parameters: + params - Object containing constructor arguments for the Connection. See 's constructor documentation. + referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of + Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection. + + Returns: + The newly created . + */ + this.connect = function(params, referenceParams) { + // prepare a final set of parameters to create connection with + var _p = _prepareConnectionParams(params, referenceParams); + // TODO probably a nicer return value if the connection was not made. _prepareConnectionParams + // will return null (and log something) if either endpoint was full. what would be nicer is to + // create a dedicated 'error' object. + if (_p) { + // a connect call will delete its created endpoints on detach, unless otherwise specified. + // this is because the endpoints belong to this connection only, and are no use to + // anyone else, so they hang around like a bad smell. + if (_p.deleteEndpointsOnDetach == null) + _p.deleteEndpointsOnDetach = true; + + // create the connection. it is not yet registered + var jpc = _newConnection(_p); + // now add it the model, fire an event, and redraw + _finaliseConnection(jpc, _p); + return jpc; + } + }; + + /* + Function: deleteEndpoint + Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too) + + Parameters: + object - either an object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElement(endpoint.canvas, endpoint.parent); + // remove from endpointsbyElement + // DEPRECATED. SHOULD NOT BE NECESSARY ONCE THE ANCHOR MANAGER IS WORKING PROPERLY. + _currentInstance.anchorManager.deleteEndpoint(endpoint); + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + delete endpoint; + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every , and their associated s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc, doFireEvent) { + // may have been given a connection, or in special cases, an object + var connType = jsPlumb.Defaults.ConnectionType || jsPlumb.getDefaultConnectionType(), + argIsConnection = jpc.constructor == connType, + params = argIsConnection ? { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + } : jpc; + + if (doFireEvent) _currentInstance.fire("jsPlumbConnectionDetached", params); + _currentInstance.anchorManager.connectionDetached(params); + }; + + /* + Function: detach + Detaches and then removes a . From 1.3.5 this method has been altered to remove support for + specifying Connections by various parameters; you can now pass in a Connection as the first argument and + an optional parameters object as a second argument. If you need the functionality this method provided + before 1.3.5 then you should use the getConnections method to get the list of Connections to detach, and + then iterate through them, calling this for each one. + + Parameters: + connection - the to detach + params - optional parameters to the detach call. valid values here are + fireEvent : defaults to false; indicates you want jsPlumb to fire a connection + detached event. The thinking behind this is that if you made a programmatic + call to detach an event, you probably don't need the callback. + forceDetach : defaults to false. allows you to override any beforeDetach listeners that may be registered. + + Returns: + true if successful, false if not. + */ + this.detach = function() { + + if (arguments.length == 0) return; + var connType = jsPlumb.Defaults.ConnectionType || jsPlumb.getDefaultConnectionType(), + firstArgIsConnection = arguments[0].constructor == connType, + params = arguments.length == 2 ? firstArgIsConnection ? (arguments[1] || {}) : arguments[0] : arguments[0], + fireEvent = (params.fireEvent !== false), + forceDetach = params.forceDetach, + connection = firstArgIsConnection ? arguments[0] : params.connection; + + if (connection) { + if (forceDetach || (connection.isDetachAllowed(connection) + && connection.endpoints[0].isDetachAllowed(connection) + && connection.endpoints[1].isDetachAllowed(connection))) { + if (forceDetach || _currentInstance.checkCondition("beforeDetach", connection)) + connection.endpoints[0].detach(connection, false, true, fireEvent); // TODO check this param iscorrect for endpoint's detach method + } + } + else { + var _p = jsPlumb.extend( {}, params); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1]), fireEvent); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source), + targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + if (_currentInstance.checkCondition("beforeDetach", jpc)) { + jpc.endpoints[0].detach(jpc, false, true, fireEvent); + } + } + }); + } + } + }; + + /* + Function: detachAllConnections + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + params - optional parameters. alowed values: + fireEvent : defaults to true, whether or not to fire the detach event. + + Returns: + void + */ + this.detachAllConnections = function(el, params) { + params = params || {}; + el = _getElementObject(el); + var id = _getAttribute(el, "id"), + endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + }; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Parameters: + params - optional params object containing: + fireEvent : whether or not to fire detach events. defaults to true. + + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function(params) { + params = params || {}; + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of y + our library's draggable method so that jsPlumb can setup the appropriate callbacks. Your + underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + // TODO it would be nice if this supported a selector string, instead of an id. + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method, + * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the + * default scope). If multiple scopes are passed in, the return value will be a map of { scope -> [ connection... ] }. + * + * Parameters + * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list + * of connections that are in the given scope. + * options - if the argument is a JS object, you can specify a finer-grained filter: + * + * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes. + * - *source* either a string representing an element id, or a selector. constrains the result to connections having this source. + * - *target* either a string representing an element id, or a selector. constrains the result to connections having this target. + * + */ + this.getConnections = function(options) { + if (!options) { + options = {}; + } else if (options.constructor == String) { + options = { "scope": options }; + } + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') + r.push(input); + else + r = input; + } + return r; + }; + var scope = options.scope || jsPlumb.getDefaultScope(), + scopes = prepareList(scope), + sources = prepareList(options.source), + targets = prepareList(options.target), + filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }, + results = scopes.length > 1 ? {} : [], + _addOne = function(scope, obj) { + if (scopes.length > 1) { + var ss = results[scope]; + if (ss == null) { + ss = []; results[scope] = ss; + } + ss.push(obj); + } else results.push(obj); + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + _addOne(i, c); + } + } + } + return results; + }; + + /* + * Function: getAllConnections + * Gets all connections, as a map of { scope -> [ connection... ] }. + */ + this.getAllConnections = function() { + return connectionsByScope; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /** + * Function:getEndpoints + * Gets the list of Endpoints for a given selector, or element id. + * @param el + * @return + */ + this.getEndpoints = function(el) { + return endpointsByElement[_getId(el)]; + }; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + this.getOffset = function(id) { + var o = offsets[id]; + return _updateOffset({elId:id}); + }; + + this.getSelector = function(spec) { + return jsPlumb.CurrentLibrary.getSelector(spec); + }; + + this.getSize = function(id) { + var s = sizes[id]; + if (!s) _updateOffset({elId:id}); + return sizes[id]; + }; + + this.appendElement = _appendElement; + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + changeEndpoints - whether not to also hide endpoints on the element. by default this is false. + + Returns: + void + */ + this.hide = function(el, changeEndpoints) { + _setVisible(el, "none", changeEndpoints); + }; + + // exposed for other objects to use to get a unique id. + this.idstamp = _idstamp; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + if (!initialized) { + _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run. + + var bindOne = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && _mouseEventsEnabled && renderMode == jsPlumb.CANVAS) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + var t = c[i].connector[event](e); + if (t) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i].endpoint[event](e)) return; + } + } + } + }); + }; + bindOne("click");bindOne("dblclick");bindOne("mousemove");bindOne("mousedown");bindOne("mouseup"); + + initialized = true; + _currentInstance.fire("ready"); + } + }; + + this.log = log; + this.jsPlumbUIComponent = jsPlumbUIComponent; + this.EventGenerator = EventGenerator; + + /* + * Creates an anchor with the given params. + * + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function() { + if (arguments.length == 0) return null; + var specimen = arguments[0], elementId = arguments[1], jsPlumbInstance = arguments[2], newAnchor = null; + if (!jsPlumbInstance) + throw "NO JSPLUMB SET"; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; //TODO hazy here about whether it should be added or is already added somehow. + // is it the name of an anchor type? + else if (typeof specimen == "string") { + newAnchor = _currentInstance.Anchors[arguments[0]]({elementId:elementId, jsPlumbInstance:_currentInstance}); + } + // is it an array? it will be one of: + // an array of [name, params] - this defines a single anchor + // an array of arrays - this defines some dynamic anchors + // an array of numbers - this defines a single anchor. + else if (specimen.constructor == Array) { + if (specimen[0].constructor == Array || specimen[0].constructor == String) { + if (specimen.length == 2 && specimen[0].constructor == String && specimen[1].constructor == Object) { + var pp = jsPlumb.extend({elementId:elementId, jsPlumbInstance:_currentInstance}, specimen[1]); + newAnchor = _currentInstance.Anchors[specimen[0]](pp); + } + else + newAnchor = new DynamicAnchor(specimen, null, elementId); + } + else { + var anchorParams = { + x:specimen[0], y:specimen[1], + orientation : (specimen.length >= 4) ? [ specimen[2], specimen[3] ] : [0,0], + offsets : (specimen.length == 6) ? [ specimen[4], specimen[5] ] : [ 0, 0 ], + elementId:elementId + }; + newAnchor = new Anchor(anchorParams); + newAnchor.clone = function() { return new Anchor(anchorParams); }; + } + } + + if (!newAnchor.id) newAnchor.id = "anchor_" + _idstamp(); + return newAnchor; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types, elementId, jsPlumbInstance) { + var r = []; + for ( var i = 0; i < types.length; i++) { + if (typeof types[i] == "string") + r.push(_currentInstance.Anchors[types[i]]({elementId:elementId, jsPlumbInstance:jsPlumbInstance})); + else if (types[i].constructor == Array) + r.push(jsPlumb.makeAnchor(types[i], elementId, jsPlumbInstance)); + } + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /** + * Function: makeTarget + * Makes some DOM element a Connection target, allowing you to drag connections to it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a target. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * scope optional. scope for the drop zone. + * dropOptions optional. same stuff as you would pass to dropOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to true. whether or not to delete + * any Endpoints created by a connection to this target if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + var _targetEndpointDefinitions = {}; + this.makeTarget = function(el, params, referenceParams) { + + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + var jpcl = jsPlumb.CurrentLibrary, + targetScope = p.scope || _currentInstance.Defaults.Scope, + deleteEndpointsOnDetach = p.deleteEndpointsOnDetach || true, + _doOne = function(_el) { + + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el); + _targetEndpointDefinitions[elid] = p.endpoint; + + var dropOptions = jsPlumb.extend({}, p.dropOptions || {}), + _drop = function() { + _currentInstance.currentlyDragging = false; + var draggable = _getElementObject(jpcl.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + // restore the original scope if necessary (issue 57) + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id], + source = jpc.endpoints[0], + _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : {}; + + // use defaults for endpoint style, if not present..this either uses EndpointStyle, or EndpointStyles[1], + // if it is present, since this is a target endpoint. + // TODO - this should be a helper method. makeTarget should use it too. give it an endpoint + _endpoint.paintStyle = _endpoint.paintStyle || + _currentInstance.Defaults.EndpointStyles[1] || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyles[1] || + jsPlumb.Defaults.EndpointStyle; + + _endpoint.hoverPaintStyle = _endpoint.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyles[1] || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyles[1] || + jsPlumb.Defaults.EndpointHoverStyle; + + _endpoint.anchor = _endpoint.anchor || + _currentInstance.Defaults.Anchors[1] || + _currentInstance.Defaults.Anchor || + jsPlumb.Defaults.Anchors[1] || + jsPlumb.Defaults.Anchor; + + // unlock the source anchor to allow it to refresh its position if necessary + source.anchor.locked = false; + + // if (!jpcl.hasClass(draggable, _currentInstance.endpointClass)) return; + + if (scope) jpcl.setDragScope(draggable, scope); + + // check if drop is allowed here. + var _continue = jpc.isDropAllowed(jpc.sourceId, _getId(_el), jpc.scope); + + // regardless of whether the connection is ok, reconfigure the existing connection to + // point at the current info. we need this to be correct for the detach event that will follow. + // clear the source endpoint from the list to detach. we will detach this connection at this + // point, but we want to keep the source endpoint. the target is a floating endpoint and should + // be removed. TODO need to figure out whether this code can result in endpoints kicking around + // when they shouldnt be. like is this a full detach of a connection? can it be? + if (jpc.endpointsToDeleteOnDetach) { + if (source === jpc.endpointsToDeleteOnDetach[0]) + jpc.endpointsToDeleteOnDetach[0] = null; + else if (source === jpc.endpointsToDeleteOnDetach[1]) + jpc.endpointsToDeleteOnDetach[1] = null; + } + // reinstate any suspended endpoint; this just puts the connection back into + // a state in which it will report sensible values if someone asks it about + // its target. we're going to throw this connection away shortly so it doesnt matter + // if we manipulate it a bit. + if (jpc.suspendedEndpoint) { + jpc.targetId = jpc.suspendedEndpoint.elementId; + jpc.target = jpcl.getElementObject(jpc.suspendedEndpoint.elementId); + jpc.endpoints[1] = jpc.suspendedEndpoint; + } + + if (_continue) { + + // detach this connection from the source. + source.detach(jpc, false, true, true);//source.endpointWillMoveAfterConnection); + + // make a new Endpoint for the target + var newEndpoint = jsPlumb.addEndpoint(_el, _endpoint); + + // if the anchor has a 'positionFinder' set, then delegate to that function to find + // out where to locate the anchor. + if (newEndpoint.anchor.positionFinder != null) { + var dropPosition = jpcl.getUIPosition(arguments), + elPosition = jpcl.getOffset(_el), + elSize = jpcl.getSize(_el), + ap = newEndpoint.anchor.positionFinder(dropPosition, elPosition, elSize, newEndpoint.anchor); + newEndpoint.anchor.x = ap[0]; + newEndpoint.anchor.y = ap[1]; + // now figure an orientation for it..kind of hard to know what to do actually. probably the best thing i can do is to + // support specifying an orientation in the anchor's spec. if one is not supplied then i will make the orientation + // be what will cause the most natural link to the source: it will be pointing at the source, but it needs to be + // specified in one axis only, and so how to make that choice? i think i will use whichever axis is the one in which + // the target is furthest away from the source. + } + var c = jsPlumb.connect({ + source:source, + target:newEndpoint, + scope:scope, + previousConnection:jpc, + container:jpc.parent, + // 'endpointWillMoveAfterConnection' is set by the makeSource function, and it indicates that the + // given endpoint will actually transfer from the element it is currently attached to to some other + // element after a connection has been established. in that case, we do not want to fire the + // connection event, since it will have the wrong data in it; makeSource will do it for us. + // this is controlled by the 'parent' parameter on a makeSource call. + doNotFireConnectionEvent:source.endpointWillMoveAfterConnection + }); + if (deleteEndpointsOnDetach) + c.endpointsToDeleteOnDetach = [ source, newEndpoint ]; + } + // if not allowed to drop... + else { + // TODO this code is identical (pretty much) to what happens when a connection + // dragged from a normal endpoint is in this situation. refactor. + // is this an existing connection, and will we reattach? + if (jpc.suspendedEndpoint) { + if (source.isReattach) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(source.elementId); + } + else + source.detach(jpc, false, true, true); // otherwise, detach the connection and tell everyone about it. + } + + } + }; + + var dropEvent = jpcl.dragEvents['drop']; + dropOptions["scope"] = dropOptions["scope"] || targetScope; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop); + + jpcl.initDroppable(_el, dropOptions, true); + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements drop targets. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeTargets = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeTarget(els[i], params, referenceParams); + } + }; + + /** + * Function: makeSource + * Makes some DOM element a Connection source, allowing you to drag connections from it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a source. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * parent optional. the element to add Endpoints to when a Connection is established. if you omit this, + * Endpoints will be added to 'el'. + * scope optional. scope for the connections dragged from this element. + * dragOptions optional. same stuff as you would pass to dragOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to false. whether or not to delete + * any Endpoints created by a connection from this source if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + var _sourceEndpointDefinitions = {}; + this.makeSource = function(el, params, referenceParams) { + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + var jpcl = jsPlumb.CurrentLibrary, + _doOne = function(_el) { + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el); + _sourceEndpointDefinitions[elid] = p.endpoint || {}; + var stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"], + dragOptions = jsPlumb.extend({ }, _sourceEndpointDefinitions[elid].dragOptions || {}), + ep = null, + endpointAddedButNoDragYet = false; + + // make sure this method honours whatever defaults have been set for Endpoint. + _sourceEndpointDefinitions[elid].endpoint = _sourceEndpointDefinitions[elid].endpoint || _currentInstance.Defaults.Endpoints[0] || _currentInstance.Defaults.Endpoint; + + // TODO this, and the makeTarget equivalent, and probably elsewhere, should all be handed off to + // some helper method that can make this decision for us. + _sourceEndpointDefinitions[elid].paintStyle = _sourceEndpointDefinitions[elid].paintStyle || + _currentInstance.Defaults.EndpointStyles[0] || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyles[0] || + jsPlumb.Defaults.EndpointStyle; + + _sourceEndpointDefinitions[elid].hoverPaintStyle = _sourceEndpointDefinitions[elid].hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyles[0] || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyles[0] || + jsPlumb.Defaults.EndpointHoverStyle; + + _sourceEndpointDefinitions[elid].anchor = _sourceEndpointDefinitions[elid].anchor || + _currentInstance.Defaults.Anchors[0] || + _currentInstance.Defaults.Anchor || + jsPlumb.Defaults.Anchors[0] || + jsPlumb.Defaults.Anchor; + + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + endpointAddedButNoDragYet = false; + }); + + dragOptions[stopEvent] = function() { + // here we need to do a couple of things; + // first determine whether or not a connection was dragged. if not, just delete this endpoint. + // ...if so, though, then we need to check to see if a 'parent' was specified in the + // options to makeSource. a 'parent' is a reference to an element other than the one from + // which the connection is dragged, and it indicates that after a successful connection, the + // endpoint should be moved off of this element and onto 'parent', using all of the + // options passed in to the makeSource call. + // + // one thing that occurs to me right now is that we dont really want the first + // connection to have fired a connection event. but how can we prevent it from doing so? + // + // + + //_currentlyDown = false; + _currentInstance.currentlyDragging = false; + + if (ep.connections.length == 0) + jsPlumb.deleteEndpoint(ep); + else { + + jpcl.unbind(ep.canvas, "mousedown"); + + // reset the anchor to the anchor that was initially provided. the one we were using to drag + // the connection was just a placeholder that was located at the place the user pressed the + // mouse button to initiate the drag. + var anchorDef = _sourceEndpointDefinitions[elid].anchor || _currentInstance.Defaults.Anchor; + ep.anchor = jsPlumb.makeAnchor(anchorDef, elid, _currentInstance); + + if (p.parent) { + var parent = jpcl.getElementObject(p.parent); + if (parent) { + var currentId = ep.elementId; + ep.setElement(parent); + ep.endpointWillMoveAfterConnection = false; + _currentInstance.anchorManager.rehomeEndpoint(currentId, parent); + var jpc = ep.connections[0]; // TODO will this always be correct? + _finaliseConnection(jpc); + _currentInstance.repaintEverything(); + } + } + _currentInstance.repaint(elid); + } + }; + // when the user presses the mouse, add an Endpoint + var mouseDownListener = function(e) { + // make sure we have the latest offset for this div + _updateOffset({elId:elid}); + // and get it, and the div's size + var myOffset = offsets[elid], myWH = sizes[elid]; + // if there is a parent, the endpoint will actually be added to it now, rather than the div + // that was the source. in that case, we have to adjust the anchor position so it refers to + // the parent. + if (p.parent) { + var pEl = jsPlumb.CurrentLibrary.getElementObject(p.parent), + pId = _getId(pEl); + _updateOffset({elId:pId}); + myOffset = offsets[pId]; + myWH = sizes[pId]; + } + + var x = ((e.pageX || e.page.x) - myOffset.left) / myWH[0], + y = ((e.pageY || e.page.y) - myOffset.top) / myWH[1]; + + // we need to override the anchor in here, and force 'isSource', but we don't want to mess with + // the params passed in, because after a connection is established we're going to reset the endpoint + // to have the anchor we were given. + var tempEndpointParams = {}; + jsPlumb.extend(tempEndpointParams, _sourceEndpointDefinitions[elid]); + tempEndpointParams.isSource = true; + tempEndpointParams.anchor = [x,y,0,0]; + tempEndpointParams.dragOptions = dragOptions; + // if a parent was given we need to turn that into a "container" argument. this is, by default, + // the parent of the element we will move to, so parent of p.parent in this case. however, if + // the user has specified a 'container' on the endpoint definition or on + // the defaults, we should use that. + if (p.parent) { + var potentialParent = tempEndpointParams.container || jsPlumb.Defaults.Container; + if (potentialParent) + tempEndpointParams.container = potentialParent; + else + tempEndpointParams.container = jsPlumb.CurrentLibrary.getParent(p.parent); + } + + ep = jsPlumb.addEndpoint(elid, tempEndpointParams); + + endpointAddedButNoDragYet = true; + // we set this to prevent connections from firing attach events before this function has had a chance + // to move the endpoint. + ep.endpointWillMoveAfterConnection = p.parent != null; + ep.endpointWillMoveTo = p.parent ? jpcl.getElementObject(p.parent) : null; + + var _delTempEndpoint = function() { + // this mouseup event is fired only if no dragging occurred, by jquery and yui, but for mootools + // it is fired even if dragging has occurred, in which case we would blow away a perfectly + // legitimate endpoint, were it not for this check. the flag is set after adding an + // endpoint and cleared in a drag listener we set in the dragOptions above. + if(endpointAddedButNoDragYet) { + jsPlumb.deleteEndpoint(ep); + } + }; + + _currentInstance.registerListener(ep.canvas, "mouseup", _delTempEndpoint); + _currentInstance.registerListener(_el, "mouseup", _delTempEndpoint); + + // and then trigger its mousedown event, which will kick off a drag, which will start dragging + // a new connection from this endpoint. + jpcl.trigger(ep.canvas, "mousedown", e); + }; + + // jpcl.bind(_el, "mousedown", mouseDownListener); + // register this on jsPlumb so that it can be cleared by a reset. + _currentInstance.registerListener(_el, "mousedown", mouseDownListener); + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements connection sources. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeSources = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeSource(els[i], params, referenceParams); + } + }; + + /* + Function: ready + Helper method to bind a function to jsPlumb's ready event. + */ + this.ready = function(fn) { + _currentInstance.bind("ready", fn); + }, + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"), + ebe = endpointsByElement[elId]; + if (ebe) { + for ( var i = 0; i < ebe.length; i++) + _currentInstance.deleteEndpoint(ebe[i]); + } + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + var _registeredListeners = {}, + _unbindRegisteredListeners = function() { + for (var i in _registeredListeners) { + for (var j = 0; j < _registeredListeners[i].length; j++) { + var info = _registeredListeners[i][j]; + jsPlumb.CurrentLibrary.unbind(info.el, info.event, info.listener); + } + } + _registeredListeners = {}; + }; + + // internal register listener method. gives us a hook to clean things up + // with if the user calls jsPlumb.reset. + this.registerListener = function(el, type, listener) { + jsPlumb.CurrentLibrary.bind(el, type, listener); + _addToList(_registeredListeners, type, {el:el, event:type, listener:listener}); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + this.deleteEveryEndpoint(); + this.clearListeners(); + _unbindRegisteredListeners(); + this.anchorManager.reset(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + + Parameters: + value - whether or not to automatically repaint when the window is resized. + + Returns: void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction + * Sets the function to fire when the window size has changed and a repaint was fired. + * + * Parameters: + * f - Function to execute. + * + * Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: setMouseEventsEnabled + * Sets whether or not mouse events are enabled. Default is true. + * + * Parameters: + * enabled - whether or not mouse events should be enabled. + * + * Returns: + * void + */ + this.setMouseEventsEnabled = function(enabled) { + _mouseEventsEnabled = enabled; + }; + + /* + * Constant for use with the setRenderMode method + */ + this.CANVAS = "canvas"; + + /* + * Constant for use with the setRenderMode method + */ + this.SVG = "svg"; + + this.VML = "vml"; + + /* + * Function: setRenderMode + * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that + * what you asked for is not supported (and that VML is). If you asked for VML but the browser does + * not support it, jsPlumb uses SVG. + * + * Returns: + * the render mode that jsPlumb set, which of course may be different from that requested. + */ + this.setRenderMode = function(mode) { + if (mode) + mode = mode.toLowerCase(); + else + return; + if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); + // now test we actually have the capability to do this. + if (mode === jsPlumb.CANVAS && canvasAvailable) + renderMode = jsPlumb.CANVAS; + else if (mode === jsPlumb.SVG && svgAvailable) + renderMode = jsPlumb.SVG; + else if (vmlAvailable) + renderMode = jsPlumb.VML; + + return renderMode; + }; + + this.getRenderMode = function() { return renderMode; }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * changeEndpoints - whether or not to also change the visible state of the endpoints on the element. this also has a bearing on + * other connections on those endpoints: if their other endpoint is also visible, the connections are made visible. + * + * Returns: + * void + */ + this.show = function(el, changeEndpoints) { + _setVisible(el, "block", changeEndpoints); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId, + makeAnchor:self.makeAnchor, + makeDynamicAnchor:self.makeDynamicAnchor + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * changeEndpoints - whether or not to also toggle the endpoints on the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload + * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element. + * + * Returns: + * void + */ + this.unload = function() { + delete endpointsByElement; + delete endpointsByUUID; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + delete canvasList; + }; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + var adjustForParentOffsetAndScroll = function(xy, el) { + var offsetParent = null, result = xy; + if (el.tagName.toLowerCase() === "svg" && el.parentNode) { + offsetParent = el.parentNode; + } + else if (el.offsetParent) { + offsetParent = el.offsetParent; + } + if (offsetParent != null) { + var po = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(offsetParent), + so = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : {left:offsetParent.scrollLeft, top:offsetParent.scrollTop}; + + // i thought it might be cool to do this: + // lastReturnValue[0] = lastReturnValue[0] - offsetParent.offsetLeft + offsetParent.scrollLeft; + // lastReturnValue[1] = lastReturnValue[1] - offsetParent.offsetTop + offsetParent.scrollTop; + // but i think it ignores margins. my reasoning was that it's quicker to not hand off to some underlying + // library. + + result[0] = xy[0] - po.left + so.left; + result[1] = xy[1] - po.top + so.top; + } + + return result; + }; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + this.elementId = params.elementId; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + + if (timestamp && timestamp === self.timestamp) + return lastReturnValue; + + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + + // adjust loc if there is an offsetParent + lastReturnValue = adjustForParentOffsetAndScroll(lastReturnValue, element.canvas); + + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + * + * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference, + // the canvas this refers to. + refCanvas = params.referenceCanvas, + size = _getSize(_getElementObject(refCanvas)), + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + xDir = 0, yDir = 0, + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + orientation = null, + _lastResult = null; + + this.compute = function(params) { + var xy = params.xy, element = params.element, + result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + + // adjust loc if there is an offsetParent + result = adjustForParentOffsetAndScroll(result, element.canvas); + + _lastResult = result; + return result; + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { orientation = anchor.getOrientation(); }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchor is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector, elementId) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = [], + _convert = function(anchor) { return anchor.constructor == Anchor ? anchor: jsPlumb.makeAnchor(anchor, elementId, _currentInstance); }; + for (var i = 0; i < anchors.length; i++) _anchors[i] = _convert(anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + this.locked = false; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null, + _curIndex = _anchors.length > 0 ? 0 : -1, + self = this, + + // helper method to calculate the distance between the centers of the two elements. + _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }, + + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + + return _curAnchor.compute(params); + }; + + this.getCurrentLocation = function() { + return _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + }; + + this.getOrientation = function() { return _curAnchor != null ? _curAnchor.getOrientation() : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + manages anchors for all elements. + */ + // "continuous" anchors: anchors that pick their location based on how many connections the given element has. + // this requires looking at a lot more elements than normal - anything that has had a Continuous anchor applied has + // to be recalculated. so this manager is used as a reference point. the first time, with a new timestamp, that + // a continuous anchor is asked to compute, it calls this guy. or maybe, even, this guy gets called somewhere else + // and compute only ever returns pre-computed values. either way, this is the central point, and we want it to + // be called as few times as possible. + var continuousAnchors = {}, + continuousAnchorLocations = {}, + continuousAnchorOrientations = {}, + Orientation = { HORIZONTAL : "horizontal", VERTICAL : "vertical", DIAGONAL : "diagonal", IDENTITY:"identity" }, + + // TODO this functions uses a crude method of determining orientation between two elements. + // 'diagonal' should be chosen when the angle of the line between the two centers is around + // one of 45, 135, 225 and 315 degrees. maybe +- 15 degrees. + calculateOrientation = function(sourceId, targetId, sd, td) { + + if (sourceId === targetId) return { + orientation:Orientation.IDENTITY, + a:["top", "top"] + }; + + var theta = Math.atan2((td.centery - sd.centery) , (td.centerx - sd.centerx)), + theta2 = Math.atan2((sd.centery - td.centery) , (sd.centerx - td.centerx)), + h = ((sd.left <= td.left && sd.right >= td.left) || (sd.left <= td.right && sd.right >= td.right) || + (sd.left <= td.left && sd.right >= td.right) || (td.left <= sd.left && td.right >= sd.right)), + v = ((sd.top <= td.top && sd.bottom >= td.top) || (sd.top <= td.bottom && sd.bottom >= td.bottom) || + (sd.top <= td.top && sd.bottom >= td.bottom) || (td.top <= sd.top && td.bottom >= sd.bottom)); + + if (! (h || v)) { + var a = null, rls = false, rrs = false, sortValue = null; + if (td.left > sd.left && td.top > sd.top) + a = ["right", "top"]; + else if (td.left > sd.left && sd.top > td.top) + a = [ "top", "left"]; + else if (td.left < sd.left && td.top < sd.top) + a = [ "top", "right"]; + else if (td.left < sd.left && td.top > sd.top) + a = ["left", "top" ]; + + return { orientation:Orientation.DIAGONAL, a:a, theta:theta, theta2:theta2 }; + } + else if (h) return { + orientation:Orientation.HORIZONTAL, + a:sd.top < td.top ? ["bottom", "top"] : ["top", "bottom"], + theta:theta, theta2:theta2 + } + else return { + orientation:Orientation.VERTICAL, + a:sd.left < td.left ? ["right", "left"] : ["left", "right"], + theta:theta, theta2:theta2 + } + }, + placeAnchorsOnLine = function(desc, elementDimensions, elementPosition, + connections, horizontal, otherMultiplier, reverse) { + var a = [], step = elementDimensions[horizontal ? 0 : 1] / (connections.length + 1); + + for (var i = 0; i < connections.length; i++) { + var val = (i + 1) * step, other = otherMultiplier * elementDimensions[horizontal ? 1 : 0]; + if (reverse) + val = elementDimensions[horizontal ? 0 : 1] - val; + + var dx = (horizontal ? val : other), x = elementPosition[0] + dx, xp = dx / elementDimensions[0], + dy = (horizontal ? other : val), y = elementPosition[1] + dy, yp = dy / elementDimensions[1]; + + a.push([ x, y, xp, yp, connections[i][1], connections[i][2] ]); + } + + return a; + }, + standardEdgeSort = function(a, b) { return a[0] > b[0] ? 1 : -1 }, + currySort = function(reverseAngles) { + return function(a,b) { + var r = true; + if (reverseAngles) { + if (a[0][0] < b[0][0]) + r = true; + else + r = a[0][1] > b[0][1]; + } + else { + if (a[0][0] > b[0][0]) + r= true; + else + r =a[0][1] > b[0][1]; + } + return r === false ? -1 : 1; + }; + }, + leftSort = function(a,b) { + // first get adjusted values + var p1 = a[0][0] < 0 ? -Math.PI - a[0][0] : Math.PI - a[0][0], + p2 = b[0][0] < 0 ? -Math.PI - b[0][0] : Math.PI - b[0][0]; + if (p1 > p2) return 1; + else return a[0][1] > b[0][1] ? 1 : -1; + }, + edgeSortFunctions = { + "top":standardEdgeSort, + "right":currySort(true), + "bottom":currySort(true), + "left":leftSort + }, + _sortHelper = function(_array, _fn) { + return _array.sort(_fn); + }, + placeAnchors = function(elementId, _anchorLists) { + var sS = sizes[elementId], sO = offsets[elementId], + placeSomeAnchors = function(desc, elementDimensions, elementPosition, unsortedConnections, isHorizontal, otherMultiplier) { + if (unsortedConnections.length > 0) { + var sc = _sortHelper(unsortedConnections, edgeSortFunctions[desc]), // puts them in order based on the target element's pos on screen + //sc = unsortedConnections.sort(edgeSortFunctions[desc]), // puts them in order based on the target element's pos on screen + reverse = desc === "right" || desc === "top", + anchors = placeAnchorsOnLine(desc, elementDimensions, + elementPosition, sc, + isHorizontal, otherMultiplier, reverse ); + + // takes a computed anchor position and adjusts it for parent offset and scroll, then stores it. + var _setAnchorLocation = function(endpoint, anchorPos) { + var a = adjustForParentOffsetAndScroll([anchorPos[0], anchorPos[1]], endpoint.canvas); + continuousAnchorLocations[endpoint.id] = [ a[0], a[1], anchorPos[2], anchorPos[3] ]; + }; + + for (var i = 0; i < anchors.length; i++) { + var c = anchors[i][4], weAreSource = c.endpoints[0].elementId === elementId, weAreTarget = c.endpoints[1].elementId === elementId; + if (weAreSource) + _setAnchorLocation(c.endpoints[0], anchors[i]); + else if (weAreTarget) + _setAnchorLocation(c.endpoints[1], anchors[i]); + } + } + }; + + placeSomeAnchors("bottom", sS, [sO.left,sO.top], _anchorLists.bottom, true, 1); + placeSomeAnchors("top", sS, [sO.left,sO.top], _anchorLists.top, true, 0); + placeSomeAnchors("left", sS, [sO.left,sO.top], _anchorLists.left, false, 0); + placeSomeAnchors("right", sS, [sO.left,sO.top], _anchorLists.right, false, 1); + }, + AnchorManager = function() { + var _amEndpoints = {}, + endpointConnectionsByElementId = {}, + continuousAnchorConnectionsByElementId = {}, + continuousAnchorEndpoints = [], + self = this, + anchorLists = {}; + + this.reset = function() { + endpointConnectionsByElementId = {}; + continuousAnchorConnectionsByElementId = {}; + continuousAnchorEndpoints = []; + anchorLists = {}; + }; + + this.newConnection = function(conn) { + var sourceId = conn.sourceId, targetId = conn.targetId, + ep = conn.endpoints, + doRegisterTarget = true, + registerConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + if (otherAnchor.constructor == DynamicAnchor || otherAnchor.constructor == Anchor) { + _addToList(endpointConnectionsByElementId, elId, [c, otherEndpoint, otherAnchor.constructor == DynamicAnchor]); + } + else { + // continuous. if they are the same element, just assign the same anchor + // to both. + if (sourceId == targetId) { + // remove the target endpoint's canvas. we dont need it. + jsPlumb.CurrentLibrary.removeElement(ep[1].canvas); + doRegisterTarget = false; + } + _addToList(continuousAnchorConnectionsByElementId, elId, c); + } + }; + registerConnection(0, ep[0], ep[0].anchor, targetId, conn); + if (doRegisterTarget) + registerConnection(1, ep[1], ep[1].anchor, sourceId, conn); + }; + this.connectionDetached = function(connInfo) { + var sourceId = connInfo.sourceId, + targetId = connInfo.targetId, + ep = connInfo.connection.endpoints, + removeConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + if (otherAnchor.constructor == FloatingAnchor) { + // no-op + } + else if (otherAnchor.constructor == DynamicAnchor || otherAnchor.constructor == Anchor) { + var _conns = endpointConnectionsByElementId[elId]; + if (_conns) { + _removeWithFunction(_conns, function(e) { return e[0].id == c.id; }); + } + } + else // continuous. + _removeFromList(continuousAnchorConnectionsByElementId, elId, c); + }; + + removeConnection(1, ep[1], ep[1].anchor, sourceId, connInfo.connection); + removeConnection(0, ep[0], ep[0].anchor, targetId, connInfo.connection); + + // remove from anchorLists + var sEl = connInfo.connection.sourceId, + tEl = connInfo.connection.targetId, + sE = connInfo.connection.endpoints[0].id, + tE = connInfo.connection.endpoints[1].id, + _remove = function(list, eId) { + if (list) { // transient anchors dont get entries in this list. + var f = function(e) { return e[4] == eId; }; + _removeWithFunction(list["top"], f); + _removeWithFunction(list["left"], f); + _removeWithFunction(list["bottom"], f); + _removeWithFunction(list["right"], f); + } + }; + + _remove(anchorLists[sEl], sE); + _remove(anchorLists[tEl], tE); + self.redraw(sEl); + self.redraw(tEl); + }; + this.add = function(endpoint, elementId) { + _addToList(_amEndpoints, elementId, endpoint); + }; + this.get = function(elementId) { + return { + "standard":endpointConnectionsByElementId[elementId] || [], + "continuous":continuousAnchorConnectionsByElementId[elementId] || [], + "endpoints":_amEndpoints[elementId], + "continuousAnchorEndpoints":continuousAnchorEndpoints + }; + }; + this.deleteEndpoint = function(endpoint) { + var cIdx = _findIndex(continuousAnchorEndpoints, endpoint); + if (cIdx > -1) + continuousAnchorEndpoints.splice(cIdx, 1); + else + _removeFromList(_amEndpoints, endpoint.elementId, endpoint); + }; + this.clearFor = function(elementId) { + delete _amEndpoints[elementId]; + _amEndpoints[elementId] = []; + }; + // updates the given anchor list by either updating an existing anchor's info, or adding it. this function + // also removes the anchor from its previous list, if the edge it is on has changed. + // all connections found along the way (those that are connected to one of the faces this function + // operates on) are added to the connsToPaint list, as are their endpoints. in this way we know to repaint + // them wthout having to calculate anything else about them. + var _updateAnchorList = function(lists, theta, order, conn, aBoolean, otherElId, idx, reverse, edgeId, elId, connsToPaint, endpointsToPaint) { + // first try to find the exact match, but keep track of the first index of a matching element id along the way.s + var exactIdx = -1, + firstMatchingElIdx = -1, + endpoint = conn.endpoints[idx], + endpointId = endpoint.id, + oIdx = [1,0][idx], + values = [ [ theta, order ], conn, aBoolean, otherElId, endpointId ], + listToAddTo = lists[edgeId], + listToRemoveFrom = endpoint._continuousAnchorEdge ? lists[endpoint._continuousAnchorEdge] : null; + + if (listToRemoveFrom) { + var rIdx = _findWithFunction(listToRemoveFrom, function(e) { return e[4] == endpointId }); + if (rIdx != -1) { + listToRemoveFrom.splice(rIdx, 1); + // get all connections from this list + for (var i = 0; i < listToRemoveFrom.length; i++) { + connsToPaint.push(listToRemoveFrom[i][1]); + endpointsToPaint.push(listToRemoveFrom[i][1].endpoints[idx]); + } + } + } + + for (var i = 0; i < listToAddTo.length; i++) { + if (idx == 1 && listToAddTo[i][3] === otherElId && firstMatchingElIdx == -1) + firstMatchingElIdx = i; + connsToPaint.push(listToAddTo[i][1]); + endpointsToPaint.push(listToAddTo[i][1].endpoints[idx]); + } + if (exactIdx != -1) { + listToAddTo[exactIdx] = values; + } + else { + var insertIdx = reverse ? firstMatchingElIdx != -1 ? firstMatchingElIdx : 0 : listToAddTo.length; // of course we will get this from having looked through the array shortly. + listToAddTo.splice(insertIdx, 0, values); + } + + // store this for next time. + endpoint._continuousAnchorEdge = edgeId; + }; + this.redraw = function(elementId, ui, timestamp) { + // get all the endpoints for this element + var ep = _amEndpoints[elementId] || [], + endpointConnections = endpointConnectionsByElementId[elementId] || [], + continuousAnchorConnections = continuousAnchorConnectionsByElementId[elementId] || [], + connectionsToPaint = [], + endpointsToPaint = []; + + timestamp = timestamp || _timestamp(); + + _updateOffset( { elId : elementId, offset : ui, recalc : false, timestamp : timestamp }); + // valid for one paint cycle. + var myOffset = offsets[elementId], + myWH = sizes[elementId], + orientationCache = {}; + + // actually, first we should compute the orientation of this element to all other elements to which + // this element is connected with a continuous anchor (whether both ends of the connection have + // a continuous anchor or just one) + for (var i = 0; i < continuousAnchorConnections.length; i++) { + var conn = continuousAnchorConnections[i], + sourceId = conn.sourceId, + targetId = conn.targetId, + oKey = sourceId + "_" + targetId, + oKey2 = targetId + "_" + sourceId, + o = orientationCache[oKey], + td = _getCachedData(targetId), + sd = _getCachedData(sourceId), + oIdx = conn.sourceId == elementId ? 1 : 0; + + if (!anchorLists[sourceId]) anchorLists[sourceId] = { top:[], right:[], bottom:[], left:[] }; + if (!anchorLists[targetId]) anchorLists[targetId] = { top:[], right:[], bottom:[], left:[] }; + + if (targetId == sourceId) { + // here we may want to improve this by somehow determining the face we'd like + // to put the connector on. ideally, when drawing, the face should be calculated + // by determining which face is closest to the point at which the mouse button + // was released. for now, we're putting it on the top face. + _updateAnchorList(anchorLists[sourceId], -Math.PI / 2, 0, conn, false, targetId, 0, false, "top", sourceId, connectionsToPaint, endpointsToPaint) + } + else { + if (!o) { + o = calculateOrientation(sourceId, targetId, sd.o, td.o); + orientationCache[oKey] = o; + // this would be a performance enhancement, but the computed angles need to be clamped to + //the (-PI/2 -> PI/2) range in order for the sorting to work properly. + /* orientationCache[oKey2] = { + orientation:o.orientation, + a:[o.a[1], o.a[0]], + theta:o.theta + Math.PI, + theta2:o.theta2 + Math.PI + };*/ + } + _updateAnchorList(anchorLists[sourceId], o.theta, 0, conn, false, targetId, 0, false, o.a[0], sourceId, connectionsToPaint, endpointsToPaint); + _updateAnchorList(anchorLists[targetId], o.theta2, -1, conn, true, sourceId, 1, true, o.a[1], targetId, connectionsToPaint, endpointsToPaint); + } + + connectionsToPaint.push(conn); + endpointsToPaint.push(conn.endpoints[oIdx]); + } + + // now place all the continuous anchors; + for (var anElement in anchorLists) { + placeAnchors(anElement, anchorLists[anElement]); + } + + // now that continuous anchors have been placed, paint all the endpoints for this element + for (var i = 0; i < ep.length; i++) { + ep[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + // ... and any other endpoints we came across as a result of the continuous anchors. + for (var i = 0; i < endpointsToPaint.length; i++) { + endpointsToPaint[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + + // paint all the standard and "dynamic connections", which are connections whose other anchor is + // static and therefore does need to be recomputed; we make sure that happens only one time. + for (var i = 0; i < endpointConnections.length; i++) { + var otherEndpoint = endpointConnections[i][1]; + if (otherEndpoint.anchor.constructor == DynamicAnchor) { + // _updateOffset( { elId : otherEndpoint.elementId, timestamp : timestamp }); + otherEndpoint.paint({ elementWithPrecedence:elementId }); + connectionsToPaint.push(endpointConnections[i][0]); + // all the connections for the other endpoint now need to be repainted + for (var k = 0; k < otherEndpoint.connections.length; k++) { + if (otherEndpoint.connections[k] !== endpointConnections[i][0]) + connectionsToPaint.push(otherEndpoint.connections[k]); + } + } else if (otherEndpoint.anchor.constructor == Anchor) { + connectionsToPaint.push(endpointConnections[i][0]); + } + } + // paint current floating connection for this element, if there is one. + var fc = floatingConnections[elementId]; + if (fc) + fc.paint({timestamp:timestamp, recalc:false, elId:elementId}); + + // paint all the connections + for (var i = 0; i < connectionsToPaint.length; i++) { + connectionsToPaint[i].paint({elId:elementId, timestamp:timestamp, recalc:false}); + } + }; + this.rehomeEndpoint = function(currentId, element) { + var eps = _amEndpoints[currentId] || [], //, + elementId = _currentInstance.getId(element); + for (var i = 0; i < eps.length; i++) { + self.add(eps[i], elementId); + } + eps.splice(0, eps.length); + }; + }; + _currentInstance.anchorManager = new AnchorManager(); + //_currentInstance.bind("jsPlumbConnection", _currentInstance.anchorManager.connectionListener); + //_currentInstance.bind("jsPlumbConnectionDetached", _currentInstance.anchorManager.connectionDetachedListener); + + _currentInstance.continuousAnchorFactory = { + get:function(params) { + var existing = continuousAnchors[params.elementId]; + if (!existing) { + existing = { + type:"Continuous", + compute : function(params) { + return continuousAnchorLocations[params.element.id] || [0,0]; + }, + getCurrentLocation : function(endpoint) { + return continuousAnchorLocations[endpoint.id] || [0,0]; + }, + getOrientation : function(endpoint) { + return continuousAnchorOrientations[endpoint.id] || [0,0]; + }, + isDynamic : true, + isContinuous : true + }; + continuousAnchors[params.elementId] = existing; + } + return existing; + } + }; + + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * container - optional id or selector instructing jsPlumb where to attach all the elements it creates for this connection. you should read the documentation for a full discussion of this. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * overlays - Optional array of Overlay definitions to appear on this Connection. + * drawEndpoints - if false, instructs jsPlumb to not draw the endpoints for this Connection. Be careful with this: it only really works when you tell jsPlumb to attach elements to the document body. Read the documentation for a full discussion of this. + */ + var Connection = function(params) { + var self = this, visible = true; + self.idPrefix = "_jsplumb_c_"; + jsPlumbUIComponent.apply(this, arguments); + // ************** get the source and target and register the connection. ******************* + + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none"; + }; + this.parent = params.parent; + /** + Property: source + The source element for this Connection. + */ + this.source = _getElementObject(params.source); + /** + Property:target + The target element for this Connection. + */ + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. but + // source is not overridden if the Endpoint has declared it is not the final target of a connection; + // instead we use the source that the Endpoint declares will be the final source element. + if (params.sourceEndpoint) { + this.source = params.sourceEndpoint.endpointWillMoveTo || params.sourceEndpoint.getElement(); + } + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + + // if a new connection is the result of moving some existing connection, params.previousConnection + // will have that Connection in it. listeners for the jsPlumbConnection event can look for that + // member and take action if they need to. + self.previousConnection = params.previousConnection; + + /* + * Property: sourceId + * Id of the source element in the connection. + */ + this.sourceId = _getAttribute(this.source, "id"); + /* + * Property: targetId + * Id of the target element in the connection. + */ + this.targetId = _getAttribute(this.target, "id"); + + /** + * implementation of abstract method in EventGenerator + * @return list of attached elements. in our case, a list of Endpoints. + */ + this.getAttachedElements = function() { + return self.endpoints; + }; + + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams, elementId) { + if (anchorParams) + return jsPlumb.makeAnchor(anchorParams, elementId, _currentInstance); + }, + prepareEndpoint = function(existing, index, params, element, elementId, connectorPaintStyle, connectorHoverPaintStyle) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] + || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint; + + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified. + if (es.fillStyle == null && connectorPaintStyle != null) + es.fillStyle = connectorPaintStyle.strokeStyle; + + // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does: + //* + if (es.outlineColor == null && connectorPaintStyle != null) + es.outlineColor = connectorPaintStyle.outlineColor; + if (es.outlineWidth == null && connectorPaintStyle != null) + es.outlineWidth = connectorPaintStyle.outlineWidth; + //*/ + + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure? + if (connectorHoverPaintStyle != null) { + if (ehs == null) ehs = {}; + if (ehs.fillStyle == null) { + ehs.fillStyle = connectorHoverPaintStyle.strokeStyle; + } + } + var a = params.anchors ? params.anchors[index] : + params.anchor ? params.anchor : + _makeAnchor(_currentInstance.Defaults.Anchors[index], elementId) || + _makeAnchor(jsPlumb.Defaults.Anchors[index], elementId) || + _makeAnchor(_currentInstance.Defaults.Anchor, elementId) || + _makeAnchor(jsPlumb.Defaults.Anchor, elementId), + u = params.uuids ? params.uuids[index] : null, + e = _newEndpoint({ + paintStyle : es, + hoverPaintStyle:ehs, + endpoint : ep, + connections : [ self ], + uuid : u, + anchor : a, + source : element, + container:params.container, + reattach:params.reattach, + detachable:params.detachable + }); + self.endpoints[index] = e; + + + if (params.drawEndpoints === false) e.setVisible(false, true, true); + + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, + 0, + params, + self.source, + self.sourceId, + params.paintStyle, + params.hoverPaintStyle); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + + // if there were no endpoints supplied and the source element is the target element, we will reuse the source + // endpoint that was just created. + var existingTargetEndpoint = ((self.sourceId == self.targetId) && params.targetEndpoint == null) ? eS : params.targetEndpoint, + eT = prepareEndpoint(existingTargetEndpoint, + 1, + params, + self.target, + self.targetId, + params.paintStyle, + params.hoverPaintStyle); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + // if delete endpoints on detach, keep a record of just exactly which endpoints they are. + if (params.deleteEndpointsOnDetach) + self.endpointsToDeleteOnDetach = [eS, eT]; + + var _detachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.detachable === false) _detachable = false; + if(self.endpoints[0].connectionsDetachable === false) _detachable = false; + if(self.endpoints[1].connectionsDetachable === false) _detachable = false; + + /* + Function: isDetachable + Returns whether or not this connection can be detached from its target/source endpoint. by default this + is false; use it in conjunction with the 'reattach' parameter. + */ + this.isDetachable = function() { + return _detachable === true; + }; + + /* + Function: setDetachable + Sets whether or not this connection is detachable. + */ + this.setDetachable = function(detachable) { + _detachable = detachable === true; + }; + + // merge all the parameters objects into the connection. parameters set + // on the connection take precedence; then target endpoint params, then + // finally source endpoint params. + var _p = jsPlumb.CurrentLibrary.extend({}, this.endpoints[0].getParameters()); + jsPlumb.CurrentLibrary.extend(_p, this.endpoints[1].getParameters()); + jsPlumb.CurrentLibrary.extend(_p, self.getParameters()); + self.setParameters(_p); + + var _bindConnectorEvents = function() { + // add mouse events + self.connector.bind("click", function(con, e) { + self.fire("click", self, e); + }); + self.connector.bind("dblclick", function(con, e) { + self.fire("dblclick", self, e); }); + self.connector.bind("mouseenter", function(con, e) { + if (!self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(true, false); + } + self.fire("mouseenter", self, e); + } + }); + self.connector.bind("mouseexit", function(con, e) { + if (self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(false, false); + } + self.fire("mouseexit", self, e); + } + }); + + }; + + /* + * Function: setConnector + * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method - the same + * thing that you would set as the 'connector' property on a jsPlumb.connect call. + * + * Parameters: + * connector - Connector definition + */ + this.setConnector = function(connector, doNotRepaint) { + if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent); + var connectorArgs = { + _jsPlumb:self._jsPlumb, + parent:params.parent, + cssClass:params.cssClass, + container:params.container, + tooltip:self.tooltip + }; + if (connector.constructor == String) + this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand. + else if (connector.constructor == Array) + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs)); + self.canvas = self.connector.canvas; + _bindConnectorEvents(); + if (!doNotRepaint) self.repaint(); + }; + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc) + */ + + self.setConnector(this.endpoints[0].connector || + this.endpoints[1].connector || + params.connector || + _currentInstance.Defaults.Connector || + jsPlumb.Defaults.Connector, true); + + + // override setHover to pass it down to the underlying connector + var _sh = self.setHover; + self.setHover = function() { + self.connector.setHover.apply(self.connector, arguments); + _sh.apply(self, arguments); + }; + + this.setPaintStyle(this.endpoints[0].connectorStyle || + this.endpoints[1].connectorStyle || + params.paintStyle || + _currentInstance.Defaults.PaintStyle || + jsPlumb.Defaults.PaintStyle, true); + + this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || + this.endpoints[1].connectorHoverStyle || + params.hoverPaintStyle || + _currentInstance.Defaults.HoverPaintStyle || + jsPlumb.Defaults.HoverPaintStyle, true); + + this.paintStyleInUse = this.paintStyle; + + /* + * Property: overlays + * List of Overlays for this Connection. + */ + this.overlays = []; + var processOverlay = function(o) { + var _newOverlay = null; + if (o.constructor == Array) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0], + // make a copy of the object so as not to mess up anyone else's reference... + p = jsPlumb.CurrentLibrary.extend({connection:self, _jsPlumb:_currentInstance}, o[1]); + if (o.length == 3) jsPlumb.CurrentLibrary.extend(p, o[2]); + _newOverlay = new jsPlumb.Overlays[renderMode][type](p); + if (p.events) { + for (var evt in p.events) { + _newOverlay.bind(evt, p.events[evt]); + } + } + } else if (o.constructor == String) { + _newOverlay = new jsPlumb.Overlays[renderMode][o]({connection:self, _jsPlumb:_currentInstance}); + } else { + _newOverlay = o; + } + + self.overlays.push(_newOverlay); + }; + var _overlays = params.overlays || _currentInstance.Defaults.Overlays; + if (_overlays) { + for (var i = 0; i < _overlays.length; i++) { + processOverlay(_overlays[i]); + } + } + + this.moveParent = function(newParent) { + var jpcl = jsPlumb.CurrentLibrary, curParent = jpcl.getParent(self.connector.canvas); + jpcl.removeElement(self.connector.canvas, curParent); + jpcl.appendElement(self.connector.canvas, newParent); + if (self.connector.bgCanvas) { + jpcl.removeElement(self.connector.bgCanvas, curParent); + jpcl.appendElement(self.connector.bgCanvas, newParent); + } + // this only applies for DOMOverlays + for (var i = 0; i < self.overlays.length; i++) { + if (self.overlays[i].isAppendedAtTopLevel) { + jpcl.removeElement(self.overlays[i].canvas, curParent); + jpcl.appendElement(self.overlays[i].canvas, newParent); + if (self.overlays[i].reattachListeners) self.overlays[i].reattachListeners(); + } + } + if (self.connector.reattachListeners) // this is for SVG/VML; change an element's parent and you have to reinit its listeners. + self.connector.reattachListeners(); // the Canvas implementation doesn't have to care about this + }; + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Connection. + * + * Parameters: + * event - the event to bind. Available events on a Connection are: + * - *click* : notification that a Connection was clicked. + * - *dblclick* : notification that a Connection was double clicked. + * - *mouseenter* : notification that the mouse is over a Connection. + * - *mouseexit* : notification that the mouse exited a Connection. + * + * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Connection's paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Connection will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Connection. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + // overlay finder helper method + var _getOverlayIndex = function(id) { + var idx = -1; + for (var i = 0; i < self.overlays.length; i++) { + if (id === self.overlays[i].id) { + idx = i; + break; + } + } + return idx; + }; + + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + this.addOverlay = function(overlay) { + processOverlay(overlay); + self.repaint(); + }; + + /* + * Function: getOverlay + * Gets an overlay, by ID. Note: by ID. You would pass an 'id' parameter + * in to the Overlay's constructor arguments, and then use that to retrieve + * it via this method. + */ + this.getOverlay = function(id) { + var idx = _getOverlayIndex(id); + return idx >= 0 ? self.overlays[idx] : null; + }; + + /* + * Function: hideOverlay + * Hides the overlay specified by the given id. + */ + this.hideOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.hide(); + }; + + /* + * Function: showOverlay + * Shows the overlay specified by the given id. + */ + this.showOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.show(); + }; + + /** + * Function: removeAllOverlays + * Removes all overlays from the Connection, and then repaints. + */ + this.removeAllOverlays = function() { + self.overlays.splice(0, self.overlays.length); + self.repaint(); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + this.removeOverlay = function(overlayId) { + var idx = _getOverlayIndex(overlayId); + if (idx != -1) { + var o = self.overlays[idx]; + o.cleanup(); + self.overlays.splice(idx, 1); + } + }; + + /** + * Function:removeOverlays + * Removes a set of overlays by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + this.removeOverlays = function() { + for (var i = 0; i < arguments.length; i++) + self.removeOverlay(arguments[i]); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + this.labelStyle = params.labelStyle || _currentInstance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + if (this.label) { + this.overlays.push(new jsPlumb.Overlays[renderMode].Label( { + cssClass:params.cssClass, + labelStyle : this.labelStyle, + label : this.label, + connection:self, + _jsPlumb:_currentInstance + })); + } + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String or a Function that returns a String. + */ + this.setLabel = function(l) { + self.label = l; + _currentInstance.repaint(self.source); + }; + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId], + otherOffset = offsets[this.targetId], + otherWH = sizes[this.targetId], + initialTimestamp = _timestamp(), + anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + elementId:this.endpoints[0].elementId, + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1], + timestamp:initialTimestamp + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc, timestamp:initialTimestamp }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + elementId:this.endpoints[1].elementId, + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0], + timestamp:initialTimestamp + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc, timestamp:initialTimestamp }); + + /* + * Paints the Connection. Not exposed for public usage. + * + * Parameters: + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp, + // if the moving object is not the source we must transpose the two references. + swap = false, + tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId, + tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + + _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }); + _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var sE = this.endpoints[sIdx], tE = this.endpoints[tIdx], + sAnchorP = sE.anchor.getCurrentLocation(sE), + tAnchorP = tE.anchor.getCurrentLocation(tE); + + /* paint overlays*/ + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible()) maxSize = Math.max(maxSize, o.computeMaxSize(self.connector)); + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, + this.endpoints[sIdx], this.endpoints[tIdx], + this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, + self.paintStyleInUse.lineWidth, maxSize); + + self.connector.paint(dim, self.paintStyleInUse); + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim); + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function(params) { + params = params || {}; + var recalc = !(params.recalc === false); + this.paint({ elId : this.sourceId, recalc : recalc, timestamp:params.timestamp }); + }; + // resizing (using the jquery.ba-resize plugin). todo: decide + // whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + + // just to make sure the UI gets initialised fully on all browsers. + self.repaint(); + }; + +// ENDPOINT HELPER FUNCTIONS + var _makeConnectionDragHandler = function(placeholder) { + var stopped = false; + return function() { + if (stopped) return true; + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments), + el = placeholder.element; + if (el) { + jsPlumb.CurrentLibrary.setOffset(el, _ui); + _draw(_getElementObject(el), _ui); + } + + this.stopDrag = function() { + stopped = true; + }; + }; + }; + + var _makeFloatingEndpoint = function(paintStyle, referenceAnchor, endpoint, referenceCanvas, sourceElement) { + var floatingAnchor = new FloatingAnchor( { reference : referenceAnchor, referenceCanvas : referenceCanvas }); + + //setting the scope here should not be the way to fix that mootools issue. it should be fixed by not + // adding the floating endpoint as a droppable. that makes more sense anyway! + + return _newEndpoint({ paintStyle : paintStyle, endpoint : endpoint, anchor : floatingAnchor, source : sourceElement, scope:"__floating" }); + }; + + /** + * creates a placeholder div for dragging purposes, adds it to the DOM, and pre-computes its offset. then returns + * both the element id and a selector for the element. + */ + var _makeDraggablePlaceholder = function(placeholder, parent) { + var n = document.createElement("div"); + n.style.position = "absolute"; + var placeholderDragElement = _getElementObject(n); + _appendElement(n, parent); + var id = _getId(placeholderDragElement); + _updateOffset( { elId : id }); + // create and assign an id, and initialize the offset. + placeholder.id = id; + placeholder.element = placeholderDragElement; + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1 + * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't + * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you + * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or + * 'target' Endpoints for Connections. + * + * + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * container - optional id or selector instructing jsPlumb where to attach the element it creates for this endpoint. you should read the documentation for a full discussion of this. + * connections - optional list of Connections to configure the Endpoint with. + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + var self = this; + self.idPrefix = "_jsplumb_e_"; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + params = params || {}; + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Endpoint. + * + * Parameters: + * event - the event to bind. Available events on an Endpoint are: + * - *click* : notification that a Endpoint was clicked. + * - *dblclick* : notification that a Endpoint was double clicked. + * - *mouseenter* : notification that the mouse is over a Endpoint. + * - *mouseexit* : notification that the mouse exited a Endpoint. + * + * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Endpoint's paint style and then repaints the Endpoint. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Endpoint will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Endpoint. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + var visible = true; + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + var _element = params.source, _uuid = params.uuid, floatingEndpoint = null, inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + this.element = _element; + + if (params.dynamicAnchors) + self.anchor = new DynamicAnchor(jsPlumb.makeAnchors(params.dynamicAnchors)); + else + self.anchor = params.anchor ? jsPlumb.makeAnchor(params.anchor, _elementId, _currentInstance) : params.anchors ? jsPlumb.makeAnchor(params.anchors, _elementId, _currentInstance) : jsPlumb.makeAnchor("TopCenter", _elementId, _currentInstance); + + // ANCHOR MANAGER + if (!params._transient) // in place copies, for example, are transient. they will never need to be retrieved during a paint cycle, because they dont move, and then they are deleted. + _currentInstance.anchorManager.add(self, _elementId); + + var _endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot", + endpointArgs = { + _jsPlumb:self._jsPlumb, + parent:params.parent, + container:params.container, + tooltip:params.tooltip, + connectorTooltip:params.connectorTooltip, + endpoint:self + }; + if (_endpoint.constructor == String) + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint](endpointArgs); + else if (_endpoint.constructor == Array) { + endpointArgs = jsPlumb.extend(_endpoint[1], endpointArgs); + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint[0]](endpointArgs); + } + else + _endpoint = _endpoint.clone(); + + // assign a clone function using our derived endpointArgs. this is used when a drag starts: the endpoint that was dragged is cloned, + // and the clone is left in its place while the original one goes off on a magical journey. + this.clone = function() { + var o = new Object(); + _endpoint.constructor.apply(o, [endpointArgs]); + return o; + }; + + self.endpoint = _endpoint; + self.type = self.endpoint.type; + // override setHover to pass it down to the underlying endpoint + var _sh = self.setHover; + self.setHover = function() { + self.endpoint.setHover.apply(self.endpoint, arguments); + _sh.apply(self, arguments); + }; + // endpoint delegates to first connection for hover, if there is one. + var internalHover = function(state) { + if (self.connections.length > 0) + self.connections[0].setHover(state, false); + else + self.setHover(state); + }; + + // TODO this event listener registration code is identical to what Connection does: it should be refactored. + this.endpoint.bind("click", function(e) { self.fire("click", self, e); }); + this.endpoint.bind("dblclick", function(e) { self.fire("dblclick", self, e); }); + // this method is different; endpoint delegates hover to the first connection if there is one. + // that allows the connection to notify all its endpoint and avoids a circular loop + this.endpoint.bind("mouseenter", function(con, e) { + if (!self.isHover()) { + internalHover(true); + self.fire("mouseenter", self, e); + } + }); + // this method is different; endpoint delegates hover to the first connection if there is one. + // that allows the connection to notify all its endpoint and avoids a circular loop + this.endpoint.bind("mouseexit", function(con, e) { + if (self.isHover()) { + internalHover(false); + self.fire("mouseexit", self, e); + } + }); + + this.setPaintStyle(params.paintStyle || + params.style || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyle, true); + this.setHoverPaintStyle(params.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyle, true); + this.paintStyleInUse = this.paintStyle; + this.connectorStyle = params.connectorStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.connectorTooltip = params.connectorTooltip; + this.parent = params.parent; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + + var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + /* + * Property: canvas + * The Endpoint's Canvas. + */ + this.canvas = this.endpoint.canvas; + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + this.connections = params.connections || []; + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + self.isReattach = params.reattach || false; + self.connectionsDetachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.connectionsDetachable === false || params.detachable === false) + self.connectionsDetachable = false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget, forceDetach, fireEvent) { + var idx = _findIndex(self.connections, connection), actuallyDetached = false; + fireEvent = (fireEvent !== false); + if (idx >= 0) { + // 1. does the connection have a before detach (note this also checks jsPlumb's bound + // detach handlers; but then Endpoint's check will, too, hmm.) + if (forceDetach || connection._forceDetach || connection.isDetachable() || connection.isDetachAllowed(connection)) { + // get the target endpoint + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + // it would be nice to check with both endpoints that it is ok to detach. but + // for this we'll have to get a bit fancier: right now if you use the same beforeDetach + // interceptor for two endpoints (which is kind of common, because it's part of the + // endpoint definition), then it gets fired twice. so in fact we need to loop through + // each beforeDetach and see if it returns false, at which point we exit. but if it + // returns true, we have to check the next one. however we need to track which ones + // have already been run, and not run them again. + if (forceDetach || connection._forceDetach || (self.isDetachAllowed(connection) /*&& t.isDetachAllowed(connection)*/)) { + + self.connections.splice(idx, 1); + + // this avoids a circular loop + if (!ignoreTarget) { + + t.detach(connection, true, forceDetach); + // check connection to see if we want to delete the endpoints associated with it. + // we only detach those that have just this connection; this scenario is most + // likely if we got to this bit of code because it is set by the methods that + // create their own endpoints, like .connect or .makeTarget. the user is + // not likely to have interacted with those endpoints. + if (connection.endpointsToDeleteOnDetach){ + for (var i = 0; i < connection.endpointsToDeleteOnDetach.length; i++) { + var cde = connection.endpointsToDeleteOnDetach[i]; + if (cde && cde.connections.length == 0) + jsPlumb.deleteEndpoint(cde); + } + } + } + _removeElements(connection.connector.getDisplayElements(), connection.parent); + _removeFromList(connectionsByScope, connection.scope, connection); + actuallyDetached = true; + var doFireEvent = (!ignoreTarget && fireEvent) + fireDetachEvent(connection, doFireEvent); + } + } + } + return actuallyDetached; + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + * + * Parameters: + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachAll = function(fireEvent) { + while (self.connections.length > 0) { + self.detach(self.connections[0], false, true, fireEvent); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachFrom = function(targetEndpoint, fireEvent) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + if (self.detach(c[i], false, true, fireEvent)) + c[i].setHover(false, false); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: setElement + * Sets the DOM element this Endpoint is attached to. + */ + this.setElement = function(el) { + // TODO possibly have this object take charge of moving the UI components into the appropriate + // parent. this is used only by makeSource right now, and that function takes care of + // moving the UI bits and pieces. however it would s + var parentId = _getId(el); + // remove the endpoint from the list for the current endpoint's element + _removeFromList(endpointsByElement, _elementId, self); + _element = _getElementObject(el); + _elementId = _getId(_element); + self.elementId = _elementId; + + // need to get the new parent now + var newParentElement = _getParentFromParams({source:parentId}), + curParent = jpcl.getParent(self.canvas); + jpcl.removeElement(self.canvas, curParent); + jpcl.appendElement(self.canvas, newParentElement); + + // now move connection(s)...i would expect there to be only one but we will iterate. + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].moveParent(newParentElement); + self.connections[i].sourceId = _elementId; + self.connections[i].source = _element; + } + + _addToList(endpointsByElement, parentId, self); + _currentInstance.repaint(parentId); + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + return _newEndpoint( { + anchor : self.anchor, + source : _element, + paintStyle : this.paintStyle, + endpoint : _endpoint, + _transient:true, + scope:self.scope + }); + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + var candidate = self.connections[0]; + if (self.isTarget && candidate) return candidate; + else { + return (self.connections.length < _maxConnections) || _maxConnections == -1 ? null : candidate; + } + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + /* + * Function: setStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to + * setStyle and deprecate it if so. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + * + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + // a helper function that tries to find a connection to the given element, and returns it if so. if elementWithPrecedence is null, + // or no connection to it is found, we return the first connection in our list. + var findConnectionToUseForDynamicAnchor = function(elementWithPrecedence) { + var idx = 0; + if (elementWithPrecedence != null) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].sourceId == elementWithPrecedence || self.connections[i].targetId == elementWithPrecedence) { + idx = i; + break; + } + } + } + + return self.connections[idx]; + }; + + /* + * Function: paint + * Paints the Endpoint, recalculating offset and anchor positions if necessary. This does NOT paint + * any of the Endpoint's connections. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + params = params || {}; + var timestamp = params.timestamp, + recalc = !(params.recalc === false); + if (!timestamp || self.timestamp !== timestamp) { + _updateOffset({ elId:_elementId, timestamp:timestamp, recalc:recalc }); + var ap = params.anchorPoint, connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + var xy = params.offset || offsets[_elementId], + wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (recalc && self.anchor.isDynamic && self.connections.length > 0) { + var c = findConnectionToUseForDynamicAnchor(params.elementWithPrecedence), + oIdx = c.endpoints[0] == self ? 1 : 0, + oId = oIdx == 0 ? c.sourceId : c.targetId, + oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + ap = self.anchor.compute(anchorParams); + } + + var d = _endpoint.compute(ap, self.anchor.getOrientation(_endpoint), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + _endpoint.paint(d, self.paintStyleInUse, self.anchor); + self.timestamp = timestamp; + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var placeholderInfo = { + id:null, + element:null + }, + jpc = null, + existingJpc = false, + existingJpcParams = null, + _dragHandler = _makeConnectionDragHandler(placeholderInfo); + + var start = function() { + // drag might have started on an endpoint that is not actually a source, but which has + // one or more connections. + jpc = self.connectorSelector(); + var _continue = true; + // if no connection and we're not a source, return. + if (jpc == null && !params.isSource) _continue = false; + // otherwise if we're full and not allowed to drag, also return false. + if (params.isSource && self.isFull() && !dragAllowedWhenFull) _continue = false; + + // if the connection was setup as not detachable (or one of its endpoints + // was setup as connectionsDetachable = false, or Defaults.ConnectionsDetachable + // is set to false... + if (jpc != null && !jpc.isDetachable()) _continue = false; + + if (_continue === false) { + // this is for mootools and yui. returning false from this causes jquery to stop drag. + // the events are wrapped in both mootools and yui anyway, but i don't think returning + // false from the start callback would stop a drag. + if (jsPlumb.CurrentLibrary.stopDrag) jsPlumb.CurrentLibrary.stopDrag(); + _dragHandler.stopDrag(); + return false; + } + + // if we're not full but there was a connection, make it null. we'll create a new one. + if (jpc && !self.isFull() && params.isSource) jpc = null; + + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + _makeDraggablePlaceholder(placeholderInfo, self.parent); + + // set the offset of this div to be where 'inPlaceCopy' is, to start with. + // TODO merge this code with the code in both Anchor and FloatingAnchor, because it + // does the same stuff. + var ipcoel = _getElementObject(inPlaceCopy.canvas), + ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel), + po = adjustForParentOffsetAndScroll([ipco.left, ipco.top], inPlaceCopy.canvas); + jsPlumb.CurrentLibrary.setOffset(placeholderInfo.element, {left:po[0], top:po[1]}); + + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", placeholderInfo.id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + floatingEndpoint = _makeFloatingEndpoint(self.paintStyle, self.anchor, _endpoint, self.canvas, placeholderInfo.element); + + if (jpc == null) { + self.anchor.locked = true; + self.setHover(false, false); + // TODO the hover call above does not reset any target endpoint's hover + // states. + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : self.endpointWillMoveTo || _getElementObject(_element), // for makeSource with parent option. ensure source element is represented correctly. + target : placeholderInfo.element, + anchors : [ self.anchor, floatingEndpoint.anchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays + }); + + } else { + existingJpc = true; + jpc.connector.setHover(false, false); + // jpc.endpoints[0].setHover(false, false); + //jpc.endpoints[1].setHover(false, false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas), false, true); + //var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; // are we the source or the target? + // new anchor idx + var anchorIdx = jpc.endpoints[0].id == self.id ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas), + dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // now we want to get this endpoint's DROP scope, and set it for now: we can only be dropped on drop zones + // that have our drop scope (issue 57). + var dropScope = jsPlumb.CurrentLibrary.getDropScope(c); + jsPlumb.CurrentLibrary.setDragScope(c, dropScope); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = placeholderInfo.element; + jpc.sourceId = placeholderInfo.id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = placeholderInfo.element; + jpc.targetId = placeholderInfo.id; + } + + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.suspendedEndpoint.setHover(false); + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + // register it and register connection on it. + floatingConnections[placeholderInfo.id] = jpc; + floatingEndpoint.addConnection(jpc); + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, placeholderInfo.id, floatingEndpoint); + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var jpcl = jsPlumb.CurrentLibrary, + dragOptions = params.dragOptions || {}, + defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions), + startEvent = jpcl.dragEvents["start"], + stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"]; + + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + // extracted drag handler function so can be used by makeSource + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], _dragHandler); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _currentInstance.currentlyDragging = false; + _removeFromList(endpointsByElement, placeholderInfo.id, floatingEndpoint); + _removeElements( [ placeholderInfo.element[0], floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + _currentInstance.anchorManager.clearFor(placeholderInfo.id); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (self.isReattach || jpc._forceDetach || !jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc)) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } + jpc._forceDetach = null; + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElements(jpc.connector.getDisplayElements(), self.parent); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint({recalc:false}); + jpc.setHover(false, false); + jpc = null; + inPlaceCopy = null; + delete endpointsByElement[floatingEndpoint.elementId]; + floatingEndpoint.anchor = null; + floatingEndpoint = null; + _currentInstance.currentlyDragging = false; + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions, true); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas, forceInit, isTransient) { + if ((params.isTarget || forceInit) && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop'], + overEvent = jsPlumb.CurrentLibrary.dragEvents['over'], + outEvent = jsPlumb.CurrentLibrary.dragEvents['out'], + drop = function() { + var draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + elId = _getAttribute(draggable, "elId"), + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id], + idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + + // restore the original scope if necessary (issue 57) + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + + var _doContinue = true; + // the second check here is for the case that the user is dropping it back + // where it came from. + if (jpc.suspendedEndpoint && jpc.suspendedEndpoint.id != self.id) { + if (!jpc.isDetachAllowed(jpc) || !jpc.endpoints[idx].isDetachAllowed(jpc) || !jpc.suspendedEndpoint.isDetachAllowed(jpc) || !_currentInstance.checkCondition("beforeDetach", jpc)) + _doContinue = false; + } + + // these have to be set before testing for beforeDrop. + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + + // now check beforeDrop. this will be available only on Endpoints that are setup to + // have a beforeDrop condition (although, secretly, under the hood all Endpoints and + // the Connection have them, because they are on jsPlumbUIComponent. shhh!), because + // it only makes sense to have it on a target endpoint. + _doContinue = _doContinue && self.isDropAllowed(jpc.sourceId, jpc.targetId, jpc.scope); + + if (_doContinue) { + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + if (!jpc.suspendedEndpoint) { + //_addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + fireDetachEvent({ + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }, true); + } + + // finalise will inform the anchor manager and also add to + // connectionsByScope if necessary. + _finaliseConnection(jpc); + } + else { + // otherwise just put it back on the endpoint it was on before the drag. + if (jpc.suspendedEndpoint) { + // self.detachFrom(jpc); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.setHover(false); + jpc._forceDetach = true; + if (idx == 0) { + jpc.source = jpc.suspendedEndpoint.element; + jpc.sourceId = jpc.suspendedEndpoint.elementId; + } else { + jpc.target = jpc.suspendedEndpoint.element; + jpc.targetId = jpc.suspendedEndpoint.elementId;; + } + jpc.suspendedEndpoint.addConnection(jpc); + + jpc.endpoints[0].repaint(); + jpc.repaint(); + jsPlumb.repaint(jpc.source.elementId); + jpc._forceDetach = false; + } + } + + jpc.floatingAnchorIndex = null; + } + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + } + }); + dropOptions[outEvent] = _wrap(dropOptions[outEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + } + }); + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions, true, isTransient); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas), true, !(params._transient || self.anchor.isFloating)); + + return self; + }; + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + j.init(); + return j; + }; + jsPlumb.util = { + convertStyle : function(s, ignoreAlpha) { + // TODO: jsPlumb should support a separate 'opacity' style member. + if ("transparent" === s) return s; + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + return o; + }, + gradient : function(p1, p2) { + p1 = p1.constructor == Array ? p1 : [p1.x, p1.y]; + p2 = p2.constructor == Array ? p2 : [p2.x, p2.y]; + return (p2[1] - p1[1]) / (p2[0] - p1[0]); + }, + normal : function(p1, p2) { + return -1 / jsPlumb.util.gradient(p1,p2); + }, + segment : function(p1, p2) { + p1 = p1.constructor == Array ? p1 : [p1.x, p1.y]; + p2 = p2.constructor == Array ? p2 : [p2.x, p2.y]; + if (p2[0] > p1[0]) { + return (p2[1] > p1[1]) ? 2 : 1; + } + else { + return (p2[1] > p1[1]) ? 3 : 4; + } + }, + segmentMultipliers : [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ], + inverseSegmentMultipliers : [null, [-1, -1], [-1, 1], [1, 1], [1, -1] ], + pointOnLine : function(fromPoint, toPoint, distance) { + var m = jsPlumb.util.gradient(fromPoint, toPoint), + s = jsPlumb.util.segment(fromPoint, toPoint), + segmentMultiplier = distance > 0 ? jsPlumb.util.segmentMultipliers[s] : jsPlumb.util.inverseSegmentMultipliers[s], + theta = Math.atan(m), + y = Math.abs(distance * Math.sin(theta)) * segmentMultiplier[1], + x = Math.abs(distance * Math.cos(theta)) * segmentMultiplier[0]; + return { x:fromPoint.x + x, y:fromPoint.y + y }; + }, + /** + * calculates a perpendicular to the line fromPoint->toPoint, that passes through toPoint and is 'length' long. + * @param fromPoint + * @param toPoint + * @param length + */ + perpendicularLineTo : function(fromPoint, toPoint, length) { + var m = jsPlumb.util.gradient(fromPoint, toPoint), + theta2 = Math.atan(-1 / m), + y = length / 2 * Math.sin(theta2), + x = length / 2 * Math.cos(theta2); + return [{x:toPoint.x + x, y:toPoint.y + y}, {x:toPoint.x - x, y:toPoint.y - y}]; + } + }; + + var _curryAnchor = function(x, y, ox, oy, type, fnInit) { + return function(params) { + params = params || {}; + var a = jsPlumb.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance); + a.type = type; + if (fnInit) fnInit(a, params); + return a; + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1, "TopCenter"); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1, "BottomCenter"); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0, "LeftMiddle"); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0, "RightMiddle"); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0, "Center"); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1, "TopRight"); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1, "BottomRight"); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1, "TopLeft"); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1, "BottomLeft"); + + jsPlumb.Defaults.DynamicAnchors = function(params) { + return jsPlumb.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"], params.elementId, params.jsPlumbInstance); + }; + jsPlumb.Anchors["AutoDefault"] = function(params) { + var a = jsPlumb.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors(params)); + a.type = "AutoDefault"; + return a; + }; + + jsPlumb.Anchors["Assign"] = _curryAnchor(0,0,0,0,"Assign", function(anchor, params) { + // find what to use as the "position finder". the user may have supplied a String which represents + // the id of a position finder in jsPlumb.AnchorPositionFinders, or the user may have supplied the + // position finder as a function. we find out what to use and then set it on the anchor. + var pf = params.position || "Fixed"; + anchor.positionFinder = pf.constructor == String ? jsPlumb.AnchorPositionFinders[pf] : pf; + // always set the constructor params; the position finder might need them later (the Grid one does, + // for example) + anchor.constructorParams = params; + }); + + // Continuous anchor is just curried through to the 'get' method of the continuous anchor + // factory. + jsPlumb.Anchors["Continuous"] = function(params) { + return params.jsPlumbInstance.continuousAnchorFactory.get(params); + }; + + // these are the default anchor positions finders, which are used by the makeTarget function. supply + // a position finder argument to that function allows you to specify where the resulting anchor will + // be located + jsPlumb.AnchorPositionFinders = { + "Fixed": function(dp, ep, es, a) { + return [ (dp.left - ep.left) / es[0], (dp.top - ep.top) / es[1] ]; + }, + "Grid":function(dp, ep, es, a) { + var dx = dp.left - ep.left, dy = dp.top - ep.top, + gx = es[0] / (a.constructorParams.grid[0]), gy = es[1] / (a.constructorParams.grid[1]), + mx = Math.floor(dx / gx), my = Math.floor(dy / gy); + return [ ((mx * gx) + (gx / 2)) / es[0], ((my * gy) + (gy / 2)) / es[1] ]; + } + }; +})(); +/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the default Connectors, Endpoint and Overlay definitions. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +(function() { + + /** + * + * Helper class to consume unused mouse events by components that are DOM elements and + * are used by all of the different rendering modes. + * + */ + jsPlumb.DOMElementComponent = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + // when render mode is canvas, these functions may be called by the canvas mouse handler. + // this component is safe to pipe this stuff to /dev/null. + this.mousemove = + this.dblclick = + this.click = + this.mousedown = + this.mouseup = function(e) { }; + }; + + /** + * Class: Connectors.Straight + * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters. + */ + jsPlumb.Connectors.Straight = function() { + this.type = "Straight"; + var self = this, + currentPoints = null, + _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty, _segment, _length; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + _dx = _tx - _sx, _dy = _ty - _sy; + //_m = _dy / _dx, _m2 = -1 / _m; + _m = jsPlumb.util.gradient({x:_sx, y:_sy}, {x:_tx, y:_ty}), _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + //_segment = jsPlumb.util.segment({x:_sx, y:_sy}, {x:_tx, y:_ty}); + _length = Math.sqrt((_dx * _dx) + (_dy * _dy)); + + return currentPoints; + }; + + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + if (location == 0) + return { x:_sx, y:_sy }; + else if (location == 1) + return { x:_tx, y:_ty }; + else + return jsPlumb.util.pointOnLine({x:_sx, y:_sy}, {x:_tx, y:_ty}, location * _length); + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { + return _m; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + * this hands off to jsPlumb.util to do the maths, supplying our gradient and position and whether or + * not the coords should be flipped + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + return jsPlumb.util.pointOnLine(p, {x:_tx,y:_ty}, distance); + }; + }; + + + /** + * Class:Connectors.Bezier + * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's + * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below. + */ + /** + * Function:Constructor + * + * Parameters: + * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line. + * Optional; defaults to 150. + * + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + params = params || {}; + this.majorAnchor = params.curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + this.type = "Bezier"; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(sourceEndpoint), + too = targetAnchor.getOrientation(targetEndpoint), + perpendicular = soo[0] != too[0] || soo[1] == too[1], + p = [], + ma = self.majorAnchor, mi = self.minorAnchor; + + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + +// this.isFlipX = function() { return + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h; + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) + { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, sourceEndpoint, targetEndpoint, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx), minx2 = Math.min(_CP[0], _CP2[0]), minx = Math.min(minx1,minx2), + maxx1 = Math.max(_sx,_tx), maxx2 = Math.max(_CP[0], _CP2[0]), maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty), miny2 = Math.min(_CP[1], _CP2[1]), miny = Math.min(miny1,miny2), + maxy1 = Math.max(_sy,_ty), maxy2 = Math.max(_CP[1], _CP2[1]), maxy = Math.max(maxy1,maxy2); + + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + }; + + + /** + * Class: Connectors.Flowchart + * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments. + */ + /** + * Function: Constructor + * + * Parameters: + * stub - minimum length for the stub at each end of the connector. defaults to 30 pixels. + */ + jsPlumb.Connectors.Flowchart = function(params) { + this.type = "Flowchart"; + params = params || {}; + var self = this, + minStubLength = params.stub || params.minStubLength /* bwds compat. */ || 30, + segments = [], + totalLength = 0, + segmentProportions = [], + segmentProportionalLengths = [], + points = [], + swapX, + swapY, + /** + * recalculates the points at which the segments begin and end, proportional to the total length travelled + * by all the segments that constitute the connector. we use this to help with pointOnPath calculations. + */ + updateSegmentProportions = function(startX, startY, endX, endY) { + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segments[i][5] / totalLength; + segmentProportions[i] = [curLoc, (curLoc += (segments[i][5] / totalLength)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0], + ly = segments.length == 0 ? sy : segments[segments.length - 1][1], + m = x == lx ? Infinity : 0, + l = Math.abs(x == lx ? y - ly : x - lx); + segments.push([x, y, lx, ly, m, l]); + totalLength += l; + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment + * that contains the point which is 'location' distance along the entire path, where + * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths + * are made up of a list of segments, each of which contributes some fraction to + * the total length. + */ + findSegmentForLocation = function(location) { + var idx = segmentProportions.length - 1, inSegmentProportion = 0; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + segments = []; + totalLength = 0; + segmentProportionalLengths = []; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + offx = (lw / 2) + (minStubLength * 2), + offy = (lw / 2) + (minStubLength * 2), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(sourceEndpoint), + to = targetAnchor.orientation || targetAnchor.getOrientation(targetEndpoint), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx, + h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy; + if (w < minWidth) { + offx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + offy += (minWidth - h) / 2; + h = minWidth; + } + var sx = swapX ? w-offx : offx, + sy = swapY ? h-offy : offy, + tx = swapX ? offx : w-offx , + ty = swapY ? offy : h-offy, + startStubX = sx + (so[0] * minStubLength), + startStubY = sy + (so[1] * minStubLength), + endStubX = tx + (to[0] * minStubLength), + endStubY = ty + (to[1] * minStubLength), + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2); + + x -= offx; y -= offy; + points = [x, y, w, h, sx, sy, tx, ty]; + var extraPoints = []; + + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + if (so[0] == 0) { + var startStubIsBeforeEndStub = startStubY < endStubY; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } + } + else { + var startStubIsBeforeEndStub = startStubX < endStubX; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } + } + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentProportions(sx, sy, tx, ty); + + return points; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location) { + return self.pointAlongPathFrom(location, 0); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location) { + return segments[findSegmentForLocation(location)["index"]][4]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance) { + var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segments[s.index][5], m = segments[s.index][4]; + var e = { + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + return e; + }; + }; + + // ********************************* END OF CONNECTOR TYPES ******************************************************************* + + // ********************************* ENDPOINT TYPES ******************************************************************* + + /** + * Class: Endpoints.Dot + * A round endpoint, with default radius 10 pixels. + */ + + /** + * Function: Constructor + * + * Parameters: + * + * radius - radius of the endpoint. defaults to 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + this.type = "Dot"; + var self = this; + params = params || {}; + this.radius = params.radius || 10; + this.defaultOffset = 0.5 * this.radius; + this.defaultInnerRadius = this.radius / 3; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var r = endpointStyle.radius || self.radius, + x = anchorPoint[0] - r, + y = anchorPoint[1] - r; + return [ x, y, r * 2, r * 2, r ]; + }; + }; + + /** + * Class: Endpoints.Rectangle + * A Rectangular Endpoint, with default size 20x20. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the endpoint. defaults to 20 pixels. + * height - height of the endpoint. defaults to 20 pixels. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + this.type = "Rectangle"; + var self = this; + params = params || {}; + this.width = params.width || 20; + this.height = params.height || 20; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height]; + }; + }; + + /** + * Class: Endpoints.Image + * Draws an image as the Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * src - location of the image to use. + */ + jsPlumb.Endpoints.Image = function(params) { + + this.type = "Image"; + jsPlumb.DOMElementComponent.apply(this, arguments); + + var self = this, + initialized = false, + widthToUse = params.width, + heightToUse = params.height, + _onload = null, + _endpoint = params.endpoint; + + this.img = new Image(); + self.ready = false; + + this.img.onload = function() { + self.ready = true; + widthToUse = widthToUse || self.img.width; + heightToUse = heightToUse || self.img.height; + if (_onload) { + _onload(this); + } + }; + + _endpoint.setImage = function(img, onload) { + var s = img.constructor == String ? img : img.src; + _onload = onload; + self.img.src = img; + }; + + _endpoint.setImage(params.src || params.url, params.onload); + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + self.anchorPoint = anchorPoint; + if (self.ready) return [anchorPoint[0] - widthToUse / 2, anchorPoint[1] - heightToUse / 2, + widthToUse, heightToUse]; + else return [0,0,0,0]; + }; + + self.canvas = document.createElement("img"), initialized = false; + self.canvas.style["margin"] = 0; + self.canvas.style["padding"] = 0; + self.canvas.style["outline"] = 0; + self.canvas.style["position"] = "absolute"; + var clazz = params.cssClass ? " " + params.cssClass : ""; + self.canvas.className = jsPlumb.endpointClass + clazz; + if (widthToUse) self.canvas.setAttribute("width", widthToUse); + if (heightToUse) self.canvas.setAttribute("height", heightToUse); + jsPlumb.appendElement(self.canvas, params.parent); + self.attachListeners(self.canvas, self); + + var actuallyPaint = function(d, style, anchor) { + if (!initialized) { + self.canvas.setAttribute("src", self.img.src); + initialized = true; + } + var x = self.anchorPoint[0] - (widthToUse / 2), + y = self.anchorPoint[1] - (heightToUse / 2); + jsPlumb.sizeCanvas(self.canvas, x, y, widthToUse, heightToUse); + }; + + this.paint = function(d, style, anchor) { + if (self.ready) { + actuallyPaint(d, style, anchor); + } + else { + window.setTimeout(function() { + self.paint(d, style, anchor); + }, 200); + } + }; + }; + + /** + * Class: Endpoints.Blank + * An Endpoint that paints nothing (visible) on the screen. Supports cssClass and hoverClass parameters like all Endpoints. + */ + jsPlumb.Endpoints.Blank = function(params) { + var self = this; + this.type = "Blank"; + jsPlumb.DOMElementComponent.apply(this, arguments); + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + return [anchorPoint[0], anchorPoint[1],10,0]; + }; + + self.canvas = document.createElement("div"); + self.canvas.style.display = "block"; + self.canvas.style.width = "1px"; + self.canvas.style.height = "1px"; + self.canvas.style.background = "transparent"; + self.canvas.style.position = "absolute"; + self.canvas.className = self._jsPlumb.endpointClass; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + }; + }; + + /** + * Class: Endpoints.Triangle + * A triangular Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the triangle's base. defaults to 55 pixels. + * height - height of the triangle from base to apex. defaults to 55 pixels. + */ + jsPlumb.Endpoints.Triangle = function(params) { + this.type = "Triangle"; + params = params || { }; + params.width = params.width || 55; + params.height = params.height || 55; + this.width = params.width; + this.height = params.height; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height ]; + }; + }; +// ********************************* END OF ENDPOINT TYPES ******************************************************************* + + +// ********************************* OVERLAY DEFINITIONS *********************************************************************** + + var AbstractOverlay = function(params) { + var visible = true, self = this; + this.isAppendedAtTopLevel = true; + this.connection = params.connection; + this.loc = params.location == null ? 0.5 : params.location; + this.setVisible = function(val) { + visible = val; + self.connection.repaint(); + }; + this.isVisible = function() { return visible; }; + this.hide = function() { self.setVisible(false); }; + this.show = function() { self.setVisible(true); }; + + this.incrementLocation = function(amount) { + self.loc += amount; + self.connection.repaint(); + }; + this.setLocation = function(l) { + self.loc = l; + self.connection.repaint(); + }; + this.getLocation = function() { + return self.loc; + }; + }; + + + /** + * Class: Overlays.Arrow + * + * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length + * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction + * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line + * across the tail. + */ + /** + * Function: Constructor + * + * Parameters: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + this.type = "Arrow"; + AbstractOverlay.apply(this, arguments); + this.isAppendedAtTopLevel = false; + params = params || {}; + var self = this; + + this.length = params.length || 20; + this.width = params.width || 20; + this.id = params.id; + var direction = (params.direction || 1) < 0 ? -1 : 1, + paintStyle = params.paintStyle || { lineWidth:1 }, + // how far along the arrow the lines folding back in come to. default is 62.3%. + foldback = params.foldback || 0.623; + + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.cleanup = function() { }; // nothing to clean up for Arrows + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var hxy, mid, txy, tail, cxy; + + if (self.loc == 1) { + hxy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, -1); + txy = jsPlumb.util.pointOnLine(hxy, mid, self.length); + } + else if (self.loc == 0) { + txy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, 1); + hxy = jsPlumb.util.pointOnLine(txy, mid, self.length); + } + else { + hxy = connector.pointAlongPathFrom(self.loc, direction * self.length / 2), + mid = connector.pointOnPath(self.loc), + txy = jsPlumb.util.pointOnLine(hxy, mid, self.length); + } + + tail = jsPlumb.util.perpendicularLineTo(hxy, txy, self.width); + cxy = jsPlumb.util.pointOnLine(hxy, txy, foldback * self.length); + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x), + maxx = Math.max(hxy.x, tail[0].x, tail[1].x), + miny = Math.min(hxy.y, tail[0].y, tail[1].y), + maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + var d = { hxy:hxy, tail:tail, cxy:cxy }, + strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle, + fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle, + lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth; + + self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions); + + return [ minx, maxx, miny, maxy]; + }; + }; + + /** + * Class: Overlays.PlainArrow + * + * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "PlainArrow"; + }; + + /** + * Class: Overlays.Diamond + * + * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40, + p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "Diamond"; + }; + + + + /** + * Class: Overlays.Label + * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb + * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter + * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it + * puts on the Label's 'style' attribute, so the end result is the same. + */ + /** + * Function: Constructor + * + * Parameters: + * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes + * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle. + * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * + */ + jsPlumb.Overlays.Label = function(params) { + this.type = "Label"; + jsPlumb.DOMElementComponent.apply(this, arguments); + AbstractOverlay.apply(this, arguments); + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.id = params.id; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var label = params.label || "", + self = this, + initialised = false, + div = document.createElement("div"), + labelText = null; + div.style["position"] = "absolute"; + + var clazz = params["_jsPlumb"].overlayClass + " " + + (self.labelStyle.cssClass ? self.labelStyle.cssClass : + params.cssClass ? params.cssClass : ""); + + div.className = clazz; + + jsPlumb.appendElement(div, params.connection.parent); + jsPlumb.getId(div); + self.attachListeners(div, self); + self.canvas = div; + + //override setVisible + var osv = self.setVisible; + self.setVisible = function(state) { + osv(state); // call superclass + div.style.display = state ? "block" : "none"; + }; + + this.getElement = function() { + return div; + }; + + this.cleanup = function() { + if (div != null) jsPlumb.CurrentLibrary.removeElement(div); + }; + + /* + * Function: setLabel + * sets the label's, um, label. you would think i'd call this function + * 'setText', but you can pass either a Function or a String to this, so + * it makes more sense as 'setLabel'. + */ + this.setLabel = function(l) { + label = l; + labelText = null; + self.connection.repaint(); + }; + + this.getLabel = function() { + return label; + }; + + this.paint = function(connector, d, connectorDimensions) { + if (!initialised) { + connector.appendDisplayElement(div); + self.attachListeners(div, connector); + initialised = true; + } + div.style.left = (connectorDimensions[0] + d.minx) + "px"; + div.style.top = (connectorDimensions[1] + d.miny) + "px"; + }; + + this.getTextDimensions = function(connector) { + if (typeof label == "function") { + var lt = label(self); + div.innerHTML = lt.replace(/\r\n/g, "
"); + } + else { + if (labelText == null) { + labelText = label; + div.innerHTML = labelText.replace(/\r\n/g, "
"); + } + } + var de = jsPlumb.CurrentLibrary.getElementObject(div), + s = jsPlumb.CurrentLibrary.getSize(de); + return {width:s[0], height:s[1]}; + }; + + this.computeMaxSize = function(connector) { + var td = self.getTextDimensions(connector); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + var td = self.getTextDimensions(connector); + if (td.width != null) { + var cxy = connector.pointOnPath(self.loc), + minx = cxy.x - (td.width / 2), + miny = cxy.y - (td.height / 2); + + self.paint(connector, { + minx:minx, + miny:miny, + td:td, + cxy:cxy + }, connectorDimensions); + + return [minx, minx+td.width, miny, miny+td.height]; + } + else return [0,0,0,0]; + }; + + this.reattachListeners = function() { + if (div) self.reattachListenersForElement(div, self); + }; + }; + + // this is really just a test overlay, so its undocumented and doesnt take any parameters. but i was loth to delete it. + jsPlumb.Overlays.GuideLines = function() { + var self = this; + self.length = 50; + self.lineWidth = 5; + this.type = "GuideLines"; + AbstractOverlay.apply(this, arguments); + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var head = connector.pointAlongPathFrom(self.loc, self.length / 2), + mid = connector.pointOnPath(self.loc), + tail = jsPlumb.util.pointOnLine(head, mid, self.length), + tailLine = jsPlumb.util.perpendicularLineTo(head, tail, 40), + headLine = jsPlumb.util.perpendicularLineTo(tail, head, 20); + + self.paint(connector, [head, tail, tailLine, headLine], self.lineWidth, "red", null, connectorDimensions); + + return [Math.min(head.x, tail.x), Math.min(head.y, tail.y), Math.max(head.x, tail.x), Math.max(head.y,tail.y)]; + }; + + this.computeMaxSize = function() { return 50; }; + + this.cleanup = function() { }; // nothing to clean up for GuideLines + }; + + // ********************************* END OF OVERLAY DEFINITIONS *********************************************************************** + + // ********************************* OVERLAY CANVAS RENDERERS*********************************************************************** + + // ********************************* END OF OVERLAY CANVAS RENDERERS *********************************************************************** +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the state machine connectors. + * + * Thanks to Brainstorm Mobile Solutions for supporting the development of these. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + var Line = function(x1, y1, x2, y2) { + + this.m = (y2 - y1) / (x2 - x1); + this.b = -1 * ((this.m * x1) - y1); + + this.rectIntersect = function(x,y,w,h) { + var results = []; + + // try top face + // the equation of the top face is y = (0 * x) + b; y = b. + var xInt = (y - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try right face + var yInt = (this.m * (x + w)) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + // bottom face + var xInt = ((y + h) - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try left face + var yInt = (this.m * x) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + if (results.length == 2) { + var midx = (results[0][0] + results[1][0]) / 2, midy = (results[0][1] + results[1][1]) / 2; + results.push([ midx,midy ]); + // now calculate the segment inside the rectangle where the midpoint lies. + var xseg = midx <= x + (w / 2) ? -1 : 1, + yseg = midy <= y + (h / 2) ? -1 : 1; + results.push([xseg, yseg]); + return results; + } + + return null; + + }; + }, + _segment = function(x1, y1, x2, y2) { + if (x1 <= x2 && y2 <= y1) return 1; + else if (x1 <= x2 && y1 <= y2) return 2; + else if (x2 <= x1 && y2 >= y1) return 3; + return 4; + }, + + // the control point we will use depends on the faces to which each end of the connection is assigned, specifically whether or not the + // two faces are parallel or perpendicular. if they are parallel then the control point lies on the midpoint of the axis in which they + // are parellel and varies only in the other axis; this variation is proportional to the distance that the anchor points lie from the + // center of that face. if the two faces are perpendicular then the control point is at some distance from both the midpoints; the amount and + // direction are dependent on the orientation of the two elements. 'seg', passed in to this method, tells you which segment the target element + // lies in with respect to the source: 1 is top right, 2 is bottom right, 3 is bottom left, 4 is top left. + // + // sourcePos and targetPos are arrays of info about where on the source and target each anchor is located. their contents are: + // + // 0 - absolute x + // 1 - absolute y + // 2 - proportional x in element (0 is left edge, 1 is right edge) + // 3 - proportional y in element (0 is top edge, 1 is bottom edge) + // + _findControlPoint = function(midx, midy, segment, sourceEdge, targetEdge, dx, dy, distance, proximityLimit) { + + // TODO (maybe) + // - if anchor pos is 0.5, make the control point take into account the relative position of the elements. + if (distance <= proximityLimit) return [midx, midy]; + + if (segment == 1) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 2) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 3) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 4) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + }; + + /* + Function: StateMachine constructor + + Allowed parameters: + curviness - measure of how "curvy" the connectors will be. this is translated as the distance that the + Bezier curve's control point is from the midpoint of the straight line connecting the two + endpoints, and does not mean that the connector is this wide. The Bezier curve never reaches + its control points; they act as gravitational masses. defaults to 10. + margin - distance from element to start and end connectors, in pixels. defaults to 5. + proximityLimit - sets the distance beneath which the elements are consider too close together to bother with fancy + curves. by default this is 80 pixels. + loopbackRadius - the radius of a loopback connector. optional; defaults to 25. + */ + jsPlumb.Connectors.StateMachine = function(params) { + var self = this, + currentPoints = null, + _sx, _sy, _tx, _ty, _controlPoint = [], + curviness = params.curviness || 10, + margin = params.margin || 5, + proximityLimit = params.proximityLimit || 80, + clockwise = params.orientation && params.orientation == "clockwise", + loopbackRadius = params.loopbackRadius || 25, + isLoopback = false; + + this.type = "StateMachine"; + params = params || {}; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *= 1.9; + //ensure at least one pixel width + lineWidth = lineWidth || 1; + var x = Math.min(sourcePos[0], targetPos[0]) - xo, + y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (sourceEndpoint.elementId != targetEndpoint.elementId) { + + isLoopback = false; + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + + // now adjust for the margin + if (sourcePos[2] == 0) _sx -= margin; + if (sourcePos[2] == 1) _sx += margin; + if (sourcePos[3] == 0) _sy -= margin; + if (sourcePos[3] == 1) _sy += margin; + if (targetPos[2] == 0) _tx -= margin; + if (targetPos[2] == 1) _tx += margin; + if (targetPos[3] == 0) _ty -= margin; + if (targetPos[3] == 1) _ty += margin; + + // + // these connectors are quadratic bezier curves, having a single control point. if both anchors + // are located at 0.5 on their respective faces, the control point is set to the midpoint and you + // get a straight line. this is also the case if the two anchors are within 'proximityLimit', since + // it seems to make good aesthetic sense to do that. outside of that, the control point is positioned + // at 'curviness' pixels away along the normal to the straight line connecting the two anchors. + // + // there may be two improvements to this. firstly, we might actually support the notion of avoiding nodes + // in the UI, or at least making a good effort at doing so. if a connection would pass underneath some node, + // for example, we might increase the distance the control point is away from the midpoint in a bid to + // steer it around that node. this will work within limits, but i think those limits would also be the likely + // limits for, once again, aesthetic good sense in the layout of a chart using these connectors. + // + // the second possible change is actually two possible changes: firstly, it is possible we should gradually + // decrease the 'curviness' as the distance between the anchors decreases; start tailing it off to 0 at some + // point (which should be configurable). secondly, we might slightly increase the 'curviness' for connectors + // with respect to how far their anchor is from the center of its respective face. this could either look cool, + // or stupid, and may indeed work only in a way that is so subtle as to have been a waste of time. + // + + var _midx = (_sx + _tx) / 2, _midy = (_sy + _ty) / 2, + m2 = (-1 * _midx) / _midy, theta2 = Math.atan(m2), + dy = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.sin(theta2)), + dx = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.cos(theta2)), + segment = _segment(_sx, _sy, _tx, _ty), + distance = Math.sqrt(Math.pow(_tx - _sx, 2) + Math.pow(_ty - _sy, 2)); + + // calculate the control point. this code will be where we'll put in a rudimentary element avoidance scheme; it + // will work by extending the control point to force the curve to be, um, curvier. + _controlPoint = _findControlPoint(_midx, + _midy, + segment, + sourcePos, + targetPos, + curviness, curviness, + distance, + proximityLimit); + + + var requiredWidth = Math.max(Math.abs(_controlPoint[0] - _sx) * 3, Math.abs(_controlPoint[0] - _tx) * 3, Math.abs(_tx-_sx), 2 * lineWidth, minWidth), + requiredHeight = Math.max(Math.abs(_controlPoint[1] - _sy) * 3, Math.abs(_controlPoint[1] - _ty) * 3, Math.abs(_ty-_sy), 2 * lineWidth, minWidth); + + if (w < requiredWidth) { + var dw = requiredWidth - w; + x -= (dw / 2); + _sx += (dw / 2); + _tx += (dw / 2); + w = requiredWidth; + _controlPoint[0] += (dw / 2); + } + + if (h < requiredHeight) { + var dh = requiredHeight - h; + y -= (dh / 2); + _sy += (dh / 2); + _ty += (dh / 2); + h = requiredHeight; + _controlPoint[1] += (dh / 2); + } + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty, _controlPoint[0], _controlPoint[1] ]; + } + else { + isLoopback = true; + // a loopback connector. draw an arc from one anchor to the other. + // i guess we'll do this the same way as the others. just the control point will be a fair distance away. + var x1 = sourcePos[0], x2 = sourcePos[0], y1 = sourcePos[1] - margin, y2 = sourcePos[1] - margin, + cx = x1, cy = y1 - loopbackRadius; + + // canvas sizing stuff, to ensure the whole painted area is visible. + w = ((2 * lineWidth) + (4 * loopbackRadius)), h = ((2 * lineWidth) + (4 * loopbackRadius)); + x = cx - loopbackRadius - lineWidth - loopbackRadius, y = cy - loopbackRadius - lineWidth - loopbackRadius; + currentPoints = [ x, y, w, h, cx-x, cy-y, loopbackRadius, clockwise, x1-x, y1-y, x2-x, y2-y]; + } + + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_tx, y:_ty }, + { x:_controlPoint[0], y:_controlPoint[1] }, + { x:_controlPoint[0] + 1, y:_controlPoint[1] + 1}, + { x:_sx, y:_sy } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + if (isLoopback) { + + if (location > 0 && location < 1) location = 1- location; + +// current points are [ x, y, width, height, center x, center y, radius, clockwise, startx, starty, endx, endy ] + // so the path length is the circumference of the circle + //var len = 2 * Math.PI * currentPoints[6], + // map 'location' to an angle. 0 is PI/2 when the connector is on the top face; if we + // support other faces it will have to be calculated for each one. 1 is also PI/2. + // 0.5 is -PI/2. + var startAngle = (location * 2 * Math.PI) + (Math.PI / 2), + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + + } + else return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + if (isLoopback) + return Math.atan(location * 2 * Math.PI); + else + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + if (isLoopback) { + + if (location > 0 && location < 1) location = 1- location; + + var circumference = 2 * Math.PI * currentPoints[6], + arcSpan = distance / circumference * 2 * Math.PI, + startAngle = (location * 2 * Math.PI) - arcSpan + (Math.PI / 2), + + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + } + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + }; + + /* + * Canvas state machine renderer. + */ + jsPlumb.Connectors.canvas.StateMachine = function(params) { + params = params || {}; + var self = this, drawGuideline = params.drawGuideline || true, avoidSelector = params.avoidSelector; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.CanvasConnector.apply(this, arguments); + + + this._paint = function(dimensions) { + + if (dimensions.length == 10) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.quadraticCurveTo(dimensions[8], dimensions[9], dimensions[6], dimensions[7]); + self.ctx.stroke(); + + /*/ draw the guideline + if (drawGuideline) { + self.ctx.save(); + self.ctx.beginPath(); + self.ctx.strokeStyle = "silver"; + self.ctx.lineWidth = 1; + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + self.ctx.restore(); + } + //*/ + } + else { + // a loopback connector + self.ctx.save(); + self.ctx.beginPath(); + var startAngle = 0, // Starting point on circle + endAngle = 2 * Math.PI, // End point on circle + clockwise = dimensions[7]; // clockwise or anticlockwise + self.ctx.arc(dimensions[4],dimensions[5],dimensions[6],0, endAngle, clockwise); + self.ctx.stroke(); + self.ctx.closePath(); + self.ctx.restore(); + } + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + /* + * SVG State Machine renderer + */ + jsPlumb.Connectors.svg.StateMachine = function() { + var self = this; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.SvgConnector.apply(this, arguments); + this.getPath = function(d) { + + if (d.length == 10) + return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[8] + " " + d[9] + " " + d[6] + " " + d[7]; + else { + // loopback + return "M" + (d[8] + 4) + " " + d[9] + " A " + d[6] + " " + d[6] + " 0 1,0 " + (d[8]-4) + " " + d[9]; + } + }; + }; + + /* + * VML state machine renderer + */ + jsPlumb.Connectors.vml.StateMachine = function() { + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.VmlConnector.apply(this, arguments); + var _conv = jsPlumb.vml.convertValue; + this.getPath = function(d) { + if (d.length == 10) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + } + else { + // loopback + var left = _conv(d[8] - d[6]), + top = _conv(d[9] - (2 * d[6])), + right = left + _conv(2 * d[6]), + bottom = top + _conv(2 * d[6]), + posString = left + "," + top + "," + right + "," + bottom; + + var o = "ar " + posString + "," + _conv(d[8]) + "," + + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + " e"; + + return o; + } + }; + }; + +})(); + +/* + // now for a rudimentary avoidance scheme. TODO: how to set this in a cross-library way? + // if (avoidSelector) { + // var testLine = new Line(sourcePos[0] + _sx,sourcePos[1] + _sy,sourcePos[0] + _tx,sourcePos[1] + _ty); + // var sel = jsPlumb.getSelector(avoidSelector); + // for (var i = 0; i < sel.length; i++) { + // var id = jsPlumb.getId(sel[i]); + // if (id != sourceEndpoint.elementId && id != targetEndpoint.elementId) { + // o = jsPlumb.getOffset(id), s = jsPlumb.getSize(id); +// +// if (o && s) { +// var collision = testLine.rectIntersect(o.left,o.top,s[0],s[1]); +// if (collision) { + // set the control point to be a certain distance from the midpoint of the two points that + // the line crosses on the rectangle. + // TODO where will this 75 number come from? + // _controlX = collision[2][0] + (75 * collision[3][0]); + // / _controlY = collision[2][1] + (75 * collision[3][1]); +// } +// } + // } + // } + //} + *//* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the VML renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + // http://ajaxian.com/archives/the-vml-changes-in-ie-8 + // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/ + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + + var vmlAttributeMap = { + "stroke-linejoin":"joinstyle", + "joinstyle":"joinstyle", + "endcap":"endcap", + "miterlimit":"miterlimit" + }; + + if (document.createStyleSheet) { + + // this is the style rule for IE7/6: it uses a CSS class, tidy. + document.createStyleSheet().addRule(".jsplumb_vml", "behavior:url(#default#VML);position:absolute;"); + + // these are for VML in IE8. you have to explicitly call out which elements + // you're going to expect to support VML! + // + document.createStyleSheet().addRule("jsplumb\\:textbox", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:oval", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:rect", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:stroke", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:shape", "behavior:url(#default#VML);position:absolute;"); + + // in this page it is also mentioned that IE requires the extra arg to the namespace + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either. + // var iev = document.documentMode; + //if (!iev || iev < 8) + document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); + //else + // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML"); + } + + jsPlumb.vml = {}; + + var scale = 1000, + _atts = function(o, atts) { + for (var i in atts) { + // IE8 fix: setattribute does not work after an element has been added to the dom! + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + //o.setAttribute(i, atts[i]); + + o[i] = atts[i]; + } + }, + _node = function(name, d, atts) { + atts = atts || {}; + var o = document.createElement("jsplumb:" + name); + o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml"; + _pos(o, d); + _atts(o, atts); + return o; + }, + _pos = function(o,d) { + o.style.left = d[0] + "px"; + o.style.top = d[1] + "px"; + o.style.width= d[2] + "px"; + o.style.height= d[3] + "px"; + o.style.position = "absolute"; + }, + _conv = jsPlumb.vml.convertValue = function(v) { + return Math.floor(v * scale); + }, + // tests if the given style is "transparent" and then sets the appropriate opacity node to 0 if so, + // or 1 if not. TODO in the future, support variable opacity. + _maybeSetOpacity = function(styleToWrite, styleToCheck, type, component) { + if ("transparent" === styleToCheck) + component.setOpacity(type, "0.0"); + else + component.setOpacity(type, "1.0"); + }, + _applyStyles = function(node, style, component) { + var styleToWrite = {}; + if (style.strokeStyle) { + styleToWrite["stroked"] = "true"; + var strokeColor = jsPlumb.util.convertStyle(style.strokeStyle, true); + styleToWrite["strokecolor"] = strokeColor; + _maybeSetOpacity(styleToWrite, strokeColor, "stroke", component); + styleToWrite["strokeweight"] = style.lineWidth + "px"; + } + else styleToWrite["stroked"] = "false"; + + if (style.fillStyle) { + styleToWrite["filled"] = "true"; + var fillColor = jsPlumb.util.convertStyle(style.fillStyle, true); + styleToWrite["fillcolor"] = fillColor; + _maybeSetOpacity(styleToWrite, fillColor, "fill", component); + } + else styleToWrite["filled"] = "false"; + + if(style["dashstyle"]) { + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = style["dashstyle"]; + } + else if (style["stroke-dasharray"] && style["lineWidth"]) { + var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",", + parts = style["stroke-dasharray"].split(sep), + styleToUse = ""; + for(var i = 0; i < parts.length; i++) { + styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep); + } + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = styleToUse; + } + + _atts(node, styleToWrite); + }, + /* + * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. + */ + VmlComponent = function() { + var self = this; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.opacityNodes = { + "stroke":null, + "fill":null + }; + this.initOpacityNodes = function(vml) { + self.opacityNodes["stroke"] = _node("stroke", [0,0,1,1], {opacity:"0.0"}); + self.opacityNodes["fill"] = _node("fill", [0,0,1,1], {opacity:"0.0"}); + vml.appendChild(self.opacityNodes["stroke"]); + vml.appendChild(self.opacityNodes["fill"]); + }; + this.setOpacity = function(type, value) { + var node = self.opacityNodes[type]; + if (node) node["opacity"] = "" + value; + }; + }, + /* + * Base class for Vml connectors. extends VmlComponent. + */ + VmlConnector = jsPlumb.VmlConnector = function(params) { + var self = this; + self.strokeNode = null; + self.canvas = null; + VmlComponent.apply(this, arguments); + var clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : ""); + this.paint = function(d, style, anchor) { + if (style != null) { + var path = self.getPath(d), p = { "path":path }; + + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle : jsPlumb.util.convertStyle(style.outlineColor), + lineWidth : outlineStrokeWidth + }; + for (var aa in vmlAttributeMap) outlineStyle[aa] = style[aa]; + + if (self.bgCanvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.bgCanvas = _node("shape", d, p); + jsPlumb.appendElement(self.bgCanvas, params.parent); + _pos(self.bgCanvas, d); + displayElements.push(self.bgCanvas); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.bgCanvas, d); + _atts(self.bgCanvas, p); + } + + _applyStyles(self.bgCanvas, outlineStyle, self); + } + + if (self.canvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + if (self.tooltip) p["label"] = self.tooltip; + self.canvas = _node("shape", d, p); + jsPlumb.appendElement(self.canvas, params.parent); + displayElements.push(self.canvas); + + self.attachListeners(self.canvas, self); + + self.initOpacityNodes(self.canvas, ["stroke"]); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.canvas, d); + _atts(self.canvas, p); + } + + _applyStyles(self.canvas, style, self); + } + }; + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + self.canvas.parentNode.appendChild(el); + displayElements.push(el); + }; + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + }, + /* + * + * Base class for Vml Endpoints. extends VmlComponent. + * + */ + VmlEndpoint = function(params) { + VmlComponent.apply(this, arguments); + var vml = null, self = this, opacityStrokeNode = null, opacityFillNode = null; + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.appendElement(self.canvas, params.parent); + if (self.tooltip) self.canvas.setAttribute("label", self.tooltip); + + this.paint = function(d, style, anchor) { + var p = { }; + + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (vml == null) { + p["class"] = jsPlumb.endpointClass; + vml = self.getVml([0,0, d[2], d[3]], p, anchor); + self.canvas.appendChild(vml); + self.attachListeners(vml, self); + + self.initOpacityNodes(vml, ["fill"]); + } + else { + //p["coordsize"] = "1,1";//(d[2] * scale) + "," + (d[3] * scale); again, unsure. + _pos(vml, [0,0, d[2], d[3]]); + _atts(vml, p); + } + + _applyStyles(vml, style, self); + }; + + this.reattachListeners = function() { + if (vml) self.reattachListenersForElement(vml, self); + }; + }; + + jsPlumb.Connectors.vml.Bezier = function() { + jsPlumb.Connectors.Bezier.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Straight = function() { + jsPlumb.Connectors.Straight.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Flowchart = function() { + jsPlumb.Connectors.Flowchart.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l"; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]); + } + // finally draw a line to the end + p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e"; + return p; + }; + }; + + jsPlumb.Endpoints.vml.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("oval", d, atts); }; + }; + + jsPlumb.Endpoints.vml.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("rect", d, atts); }; + }; + + /* + * VML Image Endpoint is the same as the default image endpoint. + */ + jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image; + + /** + * placeholder for Blank endpoint in vml renderer. + */ + jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank; + + /** + * VML Label renderer. uses the default label renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label; + + var AbstractVmlArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + VmlComponent.apply(this, arguments); + var self = this, path = null; + self.canvas = null; + var getPath = function(d, connectorDimensions) { + return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) + + " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + + " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + + " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + + " x e"; + }; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) { + var p = {}; + if (strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] = jsPlumb.util.convertStyle(strokeStyle, true); + } + if (lineWidth) p["strokeweight"] = lineWidth + "px"; + if (fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = fillStyle; + } + var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + w = Math.abs(xmax - xmin), + h = Math.abs(ymax - ymin), + dim = [xmin, ymin, w, h]; + + // for VML, we create overlays using shapes that have the same dimensions and + // coordsize as their connector - overlays calculate themselves relative to the + // connector (it's how it's been done since the original canvas implementation, because + // for canvas that makes sense). + p["path"] = getPath(d, connectorDimensions); + p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale); + + dim[0] = connectorDimensions[0]; + dim[1] = connectorDimensions[1]; + dim[2] = connectorDimensions[2]; + dim[3] = connectorDimensions[3]; + + if (self.canvas == null) { + //p["class"] = jsPlumb.overlayClass; // TODO currentInstance? + self.canvas = _node("shape", dim, p); + connector.appendDisplayElement(self.canvas); + self.attachListeners(self.canvas, connector); + } + else { + _pos(self.canvas, dim); + _atts(self.canvas, p); + } + }; + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + }; + + jsPlumb.Overlays.vml.Arrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.vml.PlainArrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.vml.Diamond = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the SVG renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +/** + * SVG support for jsPlumb. + * + * things to investigate: + * + * gradients: https://developer.mozilla.org/en/svg_in_html_introduction + * css:http://tutorials.jenkov.com/svg/svg-and-css.html + * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath + * pointer events: https://developer.mozilla.org/en/css/pointer-events + * + * IE9 hover jquery: http://forum.jquery.com/topic/1-6-2-broke-svg-hover-events + * + */ +;(function() { + + var svgAttributeMap = { + "joinstyle":"stroke-linejoin", + "stroke-linejoin":"stroke-linejoin", + "stroke-dashoffset":"stroke-dashoffset", + "stroke-linecap":"stroke-linecap" + }, + STROKE_DASHARRAY = "stroke-dasharray", + DASHSTYLE = "dashstyle", + LINEAR_GRADIENT = "linearGradient", + RADIAL_GRADIENT = "radialGradient", + FILL = "fill", + STOP = "stop", + STROKE = "stroke", + STROKE_WIDTH = "stroke-width", + STYLE = "style", + NONE = "none", + JSPLUMB_GRADIENT = "jsplumb_gradient_", + LINE_WIDTH = "lineWidth", + ns = { + svg:"http://www.w3.org/2000/svg", + xhtml:"http://www.w3.org/1999/xhtml" + }, + _attr = function(node, attributes) { + for (var i in attributes) + node.setAttribute(i, "" + attributes[i]); + }, + _node = function(name, attributes) { + var n = document.createElementNS(ns.svg, name); + attributes = attributes || {}; + attributes["version"] = "1.1"; + attributes["xmnls"] = ns.xhtml; + _attr(n, attributes); + return n; + }, + _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; }, + _clearGradient = function(parent) { + for (var i = 0; i < parent.childNodes.length; i++) { + if (parent.childNodes[i].tagName == LINEAR_GRADIENT || parent.childNodes[i].tagName == RADIAL_GRADIENT) + parent.removeChild(parent.childNodes[i]); + } + }, + _updateGradient = function(parent, node, style, dimensions, uiComponent) { + var id = JSPLUMB_GRADIENT + uiComponent._jsPlumb.idstamp(); + // first clear out any existing gradient + _clearGradient(parent); + // this checks for an 'offset' property in the gradient, and in the absence of it, assumes + // we want a linear gradient. if it's there, we create a radial gradient. + // it is possible that a more explicit means of defining the gradient type would be + // better. relying on 'offset' means that we can never have a radial gradient that uses + // some default offset, for instance. + if (!style.gradient.offset) { + var g = _node(LINEAR_GRADIENT, {id:id}); + parent.appendChild(g); + } + else { + var g = _node(RADIAL_GRADIENT, { + id:id + }); + parent.appendChild(g); + } + + // the svg radial gradient seems to treat stops in the reverse + // order to how canvas does it. so we want to keep all the maths the same, but + // iterate the actual style declarations in reverse order, if the x indexes are not in order. + for (var i = 0; i < style.gradient.stops.length; i++) { + // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of + // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments. + // so, not too concerned about leaving this in for now. + var styleToUse = i; + if (dimensions.length == 8) + styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i; + else + styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i; + var stopColor = jsPlumb.util.convertStyle(style.gradient.stops[styleToUse][1], true); + var s = _node(STOP, {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor}); + g.appendChild(s); + } + var applyGradientTo = style.strokeStyle ? STROKE : FILL; + node.setAttribute(STYLE, applyGradientTo + ":url(#" + id + ")"); + }, + _applyStyles = function(parent, node, style, dimensions, uiComponent) { + + if (style.gradient) { + _updateGradient(parent, node, style, dimensions, uiComponent); + } + else { + // make sure we clear any existing gradient + _clearGradient(parent); + node.setAttribute(STYLE, ""); + } + + node.setAttribute(FILL, style.fillStyle ? jsPlumb.util.convertStyle(style.fillStyle, true) : NONE); + node.setAttribute(STROKE, style.strokeStyle ? jsPlumb.util.convertStyle(style.strokeStyle, true) : NONE); + if (style.lineWidth) { + node.setAttribute(STROKE_WIDTH, style.lineWidth); + } + + // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like + // the syntax in VML but is actually kind of nasty: values are given in the pixel + // coordinate space, whereas in VML they are multiples of the width of the stroked + // line, which makes a lot more sense. for that reason, jsPlumb is supporting both + // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from + // VML, which will be the preferred method. the code below this converts a dashstyle + // attribute given in terms of stroke width into a pixel representation, by using the + // stroke's lineWidth. + if (style[DASHSTYLE] && style[LINE_WIDTH] && !style[STROKE_DASHARRAY]) { + var sep = style[DASHSTYLE].indexOf(",") == -1 ? " " : ",", + parts = style[DASHSTYLE].split(sep), + styleToUse = ""; + parts.forEach(function(p) { + styleToUse += (Math.floor(p * style.lineWidth) + sep); + }); + node.setAttribute(STROKE_DASHARRAY, styleToUse); + } + else if(style[STROKE_DASHARRAY]) { + node.setAttribute(STROKE_DASHARRAY, style[STROKE_DASHARRAY]); + } + + // extra attributes such as join type, dash offset. + for (var i in svgAttributeMap) { + if (style[i]) { + node.setAttribute(svgAttributeMap[i], style[i]); + } + } + }, + _decodeFont = function(f) { + var r = /([0-9].)(p[xt])\s(.*)/; + var bits = f.match(r); + return {size:bits[1] + bits[2], font:bits[3]}; + }, + _classManip = function(el, add, clazz) { + var classesToAddOrRemove = clazz.split(" "), + className = el.className, + curClasses = className.baseVal.split(" "); + + for (var i = 0; i < classesToAddOrRemove.length; i++) { + if (add) { + if (curClasses.indexOf(classesToAddOrRemove[i]) == -1) + curClasses.push(classesToAddOrRemove[i]); + } + else { + var idx = curClasses.indexOf(classesToAddOrRemove[i]); + if (idx != -1) + curClasses.splice(idx, 1); + } + } + + el.className.baseVal = curClasses.join(" "); + }, + _addClass = function(el, clazz) { + _classManip(el, true, clazz); + }, + _removeClass = function(el, clazz) { + _classManip(el, false, clazz); + }; + + /** + utility methods for other objects to use. + */ + jsPlumb.util.svg = { + addClass:_addClass, + removeClass:_removeClass + }; + + /* + * Base class for SVG components. + */ + //var SvgComponent = function(cssClass, originalArgs, pointerEventsSpec) { + var SvgComponent = function(params) { + var self = this, + pointerEventsSpec = params.pointerEventsSpec || "all"; + jsPlumb.jsPlumbUIComponent.apply(this, params.originalArgs); + self.canvas = null, self.path = null, self.svg = null; + + var clazz = params.cssClass + " " + (params.originalArgs[0].cssClass || ""), + svgParams = { + "style":"", + "width":0, + "height":0, + "pointer-events":pointerEventsSpec, + "position":"absolute" + }; + if (self.tooltip) svgParams["label"] = self.tooltip; + self.svg = _node("svg", svgParams); + if (params.useDivWrapper) { + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.sizeCanvas(self.canvas,0,0,1,1); + self.canvas.className = clazz; + if (self.tooltip) self.canvas.setAttribute("label", self.tooltip); + } + else { + _attr(self.svg, { "class":clazz }); + self.canvas = self.svg; + } + + jsPlumb.appendElement(self.canvas, params.originalArgs[0]["parent"]); + if (params.useDivWrapper) self.canvas.appendChild(self.svg); + + // TODO this displayElement stuff is common between all components, across all + // renderers. would be best moved to jsPlumbUIComponent. + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + this.paint = function(d, style, anchor) { + if (style != null) { + var x = d[0], y = d[1]; + if (params.useDivWrapper) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + x = 0, y = 0; + } + _attr(self.svg, { + "style":_pos([x, y, d[2], d[3]]), + "width": d[2], + "height": d[3] + }); + self._paint.apply(this, arguments); + } + }; + }; + + /* + * Base class for SVG connectors. + */ + var SvgConnector = jsPlumb.SvgConnector = function(params) { + var self = this; + SvgComponent.apply(this, [ { cssClass:params["_jsPlumb"].connectorClass, originalArgs:arguments, pointerEventsSpec:"none", tooltip:params.tooltip } ]); + this._paint = function(d, style) { + var p = self.getPath(d), a = { "d":p }, outlineStyle = null; + a["pointer-events"] = "all"; + + // outline style. actually means drawing an svg object underneath the main one. + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = jsPlumb.CurrentLibrary.extend({}, style); + outlineStyle.strokeStyle = jsPlumb.util.convertStyle(style.outlineColor); + outlineStyle.lineWidth = outlineStrokeWidth; + + if (self.bgPath == null) { + self.bgPath = _node("path", a); + self.svg.appendChild(self.bgPath); + self.attachListeners(self.bgPath, self); + } + else { + _attr(self.bgPath, a); + } + + _applyStyles(self.svg, self.bgPath, outlineStyle, d, self); + } + + + // test - see below + // a["clip-path"]= "url(#testClip)"; + + if (self.path == null) { + self.path = _node("path", a); + self.svg.appendChild(self.path); + self.attachListeners(self.path, self); + + /* + this is a test of a clip path. i'm looking into using one of these to animate a jsplumb connection. + you could do this by walking along the line, stepping along a little at a time, and setting the clip + path to extend as far as that point. + + self.clip = _node("clipPath", {id:"testClip", clipPathUnits:"objectBoundingBox"}); + self.svg.appendChild(self.clip); + self.clip.appendChild(_node("rect", { + x:"0",y:"0",width:"0.5",height:"1" + })); + */ + } + else { + _attr(self.path, a); + } + + _applyStyles(self.svg, self.path, style, d, self); + }; + + this.reattachListeners = function() { + if (self.bgPath) self.reattachListenersForElement(self.bgPath, self); + if (self.path) self.reattachListenersForElement(self.path, self); + }; + + }; + + /* + * SVG Bezier Connector + */ + jsPlumb.Connectors.svg.Bezier = function(params) { + jsPlumb.Connectors.Bezier.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]; }; + }; + + /* + * SVG straight line Connector + */ + jsPlumb.Connectors.svg.Straight = function(params) { + jsPlumb.Connectors.Straight.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; }; + }; + + jsPlumb.Connectors.svg.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "M " + dimensions[4] + "," + dimensions[5]; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)]; + } + // finally draw a line to the end + p = p + " " + dimensions[6] + "," + dimensions[7]; + return p; + }; + }; + + /* + * Base class for SVG endpoints. + */ + var SvgEndpoint = function(params) { + var self = this; + SvgComponent.apply(this, [ { + cssClass:params["_jsPlumb"].endpointClass, + originalArgs:arguments, + pointerEventsSpec:"all", + useDivWrapper:true + } ]); + this._paint = function(d, style) { + var s = jsPlumb.extend({}, style); + if (s.outlineColor) { + s.strokeWidth = s.outlineWidth; + s.strokeStyle = jsPlumb.util.convertStyle(s.outlineColor, true); + } + + if (self.node == null) { + self.node = self.makeNode(d, s); + self.svg.appendChild(self.node); + self.attachListeners(self.node, self); + } + _applyStyles(self.svg, self.node, s, d, self); + _pos(self.node, d); + }; + + this.reattachListeners = function() { + if (self.node) self.reattachListenersForElement(self.node, self); + }; + }; + + /* + * SVG Dot Endpoint + */ + jsPlumb.Endpoints.svg.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("circle", { + "cx" : d[2] / 2, + "cy" : d[3] / 2, + "r" : d[2] / 2 + }); + }; + }; + + /* + * SVG Rectangle Endpoint + */ + jsPlumb.Endpoints.svg.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("rect", { + "width":d[2], + "height":d[3] + }); + }; + }; + + /* + * SVG Image Endpoint is the default image endpoint. + */ + jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image; + /* + * Blank endpoint in svg renderer is the default Blank endpoint. + */ + jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank; + /* + * Label endpoint in svg renderer is the default Label endpoint. + */ + jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label; + + var AbstractSvgArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + this.isAppendedAtTopLevel = false; + var self = this, path =null; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + } + var clazz = originalArgs && (originalArgs.length == 1) ? (originalArgs[0].cssClass || "") : ""; + + _attr(path, { + "d" : makePath(d), + "class" : clazz, + stroke : strokeStyle ? strokeStyle : null, + fill : fillStyle ? fillStyle : null + }); + }; + var makePath = function(d) { + return "M" + d.hxy.x + "," + d.hxy.y + + " L" + d.tail[0].x + "," + d.tail[0].y + + " L" + d.cxy.x + "," + d.cxy.y + + " L" + d.tail[1].x + "," + d.tail[1].y + + " L" + d.hxy.x + "," + d.hxy.y; + }; + this.reattachListeners = function() { + if (path) self.reattachListenersForElement(path, self); + }; + }; + + jsPlumb.Overlays.svg.Arrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.svg.PlainArrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.svg.Diamond = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; + + // a test + jsPlumb.Overlays.svg.GuideLines = function() { + var path = null, self = this, path2 = null, p1_1, p1_2; + jsPlumb.Overlays.GuideLines.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + + p1_1 = _node("path"); + connector.svg.appendChild(p1_1); + self.attachListeners(p1_1, connector); + self.attachListeners(p1_1, self); + + p1_2 = _node("path"); + connector.svg.appendChild(p1_2); + self.attachListeners(p1_2, connector); + self.attachListeners(p1_2, self); + + } + + _attr(path, { + "d" : makePath(d[0], d[1]), + stroke : "red", + fill : null + }); + + _attr(p1_1, { + "d" : makePath(d[2][0], d[2][1]), + stroke : "blue", + fill : null + }); + + _attr(p1_2, { + "d" : makePath(d[3][0], d[3][1]), + stroke : "green", + fill : null + }); + }; + + var makePath = function(d1, d2) { + return "M " + d1.x + "," + d1.y + + " L" + d2.x + "," + d2.y; + }; + + }; +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the HTML5 canvas renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + +// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS ******************************************************************* + + // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. + var _connectionBeingDragged = null, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, + _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }; + + /* + * Class:CanvasMouseAdapter + * Provides support for mouse events on canvases. + */ + var CanvasMouseAdapter = function() { + var self = this; + self.overlayPlacements = []; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + jsPlumb.EventGenerator.apply(this, arguments); + /** + * returns whether or not the given event is ojver a painted area of the canvas. + */ + this._over = function(e) { + var o = _getOffset(_getElementObject(self.canvas)), + pageXY = _pageXY(e), + x = pageXY[0] - o.left, y = pageXY[1] - o.top; + if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + return false; + }; + + var _mouseover = false, _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false, + _nullSafeHasClass = function(el, clazz) { + return el != null && _hasClass(el, clazz); + }; + this.mousemove = function(e) { + var pageXY = _pageXY(e), clientXY = _clientXY(e), + ee = document.elementFromPoint(clientXY[0], clientXY[1]), + eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); + var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); + if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.fire("mouseenter", self, e); + return true; + } + // TODO here there is a remote chance that the overlay the mouse moved onto + // is actually not an overlay for the current component. a more thorough check would + // be to ensure the overlay belonged to the current component. + else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { + _mouseover = false; + self.fire("mouseexit", self, e); + } + self.fire("mousemove", self, e); + }; + + this.click = function(e) { + // to filter right click in FF, i could compare e.button. 0 means left mouse button; 1 middle, 2 right. + //console.log(e.button); + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); + self.fire("mousedown", self, e); + } + }; + + this.mouseup = function(e) { + _mouseDown = false; + self.fire("mouseup", self, e); + }; + }; + + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + jsPlumb.appendElement(canvas, params.parent); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + params["_jsPlumb"].getId(canvas, params.uuid); + if (params.tooltip) canvas.setAttribute("label", params.tooltip); + + return canvas; + }; + + /** + * Class:CanvasConnector + * Superclass for Canvas Connector renderers. + */ + var CanvasConnector = jsPlumb.CanvasConnector = function(params) { + + CanvasMouseAdapter.apply(this, arguments); + + var _paintOneStyle = function(dim, aStyle) { + self.ctx.save(); + jsPlumb.extend(self.ctx, aStyle); + if (aStyle.gradient) { + var g = self.createGradient(dim, self.ctx); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + self.ctx.strokeStyle = g; + } + self._paint(dim); + self.ctx.restore(); + }; + + var self = this, + clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:params.tooltip + }); + self.ctx = self.canvas.getContext("2d"); + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { return displayElements; }; + this.appendDisplayElement = function(el) { displayElements.push(el); }; + + self.paint = function(dim, style) { + if (style != null) { + jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + _paintOneStyle(dim, outlineStyle); + } + _paintOneStyle(dim, style); + } + }; + }; + + /** + * Class:CanvasEndpoint + * Superclass for Canvas Endpoint renderers. + */ + var CanvasEndpoint = function(params) { + var self = this; + CanvasMouseAdapter.apply(this, arguments); + var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""), + canvasParams = { + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:self.tooltip + }; + self.canvas = _newCanvas(canvasParams); + self.ctx = self.canvas.getContext("2d"); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + } + + self._paint.apply(this, arguments); + }; + }; + + jsPlumb.Endpoints.canvas.Dot = function(params) { + jsPlumb.Endpoints.Dot.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + var self = this, + parseValue = function(value) { + try { return parseInt(value); } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }, + calculateAdjustments = function(gradient) { + var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius; + gradient.offset && (offsetAdjustment = parseValue(gradient.offset)); + gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius)); + return [offsetAdjustment, innerRadius]; + }; + this._paint = function(d, style, anchor) { + if (style != null) { + var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + if (style.gradient) { + var adjustments = calculateAdjustments(style.gradient), + yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0], + xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0], + g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + ctx.beginPath(); + ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + } + }; + }; + + jsPlumb.Endpoints.canvas.Rectangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) { + + var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + + /* canvas gradient */ + if (style.gradient) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, d[2], d[3]); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + jsPlumb.Endpoints.canvas.Triangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Triangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) + { + var width = d[2], height = d[3], x = d[0], y = d[1], + ctx = self.canvas.getContext('2d'), + offsetX = 0, offsetY = 0, angle = 0, + orientation = anchor.getOrientation(self); + + if( orientation[0] == 1 ) { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = style.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + /* + * Canvas Image Endpoint: uses the default version, which creates an tag. + */ + jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image; + + /* + * Blank endpoint in all renderers is just the default Blank endpoint. + */ + jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank; + + /* + * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Bezier = function() { + var self = this; + jsPlumb.Connectors.Bezier.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO i doubt this handles the case that source and target are swapped. + this.createGradient = function(dim, ctx, swap) { + return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + /* + * Canvas straight line Connector. Draws a straight line onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Straight = function() { + var self = this; + jsPlumb.Connectors.Straight.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO this does not handle the case that src and target are swapped. + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + jsPlumb.Connectors.canvas.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + +// ********************************* END OF CANVAS RENDERERS ******************************************************************* + + jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label; + + /** + * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. + */ + var CanvasOverlay = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }; + + var AbstractCanvasArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + CanvasOverlay.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + var ctx = connector.ctx; + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(d.hxy.x, d.hxy.y); + ctx.lineTo(d.tail[0].x, d.tail[0].y); + ctx.lineTo(d.cxy.x, d.cxy.y); + ctx.lineTo(d.tail[1].x, d.tail[1].y); + ctx.lineTo(d.hxy.x, d.hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + }; + + jsPlumb.Overlays.canvas.Arrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.canvas.PlainArrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.canvas.Diamond = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the MooTools adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function(now) { + this.parent(now); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, _scope, value) { + var l = list[_scope]; + if (!l) { + l = []; + list[_scope] = l; + } + l.push(value); + }; + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + var _getElementObject = function(el) { + return $(el); + }, + + _removeNonPermanentDroppables = function(drag) { + /* + // remove non-permanent droppables from all arrays + var dbs = _droppables[drag.scope], d = []; + if (dbs) { + var d = []; + for (var i=0; i < dbs.length; i++) { + var isPermanent = dbs[i].getAttribute("_isPermanentDroppable"); + if (isPermanent === "true") d.push(dbs[i]); + } + dbs.splice(0, dbs.length); + _droppables[drag.scope] = d; + } + d = []; + // clear out transient droppables from the drag itself + for(var i = 0; i < drag.droppables.length; i++) { + var isPermanent = drag.droppables[i].getAttribute("_isPermanentDroppable"); + if (isPermanent === "true") d.push(drag.droppables[i]); + } + drag.droppables.splice(0, drag.droppables.length); // release old ones + drag.droppables = d; + //*/ + }; + + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el) + try { + if (el.className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.addClass(el, clazz); + } + else el.addClass(clazz); + } + catch (e) { + // SVGAnimatedString not supported; no problem. + el.addClass(clazz); + } + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + m.start(properties); + }, + + appendElement : function(child, parent) { + _getElementObject(parent).grab(child); + }, + + bind : function(el, event, callback) { + el = _getElementObject(el); + el.addEvent(event, callback); + }, + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.event.clientX, eventObject.event.clientY]; + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el), + drags = _draggablesById[id]; + return drags[0].scope; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getElementObject : _getElementObject, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + getPageXY : function(eventObject) { + return [eventObject.event.pageX, eventObject.event.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).getParent(); + }, + + getScrollLeft : function(el) { + return null; + }, + + getScrollTop : function(el) { + return null; + }, + + getSelector : function(spec) { + return $$(spec); + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e != null ? e.tagName : null; + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[0], + p = jsPlumb.CurrentLibrary.getElementObject(ui).getPosition(); + return { left:p.x, top:p.y }; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options, isPlumbedComponent) { + var id = jsPlumb.getId(el); + var drag = _draggablesById[id]; + if (!drag) { + var originalZIndex = 0, + originalCursor = null, + dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + + options['onStart'] = jsPlumb.wrap(options['onStart'], function() { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + drag.originalZIndex = originalZIndex; + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + + _removeNonPermanentDroppables(drag); + }); + + // DROPPABLES - only relevant if this is a plumbed component, ie. not just the result of the user making some DOM element + // draggable. this is the only library adapter that has to care about this parameter. + var scope = "" + (options["scope"] || jsPlumb.Defaults.Scope), + filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }, + droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + + if (isPlumbedComponent) { + + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop'); + } + }; + } + else + options["droppables"] = []; + + drag = new Drag.Move(el, options); + drag.scope = scope; + drag.originalZIndex = originalZIndex; + _add(_draggablesById, el.get("id"), drag); + // again, only keep a record of this for scope stuff if this is a plumbed component (an endpoint) + if (isPlumbedComponent) { + _add(_draggablesByScope, scope, drag); + } + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + initDroppable : function(el, options, isPlumbedComponent, isPermanent) { + var scope = options["scope"]; + _add(_droppables, scope, el); + var id = jsPlumb.getId(el); + + el.setAttribute("_isPermanentDroppable", isPermanent); // we use this to clear out droppables on drag complete. + _droppableOptions[id] = options; + _droppableScopesById[id] = scope; + var filterFunc = function(entry) { return entry.element != el; }, + draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + isAlreadyDraggable : function(el) { + return _draggablesById[jsPlumb.getId(el)] != null; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + return (typeof Drag != undefined && typeof Drag.Move != undefined); + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el.className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.removeClass(el, clazz); + } + else el.removeClass(clazz); + } + catch (e) { + // SVGAnimatedString not supported; no problem. + el.removeClass(clazz); + } + }, + + removeElement : function(element, parent) { + var el = _getElementObject(element); + if (el) el.dispose(); // ?? + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + setDragScope : function(el, scope) { + var drag = _draggablesById[el.get("id")]; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + drag[0].droppables = droppables; + }, + + setOffset : function(el, o) { + _getElementObject(el).setPosition({x:o.left, y:o.top}); + }, + + stopDrag : function() { + for (var i in _draggablesById) { + for (var j = 0; j < _draggablesById[i].length; j++) { + var d = _draggablesById[i][j]; + d.stop(); + if (d.originalZIndex != 0) + d.element.setStyle("z-index", d.originalZIndex); + } + } + }, + + /** + * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself. + * the other libraries do not. yui, in fact, cannot even pass an original event. we have to pull out stuff + * from the originalEvent to put in an options object for YUI. + * @param el + * @param event + * @param originalEvent + */ + trigger : function(el, event, originalEvent) { + originalEvent.stopPropagation(); + _getElementObject(el).fireEvent(event, originalEvent); + }, + + unbind : function(el, event, callback) { + el = _getElementObject(el); + el.removeEvent(event, callback); + } + }; + + window.addEvent('domready', jsPlumb.init); +})(); +(function(){if("undefined"==typeof Math.sgn)Math.sgn=function(a){return 0==a?0:0n?n=l:lb.location)b.location=0;return t(a,b.location)},nearestPointOnCurve:function(a,b){var f=u(a,b);return{point:r(b,b.length-1,f.location,null,null),location:f.location}},pointOnCurve:p,pointAlongCurveFrom:function(a,b,f){return s(a,b,f).point},perpendicularToCurveAt:function(a,b,f,d){b=s(a,b,null==d?0:d);a=t(a,b.location);d=Math.atan(-1/a);a=f/2*Math.sin(d);f=f/2*Math.cos(d);return[{x:b.point.x+f,y:b.point.y+a},{x:b.point.x-f,y:b.point.y-a}]}}})(); \ No newline at end of file diff --git a/archive/1.3.4/yui.jsPlumb-1.3.4-RC1.js b/archive/1.3.4/yui.jsPlumb-1.3.4-RC1.js new file mode 100644 index 000000000..87f5588da --- /dev/null +++ b/archive/1.3.4/yui.jsPlumb-1.3.4-RC1.js @@ -0,0 +1,372 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the YUI3 adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +/** + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + */ +(function() { + + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function( v, b, s ) { + for( var i = +b || 0, l = this.length; i < l; i++ ) { + if( this[i]===v || s && this[i]==v ) { return i; } + } + return -1; + }; + } + + var Y; + + YUI().use('node', 'dd', 'anim', 'node-event-simulate', function(_Y) { + Y = _Y; + Y.on("domready", function() { jsPlumb.init(); }); + }); + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }, + ddEvents = [ "drag:mouseDown", "drag:afterMouseDown", "drag:mouseup", + "drag:align", "drag:removeHandle", "drag:addHandle", "drag:removeInvalid", "drag:addInvalid", + "drag:start", "drag:end", "drag:drag", "drag:over", "drag:enter", + "drag:exit", "drag:drophit", "drag:dropmiss", "drop:over", "drop:enter", "drop:exit", "drop:hit" + ], + animEvents = [ "tween" ], + /** + * helper function to curry callbacks for some element. + */ + _wrapper = function(fn) { + return function() { + try { + return fn.apply(this, arguments); + } + catch (e) { + alert("YYYYY"); + //console.log("wrap fail", e); + } + }; + }, + /** + * extracts options from the given options object, leaving out event handlers. + */ + _getDDOptions = function(options) { + var o = {}; + for (var i in options) if (ddEvents.indexOf(i) == -1) o[i] = options[i]; + return o; + }, + /** + * attaches all event handlers found in options to the given dragdrop object, and registering + * the given el as the element of interest. + */ + _attachListeners = function(dd, options, eventList) { + for (var ev in options) { + if (eventList.indexOf(ev) != -1) { + var w = _wrapper(options[ev]); + dd.on(ev, w); + } + } + }, + _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}, + _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }, + _lastDragObject = null, + _extend = function(o1, o2) { + for (var i in o2) + o1[i] = o2[i]; + return o1; + }, + _getAttribute = function(el, attributeId) { + return el.getAttribute(attributeId); + }, + _getElementObject = function(el) { + var eee = null; + try { + eee = typeof el == 'string' ? Y.one('#' + el) : el._node ? el : Y.one(el); + } + catch(eeee) { + console.log(eeee); + } + return eee; + }; + + jsPlumb.CurrentLibrary = { + + addClass : function(el, clazz) { + jsPlumb.CurrentLibrary.getElementObject(el).addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + var o = _extend({node:el, to:properties}, options); + var id = _getAttribute(el, "id"); + o["tween"] = jsPlumb.wrap(properties["tween"], function() { + jsPlumb.repaint(id); + }); + var a = new Y.Anim(o); + _attachListeners(a, o, animEvents); + a.run(); + }, + + appendElement : function(child, parent) { + _getElementObject (parent).append(child); + }, + + /** + * event binding wrapper. + */ + bind : function(el, event, callback) { + _getElementObject(el).on(event, callback); + }, + + dragEvents : { + "start":"drag:start", "stop":"drag:end", "drag":"drag:drag", "step":"step", + "over":"drop:enter", "out":"drop:exit", "drop":"drop:hit" + }, + + extend : _extend, + + getAttribute : _getAttribute, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + // this is a workaround for the unfortunate fact that in YUI3, the 'drop:exit' event does + // not contain a reference to the drag that just exited. single-threaded js to the + // rescue: we'll just keep it for ourselves. + if (eventArgs[0].drag) _lastDragObject = eventArgs[0].drag.el; + return _lastDragObject; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + return dd.scope; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getElementObject : _getElementObject, + + getOffset : function(el) { + var o = Y.DOM.getXY(el._node); + return {left:o[0], top:o[1]}; + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).get("parentNode"); + }, + + getScrollLeft : function(el) { + return 0; + }, + + getScrollTop : function(el) { + return 0; + }, + + getSelector : function(spec) { + var s = Y.all(spec); + return s && s ._nodes ? s._nodes : []; + }, + + getSize : function(el) { + return [ el._node.offsetWidth, el._node.offsetHeight ]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e != null && e._node != null ? e._node.tagName : null; + }, + + getUIPosition : function(args) { + var n = args[0].currentTarget.el._node, + o = Y.DOM.getXY(n); + return {left:o[0], top:o[1]}; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options) { + var _opts = _getDDOptions(options); + var id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drag(_opts); + dd.el = el; + + var scope = options['scope'] || jsPlumb.Defaults.Scope; + dd.scope = scope; + + _draggablesById[id] = dd; + _add(_draggablesByScope, scope, dd); + + _attachListeners(dd, options, ddEvents); + }, + + initDroppable : function(el, options) { + var _opts = _getDDOptions(options); + var id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drop(_opts); + + _droppableOptions[id] = options; + + options = _extend({}, options); + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _droppableScopesById[id] = scope; + + options["drop:enter"] = jsPlumb.wrap(options["drop:enter"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, true); + }, true); + options["drop:exit"] = jsPlumb.wrap(options["drop:exit"], function(e) { + _checkHover(el, false); + }); + options["drop:hit"] = jsPlumb.wrap(options["drop:hit"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, false); + }, true); + + _attachListeners(dd, options, ddEvents); + }, + + isAlreadyDraggable : function(el) { + el = _getElementObject(el); + return el.hasClass("yui3-dd-draggable"); + }, + + isDragSupported : function(el) { return true; }, + isDropSupported : function(el) { return true; }, + removeClass : function(el, clazz) { + jsPlumb.CurrentLibrary.getElementObject(el).removeClass(clazz); + }, + removeElement : function(el) { _getElementObject(el).remove(); }, + + setAttribute : function(el, attributeName, attributeValue) { + el.setAttribute(attributeName, attributeValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + if (dd) dd.set("lock", !draggable); + }, + + setDragScope : function(el, scope) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + if (dd) dd.scope = scope; + }, + + setOffset : function(el, o) { + el = _getElementObject(el); + el.set("top", o.top); + el.set("left", o.left); + }, + + stopDrag : function() { + // console.log("stop drag!"); + Y.DD.DDM.stopDrag(); + }, + + trigger : function(el, event, originalEvent) { + originalEvent.stopPropagation(); + _getElementObject(el).simulate(event, { + pageX:originalEvent.pageX, + pageY:originalEvent.pageY, + clientX:originalEvent.clientX, + clientY:originalEvent.clientY + }); + }, + + /** + * event unbinding wrapper. + */ + unbind : function(el, event, callback) { + _getElementObject(el).detach(event, callback); + } + }; +})(); \ No newline at end of file diff --git a/archive/1.3.4/yui.jsPlumb-1.3.4-all-min.js b/archive/1.3.4/yui.jsPlumb-1.3.4-all-min.js new file mode 100644 index 000000000..0af55bfe0 --- /dev/null +++ b/archive/1.3.4/yui.jsPlumb-1.3.4-all-min.js @@ -0,0 +1 @@ +(function(){var t=!!document.createElement("canvas").getContext,d=!!window.SVGAngle||document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1"),a=!(t|d);var n=function(C,D,A,G){var F=function(J,I){if(J===I){return true}else{if(typeof J=="object"&&typeof I=="object"){var K=true;for(var H in J){if(!F(J[H],I[H])){K=false;break}}for(var H in I){if(!F(I[H],J[H])){K=false;break}}return K}}};for(var E=+A||0,B=C.length;E-1){B.splice(A,1)}return A!=-1};if(!window.console){window.console={time:function(){},timeEnd:function(){},group:function(){},groupEnd:function(){},log:function(){}}}var i=function(D,B,C){var A=D[B];if(A==null){A=[],D[B]=A}A.push(C);return A},r=null,c=function(A,B){return l.CurrentLibrary.getAttribute(x(A),B)},e=function(B,C,A){l.CurrentLibrary.setAttribute(x(B),C,A)},u=function(B,A){l.CurrentLibrary.addClass(x(B),A)},h=function(B,A){return l.CurrentLibrary.hasClass(x(B),A)},k=function(B,A){l.CurrentLibrary.removeClass(x(B),A)},x=function(A){return l.CurrentLibrary.getElementObject(A)},p=function(A){return l.CurrentLibrary.getOffset(x(A))},b=function(A){return l.CurrentLibrary.getSize(x(A))},w=true,m=function(){if(w&&typeof console!="undefined"){try{var B=arguments[arguments.length-1];console.log(B)}catch(A){}}},z=function(A){if(w&&typeof console!="undefined"){console.group(A)}},f=function(A){if(w&&typeof console!="undefined"){console.groupEnd(A)}},y=function(A){if(w&&typeof console!="undefined"){console.time(A)}},q=function(A){if(w&&typeof console!="undefined"){console.timeEnd(A)}};EventGenerator=function(){var C={},B=this;var A=["ready"];this.bind=function(D,E){i(C,D,E)};this.fire=function(F,G,D){if(C[F]){for(var E=0;E=0){delete (aZ[a0]);aZ.splice(a0,1);return true}}}return false},aO=function(a0,aZ){return am(a0,function(a1,a2){aR[a2]=aZ;if(l.CurrentLibrary.isDragSupported(a1)){l.CurrentLibrary.setDraggable(a1,aZ)}})},aC=function(a1,a2,aZ){a2=a2==="block";var a0=null;if(aZ){if(a2){a0=function(a4){a4.setVisible(true,true,true)}}else{a0=function(a4){a4.setVisible(false,true,true)}}}var a3=c(a1,"id");I(a3,function(a5){if(a2&&aZ){var a4=a5.sourceId===a3?1:0;if(a5.endpoints[a4].isVisible()){a5.setVisible(true)}}else{a5.setVisible(a2)}},a0)},aM=function(aZ){return am(aZ,function(a1,a0){var a2=aR[a0]==null?false:aR[a0];a2=!a2;aR[a0]=a2;l.CurrentLibrary.setDraggable(a1,a2);return a2})},at=function(aZ,a1){var a0=null;if(a1){a0=function(a2){var a3=a2.isVisible();a2.setVisible(!a3)}}I(aZ,function(a3){var a2=a3.isVisible();a3.setVisible(!a2)},a0)},K=function(a4){var a2=a4.timestamp,aZ=a4.recalc,a3=a4.offset,a0=a4.elId;if(!aZ){if(a2&&a2===aV[a0]){return U[a0]}}if(aZ||!a3){var a1=x(a0);if(a1!=null){Q[a0]=b(a1);U[a0]=p(a1);aV[a0]=a2}}else{U[a0]=a3;if(Q[a0]==null){var a1=x(a0);if(a1!=null){Q[a0]=b(a1)}}}if(U[a0]&&!U[a0].right){U[a0].right=U[a0].left+Q[a0][0];U[a0].bottom=U[a0].top+Q[a0][1];U[a0].width=Q[a0][0];U[a0].height=Q[a0][1];U[a0].centerx=U[a0].left+(U[a0].width/2);U[a0].centery=U[a0].top+(U[a0].height/2)}return U[a0]},ap=function(aZ){var a0=U[aZ];if(!a0){a0=K(aZ)}return{o:a0,s:Q[aZ]}},A=function(aZ,a0){var a1=x(aZ);var a2=c(a1,"id");if(!a2||a2=="undefined"){if(arguments.length==2&&arguments[1]!=undefined){a2=a0}else{a2="jsPlumb_"+W()}e(a1,"id",a2)}return a2},Y=function(a1,aZ,a0){a1=a1||function(){};aZ=aZ||function(){};return function(){var a2=null;try{a2=aZ.apply(this,arguments)}catch(a3){m(aS,"jsPlumb function failed : "+a3)}if(a0==null||(a2!==a0)){try{a1.apply(this,arguments)}catch(a3){m(aS,"wrapped function failed : "+a3)}}return a2}};this.connectorClass="_jsPlumb_connector";this.endpointClass="_jsPlumb_endpoint";this.overlayClass="_jsPlumb_overlay";this.Anchors={};this.Connectors={canvas:{},svg:{},vml:{}};this.Endpoints={canvas:{},svg:{},vml:{}};this.Overlays={canvas:{},svg:{},vml:{}};this.addClass=function(a0,aZ){return l.CurrentLibrary.addClass(a0,aZ)};this.removeClass=function(a0,aZ){return l.CurrentLibrary.removeClass(a0,aZ)};this.hasClass=function(a0,aZ){return l.CurrentLibrary.hasClass(a0,aZ)};this.addEndpoint=function(a1,a2,bb){bb=bb||{};var a0=l.extend({},bb);l.extend(a0,a2);a0.endpoint=a0.endpoint||aS.Defaults.Endpoint||l.Defaults.Endpoint;a0.paintStyle=a0.paintStyle||aS.Defaults.EndpointStyle||l.Defaults.EndpointStyle;a1=an(a1);var a3=[],a6=a1.length&&a1.constructor!=String?a1:[a1];for(var a4=0;a40){try{for(var a0=0;a00?n(bc,bb)!=-1:true},a3=a6.length>1?{}:[],a9=function(bc,bd){if(a6.length>1){var bb=a3[bc];if(bb==null){bb=[];a3[bc]=bb}bb.push(bd)}else{a3.push(bd)}};for(var a2 in ay){if(a0(a6,a2)){for(var a1=0;a1=4)?[a4[2],a4[3]]:[0,0],offsets:(a4.length==6)?[a4[4],a4[5]]:[0,0],elementId:a1};a2=new N(a3);a2.clone=function(){return new N(a3)}}}}}if(!a2.id){a2.id="anchor_"+W()}return a2};this.makeAnchors=function(a2,a0,aZ){var a3=[];for(var a1=0;a10?a9[0]:null,a4=a9.length>0?0:-1,a8=this,a3=function(bc,ba,bg,bf,bb){var be=bf[0]+(bc.x*bb[0]),bd=bf[1]+(bc.y*bb[1]);return Math.sqrt(Math.pow(ba-be,2)+Math.pow(bg-bd,2))},aZ=a0||function(bk,bb,bc,bd,ba){var bf=bc[0]+(bd[0]/2),be=bc[1]+(bd[1]/2);var bh=-1,bj=Infinity;for(var bg=0;bg=a2.left)||(a5.left<=a2.right&&a5.right>=a2.right)||(a5.left<=a2.left&&a5.right>=a2.right)||(a2.left<=a5.left&&a2.right>=a5.right)),ba=((a5.top<=a2.top&&a5.bottom>=a2.top)||(a5.top<=a2.bottom&&a5.bottom>=a2.bottom)||(a5.top<=a2.top&&a5.bottom>=a2.bottom)||(a2.top<=a5.top&&a2.bottom>=a5.bottom));if(!(a4||ba)){var a7=null,a1=false,aZ=false,a6=null;if(a2.left>a5.left&&a2.top>a5.top){a7=["right","top"]}else{if(a2.left>a5.left&&a5.top>a2.top){a7=["top","left"]}else{if(a2.lefta5.top){a7=["left","top"]}}}}return{orientation:H.DIAGONAL,a:a7,theta:a0,theta2:a3}}else{if(a4){return{orientation:H.HORIZONTAL,a:a5.topaZ[0]?1:-1},O=function(aZ){return function(a1,a0){var a2=true;if(aZ){if(a1[0][0]a0[0][1]}}else{if(a1[0][0]>a0[0][0]){a2=true}else{a2=a1[0][1]>a0[0][1]}}return a2===false?-1:1}},D=function(a0,aZ){var a2=a0[0][0]<0?-Math.PI-a0[0][0]:Math.PI-a0[0][0],a1=aZ[0][0]<0?-Math.PI-aZ[0][0]:Math.PI-aZ[0][0];if(a2>a1){return 1}else{return a0[0][1]>aZ[0][1]?1:-1}},aA={top:aF,right:O(true),bottom:O(true),left:D},Z=function(aZ,a0){return aZ.sort(a0)},X=function(a0,aZ){var a2=Q[a0],a3=U[a0],a1=function(a9,bg,a5,a8,be,bd){if(a8.length>0){var bc=Z(a8,aA[a9]),ba=a9==="right"||a9==="top",a4=aD(a9,bg,a5,bc,be,bd,ba);var bh=function(bk,bj){var bi=aT([bj[0],bj[1]],bk.canvas);V[bk.id]=[bi[0],bi[1],bj[2],bj[3]]};for(var a6=0;a6-1){a5.splice(a6,1)}else{R(aZ,a7.elementId,a7)}};this.clearFor=function(a6){delete aZ[a6];aZ[a6]=[]};var a3=function(bq,bd,bl,ba,bg,bh,bj,bf,bs,bi,a9,bp){var bn=-1,a8=-1,bb=ba.endpoints[bj],bk=bb.id,be=[1,0][bj],a6=[[bd,bl],ba,bg,bh,bk],a7=bq[bs],br=bb._continuousAnchorEdge?bq[bb._continuousAnchorEdge]:null;if(br){var bo=g(br,function(bt){return bt[4]==bk});if(bo!=-1){br.splice(bo,1);for(var bm=0;bm=0?ba.overlays[bl]:null};this.hideOverlay=function(bm){var bl=ba.getOverlay(bm);if(bl){bl.hide()}};this.showOverlay=function(bm){var bl=ba.getOverlay(bm);if(bl){bl.show()}};this.removeAllOverlays=function(){ba.overlays.splice(0,ba.overlays.length);ba.repaint()};this.removeOverlay=function(bm){var bl=bh(bm);if(bl!=-1){var bn=ba.overlays[bl];bn.cleanup();ba.overlays.splice(bl,1)}};this.removeOverlays=function(){for(var bl=0;bl0){bg.connections[0].setHover(bs,false)}else{bg.setHover(bs)}};this.endpoint.bind("click",function(bs){bg.fire("click",bg,bs)});this.endpoint.bind("dblclick",function(bs){bg.fire("dblclick",bg,bs)});this.endpoint.bind("mouseenter",function(bs,bt){if(!bg.isHover()){br(true);bg.fire("mouseenter",bg,bt)}});this.endpoint.bind("mouseexit",function(bs,bt){if(bg.isHover()){br(false);bg.fire("mouseexit",bg,bt)}});this.setPaintStyle(bq.paintStyle||bq.style||aS.Defaults.EndpointStyle||l.Defaults.EndpointStyle,true);this.setHoverPaintStyle(bq.hoverPaintStyle||aS.Defaults.EndpointHoverStyle||l.Defaults.EndpointHoverStyle,true);this.paintStyleInUse=this.paintStyle;this.connectorStyle=bq.connectorStyle;this.connectorHoverStyle=bq.connectorHoverStyle;this.connectorOverlays=bq.connectorOverlays;this.connector=bq.connector;this.connectorTooltip=bq.connectorTooltip;this.parent=bq.parent;this.isSource=bq.isSource||false;this.isTarget=bq.isTarget||false;var bl=bq.maxConnections||aS.Defaults.MaxConnections;this.getAttachedElements=function(){return bg.connections};this.canvas=this.endpoint.canvas;this.connections=bq.connections||[];this.scope=bq.scope||F;this.timestamp=null;bg.isReattach=bq.reattach||false;bg.connectionsDetachable=aS.Defaults.ConnectionsDetachable;if(bq.connectionsDetachable===false||bq.detachable===false){bg.connectionsDetachable=false}var bb=bq.dragAllowedWhenFull||true;this.computeAnchor=function(bs){return bg.anchor.compute(bs)};this.addConnection=function(bs){bg.connections.push(bs)};this.detach=function(bs,bw,bt,bA){var bz=n(bg.connections,bs),by=false;bA=(bA!==false);if(bz>=0){if(bt||bs._forceDetach||bs.isDetachable()||bs.isDetachAllowed(bs)){var bB=bs.endpoints[0]==bg?bs.endpoints[1]:bs.endpoints[0];if(bt||bs._forceDetach||(bg.isDetachAllowed(bs))){bg.connections.splice(bz,1);if(!bw){bB.detach(bs,true,bt);if(bs.endpointsToDeleteOnDetach){for(var bx=0;bx0){bg.detach(bg.connections[0],false,true,bs)}};this.detachFrom=function(bu,bt){var bv=[];for(var bs=0;bs=0){bg.connections.splice(bs,1)}};this.getElement=function(){return bf};this.setElement=function(bu){var bw=A(bu);R(au,a7,bg);bf=x(bu);a7=A(bf);bg.elementId=a7;var bv=af({source:bw}),bt=a1.getParent(bg.canvas);a1.removeElement(bg.canvas,bt);a1.appendElement(bg.canvas,bv);for(var bs=0;bs0){var bC=bc(bu.elementWithPrecedence),bE=bC.endpoints[0]==bg?1:0,bw=bE==0?bC.sourceId:bC.targetId,bB=U[bw],bD=Q[bw];bt.txy=[bB.left,bB.top];bt.twh=bD;bt.tElement=bC.endpoints[bE]}bx=bg.anchor.compute(bt)}var bA=bd.compute(bx,bg.anchor.getOrientation(bd),bg.paintStyleInUse,bv||bg.paintStyleInUse);bd.paint(bA,bg.paintStyleInUse,bg.anchor);bg.timestamp=bz}};this.repaint=this.paint;this.removeConnection=this.detach;if(l.CurrentLibrary.isDragSupported(bf)){var bk={id:null,element:null},bj=null,a0=false,a3=null,aZ=aI(bk);var a5=function(){bj=bg.connectorSelector();var bs=true;if(bj==null&&!bq.isSource){bs=false}if(bq.isSource&&bg.isFull()&&!bb){bs=false}if(bj!=null&&!bj.isDetachable()){bs=false}if(bs===false){if(l.CurrentLibrary.stopDrag){l.CurrentLibrary.stopDrag()}aZ.stopDrag();return false}if(bj&&!bg.isFull()&&bq.isSource){bj=null}K({elId:a7});a4=bg.makeInPlaceCopy();a4.paint();G(bk,bg.parent);var by=x(a4.canvas),bw=l.CurrentLibrary.getOffset(by),bt=aT([bw.left,bw.top],a4.canvas);l.CurrentLibrary.setOffset(bk.element,{left:bt[0],top:bt[1]});e(x(bg.canvas),"dragId",bk.id);e(x(bg.canvas),"elId",a7);bo=ac(bg.paintStyle,bg.anchor,bd,bg.canvas,bk.element);if(bj==null){bg.anchor.locked=true;bg.setHover(false,false);bj=P({sourceEndpoint:bg,targetEndpoint:bo,source:bg.endpointWillMoveTo||x(bf),target:bk.element,anchors:[bg.anchor,bo.anchor],paintStyle:bq.connectorStyle,hoverPaintStyle:bq.connectorHoverStyle,connector:bq.connector,overlays:bq.connectorOverlays})}else{a0=true;bj.connector.setHover(false,false);a6(x(a4.canvas),false,true);var bv=bj.endpoints[0].id==bg.id?0:1;bj.floatingAnchorIndex=bv;bg.detachFromConnection(bj);var bz=x(bg.canvas),bx=l.CurrentLibrary.getDragScope(bz);e(bz,"originalScope",bx);var bu=l.CurrentLibrary.getDropScope(bz);l.CurrentLibrary.setDragScope(bz,bu);if(bv==0){a3=[bj.source,bj.sourceId,bn,bx];bj.source=bk.element;bj.sourceId=bk.id}else{a3=[bj.target,bj.targetId,bn,bx];bj.target=bk.element;bj.targetId=bk.id}bj.endpoints[bv==0?1:0].anchor.locked=true;bj.suspendedEndpoint=bj.endpoints[bv];bj.suspendedEndpoint.setHover(false);bj.endpoints[bv]=bo}aL[bk.id]=bj;bo.addConnection(bj);J(au,bk.id,bo);aS.currentlyDragging=true};var a1=l.CurrentLibrary,bm=bq.dragOptions||{},bh=l.extend({},a1.defaultDragOptions),bi=a1.dragEvents.start,bp=a1.dragEvents.stop,a9=a1.dragEvents.drag;bm=l.extend(bh,bm);bm.scope=bm.scope||bg.scope;bm[bi]=Y(bm[bi],a5);bm[a9]=Y(bm[a9],aZ);bm[bp]=Y(bm[bp],function(){aS.currentlyDragging=false;R(au,bk.id,bo);ax([bk.element[0],bo.canvas],bf);ad(a4.canvas,bf);aS.anchorManager.clearFor(bk.id);var bs=bj.floatingAnchorIndex==null?1:bj.floatingAnchorIndex;bj.endpoints[bs==0?1:0].anchor.locked=false;if(bj.endpoints[bs]==bo){if(a0&&bj.suspendedEndpoint){if(bs==0){bj.source=a3[0];bj.sourceId=a3[1]}else{bj.target=a3[0];bj.targetId=a3[1]}l.CurrentLibrary.setDragScope(a3[2],a3[3]);bj.endpoints[bs]=bj.suspendedEndpoint;if(bg.isReattach||bj._forceDetach||!bj.endpoints[bs==0?1:0].detach(bj)){bj.setHover(false);bj.floatingAnchorIndex=null;bj.suspendedEndpoint.addConnection(bj);l.repaint(a3[1])}bj._forceDetach=null}else{ax(bj.connector.getDisplayElements(),bg.parent);bg.detachFromConnection(bj)}}bg.anchor.locked=false;bg.paint({recalc:false});bj.setHover(false,false);bj=null;a4=null;delete au[bo.elementId];bo.anchor=null;bo=null;aS.currentlyDragging=false});var bn=x(bg.canvas);l.CurrentLibrary.initDraggable(bn,bm,true)}var a6=function(bw,bz,bu){if((bq.isTarget||bz)&&l.CurrentLibrary.isDropSupported(bf)){var bs=bq.dropOptions||aS.Defaults.DropOptions||l.Defaults.DropOptions;bs=l.extend({},bs);bs.scope=bs.scope||bg.scope;var bx=l.CurrentLibrary.dragEvents.drop,by=l.CurrentLibrary.dragEvents.over,bt=l.CurrentLibrary.dragEvents.out,bv=function(){var bJ=x(l.CurrentLibrary.getDragObject(arguments)),bA=c(bJ,"dragId"),bC=c(bJ,"elId"),bI=c(bJ,"originalScope"),bF=aL[bA],bG=bF.floatingAnchorIndex==null?1:bF.floatingAnchorIndex,bH=bG==0?1:0;if(bI){l.CurrentLibrary.setDragScope(bJ,bI)}if(!bg.isFull()&&!(bG==0&&!bg.isSource)&&!(bG==1&&!bg.isTarget)){var bD=true;if(bF.suspendedEndpoint&&bF.suspendedEndpoint.id!=bg.id){if(!bF.isDetachAllowed(bF)||!bF.endpoints[bG].isDetachAllowed(bF)||!bF.suspendedEndpoint.isDetachAllowed(bF)||!aS.checkCondition("beforeDetach",bF)){bD=false}}if(bG==0){bF.source=bf;bF.sourceId=a7}else{bF.target=bf;bF.targetId=a7}bD=bD&&bg.isDropAllowed(bF.sourceId,bF.targetId,bF.scope);if(bD){bF.endpoints[bG].detachFromConnection(bF);if(bF.suspendedEndpoint){bF.suspendedEndpoint.detachFromConnection(bF)}bF.endpoints[bG]=bg;bg.addConnection(bF);if(!bF.suspendedEndpoint){aH(bf,bq.draggable,{})}else{var bE=bF.suspendedEndpoint.getElement(),bB=bF.suspendedEndpoint.elementId;aE({source:bG==0?bE:bF.source,target:bG==1?bE:bF.target,sourceId:bG==0?bB:bF.sourceId,targetId:bG==1?bB:bF.targetId,sourceEndpoint:bG==0?bF.suspendedEndpoint:bF.endpoints[0],targetEndpoint:bG==1?bF.suspendedEndpoint:bF.endpoints[1],connection:bF},true)}aX(bF)}else{if(bF.suspendedEndpoint){bF.endpoints[bG]=bF.suspendedEndpoint;bF.setHover(false);bF._forceDetach=true;if(bG==0){bF.source=bF.suspendedEndpoint.element;bF.sourceId=bF.suspendedEndpoint.elementId}else{bF.target=bF.suspendedEndpoint.element;bF.targetId=bF.suspendedEndpoint.elementId}bF.suspendedEndpoint.addConnection(bF);bF.endpoints[0].repaint();bF.repaint();l.repaint(bF.source.elementId);bF._forceDetach=false}}bF.floatingAnchorIndex=null}aS.currentlyDragging=false;delete aL[bA]};bs[bx]=Y(bs[bx],bv);bs[by]=Y(bs[by],function(){var bB=l.CurrentLibrary.getDragObject(arguments),bD=c(x(bB),"dragId"),bC=aL[bD];if(bC!=null){var bA=bC.floatingAnchorIndex==null?1:bC.floatingAnchorIndex;bC.endpoints[bA].anchor.over(bg.anchor)}});bs[bt]=Y(bs[bt],function(){var bB=l.CurrentLibrary.getDragObject(arguments),bD=c(x(bB),"dragId"),bC=aL[bD];if(bC!=null){var bA=bC.floatingAnchorIndex==null?1:bC.floatingAnchorIndex;bC.endpoints[bA].anchor.out()}});l.CurrentLibrary.initDroppable(bw,bs,true,bu)}};a6(x(bg.canvas),true,!(bq._transient||bg.anchor.isFloating));return bg}};var l=window.jsPlumb=new s();l.getInstance=function(B){var A=new s(B);A.init();return A};l.util={convertStyle:function(B,A){if("transparent"===B){return B}var G=B,F=function(H){return H.length==1?"0"+H:H},C=function(H){return F(Number(H).toString(16))},D=/(rgb[a]?\()(.*)(\))/;if(B.match(D)){var E=B.match(D)[2].split(",");G="#"+C(E[0])+C(E[1])+C(E[2]);if(!A&&E.length==4){G=G+C(E[3])}}return G},gradient:function(B,A){B=B.constructor==Array?B:[B.x,B.y];A=A.constructor==Array?A:[A.x,A.y];return(A[1]-B[1])/(A[0]-B[0])},normal:function(B,A){return -1/l.util.gradient(B,A)},segment:function(B,A){B=B.constructor==Array?B:[B.x,B.y];A=A.constructor==Array?A:[A.x,A.y];if(A[0]>B[0]){return(A[1]>B[1])?2:1}else{return(A[1]>B[1])?3:4}},segmentMultipliers:[null,[1,-1],[1,1],[-1,1],[-1,-1]],inverseSegmentMultipliers:[null,[-1,-1],[-1,1],[1,1],[1,-1]],pointOnLine:function(A,E,B){var D=l.util.gradient(A,E),I=l.util.segment(A,E),H=B>0?l.util.segmentMultipliers[I]:l.util.inverseSegmentMultipliers[I],C=Math.atan(D),F=Math.abs(B*Math.sin(C))*H[1],G=Math.abs(B*Math.cos(C))*H[0];return{x:A.x+G,y:A.y+F}},perpendicularLineTo:function(C,D,E){var B=l.util.gradient(C,D),F=Math.atan(-1/B),G=E/2*Math.sin(F),A=E/2*Math.cos(F);return[{x:D.x+A,y:D.y+G},{x:D.x-A,y:D.y-G}]}};var o=function(A,F,C,B,E,D){return function(H){H=H||{};var G=l.makeAnchor([A,F,C,B,0,0],H.elementId,H.jsPlumbInstance);G.type=E;if(D){D(G,H)}return G}};l.Anchors.TopCenter=o(0.5,0,0,-1,"TopCenter");l.Anchors.BottomCenter=o(0.5,1,0,1,"BottomCenter");l.Anchors.LeftMiddle=o(0,0.5,-1,0,"LeftMiddle");l.Anchors.RightMiddle=o(1,0.5,1,0,"RightMiddle");l.Anchors.Center=o(0.5,0.5,0,0,"Center");l.Anchors.TopRight=o(1,0,0,-1,"TopRight");l.Anchors.BottomRight=o(1,1,0,1,"BottomRight");l.Anchors.TopLeft=o(0,0,0,-1,"TopLeft");l.Anchors.BottomLeft=o(0,1,0,1,"BottomLeft");l.Defaults.DynamicAnchors=function(A){return l.makeAnchors(["TopCenter","RightMiddle","BottomCenter","LeftMiddle"],A.elementId,A.jsPlumbInstance)};l.Anchors.AutoDefault=function(B){var A=l.makeDynamicAnchor(l.Defaults.DynamicAnchors(B));A.type="AutoDefault";return A};l.Anchors.Assign=o(0,0,0,0,"Assign",function(B,C){var A=C.position||"Fixed";B.positionFinder=A.constructor==String?l.AnchorPositionFinders[A]:A;B.constructorParams=C});l.Anchors.Continuous=function(A){return A.jsPlumbInstance.continuousAnchorFactory.get(A)};l.AnchorPositionFinders={Fixed:function(D,B,C,A){return[(D.left-B.left)/C[0],(D.top-B.top)/C[1]]},Grid:function(A,J,E,B){var I=A.left-J.left,H=A.top-J.top,G=E[0]/(B.constructorParams.grid[0]),F=E[1]/(B.constructorParams.grid[1]),D=Math.floor(I/G),C=Math.floor(H/F);return[((D*G)+(G/2))/E[0],((C*F)+(F/2))/E[1]]}}})();(function(){jsPlumb.DOMElementComponent=function(b){jsPlumb.jsPlumbUIComponent.apply(this,arguments);this.mousemove=this.dblclick=this.click=this.mousedown=this.mouseup=function(c){}};jsPlumb.Connectors.Straight=function(){this.type="Straight";var q=this,h=null,d,i,o,m,k,e,p,g,f,c,b,n,l;this.compute=function(z,I,r,v,E,s,C,u){var H=Math.abs(z[0]-I[0]),B=Math.abs(z[1]-I[1]),A=0.45*H,t=0.45*B;H*=1.9;B*=1.9;var F=Math.min(z[0],I[0])-A;var D=Math.min(z[1],I[1])-t;var G=Math.max(2*C,u);if(Hm){m=w}if(z<0){e+=z;var B=Math.abs(z);m+=B;o[0]+=B;k+=B;c+=B;n[0]+=B}var J=Math.min(g,b),H=Math.min(o[1],n[1]),v=Math.min(J,H),A=Math.max(g,b),y=Math.max(o[1],n[1]),s=Math.max(A,y);if(s>h){h=s}if(v<0){d+=v;var x=Math.abs(v);h+=x;o[1]+=x;g+=x;b+=x;n[1]+=x}if(F&&m=s){q=t;r=(s-l[t][0])/b[t];break}}return{segment:i[q],proportion:r,index:q}};this.compute=function(T,v,L,z,S,r,q,I){i=[];n=0;b=[];e=v[0]t[0]?t[0]+((1-x)*r)-y:t[2]+(x*r)+y,y:q==0?t[3]:t[3]>t[1]?t[1]+((1-x)*r)-y:t[3]+(x*r)+y,segmentInfo:v};return w}};jsPlumb.Endpoints.Dot=function(c){this.type="Dot";var b=this;c=c||{};this.radius=c.radius||10;this.defaultOffset=0.5*this.radius;this.defaultInnerRadius=this.radius/3;this.compute=function(h,e,k,g){var f=k.radius||b.radius,d=h[0]-f,i=h[1]-f;return[d,i,f*2,f*2,f]}};jsPlumb.Endpoints.Rectangle=function(c){this.type="Rectangle";var b=this;c=c||{};this.width=c.width||20;this.height=c.height||20;this.compute=function(i,f,l,h){var g=l.width||b.width,e=l.height||b.height,d=i[0]-(g/2),k=i[1]-(e/2);return[d,k,g,e]}};jsPlumb.Endpoints.Image=function(f){this.type="Image";jsPlumb.DOMElementComponent.apply(this,arguments);var k=this,e=false,d=f.width,c=f.height,h=null,b=f.endpoint;this.img=new Image();k.ready=false;this.img.onload=function(){k.ready=true;d=d||k.img.width;c=c||k.img.height;if(h){h(this)}};b.setImage=function(l,n){var m=l.constructor==String?l:l.src;h=n;k.img.src=l};b.setImage(f.src||f.url,f.onload);this.compute=function(n,l,o,m){k.anchorPoint=n;if(k.ready){return[n[0]-d/2,n[1]-c/2,d,c]}else{return[0,0,0,0]}};k.canvas=document.createElement("img"),e=false;k.canvas.style.margin=0;k.canvas.style.padding=0;k.canvas.style.outline=0;k.canvas.style.position="absolute";var g=f.cssClass?" "+f.cssClass:"";k.canvas.className=jsPlumb.endpointClass+g;if(d){k.canvas.setAttribute("width",d)}if(c){k.canvas.setAttribute("height",c)}jsPlumb.appendElement(k.canvas,f.parent);k.attachListeners(k.canvas,k);var i=function(o,n,m){if(!e){k.canvas.setAttribute("src",k.img.src);e=true}var l=k.anchorPoint[0]-(d/2),p=k.anchorPoint[1]-(c/2);jsPlumb.sizeCanvas(k.canvas,l,p,d,c)};this.paint=function(n,m,l){if(k.ready){i(n,m,l)}else{window.setTimeout(function(){k.paint(n,m,l)},200)}}};jsPlumb.Endpoints.Blank=function(c){var b=this;this.type="Blank";jsPlumb.DOMElementComponent.apply(this,arguments);this.compute=function(f,d,g,e){return[f[0],f[1],10,0]};b.canvas=document.createElement("div");b.canvas.style.display="block";b.canvas.style.width="1px";b.canvas.style.height="1px";b.canvas.style.background="transparent";b.canvas.style.position="absolute";b.canvas.className=b._jsPlumb.endpointClass;jsPlumb.appendElement(b.canvas,c.parent);this.paint=function(g,f,e){jsPlumb.sizeCanvas(b.canvas,g[0],g[1],g[2],g[3])}};jsPlumb.Endpoints.Triangle=function(b){this.type="Triangle";b=b||{};b.width=b.width||55;b.height=b.height||55;this.width=b.width;this.height=b.height;this.compute=function(h,e,k,g){var f=k.width||self.width,d=k.height||self.height,c=h[0]-(f/2),i=h[1]-(d/2);return[c,i,f,d]}};var a=function(d){var c=true,b=this;this.isAppendedAtTopLevel=true;this.connection=d.connection;this.loc=d.location==null?0.5:d.location;this.setVisible=function(e){c=e;b.connection.repaint()};this.isVisible=function(){return c};this.hide=function(){b.setVisible(false)};this.show=function(){b.setVisible(true)};this.incrementLocation=function(e){b.loc+=e;b.connection.repaint()};this.setLocation=function(e){b.loc=e;b.connection.repaint()};this.getLocation=function(){return b.loc}};jsPlumb.Overlays.Arrow=function(f){this.type="Arrow";a.apply(this,arguments);this.isAppendedAtTopLevel=false;f=f||{};var c=this;this.length=f.length||20;this.width=f.width||20;this.id=f.id;var e=(f.direction||1)<0?-1:1,d=f.paintStyle||{lineWidth:1},b=f.foldback||0.623;this.computeMaxSize=function(){return c.width*1.5};this.cleanup=function(){};this.draw=function(h,w,r){var l,s,g,m,k;if(c.loc==1){l=h.pointOnPath(c.loc);s=h.pointAlongPathFrom(c.loc,-1);g=jsPlumb.util.pointOnLine(l,s,c.length)}else{if(c.loc==0){g=h.pointOnPath(c.loc);s=h.pointAlongPathFrom(c.loc,1);l=jsPlumb.util.pointOnLine(g,s,c.length)}else{l=h.pointAlongPathFrom(c.loc,e*c.length/2),s=h.pointOnPath(c.loc),g=jsPlumb.util.pointOnLine(l,s,c.length)}}m=jsPlumb.util.perpendicularLineTo(l,g,c.width);k=jsPlumb.util.pointOnLine(l,g,b*c.length);var v=Math.min(l.x,m[0].x,m[1].x),p=Math.max(l.x,m[0].x,m[1].x),u=Math.min(l.y,m[0].y,m[1].y),o=Math.max(l.y,m[0].y,m[1].y);var n={hxy:l,tail:m,cxy:k},q=d.strokeStyle||w.strokeStyle,t=d.fillStyle||w.strokeStyle,i=d.lineWidth||w.lineWidth;c.paint(h,n,i,q,t,r);return[v,p,u,o]}};jsPlumb.Overlays.PlainArrow=function(c){c=c||{};var b=jsPlumb.extend(c,{foldback:1});jsPlumb.Overlays.Arrow.call(this,b);this.type="PlainArrow"};jsPlumb.Overlays.Diamond=function(d){d=d||{};var b=d.length||40,c=jsPlumb.extend(d,{length:b/2,foldback:2});jsPlumb.Overlays.Arrow.call(this,c);this.type="Diamond"};jsPlumb.Overlays.Label=function(h){this.type="Label";jsPlumb.DOMElementComponent.apply(this,arguments);a.apply(this,arguments);this.labelStyle=h.labelStyle||jsPlumb.Defaults.LabelStyle;this.id=h.id;this.cachedDimensions=null;var d=h.label||"",b=this,e=false,i=document.createElement("div"),f=null;i.style.position="absolute";var c=h._jsPlumb.overlayClass+" "+(b.labelStyle.cssClass?b.labelStyle.cssClass:h.cssClass?h.cssClass:"");i.className=c;jsPlumb.appendElement(i,h.connection.parent);jsPlumb.getId(i);b.attachListeners(i,b);b.canvas=i;var g=b.setVisible;b.setVisible=function(k){g(k);i.style.display=k?"block":"none"};this.getElement=function(){return i};this.cleanup=function(){if(i!=null){jsPlumb.CurrentLibrary.removeElement(i)}};this.setLabel=function(k){d=k;f=null;b.connection.repaint()};this.getLabel=function(){return d};this.paint=function(k,m,l){if(!e){k.appendDisplayElement(i);b.attachListeners(i,k);e=true}i.style.left=(l[0]+m.minx)+"px";i.style.top=(l[1]+m.miny)+"px"};this.getTextDimensions=function(l){if(typeof d=="function"){var k=d(b);i.innerHTML=k.replace(/\r\n/g,"
")}else{if(f==null){f=d;i.innerHTML=f.replace(/\r\n/g,"
")}}var n=jsPlumb.CurrentLibrary.getElementObject(i),m=jsPlumb.CurrentLibrary.getSize(n);return{width:m[0],height:m[1]}};this.computeMaxSize=function(k){var l=b.getTextDimensions(k);return l.width?Math.max(l.width,l.height)*1.5:0};this.draw=function(m,o,n){var q=b.getTextDimensions(m);if(q.width!=null){var p=m.pointOnPath(b.loc),l=p.x-(q.width/2),k=p.y-(q.height/2);b.paint(m,{minx:l,miny:k,td:q,cxy:p},n);return[l,l+q.width,k,k+q.height]}else{return[0,0,0,0]}};this.reattachListeners=function(){if(i){b.reattachListenersForElement(i,b)}}};jsPlumb.Overlays.GuideLines=function(){var b=this;b.length=50;b.lineWidth=5;this.type="GuideLines";a.apply(this,arguments);jsPlumb.jsPlumbUIComponent.apply(this,arguments);this.draw=function(d,k,i){var h=d.pointAlongPathFrom(b.loc,b.length/2),g=d.pointOnPath(b.loc),f=jsPlumb.util.pointOnLine(h,g,b.length),e=jsPlumb.util.perpendicularLineTo(h,f,40),c=jsPlumb.util.perpendicularLineTo(f,h,20);b.paint(d,[h,f,e,c],b.lineWidth,"red",null,i);return[Math.min(h.x,f.x),Math.min(h.y,f.y),Math.max(h.x,f.x),Math.max(h.y,f.y)]};this.computeMaxSize=function(){return 50};this.cleanup=function(){}}})();(function(){var c=function(e,g,d,f){this.m=(f-g)/(d-e);this.b=-1*((this.m*e)-g);this.rectIntersect=function(q,p,s,o){var n=[];var k=(p-this.b)/this.m;if(k>=q&&k<=(q+s)){n.push([k,(this.m*k)+this.b])}var t=(this.m*(q+s))+this.b;if(t>=p&&t<=(p+o)){n.push([(t-this.b)/this.m,t])}var k=((p+o)-this.b)/this.m;if(k>=q&&k<=(q+s)){n.push([k,(this.m*k)+this.b])}var t=(this.m*q)+this.b;if(t>=p&&t<=(p+o)){n.push([(t-this.b)/this.m,t])}if(n.length==2){var m=(n[0][0]+n[1][0])/2,l=(n[0][1]+n[1][1])/2;n.push([m,l]);var i=m<=q+(s/2)?-1:1,r=l<=p+(o/2)?-1:1;n.push([i,r]);return n}return null}},a=function(e,g,d,f){if(e<=d&&f<=g){return 1}else{if(e<=d&&g<=f){return 2}else{if(d<=e&&f>=g){return 3}}}return 4},b=function(g,f,i,e,h,m,l,d,k){if(d<=k){return[g,f]}if(i==1){if(e[3]<=0&&h[3]>=1){return[g+(e[2]<0.5?-1*m:m),f]}else{if(e[2]>=1&&h[2]<=0){return[g,f+(e[3]<0.5?-1*l:l)]}else{return[g+(-1*m),f+(-1*l)]}}}else{if(i==2){if(e[3]>=1&&h[3]<=0){return[g+(e[2]<0.5?-1*m:m),f]}else{if(e[2]>=1&&h[2]<=0){return[g,f+(e[3]<0.5?-1*l:l)]}else{return[g+(1*m),f+(-1*l)]}}}else{if(i==3){if(e[3]>=1&&h[3]<=0){return[g+(e[2]<0.5?-1*m:m),f]}else{if(e[2]<=0&&h[2]>=1){return[g,f+(e[3]<0.5?-1*l:l)]}else{return[g+(-1*m),f+(-1*l)]}}}else{if(i==4){if(e[3]<=0&&h[3]>=1){return[g+(e[2]<0.5?-1*m:m),f]}else{if(e[2]<=0&&h[2]>=1){return[g,f+(e[3]<0.5?-1*l:l)]}else{return[g+(1*m),f+(-1*l)]}}}}}}};jsPlumb.Connectors.StateMachine=function(l){var s=this,n=null,o,m,g,e,p=[],d=l.curviness||10,k=l.margin||5,q=l.proximityLimit||80,f=l.orientation&&l.orientation=="clockwise",i=l.loopbackRadius||25,h=false;this.type="StateMachine";l=l||{};this.compute=function(ab,F,U,G,aa,u,t,S){var O=Math.abs(ab[0]-F[0]),W=Math.abs(ab[1]-F[1]),Q=0.45*O,Z=0.45*W;O*=1.9;W*=1.9;t=t||1;var M=Math.min(ab[0],F[0])-Q,K=Math.min(ab[1],F[1])-Z;if(U.elementId!=G.elementId){h=false;o=ab[0]0&&v<1){v=1-v}var w=(v*2*Math.PI)+(Math.PI/2),u=n[4]+(n[6]*Math.cos(w)),t=n[5]+(n[6]*Math.sin(w));return{x:u,y:t}}else{return jsBezier.pointOnCurve(r(),v)}};this.gradientAtPoint=function(t){if(h){return Math.atan(t*2*Math.PI)}else{return jsBezier.gradientAtPoint(r(),t)}};this.pointAlongPathFrom=function(v,z){if(h){if(v>0&&v<1){v=1-v}var w=2*Math.PI*n[6],y=z/w*2*Math.PI,x=(v*2*Math.PI)-y+(Math.PI/2),u=n[4]+(n[6]*Math.cos(x)),t=n[5]+(n[6]*Math.sin(x));return{x:u,y:t}}return jsBezier.pointAlongCurveFrom(r(),v,z)}};jsPlumb.Connectors.canvas.StateMachine=function(f){f=f||{};var d=this,g=f.drawGuideline||true,e=f.avoidSelector;jsPlumb.Connectors.StateMachine.apply(this,arguments);jsPlumb.CanvasConnector.apply(this,arguments);this._paint=function(l){if(l.length==10){d.ctx.beginPath();d.ctx.moveTo(l[4],l[5]);d.ctx.quadraticCurveTo(l[8],l[9],l[6],l[7]);d.ctx.stroke()}else{d.ctx.save();d.ctx.beginPath();var k=0,i=2*Math.PI,h=l[7];d.ctx.arc(l[4],l[5],l[6],0,i,h);d.ctx.stroke();d.ctx.closePath();d.ctx.restore()}};this.createGradient=function(i,h){return h.createLinearGradient(i[4],i[5],i[6],i[7])}};jsPlumb.Connectors.svg.StateMachine=function(){var d=this;jsPlumb.Connectors.StateMachine.apply(this,arguments);jsPlumb.SvgConnector.apply(this,arguments);this.getPath=function(e){if(e.length==10){return"M "+e[4]+" "+e[5]+" C "+e[8]+" "+e[9]+" "+e[8]+" "+e[9]+" "+e[6]+" "+e[7]}else{return"M"+(e[8]+4)+" "+e[9]+" A "+e[6]+" "+e[6]+" 0 1,0 "+(e[8]-4)+" "+e[9]}}};jsPlumb.Connectors.vml.StateMachine=function(){jsPlumb.Connectors.StateMachine.apply(this,arguments);jsPlumb.VmlConnector.apply(this,arguments);var d=jsPlumb.vml.convertValue;this.getPath=function(k){if(k.length==10){return"m"+d(k[4])+","+d(k[5])+" c"+d(k[8])+","+d(k[9])+","+d(k[8])+","+d(k[9])+","+d(k[6])+","+d(k[7])+" e"}else{var h=d(k[8]-k[6]),g=d(k[9]-(2*k[6])),f=h+d(2*k[6]),e=g+d(2*k[6]),l=h+","+g+","+f+","+e;var i="ar "+l+","+d(k[8])+","+d(k[9])+","+d(k[8])+","+d(k[9])+" e";return i}}}})();(function(){var h={"stroke-linejoin":"joinstyle",joinstyle:"joinstyle",endcap:"endcap",miterlimit:"miterlimit"};if(document.createStyleSheet){document.createStyleSheet().addRule(".jsplumb_vml","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:textbox","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:oval","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:rect","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:stroke","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:shape","behavior:url(#default#VML);position:absolute;");document.namespaces.add("jsplumb","urn:schemas-microsoft-com:vml")}jsPlumb.vml={};var b=1000,c=function(p,q){for(var n in q){p[n]=q[n]}},m=function(n,q,r){r=r||{};var p=document.createElement("jsplumb:"+n);p.className=(r["class"]?r["class"]+" ":"")+"jsplumb_vml";l(p,q);c(p,r);return p},l=function(p,n){p.style.left=n[0]+"px";p.style.top=n[1]+"px";p.style.width=n[2]+"px";p.style.height=n[3]+"px";p.style.position="absolute"},g=jsPlumb.vml.convertValue=function(n){return Math.floor(n*b)},d=function(q,o,p,n){if("transparent"===o){n.setOpacity(p,"0.0")}else{n.setOpacity(p,"1.0")}},f=function(r,n,u){var q={};if(n.strokeStyle){q.stroked="true";var v=jsPlumb.util.convertStyle(n.strokeStyle,true);q.strokecolor=v;d(q,v,"stroke",u);q.strokeweight=n.lineWidth+"px"}else{q.stroked="false"}if(n.fillStyle){q.filled="true";var o=jsPlumb.util.convertStyle(n.fillStyle,true);q.fillcolor=o;d(q,o,"fill",u)}else{q.filled="false"}if(n.dashstyle){if(u.strokeNode==null){u.strokeNode=m("stroke",[0,0,0,0],{dashstyle:n.dashstyle});r.appendChild(u.strokeNode)}else{u.strokeNode.dashstyle=n.dashstyle}}else{if(n["stroke-dasharray"]&&n.lineWidth){var w=n["stroke-dasharray"].indexOf(",")==-1?" ":",",s=n["stroke-dasharray"].split(w),p="";for(var t=0;t0&&B>0&&t=t&&v[2]<=B&&v[3]>=B)){return true}}var z=p.canvas.getContext("2d").getImageData(parseInt(t),parseInt(B),1,1);return z.data[0]!=0||z.data[1]!=0||z.data[2]!=0||z.data[3]!=0}return false};var o=false,n=false,s=null,r=false,q=function(u,t){return u!=null&&i(u,t)};this.mousemove=function(w){var y=m(w),v=f(w),u=document.elementFromPoint(v[0],v[1]),x=q(u,"_jsPlumb_overlay");var t=d==null&&(q(u,"_jsPlumb_endpoint")||q(u,"_jsPlumb_connector"));if(!o&&t&&p._over(w)){o=true;p.fire("mouseenter",p,w);return true}else{if(o&&(!p._over(w)||!t)&&!x){o=false;p.fire("mouseexit",p,w)}}p.fire("mousemove",p,w)};this.click=function(t){if(o&&p._over(t)&&!r){p.fire("click",p,t)}r=false};this.dblclick=function(t){if(o&&p._over(t)&&!r){p.fire("dblclick",p,t)}r=false};this.mousedown=function(t){if(p._over(t)&&!n){n=true;s=l(a(p.canvas));p.fire("mousedown",p,t)}};this.mouseup=function(t){n=false;p.fire("mouseup",p,t)}};var c=function(o){var n=document.createElement("canvas");jsPlumb.appendElement(n,o.parent);n.style.position="absolute";if(o["class"]){n.className=o["class"]}o._jsPlumb.getId(n,o.uuid);if(o.tooltip){n.setAttribute("label",o.tooltip)}return n};var h=jsPlumb.CanvasConnector=function(r){k.apply(this,arguments);var n=function(v,t){o.ctx.save();jsPlumb.extend(o.ctx,t);if(t.gradient){var u=o.createGradient(v,o.ctx);for(var s=0;sq?q=s:sl.location){l.location=0}return i(m,l.location)},nearestPointOnCurve:function(m,l){var n=h(m,l);return{point:a(l,l.length-1,n.location,null,null),location:n.location}},pointOnCurve:c,pointAlongCurveFrom:function(m,l,n){return k(m,l,n).point},perpendicularToCurveAt:function(m,l,n,o){l=k(m,l,null==o?0:o);m=i(m,l.location);o=Math.atan(-1/m);m=n/2*Math.sin(o);n=n/2*Math.cos(o);return[{x:l.point.x+n,y:l.point.y+m},{x:l.point.x-n,y:l.point.y-m}]}}})(); \ No newline at end of file diff --git a/archive/1.3.4/yui.jsPlumb-1.3.4-all.js b/archive/1.3.4/yui.jsPlumb-1.3.4-all.js new file mode 100644 index 000000000..8cf77f799 --- /dev/null +++ b/archive/1.3.4/yui.jsPlumb-1.3.4-all.js @@ -0,0 +1,7986 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the jsPlumb core code. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + /** + * Class:jsPlumb + * The jsPlumb engine, registered as a static object in the window. This object contains all of the methods you will use to + * create and maintain Connections and Endpoints. + */ + + var canvasAvailable = !!document.createElement('canvas').getContext, + svgAvailable = !!window.SVGAngle || document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"), + // TODO what is a good test for VML availability? aside from just assuming its there because nothing else is. + vmlAvailable = !(canvasAvailable | svgAvailable); + + // finds the index of some value in an array, using a deep equals test. + var _findIndex = function(a, v, b, s) { + var _eq = function(o1, o2) { + if (o1 === o2) return true; + else if (typeof o1 == "object" && typeof o2 == "object") { + var same = true; + for ( var propertyName in o1) { + if (!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for ( var propertyName in o2) { + if (!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for ( var i = +b || 0, l = a.length; i < l; i++) { + if (_eq(a[i], v)) + return i; + } + return -1; + }, + _findWithFunction = function(a, f) { + for (var i = 0; i < a.length; i++) if (f(a[i])) return i; + return -1; + }, + _removeWithFunction = function(a, f) { + var idx = _findWithFunction(a, f); + if (idx > -1) a.splice(idx, 1); + return idx != -1; + }; + + + // for those browsers that dont have it. they still don't have it! but at least they won't crash. + if (!window.console) + window.console = { time:function(){}, timeEnd:function(){}, group:function(){}, groupEnd:function(){}, log:function(){} }; + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = [], map[key] = l; + } + l.push(value); + return l; + }, + _connectionBeingDragged = null, + _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }, + _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }, + _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }, + _logEnabled = true, + _log = function() { + if (_logEnabled && typeof console != "undefined") { + try { + var msg = arguments[arguments.length - 1]; + console.log(msg); + } + catch (e) {} + } + }, + _group = function(g) { + if (_logEnabled && typeof console != "undefined") console.group(g); + }, + _groupEnd = function(g) { + if (_logEnabled && typeof console != "undefined") console.groupEnd(g); + }, + _time = function(t) { + if (_logEnabled && typeof console != "undefined") console.time(t); + }, + _timeEnd = function(t) { + if (_logEnabled && typeof console != "undefined") console.timeEnd(t); + }; + + /** + * EventGenerator + * Superclass for objects that generate events - jsPlumb extends this, as does jsPlumbUIComponent, which all the UI elements extend. + */ + EventGenerator = function() { + var _listeners = {}, self = this; + + // this is a list of events that should re-throw any errors that occur during their dispatch. as of 1.3.0 this is private to + // jsPlumb, but it seems feasible that people might want to manipulate this list. the thinking is that we don't want event + // listeners to bring down jsPlumb - or do we. i can't make up my mind about this, but i know i want to hear about it if the "ready" + // event fails, because then my page has most likely not initialised. so i have this halfway-house solution. it will be interesting + // to hear what other people think. + var eventsToDieOn = [ "ready" ]; + + /* + * Binds a listener to an event. + * + * Parameters: + * event - name of the event to bind to. + * listener - function to execute. + */ + this.bind = function(event, listener) { + _addToList(_listeners, event, listener); + }; + /* + * Fires an update for the given event. + * + * Parameters: + * event - event to fire + * value - value to pass to the event listener(s). + * o riginalEvent - the original event from the browser + */ + this.fire = function(event, value, originalEvent) { + if (_listeners[event]) { + for ( var i = 0; i < _listeners[event].length; i++) { + // doing it this way rather than catching and then possibly re-throwing means that an error propagated by this + // method will have the whole call stack available in the debugger. + if (_findIndex(eventsToDieOn, event) != -1) + _listeners[event][i](value, originalEvent); + else { + // for events we don't want to die on, catch and log. + try { + _listeners[event][i](value, originalEvent); + } catch (e) { + _log("jsPlumb: fire failed for event " + event + " : " + e); + } + } + } + } + }; + /* + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + this.clearListeners = function(event) { + if (event) + delete _listeners[event]; + else { + delete _listeners; + _listeners = {}; + } + }; + + this.getListener = function(forEvent) { + return _listeners[forEvent]; + }; + }, + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + _timestamp = function() { return "" + (new Date()).getTime(); }, + + /* + * Class:jsPlumbUIComponent + * Abstract superclass for UI components Endpoint and Connection. Provides the abstraction of paintStyle/hoverPaintStyle, + * and also extends EventGenerator to provide the bind and fire methods. + */ + jsPlumbUIComponent = function(params) { + var self = this, a = arguments, _hover = false, parameters = params.parameters || {}, idPrefix = self.idPrefix, + id = idPrefix + (new Date()).getTime(); + self._jsPlumb = params["_jsPlumb"]; + self.getId = function() { return id; }; + self.tooltip = params.tooltip; + self.hoverClass = params.hoverClass; + + // all components can generate events + EventGenerator.apply(this); + // all components get this clone function. + // TODO issue 116 showed a problem with this - it seems 'a' that is in + // the clone function's scope is shared by all invocations of it, the classic + // JS closure problem. for now, jsPlumb does a version of this inline where + // it used to call clone. but it would be nice to find some time to look + // further at this. + this.clone = function() { + var o = new Object(); + self.constructor.apply(o, a); + return o; + }; + + this.getParameter = function(name) { return parameters[name]; }, + this.getParameters = function() { return parameters; }, + this.setParameter = function(name, value) { parameters[name] = value; }, + this.setParameters = function(p) { parameters = p; }, + this.overlayPlacements = [], + this.paintStyle = null, + this.hoverPaintStyle = null; + + // user can supply a beforeDetach callback, which will be executed before a detach + // is performed; returning false prevents the detach. + var beforeDetach = params.beforeDetach; + this.isDetachAllowed = function(connection) { + var r = true; + if (beforeDetach) { + try { + r = beforeDetach(connection); + } + catch (e) { _log("jsPlumb: beforeDetach callback failed", e); } + } + return r; + }; + + // user can supply a beforeDrop callback, which will be executed before a dropped + // connection is confirmed. user can return false to reject connection. + var beforeDrop = params.beforeDrop; + this.isDropAllowed = function(sourceId, targetId, scope) { + var r = self._jsPlumb.checkCondition("beforeDrop", { sourceId:sourceId, targetId:targetId, scope:scope }); + if (beforeDrop) { + try { + r = beforeDrop({ sourceId:sourceId, targetId:targetId, scope:scope }); + } + catch (e) { _log("jsPlumb: beforeDrop callback failed", e); } + } + return r; + }; + + // helper method to update the hover style whenever it, or paintStyle, changes. + // we use paintStyle as the foundation and merge hoverPaintStyle over the + // top. + var _updateHoverStyle = function() { + if (self.paintStyle && self.hoverPaintStyle) { + var mergedHoverStyle = {}; + jsPlumb.extend(mergedHoverStyle, self.paintStyle); + jsPlumb.extend(mergedHoverStyle, self.hoverPaintStyle); + delete self["hoverPaintStyle"]; + // we want the fillStyle of paintStyle to override a gradient, if possible. + if (mergedHoverStyle.gradient && self.paintStyle.fillStyle) + delete mergedHoverStyle["gradient"]; + self.hoverPaintStyle = mergedHoverStyle; + } + }; + + /* + * Sets the paint style and then repaints the element. + * + * Parameters: + * style - Style to use. + */ + this.setPaintStyle = function(style, doNotRepaint) { + self.paintStyle = style; + self.paintStyleInUse = self.paintStyle; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /* + * Sets the paint style to use when the mouse is hovering over the element. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the component will not be repainted. useful when setting things up initially. + */ + this.setHoverPaintStyle = function(style, doNotRepaint) { + self.hoverPaintStyle = style; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /* + * sets/unsets the hover state of this element. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + this.setHover = function(hover, ignoreAttachedElements, timestamp) { + // while dragging, we ignore these events. this keeps the UI from flashing and + // swishing and whatevering. + if (!self._jsPlumb.currentlyDragging) { + + _hover = hover; + if (self.hoverClass != null && self.canvas != null) { + if (hover) + jpcl.addClass(self.canvas, self.hoverClass); + else + jpcl.removeClass(self.canvas, self.hoverClass); + } + if (self.hoverPaintStyle != null) { + self.paintStyleInUse = hover ? self.hoverPaintStyle : self.paintStyle; + timestamp = timestamp || _timestamp(); + self.repaint({timestamp:timestamp, recalc:false}); + } + // get the list of other affected elements, if supported by this component. + // for a connection, its the endpoints. for an endpoint, its the connections! surprise. + if (self.getAttachedElements && !ignoreAttachedElements) + _updateAttachedElements(hover, _timestamp(), self); + } + }; + + this.isHover = function() { return _hover; }; + + var jpcl = jsPlumb.CurrentLibrary, + events = [ "click", "dblclick", "mouseenter", "mouseout", "mousemove", "mousedown", "mouseup" ], + eventFilters = { "mouseout":"mouseexit" }, + bindOne = function(o, c, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.bind(o, evt, function(ee) { + c.fire(filteredEvent, c, ee); + }); + }, + unbindOne = function(o, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.unbind(o, evt); + }; + + this.attachListeners = function(o, c) { + for (var i = 0; i < events.length; i++) { + bindOne(o, c, events[i]); + } + }; + + var _updateAttachedElements = function(state, timestamp, sourceElement) { + var affectedElements = self.getAttachedElements(); // implemented in subclasses + if (affectedElements) { + for (var i = 0; i < affectedElements.length; i++) { + if (!sourceElement || sourceElement != affectedElements[i]) + affectedElements[i].setHover(state, true, timestamp); // tell the attached elements not to inform their own attached elements. + } + } + }; + + this.reattachListenersForElement = function(o, c) { + for (var i = 0; i < events.length; i++) + unbindOne(o, events[i]); + self.attachListeners(o, c); + }; + }; + + var jsPlumbInstance = function(_defaults) { + + /* + * Property: Defaults + * + * These are the default settings for jsPlumb. They are what will be used if you do not supply specific pieces of information + * to the various API calls. A convenient way to implement your own look and feel can be to override these defaults + * by including a script somewhere after the jsPlumb include, but before you make any calls to jsPlumb. + * + * Properties: + * - *Anchor* The default anchor to use for all connections (both source and target). Default is "BottomCenter". + * - *Anchors* The default anchors to use ([source, target]) for all connections. Defaults are ["BottomCenter", "BottomCenter"]. + * - *Connector* The default connector definition to use for all connections. Default is "Bezier". + * - *Container* Optional selector or element id that instructs jsPlumb to append elements it creates to a specific element. + * - *DragOptions* The default drag options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *DropOptions* The default drop options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *Endpoint* The default endpoint definition to use for all connections (both source and target). Default is "Dot". + * - *Endpoints* The default endpoint definitions ([ source, target ]) to use for all connections. Defaults are ["Dot", "Dot"]. + * - *EndpointStyle* The default style definition to use for all endpoints. Default is fillStyle:"#456". + * - *EndpointStyles* The default style definitions ([ source, target ]) to use for all endpoints. Defaults are empty. + * - *EndpointHoverStyle* The default hover style definition to use for all endpoints. Default is null. + * - *EndpointHoverStyles* The default hover style definitions ([ source, target ]) to use for all endpoints. Defaults are null. + * - *HoverPaintStyle* The default hover style definition to use for all connections. Defaults are null. + * - *LabelStyle* The default style to use for label overlays on connections. + * - *LogEnabled* Whether or not the jsPlumb log is enabled. defaults to false. + * - *Overlays* The default overlay definitions. Defaults to an empty list. + * - *MaxConnections* The default maximum number of connections for an Endpoint. Defaults to 1. + * - *MouseEventsEnabled* Whether or not mouse events are enabled when using the canvas renderer. Defaults to true. + * The idea of this is just to give people a way to prevent all the mouse listeners from activating if they know they won't need mouse events. + * - *PaintStyle* The default paint style for a connection. Default is line width of 8 pixels, with color "#456". + * - *RenderMode* What mode to use to paint with. If you're on IE<9, you don't really get to choose this. You'll just get VML. Otherwise, the jsPlumb default is to use Canvas elements. + * - *Scope* The default "scope" to use for connections. Scope lets you assign connections to different categories. + */ + this.Defaults = { + Anchor : "BottomCenter", + Anchors : [ null, null ], + ConnectionsDetachable : true, + Connector : "Bezier", + Container : null, + DragOptions : { }, + DropOptions : { }, + Endpoint : "Dot", + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : "#456" }, + EndpointStyles : [ null, null ], + EndpointHoverStyle : null, + EndpointHoverStyles : [ null, null ], + HoverPaintStyle : null, + LabelStyle : { color : "black" }, + LogEnabled : false, + Overlays : [ ], + MaxConnections : 1, + MouseEventsEnabled : true, + PaintStyle : { lineWidth : 8, strokeStyle : "#456" }, + //Reattach:false, + RenderMode : "svg", + Scope : "jsPlumb_DefaultScope" + }; + if (_defaults) jsPlumb.extend(this.Defaults, _defaults); + + this.logEnabled = this.Defaults.LogEnabled; + + EventGenerator.apply(this); + var _bb = this.bind; + this.bind = function(event, fn) { + if ("ready" === event && initialized) fn(); + else _bb(event, fn); + }; + var _currentInstance = this, + log = null, + repaintFunction = function() { + jsPlumb.repaintEverything(); + }, + automaticRepaint = true, + repaintEverything = function() { + if (automaticRepaint) + repaintFunction(); + }, + resizeTimer = null, + initialized = false, + connectionsByScope = {}, + /** + * map of element id -> endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + endpointsByElement = {}, + endpointsByUUID = {}, + offsets = {}, + offsetTimestamps = {}, + floatingConnections = {}, + draggableStates = {}, + _mouseEventsEnabled = this.Defaults.MouseEventsEnabled, + canvasList = [], + sizes = [], + //listeners = {}, // a map: keys are event types, values are lists of listeners. + DEFAULT_SCOPE = this.Defaults.Scope, + renderMode = null, // will be set in init() + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }, + + /** + * appends an element to some other element, which is calculated as follows: + * + * 1. if _currentInstance.Defaults.Container exists, use that element. + * 2. if the 'parent' parameter exists, use that. + * 3. otherwise just use the document body. + * + */ + _appendElement = function(el, parent) { + if (_currentInstance.Defaults.Container) + jsPlumb.CurrentLibrary.appendElement(el, _currentInstance.Defaults.Container); + else if (!parent) + document.body.appendChild(el); + else + jsPlumb.CurrentLibrary.appendElement(el, parent); + }, + + _curIdStamp = 1, + _idstamp = function() { return "" + _curIdStamp++; }, + + /** + * YUI, for some reason, put the result of a Y.all call into an object that contains + * a '_nodes' array, instead of handing back an array-like object like the other + * libraries do. + */ + _convertYUICollection = function(c) { + return c._nodes ? c._nodes : c; + }, + + /** + * Draws an endpoint and its connections. this is the main entry point into drawing connections as well + * as endpoints, since jsPlumb is endpoint-centric under the hood. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + _draw = function(element, ui, timestamp) { + var id = _getAttribute(element, "id"); + _currentInstance.anchorManager.redraw(id, ui, timestamp); + }, + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }, + + /** + * gets an Endpoint by uuid. + */ + _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }, + + /** + * inits a draggable if it's not already initialised. + */ + _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? false : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element) && !jsPlumb.CurrentLibrary.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + draggableStates[_getId(element)] = true; + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options, false); + } + } + }, + + /* + * prepares a final params object that can be passed to _newConnection, taking into account defaults, events, etc. + */ + _prepareConnectionParams = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + // copy in any connectorOverlays that were specified on the source endpoint. + // it doesnt copy target endpoint overlays. i'm not sure if we want it to or not. + if (_p.sourceEndpoint && _p.sourceEndpoint.connectorOverlays) { + _p.overlays = _p.overlays || []; + for (var i = 0; i < _p.sourceEndpoint.connectorOverlays.length; i++) { + _p.overlays.push(_p.sourceEndpoint.connectorOverlays[i]); + } + } + + // tooltip. params.tooltip takes precedence, then sourceEndpoint.connectorTooltip. + _p.tooltip = params.tooltip; + if (!_p.tooltip && _p.sourceEndpoint && _p.sourceEndpoint.connectorTooltip) + _p.tooltip = _p.sourceEndpoint.connectorTooltip; + + if (_p.target && !_p.target.endpoint) { + var tid = _getId(_p.target), + tep =_targetEndpointDefinitions[tid]; + + var overrideOne = function(singlePropertyName, pluralPropertyName, tepProperty, tep) { + if (tep[tepProperty]) { + if (_p[pluralPropertyName]) _p[pluralPropertyName][1] = tep[tepProperty]; + else if (_p[singlePropertyName]) { + _p[pluralPropertyName] = [ _p[singlePropertyName], tep[tepProperty] ]; + _p[singlePropertyName] = null; + } + else _p[pluralPropertyName] = [ null, tep[tepProperty] ]; + } + }; + + if (tep) { + overrideOne("endpoint", "endpoints", "endpoint", tep); + overrideOne("endpointStyle", "endpointStyles", "paintStyle", tep); + overrideOne("endpointHoverStyle", "endpointHoverStyles", "hoverPaintStyle", tep); + } + } + + // dynamic anchors. backwards compatibility here: from 1.2.6 onwards you don't need to specify "dynamicAnchors". the fact that some anchor consists + // of multiple definitions is enough to tell jsPlumb you want it to be dynamic. + if (_p.dynamicAnchors) { + // these can either be an array of anchor coords, which we will use for both source and target, or an object with {source:[anchors], target:[anchors]}, in which + // case we will use a different set for each element. + var a = _p.dynamicAnchors.constructor == Array, + sourceId = _p.sourceEndpoint ? _p.sourceEndpoint.elementId : _getId(_p.source), + targetId = _p.targetEndpoint ? _p.targetEndpoint.elementId : _getId(_p.target), + sa = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors, sourceId, _currentInstance)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.source, sourceId, _currentInstance)), + ta = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors, targetId, _currentInstance)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.target, targetId, _currentInstance)); + _p.anchors = [sa,ta]; + } + return _p; + }, + + _newConnection = function(params) { + var connectionFunc = jsPlumb.Defaults.ConnectionType || jsPlumb.getDefaultConnectionType(), + endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint, + parent = jsPlumb.CurrentLibrary.getParent; + + if (params.container) + params["parent"] = params.container; + else { + if (params.sourceEndpoint) + params["parent"] = params.sourceEndpoint.parent; + else if (params.source.constructor == endpointFunc) + params["parent"] = params.source.parent; + else params["parent"] = parent(params.source); + } + + params["_jsPlumb"] = _currentInstance; + var con = new connectionFunc(params); + con.id = "con_" + _idstamp(); + _eventFireProxy("click", "click", con); + _eventFireProxy("dblclick", "dblclick", con); + return con; + }, + + /** + * adds the connection to the backing model, fires an event if necessary and then redraws + */ + _finaliseConnection = function(jpc, params) { + params = params || {}; + // add to list of connections (by scope). + if (!jpc.suspendedEndpoint) + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + if (!params.doNotFireConnectionEvent && params.fireEvent !== false) { + _currentInstance.fire("jsPlumbConnection", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + } + // always inform the anchor manager + // except that if jpc has a suspended endpoint it's not true to say the + // connection is new; it has just (possibly) moved. the question is whether + // to make that call here or in the anchor manager. i think perhaps here. + _currentInstance.anchorManager.newConnection(jpc); + // force a paint + _draw(jpc.source); + }, + + _eventFireProxy = function(event, proxyEvent, obj) { + obj.bind(event, function(originalObject, originalEvent) { + _currentInstance.fire(proxyEvent, obj, originalEvent); + }); + }, + + /** + * for the given endpoint params, returns an appropriate parent element for the UI elements that will be added. + * this function is used by _newEndpoint (directly below), and also in the makeSource function in jsPlumb. + * + * the logic is to first look for a "container" member of params, and pass that back if found. otherwise we + * handoff to the 'getParent' function in the current library. + */ + _getParentFromParams = function(params) { + if (params.container) + return params.container; + else { + var tag = jsPlumb.CurrentLibrary.getTagName(params.source), + p = jsPlumb.CurrentLibrary.getParent(params.source); + if (tag.toLowerCase() === "td") + return jsPlumb.CurrentLibrary.getParent(p); + else return p; + } + }, + + /** + factory method to prepare a new endpoint. this should always be used instead of creating Endpoints + manually, since this method attaches event listeners and an id. + */ + _newEndpoint = function(params) { + var endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint; + params.parent = _getParentFromParams(params); + params["_jsPlumb"] = _currentInstance; + var ep = new endpointFunc(params); + ep.id = "ep_" + _idstamp(); + _eventFireProxy("click", "endpointClick", ep); + _eventFireProxy("dblclick", "endpointDblClick", ep); + return ep; + }, + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + _operation = function(elId, func, endpointFunc) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + if (endpointFunc) endpointFunc(endpoints[i]); + } + } + }, + /** + * perform an operation on all elements. + */ + _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }, + + /** + * helper to remove an element from the DOM. + */ + _removeElement = function(element, parent) { + if (element != null && element.parentNode != null) { + element.parentNode.removeChild(element); + } + }, + /** + * helper to remove a list of elements from the DOM. + */ + _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }, + /** + * helper method to remove an item from a list. + */ + _removeFromList = function(map, key, value) { + if (key != null) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete (l[i]); + l.splice(i, 1); + return true; + } + } + } + return false; + }, + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }, + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + _setVisible = function(el, state, alsoChangeEndpoints) { + state = state === "block"; + var endpointFunc = null; + if (alsoChangeEndpoints) { + if (state) endpointFunc = function(ep) { + ep.setVisible(true, true, true); + }; + else endpointFunc = function(ep) { + ep.setVisible(false, true, true); + }; + } + var id = _getAttribute(el, "id"); + _operation(id, function(jpc) { + if (state && alsoChangeEndpoints) { + // this test is necessary because this functionality is new, and i wanted to maintain backwards compatibility. + // this block will only set a connection to be visible if the other endpoint in the connection is also visible. + var oidx = jpc.sourceId === id ? 1 : 0; + if (jpc.endpoints[oidx].isVisible()) jpc.setVisible(true); + } + else // the default behaviour for show, and what always happens for hide, is to just set the visibility without getting clever. + jpc.setVisible(state); + }, endpointFunc); + }, + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? false : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }, + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + _toggleVisible = function(elId, changeEndpoints) { + var endpointFunc = null; + if (changeEndpoints) { + endpointFunc = function(ep) { + var state = ep.isVisible(); + ep.setVisible(!state); + }; + } + _operation(elId, function(jpc) { + var state = jpc.isVisible(); + jpc.setVisible(!state); + }, endpointFunc); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }, + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return offsets[elId]; + } + if (recalc || !offset) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + if (sizes[elId] == null) { + var s = _getElementObject(elId); + if (s != null) + sizes[elId] = _getSize(s); + } + } + + if(offsets[elId] && !offsets[elId].right) { + offsets[elId].right = offsets[elId].left + sizes[elId][0]; + offsets[elId].bottom = offsets[elId].top + sizes[elId][1]; + offsets[elId].width = sizes[elId][0]; + offsets[elId].height = sizes[elId][1]; + offsets[elId].centerx = offsets[elId].left + (offsets[elId].width / 2); + offsets[elId].centery = offsets[elId].top + (offsets[elId].height / 2); + } + return offsets[elId]; + }, + + _getCachedData = function(elId) { + var o = offsets[elId]; + if (!o) o = _updateOffset(elId); + return {o:o, s:sizes[elId]}; + }, + +/** + * gets an id for the given element, creating and setting one if + * necessary. + */ + _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else + id = "jsPlumb_" + _idstamp(); + _setAttribute(ele, "id", id); + } + return id; + }, + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'jsPlumb function failed : ' + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'wrapped function failed : ' + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = "_jsPlumb_connector"; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = "_jsPlumb_endpoint"; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = "_jsPlumb_overlay"; + + this.Anchors = {}; + + this.Connectors = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Endpoints = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Overlays = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + +// ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***************************************** + /* + * Function: bind + * Bind to an event on jsPlumb. + * + * Parameters: + * event - the event to bind. Available events on jsPlumb are: + * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback. + * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback. + * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback. + * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback. + * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback. + * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback. + * + * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event. + */ + + /* + * Function: clearListeners + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + +// *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *********************************************************** + + /* + Function: addClass + + Helper method to abstract out differences in setting css classes on the different renderer types. + */ + this.addClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.addClass(el, clazz); + }; + + /* + Function: removeClass + + Helper method to abstract out differences in setting css classes on the different renderer types. + */ + this.removeClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.removeClass(el, clazz); + }; + + /* + Function: hasClass + + Helper method to abstract out differences in testing for css classes on the different renderer types. + */ + this.hasClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.hasClass(el, clazz); + }; + + /* + Function: addEndpoint + + Adds an to a given element or elements. + + Parameters: + + el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + params - Object containing Endpoint constructor arguments. For more information, see . + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some + shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in + this object are anything that 'params' can contain. See . + + Returns: + The newly created , if el referred to a single element. Otherwise, an array of newly created s. + + See Also: + + */ + this.addEndpoint = function(el, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + //p.scope="_jsPlumb_DefaultScope"; + //p.scope="fff"; + // YUI wrapper + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + var _el = _getElementObject(inputs[i]), id = _getId(_el); + p.source = _el; + //p.scope="f"; + _updateOffset({ elId : id }); + var e = _newEndpoint(p); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + results.push(e); + } + + return results.length == 1 ? results[0] : results; + }; + + /* + Function: addEndpoints + Adds a list of s to a given element or elements. + + Parameters: + target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See 's constructor documentation. + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created s, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(el, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams); + if (e.constructor == Array) + Array.prototype.push.apply(results, e); + else results.push(e); + } + return results; + }; + + /* + Function: animate + This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating + the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // onComplete repaints, just to make sure everything looks good at the end of the animation. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /** + * checks for a listener for the given condition, executing it if found, passing in the given value. + * condition listeners would have been attached using "bind" (which is, you could argue, now overloaded, since + * firing click events etc is a bit different to what this does). i thought about adding a "bindCondition" + * or something, but decided against it, for the sake of simplicity. jsPlumb will never fire one of these + * condition events anyway. + */ + this.checkCondition = function(conditionName, value) { + var l = _currentInstance.getListener(conditionName); + var r = true; + if (l && l.length > 0) { + try { + for (var i = 0 ; i < l.length; i++) { + r = r && l[i](value); + } + } + catch (e) { + _log(_currentInstance, "cannot check condition [" + conditionName + "]" + e); + } + } + return r; + }; + + /* + Function: connect + Establishes a between two elements (or s, which are themselves registered to elements). + + Parameters: + params - Object containing constructor arguments for the Connection. See 's constructor documentation. + referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of + Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection. + + Returns: + The newly created . + */ + this.connect = function(params, referenceParams) { + // prepare a final set of parameters to create connection with + var _p = _prepareConnectionParams(params, referenceParams); + // TODO probably a nicer return value if the connection was not made. _prepareConnectionParams + // will return null (and log something) if either endpoint was full. what would be nicer is to + // create a dedicated 'error' object. + if (_p) { + // a connect call will delete its created endpoints on detach, unless otherwise specified. + // this is because the endpoints belong to this connection only, and are no use to + // anyone else, so they hang around like a bad smell. + if (_p.deleteEndpointsOnDetach == null) + _p.deleteEndpointsOnDetach = true; + + // create the connection. it is not yet registered + var jpc = _newConnection(_p); + // now add it the model, fire an event, and redraw + _finaliseConnection(jpc, _p); + return jpc; + } + }; + + /* + Function: deleteEndpoint + Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too) + + Parameters: + object - either an object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElement(endpoint.canvas, endpoint.parent); + // remove from endpointsbyElement + // DEPRECATED. SHOULD NOT BE NECESSARY ONCE THE ANCHOR MANAGER IS WORKING PROPERLY. + _currentInstance.anchorManager.deleteEndpoint(endpoint); + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + delete endpoint; + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every , and their associated s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc, doFireEvent) { + // may have been given a connection, or in special cases, an object + var connType = jsPlumb.Defaults.ConnectionType || jsPlumb.getDefaultConnectionType(), + argIsConnection = jpc.constructor == connType, + params = argIsConnection ? { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + } : jpc; + + if (doFireEvent) _currentInstance.fire("jsPlumbConnectionDetached", params); + _currentInstance.anchorManager.connectionDetached(params); + }; + + /* + Function: detach + Detaches and then removes a . From 1.3.5 this method has been altered to remove support for + specifying Connections by various parameters; you can now pass in a Connection as the first argument and + an optional parameters object as a second argument. If you need the functionality this method provided + before 1.3.5 then you should use the getConnections method to get the list of Connections to detach, and + then iterate through them, calling this for each one. + + Parameters: + connection - the to detach + params - optional parameters to the detach call. valid values here are + fireEvent : defaults to false; indicates you want jsPlumb to fire a connection + detached event. The thinking behind this is that if you made a programmatic + call to detach an event, you probably don't need the callback. + forceDetach : defaults to false. allows you to override any beforeDetach listeners that may be registered. + + Returns: + true if successful, false if not. + */ + this.detach = function() { + + if (arguments.length == 0) return; + var connType = jsPlumb.Defaults.ConnectionType || jsPlumb.getDefaultConnectionType(), + firstArgIsConnection = arguments[0].constructor == connType, + params = arguments.length == 2 ? firstArgIsConnection ? (arguments[1] || {}) : arguments[0] : arguments[0], + fireEvent = (params.fireEvent !== false), + forceDetach = params.forceDetach, + connection = firstArgIsConnection ? arguments[0] : params.connection; + + if (connection) { + if (forceDetach || (connection.isDetachAllowed(connection) + && connection.endpoints[0].isDetachAllowed(connection) + && connection.endpoints[1].isDetachAllowed(connection))) { + if (forceDetach || _currentInstance.checkCondition("beforeDetach", connection)) + connection.endpoints[0].detach(connection, false, true, fireEvent); // TODO check this param iscorrect for endpoint's detach method + } + } + else { + var _p = jsPlumb.extend( {}, params); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1]), fireEvent); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source), + targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + if (_currentInstance.checkCondition("beforeDetach", jpc)) { + jpc.endpoints[0].detach(jpc, false, true, fireEvent); + } + } + }); + } + } + }; + + /* + Function: detachAllConnections + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + params - optional parameters. alowed values: + fireEvent : defaults to true, whether or not to fire the detach event. + + Returns: + void + */ + this.detachAllConnections = function(el, params) { + params = params || {}; + el = _getElementObject(el); + var id = _getAttribute(el, "id"), + endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + }; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Parameters: + params - optional params object containing: + fireEvent : whether or not to fire detach events. defaults to true. + + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function(params) { + params = params || {}; + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of y + our library's draggable method so that jsPlumb can setup the appropriate callbacks. Your + underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + // TODO it would be nice if this supported a selector string, instead of an id. + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method, + * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the + * default scope). If multiple scopes are passed in, the return value will be a map of { scope -> [ connection... ] }. + * + * Parameters + * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list + * of connections that are in the given scope. + * options - if the argument is a JS object, you can specify a finer-grained filter: + * + * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes. + * - *source* either a string representing an element id, or a selector. constrains the result to connections having this source. + * - *target* either a string representing an element id, or a selector. constrains the result to connections having this target. + * + */ + this.getConnections = function(options) { + if (!options) { + options = {}; + } else if (options.constructor == String) { + options = { "scope": options }; + } + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') + r.push(input); + else + r = input; + } + return r; + }; + var scope = options.scope || jsPlumb.getDefaultScope(), + scopes = prepareList(scope), + sources = prepareList(options.source), + targets = prepareList(options.target), + filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }, + results = scopes.length > 1 ? {} : [], + _addOne = function(scope, obj) { + if (scopes.length > 1) { + var ss = results[scope]; + if (ss == null) { + ss = []; results[scope] = ss; + } + ss.push(obj); + } else results.push(obj); + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + _addOne(i, c); + } + } + } + return results; + }; + + /* + * Function: getAllConnections + * Gets all connections, as a map of { scope -> [ connection... ] }. + */ + this.getAllConnections = function() { + return connectionsByScope; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /** + * Function:getEndpoints + * Gets the list of Endpoints for a given selector, or element id. + * @param el + * @return + */ + this.getEndpoints = function(el) { + return endpointsByElement[_getId(el)]; + }; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + this.getOffset = function(id) { + var o = offsets[id]; + return _updateOffset({elId:id}); + }; + + this.getSelector = function(spec) { + return jsPlumb.CurrentLibrary.getSelector(spec); + }; + + this.getSize = function(id) { + var s = sizes[id]; + if (!s) _updateOffset({elId:id}); + return sizes[id]; + }; + + this.appendElement = _appendElement; + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + changeEndpoints - whether not to also hide endpoints on the element. by default this is false. + + Returns: + void + */ + this.hide = function(el, changeEndpoints) { + _setVisible(el, "none", changeEndpoints); + }; + + // exposed for other objects to use to get a unique id. + this.idstamp = _idstamp; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + if (!initialized) { + _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run. + + var bindOne = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && _mouseEventsEnabled && renderMode == jsPlumb.CANVAS) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + var t = c[i].connector[event](e); + if (t) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i].endpoint[event](e)) return; + } + } + } + }); + }; + bindOne("click");bindOne("dblclick");bindOne("mousemove");bindOne("mousedown");bindOne("mouseup"); + + initialized = true; + _currentInstance.fire("ready"); + } + }; + + this.log = log; + this.jsPlumbUIComponent = jsPlumbUIComponent; + this.EventGenerator = EventGenerator; + + /* + * Creates an anchor with the given params. + * + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function() { + if (arguments.length == 0) return null; + var specimen = arguments[0], elementId = arguments[1], jsPlumbInstance = arguments[2], newAnchor = null; + if (!jsPlumbInstance) + throw "NO JSPLUMB SET"; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; //TODO hazy here about whether it should be added or is already added somehow. + // is it the name of an anchor type? + else if (typeof specimen == "string") { + newAnchor = _currentInstance.Anchors[arguments[0]]({elementId:elementId, jsPlumbInstance:_currentInstance}); + } + // is it an array? it will be one of: + // an array of [name, params] - this defines a single anchor + // an array of arrays - this defines some dynamic anchors + // an array of numbers - this defines a single anchor. + else if (specimen.constructor == Array) { + if (specimen[0].constructor == Array || specimen[0].constructor == String) { + if (specimen.length == 2 && specimen[0].constructor == String && specimen[1].constructor == Object) { + var pp = jsPlumb.extend({elementId:elementId, jsPlumbInstance:_currentInstance}, specimen[1]); + newAnchor = _currentInstance.Anchors[specimen[0]](pp); + } + else + newAnchor = new DynamicAnchor(specimen, null, elementId); + } + else { + var anchorParams = { + x:specimen[0], y:specimen[1], + orientation : (specimen.length >= 4) ? [ specimen[2], specimen[3] ] : [0,0], + offsets : (specimen.length == 6) ? [ specimen[4], specimen[5] ] : [ 0, 0 ], + elementId:elementId + }; + newAnchor = new Anchor(anchorParams); + newAnchor.clone = function() { return new Anchor(anchorParams); }; + } + } + + if (!newAnchor.id) newAnchor.id = "anchor_" + _idstamp(); + return newAnchor; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types, elementId, jsPlumbInstance) { + var r = []; + for ( var i = 0; i < types.length; i++) { + if (typeof types[i] == "string") + r.push(_currentInstance.Anchors[types[i]]({elementId:elementId, jsPlumbInstance:jsPlumbInstance})); + else if (types[i].constructor == Array) + r.push(jsPlumb.makeAnchor(types[i], elementId, jsPlumbInstance)); + } + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /** + * Function: makeTarget + * Makes some DOM element a Connection target, allowing you to drag connections to it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a target. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * scope optional. scope for the drop zone. + * dropOptions optional. same stuff as you would pass to dropOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to true. whether or not to delete + * any Endpoints created by a connection to this target if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + var _targetEndpointDefinitions = {}; + this.makeTarget = function(el, params, referenceParams) { + + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + var jpcl = jsPlumb.CurrentLibrary, + targetScope = p.scope || _currentInstance.Defaults.Scope, + deleteEndpointsOnDetach = p.deleteEndpointsOnDetach || true, + _doOne = function(_el) { + + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el); + _targetEndpointDefinitions[elid] = p.endpoint; + + var dropOptions = jsPlumb.extend({}, p.dropOptions || {}), + _drop = function() { + _currentInstance.currentlyDragging = false; + var draggable = _getElementObject(jpcl.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + // restore the original scope if necessary (issue 57) + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id], + source = jpc.endpoints[0], + _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : {}; + + // use defaults for endpoint style, if not present..this either uses EndpointStyle, or EndpointStyles[1], + // if it is present, since this is a target endpoint. + // TODO - this should be a helper method. makeTarget should use it too. give it an endpoint + _endpoint.paintStyle = _endpoint.paintStyle || + _currentInstance.Defaults.EndpointStyles[1] || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyles[1] || + jsPlumb.Defaults.EndpointStyle; + + _endpoint.hoverPaintStyle = _endpoint.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyles[1] || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyles[1] || + jsPlumb.Defaults.EndpointHoverStyle; + + _endpoint.anchor = _endpoint.anchor || + _currentInstance.Defaults.Anchors[1] || + _currentInstance.Defaults.Anchor || + jsPlumb.Defaults.Anchors[1] || + jsPlumb.Defaults.Anchor; + + // unlock the source anchor to allow it to refresh its position if necessary + source.anchor.locked = false; + + // if (!jpcl.hasClass(draggable, _currentInstance.endpointClass)) return; + + if (scope) jpcl.setDragScope(draggable, scope); + + // check if drop is allowed here. + var _continue = jpc.isDropAllowed(jpc.sourceId, _getId(_el), jpc.scope); + + // regardless of whether the connection is ok, reconfigure the existing connection to + // point at the current info. we need this to be correct for the detach event that will follow. + // clear the source endpoint from the list to detach. we will detach this connection at this + // point, but we want to keep the source endpoint. the target is a floating endpoint and should + // be removed. TODO need to figure out whether this code can result in endpoints kicking around + // when they shouldnt be. like is this a full detach of a connection? can it be? + if (jpc.endpointsToDeleteOnDetach) { + if (source === jpc.endpointsToDeleteOnDetach[0]) + jpc.endpointsToDeleteOnDetach[0] = null; + else if (source === jpc.endpointsToDeleteOnDetach[1]) + jpc.endpointsToDeleteOnDetach[1] = null; + } + // reinstate any suspended endpoint; this just puts the connection back into + // a state in which it will report sensible values if someone asks it about + // its target. we're going to throw this connection away shortly so it doesnt matter + // if we manipulate it a bit. + if (jpc.suspendedEndpoint) { + jpc.targetId = jpc.suspendedEndpoint.elementId; + jpc.target = jpcl.getElementObject(jpc.suspendedEndpoint.elementId); + jpc.endpoints[1] = jpc.suspendedEndpoint; + } + + if (_continue) { + + // detach this connection from the source. + source.detach(jpc, false, true, true);//source.endpointWillMoveAfterConnection); + + // make a new Endpoint for the target + var newEndpoint = jsPlumb.addEndpoint(_el, _endpoint); + + // if the anchor has a 'positionFinder' set, then delegate to that function to find + // out where to locate the anchor. + if (newEndpoint.anchor.positionFinder != null) { + var dropPosition = jpcl.getUIPosition(arguments), + elPosition = jpcl.getOffset(_el), + elSize = jpcl.getSize(_el), + ap = newEndpoint.anchor.positionFinder(dropPosition, elPosition, elSize, newEndpoint.anchor); + newEndpoint.anchor.x = ap[0]; + newEndpoint.anchor.y = ap[1]; + // now figure an orientation for it..kind of hard to know what to do actually. probably the best thing i can do is to + // support specifying an orientation in the anchor's spec. if one is not supplied then i will make the orientation + // be what will cause the most natural link to the source: it will be pointing at the source, but it needs to be + // specified in one axis only, and so how to make that choice? i think i will use whichever axis is the one in which + // the target is furthest away from the source. + } + var c = jsPlumb.connect({ + source:source, + target:newEndpoint, + scope:scope, + previousConnection:jpc, + container:jpc.parent, + // 'endpointWillMoveAfterConnection' is set by the makeSource function, and it indicates that the + // given endpoint will actually transfer from the element it is currently attached to to some other + // element after a connection has been established. in that case, we do not want to fire the + // connection event, since it will have the wrong data in it; makeSource will do it for us. + // this is controlled by the 'parent' parameter on a makeSource call. + doNotFireConnectionEvent:source.endpointWillMoveAfterConnection + }); + if (deleteEndpointsOnDetach) + c.endpointsToDeleteOnDetach = [ source, newEndpoint ]; + } + // if not allowed to drop... + else { + // TODO this code is identical (pretty much) to what happens when a connection + // dragged from a normal endpoint is in this situation. refactor. + // is this an existing connection, and will we reattach? + if (jpc.suspendedEndpoint) { + if (source.isReattach) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(source.elementId); + } + else + source.detach(jpc, false, true, true); // otherwise, detach the connection and tell everyone about it. + } + + } + }; + + var dropEvent = jpcl.dragEvents['drop']; + dropOptions["scope"] = dropOptions["scope"] || targetScope; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop); + + jpcl.initDroppable(_el, dropOptions, true); + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements drop targets. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeTargets = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeTarget(els[i], params, referenceParams); + } + }; + + /** + * Function: makeSource + * Makes some DOM element a Connection source, allowing you to drag connections from it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a source. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * parent optional. the element to add Endpoints to when a Connection is established. if you omit this, + * Endpoints will be added to 'el'. + * scope optional. scope for the connections dragged from this element. + * dragOptions optional. same stuff as you would pass to dragOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to false. whether or not to delete + * any Endpoints created by a connection from this source if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + var _sourceEndpointDefinitions = {}; + this.makeSource = function(el, params, referenceParams) { + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + var jpcl = jsPlumb.CurrentLibrary, + _doOne = function(_el) { + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el); + _sourceEndpointDefinitions[elid] = p.endpoint || {}; + var stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"], + dragOptions = jsPlumb.extend({ }, _sourceEndpointDefinitions[elid].dragOptions || {}), + ep = null, + endpointAddedButNoDragYet = false; + + // make sure this method honours whatever defaults have been set for Endpoint. + _sourceEndpointDefinitions[elid].endpoint = _sourceEndpointDefinitions[elid].endpoint || _currentInstance.Defaults.Endpoints[0] || _currentInstance.Defaults.Endpoint; + + // TODO this, and the makeTarget equivalent, and probably elsewhere, should all be handed off to + // some helper method that can make this decision for us. + _sourceEndpointDefinitions[elid].paintStyle = _sourceEndpointDefinitions[elid].paintStyle || + _currentInstance.Defaults.EndpointStyles[0] || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyles[0] || + jsPlumb.Defaults.EndpointStyle; + + _sourceEndpointDefinitions[elid].hoverPaintStyle = _sourceEndpointDefinitions[elid].hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyles[0] || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyles[0] || + jsPlumb.Defaults.EndpointHoverStyle; + + _sourceEndpointDefinitions[elid].anchor = _sourceEndpointDefinitions[elid].anchor || + _currentInstance.Defaults.Anchors[0] || + _currentInstance.Defaults.Anchor || + jsPlumb.Defaults.Anchors[0] || + jsPlumb.Defaults.Anchor; + + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + endpointAddedButNoDragYet = false; + }); + + dragOptions[stopEvent] = function() { + // here we need to do a couple of things; + // first determine whether or not a connection was dragged. if not, just delete this endpoint. + // ...if so, though, then we need to check to see if a 'parent' was specified in the + // options to makeSource. a 'parent' is a reference to an element other than the one from + // which the connection is dragged, and it indicates that after a successful connection, the + // endpoint should be moved off of this element and onto 'parent', using all of the + // options passed in to the makeSource call. + // + // one thing that occurs to me right now is that we dont really want the first + // connection to have fired a connection event. but how can we prevent it from doing so? + // + // + + //_currentlyDown = false; + _currentInstance.currentlyDragging = false; + + if (ep.connections.length == 0) + jsPlumb.deleteEndpoint(ep); + else { + + jpcl.unbind(ep.canvas, "mousedown"); + + // reset the anchor to the anchor that was initially provided. the one we were using to drag + // the connection was just a placeholder that was located at the place the user pressed the + // mouse button to initiate the drag. + var anchorDef = _sourceEndpointDefinitions[elid].anchor || _currentInstance.Defaults.Anchor; + ep.anchor = jsPlumb.makeAnchor(anchorDef, elid, _currentInstance); + + if (p.parent) { + var parent = jpcl.getElementObject(p.parent); + if (parent) { + var currentId = ep.elementId; + ep.setElement(parent); + ep.endpointWillMoveAfterConnection = false; + _currentInstance.anchorManager.rehomeEndpoint(currentId, parent); + var jpc = ep.connections[0]; // TODO will this always be correct? + _finaliseConnection(jpc); + _currentInstance.repaintEverything(); + } + } + _currentInstance.repaint(elid); + } + }; + // when the user presses the mouse, add an Endpoint + var mouseDownListener = function(e) { + // make sure we have the latest offset for this div + _updateOffset({elId:elid}); + // and get it, and the div's size + var myOffset = offsets[elid], myWH = sizes[elid]; + // if there is a parent, the endpoint will actually be added to it now, rather than the div + // that was the source. in that case, we have to adjust the anchor position so it refers to + // the parent. + if (p.parent) { + var pEl = jsPlumb.CurrentLibrary.getElementObject(p.parent), + pId = _getId(pEl); + _updateOffset({elId:pId}); + myOffset = offsets[pId]; + myWH = sizes[pId]; + } + + var x = ((e.pageX || e.page.x) - myOffset.left) / myWH[0], + y = ((e.pageY || e.page.y) - myOffset.top) / myWH[1]; + + // we need to override the anchor in here, and force 'isSource', but we don't want to mess with + // the params passed in, because after a connection is established we're going to reset the endpoint + // to have the anchor we were given. + var tempEndpointParams = {}; + jsPlumb.extend(tempEndpointParams, _sourceEndpointDefinitions[elid]); + tempEndpointParams.isSource = true; + tempEndpointParams.anchor = [x,y,0,0]; + tempEndpointParams.dragOptions = dragOptions; + // if a parent was given we need to turn that into a "container" argument. this is, by default, + // the parent of the element we will move to, so parent of p.parent in this case. however, if + // the user has specified a 'container' on the endpoint definition or on + // the defaults, we should use that. + if (p.parent) { + var potentialParent = tempEndpointParams.container || jsPlumb.Defaults.Container; + if (potentialParent) + tempEndpointParams.container = potentialParent; + else + tempEndpointParams.container = jsPlumb.CurrentLibrary.getParent(p.parent); + } + + ep = jsPlumb.addEndpoint(elid, tempEndpointParams); + + endpointAddedButNoDragYet = true; + // we set this to prevent connections from firing attach events before this function has had a chance + // to move the endpoint. + ep.endpointWillMoveAfterConnection = p.parent != null; + ep.endpointWillMoveTo = p.parent ? jpcl.getElementObject(p.parent) : null; + + var _delTempEndpoint = function() { + // this mouseup event is fired only if no dragging occurred, by jquery and yui, but for mootools + // it is fired even if dragging has occurred, in which case we would blow away a perfectly + // legitimate endpoint, were it not for this check. the flag is set after adding an + // endpoint and cleared in a drag listener we set in the dragOptions above. + if(endpointAddedButNoDragYet) { + jsPlumb.deleteEndpoint(ep); + } + }; + + _currentInstance.registerListener(ep.canvas, "mouseup", _delTempEndpoint); + _currentInstance.registerListener(_el, "mouseup", _delTempEndpoint); + + // and then trigger its mousedown event, which will kick off a drag, which will start dragging + // a new connection from this endpoint. + jpcl.trigger(ep.canvas, "mousedown", e); + }; + + // jpcl.bind(_el, "mousedown", mouseDownListener); + // register this on jsPlumb so that it can be cleared by a reset. + _currentInstance.registerListener(_el, "mousedown", mouseDownListener); + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements connection sources. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeSources = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeSource(els[i], params, referenceParams); + } + }; + + /* + Function: ready + Helper method to bind a function to jsPlumb's ready event. + */ + this.ready = function(fn) { + _currentInstance.bind("ready", fn); + }, + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"), + ebe = endpointsByElement[elId]; + if (ebe) { + for ( var i = 0; i < ebe.length; i++) + _currentInstance.deleteEndpoint(ebe[i]); + } + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + var _registeredListeners = {}, + _unbindRegisteredListeners = function() { + for (var i in _registeredListeners) { + for (var j = 0; j < _registeredListeners[i].length; j++) { + var info = _registeredListeners[i][j]; + jsPlumb.CurrentLibrary.unbind(info.el, info.event, info.listener); + } + } + _registeredListeners = {}; + }; + + // internal register listener method. gives us a hook to clean things up + // with if the user calls jsPlumb.reset. + this.registerListener = function(el, type, listener) { + jsPlumb.CurrentLibrary.bind(el, type, listener); + _addToList(_registeredListeners, type, {el:el, event:type, listener:listener}); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + this.deleteEveryEndpoint(); + this.clearListeners(); + _unbindRegisteredListeners(); + this.anchorManager.reset(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + + Parameters: + value - whether or not to automatically repaint when the window is resized. + + Returns: void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction + * Sets the function to fire when the window size has changed and a repaint was fired. + * + * Parameters: + * f - Function to execute. + * + * Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: setMouseEventsEnabled + * Sets whether or not mouse events are enabled. Default is true. + * + * Parameters: + * enabled - whether or not mouse events should be enabled. + * + * Returns: + * void + */ + this.setMouseEventsEnabled = function(enabled) { + _mouseEventsEnabled = enabled; + }; + + /* + * Constant for use with the setRenderMode method + */ + this.CANVAS = "canvas"; + + /* + * Constant for use with the setRenderMode method + */ + this.SVG = "svg"; + + this.VML = "vml"; + + /* + * Function: setRenderMode + * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that + * what you asked for is not supported (and that VML is). If you asked for VML but the browser does + * not support it, jsPlumb uses SVG. + * + * Returns: + * the render mode that jsPlumb set, which of course may be different from that requested. + */ + this.setRenderMode = function(mode) { + if (mode) + mode = mode.toLowerCase(); + else + return; + if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); + // now test we actually have the capability to do this. + if (mode === jsPlumb.CANVAS && canvasAvailable) + renderMode = jsPlumb.CANVAS; + else if (mode === jsPlumb.SVG && svgAvailable) + renderMode = jsPlumb.SVG; + else if (vmlAvailable) + renderMode = jsPlumb.VML; + + return renderMode; + }; + + this.getRenderMode = function() { return renderMode; }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * changeEndpoints - whether or not to also change the visible state of the endpoints on the element. this also has a bearing on + * other connections on those endpoints: if their other endpoint is also visible, the connections are made visible. + * + * Returns: + * void + */ + this.show = function(el, changeEndpoints) { + _setVisible(el, "block", changeEndpoints); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId, + makeAnchor:self.makeAnchor, + makeDynamicAnchor:self.makeDynamicAnchor + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * changeEndpoints - whether or not to also toggle the endpoints on the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload + * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element. + * + * Returns: + * void + */ + this.unload = function() { + delete endpointsByElement; + delete endpointsByUUID; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + delete canvasList; + }; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + var adjustForParentOffsetAndScroll = function(xy, el) { + var offsetParent = null, result = xy; + if (el.tagName.toLowerCase() === "svg" && el.parentNode) { + offsetParent = el.parentNode; + } + else if (el.offsetParent) { + offsetParent = el.offsetParent; + } + if (offsetParent != null) { + var po = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(offsetParent), + so = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : {left:offsetParent.scrollLeft, top:offsetParent.scrollTop}; + + // i thought it might be cool to do this: + // lastReturnValue[0] = lastReturnValue[0] - offsetParent.offsetLeft + offsetParent.scrollLeft; + // lastReturnValue[1] = lastReturnValue[1] - offsetParent.offsetTop + offsetParent.scrollTop; + // but i think it ignores margins. my reasoning was that it's quicker to not hand off to some underlying + // library. + + result[0] = xy[0] - po.left + so.left; + result[1] = xy[1] - po.top + so.top; + } + + return result; + }; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + this.elementId = params.elementId; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + + if (timestamp && timestamp === self.timestamp) + return lastReturnValue; + + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + + // adjust loc if there is an offsetParent + lastReturnValue = adjustForParentOffsetAndScroll(lastReturnValue, element.canvas); + + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + * + * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference, + // the canvas this refers to. + refCanvas = params.referenceCanvas, + size = _getSize(_getElementObject(refCanvas)), + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + xDir = 0, yDir = 0, + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + orientation = null, + _lastResult = null; + + this.compute = function(params) { + var xy = params.xy, element = params.element, + result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + + // adjust loc if there is an offsetParent + result = adjustForParentOffsetAndScroll(result, element.canvas); + + _lastResult = result; + return result; + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { orientation = anchor.getOrientation(); }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchor is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector, elementId) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = [], + _convert = function(anchor) { return anchor.constructor == Anchor ? anchor: jsPlumb.makeAnchor(anchor, elementId, _currentInstance); }; + for (var i = 0; i < anchors.length; i++) _anchors[i] = _convert(anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + this.locked = false; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null, + _curIndex = _anchors.length > 0 ? 0 : -1, + self = this, + + // helper method to calculate the distance between the centers of the two elements. + _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }, + + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + + return _curAnchor.compute(params); + }; + + this.getCurrentLocation = function() { + return _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + }; + + this.getOrientation = function() { return _curAnchor != null ? _curAnchor.getOrientation() : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + manages anchors for all elements. + */ + // "continuous" anchors: anchors that pick their location based on how many connections the given element has. + // this requires looking at a lot more elements than normal - anything that has had a Continuous anchor applied has + // to be recalculated. so this manager is used as a reference point. the first time, with a new timestamp, that + // a continuous anchor is asked to compute, it calls this guy. or maybe, even, this guy gets called somewhere else + // and compute only ever returns pre-computed values. either way, this is the central point, and we want it to + // be called as few times as possible. + var continuousAnchors = {}, + continuousAnchorLocations = {}, + continuousAnchorOrientations = {}, + Orientation = { HORIZONTAL : "horizontal", VERTICAL : "vertical", DIAGONAL : "diagonal", IDENTITY:"identity" }, + + // TODO this functions uses a crude method of determining orientation between two elements. + // 'diagonal' should be chosen when the angle of the line between the two centers is around + // one of 45, 135, 225 and 315 degrees. maybe +- 15 degrees. + calculateOrientation = function(sourceId, targetId, sd, td) { + + if (sourceId === targetId) return { + orientation:Orientation.IDENTITY, + a:["top", "top"] + }; + + var theta = Math.atan2((td.centery - sd.centery) , (td.centerx - sd.centerx)), + theta2 = Math.atan2((sd.centery - td.centery) , (sd.centerx - td.centerx)), + h = ((sd.left <= td.left && sd.right >= td.left) || (sd.left <= td.right && sd.right >= td.right) || + (sd.left <= td.left && sd.right >= td.right) || (td.left <= sd.left && td.right >= sd.right)), + v = ((sd.top <= td.top && sd.bottom >= td.top) || (sd.top <= td.bottom && sd.bottom >= td.bottom) || + (sd.top <= td.top && sd.bottom >= td.bottom) || (td.top <= sd.top && td.bottom >= sd.bottom)); + + if (! (h || v)) { + var a = null, rls = false, rrs = false, sortValue = null; + if (td.left > sd.left && td.top > sd.top) + a = ["right", "top"]; + else if (td.left > sd.left && sd.top > td.top) + a = [ "top", "left"]; + else if (td.left < sd.left && td.top < sd.top) + a = [ "top", "right"]; + else if (td.left < sd.left && td.top > sd.top) + a = ["left", "top" ]; + + return { orientation:Orientation.DIAGONAL, a:a, theta:theta, theta2:theta2 }; + } + else if (h) return { + orientation:Orientation.HORIZONTAL, + a:sd.top < td.top ? ["bottom", "top"] : ["top", "bottom"], + theta:theta, theta2:theta2 + } + else return { + orientation:Orientation.VERTICAL, + a:sd.left < td.left ? ["right", "left"] : ["left", "right"], + theta:theta, theta2:theta2 + } + }, + placeAnchorsOnLine = function(desc, elementDimensions, elementPosition, + connections, horizontal, otherMultiplier, reverse) { + var a = [], step = elementDimensions[horizontal ? 0 : 1] / (connections.length + 1); + + for (var i = 0; i < connections.length; i++) { + var val = (i + 1) * step, other = otherMultiplier * elementDimensions[horizontal ? 1 : 0]; + if (reverse) + val = elementDimensions[horizontal ? 0 : 1] - val; + + var dx = (horizontal ? val : other), x = elementPosition[0] + dx, xp = dx / elementDimensions[0], + dy = (horizontal ? other : val), y = elementPosition[1] + dy, yp = dy / elementDimensions[1]; + + a.push([ x, y, xp, yp, connections[i][1], connections[i][2] ]); + } + + return a; + }, + standardEdgeSort = function(a, b) { return a[0] > b[0] ? 1 : -1 }, + currySort = function(reverseAngles) { + return function(a,b) { + var r = true; + if (reverseAngles) { + if (a[0][0] < b[0][0]) + r = true; + else + r = a[0][1] > b[0][1]; + } + else { + if (a[0][0] > b[0][0]) + r= true; + else + r =a[0][1] > b[0][1]; + } + return r === false ? -1 : 1; + }; + }, + leftSort = function(a,b) { + // first get adjusted values + var p1 = a[0][0] < 0 ? -Math.PI - a[0][0] : Math.PI - a[0][0], + p2 = b[0][0] < 0 ? -Math.PI - b[0][0] : Math.PI - b[0][0]; + if (p1 > p2) return 1; + else return a[0][1] > b[0][1] ? 1 : -1; + }, + edgeSortFunctions = { + "top":standardEdgeSort, + "right":currySort(true), + "bottom":currySort(true), + "left":leftSort + }, + _sortHelper = function(_array, _fn) { + return _array.sort(_fn); + }, + placeAnchors = function(elementId, _anchorLists) { + var sS = sizes[elementId], sO = offsets[elementId], + placeSomeAnchors = function(desc, elementDimensions, elementPosition, unsortedConnections, isHorizontal, otherMultiplier) { + if (unsortedConnections.length > 0) { + var sc = _sortHelper(unsortedConnections, edgeSortFunctions[desc]), // puts them in order based on the target element's pos on screen + //sc = unsortedConnections.sort(edgeSortFunctions[desc]), // puts them in order based on the target element's pos on screen + reverse = desc === "right" || desc === "top", + anchors = placeAnchorsOnLine(desc, elementDimensions, + elementPosition, sc, + isHorizontal, otherMultiplier, reverse ); + + // takes a computed anchor position and adjusts it for parent offset and scroll, then stores it. + var _setAnchorLocation = function(endpoint, anchorPos) { + var a = adjustForParentOffsetAndScroll([anchorPos[0], anchorPos[1]], endpoint.canvas); + continuousAnchorLocations[endpoint.id] = [ a[0], a[1], anchorPos[2], anchorPos[3] ]; + }; + + for (var i = 0; i < anchors.length; i++) { + var c = anchors[i][4], weAreSource = c.endpoints[0].elementId === elementId, weAreTarget = c.endpoints[1].elementId === elementId; + if (weAreSource) + _setAnchorLocation(c.endpoints[0], anchors[i]); + else if (weAreTarget) + _setAnchorLocation(c.endpoints[1], anchors[i]); + } + } + }; + + placeSomeAnchors("bottom", sS, [sO.left,sO.top], _anchorLists.bottom, true, 1); + placeSomeAnchors("top", sS, [sO.left,sO.top], _anchorLists.top, true, 0); + placeSomeAnchors("left", sS, [sO.left,sO.top], _anchorLists.left, false, 0); + placeSomeAnchors("right", sS, [sO.left,sO.top], _anchorLists.right, false, 1); + }, + AnchorManager = function() { + var _amEndpoints = {}, + endpointConnectionsByElementId = {}, + continuousAnchorConnectionsByElementId = {}, + continuousAnchorEndpoints = [], + self = this, + anchorLists = {}; + + this.reset = function() { + endpointConnectionsByElementId = {}; + continuousAnchorConnectionsByElementId = {}; + continuousAnchorEndpoints = []; + anchorLists = {}; + }; + + this.newConnection = function(conn) { + var sourceId = conn.sourceId, targetId = conn.targetId, + ep = conn.endpoints, + doRegisterTarget = true, + registerConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + if (otherAnchor.constructor == DynamicAnchor || otherAnchor.constructor == Anchor) { + _addToList(endpointConnectionsByElementId, elId, [c, otherEndpoint, otherAnchor.constructor == DynamicAnchor]); + } + else { + // continuous. if they are the same element, just assign the same anchor + // to both. + if (sourceId == targetId) { + // remove the target endpoint's canvas. we dont need it. + jsPlumb.CurrentLibrary.removeElement(ep[1].canvas); + doRegisterTarget = false; + } + _addToList(continuousAnchorConnectionsByElementId, elId, c); + } + }; + registerConnection(0, ep[0], ep[0].anchor, targetId, conn); + if (doRegisterTarget) + registerConnection(1, ep[1], ep[1].anchor, sourceId, conn); + }; + this.connectionDetached = function(connInfo) { + var sourceId = connInfo.sourceId, + targetId = connInfo.targetId, + ep = connInfo.connection.endpoints, + removeConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + if (otherAnchor.constructor == FloatingAnchor) { + // no-op + } + else if (otherAnchor.constructor == DynamicAnchor || otherAnchor.constructor == Anchor) { + var _conns = endpointConnectionsByElementId[elId]; + if (_conns) { + _removeWithFunction(_conns, function(e) { return e[0].id == c.id; }); + } + } + else // continuous. + _removeFromList(continuousAnchorConnectionsByElementId, elId, c); + }; + + removeConnection(1, ep[1], ep[1].anchor, sourceId, connInfo.connection); + removeConnection(0, ep[0], ep[0].anchor, targetId, connInfo.connection); + + // remove from anchorLists + var sEl = connInfo.connection.sourceId, + tEl = connInfo.connection.targetId, + sE = connInfo.connection.endpoints[0].id, + tE = connInfo.connection.endpoints[1].id, + _remove = function(list, eId) { + if (list) { // transient anchors dont get entries in this list. + var f = function(e) { return e[4] == eId; }; + _removeWithFunction(list["top"], f); + _removeWithFunction(list["left"], f); + _removeWithFunction(list["bottom"], f); + _removeWithFunction(list["right"], f); + } + }; + + _remove(anchorLists[sEl], sE); + _remove(anchorLists[tEl], tE); + self.redraw(sEl); + self.redraw(tEl); + }; + this.add = function(endpoint, elementId) { + _addToList(_amEndpoints, elementId, endpoint); + }; + this.get = function(elementId) { + return { + "standard":endpointConnectionsByElementId[elementId] || [], + "continuous":continuousAnchorConnectionsByElementId[elementId] || [], + "endpoints":_amEndpoints[elementId], + "continuousAnchorEndpoints":continuousAnchorEndpoints + }; + }; + this.deleteEndpoint = function(endpoint) { + var cIdx = _findIndex(continuousAnchorEndpoints, endpoint); + if (cIdx > -1) + continuousAnchorEndpoints.splice(cIdx, 1); + else + _removeFromList(_amEndpoints, endpoint.elementId, endpoint); + }; + this.clearFor = function(elementId) { + delete _amEndpoints[elementId]; + _amEndpoints[elementId] = []; + }; + // updates the given anchor list by either updating an existing anchor's info, or adding it. this function + // also removes the anchor from its previous list, if the edge it is on has changed. + // all connections found along the way (those that are connected to one of the faces this function + // operates on) are added to the connsToPaint list, as are their endpoints. in this way we know to repaint + // them wthout having to calculate anything else about them. + var _updateAnchorList = function(lists, theta, order, conn, aBoolean, otherElId, idx, reverse, edgeId, elId, connsToPaint, endpointsToPaint) { + // first try to find the exact match, but keep track of the first index of a matching element id along the way.s + var exactIdx = -1, + firstMatchingElIdx = -1, + endpoint = conn.endpoints[idx], + endpointId = endpoint.id, + oIdx = [1,0][idx], + values = [ [ theta, order ], conn, aBoolean, otherElId, endpointId ], + listToAddTo = lists[edgeId], + listToRemoveFrom = endpoint._continuousAnchorEdge ? lists[endpoint._continuousAnchorEdge] : null; + + if (listToRemoveFrom) { + var rIdx = _findWithFunction(listToRemoveFrom, function(e) { return e[4] == endpointId }); + if (rIdx != -1) { + listToRemoveFrom.splice(rIdx, 1); + // get all connections from this list + for (var i = 0; i < listToRemoveFrom.length; i++) { + connsToPaint.push(listToRemoveFrom[i][1]); + endpointsToPaint.push(listToRemoveFrom[i][1].endpoints[idx]); + } + } + } + + for (var i = 0; i < listToAddTo.length; i++) { + if (idx == 1 && listToAddTo[i][3] === otherElId && firstMatchingElIdx == -1) + firstMatchingElIdx = i; + connsToPaint.push(listToAddTo[i][1]); + endpointsToPaint.push(listToAddTo[i][1].endpoints[idx]); + } + if (exactIdx != -1) { + listToAddTo[exactIdx] = values; + } + else { + var insertIdx = reverse ? firstMatchingElIdx != -1 ? firstMatchingElIdx : 0 : listToAddTo.length; // of course we will get this from having looked through the array shortly. + listToAddTo.splice(insertIdx, 0, values); + } + + // store this for next time. + endpoint._continuousAnchorEdge = edgeId; + }; + this.redraw = function(elementId, ui, timestamp) { + // get all the endpoints for this element + var ep = _amEndpoints[elementId] || [], + endpointConnections = endpointConnectionsByElementId[elementId] || [], + continuousAnchorConnections = continuousAnchorConnectionsByElementId[elementId] || [], + connectionsToPaint = [], + endpointsToPaint = []; + + timestamp = timestamp || _timestamp(); + + _updateOffset( { elId : elementId, offset : ui, recalc : false, timestamp : timestamp }); + // valid for one paint cycle. + var myOffset = offsets[elementId], + myWH = sizes[elementId], + orientationCache = {}; + + // actually, first we should compute the orientation of this element to all other elements to which + // this element is connected with a continuous anchor (whether both ends of the connection have + // a continuous anchor or just one) + for (var i = 0; i < continuousAnchorConnections.length; i++) { + var conn = continuousAnchorConnections[i], + sourceId = conn.sourceId, + targetId = conn.targetId, + oKey = sourceId + "_" + targetId, + oKey2 = targetId + "_" + sourceId, + o = orientationCache[oKey], + td = _getCachedData(targetId), + sd = _getCachedData(sourceId), + oIdx = conn.sourceId == elementId ? 1 : 0; + + if (!anchorLists[sourceId]) anchorLists[sourceId] = { top:[], right:[], bottom:[], left:[] }; + if (!anchorLists[targetId]) anchorLists[targetId] = { top:[], right:[], bottom:[], left:[] }; + + if (targetId == sourceId) { + // here we may want to improve this by somehow determining the face we'd like + // to put the connector on. ideally, when drawing, the face should be calculated + // by determining which face is closest to the point at which the mouse button + // was released. for now, we're putting it on the top face. + _updateAnchorList(anchorLists[sourceId], -Math.PI / 2, 0, conn, false, targetId, 0, false, "top", sourceId, connectionsToPaint, endpointsToPaint) + } + else { + if (!o) { + o = calculateOrientation(sourceId, targetId, sd.o, td.o); + orientationCache[oKey] = o; + // this would be a performance enhancement, but the computed angles need to be clamped to + //the (-PI/2 -> PI/2) range in order for the sorting to work properly. + /* orientationCache[oKey2] = { + orientation:o.orientation, + a:[o.a[1], o.a[0]], + theta:o.theta + Math.PI, + theta2:o.theta2 + Math.PI + };*/ + } + _updateAnchorList(anchorLists[sourceId], o.theta, 0, conn, false, targetId, 0, false, o.a[0], sourceId, connectionsToPaint, endpointsToPaint); + _updateAnchorList(anchorLists[targetId], o.theta2, -1, conn, true, sourceId, 1, true, o.a[1], targetId, connectionsToPaint, endpointsToPaint); + } + + connectionsToPaint.push(conn); + endpointsToPaint.push(conn.endpoints[oIdx]); + } + + // now place all the continuous anchors; + for (var anElement in anchorLists) { + placeAnchors(anElement, anchorLists[anElement]); + } + + // now that continuous anchors have been placed, paint all the endpoints for this element + for (var i = 0; i < ep.length; i++) { + ep[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + // ... and any other endpoints we came across as a result of the continuous anchors. + for (var i = 0; i < endpointsToPaint.length; i++) { + endpointsToPaint[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + + // paint all the standard and "dynamic connections", which are connections whose other anchor is + // static and therefore does need to be recomputed; we make sure that happens only one time. + for (var i = 0; i < endpointConnections.length; i++) { + var otherEndpoint = endpointConnections[i][1]; + if (otherEndpoint.anchor.constructor == DynamicAnchor) { + // _updateOffset( { elId : otherEndpoint.elementId, timestamp : timestamp }); + otherEndpoint.paint({ elementWithPrecedence:elementId }); + connectionsToPaint.push(endpointConnections[i][0]); + // all the connections for the other endpoint now need to be repainted + for (var k = 0; k < otherEndpoint.connections.length; k++) { + if (otherEndpoint.connections[k] !== endpointConnections[i][0]) + connectionsToPaint.push(otherEndpoint.connections[k]); + } + } else if (otherEndpoint.anchor.constructor == Anchor) { + connectionsToPaint.push(endpointConnections[i][0]); + } + } + // paint current floating connection for this element, if there is one. + var fc = floatingConnections[elementId]; + if (fc) + fc.paint({timestamp:timestamp, recalc:false, elId:elementId}); + + // paint all the connections + for (var i = 0; i < connectionsToPaint.length; i++) { + connectionsToPaint[i].paint({elId:elementId, timestamp:timestamp, recalc:false}); + } + }; + this.rehomeEndpoint = function(currentId, element) { + var eps = _amEndpoints[currentId] || [], //, + elementId = _currentInstance.getId(element); + for (var i = 0; i < eps.length; i++) { + self.add(eps[i], elementId); + } + eps.splice(0, eps.length); + }; + }; + _currentInstance.anchorManager = new AnchorManager(); + //_currentInstance.bind("jsPlumbConnection", _currentInstance.anchorManager.connectionListener); + //_currentInstance.bind("jsPlumbConnectionDetached", _currentInstance.anchorManager.connectionDetachedListener); + + _currentInstance.continuousAnchorFactory = { + get:function(params) { + var existing = continuousAnchors[params.elementId]; + if (!existing) { + existing = { + type:"Continuous", + compute : function(params) { + return continuousAnchorLocations[params.element.id] || [0,0]; + }, + getCurrentLocation : function(endpoint) { + return continuousAnchorLocations[endpoint.id] || [0,0]; + }, + getOrientation : function(endpoint) { + return continuousAnchorOrientations[endpoint.id] || [0,0]; + }, + isDynamic : true, + isContinuous : true + }; + continuousAnchors[params.elementId] = existing; + } + return existing; + } + }; + + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * container - optional id or selector instructing jsPlumb where to attach all the elements it creates for this connection. you should read the documentation for a full discussion of this. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * overlays - Optional array of Overlay definitions to appear on this Connection. + * drawEndpoints - if false, instructs jsPlumb to not draw the endpoints for this Connection. Be careful with this: it only really works when you tell jsPlumb to attach elements to the document body. Read the documentation for a full discussion of this. + */ + var Connection = function(params) { + var self = this, visible = true; + self.idPrefix = "_jsplumb_c_"; + jsPlumbUIComponent.apply(this, arguments); + // ************** get the source and target and register the connection. ******************* + + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none"; + }; + this.parent = params.parent; + /** + Property: source + The source element for this Connection. + */ + this.source = _getElementObject(params.source); + /** + Property:target + The target element for this Connection. + */ + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. but + // source is not overridden if the Endpoint has declared it is not the final target of a connection; + // instead we use the source that the Endpoint declares will be the final source element. + if (params.sourceEndpoint) { + this.source = params.sourceEndpoint.endpointWillMoveTo || params.sourceEndpoint.getElement(); + } + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + + // if a new connection is the result of moving some existing connection, params.previousConnection + // will have that Connection in it. listeners for the jsPlumbConnection event can look for that + // member and take action if they need to. + self.previousConnection = params.previousConnection; + + /* + * Property: sourceId + * Id of the source element in the connection. + */ + this.sourceId = _getAttribute(this.source, "id"); + /* + * Property: targetId + * Id of the target element in the connection. + */ + this.targetId = _getAttribute(this.target, "id"); + + /** + * implementation of abstract method in EventGenerator + * @return list of attached elements. in our case, a list of Endpoints. + */ + this.getAttachedElements = function() { + return self.endpoints; + }; + + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams, elementId) { + if (anchorParams) + return jsPlumb.makeAnchor(anchorParams, elementId, _currentInstance); + }, + prepareEndpoint = function(existing, index, params, element, elementId, connectorPaintStyle, connectorHoverPaintStyle) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] + || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint; + + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified. + if (es.fillStyle == null && connectorPaintStyle != null) + es.fillStyle = connectorPaintStyle.strokeStyle; + + // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does: + //* + if (es.outlineColor == null && connectorPaintStyle != null) + es.outlineColor = connectorPaintStyle.outlineColor; + if (es.outlineWidth == null && connectorPaintStyle != null) + es.outlineWidth = connectorPaintStyle.outlineWidth; + //*/ + + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure? + if (connectorHoverPaintStyle != null) { + if (ehs == null) ehs = {}; + if (ehs.fillStyle == null) { + ehs.fillStyle = connectorHoverPaintStyle.strokeStyle; + } + } + var a = params.anchors ? params.anchors[index] : + params.anchor ? params.anchor : + _makeAnchor(_currentInstance.Defaults.Anchors[index], elementId) || + _makeAnchor(jsPlumb.Defaults.Anchors[index], elementId) || + _makeAnchor(_currentInstance.Defaults.Anchor, elementId) || + _makeAnchor(jsPlumb.Defaults.Anchor, elementId), + u = params.uuids ? params.uuids[index] : null, + e = _newEndpoint({ + paintStyle : es, + hoverPaintStyle:ehs, + endpoint : ep, + connections : [ self ], + uuid : u, + anchor : a, + source : element, + container:params.container, + reattach:params.reattach, + detachable:params.detachable + }); + self.endpoints[index] = e; + + + if (params.drawEndpoints === false) e.setVisible(false, true, true); + + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, + 0, + params, + self.source, + self.sourceId, + params.paintStyle, + params.hoverPaintStyle); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + + // if there were no endpoints supplied and the source element is the target element, we will reuse the source + // endpoint that was just created. + var existingTargetEndpoint = ((self.sourceId == self.targetId) && params.targetEndpoint == null) ? eS : params.targetEndpoint, + eT = prepareEndpoint(existingTargetEndpoint, + 1, + params, + self.target, + self.targetId, + params.paintStyle, + params.hoverPaintStyle); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + // if delete endpoints on detach, keep a record of just exactly which endpoints they are. + if (params.deleteEndpointsOnDetach) + self.endpointsToDeleteOnDetach = [eS, eT]; + + var _detachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.detachable === false) _detachable = false; + if(self.endpoints[0].connectionsDetachable === false) _detachable = false; + if(self.endpoints[1].connectionsDetachable === false) _detachable = false; + + /* + Function: isDetachable + Returns whether or not this connection can be detached from its target/source endpoint. by default this + is false; use it in conjunction with the 'reattach' parameter. + */ + this.isDetachable = function() { + return _detachable === true; + }; + + /* + Function: setDetachable + Sets whether or not this connection is detachable. + */ + this.setDetachable = function(detachable) { + _detachable = detachable === true; + }; + + // merge all the parameters objects into the connection. parameters set + // on the connection take precedence; then target endpoint params, then + // finally source endpoint params. + var _p = jsPlumb.CurrentLibrary.extend({}, this.endpoints[0].getParameters()); + jsPlumb.CurrentLibrary.extend(_p, this.endpoints[1].getParameters()); + jsPlumb.CurrentLibrary.extend(_p, self.getParameters()); + self.setParameters(_p); + + var _bindConnectorEvents = function() { + // add mouse events + self.connector.bind("click", function(con, e) { + self.fire("click", self, e); + }); + self.connector.bind("dblclick", function(con, e) { + self.fire("dblclick", self, e); }); + self.connector.bind("mouseenter", function(con, e) { + if (!self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(true, false); + } + self.fire("mouseenter", self, e); + } + }); + self.connector.bind("mouseexit", function(con, e) { + if (self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(false, false); + } + self.fire("mouseexit", self, e); + } + }); + + }; + + /* + * Function: setConnector + * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method - the same + * thing that you would set as the 'connector' property on a jsPlumb.connect call. + * + * Parameters: + * connector - Connector definition + */ + this.setConnector = function(connector, doNotRepaint) { + if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent); + var connectorArgs = { + _jsPlumb:self._jsPlumb, + parent:params.parent, + cssClass:params.cssClass, + container:params.container, + tooltip:self.tooltip + }; + if (connector.constructor == String) + this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand. + else if (connector.constructor == Array) + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs)); + self.canvas = self.connector.canvas; + _bindConnectorEvents(); + if (!doNotRepaint) self.repaint(); + }; + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc) + */ + + self.setConnector(this.endpoints[0].connector || + this.endpoints[1].connector || + params.connector || + _currentInstance.Defaults.Connector || + jsPlumb.Defaults.Connector, true); + + + // override setHover to pass it down to the underlying connector + var _sh = self.setHover; + self.setHover = function() { + self.connector.setHover.apply(self.connector, arguments); + _sh.apply(self, arguments); + }; + + this.setPaintStyle(this.endpoints[0].connectorStyle || + this.endpoints[1].connectorStyle || + params.paintStyle || + _currentInstance.Defaults.PaintStyle || + jsPlumb.Defaults.PaintStyle, true); + + this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || + this.endpoints[1].connectorHoverStyle || + params.hoverPaintStyle || + _currentInstance.Defaults.HoverPaintStyle || + jsPlumb.Defaults.HoverPaintStyle, true); + + this.paintStyleInUse = this.paintStyle; + + /* + * Property: overlays + * List of Overlays for this Connection. + */ + this.overlays = []; + var processOverlay = function(o) { + var _newOverlay = null; + if (o.constructor == Array) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0], + // make a copy of the object so as not to mess up anyone else's reference... + p = jsPlumb.CurrentLibrary.extend({connection:self, _jsPlumb:_currentInstance}, o[1]); + if (o.length == 3) jsPlumb.CurrentLibrary.extend(p, o[2]); + _newOverlay = new jsPlumb.Overlays[renderMode][type](p); + if (p.events) { + for (var evt in p.events) { + _newOverlay.bind(evt, p.events[evt]); + } + } + } else if (o.constructor == String) { + _newOverlay = new jsPlumb.Overlays[renderMode][o]({connection:self, _jsPlumb:_currentInstance}); + } else { + _newOverlay = o; + } + + self.overlays.push(_newOverlay); + }; + var _overlays = params.overlays || _currentInstance.Defaults.Overlays; + if (_overlays) { + for (var i = 0; i < _overlays.length; i++) { + processOverlay(_overlays[i]); + } + } + + this.moveParent = function(newParent) { + var jpcl = jsPlumb.CurrentLibrary, curParent = jpcl.getParent(self.connector.canvas); + jpcl.removeElement(self.connector.canvas, curParent); + jpcl.appendElement(self.connector.canvas, newParent); + if (self.connector.bgCanvas) { + jpcl.removeElement(self.connector.bgCanvas, curParent); + jpcl.appendElement(self.connector.bgCanvas, newParent); + } + // this only applies for DOMOverlays + for (var i = 0; i < self.overlays.length; i++) { + if (self.overlays[i].isAppendedAtTopLevel) { + jpcl.removeElement(self.overlays[i].canvas, curParent); + jpcl.appendElement(self.overlays[i].canvas, newParent); + if (self.overlays[i].reattachListeners) self.overlays[i].reattachListeners(); + } + } + if (self.connector.reattachListeners) // this is for SVG/VML; change an element's parent and you have to reinit its listeners. + self.connector.reattachListeners(); // the Canvas implementation doesn't have to care about this + }; + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Connection. + * + * Parameters: + * event - the event to bind. Available events on a Connection are: + * - *click* : notification that a Connection was clicked. + * - *dblclick* : notification that a Connection was double clicked. + * - *mouseenter* : notification that the mouse is over a Connection. + * - *mouseexit* : notification that the mouse exited a Connection. + * + * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Connection's paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Connection will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Connection. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + // overlay finder helper method + var _getOverlayIndex = function(id) { + var idx = -1; + for (var i = 0; i < self.overlays.length; i++) { + if (id === self.overlays[i].id) { + idx = i; + break; + } + } + return idx; + }; + + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + this.addOverlay = function(overlay) { + processOverlay(overlay); + self.repaint(); + }; + + /* + * Function: getOverlay + * Gets an overlay, by ID. Note: by ID. You would pass an 'id' parameter + * in to the Overlay's constructor arguments, and then use that to retrieve + * it via this method. + */ + this.getOverlay = function(id) { + var idx = _getOverlayIndex(id); + return idx >= 0 ? self.overlays[idx] : null; + }; + + /* + * Function: hideOverlay + * Hides the overlay specified by the given id. + */ + this.hideOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.hide(); + }; + + /* + * Function: showOverlay + * Shows the overlay specified by the given id. + */ + this.showOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.show(); + }; + + /** + * Function: removeAllOverlays + * Removes all overlays from the Connection, and then repaints. + */ + this.removeAllOverlays = function() { + self.overlays.splice(0, self.overlays.length); + self.repaint(); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + this.removeOverlay = function(overlayId) { + var idx = _getOverlayIndex(overlayId); + if (idx != -1) { + var o = self.overlays[idx]; + o.cleanup(); + self.overlays.splice(idx, 1); + } + }; + + /** + * Function:removeOverlays + * Removes a set of overlays by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + this.removeOverlays = function() { + for (var i = 0; i < arguments.length; i++) + self.removeOverlay(arguments[i]); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + this.labelStyle = params.labelStyle || _currentInstance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + if (this.label) { + this.overlays.push(new jsPlumb.Overlays[renderMode].Label( { + cssClass:params.cssClass, + labelStyle : this.labelStyle, + label : this.label, + connection:self, + _jsPlumb:_currentInstance + })); + } + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String or a Function that returns a String. + */ + this.setLabel = function(l) { + self.label = l; + _currentInstance.repaint(self.source); + }; + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId], + otherOffset = offsets[this.targetId], + otherWH = sizes[this.targetId], + initialTimestamp = _timestamp(), + anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + elementId:this.endpoints[0].elementId, + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1], + timestamp:initialTimestamp + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc, timestamp:initialTimestamp }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + elementId:this.endpoints[1].elementId, + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0], + timestamp:initialTimestamp + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc, timestamp:initialTimestamp }); + + /* + * Paints the Connection. Not exposed for public usage. + * + * Parameters: + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp, + // if the moving object is not the source we must transpose the two references. + swap = false, + tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId, + tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + + _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }); + _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var sE = this.endpoints[sIdx], tE = this.endpoints[tIdx], + sAnchorP = sE.anchor.getCurrentLocation(sE), + tAnchorP = tE.anchor.getCurrentLocation(tE); + + /* paint overlays*/ + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible()) maxSize = Math.max(maxSize, o.computeMaxSize(self.connector)); + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, + this.endpoints[sIdx], this.endpoints[tIdx], + this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, + self.paintStyleInUse.lineWidth, maxSize); + + self.connector.paint(dim, self.paintStyleInUse); + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim); + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function(params) { + params = params || {}; + var recalc = !(params.recalc === false); + this.paint({ elId : this.sourceId, recalc : recalc, timestamp:params.timestamp }); + }; + // resizing (using the jquery.ba-resize plugin). todo: decide + // whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + + // just to make sure the UI gets initialised fully on all browsers. + self.repaint(); + }; + +// ENDPOINT HELPER FUNCTIONS + var _makeConnectionDragHandler = function(placeholder) { + var stopped = false; + return function() { + if (stopped) return true; + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments), + el = placeholder.element; + if (el) { + jsPlumb.CurrentLibrary.setOffset(el, _ui); + _draw(_getElementObject(el), _ui); + } + + this.stopDrag = function() { + stopped = true; + }; + }; + }; + + var _makeFloatingEndpoint = function(paintStyle, referenceAnchor, endpoint, referenceCanvas, sourceElement) { + var floatingAnchor = new FloatingAnchor( { reference : referenceAnchor, referenceCanvas : referenceCanvas }); + + //setting the scope here should not be the way to fix that mootools issue. it should be fixed by not + // adding the floating endpoint as a droppable. that makes more sense anyway! + + return _newEndpoint({ paintStyle : paintStyle, endpoint : endpoint, anchor : floatingAnchor, source : sourceElement, scope:"__floating" }); + }; + + /** + * creates a placeholder div for dragging purposes, adds it to the DOM, and pre-computes its offset. then returns + * both the element id and a selector for the element. + */ + var _makeDraggablePlaceholder = function(placeholder, parent) { + var n = document.createElement("div"); + n.style.position = "absolute"; + var placeholderDragElement = _getElementObject(n); + _appendElement(n, parent); + var id = _getId(placeholderDragElement); + _updateOffset( { elId : id }); + // create and assign an id, and initialize the offset. + placeholder.id = id; + placeholder.element = placeholderDragElement; + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1 + * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't + * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you + * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or + * 'target' Endpoints for Connections. + * + * + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * container - optional id or selector instructing jsPlumb where to attach the element it creates for this endpoint. you should read the documentation for a full discussion of this. + * connections - optional list of Connections to configure the Endpoint with. + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + var self = this; + self.idPrefix = "_jsplumb_e_"; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + params = params || {}; + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Endpoint. + * + * Parameters: + * event - the event to bind. Available events on an Endpoint are: + * - *click* : notification that a Endpoint was clicked. + * - *dblclick* : notification that a Endpoint was double clicked. + * - *mouseenter* : notification that the mouse is over a Endpoint. + * - *mouseexit* : notification that the mouse exited a Endpoint. + * + * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Endpoint's paint style and then repaints the Endpoint. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Endpoint will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Endpoint. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + var visible = true; + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + var _element = params.source, _uuid = params.uuid, floatingEndpoint = null, inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + this.element = _element; + + if (params.dynamicAnchors) + self.anchor = new DynamicAnchor(jsPlumb.makeAnchors(params.dynamicAnchors)); + else + self.anchor = params.anchor ? jsPlumb.makeAnchor(params.anchor, _elementId, _currentInstance) : params.anchors ? jsPlumb.makeAnchor(params.anchors, _elementId, _currentInstance) : jsPlumb.makeAnchor("TopCenter", _elementId, _currentInstance); + + // ANCHOR MANAGER + if (!params._transient) // in place copies, for example, are transient. they will never need to be retrieved during a paint cycle, because they dont move, and then they are deleted. + _currentInstance.anchorManager.add(self, _elementId); + + var _endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot", + endpointArgs = { + _jsPlumb:self._jsPlumb, + parent:params.parent, + container:params.container, + tooltip:params.tooltip, + connectorTooltip:params.connectorTooltip, + endpoint:self + }; + if (_endpoint.constructor == String) + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint](endpointArgs); + else if (_endpoint.constructor == Array) { + endpointArgs = jsPlumb.extend(_endpoint[1], endpointArgs); + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint[0]](endpointArgs); + } + else + _endpoint = _endpoint.clone(); + + // assign a clone function using our derived endpointArgs. this is used when a drag starts: the endpoint that was dragged is cloned, + // and the clone is left in its place while the original one goes off on a magical journey. + this.clone = function() { + var o = new Object(); + _endpoint.constructor.apply(o, [endpointArgs]); + return o; + }; + + self.endpoint = _endpoint; + self.type = self.endpoint.type; + // override setHover to pass it down to the underlying endpoint + var _sh = self.setHover; + self.setHover = function() { + self.endpoint.setHover.apply(self.endpoint, arguments); + _sh.apply(self, arguments); + }; + // endpoint delegates to first connection for hover, if there is one. + var internalHover = function(state) { + if (self.connections.length > 0) + self.connections[0].setHover(state, false); + else + self.setHover(state); + }; + + // TODO this event listener registration code is identical to what Connection does: it should be refactored. + this.endpoint.bind("click", function(e) { self.fire("click", self, e); }); + this.endpoint.bind("dblclick", function(e) { self.fire("dblclick", self, e); }); + // this method is different; endpoint delegates hover to the first connection if there is one. + // that allows the connection to notify all its endpoint and avoids a circular loop + this.endpoint.bind("mouseenter", function(con, e) { + if (!self.isHover()) { + internalHover(true); + self.fire("mouseenter", self, e); + } + }); + // this method is different; endpoint delegates hover to the first connection if there is one. + // that allows the connection to notify all its endpoint and avoids a circular loop + this.endpoint.bind("mouseexit", function(con, e) { + if (self.isHover()) { + internalHover(false); + self.fire("mouseexit", self, e); + } + }); + + this.setPaintStyle(params.paintStyle || + params.style || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyle, true); + this.setHoverPaintStyle(params.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyle, true); + this.paintStyleInUse = this.paintStyle; + this.connectorStyle = params.connectorStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.connectorTooltip = params.connectorTooltip; + this.parent = params.parent; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + + var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + /* + * Property: canvas + * The Endpoint's Canvas. + */ + this.canvas = this.endpoint.canvas; + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + this.connections = params.connections || []; + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + self.isReattach = params.reattach || false; + self.connectionsDetachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.connectionsDetachable === false || params.detachable === false) + self.connectionsDetachable = false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget, forceDetach, fireEvent) { + var idx = _findIndex(self.connections, connection), actuallyDetached = false; + fireEvent = (fireEvent !== false); + if (idx >= 0) { + // 1. does the connection have a before detach (note this also checks jsPlumb's bound + // detach handlers; but then Endpoint's check will, too, hmm.) + if (forceDetach || connection._forceDetach || connection.isDetachable() || connection.isDetachAllowed(connection)) { + // get the target endpoint + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + // it would be nice to check with both endpoints that it is ok to detach. but + // for this we'll have to get a bit fancier: right now if you use the same beforeDetach + // interceptor for two endpoints (which is kind of common, because it's part of the + // endpoint definition), then it gets fired twice. so in fact we need to loop through + // each beforeDetach and see if it returns false, at which point we exit. but if it + // returns true, we have to check the next one. however we need to track which ones + // have already been run, and not run them again. + if (forceDetach || connection._forceDetach || (self.isDetachAllowed(connection) /*&& t.isDetachAllowed(connection)*/)) { + + self.connections.splice(idx, 1); + + // this avoids a circular loop + if (!ignoreTarget) { + + t.detach(connection, true, forceDetach); + // check connection to see if we want to delete the endpoints associated with it. + // we only detach those that have just this connection; this scenario is most + // likely if we got to this bit of code because it is set by the methods that + // create their own endpoints, like .connect or .makeTarget. the user is + // not likely to have interacted with those endpoints. + if (connection.endpointsToDeleteOnDetach){ + for (var i = 0; i < connection.endpointsToDeleteOnDetach.length; i++) { + var cde = connection.endpointsToDeleteOnDetach[i]; + if (cde && cde.connections.length == 0) + jsPlumb.deleteEndpoint(cde); + } + } + } + _removeElements(connection.connector.getDisplayElements(), connection.parent); + _removeFromList(connectionsByScope, connection.scope, connection); + actuallyDetached = true; + var doFireEvent = (!ignoreTarget && fireEvent) + fireDetachEvent(connection, doFireEvent); + } + } + } + return actuallyDetached; + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + * + * Parameters: + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachAll = function(fireEvent) { + while (self.connections.length > 0) { + self.detach(self.connections[0], false, true, fireEvent); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachFrom = function(targetEndpoint, fireEvent) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + if (self.detach(c[i], false, true, fireEvent)) + c[i].setHover(false, false); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: setElement + * Sets the DOM element this Endpoint is attached to. + */ + this.setElement = function(el) { + // TODO possibly have this object take charge of moving the UI components into the appropriate + // parent. this is used only by makeSource right now, and that function takes care of + // moving the UI bits and pieces. however it would s + var parentId = _getId(el); + // remove the endpoint from the list for the current endpoint's element + _removeFromList(endpointsByElement, _elementId, self); + _element = _getElementObject(el); + _elementId = _getId(_element); + self.elementId = _elementId; + + // need to get the new parent now + var newParentElement = _getParentFromParams({source:parentId}), + curParent = jpcl.getParent(self.canvas); + jpcl.removeElement(self.canvas, curParent); + jpcl.appendElement(self.canvas, newParentElement); + + // now move connection(s)...i would expect there to be only one but we will iterate. + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].moveParent(newParentElement); + self.connections[i].sourceId = _elementId; + self.connections[i].source = _element; + } + + _addToList(endpointsByElement, parentId, self); + _currentInstance.repaint(parentId); + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + return _newEndpoint( { + anchor : self.anchor, + source : _element, + paintStyle : this.paintStyle, + endpoint : _endpoint, + _transient:true, + scope:self.scope + }); + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + var candidate = self.connections[0]; + if (self.isTarget && candidate) return candidate; + else { + return (self.connections.length < _maxConnections) || _maxConnections == -1 ? null : candidate; + } + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + /* + * Function: setStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to + * setStyle and deprecate it if so. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + * + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + // a helper function that tries to find a connection to the given element, and returns it if so. if elementWithPrecedence is null, + // or no connection to it is found, we return the first connection in our list. + var findConnectionToUseForDynamicAnchor = function(elementWithPrecedence) { + var idx = 0; + if (elementWithPrecedence != null) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].sourceId == elementWithPrecedence || self.connections[i].targetId == elementWithPrecedence) { + idx = i; + break; + } + } + } + + return self.connections[idx]; + }; + + /* + * Function: paint + * Paints the Endpoint, recalculating offset and anchor positions if necessary. This does NOT paint + * any of the Endpoint's connections. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + params = params || {}; + var timestamp = params.timestamp, + recalc = !(params.recalc === false); + if (!timestamp || self.timestamp !== timestamp) { + _updateOffset({ elId:_elementId, timestamp:timestamp, recalc:recalc }); + var ap = params.anchorPoint, connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + var xy = params.offset || offsets[_elementId], + wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (recalc && self.anchor.isDynamic && self.connections.length > 0) { + var c = findConnectionToUseForDynamicAnchor(params.elementWithPrecedence), + oIdx = c.endpoints[0] == self ? 1 : 0, + oId = oIdx == 0 ? c.sourceId : c.targetId, + oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + ap = self.anchor.compute(anchorParams); + } + + var d = _endpoint.compute(ap, self.anchor.getOrientation(_endpoint), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + _endpoint.paint(d, self.paintStyleInUse, self.anchor); + self.timestamp = timestamp; + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var placeholderInfo = { + id:null, + element:null + }, + jpc = null, + existingJpc = false, + existingJpcParams = null, + _dragHandler = _makeConnectionDragHandler(placeholderInfo); + + var start = function() { + // drag might have started on an endpoint that is not actually a source, but which has + // one or more connections. + jpc = self.connectorSelector(); + var _continue = true; + // if no connection and we're not a source, return. + if (jpc == null && !params.isSource) _continue = false; + // otherwise if we're full and not allowed to drag, also return false. + if (params.isSource && self.isFull() && !dragAllowedWhenFull) _continue = false; + + // if the connection was setup as not detachable (or one of its endpoints + // was setup as connectionsDetachable = false, or Defaults.ConnectionsDetachable + // is set to false... + if (jpc != null && !jpc.isDetachable()) _continue = false; + + if (_continue === false) { + // this is for mootools and yui. returning false from this causes jquery to stop drag. + // the events are wrapped in both mootools and yui anyway, but i don't think returning + // false from the start callback would stop a drag. + if (jsPlumb.CurrentLibrary.stopDrag) jsPlumb.CurrentLibrary.stopDrag(); + _dragHandler.stopDrag(); + return false; + } + + // if we're not full but there was a connection, make it null. we'll create a new one. + if (jpc && !self.isFull() && params.isSource) jpc = null; + + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + _makeDraggablePlaceholder(placeholderInfo, self.parent); + + // set the offset of this div to be where 'inPlaceCopy' is, to start with. + // TODO merge this code with the code in both Anchor and FloatingAnchor, because it + // does the same stuff. + var ipcoel = _getElementObject(inPlaceCopy.canvas), + ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel), + po = adjustForParentOffsetAndScroll([ipco.left, ipco.top], inPlaceCopy.canvas); + jsPlumb.CurrentLibrary.setOffset(placeholderInfo.element, {left:po[0], top:po[1]}); + + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", placeholderInfo.id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + floatingEndpoint = _makeFloatingEndpoint(self.paintStyle, self.anchor, _endpoint, self.canvas, placeholderInfo.element); + + if (jpc == null) { + self.anchor.locked = true; + self.setHover(false, false); + // TODO the hover call above does not reset any target endpoint's hover + // states. + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : self.endpointWillMoveTo || _getElementObject(_element), // for makeSource with parent option. ensure source element is represented correctly. + target : placeholderInfo.element, + anchors : [ self.anchor, floatingEndpoint.anchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays + }); + + } else { + existingJpc = true; + jpc.connector.setHover(false, false); + // jpc.endpoints[0].setHover(false, false); + //jpc.endpoints[1].setHover(false, false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas), false, true); + //var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; // are we the source or the target? + // new anchor idx + var anchorIdx = jpc.endpoints[0].id == self.id ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas), + dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // now we want to get this endpoint's DROP scope, and set it for now: we can only be dropped on drop zones + // that have our drop scope (issue 57). + var dropScope = jsPlumb.CurrentLibrary.getDropScope(c); + jsPlumb.CurrentLibrary.setDragScope(c, dropScope); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = placeholderInfo.element; + jpc.sourceId = placeholderInfo.id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = placeholderInfo.element; + jpc.targetId = placeholderInfo.id; + } + + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.suspendedEndpoint.setHover(false); + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + // register it and register connection on it. + floatingConnections[placeholderInfo.id] = jpc; + floatingEndpoint.addConnection(jpc); + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, placeholderInfo.id, floatingEndpoint); + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var jpcl = jsPlumb.CurrentLibrary, + dragOptions = params.dragOptions || {}, + defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions), + startEvent = jpcl.dragEvents["start"], + stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"]; + + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + // extracted drag handler function so can be used by makeSource + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], _dragHandler); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _currentInstance.currentlyDragging = false; + _removeFromList(endpointsByElement, placeholderInfo.id, floatingEndpoint); + _removeElements( [ placeholderInfo.element[0], floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + _currentInstance.anchorManager.clearFor(placeholderInfo.id); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (self.isReattach || jpc._forceDetach || !jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc)) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } + jpc._forceDetach = null; + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElements(jpc.connector.getDisplayElements(), self.parent); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint({recalc:false}); + jpc.setHover(false, false); + jpc = null; + inPlaceCopy = null; + delete endpointsByElement[floatingEndpoint.elementId]; + floatingEndpoint.anchor = null; + floatingEndpoint = null; + _currentInstance.currentlyDragging = false; + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions, true); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas, forceInit, isTransient) { + if ((params.isTarget || forceInit) && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop'], + overEvent = jsPlumb.CurrentLibrary.dragEvents['over'], + outEvent = jsPlumb.CurrentLibrary.dragEvents['out'], + drop = function() { + var draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + elId = _getAttribute(draggable, "elId"), + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id], + idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + + // restore the original scope if necessary (issue 57) + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + + var _doContinue = true; + // the second check here is for the case that the user is dropping it back + // where it came from. + if (jpc.suspendedEndpoint && jpc.suspendedEndpoint.id != self.id) { + if (!jpc.isDetachAllowed(jpc) || !jpc.endpoints[idx].isDetachAllowed(jpc) || !jpc.suspendedEndpoint.isDetachAllowed(jpc) || !_currentInstance.checkCondition("beforeDetach", jpc)) + _doContinue = false; + } + + // these have to be set before testing for beforeDrop. + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + + // now check beforeDrop. this will be available only on Endpoints that are setup to + // have a beforeDrop condition (although, secretly, under the hood all Endpoints and + // the Connection have them, because they are on jsPlumbUIComponent. shhh!), because + // it only makes sense to have it on a target endpoint. + _doContinue = _doContinue && self.isDropAllowed(jpc.sourceId, jpc.targetId, jpc.scope); + + if (_doContinue) { + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + if (!jpc.suspendedEndpoint) { + //_addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + fireDetachEvent({ + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }, true); + } + + // finalise will inform the anchor manager and also add to + // connectionsByScope if necessary. + _finaliseConnection(jpc); + } + else { + // otherwise just put it back on the endpoint it was on before the drag. + if (jpc.suspendedEndpoint) { + // self.detachFrom(jpc); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.setHover(false); + jpc._forceDetach = true; + if (idx == 0) { + jpc.source = jpc.suspendedEndpoint.element; + jpc.sourceId = jpc.suspendedEndpoint.elementId; + } else { + jpc.target = jpc.suspendedEndpoint.element; + jpc.targetId = jpc.suspendedEndpoint.elementId;; + } + jpc.suspendedEndpoint.addConnection(jpc); + + jpc.endpoints[0].repaint(); + jpc.repaint(); + jsPlumb.repaint(jpc.source.elementId); + jpc._forceDetach = false; + } + } + + jpc.floatingAnchorIndex = null; + } + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + } + }); + dropOptions[outEvent] = _wrap(dropOptions[outEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + } + }); + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions, true, isTransient); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas), true, !(params._transient || self.anchor.isFloating)); + + return self; + }; + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + j.init(); + return j; + }; + jsPlumb.util = { + convertStyle : function(s, ignoreAlpha) { + // TODO: jsPlumb should support a separate 'opacity' style member. + if ("transparent" === s) return s; + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + return o; + }, + gradient : function(p1, p2) { + p1 = p1.constructor == Array ? p1 : [p1.x, p1.y]; + p2 = p2.constructor == Array ? p2 : [p2.x, p2.y]; + return (p2[1] - p1[1]) / (p2[0] - p1[0]); + }, + normal : function(p1, p2) { + return -1 / jsPlumb.util.gradient(p1,p2); + }, + segment : function(p1, p2) { + p1 = p1.constructor == Array ? p1 : [p1.x, p1.y]; + p2 = p2.constructor == Array ? p2 : [p2.x, p2.y]; + if (p2[0] > p1[0]) { + return (p2[1] > p1[1]) ? 2 : 1; + } + else { + return (p2[1] > p1[1]) ? 3 : 4; + } + }, + segmentMultipliers : [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ], + inverseSegmentMultipliers : [null, [-1, -1], [-1, 1], [1, 1], [1, -1] ], + pointOnLine : function(fromPoint, toPoint, distance) { + var m = jsPlumb.util.gradient(fromPoint, toPoint), + s = jsPlumb.util.segment(fromPoint, toPoint), + segmentMultiplier = distance > 0 ? jsPlumb.util.segmentMultipliers[s] : jsPlumb.util.inverseSegmentMultipliers[s], + theta = Math.atan(m), + y = Math.abs(distance * Math.sin(theta)) * segmentMultiplier[1], + x = Math.abs(distance * Math.cos(theta)) * segmentMultiplier[0]; + return { x:fromPoint.x + x, y:fromPoint.y + y }; + }, + /** + * calculates a perpendicular to the line fromPoint->toPoint, that passes through toPoint and is 'length' long. + * @param fromPoint + * @param toPoint + * @param length + */ + perpendicularLineTo : function(fromPoint, toPoint, length) { + var m = jsPlumb.util.gradient(fromPoint, toPoint), + theta2 = Math.atan(-1 / m), + y = length / 2 * Math.sin(theta2), + x = length / 2 * Math.cos(theta2); + return [{x:toPoint.x + x, y:toPoint.y + y}, {x:toPoint.x - x, y:toPoint.y - y}]; + } + }; + + var _curryAnchor = function(x, y, ox, oy, type, fnInit) { + return function(params) { + params = params || {}; + var a = jsPlumb.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance); + a.type = type; + if (fnInit) fnInit(a, params); + return a; + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1, "TopCenter"); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1, "BottomCenter"); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0, "LeftMiddle"); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0, "RightMiddle"); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0, "Center"); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1, "TopRight"); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1, "BottomRight"); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1, "TopLeft"); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1, "BottomLeft"); + + jsPlumb.Defaults.DynamicAnchors = function(params) { + return jsPlumb.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"], params.elementId, params.jsPlumbInstance); + }; + jsPlumb.Anchors["AutoDefault"] = function(params) { + var a = jsPlumb.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors(params)); + a.type = "AutoDefault"; + return a; + }; + + jsPlumb.Anchors["Assign"] = _curryAnchor(0,0,0,0,"Assign", function(anchor, params) { + // find what to use as the "position finder". the user may have supplied a String which represents + // the id of a position finder in jsPlumb.AnchorPositionFinders, or the user may have supplied the + // position finder as a function. we find out what to use and then set it on the anchor. + var pf = params.position || "Fixed"; + anchor.positionFinder = pf.constructor == String ? jsPlumb.AnchorPositionFinders[pf] : pf; + // always set the constructor params; the position finder might need them later (the Grid one does, + // for example) + anchor.constructorParams = params; + }); + + // Continuous anchor is just curried through to the 'get' method of the continuous anchor + // factory. + jsPlumb.Anchors["Continuous"] = function(params) { + return params.jsPlumbInstance.continuousAnchorFactory.get(params); + }; + + // these are the default anchor positions finders, which are used by the makeTarget function. supply + // a position finder argument to that function allows you to specify where the resulting anchor will + // be located + jsPlumb.AnchorPositionFinders = { + "Fixed": function(dp, ep, es, a) { + return [ (dp.left - ep.left) / es[0], (dp.top - ep.top) / es[1] ]; + }, + "Grid":function(dp, ep, es, a) { + var dx = dp.left - ep.left, dy = dp.top - ep.top, + gx = es[0] / (a.constructorParams.grid[0]), gy = es[1] / (a.constructorParams.grid[1]), + mx = Math.floor(dx / gx), my = Math.floor(dy / gy); + return [ ((mx * gx) + (gx / 2)) / es[0], ((my * gy) + (gy / 2)) / es[1] ]; + } + }; +})(); +/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the default Connectors, Endpoint and Overlay definitions. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +(function() { + + /** + * + * Helper class to consume unused mouse events by components that are DOM elements and + * are used by all of the different rendering modes. + * + */ + jsPlumb.DOMElementComponent = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + // when render mode is canvas, these functions may be called by the canvas mouse handler. + // this component is safe to pipe this stuff to /dev/null. + this.mousemove = + this.dblclick = + this.click = + this.mousedown = + this.mouseup = function(e) { }; + }; + + /** + * Class: Connectors.Straight + * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters. + */ + jsPlumb.Connectors.Straight = function() { + this.type = "Straight"; + var self = this, + currentPoints = null, + _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty, _segment, _length; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + _dx = _tx - _sx, _dy = _ty - _sy; + //_m = _dy / _dx, _m2 = -1 / _m; + _m = jsPlumb.util.gradient({x:_sx, y:_sy}, {x:_tx, y:_ty}), _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + //_segment = jsPlumb.util.segment({x:_sx, y:_sy}, {x:_tx, y:_ty}); + _length = Math.sqrt((_dx * _dx) + (_dy * _dy)); + + return currentPoints; + }; + + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + if (location == 0) + return { x:_sx, y:_sy }; + else if (location == 1) + return { x:_tx, y:_ty }; + else + return jsPlumb.util.pointOnLine({x:_sx, y:_sy}, {x:_tx, y:_ty}, location * _length); + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { + return _m; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + * this hands off to jsPlumb.util to do the maths, supplying our gradient and position and whether or + * not the coords should be flipped + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + return jsPlumb.util.pointOnLine(p, {x:_tx,y:_ty}, distance); + }; + }; + + + /** + * Class:Connectors.Bezier + * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's + * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below. + */ + /** + * Function:Constructor + * + * Parameters: + * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line. + * Optional; defaults to 150. + * + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + params = params || {}; + this.majorAnchor = params.curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + this.type = "Bezier"; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(sourceEndpoint), + too = targetAnchor.getOrientation(targetEndpoint), + perpendicular = soo[0] != too[0] || soo[1] == too[1], + p = [], + ma = self.majorAnchor, mi = self.minorAnchor; + + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + +// this.isFlipX = function() { return + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h; + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) + { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, sourceEndpoint, targetEndpoint, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx), minx2 = Math.min(_CP[0], _CP2[0]), minx = Math.min(minx1,minx2), + maxx1 = Math.max(_sx,_tx), maxx2 = Math.max(_CP[0], _CP2[0]), maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty), miny2 = Math.min(_CP[1], _CP2[1]), miny = Math.min(miny1,miny2), + maxy1 = Math.max(_sy,_ty), maxy2 = Math.max(_CP[1], _CP2[1]), maxy = Math.max(maxy1,maxy2); + + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + }; + + + /** + * Class: Connectors.Flowchart + * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments. + */ + /** + * Function: Constructor + * + * Parameters: + * stub - minimum length for the stub at each end of the connector. defaults to 30 pixels. + */ + jsPlumb.Connectors.Flowchart = function(params) { + this.type = "Flowchart"; + params = params || {}; + var self = this, + minStubLength = params.stub || params.minStubLength /* bwds compat. */ || 30, + segments = [], + totalLength = 0, + segmentProportions = [], + segmentProportionalLengths = [], + points = [], + swapX, + swapY, + /** + * recalculates the points at which the segments begin and end, proportional to the total length travelled + * by all the segments that constitute the connector. we use this to help with pointOnPath calculations. + */ + updateSegmentProportions = function(startX, startY, endX, endY) { + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segments[i][5] / totalLength; + segmentProportions[i] = [curLoc, (curLoc += (segments[i][5] / totalLength)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0], + ly = segments.length == 0 ? sy : segments[segments.length - 1][1], + m = x == lx ? Infinity : 0, + l = Math.abs(x == lx ? y - ly : x - lx); + segments.push([x, y, lx, ly, m, l]); + totalLength += l; + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment + * that contains the point which is 'location' distance along the entire path, where + * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths + * are made up of a list of segments, each of which contributes some fraction to + * the total length. + */ + findSegmentForLocation = function(location) { + var idx = segmentProportions.length - 1, inSegmentProportion = 0; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + segments = []; + totalLength = 0; + segmentProportionalLengths = []; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + offx = (lw / 2) + (minStubLength * 2), + offy = (lw / 2) + (minStubLength * 2), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(sourceEndpoint), + to = targetAnchor.orientation || targetAnchor.getOrientation(targetEndpoint), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx, + h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy; + if (w < minWidth) { + offx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + offy += (minWidth - h) / 2; + h = minWidth; + } + var sx = swapX ? w-offx : offx, + sy = swapY ? h-offy : offy, + tx = swapX ? offx : w-offx , + ty = swapY ? offy : h-offy, + startStubX = sx + (so[0] * minStubLength), + startStubY = sy + (so[1] * minStubLength), + endStubX = tx + (to[0] * minStubLength), + endStubY = ty + (to[1] * minStubLength), + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2); + + x -= offx; y -= offy; + points = [x, y, w, h, sx, sy, tx, ty]; + var extraPoints = []; + + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + if (so[0] == 0) { + var startStubIsBeforeEndStub = startStubY < endStubY; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } + } + else { + var startStubIsBeforeEndStub = startStubX < endStubX; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } + } + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentProportions(sx, sy, tx, ty); + + return points; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location) { + return self.pointAlongPathFrom(location, 0); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location) { + return segments[findSegmentForLocation(location)["index"]][4]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance) { + var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segments[s.index][5], m = segments[s.index][4]; + var e = { + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + return e; + }; + }; + + // ********************************* END OF CONNECTOR TYPES ******************************************************************* + + // ********************************* ENDPOINT TYPES ******************************************************************* + + /** + * Class: Endpoints.Dot + * A round endpoint, with default radius 10 pixels. + */ + + /** + * Function: Constructor + * + * Parameters: + * + * radius - radius of the endpoint. defaults to 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + this.type = "Dot"; + var self = this; + params = params || {}; + this.radius = params.radius || 10; + this.defaultOffset = 0.5 * this.radius; + this.defaultInnerRadius = this.radius / 3; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var r = endpointStyle.radius || self.radius, + x = anchorPoint[0] - r, + y = anchorPoint[1] - r; + return [ x, y, r * 2, r * 2, r ]; + }; + }; + + /** + * Class: Endpoints.Rectangle + * A Rectangular Endpoint, with default size 20x20. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the endpoint. defaults to 20 pixels. + * height - height of the endpoint. defaults to 20 pixels. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + this.type = "Rectangle"; + var self = this; + params = params || {}; + this.width = params.width || 20; + this.height = params.height || 20; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height]; + }; + }; + + /** + * Class: Endpoints.Image + * Draws an image as the Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * src - location of the image to use. + */ + jsPlumb.Endpoints.Image = function(params) { + + this.type = "Image"; + jsPlumb.DOMElementComponent.apply(this, arguments); + + var self = this, + initialized = false, + widthToUse = params.width, + heightToUse = params.height, + _onload = null, + _endpoint = params.endpoint; + + this.img = new Image(); + self.ready = false; + + this.img.onload = function() { + self.ready = true; + widthToUse = widthToUse || self.img.width; + heightToUse = heightToUse || self.img.height; + if (_onload) { + _onload(this); + } + }; + + _endpoint.setImage = function(img, onload) { + var s = img.constructor == String ? img : img.src; + _onload = onload; + self.img.src = img; + }; + + _endpoint.setImage(params.src || params.url, params.onload); + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + self.anchorPoint = anchorPoint; + if (self.ready) return [anchorPoint[0] - widthToUse / 2, anchorPoint[1] - heightToUse / 2, + widthToUse, heightToUse]; + else return [0,0,0,0]; + }; + + self.canvas = document.createElement("img"), initialized = false; + self.canvas.style["margin"] = 0; + self.canvas.style["padding"] = 0; + self.canvas.style["outline"] = 0; + self.canvas.style["position"] = "absolute"; + var clazz = params.cssClass ? " " + params.cssClass : ""; + self.canvas.className = jsPlumb.endpointClass + clazz; + if (widthToUse) self.canvas.setAttribute("width", widthToUse); + if (heightToUse) self.canvas.setAttribute("height", heightToUse); + jsPlumb.appendElement(self.canvas, params.parent); + self.attachListeners(self.canvas, self); + + var actuallyPaint = function(d, style, anchor) { + if (!initialized) { + self.canvas.setAttribute("src", self.img.src); + initialized = true; + } + var x = self.anchorPoint[0] - (widthToUse / 2), + y = self.anchorPoint[1] - (heightToUse / 2); + jsPlumb.sizeCanvas(self.canvas, x, y, widthToUse, heightToUse); + }; + + this.paint = function(d, style, anchor) { + if (self.ready) { + actuallyPaint(d, style, anchor); + } + else { + window.setTimeout(function() { + self.paint(d, style, anchor); + }, 200); + } + }; + }; + + /** + * Class: Endpoints.Blank + * An Endpoint that paints nothing (visible) on the screen. Supports cssClass and hoverClass parameters like all Endpoints. + */ + jsPlumb.Endpoints.Blank = function(params) { + var self = this; + this.type = "Blank"; + jsPlumb.DOMElementComponent.apply(this, arguments); + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + return [anchorPoint[0], anchorPoint[1],10,0]; + }; + + self.canvas = document.createElement("div"); + self.canvas.style.display = "block"; + self.canvas.style.width = "1px"; + self.canvas.style.height = "1px"; + self.canvas.style.background = "transparent"; + self.canvas.style.position = "absolute"; + self.canvas.className = self._jsPlumb.endpointClass; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + }; + }; + + /** + * Class: Endpoints.Triangle + * A triangular Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the triangle's base. defaults to 55 pixels. + * height - height of the triangle from base to apex. defaults to 55 pixels. + */ + jsPlumb.Endpoints.Triangle = function(params) { + this.type = "Triangle"; + params = params || { }; + params.width = params.width || 55; + params.height = params.height || 55; + this.width = params.width; + this.height = params.height; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height ]; + }; + }; +// ********************************* END OF ENDPOINT TYPES ******************************************************************* + + +// ********************************* OVERLAY DEFINITIONS *********************************************************************** + + var AbstractOverlay = function(params) { + var visible = true, self = this; + this.isAppendedAtTopLevel = true; + this.connection = params.connection; + this.loc = params.location == null ? 0.5 : params.location; + this.setVisible = function(val) { + visible = val; + self.connection.repaint(); + }; + this.isVisible = function() { return visible; }; + this.hide = function() { self.setVisible(false); }; + this.show = function() { self.setVisible(true); }; + + this.incrementLocation = function(amount) { + self.loc += amount; + self.connection.repaint(); + }; + this.setLocation = function(l) { + self.loc = l; + self.connection.repaint(); + }; + this.getLocation = function() { + return self.loc; + }; + }; + + + /** + * Class: Overlays.Arrow + * + * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length + * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction + * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line + * across the tail. + */ + /** + * Function: Constructor + * + * Parameters: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + this.type = "Arrow"; + AbstractOverlay.apply(this, arguments); + this.isAppendedAtTopLevel = false; + params = params || {}; + var self = this; + + this.length = params.length || 20; + this.width = params.width || 20; + this.id = params.id; + var direction = (params.direction || 1) < 0 ? -1 : 1, + paintStyle = params.paintStyle || { lineWidth:1 }, + // how far along the arrow the lines folding back in come to. default is 62.3%. + foldback = params.foldback || 0.623; + + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.cleanup = function() { }; // nothing to clean up for Arrows + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var hxy, mid, txy, tail, cxy; + + if (self.loc == 1) { + hxy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, -1); + txy = jsPlumb.util.pointOnLine(hxy, mid, self.length); + } + else if (self.loc == 0) { + txy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, 1); + hxy = jsPlumb.util.pointOnLine(txy, mid, self.length); + } + else { + hxy = connector.pointAlongPathFrom(self.loc, direction * self.length / 2), + mid = connector.pointOnPath(self.loc), + txy = jsPlumb.util.pointOnLine(hxy, mid, self.length); + } + + tail = jsPlumb.util.perpendicularLineTo(hxy, txy, self.width); + cxy = jsPlumb.util.pointOnLine(hxy, txy, foldback * self.length); + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x), + maxx = Math.max(hxy.x, tail[0].x, tail[1].x), + miny = Math.min(hxy.y, tail[0].y, tail[1].y), + maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + var d = { hxy:hxy, tail:tail, cxy:cxy }, + strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle, + fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle, + lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth; + + self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions); + + return [ minx, maxx, miny, maxy]; + }; + }; + + /** + * Class: Overlays.PlainArrow + * + * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "PlainArrow"; + }; + + /** + * Class: Overlays.Diamond + * + * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40, + p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "Diamond"; + }; + + + + /** + * Class: Overlays.Label + * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb + * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter + * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it + * puts on the Label's 'style' attribute, so the end result is the same. + */ + /** + * Function: Constructor + * + * Parameters: + * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes + * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle. + * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * + */ + jsPlumb.Overlays.Label = function(params) { + this.type = "Label"; + jsPlumb.DOMElementComponent.apply(this, arguments); + AbstractOverlay.apply(this, arguments); + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.id = params.id; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var label = params.label || "", + self = this, + initialised = false, + div = document.createElement("div"), + labelText = null; + div.style["position"] = "absolute"; + + var clazz = params["_jsPlumb"].overlayClass + " " + + (self.labelStyle.cssClass ? self.labelStyle.cssClass : + params.cssClass ? params.cssClass : ""); + + div.className = clazz; + + jsPlumb.appendElement(div, params.connection.parent); + jsPlumb.getId(div); + self.attachListeners(div, self); + self.canvas = div; + + //override setVisible + var osv = self.setVisible; + self.setVisible = function(state) { + osv(state); // call superclass + div.style.display = state ? "block" : "none"; + }; + + this.getElement = function() { + return div; + }; + + this.cleanup = function() { + if (div != null) jsPlumb.CurrentLibrary.removeElement(div); + }; + + /* + * Function: setLabel + * sets the label's, um, label. you would think i'd call this function + * 'setText', but you can pass either a Function or a String to this, so + * it makes more sense as 'setLabel'. + */ + this.setLabel = function(l) { + label = l; + labelText = null; + self.connection.repaint(); + }; + + this.getLabel = function() { + return label; + }; + + this.paint = function(connector, d, connectorDimensions) { + if (!initialised) { + connector.appendDisplayElement(div); + self.attachListeners(div, connector); + initialised = true; + } + div.style.left = (connectorDimensions[0] + d.minx) + "px"; + div.style.top = (connectorDimensions[1] + d.miny) + "px"; + }; + + this.getTextDimensions = function(connector) { + if (typeof label == "function") { + var lt = label(self); + div.innerHTML = lt.replace(/\r\n/g, "
"); + } + else { + if (labelText == null) { + labelText = label; + div.innerHTML = labelText.replace(/\r\n/g, "
"); + } + } + var de = jsPlumb.CurrentLibrary.getElementObject(div), + s = jsPlumb.CurrentLibrary.getSize(de); + return {width:s[0], height:s[1]}; + }; + + this.computeMaxSize = function(connector) { + var td = self.getTextDimensions(connector); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + var td = self.getTextDimensions(connector); + if (td.width != null) { + var cxy = connector.pointOnPath(self.loc), + minx = cxy.x - (td.width / 2), + miny = cxy.y - (td.height / 2); + + self.paint(connector, { + minx:minx, + miny:miny, + td:td, + cxy:cxy + }, connectorDimensions); + + return [minx, minx+td.width, miny, miny+td.height]; + } + else return [0,0,0,0]; + }; + + this.reattachListeners = function() { + if (div) self.reattachListenersForElement(div, self); + }; + }; + + // this is really just a test overlay, so its undocumented and doesnt take any parameters. but i was loth to delete it. + jsPlumb.Overlays.GuideLines = function() { + var self = this; + self.length = 50; + self.lineWidth = 5; + this.type = "GuideLines"; + AbstractOverlay.apply(this, arguments); + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var head = connector.pointAlongPathFrom(self.loc, self.length / 2), + mid = connector.pointOnPath(self.loc), + tail = jsPlumb.util.pointOnLine(head, mid, self.length), + tailLine = jsPlumb.util.perpendicularLineTo(head, tail, 40), + headLine = jsPlumb.util.perpendicularLineTo(tail, head, 20); + + self.paint(connector, [head, tail, tailLine, headLine], self.lineWidth, "red", null, connectorDimensions); + + return [Math.min(head.x, tail.x), Math.min(head.y, tail.y), Math.max(head.x, tail.x), Math.max(head.y,tail.y)]; + }; + + this.computeMaxSize = function() { return 50; }; + + this.cleanup = function() { }; // nothing to clean up for GuideLines + }; + + // ********************************* END OF OVERLAY DEFINITIONS *********************************************************************** + + // ********************************* OVERLAY CANVAS RENDERERS*********************************************************************** + + // ********************************* END OF OVERLAY CANVAS RENDERERS *********************************************************************** +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the state machine connectors. + * + * Thanks to Brainstorm Mobile Solutions for supporting the development of these. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + var Line = function(x1, y1, x2, y2) { + + this.m = (y2 - y1) / (x2 - x1); + this.b = -1 * ((this.m * x1) - y1); + + this.rectIntersect = function(x,y,w,h) { + var results = []; + + // try top face + // the equation of the top face is y = (0 * x) + b; y = b. + var xInt = (y - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try right face + var yInt = (this.m * (x + w)) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + // bottom face + var xInt = ((y + h) - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try left face + var yInt = (this.m * x) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + if (results.length == 2) { + var midx = (results[0][0] + results[1][0]) / 2, midy = (results[0][1] + results[1][1]) / 2; + results.push([ midx,midy ]); + // now calculate the segment inside the rectangle where the midpoint lies. + var xseg = midx <= x + (w / 2) ? -1 : 1, + yseg = midy <= y + (h / 2) ? -1 : 1; + results.push([xseg, yseg]); + return results; + } + + return null; + + }; + }, + _segment = function(x1, y1, x2, y2) { + if (x1 <= x2 && y2 <= y1) return 1; + else if (x1 <= x2 && y1 <= y2) return 2; + else if (x2 <= x1 && y2 >= y1) return 3; + return 4; + }, + + // the control point we will use depends on the faces to which each end of the connection is assigned, specifically whether or not the + // two faces are parallel or perpendicular. if they are parallel then the control point lies on the midpoint of the axis in which they + // are parellel and varies only in the other axis; this variation is proportional to the distance that the anchor points lie from the + // center of that face. if the two faces are perpendicular then the control point is at some distance from both the midpoints; the amount and + // direction are dependent on the orientation of the two elements. 'seg', passed in to this method, tells you which segment the target element + // lies in with respect to the source: 1 is top right, 2 is bottom right, 3 is bottom left, 4 is top left. + // + // sourcePos and targetPos are arrays of info about where on the source and target each anchor is located. their contents are: + // + // 0 - absolute x + // 1 - absolute y + // 2 - proportional x in element (0 is left edge, 1 is right edge) + // 3 - proportional y in element (0 is top edge, 1 is bottom edge) + // + _findControlPoint = function(midx, midy, segment, sourceEdge, targetEdge, dx, dy, distance, proximityLimit) { + + // TODO (maybe) + // - if anchor pos is 0.5, make the control point take into account the relative position of the elements. + if (distance <= proximityLimit) return [midx, midy]; + + if (segment == 1) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 2) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 3) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 4) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + }; + + /* + Function: StateMachine constructor + + Allowed parameters: + curviness - measure of how "curvy" the connectors will be. this is translated as the distance that the + Bezier curve's control point is from the midpoint of the straight line connecting the two + endpoints, and does not mean that the connector is this wide. The Bezier curve never reaches + its control points; they act as gravitational masses. defaults to 10. + margin - distance from element to start and end connectors, in pixels. defaults to 5. + proximityLimit - sets the distance beneath which the elements are consider too close together to bother with fancy + curves. by default this is 80 pixels. + loopbackRadius - the radius of a loopback connector. optional; defaults to 25. + */ + jsPlumb.Connectors.StateMachine = function(params) { + var self = this, + currentPoints = null, + _sx, _sy, _tx, _ty, _controlPoint = [], + curviness = params.curviness || 10, + margin = params.margin || 5, + proximityLimit = params.proximityLimit || 80, + clockwise = params.orientation && params.orientation == "clockwise", + loopbackRadius = params.loopbackRadius || 25, + isLoopback = false; + + this.type = "StateMachine"; + params = params || {}; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *= 1.9; + //ensure at least one pixel width + lineWidth = lineWidth || 1; + var x = Math.min(sourcePos[0], targetPos[0]) - xo, + y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (sourceEndpoint.elementId != targetEndpoint.elementId) { + + isLoopback = false; + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + + // now adjust for the margin + if (sourcePos[2] == 0) _sx -= margin; + if (sourcePos[2] == 1) _sx += margin; + if (sourcePos[3] == 0) _sy -= margin; + if (sourcePos[3] == 1) _sy += margin; + if (targetPos[2] == 0) _tx -= margin; + if (targetPos[2] == 1) _tx += margin; + if (targetPos[3] == 0) _ty -= margin; + if (targetPos[3] == 1) _ty += margin; + + // + // these connectors are quadratic bezier curves, having a single control point. if both anchors + // are located at 0.5 on their respective faces, the control point is set to the midpoint and you + // get a straight line. this is also the case if the two anchors are within 'proximityLimit', since + // it seems to make good aesthetic sense to do that. outside of that, the control point is positioned + // at 'curviness' pixels away along the normal to the straight line connecting the two anchors. + // + // there may be two improvements to this. firstly, we might actually support the notion of avoiding nodes + // in the UI, or at least making a good effort at doing so. if a connection would pass underneath some node, + // for example, we might increase the distance the control point is away from the midpoint in a bid to + // steer it around that node. this will work within limits, but i think those limits would also be the likely + // limits for, once again, aesthetic good sense in the layout of a chart using these connectors. + // + // the second possible change is actually two possible changes: firstly, it is possible we should gradually + // decrease the 'curviness' as the distance between the anchors decreases; start tailing it off to 0 at some + // point (which should be configurable). secondly, we might slightly increase the 'curviness' for connectors + // with respect to how far their anchor is from the center of its respective face. this could either look cool, + // or stupid, and may indeed work only in a way that is so subtle as to have been a waste of time. + // + + var _midx = (_sx + _tx) / 2, _midy = (_sy + _ty) / 2, + m2 = (-1 * _midx) / _midy, theta2 = Math.atan(m2), + dy = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.sin(theta2)), + dx = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.cos(theta2)), + segment = _segment(_sx, _sy, _tx, _ty), + distance = Math.sqrt(Math.pow(_tx - _sx, 2) + Math.pow(_ty - _sy, 2)); + + // calculate the control point. this code will be where we'll put in a rudimentary element avoidance scheme; it + // will work by extending the control point to force the curve to be, um, curvier. + _controlPoint = _findControlPoint(_midx, + _midy, + segment, + sourcePos, + targetPos, + curviness, curviness, + distance, + proximityLimit); + + + var requiredWidth = Math.max(Math.abs(_controlPoint[0] - _sx) * 3, Math.abs(_controlPoint[0] - _tx) * 3, Math.abs(_tx-_sx), 2 * lineWidth, minWidth), + requiredHeight = Math.max(Math.abs(_controlPoint[1] - _sy) * 3, Math.abs(_controlPoint[1] - _ty) * 3, Math.abs(_ty-_sy), 2 * lineWidth, minWidth); + + if (w < requiredWidth) { + var dw = requiredWidth - w; + x -= (dw / 2); + _sx += (dw / 2); + _tx += (dw / 2); + w = requiredWidth; + _controlPoint[0] += (dw / 2); + } + + if (h < requiredHeight) { + var dh = requiredHeight - h; + y -= (dh / 2); + _sy += (dh / 2); + _ty += (dh / 2); + h = requiredHeight; + _controlPoint[1] += (dh / 2); + } + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty, _controlPoint[0], _controlPoint[1] ]; + } + else { + isLoopback = true; + // a loopback connector. draw an arc from one anchor to the other. + // i guess we'll do this the same way as the others. just the control point will be a fair distance away. + var x1 = sourcePos[0], x2 = sourcePos[0], y1 = sourcePos[1] - margin, y2 = sourcePos[1] - margin, + cx = x1, cy = y1 - loopbackRadius; + + // canvas sizing stuff, to ensure the whole painted area is visible. + w = ((2 * lineWidth) + (4 * loopbackRadius)), h = ((2 * lineWidth) + (4 * loopbackRadius)); + x = cx - loopbackRadius - lineWidth - loopbackRadius, y = cy - loopbackRadius - lineWidth - loopbackRadius; + currentPoints = [ x, y, w, h, cx-x, cy-y, loopbackRadius, clockwise, x1-x, y1-y, x2-x, y2-y]; + } + + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_tx, y:_ty }, + { x:_controlPoint[0], y:_controlPoint[1] }, + { x:_controlPoint[0] + 1, y:_controlPoint[1] + 1}, + { x:_sx, y:_sy } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + if (isLoopback) { + + if (location > 0 && location < 1) location = 1- location; + +// current points are [ x, y, width, height, center x, center y, radius, clockwise, startx, starty, endx, endy ] + // so the path length is the circumference of the circle + //var len = 2 * Math.PI * currentPoints[6], + // map 'location' to an angle. 0 is PI/2 when the connector is on the top face; if we + // support other faces it will have to be calculated for each one. 1 is also PI/2. + // 0.5 is -PI/2. + var startAngle = (location * 2 * Math.PI) + (Math.PI / 2), + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + + } + else return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + if (isLoopback) + return Math.atan(location * 2 * Math.PI); + else + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + if (isLoopback) { + + if (location > 0 && location < 1) location = 1- location; + + var circumference = 2 * Math.PI * currentPoints[6], + arcSpan = distance / circumference * 2 * Math.PI, + startAngle = (location * 2 * Math.PI) - arcSpan + (Math.PI / 2), + + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + } + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + }; + + /* + * Canvas state machine renderer. + */ + jsPlumb.Connectors.canvas.StateMachine = function(params) { + params = params || {}; + var self = this, drawGuideline = params.drawGuideline || true, avoidSelector = params.avoidSelector; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.CanvasConnector.apply(this, arguments); + + + this._paint = function(dimensions) { + + if (dimensions.length == 10) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.quadraticCurveTo(dimensions[8], dimensions[9], dimensions[6], dimensions[7]); + self.ctx.stroke(); + + /*/ draw the guideline + if (drawGuideline) { + self.ctx.save(); + self.ctx.beginPath(); + self.ctx.strokeStyle = "silver"; + self.ctx.lineWidth = 1; + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + self.ctx.restore(); + } + //*/ + } + else { + // a loopback connector + self.ctx.save(); + self.ctx.beginPath(); + var startAngle = 0, // Starting point on circle + endAngle = 2 * Math.PI, // End point on circle + clockwise = dimensions[7]; // clockwise or anticlockwise + self.ctx.arc(dimensions[4],dimensions[5],dimensions[6],0, endAngle, clockwise); + self.ctx.stroke(); + self.ctx.closePath(); + self.ctx.restore(); + } + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + /* + * SVG State Machine renderer + */ + jsPlumb.Connectors.svg.StateMachine = function() { + var self = this; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.SvgConnector.apply(this, arguments); + this.getPath = function(d) { + + if (d.length == 10) + return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[8] + " " + d[9] + " " + d[6] + " " + d[7]; + else { + // loopback + return "M" + (d[8] + 4) + " " + d[9] + " A " + d[6] + " " + d[6] + " 0 1,0 " + (d[8]-4) + " " + d[9]; + } + }; + }; + + /* + * VML state machine renderer + */ + jsPlumb.Connectors.vml.StateMachine = function() { + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.VmlConnector.apply(this, arguments); + var _conv = jsPlumb.vml.convertValue; + this.getPath = function(d) { + if (d.length == 10) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + } + else { + // loopback + var left = _conv(d[8] - d[6]), + top = _conv(d[9] - (2 * d[6])), + right = left + _conv(2 * d[6]), + bottom = top + _conv(2 * d[6]), + posString = left + "," + top + "," + right + "," + bottom; + + var o = "ar " + posString + "," + _conv(d[8]) + "," + + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + " e"; + + return o; + } + }; + }; + +})(); + +/* + // now for a rudimentary avoidance scheme. TODO: how to set this in a cross-library way? + // if (avoidSelector) { + // var testLine = new Line(sourcePos[0] + _sx,sourcePos[1] + _sy,sourcePos[0] + _tx,sourcePos[1] + _ty); + // var sel = jsPlumb.getSelector(avoidSelector); + // for (var i = 0; i < sel.length; i++) { + // var id = jsPlumb.getId(sel[i]); + // if (id != sourceEndpoint.elementId && id != targetEndpoint.elementId) { + // o = jsPlumb.getOffset(id), s = jsPlumb.getSize(id); +// +// if (o && s) { +// var collision = testLine.rectIntersect(o.left,o.top,s[0],s[1]); +// if (collision) { + // set the control point to be a certain distance from the midpoint of the two points that + // the line crosses on the rectangle. + // TODO where will this 75 number come from? + // _controlX = collision[2][0] + (75 * collision[3][0]); + // / _controlY = collision[2][1] + (75 * collision[3][1]); +// } +// } + // } + // } + //} + *//* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the VML renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + // http://ajaxian.com/archives/the-vml-changes-in-ie-8 + // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/ + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + + var vmlAttributeMap = { + "stroke-linejoin":"joinstyle", + "joinstyle":"joinstyle", + "endcap":"endcap", + "miterlimit":"miterlimit" + }; + + if (document.createStyleSheet) { + + // this is the style rule for IE7/6: it uses a CSS class, tidy. + document.createStyleSheet().addRule(".jsplumb_vml", "behavior:url(#default#VML);position:absolute;"); + + // these are for VML in IE8. you have to explicitly call out which elements + // you're going to expect to support VML! + // + document.createStyleSheet().addRule("jsplumb\\:textbox", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:oval", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:rect", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:stroke", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:shape", "behavior:url(#default#VML);position:absolute;"); + + // in this page it is also mentioned that IE requires the extra arg to the namespace + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either. + // var iev = document.documentMode; + //if (!iev || iev < 8) + document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); + //else + // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML"); + } + + jsPlumb.vml = {}; + + var scale = 1000, + _atts = function(o, atts) { + for (var i in atts) { + // IE8 fix: setattribute does not work after an element has been added to the dom! + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + //o.setAttribute(i, atts[i]); + + o[i] = atts[i]; + } + }, + _node = function(name, d, atts) { + atts = atts || {}; + var o = document.createElement("jsplumb:" + name); + o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml"; + _pos(o, d); + _atts(o, atts); + return o; + }, + _pos = function(o,d) { + o.style.left = d[0] + "px"; + o.style.top = d[1] + "px"; + o.style.width= d[2] + "px"; + o.style.height= d[3] + "px"; + o.style.position = "absolute"; + }, + _conv = jsPlumb.vml.convertValue = function(v) { + return Math.floor(v * scale); + }, + // tests if the given style is "transparent" and then sets the appropriate opacity node to 0 if so, + // or 1 if not. TODO in the future, support variable opacity. + _maybeSetOpacity = function(styleToWrite, styleToCheck, type, component) { + if ("transparent" === styleToCheck) + component.setOpacity(type, "0.0"); + else + component.setOpacity(type, "1.0"); + }, + _applyStyles = function(node, style, component) { + var styleToWrite = {}; + if (style.strokeStyle) { + styleToWrite["stroked"] = "true"; + var strokeColor = jsPlumb.util.convertStyle(style.strokeStyle, true); + styleToWrite["strokecolor"] = strokeColor; + _maybeSetOpacity(styleToWrite, strokeColor, "stroke", component); + styleToWrite["strokeweight"] = style.lineWidth + "px"; + } + else styleToWrite["stroked"] = "false"; + + if (style.fillStyle) { + styleToWrite["filled"] = "true"; + var fillColor = jsPlumb.util.convertStyle(style.fillStyle, true); + styleToWrite["fillcolor"] = fillColor; + _maybeSetOpacity(styleToWrite, fillColor, "fill", component); + } + else styleToWrite["filled"] = "false"; + + if(style["dashstyle"]) { + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = style["dashstyle"]; + } + else if (style["stroke-dasharray"] && style["lineWidth"]) { + var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",", + parts = style["stroke-dasharray"].split(sep), + styleToUse = ""; + for(var i = 0; i < parts.length; i++) { + styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep); + } + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = styleToUse; + } + + _atts(node, styleToWrite); + }, + /* + * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. + */ + VmlComponent = function() { + var self = this; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.opacityNodes = { + "stroke":null, + "fill":null + }; + this.initOpacityNodes = function(vml) { + self.opacityNodes["stroke"] = _node("stroke", [0,0,1,1], {opacity:"0.0"}); + self.opacityNodes["fill"] = _node("fill", [0,0,1,1], {opacity:"0.0"}); + vml.appendChild(self.opacityNodes["stroke"]); + vml.appendChild(self.opacityNodes["fill"]); + }; + this.setOpacity = function(type, value) { + var node = self.opacityNodes[type]; + if (node) node["opacity"] = "" + value; + }; + }, + /* + * Base class for Vml connectors. extends VmlComponent. + */ + VmlConnector = jsPlumb.VmlConnector = function(params) { + var self = this; + self.strokeNode = null; + self.canvas = null; + VmlComponent.apply(this, arguments); + var clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : ""); + this.paint = function(d, style, anchor) { + if (style != null) { + var path = self.getPath(d), p = { "path":path }; + + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle : jsPlumb.util.convertStyle(style.outlineColor), + lineWidth : outlineStrokeWidth + }; + for (var aa in vmlAttributeMap) outlineStyle[aa] = style[aa]; + + if (self.bgCanvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.bgCanvas = _node("shape", d, p); + jsPlumb.appendElement(self.bgCanvas, params.parent); + _pos(self.bgCanvas, d); + displayElements.push(self.bgCanvas); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.bgCanvas, d); + _atts(self.bgCanvas, p); + } + + _applyStyles(self.bgCanvas, outlineStyle, self); + } + + if (self.canvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + if (self.tooltip) p["label"] = self.tooltip; + self.canvas = _node("shape", d, p); + jsPlumb.appendElement(self.canvas, params.parent); + displayElements.push(self.canvas); + + self.attachListeners(self.canvas, self); + + self.initOpacityNodes(self.canvas, ["stroke"]); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.canvas, d); + _atts(self.canvas, p); + } + + _applyStyles(self.canvas, style, self); + } + }; + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + self.canvas.parentNode.appendChild(el); + displayElements.push(el); + }; + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + }, + /* + * + * Base class for Vml Endpoints. extends VmlComponent. + * + */ + VmlEndpoint = function(params) { + VmlComponent.apply(this, arguments); + var vml = null, self = this, opacityStrokeNode = null, opacityFillNode = null; + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.appendElement(self.canvas, params.parent); + if (self.tooltip) self.canvas.setAttribute("label", self.tooltip); + + this.paint = function(d, style, anchor) { + var p = { }; + + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (vml == null) { + p["class"] = jsPlumb.endpointClass; + vml = self.getVml([0,0, d[2], d[3]], p, anchor); + self.canvas.appendChild(vml); + self.attachListeners(vml, self); + + self.initOpacityNodes(vml, ["fill"]); + } + else { + //p["coordsize"] = "1,1";//(d[2] * scale) + "," + (d[3] * scale); again, unsure. + _pos(vml, [0,0, d[2], d[3]]); + _atts(vml, p); + } + + _applyStyles(vml, style, self); + }; + + this.reattachListeners = function() { + if (vml) self.reattachListenersForElement(vml, self); + }; + }; + + jsPlumb.Connectors.vml.Bezier = function() { + jsPlumb.Connectors.Bezier.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Straight = function() { + jsPlumb.Connectors.Straight.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Flowchart = function() { + jsPlumb.Connectors.Flowchart.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l"; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]); + } + // finally draw a line to the end + p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e"; + return p; + }; + }; + + jsPlumb.Endpoints.vml.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("oval", d, atts); }; + }; + + jsPlumb.Endpoints.vml.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("rect", d, atts); }; + }; + + /* + * VML Image Endpoint is the same as the default image endpoint. + */ + jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image; + + /** + * placeholder for Blank endpoint in vml renderer. + */ + jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank; + + /** + * VML Label renderer. uses the default label renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label; + + var AbstractVmlArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + VmlComponent.apply(this, arguments); + var self = this, path = null; + self.canvas = null; + var getPath = function(d, connectorDimensions) { + return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) + + " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + + " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + + " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + + " x e"; + }; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) { + var p = {}; + if (strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] = jsPlumb.util.convertStyle(strokeStyle, true); + } + if (lineWidth) p["strokeweight"] = lineWidth + "px"; + if (fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = fillStyle; + } + var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + w = Math.abs(xmax - xmin), + h = Math.abs(ymax - ymin), + dim = [xmin, ymin, w, h]; + + // for VML, we create overlays using shapes that have the same dimensions and + // coordsize as their connector - overlays calculate themselves relative to the + // connector (it's how it's been done since the original canvas implementation, because + // for canvas that makes sense). + p["path"] = getPath(d, connectorDimensions); + p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale); + + dim[0] = connectorDimensions[0]; + dim[1] = connectorDimensions[1]; + dim[2] = connectorDimensions[2]; + dim[3] = connectorDimensions[3]; + + if (self.canvas == null) { + //p["class"] = jsPlumb.overlayClass; // TODO currentInstance? + self.canvas = _node("shape", dim, p); + connector.appendDisplayElement(self.canvas); + self.attachListeners(self.canvas, connector); + } + else { + _pos(self.canvas, dim); + _atts(self.canvas, p); + } + }; + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + }; + + jsPlumb.Overlays.vml.Arrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.vml.PlainArrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.vml.Diamond = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the SVG renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +/** + * SVG support for jsPlumb. + * + * things to investigate: + * + * gradients: https://developer.mozilla.org/en/svg_in_html_introduction + * css:http://tutorials.jenkov.com/svg/svg-and-css.html + * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath + * pointer events: https://developer.mozilla.org/en/css/pointer-events + * + * IE9 hover jquery: http://forum.jquery.com/topic/1-6-2-broke-svg-hover-events + * + */ +;(function() { + + var svgAttributeMap = { + "joinstyle":"stroke-linejoin", + "stroke-linejoin":"stroke-linejoin", + "stroke-dashoffset":"stroke-dashoffset", + "stroke-linecap":"stroke-linecap" + }, + STROKE_DASHARRAY = "stroke-dasharray", + DASHSTYLE = "dashstyle", + LINEAR_GRADIENT = "linearGradient", + RADIAL_GRADIENT = "radialGradient", + FILL = "fill", + STOP = "stop", + STROKE = "stroke", + STROKE_WIDTH = "stroke-width", + STYLE = "style", + NONE = "none", + JSPLUMB_GRADIENT = "jsplumb_gradient_", + LINE_WIDTH = "lineWidth", + ns = { + svg:"http://www.w3.org/2000/svg", + xhtml:"http://www.w3.org/1999/xhtml" + }, + _attr = function(node, attributes) { + for (var i in attributes) + node.setAttribute(i, "" + attributes[i]); + }, + _node = function(name, attributes) { + var n = document.createElementNS(ns.svg, name); + attributes = attributes || {}; + attributes["version"] = "1.1"; + attributes["xmnls"] = ns.xhtml; + _attr(n, attributes); + return n; + }, + _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; }, + _clearGradient = function(parent) { + for (var i = 0; i < parent.childNodes.length; i++) { + if (parent.childNodes[i].tagName == LINEAR_GRADIENT || parent.childNodes[i].tagName == RADIAL_GRADIENT) + parent.removeChild(parent.childNodes[i]); + } + }, + _updateGradient = function(parent, node, style, dimensions, uiComponent) { + var id = JSPLUMB_GRADIENT + uiComponent._jsPlumb.idstamp(); + // first clear out any existing gradient + _clearGradient(parent); + // this checks for an 'offset' property in the gradient, and in the absence of it, assumes + // we want a linear gradient. if it's there, we create a radial gradient. + // it is possible that a more explicit means of defining the gradient type would be + // better. relying on 'offset' means that we can never have a radial gradient that uses + // some default offset, for instance. + if (!style.gradient.offset) { + var g = _node(LINEAR_GRADIENT, {id:id}); + parent.appendChild(g); + } + else { + var g = _node(RADIAL_GRADIENT, { + id:id + }); + parent.appendChild(g); + } + + // the svg radial gradient seems to treat stops in the reverse + // order to how canvas does it. so we want to keep all the maths the same, but + // iterate the actual style declarations in reverse order, if the x indexes are not in order. + for (var i = 0; i < style.gradient.stops.length; i++) { + // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of + // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments. + // so, not too concerned about leaving this in for now. + var styleToUse = i; + if (dimensions.length == 8) + styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i; + else + styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i; + var stopColor = jsPlumb.util.convertStyle(style.gradient.stops[styleToUse][1], true); + var s = _node(STOP, {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor}); + g.appendChild(s); + } + var applyGradientTo = style.strokeStyle ? STROKE : FILL; + node.setAttribute(STYLE, applyGradientTo + ":url(#" + id + ")"); + }, + _applyStyles = function(parent, node, style, dimensions, uiComponent) { + + if (style.gradient) { + _updateGradient(parent, node, style, dimensions, uiComponent); + } + else { + // make sure we clear any existing gradient + _clearGradient(parent); + node.setAttribute(STYLE, ""); + } + + node.setAttribute(FILL, style.fillStyle ? jsPlumb.util.convertStyle(style.fillStyle, true) : NONE); + node.setAttribute(STROKE, style.strokeStyle ? jsPlumb.util.convertStyle(style.strokeStyle, true) : NONE); + if (style.lineWidth) { + node.setAttribute(STROKE_WIDTH, style.lineWidth); + } + + // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like + // the syntax in VML but is actually kind of nasty: values are given in the pixel + // coordinate space, whereas in VML they are multiples of the width of the stroked + // line, which makes a lot more sense. for that reason, jsPlumb is supporting both + // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from + // VML, which will be the preferred method. the code below this converts a dashstyle + // attribute given in terms of stroke width into a pixel representation, by using the + // stroke's lineWidth. + if (style[DASHSTYLE] && style[LINE_WIDTH] && !style[STROKE_DASHARRAY]) { + var sep = style[DASHSTYLE].indexOf(",") == -1 ? " " : ",", + parts = style[DASHSTYLE].split(sep), + styleToUse = ""; + parts.forEach(function(p) { + styleToUse += (Math.floor(p * style.lineWidth) + sep); + }); + node.setAttribute(STROKE_DASHARRAY, styleToUse); + } + else if(style[STROKE_DASHARRAY]) { + node.setAttribute(STROKE_DASHARRAY, style[STROKE_DASHARRAY]); + } + + // extra attributes such as join type, dash offset. + for (var i in svgAttributeMap) { + if (style[i]) { + node.setAttribute(svgAttributeMap[i], style[i]); + } + } + }, + _decodeFont = function(f) { + var r = /([0-9].)(p[xt])\s(.*)/; + var bits = f.match(r); + return {size:bits[1] + bits[2], font:bits[3]}; + }, + _classManip = function(el, add, clazz) { + var classesToAddOrRemove = clazz.split(" "), + className = el.className, + curClasses = className.baseVal.split(" "); + + for (var i = 0; i < classesToAddOrRemove.length; i++) { + if (add) { + if (curClasses.indexOf(classesToAddOrRemove[i]) == -1) + curClasses.push(classesToAddOrRemove[i]); + } + else { + var idx = curClasses.indexOf(classesToAddOrRemove[i]); + if (idx != -1) + curClasses.splice(idx, 1); + } + } + + el.className.baseVal = curClasses.join(" "); + }, + _addClass = function(el, clazz) { + _classManip(el, true, clazz); + }, + _removeClass = function(el, clazz) { + _classManip(el, false, clazz); + }; + + /** + utility methods for other objects to use. + */ + jsPlumb.util.svg = { + addClass:_addClass, + removeClass:_removeClass + }; + + /* + * Base class for SVG components. + */ + //var SvgComponent = function(cssClass, originalArgs, pointerEventsSpec) { + var SvgComponent = function(params) { + var self = this, + pointerEventsSpec = params.pointerEventsSpec || "all"; + jsPlumb.jsPlumbUIComponent.apply(this, params.originalArgs); + self.canvas = null, self.path = null, self.svg = null; + + var clazz = params.cssClass + " " + (params.originalArgs[0].cssClass || ""), + svgParams = { + "style":"", + "width":0, + "height":0, + "pointer-events":pointerEventsSpec, + "position":"absolute" + }; + if (self.tooltip) svgParams["label"] = self.tooltip; + self.svg = _node("svg", svgParams); + if (params.useDivWrapper) { + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.sizeCanvas(self.canvas,0,0,1,1); + self.canvas.className = clazz; + if (self.tooltip) self.canvas.setAttribute("label", self.tooltip); + } + else { + _attr(self.svg, { "class":clazz }); + self.canvas = self.svg; + } + + jsPlumb.appendElement(self.canvas, params.originalArgs[0]["parent"]); + if (params.useDivWrapper) self.canvas.appendChild(self.svg); + + // TODO this displayElement stuff is common between all components, across all + // renderers. would be best moved to jsPlumbUIComponent. + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + this.paint = function(d, style, anchor) { + if (style != null) { + var x = d[0], y = d[1]; + if (params.useDivWrapper) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + x = 0, y = 0; + } + _attr(self.svg, { + "style":_pos([x, y, d[2], d[3]]), + "width": d[2], + "height": d[3] + }); + self._paint.apply(this, arguments); + } + }; + }; + + /* + * Base class for SVG connectors. + */ + var SvgConnector = jsPlumb.SvgConnector = function(params) { + var self = this; + SvgComponent.apply(this, [ { cssClass:params["_jsPlumb"].connectorClass, originalArgs:arguments, pointerEventsSpec:"none", tooltip:params.tooltip } ]); + this._paint = function(d, style) { + var p = self.getPath(d), a = { "d":p }, outlineStyle = null; + a["pointer-events"] = "all"; + + // outline style. actually means drawing an svg object underneath the main one. + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = jsPlumb.CurrentLibrary.extend({}, style); + outlineStyle.strokeStyle = jsPlumb.util.convertStyle(style.outlineColor); + outlineStyle.lineWidth = outlineStrokeWidth; + + if (self.bgPath == null) { + self.bgPath = _node("path", a); + self.svg.appendChild(self.bgPath); + self.attachListeners(self.bgPath, self); + } + else { + _attr(self.bgPath, a); + } + + _applyStyles(self.svg, self.bgPath, outlineStyle, d, self); + } + + + // test - see below + // a["clip-path"]= "url(#testClip)"; + + if (self.path == null) { + self.path = _node("path", a); + self.svg.appendChild(self.path); + self.attachListeners(self.path, self); + + /* + this is a test of a clip path. i'm looking into using one of these to animate a jsplumb connection. + you could do this by walking along the line, stepping along a little at a time, and setting the clip + path to extend as far as that point. + + self.clip = _node("clipPath", {id:"testClip", clipPathUnits:"objectBoundingBox"}); + self.svg.appendChild(self.clip); + self.clip.appendChild(_node("rect", { + x:"0",y:"0",width:"0.5",height:"1" + })); + */ + } + else { + _attr(self.path, a); + } + + _applyStyles(self.svg, self.path, style, d, self); + }; + + this.reattachListeners = function() { + if (self.bgPath) self.reattachListenersForElement(self.bgPath, self); + if (self.path) self.reattachListenersForElement(self.path, self); + }; + + }; + + /* + * SVG Bezier Connector + */ + jsPlumb.Connectors.svg.Bezier = function(params) { + jsPlumb.Connectors.Bezier.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]; }; + }; + + /* + * SVG straight line Connector + */ + jsPlumb.Connectors.svg.Straight = function(params) { + jsPlumb.Connectors.Straight.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; }; + }; + + jsPlumb.Connectors.svg.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "M " + dimensions[4] + "," + dimensions[5]; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)]; + } + // finally draw a line to the end + p = p + " " + dimensions[6] + "," + dimensions[7]; + return p; + }; + }; + + /* + * Base class for SVG endpoints. + */ + var SvgEndpoint = function(params) { + var self = this; + SvgComponent.apply(this, [ { + cssClass:params["_jsPlumb"].endpointClass, + originalArgs:arguments, + pointerEventsSpec:"all", + useDivWrapper:true + } ]); + this._paint = function(d, style) { + var s = jsPlumb.extend({}, style); + if (s.outlineColor) { + s.strokeWidth = s.outlineWidth; + s.strokeStyle = jsPlumb.util.convertStyle(s.outlineColor, true); + } + + if (self.node == null) { + self.node = self.makeNode(d, s); + self.svg.appendChild(self.node); + self.attachListeners(self.node, self); + } + _applyStyles(self.svg, self.node, s, d, self); + _pos(self.node, d); + }; + + this.reattachListeners = function() { + if (self.node) self.reattachListenersForElement(self.node, self); + }; + }; + + /* + * SVG Dot Endpoint + */ + jsPlumb.Endpoints.svg.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("circle", { + "cx" : d[2] / 2, + "cy" : d[3] / 2, + "r" : d[2] / 2 + }); + }; + }; + + /* + * SVG Rectangle Endpoint + */ + jsPlumb.Endpoints.svg.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("rect", { + "width":d[2], + "height":d[3] + }); + }; + }; + + /* + * SVG Image Endpoint is the default image endpoint. + */ + jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image; + /* + * Blank endpoint in svg renderer is the default Blank endpoint. + */ + jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank; + /* + * Label endpoint in svg renderer is the default Label endpoint. + */ + jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label; + + var AbstractSvgArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + this.isAppendedAtTopLevel = false; + var self = this, path =null; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + } + var clazz = originalArgs && (originalArgs.length == 1) ? (originalArgs[0].cssClass || "") : ""; + + _attr(path, { + "d" : makePath(d), + "class" : clazz, + stroke : strokeStyle ? strokeStyle : null, + fill : fillStyle ? fillStyle : null + }); + }; + var makePath = function(d) { + return "M" + d.hxy.x + "," + d.hxy.y + + " L" + d.tail[0].x + "," + d.tail[0].y + + " L" + d.cxy.x + "," + d.cxy.y + + " L" + d.tail[1].x + "," + d.tail[1].y + + " L" + d.hxy.x + "," + d.hxy.y; + }; + this.reattachListeners = function() { + if (path) self.reattachListenersForElement(path, self); + }; + }; + + jsPlumb.Overlays.svg.Arrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.svg.PlainArrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.svg.Diamond = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; + + // a test + jsPlumb.Overlays.svg.GuideLines = function() { + var path = null, self = this, path2 = null, p1_1, p1_2; + jsPlumb.Overlays.GuideLines.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + + p1_1 = _node("path"); + connector.svg.appendChild(p1_1); + self.attachListeners(p1_1, connector); + self.attachListeners(p1_1, self); + + p1_2 = _node("path"); + connector.svg.appendChild(p1_2); + self.attachListeners(p1_2, connector); + self.attachListeners(p1_2, self); + + } + + _attr(path, { + "d" : makePath(d[0], d[1]), + stroke : "red", + fill : null + }); + + _attr(p1_1, { + "d" : makePath(d[2][0], d[2][1]), + stroke : "blue", + fill : null + }); + + _attr(p1_2, { + "d" : makePath(d[3][0], d[3][1]), + stroke : "green", + fill : null + }); + }; + + var makePath = function(d1, d2) { + return "M " + d1.x + "," + d1.y + + " L" + d2.x + "," + d2.y; + }; + + }; +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the HTML5 canvas renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + +// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS ******************************************************************* + + // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. + var _connectionBeingDragged = null, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, + _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }; + + /* + * Class:CanvasMouseAdapter + * Provides support for mouse events on canvases. + */ + var CanvasMouseAdapter = function() { + var self = this; + self.overlayPlacements = []; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + jsPlumb.EventGenerator.apply(this, arguments); + /** + * returns whether or not the given event is ojver a painted area of the canvas. + */ + this._over = function(e) { + var o = _getOffset(_getElementObject(self.canvas)), + pageXY = _pageXY(e), + x = pageXY[0] - o.left, y = pageXY[1] - o.top; + if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + return false; + }; + + var _mouseover = false, _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false, + _nullSafeHasClass = function(el, clazz) { + return el != null && _hasClass(el, clazz); + }; + this.mousemove = function(e) { + var pageXY = _pageXY(e), clientXY = _clientXY(e), + ee = document.elementFromPoint(clientXY[0], clientXY[1]), + eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); + var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); + if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.fire("mouseenter", self, e); + return true; + } + // TODO here there is a remote chance that the overlay the mouse moved onto + // is actually not an overlay for the current component. a more thorough check would + // be to ensure the overlay belonged to the current component. + else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { + _mouseover = false; + self.fire("mouseexit", self, e); + } + self.fire("mousemove", self, e); + }; + + this.click = function(e) { + // to filter right click in FF, i could compare e.button. 0 means left mouse button; 1 middle, 2 right. + //console.log(e.button); + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); + self.fire("mousedown", self, e); + } + }; + + this.mouseup = function(e) { + _mouseDown = false; + self.fire("mouseup", self, e); + }; + }; + + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + jsPlumb.appendElement(canvas, params.parent); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + params["_jsPlumb"].getId(canvas, params.uuid); + if (params.tooltip) canvas.setAttribute("label", params.tooltip); + + return canvas; + }; + + /** + * Class:CanvasConnector + * Superclass for Canvas Connector renderers. + */ + var CanvasConnector = jsPlumb.CanvasConnector = function(params) { + + CanvasMouseAdapter.apply(this, arguments); + + var _paintOneStyle = function(dim, aStyle) { + self.ctx.save(); + jsPlumb.extend(self.ctx, aStyle); + if (aStyle.gradient) { + var g = self.createGradient(dim, self.ctx); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + self.ctx.strokeStyle = g; + } + self._paint(dim); + self.ctx.restore(); + }; + + var self = this, + clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:params.tooltip + }); + self.ctx = self.canvas.getContext("2d"); + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { return displayElements; }; + this.appendDisplayElement = function(el) { displayElements.push(el); }; + + self.paint = function(dim, style) { + if (style != null) { + jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + _paintOneStyle(dim, outlineStyle); + } + _paintOneStyle(dim, style); + } + }; + }; + + /** + * Class:CanvasEndpoint + * Superclass for Canvas Endpoint renderers. + */ + var CanvasEndpoint = function(params) { + var self = this; + CanvasMouseAdapter.apply(this, arguments); + var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""), + canvasParams = { + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:self.tooltip + }; + self.canvas = _newCanvas(canvasParams); + self.ctx = self.canvas.getContext("2d"); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + } + + self._paint.apply(this, arguments); + }; + }; + + jsPlumb.Endpoints.canvas.Dot = function(params) { + jsPlumb.Endpoints.Dot.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + var self = this, + parseValue = function(value) { + try { return parseInt(value); } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }, + calculateAdjustments = function(gradient) { + var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius; + gradient.offset && (offsetAdjustment = parseValue(gradient.offset)); + gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius)); + return [offsetAdjustment, innerRadius]; + }; + this._paint = function(d, style, anchor) { + if (style != null) { + var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + if (style.gradient) { + var adjustments = calculateAdjustments(style.gradient), + yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0], + xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0], + g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + ctx.beginPath(); + ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + } + }; + }; + + jsPlumb.Endpoints.canvas.Rectangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) { + + var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + + /* canvas gradient */ + if (style.gradient) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, d[2], d[3]); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + jsPlumb.Endpoints.canvas.Triangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Triangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) + { + var width = d[2], height = d[3], x = d[0], y = d[1], + ctx = self.canvas.getContext('2d'), + offsetX = 0, offsetY = 0, angle = 0, + orientation = anchor.getOrientation(self); + + if( orientation[0] == 1 ) { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = style.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + /* + * Canvas Image Endpoint: uses the default version, which creates an tag. + */ + jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image; + + /* + * Blank endpoint in all renderers is just the default Blank endpoint. + */ + jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank; + + /* + * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Bezier = function() { + var self = this; + jsPlumb.Connectors.Bezier.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO i doubt this handles the case that source and target are swapped. + this.createGradient = function(dim, ctx, swap) { + return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + /* + * Canvas straight line Connector. Draws a straight line onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Straight = function() { + var self = this; + jsPlumb.Connectors.Straight.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO this does not handle the case that src and target are swapped. + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + jsPlumb.Connectors.canvas.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + +// ********************************* END OF CANVAS RENDERERS ******************************************************************* + + jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label; + + /** + * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. + */ + var CanvasOverlay = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }; + + var AbstractCanvasArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + CanvasOverlay.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + var ctx = connector.ctx; + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(d.hxy.x, d.hxy.y); + ctx.lineTo(d.tail[0].x, d.tail[0].y); + ctx.lineTo(d.cxy.x, d.cxy.y); + ctx.lineTo(d.tail[1].x, d.tail[1].y); + ctx.lineTo(d.hxy.x, d.hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + }; + + jsPlumb.Overlays.canvas.Arrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.canvas.PlainArrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.canvas.Diamond = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the YUI3 adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +/** + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + */ +(function() { + + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function( v, b, s ) { + for( var i = +b || 0, l = this.length; i < l; i++ ) { + if( this[i]===v || s && this[i]==v ) { return i; } + } + return -1; + }; + } + + var Y; + + YUI().use('node', 'dd', 'anim', 'node-event-simulate', function(_Y) { + Y = _Y; + Y.on("domready", function() { jsPlumb.init(); }); + }); + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }, + ddEvents = [ "drag:mouseDown", "drag:afterMouseDown", "drag:mouseup", + "drag:align", "drag:removeHandle", "drag:addHandle", "drag:removeInvalid", "drag:addInvalid", + "drag:start", "drag:end", "drag:drag", "drag:over", "drag:enter", + "drag:exit", "drag:drophit", "drag:dropmiss", "drop:over", "drop:enter", "drop:exit", "drop:hit" + ], + animEvents = [ "tween" ], + /** + * helper function to curry callbacks for some element. + */ + _wrapper = function(fn) { + return function() { + try { + return fn.apply(this, arguments); + } + catch (e) { + alert("YYYYY"); + //console.log("wrap fail", e); + } + }; + }, + /** + * extracts options from the given options object, leaving out event handlers. + */ + _getDDOptions = function(options) { + var o = {}; + for (var i in options) if (ddEvents.indexOf(i) == -1) o[i] = options[i]; + return o; + }, + /** + * attaches all event handlers found in options to the given dragdrop object, and registering + * the given el as the element of interest. + */ + _attachListeners = function(dd, options, eventList) { + for (var ev in options) { + if (eventList.indexOf(ev) != -1) { + var w = _wrapper(options[ev]); + dd.on(ev, w); + } + } + }, + _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}, + _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }, + _lastDragObject = null, + _extend = function(o1, o2) { + for (var i in o2) + o1[i] = o2[i]; + return o1; + }, + _getAttribute = function(el, attributeId) { + return el.getAttribute(attributeId); + }, + _getElementObject = function(el) { + var eee = null; + try { + eee = typeof el == 'string' ? Y.one('#' + el) : el._node ? el : Y.one(el); + } + catch(eeee) { + console.log(eeee); + } + return eee; + }; + + jsPlumb.CurrentLibrary = { + + addClass : function(el, clazz) { + jsPlumb.CurrentLibrary.getElementObject(el).addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + var o = _extend({node:el, to:properties}, options); + var id = _getAttribute(el, "id"); + o["tween"] = jsPlumb.wrap(properties["tween"], function() { + jsPlumb.repaint(id); + }); + var a = new Y.Anim(o); + _attachListeners(a, o, animEvents); + a.run(); + }, + + appendElement : function(child, parent) { + _getElementObject (parent).append(child); + }, + + /** + * event binding wrapper. + */ + bind : function(el, event, callback) { + _getElementObject(el).on(event, callback); + }, + + dragEvents : { + "start":"drag:start", "stop":"drag:end", "drag":"drag:drag", "step":"step", + "over":"drop:enter", "out":"drop:exit", "drop":"drop:hit" + }, + + extend : _extend, + + getAttribute : _getAttribute, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + // this is a workaround for the unfortunate fact that in YUI3, the 'drop:exit' event does + // not contain a reference to the drag that just exited. single-threaded js to the + // rescue: we'll just keep it for ourselves. + if (eventArgs[0].drag) _lastDragObject = eventArgs[0].drag.el; + return _lastDragObject; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + return dd.scope; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getElementObject : _getElementObject, + + getOffset : function(el) { + var o = Y.DOM.getXY(el._node); + return {left:o[0], top:o[1]}; + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).get("parentNode"); + }, + + getScrollLeft : function(el) { + return 0; + }, + + getScrollTop : function(el) { + return 0; + }, + + getSelector : function(spec) { + var s = Y.all(spec); + return s && s ._nodes ? s._nodes : []; + }, + + getSize : function(el) { + return [ el._node.offsetWidth, el._node.offsetHeight ]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e != null && e._node != null ? e._node.tagName : null; + }, + + getUIPosition : function(args) { + var n = args[0].currentTarget.el._node, + o = Y.DOM.getXY(n); + return {left:o[0], top:o[1]}; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options) { + var _opts = _getDDOptions(options); + var id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drag(_opts); + dd.el = el; + + var scope = options['scope'] || jsPlumb.Defaults.Scope; + dd.scope = scope; + + _draggablesById[id] = dd; + _add(_draggablesByScope, scope, dd); + + _attachListeners(dd, options, ddEvents); + }, + + initDroppable : function(el, options) { + var _opts = _getDDOptions(options); + var id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drop(_opts); + + _droppableOptions[id] = options; + + options = _extend({}, options); + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _droppableScopesById[id] = scope; + + options["drop:enter"] = jsPlumb.wrap(options["drop:enter"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, true); + }, true); + options["drop:exit"] = jsPlumb.wrap(options["drop:exit"], function(e) { + _checkHover(el, false); + }); + options["drop:hit"] = jsPlumb.wrap(options["drop:hit"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, false); + }, true); + + _attachListeners(dd, options, ddEvents); + }, + + isAlreadyDraggable : function(el) { + el = _getElementObject(el); + return el.hasClass("yui3-dd-draggable"); + }, + + isDragSupported : function(el) { return true; }, + isDropSupported : function(el) { return true; }, + removeClass : function(el, clazz) { + jsPlumb.CurrentLibrary.getElementObject(el).removeClass(clazz); + }, + removeElement : function(el) { _getElementObject(el).remove(); }, + + setAttribute : function(el, attributeName, attributeValue) { + el.setAttribute(attributeName, attributeValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + if (dd) dd.set("lock", !draggable); + }, + + setDragScope : function(el, scope) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + if (dd) dd.scope = scope; + }, + + setOffset : function(el, o) { + el = _getElementObject(el); + el.set("top", o.top); + el.set("left", o.left); + }, + + stopDrag : function() { + // console.log("stop drag!"); + Y.DD.DDM.stopDrag(); + }, + + trigger : function(el, event, originalEvent) { + originalEvent.stopPropagation(); + _getElementObject(el).simulate(event, { + pageX:originalEvent.pageX, + pageY:originalEvent.pageY, + clientX:originalEvent.clientX, + clientY:originalEvent.clientY + }); + }, + + /** + * event unbinding wrapper. + */ + unbind : function(el, event, callback) { + _getElementObject(el).detach(event, callback); + } + }; +})();(function(){if("undefined"==typeof Math.sgn)Math.sgn=function(a){return 0==a?0:0n?n=l:lb.location)b.location=0;return t(a,b.location)},nearestPointOnCurve:function(a,b){var f=u(a,b);return{point:r(b,b.length-1,f.location,null,null),location:f.location}},pointOnCurve:p,pointAlongCurveFrom:function(a,b,f){return s(a,b,f).point},perpendicularToCurveAt:function(a,b,f,d){b=s(a,b,null==d?0:d);a=t(a,b.location);d=Math.atan(-1/a);a=f/2*Math.sin(d);f=f/2*Math.cos(d);return[{x:b.point.x+f,y:b.point.y+a},{x:b.point.x-f,y:b.point.y-a}]}}})(); \ No newline at end of file diff --git a/archive/1.3.5/jquery.jsPlumb-1.3.5-RC1.js b/archive/1.3.5/jquery.jsPlumb-1.3.5-RC1.js new file mode 100644 index 000000000..66c7e5496 --- /dev/null +++ b/archive/1.3.5/jquery.jsPlumb-1.3.5-RC1.js @@ -0,0 +1,355 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the jQuery adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ +/* + * the library specific functions, such as find offset, get id, get attribute, extend etc. + * the full list is: + * + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getDropScope gets the drop scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getPageXY gets the page event's xy location. + * getParent gets the parent of some element. + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * hasClass returns whether or not the given element has the given class. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + * trigger triggers some event on an element. + * unbind unbinds some listener from some element. + */ +(function($) { + + //var getBoundingClientRectSupported = "getBoundingClientRect" in document.documentElement; + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el[0].className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.addClass(el[0], clazz); + } + } + catch (e) { + // SVGAnimatedString not supported; no problem. + } + el.addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * appends the given child to the given parent. + */ + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).append(child); + }, + + /** + * event binding wrapper. it just so happens that jQuery uses 'bind' also. yui3, for example, + * uses 'on'. + */ + bind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.bind(event, callback); + }, + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step', + 'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete' + }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + getDocumentElement : function() { return document; }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + }, + + getDragScope : function(el) { + return el.draggable("option", "scope"); + }, + + getDropScope : function(el) { + return el.droppable("option", "scope"); + }, + + /** + * gets a DOM element from the given input, which might be a string (in which case we just do document.getElementById), + * a selector (in which case we return el[0]), or a DOM element already (we assume this if it's not either of the other + * two cases). this is the opposite of getElementObject below. + */ + getDOMElement : function(el) { + if (typeof(el) == "string") return document.getElementById(el); + else if (el.context) return el[0]; + else return el; + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + * + */ + getElementObject : function(el) { + return typeof(el) == "string" ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).parent(); + }, + + getScrollLeft : function(el) { + return el.scrollLeft(); + }, + + getScrollTop : function(el) { + return el.scrollTop(); + }, + + getSelector : function(spec) { + return $(spec); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e.length > 0 ? e[0].tagName : null; + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + + // this code is a workaround for the case that the element being dragged has a margin set on it. jquery UI passes + // in the wrong offset if the element has a margin (it doesn't take the margin into account). the getBoundingClientRect + // method, which is in pretty much all browsers now, reports the right numbers. but it introduces a noticeable lag, which + // i don't like. + + /*if ( getBoundingClientRectSupported ) { + var r = eventArgs[1].helper[0].getBoundingClientRect(); + return { left : r.left, top: r.top }; + } else {*/ + if (eventArgs.length == 1) { + ret = { left: eventArgs[0].pageX, top:eventArgs[0].pageY }; + } + else { + var ui = eventArgs[1], _offset = ui.offset; + ret = _offset || ui.absolutePosition; + } + return ret; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options) { + options = options || {}; + // remove helper directive if present. + options.helper = null; + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.draggable(options); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.droppable(options); + }, + + isAlreadyDraggable : function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return el.hasClass("ui-draggable"); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el[0].className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.removeClass(el[0], clazz); + } + } + catch (e) { + // SVGAnimatedString not supported; no problem. + } + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).remove(); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * sets the drag scope. probably time for a setDragOption method (roll this and the one above together) + * @param el + * @param scope + */ + setDragScope : function(el, scope) { + el.draggable("option", "scope", scope); + }, + + setOffset : function(el, o) { + jsPlumb.CurrentLibrary.getElementObject(el).offset(o); + }, + + /** + * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself. + * the other libraries do not. yui, in fact, cannot even pass an original event. we have to pull out stuff + * from the originalEvent to put in an options object for YUI. + * @param el + * @param event + * @param originalEvent + */ + trigger : function(el, event, originalEvent) { + //originalEvent.stopPropagation(); + //jsPlumb.CurrentLibrary.getElementObject(el).trigger(originalEvent); + var h = jQuery._data(jsPlumb.CurrentLibrary.getElementObject(el)[0], "handle"); + h(originalEvent); + //originalEvent.stopPropagation(); + }, + + /** + * event unbinding wrapper. it just so happens that jQuery uses 'unbind' also. yui3, for example, + * uses..something else. + */ + unbind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.unbind(event, callback); + } + }; + + $(document).ready(jsPlumb.init); + +})(jQuery); + diff --git a/archive/1.3.5/jquery.jsPlumb-1.3.5-all-min.js b/archive/1.3.5/jquery.jsPlumb-1.3.5-all-min.js new file mode 100644 index 000000000..47725f9b6 --- /dev/null +++ b/archive/1.3.5/jquery.jsPlumb-1.3.5-all-min.js @@ -0,0 +1 @@ +(function(){var w=!!document.createElement("canvas").getContext,e=!!window.SVGAngle||document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1"),a=!(w|e);var p=function(F,G,D,J){var I=function(M,L){if(M===L){return true}else{if(typeof M=="object"&&typeof L=="object"){var N=true;for(var K in M){if(!I(M[K],L[K])){N=false;break}}for(var K in L){if(!I(L[K],M[K])){N=false;break}}return N}}};for(var H=+D||0,E=F.length;H-1){E.splice(D,1)}return D!=-1},r=function(F,E,D){if(i(F,D)==-1){F.push(E)}};if(!window.console){window.console={time:function(){},timeEnd:function(){},group:function(){},groupEnd:function(){},log:function(){}}}var l=function(G,E,F){var D=G[E];if(D==null){D=[],G[E]=D}D.push(F);return D},u=null,d=function(D,E){return n.CurrentLibrary.getAttribute(A(D),E)},f=function(E,F,D){n.CurrentLibrary.setAttribute(A(E),F,D)},x=function(E,D){n.CurrentLibrary.addClass(A(E),D)},k=function(E,D){return n.CurrentLibrary.hasClass(A(E),D)},m=function(E,D){n.CurrentLibrary.removeClass(A(E),D)},A=function(D){return n.CurrentLibrary.getElementObject(D)},s=function(D){return n.CurrentLibrary.getOffset(A(D))},b=function(D){return n.CurrentLibrary.getSize(A(D))},z=true,o=function(){if(z&&typeof console!="undefined"){try{var E=arguments[arguments.length-1];console.log(E)}catch(D){}}},C=function(D){if(z&&typeof console!="undefined"){console.group(D)}},h=function(D){if(z&&typeof console!="undefined"){console.groupEnd(D)}},B=function(D){if(z&&typeof console!="undefined"){console.time(D)}},t=function(D){if(z&&typeof console!="undefined"){console.timeEnd(D)}};EventGenerator=function(){var F={},E=this;var D=["ready"];this.bind=function(G,H){l(F,G,H)};this.fire=function(I,J,G){if(F[I]){for(var H=0;H1){for(var T=0;T=0?N.overlays[O]:null};this.hideOverlay=function(P){var O=N.getOverlay(P);if(O){O.hide()}};this.showOverlay=function(P){var O=N.getOverlay(P);if(O){O.show()}};this.removeAllOverlays=function(){N.overlays.splice(0,N.overlays.length);N.repaint()};this.removeOverlay=function(P){var O=D(P);if(O!=-1){var Q=N.overlays[O];Q.cleanup();N.overlays.splice(O,1)}};this.removeOverlays=function(){for(var O=0;O0){try{for(var a7=0;a70?p(bj,bi)!=-1:true},ba=bd.length>1?{}:[],bg=function(bj,bk){if(bd.length>1){var bi=ba[bj];if(bi==null){bi=[];ba[bj]=bi}bi.push(bk)}else{ba.push(bk)}};for(var a9 in aD){if(a7(bd,a9)){for(var a8=0;a8=4)?[bb[2],bb[3]]:[0,0],offsets:(bb.length==6)?[bb[4],bb[5]]:[0,0],elementId:a8};a9=new Q(ba);a9.clone=function(){return new Q(ba)}}}}}if(!a9.id){a9.id="anchor_"+Z()}return a9};this.makeAnchors=function(a9,a7,a6){var ba=[];for(var a8=0;a80?bg[0]:null,bb=bg.length>0?0:-1,bf=this,ba=function(bj,bh,bn,bm,bi){var bl=bm[0]+(bj.x*bi[0]),bk=bm[1]+(bj.y*bi[1]);return Math.sqrt(Math.pow(bh-bl,2)+Math.pow(bn-bk,2))},a6=a7||function(br,bi,bj,bk,bh){var bm=bj[0]+(bk[0]/2),bl=bj[1]+(bk[1]/2);var bo=-1,bq=Infinity;for(var bn=0;bn=a9.left)||(bc.left<=a9.right&&bc.right>=a9.right)||(bc.left<=a9.left&&bc.right>=a9.right)||(a9.left<=bc.left&&a9.right>=bc.right)),bh=((bc.top<=a9.top&&bc.bottom>=a9.top)||(bc.top<=a9.bottom&&bc.bottom>=a9.bottom)||(bc.top<=a9.top&&bc.bottom>=a9.bottom)||(a9.top<=bc.top&&a9.bottom>=bc.bottom));if(!(bb||bh)){var be=null,a8=false,a6=false,bd=null;if(a9.left>bc.left&&a9.top>bc.top){be=["right","top"]}else{if(a9.left>bc.left&&bc.top>a9.top){be=["top","left"]}else{if(a9.leftbc.top){be=["left","top"]}}}}return{orientation:K.DIAGONAL,a:be,theta:a7,theta2:ba}}else{if(bb){return{orientation:K.HORIZONTAL,a:bc.topa6[0]?1:-1},R=function(a6){return function(a8,a7){var a9=true;if(a6){if(a8[0][0]a7[0][1]}}else{if(a8[0][0]>a7[0][0]){a9=true}else{a9=a8[0][1]>a7[0][1]}}return a9===false?-1:1}},G=function(a7,a6){var a9=a7[0][0]<0?-Math.PI-a7[0][0]:Math.PI-a7[0][0],a8=a6[0][0]<0?-Math.PI-a6[0][0]:Math.PI-a6[0][0];if(a9>a8){return 1}else{return a7[0][1]>a6[0][1]?1:-1}},aF={top:aL,right:R(true),bottom:R(true),left:G},ac=function(a6,a7){return a6.sort(a7)},aa=function(a7,a6){var a9=U[a7],ba=X[a7],a8=function(bg,bn,bc,bf,bl,bk){if(bf.length>0){var bj=ac(bf,aF[bg]),bh=bg==="right"||bg==="top",bb=aI(bg,bn,bc,bj,bl,bk,bh);var bo=function(br,bq){var bp=a0([bq[0],bq[1]],br.canvas);Y[br.id]=[bp[0],bp[1],bq[2],bq[3]]};for(var bd=0;bd-1){bc.splice(bd,1)}else{y(a6[be.elementId],function(bf){return bf.id==be.id})}};this.clearFor=function(bd){delete a6[bd];a6[bd]=[]};var ba=function(bx,bk,bs,bh,bn,bo,bq,bm,bz,bp,bg,bw){var bu=-1,bf=-1,bi=bh.endpoints[bq],br=bi.id,bl=[1,0][bq],bd=[[bk,bs],bh,bn,bo,br],be=bx[bz],by=bi._continuousAnchorEdge?bx[bi._continuousAnchorEdge]:null;if(by){var bv=i(by,function(bA){return bA[4]==br});if(bv!=-1){by.splice(bv,1);for(var bt=0;bt0){bp.connections[0].setHover(bC,false)}else{bp.setHover(bC)}};_bindListeners(bp.endpoint,bp,bB);this.setPaintStyle(bA.paintStyle||bA.style||aZ.Defaults.EndpointStyle||n.Defaults.EndpointStyle,true);this.setHoverPaintStyle(bA.hoverPaintStyle||aZ.Defaults.EndpointHoverStyle||n.Defaults.EndpointHoverStyle,true);this.paintStyleInUse=this.paintStyle;this.connectorStyle=bA.connectorStyle;this.connectorHoverStyle=bA.connectorHoverStyle;this.connectorOverlays=bA.connectorOverlays;this.connector=bA.connector;this.connectorTooltip=bA.connectorTooltip;this.isSource=bA.isSource||false;this.isTarget=bA.isTarget||false;var bu=bA.maxConnections||aZ.Defaults.MaxConnections;this.getAttachedElements=function(){return bp.connections};this.canvas=this.endpoint.canvas;this.connections=bA.connections||[];this.scope=bA.scope||I;this.timestamp=null;bp.isReattach=bA.reattach||false;bp.connectionsDetachable=aZ.Defaults.ConnectionsDetachable;if(bA.connectionsDetachable===false||bA.detachable===false){bp.connectionsDetachable=false}var bk=bA.dragAllowedWhenFull||true;this.computeAnchor=function(bC){return bp.anchor.compute(bC)};this.addConnection=function(bC){bp.connections.push(bC)};this.detach=function(bC,bG,bD,bK){var bJ=i(bp.connections,function(bM){return bM.id==bC.id}),bI=false;bK=(bK!==false);if(bJ>=0){if(bD||bC._forceDetach||bC.isDetachable()||bC.isDetachAllowed(bC)){var bL=bC.endpoints[0]==bp?bC.endpoints[1]:bC.endpoints[0];if(bD||bC._forceDetach||(bp.isDetachAllowed(bC))){bp.connections.splice(bJ,1);if(!bG){bL.detach(bC,true,bD);if(bC.endpointsToDeleteOnDetach){for(var bH=0;bH0){bp.detach(bp.connections[0],false,true,bC)}};this.detachFrom=function(bE,bD){var bF=[];for(var bC=0;bC=0){bp.connections.splice(bC,1)}};this.getElement=function(){return bo};this.setElement=function(bE){var bG=D(bE);y(az[bf],function(bH){return bH.id==bp.id});bo=A(bE);bf=D(bo);bp.elementId=bf;var bF=ai({source:bG}),bD=a9.getParent(bp.canvas);a9.removeElement(bp.canvas,bD);a9.appendElement(bp.canvas,bF);for(var bC=0;bC0){var bO=bl(bF.elementWithPrecedence),bQ=bO.endpoints[0]==bp?1:0,bH=bQ==0?bO.sourceId:bO.targetId,bN=X[bH],bP=U[bH];bE.txy=[bN.left,bN.top];bE.twh=bP;bE.tElement=bO.endpoints[bQ]}bI=bp.anchor.compute(bE)}var bM=bm.compute(bI,bp.anchor.getOrientation(bm),bp.paintStyleInUse,bG||bp.paintStyleInUse);bm.paint(bM,bp.paintStyleInUse,bp.anchor);bp.timestamp=bL;for(var bJ=0;bJE[0]){return(D[1]>E[1])?2:1}else{return(D[1]>E[1])?3:4}},segmentMultipliers:[null,[1,-1],[1,1],[-1,1],[-1,-1]],inverseSegmentMultipliers:[null,[-1,-1],[-1,1],[1,1],[1,-1]],pointOnLine:function(D,H,E){var G=n.util.gradient(D,H),L=n.util.segment(D,H),K=E>0?n.util.segmentMultipliers[L]:n.util.inverseSegmentMultipliers[L],F=Math.atan(G),I=Math.abs(E*Math.sin(F))*K[1],J=Math.abs(E*Math.cos(F))*K[0];return{x:D.x+J,y:D.y+I}},perpendicularLineTo:function(F,G,H){var E=n.util.gradient(F,G),I=Math.atan(-1/E),J=H/2*Math.sin(I),D=H/2*Math.cos(I);return[{x:G.x+D,y:G.y+J},{x:G.x-D,y:G.y-J}]}};var q=function(D,I,F,E,H,G){return function(K){K=K||{};var J=K.jsPlumbInstance.makeAnchor([D,I,F,E,0,0],K.elementId,K.jsPlumbInstance);J.type=H;if(G){G(J,K)}return J}};n.Anchors.TopCenter=q(0.5,0,0,-1,"TopCenter");n.Anchors.BottomCenter=q(0.5,1,0,1,"BottomCenter");n.Anchors.LeftMiddle=q(0,0.5,-1,0,"LeftMiddle");n.Anchors.RightMiddle=q(1,0.5,1,0,"RightMiddle");n.Anchors.Center=q(0.5,0.5,0,0,"Center");n.Anchors.TopRight=q(1,0,0,-1,"TopRight");n.Anchors.BottomRight=q(1,1,0,1,"BottomRight");n.Anchors.TopLeft=q(0,0,0,-1,"TopLeft");n.Anchors.BottomLeft=q(0,1,0,1,"BottomLeft");n.Defaults.DynamicAnchors=function(D){return D.jsPlumbInstance.makeAnchors(["TopCenter","RightMiddle","BottomCenter","LeftMiddle"],D.elementId,D.jsPlumbInstance)};n.Anchors.AutoDefault=function(E){var D=E.jsPlumbInstance.makeDynamicAnchor(n.Defaults.DynamicAnchors(E));D.type="AutoDefault";return D};n.Anchors.Assign=q(0,0,0,0,"Assign",function(E,F){var D=F.position||"Fixed";E.positionFinder=D.constructor==String?F.jsPlumbInstance.AnchorPositionFinders[D]:D;E.constructorParams=F});n.Anchors.Continuous=function(D){return D.jsPlumbInstance.continuousAnchorFactory.get(D)};n.AnchorPositionFinders={Fixed:function(G,E,F,D){return[(G.left-E.left)/F[0],(G.top-E.top)/F[1]]},Grid:function(D,M,H,E){var L=D.left-M.left,K=D.top-M.top,J=H[0]/(E.constructorParams.grid[0]),I=H[1]/(E.constructorParams.grid[1]),G=Math.floor(L/J),F=Math.floor(K/I);return[((G*J)+(J/2))/H[0],((F*I)+(I/2))/H[1]]}}})();(function(){jsPlumb.DOMElementComponent=function(c){jsPlumb.jsPlumbUIComponent.apply(this,arguments);this.mousemove=this.dblclick=this.click=this.mousedown=this.mouseup=function(d){}};jsPlumb.Connectors.Straight=function(){this.type="Straight";var r=this,i=null,e,k,p,n,l,f,q,h,g,d,c,o,m;this.compute=function(A,J,s,z,F,t,D,v){var I=Math.abs(A[0]-J[0]),C=Math.abs(A[1]-J[1]),B=0.45*I,u=0.45*C;I*=1.9;C*=1.9;var G=Math.min(A[0],J[0])-B;var E=Math.min(A[1],J[1])-u;var H=Math.max(2*D,v);if(In){n=x}if(A<0){f+=A;var C=Math.abs(A);n+=C;p[0]+=C;l+=C;d+=C;o[0]+=C}var K=Math.min(h,c),I=Math.min(p[1],o[1]),w=Math.min(K,I),B=Math.max(h,c),z=Math.max(p[1],o[1]),t=Math.max(B,z);if(t>i){i=t}if(w<0){e+=w;var y=Math.abs(w);i+=y;p[1]+=y;h+=y;c+=y;o[1]+=y}if(G&&n=t){r=u;s=(t-m[u][0])/c[u];break}}return{segment:k[r],proportion:s,index:r}};this.compute=function(Q,ae,r,J,ao,D,O,I,aj,ag){k=[];o=0;c=[];f=ae[0]an?0:1,Y=[1,0][W];G=[];ap=[];G[W]=Q[W]>ae[W]?-1:1;ap[W]=Q[W]>ae[W]?1:-1;G[Y]=0;ap[Y]=0}if(af2*d,R=Math.abs(v-ak)>2*d,ab=T+((E-T)/2),Z=S+((C-S)/2),H=((G[0]*ap[0])+(G[1]*ap[1])),V=H==-1,X=H==0,s=H==1;ad-=B;ac-=z;n=[ad,ac,af,an,A,v,al,ak];var ai=[];g(T,S,A,v,al,ak);var L=G[0]==0?"y":"x",F=V?"opposite":s?"orthogonal":"perpendicular",t=jsPlumb.util.segment([A,v],[al,ak]),aa=G[L=="x"?0:1]==-1,K={x:[null,4,3,2,1],y:[null,2,1,4,3]};if(aa){t=K[L][t]}var N=function(aq,y,w,x){return aq+(y*((1-w)*x)+d)},u={oppositex:function(){if(r.elementId==J.elementId){var w=S+((1-ao.y)*aj.height)+d;return[[T,w],[E,w]]}else{if(P&&(t==1||t==2)){return[[ab,v],[ab,ak]]}else{return[[T,Z],[E,Z]]}}},orthogonalx:function(){if(t==1||t==2){return[[E,S]]}else{return[[T,C]]}},perpendicularx:function(){var w=(ak+v)/2;if((t==1&&ap[1]==1)||(t==2&&ap[1]==-1)){if(Math.abs(al-A)>d){return[[E,S]]}else{return[[T,S],[T,w],[E,w]]}}else{if((t==3&&ap[1]==-1)||(t==4&&ap[1]==1)){return[[T,w],[E,w]]}else{if((t==3&&ap[1]==1)||(t==4&&ap[1]==-1)){return[[T,C]]}else{if((t==1&&ap[1]==-1)||(t==2&&ap[1]==1)){if(Math.abs(al-A)>d){return[[ab,S],[ab,C]]}else{return[[T,C]]}}}}}},oppositey:function(){if(r.elementId==J.elementId){var w=T+((1-ao.x)*aj.width)+d;return[[w,S],[w,C]]}else{if(R&&(t==2||t==3)){return[[A,Z],[al,Z]]}else{return[[ab,S],[ab,C]]}}},orthogonaly:function(){if(t==2||t==3){return[[T,C]]}else{return[[E,S]]}},perpendiculary:function(){var w=(al+A)/2;if((t==2&&ap[0]==-1)||(t==3&&ap[0]==1)){if(Math.abs(al-A)>d){return[[T,C]]}else{return[[T,Z],[E,Z]]}}else{if((t==1&&ap[0]==-1)||(t==4&&ap[0]==1)){var w=(al+A)/2;return[[w,S],[w,C]]}else{if((t==1&&ap[0]==1)||(t==4&&ap[0]==-1)){return[[E,S]]}else{if((t==2&&ap[0]==1)||(t==3&&ap[0]==-1)){if(Math.abs(ak-v)>d){return[[T,Z],[E,Z]]}else{return[[E,S]]}}}}}}};var M=u[F+L];var ah=M();if(ah){for(var am=0;amu[0]?u[0]+((1-y)*t)-z:u[2]+(y*t)+z,y:r==0?u[3]:u[3]>u[1]?u[1]+((1-y)*t)-z:u[3]+(y*t)+z,segmentInfo:w};return x}};jsPlumb.Endpoints.Dot=function(d){this.type="Dot";var c=this;d=d||{};this.radius=d.radius||10;this.defaultOffset=0.5*this.radius;this.defaultInnerRadius=this.radius/3;this.compute=function(i,f,l,h){var g=l.radius||c.radius,e=i[0]-g,k=i[1]-g;return[e,k,g*2,g*2,g]}};jsPlumb.Endpoints.Rectangle=function(d){this.type="Rectangle";var c=this;d=d||{};this.width=d.width||20;this.height=d.height||20;this.compute=function(k,g,m,i){var h=m.width||c.width,f=m.height||c.height,e=k[0]-(h/2),l=k[1]-(f/2);return[e,l,h,f]}};var a=function(e){jsPlumb.DOMElementComponent.apply(this,arguments);var c=this;var d=[];this.getDisplayElements=function(){return d};this.appendDisplayElement=function(f){d.push(f)}};jsPlumb.Endpoints.Image=function(g){this.type="Image";a.apply(this,arguments);var l=this,f=false,e=g.width,d=g.height,i=null,c=g.endpoint;this.img=new Image();l.ready=false;this.img.onload=function(){l.ready=true;e=e||l.img.width;d=d||l.img.height;if(i){i(l)}};c.setImage=function(m,o){var n=m.constructor==String?m:m.src;i=o;l.img.src=m};c.setImage(g.src||g.url,g.onload);this.compute=function(o,m,p,n){l.anchorPoint=o;if(l.ready){return[o[0]-e/2,o[1]-d/2,e,d]}else{return[0,0,0,0]}};l.canvas=document.createElement("img"),f=false;l.canvas.style.margin=0;l.canvas.style.padding=0;l.canvas.style.outline=0;l.canvas.style.position="absolute";var h=g.cssClass?" "+g.cssClass:"";l.canvas.className=jsPlumb.endpointClass+h;if(e){l.canvas.setAttribute("width",e)}if(d){l.canvas.setAttribute("height",d)}jsPlumb.appendElement(l.canvas,g.parent);l.attachListeners(l.canvas,l);var k=function(p,o,n){if(!f){l.canvas.setAttribute("src",l.img.src);f=true}var m=l.anchorPoint[0]-(e/2),q=l.anchorPoint[1]-(d/2);jsPlumb.sizeCanvas(l.canvas,m,q,e,d)};this.paint=function(o,n,m){if(l.ready){k(o,n,m)}else{window.setTimeout(function(){l.paint(o,n,m)},200)}}};jsPlumb.Endpoints.Blank=function(d){var c=this;this.type="Blank";a.apply(this,arguments);this.compute=function(g,e,h,f){return[g[0],g[1],10,0]};c.canvas=document.createElement("div");c.canvas.style.display="block";c.canvas.style.width="1px";c.canvas.style.height="1px";c.canvas.style.background="transparent";c.canvas.style.position="absolute";c.canvas.className=c._jsPlumb.endpointClass;jsPlumb.appendElement(c.canvas,d.parent);this.paint=function(g,f,e){jsPlumb.sizeCanvas(c.canvas,g[0],g[1],g[2],g[3])}};jsPlumb.Endpoints.Triangle=function(c){this.type="Triangle";c=c||{};c.width=c.width||55;c.height=c.height||55;this.width=c.width;this.height=c.height;this.compute=function(i,f,l,h){var g=l.width||self.width,e=l.height||self.height,d=i[0]-(g/2),k=i[1]-(e/2);return[d,k,g,e]}};var b=function(e){var d=true,c=this;this.isAppendedAtTopLevel=true;this.component=e.component;this.loc=e.location==null?0.5:e.location;this.endpointLoc=e.endpointLocation==null?[0.5,0.5]:e.endpointLocation;this.setVisible=function(f){d=f;c.component.repaint()};this.isVisible=function(){return d};this.hide=function(){c.setVisible(false)};this.show=function(){c.setVisible(true)};this.incrementLocation=function(f){c.loc+=f;c.component.repaint()};this.setLocation=function(f){c.loc=f;c.component.repaint()};this.getLocation=function(){return c.loc}};jsPlumb.Overlays.Arrow=function(g){this.type="Arrow";b.apply(this,arguments);this.isAppendedAtTopLevel=false;g=g||{};var d=this;this.length=g.length||20;this.width=g.width||20;this.id=g.id;var f=(g.direction||1)<0?-1:1,e=g.paintStyle||{lineWidth:1},c=g.foldback||0.623;this.computeMaxSize=function(){return d.width*1.5};this.cleanup=function(){};this.draw=function(i,x,s){var m,t,h,n,l;if(i.pointAlongPathFrom){if(d.loc==1){m=i.pointOnPath(d.loc);t=i.pointAlongPathFrom(d.loc,-1);h=jsPlumb.util.pointOnLine(m,t,d.length)}else{if(d.loc==0){h=i.pointOnPath(d.loc);t=i.pointAlongPathFrom(d.loc,1);m=jsPlumb.util.pointOnLine(h,t,d.length)}else{m=i.pointAlongPathFrom(d.loc,f*d.length/2),t=i.pointOnPath(d.loc),h=jsPlumb.util.pointOnLine(m,t,d.length)}}n=jsPlumb.util.perpendicularLineTo(m,h,d.width);l=jsPlumb.util.pointOnLine(m,h,c*d.length);var w=Math.min(m.x,n[0].x,n[1].x),q=Math.max(m.x,n[0].x,n[1].x),v=Math.min(m.y,n[0].y,n[1].y),p=Math.max(m.y,n[0].y,n[1].y);var o={hxy:m,tail:n,cxy:l},r=e.strokeStyle||x.strokeStyle,u=e.fillStyle||x.strokeStyle,k=e.lineWidth||x.lineWidth;d.paint(i,o,k,r,u,s);return[w,q,v,p]}else{return[0,0,0,0]}}};jsPlumb.Overlays.PlainArrow=function(d){d=d||{};var c=jsPlumb.extend(d,{foldback:1});jsPlumb.Overlays.Arrow.call(this,c);this.type="PlainArrow"};jsPlumb.Overlays.Diamond=function(e){e=e||{};var c=e.length||40,d=jsPlumb.extend(e,{length:c/2,foldback:2});jsPlumb.Overlays.Arrow.call(this,d);this.type="Diamond"};jsPlumb.Overlays.Label=function(i){this.type="Label";jsPlumb.DOMElementComponent.apply(this,arguments);b.apply(this,arguments);this.labelStyle=i.labelStyle||jsPlumb.Defaults.LabelStyle;this.id=i.id;this.cachedDimensions=null;var e=i.label||"",c=this,f=false,k=document.createElement("div"),g=null;k.style.position="absolute";var d=i._jsPlumb.overlayClass+" "+(c.labelStyle.cssClass?c.labelStyle.cssClass:i.cssClass?i.cssClass:"");k.className=d;jsPlumb.appendElement(k,i.component.parent);jsPlumb.getId(k);c.attachListeners(k,c);c.canvas=k;var h=c.setVisible;c.setVisible=function(l){h(l);k.style.display=l?"block":"none"};this.getElement=function(){return k};this.cleanup=function(){if(k!=null){jsPlumb.CurrentLibrary.removeElement(k)}};this.setLabel=function(m){e=m;g=null;c.component.repaint()};this.getLabel=function(){return e};this.paint=function(l,n,m){if(!f){l.appendDisplayElement(k);c.attachListeners(k,l);f=true}k.style.left=(m[0]+n.minx)+"px";k.style.top=(m[1]+n.miny)+"px"};this.getTextDimensions=function(){if(typeof e=="function"){var l=e(c);k.innerHTML=l.replace(/\r\n/g,"
")}else{if(g==null){g=e;k.innerHTML=g.replace(/\r\n/g,"
")}}var n=jsPlumb.CurrentLibrary.getElementObject(k),m=jsPlumb.CurrentLibrary.getSize(n);return{width:m[0],height:m[1]}};this.computeMaxSize=function(l){var m=c.getTextDimensions(l);return m.width?Math.max(m.width,m.height)*1.5:0};this.draw=function(m,n,o){var q=c.getTextDimensions(m);if(q.width!=null){var p={x:0,y:0};if(m.pointOnPath){p=m.pointOnPath(c.loc)}else{var l=c.loc.constructor==Array?c.loc:c.endpointLoc;p={x:l[0]*o[2],y:l[1]*o[3]}}minx=p.x-(q.width/2),miny=p.y-(q.height/2);c.paint(m,{minx:minx,miny:miny,td:q,cxy:p},o);return[minx,minx+q.width,miny,miny+q.height]}else{return[0,0,0,0]}};this.reattachListeners=function(l){if(k){c.reattachListenersForElement(k,c,l)}}};jsPlumb.Overlays.GuideLines=function(){var c=this;c.length=50;c.lineWidth=5;this.type="GuideLines";b.apply(this,arguments);jsPlumb.jsPlumbUIComponent.apply(this,arguments);this.draw=function(e,l,k){var i=e.pointAlongPathFrom(c.loc,c.length/2),h=e.pointOnPath(c.loc),g=jsPlumb.util.pointOnLine(i,h,c.length),f=jsPlumb.util.perpendicularLineTo(i,g,40),d=jsPlumb.util.perpendicularLineTo(g,i,20);c.paint(e,[i,g,f,d],c.lineWidth,"red",null,k);return[Math.min(i.x,g.x),Math.min(i.y,g.y),Math.max(i.x,g.x),Math.max(i.y,g.y)]};this.computeMaxSize=function(){return 50};this.cleanup=function(){}}})();(function(){var c=function(e,g,d,f){this.m=(f-g)/(d-e);this.b=-1*((this.m*e)-g);this.rectIntersect=function(q,p,s,o){var n=[];var k=(p-this.b)/this.m;if(k>=q&&k<=(q+s)){n.push([k,(this.m*k)+this.b])}var t=(this.m*(q+s))+this.b;if(t>=p&&t<=(p+o)){n.push([(t-this.b)/this.m,t])}var k=((p+o)-this.b)/this.m;if(k>=q&&k<=(q+s)){n.push([k,(this.m*k)+this.b])}var t=(this.m*q)+this.b;if(t>=p&&t<=(p+o)){n.push([(t-this.b)/this.m,t])}if(n.length==2){var m=(n[0][0]+n[1][0])/2,l=(n[0][1]+n[1][1])/2;n.push([m,l]);var i=m<=q+(s/2)?-1:1,r=l<=p+(o/2)?-1:1;n.push([i,r]);return n}return null}},a=function(e,g,d,f){if(e<=d&&f<=g){return 1}else{if(e<=d&&g<=f){return 2}else{if(d<=e&&f>=g){return 3}}}return 4},b=function(g,f,i,e,h,m,l,d,k){if(d<=k){return[g,f]}if(i==1){if(e[3]<=0&&h[3]>=1){return[g+(e[2]<0.5?-1*m:m),f]}else{if(e[2]>=1&&h[2]<=0){return[g,f+(e[3]<0.5?-1*l:l)]}else{return[g+(-1*m),f+(-1*l)]}}}else{if(i==2){if(e[3]>=1&&h[3]<=0){return[g+(e[2]<0.5?-1*m:m),f]}else{if(e[2]>=1&&h[2]<=0){return[g,f+(e[3]<0.5?-1*l:l)]}else{return[g+(1*m),f+(-1*l)]}}}else{if(i==3){if(e[3]>=1&&h[3]<=0){return[g+(e[2]<0.5?-1*m:m),f]}else{if(e[2]<=0&&h[2]>=1){return[g,f+(e[3]<0.5?-1*l:l)]}else{return[g+(-1*m),f+(-1*l)]}}}else{if(i==4){if(e[3]<=0&&h[3]>=1){return[g+(e[2]<0.5?-1*m:m),f]}else{if(e[2]<=0&&h[2]>=1){return[g,f+(e[3]<0.5?-1*l:l)]}else{return[g+(1*m),f+(-1*l)]}}}}}}};jsPlumb.Connectors.StateMachine=function(l){var s=this,n=null,o,m,g,e,p=[],d=l.curviness||10,k=l.margin||5,q=l.proximityLimit||80,f=l.orientation&&l.orientation=="clockwise",i=l.loopbackRadius||25,h=false;this.type="StateMachine";l=l||{};this.compute=function(ab,F,U,G,aa,u,t,S){var O=Math.abs(ab[0]-F[0]),W=Math.abs(ab[1]-F[1]),Q=0.45*O,Z=0.45*W;O*=1.9;W*=1.9;t=t||1;var M=Math.min(ab[0],F[0])-Q,K=Math.min(ab[1],F[1])-Z;if(U.elementId!=G.elementId){h=false;o=ab[0]0&&v<1){v=1-v}var w=(v*2*Math.PI)+(Math.PI/2),u=n[4]+(n[6]*Math.cos(w)),t=n[5]+(n[6]*Math.sin(w));return{x:u,y:t}}else{return jsBezier.pointOnCurve(r(),v)}};this.gradientAtPoint=function(t){if(h){return Math.atan(t*2*Math.PI)}else{return jsBezier.gradientAtPoint(r(),t)}};this.pointAlongPathFrom=function(v,z){if(h){if(v>0&&v<1){v=1-v}var w=2*Math.PI*n[6],y=z/w*2*Math.PI,x=(v*2*Math.PI)-y+(Math.PI/2),u=n[4]+(n[6]*Math.cos(x)),t=n[5]+(n[6]*Math.sin(x));return{x:u,y:t}}return jsBezier.pointAlongCurveFrom(r(),v,z)}};jsPlumb.Connectors.canvas.StateMachine=function(f){f=f||{};var d=this,g=f.drawGuideline||true,e=f.avoidSelector;jsPlumb.Connectors.StateMachine.apply(this,arguments);jsPlumb.CanvasConnector.apply(this,arguments);this._paint=function(l){if(l.length==10){d.ctx.beginPath();d.ctx.moveTo(l[4],l[5]);d.ctx.quadraticCurveTo(l[8],l[9],l[6],l[7]);d.ctx.stroke()}else{d.ctx.save();d.ctx.beginPath();var k=0,i=2*Math.PI,h=l[7];d.ctx.arc(l[4],l[5],l[6],0,i,h);d.ctx.stroke();d.ctx.closePath();d.ctx.restore()}};this.createGradient=function(i,h){return h.createLinearGradient(i[4],i[5],i[6],i[7])}};jsPlumb.Connectors.svg.StateMachine=function(){var d=this;jsPlumb.Connectors.StateMachine.apply(this,arguments);jsPlumb.SvgConnector.apply(this,arguments);this.getPath=function(e){if(e.length==10){return"M "+e[4]+" "+e[5]+" C "+e[8]+" "+e[9]+" "+e[8]+" "+e[9]+" "+e[6]+" "+e[7]}else{return"M"+(e[8]+4)+" "+e[9]+" A "+e[6]+" "+e[6]+" 0 1,0 "+(e[8]-4)+" "+e[9]}}};jsPlumb.Connectors.vml.StateMachine=function(){jsPlumb.Connectors.StateMachine.apply(this,arguments);jsPlumb.VmlConnector.apply(this,arguments);var d=jsPlumb.vml.convertValue;this.getPath=function(k){if(k.length==10){return"m"+d(k[4])+","+d(k[5])+" c"+d(k[8])+","+d(k[9])+","+d(k[8])+","+d(k[9])+","+d(k[6])+","+d(k[7])+" e"}else{var h=d(k[8]-k[6]),g=d(k[9]-(2*k[6])),f=h+d(2*k[6]),e=g+d(2*k[6]),l=h+","+g+","+f+","+e;var i="ar "+l+","+d(k[8])+","+d(k[9])+","+d(k[8])+","+d(k[9])+" e";return i}}}})();(function(){var k={"stroke-linejoin":"joinstyle",joinstyle:"joinstyle",endcap:"endcap",miterlimit:"miterlimit"};if(document.createStyleSheet){document.createStyleSheet().addRule(".jsplumb_vml","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:textbox","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:oval","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:rect","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:stroke","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:shape","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:group","behavior:url(#default#VML);position:absolute;");document.namespaces.add("jsplumb","urn:schemas-microsoft-com:vml")}jsPlumb.vml={};var b=1000,d={},h=function(q,p){var s=jsPlumb.getId(q),r=d[s];if(!r){r=o("group",[0,0,b,b],{"class":p});r.style.backgroundColor="red";d[s]=r;jsPlumb.appendElement(r,q)}return r},c=function(q,r){for(var p in r){q[p]=r[p]}},o=function(p,r,s){s=s||{};var q=document.createElement("jsplumb:"+p);q.className=(s["class"]?s["class"]+" ":"")+"jsplumb_vml";n(q,r);c(q,s);return q},n=function(q,p){q.style.left=p[0]+"px";q.style.top=p[1]+"px";q.style.width=p[2]+"px";q.style.height=p[3]+"px";q.style.position="absolute"},i=jsPlumb.vml.convertValue=function(p){return Math.floor(p*b)},e=function(s,q,r,p){if("transparent"===q){p.setOpacity(r,"0.0")}else{p.setOpacity(r,"1.0")}},g=function(t,p,w){var s={};if(p.strokeStyle){s.stroked="true";var x=jsPlumb.util.convertStyle(p.strokeStyle,true);s.strokecolor=x;e(s,x,"stroke",w);s.strokeweight=p.lineWidth+"px"}else{s.stroked="false"}if(p.fillStyle){s.filled="true";var q=jsPlumb.util.convertStyle(p.fillStyle,true);s.fillcolor=q;e(s,q,"fill",w)}else{s.filled="false"}if(p.dashstyle){if(w.strokeNode==null){w.strokeNode=o("stroke",[0,0,0,0],{dashstyle:p.dashstyle});t.appendChild(w.strokeNode)}else{w.strokeNode.dashstyle=p.dashstyle}}else{if(p["stroke-dasharray"]&&p.lineWidth){var y=p["stroke-dasharray"].indexOf(",")==-1?" ":",",u=p["stroke-dasharray"].split(y),r="";for(var v=0;v0&&C>0&&u=u&&w[2]<=C&&w[3]>=C)){return true}}var A=q.canvas.getContext("2d").getImageData(parseInt(u),parseInt(C),1,1);return A.data[0]!=0||A.data[1]!=0||A.data[2]!=0||A.data[3]!=0}return false};var p=false,o=false,t=null,s=false,r=function(v,u){return v!=null&&i(v,u)};this.mousemove=function(x){var z=n(x),w=f(x),v=document.elementFromPoint(w[0],w[1]),y=r(v,"_jsPlumb_overlay");var u=d==null&&(r(v,"_jsPlumb_endpoint")||r(v,"_jsPlumb_connector"));if(!p&&u&&q._over(x)){p=true;q.fire("mouseenter",q,x);return true}else{if(p&&(!q._over(x)||!u)&&!y){p=false;q.fire("mouseexit",q,x)}}q.fire("mousemove",q,x)};this.click=function(u){if(p&&q._over(u)&&!s){q.fire("click",q,u)}s=false};this.dblclick=function(u){if(p&&q._over(u)&&!s){q.fire("dblclick",q,u)}s=false};this.mousedown=function(u){if(q._over(u)&&!o){o=true;t=m(a(q.canvas));q.fire("mousedown",q,u)}};this.mouseup=function(u){o=false;q.fire("mouseup",q,u)};this.contextmenu=function(u){if(p&&q._over(u)&&!s){q.fire("contextmenu",q,u)}s=false}};var c=function(p){var o=document.createElement("canvas");jsPlumb.appendElement(o,p.parent);o.style.position="absolute";if(p["class"]){o.className=p["class"]}p._jsPlumb.getId(o,p.uuid);if(p.tooltip){o.setAttribute("title",p.tooltip)}return o};var l=function(p){k.apply(this,arguments);var o=[];this.getDisplayElements=function(){return o};this.appendDisplayElement=function(q){o.push(q)}};var h=jsPlumb.CanvasConnector=function(r){l.apply(this,arguments);var o=function(v,t){p.ctx.save();jsPlumb.extend(p.ctx,t);if(t.gradient){var u=p.createGradient(v,p.ctx);for(var s=0;s0?c[0].tagName:null},getUIPosition:function(c){if(c.length==1){ret={left:c[0].pageX,top:c[0].pageY}}else{var d=c[1],b=d.offset;ret=b||d.absolutePosition}return ret},hasClass:function(c,b){return c.hasClass(b)},initDraggable:function(c,b){b=b||{};b.helper=null;b.scope=b.scope||jsPlumb.Defaults.Scope;c.draggable(b)},initDroppable:function(c,b){b.scope=b.scope||jsPlumb.Defaults.Scope;c.droppable(b)},isAlreadyDraggable:function(b){b=jsPlumb.CurrentLibrary.getElementObject(b);return b.hasClass("ui-draggable")},isDragSupported:function(c,b){return c.draggable},isDropSupported:function(c,b){return c.droppable},removeClass:function(c,b){c=jsPlumb.CurrentLibrary.getElementObject(c);try{if(c[0].className.constructor==SVGAnimatedString){jsPlumb.util.svg.removeClass(c[0],b)}}catch(d){}c.removeClass(b)},removeElement:function(b,c){jsPlumb.CurrentLibrary.getElementObject(b).remove()},setAttribute:function(c,d,b){c.attr(d,b)},setDraggable:function(c,b){c.draggable("option","disabled",!b)},setDragScope:function(c,b){c.draggable("option","scope",b)},setOffset:function(b,c){jsPlumb.CurrentLibrary.getElementObject(b).offset(c)},trigger:function(d,e,b){var c=jQuery._data(jsPlumb.CurrentLibrary.getElementObject(d)[0],"handle");c(b)},unbind:function(b,c,d){b=jsPlumb.CurrentLibrary.getElementObject(b);b.unbind(c,d)}};a(document).ready(jsPlumb.init)})(jQuery);(function(){if("undefined"==typeof Math.sgn){Math.sgn=function(l){return 0==l?0:0q?q=s:sl.location){l.location=0}return i(m,l.location)},nearestPointOnCurve:function(m,l){var n=h(m,l);return{point:a(l,l.length-1,n.location,null,null),location:n.location}},pointOnCurve:c,pointAlongCurveFrom:function(m,l,n){return k(m,l,n).point},perpendicularToCurveAt:function(m,l,n,o){l=k(m,l,null==o?0:o);m=i(m,l.location);o=Math.atan(-1/m);m=n/2*Math.sin(o);n=n/2*Math.cos(o);return[{x:l.point.x+n,y:l.point.y+m},{x:l.point.x-n,y:l.point.y-m}]}}})(); \ No newline at end of file diff --git a/archive/1.3.5/jquery.jsPlumb-1.3.5-all.js b/archive/1.3.5/jquery.jsPlumb-1.3.5-all.js new file mode 100644 index 000000000..88d5c6cde --- /dev/null +++ b/archive/1.3.5/jquery.jsPlumb-1.3.5-all.js @@ -0,0 +1,8267 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the jsPlumb core code. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + /** + * Class:jsPlumb + * The jsPlumb engine, registered as a static object in the window. This object contains all of the methods you will use to + * create and maintain Connections and Endpoints. + */ + + var canvasAvailable = !!document.createElement('canvas').getContext, + svgAvailable = !!window.SVGAngle || document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"), + // TODO what is a good test for VML availability? aside from just assuming its there because nothing else is. + vmlAvailable = !(canvasAvailable | svgAvailable); + + // finds the index of some value in an array, using a deep equals test. + var _findIndex = function(a, v, b, s) { + var _eq = function(o1, o2) { + if (o1 === o2) return true; + else if (typeof o1 == "object" && typeof o2 == "object") { + var same = true; + for ( var propertyName in o1) { + if (!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for ( var propertyName in o2) { + if (!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for ( var i = +b || 0, l = a.length; i < l; i++) { + if (_eq(a[i], v)) + return i; + } + return -1; + }, + _findWithFunction = function(a, f) { + if (a) + for (var i = 0; i < a.length; i++) if (f(a[i])) return i; + return -1; + }, + _removeWithFunction = function(a, f) { + var idx = _findWithFunction(a, f); + if (idx > -1) a.splice(idx, 1); + return idx != -1; + }, + // TODO support insert index + _addWithFunction = function(list, item, hashFunction) { + if (_findWithFunction(list, hashFunction) == -1) list.push(item); + }; + + + // for those browsers that dont have it. they still don't have it! but at least they won't crash. + if (!window.console) + window.console = { time:function(){}, timeEnd:function(){}, group:function(){}, groupEnd:function(){}, log:function(){} }; + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = [], map[key] = l; + } + l.push(value); + return l; + }, + _connectionBeingDragged = null, + _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }, + _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }, + _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }, + _logEnabled = true, + _log = function() { + if (_logEnabled && typeof console != "undefined") { + try { + var msg = arguments[arguments.length - 1]; + console.log(msg); + } + catch (e) {} + } + }, + _group = function(g) { + if (_logEnabled && typeof console != "undefined") console.group(g); + }, + _groupEnd = function(g) { + if (_logEnabled && typeof console != "undefined") console.groupEnd(g); + }, + _time = function(t) { + if (_logEnabled && typeof console != "undefined") console.time(t); + }, + _timeEnd = function(t) { + if (_logEnabled && typeof console != "undefined") console.timeEnd(t); + }; + + /** + * EventGenerator + * Superclass for objects that generate events - jsPlumb extends this, as does jsPlumbUIComponent, which all the UI elements extend. + */ + EventGenerator = function() { + var _listeners = {}, self = this; + + // this is a list of events that should re-throw any errors that occur during their dispatch. as of 1.3.0 this is private to + // jsPlumb, but it seems feasible that people might want to manipulate this list. the thinking is that we don't want event + // listeners to bring down jsPlumb - or do we. i can't make up my mind about this, but i know i want to hear about it if the "ready" + // event fails, because then my page has most likely not initialised. so i have this halfway-house solution. it will be interesting + // to hear what other people think. + var eventsToDieOn = [ "ready" ]; + + /* + * Binds a listener to an event. + * + * Parameters: + * event - name of the event to bind to. + * listener - function to execute. + */ + this.bind = function(event, listener) { + _addToList(_listeners, event, listener); + }; + /* + * Fires an update for the given event. + * + * Parameters: + * event - event to fire + * value - value to pass to the event listener(s). + * originalEvent - the original event from the browser + */ + this.fire = function(event, value, originalEvent) { + if (_listeners[event]) { + for ( var i = 0; i < _listeners[event].length; i++) { + // doing it this way rather than catching and then possibly re-throwing means that an error propagated by this + // method will have the whole call stack available in the debugger. + if (_findIndex(eventsToDieOn, event) != -1) + _listeners[event][i](value, originalEvent); + else { + // for events we don't want to die on, catch and log. + try { + _listeners[event][i](value, originalEvent); + } catch (e) { + _log("jsPlumb: fire failed for event " + event + " : " + e); + } + } + } + } + }; + /* + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + this.clearListeners = function(event) { + if (event) + delete _listeners[event]; + else { + delete _listeners; + _listeners = {}; + } + }; + + this.getListener = function(forEvent) { + return _listeners[forEvent]; + }; + }, + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + _timestamp = function() { return "" + (new Date()).getTime(); }, + + /* + * Class:jsPlumbUIComponent + * Abstract superclass for UI components Endpoint and Connection. Provides the abstraction of paintStyle/hoverPaintStyle, + * and also extends EventGenerator to provide the bind and fire methods. + */ + jsPlumbUIComponent = function(params) { + var self = this, a = arguments, _hover = false, parameters = params.parameters || {}, idPrefix = self.idPrefix, + id = idPrefix + (new Date()).getTime(); + self._jsPlumb = params["_jsPlumb"]; + self.getId = function() { return id; }; + self.tooltip = params.tooltip; + self.hoverClass = params.hoverClass; + + // all components can generate events + EventGenerator.apply(this); + // all components get this clone function. + // TODO issue 116 showed a problem with this - it seems 'a' that is in + // the clone function's scope is shared by all invocations of it, the classic + // JS closure problem. for now, jsPlumb does a version of this inline where + // it used to call clone. but it would be nice to find some time to look + // further at this. + this.clone = function() { + var o = new Object(); + self.constructor.apply(o, a); + return o; + }; + + this.getParameter = function(name) { return parameters[name]; }, + this.getParameters = function() { return parameters; }, + this.setParameter = function(name, value) { parameters[name] = value; }, + this.setParameters = function(p) { parameters = p; }, + this.overlayPlacements = [], + this.paintStyle = null, + this.hoverPaintStyle = null; + + // user can supply a beforeDetach callback, which will be executed before a detach + // is performed; returning false prevents the detach. + var beforeDetach = params.beforeDetach; + this.isDetachAllowed = function(connection) { + var r = true; + if (beforeDetach) { + try { + r = beforeDetach(connection); + } + catch (e) { _log("jsPlumb: beforeDetach callback failed", e); } + } + return r; + }; + + // user can supply a beforeDrop callback, which will be executed before a dropped + // connection is confirmed. user can return false to reject connection. + var beforeDrop = params.beforeDrop; + this.isDropAllowed = function(sourceId, targetId, scope) { + var r = self._jsPlumb.checkCondition("beforeDrop", { sourceId:sourceId, targetId:targetId, scope:scope }); + if (beforeDrop) { + try { + r = beforeDrop({ sourceId:sourceId, targetId:targetId, scope:scope }); + } + catch (e) { _log("jsPlumb: beforeDrop callback failed", e); } + } + return r; + }; + + // helper method to update the hover style whenever it, or paintStyle, changes. + // we use paintStyle as the foundation and merge hoverPaintStyle over the + // top. + var _updateHoverStyle = function() { + if (self.paintStyle && self.hoverPaintStyle) { + var mergedHoverStyle = {}; + jsPlumb.extend(mergedHoverStyle, self.paintStyle); + jsPlumb.extend(mergedHoverStyle, self.hoverPaintStyle); + delete self["hoverPaintStyle"]; + // we want the fillStyle of paintStyle to override a gradient, if possible. + if (mergedHoverStyle.gradient && self.paintStyle.fillStyle) + delete mergedHoverStyle["gradient"]; + self.hoverPaintStyle = mergedHoverStyle; + } + }; + + /* + * Sets the paint style and then repaints the element. + * + * Parameters: + * style - Style to use. + */ + this.setPaintStyle = function(style, doNotRepaint) { + self.paintStyle = style; + self.paintStyleInUse = self.paintStyle; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /* + * Sets the paint style to use when the mouse is hovering over the element. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the component will not be repainted. useful when setting things up initially. + */ + this.setHoverPaintStyle = function(style, doNotRepaint) { + self.hoverPaintStyle = style; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /* + * sets/unsets the hover state of this element. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + this.setHover = function(hover, ignoreAttachedElements, timestamp) { + // while dragging, we ignore these events. this keeps the UI from flashing and + // swishing and whatevering. + if (!self._jsPlumb.currentlyDragging && !self._jsPlumb.isHoverSuspended()) { + + _hover = hover; + if (self.hoverClass != null && self.canvas != null) { + if (hover) + jpcl.addClass(self.canvas, self.hoverClass); + else + jpcl.removeClass(self.canvas, self.hoverClass); + } + if (self.hoverPaintStyle != null) { + self.paintStyleInUse = hover ? self.hoverPaintStyle : self.paintStyle; + timestamp = timestamp || _timestamp(); + self.repaint({timestamp:timestamp, recalc:false}); + } + // get the list of other affected elements, if supported by this component. + // for a connection, its the endpoints. for an endpoint, its the connections! surprise. + if (self.getAttachedElements && !ignoreAttachedElements) + _updateAttachedElements(hover, _timestamp(), self); + } + }; + + this.isHover = function() { return _hover; }; + + var jpcl = jsPlumb.CurrentLibrary, + events = [ "click", "dblclick", "mouseenter", "mouseout", "mousemove", "mousedown", "mouseup", "contextmenu" ], + eventFilters = { "mouseout":"mouseexit" }, + bindOne = function(o, c, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.bind(o, evt, function(ee) { + c.fire(filteredEvent, c, ee); + }); + }, + unbindOne = function(o, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.unbind(o, evt); + }; + + this.attachListeners = function(o, c) { + for (var i = 0; i < events.length; i++) { + bindOne(o, c, events[i]); + } + }; + + var _updateAttachedElements = function(state, timestamp, sourceElement) { + var affectedElements = self.getAttachedElements(); // implemented in subclasses + if (affectedElements) { + for (var i = 0; i < affectedElements.length; i++) { + if (!sourceElement || sourceElement != affectedElements[i]) + affectedElements[i].setHover(state, true, timestamp); // tell the attached elements not to inform their own attached elements. + } + } + }; + + this.reattachListenersForElement = function(o) { + if (arguments.length > 1) { + for (var i = 0; i < events.length; i++) + unbindOne(o, events[i]); + for (var i = 1; i < arguments.length; i++) + self.attachListeners(o, arguments[i]); + } + }; + }, + + overlayCapableJsPlumbUIComponent = function(params) { + jsPlumbUIComponent.apply(this, arguments); + var self = this; + /* + * Property: overlays + * List of Overlays for this component. + */ + this.overlays = []; + + var processOverlay = function(o) { + var _newOverlay = null; + if (o.constructor == Array) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0], + // make a copy of the object so as not to mess up anyone else's reference... + p = jsPlumb.extend({component:self, _jsPlumb:self._jsPlumb}, o[1]); + if (o.length == 3) jsPlumb.extend(p, o[2]); + _newOverlay = new jsPlumb.Overlays[self._jsPlumb.getRenderMode()][type](p); + if (p.events) { + for (var evt in p.events) { + _newOverlay.bind(evt, p.events[evt]); + } + } + } else if (o.constructor == String) { + _newOverlay = new jsPlumb.Overlays[self._jsPlumb.getRenderMode()][o]({component:self, _jsPlumb:self._jsPlumb}); + } else { + _newOverlay = o; + } + + self.overlays.push(_newOverlay); + }, + calculateOverlaysToAdd = function(params) { + var defaultKeys = self.defaultOverlayKeys || [], + o = params.overlays, + checkKey = function(k) { + return self._jsPlumb.Defaults[k] || jsPlumb.Defaults[k] || []; + }; + + if (!o) o = []; + + for (var i = 0; i < defaultKeys.length; i++) + o.unshift.apply(o, checkKey(defaultKeys[i])); + + return o; + } + + var _overlays = calculateOverlaysToAdd(params);//params.overlays || self._jsPlumb.Defaults.Overlays; + if (_overlays) { + for (var i = 0; i < _overlays.length; i++) { + processOverlay(_overlays[i]); + } + } + + // overlay finder helper method + var _getOverlayIndex = function(id) { + var idx = -1; + for (var i = 0; i < self.overlays.length; i++) { + if (id === self.overlays[i].id) { + idx = i; + break; + } + } + return idx; + }; + + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + this.addOverlay = function(overlay) { + processOverlay(overlay); + self.repaint(); + }; + + /* + * Function: getOverlay + * Gets an overlay, by ID. Note: by ID. You would pass an 'id' parameter + * in to the Overlay's constructor arguments, and then use that to retrieve + * it via this method. + */ + this.getOverlay = function(id) { + var idx = _getOverlayIndex(id); + return idx >= 0 ? self.overlays[idx] : null; + }; + + /* + * Function: hideOverlay + * Hides the overlay specified by the given id. + */ + this.hideOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.hide(); + }; + + /* + * Function: showOverlay + * Shows the overlay specified by the given id. + */ + this.showOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.show(); + }; + + /** + * Function: removeAllOverlays + * Removes all overlays from the Connection, and then repaints. + */ + this.removeAllOverlays = function() { + self.overlays.splice(0, self.overlays.length); + self.repaint(); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + this.removeOverlay = function(overlayId) { + var idx = _getOverlayIndex(overlayId); + if (idx != -1) { + var o = self.overlays[idx]; + o.cleanup(); + self.overlays.splice(idx, 1); + } + }; + + /** + * Function:removeOverlays + * Removes a set of overlays by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + this.removeOverlays = function() { + for (var i = 0; i < arguments.length; i++) + self.removeOverlay(arguments[i]); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + var _internalLabelOverlayId = "__label", + _makeLabelOverlay = function(params) { + + var _params = { + cssClass:params.cssClass, + labelStyle : this.labelStyle, + id:_internalLabelOverlayId, + component:self, + _jsPlumb:self._jsPlumb + }, + mergedParams = jsPlumb.extend(_params, params); + + return new jsPlumb.Overlays[self._jsPlumb.getRenderMode()].Label( mergedParams ); + }; + if (params.label) { + var loc = params.labelLocation || self.defaultLabelLocation || 0.5, + labelStyle = params.labelStyle || self._jsPlumb.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.overlays.push(_makeLabelOverlay({ + label:params.label, + location:loc, + labelStyle:labelStyle + })); + } + + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String, a Function that returns a String, or a params object containing { "label", "labelStyle", "location", "cssClass" } + */ + this.setLabel = function(l) { + var lo = self.getOverlay(_internalLabelOverlayId); + if (!lo) { + var params = l.constructor == String || l.constructor == Function ? { label:l } : l; + lo = _makeLabelOverlay(params); + this.overlays.push(lo); + } + else { + if (l.constructor == String || l.constructor == Function) lo.setLabel(l); + else { + if (l.label) lo.setLabel(l.label); + if (l.location) lo.setLocation(l.location); + } + } + + self.repaint(); + }; + + /* + Function:getLabel + Returns the label text for this component (or a function if you are labelling with a function). + This does not return the overlay itself; this is a convenience method which is a pair with + setLabel; together they allow you to add and access a Label Overlay without having to create the + Overlay object itself. For access to the underlying label overlay that jsPlumb has created, + use getLabelOverlay. + */ + this.getLabel = function() { + var lo = self.getOverlay(_internalLabelOverlayId); + return lo != null ? lo.getLabel() : null; + }; + + /* + Function:getLabelOverlay + Returns the underlying internal label overlay, which will exist if you specified a label on + a connect or addEndpoint call, or have called setLabel at any stage. + */ + this.getLabelOverlay = function() { + return self.getOverlay(_internalLabelOverlayId); + } + }, + + _bindListeners = function(obj, _self, _hoverFunction) { + obj.bind("click", function(ep, e) { _self.fire("click", _self, e); }); + obj.bind("dblclick", function(ep, e) { _self.fire("dblclick", _self, e); }); + obj.bind("contextmenu", function(ep, e) { _self.fire("contextmenu", _self, e); }); + // this method is different; endpoint delegates hover to the first connection if there is one. + // that allows the connection to notify all its endpoint and avoids a circular loop + obj.bind("mouseenter", function(ep, e) { + if (!_self.isHover()) { + _hoverFunction(true); + _self.fire("mouseenter", _self, e); + } + }); + // this method is different; endpoint delegates hover to the first connection if there is one. + // that allows the connection to notify all its endpoint and avoids a circular loop + obj.bind("mouseexit", function(ep, e) { + if (_self.isHover()) { + _hoverFunction(false); + _self.fire("mouseexit", self, e); + } + }); + }; + + var _jsPlumbInstanceIndex = 0, + getInstanceIndex = function() { + var i = _jsPlumbInstanceIndex + 1; + _jsPlumbInstanceIndex++; + return i; + }; + + var jsPlumbInstance = function(_defaults) { + + /* + * Property: Defaults + * + * These are the default settings for jsPlumb. They are what will be used if you do not supply specific pieces of information + * to the various API calls. A convenient way to implement your own look and feel can be to override these defaults + * by including a script somewhere after the jsPlumb include, but before you make any calls to jsPlumb. + * + * Properties: + * - *Anchor* The default anchor to use for all connections (both source and target). Default is "BottomCenter". + * - *Anchors* The default anchors to use ([source, target]) for all connections. Defaults are ["BottomCenter", "BottomCenter"]. + * - *Connector* The default connector definition to use for all connections. Default is "Bezier". + * - *Container* Optional selector or element id that instructs jsPlumb to append elements it creates to a specific element. + * - *DragOptions* The default drag options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *DropOptions* The default drop options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *Endpoint* The default endpoint definition to use for all connections (both source and target). Default is "Dot". + * - *Endpoints* The default endpoint definitions ([ source, target ]) to use for all connections. Defaults are ["Dot", "Dot"]. + * - *EndpointStyle* The default style definition to use for all endpoints. Default is fillStyle:"#456". + * - *EndpointStyles* The default style definitions ([ source, target ]) to use for all endpoints. Defaults are empty. + * - *EndpointHoverStyle* The default hover style definition to use for all endpoints. Default is null. + * - *EndpointHoverStyles* The default hover style definitions ([ source, target ]) to use for all endpoints. Defaults are null. + * - *HoverPaintStyle* The default hover style definition to use for all connections. Defaults are null. + * - *LabelStyle* The default style to use for label overlays on connections. + * - *LogEnabled* Whether or not the jsPlumb log is enabled. defaults to false. + * - *Overlays* The default overlay definitions. Defaults to an empty list. + * - *MaxConnections* The default maximum number of connections for an Endpoint. Defaults to 1. + * - *MouseEventsEnabled* Whether or not mouse events are enabled when using the canvas renderer. Defaults to true. + * The idea of this is just to give people a way to prevent all the mouse listeners from activating if they know they won't need mouse events. + * - *PaintStyle* The default paint style for a connection. Default is line width of 8 pixels, with color "#456". + * - *RenderMode* What mode to use to paint with. If you're on IE<9, you don't really get to choose this. You'll just get VML. Otherwise, the jsPlumb default is to use Canvas elements. + * - *Scope* The default "scope" to use for connections. Scope lets you assign connections to different categories. + */ + this.Defaults = { + Anchor : "BottomCenter", + Anchors : [ null, null ], + ConnectionsDetachable : true, + Connector : "Bezier", + Container : null, + DragOptions : { }, + DropOptions : { }, + Endpoint : "Dot", + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : "#456" }, + EndpointStyles : [ null, null ], + EndpointHoverStyle : null, + EndpointHoverStyles : [ null, null ], + HoverPaintStyle : null, + LabelStyle : { color : "black" }, + LogEnabled : false, + Overlays : [ ], + MaxConnections : 1, + MouseEventsEnabled : true, + PaintStyle : { lineWidth : 8, strokeStyle : "#456" }, + //Reattach:false, + RenderMode : "svg", + Scope : "jsPlumb_DefaultScope" + }; + if (_defaults) jsPlumb.extend(this.Defaults, _defaults); + + this.logEnabled = this.Defaults.LogEnabled; + + EventGenerator.apply(this); + var _bb = this.bind; + this.bind = function(event, fn) { + if ("ready" === event && initialized) fn(); + else _bb(event, fn); + }; + var _currentInstance = this, + _instanceIndex = getInstanceIndex(), + + log = null, + repaintFunction = function() { + jsPlumb.repaintEverything(); + }, + automaticRepaint = true, + repaintEverything = function() { + if (automaticRepaint) + repaintFunction(); + }, + resizeTimer = null, + initialized = false, + connectionsByScope = {}, + /** + * map of element id -> endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + endpointsByElement = {}, + endpointsByUUID = {}, + offsets = {}, + offsetTimestamps = {}, + floatingConnections = {}, + draggableStates = {}, + _mouseEventsEnabled = this.Defaults.MouseEventsEnabled, + canvasList = [], + sizes = [], + //listeners = {}, // a map: keys are event types, values are lists of listeners. + DEFAULT_SCOPE = this.Defaults.Scope, + renderMode = null, // will be set in init() + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }, + + /** + * appends an element to some other element, which is calculated as follows: + * + * 1. if _currentInstance.Defaults.Container exists, use that element. + * 2. if the 'parent' parameter exists, use that. + * 3. otherwise just use the document body. + * + */ + _appendElement = function(el, parent) { + if (_currentInstance.Defaults.Container) + jsPlumb.CurrentLibrary.appendElement(el, _currentInstance.Defaults.Container); + else if (!parent) + document.body.appendChild(el); + else + jsPlumb.CurrentLibrary.appendElement(el, parent); + }, + + _curIdStamp = 1, + _idstamp = function() { return "" + _curIdStamp++; }, + + /** + * YUI, for some reason, put the result of a Y.all call into an object that contains + * a '_nodes' array, instead of handing back an array-like object like the other + * libraries do. + */ + _convertYUICollection = function(c) { + return c._nodes ? c._nodes : c; + }, + + _suspendDrawing = false, + /* + sets whether or not to suspend drawing. you should use this if you need to connect a whole load of things in one go. + it will save you a lot of time. + */ + _setSuspendDrawing = function(val, repaintAfterwards) { + _suspendDrawing = val; + if (repaintAfterwards) _currentInstance.repaintEverything(); + }, + + /** + * Draws an endpoint and its connections. this is the main entry point into drawing connections as well + * as endpoints, since jsPlumb is endpoint-centric under the hood. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + _draw = function(element, ui, timestamp) { + if (!_suspendDrawing) { + var id = _getAttribute(element, "id"); + _currentInstance.anchorManager.redraw(id, ui, timestamp); + } + }, + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }, + + /** + * gets an Endpoint by uuid. + */ + _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }, + + /** + * inits a draggable if it's not already initialised. + */ + _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? false : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element) && !jsPlumb.CurrentLibrary.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents["drag"], + stopEvent = jsPlumb.CurrentLibrary.dragEvents["stop"]; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + draggableStates[_getId(element)] = true; + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options, false); + } + } + }, + + /* + * prepares a final params object that can be passed to _newConnection, taking into account defaults, events, etc. + */ + _prepareConnectionParams = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + // copy in any connectorOverlays that were specified on the source endpoint. + // it doesnt copy target endpoint overlays. i'm not sure if we want it to or not. + if (_p.sourceEndpoint && _p.sourceEndpoint.connectorOverlays) { + _p.overlays = _p.overlays || []; + for (var i = 0; i < _p.sourceEndpoint.connectorOverlays.length; i++) { + _p.overlays.push(_p.sourceEndpoint.connectorOverlays[i]); + } + } + + // tooltip. params.tooltip takes precedence, then sourceEndpoint.connectorTooltip. + _p.tooltip = params.tooltip; + if (!_p.tooltip && _p.sourceEndpoint && _p.sourceEndpoint.connectorTooltip) + _p.tooltip = _p.sourceEndpoint.connectorTooltip; + + if (_p.target && !_p.target.endpoint) { + var tid = _getId(_p.target), + tep =_targetEndpointDefinitions[tid]; + + var overrideOne = function(singlePropertyName, pluralPropertyName, tepProperty, tep) { + if (tep[tepProperty]) { + if (_p[pluralPropertyName]) _p[pluralPropertyName][1] = tep[tepProperty]; + else if (_p[singlePropertyName]) { + _p[pluralPropertyName] = [ _p[singlePropertyName], tep[tepProperty] ]; + _p[singlePropertyName] = null; + } + else _p[pluralPropertyName] = [ null, tep[tepProperty] ]; + } + }; + + if (tep) { + overrideOne("endpoint", "endpoints", "endpoint", tep); + overrideOne("endpointStyle", "endpointStyles", "paintStyle", tep); + overrideOne("endpointHoverStyle", "endpointHoverStyles", "hoverPaintStyle", tep); + } + } + + return _p; + }, + + _newConnection = function(params) { + var connectionFunc = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + endpointFunc = _currentInstance.Defaults.EndpointType || Endpoint, + parent = jsPlumb.CurrentLibrary.getParent; + + if (params.container) + params["parent"] = params.container; + else { + if (params.sourceEndpoint) + params["parent"] = params.sourceEndpoint.parent; + else if (params.source.constructor == endpointFunc) + params["parent"] = params.source.parent; + else params["parent"] = parent(params.source); + } + + params["_jsPlumb"] = _currentInstance; + var con = new connectionFunc(params); + con.id = "con_" + _idstamp(); + _eventFireProxy("click", "click", con); + _eventFireProxy("dblclick", "dblclick", con); + _eventFireProxy("contextmenu", "contextmenu", con); + return con; + }, + + /** + * adds the connection to the backing model, fires an event if necessary and then redraws + */ + _finaliseConnection = function(jpc, params, originalEvent) { + params = params || {}; + // add to list of connections (by scope). + if (!jpc.suspendedEndpoint) + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + if (!params.doNotFireConnectionEvent && params.fireEvent !== false) { + _currentInstance.fire("jsPlumbConnection", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }, originalEvent); + } + // always inform the anchor manager + // except that if jpc has a suspended endpoint it's not true to say the + // connection is new; it has just (possibly) moved. the question is whether + // to make that call here or in the anchor manager. i think perhaps here. + _currentInstance.anchorManager.newConnection(jpc); + // force a paint + _draw(jpc.source); + }, + + _eventFireProxy = function(event, proxyEvent, obj) { + obj.bind(event, function(originalObject, originalEvent) { + _currentInstance.fire(proxyEvent, obj, originalEvent); + }); + }, + + /** + * for the given endpoint params, returns an appropriate parent element for the UI elements that will be added. + * this function is used by _newEndpoint (directly below), and also in the makeSource function in jsPlumb. + * + * the logic is to first look for a "container" member of params, and pass that back if found. otherwise we + * handoff to the 'getParent' function in the current library. + */ + _getParentFromParams = function(params) { + if (params.container) + return params.container; + else { + var tag = jsPlumb.CurrentLibrary.getTagName(params.source), + p = jsPlumb.CurrentLibrary.getParent(params.source); + if (tag && tag.toLowerCase() === "td") + return jsPlumb.CurrentLibrary.getParent(p); + else return p; + } + }, + + /** + factory method to prepare a new endpoint. this should always be used instead of creating Endpoints + manually, since this method attaches event listeners and an id. + */ + _newEndpoint = function(params) { + var endpointFunc = _currentInstance.Defaults.EndpointType || Endpoint; + params.parent = _getParentFromParams(params); + params["_jsPlumb"] = _currentInstance; + var ep = new endpointFunc(params); + ep.id = "ep_" + _idstamp(); + _eventFireProxy("click", "endpointClick", ep); + _eventFireProxy("dblclick", "endpointDblClick", ep); + _eventFireProxy("contextmenu", "contextmenu", ep); + return ep; + }, + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + _operation = function(elId, func, endpointFunc) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + if (endpointFunc) endpointFunc(endpoints[i]); + } + } + }, + /** + * perform an operation on all elements. + */ + _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }, + + /** + * helper to remove an element from the DOM. + */ + _removeElement = function(element, parent) { + if (element != null && element.parentNode != null) { + element.parentNode.removeChild(element); + } + }, + /** + * helper to remove a list of elements from the DOM. + */ + _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }, + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }, + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + _setVisible = function(el, state, alsoChangeEndpoints) { + state = state === "block"; + var endpointFunc = null; + if (alsoChangeEndpoints) { + if (state) endpointFunc = function(ep) { + ep.setVisible(true, true, true); + }; + else endpointFunc = function(ep) { + ep.setVisible(false, true, true); + }; + } + var id = _getAttribute(el, "id"); + _operation(id, function(jpc) { + if (state && alsoChangeEndpoints) { + // this test is necessary because this functionality is new, and i wanted to maintain backwards compatibility. + // this block will only set a connection to be visible if the other endpoint in the connection is also visible. + var oidx = jpc.sourceId === id ? 1 : 0; + if (jpc.endpoints[oidx].isVisible()) jpc.setVisible(true); + } + else // the default behaviour for show, and what always happens for hide, is to just set the visibility without getting clever. + jpc.setVisible(state); + }, endpointFunc); + }, + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? false : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }, + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + _toggleVisible = function(elId, changeEndpoints) { + var endpointFunc = null; + if (changeEndpoints) { + endpointFunc = function(ep) { + var state = ep.isVisible(); + ep.setVisible(!state); + }; + } + _operation(elId, function(jpc) { + var state = jpc.isVisible(); + jpc.setVisible(!state); + }, endpointFunc); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }, + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return offsets[elId]; + } + if (recalc || !offset) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + if (sizes[elId] == null) { + var s = _getElementObject(elId); + if (s != null) + sizes[elId] = _getSize(s); + } + } + + if(offsets[elId] && !offsets[elId].right) { + offsets[elId].right = offsets[elId].left + sizes[elId][0]; + offsets[elId].bottom = offsets[elId].top + sizes[elId][1]; + offsets[elId].width = sizes[elId][0]; + offsets[elId].height = sizes[elId][1]; + offsets[elId].centerx = offsets[elId].left + (offsets[elId].width / 2); + offsets[elId].centery = offsets[elId].top + (offsets[elId].height / 2); + } + return offsets[elId]; + }, + + // TODO comparison performance + _getCachedData = function(elId) { + var o = offsets[elId]; + if (!o) o = _updateOffset({elId:elId}); + return {o:o, s:sizes[elId]}; + }, + + /** + * gets an id for the given element, creating and setting one if + * necessary. the id is of the form + * + * jsPlumb__ + * + * where "index in instance" is a monotonically increasing integer that starts at 0, + * for each instance. this method is used not only to assign ids to elements that do not + * have them but also to connections and endpoints. + */ + _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else + id = "jsPlumb_" + _instanceIndex + "_" + _idstamp(); + _setAttribute(ele, "id", id); + } + return id; + }, + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'jsPlumb function failed : ' + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'wrapped function failed : ' + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = "_jsPlumb_connector"; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = "_jsPlumb_endpoint"; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = "_jsPlumb_overlay"; + + this.Anchors = {}; + + this.Connectors = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Endpoints = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Overlays = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + +// ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***************************************** + /* + * Function: bind + * Bind to an event on jsPlumb. + * + * Parameters: + * event - the event to bind. Available events on jsPlumb are: + * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback. + * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback. + * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback. + * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback. + * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback. + * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback. + * + * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event. + */ + + /* + * Function: clearListeners + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + +// *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *********************************************************** + + /* + Function: addClass + + Helper method to abstract out differences in setting css classes on the different renderer types. + */ + this.addClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.addClass(el, clazz); + }; + + /* + Function: removeClass + + Helper method to abstract out differences in setting css classes on the different renderer types. + */ + this.removeClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.removeClass(el, clazz); + }; + + /* + Function: hasClass + + Helper method to abstract out differences in testing for css classes on the different renderer types. + */ + this.hasClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.hasClass(el, clazz); + }; + + /* + Function: addEndpoint + + Adds an to a given element or elements. + + Parameters: + + el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + params - Object containing Endpoint constructor arguments. For more information, see . + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some + shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in + this object are anything that 'params' can contain. See . + + Returns: + The newly created , if el referred to a single element. Otherwise, an array of newly created s. + + See Also: + + */ + this.addEndpoint = function(el, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // YUI wrapper + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + var _el = _getElementObject(inputs[i]), id = _getId(_el); + p.source = _el; + _updateOffset({ elId : id }); + var e = _newEndpoint(p); + if (p.parentAnchor) e.parentAnchor = p.parentAnchor; + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + results.push(e); + } + + return results.length == 1 ? results[0] : results; + }; + + /* + Function: addEndpoints + Adds a list of s to a given element or elements. + + Parameters: + target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See 's constructor documentation. + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created s, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(el, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams); + if (e.constructor == Array) + Array.prototype.push.apply(results, e); + else results.push(e); + } + return results; + }; + + /* + Function: animate + This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating + the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // onComplete repaints, just to make sure everything looks good at the end of the animation. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /** + * checks for a listener for the given condition, executing it if found, passing in the given value. + * condition listeners would have been attached using "bind" (which is, you could argue, now overloaded, since + * firing click events etc is a bit different to what this does). i thought about adding a "bindCondition" + * or something, but decided against it, for the sake of simplicity. jsPlumb will never fire one of these + * condition events anyway. + */ + this.checkCondition = function(conditionName, value) { + var l = _currentInstance.getListener(conditionName); + var r = true; + if (l && l.length > 0) { + try { + for (var i = 0 ; i < l.length; i++) { + r = r && l[i](value); + } + } + catch (e) { + _log(_currentInstance, "cannot check condition [" + conditionName + "]" + e); + } + } + return r; + }; + + /* + Function: connect + Establishes a between two elements (or s, which are themselves registered to elements). + + Parameters: + params - Object containing constructor arguments for the Connection. See 's constructor documentation. + referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of + Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection. + + Returns: + The newly created . + */ + this.connect = function(params, referenceParams) { + // prepare a final set of parameters to create connection with + var _p = _prepareConnectionParams(params, referenceParams); + // TODO probably a nicer return value if the connection was not made. _prepareConnectionParams + // will return null (and log something) if either endpoint was full. what would be nicer is to + // create a dedicated 'error' object. + if (_p) { + // a connect call will delete its created endpoints on detach, unless otherwise specified. + // this is because the endpoints belong to this connection only, and are no use to + // anyone else, so they hang around like a bad smell. + if (_p.deleteEndpointsOnDetach == null) + _p.deleteEndpointsOnDetach = true; + + // create the connection. it is not yet registered + var jpc = _newConnection(_p); + // now add it the model, fire an event, and redraw + _finaliseConnection(jpc, _p); + return jpc; + } + }; + + /* + Function: deleteEndpoint + Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too) + + Parameters: + object - either an object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElements(endpoint.endpoint.getDisplayElements()); + _currentInstance.anchorManager.deleteEndpoint(endpoint); + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + delete endpoint; + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every , and their associated s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc, doFireEvent) { + // may have been given a connection, or in special cases, an object + var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + argIsConnection = jpc.constructor == connType, + params = argIsConnection ? { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + } : jpc; + + if (doFireEvent) _currentInstance.fire("jsPlumbConnectionDetached", params); + _currentInstance.anchorManager.connectionDetached(params); + }; + + /* + Function: detach + Detaches and then removes a . From 1.3.5 this method has been altered to remove support for + specifying Connections by various parameters; you can now pass in a Connection as the first argument and + an optional parameters object as a second argument. If you need the functionality this method provided + before 1.3.5 then you should use the getConnections method to get the list of Connections to detach, and + then iterate through them, calling this for each one. + + Parameters: + connection - the to detach + params - optional parameters to the detach call. valid values here are + fireEvent : defaults to false; indicates you want jsPlumb to fire a connection + detached event. The thinking behind this is that if you made a programmatic + call to detach an event, you probably don't need the callback. + forceDetach : defaults to false. allows you to override any beforeDetach listeners that may be registered. + + Returns: + true if successful, false if not. + */ + this.detach = function() { + + if (arguments.length == 0) return; + var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + firstArgIsConnection = arguments[0].constructor == connType, + params = arguments.length == 2 ? firstArgIsConnection ? (arguments[1] || {}) : arguments[0] : arguments[0], + fireEvent = (params.fireEvent !== false), + forceDetach = params.forceDetach, + connection = firstArgIsConnection ? arguments[0] : params.connection; + + if (connection) { + if (forceDetach || (connection.isDetachAllowed(connection) + && connection.endpoints[0].isDetachAllowed(connection) + && connection.endpoints[1].isDetachAllowed(connection))) { + if (forceDetach || _currentInstance.checkCondition("beforeDetach", connection)) + connection.endpoints[0].detach(connection, false, true, fireEvent); // TODO check this param iscorrect for endpoint's detach method + } + } + else { + var _p = jsPlumb.extend( {}, params); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1]), fireEvent); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source), + targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + if (_currentInstance.checkCondition("beforeDetach", jpc)) { + jpc.endpoints[0].detach(jpc, false, true, fireEvent); + } + } + }); + } + } + }; + + /* + Function: detachAllConnections + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + params - optional parameters. alowed values: + fireEvent : defaults to true, whether or not to fire the detach event. + + Returns: + void + */ + this.detachAllConnections = function(el, params) { + params = params || {}; + el = _getElementObject(el); + var id = _getAttribute(el, "id"), + endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + }; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Parameters: + params - optional params object containing: + fireEvent : whether or not to fire detach events. defaults to true. + + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function(params) { + params = params || {}; + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of y + our library's draggable method so that jsPlumb can setup the appropriate callbacks. Your + underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + // TODO it would be nice if this supported a selector string, instead of an id. + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method, + * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the + * default scope). If multiple scopes are passed in, the return value will be a map of { scope -> [ connection... ] }. + * + * Parameters + * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list + * of connections that are in the given scope. + * options - if the argument is a JS object, you can specify a finer-grained filter: + * + * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes. + * - *source* either a string representing an element id, or a selector. constrains the result to connections having this source. + * - *target* either a string representing an element id, or a selector. constrains the result to connections having this target. + * + */ + this.getConnections = function(options) { + if (!options) { + options = {}; + } else if (options.constructor == String) { + options = { "scope": options }; + } + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') + r.push(input); + else + r = input; + } + return r; + }, + scope = options.scope || _currentInstance.getDefaultScope(), + scopes = prepareList(scope), + sources = prepareList(options.source), + targets = prepareList(options.target), + filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }, + results = scopes.length > 1 ? {} : [], + _addOne = function(scope, obj) { + if (scopes.length > 1) { + var ss = results[scope]; + if (ss == null) { + ss = []; results[scope] = ss; + } + ss.push(obj); + } else results.push(obj); + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + _addOne(i, c); + } + } + } + return results; + }; + + /* + * Function: getAllConnections + * Gets all connections, as a map of { scope -> [ connection... ] }. + */ + this.getAllConnections = function() { + return connectionsByScope; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /** + * Function:getEndpoints + * Gets the list of Endpoints for a given selector, or element id. + * @param el + * @return + */ + this.getEndpoints = function(el) { + return endpointsByElement[_getId(el)]; + }; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + this.getOffset = function(id) { + var o = offsets[id]; + return _updateOffset({elId:id}); + }; + + this.getSelector = function(spec) { + return jsPlumb.CurrentLibrary.getSelector(spec); + }; + + this.getSize = function(id) { + var s = sizes[id]; + if (!s) _updateOffset({elId:id}); + return sizes[id]; + }; + + this.appendElement = _appendElement; + + var _hoverSuspended = false; + this.isHoverSuspended = function() { return _hoverSuspended; }; + this.setHoverSuspended = function(s) { _hoverSuspended = s; }; + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + changeEndpoints - whether not to also hide endpoints on the element. by default this is false. + + Returns: + void + */ + this.hide = function(el, changeEndpoints) { + _setVisible(el, "none", changeEndpoints); + }; + + // exposed for other objects to use to get a unique id. + this.idstamp = _idstamp; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + if (!initialized) { + _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run. + + var bindOne = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && _mouseEventsEnabled && renderMode == jsPlumb.CANVAS) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + var t = c[i].connector[event](e); + if (t) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i].endpoint[event](e)) return; + } + } + } + }); + }; + bindOne("click");bindOne("dblclick");bindOne("mousemove");bindOne("mousedown");bindOne("mouseup");bindOne("contextmenu"); + + initialized = true; + _currentInstance.fire("ready"); + } + }; + + this.log = log; + this.jsPlumbUIComponent = jsPlumbUIComponent; + this.EventGenerator = EventGenerator; + + /* + * Creates an anchor with the given params. + * + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function() { + if (arguments.length == 0) return null; + var specimen = arguments[0], elementId = arguments[1], jsPlumbInstance = arguments[2], newAnchor = null; + if (!jsPlumbInstance) + throw "NO JSPLUMB SET"; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; //TODO hazy here about whether it should be added or is already added somehow. + // is it the name of an anchor type? + else if (typeof specimen == "string") { + newAnchor = jsPlumb.Anchors[arguments[0]]({elementId:elementId, jsPlumbInstance:_currentInstance}); + } + // is it an array? it will be one of: + // an array of [name, params] - this defines a single anchor + // an array of arrays - this defines some dynamic anchors + // an array of numbers - this defines a single anchor. + else if (specimen.constructor == Array) { + if (specimen[0].constructor == Array || specimen[0].constructor == String) { + if (specimen.length == 2 && specimen[0].constructor == String && specimen[1].constructor == Object) { + var pp = jsPlumb.extend({elementId:elementId, jsPlumbInstance:_currentInstance}, specimen[1]); + newAnchor = jsPlumb.Anchors[specimen[0]](pp); + } + else + newAnchor = new DynamicAnchor(specimen, null, elementId); + } + else { + var anchorParams = { + x:specimen[0], y:specimen[1], + orientation : (specimen.length >= 4) ? [ specimen[2], specimen[3] ] : [0,0], + offsets : (specimen.length == 6) ? [ specimen[4], specimen[5] ] : [ 0, 0 ], + elementId:elementId + }; + newAnchor = new Anchor(anchorParams); + newAnchor.clone = function() { return new Anchor(anchorParams); }; + } + } + + if (!newAnchor.id) newAnchor.id = "anchor_" + _idstamp(); + return newAnchor; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types, elementId, jsPlumbInstance) { + var r = []; + for ( var i = 0; i < types.length; i++) { + if (typeof types[i] == "string") + r.push(jsPlumb.Anchors[types[i]]({elementId:elementId, jsPlumbInstance:jsPlumbInstance})); + else if (types[i].constructor == Array) + r.push(_currentInstance.makeAnchor(types[i], elementId, jsPlumbInstance)); + } + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /** + * Function: makeTarget + * Makes some DOM element a Connection target, allowing you to drag connections to it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a target. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * scope optional. scope for the drop zone. + * dropOptions optional. same stuff as you would pass to dropOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to true. whether or not to delete + * any Endpoints created by a connection to this target if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + var _targetEndpointDefinitions = {}; + var _setEndpointPaintStylesAndAnchor = function(ep, epIndex) { + ep.paintStyle = ep.paintStyle || + _currentInstance.Defaults.EndpointStyles[epIndex] || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyles[epIndex] || + jsPlumb.Defaults.EndpointStyle; + ep.hoverPaintStyle = ep.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyles[epIndex] || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyles[epIndex] || + jsPlumb.Defaults.EndpointHoverStyle; + + ep.anchor = ep.anchor || + _currentInstance.Defaults.Anchors[epIndex] || + _currentInstance.Defaults.Anchor || + jsPlumb.Defaults.Anchors[epIndex] || + jsPlumb.Defaults.Anchor; + + }; + this.makeTarget = function(el, params, referenceParams) { + + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + var jpcl = jsPlumb.CurrentLibrary, + targetScope = p.scope || _currentInstance.Defaults.Scope, + deleteEndpointsOnDetach = p.deleteEndpointsOnDetach || true, + _doOne = function(_el) { + + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el); + _targetEndpointDefinitions[elid] = p.endpoint; + + var dropOptions = jsPlumb.extend({}, p.dropOptions || {}), + _drop = function() { + _currentInstance.currentlyDragging = false; + var draggable = _getElementObject(jpcl.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + // restore the original scope if necessary (issue 57) + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id], + source = jpc.endpoints[0], + _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : {}; + + // use defaults for endpoint style, if not present..this either uses EndpointStyle, or EndpointStyles[1], + // if it is present, since this is a target endpoint. + // TODO - this should be a helper method. makeTarget should use it too. give it an endpoint + _setEndpointPaintStylesAndAnchor(_endpoint, 1); + + // unlock the source anchor to allow it to refresh its position if necessary + source.anchor.locked = false; + + if (scope) jpcl.setDragScope(draggable, scope); + + // check if drop is allowed here. + var _continue = jpc.isDropAllowed(jpc.sourceId, _getId(_el), jpc.scope); + + // regardless of whether the connection is ok, reconfigure the existing connection to + // point at the current info. we need this to be correct for the detach event that will follow. + // clear the source endpoint from the list to detach. we will detach this connection at this + // point, but we want to keep the source endpoint. the target is a floating endpoint and should + // be removed. TODO need to figure out whether this code can result in endpoints kicking around + // when they shouldnt be. like is this a full detach of a connection? can it be? + if (jpc.endpointsToDeleteOnDetach) { + if (source === jpc.endpointsToDeleteOnDetach[0]) + jpc.endpointsToDeleteOnDetach[0] = null; + else if (source === jpc.endpointsToDeleteOnDetach[1]) + jpc.endpointsToDeleteOnDetach[1] = null; + } + // reinstate any suspended endpoint; this just puts the connection back into + // a state in which it will report sensible values if someone asks it about + // its target. we're going to throw this connection away shortly so it doesnt matter + // if we manipulate it a bit. + if (jpc.suspendedEndpoint) { + jpc.targetId = jpc.suspendedEndpoint.elementId; + jpc.target = jpcl.getElementObject(jpc.suspendedEndpoint.elementId); + jpc.endpoints[1] = jpc.suspendedEndpoint; + } + + if (_continue) { + + // detach this connection from the source. + source.detach(jpc, false, true, true);//source.endpointWillMoveAfterConnection); + + // make a new Endpoint for the target + var newEndpoint = _currentInstance.addEndpoint(_el, _endpoint); + + // if the anchor has a 'positionFinder' set, then delegate to that function to find + // out where to locate the anchor. + if (newEndpoint.anchor.positionFinder != null) { + var dropPosition = jpcl.getUIPosition(arguments), + elPosition = jpcl.getOffset(_el), + elSize = jpcl.getSize(_el), + ap = newEndpoint.anchor.positionFinder(dropPosition, elPosition, elSize, newEndpoint.anchor); + newEndpoint.anchor.x = ap[0]; + newEndpoint.anchor.y = ap[1]; + // now figure an orientation for it..kind of hard to know what to do actually. probably the best thing i can do is to + // support specifying an orientation in the anchor's spec. if one is not supplied then i will make the orientation + // be what will cause the most natural link to the source: it will be pointing at the source, but it needs to be + // specified in one axis only, and so how to make that choice? i think i will use whichever axis is the one in which + // the target is furthest away from the source. + } + var c = _currentInstance.connect({ + source:source, + target:newEndpoint, + scope:scope, + previousConnection:jpc, + container:jpc.parent, + // 'endpointWillMoveAfterConnection' is set by the makeSource function, and it indicates that the + // given endpoint will actually transfer from the element it is currently attached to to some other + // element after a connection has been established. in that case, we do not want to fire the + // connection event, since it will have the wrong data in it; makeSource will do it for us. + // this is controlled by the 'parent' parameter on a makeSource call. + doNotFireConnectionEvent:source.endpointWillMoveAfterConnection + }); + if (deleteEndpointsOnDetach) + c.endpointsToDeleteOnDetach = [ source, newEndpoint ]; + } + // if not allowed to drop... + else { + // TODO this code is identical (pretty much) to what happens when a connection + // dragged from a normal endpoint is in this situation. refactor. + // is this an existing connection, and will we reattach? + if (jpc.suspendedEndpoint) { + if (source.isReattach) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + _currentInstance.repaint(source.elementId); + } + else + source.detach(jpc, false, true, true); // otherwise, detach the connection and tell everyone about it. + } + + } + }; + + var dropEvent = jpcl.dragEvents['drop']; + dropOptions["scope"] = dropOptions["scope"] || targetScope; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop); + + jpcl.initDroppable(_el, dropOptions, true); + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements drop targets. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeTargets = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeTarget(els[i], params, referenceParams); + } + }; + + /** + * Function: makeSource + * Makes some DOM element a Connection source, allowing you to drag connections from it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a source. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * parent optional. the element to add Endpoints to when a Connection is established. if you omit this, + * Endpoints will be added to 'el'. + * scope optional. scope for the connections dragged from this element. + * dragOptions optional. same stuff as you would pass to dragOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to false. whether or not to delete + * any Endpoints created by a connection from this source if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + var _sourceEndpointDefinitions = {}; + this.makeSource = function(el, params, referenceParams) { + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + var jpcl = jsPlumb.CurrentLibrary, + _doOne = function(_el) { + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el); + _sourceEndpointDefinitions[elid] = p.endpoint || {}; + var stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"], + dragOptions = jsPlumb.extend({ }, p.dragOptions || _sourceEndpointDefinitions[elid].dragOptions || {}), + existingDrag = dragOptions.drag, + existingStop = dragOptions.stop, + ep = null, + endpointAddedButNoDragYet = false; + + // set scope if its not set in dragOptions but was passed in in params + dragOptions["scope"] = dragOptions["scope"] || p.scope; + + // make sure this method honours whatever defaults have been set for Endpoint. + _sourceEndpointDefinitions[elid].endpoint = _sourceEndpointDefinitions[elid].endpoint || _currentInstance.Defaults.Endpoints[0] || _currentInstance.Defaults.Endpoint; + + // set endpoint paint styles and anchor, using either styles that are set or defaults. + _setEndpointPaintStylesAndAnchor(_sourceEndpointDefinitions[elid], 0); + + + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + if (existingDrag) existingDrag.apply(this, arguments); + endpointAddedButNoDragYet = false; + }); + + dragOptions[stopEvent] = function() { + + if (existingStop) existingStop.apply(this, arguments); + + //_currentlyDown = false; + _currentInstance.currentlyDragging = false; + + if (ep.connections.length == 0) + _currentInstance.deleteEndpoint(ep); + else { + + jpcl.unbind(ep.canvas, "mousedown"); + + // reset the anchor to the anchor that was initially provided. the one we were using to drag + // the connection was just a placeholder that was located at the place the user pressed the + // mouse button to initiate the drag. + var anchorDef = _sourceEndpointDefinitions[elid].anchor || _currentInstance.Defaults.Anchor; + ep.anchor = _currentInstance.makeAnchor(anchorDef, elid, _currentInstance); + + if (p.parent) { + var parent = jpcl.getElementObject(p.parent); + if (parent) { + var currentId = ep.elementId; + ep.setElement(parent); + ep.endpointWillMoveAfterConnection = false; + _currentInstance.anchorManager.rehomeEndpoint(currentId, parent); + ep.connections[0].previousConnection = null; + _finaliseConnection(ep.connections[0]); + } + } + _currentInstance.repaint(elid); + } + }; + // when the user presses the mouse, add an Endpoint + var mouseDownListener = function(e) { + // make sure we have the latest offset for this div + var myOffsetInfo = _updateOffset({elId:elid}); + + var x = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width, + y = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height, + parentX = x, + parentY = y; + + + // if there is a parent, the endpoint will actually be added to it now, rather than the div + // that was the source. in that case, we have to adjust the anchor position so it refers to + // the parent. + if (p.parent) { + var pEl = jsPlumb.CurrentLibrary.getElementObject(p.parent), + pId = _getId(pEl); + myOffsetInfo = _updateOffset({elId:pId}); + parentX = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width, + parentY = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height; + } + + // we need to override the anchor in here, and force 'isSource', but we don't want to mess with + // the params passed in, because after a connection is established we're going to reset the endpoint + // to have the anchor we were given. + var tempEndpointParams = {}; + jsPlumb.extend(tempEndpointParams, _sourceEndpointDefinitions[elid]); + tempEndpointParams.isSource = true; + tempEndpointParams.anchor = [x,y,0,0]; + tempEndpointParams.parentAnchor = [ parentX, parentY, 0, 0 ]; + tempEndpointParams.dragOptions = dragOptions; + // if a parent was given we need to turn that into a "container" argument. this is, by default, + // the parent of the element we will move to, so parent of p.parent in this case. however, if + // the user has specified a 'container' on the endpoint definition or on + // the defaults, we should use that. + if (p.parent) { + var potentialParent = tempEndpointParams.container || _currentInstance.Defaults.Container; + if (potentialParent) + tempEndpointParams.container = potentialParent; + else + tempEndpointParams.container = jsPlumb.CurrentLibrary.getParent(p.parent); + } + + ep = _currentInstance.addEndpoint(elid, tempEndpointParams); + + endpointAddedButNoDragYet = true; + // we set this to prevent connections from firing attach events before this function has had a chance + // to move the endpoint. + ep.endpointWillMoveAfterConnection = p.parent != null; + ep.endpointWillMoveTo = p.parent ? jpcl.getElementObject(p.parent) : null; + + var _delTempEndpoint = function() { + // this mouseup event is fired only if no dragging occurred, by jquery and yui, but for mootools + // it is fired even if dragging has occurred, in which case we would blow away a perfectly + // legitimate endpoint, were it not for this check. the flag is set after adding an + // endpoint and cleared in a drag listener we set in the dragOptions above. + if(endpointAddedButNoDragYet) { + _currentInstance.deleteEndpoint(ep); + } + }; + + _currentInstance.registerListener(ep.canvas, "mouseup", _delTempEndpoint); + _currentInstance.registerListener(_el, "mouseup", _delTempEndpoint); + + // and then trigger its mousedown event, which will kick off a drag, which will start dragging + // a new connection from this endpoint. + jpcl.trigger(ep.canvas, "mousedown", e); + }; + + // register this on jsPlumb so that it can be cleared by a reset. + _currentInstance.registerListener(_el, "mousedown", mouseDownListener); + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements connection sources. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeSources = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeSource(els[i], params, referenceParams); + } + }; + + /* + Function: ready + Helper method to bind a function to jsPlumb's ready event. + */ + this.ready = function(fn) { + _currentInstance.bind("ready", fn); + }, + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"), + ebe = endpointsByElement[elId]; + if (ebe) { + for ( var i = 0; i < ebe.length; i++) + _currentInstance.deleteEndpoint(ebe[i]); + } + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + var _registeredListeners = {}, + _unbindRegisteredListeners = function() { + for (var i in _registeredListeners) { + for (var j = 0; j < _registeredListeners[i].length; j++) { + var info = _registeredListeners[i][j]; + jsPlumb.CurrentLibrary.unbind(info.el, info.event, info.listener); + } + } + _registeredListeners = {}; + }; + + // internal register listener method. gives us a hook to clean things up + // with if the user calls jsPlumb.reset. + this.registerListener = function(el, type, listener) { + jsPlumb.CurrentLibrary.bind(el, type, listener); + _addToList(_registeredListeners, type, {el:el, event:type, listener:listener}); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + _currentInstance.deleteEveryEndpoint(); + _currentInstance.clearListeners(); + _unbindRegisteredListeners(); + _currentInstance.anchorManager.reset(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + + Parameters: + value - whether or not to automatically repaint when the window is resized. + + Returns: void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction + * Sets the function to fire when the window size has changed and a repaint was fired. + * + * Parameters: + * f - Function to execute. + * + * Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: setMouseEventsEnabled + * Sets whether or not mouse events are enabled. Default is true. + * + * Parameters: + * enabled - whether or not mouse events should be enabled. + * + * Returns: + * void + */ + this.setMouseEventsEnabled = function(enabled) { + _mouseEventsEnabled = enabled; + }; + + /* + * Function: setSuspendDrawing + * Suspends drawing operations. This can be used when you have a lot of connections to make or endpoints to register; + * it will save you a lot of time. + */ + this.setSuspendDrawing = _setSuspendDrawing; + + /* + * Constant for use with the setRenderMode method + */ + this.CANVAS = "canvas"; + + /* + * Constant for use with the setRenderMode method + */ + this.SVG = "svg"; + + this.VML = "vml"; + + /* + * Function: setRenderMode + * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that + * what you asked for is not supported (and that VML is). If you asked for VML but the browser does + * not support it, jsPlumb uses SVG. + * + * Returns: + * the render mode that jsPlumb set, which of course may be different from that requested. + */ + this.setRenderMode = function(mode) { + if (mode) + mode = mode.toLowerCase(); + else + return; + if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); + // now test we actually have the capability to do this. + if (mode === jsPlumb.CANVAS && canvasAvailable) + renderMode = jsPlumb.CANVAS; + else if (mode === jsPlumb.SVG && svgAvailable) + renderMode = jsPlumb.SVG; + else if (vmlAvailable) + renderMode = jsPlumb.VML; + + return renderMode; + }; + + this.getRenderMode = function() { return renderMode; }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * changeEndpoints - whether or not to also change the visible state of the endpoints on the element. this also has a bearing on + * other connections on those endpoints: if their other endpoint is also visible, the connections are made visible. + * + * Returns: + * void + */ + this.show = function(el, changeEndpoints) { + _setVisible(el, "block", changeEndpoints); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId, + makeAnchor:self.makeAnchor, + makeDynamicAnchor:self.makeDynamicAnchor + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * changeEndpoints - whether or not to also toggle the endpoints on the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload + * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element. + * + * Returns: + * void + */ + this.unload = function() { + // this used to do something, but it turns out that what it did was nothing. + // now it exists only for backwards compatibility. + }; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + var adjustForParentOffsetAndScroll = function(xy, el) { + + var offsetParent = null, result = xy; + if (el.tagName.toLowerCase() === "svg" && el.parentNode) { + offsetParent = el.parentNode; + } + else if (el.offsetParent) { + offsetParent = el.offsetParent; + } + if (offsetParent != null) { + var po = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(offsetParent), + so = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : {left:offsetParent.scrollLeft, top:offsetParent.scrollTop}; + + + // i thought it might be cool to do this: + // lastReturnValue[0] = lastReturnValue[0] - offsetParent.offsetLeft + offsetParent.scrollLeft; + // lastReturnValue[1] = lastReturnValue[1] - offsetParent.offsetTop + offsetParent.scrollTop; + // but i think it ignores margins. my reasoning was that it's quicker to not hand off to some underlying + // library. + + result[0] = xy[0] - po.left + so.left; + result[1] = xy[1] - po.top + so.top; + } + + return result; + + }; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + this.elementId = params.elementId; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + + if (timestamp && timestamp === self.timestamp) + return lastReturnValue; + + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + + // adjust loc if there is an offsetParent + lastReturnValue = adjustForParentOffsetAndScroll(lastReturnValue, element.canvas); + + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function(_endpoint) { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + * + * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference, + // the canvas this refers to. + refCanvas = params.referenceCanvas, + size = _getSize(_getElementObject(refCanvas)), + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + xDir = 0, yDir = 0, + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + orientation = null, + _lastResult = null; + + // set these to 0 each; they are used by certain types of connectors in the loopback case, + // when the connector is trying to clear the element it is on. but for floating anchor it's not + // very important. + this.x = 0; this.y = 0; + + this.isFloating = true; + + this.compute = function(params) { + var xy = params.xy, element = params.element, + result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + + // adjust loc if there is an offsetParent + result = adjustForParentOffsetAndScroll(result, element.canvas); + + _lastResult = result; + return result; + }; + + this.getOrientation = function(_endpoint) { + if (orientation) return orientation; + else { + var o = ref.getOrientation(_endpoint); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchor is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector, elementId) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = [], self = this, + _convert = function(anchor) { + return anchor.constructor == Anchor ? anchor: _currentInstance.makeAnchor(anchor, elementId, _currentInstance); + }; + for (var i = 0; i < anchors.length; i++) + _anchors[i] = _convert(anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + this.locked = false; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null, + _curIndex = _anchors.length > 0 ? 0 : -1, + self = this, + + // helper method to calculate the distance between the centers of the two elements. + _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }, + + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + self.x = _curAnchor.x; + self.y = _curAnchor.y; + + return _curAnchor.compute(params); + }; + + this.getCurrentLocation = function() { + return _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + }; + + this.getOrientation = function(_endpoint) { return _curAnchor != null ? _curAnchor.getOrientation(_endpoint) : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + manages anchors for all elements. + */ + // "continuous" anchors: anchors that pick their location based on how many connections the given element has. + // this requires looking at a lot more elements than normal - anything that has had a Continuous anchor applied has + // to be recalculated. so this manager is used as a reference point. the first time, with a new timestamp, that + // a continuous anchor is asked to compute, it calls this guy. or maybe, even, this guy gets called somewhere else + // and compute only ever returns pre-computed values. either way, this is the central point, and we want it to + // be called as few times as possible. + var continuousAnchors = {}, + continuousAnchorLocations = {}, + continuousAnchorOrientations = {}, + Orientation = { HORIZONTAL : "horizontal", VERTICAL : "vertical", DIAGONAL : "diagonal", IDENTITY:"identity" }, + + // TODO this functions uses a crude method of determining orientation between two elements. + // 'diagonal' should be chosen when the angle of the line between the two centers is around + // one of 45, 135, 225 and 315 degrees. maybe +- 15 degrees. + calculateOrientation = function(sourceId, targetId, sd, td) { + + if (sourceId === targetId) return { + orientation:Orientation.IDENTITY, + a:["top", "top"] + }; + + var theta = Math.atan2((td.centery - sd.centery) , (td.centerx - sd.centerx)), + theta2 = Math.atan2((sd.centery - td.centery) , (sd.centerx - td.centerx)), + h = ((sd.left <= td.left && sd.right >= td.left) || (sd.left <= td.right && sd.right >= td.right) || + (sd.left <= td.left && sd.right >= td.right) || (td.left <= sd.left && td.right >= sd.right)), + v = ((sd.top <= td.top && sd.bottom >= td.top) || (sd.top <= td.bottom && sd.bottom >= td.bottom) || + (sd.top <= td.top && sd.bottom >= td.bottom) || (td.top <= sd.top && td.bottom >= sd.bottom)); + + if (! (h || v)) { + var a = null, rls = false, rrs = false, sortValue = null; + if (td.left > sd.left && td.top > sd.top) + a = ["right", "top"]; + else if (td.left > sd.left && sd.top > td.top) + a = [ "top", "left"]; + else if (td.left < sd.left && td.top < sd.top) + a = [ "top", "right"]; + else if (td.left < sd.left && td.top > sd.top) + a = ["left", "top" ]; + + return { orientation:Orientation.DIAGONAL, a:a, theta:theta, theta2:theta2 }; + } + else if (h) return { + orientation:Orientation.HORIZONTAL, + a:sd.top < td.top ? ["bottom", "top"] : ["top", "bottom"], + theta:theta, theta2:theta2 + } + else return { + orientation:Orientation.VERTICAL, + a:sd.left < td.left ? ["right", "left"] : ["left", "right"], + theta:theta, theta2:theta2 + } + }, + placeAnchorsOnLine = function(desc, elementDimensions, elementPosition, + connections, horizontal, otherMultiplier, reverse) { + var a = [], step = elementDimensions[horizontal ? 0 : 1] / (connections.length + 1); + + for (var i = 0; i < connections.length; i++) { + var val = (i + 1) * step, other = otherMultiplier * elementDimensions[horizontal ? 1 : 0]; + if (reverse) + val = elementDimensions[horizontal ? 0 : 1] - val; + + var dx = (horizontal ? val : other), x = elementPosition[0] + dx, xp = dx / elementDimensions[0], + dy = (horizontal ? other : val), y = elementPosition[1] + dy, yp = dy / elementDimensions[1]; + + a.push([ x, y, xp, yp, connections[i][1], connections[i][2] ]); + } + + return a; + }, + standardEdgeSort = function(a, b) { return a[0] > b[0] ? 1 : -1 }, + currySort = function(reverseAngles) { + return function(a,b) { + var r = true; + if (reverseAngles) { + if (a[0][0] < b[0][0]) + r = true; + else + r = a[0][1] > b[0][1]; + } + else { + if (a[0][0] > b[0][0]) + r= true; + else + r =a[0][1] > b[0][1]; + } + return r === false ? -1 : 1; + }; + }, + leftSort = function(a,b) { + // first get adjusted values + var p1 = a[0][0] < 0 ? -Math.PI - a[0][0] : Math.PI - a[0][0], + p2 = b[0][0] < 0 ? -Math.PI - b[0][0] : Math.PI - b[0][0]; + if (p1 > p2) return 1; + else return a[0][1] > b[0][1] ? 1 : -1; + }, + edgeSortFunctions = { + "top":standardEdgeSort, + "right":currySort(true), + "bottom":currySort(true), + "left":leftSort + }, + _sortHelper = function(_array, _fn) { + return _array.sort(_fn); + }, + placeAnchors = function(elementId, _anchorLists) { + var sS = sizes[elementId], sO = offsets[elementId], + placeSomeAnchors = function(desc, elementDimensions, elementPosition, unsortedConnections, isHorizontal, otherMultiplier) { + if (unsortedConnections.length > 0) { + var sc = _sortHelper(unsortedConnections, edgeSortFunctions[desc]), // puts them in order based on the target element's pos on screen + //sc = unsortedConnections.sort(edgeSortFunctions[desc]), // puts them in order based on the target element's pos on screen + reverse = desc === "right" || desc === "top", + anchors = placeAnchorsOnLine(desc, elementDimensions, + elementPosition, sc, + isHorizontal, otherMultiplier, reverse ); + + // takes a computed anchor position and adjusts it for parent offset and scroll, then stores it. + var _setAnchorLocation = function(endpoint, anchorPos) { + var a = adjustForParentOffsetAndScroll([anchorPos[0], anchorPos[1]], endpoint.canvas); + continuousAnchorLocations[endpoint.id] = [ a[0], a[1], anchorPos[2], anchorPos[3] ]; + }; + + for (var i = 0; i < anchors.length; i++) { + var c = anchors[i][4], weAreSource = c.endpoints[0].elementId === elementId, weAreTarget = c.endpoints[1].elementId === elementId; + if (weAreSource) + _setAnchorLocation(c.endpoints[0], anchors[i]); + else if (weAreTarget) + _setAnchorLocation(c.endpoints[1], anchors[i]); + } + } + }; + + placeSomeAnchors("bottom", sS, [sO.left,sO.top], _anchorLists.bottom, true, 1); + placeSomeAnchors("top", sS, [sO.left,sO.top], _anchorLists.top, true, 0); + placeSomeAnchors("left", sS, [sO.left,sO.top], _anchorLists.left, false, 0); + placeSomeAnchors("right", sS, [sO.left,sO.top], _anchorLists.right, false, 1); + }, + AnchorManager = function() { + var _amEndpoints = {}, + endpointConnectionsByElementId = {}, + continuousAnchorConnectionsByElementId = {}, + continuousAnchorEndpoints = [], + self = this, + anchorLists = {}; + + this.reset = function() { + endpointConnectionsByElementId = {}; + continuousAnchorConnectionsByElementId = {}; + continuousAnchorEndpoints = []; + anchorLists = {}; + }; + + this.newConnection = function(conn) { + var sourceId = conn.sourceId, targetId = conn.targetId, + ep = conn.endpoints, + doRegisterTarget = true, + registerConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + if (otherAnchor.constructor == DynamicAnchor || otherAnchor.constructor == Anchor) { + _addToList(endpointConnectionsByElementId, elId, [c, otherEndpoint, otherAnchor.constructor == DynamicAnchor]); + } + else { + // continuous. if they are the same element, just assign the same anchor + // to both. + if (sourceId == targetId) { + // remove the target endpoint's canvas. we dont need it. + jsPlumb.CurrentLibrary.removeElement(ep[1].canvas); + doRegisterTarget = false; + } + _addToList(continuousAnchorConnectionsByElementId, elId, c); + } + }; + registerConnection(0, ep[0], ep[0].anchor, targetId, conn); + if (doRegisterTarget) + registerConnection(1, ep[1], ep[1].anchor, sourceId, conn); + }; + this.connectionDetached = function(connInfo) { + var sourceId = connInfo.sourceId, + targetId = connInfo.targetId, + ep = connInfo.connection.endpoints, + removeConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + if (otherAnchor.constructor == FloatingAnchor) { + // no-op + } + else if (otherAnchor.constructor == DynamicAnchor || otherAnchor.constructor == Anchor) { + var _conns = endpointConnectionsByElementId[elId]; + if (_conns) { + _removeWithFunction(_conns, function(e) { return e[0].id == c.id; }); + } + } + else // continuous. + _removeWithFunction(continuousAnchorConnectionsByElementId[elId], function(_c) { + return _c.id == c.id; + }); + }; + + removeConnection(1, ep[1], ep[1].anchor, sourceId, connInfo.connection); + removeConnection(0, ep[0], ep[0].anchor, targetId, connInfo.connection); + + // remove from anchorLists + var sEl = connInfo.connection.sourceId, + tEl = connInfo.connection.targetId, + sE = connInfo.connection.endpoints[0].id, + tE = connInfo.connection.endpoints[1].id, + _remove = function(list, eId) { + if (list) { // transient anchors dont get entries in this list. + var f = function(e) { return e[4] == eId; }; + _removeWithFunction(list["top"], f); + _removeWithFunction(list["left"], f); + _removeWithFunction(list["bottom"], f); + _removeWithFunction(list["right"], f); + } + }; + + _remove(anchorLists[sEl], sE); + _remove(anchorLists[tEl], tE); + self.redraw(sEl); + self.redraw(tEl); + }; + this.add = function(endpoint, elementId) { + _addToList(_amEndpoints, elementId, endpoint); + }; + this.get = function(elementId) { + return { + "standard":endpointConnectionsByElementId[elementId] || [], + "continuous":continuousAnchorConnectionsByElementId[elementId] || [], + "endpoints":_amEndpoints[elementId], + "continuousAnchorEndpoints":continuousAnchorEndpoints + }; + }; + this.deleteEndpoint = function(endpoint) { + var cIdx = _findIndex(continuousAnchorEndpoints, endpoint); + if (cIdx > -1) + continuousAnchorEndpoints.splice(cIdx, 1); + else + _removeWithFunction(_amEndpoints[endpoint.elementId], function(e) { + return e.id == endpoint.id; + }); + }; + this.clearFor = function(elementId) { + delete _amEndpoints[elementId]; + _amEndpoints[elementId] = []; + }; + // updates the given anchor list by either updating an existing anchor's info, or adding it. this function + // also removes the anchor from its previous list, if the edge it is on has changed. + // all connections found along the way (those that are connected to one of the faces this function + // operates on) are added to the connsToPaint list, as are their endpoints. in this way we know to repaint + // them wthout having to calculate anything else about them. + var _updateAnchorList = function(lists, theta, order, conn, aBoolean, otherElId, idx, reverse, edgeId, elId, connsToPaint, endpointsToPaint) { + // first try to find the exact match, but keep track of the first index of a matching element id along the way.s + var exactIdx = -1, + firstMatchingElIdx = -1, + endpoint = conn.endpoints[idx], + endpointId = endpoint.id, + oIdx = [1,0][idx], + values = [ [ theta, order ], conn, aBoolean, otherElId, endpointId ], + listToAddTo = lists[edgeId], + listToRemoveFrom = endpoint._continuousAnchorEdge ? lists[endpoint._continuousAnchorEdge] : null; + + if (listToRemoveFrom) { + var rIdx = _findWithFunction(listToRemoveFrom, function(e) { return e[4] == endpointId }); + if (rIdx != -1) { + listToRemoveFrom.splice(rIdx, 1); + // get all connections from this list + for (var i = 0; i < listToRemoveFrom.length; i++) { + _addWithFunction(connsToPaint, listToRemoveFrom[i][1], function(c) { return c.id == listToRemoveFrom[i][1].id }); + _addWithFunction(endpointsToPaint, listToRemoveFrom[i][1].endpoints[idx], function(e) { return e.id == listToRemoveFrom[i][1].endpoints[idx].id }); + } + } + } + + for (var i = 0; i < listToAddTo.length; i++) { + if (idx == 1 && listToAddTo[i][3] === otherElId && firstMatchingElIdx == -1) + firstMatchingElIdx = i; + _addWithFunction(connsToPaint, listToAddTo[i][1], function(c) { return c.id == listToAddTo[i][1].id }); + _addWithFunction(endpointsToPaint, listToAddTo[i][1].endpoints[idx], function(e) { return e.id == listToAddTo[i][1].endpoints[idx].id }); + } + if (exactIdx != -1) { + listToAddTo[exactIdx] = values; + } + else { + var insertIdx = reverse ? firstMatchingElIdx != -1 ? firstMatchingElIdx : 0 : listToAddTo.length; // of course we will get this from having looked through the array shortly. + listToAddTo.splice(insertIdx, 0, values); + } + + // store this for next time. + endpoint._continuousAnchorEdge = edgeId; + }; + this.redraw = function(elementId, ui, timestamp) { + // get all the endpoints for this element + var ep = _amEndpoints[elementId] || [], + endpointConnections = endpointConnectionsByElementId[elementId] || [], + continuousAnchorConnections = continuousAnchorConnectionsByElementId[elementId] || [], + connectionsToPaint = [], + endpointsToPaint = [], + anchorsToUpdate = []; + + timestamp = timestamp || _timestamp(); + + _updateOffset( { elId : elementId, offset : ui, recalc : false, timestamp : timestamp }); + // valid for one paint cycle. + var myOffset = offsets[elementId], + myWH = sizes[elementId], + orientationCache = {}; + + // actually, first we should compute the orientation of this element to all other elements to which + // this element is connected with a continuous anchor (whether both ends of the connection have + // a continuous anchor or just one) + for (var i = 0; i < continuousAnchorConnections.length; i++) { + var conn = continuousAnchorConnections[i], + sourceId = conn.sourceId, + targetId = conn.targetId, + oKey = sourceId + "_" + targetId, + oKey2 = targetId + "_" + sourceId, + o = orientationCache[oKey], + td = _getCachedData(targetId), + sd = _getCachedData(sourceId), + oIdx = conn.sourceId == elementId ? 1 : 0; + + if (!anchorLists[sourceId]) anchorLists[sourceId] = { top:[], right:[], bottom:[], left:[] }; + if (!anchorLists[targetId]) anchorLists[targetId] = { top:[], right:[], bottom:[], left:[] }; + + if (targetId == sourceId) { + // here we may want to improve this by somehow determining the face we'd like + // to put the connector on. ideally, when drawing, the face should be calculated + // by determining which face is closest to the point at which the mouse button + // was released. for now, we're putting it on the top face. + _updateAnchorList(anchorLists[sourceId], -Math.PI / 2, 0, conn, false, targetId, 0, false, "top", sourceId, connectionsToPaint, endpointsToPaint) + } + else { + if (!o) { + o = calculateOrientation(sourceId, targetId, sd.o, td.o); + orientationCache[oKey] = o; + // this would be a performance enhancement, but the computed angles need to be clamped to + //the (-PI/2 -> PI/2) range in order for the sorting to work properly. + /* orientationCache[oKey2] = { + orientation:o.orientation, + a:[o.a[1], o.a[0]], + theta:o.theta + Math.PI, + theta2:o.theta2 + Math.PI + };*/ + } + _updateAnchorList(anchorLists[sourceId], o.theta, 0, conn, false, targetId, 0, false, o.a[0], sourceId, connectionsToPaint, endpointsToPaint); + _updateAnchorList(anchorLists[targetId], o.theta2, -1, conn, true, sourceId, 1, true, o.a[1], targetId, connectionsToPaint, endpointsToPaint); + } + + _addWithFunction(anchorsToUpdate, sourceId, function(a) { return a === sourceId; }); + _addWithFunction(anchorsToUpdate, targetId, function(a) { return a === targetId; }); + _addWithFunction(connectionsToPaint, conn, function(c) { return c.id == conn.id; }); + _addWithFunction(endpointsToPaint, conn.endpoints[oIdx], function(e) { return e.id == conn.endpoints[oIdx].id; }); + } + + // now place all the continuous anchors we need to; + for (var i = 0; i < anchorsToUpdate.length; i++) { + placeAnchors(anchorsToUpdate[i], anchorLists[anchorsToUpdate[i]]); + } + + // now that continuous anchors have been placed, paint all the endpoints for this element + // TODO performance: add the endpoint ids to a temp array, and then when iterating in the next + // loop, check that we didn't just paint that endpoint. we can probably shave off a few more milliseconds this way. + for (var i = 0; i < ep.length; i++) { + ep[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + // ... and any other endpoints we came across as a result of the continuous anchors. + for (var i = 0; i < endpointsToPaint.length; i++) { + endpointsToPaint[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + + + // paint all the standard and "dynamic connections", which are connections whose other anchor is + // static and therefore does need to be recomputed; we make sure that happens only one time. + for (var i = 0; i < endpointConnections.length; i++) { + var otherEndpoint = endpointConnections[i][1]; + if (otherEndpoint.anchor.constructor == DynamicAnchor) { + otherEndpoint.paint({ elementWithPrecedence:elementId }); + _addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; }); + // all the connections for the other endpoint now need to be repainted + for (var k = 0; k < otherEndpoint.connections.length; k++) { + if (otherEndpoint.connections[k] !== endpointConnections[i][0]) + _addWithFunction(connectionsToPaint, otherEndpoint.connections[k], function(c) { return c.id == otherEndpoint.connections[k].id; }); + } + } else if (otherEndpoint.anchor.constructor == Anchor) { + _addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; }); + } + } + // paint current floating connection for this element, if there is one. + var fc = floatingConnections[elementId]; + if (fc) + fc.paint({timestamp:timestamp, recalc:false, elId:elementId}); + + // paint all the connections + for (var i = 0; i < connectionsToPaint.length; i++) { + connectionsToPaint[i].paint({elId:elementId, timestamp:timestamp, recalc:false}); + } + }; + this.rehomeEndpoint = function(currentId, element) { + var eps = _amEndpoints[currentId] || [], //, + elementId = _currentInstance.getId(element); + for (var i = 0; i < eps.length; i++) { + self.add(eps[i], elementId); + } + eps.splice(0, eps.length); + }; + }; + _currentInstance.anchorManager = new AnchorManager(); + _currentInstance.continuousAnchorFactory = { + get:function(params) { + var existing = continuousAnchors[params.elementId]; + if (!existing) { + existing = { + type:"Continuous", + compute : function(params) { + return continuousAnchorLocations[params.element.id] || [0,0]; + }, + getCurrentLocation : function(endpoint) { + return continuousAnchorLocations[endpoint.id] || [0,0]; + }, + getOrientation : function(endpoint) { + return continuousAnchorOrientations[endpoint.id] || [0,0]; + }, + isDynamic : true, + isContinuous : true + }; + continuousAnchors[params.elementId] = existing; + } + return existing; + } + }; + + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * container - optional id or selector instructing jsPlumb where to attach all the elements it creates for this connection. you should read the documentation for a full discussion of this. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * overlays - Optional array of Overlay definitions to appear on this Connection. + * drawEndpoints - if false, instructs jsPlumb to not draw the endpoints for this Connection. Be careful with this: it only really works when you tell jsPlumb to attach elements to the document body. Read the documentation for a full discussion of this. + */ + var Connection = function(params) { + var self = this, visible = true; + self.idPrefix = "_jsplumb_c_"; + self.defaultLabelLocation = 0.5; + self.defaultOverlayKeys = ["Overlays", "ConnectionOverlays"]; + this.parent = params.parent; + overlayCapableJsPlumbUIComponent.apply(this, arguments); + // ************** get the source and target and register the connection. ******************* + + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none"; + }; + + /** + Property: source + The source element for this Connection. + */ + this.source = _getElementObject(params.source); + /** + Property:target + The target element for this Connection. + */ + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. but + // source is not overridden if the Endpoint has declared it is not the final target of a connection; + // instead we use the source that the Endpoint declares will be the final source element. + if (params.sourceEndpoint) { + this.source = params.sourceEndpoint.endpointWillMoveTo || params.sourceEndpoint.getElement(); + } + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + + // if a new connection is the result of moving some existing connection, params.previousConnection + // will have that Connection in it. listeners for the jsPlumbConnection event can look for that + // member and take action if they need to. + self.previousConnection = params.previousConnection; + + var _cost = params.cost; + self.getCost = function() { return _cost; }; + self.setCost = function(c) { _cost = c; }; + + var _bidirectional = params.bidirectional === true; + self.isBidirectional = function() { return _bidirectional; }; + + /* + * Property: sourceId + * Id of the source element in the connection. + */ + this.sourceId = _getAttribute(this.source, "id"); + /* + * Property: targetId + * Id of the target element in the connection. + */ + this.targetId = _getAttribute(this.target, "id"); + + /** + * implementation of abstract method in EventGenerator + * @return list of attached elements. in our case, a list of Endpoints. + */ + this.getAttachedElements = function() { + return self.endpoints; + }; + + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams, elementId) { + if (anchorParams) + return _currentInstance.makeAnchor(anchorParams, elementId, _currentInstance); + }, + prepareEndpoint = function(existing, index, params, element, elementId, connectorPaintStyle, connectorHoverPaintStyle) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] + || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint; + + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified. + if (es.fillStyle == null && connectorPaintStyle != null) + es.fillStyle = connectorPaintStyle.strokeStyle; + + // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does: + //* + if (es.outlineColor == null && connectorPaintStyle != null) + es.outlineColor = connectorPaintStyle.outlineColor; + if (es.outlineWidth == null && connectorPaintStyle != null) + es.outlineWidth = connectorPaintStyle.outlineWidth; + //*/ + + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure? + if (connectorHoverPaintStyle != null) { + if (ehs == null) ehs = {}; + if (ehs.fillStyle == null) { + ehs.fillStyle = connectorHoverPaintStyle.strokeStyle; + } + } + var a = params.anchors ? params.anchors[index] : + params.anchor ? params.anchor : + _makeAnchor(_currentInstance.Defaults.Anchors[index], elementId) || + _makeAnchor(jsPlumb.Defaults.Anchors[index], elementId) || + _makeAnchor(_currentInstance.Defaults.Anchor, elementId) || + _makeAnchor(jsPlumb.Defaults.Anchor, elementId), + u = params.uuids ? params.uuids[index] : null, + e = _newEndpoint({ + paintStyle : es, + hoverPaintStyle:ehs, + endpoint : ep, + connections : [ self ], + uuid : u, + anchor : a, + source : element, + container:params.container, + reattach:params.reattach, + detachable:params.detachable + }); + self.endpoints[index] = e; + + + if (params.drawEndpoints === false) e.setVisible(false, true, true); + + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, + 0, + params, + self.source, + self.sourceId, + params.paintStyle, + params.hoverPaintStyle); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + + // if there were no endpoints supplied and the source element is the target element, we will reuse the source + // endpoint that was just created. + var existingTargetEndpoint = ((self.sourceId == self.targetId) && params.targetEndpoint == null) ? eS : params.targetEndpoint, + eT = prepareEndpoint(existingTargetEndpoint, + 1, + params, + self.target, + self.targetId, + params.paintStyle, + params.hoverPaintStyle); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + // if delete endpoints on detach, keep a record of just exactly which endpoints they are. + if (params.deleteEndpointsOnDetach) + self.endpointsToDeleteOnDetach = [eS, eT]; + + var _detachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.detachable === false) _detachable = false; + if(self.endpoints[0].connectionsDetachable === false) _detachable = false; + if(self.endpoints[1].connectionsDetachable === false) _detachable = false; + + // inherit connectin cost if it was set on source endpoint + if (_cost == null) _cost = self.endpoints[0].getConnectionCost(); + // inherit bidirectional flag if set no source endpoint + if (params.bidirectional == null) _bidirectional = self.endpoints[0].areConnectionsBidirectional(); + + /* + Function: isDetachable + Returns whether or not this connection can be detached from its target/source endpoint. by default this + is false; use it in conjunction with the 'reattach' parameter. + */ + this.isDetachable = function() { + return _detachable === true; + }; + + /* + Function: setDetachable + Sets whether or not this connection is detachable. + */ + this.setDetachable = function(detachable) { + _detachable = detachable === true; + }; + + // merge all the parameters objects into the connection. parameters set + // on the connection take precedence; then target endpoint params, then + // finally source endpoint params. + // TODO jsPlumb.extend could be made to take more than two args, and it would + // apply the second through nth args in order. + var _p = jsPlumb.extend({}, this.endpoints[0].getParameters()); + jsPlumb.extend(_p, this.endpoints[1].getParameters()); + jsPlumb.extend(_p, self.getParameters()); + self.setParameters(_p); + + // override setHover to pass it down to the underlying connector + var _sh = self.setHover; + + self.setHover = function() { + self.connector.setHover.apply(self.connector, arguments); + _sh.apply(self, arguments); + }; + + var _internalHover = function(state) { + if (_connectionBeingDragged == null) { + self.setHover(state, false); + } + }; + + /* + * Function: setConnector + * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method - the same + * thing that you would set as the 'connector' property on a jsPlumb.connect call. + * + * Parameters: + * connector - Connector definition + */ + this.setConnector = function(connector, doNotRepaint) { + if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent); + var connectorArgs = { + _jsPlumb:self._jsPlumb, + parent:params.parent, + cssClass:params.cssClass, + container:params.container, + tooltip:self.tooltip + }; + if (connector.constructor == String) + this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand. + else if (connector.constructor == Array) + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs)); + self.canvas = self.connector.canvas; + // binds mouse listeners to the current connector. + _bindListeners(self.connector, self, _internalHover); + if (!doNotRepaint) self.repaint(); + }; + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc) + */ + + self.setConnector(this.endpoints[0].connector || + this.endpoints[1].connector || + params.connector || + _currentInstance.Defaults.Connector || + jsPlumb.Defaults.Connector, true); + + + this.setPaintStyle(this.endpoints[0].connectorStyle || + this.endpoints[1].connectorStyle || + params.paintStyle || + _currentInstance.Defaults.PaintStyle || + jsPlumb.Defaults.PaintStyle, true); + + this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || + this.endpoints[1].connectorHoverStyle || + params.hoverPaintStyle || + _currentInstance.Defaults.HoverPaintStyle || + jsPlumb.Defaults.HoverPaintStyle, true); + + this.paintStyleInUse = this.paintStyle; + + + this.moveParent = function(newParent) { + var jpcl = jsPlumb.CurrentLibrary, curParent = jpcl.getParent(self.connector.canvas); + jpcl.removeElement(self.connector.canvas, curParent); + jpcl.appendElement(self.connector.canvas, newParent); + if (self.connector.bgCanvas) { + jpcl.removeElement(self.connector.bgCanvas, curParent); + jpcl.appendElement(self.connector.bgCanvas, newParent); + } + // this only applies for DOMOverlays + for (var i = 0; i < self.overlays.length; i++) { + if (self.overlays[i].isAppendedAtTopLevel) { + jpcl.removeElement(self.overlays[i].canvas, curParent); + jpcl.appendElement(self.overlays[i].canvas, newParent); + if (self.overlays[i].reattachListeners) + self.overlays[i].reattachListeners(self.connector); + } + } + if (self.connector.reattachListeners) // this is for SVG/VML; change an element's parent and you have to reinit its listeners. + self.connector.reattachListeners(); // the Canvas implementation doesn't have to care about this + }; + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Connection. + * + * Parameters: + * event - the event to bind. Available events on a Connection are: + * - *click* : notification that a Connection was clicked. + * - *dblclick* : notification that a Connection was double clicked. + * - *mouseenter* : notification that the mouse is over a Connection. + * - *mouseexit* : notification that the mouse exited a Connection. + * + * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Connection's paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Connection will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Connection. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId], + otherOffset = offsets[this.targetId], + otherWH = sizes[this.targetId], + initialTimestamp = _timestamp(), + anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + elementId:this.endpoints[0].elementId, + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1], + timestamp:initialTimestamp + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc, timestamp:initialTimestamp }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + elementId:this.endpoints[1].elementId, + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0], + timestamp:initialTimestamp + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc, timestamp:initialTimestamp }); + + /* + * Paints the Connection. Not exposed for public usage. + * + * Parameters: + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp, + // if the moving object is not the source we must transpose the two references. + swap = false, + tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId, + tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + + var sourceInfo = _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }), + targetInfo = _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var sE = this.endpoints[sIdx], tE = this.endpoints[tIdx], + sAnchorP = sE.anchor.getCurrentLocation(sE), + tAnchorP = tE.anchor.getCurrentLocation(tE); + + /* paint overlays*/ + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible()) maxSize = Math.max(maxSize, o.computeMaxSize(self.connector)); + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, + this.endpoints[sIdx], this.endpoints[tIdx], + this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, + self.paintStyleInUse.lineWidth, maxSize, + sourceInfo, + targetInfo); + + self.connector.paint(dim, self.paintStyleInUse); + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim); + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function(params) { + params = params || {}; + var recalc = !(params.recalc === false); + this.paint({ elId : this.sourceId, recalc : recalc, timestamp:params.timestamp }); + }; + + // just to make sure the UI gets initialised fully on all browsers. + self.repaint(); + }; + +// ENDPOINT HELPER FUNCTIONS + var _makeConnectionDragHandler = function(placeholder) { + var stopped = false; + return { + drag : function() { + if (stopped) return true; + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments), + el = placeholder.element; + if (el) { + jsPlumb.CurrentLibrary.setOffset(el, _ui); + _draw(_getElementObject(el), _ui); + } + }, + stopDrag : function() { + stopped = true; + } + }; + }; + + var _makeFloatingEndpoint = function(paintStyle, referenceAnchor, endpoint, referenceCanvas, sourceElement) { + var floatingAnchor = new FloatingAnchor( { reference : referenceAnchor, referenceCanvas : referenceCanvas }); + + //setting the scope here should not be the way to fix that mootools issue. it should be fixed by not + // adding the floating endpoint as a droppable. that makes more sense anyway! + + return _newEndpoint({ paintStyle : paintStyle, endpoint : endpoint, anchor : floatingAnchor, source : sourceElement, scope:"__floating" }); + }; + + /** + * creates a placeholder div for dragging purposes, adds it to the DOM, and pre-computes its offset. then returns + * both the element id and a selector for the element. + */ + var _makeDraggablePlaceholder = function(placeholder, parent) { + var n = document.createElement("div"); + n.style.position = "absolute"; + var placeholderDragElement = _getElementObject(n); + _appendElement(n, parent); + var id = _getId(placeholderDragElement); + _updateOffset( { elId : id }); + // create and assign an id, and initialize the offset. + placeholder.id = id; + placeholder.element = placeholderDragElement; + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1 + * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't + * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you + * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or + * 'target' Endpoints for Connections. + * + * + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * container - optional id or selector instructing jsPlumb where to attach the element it creates for this endpoint. you should read the documentation for a full discussion of this. + * connections - optional list of Connections to configure the Endpoint with. + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + var self = this; + self.idPrefix = "_jsplumb_e_"; + self.defaultLabelLocation = [ 0.5, 0.5 ]; + self.defaultOverlayKeys = ["Overlays", "EndpointOverlays"]; + this.parent = params.parent; + overlayCapableJsPlumbUIComponent.apply(this, arguments); + params = params || {}; + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Endpoint. + * + * Parameters: + * event - the event to bind. Available events on an Endpoint are: + * - *click* : notification that a Endpoint was clicked. + * - *dblclick* : notification that a Endpoint was double clicked. + * - *mouseenter* : notification that the mouse is over a Endpoint. + * - *mouseexit* : notification that the mouse exited a Endpoint. + * + * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Endpoint's paint style and then repaints the Endpoint. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Endpoint will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Endpoint. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + var visible = true; + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + var _element = params.source, _uuid = params.uuid, floatingEndpoint = null, inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + this.element = _element; + + var _connectionCost = params.connectionCost; + this.getConnectionCost = function() { return _connectionCost; }; + this.setConnectionCost = function(c) { + _connectionCost = c; + }; + + var _connectionsBidirectional = params.connectionsBidirectional === true; + this.areConnectionsBidirectional = function() { return _connectionsBidirectional; }; + this.setConnectionsBidirectional = function(b) { _connectionsBidirectional = b; }; + + self.anchor = params.anchor ? _currentInstance.makeAnchor(params.anchor, _elementId, _currentInstance) : params.anchors ? _currentInstance.makeAnchor(params.anchors, _elementId, _currentInstance) : _currentInstance.makeAnchor("TopCenter", _elementId, _currentInstance); + + // ANCHOR MANAGER + if (!params._transient) // in place copies, for example, are transient. they will never need to be retrieved during a paint cycle, because they dont move, and then they are deleted. + _currentInstance.anchorManager.add(self, _elementId); + + var _endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot", + endpointArgs = { + _jsPlumb:self._jsPlumb, + parent:params.parent, + container:params.container, + tooltip:params.tooltip, + connectorTooltip:params.connectorTooltip, + endpoint:self + }; + if (_endpoint.constructor == String) + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint](endpointArgs); + else if (_endpoint.constructor == Array) { + endpointArgs = jsPlumb.extend(_endpoint[1], endpointArgs); + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint[0]](endpointArgs); + } + else { + _endpoint = _endpoint.clone(); + } + + // assign a clone function using a copy of endpointArgs. this is used when a drag starts: the endpoint that was dragged is cloned, + // and the clone is left in its place while the original one goes off on a magical journey. + // the copy is to get around a closure problem, in which endpointArgs ends up getting shared by + // the whole world. + var argsForClone = jsPlumb.extend({}, endpointArgs); + _endpoint.clone = function() { + var o = new Object(); + _endpoint.constructor.apply(o, [argsForClone]); + return o; + }; + + self.endpoint = _endpoint; + self.type = self.endpoint.type; + // override setHover to pass it down to the underlying endpoint + var _sh = self.setHover; + self.setHover = function() { + self.endpoint.setHover.apply(self.endpoint, arguments); + _sh.apply(self, arguments); + }; + // endpoint delegates to first connection for hover, if there is one. + var internalHover = function(state) { + if (self.connections.length > 0) + self.connections[0].setHover(state, false); + else + self.setHover(state); + }; + + // bind listeners from endpoint to self, with the internal hover function defined above. + _bindListeners(self.endpoint, self, internalHover); + + this.setPaintStyle(params.paintStyle || + params.style || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyle, true); + this.setHoverPaintStyle(params.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyle, true); + this.paintStyleInUse = this.paintStyle; + this.connectorStyle = params.connectorStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.connectorTooltip = params.connectorTooltip; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + + var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + /* + * Property: canvas + * The Endpoint's Canvas. + */ + this.canvas = this.endpoint.canvas; + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + this.connections = params.connections || []; + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + self.isReattach = params.reattach || false; + self.connectionsDetachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.connectionsDetachable === false || params.detachable === false) + self.connectionsDetachable = false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget, forceDetach, fireEvent) { + //var idx = _findIndex(self.connections, connection), actuallyDetached = false; + var idx = _findWithFunction(self.connections, function(c) { return c.id == connection.id}), + actuallyDetached = false; + fireEvent = (fireEvent !== false); + if (idx >= 0) { + // 1. does the connection have a before detach (note this also checks jsPlumb's bound + // detach handlers; but then Endpoint's check will, too, hmm.) + if (forceDetach || connection._forceDetach || connection.isDetachable() || connection.isDetachAllowed(connection)) { + // get the target endpoint + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + // it would be nice to check with both endpoints that it is ok to detach. but + // for this we'll have to get a bit fancier: right now if you use the same beforeDetach + // interceptor for two endpoints (which is kind of common, because it's part of the + // endpoint definition), then it gets fired twice. so in fact we need to loop through + // each beforeDetach and see if it returns false, at which point we exit. but if it + // returns true, we have to check the next one. however we need to track which ones + // have already been run, and not run them again. + if (forceDetach || connection._forceDetach || (self.isDetachAllowed(connection) /*&& t.isDetachAllowed(connection)*/)) { + + self.connections.splice(idx, 1); + + // this avoids a circular loop + if (!ignoreTarget) { + + t.detach(connection, true, forceDetach); + // check connection to see if we want to delete the endpoints associated with it. + // we only detach those that have just this connection; this scenario is most + // likely if we got to this bit of code because it is set by the methods that + // create their own endpoints, like .connect or .makeTarget. the user is + // not likely to have interacted with those endpoints. + if (connection.endpointsToDeleteOnDetach){ + for (var i = 0; i < connection.endpointsToDeleteOnDetach.length; i++) { + var cde = connection.endpointsToDeleteOnDetach[i]; + if (cde && cde.connections.length == 0) + _currentInstance.deleteEndpoint(cde); + } + } + } + _removeElements(connection.connector.getDisplayElements(), connection.parent); + _removeWithFunction(connectionsByScope[connection.scope], function(c) { + return c.id == connection.id; + }); + actuallyDetached = true; + var doFireEvent = (!ignoreTarget && fireEvent) + fireDetachEvent(connection, doFireEvent); + } + } + } + return actuallyDetached; + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + * + * Parameters: + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachAll = function(fireEvent) { + while (self.connections.length > 0) { + self.detach(self.connections[0], false, true, fireEvent); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachFrom = function(targetEndpoint, fireEvent) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + if (self.detach(c[i], false, true, fireEvent)) + c[i].setHover(false, false); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: setElement + * Sets the DOM element this Endpoint is attached to. + */ + this.setElement = function(el) { + // TODO possibly have this object take charge of moving the UI components into the appropriate + // parent. this is used only by makeSource right now, and that function takes care of + // moving the UI bits and pieces. however it would s + var parentId = _getId(el); + // remove the endpoint from the list for the current endpoint's element + _removeWithFunction(endpointsByElement[_elementId], function(e) { + return e.id == self.id; + }); + _element = _getElementObject(el); + _elementId = _getId(_element); + self.elementId = _elementId; + + // need to get the new parent now + var newParentElement = _getParentFromParams({source:parentId}), + curParent = jpcl.getParent(self.canvas); + jpcl.removeElement(self.canvas, curParent); + jpcl.appendElement(self.canvas, newParentElement); + + // now move connection(s)...i would expect there to be only one but we will iterate. + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].moveParent(newParentElement); + self.connections[i].sourceId = _elementId; + self.connections[i].source = _element; + } + + _addToList(endpointsByElement, parentId, self); + _currentInstance.repaint(parentId); + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + return _newEndpoint( { + anchor : self.anchor, + source : _element, + paintStyle : this.paintStyle, + endpoint : _endpoint, + _transient:true, + scope:self.scope + }); + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + var candidate = self.connections[0]; + if (self.isTarget && candidate) return candidate; + else { + return (self.connections.length < _maxConnections) || _maxConnections == -1 ? null : candidate; + } + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + /* + * Function: setStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to + * setStyle and deprecate it if so. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + * + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + // a helper function that tries to find a connection to the given element, and returns it if so. if elementWithPrecedence is null, + // or no connection to it is found, we return the first connection in our list. + var findConnectionToUseForDynamicAnchor = function(elementWithPrecedence) { + var idx = 0; + if (elementWithPrecedence != null) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].sourceId == elementWithPrecedence || self.connections[i].targetId == elementWithPrecedence) { + idx = i; + break; + } + } + } + + return self.connections[idx]; + }; + + /* + * Function: paint + * Paints the Endpoint, recalculating offset and anchor positions if necessary. This does NOT paint + * any of the Endpoint's connections. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + params = params || {}; + var timestamp = params.timestamp, + recalc = !(params.recalc === false); + if (!timestamp || self.timestamp !== timestamp) { + _updateOffset({ elId:_elementId, timestamp:timestamp, recalc:recalc }); + var xy = params.offset || offsets[_elementId]; + if(xy) { + var ap = params.anchorPoint,connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (recalc && self.anchor.isDynamic && self.connections.length > 0) { + var c = findConnectionToUseForDynamicAnchor(params.elementWithPrecedence), + oIdx = c.endpoints[0] == self ? 1 : 0, + oId = oIdx == 0 ? c.sourceId : c.targetId, + oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + ap = self.anchor.compute(anchorParams); + } + + var d = _endpoint.compute(ap, self.anchor.getOrientation(_endpoint), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + _endpoint.paint(d, self.paintStyleInUse, self.anchor); + self.timestamp = timestamp; + + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) self.overlayPlacements[i] = o.draw(self.endpoint, self.paintStyleInUse, d); + } + } + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var placeholderInfo = { + id:null, + element:null + }, + jpc = null, + existingJpc = false, + existingJpcParams = null, + _dragHandler = _makeConnectionDragHandler(placeholderInfo); + + var start = function() { + // drag might have started on an endpoint that is not actually a source, but which has + // one or more connections. + jpc = self.connectorSelector(); + var _continue = true; + // if no connection and we're not a source, return. + if (jpc == null && !params.isSource) _continue = false; + // otherwise if we're full and not allowed to drag, also return false. + if (params.isSource && self.isFull() && !dragAllowedWhenFull) _continue = false; + + // if the connection was setup as not detachable (or one of its endpoints + // was setup as connectionsDetachable = false, or Defaults.ConnectionsDetachable + // is set to false... + if (jpc != null && !jpc.isDetachable()) _continue = false; + + if (_continue === false) { + // this is for mootools and yui. returning false from this causes jquery to stop drag. + // the events are wrapped in both mootools and yui anyway, but i don't think returning + // false from the start callback would stop a drag. + if (jsPlumb.CurrentLibrary.stopDrag) jsPlumb.CurrentLibrary.stopDrag(); + _dragHandler.stopDrag(); + return false; + } + + // if we're not full but there was a connection, make it null. we'll create a new one. + if (jpc && !self.isFull() && params.isSource) jpc = null; + + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + _makeDraggablePlaceholder(placeholderInfo, self.parent); + + // set the offset of this div to be where 'inPlaceCopy' is, to start with. + // TODO merge this code with the code in both Anchor and FloatingAnchor, because it + // does the same stuff. + var ipcoel = _getElementObject(inPlaceCopy.canvas), + ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel), + po = adjustForParentOffsetAndScroll([ipco.left, ipco.top], inPlaceCopy.canvas); + jsPlumb.CurrentLibrary.setOffset(placeholderInfo.element, {left:po[0], top:po[1]}); + + // when using makeSource and a parent, we first draw the source anchor on the source element, then + // move it to the parent. note that this happens after drawing the placeholder for the + // first time. + if (self.parentAnchor) self.anchor = _currentInstance.makeAnchor(self.parentAnchor, self.elementId, _currentInstance); + + + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", placeholderInfo.id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + floatingEndpoint = _makeFloatingEndpoint(self.paintStyle, self.anchor, _endpoint, self.canvas, placeholderInfo.element); + + if (jpc == null) { + self.anchor.locked = true; + self.setHover(false, false); + // TODO the hover call above does not reset any target endpoint's hover + // states. + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : self.endpointWillMoveTo || _getElementObject(_element), // for makeSource with parent option. ensure source element is represented correctly. + target : placeholderInfo.element, + anchors : [ self.anchor, floatingEndpoint.anchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays + }); + + } else { + existingJpc = true; + jpc.connector.setHover(false, false); + // jpc.endpoints[0].setHover(false, false); + //jpc.endpoints[1].setHover(false, false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas), false, true); + //var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; // are we the source or the target? + // new anchor idx + var anchorIdx = jpc.endpoints[0].id == self.id ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas), + dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // now we want to get this endpoint's DROP scope, and set it for now: we can only be dropped on drop zones + // that have our drop scope (issue 57). + var dropScope = jsPlumb.CurrentLibrary.getDropScope(c); + jsPlumb.CurrentLibrary.setDragScope(c, dropScope); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = placeholderInfo.element; + jpc.sourceId = placeholderInfo.id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = placeholderInfo.element; + jpc.targetId = placeholderInfo.id; + } + + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.suspendedEndpoint.setHover(false); + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + // register it and register connection on it. + floatingConnections[placeholderInfo.id] = jpc; + floatingEndpoint.addConnection(jpc); + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, placeholderInfo.id, floatingEndpoint); + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var jpcl = jsPlumb.CurrentLibrary, + dragOptions = params.dragOptions || {}, + defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions), + startEvent = jpcl.dragEvents["start"], + stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"]; + + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + // extracted drag handler function so can be used by makeSource + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], _dragHandler.drag); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _currentInstance.currentlyDragging = false; + _removeWithFunction(endpointsByElement[placeholderInfo.id], function(e) { + return e.id == floatingEndpoint.id; + }); + _removeElements( [ placeholderInfo.element[0], floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + _currentInstance.anchorManager.clearFor(placeholderInfo.id); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (self.isReattach || jpc._forceDetach || !jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc)) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + _currentInstance.repaint(existingJpcParams[1]); + } + jpc._forceDetach = null; + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElements(jpc.connector.getDisplayElements(), self.parent); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint({recalc:false}); + jpc.setHover(false, false); + jpc = null; + inPlaceCopy = null; + delete endpointsByElement[floatingEndpoint.elementId]; + floatingEndpoint.anchor = null; + floatingEndpoint = null; + _currentInstance.currentlyDragging = false; + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions, true); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas, forceInit, isTransient) { + if ((params.isTarget || forceInit) && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop'], + overEvent = jsPlumb.CurrentLibrary.dragEvents['over'], + outEvent = jsPlumb.CurrentLibrary.dragEvents['out'], + drop = function(originalEvent) { + var draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + elId = _getAttribute(draggable, "elId"), + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id], + idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + + // restore the original scope if necessary (issue 57) + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + + var _doContinue = true; + // the second check here is for the case that the user is dropping it back + // where it came from. + if (jpc.suspendedEndpoint && jpc.suspendedEndpoint.id != self.id) { + if (!jpc.isDetachAllowed(jpc) || !jpc.endpoints[idx].isDetachAllowed(jpc) || !jpc.suspendedEndpoint.isDetachAllowed(jpc) || !_currentInstance.checkCondition("beforeDetach", jpc)) + _doContinue = false; + } + + // these have to be set before testing for beforeDrop. + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + + // now check beforeDrop. this will be available only on Endpoints that are setup to + // have a beforeDrop condition (although, secretly, under the hood all Endpoints and + // the Connection have them, because they are on jsPlumbUIComponent. shhh!), because + // it only makes sense to have it on a target endpoint. + _doContinue = _doContinue && self.isDropAllowed(jpc.sourceId, jpc.targetId, jpc.scope); + + if (_doContinue) { + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + if (!jpc.suspendedEndpoint) { + //_addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + fireDetachEvent({ + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }, true); + } + + // finalise will inform the anchor manager and also add to + // connectionsByScope if necessary. + _finaliseConnection(jpc, null, originalEvent); + } + else { + // otherwise just put it back on the endpoint it was on before the drag. + if (jpc.suspendedEndpoint) { + // self.detachFrom(jpc); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.setHover(false); + jpc._forceDetach = true; + if (idx == 0) { + jpc.source = jpc.suspendedEndpoint.element; + jpc.sourceId = jpc.suspendedEndpoint.elementId; + } else { + jpc.target = jpc.suspendedEndpoint.element; + jpc.targetId = jpc.suspendedEndpoint.elementId;; + } + jpc.suspendedEndpoint.addConnection(jpc); + + jpc.endpoints[0].repaint(); + jpc.repaint(); + _currentInstance.repaint(jpc.source.elementId); + jpc._forceDetach = false; + } + } + + jpc.floatingAnchorIndex = null; + } + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], function() { + if (self.isTarget) { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + } + } + }); + dropOptions[outEvent] = _wrap(dropOptions[outEvent], function() { + if (self.isTarget) { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + } + } + }); + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions, true, isTransient); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas), true, !(params._transient || self.anchor.isFloating)); + + return self; + }; + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + j.init(); + return j; + }; + jsPlumb.util = { + convertStyle : function(s, ignoreAlpha) { + // TODO: jsPlumb should support a separate 'opacity' style member. + if ("transparent" === s) return s; + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + return o; + }, + gradient : function(p1, p2) { + p1 = p1.constructor == Array ? p1 : [p1.x, p1.y]; + p2 = p2.constructor == Array ? p2 : [p2.x, p2.y]; + return (p2[1] - p1[1]) / (p2[0] - p1[0]); + }, + normal : function(p1, p2) { + return -1 / jsPlumb.util.gradient(p1,p2); + }, + segment : function(p1, p2) { + p1 = p1.constructor == Array ? p1 : [p1.x, p1.y]; + p2 = p2.constructor == Array ? p2 : [p2.x, p2.y]; + if (p2[0] > p1[0]) { + return (p2[1] > p1[1]) ? 2 : 1; + } + else { + return (p2[1] > p1[1]) ? 3 : 4; + } + }, + segmentMultipliers : [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ], + inverseSegmentMultipliers : [null, [-1, -1], [-1, 1], [1, 1], [1, -1] ], + pointOnLine : function(fromPoint, toPoint, distance) { + var m = jsPlumb.util.gradient(fromPoint, toPoint), + s = jsPlumb.util.segment(fromPoint, toPoint), + segmentMultiplier = distance > 0 ? jsPlumb.util.segmentMultipliers[s] : jsPlumb.util.inverseSegmentMultipliers[s], + theta = Math.atan(m), + y = Math.abs(distance * Math.sin(theta)) * segmentMultiplier[1], + x = Math.abs(distance * Math.cos(theta)) * segmentMultiplier[0]; + return { x:fromPoint.x + x, y:fromPoint.y + y }; + }, + /** + * calculates a perpendicular to the line fromPoint->toPoint, that passes through toPoint and is 'length' long. + * @param fromPoint + * @param toPoint + * @param length + */ + perpendicularLineTo : function(fromPoint, toPoint, length) { + var m = jsPlumb.util.gradient(fromPoint, toPoint), + theta2 = Math.atan(-1 / m), + y = length / 2 * Math.sin(theta2), + x = length / 2 * Math.cos(theta2); + return [{x:toPoint.x + x, y:toPoint.y + y}, {x:toPoint.x - x, y:toPoint.y - y}]; + } + }; + + var _curryAnchor = function(x, y, ox, oy, type, fnInit) { + return function(params) { + params = params || {}; + //var a = jsPlumb.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance); + var a = params.jsPlumbInstance.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance); + a.type = type; + if (fnInit) fnInit(a, params); + return a; + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1, "TopCenter"); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1, "BottomCenter"); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0, "LeftMiddle"); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0, "RightMiddle"); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0, "Center"); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1, "TopRight"); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1, "BottomRight"); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1, "TopLeft"); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1, "BottomLeft"); + + // TODO test that this does not break with the current instance idea + jsPlumb.Defaults.DynamicAnchors = function(params) { + return params.jsPlumbInstance.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"], params.elementId, params.jsPlumbInstance); + }; + jsPlumb.Anchors["AutoDefault"] = function(params) { + var a = params.jsPlumbInstance.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors(params)); + a.type = "AutoDefault"; + return a; + }; + + jsPlumb.Anchors["Assign"] = _curryAnchor(0,0,0,0,"Assign", function(anchor, params) { + // find what to use as the "position finder". the user may have supplied a String which represents + // the id of a position finder in jsPlumb.AnchorPositionFinders, or the user may have supplied the + // position finder as a function. we find out what to use and then set it on the anchor. + var pf = params.position || "Fixed"; + anchor.positionFinder = pf.constructor == String ? params.jsPlumbInstance.AnchorPositionFinders[pf] : pf; + // always set the constructor params; the position finder might need them later (the Grid one does, + // for example) + anchor.constructorParams = params; + }); + + // Continuous anchor is just curried through to the 'get' method of the continuous anchor + // factory. + jsPlumb.Anchors["Continuous"] = function(params) { + return params.jsPlumbInstance.continuousAnchorFactory.get(params); + }; + + // these are the default anchor positions finders, which are used by the makeTarget function. supply + // a position finder argument to that function allows you to specify where the resulting anchor will + // be located + jsPlumb.AnchorPositionFinders = { + "Fixed": function(dp, ep, es, a) { + return [ (dp.left - ep.left) / es[0], (dp.top - ep.top) / es[1] ]; + }, + "Grid":function(dp, ep, es, a) { + var dx = dp.left - ep.left, dy = dp.top - ep.top, + gx = es[0] / (a.constructorParams.grid[0]), gy = es[1] / (a.constructorParams.grid[1]), + mx = Math.floor(dx / gx), my = Math.floor(dy / gy); + return [ ((mx * gx) + (gx / 2)) / es[0], ((my * gy) + (gy / 2)) / es[1] ]; + } + }; +})(); +/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the default Connectors, Endpoint and Overlay definitions. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +(function() { + + /** + * + * Helper class to consume unused mouse events by components that are DOM elements and + * are used by all of the different rendering modes. + * + */ + jsPlumb.DOMElementComponent = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + // when render mode is canvas, these functions may be called by the canvas mouse handler. + // this component is safe to pipe this stuff to /dev/null. + this.mousemove = + this.dblclick = + this.click = + this.mousedown = + this.mouseup = function(e) { }; + }; + + /** + * Class: Connectors.Straight + * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters. + */ + jsPlumb.Connectors.Straight = function() { + this.type = "Straight"; + var self = this, + currentPoints = null, + _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty, _segment, _length; + + /** + * Computes the new size and position of the canvas. + */ + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + _dx = _tx - _sx, _dy = _ty - _sy; + //_m = _dy / _dx, _m2 = -1 / _m; + _m = jsPlumb.util.gradient({x:_sx, y:_sy}, {x:_tx, y:_ty}), _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + //_segment = jsPlumb.util.segment({x:_sx, y:_sy}, {x:_tx, y:_ty}); + _length = Math.sqrt((_dx * _dx) + (_dy * _dy)); + + return currentPoints; + }; + + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + if (location == 0) + return { x:_sx, y:_sy }; + else if (location == 1) + return { x:_tx, y:_ty }; + else + return jsPlumb.util.pointOnLine({x:_sx, y:_sy}, {x:_tx, y:_ty}, location * _length); + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { + return _m; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + * this hands off to jsPlumb.util to do the maths, supplying our gradient and position and whether or + * not the coords should be flipped + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + return jsPlumb.util.pointOnLine(p, {x:_tx,y:_ty}, distance); + }; + }; + + + /** + * Class:Connectors.Bezier + * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's + * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below. + */ + /** + * Function:Constructor + * + * Parameters: + * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line. + * Optional; defaults to 150. + * + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + params = params || {}; + this.majorAnchor = params.curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + this.type = "Bezier"; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(sourceEndpoint), + too = targetAnchor.getOrientation(targetEndpoint), + perpendicular = soo[0] != too[0] || soo[1] == too[1], + p = [], + ma = self.majorAnchor, mi = self.minorAnchor; + + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, sourceEndpoint, targetEndpoint, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx), minx2 = Math.min(_CP[0], _CP2[0]), minx = Math.min(minx1,minx2), + maxx1 = Math.max(_sx,_tx), maxx2 = Math.max(_CP[0], _CP2[0]), maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty), miny2 = Math.min(_CP[1], _CP2[1]), miny = Math.min(miny1,miny2), + maxy1 = Math.max(_sy,_ty), maxy2 = Math.max(_CP[1], _CP2[1]), maxy = Math.max(maxy1,maxy2); + + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + }; + + + /** + * Class: Connectors.Flowchart + * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments. + */ + /** + * Function: Constructor + * + * Parameters: + * stub - minimum length for the stub at each end of the connector. defaults to 30 pixels. + */ + jsPlumb.Connectors.Flowchart = function(params) { + this.type = "Flowchart"; + params = params || {}; + var self = this, + minStubLength = params.stub || params.minStubLength /* bwds compat. */ || 30, + segments = [], + totalLength = 0, + segmentProportions = [], + segmentProportionalLengths = [], + points = [], + swapX, + swapY, + /** + * recalculates the points at which the segments begin and end, proportional to the total length travelled + * by all the segments that constitute the connector. we use this to help with pointOnPath calculations. + */ + updateSegmentProportions = function(startX, startY, endX, endY) { + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segments[i][5] / totalLength; + segmentProportions[i] = [curLoc, (curLoc += (segments[i][5] / totalLength)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0], + ly = segments.length == 0 ? sy : segments[segments.length - 1][1], + m = x == lx ? Infinity : 0, + l = Math.abs(x == lx ? y - ly : x - lx); + segments.push([x, y, lx, ly, m, l]); + totalLength += l; + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment + * that contains the point which is 'location' distance along the entire path, where + * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths + * are made up of a list of segments, each of which contributes some fraction to + * the total length. + */ + findSegmentForLocation = function(location) { + var idx = segmentProportions.length - 1, inSegmentProportion = 0; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, + sourceAnchor, targetAnchor, lineWidth, minWidth, sourceInfo, targetInfo) { + + segments = []; + totalLength = 0; + segmentProportionalLengths = []; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + offx = (lw / 2) + (minStubLength * 2), + offy = (lw / 2) + (minStubLength * 2), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(sourceEndpoint), + to = targetAnchor.orientation || targetAnchor.getOrientation(targetEndpoint), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx, + h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy; + + // if either anchor does not have an orientation set, we derive one from their relative + // positions. we fix the axis to be the one in which the two elements are further apart, and + // point each anchor at the other element. this is also used when dragging a new connection. + if (so[0] == 0 && so[1] == 0 || to[0] == 0 && to[1] == 0) { + var index = w > h ? 0 : 1, oIndex = [1,0][index]; + so = []; to = []; + so[index] = sourcePos[index] > targetPos[index] ? -1 : 1; + to[index] = sourcePos[index] > targetPos[index] ? 1 : -1; + so[oIndex] = 0; + to[oIndex] = 0; + } + + if (w < minWidth) { + offx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + offy += (minWidth - h) / 2; + h = minWidth; + } + + var sx = swapX ? w-offx : offx, + sy = swapY ? h-offy : offy, + tx = swapX ? offx : w-offx , + ty = swapY ? offy : h-offy, + startStubX = sx + (so[0] * minStubLength), + startStubY = sy + (so[1] * minStubLength), + endStubX = tx + (to[0] * minStubLength), + endStubY = ty + (to[1] * minStubLength), + isXGreaterThanStubTimes2 = Math.abs(sx - tx) > 2 * minStubLength, + isYGreaterThanStubTimes2 = Math.abs(sy - ty) > 2 * minStubLength, + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2), + oProduct = ((so[0] * to[0]) + (so[1] * to[1])), + opposite = oProduct == -1, + perpendicular = oProduct == 0, + orthogonal = oProduct == 1; + + x -= offx; y -= offy; + points = [x, y, w, h, sx, sy, tx, ty]; + var extraPoints = []; + + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + var sourceAxis = so[0] == 0 ? "y" : "x", + anchorOrientation = opposite ? "opposite" : orthogonal ? "orthogonal" : "perpendicular", + segment = jsPlumb.util.segment([sx, sy], [tx, ty]), + flipSourceSegments = so[sourceAxis == "x" ? 0 : 1] == -1, + flipSegments = { + "x":[null, 4, 3, 2, 1], + "y":[null, 2, 1, 4, 3] + } + + if (flipSourceSegments) + segment = flipSegments[sourceAxis][segment]; + + var findClearedLine = function(start, mult, anchorPos, dimension) { + return start + (mult * (( 1 - anchorPos) * dimension) + minStubLength); + //mx = so[0] == 0 ? startStubX + ((1 - sourceAnchor.x) * sourceInfo.width) + minStubLength : startStubX, + }, + + lineCalculators = { + oppositex : function() { + if (sourceEndpoint.elementId == targetEndpoint.elementId) { + var _y = startStubY + ((1 - sourceAnchor.y) * sourceInfo.height) + minStubLength; + return [ [ startStubX, _y ], [ endStubX, _y ]]; + } + else if (isXGreaterThanStubTimes2 && (segment == 1 || segment == 2)) { + return [[ midx, sy ], [ midx, ty ]]; + } + else { + return [[ startStubX, midy ], [endStubX, midy ]]; + } + }, + orthogonalx : function() { + if (segment == 1 || segment == 2) { + return [ [ endStubX, startStubY ]]; + } + else { + return [ [ startStubX, endStubY ]]; + } + }, + perpendicularx : function() { + var my = (ty + sy) / 2; + if ((segment == 1 && to[1] == 1) || (segment == 2 && to[1] == -1)) { + if (Math.abs(tx - sx) > minStubLength) + return [ [endStubX, startStubY ]]; + else + return [ [startStubX, startStubY ], [ startStubX, my ], [ endStubX, my ]]; + } + else if ((segment == 3 && to[1] == -1) || (segment == 4 && to[1] == 1)) { + return [ [ startStubX, my ], [ endStubX, my ]]; + } + else if ((segment == 3 && to[1] == 1) || (segment == 4 && to[1] == -1)) { + return [ [ startStubX, endStubY ]]; + } + else if ((segment == 1 && to[1] == -1) || (segment == 2 && to[1] == 1)) { + if (Math.abs(tx - sx) > minStubLength) + return [ [ midx, startStubY ], [ midx, endStubY ]]; + else + return [ [ startStubX, endStubY ]]; + } + }, + oppositey : function() { + if (sourceEndpoint.elementId == targetEndpoint.elementId) { + var _x = startStubX + ((1 - sourceAnchor.x) * sourceInfo.width) + minStubLength; + return [ [ _x, startStubY ], [ _x, endStubY ]]; + } + else if (isYGreaterThanStubTimes2 && (segment == 2 || segment == 3)) { + return [[ sx, midy ], [ tx, midy ]]; + } + else { + return [[ midx, startStubY ], [midx, endStubY ]]; + } + }, + orthogonaly : function() { + if (segment == 2 || segment == 3) { + return [ [ startStubX, endStubY ]]; + } + else { + return [ [ endStubX, startStubY ]]; + } + }, + perpendiculary : function() { + var mx = (tx + sx) / 2; + if ((segment == 2 && to[0] == -1) || (segment == 3 && to[0] == 1)) { + if (Math.abs(tx - sx) > minStubLength) + return [ [startStubX, endStubY ]]; + else + return [ [startStubX, midy ], [ endStubX, midy ]]; + } + else if ((segment == 1 && to[0] == -1) || (segment == 4 && to[0] == 1)) { + var mx = (tx + sx) / 2; + return [ [ mx, startStubY ], [ mx, endStubY ]]; + } + else if ((segment == 1 && to[0] == 1) || (segment == 4 && to[0] == -1)) { + return [ [ endStubX, startStubY ]]; + } + else if ((segment == 2 && to[0] == 1) || (segment == 3 && to[0] == -1)) { + if (Math.abs(ty - sy) > minStubLength) + return [ [ startStubX, midy ], [ endStubX, midy ]]; + else + return [ [ endStubX, startStubY ]]; + } + } + }; + + + + var pointFinder = lineCalculators[anchorOrientation + sourceAxis]; + var p = pointFinder(); + if (p) { + for (var i = 0; i < p.length; i++) { + addSegment(p[i][0], p[i][1], sx, sy, tx, ty); + } + } + + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentProportions(sx, sy, tx, ty); + + return points; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location) { + return self.pointAlongPathFrom(location, 0); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location) { + return segments[findSegmentForLocation(location)["index"]][4]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance) { + var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segments[s.index][5], m = segments[s.index][4]; + var e = { + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + return e; + }; + }; + + // ********************************* END OF CONNECTOR TYPES ******************************************************************* + + // ********************************* ENDPOINT TYPES ******************************************************************* + + /** + * Class: Endpoints.Dot + * A round endpoint, with default radius 10 pixels. + */ + + /** + * Function: Constructor + * + * Parameters: + * + * radius - radius of the endpoint. defaults to 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + this.type = "Dot"; + var self = this; + params = params || {}; + this.radius = params.radius || 10; + this.defaultOffset = 0.5 * this.radius; + this.defaultInnerRadius = this.radius / 3; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var r = endpointStyle.radius || self.radius, + x = anchorPoint[0] - r, + y = anchorPoint[1] - r; + return [ x, y, r * 2, r * 2, r ]; + }; + }; + + /** + * Class: Endpoints.Rectangle + * A Rectangular Endpoint, with default size 20x20. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the endpoint. defaults to 20 pixels. + * height - height of the endpoint. defaults to 20 pixels. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + this.type = "Rectangle"; + var self = this; + params = params || {}; + this.width = params.width || 20; + this.height = params.height || 20; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height]; + }; + }; + + + var DOMElementEndpoint = function(params) { + jsPlumb.DOMElementComponent.apply(this, arguments); + var self = this; + + var displayElements = [ ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + }; + /** + * Class: Endpoints.Image + * Draws an image as the Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * src - location of the image to use. + */ + jsPlumb.Endpoints.Image = function(params) { + + this.type = "Image"; + DOMElementEndpoint.apply(this, arguments); + + var self = this, + initialized = false, + widthToUse = params.width, + heightToUse = params.height, + _onload = null, + _endpoint = params.endpoint; + + this.img = new Image(); + self.ready = false; + + this.img.onload = function() { + self.ready = true; + widthToUse = widthToUse || self.img.width; + heightToUse = heightToUse || self.img.height; + if (_onload) { + _onload(self); + } + }; + + _endpoint.setImage = function(img, onload) { + var s = img.constructor == String ? img : img.src; + _onload = onload; + self.img.src = img; + }; + + _endpoint.setImage(params.src || params.url, params.onload); + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + self.anchorPoint = anchorPoint; + if (self.ready) return [anchorPoint[0] - widthToUse / 2, anchorPoint[1] - heightToUse / 2, + widthToUse, heightToUse]; + else return [0,0,0,0]; + }; + + self.canvas = document.createElement("img"), initialized = false; + self.canvas.style["margin"] = 0; + self.canvas.style["padding"] = 0; + self.canvas.style["outline"] = 0; + self.canvas.style["position"] = "absolute"; + var clazz = params.cssClass ? " " + params.cssClass : ""; + self.canvas.className = jsPlumb.endpointClass + clazz; + if (widthToUse) self.canvas.setAttribute("width", widthToUse); + if (heightToUse) self.canvas.setAttribute("height", heightToUse); + jsPlumb.appendElement(self.canvas, params.parent); + self.attachListeners(self.canvas, self); + + var actuallyPaint = function(d, style, anchor) { + if (!initialized) { + self.canvas.setAttribute("src", self.img.src); + initialized = true; + } + var x = self.anchorPoint[0] - (widthToUse / 2), + y = self.anchorPoint[1] - (heightToUse / 2); + jsPlumb.sizeCanvas(self.canvas, x, y, widthToUse, heightToUse); + }; + + this.paint = function(d, style, anchor) { + if (self.ready) { + actuallyPaint(d, style, anchor); + } + else { + window.setTimeout(function() { + self.paint(d, style, anchor); + }, 200); + } + }; + }; + + /** + * Class: Endpoints.Blank + * An Endpoint that paints nothing (visible) on the screen. Supports cssClass and hoverClass parameters like all Endpoints. + */ + jsPlumb.Endpoints.Blank = function(params) { + var self = this; + this.type = "Blank"; + DOMElementEndpoint.apply(this, arguments); + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + return [anchorPoint[0], anchorPoint[1],10,0]; + }; + + self.canvas = document.createElement("div"); + self.canvas.style.display = "block"; + self.canvas.style.width = "1px"; + self.canvas.style.height = "1px"; + self.canvas.style.background = "transparent"; + self.canvas.style.position = "absolute"; + self.canvas.className = self._jsPlumb.endpointClass; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + }; + }; + + /** + * Class: Endpoints.Triangle + * A triangular Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the triangle's base. defaults to 55 pixels. + * height - height of the triangle from base to apex. defaults to 55 pixels. + */ + jsPlumb.Endpoints.Triangle = function(params) { + this.type = "Triangle"; + params = params || { }; + params.width = params.width || 55; + params.height = params.height || 55; + this.width = params.width; + this.height = params.height; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height ]; + }; + }; +// ********************************* END OF ENDPOINT TYPES ******************************************************************* + + +// ********************************* OVERLAY DEFINITIONS *********************************************************************** + + var AbstractOverlay = function(params) { + var visible = true, self = this; + this.isAppendedAtTopLevel = true; + this.component = params.component; + this.loc = params.location == null ? 0.5 : params.location; + this.endpointLoc = params.endpointLocation == null ? [ 0.5, 0.5] : params.endpointLocation; + this.setVisible = function(val) { + visible = val; + self.component.repaint(); + }; + this.isVisible = function() { return visible; }; + this.hide = function() { self.setVisible(false); }; + this.show = function() { self.setVisible(true); }; + + this.incrementLocation = function(amount) { + self.loc += amount; + self.component.repaint(); + }; + this.setLocation = function(l) { + self.loc = l; + self.component.repaint(); + }; + this.getLocation = function() { + return self.loc; + }; + }; + + + /** + * Class: Overlays.Arrow + * + * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length + * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction + * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line + * across the tail. + */ + /** + * Function: Constructor + * + * Parameters: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + this.type = "Arrow"; + AbstractOverlay.apply(this, arguments); + this.isAppendedAtTopLevel = false; + params = params || {}; + var self = this; + + this.length = params.length || 20; + this.width = params.width || 20; + this.id = params.id; + var direction = (params.direction || 1) < 0 ? -1 : 1, + paintStyle = params.paintStyle || { lineWidth:1 }, + // how far along the arrow the lines folding back in come to. default is 62.3%. + foldback = params.foldback || 0.623; + + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.cleanup = function() { }; // nothing to clean up for Arrows + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var hxy, mid, txy, tail, cxy; + if (connector.pointAlongPathFrom) { + + if (self.loc == 1) { + hxy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, -1); + txy = jsPlumb.util.pointOnLine(hxy, mid, self.length); + } + else if (self.loc == 0) { + txy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, 1); + hxy = jsPlumb.util.pointOnLine(txy, mid, self.length); + } + else { + hxy = connector.pointAlongPathFrom(self.loc, direction * self.length / 2), + mid = connector.pointOnPath(self.loc), + txy = jsPlumb.util.pointOnLine(hxy, mid, self.length); + } + + tail = jsPlumb.util.perpendicularLineTo(hxy, txy, self.width); + cxy = jsPlumb.util.pointOnLine(hxy, txy, foldback * self.length); + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x), + maxx = Math.max(hxy.x, tail[0].x, tail[1].x), + miny = Math.min(hxy.y, tail[0].y, tail[1].y), + maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + var d = { hxy:hxy, tail:tail, cxy:cxy }, + strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle, + fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle, + lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth; + + self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions); + + return [ minx, maxx, miny, maxy]; + } + else return [0,0,0,0]; + }; + }; + + /** + * Class: Overlays.PlainArrow + * + * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "PlainArrow"; + }; + + /** + * Class: Overlays.Diamond + * + * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40, + p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "Diamond"; + }; + + + + /** + * Class: Overlays.Label + * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb + * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter + * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it + * puts on the Label's 'style' attribute, so the end result is the same. + */ + /** + * Function: Constructor + * + * Parameters: + * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes + * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle. + * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * + */ + jsPlumb.Overlays.Label = function(params) { + this.type = "Label"; + jsPlumb.DOMElementComponent.apply(this, arguments); + AbstractOverlay.apply(this, arguments); + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.id = params.id; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var label = params.label || "", + self = this, + initialised = false, + div = document.createElement("div"), + labelText = null; + div.style["position"] = "absolute"; + + var clazz = params["_jsPlumb"].overlayClass + " " + + (self.labelStyle.cssClass ? self.labelStyle.cssClass : + params.cssClass ? params.cssClass : ""); + + div.className = clazz; + + jsPlumb.appendElement(div, params.component.parent); + jsPlumb.getId(div); + self.attachListeners(div, self); + self.canvas = div; + + //override setVisible + var osv = self.setVisible; + self.setVisible = function(state) { + osv(state); // call superclass + div.style.display = state ? "block" : "none"; + }; + + this.getElement = function() { + return div; + }; + + this.cleanup = function() { + if (div != null) jsPlumb.CurrentLibrary.removeElement(div); + }; + + /* + * Function: setLabel + * sets the label's, um, label. you would think i'd call this function + * 'setText', but you can pass either a Function or a String to this, so + * it makes more sense as 'setLabel'. + */ + this.setLabel = function(l) { + label = l; + labelText = null; + self.component.repaint(); + }; + + this.getLabel = function() { + return label; + }; + + this.paint = function(component, d, componentDimensions) { + if (!initialised) { + component.appendDisplayElement(div); + self.attachListeners(div, component); + initialised = true; + } + div.style.left = (componentDimensions[0] + d.minx) + "px"; + div.style.top = (componentDimensions[1] + d.miny) + "px"; + }; + + this.getTextDimensions = function() { + if (typeof label == "function") { + var lt = label(self); + div.innerHTML = lt.replace(/\r\n/g, "
"); + } + else { + if (labelText == null) { + labelText = label; + div.innerHTML = labelText.replace(/\r\n/g, "
"); + } + } + var de = jsPlumb.CurrentLibrary.getElementObject(div), + s = jsPlumb.CurrentLibrary.getSize(de); + return {width:s[0], height:s[1]}; + }; + + this.computeMaxSize = function(connector) { + var td = self.getTextDimensions(connector); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + + this.draw = function(component, currentConnectionPaintStyle, componentDimensions) { + var td = self.getTextDimensions(component); + if (td.width != null) { + var cxy = {x:0,y:0}; + if (component.pointOnPath) + cxy = component.pointOnPath(self.loc); // a connection + else { + var locToUse = self.loc.constructor == Array ? self.loc : self.endpointLoc; + cxy = { x:locToUse[0] * componentDimensions[2], + y:locToUse[1] * componentDimensions[3] }; + } + + minx = cxy.x - (td.width / 2), + miny = cxy.y - (td.height / 2); + + self.paint(component, { + minx:minx, + miny:miny, + td:td, + cxy:cxy + }, componentDimensions); + + return [minx, minx+td.width, miny, miny+td.height]; + } + else return [0,0,0,0]; + }; + + this.reattachListeners = function(connector) { + if (div) { + self.reattachListenersForElement(div, self, connector); + } + }; + }; + + // this is really just a test overlay, so its undocumented and doesnt take any parameters. but i was loth to delete it. + jsPlumb.Overlays.GuideLines = function() { + var self = this; + self.length = 50; + self.lineWidth = 5; + this.type = "GuideLines"; + AbstractOverlay.apply(this, arguments); + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var head = connector.pointAlongPathFrom(self.loc, self.length / 2), + mid = connector.pointOnPath(self.loc), + tail = jsPlumb.util.pointOnLine(head, mid, self.length), + tailLine = jsPlumb.util.perpendicularLineTo(head, tail, 40), + headLine = jsPlumb.util.perpendicularLineTo(tail, head, 20); + + self.paint(connector, [head, tail, tailLine, headLine], self.lineWidth, "red", null, connectorDimensions); + + return [Math.min(head.x, tail.x), Math.min(head.y, tail.y), Math.max(head.x, tail.x), Math.max(head.y,tail.y)]; + }; + + this.computeMaxSize = function() { return 50; }; + + this.cleanup = function() { }; // nothing to clean up for GuideLines + }; + + // ********************************* END OF OVERLAY DEFINITIONS *********************************************************************** + + // ********************************* OVERLAY CANVAS RENDERERS*********************************************************************** + + // ********************************* END OF OVERLAY CANVAS RENDERERS *********************************************************************** +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the state machine connectors. + * + * Thanks to Brainstorm Mobile Solutions for supporting the development of these. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + var Line = function(x1, y1, x2, y2) { + + this.m = (y2 - y1) / (x2 - x1); + this.b = -1 * ((this.m * x1) - y1); + + this.rectIntersect = function(x,y,w,h) { + var results = []; + + // try top face + // the equation of the top face is y = (0 * x) + b; y = b. + var xInt = (y - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try right face + var yInt = (this.m * (x + w)) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + // bottom face + var xInt = ((y + h) - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try left face + var yInt = (this.m * x) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + if (results.length == 2) { + var midx = (results[0][0] + results[1][0]) / 2, midy = (results[0][1] + results[1][1]) / 2; + results.push([ midx,midy ]); + // now calculate the segment inside the rectangle where the midpoint lies. + var xseg = midx <= x + (w / 2) ? -1 : 1, + yseg = midy <= y + (h / 2) ? -1 : 1; + results.push([xseg, yseg]); + return results; + } + + return null; + + }; + }, + _segment = function(x1, y1, x2, y2) { + if (x1 <= x2 && y2 <= y1) return 1; + else if (x1 <= x2 && y1 <= y2) return 2; + else if (x2 <= x1 && y2 >= y1) return 3; + return 4; + }, + + // the control point we will use depends on the faces to which each end of the connection is assigned, specifically whether or not the + // two faces are parallel or perpendicular. if they are parallel then the control point lies on the midpoint of the axis in which they + // are parellel and varies only in the other axis; this variation is proportional to the distance that the anchor points lie from the + // center of that face. if the two faces are perpendicular then the control point is at some distance from both the midpoints; the amount and + // direction are dependent on the orientation of the two elements. 'seg', passed in to this method, tells you which segment the target element + // lies in with respect to the source: 1 is top right, 2 is bottom right, 3 is bottom left, 4 is top left. + // + // sourcePos and targetPos are arrays of info about where on the source and target each anchor is located. their contents are: + // + // 0 - absolute x + // 1 - absolute y + // 2 - proportional x in element (0 is left edge, 1 is right edge) + // 3 - proportional y in element (0 is top edge, 1 is bottom edge) + // + _findControlPoint = function(midx, midy, segment, sourceEdge, targetEdge, dx, dy, distance, proximityLimit) { + + // TODO (maybe) + // - if anchor pos is 0.5, make the control point take into account the relative position of the elements. + if (distance <= proximityLimit) return [midx, midy]; + + if (segment == 1) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 2) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 3) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 4) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + }; + + /* + Function: StateMachine constructor + + Allowed parameters: + curviness - measure of how "curvy" the connectors will be. this is translated as the distance that the + Bezier curve's control point is from the midpoint of the straight line connecting the two + endpoints, and does not mean that the connector is this wide. The Bezier curve never reaches + its control points; they act as gravitational masses. defaults to 10. + margin - distance from element to start and end connectors, in pixels. defaults to 5. + proximityLimit - sets the distance beneath which the elements are consider too close together to bother with fancy + curves. by default this is 80 pixels. + loopbackRadius - the radius of a loopback connector. optional; defaults to 25. + */ + jsPlumb.Connectors.StateMachine = function(params) { + var self = this, + currentPoints = null, + _sx, _sy, _tx, _ty, _controlPoint = [], + curviness = params.curviness || 10, + margin = params.margin || 5, + proximityLimit = params.proximityLimit || 80, + clockwise = params.orientation && params.orientation == "clockwise", + loopbackRadius = params.loopbackRadius || 25, + isLoopback = false; + + this.type = "StateMachine"; + params = params || {}; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *= 1.9; + //ensure at least one pixel width + lineWidth = lineWidth || 1; + var x = Math.min(sourcePos[0], targetPos[0]) - xo, + y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (sourceEndpoint.elementId != targetEndpoint.elementId) { + + isLoopback = false; + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + + // now adjust for the margin + if (sourcePos[2] == 0) _sx -= margin; + if (sourcePos[2] == 1) _sx += margin; + if (sourcePos[3] == 0) _sy -= margin; + if (sourcePos[3] == 1) _sy += margin; + if (targetPos[2] == 0) _tx -= margin; + if (targetPos[2] == 1) _tx += margin; + if (targetPos[3] == 0) _ty -= margin; + if (targetPos[3] == 1) _ty += margin; + + // + // these connectors are quadratic bezier curves, having a single control point. if both anchors + // are located at 0.5 on their respective faces, the control point is set to the midpoint and you + // get a straight line. this is also the case if the two anchors are within 'proximityLimit', since + // it seems to make good aesthetic sense to do that. outside of that, the control point is positioned + // at 'curviness' pixels away along the normal to the straight line connecting the two anchors. + // + // there may be two improvements to this. firstly, we might actually support the notion of avoiding nodes + // in the UI, or at least making a good effort at doing so. if a connection would pass underneath some node, + // for example, we might increase the distance the control point is away from the midpoint in a bid to + // steer it around that node. this will work within limits, but i think those limits would also be the likely + // limits for, once again, aesthetic good sense in the layout of a chart using these connectors. + // + // the second possible change is actually two possible changes: firstly, it is possible we should gradually + // decrease the 'curviness' as the distance between the anchors decreases; start tailing it off to 0 at some + // point (which should be configurable). secondly, we might slightly increase the 'curviness' for connectors + // with respect to how far their anchor is from the center of its respective face. this could either look cool, + // or stupid, and may indeed work only in a way that is so subtle as to have been a waste of time. + // + + var _midx = (_sx + _tx) / 2, _midy = (_sy + _ty) / 2, + m2 = (-1 * _midx) / _midy, theta2 = Math.atan(m2), + dy = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.sin(theta2)), + dx = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.cos(theta2)), + segment = _segment(_sx, _sy, _tx, _ty), + distance = Math.sqrt(Math.pow(_tx - _sx, 2) + Math.pow(_ty - _sy, 2)); + + // calculate the control point. this code will be where we'll put in a rudimentary element avoidance scheme; it + // will work by extending the control point to force the curve to be, um, curvier. + _controlPoint = _findControlPoint(_midx, + _midy, + segment, + sourcePos, + targetPos, + curviness, curviness, + distance, + proximityLimit); + + + var requiredWidth = Math.max(Math.abs(_controlPoint[0] - _sx) * 3, Math.abs(_controlPoint[0] - _tx) * 3, Math.abs(_tx-_sx), 2 * lineWidth, minWidth), + requiredHeight = Math.max(Math.abs(_controlPoint[1] - _sy) * 3, Math.abs(_controlPoint[1] - _ty) * 3, Math.abs(_ty-_sy), 2 * lineWidth, minWidth); + + if (w < requiredWidth) { + var dw = requiredWidth - w; + x -= (dw / 2); + _sx += (dw / 2); + _tx += (dw / 2); + w = requiredWidth; + _controlPoint[0] += (dw / 2); + } + + if (h < requiredHeight) { + var dh = requiredHeight - h; + y -= (dh / 2); + _sy += (dh / 2); + _ty += (dh / 2); + h = requiredHeight; + _controlPoint[1] += (dh / 2); + } + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty, _controlPoint[0], _controlPoint[1] ]; + } + else { + isLoopback = true; + // a loopback connector. draw an arc from one anchor to the other. + // i guess we'll do this the same way as the others. just the control point will be a fair distance away. + var x1 = sourcePos[0], x2 = sourcePos[0], y1 = sourcePos[1] - margin, y2 = sourcePos[1] - margin, + cx = x1, cy = y1 - loopbackRadius; + + // canvas sizing stuff, to ensure the whole painted area is visible. + w = ((2 * lineWidth) + (4 * loopbackRadius)), h = ((2 * lineWidth) + (4 * loopbackRadius)); + x = cx - loopbackRadius - lineWidth - loopbackRadius, y = cy - loopbackRadius - lineWidth - loopbackRadius; + currentPoints = [ x, y, w, h, cx-x, cy-y, loopbackRadius, clockwise, x1-x, y1-y, x2-x, y2-y]; + } + + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_tx, y:_ty }, + { x:_controlPoint[0], y:_controlPoint[1] }, + { x:_controlPoint[0] + 1, y:_controlPoint[1] + 1}, + { x:_sx, y:_sy } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + if (isLoopback) { + + if (location > 0 && location < 1) location = 1- location; + +// current points are [ x, y, width, height, center x, center y, radius, clockwise, startx, starty, endx, endy ] + // so the path length is the circumference of the circle + //var len = 2 * Math.PI * currentPoints[6], + // map 'location' to an angle. 0 is PI/2 when the connector is on the top face; if we + // support other faces it will have to be calculated for each one. 1 is also PI/2. + // 0.5 is -PI/2. + var startAngle = (location * 2 * Math.PI) + (Math.PI / 2), + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + + } + else return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + if (isLoopback) + return Math.atan(location * 2 * Math.PI); + else + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + if (isLoopback) { + + if (location > 0 && location < 1) location = 1- location; + + var circumference = 2 * Math.PI * currentPoints[6], + arcSpan = distance / circumference * 2 * Math.PI, + startAngle = (location * 2 * Math.PI) - arcSpan + (Math.PI / 2), + + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + } + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + }; + + /* + * Canvas state machine renderer. + */ + jsPlumb.Connectors.canvas.StateMachine = function(params) { + params = params || {}; + var self = this, drawGuideline = params.drawGuideline || true, avoidSelector = params.avoidSelector; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.CanvasConnector.apply(this, arguments); + + + this._paint = function(dimensions) { + + if (dimensions.length == 10) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.quadraticCurveTo(dimensions[8], dimensions[9], dimensions[6], dimensions[7]); + self.ctx.stroke(); + + /*/ draw the guideline + if (drawGuideline) { + self.ctx.save(); + self.ctx.beginPath(); + self.ctx.strokeStyle = "silver"; + self.ctx.lineWidth = 1; + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + self.ctx.restore(); + } + //*/ + } + else { + // a loopback connector + self.ctx.save(); + self.ctx.beginPath(); + var startAngle = 0, // Starting point on circle + endAngle = 2 * Math.PI, // End point on circle + clockwise = dimensions[7]; // clockwise or anticlockwise + self.ctx.arc(dimensions[4],dimensions[5],dimensions[6],0, endAngle, clockwise); + self.ctx.stroke(); + self.ctx.closePath(); + self.ctx.restore(); + } + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + /* + * SVG State Machine renderer + */ + jsPlumb.Connectors.svg.StateMachine = function() { + var self = this; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.SvgConnector.apply(this, arguments); + this.getPath = function(d) { + + if (d.length == 10) + return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[8] + " " + d[9] + " " + d[6] + " " + d[7]; + else { + // loopback + return "M" + (d[8] + 4) + " " + d[9] + " A " + d[6] + " " + d[6] + " 0 1,0 " + (d[8]-4) + " " + d[9]; + } + }; + }; + + /* + * VML state machine renderer + */ + jsPlumb.Connectors.vml.StateMachine = function() { + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.VmlConnector.apply(this, arguments); + var _conv = jsPlumb.vml.convertValue; + this.getPath = function(d) { + if (d.length == 10) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + } + else { + // loopback + var left = _conv(d[8] - d[6]), + top = _conv(d[9] - (2 * d[6])), + right = left + _conv(2 * d[6]), + bottom = top + _conv(2 * d[6]), + posString = left + "," + top + "," + right + "," + bottom; + + var o = "ar " + posString + "," + _conv(d[8]) + "," + + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + " e"; + + return o; + } + }; + }; + +})(); + +/* + // now for a rudimentary avoidance scheme. TODO: how to set this in a cross-library way? + // if (avoidSelector) { + // var testLine = new Line(sourcePos[0] + _sx,sourcePos[1] + _sy,sourcePos[0] + _tx,sourcePos[1] + _ty); + // var sel = jsPlumb.getSelector(avoidSelector); + // for (var i = 0; i < sel.length; i++) { + // var id = jsPlumb.getId(sel[i]); + // if (id != sourceEndpoint.elementId && id != targetEndpoint.elementId) { + // o = jsPlumb.getOffset(id), s = jsPlumb.getSize(id); +// +// if (o && s) { +// var collision = testLine.rectIntersect(o.left,o.top,s[0],s[1]); +// if (collision) { + // set the control point to be a certain distance from the midpoint of the two points that + // the line crosses on the rectangle. + // TODO where will this 75 number come from? + // _controlX = collision[2][0] + (75 * collision[3][0]); + // / _controlY = collision[2][1] + (75 * collision[3][1]); +// } +// } + // } + // } + //} + *//* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the VML renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + // http://ajaxian.com/archives/the-vml-changes-in-ie-8 + // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/ + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + + var vmlAttributeMap = { + "stroke-linejoin":"joinstyle", + "joinstyle":"joinstyle", + "endcap":"endcap", + "miterlimit":"miterlimit" + }; + + if (document.createStyleSheet) { + + // this is the style rule for IE7/6: it uses a CSS class, tidy. + document.createStyleSheet().addRule(".jsplumb_vml", "behavior:url(#default#VML);position:absolute;"); + + // these are for VML in IE8. you have to explicitly call out which elements + // you're going to expect to support VML! + // + document.createStyleSheet().addRule("jsplumb\\:textbox", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:oval", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:rect", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:stroke", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:shape", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:group", "behavior:url(#default#VML);position:absolute;"); + + // in this page it is also mentioned that IE requires the extra arg to the namespace + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either. + // var iev = document.documentMode; + //if (!iev || iev < 8) + document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); + //else + // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML"); + } + + jsPlumb.vml = {}; + + var scale = 1000, + + _groupMap = {}, + _getGroup = function(container, connectorClass) { + var id = jsPlumb.getId(container), + g = _groupMap[id]; + if(!g) { + g = _node("group", [0,0,scale, scale], {"class":connectorClass}); + //g.style.position=absolute; + //g["coordsize"] = "1000,1000"; + g.style.backgroundColor="red"; + _groupMap[id] = g; + jsPlumb.appendElement(g, container); + //document.body.appendChild(g); + } + return g; + }, + _atts = function(o, atts) { + for (var i in atts) { + // IE8 fix: setattribute does not work after an element has been added to the dom! + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + //o.setAttribute(i, atts[i]); + + o[i] = atts[i]; + } + }, + _node = function(name, d, atts) { + atts = atts || {}; + var o = document.createElement("jsplumb:" + name); + o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml"; + _pos(o, d); + _atts(o, atts); + return o; + }, + _pos = function(o,d) { + o.style.left = d[0] + "px"; + o.style.top = d[1] + "px"; + o.style.width= d[2] + "px"; + o.style.height= d[3] + "px"; + o.style.position = "absolute"; + }, + _conv = jsPlumb.vml.convertValue = function(v) { + return Math.floor(v * scale); + }, + // tests if the given style is "transparent" and then sets the appropriate opacity node to 0 if so, + // or 1 if not. TODO in the future, support variable opacity. + _maybeSetOpacity = function(styleToWrite, styleToCheck, type, component) { + if ("transparent" === styleToCheck) + component.setOpacity(type, "0.0"); + else + component.setOpacity(type, "1.0"); + }, + _applyStyles = function(node, style, component) { + var styleToWrite = {}; + if (style.strokeStyle) { + styleToWrite["stroked"] = "true"; + var strokeColor = jsPlumb.util.convertStyle(style.strokeStyle, true); + styleToWrite["strokecolor"] = strokeColor; + _maybeSetOpacity(styleToWrite, strokeColor, "stroke", component); + styleToWrite["strokeweight"] = style.lineWidth + "px"; + } + else styleToWrite["stroked"] = "false"; + + if (style.fillStyle) { + styleToWrite["filled"] = "true"; + var fillColor = jsPlumb.util.convertStyle(style.fillStyle, true); + styleToWrite["fillcolor"] = fillColor; + _maybeSetOpacity(styleToWrite, fillColor, "fill", component); + } + else styleToWrite["filled"] = "false"; + + if(style["dashstyle"]) { + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = style["dashstyle"]; + } + else if (style["stroke-dasharray"] && style["lineWidth"]) { + var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",", + parts = style["stroke-dasharray"].split(sep), + styleToUse = ""; + for(var i = 0; i < parts.length; i++) { + styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep); + } + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = styleToUse; + } + + _atts(node, styleToWrite); + }, + /* + * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. + */ + VmlComponent = function() { + var self = this; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.opacityNodes = { + "stroke":null, + "fill":null + }; + this.initOpacityNodes = function(vml) { + self.opacityNodes["stroke"] = _node("stroke", [0,0,1,1], {opacity:"0.0"}); + self.opacityNodes["fill"] = _node("fill", [0,0,1,1], {opacity:"0.0"}); + vml.appendChild(self.opacityNodes["stroke"]); + vml.appendChild(self.opacityNodes["fill"]); + }; + this.setOpacity = function(type, value) { + var node = self.opacityNodes[type]; + if (node) node["opacity"] = "" + value; + }; + var displayElements = [ ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el, doNotAppendToCanvas) { + if (!doNotAppendToCanvas) self.canvas.parentNode.appendChild(el); + displayElements.push(el); + }; + }, + /* + * Base class for Vml connectors. extends VmlComponent. + */ + VmlConnector = jsPlumb.VmlConnector = function(params) { + var self = this; + self.strokeNode = null; + self.canvas = null; + VmlComponent.apply(this, arguments); + var clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : ""); + this.paint = function(d, style, anchor) { + if (style != null) { + var path = self.getPath(d), p = { "path":path }; + + //* + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle : jsPlumb.util.convertStyle(style.outlineColor), + lineWidth : outlineStrokeWidth + }; + for (var aa in vmlAttributeMap) outlineStyle[aa] = style[aa]; + + if (self.bgCanvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.bgCanvas = _node("shape", d, p); + jsPlumb.appendElement(self.bgCanvas, params.parent); + _pos(self.bgCanvas, d); + self.appendDisplayElement(self.bgCanvas, true); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.bgCanvas, d); + _atts(self.bgCanvas, p); + } + + _applyStyles(self.bgCanvas, outlineStyle, self); + } + //*/ + + if (self.canvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + if (self.tooltip) p["label"] = self.tooltip; + self.canvas = _node("shape", d, p); + + //var group = _getGroup(params.parent); // test of append everything to a group + //group.appendChild(self.canvas); // sort of works but not exactly; + jsPlumb.appendElement(self.canvas, params.parent); //before introduction of groups + + self.appendDisplayElement(self.canvas, true); + + self.attachListeners(self.canvas, self); + + self.initOpacityNodes(self.canvas, ["stroke"]); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.canvas, d); + _atts(self.canvas, p); + } + + _applyStyles(self.canvas, style, self); + } + }; + + //self.appendDisplayElement(self.canvas); + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + }, + /* + * + * Base class for Vml Endpoints. extends VmlComponent. + * + */ + VmlEndpoint = function(params) { + VmlComponent.apply(this, arguments); + var vml = null, self = this, opacityStrokeNode = null, opacityFillNode = null; + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + + //var group = _getGroup(params.parent); + //group.appendChild(self.canvas); + jsPlumb.appendElement(self.canvas, params.parent); + + if (self.tooltip) self.canvas.setAttribute("label", self.tooltip); + + this.paint = function(d, style, anchor) { + var p = { }; + + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (vml == null) { + p["class"] = jsPlumb.endpointClass; + vml = self.getVml([0,0, d[2], d[3]], p, anchor); + self.canvas.appendChild(vml); + self.attachListeners(vml, self); + + self.appendDisplayElement(vml, true); + self.appendDisplayElement(self.canvas, true); + + self.initOpacityNodes(vml, ["fill"]); + } + else { + //p["coordsize"] = "1,1";//(d[2] * scale) + "," + (d[3] * scale); again, unsure. + _pos(vml, [0,0, d[2], d[3]]); + _atts(vml, p); + } + + _applyStyles(vml, style, self); + }; + + this.reattachListeners = function() { + if (vml) self.reattachListenersForElement(vml, self); + }; + }; + + jsPlumb.Connectors.vml.Bezier = function() { + jsPlumb.Connectors.Bezier.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Straight = function() { + jsPlumb.Connectors.Straight.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Flowchart = function() { + jsPlumb.Connectors.Flowchart.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l"; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]); + } + // finally draw a line to the end + p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e"; + return p; + }; + }; + + jsPlumb.Endpoints.vml.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("oval", d, atts); }; + }; + + jsPlumb.Endpoints.vml.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("rect", d, atts); }; + }; + + /* + * VML Image Endpoint is the same as the default image endpoint. + */ + jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image; + + /** + * placeholder for Blank endpoint in vml renderer. + */ + jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank; + + /** + * VML Label renderer. uses the default label renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label; + + var AbstractVmlArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + VmlComponent.apply(this, arguments); + var self = this, path = null; + self.canvas = null; + var getPath = function(d, connectorDimensions) { + return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) + + " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + + " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + + " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + + " x e"; + }; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) { + var p = {}; + if (strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] = jsPlumb.util.convertStyle(strokeStyle, true); + } + if (lineWidth) p["strokeweight"] = lineWidth + "px"; + if (fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = fillStyle; + } + var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + w = Math.abs(xmax - xmin), + h = Math.abs(ymax - ymin), + dim = [xmin, ymin, w, h]; + + // for VML, we create overlays using shapes that have the same dimensions and + // coordsize as their connector - overlays calculate themselves relative to the + // connector (it's how it's been done since the original canvas implementation, because + // for canvas that makes sense). + p["path"] = getPath(d, connectorDimensions); + p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale); + + dim[0] = connectorDimensions[0]; + dim[1] = connectorDimensions[1]; + dim[2] = connectorDimensions[2]; + dim[3] = connectorDimensions[3]; + + if (self.canvas == null) { + //p["class"] = jsPlumb.overlayClass; // TODO currentInstance? + self.canvas = _node("shape", dim, p); + connector.appendDisplayElement(self.canvas); + self.attachListeners(self.canvas, connector); + } + else { + _pos(self.canvas, dim); + _atts(self.canvas, p); + } + }; + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + }; + + jsPlumb.Overlays.vml.Arrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.vml.PlainArrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.vml.Diamond = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the SVG renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +/** + * SVG support for jsPlumb. + * + * things to investigate: + * + * gradients: https://developer.mozilla.org/en/svg_in_html_introduction + * css:http://tutorials.jenkov.com/svg/svg-and-css.html + * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath + * pointer events: https://developer.mozilla.org/en/css/pointer-events + * + * IE9 hover jquery: http://forum.jquery.com/topic/1-6-2-broke-svg-hover-events + * + */ +;(function() { + + var svgAttributeMap = { + "joinstyle":"stroke-linejoin", + "stroke-linejoin":"stroke-linejoin", + "stroke-dashoffset":"stroke-dashoffset", + "stroke-linecap":"stroke-linecap" + }, + STROKE_DASHARRAY = "stroke-dasharray", + DASHSTYLE = "dashstyle", + LINEAR_GRADIENT = "linearGradient", + RADIAL_GRADIENT = "radialGradient", + FILL = "fill", + STOP = "stop", + STROKE = "stroke", + STROKE_WIDTH = "stroke-width", + STYLE = "style", + NONE = "none", + JSPLUMB_GRADIENT = "jsplumb_gradient_", + LINE_WIDTH = "lineWidth", + ns = { + svg:"http://www.w3.org/2000/svg", + xhtml:"http://www.w3.org/1999/xhtml" + }, + _attr = function(node, attributes) { + for (var i in attributes) + node.setAttribute(i, "" + attributes[i]); + }, + _node = function(name, attributes) { + var n = document.createElementNS(ns.svg, name); + attributes = attributes || {}; + attributes["version"] = "1.1"; + attributes["xmnls"] = ns.xhtml; + _attr(n, attributes); + return n; + }, + _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; }, + _clearGradient = function(parent) { + for (var i = 0; i < parent.childNodes.length; i++) { + if (parent.childNodes[i].tagName == LINEAR_GRADIENT || parent.childNodes[i].tagName == RADIAL_GRADIENT) + parent.removeChild(parent.childNodes[i]); + } + }, + _updateGradient = function(parent, node, style, dimensions, uiComponent) { + var id = JSPLUMB_GRADIENT + uiComponent._jsPlumb.idstamp(); + // first clear out any existing gradient + _clearGradient(parent); + // this checks for an 'offset' property in the gradient, and in the absence of it, assumes + // we want a linear gradient. if it's there, we create a radial gradient. + // it is possible that a more explicit means of defining the gradient type would be + // better. relying on 'offset' means that we can never have a radial gradient that uses + // some default offset, for instance. + if (!style.gradient.offset) { + var g = _node(LINEAR_GRADIENT, {id:id}); + parent.appendChild(g); + } + else { + var g = _node(RADIAL_GRADIENT, { + id:id + }); + parent.appendChild(g); + } + + // the svg radial gradient seems to treat stops in the reverse + // order to how canvas does it. so we want to keep all the maths the same, but + // iterate the actual style declarations in reverse order, if the x indexes are not in order. + for (var i = 0; i < style.gradient.stops.length; i++) { + // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of + // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments. + // so, not too concerned about leaving this in for now. + var styleToUse = i; + if (dimensions.length == 8) + styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i; + else + styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i; + var stopColor = jsPlumb.util.convertStyle(style.gradient.stops[styleToUse][1], true); + var s = _node(STOP, {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor}); + g.appendChild(s); + } + var applyGradientTo = style.strokeStyle ? STROKE : FILL; + node.setAttribute(STYLE, applyGradientTo + ":url(#" + id + ")"); + }, + _applyStyles = function(parent, node, style, dimensions, uiComponent) { + + if (style.gradient) { + _updateGradient(parent, node, style, dimensions, uiComponent); + } + else { + // make sure we clear any existing gradient + _clearGradient(parent); + node.setAttribute(STYLE, ""); + } + + node.setAttribute(FILL, style.fillStyle ? jsPlumb.util.convertStyle(style.fillStyle, true) : NONE); + node.setAttribute(STROKE, style.strokeStyle ? jsPlumb.util.convertStyle(style.strokeStyle, true) : NONE); + if (style.lineWidth) { + node.setAttribute(STROKE_WIDTH, style.lineWidth); + } + + // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like + // the syntax in VML but is actually kind of nasty: values are given in the pixel + // coordinate space, whereas in VML they are multiples of the width of the stroked + // line, which makes a lot more sense. for that reason, jsPlumb is supporting both + // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from + // VML, which will be the preferred method. the code below this converts a dashstyle + // attribute given in terms of stroke width into a pixel representation, by using the + // stroke's lineWidth. + if (style[DASHSTYLE] && style[LINE_WIDTH] && !style[STROKE_DASHARRAY]) { + var sep = style[DASHSTYLE].indexOf(",") == -1 ? " " : ",", + parts = style[DASHSTYLE].split(sep), + styleToUse = ""; + parts.forEach(function(p) { + styleToUse += (Math.floor(p * style.lineWidth) + sep); + }); + node.setAttribute(STROKE_DASHARRAY, styleToUse); + } + else if(style[STROKE_DASHARRAY]) { + node.setAttribute(STROKE_DASHARRAY, style[STROKE_DASHARRAY]); + } + + // extra attributes such as join type, dash offset. + for (var i in svgAttributeMap) { + if (style[i]) { + node.setAttribute(svgAttributeMap[i], style[i]); + } + } + }, + _decodeFont = function(f) { + var r = /([0-9].)(p[xt])\s(.*)/; + var bits = f.match(r); + return {size:bits[1] + bits[2], font:bits[3]}; + }, + _classManip = function(el, add, clazz) { + var classesToAddOrRemove = clazz.split(" "), + className = el.className, + curClasses = className.baseVal.split(" "); + + for (var i = 0; i < classesToAddOrRemove.length; i++) { + if (add) { + if (curClasses.indexOf(classesToAddOrRemove[i]) == -1) + curClasses.push(classesToAddOrRemove[i]); + } + else { + var idx = curClasses.indexOf(classesToAddOrRemove[i]); + if (idx != -1) + curClasses.splice(idx, 1); + } + } + + el.className.baseVal = curClasses.join(" "); + }, + _addClass = function(el, clazz) { + _classManip(el, true, clazz); + }, + _removeClass = function(el, clazz) { + _classManip(el, false, clazz); + }; + + /** + utility methods for other objects to use. + */ + jsPlumb.util.svg = { + addClass:_addClass, + removeClass:_removeClass + }; + + /* + * Base class for SVG components. + */ + //var SvgComponent = function(cssClass, originalArgs, pointerEventsSpec) { + var SvgComponent = function(params) { + var self = this, + pointerEventsSpec = params.pointerEventsSpec || "all"; + jsPlumb.jsPlumbUIComponent.apply(this, params.originalArgs); + self.canvas = null, self.path = null, self.svg = null; + + var clazz = params.cssClass + " " + (params.originalArgs[0].cssClass || ""), + svgParams = { + "style":"", + "width":0, + "height":0, + "pointer-events":pointerEventsSpec, + "position":"absolute" + }; + if (self.tooltip) svgParams["title"] = self.tooltip; + self.svg = _node("svg", svgParams); + if (params.useDivWrapper) { + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.sizeCanvas(self.canvas,0,0,1,1); + self.canvas.className = clazz; + if (self.tooltip) self.canvas.setAttribute("title", self.tooltip); + } + else { + _attr(self.svg, { "class":clazz }); + self.canvas = self.svg; + } + + params._jsPlumb.appendElement(self.canvas, params.originalArgs[0]["parent"]); + if (params.useDivWrapper) self.canvas.appendChild(self.svg); + + // TODO this displayElement stuff is common between all components, across all + // renderers. would be best moved to jsPlumbUIComponent. + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + this.paint = function(d, style, anchor) { + if (style != null) { + var x = d[0], y = d[1]; + if (params.useDivWrapper) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + x = 0, y = 0; + } + _attr(self.svg, { + "style":_pos([x, y, d[2], d[3]]), + "width": d[2], + "height": d[3] + }); + self._paint.apply(this, arguments); + } + }; + }; + + /* + * Base class for SVG connectors. + */ + var SvgConnector = jsPlumb.SvgConnector = function(params) { + var self = this; + SvgComponent.apply(this, [ { + cssClass:params["_jsPlumb"].connectorClass, + originalArgs:arguments, + pointerEventsSpec:"none", + tooltip:params.tooltip, + _jsPlumb:params["_jsPlumb"] + } ]); + this._paint = function(d, style) { + var p = self.getPath(d), a = { "d":p }, outlineStyle = null; + a["pointer-events"] = "all"; + + // outline style. actually means drawing an svg object underneath the main one. + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = jsPlumb.CurrentLibrary.extend({}, style); + outlineStyle.strokeStyle = jsPlumb.util.convertStyle(style.outlineColor); + outlineStyle.lineWidth = outlineStrokeWidth; + + if (self.bgPath == null) { + self.bgPath = _node("path", a); + self.svg.appendChild(self.bgPath); + self.attachListeners(self.bgPath, self); + } + else { + _attr(self.bgPath, a); + } + + _applyStyles(self.svg, self.bgPath, outlineStyle, d, self); + } + + + // test - see below + // a["clip-path"]= "url(#testClip)"; + + if (self.path == null) { + self.path = _node("path", a); + self.svg.appendChild(self.path); + self.attachListeners(self.path, self); + + /* + this is a test of a clip path. i'm looking into using one of these to animate a jsplumb connection. + you could do this by walking along the line, stepping along a little at a time, and setting the clip + path to extend as far as that point. + + self.clip = _node("clipPath", {id:"testClip", clipPathUnits:"objectBoundingBox"}); + self.svg.appendChild(self.clip); + self.clip.appendChild(_node("rect", { + x:"0",y:"0",width:"0.5",height:"1" + })); + */ + } + else { + _attr(self.path, a); + } + + _applyStyles(self.svg, self.path, style, d, self); + }; + + this.reattachListeners = function() { + if (self.bgPath) self.reattachListenersForElement(self.bgPath, self); + if (self.path) self.reattachListenersForElement(self.path, self); + }; + + }; + + /* + * SVG Bezier Connector + */ + jsPlumb.Connectors.svg.Bezier = function(params) { + jsPlumb.Connectors.Bezier.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]; }; + }; + + /* + * SVG straight line Connector + */ + jsPlumb.Connectors.svg.Straight = function(params) { + jsPlumb.Connectors.Straight.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; }; + }; + + jsPlumb.Connectors.svg.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "M " + dimensions[4] + "," + dimensions[5]; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)]; + } + // finally draw a line to the end + p = p + " " + dimensions[6] + "," + dimensions[7]; + return p; + }; + }; + + /* + * Base class for SVG endpoints. + */ + var SvgEndpoint = function(params) { + var self = this; + SvgComponent.apply(this, [ { + cssClass:params["_jsPlumb"].endpointClass, + originalArgs:arguments, + pointerEventsSpec:"all", + useDivWrapper:true, + _jsPlumb:params["_jsPlumb"] + } ]); + this._paint = function(d, style) { + var s = jsPlumb.extend({}, style); + if (s.outlineColor) { + s.strokeWidth = s.outlineWidth; + s.strokeStyle = jsPlumb.util.convertStyle(s.outlineColor, true); + } + + if (self.node == null) { + self.node = self.makeNode(d, s); + self.svg.appendChild(self.node); + self.attachListeners(self.node, self); + } + _applyStyles(self.svg, self.node, s, d, self); + _pos(self.node, d); + }; + + this.reattachListeners = function() { + if (self.node) self.reattachListenersForElement(self.node, self); + }; + }; + + /* + * SVG Dot Endpoint + */ + jsPlumb.Endpoints.svg.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("circle", { + "cx" : d[2] / 2, + "cy" : d[3] / 2, + "r" : d[2] / 2 + }); + }; + }; + + /* + * SVG Rectangle Endpoint + */ + jsPlumb.Endpoints.svg.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("rect", { + "width":d[2], + "height":d[3] + }); + }; + }; + + /* + * SVG Image Endpoint is the default image endpoint. + */ + jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image; + /* + * Blank endpoint in svg renderer is the default Blank endpoint. + */ + jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank; + /* + * Label endpoint in svg renderer is the default Label endpoint. + */ + jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label; + + var AbstractSvgArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + this.isAppendedAtTopLevel = false; + var self = this, path =null; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + } + var clazz = originalArgs && (originalArgs.length == 1) ? (originalArgs[0].cssClass || "") : ""; + + _attr(path, { + "d" : makePath(d), + "class" : clazz, + stroke : strokeStyle ? strokeStyle : null, + fill : fillStyle ? fillStyle : null + }); + }; + var makePath = function(d) { + return "M" + d.hxy.x + "," + d.hxy.y + + " L" + d.tail[0].x + "," + d.tail[0].y + + " L" + d.cxy.x + "," + d.cxy.y + + " L" + d.tail[1].x + "," + d.tail[1].y + + " L" + d.hxy.x + "," + d.hxy.y; + }; + this.reattachListeners = function() { + if (path) self.reattachListenersForElement(path, self); + }; + }; + + jsPlumb.Overlays.svg.Arrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.svg.PlainArrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.svg.Diamond = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; + + // a test + jsPlumb.Overlays.svg.GuideLines = function() { + var path = null, self = this, path2 = null, p1_1, p1_2; + jsPlumb.Overlays.GuideLines.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + + p1_1 = _node("path"); + connector.svg.appendChild(p1_1); + self.attachListeners(p1_1, connector); + self.attachListeners(p1_1, self); + + p1_2 = _node("path"); + connector.svg.appendChild(p1_2); + self.attachListeners(p1_2, connector); + self.attachListeners(p1_2, self); + + } + + _attr(path, { + "d" : makePath(d[0], d[1]), + stroke : "red", + fill : null + }); + + _attr(p1_1, { + "d" : makePath(d[2][0], d[2][1]), + stroke : "blue", + fill : null + }); + + _attr(p1_2, { + "d" : makePath(d[3][0], d[3][1]), + stroke : "green", + fill : null + }); + }; + + var makePath = function(d1, d2) { + return "M " + d1.x + "," + d1.y + + " L" + d2.x + "," + d2.y; + }; + + }; +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the HTML5 canvas renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + +// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS ******************************************************************* + + // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. + var _connectionBeingDragged = null, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, + _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }; + + /* + * Class:CanvasMouseAdapter + * Provides support for mouse events on canvases. + */ + var CanvasMouseAdapter = function() { + var self = this; + self.overlayPlacements = []; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + jsPlumb.EventGenerator.apply(this, arguments); + /** + * returns whether or not the given event is ojver a painted area of the canvas. + */ + this._over = function(e) { + var o = _getOffset(_getElementObject(self.canvas)), + pageXY = _pageXY(e), + x = pageXY[0] - o.left, y = pageXY[1] - o.top; + if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + return false; + }; + + var _mouseover = false, _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false, + _nullSafeHasClass = function(el, clazz) { + return el != null && _hasClass(el, clazz); + }; + this.mousemove = function(e) { + var pageXY = _pageXY(e), clientXY = _clientXY(e), + ee = document.elementFromPoint(clientXY[0], clientXY[1]), + eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); + var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); + if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.fire("mouseenter", self, e); + return true; + } + // TODO here there is a remote chance that the overlay the mouse moved onto + // is actually not an overlay for the current component. a more thorough check would + // be to ensure the overlay belonged to the current component. + else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { + _mouseover = false; + self.fire("mouseexit", self, e); + } + self.fire("mousemove", self, e); + }; + + this.click = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); + self.fire("mousedown", self, e); + } + }; + + this.mouseup = function(e) { + _mouseDown = false; + self.fire("mouseup", self, e); + }; + + this.contextmenu = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("contextmenu", self, e); + _mouseWasDown = false; + }; + }; + + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + jsPlumb.appendElement(canvas, params.parent); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + params["_jsPlumb"].getId(canvas, params.uuid); + if (params.tooltip) canvas.setAttribute("title", params.tooltip); + + return canvas; + }; + + var CanvasComponent = function(params) { + CanvasMouseAdapter.apply(this, arguments); + + var displayElements = [ ]; + this.getDisplayElements = function() { return displayElements; }; + this.appendDisplayElement = function(el) { displayElements.push(el); }; + } + + /** + * Class:CanvasConnector + * Superclass for Canvas Connector renderers. + */ + var CanvasConnector = jsPlumb.CanvasConnector = function(params) { + + CanvasComponent.apply(this, arguments); + + var _paintOneStyle = function(dim, aStyle) { + self.ctx.save(); + jsPlumb.extend(self.ctx, aStyle); + if (aStyle.gradient) { + var g = self.createGradient(dim, self.ctx); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + self.ctx.strokeStyle = g; + } + self._paint(dim); + self.ctx.restore(); + }; + + var self = this, + clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:params.tooltip + }); + self.ctx = self.canvas.getContext("2d"); + + self.appendDisplayElement(self.canvas); + + self.paint = function(dim, style) { + if (style != null) { + jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + _paintOneStyle(dim, outlineStyle); + } + _paintOneStyle(dim, style); + } + }; + }; + + /** + * Class:CanvasEndpoint + * Superclass for Canvas Endpoint renderers. + */ + var CanvasEndpoint = function(params) { + var self = this; + CanvasComponent.apply(this, arguments); + var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""), + canvasParams = { + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:self.tooltip + }; + self.canvas = _newCanvas(canvasParams); + self.ctx = self.canvas.getContext("2d"); + + self.appendDisplayElement(self.canvas); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + } + + self._paint.apply(this, arguments); + }; + }; + + jsPlumb.Endpoints.canvas.Dot = function(params) { + jsPlumb.Endpoints.Dot.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + var self = this, + parseValue = function(value) { + try { return parseInt(value); } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }, + calculateAdjustments = function(gradient) { + var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius; + gradient.offset && (offsetAdjustment = parseValue(gradient.offset)); + gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius)); + return [offsetAdjustment, innerRadius]; + }; + this._paint = function(d, style, anchor) { + if (style != null) { + var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + if (style.gradient) { + var adjustments = calculateAdjustments(style.gradient), + yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0], + xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0], + g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + ctx.beginPath(); + ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + } + }; + }; + + jsPlumb.Endpoints.canvas.Rectangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) { + + var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + + /* canvas gradient */ + if (style.gradient) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, d[2], d[3]); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + jsPlumb.Endpoints.canvas.Triangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Triangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) + { + var width = d[2], height = d[3], x = d[0], y = d[1], + ctx = self.canvas.getContext('2d'), + offsetX = 0, offsetY = 0, angle = 0, + orientation = anchor.getOrientation(self); + + if( orientation[0] == 1 ) { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = style.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + /* + * Canvas Image Endpoint: uses the default version, which creates an tag. + */ + jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image; + + /* + * Blank endpoint in all renderers is just the default Blank endpoint. + */ + jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank; + + /* + * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Bezier = function() { + var self = this; + jsPlumb.Connectors.Bezier.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO i doubt this handles the case that source and target are swapped. + this.createGradient = function(dim, ctx, swap) { + return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + /* + * Canvas straight line Connector. Draws a straight line onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Straight = function() { + var self = this; + jsPlumb.Connectors.Straight.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO this does not handle the case that src and target are swapped. + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + jsPlumb.Connectors.canvas.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + +// ********************************* END OF CANVAS RENDERERS ******************************************************************* + + jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label; + + /** + * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. + */ + var CanvasOverlay = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }; + + var AbstractCanvasArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + CanvasOverlay.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + var ctx = connector.ctx; + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(d.hxy.x, d.hxy.y); + ctx.lineTo(d.tail[0].x, d.tail[0].y); + ctx.lineTo(d.cxy.x, d.cxy.y); + ctx.lineTo(d.tail[1].x, d.tail[1].y); + ctx.lineTo(d.hxy.x, d.hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + }; + + jsPlumb.Overlays.canvas.Arrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.canvas.PlainArrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.canvas.Diamond = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the jQuery adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ +/* + * the library specific functions, such as find offset, get id, get attribute, extend etc. + * the full list is: + * + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getDropScope gets the drop scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getPageXY gets the page event's xy location. + * getParent gets the parent of some element. + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * hasClass returns whether or not the given element has the given class. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + * trigger triggers some event on an element. + * unbind unbinds some listener from some element. + */ +(function($) { + + //var getBoundingClientRectSupported = "getBoundingClientRect" in document.documentElement; + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el[0].className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.addClass(el[0], clazz); + } + } + catch (e) { + // SVGAnimatedString not supported; no problem. + } + el.addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * appends the given child to the given parent. + */ + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).append(child); + }, + + /** + * event binding wrapper. it just so happens that jQuery uses 'bind' also. yui3, for example, + * uses 'on'. + */ + bind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.bind(event, callback); + }, + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step', + 'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete' + }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + getDocumentElement : function() { return document; }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + }, + + getDragScope : function(el) { + return el.draggable("option", "scope"); + }, + + getDropScope : function(el) { + return el.droppable("option", "scope"); + }, + + /** + * gets a DOM element from the given input, which might be a string (in which case we just do document.getElementById), + * a selector (in which case we return el[0]), or a DOM element already (we assume this if it's not either of the other + * two cases). this is the opposite of getElementObject below. + */ + getDOMElement : function(el) { + if (typeof(el) == "string") return document.getElementById(el); + else if (el.context) return el[0]; + else return el; + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + * + */ + getElementObject : function(el) { + return typeof(el) == "string" ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).parent(); + }, + + getScrollLeft : function(el) { + return el.scrollLeft(); + }, + + getScrollTop : function(el) { + return el.scrollTop(); + }, + + getSelector : function(spec) { + return $(spec); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e.length > 0 ? e[0].tagName : null; + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + + // this code is a workaround for the case that the element being dragged has a margin set on it. jquery UI passes + // in the wrong offset if the element has a margin (it doesn't take the margin into account). the getBoundingClientRect + // method, which is in pretty much all browsers now, reports the right numbers. but it introduces a noticeable lag, which + // i don't like. + + /*if ( getBoundingClientRectSupported ) { + var r = eventArgs[1].helper[0].getBoundingClientRect(); + return { left : r.left, top: r.top }; + } else {*/ + if (eventArgs.length == 1) { + ret = { left: eventArgs[0].pageX, top:eventArgs[0].pageY }; + } + else { + var ui = eventArgs[1], _offset = ui.offset; + ret = _offset || ui.absolutePosition; + } + return ret; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options) { + options = options || {}; + // remove helper directive if present. + options.helper = null; + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.draggable(options); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.droppable(options); + }, + + isAlreadyDraggable : function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return el.hasClass("ui-draggable"); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el[0].className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.removeClass(el[0], clazz); + } + } + catch (e) { + // SVGAnimatedString not supported; no problem. + } + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).remove(); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * sets the drag scope. probably time for a setDragOption method (roll this and the one above together) + * @param el + * @param scope + */ + setDragScope : function(el, scope) { + el.draggable("option", "scope", scope); + }, + + setOffset : function(el, o) { + jsPlumb.CurrentLibrary.getElementObject(el).offset(o); + }, + + /** + * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself. + * the other libraries do not. yui, in fact, cannot even pass an original event. we have to pull out stuff + * from the originalEvent to put in an options object for YUI. + * @param el + * @param event + * @param originalEvent + */ + trigger : function(el, event, originalEvent) { + //originalEvent.stopPropagation(); + //jsPlumb.CurrentLibrary.getElementObject(el).trigger(originalEvent); + var h = jQuery._data(jsPlumb.CurrentLibrary.getElementObject(el)[0], "handle"); + h(originalEvent); + //originalEvent.stopPropagation(); + }, + + /** + * event unbinding wrapper. it just so happens that jQuery uses 'unbind' also. yui3, for example, + * uses..something else. + */ + unbind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.unbind(event, callback); + } + }; + + $(document).ready(jsPlumb.init); + +})(jQuery); + +(function(){if("undefined"==typeof Math.sgn)Math.sgn=function(a){return 0==a?0:0n?n=l:lb.location)b.location=0;return t(a,b.location)},nearestPointOnCurve:function(a,b){var f=u(a,b);return{point:r(b,b.length-1,f.location,null,null),location:f.location}},pointOnCurve:p,pointAlongCurveFrom:function(a,b,f){return s(a,b,f).point},perpendicularToCurveAt:function(a,b,f,d){b=s(a,b,null==d?0:d);a=t(a,b.location);d=Math.atan(-1/a);a=f/2*Math.sin(d);f=f/2*Math.cos(d);return[{x:b.point.x+f,y:b.point.y+a},{x:b.point.x-f,y:b.point.y-a}]}}})(); \ No newline at end of file diff --git a/archive/1.3.5/jsPlumb-1.3.5-RC1.js b/archive/1.3.5/jsPlumb-1.3.5-RC1.js new file mode 100644 index 000000000..da5c83cc1 --- /dev/null +++ b/archive/1.3.5/jsPlumb-1.3.5-RC1.js @@ -0,0 +1,4938 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the jsPlumb core code. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + /** + * Class:jsPlumb + * The jsPlumb engine, registered as a static object in the window. This object contains all of the methods you will use to + * create and maintain Connections and Endpoints. + */ + + var canvasAvailable = !!document.createElement('canvas').getContext, + svgAvailable = !!window.SVGAngle || document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"), + // TODO what is a good test for VML availability? aside from just assuming its there because nothing else is. + vmlAvailable = !(canvasAvailable | svgAvailable); + + // finds the index of some value in an array, using a deep equals test. + var _findIndex = function(a, v, b, s) { + var _eq = function(o1, o2) { + if (o1 === o2) return true; + else if (typeof o1 == "object" && typeof o2 == "object") { + var same = true; + for ( var propertyName in o1) { + if (!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for ( var propertyName in o2) { + if (!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for ( var i = +b || 0, l = a.length; i < l; i++) { + if (_eq(a[i], v)) + return i; + } + return -1; + }, + _findWithFunction = function(a, f) { + if (a) + for (var i = 0; i < a.length; i++) if (f(a[i])) return i; + return -1; + }, + _removeWithFunction = function(a, f) { + var idx = _findWithFunction(a, f); + if (idx > -1) a.splice(idx, 1); + return idx != -1; + }, + // TODO support insert index + _addWithFunction = function(list, item, hashFunction) { + if (_findWithFunction(list, hashFunction) == -1) list.push(item); + }; + + + // for those browsers that dont have it. they still don't have it! but at least they won't crash. + if (!window.console) + window.console = { time:function(){}, timeEnd:function(){}, group:function(){}, groupEnd:function(){}, log:function(){} }; + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = [], map[key] = l; + } + l.push(value); + return l; + }, + _connectionBeingDragged = null, + _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }, + _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }, + _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }, + _logEnabled = true, + _log = function() { + if (_logEnabled && typeof console != "undefined") { + try { + var msg = arguments[arguments.length - 1]; + console.log(msg); + } + catch (e) {} + } + }, + _group = function(g) { + if (_logEnabled && typeof console != "undefined") console.group(g); + }, + _groupEnd = function(g) { + if (_logEnabled && typeof console != "undefined") console.groupEnd(g); + }, + _time = function(t) { + if (_logEnabled && typeof console != "undefined") console.time(t); + }, + _timeEnd = function(t) { + if (_logEnabled && typeof console != "undefined") console.timeEnd(t); + }; + + /** + * EventGenerator + * Superclass for objects that generate events - jsPlumb extends this, as does jsPlumbUIComponent, which all the UI elements extend. + */ + EventGenerator = function() { + var _listeners = {}, self = this; + + // this is a list of events that should re-throw any errors that occur during their dispatch. as of 1.3.0 this is private to + // jsPlumb, but it seems feasible that people might want to manipulate this list. the thinking is that we don't want event + // listeners to bring down jsPlumb - or do we. i can't make up my mind about this, but i know i want to hear about it if the "ready" + // event fails, because then my page has most likely not initialised. so i have this halfway-house solution. it will be interesting + // to hear what other people think. + var eventsToDieOn = [ "ready" ]; + + /* + * Binds a listener to an event. + * + * Parameters: + * event - name of the event to bind to. + * listener - function to execute. + */ + this.bind = function(event, listener) { + _addToList(_listeners, event, listener); + }; + /* + * Fires an update for the given event. + * + * Parameters: + * event - event to fire + * value - value to pass to the event listener(s). + * originalEvent - the original event from the browser + */ + this.fire = function(event, value, originalEvent) { + if (_listeners[event]) { + for ( var i = 0; i < _listeners[event].length; i++) { + // doing it this way rather than catching and then possibly re-throwing means that an error propagated by this + // method will have the whole call stack available in the debugger. + if (_findIndex(eventsToDieOn, event) != -1) + _listeners[event][i](value, originalEvent); + else { + // for events we don't want to die on, catch and log. + try { + _listeners[event][i](value, originalEvent); + } catch (e) { + _log("jsPlumb: fire failed for event " + event + " : " + e); + } + } + } + } + }; + /* + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + this.clearListeners = function(event) { + if (event) + delete _listeners[event]; + else { + delete _listeners; + _listeners = {}; + } + }; + + this.getListener = function(forEvent) { + return _listeners[forEvent]; + }; + }, + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + _timestamp = function() { return "" + (new Date()).getTime(); }, + + /* + * Class:jsPlumbUIComponent + * Abstract superclass for UI components Endpoint and Connection. Provides the abstraction of paintStyle/hoverPaintStyle, + * and also extends EventGenerator to provide the bind and fire methods. + */ + jsPlumbUIComponent = function(params) { + var self = this, a = arguments, _hover = false, parameters = params.parameters || {}, idPrefix = self.idPrefix, + id = idPrefix + (new Date()).getTime(); + self._jsPlumb = params["_jsPlumb"]; + self.getId = function() { return id; }; + self.tooltip = params.tooltip; + self.hoverClass = params.hoverClass; + + // all components can generate events + EventGenerator.apply(this); + // all components get this clone function. + // TODO issue 116 showed a problem with this - it seems 'a' that is in + // the clone function's scope is shared by all invocations of it, the classic + // JS closure problem. for now, jsPlumb does a version of this inline where + // it used to call clone. but it would be nice to find some time to look + // further at this. + this.clone = function() { + var o = new Object(); + self.constructor.apply(o, a); + return o; + }; + + this.getParameter = function(name) { return parameters[name]; }, + this.getParameters = function() { return parameters; }, + this.setParameter = function(name, value) { parameters[name] = value; }, + this.setParameters = function(p) { parameters = p; }, + this.overlayPlacements = [], + this.paintStyle = null, + this.hoverPaintStyle = null; + + // user can supply a beforeDetach callback, which will be executed before a detach + // is performed; returning false prevents the detach. + var beforeDetach = params.beforeDetach; + this.isDetachAllowed = function(connection) { + var r = true; + if (beforeDetach) { + try { + r = beforeDetach(connection); + } + catch (e) { _log("jsPlumb: beforeDetach callback failed", e); } + } + return r; + }; + + // user can supply a beforeDrop callback, which will be executed before a dropped + // connection is confirmed. user can return false to reject connection. + var beforeDrop = params.beforeDrop; + this.isDropAllowed = function(sourceId, targetId, scope) { + var r = self._jsPlumb.checkCondition("beforeDrop", { sourceId:sourceId, targetId:targetId, scope:scope }); + if (beforeDrop) { + try { + r = beforeDrop({ sourceId:sourceId, targetId:targetId, scope:scope }); + } + catch (e) { _log("jsPlumb: beforeDrop callback failed", e); } + } + return r; + }; + + // helper method to update the hover style whenever it, or paintStyle, changes. + // we use paintStyle as the foundation and merge hoverPaintStyle over the + // top. + var _updateHoverStyle = function() { + if (self.paintStyle && self.hoverPaintStyle) { + var mergedHoverStyle = {}; + jsPlumb.extend(mergedHoverStyle, self.paintStyle); + jsPlumb.extend(mergedHoverStyle, self.hoverPaintStyle); + delete self["hoverPaintStyle"]; + // we want the fillStyle of paintStyle to override a gradient, if possible. + if (mergedHoverStyle.gradient && self.paintStyle.fillStyle) + delete mergedHoverStyle["gradient"]; + self.hoverPaintStyle = mergedHoverStyle; + } + }; + + /* + * Sets the paint style and then repaints the element. + * + * Parameters: + * style - Style to use. + */ + this.setPaintStyle = function(style, doNotRepaint) { + self.paintStyle = style; + self.paintStyleInUse = self.paintStyle; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /* + * Sets the paint style to use when the mouse is hovering over the element. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the component will not be repainted. useful when setting things up initially. + */ + this.setHoverPaintStyle = function(style, doNotRepaint) { + self.hoverPaintStyle = style; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /* + * sets/unsets the hover state of this element. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + this.setHover = function(hover, ignoreAttachedElements, timestamp) { + // while dragging, we ignore these events. this keeps the UI from flashing and + // swishing and whatevering. + if (!self._jsPlumb.currentlyDragging && !self._jsPlumb.isHoverSuspended()) { + + _hover = hover; + if (self.hoverClass != null && self.canvas != null) { + if (hover) + jpcl.addClass(self.canvas, self.hoverClass); + else + jpcl.removeClass(self.canvas, self.hoverClass); + } + if (self.hoverPaintStyle != null) { + self.paintStyleInUse = hover ? self.hoverPaintStyle : self.paintStyle; + timestamp = timestamp || _timestamp(); + self.repaint({timestamp:timestamp, recalc:false}); + } + // get the list of other affected elements, if supported by this component. + // for a connection, its the endpoints. for an endpoint, its the connections! surprise. + if (self.getAttachedElements && !ignoreAttachedElements) + _updateAttachedElements(hover, _timestamp(), self); + } + }; + + this.isHover = function() { return _hover; }; + + var jpcl = jsPlumb.CurrentLibrary, + events = [ "click", "dblclick", "mouseenter", "mouseout", "mousemove", "mousedown", "mouseup", "contextmenu" ], + eventFilters = { "mouseout":"mouseexit" }, + bindOne = function(o, c, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.bind(o, evt, function(ee) { + c.fire(filteredEvent, c, ee); + }); + }, + unbindOne = function(o, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.unbind(o, evt); + }; + + this.attachListeners = function(o, c) { + for (var i = 0; i < events.length; i++) { + bindOne(o, c, events[i]); + } + }; + + var _updateAttachedElements = function(state, timestamp, sourceElement) { + var affectedElements = self.getAttachedElements(); // implemented in subclasses + if (affectedElements) { + for (var i = 0; i < affectedElements.length; i++) { + if (!sourceElement || sourceElement != affectedElements[i]) + affectedElements[i].setHover(state, true, timestamp); // tell the attached elements not to inform their own attached elements. + } + } + }; + + this.reattachListenersForElement = function(o) { + if (arguments.length > 1) { + for (var i = 0; i < events.length; i++) + unbindOne(o, events[i]); + for (var i = 1; i < arguments.length; i++) + self.attachListeners(o, arguments[i]); + } + }; + }, + + overlayCapableJsPlumbUIComponent = function(params) { + jsPlumbUIComponent.apply(this, arguments); + var self = this; + /* + * Property: overlays + * List of Overlays for this component. + */ + this.overlays = []; + + var processOverlay = function(o) { + var _newOverlay = null; + if (o.constructor == Array) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0], + // make a copy of the object so as not to mess up anyone else's reference... + p = jsPlumb.extend({component:self, _jsPlumb:self._jsPlumb}, o[1]); + if (o.length == 3) jsPlumb.extend(p, o[2]); + _newOverlay = new jsPlumb.Overlays[self._jsPlumb.getRenderMode()][type](p); + if (p.events) { + for (var evt in p.events) { + _newOverlay.bind(evt, p.events[evt]); + } + } + } else if (o.constructor == String) { + _newOverlay = new jsPlumb.Overlays[self._jsPlumb.getRenderMode()][o]({component:self, _jsPlumb:self._jsPlumb}); + } else { + _newOverlay = o; + } + + self.overlays.push(_newOverlay); + }, + calculateOverlaysToAdd = function(params) { + var defaultKeys = self.defaultOverlayKeys || [], + o = params.overlays, + checkKey = function(k) { + return self._jsPlumb.Defaults[k] || jsPlumb.Defaults[k] || []; + }; + + if (!o) o = []; + + for (var i = 0; i < defaultKeys.length; i++) + o.unshift.apply(o, checkKey(defaultKeys[i])); + + return o; + } + + var _overlays = calculateOverlaysToAdd(params);//params.overlays || self._jsPlumb.Defaults.Overlays; + if (_overlays) { + for (var i = 0; i < _overlays.length; i++) { + processOverlay(_overlays[i]); + } + } + + // overlay finder helper method + var _getOverlayIndex = function(id) { + var idx = -1; + for (var i = 0; i < self.overlays.length; i++) { + if (id === self.overlays[i].id) { + idx = i; + break; + } + } + return idx; + }; + + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + this.addOverlay = function(overlay) { + processOverlay(overlay); + self.repaint(); + }; + + /* + * Function: getOverlay + * Gets an overlay, by ID. Note: by ID. You would pass an 'id' parameter + * in to the Overlay's constructor arguments, and then use that to retrieve + * it via this method. + */ + this.getOverlay = function(id) { + var idx = _getOverlayIndex(id); + return idx >= 0 ? self.overlays[idx] : null; + }; + + /* + * Function: hideOverlay + * Hides the overlay specified by the given id. + */ + this.hideOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.hide(); + }; + + /* + * Function: showOverlay + * Shows the overlay specified by the given id. + */ + this.showOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.show(); + }; + + /** + * Function: removeAllOverlays + * Removes all overlays from the Connection, and then repaints. + */ + this.removeAllOverlays = function() { + self.overlays.splice(0, self.overlays.length); + self.repaint(); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + this.removeOverlay = function(overlayId) { + var idx = _getOverlayIndex(overlayId); + if (idx != -1) { + var o = self.overlays[idx]; + o.cleanup(); + self.overlays.splice(idx, 1); + } + }; + + /** + * Function:removeOverlays + * Removes a set of overlays by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + this.removeOverlays = function() { + for (var i = 0; i < arguments.length; i++) + self.removeOverlay(arguments[i]); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + var _internalLabelOverlayId = "__label", + _makeLabelOverlay = function(params) { + + var _params = { + cssClass:params.cssClass, + labelStyle : this.labelStyle, + id:_internalLabelOverlayId, + component:self, + _jsPlumb:self._jsPlumb + }, + mergedParams = jsPlumb.extend(_params, params); + + return new jsPlumb.Overlays[self._jsPlumb.getRenderMode()].Label( mergedParams ); + }; + if (params.label) { + var loc = params.labelLocation || self.defaultLabelLocation || 0.5, + labelStyle = params.labelStyle || self._jsPlumb.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.overlays.push(_makeLabelOverlay({ + label:params.label, + location:loc, + labelStyle:labelStyle + })); + } + + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String, a Function that returns a String, or a params object containing { "label", "labelStyle", "location", "cssClass" } + */ + this.setLabel = function(l) { + var lo = self.getOverlay(_internalLabelOverlayId); + if (!lo) { + var params = l.constructor == String || l.constructor == Function ? { label:l } : l; + lo = _makeLabelOverlay(params); + this.overlays.push(lo); + } + else { + if (l.constructor == String || l.constructor == Function) lo.setLabel(l); + else { + if (l.label) lo.setLabel(l.label); + if (l.location) lo.setLocation(l.location); + } + } + + self.repaint(); + }; + + /* + Function:getLabel + Returns the label text for this component (or a function if you are labelling with a function). + This does not return the overlay itself; this is a convenience method which is a pair with + setLabel; together they allow you to add and access a Label Overlay without having to create the + Overlay object itself. For access to the underlying label overlay that jsPlumb has created, + use getLabelOverlay. + */ + this.getLabel = function() { + var lo = self.getOverlay(_internalLabelOverlayId); + return lo != null ? lo.getLabel() : null; + }; + + /* + Function:getLabelOverlay + Returns the underlying internal label overlay, which will exist if you specified a label on + a connect or addEndpoint call, or have called setLabel at any stage. + */ + this.getLabelOverlay = function() { + return self.getOverlay(_internalLabelOverlayId); + } + }, + + _bindListeners = function(obj, _self, _hoverFunction) { + obj.bind("click", function(ep, e) { _self.fire("click", _self, e); }); + obj.bind("dblclick", function(ep, e) { _self.fire("dblclick", _self, e); }); + obj.bind("contextmenu", function(ep, e) { _self.fire("contextmenu", _self, e); }); + // this method is different; endpoint delegates hover to the first connection if there is one. + // that allows the connection to notify all its endpoint and avoids a circular loop + obj.bind("mouseenter", function(ep, e) { + if (!_self.isHover()) { + _hoverFunction(true); + _self.fire("mouseenter", _self, e); + } + }); + // this method is different; endpoint delegates hover to the first connection if there is one. + // that allows the connection to notify all its endpoint and avoids a circular loop + obj.bind("mouseexit", function(ep, e) { + if (_self.isHover()) { + _hoverFunction(false); + _self.fire("mouseexit", self, e); + } + }); + }; + + var _jsPlumbInstanceIndex = 0, + getInstanceIndex = function() { + var i = _jsPlumbInstanceIndex + 1; + _jsPlumbInstanceIndex++; + return i; + }; + + var jsPlumbInstance = function(_defaults) { + + /* + * Property: Defaults + * + * These are the default settings for jsPlumb. They are what will be used if you do not supply specific pieces of information + * to the various API calls. A convenient way to implement your own look and feel can be to override these defaults + * by including a script somewhere after the jsPlumb include, but before you make any calls to jsPlumb. + * + * Properties: + * - *Anchor* The default anchor to use for all connections (both source and target). Default is "BottomCenter". + * - *Anchors* The default anchors to use ([source, target]) for all connections. Defaults are ["BottomCenter", "BottomCenter"]. + * - *Connector* The default connector definition to use for all connections. Default is "Bezier". + * - *Container* Optional selector or element id that instructs jsPlumb to append elements it creates to a specific element. + * - *DragOptions* The default drag options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *DropOptions* The default drop options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *Endpoint* The default endpoint definition to use for all connections (both source and target). Default is "Dot". + * - *Endpoints* The default endpoint definitions ([ source, target ]) to use for all connections. Defaults are ["Dot", "Dot"]. + * - *EndpointStyle* The default style definition to use for all endpoints. Default is fillStyle:"#456". + * - *EndpointStyles* The default style definitions ([ source, target ]) to use for all endpoints. Defaults are empty. + * - *EndpointHoverStyle* The default hover style definition to use for all endpoints. Default is null. + * - *EndpointHoverStyles* The default hover style definitions ([ source, target ]) to use for all endpoints. Defaults are null. + * - *HoverPaintStyle* The default hover style definition to use for all connections. Defaults are null. + * - *LabelStyle* The default style to use for label overlays on connections. + * - *LogEnabled* Whether or not the jsPlumb log is enabled. defaults to false. + * - *Overlays* The default overlay definitions. Defaults to an empty list. + * - *MaxConnections* The default maximum number of connections for an Endpoint. Defaults to 1. + * - *MouseEventsEnabled* Whether or not mouse events are enabled when using the canvas renderer. Defaults to true. + * The idea of this is just to give people a way to prevent all the mouse listeners from activating if they know they won't need mouse events. + * - *PaintStyle* The default paint style for a connection. Default is line width of 8 pixels, with color "#456". + * - *RenderMode* What mode to use to paint with. If you're on IE<9, you don't really get to choose this. You'll just get VML. Otherwise, the jsPlumb default is to use Canvas elements. + * - *Scope* The default "scope" to use for connections. Scope lets you assign connections to different categories. + */ + this.Defaults = { + Anchor : "BottomCenter", + Anchors : [ null, null ], + ConnectionsDetachable : true, + Connector : "Bezier", + Container : null, + DragOptions : { }, + DropOptions : { }, + Endpoint : "Dot", + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : "#456" }, + EndpointStyles : [ null, null ], + EndpointHoverStyle : null, + EndpointHoverStyles : [ null, null ], + HoverPaintStyle : null, + LabelStyle : { color : "black" }, + LogEnabled : false, + Overlays : [ ], + MaxConnections : 1, + MouseEventsEnabled : true, + PaintStyle : { lineWidth : 8, strokeStyle : "#456" }, + //Reattach:false, + RenderMode : "svg", + Scope : "jsPlumb_DefaultScope" + }; + if (_defaults) jsPlumb.extend(this.Defaults, _defaults); + + this.logEnabled = this.Defaults.LogEnabled; + + EventGenerator.apply(this); + var _bb = this.bind; + this.bind = function(event, fn) { + if ("ready" === event && initialized) fn(); + else _bb(event, fn); + }; + var _currentInstance = this, + _instanceIndex = getInstanceIndex(), + + log = null, + repaintFunction = function() { + jsPlumb.repaintEverything(); + }, + automaticRepaint = true, + repaintEverything = function() { + if (automaticRepaint) + repaintFunction(); + }, + resizeTimer = null, + initialized = false, + connectionsByScope = {}, + /** + * map of element id -> endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + endpointsByElement = {}, + endpointsByUUID = {}, + offsets = {}, + offsetTimestamps = {}, + floatingConnections = {}, + draggableStates = {}, + _mouseEventsEnabled = this.Defaults.MouseEventsEnabled, + canvasList = [], + sizes = [], + //listeners = {}, // a map: keys are event types, values are lists of listeners. + DEFAULT_SCOPE = this.Defaults.Scope, + renderMode = null, // will be set in init() + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }, + + /** + * appends an element to some other element, which is calculated as follows: + * + * 1. if _currentInstance.Defaults.Container exists, use that element. + * 2. if the 'parent' parameter exists, use that. + * 3. otherwise just use the document body. + * + */ + _appendElement = function(el, parent) { + if (_currentInstance.Defaults.Container) + jsPlumb.CurrentLibrary.appendElement(el, _currentInstance.Defaults.Container); + else if (!parent) + document.body.appendChild(el); + else + jsPlumb.CurrentLibrary.appendElement(el, parent); + }, + + _curIdStamp = 1, + _idstamp = function() { return "" + _curIdStamp++; }, + + /** + * YUI, for some reason, put the result of a Y.all call into an object that contains + * a '_nodes' array, instead of handing back an array-like object like the other + * libraries do. + */ + _convertYUICollection = function(c) { + return c._nodes ? c._nodes : c; + }, + + _suspendDrawing = false, + /* + sets whether or not to suspend drawing. you should use this if you need to connect a whole load of things in one go. + it will save you a lot of time. + */ + _setSuspendDrawing = function(val, repaintAfterwards) { + _suspendDrawing = val; + if (repaintAfterwards) _currentInstance.repaintEverything(); + }, + + /** + * Draws an endpoint and its connections. this is the main entry point into drawing connections as well + * as endpoints, since jsPlumb is endpoint-centric under the hood. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + _draw = function(element, ui, timestamp) { + if (!_suspendDrawing) { + var id = _getAttribute(element, "id"); + _currentInstance.anchorManager.redraw(id, ui, timestamp); + } + }, + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }, + + /** + * gets an Endpoint by uuid. + */ + _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }, + + /** + * inits a draggable if it's not already initialised. + */ + _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? false : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element) && !jsPlumb.CurrentLibrary.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents["drag"], + stopEvent = jsPlumb.CurrentLibrary.dragEvents["stop"]; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + draggableStates[_getId(element)] = true; + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options, false); + } + } + }, + + /* + * prepares a final params object that can be passed to _newConnection, taking into account defaults, events, etc. + */ + _prepareConnectionParams = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + // copy in any connectorOverlays that were specified on the source endpoint. + // it doesnt copy target endpoint overlays. i'm not sure if we want it to or not. + if (_p.sourceEndpoint && _p.sourceEndpoint.connectorOverlays) { + _p.overlays = _p.overlays || []; + for (var i = 0; i < _p.sourceEndpoint.connectorOverlays.length; i++) { + _p.overlays.push(_p.sourceEndpoint.connectorOverlays[i]); + } + } + + // tooltip. params.tooltip takes precedence, then sourceEndpoint.connectorTooltip. + _p.tooltip = params.tooltip; + if (!_p.tooltip && _p.sourceEndpoint && _p.sourceEndpoint.connectorTooltip) + _p.tooltip = _p.sourceEndpoint.connectorTooltip; + + if (_p.target && !_p.target.endpoint) { + var tid = _getId(_p.target), + tep =_targetEndpointDefinitions[tid]; + + var overrideOne = function(singlePropertyName, pluralPropertyName, tepProperty, tep) { + if (tep[tepProperty]) { + if (_p[pluralPropertyName]) _p[pluralPropertyName][1] = tep[tepProperty]; + else if (_p[singlePropertyName]) { + _p[pluralPropertyName] = [ _p[singlePropertyName], tep[tepProperty] ]; + _p[singlePropertyName] = null; + } + else _p[pluralPropertyName] = [ null, tep[tepProperty] ]; + } + }; + + if (tep) { + overrideOne("endpoint", "endpoints", "endpoint", tep); + overrideOne("endpointStyle", "endpointStyles", "paintStyle", tep); + overrideOne("endpointHoverStyle", "endpointHoverStyles", "hoverPaintStyle", tep); + } + } + + return _p; + }, + + _newConnection = function(params) { + var connectionFunc = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + endpointFunc = _currentInstance.Defaults.EndpointType || Endpoint, + parent = jsPlumb.CurrentLibrary.getParent; + + if (params.container) + params["parent"] = params.container; + else { + if (params.sourceEndpoint) + params["parent"] = params.sourceEndpoint.parent; + else if (params.source.constructor == endpointFunc) + params["parent"] = params.source.parent; + else params["parent"] = parent(params.source); + } + + params["_jsPlumb"] = _currentInstance; + var con = new connectionFunc(params); + con.id = "con_" + _idstamp(); + _eventFireProxy("click", "click", con); + _eventFireProxy("dblclick", "dblclick", con); + _eventFireProxy("contextmenu", "contextmenu", con); + return con; + }, + + /** + * adds the connection to the backing model, fires an event if necessary and then redraws + */ + _finaliseConnection = function(jpc, params, originalEvent) { + params = params || {}; + // add to list of connections (by scope). + if (!jpc.suspendedEndpoint) + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + if (!params.doNotFireConnectionEvent && params.fireEvent !== false) { + _currentInstance.fire("jsPlumbConnection", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }, originalEvent); + } + // always inform the anchor manager + // except that if jpc has a suspended endpoint it's not true to say the + // connection is new; it has just (possibly) moved. the question is whether + // to make that call here or in the anchor manager. i think perhaps here. + _currentInstance.anchorManager.newConnection(jpc); + // force a paint + _draw(jpc.source); + }, + + _eventFireProxy = function(event, proxyEvent, obj) { + obj.bind(event, function(originalObject, originalEvent) { + _currentInstance.fire(proxyEvent, obj, originalEvent); + }); + }, + + /** + * for the given endpoint params, returns an appropriate parent element for the UI elements that will be added. + * this function is used by _newEndpoint (directly below), and also in the makeSource function in jsPlumb. + * + * the logic is to first look for a "container" member of params, and pass that back if found. otherwise we + * handoff to the 'getParent' function in the current library. + */ + _getParentFromParams = function(params) { + if (params.container) + return params.container; + else { + var tag = jsPlumb.CurrentLibrary.getTagName(params.source), + p = jsPlumb.CurrentLibrary.getParent(params.source); + if (tag && tag.toLowerCase() === "td") + return jsPlumb.CurrentLibrary.getParent(p); + else return p; + } + }, + + /** + factory method to prepare a new endpoint. this should always be used instead of creating Endpoints + manually, since this method attaches event listeners and an id. + */ + _newEndpoint = function(params) { + var endpointFunc = _currentInstance.Defaults.EndpointType || Endpoint; + params.parent = _getParentFromParams(params); + params["_jsPlumb"] = _currentInstance; + var ep = new endpointFunc(params); + ep.id = "ep_" + _idstamp(); + _eventFireProxy("click", "endpointClick", ep); + _eventFireProxy("dblclick", "endpointDblClick", ep); + _eventFireProxy("contextmenu", "contextmenu", ep); + return ep; + }, + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + _operation = function(elId, func, endpointFunc) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + if (endpointFunc) endpointFunc(endpoints[i]); + } + } + }, + /** + * perform an operation on all elements. + */ + _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }, + + /** + * helper to remove an element from the DOM. + */ + _removeElement = function(element, parent) { + if (element != null && element.parentNode != null) { + element.parentNode.removeChild(element); + } + }, + /** + * helper to remove a list of elements from the DOM. + */ + _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }, + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }, + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + _setVisible = function(el, state, alsoChangeEndpoints) { + state = state === "block"; + var endpointFunc = null; + if (alsoChangeEndpoints) { + if (state) endpointFunc = function(ep) { + ep.setVisible(true, true, true); + }; + else endpointFunc = function(ep) { + ep.setVisible(false, true, true); + }; + } + var id = _getAttribute(el, "id"); + _operation(id, function(jpc) { + if (state && alsoChangeEndpoints) { + // this test is necessary because this functionality is new, and i wanted to maintain backwards compatibility. + // this block will only set a connection to be visible if the other endpoint in the connection is also visible. + var oidx = jpc.sourceId === id ? 1 : 0; + if (jpc.endpoints[oidx].isVisible()) jpc.setVisible(true); + } + else // the default behaviour for show, and what always happens for hide, is to just set the visibility without getting clever. + jpc.setVisible(state); + }, endpointFunc); + }, + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? false : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }, + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + _toggleVisible = function(elId, changeEndpoints) { + var endpointFunc = null; + if (changeEndpoints) { + endpointFunc = function(ep) { + var state = ep.isVisible(); + ep.setVisible(!state); + }; + } + _operation(elId, function(jpc) { + var state = jpc.isVisible(); + jpc.setVisible(!state); + }, endpointFunc); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }, + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return offsets[elId]; + } + if (recalc || !offset) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + if (sizes[elId] == null) { + var s = _getElementObject(elId); + if (s != null) + sizes[elId] = _getSize(s); + } + } + + if(offsets[elId] && !offsets[elId].right) { + offsets[elId].right = offsets[elId].left + sizes[elId][0]; + offsets[elId].bottom = offsets[elId].top + sizes[elId][1]; + offsets[elId].width = sizes[elId][0]; + offsets[elId].height = sizes[elId][1]; + offsets[elId].centerx = offsets[elId].left + (offsets[elId].width / 2); + offsets[elId].centery = offsets[elId].top + (offsets[elId].height / 2); + } + return offsets[elId]; + }, + + // TODO comparison performance + _getCachedData = function(elId) { + var o = offsets[elId]; + if (!o) o = _updateOffset({elId:elId}); + return {o:o, s:sizes[elId]}; + }, + + /** + * gets an id for the given element, creating and setting one if + * necessary. the id is of the form + * + * jsPlumb__ + * + * where "index in instance" is a monotonically increasing integer that starts at 0, + * for each instance. this method is used not only to assign ids to elements that do not + * have them but also to connections and endpoints. + */ + _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else + id = "jsPlumb_" + _instanceIndex + "_" + _idstamp(); + _setAttribute(ele, "id", id); + } + return id; + }, + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'jsPlumb function failed : ' + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'wrapped function failed : ' + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = "_jsPlumb_connector"; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = "_jsPlumb_endpoint"; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = "_jsPlumb_overlay"; + + this.Anchors = {}; + + this.Connectors = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Endpoints = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Overlays = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + +// ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***************************************** + /* + * Function: bind + * Bind to an event on jsPlumb. + * + * Parameters: + * event - the event to bind. Available events on jsPlumb are: + * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback. + * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback. + * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback. + * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback. + * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback. + * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback. + * + * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event. + */ + + /* + * Function: clearListeners + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + +// *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *********************************************************** + + /* + Function: addClass + + Helper method to abstract out differences in setting css classes on the different renderer types. + */ + this.addClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.addClass(el, clazz); + }; + + /* + Function: removeClass + + Helper method to abstract out differences in setting css classes on the different renderer types. + */ + this.removeClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.removeClass(el, clazz); + }; + + /* + Function: hasClass + + Helper method to abstract out differences in testing for css classes on the different renderer types. + */ + this.hasClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.hasClass(el, clazz); + }; + + /* + Function: addEndpoint + + Adds an to a given element or elements. + + Parameters: + + el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + params - Object containing Endpoint constructor arguments. For more information, see . + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some + shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in + this object are anything that 'params' can contain. See . + + Returns: + The newly created , if el referred to a single element. Otherwise, an array of newly created s. + + See Also: + + */ + this.addEndpoint = function(el, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // YUI wrapper + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + var _el = _getElementObject(inputs[i]), id = _getId(_el); + p.source = _el; + _updateOffset({ elId : id }); + var e = _newEndpoint(p); + if (p.parentAnchor) e.parentAnchor = p.parentAnchor; + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + results.push(e); + } + + return results.length == 1 ? results[0] : results; + }; + + /* + Function: addEndpoints + Adds a list of s to a given element or elements. + + Parameters: + target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See 's constructor documentation. + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created s, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(el, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams); + if (e.constructor == Array) + Array.prototype.push.apply(results, e); + else results.push(e); + } + return results; + }; + + /* + Function: animate + This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating + the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // onComplete repaints, just to make sure everything looks good at the end of the animation. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /** + * checks for a listener for the given condition, executing it if found, passing in the given value. + * condition listeners would have been attached using "bind" (which is, you could argue, now overloaded, since + * firing click events etc is a bit different to what this does). i thought about adding a "bindCondition" + * or something, but decided against it, for the sake of simplicity. jsPlumb will never fire one of these + * condition events anyway. + */ + this.checkCondition = function(conditionName, value) { + var l = _currentInstance.getListener(conditionName); + var r = true; + if (l && l.length > 0) { + try { + for (var i = 0 ; i < l.length; i++) { + r = r && l[i](value); + } + } + catch (e) { + _log(_currentInstance, "cannot check condition [" + conditionName + "]" + e); + } + } + return r; + }; + + /* + Function: connect + Establishes a between two elements (or s, which are themselves registered to elements). + + Parameters: + params - Object containing constructor arguments for the Connection. See 's constructor documentation. + referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of + Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection. + + Returns: + The newly created . + */ + this.connect = function(params, referenceParams) { + // prepare a final set of parameters to create connection with + var _p = _prepareConnectionParams(params, referenceParams); + // TODO probably a nicer return value if the connection was not made. _prepareConnectionParams + // will return null (and log something) if either endpoint was full. what would be nicer is to + // create a dedicated 'error' object. + if (_p) { + // a connect call will delete its created endpoints on detach, unless otherwise specified. + // this is because the endpoints belong to this connection only, and are no use to + // anyone else, so they hang around like a bad smell. + if (_p.deleteEndpointsOnDetach == null) + _p.deleteEndpointsOnDetach = true; + + // create the connection. it is not yet registered + var jpc = _newConnection(_p); + // now add it the model, fire an event, and redraw + _finaliseConnection(jpc, _p); + return jpc; + } + }; + + /* + Function: deleteEndpoint + Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too) + + Parameters: + object - either an object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElements(endpoint.endpoint.getDisplayElements()); + _currentInstance.anchorManager.deleteEndpoint(endpoint); + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + delete endpoint; + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every , and their associated s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc, doFireEvent) { + // may have been given a connection, or in special cases, an object + var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + argIsConnection = jpc.constructor == connType, + params = argIsConnection ? { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + } : jpc; + + if (doFireEvent) _currentInstance.fire("jsPlumbConnectionDetached", params); + _currentInstance.anchorManager.connectionDetached(params); + }; + + /* + Function: detach + Detaches and then removes a . From 1.3.5 this method has been altered to remove support for + specifying Connections by various parameters; you can now pass in a Connection as the first argument and + an optional parameters object as a second argument. If you need the functionality this method provided + before 1.3.5 then you should use the getConnections method to get the list of Connections to detach, and + then iterate through them, calling this for each one. + + Parameters: + connection - the to detach + params - optional parameters to the detach call. valid values here are + fireEvent : defaults to false; indicates you want jsPlumb to fire a connection + detached event. The thinking behind this is that if you made a programmatic + call to detach an event, you probably don't need the callback. + forceDetach : defaults to false. allows you to override any beforeDetach listeners that may be registered. + + Returns: + true if successful, false if not. + */ + this.detach = function() { + + if (arguments.length == 0) return; + var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + firstArgIsConnection = arguments[0].constructor == connType, + params = arguments.length == 2 ? firstArgIsConnection ? (arguments[1] || {}) : arguments[0] : arguments[0], + fireEvent = (params.fireEvent !== false), + forceDetach = params.forceDetach, + connection = firstArgIsConnection ? arguments[0] : params.connection; + + if (connection) { + if (forceDetach || (connection.isDetachAllowed(connection) + && connection.endpoints[0].isDetachAllowed(connection) + && connection.endpoints[1].isDetachAllowed(connection))) { + if (forceDetach || _currentInstance.checkCondition("beforeDetach", connection)) + connection.endpoints[0].detach(connection, false, true, fireEvent); // TODO check this param iscorrect for endpoint's detach method + } + } + else { + var _p = jsPlumb.extend( {}, params); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1]), fireEvent); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source), + targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + if (_currentInstance.checkCondition("beforeDetach", jpc)) { + jpc.endpoints[0].detach(jpc, false, true, fireEvent); + } + } + }); + } + } + }; + + /* + Function: detachAllConnections + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + params - optional parameters. alowed values: + fireEvent : defaults to true, whether or not to fire the detach event. + + Returns: + void + */ + this.detachAllConnections = function(el, params) { + params = params || {}; + el = _getElementObject(el); + var id = _getAttribute(el, "id"), + endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + }; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Parameters: + params - optional params object containing: + fireEvent : whether or not to fire detach events. defaults to true. + + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function(params) { + params = params || {}; + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of y + our library's draggable method so that jsPlumb can setup the appropriate callbacks. Your + underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + // TODO it would be nice if this supported a selector string, instead of an id. + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method, + * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the + * default scope). If multiple scopes are passed in, the return value will be a map of { scope -> [ connection... ] }. + * + * Parameters + * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list + * of connections that are in the given scope. + * options - if the argument is a JS object, you can specify a finer-grained filter: + * + * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes. + * - *source* either a string representing an element id, or a selector. constrains the result to connections having this source. + * - *target* either a string representing an element id, or a selector. constrains the result to connections having this target. + * + */ + this.getConnections = function(options) { + if (!options) { + options = {}; + } else if (options.constructor == String) { + options = { "scope": options }; + } + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') + r.push(input); + else + r = input; + } + return r; + }, + scope = options.scope || _currentInstance.getDefaultScope(), + scopes = prepareList(scope), + sources = prepareList(options.source), + targets = prepareList(options.target), + filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }, + results = scopes.length > 1 ? {} : [], + _addOne = function(scope, obj) { + if (scopes.length > 1) { + var ss = results[scope]; + if (ss == null) { + ss = []; results[scope] = ss; + } + ss.push(obj); + } else results.push(obj); + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + _addOne(i, c); + } + } + } + return results; + }; + + /* + * Function: getAllConnections + * Gets all connections, as a map of { scope -> [ connection... ] }. + */ + this.getAllConnections = function() { + return connectionsByScope; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /** + * Function:getEndpoints + * Gets the list of Endpoints for a given selector, or element id. + * @param el + * @return + */ + this.getEndpoints = function(el) { + return endpointsByElement[_getId(el)]; + }; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + this.getOffset = function(id) { + var o = offsets[id]; + return _updateOffset({elId:id}); + }; + + this.getSelector = function(spec) { + return jsPlumb.CurrentLibrary.getSelector(spec); + }; + + this.getSize = function(id) { + var s = sizes[id]; + if (!s) _updateOffset({elId:id}); + return sizes[id]; + }; + + this.appendElement = _appendElement; + + var _hoverSuspended = false; + this.isHoverSuspended = function() { return _hoverSuspended; }; + this.setHoverSuspended = function(s) { _hoverSuspended = s; }; + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + changeEndpoints - whether not to also hide endpoints on the element. by default this is false. + + Returns: + void + */ + this.hide = function(el, changeEndpoints) { + _setVisible(el, "none", changeEndpoints); + }; + + // exposed for other objects to use to get a unique id. + this.idstamp = _idstamp; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + if (!initialized) { + _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run. + + var bindOne = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && _mouseEventsEnabled && renderMode == jsPlumb.CANVAS) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + var t = c[i].connector[event](e); + if (t) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i].endpoint[event](e)) return; + } + } + } + }); + }; + bindOne("click");bindOne("dblclick");bindOne("mousemove");bindOne("mousedown");bindOne("mouseup");bindOne("contextmenu"); + + initialized = true; + _currentInstance.fire("ready"); + } + }; + + this.log = log; + this.jsPlumbUIComponent = jsPlumbUIComponent; + this.EventGenerator = EventGenerator; + + /* + * Creates an anchor with the given params. + * + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function() { + if (arguments.length == 0) return null; + var specimen = arguments[0], elementId = arguments[1], jsPlumbInstance = arguments[2], newAnchor = null; + if (!jsPlumbInstance) + throw "NO JSPLUMB SET"; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; //TODO hazy here about whether it should be added or is already added somehow. + // is it the name of an anchor type? + else if (typeof specimen == "string") { + newAnchor = jsPlumb.Anchors[arguments[0]]({elementId:elementId, jsPlumbInstance:_currentInstance}); + } + // is it an array? it will be one of: + // an array of [name, params] - this defines a single anchor + // an array of arrays - this defines some dynamic anchors + // an array of numbers - this defines a single anchor. + else if (specimen.constructor == Array) { + if (specimen[0].constructor == Array || specimen[0].constructor == String) { + if (specimen.length == 2 && specimen[0].constructor == String && specimen[1].constructor == Object) { + var pp = jsPlumb.extend({elementId:elementId, jsPlumbInstance:_currentInstance}, specimen[1]); + newAnchor = jsPlumb.Anchors[specimen[0]](pp); + } + else + newAnchor = new DynamicAnchor(specimen, null, elementId); + } + else { + var anchorParams = { + x:specimen[0], y:specimen[1], + orientation : (specimen.length >= 4) ? [ specimen[2], specimen[3] ] : [0,0], + offsets : (specimen.length == 6) ? [ specimen[4], specimen[5] ] : [ 0, 0 ], + elementId:elementId + }; + newAnchor = new Anchor(anchorParams); + newAnchor.clone = function() { return new Anchor(anchorParams); }; + } + } + + if (!newAnchor.id) newAnchor.id = "anchor_" + _idstamp(); + return newAnchor; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types, elementId, jsPlumbInstance) { + var r = []; + for ( var i = 0; i < types.length; i++) { + if (typeof types[i] == "string") + r.push(jsPlumb.Anchors[types[i]]({elementId:elementId, jsPlumbInstance:jsPlumbInstance})); + else if (types[i].constructor == Array) + r.push(_currentInstance.makeAnchor(types[i], elementId, jsPlumbInstance)); + } + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /** + * Function: makeTarget + * Makes some DOM element a Connection target, allowing you to drag connections to it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a target. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * scope optional. scope for the drop zone. + * dropOptions optional. same stuff as you would pass to dropOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to true. whether or not to delete + * any Endpoints created by a connection to this target if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + var _targetEndpointDefinitions = {}; + var _setEndpointPaintStylesAndAnchor = function(ep, epIndex) { + ep.paintStyle = ep.paintStyle || + _currentInstance.Defaults.EndpointStyles[epIndex] || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyles[epIndex] || + jsPlumb.Defaults.EndpointStyle; + ep.hoverPaintStyle = ep.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyles[epIndex] || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyles[epIndex] || + jsPlumb.Defaults.EndpointHoverStyle; + + ep.anchor = ep.anchor || + _currentInstance.Defaults.Anchors[epIndex] || + _currentInstance.Defaults.Anchor || + jsPlumb.Defaults.Anchors[epIndex] || + jsPlumb.Defaults.Anchor; + + }; + this.makeTarget = function(el, params, referenceParams) { + + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + var jpcl = jsPlumb.CurrentLibrary, + targetScope = p.scope || _currentInstance.Defaults.Scope, + deleteEndpointsOnDetach = p.deleteEndpointsOnDetach || true, + _doOne = function(_el) { + + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el); + _targetEndpointDefinitions[elid] = p.endpoint; + + var dropOptions = jsPlumb.extend({}, p.dropOptions || {}), + _drop = function() { + _currentInstance.currentlyDragging = false; + var draggable = _getElementObject(jpcl.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + // restore the original scope if necessary (issue 57) + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id], + source = jpc.endpoints[0], + _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : {}; + + // use defaults for endpoint style, if not present..this either uses EndpointStyle, or EndpointStyles[1], + // if it is present, since this is a target endpoint. + // TODO - this should be a helper method. makeTarget should use it too. give it an endpoint + _setEndpointPaintStylesAndAnchor(_endpoint, 1); + + // unlock the source anchor to allow it to refresh its position if necessary + source.anchor.locked = false; + + if (scope) jpcl.setDragScope(draggable, scope); + + // check if drop is allowed here. + var _continue = jpc.isDropAllowed(jpc.sourceId, _getId(_el), jpc.scope); + + // regardless of whether the connection is ok, reconfigure the existing connection to + // point at the current info. we need this to be correct for the detach event that will follow. + // clear the source endpoint from the list to detach. we will detach this connection at this + // point, but we want to keep the source endpoint. the target is a floating endpoint and should + // be removed. TODO need to figure out whether this code can result in endpoints kicking around + // when they shouldnt be. like is this a full detach of a connection? can it be? + if (jpc.endpointsToDeleteOnDetach) { + if (source === jpc.endpointsToDeleteOnDetach[0]) + jpc.endpointsToDeleteOnDetach[0] = null; + else if (source === jpc.endpointsToDeleteOnDetach[1]) + jpc.endpointsToDeleteOnDetach[1] = null; + } + // reinstate any suspended endpoint; this just puts the connection back into + // a state in which it will report sensible values if someone asks it about + // its target. we're going to throw this connection away shortly so it doesnt matter + // if we manipulate it a bit. + if (jpc.suspendedEndpoint) { + jpc.targetId = jpc.suspendedEndpoint.elementId; + jpc.target = jpcl.getElementObject(jpc.suspendedEndpoint.elementId); + jpc.endpoints[1] = jpc.suspendedEndpoint; + } + + if (_continue) { + + // detach this connection from the source. + source.detach(jpc, false, true, true);//source.endpointWillMoveAfterConnection); + + // make a new Endpoint for the target + var newEndpoint = _currentInstance.addEndpoint(_el, _endpoint); + + // if the anchor has a 'positionFinder' set, then delegate to that function to find + // out where to locate the anchor. + if (newEndpoint.anchor.positionFinder != null) { + var dropPosition = jpcl.getUIPosition(arguments), + elPosition = jpcl.getOffset(_el), + elSize = jpcl.getSize(_el), + ap = newEndpoint.anchor.positionFinder(dropPosition, elPosition, elSize, newEndpoint.anchor); + newEndpoint.anchor.x = ap[0]; + newEndpoint.anchor.y = ap[1]; + // now figure an orientation for it..kind of hard to know what to do actually. probably the best thing i can do is to + // support specifying an orientation in the anchor's spec. if one is not supplied then i will make the orientation + // be what will cause the most natural link to the source: it will be pointing at the source, but it needs to be + // specified in one axis only, and so how to make that choice? i think i will use whichever axis is the one in which + // the target is furthest away from the source. + } + var c = _currentInstance.connect({ + source:source, + target:newEndpoint, + scope:scope, + previousConnection:jpc, + container:jpc.parent, + // 'endpointWillMoveAfterConnection' is set by the makeSource function, and it indicates that the + // given endpoint will actually transfer from the element it is currently attached to to some other + // element after a connection has been established. in that case, we do not want to fire the + // connection event, since it will have the wrong data in it; makeSource will do it for us. + // this is controlled by the 'parent' parameter on a makeSource call. + doNotFireConnectionEvent:source.endpointWillMoveAfterConnection + }); + if (deleteEndpointsOnDetach) + c.endpointsToDeleteOnDetach = [ source, newEndpoint ]; + } + // if not allowed to drop... + else { + // TODO this code is identical (pretty much) to what happens when a connection + // dragged from a normal endpoint is in this situation. refactor. + // is this an existing connection, and will we reattach? + if (jpc.suspendedEndpoint) { + if (source.isReattach) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + _currentInstance.repaint(source.elementId); + } + else + source.detach(jpc, false, true, true); // otherwise, detach the connection and tell everyone about it. + } + + } + }; + + var dropEvent = jpcl.dragEvents['drop']; + dropOptions["scope"] = dropOptions["scope"] || targetScope; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop); + + jpcl.initDroppable(_el, dropOptions, true); + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements drop targets. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeTargets = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeTarget(els[i], params, referenceParams); + } + }; + + /** + * Function: makeSource + * Makes some DOM element a Connection source, allowing you to drag connections from it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a source. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * parent optional. the element to add Endpoints to when a Connection is established. if you omit this, + * Endpoints will be added to 'el'. + * scope optional. scope for the connections dragged from this element. + * dragOptions optional. same stuff as you would pass to dragOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to false. whether or not to delete + * any Endpoints created by a connection from this source if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + var _sourceEndpointDefinitions = {}; + this.makeSource = function(el, params, referenceParams) { + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + var jpcl = jsPlumb.CurrentLibrary, + _doOne = function(_el) { + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el); + _sourceEndpointDefinitions[elid] = p.endpoint || {}; + var stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"], + dragOptions = jsPlumb.extend({ }, p.dragOptions || _sourceEndpointDefinitions[elid].dragOptions || {}), + existingDrag = dragOptions.drag, + existingStop = dragOptions.stop, + ep = null, + endpointAddedButNoDragYet = false; + + // set scope if its not set in dragOptions but was passed in in params + dragOptions["scope"] = dragOptions["scope"] || p.scope; + + // make sure this method honours whatever defaults have been set for Endpoint. + _sourceEndpointDefinitions[elid].endpoint = _sourceEndpointDefinitions[elid].endpoint || _currentInstance.Defaults.Endpoints[0] || _currentInstance.Defaults.Endpoint; + + // set endpoint paint styles and anchor, using either styles that are set or defaults. + _setEndpointPaintStylesAndAnchor(_sourceEndpointDefinitions[elid], 0); + + + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + if (existingDrag) existingDrag.apply(this, arguments); + endpointAddedButNoDragYet = false; + }); + + dragOptions[stopEvent] = function() { + + if (existingStop) existingStop.apply(this, arguments); + + //_currentlyDown = false; + _currentInstance.currentlyDragging = false; + + if (ep.connections.length == 0) + _currentInstance.deleteEndpoint(ep); + else { + + jpcl.unbind(ep.canvas, "mousedown"); + + // reset the anchor to the anchor that was initially provided. the one we were using to drag + // the connection was just a placeholder that was located at the place the user pressed the + // mouse button to initiate the drag. + var anchorDef = _sourceEndpointDefinitions[elid].anchor || _currentInstance.Defaults.Anchor; + ep.anchor = _currentInstance.makeAnchor(anchorDef, elid, _currentInstance); + + if (p.parent) { + var parent = jpcl.getElementObject(p.parent); + if (parent) { + var currentId = ep.elementId; + ep.setElement(parent); + ep.endpointWillMoveAfterConnection = false; + _currentInstance.anchorManager.rehomeEndpoint(currentId, parent); + ep.connections[0].previousConnection = null; + _finaliseConnection(ep.connections[0]); + } + } + _currentInstance.repaint(elid); + } + }; + // when the user presses the mouse, add an Endpoint + var mouseDownListener = function(e) { + // make sure we have the latest offset for this div + var myOffsetInfo = _updateOffset({elId:elid}); + + var x = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width, + y = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height, + parentX = x, + parentY = y; + + + // if there is a parent, the endpoint will actually be added to it now, rather than the div + // that was the source. in that case, we have to adjust the anchor position so it refers to + // the parent. + if (p.parent) { + var pEl = jsPlumb.CurrentLibrary.getElementObject(p.parent), + pId = _getId(pEl); + myOffsetInfo = _updateOffset({elId:pId}); + parentX = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width, + parentY = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height; + } + + // we need to override the anchor in here, and force 'isSource', but we don't want to mess with + // the params passed in, because after a connection is established we're going to reset the endpoint + // to have the anchor we were given. + var tempEndpointParams = {}; + jsPlumb.extend(tempEndpointParams, _sourceEndpointDefinitions[elid]); + tempEndpointParams.isSource = true; + tempEndpointParams.anchor = [x,y,0,0]; + tempEndpointParams.parentAnchor = [ parentX, parentY, 0, 0 ]; + tempEndpointParams.dragOptions = dragOptions; + // if a parent was given we need to turn that into a "container" argument. this is, by default, + // the parent of the element we will move to, so parent of p.parent in this case. however, if + // the user has specified a 'container' on the endpoint definition or on + // the defaults, we should use that. + if (p.parent) { + var potentialParent = tempEndpointParams.container || _currentInstance.Defaults.Container; + if (potentialParent) + tempEndpointParams.container = potentialParent; + else + tempEndpointParams.container = jsPlumb.CurrentLibrary.getParent(p.parent); + } + + ep = _currentInstance.addEndpoint(elid, tempEndpointParams); + + endpointAddedButNoDragYet = true; + // we set this to prevent connections from firing attach events before this function has had a chance + // to move the endpoint. + ep.endpointWillMoveAfterConnection = p.parent != null; + ep.endpointWillMoveTo = p.parent ? jpcl.getElementObject(p.parent) : null; + + var _delTempEndpoint = function() { + // this mouseup event is fired only if no dragging occurred, by jquery and yui, but for mootools + // it is fired even if dragging has occurred, in which case we would blow away a perfectly + // legitimate endpoint, were it not for this check. the flag is set after adding an + // endpoint and cleared in a drag listener we set in the dragOptions above. + if(endpointAddedButNoDragYet) { + _currentInstance.deleteEndpoint(ep); + } + }; + + _currentInstance.registerListener(ep.canvas, "mouseup", _delTempEndpoint); + _currentInstance.registerListener(_el, "mouseup", _delTempEndpoint); + + // and then trigger its mousedown event, which will kick off a drag, which will start dragging + // a new connection from this endpoint. + jpcl.trigger(ep.canvas, "mousedown", e); + }; + + // register this on jsPlumb so that it can be cleared by a reset. + _currentInstance.registerListener(_el, "mousedown", mouseDownListener); + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements connection sources. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeSources = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeSource(els[i], params, referenceParams); + } + }; + + /* + Function: ready + Helper method to bind a function to jsPlumb's ready event. + */ + this.ready = function(fn) { + _currentInstance.bind("ready", fn); + }, + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"), + ebe = endpointsByElement[elId]; + if (ebe) { + for ( var i = 0; i < ebe.length; i++) + _currentInstance.deleteEndpoint(ebe[i]); + } + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + var _registeredListeners = {}, + _unbindRegisteredListeners = function() { + for (var i in _registeredListeners) { + for (var j = 0; j < _registeredListeners[i].length; j++) { + var info = _registeredListeners[i][j]; + jsPlumb.CurrentLibrary.unbind(info.el, info.event, info.listener); + } + } + _registeredListeners = {}; + }; + + // internal register listener method. gives us a hook to clean things up + // with if the user calls jsPlumb.reset. + this.registerListener = function(el, type, listener) { + jsPlumb.CurrentLibrary.bind(el, type, listener); + _addToList(_registeredListeners, type, {el:el, event:type, listener:listener}); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + _currentInstance.deleteEveryEndpoint(); + _currentInstance.clearListeners(); + _unbindRegisteredListeners(); + _currentInstance.anchorManager.reset(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + + Parameters: + value - whether or not to automatically repaint when the window is resized. + + Returns: void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction + * Sets the function to fire when the window size has changed and a repaint was fired. + * + * Parameters: + * f - Function to execute. + * + * Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: setMouseEventsEnabled + * Sets whether or not mouse events are enabled. Default is true. + * + * Parameters: + * enabled - whether or not mouse events should be enabled. + * + * Returns: + * void + */ + this.setMouseEventsEnabled = function(enabled) { + _mouseEventsEnabled = enabled; + }; + + /* + * Function: setSuspendDrawing + * Suspends drawing operations. This can be used when you have a lot of connections to make or endpoints to register; + * it will save you a lot of time. + */ + this.setSuspendDrawing = _setSuspendDrawing; + + /* + * Constant for use with the setRenderMode method + */ + this.CANVAS = "canvas"; + + /* + * Constant for use with the setRenderMode method + */ + this.SVG = "svg"; + + this.VML = "vml"; + + /* + * Function: setRenderMode + * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that + * what you asked for is not supported (and that VML is). If you asked for VML but the browser does + * not support it, jsPlumb uses SVG. + * + * Returns: + * the render mode that jsPlumb set, which of course may be different from that requested. + */ + this.setRenderMode = function(mode) { + if (mode) + mode = mode.toLowerCase(); + else + return; + if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); + // now test we actually have the capability to do this. + if (mode === jsPlumb.CANVAS && canvasAvailable) + renderMode = jsPlumb.CANVAS; + else if (mode === jsPlumb.SVG && svgAvailable) + renderMode = jsPlumb.SVG; + else if (vmlAvailable) + renderMode = jsPlumb.VML; + + return renderMode; + }; + + this.getRenderMode = function() { return renderMode; }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * changeEndpoints - whether or not to also change the visible state of the endpoints on the element. this also has a bearing on + * other connections on those endpoints: if their other endpoint is also visible, the connections are made visible. + * + * Returns: + * void + */ + this.show = function(el, changeEndpoints) { + _setVisible(el, "block", changeEndpoints); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId, + makeAnchor:self.makeAnchor, + makeDynamicAnchor:self.makeDynamicAnchor + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * changeEndpoints - whether or not to also toggle the endpoints on the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload + * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element. + * + * Returns: + * void + */ + this.unload = function() { + // this used to do something, but it turns out that what it did was nothing. + // now it exists only for backwards compatibility. + }; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + var adjustForParentOffsetAndScroll = function(xy, el) { + + var offsetParent = null, result = xy; + if (el.tagName.toLowerCase() === "svg" && el.parentNode) { + offsetParent = el.parentNode; + } + else if (el.offsetParent) { + offsetParent = el.offsetParent; + } + if (offsetParent != null) { + var po = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(offsetParent), + so = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : {left:offsetParent.scrollLeft, top:offsetParent.scrollTop}; + + + // i thought it might be cool to do this: + // lastReturnValue[0] = lastReturnValue[0] - offsetParent.offsetLeft + offsetParent.scrollLeft; + // lastReturnValue[1] = lastReturnValue[1] - offsetParent.offsetTop + offsetParent.scrollTop; + // but i think it ignores margins. my reasoning was that it's quicker to not hand off to some underlying + // library. + + result[0] = xy[0] - po.left + so.left; + result[1] = xy[1] - po.top + so.top; + } + + return result; + + }; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + this.elementId = params.elementId; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + + if (timestamp && timestamp === self.timestamp) + return lastReturnValue; + + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + + // adjust loc if there is an offsetParent + lastReturnValue = adjustForParentOffsetAndScroll(lastReturnValue, element.canvas); + + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function(_endpoint) { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + * + * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference, + // the canvas this refers to. + refCanvas = params.referenceCanvas, + size = _getSize(_getElementObject(refCanvas)), + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + xDir = 0, yDir = 0, + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + orientation = null, + _lastResult = null; + + // set these to 0 each; they are used by certain types of connectors in the loopback case, + // when the connector is trying to clear the element it is on. but for floating anchor it's not + // very important. + this.x = 0; this.y = 0; + + this.isFloating = true; + + this.compute = function(params) { + var xy = params.xy, element = params.element, + result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + + // adjust loc if there is an offsetParent + result = adjustForParentOffsetAndScroll(result, element.canvas); + + _lastResult = result; + return result; + }; + + this.getOrientation = function(_endpoint) { + if (orientation) return orientation; + else { + var o = ref.getOrientation(_endpoint); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchor is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector, elementId) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = [], self = this, + _convert = function(anchor) { + return anchor.constructor == Anchor ? anchor: _currentInstance.makeAnchor(anchor, elementId, _currentInstance); + }; + for (var i = 0; i < anchors.length; i++) + _anchors[i] = _convert(anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + this.locked = false; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null, + _curIndex = _anchors.length > 0 ? 0 : -1, + self = this, + + // helper method to calculate the distance between the centers of the two elements. + _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }, + + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + self.x = _curAnchor.x; + self.y = _curAnchor.y; + + return _curAnchor.compute(params); + }; + + this.getCurrentLocation = function() { + return _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + }; + + this.getOrientation = function(_endpoint) { return _curAnchor != null ? _curAnchor.getOrientation(_endpoint) : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + manages anchors for all elements. + */ + // "continuous" anchors: anchors that pick their location based on how many connections the given element has. + // this requires looking at a lot more elements than normal - anything that has had a Continuous anchor applied has + // to be recalculated. so this manager is used as a reference point. the first time, with a new timestamp, that + // a continuous anchor is asked to compute, it calls this guy. or maybe, even, this guy gets called somewhere else + // and compute only ever returns pre-computed values. either way, this is the central point, and we want it to + // be called as few times as possible. + var continuousAnchors = {}, + continuousAnchorLocations = {}, + continuousAnchorOrientations = {}, + Orientation = { HORIZONTAL : "horizontal", VERTICAL : "vertical", DIAGONAL : "diagonal", IDENTITY:"identity" }, + + // TODO this functions uses a crude method of determining orientation between two elements. + // 'diagonal' should be chosen when the angle of the line between the two centers is around + // one of 45, 135, 225 and 315 degrees. maybe +- 15 degrees. + calculateOrientation = function(sourceId, targetId, sd, td) { + + if (sourceId === targetId) return { + orientation:Orientation.IDENTITY, + a:["top", "top"] + }; + + var theta = Math.atan2((td.centery - sd.centery) , (td.centerx - sd.centerx)), + theta2 = Math.atan2((sd.centery - td.centery) , (sd.centerx - td.centerx)), + h = ((sd.left <= td.left && sd.right >= td.left) || (sd.left <= td.right && sd.right >= td.right) || + (sd.left <= td.left && sd.right >= td.right) || (td.left <= sd.left && td.right >= sd.right)), + v = ((sd.top <= td.top && sd.bottom >= td.top) || (sd.top <= td.bottom && sd.bottom >= td.bottom) || + (sd.top <= td.top && sd.bottom >= td.bottom) || (td.top <= sd.top && td.bottom >= sd.bottom)); + + if (! (h || v)) { + var a = null, rls = false, rrs = false, sortValue = null; + if (td.left > sd.left && td.top > sd.top) + a = ["right", "top"]; + else if (td.left > sd.left && sd.top > td.top) + a = [ "top", "left"]; + else if (td.left < sd.left && td.top < sd.top) + a = [ "top", "right"]; + else if (td.left < sd.left && td.top > sd.top) + a = ["left", "top" ]; + + return { orientation:Orientation.DIAGONAL, a:a, theta:theta, theta2:theta2 }; + } + else if (h) return { + orientation:Orientation.HORIZONTAL, + a:sd.top < td.top ? ["bottom", "top"] : ["top", "bottom"], + theta:theta, theta2:theta2 + } + else return { + orientation:Orientation.VERTICAL, + a:sd.left < td.left ? ["right", "left"] : ["left", "right"], + theta:theta, theta2:theta2 + } + }, + placeAnchorsOnLine = function(desc, elementDimensions, elementPosition, + connections, horizontal, otherMultiplier, reverse) { + var a = [], step = elementDimensions[horizontal ? 0 : 1] / (connections.length + 1); + + for (var i = 0; i < connections.length; i++) { + var val = (i + 1) * step, other = otherMultiplier * elementDimensions[horizontal ? 1 : 0]; + if (reverse) + val = elementDimensions[horizontal ? 0 : 1] - val; + + var dx = (horizontal ? val : other), x = elementPosition[0] + dx, xp = dx / elementDimensions[0], + dy = (horizontal ? other : val), y = elementPosition[1] + dy, yp = dy / elementDimensions[1]; + + a.push([ x, y, xp, yp, connections[i][1], connections[i][2] ]); + } + + return a; + }, + standardEdgeSort = function(a, b) { return a[0] > b[0] ? 1 : -1 }, + currySort = function(reverseAngles) { + return function(a,b) { + var r = true; + if (reverseAngles) { + if (a[0][0] < b[0][0]) + r = true; + else + r = a[0][1] > b[0][1]; + } + else { + if (a[0][0] > b[0][0]) + r= true; + else + r =a[0][1] > b[0][1]; + } + return r === false ? -1 : 1; + }; + }, + leftSort = function(a,b) { + // first get adjusted values + var p1 = a[0][0] < 0 ? -Math.PI - a[0][0] : Math.PI - a[0][0], + p2 = b[0][0] < 0 ? -Math.PI - b[0][0] : Math.PI - b[0][0]; + if (p1 > p2) return 1; + else return a[0][1] > b[0][1] ? 1 : -1; + }, + edgeSortFunctions = { + "top":standardEdgeSort, + "right":currySort(true), + "bottom":currySort(true), + "left":leftSort + }, + _sortHelper = function(_array, _fn) { + return _array.sort(_fn); + }, + placeAnchors = function(elementId, _anchorLists) { + var sS = sizes[elementId], sO = offsets[elementId], + placeSomeAnchors = function(desc, elementDimensions, elementPosition, unsortedConnections, isHorizontal, otherMultiplier) { + if (unsortedConnections.length > 0) { + var sc = _sortHelper(unsortedConnections, edgeSortFunctions[desc]), // puts them in order based on the target element's pos on screen + //sc = unsortedConnections.sort(edgeSortFunctions[desc]), // puts them in order based on the target element's pos on screen + reverse = desc === "right" || desc === "top", + anchors = placeAnchorsOnLine(desc, elementDimensions, + elementPosition, sc, + isHorizontal, otherMultiplier, reverse ); + + // takes a computed anchor position and adjusts it for parent offset and scroll, then stores it. + var _setAnchorLocation = function(endpoint, anchorPos) { + var a = adjustForParentOffsetAndScroll([anchorPos[0], anchorPos[1]], endpoint.canvas); + continuousAnchorLocations[endpoint.id] = [ a[0], a[1], anchorPos[2], anchorPos[3] ]; + }; + + for (var i = 0; i < anchors.length; i++) { + var c = anchors[i][4], weAreSource = c.endpoints[0].elementId === elementId, weAreTarget = c.endpoints[1].elementId === elementId; + if (weAreSource) + _setAnchorLocation(c.endpoints[0], anchors[i]); + else if (weAreTarget) + _setAnchorLocation(c.endpoints[1], anchors[i]); + } + } + }; + + placeSomeAnchors("bottom", sS, [sO.left,sO.top], _anchorLists.bottom, true, 1); + placeSomeAnchors("top", sS, [sO.left,sO.top], _anchorLists.top, true, 0); + placeSomeAnchors("left", sS, [sO.left,sO.top], _anchorLists.left, false, 0); + placeSomeAnchors("right", sS, [sO.left,sO.top], _anchorLists.right, false, 1); + }, + AnchorManager = function() { + var _amEndpoints = {}, + endpointConnectionsByElementId = {}, + continuousAnchorConnectionsByElementId = {}, + continuousAnchorEndpoints = [], + self = this, + anchorLists = {}; + + this.reset = function() { + endpointConnectionsByElementId = {}; + continuousAnchorConnectionsByElementId = {}; + continuousAnchorEndpoints = []; + anchorLists = {}; + }; + + this.newConnection = function(conn) { + var sourceId = conn.sourceId, targetId = conn.targetId, + ep = conn.endpoints, + doRegisterTarget = true, + registerConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + if (otherAnchor.constructor == DynamicAnchor || otherAnchor.constructor == Anchor) { + _addToList(endpointConnectionsByElementId, elId, [c, otherEndpoint, otherAnchor.constructor == DynamicAnchor]); + } + else { + // continuous. if they are the same element, just assign the same anchor + // to both. + if (sourceId == targetId) { + // remove the target endpoint's canvas. we dont need it. + jsPlumb.CurrentLibrary.removeElement(ep[1].canvas); + doRegisterTarget = false; + } + _addToList(continuousAnchorConnectionsByElementId, elId, c); + } + }; + registerConnection(0, ep[0], ep[0].anchor, targetId, conn); + if (doRegisterTarget) + registerConnection(1, ep[1], ep[1].anchor, sourceId, conn); + }; + this.connectionDetached = function(connInfo) { + var sourceId = connInfo.sourceId, + targetId = connInfo.targetId, + ep = connInfo.connection.endpoints, + removeConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + if (otherAnchor.constructor == FloatingAnchor) { + // no-op + } + else if (otherAnchor.constructor == DynamicAnchor || otherAnchor.constructor == Anchor) { + var _conns = endpointConnectionsByElementId[elId]; + if (_conns) { + _removeWithFunction(_conns, function(e) { return e[0].id == c.id; }); + } + } + else // continuous. + _removeWithFunction(continuousAnchorConnectionsByElementId[elId], function(_c) { + return _c.id == c.id; + }); + }; + + removeConnection(1, ep[1], ep[1].anchor, sourceId, connInfo.connection); + removeConnection(0, ep[0], ep[0].anchor, targetId, connInfo.connection); + + // remove from anchorLists + var sEl = connInfo.connection.sourceId, + tEl = connInfo.connection.targetId, + sE = connInfo.connection.endpoints[0].id, + tE = connInfo.connection.endpoints[1].id, + _remove = function(list, eId) { + if (list) { // transient anchors dont get entries in this list. + var f = function(e) { return e[4] == eId; }; + _removeWithFunction(list["top"], f); + _removeWithFunction(list["left"], f); + _removeWithFunction(list["bottom"], f); + _removeWithFunction(list["right"], f); + } + }; + + _remove(anchorLists[sEl], sE); + _remove(anchorLists[tEl], tE); + self.redraw(sEl); + self.redraw(tEl); + }; + this.add = function(endpoint, elementId) { + _addToList(_amEndpoints, elementId, endpoint); + }; + this.get = function(elementId) { + return { + "standard":endpointConnectionsByElementId[elementId] || [], + "continuous":continuousAnchorConnectionsByElementId[elementId] || [], + "endpoints":_amEndpoints[elementId], + "continuousAnchorEndpoints":continuousAnchorEndpoints + }; + }; + this.deleteEndpoint = function(endpoint) { + var cIdx = _findIndex(continuousAnchorEndpoints, endpoint); + if (cIdx > -1) + continuousAnchorEndpoints.splice(cIdx, 1); + else + _removeWithFunction(_amEndpoints[endpoint.elementId], function(e) { + return e.id == endpoint.id; + }); + }; + this.clearFor = function(elementId) { + delete _amEndpoints[elementId]; + _amEndpoints[elementId] = []; + }; + // updates the given anchor list by either updating an existing anchor's info, or adding it. this function + // also removes the anchor from its previous list, if the edge it is on has changed. + // all connections found along the way (those that are connected to one of the faces this function + // operates on) are added to the connsToPaint list, as are their endpoints. in this way we know to repaint + // them wthout having to calculate anything else about them. + var _updateAnchorList = function(lists, theta, order, conn, aBoolean, otherElId, idx, reverse, edgeId, elId, connsToPaint, endpointsToPaint) { + // first try to find the exact match, but keep track of the first index of a matching element id along the way.s + var exactIdx = -1, + firstMatchingElIdx = -1, + endpoint = conn.endpoints[idx], + endpointId = endpoint.id, + oIdx = [1,0][idx], + values = [ [ theta, order ], conn, aBoolean, otherElId, endpointId ], + listToAddTo = lists[edgeId], + listToRemoveFrom = endpoint._continuousAnchorEdge ? lists[endpoint._continuousAnchorEdge] : null; + + if (listToRemoveFrom) { + var rIdx = _findWithFunction(listToRemoveFrom, function(e) { return e[4] == endpointId }); + if (rIdx != -1) { + listToRemoveFrom.splice(rIdx, 1); + // get all connections from this list + for (var i = 0; i < listToRemoveFrom.length; i++) { + _addWithFunction(connsToPaint, listToRemoveFrom[i][1], function(c) { return c.id == listToRemoveFrom[i][1].id }); + _addWithFunction(endpointsToPaint, listToRemoveFrom[i][1].endpoints[idx], function(e) { return e.id == listToRemoveFrom[i][1].endpoints[idx].id }); + } + } + } + + for (var i = 0; i < listToAddTo.length; i++) { + if (idx == 1 && listToAddTo[i][3] === otherElId && firstMatchingElIdx == -1) + firstMatchingElIdx = i; + _addWithFunction(connsToPaint, listToAddTo[i][1], function(c) { return c.id == listToAddTo[i][1].id }); + _addWithFunction(endpointsToPaint, listToAddTo[i][1].endpoints[idx], function(e) { return e.id == listToAddTo[i][1].endpoints[idx].id }); + } + if (exactIdx != -1) { + listToAddTo[exactIdx] = values; + } + else { + var insertIdx = reverse ? firstMatchingElIdx != -1 ? firstMatchingElIdx : 0 : listToAddTo.length; // of course we will get this from having looked through the array shortly. + listToAddTo.splice(insertIdx, 0, values); + } + + // store this for next time. + endpoint._continuousAnchorEdge = edgeId; + }; + this.redraw = function(elementId, ui, timestamp) { + // get all the endpoints for this element + var ep = _amEndpoints[elementId] || [], + endpointConnections = endpointConnectionsByElementId[elementId] || [], + continuousAnchorConnections = continuousAnchorConnectionsByElementId[elementId] || [], + connectionsToPaint = [], + endpointsToPaint = [], + anchorsToUpdate = []; + + timestamp = timestamp || _timestamp(); + + _updateOffset( { elId : elementId, offset : ui, recalc : false, timestamp : timestamp }); + // valid for one paint cycle. + var myOffset = offsets[elementId], + myWH = sizes[elementId], + orientationCache = {}; + + // actually, first we should compute the orientation of this element to all other elements to which + // this element is connected with a continuous anchor (whether both ends of the connection have + // a continuous anchor or just one) + for (var i = 0; i < continuousAnchorConnections.length; i++) { + var conn = continuousAnchorConnections[i], + sourceId = conn.sourceId, + targetId = conn.targetId, + oKey = sourceId + "_" + targetId, + oKey2 = targetId + "_" + sourceId, + o = orientationCache[oKey], + td = _getCachedData(targetId), + sd = _getCachedData(sourceId), + oIdx = conn.sourceId == elementId ? 1 : 0; + + if (!anchorLists[sourceId]) anchorLists[sourceId] = { top:[], right:[], bottom:[], left:[] }; + if (!anchorLists[targetId]) anchorLists[targetId] = { top:[], right:[], bottom:[], left:[] }; + + if (targetId == sourceId) { + // here we may want to improve this by somehow determining the face we'd like + // to put the connector on. ideally, when drawing, the face should be calculated + // by determining which face is closest to the point at which the mouse button + // was released. for now, we're putting it on the top face. + _updateAnchorList(anchorLists[sourceId], -Math.PI / 2, 0, conn, false, targetId, 0, false, "top", sourceId, connectionsToPaint, endpointsToPaint) + } + else { + if (!o) { + o = calculateOrientation(sourceId, targetId, sd.o, td.o); + orientationCache[oKey] = o; + // this would be a performance enhancement, but the computed angles need to be clamped to + //the (-PI/2 -> PI/2) range in order for the sorting to work properly. + /* orientationCache[oKey2] = { + orientation:o.orientation, + a:[o.a[1], o.a[0]], + theta:o.theta + Math.PI, + theta2:o.theta2 + Math.PI + };*/ + } + _updateAnchorList(anchorLists[sourceId], o.theta, 0, conn, false, targetId, 0, false, o.a[0], sourceId, connectionsToPaint, endpointsToPaint); + _updateAnchorList(anchorLists[targetId], o.theta2, -1, conn, true, sourceId, 1, true, o.a[1], targetId, connectionsToPaint, endpointsToPaint); + } + + _addWithFunction(anchorsToUpdate, sourceId, function(a) { return a === sourceId; }); + _addWithFunction(anchorsToUpdate, targetId, function(a) { return a === targetId; }); + _addWithFunction(connectionsToPaint, conn, function(c) { return c.id == conn.id; }); + _addWithFunction(endpointsToPaint, conn.endpoints[oIdx], function(e) { return e.id == conn.endpoints[oIdx].id; }); + } + + // now place all the continuous anchors we need to; + for (var i = 0; i < anchorsToUpdate.length; i++) { + placeAnchors(anchorsToUpdate[i], anchorLists[anchorsToUpdate[i]]); + } + + // now that continuous anchors have been placed, paint all the endpoints for this element + // TODO performance: add the endpoint ids to a temp array, and then when iterating in the next + // loop, check that we didn't just paint that endpoint. we can probably shave off a few more milliseconds this way. + for (var i = 0; i < ep.length; i++) { + ep[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + // ... and any other endpoints we came across as a result of the continuous anchors. + for (var i = 0; i < endpointsToPaint.length; i++) { + endpointsToPaint[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + + + // paint all the standard and "dynamic connections", which are connections whose other anchor is + // static and therefore does need to be recomputed; we make sure that happens only one time. + for (var i = 0; i < endpointConnections.length; i++) { + var otherEndpoint = endpointConnections[i][1]; + if (otherEndpoint.anchor.constructor == DynamicAnchor) { + otherEndpoint.paint({ elementWithPrecedence:elementId }); + _addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; }); + // all the connections for the other endpoint now need to be repainted + for (var k = 0; k < otherEndpoint.connections.length; k++) { + if (otherEndpoint.connections[k] !== endpointConnections[i][0]) + _addWithFunction(connectionsToPaint, otherEndpoint.connections[k], function(c) { return c.id == otherEndpoint.connections[k].id; }); + } + } else if (otherEndpoint.anchor.constructor == Anchor) { + _addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; }); + } + } + // paint current floating connection for this element, if there is one. + var fc = floatingConnections[elementId]; + if (fc) + fc.paint({timestamp:timestamp, recalc:false, elId:elementId}); + + // paint all the connections + for (var i = 0; i < connectionsToPaint.length; i++) { + connectionsToPaint[i].paint({elId:elementId, timestamp:timestamp, recalc:false}); + } + }; + this.rehomeEndpoint = function(currentId, element) { + var eps = _amEndpoints[currentId] || [], //, + elementId = _currentInstance.getId(element); + for (var i = 0; i < eps.length; i++) { + self.add(eps[i], elementId); + } + eps.splice(0, eps.length); + }; + }; + _currentInstance.anchorManager = new AnchorManager(); + _currentInstance.continuousAnchorFactory = { + get:function(params) { + var existing = continuousAnchors[params.elementId]; + if (!existing) { + existing = { + type:"Continuous", + compute : function(params) { + return continuousAnchorLocations[params.element.id] || [0,0]; + }, + getCurrentLocation : function(endpoint) { + return continuousAnchorLocations[endpoint.id] || [0,0]; + }, + getOrientation : function(endpoint) { + return continuousAnchorOrientations[endpoint.id] || [0,0]; + }, + isDynamic : true, + isContinuous : true + }; + continuousAnchors[params.elementId] = existing; + } + return existing; + } + }; + + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * container - optional id or selector instructing jsPlumb where to attach all the elements it creates for this connection. you should read the documentation for a full discussion of this. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * overlays - Optional array of Overlay definitions to appear on this Connection. + * drawEndpoints - if false, instructs jsPlumb to not draw the endpoints for this Connection. Be careful with this: it only really works when you tell jsPlumb to attach elements to the document body. Read the documentation for a full discussion of this. + */ + var Connection = function(params) { + var self = this, visible = true; + self.idPrefix = "_jsplumb_c_"; + self.defaultLabelLocation = 0.5; + self.defaultOverlayKeys = ["Overlays", "ConnectionOverlays"]; + this.parent = params.parent; + overlayCapableJsPlumbUIComponent.apply(this, arguments); + // ************** get the source and target and register the connection. ******************* + + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none"; + }; + + /** + Property: source + The source element for this Connection. + */ + this.source = _getElementObject(params.source); + /** + Property:target + The target element for this Connection. + */ + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. but + // source is not overridden if the Endpoint has declared it is not the final target of a connection; + // instead we use the source that the Endpoint declares will be the final source element. + if (params.sourceEndpoint) { + this.source = params.sourceEndpoint.endpointWillMoveTo || params.sourceEndpoint.getElement(); + } + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + + // if a new connection is the result of moving some existing connection, params.previousConnection + // will have that Connection in it. listeners for the jsPlumbConnection event can look for that + // member and take action if they need to. + self.previousConnection = params.previousConnection; + + var _cost = params.cost; + self.getCost = function() { return _cost; }; + self.setCost = function(c) { _cost = c; }; + + var _bidirectional = params.bidirectional === true; + self.isBidirectional = function() { return _bidirectional; }; + + /* + * Property: sourceId + * Id of the source element in the connection. + */ + this.sourceId = _getAttribute(this.source, "id"); + /* + * Property: targetId + * Id of the target element in the connection. + */ + this.targetId = _getAttribute(this.target, "id"); + + /** + * implementation of abstract method in EventGenerator + * @return list of attached elements. in our case, a list of Endpoints. + */ + this.getAttachedElements = function() { + return self.endpoints; + }; + + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams, elementId) { + if (anchorParams) + return _currentInstance.makeAnchor(anchorParams, elementId, _currentInstance); + }, + prepareEndpoint = function(existing, index, params, element, elementId, connectorPaintStyle, connectorHoverPaintStyle) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] + || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint; + + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified. + if (es.fillStyle == null && connectorPaintStyle != null) + es.fillStyle = connectorPaintStyle.strokeStyle; + + // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does: + //* + if (es.outlineColor == null && connectorPaintStyle != null) + es.outlineColor = connectorPaintStyle.outlineColor; + if (es.outlineWidth == null && connectorPaintStyle != null) + es.outlineWidth = connectorPaintStyle.outlineWidth; + //*/ + + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure? + if (connectorHoverPaintStyle != null) { + if (ehs == null) ehs = {}; + if (ehs.fillStyle == null) { + ehs.fillStyle = connectorHoverPaintStyle.strokeStyle; + } + } + var a = params.anchors ? params.anchors[index] : + params.anchor ? params.anchor : + _makeAnchor(_currentInstance.Defaults.Anchors[index], elementId) || + _makeAnchor(jsPlumb.Defaults.Anchors[index], elementId) || + _makeAnchor(_currentInstance.Defaults.Anchor, elementId) || + _makeAnchor(jsPlumb.Defaults.Anchor, elementId), + u = params.uuids ? params.uuids[index] : null, + e = _newEndpoint({ + paintStyle : es, + hoverPaintStyle:ehs, + endpoint : ep, + connections : [ self ], + uuid : u, + anchor : a, + source : element, + container:params.container, + reattach:params.reattach, + detachable:params.detachable + }); + self.endpoints[index] = e; + + + if (params.drawEndpoints === false) e.setVisible(false, true, true); + + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, + 0, + params, + self.source, + self.sourceId, + params.paintStyle, + params.hoverPaintStyle); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + + // if there were no endpoints supplied and the source element is the target element, we will reuse the source + // endpoint that was just created. + var existingTargetEndpoint = ((self.sourceId == self.targetId) && params.targetEndpoint == null) ? eS : params.targetEndpoint, + eT = prepareEndpoint(existingTargetEndpoint, + 1, + params, + self.target, + self.targetId, + params.paintStyle, + params.hoverPaintStyle); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + // if delete endpoints on detach, keep a record of just exactly which endpoints they are. + if (params.deleteEndpointsOnDetach) + self.endpointsToDeleteOnDetach = [eS, eT]; + + var _detachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.detachable === false) _detachable = false; + if(self.endpoints[0].connectionsDetachable === false) _detachable = false; + if(self.endpoints[1].connectionsDetachable === false) _detachable = false; + + // inherit connectin cost if it was set on source endpoint + if (_cost == null) _cost = self.endpoints[0].getConnectionCost(); + // inherit bidirectional flag if set no source endpoint + if (params.bidirectional == null) _bidirectional = self.endpoints[0].areConnectionsBidirectional(); + + /* + Function: isDetachable + Returns whether or not this connection can be detached from its target/source endpoint. by default this + is false; use it in conjunction with the 'reattach' parameter. + */ + this.isDetachable = function() { + return _detachable === true; + }; + + /* + Function: setDetachable + Sets whether or not this connection is detachable. + */ + this.setDetachable = function(detachable) { + _detachable = detachable === true; + }; + + // merge all the parameters objects into the connection. parameters set + // on the connection take precedence; then target endpoint params, then + // finally source endpoint params. + // TODO jsPlumb.extend could be made to take more than two args, and it would + // apply the second through nth args in order. + var _p = jsPlumb.extend({}, this.endpoints[0].getParameters()); + jsPlumb.extend(_p, this.endpoints[1].getParameters()); + jsPlumb.extend(_p, self.getParameters()); + self.setParameters(_p); + + // override setHover to pass it down to the underlying connector + var _sh = self.setHover; + + self.setHover = function() { + self.connector.setHover.apply(self.connector, arguments); + _sh.apply(self, arguments); + }; + + var _internalHover = function(state) { + if (_connectionBeingDragged == null) { + self.setHover(state, false); + } + }; + + /* + * Function: setConnector + * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method - the same + * thing that you would set as the 'connector' property on a jsPlumb.connect call. + * + * Parameters: + * connector - Connector definition + */ + this.setConnector = function(connector, doNotRepaint) { + if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent); + var connectorArgs = { + _jsPlumb:self._jsPlumb, + parent:params.parent, + cssClass:params.cssClass, + container:params.container, + tooltip:self.tooltip + }; + if (connector.constructor == String) + this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand. + else if (connector.constructor == Array) + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs)); + self.canvas = self.connector.canvas; + // binds mouse listeners to the current connector. + _bindListeners(self.connector, self, _internalHover); + if (!doNotRepaint) self.repaint(); + }; + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc) + */ + + self.setConnector(this.endpoints[0].connector || + this.endpoints[1].connector || + params.connector || + _currentInstance.Defaults.Connector || + jsPlumb.Defaults.Connector, true); + + + this.setPaintStyle(this.endpoints[0].connectorStyle || + this.endpoints[1].connectorStyle || + params.paintStyle || + _currentInstance.Defaults.PaintStyle || + jsPlumb.Defaults.PaintStyle, true); + + this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || + this.endpoints[1].connectorHoverStyle || + params.hoverPaintStyle || + _currentInstance.Defaults.HoverPaintStyle || + jsPlumb.Defaults.HoverPaintStyle, true); + + this.paintStyleInUse = this.paintStyle; + + + this.moveParent = function(newParent) { + var jpcl = jsPlumb.CurrentLibrary, curParent = jpcl.getParent(self.connector.canvas); + jpcl.removeElement(self.connector.canvas, curParent); + jpcl.appendElement(self.connector.canvas, newParent); + if (self.connector.bgCanvas) { + jpcl.removeElement(self.connector.bgCanvas, curParent); + jpcl.appendElement(self.connector.bgCanvas, newParent); + } + // this only applies for DOMOverlays + for (var i = 0; i < self.overlays.length; i++) { + if (self.overlays[i].isAppendedAtTopLevel) { + jpcl.removeElement(self.overlays[i].canvas, curParent); + jpcl.appendElement(self.overlays[i].canvas, newParent); + if (self.overlays[i].reattachListeners) + self.overlays[i].reattachListeners(self.connector); + } + } + if (self.connector.reattachListeners) // this is for SVG/VML; change an element's parent and you have to reinit its listeners. + self.connector.reattachListeners(); // the Canvas implementation doesn't have to care about this + }; + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Connection. + * + * Parameters: + * event - the event to bind. Available events on a Connection are: + * - *click* : notification that a Connection was clicked. + * - *dblclick* : notification that a Connection was double clicked. + * - *mouseenter* : notification that the mouse is over a Connection. + * - *mouseexit* : notification that the mouse exited a Connection. + * + * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Connection's paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Connection will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Connection. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId], + otherOffset = offsets[this.targetId], + otherWH = sizes[this.targetId], + initialTimestamp = _timestamp(), + anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + elementId:this.endpoints[0].elementId, + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1], + timestamp:initialTimestamp + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc, timestamp:initialTimestamp }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + elementId:this.endpoints[1].elementId, + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0], + timestamp:initialTimestamp + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc, timestamp:initialTimestamp }); + + /* + * Paints the Connection. Not exposed for public usage. + * + * Parameters: + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp, + // if the moving object is not the source we must transpose the two references. + swap = false, + tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId, + tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + + var sourceInfo = _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }), + targetInfo = _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var sE = this.endpoints[sIdx], tE = this.endpoints[tIdx], + sAnchorP = sE.anchor.getCurrentLocation(sE), + tAnchorP = tE.anchor.getCurrentLocation(tE); + + /* paint overlays*/ + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible()) maxSize = Math.max(maxSize, o.computeMaxSize(self.connector)); + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, + this.endpoints[sIdx], this.endpoints[tIdx], + this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, + self.paintStyleInUse.lineWidth, maxSize, + sourceInfo, + targetInfo); + + self.connector.paint(dim, self.paintStyleInUse); + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim); + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function(params) { + params = params || {}; + var recalc = !(params.recalc === false); + this.paint({ elId : this.sourceId, recalc : recalc, timestamp:params.timestamp }); + }; + + // just to make sure the UI gets initialised fully on all browsers. + self.repaint(); + }; + +// ENDPOINT HELPER FUNCTIONS + var _makeConnectionDragHandler = function(placeholder) { + var stopped = false; + return { + drag : function() { + if (stopped) return true; + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments), + el = placeholder.element; + if (el) { + jsPlumb.CurrentLibrary.setOffset(el, _ui); + _draw(_getElementObject(el), _ui); + } + }, + stopDrag : function() { + stopped = true; + } + }; + }; + + var _makeFloatingEndpoint = function(paintStyle, referenceAnchor, endpoint, referenceCanvas, sourceElement) { + var floatingAnchor = new FloatingAnchor( { reference : referenceAnchor, referenceCanvas : referenceCanvas }); + + //setting the scope here should not be the way to fix that mootools issue. it should be fixed by not + // adding the floating endpoint as a droppable. that makes more sense anyway! + + return _newEndpoint({ paintStyle : paintStyle, endpoint : endpoint, anchor : floatingAnchor, source : sourceElement, scope:"__floating" }); + }; + + /** + * creates a placeholder div for dragging purposes, adds it to the DOM, and pre-computes its offset. then returns + * both the element id and a selector for the element. + */ + var _makeDraggablePlaceholder = function(placeholder, parent) { + var n = document.createElement("div"); + n.style.position = "absolute"; + var placeholderDragElement = _getElementObject(n); + _appendElement(n, parent); + var id = _getId(placeholderDragElement); + _updateOffset( { elId : id }); + // create and assign an id, and initialize the offset. + placeholder.id = id; + placeholder.element = placeholderDragElement; + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1 + * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't + * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you + * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or + * 'target' Endpoints for Connections. + * + * + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * container - optional id or selector instructing jsPlumb where to attach the element it creates for this endpoint. you should read the documentation for a full discussion of this. + * connections - optional list of Connections to configure the Endpoint with. + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + var self = this; + self.idPrefix = "_jsplumb_e_"; + self.defaultLabelLocation = [ 0.5, 0.5 ]; + self.defaultOverlayKeys = ["Overlays", "EndpointOverlays"]; + this.parent = params.parent; + overlayCapableJsPlumbUIComponent.apply(this, arguments); + params = params || {}; + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Endpoint. + * + * Parameters: + * event - the event to bind. Available events on an Endpoint are: + * - *click* : notification that a Endpoint was clicked. + * - *dblclick* : notification that a Endpoint was double clicked. + * - *mouseenter* : notification that the mouse is over a Endpoint. + * - *mouseexit* : notification that the mouse exited a Endpoint. + * + * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Endpoint's paint style and then repaints the Endpoint. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Endpoint will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Endpoint. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + var visible = true; + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + var _element = params.source, _uuid = params.uuid, floatingEndpoint = null, inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + this.element = _element; + + var _connectionCost = params.connectionCost; + this.getConnectionCost = function() { return _connectionCost; }; + this.setConnectionCost = function(c) { + _connectionCost = c; + }; + + var _connectionsBidirectional = params.connectionsBidirectional === true; + this.areConnectionsBidirectional = function() { return _connectionsBidirectional; }; + this.setConnectionsBidirectional = function(b) { _connectionsBidirectional = b; }; + + self.anchor = params.anchor ? _currentInstance.makeAnchor(params.anchor, _elementId, _currentInstance) : params.anchors ? _currentInstance.makeAnchor(params.anchors, _elementId, _currentInstance) : _currentInstance.makeAnchor("TopCenter", _elementId, _currentInstance); + + // ANCHOR MANAGER + if (!params._transient) // in place copies, for example, are transient. they will never need to be retrieved during a paint cycle, because they dont move, and then they are deleted. + _currentInstance.anchorManager.add(self, _elementId); + + var _endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot", + endpointArgs = { + _jsPlumb:self._jsPlumb, + parent:params.parent, + container:params.container, + tooltip:params.tooltip, + connectorTooltip:params.connectorTooltip, + endpoint:self + }; + if (_endpoint.constructor == String) + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint](endpointArgs); + else if (_endpoint.constructor == Array) { + endpointArgs = jsPlumb.extend(_endpoint[1], endpointArgs); + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint[0]](endpointArgs); + } + else { + _endpoint = _endpoint.clone(); + } + + // assign a clone function using a copy of endpointArgs. this is used when a drag starts: the endpoint that was dragged is cloned, + // and the clone is left in its place while the original one goes off on a magical journey. + // the copy is to get around a closure problem, in which endpointArgs ends up getting shared by + // the whole world. + var argsForClone = jsPlumb.extend({}, endpointArgs); + _endpoint.clone = function() { + var o = new Object(); + _endpoint.constructor.apply(o, [argsForClone]); + return o; + }; + + self.endpoint = _endpoint; + self.type = self.endpoint.type; + // override setHover to pass it down to the underlying endpoint + var _sh = self.setHover; + self.setHover = function() { + self.endpoint.setHover.apply(self.endpoint, arguments); + _sh.apply(self, arguments); + }; + // endpoint delegates to first connection for hover, if there is one. + var internalHover = function(state) { + if (self.connections.length > 0) + self.connections[0].setHover(state, false); + else + self.setHover(state); + }; + + // bind listeners from endpoint to self, with the internal hover function defined above. + _bindListeners(self.endpoint, self, internalHover); + + this.setPaintStyle(params.paintStyle || + params.style || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyle, true); + this.setHoverPaintStyle(params.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyle, true); + this.paintStyleInUse = this.paintStyle; + this.connectorStyle = params.connectorStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.connectorTooltip = params.connectorTooltip; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + + var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + /* + * Property: canvas + * The Endpoint's Canvas. + */ + this.canvas = this.endpoint.canvas; + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + this.connections = params.connections || []; + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + self.isReattach = params.reattach || false; + self.connectionsDetachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.connectionsDetachable === false || params.detachable === false) + self.connectionsDetachable = false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget, forceDetach, fireEvent) { + //var idx = _findIndex(self.connections, connection), actuallyDetached = false; + var idx = _findWithFunction(self.connections, function(c) { return c.id == connection.id}), + actuallyDetached = false; + fireEvent = (fireEvent !== false); + if (idx >= 0) { + // 1. does the connection have a before detach (note this also checks jsPlumb's bound + // detach handlers; but then Endpoint's check will, too, hmm.) + if (forceDetach || connection._forceDetach || connection.isDetachable() || connection.isDetachAllowed(connection)) { + // get the target endpoint + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + // it would be nice to check with both endpoints that it is ok to detach. but + // for this we'll have to get a bit fancier: right now if you use the same beforeDetach + // interceptor for two endpoints (which is kind of common, because it's part of the + // endpoint definition), then it gets fired twice. so in fact we need to loop through + // each beforeDetach and see if it returns false, at which point we exit. but if it + // returns true, we have to check the next one. however we need to track which ones + // have already been run, and not run them again. + if (forceDetach || connection._forceDetach || (self.isDetachAllowed(connection) /*&& t.isDetachAllowed(connection)*/)) { + + self.connections.splice(idx, 1); + + // this avoids a circular loop + if (!ignoreTarget) { + + t.detach(connection, true, forceDetach); + // check connection to see if we want to delete the endpoints associated with it. + // we only detach those that have just this connection; this scenario is most + // likely if we got to this bit of code because it is set by the methods that + // create their own endpoints, like .connect or .makeTarget. the user is + // not likely to have interacted with those endpoints. + if (connection.endpointsToDeleteOnDetach){ + for (var i = 0; i < connection.endpointsToDeleteOnDetach.length; i++) { + var cde = connection.endpointsToDeleteOnDetach[i]; + if (cde && cde.connections.length == 0) + _currentInstance.deleteEndpoint(cde); + } + } + } + _removeElements(connection.connector.getDisplayElements(), connection.parent); + _removeWithFunction(connectionsByScope[connection.scope], function(c) { + return c.id == connection.id; + }); + actuallyDetached = true; + var doFireEvent = (!ignoreTarget && fireEvent) + fireDetachEvent(connection, doFireEvent); + } + } + } + return actuallyDetached; + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + * + * Parameters: + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachAll = function(fireEvent) { + while (self.connections.length > 0) { + self.detach(self.connections[0], false, true, fireEvent); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachFrom = function(targetEndpoint, fireEvent) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + if (self.detach(c[i], false, true, fireEvent)) + c[i].setHover(false, false); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: setElement + * Sets the DOM element this Endpoint is attached to. + */ + this.setElement = function(el) { + // TODO possibly have this object take charge of moving the UI components into the appropriate + // parent. this is used only by makeSource right now, and that function takes care of + // moving the UI bits and pieces. however it would s + var parentId = _getId(el); + // remove the endpoint from the list for the current endpoint's element + _removeWithFunction(endpointsByElement[_elementId], function(e) { + return e.id == self.id; + }); + _element = _getElementObject(el); + _elementId = _getId(_element); + self.elementId = _elementId; + + // need to get the new parent now + var newParentElement = _getParentFromParams({source:parentId}), + curParent = jpcl.getParent(self.canvas); + jpcl.removeElement(self.canvas, curParent); + jpcl.appendElement(self.canvas, newParentElement); + + // now move connection(s)...i would expect there to be only one but we will iterate. + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].moveParent(newParentElement); + self.connections[i].sourceId = _elementId; + self.connections[i].source = _element; + } + + _addToList(endpointsByElement, parentId, self); + _currentInstance.repaint(parentId); + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + return _newEndpoint( { + anchor : self.anchor, + source : _element, + paintStyle : this.paintStyle, + endpoint : _endpoint, + _transient:true, + scope:self.scope + }); + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + var candidate = self.connections[0]; + if (self.isTarget && candidate) return candidate; + else { + return (self.connections.length < _maxConnections) || _maxConnections == -1 ? null : candidate; + } + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + /* + * Function: setStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to + * setStyle and deprecate it if so. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + * + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + // a helper function that tries to find a connection to the given element, and returns it if so. if elementWithPrecedence is null, + // or no connection to it is found, we return the first connection in our list. + var findConnectionToUseForDynamicAnchor = function(elementWithPrecedence) { + var idx = 0; + if (elementWithPrecedence != null) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].sourceId == elementWithPrecedence || self.connections[i].targetId == elementWithPrecedence) { + idx = i; + break; + } + } + } + + return self.connections[idx]; + }; + + /* + * Function: paint + * Paints the Endpoint, recalculating offset and anchor positions if necessary. This does NOT paint + * any of the Endpoint's connections. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + params = params || {}; + var timestamp = params.timestamp, + recalc = !(params.recalc === false); + if (!timestamp || self.timestamp !== timestamp) { + _updateOffset({ elId:_elementId, timestamp:timestamp, recalc:recalc }); + var xy = params.offset || offsets[_elementId]; + if(xy) { + var ap = params.anchorPoint,connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (recalc && self.anchor.isDynamic && self.connections.length > 0) { + var c = findConnectionToUseForDynamicAnchor(params.elementWithPrecedence), + oIdx = c.endpoints[0] == self ? 1 : 0, + oId = oIdx == 0 ? c.sourceId : c.targetId, + oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + ap = self.anchor.compute(anchorParams); + } + + var d = _endpoint.compute(ap, self.anchor.getOrientation(_endpoint), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + _endpoint.paint(d, self.paintStyleInUse, self.anchor); + self.timestamp = timestamp; + + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) self.overlayPlacements[i] = o.draw(self.endpoint, self.paintStyleInUse, d); + } + } + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var placeholderInfo = { + id:null, + element:null + }, + jpc = null, + existingJpc = false, + existingJpcParams = null, + _dragHandler = _makeConnectionDragHandler(placeholderInfo); + + var start = function() { + // drag might have started on an endpoint that is not actually a source, but which has + // one or more connections. + jpc = self.connectorSelector(); + var _continue = true; + // if no connection and we're not a source, return. + if (jpc == null && !params.isSource) _continue = false; + // otherwise if we're full and not allowed to drag, also return false. + if (params.isSource && self.isFull() && !dragAllowedWhenFull) _continue = false; + + // if the connection was setup as not detachable (or one of its endpoints + // was setup as connectionsDetachable = false, or Defaults.ConnectionsDetachable + // is set to false... + if (jpc != null && !jpc.isDetachable()) _continue = false; + + if (_continue === false) { + // this is for mootools and yui. returning false from this causes jquery to stop drag. + // the events are wrapped in both mootools and yui anyway, but i don't think returning + // false from the start callback would stop a drag. + if (jsPlumb.CurrentLibrary.stopDrag) jsPlumb.CurrentLibrary.stopDrag(); + _dragHandler.stopDrag(); + return false; + } + + // if we're not full but there was a connection, make it null. we'll create a new one. + if (jpc && !self.isFull() && params.isSource) jpc = null; + + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + _makeDraggablePlaceholder(placeholderInfo, self.parent); + + // set the offset of this div to be where 'inPlaceCopy' is, to start with. + // TODO merge this code with the code in both Anchor and FloatingAnchor, because it + // does the same stuff. + var ipcoel = _getElementObject(inPlaceCopy.canvas), + ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel), + po = adjustForParentOffsetAndScroll([ipco.left, ipco.top], inPlaceCopy.canvas); + jsPlumb.CurrentLibrary.setOffset(placeholderInfo.element, {left:po[0], top:po[1]}); + + // when using makeSource and a parent, we first draw the source anchor on the source element, then + // move it to the parent. note that this happens after drawing the placeholder for the + // first time. + if (self.parentAnchor) self.anchor = _currentInstance.makeAnchor(self.parentAnchor, self.elementId, _currentInstance); + + + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", placeholderInfo.id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + floatingEndpoint = _makeFloatingEndpoint(self.paintStyle, self.anchor, _endpoint, self.canvas, placeholderInfo.element); + + if (jpc == null) { + self.anchor.locked = true; + self.setHover(false, false); + // TODO the hover call above does not reset any target endpoint's hover + // states. + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : self.endpointWillMoveTo || _getElementObject(_element), // for makeSource with parent option. ensure source element is represented correctly. + target : placeholderInfo.element, + anchors : [ self.anchor, floatingEndpoint.anchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays + }); + + } else { + existingJpc = true; + jpc.connector.setHover(false, false); + // jpc.endpoints[0].setHover(false, false); + //jpc.endpoints[1].setHover(false, false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas), false, true); + //var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; // are we the source or the target? + // new anchor idx + var anchorIdx = jpc.endpoints[0].id == self.id ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas), + dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // now we want to get this endpoint's DROP scope, and set it for now: we can only be dropped on drop zones + // that have our drop scope (issue 57). + var dropScope = jsPlumb.CurrentLibrary.getDropScope(c); + jsPlumb.CurrentLibrary.setDragScope(c, dropScope); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = placeholderInfo.element; + jpc.sourceId = placeholderInfo.id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = placeholderInfo.element; + jpc.targetId = placeholderInfo.id; + } + + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.suspendedEndpoint.setHover(false); + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + // register it and register connection on it. + floatingConnections[placeholderInfo.id] = jpc; + floatingEndpoint.addConnection(jpc); + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, placeholderInfo.id, floatingEndpoint); + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var jpcl = jsPlumb.CurrentLibrary, + dragOptions = params.dragOptions || {}, + defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions), + startEvent = jpcl.dragEvents["start"], + stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"]; + + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + // extracted drag handler function so can be used by makeSource + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], _dragHandler.drag); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _currentInstance.currentlyDragging = false; + _removeWithFunction(endpointsByElement[placeholderInfo.id], function(e) { + return e.id == floatingEndpoint.id; + }); + _removeElements( [ placeholderInfo.element[0], floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + _currentInstance.anchorManager.clearFor(placeholderInfo.id); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (self.isReattach || jpc._forceDetach || !jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc)) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + _currentInstance.repaint(existingJpcParams[1]); + } + jpc._forceDetach = null; + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElements(jpc.connector.getDisplayElements(), self.parent); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint({recalc:false}); + jpc.setHover(false, false); + jpc = null; + inPlaceCopy = null; + delete endpointsByElement[floatingEndpoint.elementId]; + floatingEndpoint.anchor = null; + floatingEndpoint = null; + _currentInstance.currentlyDragging = false; + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions, true); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas, forceInit, isTransient) { + if ((params.isTarget || forceInit) && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop'], + overEvent = jsPlumb.CurrentLibrary.dragEvents['over'], + outEvent = jsPlumb.CurrentLibrary.dragEvents['out'], + drop = function(originalEvent) { + var draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + elId = _getAttribute(draggable, "elId"), + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id], + idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + + // restore the original scope if necessary (issue 57) + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + + var _doContinue = true; + // the second check here is for the case that the user is dropping it back + // where it came from. + if (jpc.suspendedEndpoint && jpc.suspendedEndpoint.id != self.id) { + if (!jpc.isDetachAllowed(jpc) || !jpc.endpoints[idx].isDetachAllowed(jpc) || !jpc.suspendedEndpoint.isDetachAllowed(jpc) || !_currentInstance.checkCondition("beforeDetach", jpc)) + _doContinue = false; + } + + // these have to be set before testing for beforeDrop. + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + + // now check beforeDrop. this will be available only on Endpoints that are setup to + // have a beforeDrop condition (although, secretly, under the hood all Endpoints and + // the Connection have them, because they are on jsPlumbUIComponent. shhh!), because + // it only makes sense to have it on a target endpoint. + _doContinue = _doContinue && self.isDropAllowed(jpc.sourceId, jpc.targetId, jpc.scope); + + if (_doContinue) { + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + if (!jpc.suspendedEndpoint) { + //_addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + fireDetachEvent({ + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }, true); + } + + // finalise will inform the anchor manager and also add to + // connectionsByScope if necessary. + _finaliseConnection(jpc, null, originalEvent); + } + else { + // otherwise just put it back on the endpoint it was on before the drag. + if (jpc.suspendedEndpoint) { + // self.detachFrom(jpc); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.setHover(false); + jpc._forceDetach = true; + if (idx == 0) { + jpc.source = jpc.suspendedEndpoint.element; + jpc.sourceId = jpc.suspendedEndpoint.elementId; + } else { + jpc.target = jpc.suspendedEndpoint.element; + jpc.targetId = jpc.suspendedEndpoint.elementId;; + } + jpc.suspendedEndpoint.addConnection(jpc); + + jpc.endpoints[0].repaint(); + jpc.repaint(); + _currentInstance.repaint(jpc.source.elementId); + jpc._forceDetach = false; + } + } + + jpc.floatingAnchorIndex = null; + } + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], function() { + if (self.isTarget) { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + } + } + }); + dropOptions[outEvent] = _wrap(dropOptions[outEvent], function() { + if (self.isTarget) { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + } + } + }); + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions, true, isTransient); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas), true, !(params._transient || self.anchor.isFloating)); + + return self; + }; + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + j.init(); + return j; + }; + jsPlumb.util = { + convertStyle : function(s, ignoreAlpha) { + // TODO: jsPlumb should support a separate 'opacity' style member. + if ("transparent" === s) return s; + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + return o; + }, + gradient : function(p1, p2) { + p1 = p1.constructor == Array ? p1 : [p1.x, p1.y]; + p2 = p2.constructor == Array ? p2 : [p2.x, p2.y]; + return (p2[1] - p1[1]) / (p2[0] - p1[0]); + }, + normal : function(p1, p2) { + return -1 / jsPlumb.util.gradient(p1,p2); + }, + segment : function(p1, p2) { + p1 = p1.constructor == Array ? p1 : [p1.x, p1.y]; + p2 = p2.constructor == Array ? p2 : [p2.x, p2.y]; + if (p2[0] > p1[0]) { + return (p2[1] > p1[1]) ? 2 : 1; + } + else { + return (p2[1] > p1[1]) ? 3 : 4; + } + }, + segmentMultipliers : [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ], + inverseSegmentMultipliers : [null, [-1, -1], [-1, 1], [1, 1], [1, -1] ], + pointOnLine : function(fromPoint, toPoint, distance) { + var m = jsPlumb.util.gradient(fromPoint, toPoint), + s = jsPlumb.util.segment(fromPoint, toPoint), + segmentMultiplier = distance > 0 ? jsPlumb.util.segmentMultipliers[s] : jsPlumb.util.inverseSegmentMultipliers[s], + theta = Math.atan(m), + y = Math.abs(distance * Math.sin(theta)) * segmentMultiplier[1], + x = Math.abs(distance * Math.cos(theta)) * segmentMultiplier[0]; + return { x:fromPoint.x + x, y:fromPoint.y + y }; + }, + /** + * calculates a perpendicular to the line fromPoint->toPoint, that passes through toPoint and is 'length' long. + * @param fromPoint + * @param toPoint + * @param length + */ + perpendicularLineTo : function(fromPoint, toPoint, length) { + var m = jsPlumb.util.gradient(fromPoint, toPoint), + theta2 = Math.atan(-1 / m), + y = length / 2 * Math.sin(theta2), + x = length / 2 * Math.cos(theta2); + return [{x:toPoint.x + x, y:toPoint.y + y}, {x:toPoint.x - x, y:toPoint.y - y}]; + } + }; + + var _curryAnchor = function(x, y, ox, oy, type, fnInit) { + return function(params) { + params = params || {}; + //var a = jsPlumb.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance); + var a = params.jsPlumbInstance.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance); + a.type = type; + if (fnInit) fnInit(a, params); + return a; + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1, "TopCenter"); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1, "BottomCenter"); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0, "LeftMiddle"); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0, "RightMiddle"); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0, "Center"); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1, "TopRight"); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1, "BottomRight"); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1, "TopLeft"); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1, "BottomLeft"); + + // TODO test that this does not break with the current instance idea + jsPlumb.Defaults.DynamicAnchors = function(params) { + return params.jsPlumbInstance.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"], params.elementId, params.jsPlumbInstance); + }; + jsPlumb.Anchors["AutoDefault"] = function(params) { + var a = params.jsPlumbInstance.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors(params)); + a.type = "AutoDefault"; + return a; + }; + + jsPlumb.Anchors["Assign"] = _curryAnchor(0,0,0,0,"Assign", function(anchor, params) { + // find what to use as the "position finder". the user may have supplied a String which represents + // the id of a position finder in jsPlumb.AnchorPositionFinders, or the user may have supplied the + // position finder as a function. we find out what to use and then set it on the anchor. + var pf = params.position || "Fixed"; + anchor.positionFinder = pf.constructor == String ? params.jsPlumbInstance.AnchorPositionFinders[pf] : pf; + // always set the constructor params; the position finder might need them later (the Grid one does, + // for example) + anchor.constructorParams = params; + }); + + // Continuous anchor is just curried through to the 'get' method of the continuous anchor + // factory. + jsPlumb.Anchors["Continuous"] = function(params) { + return params.jsPlumbInstance.continuousAnchorFactory.get(params); + }; + + // these are the default anchor positions finders, which are used by the makeTarget function. supply + // a position finder argument to that function allows you to specify where the resulting anchor will + // be located + jsPlumb.AnchorPositionFinders = { + "Fixed": function(dp, ep, es, a) { + return [ (dp.left - ep.left) / es[0], (dp.top - ep.top) / es[1] ]; + }, + "Grid":function(dp, ep, es, a) { + var dx = dp.left - ep.left, dy = dp.top - ep.top, + gx = es[0] / (a.constructorParams.grid[0]), gy = es[1] / (a.constructorParams.grid[1]), + mx = Math.floor(dx / gx), my = Math.floor(dy / gy); + return [ ((mx * gx) + (gx / 2)) / es[0], ((my * gy) + (gy / 2)) / es[1] ]; + } + }; +})(); diff --git a/archive/1.3.5/jsPlumb-1.3.5-tests.js b/archive/1.3.5/jsPlumb-1.3.5-tests.js new file mode 100644 index 000000000..2c787e6e0 --- /dev/null +++ b/archive/1.3.5/jsPlumb-1.3.5-tests.js @@ -0,0 +1,2900 @@ +// _jsPlumb qunit tests. + +QUnit.config.reorder = false; + +var _getContextNode = function() { + return $("#container"); +}; + +var assertContextExists = function() { + ok(_getContextNode().length == 1, "context node exists"); +}; + +var assertContextSize = function(elementCount) { + //equals(_getContextNode().children().length - _divs.length, elementCount, 'context has ' + elementCount + ' children'); +}; + +var assertContextEmpty = function() { + equals(_getContextNode().children.length, 0, "context empty"); +}; + +var assertEndpointCount = function(elId, count, _jsPlumb) { + equals(_jsPlumb.getTestHarness().endpointCount(elId), count, elId + " has " + count + (count > 1) ? "endpoints" : "endpoint"); +}; + +var assertConnectionCount = function(endpoint, count) { + equals(endpoint.connections.length, count, "endpoint has " + count + " connections"); +}; + +var assertConnectionByScopeCount = function(scope, count, _jsPlumb) { + equals(_jsPlumb.getTestHarness().connectionCount(scope), count, 'Scope ' + scope + " has " + count + (count > 1) ? "connections" : "connection"); + +}; + +var _divs = []; +var _addDiv = function(id) { + var d1 = document.createElement("div"); + _getContextNode().append(d1); + d1 = jsPlumb.CurrentLibrary.getElementObject(d1); + jsPlumb.CurrentLibrary.setAttribute(d1, "id", id); + _divs.push(id); + return d1; +}; + +var _cleanup = function(_jsPlumb) { + + _jsPlumb.reset(); + _jsPlumb.Defaults.Container = null; + _jsPlumb.Defaults.ConnectionsDetachable = true; + _jsPlumb.Defaults.Overlays = null; + _jsPlumb.Defaults.ConnectionOverlays = null; + _jsPlumb.Defaults.EndpointOverlays = null; + + for (var i in _divs) { + $("#" + _divs[i]).remove(); + } + _divs.splice(0, _divs.length - 1); + + $("#container").empty(); +}; + +var testSuite = function(renderMode, _jsPlumb) { + + + module("jsPlumb", {teardown: function() { _cleanup(_jsPlumb); } }); + + // setup the container + var container = document.createElement("div"); + container.id = "container"; + document.body.appendChild(container); + + var jpcl = jsPlumb.CurrentLibrary; + + _jsPlumb.setRenderMode(renderMode); + + test(renderMode + " : getElementObject", function() { + var e = document.createElement("div"); + e.id = "FOO"; + document.body.appendChild(e); + var el = jpcl.getElementObject(e); + equals(jpcl.getAttribute(el, "id"), "FOO"); + }); + + test(renderMode + " : getDOMElement", function() { + var e = document.createElement("div"); + e.id = "FOO"; + document.body.appendChild(e); + var el = jpcl.getElementObject(e); + var e2 = jpcl.getDOMElement(el); + equals(e2.id, "FOO"); + }); + + test(renderMode + ': findIndex method', function() { + var array = [ 1,2,3, "test", "a string", { 'foo':'bar', 'baz':1 }, { 'ding':'dong' } ]; + equals(_jsPlumb.getTestHarness().findIndex(array, 1), 0, "find works for integers"); + equals(_jsPlumb.getTestHarness().findIndex(array, "test"), 3, "find works for strings"); + equals(_jsPlumb.getTestHarness().findIndex(array, { 'foo':'bar', 'baz':1 }), 5, "find works for objects"); + equals(_jsPlumb.getTestHarness().findIndex(array, { 'ding':'dong', 'baz':1 }), -1, "find works properly for objects (objects have different length but some same properties)"); + }); + + test(renderMode + ': _jsPlumb setup', function() { + ok(_jsPlumb, "loaded"); + }); + + test(renderMode + ': getId', function() { + var d10 = _addDiv('d10'); + equals(_jsPlumb.getTestHarness().getId(d10), "d10"); + }); + + test(renderMode + ': create a simple endpoint', function() { + var d1 = _addDiv("d1"); + var e = _jsPlumb.addEndpoint($("#d1"), {}); + ok(e, 'endpoint exists'); + assertEndpointCount("d1", 1, _jsPlumb); + ok(e.id != null, "endpoint has had an id assigned"); + }); + + test(renderMode + ': create and remove a simple endpoint', function() { + var d1 = _addDiv("d1"); + var ee = _jsPlumb.addEndpoint(d1, {uuid:"78978597593"}); + ok(ee != null, "endpoint exists"); + var e = _jsPlumb.getEndpoint("78978597593"); + ok(e != null, "the endpoint could be retrieved by UUID"); + ok(e.id != null, "the endpoint has had an id assigned to it"); + assertEndpointCount("d1", 1, _jsPlumb); + assertContextSize(1); // one Endpoint canvas. + _jsPlumb.removeEndpoint(d1, ee); + assertEndpointCount("d1", 0, _jsPlumb); + e = _jsPlumb.getEndpoint("78978597593"); + equals(e, null, "the endpoint has been deleted"); + assertContextSize(0); // no Endpoint canvases. + }); + + test(renderMode + ': create two simple endpoints, registered using a selector', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + d1.addClass("window");d2.addClass("window"); + var endpoints = _jsPlumb.addEndpoint($(".window"), {}); + equals(endpoints.length, 2, "endpoint added to both windows"); + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + }); + + test(renderMode + ': create two simple endpoints, registered using an array of element ids', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + d1.addClass("window");d2.addClass("window"); + var endpoints = _jsPlumb.addEndpoint(["d1", "d2"], {}); + equals(endpoints.length, 2, "endpoint added to both windows"); + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + }); + + test(renderMode + ': draggable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1"); + var e = _jsPlumb.addEndpoint(d1, {isSource:true}); + ok(e, 'endpoint exists'); + }); + + test(renderMode + ': droppable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1") + var e = _jsPlumb.addEndpoint(d1, {isTarget:true}); + ok(e, 'endpoint exists'); + }); + + test(renderMode + ': defaultEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = _jsPlumb.addEndpoint(d3, {isSource:true}); + ok(e3.anchor, 'endpoint 3 has an anchor'); + var e4 = _jsPlumb.addEndpoint(d4, {isSource:true}); + assertEndpointCount("d3", 1, _jsPlumb); + assertEndpointCount("d4", 1, _jsPlumb); + assertContextSize(2); // one canvas for each of our two endpoints. + ok(!e3.isFull(), "endpoint 3 is not full."); + _jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // we have one connection + _jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // should have refused the connection; default max is 1. + assertContextSize(3); // now we have one more canvas, for the Connection that was accepted. + }); + + test(renderMode + ': specifiedEndpointMaxConnections', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = _jsPlumb.addEndpoint(d5, {isSource:true, maxConnections:3}); + ok(e5.anchor, 'endpoint 5 has an anchor'); + var e6 = _jsPlumb.addEndpoint(d6, {isSource:true, maxConnections:2}); // this one has max TWO + assertEndpointCount("d5", 1, _jsPlumb); assertEndpointCount("d6", 1, _jsPlumb); + assertContextSize(2); + ok(!e5.isFull(), "endpoint 5 is not full."); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 1); // we have one connection + assertContextSize(3); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // two connections + assertContextSize(4); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // should have refused; max is 2, for d4. + assertContextSize(4); + }); + + test(renderMode + ': noEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = _jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + var e4 = _jsPlumb.addEndpoint(d4, {isSource:true, maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertContextSize(3); + assertConnectionCount(e3, 1); // we have one connection + _jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 2); // we have two. etc (default was one. this proves max is working). + assertContextSize(4); + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = _jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e4}); + assertConnectionCount(e4, 3); + assertContextSize(6); + }); + + test(renderMode + ': anchors equal', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + ok(a1.equals(a2), "anchors are the same"); + }); + + test(renderMode + ': anchors equal with offsets', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb); + ok(a1.equals(a2), "anchors are the same"); + }); + + test(renderMode + ': anchors not equal', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 0, 1], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + ok(!a1.equals(a2), "anchors are different"); + }); + + test(renderMode + ': anchor not equal with offsets', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + ok(!a1.equals(a2), "anchors are different"); + }); + + test(renderMode + ': detach does not fail when no arguments are provided', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + _jsPlumb.connect({source:d3, target:d4}); + _jsPlumb.detach(); + }); + + // test that detach does not fire an event by default + test(renderMode + ': _jsPlumb.detach should fire detach event by default', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach(conn); + equals(eventCount, 1); + }); + + // test that detach does not fire an event by default + test(renderMode + ': _jsPlumb.detach should fire detach event by default, using params object', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({connection:conn}); + equals(eventCount, 1); + }); + + // test that detach fires an event when instructed to do so + test(renderMode + ': _jsPlumb.detach should not fire detach event when instructed to not do so', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach(conn, {fireEvent:false}); + equals(eventCount, 0); + }); + + // issue 81 + test(renderMode + ': _jsPlumb.detach should fire only one detach event (pass Connection as argument)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach(conn); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': _jsPlumb.detach should fire only one detach event (pass Connection as param in argument)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({connection:conn}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': detach should fire only one detach event (pass source and targets as strings as arguments in params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({source:"d5", target:"d6"}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': detach should fire only one detach event (pass source and targets as divs as arguments in params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({source:d5, target:d6, fireEvent:true}); + equals(eventCount, 1); + }); + + //TODO make sure you run this test with a single detach call, to ensure that + // single detach calls result in the connection being removed. detachEveryConnection can + // just blow away the connectionsByScope array and recreate it. + test(renderMode + ': getConnections (simple case, default scope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + _jsPlumb.connect({source:d5, target:d6}); + assertContextSize(3); + var c = _jsPlumb.getConnections(); // will get all connections in the default scope. + equals(c.length, 1, "there is one connection"); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by element id using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + _jsPlumb.connect({source:d5, target:d6}); + _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach({source:'d5', target:'d6'}); + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by id using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + _jsPlumb.connect({source:d5, target:d6}); + _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach({source:"d5", target:"d6"}); + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by element object using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + _jsPlumb.connect({source:d5, target:d6}); + _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach({source:d5, target:d6}); + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by Connection)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + var c56 = _jsPlumb.connect({source:d5, target:d6}); + var c67 = _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach(c56); + assertContextSize(5); // check that the connection canvas was removed. + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + }); + +// beforeDetach functionality + + test(renderMode + ": detach; beforeDetach on connect call returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { return false; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": detach; beforeDetach on connect call returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { return true; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": detach; beforeDetach on connect call throws an exception; we treat it with the contempt it deserves and pretend it said the detach was ok.", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { throw "i am an example of badly coded beforeDetach, but i don't break jsPlumb "; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": detach; beforeDetach on addEndpoint call to source Endpoint returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return false; } }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": detach; beforeDetach on addEndpoint call to source Endpoint returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return true; } }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + + test(renderMode + ": Endpoint.detach; beforeDetach on addEndpoint call to source Endpoint returns false; Endpoint.detach returns false too (the UI needs it to)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return false; } }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + var success = e1.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + ok(!success, "Endpoint reported detach did not execute"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns false but detach is forced", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c, {forceDetach:true}); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was denied but forced anyway"); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was denied but forced anyway"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return true; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach bound to _jsPlumb returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + _jsPlumb.bind("beforeDetach", function(connection) { + ok(connection.sourceId === "d1", "connection is provided and configured with correct source"); + ok(connection.targetId === "d2", "connection is provided and configured with correct target"); + return false; + }); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach bound to _jsPlumb returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + _jsPlumb.bind("beforeDetach", function(connection) { + ok(connection.sourceId === "d1", "connection is provided and configured with correct source"); + ok(connection.targetId === "d2", "connection is provided and configured with correct target"); + return true; + }); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was denied"); + }); + + test(renderMode + ": _jsPlumb.detachAllConnections ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detachAllConnections(d1); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachAll was called"); + }); + + test(renderMode + ": _jsPlumb.detachEveryConnection ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detachEveryConnection(); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachEveryConnection was called"); + }); + + test(renderMode + ": Endpoint.detachAll ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + e1.detachAll(); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachAllConnections was called"); + }); + +// ******** end of beforeDetach tests ************** + +// detachEveryConnection/detachAllConnections fireEvent overrides tests + + test(renderMode + ": _jsPlumb.detachEveryConnection fires events", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0; + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachEveryConnection(); + equals(connCount, 0, "no connections registered"); + }); + + test(renderMode + ": _jsPlumb.detachEveryConnection doesn't fire events when instructed not to", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0; + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachEveryConnection({fireEvent:false}); + equals(connCount, 2, "two connections registered"); + }); + + test(renderMode + ": _jsPlumb.detachAllConnections fires events", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0, + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachAllConnections("d1"); + equals(connCount, 0, "no connections registered"); + }); + + test(renderMode + ": _jsPlumb.detachAllConnections doesn't fire events when instructed not to", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0, + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachAllConnections("d1", {fireEvent:false}); + equals(connCount, 2, "two connections registered"); + }); + + test(renderMode + ': getConnections (scope testScope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + _jsPlumb.connect({source:d5, target:d6, scope:'testScope'}); + var c = _jsPlumb.getConnections("testScope"); // will get all connections in testScope + equals(c.length, 1, "there is one connection"); + equals(c[0].sourceId, 'd5', "the connection's source is d5"); + equals(c[0].targetId, 'd6', "the connection's source is d6"); + c = _jsPlumb.getConnections(); // will get all connections in default scope; should be none. + equals(c.length, 0, "there are no connections in the default scope"); + }); + + test(renderMode + ': _jsPlumb.getAllConnections (filtered by scope)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + _jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + _jsPlumb.connect({source:d9, target:d10}); // default scope + var c = _jsPlumb.getAllConnections(); // will get all connections + equals(c[_jsPlumb.getDefaultScope()].length, 1); + equals(c['testScope'].length, 1); + // now supply a list of scopes + c = _jsPlumb.getConnections(); + equals(c.length, 1); + c = _jsPlumb.getConnections("testScope"); + equals(c.length, 1, "there is one connection in 'testScope'"); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by scope and sourceId)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + _jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + _jsPlumb.connect({source:d9, target:d8, scope:'testScope'}); + _jsPlumb.connect({source:d9, target:d10}); // default scope + var c = _jsPlumb.getConnections({scope:'testScope', source:'d8'}); // will get all connections with sourceId 'd8' + equals(c.length, 1, "there is one connection in 'testScope' from d8"); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by scope, source id and target id)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope'}); + var c = _jsPlumb.getConnections({scope:'testScope', source:'d11', target:'d13'}); + equals(c.length, 1, "there is one connection from d11 to d13"); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = _jsPlumb.getConnections({scope:['testScope','testScope3']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes and source ids)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = _jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes, source ids and target ids)', function() { + _jsPlumb.detachEveryConnection(); + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'), d14=_addDiv("d14"), d15=_addDiv("d15"); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d11, target:d14, scope:'testScope'}); + _jsPlumb.connect({source:d11, target:d15, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + assertContextSize(18); + var c = _jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11'], target:['d14', 'd15']}); + equals(c['testScope'].length, 2, 'there are two connections in testScope'); + equals(c['testScope3'], null, 'there are no connections in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + var anEntry = c['testScope'][0]; + ok(anEntry.endpoints[0] != null, "Source endpoint is set"); + ok(anEntry.endpoints[1] != null, "Target endpoint is set"); + equals(anEntry.source.attr("id"), "d11", "Source is div d11"); + equals(anEntry.target.attr("id"), "d14", "Target is div d14"); + }); + + test(renderMode + ': getEndpoints, one Endpoint added by addEndpoint, get Endpoints by selector', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + _jsPlumb.addEndpoint(d1); + var e = _jsPlumb.getEndpoints(d1); + equals(e.length, 1, "there is one endpoint for element d1"); + }); + + test(renderMode + ': getEndpoints, one Endpoint added by addEndpoint, get Endpoints by id', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + _jsPlumb.addEndpoint(d1); + var e = _jsPlumb.getEndpoints("d1"); + equals(e.length, 1, "there is one endpoint for element d1"); + }); + + test(renderMode + ': connection event listeners', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null/*, returnedParams2 = null*/; + _jsPlumb.bind("jsPlumbConnection", function(params) { + returnedParams = $.extend({}, params); + }); + var c = _jsPlumb.connect({source:d1, target:d2}); + ok(returnedParams != null, "new connection listener event was fired"); + ok(returnedParams.connection != null, 'connection is set'); + equals(returnedParams.sourceId, "d1", 'sourceid is set'); + equals(returnedParams.targetId, "d2", 'targetid is set'); + equals(returnedParams.source.attr("id"), "d1", 'source is set'); + equals(returnedParams.target.attr("id"), "d2" , 'target is set'); + ok(returnedParams.sourceEndpoint != null, "source endpoint is not null"); + ok(returnedParams.targetEndpoint != null, "target endpoint is not null"); + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.detach(c); + ok(returnedParams.connection != null, 'connection is set'); + //_jsPlumb.detachAll("d1"); + //ok(returnedParams2 != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (detach by connection)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + ok(returnedParams.connection != null, "removed connection listener event passed in connection"); + }); + + test(renderMode + ': detach event listeners (detach by element ids)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach({source:"d1",target:"d2"}); + ok(returnedParams != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (detach by elements)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach({source:d1,target:d2}); + ok(returnedParams != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detach method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detachFrom method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachFrom(e2); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detachAll method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachAll(); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via _jsPlumb.deleteEndpoint method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + _jsPlumb.deleteEndpoint(e1); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(1); + }); + + test(renderMode + ': detach event listeners (ensure cleared by _jsPlumb.reset)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach({source:d1,target:d2, fireEvent:true}); + ok(returnedParams != null, "removed connection listener event was fired"); + returnedParams = null; + + _jsPlumb.reset(); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach(d1,d2); + ok(returnedParams == null, "connection listener was cleared by _jsPlumb.reset()"); + }); + + test(renderMode + ': connection events that throw errors', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null, returnedParams2 = null; + _jsPlumb.bind("jsPlumbConnection", function(params) { + returnedParams = $.extend({}, params); + throw "oh no!"; + }); + _jsPlumb.connect({source:d1, target:d2}); + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + _jsPlumb.connect({source:d3, target:d4}); + ok(returnedParams != null, "new connection listener event was fired; we threw an error, _jsPlumb survived."); + }); + + test(renderMode + ": Endpoint.detachFrom", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + e16.detachFrom(e17); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by _jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": Endpoint.detachFromConnection", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + e16.detachFromConnection(conn); + assertContextSize(3); // all canvases should remain; the connection was not removed. + // but endpoint e16 should have no connections now. + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": Endpoint.detachAll", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + var e16 = _jsPlumb.addEndpoint($("#d16"), {isSource:true,maxConnections:-1}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint($("#d17"), {isSource:true}); + var e18 = _jsPlumb.addEndpoint($("#d18"), {isSource:true}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e18}); + assertConnectionCount(e16, 2); + assertConnectionCount(e17, 1); + assertConnectionCount(e18, 1); + assertContextSize(5); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 2, _jsPlumb); + e16.detachAll(); + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": Endpoint.detach", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + e16.detach(conn); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by _jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": Endpoint.isConnectedTo", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.isConnectedTo(e17), true, "e16 and e17 are connected"); + }); + + test(renderMode + ": setting endpoint uuid", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + equals(e16.getUuid(), uuid, "endpoint's uuid was set correctly"); + }); + + test(renderMode + ": _jsPlumb.getEndpoint (by uuid)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by uuid, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + _jsPlumb.deleteEndpoint(uuid); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + var ebe = _jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by uuid, connections too)", function() { + // create two endpoints (one with a uuid), add them to two divs and then connect them. + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + // check that the connection was ok. + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + // delete the endpoint that has a uuid. verify that the endpoint cannot be retrieved and that the connection has been removed, but that + // element d17 still has its Endpoint. + _jsPlumb.deleteEndpoint(uuid); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + var ebe = _jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + ebe = _jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 1, "element d17 still has its Endpoint"); + assertContextSize(1); + + // now delete d17's endpoint and check that it has gone. + _jsPlumb.deleteEndpoint(e17); + f = _jsPlumb.getEndpoint(e17); + equals(f, null, "endpoint has been deleted"); + ebe = _jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 0, "element d17 no longer has any Endpoints"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by reference, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + _jsPlumb.deleteEndpoint(e16); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by reference, connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + _jsPlumb.deleteEndpoint(e16); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + assertContextSize(1); + }); + + test(renderMode + ": _jsPlumb.deleteEveryEndpoint", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + assertContextSize(2); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + _jsPlumb.deleteEveryEndpoint(); + + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = _jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEveryEndpoint (connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + _jsPlumb.deleteEveryEndpoint(); + + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = _jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); // no canvases. so all connection canvases have been cleaned up too. + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchor:[0,0.5,0,-1]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:"TopCenter"}); + assertContextSize(2); + equals(e16.anchor.x, 0); + equals(e16.anchor.y, 0.5); + equals(e17.anchor.x, 0.5); + equals(e17.anchor.y, 0); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (tooltip)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {tooltip:"FOO"}), + e17 = _jsPlumb.addEndpoint(d17, {tooltip:"BAZ"}); + assertContextSize(2); + equals(e16.canvas.getAttribute("title"), "FOO"); + equals(e17.canvas.getAttribute("title"), "BAZ"); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case, dynamic anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + assertContextSize(2); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case, two arg method)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchor:[0,0.5,0,-1]}); + var e17 = _jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchor:"TopCenter"}); + assertContextSize(2); + equals(e16.anchor.x, 0); + equals(e16.anchor.y, 0.5); + equals(false, e16.isTarget); + equals(true, e16.isSource); + equals(e17.anchor.x, 0.5); + equals(e17.anchor.y, 0); + equals(true, e17.isTarget); + equals(false, e17.isSource); + }); + + + test(renderMode + ": _jsPlumb.addEndpoints (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false, anchor:[0,0.5,0,-1] }, { isTarget:true, isSource:false, anchor:"TopCenter" }]); + assertContextSize(2); + equals(e16[0].anchor.x, 0); + equals(e16[0].anchor.y, 0.5); + equals(false, e16[0].isTarget); + equals(true, e16[0].isSource); + equals(e16[1].anchor.x, 0.5); + equals(e16[1].anchor.y, 0); + equals(true, e16[1].isTarget); + equals(false, e16[1].isSource); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (with reference params)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var refParams = {anchor:"RightMiddle"}; + var e16 = _jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false}, { isTarget:true, isSource:false }], refParams); + assertContextSize(2); + equals(e16[0].anchor.x, 1); + equals(e16[0].anchor.y, 0.5); + equals(false, e16[0].isTarget); + equals(true, e16[0].isSource); + equals(e16[1].anchor.x, 1); + equals(e16[1].anchor.y, 0.5); + equals(true, e16[1].isTarget); + equals(false, e16[1].isSource); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case, dynamic anchors, two arg method)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchors:["TopCenter", "BottomCenter"]}); + assertContextSize(2); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (default overlays)", function() { + _jsPlumb.Defaults.Overlays = [ + [ "Label", { id:"label" } ] + ]; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16), + e2 = _jsPlumb.addEndpoint(d17); + + ok(e1.getOverlay("label") != null, "endpoint 1 has overlay from defaults"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (default overlays)", function() { + _jsPlumb.Defaults.Overlays = [ + [ "Label", { id:"label" } ] + ]; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16, { + overlays:[ + ["Label", { id:"label2", location:[ 0.5, 1 ] } ] + ] + }), + e2 = _jsPlumb.addEndpoint(d17); + + ok(e1.getOverlay("label") != null, "endpoint 1 has overlay from defaults"); + ok(e1.getOverlay("label2") != null, "endpoint 1 has overlay from addEndpoint call"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (end point set label)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16), + e2 = _jsPlumb.addEndpoint(d17); + + e1.setLabel("FOO"); + equals(e1.getLabel(), "FOO", "endpoint's label is correct"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (end point set label in constructor, as string)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16, {label:"FOO"}), + e2 = _jsPlumb.addEndpoint(d17); + + equals(e1.getLabel(), "FOO", "endpoint's label is correct"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (end point set label in constructor, as function)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16, {label:function() { return "BAZ"; }, labelLocation:0.1}), + e2 = _jsPlumb.addEndpoint(d17); + + equals(e1.getLabel()(), "BAZ", "endpoint's label is correct"); + equals(e1.getLabelOverlay().getLocation(), 0.1, "endpoint's label's location is correct"); + }); + + test(renderMode + ": _jsPlumb.makeTarget (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.makeTarget (specify two divs in an array)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.makeTarget([d16, d17], { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.makeTarget (specify two divs by id in an array)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.makeTarget(["d16", "d17"], { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.makeTarget (specify divs by selector)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + d16.addClass("FOO");d17.addClass("FOO"); + _jsPlumb.makeTarget($(".FOO"), { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ': _jsPlumb.connect (between two Endpoints)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1, _jsPlumb); // no new endpoint should have been added + assertEndpointCount("d2", 1, _jsPlumb); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. + ok(c.id != null, "connection has had an id assigned"); + }); + + test(renderMode + ': _jsPlumb.connect (Endpoint connectorTooltip parameter)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1, {connectorTooltip:"FOO"}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + equals(c.connector.canvas.getAttribute("title"), "FOO", "connector canvas has label attribute set"); + }); + + test(renderMode + ': _jsPlumb.connect (tooltip parameter)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2, tooltip:"FOO"}); + equals(c.connector.canvas.getAttribute("title"), "FOO", "connector canvas has label attribute set"); + }); + + test(renderMode + ': _jsPlumb.connect (between two Endpoints, and dont supply any parameters to the Endpoints.)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1, _jsPlumb); // no new endpoint should have been added + assertEndpointCount("d2", 1, _jsPlumb); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. + }); + + test(renderMode + " : _jsPlumb.connect, passing 'anchors' array" ,function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, anchors:["LeftMiddle", "RightMiddle"]}); + + equals(c.endpoints[0].anchor.x, 0, "source anchor is at x=0"); + equals(c.endpoints[0].anchor.y, 0.5, "source anchor is at y=0.5"); + equals(c.endpoints[1].anchor.x, 1, "target anchor is at x=1"); + equals(c.endpoints[1].anchor.y, 0.5, "target anchor is at y=0.5"); + }); + + test(renderMode + ': _jsPlumb.connect (by endpoint)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + }); + + test(renderMode + ': _jsPlumb.connect (cost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17, cost:567}); + assertContextSize(3); + equals(c.getCost(), 567, "connection cost is 567"); + }); + + test(renderMode + ': _jsPlumb.connect (default cost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), undefined, "default connection cost is 1"); + }); + + test(renderMode + ': _jsPlumb.connect (set cost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), undefined, "default connection cost is 1"); + c.setCost(8989); + equals(c.getCost(), 8989, "connection cost is 8989"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (connectionCost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionCost:567}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), 567, "connection cost is 567"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (change connectionCost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionCost:567, maxConnections:-1}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1}); + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), 567, "connection cost is 567"); + e16.setConnectionCost(23); + var c2 = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(c2.getCost(), 23, "connection cost is 23 after change on endpoint"); + }); + + test(renderMode + ': _jsPlumb.connect (default bidirectional)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true}), + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.isBidirectional(), false, "default connection is not bidirectional"); + }); + + test(renderMode + ': _jsPlumb.connect (bidirectional true)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true}), + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17, bidirectional:true}); + assertContextSize(3); + equals(c.isBidirectional(), true, "connection is bidirectional"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (connectionsBidirectional)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionsBidirectional:true, maxConnections:-1}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.isBidirectional(), true, "connection is bidrectional"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (change connectionsBidirectional)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionsBidirectional:true, maxConnections:-1}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1}); + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.isBidirectional(), true, "connection is bidirectional"); + e16.setConnectionsBidirectional(false); + var c2 = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(c2.isBidirectional(), false, "connection is not bidirectional"); + }); + + test(renderMode + ": _jsPlumb.connect (two Endpoints - that have been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = _jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1, uuid:srcEndpointUuid}); + var e2 = _jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1, uuid:dstEndpointUuid}); + _jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ] }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertContextSize(3); + }); + + test(renderMode + ": _jsPlumb.connect (two Endpoints - that have not been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ], source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + var e1 = _jsPlumb.getEndpoint(srcEndpointUuid); + ok(e1 != null, "endpoint with src uuid added"); + ok(e1.canvas != null); + var e2 = _jsPlumb.getEndpoint(dstEndpointUuid); + ok(e2 != null, "endpoint with target uuid added"); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.connect (two Endpoints - that have been already added - by endpoint reference)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = _jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1}); + var e2 = _jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1}); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.connect (two elements)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.connect({ source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, straight)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, bezier, no params)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Bezier" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 150, "Bezier connector chose 150 curviness"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, bezier, curviness as int)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", { curviness:200 }] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Canvas Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 200, "Bezier connector chose 200 curviness"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, bezier, curviness as named option)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", {curviness:300}] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 300, "Bezier connector chose 300 curviness"); + }); + + test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Canvas Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as strings)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:["LeftMiddle", "RightMiddle"] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.5, conn.endpoints[0].anchor.y, "source anchor y"); + equals(1, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.5, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + + test(renderMode + ": _jsPlumb.connect (two argument method in which some data is reused across connections)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"), d19 = _addDiv("d19"); + var sharedData = { connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }; + var conn = _jsPlumb.connect({ source:d16, target:d17}, sharedData); + var conn2 = _jsPlumb.connect({ source:d18, target:d19}, sharedData); + assertContextSize(6); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 2, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(conn2.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + equals(0.3, conn2.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn2.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn2.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn2.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoint test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoints test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Dot, "Dot endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Blank Endpoint specified via 'endpoint' param)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Blank" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Blank Endpoint specified via 'endpoints' param)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Blank", "Blank" ] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Dot, "Dot endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, connector test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {}); + var e17 = _jsPlumb.addEndpoint(d17, {}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {}); + var e17 = _jsPlumb.addEndpoint(d17, {}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, anchors as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = "TopCenter", a17 = "BottomCenter"; + var e16 = _jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, 0.5, "endpoint 16 is at top center");equals(e16.anchor.y, 0, "endpoint 16 is at top center"); + equals(e17.anchor.x, 0.5, "endpoint 17 is at bottom center");equals(e17.anchor.y, 1, "endpoint 17 is at bottom center"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = [0,0.5,0,-1], a17 = [1,0.0,-1,-1]; + var e16 = _jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, a16[0]);equals(e16.anchor.y, a16[1]); + equals(e17.anchor.x, a17[0]);equals(e17.anchor.y, a17[1]); + equals(e16.anchor.getOrientation()[0], a16[2]); equals(e16.anchor.getOrientation()[1], a16[3]); + equals(e17.anchor.getOrientation()[0], a17[2]); equals(e17.anchor.getOrientation()[1], a17[3]); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchor')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchor:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:["TopCenter", "BottomCenter"]}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchors')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, default endpoint, supplied dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var anchors = [ [0.25, 0, 0, -1], [1, 0.25, 1, 0], [0.75, 1, 0, 1], [0, 0.75, -1, 0] ]; + _jsPlumb.connect({source:d1, target:d2, dynamicAnchors:anchors}); // auto connect with default endpoint and provided anchors + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.detach({source:d1, target:d2}); + // this changed in 1.3.5, because auto generated endpoints are now removed by detach. so i added the test below this one + // to check that the deleteEndpointsOnDetach flag is honoured. + assertEndpointCount("d1", 0, _jsPlumb);assertEndpointCount("d2", 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, default endpoint, supplied dynamic anchors, delete on detach false)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var anchors = [ [0.25, 0, 0, -1], [1, 0.25, 1, 0], [0.75, 1, 0, 1], [0, 0.75, -1, 0] ]; + _jsPlumb.connect({ + source:d1, + target:d2, + dynamicAnchors:anchors, + deleteEndpointsOnDetach:false + }); // auto connect with default endpoint and provided anchors + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.detach({source:d1, target:d2}); + // this changed in 1.3.5, because auto generated endpoints are now removed by detach. so i added this test + // to check that the deleteEndpointsOnDetach flag is honoured. + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, supplied endpoint and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = _jsPlumb.addEndpoint(d1, endpoint); + var e2 = _jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(3); + _jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, supplied endpoints using 'source' and 'target' (this test is identical to the one above apart from the param names), and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = _jsPlumb.addEndpoint(d1, endpoint); + var e2 = _jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + _jsPlumb.connect({source:e1, target:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(3); + _jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + " detachable parameter defaults to true on _jsPlumb.connect", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), true, "connections detachable by default"); + }); + + test(renderMode + " detachable parameter set to false on _jsPlumb.connect", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, detachable:false}); + equals(c.isDetachable(), false, "connection detachable"); + }); + + test(renderMode + " setDetachable on initially detachable connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), true, "connection initially detachable"); + c.setDetachable(false); + equals(c.isDetachable(), false, "connection not detachable"); + }); + + test(renderMode + " setDetachable on initially not detachable connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, detachable:false }); + equals(c.isDetachable(), false, "connection not initially detachable"); + c.setDetachable(true); + equals(c.isDetachable(), true, "connection now detachable"); + }); + + test(renderMode + " _jsPlumb.Defaults.ConnectionsDetachable", function() { + _jsPlumb.Defaults.ConnectionsDetachable = false; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), false, "connections not detachable by default (overrode the defaults)"); + _jsPlumb.Defaults.ConnectionsDetachable = true; + }); + + + test(renderMode + ": _jsPlumb.connect (testing for connection event callback)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var connectCallback = null, detachCallback = null; + _jsPlumb.bind("jsPlumbConnection", function(params) { + connectCallback = $.extend({}, params); + }); + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + detachCallback = $.extend({}, params); + }); + _jsPlumb.connect({source:d1, target:d2}); // auto connect with default endpoint and anchor set + ok(connectCallback != null, "connect callback was made"); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.detach({source:d1, target:d2}); + assertContextSize(2); + ok(detachCallback != null, "detach callback was made"); + }); + + test(renderMode + ": _jsPlumb.connect (setting cssClass on Connector)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2,cssClass:"CSS"}); + var has = function(clazz) { + var cn = c.connector.canvas.className, + cns = cn.constructor == String ? cn : cn.baseVal; + + return cns.indexOf(clazz) != -1; + }; + ok(has("CSS"), "custom cssClass set correctly"); + ok(has(_jsPlumb.connectorClass), "basic connector class set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (overlays, long-hand version)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = {width:40,length:40,location:0.7, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"}}; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + }); + + test(renderMode + ": _jsPlumb.connect (overlays, long-hand version, IDs specified)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + equals("aLabel", connection1.overlays[0].id); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + equals("anArrow", connection1.overlays[1].id); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays)", function() { + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays + overlays specified in connect call)", function() { + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, overlays:[ + ["Label", {id:"label"}] + ]}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("label") != null, "Label overlay created from connect call"); + }); + + test(renderMode + ": _jsPlumb.connect (default connection overlays)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + }); + + test(renderMode + ": _jsPlumb.connect (default connection overlays + overlays specified in connect call)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, overlays:[ + ["Label", {id:"label"}] + ]}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("label") != null, "Label overlay created from connect call"); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays + default connection overlays)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow2" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("arrow2") != null, "Arrow overlay created from connection defaults"); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays + default connection overlays)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow2" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, overlays:[ + ["Label", {id:"label"}] + ]}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("arrow2") != null, "Arrow overlay created from connection defaults"); + ok(c.getOverlay("label") != null, "Label overlay created from connect call"); + }); + + test(renderMode + ": _jsPlumb.connect (label overlay set using 'label')", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO" + }); + + equals(c.getLabel(), "FOO", "label is set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction, with string)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2}); + + c.setLabel("FOO"); + equals(c.getLabel(), "FOO", "label is set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction, with function)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2}); + + c.setLabel(function() { return "BAR"; }); + equals(c.getLabel()(), "BAR", "label is set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction, with params object)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2}); + + c.setLabel({ + label:"BAZ", + cssClass:"CLASSY", + location:0.9 + }); + var lo = c.getLabelOverlay(); + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "BAZ", "label overlay has correct value"); + equals(lo.getLocation(), 0.9, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction with existing label set, with params object)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO", + labelLocation:0.2 + }); + + var lo = c.getLabelOverlay(); + equals(lo.getLocation(), 0.2, "label overlay has correct location"); + + c.setLabel({ + label:"BAZ", + cssClass:"CLASSY", + location:0.9 + }); + + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "BAZ", "label overlay has correct value"); + equals(lo.getLocation(), 0.9, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (getLabelOverlay, label on connect call)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO" + }), + lo = c.getLabelOverlay(); + + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "FOO", "label overlay has correct value"); + equals(lo.getLocation(), 0.5, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (getLabelOverlay, label on connect call, location set)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO", + labelLocation:0.2 + }), + lo = c.getLabelOverlay(); + + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "FOO", "label overlay has correct value"); + equals(lo.getLocation(), 0.2, "label overlay has correct location"); + }); + + + test(renderMode + ": _jsPlumb.connect (remove single overlay by id)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + connection1.removeOverlay("aLabel"); + equals(1, connection1.overlays.length); + equals("anArrow", connection1.overlays[0].id); + }); + + test(renderMode + ": _jsPlumb.connect (remove multiple overlays by id)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + connection1.removeOverlays("aLabel", "anArrow"); + equals(0, connection1.overlays.length); + }); + + test(renderMode + ": _jsPlumb.connect (overlays, short-hand version)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var loc = { location:0.7 }; + var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label", {label:"CONNECTION 1", location:0.3}], + ["Arrow", arrowSpec, loc] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + }); + + test(renderMode + ": _jsPlumb.connect (removeAllOverlays)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var loc = { location:0.7 }; + var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label", {label:"CONNECTION 1", location:0.3}], + ["Arrow", arrowSpec, loc] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + + connection1.removeAllOverlays(); + equals(0, connection1.overlays.length); + }); + + test(renderMode + ": _jsPlumb.connect, specify arrow overlay using string identifier only", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:["Arrow"]}); + equals(jsPlumb.Overlays[renderMode].Arrow, conn.overlays[0].constructor); + }); + + test(renderMode + ": Connection.getOverlay method, existing overlay", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("arrowOverlay"); + ok(overlay != null); + }); + + test(renderMode + ": Connection.getOverlay method, non-existent overlay", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("IDONTEXIST"); + ok(overlay == null); + }); + + test(renderMode + ": Overlay.setVisible method", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("arrowOverlay"); + ok(overlay.isVisible()); + overlay.setVisible(false); + ok(!overlay.isVisible()); + overlay.setVisible(true); + ok(overlay.isVisible()); + }); + + // this test is for the original detach function; it should stay working after i mess with it + // a little. + test(renderMode + ": _jsPlumb.detach (by element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + var e3 = _jsPlumb.addEndpoint(d1); + var e4 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + _jsPlumb.connect({ sourceEndpoint:e3, targetEndpoint:e4 }); // make two connections to be sure this works ;) + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionCount(e3, 1); + assertConnectionCount(e4, 1); + + _jsPlumb.detach({source:"d1", target:"d2"}); + + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionCount(e3, 0); + assertConnectionCount(e4, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + // detach is being made to operate more like connect - by taking one argument with a whole + // bunch of possible params in it. if two args are passed in it will continue working + // in the old way. + test(renderMode + ": _jsPlumb.detach (params object, using element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({source:"d1", target:"d2"}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); +/* + test(renderMode + ": _jsPlumb.detach (params object, using target only)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e1 = _jsPlumb.addEndpoint(d1, {maxConnections:2}), + e2 = _jsPlumb.addEndpoint(d2), + e3 = _jsPlumb.addEndpoint(d3); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e3 }); + assertConnectionCount(e1, 2); + assertConnectionCount(e2, 1); + assertConnectionCount(e3, 1); + _jsPlumb.detach({target:"d2"}); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 0); + assertConnectionCount(e3, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1); + }); +*/ + test(renderMode + ": _jsPlumb.detach (params object, using element objects)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({source:d1, target:d2}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.detach (source and target as endpoint UUIDs)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {uuid:"abcdefg"}); + ok(_jsPlumb.getEndpoint("abcdefg") != null, "e1 exists"); + var e2 = _jsPlumb.addEndpoint(d2, {uuid:"hijklmn"}); + ok(_jsPlumb.getEndpoint("hijklmn") != null, "e2 exists"); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({uuids:["abcdefg", "hijklmn"]}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.detach (sourceEndpoint and targetEndpoint supplied)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.makeDynamicAnchors (longhand)", function() { + var anchors = [_jsPlumb.makeAnchor([0.2, 0, 0, -1], null, _jsPlumb), _jsPlumb.makeAnchor([1, 0.2, 1, 0], null, _jsPlumb), + _jsPlumb.makeAnchor([0.8, 1, 0, 1], null, _jsPlumb), _jsPlumb.makeAnchor([0, 0.8, -1, 0], null, _jsPlumb) ]; + var dynamicAnchor = _jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); + }); + + test(renderMode + ": _jsPlumb.makeDynamicAnchors (shorthand)", function() { + var anchors = [[0.2, 0, 0, -1], [1, 0.2, 1, 0], + [0.8, 1, 0, 1], [0, 0.8, -1, 0] ]; + var dynamicAnchor = _jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); + }); + + test(renderMode + ": Connection.isVisible/setVisible", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c1 = _jsPlumb.connect({source:d1,target:d2}); + equals(true, c1.isVisible(), "Connection is visible after creation."); + c1.setVisible(false); + equals(false, c1.isVisible(), "Connection is not visible after calling setVisible(false)."); + equals($(c1.connector.canvas).css("display"), "none"); + c1.setVisible(true); + equals(true, c1.isVisible(), "Connection is visible after calling setVisible(true)."); + equals($(c1.connector.canvas).css("display"), "block"); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible basic test (no connections)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals($(e1.canvas).css("display"), "none"); + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals($(e1.canvas).css("display"), "block"); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should track changes in the source, because it has only this connection.)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + var c1 = _jsPlumb.connect({source:e1, target:e2}); + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals(false, e2.isVisible(), "other Endpoint is not visible either."); + equals(false, c1.isVisible(), "connection between the two is not visible either."); + + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals(true, e2.isVisible(), "other Endpoint is visible too"); + equals(true, c1.isVisible(), "connection between the two is visible too."); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should not track changes in the source, because it has another connection.)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2, { maxConnections:2 }), e3 = _jsPlumb.addEndpoint(d3); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + var c1 = _jsPlumb.connect({source:e1, target:e2}); + var c2 = _jsPlumb.connect({source:e2, target:e3}); + + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals(true, e2.isVisible(), "other Endpoint should still be visible."); + equals(true, e3.isVisible(), "third Endpoint should still be visible."); + equals(false, c1.isVisible(), "connection between the two is not visible either."); + equals(true, c2.isVisible(), "other connection is visible."); + + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals(true, e2.isVisible(), "other Endpoint is visible too"); + equals(true, c1.isVisible(), "connection between the two is visible too."); + equals(true, c2.isVisible(), "other connection is visible."); + }); + + // tests of the functionality that allows a user to specify that they want elements appended to the document body + test(renderMode + " _jsPlumb.Defaults.Container, specified with a selector", function() { + _jsPlumb.Defaults.Container = $("body"); + equals($("#container")[0].childNodes.length, 0, "container has no nodes"); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals($("#container")[0].childNodes.length, 2, "container has two div elements"); // the divs we added have been added to the 'container' div. + // but we have told _jsPlumb to add its canvas to the body, so this connect call should not add another few elements to the container: + _jsPlumb.connect({source:d1, target:d2}); + equals($("#container")[0].childNodes.length, 2, "container still has two div elements"); + }); + + // tests of the functionality that allows a user to specify that they want elements appended to some specific container. + test(renderMode + " _jsPlumb.Defaults.Container, specified with DOM element", function() { + _jsPlumb.Defaults.Container = document.getElementsByTagName("body")[0]; + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length, "two divs added to the container"); // the divs we added have been added to the 'container' div. + // but we have told _jsPlumb to add its canvas to the body, so this connect call should not add another few elements to the container: + var bodyElementCount = $("body")[0].childNodes.length; + _jsPlumb.connect({source:d1, target:d2}); + equals(2, $("#container")[0].childNodes.length, "still only two children in container; elements were added to the body by _jsPlumb"); + // test to see if 3 elements have been added + equals(bodyElementCount + 3, $("body")[0].childNodes.length, "3 new elements added to the document body"); + }); + + test(renderMode + " container specified to connect call, with a selector", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length); // the divs we added have been added to the 'container' div. + var bodyElementCount = $("body")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to the body, so this connect call should not add another few elements to the container: + _jsPlumb.connect({source:d1, target:d2, container:$("body")}); + equals(2, $("#container")[0].childNodes.length); + equals(bodyElementCount + 3, $("body")[0].childNodes.length, "3 new elements added to the document body"); + }); + + test(renderMode + " container specified to connect call, with a string ID", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + equals(3, $("#container")[0].childNodes.length, "container has divs we added"); // the divs we added have been added to the 'container' div. + var d3ElementCount = $("#d3")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to "d3", so this connect call should not add another few elements to the container: + _jsPlumb.connect({source:d1, target:d2, container:"d3"}); + equals(3, $("#container")[0].childNodes.length, "container still has only the divs we added"); + equals(d3ElementCount + 3, $("#d3")[0].childNodes.length, "3 new elements added to div d3"); + }); + + test(renderMode + " container specified to addEndpoint call, with a selector", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length); // the divs we added have been added to the 'container' div. + var bodyElementCount = $("body")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to the body, so this connect call should not add another few elements to the container: + _jsPlumb.addEndpoint(d1, {container:$("body")}); + equals(2, $("#container")[0].childNodes.length); + equals(bodyElementCount + 1, $("body")[0].childNodes.length, "1 new element added to the document body"); + }); + + test(renderMode + " container specified to addEndpoint call, with a string ID", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + equals(3, $("#container")[0].childNodes.length, "container has divs we added"); // the divs we added have been added to the 'container' div. + var d3ElementCount = $("#d3")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to "d3", so this connect call should not add another few elements to the container: + _jsPlumb.addEndpoint(d1, { container:"d3" }); + equals(3, $("#container")[0].childNodes.length, "container still has only the divs we added"); + equals(d3ElementCount + 1, $("#d3")[0].childNodes.length, "1 new element added to div d3"); + }); + + test(renderMode + " detachable defaults to true when connection made between two endpoints", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2), + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection not detachable"); + }); + + test(renderMode + " connection detachable when target endpoint has connectionsDetachable set to true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2, {connectionsDetachable:true}), + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection detachable because connectionsDetachable was set on target endpoint"); + }); + + test(renderMode + " connection detachable when source endpoint has connectionsDetachable set to true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {connectionsDetachable:true}), e2 = _jsPlumb.addEndpoint(d2), + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection detachable because connectionsDetachable was set on source endpoint"); + }); + + test(renderMode + " Connector has 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.connector.type, "Bezier", "Bezier connector has type set"); + + var c2 = _jsPlumb.connect({source:d1, target:d2, connector:"Straight"}); + equals(c2.connector.type, "Straight", "Straight connector has type set"); + + var c3 = _jsPlumb.connect({source:d1, target:d2, connector:"Flowchart"}); + equals(c3.connector.type, "Flowchart", "Flowchart connector has type set"); + }); + + test(renderMode + " Endpoints have 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.endpoints[0].type, "Dot", "Dot endpoint has type set"); + + var c2 = _jsPlumb.connect({source:d1, target:d2, endpoints:["Rectangle", "Blank"]}); + equals(c2.endpoints[1].type, "Blank", "Blank endpoint has type set"); + equals(c2.endpoints[0].type, "Rectangle", "Rectangle endpoint has type set"); + }); + + test(renderMode + " Overlays have 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = _jsPlumb.connect({ + source:d1, + target:d2, + overlays:[ "Arrow", "Label", "PlainArrow", "Diamond" ] + }); + equals(c.overlays[0].type, "Arrow", "Arrow overlay has type set"); + equals(c.overlays[1].type, "Label", "Label overlay has type set"); + equals(c.overlays[2].type, "PlainArrow", "PlainArrow overlay has type set"); + equals(c.overlays[3].type, "Diamond", "Diamond overlay has type set"); + }); + + test(renderMode + " _jsPlumb.hide, original one-arg version", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + _jsPlumb.hide(d1); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(true, e1.isVisible(), "endpoint 1 is still visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + _jsPlumb.show(d1); + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + }); + + test(renderMode + " _jsPlumb.hide, two-arg version, endpoints should also be hidden", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + _jsPlumb.show(d1); // now show d1, but do not alter the endpoints. e1 should still be hidden + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + test(renderMode + " _jsPlumb.show, two-arg version, endpoints should become visible", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + _jsPlumb.show(d1, true); // now show d1, and alter the endpoints. e1 should be visible. + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(true, e1.isVisible(), "endpoint 1 is visible again."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + test(renderMode + " _jsPlumb.show, two-arg version, endpoints should become visible, but not all connections, because some other endpoints are not visible.", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e11 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + e3 = _jsPlumb.addEndpoint(d3, e), + c1 = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e11, target:e3}); + + // we now have d1 connected to both d3 and d2. we'll hide d1, and everything on d1 should be hidden. + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "connection 1 is no longer visible."); + equals(false, c2.isVisible(), "connection 2 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(false, e11.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + equals(true, e3.isVisible(), "endpoint 3 is still visible."); + + // now, we will also hide d3. making d1 visible again should NOT result in c2 becoming visible, because the other endpoint + // for c2 is e3, which is not visible. + _jsPlumb.hide(d3, true); + equals(false, e3.isVisible(), "endpoint 3 is no longer visible."); + + _jsPlumb.show(d1, true); // now show d1, and alter the endpoints. e1 should be visible, c1 should be visible, but c2 should not. + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(false, c2.isVisible(), "Connection 2 is not visible."); + equals(true, e1.isVisible(), "endpoint 1 is visible again."); + equals(true, e11.isVisible(), "endpoint 11 is visible again."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + equals(false, e3.isVisible(), "endpoint 3 is still not visible."); + }); + + /* + test(renderMode + " _jsPlumb.hide, two-arg version, endpoints should also be hidden", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + /** + * test for issue 132: label leaves its element in the DOM after it has been + * removed from a connection. + */ + test(renderMode + " label cleans itself up properly", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label", cssClass:"foo"}] + ]}); + ok($(".foo").length == 1, "label element exists in DOM"); + c.removeOverlay("label"); + ok($(".foo").length == 0, "label element does not exist in DOM"); + }); + + test(renderMode + " arrow cleans itself up properly", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Arrow", {id:"arrow"}] + ]}); + ok(c.getOverlay("arrow") != null, "arrow overlay exists"); + c.removeOverlay("arrow"); + ok(c.getOverlay("arrow") == null, "arrow overlay has been removed"); + }); + + test(renderMode + " label overlay getElement function", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label"}] + ]}); + ok(c.getOverlay("label").getElement() != null, "label overlay exposes element via getElement method"); + }); + + test(renderMode + " label overlay provides getLabel and setLabel methods", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label", label:"foo"}] + ]}); + var o = c.getOverlay("label"), e = o.getElement(); + ok(e.innerHTML == "foo", "label text is set to original value"); + o.setLabel("baz"); + ok(e.innerHTML == "baz", "label text is set to new value 'baz'"); + ok(o.getLabel() === "baz", "getLabel function works correctly with String"); + // now try functions + var aFunction = function() { return "aFunction"; }; + o.setLabel(aFunction); + ok(e.innerHTML == "aFunction", "label text is set to new value from Function"); + ok(o.getLabel() === aFunction, "getLabel function works correctly with Function"); + }); + + test(renderMode + " parameters object works for Endpoint", function() { + var d1 = _addDiv("d1"), + f = function() { alert("FOO!"); }, + e = _jsPlumb.addEndpoint(d1, { + isSource:true, + parameters:{ + "string":"param1", + "int":4, + "function":f + } + }); + ok(e.getParameter("string") === "param1", "getParameter(String) works correctly"); + ok(e.getParameter("int") === 4, "getParameter(int) works correctly"); + ok(e.getParameter("function") == f, "getParameter(Function) works correctly"); + }); + + test(renderMode + " parameters object works for Connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + f = function() { alert("FOO!"); }; + var c = _jsPlumb.connect({ + source:d1, + target:d2, + parameters:{ + "string":"param1", + "int":4, + "function":f + } + }); + ok(c.getParameter("string") === "param1", "getParameter(String) works correctly"); + ok(c.getParameter("int") === 4, "getParameter(int) works correctly"); + ok(c.getParameter("function") == f, "getParameter(Function) works correctly"); + }); + + test(renderMode + " parameters set on Endpoints and Connections are all merged, and merged correctly at that.", function() { + var d1 = _addDiv("d1"), + d2 = _addDiv("d2"), + e = _jsPlumb.addEndpoint(d1, { + isSource:true, + parameters:{ + "string":"sourceEndpoint", + "int":0, + "function":function() { return "sourceEndpoint"; } + } + }), + e2 = _jsPlumb.addEndpoint(d2, { + isTarget:true, + parameters:{ + "int":1, + "function":function() { return "targetEndpoint"; } + } + }), + c = _jsPlumb.connect({source:e, target:e2, parameters:{ + "function":function() { return "connection"; } + }}); + + ok(c.getParameter("string") === "sourceEndpoint", "getParameter(String) works correctly"); + ok(c.getParameter("int") === 1, "getParameter(int) works correctly"); + ok(c.getParameter("function")() == "connection", "getParameter(Function) works correctly"); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers standard connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2}); + equals(_jsPlumb.anchorManager.get("d1")["standard"].length, 1); + equals(_jsPlumb.anchorManager.get("d1")["endpoints"].length, 1); + equals(_jsPlumb.anchorManager.get("d2")["standard"].length, 1); + equals(_jsPlumb.anchorManager.get("d2")["endpoints"].length, 1); + var c2 = _jsPlumb.connect({source:d1, target:d2}); + equals(_jsPlumb.anchorManager.get("d1")["standard"].length, 2); + equals(_jsPlumb.anchorManager.get("d2")["standard"].length, 2); + equals(_jsPlumb.anchorManager.get("d1")["endpoints"].length, 2); + equals(_jsPlumb.anchorManager.get("d2")["endpoints"].length, 2); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers dynamic anchor connection, and removes it.", function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var c = _jsPlumb.connect({source:d3, target:d4, anchors:["AutoDefault", "AutoDefault"]}); + + equals(_jsPlumb.anchorManager.get("d3")["standard"].length, 1); + + var c2 = _jsPlumb.connect({source:d3, target:d4}); + equals(_jsPlumb.anchorManager.get("d3")["standard"].length, 2); + equals(_jsPlumb.anchorManager.get("d4")["standard"].length, 2); + + equals(_jsPlumb.anchorManager.get("d3")["endpoints"].length, 2); + _jsPlumb.detach(c); + equals(_jsPlumb.anchorManager.get("d3")["standard"].length, 1); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers continuous anchor connection, and removes it.", function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var c = _jsPlumb.connect({source:d3, target:d4, anchors:["Continuous", "Continuous"]}); + + equals(_jsPlumb.anchorManager.get("d3")["standard"].length, 0); + equals(_jsPlumb.anchorManager.get("d3")["continuous"].length, 1); + equals(_jsPlumb.anchorManager.get("d4")["standard"].length, 0); + equals(_jsPlumb.anchorManager.get("d4")["continuous"].length, 1); + + _jsPlumb.detach(c); + equals(_jsPlumb.anchorManager.get("d3")["continuous"].length, 0); + equals(_jsPlumb.anchorManager.get("d4")["continuous"].length, 0); + + _jsPlumb.reset(); + equals(_jsPlumb.anchorManager.get("d4")["continuousAnchorEndpoints"].length, 0); + }); + +// ------------- utility functions - math stuff, mostly -------------------------- + + var tolerance = 0.00000005, withinTolerance = function(v1, v2, msg) { + if (Math.abs(v1 - v2) < tolerance) ok(true, msg + "; expected " + v1 + " and got it"); + else { + ok(false, msg + "; expected " + v1 + " got " + v2); + } + }; + test(renderMode + " jsPlumb.util.gradient, segment 1", function() { + var p1 = [2,2], p2 = [3,1], m = jsPlumb.util.gradient(p1,p2); + equals(m, -1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumb.util.normal, segment 1", function() { + var p1 = [2,2], p2 = [3,1], m = jsPlumb.util.normal(p1,p2); + equals(m, 1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumb.util.gradient, segment 2", function() { + var p1 = [2,2], p2 = [3,3], m = jsPlumb.util.gradient(p1,p2); + equals(m, 1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumb.util.normal, segment 2", function() { + var p1 = [2,2], p2 = [3,3], m = jsPlumb.util.normal(p1,p2); + equals(m, -1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumb.util.gradient, segment 3", function() { + var p1 = [2,2], p2 = [1,3], m = jsPlumb.util.gradient(p1,p2); + equals(m, -1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumb.util.normal, segment 3", function() { + var p1 = [2,2], p2 = [1,3], m = jsPlumb.util.normal(p1,p2); + equals(m, 1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumb.util.gradient, segment 4", function() { + var p1 = [2,2], p2 = [1,1], m = jsPlumb.util.gradient(p1,p2); + equals(m, 1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumb.util.normal, segment 4", function() { + var p1 = [2,2], p2 = [1,1], m = jsPlumb.util.normal(p1,p2); + equals(m, -1, "normal calculated correctly for simple case"); + }); + test(renderMode + "jsPlumb.util.pointOnLine, segment 1", function() { + var p1 = {x:2,y:2}, p2={x:3, y:1}, + target = jsPlumb.util.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumb.util.pointOnLine, segment 2", function() { + var p1 = {x:2,y:2}, p2={x:3, y:3}, + target = jsPlumb.util.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumb.util.pointOnLine, segment 3", function() { + var p1 = {x:2,y:2}, p2={x:1, y:3}, + target = jsPlumb.util.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumb.util.pointOnLine, segment 4", function() { + var p1 = {x:2,y:2}, p2={x:1, y:1}, + target = jsPlumb.util.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumb.util.perpendicularLineTo, segment 1", function() { + var p1 = {x:2, y:2}, p2={x:3, y:1}, m = jsPlumb.util.gradient(p1, p2), + l = jsPlumb.util.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(4, l[0].x, "point 1 x is correct"); + withinTolerance(2, l[0].y, "point 1 y is correct"); + + withinTolerance(2, l[1].x, "point 2 x is correct"); + withinTolerance(0, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumb.util.perpendicularLineTo, segment 2", function() { + var p1 = {x:2, y:2}, p2={x:3, y:3}, m = jsPlumb.util.gradient(p1, p2), + l = jsPlumb.util.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(4, l[0].x, "point 1 x is correct"); + withinTolerance(2, l[0].y, "point 1 y is correct"); + + withinTolerance(2, l[1].x, "point 2 x is correct"); + withinTolerance(4, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumb.util.perpendicularLineTo, segment 3", function() { + var p1 = {x:2, y:2}, p2={x:1, y:3}, m = jsPlumb.util.gradient(p1, p2), + l = jsPlumb.util.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(2, l[0].x, "point 1 x is correct"); + withinTolerance(4, l[0].y, "point 1 y is correct"); + + withinTolerance(0, l[1].x, "point 2 x is correct"); + withinTolerance(2, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumb.util.perpendicularLineTo, segment 4", function() { + var p1 = {x:2, y:2}, p2={x:1, y:1}, m = jsPlumb.util.gradient(p1, p2), + l = jsPlumb.util.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(2, l[0].x, "point 1 x is correct"); + withinTolerance(0, l[0].y, "point 1 y is correct"); + + withinTolerance(0, l[1].x, "point 2 x is correct"); + withinTolerance(2, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumb.util.setImage on Endpoint", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { + endpoint:[ "Image", { src:"../../img/endpointTest1.png" } ] + }, + ep = _jsPlumb.addEndpoint(d1, e); + ep.setImage("../../img/littledot.png"); + }); + test(renderMode + "jsPlumb.util.setImage on Endpoint, with supplied onload", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { + endpoint:[ "Image", { + src:"../../img/endpointTest1.png", + onload:function(imgEp) { + equals("../../img/endpointTest1.png", imgEp.img.src); + } + } ] + }, + ep = _jsPlumb.addEndpoint(d1, e); + ep.setImage("../../img/littledot.png", function(imgEp) { + equals("../../img/littledot.png", imgEp.img.src); + }); + }); + + test(renderMode + "attach endpoint to table when desired element was td", function() { + var table = document.createElement("table"), + tr = document.createElement("tr"), + td = document.createElement("td"); + table.appendChild(tr); tr.appendChild(td); + document.body.appendChild(table); + var ep = _jsPlumb.addEndpoint(td); + equals(ep.canvas.parentNode.tagName.toLowerCase(), "table"); + }); + +// issue 190 - regressions with getInstance. these tests ensure that generated ids are unique across +// instances. + + test(renderMode + " id clashes between instances", function() { + var d1 = document.createElement("div"), + d2 = document.createElement("div"), + _jsp2 = jsPlumb.getInstance(); + + document.body.appendChild(d1); + document.body.appendChild(d2); + + _jsPlumb.addEndpoint(d1); + _jsp2.addEndpoint(d2); + + var id1 = d1.getAttribute("id"), + id2 = d2.getAttribute("id"); + + var idx = id1.indexOf("_"), idx2 = id1.lastIndexOf("_"), v1 = id1.substring(idx, idx2); + var idx3 = id2.indexOf("_"), idx4 = id2.lastIndexOf("_"), v2 = id2.substring(idx3, idx4); + + ok (v1 != v2, "instance versions are different : " + v1 + " : " + v2); + }); + + test(renderMode + " id clashes between instances", function() { + var d1 = document.createElement("div"), + d2 = document.createElement("div"), + _jsp2 = jsPlumb.getInstance(); + + document.body.appendChild(d1); + document.body.appendChild(d2); + + _jsPlumb.addEndpoint(d1); + _jsPlumb.addEndpoint(d2); + + var id1 = d1.getAttribute("id"), + id2 = d2.getAttribute("id"); + + var idx = id1.indexOf("_"), idx2 = id1.lastIndexOf("_"), v1 = id1.substring(idx, idx2); + var idx3 = id2.indexOf("_"), idx4 = id2.lastIndexOf("_"), v2 = id2.substring(idx3, idx4); + + ok (v1 == v2, "instance versions are the same : " + v1 + " : " + v2); + }); + + + /** + * leave this test at the bottom! + */ + test(renderMode + ': unload test', function() { + _jsPlumb.unload(); + var contextNode = $(".__jsPlumb_context"); + ok(contextNode.length == 0, 'context node unloaded'); + }); +}; + diff --git a/archive/1.3.5/jsPlumb-connectors-statemachine-1.3.5-RC1.js b/archive/1.3.5/jsPlumb-connectors-statemachine-1.3.5-RC1.js new file mode 100644 index 000000000..381925a33 --- /dev/null +++ b/archive/1.3.5/jsPlumb-connectors-statemachine-1.3.5-RC1.js @@ -0,0 +1,442 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the state machine connectors. + * + * Thanks to Brainstorm Mobile Solutions for supporting the development of these. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + var Line = function(x1, y1, x2, y2) { + + this.m = (y2 - y1) / (x2 - x1); + this.b = -1 * ((this.m * x1) - y1); + + this.rectIntersect = function(x,y,w,h) { + var results = []; + + // try top face + // the equation of the top face is y = (0 * x) + b; y = b. + var xInt = (y - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try right face + var yInt = (this.m * (x + w)) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + // bottom face + var xInt = ((y + h) - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try left face + var yInt = (this.m * x) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + if (results.length == 2) { + var midx = (results[0][0] + results[1][0]) / 2, midy = (results[0][1] + results[1][1]) / 2; + results.push([ midx,midy ]); + // now calculate the segment inside the rectangle where the midpoint lies. + var xseg = midx <= x + (w / 2) ? -1 : 1, + yseg = midy <= y + (h / 2) ? -1 : 1; + results.push([xseg, yseg]); + return results; + } + + return null; + + }; + }, + _segment = function(x1, y1, x2, y2) { + if (x1 <= x2 && y2 <= y1) return 1; + else if (x1 <= x2 && y1 <= y2) return 2; + else if (x2 <= x1 && y2 >= y1) return 3; + return 4; + }, + + // the control point we will use depends on the faces to which each end of the connection is assigned, specifically whether or not the + // two faces are parallel or perpendicular. if they are parallel then the control point lies on the midpoint of the axis in which they + // are parellel and varies only in the other axis; this variation is proportional to the distance that the anchor points lie from the + // center of that face. if the two faces are perpendicular then the control point is at some distance from both the midpoints; the amount and + // direction are dependent on the orientation of the two elements. 'seg', passed in to this method, tells you which segment the target element + // lies in with respect to the source: 1 is top right, 2 is bottom right, 3 is bottom left, 4 is top left. + // + // sourcePos and targetPos are arrays of info about where on the source and target each anchor is located. their contents are: + // + // 0 - absolute x + // 1 - absolute y + // 2 - proportional x in element (0 is left edge, 1 is right edge) + // 3 - proportional y in element (0 is top edge, 1 is bottom edge) + // + _findControlPoint = function(midx, midy, segment, sourceEdge, targetEdge, dx, dy, distance, proximityLimit) { + + // TODO (maybe) + // - if anchor pos is 0.5, make the control point take into account the relative position of the elements. + if (distance <= proximityLimit) return [midx, midy]; + + if (segment == 1) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 2) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 3) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 4) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + }; + + /* + Function: StateMachine constructor + + Allowed parameters: + curviness - measure of how "curvy" the connectors will be. this is translated as the distance that the + Bezier curve's control point is from the midpoint of the straight line connecting the two + endpoints, and does not mean that the connector is this wide. The Bezier curve never reaches + its control points; they act as gravitational masses. defaults to 10. + margin - distance from element to start and end connectors, in pixels. defaults to 5. + proximityLimit - sets the distance beneath which the elements are consider too close together to bother with fancy + curves. by default this is 80 pixels. + loopbackRadius - the radius of a loopback connector. optional; defaults to 25. + */ + jsPlumb.Connectors.StateMachine = function(params) { + var self = this, + currentPoints = null, + _sx, _sy, _tx, _ty, _controlPoint = [], + curviness = params.curviness || 10, + margin = params.margin || 5, + proximityLimit = params.proximityLimit || 80, + clockwise = params.orientation && params.orientation == "clockwise", + loopbackRadius = params.loopbackRadius || 25, + isLoopback = false; + + this.type = "StateMachine"; + params = params || {}; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *= 1.9; + //ensure at least one pixel width + lineWidth = lineWidth || 1; + var x = Math.min(sourcePos[0], targetPos[0]) - xo, + y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (sourceEndpoint.elementId != targetEndpoint.elementId) { + + isLoopback = false; + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + + // now adjust for the margin + if (sourcePos[2] == 0) _sx -= margin; + if (sourcePos[2] == 1) _sx += margin; + if (sourcePos[3] == 0) _sy -= margin; + if (sourcePos[3] == 1) _sy += margin; + if (targetPos[2] == 0) _tx -= margin; + if (targetPos[2] == 1) _tx += margin; + if (targetPos[3] == 0) _ty -= margin; + if (targetPos[3] == 1) _ty += margin; + + // + // these connectors are quadratic bezier curves, having a single control point. if both anchors + // are located at 0.5 on their respective faces, the control point is set to the midpoint and you + // get a straight line. this is also the case if the two anchors are within 'proximityLimit', since + // it seems to make good aesthetic sense to do that. outside of that, the control point is positioned + // at 'curviness' pixels away along the normal to the straight line connecting the two anchors. + // + // there may be two improvements to this. firstly, we might actually support the notion of avoiding nodes + // in the UI, or at least making a good effort at doing so. if a connection would pass underneath some node, + // for example, we might increase the distance the control point is away from the midpoint in a bid to + // steer it around that node. this will work within limits, but i think those limits would also be the likely + // limits for, once again, aesthetic good sense in the layout of a chart using these connectors. + // + // the second possible change is actually two possible changes: firstly, it is possible we should gradually + // decrease the 'curviness' as the distance between the anchors decreases; start tailing it off to 0 at some + // point (which should be configurable). secondly, we might slightly increase the 'curviness' for connectors + // with respect to how far their anchor is from the center of its respective face. this could either look cool, + // or stupid, and may indeed work only in a way that is so subtle as to have been a waste of time. + // + + var _midx = (_sx + _tx) / 2, _midy = (_sy + _ty) / 2, + m2 = (-1 * _midx) / _midy, theta2 = Math.atan(m2), + dy = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.sin(theta2)), + dx = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.cos(theta2)), + segment = _segment(_sx, _sy, _tx, _ty), + distance = Math.sqrt(Math.pow(_tx - _sx, 2) + Math.pow(_ty - _sy, 2)); + + // calculate the control point. this code will be where we'll put in a rudimentary element avoidance scheme; it + // will work by extending the control point to force the curve to be, um, curvier. + _controlPoint = _findControlPoint(_midx, + _midy, + segment, + sourcePos, + targetPos, + curviness, curviness, + distance, + proximityLimit); + + + var requiredWidth = Math.max(Math.abs(_controlPoint[0] - _sx) * 3, Math.abs(_controlPoint[0] - _tx) * 3, Math.abs(_tx-_sx), 2 * lineWidth, minWidth), + requiredHeight = Math.max(Math.abs(_controlPoint[1] - _sy) * 3, Math.abs(_controlPoint[1] - _ty) * 3, Math.abs(_ty-_sy), 2 * lineWidth, minWidth); + + if (w < requiredWidth) { + var dw = requiredWidth - w; + x -= (dw / 2); + _sx += (dw / 2); + _tx += (dw / 2); + w = requiredWidth; + _controlPoint[0] += (dw / 2); + } + + if (h < requiredHeight) { + var dh = requiredHeight - h; + y -= (dh / 2); + _sy += (dh / 2); + _ty += (dh / 2); + h = requiredHeight; + _controlPoint[1] += (dh / 2); + } + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty, _controlPoint[0], _controlPoint[1] ]; + } + else { + isLoopback = true; + // a loopback connector. draw an arc from one anchor to the other. + // i guess we'll do this the same way as the others. just the control point will be a fair distance away. + var x1 = sourcePos[0], x2 = sourcePos[0], y1 = sourcePos[1] - margin, y2 = sourcePos[1] - margin, + cx = x1, cy = y1 - loopbackRadius; + + // canvas sizing stuff, to ensure the whole painted area is visible. + w = ((2 * lineWidth) + (4 * loopbackRadius)), h = ((2 * lineWidth) + (4 * loopbackRadius)); + x = cx - loopbackRadius - lineWidth - loopbackRadius, y = cy - loopbackRadius - lineWidth - loopbackRadius; + currentPoints = [ x, y, w, h, cx-x, cy-y, loopbackRadius, clockwise, x1-x, y1-y, x2-x, y2-y]; + } + + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_tx, y:_ty }, + { x:_controlPoint[0], y:_controlPoint[1] }, + { x:_controlPoint[0] + 1, y:_controlPoint[1] + 1}, + { x:_sx, y:_sy } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + if (isLoopback) { + + if (location > 0 && location < 1) location = 1- location; + +// current points are [ x, y, width, height, center x, center y, radius, clockwise, startx, starty, endx, endy ] + // so the path length is the circumference of the circle + //var len = 2 * Math.PI * currentPoints[6], + // map 'location' to an angle. 0 is PI/2 when the connector is on the top face; if we + // support other faces it will have to be calculated for each one. 1 is also PI/2. + // 0.5 is -PI/2. + var startAngle = (location * 2 * Math.PI) + (Math.PI / 2), + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + + } + else return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + if (isLoopback) + return Math.atan(location * 2 * Math.PI); + else + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + if (isLoopback) { + + if (location > 0 && location < 1) location = 1- location; + + var circumference = 2 * Math.PI * currentPoints[6], + arcSpan = distance / circumference * 2 * Math.PI, + startAngle = (location * 2 * Math.PI) - arcSpan + (Math.PI / 2), + + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + } + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + }; + + /* + * Canvas state machine renderer. + */ + jsPlumb.Connectors.canvas.StateMachine = function(params) { + params = params || {}; + var self = this, drawGuideline = params.drawGuideline || true, avoidSelector = params.avoidSelector; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.CanvasConnector.apply(this, arguments); + + + this._paint = function(dimensions) { + + if (dimensions.length == 10) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.quadraticCurveTo(dimensions[8], dimensions[9], dimensions[6], dimensions[7]); + self.ctx.stroke(); + + /*/ draw the guideline + if (drawGuideline) { + self.ctx.save(); + self.ctx.beginPath(); + self.ctx.strokeStyle = "silver"; + self.ctx.lineWidth = 1; + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + self.ctx.restore(); + } + //*/ + } + else { + // a loopback connector + self.ctx.save(); + self.ctx.beginPath(); + var startAngle = 0, // Starting point on circle + endAngle = 2 * Math.PI, // End point on circle + clockwise = dimensions[7]; // clockwise or anticlockwise + self.ctx.arc(dimensions[4],dimensions[5],dimensions[6],0, endAngle, clockwise); + self.ctx.stroke(); + self.ctx.closePath(); + self.ctx.restore(); + } + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + /* + * SVG State Machine renderer + */ + jsPlumb.Connectors.svg.StateMachine = function() { + var self = this; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.SvgConnector.apply(this, arguments); + this.getPath = function(d) { + + if (d.length == 10) + return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[8] + " " + d[9] + " " + d[6] + " " + d[7]; + else { + // loopback + return "M" + (d[8] + 4) + " " + d[9] + " A " + d[6] + " " + d[6] + " 0 1,0 " + (d[8]-4) + " " + d[9]; + } + }; + }; + + /* + * VML state machine renderer + */ + jsPlumb.Connectors.vml.StateMachine = function() { + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.VmlConnector.apply(this, arguments); + var _conv = jsPlumb.vml.convertValue; + this.getPath = function(d) { + if (d.length == 10) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + } + else { + // loopback + var left = _conv(d[8] - d[6]), + top = _conv(d[9] - (2 * d[6])), + right = left + _conv(2 * d[6]), + bottom = top + _conv(2 * d[6]), + posString = left + "," + top + "," + right + "," + bottom; + + var o = "ar " + posString + "," + _conv(d[8]) + "," + + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + " e"; + + return o; + } + }; + }; + +})(); + +/* + // now for a rudimentary avoidance scheme. TODO: how to set this in a cross-library way? + // if (avoidSelector) { + // var testLine = new Line(sourcePos[0] + _sx,sourcePos[1] + _sy,sourcePos[0] + _tx,sourcePos[1] + _ty); + // var sel = jsPlumb.getSelector(avoidSelector); + // for (var i = 0; i < sel.length; i++) { + // var id = jsPlumb.getId(sel[i]); + // if (id != sourceEndpoint.elementId && id != targetEndpoint.elementId) { + // o = jsPlumb.getOffset(id), s = jsPlumb.getSize(id); +// +// if (o && s) { +// var collision = testLine.rectIntersect(o.left,o.top,s[0],s[1]); +// if (collision) { + // set the control point to be a certain distance from the midpoint of the two points that + // the line crosses on the rectangle. + // TODO where will this 75 number come from? + // _controlX = collision[2][0] + (75 * collision[3][0]); + // / _controlY = collision[2][1] + (75 * collision[3][1]); +// } +// } + // } + // } + //} + */ \ No newline at end of file diff --git a/archive/1.3.5/jsPlumb-defaults-1.3.5-RC1.js b/archive/1.3.5/jsPlumb-defaults-1.3.5-RC1.js new file mode 100644 index 000000000..26cd1a217 --- /dev/null +++ b/archive/1.3.5/jsPlumb-defaults-1.3.5-RC1.js @@ -0,0 +1,1104 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the default Connectors, Endpoint and Overlay definitions. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +(function() { + + /** + * + * Helper class to consume unused mouse events by components that are DOM elements and + * are used by all of the different rendering modes. + * + */ + jsPlumb.DOMElementComponent = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + // when render mode is canvas, these functions may be called by the canvas mouse handler. + // this component is safe to pipe this stuff to /dev/null. + this.mousemove = + this.dblclick = + this.click = + this.mousedown = + this.mouseup = function(e) { }; + }; + + /** + * Class: Connectors.Straight + * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters. + */ + jsPlumb.Connectors.Straight = function() { + this.type = "Straight"; + var self = this, + currentPoints = null, + _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty, _segment, _length; + + /** + * Computes the new size and position of the canvas. + */ + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + _dx = _tx - _sx, _dy = _ty - _sy; + //_m = _dy / _dx, _m2 = -1 / _m; + _m = jsPlumb.util.gradient({x:_sx, y:_sy}, {x:_tx, y:_ty}), _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + //_segment = jsPlumb.util.segment({x:_sx, y:_sy}, {x:_tx, y:_ty}); + _length = Math.sqrt((_dx * _dx) + (_dy * _dy)); + + return currentPoints; + }; + + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + if (location == 0) + return { x:_sx, y:_sy }; + else if (location == 1) + return { x:_tx, y:_ty }; + else + return jsPlumb.util.pointOnLine({x:_sx, y:_sy}, {x:_tx, y:_ty}, location * _length); + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { + return _m; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + * this hands off to jsPlumb.util to do the maths, supplying our gradient and position and whether or + * not the coords should be flipped + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + return jsPlumb.util.pointOnLine(p, {x:_tx,y:_ty}, distance); + }; + }; + + + /** + * Class:Connectors.Bezier + * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's + * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below. + */ + /** + * Function:Constructor + * + * Parameters: + * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line. + * Optional; defaults to 150. + * + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + params = params || {}; + this.majorAnchor = params.curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + this.type = "Bezier"; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(sourceEndpoint), + too = targetAnchor.getOrientation(targetEndpoint), + perpendicular = soo[0] != too[0] || soo[1] == too[1], + p = [], + ma = self.majorAnchor, mi = self.minorAnchor; + + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, sourceEndpoint, targetEndpoint, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx), minx2 = Math.min(_CP[0], _CP2[0]), minx = Math.min(minx1,minx2), + maxx1 = Math.max(_sx,_tx), maxx2 = Math.max(_CP[0], _CP2[0]), maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty), miny2 = Math.min(_CP[1], _CP2[1]), miny = Math.min(miny1,miny2), + maxy1 = Math.max(_sy,_ty), maxy2 = Math.max(_CP[1], _CP2[1]), maxy = Math.max(maxy1,maxy2); + + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + }; + + + /** + * Class: Connectors.Flowchart + * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments. + */ + /** + * Function: Constructor + * + * Parameters: + * stub - minimum length for the stub at each end of the connector. defaults to 30 pixels. + */ + jsPlumb.Connectors.Flowchart = function(params) { + this.type = "Flowchart"; + params = params || {}; + var self = this, + minStubLength = params.stub || params.minStubLength /* bwds compat. */ || 30, + segments = [], + totalLength = 0, + segmentProportions = [], + segmentProportionalLengths = [], + points = [], + swapX, + swapY, + /** + * recalculates the points at which the segments begin and end, proportional to the total length travelled + * by all the segments that constitute the connector. we use this to help with pointOnPath calculations. + */ + updateSegmentProportions = function(startX, startY, endX, endY) { + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segments[i][5] / totalLength; + segmentProportions[i] = [curLoc, (curLoc += (segments[i][5] / totalLength)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0], + ly = segments.length == 0 ? sy : segments[segments.length - 1][1], + m = x == lx ? Infinity : 0, + l = Math.abs(x == lx ? y - ly : x - lx); + segments.push([x, y, lx, ly, m, l]); + totalLength += l; + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment + * that contains the point which is 'location' distance along the entire path, where + * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths + * are made up of a list of segments, each of which contributes some fraction to + * the total length. + */ + findSegmentForLocation = function(location) { + var idx = segmentProportions.length - 1, inSegmentProportion = 0; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, + sourceAnchor, targetAnchor, lineWidth, minWidth, sourceInfo, targetInfo) { + + segments = []; + totalLength = 0; + segmentProportionalLengths = []; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + offx = (lw / 2) + (minStubLength * 2), + offy = (lw / 2) + (minStubLength * 2), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(sourceEndpoint), + to = targetAnchor.orientation || targetAnchor.getOrientation(targetEndpoint), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx, + h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy; + + // if either anchor does not have an orientation set, we derive one from their relative + // positions. we fix the axis to be the one in which the two elements are further apart, and + // point each anchor at the other element. this is also used when dragging a new connection. + if (so[0] == 0 && so[1] == 0 || to[0] == 0 && to[1] == 0) { + var index = w > h ? 0 : 1, oIndex = [1,0][index]; + so = []; to = []; + so[index] = sourcePos[index] > targetPos[index] ? -1 : 1; + to[index] = sourcePos[index] > targetPos[index] ? 1 : -1; + so[oIndex] = 0; + to[oIndex] = 0; + } + + if (w < minWidth) { + offx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + offy += (minWidth - h) / 2; + h = minWidth; + } + + var sx = swapX ? w-offx : offx, + sy = swapY ? h-offy : offy, + tx = swapX ? offx : w-offx , + ty = swapY ? offy : h-offy, + startStubX = sx + (so[0] * minStubLength), + startStubY = sy + (so[1] * minStubLength), + endStubX = tx + (to[0] * minStubLength), + endStubY = ty + (to[1] * minStubLength), + isXGreaterThanStubTimes2 = Math.abs(sx - tx) > 2 * minStubLength, + isYGreaterThanStubTimes2 = Math.abs(sy - ty) > 2 * minStubLength, + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2), + oProduct = ((so[0] * to[0]) + (so[1] * to[1])), + opposite = oProduct == -1, + perpendicular = oProduct == 0, + orthogonal = oProduct == 1; + + x -= offx; y -= offy; + points = [x, y, w, h, sx, sy, tx, ty]; + var extraPoints = []; + + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + var sourceAxis = so[0] == 0 ? "y" : "x", + anchorOrientation = opposite ? "opposite" : orthogonal ? "orthogonal" : "perpendicular", + segment = jsPlumb.util.segment([sx, sy], [tx, ty]), + flipSourceSegments = so[sourceAxis == "x" ? 0 : 1] == -1, + flipSegments = { + "x":[null, 4, 3, 2, 1], + "y":[null, 2, 1, 4, 3] + } + + if (flipSourceSegments) + segment = flipSegments[sourceAxis][segment]; + + var findClearedLine = function(start, mult, anchorPos, dimension) { + return start + (mult * (( 1 - anchorPos) * dimension) + minStubLength); + //mx = so[0] == 0 ? startStubX + ((1 - sourceAnchor.x) * sourceInfo.width) + minStubLength : startStubX, + }, + + lineCalculators = { + oppositex : function() { + if (sourceEndpoint.elementId == targetEndpoint.elementId) { + var _y = startStubY + ((1 - sourceAnchor.y) * sourceInfo.height) + minStubLength; + return [ [ startStubX, _y ], [ endStubX, _y ]]; + } + else if (isXGreaterThanStubTimes2 && (segment == 1 || segment == 2)) { + return [[ midx, sy ], [ midx, ty ]]; + } + else { + return [[ startStubX, midy ], [endStubX, midy ]]; + } + }, + orthogonalx : function() { + if (segment == 1 || segment == 2) { + return [ [ endStubX, startStubY ]]; + } + else { + return [ [ startStubX, endStubY ]]; + } + }, + perpendicularx : function() { + var my = (ty + sy) / 2; + if ((segment == 1 && to[1] == 1) || (segment == 2 && to[1] == -1)) { + if (Math.abs(tx - sx) > minStubLength) + return [ [endStubX, startStubY ]]; + else + return [ [startStubX, startStubY ], [ startStubX, my ], [ endStubX, my ]]; + } + else if ((segment == 3 && to[1] == -1) || (segment == 4 && to[1] == 1)) { + return [ [ startStubX, my ], [ endStubX, my ]]; + } + else if ((segment == 3 && to[1] == 1) || (segment == 4 && to[1] == -1)) { + return [ [ startStubX, endStubY ]]; + } + else if ((segment == 1 && to[1] == -1) || (segment == 2 && to[1] == 1)) { + if (Math.abs(tx - sx) > minStubLength) + return [ [ midx, startStubY ], [ midx, endStubY ]]; + else + return [ [ startStubX, endStubY ]]; + } + }, + oppositey : function() { + if (sourceEndpoint.elementId == targetEndpoint.elementId) { + var _x = startStubX + ((1 - sourceAnchor.x) * sourceInfo.width) + minStubLength; + return [ [ _x, startStubY ], [ _x, endStubY ]]; + } + else if (isYGreaterThanStubTimes2 && (segment == 2 || segment == 3)) { + return [[ sx, midy ], [ tx, midy ]]; + } + else { + return [[ midx, startStubY ], [midx, endStubY ]]; + } + }, + orthogonaly : function() { + if (segment == 2 || segment == 3) { + return [ [ startStubX, endStubY ]]; + } + else { + return [ [ endStubX, startStubY ]]; + } + }, + perpendiculary : function() { + var mx = (tx + sx) / 2; + if ((segment == 2 && to[0] == -1) || (segment == 3 && to[0] == 1)) { + if (Math.abs(tx - sx) > minStubLength) + return [ [startStubX, endStubY ]]; + else + return [ [startStubX, midy ], [ endStubX, midy ]]; + } + else if ((segment == 1 && to[0] == -1) || (segment == 4 && to[0] == 1)) { + var mx = (tx + sx) / 2; + return [ [ mx, startStubY ], [ mx, endStubY ]]; + } + else if ((segment == 1 && to[0] == 1) || (segment == 4 && to[0] == -1)) { + return [ [ endStubX, startStubY ]]; + } + else if ((segment == 2 && to[0] == 1) || (segment == 3 && to[0] == -1)) { + if (Math.abs(ty - sy) > minStubLength) + return [ [ startStubX, midy ], [ endStubX, midy ]]; + else + return [ [ endStubX, startStubY ]]; + } + } + }; + + + + var pointFinder = lineCalculators[anchorOrientation + sourceAxis]; + var p = pointFinder(); + if (p) { + for (var i = 0; i < p.length; i++) { + addSegment(p[i][0], p[i][1], sx, sy, tx, ty); + } + } + + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentProportions(sx, sy, tx, ty); + + return points; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location) { + return self.pointAlongPathFrom(location, 0); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location) { + return segments[findSegmentForLocation(location)["index"]][4]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance) { + var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segments[s.index][5], m = segments[s.index][4]; + var e = { + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + return e; + }; + }; + + // ********************************* END OF CONNECTOR TYPES ******************************************************************* + + // ********************************* ENDPOINT TYPES ******************************************************************* + + /** + * Class: Endpoints.Dot + * A round endpoint, with default radius 10 pixels. + */ + + /** + * Function: Constructor + * + * Parameters: + * + * radius - radius of the endpoint. defaults to 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + this.type = "Dot"; + var self = this; + params = params || {}; + this.radius = params.radius || 10; + this.defaultOffset = 0.5 * this.radius; + this.defaultInnerRadius = this.radius / 3; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var r = endpointStyle.radius || self.radius, + x = anchorPoint[0] - r, + y = anchorPoint[1] - r; + return [ x, y, r * 2, r * 2, r ]; + }; + }; + + /** + * Class: Endpoints.Rectangle + * A Rectangular Endpoint, with default size 20x20. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the endpoint. defaults to 20 pixels. + * height - height of the endpoint. defaults to 20 pixels. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + this.type = "Rectangle"; + var self = this; + params = params || {}; + this.width = params.width || 20; + this.height = params.height || 20; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height]; + }; + }; + + + var DOMElementEndpoint = function(params) { + jsPlumb.DOMElementComponent.apply(this, arguments); + var self = this; + + var displayElements = [ ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + }; + /** + * Class: Endpoints.Image + * Draws an image as the Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * src - location of the image to use. + */ + jsPlumb.Endpoints.Image = function(params) { + + this.type = "Image"; + DOMElementEndpoint.apply(this, arguments); + + var self = this, + initialized = false, + widthToUse = params.width, + heightToUse = params.height, + _onload = null, + _endpoint = params.endpoint; + + this.img = new Image(); + self.ready = false; + + this.img.onload = function() { + self.ready = true; + widthToUse = widthToUse || self.img.width; + heightToUse = heightToUse || self.img.height; + if (_onload) { + _onload(self); + } + }; + + _endpoint.setImage = function(img, onload) { + var s = img.constructor == String ? img : img.src; + _onload = onload; + self.img.src = img; + }; + + _endpoint.setImage(params.src || params.url, params.onload); + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + self.anchorPoint = anchorPoint; + if (self.ready) return [anchorPoint[0] - widthToUse / 2, anchorPoint[1] - heightToUse / 2, + widthToUse, heightToUse]; + else return [0,0,0,0]; + }; + + self.canvas = document.createElement("img"), initialized = false; + self.canvas.style["margin"] = 0; + self.canvas.style["padding"] = 0; + self.canvas.style["outline"] = 0; + self.canvas.style["position"] = "absolute"; + var clazz = params.cssClass ? " " + params.cssClass : ""; + self.canvas.className = jsPlumb.endpointClass + clazz; + if (widthToUse) self.canvas.setAttribute("width", widthToUse); + if (heightToUse) self.canvas.setAttribute("height", heightToUse); + jsPlumb.appendElement(self.canvas, params.parent); + self.attachListeners(self.canvas, self); + + var actuallyPaint = function(d, style, anchor) { + if (!initialized) { + self.canvas.setAttribute("src", self.img.src); + initialized = true; + } + var x = self.anchorPoint[0] - (widthToUse / 2), + y = self.anchorPoint[1] - (heightToUse / 2); + jsPlumb.sizeCanvas(self.canvas, x, y, widthToUse, heightToUse); + }; + + this.paint = function(d, style, anchor) { + if (self.ready) { + actuallyPaint(d, style, anchor); + } + else { + window.setTimeout(function() { + self.paint(d, style, anchor); + }, 200); + } + }; + }; + + /** + * Class: Endpoints.Blank + * An Endpoint that paints nothing (visible) on the screen. Supports cssClass and hoverClass parameters like all Endpoints. + */ + jsPlumb.Endpoints.Blank = function(params) { + var self = this; + this.type = "Blank"; + DOMElementEndpoint.apply(this, arguments); + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + return [anchorPoint[0], anchorPoint[1],10,0]; + }; + + self.canvas = document.createElement("div"); + self.canvas.style.display = "block"; + self.canvas.style.width = "1px"; + self.canvas.style.height = "1px"; + self.canvas.style.background = "transparent"; + self.canvas.style.position = "absolute"; + self.canvas.className = self._jsPlumb.endpointClass; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + }; + }; + + /** + * Class: Endpoints.Triangle + * A triangular Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the triangle's base. defaults to 55 pixels. + * height - height of the triangle from base to apex. defaults to 55 pixels. + */ + jsPlumb.Endpoints.Triangle = function(params) { + this.type = "Triangle"; + params = params || { }; + params.width = params.width || 55; + params.height = params.height || 55; + this.width = params.width; + this.height = params.height; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height ]; + }; + }; +// ********************************* END OF ENDPOINT TYPES ******************************************************************* + + +// ********************************* OVERLAY DEFINITIONS *********************************************************************** + + var AbstractOverlay = function(params) { + var visible = true, self = this; + this.isAppendedAtTopLevel = true; + this.component = params.component; + this.loc = params.location == null ? 0.5 : params.location; + this.endpointLoc = params.endpointLocation == null ? [ 0.5, 0.5] : params.endpointLocation; + this.setVisible = function(val) { + visible = val; + self.component.repaint(); + }; + this.isVisible = function() { return visible; }; + this.hide = function() { self.setVisible(false); }; + this.show = function() { self.setVisible(true); }; + + this.incrementLocation = function(amount) { + self.loc += amount; + self.component.repaint(); + }; + this.setLocation = function(l) { + self.loc = l; + self.component.repaint(); + }; + this.getLocation = function() { + return self.loc; + }; + }; + + + /** + * Class: Overlays.Arrow + * + * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length + * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction + * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line + * across the tail. + */ + /** + * Function: Constructor + * + * Parameters: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + this.type = "Arrow"; + AbstractOverlay.apply(this, arguments); + this.isAppendedAtTopLevel = false; + params = params || {}; + var self = this; + + this.length = params.length || 20; + this.width = params.width || 20; + this.id = params.id; + var direction = (params.direction || 1) < 0 ? -1 : 1, + paintStyle = params.paintStyle || { lineWidth:1 }, + // how far along the arrow the lines folding back in come to. default is 62.3%. + foldback = params.foldback || 0.623; + + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.cleanup = function() { }; // nothing to clean up for Arrows + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var hxy, mid, txy, tail, cxy; + if (connector.pointAlongPathFrom) { + + if (self.loc == 1) { + hxy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, -1); + txy = jsPlumb.util.pointOnLine(hxy, mid, self.length); + } + else if (self.loc == 0) { + txy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, 1); + hxy = jsPlumb.util.pointOnLine(txy, mid, self.length); + } + else { + hxy = connector.pointAlongPathFrom(self.loc, direction * self.length / 2), + mid = connector.pointOnPath(self.loc), + txy = jsPlumb.util.pointOnLine(hxy, mid, self.length); + } + + tail = jsPlumb.util.perpendicularLineTo(hxy, txy, self.width); + cxy = jsPlumb.util.pointOnLine(hxy, txy, foldback * self.length); + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x), + maxx = Math.max(hxy.x, tail[0].x, tail[1].x), + miny = Math.min(hxy.y, tail[0].y, tail[1].y), + maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + var d = { hxy:hxy, tail:tail, cxy:cxy }, + strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle, + fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle, + lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth; + + self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions); + + return [ minx, maxx, miny, maxy]; + } + else return [0,0,0,0]; + }; + }; + + /** + * Class: Overlays.PlainArrow + * + * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "PlainArrow"; + }; + + /** + * Class: Overlays.Diamond + * + * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40, + p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "Diamond"; + }; + + + + /** + * Class: Overlays.Label + * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb + * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter + * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it + * puts on the Label's 'style' attribute, so the end result is the same. + */ + /** + * Function: Constructor + * + * Parameters: + * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes + * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle. + * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * + */ + jsPlumb.Overlays.Label = function(params) { + this.type = "Label"; + jsPlumb.DOMElementComponent.apply(this, arguments); + AbstractOverlay.apply(this, arguments); + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.id = params.id; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var label = params.label || "", + self = this, + initialised = false, + div = document.createElement("div"), + labelText = null; + div.style["position"] = "absolute"; + + var clazz = params["_jsPlumb"].overlayClass + " " + + (self.labelStyle.cssClass ? self.labelStyle.cssClass : + params.cssClass ? params.cssClass : ""); + + div.className = clazz; + + jsPlumb.appendElement(div, params.component.parent); + jsPlumb.getId(div); + self.attachListeners(div, self); + self.canvas = div; + + //override setVisible + var osv = self.setVisible; + self.setVisible = function(state) { + osv(state); // call superclass + div.style.display = state ? "block" : "none"; + }; + + this.getElement = function() { + return div; + }; + + this.cleanup = function() { + if (div != null) jsPlumb.CurrentLibrary.removeElement(div); + }; + + /* + * Function: setLabel + * sets the label's, um, label. you would think i'd call this function + * 'setText', but you can pass either a Function or a String to this, so + * it makes more sense as 'setLabel'. + */ + this.setLabel = function(l) { + label = l; + labelText = null; + self.component.repaint(); + }; + + this.getLabel = function() { + return label; + }; + + this.paint = function(component, d, componentDimensions) { + if (!initialised) { + component.appendDisplayElement(div); + self.attachListeners(div, component); + initialised = true; + } + div.style.left = (componentDimensions[0] + d.minx) + "px"; + div.style.top = (componentDimensions[1] + d.miny) + "px"; + }; + + this.getTextDimensions = function() { + if (typeof label == "function") { + var lt = label(self); + div.innerHTML = lt.replace(/\r\n/g, "
"); + } + else { + if (labelText == null) { + labelText = label; + div.innerHTML = labelText.replace(/\r\n/g, "
"); + } + } + var de = jsPlumb.CurrentLibrary.getElementObject(div), + s = jsPlumb.CurrentLibrary.getSize(de); + return {width:s[0], height:s[1]}; + }; + + this.computeMaxSize = function(connector) { + var td = self.getTextDimensions(connector); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + + this.draw = function(component, currentConnectionPaintStyle, componentDimensions) { + var td = self.getTextDimensions(component); + if (td.width != null) { + var cxy = {x:0,y:0}; + if (component.pointOnPath) + cxy = component.pointOnPath(self.loc); // a connection + else { + var locToUse = self.loc.constructor == Array ? self.loc : self.endpointLoc; + cxy = { x:locToUse[0] * componentDimensions[2], + y:locToUse[1] * componentDimensions[3] }; + } + + minx = cxy.x - (td.width / 2), + miny = cxy.y - (td.height / 2); + + self.paint(component, { + minx:minx, + miny:miny, + td:td, + cxy:cxy + }, componentDimensions); + + return [minx, minx+td.width, miny, miny+td.height]; + } + else return [0,0,0,0]; + }; + + this.reattachListeners = function(connector) { + if (div) { + self.reattachListenersForElement(div, self, connector); + } + }; + }; + + // this is really just a test overlay, so its undocumented and doesnt take any parameters. but i was loth to delete it. + jsPlumb.Overlays.GuideLines = function() { + var self = this; + self.length = 50; + self.lineWidth = 5; + this.type = "GuideLines"; + AbstractOverlay.apply(this, arguments); + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var head = connector.pointAlongPathFrom(self.loc, self.length / 2), + mid = connector.pointOnPath(self.loc), + tail = jsPlumb.util.pointOnLine(head, mid, self.length), + tailLine = jsPlumb.util.perpendicularLineTo(head, tail, 40), + headLine = jsPlumb.util.perpendicularLineTo(tail, head, 20); + + self.paint(connector, [head, tail, tailLine, headLine], self.lineWidth, "red", null, connectorDimensions); + + return [Math.min(head.x, tail.x), Math.min(head.y, tail.y), Math.max(head.x, tail.x), Math.max(head.y,tail.y)]; + }; + + this.computeMaxSize = function() { return 50; }; + + this.cleanup = function() { }; // nothing to clean up for GuideLines + }; + + // ********************************* END OF OVERLAY DEFINITIONS *********************************************************************** + + // ********************************* OVERLAY CANVAS RENDERERS*********************************************************************** + + // ********************************* END OF OVERLAY CANVAS RENDERERS *********************************************************************** +})(); \ No newline at end of file diff --git a/archive/1.3.5/jsPlumb-renderers-canvas-1.3.5-RC1.js b/archive/1.3.5/jsPlumb-renderers-canvas-1.3.5-RC1.js new file mode 100644 index 000000000..4983e5cb7 --- /dev/null +++ b/archive/1.3.5/jsPlumb-renderers-canvas-1.3.5-RC1.js @@ -0,0 +1,456 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the HTML5 canvas renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + +// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS ******************************************************************* + + // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. + var _connectionBeingDragged = null, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, + _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }; + + /* + * Class:CanvasMouseAdapter + * Provides support for mouse events on canvases. + */ + var CanvasMouseAdapter = function() { + var self = this; + self.overlayPlacements = []; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + jsPlumb.EventGenerator.apply(this, arguments); + /** + * returns whether or not the given event is ojver a painted area of the canvas. + */ + this._over = function(e) { + var o = _getOffset(_getElementObject(self.canvas)), + pageXY = _pageXY(e), + x = pageXY[0] - o.left, y = pageXY[1] - o.top; + if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + return false; + }; + + var _mouseover = false, _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false, + _nullSafeHasClass = function(el, clazz) { + return el != null && _hasClass(el, clazz); + }; + this.mousemove = function(e) { + var pageXY = _pageXY(e), clientXY = _clientXY(e), + ee = document.elementFromPoint(clientXY[0], clientXY[1]), + eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); + var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); + if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.fire("mouseenter", self, e); + return true; + } + // TODO here there is a remote chance that the overlay the mouse moved onto + // is actually not an overlay for the current component. a more thorough check would + // be to ensure the overlay belonged to the current component. + else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { + _mouseover = false; + self.fire("mouseexit", self, e); + } + self.fire("mousemove", self, e); + }; + + this.click = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); + self.fire("mousedown", self, e); + } + }; + + this.mouseup = function(e) { + _mouseDown = false; + self.fire("mouseup", self, e); + }; + + this.contextmenu = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("contextmenu", self, e); + _mouseWasDown = false; + }; + }; + + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + jsPlumb.appendElement(canvas, params.parent); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + params["_jsPlumb"].getId(canvas, params.uuid); + if (params.tooltip) canvas.setAttribute("title", params.tooltip); + + return canvas; + }; + + var CanvasComponent = function(params) { + CanvasMouseAdapter.apply(this, arguments); + + var displayElements = [ ]; + this.getDisplayElements = function() { return displayElements; }; + this.appendDisplayElement = function(el) { displayElements.push(el); }; + } + + /** + * Class:CanvasConnector + * Superclass for Canvas Connector renderers. + */ + var CanvasConnector = jsPlumb.CanvasConnector = function(params) { + + CanvasComponent.apply(this, arguments); + + var _paintOneStyle = function(dim, aStyle) { + self.ctx.save(); + jsPlumb.extend(self.ctx, aStyle); + if (aStyle.gradient) { + var g = self.createGradient(dim, self.ctx); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + self.ctx.strokeStyle = g; + } + self._paint(dim); + self.ctx.restore(); + }; + + var self = this, + clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:params.tooltip + }); + self.ctx = self.canvas.getContext("2d"); + + self.appendDisplayElement(self.canvas); + + self.paint = function(dim, style) { + if (style != null) { + jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + _paintOneStyle(dim, outlineStyle); + } + _paintOneStyle(dim, style); + } + }; + }; + + /** + * Class:CanvasEndpoint + * Superclass for Canvas Endpoint renderers. + */ + var CanvasEndpoint = function(params) { + var self = this; + CanvasComponent.apply(this, arguments); + var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""), + canvasParams = { + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:self.tooltip + }; + self.canvas = _newCanvas(canvasParams); + self.ctx = self.canvas.getContext("2d"); + + self.appendDisplayElement(self.canvas); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + } + + self._paint.apply(this, arguments); + }; + }; + + jsPlumb.Endpoints.canvas.Dot = function(params) { + jsPlumb.Endpoints.Dot.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + var self = this, + parseValue = function(value) { + try { return parseInt(value); } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }, + calculateAdjustments = function(gradient) { + var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius; + gradient.offset && (offsetAdjustment = parseValue(gradient.offset)); + gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius)); + return [offsetAdjustment, innerRadius]; + }; + this._paint = function(d, style, anchor) { + if (style != null) { + var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + if (style.gradient) { + var adjustments = calculateAdjustments(style.gradient), + yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0], + xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0], + g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + ctx.beginPath(); + ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + } + }; + }; + + jsPlumb.Endpoints.canvas.Rectangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) { + + var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + + /* canvas gradient */ + if (style.gradient) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, d[2], d[3]); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + jsPlumb.Endpoints.canvas.Triangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Triangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) + { + var width = d[2], height = d[3], x = d[0], y = d[1], + ctx = self.canvas.getContext('2d'), + offsetX = 0, offsetY = 0, angle = 0, + orientation = anchor.getOrientation(self); + + if( orientation[0] == 1 ) { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = style.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + /* + * Canvas Image Endpoint: uses the default version, which creates an tag. + */ + jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image; + + /* + * Blank endpoint in all renderers is just the default Blank endpoint. + */ + jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank; + + /* + * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Bezier = function() { + var self = this; + jsPlumb.Connectors.Bezier.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO i doubt this handles the case that source and target are swapped. + this.createGradient = function(dim, ctx, swap) { + return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + /* + * Canvas straight line Connector. Draws a straight line onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Straight = function() { + var self = this; + jsPlumb.Connectors.Straight.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO this does not handle the case that src and target are swapped. + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + jsPlumb.Connectors.canvas.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + +// ********************************* END OF CANVAS RENDERERS ******************************************************************* + + jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label; + + /** + * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. + */ + var CanvasOverlay = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }; + + var AbstractCanvasArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + CanvasOverlay.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + var ctx = connector.ctx; + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(d.hxy.x, d.hxy.y); + ctx.lineTo(d.tail[0].x, d.tail[0].y); + ctx.lineTo(d.cxy.x, d.cxy.y); + ctx.lineTo(d.tail[1].x, d.tail[1].y); + ctx.lineTo(d.hxy.x, d.hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + }; + + jsPlumb.Overlays.canvas.Arrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.canvas.PlainArrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.canvas.Diamond = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.5/jsPlumb-renderers-svg-1.3.5-RC1.js b/archive/1.3.5/jsPlumb-renderers-svg-1.3.5-RC1.js new file mode 100644 index 000000000..759630376 --- /dev/null +++ b/archive/1.3.5/jsPlumb-renderers-svg-1.3.5-RC1.js @@ -0,0 +1,535 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the SVG renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +/** + * SVG support for jsPlumb. + * + * things to investigate: + * + * gradients: https://developer.mozilla.org/en/svg_in_html_introduction + * css:http://tutorials.jenkov.com/svg/svg-and-css.html + * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath + * pointer events: https://developer.mozilla.org/en/css/pointer-events + * + * IE9 hover jquery: http://forum.jquery.com/topic/1-6-2-broke-svg-hover-events + * + */ +;(function() { + + var svgAttributeMap = { + "joinstyle":"stroke-linejoin", + "stroke-linejoin":"stroke-linejoin", + "stroke-dashoffset":"stroke-dashoffset", + "stroke-linecap":"stroke-linecap" + }, + STROKE_DASHARRAY = "stroke-dasharray", + DASHSTYLE = "dashstyle", + LINEAR_GRADIENT = "linearGradient", + RADIAL_GRADIENT = "radialGradient", + FILL = "fill", + STOP = "stop", + STROKE = "stroke", + STROKE_WIDTH = "stroke-width", + STYLE = "style", + NONE = "none", + JSPLUMB_GRADIENT = "jsplumb_gradient_", + LINE_WIDTH = "lineWidth", + ns = { + svg:"http://www.w3.org/2000/svg", + xhtml:"http://www.w3.org/1999/xhtml" + }, + _attr = function(node, attributes) { + for (var i in attributes) + node.setAttribute(i, "" + attributes[i]); + }, + _node = function(name, attributes) { + var n = document.createElementNS(ns.svg, name); + attributes = attributes || {}; + attributes["version"] = "1.1"; + attributes["xmnls"] = ns.xhtml; + _attr(n, attributes); + return n; + }, + _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; }, + _clearGradient = function(parent) { + for (var i = 0; i < parent.childNodes.length; i++) { + if (parent.childNodes[i].tagName == LINEAR_GRADIENT || parent.childNodes[i].tagName == RADIAL_GRADIENT) + parent.removeChild(parent.childNodes[i]); + } + }, + _updateGradient = function(parent, node, style, dimensions, uiComponent) { + var id = JSPLUMB_GRADIENT + uiComponent._jsPlumb.idstamp(); + // first clear out any existing gradient + _clearGradient(parent); + // this checks for an 'offset' property in the gradient, and in the absence of it, assumes + // we want a linear gradient. if it's there, we create a radial gradient. + // it is possible that a more explicit means of defining the gradient type would be + // better. relying on 'offset' means that we can never have a radial gradient that uses + // some default offset, for instance. + if (!style.gradient.offset) { + var g = _node(LINEAR_GRADIENT, {id:id}); + parent.appendChild(g); + } + else { + var g = _node(RADIAL_GRADIENT, { + id:id + }); + parent.appendChild(g); + } + + // the svg radial gradient seems to treat stops in the reverse + // order to how canvas does it. so we want to keep all the maths the same, but + // iterate the actual style declarations in reverse order, if the x indexes are not in order. + for (var i = 0; i < style.gradient.stops.length; i++) { + // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of + // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments. + // so, not too concerned about leaving this in for now. + var styleToUse = i; + if (dimensions.length == 8) + styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i; + else + styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i; + var stopColor = jsPlumb.util.convertStyle(style.gradient.stops[styleToUse][1], true); + var s = _node(STOP, {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor}); + g.appendChild(s); + } + var applyGradientTo = style.strokeStyle ? STROKE : FILL; + node.setAttribute(STYLE, applyGradientTo + ":url(#" + id + ")"); + }, + _applyStyles = function(parent, node, style, dimensions, uiComponent) { + + if (style.gradient) { + _updateGradient(parent, node, style, dimensions, uiComponent); + } + else { + // make sure we clear any existing gradient + _clearGradient(parent); + node.setAttribute(STYLE, ""); + } + + node.setAttribute(FILL, style.fillStyle ? jsPlumb.util.convertStyle(style.fillStyle, true) : NONE); + node.setAttribute(STROKE, style.strokeStyle ? jsPlumb.util.convertStyle(style.strokeStyle, true) : NONE); + if (style.lineWidth) { + node.setAttribute(STROKE_WIDTH, style.lineWidth); + } + + // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like + // the syntax in VML but is actually kind of nasty: values are given in the pixel + // coordinate space, whereas in VML they are multiples of the width of the stroked + // line, which makes a lot more sense. for that reason, jsPlumb is supporting both + // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from + // VML, which will be the preferred method. the code below this converts a dashstyle + // attribute given in terms of stroke width into a pixel representation, by using the + // stroke's lineWidth. + if (style[DASHSTYLE] && style[LINE_WIDTH] && !style[STROKE_DASHARRAY]) { + var sep = style[DASHSTYLE].indexOf(",") == -1 ? " " : ",", + parts = style[DASHSTYLE].split(sep), + styleToUse = ""; + parts.forEach(function(p) { + styleToUse += (Math.floor(p * style.lineWidth) + sep); + }); + node.setAttribute(STROKE_DASHARRAY, styleToUse); + } + else if(style[STROKE_DASHARRAY]) { + node.setAttribute(STROKE_DASHARRAY, style[STROKE_DASHARRAY]); + } + + // extra attributes such as join type, dash offset. + for (var i in svgAttributeMap) { + if (style[i]) { + node.setAttribute(svgAttributeMap[i], style[i]); + } + } + }, + _decodeFont = function(f) { + var r = /([0-9].)(p[xt])\s(.*)/; + var bits = f.match(r); + return {size:bits[1] + bits[2], font:bits[3]}; + }, + _classManip = function(el, add, clazz) { + var classesToAddOrRemove = clazz.split(" "), + className = el.className, + curClasses = className.baseVal.split(" "); + + for (var i = 0; i < classesToAddOrRemove.length; i++) { + if (add) { + if (curClasses.indexOf(classesToAddOrRemove[i]) == -1) + curClasses.push(classesToAddOrRemove[i]); + } + else { + var idx = curClasses.indexOf(classesToAddOrRemove[i]); + if (idx != -1) + curClasses.splice(idx, 1); + } + } + + el.className.baseVal = curClasses.join(" "); + }, + _addClass = function(el, clazz) { + _classManip(el, true, clazz); + }, + _removeClass = function(el, clazz) { + _classManip(el, false, clazz); + }; + + /** + utility methods for other objects to use. + */ + jsPlumb.util.svg = { + addClass:_addClass, + removeClass:_removeClass + }; + + /* + * Base class for SVG components. + */ + //var SvgComponent = function(cssClass, originalArgs, pointerEventsSpec) { + var SvgComponent = function(params) { + var self = this, + pointerEventsSpec = params.pointerEventsSpec || "all"; + jsPlumb.jsPlumbUIComponent.apply(this, params.originalArgs); + self.canvas = null, self.path = null, self.svg = null; + + var clazz = params.cssClass + " " + (params.originalArgs[0].cssClass || ""), + svgParams = { + "style":"", + "width":0, + "height":0, + "pointer-events":pointerEventsSpec, + "position":"absolute" + }; + if (self.tooltip) svgParams["title"] = self.tooltip; + self.svg = _node("svg", svgParams); + if (params.useDivWrapper) { + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.sizeCanvas(self.canvas,0,0,1,1); + self.canvas.className = clazz; + if (self.tooltip) self.canvas.setAttribute("title", self.tooltip); + } + else { + _attr(self.svg, { "class":clazz }); + self.canvas = self.svg; + } + + params._jsPlumb.appendElement(self.canvas, params.originalArgs[0]["parent"]); + if (params.useDivWrapper) self.canvas.appendChild(self.svg); + + // TODO this displayElement stuff is common between all components, across all + // renderers. would be best moved to jsPlumbUIComponent. + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + this.paint = function(d, style, anchor) { + if (style != null) { + var x = d[0], y = d[1]; + if (params.useDivWrapper) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + x = 0, y = 0; + } + _attr(self.svg, { + "style":_pos([x, y, d[2], d[3]]), + "width": d[2], + "height": d[3] + }); + self._paint.apply(this, arguments); + } + }; + }; + + /* + * Base class for SVG connectors. + */ + var SvgConnector = jsPlumb.SvgConnector = function(params) { + var self = this; + SvgComponent.apply(this, [ { + cssClass:params["_jsPlumb"].connectorClass, + originalArgs:arguments, + pointerEventsSpec:"none", + tooltip:params.tooltip, + _jsPlumb:params["_jsPlumb"] + } ]); + this._paint = function(d, style) { + var p = self.getPath(d), a = { "d":p }, outlineStyle = null; + a["pointer-events"] = "all"; + + // outline style. actually means drawing an svg object underneath the main one. + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = jsPlumb.CurrentLibrary.extend({}, style); + outlineStyle.strokeStyle = jsPlumb.util.convertStyle(style.outlineColor); + outlineStyle.lineWidth = outlineStrokeWidth; + + if (self.bgPath == null) { + self.bgPath = _node("path", a); + self.svg.appendChild(self.bgPath); + self.attachListeners(self.bgPath, self); + } + else { + _attr(self.bgPath, a); + } + + _applyStyles(self.svg, self.bgPath, outlineStyle, d, self); + } + + + // test - see below + // a["clip-path"]= "url(#testClip)"; + + if (self.path == null) { + self.path = _node("path", a); + self.svg.appendChild(self.path); + self.attachListeners(self.path, self); + + /* + this is a test of a clip path. i'm looking into using one of these to animate a jsplumb connection. + you could do this by walking along the line, stepping along a little at a time, and setting the clip + path to extend as far as that point. + + self.clip = _node("clipPath", {id:"testClip", clipPathUnits:"objectBoundingBox"}); + self.svg.appendChild(self.clip); + self.clip.appendChild(_node("rect", { + x:"0",y:"0",width:"0.5",height:"1" + })); + */ + } + else { + _attr(self.path, a); + } + + _applyStyles(self.svg, self.path, style, d, self); + }; + + this.reattachListeners = function() { + if (self.bgPath) self.reattachListenersForElement(self.bgPath, self); + if (self.path) self.reattachListenersForElement(self.path, self); + }; + + }; + + /* + * SVG Bezier Connector + */ + jsPlumb.Connectors.svg.Bezier = function(params) { + jsPlumb.Connectors.Bezier.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]; }; + }; + + /* + * SVG straight line Connector + */ + jsPlumb.Connectors.svg.Straight = function(params) { + jsPlumb.Connectors.Straight.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; }; + }; + + jsPlumb.Connectors.svg.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "M " + dimensions[4] + "," + dimensions[5]; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)]; + } + // finally draw a line to the end + p = p + " " + dimensions[6] + "," + dimensions[7]; + return p; + }; + }; + + /* + * Base class for SVG endpoints. + */ + var SvgEndpoint = function(params) { + var self = this; + SvgComponent.apply(this, [ { + cssClass:params["_jsPlumb"].endpointClass, + originalArgs:arguments, + pointerEventsSpec:"all", + useDivWrapper:true, + _jsPlumb:params["_jsPlumb"] + } ]); + this._paint = function(d, style) { + var s = jsPlumb.extend({}, style); + if (s.outlineColor) { + s.strokeWidth = s.outlineWidth; + s.strokeStyle = jsPlumb.util.convertStyle(s.outlineColor, true); + } + + if (self.node == null) { + self.node = self.makeNode(d, s); + self.svg.appendChild(self.node); + self.attachListeners(self.node, self); + } + _applyStyles(self.svg, self.node, s, d, self); + _pos(self.node, d); + }; + + this.reattachListeners = function() { + if (self.node) self.reattachListenersForElement(self.node, self); + }; + }; + + /* + * SVG Dot Endpoint + */ + jsPlumb.Endpoints.svg.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("circle", { + "cx" : d[2] / 2, + "cy" : d[3] / 2, + "r" : d[2] / 2 + }); + }; + }; + + /* + * SVG Rectangle Endpoint + */ + jsPlumb.Endpoints.svg.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("rect", { + "width":d[2], + "height":d[3] + }); + }; + }; + + /* + * SVG Image Endpoint is the default image endpoint. + */ + jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image; + /* + * Blank endpoint in svg renderer is the default Blank endpoint. + */ + jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank; + /* + * Label endpoint in svg renderer is the default Label endpoint. + */ + jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label; + + var AbstractSvgArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + this.isAppendedAtTopLevel = false; + var self = this, path =null; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + } + var clazz = originalArgs && (originalArgs.length == 1) ? (originalArgs[0].cssClass || "") : ""; + + _attr(path, { + "d" : makePath(d), + "class" : clazz, + stroke : strokeStyle ? strokeStyle : null, + fill : fillStyle ? fillStyle : null + }); + }; + var makePath = function(d) { + return "M" + d.hxy.x + "," + d.hxy.y + + " L" + d.tail[0].x + "," + d.tail[0].y + + " L" + d.cxy.x + "," + d.cxy.y + + " L" + d.tail[1].x + "," + d.tail[1].y + + " L" + d.hxy.x + "," + d.hxy.y; + }; + this.reattachListeners = function() { + if (path) self.reattachListenersForElement(path, self); + }; + }; + + jsPlumb.Overlays.svg.Arrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.svg.PlainArrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.svg.Diamond = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; + + // a test + jsPlumb.Overlays.svg.GuideLines = function() { + var path = null, self = this, path2 = null, p1_1, p1_2; + jsPlumb.Overlays.GuideLines.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + + p1_1 = _node("path"); + connector.svg.appendChild(p1_1); + self.attachListeners(p1_1, connector); + self.attachListeners(p1_1, self); + + p1_2 = _node("path"); + connector.svg.appendChild(p1_2); + self.attachListeners(p1_2, connector); + self.attachListeners(p1_2, self); + + } + + _attr(path, { + "d" : makePath(d[0], d[1]), + stroke : "red", + fill : null + }); + + _attr(p1_1, { + "d" : makePath(d[2][0], d[2][1]), + stroke : "blue", + fill : null + }); + + _attr(p1_2, { + "d" : makePath(d[3][0], d[3][1]), + stroke : "green", + fill : null + }); + }; + + var makePath = function(d1, d2) { + return "M " + d1.x + "," + d1.y + + " L" + d2.x + "," + d2.y; + }; + + }; +})(); \ No newline at end of file diff --git a/archive/1.3.5/jsPlumb-renderers-vml-1.3.5-RC1.js b/archive/1.3.5/jsPlumb-renderers-vml-1.3.5-RC1.js new file mode 100644 index 000000000..b3f038d83 --- /dev/null +++ b/archive/1.3.5/jsPlumb-renderers-vml-1.3.5-RC1.js @@ -0,0 +1,435 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the VML renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + // http://ajaxian.com/archives/the-vml-changes-in-ie-8 + // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/ + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + + var vmlAttributeMap = { + "stroke-linejoin":"joinstyle", + "joinstyle":"joinstyle", + "endcap":"endcap", + "miterlimit":"miterlimit" + }; + + if (document.createStyleSheet) { + + // this is the style rule for IE7/6: it uses a CSS class, tidy. + document.createStyleSheet().addRule(".jsplumb_vml", "behavior:url(#default#VML);position:absolute;"); + + // these are for VML in IE8. you have to explicitly call out which elements + // you're going to expect to support VML! + // + document.createStyleSheet().addRule("jsplumb\\:textbox", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:oval", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:rect", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:stroke", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:shape", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:group", "behavior:url(#default#VML);position:absolute;"); + + // in this page it is also mentioned that IE requires the extra arg to the namespace + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either. + // var iev = document.documentMode; + //if (!iev || iev < 8) + document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); + //else + // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML"); + } + + jsPlumb.vml = {}; + + var scale = 1000, + + _groupMap = {}, + _getGroup = function(container, connectorClass) { + var id = jsPlumb.getId(container), + g = _groupMap[id]; + if(!g) { + g = _node("group", [0,0,scale, scale], {"class":connectorClass}); + //g.style.position=absolute; + //g["coordsize"] = "1000,1000"; + g.style.backgroundColor="red"; + _groupMap[id] = g; + jsPlumb.appendElement(g, container); + //document.body.appendChild(g); + } + return g; + }, + _atts = function(o, atts) { + for (var i in atts) { + // IE8 fix: setattribute does not work after an element has been added to the dom! + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + //o.setAttribute(i, atts[i]); + + o[i] = atts[i]; + } + }, + _node = function(name, d, atts) { + atts = atts || {}; + var o = document.createElement("jsplumb:" + name); + o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml"; + _pos(o, d); + _atts(o, atts); + return o; + }, + _pos = function(o,d) { + o.style.left = d[0] + "px"; + o.style.top = d[1] + "px"; + o.style.width= d[2] + "px"; + o.style.height= d[3] + "px"; + o.style.position = "absolute"; + }, + _conv = jsPlumb.vml.convertValue = function(v) { + return Math.floor(v * scale); + }, + // tests if the given style is "transparent" and then sets the appropriate opacity node to 0 if so, + // or 1 if not. TODO in the future, support variable opacity. + _maybeSetOpacity = function(styleToWrite, styleToCheck, type, component) { + if ("transparent" === styleToCheck) + component.setOpacity(type, "0.0"); + else + component.setOpacity(type, "1.0"); + }, + _applyStyles = function(node, style, component) { + var styleToWrite = {}; + if (style.strokeStyle) { + styleToWrite["stroked"] = "true"; + var strokeColor = jsPlumb.util.convertStyle(style.strokeStyle, true); + styleToWrite["strokecolor"] = strokeColor; + _maybeSetOpacity(styleToWrite, strokeColor, "stroke", component); + styleToWrite["strokeweight"] = style.lineWidth + "px"; + } + else styleToWrite["stroked"] = "false"; + + if (style.fillStyle) { + styleToWrite["filled"] = "true"; + var fillColor = jsPlumb.util.convertStyle(style.fillStyle, true); + styleToWrite["fillcolor"] = fillColor; + _maybeSetOpacity(styleToWrite, fillColor, "fill", component); + } + else styleToWrite["filled"] = "false"; + + if(style["dashstyle"]) { + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = style["dashstyle"]; + } + else if (style["stroke-dasharray"] && style["lineWidth"]) { + var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",", + parts = style["stroke-dasharray"].split(sep), + styleToUse = ""; + for(var i = 0; i < parts.length; i++) { + styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep); + } + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = styleToUse; + } + + _atts(node, styleToWrite); + }, + /* + * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. + */ + VmlComponent = function() { + var self = this; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.opacityNodes = { + "stroke":null, + "fill":null + }; + this.initOpacityNodes = function(vml) { + self.opacityNodes["stroke"] = _node("stroke", [0,0,1,1], {opacity:"0.0"}); + self.opacityNodes["fill"] = _node("fill", [0,0,1,1], {opacity:"0.0"}); + vml.appendChild(self.opacityNodes["stroke"]); + vml.appendChild(self.opacityNodes["fill"]); + }; + this.setOpacity = function(type, value) { + var node = self.opacityNodes[type]; + if (node) node["opacity"] = "" + value; + }; + var displayElements = [ ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el, doNotAppendToCanvas) { + if (!doNotAppendToCanvas) self.canvas.parentNode.appendChild(el); + displayElements.push(el); + }; + }, + /* + * Base class for Vml connectors. extends VmlComponent. + */ + VmlConnector = jsPlumb.VmlConnector = function(params) { + var self = this; + self.strokeNode = null; + self.canvas = null; + VmlComponent.apply(this, arguments); + var clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : ""); + this.paint = function(d, style, anchor) { + if (style != null) { + var path = self.getPath(d), p = { "path":path }; + + //* + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle : jsPlumb.util.convertStyle(style.outlineColor), + lineWidth : outlineStrokeWidth + }; + for (var aa in vmlAttributeMap) outlineStyle[aa] = style[aa]; + + if (self.bgCanvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.bgCanvas = _node("shape", d, p); + jsPlumb.appendElement(self.bgCanvas, params.parent); + _pos(self.bgCanvas, d); + self.appendDisplayElement(self.bgCanvas, true); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.bgCanvas, d); + _atts(self.bgCanvas, p); + } + + _applyStyles(self.bgCanvas, outlineStyle, self); + } + //*/ + + if (self.canvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + if (self.tooltip) p["label"] = self.tooltip; + self.canvas = _node("shape", d, p); + + //var group = _getGroup(params.parent); // test of append everything to a group + //group.appendChild(self.canvas); // sort of works but not exactly; + jsPlumb.appendElement(self.canvas, params.parent); //before introduction of groups + + self.appendDisplayElement(self.canvas, true); + + self.attachListeners(self.canvas, self); + + self.initOpacityNodes(self.canvas, ["stroke"]); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.canvas, d); + _atts(self.canvas, p); + } + + _applyStyles(self.canvas, style, self); + } + }; + + //self.appendDisplayElement(self.canvas); + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + }, + /* + * + * Base class for Vml Endpoints. extends VmlComponent. + * + */ + VmlEndpoint = function(params) { + VmlComponent.apply(this, arguments); + var vml = null, self = this, opacityStrokeNode = null, opacityFillNode = null; + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + + //var group = _getGroup(params.parent); + //group.appendChild(self.canvas); + jsPlumb.appendElement(self.canvas, params.parent); + + if (self.tooltip) self.canvas.setAttribute("label", self.tooltip); + + this.paint = function(d, style, anchor) { + var p = { }; + + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (vml == null) { + p["class"] = jsPlumb.endpointClass; + vml = self.getVml([0,0, d[2], d[3]], p, anchor); + self.canvas.appendChild(vml); + self.attachListeners(vml, self); + + self.appendDisplayElement(vml, true); + self.appendDisplayElement(self.canvas, true); + + self.initOpacityNodes(vml, ["fill"]); + } + else { + //p["coordsize"] = "1,1";//(d[2] * scale) + "," + (d[3] * scale); again, unsure. + _pos(vml, [0,0, d[2], d[3]]); + _atts(vml, p); + } + + _applyStyles(vml, style, self); + }; + + this.reattachListeners = function() { + if (vml) self.reattachListenersForElement(vml, self); + }; + }; + + jsPlumb.Connectors.vml.Bezier = function() { + jsPlumb.Connectors.Bezier.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Straight = function() { + jsPlumb.Connectors.Straight.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Flowchart = function() { + jsPlumb.Connectors.Flowchart.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l"; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]); + } + // finally draw a line to the end + p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e"; + return p; + }; + }; + + jsPlumb.Endpoints.vml.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("oval", d, atts); }; + }; + + jsPlumb.Endpoints.vml.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("rect", d, atts); }; + }; + + /* + * VML Image Endpoint is the same as the default image endpoint. + */ + jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image; + + /** + * placeholder for Blank endpoint in vml renderer. + */ + jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank; + + /** + * VML Label renderer. uses the default label renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label; + + var AbstractVmlArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + VmlComponent.apply(this, arguments); + var self = this, path = null; + self.canvas = null; + var getPath = function(d, connectorDimensions) { + return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) + + " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + + " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + + " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + + " x e"; + }; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) { + var p = {}; + if (strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] = jsPlumb.util.convertStyle(strokeStyle, true); + } + if (lineWidth) p["strokeweight"] = lineWidth + "px"; + if (fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = fillStyle; + } + var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + w = Math.abs(xmax - xmin), + h = Math.abs(ymax - ymin), + dim = [xmin, ymin, w, h]; + + // for VML, we create overlays using shapes that have the same dimensions and + // coordsize as their connector - overlays calculate themselves relative to the + // connector (it's how it's been done since the original canvas implementation, because + // for canvas that makes sense). + p["path"] = getPath(d, connectorDimensions); + p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale); + + dim[0] = connectorDimensions[0]; + dim[1] = connectorDimensions[1]; + dim[2] = connectorDimensions[2]; + dim[3] = connectorDimensions[3]; + + if (self.canvas == null) { + //p["class"] = jsPlumb.overlayClass; // TODO currentInstance? + self.canvas = _node("shape", dim, p); + connector.appendDisplayElement(self.canvas); + self.attachListeners(self.canvas, connector); + } + else { + _pos(self.canvas, dim); + _atts(self.canvas, p); + } + }; + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + }; + + jsPlumb.Overlays.vml.Arrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.vml.PlainArrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.vml.Diamond = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.5/mootools.jsPlumb-1.3.5-RC1.js b/archive/1.3.5/mootools.jsPlumb-1.3.5-RC1.js new file mode 100644 index 000000000..0c7da13cc --- /dev/null +++ b/archive/1.3.5/mootools.jsPlumb-1.3.5-RC1.js @@ -0,0 +1,455 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the MooTools adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function(now) { + this.parent(now); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, _scope, value) { + var l = list[_scope]; + if (!l) { + l = []; + list[_scope] = l; + } + l.push(value); + }; + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + var _getElementObject = function(el) { + return $(el); + }, + + _removeNonPermanentDroppables = function(drag) { + /* + // remove non-permanent droppables from all arrays + var dbs = _droppables[drag.scope], d = []; + if (dbs) { + var d = []; + for (var i=0; i < dbs.length; i++) { + var isPermanent = dbs[i].getAttribute("_isPermanentDroppable"); + if (isPermanent === "true") d.push(dbs[i]); + } + dbs.splice(0, dbs.length); + _droppables[drag.scope] = d; + } + d = []; + // clear out transient droppables from the drag itself + for(var i = 0; i < drag.droppables.length; i++) { + var isPermanent = drag.droppables[i].getAttribute("_isPermanentDroppable"); + if (isPermanent === "true") d.push(drag.droppables[i]); + } + drag.droppables.splice(0, drag.droppables.length); // release old ones + drag.droppables = d; + //*/ + }; + + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el) + try { + if (el.className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.addClass(el, clazz); + } + else el.addClass(clazz); + } + catch (e) { + // SVGAnimatedString not supported; no problem. + el.addClass(clazz); + } + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + m.start(properties); + }, + + appendElement : function(child, parent) { + _getElementObject(parent).grab(child); + }, + + bind : function(el, event, callback) { + el = _getElementObject(el); + el.addEvent(event, callback); + }, + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.event.clientX, eventObject.event.clientY]; + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el), + drags = _draggablesById[id]; + return drags[0].scope; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getDOMElement : function(el) { + // MooTools just decorates the DOM elements. so we have either an ID or an Element here. + return typeof(el) == "String" ? document.getElementById(el) : el; + }, + + getElementObject : _getElementObject, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + getPageXY : function(eventObject) { + return [eventObject.event.pageX, eventObject.event.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).getParent(); + }, + + getScrollLeft : function(el) { + return null; + }, + + getScrollTop : function(el) { + return null; + }, + + getSelector : function(spec) { + return $$(spec); + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e != null ? e.tagName : null; + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[0], + p = jsPlumb.CurrentLibrary.getElementObject(ui).getPosition(); + return { left:p.x, top:p.y }; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options, isPlumbedComponent) { + var id = jsPlumb.getId(el); + var drag = _draggablesById[id]; + if (!drag) { + var originalZIndex = 0, + originalCursor = null, + dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + + options['onStart'] = jsPlumb.wrap(options['onStart'], function() { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + drag.originalZIndex = originalZIndex; + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + + _removeNonPermanentDroppables(drag); + }); + + // DROPPABLES - only relevant if this is a plumbed component, ie. not just the result of the user making some DOM element + // draggable. this is the only library adapter that has to care about this parameter. + var scope = "" + (options["scope"] || jsPlumb.Defaults.Scope), + filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }, + droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + + if (isPlumbedComponent) { + + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop'); + } + }; + } + else + options["droppables"] = []; + + drag = new Drag.Move(el, options); + drag.scope = scope; + drag.originalZIndex = originalZIndex; + _add(_draggablesById, el.get("id"), drag); + // again, only keep a record of this for scope stuff if this is a plumbed component (an endpoint) + if (isPlumbedComponent) { + _add(_draggablesByScope, scope, drag); + } + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + initDroppable : function(el, options, isPlumbedComponent, isPermanent) { + var scope = options["scope"]; + _add(_droppables, scope, el); + var id = jsPlumb.getId(el); + + el.setAttribute("_isPermanentDroppable", isPermanent); // we use this to clear out droppables on drag complete. + _droppableOptions[id] = options; + _droppableScopesById[id] = scope; + var filterFunc = function(entry) { return entry.element != el; }, + draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + isAlreadyDraggable : function(el) { + return _draggablesById[jsPlumb.getId(el)] != null; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + return (typeof Drag != undefined && typeof Drag.Move != undefined); + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el.className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.removeClass(el, clazz); + } + else el.removeClass(clazz); + } + catch (e) { + // SVGAnimatedString not supported; no problem. + el.removeClass(clazz); + } + }, + + removeElement : function(element, parent) { + var el = _getElementObject(element); + if (el) el.dispose(); // ?? + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + setDragScope : function(el, scope) { + var drag = _draggablesById[el.get("id")]; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + drag[0].droppables = droppables; + }, + + setOffset : function(el, o) { + _getElementObject(el).setPosition({x:o.left, y:o.top}); + }, + + stopDrag : function() { + for (var i in _draggablesById) { + for (var j = 0; j < _draggablesById[i].length; j++) { + var d = _draggablesById[i][j]; + d.stop(); + if (d.originalZIndex != 0) + d.element.setStyle("z-index", d.originalZIndex); + } + } + }, + + /** + * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself. + * the other libraries do not. yui, in fact, cannot even pass an original event. we have to pull out stuff + * from the originalEvent to put in an options object for YUI. + * @param el + * @param event + * @param originalEvent + */ + trigger : function(el, event, originalEvent) { + originalEvent.stopPropagation(); + _getElementObject(el).fireEvent(event, originalEvent); + }, + + unbind : function(el, event, callback) { + el = _getElementObject(el); + el.removeEvent(event, callback); + } + }; + + window.addEvent('domready', jsPlumb.init); +})(); diff --git a/archive/1.3.5/mootools.jsPlumb-1.3.5-all-min.js b/archive/1.3.5/mootools.jsPlumb-1.3.5-all-min.js new file mode 100644 index 000000000..4ef76f5f3 --- /dev/null +++ b/archive/1.3.5/mootools.jsPlumb-1.3.5-all-min.js @@ -0,0 +1 @@ +(function(){var w=!!document.createElement("canvas").getContext,e=!!window.SVGAngle||document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1"),a=!(w|e);var p=function(F,G,D,J){var I=function(M,L){if(M===L){return true}else{if(typeof M=="object"&&typeof L=="object"){var N=true;for(var K in M){if(!I(M[K],L[K])){N=false;break}}for(var K in L){if(!I(L[K],M[K])){N=false;break}}return N}}};for(var H=+D||0,E=F.length;H-1){E.splice(D,1)}return D!=-1},r=function(F,E,D){if(i(F,D)==-1){F.push(E)}};if(!window.console){window.console={time:function(){},timeEnd:function(){},group:function(){},groupEnd:function(){},log:function(){}}}var l=function(G,E,F){var D=G[E];if(D==null){D=[],G[E]=D}D.push(F);return D},u=null,d=function(D,E){return n.CurrentLibrary.getAttribute(A(D),E)},f=function(E,F,D){n.CurrentLibrary.setAttribute(A(E),F,D)},x=function(E,D){n.CurrentLibrary.addClass(A(E),D)},k=function(E,D){return n.CurrentLibrary.hasClass(A(E),D)},m=function(E,D){n.CurrentLibrary.removeClass(A(E),D)},A=function(D){return n.CurrentLibrary.getElementObject(D)},s=function(D){return n.CurrentLibrary.getOffset(A(D))},b=function(D){return n.CurrentLibrary.getSize(A(D))},z=true,o=function(){if(z&&typeof console!="undefined"){try{var E=arguments[arguments.length-1];console.log(E)}catch(D){}}},C=function(D){if(z&&typeof console!="undefined"){console.group(D)}},h=function(D){if(z&&typeof console!="undefined"){console.groupEnd(D)}},B=function(D){if(z&&typeof console!="undefined"){console.time(D)}},t=function(D){if(z&&typeof console!="undefined"){console.timeEnd(D)}};EventGenerator=function(){var F={},E=this;var D=["ready"];this.bind=function(G,H){l(F,G,H)};this.fire=function(I,J,G){if(F[I]){for(var H=0;H1){for(var T=0;T=0?N.overlays[O]:null};this.hideOverlay=function(P){var O=N.getOverlay(P);if(O){O.hide()}};this.showOverlay=function(P){var O=N.getOverlay(P);if(O){O.show()}};this.removeAllOverlays=function(){N.overlays.splice(0,N.overlays.length);N.repaint()};this.removeOverlay=function(P){var O=D(P);if(O!=-1){var Q=N.overlays[O];Q.cleanup();N.overlays.splice(O,1)}};this.removeOverlays=function(){for(var O=0;O0){try{for(var a7=0;a70?p(bj,bi)!=-1:true},ba=bd.length>1?{}:[],bg=function(bj,bk){if(bd.length>1){var bi=ba[bj];if(bi==null){bi=[];ba[bj]=bi}bi.push(bk)}else{ba.push(bk)}};for(var a9 in aD){if(a7(bd,a9)){for(var a8=0;a8=4)?[bb[2],bb[3]]:[0,0],offsets:(bb.length==6)?[bb[4],bb[5]]:[0,0],elementId:a8};a9=new Q(ba);a9.clone=function(){return new Q(ba)}}}}}if(!a9.id){a9.id="anchor_"+Z()}return a9};this.makeAnchors=function(a9,a7,a6){var ba=[];for(var a8=0;a80?bg[0]:null,bb=bg.length>0?0:-1,bf=this,ba=function(bj,bh,bn,bm,bi){var bl=bm[0]+(bj.x*bi[0]),bk=bm[1]+(bj.y*bi[1]);return Math.sqrt(Math.pow(bh-bl,2)+Math.pow(bn-bk,2))},a6=a7||function(br,bi,bj,bk,bh){var bm=bj[0]+(bk[0]/2),bl=bj[1]+(bk[1]/2);var bo=-1,bq=Infinity;for(var bn=0;bn=a9.left)||(bc.left<=a9.right&&bc.right>=a9.right)||(bc.left<=a9.left&&bc.right>=a9.right)||(a9.left<=bc.left&&a9.right>=bc.right)),bh=((bc.top<=a9.top&&bc.bottom>=a9.top)||(bc.top<=a9.bottom&&bc.bottom>=a9.bottom)||(bc.top<=a9.top&&bc.bottom>=a9.bottom)||(a9.top<=bc.top&&a9.bottom>=bc.bottom));if(!(bb||bh)){var be=null,a8=false,a6=false,bd=null;if(a9.left>bc.left&&a9.top>bc.top){be=["right","top"]}else{if(a9.left>bc.left&&bc.top>a9.top){be=["top","left"]}else{if(a9.leftbc.top){be=["left","top"]}}}}return{orientation:K.DIAGONAL,a:be,theta:a7,theta2:ba}}else{if(bb){return{orientation:K.HORIZONTAL,a:bc.topa6[0]?1:-1},R=function(a6){return function(a8,a7){var a9=true;if(a6){if(a8[0][0]a7[0][1]}}else{if(a8[0][0]>a7[0][0]){a9=true}else{a9=a8[0][1]>a7[0][1]}}return a9===false?-1:1}},G=function(a7,a6){var a9=a7[0][0]<0?-Math.PI-a7[0][0]:Math.PI-a7[0][0],a8=a6[0][0]<0?-Math.PI-a6[0][0]:Math.PI-a6[0][0];if(a9>a8){return 1}else{return a7[0][1]>a6[0][1]?1:-1}},aF={top:aL,right:R(true),bottom:R(true),left:G},ac=function(a6,a7){return a6.sort(a7)},aa=function(a7,a6){var a9=U[a7],ba=X[a7],a8=function(bg,bn,bc,bf,bl,bk){if(bf.length>0){var bj=ac(bf,aF[bg]),bh=bg==="right"||bg==="top",bb=aI(bg,bn,bc,bj,bl,bk,bh);var bo=function(br,bq){var bp=a0([bq[0],bq[1]],br.canvas);Y[br.id]=[bp[0],bp[1],bq[2],bq[3]]};for(var bd=0;bd-1){bc.splice(bd,1)}else{y(a6[be.elementId],function(bf){return bf.id==be.id})}};this.clearFor=function(bd){delete a6[bd];a6[bd]=[]};var ba=function(bx,bk,bs,bh,bn,bo,bq,bm,bz,bp,bg,bw){var bu=-1,bf=-1,bi=bh.endpoints[bq],br=bi.id,bl=[1,0][bq],bd=[[bk,bs],bh,bn,bo,br],be=bx[bz],by=bi._continuousAnchorEdge?bx[bi._continuousAnchorEdge]:null;if(by){var bv=i(by,function(bA){return bA[4]==br});if(bv!=-1){by.splice(bv,1);for(var bt=0;bt0){bp.connections[0].setHover(bC,false)}else{bp.setHover(bC)}};_bindListeners(bp.endpoint,bp,bB);this.setPaintStyle(bA.paintStyle||bA.style||aZ.Defaults.EndpointStyle||n.Defaults.EndpointStyle,true);this.setHoverPaintStyle(bA.hoverPaintStyle||aZ.Defaults.EndpointHoverStyle||n.Defaults.EndpointHoverStyle,true);this.paintStyleInUse=this.paintStyle;this.connectorStyle=bA.connectorStyle;this.connectorHoverStyle=bA.connectorHoverStyle;this.connectorOverlays=bA.connectorOverlays;this.connector=bA.connector;this.connectorTooltip=bA.connectorTooltip;this.isSource=bA.isSource||false;this.isTarget=bA.isTarget||false;var bu=bA.maxConnections||aZ.Defaults.MaxConnections;this.getAttachedElements=function(){return bp.connections};this.canvas=this.endpoint.canvas;this.connections=bA.connections||[];this.scope=bA.scope||I;this.timestamp=null;bp.isReattach=bA.reattach||false;bp.connectionsDetachable=aZ.Defaults.ConnectionsDetachable;if(bA.connectionsDetachable===false||bA.detachable===false){bp.connectionsDetachable=false}var bk=bA.dragAllowedWhenFull||true;this.computeAnchor=function(bC){return bp.anchor.compute(bC)};this.addConnection=function(bC){bp.connections.push(bC)};this.detach=function(bC,bG,bD,bK){var bJ=i(bp.connections,function(bM){return bM.id==bC.id}),bI=false;bK=(bK!==false);if(bJ>=0){if(bD||bC._forceDetach||bC.isDetachable()||bC.isDetachAllowed(bC)){var bL=bC.endpoints[0]==bp?bC.endpoints[1]:bC.endpoints[0];if(bD||bC._forceDetach||(bp.isDetachAllowed(bC))){bp.connections.splice(bJ,1);if(!bG){bL.detach(bC,true,bD);if(bC.endpointsToDeleteOnDetach){for(var bH=0;bH0){bp.detach(bp.connections[0],false,true,bC)}};this.detachFrom=function(bE,bD){var bF=[];for(var bC=0;bC=0){bp.connections.splice(bC,1)}};this.getElement=function(){return bo};this.setElement=function(bE){var bG=D(bE);y(az[bf],function(bH){return bH.id==bp.id});bo=A(bE);bf=D(bo);bp.elementId=bf;var bF=ai({source:bG}),bD=a9.getParent(bp.canvas);a9.removeElement(bp.canvas,bD);a9.appendElement(bp.canvas,bF);for(var bC=0;bC0){var bO=bl(bF.elementWithPrecedence),bQ=bO.endpoints[0]==bp?1:0,bH=bQ==0?bO.sourceId:bO.targetId,bN=X[bH],bP=U[bH];bE.txy=[bN.left,bN.top];bE.twh=bP;bE.tElement=bO.endpoints[bQ]}bI=bp.anchor.compute(bE)}var bM=bm.compute(bI,bp.anchor.getOrientation(bm),bp.paintStyleInUse,bG||bp.paintStyleInUse);bm.paint(bM,bp.paintStyleInUse,bp.anchor);bp.timestamp=bL;for(var bJ=0;bJE[0]){return(D[1]>E[1])?2:1}else{return(D[1]>E[1])?3:4}},segmentMultipliers:[null,[1,-1],[1,1],[-1,1],[-1,-1]],inverseSegmentMultipliers:[null,[-1,-1],[-1,1],[1,1],[1,-1]],pointOnLine:function(D,H,E){var G=n.util.gradient(D,H),L=n.util.segment(D,H),K=E>0?n.util.segmentMultipliers[L]:n.util.inverseSegmentMultipliers[L],F=Math.atan(G),I=Math.abs(E*Math.sin(F))*K[1],J=Math.abs(E*Math.cos(F))*K[0];return{x:D.x+J,y:D.y+I}},perpendicularLineTo:function(F,G,H){var E=n.util.gradient(F,G),I=Math.atan(-1/E),J=H/2*Math.sin(I),D=H/2*Math.cos(I);return[{x:G.x+D,y:G.y+J},{x:G.x-D,y:G.y-J}]}};var q=function(D,I,F,E,H,G){return function(K){K=K||{};var J=K.jsPlumbInstance.makeAnchor([D,I,F,E,0,0],K.elementId,K.jsPlumbInstance);J.type=H;if(G){G(J,K)}return J}};n.Anchors.TopCenter=q(0.5,0,0,-1,"TopCenter");n.Anchors.BottomCenter=q(0.5,1,0,1,"BottomCenter");n.Anchors.LeftMiddle=q(0,0.5,-1,0,"LeftMiddle");n.Anchors.RightMiddle=q(1,0.5,1,0,"RightMiddle");n.Anchors.Center=q(0.5,0.5,0,0,"Center");n.Anchors.TopRight=q(1,0,0,-1,"TopRight");n.Anchors.BottomRight=q(1,1,0,1,"BottomRight");n.Anchors.TopLeft=q(0,0,0,-1,"TopLeft");n.Anchors.BottomLeft=q(0,1,0,1,"BottomLeft");n.Defaults.DynamicAnchors=function(D){return D.jsPlumbInstance.makeAnchors(["TopCenter","RightMiddle","BottomCenter","LeftMiddle"],D.elementId,D.jsPlumbInstance)};n.Anchors.AutoDefault=function(E){var D=E.jsPlumbInstance.makeDynamicAnchor(n.Defaults.DynamicAnchors(E));D.type="AutoDefault";return D};n.Anchors.Assign=q(0,0,0,0,"Assign",function(E,F){var D=F.position||"Fixed";E.positionFinder=D.constructor==String?F.jsPlumbInstance.AnchorPositionFinders[D]:D;E.constructorParams=F});n.Anchors.Continuous=function(D){return D.jsPlumbInstance.continuousAnchorFactory.get(D)};n.AnchorPositionFinders={Fixed:function(G,E,F,D){return[(G.left-E.left)/F[0],(G.top-E.top)/F[1]]},Grid:function(D,M,H,E){var L=D.left-M.left,K=D.top-M.top,J=H[0]/(E.constructorParams.grid[0]),I=H[1]/(E.constructorParams.grid[1]),G=Math.floor(L/J),F=Math.floor(K/I);return[((G*J)+(J/2))/H[0],((F*I)+(I/2))/H[1]]}}})();(function(){jsPlumb.DOMElementComponent=function(c){jsPlumb.jsPlumbUIComponent.apply(this,arguments);this.mousemove=this.dblclick=this.click=this.mousedown=this.mouseup=function(d){}};jsPlumb.Connectors.Straight=function(){this.type="Straight";var r=this,i=null,e,k,p,n,l,f,q,h,g,d,c,o,m;this.compute=function(A,J,s,z,F,t,D,v){var I=Math.abs(A[0]-J[0]),C=Math.abs(A[1]-J[1]),B=0.45*I,u=0.45*C;I*=1.9;C*=1.9;var G=Math.min(A[0],J[0])-B;var E=Math.min(A[1],J[1])-u;var H=Math.max(2*D,v);if(In){n=x}if(A<0){f+=A;var C=Math.abs(A);n+=C;p[0]+=C;l+=C;d+=C;o[0]+=C}var K=Math.min(h,c),I=Math.min(p[1],o[1]),w=Math.min(K,I),B=Math.max(h,c),z=Math.max(p[1],o[1]),t=Math.max(B,z);if(t>i){i=t}if(w<0){e+=w;var y=Math.abs(w);i+=y;p[1]+=y;h+=y;c+=y;o[1]+=y}if(G&&n=t){r=u;s=(t-m[u][0])/c[u];break}}return{segment:k[r],proportion:s,index:r}};this.compute=function(Q,ae,r,J,ao,D,O,I,aj,ag){k=[];o=0;c=[];f=ae[0]an?0:1,Y=[1,0][W];G=[];ap=[];G[W]=Q[W]>ae[W]?-1:1;ap[W]=Q[W]>ae[W]?1:-1;G[Y]=0;ap[Y]=0}if(af2*d,R=Math.abs(v-ak)>2*d,ab=T+((E-T)/2),Z=S+((C-S)/2),H=((G[0]*ap[0])+(G[1]*ap[1])),V=H==-1,X=H==0,s=H==1;ad-=B;ac-=z;n=[ad,ac,af,an,A,v,al,ak];var ai=[];g(T,S,A,v,al,ak);var L=G[0]==0?"y":"x",F=V?"opposite":s?"orthogonal":"perpendicular",t=jsPlumb.util.segment([A,v],[al,ak]),aa=G[L=="x"?0:1]==-1,K={x:[null,4,3,2,1],y:[null,2,1,4,3]};if(aa){t=K[L][t]}var N=function(aq,y,w,x){return aq+(y*((1-w)*x)+d)},u={oppositex:function(){if(r.elementId==J.elementId){var w=S+((1-ao.y)*aj.height)+d;return[[T,w],[E,w]]}else{if(P&&(t==1||t==2)){return[[ab,v],[ab,ak]]}else{return[[T,Z],[E,Z]]}}},orthogonalx:function(){if(t==1||t==2){return[[E,S]]}else{return[[T,C]]}},perpendicularx:function(){var w=(ak+v)/2;if((t==1&&ap[1]==1)||(t==2&&ap[1]==-1)){if(Math.abs(al-A)>d){return[[E,S]]}else{return[[T,S],[T,w],[E,w]]}}else{if((t==3&&ap[1]==-1)||(t==4&&ap[1]==1)){return[[T,w],[E,w]]}else{if((t==3&&ap[1]==1)||(t==4&&ap[1]==-1)){return[[T,C]]}else{if((t==1&&ap[1]==-1)||(t==2&&ap[1]==1)){if(Math.abs(al-A)>d){return[[ab,S],[ab,C]]}else{return[[T,C]]}}}}}},oppositey:function(){if(r.elementId==J.elementId){var w=T+((1-ao.x)*aj.width)+d;return[[w,S],[w,C]]}else{if(R&&(t==2||t==3)){return[[A,Z],[al,Z]]}else{return[[ab,S],[ab,C]]}}},orthogonaly:function(){if(t==2||t==3){return[[T,C]]}else{return[[E,S]]}},perpendiculary:function(){var w=(al+A)/2;if((t==2&&ap[0]==-1)||(t==3&&ap[0]==1)){if(Math.abs(al-A)>d){return[[T,C]]}else{return[[T,Z],[E,Z]]}}else{if((t==1&&ap[0]==-1)||(t==4&&ap[0]==1)){var w=(al+A)/2;return[[w,S],[w,C]]}else{if((t==1&&ap[0]==1)||(t==4&&ap[0]==-1)){return[[E,S]]}else{if((t==2&&ap[0]==1)||(t==3&&ap[0]==-1)){if(Math.abs(ak-v)>d){return[[T,Z],[E,Z]]}else{return[[E,S]]}}}}}}};var M=u[F+L];var ah=M();if(ah){for(var am=0;amu[0]?u[0]+((1-y)*t)-z:u[2]+(y*t)+z,y:r==0?u[3]:u[3]>u[1]?u[1]+((1-y)*t)-z:u[3]+(y*t)+z,segmentInfo:w};return x}};jsPlumb.Endpoints.Dot=function(d){this.type="Dot";var c=this;d=d||{};this.radius=d.radius||10;this.defaultOffset=0.5*this.radius;this.defaultInnerRadius=this.radius/3;this.compute=function(i,f,l,h){var g=l.radius||c.radius,e=i[0]-g,k=i[1]-g;return[e,k,g*2,g*2,g]}};jsPlumb.Endpoints.Rectangle=function(d){this.type="Rectangle";var c=this;d=d||{};this.width=d.width||20;this.height=d.height||20;this.compute=function(k,g,m,i){var h=m.width||c.width,f=m.height||c.height,e=k[0]-(h/2),l=k[1]-(f/2);return[e,l,h,f]}};var a=function(e){jsPlumb.DOMElementComponent.apply(this,arguments);var c=this;var d=[];this.getDisplayElements=function(){return d};this.appendDisplayElement=function(f){d.push(f)}};jsPlumb.Endpoints.Image=function(g){this.type="Image";a.apply(this,arguments);var l=this,f=false,e=g.width,d=g.height,i=null,c=g.endpoint;this.img=new Image();l.ready=false;this.img.onload=function(){l.ready=true;e=e||l.img.width;d=d||l.img.height;if(i){i(l)}};c.setImage=function(m,o){var n=m.constructor==String?m:m.src;i=o;l.img.src=m};c.setImage(g.src||g.url,g.onload);this.compute=function(o,m,p,n){l.anchorPoint=o;if(l.ready){return[o[0]-e/2,o[1]-d/2,e,d]}else{return[0,0,0,0]}};l.canvas=document.createElement("img"),f=false;l.canvas.style.margin=0;l.canvas.style.padding=0;l.canvas.style.outline=0;l.canvas.style.position="absolute";var h=g.cssClass?" "+g.cssClass:"";l.canvas.className=jsPlumb.endpointClass+h;if(e){l.canvas.setAttribute("width",e)}if(d){l.canvas.setAttribute("height",d)}jsPlumb.appendElement(l.canvas,g.parent);l.attachListeners(l.canvas,l);var k=function(p,o,n){if(!f){l.canvas.setAttribute("src",l.img.src);f=true}var m=l.anchorPoint[0]-(e/2),q=l.anchorPoint[1]-(d/2);jsPlumb.sizeCanvas(l.canvas,m,q,e,d)};this.paint=function(o,n,m){if(l.ready){k(o,n,m)}else{window.setTimeout(function(){l.paint(o,n,m)},200)}}};jsPlumb.Endpoints.Blank=function(d){var c=this;this.type="Blank";a.apply(this,arguments);this.compute=function(g,e,h,f){return[g[0],g[1],10,0]};c.canvas=document.createElement("div");c.canvas.style.display="block";c.canvas.style.width="1px";c.canvas.style.height="1px";c.canvas.style.background="transparent";c.canvas.style.position="absolute";c.canvas.className=c._jsPlumb.endpointClass;jsPlumb.appendElement(c.canvas,d.parent);this.paint=function(g,f,e){jsPlumb.sizeCanvas(c.canvas,g[0],g[1],g[2],g[3])}};jsPlumb.Endpoints.Triangle=function(c){this.type="Triangle";c=c||{};c.width=c.width||55;c.height=c.height||55;this.width=c.width;this.height=c.height;this.compute=function(i,f,l,h){var g=l.width||self.width,e=l.height||self.height,d=i[0]-(g/2),k=i[1]-(e/2);return[d,k,g,e]}};var b=function(e){var d=true,c=this;this.isAppendedAtTopLevel=true;this.component=e.component;this.loc=e.location==null?0.5:e.location;this.endpointLoc=e.endpointLocation==null?[0.5,0.5]:e.endpointLocation;this.setVisible=function(f){d=f;c.component.repaint()};this.isVisible=function(){return d};this.hide=function(){c.setVisible(false)};this.show=function(){c.setVisible(true)};this.incrementLocation=function(f){c.loc+=f;c.component.repaint()};this.setLocation=function(f){c.loc=f;c.component.repaint()};this.getLocation=function(){return c.loc}};jsPlumb.Overlays.Arrow=function(g){this.type="Arrow";b.apply(this,arguments);this.isAppendedAtTopLevel=false;g=g||{};var d=this;this.length=g.length||20;this.width=g.width||20;this.id=g.id;var f=(g.direction||1)<0?-1:1,e=g.paintStyle||{lineWidth:1},c=g.foldback||0.623;this.computeMaxSize=function(){return d.width*1.5};this.cleanup=function(){};this.draw=function(i,x,s){var m,t,h,n,l;if(i.pointAlongPathFrom){if(d.loc==1){m=i.pointOnPath(d.loc);t=i.pointAlongPathFrom(d.loc,-1);h=jsPlumb.util.pointOnLine(m,t,d.length)}else{if(d.loc==0){h=i.pointOnPath(d.loc);t=i.pointAlongPathFrom(d.loc,1);m=jsPlumb.util.pointOnLine(h,t,d.length)}else{m=i.pointAlongPathFrom(d.loc,f*d.length/2),t=i.pointOnPath(d.loc),h=jsPlumb.util.pointOnLine(m,t,d.length)}}n=jsPlumb.util.perpendicularLineTo(m,h,d.width);l=jsPlumb.util.pointOnLine(m,h,c*d.length);var w=Math.min(m.x,n[0].x,n[1].x),q=Math.max(m.x,n[0].x,n[1].x),v=Math.min(m.y,n[0].y,n[1].y),p=Math.max(m.y,n[0].y,n[1].y);var o={hxy:m,tail:n,cxy:l},r=e.strokeStyle||x.strokeStyle,u=e.fillStyle||x.strokeStyle,k=e.lineWidth||x.lineWidth;d.paint(i,o,k,r,u,s);return[w,q,v,p]}else{return[0,0,0,0]}}};jsPlumb.Overlays.PlainArrow=function(d){d=d||{};var c=jsPlumb.extend(d,{foldback:1});jsPlumb.Overlays.Arrow.call(this,c);this.type="PlainArrow"};jsPlumb.Overlays.Diamond=function(e){e=e||{};var c=e.length||40,d=jsPlumb.extend(e,{length:c/2,foldback:2});jsPlumb.Overlays.Arrow.call(this,d);this.type="Diamond"};jsPlumb.Overlays.Label=function(i){this.type="Label";jsPlumb.DOMElementComponent.apply(this,arguments);b.apply(this,arguments);this.labelStyle=i.labelStyle||jsPlumb.Defaults.LabelStyle;this.id=i.id;this.cachedDimensions=null;var e=i.label||"",c=this,f=false,k=document.createElement("div"),g=null;k.style.position="absolute";var d=i._jsPlumb.overlayClass+" "+(c.labelStyle.cssClass?c.labelStyle.cssClass:i.cssClass?i.cssClass:"");k.className=d;jsPlumb.appendElement(k,i.component.parent);jsPlumb.getId(k);c.attachListeners(k,c);c.canvas=k;var h=c.setVisible;c.setVisible=function(l){h(l);k.style.display=l?"block":"none"};this.getElement=function(){return k};this.cleanup=function(){if(k!=null){jsPlumb.CurrentLibrary.removeElement(k)}};this.setLabel=function(m){e=m;g=null;c.component.repaint()};this.getLabel=function(){return e};this.paint=function(l,n,m){if(!f){l.appendDisplayElement(k);c.attachListeners(k,l);f=true}k.style.left=(m[0]+n.minx)+"px";k.style.top=(m[1]+n.miny)+"px"};this.getTextDimensions=function(){if(typeof e=="function"){var l=e(c);k.innerHTML=l.replace(/\r\n/g,"
")}else{if(g==null){g=e;k.innerHTML=g.replace(/\r\n/g,"
")}}var n=jsPlumb.CurrentLibrary.getElementObject(k),m=jsPlumb.CurrentLibrary.getSize(n);return{width:m[0],height:m[1]}};this.computeMaxSize=function(l){var m=c.getTextDimensions(l);return m.width?Math.max(m.width,m.height)*1.5:0};this.draw=function(m,n,o){var q=c.getTextDimensions(m);if(q.width!=null){var p={x:0,y:0};if(m.pointOnPath){p=m.pointOnPath(c.loc)}else{var l=c.loc.constructor==Array?c.loc:c.endpointLoc;p={x:l[0]*o[2],y:l[1]*o[3]}}minx=p.x-(q.width/2),miny=p.y-(q.height/2);c.paint(m,{minx:minx,miny:miny,td:q,cxy:p},o);return[minx,minx+q.width,miny,miny+q.height]}else{return[0,0,0,0]}};this.reattachListeners=function(l){if(k){c.reattachListenersForElement(k,c,l)}}};jsPlumb.Overlays.GuideLines=function(){var c=this;c.length=50;c.lineWidth=5;this.type="GuideLines";b.apply(this,arguments);jsPlumb.jsPlumbUIComponent.apply(this,arguments);this.draw=function(e,l,k){var i=e.pointAlongPathFrom(c.loc,c.length/2),h=e.pointOnPath(c.loc),g=jsPlumb.util.pointOnLine(i,h,c.length),f=jsPlumb.util.perpendicularLineTo(i,g,40),d=jsPlumb.util.perpendicularLineTo(g,i,20);c.paint(e,[i,g,f,d],c.lineWidth,"red",null,k);return[Math.min(i.x,g.x),Math.min(i.y,g.y),Math.max(i.x,g.x),Math.max(i.y,g.y)]};this.computeMaxSize=function(){return 50};this.cleanup=function(){}}})();(function(){var c=function(e,g,d,f){this.m=(f-g)/(d-e);this.b=-1*((this.m*e)-g);this.rectIntersect=function(q,p,s,o){var n=[];var k=(p-this.b)/this.m;if(k>=q&&k<=(q+s)){n.push([k,(this.m*k)+this.b])}var t=(this.m*(q+s))+this.b;if(t>=p&&t<=(p+o)){n.push([(t-this.b)/this.m,t])}var k=((p+o)-this.b)/this.m;if(k>=q&&k<=(q+s)){n.push([k,(this.m*k)+this.b])}var t=(this.m*q)+this.b;if(t>=p&&t<=(p+o)){n.push([(t-this.b)/this.m,t])}if(n.length==2){var m=(n[0][0]+n[1][0])/2,l=(n[0][1]+n[1][1])/2;n.push([m,l]);var i=m<=q+(s/2)?-1:1,r=l<=p+(o/2)?-1:1;n.push([i,r]);return n}return null}},a=function(e,g,d,f){if(e<=d&&f<=g){return 1}else{if(e<=d&&g<=f){return 2}else{if(d<=e&&f>=g){return 3}}}return 4},b=function(g,f,i,e,h,m,l,d,k){if(d<=k){return[g,f]}if(i==1){if(e[3]<=0&&h[3]>=1){return[g+(e[2]<0.5?-1*m:m),f]}else{if(e[2]>=1&&h[2]<=0){return[g,f+(e[3]<0.5?-1*l:l)]}else{return[g+(-1*m),f+(-1*l)]}}}else{if(i==2){if(e[3]>=1&&h[3]<=0){return[g+(e[2]<0.5?-1*m:m),f]}else{if(e[2]>=1&&h[2]<=0){return[g,f+(e[3]<0.5?-1*l:l)]}else{return[g+(1*m),f+(-1*l)]}}}else{if(i==3){if(e[3]>=1&&h[3]<=0){return[g+(e[2]<0.5?-1*m:m),f]}else{if(e[2]<=0&&h[2]>=1){return[g,f+(e[3]<0.5?-1*l:l)]}else{return[g+(-1*m),f+(-1*l)]}}}else{if(i==4){if(e[3]<=0&&h[3]>=1){return[g+(e[2]<0.5?-1*m:m),f]}else{if(e[2]<=0&&h[2]>=1){return[g,f+(e[3]<0.5?-1*l:l)]}else{return[g+(1*m),f+(-1*l)]}}}}}}};jsPlumb.Connectors.StateMachine=function(l){var s=this,n=null,o,m,g,e,p=[],d=l.curviness||10,k=l.margin||5,q=l.proximityLimit||80,f=l.orientation&&l.orientation=="clockwise",i=l.loopbackRadius||25,h=false;this.type="StateMachine";l=l||{};this.compute=function(ab,F,U,G,aa,u,t,S){var O=Math.abs(ab[0]-F[0]),W=Math.abs(ab[1]-F[1]),Q=0.45*O,Z=0.45*W;O*=1.9;W*=1.9;t=t||1;var M=Math.min(ab[0],F[0])-Q,K=Math.min(ab[1],F[1])-Z;if(U.elementId!=G.elementId){h=false;o=ab[0]0&&v<1){v=1-v}var w=(v*2*Math.PI)+(Math.PI/2),u=n[4]+(n[6]*Math.cos(w)),t=n[5]+(n[6]*Math.sin(w));return{x:u,y:t}}else{return jsBezier.pointOnCurve(r(),v)}};this.gradientAtPoint=function(t){if(h){return Math.atan(t*2*Math.PI)}else{return jsBezier.gradientAtPoint(r(),t)}};this.pointAlongPathFrom=function(v,z){if(h){if(v>0&&v<1){v=1-v}var w=2*Math.PI*n[6],y=z/w*2*Math.PI,x=(v*2*Math.PI)-y+(Math.PI/2),u=n[4]+(n[6]*Math.cos(x)),t=n[5]+(n[6]*Math.sin(x));return{x:u,y:t}}return jsBezier.pointAlongCurveFrom(r(),v,z)}};jsPlumb.Connectors.canvas.StateMachine=function(f){f=f||{};var d=this,g=f.drawGuideline||true,e=f.avoidSelector;jsPlumb.Connectors.StateMachine.apply(this,arguments);jsPlumb.CanvasConnector.apply(this,arguments);this._paint=function(l){if(l.length==10){d.ctx.beginPath();d.ctx.moveTo(l[4],l[5]);d.ctx.quadraticCurveTo(l[8],l[9],l[6],l[7]);d.ctx.stroke()}else{d.ctx.save();d.ctx.beginPath();var k=0,i=2*Math.PI,h=l[7];d.ctx.arc(l[4],l[5],l[6],0,i,h);d.ctx.stroke();d.ctx.closePath();d.ctx.restore()}};this.createGradient=function(i,h){return h.createLinearGradient(i[4],i[5],i[6],i[7])}};jsPlumb.Connectors.svg.StateMachine=function(){var d=this;jsPlumb.Connectors.StateMachine.apply(this,arguments);jsPlumb.SvgConnector.apply(this,arguments);this.getPath=function(e){if(e.length==10){return"M "+e[4]+" "+e[5]+" C "+e[8]+" "+e[9]+" "+e[8]+" "+e[9]+" "+e[6]+" "+e[7]}else{return"M"+(e[8]+4)+" "+e[9]+" A "+e[6]+" "+e[6]+" 0 1,0 "+(e[8]-4)+" "+e[9]}}};jsPlumb.Connectors.vml.StateMachine=function(){jsPlumb.Connectors.StateMachine.apply(this,arguments);jsPlumb.VmlConnector.apply(this,arguments);var d=jsPlumb.vml.convertValue;this.getPath=function(k){if(k.length==10){return"m"+d(k[4])+","+d(k[5])+" c"+d(k[8])+","+d(k[9])+","+d(k[8])+","+d(k[9])+","+d(k[6])+","+d(k[7])+" e"}else{var h=d(k[8]-k[6]),g=d(k[9]-(2*k[6])),f=h+d(2*k[6]),e=g+d(2*k[6]),l=h+","+g+","+f+","+e;var i="ar "+l+","+d(k[8])+","+d(k[9])+","+d(k[8])+","+d(k[9])+" e";return i}}}})();(function(){var k={"stroke-linejoin":"joinstyle",joinstyle:"joinstyle",endcap:"endcap",miterlimit:"miterlimit"};if(document.createStyleSheet){document.createStyleSheet().addRule(".jsplumb_vml","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:textbox","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:oval","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:rect","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:stroke","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:shape","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:group","behavior:url(#default#VML);position:absolute;");document.namespaces.add("jsplumb","urn:schemas-microsoft-com:vml")}jsPlumb.vml={};var b=1000,d={},h=function(q,p){var s=jsPlumb.getId(q),r=d[s];if(!r){r=o("group",[0,0,b,b],{"class":p});r.style.backgroundColor="red";d[s]=r;jsPlumb.appendElement(r,q)}return r},c=function(q,r){for(var p in r){q[p]=r[p]}},o=function(p,r,s){s=s||{};var q=document.createElement("jsplumb:"+p);q.className=(s["class"]?s["class"]+" ":"")+"jsplumb_vml";n(q,r);c(q,s);return q},n=function(q,p){q.style.left=p[0]+"px";q.style.top=p[1]+"px";q.style.width=p[2]+"px";q.style.height=p[3]+"px";q.style.position="absolute"},i=jsPlumb.vml.convertValue=function(p){return Math.floor(p*b)},e=function(s,q,r,p){if("transparent"===q){p.setOpacity(r,"0.0")}else{p.setOpacity(r,"1.0")}},g=function(t,p,w){var s={};if(p.strokeStyle){s.stroked="true";var x=jsPlumb.util.convertStyle(p.strokeStyle,true);s.strokecolor=x;e(s,x,"stroke",w);s.strokeweight=p.lineWidth+"px"}else{s.stroked="false"}if(p.fillStyle){s.filled="true";var q=jsPlumb.util.convertStyle(p.fillStyle,true);s.fillcolor=q;e(s,q,"fill",w)}else{s.filled="false"}if(p.dashstyle){if(w.strokeNode==null){w.strokeNode=o("stroke",[0,0,0,0],{dashstyle:p.dashstyle});t.appendChild(w.strokeNode)}else{w.strokeNode.dashstyle=p.dashstyle}}else{if(p["stroke-dasharray"]&&p.lineWidth){var y=p["stroke-dasharray"].indexOf(",")==-1?" ":",",u=p["stroke-dasharray"].split(y),r="";for(var v=0;v0&&C>0&&u=u&&w[2]<=C&&w[3]>=C)){return true}}var A=q.canvas.getContext("2d").getImageData(parseInt(u),parseInt(C),1,1);return A.data[0]!=0||A.data[1]!=0||A.data[2]!=0||A.data[3]!=0}return false};var p=false,o=false,t=null,s=false,r=function(v,u){return v!=null&&i(v,u)};this.mousemove=function(x){var z=n(x),w=f(x),v=document.elementFromPoint(w[0],w[1]),y=r(v,"_jsPlumb_overlay");var u=d==null&&(r(v,"_jsPlumb_endpoint")||r(v,"_jsPlumb_connector"));if(!p&&u&&q._over(x)){p=true;q.fire("mouseenter",q,x);return true}else{if(p&&(!q._over(x)||!u)&&!y){p=false;q.fire("mouseexit",q,x)}}q.fire("mousemove",q,x)};this.click=function(u){if(p&&q._over(u)&&!s){q.fire("click",q,u)}s=false};this.dblclick=function(u){if(p&&q._over(u)&&!s){q.fire("dblclick",q,u)}s=false};this.mousedown=function(u){if(q._over(u)&&!o){o=true;t=m(a(q.canvas));q.fire("mousedown",q,u)}};this.mouseup=function(u){o=false;q.fire("mouseup",q,u)};this.contextmenu=function(u){if(p&&q._over(u)&&!s){q.fire("contextmenu",q,u)}s=false}};var c=function(p){var o=document.createElement("canvas");jsPlumb.appendElement(o,p.parent);o.style.position="absolute";if(p["class"]){o.className=p["class"]}p._jsPlumb.getId(o,p.uuid);if(p.tooltip){o.setAttribute("title",p.tooltip)}return o};var l=function(p){k.apply(this,arguments);var o=[];this.getDisplayElements=function(){return o};this.appendDisplayElement=function(q){o.push(q)}};var h=jsPlumb.CanvasConnector=function(r){l.apply(this,arguments);var o=function(v,t){p.ctx.save();jsPlumb.extend(p.ctx,t);if(t.gradient){var u=p.createGradient(v,p.ctx);for(var s=0;sq?q=s:sl.location){l.location=0}return i(m,l.location)},nearestPointOnCurve:function(m,l){var n=h(m,l);return{point:a(l,l.length-1,n.location,null,null),location:n.location}},pointOnCurve:c,pointAlongCurveFrom:function(m,l,n){return k(m,l,n).point},perpendicularToCurveAt:function(m,l,n,o){l=k(m,l,null==o?0:o);m=i(m,l.location);o=Math.atan(-1/m);m=n/2*Math.sin(o);n=n/2*Math.cos(o);return[{x:l.point.x+n,y:l.point.y+m},{x:l.point.x-n,y:l.point.y-m}]}}})(); \ No newline at end of file diff --git a/archive/1.3.5/mootools.jsPlumb-1.3.5-all.js b/archive/1.3.5/mootools.jsPlumb-1.3.5-all.js new file mode 100644 index 000000000..626345799 --- /dev/null +++ b/archive/1.3.5/mootools.jsPlumb-1.3.5-all.js @@ -0,0 +1,8367 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the jsPlumb core code. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + /** + * Class:jsPlumb + * The jsPlumb engine, registered as a static object in the window. This object contains all of the methods you will use to + * create and maintain Connections and Endpoints. + */ + + var canvasAvailable = !!document.createElement('canvas').getContext, + svgAvailable = !!window.SVGAngle || document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"), + // TODO what is a good test for VML availability? aside from just assuming its there because nothing else is. + vmlAvailable = !(canvasAvailable | svgAvailable); + + // finds the index of some value in an array, using a deep equals test. + var _findIndex = function(a, v, b, s) { + var _eq = function(o1, o2) { + if (o1 === o2) return true; + else if (typeof o1 == "object" && typeof o2 == "object") { + var same = true; + for ( var propertyName in o1) { + if (!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for ( var propertyName in o2) { + if (!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for ( var i = +b || 0, l = a.length; i < l; i++) { + if (_eq(a[i], v)) + return i; + } + return -1; + }, + _findWithFunction = function(a, f) { + if (a) + for (var i = 0; i < a.length; i++) if (f(a[i])) return i; + return -1; + }, + _removeWithFunction = function(a, f) { + var idx = _findWithFunction(a, f); + if (idx > -1) a.splice(idx, 1); + return idx != -1; + }, + // TODO support insert index + _addWithFunction = function(list, item, hashFunction) { + if (_findWithFunction(list, hashFunction) == -1) list.push(item); + }; + + + // for those browsers that dont have it. they still don't have it! but at least they won't crash. + if (!window.console) + window.console = { time:function(){}, timeEnd:function(){}, group:function(){}, groupEnd:function(){}, log:function(){} }; + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = [], map[key] = l; + } + l.push(value); + return l; + }, + _connectionBeingDragged = null, + _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }, + _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }, + _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }, + _logEnabled = true, + _log = function() { + if (_logEnabled && typeof console != "undefined") { + try { + var msg = arguments[arguments.length - 1]; + console.log(msg); + } + catch (e) {} + } + }, + _group = function(g) { + if (_logEnabled && typeof console != "undefined") console.group(g); + }, + _groupEnd = function(g) { + if (_logEnabled && typeof console != "undefined") console.groupEnd(g); + }, + _time = function(t) { + if (_logEnabled && typeof console != "undefined") console.time(t); + }, + _timeEnd = function(t) { + if (_logEnabled && typeof console != "undefined") console.timeEnd(t); + }; + + /** + * EventGenerator + * Superclass for objects that generate events - jsPlumb extends this, as does jsPlumbUIComponent, which all the UI elements extend. + */ + EventGenerator = function() { + var _listeners = {}, self = this; + + // this is a list of events that should re-throw any errors that occur during their dispatch. as of 1.3.0 this is private to + // jsPlumb, but it seems feasible that people might want to manipulate this list. the thinking is that we don't want event + // listeners to bring down jsPlumb - or do we. i can't make up my mind about this, but i know i want to hear about it if the "ready" + // event fails, because then my page has most likely not initialised. so i have this halfway-house solution. it will be interesting + // to hear what other people think. + var eventsToDieOn = [ "ready" ]; + + /* + * Binds a listener to an event. + * + * Parameters: + * event - name of the event to bind to. + * listener - function to execute. + */ + this.bind = function(event, listener) { + _addToList(_listeners, event, listener); + }; + /* + * Fires an update for the given event. + * + * Parameters: + * event - event to fire + * value - value to pass to the event listener(s). + * originalEvent - the original event from the browser + */ + this.fire = function(event, value, originalEvent) { + if (_listeners[event]) { + for ( var i = 0; i < _listeners[event].length; i++) { + // doing it this way rather than catching and then possibly re-throwing means that an error propagated by this + // method will have the whole call stack available in the debugger. + if (_findIndex(eventsToDieOn, event) != -1) + _listeners[event][i](value, originalEvent); + else { + // for events we don't want to die on, catch and log. + try { + _listeners[event][i](value, originalEvent); + } catch (e) { + _log("jsPlumb: fire failed for event " + event + " : " + e); + } + } + } + } + }; + /* + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + this.clearListeners = function(event) { + if (event) + delete _listeners[event]; + else { + delete _listeners; + _listeners = {}; + } + }; + + this.getListener = function(forEvent) { + return _listeners[forEvent]; + }; + }, + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + _timestamp = function() { return "" + (new Date()).getTime(); }, + + /* + * Class:jsPlumbUIComponent + * Abstract superclass for UI components Endpoint and Connection. Provides the abstraction of paintStyle/hoverPaintStyle, + * and also extends EventGenerator to provide the bind and fire methods. + */ + jsPlumbUIComponent = function(params) { + var self = this, a = arguments, _hover = false, parameters = params.parameters || {}, idPrefix = self.idPrefix, + id = idPrefix + (new Date()).getTime(); + self._jsPlumb = params["_jsPlumb"]; + self.getId = function() { return id; }; + self.tooltip = params.tooltip; + self.hoverClass = params.hoverClass; + + // all components can generate events + EventGenerator.apply(this); + // all components get this clone function. + // TODO issue 116 showed a problem with this - it seems 'a' that is in + // the clone function's scope is shared by all invocations of it, the classic + // JS closure problem. for now, jsPlumb does a version of this inline where + // it used to call clone. but it would be nice to find some time to look + // further at this. + this.clone = function() { + var o = new Object(); + self.constructor.apply(o, a); + return o; + }; + + this.getParameter = function(name) { return parameters[name]; }, + this.getParameters = function() { return parameters; }, + this.setParameter = function(name, value) { parameters[name] = value; }, + this.setParameters = function(p) { parameters = p; }, + this.overlayPlacements = [], + this.paintStyle = null, + this.hoverPaintStyle = null; + + // user can supply a beforeDetach callback, which will be executed before a detach + // is performed; returning false prevents the detach. + var beforeDetach = params.beforeDetach; + this.isDetachAllowed = function(connection) { + var r = true; + if (beforeDetach) { + try { + r = beforeDetach(connection); + } + catch (e) { _log("jsPlumb: beforeDetach callback failed", e); } + } + return r; + }; + + // user can supply a beforeDrop callback, which will be executed before a dropped + // connection is confirmed. user can return false to reject connection. + var beforeDrop = params.beforeDrop; + this.isDropAllowed = function(sourceId, targetId, scope) { + var r = self._jsPlumb.checkCondition("beforeDrop", { sourceId:sourceId, targetId:targetId, scope:scope }); + if (beforeDrop) { + try { + r = beforeDrop({ sourceId:sourceId, targetId:targetId, scope:scope }); + } + catch (e) { _log("jsPlumb: beforeDrop callback failed", e); } + } + return r; + }; + + // helper method to update the hover style whenever it, or paintStyle, changes. + // we use paintStyle as the foundation and merge hoverPaintStyle over the + // top. + var _updateHoverStyle = function() { + if (self.paintStyle && self.hoverPaintStyle) { + var mergedHoverStyle = {}; + jsPlumb.extend(mergedHoverStyle, self.paintStyle); + jsPlumb.extend(mergedHoverStyle, self.hoverPaintStyle); + delete self["hoverPaintStyle"]; + // we want the fillStyle of paintStyle to override a gradient, if possible. + if (mergedHoverStyle.gradient && self.paintStyle.fillStyle) + delete mergedHoverStyle["gradient"]; + self.hoverPaintStyle = mergedHoverStyle; + } + }; + + /* + * Sets the paint style and then repaints the element. + * + * Parameters: + * style - Style to use. + */ + this.setPaintStyle = function(style, doNotRepaint) { + self.paintStyle = style; + self.paintStyleInUse = self.paintStyle; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /* + * Sets the paint style to use when the mouse is hovering over the element. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the component will not be repainted. useful when setting things up initially. + */ + this.setHoverPaintStyle = function(style, doNotRepaint) { + self.hoverPaintStyle = style; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /* + * sets/unsets the hover state of this element. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + this.setHover = function(hover, ignoreAttachedElements, timestamp) { + // while dragging, we ignore these events. this keeps the UI from flashing and + // swishing and whatevering. + if (!self._jsPlumb.currentlyDragging && !self._jsPlumb.isHoverSuspended()) { + + _hover = hover; + if (self.hoverClass != null && self.canvas != null) { + if (hover) + jpcl.addClass(self.canvas, self.hoverClass); + else + jpcl.removeClass(self.canvas, self.hoverClass); + } + if (self.hoverPaintStyle != null) { + self.paintStyleInUse = hover ? self.hoverPaintStyle : self.paintStyle; + timestamp = timestamp || _timestamp(); + self.repaint({timestamp:timestamp, recalc:false}); + } + // get the list of other affected elements, if supported by this component. + // for a connection, its the endpoints. for an endpoint, its the connections! surprise. + if (self.getAttachedElements && !ignoreAttachedElements) + _updateAttachedElements(hover, _timestamp(), self); + } + }; + + this.isHover = function() { return _hover; }; + + var jpcl = jsPlumb.CurrentLibrary, + events = [ "click", "dblclick", "mouseenter", "mouseout", "mousemove", "mousedown", "mouseup", "contextmenu" ], + eventFilters = { "mouseout":"mouseexit" }, + bindOne = function(o, c, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.bind(o, evt, function(ee) { + c.fire(filteredEvent, c, ee); + }); + }, + unbindOne = function(o, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.unbind(o, evt); + }; + + this.attachListeners = function(o, c) { + for (var i = 0; i < events.length; i++) { + bindOne(o, c, events[i]); + } + }; + + var _updateAttachedElements = function(state, timestamp, sourceElement) { + var affectedElements = self.getAttachedElements(); // implemented in subclasses + if (affectedElements) { + for (var i = 0; i < affectedElements.length; i++) { + if (!sourceElement || sourceElement != affectedElements[i]) + affectedElements[i].setHover(state, true, timestamp); // tell the attached elements not to inform their own attached elements. + } + } + }; + + this.reattachListenersForElement = function(o) { + if (arguments.length > 1) { + for (var i = 0; i < events.length; i++) + unbindOne(o, events[i]); + for (var i = 1; i < arguments.length; i++) + self.attachListeners(o, arguments[i]); + } + }; + }, + + overlayCapableJsPlumbUIComponent = function(params) { + jsPlumbUIComponent.apply(this, arguments); + var self = this; + /* + * Property: overlays + * List of Overlays for this component. + */ + this.overlays = []; + + var processOverlay = function(o) { + var _newOverlay = null; + if (o.constructor == Array) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0], + // make a copy of the object so as not to mess up anyone else's reference... + p = jsPlumb.extend({component:self, _jsPlumb:self._jsPlumb}, o[1]); + if (o.length == 3) jsPlumb.extend(p, o[2]); + _newOverlay = new jsPlumb.Overlays[self._jsPlumb.getRenderMode()][type](p); + if (p.events) { + for (var evt in p.events) { + _newOverlay.bind(evt, p.events[evt]); + } + } + } else if (o.constructor == String) { + _newOverlay = new jsPlumb.Overlays[self._jsPlumb.getRenderMode()][o]({component:self, _jsPlumb:self._jsPlumb}); + } else { + _newOverlay = o; + } + + self.overlays.push(_newOverlay); + }, + calculateOverlaysToAdd = function(params) { + var defaultKeys = self.defaultOverlayKeys || [], + o = params.overlays, + checkKey = function(k) { + return self._jsPlumb.Defaults[k] || jsPlumb.Defaults[k] || []; + }; + + if (!o) o = []; + + for (var i = 0; i < defaultKeys.length; i++) + o.unshift.apply(o, checkKey(defaultKeys[i])); + + return o; + } + + var _overlays = calculateOverlaysToAdd(params);//params.overlays || self._jsPlumb.Defaults.Overlays; + if (_overlays) { + for (var i = 0; i < _overlays.length; i++) { + processOverlay(_overlays[i]); + } + } + + // overlay finder helper method + var _getOverlayIndex = function(id) { + var idx = -1; + for (var i = 0; i < self.overlays.length; i++) { + if (id === self.overlays[i].id) { + idx = i; + break; + } + } + return idx; + }; + + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + this.addOverlay = function(overlay) { + processOverlay(overlay); + self.repaint(); + }; + + /* + * Function: getOverlay + * Gets an overlay, by ID. Note: by ID. You would pass an 'id' parameter + * in to the Overlay's constructor arguments, and then use that to retrieve + * it via this method. + */ + this.getOverlay = function(id) { + var idx = _getOverlayIndex(id); + return idx >= 0 ? self.overlays[idx] : null; + }; + + /* + * Function: hideOverlay + * Hides the overlay specified by the given id. + */ + this.hideOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.hide(); + }; + + /* + * Function: showOverlay + * Shows the overlay specified by the given id. + */ + this.showOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.show(); + }; + + /** + * Function: removeAllOverlays + * Removes all overlays from the Connection, and then repaints. + */ + this.removeAllOverlays = function() { + self.overlays.splice(0, self.overlays.length); + self.repaint(); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + this.removeOverlay = function(overlayId) { + var idx = _getOverlayIndex(overlayId); + if (idx != -1) { + var o = self.overlays[idx]; + o.cleanup(); + self.overlays.splice(idx, 1); + } + }; + + /** + * Function:removeOverlays + * Removes a set of overlays by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + this.removeOverlays = function() { + for (var i = 0; i < arguments.length; i++) + self.removeOverlay(arguments[i]); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + var _internalLabelOverlayId = "__label", + _makeLabelOverlay = function(params) { + + var _params = { + cssClass:params.cssClass, + labelStyle : this.labelStyle, + id:_internalLabelOverlayId, + component:self, + _jsPlumb:self._jsPlumb + }, + mergedParams = jsPlumb.extend(_params, params); + + return new jsPlumb.Overlays[self._jsPlumb.getRenderMode()].Label( mergedParams ); + }; + if (params.label) { + var loc = params.labelLocation || self.defaultLabelLocation || 0.5, + labelStyle = params.labelStyle || self._jsPlumb.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.overlays.push(_makeLabelOverlay({ + label:params.label, + location:loc, + labelStyle:labelStyle + })); + } + + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String, a Function that returns a String, or a params object containing { "label", "labelStyle", "location", "cssClass" } + */ + this.setLabel = function(l) { + var lo = self.getOverlay(_internalLabelOverlayId); + if (!lo) { + var params = l.constructor == String || l.constructor == Function ? { label:l } : l; + lo = _makeLabelOverlay(params); + this.overlays.push(lo); + } + else { + if (l.constructor == String || l.constructor == Function) lo.setLabel(l); + else { + if (l.label) lo.setLabel(l.label); + if (l.location) lo.setLocation(l.location); + } + } + + self.repaint(); + }; + + /* + Function:getLabel + Returns the label text for this component (or a function if you are labelling with a function). + This does not return the overlay itself; this is a convenience method which is a pair with + setLabel; together they allow you to add and access a Label Overlay without having to create the + Overlay object itself. For access to the underlying label overlay that jsPlumb has created, + use getLabelOverlay. + */ + this.getLabel = function() { + var lo = self.getOverlay(_internalLabelOverlayId); + return lo != null ? lo.getLabel() : null; + }; + + /* + Function:getLabelOverlay + Returns the underlying internal label overlay, which will exist if you specified a label on + a connect or addEndpoint call, or have called setLabel at any stage. + */ + this.getLabelOverlay = function() { + return self.getOverlay(_internalLabelOverlayId); + } + }, + + _bindListeners = function(obj, _self, _hoverFunction) { + obj.bind("click", function(ep, e) { _self.fire("click", _self, e); }); + obj.bind("dblclick", function(ep, e) { _self.fire("dblclick", _self, e); }); + obj.bind("contextmenu", function(ep, e) { _self.fire("contextmenu", _self, e); }); + // this method is different; endpoint delegates hover to the first connection if there is one. + // that allows the connection to notify all its endpoint and avoids a circular loop + obj.bind("mouseenter", function(ep, e) { + if (!_self.isHover()) { + _hoverFunction(true); + _self.fire("mouseenter", _self, e); + } + }); + // this method is different; endpoint delegates hover to the first connection if there is one. + // that allows the connection to notify all its endpoint and avoids a circular loop + obj.bind("mouseexit", function(ep, e) { + if (_self.isHover()) { + _hoverFunction(false); + _self.fire("mouseexit", self, e); + } + }); + }; + + var _jsPlumbInstanceIndex = 0, + getInstanceIndex = function() { + var i = _jsPlumbInstanceIndex + 1; + _jsPlumbInstanceIndex++; + return i; + }; + + var jsPlumbInstance = function(_defaults) { + + /* + * Property: Defaults + * + * These are the default settings for jsPlumb. They are what will be used if you do not supply specific pieces of information + * to the various API calls. A convenient way to implement your own look and feel can be to override these defaults + * by including a script somewhere after the jsPlumb include, but before you make any calls to jsPlumb. + * + * Properties: + * - *Anchor* The default anchor to use for all connections (both source and target). Default is "BottomCenter". + * - *Anchors* The default anchors to use ([source, target]) for all connections. Defaults are ["BottomCenter", "BottomCenter"]. + * - *Connector* The default connector definition to use for all connections. Default is "Bezier". + * - *Container* Optional selector or element id that instructs jsPlumb to append elements it creates to a specific element. + * - *DragOptions* The default drag options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *DropOptions* The default drop options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *Endpoint* The default endpoint definition to use for all connections (both source and target). Default is "Dot". + * - *Endpoints* The default endpoint definitions ([ source, target ]) to use for all connections. Defaults are ["Dot", "Dot"]. + * - *EndpointStyle* The default style definition to use for all endpoints. Default is fillStyle:"#456". + * - *EndpointStyles* The default style definitions ([ source, target ]) to use for all endpoints. Defaults are empty. + * - *EndpointHoverStyle* The default hover style definition to use for all endpoints. Default is null. + * - *EndpointHoverStyles* The default hover style definitions ([ source, target ]) to use for all endpoints. Defaults are null. + * - *HoverPaintStyle* The default hover style definition to use for all connections. Defaults are null. + * - *LabelStyle* The default style to use for label overlays on connections. + * - *LogEnabled* Whether or not the jsPlumb log is enabled. defaults to false. + * - *Overlays* The default overlay definitions. Defaults to an empty list. + * - *MaxConnections* The default maximum number of connections for an Endpoint. Defaults to 1. + * - *MouseEventsEnabled* Whether or not mouse events are enabled when using the canvas renderer. Defaults to true. + * The idea of this is just to give people a way to prevent all the mouse listeners from activating if they know they won't need mouse events. + * - *PaintStyle* The default paint style for a connection. Default is line width of 8 pixels, with color "#456". + * - *RenderMode* What mode to use to paint with. If you're on IE<9, you don't really get to choose this. You'll just get VML. Otherwise, the jsPlumb default is to use Canvas elements. + * - *Scope* The default "scope" to use for connections. Scope lets you assign connections to different categories. + */ + this.Defaults = { + Anchor : "BottomCenter", + Anchors : [ null, null ], + ConnectionsDetachable : true, + Connector : "Bezier", + Container : null, + DragOptions : { }, + DropOptions : { }, + Endpoint : "Dot", + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : "#456" }, + EndpointStyles : [ null, null ], + EndpointHoverStyle : null, + EndpointHoverStyles : [ null, null ], + HoverPaintStyle : null, + LabelStyle : { color : "black" }, + LogEnabled : false, + Overlays : [ ], + MaxConnections : 1, + MouseEventsEnabled : true, + PaintStyle : { lineWidth : 8, strokeStyle : "#456" }, + //Reattach:false, + RenderMode : "svg", + Scope : "jsPlumb_DefaultScope" + }; + if (_defaults) jsPlumb.extend(this.Defaults, _defaults); + + this.logEnabled = this.Defaults.LogEnabled; + + EventGenerator.apply(this); + var _bb = this.bind; + this.bind = function(event, fn) { + if ("ready" === event && initialized) fn(); + else _bb(event, fn); + }; + var _currentInstance = this, + _instanceIndex = getInstanceIndex(), + + log = null, + repaintFunction = function() { + jsPlumb.repaintEverything(); + }, + automaticRepaint = true, + repaintEverything = function() { + if (automaticRepaint) + repaintFunction(); + }, + resizeTimer = null, + initialized = false, + connectionsByScope = {}, + /** + * map of element id -> endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + endpointsByElement = {}, + endpointsByUUID = {}, + offsets = {}, + offsetTimestamps = {}, + floatingConnections = {}, + draggableStates = {}, + _mouseEventsEnabled = this.Defaults.MouseEventsEnabled, + canvasList = [], + sizes = [], + //listeners = {}, // a map: keys are event types, values are lists of listeners. + DEFAULT_SCOPE = this.Defaults.Scope, + renderMode = null, // will be set in init() + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }, + + /** + * appends an element to some other element, which is calculated as follows: + * + * 1. if _currentInstance.Defaults.Container exists, use that element. + * 2. if the 'parent' parameter exists, use that. + * 3. otherwise just use the document body. + * + */ + _appendElement = function(el, parent) { + if (_currentInstance.Defaults.Container) + jsPlumb.CurrentLibrary.appendElement(el, _currentInstance.Defaults.Container); + else if (!parent) + document.body.appendChild(el); + else + jsPlumb.CurrentLibrary.appendElement(el, parent); + }, + + _curIdStamp = 1, + _idstamp = function() { return "" + _curIdStamp++; }, + + /** + * YUI, for some reason, put the result of a Y.all call into an object that contains + * a '_nodes' array, instead of handing back an array-like object like the other + * libraries do. + */ + _convertYUICollection = function(c) { + return c._nodes ? c._nodes : c; + }, + + _suspendDrawing = false, + /* + sets whether or not to suspend drawing. you should use this if you need to connect a whole load of things in one go. + it will save you a lot of time. + */ + _setSuspendDrawing = function(val, repaintAfterwards) { + _suspendDrawing = val; + if (repaintAfterwards) _currentInstance.repaintEverything(); + }, + + /** + * Draws an endpoint and its connections. this is the main entry point into drawing connections as well + * as endpoints, since jsPlumb is endpoint-centric under the hood. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + _draw = function(element, ui, timestamp) { + if (!_suspendDrawing) { + var id = _getAttribute(element, "id"); + _currentInstance.anchorManager.redraw(id, ui, timestamp); + } + }, + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }, + + /** + * gets an Endpoint by uuid. + */ + _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }, + + /** + * inits a draggable if it's not already initialised. + */ + _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? false : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element) && !jsPlumb.CurrentLibrary.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents["drag"], + stopEvent = jsPlumb.CurrentLibrary.dragEvents["stop"]; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + draggableStates[_getId(element)] = true; + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options, false); + } + } + }, + + /* + * prepares a final params object that can be passed to _newConnection, taking into account defaults, events, etc. + */ + _prepareConnectionParams = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + // copy in any connectorOverlays that were specified on the source endpoint. + // it doesnt copy target endpoint overlays. i'm not sure if we want it to or not. + if (_p.sourceEndpoint && _p.sourceEndpoint.connectorOverlays) { + _p.overlays = _p.overlays || []; + for (var i = 0; i < _p.sourceEndpoint.connectorOverlays.length; i++) { + _p.overlays.push(_p.sourceEndpoint.connectorOverlays[i]); + } + } + + // tooltip. params.tooltip takes precedence, then sourceEndpoint.connectorTooltip. + _p.tooltip = params.tooltip; + if (!_p.tooltip && _p.sourceEndpoint && _p.sourceEndpoint.connectorTooltip) + _p.tooltip = _p.sourceEndpoint.connectorTooltip; + + if (_p.target && !_p.target.endpoint) { + var tid = _getId(_p.target), + tep =_targetEndpointDefinitions[tid]; + + var overrideOne = function(singlePropertyName, pluralPropertyName, tepProperty, tep) { + if (tep[tepProperty]) { + if (_p[pluralPropertyName]) _p[pluralPropertyName][1] = tep[tepProperty]; + else if (_p[singlePropertyName]) { + _p[pluralPropertyName] = [ _p[singlePropertyName], tep[tepProperty] ]; + _p[singlePropertyName] = null; + } + else _p[pluralPropertyName] = [ null, tep[tepProperty] ]; + } + }; + + if (tep) { + overrideOne("endpoint", "endpoints", "endpoint", tep); + overrideOne("endpointStyle", "endpointStyles", "paintStyle", tep); + overrideOne("endpointHoverStyle", "endpointHoverStyles", "hoverPaintStyle", tep); + } + } + + return _p; + }, + + _newConnection = function(params) { + var connectionFunc = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + endpointFunc = _currentInstance.Defaults.EndpointType || Endpoint, + parent = jsPlumb.CurrentLibrary.getParent; + + if (params.container) + params["parent"] = params.container; + else { + if (params.sourceEndpoint) + params["parent"] = params.sourceEndpoint.parent; + else if (params.source.constructor == endpointFunc) + params["parent"] = params.source.parent; + else params["parent"] = parent(params.source); + } + + params["_jsPlumb"] = _currentInstance; + var con = new connectionFunc(params); + con.id = "con_" + _idstamp(); + _eventFireProxy("click", "click", con); + _eventFireProxy("dblclick", "dblclick", con); + _eventFireProxy("contextmenu", "contextmenu", con); + return con; + }, + + /** + * adds the connection to the backing model, fires an event if necessary and then redraws + */ + _finaliseConnection = function(jpc, params, originalEvent) { + params = params || {}; + // add to list of connections (by scope). + if (!jpc.suspendedEndpoint) + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + if (!params.doNotFireConnectionEvent && params.fireEvent !== false) { + _currentInstance.fire("jsPlumbConnection", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }, originalEvent); + } + // always inform the anchor manager + // except that if jpc has a suspended endpoint it's not true to say the + // connection is new; it has just (possibly) moved. the question is whether + // to make that call here or in the anchor manager. i think perhaps here. + _currentInstance.anchorManager.newConnection(jpc); + // force a paint + _draw(jpc.source); + }, + + _eventFireProxy = function(event, proxyEvent, obj) { + obj.bind(event, function(originalObject, originalEvent) { + _currentInstance.fire(proxyEvent, obj, originalEvent); + }); + }, + + /** + * for the given endpoint params, returns an appropriate parent element for the UI elements that will be added. + * this function is used by _newEndpoint (directly below), and also in the makeSource function in jsPlumb. + * + * the logic is to first look for a "container" member of params, and pass that back if found. otherwise we + * handoff to the 'getParent' function in the current library. + */ + _getParentFromParams = function(params) { + if (params.container) + return params.container; + else { + var tag = jsPlumb.CurrentLibrary.getTagName(params.source), + p = jsPlumb.CurrentLibrary.getParent(params.source); + if (tag && tag.toLowerCase() === "td") + return jsPlumb.CurrentLibrary.getParent(p); + else return p; + } + }, + + /** + factory method to prepare a new endpoint. this should always be used instead of creating Endpoints + manually, since this method attaches event listeners and an id. + */ + _newEndpoint = function(params) { + var endpointFunc = _currentInstance.Defaults.EndpointType || Endpoint; + params.parent = _getParentFromParams(params); + params["_jsPlumb"] = _currentInstance; + var ep = new endpointFunc(params); + ep.id = "ep_" + _idstamp(); + _eventFireProxy("click", "endpointClick", ep); + _eventFireProxy("dblclick", "endpointDblClick", ep); + _eventFireProxy("contextmenu", "contextmenu", ep); + return ep; + }, + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + _operation = function(elId, func, endpointFunc) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + if (endpointFunc) endpointFunc(endpoints[i]); + } + } + }, + /** + * perform an operation on all elements. + */ + _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }, + + /** + * helper to remove an element from the DOM. + */ + _removeElement = function(element, parent) { + if (element != null && element.parentNode != null) { + element.parentNode.removeChild(element); + } + }, + /** + * helper to remove a list of elements from the DOM. + */ + _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }, + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }, + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + _setVisible = function(el, state, alsoChangeEndpoints) { + state = state === "block"; + var endpointFunc = null; + if (alsoChangeEndpoints) { + if (state) endpointFunc = function(ep) { + ep.setVisible(true, true, true); + }; + else endpointFunc = function(ep) { + ep.setVisible(false, true, true); + }; + } + var id = _getAttribute(el, "id"); + _operation(id, function(jpc) { + if (state && alsoChangeEndpoints) { + // this test is necessary because this functionality is new, and i wanted to maintain backwards compatibility. + // this block will only set a connection to be visible if the other endpoint in the connection is also visible. + var oidx = jpc.sourceId === id ? 1 : 0; + if (jpc.endpoints[oidx].isVisible()) jpc.setVisible(true); + } + else // the default behaviour for show, and what always happens for hide, is to just set the visibility without getting clever. + jpc.setVisible(state); + }, endpointFunc); + }, + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? false : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }, + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + _toggleVisible = function(elId, changeEndpoints) { + var endpointFunc = null; + if (changeEndpoints) { + endpointFunc = function(ep) { + var state = ep.isVisible(); + ep.setVisible(!state); + }; + } + _operation(elId, function(jpc) { + var state = jpc.isVisible(); + jpc.setVisible(!state); + }, endpointFunc); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }, + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return offsets[elId]; + } + if (recalc || !offset) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + if (sizes[elId] == null) { + var s = _getElementObject(elId); + if (s != null) + sizes[elId] = _getSize(s); + } + } + + if(offsets[elId] && !offsets[elId].right) { + offsets[elId].right = offsets[elId].left + sizes[elId][0]; + offsets[elId].bottom = offsets[elId].top + sizes[elId][1]; + offsets[elId].width = sizes[elId][0]; + offsets[elId].height = sizes[elId][1]; + offsets[elId].centerx = offsets[elId].left + (offsets[elId].width / 2); + offsets[elId].centery = offsets[elId].top + (offsets[elId].height / 2); + } + return offsets[elId]; + }, + + // TODO comparison performance + _getCachedData = function(elId) { + var o = offsets[elId]; + if (!o) o = _updateOffset({elId:elId}); + return {o:o, s:sizes[elId]}; + }, + + /** + * gets an id for the given element, creating and setting one if + * necessary. the id is of the form + * + * jsPlumb__ + * + * where "index in instance" is a monotonically increasing integer that starts at 0, + * for each instance. this method is used not only to assign ids to elements that do not + * have them but also to connections and endpoints. + */ + _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else + id = "jsPlumb_" + _instanceIndex + "_" + _idstamp(); + _setAttribute(ele, "id", id); + } + return id; + }, + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'jsPlumb function failed : ' + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'wrapped function failed : ' + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = "_jsPlumb_connector"; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = "_jsPlumb_endpoint"; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = "_jsPlumb_overlay"; + + this.Anchors = {}; + + this.Connectors = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Endpoints = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Overlays = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + +// ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***************************************** + /* + * Function: bind + * Bind to an event on jsPlumb. + * + * Parameters: + * event - the event to bind. Available events on jsPlumb are: + * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback. + * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback. + * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback. + * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback. + * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback. + * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback. + * + * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event. + */ + + /* + * Function: clearListeners + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + +// *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *********************************************************** + + /* + Function: addClass + + Helper method to abstract out differences in setting css classes on the different renderer types. + */ + this.addClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.addClass(el, clazz); + }; + + /* + Function: removeClass + + Helper method to abstract out differences in setting css classes on the different renderer types. + */ + this.removeClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.removeClass(el, clazz); + }; + + /* + Function: hasClass + + Helper method to abstract out differences in testing for css classes on the different renderer types. + */ + this.hasClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.hasClass(el, clazz); + }; + + /* + Function: addEndpoint + + Adds an to a given element or elements. + + Parameters: + + el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + params - Object containing Endpoint constructor arguments. For more information, see . + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some + shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in + this object are anything that 'params' can contain. See . + + Returns: + The newly created , if el referred to a single element. Otherwise, an array of newly created s. + + See Also: + + */ + this.addEndpoint = function(el, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // YUI wrapper + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + var _el = _getElementObject(inputs[i]), id = _getId(_el); + p.source = _el; + _updateOffset({ elId : id }); + var e = _newEndpoint(p); + if (p.parentAnchor) e.parentAnchor = p.parentAnchor; + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + results.push(e); + } + + return results.length == 1 ? results[0] : results; + }; + + /* + Function: addEndpoints + Adds a list of s to a given element or elements. + + Parameters: + target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See 's constructor documentation. + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created s, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(el, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams); + if (e.constructor == Array) + Array.prototype.push.apply(results, e); + else results.push(e); + } + return results; + }; + + /* + Function: animate + This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating + the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // onComplete repaints, just to make sure everything looks good at the end of the animation. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /** + * checks for a listener for the given condition, executing it if found, passing in the given value. + * condition listeners would have been attached using "bind" (which is, you could argue, now overloaded, since + * firing click events etc is a bit different to what this does). i thought about adding a "bindCondition" + * or something, but decided against it, for the sake of simplicity. jsPlumb will never fire one of these + * condition events anyway. + */ + this.checkCondition = function(conditionName, value) { + var l = _currentInstance.getListener(conditionName); + var r = true; + if (l && l.length > 0) { + try { + for (var i = 0 ; i < l.length; i++) { + r = r && l[i](value); + } + } + catch (e) { + _log(_currentInstance, "cannot check condition [" + conditionName + "]" + e); + } + } + return r; + }; + + /* + Function: connect + Establishes a between two elements (or s, which are themselves registered to elements). + + Parameters: + params - Object containing constructor arguments for the Connection. See 's constructor documentation. + referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of + Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection. + + Returns: + The newly created . + */ + this.connect = function(params, referenceParams) { + // prepare a final set of parameters to create connection with + var _p = _prepareConnectionParams(params, referenceParams); + // TODO probably a nicer return value if the connection was not made. _prepareConnectionParams + // will return null (and log something) if either endpoint was full. what would be nicer is to + // create a dedicated 'error' object. + if (_p) { + // a connect call will delete its created endpoints on detach, unless otherwise specified. + // this is because the endpoints belong to this connection only, and are no use to + // anyone else, so they hang around like a bad smell. + if (_p.deleteEndpointsOnDetach == null) + _p.deleteEndpointsOnDetach = true; + + // create the connection. it is not yet registered + var jpc = _newConnection(_p); + // now add it the model, fire an event, and redraw + _finaliseConnection(jpc, _p); + return jpc; + } + }; + + /* + Function: deleteEndpoint + Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too) + + Parameters: + object - either an object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElements(endpoint.endpoint.getDisplayElements()); + _currentInstance.anchorManager.deleteEndpoint(endpoint); + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + delete endpoint; + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every , and their associated s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc, doFireEvent) { + // may have been given a connection, or in special cases, an object + var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + argIsConnection = jpc.constructor == connType, + params = argIsConnection ? { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + } : jpc; + + if (doFireEvent) _currentInstance.fire("jsPlumbConnectionDetached", params); + _currentInstance.anchorManager.connectionDetached(params); + }; + + /* + Function: detach + Detaches and then removes a . From 1.3.5 this method has been altered to remove support for + specifying Connections by various parameters; you can now pass in a Connection as the first argument and + an optional parameters object as a second argument. If you need the functionality this method provided + before 1.3.5 then you should use the getConnections method to get the list of Connections to detach, and + then iterate through them, calling this for each one. + + Parameters: + connection - the to detach + params - optional parameters to the detach call. valid values here are + fireEvent : defaults to false; indicates you want jsPlumb to fire a connection + detached event. The thinking behind this is that if you made a programmatic + call to detach an event, you probably don't need the callback. + forceDetach : defaults to false. allows you to override any beforeDetach listeners that may be registered. + + Returns: + true if successful, false if not. + */ + this.detach = function() { + + if (arguments.length == 0) return; + var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + firstArgIsConnection = arguments[0].constructor == connType, + params = arguments.length == 2 ? firstArgIsConnection ? (arguments[1] || {}) : arguments[0] : arguments[0], + fireEvent = (params.fireEvent !== false), + forceDetach = params.forceDetach, + connection = firstArgIsConnection ? arguments[0] : params.connection; + + if (connection) { + if (forceDetach || (connection.isDetachAllowed(connection) + && connection.endpoints[0].isDetachAllowed(connection) + && connection.endpoints[1].isDetachAllowed(connection))) { + if (forceDetach || _currentInstance.checkCondition("beforeDetach", connection)) + connection.endpoints[0].detach(connection, false, true, fireEvent); // TODO check this param iscorrect for endpoint's detach method + } + } + else { + var _p = jsPlumb.extend( {}, params); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1]), fireEvent); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source), + targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + if (_currentInstance.checkCondition("beforeDetach", jpc)) { + jpc.endpoints[0].detach(jpc, false, true, fireEvent); + } + } + }); + } + } + }; + + /* + Function: detachAllConnections + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + params - optional parameters. alowed values: + fireEvent : defaults to true, whether or not to fire the detach event. + + Returns: + void + */ + this.detachAllConnections = function(el, params) { + params = params || {}; + el = _getElementObject(el); + var id = _getAttribute(el, "id"), + endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + }; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Parameters: + params - optional params object containing: + fireEvent : whether or not to fire detach events. defaults to true. + + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function(params) { + params = params || {}; + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of y + our library's draggable method so that jsPlumb can setup the appropriate callbacks. Your + underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + // TODO it would be nice if this supported a selector string, instead of an id. + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method, + * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the + * default scope). If multiple scopes are passed in, the return value will be a map of { scope -> [ connection... ] }. + * + * Parameters + * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list + * of connections that are in the given scope. + * options - if the argument is a JS object, you can specify a finer-grained filter: + * + * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes. + * - *source* either a string representing an element id, or a selector. constrains the result to connections having this source. + * - *target* either a string representing an element id, or a selector. constrains the result to connections having this target. + * + */ + this.getConnections = function(options) { + if (!options) { + options = {}; + } else if (options.constructor == String) { + options = { "scope": options }; + } + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') + r.push(input); + else + r = input; + } + return r; + }, + scope = options.scope || _currentInstance.getDefaultScope(), + scopes = prepareList(scope), + sources = prepareList(options.source), + targets = prepareList(options.target), + filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }, + results = scopes.length > 1 ? {} : [], + _addOne = function(scope, obj) { + if (scopes.length > 1) { + var ss = results[scope]; + if (ss == null) { + ss = []; results[scope] = ss; + } + ss.push(obj); + } else results.push(obj); + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + _addOne(i, c); + } + } + } + return results; + }; + + /* + * Function: getAllConnections + * Gets all connections, as a map of { scope -> [ connection... ] }. + */ + this.getAllConnections = function() { + return connectionsByScope; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /** + * Function:getEndpoints + * Gets the list of Endpoints for a given selector, or element id. + * @param el + * @return + */ + this.getEndpoints = function(el) { + return endpointsByElement[_getId(el)]; + }; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + this.getOffset = function(id) { + var o = offsets[id]; + return _updateOffset({elId:id}); + }; + + this.getSelector = function(spec) { + return jsPlumb.CurrentLibrary.getSelector(spec); + }; + + this.getSize = function(id) { + var s = sizes[id]; + if (!s) _updateOffset({elId:id}); + return sizes[id]; + }; + + this.appendElement = _appendElement; + + var _hoverSuspended = false; + this.isHoverSuspended = function() { return _hoverSuspended; }; + this.setHoverSuspended = function(s) { _hoverSuspended = s; }; + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + changeEndpoints - whether not to also hide endpoints on the element. by default this is false. + + Returns: + void + */ + this.hide = function(el, changeEndpoints) { + _setVisible(el, "none", changeEndpoints); + }; + + // exposed for other objects to use to get a unique id. + this.idstamp = _idstamp; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + if (!initialized) { + _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run. + + var bindOne = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && _mouseEventsEnabled && renderMode == jsPlumb.CANVAS) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + var t = c[i].connector[event](e); + if (t) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i].endpoint[event](e)) return; + } + } + } + }); + }; + bindOne("click");bindOne("dblclick");bindOne("mousemove");bindOne("mousedown");bindOne("mouseup");bindOne("contextmenu"); + + initialized = true; + _currentInstance.fire("ready"); + } + }; + + this.log = log; + this.jsPlumbUIComponent = jsPlumbUIComponent; + this.EventGenerator = EventGenerator; + + /* + * Creates an anchor with the given params. + * + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function() { + if (arguments.length == 0) return null; + var specimen = arguments[0], elementId = arguments[1], jsPlumbInstance = arguments[2], newAnchor = null; + if (!jsPlumbInstance) + throw "NO JSPLUMB SET"; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; //TODO hazy here about whether it should be added or is already added somehow. + // is it the name of an anchor type? + else if (typeof specimen == "string") { + newAnchor = jsPlumb.Anchors[arguments[0]]({elementId:elementId, jsPlumbInstance:_currentInstance}); + } + // is it an array? it will be one of: + // an array of [name, params] - this defines a single anchor + // an array of arrays - this defines some dynamic anchors + // an array of numbers - this defines a single anchor. + else if (specimen.constructor == Array) { + if (specimen[0].constructor == Array || specimen[0].constructor == String) { + if (specimen.length == 2 && specimen[0].constructor == String && specimen[1].constructor == Object) { + var pp = jsPlumb.extend({elementId:elementId, jsPlumbInstance:_currentInstance}, specimen[1]); + newAnchor = jsPlumb.Anchors[specimen[0]](pp); + } + else + newAnchor = new DynamicAnchor(specimen, null, elementId); + } + else { + var anchorParams = { + x:specimen[0], y:specimen[1], + orientation : (specimen.length >= 4) ? [ specimen[2], specimen[3] ] : [0,0], + offsets : (specimen.length == 6) ? [ specimen[4], specimen[5] ] : [ 0, 0 ], + elementId:elementId + }; + newAnchor = new Anchor(anchorParams); + newAnchor.clone = function() { return new Anchor(anchorParams); }; + } + } + + if (!newAnchor.id) newAnchor.id = "anchor_" + _idstamp(); + return newAnchor; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types, elementId, jsPlumbInstance) { + var r = []; + for ( var i = 0; i < types.length; i++) { + if (typeof types[i] == "string") + r.push(jsPlumb.Anchors[types[i]]({elementId:elementId, jsPlumbInstance:jsPlumbInstance})); + else if (types[i].constructor == Array) + r.push(_currentInstance.makeAnchor(types[i], elementId, jsPlumbInstance)); + } + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /** + * Function: makeTarget + * Makes some DOM element a Connection target, allowing you to drag connections to it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a target. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * scope optional. scope for the drop zone. + * dropOptions optional. same stuff as you would pass to dropOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to true. whether or not to delete + * any Endpoints created by a connection to this target if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + var _targetEndpointDefinitions = {}; + var _setEndpointPaintStylesAndAnchor = function(ep, epIndex) { + ep.paintStyle = ep.paintStyle || + _currentInstance.Defaults.EndpointStyles[epIndex] || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyles[epIndex] || + jsPlumb.Defaults.EndpointStyle; + ep.hoverPaintStyle = ep.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyles[epIndex] || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyles[epIndex] || + jsPlumb.Defaults.EndpointHoverStyle; + + ep.anchor = ep.anchor || + _currentInstance.Defaults.Anchors[epIndex] || + _currentInstance.Defaults.Anchor || + jsPlumb.Defaults.Anchors[epIndex] || + jsPlumb.Defaults.Anchor; + + }; + this.makeTarget = function(el, params, referenceParams) { + + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + var jpcl = jsPlumb.CurrentLibrary, + targetScope = p.scope || _currentInstance.Defaults.Scope, + deleteEndpointsOnDetach = p.deleteEndpointsOnDetach || true, + _doOne = function(_el) { + + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el); + _targetEndpointDefinitions[elid] = p.endpoint; + + var dropOptions = jsPlumb.extend({}, p.dropOptions || {}), + _drop = function() { + _currentInstance.currentlyDragging = false; + var draggable = _getElementObject(jpcl.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + // restore the original scope if necessary (issue 57) + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id], + source = jpc.endpoints[0], + _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : {}; + + // use defaults for endpoint style, if not present..this either uses EndpointStyle, or EndpointStyles[1], + // if it is present, since this is a target endpoint. + // TODO - this should be a helper method. makeTarget should use it too. give it an endpoint + _setEndpointPaintStylesAndAnchor(_endpoint, 1); + + // unlock the source anchor to allow it to refresh its position if necessary + source.anchor.locked = false; + + if (scope) jpcl.setDragScope(draggable, scope); + + // check if drop is allowed here. + var _continue = jpc.isDropAllowed(jpc.sourceId, _getId(_el), jpc.scope); + + // regardless of whether the connection is ok, reconfigure the existing connection to + // point at the current info. we need this to be correct for the detach event that will follow. + // clear the source endpoint from the list to detach. we will detach this connection at this + // point, but we want to keep the source endpoint. the target is a floating endpoint and should + // be removed. TODO need to figure out whether this code can result in endpoints kicking around + // when they shouldnt be. like is this a full detach of a connection? can it be? + if (jpc.endpointsToDeleteOnDetach) { + if (source === jpc.endpointsToDeleteOnDetach[0]) + jpc.endpointsToDeleteOnDetach[0] = null; + else if (source === jpc.endpointsToDeleteOnDetach[1]) + jpc.endpointsToDeleteOnDetach[1] = null; + } + // reinstate any suspended endpoint; this just puts the connection back into + // a state in which it will report sensible values if someone asks it about + // its target. we're going to throw this connection away shortly so it doesnt matter + // if we manipulate it a bit. + if (jpc.suspendedEndpoint) { + jpc.targetId = jpc.suspendedEndpoint.elementId; + jpc.target = jpcl.getElementObject(jpc.suspendedEndpoint.elementId); + jpc.endpoints[1] = jpc.suspendedEndpoint; + } + + if (_continue) { + + // detach this connection from the source. + source.detach(jpc, false, true, true);//source.endpointWillMoveAfterConnection); + + // make a new Endpoint for the target + var newEndpoint = _currentInstance.addEndpoint(_el, _endpoint); + + // if the anchor has a 'positionFinder' set, then delegate to that function to find + // out where to locate the anchor. + if (newEndpoint.anchor.positionFinder != null) { + var dropPosition = jpcl.getUIPosition(arguments), + elPosition = jpcl.getOffset(_el), + elSize = jpcl.getSize(_el), + ap = newEndpoint.anchor.positionFinder(dropPosition, elPosition, elSize, newEndpoint.anchor); + newEndpoint.anchor.x = ap[0]; + newEndpoint.anchor.y = ap[1]; + // now figure an orientation for it..kind of hard to know what to do actually. probably the best thing i can do is to + // support specifying an orientation in the anchor's spec. if one is not supplied then i will make the orientation + // be what will cause the most natural link to the source: it will be pointing at the source, but it needs to be + // specified in one axis only, and so how to make that choice? i think i will use whichever axis is the one in which + // the target is furthest away from the source. + } + var c = _currentInstance.connect({ + source:source, + target:newEndpoint, + scope:scope, + previousConnection:jpc, + container:jpc.parent, + // 'endpointWillMoveAfterConnection' is set by the makeSource function, and it indicates that the + // given endpoint will actually transfer from the element it is currently attached to to some other + // element after a connection has been established. in that case, we do not want to fire the + // connection event, since it will have the wrong data in it; makeSource will do it for us. + // this is controlled by the 'parent' parameter on a makeSource call. + doNotFireConnectionEvent:source.endpointWillMoveAfterConnection + }); + if (deleteEndpointsOnDetach) + c.endpointsToDeleteOnDetach = [ source, newEndpoint ]; + } + // if not allowed to drop... + else { + // TODO this code is identical (pretty much) to what happens when a connection + // dragged from a normal endpoint is in this situation. refactor. + // is this an existing connection, and will we reattach? + if (jpc.suspendedEndpoint) { + if (source.isReattach) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + _currentInstance.repaint(source.elementId); + } + else + source.detach(jpc, false, true, true); // otherwise, detach the connection and tell everyone about it. + } + + } + }; + + var dropEvent = jpcl.dragEvents['drop']; + dropOptions["scope"] = dropOptions["scope"] || targetScope; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop); + + jpcl.initDroppable(_el, dropOptions, true); + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements drop targets. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeTargets = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeTarget(els[i], params, referenceParams); + } + }; + + /** + * Function: makeSource + * Makes some DOM element a Connection source, allowing you to drag connections from it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a source. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * parent optional. the element to add Endpoints to when a Connection is established. if you omit this, + * Endpoints will be added to 'el'. + * scope optional. scope for the connections dragged from this element. + * dragOptions optional. same stuff as you would pass to dragOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to false. whether or not to delete + * any Endpoints created by a connection from this source if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + var _sourceEndpointDefinitions = {}; + this.makeSource = function(el, params, referenceParams) { + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + var jpcl = jsPlumb.CurrentLibrary, + _doOne = function(_el) { + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el); + _sourceEndpointDefinitions[elid] = p.endpoint || {}; + var stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"], + dragOptions = jsPlumb.extend({ }, p.dragOptions || _sourceEndpointDefinitions[elid].dragOptions || {}), + existingDrag = dragOptions.drag, + existingStop = dragOptions.stop, + ep = null, + endpointAddedButNoDragYet = false; + + // set scope if its not set in dragOptions but was passed in in params + dragOptions["scope"] = dragOptions["scope"] || p.scope; + + // make sure this method honours whatever defaults have been set for Endpoint. + _sourceEndpointDefinitions[elid].endpoint = _sourceEndpointDefinitions[elid].endpoint || _currentInstance.Defaults.Endpoints[0] || _currentInstance.Defaults.Endpoint; + + // set endpoint paint styles and anchor, using either styles that are set or defaults. + _setEndpointPaintStylesAndAnchor(_sourceEndpointDefinitions[elid], 0); + + + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + if (existingDrag) existingDrag.apply(this, arguments); + endpointAddedButNoDragYet = false; + }); + + dragOptions[stopEvent] = function() { + + if (existingStop) existingStop.apply(this, arguments); + + //_currentlyDown = false; + _currentInstance.currentlyDragging = false; + + if (ep.connections.length == 0) + _currentInstance.deleteEndpoint(ep); + else { + + jpcl.unbind(ep.canvas, "mousedown"); + + // reset the anchor to the anchor that was initially provided. the one we were using to drag + // the connection was just a placeholder that was located at the place the user pressed the + // mouse button to initiate the drag. + var anchorDef = _sourceEndpointDefinitions[elid].anchor || _currentInstance.Defaults.Anchor; + ep.anchor = _currentInstance.makeAnchor(anchorDef, elid, _currentInstance); + + if (p.parent) { + var parent = jpcl.getElementObject(p.parent); + if (parent) { + var currentId = ep.elementId; + ep.setElement(parent); + ep.endpointWillMoveAfterConnection = false; + _currentInstance.anchorManager.rehomeEndpoint(currentId, parent); + ep.connections[0].previousConnection = null; + _finaliseConnection(ep.connections[0]); + } + } + _currentInstance.repaint(elid); + } + }; + // when the user presses the mouse, add an Endpoint + var mouseDownListener = function(e) { + // make sure we have the latest offset for this div + var myOffsetInfo = _updateOffset({elId:elid}); + + var x = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width, + y = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height, + parentX = x, + parentY = y; + + + // if there is a parent, the endpoint will actually be added to it now, rather than the div + // that was the source. in that case, we have to adjust the anchor position so it refers to + // the parent. + if (p.parent) { + var pEl = jsPlumb.CurrentLibrary.getElementObject(p.parent), + pId = _getId(pEl); + myOffsetInfo = _updateOffset({elId:pId}); + parentX = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width, + parentY = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height; + } + + // we need to override the anchor in here, and force 'isSource', but we don't want to mess with + // the params passed in, because after a connection is established we're going to reset the endpoint + // to have the anchor we were given. + var tempEndpointParams = {}; + jsPlumb.extend(tempEndpointParams, _sourceEndpointDefinitions[elid]); + tempEndpointParams.isSource = true; + tempEndpointParams.anchor = [x,y,0,0]; + tempEndpointParams.parentAnchor = [ parentX, parentY, 0, 0 ]; + tempEndpointParams.dragOptions = dragOptions; + // if a parent was given we need to turn that into a "container" argument. this is, by default, + // the parent of the element we will move to, so parent of p.parent in this case. however, if + // the user has specified a 'container' on the endpoint definition or on + // the defaults, we should use that. + if (p.parent) { + var potentialParent = tempEndpointParams.container || _currentInstance.Defaults.Container; + if (potentialParent) + tempEndpointParams.container = potentialParent; + else + tempEndpointParams.container = jsPlumb.CurrentLibrary.getParent(p.parent); + } + + ep = _currentInstance.addEndpoint(elid, tempEndpointParams); + + endpointAddedButNoDragYet = true; + // we set this to prevent connections from firing attach events before this function has had a chance + // to move the endpoint. + ep.endpointWillMoveAfterConnection = p.parent != null; + ep.endpointWillMoveTo = p.parent ? jpcl.getElementObject(p.parent) : null; + + var _delTempEndpoint = function() { + // this mouseup event is fired only if no dragging occurred, by jquery and yui, but for mootools + // it is fired even if dragging has occurred, in which case we would blow away a perfectly + // legitimate endpoint, were it not for this check. the flag is set after adding an + // endpoint and cleared in a drag listener we set in the dragOptions above. + if(endpointAddedButNoDragYet) { + _currentInstance.deleteEndpoint(ep); + } + }; + + _currentInstance.registerListener(ep.canvas, "mouseup", _delTempEndpoint); + _currentInstance.registerListener(_el, "mouseup", _delTempEndpoint); + + // and then trigger its mousedown event, which will kick off a drag, which will start dragging + // a new connection from this endpoint. + jpcl.trigger(ep.canvas, "mousedown", e); + }; + + // register this on jsPlumb so that it can be cleared by a reset. + _currentInstance.registerListener(_el, "mousedown", mouseDownListener); + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements connection sources. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeSources = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeSource(els[i], params, referenceParams); + } + }; + + /* + Function: ready + Helper method to bind a function to jsPlumb's ready event. + */ + this.ready = function(fn) { + _currentInstance.bind("ready", fn); + }, + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"), + ebe = endpointsByElement[elId]; + if (ebe) { + for ( var i = 0; i < ebe.length; i++) + _currentInstance.deleteEndpoint(ebe[i]); + } + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + var _registeredListeners = {}, + _unbindRegisteredListeners = function() { + for (var i in _registeredListeners) { + for (var j = 0; j < _registeredListeners[i].length; j++) { + var info = _registeredListeners[i][j]; + jsPlumb.CurrentLibrary.unbind(info.el, info.event, info.listener); + } + } + _registeredListeners = {}; + }; + + // internal register listener method. gives us a hook to clean things up + // with if the user calls jsPlumb.reset. + this.registerListener = function(el, type, listener) { + jsPlumb.CurrentLibrary.bind(el, type, listener); + _addToList(_registeredListeners, type, {el:el, event:type, listener:listener}); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + _currentInstance.deleteEveryEndpoint(); + _currentInstance.clearListeners(); + _unbindRegisteredListeners(); + _currentInstance.anchorManager.reset(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + + Parameters: + value - whether or not to automatically repaint when the window is resized. + + Returns: void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction + * Sets the function to fire when the window size has changed and a repaint was fired. + * + * Parameters: + * f - Function to execute. + * + * Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: setMouseEventsEnabled + * Sets whether or not mouse events are enabled. Default is true. + * + * Parameters: + * enabled - whether or not mouse events should be enabled. + * + * Returns: + * void + */ + this.setMouseEventsEnabled = function(enabled) { + _mouseEventsEnabled = enabled; + }; + + /* + * Function: setSuspendDrawing + * Suspends drawing operations. This can be used when you have a lot of connections to make or endpoints to register; + * it will save you a lot of time. + */ + this.setSuspendDrawing = _setSuspendDrawing; + + /* + * Constant for use with the setRenderMode method + */ + this.CANVAS = "canvas"; + + /* + * Constant for use with the setRenderMode method + */ + this.SVG = "svg"; + + this.VML = "vml"; + + /* + * Function: setRenderMode + * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that + * what you asked for is not supported (and that VML is). If you asked for VML but the browser does + * not support it, jsPlumb uses SVG. + * + * Returns: + * the render mode that jsPlumb set, which of course may be different from that requested. + */ + this.setRenderMode = function(mode) { + if (mode) + mode = mode.toLowerCase(); + else + return; + if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); + // now test we actually have the capability to do this. + if (mode === jsPlumb.CANVAS && canvasAvailable) + renderMode = jsPlumb.CANVAS; + else if (mode === jsPlumb.SVG && svgAvailable) + renderMode = jsPlumb.SVG; + else if (vmlAvailable) + renderMode = jsPlumb.VML; + + return renderMode; + }; + + this.getRenderMode = function() { return renderMode; }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * changeEndpoints - whether or not to also change the visible state of the endpoints on the element. this also has a bearing on + * other connections on those endpoints: if their other endpoint is also visible, the connections are made visible. + * + * Returns: + * void + */ + this.show = function(el, changeEndpoints) { + _setVisible(el, "block", changeEndpoints); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId, + makeAnchor:self.makeAnchor, + makeDynamicAnchor:self.makeDynamicAnchor + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * changeEndpoints - whether or not to also toggle the endpoints on the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload + * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element. + * + * Returns: + * void + */ + this.unload = function() { + // this used to do something, but it turns out that what it did was nothing. + // now it exists only for backwards compatibility. + }; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + var adjustForParentOffsetAndScroll = function(xy, el) { + + var offsetParent = null, result = xy; + if (el.tagName.toLowerCase() === "svg" && el.parentNode) { + offsetParent = el.parentNode; + } + else if (el.offsetParent) { + offsetParent = el.offsetParent; + } + if (offsetParent != null) { + var po = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(offsetParent), + so = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : {left:offsetParent.scrollLeft, top:offsetParent.scrollTop}; + + + // i thought it might be cool to do this: + // lastReturnValue[0] = lastReturnValue[0] - offsetParent.offsetLeft + offsetParent.scrollLeft; + // lastReturnValue[1] = lastReturnValue[1] - offsetParent.offsetTop + offsetParent.scrollTop; + // but i think it ignores margins. my reasoning was that it's quicker to not hand off to some underlying + // library. + + result[0] = xy[0] - po.left + so.left; + result[1] = xy[1] - po.top + so.top; + } + + return result; + + }; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + this.elementId = params.elementId; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + + if (timestamp && timestamp === self.timestamp) + return lastReturnValue; + + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + + // adjust loc if there is an offsetParent + lastReturnValue = adjustForParentOffsetAndScroll(lastReturnValue, element.canvas); + + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function(_endpoint) { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + * + * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference, + // the canvas this refers to. + refCanvas = params.referenceCanvas, + size = _getSize(_getElementObject(refCanvas)), + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + xDir = 0, yDir = 0, + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + orientation = null, + _lastResult = null; + + // set these to 0 each; they are used by certain types of connectors in the loopback case, + // when the connector is trying to clear the element it is on. but for floating anchor it's not + // very important. + this.x = 0; this.y = 0; + + this.isFloating = true; + + this.compute = function(params) { + var xy = params.xy, element = params.element, + result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + + // adjust loc if there is an offsetParent + result = adjustForParentOffsetAndScroll(result, element.canvas); + + _lastResult = result; + return result; + }; + + this.getOrientation = function(_endpoint) { + if (orientation) return orientation; + else { + var o = ref.getOrientation(_endpoint); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchor is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector, elementId) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = [], self = this, + _convert = function(anchor) { + return anchor.constructor == Anchor ? anchor: _currentInstance.makeAnchor(anchor, elementId, _currentInstance); + }; + for (var i = 0; i < anchors.length; i++) + _anchors[i] = _convert(anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + this.locked = false; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null, + _curIndex = _anchors.length > 0 ? 0 : -1, + self = this, + + // helper method to calculate the distance between the centers of the two elements. + _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }, + + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + self.x = _curAnchor.x; + self.y = _curAnchor.y; + + return _curAnchor.compute(params); + }; + + this.getCurrentLocation = function() { + return _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + }; + + this.getOrientation = function(_endpoint) { return _curAnchor != null ? _curAnchor.getOrientation(_endpoint) : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + manages anchors for all elements. + */ + // "continuous" anchors: anchors that pick their location based on how many connections the given element has. + // this requires looking at a lot more elements than normal - anything that has had a Continuous anchor applied has + // to be recalculated. so this manager is used as a reference point. the first time, with a new timestamp, that + // a continuous anchor is asked to compute, it calls this guy. or maybe, even, this guy gets called somewhere else + // and compute only ever returns pre-computed values. either way, this is the central point, and we want it to + // be called as few times as possible. + var continuousAnchors = {}, + continuousAnchorLocations = {}, + continuousAnchorOrientations = {}, + Orientation = { HORIZONTAL : "horizontal", VERTICAL : "vertical", DIAGONAL : "diagonal", IDENTITY:"identity" }, + + // TODO this functions uses a crude method of determining orientation between two elements. + // 'diagonal' should be chosen when the angle of the line between the two centers is around + // one of 45, 135, 225 and 315 degrees. maybe +- 15 degrees. + calculateOrientation = function(sourceId, targetId, sd, td) { + + if (sourceId === targetId) return { + orientation:Orientation.IDENTITY, + a:["top", "top"] + }; + + var theta = Math.atan2((td.centery - sd.centery) , (td.centerx - sd.centerx)), + theta2 = Math.atan2((sd.centery - td.centery) , (sd.centerx - td.centerx)), + h = ((sd.left <= td.left && sd.right >= td.left) || (sd.left <= td.right && sd.right >= td.right) || + (sd.left <= td.left && sd.right >= td.right) || (td.left <= sd.left && td.right >= sd.right)), + v = ((sd.top <= td.top && sd.bottom >= td.top) || (sd.top <= td.bottom && sd.bottom >= td.bottom) || + (sd.top <= td.top && sd.bottom >= td.bottom) || (td.top <= sd.top && td.bottom >= sd.bottom)); + + if (! (h || v)) { + var a = null, rls = false, rrs = false, sortValue = null; + if (td.left > sd.left && td.top > sd.top) + a = ["right", "top"]; + else if (td.left > sd.left && sd.top > td.top) + a = [ "top", "left"]; + else if (td.left < sd.left && td.top < sd.top) + a = [ "top", "right"]; + else if (td.left < sd.left && td.top > sd.top) + a = ["left", "top" ]; + + return { orientation:Orientation.DIAGONAL, a:a, theta:theta, theta2:theta2 }; + } + else if (h) return { + orientation:Orientation.HORIZONTAL, + a:sd.top < td.top ? ["bottom", "top"] : ["top", "bottom"], + theta:theta, theta2:theta2 + } + else return { + orientation:Orientation.VERTICAL, + a:sd.left < td.left ? ["right", "left"] : ["left", "right"], + theta:theta, theta2:theta2 + } + }, + placeAnchorsOnLine = function(desc, elementDimensions, elementPosition, + connections, horizontal, otherMultiplier, reverse) { + var a = [], step = elementDimensions[horizontal ? 0 : 1] / (connections.length + 1); + + for (var i = 0; i < connections.length; i++) { + var val = (i + 1) * step, other = otherMultiplier * elementDimensions[horizontal ? 1 : 0]; + if (reverse) + val = elementDimensions[horizontal ? 0 : 1] - val; + + var dx = (horizontal ? val : other), x = elementPosition[0] + dx, xp = dx / elementDimensions[0], + dy = (horizontal ? other : val), y = elementPosition[1] + dy, yp = dy / elementDimensions[1]; + + a.push([ x, y, xp, yp, connections[i][1], connections[i][2] ]); + } + + return a; + }, + standardEdgeSort = function(a, b) { return a[0] > b[0] ? 1 : -1 }, + currySort = function(reverseAngles) { + return function(a,b) { + var r = true; + if (reverseAngles) { + if (a[0][0] < b[0][0]) + r = true; + else + r = a[0][1] > b[0][1]; + } + else { + if (a[0][0] > b[0][0]) + r= true; + else + r =a[0][1] > b[0][1]; + } + return r === false ? -1 : 1; + }; + }, + leftSort = function(a,b) { + // first get adjusted values + var p1 = a[0][0] < 0 ? -Math.PI - a[0][0] : Math.PI - a[0][0], + p2 = b[0][0] < 0 ? -Math.PI - b[0][0] : Math.PI - b[0][0]; + if (p1 > p2) return 1; + else return a[0][1] > b[0][1] ? 1 : -1; + }, + edgeSortFunctions = { + "top":standardEdgeSort, + "right":currySort(true), + "bottom":currySort(true), + "left":leftSort + }, + _sortHelper = function(_array, _fn) { + return _array.sort(_fn); + }, + placeAnchors = function(elementId, _anchorLists) { + var sS = sizes[elementId], sO = offsets[elementId], + placeSomeAnchors = function(desc, elementDimensions, elementPosition, unsortedConnections, isHorizontal, otherMultiplier) { + if (unsortedConnections.length > 0) { + var sc = _sortHelper(unsortedConnections, edgeSortFunctions[desc]), // puts them in order based on the target element's pos on screen + //sc = unsortedConnections.sort(edgeSortFunctions[desc]), // puts them in order based on the target element's pos on screen + reverse = desc === "right" || desc === "top", + anchors = placeAnchorsOnLine(desc, elementDimensions, + elementPosition, sc, + isHorizontal, otherMultiplier, reverse ); + + // takes a computed anchor position and adjusts it for parent offset and scroll, then stores it. + var _setAnchorLocation = function(endpoint, anchorPos) { + var a = adjustForParentOffsetAndScroll([anchorPos[0], anchorPos[1]], endpoint.canvas); + continuousAnchorLocations[endpoint.id] = [ a[0], a[1], anchorPos[2], anchorPos[3] ]; + }; + + for (var i = 0; i < anchors.length; i++) { + var c = anchors[i][4], weAreSource = c.endpoints[0].elementId === elementId, weAreTarget = c.endpoints[1].elementId === elementId; + if (weAreSource) + _setAnchorLocation(c.endpoints[0], anchors[i]); + else if (weAreTarget) + _setAnchorLocation(c.endpoints[1], anchors[i]); + } + } + }; + + placeSomeAnchors("bottom", sS, [sO.left,sO.top], _anchorLists.bottom, true, 1); + placeSomeAnchors("top", sS, [sO.left,sO.top], _anchorLists.top, true, 0); + placeSomeAnchors("left", sS, [sO.left,sO.top], _anchorLists.left, false, 0); + placeSomeAnchors("right", sS, [sO.left,sO.top], _anchorLists.right, false, 1); + }, + AnchorManager = function() { + var _amEndpoints = {}, + endpointConnectionsByElementId = {}, + continuousAnchorConnectionsByElementId = {}, + continuousAnchorEndpoints = [], + self = this, + anchorLists = {}; + + this.reset = function() { + endpointConnectionsByElementId = {}; + continuousAnchorConnectionsByElementId = {}; + continuousAnchorEndpoints = []; + anchorLists = {}; + }; + + this.newConnection = function(conn) { + var sourceId = conn.sourceId, targetId = conn.targetId, + ep = conn.endpoints, + doRegisterTarget = true, + registerConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + if (otherAnchor.constructor == DynamicAnchor || otherAnchor.constructor == Anchor) { + _addToList(endpointConnectionsByElementId, elId, [c, otherEndpoint, otherAnchor.constructor == DynamicAnchor]); + } + else { + // continuous. if they are the same element, just assign the same anchor + // to both. + if (sourceId == targetId) { + // remove the target endpoint's canvas. we dont need it. + jsPlumb.CurrentLibrary.removeElement(ep[1].canvas); + doRegisterTarget = false; + } + _addToList(continuousAnchorConnectionsByElementId, elId, c); + } + }; + registerConnection(0, ep[0], ep[0].anchor, targetId, conn); + if (doRegisterTarget) + registerConnection(1, ep[1], ep[1].anchor, sourceId, conn); + }; + this.connectionDetached = function(connInfo) { + var sourceId = connInfo.sourceId, + targetId = connInfo.targetId, + ep = connInfo.connection.endpoints, + removeConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + if (otherAnchor.constructor == FloatingAnchor) { + // no-op + } + else if (otherAnchor.constructor == DynamicAnchor || otherAnchor.constructor == Anchor) { + var _conns = endpointConnectionsByElementId[elId]; + if (_conns) { + _removeWithFunction(_conns, function(e) { return e[0].id == c.id; }); + } + } + else // continuous. + _removeWithFunction(continuousAnchorConnectionsByElementId[elId], function(_c) { + return _c.id == c.id; + }); + }; + + removeConnection(1, ep[1], ep[1].anchor, sourceId, connInfo.connection); + removeConnection(0, ep[0], ep[0].anchor, targetId, connInfo.connection); + + // remove from anchorLists + var sEl = connInfo.connection.sourceId, + tEl = connInfo.connection.targetId, + sE = connInfo.connection.endpoints[0].id, + tE = connInfo.connection.endpoints[1].id, + _remove = function(list, eId) { + if (list) { // transient anchors dont get entries in this list. + var f = function(e) { return e[4] == eId; }; + _removeWithFunction(list["top"], f); + _removeWithFunction(list["left"], f); + _removeWithFunction(list["bottom"], f); + _removeWithFunction(list["right"], f); + } + }; + + _remove(anchorLists[sEl], sE); + _remove(anchorLists[tEl], tE); + self.redraw(sEl); + self.redraw(tEl); + }; + this.add = function(endpoint, elementId) { + _addToList(_amEndpoints, elementId, endpoint); + }; + this.get = function(elementId) { + return { + "standard":endpointConnectionsByElementId[elementId] || [], + "continuous":continuousAnchorConnectionsByElementId[elementId] || [], + "endpoints":_amEndpoints[elementId], + "continuousAnchorEndpoints":continuousAnchorEndpoints + }; + }; + this.deleteEndpoint = function(endpoint) { + var cIdx = _findIndex(continuousAnchorEndpoints, endpoint); + if (cIdx > -1) + continuousAnchorEndpoints.splice(cIdx, 1); + else + _removeWithFunction(_amEndpoints[endpoint.elementId], function(e) { + return e.id == endpoint.id; + }); + }; + this.clearFor = function(elementId) { + delete _amEndpoints[elementId]; + _amEndpoints[elementId] = []; + }; + // updates the given anchor list by either updating an existing anchor's info, or adding it. this function + // also removes the anchor from its previous list, if the edge it is on has changed. + // all connections found along the way (those that are connected to one of the faces this function + // operates on) are added to the connsToPaint list, as are their endpoints. in this way we know to repaint + // them wthout having to calculate anything else about them. + var _updateAnchorList = function(lists, theta, order, conn, aBoolean, otherElId, idx, reverse, edgeId, elId, connsToPaint, endpointsToPaint) { + // first try to find the exact match, but keep track of the first index of a matching element id along the way.s + var exactIdx = -1, + firstMatchingElIdx = -1, + endpoint = conn.endpoints[idx], + endpointId = endpoint.id, + oIdx = [1,0][idx], + values = [ [ theta, order ], conn, aBoolean, otherElId, endpointId ], + listToAddTo = lists[edgeId], + listToRemoveFrom = endpoint._continuousAnchorEdge ? lists[endpoint._continuousAnchorEdge] : null; + + if (listToRemoveFrom) { + var rIdx = _findWithFunction(listToRemoveFrom, function(e) { return e[4] == endpointId }); + if (rIdx != -1) { + listToRemoveFrom.splice(rIdx, 1); + // get all connections from this list + for (var i = 0; i < listToRemoveFrom.length; i++) { + _addWithFunction(connsToPaint, listToRemoveFrom[i][1], function(c) { return c.id == listToRemoveFrom[i][1].id }); + _addWithFunction(endpointsToPaint, listToRemoveFrom[i][1].endpoints[idx], function(e) { return e.id == listToRemoveFrom[i][1].endpoints[idx].id }); + } + } + } + + for (var i = 0; i < listToAddTo.length; i++) { + if (idx == 1 && listToAddTo[i][3] === otherElId && firstMatchingElIdx == -1) + firstMatchingElIdx = i; + _addWithFunction(connsToPaint, listToAddTo[i][1], function(c) { return c.id == listToAddTo[i][1].id }); + _addWithFunction(endpointsToPaint, listToAddTo[i][1].endpoints[idx], function(e) { return e.id == listToAddTo[i][1].endpoints[idx].id }); + } + if (exactIdx != -1) { + listToAddTo[exactIdx] = values; + } + else { + var insertIdx = reverse ? firstMatchingElIdx != -1 ? firstMatchingElIdx : 0 : listToAddTo.length; // of course we will get this from having looked through the array shortly. + listToAddTo.splice(insertIdx, 0, values); + } + + // store this for next time. + endpoint._continuousAnchorEdge = edgeId; + }; + this.redraw = function(elementId, ui, timestamp) { + // get all the endpoints for this element + var ep = _amEndpoints[elementId] || [], + endpointConnections = endpointConnectionsByElementId[elementId] || [], + continuousAnchorConnections = continuousAnchorConnectionsByElementId[elementId] || [], + connectionsToPaint = [], + endpointsToPaint = [], + anchorsToUpdate = []; + + timestamp = timestamp || _timestamp(); + + _updateOffset( { elId : elementId, offset : ui, recalc : false, timestamp : timestamp }); + // valid for one paint cycle. + var myOffset = offsets[elementId], + myWH = sizes[elementId], + orientationCache = {}; + + // actually, first we should compute the orientation of this element to all other elements to which + // this element is connected with a continuous anchor (whether both ends of the connection have + // a continuous anchor or just one) + for (var i = 0; i < continuousAnchorConnections.length; i++) { + var conn = continuousAnchorConnections[i], + sourceId = conn.sourceId, + targetId = conn.targetId, + oKey = sourceId + "_" + targetId, + oKey2 = targetId + "_" + sourceId, + o = orientationCache[oKey], + td = _getCachedData(targetId), + sd = _getCachedData(sourceId), + oIdx = conn.sourceId == elementId ? 1 : 0; + + if (!anchorLists[sourceId]) anchorLists[sourceId] = { top:[], right:[], bottom:[], left:[] }; + if (!anchorLists[targetId]) anchorLists[targetId] = { top:[], right:[], bottom:[], left:[] }; + + if (targetId == sourceId) { + // here we may want to improve this by somehow determining the face we'd like + // to put the connector on. ideally, when drawing, the face should be calculated + // by determining which face is closest to the point at which the mouse button + // was released. for now, we're putting it on the top face. + _updateAnchorList(anchorLists[sourceId], -Math.PI / 2, 0, conn, false, targetId, 0, false, "top", sourceId, connectionsToPaint, endpointsToPaint) + } + else { + if (!o) { + o = calculateOrientation(sourceId, targetId, sd.o, td.o); + orientationCache[oKey] = o; + // this would be a performance enhancement, but the computed angles need to be clamped to + //the (-PI/2 -> PI/2) range in order for the sorting to work properly. + /* orientationCache[oKey2] = { + orientation:o.orientation, + a:[o.a[1], o.a[0]], + theta:o.theta + Math.PI, + theta2:o.theta2 + Math.PI + };*/ + } + _updateAnchorList(anchorLists[sourceId], o.theta, 0, conn, false, targetId, 0, false, o.a[0], sourceId, connectionsToPaint, endpointsToPaint); + _updateAnchorList(anchorLists[targetId], o.theta2, -1, conn, true, sourceId, 1, true, o.a[1], targetId, connectionsToPaint, endpointsToPaint); + } + + _addWithFunction(anchorsToUpdate, sourceId, function(a) { return a === sourceId; }); + _addWithFunction(anchorsToUpdate, targetId, function(a) { return a === targetId; }); + _addWithFunction(connectionsToPaint, conn, function(c) { return c.id == conn.id; }); + _addWithFunction(endpointsToPaint, conn.endpoints[oIdx], function(e) { return e.id == conn.endpoints[oIdx].id; }); + } + + // now place all the continuous anchors we need to; + for (var i = 0; i < anchorsToUpdate.length; i++) { + placeAnchors(anchorsToUpdate[i], anchorLists[anchorsToUpdate[i]]); + } + + // now that continuous anchors have been placed, paint all the endpoints for this element + // TODO performance: add the endpoint ids to a temp array, and then when iterating in the next + // loop, check that we didn't just paint that endpoint. we can probably shave off a few more milliseconds this way. + for (var i = 0; i < ep.length; i++) { + ep[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + // ... and any other endpoints we came across as a result of the continuous anchors. + for (var i = 0; i < endpointsToPaint.length; i++) { + endpointsToPaint[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + + + // paint all the standard and "dynamic connections", which are connections whose other anchor is + // static and therefore does need to be recomputed; we make sure that happens only one time. + for (var i = 0; i < endpointConnections.length; i++) { + var otherEndpoint = endpointConnections[i][1]; + if (otherEndpoint.anchor.constructor == DynamicAnchor) { + otherEndpoint.paint({ elementWithPrecedence:elementId }); + _addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; }); + // all the connections for the other endpoint now need to be repainted + for (var k = 0; k < otherEndpoint.connections.length; k++) { + if (otherEndpoint.connections[k] !== endpointConnections[i][0]) + _addWithFunction(connectionsToPaint, otherEndpoint.connections[k], function(c) { return c.id == otherEndpoint.connections[k].id; }); + } + } else if (otherEndpoint.anchor.constructor == Anchor) { + _addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; }); + } + } + // paint current floating connection for this element, if there is one. + var fc = floatingConnections[elementId]; + if (fc) + fc.paint({timestamp:timestamp, recalc:false, elId:elementId}); + + // paint all the connections + for (var i = 0; i < connectionsToPaint.length; i++) { + connectionsToPaint[i].paint({elId:elementId, timestamp:timestamp, recalc:false}); + } + }; + this.rehomeEndpoint = function(currentId, element) { + var eps = _amEndpoints[currentId] || [], //, + elementId = _currentInstance.getId(element); + for (var i = 0; i < eps.length; i++) { + self.add(eps[i], elementId); + } + eps.splice(0, eps.length); + }; + }; + _currentInstance.anchorManager = new AnchorManager(); + _currentInstance.continuousAnchorFactory = { + get:function(params) { + var existing = continuousAnchors[params.elementId]; + if (!existing) { + existing = { + type:"Continuous", + compute : function(params) { + return continuousAnchorLocations[params.element.id] || [0,0]; + }, + getCurrentLocation : function(endpoint) { + return continuousAnchorLocations[endpoint.id] || [0,0]; + }, + getOrientation : function(endpoint) { + return continuousAnchorOrientations[endpoint.id] || [0,0]; + }, + isDynamic : true, + isContinuous : true + }; + continuousAnchors[params.elementId] = existing; + } + return existing; + } + }; + + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * container - optional id or selector instructing jsPlumb where to attach all the elements it creates for this connection. you should read the documentation for a full discussion of this. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * overlays - Optional array of Overlay definitions to appear on this Connection. + * drawEndpoints - if false, instructs jsPlumb to not draw the endpoints for this Connection. Be careful with this: it only really works when you tell jsPlumb to attach elements to the document body. Read the documentation for a full discussion of this. + */ + var Connection = function(params) { + var self = this, visible = true; + self.idPrefix = "_jsplumb_c_"; + self.defaultLabelLocation = 0.5; + self.defaultOverlayKeys = ["Overlays", "ConnectionOverlays"]; + this.parent = params.parent; + overlayCapableJsPlumbUIComponent.apply(this, arguments); + // ************** get the source and target and register the connection. ******************* + + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none"; + }; + + /** + Property: source + The source element for this Connection. + */ + this.source = _getElementObject(params.source); + /** + Property:target + The target element for this Connection. + */ + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. but + // source is not overridden if the Endpoint has declared it is not the final target of a connection; + // instead we use the source that the Endpoint declares will be the final source element. + if (params.sourceEndpoint) { + this.source = params.sourceEndpoint.endpointWillMoveTo || params.sourceEndpoint.getElement(); + } + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + + // if a new connection is the result of moving some existing connection, params.previousConnection + // will have that Connection in it. listeners for the jsPlumbConnection event can look for that + // member and take action if they need to. + self.previousConnection = params.previousConnection; + + var _cost = params.cost; + self.getCost = function() { return _cost; }; + self.setCost = function(c) { _cost = c; }; + + var _bidirectional = params.bidirectional === true; + self.isBidirectional = function() { return _bidirectional; }; + + /* + * Property: sourceId + * Id of the source element in the connection. + */ + this.sourceId = _getAttribute(this.source, "id"); + /* + * Property: targetId + * Id of the target element in the connection. + */ + this.targetId = _getAttribute(this.target, "id"); + + /** + * implementation of abstract method in EventGenerator + * @return list of attached elements. in our case, a list of Endpoints. + */ + this.getAttachedElements = function() { + return self.endpoints; + }; + + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams, elementId) { + if (anchorParams) + return _currentInstance.makeAnchor(anchorParams, elementId, _currentInstance); + }, + prepareEndpoint = function(existing, index, params, element, elementId, connectorPaintStyle, connectorHoverPaintStyle) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] + || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint; + + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified. + if (es.fillStyle == null && connectorPaintStyle != null) + es.fillStyle = connectorPaintStyle.strokeStyle; + + // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does: + //* + if (es.outlineColor == null && connectorPaintStyle != null) + es.outlineColor = connectorPaintStyle.outlineColor; + if (es.outlineWidth == null && connectorPaintStyle != null) + es.outlineWidth = connectorPaintStyle.outlineWidth; + //*/ + + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure? + if (connectorHoverPaintStyle != null) { + if (ehs == null) ehs = {}; + if (ehs.fillStyle == null) { + ehs.fillStyle = connectorHoverPaintStyle.strokeStyle; + } + } + var a = params.anchors ? params.anchors[index] : + params.anchor ? params.anchor : + _makeAnchor(_currentInstance.Defaults.Anchors[index], elementId) || + _makeAnchor(jsPlumb.Defaults.Anchors[index], elementId) || + _makeAnchor(_currentInstance.Defaults.Anchor, elementId) || + _makeAnchor(jsPlumb.Defaults.Anchor, elementId), + u = params.uuids ? params.uuids[index] : null, + e = _newEndpoint({ + paintStyle : es, + hoverPaintStyle:ehs, + endpoint : ep, + connections : [ self ], + uuid : u, + anchor : a, + source : element, + container:params.container, + reattach:params.reattach, + detachable:params.detachable + }); + self.endpoints[index] = e; + + + if (params.drawEndpoints === false) e.setVisible(false, true, true); + + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, + 0, + params, + self.source, + self.sourceId, + params.paintStyle, + params.hoverPaintStyle); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + + // if there were no endpoints supplied and the source element is the target element, we will reuse the source + // endpoint that was just created. + var existingTargetEndpoint = ((self.sourceId == self.targetId) && params.targetEndpoint == null) ? eS : params.targetEndpoint, + eT = prepareEndpoint(existingTargetEndpoint, + 1, + params, + self.target, + self.targetId, + params.paintStyle, + params.hoverPaintStyle); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + // if delete endpoints on detach, keep a record of just exactly which endpoints they are. + if (params.deleteEndpointsOnDetach) + self.endpointsToDeleteOnDetach = [eS, eT]; + + var _detachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.detachable === false) _detachable = false; + if(self.endpoints[0].connectionsDetachable === false) _detachable = false; + if(self.endpoints[1].connectionsDetachable === false) _detachable = false; + + // inherit connectin cost if it was set on source endpoint + if (_cost == null) _cost = self.endpoints[0].getConnectionCost(); + // inherit bidirectional flag if set no source endpoint + if (params.bidirectional == null) _bidirectional = self.endpoints[0].areConnectionsBidirectional(); + + /* + Function: isDetachable + Returns whether or not this connection can be detached from its target/source endpoint. by default this + is false; use it in conjunction with the 'reattach' parameter. + */ + this.isDetachable = function() { + return _detachable === true; + }; + + /* + Function: setDetachable + Sets whether or not this connection is detachable. + */ + this.setDetachable = function(detachable) { + _detachable = detachable === true; + }; + + // merge all the parameters objects into the connection. parameters set + // on the connection take precedence; then target endpoint params, then + // finally source endpoint params. + // TODO jsPlumb.extend could be made to take more than two args, and it would + // apply the second through nth args in order. + var _p = jsPlumb.extend({}, this.endpoints[0].getParameters()); + jsPlumb.extend(_p, this.endpoints[1].getParameters()); + jsPlumb.extend(_p, self.getParameters()); + self.setParameters(_p); + + // override setHover to pass it down to the underlying connector + var _sh = self.setHover; + + self.setHover = function() { + self.connector.setHover.apply(self.connector, arguments); + _sh.apply(self, arguments); + }; + + var _internalHover = function(state) { + if (_connectionBeingDragged == null) { + self.setHover(state, false); + } + }; + + /* + * Function: setConnector + * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method - the same + * thing that you would set as the 'connector' property on a jsPlumb.connect call. + * + * Parameters: + * connector - Connector definition + */ + this.setConnector = function(connector, doNotRepaint) { + if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent); + var connectorArgs = { + _jsPlumb:self._jsPlumb, + parent:params.parent, + cssClass:params.cssClass, + container:params.container, + tooltip:self.tooltip + }; + if (connector.constructor == String) + this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand. + else if (connector.constructor == Array) + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs)); + self.canvas = self.connector.canvas; + // binds mouse listeners to the current connector. + _bindListeners(self.connector, self, _internalHover); + if (!doNotRepaint) self.repaint(); + }; + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc) + */ + + self.setConnector(this.endpoints[0].connector || + this.endpoints[1].connector || + params.connector || + _currentInstance.Defaults.Connector || + jsPlumb.Defaults.Connector, true); + + + this.setPaintStyle(this.endpoints[0].connectorStyle || + this.endpoints[1].connectorStyle || + params.paintStyle || + _currentInstance.Defaults.PaintStyle || + jsPlumb.Defaults.PaintStyle, true); + + this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || + this.endpoints[1].connectorHoverStyle || + params.hoverPaintStyle || + _currentInstance.Defaults.HoverPaintStyle || + jsPlumb.Defaults.HoverPaintStyle, true); + + this.paintStyleInUse = this.paintStyle; + + + this.moveParent = function(newParent) { + var jpcl = jsPlumb.CurrentLibrary, curParent = jpcl.getParent(self.connector.canvas); + jpcl.removeElement(self.connector.canvas, curParent); + jpcl.appendElement(self.connector.canvas, newParent); + if (self.connector.bgCanvas) { + jpcl.removeElement(self.connector.bgCanvas, curParent); + jpcl.appendElement(self.connector.bgCanvas, newParent); + } + // this only applies for DOMOverlays + for (var i = 0; i < self.overlays.length; i++) { + if (self.overlays[i].isAppendedAtTopLevel) { + jpcl.removeElement(self.overlays[i].canvas, curParent); + jpcl.appendElement(self.overlays[i].canvas, newParent); + if (self.overlays[i].reattachListeners) + self.overlays[i].reattachListeners(self.connector); + } + } + if (self.connector.reattachListeners) // this is for SVG/VML; change an element's parent and you have to reinit its listeners. + self.connector.reattachListeners(); // the Canvas implementation doesn't have to care about this + }; + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Connection. + * + * Parameters: + * event - the event to bind. Available events on a Connection are: + * - *click* : notification that a Connection was clicked. + * - *dblclick* : notification that a Connection was double clicked. + * - *mouseenter* : notification that the mouse is over a Connection. + * - *mouseexit* : notification that the mouse exited a Connection. + * + * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Connection's paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Connection will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Connection. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId], + otherOffset = offsets[this.targetId], + otherWH = sizes[this.targetId], + initialTimestamp = _timestamp(), + anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + elementId:this.endpoints[0].elementId, + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1], + timestamp:initialTimestamp + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc, timestamp:initialTimestamp }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + elementId:this.endpoints[1].elementId, + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0], + timestamp:initialTimestamp + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc, timestamp:initialTimestamp }); + + /* + * Paints the Connection. Not exposed for public usage. + * + * Parameters: + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp, + // if the moving object is not the source we must transpose the two references. + swap = false, + tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId, + tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + + var sourceInfo = _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }), + targetInfo = _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var sE = this.endpoints[sIdx], tE = this.endpoints[tIdx], + sAnchorP = sE.anchor.getCurrentLocation(sE), + tAnchorP = tE.anchor.getCurrentLocation(tE); + + /* paint overlays*/ + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible()) maxSize = Math.max(maxSize, o.computeMaxSize(self.connector)); + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, + this.endpoints[sIdx], this.endpoints[tIdx], + this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, + self.paintStyleInUse.lineWidth, maxSize, + sourceInfo, + targetInfo); + + self.connector.paint(dim, self.paintStyleInUse); + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim); + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function(params) { + params = params || {}; + var recalc = !(params.recalc === false); + this.paint({ elId : this.sourceId, recalc : recalc, timestamp:params.timestamp }); + }; + + // just to make sure the UI gets initialised fully on all browsers. + self.repaint(); + }; + +// ENDPOINT HELPER FUNCTIONS + var _makeConnectionDragHandler = function(placeholder) { + var stopped = false; + return { + drag : function() { + if (stopped) return true; + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments), + el = placeholder.element; + if (el) { + jsPlumb.CurrentLibrary.setOffset(el, _ui); + _draw(_getElementObject(el), _ui); + } + }, + stopDrag : function() { + stopped = true; + } + }; + }; + + var _makeFloatingEndpoint = function(paintStyle, referenceAnchor, endpoint, referenceCanvas, sourceElement) { + var floatingAnchor = new FloatingAnchor( { reference : referenceAnchor, referenceCanvas : referenceCanvas }); + + //setting the scope here should not be the way to fix that mootools issue. it should be fixed by not + // adding the floating endpoint as a droppable. that makes more sense anyway! + + return _newEndpoint({ paintStyle : paintStyle, endpoint : endpoint, anchor : floatingAnchor, source : sourceElement, scope:"__floating" }); + }; + + /** + * creates a placeholder div for dragging purposes, adds it to the DOM, and pre-computes its offset. then returns + * both the element id and a selector for the element. + */ + var _makeDraggablePlaceholder = function(placeholder, parent) { + var n = document.createElement("div"); + n.style.position = "absolute"; + var placeholderDragElement = _getElementObject(n); + _appendElement(n, parent); + var id = _getId(placeholderDragElement); + _updateOffset( { elId : id }); + // create and assign an id, and initialize the offset. + placeholder.id = id; + placeholder.element = placeholderDragElement; + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1 + * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't + * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you + * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or + * 'target' Endpoints for Connections. + * + * + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * container - optional id or selector instructing jsPlumb where to attach the element it creates for this endpoint. you should read the documentation for a full discussion of this. + * connections - optional list of Connections to configure the Endpoint with. + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + var self = this; + self.idPrefix = "_jsplumb_e_"; + self.defaultLabelLocation = [ 0.5, 0.5 ]; + self.defaultOverlayKeys = ["Overlays", "EndpointOverlays"]; + this.parent = params.parent; + overlayCapableJsPlumbUIComponent.apply(this, arguments); + params = params || {}; + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Endpoint. + * + * Parameters: + * event - the event to bind. Available events on an Endpoint are: + * - *click* : notification that a Endpoint was clicked. + * - *dblclick* : notification that a Endpoint was double clicked. + * - *mouseenter* : notification that the mouse is over a Endpoint. + * - *mouseexit* : notification that the mouse exited a Endpoint. + * + * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Endpoint's paint style and then repaints the Endpoint. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Endpoint will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Endpoint. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + var visible = true; + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + var _element = params.source, _uuid = params.uuid, floatingEndpoint = null, inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + this.element = _element; + + var _connectionCost = params.connectionCost; + this.getConnectionCost = function() { return _connectionCost; }; + this.setConnectionCost = function(c) { + _connectionCost = c; + }; + + var _connectionsBidirectional = params.connectionsBidirectional === true; + this.areConnectionsBidirectional = function() { return _connectionsBidirectional; }; + this.setConnectionsBidirectional = function(b) { _connectionsBidirectional = b; }; + + self.anchor = params.anchor ? _currentInstance.makeAnchor(params.anchor, _elementId, _currentInstance) : params.anchors ? _currentInstance.makeAnchor(params.anchors, _elementId, _currentInstance) : _currentInstance.makeAnchor("TopCenter", _elementId, _currentInstance); + + // ANCHOR MANAGER + if (!params._transient) // in place copies, for example, are transient. they will never need to be retrieved during a paint cycle, because they dont move, and then they are deleted. + _currentInstance.anchorManager.add(self, _elementId); + + var _endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot", + endpointArgs = { + _jsPlumb:self._jsPlumb, + parent:params.parent, + container:params.container, + tooltip:params.tooltip, + connectorTooltip:params.connectorTooltip, + endpoint:self + }; + if (_endpoint.constructor == String) + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint](endpointArgs); + else if (_endpoint.constructor == Array) { + endpointArgs = jsPlumb.extend(_endpoint[1], endpointArgs); + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint[0]](endpointArgs); + } + else { + _endpoint = _endpoint.clone(); + } + + // assign a clone function using a copy of endpointArgs. this is used when a drag starts: the endpoint that was dragged is cloned, + // and the clone is left in its place while the original one goes off on a magical journey. + // the copy is to get around a closure problem, in which endpointArgs ends up getting shared by + // the whole world. + var argsForClone = jsPlumb.extend({}, endpointArgs); + _endpoint.clone = function() { + var o = new Object(); + _endpoint.constructor.apply(o, [argsForClone]); + return o; + }; + + self.endpoint = _endpoint; + self.type = self.endpoint.type; + // override setHover to pass it down to the underlying endpoint + var _sh = self.setHover; + self.setHover = function() { + self.endpoint.setHover.apply(self.endpoint, arguments); + _sh.apply(self, arguments); + }; + // endpoint delegates to first connection for hover, if there is one. + var internalHover = function(state) { + if (self.connections.length > 0) + self.connections[0].setHover(state, false); + else + self.setHover(state); + }; + + // bind listeners from endpoint to self, with the internal hover function defined above. + _bindListeners(self.endpoint, self, internalHover); + + this.setPaintStyle(params.paintStyle || + params.style || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyle, true); + this.setHoverPaintStyle(params.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyle, true); + this.paintStyleInUse = this.paintStyle; + this.connectorStyle = params.connectorStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.connectorTooltip = params.connectorTooltip; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + + var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + /* + * Property: canvas + * The Endpoint's Canvas. + */ + this.canvas = this.endpoint.canvas; + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + this.connections = params.connections || []; + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + self.isReattach = params.reattach || false; + self.connectionsDetachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.connectionsDetachable === false || params.detachable === false) + self.connectionsDetachable = false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget, forceDetach, fireEvent) { + //var idx = _findIndex(self.connections, connection), actuallyDetached = false; + var idx = _findWithFunction(self.connections, function(c) { return c.id == connection.id}), + actuallyDetached = false; + fireEvent = (fireEvent !== false); + if (idx >= 0) { + // 1. does the connection have a before detach (note this also checks jsPlumb's bound + // detach handlers; but then Endpoint's check will, too, hmm.) + if (forceDetach || connection._forceDetach || connection.isDetachable() || connection.isDetachAllowed(connection)) { + // get the target endpoint + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + // it would be nice to check with both endpoints that it is ok to detach. but + // for this we'll have to get a bit fancier: right now if you use the same beforeDetach + // interceptor for two endpoints (which is kind of common, because it's part of the + // endpoint definition), then it gets fired twice. so in fact we need to loop through + // each beforeDetach and see if it returns false, at which point we exit. but if it + // returns true, we have to check the next one. however we need to track which ones + // have already been run, and not run them again. + if (forceDetach || connection._forceDetach || (self.isDetachAllowed(connection) /*&& t.isDetachAllowed(connection)*/)) { + + self.connections.splice(idx, 1); + + // this avoids a circular loop + if (!ignoreTarget) { + + t.detach(connection, true, forceDetach); + // check connection to see if we want to delete the endpoints associated with it. + // we only detach those that have just this connection; this scenario is most + // likely if we got to this bit of code because it is set by the methods that + // create their own endpoints, like .connect or .makeTarget. the user is + // not likely to have interacted with those endpoints. + if (connection.endpointsToDeleteOnDetach){ + for (var i = 0; i < connection.endpointsToDeleteOnDetach.length; i++) { + var cde = connection.endpointsToDeleteOnDetach[i]; + if (cde && cde.connections.length == 0) + _currentInstance.deleteEndpoint(cde); + } + } + } + _removeElements(connection.connector.getDisplayElements(), connection.parent); + _removeWithFunction(connectionsByScope[connection.scope], function(c) { + return c.id == connection.id; + }); + actuallyDetached = true; + var doFireEvent = (!ignoreTarget && fireEvent) + fireDetachEvent(connection, doFireEvent); + } + } + } + return actuallyDetached; + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + * + * Parameters: + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachAll = function(fireEvent) { + while (self.connections.length > 0) { + self.detach(self.connections[0], false, true, fireEvent); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachFrom = function(targetEndpoint, fireEvent) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + if (self.detach(c[i], false, true, fireEvent)) + c[i].setHover(false, false); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: setElement + * Sets the DOM element this Endpoint is attached to. + */ + this.setElement = function(el) { + // TODO possibly have this object take charge of moving the UI components into the appropriate + // parent. this is used only by makeSource right now, and that function takes care of + // moving the UI bits and pieces. however it would s + var parentId = _getId(el); + // remove the endpoint from the list for the current endpoint's element + _removeWithFunction(endpointsByElement[_elementId], function(e) { + return e.id == self.id; + }); + _element = _getElementObject(el); + _elementId = _getId(_element); + self.elementId = _elementId; + + // need to get the new parent now + var newParentElement = _getParentFromParams({source:parentId}), + curParent = jpcl.getParent(self.canvas); + jpcl.removeElement(self.canvas, curParent); + jpcl.appendElement(self.canvas, newParentElement); + + // now move connection(s)...i would expect there to be only one but we will iterate. + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].moveParent(newParentElement); + self.connections[i].sourceId = _elementId; + self.connections[i].source = _element; + } + + _addToList(endpointsByElement, parentId, self); + _currentInstance.repaint(parentId); + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + return _newEndpoint( { + anchor : self.anchor, + source : _element, + paintStyle : this.paintStyle, + endpoint : _endpoint, + _transient:true, + scope:self.scope + }); + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + var candidate = self.connections[0]; + if (self.isTarget && candidate) return candidate; + else { + return (self.connections.length < _maxConnections) || _maxConnections == -1 ? null : candidate; + } + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + /* + * Function: setStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to + * setStyle and deprecate it if so. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + * + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + // a helper function that tries to find a connection to the given element, and returns it if so. if elementWithPrecedence is null, + // or no connection to it is found, we return the first connection in our list. + var findConnectionToUseForDynamicAnchor = function(elementWithPrecedence) { + var idx = 0; + if (elementWithPrecedence != null) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].sourceId == elementWithPrecedence || self.connections[i].targetId == elementWithPrecedence) { + idx = i; + break; + } + } + } + + return self.connections[idx]; + }; + + /* + * Function: paint + * Paints the Endpoint, recalculating offset and anchor positions if necessary. This does NOT paint + * any of the Endpoint's connections. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + params = params || {}; + var timestamp = params.timestamp, + recalc = !(params.recalc === false); + if (!timestamp || self.timestamp !== timestamp) { + _updateOffset({ elId:_elementId, timestamp:timestamp, recalc:recalc }); + var xy = params.offset || offsets[_elementId]; + if(xy) { + var ap = params.anchorPoint,connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (recalc && self.anchor.isDynamic && self.connections.length > 0) { + var c = findConnectionToUseForDynamicAnchor(params.elementWithPrecedence), + oIdx = c.endpoints[0] == self ? 1 : 0, + oId = oIdx == 0 ? c.sourceId : c.targetId, + oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + ap = self.anchor.compute(anchorParams); + } + + var d = _endpoint.compute(ap, self.anchor.getOrientation(_endpoint), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + _endpoint.paint(d, self.paintStyleInUse, self.anchor); + self.timestamp = timestamp; + + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) self.overlayPlacements[i] = o.draw(self.endpoint, self.paintStyleInUse, d); + } + } + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var placeholderInfo = { + id:null, + element:null + }, + jpc = null, + existingJpc = false, + existingJpcParams = null, + _dragHandler = _makeConnectionDragHandler(placeholderInfo); + + var start = function() { + // drag might have started on an endpoint that is not actually a source, but which has + // one or more connections. + jpc = self.connectorSelector(); + var _continue = true; + // if no connection and we're not a source, return. + if (jpc == null && !params.isSource) _continue = false; + // otherwise if we're full and not allowed to drag, also return false. + if (params.isSource && self.isFull() && !dragAllowedWhenFull) _continue = false; + + // if the connection was setup as not detachable (or one of its endpoints + // was setup as connectionsDetachable = false, or Defaults.ConnectionsDetachable + // is set to false... + if (jpc != null && !jpc.isDetachable()) _continue = false; + + if (_continue === false) { + // this is for mootools and yui. returning false from this causes jquery to stop drag. + // the events are wrapped in both mootools and yui anyway, but i don't think returning + // false from the start callback would stop a drag. + if (jsPlumb.CurrentLibrary.stopDrag) jsPlumb.CurrentLibrary.stopDrag(); + _dragHandler.stopDrag(); + return false; + } + + // if we're not full but there was a connection, make it null. we'll create a new one. + if (jpc && !self.isFull() && params.isSource) jpc = null; + + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + _makeDraggablePlaceholder(placeholderInfo, self.parent); + + // set the offset of this div to be where 'inPlaceCopy' is, to start with. + // TODO merge this code with the code in both Anchor and FloatingAnchor, because it + // does the same stuff. + var ipcoel = _getElementObject(inPlaceCopy.canvas), + ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel), + po = adjustForParentOffsetAndScroll([ipco.left, ipco.top], inPlaceCopy.canvas); + jsPlumb.CurrentLibrary.setOffset(placeholderInfo.element, {left:po[0], top:po[1]}); + + // when using makeSource and a parent, we first draw the source anchor on the source element, then + // move it to the parent. note that this happens after drawing the placeholder for the + // first time. + if (self.parentAnchor) self.anchor = _currentInstance.makeAnchor(self.parentAnchor, self.elementId, _currentInstance); + + + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", placeholderInfo.id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + floatingEndpoint = _makeFloatingEndpoint(self.paintStyle, self.anchor, _endpoint, self.canvas, placeholderInfo.element); + + if (jpc == null) { + self.anchor.locked = true; + self.setHover(false, false); + // TODO the hover call above does not reset any target endpoint's hover + // states. + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : self.endpointWillMoveTo || _getElementObject(_element), // for makeSource with parent option. ensure source element is represented correctly. + target : placeholderInfo.element, + anchors : [ self.anchor, floatingEndpoint.anchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays + }); + + } else { + existingJpc = true; + jpc.connector.setHover(false, false); + // jpc.endpoints[0].setHover(false, false); + //jpc.endpoints[1].setHover(false, false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas), false, true); + //var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; // are we the source or the target? + // new anchor idx + var anchorIdx = jpc.endpoints[0].id == self.id ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas), + dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // now we want to get this endpoint's DROP scope, and set it for now: we can only be dropped on drop zones + // that have our drop scope (issue 57). + var dropScope = jsPlumb.CurrentLibrary.getDropScope(c); + jsPlumb.CurrentLibrary.setDragScope(c, dropScope); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = placeholderInfo.element; + jpc.sourceId = placeholderInfo.id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = placeholderInfo.element; + jpc.targetId = placeholderInfo.id; + } + + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.suspendedEndpoint.setHover(false); + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + // register it and register connection on it. + floatingConnections[placeholderInfo.id] = jpc; + floatingEndpoint.addConnection(jpc); + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, placeholderInfo.id, floatingEndpoint); + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var jpcl = jsPlumb.CurrentLibrary, + dragOptions = params.dragOptions || {}, + defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions), + startEvent = jpcl.dragEvents["start"], + stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"]; + + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + // extracted drag handler function so can be used by makeSource + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], _dragHandler.drag); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _currentInstance.currentlyDragging = false; + _removeWithFunction(endpointsByElement[placeholderInfo.id], function(e) { + return e.id == floatingEndpoint.id; + }); + _removeElements( [ placeholderInfo.element[0], floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + _currentInstance.anchorManager.clearFor(placeholderInfo.id); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (self.isReattach || jpc._forceDetach || !jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc)) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + _currentInstance.repaint(existingJpcParams[1]); + } + jpc._forceDetach = null; + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElements(jpc.connector.getDisplayElements(), self.parent); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint({recalc:false}); + jpc.setHover(false, false); + jpc = null; + inPlaceCopy = null; + delete endpointsByElement[floatingEndpoint.elementId]; + floatingEndpoint.anchor = null; + floatingEndpoint = null; + _currentInstance.currentlyDragging = false; + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions, true); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas, forceInit, isTransient) { + if ((params.isTarget || forceInit) && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop'], + overEvent = jsPlumb.CurrentLibrary.dragEvents['over'], + outEvent = jsPlumb.CurrentLibrary.dragEvents['out'], + drop = function(originalEvent) { + var draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + elId = _getAttribute(draggable, "elId"), + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id], + idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + + // restore the original scope if necessary (issue 57) + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + + var _doContinue = true; + // the second check here is for the case that the user is dropping it back + // where it came from. + if (jpc.suspendedEndpoint && jpc.suspendedEndpoint.id != self.id) { + if (!jpc.isDetachAllowed(jpc) || !jpc.endpoints[idx].isDetachAllowed(jpc) || !jpc.suspendedEndpoint.isDetachAllowed(jpc) || !_currentInstance.checkCondition("beforeDetach", jpc)) + _doContinue = false; + } + + // these have to be set before testing for beforeDrop. + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + + // now check beforeDrop. this will be available only on Endpoints that are setup to + // have a beforeDrop condition (although, secretly, under the hood all Endpoints and + // the Connection have them, because they are on jsPlumbUIComponent. shhh!), because + // it only makes sense to have it on a target endpoint. + _doContinue = _doContinue && self.isDropAllowed(jpc.sourceId, jpc.targetId, jpc.scope); + + if (_doContinue) { + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + if (!jpc.suspendedEndpoint) { + //_addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + fireDetachEvent({ + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }, true); + } + + // finalise will inform the anchor manager and also add to + // connectionsByScope if necessary. + _finaliseConnection(jpc, null, originalEvent); + } + else { + // otherwise just put it back on the endpoint it was on before the drag. + if (jpc.suspendedEndpoint) { + // self.detachFrom(jpc); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.setHover(false); + jpc._forceDetach = true; + if (idx == 0) { + jpc.source = jpc.suspendedEndpoint.element; + jpc.sourceId = jpc.suspendedEndpoint.elementId; + } else { + jpc.target = jpc.suspendedEndpoint.element; + jpc.targetId = jpc.suspendedEndpoint.elementId;; + } + jpc.suspendedEndpoint.addConnection(jpc); + + jpc.endpoints[0].repaint(); + jpc.repaint(); + _currentInstance.repaint(jpc.source.elementId); + jpc._forceDetach = false; + } + } + + jpc.floatingAnchorIndex = null; + } + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], function() { + if (self.isTarget) { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + } + } + }); + dropOptions[outEvent] = _wrap(dropOptions[outEvent], function() { + if (self.isTarget) { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + } + } + }); + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions, true, isTransient); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas), true, !(params._transient || self.anchor.isFloating)); + + return self; + }; + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + j.init(); + return j; + }; + jsPlumb.util = { + convertStyle : function(s, ignoreAlpha) { + // TODO: jsPlumb should support a separate 'opacity' style member. + if ("transparent" === s) return s; + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + return o; + }, + gradient : function(p1, p2) { + p1 = p1.constructor == Array ? p1 : [p1.x, p1.y]; + p2 = p2.constructor == Array ? p2 : [p2.x, p2.y]; + return (p2[1] - p1[1]) / (p2[0] - p1[0]); + }, + normal : function(p1, p2) { + return -1 / jsPlumb.util.gradient(p1,p2); + }, + segment : function(p1, p2) { + p1 = p1.constructor == Array ? p1 : [p1.x, p1.y]; + p2 = p2.constructor == Array ? p2 : [p2.x, p2.y]; + if (p2[0] > p1[0]) { + return (p2[1] > p1[1]) ? 2 : 1; + } + else { + return (p2[1] > p1[1]) ? 3 : 4; + } + }, + segmentMultipliers : [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ], + inverseSegmentMultipliers : [null, [-1, -1], [-1, 1], [1, 1], [1, -1] ], + pointOnLine : function(fromPoint, toPoint, distance) { + var m = jsPlumb.util.gradient(fromPoint, toPoint), + s = jsPlumb.util.segment(fromPoint, toPoint), + segmentMultiplier = distance > 0 ? jsPlumb.util.segmentMultipliers[s] : jsPlumb.util.inverseSegmentMultipliers[s], + theta = Math.atan(m), + y = Math.abs(distance * Math.sin(theta)) * segmentMultiplier[1], + x = Math.abs(distance * Math.cos(theta)) * segmentMultiplier[0]; + return { x:fromPoint.x + x, y:fromPoint.y + y }; + }, + /** + * calculates a perpendicular to the line fromPoint->toPoint, that passes through toPoint and is 'length' long. + * @param fromPoint + * @param toPoint + * @param length + */ + perpendicularLineTo : function(fromPoint, toPoint, length) { + var m = jsPlumb.util.gradient(fromPoint, toPoint), + theta2 = Math.atan(-1 / m), + y = length / 2 * Math.sin(theta2), + x = length / 2 * Math.cos(theta2); + return [{x:toPoint.x + x, y:toPoint.y + y}, {x:toPoint.x - x, y:toPoint.y - y}]; + } + }; + + var _curryAnchor = function(x, y, ox, oy, type, fnInit) { + return function(params) { + params = params || {}; + //var a = jsPlumb.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance); + var a = params.jsPlumbInstance.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance); + a.type = type; + if (fnInit) fnInit(a, params); + return a; + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1, "TopCenter"); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1, "BottomCenter"); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0, "LeftMiddle"); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0, "RightMiddle"); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0, "Center"); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1, "TopRight"); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1, "BottomRight"); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1, "TopLeft"); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1, "BottomLeft"); + + // TODO test that this does not break with the current instance idea + jsPlumb.Defaults.DynamicAnchors = function(params) { + return params.jsPlumbInstance.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"], params.elementId, params.jsPlumbInstance); + }; + jsPlumb.Anchors["AutoDefault"] = function(params) { + var a = params.jsPlumbInstance.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors(params)); + a.type = "AutoDefault"; + return a; + }; + + jsPlumb.Anchors["Assign"] = _curryAnchor(0,0,0,0,"Assign", function(anchor, params) { + // find what to use as the "position finder". the user may have supplied a String which represents + // the id of a position finder in jsPlumb.AnchorPositionFinders, or the user may have supplied the + // position finder as a function. we find out what to use and then set it on the anchor. + var pf = params.position || "Fixed"; + anchor.positionFinder = pf.constructor == String ? params.jsPlumbInstance.AnchorPositionFinders[pf] : pf; + // always set the constructor params; the position finder might need them later (the Grid one does, + // for example) + anchor.constructorParams = params; + }); + + // Continuous anchor is just curried through to the 'get' method of the continuous anchor + // factory. + jsPlumb.Anchors["Continuous"] = function(params) { + return params.jsPlumbInstance.continuousAnchorFactory.get(params); + }; + + // these are the default anchor positions finders, which are used by the makeTarget function. supply + // a position finder argument to that function allows you to specify where the resulting anchor will + // be located + jsPlumb.AnchorPositionFinders = { + "Fixed": function(dp, ep, es, a) { + return [ (dp.left - ep.left) / es[0], (dp.top - ep.top) / es[1] ]; + }, + "Grid":function(dp, ep, es, a) { + var dx = dp.left - ep.left, dy = dp.top - ep.top, + gx = es[0] / (a.constructorParams.grid[0]), gy = es[1] / (a.constructorParams.grid[1]), + mx = Math.floor(dx / gx), my = Math.floor(dy / gy); + return [ ((mx * gx) + (gx / 2)) / es[0], ((my * gy) + (gy / 2)) / es[1] ]; + } + }; +})(); +/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the default Connectors, Endpoint and Overlay definitions. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +(function() { + + /** + * + * Helper class to consume unused mouse events by components that are DOM elements and + * are used by all of the different rendering modes. + * + */ + jsPlumb.DOMElementComponent = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + // when render mode is canvas, these functions may be called by the canvas mouse handler. + // this component is safe to pipe this stuff to /dev/null. + this.mousemove = + this.dblclick = + this.click = + this.mousedown = + this.mouseup = function(e) { }; + }; + + /** + * Class: Connectors.Straight + * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters. + */ + jsPlumb.Connectors.Straight = function() { + this.type = "Straight"; + var self = this, + currentPoints = null, + _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty, _segment, _length; + + /** + * Computes the new size and position of the canvas. + */ + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + _dx = _tx - _sx, _dy = _ty - _sy; + //_m = _dy / _dx, _m2 = -1 / _m; + _m = jsPlumb.util.gradient({x:_sx, y:_sy}, {x:_tx, y:_ty}), _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + //_segment = jsPlumb.util.segment({x:_sx, y:_sy}, {x:_tx, y:_ty}); + _length = Math.sqrt((_dx * _dx) + (_dy * _dy)); + + return currentPoints; + }; + + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + if (location == 0) + return { x:_sx, y:_sy }; + else if (location == 1) + return { x:_tx, y:_ty }; + else + return jsPlumb.util.pointOnLine({x:_sx, y:_sy}, {x:_tx, y:_ty}, location * _length); + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { + return _m; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + * this hands off to jsPlumb.util to do the maths, supplying our gradient and position and whether or + * not the coords should be flipped + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + return jsPlumb.util.pointOnLine(p, {x:_tx,y:_ty}, distance); + }; + }; + + + /** + * Class:Connectors.Bezier + * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's + * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below. + */ + /** + * Function:Constructor + * + * Parameters: + * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line. + * Optional; defaults to 150. + * + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + params = params || {}; + this.majorAnchor = params.curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + this.type = "Bezier"; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(sourceEndpoint), + too = targetAnchor.getOrientation(targetEndpoint), + perpendicular = soo[0] != too[0] || soo[1] == too[1], + p = [], + ma = self.majorAnchor, mi = self.minorAnchor; + + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, sourceEndpoint, targetEndpoint, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx), minx2 = Math.min(_CP[0], _CP2[0]), minx = Math.min(minx1,minx2), + maxx1 = Math.max(_sx,_tx), maxx2 = Math.max(_CP[0], _CP2[0]), maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty), miny2 = Math.min(_CP[1], _CP2[1]), miny = Math.min(miny1,miny2), + maxy1 = Math.max(_sy,_ty), maxy2 = Math.max(_CP[1], _CP2[1]), maxy = Math.max(maxy1,maxy2); + + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + }; + + + /** + * Class: Connectors.Flowchart + * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments. + */ + /** + * Function: Constructor + * + * Parameters: + * stub - minimum length for the stub at each end of the connector. defaults to 30 pixels. + */ + jsPlumb.Connectors.Flowchart = function(params) { + this.type = "Flowchart"; + params = params || {}; + var self = this, + minStubLength = params.stub || params.minStubLength /* bwds compat. */ || 30, + segments = [], + totalLength = 0, + segmentProportions = [], + segmentProportionalLengths = [], + points = [], + swapX, + swapY, + /** + * recalculates the points at which the segments begin and end, proportional to the total length travelled + * by all the segments that constitute the connector. we use this to help with pointOnPath calculations. + */ + updateSegmentProportions = function(startX, startY, endX, endY) { + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segments[i][5] / totalLength; + segmentProportions[i] = [curLoc, (curLoc += (segments[i][5] / totalLength)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0], + ly = segments.length == 0 ? sy : segments[segments.length - 1][1], + m = x == lx ? Infinity : 0, + l = Math.abs(x == lx ? y - ly : x - lx); + segments.push([x, y, lx, ly, m, l]); + totalLength += l; + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment + * that contains the point which is 'location' distance along the entire path, where + * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths + * are made up of a list of segments, each of which contributes some fraction to + * the total length. + */ + findSegmentForLocation = function(location) { + var idx = segmentProportions.length - 1, inSegmentProportion = 0; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, + sourceAnchor, targetAnchor, lineWidth, minWidth, sourceInfo, targetInfo) { + + segments = []; + totalLength = 0; + segmentProportionalLengths = []; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + offx = (lw / 2) + (minStubLength * 2), + offy = (lw / 2) + (minStubLength * 2), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(sourceEndpoint), + to = targetAnchor.orientation || targetAnchor.getOrientation(targetEndpoint), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx, + h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy; + + // if either anchor does not have an orientation set, we derive one from their relative + // positions. we fix the axis to be the one in which the two elements are further apart, and + // point each anchor at the other element. this is also used when dragging a new connection. + if (so[0] == 0 && so[1] == 0 || to[0] == 0 && to[1] == 0) { + var index = w > h ? 0 : 1, oIndex = [1,0][index]; + so = []; to = []; + so[index] = sourcePos[index] > targetPos[index] ? -1 : 1; + to[index] = sourcePos[index] > targetPos[index] ? 1 : -1; + so[oIndex] = 0; + to[oIndex] = 0; + } + + if (w < minWidth) { + offx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + offy += (minWidth - h) / 2; + h = minWidth; + } + + var sx = swapX ? w-offx : offx, + sy = swapY ? h-offy : offy, + tx = swapX ? offx : w-offx , + ty = swapY ? offy : h-offy, + startStubX = sx + (so[0] * minStubLength), + startStubY = sy + (so[1] * minStubLength), + endStubX = tx + (to[0] * minStubLength), + endStubY = ty + (to[1] * minStubLength), + isXGreaterThanStubTimes2 = Math.abs(sx - tx) > 2 * minStubLength, + isYGreaterThanStubTimes2 = Math.abs(sy - ty) > 2 * minStubLength, + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2), + oProduct = ((so[0] * to[0]) + (so[1] * to[1])), + opposite = oProduct == -1, + perpendicular = oProduct == 0, + orthogonal = oProduct == 1; + + x -= offx; y -= offy; + points = [x, y, w, h, sx, sy, tx, ty]; + var extraPoints = []; + + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + var sourceAxis = so[0] == 0 ? "y" : "x", + anchorOrientation = opposite ? "opposite" : orthogonal ? "orthogonal" : "perpendicular", + segment = jsPlumb.util.segment([sx, sy], [tx, ty]), + flipSourceSegments = so[sourceAxis == "x" ? 0 : 1] == -1, + flipSegments = { + "x":[null, 4, 3, 2, 1], + "y":[null, 2, 1, 4, 3] + } + + if (flipSourceSegments) + segment = flipSegments[sourceAxis][segment]; + + var findClearedLine = function(start, mult, anchorPos, dimension) { + return start + (mult * (( 1 - anchorPos) * dimension) + minStubLength); + //mx = so[0] == 0 ? startStubX + ((1 - sourceAnchor.x) * sourceInfo.width) + minStubLength : startStubX, + }, + + lineCalculators = { + oppositex : function() { + if (sourceEndpoint.elementId == targetEndpoint.elementId) { + var _y = startStubY + ((1 - sourceAnchor.y) * sourceInfo.height) + minStubLength; + return [ [ startStubX, _y ], [ endStubX, _y ]]; + } + else if (isXGreaterThanStubTimes2 && (segment == 1 || segment == 2)) { + return [[ midx, sy ], [ midx, ty ]]; + } + else { + return [[ startStubX, midy ], [endStubX, midy ]]; + } + }, + orthogonalx : function() { + if (segment == 1 || segment == 2) { + return [ [ endStubX, startStubY ]]; + } + else { + return [ [ startStubX, endStubY ]]; + } + }, + perpendicularx : function() { + var my = (ty + sy) / 2; + if ((segment == 1 && to[1] == 1) || (segment == 2 && to[1] == -1)) { + if (Math.abs(tx - sx) > minStubLength) + return [ [endStubX, startStubY ]]; + else + return [ [startStubX, startStubY ], [ startStubX, my ], [ endStubX, my ]]; + } + else if ((segment == 3 && to[1] == -1) || (segment == 4 && to[1] == 1)) { + return [ [ startStubX, my ], [ endStubX, my ]]; + } + else if ((segment == 3 && to[1] == 1) || (segment == 4 && to[1] == -1)) { + return [ [ startStubX, endStubY ]]; + } + else if ((segment == 1 && to[1] == -1) || (segment == 2 && to[1] == 1)) { + if (Math.abs(tx - sx) > minStubLength) + return [ [ midx, startStubY ], [ midx, endStubY ]]; + else + return [ [ startStubX, endStubY ]]; + } + }, + oppositey : function() { + if (sourceEndpoint.elementId == targetEndpoint.elementId) { + var _x = startStubX + ((1 - sourceAnchor.x) * sourceInfo.width) + minStubLength; + return [ [ _x, startStubY ], [ _x, endStubY ]]; + } + else if (isYGreaterThanStubTimes2 && (segment == 2 || segment == 3)) { + return [[ sx, midy ], [ tx, midy ]]; + } + else { + return [[ midx, startStubY ], [midx, endStubY ]]; + } + }, + orthogonaly : function() { + if (segment == 2 || segment == 3) { + return [ [ startStubX, endStubY ]]; + } + else { + return [ [ endStubX, startStubY ]]; + } + }, + perpendiculary : function() { + var mx = (tx + sx) / 2; + if ((segment == 2 && to[0] == -1) || (segment == 3 && to[0] == 1)) { + if (Math.abs(tx - sx) > minStubLength) + return [ [startStubX, endStubY ]]; + else + return [ [startStubX, midy ], [ endStubX, midy ]]; + } + else if ((segment == 1 && to[0] == -1) || (segment == 4 && to[0] == 1)) { + var mx = (tx + sx) / 2; + return [ [ mx, startStubY ], [ mx, endStubY ]]; + } + else if ((segment == 1 && to[0] == 1) || (segment == 4 && to[0] == -1)) { + return [ [ endStubX, startStubY ]]; + } + else if ((segment == 2 && to[0] == 1) || (segment == 3 && to[0] == -1)) { + if (Math.abs(ty - sy) > minStubLength) + return [ [ startStubX, midy ], [ endStubX, midy ]]; + else + return [ [ endStubX, startStubY ]]; + } + } + }; + + + + var pointFinder = lineCalculators[anchorOrientation + sourceAxis]; + var p = pointFinder(); + if (p) { + for (var i = 0; i < p.length; i++) { + addSegment(p[i][0], p[i][1], sx, sy, tx, ty); + } + } + + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentProportions(sx, sy, tx, ty); + + return points; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location) { + return self.pointAlongPathFrom(location, 0); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location) { + return segments[findSegmentForLocation(location)["index"]][4]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance) { + var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segments[s.index][5], m = segments[s.index][4]; + var e = { + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + return e; + }; + }; + + // ********************************* END OF CONNECTOR TYPES ******************************************************************* + + // ********************************* ENDPOINT TYPES ******************************************************************* + + /** + * Class: Endpoints.Dot + * A round endpoint, with default radius 10 pixels. + */ + + /** + * Function: Constructor + * + * Parameters: + * + * radius - radius of the endpoint. defaults to 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + this.type = "Dot"; + var self = this; + params = params || {}; + this.radius = params.radius || 10; + this.defaultOffset = 0.5 * this.radius; + this.defaultInnerRadius = this.radius / 3; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var r = endpointStyle.radius || self.radius, + x = anchorPoint[0] - r, + y = anchorPoint[1] - r; + return [ x, y, r * 2, r * 2, r ]; + }; + }; + + /** + * Class: Endpoints.Rectangle + * A Rectangular Endpoint, with default size 20x20. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the endpoint. defaults to 20 pixels. + * height - height of the endpoint. defaults to 20 pixels. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + this.type = "Rectangle"; + var self = this; + params = params || {}; + this.width = params.width || 20; + this.height = params.height || 20; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height]; + }; + }; + + + var DOMElementEndpoint = function(params) { + jsPlumb.DOMElementComponent.apply(this, arguments); + var self = this; + + var displayElements = [ ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + }; + /** + * Class: Endpoints.Image + * Draws an image as the Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * src - location of the image to use. + */ + jsPlumb.Endpoints.Image = function(params) { + + this.type = "Image"; + DOMElementEndpoint.apply(this, arguments); + + var self = this, + initialized = false, + widthToUse = params.width, + heightToUse = params.height, + _onload = null, + _endpoint = params.endpoint; + + this.img = new Image(); + self.ready = false; + + this.img.onload = function() { + self.ready = true; + widthToUse = widthToUse || self.img.width; + heightToUse = heightToUse || self.img.height; + if (_onload) { + _onload(self); + } + }; + + _endpoint.setImage = function(img, onload) { + var s = img.constructor == String ? img : img.src; + _onload = onload; + self.img.src = img; + }; + + _endpoint.setImage(params.src || params.url, params.onload); + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + self.anchorPoint = anchorPoint; + if (self.ready) return [anchorPoint[0] - widthToUse / 2, anchorPoint[1] - heightToUse / 2, + widthToUse, heightToUse]; + else return [0,0,0,0]; + }; + + self.canvas = document.createElement("img"), initialized = false; + self.canvas.style["margin"] = 0; + self.canvas.style["padding"] = 0; + self.canvas.style["outline"] = 0; + self.canvas.style["position"] = "absolute"; + var clazz = params.cssClass ? " " + params.cssClass : ""; + self.canvas.className = jsPlumb.endpointClass + clazz; + if (widthToUse) self.canvas.setAttribute("width", widthToUse); + if (heightToUse) self.canvas.setAttribute("height", heightToUse); + jsPlumb.appendElement(self.canvas, params.parent); + self.attachListeners(self.canvas, self); + + var actuallyPaint = function(d, style, anchor) { + if (!initialized) { + self.canvas.setAttribute("src", self.img.src); + initialized = true; + } + var x = self.anchorPoint[0] - (widthToUse / 2), + y = self.anchorPoint[1] - (heightToUse / 2); + jsPlumb.sizeCanvas(self.canvas, x, y, widthToUse, heightToUse); + }; + + this.paint = function(d, style, anchor) { + if (self.ready) { + actuallyPaint(d, style, anchor); + } + else { + window.setTimeout(function() { + self.paint(d, style, anchor); + }, 200); + } + }; + }; + + /** + * Class: Endpoints.Blank + * An Endpoint that paints nothing (visible) on the screen. Supports cssClass and hoverClass parameters like all Endpoints. + */ + jsPlumb.Endpoints.Blank = function(params) { + var self = this; + this.type = "Blank"; + DOMElementEndpoint.apply(this, arguments); + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + return [anchorPoint[0], anchorPoint[1],10,0]; + }; + + self.canvas = document.createElement("div"); + self.canvas.style.display = "block"; + self.canvas.style.width = "1px"; + self.canvas.style.height = "1px"; + self.canvas.style.background = "transparent"; + self.canvas.style.position = "absolute"; + self.canvas.className = self._jsPlumb.endpointClass; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + }; + }; + + /** + * Class: Endpoints.Triangle + * A triangular Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the triangle's base. defaults to 55 pixels. + * height - height of the triangle from base to apex. defaults to 55 pixels. + */ + jsPlumb.Endpoints.Triangle = function(params) { + this.type = "Triangle"; + params = params || { }; + params.width = params.width || 55; + params.height = params.height || 55; + this.width = params.width; + this.height = params.height; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height ]; + }; + }; +// ********************************* END OF ENDPOINT TYPES ******************************************************************* + + +// ********************************* OVERLAY DEFINITIONS *********************************************************************** + + var AbstractOverlay = function(params) { + var visible = true, self = this; + this.isAppendedAtTopLevel = true; + this.component = params.component; + this.loc = params.location == null ? 0.5 : params.location; + this.endpointLoc = params.endpointLocation == null ? [ 0.5, 0.5] : params.endpointLocation; + this.setVisible = function(val) { + visible = val; + self.component.repaint(); + }; + this.isVisible = function() { return visible; }; + this.hide = function() { self.setVisible(false); }; + this.show = function() { self.setVisible(true); }; + + this.incrementLocation = function(amount) { + self.loc += amount; + self.component.repaint(); + }; + this.setLocation = function(l) { + self.loc = l; + self.component.repaint(); + }; + this.getLocation = function() { + return self.loc; + }; + }; + + + /** + * Class: Overlays.Arrow + * + * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length + * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction + * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line + * across the tail. + */ + /** + * Function: Constructor + * + * Parameters: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + this.type = "Arrow"; + AbstractOverlay.apply(this, arguments); + this.isAppendedAtTopLevel = false; + params = params || {}; + var self = this; + + this.length = params.length || 20; + this.width = params.width || 20; + this.id = params.id; + var direction = (params.direction || 1) < 0 ? -1 : 1, + paintStyle = params.paintStyle || { lineWidth:1 }, + // how far along the arrow the lines folding back in come to. default is 62.3%. + foldback = params.foldback || 0.623; + + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.cleanup = function() { }; // nothing to clean up for Arrows + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var hxy, mid, txy, tail, cxy; + if (connector.pointAlongPathFrom) { + + if (self.loc == 1) { + hxy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, -1); + txy = jsPlumb.util.pointOnLine(hxy, mid, self.length); + } + else if (self.loc == 0) { + txy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, 1); + hxy = jsPlumb.util.pointOnLine(txy, mid, self.length); + } + else { + hxy = connector.pointAlongPathFrom(self.loc, direction * self.length / 2), + mid = connector.pointOnPath(self.loc), + txy = jsPlumb.util.pointOnLine(hxy, mid, self.length); + } + + tail = jsPlumb.util.perpendicularLineTo(hxy, txy, self.width); + cxy = jsPlumb.util.pointOnLine(hxy, txy, foldback * self.length); + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x), + maxx = Math.max(hxy.x, tail[0].x, tail[1].x), + miny = Math.min(hxy.y, tail[0].y, tail[1].y), + maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + var d = { hxy:hxy, tail:tail, cxy:cxy }, + strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle, + fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle, + lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth; + + self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions); + + return [ minx, maxx, miny, maxy]; + } + else return [0,0,0,0]; + }; + }; + + /** + * Class: Overlays.PlainArrow + * + * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "PlainArrow"; + }; + + /** + * Class: Overlays.Diamond + * + * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40, + p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "Diamond"; + }; + + + + /** + * Class: Overlays.Label + * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb + * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter + * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it + * puts on the Label's 'style' attribute, so the end result is the same. + */ + /** + * Function: Constructor + * + * Parameters: + * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes + * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle. + * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * + */ + jsPlumb.Overlays.Label = function(params) { + this.type = "Label"; + jsPlumb.DOMElementComponent.apply(this, arguments); + AbstractOverlay.apply(this, arguments); + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.id = params.id; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var label = params.label || "", + self = this, + initialised = false, + div = document.createElement("div"), + labelText = null; + div.style["position"] = "absolute"; + + var clazz = params["_jsPlumb"].overlayClass + " " + + (self.labelStyle.cssClass ? self.labelStyle.cssClass : + params.cssClass ? params.cssClass : ""); + + div.className = clazz; + + jsPlumb.appendElement(div, params.component.parent); + jsPlumb.getId(div); + self.attachListeners(div, self); + self.canvas = div; + + //override setVisible + var osv = self.setVisible; + self.setVisible = function(state) { + osv(state); // call superclass + div.style.display = state ? "block" : "none"; + }; + + this.getElement = function() { + return div; + }; + + this.cleanup = function() { + if (div != null) jsPlumb.CurrentLibrary.removeElement(div); + }; + + /* + * Function: setLabel + * sets the label's, um, label. you would think i'd call this function + * 'setText', but you can pass either a Function or a String to this, so + * it makes more sense as 'setLabel'. + */ + this.setLabel = function(l) { + label = l; + labelText = null; + self.component.repaint(); + }; + + this.getLabel = function() { + return label; + }; + + this.paint = function(component, d, componentDimensions) { + if (!initialised) { + component.appendDisplayElement(div); + self.attachListeners(div, component); + initialised = true; + } + div.style.left = (componentDimensions[0] + d.minx) + "px"; + div.style.top = (componentDimensions[1] + d.miny) + "px"; + }; + + this.getTextDimensions = function() { + if (typeof label == "function") { + var lt = label(self); + div.innerHTML = lt.replace(/\r\n/g, "
"); + } + else { + if (labelText == null) { + labelText = label; + div.innerHTML = labelText.replace(/\r\n/g, "
"); + } + } + var de = jsPlumb.CurrentLibrary.getElementObject(div), + s = jsPlumb.CurrentLibrary.getSize(de); + return {width:s[0], height:s[1]}; + }; + + this.computeMaxSize = function(connector) { + var td = self.getTextDimensions(connector); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + + this.draw = function(component, currentConnectionPaintStyle, componentDimensions) { + var td = self.getTextDimensions(component); + if (td.width != null) { + var cxy = {x:0,y:0}; + if (component.pointOnPath) + cxy = component.pointOnPath(self.loc); // a connection + else { + var locToUse = self.loc.constructor == Array ? self.loc : self.endpointLoc; + cxy = { x:locToUse[0] * componentDimensions[2], + y:locToUse[1] * componentDimensions[3] }; + } + + minx = cxy.x - (td.width / 2), + miny = cxy.y - (td.height / 2); + + self.paint(component, { + minx:minx, + miny:miny, + td:td, + cxy:cxy + }, componentDimensions); + + return [minx, minx+td.width, miny, miny+td.height]; + } + else return [0,0,0,0]; + }; + + this.reattachListeners = function(connector) { + if (div) { + self.reattachListenersForElement(div, self, connector); + } + }; + }; + + // this is really just a test overlay, so its undocumented and doesnt take any parameters. but i was loth to delete it. + jsPlumb.Overlays.GuideLines = function() { + var self = this; + self.length = 50; + self.lineWidth = 5; + this.type = "GuideLines"; + AbstractOverlay.apply(this, arguments); + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var head = connector.pointAlongPathFrom(self.loc, self.length / 2), + mid = connector.pointOnPath(self.loc), + tail = jsPlumb.util.pointOnLine(head, mid, self.length), + tailLine = jsPlumb.util.perpendicularLineTo(head, tail, 40), + headLine = jsPlumb.util.perpendicularLineTo(tail, head, 20); + + self.paint(connector, [head, tail, tailLine, headLine], self.lineWidth, "red", null, connectorDimensions); + + return [Math.min(head.x, tail.x), Math.min(head.y, tail.y), Math.max(head.x, tail.x), Math.max(head.y,tail.y)]; + }; + + this.computeMaxSize = function() { return 50; }; + + this.cleanup = function() { }; // nothing to clean up for GuideLines + }; + + // ********************************* END OF OVERLAY DEFINITIONS *********************************************************************** + + // ********************************* OVERLAY CANVAS RENDERERS*********************************************************************** + + // ********************************* END OF OVERLAY CANVAS RENDERERS *********************************************************************** +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the state machine connectors. + * + * Thanks to Brainstorm Mobile Solutions for supporting the development of these. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + var Line = function(x1, y1, x2, y2) { + + this.m = (y2 - y1) / (x2 - x1); + this.b = -1 * ((this.m * x1) - y1); + + this.rectIntersect = function(x,y,w,h) { + var results = []; + + // try top face + // the equation of the top face is y = (0 * x) + b; y = b. + var xInt = (y - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try right face + var yInt = (this.m * (x + w)) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + // bottom face + var xInt = ((y + h) - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try left face + var yInt = (this.m * x) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + if (results.length == 2) { + var midx = (results[0][0] + results[1][0]) / 2, midy = (results[0][1] + results[1][1]) / 2; + results.push([ midx,midy ]); + // now calculate the segment inside the rectangle where the midpoint lies. + var xseg = midx <= x + (w / 2) ? -1 : 1, + yseg = midy <= y + (h / 2) ? -1 : 1; + results.push([xseg, yseg]); + return results; + } + + return null; + + }; + }, + _segment = function(x1, y1, x2, y2) { + if (x1 <= x2 && y2 <= y1) return 1; + else if (x1 <= x2 && y1 <= y2) return 2; + else if (x2 <= x1 && y2 >= y1) return 3; + return 4; + }, + + // the control point we will use depends on the faces to which each end of the connection is assigned, specifically whether or not the + // two faces are parallel or perpendicular. if they are parallel then the control point lies on the midpoint of the axis in which they + // are parellel and varies only in the other axis; this variation is proportional to the distance that the anchor points lie from the + // center of that face. if the two faces are perpendicular then the control point is at some distance from both the midpoints; the amount and + // direction are dependent on the orientation of the two elements. 'seg', passed in to this method, tells you which segment the target element + // lies in with respect to the source: 1 is top right, 2 is bottom right, 3 is bottom left, 4 is top left. + // + // sourcePos and targetPos are arrays of info about where on the source and target each anchor is located. their contents are: + // + // 0 - absolute x + // 1 - absolute y + // 2 - proportional x in element (0 is left edge, 1 is right edge) + // 3 - proportional y in element (0 is top edge, 1 is bottom edge) + // + _findControlPoint = function(midx, midy, segment, sourceEdge, targetEdge, dx, dy, distance, proximityLimit) { + + // TODO (maybe) + // - if anchor pos is 0.5, make the control point take into account the relative position of the elements. + if (distance <= proximityLimit) return [midx, midy]; + + if (segment == 1) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 2) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 3) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 4) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + }; + + /* + Function: StateMachine constructor + + Allowed parameters: + curviness - measure of how "curvy" the connectors will be. this is translated as the distance that the + Bezier curve's control point is from the midpoint of the straight line connecting the two + endpoints, and does not mean that the connector is this wide. The Bezier curve never reaches + its control points; they act as gravitational masses. defaults to 10. + margin - distance from element to start and end connectors, in pixels. defaults to 5. + proximityLimit - sets the distance beneath which the elements are consider too close together to bother with fancy + curves. by default this is 80 pixels. + loopbackRadius - the radius of a loopback connector. optional; defaults to 25. + */ + jsPlumb.Connectors.StateMachine = function(params) { + var self = this, + currentPoints = null, + _sx, _sy, _tx, _ty, _controlPoint = [], + curviness = params.curviness || 10, + margin = params.margin || 5, + proximityLimit = params.proximityLimit || 80, + clockwise = params.orientation && params.orientation == "clockwise", + loopbackRadius = params.loopbackRadius || 25, + isLoopback = false; + + this.type = "StateMachine"; + params = params || {}; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *= 1.9; + //ensure at least one pixel width + lineWidth = lineWidth || 1; + var x = Math.min(sourcePos[0], targetPos[0]) - xo, + y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (sourceEndpoint.elementId != targetEndpoint.elementId) { + + isLoopback = false; + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + + // now adjust for the margin + if (sourcePos[2] == 0) _sx -= margin; + if (sourcePos[2] == 1) _sx += margin; + if (sourcePos[3] == 0) _sy -= margin; + if (sourcePos[3] == 1) _sy += margin; + if (targetPos[2] == 0) _tx -= margin; + if (targetPos[2] == 1) _tx += margin; + if (targetPos[3] == 0) _ty -= margin; + if (targetPos[3] == 1) _ty += margin; + + // + // these connectors are quadratic bezier curves, having a single control point. if both anchors + // are located at 0.5 on their respective faces, the control point is set to the midpoint and you + // get a straight line. this is also the case if the two anchors are within 'proximityLimit', since + // it seems to make good aesthetic sense to do that. outside of that, the control point is positioned + // at 'curviness' pixels away along the normal to the straight line connecting the two anchors. + // + // there may be two improvements to this. firstly, we might actually support the notion of avoiding nodes + // in the UI, or at least making a good effort at doing so. if a connection would pass underneath some node, + // for example, we might increase the distance the control point is away from the midpoint in a bid to + // steer it around that node. this will work within limits, but i think those limits would also be the likely + // limits for, once again, aesthetic good sense in the layout of a chart using these connectors. + // + // the second possible change is actually two possible changes: firstly, it is possible we should gradually + // decrease the 'curviness' as the distance between the anchors decreases; start tailing it off to 0 at some + // point (which should be configurable). secondly, we might slightly increase the 'curviness' for connectors + // with respect to how far their anchor is from the center of its respective face. this could either look cool, + // or stupid, and may indeed work only in a way that is so subtle as to have been a waste of time. + // + + var _midx = (_sx + _tx) / 2, _midy = (_sy + _ty) / 2, + m2 = (-1 * _midx) / _midy, theta2 = Math.atan(m2), + dy = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.sin(theta2)), + dx = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.cos(theta2)), + segment = _segment(_sx, _sy, _tx, _ty), + distance = Math.sqrt(Math.pow(_tx - _sx, 2) + Math.pow(_ty - _sy, 2)); + + // calculate the control point. this code will be where we'll put in a rudimentary element avoidance scheme; it + // will work by extending the control point to force the curve to be, um, curvier. + _controlPoint = _findControlPoint(_midx, + _midy, + segment, + sourcePos, + targetPos, + curviness, curviness, + distance, + proximityLimit); + + + var requiredWidth = Math.max(Math.abs(_controlPoint[0] - _sx) * 3, Math.abs(_controlPoint[0] - _tx) * 3, Math.abs(_tx-_sx), 2 * lineWidth, minWidth), + requiredHeight = Math.max(Math.abs(_controlPoint[1] - _sy) * 3, Math.abs(_controlPoint[1] - _ty) * 3, Math.abs(_ty-_sy), 2 * lineWidth, minWidth); + + if (w < requiredWidth) { + var dw = requiredWidth - w; + x -= (dw / 2); + _sx += (dw / 2); + _tx += (dw / 2); + w = requiredWidth; + _controlPoint[0] += (dw / 2); + } + + if (h < requiredHeight) { + var dh = requiredHeight - h; + y -= (dh / 2); + _sy += (dh / 2); + _ty += (dh / 2); + h = requiredHeight; + _controlPoint[1] += (dh / 2); + } + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty, _controlPoint[0], _controlPoint[1] ]; + } + else { + isLoopback = true; + // a loopback connector. draw an arc from one anchor to the other. + // i guess we'll do this the same way as the others. just the control point will be a fair distance away. + var x1 = sourcePos[0], x2 = sourcePos[0], y1 = sourcePos[1] - margin, y2 = sourcePos[1] - margin, + cx = x1, cy = y1 - loopbackRadius; + + // canvas sizing stuff, to ensure the whole painted area is visible. + w = ((2 * lineWidth) + (4 * loopbackRadius)), h = ((2 * lineWidth) + (4 * loopbackRadius)); + x = cx - loopbackRadius - lineWidth - loopbackRadius, y = cy - loopbackRadius - lineWidth - loopbackRadius; + currentPoints = [ x, y, w, h, cx-x, cy-y, loopbackRadius, clockwise, x1-x, y1-y, x2-x, y2-y]; + } + + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_tx, y:_ty }, + { x:_controlPoint[0], y:_controlPoint[1] }, + { x:_controlPoint[0] + 1, y:_controlPoint[1] + 1}, + { x:_sx, y:_sy } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + if (isLoopback) { + + if (location > 0 && location < 1) location = 1- location; + +// current points are [ x, y, width, height, center x, center y, radius, clockwise, startx, starty, endx, endy ] + // so the path length is the circumference of the circle + //var len = 2 * Math.PI * currentPoints[6], + // map 'location' to an angle. 0 is PI/2 when the connector is on the top face; if we + // support other faces it will have to be calculated for each one. 1 is also PI/2. + // 0.5 is -PI/2. + var startAngle = (location * 2 * Math.PI) + (Math.PI / 2), + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + + } + else return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + if (isLoopback) + return Math.atan(location * 2 * Math.PI); + else + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + if (isLoopback) { + + if (location > 0 && location < 1) location = 1- location; + + var circumference = 2 * Math.PI * currentPoints[6], + arcSpan = distance / circumference * 2 * Math.PI, + startAngle = (location * 2 * Math.PI) - arcSpan + (Math.PI / 2), + + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + } + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + }; + + /* + * Canvas state machine renderer. + */ + jsPlumb.Connectors.canvas.StateMachine = function(params) { + params = params || {}; + var self = this, drawGuideline = params.drawGuideline || true, avoidSelector = params.avoidSelector; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.CanvasConnector.apply(this, arguments); + + + this._paint = function(dimensions) { + + if (dimensions.length == 10) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.quadraticCurveTo(dimensions[8], dimensions[9], dimensions[6], dimensions[7]); + self.ctx.stroke(); + + /*/ draw the guideline + if (drawGuideline) { + self.ctx.save(); + self.ctx.beginPath(); + self.ctx.strokeStyle = "silver"; + self.ctx.lineWidth = 1; + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + self.ctx.restore(); + } + //*/ + } + else { + // a loopback connector + self.ctx.save(); + self.ctx.beginPath(); + var startAngle = 0, // Starting point on circle + endAngle = 2 * Math.PI, // End point on circle + clockwise = dimensions[7]; // clockwise or anticlockwise + self.ctx.arc(dimensions[4],dimensions[5],dimensions[6],0, endAngle, clockwise); + self.ctx.stroke(); + self.ctx.closePath(); + self.ctx.restore(); + } + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + /* + * SVG State Machine renderer + */ + jsPlumb.Connectors.svg.StateMachine = function() { + var self = this; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.SvgConnector.apply(this, arguments); + this.getPath = function(d) { + + if (d.length == 10) + return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[8] + " " + d[9] + " " + d[6] + " " + d[7]; + else { + // loopback + return "M" + (d[8] + 4) + " " + d[9] + " A " + d[6] + " " + d[6] + " 0 1,0 " + (d[8]-4) + " " + d[9]; + } + }; + }; + + /* + * VML state machine renderer + */ + jsPlumb.Connectors.vml.StateMachine = function() { + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.VmlConnector.apply(this, arguments); + var _conv = jsPlumb.vml.convertValue; + this.getPath = function(d) { + if (d.length == 10) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + } + else { + // loopback + var left = _conv(d[8] - d[6]), + top = _conv(d[9] - (2 * d[6])), + right = left + _conv(2 * d[6]), + bottom = top + _conv(2 * d[6]), + posString = left + "," + top + "," + right + "," + bottom; + + var o = "ar " + posString + "," + _conv(d[8]) + "," + + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + " e"; + + return o; + } + }; + }; + +})(); + +/* + // now for a rudimentary avoidance scheme. TODO: how to set this in a cross-library way? + // if (avoidSelector) { + // var testLine = new Line(sourcePos[0] + _sx,sourcePos[1] + _sy,sourcePos[0] + _tx,sourcePos[1] + _ty); + // var sel = jsPlumb.getSelector(avoidSelector); + // for (var i = 0; i < sel.length; i++) { + // var id = jsPlumb.getId(sel[i]); + // if (id != sourceEndpoint.elementId && id != targetEndpoint.elementId) { + // o = jsPlumb.getOffset(id), s = jsPlumb.getSize(id); +// +// if (o && s) { +// var collision = testLine.rectIntersect(o.left,o.top,s[0],s[1]); +// if (collision) { + // set the control point to be a certain distance from the midpoint of the two points that + // the line crosses on the rectangle. + // TODO where will this 75 number come from? + // _controlX = collision[2][0] + (75 * collision[3][0]); + // / _controlY = collision[2][1] + (75 * collision[3][1]); +// } +// } + // } + // } + //} + *//* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the VML renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + // http://ajaxian.com/archives/the-vml-changes-in-ie-8 + // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/ + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + + var vmlAttributeMap = { + "stroke-linejoin":"joinstyle", + "joinstyle":"joinstyle", + "endcap":"endcap", + "miterlimit":"miterlimit" + }; + + if (document.createStyleSheet) { + + // this is the style rule for IE7/6: it uses a CSS class, tidy. + document.createStyleSheet().addRule(".jsplumb_vml", "behavior:url(#default#VML);position:absolute;"); + + // these are for VML in IE8. you have to explicitly call out which elements + // you're going to expect to support VML! + // + document.createStyleSheet().addRule("jsplumb\\:textbox", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:oval", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:rect", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:stroke", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:shape", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:group", "behavior:url(#default#VML);position:absolute;"); + + // in this page it is also mentioned that IE requires the extra arg to the namespace + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either. + // var iev = document.documentMode; + //if (!iev || iev < 8) + document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); + //else + // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML"); + } + + jsPlumb.vml = {}; + + var scale = 1000, + + _groupMap = {}, + _getGroup = function(container, connectorClass) { + var id = jsPlumb.getId(container), + g = _groupMap[id]; + if(!g) { + g = _node("group", [0,0,scale, scale], {"class":connectorClass}); + //g.style.position=absolute; + //g["coordsize"] = "1000,1000"; + g.style.backgroundColor="red"; + _groupMap[id] = g; + jsPlumb.appendElement(g, container); + //document.body.appendChild(g); + } + return g; + }, + _atts = function(o, atts) { + for (var i in atts) { + // IE8 fix: setattribute does not work after an element has been added to the dom! + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + //o.setAttribute(i, atts[i]); + + o[i] = atts[i]; + } + }, + _node = function(name, d, atts) { + atts = atts || {}; + var o = document.createElement("jsplumb:" + name); + o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml"; + _pos(o, d); + _atts(o, atts); + return o; + }, + _pos = function(o,d) { + o.style.left = d[0] + "px"; + o.style.top = d[1] + "px"; + o.style.width= d[2] + "px"; + o.style.height= d[3] + "px"; + o.style.position = "absolute"; + }, + _conv = jsPlumb.vml.convertValue = function(v) { + return Math.floor(v * scale); + }, + // tests if the given style is "transparent" and then sets the appropriate opacity node to 0 if so, + // or 1 if not. TODO in the future, support variable opacity. + _maybeSetOpacity = function(styleToWrite, styleToCheck, type, component) { + if ("transparent" === styleToCheck) + component.setOpacity(type, "0.0"); + else + component.setOpacity(type, "1.0"); + }, + _applyStyles = function(node, style, component) { + var styleToWrite = {}; + if (style.strokeStyle) { + styleToWrite["stroked"] = "true"; + var strokeColor = jsPlumb.util.convertStyle(style.strokeStyle, true); + styleToWrite["strokecolor"] = strokeColor; + _maybeSetOpacity(styleToWrite, strokeColor, "stroke", component); + styleToWrite["strokeweight"] = style.lineWidth + "px"; + } + else styleToWrite["stroked"] = "false"; + + if (style.fillStyle) { + styleToWrite["filled"] = "true"; + var fillColor = jsPlumb.util.convertStyle(style.fillStyle, true); + styleToWrite["fillcolor"] = fillColor; + _maybeSetOpacity(styleToWrite, fillColor, "fill", component); + } + else styleToWrite["filled"] = "false"; + + if(style["dashstyle"]) { + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = style["dashstyle"]; + } + else if (style["stroke-dasharray"] && style["lineWidth"]) { + var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",", + parts = style["stroke-dasharray"].split(sep), + styleToUse = ""; + for(var i = 0; i < parts.length; i++) { + styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep); + } + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = styleToUse; + } + + _atts(node, styleToWrite); + }, + /* + * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. + */ + VmlComponent = function() { + var self = this; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.opacityNodes = { + "stroke":null, + "fill":null + }; + this.initOpacityNodes = function(vml) { + self.opacityNodes["stroke"] = _node("stroke", [0,0,1,1], {opacity:"0.0"}); + self.opacityNodes["fill"] = _node("fill", [0,0,1,1], {opacity:"0.0"}); + vml.appendChild(self.opacityNodes["stroke"]); + vml.appendChild(self.opacityNodes["fill"]); + }; + this.setOpacity = function(type, value) { + var node = self.opacityNodes[type]; + if (node) node["opacity"] = "" + value; + }; + var displayElements = [ ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el, doNotAppendToCanvas) { + if (!doNotAppendToCanvas) self.canvas.parentNode.appendChild(el); + displayElements.push(el); + }; + }, + /* + * Base class for Vml connectors. extends VmlComponent. + */ + VmlConnector = jsPlumb.VmlConnector = function(params) { + var self = this; + self.strokeNode = null; + self.canvas = null; + VmlComponent.apply(this, arguments); + var clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : ""); + this.paint = function(d, style, anchor) { + if (style != null) { + var path = self.getPath(d), p = { "path":path }; + + //* + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle : jsPlumb.util.convertStyle(style.outlineColor), + lineWidth : outlineStrokeWidth + }; + for (var aa in vmlAttributeMap) outlineStyle[aa] = style[aa]; + + if (self.bgCanvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.bgCanvas = _node("shape", d, p); + jsPlumb.appendElement(self.bgCanvas, params.parent); + _pos(self.bgCanvas, d); + self.appendDisplayElement(self.bgCanvas, true); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.bgCanvas, d); + _atts(self.bgCanvas, p); + } + + _applyStyles(self.bgCanvas, outlineStyle, self); + } + //*/ + + if (self.canvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + if (self.tooltip) p["label"] = self.tooltip; + self.canvas = _node("shape", d, p); + + //var group = _getGroup(params.parent); // test of append everything to a group + //group.appendChild(self.canvas); // sort of works but not exactly; + jsPlumb.appendElement(self.canvas, params.parent); //before introduction of groups + + self.appendDisplayElement(self.canvas, true); + + self.attachListeners(self.canvas, self); + + self.initOpacityNodes(self.canvas, ["stroke"]); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.canvas, d); + _atts(self.canvas, p); + } + + _applyStyles(self.canvas, style, self); + } + }; + + //self.appendDisplayElement(self.canvas); + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + }, + /* + * + * Base class for Vml Endpoints. extends VmlComponent. + * + */ + VmlEndpoint = function(params) { + VmlComponent.apply(this, arguments); + var vml = null, self = this, opacityStrokeNode = null, opacityFillNode = null; + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + + //var group = _getGroup(params.parent); + //group.appendChild(self.canvas); + jsPlumb.appendElement(self.canvas, params.parent); + + if (self.tooltip) self.canvas.setAttribute("label", self.tooltip); + + this.paint = function(d, style, anchor) { + var p = { }; + + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (vml == null) { + p["class"] = jsPlumb.endpointClass; + vml = self.getVml([0,0, d[2], d[3]], p, anchor); + self.canvas.appendChild(vml); + self.attachListeners(vml, self); + + self.appendDisplayElement(vml, true); + self.appendDisplayElement(self.canvas, true); + + self.initOpacityNodes(vml, ["fill"]); + } + else { + //p["coordsize"] = "1,1";//(d[2] * scale) + "," + (d[3] * scale); again, unsure. + _pos(vml, [0,0, d[2], d[3]]); + _atts(vml, p); + } + + _applyStyles(vml, style, self); + }; + + this.reattachListeners = function() { + if (vml) self.reattachListenersForElement(vml, self); + }; + }; + + jsPlumb.Connectors.vml.Bezier = function() { + jsPlumb.Connectors.Bezier.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Straight = function() { + jsPlumb.Connectors.Straight.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Flowchart = function() { + jsPlumb.Connectors.Flowchart.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l"; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]); + } + // finally draw a line to the end + p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e"; + return p; + }; + }; + + jsPlumb.Endpoints.vml.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("oval", d, atts); }; + }; + + jsPlumb.Endpoints.vml.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("rect", d, atts); }; + }; + + /* + * VML Image Endpoint is the same as the default image endpoint. + */ + jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image; + + /** + * placeholder for Blank endpoint in vml renderer. + */ + jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank; + + /** + * VML Label renderer. uses the default label renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label; + + var AbstractVmlArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + VmlComponent.apply(this, arguments); + var self = this, path = null; + self.canvas = null; + var getPath = function(d, connectorDimensions) { + return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) + + " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + + " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + + " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + + " x e"; + }; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) { + var p = {}; + if (strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] = jsPlumb.util.convertStyle(strokeStyle, true); + } + if (lineWidth) p["strokeweight"] = lineWidth + "px"; + if (fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = fillStyle; + } + var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + w = Math.abs(xmax - xmin), + h = Math.abs(ymax - ymin), + dim = [xmin, ymin, w, h]; + + // for VML, we create overlays using shapes that have the same dimensions and + // coordsize as their connector - overlays calculate themselves relative to the + // connector (it's how it's been done since the original canvas implementation, because + // for canvas that makes sense). + p["path"] = getPath(d, connectorDimensions); + p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale); + + dim[0] = connectorDimensions[0]; + dim[1] = connectorDimensions[1]; + dim[2] = connectorDimensions[2]; + dim[3] = connectorDimensions[3]; + + if (self.canvas == null) { + //p["class"] = jsPlumb.overlayClass; // TODO currentInstance? + self.canvas = _node("shape", dim, p); + connector.appendDisplayElement(self.canvas); + self.attachListeners(self.canvas, connector); + } + else { + _pos(self.canvas, dim); + _atts(self.canvas, p); + } + }; + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + }; + + jsPlumb.Overlays.vml.Arrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.vml.PlainArrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.vml.Diamond = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the SVG renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +/** + * SVG support for jsPlumb. + * + * things to investigate: + * + * gradients: https://developer.mozilla.org/en/svg_in_html_introduction + * css:http://tutorials.jenkov.com/svg/svg-and-css.html + * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath + * pointer events: https://developer.mozilla.org/en/css/pointer-events + * + * IE9 hover jquery: http://forum.jquery.com/topic/1-6-2-broke-svg-hover-events + * + */ +;(function() { + + var svgAttributeMap = { + "joinstyle":"stroke-linejoin", + "stroke-linejoin":"stroke-linejoin", + "stroke-dashoffset":"stroke-dashoffset", + "stroke-linecap":"stroke-linecap" + }, + STROKE_DASHARRAY = "stroke-dasharray", + DASHSTYLE = "dashstyle", + LINEAR_GRADIENT = "linearGradient", + RADIAL_GRADIENT = "radialGradient", + FILL = "fill", + STOP = "stop", + STROKE = "stroke", + STROKE_WIDTH = "stroke-width", + STYLE = "style", + NONE = "none", + JSPLUMB_GRADIENT = "jsplumb_gradient_", + LINE_WIDTH = "lineWidth", + ns = { + svg:"http://www.w3.org/2000/svg", + xhtml:"http://www.w3.org/1999/xhtml" + }, + _attr = function(node, attributes) { + for (var i in attributes) + node.setAttribute(i, "" + attributes[i]); + }, + _node = function(name, attributes) { + var n = document.createElementNS(ns.svg, name); + attributes = attributes || {}; + attributes["version"] = "1.1"; + attributes["xmnls"] = ns.xhtml; + _attr(n, attributes); + return n; + }, + _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; }, + _clearGradient = function(parent) { + for (var i = 0; i < parent.childNodes.length; i++) { + if (parent.childNodes[i].tagName == LINEAR_GRADIENT || parent.childNodes[i].tagName == RADIAL_GRADIENT) + parent.removeChild(parent.childNodes[i]); + } + }, + _updateGradient = function(parent, node, style, dimensions, uiComponent) { + var id = JSPLUMB_GRADIENT + uiComponent._jsPlumb.idstamp(); + // first clear out any existing gradient + _clearGradient(parent); + // this checks for an 'offset' property in the gradient, and in the absence of it, assumes + // we want a linear gradient. if it's there, we create a radial gradient. + // it is possible that a more explicit means of defining the gradient type would be + // better. relying on 'offset' means that we can never have a radial gradient that uses + // some default offset, for instance. + if (!style.gradient.offset) { + var g = _node(LINEAR_GRADIENT, {id:id}); + parent.appendChild(g); + } + else { + var g = _node(RADIAL_GRADIENT, { + id:id + }); + parent.appendChild(g); + } + + // the svg radial gradient seems to treat stops in the reverse + // order to how canvas does it. so we want to keep all the maths the same, but + // iterate the actual style declarations in reverse order, if the x indexes are not in order. + for (var i = 0; i < style.gradient.stops.length; i++) { + // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of + // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments. + // so, not too concerned about leaving this in for now. + var styleToUse = i; + if (dimensions.length == 8) + styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i; + else + styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i; + var stopColor = jsPlumb.util.convertStyle(style.gradient.stops[styleToUse][1], true); + var s = _node(STOP, {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor}); + g.appendChild(s); + } + var applyGradientTo = style.strokeStyle ? STROKE : FILL; + node.setAttribute(STYLE, applyGradientTo + ":url(#" + id + ")"); + }, + _applyStyles = function(parent, node, style, dimensions, uiComponent) { + + if (style.gradient) { + _updateGradient(parent, node, style, dimensions, uiComponent); + } + else { + // make sure we clear any existing gradient + _clearGradient(parent); + node.setAttribute(STYLE, ""); + } + + node.setAttribute(FILL, style.fillStyle ? jsPlumb.util.convertStyle(style.fillStyle, true) : NONE); + node.setAttribute(STROKE, style.strokeStyle ? jsPlumb.util.convertStyle(style.strokeStyle, true) : NONE); + if (style.lineWidth) { + node.setAttribute(STROKE_WIDTH, style.lineWidth); + } + + // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like + // the syntax in VML but is actually kind of nasty: values are given in the pixel + // coordinate space, whereas in VML they are multiples of the width of the stroked + // line, which makes a lot more sense. for that reason, jsPlumb is supporting both + // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from + // VML, which will be the preferred method. the code below this converts a dashstyle + // attribute given in terms of stroke width into a pixel representation, by using the + // stroke's lineWidth. + if (style[DASHSTYLE] && style[LINE_WIDTH] && !style[STROKE_DASHARRAY]) { + var sep = style[DASHSTYLE].indexOf(",") == -1 ? " " : ",", + parts = style[DASHSTYLE].split(sep), + styleToUse = ""; + parts.forEach(function(p) { + styleToUse += (Math.floor(p * style.lineWidth) + sep); + }); + node.setAttribute(STROKE_DASHARRAY, styleToUse); + } + else if(style[STROKE_DASHARRAY]) { + node.setAttribute(STROKE_DASHARRAY, style[STROKE_DASHARRAY]); + } + + // extra attributes such as join type, dash offset. + for (var i in svgAttributeMap) { + if (style[i]) { + node.setAttribute(svgAttributeMap[i], style[i]); + } + } + }, + _decodeFont = function(f) { + var r = /([0-9].)(p[xt])\s(.*)/; + var bits = f.match(r); + return {size:bits[1] + bits[2], font:bits[3]}; + }, + _classManip = function(el, add, clazz) { + var classesToAddOrRemove = clazz.split(" "), + className = el.className, + curClasses = className.baseVal.split(" "); + + for (var i = 0; i < classesToAddOrRemove.length; i++) { + if (add) { + if (curClasses.indexOf(classesToAddOrRemove[i]) == -1) + curClasses.push(classesToAddOrRemove[i]); + } + else { + var idx = curClasses.indexOf(classesToAddOrRemove[i]); + if (idx != -1) + curClasses.splice(idx, 1); + } + } + + el.className.baseVal = curClasses.join(" "); + }, + _addClass = function(el, clazz) { + _classManip(el, true, clazz); + }, + _removeClass = function(el, clazz) { + _classManip(el, false, clazz); + }; + + /** + utility methods for other objects to use. + */ + jsPlumb.util.svg = { + addClass:_addClass, + removeClass:_removeClass + }; + + /* + * Base class for SVG components. + */ + //var SvgComponent = function(cssClass, originalArgs, pointerEventsSpec) { + var SvgComponent = function(params) { + var self = this, + pointerEventsSpec = params.pointerEventsSpec || "all"; + jsPlumb.jsPlumbUIComponent.apply(this, params.originalArgs); + self.canvas = null, self.path = null, self.svg = null; + + var clazz = params.cssClass + " " + (params.originalArgs[0].cssClass || ""), + svgParams = { + "style":"", + "width":0, + "height":0, + "pointer-events":pointerEventsSpec, + "position":"absolute" + }; + if (self.tooltip) svgParams["title"] = self.tooltip; + self.svg = _node("svg", svgParams); + if (params.useDivWrapper) { + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.sizeCanvas(self.canvas,0,0,1,1); + self.canvas.className = clazz; + if (self.tooltip) self.canvas.setAttribute("title", self.tooltip); + } + else { + _attr(self.svg, { "class":clazz }); + self.canvas = self.svg; + } + + params._jsPlumb.appendElement(self.canvas, params.originalArgs[0]["parent"]); + if (params.useDivWrapper) self.canvas.appendChild(self.svg); + + // TODO this displayElement stuff is common between all components, across all + // renderers. would be best moved to jsPlumbUIComponent. + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + this.paint = function(d, style, anchor) { + if (style != null) { + var x = d[0], y = d[1]; + if (params.useDivWrapper) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + x = 0, y = 0; + } + _attr(self.svg, { + "style":_pos([x, y, d[2], d[3]]), + "width": d[2], + "height": d[3] + }); + self._paint.apply(this, arguments); + } + }; + }; + + /* + * Base class for SVG connectors. + */ + var SvgConnector = jsPlumb.SvgConnector = function(params) { + var self = this; + SvgComponent.apply(this, [ { + cssClass:params["_jsPlumb"].connectorClass, + originalArgs:arguments, + pointerEventsSpec:"none", + tooltip:params.tooltip, + _jsPlumb:params["_jsPlumb"] + } ]); + this._paint = function(d, style) { + var p = self.getPath(d), a = { "d":p }, outlineStyle = null; + a["pointer-events"] = "all"; + + // outline style. actually means drawing an svg object underneath the main one. + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = jsPlumb.CurrentLibrary.extend({}, style); + outlineStyle.strokeStyle = jsPlumb.util.convertStyle(style.outlineColor); + outlineStyle.lineWidth = outlineStrokeWidth; + + if (self.bgPath == null) { + self.bgPath = _node("path", a); + self.svg.appendChild(self.bgPath); + self.attachListeners(self.bgPath, self); + } + else { + _attr(self.bgPath, a); + } + + _applyStyles(self.svg, self.bgPath, outlineStyle, d, self); + } + + + // test - see below + // a["clip-path"]= "url(#testClip)"; + + if (self.path == null) { + self.path = _node("path", a); + self.svg.appendChild(self.path); + self.attachListeners(self.path, self); + + /* + this is a test of a clip path. i'm looking into using one of these to animate a jsplumb connection. + you could do this by walking along the line, stepping along a little at a time, and setting the clip + path to extend as far as that point. + + self.clip = _node("clipPath", {id:"testClip", clipPathUnits:"objectBoundingBox"}); + self.svg.appendChild(self.clip); + self.clip.appendChild(_node("rect", { + x:"0",y:"0",width:"0.5",height:"1" + })); + */ + } + else { + _attr(self.path, a); + } + + _applyStyles(self.svg, self.path, style, d, self); + }; + + this.reattachListeners = function() { + if (self.bgPath) self.reattachListenersForElement(self.bgPath, self); + if (self.path) self.reattachListenersForElement(self.path, self); + }; + + }; + + /* + * SVG Bezier Connector + */ + jsPlumb.Connectors.svg.Bezier = function(params) { + jsPlumb.Connectors.Bezier.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]; }; + }; + + /* + * SVG straight line Connector + */ + jsPlumb.Connectors.svg.Straight = function(params) { + jsPlumb.Connectors.Straight.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; }; + }; + + jsPlumb.Connectors.svg.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "M " + dimensions[4] + "," + dimensions[5]; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)]; + } + // finally draw a line to the end + p = p + " " + dimensions[6] + "," + dimensions[7]; + return p; + }; + }; + + /* + * Base class for SVG endpoints. + */ + var SvgEndpoint = function(params) { + var self = this; + SvgComponent.apply(this, [ { + cssClass:params["_jsPlumb"].endpointClass, + originalArgs:arguments, + pointerEventsSpec:"all", + useDivWrapper:true, + _jsPlumb:params["_jsPlumb"] + } ]); + this._paint = function(d, style) { + var s = jsPlumb.extend({}, style); + if (s.outlineColor) { + s.strokeWidth = s.outlineWidth; + s.strokeStyle = jsPlumb.util.convertStyle(s.outlineColor, true); + } + + if (self.node == null) { + self.node = self.makeNode(d, s); + self.svg.appendChild(self.node); + self.attachListeners(self.node, self); + } + _applyStyles(self.svg, self.node, s, d, self); + _pos(self.node, d); + }; + + this.reattachListeners = function() { + if (self.node) self.reattachListenersForElement(self.node, self); + }; + }; + + /* + * SVG Dot Endpoint + */ + jsPlumb.Endpoints.svg.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("circle", { + "cx" : d[2] / 2, + "cy" : d[3] / 2, + "r" : d[2] / 2 + }); + }; + }; + + /* + * SVG Rectangle Endpoint + */ + jsPlumb.Endpoints.svg.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("rect", { + "width":d[2], + "height":d[3] + }); + }; + }; + + /* + * SVG Image Endpoint is the default image endpoint. + */ + jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image; + /* + * Blank endpoint in svg renderer is the default Blank endpoint. + */ + jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank; + /* + * Label endpoint in svg renderer is the default Label endpoint. + */ + jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label; + + var AbstractSvgArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + this.isAppendedAtTopLevel = false; + var self = this, path =null; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + } + var clazz = originalArgs && (originalArgs.length == 1) ? (originalArgs[0].cssClass || "") : ""; + + _attr(path, { + "d" : makePath(d), + "class" : clazz, + stroke : strokeStyle ? strokeStyle : null, + fill : fillStyle ? fillStyle : null + }); + }; + var makePath = function(d) { + return "M" + d.hxy.x + "," + d.hxy.y + + " L" + d.tail[0].x + "," + d.tail[0].y + + " L" + d.cxy.x + "," + d.cxy.y + + " L" + d.tail[1].x + "," + d.tail[1].y + + " L" + d.hxy.x + "," + d.hxy.y; + }; + this.reattachListeners = function() { + if (path) self.reattachListenersForElement(path, self); + }; + }; + + jsPlumb.Overlays.svg.Arrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.svg.PlainArrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.svg.Diamond = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; + + // a test + jsPlumb.Overlays.svg.GuideLines = function() { + var path = null, self = this, path2 = null, p1_1, p1_2; + jsPlumb.Overlays.GuideLines.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + + p1_1 = _node("path"); + connector.svg.appendChild(p1_1); + self.attachListeners(p1_1, connector); + self.attachListeners(p1_1, self); + + p1_2 = _node("path"); + connector.svg.appendChild(p1_2); + self.attachListeners(p1_2, connector); + self.attachListeners(p1_2, self); + + } + + _attr(path, { + "d" : makePath(d[0], d[1]), + stroke : "red", + fill : null + }); + + _attr(p1_1, { + "d" : makePath(d[2][0], d[2][1]), + stroke : "blue", + fill : null + }); + + _attr(p1_2, { + "d" : makePath(d[3][0], d[3][1]), + stroke : "green", + fill : null + }); + }; + + var makePath = function(d1, d2) { + return "M " + d1.x + "," + d1.y + + " L" + d2.x + "," + d2.y; + }; + + }; +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the HTML5 canvas renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + +// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS ******************************************************************* + + // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. + var _connectionBeingDragged = null, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, + _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }; + + /* + * Class:CanvasMouseAdapter + * Provides support for mouse events on canvases. + */ + var CanvasMouseAdapter = function() { + var self = this; + self.overlayPlacements = []; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + jsPlumb.EventGenerator.apply(this, arguments); + /** + * returns whether or not the given event is ojver a painted area of the canvas. + */ + this._over = function(e) { + var o = _getOffset(_getElementObject(self.canvas)), + pageXY = _pageXY(e), + x = pageXY[0] - o.left, y = pageXY[1] - o.top; + if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + return false; + }; + + var _mouseover = false, _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false, + _nullSafeHasClass = function(el, clazz) { + return el != null && _hasClass(el, clazz); + }; + this.mousemove = function(e) { + var pageXY = _pageXY(e), clientXY = _clientXY(e), + ee = document.elementFromPoint(clientXY[0], clientXY[1]), + eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); + var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); + if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.fire("mouseenter", self, e); + return true; + } + // TODO here there is a remote chance that the overlay the mouse moved onto + // is actually not an overlay for the current component. a more thorough check would + // be to ensure the overlay belonged to the current component. + else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { + _mouseover = false; + self.fire("mouseexit", self, e); + } + self.fire("mousemove", self, e); + }; + + this.click = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); + self.fire("mousedown", self, e); + } + }; + + this.mouseup = function(e) { + _mouseDown = false; + self.fire("mouseup", self, e); + }; + + this.contextmenu = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("contextmenu", self, e); + _mouseWasDown = false; + }; + }; + + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + jsPlumb.appendElement(canvas, params.parent); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + params["_jsPlumb"].getId(canvas, params.uuid); + if (params.tooltip) canvas.setAttribute("title", params.tooltip); + + return canvas; + }; + + var CanvasComponent = function(params) { + CanvasMouseAdapter.apply(this, arguments); + + var displayElements = [ ]; + this.getDisplayElements = function() { return displayElements; }; + this.appendDisplayElement = function(el) { displayElements.push(el); }; + } + + /** + * Class:CanvasConnector + * Superclass for Canvas Connector renderers. + */ + var CanvasConnector = jsPlumb.CanvasConnector = function(params) { + + CanvasComponent.apply(this, arguments); + + var _paintOneStyle = function(dim, aStyle) { + self.ctx.save(); + jsPlumb.extend(self.ctx, aStyle); + if (aStyle.gradient) { + var g = self.createGradient(dim, self.ctx); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + self.ctx.strokeStyle = g; + } + self._paint(dim); + self.ctx.restore(); + }; + + var self = this, + clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:params.tooltip + }); + self.ctx = self.canvas.getContext("2d"); + + self.appendDisplayElement(self.canvas); + + self.paint = function(dim, style) { + if (style != null) { + jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + _paintOneStyle(dim, outlineStyle); + } + _paintOneStyle(dim, style); + } + }; + }; + + /** + * Class:CanvasEndpoint + * Superclass for Canvas Endpoint renderers. + */ + var CanvasEndpoint = function(params) { + var self = this; + CanvasComponent.apply(this, arguments); + var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""), + canvasParams = { + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:self.tooltip + }; + self.canvas = _newCanvas(canvasParams); + self.ctx = self.canvas.getContext("2d"); + + self.appendDisplayElement(self.canvas); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + } + + self._paint.apply(this, arguments); + }; + }; + + jsPlumb.Endpoints.canvas.Dot = function(params) { + jsPlumb.Endpoints.Dot.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + var self = this, + parseValue = function(value) { + try { return parseInt(value); } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }, + calculateAdjustments = function(gradient) { + var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius; + gradient.offset && (offsetAdjustment = parseValue(gradient.offset)); + gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius)); + return [offsetAdjustment, innerRadius]; + }; + this._paint = function(d, style, anchor) { + if (style != null) { + var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + if (style.gradient) { + var adjustments = calculateAdjustments(style.gradient), + yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0], + xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0], + g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + ctx.beginPath(); + ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + } + }; + }; + + jsPlumb.Endpoints.canvas.Rectangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) { + + var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + + /* canvas gradient */ + if (style.gradient) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, d[2], d[3]); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + jsPlumb.Endpoints.canvas.Triangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Triangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) + { + var width = d[2], height = d[3], x = d[0], y = d[1], + ctx = self.canvas.getContext('2d'), + offsetX = 0, offsetY = 0, angle = 0, + orientation = anchor.getOrientation(self); + + if( orientation[0] == 1 ) { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = style.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + /* + * Canvas Image Endpoint: uses the default version, which creates an tag. + */ + jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image; + + /* + * Blank endpoint in all renderers is just the default Blank endpoint. + */ + jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank; + + /* + * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Bezier = function() { + var self = this; + jsPlumb.Connectors.Bezier.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO i doubt this handles the case that source and target are swapped. + this.createGradient = function(dim, ctx, swap) { + return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + /* + * Canvas straight line Connector. Draws a straight line onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Straight = function() { + var self = this; + jsPlumb.Connectors.Straight.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO this does not handle the case that src and target are swapped. + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + jsPlumb.Connectors.canvas.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + +// ********************************* END OF CANVAS RENDERERS ******************************************************************* + + jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label; + + /** + * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. + */ + var CanvasOverlay = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }; + + var AbstractCanvasArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + CanvasOverlay.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + var ctx = connector.ctx; + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(d.hxy.x, d.hxy.y); + ctx.lineTo(d.tail[0].x, d.tail[0].y); + ctx.lineTo(d.cxy.x, d.cxy.y); + ctx.lineTo(d.tail[1].x, d.tail[1].y); + ctx.lineTo(d.hxy.x, d.hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + }; + + jsPlumb.Overlays.canvas.Arrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.canvas.PlainArrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.canvas.Diamond = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the MooTools adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function(now) { + this.parent(now); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, _scope, value) { + var l = list[_scope]; + if (!l) { + l = []; + list[_scope] = l; + } + l.push(value); + }; + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + var _getElementObject = function(el) { + return $(el); + }, + + _removeNonPermanentDroppables = function(drag) { + /* + // remove non-permanent droppables from all arrays + var dbs = _droppables[drag.scope], d = []; + if (dbs) { + var d = []; + for (var i=0; i < dbs.length; i++) { + var isPermanent = dbs[i].getAttribute("_isPermanentDroppable"); + if (isPermanent === "true") d.push(dbs[i]); + } + dbs.splice(0, dbs.length); + _droppables[drag.scope] = d; + } + d = []; + // clear out transient droppables from the drag itself + for(var i = 0; i < drag.droppables.length; i++) { + var isPermanent = drag.droppables[i].getAttribute("_isPermanentDroppable"); + if (isPermanent === "true") d.push(drag.droppables[i]); + } + drag.droppables.splice(0, drag.droppables.length); // release old ones + drag.droppables = d; + //*/ + }; + + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el) + try { + if (el.className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.addClass(el, clazz); + } + else el.addClass(clazz); + } + catch (e) { + // SVGAnimatedString not supported; no problem. + el.addClass(clazz); + } + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + m.start(properties); + }, + + appendElement : function(child, parent) { + _getElementObject(parent).grab(child); + }, + + bind : function(el, event, callback) { + el = _getElementObject(el); + el.addEvent(event, callback); + }, + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.event.clientX, eventObject.event.clientY]; + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el), + drags = _draggablesById[id]; + return drags[0].scope; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getDOMElement : function(el) { + // MooTools just decorates the DOM elements. so we have either an ID or an Element here. + return typeof(el) == "String" ? document.getElementById(el) : el; + }, + + getElementObject : _getElementObject, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + getPageXY : function(eventObject) { + return [eventObject.event.pageX, eventObject.event.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).getParent(); + }, + + getScrollLeft : function(el) { + return null; + }, + + getScrollTop : function(el) { + return null; + }, + + getSelector : function(spec) { + return $$(spec); + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e != null ? e.tagName : null; + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[0], + p = jsPlumb.CurrentLibrary.getElementObject(ui).getPosition(); + return { left:p.x, top:p.y }; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options, isPlumbedComponent) { + var id = jsPlumb.getId(el); + var drag = _draggablesById[id]; + if (!drag) { + var originalZIndex = 0, + originalCursor = null, + dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + + options['onStart'] = jsPlumb.wrap(options['onStart'], function() { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + drag.originalZIndex = originalZIndex; + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + + _removeNonPermanentDroppables(drag); + }); + + // DROPPABLES - only relevant if this is a plumbed component, ie. not just the result of the user making some DOM element + // draggable. this is the only library adapter that has to care about this parameter. + var scope = "" + (options["scope"] || jsPlumb.Defaults.Scope), + filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }, + droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + + if (isPlumbedComponent) { + + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop'); + } + }; + } + else + options["droppables"] = []; + + drag = new Drag.Move(el, options); + drag.scope = scope; + drag.originalZIndex = originalZIndex; + _add(_draggablesById, el.get("id"), drag); + // again, only keep a record of this for scope stuff if this is a plumbed component (an endpoint) + if (isPlumbedComponent) { + _add(_draggablesByScope, scope, drag); + } + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + initDroppable : function(el, options, isPlumbedComponent, isPermanent) { + var scope = options["scope"]; + _add(_droppables, scope, el); + var id = jsPlumb.getId(el); + + el.setAttribute("_isPermanentDroppable", isPermanent); // we use this to clear out droppables on drag complete. + _droppableOptions[id] = options; + _droppableScopesById[id] = scope; + var filterFunc = function(entry) { return entry.element != el; }, + draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + isAlreadyDraggable : function(el) { + return _draggablesById[jsPlumb.getId(el)] != null; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + return (typeof Drag != undefined && typeof Drag.Move != undefined); + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el.className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.removeClass(el, clazz); + } + else el.removeClass(clazz); + } + catch (e) { + // SVGAnimatedString not supported; no problem. + el.removeClass(clazz); + } + }, + + removeElement : function(element, parent) { + var el = _getElementObject(element); + if (el) el.dispose(); // ?? + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + setDragScope : function(el, scope) { + var drag = _draggablesById[el.get("id")]; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + drag[0].droppables = droppables; + }, + + setOffset : function(el, o) { + _getElementObject(el).setPosition({x:o.left, y:o.top}); + }, + + stopDrag : function() { + for (var i in _draggablesById) { + for (var j = 0; j < _draggablesById[i].length; j++) { + var d = _draggablesById[i][j]; + d.stop(); + if (d.originalZIndex != 0) + d.element.setStyle("z-index", d.originalZIndex); + } + } + }, + + /** + * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself. + * the other libraries do not. yui, in fact, cannot even pass an original event. we have to pull out stuff + * from the originalEvent to put in an options object for YUI. + * @param el + * @param event + * @param originalEvent + */ + trigger : function(el, event, originalEvent) { + originalEvent.stopPropagation(); + _getElementObject(el).fireEvent(event, originalEvent); + }, + + unbind : function(el, event, callback) { + el = _getElementObject(el); + el.removeEvent(event, callback); + } + }; + + window.addEvent('domready', jsPlumb.init); +})(); +(function(){if("undefined"==typeof Math.sgn)Math.sgn=function(a){return 0==a?0:0n?n=l:lb.location)b.location=0;return t(a,b.location)},nearestPointOnCurve:function(a,b){var f=u(a,b);return{point:r(b,b.length-1,f.location,null,null),location:f.location}},pointOnCurve:p,pointAlongCurveFrom:function(a,b,f){return s(a,b,f).point},perpendicularToCurveAt:function(a,b,f,d){b=s(a,b,null==d?0:d);a=t(a,b.location);d=Math.atan(-1/a);a=f/2*Math.sin(d);f=f/2*Math.cos(d);return[{x:b.point.x+f,y:b.point.y+a},{x:b.point.x-f,y:b.point.y-a}]}}})(); \ No newline at end of file diff --git a/archive/1.3.5/yui.jsPlumb-1.3.5-RC1.js b/archive/1.3.5/yui.jsPlumb-1.3.5-RC1.js new file mode 100644 index 000000000..4b5cd934d --- /dev/null +++ b/archive/1.3.5/yui.jsPlumb-1.3.5-RC1.js @@ -0,0 +1,372 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the YUI3 adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +/** + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + */ +(function() { + + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function( v, b, s ) { + for( var i = +b || 0, l = this.length; i < l; i++ ) { + if( this[i]===v || s && this[i]==v ) { return i; } + } + return -1; + }; + } + + var Y; + + YUI().use('node', 'dd', 'anim', 'node-event-simulate', function(_Y) { + Y = _Y; + Y.on("domready", function() { jsPlumb.init(); }); + }); + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }, + ddEvents = [ "drag:mouseDown", "drag:afterMouseDown", "drag:mouseup", + "drag:align", "drag:removeHandle", "drag:addHandle", "drag:removeInvalid", "drag:addInvalid", + "drag:start", "drag:end", "drag:drag", "drag:over", "drag:enter", + "drag:exit", "drag:drophit", "drag:dropmiss", "drop:over", "drop:enter", "drop:exit", "drop:hit" + ], + animEvents = [ "tween" ], + /** + * helper function to curry callbacks for some element. + */ + _wrapper = function(fn) { + return function() { + try { + return fn.apply(this, arguments); + } + catch (e) { } + }; + }, + /** + * extracts options from the given options object, leaving out event handlers. + */ + _getDDOptions = function(options) { + var o = {}; + for (var i in options) if (ddEvents.indexOf(i) == -1) o[i] = options[i]; + return o; + }, + /** + * attaches all event handlers found in options to the given dragdrop object, and registering + * the given el as the element of interest. + */ + _attachListeners = function(dd, options, eventList) { + for (var ev in options) { + if (eventList.indexOf(ev) != -1) { + var w = _wrapper(options[ev]); + dd.on(ev, w); + } + } + }, + _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}, + _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }, + _lastDragObject = null, + _extend = function(o1, o2) { + for (var i in o2) + o1[i] = o2[i]; + return o1; + }, + _getAttribute = function(el, attributeId) { + return el.getAttribute(attributeId); + }, + _getElementObject = function(el) { + if (el == null) return null; + var eee = null; + eee = typeof el == 'string' ? Y.one('#' + el) : el._node ? el : Y.one(el); + return eee; + }; + + jsPlumb.CurrentLibrary = { + + addClass : function(el, clazz) { + jsPlumb.CurrentLibrary.getElementObject(el).addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + var o = _extend({node:el, to:properties}, options); + var id = _getAttribute(el, "id"); + o["tween"] = jsPlumb.wrap(properties["tween"], function() { + jsPlumb.repaint(id); + }); + var a = new Y.Anim(o); + _attachListeners(a, o, animEvents); + a.run(); + }, + + appendElement : function(child, parent) { + _getElementObject (parent).append(child); + }, + + /** + * event binding wrapper. + */ + bind : function(el, event, callback) { + _getElementObject(el).on(event, callback); + }, + + dragEvents : { + "start":"drag:start", "stop":"drag:end", "drag":"drag:drag", "step":"step", + "over":"drop:enter", "out":"drop:exit", "drop":"drop:hit" + }, + + extend : _extend, + + getAttribute : _getAttribute, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + // this is a workaround for the unfortunate fact that in YUI3, the 'drop:exit' event does + // not contain a reference to the drag that just exited. single-threaded js to the + // rescue: we'll just keep it for ourselves. + if (eventArgs[0].drag) _lastDragObject = eventArgs[0].drag.el; + return _lastDragObject; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + return dd.scope; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getDOMElement : function(el) { + if (typeof(el) == "String") + return document.getElementById(el); + else if (el._node) + return el._node; + else return el; + }, + + getElementObject : _getElementObject, + + getOffset : function(el) { + var o = Y.DOM.getXY(el._node); + return {left:o[0], top:o[1]}; + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).get("parentNode"); + }, + + getScrollLeft : function(el) { + return 0; + }, + + getScrollTop : function(el) { + return 0; + }, + + getSelector : function(spec) { + var s = Y.all(spec); + return s && s ._nodes ? s._nodes : []; + }, + + getSize : function(el) { + return [ el._node.offsetWidth, el._node.offsetHeight ]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e != null && e._node != null ? e._node.tagName : null; + }, + + getUIPosition : function(args) { + var n = args[0].currentTarget.el._node, + o = Y.DOM.getXY(n); + return {left:o[0], top:o[1]}; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options) { + var _opts = _getDDOptions(options); + var id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drag(_opts); + dd.el = el; + + var scope = options['scope'] || jsPlumb.Defaults.Scope; + dd.scope = scope; + + _draggablesById[id] = dd; + _add(_draggablesByScope, scope, dd); + + _attachListeners(dd, options, ddEvents); + }, + + initDroppable : function(el, options) { + var _opts = _getDDOptions(options); + var id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drop(_opts); + + _droppableOptions[id] = options; + + options = _extend({}, options); + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _droppableScopesById[id] = scope; + + options["drop:enter"] = jsPlumb.wrap(options["drop:enter"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, true); + }, true); + options["drop:exit"] = jsPlumb.wrap(options["drop:exit"], function(e) { + _checkHover(el, false); + }); + options["drop:hit"] = jsPlumb.wrap(options["drop:hit"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, false); + }, true); + + _attachListeners(dd, options, ddEvents); + }, + + isAlreadyDraggable : function(el) { + el = _getElementObject(el); + return el.hasClass("yui3-dd-draggable"); + }, + + isDragSupported : function(el) { return true; }, + isDropSupported : function(el) { return true; }, + removeClass : function(el, clazz) { + jsPlumb.CurrentLibrary.getElementObject(el).removeClass(clazz); + }, + removeElement : function(el) { _getElementObject(el).remove(); }, + + setAttribute : function(el, attributeName, attributeValue) { + el.setAttribute(attributeName, attributeValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + if (dd) dd.set("lock", !draggable); + }, + + setDragScope : function(el, scope) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + if (dd) dd.scope = scope; + }, + + setOffset : function(el, o) { + el = _getElementObject(el); + el.set("top", o.top); + el.set("left", o.left); + }, + + stopDrag : function() { + Y.DD.DDM.stopDrag(); + }, + + trigger : function(el, event, originalEvent) { + originalEvent.stopPropagation(); + _getElementObject(el).simulate(event, { + pageX:originalEvent.pageX, + pageY:originalEvent.pageY, + clientX:originalEvent.clientX, + clientY:originalEvent.clientY + }); + }, + + /** + * event unbinding wrapper. + */ + unbind : function(el, event, callback) { + _getElementObject(el).detach(event, callback); + } + }; +})(); \ No newline at end of file diff --git a/archive/1.3.5/yui.jsPlumb-1.3.5-all-min.js b/archive/1.3.5/yui.jsPlumb-1.3.5-all-min.js new file mode 100644 index 000000000..0b61d5a0b --- /dev/null +++ b/archive/1.3.5/yui.jsPlumb-1.3.5-all-min.js @@ -0,0 +1 @@ +(function(){var w=!!document.createElement("canvas").getContext,e=!!window.SVGAngle||document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1"),a=!(w|e);var p=function(F,G,D,J){var I=function(M,L){if(M===L){return true}else{if(typeof M=="object"&&typeof L=="object"){var N=true;for(var K in M){if(!I(M[K],L[K])){N=false;break}}for(var K in L){if(!I(L[K],M[K])){N=false;break}}return N}}};for(var H=+D||0,E=F.length;H-1){E.splice(D,1)}return D!=-1},r=function(F,E,D){if(i(F,D)==-1){F.push(E)}};if(!window.console){window.console={time:function(){},timeEnd:function(){},group:function(){},groupEnd:function(){},log:function(){}}}var l=function(G,E,F){var D=G[E];if(D==null){D=[],G[E]=D}D.push(F);return D},u=null,d=function(D,E){return n.CurrentLibrary.getAttribute(A(D),E)},f=function(E,F,D){n.CurrentLibrary.setAttribute(A(E),F,D)},x=function(E,D){n.CurrentLibrary.addClass(A(E),D)},k=function(E,D){return n.CurrentLibrary.hasClass(A(E),D)},m=function(E,D){n.CurrentLibrary.removeClass(A(E),D)},A=function(D){return n.CurrentLibrary.getElementObject(D)},s=function(D){return n.CurrentLibrary.getOffset(A(D))},b=function(D){return n.CurrentLibrary.getSize(A(D))},z=true,o=function(){if(z&&typeof console!="undefined"){try{var E=arguments[arguments.length-1];console.log(E)}catch(D){}}},C=function(D){if(z&&typeof console!="undefined"){console.group(D)}},h=function(D){if(z&&typeof console!="undefined"){console.groupEnd(D)}},B=function(D){if(z&&typeof console!="undefined"){console.time(D)}},t=function(D){if(z&&typeof console!="undefined"){console.timeEnd(D)}};EventGenerator=function(){var F={},E=this;var D=["ready"];this.bind=function(G,H){l(F,G,H)};this.fire=function(I,J,G){if(F[I]){for(var H=0;H1){for(var T=0;T=0?N.overlays[O]:null};this.hideOverlay=function(P){var O=N.getOverlay(P);if(O){O.hide()}};this.showOverlay=function(P){var O=N.getOverlay(P);if(O){O.show()}};this.removeAllOverlays=function(){N.overlays.splice(0,N.overlays.length);N.repaint()};this.removeOverlay=function(P){var O=D(P);if(O!=-1){var Q=N.overlays[O];Q.cleanup();N.overlays.splice(O,1)}};this.removeOverlays=function(){for(var O=0;O0){try{for(var a7=0;a70?p(bj,bi)!=-1:true},ba=bd.length>1?{}:[],bg=function(bj,bk){if(bd.length>1){var bi=ba[bj];if(bi==null){bi=[];ba[bj]=bi}bi.push(bk)}else{ba.push(bk)}};for(var a9 in aD){if(a7(bd,a9)){for(var a8=0;a8=4)?[bb[2],bb[3]]:[0,0],offsets:(bb.length==6)?[bb[4],bb[5]]:[0,0],elementId:a8};a9=new Q(ba);a9.clone=function(){return new Q(ba)}}}}}if(!a9.id){a9.id="anchor_"+Z()}return a9};this.makeAnchors=function(a9,a7,a6){var ba=[];for(var a8=0;a80?bg[0]:null,bb=bg.length>0?0:-1,bf=this,ba=function(bj,bh,bn,bm,bi){var bl=bm[0]+(bj.x*bi[0]),bk=bm[1]+(bj.y*bi[1]);return Math.sqrt(Math.pow(bh-bl,2)+Math.pow(bn-bk,2))},a6=a7||function(br,bi,bj,bk,bh){var bm=bj[0]+(bk[0]/2),bl=bj[1]+(bk[1]/2);var bo=-1,bq=Infinity;for(var bn=0;bn=a9.left)||(bc.left<=a9.right&&bc.right>=a9.right)||(bc.left<=a9.left&&bc.right>=a9.right)||(a9.left<=bc.left&&a9.right>=bc.right)),bh=((bc.top<=a9.top&&bc.bottom>=a9.top)||(bc.top<=a9.bottom&&bc.bottom>=a9.bottom)||(bc.top<=a9.top&&bc.bottom>=a9.bottom)||(a9.top<=bc.top&&a9.bottom>=bc.bottom));if(!(bb||bh)){var be=null,a8=false,a6=false,bd=null;if(a9.left>bc.left&&a9.top>bc.top){be=["right","top"]}else{if(a9.left>bc.left&&bc.top>a9.top){be=["top","left"]}else{if(a9.leftbc.top){be=["left","top"]}}}}return{orientation:K.DIAGONAL,a:be,theta:a7,theta2:ba}}else{if(bb){return{orientation:K.HORIZONTAL,a:bc.topa6[0]?1:-1},R=function(a6){return function(a8,a7){var a9=true;if(a6){if(a8[0][0]a7[0][1]}}else{if(a8[0][0]>a7[0][0]){a9=true}else{a9=a8[0][1]>a7[0][1]}}return a9===false?-1:1}},G=function(a7,a6){var a9=a7[0][0]<0?-Math.PI-a7[0][0]:Math.PI-a7[0][0],a8=a6[0][0]<0?-Math.PI-a6[0][0]:Math.PI-a6[0][0];if(a9>a8){return 1}else{return a7[0][1]>a6[0][1]?1:-1}},aF={top:aL,right:R(true),bottom:R(true),left:G},ac=function(a6,a7){return a6.sort(a7)},aa=function(a7,a6){var a9=U[a7],ba=X[a7],a8=function(bg,bn,bc,bf,bl,bk){if(bf.length>0){var bj=ac(bf,aF[bg]),bh=bg==="right"||bg==="top",bb=aI(bg,bn,bc,bj,bl,bk,bh);var bo=function(br,bq){var bp=a0([bq[0],bq[1]],br.canvas);Y[br.id]=[bp[0],bp[1],bq[2],bq[3]]};for(var bd=0;bd-1){bc.splice(bd,1)}else{y(a6[be.elementId],function(bf){return bf.id==be.id})}};this.clearFor=function(bd){delete a6[bd];a6[bd]=[]};var ba=function(bx,bk,bs,bh,bn,bo,bq,bm,bz,bp,bg,bw){var bu=-1,bf=-1,bi=bh.endpoints[bq],br=bi.id,bl=[1,0][bq],bd=[[bk,bs],bh,bn,bo,br],be=bx[bz],by=bi._continuousAnchorEdge?bx[bi._continuousAnchorEdge]:null;if(by){var bv=i(by,function(bA){return bA[4]==br});if(bv!=-1){by.splice(bv,1);for(var bt=0;bt0){bp.connections[0].setHover(bC,false)}else{bp.setHover(bC)}};_bindListeners(bp.endpoint,bp,bB);this.setPaintStyle(bA.paintStyle||bA.style||aZ.Defaults.EndpointStyle||n.Defaults.EndpointStyle,true);this.setHoverPaintStyle(bA.hoverPaintStyle||aZ.Defaults.EndpointHoverStyle||n.Defaults.EndpointHoverStyle,true);this.paintStyleInUse=this.paintStyle;this.connectorStyle=bA.connectorStyle;this.connectorHoverStyle=bA.connectorHoverStyle;this.connectorOverlays=bA.connectorOverlays;this.connector=bA.connector;this.connectorTooltip=bA.connectorTooltip;this.isSource=bA.isSource||false;this.isTarget=bA.isTarget||false;var bu=bA.maxConnections||aZ.Defaults.MaxConnections;this.getAttachedElements=function(){return bp.connections};this.canvas=this.endpoint.canvas;this.connections=bA.connections||[];this.scope=bA.scope||I;this.timestamp=null;bp.isReattach=bA.reattach||false;bp.connectionsDetachable=aZ.Defaults.ConnectionsDetachable;if(bA.connectionsDetachable===false||bA.detachable===false){bp.connectionsDetachable=false}var bk=bA.dragAllowedWhenFull||true;this.computeAnchor=function(bC){return bp.anchor.compute(bC)};this.addConnection=function(bC){bp.connections.push(bC)};this.detach=function(bC,bG,bD,bK){var bJ=i(bp.connections,function(bM){return bM.id==bC.id}),bI=false;bK=(bK!==false);if(bJ>=0){if(bD||bC._forceDetach||bC.isDetachable()||bC.isDetachAllowed(bC)){var bL=bC.endpoints[0]==bp?bC.endpoints[1]:bC.endpoints[0];if(bD||bC._forceDetach||(bp.isDetachAllowed(bC))){bp.connections.splice(bJ,1);if(!bG){bL.detach(bC,true,bD);if(bC.endpointsToDeleteOnDetach){for(var bH=0;bH0){bp.detach(bp.connections[0],false,true,bC)}};this.detachFrom=function(bE,bD){var bF=[];for(var bC=0;bC=0){bp.connections.splice(bC,1)}};this.getElement=function(){return bo};this.setElement=function(bE){var bG=D(bE);y(az[bf],function(bH){return bH.id==bp.id});bo=A(bE);bf=D(bo);bp.elementId=bf;var bF=ai({source:bG}),bD=a9.getParent(bp.canvas);a9.removeElement(bp.canvas,bD);a9.appendElement(bp.canvas,bF);for(var bC=0;bC0){var bO=bl(bF.elementWithPrecedence),bQ=bO.endpoints[0]==bp?1:0,bH=bQ==0?bO.sourceId:bO.targetId,bN=X[bH],bP=U[bH];bE.txy=[bN.left,bN.top];bE.twh=bP;bE.tElement=bO.endpoints[bQ]}bI=bp.anchor.compute(bE)}var bM=bm.compute(bI,bp.anchor.getOrientation(bm),bp.paintStyleInUse,bG||bp.paintStyleInUse);bm.paint(bM,bp.paintStyleInUse,bp.anchor);bp.timestamp=bL;for(var bJ=0;bJE[0]){return(D[1]>E[1])?2:1}else{return(D[1]>E[1])?3:4}},segmentMultipliers:[null,[1,-1],[1,1],[-1,1],[-1,-1]],inverseSegmentMultipliers:[null,[-1,-1],[-1,1],[1,1],[1,-1]],pointOnLine:function(D,H,E){var G=n.util.gradient(D,H),L=n.util.segment(D,H),K=E>0?n.util.segmentMultipliers[L]:n.util.inverseSegmentMultipliers[L],F=Math.atan(G),I=Math.abs(E*Math.sin(F))*K[1],J=Math.abs(E*Math.cos(F))*K[0];return{x:D.x+J,y:D.y+I}},perpendicularLineTo:function(F,G,H){var E=n.util.gradient(F,G),I=Math.atan(-1/E),J=H/2*Math.sin(I),D=H/2*Math.cos(I);return[{x:G.x+D,y:G.y+J},{x:G.x-D,y:G.y-J}]}};var q=function(D,I,F,E,H,G){return function(K){K=K||{};var J=K.jsPlumbInstance.makeAnchor([D,I,F,E,0,0],K.elementId,K.jsPlumbInstance);J.type=H;if(G){G(J,K)}return J}};n.Anchors.TopCenter=q(0.5,0,0,-1,"TopCenter");n.Anchors.BottomCenter=q(0.5,1,0,1,"BottomCenter");n.Anchors.LeftMiddle=q(0,0.5,-1,0,"LeftMiddle");n.Anchors.RightMiddle=q(1,0.5,1,0,"RightMiddle");n.Anchors.Center=q(0.5,0.5,0,0,"Center");n.Anchors.TopRight=q(1,0,0,-1,"TopRight");n.Anchors.BottomRight=q(1,1,0,1,"BottomRight");n.Anchors.TopLeft=q(0,0,0,-1,"TopLeft");n.Anchors.BottomLeft=q(0,1,0,1,"BottomLeft");n.Defaults.DynamicAnchors=function(D){return D.jsPlumbInstance.makeAnchors(["TopCenter","RightMiddle","BottomCenter","LeftMiddle"],D.elementId,D.jsPlumbInstance)};n.Anchors.AutoDefault=function(E){var D=E.jsPlumbInstance.makeDynamicAnchor(n.Defaults.DynamicAnchors(E));D.type="AutoDefault";return D};n.Anchors.Assign=q(0,0,0,0,"Assign",function(E,F){var D=F.position||"Fixed";E.positionFinder=D.constructor==String?F.jsPlumbInstance.AnchorPositionFinders[D]:D;E.constructorParams=F});n.Anchors.Continuous=function(D){return D.jsPlumbInstance.continuousAnchorFactory.get(D)};n.AnchorPositionFinders={Fixed:function(G,E,F,D){return[(G.left-E.left)/F[0],(G.top-E.top)/F[1]]},Grid:function(D,M,H,E){var L=D.left-M.left,K=D.top-M.top,J=H[0]/(E.constructorParams.grid[0]),I=H[1]/(E.constructorParams.grid[1]),G=Math.floor(L/J),F=Math.floor(K/I);return[((G*J)+(J/2))/H[0],((F*I)+(I/2))/H[1]]}}})();(function(){jsPlumb.DOMElementComponent=function(c){jsPlumb.jsPlumbUIComponent.apply(this,arguments);this.mousemove=this.dblclick=this.click=this.mousedown=this.mouseup=function(d){}};jsPlumb.Connectors.Straight=function(){this.type="Straight";var r=this,i=null,e,k,p,n,l,f,q,h,g,d,c,o,m;this.compute=function(A,J,s,z,F,t,D,v){var I=Math.abs(A[0]-J[0]),C=Math.abs(A[1]-J[1]),B=0.45*I,u=0.45*C;I*=1.9;C*=1.9;var G=Math.min(A[0],J[0])-B;var E=Math.min(A[1],J[1])-u;var H=Math.max(2*D,v);if(In){n=x}if(A<0){f+=A;var C=Math.abs(A);n+=C;p[0]+=C;l+=C;d+=C;o[0]+=C}var K=Math.min(h,c),I=Math.min(p[1],o[1]),w=Math.min(K,I),B=Math.max(h,c),z=Math.max(p[1],o[1]),t=Math.max(B,z);if(t>i){i=t}if(w<0){e+=w;var y=Math.abs(w);i+=y;p[1]+=y;h+=y;c+=y;o[1]+=y}if(G&&n=t){r=u;s=(t-m[u][0])/c[u];break}}return{segment:k[r],proportion:s,index:r}};this.compute=function(Q,ae,r,J,ao,D,O,I,aj,ag){k=[];o=0;c=[];f=ae[0]an?0:1,Y=[1,0][W];G=[];ap=[];G[W]=Q[W]>ae[W]?-1:1;ap[W]=Q[W]>ae[W]?1:-1;G[Y]=0;ap[Y]=0}if(af2*d,R=Math.abs(v-ak)>2*d,ab=T+((E-T)/2),Z=S+((C-S)/2),H=((G[0]*ap[0])+(G[1]*ap[1])),V=H==-1,X=H==0,s=H==1;ad-=B;ac-=z;n=[ad,ac,af,an,A,v,al,ak];var ai=[];g(T,S,A,v,al,ak);var L=G[0]==0?"y":"x",F=V?"opposite":s?"orthogonal":"perpendicular",t=jsPlumb.util.segment([A,v],[al,ak]),aa=G[L=="x"?0:1]==-1,K={x:[null,4,3,2,1],y:[null,2,1,4,3]};if(aa){t=K[L][t]}var N=function(aq,y,w,x){return aq+(y*((1-w)*x)+d)},u={oppositex:function(){if(r.elementId==J.elementId){var w=S+((1-ao.y)*aj.height)+d;return[[T,w],[E,w]]}else{if(P&&(t==1||t==2)){return[[ab,v],[ab,ak]]}else{return[[T,Z],[E,Z]]}}},orthogonalx:function(){if(t==1||t==2){return[[E,S]]}else{return[[T,C]]}},perpendicularx:function(){var w=(ak+v)/2;if((t==1&&ap[1]==1)||(t==2&&ap[1]==-1)){if(Math.abs(al-A)>d){return[[E,S]]}else{return[[T,S],[T,w],[E,w]]}}else{if((t==3&&ap[1]==-1)||(t==4&&ap[1]==1)){return[[T,w],[E,w]]}else{if((t==3&&ap[1]==1)||(t==4&&ap[1]==-1)){return[[T,C]]}else{if((t==1&&ap[1]==-1)||(t==2&&ap[1]==1)){if(Math.abs(al-A)>d){return[[ab,S],[ab,C]]}else{return[[T,C]]}}}}}},oppositey:function(){if(r.elementId==J.elementId){var w=T+((1-ao.x)*aj.width)+d;return[[w,S],[w,C]]}else{if(R&&(t==2||t==3)){return[[A,Z],[al,Z]]}else{return[[ab,S],[ab,C]]}}},orthogonaly:function(){if(t==2||t==3){return[[T,C]]}else{return[[E,S]]}},perpendiculary:function(){var w=(al+A)/2;if((t==2&&ap[0]==-1)||(t==3&&ap[0]==1)){if(Math.abs(al-A)>d){return[[T,C]]}else{return[[T,Z],[E,Z]]}}else{if((t==1&&ap[0]==-1)||(t==4&&ap[0]==1)){var w=(al+A)/2;return[[w,S],[w,C]]}else{if((t==1&&ap[0]==1)||(t==4&&ap[0]==-1)){return[[E,S]]}else{if((t==2&&ap[0]==1)||(t==3&&ap[0]==-1)){if(Math.abs(ak-v)>d){return[[T,Z],[E,Z]]}else{return[[E,S]]}}}}}}};var M=u[F+L];var ah=M();if(ah){for(var am=0;amu[0]?u[0]+((1-y)*t)-z:u[2]+(y*t)+z,y:r==0?u[3]:u[3]>u[1]?u[1]+((1-y)*t)-z:u[3]+(y*t)+z,segmentInfo:w};return x}};jsPlumb.Endpoints.Dot=function(d){this.type="Dot";var c=this;d=d||{};this.radius=d.radius||10;this.defaultOffset=0.5*this.radius;this.defaultInnerRadius=this.radius/3;this.compute=function(i,f,l,h){var g=l.radius||c.radius,e=i[0]-g,k=i[1]-g;return[e,k,g*2,g*2,g]}};jsPlumb.Endpoints.Rectangle=function(d){this.type="Rectangle";var c=this;d=d||{};this.width=d.width||20;this.height=d.height||20;this.compute=function(k,g,m,i){var h=m.width||c.width,f=m.height||c.height,e=k[0]-(h/2),l=k[1]-(f/2);return[e,l,h,f]}};var a=function(e){jsPlumb.DOMElementComponent.apply(this,arguments);var c=this;var d=[];this.getDisplayElements=function(){return d};this.appendDisplayElement=function(f){d.push(f)}};jsPlumb.Endpoints.Image=function(g){this.type="Image";a.apply(this,arguments);var l=this,f=false,e=g.width,d=g.height,i=null,c=g.endpoint;this.img=new Image();l.ready=false;this.img.onload=function(){l.ready=true;e=e||l.img.width;d=d||l.img.height;if(i){i(l)}};c.setImage=function(m,o){var n=m.constructor==String?m:m.src;i=o;l.img.src=m};c.setImage(g.src||g.url,g.onload);this.compute=function(o,m,p,n){l.anchorPoint=o;if(l.ready){return[o[0]-e/2,o[1]-d/2,e,d]}else{return[0,0,0,0]}};l.canvas=document.createElement("img"),f=false;l.canvas.style.margin=0;l.canvas.style.padding=0;l.canvas.style.outline=0;l.canvas.style.position="absolute";var h=g.cssClass?" "+g.cssClass:"";l.canvas.className=jsPlumb.endpointClass+h;if(e){l.canvas.setAttribute("width",e)}if(d){l.canvas.setAttribute("height",d)}jsPlumb.appendElement(l.canvas,g.parent);l.attachListeners(l.canvas,l);var k=function(p,o,n){if(!f){l.canvas.setAttribute("src",l.img.src);f=true}var m=l.anchorPoint[0]-(e/2),q=l.anchorPoint[1]-(d/2);jsPlumb.sizeCanvas(l.canvas,m,q,e,d)};this.paint=function(o,n,m){if(l.ready){k(o,n,m)}else{window.setTimeout(function(){l.paint(o,n,m)},200)}}};jsPlumb.Endpoints.Blank=function(d){var c=this;this.type="Blank";a.apply(this,arguments);this.compute=function(g,e,h,f){return[g[0],g[1],10,0]};c.canvas=document.createElement("div");c.canvas.style.display="block";c.canvas.style.width="1px";c.canvas.style.height="1px";c.canvas.style.background="transparent";c.canvas.style.position="absolute";c.canvas.className=c._jsPlumb.endpointClass;jsPlumb.appendElement(c.canvas,d.parent);this.paint=function(g,f,e){jsPlumb.sizeCanvas(c.canvas,g[0],g[1],g[2],g[3])}};jsPlumb.Endpoints.Triangle=function(c){this.type="Triangle";c=c||{};c.width=c.width||55;c.height=c.height||55;this.width=c.width;this.height=c.height;this.compute=function(i,f,l,h){var g=l.width||self.width,e=l.height||self.height,d=i[0]-(g/2),k=i[1]-(e/2);return[d,k,g,e]}};var b=function(e){var d=true,c=this;this.isAppendedAtTopLevel=true;this.component=e.component;this.loc=e.location==null?0.5:e.location;this.endpointLoc=e.endpointLocation==null?[0.5,0.5]:e.endpointLocation;this.setVisible=function(f){d=f;c.component.repaint()};this.isVisible=function(){return d};this.hide=function(){c.setVisible(false)};this.show=function(){c.setVisible(true)};this.incrementLocation=function(f){c.loc+=f;c.component.repaint()};this.setLocation=function(f){c.loc=f;c.component.repaint()};this.getLocation=function(){return c.loc}};jsPlumb.Overlays.Arrow=function(g){this.type="Arrow";b.apply(this,arguments);this.isAppendedAtTopLevel=false;g=g||{};var d=this;this.length=g.length||20;this.width=g.width||20;this.id=g.id;var f=(g.direction||1)<0?-1:1,e=g.paintStyle||{lineWidth:1},c=g.foldback||0.623;this.computeMaxSize=function(){return d.width*1.5};this.cleanup=function(){};this.draw=function(i,x,s){var m,t,h,n,l;if(i.pointAlongPathFrom){if(d.loc==1){m=i.pointOnPath(d.loc);t=i.pointAlongPathFrom(d.loc,-1);h=jsPlumb.util.pointOnLine(m,t,d.length)}else{if(d.loc==0){h=i.pointOnPath(d.loc);t=i.pointAlongPathFrom(d.loc,1);m=jsPlumb.util.pointOnLine(h,t,d.length)}else{m=i.pointAlongPathFrom(d.loc,f*d.length/2),t=i.pointOnPath(d.loc),h=jsPlumb.util.pointOnLine(m,t,d.length)}}n=jsPlumb.util.perpendicularLineTo(m,h,d.width);l=jsPlumb.util.pointOnLine(m,h,c*d.length);var w=Math.min(m.x,n[0].x,n[1].x),q=Math.max(m.x,n[0].x,n[1].x),v=Math.min(m.y,n[0].y,n[1].y),p=Math.max(m.y,n[0].y,n[1].y);var o={hxy:m,tail:n,cxy:l},r=e.strokeStyle||x.strokeStyle,u=e.fillStyle||x.strokeStyle,k=e.lineWidth||x.lineWidth;d.paint(i,o,k,r,u,s);return[w,q,v,p]}else{return[0,0,0,0]}}};jsPlumb.Overlays.PlainArrow=function(d){d=d||{};var c=jsPlumb.extend(d,{foldback:1});jsPlumb.Overlays.Arrow.call(this,c);this.type="PlainArrow"};jsPlumb.Overlays.Diamond=function(e){e=e||{};var c=e.length||40,d=jsPlumb.extend(e,{length:c/2,foldback:2});jsPlumb.Overlays.Arrow.call(this,d);this.type="Diamond"};jsPlumb.Overlays.Label=function(i){this.type="Label";jsPlumb.DOMElementComponent.apply(this,arguments);b.apply(this,arguments);this.labelStyle=i.labelStyle||jsPlumb.Defaults.LabelStyle;this.id=i.id;this.cachedDimensions=null;var e=i.label||"",c=this,f=false,k=document.createElement("div"),g=null;k.style.position="absolute";var d=i._jsPlumb.overlayClass+" "+(c.labelStyle.cssClass?c.labelStyle.cssClass:i.cssClass?i.cssClass:"");k.className=d;jsPlumb.appendElement(k,i.component.parent);jsPlumb.getId(k);c.attachListeners(k,c);c.canvas=k;var h=c.setVisible;c.setVisible=function(l){h(l);k.style.display=l?"block":"none"};this.getElement=function(){return k};this.cleanup=function(){if(k!=null){jsPlumb.CurrentLibrary.removeElement(k)}};this.setLabel=function(m){e=m;g=null;c.component.repaint()};this.getLabel=function(){return e};this.paint=function(l,n,m){if(!f){l.appendDisplayElement(k);c.attachListeners(k,l);f=true}k.style.left=(m[0]+n.minx)+"px";k.style.top=(m[1]+n.miny)+"px"};this.getTextDimensions=function(){if(typeof e=="function"){var l=e(c);k.innerHTML=l.replace(/\r\n/g,"
")}else{if(g==null){g=e;k.innerHTML=g.replace(/\r\n/g,"
")}}var n=jsPlumb.CurrentLibrary.getElementObject(k),m=jsPlumb.CurrentLibrary.getSize(n);return{width:m[0],height:m[1]}};this.computeMaxSize=function(l){var m=c.getTextDimensions(l);return m.width?Math.max(m.width,m.height)*1.5:0};this.draw=function(m,n,o){var q=c.getTextDimensions(m);if(q.width!=null){var p={x:0,y:0};if(m.pointOnPath){p=m.pointOnPath(c.loc)}else{var l=c.loc.constructor==Array?c.loc:c.endpointLoc;p={x:l[0]*o[2],y:l[1]*o[3]}}minx=p.x-(q.width/2),miny=p.y-(q.height/2);c.paint(m,{minx:minx,miny:miny,td:q,cxy:p},o);return[minx,minx+q.width,miny,miny+q.height]}else{return[0,0,0,0]}};this.reattachListeners=function(l){if(k){c.reattachListenersForElement(k,c,l)}}};jsPlumb.Overlays.GuideLines=function(){var c=this;c.length=50;c.lineWidth=5;this.type="GuideLines";b.apply(this,arguments);jsPlumb.jsPlumbUIComponent.apply(this,arguments);this.draw=function(e,l,k){var i=e.pointAlongPathFrom(c.loc,c.length/2),h=e.pointOnPath(c.loc),g=jsPlumb.util.pointOnLine(i,h,c.length),f=jsPlumb.util.perpendicularLineTo(i,g,40),d=jsPlumb.util.perpendicularLineTo(g,i,20);c.paint(e,[i,g,f,d],c.lineWidth,"red",null,k);return[Math.min(i.x,g.x),Math.min(i.y,g.y),Math.max(i.x,g.x),Math.max(i.y,g.y)]};this.computeMaxSize=function(){return 50};this.cleanup=function(){}}})();(function(){var c=function(e,g,d,f){this.m=(f-g)/(d-e);this.b=-1*((this.m*e)-g);this.rectIntersect=function(q,p,s,o){var n=[];var k=(p-this.b)/this.m;if(k>=q&&k<=(q+s)){n.push([k,(this.m*k)+this.b])}var t=(this.m*(q+s))+this.b;if(t>=p&&t<=(p+o)){n.push([(t-this.b)/this.m,t])}var k=((p+o)-this.b)/this.m;if(k>=q&&k<=(q+s)){n.push([k,(this.m*k)+this.b])}var t=(this.m*q)+this.b;if(t>=p&&t<=(p+o)){n.push([(t-this.b)/this.m,t])}if(n.length==2){var m=(n[0][0]+n[1][0])/2,l=(n[0][1]+n[1][1])/2;n.push([m,l]);var i=m<=q+(s/2)?-1:1,r=l<=p+(o/2)?-1:1;n.push([i,r]);return n}return null}},a=function(e,g,d,f){if(e<=d&&f<=g){return 1}else{if(e<=d&&g<=f){return 2}else{if(d<=e&&f>=g){return 3}}}return 4},b=function(g,f,i,e,h,m,l,d,k){if(d<=k){return[g,f]}if(i==1){if(e[3]<=0&&h[3]>=1){return[g+(e[2]<0.5?-1*m:m),f]}else{if(e[2]>=1&&h[2]<=0){return[g,f+(e[3]<0.5?-1*l:l)]}else{return[g+(-1*m),f+(-1*l)]}}}else{if(i==2){if(e[3]>=1&&h[3]<=0){return[g+(e[2]<0.5?-1*m:m),f]}else{if(e[2]>=1&&h[2]<=0){return[g,f+(e[3]<0.5?-1*l:l)]}else{return[g+(1*m),f+(-1*l)]}}}else{if(i==3){if(e[3]>=1&&h[3]<=0){return[g+(e[2]<0.5?-1*m:m),f]}else{if(e[2]<=0&&h[2]>=1){return[g,f+(e[3]<0.5?-1*l:l)]}else{return[g+(-1*m),f+(-1*l)]}}}else{if(i==4){if(e[3]<=0&&h[3]>=1){return[g+(e[2]<0.5?-1*m:m),f]}else{if(e[2]<=0&&h[2]>=1){return[g,f+(e[3]<0.5?-1*l:l)]}else{return[g+(1*m),f+(-1*l)]}}}}}}};jsPlumb.Connectors.StateMachine=function(l){var s=this,n=null,o,m,g,e,p=[],d=l.curviness||10,k=l.margin||5,q=l.proximityLimit||80,f=l.orientation&&l.orientation=="clockwise",i=l.loopbackRadius||25,h=false;this.type="StateMachine";l=l||{};this.compute=function(ab,F,U,G,aa,u,t,S){var O=Math.abs(ab[0]-F[0]),W=Math.abs(ab[1]-F[1]),Q=0.45*O,Z=0.45*W;O*=1.9;W*=1.9;t=t||1;var M=Math.min(ab[0],F[0])-Q,K=Math.min(ab[1],F[1])-Z;if(U.elementId!=G.elementId){h=false;o=ab[0]0&&v<1){v=1-v}var w=(v*2*Math.PI)+(Math.PI/2),u=n[4]+(n[6]*Math.cos(w)),t=n[5]+(n[6]*Math.sin(w));return{x:u,y:t}}else{return jsBezier.pointOnCurve(r(),v)}};this.gradientAtPoint=function(t){if(h){return Math.atan(t*2*Math.PI)}else{return jsBezier.gradientAtPoint(r(),t)}};this.pointAlongPathFrom=function(v,z){if(h){if(v>0&&v<1){v=1-v}var w=2*Math.PI*n[6],y=z/w*2*Math.PI,x=(v*2*Math.PI)-y+(Math.PI/2),u=n[4]+(n[6]*Math.cos(x)),t=n[5]+(n[6]*Math.sin(x));return{x:u,y:t}}return jsBezier.pointAlongCurveFrom(r(),v,z)}};jsPlumb.Connectors.canvas.StateMachine=function(f){f=f||{};var d=this,g=f.drawGuideline||true,e=f.avoidSelector;jsPlumb.Connectors.StateMachine.apply(this,arguments);jsPlumb.CanvasConnector.apply(this,arguments);this._paint=function(l){if(l.length==10){d.ctx.beginPath();d.ctx.moveTo(l[4],l[5]);d.ctx.quadraticCurveTo(l[8],l[9],l[6],l[7]);d.ctx.stroke()}else{d.ctx.save();d.ctx.beginPath();var k=0,i=2*Math.PI,h=l[7];d.ctx.arc(l[4],l[5],l[6],0,i,h);d.ctx.stroke();d.ctx.closePath();d.ctx.restore()}};this.createGradient=function(i,h){return h.createLinearGradient(i[4],i[5],i[6],i[7])}};jsPlumb.Connectors.svg.StateMachine=function(){var d=this;jsPlumb.Connectors.StateMachine.apply(this,arguments);jsPlumb.SvgConnector.apply(this,arguments);this.getPath=function(e){if(e.length==10){return"M "+e[4]+" "+e[5]+" C "+e[8]+" "+e[9]+" "+e[8]+" "+e[9]+" "+e[6]+" "+e[7]}else{return"M"+(e[8]+4)+" "+e[9]+" A "+e[6]+" "+e[6]+" 0 1,0 "+(e[8]-4)+" "+e[9]}}};jsPlumb.Connectors.vml.StateMachine=function(){jsPlumb.Connectors.StateMachine.apply(this,arguments);jsPlumb.VmlConnector.apply(this,arguments);var d=jsPlumb.vml.convertValue;this.getPath=function(k){if(k.length==10){return"m"+d(k[4])+","+d(k[5])+" c"+d(k[8])+","+d(k[9])+","+d(k[8])+","+d(k[9])+","+d(k[6])+","+d(k[7])+" e"}else{var h=d(k[8]-k[6]),g=d(k[9]-(2*k[6])),f=h+d(2*k[6]),e=g+d(2*k[6]),l=h+","+g+","+f+","+e;var i="ar "+l+","+d(k[8])+","+d(k[9])+","+d(k[8])+","+d(k[9])+" e";return i}}}})();(function(){var k={"stroke-linejoin":"joinstyle",joinstyle:"joinstyle",endcap:"endcap",miterlimit:"miterlimit"};if(document.createStyleSheet){document.createStyleSheet().addRule(".jsplumb_vml","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:textbox","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:oval","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:rect","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:stroke","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:shape","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:group","behavior:url(#default#VML);position:absolute;");document.namespaces.add("jsplumb","urn:schemas-microsoft-com:vml")}jsPlumb.vml={};var b=1000,d={},h=function(q,p){var s=jsPlumb.getId(q),r=d[s];if(!r){r=o("group",[0,0,b,b],{"class":p});r.style.backgroundColor="red";d[s]=r;jsPlumb.appendElement(r,q)}return r},c=function(q,r){for(var p in r){q[p]=r[p]}},o=function(p,r,s){s=s||{};var q=document.createElement("jsplumb:"+p);q.className=(s["class"]?s["class"]+" ":"")+"jsplumb_vml";n(q,r);c(q,s);return q},n=function(q,p){q.style.left=p[0]+"px";q.style.top=p[1]+"px";q.style.width=p[2]+"px";q.style.height=p[3]+"px";q.style.position="absolute"},i=jsPlumb.vml.convertValue=function(p){return Math.floor(p*b)},e=function(s,q,r,p){if("transparent"===q){p.setOpacity(r,"0.0")}else{p.setOpacity(r,"1.0")}},g=function(t,p,w){var s={};if(p.strokeStyle){s.stroked="true";var x=jsPlumb.util.convertStyle(p.strokeStyle,true);s.strokecolor=x;e(s,x,"stroke",w);s.strokeweight=p.lineWidth+"px"}else{s.stroked="false"}if(p.fillStyle){s.filled="true";var q=jsPlumb.util.convertStyle(p.fillStyle,true);s.fillcolor=q;e(s,q,"fill",w)}else{s.filled="false"}if(p.dashstyle){if(w.strokeNode==null){w.strokeNode=o("stroke",[0,0,0,0],{dashstyle:p.dashstyle});t.appendChild(w.strokeNode)}else{w.strokeNode.dashstyle=p.dashstyle}}else{if(p["stroke-dasharray"]&&p.lineWidth){var y=p["stroke-dasharray"].indexOf(",")==-1?" ":",",u=p["stroke-dasharray"].split(y),r="";for(var v=0;v0&&C>0&&u=u&&w[2]<=C&&w[3]>=C)){return true}}var A=q.canvas.getContext("2d").getImageData(parseInt(u),parseInt(C),1,1);return A.data[0]!=0||A.data[1]!=0||A.data[2]!=0||A.data[3]!=0}return false};var p=false,o=false,t=null,s=false,r=function(v,u){return v!=null&&i(v,u)};this.mousemove=function(x){var z=n(x),w=f(x),v=document.elementFromPoint(w[0],w[1]),y=r(v,"_jsPlumb_overlay");var u=d==null&&(r(v,"_jsPlumb_endpoint")||r(v,"_jsPlumb_connector"));if(!p&&u&&q._over(x)){p=true;q.fire("mouseenter",q,x);return true}else{if(p&&(!q._over(x)||!u)&&!y){p=false;q.fire("mouseexit",q,x)}}q.fire("mousemove",q,x)};this.click=function(u){if(p&&q._over(u)&&!s){q.fire("click",q,u)}s=false};this.dblclick=function(u){if(p&&q._over(u)&&!s){q.fire("dblclick",q,u)}s=false};this.mousedown=function(u){if(q._over(u)&&!o){o=true;t=m(a(q.canvas));q.fire("mousedown",q,u)}};this.mouseup=function(u){o=false;q.fire("mouseup",q,u)};this.contextmenu=function(u){if(p&&q._over(u)&&!s){q.fire("contextmenu",q,u)}s=false}};var c=function(p){var o=document.createElement("canvas");jsPlumb.appendElement(o,p.parent);o.style.position="absolute";if(p["class"]){o.className=p["class"]}p._jsPlumb.getId(o,p.uuid);if(p.tooltip){o.setAttribute("title",p.tooltip)}return o};var l=function(p){k.apply(this,arguments);var o=[];this.getDisplayElements=function(){return o};this.appendDisplayElement=function(q){o.push(q)}};var h=jsPlumb.CanvasConnector=function(r){l.apply(this,arguments);var o=function(v,t){p.ctx.save();jsPlumb.extend(p.ctx,t);if(t.gradient){var u=p.createGradient(v,p.ctx);for(var s=0;sq?q=s:sl.location){l.location=0}return i(m,l.location)},nearestPointOnCurve:function(m,l){var n=h(m,l);return{point:a(l,l.length-1,n.location,null,null),location:n.location}},pointOnCurve:c,pointAlongCurveFrom:function(m,l,n){return k(m,l,n).point},perpendicularToCurveAt:function(m,l,n,o){l=k(m,l,null==o?0:o);m=i(m,l.location);o=Math.atan(-1/m);m=n/2*Math.sin(o);n=n/2*Math.cos(o);return[{x:l.point.x+n,y:l.point.y+m},{x:l.point.x-n,y:l.point.y-m}]}}})(); \ No newline at end of file diff --git a/archive/1.3.5/yui.jsPlumb-1.3.5-all.js b/archive/1.3.5/yui.jsPlumb-1.3.5-all.js new file mode 100644 index 000000000..38968ab7f --- /dev/null +++ b/archive/1.3.5/yui.jsPlumb-1.3.5-all.js @@ -0,0 +1,8283 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the jsPlumb core code. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + /** + * Class:jsPlumb + * The jsPlumb engine, registered as a static object in the window. This object contains all of the methods you will use to + * create and maintain Connections and Endpoints. + */ + + var canvasAvailable = !!document.createElement('canvas').getContext, + svgAvailable = !!window.SVGAngle || document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"), + // TODO what is a good test for VML availability? aside from just assuming its there because nothing else is. + vmlAvailable = !(canvasAvailable | svgAvailable); + + // finds the index of some value in an array, using a deep equals test. + var _findIndex = function(a, v, b, s) { + var _eq = function(o1, o2) { + if (o1 === o2) return true; + else if (typeof o1 == "object" && typeof o2 == "object") { + var same = true; + for ( var propertyName in o1) { + if (!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for ( var propertyName in o2) { + if (!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for ( var i = +b || 0, l = a.length; i < l; i++) { + if (_eq(a[i], v)) + return i; + } + return -1; + }, + _findWithFunction = function(a, f) { + if (a) + for (var i = 0; i < a.length; i++) if (f(a[i])) return i; + return -1; + }, + _removeWithFunction = function(a, f) { + var idx = _findWithFunction(a, f); + if (idx > -1) a.splice(idx, 1); + return idx != -1; + }, + // TODO support insert index + _addWithFunction = function(list, item, hashFunction) { + if (_findWithFunction(list, hashFunction) == -1) list.push(item); + }; + + + // for those browsers that dont have it. they still don't have it! but at least they won't crash. + if (!window.console) + window.console = { time:function(){}, timeEnd:function(){}, group:function(){}, groupEnd:function(){}, log:function(){} }; + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = [], map[key] = l; + } + l.push(value); + return l; + }, + _connectionBeingDragged = null, + _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }, + _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }, + _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }, + _logEnabled = true, + _log = function() { + if (_logEnabled && typeof console != "undefined") { + try { + var msg = arguments[arguments.length - 1]; + console.log(msg); + } + catch (e) {} + } + }, + _group = function(g) { + if (_logEnabled && typeof console != "undefined") console.group(g); + }, + _groupEnd = function(g) { + if (_logEnabled && typeof console != "undefined") console.groupEnd(g); + }, + _time = function(t) { + if (_logEnabled && typeof console != "undefined") console.time(t); + }, + _timeEnd = function(t) { + if (_logEnabled && typeof console != "undefined") console.timeEnd(t); + }; + + /** + * EventGenerator + * Superclass for objects that generate events - jsPlumb extends this, as does jsPlumbUIComponent, which all the UI elements extend. + */ + EventGenerator = function() { + var _listeners = {}, self = this; + + // this is a list of events that should re-throw any errors that occur during their dispatch. as of 1.3.0 this is private to + // jsPlumb, but it seems feasible that people might want to manipulate this list. the thinking is that we don't want event + // listeners to bring down jsPlumb - or do we. i can't make up my mind about this, but i know i want to hear about it if the "ready" + // event fails, because then my page has most likely not initialised. so i have this halfway-house solution. it will be interesting + // to hear what other people think. + var eventsToDieOn = [ "ready" ]; + + /* + * Binds a listener to an event. + * + * Parameters: + * event - name of the event to bind to. + * listener - function to execute. + */ + this.bind = function(event, listener) { + _addToList(_listeners, event, listener); + }; + /* + * Fires an update for the given event. + * + * Parameters: + * event - event to fire + * value - value to pass to the event listener(s). + * originalEvent - the original event from the browser + */ + this.fire = function(event, value, originalEvent) { + if (_listeners[event]) { + for ( var i = 0; i < _listeners[event].length; i++) { + // doing it this way rather than catching and then possibly re-throwing means that an error propagated by this + // method will have the whole call stack available in the debugger. + if (_findIndex(eventsToDieOn, event) != -1) + _listeners[event][i](value, originalEvent); + else { + // for events we don't want to die on, catch and log. + try { + _listeners[event][i](value, originalEvent); + } catch (e) { + _log("jsPlumb: fire failed for event " + event + " : " + e); + } + } + } + } + }; + /* + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + this.clearListeners = function(event) { + if (event) + delete _listeners[event]; + else { + delete _listeners; + _listeners = {}; + } + }; + + this.getListener = function(forEvent) { + return _listeners[forEvent]; + }; + }, + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + _timestamp = function() { return "" + (new Date()).getTime(); }, + + /* + * Class:jsPlumbUIComponent + * Abstract superclass for UI components Endpoint and Connection. Provides the abstraction of paintStyle/hoverPaintStyle, + * and also extends EventGenerator to provide the bind and fire methods. + */ + jsPlumbUIComponent = function(params) { + var self = this, a = arguments, _hover = false, parameters = params.parameters || {}, idPrefix = self.idPrefix, + id = idPrefix + (new Date()).getTime(); + self._jsPlumb = params["_jsPlumb"]; + self.getId = function() { return id; }; + self.tooltip = params.tooltip; + self.hoverClass = params.hoverClass; + + // all components can generate events + EventGenerator.apply(this); + // all components get this clone function. + // TODO issue 116 showed a problem with this - it seems 'a' that is in + // the clone function's scope is shared by all invocations of it, the classic + // JS closure problem. for now, jsPlumb does a version of this inline where + // it used to call clone. but it would be nice to find some time to look + // further at this. + this.clone = function() { + var o = new Object(); + self.constructor.apply(o, a); + return o; + }; + + this.getParameter = function(name) { return parameters[name]; }, + this.getParameters = function() { return parameters; }, + this.setParameter = function(name, value) { parameters[name] = value; }, + this.setParameters = function(p) { parameters = p; }, + this.overlayPlacements = [], + this.paintStyle = null, + this.hoverPaintStyle = null; + + // user can supply a beforeDetach callback, which will be executed before a detach + // is performed; returning false prevents the detach. + var beforeDetach = params.beforeDetach; + this.isDetachAllowed = function(connection) { + var r = true; + if (beforeDetach) { + try { + r = beforeDetach(connection); + } + catch (e) { _log("jsPlumb: beforeDetach callback failed", e); } + } + return r; + }; + + // user can supply a beforeDrop callback, which will be executed before a dropped + // connection is confirmed. user can return false to reject connection. + var beforeDrop = params.beforeDrop; + this.isDropAllowed = function(sourceId, targetId, scope) { + var r = self._jsPlumb.checkCondition("beforeDrop", { sourceId:sourceId, targetId:targetId, scope:scope }); + if (beforeDrop) { + try { + r = beforeDrop({ sourceId:sourceId, targetId:targetId, scope:scope }); + } + catch (e) { _log("jsPlumb: beforeDrop callback failed", e); } + } + return r; + }; + + // helper method to update the hover style whenever it, or paintStyle, changes. + // we use paintStyle as the foundation and merge hoverPaintStyle over the + // top. + var _updateHoverStyle = function() { + if (self.paintStyle && self.hoverPaintStyle) { + var mergedHoverStyle = {}; + jsPlumb.extend(mergedHoverStyle, self.paintStyle); + jsPlumb.extend(mergedHoverStyle, self.hoverPaintStyle); + delete self["hoverPaintStyle"]; + // we want the fillStyle of paintStyle to override a gradient, if possible. + if (mergedHoverStyle.gradient && self.paintStyle.fillStyle) + delete mergedHoverStyle["gradient"]; + self.hoverPaintStyle = mergedHoverStyle; + } + }; + + /* + * Sets the paint style and then repaints the element. + * + * Parameters: + * style - Style to use. + */ + this.setPaintStyle = function(style, doNotRepaint) { + self.paintStyle = style; + self.paintStyleInUse = self.paintStyle; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /* + * Sets the paint style to use when the mouse is hovering over the element. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the component will not be repainted. useful when setting things up initially. + */ + this.setHoverPaintStyle = function(style, doNotRepaint) { + self.hoverPaintStyle = style; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /* + * sets/unsets the hover state of this element. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + this.setHover = function(hover, ignoreAttachedElements, timestamp) { + // while dragging, we ignore these events. this keeps the UI from flashing and + // swishing and whatevering. + if (!self._jsPlumb.currentlyDragging && !self._jsPlumb.isHoverSuspended()) { + + _hover = hover; + if (self.hoverClass != null && self.canvas != null) { + if (hover) + jpcl.addClass(self.canvas, self.hoverClass); + else + jpcl.removeClass(self.canvas, self.hoverClass); + } + if (self.hoverPaintStyle != null) { + self.paintStyleInUse = hover ? self.hoverPaintStyle : self.paintStyle; + timestamp = timestamp || _timestamp(); + self.repaint({timestamp:timestamp, recalc:false}); + } + // get the list of other affected elements, if supported by this component. + // for a connection, its the endpoints. for an endpoint, its the connections! surprise. + if (self.getAttachedElements && !ignoreAttachedElements) + _updateAttachedElements(hover, _timestamp(), self); + } + }; + + this.isHover = function() { return _hover; }; + + var jpcl = jsPlumb.CurrentLibrary, + events = [ "click", "dblclick", "mouseenter", "mouseout", "mousemove", "mousedown", "mouseup", "contextmenu" ], + eventFilters = { "mouseout":"mouseexit" }, + bindOne = function(o, c, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.bind(o, evt, function(ee) { + c.fire(filteredEvent, c, ee); + }); + }, + unbindOne = function(o, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.unbind(o, evt); + }; + + this.attachListeners = function(o, c) { + for (var i = 0; i < events.length; i++) { + bindOne(o, c, events[i]); + } + }; + + var _updateAttachedElements = function(state, timestamp, sourceElement) { + var affectedElements = self.getAttachedElements(); // implemented in subclasses + if (affectedElements) { + for (var i = 0; i < affectedElements.length; i++) { + if (!sourceElement || sourceElement != affectedElements[i]) + affectedElements[i].setHover(state, true, timestamp); // tell the attached elements not to inform their own attached elements. + } + } + }; + + this.reattachListenersForElement = function(o) { + if (arguments.length > 1) { + for (var i = 0; i < events.length; i++) + unbindOne(o, events[i]); + for (var i = 1; i < arguments.length; i++) + self.attachListeners(o, arguments[i]); + } + }; + }, + + overlayCapableJsPlumbUIComponent = function(params) { + jsPlumbUIComponent.apply(this, arguments); + var self = this; + /* + * Property: overlays + * List of Overlays for this component. + */ + this.overlays = []; + + var processOverlay = function(o) { + var _newOverlay = null; + if (o.constructor == Array) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0], + // make a copy of the object so as not to mess up anyone else's reference... + p = jsPlumb.extend({component:self, _jsPlumb:self._jsPlumb}, o[1]); + if (o.length == 3) jsPlumb.extend(p, o[2]); + _newOverlay = new jsPlumb.Overlays[self._jsPlumb.getRenderMode()][type](p); + if (p.events) { + for (var evt in p.events) { + _newOverlay.bind(evt, p.events[evt]); + } + } + } else if (o.constructor == String) { + _newOverlay = new jsPlumb.Overlays[self._jsPlumb.getRenderMode()][o]({component:self, _jsPlumb:self._jsPlumb}); + } else { + _newOverlay = o; + } + + self.overlays.push(_newOverlay); + }, + calculateOverlaysToAdd = function(params) { + var defaultKeys = self.defaultOverlayKeys || [], + o = params.overlays, + checkKey = function(k) { + return self._jsPlumb.Defaults[k] || jsPlumb.Defaults[k] || []; + }; + + if (!o) o = []; + + for (var i = 0; i < defaultKeys.length; i++) + o.unshift.apply(o, checkKey(defaultKeys[i])); + + return o; + } + + var _overlays = calculateOverlaysToAdd(params);//params.overlays || self._jsPlumb.Defaults.Overlays; + if (_overlays) { + for (var i = 0; i < _overlays.length; i++) { + processOverlay(_overlays[i]); + } + } + + // overlay finder helper method + var _getOverlayIndex = function(id) { + var idx = -1; + for (var i = 0; i < self.overlays.length; i++) { + if (id === self.overlays[i].id) { + idx = i; + break; + } + } + return idx; + }; + + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + this.addOverlay = function(overlay) { + processOverlay(overlay); + self.repaint(); + }; + + /* + * Function: getOverlay + * Gets an overlay, by ID. Note: by ID. You would pass an 'id' parameter + * in to the Overlay's constructor arguments, and then use that to retrieve + * it via this method. + */ + this.getOverlay = function(id) { + var idx = _getOverlayIndex(id); + return idx >= 0 ? self.overlays[idx] : null; + }; + + /* + * Function: hideOverlay + * Hides the overlay specified by the given id. + */ + this.hideOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.hide(); + }; + + /* + * Function: showOverlay + * Shows the overlay specified by the given id. + */ + this.showOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.show(); + }; + + /** + * Function: removeAllOverlays + * Removes all overlays from the Connection, and then repaints. + */ + this.removeAllOverlays = function() { + self.overlays.splice(0, self.overlays.length); + self.repaint(); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + this.removeOverlay = function(overlayId) { + var idx = _getOverlayIndex(overlayId); + if (idx != -1) { + var o = self.overlays[idx]; + o.cleanup(); + self.overlays.splice(idx, 1); + } + }; + + /** + * Function:removeOverlays + * Removes a set of overlays by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + this.removeOverlays = function() { + for (var i = 0; i < arguments.length; i++) + self.removeOverlay(arguments[i]); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + var _internalLabelOverlayId = "__label", + _makeLabelOverlay = function(params) { + + var _params = { + cssClass:params.cssClass, + labelStyle : this.labelStyle, + id:_internalLabelOverlayId, + component:self, + _jsPlumb:self._jsPlumb + }, + mergedParams = jsPlumb.extend(_params, params); + + return new jsPlumb.Overlays[self._jsPlumb.getRenderMode()].Label( mergedParams ); + }; + if (params.label) { + var loc = params.labelLocation || self.defaultLabelLocation || 0.5, + labelStyle = params.labelStyle || self._jsPlumb.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.overlays.push(_makeLabelOverlay({ + label:params.label, + location:loc, + labelStyle:labelStyle + })); + } + + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String, a Function that returns a String, or a params object containing { "label", "labelStyle", "location", "cssClass" } + */ + this.setLabel = function(l) { + var lo = self.getOverlay(_internalLabelOverlayId); + if (!lo) { + var params = l.constructor == String || l.constructor == Function ? { label:l } : l; + lo = _makeLabelOverlay(params); + this.overlays.push(lo); + } + else { + if (l.constructor == String || l.constructor == Function) lo.setLabel(l); + else { + if (l.label) lo.setLabel(l.label); + if (l.location) lo.setLocation(l.location); + } + } + + self.repaint(); + }; + + /* + Function:getLabel + Returns the label text for this component (or a function if you are labelling with a function). + This does not return the overlay itself; this is a convenience method which is a pair with + setLabel; together they allow you to add and access a Label Overlay without having to create the + Overlay object itself. For access to the underlying label overlay that jsPlumb has created, + use getLabelOverlay. + */ + this.getLabel = function() { + var lo = self.getOverlay(_internalLabelOverlayId); + return lo != null ? lo.getLabel() : null; + }; + + /* + Function:getLabelOverlay + Returns the underlying internal label overlay, which will exist if you specified a label on + a connect or addEndpoint call, or have called setLabel at any stage. + */ + this.getLabelOverlay = function() { + return self.getOverlay(_internalLabelOverlayId); + } + }, + + _bindListeners = function(obj, _self, _hoverFunction) { + obj.bind("click", function(ep, e) { _self.fire("click", _self, e); }); + obj.bind("dblclick", function(ep, e) { _self.fire("dblclick", _self, e); }); + obj.bind("contextmenu", function(ep, e) { _self.fire("contextmenu", _self, e); }); + // this method is different; endpoint delegates hover to the first connection if there is one. + // that allows the connection to notify all its endpoint and avoids a circular loop + obj.bind("mouseenter", function(ep, e) { + if (!_self.isHover()) { + _hoverFunction(true); + _self.fire("mouseenter", _self, e); + } + }); + // this method is different; endpoint delegates hover to the first connection if there is one. + // that allows the connection to notify all its endpoint and avoids a circular loop + obj.bind("mouseexit", function(ep, e) { + if (_self.isHover()) { + _hoverFunction(false); + _self.fire("mouseexit", self, e); + } + }); + }; + + var _jsPlumbInstanceIndex = 0, + getInstanceIndex = function() { + var i = _jsPlumbInstanceIndex + 1; + _jsPlumbInstanceIndex++; + return i; + }; + + var jsPlumbInstance = function(_defaults) { + + /* + * Property: Defaults + * + * These are the default settings for jsPlumb. They are what will be used if you do not supply specific pieces of information + * to the various API calls. A convenient way to implement your own look and feel can be to override these defaults + * by including a script somewhere after the jsPlumb include, but before you make any calls to jsPlumb. + * + * Properties: + * - *Anchor* The default anchor to use for all connections (both source and target). Default is "BottomCenter". + * - *Anchors* The default anchors to use ([source, target]) for all connections. Defaults are ["BottomCenter", "BottomCenter"]. + * - *Connector* The default connector definition to use for all connections. Default is "Bezier". + * - *Container* Optional selector or element id that instructs jsPlumb to append elements it creates to a specific element. + * - *DragOptions* The default drag options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *DropOptions* The default drop options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *Endpoint* The default endpoint definition to use for all connections (both source and target). Default is "Dot". + * - *Endpoints* The default endpoint definitions ([ source, target ]) to use for all connections. Defaults are ["Dot", "Dot"]. + * - *EndpointStyle* The default style definition to use for all endpoints. Default is fillStyle:"#456". + * - *EndpointStyles* The default style definitions ([ source, target ]) to use for all endpoints. Defaults are empty. + * - *EndpointHoverStyle* The default hover style definition to use for all endpoints. Default is null. + * - *EndpointHoverStyles* The default hover style definitions ([ source, target ]) to use for all endpoints. Defaults are null. + * - *HoverPaintStyle* The default hover style definition to use for all connections. Defaults are null. + * - *LabelStyle* The default style to use for label overlays on connections. + * - *LogEnabled* Whether or not the jsPlumb log is enabled. defaults to false. + * - *Overlays* The default overlay definitions. Defaults to an empty list. + * - *MaxConnections* The default maximum number of connections for an Endpoint. Defaults to 1. + * - *MouseEventsEnabled* Whether or not mouse events are enabled when using the canvas renderer. Defaults to true. + * The idea of this is just to give people a way to prevent all the mouse listeners from activating if they know they won't need mouse events. + * - *PaintStyle* The default paint style for a connection. Default is line width of 8 pixels, with color "#456". + * - *RenderMode* What mode to use to paint with. If you're on IE<9, you don't really get to choose this. You'll just get VML. Otherwise, the jsPlumb default is to use Canvas elements. + * - *Scope* The default "scope" to use for connections. Scope lets you assign connections to different categories. + */ + this.Defaults = { + Anchor : "BottomCenter", + Anchors : [ null, null ], + ConnectionsDetachable : true, + Connector : "Bezier", + Container : null, + DragOptions : { }, + DropOptions : { }, + Endpoint : "Dot", + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : "#456" }, + EndpointStyles : [ null, null ], + EndpointHoverStyle : null, + EndpointHoverStyles : [ null, null ], + HoverPaintStyle : null, + LabelStyle : { color : "black" }, + LogEnabled : false, + Overlays : [ ], + MaxConnections : 1, + MouseEventsEnabled : true, + PaintStyle : { lineWidth : 8, strokeStyle : "#456" }, + //Reattach:false, + RenderMode : "svg", + Scope : "jsPlumb_DefaultScope" + }; + if (_defaults) jsPlumb.extend(this.Defaults, _defaults); + + this.logEnabled = this.Defaults.LogEnabled; + + EventGenerator.apply(this); + var _bb = this.bind; + this.bind = function(event, fn) { + if ("ready" === event && initialized) fn(); + else _bb(event, fn); + }; + var _currentInstance = this, + _instanceIndex = getInstanceIndex(), + + log = null, + repaintFunction = function() { + jsPlumb.repaintEverything(); + }, + automaticRepaint = true, + repaintEverything = function() { + if (automaticRepaint) + repaintFunction(); + }, + resizeTimer = null, + initialized = false, + connectionsByScope = {}, + /** + * map of element id -> endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + endpointsByElement = {}, + endpointsByUUID = {}, + offsets = {}, + offsetTimestamps = {}, + floatingConnections = {}, + draggableStates = {}, + _mouseEventsEnabled = this.Defaults.MouseEventsEnabled, + canvasList = [], + sizes = [], + //listeners = {}, // a map: keys are event types, values are lists of listeners. + DEFAULT_SCOPE = this.Defaults.Scope, + renderMode = null, // will be set in init() + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }, + + /** + * appends an element to some other element, which is calculated as follows: + * + * 1. if _currentInstance.Defaults.Container exists, use that element. + * 2. if the 'parent' parameter exists, use that. + * 3. otherwise just use the document body. + * + */ + _appendElement = function(el, parent) { + if (_currentInstance.Defaults.Container) + jsPlumb.CurrentLibrary.appendElement(el, _currentInstance.Defaults.Container); + else if (!parent) + document.body.appendChild(el); + else + jsPlumb.CurrentLibrary.appendElement(el, parent); + }, + + _curIdStamp = 1, + _idstamp = function() { return "" + _curIdStamp++; }, + + /** + * YUI, for some reason, put the result of a Y.all call into an object that contains + * a '_nodes' array, instead of handing back an array-like object like the other + * libraries do. + */ + _convertYUICollection = function(c) { + return c._nodes ? c._nodes : c; + }, + + _suspendDrawing = false, + /* + sets whether or not to suspend drawing. you should use this if you need to connect a whole load of things in one go. + it will save you a lot of time. + */ + _setSuspendDrawing = function(val, repaintAfterwards) { + _suspendDrawing = val; + if (repaintAfterwards) _currentInstance.repaintEverything(); + }, + + /** + * Draws an endpoint and its connections. this is the main entry point into drawing connections as well + * as endpoints, since jsPlumb is endpoint-centric under the hood. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + _draw = function(element, ui, timestamp) { + if (!_suspendDrawing) { + var id = _getAttribute(element, "id"); + _currentInstance.anchorManager.redraw(id, ui, timestamp); + } + }, + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }, + + /** + * gets an Endpoint by uuid. + */ + _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }, + + /** + * inits a draggable if it's not already initialised. + */ + _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? false : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element) && !jsPlumb.CurrentLibrary.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents["drag"], + stopEvent = jsPlumb.CurrentLibrary.dragEvents["stop"]; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + draggableStates[_getId(element)] = true; + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options, false); + } + } + }, + + /* + * prepares a final params object that can be passed to _newConnection, taking into account defaults, events, etc. + */ + _prepareConnectionParams = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + // copy in any connectorOverlays that were specified on the source endpoint. + // it doesnt copy target endpoint overlays. i'm not sure if we want it to or not. + if (_p.sourceEndpoint && _p.sourceEndpoint.connectorOverlays) { + _p.overlays = _p.overlays || []; + for (var i = 0; i < _p.sourceEndpoint.connectorOverlays.length; i++) { + _p.overlays.push(_p.sourceEndpoint.connectorOverlays[i]); + } + } + + // tooltip. params.tooltip takes precedence, then sourceEndpoint.connectorTooltip. + _p.tooltip = params.tooltip; + if (!_p.tooltip && _p.sourceEndpoint && _p.sourceEndpoint.connectorTooltip) + _p.tooltip = _p.sourceEndpoint.connectorTooltip; + + if (_p.target && !_p.target.endpoint) { + var tid = _getId(_p.target), + tep =_targetEndpointDefinitions[tid]; + + var overrideOne = function(singlePropertyName, pluralPropertyName, tepProperty, tep) { + if (tep[tepProperty]) { + if (_p[pluralPropertyName]) _p[pluralPropertyName][1] = tep[tepProperty]; + else if (_p[singlePropertyName]) { + _p[pluralPropertyName] = [ _p[singlePropertyName], tep[tepProperty] ]; + _p[singlePropertyName] = null; + } + else _p[pluralPropertyName] = [ null, tep[tepProperty] ]; + } + }; + + if (tep) { + overrideOne("endpoint", "endpoints", "endpoint", tep); + overrideOne("endpointStyle", "endpointStyles", "paintStyle", tep); + overrideOne("endpointHoverStyle", "endpointHoverStyles", "hoverPaintStyle", tep); + } + } + + return _p; + }, + + _newConnection = function(params) { + var connectionFunc = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + endpointFunc = _currentInstance.Defaults.EndpointType || Endpoint, + parent = jsPlumb.CurrentLibrary.getParent; + + if (params.container) + params["parent"] = params.container; + else { + if (params.sourceEndpoint) + params["parent"] = params.sourceEndpoint.parent; + else if (params.source.constructor == endpointFunc) + params["parent"] = params.source.parent; + else params["parent"] = parent(params.source); + } + + params["_jsPlumb"] = _currentInstance; + var con = new connectionFunc(params); + con.id = "con_" + _idstamp(); + _eventFireProxy("click", "click", con); + _eventFireProxy("dblclick", "dblclick", con); + _eventFireProxy("contextmenu", "contextmenu", con); + return con; + }, + + /** + * adds the connection to the backing model, fires an event if necessary and then redraws + */ + _finaliseConnection = function(jpc, params, originalEvent) { + params = params || {}; + // add to list of connections (by scope). + if (!jpc.suspendedEndpoint) + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + if (!params.doNotFireConnectionEvent && params.fireEvent !== false) { + _currentInstance.fire("jsPlumbConnection", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }, originalEvent); + } + // always inform the anchor manager + // except that if jpc has a suspended endpoint it's not true to say the + // connection is new; it has just (possibly) moved. the question is whether + // to make that call here or in the anchor manager. i think perhaps here. + _currentInstance.anchorManager.newConnection(jpc); + // force a paint + _draw(jpc.source); + }, + + _eventFireProxy = function(event, proxyEvent, obj) { + obj.bind(event, function(originalObject, originalEvent) { + _currentInstance.fire(proxyEvent, obj, originalEvent); + }); + }, + + /** + * for the given endpoint params, returns an appropriate parent element for the UI elements that will be added. + * this function is used by _newEndpoint (directly below), and also in the makeSource function in jsPlumb. + * + * the logic is to first look for a "container" member of params, and pass that back if found. otherwise we + * handoff to the 'getParent' function in the current library. + */ + _getParentFromParams = function(params) { + if (params.container) + return params.container; + else { + var tag = jsPlumb.CurrentLibrary.getTagName(params.source), + p = jsPlumb.CurrentLibrary.getParent(params.source); + if (tag && tag.toLowerCase() === "td") + return jsPlumb.CurrentLibrary.getParent(p); + else return p; + } + }, + + /** + factory method to prepare a new endpoint. this should always be used instead of creating Endpoints + manually, since this method attaches event listeners and an id. + */ + _newEndpoint = function(params) { + var endpointFunc = _currentInstance.Defaults.EndpointType || Endpoint; + params.parent = _getParentFromParams(params); + params["_jsPlumb"] = _currentInstance; + var ep = new endpointFunc(params); + ep.id = "ep_" + _idstamp(); + _eventFireProxy("click", "endpointClick", ep); + _eventFireProxy("dblclick", "endpointDblClick", ep); + _eventFireProxy("contextmenu", "contextmenu", ep); + return ep; + }, + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + _operation = function(elId, func, endpointFunc) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + if (endpointFunc) endpointFunc(endpoints[i]); + } + } + }, + /** + * perform an operation on all elements. + */ + _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }, + + /** + * helper to remove an element from the DOM. + */ + _removeElement = function(element, parent) { + if (element != null && element.parentNode != null) { + element.parentNode.removeChild(element); + } + }, + /** + * helper to remove a list of elements from the DOM. + */ + _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }, + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }, + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + _setVisible = function(el, state, alsoChangeEndpoints) { + state = state === "block"; + var endpointFunc = null; + if (alsoChangeEndpoints) { + if (state) endpointFunc = function(ep) { + ep.setVisible(true, true, true); + }; + else endpointFunc = function(ep) { + ep.setVisible(false, true, true); + }; + } + var id = _getAttribute(el, "id"); + _operation(id, function(jpc) { + if (state && alsoChangeEndpoints) { + // this test is necessary because this functionality is new, and i wanted to maintain backwards compatibility. + // this block will only set a connection to be visible if the other endpoint in the connection is also visible. + var oidx = jpc.sourceId === id ? 1 : 0; + if (jpc.endpoints[oidx].isVisible()) jpc.setVisible(true); + } + else // the default behaviour for show, and what always happens for hide, is to just set the visibility without getting clever. + jpc.setVisible(state); + }, endpointFunc); + }, + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? false : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }, + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + _toggleVisible = function(elId, changeEndpoints) { + var endpointFunc = null; + if (changeEndpoints) { + endpointFunc = function(ep) { + var state = ep.isVisible(); + ep.setVisible(!state); + }; + } + _operation(elId, function(jpc) { + var state = jpc.isVisible(); + jpc.setVisible(!state); + }, endpointFunc); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }, + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return offsets[elId]; + } + if (recalc || !offset) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + if (sizes[elId] == null) { + var s = _getElementObject(elId); + if (s != null) + sizes[elId] = _getSize(s); + } + } + + if(offsets[elId] && !offsets[elId].right) { + offsets[elId].right = offsets[elId].left + sizes[elId][0]; + offsets[elId].bottom = offsets[elId].top + sizes[elId][1]; + offsets[elId].width = sizes[elId][0]; + offsets[elId].height = sizes[elId][1]; + offsets[elId].centerx = offsets[elId].left + (offsets[elId].width / 2); + offsets[elId].centery = offsets[elId].top + (offsets[elId].height / 2); + } + return offsets[elId]; + }, + + // TODO comparison performance + _getCachedData = function(elId) { + var o = offsets[elId]; + if (!o) o = _updateOffset({elId:elId}); + return {o:o, s:sizes[elId]}; + }, + + /** + * gets an id for the given element, creating and setting one if + * necessary. the id is of the form + * + * jsPlumb__ + * + * where "index in instance" is a monotonically increasing integer that starts at 0, + * for each instance. this method is used not only to assign ids to elements that do not + * have them but also to connections and endpoints. + */ + _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else + id = "jsPlumb_" + _instanceIndex + "_" + _idstamp(); + _setAttribute(ele, "id", id); + } + return id; + }, + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'jsPlumb function failed : ' + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'wrapped function failed : ' + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = "_jsPlumb_connector"; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = "_jsPlumb_endpoint"; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = "_jsPlumb_overlay"; + + this.Anchors = {}; + + this.Connectors = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Endpoints = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Overlays = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + +// ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***************************************** + /* + * Function: bind + * Bind to an event on jsPlumb. + * + * Parameters: + * event - the event to bind. Available events on jsPlumb are: + * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback. + * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback. + * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback. + * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback. + * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback. + * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback. + * + * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event. + */ + + /* + * Function: clearListeners + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + +// *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *********************************************************** + + /* + Function: addClass + + Helper method to abstract out differences in setting css classes on the different renderer types. + */ + this.addClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.addClass(el, clazz); + }; + + /* + Function: removeClass + + Helper method to abstract out differences in setting css classes on the different renderer types. + */ + this.removeClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.removeClass(el, clazz); + }; + + /* + Function: hasClass + + Helper method to abstract out differences in testing for css classes on the different renderer types. + */ + this.hasClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.hasClass(el, clazz); + }; + + /* + Function: addEndpoint + + Adds an to a given element or elements. + + Parameters: + + el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + params - Object containing Endpoint constructor arguments. For more information, see . + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some + shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in + this object are anything that 'params' can contain. See . + + Returns: + The newly created , if el referred to a single element. Otherwise, an array of newly created s. + + See Also: + + */ + this.addEndpoint = function(el, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // YUI wrapper + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + var _el = _getElementObject(inputs[i]), id = _getId(_el); + p.source = _el; + _updateOffset({ elId : id }); + var e = _newEndpoint(p); + if (p.parentAnchor) e.parentAnchor = p.parentAnchor; + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + results.push(e); + } + + return results.length == 1 ? results[0] : results; + }; + + /* + Function: addEndpoints + Adds a list of s to a given element or elements. + + Parameters: + target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See 's constructor documentation. + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created s, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(el, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams); + if (e.constructor == Array) + Array.prototype.push.apply(results, e); + else results.push(e); + } + return results; + }; + + /* + Function: animate + This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating + the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // onComplete repaints, just to make sure everything looks good at the end of the animation. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /** + * checks for a listener for the given condition, executing it if found, passing in the given value. + * condition listeners would have been attached using "bind" (which is, you could argue, now overloaded, since + * firing click events etc is a bit different to what this does). i thought about adding a "bindCondition" + * or something, but decided against it, for the sake of simplicity. jsPlumb will never fire one of these + * condition events anyway. + */ + this.checkCondition = function(conditionName, value) { + var l = _currentInstance.getListener(conditionName); + var r = true; + if (l && l.length > 0) { + try { + for (var i = 0 ; i < l.length; i++) { + r = r && l[i](value); + } + } + catch (e) { + _log(_currentInstance, "cannot check condition [" + conditionName + "]" + e); + } + } + return r; + }; + + /* + Function: connect + Establishes a between two elements (or s, which are themselves registered to elements). + + Parameters: + params - Object containing constructor arguments for the Connection. See 's constructor documentation. + referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of + Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection. + + Returns: + The newly created . + */ + this.connect = function(params, referenceParams) { + // prepare a final set of parameters to create connection with + var _p = _prepareConnectionParams(params, referenceParams); + // TODO probably a nicer return value if the connection was not made. _prepareConnectionParams + // will return null (and log something) if either endpoint was full. what would be nicer is to + // create a dedicated 'error' object. + if (_p) { + // a connect call will delete its created endpoints on detach, unless otherwise specified. + // this is because the endpoints belong to this connection only, and are no use to + // anyone else, so they hang around like a bad smell. + if (_p.deleteEndpointsOnDetach == null) + _p.deleteEndpointsOnDetach = true; + + // create the connection. it is not yet registered + var jpc = _newConnection(_p); + // now add it the model, fire an event, and redraw + _finaliseConnection(jpc, _p); + return jpc; + } + }; + + /* + Function: deleteEndpoint + Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too) + + Parameters: + object - either an object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElements(endpoint.endpoint.getDisplayElements()); + _currentInstance.anchorManager.deleteEndpoint(endpoint); + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + delete endpoint; + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every , and their associated s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc, doFireEvent) { + // may have been given a connection, or in special cases, an object + var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + argIsConnection = jpc.constructor == connType, + params = argIsConnection ? { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + } : jpc; + + if (doFireEvent) _currentInstance.fire("jsPlumbConnectionDetached", params); + _currentInstance.anchorManager.connectionDetached(params); + }; + + /* + Function: detach + Detaches and then removes a . From 1.3.5 this method has been altered to remove support for + specifying Connections by various parameters; you can now pass in a Connection as the first argument and + an optional parameters object as a second argument. If you need the functionality this method provided + before 1.3.5 then you should use the getConnections method to get the list of Connections to detach, and + then iterate through them, calling this for each one. + + Parameters: + connection - the to detach + params - optional parameters to the detach call. valid values here are + fireEvent : defaults to false; indicates you want jsPlumb to fire a connection + detached event. The thinking behind this is that if you made a programmatic + call to detach an event, you probably don't need the callback. + forceDetach : defaults to false. allows you to override any beforeDetach listeners that may be registered. + + Returns: + true if successful, false if not. + */ + this.detach = function() { + + if (arguments.length == 0) return; + var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + firstArgIsConnection = arguments[0].constructor == connType, + params = arguments.length == 2 ? firstArgIsConnection ? (arguments[1] || {}) : arguments[0] : arguments[0], + fireEvent = (params.fireEvent !== false), + forceDetach = params.forceDetach, + connection = firstArgIsConnection ? arguments[0] : params.connection; + + if (connection) { + if (forceDetach || (connection.isDetachAllowed(connection) + && connection.endpoints[0].isDetachAllowed(connection) + && connection.endpoints[1].isDetachAllowed(connection))) { + if (forceDetach || _currentInstance.checkCondition("beforeDetach", connection)) + connection.endpoints[0].detach(connection, false, true, fireEvent); // TODO check this param iscorrect for endpoint's detach method + } + } + else { + var _p = jsPlumb.extend( {}, params); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1]), fireEvent); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source), + targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + if (_currentInstance.checkCondition("beforeDetach", jpc)) { + jpc.endpoints[0].detach(jpc, false, true, fireEvent); + } + } + }); + } + } + }; + + /* + Function: detachAllConnections + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + params - optional parameters. alowed values: + fireEvent : defaults to true, whether or not to fire the detach event. + + Returns: + void + */ + this.detachAllConnections = function(el, params) { + params = params || {}; + el = _getElementObject(el); + var id = _getAttribute(el, "id"), + endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + }; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Parameters: + params - optional params object containing: + fireEvent : whether or not to fire detach events. defaults to true. + + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function(params) { + params = params || {}; + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of y + our library's draggable method so that jsPlumb can setup the appropriate callbacks. Your + underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + // TODO it would be nice if this supported a selector string, instead of an id. + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method, + * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the + * default scope). If multiple scopes are passed in, the return value will be a map of { scope -> [ connection... ] }. + * + * Parameters + * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list + * of connections that are in the given scope. + * options - if the argument is a JS object, you can specify a finer-grained filter: + * + * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes. + * - *source* either a string representing an element id, or a selector. constrains the result to connections having this source. + * - *target* either a string representing an element id, or a selector. constrains the result to connections having this target. + * + */ + this.getConnections = function(options) { + if (!options) { + options = {}; + } else if (options.constructor == String) { + options = { "scope": options }; + } + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') + r.push(input); + else + r = input; + } + return r; + }, + scope = options.scope || _currentInstance.getDefaultScope(), + scopes = prepareList(scope), + sources = prepareList(options.source), + targets = prepareList(options.target), + filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }, + results = scopes.length > 1 ? {} : [], + _addOne = function(scope, obj) { + if (scopes.length > 1) { + var ss = results[scope]; + if (ss == null) { + ss = []; results[scope] = ss; + } + ss.push(obj); + } else results.push(obj); + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + _addOne(i, c); + } + } + } + return results; + }; + + /* + * Function: getAllConnections + * Gets all connections, as a map of { scope -> [ connection... ] }. + */ + this.getAllConnections = function() { + return connectionsByScope; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /** + * Function:getEndpoints + * Gets the list of Endpoints for a given selector, or element id. + * @param el + * @return + */ + this.getEndpoints = function(el) { + return endpointsByElement[_getId(el)]; + }; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + this.getOffset = function(id) { + var o = offsets[id]; + return _updateOffset({elId:id}); + }; + + this.getSelector = function(spec) { + return jsPlumb.CurrentLibrary.getSelector(spec); + }; + + this.getSize = function(id) { + var s = sizes[id]; + if (!s) _updateOffset({elId:id}); + return sizes[id]; + }; + + this.appendElement = _appendElement; + + var _hoverSuspended = false; + this.isHoverSuspended = function() { return _hoverSuspended; }; + this.setHoverSuspended = function(s) { _hoverSuspended = s; }; + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + changeEndpoints - whether not to also hide endpoints on the element. by default this is false. + + Returns: + void + */ + this.hide = function(el, changeEndpoints) { + _setVisible(el, "none", changeEndpoints); + }; + + // exposed for other objects to use to get a unique id. + this.idstamp = _idstamp; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + if (!initialized) { + _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run. + + var bindOne = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && _mouseEventsEnabled && renderMode == jsPlumb.CANVAS) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + var t = c[i].connector[event](e); + if (t) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i].endpoint[event](e)) return; + } + } + } + }); + }; + bindOne("click");bindOne("dblclick");bindOne("mousemove");bindOne("mousedown");bindOne("mouseup");bindOne("contextmenu"); + + initialized = true; + _currentInstance.fire("ready"); + } + }; + + this.log = log; + this.jsPlumbUIComponent = jsPlumbUIComponent; + this.EventGenerator = EventGenerator; + + /* + * Creates an anchor with the given params. + * + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function() { + if (arguments.length == 0) return null; + var specimen = arguments[0], elementId = arguments[1], jsPlumbInstance = arguments[2], newAnchor = null; + if (!jsPlumbInstance) + throw "NO JSPLUMB SET"; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; //TODO hazy here about whether it should be added or is already added somehow. + // is it the name of an anchor type? + else if (typeof specimen == "string") { + newAnchor = jsPlumb.Anchors[arguments[0]]({elementId:elementId, jsPlumbInstance:_currentInstance}); + } + // is it an array? it will be one of: + // an array of [name, params] - this defines a single anchor + // an array of arrays - this defines some dynamic anchors + // an array of numbers - this defines a single anchor. + else if (specimen.constructor == Array) { + if (specimen[0].constructor == Array || specimen[0].constructor == String) { + if (specimen.length == 2 && specimen[0].constructor == String && specimen[1].constructor == Object) { + var pp = jsPlumb.extend({elementId:elementId, jsPlumbInstance:_currentInstance}, specimen[1]); + newAnchor = jsPlumb.Anchors[specimen[0]](pp); + } + else + newAnchor = new DynamicAnchor(specimen, null, elementId); + } + else { + var anchorParams = { + x:specimen[0], y:specimen[1], + orientation : (specimen.length >= 4) ? [ specimen[2], specimen[3] ] : [0,0], + offsets : (specimen.length == 6) ? [ specimen[4], specimen[5] ] : [ 0, 0 ], + elementId:elementId + }; + newAnchor = new Anchor(anchorParams); + newAnchor.clone = function() { return new Anchor(anchorParams); }; + } + } + + if (!newAnchor.id) newAnchor.id = "anchor_" + _idstamp(); + return newAnchor; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types, elementId, jsPlumbInstance) { + var r = []; + for ( var i = 0; i < types.length; i++) { + if (typeof types[i] == "string") + r.push(jsPlumb.Anchors[types[i]]({elementId:elementId, jsPlumbInstance:jsPlumbInstance})); + else if (types[i].constructor == Array) + r.push(_currentInstance.makeAnchor(types[i], elementId, jsPlumbInstance)); + } + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /** + * Function: makeTarget + * Makes some DOM element a Connection target, allowing you to drag connections to it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a target. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * scope optional. scope for the drop zone. + * dropOptions optional. same stuff as you would pass to dropOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to true. whether or not to delete + * any Endpoints created by a connection to this target if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + var _targetEndpointDefinitions = {}; + var _setEndpointPaintStylesAndAnchor = function(ep, epIndex) { + ep.paintStyle = ep.paintStyle || + _currentInstance.Defaults.EndpointStyles[epIndex] || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyles[epIndex] || + jsPlumb.Defaults.EndpointStyle; + ep.hoverPaintStyle = ep.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyles[epIndex] || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyles[epIndex] || + jsPlumb.Defaults.EndpointHoverStyle; + + ep.anchor = ep.anchor || + _currentInstance.Defaults.Anchors[epIndex] || + _currentInstance.Defaults.Anchor || + jsPlumb.Defaults.Anchors[epIndex] || + jsPlumb.Defaults.Anchor; + + }; + this.makeTarget = function(el, params, referenceParams) { + + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + var jpcl = jsPlumb.CurrentLibrary, + targetScope = p.scope || _currentInstance.Defaults.Scope, + deleteEndpointsOnDetach = p.deleteEndpointsOnDetach || true, + _doOne = function(_el) { + + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el); + _targetEndpointDefinitions[elid] = p.endpoint; + + var dropOptions = jsPlumb.extend({}, p.dropOptions || {}), + _drop = function() { + _currentInstance.currentlyDragging = false; + var draggable = _getElementObject(jpcl.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + // restore the original scope if necessary (issue 57) + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id], + source = jpc.endpoints[0], + _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : {}; + + // use defaults for endpoint style, if not present..this either uses EndpointStyle, or EndpointStyles[1], + // if it is present, since this is a target endpoint. + // TODO - this should be a helper method. makeTarget should use it too. give it an endpoint + _setEndpointPaintStylesAndAnchor(_endpoint, 1); + + // unlock the source anchor to allow it to refresh its position if necessary + source.anchor.locked = false; + + if (scope) jpcl.setDragScope(draggable, scope); + + // check if drop is allowed here. + var _continue = jpc.isDropAllowed(jpc.sourceId, _getId(_el), jpc.scope); + + // regardless of whether the connection is ok, reconfigure the existing connection to + // point at the current info. we need this to be correct for the detach event that will follow. + // clear the source endpoint from the list to detach. we will detach this connection at this + // point, but we want to keep the source endpoint. the target is a floating endpoint and should + // be removed. TODO need to figure out whether this code can result in endpoints kicking around + // when they shouldnt be. like is this a full detach of a connection? can it be? + if (jpc.endpointsToDeleteOnDetach) { + if (source === jpc.endpointsToDeleteOnDetach[0]) + jpc.endpointsToDeleteOnDetach[0] = null; + else if (source === jpc.endpointsToDeleteOnDetach[1]) + jpc.endpointsToDeleteOnDetach[1] = null; + } + // reinstate any suspended endpoint; this just puts the connection back into + // a state in which it will report sensible values if someone asks it about + // its target. we're going to throw this connection away shortly so it doesnt matter + // if we manipulate it a bit. + if (jpc.suspendedEndpoint) { + jpc.targetId = jpc.suspendedEndpoint.elementId; + jpc.target = jpcl.getElementObject(jpc.suspendedEndpoint.elementId); + jpc.endpoints[1] = jpc.suspendedEndpoint; + } + + if (_continue) { + + // detach this connection from the source. + source.detach(jpc, false, true, true);//source.endpointWillMoveAfterConnection); + + // make a new Endpoint for the target + var newEndpoint = _currentInstance.addEndpoint(_el, _endpoint); + + // if the anchor has a 'positionFinder' set, then delegate to that function to find + // out where to locate the anchor. + if (newEndpoint.anchor.positionFinder != null) { + var dropPosition = jpcl.getUIPosition(arguments), + elPosition = jpcl.getOffset(_el), + elSize = jpcl.getSize(_el), + ap = newEndpoint.anchor.positionFinder(dropPosition, elPosition, elSize, newEndpoint.anchor); + newEndpoint.anchor.x = ap[0]; + newEndpoint.anchor.y = ap[1]; + // now figure an orientation for it..kind of hard to know what to do actually. probably the best thing i can do is to + // support specifying an orientation in the anchor's spec. if one is not supplied then i will make the orientation + // be what will cause the most natural link to the source: it will be pointing at the source, but it needs to be + // specified in one axis only, and so how to make that choice? i think i will use whichever axis is the one in which + // the target is furthest away from the source. + } + var c = _currentInstance.connect({ + source:source, + target:newEndpoint, + scope:scope, + previousConnection:jpc, + container:jpc.parent, + // 'endpointWillMoveAfterConnection' is set by the makeSource function, and it indicates that the + // given endpoint will actually transfer from the element it is currently attached to to some other + // element after a connection has been established. in that case, we do not want to fire the + // connection event, since it will have the wrong data in it; makeSource will do it for us. + // this is controlled by the 'parent' parameter on a makeSource call. + doNotFireConnectionEvent:source.endpointWillMoveAfterConnection + }); + if (deleteEndpointsOnDetach) + c.endpointsToDeleteOnDetach = [ source, newEndpoint ]; + } + // if not allowed to drop... + else { + // TODO this code is identical (pretty much) to what happens when a connection + // dragged from a normal endpoint is in this situation. refactor. + // is this an existing connection, and will we reattach? + if (jpc.suspendedEndpoint) { + if (source.isReattach) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + _currentInstance.repaint(source.elementId); + } + else + source.detach(jpc, false, true, true); // otherwise, detach the connection and tell everyone about it. + } + + } + }; + + var dropEvent = jpcl.dragEvents['drop']; + dropOptions["scope"] = dropOptions["scope"] || targetScope; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop); + + jpcl.initDroppable(_el, dropOptions, true); + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements drop targets. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeTargets = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeTarget(els[i], params, referenceParams); + } + }; + + /** + * Function: makeSource + * Makes some DOM element a Connection source, allowing you to drag connections from it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a source. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * parent optional. the element to add Endpoints to when a Connection is established. if you omit this, + * Endpoints will be added to 'el'. + * scope optional. scope for the connections dragged from this element. + * dragOptions optional. same stuff as you would pass to dragOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to false. whether or not to delete + * any Endpoints created by a connection from this source if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + var _sourceEndpointDefinitions = {}; + this.makeSource = function(el, params, referenceParams) { + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + var jpcl = jsPlumb.CurrentLibrary, + _doOne = function(_el) { + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el); + _sourceEndpointDefinitions[elid] = p.endpoint || {}; + var stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"], + dragOptions = jsPlumb.extend({ }, p.dragOptions || _sourceEndpointDefinitions[elid].dragOptions || {}), + existingDrag = dragOptions.drag, + existingStop = dragOptions.stop, + ep = null, + endpointAddedButNoDragYet = false; + + // set scope if its not set in dragOptions but was passed in in params + dragOptions["scope"] = dragOptions["scope"] || p.scope; + + // make sure this method honours whatever defaults have been set for Endpoint. + _sourceEndpointDefinitions[elid].endpoint = _sourceEndpointDefinitions[elid].endpoint || _currentInstance.Defaults.Endpoints[0] || _currentInstance.Defaults.Endpoint; + + // set endpoint paint styles and anchor, using either styles that are set or defaults. + _setEndpointPaintStylesAndAnchor(_sourceEndpointDefinitions[elid], 0); + + + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + if (existingDrag) existingDrag.apply(this, arguments); + endpointAddedButNoDragYet = false; + }); + + dragOptions[stopEvent] = function() { + + if (existingStop) existingStop.apply(this, arguments); + + //_currentlyDown = false; + _currentInstance.currentlyDragging = false; + + if (ep.connections.length == 0) + _currentInstance.deleteEndpoint(ep); + else { + + jpcl.unbind(ep.canvas, "mousedown"); + + // reset the anchor to the anchor that was initially provided. the one we were using to drag + // the connection was just a placeholder that was located at the place the user pressed the + // mouse button to initiate the drag. + var anchorDef = _sourceEndpointDefinitions[elid].anchor || _currentInstance.Defaults.Anchor; + ep.anchor = _currentInstance.makeAnchor(anchorDef, elid, _currentInstance); + + if (p.parent) { + var parent = jpcl.getElementObject(p.parent); + if (parent) { + var currentId = ep.elementId; + ep.setElement(parent); + ep.endpointWillMoveAfterConnection = false; + _currentInstance.anchorManager.rehomeEndpoint(currentId, parent); + ep.connections[0].previousConnection = null; + _finaliseConnection(ep.connections[0]); + } + } + _currentInstance.repaint(elid); + } + }; + // when the user presses the mouse, add an Endpoint + var mouseDownListener = function(e) { + // make sure we have the latest offset for this div + var myOffsetInfo = _updateOffset({elId:elid}); + + var x = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width, + y = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height, + parentX = x, + parentY = y; + + + // if there is a parent, the endpoint will actually be added to it now, rather than the div + // that was the source. in that case, we have to adjust the anchor position so it refers to + // the parent. + if (p.parent) { + var pEl = jsPlumb.CurrentLibrary.getElementObject(p.parent), + pId = _getId(pEl); + myOffsetInfo = _updateOffset({elId:pId}); + parentX = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width, + parentY = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height; + } + + // we need to override the anchor in here, and force 'isSource', but we don't want to mess with + // the params passed in, because after a connection is established we're going to reset the endpoint + // to have the anchor we were given. + var tempEndpointParams = {}; + jsPlumb.extend(tempEndpointParams, _sourceEndpointDefinitions[elid]); + tempEndpointParams.isSource = true; + tempEndpointParams.anchor = [x,y,0,0]; + tempEndpointParams.parentAnchor = [ parentX, parentY, 0, 0 ]; + tempEndpointParams.dragOptions = dragOptions; + // if a parent was given we need to turn that into a "container" argument. this is, by default, + // the parent of the element we will move to, so parent of p.parent in this case. however, if + // the user has specified a 'container' on the endpoint definition or on + // the defaults, we should use that. + if (p.parent) { + var potentialParent = tempEndpointParams.container || _currentInstance.Defaults.Container; + if (potentialParent) + tempEndpointParams.container = potentialParent; + else + tempEndpointParams.container = jsPlumb.CurrentLibrary.getParent(p.parent); + } + + ep = _currentInstance.addEndpoint(elid, tempEndpointParams); + + endpointAddedButNoDragYet = true; + // we set this to prevent connections from firing attach events before this function has had a chance + // to move the endpoint. + ep.endpointWillMoveAfterConnection = p.parent != null; + ep.endpointWillMoveTo = p.parent ? jpcl.getElementObject(p.parent) : null; + + var _delTempEndpoint = function() { + // this mouseup event is fired only if no dragging occurred, by jquery and yui, but for mootools + // it is fired even if dragging has occurred, in which case we would blow away a perfectly + // legitimate endpoint, were it not for this check. the flag is set after adding an + // endpoint and cleared in a drag listener we set in the dragOptions above. + if(endpointAddedButNoDragYet) { + _currentInstance.deleteEndpoint(ep); + } + }; + + _currentInstance.registerListener(ep.canvas, "mouseup", _delTempEndpoint); + _currentInstance.registerListener(_el, "mouseup", _delTempEndpoint); + + // and then trigger its mousedown event, which will kick off a drag, which will start dragging + // a new connection from this endpoint. + jpcl.trigger(ep.canvas, "mousedown", e); + }; + + // register this on jsPlumb so that it can be cleared by a reset. + _currentInstance.registerListener(_el, "mousedown", mouseDownListener); + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements connection sources. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeSources = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeSource(els[i], params, referenceParams); + } + }; + + /* + Function: ready + Helper method to bind a function to jsPlumb's ready event. + */ + this.ready = function(fn) { + _currentInstance.bind("ready", fn); + }, + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"), + ebe = endpointsByElement[elId]; + if (ebe) { + for ( var i = 0; i < ebe.length; i++) + _currentInstance.deleteEndpoint(ebe[i]); + } + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + var _registeredListeners = {}, + _unbindRegisteredListeners = function() { + for (var i in _registeredListeners) { + for (var j = 0; j < _registeredListeners[i].length; j++) { + var info = _registeredListeners[i][j]; + jsPlumb.CurrentLibrary.unbind(info.el, info.event, info.listener); + } + } + _registeredListeners = {}; + }; + + // internal register listener method. gives us a hook to clean things up + // with if the user calls jsPlumb.reset. + this.registerListener = function(el, type, listener) { + jsPlumb.CurrentLibrary.bind(el, type, listener); + _addToList(_registeredListeners, type, {el:el, event:type, listener:listener}); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + _currentInstance.deleteEveryEndpoint(); + _currentInstance.clearListeners(); + _unbindRegisteredListeners(); + _currentInstance.anchorManager.reset(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + + Parameters: + value - whether or not to automatically repaint when the window is resized. + + Returns: void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction + * Sets the function to fire when the window size has changed and a repaint was fired. + * + * Parameters: + * f - Function to execute. + * + * Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: setMouseEventsEnabled + * Sets whether or not mouse events are enabled. Default is true. + * + * Parameters: + * enabled - whether or not mouse events should be enabled. + * + * Returns: + * void + */ + this.setMouseEventsEnabled = function(enabled) { + _mouseEventsEnabled = enabled; + }; + + /* + * Function: setSuspendDrawing + * Suspends drawing operations. This can be used when you have a lot of connections to make or endpoints to register; + * it will save you a lot of time. + */ + this.setSuspendDrawing = _setSuspendDrawing; + + /* + * Constant for use with the setRenderMode method + */ + this.CANVAS = "canvas"; + + /* + * Constant for use with the setRenderMode method + */ + this.SVG = "svg"; + + this.VML = "vml"; + + /* + * Function: setRenderMode + * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that + * what you asked for is not supported (and that VML is). If you asked for VML but the browser does + * not support it, jsPlumb uses SVG. + * + * Returns: + * the render mode that jsPlumb set, which of course may be different from that requested. + */ + this.setRenderMode = function(mode) { + if (mode) + mode = mode.toLowerCase(); + else + return; + if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); + // now test we actually have the capability to do this. + if (mode === jsPlumb.CANVAS && canvasAvailable) + renderMode = jsPlumb.CANVAS; + else if (mode === jsPlumb.SVG && svgAvailable) + renderMode = jsPlumb.SVG; + else if (vmlAvailable) + renderMode = jsPlumb.VML; + + return renderMode; + }; + + this.getRenderMode = function() { return renderMode; }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * changeEndpoints - whether or not to also change the visible state of the endpoints on the element. this also has a bearing on + * other connections on those endpoints: if their other endpoint is also visible, the connections are made visible. + * + * Returns: + * void + */ + this.show = function(el, changeEndpoints) { + _setVisible(el, "block", changeEndpoints); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId, + makeAnchor:self.makeAnchor, + makeDynamicAnchor:self.makeDynamicAnchor + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * changeEndpoints - whether or not to also toggle the endpoints on the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload + * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element. + * + * Returns: + * void + */ + this.unload = function() { + // this used to do something, but it turns out that what it did was nothing. + // now it exists only for backwards compatibility. + }; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + var adjustForParentOffsetAndScroll = function(xy, el) { + + var offsetParent = null, result = xy; + if (el.tagName.toLowerCase() === "svg" && el.parentNode) { + offsetParent = el.parentNode; + } + else if (el.offsetParent) { + offsetParent = el.offsetParent; + } + if (offsetParent != null) { + var po = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(offsetParent), + so = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : {left:offsetParent.scrollLeft, top:offsetParent.scrollTop}; + + + // i thought it might be cool to do this: + // lastReturnValue[0] = lastReturnValue[0] - offsetParent.offsetLeft + offsetParent.scrollLeft; + // lastReturnValue[1] = lastReturnValue[1] - offsetParent.offsetTop + offsetParent.scrollTop; + // but i think it ignores margins. my reasoning was that it's quicker to not hand off to some underlying + // library. + + result[0] = xy[0] - po.left + so.left; + result[1] = xy[1] - po.top + so.top; + } + + return result; + + }; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + this.elementId = params.elementId; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + + if (timestamp && timestamp === self.timestamp) + return lastReturnValue; + + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + + // adjust loc if there is an offsetParent + lastReturnValue = adjustForParentOffsetAndScroll(lastReturnValue, element.canvas); + + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function(_endpoint) { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + * + * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference, + // the canvas this refers to. + refCanvas = params.referenceCanvas, + size = _getSize(_getElementObject(refCanvas)), + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + xDir = 0, yDir = 0, + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + orientation = null, + _lastResult = null; + + // set these to 0 each; they are used by certain types of connectors in the loopback case, + // when the connector is trying to clear the element it is on. but for floating anchor it's not + // very important. + this.x = 0; this.y = 0; + + this.isFloating = true; + + this.compute = function(params) { + var xy = params.xy, element = params.element, + result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + + // adjust loc if there is an offsetParent + result = adjustForParentOffsetAndScroll(result, element.canvas); + + _lastResult = result; + return result; + }; + + this.getOrientation = function(_endpoint) { + if (orientation) return orientation; + else { + var o = ref.getOrientation(_endpoint); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchor is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector, elementId) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = [], self = this, + _convert = function(anchor) { + return anchor.constructor == Anchor ? anchor: _currentInstance.makeAnchor(anchor, elementId, _currentInstance); + }; + for (var i = 0; i < anchors.length; i++) + _anchors[i] = _convert(anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + this.locked = false; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null, + _curIndex = _anchors.length > 0 ? 0 : -1, + self = this, + + // helper method to calculate the distance between the centers of the two elements. + _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }, + + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + self.x = _curAnchor.x; + self.y = _curAnchor.y; + + return _curAnchor.compute(params); + }; + + this.getCurrentLocation = function() { + return _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + }; + + this.getOrientation = function(_endpoint) { return _curAnchor != null ? _curAnchor.getOrientation(_endpoint) : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + manages anchors for all elements. + */ + // "continuous" anchors: anchors that pick their location based on how many connections the given element has. + // this requires looking at a lot more elements than normal - anything that has had a Continuous anchor applied has + // to be recalculated. so this manager is used as a reference point. the first time, with a new timestamp, that + // a continuous anchor is asked to compute, it calls this guy. or maybe, even, this guy gets called somewhere else + // and compute only ever returns pre-computed values. either way, this is the central point, and we want it to + // be called as few times as possible. + var continuousAnchors = {}, + continuousAnchorLocations = {}, + continuousAnchorOrientations = {}, + Orientation = { HORIZONTAL : "horizontal", VERTICAL : "vertical", DIAGONAL : "diagonal", IDENTITY:"identity" }, + + // TODO this functions uses a crude method of determining orientation between two elements. + // 'diagonal' should be chosen when the angle of the line between the two centers is around + // one of 45, 135, 225 and 315 degrees. maybe +- 15 degrees. + calculateOrientation = function(sourceId, targetId, sd, td) { + + if (sourceId === targetId) return { + orientation:Orientation.IDENTITY, + a:["top", "top"] + }; + + var theta = Math.atan2((td.centery - sd.centery) , (td.centerx - sd.centerx)), + theta2 = Math.atan2((sd.centery - td.centery) , (sd.centerx - td.centerx)), + h = ((sd.left <= td.left && sd.right >= td.left) || (sd.left <= td.right && sd.right >= td.right) || + (sd.left <= td.left && sd.right >= td.right) || (td.left <= sd.left && td.right >= sd.right)), + v = ((sd.top <= td.top && sd.bottom >= td.top) || (sd.top <= td.bottom && sd.bottom >= td.bottom) || + (sd.top <= td.top && sd.bottom >= td.bottom) || (td.top <= sd.top && td.bottom >= sd.bottom)); + + if (! (h || v)) { + var a = null, rls = false, rrs = false, sortValue = null; + if (td.left > sd.left && td.top > sd.top) + a = ["right", "top"]; + else if (td.left > sd.left && sd.top > td.top) + a = [ "top", "left"]; + else if (td.left < sd.left && td.top < sd.top) + a = [ "top", "right"]; + else if (td.left < sd.left && td.top > sd.top) + a = ["left", "top" ]; + + return { orientation:Orientation.DIAGONAL, a:a, theta:theta, theta2:theta2 }; + } + else if (h) return { + orientation:Orientation.HORIZONTAL, + a:sd.top < td.top ? ["bottom", "top"] : ["top", "bottom"], + theta:theta, theta2:theta2 + } + else return { + orientation:Orientation.VERTICAL, + a:sd.left < td.left ? ["right", "left"] : ["left", "right"], + theta:theta, theta2:theta2 + } + }, + placeAnchorsOnLine = function(desc, elementDimensions, elementPosition, + connections, horizontal, otherMultiplier, reverse) { + var a = [], step = elementDimensions[horizontal ? 0 : 1] / (connections.length + 1); + + for (var i = 0; i < connections.length; i++) { + var val = (i + 1) * step, other = otherMultiplier * elementDimensions[horizontal ? 1 : 0]; + if (reverse) + val = elementDimensions[horizontal ? 0 : 1] - val; + + var dx = (horizontal ? val : other), x = elementPosition[0] + dx, xp = dx / elementDimensions[0], + dy = (horizontal ? other : val), y = elementPosition[1] + dy, yp = dy / elementDimensions[1]; + + a.push([ x, y, xp, yp, connections[i][1], connections[i][2] ]); + } + + return a; + }, + standardEdgeSort = function(a, b) { return a[0] > b[0] ? 1 : -1 }, + currySort = function(reverseAngles) { + return function(a,b) { + var r = true; + if (reverseAngles) { + if (a[0][0] < b[0][0]) + r = true; + else + r = a[0][1] > b[0][1]; + } + else { + if (a[0][0] > b[0][0]) + r= true; + else + r =a[0][1] > b[0][1]; + } + return r === false ? -1 : 1; + }; + }, + leftSort = function(a,b) { + // first get adjusted values + var p1 = a[0][0] < 0 ? -Math.PI - a[0][0] : Math.PI - a[0][0], + p2 = b[0][0] < 0 ? -Math.PI - b[0][0] : Math.PI - b[0][0]; + if (p1 > p2) return 1; + else return a[0][1] > b[0][1] ? 1 : -1; + }, + edgeSortFunctions = { + "top":standardEdgeSort, + "right":currySort(true), + "bottom":currySort(true), + "left":leftSort + }, + _sortHelper = function(_array, _fn) { + return _array.sort(_fn); + }, + placeAnchors = function(elementId, _anchorLists) { + var sS = sizes[elementId], sO = offsets[elementId], + placeSomeAnchors = function(desc, elementDimensions, elementPosition, unsortedConnections, isHorizontal, otherMultiplier) { + if (unsortedConnections.length > 0) { + var sc = _sortHelper(unsortedConnections, edgeSortFunctions[desc]), // puts them in order based on the target element's pos on screen + //sc = unsortedConnections.sort(edgeSortFunctions[desc]), // puts them in order based on the target element's pos on screen + reverse = desc === "right" || desc === "top", + anchors = placeAnchorsOnLine(desc, elementDimensions, + elementPosition, sc, + isHorizontal, otherMultiplier, reverse ); + + // takes a computed anchor position and adjusts it for parent offset and scroll, then stores it. + var _setAnchorLocation = function(endpoint, anchorPos) { + var a = adjustForParentOffsetAndScroll([anchorPos[0], anchorPos[1]], endpoint.canvas); + continuousAnchorLocations[endpoint.id] = [ a[0], a[1], anchorPos[2], anchorPos[3] ]; + }; + + for (var i = 0; i < anchors.length; i++) { + var c = anchors[i][4], weAreSource = c.endpoints[0].elementId === elementId, weAreTarget = c.endpoints[1].elementId === elementId; + if (weAreSource) + _setAnchorLocation(c.endpoints[0], anchors[i]); + else if (weAreTarget) + _setAnchorLocation(c.endpoints[1], anchors[i]); + } + } + }; + + placeSomeAnchors("bottom", sS, [sO.left,sO.top], _anchorLists.bottom, true, 1); + placeSomeAnchors("top", sS, [sO.left,sO.top], _anchorLists.top, true, 0); + placeSomeAnchors("left", sS, [sO.left,sO.top], _anchorLists.left, false, 0); + placeSomeAnchors("right", sS, [sO.left,sO.top], _anchorLists.right, false, 1); + }, + AnchorManager = function() { + var _amEndpoints = {}, + endpointConnectionsByElementId = {}, + continuousAnchorConnectionsByElementId = {}, + continuousAnchorEndpoints = [], + self = this, + anchorLists = {}; + + this.reset = function() { + endpointConnectionsByElementId = {}; + continuousAnchorConnectionsByElementId = {}; + continuousAnchorEndpoints = []; + anchorLists = {}; + }; + + this.newConnection = function(conn) { + var sourceId = conn.sourceId, targetId = conn.targetId, + ep = conn.endpoints, + doRegisterTarget = true, + registerConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + if (otherAnchor.constructor == DynamicAnchor || otherAnchor.constructor == Anchor) { + _addToList(endpointConnectionsByElementId, elId, [c, otherEndpoint, otherAnchor.constructor == DynamicAnchor]); + } + else { + // continuous. if they are the same element, just assign the same anchor + // to both. + if (sourceId == targetId) { + // remove the target endpoint's canvas. we dont need it. + jsPlumb.CurrentLibrary.removeElement(ep[1].canvas); + doRegisterTarget = false; + } + _addToList(continuousAnchorConnectionsByElementId, elId, c); + } + }; + registerConnection(0, ep[0], ep[0].anchor, targetId, conn); + if (doRegisterTarget) + registerConnection(1, ep[1], ep[1].anchor, sourceId, conn); + }; + this.connectionDetached = function(connInfo) { + var sourceId = connInfo.sourceId, + targetId = connInfo.targetId, + ep = connInfo.connection.endpoints, + removeConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + if (otherAnchor.constructor == FloatingAnchor) { + // no-op + } + else if (otherAnchor.constructor == DynamicAnchor || otherAnchor.constructor == Anchor) { + var _conns = endpointConnectionsByElementId[elId]; + if (_conns) { + _removeWithFunction(_conns, function(e) { return e[0].id == c.id; }); + } + } + else // continuous. + _removeWithFunction(continuousAnchorConnectionsByElementId[elId], function(_c) { + return _c.id == c.id; + }); + }; + + removeConnection(1, ep[1], ep[1].anchor, sourceId, connInfo.connection); + removeConnection(0, ep[0], ep[0].anchor, targetId, connInfo.connection); + + // remove from anchorLists + var sEl = connInfo.connection.sourceId, + tEl = connInfo.connection.targetId, + sE = connInfo.connection.endpoints[0].id, + tE = connInfo.connection.endpoints[1].id, + _remove = function(list, eId) { + if (list) { // transient anchors dont get entries in this list. + var f = function(e) { return e[4] == eId; }; + _removeWithFunction(list["top"], f); + _removeWithFunction(list["left"], f); + _removeWithFunction(list["bottom"], f); + _removeWithFunction(list["right"], f); + } + }; + + _remove(anchorLists[sEl], sE); + _remove(anchorLists[tEl], tE); + self.redraw(sEl); + self.redraw(tEl); + }; + this.add = function(endpoint, elementId) { + _addToList(_amEndpoints, elementId, endpoint); + }; + this.get = function(elementId) { + return { + "standard":endpointConnectionsByElementId[elementId] || [], + "continuous":continuousAnchorConnectionsByElementId[elementId] || [], + "endpoints":_amEndpoints[elementId], + "continuousAnchorEndpoints":continuousAnchorEndpoints + }; + }; + this.deleteEndpoint = function(endpoint) { + var cIdx = _findIndex(continuousAnchorEndpoints, endpoint); + if (cIdx > -1) + continuousAnchorEndpoints.splice(cIdx, 1); + else + _removeWithFunction(_amEndpoints[endpoint.elementId], function(e) { + return e.id == endpoint.id; + }); + }; + this.clearFor = function(elementId) { + delete _amEndpoints[elementId]; + _amEndpoints[elementId] = []; + }; + // updates the given anchor list by either updating an existing anchor's info, or adding it. this function + // also removes the anchor from its previous list, if the edge it is on has changed. + // all connections found along the way (those that are connected to one of the faces this function + // operates on) are added to the connsToPaint list, as are their endpoints. in this way we know to repaint + // them wthout having to calculate anything else about them. + var _updateAnchorList = function(lists, theta, order, conn, aBoolean, otherElId, idx, reverse, edgeId, elId, connsToPaint, endpointsToPaint) { + // first try to find the exact match, but keep track of the first index of a matching element id along the way.s + var exactIdx = -1, + firstMatchingElIdx = -1, + endpoint = conn.endpoints[idx], + endpointId = endpoint.id, + oIdx = [1,0][idx], + values = [ [ theta, order ], conn, aBoolean, otherElId, endpointId ], + listToAddTo = lists[edgeId], + listToRemoveFrom = endpoint._continuousAnchorEdge ? lists[endpoint._continuousAnchorEdge] : null; + + if (listToRemoveFrom) { + var rIdx = _findWithFunction(listToRemoveFrom, function(e) { return e[4] == endpointId }); + if (rIdx != -1) { + listToRemoveFrom.splice(rIdx, 1); + // get all connections from this list + for (var i = 0; i < listToRemoveFrom.length; i++) { + _addWithFunction(connsToPaint, listToRemoveFrom[i][1], function(c) { return c.id == listToRemoveFrom[i][1].id }); + _addWithFunction(endpointsToPaint, listToRemoveFrom[i][1].endpoints[idx], function(e) { return e.id == listToRemoveFrom[i][1].endpoints[idx].id }); + } + } + } + + for (var i = 0; i < listToAddTo.length; i++) { + if (idx == 1 && listToAddTo[i][3] === otherElId && firstMatchingElIdx == -1) + firstMatchingElIdx = i; + _addWithFunction(connsToPaint, listToAddTo[i][1], function(c) { return c.id == listToAddTo[i][1].id }); + _addWithFunction(endpointsToPaint, listToAddTo[i][1].endpoints[idx], function(e) { return e.id == listToAddTo[i][1].endpoints[idx].id }); + } + if (exactIdx != -1) { + listToAddTo[exactIdx] = values; + } + else { + var insertIdx = reverse ? firstMatchingElIdx != -1 ? firstMatchingElIdx : 0 : listToAddTo.length; // of course we will get this from having looked through the array shortly. + listToAddTo.splice(insertIdx, 0, values); + } + + // store this for next time. + endpoint._continuousAnchorEdge = edgeId; + }; + this.redraw = function(elementId, ui, timestamp) { + // get all the endpoints for this element + var ep = _amEndpoints[elementId] || [], + endpointConnections = endpointConnectionsByElementId[elementId] || [], + continuousAnchorConnections = continuousAnchorConnectionsByElementId[elementId] || [], + connectionsToPaint = [], + endpointsToPaint = [], + anchorsToUpdate = []; + + timestamp = timestamp || _timestamp(); + + _updateOffset( { elId : elementId, offset : ui, recalc : false, timestamp : timestamp }); + // valid for one paint cycle. + var myOffset = offsets[elementId], + myWH = sizes[elementId], + orientationCache = {}; + + // actually, first we should compute the orientation of this element to all other elements to which + // this element is connected with a continuous anchor (whether both ends of the connection have + // a continuous anchor or just one) + for (var i = 0; i < continuousAnchorConnections.length; i++) { + var conn = continuousAnchorConnections[i], + sourceId = conn.sourceId, + targetId = conn.targetId, + oKey = sourceId + "_" + targetId, + oKey2 = targetId + "_" + sourceId, + o = orientationCache[oKey], + td = _getCachedData(targetId), + sd = _getCachedData(sourceId), + oIdx = conn.sourceId == elementId ? 1 : 0; + + if (!anchorLists[sourceId]) anchorLists[sourceId] = { top:[], right:[], bottom:[], left:[] }; + if (!anchorLists[targetId]) anchorLists[targetId] = { top:[], right:[], bottom:[], left:[] }; + + if (targetId == sourceId) { + // here we may want to improve this by somehow determining the face we'd like + // to put the connector on. ideally, when drawing, the face should be calculated + // by determining which face is closest to the point at which the mouse button + // was released. for now, we're putting it on the top face. + _updateAnchorList(anchorLists[sourceId], -Math.PI / 2, 0, conn, false, targetId, 0, false, "top", sourceId, connectionsToPaint, endpointsToPaint) + } + else { + if (!o) { + o = calculateOrientation(sourceId, targetId, sd.o, td.o); + orientationCache[oKey] = o; + // this would be a performance enhancement, but the computed angles need to be clamped to + //the (-PI/2 -> PI/2) range in order for the sorting to work properly. + /* orientationCache[oKey2] = { + orientation:o.orientation, + a:[o.a[1], o.a[0]], + theta:o.theta + Math.PI, + theta2:o.theta2 + Math.PI + };*/ + } + _updateAnchorList(anchorLists[sourceId], o.theta, 0, conn, false, targetId, 0, false, o.a[0], sourceId, connectionsToPaint, endpointsToPaint); + _updateAnchorList(anchorLists[targetId], o.theta2, -1, conn, true, sourceId, 1, true, o.a[1], targetId, connectionsToPaint, endpointsToPaint); + } + + _addWithFunction(anchorsToUpdate, sourceId, function(a) { return a === sourceId; }); + _addWithFunction(anchorsToUpdate, targetId, function(a) { return a === targetId; }); + _addWithFunction(connectionsToPaint, conn, function(c) { return c.id == conn.id; }); + _addWithFunction(endpointsToPaint, conn.endpoints[oIdx], function(e) { return e.id == conn.endpoints[oIdx].id; }); + } + + // now place all the continuous anchors we need to; + for (var i = 0; i < anchorsToUpdate.length; i++) { + placeAnchors(anchorsToUpdate[i], anchorLists[anchorsToUpdate[i]]); + } + + // now that continuous anchors have been placed, paint all the endpoints for this element + // TODO performance: add the endpoint ids to a temp array, and then when iterating in the next + // loop, check that we didn't just paint that endpoint. we can probably shave off a few more milliseconds this way. + for (var i = 0; i < ep.length; i++) { + ep[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + // ... and any other endpoints we came across as a result of the continuous anchors. + for (var i = 0; i < endpointsToPaint.length; i++) { + endpointsToPaint[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + + + // paint all the standard and "dynamic connections", which are connections whose other anchor is + // static and therefore does need to be recomputed; we make sure that happens only one time. + for (var i = 0; i < endpointConnections.length; i++) { + var otherEndpoint = endpointConnections[i][1]; + if (otherEndpoint.anchor.constructor == DynamicAnchor) { + otherEndpoint.paint({ elementWithPrecedence:elementId }); + _addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; }); + // all the connections for the other endpoint now need to be repainted + for (var k = 0; k < otherEndpoint.connections.length; k++) { + if (otherEndpoint.connections[k] !== endpointConnections[i][0]) + _addWithFunction(connectionsToPaint, otherEndpoint.connections[k], function(c) { return c.id == otherEndpoint.connections[k].id; }); + } + } else if (otherEndpoint.anchor.constructor == Anchor) { + _addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; }); + } + } + // paint current floating connection for this element, if there is one. + var fc = floatingConnections[elementId]; + if (fc) + fc.paint({timestamp:timestamp, recalc:false, elId:elementId}); + + // paint all the connections + for (var i = 0; i < connectionsToPaint.length; i++) { + connectionsToPaint[i].paint({elId:elementId, timestamp:timestamp, recalc:false}); + } + }; + this.rehomeEndpoint = function(currentId, element) { + var eps = _amEndpoints[currentId] || [], //, + elementId = _currentInstance.getId(element); + for (var i = 0; i < eps.length; i++) { + self.add(eps[i], elementId); + } + eps.splice(0, eps.length); + }; + }; + _currentInstance.anchorManager = new AnchorManager(); + _currentInstance.continuousAnchorFactory = { + get:function(params) { + var existing = continuousAnchors[params.elementId]; + if (!existing) { + existing = { + type:"Continuous", + compute : function(params) { + return continuousAnchorLocations[params.element.id] || [0,0]; + }, + getCurrentLocation : function(endpoint) { + return continuousAnchorLocations[endpoint.id] || [0,0]; + }, + getOrientation : function(endpoint) { + return continuousAnchorOrientations[endpoint.id] || [0,0]; + }, + isDynamic : true, + isContinuous : true + }; + continuousAnchors[params.elementId] = existing; + } + return existing; + } + }; + + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * container - optional id or selector instructing jsPlumb where to attach all the elements it creates for this connection. you should read the documentation for a full discussion of this. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * overlays - Optional array of Overlay definitions to appear on this Connection. + * drawEndpoints - if false, instructs jsPlumb to not draw the endpoints for this Connection. Be careful with this: it only really works when you tell jsPlumb to attach elements to the document body. Read the documentation for a full discussion of this. + */ + var Connection = function(params) { + var self = this, visible = true; + self.idPrefix = "_jsplumb_c_"; + self.defaultLabelLocation = 0.5; + self.defaultOverlayKeys = ["Overlays", "ConnectionOverlays"]; + this.parent = params.parent; + overlayCapableJsPlumbUIComponent.apply(this, arguments); + // ************** get the source and target and register the connection. ******************* + + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none"; + }; + + /** + Property: source + The source element for this Connection. + */ + this.source = _getElementObject(params.source); + /** + Property:target + The target element for this Connection. + */ + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. but + // source is not overridden if the Endpoint has declared it is not the final target of a connection; + // instead we use the source that the Endpoint declares will be the final source element. + if (params.sourceEndpoint) { + this.source = params.sourceEndpoint.endpointWillMoveTo || params.sourceEndpoint.getElement(); + } + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + + // if a new connection is the result of moving some existing connection, params.previousConnection + // will have that Connection in it. listeners for the jsPlumbConnection event can look for that + // member and take action if they need to. + self.previousConnection = params.previousConnection; + + var _cost = params.cost; + self.getCost = function() { return _cost; }; + self.setCost = function(c) { _cost = c; }; + + var _bidirectional = params.bidirectional === true; + self.isBidirectional = function() { return _bidirectional; }; + + /* + * Property: sourceId + * Id of the source element in the connection. + */ + this.sourceId = _getAttribute(this.source, "id"); + /* + * Property: targetId + * Id of the target element in the connection. + */ + this.targetId = _getAttribute(this.target, "id"); + + /** + * implementation of abstract method in EventGenerator + * @return list of attached elements. in our case, a list of Endpoints. + */ + this.getAttachedElements = function() { + return self.endpoints; + }; + + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams, elementId) { + if (anchorParams) + return _currentInstance.makeAnchor(anchorParams, elementId, _currentInstance); + }, + prepareEndpoint = function(existing, index, params, element, elementId, connectorPaintStyle, connectorHoverPaintStyle) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] + || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint; + + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified. + if (es.fillStyle == null && connectorPaintStyle != null) + es.fillStyle = connectorPaintStyle.strokeStyle; + + // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does: + //* + if (es.outlineColor == null && connectorPaintStyle != null) + es.outlineColor = connectorPaintStyle.outlineColor; + if (es.outlineWidth == null && connectorPaintStyle != null) + es.outlineWidth = connectorPaintStyle.outlineWidth; + //*/ + + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure? + if (connectorHoverPaintStyle != null) { + if (ehs == null) ehs = {}; + if (ehs.fillStyle == null) { + ehs.fillStyle = connectorHoverPaintStyle.strokeStyle; + } + } + var a = params.anchors ? params.anchors[index] : + params.anchor ? params.anchor : + _makeAnchor(_currentInstance.Defaults.Anchors[index], elementId) || + _makeAnchor(jsPlumb.Defaults.Anchors[index], elementId) || + _makeAnchor(_currentInstance.Defaults.Anchor, elementId) || + _makeAnchor(jsPlumb.Defaults.Anchor, elementId), + u = params.uuids ? params.uuids[index] : null, + e = _newEndpoint({ + paintStyle : es, + hoverPaintStyle:ehs, + endpoint : ep, + connections : [ self ], + uuid : u, + anchor : a, + source : element, + container:params.container, + reattach:params.reattach, + detachable:params.detachable + }); + self.endpoints[index] = e; + + + if (params.drawEndpoints === false) e.setVisible(false, true, true); + + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, + 0, + params, + self.source, + self.sourceId, + params.paintStyle, + params.hoverPaintStyle); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + + // if there were no endpoints supplied and the source element is the target element, we will reuse the source + // endpoint that was just created. + var existingTargetEndpoint = ((self.sourceId == self.targetId) && params.targetEndpoint == null) ? eS : params.targetEndpoint, + eT = prepareEndpoint(existingTargetEndpoint, + 1, + params, + self.target, + self.targetId, + params.paintStyle, + params.hoverPaintStyle); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + // if delete endpoints on detach, keep a record of just exactly which endpoints they are. + if (params.deleteEndpointsOnDetach) + self.endpointsToDeleteOnDetach = [eS, eT]; + + var _detachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.detachable === false) _detachable = false; + if(self.endpoints[0].connectionsDetachable === false) _detachable = false; + if(self.endpoints[1].connectionsDetachable === false) _detachable = false; + + // inherit connectin cost if it was set on source endpoint + if (_cost == null) _cost = self.endpoints[0].getConnectionCost(); + // inherit bidirectional flag if set no source endpoint + if (params.bidirectional == null) _bidirectional = self.endpoints[0].areConnectionsBidirectional(); + + /* + Function: isDetachable + Returns whether or not this connection can be detached from its target/source endpoint. by default this + is false; use it in conjunction with the 'reattach' parameter. + */ + this.isDetachable = function() { + return _detachable === true; + }; + + /* + Function: setDetachable + Sets whether or not this connection is detachable. + */ + this.setDetachable = function(detachable) { + _detachable = detachable === true; + }; + + // merge all the parameters objects into the connection. parameters set + // on the connection take precedence; then target endpoint params, then + // finally source endpoint params. + // TODO jsPlumb.extend could be made to take more than two args, and it would + // apply the second through nth args in order. + var _p = jsPlumb.extend({}, this.endpoints[0].getParameters()); + jsPlumb.extend(_p, this.endpoints[1].getParameters()); + jsPlumb.extend(_p, self.getParameters()); + self.setParameters(_p); + + // override setHover to pass it down to the underlying connector + var _sh = self.setHover; + + self.setHover = function() { + self.connector.setHover.apply(self.connector, arguments); + _sh.apply(self, arguments); + }; + + var _internalHover = function(state) { + if (_connectionBeingDragged == null) { + self.setHover(state, false); + } + }; + + /* + * Function: setConnector + * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method - the same + * thing that you would set as the 'connector' property on a jsPlumb.connect call. + * + * Parameters: + * connector - Connector definition + */ + this.setConnector = function(connector, doNotRepaint) { + if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent); + var connectorArgs = { + _jsPlumb:self._jsPlumb, + parent:params.parent, + cssClass:params.cssClass, + container:params.container, + tooltip:self.tooltip + }; + if (connector.constructor == String) + this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand. + else if (connector.constructor == Array) + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs)); + self.canvas = self.connector.canvas; + // binds mouse listeners to the current connector. + _bindListeners(self.connector, self, _internalHover); + if (!doNotRepaint) self.repaint(); + }; + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc) + */ + + self.setConnector(this.endpoints[0].connector || + this.endpoints[1].connector || + params.connector || + _currentInstance.Defaults.Connector || + jsPlumb.Defaults.Connector, true); + + + this.setPaintStyle(this.endpoints[0].connectorStyle || + this.endpoints[1].connectorStyle || + params.paintStyle || + _currentInstance.Defaults.PaintStyle || + jsPlumb.Defaults.PaintStyle, true); + + this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || + this.endpoints[1].connectorHoverStyle || + params.hoverPaintStyle || + _currentInstance.Defaults.HoverPaintStyle || + jsPlumb.Defaults.HoverPaintStyle, true); + + this.paintStyleInUse = this.paintStyle; + + + this.moveParent = function(newParent) { + var jpcl = jsPlumb.CurrentLibrary, curParent = jpcl.getParent(self.connector.canvas); + jpcl.removeElement(self.connector.canvas, curParent); + jpcl.appendElement(self.connector.canvas, newParent); + if (self.connector.bgCanvas) { + jpcl.removeElement(self.connector.bgCanvas, curParent); + jpcl.appendElement(self.connector.bgCanvas, newParent); + } + // this only applies for DOMOverlays + for (var i = 0; i < self.overlays.length; i++) { + if (self.overlays[i].isAppendedAtTopLevel) { + jpcl.removeElement(self.overlays[i].canvas, curParent); + jpcl.appendElement(self.overlays[i].canvas, newParent); + if (self.overlays[i].reattachListeners) + self.overlays[i].reattachListeners(self.connector); + } + } + if (self.connector.reattachListeners) // this is for SVG/VML; change an element's parent and you have to reinit its listeners. + self.connector.reattachListeners(); // the Canvas implementation doesn't have to care about this + }; + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Connection. + * + * Parameters: + * event - the event to bind. Available events on a Connection are: + * - *click* : notification that a Connection was clicked. + * - *dblclick* : notification that a Connection was double clicked. + * - *mouseenter* : notification that the mouse is over a Connection. + * - *mouseexit* : notification that the mouse exited a Connection. + * + * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Connection's paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Connection will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Connection. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId], + otherOffset = offsets[this.targetId], + otherWH = sizes[this.targetId], + initialTimestamp = _timestamp(), + anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + elementId:this.endpoints[0].elementId, + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1], + timestamp:initialTimestamp + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc, timestamp:initialTimestamp }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + elementId:this.endpoints[1].elementId, + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0], + timestamp:initialTimestamp + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc, timestamp:initialTimestamp }); + + /* + * Paints the Connection. Not exposed for public usage. + * + * Parameters: + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp, + // if the moving object is not the source we must transpose the two references. + swap = false, + tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId, + tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + + var sourceInfo = _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }), + targetInfo = _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var sE = this.endpoints[sIdx], tE = this.endpoints[tIdx], + sAnchorP = sE.anchor.getCurrentLocation(sE), + tAnchorP = tE.anchor.getCurrentLocation(tE); + + /* paint overlays*/ + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible()) maxSize = Math.max(maxSize, o.computeMaxSize(self.connector)); + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, + this.endpoints[sIdx], this.endpoints[tIdx], + this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, + self.paintStyleInUse.lineWidth, maxSize, + sourceInfo, + targetInfo); + + self.connector.paint(dim, self.paintStyleInUse); + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim); + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function(params) { + params = params || {}; + var recalc = !(params.recalc === false); + this.paint({ elId : this.sourceId, recalc : recalc, timestamp:params.timestamp }); + }; + + // just to make sure the UI gets initialised fully on all browsers. + self.repaint(); + }; + +// ENDPOINT HELPER FUNCTIONS + var _makeConnectionDragHandler = function(placeholder) { + var stopped = false; + return { + drag : function() { + if (stopped) return true; + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments), + el = placeholder.element; + if (el) { + jsPlumb.CurrentLibrary.setOffset(el, _ui); + _draw(_getElementObject(el), _ui); + } + }, + stopDrag : function() { + stopped = true; + } + }; + }; + + var _makeFloatingEndpoint = function(paintStyle, referenceAnchor, endpoint, referenceCanvas, sourceElement) { + var floatingAnchor = new FloatingAnchor( { reference : referenceAnchor, referenceCanvas : referenceCanvas }); + + //setting the scope here should not be the way to fix that mootools issue. it should be fixed by not + // adding the floating endpoint as a droppable. that makes more sense anyway! + + return _newEndpoint({ paintStyle : paintStyle, endpoint : endpoint, anchor : floatingAnchor, source : sourceElement, scope:"__floating" }); + }; + + /** + * creates a placeholder div for dragging purposes, adds it to the DOM, and pre-computes its offset. then returns + * both the element id and a selector for the element. + */ + var _makeDraggablePlaceholder = function(placeholder, parent) { + var n = document.createElement("div"); + n.style.position = "absolute"; + var placeholderDragElement = _getElementObject(n); + _appendElement(n, parent); + var id = _getId(placeholderDragElement); + _updateOffset( { elId : id }); + // create and assign an id, and initialize the offset. + placeholder.id = id; + placeholder.element = placeholderDragElement; + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1 + * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't + * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you + * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or + * 'target' Endpoints for Connections. + * + * + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * container - optional id or selector instructing jsPlumb where to attach the element it creates for this endpoint. you should read the documentation for a full discussion of this. + * connections - optional list of Connections to configure the Endpoint with. + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + var self = this; + self.idPrefix = "_jsplumb_e_"; + self.defaultLabelLocation = [ 0.5, 0.5 ]; + self.defaultOverlayKeys = ["Overlays", "EndpointOverlays"]; + this.parent = params.parent; + overlayCapableJsPlumbUIComponent.apply(this, arguments); + params = params || {}; + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Endpoint. + * + * Parameters: + * event - the event to bind. Available events on an Endpoint are: + * - *click* : notification that a Endpoint was clicked. + * - *dblclick* : notification that a Endpoint was double clicked. + * - *mouseenter* : notification that the mouse is over a Endpoint. + * - *mouseexit* : notification that the mouse exited a Endpoint. + * + * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Endpoint's paint style and then repaints the Endpoint. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Endpoint will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Endpoint. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + var visible = true; + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + var _element = params.source, _uuid = params.uuid, floatingEndpoint = null, inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + this.element = _element; + + var _connectionCost = params.connectionCost; + this.getConnectionCost = function() { return _connectionCost; }; + this.setConnectionCost = function(c) { + _connectionCost = c; + }; + + var _connectionsBidirectional = params.connectionsBidirectional === true; + this.areConnectionsBidirectional = function() { return _connectionsBidirectional; }; + this.setConnectionsBidirectional = function(b) { _connectionsBidirectional = b; }; + + self.anchor = params.anchor ? _currentInstance.makeAnchor(params.anchor, _elementId, _currentInstance) : params.anchors ? _currentInstance.makeAnchor(params.anchors, _elementId, _currentInstance) : _currentInstance.makeAnchor("TopCenter", _elementId, _currentInstance); + + // ANCHOR MANAGER + if (!params._transient) // in place copies, for example, are transient. they will never need to be retrieved during a paint cycle, because they dont move, and then they are deleted. + _currentInstance.anchorManager.add(self, _elementId); + + var _endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot", + endpointArgs = { + _jsPlumb:self._jsPlumb, + parent:params.parent, + container:params.container, + tooltip:params.tooltip, + connectorTooltip:params.connectorTooltip, + endpoint:self + }; + if (_endpoint.constructor == String) + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint](endpointArgs); + else if (_endpoint.constructor == Array) { + endpointArgs = jsPlumb.extend(_endpoint[1], endpointArgs); + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint[0]](endpointArgs); + } + else { + _endpoint = _endpoint.clone(); + } + + // assign a clone function using a copy of endpointArgs. this is used when a drag starts: the endpoint that was dragged is cloned, + // and the clone is left in its place while the original one goes off on a magical journey. + // the copy is to get around a closure problem, in which endpointArgs ends up getting shared by + // the whole world. + var argsForClone = jsPlumb.extend({}, endpointArgs); + _endpoint.clone = function() { + var o = new Object(); + _endpoint.constructor.apply(o, [argsForClone]); + return o; + }; + + self.endpoint = _endpoint; + self.type = self.endpoint.type; + // override setHover to pass it down to the underlying endpoint + var _sh = self.setHover; + self.setHover = function() { + self.endpoint.setHover.apply(self.endpoint, arguments); + _sh.apply(self, arguments); + }; + // endpoint delegates to first connection for hover, if there is one. + var internalHover = function(state) { + if (self.connections.length > 0) + self.connections[0].setHover(state, false); + else + self.setHover(state); + }; + + // bind listeners from endpoint to self, with the internal hover function defined above. + _bindListeners(self.endpoint, self, internalHover); + + this.setPaintStyle(params.paintStyle || + params.style || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyle, true); + this.setHoverPaintStyle(params.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyle, true); + this.paintStyleInUse = this.paintStyle; + this.connectorStyle = params.connectorStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.connectorTooltip = params.connectorTooltip; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + + var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + /* + * Property: canvas + * The Endpoint's Canvas. + */ + this.canvas = this.endpoint.canvas; + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + this.connections = params.connections || []; + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + self.isReattach = params.reattach || false; + self.connectionsDetachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.connectionsDetachable === false || params.detachable === false) + self.connectionsDetachable = false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget, forceDetach, fireEvent) { + //var idx = _findIndex(self.connections, connection), actuallyDetached = false; + var idx = _findWithFunction(self.connections, function(c) { return c.id == connection.id}), + actuallyDetached = false; + fireEvent = (fireEvent !== false); + if (idx >= 0) { + // 1. does the connection have a before detach (note this also checks jsPlumb's bound + // detach handlers; but then Endpoint's check will, too, hmm.) + if (forceDetach || connection._forceDetach || connection.isDetachable() || connection.isDetachAllowed(connection)) { + // get the target endpoint + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + // it would be nice to check with both endpoints that it is ok to detach. but + // for this we'll have to get a bit fancier: right now if you use the same beforeDetach + // interceptor for two endpoints (which is kind of common, because it's part of the + // endpoint definition), then it gets fired twice. so in fact we need to loop through + // each beforeDetach and see if it returns false, at which point we exit. but if it + // returns true, we have to check the next one. however we need to track which ones + // have already been run, and not run them again. + if (forceDetach || connection._forceDetach || (self.isDetachAllowed(connection) /*&& t.isDetachAllowed(connection)*/)) { + + self.connections.splice(idx, 1); + + // this avoids a circular loop + if (!ignoreTarget) { + + t.detach(connection, true, forceDetach); + // check connection to see if we want to delete the endpoints associated with it. + // we only detach those that have just this connection; this scenario is most + // likely if we got to this bit of code because it is set by the methods that + // create their own endpoints, like .connect or .makeTarget. the user is + // not likely to have interacted with those endpoints. + if (connection.endpointsToDeleteOnDetach){ + for (var i = 0; i < connection.endpointsToDeleteOnDetach.length; i++) { + var cde = connection.endpointsToDeleteOnDetach[i]; + if (cde && cde.connections.length == 0) + _currentInstance.deleteEndpoint(cde); + } + } + } + _removeElements(connection.connector.getDisplayElements(), connection.parent); + _removeWithFunction(connectionsByScope[connection.scope], function(c) { + return c.id == connection.id; + }); + actuallyDetached = true; + var doFireEvent = (!ignoreTarget && fireEvent) + fireDetachEvent(connection, doFireEvent); + } + } + } + return actuallyDetached; + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + * + * Parameters: + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachAll = function(fireEvent) { + while (self.connections.length > 0) { + self.detach(self.connections[0], false, true, fireEvent); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachFrom = function(targetEndpoint, fireEvent) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + if (self.detach(c[i], false, true, fireEvent)) + c[i].setHover(false, false); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: setElement + * Sets the DOM element this Endpoint is attached to. + */ + this.setElement = function(el) { + // TODO possibly have this object take charge of moving the UI components into the appropriate + // parent. this is used only by makeSource right now, and that function takes care of + // moving the UI bits and pieces. however it would s + var parentId = _getId(el); + // remove the endpoint from the list for the current endpoint's element + _removeWithFunction(endpointsByElement[_elementId], function(e) { + return e.id == self.id; + }); + _element = _getElementObject(el); + _elementId = _getId(_element); + self.elementId = _elementId; + + // need to get the new parent now + var newParentElement = _getParentFromParams({source:parentId}), + curParent = jpcl.getParent(self.canvas); + jpcl.removeElement(self.canvas, curParent); + jpcl.appendElement(self.canvas, newParentElement); + + // now move connection(s)...i would expect there to be only one but we will iterate. + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].moveParent(newParentElement); + self.connections[i].sourceId = _elementId; + self.connections[i].source = _element; + } + + _addToList(endpointsByElement, parentId, self); + _currentInstance.repaint(parentId); + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + return _newEndpoint( { + anchor : self.anchor, + source : _element, + paintStyle : this.paintStyle, + endpoint : _endpoint, + _transient:true, + scope:self.scope + }); + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + var candidate = self.connections[0]; + if (self.isTarget && candidate) return candidate; + else { + return (self.connections.length < _maxConnections) || _maxConnections == -1 ? null : candidate; + } + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + /* + * Function: setStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to + * setStyle and deprecate it if so. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + * + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + // a helper function that tries to find a connection to the given element, and returns it if so. if elementWithPrecedence is null, + // or no connection to it is found, we return the first connection in our list. + var findConnectionToUseForDynamicAnchor = function(elementWithPrecedence) { + var idx = 0; + if (elementWithPrecedence != null) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].sourceId == elementWithPrecedence || self.connections[i].targetId == elementWithPrecedence) { + idx = i; + break; + } + } + } + + return self.connections[idx]; + }; + + /* + * Function: paint + * Paints the Endpoint, recalculating offset and anchor positions if necessary. This does NOT paint + * any of the Endpoint's connections. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + params = params || {}; + var timestamp = params.timestamp, + recalc = !(params.recalc === false); + if (!timestamp || self.timestamp !== timestamp) { + _updateOffset({ elId:_elementId, timestamp:timestamp, recalc:recalc }); + var xy = params.offset || offsets[_elementId]; + if(xy) { + var ap = params.anchorPoint,connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (recalc && self.anchor.isDynamic && self.connections.length > 0) { + var c = findConnectionToUseForDynamicAnchor(params.elementWithPrecedence), + oIdx = c.endpoints[0] == self ? 1 : 0, + oId = oIdx == 0 ? c.sourceId : c.targetId, + oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + ap = self.anchor.compute(anchorParams); + } + + var d = _endpoint.compute(ap, self.anchor.getOrientation(_endpoint), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + _endpoint.paint(d, self.paintStyleInUse, self.anchor); + self.timestamp = timestamp; + + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) self.overlayPlacements[i] = o.draw(self.endpoint, self.paintStyleInUse, d); + } + } + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var placeholderInfo = { + id:null, + element:null + }, + jpc = null, + existingJpc = false, + existingJpcParams = null, + _dragHandler = _makeConnectionDragHandler(placeholderInfo); + + var start = function() { + // drag might have started on an endpoint that is not actually a source, but which has + // one or more connections. + jpc = self.connectorSelector(); + var _continue = true; + // if no connection and we're not a source, return. + if (jpc == null && !params.isSource) _continue = false; + // otherwise if we're full and not allowed to drag, also return false. + if (params.isSource && self.isFull() && !dragAllowedWhenFull) _continue = false; + + // if the connection was setup as not detachable (or one of its endpoints + // was setup as connectionsDetachable = false, or Defaults.ConnectionsDetachable + // is set to false... + if (jpc != null && !jpc.isDetachable()) _continue = false; + + if (_continue === false) { + // this is for mootools and yui. returning false from this causes jquery to stop drag. + // the events are wrapped in both mootools and yui anyway, but i don't think returning + // false from the start callback would stop a drag. + if (jsPlumb.CurrentLibrary.stopDrag) jsPlumb.CurrentLibrary.stopDrag(); + _dragHandler.stopDrag(); + return false; + } + + // if we're not full but there was a connection, make it null. we'll create a new one. + if (jpc && !self.isFull() && params.isSource) jpc = null; + + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + _makeDraggablePlaceholder(placeholderInfo, self.parent); + + // set the offset of this div to be where 'inPlaceCopy' is, to start with. + // TODO merge this code with the code in both Anchor and FloatingAnchor, because it + // does the same stuff. + var ipcoel = _getElementObject(inPlaceCopy.canvas), + ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel), + po = adjustForParentOffsetAndScroll([ipco.left, ipco.top], inPlaceCopy.canvas); + jsPlumb.CurrentLibrary.setOffset(placeholderInfo.element, {left:po[0], top:po[1]}); + + // when using makeSource and a parent, we first draw the source anchor on the source element, then + // move it to the parent. note that this happens after drawing the placeholder for the + // first time. + if (self.parentAnchor) self.anchor = _currentInstance.makeAnchor(self.parentAnchor, self.elementId, _currentInstance); + + + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", placeholderInfo.id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + floatingEndpoint = _makeFloatingEndpoint(self.paintStyle, self.anchor, _endpoint, self.canvas, placeholderInfo.element); + + if (jpc == null) { + self.anchor.locked = true; + self.setHover(false, false); + // TODO the hover call above does not reset any target endpoint's hover + // states. + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : self.endpointWillMoveTo || _getElementObject(_element), // for makeSource with parent option. ensure source element is represented correctly. + target : placeholderInfo.element, + anchors : [ self.anchor, floatingEndpoint.anchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays + }); + + } else { + existingJpc = true; + jpc.connector.setHover(false, false); + // jpc.endpoints[0].setHover(false, false); + //jpc.endpoints[1].setHover(false, false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas), false, true); + //var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; // are we the source or the target? + // new anchor idx + var anchorIdx = jpc.endpoints[0].id == self.id ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas), + dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // now we want to get this endpoint's DROP scope, and set it for now: we can only be dropped on drop zones + // that have our drop scope (issue 57). + var dropScope = jsPlumb.CurrentLibrary.getDropScope(c); + jsPlumb.CurrentLibrary.setDragScope(c, dropScope); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = placeholderInfo.element; + jpc.sourceId = placeholderInfo.id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = placeholderInfo.element; + jpc.targetId = placeholderInfo.id; + } + + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.suspendedEndpoint.setHover(false); + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + // register it and register connection on it. + floatingConnections[placeholderInfo.id] = jpc; + floatingEndpoint.addConnection(jpc); + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, placeholderInfo.id, floatingEndpoint); + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var jpcl = jsPlumb.CurrentLibrary, + dragOptions = params.dragOptions || {}, + defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions), + startEvent = jpcl.dragEvents["start"], + stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"]; + + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + // extracted drag handler function so can be used by makeSource + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], _dragHandler.drag); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _currentInstance.currentlyDragging = false; + _removeWithFunction(endpointsByElement[placeholderInfo.id], function(e) { + return e.id == floatingEndpoint.id; + }); + _removeElements( [ placeholderInfo.element[0], floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + _currentInstance.anchorManager.clearFor(placeholderInfo.id); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (self.isReattach || jpc._forceDetach || !jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc)) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + _currentInstance.repaint(existingJpcParams[1]); + } + jpc._forceDetach = null; + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElements(jpc.connector.getDisplayElements(), self.parent); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint({recalc:false}); + jpc.setHover(false, false); + jpc = null; + inPlaceCopy = null; + delete endpointsByElement[floatingEndpoint.elementId]; + floatingEndpoint.anchor = null; + floatingEndpoint = null; + _currentInstance.currentlyDragging = false; + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions, true); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas, forceInit, isTransient) { + if ((params.isTarget || forceInit) && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop'], + overEvent = jsPlumb.CurrentLibrary.dragEvents['over'], + outEvent = jsPlumb.CurrentLibrary.dragEvents['out'], + drop = function(originalEvent) { + var draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + elId = _getAttribute(draggable, "elId"), + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id], + idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + + // restore the original scope if necessary (issue 57) + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + + var _doContinue = true; + // the second check here is for the case that the user is dropping it back + // where it came from. + if (jpc.suspendedEndpoint && jpc.suspendedEndpoint.id != self.id) { + if (!jpc.isDetachAllowed(jpc) || !jpc.endpoints[idx].isDetachAllowed(jpc) || !jpc.suspendedEndpoint.isDetachAllowed(jpc) || !_currentInstance.checkCondition("beforeDetach", jpc)) + _doContinue = false; + } + + // these have to be set before testing for beforeDrop. + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + + // now check beforeDrop. this will be available only on Endpoints that are setup to + // have a beforeDrop condition (although, secretly, under the hood all Endpoints and + // the Connection have them, because they are on jsPlumbUIComponent. shhh!), because + // it only makes sense to have it on a target endpoint. + _doContinue = _doContinue && self.isDropAllowed(jpc.sourceId, jpc.targetId, jpc.scope); + + if (_doContinue) { + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + if (!jpc.suspendedEndpoint) { + //_addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + fireDetachEvent({ + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }, true); + } + + // finalise will inform the anchor manager and also add to + // connectionsByScope if necessary. + _finaliseConnection(jpc, null, originalEvent); + } + else { + // otherwise just put it back on the endpoint it was on before the drag. + if (jpc.suspendedEndpoint) { + // self.detachFrom(jpc); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.setHover(false); + jpc._forceDetach = true; + if (idx == 0) { + jpc.source = jpc.suspendedEndpoint.element; + jpc.sourceId = jpc.suspendedEndpoint.elementId; + } else { + jpc.target = jpc.suspendedEndpoint.element; + jpc.targetId = jpc.suspendedEndpoint.elementId;; + } + jpc.suspendedEndpoint.addConnection(jpc); + + jpc.endpoints[0].repaint(); + jpc.repaint(); + _currentInstance.repaint(jpc.source.elementId); + jpc._forceDetach = false; + } + } + + jpc.floatingAnchorIndex = null; + } + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], function() { + if (self.isTarget) { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + } + } + }); + dropOptions[outEvent] = _wrap(dropOptions[outEvent], function() { + if (self.isTarget) { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + } + } + }); + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions, true, isTransient); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas), true, !(params._transient || self.anchor.isFloating)); + + return self; + }; + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + j.init(); + return j; + }; + jsPlumb.util = { + convertStyle : function(s, ignoreAlpha) { + // TODO: jsPlumb should support a separate 'opacity' style member. + if ("transparent" === s) return s; + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + return o; + }, + gradient : function(p1, p2) { + p1 = p1.constructor == Array ? p1 : [p1.x, p1.y]; + p2 = p2.constructor == Array ? p2 : [p2.x, p2.y]; + return (p2[1] - p1[1]) / (p2[0] - p1[0]); + }, + normal : function(p1, p2) { + return -1 / jsPlumb.util.gradient(p1,p2); + }, + segment : function(p1, p2) { + p1 = p1.constructor == Array ? p1 : [p1.x, p1.y]; + p2 = p2.constructor == Array ? p2 : [p2.x, p2.y]; + if (p2[0] > p1[0]) { + return (p2[1] > p1[1]) ? 2 : 1; + } + else { + return (p2[1] > p1[1]) ? 3 : 4; + } + }, + segmentMultipliers : [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ], + inverseSegmentMultipliers : [null, [-1, -1], [-1, 1], [1, 1], [1, -1] ], + pointOnLine : function(fromPoint, toPoint, distance) { + var m = jsPlumb.util.gradient(fromPoint, toPoint), + s = jsPlumb.util.segment(fromPoint, toPoint), + segmentMultiplier = distance > 0 ? jsPlumb.util.segmentMultipliers[s] : jsPlumb.util.inverseSegmentMultipliers[s], + theta = Math.atan(m), + y = Math.abs(distance * Math.sin(theta)) * segmentMultiplier[1], + x = Math.abs(distance * Math.cos(theta)) * segmentMultiplier[0]; + return { x:fromPoint.x + x, y:fromPoint.y + y }; + }, + /** + * calculates a perpendicular to the line fromPoint->toPoint, that passes through toPoint and is 'length' long. + * @param fromPoint + * @param toPoint + * @param length + */ + perpendicularLineTo : function(fromPoint, toPoint, length) { + var m = jsPlumb.util.gradient(fromPoint, toPoint), + theta2 = Math.atan(-1 / m), + y = length / 2 * Math.sin(theta2), + x = length / 2 * Math.cos(theta2); + return [{x:toPoint.x + x, y:toPoint.y + y}, {x:toPoint.x - x, y:toPoint.y - y}]; + } + }; + + var _curryAnchor = function(x, y, ox, oy, type, fnInit) { + return function(params) { + params = params || {}; + //var a = jsPlumb.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance); + var a = params.jsPlumbInstance.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance); + a.type = type; + if (fnInit) fnInit(a, params); + return a; + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1, "TopCenter"); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1, "BottomCenter"); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0, "LeftMiddle"); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0, "RightMiddle"); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0, "Center"); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1, "TopRight"); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1, "BottomRight"); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1, "TopLeft"); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1, "BottomLeft"); + + // TODO test that this does not break with the current instance idea + jsPlumb.Defaults.DynamicAnchors = function(params) { + return params.jsPlumbInstance.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"], params.elementId, params.jsPlumbInstance); + }; + jsPlumb.Anchors["AutoDefault"] = function(params) { + var a = params.jsPlumbInstance.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors(params)); + a.type = "AutoDefault"; + return a; + }; + + jsPlumb.Anchors["Assign"] = _curryAnchor(0,0,0,0,"Assign", function(anchor, params) { + // find what to use as the "position finder". the user may have supplied a String which represents + // the id of a position finder in jsPlumb.AnchorPositionFinders, or the user may have supplied the + // position finder as a function. we find out what to use and then set it on the anchor. + var pf = params.position || "Fixed"; + anchor.positionFinder = pf.constructor == String ? params.jsPlumbInstance.AnchorPositionFinders[pf] : pf; + // always set the constructor params; the position finder might need them later (the Grid one does, + // for example) + anchor.constructorParams = params; + }); + + // Continuous anchor is just curried through to the 'get' method of the continuous anchor + // factory. + jsPlumb.Anchors["Continuous"] = function(params) { + return params.jsPlumbInstance.continuousAnchorFactory.get(params); + }; + + // these are the default anchor positions finders, which are used by the makeTarget function. supply + // a position finder argument to that function allows you to specify where the resulting anchor will + // be located + jsPlumb.AnchorPositionFinders = { + "Fixed": function(dp, ep, es, a) { + return [ (dp.left - ep.left) / es[0], (dp.top - ep.top) / es[1] ]; + }, + "Grid":function(dp, ep, es, a) { + var dx = dp.left - ep.left, dy = dp.top - ep.top, + gx = es[0] / (a.constructorParams.grid[0]), gy = es[1] / (a.constructorParams.grid[1]), + mx = Math.floor(dx / gx), my = Math.floor(dy / gy); + return [ ((mx * gx) + (gx / 2)) / es[0], ((my * gy) + (gy / 2)) / es[1] ]; + } + }; +})(); +/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the default Connectors, Endpoint and Overlay definitions. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +(function() { + + /** + * + * Helper class to consume unused mouse events by components that are DOM elements and + * are used by all of the different rendering modes. + * + */ + jsPlumb.DOMElementComponent = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + // when render mode is canvas, these functions may be called by the canvas mouse handler. + // this component is safe to pipe this stuff to /dev/null. + this.mousemove = + this.dblclick = + this.click = + this.mousedown = + this.mouseup = function(e) { }; + }; + + /** + * Class: Connectors.Straight + * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters. + */ + jsPlumb.Connectors.Straight = function() { + this.type = "Straight"; + var self = this, + currentPoints = null, + _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty, _segment, _length; + + /** + * Computes the new size and position of the canvas. + */ + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + _dx = _tx - _sx, _dy = _ty - _sy; + //_m = _dy / _dx, _m2 = -1 / _m; + _m = jsPlumb.util.gradient({x:_sx, y:_sy}, {x:_tx, y:_ty}), _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + //_segment = jsPlumb.util.segment({x:_sx, y:_sy}, {x:_tx, y:_ty}); + _length = Math.sqrt((_dx * _dx) + (_dy * _dy)); + + return currentPoints; + }; + + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + if (location == 0) + return { x:_sx, y:_sy }; + else if (location == 1) + return { x:_tx, y:_ty }; + else + return jsPlumb.util.pointOnLine({x:_sx, y:_sy}, {x:_tx, y:_ty}, location * _length); + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { + return _m; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + * this hands off to jsPlumb.util to do the maths, supplying our gradient and position and whether or + * not the coords should be flipped + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + return jsPlumb.util.pointOnLine(p, {x:_tx,y:_ty}, distance); + }; + }; + + + /** + * Class:Connectors.Bezier + * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's + * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below. + */ + /** + * Function:Constructor + * + * Parameters: + * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line. + * Optional; defaults to 150. + * + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + params = params || {}; + this.majorAnchor = params.curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + this.type = "Bezier"; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(sourceEndpoint), + too = targetAnchor.getOrientation(targetEndpoint), + perpendicular = soo[0] != too[0] || soo[1] == too[1], + p = [], + ma = self.majorAnchor, mi = self.minorAnchor; + + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, sourceEndpoint, targetEndpoint, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx), minx2 = Math.min(_CP[0], _CP2[0]), minx = Math.min(minx1,minx2), + maxx1 = Math.max(_sx,_tx), maxx2 = Math.max(_CP[0], _CP2[0]), maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty), miny2 = Math.min(_CP[1], _CP2[1]), miny = Math.min(miny1,miny2), + maxy1 = Math.max(_sy,_ty), maxy2 = Math.max(_CP[1], _CP2[1]), maxy = Math.max(maxy1,maxy2); + + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + }; + + + /** + * Class: Connectors.Flowchart + * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments. + */ + /** + * Function: Constructor + * + * Parameters: + * stub - minimum length for the stub at each end of the connector. defaults to 30 pixels. + */ + jsPlumb.Connectors.Flowchart = function(params) { + this.type = "Flowchart"; + params = params || {}; + var self = this, + minStubLength = params.stub || params.minStubLength /* bwds compat. */ || 30, + segments = [], + totalLength = 0, + segmentProportions = [], + segmentProportionalLengths = [], + points = [], + swapX, + swapY, + /** + * recalculates the points at which the segments begin and end, proportional to the total length travelled + * by all the segments that constitute the connector. we use this to help with pointOnPath calculations. + */ + updateSegmentProportions = function(startX, startY, endX, endY) { + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segments[i][5] / totalLength; + segmentProportions[i] = [curLoc, (curLoc += (segments[i][5] / totalLength)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0], + ly = segments.length == 0 ? sy : segments[segments.length - 1][1], + m = x == lx ? Infinity : 0, + l = Math.abs(x == lx ? y - ly : x - lx); + segments.push([x, y, lx, ly, m, l]); + totalLength += l; + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment + * that contains the point which is 'location' distance along the entire path, where + * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths + * are made up of a list of segments, each of which contributes some fraction to + * the total length. + */ + findSegmentForLocation = function(location) { + var idx = segmentProportions.length - 1, inSegmentProportion = 0; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, + sourceAnchor, targetAnchor, lineWidth, minWidth, sourceInfo, targetInfo) { + + segments = []; + totalLength = 0; + segmentProportionalLengths = []; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + offx = (lw / 2) + (minStubLength * 2), + offy = (lw / 2) + (minStubLength * 2), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(sourceEndpoint), + to = targetAnchor.orientation || targetAnchor.getOrientation(targetEndpoint), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx, + h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy; + + // if either anchor does not have an orientation set, we derive one from their relative + // positions. we fix the axis to be the one in which the two elements are further apart, and + // point each anchor at the other element. this is also used when dragging a new connection. + if (so[0] == 0 && so[1] == 0 || to[0] == 0 && to[1] == 0) { + var index = w > h ? 0 : 1, oIndex = [1,0][index]; + so = []; to = []; + so[index] = sourcePos[index] > targetPos[index] ? -1 : 1; + to[index] = sourcePos[index] > targetPos[index] ? 1 : -1; + so[oIndex] = 0; + to[oIndex] = 0; + } + + if (w < minWidth) { + offx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + offy += (minWidth - h) / 2; + h = minWidth; + } + + var sx = swapX ? w-offx : offx, + sy = swapY ? h-offy : offy, + tx = swapX ? offx : w-offx , + ty = swapY ? offy : h-offy, + startStubX = sx + (so[0] * minStubLength), + startStubY = sy + (so[1] * minStubLength), + endStubX = tx + (to[0] * minStubLength), + endStubY = ty + (to[1] * minStubLength), + isXGreaterThanStubTimes2 = Math.abs(sx - tx) > 2 * minStubLength, + isYGreaterThanStubTimes2 = Math.abs(sy - ty) > 2 * minStubLength, + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2), + oProduct = ((so[0] * to[0]) + (so[1] * to[1])), + opposite = oProduct == -1, + perpendicular = oProduct == 0, + orthogonal = oProduct == 1; + + x -= offx; y -= offy; + points = [x, y, w, h, sx, sy, tx, ty]; + var extraPoints = []; + + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + var sourceAxis = so[0] == 0 ? "y" : "x", + anchorOrientation = opposite ? "opposite" : orthogonal ? "orthogonal" : "perpendicular", + segment = jsPlumb.util.segment([sx, sy], [tx, ty]), + flipSourceSegments = so[sourceAxis == "x" ? 0 : 1] == -1, + flipSegments = { + "x":[null, 4, 3, 2, 1], + "y":[null, 2, 1, 4, 3] + } + + if (flipSourceSegments) + segment = flipSegments[sourceAxis][segment]; + + var findClearedLine = function(start, mult, anchorPos, dimension) { + return start + (mult * (( 1 - anchorPos) * dimension) + minStubLength); + //mx = so[0] == 0 ? startStubX + ((1 - sourceAnchor.x) * sourceInfo.width) + minStubLength : startStubX, + }, + + lineCalculators = { + oppositex : function() { + if (sourceEndpoint.elementId == targetEndpoint.elementId) { + var _y = startStubY + ((1 - sourceAnchor.y) * sourceInfo.height) + minStubLength; + return [ [ startStubX, _y ], [ endStubX, _y ]]; + } + else if (isXGreaterThanStubTimes2 && (segment == 1 || segment == 2)) { + return [[ midx, sy ], [ midx, ty ]]; + } + else { + return [[ startStubX, midy ], [endStubX, midy ]]; + } + }, + orthogonalx : function() { + if (segment == 1 || segment == 2) { + return [ [ endStubX, startStubY ]]; + } + else { + return [ [ startStubX, endStubY ]]; + } + }, + perpendicularx : function() { + var my = (ty + sy) / 2; + if ((segment == 1 && to[1] == 1) || (segment == 2 && to[1] == -1)) { + if (Math.abs(tx - sx) > minStubLength) + return [ [endStubX, startStubY ]]; + else + return [ [startStubX, startStubY ], [ startStubX, my ], [ endStubX, my ]]; + } + else if ((segment == 3 && to[1] == -1) || (segment == 4 && to[1] == 1)) { + return [ [ startStubX, my ], [ endStubX, my ]]; + } + else if ((segment == 3 && to[1] == 1) || (segment == 4 && to[1] == -1)) { + return [ [ startStubX, endStubY ]]; + } + else if ((segment == 1 && to[1] == -1) || (segment == 2 && to[1] == 1)) { + if (Math.abs(tx - sx) > minStubLength) + return [ [ midx, startStubY ], [ midx, endStubY ]]; + else + return [ [ startStubX, endStubY ]]; + } + }, + oppositey : function() { + if (sourceEndpoint.elementId == targetEndpoint.elementId) { + var _x = startStubX + ((1 - sourceAnchor.x) * sourceInfo.width) + minStubLength; + return [ [ _x, startStubY ], [ _x, endStubY ]]; + } + else if (isYGreaterThanStubTimes2 && (segment == 2 || segment == 3)) { + return [[ sx, midy ], [ tx, midy ]]; + } + else { + return [[ midx, startStubY ], [midx, endStubY ]]; + } + }, + orthogonaly : function() { + if (segment == 2 || segment == 3) { + return [ [ startStubX, endStubY ]]; + } + else { + return [ [ endStubX, startStubY ]]; + } + }, + perpendiculary : function() { + var mx = (tx + sx) / 2; + if ((segment == 2 && to[0] == -1) || (segment == 3 && to[0] == 1)) { + if (Math.abs(tx - sx) > minStubLength) + return [ [startStubX, endStubY ]]; + else + return [ [startStubX, midy ], [ endStubX, midy ]]; + } + else if ((segment == 1 && to[0] == -1) || (segment == 4 && to[0] == 1)) { + var mx = (tx + sx) / 2; + return [ [ mx, startStubY ], [ mx, endStubY ]]; + } + else if ((segment == 1 && to[0] == 1) || (segment == 4 && to[0] == -1)) { + return [ [ endStubX, startStubY ]]; + } + else if ((segment == 2 && to[0] == 1) || (segment == 3 && to[0] == -1)) { + if (Math.abs(ty - sy) > minStubLength) + return [ [ startStubX, midy ], [ endStubX, midy ]]; + else + return [ [ endStubX, startStubY ]]; + } + } + }; + + + + var pointFinder = lineCalculators[anchorOrientation + sourceAxis]; + var p = pointFinder(); + if (p) { + for (var i = 0; i < p.length; i++) { + addSegment(p[i][0], p[i][1], sx, sy, tx, ty); + } + } + + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentProportions(sx, sy, tx, ty); + + return points; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location) { + return self.pointAlongPathFrom(location, 0); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location) { + return segments[findSegmentForLocation(location)["index"]][4]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance) { + var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segments[s.index][5], m = segments[s.index][4]; + var e = { + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + return e; + }; + }; + + // ********************************* END OF CONNECTOR TYPES ******************************************************************* + + // ********************************* ENDPOINT TYPES ******************************************************************* + + /** + * Class: Endpoints.Dot + * A round endpoint, with default radius 10 pixels. + */ + + /** + * Function: Constructor + * + * Parameters: + * + * radius - radius of the endpoint. defaults to 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + this.type = "Dot"; + var self = this; + params = params || {}; + this.radius = params.radius || 10; + this.defaultOffset = 0.5 * this.radius; + this.defaultInnerRadius = this.radius / 3; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var r = endpointStyle.radius || self.radius, + x = anchorPoint[0] - r, + y = anchorPoint[1] - r; + return [ x, y, r * 2, r * 2, r ]; + }; + }; + + /** + * Class: Endpoints.Rectangle + * A Rectangular Endpoint, with default size 20x20. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the endpoint. defaults to 20 pixels. + * height - height of the endpoint. defaults to 20 pixels. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + this.type = "Rectangle"; + var self = this; + params = params || {}; + this.width = params.width || 20; + this.height = params.height || 20; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height]; + }; + }; + + + var DOMElementEndpoint = function(params) { + jsPlumb.DOMElementComponent.apply(this, arguments); + var self = this; + + var displayElements = [ ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + }; + /** + * Class: Endpoints.Image + * Draws an image as the Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * src - location of the image to use. + */ + jsPlumb.Endpoints.Image = function(params) { + + this.type = "Image"; + DOMElementEndpoint.apply(this, arguments); + + var self = this, + initialized = false, + widthToUse = params.width, + heightToUse = params.height, + _onload = null, + _endpoint = params.endpoint; + + this.img = new Image(); + self.ready = false; + + this.img.onload = function() { + self.ready = true; + widthToUse = widthToUse || self.img.width; + heightToUse = heightToUse || self.img.height; + if (_onload) { + _onload(self); + } + }; + + _endpoint.setImage = function(img, onload) { + var s = img.constructor == String ? img : img.src; + _onload = onload; + self.img.src = img; + }; + + _endpoint.setImage(params.src || params.url, params.onload); + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + self.anchorPoint = anchorPoint; + if (self.ready) return [anchorPoint[0] - widthToUse / 2, anchorPoint[1] - heightToUse / 2, + widthToUse, heightToUse]; + else return [0,0,0,0]; + }; + + self.canvas = document.createElement("img"), initialized = false; + self.canvas.style["margin"] = 0; + self.canvas.style["padding"] = 0; + self.canvas.style["outline"] = 0; + self.canvas.style["position"] = "absolute"; + var clazz = params.cssClass ? " " + params.cssClass : ""; + self.canvas.className = jsPlumb.endpointClass + clazz; + if (widthToUse) self.canvas.setAttribute("width", widthToUse); + if (heightToUse) self.canvas.setAttribute("height", heightToUse); + jsPlumb.appendElement(self.canvas, params.parent); + self.attachListeners(self.canvas, self); + + var actuallyPaint = function(d, style, anchor) { + if (!initialized) { + self.canvas.setAttribute("src", self.img.src); + initialized = true; + } + var x = self.anchorPoint[0] - (widthToUse / 2), + y = self.anchorPoint[1] - (heightToUse / 2); + jsPlumb.sizeCanvas(self.canvas, x, y, widthToUse, heightToUse); + }; + + this.paint = function(d, style, anchor) { + if (self.ready) { + actuallyPaint(d, style, anchor); + } + else { + window.setTimeout(function() { + self.paint(d, style, anchor); + }, 200); + } + }; + }; + + /** + * Class: Endpoints.Blank + * An Endpoint that paints nothing (visible) on the screen. Supports cssClass and hoverClass parameters like all Endpoints. + */ + jsPlumb.Endpoints.Blank = function(params) { + var self = this; + this.type = "Blank"; + DOMElementEndpoint.apply(this, arguments); + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + return [anchorPoint[0], anchorPoint[1],10,0]; + }; + + self.canvas = document.createElement("div"); + self.canvas.style.display = "block"; + self.canvas.style.width = "1px"; + self.canvas.style.height = "1px"; + self.canvas.style.background = "transparent"; + self.canvas.style.position = "absolute"; + self.canvas.className = self._jsPlumb.endpointClass; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + }; + }; + + /** + * Class: Endpoints.Triangle + * A triangular Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the triangle's base. defaults to 55 pixels. + * height - height of the triangle from base to apex. defaults to 55 pixels. + */ + jsPlumb.Endpoints.Triangle = function(params) { + this.type = "Triangle"; + params = params || { }; + params.width = params.width || 55; + params.height = params.height || 55; + this.width = params.width; + this.height = params.height; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height ]; + }; + }; +// ********************************* END OF ENDPOINT TYPES ******************************************************************* + + +// ********************************* OVERLAY DEFINITIONS *********************************************************************** + + var AbstractOverlay = function(params) { + var visible = true, self = this; + this.isAppendedAtTopLevel = true; + this.component = params.component; + this.loc = params.location == null ? 0.5 : params.location; + this.endpointLoc = params.endpointLocation == null ? [ 0.5, 0.5] : params.endpointLocation; + this.setVisible = function(val) { + visible = val; + self.component.repaint(); + }; + this.isVisible = function() { return visible; }; + this.hide = function() { self.setVisible(false); }; + this.show = function() { self.setVisible(true); }; + + this.incrementLocation = function(amount) { + self.loc += amount; + self.component.repaint(); + }; + this.setLocation = function(l) { + self.loc = l; + self.component.repaint(); + }; + this.getLocation = function() { + return self.loc; + }; + }; + + + /** + * Class: Overlays.Arrow + * + * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length + * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction + * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line + * across the tail. + */ + /** + * Function: Constructor + * + * Parameters: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + this.type = "Arrow"; + AbstractOverlay.apply(this, arguments); + this.isAppendedAtTopLevel = false; + params = params || {}; + var self = this; + + this.length = params.length || 20; + this.width = params.width || 20; + this.id = params.id; + var direction = (params.direction || 1) < 0 ? -1 : 1, + paintStyle = params.paintStyle || { lineWidth:1 }, + // how far along the arrow the lines folding back in come to. default is 62.3%. + foldback = params.foldback || 0.623; + + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.cleanup = function() { }; // nothing to clean up for Arrows + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var hxy, mid, txy, tail, cxy; + if (connector.pointAlongPathFrom) { + + if (self.loc == 1) { + hxy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, -1); + txy = jsPlumb.util.pointOnLine(hxy, mid, self.length); + } + else if (self.loc == 0) { + txy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, 1); + hxy = jsPlumb.util.pointOnLine(txy, mid, self.length); + } + else { + hxy = connector.pointAlongPathFrom(self.loc, direction * self.length / 2), + mid = connector.pointOnPath(self.loc), + txy = jsPlumb.util.pointOnLine(hxy, mid, self.length); + } + + tail = jsPlumb.util.perpendicularLineTo(hxy, txy, self.width); + cxy = jsPlumb.util.pointOnLine(hxy, txy, foldback * self.length); + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x), + maxx = Math.max(hxy.x, tail[0].x, tail[1].x), + miny = Math.min(hxy.y, tail[0].y, tail[1].y), + maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + var d = { hxy:hxy, tail:tail, cxy:cxy }, + strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle, + fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle, + lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth; + + self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions); + + return [ minx, maxx, miny, maxy]; + } + else return [0,0,0,0]; + }; + }; + + /** + * Class: Overlays.PlainArrow + * + * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "PlainArrow"; + }; + + /** + * Class: Overlays.Diamond + * + * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40, + p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "Diamond"; + }; + + + + /** + * Class: Overlays.Label + * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb + * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter + * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it + * puts on the Label's 'style' attribute, so the end result is the same. + */ + /** + * Function: Constructor + * + * Parameters: + * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes + * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle. + * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * + */ + jsPlumb.Overlays.Label = function(params) { + this.type = "Label"; + jsPlumb.DOMElementComponent.apply(this, arguments); + AbstractOverlay.apply(this, arguments); + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.id = params.id; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var label = params.label || "", + self = this, + initialised = false, + div = document.createElement("div"), + labelText = null; + div.style["position"] = "absolute"; + + var clazz = params["_jsPlumb"].overlayClass + " " + + (self.labelStyle.cssClass ? self.labelStyle.cssClass : + params.cssClass ? params.cssClass : ""); + + div.className = clazz; + + jsPlumb.appendElement(div, params.component.parent); + jsPlumb.getId(div); + self.attachListeners(div, self); + self.canvas = div; + + //override setVisible + var osv = self.setVisible; + self.setVisible = function(state) { + osv(state); // call superclass + div.style.display = state ? "block" : "none"; + }; + + this.getElement = function() { + return div; + }; + + this.cleanup = function() { + if (div != null) jsPlumb.CurrentLibrary.removeElement(div); + }; + + /* + * Function: setLabel + * sets the label's, um, label. you would think i'd call this function + * 'setText', but you can pass either a Function or a String to this, so + * it makes more sense as 'setLabel'. + */ + this.setLabel = function(l) { + label = l; + labelText = null; + self.component.repaint(); + }; + + this.getLabel = function() { + return label; + }; + + this.paint = function(component, d, componentDimensions) { + if (!initialised) { + component.appendDisplayElement(div); + self.attachListeners(div, component); + initialised = true; + } + div.style.left = (componentDimensions[0] + d.minx) + "px"; + div.style.top = (componentDimensions[1] + d.miny) + "px"; + }; + + this.getTextDimensions = function() { + if (typeof label == "function") { + var lt = label(self); + div.innerHTML = lt.replace(/\r\n/g, "
"); + } + else { + if (labelText == null) { + labelText = label; + div.innerHTML = labelText.replace(/\r\n/g, "
"); + } + } + var de = jsPlumb.CurrentLibrary.getElementObject(div), + s = jsPlumb.CurrentLibrary.getSize(de); + return {width:s[0], height:s[1]}; + }; + + this.computeMaxSize = function(connector) { + var td = self.getTextDimensions(connector); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + + this.draw = function(component, currentConnectionPaintStyle, componentDimensions) { + var td = self.getTextDimensions(component); + if (td.width != null) { + var cxy = {x:0,y:0}; + if (component.pointOnPath) + cxy = component.pointOnPath(self.loc); // a connection + else { + var locToUse = self.loc.constructor == Array ? self.loc : self.endpointLoc; + cxy = { x:locToUse[0] * componentDimensions[2], + y:locToUse[1] * componentDimensions[3] }; + } + + minx = cxy.x - (td.width / 2), + miny = cxy.y - (td.height / 2); + + self.paint(component, { + minx:minx, + miny:miny, + td:td, + cxy:cxy + }, componentDimensions); + + return [minx, minx+td.width, miny, miny+td.height]; + } + else return [0,0,0,0]; + }; + + this.reattachListeners = function(connector) { + if (div) { + self.reattachListenersForElement(div, self, connector); + } + }; + }; + + // this is really just a test overlay, so its undocumented and doesnt take any parameters. but i was loth to delete it. + jsPlumb.Overlays.GuideLines = function() { + var self = this; + self.length = 50; + self.lineWidth = 5; + this.type = "GuideLines"; + AbstractOverlay.apply(this, arguments); + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var head = connector.pointAlongPathFrom(self.loc, self.length / 2), + mid = connector.pointOnPath(self.loc), + tail = jsPlumb.util.pointOnLine(head, mid, self.length), + tailLine = jsPlumb.util.perpendicularLineTo(head, tail, 40), + headLine = jsPlumb.util.perpendicularLineTo(tail, head, 20); + + self.paint(connector, [head, tail, tailLine, headLine], self.lineWidth, "red", null, connectorDimensions); + + return [Math.min(head.x, tail.x), Math.min(head.y, tail.y), Math.max(head.x, tail.x), Math.max(head.y,tail.y)]; + }; + + this.computeMaxSize = function() { return 50; }; + + this.cleanup = function() { }; // nothing to clean up for GuideLines + }; + + // ********************************* END OF OVERLAY DEFINITIONS *********************************************************************** + + // ********************************* OVERLAY CANVAS RENDERERS*********************************************************************** + + // ********************************* END OF OVERLAY CANVAS RENDERERS *********************************************************************** +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the state machine connectors. + * + * Thanks to Brainstorm Mobile Solutions for supporting the development of these. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + var Line = function(x1, y1, x2, y2) { + + this.m = (y2 - y1) / (x2 - x1); + this.b = -1 * ((this.m * x1) - y1); + + this.rectIntersect = function(x,y,w,h) { + var results = []; + + // try top face + // the equation of the top face is y = (0 * x) + b; y = b. + var xInt = (y - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try right face + var yInt = (this.m * (x + w)) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + // bottom face + var xInt = ((y + h) - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try left face + var yInt = (this.m * x) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + if (results.length == 2) { + var midx = (results[0][0] + results[1][0]) / 2, midy = (results[0][1] + results[1][1]) / 2; + results.push([ midx,midy ]); + // now calculate the segment inside the rectangle where the midpoint lies. + var xseg = midx <= x + (w / 2) ? -1 : 1, + yseg = midy <= y + (h / 2) ? -1 : 1; + results.push([xseg, yseg]); + return results; + } + + return null; + + }; + }, + _segment = function(x1, y1, x2, y2) { + if (x1 <= x2 && y2 <= y1) return 1; + else if (x1 <= x2 && y1 <= y2) return 2; + else if (x2 <= x1 && y2 >= y1) return 3; + return 4; + }, + + // the control point we will use depends on the faces to which each end of the connection is assigned, specifically whether or not the + // two faces are parallel or perpendicular. if they are parallel then the control point lies on the midpoint of the axis in which they + // are parellel and varies only in the other axis; this variation is proportional to the distance that the anchor points lie from the + // center of that face. if the two faces are perpendicular then the control point is at some distance from both the midpoints; the amount and + // direction are dependent on the orientation of the two elements. 'seg', passed in to this method, tells you which segment the target element + // lies in with respect to the source: 1 is top right, 2 is bottom right, 3 is bottom left, 4 is top left. + // + // sourcePos and targetPos are arrays of info about where on the source and target each anchor is located. their contents are: + // + // 0 - absolute x + // 1 - absolute y + // 2 - proportional x in element (0 is left edge, 1 is right edge) + // 3 - proportional y in element (0 is top edge, 1 is bottom edge) + // + _findControlPoint = function(midx, midy, segment, sourceEdge, targetEdge, dx, dy, distance, proximityLimit) { + + // TODO (maybe) + // - if anchor pos is 0.5, make the control point take into account the relative position of the elements. + if (distance <= proximityLimit) return [midx, midy]; + + if (segment == 1) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 2) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 3) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 4) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + }; + + /* + Function: StateMachine constructor + + Allowed parameters: + curviness - measure of how "curvy" the connectors will be. this is translated as the distance that the + Bezier curve's control point is from the midpoint of the straight line connecting the two + endpoints, and does not mean that the connector is this wide. The Bezier curve never reaches + its control points; they act as gravitational masses. defaults to 10. + margin - distance from element to start and end connectors, in pixels. defaults to 5. + proximityLimit - sets the distance beneath which the elements are consider too close together to bother with fancy + curves. by default this is 80 pixels. + loopbackRadius - the radius of a loopback connector. optional; defaults to 25. + */ + jsPlumb.Connectors.StateMachine = function(params) { + var self = this, + currentPoints = null, + _sx, _sy, _tx, _ty, _controlPoint = [], + curviness = params.curviness || 10, + margin = params.margin || 5, + proximityLimit = params.proximityLimit || 80, + clockwise = params.orientation && params.orientation == "clockwise", + loopbackRadius = params.loopbackRadius || 25, + isLoopback = false; + + this.type = "StateMachine"; + params = params || {}; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *= 1.9; + //ensure at least one pixel width + lineWidth = lineWidth || 1; + var x = Math.min(sourcePos[0], targetPos[0]) - xo, + y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (sourceEndpoint.elementId != targetEndpoint.elementId) { + + isLoopback = false; + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + + // now adjust for the margin + if (sourcePos[2] == 0) _sx -= margin; + if (sourcePos[2] == 1) _sx += margin; + if (sourcePos[3] == 0) _sy -= margin; + if (sourcePos[3] == 1) _sy += margin; + if (targetPos[2] == 0) _tx -= margin; + if (targetPos[2] == 1) _tx += margin; + if (targetPos[3] == 0) _ty -= margin; + if (targetPos[3] == 1) _ty += margin; + + // + // these connectors are quadratic bezier curves, having a single control point. if both anchors + // are located at 0.5 on their respective faces, the control point is set to the midpoint and you + // get a straight line. this is also the case if the two anchors are within 'proximityLimit', since + // it seems to make good aesthetic sense to do that. outside of that, the control point is positioned + // at 'curviness' pixels away along the normal to the straight line connecting the two anchors. + // + // there may be two improvements to this. firstly, we might actually support the notion of avoiding nodes + // in the UI, or at least making a good effort at doing so. if a connection would pass underneath some node, + // for example, we might increase the distance the control point is away from the midpoint in a bid to + // steer it around that node. this will work within limits, but i think those limits would also be the likely + // limits for, once again, aesthetic good sense in the layout of a chart using these connectors. + // + // the second possible change is actually two possible changes: firstly, it is possible we should gradually + // decrease the 'curviness' as the distance between the anchors decreases; start tailing it off to 0 at some + // point (which should be configurable). secondly, we might slightly increase the 'curviness' for connectors + // with respect to how far their anchor is from the center of its respective face. this could either look cool, + // or stupid, and may indeed work only in a way that is so subtle as to have been a waste of time. + // + + var _midx = (_sx + _tx) / 2, _midy = (_sy + _ty) / 2, + m2 = (-1 * _midx) / _midy, theta2 = Math.atan(m2), + dy = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.sin(theta2)), + dx = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.cos(theta2)), + segment = _segment(_sx, _sy, _tx, _ty), + distance = Math.sqrt(Math.pow(_tx - _sx, 2) + Math.pow(_ty - _sy, 2)); + + // calculate the control point. this code will be where we'll put in a rudimentary element avoidance scheme; it + // will work by extending the control point to force the curve to be, um, curvier. + _controlPoint = _findControlPoint(_midx, + _midy, + segment, + sourcePos, + targetPos, + curviness, curviness, + distance, + proximityLimit); + + + var requiredWidth = Math.max(Math.abs(_controlPoint[0] - _sx) * 3, Math.abs(_controlPoint[0] - _tx) * 3, Math.abs(_tx-_sx), 2 * lineWidth, minWidth), + requiredHeight = Math.max(Math.abs(_controlPoint[1] - _sy) * 3, Math.abs(_controlPoint[1] - _ty) * 3, Math.abs(_ty-_sy), 2 * lineWidth, minWidth); + + if (w < requiredWidth) { + var dw = requiredWidth - w; + x -= (dw / 2); + _sx += (dw / 2); + _tx += (dw / 2); + w = requiredWidth; + _controlPoint[0] += (dw / 2); + } + + if (h < requiredHeight) { + var dh = requiredHeight - h; + y -= (dh / 2); + _sy += (dh / 2); + _ty += (dh / 2); + h = requiredHeight; + _controlPoint[1] += (dh / 2); + } + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty, _controlPoint[0], _controlPoint[1] ]; + } + else { + isLoopback = true; + // a loopback connector. draw an arc from one anchor to the other. + // i guess we'll do this the same way as the others. just the control point will be a fair distance away. + var x1 = sourcePos[0], x2 = sourcePos[0], y1 = sourcePos[1] - margin, y2 = sourcePos[1] - margin, + cx = x1, cy = y1 - loopbackRadius; + + // canvas sizing stuff, to ensure the whole painted area is visible. + w = ((2 * lineWidth) + (4 * loopbackRadius)), h = ((2 * lineWidth) + (4 * loopbackRadius)); + x = cx - loopbackRadius - lineWidth - loopbackRadius, y = cy - loopbackRadius - lineWidth - loopbackRadius; + currentPoints = [ x, y, w, h, cx-x, cy-y, loopbackRadius, clockwise, x1-x, y1-y, x2-x, y2-y]; + } + + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_tx, y:_ty }, + { x:_controlPoint[0], y:_controlPoint[1] }, + { x:_controlPoint[0] + 1, y:_controlPoint[1] + 1}, + { x:_sx, y:_sy } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + if (isLoopback) { + + if (location > 0 && location < 1) location = 1- location; + +// current points are [ x, y, width, height, center x, center y, radius, clockwise, startx, starty, endx, endy ] + // so the path length is the circumference of the circle + //var len = 2 * Math.PI * currentPoints[6], + // map 'location' to an angle. 0 is PI/2 when the connector is on the top face; if we + // support other faces it will have to be calculated for each one. 1 is also PI/2. + // 0.5 is -PI/2. + var startAngle = (location * 2 * Math.PI) + (Math.PI / 2), + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + + } + else return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + if (isLoopback) + return Math.atan(location * 2 * Math.PI); + else + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + if (isLoopback) { + + if (location > 0 && location < 1) location = 1- location; + + var circumference = 2 * Math.PI * currentPoints[6], + arcSpan = distance / circumference * 2 * Math.PI, + startAngle = (location * 2 * Math.PI) - arcSpan + (Math.PI / 2), + + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + } + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + }; + + /* + * Canvas state machine renderer. + */ + jsPlumb.Connectors.canvas.StateMachine = function(params) { + params = params || {}; + var self = this, drawGuideline = params.drawGuideline || true, avoidSelector = params.avoidSelector; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.CanvasConnector.apply(this, arguments); + + + this._paint = function(dimensions) { + + if (dimensions.length == 10) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.quadraticCurveTo(dimensions[8], dimensions[9], dimensions[6], dimensions[7]); + self.ctx.stroke(); + + /*/ draw the guideline + if (drawGuideline) { + self.ctx.save(); + self.ctx.beginPath(); + self.ctx.strokeStyle = "silver"; + self.ctx.lineWidth = 1; + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + self.ctx.restore(); + } + //*/ + } + else { + // a loopback connector + self.ctx.save(); + self.ctx.beginPath(); + var startAngle = 0, // Starting point on circle + endAngle = 2 * Math.PI, // End point on circle + clockwise = dimensions[7]; // clockwise or anticlockwise + self.ctx.arc(dimensions[4],dimensions[5],dimensions[6],0, endAngle, clockwise); + self.ctx.stroke(); + self.ctx.closePath(); + self.ctx.restore(); + } + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + /* + * SVG State Machine renderer + */ + jsPlumb.Connectors.svg.StateMachine = function() { + var self = this; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.SvgConnector.apply(this, arguments); + this.getPath = function(d) { + + if (d.length == 10) + return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[8] + " " + d[9] + " " + d[6] + " " + d[7]; + else { + // loopback + return "M" + (d[8] + 4) + " " + d[9] + " A " + d[6] + " " + d[6] + " 0 1,0 " + (d[8]-4) + " " + d[9]; + } + }; + }; + + /* + * VML state machine renderer + */ + jsPlumb.Connectors.vml.StateMachine = function() { + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.VmlConnector.apply(this, arguments); + var _conv = jsPlumb.vml.convertValue; + this.getPath = function(d) { + if (d.length == 10) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + } + else { + // loopback + var left = _conv(d[8] - d[6]), + top = _conv(d[9] - (2 * d[6])), + right = left + _conv(2 * d[6]), + bottom = top + _conv(2 * d[6]), + posString = left + "," + top + "," + right + "," + bottom; + + var o = "ar " + posString + "," + _conv(d[8]) + "," + + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + " e"; + + return o; + } + }; + }; + +})(); + +/* + // now for a rudimentary avoidance scheme. TODO: how to set this in a cross-library way? + // if (avoidSelector) { + // var testLine = new Line(sourcePos[0] + _sx,sourcePos[1] + _sy,sourcePos[0] + _tx,sourcePos[1] + _ty); + // var sel = jsPlumb.getSelector(avoidSelector); + // for (var i = 0; i < sel.length; i++) { + // var id = jsPlumb.getId(sel[i]); + // if (id != sourceEndpoint.elementId && id != targetEndpoint.elementId) { + // o = jsPlumb.getOffset(id), s = jsPlumb.getSize(id); +// +// if (o && s) { +// var collision = testLine.rectIntersect(o.left,o.top,s[0],s[1]); +// if (collision) { + // set the control point to be a certain distance from the midpoint of the two points that + // the line crosses on the rectangle. + // TODO where will this 75 number come from? + // _controlX = collision[2][0] + (75 * collision[3][0]); + // / _controlY = collision[2][1] + (75 * collision[3][1]); +// } +// } + // } + // } + //} + *//* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the VML renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + // http://ajaxian.com/archives/the-vml-changes-in-ie-8 + // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/ + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + + var vmlAttributeMap = { + "stroke-linejoin":"joinstyle", + "joinstyle":"joinstyle", + "endcap":"endcap", + "miterlimit":"miterlimit" + }; + + if (document.createStyleSheet) { + + // this is the style rule for IE7/6: it uses a CSS class, tidy. + document.createStyleSheet().addRule(".jsplumb_vml", "behavior:url(#default#VML);position:absolute;"); + + // these are for VML in IE8. you have to explicitly call out which elements + // you're going to expect to support VML! + // + document.createStyleSheet().addRule("jsplumb\\:textbox", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:oval", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:rect", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:stroke", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:shape", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:group", "behavior:url(#default#VML);position:absolute;"); + + // in this page it is also mentioned that IE requires the extra arg to the namespace + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either. + // var iev = document.documentMode; + //if (!iev || iev < 8) + document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); + //else + // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML"); + } + + jsPlumb.vml = {}; + + var scale = 1000, + + _groupMap = {}, + _getGroup = function(container, connectorClass) { + var id = jsPlumb.getId(container), + g = _groupMap[id]; + if(!g) { + g = _node("group", [0,0,scale, scale], {"class":connectorClass}); + //g.style.position=absolute; + //g["coordsize"] = "1000,1000"; + g.style.backgroundColor="red"; + _groupMap[id] = g; + jsPlumb.appendElement(g, container); + //document.body.appendChild(g); + } + return g; + }, + _atts = function(o, atts) { + for (var i in atts) { + // IE8 fix: setattribute does not work after an element has been added to the dom! + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + //o.setAttribute(i, atts[i]); + + o[i] = atts[i]; + } + }, + _node = function(name, d, atts) { + atts = atts || {}; + var o = document.createElement("jsplumb:" + name); + o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml"; + _pos(o, d); + _atts(o, atts); + return o; + }, + _pos = function(o,d) { + o.style.left = d[0] + "px"; + o.style.top = d[1] + "px"; + o.style.width= d[2] + "px"; + o.style.height= d[3] + "px"; + o.style.position = "absolute"; + }, + _conv = jsPlumb.vml.convertValue = function(v) { + return Math.floor(v * scale); + }, + // tests if the given style is "transparent" and then sets the appropriate opacity node to 0 if so, + // or 1 if not. TODO in the future, support variable opacity. + _maybeSetOpacity = function(styleToWrite, styleToCheck, type, component) { + if ("transparent" === styleToCheck) + component.setOpacity(type, "0.0"); + else + component.setOpacity(type, "1.0"); + }, + _applyStyles = function(node, style, component) { + var styleToWrite = {}; + if (style.strokeStyle) { + styleToWrite["stroked"] = "true"; + var strokeColor = jsPlumb.util.convertStyle(style.strokeStyle, true); + styleToWrite["strokecolor"] = strokeColor; + _maybeSetOpacity(styleToWrite, strokeColor, "stroke", component); + styleToWrite["strokeweight"] = style.lineWidth + "px"; + } + else styleToWrite["stroked"] = "false"; + + if (style.fillStyle) { + styleToWrite["filled"] = "true"; + var fillColor = jsPlumb.util.convertStyle(style.fillStyle, true); + styleToWrite["fillcolor"] = fillColor; + _maybeSetOpacity(styleToWrite, fillColor, "fill", component); + } + else styleToWrite["filled"] = "false"; + + if(style["dashstyle"]) { + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = style["dashstyle"]; + } + else if (style["stroke-dasharray"] && style["lineWidth"]) { + var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",", + parts = style["stroke-dasharray"].split(sep), + styleToUse = ""; + for(var i = 0; i < parts.length; i++) { + styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep); + } + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = styleToUse; + } + + _atts(node, styleToWrite); + }, + /* + * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. + */ + VmlComponent = function() { + var self = this; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.opacityNodes = { + "stroke":null, + "fill":null + }; + this.initOpacityNodes = function(vml) { + self.opacityNodes["stroke"] = _node("stroke", [0,0,1,1], {opacity:"0.0"}); + self.opacityNodes["fill"] = _node("fill", [0,0,1,1], {opacity:"0.0"}); + vml.appendChild(self.opacityNodes["stroke"]); + vml.appendChild(self.opacityNodes["fill"]); + }; + this.setOpacity = function(type, value) { + var node = self.opacityNodes[type]; + if (node) node["opacity"] = "" + value; + }; + var displayElements = [ ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el, doNotAppendToCanvas) { + if (!doNotAppendToCanvas) self.canvas.parentNode.appendChild(el); + displayElements.push(el); + }; + }, + /* + * Base class for Vml connectors. extends VmlComponent. + */ + VmlConnector = jsPlumb.VmlConnector = function(params) { + var self = this; + self.strokeNode = null; + self.canvas = null; + VmlComponent.apply(this, arguments); + var clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : ""); + this.paint = function(d, style, anchor) { + if (style != null) { + var path = self.getPath(d), p = { "path":path }; + + //* + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle : jsPlumb.util.convertStyle(style.outlineColor), + lineWidth : outlineStrokeWidth + }; + for (var aa in vmlAttributeMap) outlineStyle[aa] = style[aa]; + + if (self.bgCanvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.bgCanvas = _node("shape", d, p); + jsPlumb.appendElement(self.bgCanvas, params.parent); + _pos(self.bgCanvas, d); + self.appendDisplayElement(self.bgCanvas, true); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.bgCanvas, d); + _atts(self.bgCanvas, p); + } + + _applyStyles(self.bgCanvas, outlineStyle, self); + } + //*/ + + if (self.canvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + if (self.tooltip) p["label"] = self.tooltip; + self.canvas = _node("shape", d, p); + + //var group = _getGroup(params.parent); // test of append everything to a group + //group.appendChild(self.canvas); // sort of works but not exactly; + jsPlumb.appendElement(self.canvas, params.parent); //before introduction of groups + + self.appendDisplayElement(self.canvas, true); + + self.attachListeners(self.canvas, self); + + self.initOpacityNodes(self.canvas, ["stroke"]); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.canvas, d); + _atts(self.canvas, p); + } + + _applyStyles(self.canvas, style, self); + } + }; + + //self.appendDisplayElement(self.canvas); + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + }, + /* + * + * Base class for Vml Endpoints. extends VmlComponent. + * + */ + VmlEndpoint = function(params) { + VmlComponent.apply(this, arguments); + var vml = null, self = this, opacityStrokeNode = null, opacityFillNode = null; + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + + //var group = _getGroup(params.parent); + //group.appendChild(self.canvas); + jsPlumb.appendElement(self.canvas, params.parent); + + if (self.tooltip) self.canvas.setAttribute("label", self.tooltip); + + this.paint = function(d, style, anchor) { + var p = { }; + + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (vml == null) { + p["class"] = jsPlumb.endpointClass; + vml = self.getVml([0,0, d[2], d[3]], p, anchor); + self.canvas.appendChild(vml); + self.attachListeners(vml, self); + + self.appendDisplayElement(vml, true); + self.appendDisplayElement(self.canvas, true); + + self.initOpacityNodes(vml, ["fill"]); + } + else { + //p["coordsize"] = "1,1";//(d[2] * scale) + "," + (d[3] * scale); again, unsure. + _pos(vml, [0,0, d[2], d[3]]); + _atts(vml, p); + } + + _applyStyles(vml, style, self); + }; + + this.reattachListeners = function() { + if (vml) self.reattachListenersForElement(vml, self); + }; + }; + + jsPlumb.Connectors.vml.Bezier = function() { + jsPlumb.Connectors.Bezier.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Straight = function() { + jsPlumb.Connectors.Straight.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Flowchart = function() { + jsPlumb.Connectors.Flowchart.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l"; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]); + } + // finally draw a line to the end + p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e"; + return p; + }; + }; + + jsPlumb.Endpoints.vml.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("oval", d, atts); }; + }; + + jsPlumb.Endpoints.vml.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("rect", d, atts); }; + }; + + /* + * VML Image Endpoint is the same as the default image endpoint. + */ + jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image; + + /** + * placeholder for Blank endpoint in vml renderer. + */ + jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank; + + /** + * VML Label renderer. uses the default label renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label; + + var AbstractVmlArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + VmlComponent.apply(this, arguments); + var self = this, path = null; + self.canvas = null; + var getPath = function(d, connectorDimensions) { + return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) + + " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + + " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + + " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + + " x e"; + }; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) { + var p = {}; + if (strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] = jsPlumb.util.convertStyle(strokeStyle, true); + } + if (lineWidth) p["strokeweight"] = lineWidth + "px"; + if (fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = fillStyle; + } + var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + w = Math.abs(xmax - xmin), + h = Math.abs(ymax - ymin), + dim = [xmin, ymin, w, h]; + + // for VML, we create overlays using shapes that have the same dimensions and + // coordsize as their connector - overlays calculate themselves relative to the + // connector (it's how it's been done since the original canvas implementation, because + // for canvas that makes sense). + p["path"] = getPath(d, connectorDimensions); + p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale); + + dim[0] = connectorDimensions[0]; + dim[1] = connectorDimensions[1]; + dim[2] = connectorDimensions[2]; + dim[3] = connectorDimensions[3]; + + if (self.canvas == null) { + //p["class"] = jsPlumb.overlayClass; // TODO currentInstance? + self.canvas = _node("shape", dim, p); + connector.appendDisplayElement(self.canvas); + self.attachListeners(self.canvas, connector); + } + else { + _pos(self.canvas, dim); + _atts(self.canvas, p); + } + }; + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + }; + + jsPlumb.Overlays.vml.Arrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.vml.PlainArrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.vml.Diamond = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the SVG renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +/** + * SVG support for jsPlumb. + * + * things to investigate: + * + * gradients: https://developer.mozilla.org/en/svg_in_html_introduction + * css:http://tutorials.jenkov.com/svg/svg-and-css.html + * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath + * pointer events: https://developer.mozilla.org/en/css/pointer-events + * + * IE9 hover jquery: http://forum.jquery.com/topic/1-6-2-broke-svg-hover-events + * + */ +;(function() { + + var svgAttributeMap = { + "joinstyle":"stroke-linejoin", + "stroke-linejoin":"stroke-linejoin", + "stroke-dashoffset":"stroke-dashoffset", + "stroke-linecap":"stroke-linecap" + }, + STROKE_DASHARRAY = "stroke-dasharray", + DASHSTYLE = "dashstyle", + LINEAR_GRADIENT = "linearGradient", + RADIAL_GRADIENT = "radialGradient", + FILL = "fill", + STOP = "stop", + STROKE = "stroke", + STROKE_WIDTH = "stroke-width", + STYLE = "style", + NONE = "none", + JSPLUMB_GRADIENT = "jsplumb_gradient_", + LINE_WIDTH = "lineWidth", + ns = { + svg:"http://www.w3.org/2000/svg", + xhtml:"http://www.w3.org/1999/xhtml" + }, + _attr = function(node, attributes) { + for (var i in attributes) + node.setAttribute(i, "" + attributes[i]); + }, + _node = function(name, attributes) { + var n = document.createElementNS(ns.svg, name); + attributes = attributes || {}; + attributes["version"] = "1.1"; + attributes["xmnls"] = ns.xhtml; + _attr(n, attributes); + return n; + }, + _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; }, + _clearGradient = function(parent) { + for (var i = 0; i < parent.childNodes.length; i++) { + if (parent.childNodes[i].tagName == LINEAR_GRADIENT || parent.childNodes[i].tagName == RADIAL_GRADIENT) + parent.removeChild(parent.childNodes[i]); + } + }, + _updateGradient = function(parent, node, style, dimensions, uiComponent) { + var id = JSPLUMB_GRADIENT + uiComponent._jsPlumb.idstamp(); + // first clear out any existing gradient + _clearGradient(parent); + // this checks for an 'offset' property in the gradient, and in the absence of it, assumes + // we want a linear gradient. if it's there, we create a radial gradient. + // it is possible that a more explicit means of defining the gradient type would be + // better. relying on 'offset' means that we can never have a radial gradient that uses + // some default offset, for instance. + if (!style.gradient.offset) { + var g = _node(LINEAR_GRADIENT, {id:id}); + parent.appendChild(g); + } + else { + var g = _node(RADIAL_GRADIENT, { + id:id + }); + parent.appendChild(g); + } + + // the svg radial gradient seems to treat stops in the reverse + // order to how canvas does it. so we want to keep all the maths the same, but + // iterate the actual style declarations in reverse order, if the x indexes are not in order. + for (var i = 0; i < style.gradient.stops.length; i++) { + // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of + // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments. + // so, not too concerned about leaving this in for now. + var styleToUse = i; + if (dimensions.length == 8) + styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i; + else + styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i; + var stopColor = jsPlumb.util.convertStyle(style.gradient.stops[styleToUse][1], true); + var s = _node(STOP, {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor}); + g.appendChild(s); + } + var applyGradientTo = style.strokeStyle ? STROKE : FILL; + node.setAttribute(STYLE, applyGradientTo + ":url(#" + id + ")"); + }, + _applyStyles = function(parent, node, style, dimensions, uiComponent) { + + if (style.gradient) { + _updateGradient(parent, node, style, dimensions, uiComponent); + } + else { + // make sure we clear any existing gradient + _clearGradient(parent); + node.setAttribute(STYLE, ""); + } + + node.setAttribute(FILL, style.fillStyle ? jsPlumb.util.convertStyle(style.fillStyle, true) : NONE); + node.setAttribute(STROKE, style.strokeStyle ? jsPlumb.util.convertStyle(style.strokeStyle, true) : NONE); + if (style.lineWidth) { + node.setAttribute(STROKE_WIDTH, style.lineWidth); + } + + // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like + // the syntax in VML but is actually kind of nasty: values are given in the pixel + // coordinate space, whereas in VML they are multiples of the width of the stroked + // line, which makes a lot more sense. for that reason, jsPlumb is supporting both + // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from + // VML, which will be the preferred method. the code below this converts a dashstyle + // attribute given in terms of stroke width into a pixel representation, by using the + // stroke's lineWidth. + if (style[DASHSTYLE] && style[LINE_WIDTH] && !style[STROKE_DASHARRAY]) { + var sep = style[DASHSTYLE].indexOf(",") == -1 ? " " : ",", + parts = style[DASHSTYLE].split(sep), + styleToUse = ""; + parts.forEach(function(p) { + styleToUse += (Math.floor(p * style.lineWidth) + sep); + }); + node.setAttribute(STROKE_DASHARRAY, styleToUse); + } + else if(style[STROKE_DASHARRAY]) { + node.setAttribute(STROKE_DASHARRAY, style[STROKE_DASHARRAY]); + } + + // extra attributes such as join type, dash offset. + for (var i in svgAttributeMap) { + if (style[i]) { + node.setAttribute(svgAttributeMap[i], style[i]); + } + } + }, + _decodeFont = function(f) { + var r = /([0-9].)(p[xt])\s(.*)/; + var bits = f.match(r); + return {size:bits[1] + bits[2], font:bits[3]}; + }, + _classManip = function(el, add, clazz) { + var classesToAddOrRemove = clazz.split(" "), + className = el.className, + curClasses = className.baseVal.split(" "); + + for (var i = 0; i < classesToAddOrRemove.length; i++) { + if (add) { + if (curClasses.indexOf(classesToAddOrRemove[i]) == -1) + curClasses.push(classesToAddOrRemove[i]); + } + else { + var idx = curClasses.indexOf(classesToAddOrRemove[i]); + if (idx != -1) + curClasses.splice(idx, 1); + } + } + + el.className.baseVal = curClasses.join(" "); + }, + _addClass = function(el, clazz) { + _classManip(el, true, clazz); + }, + _removeClass = function(el, clazz) { + _classManip(el, false, clazz); + }; + + /** + utility methods for other objects to use. + */ + jsPlumb.util.svg = { + addClass:_addClass, + removeClass:_removeClass + }; + + /* + * Base class for SVG components. + */ + //var SvgComponent = function(cssClass, originalArgs, pointerEventsSpec) { + var SvgComponent = function(params) { + var self = this, + pointerEventsSpec = params.pointerEventsSpec || "all"; + jsPlumb.jsPlumbUIComponent.apply(this, params.originalArgs); + self.canvas = null, self.path = null, self.svg = null; + + var clazz = params.cssClass + " " + (params.originalArgs[0].cssClass || ""), + svgParams = { + "style":"", + "width":0, + "height":0, + "pointer-events":pointerEventsSpec, + "position":"absolute" + }; + if (self.tooltip) svgParams["title"] = self.tooltip; + self.svg = _node("svg", svgParams); + if (params.useDivWrapper) { + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.sizeCanvas(self.canvas,0,0,1,1); + self.canvas.className = clazz; + if (self.tooltip) self.canvas.setAttribute("title", self.tooltip); + } + else { + _attr(self.svg, { "class":clazz }); + self.canvas = self.svg; + } + + params._jsPlumb.appendElement(self.canvas, params.originalArgs[0]["parent"]); + if (params.useDivWrapper) self.canvas.appendChild(self.svg); + + // TODO this displayElement stuff is common between all components, across all + // renderers. would be best moved to jsPlumbUIComponent. + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + this.paint = function(d, style, anchor) { + if (style != null) { + var x = d[0], y = d[1]; + if (params.useDivWrapper) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + x = 0, y = 0; + } + _attr(self.svg, { + "style":_pos([x, y, d[2], d[3]]), + "width": d[2], + "height": d[3] + }); + self._paint.apply(this, arguments); + } + }; + }; + + /* + * Base class for SVG connectors. + */ + var SvgConnector = jsPlumb.SvgConnector = function(params) { + var self = this; + SvgComponent.apply(this, [ { + cssClass:params["_jsPlumb"].connectorClass, + originalArgs:arguments, + pointerEventsSpec:"none", + tooltip:params.tooltip, + _jsPlumb:params["_jsPlumb"] + } ]); + this._paint = function(d, style) { + var p = self.getPath(d), a = { "d":p }, outlineStyle = null; + a["pointer-events"] = "all"; + + // outline style. actually means drawing an svg object underneath the main one. + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = jsPlumb.CurrentLibrary.extend({}, style); + outlineStyle.strokeStyle = jsPlumb.util.convertStyle(style.outlineColor); + outlineStyle.lineWidth = outlineStrokeWidth; + + if (self.bgPath == null) { + self.bgPath = _node("path", a); + self.svg.appendChild(self.bgPath); + self.attachListeners(self.bgPath, self); + } + else { + _attr(self.bgPath, a); + } + + _applyStyles(self.svg, self.bgPath, outlineStyle, d, self); + } + + + // test - see below + // a["clip-path"]= "url(#testClip)"; + + if (self.path == null) { + self.path = _node("path", a); + self.svg.appendChild(self.path); + self.attachListeners(self.path, self); + + /* + this is a test of a clip path. i'm looking into using one of these to animate a jsplumb connection. + you could do this by walking along the line, stepping along a little at a time, and setting the clip + path to extend as far as that point. + + self.clip = _node("clipPath", {id:"testClip", clipPathUnits:"objectBoundingBox"}); + self.svg.appendChild(self.clip); + self.clip.appendChild(_node("rect", { + x:"0",y:"0",width:"0.5",height:"1" + })); + */ + } + else { + _attr(self.path, a); + } + + _applyStyles(self.svg, self.path, style, d, self); + }; + + this.reattachListeners = function() { + if (self.bgPath) self.reattachListenersForElement(self.bgPath, self); + if (self.path) self.reattachListenersForElement(self.path, self); + }; + + }; + + /* + * SVG Bezier Connector + */ + jsPlumb.Connectors.svg.Bezier = function(params) { + jsPlumb.Connectors.Bezier.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]; }; + }; + + /* + * SVG straight line Connector + */ + jsPlumb.Connectors.svg.Straight = function(params) { + jsPlumb.Connectors.Straight.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; }; + }; + + jsPlumb.Connectors.svg.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "M " + dimensions[4] + "," + dimensions[5]; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)]; + } + // finally draw a line to the end + p = p + " " + dimensions[6] + "," + dimensions[7]; + return p; + }; + }; + + /* + * Base class for SVG endpoints. + */ + var SvgEndpoint = function(params) { + var self = this; + SvgComponent.apply(this, [ { + cssClass:params["_jsPlumb"].endpointClass, + originalArgs:arguments, + pointerEventsSpec:"all", + useDivWrapper:true, + _jsPlumb:params["_jsPlumb"] + } ]); + this._paint = function(d, style) { + var s = jsPlumb.extend({}, style); + if (s.outlineColor) { + s.strokeWidth = s.outlineWidth; + s.strokeStyle = jsPlumb.util.convertStyle(s.outlineColor, true); + } + + if (self.node == null) { + self.node = self.makeNode(d, s); + self.svg.appendChild(self.node); + self.attachListeners(self.node, self); + } + _applyStyles(self.svg, self.node, s, d, self); + _pos(self.node, d); + }; + + this.reattachListeners = function() { + if (self.node) self.reattachListenersForElement(self.node, self); + }; + }; + + /* + * SVG Dot Endpoint + */ + jsPlumb.Endpoints.svg.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("circle", { + "cx" : d[2] / 2, + "cy" : d[3] / 2, + "r" : d[2] / 2 + }); + }; + }; + + /* + * SVG Rectangle Endpoint + */ + jsPlumb.Endpoints.svg.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("rect", { + "width":d[2], + "height":d[3] + }); + }; + }; + + /* + * SVG Image Endpoint is the default image endpoint. + */ + jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image; + /* + * Blank endpoint in svg renderer is the default Blank endpoint. + */ + jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank; + /* + * Label endpoint in svg renderer is the default Label endpoint. + */ + jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label; + + var AbstractSvgArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + this.isAppendedAtTopLevel = false; + var self = this, path =null; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + } + var clazz = originalArgs && (originalArgs.length == 1) ? (originalArgs[0].cssClass || "") : ""; + + _attr(path, { + "d" : makePath(d), + "class" : clazz, + stroke : strokeStyle ? strokeStyle : null, + fill : fillStyle ? fillStyle : null + }); + }; + var makePath = function(d) { + return "M" + d.hxy.x + "," + d.hxy.y + + " L" + d.tail[0].x + "," + d.tail[0].y + + " L" + d.cxy.x + "," + d.cxy.y + + " L" + d.tail[1].x + "," + d.tail[1].y + + " L" + d.hxy.x + "," + d.hxy.y; + }; + this.reattachListeners = function() { + if (path) self.reattachListenersForElement(path, self); + }; + }; + + jsPlumb.Overlays.svg.Arrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.svg.PlainArrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.svg.Diamond = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; + + // a test + jsPlumb.Overlays.svg.GuideLines = function() { + var path = null, self = this, path2 = null, p1_1, p1_2; + jsPlumb.Overlays.GuideLines.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + + p1_1 = _node("path"); + connector.svg.appendChild(p1_1); + self.attachListeners(p1_1, connector); + self.attachListeners(p1_1, self); + + p1_2 = _node("path"); + connector.svg.appendChild(p1_2); + self.attachListeners(p1_2, connector); + self.attachListeners(p1_2, self); + + } + + _attr(path, { + "d" : makePath(d[0], d[1]), + stroke : "red", + fill : null + }); + + _attr(p1_1, { + "d" : makePath(d[2][0], d[2][1]), + stroke : "blue", + fill : null + }); + + _attr(p1_2, { + "d" : makePath(d[3][0], d[3][1]), + stroke : "green", + fill : null + }); + }; + + var makePath = function(d1, d2) { + return "M " + d1.x + "," + d1.y + + " L" + d2.x + "," + d2.y; + }; + + }; +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the HTML5 canvas renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + +// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS ******************************************************************* + + // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. + var _connectionBeingDragged = null, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, + _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }; + + /* + * Class:CanvasMouseAdapter + * Provides support for mouse events on canvases. + */ + var CanvasMouseAdapter = function() { + var self = this; + self.overlayPlacements = []; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + jsPlumb.EventGenerator.apply(this, arguments); + /** + * returns whether or not the given event is ojver a painted area of the canvas. + */ + this._over = function(e) { + var o = _getOffset(_getElementObject(self.canvas)), + pageXY = _pageXY(e), + x = pageXY[0] - o.left, y = pageXY[1] - o.top; + if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + return false; + }; + + var _mouseover = false, _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false, + _nullSafeHasClass = function(el, clazz) { + return el != null && _hasClass(el, clazz); + }; + this.mousemove = function(e) { + var pageXY = _pageXY(e), clientXY = _clientXY(e), + ee = document.elementFromPoint(clientXY[0], clientXY[1]), + eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); + var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); + if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.fire("mouseenter", self, e); + return true; + } + // TODO here there is a remote chance that the overlay the mouse moved onto + // is actually not an overlay for the current component. a more thorough check would + // be to ensure the overlay belonged to the current component. + else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { + _mouseover = false; + self.fire("mouseexit", self, e); + } + self.fire("mousemove", self, e); + }; + + this.click = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); + self.fire("mousedown", self, e); + } + }; + + this.mouseup = function(e) { + _mouseDown = false; + self.fire("mouseup", self, e); + }; + + this.contextmenu = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("contextmenu", self, e); + _mouseWasDown = false; + }; + }; + + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + jsPlumb.appendElement(canvas, params.parent); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + params["_jsPlumb"].getId(canvas, params.uuid); + if (params.tooltip) canvas.setAttribute("title", params.tooltip); + + return canvas; + }; + + var CanvasComponent = function(params) { + CanvasMouseAdapter.apply(this, arguments); + + var displayElements = [ ]; + this.getDisplayElements = function() { return displayElements; }; + this.appendDisplayElement = function(el) { displayElements.push(el); }; + } + + /** + * Class:CanvasConnector + * Superclass for Canvas Connector renderers. + */ + var CanvasConnector = jsPlumb.CanvasConnector = function(params) { + + CanvasComponent.apply(this, arguments); + + var _paintOneStyle = function(dim, aStyle) { + self.ctx.save(); + jsPlumb.extend(self.ctx, aStyle); + if (aStyle.gradient) { + var g = self.createGradient(dim, self.ctx); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + self.ctx.strokeStyle = g; + } + self._paint(dim); + self.ctx.restore(); + }; + + var self = this, + clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:params.tooltip + }); + self.ctx = self.canvas.getContext("2d"); + + self.appendDisplayElement(self.canvas); + + self.paint = function(dim, style) { + if (style != null) { + jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + _paintOneStyle(dim, outlineStyle); + } + _paintOneStyle(dim, style); + } + }; + }; + + /** + * Class:CanvasEndpoint + * Superclass for Canvas Endpoint renderers. + */ + var CanvasEndpoint = function(params) { + var self = this; + CanvasComponent.apply(this, arguments); + var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""), + canvasParams = { + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:self.tooltip + }; + self.canvas = _newCanvas(canvasParams); + self.ctx = self.canvas.getContext("2d"); + + self.appendDisplayElement(self.canvas); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + } + + self._paint.apply(this, arguments); + }; + }; + + jsPlumb.Endpoints.canvas.Dot = function(params) { + jsPlumb.Endpoints.Dot.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + var self = this, + parseValue = function(value) { + try { return parseInt(value); } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }, + calculateAdjustments = function(gradient) { + var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius; + gradient.offset && (offsetAdjustment = parseValue(gradient.offset)); + gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius)); + return [offsetAdjustment, innerRadius]; + }; + this._paint = function(d, style, anchor) { + if (style != null) { + var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + if (style.gradient) { + var adjustments = calculateAdjustments(style.gradient), + yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0], + xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0], + g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + ctx.beginPath(); + ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + } + }; + }; + + jsPlumb.Endpoints.canvas.Rectangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) { + + var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + + /* canvas gradient */ + if (style.gradient) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, d[2], d[3]); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + jsPlumb.Endpoints.canvas.Triangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Triangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) + { + var width = d[2], height = d[3], x = d[0], y = d[1], + ctx = self.canvas.getContext('2d'), + offsetX = 0, offsetY = 0, angle = 0, + orientation = anchor.getOrientation(self); + + if( orientation[0] == 1 ) { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = style.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + /* + * Canvas Image Endpoint: uses the default version, which creates an tag. + */ + jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image; + + /* + * Blank endpoint in all renderers is just the default Blank endpoint. + */ + jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank; + + /* + * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Bezier = function() { + var self = this; + jsPlumb.Connectors.Bezier.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO i doubt this handles the case that source and target are swapped. + this.createGradient = function(dim, ctx, swap) { + return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + /* + * Canvas straight line Connector. Draws a straight line onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Straight = function() { + var self = this; + jsPlumb.Connectors.Straight.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO this does not handle the case that src and target are swapped. + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + jsPlumb.Connectors.canvas.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + +// ********************************* END OF CANVAS RENDERERS ******************************************************************* + + jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label; + + /** + * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. + */ + var CanvasOverlay = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }; + + var AbstractCanvasArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + CanvasOverlay.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + var ctx = connector.ctx; + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(d.hxy.x, d.hxy.y); + ctx.lineTo(d.tail[0].x, d.tail[0].y); + ctx.lineTo(d.cxy.x, d.cxy.y); + ctx.lineTo(d.tail[1].x, d.tail[1].y); + ctx.lineTo(d.hxy.x, d.hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + }; + + jsPlumb.Overlays.canvas.Arrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.canvas.PlainArrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.canvas.Diamond = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.5 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the YUI3 adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +/** + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + */ +(function() { + + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function( v, b, s ) { + for( var i = +b || 0, l = this.length; i < l; i++ ) { + if( this[i]===v || s && this[i]==v ) { return i; } + } + return -1; + }; + } + + var Y; + + YUI().use('node', 'dd', 'anim', 'node-event-simulate', function(_Y) { + Y = _Y; + Y.on("domready", function() { jsPlumb.init(); }); + }); + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }, + ddEvents = [ "drag:mouseDown", "drag:afterMouseDown", "drag:mouseup", + "drag:align", "drag:removeHandle", "drag:addHandle", "drag:removeInvalid", "drag:addInvalid", + "drag:start", "drag:end", "drag:drag", "drag:over", "drag:enter", + "drag:exit", "drag:drophit", "drag:dropmiss", "drop:over", "drop:enter", "drop:exit", "drop:hit" + ], + animEvents = [ "tween" ], + /** + * helper function to curry callbacks for some element. + */ + _wrapper = function(fn) { + return function() { + try { + return fn.apply(this, arguments); + } + catch (e) { } + }; + }, + /** + * extracts options from the given options object, leaving out event handlers. + */ + _getDDOptions = function(options) { + var o = {}; + for (var i in options) if (ddEvents.indexOf(i) == -1) o[i] = options[i]; + return o; + }, + /** + * attaches all event handlers found in options to the given dragdrop object, and registering + * the given el as the element of interest. + */ + _attachListeners = function(dd, options, eventList) { + for (var ev in options) { + if (eventList.indexOf(ev) != -1) { + var w = _wrapper(options[ev]); + dd.on(ev, w); + } + } + }, + _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}, + _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }, + _lastDragObject = null, + _extend = function(o1, o2) { + for (var i in o2) + o1[i] = o2[i]; + return o1; + }, + _getAttribute = function(el, attributeId) { + return el.getAttribute(attributeId); + }, + _getElementObject = function(el) { + if (el == null) return null; + var eee = null; + eee = typeof el == 'string' ? Y.one('#' + el) : el._node ? el : Y.one(el); + return eee; + }; + + jsPlumb.CurrentLibrary = { + + addClass : function(el, clazz) { + jsPlumb.CurrentLibrary.getElementObject(el).addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + var o = _extend({node:el, to:properties}, options); + var id = _getAttribute(el, "id"); + o["tween"] = jsPlumb.wrap(properties["tween"], function() { + jsPlumb.repaint(id); + }); + var a = new Y.Anim(o); + _attachListeners(a, o, animEvents); + a.run(); + }, + + appendElement : function(child, parent) { + _getElementObject (parent).append(child); + }, + + /** + * event binding wrapper. + */ + bind : function(el, event, callback) { + _getElementObject(el).on(event, callback); + }, + + dragEvents : { + "start":"drag:start", "stop":"drag:end", "drag":"drag:drag", "step":"step", + "over":"drop:enter", "out":"drop:exit", "drop":"drop:hit" + }, + + extend : _extend, + + getAttribute : _getAttribute, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + // this is a workaround for the unfortunate fact that in YUI3, the 'drop:exit' event does + // not contain a reference to the drag that just exited. single-threaded js to the + // rescue: we'll just keep it for ourselves. + if (eventArgs[0].drag) _lastDragObject = eventArgs[0].drag.el; + return _lastDragObject; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + return dd.scope; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getDOMElement : function(el) { + if (typeof(el) == "String") + return document.getElementById(el); + else if (el._node) + return el._node; + else return el; + }, + + getElementObject : _getElementObject, + + getOffset : function(el) { + var o = Y.DOM.getXY(el._node); + return {left:o[0], top:o[1]}; + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).get("parentNode"); + }, + + getScrollLeft : function(el) { + return 0; + }, + + getScrollTop : function(el) { + return 0; + }, + + getSelector : function(spec) { + var s = Y.all(spec); + return s && s ._nodes ? s._nodes : []; + }, + + getSize : function(el) { + return [ el._node.offsetWidth, el._node.offsetHeight ]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e != null && e._node != null ? e._node.tagName : null; + }, + + getUIPosition : function(args) { + var n = args[0].currentTarget.el._node, + o = Y.DOM.getXY(n); + return {left:o[0], top:o[1]}; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options) { + var _opts = _getDDOptions(options); + var id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drag(_opts); + dd.el = el; + + var scope = options['scope'] || jsPlumb.Defaults.Scope; + dd.scope = scope; + + _draggablesById[id] = dd; + _add(_draggablesByScope, scope, dd); + + _attachListeners(dd, options, ddEvents); + }, + + initDroppable : function(el, options) { + var _opts = _getDDOptions(options); + var id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drop(_opts); + + _droppableOptions[id] = options; + + options = _extend({}, options); + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _droppableScopesById[id] = scope; + + options["drop:enter"] = jsPlumb.wrap(options["drop:enter"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, true); + }, true); + options["drop:exit"] = jsPlumb.wrap(options["drop:exit"], function(e) { + _checkHover(el, false); + }); + options["drop:hit"] = jsPlumb.wrap(options["drop:hit"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, false); + }, true); + + _attachListeners(dd, options, ddEvents); + }, + + isAlreadyDraggable : function(el) { + el = _getElementObject(el); + return el.hasClass("yui3-dd-draggable"); + }, + + isDragSupported : function(el) { return true; }, + isDropSupported : function(el) { return true; }, + removeClass : function(el, clazz) { + jsPlumb.CurrentLibrary.getElementObject(el).removeClass(clazz); + }, + removeElement : function(el) { _getElementObject(el).remove(); }, + + setAttribute : function(el, attributeName, attributeValue) { + el.setAttribute(attributeName, attributeValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + if (dd) dd.set("lock", !draggable); + }, + + setDragScope : function(el, scope) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + if (dd) dd.scope = scope; + }, + + setOffset : function(el, o) { + el = _getElementObject(el); + el.set("top", o.top); + el.set("left", o.left); + }, + + stopDrag : function() { + Y.DD.DDM.stopDrag(); + }, + + trigger : function(el, event, originalEvent) { + originalEvent.stopPropagation(); + _getElementObject(el).simulate(event, { + pageX:originalEvent.pageX, + pageY:originalEvent.pageY, + clientX:originalEvent.clientX, + clientY:originalEvent.clientY + }); + }, + + /** + * event unbinding wrapper. + */ + unbind : function(el, event, callback) { + _getElementObject(el).detach(event, callback); + } + }; +})();(function(){if("undefined"==typeof Math.sgn)Math.sgn=function(a){return 0==a?0:0n?n=l:lb.location)b.location=0;return t(a,b.location)},nearestPointOnCurve:function(a,b){var f=u(a,b);return{point:r(b,b.length-1,f.location,null,null),location:f.location}},pointOnCurve:p,pointAlongCurveFrom:function(a,b,f){return s(a,b,f).point},perpendicularToCurveAt:function(a,b,f,d){b=s(a,b,null==d?0:d);a=t(a,b.location);d=Math.atan(-1/a);a=f/2*Math.sin(d);f=f/2*Math.cos(d);return[{x:b.point.x+f,y:b.point.y+a},{x:b.point.x-f,y:b.point.y-a}]}}})(); \ No newline at end of file diff --git a/build/1.3.6/demo/apidocs/files/jquery-jsPlumb-1-3-6-all-js.html b/archive/1.3.6/demo/apidocs/files/jquery-jsPlumb-1-3-6-all-js.html similarity index 100% rename from build/1.3.6/demo/apidocs/files/jquery-jsPlumb-1-3-6-all-js.html rename to archive/1.3.6/demo/apidocs/files/jquery-jsPlumb-1-3-6-all-js.html diff --git a/build/1.3.6/demo/apidocs/index.html b/archive/1.3.6/demo/apidocs/index.html similarity index 100% rename from build/1.3.6/demo/apidocs/index.html rename to archive/1.3.6/demo/apidocs/index.html diff --git a/build/1.3.6/demo/apidocs/javascript/main.js b/archive/1.3.6/demo/apidocs/javascript/main.js similarity index 100% rename from build/1.3.6/demo/apidocs/javascript/main.js rename to archive/1.3.6/demo/apidocs/javascript/main.js diff --git a/build/1.3.6/demo/apidocs/javascript/prettify.js b/archive/1.3.6/demo/apidocs/javascript/prettify.js similarity index 100% rename from build/1.3.6/demo/apidocs/javascript/prettify.js rename to archive/1.3.6/demo/apidocs/javascript/prettify.js diff --git a/build/1.3.6/demo/apidocs/javascript/searchdata.js b/archive/1.3.6/demo/apidocs/javascript/searchdata.js similarity index 100% rename from build/1.3.6/demo/apidocs/javascript/searchdata.js rename to archive/1.3.6/demo/apidocs/javascript/searchdata.js diff --git a/build/1.3.6/demo/apidocs/styles/main.css b/archive/1.3.6/demo/apidocs/styles/main.css similarity index 100% rename from build/1.3.6/demo/apidocs/styles/main.css rename to archive/1.3.6/demo/apidocs/styles/main.css diff --git a/build/1.3.6/demo/css/anchorDemo.css b/archive/1.3.6/demo/css/anchorDemo.css similarity index 100% rename from build/1.3.6/demo/css/anchorDemo.css rename to archive/1.3.6/demo/css/anchorDemo.css diff --git a/build/1.3.6/demo/css/chartDemo.css b/archive/1.3.6/demo/css/chartDemo.css similarity index 100% rename from build/1.3.6/demo/css/chartDemo.css rename to archive/1.3.6/demo/css/chartDemo.css diff --git a/build/1.3.6/demo/css/demo.css b/archive/1.3.6/demo/css/demo.css similarity index 100% rename from build/1.3.6/demo/css/demo.css rename to archive/1.3.6/demo/css/demo.css diff --git a/build/1.3.6/demo/css/dragAnimDemo.css b/archive/1.3.6/demo/css/dragAnimDemo.css similarity index 100% rename from build/1.3.6/demo/css/dragAnimDemo.css rename to archive/1.3.6/demo/css/dragAnimDemo.css diff --git a/build/1.3.6/demo/css/draggableConnectorsDemo.css b/archive/1.3.6/demo/css/draggableConnectorsDemo.css similarity index 100% rename from build/1.3.6/demo/css/draggableConnectorsDemo.css rename to archive/1.3.6/demo/css/draggableConnectorsDemo.css diff --git a/build/1.3.6/demo/css/dynamicAnchorsDemo.css b/archive/1.3.6/demo/css/dynamicAnchorsDemo.css similarity index 100% rename from build/1.3.6/demo/css/dynamicAnchorsDemo.css rename to archive/1.3.6/demo/css/dynamicAnchorsDemo.css diff --git a/build/1.3.6/demo/css/flowchartDemo.css b/archive/1.3.6/demo/css/flowchartDemo.css similarity index 100% rename from build/1.3.6/demo/css/flowchartDemo.css rename to archive/1.3.6/demo/css/flowchartDemo.css diff --git a/build/1.3.6/demo/css/jsPlumbDemo.css b/archive/1.3.6/demo/css/jsPlumbDemo.css similarity index 100% rename from build/1.3.6/demo/css/jsPlumbDemo.css rename to archive/1.3.6/demo/css/jsPlumbDemo.css diff --git a/build/1.3.6/demo/css/makeTargetDemo.css b/archive/1.3.6/demo/css/makeTargetDemo.css similarity index 100% rename from build/1.3.6/demo/css/makeTargetDemo.css rename to archive/1.3.6/demo/css/makeTargetDemo.css diff --git a/build/1.3.6/demo/css/multipleJsPlumbDemo.css b/archive/1.3.6/demo/css/multipleJsPlumbDemo.css similarity index 100% rename from build/1.3.6/demo/css/multipleJsPlumbDemo.css rename to archive/1.3.6/demo/css/multipleJsPlumbDemo.css diff --git a/build/1.3.6/demo/css/selectDemo.css b/archive/1.3.6/demo/css/selectDemo.css similarity index 100% rename from build/1.3.6/demo/css/selectDemo.css rename to archive/1.3.6/demo/css/selectDemo.css diff --git a/build/1.3.6/demo/css/stateMachineDemo.css b/archive/1.3.6/demo/css/stateMachineDemo.css similarity index 100% rename from build/1.3.6/demo/css/stateMachineDemo.css rename to archive/1.3.6/demo/css/stateMachineDemo.css diff --git a/build/1.3.6/demo/doc/archive/1.2.6/content.html b/archive/1.3.6/demo/doc/archive/1.2.6/content.html similarity index 100% rename from build/1.3.6/demo/doc/archive/1.2.6/content.html rename to archive/1.3.6/demo/doc/archive/1.2.6/content.html diff --git a/build/1.3.6/demo/doc/archive/1.2.6/index.html b/archive/1.3.6/demo/doc/archive/1.2.6/index.html similarity index 100% rename from build/1.3.6/demo/doc/archive/1.2.6/index.html rename to archive/1.3.6/demo/doc/archive/1.2.6/index.html diff --git a/build/1.3.6/demo/doc/archive/1.2.6/usage.html b/archive/1.3.6/demo/doc/archive/1.2.6/usage.html similarity index 100% rename from build/1.3.6/demo/doc/archive/1.2.6/usage.html rename to archive/1.3.6/demo/doc/archive/1.2.6/usage.html diff --git a/build/1.3.6/demo/doc/archive/1.3.2/content.html b/archive/1.3.6/demo/doc/archive/1.3.2/content.html similarity index 100% rename from build/1.3.6/demo/doc/archive/1.3.2/content.html rename to archive/1.3.6/demo/doc/archive/1.3.2/content.html diff --git a/build/1.3.6/demo/doc/archive/1.3.2/index.html b/archive/1.3.6/demo/doc/archive/1.3.2/index.html similarity index 100% rename from build/1.3.6/demo/doc/archive/1.3.2/index.html rename to archive/1.3.6/demo/doc/archive/1.3.2/index.html diff --git a/build/1.3.6/demo/doc/archive/1.3.2/usage.html b/archive/1.3.6/demo/doc/archive/1.3.2/usage.html similarity index 100% rename from build/1.3.6/demo/doc/archive/1.3.2/usage.html rename to archive/1.3.6/demo/doc/archive/1.3.2/usage.html diff --git a/build/1.3.6/demo/doc/archive/1.3.3/content.html b/archive/1.3.6/demo/doc/archive/1.3.3/content.html similarity index 100% rename from build/1.3.6/demo/doc/archive/1.3.3/content.html rename to archive/1.3.6/demo/doc/archive/1.3.3/content.html diff --git a/build/1.3.6/demo/doc/archive/1.3.3/index.html b/archive/1.3.6/demo/doc/archive/1.3.3/index.html similarity index 100% rename from build/1.3.6/demo/doc/archive/1.3.3/index.html rename to archive/1.3.6/demo/doc/archive/1.3.3/index.html diff --git a/build/1.3.6/demo/doc/archive/1.3.3/jsPlumbDoc.css b/archive/1.3.6/demo/doc/archive/1.3.3/jsPlumbDoc.css similarity index 100% rename from build/1.3.6/demo/doc/archive/1.3.3/jsPlumbDoc.css rename to archive/1.3.6/demo/doc/archive/1.3.3/jsPlumbDoc.css diff --git a/build/1.3.6/demo/doc/archive/1.3.3/usage.html b/archive/1.3.6/demo/doc/archive/1.3.3/usage.html similarity index 100% rename from build/1.3.6/demo/doc/archive/1.3.3/usage.html rename to archive/1.3.6/demo/doc/archive/1.3.3/usage.html diff --git a/build/1.3.6/demo/doc/archive/1.3.4/content.html b/archive/1.3.6/demo/doc/archive/1.3.4/content.html similarity index 100% rename from build/1.3.6/demo/doc/archive/1.3.4/content.html rename to archive/1.3.6/demo/doc/archive/1.3.4/content.html diff --git a/build/1.3.6/demo/doc/archive/1.3.4/index.html b/archive/1.3.6/demo/doc/archive/1.3.4/index.html similarity index 100% rename from build/1.3.6/demo/doc/archive/1.3.4/index.html rename to archive/1.3.6/demo/doc/archive/1.3.4/index.html diff --git a/build/1.3.6/demo/doc/archive/1.3.4/jsPlumbDoc.css b/archive/1.3.6/demo/doc/archive/1.3.4/jsPlumbDoc.css similarity index 100% rename from build/1.3.6/demo/doc/archive/1.3.4/jsPlumbDoc.css rename to archive/1.3.6/demo/doc/archive/1.3.4/jsPlumbDoc.css diff --git a/build/1.3.6/demo/doc/archive/1.3.4/usage.html b/archive/1.3.6/demo/doc/archive/1.3.4/usage.html similarity index 100% rename from build/1.3.6/demo/doc/archive/1.3.4/usage.html rename to archive/1.3.6/demo/doc/archive/1.3.4/usage.html diff --git a/build/1.3.6/demo/doc/archive/1.3.5/content.html b/archive/1.3.6/demo/doc/archive/1.3.5/content.html similarity index 100% rename from build/1.3.6/demo/doc/archive/1.3.5/content.html rename to archive/1.3.6/demo/doc/archive/1.3.5/content.html diff --git a/build/1.3.6/demo/doc/archive/1.3.5/index.html b/archive/1.3.6/demo/doc/archive/1.3.5/index.html similarity index 100% rename from build/1.3.6/demo/doc/archive/1.3.5/index.html rename to archive/1.3.6/demo/doc/archive/1.3.5/index.html diff --git a/build/1.3.6/demo/doc/archive/1.3.5/jsPlumbDoc.css b/archive/1.3.6/demo/doc/archive/1.3.5/jsPlumbDoc.css similarity index 100% rename from build/1.3.6/demo/doc/archive/1.3.5/jsPlumbDoc.css rename to archive/1.3.6/demo/doc/archive/1.3.5/jsPlumbDoc.css diff --git a/build/1.3.6/demo/doc/archive/1.3.5/usage.html b/archive/1.3.6/demo/doc/archive/1.3.5/usage.html similarity index 100% rename from build/1.3.6/demo/doc/archive/1.3.5/usage.html rename to archive/1.3.6/demo/doc/archive/1.3.5/usage.html diff --git a/build/1.3.6/demo/doc/archive/1.3.6/content.html b/archive/1.3.6/demo/doc/archive/1.3.6/content.html similarity index 100% rename from build/1.3.6/demo/doc/archive/1.3.6/content.html rename to archive/1.3.6/demo/doc/archive/1.3.6/content.html diff --git a/build/1.3.6/demo/doc/archive/1.3.6/index.html b/archive/1.3.6/demo/doc/archive/1.3.6/index.html similarity index 100% rename from build/1.3.6/demo/doc/archive/1.3.6/index.html rename to archive/1.3.6/demo/doc/archive/1.3.6/index.html diff --git a/build/1.3.6/demo/doc/archive/1.3.6/jsPlumbDoc.css b/archive/1.3.6/demo/doc/archive/1.3.6/jsPlumbDoc.css similarity index 100% rename from build/1.3.6/demo/doc/archive/1.3.6/jsPlumbDoc.css rename to archive/1.3.6/demo/doc/archive/1.3.6/jsPlumbDoc.css diff --git a/build/1.3.6/demo/doc/archive/1.3.6/usage.html b/archive/1.3.6/demo/doc/archive/1.3.6/usage.html similarity index 100% rename from build/1.3.6/demo/doc/archive/1.3.6/usage.html rename to archive/1.3.6/demo/doc/archive/1.3.6/usage.html diff --git a/build/1.3.6/demo/doc/content.html b/archive/1.3.6/demo/doc/content.html similarity index 100% rename from build/1.3.6/demo/doc/content.html rename to archive/1.3.6/demo/doc/content.html diff --git a/build/1.3.6/demo/doc/index.html b/archive/1.3.6/demo/doc/index.html similarity index 100% rename from build/1.3.6/demo/doc/index.html rename to archive/1.3.6/demo/doc/index.html diff --git a/build/1.3.6/demo/doc/jsPlumbDoc.css b/archive/1.3.6/demo/doc/jsPlumbDoc.css similarity index 100% rename from build/1.3.6/demo/doc/jsPlumbDoc.css rename to archive/1.3.6/demo/doc/jsPlumbDoc.css diff --git a/build/1.3.6/demo/doc/usage.html b/archive/1.3.6/demo/doc/usage.html similarity index 100% rename from build/1.3.6/demo/doc/usage.html rename to archive/1.3.6/demo/doc/usage.html diff --git a/build/1.3.6/demo/img/bigdot.jpg b/archive/1.3.6/demo/img/bigdot.jpg similarity index 100% rename from build/1.3.6/demo/img/bigdot.jpg rename to archive/1.3.6/demo/img/bigdot.jpg diff --git a/build/1.3.6/demo/img/bigdot.png b/archive/1.3.6/demo/img/bigdot.png similarity index 100% rename from build/1.3.6/demo/img/bigdot.png rename to archive/1.3.6/demo/img/bigdot.png diff --git a/build/1.3.6/demo/img/bigdot.xcf b/archive/1.3.6/demo/img/bigdot.xcf similarity index 100% rename from build/1.3.6/demo/img/bigdot.xcf rename to archive/1.3.6/demo/img/bigdot.xcf diff --git a/build/1.3.6/demo/img/dragging_1.jpg b/archive/1.3.6/demo/img/dragging_1.jpg similarity index 100% rename from build/1.3.6/demo/img/dragging_1.jpg rename to archive/1.3.6/demo/img/dragging_1.jpg diff --git a/build/1.3.6/demo/img/dragging_2.jpg b/archive/1.3.6/demo/img/dragging_2.jpg similarity index 100% rename from build/1.3.6/demo/img/dragging_2.jpg rename to archive/1.3.6/demo/img/dragging_2.jpg diff --git a/build/1.3.6/demo/img/dragging_3.jpg b/archive/1.3.6/demo/img/dragging_3.jpg similarity index 100% rename from build/1.3.6/demo/img/dragging_3.jpg rename to archive/1.3.6/demo/img/dragging_3.jpg diff --git a/build/1.3.6/demo/img/dynamicAnchorBg.jpg b/archive/1.3.6/demo/img/dynamicAnchorBg.jpg similarity index 100% rename from build/1.3.6/demo/img/dynamicAnchorBg.jpg rename to archive/1.3.6/demo/img/dynamicAnchorBg.jpg diff --git a/build/1.3.6/demo/img/endpointTest1.png b/archive/1.3.6/demo/img/endpointTest1.png similarity index 100% rename from build/1.3.6/demo/img/endpointTest1.png rename to archive/1.3.6/demo/img/endpointTest1.png diff --git a/build/1.3.6/demo/img/endpointTest1.xcf b/archive/1.3.6/demo/img/endpointTest1.xcf similarity index 100% rename from build/1.3.6/demo/img/endpointTest1.xcf rename to archive/1.3.6/demo/img/endpointTest1.xcf diff --git a/build/1.3.6/demo/img/index-bg.gif b/archive/1.3.6/demo/img/index-bg.gif similarity index 100% rename from build/1.3.6/demo/img/index-bg.gif rename to archive/1.3.6/demo/img/index-bg.gif diff --git a/build/1.3.6/demo/img/issue4_final.jpg b/archive/1.3.6/demo/img/issue4_final.jpg similarity index 100% rename from build/1.3.6/demo/img/issue4_final.jpg rename to archive/1.3.6/demo/img/issue4_final.jpg diff --git a/build/1.3.6/demo/img/littledot.png b/archive/1.3.6/demo/img/littledot.png similarity index 100% rename from build/1.3.6/demo/img/littledot.png rename to archive/1.3.6/demo/img/littledot.png diff --git a/build/1.3.6/demo/img/littledot.xcf b/archive/1.3.6/demo/img/littledot.xcf similarity index 100% rename from build/1.3.6/demo/img/littledot.xcf rename to archive/1.3.6/demo/img/littledot.xcf diff --git a/build/1.3.6/demo/img/pattern.jpg b/archive/1.3.6/demo/img/pattern.jpg similarity index 100% rename from build/1.3.6/demo/img/pattern.jpg rename to archive/1.3.6/demo/img/pattern.jpg diff --git a/build/1.3.6/demo/img/swappedAnchors.jpg b/archive/1.3.6/demo/img/swappedAnchors.jpg similarity index 100% rename from build/1.3.6/demo/img/swappedAnchors.jpg rename to archive/1.3.6/demo/img/swappedAnchors.jpg diff --git a/build/1.3.6/demo/index.html b/archive/1.3.6/demo/index.html similarity index 100% rename from build/1.3.6/demo/index.html rename to archive/1.3.6/demo/index.html diff --git a/build/1.3.6/demo/jquery/anchorDemo.html b/archive/1.3.6/demo/jquery/anchorDemo.html similarity index 100% rename from build/1.3.6/demo/jquery/anchorDemo.html rename to archive/1.3.6/demo/jquery/anchorDemo.html diff --git a/build/1.3.6/demo/jquery/chartDemo.html b/archive/1.3.6/demo/jquery/chartDemo.html similarity index 100% rename from build/1.3.6/demo/jquery/chartDemo.html rename to archive/1.3.6/demo/jquery/chartDemo.html diff --git a/build/1.3.6/demo/jquery/demo.html b/archive/1.3.6/demo/jquery/demo.html similarity index 100% rename from build/1.3.6/demo/jquery/demo.html rename to archive/1.3.6/demo/jquery/demo.html diff --git a/build/1.3.6/demo/jquery/dragAnimDemo.html b/archive/1.3.6/demo/jquery/dragAnimDemo.html similarity index 100% rename from build/1.3.6/demo/jquery/dragAnimDemo.html rename to archive/1.3.6/demo/jquery/dragAnimDemo.html diff --git a/build/1.3.6/demo/jquery/draggableConnectorsDemo.html b/archive/1.3.6/demo/jquery/draggableConnectorsDemo.html similarity index 100% rename from build/1.3.6/demo/jquery/draggableConnectorsDemo.html rename to archive/1.3.6/demo/jquery/draggableConnectorsDemo.html diff --git a/build/1.3.6/demo/jquery/dynamicAnchorsDemo.html b/archive/1.3.6/demo/jquery/dynamicAnchorsDemo.html similarity index 100% rename from build/1.3.6/demo/jquery/dynamicAnchorsDemo.html rename to archive/1.3.6/demo/jquery/dynamicAnchorsDemo.html diff --git a/build/1.3.6/demo/jquery/flowchartConnectorsDemo.html b/archive/1.3.6/demo/jquery/flowchartConnectorsDemo.html similarity index 100% rename from build/1.3.6/demo/jquery/flowchartConnectorsDemo.html rename to archive/1.3.6/demo/jquery/flowchartConnectorsDemo.html diff --git a/build/1.3.6/demo/jquery/loadTest.html b/archive/1.3.6/demo/jquery/loadTest.html similarity index 100% rename from build/1.3.6/demo/jquery/loadTest.html rename to archive/1.3.6/demo/jquery/loadTest.html diff --git a/build/1.3.6/demo/jquery/makeSourceDemo.html b/archive/1.3.6/demo/jquery/makeSourceDemo.html similarity index 100% rename from build/1.3.6/demo/jquery/makeSourceDemo.html rename to archive/1.3.6/demo/jquery/makeSourceDemo.html diff --git a/build/1.3.6/demo/jquery/makeTargetDemo.html b/archive/1.3.6/demo/jquery/makeTargetDemo.html similarity index 100% rename from build/1.3.6/demo/jquery/makeTargetDemo.html rename to archive/1.3.6/demo/jquery/makeTargetDemo.html diff --git a/build/1.3.6/demo/jquery/stateMachineDemo.html b/archive/1.3.6/demo/jquery/stateMachineDemo.html similarity index 100% rename from build/1.3.6/demo/jquery/stateMachineDemo.html rename to archive/1.3.6/demo/jquery/stateMachineDemo.html diff --git a/build/1.3.6/demo/js/anchorDemo.js b/archive/1.3.6/demo/js/anchorDemo.js similarity index 100% rename from build/1.3.6/demo/js/anchorDemo.js rename to archive/1.3.6/demo/js/anchorDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.0/anchorDemo.js b/archive/1.3.6/demo/js/archive/1.3.0/anchorDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.0/anchorDemo.js rename to archive/1.3.6/demo/js/archive/1.3.0/anchorDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.0/chartDemo.js b/archive/1.3.6/demo/js/archive/1.3.0/chartDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.0/chartDemo.js rename to archive/1.3.6/demo/js/archive/1.3.0/chartDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.0/demo-helper-jquery.js b/archive/1.3.6/demo/js/archive/1.3.0/demo-helper-jquery.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.0/demo-helper-jquery.js rename to archive/1.3.6/demo/js/archive/1.3.0/demo-helper-jquery.js diff --git a/build/1.3.6/demo/js/archive/1.3.0/demo-helper-mootools.js b/archive/1.3.6/demo/js/archive/1.3.0/demo-helper-mootools.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.0/demo-helper-mootools.js rename to archive/1.3.6/demo/js/archive/1.3.0/demo-helper-mootools.js diff --git a/build/1.3.6/demo/js/archive/1.3.0/demo-helper-yui3.js b/archive/1.3.6/demo/js/archive/1.3.0/demo-helper-yui3.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.0/demo-helper-yui3.js rename to archive/1.3.6/demo/js/archive/1.3.0/demo-helper-yui3.js diff --git a/build/1.3.6/demo/js/archive/1.3.0/demo.js b/archive/1.3.6/demo/js/archive/1.3.0/demo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.0/demo.js rename to archive/1.3.6/demo/js/archive/1.3.0/demo.js diff --git a/build/1.3.6/demo/js/archive/1.3.0/dragAnimDemo-jquery.js b/archive/1.3.6/demo/js/archive/1.3.0/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.0/dragAnimDemo-jquery.js rename to archive/1.3.6/demo/js/archive/1.3.0/dragAnimDemo-jquery.js diff --git a/build/1.3.6/demo/js/archive/1.3.0/dragAnimDemo-mootools.js b/archive/1.3.6/demo/js/archive/1.3.0/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.0/dragAnimDemo-mootools.js rename to archive/1.3.6/demo/js/archive/1.3.0/dragAnimDemo-mootools.js diff --git a/build/1.3.6/demo/js/archive/1.3.0/dragAnimDemo-yui3.js b/archive/1.3.6/demo/js/archive/1.3.0/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.0/dragAnimDemo-yui3.js rename to archive/1.3.6/demo/js/archive/1.3.0/dragAnimDemo-yui3.js diff --git a/build/1.3.6/demo/js/archive/1.3.0/dragAnimDemo.js b/archive/1.3.6/demo/js/archive/1.3.0/dragAnimDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.0/dragAnimDemo.js rename to archive/1.3.6/demo/js/archive/1.3.0/dragAnimDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js b/archive/1.3.6/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js rename to archive/1.3.6/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.6/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js b/archive/1.3.6/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js rename to archive/1.3.6/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.6/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js b/archive/1.3.6/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js rename to archive/1.3.6/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.6/demo/js/archive/1.3.0/draggableConnectorsDemo.js b/archive/1.3.6/demo/js/archive/1.3.0/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.0/draggableConnectorsDemo.js rename to archive/1.3.6/demo/js/archive/1.3.0/draggableConnectorsDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.0/dynamicAnchorsDemo.js b/archive/1.3.6/demo/js/archive/1.3.0/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.0/dynamicAnchorsDemo.js rename to archive/1.3.6/demo/js/archive/1.3.0/dynamicAnchorsDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.0/flowchartConnectorsDemo.js b/archive/1.3.6/demo/js/archive/1.3.0/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.0/flowchartConnectorsDemo.js rename to archive/1.3.6/demo/js/archive/1.3.0/flowchartConnectorsDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.1/anchorDemo.js b/archive/1.3.6/demo/js/archive/1.3.1/anchorDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.1/anchorDemo.js rename to archive/1.3.6/demo/js/archive/1.3.1/anchorDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.1/chartDemo.js b/archive/1.3.6/demo/js/archive/1.3.1/chartDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.1/chartDemo.js rename to archive/1.3.6/demo/js/archive/1.3.1/chartDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.1/demo-helper-jquery.js b/archive/1.3.6/demo/js/archive/1.3.1/demo-helper-jquery.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.1/demo-helper-jquery.js rename to archive/1.3.6/demo/js/archive/1.3.1/demo-helper-jquery.js diff --git a/build/1.3.6/demo/js/archive/1.3.1/demo-helper-mootools.js b/archive/1.3.6/demo/js/archive/1.3.1/demo-helper-mootools.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.1/demo-helper-mootools.js rename to archive/1.3.6/demo/js/archive/1.3.1/demo-helper-mootools.js diff --git a/build/1.3.6/demo/js/archive/1.3.1/demo-helper-yui3.js b/archive/1.3.6/demo/js/archive/1.3.1/demo-helper-yui3.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.1/demo-helper-yui3.js rename to archive/1.3.6/demo/js/archive/1.3.1/demo-helper-yui3.js diff --git a/build/1.3.6/demo/js/archive/1.3.1/demo.js b/archive/1.3.6/demo/js/archive/1.3.1/demo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.1/demo.js rename to archive/1.3.6/demo/js/archive/1.3.1/demo.js diff --git a/build/1.3.6/demo/js/archive/1.3.1/dragAnimDemo-jquery.js b/archive/1.3.6/demo/js/archive/1.3.1/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.1/dragAnimDemo-jquery.js rename to archive/1.3.6/demo/js/archive/1.3.1/dragAnimDemo-jquery.js diff --git a/build/1.3.6/demo/js/archive/1.3.1/dragAnimDemo-mootools.js b/archive/1.3.6/demo/js/archive/1.3.1/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.1/dragAnimDemo-mootools.js rename to archive/1.3.6/demo/js/archive/1.3.1/dragAnimDemo-mootools.js diff --git a/build/1.3.6/demo/js/archive/1.3.1/dragAnimDemo-yui3.js b/archive/1.3.6/demo/js/archive/1.3.1/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.1/dragAnimDemo-yui3.js rename to archive/1.3.6/demo/js/archive/1.3.1/dragAnimDemo-yui3.js diff --git a/build/1.3.6/demo/js/archive/1.3.1/dragAnimDemo.js b/archive/1.3.6/demo/js/archive/1.3.1/dragAnimDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.1/dragAnimDemo.js rename to archive/1.3.6/demo/js/archive/1.3.1/dragAnimDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js b/archive/1.3.6/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js rename to archive/1.3.6/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.6/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js b/archive/1.3.6/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js rename to archive/1.3.6/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.6/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js b/archive/1.3.6/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js rename to archive/1.3.6/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.6/demo/js/archive/1.3.1/draggableConnectorsDemo.js b/archive/1.3.6/demo/js/archive/1.3.1/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.1/draggableConnectorsDemo.js rename to archive/1.3.6/demo/js/archive/1.3.1/draggableConnectorsDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.1/dynamicAnchorsDemo.js b/archive/1.3.6/demo/js/archive/1.3.1/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.1/dynamicAnchorsDemo.js rename to archive/1.3.6/demo/js/archive/1.3.1/dynamicAnchorsDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.1/flowchartConnectorsDemo.js b/archive/1.3.6/demo/js/archive/1.3.1/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.1/flowchartConnectorsDemo.js rename to archive/1.3.6/demo/js/archive/1.3.1/flowchartConnectorsDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.2/anchorDemo.js b/archive/1.3.6/demo/js/archive/1.3.2/anchorDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.2/anchorDemo.js rename to archive/1.3.6/demo/js/archive/1.3.2/anchorDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.2/chartDemo.js b/archive/1.3.6/demo/js/archive/1.3.2/chartDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.2/chartDemo.js rename to archive/1.3.6/demo/js/archive/1.3.2/chartDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.2/demo-helper-jquery.js b/archive/1.3.6/demo/js/archive/1.3.2/demo-helper-jquery.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.2/demo-helper-jquery.js rename to archive/1.3.6/demo/js/archive/1.3.2/demo-helper-jquery.js diff --git a/build/1.3.6/demo/js/archive/1.3.2/demo-helper-mootools.js b/archive/1.3.6/demo/js/archive/1.3.2/demo-helper-mootools.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.2/demo-helper-mootools.js rename to archive/1.3.6/demo/js/archive/1.3.2/demo-helper-mootools.js diff --git a/build/1.3.6/demo/js/archive/1.3.2/demo-helper-yui3.js b/archive/1.3.6/demo/js/archive/1.3.2/demo-helper-yui3.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.2/demo-helper-yui3.js rename to archive/1.3.6/demo/js/archive/1.3.2/demo-helper-yui3.js diff --git a/build/1.3.6/demo/js/archive/1.3.2/demo.js b/archive/1.3.6/demo/js/archive/1.3.2/demo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.2/demo.js rename to archive/1.3.6/demo/js/archive/1.3.2/demo.js diff --git a/build/1.3.6/demo/js/archive/1.3.2/dragAnimDemo-jquery.js b/archive/1.3.6/demo/js/archive/1.3.2/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.2/dragAnimDemo-jquery.js rename to archive/1.3.6/demo/js/archive/1.3.2/dragAnimDemo-jquery.js diff --git a/build/1.3.6/demo/js/archive/1.3.2/dragAnimDemo-mootools.js b/archive/1.3.6/demo/js/archive/1.3.2/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.2/dragAnimDemo-mootools.js rename to archive/1.3.6/demo/js/archive/1.3.2/dragAnimDemo-mootools.js diff --git a/build/1.3.6/demo/js/archive/1.3.2/dragAnimDemo-yui3.js b/archive/1.3.6/demo/js/archive/1.3.2/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.2/dragAnimDemo-yui3.js rename to archive/1.3.6/demo/js/archive/1.3.2/dragAnimDemo-yui3.js diff --git a/build/1.3.6/demo/js/archive/1.3.2/dragAnimDemo.js b/archive/1.3.6/demo/js/archive/1.3.2/dragAnimDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.2/dragAnimDemo.js rename to archive/1.3.6/demo/js/archive/1.3.2/dragAnimDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js b/archive/1.3.6/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js rename to archive/1.3.6/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.6/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js b/archive/1.3.6/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js rename to archive/1.3.6/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.6/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js b/archive/1.3.6/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js rename to archive/1.3.6/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.6/demo/js/archive/1.3.2/draggableConnectorsDemo.js b/archive/1.3.6/demo/js/archive/1.3.2/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.2/draggableConnectorsDemo.js rename to archive/1.3.6/demo/js/archive/1.3.2/draggableConnectorsDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.2/dynamicAnchorsDemo.js b/archive/1.3.6/demo/js/archive/1.3.2/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.2/dynamicAnchorsDemo.js rename to archive/1.3.6/demo/js/archive/1.3.2/dynamicAnchorsDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.2/flowchartConnectorsDemo.js b/archive/1.3.6/demo/js/archive/1.3.2/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.2/flowchartConnectorsDemo.js rename to archive/1.3.6/demo/js/archive/1.3.2/flowchartConnectorsDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.3/anchorDemo.js b/archive/1.3.6/demo/js/archive/1.3.3/anchorDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.3/anchorDemo.js rename to archive/1.3.6/demo/js/archive/1.3.3/anchorDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.3/chartDemo.js b/archive/1.3.6/demo/js/archive/1.3.3/chartDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.3/chartDemo.js rename to archive/1.3.6/demo/js/archive/1.3.3/chartDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.3/demo-helper-jquery.js b/archive/1.3.6/demo/js/archive/1.3.3/demo-helper-jquery.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.3/demo-helper-jquery.js rename to archive/1.3.6/demo/js/archive/1.3.3/demo-helper-jquery.js diff --git a/build/1.3.6/demo/js/archive/1.3.3/demo-helper-mootools.js b/archive/1.3.6/demo/js/archive/1.3.3/demo-helper-mootools.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.3/demo-helper-mootools.js rename to archive/1.3.6/demo/js/archive/1.3.3/demo-helper-mootools.js diff --git a/build/1.3.6/demo/js/archive/1.3.3/demo-helper-yui3.js b/archive/1.3.6/demo/js/archive/1.3.3/demo-helper-yui3.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.3/demo-helper-yui3.js rename to archive/1.3.6/demo/js/archive/1.3.3/demo-helper-yui3.js diff --git a/build/1.3.6/demo/js/archive/1.3.3/demo.js b/archive/1.3.6/demo/js/archive/1.3.3/demo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.3/demo.js rename to archive/1.3.6/demo/js/archive/1.3.3/demo.js diff --git a/build/1.3.6/demo/js/archive/1.3.3/dragAnimDemo-jquery.js b/archive/1.3.6/demo/js/archive/1.3.3/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.3/dragAnimDemo-jquery.js rename to archive/1.3.6/demo/js/archive/1.3.3/dragAnimDemo-jquery.js diff --git a/build/1.3.6/demo/js/archive/1.3.3/dragAnimDemo-mootools.js b/archive/1.3.6/demo/js/archive/1.3.3/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.3/dragAnimDemo-mootools.js rename to archive/1.3.6/demo/js/archive/1.3.3/dragAnimDemo-mootools.js diff --git a/build/1.3.6/demo/js/archive/1.3.3/dragAnimDemo-yui3.js b/archive/1.3.6/demo/js/archive/1.3.3/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.3/dragAnimDemo-yui3.js rename to archive/1.3.6/demo/js/archive/1.3.3/dragAnimDemo-yui3.js diff --git a/build/1.3.6/demo/js/archive/1.3.3/dragAnimDemo.js b/archive/1.3.6/demo/js/archive/1.3.3/dragAnimDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.3/dragAnimDemo.js rename to archive/1.3.6/demo/js/archive/1.3.3/dragAnimDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js b/archive/1.3.6/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js rename to archive/1.3.6/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.6/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js b/archive/1.3.6/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js rename to archive/1.3.6/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.6/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js b/archive/1.3.6/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js rename to archive/1.3.6/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.6/demo/js/archive/1.3.3/draggableConnectorsDemo.js b/archive/1.3.6/demo/js/archive/1.3.3/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.3/draggableConnectorsDemo.js rename to archive/1.3.6/demo/js/archive/1.3.3/draggableConnectorsDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.3/dynamicAnchorsDemo.js b/archive/1.3.6/demo/js/archive/1.3.3/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.3/dynamicAnchorsDemo.js rename to archive/1.3.6/demo/js/archive/1.3.3/dynamicAnchorsDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.3/flowchartConnectorsDemo.js b/archive/1.3.6/demo/js/archive/1.3.3/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.3/flowchartConnectorsDemo.js rename to archive/1.3.6/demo/js/archive/1.3.3/flowchartConnectorsDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js b/archive/1.3.6/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js rename to archive/1.3.6/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js diff --git a/build/1.3.7/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all.js b/archive/1.3.6/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all.js rename to archive/1.3.6/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all.js diff --git a/build/1.3.7/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js b/archive/1.3.6/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js rename to archive/1.3.6/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js diff --git a/build/1.3.7/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js b/archive/1.3.6/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js rename to archive/1.3.6/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js diff --git a/build/1.3.7/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js b/archive/1.3.6/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js rename to archive/1.3.6/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js diff --git a/build/1.3.7/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js b/archive/1.3.6/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js rename to archive/1.3.6/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js diff --git a/build/1.3.7/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js b/archive/1.3.6/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js rename to archive/1.3.6/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js diff --git a/build/1.3.6/demo/js/archive/1.3.4/anchorDemo.js b/archive/1.3.6/demo/js/archive/1.3.4/anchorDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.4/anchorDemo.js rename to archive/1.3.6/demo/js/archive/1.3.4/anchorDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.4/chartDemo.js b/archive/1.3.6/demo/js/archive/1.3.4/chartDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.4/chartDemo.js rename to archive/1.3.6/demo/js/archive/1.3.4/chartDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.4/demo-helper-jquery.js b/archive/1.3.6/demo/js/archive/1.3.4/demo-helper-jquery.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.4/demo-helper-jquery.js rename to archive/1.3.6/demo/js/archive/1.3.4/demo-helper-jquery.js diff --git a/build/1.3.6/demo/js/archive/1.3.4/demo-helper-mootools.js b/archive/1.3.6/demo/js/archive/1.3.4/demo-helper-mootools.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.4/demo-helper-mootools.js rename to archive/1.3.6/demo/js/archive/1.3.4/demo-helper-mootools.js diff --git a/build/1.3.6/demo/js/archive/1.3.4/demo-helper-yui3.js b/archive/1.3.6/demo/js/archive/1.3.4/demo-helper-yui3.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.4/demo-helper-yui3.js rename to archive/1.3.6/demo/js/archive/1.3.4/demo-helper-yui3.js diff --git a/build/1.3.6/demo/js/archive/1.3.4/demo-list.js b/archive/1.3.6/demo/js/archive/1.3.4/demo-list.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.4/demo-list.js rename to archive/1.3.6/demo/js/archive/1.3.4/demo-list.js diff --git a/build/1.3.6/demo/js/archive/1.3.4/demo.js b/archive/1.3.6/demo/js/archive/1.3.4/demo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.4/demo.js rename to archive/1.3.6/demo/js/archive/1.3.4/demo.js diff --git a/build/1.3.6/demo/js/archive/1.3.4/dragAnimDemo-jquery.js b/archive/1.3.6/demo/js/archive/1.3.4/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.4/dragAnimDemo-jquery.js rename to archive/1.3.6/demo/js/archive/1.3.4/dragAnimDemo-jquery.js diff --git a/build/1.3.6/demo/js/archive/1.3.4/dragAnimDemo-mootools.js b/archive/1.3.6/demo/js/archive/1.3.4/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.4/dragAnimDemo-mootools.js rename to archive/1.3.6/demo/js/archive/1.3.4/dragAnimDemo-mootools.js diff --git a/build/1.3.6/demo/js/archive/1.3.4/dragAnimDemo-yui3.js b/archive/1.3.6/demo/js/archive/1.3.4/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.4/dragAnimDemo-yui3.js rename to archive/1.3.6/demo/js/archive/1.3.4/dragAnimDemo-yui3.js diff --git a/build/1.3.6/demo/js/archive/1.3.4/dragAnimDemo.js b/archive/1.3.6/demo/js/archive/1.3.4/dragAnimDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.4/dragAnimDemo.js rename to archive/1.3.6/demo/js/archive/1.3.4/dragAnimDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.4/draggableConnectorsDemo-jquery.js b/archive/1.3.6/demo/js/archive/1.3.4/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.4/draggableConnectorsDemo-jquery.js rename to archive/1.3.6/demo/js/archive/1.3.4/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.6/demo/js/archive/1.3.4/draggableConnectorsDemo-mootools.js b/archive/1.3.6/demo/js/archive/1.3.4/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.4/draggableConnectorsDemo-mootools.js rename to archive/1.3.6/demo/js/archive/1.3.4/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.6/demo/js/archive/1.3.4/draggableConnectorsDemo-yui3.js b/archive/1.3.6/demo/js/archive/1.3.4/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.4/draggableConnectorsDemo-yui3.js rename to archive/1.3.6/demo/js/archive/1.3.4/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.6/demo/js/archive/1.3.4/draggableConnectorsDemo.js b/archive/1.3.6/demo/js/archive/1.3.4/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.4/draggableConnectorsDemo.js rename to archive/1.3.6/demo/js/archive/1.3.4/draggableConnectorsDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.4/dynamicAnchorsDemo.js b/archive/1.3.6/demo/js/archive/1.3.4/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.4/dynamicAnchorsDemo.js rename to archive/1.3.6/demo/js/archive/1.3.4/dynamicAnchorsDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.4/flowchartConnectorsDemo.js b/archive/1.3.6/demo/js/archive/1.3.4/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.4/flowchartConnectorsDemo.js rename to archive/1.3.6/demo/js/archive/1.3.4/flowchartConnectorsDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.4/makeSourceDemo.js b/archive/1.3.6/demo/js/archive/1.3.4/makeSourceDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.4/makeSourceDemo.js rename to archive/1.3.6/demo/js/archive/1.3.4/makeSourceDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.4/makeTargetDemo.js b/archive/1.3.6/demo/js/archive/1.3.4/makeTargetDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.4/makeTargetDemo.js rename to archive/1.3.6/demo/js/archive/1.3.4/makeTargetDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.4/stateMachineDemo-jquery.js b/archive/1.3.6/demo/js/archive/1.3.4/stateMachineDemo-jquery.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.4/stateMachineDemo-jquery.js rename to archive/1.3.6/demo/js/archive/1.3.4/stateMachineDemo-jquery.js diff --git a/build/1.3.6/demo/js/archive/1.3.4/stateMachineDemo-mootools.js b/archive/1.3.6/demo/js/archive/1.3.4/stateMachineDemo-mootools.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.4/stateMachineDemo-mootools.js rename to archive/1.3.6/demo/js/archive/1.3.4/stateMachineDemo-mootools.js diff --git a/build/1.3.6/demo/js/archive/1.3.4/stateMachineDemo-yui3.js b/archive/1.3.6/demo/js/archive/1.3.4/stateMachineDemo-yui3.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.4/stateMachineDemo-yui3.js rename to archive/1.3.6/demo/js/archive/1.3.4/stateMachineDemo-yui3.js diff --git a/build/1.3.6/demo/js/archive/1.3.4/stateMachineDemo.js b/archive/1.3.6/demo/js/archive/1.3.4/stateMachineDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.4/stateMachineDemo.js rename to archive/1.3.6/demo/js/archive/1.3.4/stateMachineDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.5/anchorDemo.js b/archive/1.3.6/demo/js/archive/1.3.5/anchorDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.5/anchorDemo.js rename to archive/1.3.6/demo/js/archive/1.3.5/anchorDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.5/chartDemo.js b/archive/1.3.6/demo/js/archive/1.3.5/chartDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.5/chartDemo.js rename to archive/1.3.6/demo/js/archive/1.3.5/chartDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.5/demo-helper-jquery.js b/archive/1.3.6/demo/js/archive/1.3.5/demo-helper-jquery.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.5/demo-helper-jquery.js rename to archive/1.3.6/demo/js/archive/1.3.5/demo-helper-jquery.js diff --git a/build/1.3.6/demo/js/archive/1.3.5/demo-helper-mootools.js b/archive/1.3.6/demo/js/archive/1.3.5/demo-helper-mootools.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.5/demo-helper-mootools.js rename to archive/1.3.6/demo/js/archive/1.3.5/demo-helper-mootools.js diff --git a/build/1.3.6/demo/js/archive/1.3.5/demo-helper-yui3.js b/archive/1.3.6/demo/js/archive/1.3.5/demo-helper-yui3.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.5/demo-helper-yui3.js rename to archive/1.3.6/demo/js/archive/1.3.5/demo-helper-yui3.js diff --git a/build/1.3.6/demo/js/archive/1.3.5/demo-list.js b/archive/1.3.6/demo/js/archive/1.3.5/demo-list.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.5/demo-list.js rename to archive/1.3.6/demo/js/archive/1.3.5/demo-list.js diff --git a/build/1.3.6/demo/js/archive/1.3.5/demo.js b/archive/1.3.6/demo/js/archive/1.3.5/demo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.5/demo.js rename to archive/1.3.6/demo/js/archive/1.3.5/demo.js diff --git a/build/1.3.6/demo/js/archive/1.3.5/dragAnimDemo-jquery.js b/archive/1.3.6/demo/js/archive/1.3.5/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.5/dragAnimDemo-jquery.js rename to archive/1.3.6/demo/js/archive/1.3.5/dragAnimDemo-jquery.js diff --git a/build/1.3.6/demo/js/archive/1.3.5/dragAnimDemo-mootools.js b/archive/1.3.6/demo/js/archive/1.3.5/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.5/dragAnimDemo-mootools.js rename to archive/1.3.6/demo/js/archive/1.3.5/dragAnimDemo-mootools.js diff --git a/build/1.3.6/demo/js/archive/1.3.5/dragAnimDemo-yui3.js b/archive/1.3.6/demo/js/archive/1.3.5/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.5/dragAnimDemo-yui3.js rename to archive/1.3.6/demo/js/archive/1.3.5/dragAnimDemo-yui3.js diff --git a/build/1.3.6/demo/js/archive/1.3.5/dragAnimDemo.js b/archive/1.3.6/demo/js/archive/1.3.5/dragAnimDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.5/dragAnimDemo.js rename to archive/1.3.6/demo/js/archive/1.3.5/dragAnimDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.5/draggableConnectorsDemo-jquery.js b/archive/1.3.6/demo/js/archive/1.3.5/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.5/draggableConnectorsDemo-jquery.js rename to archive/1.3.6/demo/js/archive/1.3.5/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.6/demo/js/archive/1.3.5/draggableConnectorsDemo-mootools.js b/archive/1.3.6/demo/js/archive/1.3.5/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.5/draggableConnectorsDemo-mootools.js rename to archive/1.3.6/demo/js/archive/1.3.5/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.6/demo/js/archive/1.3.5/draggableConnectorsDemo-yui3.js b/archive/1.3.6/demo/js/archive/1.3.5/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.5/draggableConnectorsDemo-yui3.js rename to archive/1.3.6/demo/js/archive/1.3.5/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.6/demo/js/archive/1.3.5/draggableConnectorsDemo.js b/archive/1.3.6/demo/js/archive/1.3.5/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.5/draggableConnectorsDemo.js rename to archive/1.3.6/demo/js/archive/1.3.5/draggableConnectorsDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.5/dynamicAnchorsDemo.js b/archive/1.3.6/demo/js/archive/1.3.5/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.5/dynamicAnchorsDemo.js rename to archive/1.3.6/demo/js/archive/1.3.5/dynamicAnchorsDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.5/flowchartConnectorsDemo.js b/archive/1.3.6/demo/js/archive/1.3.5/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.5/flowchartConnectorsDemo.js rename to archive/1.3.6/demo/js/archive/1.3.5/flowchartConnectorsDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.5/loadTest.js b/archive/1.3.6/demo/js/archive/1.3.5/loadTest.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.5/loadTest.js rename to archive/1.3.6/demo/js/archive/1.3.5/loadTest.js diff --git a/build/1.3.6/demo/js/archive/1.3.5/makeSourceDemo.js b/archive/1.3.6/demo/js/archive/1.3.5/makeSourceDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.5/makeSourceDemo.js rename to archive/1.3.6/demo/js/archive/1.3.5/makeSourceDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.5/makeTargetDemo.js b/archive/1.3.6/demo/js/archive/1.3.5/makeTargetDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.5/makeTargetDemo.js rename to archive/1.3.6/demo/js/archive/1.3.5/makeTargetDemo.js diff --git a/build/1.3.6/demo/js/archive/1.3.5/stateMachineDemo-jquery.js b/archive/1.3.6/demo/js/archive/1.3.5/stateMachineDemo-jquery.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.5/stateMachineDemo-jquery.js rename to archive/1.3.6/demo/js/archive/1.3.5/stateMachineDemo-jquery.js diff --git a/build/1.3.6/demo/js/archive/1.3.5/stateMachineDemo-mootools.js b/archive/1.3.6/demo/js/archive/1.3.5/stateMachineDemo-mootools.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.5/stateMachineDemo-mootools.js rename to archive/1.3.6/demo/js/archive/1.3.5/stateMachineDemo-mootools.js diff --git a/build/1.3.6/demo/js/archive/1.3.5/stateMachineDemo-yui3.js b/archive/1.3.6/demo/js/archive/1.3.5/stateMachineDemo-yui3.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.5/stateMachineDemo-yui3.js rename to archive/1.3.6/demo/js/archive/1.3.5/stateMachineDemo-yui3.js diff --git a/build/1.3.6/demo/js/archive/1.3.5/stateMachineDemo.js b/archive/1.3.6/demo/js/archive/1.3.5/stateMachineDemo.js similarity index 100% rename from build/1.3.6/demo/js/archive/1.3.5/stateMachineDemo.js rename to archive/1.3.6/demo/js/archive/1.3.5/stateMachineDemo.js diff --git a/build/1.3.6/demo/js/chartDemo.js b/archive/1.3.6/demo/js/chartDemo.js similarity index 100% rename from build/1.3.6/demo/js/chartDemo.js rename to archive/1.3.6/demo/js/chartDemo.js diff --git a/build/1.3.6/demo/js/demo-helper-jquery.js b/archive/1.3.6/demo/js/demo-helper-jquery.js similarity index 100% rename from build/1.3.6/demo/js/demo-helper-jquery.js rename to archive/1.3.6/demo/js/demo-helper-jquery.js diff --git a/build/1.3.6/demo/js/demo-helper-mootools.js b/archive/1.3.6/demo/js/demo-helper-mootools.js similarity index 100% rename from build/1.3.6/demo/js/demo-helper-mootools.js rename to archive/1.3.6/demo/js/demo-helper-mootools.js diff --git a/build/1.3.6/demo/js/demo-helper-yui3.js b/archive/1.3.6/demo/js/demo-helper-yui3.js similarity index 100% rename from build/1.3.6/demo/js/demo-helper-yui3.js rename to archive/1.3.6/demo/js/demo-helper-yui3.js diff --git a/build/1.3.6/demo/js/demo-list.js b/archive/1.3.6/demo/js/demo-list.js similarity index 100% rename from build/1.3.6/demo/js/demo-list.js rename to archive/1.3.6/demo/js/demo-list.js diff --git a/build/1.3.6/demo/js/demo.js b/archive/1.3.6/demo/js/demo.js similarity index 100% rename from build/1.3.6/demo/js/demo.js rename to archive/1.3.6/demo/js/demo.js diff --git a/build/1.3.6/demo/js/dragAnimDemo-jquery.js b/archive/1.3.6/demo/js/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.6/demo/js/dragAnimDemo-jquery.js rename to archive/1.3.6/demo/js/dragAnimDemo-jquery.js diff --git a/build/1.3.6/demo/js/dragAnimDemo-mootools.js b/archive/1.3.6/demo/js/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.6/demo/js/dragAnimDemo-mootools.js rename to archive/1.3.6/demo/js/dragAnimDemo-mootools.js diff --git a/build/1.3.6/demo/js/dragAnimDemo-yui3.js b/archive/1.3.6/demo/js/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.6/demo/js/dragAnimDemo-yui3.js rename to archive/1.3.6/demo/js/dragAnimDemo-yui3.js diff --git a/build/1.3.6/demo/js/dragAnimDemo.js b/archive/1.3.6/demo/js/dragAnimDemo.js similarity index 100% rename from build/1.3.6/demo/js/dragAnimDemo.js rename to archive/1.3.6/demo/js/dragAnimDemo.js diff --git a/build/1.3.6/demo/js/draggableConnectorsDemo-jquery.js b/archive/1.3.6/demo/js/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.6/demo/js/draggableConnectorsDemo-jquery.js rename to archive/1.3.6/demo/js/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.6/demo/js/draggableConnectorsDemo-mootools.js b/archive/1.3.6/demo/js/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.6/demo/js/draggableConnectorsDemo-mootools.js rename to archive/1.3.6/demo/js/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.6/demo/js/draggableConnectorsDemo-yui3.js b/archive/1.3.6/demo/js/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.6/demo/js/draggableConnectorsDemo-yui3.js rename to archive/1.3.6/demo/js/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.6/demo/js/draggableConnectorsDemo.js b/archive/1.3.6/demo/js/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.6/demo/js/draggableConnectorsDemo.js rename to archive/1.3.6/demo/js/draggableConnectorsDemo.js diff --git a/build/1.3.6/demo/js/dynamicAnchorsDemo.js b/archive/1.3.6/demo/js/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.6/demo/js/dynamicAnchorsDemo.js rename to archive/1.3.6/demo/js/dynamicAnchorsDemo.js diff --git a/build/1.3.6/demo/js/flowchartConnectorsDemo.js b/archive/1.3.6/demo/js/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.6/demo/js/flowchartConnectorsDemo.js rename to archive/1.3.6/demo/js/flowchartConnectorsDemo.js diff --git a/build/1.3.6/demo/js/jquery.jsPlumb-1.3.6-all-min.js b/archive/1.3.6/demo/js/jquery.jsPlumb-1.3.6-all-min.js similarity index 100% rename from build/1.3.6/demo/js/jquery.jsPlumb-1.3.6-all-min.js rename to archive/1.3.6/demo/js/jquery.jsPlumb-1.3.6-all-min.js diff --git a/build/1.3.6/demo/js/jquery.jsPlumb-1.3.6-all.js b/archive/1.3.6/demo/js/jquery.jsPlumb-1.3.6-all.js similarity index 100% rename from build/1.3.6/demo/js/jquery.jsPlumb-1.3.6-all.js rename to archive/1.3.6/demo/js/jquery.jsPlumb-1.3.6-all.js diff --git a/build/1.3.6/demo/js/loadTest.js b/archive/1.3.6/demo/js/loadTest.js similarity index 100% rename from build/1.3.6/demo/js/loadTest.js rename to archive/1.3.6/demo/js/loadTest.js diff --git a/build/1.3.6/demo/js/makeSourceDemo.js b/archive/1.3.6/demo/js/makeSourceDemo.js similarity index 100% rename from build/1.3.6/demo/js/makeSourceDemo.js rename to archive/1.3.6/demo/js/makeSourceDemo.js diff --git a/build/1.3.6/demo/js/makeTargetDemo.js b/archive/1.3.6/demo/js/makeTargetDemo.js similarity index 100% rename from build/1.3.6/demo/js/makeTargetDemo.js rename to archive/1.3.6/demo/js/makeTargetDemo.js diff --git a/build/1.3.6/demo/js/mootools-1.3.2.1-more.js b/archive/1.3.6/demo/js/mootools-1.3.2.1-more.js similarity index 100% rename from build/1.3.6/demo/js/mootools-1.3.2.1-more.js rename to archive/1.3.6/demo/js/mootools-1.3.2.1-more.js diff --git a/build/1.3.6/demo/js/mootools.jsPlumb-1.3.6-all-min.js b/archive/1.3.6/demo/js/mootools.jsPlumb-1.3.6-all-min.js similarity index 100% rename from build/1.3.6/demo/js/mootools.jsPlumb-1.3.6-all-min.js rename to archive/1.3.6/demo/js/mootools.jsPlumb-1.3.6-all-min.js diff --git a/build/1.3.6/demo/js/mootools.jsPlumb-1.3.6-all.js b/archive/1.3.6/demo/js/mootools.jsPlumb-1.3.6-all.js similarity index 100% rename from build/1.3.6/demo/js/mootools.jsPlumb-1.3.6-all.js rename to archive/1.3.6/demo/js/mootools.jsPlumb-1.3.6-all.js diff --git a/build/1.3.6/demo/js/stateMachineDemo-jquery.js b/archive/1.3.6/demo/js/stateMachineDemo-jquery.js similarity index 100% rename from build/1.3.6/demo/js/stateMachineDemo-jquery.js rename to archive/1.3.6/demo/js/stateMachineDemo-jquery.js diff --git a/build/1.3.6/demo/js/stateMachineDemo-mootools.js b/archive/1.3.6/demo/js/stateMachineDemo-mootools.js similarity index 100% rename from build/1.3.6/demo/js/stateMachineDemo-mootools.js rename to archive/1.3.6/demo/js/stateMachineDemo-mootools.js diff --git a/build/1.3.6/demo/js/stateMachineDemo-yui3.js b/archive/1.3.6/demo/js/stateMachineDemo-yui3.js similarity index 100% rename from build/1.3.6/demo/js/stateMachineDemo-yui3.js rename to archive/1.3.6/demo/js/stateMachineDemo-yui3.js diff --git a/build/1.3.6/demo/js/stateMachineDemo.js b/archive/1.3.6/demo/js/stateMachineDemo.js similarity index 100% rename from build/1.3.6/demo/js/stateMachineDemo.js rename to archive/1.3.6/demo/js/stateMachineDemo.js diff --git a/build/1.3.6/demo/js/yui-3.3.0-min.js b/archive/1.3.6/demo/js/yui-3.3.0-min.js similarity index 100% rename from build/1.3.6/demo/js/yui-3.3.0-min.js rename to archive/1.3.6/demo/js/yui-3.3.0-min.js diff --git a/build/1.3.6/demo/js/yui.jsPlumb-1.3.6-all-min.js b/archive/1.3.6/demo/js/yui.jsPlumb-1.3.6-all-min.js similarity index 100% rename from build/1.3.6/demo/js/yui.jsPlumb-1.3.6-all-min.js rename to archive/1.3.6/demo/js/yui.jsPlumb-1.3.6-all-min.js diff --git a/build/1.3.6/demo/js/yui.jsPlumb-1.3.6-all.js b/archive/1.3.6/demo/js/yui.jsPlumb-1.3.6-all.js similarity index 100% rename from build/1.3.6/demo/js/yui.jsPlumb-1.3.6-all.js rename to archive/1.3.6/demo/js/yui.jsPlumb-1.3.6-all.js diff --git a/build/1.3.6/demo/mootools/anchorDemo.html b/archive/1.3.6/demo/mootools/anchorDemo.html similarity index 100% rename from build/1.3.6/demo/mootools/anchorDemo.html rename to archive/1.3.6/demo/mootools/anchorDemo.html diff --git a/build/1.3.6/demo/mootools/chartDemo.html b/archive/1.3.6/demo/mootools/chartDemo.html similarity index 100% rename from build/1.3.6/demo/mootools/chartDemo.html rename to archive/1.3.6/demo/mootools/chartDemo.html diff --git a/build/1.3.6/demo/mootools/demo.html b/archive/1.3.6/demo/mootools/demo.html similarity index 100% rename from build/1.3.6/demo/mootools/demo.html rename to archive/1.3.6/demo/mootools/demo.html diff --git a/build/1.3.6/demo/mootools/dragAnimDemo.html b/archive/1.3.6/demo/mootools/dragAnimDemo.html similarity index 100% rename from build/1.3.6/demo/mootools/dragAnimDemo.html rename to archive/1.3.6/demo/mootools/dragAnimDemo.html diff --git a/build/1.3.6/demo/mootools/draggableConnectorsDemo.html b/archive/1.3.6/demo/mootools/draggableConnectorsDemo.html similarity index 100% rename from build/1.3.6/demo/mootools/draggableConnectorsDemo.html rename to archive/1.3.6/demo/mootools/draggableConnectorsDemo.html diff --git a/build/1.3.6/demo/mootools/dynamicAnchorsDemo.html b/archive/1.3.6/demo/mootools/dynamicAnchorsDemo.html similarity index 100% rename from build/1.3.6/demo/mootools/dynamicAnchorsDemo.html rename to archive/1.3.6/demo/mootools/dynamicAnchorsDemo.html diff --git a/build/1.3.6/demo/mootools/flowchartConnectorsDemo.html b/archive/1.3.6/demo/mootools/flowchartConnectorsDemo.html similarity index 100% rename from build/1.3.6/demo/mootools/flowchartConnectorsDemo.html rename to archive/1.3.6/demo/mootools/flowchartConnectorsDemo.html diff --git a/build/1.3.6/demo/mootools/stateMachineDemo.html b/archive/1.3.6/demo/mootools/stateMachineDemo.html similarity index 100% rename from build/1.3.6/demo/mootools/stateMachineDemo.html rename to archive/1.3.6/demo/mootools/stateMachineDemo.html diff --git a/build/1.3.6/demo/yui3/anchorDemo.html b/archive/1.3.6/demo/yui3/anchorDemo.html similarity index 100% rename from build/1.3.6/demo/yui3/anchorDemo.html rename to archive/1.3.6/demo/yui3/anchorDemo.html diff --git a/build/1.3.6/demo/yui3/chartDemo.html b/archive/1.3.6/demo/yui3/chartDemo.html similarity index 100% rename from build/1.3.6/demo/yui3/chartDemo.html rename to archive/1.3.6/demo/yui3/chartDemo.html diff --git a/build/1.3.6/demo/yui3/demo.html b/archive/1.3.6/demo/yui3/demo.html similarity index 100% rename from build/1.3.6/demo/yui3/demo.html rename to archive/1.3.6/demo/yui3/demo.html diff --git a/build/1.3.6/demo/yui3/dragAnimDemo.html b/archive/1.3.6/demo/yui3/dragAnimDemo.html similarity index 100% rename from build/1.3.6/demo/yui3/dragAnimDemo.html rename to archive/1.3.6/demo/yui3/dragAnimDemo.html diff --git a/build/1.3.6/demo/yui3/draggableConnectorsDemo.html b/archive/1.3.6/demo/yui3/draggableConnectorsDemo.html similarity index 100% rename from build/1.3.6/demo/yui3/draggableConnectorsDemo.html rename to archive/1.3.6/demo/yui3/draggableConnectorsDemo.html diff --git a/build/1.3.6/demo/yui3/dynamicAnchorsDemo.html b/archive/1.3.6/demo/yui3/dynamicAnchorsDemo.html similarity index 100% rename from build/1.3.6/demo/yui3/dynamicAnchorsDemo.html rename to archive/1.3.6/demo/yui3/dynamicAnchorsDemo.html diff --git a/build/1.3.6/demo/yui3/flowchartConnectorsDemo.html b/archive/1.3.6/demo/yui3/flowchartConnectorsDemo.html similarity index 100% rename from build/1.3.6/demo/yui3/flowchartConnectorsDemo.html rename to archive/1.3.6/demo/yui3/flowchartConnectorsDemo.html diff --git a/build/1.3.6/demo/yui3/stateMachineDemo.html b/archive/1.3.6/demo/yui3/stateMachineDemo.html similarity index 100% rename from build/1.3.6/demo/yui3/stateMachineDemo.html rename to archive/1.3.6/demo/yui3/stateMachineDemo.html diff --git a/archive/1.3.6/jquery.jsPlumb-1.3.6-RC1.js b/archive/1.3.6/jquery.jsPlumb-1.3.6-RC1.js new file mode 100644 index 000000000..93686c933 --- /dev/null +++ b/archive/1.3.6/jquery.jsPlumb-1.3.6-RC1.js @@ -0,0 +1,355 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.6 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the jQuery adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ +/* + * the library specific functions, such as find offset, get id, get attribute, extend etc. + * the full list is: + * + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getDropScope gets the drop scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getPageXY gets the page event's xy location. + * getParent gets the parent of some element. + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * hasClass returns whether or not the given element has the given class. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + * trigger triggers some event on an element. + * unbind unbinds some listener from some element. + */ +(function($) { + + //var getBoundingClientRectSupported = "getBoundingClientRect" in document.documentElement; + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el[0].className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.addClass(el[0], clazz); + } + } + catch (e) { + // SVGAnimatedString not supported; no problem. + } + el.addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * appends the given child to the given parent. + */ + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).append(child); + }, + + /** + * event binding wrapper. it just so happens that jQuery uses 'bind' also. yui3, for example, + * uses 'on'. + */ + bind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.bind(event, callback); + }, + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step', + 'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete' + }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + getDocumentElement : function() { return document; }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + }, + + getDragScope : function(el) { + return el.draggable("option", "scope"); + }, + + getDropScope : function(el) { + return el.droppable("option", "scope"); + }, + + /** + * gets a DOM element from the given input, which might be a string (in which case we just do document.getElementById), + * a selector (in which case we return el[0]), or a DOM element already (we assume this if it's not either of the other + * two cases). this is the opposite of getElementObject below. + */ + getDOMElement : function(el) { + if (typeof(el) == "string") return document.getElementById(el); + else if (el.context) return el[0]; + else return el; + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + * + */ + getElementObject : function(el) { + return typeof(el) == "string" ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).parent(); + }, + + getScrollLeft : function(el) { + return el.scrollLeft(); + }, + + getScrollTop : function(el) { + return el.scrollTop(); + }, + + getSelector : function(spec) { + return $(spec); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e.length > 0 ? e[0].tagName : null; + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + + // this code is a workaround for the case that the element being dragged has a margin set on it. jquery UI passes + // in the wrong offset if the element has a margin (it doesn't take the margin into account). the getBoundingClientRect + // method, which is in pretty much all browsers now, reports the right numbers. but it introduces a noticeable lag, which + // i don't like. + + /*if ( getBoundingClientRectSupported ) { + var r = eventArgs[1].helper[0].getBoundingClientRect(); + return { left : r.left, top: r.top }; + } else {*/ + if (eventArgs.length == 1) { + ret = { left: eventArgs[0].pageX, top:eventArgs[0].pageY }; + } + else { + var ui = eventArgs[1], _offset = ui.offset; + ret = _offset || ui.absolutePosition; + } + return ret; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options) { + options = options || {}; + // remove helper directive if present. + options.helper = null; + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.draggable(options); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.droppable(options); + }, + + isAlreadyDraggable : function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return el.hasClass("ui-draggable"); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el[0].className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.removeClass(el[0], clazz); + } + } + catch (e) { + // SVGAnimatedString not supported; no problem. + } + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).remove(); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * sets the drag scope. probably time for a setDragOption method (roll this and the one above together) + * @param el + * @param scope + */ + setDragScope : function(el, scope) { + el.draggable("option", "scope", scope); + }, + + setOffset : function(el, o) { + jsPlumb.CurrentLibrary.getElementObject(el).offset(o); + }, + + /** + * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself. + * the other libraries do not. yui, in fact, cannot even pass an original event. we have to pull out stuff + * from the originalEvent to put in an options object for YUI. + * @param el + * @param event + * @param originalEvent + */ + trigger : function(el, event, originalEvent) { + //originalEvent.stopPropagation(); + //jsPlumb.CurrentLibrary.getElementObject(el).trigger(originalEvent); + var h = jQuery._data(jsPlumb.CurrentLibrary.getElementObject(el)[0], "handle"); + h(originalEvent); + //originalEvent.stopPropagation(); + }, + + /** + * event unbinding wrapper. it just so happens that jQuery uses 'unbind' also. yui3, for example, + * uses..something else. + */ + unbind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.unbind(event, callback); + } + }; + + $(document).ready(jsPlumb.init); + +})(jQuery); + diff --git a/build/1.3.6/js/jquery.jsPlumb-1.3.6-all-min.js b/archive/1.3.6/js/jquery.jsPlumb-1.3.6-all-min.js similarity index 100% rename from build/1.3.6/js/jquery.jsPlumb-1.3.6-all-min.js rename to archive/1.3.6/js/jquery.jsPlumb-1.3.6-all-min.js diff --git a/build/1.3.6/js/jquery.jsPlumb-1.3.6-all.js b/archive/1.3.6/js/jquery.jsPlumb-1.3.6-all.js similarity index 100% rename from build/1.3.6/js/jquery.jsPlumb-1.3.6-all.js rename to archive/1.3.6/js/jquery.jsPlumb-1.3.6-all.js diff --git a/build/1.3.6/js/jsPlumb-1.3.6-tests.js b/archive/1.3.6/js/jsPlumb-1.3.6-tests.js similarity index 100% rename from build/1.3.6/js/jsPlumb-1.3.6-tests.js rename to archive/1.3.6/js/jsPlumb-1.3.6-tests.js diff --git a/build/1.3.6/js/lib/qunit.js b/archive/1.3.6/js/lib/qunit.js similarity index 100% rename from build/1.3.6/js/lib/qunit.js rename to archive/1.3.6/js/lib/qunit.js diff --git a/build/1.3.6/js/mootools.jsPlumb-1.3.6-all-min.js b/archive/1.3.6/js/mootools.jsPlumb-1.3.6-all-min.js similarity index 100% rename from build/1.3.6/js/mootools.jsPlumb-1.3.6-all-min.js rename to archive/1.3.6/js/mootools.jsPlumb-1.3.6-all-min.js diff --git a/build/1.3.6/js/mootools.jsPlumb-1.3.6-all.js b/archive/1.3.6/js/mootools.jsPlumb-1.3.6-all.js similarity index 100% rename from build/1.3.6/js/mootools.jsPlumb-1.3.6-all.js rename to archive/1.3.6/js/mootools.jsPlumb-1.3.6-all.js diff --git a/build/1.3.6/js/yui.jsPlumb-1.3.6-all-min.js b/archive/1.3.6/js/yui.jsPlumb-1.3.6-all-min.js similarity index 100% rename from build/1.3.6/js/yui.jsPlumb-1.3.6-all-min.js rename to archive/1.3.6/js/yui.jsPlumb-1.3.6-all-min.js diff --git a/build/1.3.6/js/yui.jsPlumb-1.3.6-all.js b/archive/1.3.6/js/yui.jsPlumb-1.3.6-all.js similarity index 100% rename from build/1.3.6/js/yui.jsPlumb-1.3.6-all.js rename to archive/1.3.6/js/yui.jsPlumb-1.3.6-all.js diff --git a/archive/1.3.6/jsPlumb-1.3.6-RC1.js b/archive/1.3.6/jsPlumb-1.3.6-RC1.js new file mode 100644 index 000000000..16d92e848 --- /dev/null +++ b/archive/1.3.6/jsPlumb-1.3.6-RC1.js @@ -0,0 +1,5126 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.6 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the jsPlumb core code. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + /** + * Class:jsPlumb + * The jsPlumb engine, registered as a static object in the window. This object contains all of the methods you will use to + * create and maintain Connections and Endpoints. + */ + + var canvasAvailable = !!document.createElement('canvas').getContext, + svgAvailable = !!window.SVGAngle || document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"), + // TODO what is a good test for VML availability? aside from just assuming its there because nothing else is. + vmlAvailable = !(canvasAvailable | svgAvailable); + + var _findWithFunction = function(a, f) { + if (a) + for (var i = 0; i < a.length; i++) if (f(a[i])) return i; + return -1; + }, + _indexOf = function(l, v) { + return _findWithFunction(l, function(_v) { return _v == v; }); + }, + _removeWithFunction = function(a, f) { + var idx = _findWithFunction(a, f); + if (idx > -1) a.splice(idx, 1); + return idx != -1; + }, + _remove = function(l, v) { + var idx = _indexOf(l, v); + if (idx > -1) l.splice(idx, 1); + return idx != -1; + }, + // TODO support insert index + _addWithFunction = function(list, item, hashFunction) { + if (_findWithFunction(list, hashFunction) == -1) list.push(item); + }, + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = [], map[key] = l; + } + l.push(value); + return l; + }; + + // for those browsers that dont have it. they still don't have it! but at least they won't crash. + if (!window.console) + window.console = { time:function(){}, timeEnd:function(){}, group:function(){}, groupEnd:function(){}, log:function(){} }; + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + var _connectionBeingDragged = null, + _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }, + _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }, + _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }, + _logEnabled = true, + _log = function() { + if (_logEnabled && typeof console != "undefined") { + try { + var msg = arguments[arguments.length - 1]; + console.log(msg); + } + catch (e) {} + } + }, + _group = function(g) { if (_logEnabled && typeof console != "undefined") console.group(g); }, + _groupEnd = function(g) { if (_logEnabled && typeof console != "undefined") console.groupEnd(g); }, + _time = function(t) { if (_logEnabled && typeof console != "undefined") console.time(t); }, + _timeEnd = function(t) { if (_logEnabled && typeof console != "undefined") console.timeEnd(t); }; + + /** + * EventGenerator + * Superclass for objects that generate events - jsPlumb extends this, as does jsPlumbUIComponent, which all the UI elements extend. + */ + EventGenerator = function() { + var _listeners = {}, self = this; + + // this is a list of events that should re-throw any errors that occur during their dispatch. as of 1.3.0 this is private to + // jsPlumb, but it seems feasible that people might want to manipulate this list. the thinking is that we don't want event + // listeners to bring down jsPlumb - or do we. i can't make up my mind about this, but i know i want to hear about it if the "ready" + // event fails, because then my page has most likely not initialised. so i have this halfway-house solution. it will be interesting + // to hear what other people think. + var eventsToDieOn = [ "ready" ]; + + /* + * Binds a listener to an event. + * + * Parameters: + * event - name of the event to bind to. + * listener - function to execute. + */ + this.bind = function(event, listener) { + _addToList(_listeners, event, listener); + }; + /* + * Fires an update for the given event. + * + * Parameters: + * event - event to fire + * value - value to pass to the event listener(s). + * originalEvent - the original event from the browser + */ + this.fire = function(event, value, originalEvent) { + if (_listeners[event]) { + for ( var i = 0; i < _listeners[event].length; i++) { + // doing it this way rather than catching and then possibly re-throwing means that an error propagated by this + // method will have the whole call stack available in the debugger. + //if (_findIndex(eventsToDieOn, event) != -1) + if (_findWithFunction(eventsToDieOn, function(e) { return e === event}) != -1) + _listeners[event][i](value, originalEvent); + else { + // for events we don't want to die on, catch and log. + try { + _listeners[event][i](value, originalEvent); + } catch (e) { + _log("jsPlumb: fire failed for event " + event + " : " + e); + } + } + } + } + }; + /* + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + this.clearListeners = function(event) { + if (event) + delete _listeners[event]; + else { + delete _listeners; + _listeners = {}; + } + }; + + this.getListener = function(forEvent) { + return _listeners[forEvent]; + }; + }, + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + _timestamp = function() { return "" + (new Date()).getTime(); }, + + /* + * Class:jsPlumbUIComponent + * Abstract superclass for UI components Endpoint and Connection. Provides the abstraction of paintStyle/hoverPaintStyle, + * and also extends EventGenerator to provide the bind and fire methods. + */ + jsPlumbUIComponent = function(params) { + var self = this, a = arguments, _hover = false, parameters = params.parameters || {}, idPrefix = self.idPrefix, + id = idPrefix + (new Date()).getTime(); + self._jsPlumb = params["_jsPlumb"]; + self.getId = function() { return id; }; + self.tooltip = params.tooltip; + self.hoverClass = params.hoverClass; + + // all components can generate events + EventGenerator.apply(this); + // all components get this clone function. + // TODO issue 116 showed a problem with this - it seems 'a' that is in + // the clone function's scope is shared by all invocations of it, the classic + // JS closure problem. for now, jsPlumb does a version of this inline where + // it used to call clone. but it would be nice to find some time to look + // further at this. + this.clone = function() { + var o = new Object(); + self.constructor.apply(o, a); + return o; + }; + + this.getParameter = function(name) { return parameters[name]; }, + this.getParameters = function() { return parameters; }, + this.setParameter = function(name, value) { parameters[name] = value; }, + this.setParameters = function(p) { parameters = p; }, + this.overlayPlacements = [], + this.paintStyle = null, + this.hoverPaintStyle = null; + + // user can supply a beforeDetach callback, which will be executed before a detach + // is performed; returning false prevents the detach. + var beforeDetach = params.beforeDetach; + this.isDetachAllowed = function(connection) { + var r = self._jsPlumb.checkCondition("beforeDetach", connection ); + if (beforeDetach) { + try { + r = beforeDetach(connection); + } + catch (e) { _log("jsPlumb: beforeDetach callback failed", e); } + } + return r; + }; + + // user can supply a beforeDrop callback, which will be executed before a dropped + // connection is confirmed. user can return false to reject connection. + var beforeDrop = params.beforeDrop; + this.isDropAllowed = function(sourceId, targetId, scope) { + var r = self._jsPlumb.checkCondition("beforeDrop", { sourceId:sourceId, targetId:targetId, scope:scope }); + if (beforeDrop) { + try { + r = beforeDrop({ sourceId:sourceId, targetId:targetId, scope:scope }); + } + catch (e) { _log("jsPlumb: beforeDrop callback failed", e); } + } + return r; + }; + + // helper method to update the hover style whenever it, or paintStyle, changes. + // we use paintStyle as the foundation and merge hoverPaintStyle over the + // top. + var _updateHoverStyle = function() { + if (self.paintStyle && self.hoverPaintStyle) { + var mergedHoverStyle = {}; + jsPlumb.extend(mergedHoverStyle, self.paintStyle); + jsPlumb.extend(mergedHoverStyle, self.hoverPaintStyle); + delete self["hoverPaintStyle"]; + // we want the fillStyle of paintStyle to override a gradient, if possible. + if (mergedHoverStyle.gradient && self.paintStyle.fillStyle) + delete mergedHoverStyle["gradient"]; + self.hoverPaintStyle = mergedHoverStyle; + } + }; + + /* + * Sets the paint style and then repaints the element. + * + * Parameters: + * style - Style to use. + */ + this.setPaintStyle = function(style, doNotRepaint) { + self.paintStyle = style; + self.paintStyleInUse = self.paintStyle; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /* + * Sets the paint style to use when the mouse is hovering over the element. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the component will not be repainted. useful when setting things up initially. + */ + this.setHoverPaintStyle = function(style, doNotRepaint) { + self.hoverPaintStyle = style; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /* + * sets/unsets the hover state of this element. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + this.setHover = function(hover, ignoreAttachedElements, timestamp) { + // while dragging, we ignore these events. this keeps the UI from flashing and + // swishing and whatevering. + if (!self._jsPlumb.currentlyDragging && !self._jsPlumb.isHoverSuspended()) { + + _hover = hover; + if (self.hoverClass != null && self.canvas != null) { + if (hover) + jpcl.addClass(self.canvas, self.hoverClass); + else + jpcl.removeClass(self.canvas, self.hoverClass); + } + if (self.hoverPaintStyle != null) { + self.paintStyleInUse = hover ? self.hoverPaintStyle : self.paintStyle; + timestamp = timestamp || _timestamp(); + self.repaint({timestamp:timestamp, recalc:false}); + } + // get the list of other affected elements, if supported by this component. + // for a connection, its the endpoints. for an endpoint, its the connections! surprise. + if (self.getAttachedElements && !ignoreAttachedElements) + _updateAttachedElements(hover, _timestamp(), self); + } + }; + + this.isHover = function() { return _hover; }; + + var jpcl = jsPlumb.CurrentLibrary, + events = [ "click", "dblclick", "mouseenter", "mouseout", "mousemove", "mousedown", "mouseup", "contextmenu" ], + eventFilters = { "mouseout":"mouseexit" }, + bindOne = function(o, c, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.bind(o, evt, function(ee) { + c.fire(filteredEvent, c, ee); + }); + }, + unbindOne = function(o, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.unbind(o, evt); + }; + + this.attachListeners = function(o, c) { + for (var i = 0; i < events.length; i++) { + bindOne(o, c, events[i]); + } + }; + + var _updateAttachedElements = function(state, timestamp, sourceElement) { + var affectedElements = self.getAttachedElements(); // implemented in subclasses + if (affectedElements) { + for (var i = 0; i < affectedElements.length; i++) { + if (!sourceElement || sourceElement != affectedElements[i]) + affectedElements[i].setHover(state, true, timestamp); // tell the attached elements not to inform their own attached elements. + } + } + }; + + this.reattachListenersForElement = function(o) { + if (arguments.length > 1) { + for (var i = 0; i < events.length; i++) + unbindOne(o, events[i]); + for (var i = 1; i < arguments.length; i++) + self.attachListeners(o, arguments[i]); + } + }; + }, + + overlayCapableJsPlumbUIComponent = function(params) { + jsPlumbUIComponent.apply(this, arguments); + var self = this; + /* + * Property: overlays + * List of Overlays for this component. + */ + this.overlays = []; + + var processOverlay = function(o) { + var _newOverlay = null; + if (o.constructor == Array) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0], + // make a copy of the object so as not to mess up anyone else's reference... + p = jsPlumb.extend({component:self, _jsPlumb:self._jsPlumb}, o[1]); + if (o.length == 3) jsPlumb.extend(p, o[2]); + _newOverlay = new jsPlumb.Overlays[self._jsPlumb.getRenderMode()][type](p); + if (p.events) { + for (var evt in p.events) { + _newOverlay.bind(evt, p.events[evt]); + } + } + } else if (o.constructor == String) { + _newOverlay = new jsPlumb.Overlays[self._jsPlumb.getRenderMode()][o]({component:self, _jsPlumb:self._jsPlumb}); + } else { + _newOverlay = o; + } + + self.overlays.push(_newOverlay); + }, + calculateOverlaysToAdd = function(params) { + var defaultKeys = self.defaultOverlayKeys || [], + o = params.overlays, + checkKey = function(k) { + return self._jsPlumb.Defaults[k] || jsPlumb.Defaults[k] || []; + }; + + if (!o) o = []; + + for (var i = 0; i < defaultKeys.length; i++) + o.unshift.apply(o, checkKey(defaultKeys[i])); + + return o; + } + + var _overlays = calculateOverlaysToAdd(params);//params.overlays || self._jsPlumb.Defaults.Overlays; + if (_overlays) { + for (var i = 0; i < _overlays.length; i++) { + processOverlay(_overlays[i]); + } + } + + // overlay finder helper method + var _getOverlayIndex = function(id) { + var idx = -1; + for (var i = 0; i < self.overlays.length; i++) { + if (id === self.overlays[i].id) { + idx = i; + break; + } + } + return idx; + }; + + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + this.addOverlay = function(overlay) { + processOverlay(overlay); + self.repaint(); + }; + + /* + * Function: getOverlay + * Gets an overlay, by ID. Note: by ID. You would pass an 'id' parameter + * in to the Overlay's constructor arguments, and then use that to retrieve + * it via this method. + */ + this.getOverlay = function(id) { + var idx = _getOverlayIndex(id); + return idx >= 0 ? self.overlays[idx] : null; + }; + + /* + * Function: hideOverlay + * Hides the overlay specified by the given id. + */ + this.hideOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.hide(); + }; + + /* + * Function: showOverlay + * Shows the overlay specified by the given id. + */ + this.showOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.show(); + }; + + /** + * Function: removeAllOverlays + * Removes all overlays from the Connection, and then repaints. + */ + this.removeAllOverlays = function() { + self.overlays.splice(0, self.overlays.length); + self.repaint(); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + this.removeOverlay = function(overlayId) { + var idx = _getOverlayIndex(overlayId); + if (idx != -1) { + var o = self.overlays[idx]; + o.cleanup(); + self.overlays.splice(idx, 1); + } + }; + + /** + * Function:removeOverlays + * Removes a set of overlays by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + this.removeOverlays = function() { + for (var i = 0; i < arguments.length; i++) + self.removeOverlay(arguments[i]); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + var _internalLabelOverlayId = "__label", + _makeLabelOverlay = function(params) { + + var _params = { + cssClass:params.cssClass, + labelStyle : this.labelStyle, + id:_internalLabelOverlayId, + component:self, + _jsPlumb:self._jsPlumb + }, + mergedParams = jsPlumb.extend(_params, params); + + return new jsPlumb.Overlays[self._jsPlumb.getRenderMode()].Label( mergedParams ); + }; + if (params.label) { + var loc = params.labelLocation || self.defaultLabelLocation || 0.5, + labelStyle = params.labelStyle || self._jsPlumb.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.overlays.push(_makeLabelOverlay({ + label:params.label, + location:loc, + labelStyle:labelStyle + })); + } + + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String, a Function that returns a String, or a params object containing { "label", "labelStyle", "location", "cssClass" } + */ + this.setLabel = function(l) { + var lo = self.getOverlay(_internalLabelOverlayId); + if (!lo) { + var params = l.constructor == String || l.constructor == Function ? { label:l } : l; + lo = _makeLabelOverlay(params); + this.overlays.push(lo); + } + else { + if (l.constructor == String || l.constructor == Function) lo.setLabel(l); + else { + if (l.label) lo.setLabel(l.label); + if (l.location) lo.setLocation(l.location); + } + } + + self.repaint(); + }; + + /* + Function:getLabel + Returns the label text for this component (or a function if you are labelling with a function). + This does not return the overlay itself; this is a convenience method which is a pair with + setLabel; together they allow you to add and access a Label Overlay without having to create the + Overlay object itself. For access to the underlying label overlay that jsPlumb has created, + use getLabelOverlay. + */ + this.getLabel = function() { + var lo = self.getOverlay(_internalLabelOverlayId); + return lo != null ? lo.getLabel() : null; + }; + + /* + Function:getLabelOverlay + Returns the underlying internal label overlay, which will exist if you specified a label on + a connect or addEndpoint call, or have called setLabel at any stage. + */ + this.getLabelOverlay = function() { + return self.getOverlay(_internalLabelOverlayId); + } + }, + + _bindListeners = function(obj, _self, _hoverFunction) { + obj.bind("click", function(ep, e) { _self.fire("click", _self, e); }); + obj.bind("dblclick", function(ep, e) { _self.fire("dblclick", _self, e); }); + obj.bind("contextmenu", function(ep, e) { _self.fire("contextmenu", _self, e); }); + obj.bind("mouseenter", function(ep, e) { + if (!_self.isHover()) { + _hoverFunction(true); + _self.fire("mouseenter", _self, e); + } + }); + obj.bind("mouseexit", function(ep, e) { + if (_self.isHover()) { + _hoverFunction(false); + _self.fire("mouseexit", _self, e); + } + }); + }; + + var _jsPlumbInstanceIndex = 0, + getInstanceIndex = function() { + var i = _jsPlumbInstanceIndex + 1; + _jsPlumbInstanceIndex++; + return i; + }; + + var jsPlumbInstance = function(_defaults) { + + /* + * Property: Defaults + * + * These are the default settings for jsPlumb. They are what will be used if you do not supply specific pieces of information + * to the various API calls. A convenient way to implement your own look and feel can be to override these defaults + * by including a script somewhere after the jsPlumb include, but before you make any calls to jsPlumb. + * + * Properties: + * - *Anchor* The default anchor to use for all connections (both source and target). Default is "BottomCenter". + * - *Anchors* The default anchors to use ([source, target]) for all connections. Defaults are ["BottomCenter", "BottomCenter"]. + * - *ConnectionsDetachable* Whether or not connections are detachable by default (using the mouse). Defults to true. + * - *ConnectionOverlays* The default overlay definitions for Connections. Defaults to an empty list. + * - *Connector* The default connector definition to use for all connections. Default is "Bezier". + * - *Container* Optional selector or element id that instructs jsPlumb to append elements it creates to a specific element. + * - *DragOptions* The default drag options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *DropOptions* The default drop options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *Endpoint* The default endpoint definition to use for all connections (both source and target). Default is "Dot". + * - *EndpointOverlays* The default overlay definitions for Endpoints. Defaults to an empty list. + * - *Endpoints* The default endpoint definitions ([ source, target ]) to use for all connections. Defaults are ["Dot", "Dot"]. + * - *EndpointStyle* The default style definition to use for all endpoints. Default is fillStyle:"#456". + * - *EndpointStyles* The default style definitions ([ source, target ]) to use for all endpoints. Defaults are empty. + * - *EndpointHoverStyle* The default hover style definition to use for all endpoints. Default is null. + * - *EndpointHoverStyles* The default hover style definitions ([ source, target ]) to use for all endpoints. Defaults are null. + * - *HoverPaintStyle* The default hover style definition to use for all connections. Defaults are null. + * - *LabelStyle* The default style to use for label overlays on connections. + * - *LogEnabled* Whether or not the jsPlumb log is enabled. defaults to false. + * - *Overlays* The default overlay definitions (for both Connections and Endpoint). Defaults to an empty list. + * - *MaxConnections* The default maximum number of connections for an Endpoint. Defaults to 1. + * - *PaintStyle* The default paint style for a connection. Default is line width of 8 pixels, with color "#456". + * - *RenderMode* What mode to use to paint with. If you're on IE<9, you don't really get to choose this. You'll just get VML. Otherwise, the jsPlumb default is to use SVG. + * - *Scope* The default "scope" to use for connections. Scope lets you assign connections to different categories. + */ + this.Defaults = { + Anchor : "BottomCenter", + Anchors : [ null, null ], + ConnectionsDetachable : true, + ConnectionOverlays : [ ], + Connector : "Bezier", + Container : null, + DragOptions : { }, + DropOptions : { }, + Endpoint : "Dot", + EndpointOverlays : [ ], + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : "#456" }, + EndpointStyles : [ null, null ], + EndpointHoverStyle : null, + EndpointHoverStyles : [ null, null ], + HoverPaintStyle : null, + LabelStyle : { color : "black" }, + LogEnabled : false, + Overlays : [ ], + MaxConnections : 1, + PaintStyle : { lineWidth : 8, strokeStyle : "#456" }, + //Reattach:false, + RenderMode : "svg", + Scope : "jsPlumb_DefaultScope" + }; + if (_defaults) jsPlumb.extend(this.Defaults, _defaults); + + this.logEnabled = this.Defaults.LogEnabled; + + EventGenerator.apply(this); + var _currentInstance = this, + _instanceIndex = getInstanceIndex(), + _bb = _currentInstance.bind, + _initialDefaults = {}; + + for (var i in this.Defaults) + _initialDefaults[i] = this.Defaults[i]; + + this.bind = function(event, fn) { + if ("ready" === event && initialized) fn(); + else _bb.apply(_currentInstance,[event, fn]); + }; + + /* + Function: importDefaults + Imports all the given defaults into this instance of jsPlumb. + */ + _currentInstance.importDefaults = function(d) { + for (var i in d) { + _currentInstance.Defaults[i] = d[i]; + } + }; + + /* + Function:restoreDefaults + Restores the default settings to "factory" values. + */ + _currentInstance.restoreDefaults = function() { + _currentInstance.Defaults = jsPlumb.extend({}, _initialDefaults); + }; + + var log = null, + repaintFunction = function() { + jsPlumb.repaintEverything(); + }, + automaticRepaint = true, + repaintEverything = function() { + if (automaticRepaint) + repaintFunction(); + }, + resizeTimer = null, + initialized = false, + connectionsByScope = {}, + /** + * map of element id -> endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + endpointsByElement = {}, + endpointsByUUID = {}, + offsets = {}, + offsetTimestamps = {}, + floatingConnections = {}, + draggableStates = {}, + canvasList = [], + sizes = [], + //listeners = {}, // a map: keys are event types, values are lists of listeners. + DEFAULT_SCOPE = this.Defaults.Scope, + renderMode = null, // will be set in init() + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }, + + /** + * appends an element to some other element, which is calculated as follows: + * + * 1. if _currentInstance.Defaults.Container exists, use that element. + * 2. if the 'parent' parameter exists, use that. + * 3. otherwise just use the document body. + * + */ + _appendElement = function(el, parent) { + if (_currentInstance.Defaults.Container) + jsPlumb.CurrentLibrary.appendElement(el, _currentInstance.Defaults.Container); + else if (!parent) + document.body.appendChild(el); + else + jsPlumb.CurrentLibrary.appendElement(el, parent); + }, + + _curIdStamp = 1, + _idstamp = function() { return "" + _curIdStamp++; }, + + /** + * YUI, for some reason, put the result of a Y.all call into an object that contains + * a '_nodes' array, instead of handing back an array-like object like the other + * libraries do. + */ + _convertYUICollection = function(c) { + return c._nodes ? c._nodes : c; + }, + + _suspendDrawing = false, + /* + sets whether or not to suspend drawing. you should use this if you need to connect a whole load of things in one go. + it will save you a lot of time. + */ + _setSuspendDrawing = function(val, repaintAfterwards) { + _suspendDrawing = val; + if (repaintAfterwards) _currentInstance.repaintEverything(); + }, + + /** + * Draws an endpoint and its connections. this is the main entry point into drawing connections as well + * as endpoints, since jsPlumb is endpoint-centric under the hood. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + _draw = function(element, ui, timestamp) { + if (!_suspendDrawing) { + var id = _getAttribute(element, "id"), + repaintEls = _currentInstance.dragManager.getElementsForDraggable(id); + + if (timestamp == null) timestamp = _timestamp(); + + _currentInstance.anchorManager.redraw(id, ui, timestamp); + + if (repaintEls) { + for (var i in repaintEls) { + _currentInstance.anchorManager.redraw(repaintEls[i].id, ui, timestamp, repaintEls[i].offset); + } + } + } + }, + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }, + + /** + * gets an Endpoint by uuid. + */ + _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }, + + /** + * inits a draggable if it's not already initialised. + */ + _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? false : isDraggable, + jpcl = jsPlumb.CurrentLibrary; + if (draggable) { + if (jpcl.isDragSupported(element) && !jpcl.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jpcl.dragEvents["drag"], + stopEvent = jpcl.dragEvents["stop"], + startEvent = jpcl.dragEvents["start"]; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jpcl.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jpcl.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + draggableStates[_getId(element)] = true; + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jpcl.initDraggable(element, options, false); + _currentInstance.dragManager.register(element); + } + } + }, + + /* + * prepares a final params object that can be passed to _newConnection, taking into account defaults, events, etc. + */ + _prepareConnectionParams = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + // hotwire endpoints passed as source or target to sourceEndpoint/targetEndpoint, respectively. + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not full. + // source: + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + // target: + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + // copy in any connectorOverlays that were specified on the source endpoint. + // it doesnt copy target endpoint overlays. i'm not sure if we want it to or not. + if (_p.sourceEndpoint && _p.sourceEndpoint.connectorOverlays) { + _p.overlays = _p.overlays || []; + for (var i = 0; i < _p.sourceEndpoint.connectorOverlays.length; i++) { + _p.overlays.push(_p.sourceEndpoint.connectorOverlays[i]); + } + } + + // tooltip. params.tooltip takes precedence, then sourceEndpoint.connectorTooltip. + _p.tooltip = params.tooltip; + if (!_p.tooltip && _p.sourceEndpoint && _p.sourceEndpoint.connectorTooltip) + _p.tooltip = _p.sourceEndpoint.connectorTooltip; + + // if there's a target specified (which of course there should be), and there is no + // target endpoint specified, and 'newConnection' was not set to true, then we check to + // see if a prior call to makeTarget has provided us with the specs for the target endpoint, and + // we use those if so. additionally, if the makeTarget call was specified with 'uniqueEndpoint' set + // to true, then if that target endpoint has already been created, we re-use it. + if (_p.target && !_p.target.endpoint && !_p.targetEndpoint && !_p.newConnection) { + var tid = _getId(_p.target), + tep =_targetEndpointDefinitions[tid], + existingUniqueEndpoint = _targetEndpoints[tid]; + + if (tep) { + + var newEndpoint = existingUniqueEndpoint != null ? existingUniqueEndpoint : _currentInstance.addEndpoint(_p.target, tep); + if (_targetEndpointsUnique[tid]) _targetEndpoints[tid] = newEndpoint; + _p.targetEndpoint = newEndpoint; + } + } + + // same thing, but for source. + if (_p.source && !_p.source.endpoint && !_p.sourceEndpoint && !_p.newConnection) { + var tid = _getId(_p.source), + tep = _sourceEndpointDefinitions[tid], + existingUniqueEndpoint = _sourceEndpoints[tid]; + + if (tep) { + + var newEndpoint = existingUniqueEndpoint != null ? existingUniqueEndpoint : _currentInstance.addEndpoint(_p.source, tep); + if (_sourceEndpointsUnique[tid]) _sourceEndpoints[tid] = newEndpoint; + _p.sourceEndpoint = newEndpoint; + } + } + + return _p; + }, + + _newConnection = function(params) { + var connectionFunc = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + endpointFunc = _currentInstance.Defaults.EndpointType || Endpoint, + parent = jsPlumb.CurrentLibrary.getParent; + + if (params.container) + params["parent"] = params.container; + else { + if (params.sourceEndpoint) + params["parent"] = params.sourceEndpoint.parent; + else if (params.source.constructor == endpointFunc) + params["parent"] = params.source.parent; + else params["parent"] = parent(params.source); + } + + params["_jsPlumb"] = _currentInstance; + var con = new connectionFunc(params); + con.id = "con_" + _idstamp(); + _eventFireProxy("click", "click", con); + _eventFireProxy("dblclick", "dblclick", con); + _eventFireProxy("contextmenu", "contextmenu", con); + return con; + }, + + /** + * adds the connection to the backing model, fires an event if necessary and then redraws + */ + _finaliseConnection = function(jpc, params, originalEvent) { + params = params || {}; + // add to list of connections (by scope). + if (!jpc.suspendedEndpoint) + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + if (!params.doNotFireConnectionEvent && params.fireEvent !== false) { + _currentInstance.fire("jsPlumbConnection", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }, originalEvent); + } + // always inform the anchor manager + // except that if jpc has a suspended endpoint it's not true to say the + // connection is new; it has just (possibly) moved. the question is whether + // to make that call here or in the anchor manager. i think perhaps here. + _currentInstance.anchorManager.newConnection(jpc); + // force a paint + _draw(jpc.source); + }, + + _eventFireProxy = function(event, proxyEvent, obj) { + obj.bind(event, function(originalObject, originalEvent) { + _currentInstance.fire(proxyEvent, obj, originalEvent); + }); + }, + + /** + * for the given endpoint params, returns an appropriate parent element for the UI elements that will be added. + * this function is used by _newEndpoint (directly below), and also in the makeSource function in jsPlumb. + * + * the logic is to first look for a "container" member of params, and pass that back if found. otherwise we + * handoff to the 'getParent' function in the current library. + */ + _getParentFromParams = function(params) { + if (params.container) + return params.container; + else { + var tag = jsPlumb.CurrentLibrary.getTagName(params.source), + p = jsPlumb.CurrentLibrary.getParent(params.source); + if (tag && tag.toLowerCase() === "td") + return jsPlumb.CurrentLibrary.getParent(p); + else return p; + } + }, + + /** + factory method to prepare a new endpoint. this should always be used instead of creating Endpoints + manually, since this method attaches event listeners and an id. + */ + _newEndpoint = function(params) { + var endpointFunc = _currentInstance.Defaults.EndpointType || Endpoint; + params.parent = _getParentFromParams(params); + params["_jsPlumb"] = _currentInstance; + var ep = new endpointFunc(params); + ep.id = "ep_" + _idstamp(); + _eventFireProxy("click", "endpointClick", ep); + _eventFireProxy("dblclick", "endpointDblClick", ep); + _eventFireProxy("contextmenu", "contextmenu", ep); + return ep; + }, + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + _operation = function(elId, func, endpointFunc) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + if (endpointFunc) endpointFunc(endpoints[i]); + } + } + }, + /** + * perform an operation on all elements. + */ + _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }, + + /** + * helper to remove an element from the DOM. + */ + _removeElement = function(element, parent) { + if (element != null && element.parentNode != null) { + element.parentNode.removeChild(element); + } + }, + /** + * helper to remove a list of elements from the DOM. + */ + _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }, + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }, + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + _setVisible = function(el, state, alsoChangeEndpoints) { + state = state === "block"; + var endpointFunc = null; + if (alsoChangeEndpoints) { + if (state) endpointFunc = function(ep) { + ep.setVisible(true, true, true); + }; + else endpointFunc = function(ep) { + ep.setVisible(false, true, true); + }; + } + var id = _getAttribute(el, "id"); + _operation(id, function(jpc) { + if (state && alsoChangeEndpoints) { + // this test is necessary because this functionality is new, and i wanted to maintain backwards compatibility. + // this block will only set a connection to be visible if the other endpoint in the connection is also visible. + var oidx = jpc.sourceId === id ? 1 : 0; + if (jpc.endpoints[oidx].isVisible()) jpc.setVisible(true); + } + else // the default behaviour for show, and what always happens for hide, is to just set the visibility without getting clever. + jpc.setVisible(state); + }, endpointFunc); + }, + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? false : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }, + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + _toggleVisible = function(elId, changeEndpoints) { + var endpointFunc = null; + if (changeEndpoints) { + endpointFunc = function(ep) { + var state = ep.isVisible(); + ep.setVisible(!state); + }; + } + _operation(elId, function(jpc) { + var state = jpc.isVisible(); + jpc.setVisible(!state); + }, endpointFunc); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }, + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return offsets[elId]; + } + if (recalc || !offset) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + if (sizes[elId] == null) { + var s = _getElementObject(elId); + if (s != null) + sizes[elId] = _getSize(s); + } + } + + if(offsets[elId] && !offsets[elId].right) { + offsets[elId].right = offsets[elId].left + sizes[elId][0]; + offsets[elId].bottom = offsets[elId].top + sizes[elId][1]; + offsets[elId].width = sizes[elId][0]; + offsets[elId].height = sizes[elId][1]; + offsets[elId].centerx = offsets[elId].left + (offsets[elId].width / 2); + offsets[elId].centery = offsets[elId].top + (offsets[elId].height / 2); + } + return offsets[elId]; + }, + + // TODO comparison performance + _getCachedData = function(elId) { + var o = offsets[elId]; + if (!o) o = _updateOffset({elId:elId}); + return {o:o, s:sizes[elId]}; + }, + + /** + * gets an id for the given element, creating and setting one if + * necessary. the id is of the form + * + * jsPlumb__ + * + * where "index in instance" is a monotonically increasing integer that starts at 0, + * for each instance. this method is used not only to assign ids to elements that do not + * have them but also to connections and endpoints. + */ + _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else + id = "jsPlumb_" + _instanceIndex + "_" + _idstamp(); + _setAttribute(ele, "id", id); + } + return id; + }, + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, "jsPlumb function failed : " + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, "wrapped function failed : " + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = "_jsPlumb_connector"; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = "_jsPlumb_endpoint"; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = "_jsPlumb_overlay"; + + this.Anchors = {}; + + this.Connectors = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Endpoints = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Overlays = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + +// ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***************************************** + /* + * Function: bind + * Bind to an event on jsPlumb. + * + * Parameters: + * event - the event to bind. Available events on jsPlumb are: + * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback. + * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback. + * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback. + * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback. + * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback. + * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback. + * + * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event. + */ + + /* + * Function: clearListeners + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + +// *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *********************************************************** + + /* + Function: addClass + + Helper method to abstract out differences in setting css classes on the different renderer types. + */ + this.addClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.addClass(el, clazz); + }; + + /* + Function: removeClass + + Helper method to abstract out differences in setting css classes on the different renderer types. + */ + this.removeClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.removeClass(el, clazz); + }; + + /* + Function: hasClass + + Helper method to abstract out differences in testing for css classes on the different renderer types. + */ + this.hasClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.hasClass(el, clazz); + }; + + /* + Function: addEndpoint + + Adds an to a given element or elements. + + Parameters: + + el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + params - Object containing Endpoint constructor arguments. For more information, see . + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some + shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in + this object are anything that 'params' can contain. See . + + Returns: + The newly created , if el referred to a single element. Otherwise, an array of newly created s. + + See Also: + + */ + this.addEndpoint = function(el, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // YUI wrapper + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + var _el = _getElementObject(inputs[i]), id = _getId(_el); + p.source = _el; + _updateOffset({ elId : id }); + var e = _newEndpoint(p); + if (p.parentAnchor) e.parentAnchor = p.parentAnchor; + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + results.push(e); + _currentInstance.dragManager.endpointAdded(_el); + } + + return results.length == 1 ? results[0] : results; + }; + + /* + Function: addEndpoints + Adds a list of s to a given element or elements. + + Parameters: + target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See 's constructor documentation. + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created s, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(el, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams); + if (e.constructor == Array) + Array.prototype.push.apply(results, e); + else results.push(e); + } + return results; + }; + + /* + Function: animate + This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating + the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // onComplete repaints, just to make sure everything looks good at the end of the animation. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /** + * checks for a listener for the given condition, executing it if found, passing in the given value. + * condition listeners would have been attached using "bind" (which is, you could argue, now overloaded, since + * firing click events etc is a bit different to what this does). i thought about adding a "bindCondition" + * or something, but decided against it, for the sake of simplicity. jsPlumb will never fire one of these + * condition events anyway. + */ + this.checkCondition = function(conditionName, value) { + var l = _currentInstance.getListener(conditionName); + var r = true; + if (l && l.length > 0) { + try { + for (var i = 0 ; i < l.length; i++) { + r = r && l[i](value); + } + } + catch (e) { + _log(_currentInstance, "cannot check condition [" + conditionName + "]" + e); + } + } + return r; + }; + + /* + Function: connect + Establishes a between two elements (or s, which are themselves registered to elements). + + Parameters: + params - Object containing constructor arguments for the Connection. See 's constructor documentation. + referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of + Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection. + + Returns: + The newly created . + */ + this.connect = function(params, referenceParams) { + // prepare a final set of parameters to create connection with + var _p = _prepareConnectionParams(params, referenceParams); + // TODO probably a nicer return value if the connection was not made. _prepareConnectionParams + // will return null (and log something) if either endpoint was full. what would be nicer is to + // create a dedicated 'error' object. + if (_p) { + // a connect call will delete its created endpoints on detach, unless otherwise specified. + // this is because the endpoints belong to this connection only, and are no use to + // anyone else, so they hang around like a bad smell. + if (_p.deleteEndpointsOnDetach == null) + _p.deleteEndpointsOnDetach = true; + + // create the connection. it is not yet registered + var jpc = _newConnection(_p); + // now add it the model, fire an event, and redraw + _finaliseConnection(jpc, _p); + return jpc; + } + }; + + /* + Function: deleteEndpoint + Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too) + + Parameters: + object - either an object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElements(endpoint.endpoint.getDisplayElements()); + _currentInstance.anchorManager.deleteEndpoint(endpoint); + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + _currentInstance.dragManager.endpointDeleted(endpoint); + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every , and their associated s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc, doFireEvent) { + // may have been given a connection, or in special cases, an object + var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + argIsConnection = jpc.constructor == connType, + params = argIsConnection ? { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + } : jpc; + + if (doFireEvent) _currentInstance.fire("jsPlumbConnectionDetached", params); + _currentInstance.anchorManager.connectionDetached(params); + }; + + /** + fires an event to indicate an existing connection is being dragged. + */ + var fireConnectionDraggingEvent = function(jpc) { + _currentInstance.fire("connectionDrag", jpc); + }; + + var fireConnectionDragStopEvent = function(jpc) { + _currentInstance.fire("connectionDragStop", jpc); + } + + + /* + Function: detach + Detaches and then removes a . From 1.3.5 this method has been altered to remove support for + specifying Connections by various parameters; you can now pass in a Connection as the first argument and + an optional parameters object as a second argument. If you need the functionality this method provided + before 1.3.5 then you should use the getConnections method to get the list of Connections to detach, and + then iterate through them, calling this for each one. + + Parameters: + connection - the to detach + params - optional parameters to the detach call. valid values here are + fireEvent : defaults to false; indicates you want jsPlumb to fire a connection + detached event. The thinking behind this is that if you made a programmatic + call to detach an event, you probably don't need the callback. + forceDetach : defaults to false. allows you to override any beforeDetach listeners that may be registered. + + Returns: + true if successful, false if not. + */ + this.detach = function() { + + if (arguments.length == 0) return; + var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + firstArgIsConnection = arguments[0].constructor == connType, + params = arguments.length == 2 ? firstArgIsConnection ? (arguments[1] || {}) : arguments[0] : arguments[0], + fireEvent = (params.fireEvent !== false), + forceDetach = params.forceDetach, + connection = firstArgIsConnection ? arguments[0] : params.connection; + + if (connection) { + if (forceDetach || (connection.isDetachAllowed(connection) + && connection.endpoints[0].isDetachAllowed(connection) + && connection.endpoints[1].isDetachAllowed(connection))) { + if (forceDetach || _currentInstance.checkCondition("beforeDetach", connection)) + connection.endpoints[0].detach(connection, false, true, fireEvent); // TODO check this param iscorrect for endpoint's detach method + } + } + else { + var _p = jsPlumb.extend( {}, params); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1]), fireEvent); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source), + targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + if (_currentInstance.checkCondition("beforeDetach", jpc)) { + jpc.endpoints[0].detach(jpc, false, true, fireEvent); + } + } + }); + } + } + }; + + /* + Function: detachAllConnections + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + params - optional parameters. alowed values: + fireEvent : defaults to true, whether or not to fire the detach event. + + Returns: + void + */ + this.detachAllConnections = function(el, params) { + params = params || {}; + el = _getElementObject(el); + var id = _getAttribute(el, "id"), + endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + }; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Parameters: + params - optional params object containing: + fireEvent : whether or not to fire detach events. defaults to true. + + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function(params) { + params = params || {}; + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of y + our library's draggable method so that jsPlumb can setup the appropriate callbacks. Your + underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + // TODO it would be nice if this supported a selector string, instead of an id. + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method, + * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the + * default scope). If multiple scopes are passed in, the return value will be a map of { scope -> [ connection... ] }. + * + * Parameters + * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list + * of connections that are in the given scope. + * options - if the argument is a JS object, you can specify a finer-grained filter: + * + * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes. + * - *source* either a string representing an element id, or a selector. constrains the result to connections having this source. + * - *target* either a string representing an element id, or a selector. constrains the result to connections having this target. + * + */ + this.getConnections = function(options) { + if (!options) { + options = {}; + } else if (options.constructor == String) { + options = { "scope": options }; + } + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') + r.push(input); + else + r = input; + } + return r; + }, + scope = options.scope || _currentInstance.getDefaultScope(), + scopes = prepareList(scope), + sources = prepareList(options.source), + targets = prepareList(options.target), + filter = function(list, value) { + return list.length > 0 ? _indexOf(list, value) != -1 : true; + }, + results = scopes.length > 1 ? {} : [], + _addOne = function(scope, obj) { + if (scopes.length > 1) { + var ss = results[scope]; + if (ss == null) { + ss = []; results[scope] = ss; + } + ss.push(obj); + } else results.push(obj); + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + _addOne(i, c); + } + } + } + return results; + }; + + /* + * Function: getAllConnections + * Gets all connections, as a map of { scope -> [ connection... ] }. + */ + this.getAllConnections = function() { + return connectionsByScope; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /** + * Function:getEndpoints + * Gets the list of Endpoints for a given selector, or element id. + * @param el + * @return + */ + this.getEndpoints = function(el) { + return endpointsByElement[_getId(el)]; + }; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + this.getOffset = function(id) { + var o = offsets[id]; + return _updateOffset({elId:id}); + }; + + this.getSelector = function(spec) { + return jsPlumb.CurrentLibrary.getSelector(spec); + }; + + this.getSize = function(id) { + var s = sizes[id]; + if (!s) _updateOffset({elId:id}); + return sizes[id]; + }; + + this.appendElement = _appendElement; + + var _hoverSuspended = false; + this.isHoverSuspended = function() { return _hoverSuspended; }; + this.setHoverSuspended = function(s) { _hoverSuspended = s; }; + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + changeEndpoints - whether not to also hide endpoints on the element. by default this is false. + + Returns: + void + */ + this.hide = function(el, changeEndpoints) { + _setVisible(el, "none", changeEndpoints); + }; + + // exposed for other objects to use to get a unique id. + this.idstamp = _idstamp; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + if (!initialized) { + _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run. + + var bindOne = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && renderMode == jsPlumb.CANVAS) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + var t = c[i].connector[event](e); + if (t) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i].endpoint[event](e)) return; + } + } + } + }); + }; + bindOne("click");bindOne("dblclick");bindOne("mousemove");bindOne("mousedown");bindOne("mouseup");bindOne("contextmenu"); + + initialized = true; + _currentInstance.fire("ready"); + } + }; + + this.log = log; + this.jsPlumbUIComponent = jsPlumbUIComponent; + this.EventGenerator = EventGenerator; + + /* + * Creates an anchor with the given params. + * + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function() { + if (arguments.length == 0) return null; + var specimen = arguments[0], elementId = arguments[1], jsPlumbInstance = arguments[2], newAnchor = null; + if (!jsPlumbInstance) + throw "NO JSPLUMB SET"; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; //TODO hazy here about whether it should be added or is already added somehow. + // is it the name of an anchor type? + else if (typeof specimen == "string") { + newAnchor = jsPlumb.Anchors[arguments[0]]({elementId:elementId, jsPlumbInstance:_currentInstance}); + } + // is it an array? it will be one of: + // an array of [name, params] - this defines a single anchor + // an array of arrays - this defines some dynamic anchors + // an array of numbers - this defines a single anchor. + else if (specimen.constructor == Array) { + if (specimen[0].constructor == Array || specimen[0].constructor == String) { + if (specimen.length == 2 && specimen[0].constructor == String && specimen[1].constructor == Object) { + var pp = jsPlumb.extend({elementId:elementId, jsPlumbInstance:_currentInstance}, specimen[1]); + newAnchor = jsPlumb.Anchors[specimen[0]](pp); + } + else + newAnchor = new DynamicAnchor(specimen, null, elementId); + } + else { + var anchorParams = { + x:specimen[0], y:specimen[1], + orientation : (specimen.length >= 4) ? [ specimen[2], specimen[3] ] : [0,0], + offsets : (specimen.length == 6) ? [ specimen[4], specimen[5] ] : [ 0, 0 ], + elementId:elementId + }; + newAnchor = new Anchor(anchorParams); + newAnchor.clone = function() { return new Anchor(anchorParams); }; + } + } + + if (!newAnchor.id) newAnchor.id = "anchor_" + _idstamp(); + return newAnchor; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types, elementId, jsPlumbInstance) { + var r = []; + for ( var i = 0; i < types.length; i++) { + if (typeof types[i] == "string") + r.push(jsPlumb.Anchors[types[i]]({elementId:elementId, jsPlumbInstance:jsPlumbInstance})); + else if (types[i].constructor == Array) + r.push(_currentInstance.makeAnchor(types[i], elementId, jsPlumbInstance)); + } + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /** + * Function: makeTarget + * Makes some DOM element a Connection target, allowing you to drag connections to it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a target. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * scope optional. scope for the drop zone. + * dropOptions optional. same stuff as you would pass to dropOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to true. whether or not to delete + * any Endpoints created by a connection to this target if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + var _targetEndpointDefinitions = {}, + _targetEndpoints = {}, + _targetEndpointsUnique = {}; + var _setEndpointPaintStylesAndAnchor = function(ep, epIndex) { + ep.paintStyle = ep.paintStyle || + _currentInstance.Defaults.EndpointStyles[epIndex] || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyles[epIndex] || + jsPlumb.Defaults.EndpointStyle; + ep.hoverPaintStyle = ep.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyles[epIndex] || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyles[epIndex] || + jsPlumb.Defaults.EndpointHoverStyle; + + ep.anchor = ep.anchor || + _currentInstance.Defaults.Anchors[epIndex] || + _currentInstance.Defaults.Anchor || + jsPlumb.Defaults.Anchors[epIndex] || + jsPlumb.Defaults.Anchor; + + ep.endpoint = ep.endpoint || + _currentInstance.Defaults.Endpoints[epIndex] || + _currentInstance.Defaults.Endpoint || + jsPlumb.Defaults.Endpoints[epIndex] || + jsPlumb.Defaults.Endpoint; + }; + this.makeTarget = function(el, params, referenceParams) { + + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + _setEndpointPaintStylesAndAnchor(p, 1); + var jpcl = jsPlumb.CurrentLibrary, + targetScope = p.scope || _currentInstance.Defaults.Scope, + deleteEndpointsOnDetach = !(p.deleteEndpointsOnDetach === false), + _doOne = function(_el) { + + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el); + _targetEndpointDefinitions[elid] = p; + _targetEndpointsUnique[elid] = p.uniqueEndpoint; + + var dropOptions = jsPlumb.extend({}, p.dropOptions || {}), + _drop = function() { + _currentInstance.currentlyDragging = false; + var draggable = _getElementObject(jpcl.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + // restore the original scope if necessary (issue 57) + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id], + source = jpc.endpoints[0], + _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : {}; + + // unlock the source anchor to allow it to refresh its position if necessary + source.anchor.locked = false; + + if (scope) jpcl.setDragScope(draggable, scope); + + // check if drop is allowed here. + var _continue = jpc.isDropAllowed(jpc.sourceId, _getId(_el), jpc.scope); + + // regardless of whether the connection is ok, reconfigure the existing connection to + // point at the current info. we need this to be correct for the detach event that will follow. + // clear the source endpoint from the list to detach. we will detach this connection at this + // point, but we want to keep the source endpoint. the target is a floating endpoint and should + // be removed. TODO need to figure out whether this code can result in endpoints kicking around + // when they shouldnt be. like is this a full detach of a connection? can it be? + if (jpc.endpointsToDeleteOnDetach) { + if (source === jpc.endpointsToDeleteOnDetach[0]) + jpc.endpointsToDeleteOnDetach[0] = null; + else if (source === jpc.endpointsToDeleteOnDetach[1]) + jpc.endpointsToDeleteOnDetach[1] = null; + } + // reinstate any suspended endpoint; this just puts the connection back into + // a state in which it will report sensible values if someone asks it about + // its target. we're going to throw this connection away shortly so it doesnt matter + // if we manipulate it a bit. + if (jpc.suspendedEndpoint) { + jpc.targetId = jpc.suspendedEndpoint.elementId; + jpc.target = jpcl.getElementObject(jpc.suspendedEndpoint.elementId); + jpc.endpoints[1] = jpc.suspendedEndpoint; + } + + if (_continue) { + + // detach this connection from the source. + source.detach(jpc, false, true, false);//source.endpointWillMoveAfterConnection); + + // make a new Endpoint for the target + //var newEndpoint = _currentInstance.addEndpoint(_el, _endpoint); + + var newEndpoint = _targetEndpoints[elid] || _currentInstance.addEndpoint(_el, p); + if (p.uniqueEndpoint) _targetEndpoints[elid] = newEndpoint; // may of course just store what it just pulled out. that's ok. + + // if the anchor has a 'positionFinder' set, then delegate to that function to find + // out where to locate the anchor. + if (newEndpoint.anchor.positionFinder != null) { + var dropPosition = jpcl.getUIPosition(arguments), + elPosition = jpcl.getOffset(_el), + elSize = jpcl.getSize(_el), + ap = newEndpoint.anchor.positionFinder(dropPosition, elPosition, elSize, newEndpoint.anchor.constructorParams); + newEndpoint.anchor.x = ap[0]; + newEndpoint.anchor.y = ap[1]; + // now figure an orientation for it..kind of hard to know what to do actually. probably the best thing i can do is to + // support specifying an orientation in the anchor's spec. if one is not supplied then i will make the orientation + // be what will cause the most natural link to the source: it will be pointing at the source, but it needs to be + // specified in one axis only, and so how to make that choice? i think i will use whichever axis is the one in which + // the target is furthest away from the source. + } + var c = _currentInstance.connect({ + source:source, + target:newEndpoint, + scope:scope, + previousConnection:jpc, + container:jpc.parent, + deleteEndpointsOnDetach:deleteEndpointsOnDetach, + // 'endpointWillMoveAfterConnection' is set by the makeSource function, and it indicates that the + // given endpoint will actually transfer from the element it is currently attached to to some other + // element after a connection has been established. in that case, we do not want to fire the + // connection event, since it will have the wrong data in it; makeSource will do it for us. + // this is controlled by the 'parent' parameter on a makeSource call. + doNotFireConnectionEvent:source.endpointWillMoveAfterConnection + }); + if (deleteEndpointsOnDetach) + c.endpointsToDeleteOnDetach = [ source, newEndpoint ]; + + c.repaint(); + } + // if not allowed to drop... + else { + // TODO this code is identical (pretty much) to what happens when a connection + // dragged from a normal endpoint is in this situation. refactor. + // is this an existing connection, and will we reattach? + if (jpc.suspendedEndpoint) { + if (source.isReattach) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + _currentInstance.repaint(source.elementId); + } + else + source.detach(jpc, false, true, true); // otherwise, detach the connection and tell everyone about it. + } + + } + }; + + var dropEvent = jpcl.dragEvents['drop']; + dropOptions["scope"] = dropOptions["scope"] || targetScope; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop); + + jpcl.initDroppable(_el, dropOptions, true); + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements drop targets. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeTargets = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeTarget(els[i], params, referenceParams); + } + }; + + /** + * Function: makeSource + * Makes some DOM element a Connection source, allowing you to drag connections from it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a source. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * parent optional. the element to add Endpoints to when a Connection is established. if you omit this, + * Endpoints will be added to 'el'. + * scope optional. scope for the connections dragged from this element. + * dragOptions optional. same stuff as you would pass to dragOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to false. whether or not to delete + * any Endpoints created by a connection from this source if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + var _sourceEndpointDefinitions = {}, + _sourceEndpoints = {}, + _sourceEndpointsUnique = {}; + + this.makeSource = function(el, params, referenceParams) { + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + _setEndpointPaintStylesAndAnchor(p, 0); + var jpcl = jsPlumb.CurrentLibrary, + _doOne = function(_el) { + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el), + parent = p.parent, + idToRegisterAgainst = parent != null ? _currentInstance.getId(jpcl.getElementObject(parent)) : elid; + + _sourceEndpointDefinitions[idToRegisterAgainst] = p; + _sourceEndpointsUnique[idToRegisterAgainst] = p.uniqueEndpoint; + + var stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"], + dragOptions = jsPlumb.extend({ }, p.dragOptions || {}), + existingDrag = dragOptions.drag, + existingStop = dragOptions.stop, + ep = null, + endpointAddedButNoDragYet = false; + + // set scope if its not set in dragOptions but was passed in in params + dragOptions["scope"] = dragOptions["scope"] || p.scope; + + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + if (existingDrag) existingDrag.apply(this, arguments); + endpointAddedButNoDragYet = false; + }); + + dragOptions[stopEvent] = function() { + if (existingStop) existingStop.apply(this, arguments); + + //_currentlyDown = false; + _currentInstance.currentlyDragging = false; + + if (ep.connections.length == 0) + _currentInstance.deleteEndpoint(ep); + else { + + jpcl.unbind(ep.canvas, "mousedown"); + + // reset the anchor to the anchor that was initially provided. the one we were using to drag + // the connection was just a placeholder that was located at the place the user pressed the + // mouse button to initiate the drag. + var anchorDef = p.anchor || _currentInstance.Defaults.Anchor, + oldAnchor = ep.anchor; + + ep.anchor = _currentInstance.makeAnchor(anchorDef, elid, _currentInstance); + + + if (p.parent) { + var parent = jpcl.getElementObject(p.parent); + if (parent) { + var currentId = ep.elementId; + ep.setElement(parent); + ep.endpointWillMoveAfterConnection = false; + _currentInstance.anchorManager.rehomeEndpoint(currentId, parent); + ep.connections[0].previousConnection = null; + _currentInstance.anchorManager.connectionDetached({ + sourceId:ep.connections[0].sourceId, + targetId:ep.connections[0].targetId, + connection:ep.connections[0] + }); + _finaliseConnection(ep.connections[0]); + } + } + + ep.repaint(); + _currentInstance.repaint(ep.elementId); + _currentInstance.repaint(ep.connections[0].targetId); + + } + }; + // when the user presses the mouse, add an Endpoint + var mouseDownListener = function(e) { + // make sure we have the latest offset for this div + var myOffsetInfo = _updateOffset({elId:elid}); + + var x = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width, + y = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height, + parentX = x, + parentY = y; + + + // if there is a parent, the endpoint will actually be added to it now, rather than the div + // that was the source. in that case, we have to adjust the anchor position so it refers to + // the parent. + if (p.parent) { + var pEl = jsPlumb.CurrentLibrary.getElementObject(p.parent), + pId = _getId(pEl); + myOffsetInfo = _updateOffset({elId:pId}); + parentX = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width, + parentY = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height; + } + + // we need to override the anchor in here, and force 'isSource', but we don't want to mess with + // the params passed in, because after a connection is established we're going to reset the endpoint + // to have the anchor we were given. + var tempEndpointParams = {}; + jsPlumb.extend(tempEndpointParams, p); + tempEndpointParams.isSource = true; + tempEndpointParams.anchor = [x,y,0,0]; + tempEndpointParams.parentAnchor = [ parentX, parentY, 0, 0 ]; + tempEndpointParams.dragOptions = dragOptions; + // if a parent was given we need to turn that into a "container" argument. this is, by default, + // the parent of the element we will move to, so parent of p.parent in this case. however, if + // the user has specified a 'container' on the endpoint definition or on + // the defaults, we should use that. + if (p.parent) { + var potentialParent = tempEndpointParams.container || _currentInstance.Defaults.Container; + if (potentialParent) + tempEndpointParams.container = potentialParent; + else + tempEndpointParams.container = jsPlumb.CurrentLibrary.getParent(p.parent); + } + + ep = _currentInstance.addEndpoint(elid, tempEndpointParams); + + endpointAddedButNoDragYet = true; + // we set this to prevent connections from firing attach events before this function has had a chance + // to move the endpoint. + ep.endpointWillMoveAfterConnection = p.parent != null; + ep.endpointWillMoveTo = p.parent ? jpcl.getElementObject(p.parent) : null; + + var _delTempEndpoint = function() { + // this mouseup event is fired only if no dragging occurred, by jquery and yui, but for mootools + // it is fired even if dragging has occurred, in which case we would blow away a perfectly + // legitimate endpoint, were it not for this check. the flag is set after adding an + // endpoint and cleared in a drag listener we set in the dragOptions above. + if(endpointAddedButNoDragYet) { + _currentInstance.deleteEndpoint(ep); + } + }; + + _currentInstance.registerListener(ep.canvas, "mouseup", _delTempEndpoint); + _currentInstance.registerListener(_el, "mouseup", _delTempEndpoint); + + // and then trigger its mousedown event, which will kick off a drag, which will start dragging + // a new connection from this endpoint. + jpcl.trigger(ep.canvas, "mousedown", e); + }; + + // register this on jsPlumb so that it can be cleared by a reset. + _currentInstance.registerListener(_el, "mousedown", mouseDownListener); + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements connection sources. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeSources = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeSource(els[i], params, referenceParams); + } + }; + + /* + Function: ready + Helper method to bind a function to jsPlumb's ready event. + */ + this.ready = function(fn) { + _currentInstance.bind("ready", fn); + }, + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"), + ebe = endpointsByElement[elId]; + if (ebe) { + for ( var i = 0; i < ebe.length; i++) + _currentInstance.deleteEndpoint(ebe[i]); + } + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + var _registeredListeners = {}, + _unbindRegisteredListeners = function() { + for (var i in _registeredListeners) { + for (var j = 0; j < _registeredListeners[i].length; j++) { + var info = _registeredListeners[i][j]; + jsPlumb.CurrentLibrary.unbind(info.el, info.event, info.listener); + } + } + _registeredListeners = {}; + }; + + // internal register listener method. gives us a hook to clean things up + // with if the user calls jsPlumb.reset. + this.registerListener = function(el, type, listener) { + jsPlumb.CurrentLibrary.bind(el, type, listener); + _addToList(_registeredListeners, type, {el:el, event:type, listener:listener}); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + _currentInstance.deleteEveryEndpoint(); + _currentInstance.clearListeners(); + _targetEndpointDefinitions = {}; + _targetEndpoints = {}; + _targetEndpointsUnique = {}; + _sourceEndpointDefinitions = {}; + _sourceEndpoints = {}; + _sourceEndpointsUnique = {}; + _unbindRegisteredListeners(); + _currentInstance.anchorManager.reset(); + _currentInstance.dragManager.reset(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + + Parameters: + value - whether or not to automatically repaint when the window is resized. + + Returns: void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction + * Sets the function to fire when the window size has changed and a repaint was fired. + * + * Parameters: + * f - Function to execute. + * + * Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: setSuspendDrawing + * Suspends drawing operations. This can be used when you have a lot of connections to make or endpoints to register; + * it will save you a lot of time. + */ + this.setSuspendDrawing = _setSuspendDrawing; + + /* + * Constant for use with the setRenderMode method + */ + this.CANVAS = "canvas"; + + /* + * Constant for use with the setRenderMode method + */ + this.SVG = "svg"; + + this.VML = "vml"; + + /* + * Function: setRenderMode + * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that + * what you asked for is not supported (and that VML is). If you asked for VML but the browser does + * not support it, jsPlumb uses SVG. + * + * Returns: + * the render mode that jsPlumb set, which of course may be different from that requested. + */ + this.setRenderMode = function(mode) { + if (mode) + mode = mode.toLowerCase(); + else + return; + if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); + // now test we actually have the capability to do this. + if (mode === jsPlumb.CANVAS && canvasAvailable) + renderMode = jsPlumb.CANVAS; + else if (mode === jsPlumb.SVG && svgAvailable) + renderMode = jsPlumb.SVG; + else if (vmlAvailable) + renderMode = jsPlumb.VML; + + return renderMode; + }; + + this.getRenderMode = function() { return renderMode; }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * changeEndpoints - whether or not to also change the visible state of the endpoints on the element. this also has a bearing on + * other connections on those endpoints: if their other endpoint is also visible, the connections are made visible. + * + * Returns: + * void + */ + this.show = function(el, changeEndpoints) { + _setVisible(el, "block", changeEndpoints); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + //findIndex : _findIndex, + getId : _getId, + makeAnchor:self.makeAnchor, + makeDynamicAnchor:self.makeDynamicAnchor + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * changeEndpoints - whether or not to also toggle the endpoints on the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload + * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element. + * + * Returns: + * void + */ + this.unload = function() { + // this used to do something, but it turns out that what it did was nothing. + // now it exists only for backwards compatibility. + }; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + var adjustForParentOffsetAndScroll = function(xy, el) { + + var offsetParent = null, result = xy; + if (el.tagName.toLowerCase() === "svg" && el.parentNode) { + offsetParent = el.parentNode; + } + else if (el.offsetParent) { + offsetParent = el.offsetParent; + } + if (offsetParent != null) { + var po = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(offsetParent), + so = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : {left:offsetParent.scrollLeft, top:offsetParent.scrollTop}; + + + // i thought it might be cool to do this: + // lastReturnValue[0] = lastReturnValue[0] - offsetParent.offsetLeft + offsetParent.scrollLeft; + // lastReturnValue[1] = lastReturnValue[1] - offsetParent.offsetTop + offsetParent.scrollTop; + // but i think it ignores margins. my reasoning was that it's quicker to not hand off to some underlying + // library. + + result[0] = xy[0] - po.left + so.left; + result[1] = xy[1] - po.top + so.top; + } + + return result; + + }; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + this.elementId = params.elementId; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + + if (timestamp && timestamp === self.timestamp) + return lastReturnValue; + + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + + // adjust loc if there is an offsetParent + lastReturnValue = adjustForParentOffsetAndScroll(lastReturnValue, element.canvas); + + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function(_endpoint) { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + * + * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference, + // the canvas this refers to. + refCanvas = params.referenceCanvas, + size = _getSize(_getElementObject(refCanvas)), + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + xDir = 0, yDir = 0, + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + orientation = null, + _lastResult = null; + + // set these to 0 each; they are used by certain types of connectors in the loopback case, + // when the connector is trying to clear the element it is on. but for floating anchor it's not + // very important. + this.x = 0; this.y = 0; + + this.isFloating = true; + + this.compute = function(params) { + var xy = params.xy, element = params.element, + result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + + // adjust loc if there is an offsetParent + result = adjustForParentOffsetAndScroll(result, element.canvas); + + _lastResult = result; + return result; + }; + + this.getOrientation = function(_endpoint) { + if (orientation) return orientation; + else { + var o = ref.getOrientation(_endpoint); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchor is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector, elementId) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = [], self = this, + _convert = function(anchor) { + return anchor.constructor == Anchor ? anchor: _currentInstance.makeAnchor(anchor, elementId, _currentInstance); + }; + for (var i = 0; i < anchors.length; i++) + _anchors[i] = _convert(anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + this.locked = false; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null, + _curIndex = _anchors.length > 0 ? 0 : -1, + self = this, + + // helper method to calculate the distance between the centers of the two elements. + _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }, + + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + self.x = _curAnchor.x; + self.y = _curAnchor.y; + + return _curAnchor.compute(params); + }; + + this.getCurrentLocation = function() { + return _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + }; + + this.getOrientation = function(_endpoint) { return _curAnchor != null ? _curAnchor.getOrientation(_endpoint) : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + manages anchors for all elements. + */ + // "continuous" anchors: anchors that pick their location based on how many connections the given element has. + // this requires looking at a lot more elements than normal - anything that has had a Continuous anchor applied has + // to be recalculated. so this manager is used as a reference point. the first time, with a new timestamp, that + // a continuous anchor is asked to compute, it calls this guy. or maybe, even, this guy gets called somewhere else + // and compute only ever returns pre-computed values. either way, this is the central point, and we want it to + // be called as few times as possible. + var continuousAnchors = {}, + continuousAnchorLocations = {}, + continuousAnchorOrientations = {}, + Orientation = { HORIZONTAL : "horizontal", VERTICAL : "vertical", DIAGONAL : "diagonal", IDENTITY:"identity" }, + + // TODO this functions uses a crude method of determining orientation between two elements. + // 'diagonal' should be chosen when the angle of the line between the two centers is around + // one of 45, 135, 225 and 315 degrees. maybe +- 15 degrees. + calculateOrientation = function(sourceId, targetId, sd, td) { + + if (sourceId === targetId) return { + orientation:Orientation.IDENTITY, + a:["top", "top"] + }; + + var theta = Math.atan2((td.centery - sd.centery) , (td.centerx - sd.centerx)), + theta2 = Math.atan2((sd.centery - td.centery) , (sd.centerx - td.centerx)), + h = ((sd.left <= td.left && sd.right >= td.left) || (sd.left <= td.right && sd.right >= td.right) || + (sd.left <= td.left && sd.right >= td.right) || (td.left <= sd.left && td.right >= sd.right)), + v = ((sd.top <= td.top && sd.bottom >= td.top) || (sd.top <= td.bottom && sd.bottom >= td.bottom) || + (sd.top <= td.top && sd.bottom >= td.bottom) || (td.top <= sd.top && td.bottom >= sd.bottom)); + + if (! (h || v)) { + var a = null, rls = false, rrs = false, sortValue = null; + if (td.left > sd.left && td.top > sd.top) + a = ["right", "top"]; + else if (td.left > sd.left && sd.top > td.top) + a = [ "top", "left"]; + else if (td.left < sd.left && td.top < sd.top) + a = [ "top", "right"]; + else if (td.left < sd.left && td.top > sd.top) + a = ["left", "top" ]; + + return { orientation:Orientation.DIAGONAL, a:a, theta:theta, theta2:theta2 }; + } + else if (h) return { + orientation:Orientation.HORIZONTAL, + a:sd.top < td.top ? ["bottom", "top"] : ["top", "bottom"], + theta:theta, theta2:theta2 + } + else return { + orientation:Orientation.VERTICAL, + a:sd.left < td.left ? ["right", "left"] : ["left", "right"], + theta:theta, theta2:theta2 + } + }, + placeAnchorsOnLine = function(desc, elementDimensions, elementPosition, + connections, horizontal, otherMultiplier, reverse) { + var a = [], step = elementDimensions[horizontal ? 0 : 1] / (connections.length + 1); + + for (var i = 0; i < connections.length; i++) { + var val = (i + 1) * step, other = otherMultiplier * elementDimensions[horizontal ? 1 : 0]; + if (reverse) + val = elementDimensions[horizontal ? 0 : 1] - val; + + var dx = (horizontal ? val : other), x = elementPosition[0] + dx, xp = dx / elementDimensions[0], + dy = (horizontal ? other : val), y = elementPosition[1] + dy, yp = dy / elementDimensions[1]; + + a.push([ x, y, xp, yp, connections[i][1], connections[i][2] ]); + } + + return a; + }, + standardEdgeSort = function(a, b) { return a[0] > b[0] ? 1 : -1 }, + currySort = function(reverseAngles) { + return function(a,b) { + var r = true; + if (reverseAngles) { + if (a[0][0] < b[0][0]) + r = true; + else + r = a[0][1] > b[0][1]; + } + else { + if (a[0][0] > b[0][0]) + r= true; + else + r =a[0][1] > b[0][1]; + } + return r === false ? -1 : 1; + }; + }, + leftSort = function(a,b) { + // first get adjusted values + var p1 = a[0][0] < 0 ? -Math.PI - a[0][0] : Math.PI - a[0][0], + p2 = b[0][0] < 0 ? -Math.PI - b[0][0] : Math.PI - b[0][0]; + if (p1 > p2) return 1; + else return a[0][1] > b[0][1] ? 1 : -1; + }, + edgeSortFunctions = { + "top":standardEdgeSort, + "right":currySort(true), + "bottom":currySort(true), + "left":leftSort + }, + _sortHelper = function(_array, _fn) { + return _array.sort(_fn); + }, + placeAnchors = function(elementId, _anchorLists) { + var sS = sizes[elementId], sO = offsets[elementId], + placeSomeAnchors = function(desc, elementDimensions, elementPosition, unsortedConnections, isHorizontal, otherMultiplier, orientation) { + if (unsortedConnections.length > 0) { + var sc = _sortHelper(unsortedConnections, edgeSortFunctions[desc]), // puts them in order based on the target element's pos on screen + //sc = unsortedConnections.sort(edgeSortFunctions[desc]), // puts them in order based on the target element's pos on screen + reverse = desc === "right" || desc === "top", + anchors = placeAnchorsOnLine(desc, elementDimensions, + elementPosition, sc, + isHorizontal, otherMultiplier, reverse ); + + // takes a computed anchor position and adjusts it for parent offset and scroll, then stores it. + var _setAnchorLocation = function(endpoint, anchorPos) { + var a = adjustForParentOffsetAndScroll([anchorPos[0], anchorPos[1]], endpoint.canvas); + continuousAnchorLocations[endpoint.id] = [ a[0], a[1], anchorPos[2], anchorPos[3] ]; + continuousAnchorOrientations[endpoint.id] = orientation; + }; + + for (var i = 0; i < anchors.length; i++) { + var c = anchors[i][4], weAreSource = c.endpoints[0].elementId === elementId, weAreTarget = c.endpoints[1].elementId === elementId; + if (weAreSource) + _setAnchorLocation(c.endpoints[0], anchors[i]); + else if (weAreTarget) + _setAnchorLocation(c.endpoints[1], anchors[i]); + } + + + } + }; + + placeSomeAnchors("bottom", sS, [sO.left,sO.top], _anchorLists.bottom, true, 1, [0,1]); + placeSomeAnchors("top", sS, [sO.left,sO.top], _anchorLists.top, true, 0, [0,-1]); + placeSomeAnchors("left", sS, [sO.left,sO.top], _anchorLists.left, false, 0, [-1,0]); + placeSomeAnchors("right", sS, [sO.left,sO.top], _anchorLists.right, false, 1, [1,0]); + }, + AnchorManager = function() { + var _amEndpoints = {}, + connectionsByElementId = {}, + endpointConnectionsByElementId = {}, + continuousAnchorConnectionsByElementId = {}, + self = this, + anchorLists = {}; + + this.reset = function() { + _amEndpoints = {}; + connectionsByElementId = {}; + anchorLists = {}; + }; + + this.newConnection = function(conn) { + var sourceId = conn.sourceId, targetId = conn.targetId, + ep = conn.endpoints, + doRegisterTarget = true, + registerConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + + if ((sourceId == targetId) && otherAnchor.isContinuous){ + // remove the target endpoint's canvas. we dont need it. + jsPlumb.CurrentLibrary.removeElement(ep[1].canvas); + doRegisterTarget = false; + } + _addToList(connectionsByElementId, elId, [c, otherEndpoint, otherAnchor.constructor == DynamicAnchor]); + }; + + registerConnection(0, ep[0], ep[0].anchor, targetId, conn); + if (doRegisterTarget) + registerConnection(1, ep[1], ep[1].anchor, sourceId, conn); + }; + this.connectionDetached = function(connInfo) { + var connection = connInfo.connection || connInfo; + var sourceId = connection.sourceId, + targetId = connection.targetId, + ep = connection.endpoints, + removeConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + if (otherAnchor.constructor == FloatingAnchor) { + // no-op + } + else { + _removeWithFunction(connectionsByElementId[elId], function(_c) { + return _c[0].id == c.id; + }); + } + }; + + removeConnection(1, ep[1], ep[1].anchor, sourceId, connection); + removeConnection(0, ep[0], ep[0].anchor, targetId, connection); + + // remove from anchorLists + var sEl = connection.sourceId, + tEl = connection.targetId, + sE = connection.endpoints[0].id, + tE = connection.endpoints[1].id, + _remove = function(list, eId) { + if (list) { // transient anchors dont get entries in this list. + var f = function(e) { return e[4] == eId; }; + _removeWithFunction(list["top"], f); + _removeWithFunction(list["left"], f); + _removeWithFunction(list["bottom"], f); + _removeWithFunction(list["right"], f); + } + }; + + _remove(anchorLists[sEl], sE); + _remove(anchorLists[tEl], tE); + self.redraw(sEl); + self.redraw(tEl); + }; + this.add = function(endpoint, elementId) { + _addToList(_amEndpoints, elementId, endpoint); + }; + this.getConnectionsFor = function(elementId) { + return connectionsByElementId[elementId]; + }; + this.getEndpointsFor = function(elementId) { + return _amEndpoints[elementId]; + }; + this.deleteEndpoint = function(endpoint) { + //var cIdx = _findIndex(continuousAnchorEndpoints, endpoint); + /*var cIdx = _indexOf(continuousAnchorEndpoints, endpoint); + if (cIdx > -1) + continuousAnchorEndpoints.splice(cIdx, 1); + else*/ + _removeWithFunction(_amEndpoints[endpoint.elementId], function(e) { + return e.id == endpoint.id; + }); + }; + this.clearFor = function(elementId) { + delete _amEndpoints[elementId]; + _amEndpoints[elementId] = []; + }; + // updates the given anchor list by either updating an existing anchor's info, or adding it. this function + // also removes the anchor from its previous list, if the edge it is on has changed. + // all connections found along the way (those that are connected to one of the faces this function + // operates on) are added to the connsToPaint list, as are their endpoints. in this way we know to repaint + // them wthout having to calculate anything else about them. + var _updateAnchorList = function(lists, theta, order, conn, aBoolean, otherElId, idx, reverse, edgeId, elId, connsToPaint, endpointsToPaint) { + // first try to find the exact match, but keep track of the first index of a matching element id along the way.s + var exactIdx = -1, + firstMatchingElIdx = -1, + endpoint = conn.endpoints[idx], + endpointId = endpoint.id, + oIdx = [1,0][idx], + values = [ [ theta, order ], conn, aBoolean, otherElId, endpointId ], + listToAddTo = lists[edgeId], + listToRemoveFrom = endpoint._continuousAnchorEdge ? lists[endpoint._continuousAnchorEdge] : null; + + if (listToRemoveFrom) { + var rIdx = _findWithFunction(listToRemoveFrom, function(e) { return e[4] == endpointId }); + if (rIdx != -1) { + listToRemoveFrom.splice(rIdx, 1); + // get all connections from this list + for (var i = 0; i < listToRemoveFrom.length; i++) { + _addWithFunction(connsToPaint, listToRemoveFrom[i][1], function(c) { return c.id == listToRemoveFrom[i][1].id }); + _addWithFunction(endpointsToPaint, listToRemoveFrom[i][1].endpoints[idx], function(e) { return e.id == listToRemoveFrom[i][1].endpoints[idx].id }); + } + } + } + + for (var i = 0; i < listToAddTo.length; i++) { + if (idx == 1 && listToAddTo[i][3] === otherElId && firstMatchingElIdx == -1) + firstMatchingElIdx = i; + _addWithFunction(connsToPaint, listToAddTo[i][1], function(c) { return c.id == listToAddTo[i][1].id }); + _addWithFunction(endpointsToPaint, listToAddTo[i][1].endpoints[idx], function(e) { return e.id == listToAddTo[i][1].endpoints[idx].id }); + } + if (exactIdx != -1) { + listToAddTo[exactIdx] = values; + } + else { + var insertIdx = reverse ? firstMatchingElIdx != -1 ? firstMatchingElIdx : 0 : listToAddTo.length; // of course we will get this from having looked through the array shortly. + listToAddTo.splice(insertIdx, 0, values); + } + + // store this for next time. + endpoint._continuousAnchorEdge = edgeId; + }; + this.redraw = function(elementId, ui, timestamp, offsetToUI) { + // get all the endpoints for this element + var ep = _amEndpoints[elementId] || [], + endpointConnections = connectionsByElementId[elementId] || [], + connectionsToPaint = [], + endpointsToPaint = [], + anchorsToUpdate = []; + + timestamp = timestamp || _timestamp(); + // offsetToUI are values that would have been calculated in the dragManager when registering + // an endpoint for an element that had a parent (somewhere in the hierarchy) that had been + // registered as draggable. + offsetToUI = offsetToUI || {left:0, top:0}; + if (ui) { + ui = { + left:ui.left + offsetToUI.left, + top:ui.top + offsetToUI.top + } + } + + _updateOffset( { elId : elementId, offset : ui, recalc : false, timestamp : timestamp }); + // valid for one paint cycle. + var myOffset = offsets[elementId], + myWH = sizes[elementId], + orientationCache = {}; + + // actually, first we should compute the orientation of this element to all other elements to which + // this element is connected with a continuous anchor (whether both ends of the connection have + // a continuous anchor or just one) + //for (var i = 0; i < continuousAnchorConnections.length; i++) { + for (var i = 0; i < endpointConnections.length; i++) { + var conn = endpointConnections[i][0], + sourceId = conn.sourceId, + targetId = conn.targetId, + sourceContinuous = conn.endpoints[0].anchor.isContinuous, + targetContinuous = conn.endpoints[1].anchor.isContinuous; + + if (sourceContinuous || targetContinuous) { + var oKey = sourceId + "_" + targetId, + oKey2 = targetId + "_" + sourceId, + o = orientationCache[oKey], + oIdx = conn.sourceId == elementId ? 1 : 0; + + if (sourceContinuous && !anchorLists[sourceId]) anchorLists[sourceId] = { top:[], right:[], bottom:[], left:[] }; + if (targetContinuous && !anchorLists[targetId]) anchorLists[targetId] = { top:[], right:[], bottom:[], left:[] }; + + if (elementId != targetId) _updateOffset( { elId : targetId, timestamp : timestamp }); + if (elementId != sourceId) _updateOffset( { elId : sourceId, timestamp : timestamp }); + + var td = _getCachedData(targetId), + sd = _getCachedData(sourceId); + + if (targetId == sourceId && (sourceContinuous || targetContinuous)) { + // here we may want to improve this by somehow determining the face we'd like + // to put the connector on. ideally, when drawing, the face should be calculated + // by determining which face is closest to the point at which the mouse button + // was released. for now, we're putting it on the top face. + _updateAnchorList(anchorLists[sourceId], -Math.PI / 2, 0, conn, false, targetId, 0, false, "top", sourceId, connectionsToPaint, endpointsToPaint) + } + else { + if (!o) { + o = calculateOrientation(sourceId, targetId, sd.o, td.o); + orientationCache[oKey] = o; + // this would be a performance enhancement, but the computed angles need to be clamped to + //the (-PI/2 -> PI/2) range in order for the sorting to work properly. + /* orientationCache[oKey2] = { + orientation:o.orientation, + a:[o.a[1], o.a[0]], + theta:o.theta + Math.PI, + theta2:o.theta2 + Math.PI + };*/ + } + if (sourceContinuous) _updateAnchorList(anchorLists[sourceId], o.theta, 0, conn, false, targetId, 0, false, o.a[0], sourceId, connectionsToPaint, endpointsToPaint); + if (targetContinuous) _updateAnchorList(anchorLists[targetId], o.theta2, -1, conn, true, sourceId, 1, true, o.a[1], targetId, connectionsToPaint, endpointsToPaint); + } + + if (sourceContinuous) _addWithFunction(anchorsToUpdate, sourceId, function(a) { return a === sourceId; }); + if (targetContinuous) _addWithFunction(anchorsToUpdate, targetId, function(a) { return a === targetId; }); + _addWithFunction(connectionsToPaint, conn, function(c) { return c.id == conn.id; }); + if ((sourceContinuous && oIdx == 0) || (targetContinuous && oIdx == 1)) + _addWithFunction(endpointsToPaint, conn.endpoints[oIdx], function(e) { return e.id == conn.endpoints[oIdx].id; }); + } + } + + // now place all the continuous anchors we need to; + for (var i = 0; i < anchorsToUpdate.length; i++) { + placeAnchors(anchorsToUpdate[i], anchorLists[anchorsToUpdate[i]]); + } + + // now that continuous anchors have been placed, paint all the endpoints for this element + // TODO performance: add the endpoint ids to a temp array, and then when iterating in the next + // loop, check that we didn't just paint that endpoint. we can probably shave off a few more milliseconds this way. + for (var i = 0; i < ep.length; i++) { + ep[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + // ... and any other endpoints we came across as a result of the continuous anchors. + for (var i = 0; i < endpointsToPaint.length; i++) { + endpointsToPaint[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + + + // paint all the standard and "dynamic connections", which are connections whose other anchor is + // static and therefore does need to be recomputed; we make sure that happens only one time. + for (var i = 0; i < endpointConnections.length; i++) { + var otherEndpoint = endpointConnections[i][1]; + if (otherEndpoint.anchor.constructor == DynamicAnchor) { + otherEndpoint.paint({ elementWithPrecedence:elementId }); + _addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; }); + // all the connections for the other endpoint now need to be repainted + for (var k = 0; k < otherEndpoint.connections.length; k++) { + if (otherEndpoint.connections[k] !== endpointConnections[i][0]) + _addWithFunction(connectionsToPaint, otherEndpoint.connections[k], function(c) { return c.id == otherEndpoint.connections[k].id; }); + } + } else if (otherEndpoint.anchor.constructor == Anchor) { + _addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; }); + } + } + // paint current floating connection for this element, if there is one. + var fc = floatingConnections[elementId]; + if (fc) + fc.paint({timestamp:timestamp, recalc:false, elId:elementId}); + + // paint all the connections + for (var i = 0; i < connectionsToPaint.length; i++) { + connectionsToPaint[i].paint({elId:elementId, timestamp:timestamp, recalc:false}); + } + }; + this.rehomeEndpoint = function(currentId, element) { + var eps = _amEndpoints[currentId] || [], //, + elementId = _currentInstance.getId(element); + for (var i = 0; i < eps.length; i++) { + self.add(eps[i], elementId); + } + eps.splice(0, eps.length); + }; + }; + _currentInstance.anchorManager = new AnchorManager(); + _currentInstance.continuousAnchorFactory = { + get:function(params) { + var existing = continuousAnchors[params.elementId]; + if (!existing) { + existing = { + type:"Continuous", + compute : function(params) { + return continuousAnchorLocations[params.element.id] || [0,0]; + }, + getCurrentLocation : function(endpoint) { + return continuousAnchorLocations[endpoint.id] || [0,0]; + }, + getOrientation : function(endpoint) { + return continuousAnchorOrientations[endpoint.id] || [0,0]; + }, + isDynamic : true, + isContinuous : true + }; + continuousAnchors[params.elementId] = existing; + } + return existing; + } + }; + + /** + Manages dragging for some instance of jsPlumb. + + */ + var DragManager = function() { + + var _draggables = {}, _dlist = [], _delements = {}, _elementsWithEndpoints = {}; + + /** + register some element as draggable. right now the drag init stuff is done elsewhere, and it is + possible that will continue to be the case. + */ + this.register = function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + var id = _currentInstance.getId(el), + domEl = jsPlumb.CurrentLibrary.getDOMElement(el); + if (!_draggables[id]) { + _draggables[id] = el; + _dlist.push(el); + _delements[id] = {}; + } + + // look for child elements that have endpoints and register them against this draggable. + var _oneLevel = function(p) { + var pEl = jsPlumb.CurrentLibrary.getElementObject(p), + pOff = jsPlumb.CurrentLibrary.getOffset(pEl); + + for (var i = 0; i < p.childNodes.length; i++) { + if (p.childNodes[i].nodeType != 3) { + var cEl = jsPlumb.CurrentLibrary.getElementObject(p.childNodes[i]), + cid = _currentInstance.getId(cEl); + if (_elementsWithEndpoints[cid] && _elementsWithEndpoints[cid] > 0) { + var cOff = jsPlumb.CurrentLibrary.getOffset(cEl); + _delements[id][cid] = { + id:cid, + offset:{ + left:cOff.left - pOff.left, + top:cOff.top - pOff.top + } + }; + } + } + } + }; + + _oneLevel(domEl); + }; + + /** + notification that an endpoint was added to the given el. we go up from that el's parent + node, looking for a parent that has been registered as a draggable. if we find one, we add this + el to that parent's list of elements to update on drag (if it is not there already) + */ + this.endpointAdded = function(el) { + var jpcl = jsPlumb.CurrentLibrary, b = document.body, id = _currentInstance.getId(el), c = jpcl.getDOMElement(el), + p = c.parentNode, done = p == b; + + _elementsWithEndpoints[id] = _elementsWithEndpoints[id] ? _elementsWithEndpoints[id] + 1 : 1; + + while (p != b) { + var pid = _currentInstance.getId(p); + if (_draggables[pid]) { + var idx = -1, pEl = jpcl.getElementObject(p), pLoc = jsPlumb.CurrentLibrary.getOffset(pEl); + + if (_delements[pid][id] == null) { + var cLoc = jsPlumb.CurrentLibrary.getOffset(el); + _delements[pid][id] = { + id:id, + offset:{ + left:cLoc.left - pLoc.left, + top:cLoc.top - pLoc.top + } + }; + } + break; + } + p = p.parentNode; + } + }; + + this.endpointDeleted = function(endpoint) { + if (_elementsWithEndpoints[endpoint.elementId]) { + _elementsWithEndpoints[endpoint.elementId]--; + if (_elementsWithEndpoints[endpoint.elementId] <= 0) { + for (var i in _delements) { + delete _delements[i][endpoint.elementId]; + } + } + } + }; + + this.getElementsForDraggable = function(id) { + return _delements[id]; + }; + + this.reset = function() { + _draggables = {}; + _dlist = []; + _delements = {}; + _elementsWithEndpoints = {}; + }; + + }; + _currentInstance.dragManager = new DragManager(); + + + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * container - optional id or selector instructing jsPlumb where to attach all the elements it creates for this connection. you should read the documentation for a full discussion of this. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * overlays - Optional array of Overlay definitions to appear on this Connection. + * drawEndpoints - if false, instructs jsPlumb to not draw the endpoints for this Connection. Be careful with this: it only really works when you tell jsPlumb to attach elements to the document body. Read the documentation for a full discussion of this. + */ + var Connection = function(params) { + var self = this, visible = true; + self.idPrefix = "_jsplumb_c_"; + self.defaultLabelLocation = 0.5; + self.defaultOverlayKeys = ["Overlays", "ConnectionOverlays"]; + this.parent = params.parent; + overlayCapableJsPlumbUIComponent.apply(this, arguments); + // ************** get the source and target and register the connection. ******************* + + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none"; + }; + + /** + Property: source + The source element for this Connection. + */ + this.source = _getElementObject(params.source); + /** + Property:target + The target element for this Connection. + */ + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. but + // source is not overridden if the Endpoint has declared it is not the final target of a connection; + // instead we use the source that the Endpoint declares will be the final source element. + if (params.sourceEndpoint) { + this.source = params.sourceEndpoint.endpointWillMoveTo || params.sourceEndpoint.getElement(); + } + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + + // if a new connection is the result of moving some existing connection, params.previousConnection + // will have that Connection in it. listeners for the jsPlumbConnection event can look for that + // member and take action if they need to. + self.previousConnection = params.previousConnection; + + var _cost = params.cost; + self.getCost = function() { return _cost; }; + self.setCost = function(c) { _cost = c; }; + + var _bidirectional = params.bidirectional === false ? false : true; + self.isBidirectional = function() { return _bidirectional; }; + + /* + * Property: sourceId + * Id of the source element in the connection. + */ + this.sourceId = _getAttribute(this.source, "id"); + /* + * Property: targetId + * Id of the target element in the connection. + */ + this.targetId = _getAttribute(this.target, "id"); + + /** + * implementation of abstract method in EventGenerator + * @return list of attached elements. in our case, a list of Endpoints. + */ + this.getAttachedElements = function() { + return self.endpoints; + }; + + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams, elementId) { + if (anchorParams) + return _currentInstance.makeAnchor(anchorParams, elementId, _currentInstance); + }, + prepareEndpoint = function(existing, index, params, element, elementId, connectorPaintStyle, connectorHoverPaintStyle) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] + || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint; + + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified. + if (es.fillStyle == null && connectorPaintStyle != null) + es.fillStyle = connectorPaintStyle.strokeStyle; + + // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does: + //* + if (es.outlineColor == null && connectorPaintStyle != null) + es.outlineColor = connectorPaintStyle.outlineColor; + if (es.outlineWidth == null && connectorPaintStyle != null) + es.outlineWidth = connectorPaintStyle.outlineWidth; + //*/ + + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure? + if (connectorHoverPaintStyle != null) { + if (ehs == null) ehs = {}; + if (ehs.fillStyle == null) { + ehs.fillStyle = connectorHoverPaintStyle.strokeStyle; + } + } + var a = params.anchors ? params.anchors[index] : + params.anchor ? params.anchor : + _makeAnchor(_currentInstance.Defaults.Anchors[index], elementId) || + _makeAnchor(jsPlumb.Defaults.Anchors[index], elementId) || + _makeAnchor(_currentInstance.Defaults.Anchor, elementId) || + _makeAnchor(jsPlumb.Defaults.Anchor, elementId), + u = params.uuids ? params.uuids[index] : null, + e = _newEndpoint({ + paintStyle : es, + hoverPaintStyle:ehs, + endpoint : ep, + connections : [ self ], + uuid : u, + anchor : a, + source : element, + scope : params.scope, + container:params.container, + reattach:params.reattach, + detachable:params.detachable + }); + self.endpoints[index] = e; + + + if (params.drawEndpoints === false) e.setVisible(false, true, true); + + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, + 0, + params, + self.source, + self.sourceId, + params.paintStyle, + params.hoverPaintStyle); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + + // if there were no endpoints supplied and the source element is the target element, we will reuse the source + // endpoint that was just created. + var existingTargetEndpoint = ((self.sourceId == self.targetId) && params.targetEndpoint == null) ? eS : params.targetEndpoint, + eT = prepareEndpoint(existingTargetEndpoint, + 1, + params, + self.target, + self.targetId, + params.paintStyle, + params.hoverPaintStyle); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + // if delete endpoints on detach, keep a record of just exactly which endpoints they are. + if (params.deleteEndpointsOnDetach) + self.endpointsToDeleteOnDetach = [eS, eT]; + + var _detachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.detachable === false) _detachable = false; + if(self.endpoints[0].connectionsDetachable === false) _detachable = false; + if(self.endpoints[1].connectionsDetachable === false) _detachable = false; + + // inherit connectin cost if it was set on source endpoint + if (_cost == null) _cost = self.endpoints[0].getConnectionCost(); + // inherit bidirectional flag if set no source endpoint + if (params.bidirectional == null) _bidirectional = self.endpoints[0].areConnectionsBidirectional(); + + /* + Function: isDetachable + Returns whether or not this connection can be detached from its target/source endpoint. by default this + is false; use it in conjunction with the 'reattach' parameter. + */ + this.isDetachable = function() { + return _detachable === true; + }; + + /* + Function: setDetachable + Sets whether or not this connection is detachable. + */ + this.setDetachable = function(detachable) { + _detachable = detachable === true; + }; + + // merge all the parameters objects into the connection. parameters set + // on the connection take precedence; then target endpoint params, then + // finally source endpoint params. + // TODO jsPlumb.extend could be made to take more than two args, and it would + // apply the second through nth args in order. + var _p = jsPlumb.extend({}, this.endpoints[0].getParameters()); + jsPlumb.extend(_p, this.endpoints[1].getParameters()); + jsPlumb.extend(_p, self.getParameters()); + self.setParameters(_p); + + // override setHover to pass it down to the underlying connector + var _sh = self.setHover; + + self.setHover = function() { + self.connector.setHover.apply(self.connector, arguments); + _sh.apply(self, arguments); + }; + + var _internalHover = function(state) { + if (_connectionBeingDragged == null) { + self.setHover(state, false); + } + }; + + /* + * Function: setConnector + * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method - the same + * thing that you would set as the 'connector' property on a jsPlumb.connect call. + * + * Parameters: + * connector - Connector definition + */ + this.setConnector = function(connector, doNotRepaint) { + if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent); + var connectorArgs = { + _jsPlumb:self._jsPlumb, + parent:params.parent, + cssClass:params.cssClass, + container:params.container, + tooltip:self.tooltip + }; + if (connector.constructor == String) + this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand. + else if (connector.constructor == Array) + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs)); + self.canvas = self.connector.canvas; + // binds mouse listeners to the current connector. + _bindListeners(self.connector, self, _internalHover); + if (!doNotRepaint) self.repaint(); + }; + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc) + */ + + self.setConnector(this.endpoints[0].connector || + this.endpoints[1].connector || + params.connector || + _currentInstance.Defaults.Connector || + jsPlumb.Defaults.Connector, true); + + + this.setPaintStyle(this.endpoints[0].connectorStyle || + this.endpoints[1].connectorStyle || + params.paintStyle || + _currentInstance.Defaults.PaintStyle || + jsPlumb.Defaults.PaintStyle, true); + + this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || + this.endpoints[1].connectorHoverStyle || + params.hoverPaintStyle || + _currentInstance.Defaults.HoverPaintStyle || + jsPlumb.Defaults.HoverPaintStyle, true); + + this.paintStyleInUse = this.paintStyle; + + + this.moveParent = function(newParent) { + var jpcl = jsPlumb.CurrentLibrary, curParent = jpcl.getParent(self.connector.canvas); + jpcl.removeElement(self.connector.canvas, curParent); + jpcl.appendElement(self.connector.canvas, newParent); + if (self.connector.bgCanvas) { + jpcl.removeElement(self.connector.bgCanvas, curParent); + jpcl.appendElement(self.connector.bgCanvas, newParent); + } + // this only applies for DOMOverlays + for (var i = 0; i < self.overlays.length; i++) { + if (self.overlays[i].isAppendedAtTopLevel) { + jpcl.removeElement(self.overlays[i].canvas, curParent); + jpcl.appendElement(self.overlays[i].canvas, newParent); + if (self.overlays[i].reattachListeners) + self.overlays[i].reattachListeners(self.connector); + } + } + if (self.connector.reattachListeners) // this is for SVG/VML; change an element's parent and you have to reinit its listeners. + self.connector.reattachListeners(); // the Canvas implementation doesn't have to care about this + }; + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Connection. + * + * Parameters: + * event - the event to bind. Available events on a Connection are: + * - *click* : notification that a Connection was clicked. + * - *dblclick* : notification that a Connection was double clicked. + * - *mouseenter* : notification that the mouse is over a Connection. + * - *mouseexit* : notification that the mouse exited a Connection. + * + * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Connection's paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Connection will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Connection. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId], + otherOffset = offsets[this.targetId], + otherWH = sizes[this.targetId], + initialTimestamp = _timestamp(), + anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + elementId:this.endpoints[0].elementId, + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1], + timestamp:initialTimestamp + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc, timestamp:initialTimestamp }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + elementId:this.endpoints[1].elementId, + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0], + timestamp:initialTimestamp + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc, timestamp:initialTimestamp }); + + /* + * Paints the Connection. Not exposed for public usage. + * + * Parameters: + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp, + // if the moving object is not the source we must transpose the two references. + swap = false, + tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId, + tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + + var sourceInfo = _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }), + targetInfo = _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var sE = this.endpoints[sIdx], tE = this.endpoints[tIdx], + sAnchorP = sE.anchor.getCurrentLocation(sE), + tAnchorP = tE.anchor.getCurrentLocation(tE); + + /* paint overlays*/ + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible()) maxSize = Math.max(maxSize, o.computeMaxSize(self.connector)); + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, + this.endpoints[sIdx], this.endpoints[tIdx], + this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, + self.paintStyleInUse.lineWidth, maxSize, + sourceInfo, + targetInfo); + + self.connector.paint(dim, self.paintStyleInUse); + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim); + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function(params) { + params = params || {}; + var recalc = !(params.recalc === false); + this.paint({ elId : this.sourceId, recalc : recalc, timestamp:params.timestamp }); + }; + + // just to make sure the UI gets initialised fully on all browsers. + self.repaint(); + }; + +// ENDPOINT HELPER FUNCTIONS + var _makeConnectionDragHandler = function(placeholder) { + var stopped = false; + return { + drag : function() { + if (stopped) return true; + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments), + el = placeholder.element; + if (el) { + jsPlumb.CurrentLibrary.setOffset(el, _ui); + _draw(_getElementObject(el), _ui); + } + }, + stopDrag : function() { + stopped = true; + } + }; + }; + + var _makeFloatingEndpoint = function(paintStyle, referenceAnchor, endpoint, referenceCanvas, sourceElement) { + var floatingAnchor = new FloatingAnchor( { reference : referenceAnchor, referenceCanvas : referenceCanvas }); + + //setting the scope here should not be the way to fix that mootools issue. it should be fixed by not + // adding the floating endpoint as a droppable. that makes more sense anyway! + + return _newEndpoint({ paintStyle : paintStyle, endpoint : endpoint, anchor : floatingAnchor, source : sourceElement, scope:"__floating" }); + }; + + /** + * creates a placeholder div for dragging purposes, adds it to the DOM, and pre-computes its offset. then returns + * both the element id and a selector for the element. + */ + var _makeDraggablePlaceholder = function(placeholder, parent) { + var n = document.createElement("div"); + n.style.position = "absolute"; + var placeholderDragElement = _getElementObject(n); + _appendElement(n, parent); + var id = _getId(placeholderDragElement); + _updateOffset( { elId : id }); + // create and assign an id, and initialize the offset. + placeholder.id = id; + placeholder.element = placeholderDragElement; + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1 + * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't + * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you + * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or + * 'target' Endpoints for Connections. + * + * + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * enabled - optional, defaults to true. Indicates whether or not the Endpoint should be enabled for mouse events (drag/drop). + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * container - optional id or selector instructing jsPlumb where to attach the element it creates for this endpoint. you should read the documentation for a full discussion of this. + * connections - optional list of Connections to configure the Endpoint with. + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + var self = this; + self.idPrefix = "_jsplumb_e_"; + self.defaultLabelLocation = [ 0.5, 0.5 ]; + self.defaultOverlayKeys = ["Overlays", "EndpointOverlays"]; + this.parent = params.parent; + overlayCapableJsPlumbUIComponent.apply(this, arguments); + params = params || {}; + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Endpoint. + * + * Parameters: + * event - the event to bind. Available events on an Endpoint are: + * - *click* : notification that a Endpoint was clicked. + * - *dblclick* : notification that a Endpoint was double clicked. + * - *mouseenter* : notification that the mouse is over a Endpoint. + * - *mouseexit* : notification that the mouse exited a Endpoint. + * + * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Endpoint's paint style and then repaints the Endpoint. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Endpoint will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Endpoint. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + var visible = true, enabled = !(params.enabled === false); + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + + /* + Function: isEnabled + Returns whether or not the Endpoint is enabled for drag/drop connections. + */ + this.isEnabled = function() { return enabled; }; + + /* + Function: setEnabled + Sets whether or not the Endpoint is enabled for drag/drop connections. + */ + this.setEnabled = function(e) { enabled = e; }; + + var _element = params.source, _uuid = params.uuid, floatingEndpoint = null, inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + this.element = _element; + + var _connectionCost = params.connectionCost; + this.getConnectionCost = function() { return _connectionCost; }; + this.setConnectionCost = function(c) { + _connectionCost = c; + }; + + var _connectionsBidirectional = params.connectionsBidirectional === false ? false : true; + this.areConnectionsBidirectional = function() { return _connectionsBidirectional; }; + this.setConnectionsBidirectional = function(b) { _connectionsBidirectional = b; }; + + self.anchor = params.anchor ? _currentInstance.makeAnchor(params.anchor, _elementId, _currentInstance) : params.anchors ? _currentInstance.makeAnchor(params.anchors, _elementId, _currentInstance) : _currentInstance.makeAnchor("TopCenter", _elementId, _currentInstance); + + // ANCHOR MANAGER + if (!params._transient) // in place copies, for example, are transient. they will never need to be retrieved during a paint cycle, because they dont move, and then they are deleted. + _currentInstance.anchorManager.add(self, _elementId); + + var _endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot", + endpointArgs = { + _jsPlumb:self._jsPlumb, + parent:params.parent, + container:params.container, + tooltip:params.tooltip, + connectorTooltip:params.connectorTooltip, + endpoint:self + }; + if (_endpoint.constructor == String) + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint](endpointArgs); + else if (_endpoint.constructor == Array) { + endpointArgs = jsPlumb.extend(_endpoint[1], endpointArgs); + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint[0]](endpointArgs); + } + else { + _endpoint = _endpoint.clone(); + } + + // assign a clone function using a copy of endpointArgs. this is used when a drag starts: the endpoint that was dragged is cloned, + // and the clone is left in its place while the original one goes off on a magical journey. + // the copy is to get around a closure problem, in which endpointArgs ends up getting shared by + // the whole world. + var argsForClone = jsPlumb.extend({}, endpointArgs); + _endpoint.clone = function() { + var o = new Object(); + _endpoint.constructor.apply(o, [argsForClone]); + return o; + }; + + self.endpoint = _endpoint; + self.type = self.endpoint.type; + // override setHover to pass it down to the underlying endpoint + var _sh = self.setHover; + self.setHover = function() { + self.endpoint.setHover.apply(self.endpoint, arguments); + _sh.apply(self, arguments); + }; + // endpoint delegates to first connection for hover, if there is one. + var internalHover = function(state) { + if (self.connections.length > 0) + self.connections[0].setHover(state, false); + else + self.setHover(state); + }; + + // bind listeners from endpoint to self, with the internal hover function defined above. + _bindListeners(self.endpoint, self, internalHover); + + this.setPaintStyle(params.paintStyle || + params.style || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyle, true); + this.setHoverPaintStyle(params.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyle, true); + this.paintStyleInUse = this.paintStyle; + this.connectorStyle = params.connectorStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.connectorTooltip = params.connectorTooltip; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + + var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + /* + * Property: canvas + * The Endpoint's Canvas. + */ + this.canvas = this.endpoint.canvas; + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + this.connections = params.connections || []; + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + self.isReattach = params.reattach || false; + self.connectionsDetachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.connectionsDetachable === false || params.detachable === false) + self.connectionsDetachable = false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget, forceDetach, fireEvent) { + var idx = _findWithFunction(self.connections, function(c) { return c.id == connection.id}), + actuallyDetached = false; + fireEvent = (fireEvent !== false); + if (idx >= 0) { + // 1. does the connection have a before detach (note this also checks jsPlumb's bound + // detach handlers; but then Endpoint's check will, too, hmm.) + if (forceDetach || connection._forceDetach || connection.isDetachable() || connection.isDetachAllowed(connection)) { + // get the target endpoint + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + // it would be nice to check with both endpoints that it is ok to detach. but + // for this we'll have to get a bit fancier: right now if you use the same beforeDetach + // interceptor for two endpoints (which is kind of common, because it's part of the + // endpoint definition), then it gets fired twice. so in fact we need to loop through + // each beforeDetach and see if it returns false, at which point we exit. but if it + // returns true, we have to check the next one. however we need to track which ones + // have already been run, and not run them again. + if (forceDetach || connection._forceDetach || (self.isDetachAllowed(connection) /*&& t.isDetachAllowed(connection)*/)) { + + self.connections.splice(idx, 1); + + // this avoids a circular loop + if (!ignoreTarget) { + + t.detach(connection, true, forceDetach); + // check connection to see if we want to delete the endpoints associated with it. + // we only detach those that have just this connection; this scenario is most + // likely if we got to this bit of code because it is set by the methods that + // create their own endpoints, like .connect or .makeTarget. the user is + // not likely to have interacted with those endpoints. + if (connection.endpointsToDeleteOnDetach){ + for (var i = 0; i < connection.endpointsToDeleteOnDetach.length; i++) { + var cde = connection.endpointsToDeleteOnDetach[i]; + if (cde && cde.connections.length == 0) + _currentInstance.deleteEndpoint(cde); + } + } + } + _removeElements(connection.connector.getDisplayElements(), connection.parent); + _removeWithFunction(connectionsByScope[connection.scope], function(c) { + return c.id == connection.id; + }); + actuallyDetached = true; + var doFireEvent = (!ignoreTarget && fireEvent) + fireDetachEvent(connection, doFireEvent); + } + } + } + return actuallyDetached; + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + * + * Parameters: + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachAll = function(fireEvent) { + while (self.connections.length > 0) { + self.detach(self.connections[0], false, true, fireEvent); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachFrom = function(targetEndpoint, fireEvent) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + if (self.detach(c[i], false, true, fireEvent)) + c[i].setHover(false, false); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findWithFunction(self.connections, function(c) { return c.id == connection.id}); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: setElement + * Sets the DOM element this Endpoint is attached to. + */ + this.setElement = function(el) { + + // TODO possibly have this object take charge of moving the UI components into the appropriate + // parent. this is used only by makeSource right now, and that function takes care of + // moving the UI bits and pieces. however it would s + var parentId = _getId(el); + // remove the endpoint from the list for the current endpoint's element + _removeWithFunction(endpointsByElement[_elementId], function(e) { + return e.id == self.id; + }); + _element = _getElementObject(el); + _elementId = _getId(_element); + self.elementId = _elementId; + // need to get the new parent now + var newParentElement = _getParentFromParams({source:parentId}), + curParent = jpcl.getParent(self.canvas); + jpcl.removeElement(self.canvas, curParent); + jpcl.appendElement(self.canvas, newParentElement); + + // now move connection(s)...i would expect there to be only one but we will iterate. + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].moveParent(newParentElement); + self.connections[i].sourceId = _elementId; + self.connections[i].source = _element; + } + _addToList(endpointsByElement, parentId, self); + //_currentInstance.repaint(parentId); + + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + return _newEndpoint( { + anchor : self.anchor, + source : _element, + paintStyle : this.paintStyle, + endpoint : _endpoint, + _transient:true, + scope:self.scope + }); + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + var candidate = self.connections[0]; + if (self.isTarget && candidate) return candidate; + else { + return (self.connections.length < _maxConnections) || _maxConnections == -1 ? null : candidate; + } + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + /* + * Function: setStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to + * setStyle and deprecate it if so. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + * + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + // a helper function that tries to find a connection to the given element, and returns it if so. if elementWithPrecedence is null, + // or no connection to it is found, we return the first connection in our list. + var findConnectionToUseForDynamicAnchor = function(elementWithPrecedence) { + var idx = 0; + if (elementWithPrecedence != null) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].sourceId == elementWithPrecedence || self.connections[i].targetId == elementWithPrecedence) { + idx = i; + break; + } + } + } + + return self.connections[idx]; + }; + + /* + * Function: paint + * Paints the Endpoint, recalculating offset and anchor positions if necessary. This does NOT paint + * any of the Endpoint's connections. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + params = params || {}; + var timestamp = params.timestamp, + recalc = !(params.recalc === false); + if (!timestamp || self.timestamp !== timestamp) { + _updateOffset({ elId:_elementId, timestamp:timestamp, recalc:recalc }); + var xy = params.offset || offsets[_elementId]; + if(xy) { + var ap = params.anchorPoint,connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (recalc && self.anchor.isDynamic && self.connections.length > 0) { + var c = findConnectionToUseForDynamicAnchor(params.elementWithPrecedence), + oIdx = c.endpoints[0] == self ? 1 : 0, + oId = oIdx == 0 ? c.sourceId : c.targetId, + oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + ap = self.anchor.compute(anchorParams); + } + + var d = _endpoint.compute(ap, self.anchor.getOrientation(_endpoint), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + _endpoint.paint(d, self.paintStyleInUse, self.anchor); + self.timestamp = timestamp; + + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) self.overlayPlacements[i] = o.draw(self.endpoint, self.paintStyleInUse, d); + } + } + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var placeholderInfo = { id:null, element:null }, + jpc = null, + existingJpc = false, + existingJpcParams = null, + _dragHandler = _makeConnectionDragHandler(placeholderInfo); + + var start = function() { + // drag might have started on an endpoint that is not actually a source, but which has + // one or more connections. + jpc = self.connectorSelector(); + var _continue = true; + // if not enabled, return + if (!enabled) _continue = false; + // if no connection and we're not a source, return. + if (jpc == null && !params.isSource) _continue = false; + // otherwise if we're full and not allowed to drag, also return false. + if (params.isSource && self.isFull() && !dragAllowedWhenFull) _continue = false; + // if the connection was setup as not detachable or one of its endpoints + // was setup as connectionsDetachable = false, or Defaults.ConnectionsDetachable + // is set to false... + if (jpc != null && !jpc.isDetachable()) _continue = false; + + if (_continue === false) { + // this is for mootools and yui. returning false from this causes jquery to stop drag. + // the events are wrapped in both mootools and yui anyway, but i don't think returning + // false from the start callback would stop a drag. + if (jsPlumb.CurrentLibrary.stopDrag) jsPlumb.CurrentLibrary.stopDrag(); + _dragHandler.stopDrag(); + return false; + } + + // if we're not full but there was a connection, make it null. we'll create a new one. + if (jpc && !self.isFull() && params.isSource) jpc = null; + + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + _makeDraggablePlaceholder(placeholderInfo, self.parent); + + // set the offset of this div to be where 'inPlaceCopy' is, to start with. + // TODO merge this code with the code in both Anchor and FloatingAnchor, because it + // does the same stuff. + var ipcoel = _getElementObject(inPlaceCopy.canvas), + ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel), + po = adjustForParentOffsetAndScroll([ipco.left, ipco.top], inPlaceCopy.canvas); + jsPlumb.CurrentLibrary.setOffset(placeholderInfo.element, {left:po[0], top:po[1]}); + + // when using makeSource and a parent, we first draw the source anchor on the source element, then + // move it to the parent. note that this happens after drawing the placeholder for the + // first time. + if (self.parentAnchor) self.anchor = _currentInstance.makeAnchor(self.parentAnchor, self.elementId, _currentInstance); + + + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", placeholderInfo.id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + floatingEndpoint = _makeFloatingEndpoint(self.paintStyle, self.anchor, _endpoint, self.canvas, placeholderInfo.element); + + if (jpc == null) { + self.anchor.locked = true; + self.setHover(false, false); + // TODO the hover call above does not reset any target endpoint's hover + // states. + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : self.endpointWillMoveTo || _getElementObject(_element), // for makeSource with parent option. ensure source element is represented correctly. + target : placeholderInfo.element, + anchors : [ self.anchor, floatingEndpoint.anchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays + }); + + } else { + existingJpc = true; + jpc.connector.setHover(false, false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas), false, true); + // new anchor idx + var anchorIdx = jpc.endpoints[0].id == self.id ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas), + dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // now we want to get this endpoint's DROP scope, and set it for now: we can only be dropped on drop zones + // that have our drop scope (issue 57). + var dropScope = jsPlumb.CurrentLibrary.getDropScope(c); + jsPlumb.CurrentLibrary.setDragScope(c, dropScope); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = placeholderInfo.element; + jpc.sourceId = placeholderInfo.id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = placeholderInfo.element; + jpc.targetId = placeholderInfo.id; + } + + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.suspendedEndpoint.setHover(false); + jpc.endpoints[anchorIdx] = floatingEndpoint; + + // fire an event that informs that a connection is being dragged + fireConnectionDraggingEvent(jpc); + + } + // register it and register connection on it. + floatingConnections[placeholderInfo.id] = jpc; + floatingEndpoint.addConnection(jpc); + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, placeholderInfo.id, floatingEndpoint); + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var jpcl = jsPlumb.CurrentLibrary, + dragOptions = params.dragOptions || {}, + defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions), + startEvent = jpcl.dragEvents["start"], + stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"]; + + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + // extracted drag handler function so can be used by makeSource + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], _dragHandler.drag); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _currentInstance.currentlyDragging = false; + _removeWithFunction(endpointsByElement[placeholderInfo.id], function(e) { + return e.id == floatingEndpoint.id; + }); + _removeElements( [ placeholderInfo.element[0], floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + _currentInstance.anchorManager.clearFor(placeholderInfo.id); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (self.isReattach || jpc._forceDetach || !jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc)) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + _currentInstance.repaint(existingJpcParams[1]); + } + jpc._forceDetach = null; + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElements(jpc.connector.getDisplayElements(), self.parent); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint({recalc:false}); + jpc.setHover(false, false); + + fireConnectionDragStopEvent(jpc); + + jpc = null; + inPlaceCopy = null; + delete endpointsByElement[floatingEndpoint.elementId]; + floatingEndpoint.anchor = null; + floatingEndpoint = null; + _currentInstance.currentlyDragging = false; + + + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions, true); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas, forceInit, isTransient, endpoint) { + if ((params.isTarget || forceInit) && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop'], + overEvent = jsPlumb.CurrentLibrary.dragEvents['over'], + outEvent = jsPlumb.CurrentLibrary.dragEvents['out'], + drop = function(originalEvent) { + var draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + elId = _getAttribute(draggable, "elId"), + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id]; + + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + + // restore the original scope if necessary (issue 57) + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + var endpointEnabled = endpoint != null ? endpoint.isEnabled() : true; + + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget) && endpointEnabled) { + + var _doContinue = true; + // the second check here is for the case that the user is dropping it back + // where it came from. + if (jpc.suspendedEndpoint && jpc.suspendedEndpoint.id != self.id) { + if (!jpc.isDetachAllowed(jpc) || !jpc.endpoints[idx].isDetachAllowed(jpc) || !jpc.suspendedEndpoint.isDetachAllowed(jpc) || !_currentInstance.checkCondition("beforeDetach", jpc)) + _doContinue = false; + } + + // these have to be set before testing for beforeDrop. + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + + // now check beforeDrop. this will be available only on Endpoints that are setup to + // have a beforeDrop condition (although, secretly, under the hood all Endpoints and + // the Connection have them, because they are on jsPlumbUIComponent. shhh!), because + // it only makes sense to have it on a target endpoint. + _doContinue = _doContinue && self.isDropAllowed(jpc.sourceId, jpc.targetId, jpc.scope); + + if (_doContinue) { + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + if (!jpc.suspendedEndpoint) { + //_addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + fireDetachEvent({ + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }, true); + } + + // finalise will inform the anchor manager and also add to + // connectionsByScope if necessary. + _finaliseConnection(jpc, null, originalEvent); + } + else { + // otherwise just put it back on the endpoint it was on before the drag. + if (jpc.suspendedEndpoint) { + // self.detachFrom(jpc); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.setHover(false); + jpc._forceDetach = true; + if (idx == 0) { + jpc.source = jpc.suspendedEndpoint.element; + jpc.sourceId = jpc.suspendedEndpoint.elementId; + } else { + jpc.target = jpc.suspendedEndpoint.element; + jpc.targetId = jpc.suspendedEndpoint.elementId;; + } + jpc.suspendedEndpoint.addConnection(jpc); + + jpc.endpoints[0].repaint(); + jpc.repaint(); + _currentInstance.repaint(jpc.source.elementId); + jpc._forceDetach = false; + } + } + + jpc.floatingAnchorIndex = null; + } + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + } + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], function() { + if (self.isTarget) { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + } + } + }); + dropOptions[outEvent] = _wrap(dropOptions[outEvent], function() { + if (self.isTarget) { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + } + } + }); + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions, true, isTransient); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas), true, !(params._transient || self.anchor.isFloating), self); + + return self; + }; + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + j.init(); + return j; + }; + jsPlumb.util = { + convertStyle : function(s, ignoreAlpha) { + // TODO: jsPlumb should support a separate 'opacity' style member. + if ("transparent" === s) return s; + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + return o; + }, + gradient : function(p1, p2) { + p1 = p1.constructor == Array ? p1 : [p1.x, p1.y]; + p2 = p2.constructor == Array ? p2 : [p2.x, p2.y]; + return (p2[1] - p1[1]) / (p2[0] - p1[0]); + }, + normal : function(p1, p2) { + return -1 / jsPlumb.util.gradient(p1,p2); + }, + segment : function(p1, p2) { + p1 = p1.constructor == Array ? p1 : [p1.x, p1.y]; + p2 = p2.constructor == Array ? p2 : [p2.x, p2.y]; + if (p2[0] > p1[0]) { + return (p2[1] > p1[1]) ? 2 : 1; + } + else { + return (p2[1] > p1[1]) ? 3 : 4; + } + }, + segmentMultipliers : [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ], + inverseSegmentMultipliers : [null, [-1, -1], [-1, 1], [1, 1], [1, -1] ], + pointOnLine : function(fromPoint, toPoint, distance) { + var m = jsPlumb.util.gradient(fromPoint, toPoint), + s = jsPlumb.util.segment(fromPoint, toPoint), + segmentMultiplier = distance > 0 ? jsPlumb.util.segmentMultipliers[s] : jsPlumb.util.inverseSegmentMultipliers[s], + theta = Math.atan(m), + y = Math.abs(distance * Math.sin(theta)) * segmentMultiplier[1], + x = Math.abs(distance * Math.cos(theta)) * segmentMultiplier[0]; + return { x:fromPoint.x + x, y:fromPoint.y + y }; + }, + /** + * calculates a perpendicular to the line fromPoint->toPoint, that passes through toPoint and is 'length' long. + * @param fromPoint + * @param toPoint + * @param length + */ + perpendicularLineTo : function(fromPoint, toPoint, length) { + var m = jsPlumb.util.gradient(fromPoint, toPoint), + theta2 = Math.atan(-1 / m), + y = length / 2 * Math.sin(theta2), + x = length / 2 * Math.cos(theta2); + return [{x:toPoint.x + x, y:toPoint.y + y}, {x:toPoint.x - x, y:toPoint.y - y}]; + } + }; + + var _curryAnchor = function(x, y, ox, oy, type, fnInit) { + return function(params) { + params = params || {}; + //var a = jsPlumb.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance); + var a = params.jsPlumbInstance.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance); + a.type = type; + if (fnInit) fnInit(a, params); + return a; + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1, "TopCenter"); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1, "BottomCenter"); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0, "LeftMiddle"); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0, "RightMiddle"); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0, "Center"); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1, "TopRight"); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1, "BottomRight"); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1, "TopLeft"); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1, "BottomLeft"); + + // TODO test that this does not break with the current instance idea + jsPlumb.Defaults.DynamicAnchors = function(params) { + return params.jsPlumbInstance.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"], params.elementId, params.jsPlumbInstance); + }; + jsPlumb.Anchors["AutoDefault"] = function(params) { + var a = params.jsPlumbInstance.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors(params)); + a.type = "AutoDefault"; + return a; + }; + + jsPlumb.Anchors["Assign"] = _curryAnchor(0,0,0,0,"Assign", function(anchor, params) { + // find what to use as the "position finder". the user may have supplied a String which represents + // the id of a position finder in jsPlumb.AnchorPositionFinders, or the user may have supplied the + // position finder as a function. we find out what to use and then set it on the anchor. + var pf = params.position || "Fixed"; + anchor.positionFinder = pf.constructor == String ? params.jsPlumbInstance.AnchorPositionFinders[pf] : pf; + // always set the constructor params; the position finder might need them later (the Grid one does, + // for example) + anchor.constructorParams = params; + }); + + // Continuous anchor is just curried through to the 'get' method of the continuous anchor + // factory. + jsPlumb.Anchors["Continuous"] = function(params) { + return params.jsPlumbInstance.continuousAnchorFactory.get(params); + }; + + // these are the default anchor positions finders, which are used by the makeTarget function. supply + // a position finder argument to that function allows you to specify where the resulting anchor will + // be located + jsPlumb.AnchorPositionFinders = { + "Fixed": function(dp, ep, es, params) { + return [ (dp.left - ep.left) / es[0], (dp.top - ep.top) / es[1] ]; + }, + "Grid":function(dp, ep, es, params) { + var dx = dp.left - ep.left, dy = dp.top - ep.top, + gx = es[0] / (params.grid[0]), gy = es[1] / (params.grid[1]), + mx = Math.floor(dx / gx), my = Math.floor(dy / gy); + return [ ((mx * gx) + (gx / 2)) / es[0], ((my * gy) + (gy / 2)) / es[1] ]; + } + }; +})(); diff --git a/archive/1.3.6/jsPlumb-1.3.6-tests.js b/archive/1.3.6/jsPlumb-1.3.6-tests.js new file mode 100644 index 000000000..2c81d91f6 --- /dev/null +++ b/archive/1.3.6/jsPlumb-1.3.6-tests.js @@ -0,0 +1,3067 @@ +// _jsPlumb qunit tests. + +QUnit.config.reorder = false; + +var _getContextNode = function() { + return $("#container"); +}; + +var assertContextExists = function() { + ok(_getContextNode().length == 1, "context node exists"); +}; + +var assertContextSize = function(elementCount) { + //equals(_getContextNode().children().length - _divs.length, elementCount, 'context has ' + elementCount + ' children'); +}; + +var assertContextEmpty = function() { + equals(_getContextNode().children.length, 0, "context empty"); +}; + +var assertEndpointCount = function(elId, count, _jsPlumb) { + equals(_jsPlumb.getTestHarness().endpointCount(elId), count, elId + " has " + count + (count > 1) ? "endpoints" : "endpoint"); +}; + +var assertConnectionCount = function(endpoint, count) { + equals(endpoint.connections.length, count, "endpoint has " + count + " connections"); +}; + +var assertConnectionByScopeCount = function(scope, count, _jsPlumb) { + equals(_jsPlumb.getTestHarness().connectionCount(scope), count, 'Scope ' + scope + " has " + count + (count > 1) ? "connections" : "connection"); + +}; + +var _divs = []; +var _addDiv = function(id, parent) { + var d1 = document.createElement("div"); + if (parent) parent.append(d1); else _getContextNode().append(d1); + d1 = jsPlumb.CurrentLibrary.getElementObject(d1); + jsPlumb.CurrentLibrary.setAttribute(d1, "id", id); + _divs.push(id); + return d1; +}; + +var defaults = null, + _cleanup = function(_jsPlumb) { + + _jsPlumb.reset(); + _jsPlumb.Defaults = defaults; + + for (var i in _divs) { + $("#" + _divs[i]).remove(); + } + _divs.splice(0, _divs.length - 1); + + $("#container").empty(); +}; + +var testSuite = function(renderMode, _jsPlumb) { + + + module("jsPlumb", { + teardown: function() { + _cleanup(_jsPlumb); + }, + setup:function() { + defaults = jsPlumb.extend({}, _jsPlumb.Defaults); + } + }); + + // setup the container + var container = document.createElement("div"); + container.id = "container"; + document.body.appendChild(container); + + var jpcl = jsPlumb.CurrentLibrary; + + _jsPlumb.setRenderMode(renderMode); + + test(renderMode + " : getElementObject", function() { + var e = document.createElement("div"); + e.id = "FOO"; + document.body.appendChild(e); + var el = jpcl.getElementObject(e); + equals(jpcl.getAttribute(el, "id"), "FOO"); + }); + + test(renderMode + " : getDOMElement", function() { + var e = document.createElement("div"); + e.id = "FOO"; + document.body.appendChild(e); + var el = jpcl.getElementObject(e); + var e2 = jpcl.getDOMElement(el); + equals(e2.id, "FOO"); + }); + + test(renderMode + ': _jsPlumb setup', function() { + ok(_jsPlumb, "loaded"); + }); + + test(renderMode + ': getId', function() { + var d10 = _addDiv('d10'); + equals(_jsPlumb.getTestHarness().getId(d10), "d10"); + }); + + test(renderMode + ': create a simple endpoint', function() { + var d1 = _addDiv("d1"); + var e = _jsPlumb.addEndpoint($("#d1"), {}); + ok(e, 'endpoint exists'); + assertEndpointCount("d1", 1, _jsPlumb); + ok(e.id != null, "endpoint has had an id assigned"); + }); + + test(renderMode + ': create and remove a simple endpoint', function() { + var d1 = _addDiv("d1"); + var ee = _jsPlumb.addEndpoint(d1, {uuid:"78978597593"}); + ok(ee != null, "endpoint exists"); + var e = _jsPlumb.getEndpoint("78978597593"); + ok(e != null, "the endpoint could be retrieved by UUID"); + ok(e.id != null, "the endpoint has had an id assigned to it"); + assertEndpointCount("d1", 1, _jsPlumb); + assertContextSize(1); // one Endpoint canvas. + _jsPlumb.removeEndpoint(d1, ee); + assertEndpointCount("d1", 0, _jsPlumb); + e = _jsPlumb.getEndpoint("78978597593"); + equals(e, null, "the endpoint has been deleted"); + assertContextSize(0); // no Endpoint canvases. + }); + + test(renderMode + ': create two simple endpoints, registered using a selector', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + d1.addClass("window");d2.addClass("window"); + var endpoints = _jsPlumb.addEndpoint($(".window"), {}); + equals(endpoints.length, 2, "endpoint added to both windows"); + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + }); + + test(renderMode + ': create two simple endpoints, registered using an array of element ids', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + d1.addClass("window");d2.addClass("window"); + var endpoints = _jsPlumb.addEndpoint(["d1", "d2"], {}); + equals(endpoints.length, 2, "endpoint added to both windows"); + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + }); + + test(renderMode + ': draggable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1"); + var e = _jsPlumb.addEndpoint(d1, {isSource:true}); + ok(e, 'endpoint exists'); + }); + + test(renderMode + ': droppable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1") + var e = _jsPlumb.addEndpoint(d1, {isTarget:true}); + ok(e, 'endpoint exists'); + }); + + test(renderMode + ': defaultEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = _jsPlumb.addEndpoint(d3, {isSource:true}); + ok(e3.anchor, 'endpoint 3 has an anchor'); + var e4 = _jsPlumb.addEndpoint(d4, {isSource:true}); + assertEndpointCount("d3", 1, _jsPlumb); + assertEndpointCount("d4", 1, _jsPlumb); + assertContextSize(2); // one canvas for each of our two endpoints. + ok(!e3.isFull(), "endpoint 3 is not full."); + _jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // we have one connection + _jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // should have refused the connection; default max is 1. + assertContextSize(3); // now we have one more canvas, for the Connection that was accepted. + }); + + test(renderMode + ': specifiedEndpointMaxConnections', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = _jsPlumb.addEndpoint(d5, {isSource:true, maxConnections:3}); + ok(e5.anchor, 'endpoint 5 has an anchor'); + var e6 = _jsPlumb.addEndpoint(d6, {isSource:true, maxConnections:2}); // this one has max TWO + assertEndpointCount("d5", 1, _jsPlumb); assertEndpointCount("d6", 1, _jsPlumb); + assertContextSize(2); + ok(!e5.isFull(), "endpoint 5 is not full."); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 1); // we have one connection + assertContextSize(3); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // two connections + assertContextSize(4); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // should have refused; max is 2, for d4. + assertContextSize(4); + }); + + test(renderMode + ': noEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = _jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + var e4 = _jsPlumb.addEndpoint(d4, {isSource:true, maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertContextSize(3); + assertConnectionCount(e3, 1); // we have one connection + _jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 2); // we have two. etc (default was one. this proves max is working). + assertContextSize(4); + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = _jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e4}); + assertConnectionCount(e4, 3); + assertContextSize(6); + }); + + test(renderMode + ': anchors equal', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + ok(a1.equals(a2), "anchors are the same"); + }); + + test(renderMode + ': anchors equal with offsets', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb); + ok(a1.equals(a2), "anchors are the same"); + }); + + test(renderMode + ': anchors not equal', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 0, 1], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + ok(!a1.equals(a2), "anchors are different"); + }); + + test(renderMode + ': anchor not equal with offsets', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + ok(!a1.equals(a2), "anchors are different"); + }); + + test(renderMode + ': detach does not fail when no arguments are provided', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + _jsPlumb.connect({source:d3, target:d4}); + _jsPlumb.detach(); + }); + + // test that detach does not fire an event by default + test(renderMode + ': _jsPlumb.detach should fire detach event by default', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach(conn); + equals(eventCount, 1); + }); + + // test that detach does not fire an event by default + test(renderMode + ': _jsPlumb.detach should fire detach event by default, using params object', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({connection:conn}); + equals(eventCount, 1); + }); + + // test that detach fires an event when instructed to do so + test(renderMode + ': _jsPlumb.detach should not fire detach event when instructed to not do so', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach(conn, {fireEvent:false}); + equals(eventCount, 0); + }); + + // issue 81 + test(renderMode + ': _jsPlumb.detach should fire only one detach event (pass Connection as argument)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach(conn); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': _jsPlumb.detach should fire only one detach event (pass Connection as param in argument)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({connection:conn}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': detach should fire only one detach event (pass source and targets as strings as arguments in params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({source:"d5", target:"d6"}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': detach should fire only one detach event (pass source and targets as divs as arguments in params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({source:d5, target:d6, fireEvent:true}); + equals(eventCount, 1); + }); + + //TODO make sure you run this test with a single detach call, to ensure that + // single detach calls result in the connection being removed. detachEveryConnection can + // just blow away the connectionsByScope array and recreate it. + test(renderMode + ': getConnections (simple case, default scope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + _jsPlumb.connect({source:d5, target:d6}); + assertContextSize(3); + var c = _jsPlumb.getConnections(); // will get all connections in the default scope. + equals(c.length, 1, "there is one connection"); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by element id using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + _jsPlumb.connect({source:d5, target:d6}); + _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach({source:'d5', target:'d6'}); + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by id using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + _jsPlumb.connect({source:d5, target:d6}); + _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach({source:"d5", target:"d6"}); + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by element object using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + _jsPlumb.connect({source:d5, target:d6}); + _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach({source:d5, target:d6}); + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by Connection)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + var c56 = _jsPlumb.connect({source:d5, target:d6}); + var c67 = _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach(c56); + assertContextSize(5); // check that the connection canvas was removed. + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + }); + +// beforeDetach functionality + + test(renderMode + ": detach; beforeDetach on connect call returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { return false; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": detach; beforeDetach on connect call returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { return true; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": detach; beforeDetach on connect call throws an exception; we treat it with the contempt it deserves and pretend it said the detach was ok.", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { throw "i am an example of badly coded beforeDetach, but i don't break jsPlumb "; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": detach; beforeDetach on addEndpoint call to source Endpoint returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return false; } }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": detach; beforeDetach on addEndpoint call to source Endpoint returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return true; } }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + + test(renderMode + ": Endpoint.detach; beforeDetach on addEndpoint call to source Endpoint returns false; Endpoint.detach returns false too (the UI needs it to)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return false; } }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + var success = e1.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + ok(!success, "Endpoint reported detach did not execute"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns false but detach is forced", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c, {forceDetach:true}); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was denied but forced anyway"); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was denied but forced anyway"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return true; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach bound to _jsPlumb returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + _jsPlumb.bind("beforeDetach", function(connection) { + ok(connection.sourceId === "d1", "connection is provided and configured with correct source"); + ok(connection.targetId === "d2", "connection is provided and configured with correct target"); + return false; + }); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach bound to _jsPlumb returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + _jsPlumb.bind("beforeDetach", function(connection) { + ok(connection.sourceId === "d1", "connection is provided and configured with correct source"); + ok(connection.targetId === "d2", "connection is provided and configured with correct target"); + return true; + }); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was denied"); + }); + + test(renderMode + ": _jsPlumb.detachAllConnections ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detachAllConnections(d1); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachAll was called"); + }); + + test(renderMode + ": _jsPlumb.detachEveryConnection ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detachEveryConnection(); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachEveryConnection was called"); + }); + + test(renderMode + ": Endpoint.detachAll ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + e1.detachAll(); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachAllConnections was called"); + }); + +// ******** end of beforeDetach tests ************** + +// detachEveryConnection/detachAllConnections fireEvent overrides tests + + test(renderMode + ": _jsPlumb.detachEveryConnection fires events", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0; + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachEveryConnection(); + equals(connCount, 0, "no connections registered"); + }); + + test(renderMode + ": _jsPlumb.detachEveryConnection doesn't fire events when instructed not to", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0; + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachEveryConnection({fireEvent:false}); + equals(connCount, 2, "two connections registered"); + }); + + test(renderMode + ": _jsPlumb.detachAllConnections fires events", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0, + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachAllConnections("d1"); + equals(connCount, 0, "no connections registered"); + }); + + test(renderMode + ": _jsPlumb.detachAllConnections doesn't fire events when instructed not to", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0, + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachAllConnections("d1", {fireEvent:false}); + equals(connCount, 2, "two connections registered"); + }); + + test(renderMode + ': getConnections (scope testScope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + _jsPlumb.connect({source:d5, target:d6, scope:'testScope'}); + var c = _jsPlumb.getConnections("testScope"); // will get all connections in testScope + equals(c.length, 1, "there is one connection"); + equals(c[0].sourceId, 'd5', "the connection's source is d5"); + equals(c[0].targetId, 'd6', "the connection's source is d6"); + c = _jsPlumb.getConnections(); // will get all connections in default scope; should be none. + equals(c.length, 0, "there are no connections in the default scope"); + }); + + test(renderMode + ': _jsPlumb.getAllConnections (filtered by scope)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + _jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + _jsPlumb.connect({source:d9, target:d10}); // default scope + var c = _jsPlumb.getAllConnections(); // will get all connections + equals(c[_jsPlumb.getDefaultScope()].length, 1); + equals(c['testScope'].length, 1); + // now supply a list of scopes + c = _jsPlumb.getConnections(); + equals(c.length, 1); + c = _jsPlumb.getConnections("testScope"); + equals(c.length, 1, "there is one connection in 'testScope'"); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by scope and sourceId)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + _jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + _jsPlumb.connect({source:d9, target:d8, scope:'testScope'}); + _jsPlumb.connect({source:d9, target:d10}); // default scope + var c = _jsPlumb.getConnections({scope:'testScope', source:'d8'}); // will get all connections with sourceId 'd8' + equals(c.length, 1, "there is one connection in 'testScope' from d8"); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by scope, source id and target id)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope'}); + var c = _jsPlumb.getConnections({scope:'testScope', source:'d11', target:'d13'}); + equals(c.length, 1, "there is one connection from d11 to d13"); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = _jsPlumb.getConnections({scope:['testScope','testScope3']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes and source ids)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = _jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes, source ids and target ids)', function() { + _jsPlumb.detachEveryConnection(); + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'), d14=_addDiv("d14"), d15=_addDiv("d15"); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d11, target:d14, scope:'testScope'}); + _jsPlumb.connect({source:d11, target:d15, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + assertContextSize(18); + var c = _jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11'], target:['d14', 'd15']}); + equals(c['testScope'].length, 2, 'there are two connections in testScope'); + equals(c['testScope3'], null, 'there are no connections in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + var anEntry = c['testScope'][0]; + ok(anEntry.endpoints[0] != null, "Source endpoint is set"); + ok(anEntry.endpoints[1] != null, "Target endpoint is set"); + equals(anEntry.source.attr("id"), "d11", "Source is div d11"); + equals(anEntry.target.attr("id"), "d14", "Target is div d14"); + }); + + test(renderMode + ': getEndpoints, one Endpoint added by addEndpoint, get Endpoints by selector', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + _jsPlumb.addEndpoint(d1); + var e = _jsPlumb.getEndpoints(d1); + equals(e.length, 1, "there is one endpoint for element d1"); + }); + + test(renderMode + ': getEndpoints, one Endpoint added by addEndpoint, get Endpoints by id', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + _jsPlumb.addEndpoint(d1); + var e = _jsPlumb.getEndpoints("d1"); + equals(e.length, 1, "there is one endpoint for element d1"); + }); + + test(renderMode + ': connection event listeners', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null/*, returnedParams2 = null*/; + _jsPlumb.bind("jsPlumbConnection", function(params) { + returnedParams = $.extend({}, params); + }); + var c = _jsPlumb.connect({source:d1, target:d2}); + ok(returnedParams != null, "new connection listener event was fired"); + ok(returnedParams.connection != null, 'connection is set'); + equals(returnedParams.sourceId, "d1", 'sourceid is set'); + equals(returnedParams.targetId, "d2", 'targetid is set'); + equals(returnedParams.source.attr("id"), "d1", 'source is set'); + equals(returnedParams.target.attr("id"), "d2" , 'target is set'); + ok(returnedParams.sourceEndpoint != null, "source endpoint is not null"); + ok(returnedParams.targetEndpoint != null, "target endpoint is not null"); + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.detach(c); + ok(returnedParams.connection != null, 'connection is set'); + //_jsPlumb.detachAll("d1"); + //ok(returnedParams2 != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (detach by connection)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + ok(returnedParams.connection != null, "removed connection listener event passed in connection"); + }); + + test(renderMode + ': detach event listeners (detach by element ids)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach({source:"d1",target:"d2"}); + ok(returnedParams != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (detach by elements)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach({source:d1,target:d2}); + ok(returnedParams != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detach method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detachFrom method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachFrom(e2); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detachAll method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachAll(); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via _jsPlumb.deleteEndpoint method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + _jsPlumb.deleteEndpoint(e1); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(1); + }); + + test(renderMode + ': detach event listeners (ensure cleared by _jsPlumb.reset)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach({source:d1,target:d2, fireEvent:true}); + ok(returnedParams != null, "removed connection listener event was fired"); + returnedParams = null; + + _jsPlumb.reset(); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach(d1,d2); + ok(returnedParams == null, "connection listener was cleared by _jsPlumb.reset()"); + }); + + test(renderMode + ': connection events that throw errors', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null, returnedParams2 = null; + _jsPlumb.bind("jsPlumbConnection", function(params) { + returnedParams = $.extend({}, params); + throw "oh no!"; + }); + _jsPlumb.connect({source:d1, target:d2}); + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + _jsPlumb.connect({source:d3, target:d4}); + ok(returnedParams != null, "new connection listener event was fired; we threw an error, _jsPlumb survived."); + }); + + test(renderMode + ": Endpoint.detachFrom", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + e16.detachFrom(e17); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by _jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": Endpoint.detachFromConnection", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + e16.detachFromConnection(conn); + assertContextSize(3); // all canvases should remain; the connection was not removed. + // but endpoint e16 should have no connections now. + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": Endpoint.detachAll", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + var e16 = _jsPlumb.addEndpoint($("#d16"), {isSource:true,maxConnections:-1}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint($("#d17"), {isSource:true}); + var e18 = _jsPlumb.addEndpoint($("#d18"), {isSource:true}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e18}); + assertConnectionCount(e16, 2); + assertConnectionCount(e17, 1); + assertConnectionCount(e18, 1); + assertContextSize(5); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 2, _jsPlumb); + e16.detachAll(); + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": Endpoint.detach", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + e16.detach(conn); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by _jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": Endpoint.isConnectedTo", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.isConnectedTo(e17), true, "e16 and e17 are connected"); + }); + + test(renderMode + ": setting endpoint uuid", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + equals(e16.getUuid(), uuid, "endpoint's uuid was set correctly"); + }); + + test(renderMode + ": _jsPlumb.getEndpoint (by uuid)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by uuid, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + _jsPlumb.deleteEndpoint(uuid); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + var ebe = _jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by uuid, connections too)", function() { + // create two endpoints (one with a uuid), add them to two divs and then connect them. + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + // check that the connection was ok. + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + // delete the endpoint that has a uuid. verify that the endpoint cannot be retrieved and that the connection has been removed, but that + // element d17 still has its Endpoint. + _jsPlumb.deleteEndpoint(uuid); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + var ebe = _jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + ebe = _jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 1, "element d17 still has its Endpoint"); + assertContextSize(1); + + // now delete d17's endpoint and check that it has gone. + _jsPlumb.deleteEndpoint(e17); + f = _jsPlumb.getEndpoint(e17); + equals(f, null, "endpoint has been deleted"); + ebe = _jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 0, "element d17 no longer has any Endpoints"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by reference, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + _jsPlumb.deleteEndpoint(e16); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by reference, connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + _jsPlumb.deleteEndpoint(e16); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + assertContextSize(1); + }); + + test(renderMode + ": _jsPlumb.deleteEveryEndpoint", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + assertContextSize(2); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + _jsPlumb.deleteEveryEndpoint(); + + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = _jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEveryEndpoint (connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + _jsPlumb.deleteEveryEndpoint(); + + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = _jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); // no canvases. so all connection canvases have been cleaned up too. + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchor:[0,0.5,0,-1]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:"TopCenter"}); + assertContextSize(2); + equals(e16.anchor.x, 0); + equals(e16.anchor.y, 0.5); + equals(e17.anchor.x, 0.5); + equals(e17.anchor.y, 0); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (tooltip)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {tooltip:"FOO"}), + e17 = _jsPlumb.addEndpoint(d17, {tooltip:"BAZ"}); + assertContextSize(2); + equals(e16.canvas.getAttribute("title"), "FOO"); + equals(e17.canvas.getAttribute("title"), "BAZ"); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case, dynamic anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + assertContextSize(2); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case, two arg method)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchor:[0,0.5,0,-1]}); + var e17 = _jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchor:"TopCenter"}); + assertContextSize(2); + equals(e16.anchor.x, 0); + equals(e16.anchor.y, 0.5); + equals(false, e16.isTarget); + equals(true, e16.isSource); + equals(e17.anchor.x, 0.5); + equals(e17.anchor.y, 0); + equals(true, e17.isTarget); + equals(false, e17.isSource); + }); + + + test(renderMode + ": _jsPlumb.addEndpoints (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false, anchor:[0,0.5,0,-1] }, { isTarget:true, isSource:false, anchor:"TopCenter" }]); + assertContextSize(2); + equals(e16[0].anchor.x, 0); + equals(e16[0].anchor.y, 0.5); + equals(false, e16[0].isTarget); + equals(true, e16[0].isSource); + equals(e16[1].anchor.x, 0.5); + equals(e16[1].anchor.y, 0); + equals(true, e16[1].isTarget); + equals(false, e16[1].isSource); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (with reference params)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var refParams = {anchor:"RightMiddle"}; + var e16 = _jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false}, { isTarget:true, isSource:false }], refParams); + assertContextSize(2); + equals(e16[0].anchor.x, 1); + equals(e16[0].anchor.y, 0.5); + equals(false, e16[0].isTarget); + equals(true, e16[0].isSource); + equals(e16[1].anchor.x, 1); + equals(e16[1].anchor.y, 0.5); + equals(true, e16[1].isTarget); + equals(false, e16[1].isSource); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case, dynamic anchors, two arg method)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchors:["TopCenter", "BottomCenter"]}); + assertContextSize(2); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (default overlays)", function() { + _jsPlumb.Defaults.Overlays = [ + [ "Label", { id:"label" } ] + ]; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16), + e2 = _jsPlumb.addEndpoint(d17); + + ok(e1.getOverlay("label") != null, "endpoint 1 has overlay from defaults"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (default overlays)", function() { + _jsPlumb.Defaults.Overlays = [ + [ "Label", { id:"label" } ] + ]; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16, { + overlays:[ + ["Label", { id:"label2", location:[ 0.5, 1 ] } ] + ] + }), + e2 = _jsPlumb.addEndpoint(d17); + + ok(e1.getOverlay("label") != null, "endpoint 1 has overlay from defaults"); + ok(e1.getOverlay("label2") != null, "endpoint 1 has overlay from addEndpoint call"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (end point set label)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16), + e2 = _jsPlumb.addEndpoint(d17); + + e1.setLabel("FOO"); + equals(e1.getLabel(), "FOO", "endpoint's label is correct"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (end point set label in constructor, as string)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16, {label:"FOO"}), + e2 = _jsPlumb.addEndpoint(d17); + + equals(e1.getLabel(), "FOO", "endpoint's label is correct"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (end point set label in constructor, as function)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16, {label:function() { return "BAZ"; }, labelLocation:0.1}), + e2 = _jsPlumb.addEndpoint(d17); + + equals(e1.getLabel()(), "BAZ", "endpoint's label is correct"); + equals(e1.getLabelOverlay().getLocation(), 0.1, "endpoint's label's location is correct"); + }); + + test(renderMode + ": _jsPlumb.makeTarget (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.makeTarget (specify two divs in an array)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.makeTarget([d16, d17], { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.makeTarget (specify two divs by id in an array)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.makeTarget(["d16", "d17"], { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.makeTarget (specify divs by selector)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + d16.addClass("FOO");d17.addClass("FOO"); + _jsPlumb.makeTarget($(".FOO"), { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e.length, 1, "d17 has one endpoint"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case, two connect calls)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17"}); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 2, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case, two connect calls, uniqueEndpoint set)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true, anchor:"LeftMiddle", uniqueEndpoint:true, maxConnections:-1 }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17"}); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].connections.length, 2, "endpoint on d17 has two connections"); + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (newConnection:true specified)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17", newConnection:true}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0.5, "anchor is BottomCenter"); //here we should be seeing the default anchor + equals(e[0].anchor.y, 1, "anchor is BottomCenter"); //here we should be seeing the default anchor + }); + +// jsPlumb.connect, after makeSource has been called on some element + test(renderMode + ": _jsPlumb.connect after makeSource (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeSource (simple case, two connect calls)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 2, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case, two connect calls, uniqueEndpoint set)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true, anchor:"LeftMiddle", uniqueEndpoint:true, maxConnections:-1 }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].connections.length, 2, "endpoint on d17 has two connections"); + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (newConnection:true specified)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16, newConnection:true}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0.5, "anchor is BottomCenter"); //here we should be seeing the default anchor + equals(e[0].anchor.y, 1, "anchor is BottomCenter"); //here we should be seeing the default anchor + }); + + test(renderMode + ": _jsPlumb.connect after makeSource on child; wth parent set (parent should be recognised)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18", d17); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d18, { isSource:true,anchor:"LeftMiddle", parent:d17 }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + assertEndpointCount("d18", 0, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + +// *********************************************** + + test(renderMode + ': _jsPlumb.connect (between two Endpoints)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1, _jsPlumb); // no new endpoint should have been added + assertEndpointCount("d2", 1, _jsPlumb); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. + ok(c.id != null, "connection has had an id assigned"); + }); + + test(renderMode + ': _jsPlumb.connect (Endpoint connectorTooltip parameter)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1, {connectorTooltip:"FOO"}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + equals(c.connector.canvas.getAttribute("title"), "FOO", "connector canvas has label attribute set"); + }); + + test(renderMode + ': _jsPlumb.connect (tooltip parameter)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2, tooltip:"FOO"}); + equals(c.connector.canvas.getAttribute("title"), "FOO", "connector canvas has label attribute set"); + }); + + test(renderMode + ': _jsPlumb.connect (between two Endpoints, and dont supply any parameters to the Endpoints.)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1, _jsPlumb); // no new endpoint should have been added + assertEndpointCount("d2", 1, _jsPlumb); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. + }); + + test(renderMode + " : _jsPlumb.connect, passing 'anchors' array" ,function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, anchors:["LeftMiddle", "RightMiddle"]}); + + equals(c.endpoints[0].anchor.x, 0, "source anchor is at x=0"); + equals(c.endpoints[0].anchor.y, 0.5, "source anchor is at y=0.5"); + equals(c.endpoints[1].anchor.x, 1, "target anchor is at x=1"); + equals(c.endpoints[1].anchor.y, 0.5, "target anchor is at y=0.5"); + }); + + test(renderMode + ': _jsPlumb.connect (by endpoint)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + }); + + test(renderMode + ': _jsPlumb.connect (cost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17, cost:567}); + assertContextSize(3); + equals(c.getCost(), 567, "connection cost is 567"); + }); + + test(renderMode + ': _jsPlumb.connect (default cost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), undefined, "default connection cost is 1"); + }); + + test(renderMode + ': _jsPlumb.connect (set cost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), undefined, "default connection cost is 1"); + c.setCost(8989); + equals(c.getCost(), 8989, "connection cost is 8989"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (connectionCost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionCost:567}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), 567, "connection cost is 567"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (change connectionCost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionCost:567, maxConnections:-1}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1}); + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), 567, "connection cost is 567"); + e16.setConnectionCost(23); + var c2 = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(c2.getCost(), 23, "connection cost is 23 after change on endpoint"); + }); + + test(renderMode + ': _jsPlumb.connect (default bidirectional)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true}), + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.isBidirectional(), true, "default connection is bidirectional"); + }); + + test(renderMode + ': _jsPlumb.connect (bidirectional false)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true}), + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17, bidirectional:false}); + assertContextSize(3); + equals(c.isBidirectional(), false, "connection is not bidirectional"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (connectionsBidirectional)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionsBidirectional:true, maxConnections:-1}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.isBidirectional(), true, "connection is bidrectional"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (change connectionsBidirectional)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionsBidirectional:true, maxConnections:-1}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1}); + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.isBidirectional(), true, "connection is bidirectional"); + e16.setConnectionsBidirectional(false); + var c2 = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(c2.isBidirectional(), false, "connection is not bidirectional"); + }); + + test(renderMode + ": _jsPlumb.connect (two Endpoints - that have been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = _jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1, uuid:srcEndpointUuid}); + var e2 = _jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1, uuid:dstEndpointUuid}); + _jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ] }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertContextSize(3); + }); + + test(renderMode + ": _jsPlumb.connect (two Endpoints - that have not been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ], source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + var e1 = _jsPlumb.getEndpoint(srcEndpointUuid); + ok(e1 != null, "endpoint with src uuid added"); + ok(e1.canvas != null); + var e2 = _jsPlumb.getEndpoint(dstEndpointUuid); + ok(e2 != null, "endpoint with target uuid added"); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.connect (two Endpoints - that have been already added - by endpoint reference)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = _jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1}); + var e2 = _jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1}); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.connect (two elements)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.connect({ source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, straight)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, bezier, no params)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Bezier" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 150, "Bezier connector chose 150 curviness"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, bezier, curviness as int)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", { curviness:200 }] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Canvas Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 200, "Bezier connector chose 200 curviness"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, bezier, curviness as named option)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", {curviness:300}] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 300, "Bezier connector chose 300 curviness"); + }); + + test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Canvas Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as strings)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:["LeftMiddle", "RightMiddle"] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.5, conn.endpoints[0].anchor.y, "source anchor y"); + equals(1, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.5, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + + test(renderMode + ": _jsPlumb.connect (two argument method in which some data is reused across connections)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"), d19 = _addDiv("d19"); + var sharedData = { connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }; + var conn = _jsPlumb.connect({ source:d16, target:d17}, sharedData); + var conn2 = _jsPlumb.connect({ source:d18, target:d19}, sharedData); + assertContextSize(6); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 2, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(conn2.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + equals(0.3, conn2.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn2.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn2.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn2.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoint test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoints test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Dot, "Dot endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Blank Endpoint specified via 'endpoint' param)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Blank" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Blank Endpoint specified via 'endpoints' param)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Blank", "Blank" ] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Dot, "Dot endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, connector test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {}); + var e17 = _jsPlumb.addEndpoint(d17, {}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {}); + var e17 = _jsPlumb.addEndpoint(d17, {}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, anchors as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = "TopCenter", a17 = "BottomCenter"; + var e16 = _jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, 0.5, "endpoint 16 is at top center");equals(e16.anchor.y, 0, "endpoint 16 is at top center"); + equals(e17.anchor.x, 0.5, "endpoint 17 is at bottom center");equals(e17.anchor.y, 1, "endpoint 17 is at bottom center"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = [0,0.5,0,-1], a17 = [1,0.0,-1,-1]; + var e16 = _jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, a16[0]);equals(e16.anchor.y, a16[1]); + equals(e17.anchor.x, a17[0]);equals(e17.anchor.y, a17[1]); + equals(e16.anchor.getOrientation()[0], a16[2]); equals(e16.anchor.getOrientation()[1], a16[3]); + equals(e17.anchor.getOrientation()[0], a17[2]); equals(e17.anchor.getOrientation()[1], a17[3]); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchor')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchor:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:["TopCenter", "BottomCenter"]}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchors')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, default endpoint, supplied dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var anchors = [ [0.25, 0, 0, -1], [1, 0.25, 1, 0], [0.75, 1, 0, 1], [0, 0.75, -1, 0] ]; + _jsPlumb.connect({source:d1, target:d2, dynamicAnchors:anchors}); // auto connect with default endpoint and provided anchors + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.detach({source:d1, target:d2}); + // this changed in 1.3.5, because auto generated endpoints are now removed by detach. so i added the test below this one + // to check that the deleteEndpointsOnDetach flag is honoured. + assertEndpointCount("d1", 0, _jsPlumb);assertEndpointCount("d2", 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, default endpoint, supplied dynamic anchors, delete on detach false)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var anchors = [ [0.25, 0, 0, -1], [1, 0.25, 1, 0], [0.75, 1, 0, 1], [0, 0.75, -1, 0] ]; + _jsPlumb.connect({ + source:d1, + target:d2, + dynamicAnchors:anchors, + deleteEndpointsOnDetach:false + }); // auto connect with default endpoint and provided anchors + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.detach({source:d1, target:d2}); + // this changed in 1.3.5, because auto generated endpoints are now removed by detach. so i added this test + // to check that the deleteEndpointsOnDetach flag is honoured. + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, supplied endpoint and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = _jsPlumb.addEndpoint(d1, endpoint); + var e2 = _jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(3); + _jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, supplied endpoints using 'source' and 'target' (this test is identical to the one above apart from the param names), and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = _jsPlumb.addEndpoint(d1, endpoint); + var e2 = _jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + _jsPlumb.connect({source:e1, target:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(3); + _jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + " detachable parameter defaults to true on _jsPlumb.connect", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), true, "connections detachable by default"); + }); + + test(renderMode + " detachable parameter set to false on _jsPlumb.connect", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, detachable:false}); + equals(c.isDetachable(), false, "connection detachable"); + }); + + test(renderMode + " setDetachable on initially detachable connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), true, "connection initially detachable"); + c.setDetachable(false); + equals(c.isDetachable(), false, "connection not detachable"); + }); + + test(renderMode + " setDetachable on initially not detachable connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, detachable:false }); + equals(c.isDetachable(), false, "connection not initially detachable"); + c.setDetachable(true); + equals(c.isDetachable(), true, "connection now detachable"); + }); + + test(renderMode + " _jsPlumb.Defaults.ConnectionsDetachable", function() { + _jsPlumb.Defaults.ConnectionsDetachable = false; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), false, "connections not detachable by default (overrode the defaults)"); + _jsPlumb.Defaults.ConnectionsDetachable = true; + }); + + + test(renderMode + ": _jsPlumb.connect (testing for connection event callback)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var connectCallback = null, detachCallback = null; + _jsPlumb.bind("jsPlumbConnection", function(params) { + connectCallback = $.extend({}, params); + }); + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + detachCallback = $.extend({}, params); + }); + _jsPlumb.connect({source:d1, target:d2}); // auto connect with default endpoint and anchor set + ok(connectCallback != null, "connect callback was made"); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.detach({source:d1, target:d2}); + assertContextSize(2); + ok(detachCallback != null, "detach callback was made"); + }); + + test(renderMode + ": _jsPlumb.connect (setting cssClass on Connector)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2,cssClass:"CSS"}); + var has = function(clazz) { + var cn = c.connector.canvas.className, + cns = cn.constructor == String ? cn : cn.baseVal; + + return cns.indexOf(clazz) != -1; + }; + ok(has("CSS"), "custom cssClass set correctly"); + ok(has(_jsPlumb.connectorClass), "basic connector class set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (overlays, long-hand version)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = {width:40,length:40,location:0.7, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"}}; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + }); + + test(renderMode + ": _jsPlumb.connect (overlays, long-hand version, IDs specified)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + equals("aLabel", connection1.overlays[0].id); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + equals("anArrow", connection1.overlays[1].id); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays)", function() { + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays + overlays specified in connect call)", function() { + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, overlays:[ + ["Label", {id:"label"}] + ]}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("label") != null, "Label overlay created from connect call"); + }); + + test(renderMode + ": _jsPlumb.connect (default connection overlays)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + }); + + test(renderMode + ": _jsPlumb.connect (default connection overlays + overlays specified in connect call)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, overlays:[ + ["Label", {id:"label"}] + ]}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("label") != null, "Label overlay created from connect call"); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays + default connection overlays)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow2" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("arrow2") != null, "Arrow overlay created from connection defaults"); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays + default connection overlays)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow2" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, overlays:[ + ["Label", {id:"label"}] + ]}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("arrow2") != null, "Arrow overlay created from connection defaults"); + ok(c.getOverlay("label") != null, "Label overlay created from connect call"); + }); + + test(renderMode + ": _jsPlumb.connect (label overlay set using 'label')", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO" + }); + + equals(c.getLabel(), "FOO", "label is set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction, with string)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2}); + + c.setLabel("FOO"); + equals(c.getLabel(), "FOO", "label is set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction, with function)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2}); + + c.setLabel(function() { return "BAR"; }); + equals(c.getLabel()(), "BAR", "label is set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction, with params object)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2}); + + c.setLabel({ + label:"BAZ", + cssClass:"CLASSY", + location:0.9 + }); + var lo = c.getLabelOverlay(); + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "BAZ", "label overlay has correct value"); + equals(lo.getLocation(), 0.9, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction with existing label set, with params object)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO", + labelLocation:0.2 + }); + + var lo = c.getLabelOverlay(); + equals(lo.getLocation(), 0.2, "label overlay has correct location"); + + c.setLabel({ + label:"BAZ", + cssClass:"CLASSY", + location:0.9 + }); + + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "BAZ", "label overlay has correct value"); + equals(lo.getLocation(), 0.9, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (getLabelOverlay, label on connect call)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO" + }), + lo = c.getLabelOverlay(); + + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "FOO", "label overlay has correct value"); + equals(lo.getLocation(), 0.5, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (getLabelOverlay, label on connect call, location set)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO", + labelLocation:0.2 + }), + lo = c.getLabelOverlay(); + + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "FOO", "label overlay has correct value"); + equals(lo.getLocation(), 0.2, "label overlay has correct location"); + }); + + + test(renderMode + ": _jsPlumb.connect (remove single overlay by id)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + connection1.removeOverlay("aLabel"); + equals(1, connection1.overlays.length); + equals("anArrow", connection1.overlays[0].id); + }); + + test(renderMode + ": _jsPlumb.connect (remove multiple overlays by id)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + connection1.removeOverlays("aLabel", "anArrow"); + equals(0, connection1.overlays.length); + }); + + test(renderMode + ": _jsPlumb.connect (overlays, short-hand version)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var loc = { location:0.7 }; + var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label", {label:"CONNECTION 1", location:0.3}], + ["Arrow", arrowSpec, loc] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + }); + + test(renderMode + ": _jsPlumb.connect (removeAllOverlays)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var loc = { location:0.7 }; + var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label", {label:"CONNECTION 1", location:0.3}], + ["Arrow", arrowSpec, loc] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + + connection1.removeAllOverlays(); + equals(0, connection1.overlays.length); + }); + + test(renderMode + ": _jsPlumb.connect, specify arrow overlay using string identifier only", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:["Arrow"]}); + equals(jsPlumb.Overlays[renderMode].Arrow, conn.overlays[0].constructor); + }); + + test(renderMode + ": Connection.getOverlay method, existing overlay", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("arrowOverlay"); + ok(overlay != null); + }); + + test(renderMode + ": Connection.getOverlay method, non-existent overlay", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("IDONTEXIST"); + ok(overlay == null); + }); + + test(renderMode + ": Overlay.setVisible method", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("arrowOverlay"); + ok(overlay.isVisible()); + overlay.setVisible(false); + ok(!overlay.isVisible()); + overlay.setVisible(true); + ok(overlay.isVisible()); + }); + + // this test is for the original detach function; it should stay working after i mess with it + // a little. + test(renderMode + ": _jsPlumb.detach (by element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + var e3 = _jsPlumb.addEndpoint(d1); + var e4 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + _jsPlumb.connect({ sourceEndpoint:e3, targetEndpoint:e4 }); // make two connections to be sure this works ;) + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionCount(e3, 1); + assertConnectionCount(e4, 1); + + _jsPlumb.detach({source:"d1", target:"d2"}); + + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionCount(e3, 0); + assertConnectionCount(e4, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + // detach is being made to operate more like connect - by taking one argument with a whole + // bunch of possible params in it. if two args are passed in it will continue working + // in the old way. + test(renderMode + ": _jsPlumb.detach (params object, using element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({source:"d1", target:"d2"}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); +/* + test(renderMode + ": _jsPlumb.detach (params object, using target only)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e1 = _jsPlumb.addEndpoint(d1, {maxConnections:2}), + e2 = _jsPlumb.addEndpoint(d2), + e3 = _jsPlumb.addEndpoint(d3); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e3 }); + assertConnectionCount(e1, 2); + assertConnectionCount(e2, 1); + assertConnectionCount(e3, 1); + _jsPlumb.detach({target:"d2"}); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 0); + assertConnectionCount(e3, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1); + }); +*/ + test(renderMode + ": _jsPlumb.detach (params object, using element objects)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({source:d1, target:d2}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.detach (source and target as endpoint UUIDs)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {uuid:"abcdefg"}); + ok(_jsPlumb.getEndpoint("abcdefg") != null, "e1 exists"); + var e2 = _jsPlumb.addEndpoint(d2, {uuid:"hijklmn"}); + ok(_jsPlumb.getEndpoint("hijklmn") != null, "e2 exists"); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({uuids:["abcdefg", "hijklmn"]}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.detach (sourceEndpoint and targetEndpoint supplied)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.makeDynamicAnchors (longhand)", function() { + var anchors = [_jsPlumb.makeAnchor([0.2, 0, 0, -1], null, _jsPlumb), _jsPlumb.makeAnchor([1, 0.2, 1, 0], null, _jsPlumb), + _jsPlumb.makeAnchor([0.8, 1, 0, 1], null, _jsPlumb), _jsPlumb.makeAnchor([0, 0.8, -1, 0], null, _jsPlumb) ]; + var dynamicAnchor = _jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); + }); + + test(renderMode + ": _jsPlumb.makeDynamicAnchors (shorthand)", function() { + var anchors = [[0.2, 0, 0, -1], [1, 0.2, 1, 0], + [0.8, 1, 0, 1], [0, 0.8, -1, 0] ]; + var dynamicAnchor = _jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); + }); + + test(renderMode + ": Connection.isVisible/setVisible", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c1 = _jsPlumb.connect({source:d1,target:d2}); + equals(true, c1.isVisible(), "Connection is visible after creation."); + c1.setVisible(false); + equals(false, c1.isVisible(), "Connection is not visible after calling setVisible(false)."); + equals($(c1.connector.canvas).css("display"), "none"); + c1.setVisible(true); + equals(true, c1.isVisible(), "Connection is visible after calling setVisible(true)."); + equals($(c1.connector.canvas).css("display"), "block"); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible basic test (no connections)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals($(e1.canvas).css("display"), "none"); + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals($(e1.canvas).css("display"), "block"); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should track changes in the source, because it has only this connection.)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + var c1 = _jsPlumb.connect({source:e1, target:e2}); + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals(false, e2.isVisible(), "other Endpoint is not visible either."); + equals(false, c1.isVisible(), "connection between the two is not visible either."); + + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals(true, e2.isVisible(), "other Endpoint is visible too"); + equals(true, c1.isVisible(), "connection between the two is visible too."); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should not track changes in the source, because it has another connection.)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2, { maxConnections:2 }), e3 = _jsPlumb.addEndpoint(d3); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + var c1 = _jsPlumb.connect({source:e1, target:e2}); + var c2 = _jsPlumb.connect({source:e2, target:e3}); + + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals(true, e2.isVisible(), "other Endpoint should still be visible."); + equals(true, e3.isVisible(), "third Endpoint should still be visible."); + equals(false, c1.isVisible(), "connection between the two is not visible either."); + equals(true, c2.isVisible(), "other connection is visible."); + + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals(true, e2.isVisible(), "other Endpoint is visible too"); + equals(true, c1.isVisible(), "connection between the two is visible too."); + equals(true, c2.isVisible(), "other connection is visible."); + }); + + // tests of the functionality that allows a user to specify that they want elements appended to the document body + test(renderMode + " _jsPlumb.Defaults.Container, specified with a selector", function() { + _jsPlumb.Defaults.Container = $("body"); + equals($("#container")[0].childNodes.length, 0, "container has no nodes"); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals($("#container")[0].childNodes.length, 2, "container has two div elements"); // the divs we added have been added to the 'container' div. + // but we have told _jsPlumb to add its canvas to the body, so this connect call should not add another few elements to the container: + _jsPlumb.connect({source:d1, target:d2}); + equals($("#container")[0].childNodes.length, 2, "container still has two div elements"); + }); + + // tests of the functionality that allows a user to specify that they want elements appended to some specific container. + test(renderMode + " _jsPlumb.Defaults.Container, specified with DOM element", function() { + _jsPlumb.Defaults.Container = document.getElementsByTagName("body")[0]; + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length, "two divs added to the container"); // the divs we added have been added to the 'container' div. + // but we have told _jsPlumb to add its canvas to the body, so this connect call should not add another few elements to the container: + var bodyElementCount = $("body")[0].childNodes.length; + _jsPlumb.connect({source:d1, target:d2}); + equals(2, $("#container")[0].childNodes.length, "still only two children in container; elements were added to the body by _jsPlumb"); + // test to see if 3 elements have been added + equals(bodyElementCount + 3, $("body")[0].childNodes.length, "3 new elements added to the document body"); + }); + + test(renderMode + " container specified to connect call, with a selector", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length); // the divs we added have been added to the 'container' div. + var bodyElementCount = $("body")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to the body, so this connect call should not add another few elements to the container: + _jsPlumb.connect({source:d1, target:d2, container:$("body")}); + equals(2, $("#container")[0].childNodes.length); + equals(bodyElementCount + 3, $("body")[0].childNodes.length, "3 new elements added to the document body"); + }); + + test(renderMode + " container specified to connect call, with a string ID", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + equals(3, $("#container")[0].childNodes.length, "container has divs we added"); // the divs we added have been added to the 'container' div. + var d3ElementCount = $("#d3")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to "d3", so this connect call should not add another few elements to the container: + _jsPlumb.connect({source:d1, target:d2, container:"d3"}); + equals(3, $("#container")[0].childNodes.length, "container still has only the divs we added"); + equals(d3ElementCount + 3, $("#d3")[0].childNodes.length, "3 new elements added to div d3"); + }); + + test(renderMode + " container specified to addEndpoint call, with a selector", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length); // the divs we added have been added to the 'container' div. + var bodyElementCount = $("body")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to the body, so this connect call should not add another few elements to the container: + _jsPlumb.addEndpoint(d1, {container:$("body")}); + equals(2, $("#container")[0].childNodes.length); + equals(bodyElementCount + 1, $("body")[0].childNodes.length, "1 new element added to the document body"); + }); + + test(renderMode + " container specified to addEndpoint call, with a string ID", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + equals(3, $("#container")[0].childNodes.length, "container has divs we added"); // the divs we added have been added to the 'container' div. + var d3ElementCount = $("#d3")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to "d3", so this connect call should not add another few elements to the container: + _jsPlumb.addEndpoint(d1, { container:"d3" }); + equals(3, $("#container")[0].childNodes.length, "container still has only the divs we added"); + equals(d3ElementCount + 1, $("#d3")[0].childNodes.length, "1 new element added to div d3"); + }); + + test(renderMode + " detachable defaults to true when connection made between two endpoints", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2), + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection not detachable"); + }); + + test(renderMode + " connection detachable when target endpoint has connectionsDetachable set to true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2, {connectionsDetachable:true}), + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection detachable because connectionsDetachable was set on target endpoint"); + }); + + test(renderMode + " connection detachable when source endpoint has connectionsDetachable set to true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {connectionsDetachable:true}), e2 = _jsPlumb.addEndpoint(d2), + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection detachable because connectionsDetachable was set on source endpoint"); + }); + + test(renderMode + " Connector has 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.connector.type, "Bezier", "Bezier connector has type set"); + + var c2 = _jsPlumb.connect({source:d1, target:d2, connector:"Straight"}); + equals(c2.connector.type, "Straight", "Straight connector has type set"); + + var c3 = _jsPlumb.connect({source:d1, target:d2, connector:"Flowchart"}); + equals(c3.connector.type, "Flowchart", "Flowchart connector has type set"); + }); + + test(renderMode + " Endpoints have 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.endpoints[0].type, "Dot", "Dot endpoint has type set"); + + var c2 = _jsPlumb.connect({source:d1, target:d2, endpoints:["Rectangle", "Blank"]}); + equals(c2.endpoints[1].type, "Blank", "Blank endpoint has type set"); + equals(c2.endpoints[0].type, "Rectangle", "Rectangle endpoint has type set"); + }); + + test(renderMode + " Overlays have 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = _jsPlumb.connect({ + source:d1, + target:d2, + overlays:[ "Arrow", "Label", "PlainArrow", "Diamond" ] + }); + equals(c.overlays[0].type, "Arrow", "Arrow overlay has type set"); + equals(c.overlays[1].type, "Label", "Label overlay has type set"); + equals(c.overlays[2].type, "PlainArrow", "PlainArrow overlay has type set"); + equals(c.overlays[3].type, "Diamond", "Diamond overlay has type set"); + }); + + test(renderMode + " _jsPlumb.hide, original one-arg version", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + _jsPlumb.hide(d1); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(true, e1.isVisible(), "endpoint 1 is still visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + _jsPlumb.show(d1); + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + }); + + test(renderMode + " _jsPlumb.hide, two-arg version, endpoints should also be hidden", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + _jsPlumb.show(d1); // now show d1, but do not alter the endpoints. e1 should still be hidden + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + test(renderMode + " _jsPlumb.show, two-arg version, endpoints should become visible", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + _jsPlumb.show(d1, true); // now show d1, and alter the endpoints. e1 should be visible. + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(true, e1.isVisible(), "endpoint 1 is visible again."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + test(renderMode + " _jsPlumb.show, two-arg version, endpoints should become visible, but not all connections, because some other endpoints are not visible.", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e11 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + e3 = _jsPlumb.addEndpoint(d3, e), + c1 = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e11, target:e3}); + + // we now have d1 connected to both d3 and d2. we'll hide d1, and everything on d1 should be hidden. + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "connection 1 is no longer visible."); + equals(false, c2.isVisible(), "connection 2 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(false, e11.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + equals(true, e3.isVisible(), "endpoint 3 is still visible."); + + // now, we will also hide d3. making d1 visible again should NOT result in c2 becoming visible, because the other endpoint + // for c2 is e3, which is not visible. + _jsPlumb.hide(d3, true); + equals(false, e3.isVisible(), "endpoint 3 is no longer visible."); + + _jsPlumb.show(d1, true); // now show d1, and alter the endpoints. e1 should be visible, c1 should be visible, but c2 should not. + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(false, c2.isVisible(), "Connection 2 is not visible."); + equals(true, e1.isVisible(), "endpoint 1 is visible again."); + equals(true, e11.isVisible(), "endpoint 11 is visible again."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + equals(false, e3.isVisible(), "endpoint 3 is still not visible."); + }); + + /* + test(renderMode + " _jsPlumb.hide, two-arg version, endpoints should also be hidden", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + /** + * test for issue 132: label leaves its element in the DOM after it has been + * removed from a connection. + */ + test(renderMode + " label cleans itself up properly", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label", cssClass:"foo"}] + ]}); + ok($(".foo").length == 1, "label element exists in DOM"); + c.removeOverlay("label"); + ok($(".foo").length == 0, "label element does not exist in DOM"); + }); + + test(renderMode + " arrow cleans itself up properly", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Arrow", {id:"arrow"}] + ]}); + ok(c.getOverlay("arrow") != null, "arrow overlay exists"); + c.removeOverlay("arrow"); + ok(c.getOverlay("arrow") == null, "arrow overlay has been removed"); + }); + + test(renderMode + " label overlay getElement function", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label"}] + ]}); + ok(c.getOverlay("label").getElement() != null, "label overlay exposes element via getElement method"); + }); + + test(renderMode + " label overlay provides getLabel and setLabel methods", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label", label:"foo"}] + ]}); + var o = c.getOverlay("label"), e = o.getElement(); + ok(e.innerHTML == "foo", "label text is set to original value"); + o.setLabel("baz"); + ok(e.innerHTML == "baz", "label text is set to new value 'baz'"); + ok(o.getLabel() === "baz", "getLabel function works correctly with String"); + // now try functions + var aFunction = function() { return "aFunction"; }; + o.setLabel(aFunction); + ok(e.innerHTML == "aFunction", "label text is set to new value from Function"); + ok(o.getLabel() === aFunction, "getLabel function works correctly with Function"); + }); + + test(renderMode + " parameters object works for Endpoint", function() { + var d1 = _addDiv("d1"), + f = function() { alert("FOO!"); }, + e = _jsPlumb.addEndpoint(d1, { + isSource:true, + parameters:{ + "string":"param1", + "int":4, + "function":f + } + }); + ok(e.getParameter("string") === "param1", "getParameter(String) works correctly"); + ok(e.getParameter("int") === 4, "getParameter(int) works correctly"); + ok(e.getParameter("function") == f, "getParameter(Function) works correctly"); + }); + + test(renderMode + " parameters object works for Connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + f = function() { alert("FOO!"); }; + var c = _jsPlumb.connect({ + source:d1, + target:d2, + parameters:{ + "string":"param1", + "int":4, + "function":f + } + }); + ok(c.getParameter("string") === "param1", "getParameter(String) works correctly"); + ok(c.getParameter("int") === 4, "getParameter(int) works correctly"); + ok(c.getParameter("function") == f, "getParameter(Function) works correctly"); + }); + + test(renderMode + " parameters set on Endpoints and Connections are all merged, and merged correctly at that.", function() { + var d1 = _addDiv("d1"), + d2 = _addDiv("d2"), + e = _jsPlumb.addEndpoint(d1, { + isSource:true, + parameters:{ + "string":"sourceEndpoint", + "int":0, + "function":function() { return "sourceEndpoint"; } + } + }), + e2 = _jsPlumb.addEndpoint(d2, { + isTarget:true, + parameters:{ + "int":1, + "function":function() { return "targetEndpoint"; } + } + }), + c = _jsPlumb.connect({source:e, target:e2, parameters:{ + "function":function() { return "connection"; } + }}); + + ok(c.getParameter("string") === "sourceEndpoint", "getParameter(String) works correctly"); + ok(c.getParameter("int") === 1, "getParameter(int) works correctly"); + ok(c.getParameter("function")() == "connection", "getParameter(Function) works correctly"); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers standard connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2}); + equals(_jsPlumb.anchorManager.getConnectionsFor("d1").length, 1); + equals(_jsPlumb.anchorManager.getEndpointsFor("d1").length, 1); + equals(_jsPlumb.anchorManager.getConnectionsFor("d2").length, 1); + equals(_jsPlumb.anchorManager.getEndpointsFor("d2").length, 1); + var c2 = _jsPlumb.connect({source:d1, target:d2}); + equals(_jsPlumb.anchorManager.getConnectionsFor("d1").length, 2); + equals(_jsPlumb.anchorManager.getConnectionsFor("d2").length, 2); + equals(_jsPlumb.anchorManager.getEndpointsFor("d1").length, 2); + equals(_jsPlumb.anchorManager.getEndpointsFor("d2").length, 2); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers dynamic anchor connection, and removes it.", function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var c = _jsPlumb.connect({source:d3, target:d4, anchors:["AutoDefault", "AutoDefault"]}); + + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 1); + + var c2 = _jsPlumb.connect({source:d3, target:d4}); + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 2); + equals(_jsPlumb.anchorManager.getConnectionsFor("d4").length, 2); + + equals(_jsPlumb.anchorManager.getEndpointsFor("d3").length, 2); + _jsPlumb.detach(c); + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 1); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers continuous anchor connection, and removes it.", function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var c = _jsPlumb.connect({source:d3, target:d4, anchors:["Continuous", "Continuous"]}); + + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 1); + equals(_jsPlumb.anchorManager.getConnectionsFor("d4").length, 1); + + _jsPlumb.detach(c); + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 0); + equals(_jsPlumb.anchorManager.getConnectionsFor("d4").length, 0); + + _jsPlumb.reset(); + equals(_jsPlumb.anchorManager.getEndpointsFor("d4"), null); + }); + +// ------------- utility functions - math stuff, mostly -------------------------- + + var tolerance = 0.00000005, withinTolerance = function(v1, v2, msg) { + if (Math.abs(v1 - v2) < tolerance) ok(true, msg + "; expected " + v1 + " and got it"); + else { + ok(false, msg + "; expected " + v1 + " got " + v2); + } + }; + test(renderMode + " jsPlumb.util.gradient, segment 1", function() { + var p1 = [2,2], p2 = [3,1], m = jsPlumb.util.gradient(p1,p2); + equals(m, -1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumb.util.normal, segment 1", function() { + var p1 = [2,2], p2 = [3,1], m = jsPlumb.util.normal(p1,p2); + equals(m, 1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumb.util.gradient, segment 2", function() { + var p1 = [2,2], p2 = [3,3], m = jsPlumb.util.gradient(p1,p2); + equals(m, 1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumb.util.normal, segment 2", function() { + var p1 = [2,2], p2 = [3,3], m = jsPlumb.util.normal(p1,p2); + equals(m, -1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumb.util.gradient, segment 3", function() { + var p1 = [2,2], p2 = [1,3], m = jsPlumb.util.gradient(p1,p2); + equals(m, -1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumb.util.normal, segment 3", function() { + var p1 = [2,2], p2 = [1,3], m = jsPlumb.util.normal(p1,p2); + equals(m, 1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumb.util.gradient, segment 4", function() { + var p1 = [2,2], p2 = [1,1], m = jsPlumb.util.gradient(p1,p2); + equals(m, 1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumb.util.normal, segment 4", function() { + var p1 = [2,2], p2 = [1,1], m = jsPlumb.util.normal(p1,p2); + equals(m, -1, "normal calculated correctly for simple case"); + }); + test(renderMode + "jsPlumb.util.pointOnLine, segment 1", function() { + var p1 = {x:2,y:2}, p2={x:3, y:1}, + target = jsPlumb.util.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumb.util.pointOnLine, segment 2", function() { + var p1 = {x:2,y:2}, p2={x:3, y:3}, + target = jsPlumb.util.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumb.util.pointOnLine, segment 3", function() { + var p1 = {x:2,y:2}, p2={x:1, y:3}, + target = jsPlumb.util.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumb.util.pointOnLine, segment 4", function() { + var p1 = {x:2,y:2}, p2={x:1, y:1}, + target = jsPlumb.util.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumb.util.perpendicularLineTo, segment 1", function() { + var p1 = {x:2, y:2}, p2={x:3, y:1}, m = jsPlumb.util.gradient(p1, p2), + l = jsPlumb.util.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(4, l[0].x, "point 1 x is correct"); + withinTolerance(2, l[0].y, "point 1 y is correct"); + + withinTolerance(2, l[1].x, "point 2 x is correct"); + withinTolerance(0, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumb.util.perpendicularLineTo, segment 2", function() { + var p1 = {x:2, y:2}, p2={x:3, y:3}, m = jsPlumb.util.gradient(p1, p2), + l = jsPlumb.util.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(4, l[0].x, "point 1 x is correct"); + withinTolerance(2, l[0].y, "point 1 y is correct"); + + withinTolerance(2, l[1].x, "point 2 x is correct"); + withinTolerance(4, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumb.util.perpendicularLineTo, segment 3", function() { + var p1 = {x:2, y:2}, p2={x:1, y:3}, m = jsPlumb.util.gradient(p1, p2), + l = jsPlumb.util.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(2, l[0].x, "point 1 x is correct"); + withinTolerance(4, l[0].y, "point 1 y is correct"); + + withinTolerance(0, l[1].x, "point 2 x is correct"); + withinTolerance(2, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumb.util.perpendicularLineTo, segment 4", function() { + var p1 = {x:2, y:2}, p2={x:1, y:1}, m = jsPlumb.util.gradient(p1, p2), + l = jsPlumb.util.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(2, l[0].x, "point 1 x is correct"); + withinTolerance(0, l[0].y, "point 1 y is correct"); + + withinTolerance(0, l[1].x, "point 2 x is correct"); + withinTolerance(2, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumb.util.setImage on Endpoint", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { + endpoint:[ "Image", { src:"../../img/endpointTest1.png" } ] + }, + ep = _jsPlumb.addEndpoint(d1, e); + ep.setImage("../../img/littledot.png"); + }); + test(renderMode + "jsPlumb.util.setImage on Endpoint, with supplied onload", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { + endpoint:[ "Image", { + src:"../../img/endpointTest1.png", + onload:function(imgEp) { + equals("../../img/endpointTest1.png", imgEp.img.src); + } + } ] + }, + ep = _jsPlumb.addEndpoint(d1, e); + ep.setImage("../../img/littledot.png", function(imgEp) { + equals("../../img/littledot.png", imgEp.img.src); + }); + }); + + test(renderMode + "attach endpoint to table when desired element was td", function() { + var table = document.createElement("table"), + tr = document.createElement("tr"), + td = document.createElement("td"); + table.appendChild(tr); tr.appendChild(td); + document.body.appendChild(table); + var ep = _jsPlumb.addEndpoint(td); + equals(ep.canvas.parentNode.tagName.toLowerCase(), "table"); + }); + +// issue 190 - regressions with getInstance. these tests ensure that generated ids are unique across +// instances. + + test(renderMode + " id clashes between instances", function() { + var d1 = document.createElement("div"), + d2 = document.createElement("div"), + _jsp2 = jsPlumb.getInstance(); + + document.body.appendChild(d1); + document.body.appendChild(d2); + + _jsPlumb.addEndpoint(d1); + _jsp2.addEndpoint(d2); + + var id1 = d1.getAttribute("id"), + id2 = d2.getAttribute("id"); + + var idx = id1.indexOf("_"), idx2 = id1.lastIndexOf("_"), v1 = id1.substring(idx, idx2); + var idx3 = id2.indexOf("_"), idx4 = id2.lastIndexOf("_"), v2 = id2.substring(idx3, idx4); + + ok (v1 != v2, "instance versions are different : " + v1 + " : " + v2); + }); + + test(renderMode + " id clashes between instances", function() { + var d1 = document.createElement("div"), + d2 = document.createElement("div"), + _jsp2 = jsPlumb.getInstance(); + + document.body.appendChild(d1); + document.body.appendChild(d2); + + _jsPlumb.addEndpoint(d1); + _jsPlumb.addEndpoint(d2); + + var id1 = d1.getAttribute("id"), + id2 = d2.getAttribute("id"); + + var idx = id1.indexOf("_"), idx2 = id1.lastIndexOf("_"), v1 = id1.substring(idx, idx2); + var idx3 = id2.indexOf("_"), idx4 = id2.lastIndexOf("_"), v2 = id2.substring(idx3, idx4); + + ok (v1 == v2, "instance versions are the same : " + v1 + " : " + v2); + }); + + test(renderMode + " importDefaults", function() { + _jsPlumb.Defaults.Anchors = ["LeftMiddle", "RightMiddle"]; + var d1 = _addDiv("d1"), + d2 = _addDiv(d2), + c = _jsPlumb.connect({source:d1, target:d2}), + e = c.endpoints[0]; + + equals(e.anchor.x, 0, "left middle anchor"); + equals(e.anchor.y, 0.5, "left middle anchor"); + + _jsPlumb.importDefaults({ + Anchors:["TopLeft", "TopRight"] + }); + + var conn = _jsPlumb.connect({source:d1, target:d2}), + e1 = conn.endpoints[0], e2 = conn.endpoints[1]; + + equals(e1.anchor.x, 0, "top leftanchor"); + equals(e2.anchor.y, 0, "top left anchor"); + equals(e2.anchor.x, 1, "top right anchor"); + equals(e2.anchor.y, 0, "top right anchor"); + + }); + + test(renderMode + " restoreDefaults", function() { + _jsPlumb.importDefaults({ + Anchors:["TopLeft", "TopRight"] + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), conn = _jsPlumb.connect({source:d1, target:d2}), + e1 = conn.endpoints[0], e2 = conn.endpoints[1]; + + equals(e1.anchor.x, 0, "top leftanchor"); + equals(e2.anchor.y, 0, "top left anchor"); + equals(e2.anchor.x, 1, "top right anchor"); + equals(e2.anchor.y, 0, "top right anchor"); + + _jsPlumb.restoreDefaults(); + + var conn2 = _jsPlumb.connect({source:d1, target:d2}), + e3 = conn2.endpoints[0], e4 = conn2.endpoints[1]; + + equals(e3.anchor.x, 0.5, "bottom center anchor"); + equals(e3.anchor.y, 1, "bottom center anchor"); + equals(e4.anchor.x, 0.5, "bottom center anchor"); + equals(e4.anchor.y, 1, "bottom center anchor"); + + + }); + + + /** + * leave this test at the bottom! + */ + test(renderMode + ': unload test', function() { + _jsPlumb.unload(); + var contextNode = $(".__jsPlumb_context"); + ok(contextNode.length == 0, 'context node unloaded'); + }); +}; + diff --git a/archive/1.3.6/jsPlumb-connectors-statemachine-1.3.6-RC1.js b/archive/1.3.6/jsPlumb-connectors-statemachine-1.3.6-RC1.js new file mode 100644 index 000000000..4e3406d1a --- /dev/null +++ b/archive/1.3.6/jsPlumb-connectors-statemachine-1.3.6-RC1.js @@ -0,0 +1,442 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.6 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the state machine connectors. + * + * Thanks to Brainstorm Mobile Solutions for supporting the development of these. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + var Line = function(x1, y1, x2, y2) { + + this.m = (y2 - y1) / (x2 - x1); + this.b = -1 * ((this.m * x1) - y1); + + this.rectIntersect = function(x,y,w,h) { + var results = []; + + // try top face + // the equation of the top face is y = (0 * x) + b; y = b. + var xInt = (y - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try right face + var yInt = (this.m * (x + w)) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + // bottom face + var xInt = ((y + h) - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try left face + var yInt = (this.m * x) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + if (results.length == 2) { + var midx = (results[0][0] + results[1][0]) / 2, midy = (results[0][1] + results[1][1]) / 2; + results.push([ midx,midy ]); + // now calculate the segment inside the rectangle where the midpoint lies. + var xseg = midx <= x + (w / 2) ? -1 : 1, + yseg = midy <= y + (h / 2) ? -1 : 1; + results.push([xseg, yseg]); + return results; + } + + return null; + + }; + }, + _segment = function(x1, y1, x2, y2) { + if (x1 <= x2 && y2 <= y1) return 1; + else if (x1 <= x2 && y1 <= y2) return 2; + else if (x2 <= x1 && y2 >= y1) return 3; + return 4; + }, + + // the control point we will use depends on the faces to which each end of the connection is assigned, specifically whether or not the + // two faces are parallel or perpendicular. if they are parallel then the control point lies on the midpoint of the axis in which they + // are parellel and varies only in the other axis; this variation is proportional to the distance that the anchor points lie from the + // center of that face. if the two faces are perpendicular then the control point is at some distance from both the midpoints; the amount and + // direction are dependent on the orientation of the two elements. 'seg', passed in to this method, tells you which segment the target element + // lies in with respect to the source: 1 is top right, 2 is bottom right, 3 is bottom left, 4 is top left. + // + // sourcePos and targetPos are arrays of info about where on the source and target each anchor is located. their contents are: + // + // 0 - absolute x + // 1 - absolute y + // 2 - proportional x in element (0 is left edge, 1 is right edge) + // 3 - proportional y in element (0 is top edge, 1 is bottom edge) + // + _findControlPoint = function(midx, midy, segment, sourceEdge, targetEdge, dx, dy, distance, proximityLimit) { + + // TODO (maybe) + // - if anchor pos is 0.5, make the control point take into account the relative position of the elements. + if (distance <= proximityLimit) return [midx, midy]; + + if (segment == 1) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 2) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 3) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 4) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + }; + + /* + Function: StateMachine constructor + + Allowed parameters: + curviness - measure of how "curvy" the connectors will be. this is translated as the distance that the + Bezier curve's control point is from the midpoint of the straight line connecting the two + endpoints, and does not mean that the connector is this wide. The Bezier curve never reaches + its control points; they act as gravitational masses. defaults to 10. + margin - distance from element to start and end connectors, in pixels. defaults to 5. + proximityLimit - sets the distance beneath which the elements are consider too close together to bother with fancy + curves. by default this is 80 pixels. + loopbackRadius - the radius of a loopback connector. optional; defaults to 25. + */ + jsPlumb.Connectors.StateMachine = function(params) { + var self = this, + currentPoints = null, + _sx, _sy, _tx, _ty, _controlPoint = [], + curviness = params.curviness || 10, + margin = params.margin || 5, + proximityLimit = params.proximityLimit || 80, + clockwise = params.orientation && params.orientation == "clockwise", + loopbackRadius = params.loopbackRadius || 25, + isLoopback = false; + + this.type = "StateMachine"; + params = params || {}; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *= 1.9; + //ensure at least one pixel width + lineWidth = lineWidth || 1; + var x = Math.min(sourcePos[0], targetPos[0]) - xo, + y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (sourceEndpoint.elementId != targetEndpoint.elementId) { + + isLoopback = false; + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + + // now adjust for the margin + if (sourcePos[2] == 0) _sx -= margin; + if (sourcePos[2] == 1) _sx += margin; + if (sourcePos[3] == 0) _sy -= margin; + if (sourcePos[3] == 1) _sy += margin; + if (targetPos[2] == 0) _tx -= margin; + if (targetPos[2] == 1) _tx += margin; + if (targetPos[3] == 0) _ty -= margin; + if (targetPos[3] == 1) _ty += margin; + + // + // these connectors are quadratic bezier curves, having a single control point. if both anchors + // are located at 0.5 on their respective faces, the control point is set to the midpoint and you + // get a straight line. this is also the case if the two anchors are within 'proximityLimit', since + // it seems to make good aesthetic sense to do that. outside of that, the control point is positioned + // at 'curviness' pixels away along the normal to the straight line connecting the two anchors. + // + // there may be two improvements to this. firstly, we might actually support the notion of avoiding nodes + // in the UI, or at least making a good effort at doing so. if a connection would pass underneath some node, + // for example, we might increase the distance the control point is away from the midpoint in a bid to + // steer it around that node. this will work within limits, but i think those limits would also be the likely + // limits for, once again, aesthetic good sense in the layout of a chart using these connectors. + // + // the second possible change is actually two possible changes: firstly, it is possible we should gradually + // decrease the 'curviness' as the distance between the anchors decreases; start tailing it off to 0 at some + // point (which should be configurable). secondly, we might slightly increase the 'curviness' for connectors + // with respect to how far their anchor is from the center of its respective face. this could either look cool, + // or stupid, and may indeed work only in a way that is so subtle as to have been a waste of time. + // + + var _midx = (_sx + _tx) / 2, _midy = (_sy + _ty) / 2, + m2 = (-1 * _midx) / _midy, theta2 = Math.atan(m2), + dy = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.sin(theta2)), + dx = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.cos(theta2)), + segment = _segment(_sx, _sy, _tx, _ty), + distance = Math.sqrt(Math.pow(_tx - _sx, 2) + Math.pow(_ty - _sy, 2)); + + // calculate the control point. this code will be where we'll put in a rudimentary element avoidance scheme; it + // will work by extending the control point to force the curve to be, um, curvier. + _controlPoint = _findControlPoint(_midx, + _midy, + segment, + sourcePos, + targetPos, + curviness, curviness, + distance, + proximityLimit); + + + var requiredWidth = Math.max(Math.abs(_controlPoint[0] - _sx) * 3, Math.abs(_controlPoint[0] - _tx) * 3, Math.abs(_tx-_sx), 2 * lineWidth, minWidth), + requiredHeight = Math.max(Math.abs(_controlPoint[1] - _sy) * 3, Math.abs(_controlPoint[1] - _ty) * 3, Math.abs(_ty-_sy), 2 * lineWidth, minWidth); + + if (w < requiredWidth) { + var dw = requiredWidth - w; + x -= (dw / 2); + _sx += (dw / 2); + _tx += (dw / 2); + w = requiredWidth; + _controlPoint[0] += (dw / 2); + } + + if (h < requiredHeight) { + var dh = requiredHeight - h; + y -= (dh / 2); + _sy += (dh / 2); + _ty += (dh / 2); + h = requiredHeight; + _controlPoint[1] += (dh / 2); + } + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty, _controlPoint[0], _controlPoint[1] ]; + } + else { + isLoopback = true; + // a loopback connector. draw an arc from one anchor to the other. + // i guess we'll do this the same way as the others. just the control point will be a fair distance away. + var x1 = sourcePos[0], x2 = sourcePos[0], y1 = sourcePos[1] - margin, y2 = sourcePos[1] - margin, + cx = x1, cy = y1 - loopbackRadius; + + // canvas sizing stuff, to ensure the whole painted area is visible. + w = ((2 * lineWidth) + (4 * loopbackRadius)), h = ((2 * lineWidth) + (4 * loopbackRadius)); + x = cx - loopbackRadius - lineWidth - loopbackRadius, y = cy - loopbackRadius - lineWidth - loopbackRadius; + currentPoints = [ x, y, w, h, cx-x, cy-y, loopbackRadius, clockwise, x1-x, y1-y, x2-x, y2-y]; + } + + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_tx, y:_ty }, + { x:_controlPoint[0], y:_controlPoint[1] }, + { x:_controlPoint[0] + 1, y:_controlPoint[1] + 1}, + { x:_sx, y:_sy } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + if (isLoopback) { + + if (location > 0 && location < 1) location = 1- location; + +// current points are [ x, y, width, height, center x, center y, radius, clockwise, startx, starty, endx, endy ] + // so the path length is the circumference of the circle + //var len = 2 * Math.PI * currentPoints[6], + // map 'location' to an angle. 0 is PI/2 when the connector is on the top face; if we + // support other faces it will have to be calculated for each one. 1 is also PI/2. + // 0.5 is -PI/2. + var startAngle = (location * 2 * Math.PI) + (Math.PI / 2), + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + + } + else return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + if (isLoopback) + return Math.atan(location * 2 * Math.PI); + else + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + if (isLoopback) { + + if (location > 0 && location < 1) location = 1- location; + + var circumference = 2 * Math.PI * currentPoints[6], + arcSpan = distance / circumference * 2 * Math.PI, + startAngle = (location * 2 * Math.PI) - arcSpan + (Math.PI / 2), + + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + } + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + }; + + /* + * Canvas state machine renderer. + */ + jsPlumb.Connectors.canvas.StateMachine = function(params) { + params = params || {}; + var self = this, drawGuideline = params.drawGuideline || true, avoidSelector = params.avoidSelector; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.CanvasConnector.apply(this, arguments); + + + this._paint = function(dimensions) { + + if (dimensions.length == 10) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.quadraticCurveTo(dimensions[8], dimensions[9], dimensions[6], dimensions[7]); + self.ctx.stroke(); + + /*/ draw the guideline + if (drawGuideline) { + self.ctx.save(); + self.ctx.beginPath(); + self.ctx.strokeStyle = "silver"; + self.ctx.lineWidth = 1; + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + self.ctx.restore(); + } + //*/ + } + else { + // a loopback connector + self.ctx.save(); + self.ctx.beginPath(); + var startAngle = 0, // Starting point on circle + endAngle = 2 * Math.PI, // End point on circle + clockwise = dimensions[7]; // clockwise or anticlockwise + self.ctx.arc(dimensions[4],dimensions[5],dimensions[6],0, endAngle, clockwise); + self.ctx.stroke(); + self.ctx.closePath(); + self.ctx.restore(); + } + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + /* + * SVG State Machine renderer + */ + jsPlumb.Connectors.svg.StateMachine = function() { + var self = this; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.SvgConnector.apply(this, arguments); + this.getPath = function(d) { + + if (d.length == 10) + return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[8] + " " + d[9] + " " + d[6] + " " + d[7]; + else { + // loopback + return "M" + (d[8] + 4) + " " + d[9] + " A " + d[6] + " " + d[6] + " 0 1,0 " + (d[8]-4) + " " + d[9]; + } + }; + }; + + /* + * VML state machine renderer + */ + jsPlumb.Connectors.vml.StateMachine = function() { + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.VmlConnector.apply(this, arguments); + var _conv = jsPlumb.vml.convertValue; + this.getPath = function(d) { + if (d.length == 10) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + } + else { + // loopback + var left = _conv(d[8] - d[6]), + top = _conv(d[9] - (2 * d[6])), + right = left + _conv(2 * d[6]), + bottom = top + _conv(2 * d[6]), + posString = left + "," + top + "," + right + "," + bottom; + + var o = "ar " + posString + "," + _conv(d[8]) + "," + + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + " e"; + + return o; + } + }; + }; + +})(); + +/* + // now for a rudimentary avoidance scheme. TODO: how to set this in a cross-library way? + // if (avoidSelector) { + // var testLine = new Line(sourcePos[0] + _sx,sourcePos[1] + _sy,sourcePos[0] + _tx,sourcePos[1] + _ty); + // var sel = jsPlumb.getSelector(avoidSelector); + // for (var i = 0; i < sel.length; i++) { + // var id = jsPlumb.getId(sel[i]); + // if (id != sourceEndpoint.elementId && id != targetEndpoint.elementId) { + // o = jsPlumb.getOffset(id), s = jsPlumb.getSize(id); +// +// if (o && s) { +// var collision = testLine.rectIntersect(o.left,o.top,s[0],s[1]); +// if (collision) { + // set the control point to be a certain distance from the midpoint of the two points that + // the line crosses on the rectangle. + // TODO where will this 75 number come from? + // _controlX = collision[2][0] + (75 * collision[3][0]); + // / _controlY = collision[2][1] + (75 * collision[3][1]); +// } +// } + // } + // } + //} + */ \ No newline at end of file diff --git a/archive/1.3.6/jsPlumb-defaults-1.3.6-RC1.js b/archive/1.3.6/jsPlumb-defaults-1.3.6-RC1.js new file mode 100644 index 000000000..a166df05f --- /dev/null +++ b/archive/1.3.6/jsPlumb-defaults-1.3.6-RC1.js @@ -0,0 +1,1119 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.6 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the default Connectors, Endpoint and Overlay definitions. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +(function() { + + /** + * + * Helper class to consume unused mouse events by components that are DOM elements and + * are used by all of the different rendering modes. + * + */ + jsPlumb.DOMElementComponent = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + // when render mode is canvas, these functions may be called by the canvas mouse handler. + // this component is safe to pipe this stuff to /dev/null. + this.mousemove = + this.dblclick = + this.click = + this.mousedown = + this.mouseup = function(e) { }; + }; + + /** + * Class: Connectors.Straight + * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters. + */ + jsPlumb.Connectors.Straight = function() { + this.type = "Straight"; + var self = this, + currentPoints = null, + _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty, _segment, _length; + + /** + * Computes the new size and position of the canvas. + */ + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + _dx = _tx - _sx, _dy = _ty - _sy; + //_m = _dy / _dx, _m2 = -1 / _m; + _m = jsPlumb.util.gradient({x:_sx, y:_sy}, {x:_tx, y:_ty}), _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + //_segment = jsPlumb.util.segment({x:_sx, y:_sy}, {x:_tx, y:_ty}); + _length = Math.sqrt((_dx * _dx) + (_dy * _dy)); + + return currentPoints; + }; + + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + if (location == 0) + return { x:_sx, y:_sy }; + else if (location == 1) + return { x:_tx, y:_ty }; + else + return jsPlumb.util.pointOnLine({x:_sx, y:_sy}, {x:_tx, y:_ty}, location * _length); + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { + return _m; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + * this hands off to jsPlumb.util to do the maths, supplying two points and the distance. + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location), + farAwayPoint = location == 1 ? { + x:_sx + ((_tx - _sx) * 10), + y:_sy + ((_sy - _ty) * 10) + } : {x:_tx, y:_ty }; + + return jsPlumb.util.pointOnLine(p, farAwayPoint, distance); + }; + }; + + + /** + * Class:Connectors.Bezier + * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's + * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below. + */ + /** + * Function:Constructor + * + * Parameters: + * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line. + * Optional; defaults to 150. + * stub - optional value for a distance to travel from the connector's endpoint before beginning the Bezier curve. defaults to 0. + * + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + params = params || {}; + this.majorAnchor = params.curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + this.type = "Bezier"; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(sourceEndpoint), + too = targetAnchor.getOrientation(targetEndpoint), + perpendicular = soo[0] != too[0] || soo[1] == too[1], + p = [], + ma = self.majorAnchor, mi = self.minorAnchor; + + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h, _sStubX, _sStubY, _tStubX, _tStubY; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, sourceEndpoint, targetEndpoint, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx), minx2 = Math.min(_CP[0], _CP2[0]), minx = Math.min(minx1,minx2), + maxx1 = Math.max(_sx,_tx), maxx2 = Math.max(_CP[0], _CP2[0]), maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty), miny2 = Math.min(_CP[1], _CP2[1]), miny = Math.min(miny1,miny2), + maxy1 = Math.max(_sy,_ty), maxy2 = Math.max(_CP[1], _CP2[1]), maxy = Math.max(maxy1,maxy2); + + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, + _sx, _sy, _tx, _ty, + _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + }; + + + /** + * Class: Connectors.Flowchart + * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments. + */ + /** + * Function: Constructor + * + * Parameters: + * stub - minimum length for the stub at each end of the connector. defaults to 30 pixels. + */ + jsPlumb.Connectors.Flowchart = function(params) { + this.type = "Flowchart"; + params = params || {}; + var self = this, + minStubLength = params.stub || params.minStubLength /* bwds compat. */ || 30, + segments = [], + totalLength = 0, + segmentProportions = [], + segmentProportionalLengths = [], + points = [], + swapX, swapY, + maxX = 0, maxY = 0, + /** + * recalculates the points at which the segments begin and end, proportional to the total length travelled + * by all the segments that constitute the connector. we use this to help with pointOnPath calculations. + */ + updateSegmentProportions = function(startX, startY, endX, endY) { + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segments[i][5] / totalLength; + segmentProportions[i] = [curLoc, (curLoc += (segments[i][5] / totalLength)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0], + ly = segments.length == 0 ? sy : segments[segments.length - 1][1], + m = x == lx ? Infinity : 0, + l = Math.abs(x == lx ? y - ly : x - lx); + segments.push([x, y, lx, ly, m, l]); + totalLength += l; + + maxX = Math.max(maxX, x); + maxY = Math.max(maxY, y); + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment + * that contains the point which is 'location' distance along the entire path, where + * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths + * are made up of a list of segments, each of which contributes some fraction to + * the total length. + */ + findSegmentForLocation = function(location) { + var idx = segmentProportions.length - 1, inSegmentProportion = 1; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, + sourceAnchor, targetAnchor, lineWidth, minWidth, sourceInfo, targetInfo) { + + segments = []; + totalLength = 0; + segmentProportionalLengths = []; + maxX = maxY = 0; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + offx = (lw / 2) + (minStubLength * 2), + offy = (lw / 2) + (minStubLength * 2), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(sourceEndpoint), + to = targetAnchor.orientation || targetAnchor.getOrientation(targetEndpoint), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx, + h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy; + + // if either anchor does not have an orientation set, we derive one from their relative + // positions. we fix the axis to be the one in which the two elements are further apart, and + // point each anchor at the other element. this is also used when dragging a new connection. + if (so[0] == 0 && so[1] == 0 || to[0] == 0 && to[1] == 0) { + var index = w > h ? 0 : 1, oIndex = [1,0][index]; + so = []; to = []; + so[index] = sourcePos[index] > targetPos[index] ? -1 : 1; + to[index] = sourcePos[index] > targetPos[index] ? 1 : -1; + so[oIndex] = 0; + to[oIndex] = 0; + } + + if (w < minWidth) { + offx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + offy += (minWidth - h) / 2; + h = minWidth; + } + + var sx = swapX ? w-offx : offx, + sy = swapY ? h-offy : offy, + tx = swapX ? offx : w-offx , + ty = swapY ? offy : h-offy, + startStubX = sx + (so[0] * minStubLength), + startStubY = sy + (so[1] * minStubLength), + endStubX = tx + (to[0] * minStubLength), + endStubY = ty + (to[1] * minStubLength), + isXGreaterThanStubTimes2 = Math.abs(sx - tx) > 2 * minStubLength, + isYGreaterThanStubTimes2 = Math.abs(sy - ty) > 2 * minStubLength, + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2), + oProduct = ((so[0] * to[0]) + (so[1] * to[1])), + opposite = oProduct == -1, + perpendicular = oProduct == 0, + orthogonal = oProduct == 1; + + x -= offx; y -= offy; + points = [x, y, w, h, sx, sy, tx, ty]; + var extraPoints = []; + + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + var sourceAxis = so[0] == 0 ? "y" : "x", + anchorOrientation = opposite ? "opposite" : orthogonal ? "orthogonal" : "perpendicular", + segment = jsPlumb.util.segment([sx, sy], [tx, ty]), + flipSourceSegments = so[sourceAxis == "x" ? 0 : 1] == -1, + flipSegments = { + "x":[null, 4, 3, 2, 1], + "y":[null, 2, 1, 4, 3] + } + + if (flipSourceSegments) + segment = flipSegments[sourceAxis][segment]; + + var findClearedLine = function(start, mult, anchorPos, dimension) { + return start + (mult * (( 1 - anchorPos) * dimension) + minStubLength); + //mx = so[0] == 0 ? startStubX + ((1 - sourceAnchor.x) * sourceInfo.width) + minStubLength : startStubX, + }, + + lineCalculators = { + oppositex : function() { + if (sourceEndpoint.elementId == targetEndpoint.elementId) { + var _y = startStubY + ((1 - sourceAnchor.y) * sourceInfo.height) + minStubLength; + return [ [ startStubX, _y ], [ endStubX, _y ]]; + } + else if (isXGreaterThanStubTimes2 && (segment == 1 || segment == 2)) { + return [[ midx, sy ], [ midx, ty ]]; + } + else { + return [[ startStubX, midy ], [endStubX, midy ]]; + } + }, + orthogonalx : function() { + if (segment == 1 || segment == 2) { + return [ [ endStubX, startStubY ]]; + } + else { + return [ [ startStubX, endStubY ]]; + } + }, + perpendicularx : function() { + var my = (ty + sy) / 2; + if ((segment == 1 && to[1] == 1) || (segment == 2 && to[1] == -1)) { + if (Math.abs(tx - sx) > minStubLength) + return [ [endStubX, startStubY ]]; + else + return [ [startStubX, startStubY ], [ startStubX, my ], [ endStubX, my ]]; + } + else if ((segment == 3 && to[1] == -1) || (segment == 4 && to[1] == 1)) { + return [ [ startStubX, my ], [ endStubX, my ]]; + } + else if ((segment == 3 && to[1] == 1) || (segment == 4 && to[1] == -1)) { + return [ [ startStubX, endStubY ]]; + } + else if ((segment == 1 && to[1] == -1) || (segment == 2 && to[1] == 1)) { + if (Math.abs(tx - sx) > minStubLength) + return [ [ midx, startStubY ], [ midx, endStubY ]]; + else + return [ [ startStubX, endStubY ]]; + } + }, + oppositey : function() { + if (sourceEndpoint.elementId == targetEndpoint.elementId) { + var _x = startStubX + ((1 - sourceAnchor.x) * sourceInfo.width) + minStubLength; + return [ [ _x, startStubY ], [ _x, endStubY ]]; + } + else if (isYGreaterThanStubTimes2 && (segment == 2 || segment == 3)) { + return [[ sx, midy ], [ tx, midy ]]; + } + else { + return [[ midx, startStubY ], [midx, endStubY ]]; + } + }, + orthogonaly : function() { + if (segment == 2 || segment == 3) { + return [ [ startStubX, endStubY ]]; + } + else { + return [ [ endStubX, startStubY ]]; + } + }, + perpendiculary : function() { + var mx = (tx + sx) / 2; + if ((segment == 2 && to[0] == -1) || (segment == 3 && to[0] == 1)) { + if (Math.abs(tx - sx) > minStubLength) + return [ [startStubX, endStubY ]]; + else + return [ [startStubX, midy ], [ endStubX, midy ]]; + } + else if ((segment == 1 && to[0] == -1) || (segment == 4 && to[0] == 1)) { + var mx = (tx + sx) / 2; + return [ [ mx, startStubY ], [ mx, endStubY ]]; + } + else if ((segment == 1 && to[0] == 1) || (segment == 4 && to[0] == -1)) { + return [ [ endStubX, startStubY ]]; + } + else if ((segment == 2 && to[0] == 1) || (segment == 3 && to[0] == -1)) { + if (Math.abs(ty - sy) > minStubLength) + return [ [ startStubX, midy ], [ endStubX, midy ]]; + else + return [ [ endStubX, startStubY ]]; + } + } + }; + + var p = lineCalculators[anchorOrientation + sourceAxis](); + if (p) { + for (var i = 0; i < p.length; i++) { + addSegment(p[i][0], p[i][1], sx, sy, tx, ty); + } + } + + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentProportions(sx, sy, tx, ty); + + // adjust the max values of the canvas if we have a value that is larger than what we previously set. + // + if (maxY > points[3]) points[3] = maxY + (lineWidth * 2); + if (maxX > points[2]) points[2] = maxX + (lineWidth * 2); + + return points; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location) { + return self.pointAlongPathFrom(location, 0); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location) { + return segments[findSegmentForLocation(location)["index"]][4]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance) { + var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segments[s.index][5], m = segments[s.index][4]; + var e = { + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + return e; + }; + }; + + // ********************************* END OF CONNECTOR TYPES ******************************************************************* + + // ********************************* ENDPOINT TYPES ******************************************************************* + + /** + * Class: Endpoints.Dot + * A round endpoint, with default radius 10 pixels. + */ + + /** + * Function: Constructor + * + * Parameters: + * + * radius - radius of the endpoint. defaults to 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + this.type = "Dot"; + var self = this; + params = params || {}; + this.radius = params.radius || 10; + this.defaultOffset = 0.5 * this.radius; + this.defaultInnerRadius = this.radius / 3; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var r = endpointStyle.radius || self.radius, + x = anchorPoint[0] - r, + y = anchorPoint[1] - r; + return [ x, y, r * 2, r * 2, r ]; + }; + }; + + /** + * Class: Endpoints.Rectangle + * A Rectangular Endpoint, with default size 20x20. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the endpoint. defaults to 20 pixels. + * height - height of the endpoint. defaults to 20 pixels. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + this.type = "Rectangle"; + var self = this; + params = params || {}; + this.width = params.width || 20; + this.height = params.height || 20; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height]; + }; + }; + + + var DOMElementEndpoint = function(params) { + jsPlumb.DOMElementComponent.apply(this, arguments); + var self = this; + + var displayElements = [ ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + }; + /** + * Class: Endpoints.Image + * Draws an image as the Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * src - location of the image to use. + */ + jsPlumb.Endpoints.Image = function(params) { + + this.type = "Image"; + DOMElementEndpoint.apply(this, arguments); + + var self = this, + initialized = false, + widthToUse = params.width, + heightToUse = params.height, + _onload = null, + _endpoint = params.endpoint; + + this.img = new Image(); + self.ready = false; + + this.img.onload = function() { + self.ready = true; + widthToUse = widthToUse || self.img.width; + heightToUse = heightToUse || self.img.height; + if (_onload) { + _onload(self); + } + }; + + _endpoint.setImage = function(img, onload) { + var s = img.constructor == String ? img : img.src; + _onload = onload; + self.img.src = img; + }; + + _endpoint.setImage(params.src || params.url, params.onload); + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + self.anchorPoint = anchorPoint; + if (self.ready) return [anchorPoint[0] - widthToUse / 2, anchorPoint[1] - heightToUse / 2, + widthToUse, heightToUse]; + else return [0,0,0,0]; + }; + + self.canvas = document.createElement("img"), initialized = false; + self.canvas.style["margin"] = 0; + self.canvas.style["padding"] = 0; + self.canvas.style["outline"] = 0; + self.canvas.style["position"] = "absolute"; + var clazz = params.cssClass ? " " + params.cssClass : ""; + self.canvas.className = jsPlumb.endpointClass + clazz; + if (widthToUse) self.canvas.setAttribute("width", widthToUse); + if (heightToUse) self.canvas.setAttribute("height", heightToUse); + jsPlumb.appendElement(self.canvas, params.parent); + self.attachListeners(self.canvas, self); + + var actuallyPaint = function(d, style, anchor) { + if (!initialized) { + self.canvas.setAttribute("src", self.img.src); + initialized = true; + } + var x = self.anchorPoint[0] - (widthToUse / 2), + y = self.anchorPoint[1] - (heightToUse / 2); + jsPlumb.sizeCanvas(self.canvas, x, y, widthToUse, heightToUse); + }; + + this.paint = function(d, style, anchor) { + if (self.ready) { + actuallyPaint(d, style, anchor); + } + else { + window.setTimeout(function() { + self.paint(d, style, anchor); + }, 200); + } + }; + }; + + /** + * Class: Endpoints.Blank + * An Endpoint that paints nothing (visible) on the screen. Supports cssClass and hoverClass parameters like all Endpoints. + */ + jsPlumb.Endpoints.Blank = function(params) { + var self = this; + this.type = "Blank"; + DOMElementEndpoint.apply(this, arguments); + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + return [anchorPoint[0], anchorPoint[1],10,0]; + }; + + self.canvas = document.createElement("div"); + self.canvas.style.display = "block"; + self.canvas.style.width = "1px"; + self.canvas.style.height = "1px"; + self.canvas.style.background = "transparent"; + self.canvas.style.position = "absolute"; + self.canvas.className = self._jsPlumb.endpointClass; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + }; + }; + + /** + * Class: Endpoints.Triangle + * A triangular Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the triangle's base. defaults to 55 pixels. + * height - height of the triangle from base to apex. defaults to 55 pixels. + */ + jsPlumb.Endpoints.Triangle = function(params) { + this.type = "Triangle"; + params = params || { }; + params.width = params.width || 55; + params.height = params.height || 55; + this.width = params.width; + this.height = params.height; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height ]; + }; + }; +// ********************************* END OF ENDPOINT TYPES ******************************************************************* + + +// ********************************* OVERLAY DEFINITIONS *********************************************************************** + + var AbstractOverlay = function(params) { + var visible = true, self = this; + this.isAppendedAtTopLevel = true; + this.component = params.component; + this.loc = params.location == null ? 0.5 : params.location; + this.endpointLoc = params.endpointLocation == null ? [ 0.5, 0.5] : params.endpointLocation; + this.setVisible = function(val) { + visible = val; + self.component.repaint(); + }; + this.isVisible = function() { return visible; }; + this.hide = function() { self.setVisible(false); }; + this.show = function() { self.setVisible(true); }; + + this.incrementLocation = function(amount) { + self.loc += amount; + self.component.repaint(); + }; + this.setLocation = function(l) { + self.loc = l; + self.component.repaint(); + }; + this.getLocation = function() { + return self.loc; + }; + }; + + + /** + * Class: Overlays.Arrow + * + * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length + * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction + * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line + * across the tail. + */ + /** + * Function: Constructor + * + * Parameters: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + this.type = "Arrow"; + AbstractOverlay.apply(this, arguments); + this.isAppendedAtTopLevel = false; + params = params || {}; + var self = this; + + this.length = params.length || 20; + this.width = params.width || 20; + this.id = params.id; + var direction = (params.direction || 1) < 0 ? -1 : 1, + paintStyle = params.paintStyle || { lineWidth:1 }, + // how far along the arrow the lines folding back in come to. default is 62.3%. + foldback = params.foldback || 0.623; + + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.cleanup = function() { }; // nothing to clean up for Arrows + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var hxy, mid, txy, tail, cxy; + if (connector.pointAlongPathFrom) { + + if (self.loc == 1) { + hxy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, -1); + txy = jsPlumb.util.pointOnLine(hxy, mid, self.length); + } + else if (self.loc == 0) { + txy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, 1); + hxy = jsPlumb.util.pointOnLine(txy, mid, self.length); + } + else { + hxy = connector.pointAlongPathFrom(self.loc, direction * self.length / 2), + mid = connector.pointOnPath(self.loc), + txy = jsPlumb.util.pointOnLine(hxy, mid, self.length); + } + + tail = jsPlumb.util.perpendicularLineTo(hxy, txy, self.width); + cxy = jsPlumb.util.pointOnLine(hxy, txy, foldback * self.length); + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x), + maxx = Math.max(hxy.x, tail[0].x, tail[1].x), + miny = Math.min(hxy.y, tail[0].y, tail[1].y), + maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + var d = { hxy:hxy, tail:tail, cxy:cxy }, + strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle, + fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle, + lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth; + + self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions); + + return [ minx, maxx, miny, maxy]; + } + else return [0,0,0,0]; + }; + }; + + /** + * Class: Overlays.PlainArrow + * + * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "PlainArrow"; + }; + + /** + * Class: Overlays.Diamond + * + * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40, + p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "Diamond"; + }; + + + + /** + * Class: Overlays.Label + * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb + * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter + * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it + * puts on the Label's 'style' attribute, so the end result is the same. + */ + /** + * Function: Constructor + * + * Parameters: + * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes + * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle. + * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * + */ + jsPlumb.Overlays.Label = function(params) { + this.type = "Label"; + jsPlumb.DOMElementComponent.apply(this, arguments); + AbstractOverlay.apply(this, arguments); + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.id = params.id; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var label = params.label || "", + self = this, + initialised = false, + div = document.createElement("div"), + labelText = null; + div.style["position"] = "absolute"; + + var clazz = params["_jsPlumb"].overlayClass + " " + + (self.labelStyle.cssClass ? self.labelStyle.cssClass : + params.cssClass ? params.cssClass : ""); + + div.className = clazz; + + jsPlumb.appendElement(div, params.component.parent); + jsPlumb.getId(div); + self.attachListeners(div, self); + self.canvas = div; + + //override setVisible + var osv = self.setVisible; + self.setVisible = function(state) { + osv(state); // call superclass + div.style.display = state ? "block" : "none"; + }; + + this.getElement = function() { + return div; + }; + + this.cleanup = function() { + if (div != null) jsPlumb.CurrentLibrary.removeElement(div); + }; + + /* + * Function: setLabel + * sets the label's, um, label. you would think i'd call this function + * 'setText', but you can pass either a Function or a String to this, so + * it makes more sense as 'setLabel'. + */ + this.setLabel = function(l) { + label = l; + labelText = null; + self.component.repaint(); + }; + + this.getLabel = function() { + return label; + }; + + this.paint = function(component, d, componentDimensions) { + if (!initialised) { + component.appendDisplayElement(div); + self.attachListeners(div, component); + initialised = true; + } + div.style.left = (componentDimensions[0] + d.minx) + "px"; + div.style.top = (componentDimensions[1] + d.miny) + "px"; + }; + + this.getTextDimensions = function() { + if (typeof label == "function") { + var lt = label(self); + div.innerHTML = lt.replace(/\r\n/g, "
"); + } + else { + if (labelText == null) { + labelText = label; + div.innerHTML = labelText.replace(/\r\n/g, "
"); + } + } + var de = jsPlumb.CurrentLibrary.getElementObject(div), + s = jsPlumb.CurrentLibrary.getSize(de); + return {width:s[0], height:s[1]}; + }; + + this.computeMaxSize = function(connector) { + var td = self.getTextDimensions(connector); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + + this.draw = function(component, currentConnectionPaintStyle, componentDimensions) { + var td = self.getTextDimensions(component); + if (td.width != null) { + var cxy = {x:0,y:0}; + if (component.pointOnPath) + cxy = component.pointOnPath(self.loc); // a connection + else { + var locToUse = self.loc.constructor == Array ? self.loc : self.endpointLoc; + cxy = { x:locToUse[0] * componentDimensions[2], + y:locToUse[1] * componentDimensions[3] }; + } + + minx = cxy.x - (td.width / 2), + miny = cxy.y - (td.height / 2); + + self.paint(component, { + minx:minx, + miny:miny, + td:td, + cxy:cxy + }, componentDimensions); + + return [minx, minx+td.width, miny, miny+td.height]; + } + else return [0,0,0,0]; + }; + + this.reattachListeners = function(connector) { + if (div) { + self.reattachListenersForElement(div, self, connector); + } + }; + }; + + // this is really just a test overlay, so its undocumented and doesnt take any parameters. but i was loth to delete it. + jsPlumb.Overlays.GuideLines = function() { + var self = this; + self.length = 50; + self.lineWidth = 5; + this.type = "GuideLines"; + AbstractOverlay.apply(this, arguments); + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var head = connector.pointAlongPathFrom(self.loc, self.length / 2), + mid = connector.pointOnPath(self.loc), + tail = jsPlumb.util.pointOnLine(head, mid, self.length), + tailLine = jsPlumb.util.perpendicularLineTo(head, tail, 40), + headLine = jsPlumb.util.perpendicularLineTo(tail, head, 20); + + self.paint(connector, [head, tail, tailLine, headLine], self.lineWidth, "red", null, connectorDimensions); + + return [Math.min(head.x, tail.x), Math.min(head.y, tail.y), Math.max(head.x, tail.x), Math.max(head.y,tail.y)]; + }; + + this.computeMaxSize = function() { return 50; }; + + this.cleanup = function() { }; // nothing to clean up for GuideLines + }; + + // ********************************* END OF OVERLAY DEFINITIONS *********************************************************************** + + // ********************************* OVERLAY CANVAS RENDERERS*********************************************************************** + + // ********************************* END OF OVERLAY CANVAS RENDERERS *********************************************************************** +})(); \ No newline at end of file diff --git a/archive/1.3.6/jsPlumb-renderers-canvas-1.3.6-RC1.js b/archive/1.3.6/jsPlumb-renderers-canvas-1.3.6-RC1.js new file mode 100644 index 000000000..e6a0136a4 --- /dev/null +++ b/archive/1.3.6/jsPlumb-renderers-canvas-1.3.6-RC1.js @@ -0,0 +1,456 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.6 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the HTML5 canvas renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + +// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS ******************************************************************* + + // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. + var _connectionBeingDragged = null, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, + _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }; + + /* + * Class:CanvasMouseAdapter + * Provides support for mouse events on canvases. + */ + var CanvasMouseAdapter = function() { + var self = this; + self.overlayPlacements = []; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + jsPlumb.EventGenerator.apply(this, arguments); + /** + * returns whether or not the given event is ojver a painted area of the canvas. + */ + this._over = function(e) { + var o = _getOffset(_getElementObject(self.canvas)), + pageXY = _pageXY(e), + x = pageXY[0] - o.left, y = pageXY[1] - o.top; + if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + return false; + }; + + var _mouseover = false, _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false, + _nullSafeHasClass = function(el, clazz) { + return el != null && _hasClass(el, clazz); + }; + this.mousemove = function(e) { + var pageXY = _pageXY(e), clientXY = _clientXY(e), + ee = document.elementFromPoint(clientXY[0], clientXY[1]), + eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); + var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); + if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.fire("mouseenter", self, e); + return true; + } + // TODO here there is a remote chance that the overlay the mouse moved onto + // is actually not an overlay for the current component. a more thorough check would + // be to ensure the overlay belonged to the current component. + else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { + _mouseover = false; + self.fire("mouseexit", self, e); + } + self.fire("mousemove", self, e); + }; + + this.click = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); + self.fire("mousedown", self, e); + } + }; + + this.mouseup = function(e) { + _mouseDown = false; + self.fire("mouseup", self, e); + }; + + this.contextmenu = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("contextmenu", self, e); + _mouseWasDown = false; + }; + }; + + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + params["_jsPlumb"].appendElement(canvas, params.parent); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + params["_jsPlumb"].getId(canvas, params.uuid); + if (params.tooltip) canvas.setAttribute("title", params.tooltip); + + return canvas; + }; + + var CanvasComponent = function(params) { + CanvasMouseAdapter.apply(this, arguments); + + var displayElements = [ ]; + this.getDisplayElements = function() { return displayElements; }; + this.appendDisplayElement = function(el) { displayElements.push(el); }; + } + + /** + * Class:CanvasConnector + * Superclass for Canvas Connector renderers. + */ + var CanvasConnector = jsPlumb.CanvasConnector = function(params) { + + CanvasComponent.apply(this, arguments); + + var _paintOneStyle = function(dim, aStyle) { + self.ctx.save(); + jsPlumb.extend(self.ctx, aStyle); + if (aStyle.gradient) { + var g = self.createGradient(dim, self.ctx); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + self.ctx.strokeStyle = g; + } + self._paint(dim); + self.ctx.restore(); + }; + + var self = this, + clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:params.tooltip + }); + self.ctx = self.canvas.getContext("2d"); + + self.appendDisplayElement(self.canvas); + + self.paint = function(dim, style) { + if (style != null) { + jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + _paintOneStyle(dim, outlineStyle); + } + _paintOneStyle(dim, style); + } + }; + }; + + /** + * Class:CanvasEndpoint + * Superclass for Canvas Endpoint renderers. + */ + var CanvasEndpoint = function(params) { + var self = this; + CanvasComponent.apply(this, arguments); + var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""), + canvasParams = { + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:self.tooltip + }; + self.canvas = _newCanvas(canvasParams); + self.ctx = self.canvas.getContext("2d"); + + self.appendDisplayElement(self.canvas); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + } + + self._paint.apply(this, arguments); + }; + }; + + jsPlumb.Endpoints.canvas.Dot = function(params) { + jsPlumb.Endpoints.Dot.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + var self = this, + parseValue = function(value) { + try { return parseInt(value); } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }, + calculateAdjustments = function(gradient) { + var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius; + gradient.offset && (offsetAdjustment = parseValue(gradient.offset)); + gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius)); + return [offsetAdjustment, innerRadius]; + }; + this._paint = function(d, style, anchor) { + if (style != null) { + var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + if (style.gradient) { + var adjustments = calculateAdjustments(style.gradient), + yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0], + xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0], + g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + ctx.beginPath(); + ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + } + }; + }; + + jsPlumb.Endpoints.canvas.Rectangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) { + + var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + + /* canvas gradient */ + if (style.gradient) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, d[2], d[3]); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + jsPlumb.Endpoints.canvas.Triangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Triangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) + { + var width = d[2], height = d[3], x = d[0], y = d[1], + ctx = self.canvas.getContext('2d'), + offsetX = 0, offsetY = 0, angle = 0, + orientation = anchor.getOrientation(self); + + if( orientation[0] == 1 ) { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = style.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + /* + * Canvas Image Endpoint: uses the default version, which creates an tag. + */ + jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image; + + /* + * Blank endpoint in all renderers is just the default Blank endpoint. + */ + jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank; + + /* + * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Bezier = function() { + var self = this; + jsPlumb.Connectors.Bezier.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO i doubt this handles the case that source and target are swapped. + this.createGradient = function(dim, ctx, swap) { + return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + /* + * Canvas straight line Connector. Draws a straight line onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Straight = function() { + var self = this; + jsPlumb.Connectors.Straight.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO this does not handle the case that src and target are swapped. + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + jsPlumb.Connectors.canvas.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + +// ********************************* END OF CANVAS RENDERERS ******************************************************************* + + jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label; + + /** + * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. + */ + var CanvasOverlay = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }; + + var AbstractCanvasArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + CanvasOverlay.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + var ctx = connector.ctx; + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(d.hxy.x, d.hxy.y); + ctx.lineTo(d.tail[0].x, d.tail[0].y); + ctx.lineTo(d.cxy.x, d.cxy.y); + ctx.lineTo(d.tail[1].x, d.tail[1].y); + ctx.lineTo(d.hxy.x, d.hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + }; + + jsPlumb.Overlays.canvas.Arrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.canvas.PlainArrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.canvas.Diamond = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.6/jsPlumb-renderers-svg-1.3.6-RC1.js b/archive/1.3.6/jsPlumb-renderers-svg-1.3.6-RC1.js new file mode 100644 index 000000000..8aab14a7d --- /dev/null +++ b/archive/1.3.6/jsPlumb-renderers-svg-1.3.6-RC1.js @@ -0,0 +1,539 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.6 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the SVG renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +/** + * SVG support for jsPlumb. + * + * things to investigate: + * + * gradients: https://developer.mozilla.org/en/svg_in_html_introduction + * css:http://tutorials.jenkov.com/svg/svg-and-css.html + * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath + * pointer events: https://developer.mozilla.org/en/css/pointer-events + * + * IE9 hover jquery: http://forum.jquery.com/topic/1-6-2-broke-svg-hover-events + * + */ +;(function() { + + var svgAttributeMap = { + "joinstyle":"stroke-linejoin", + "stroke-linejoin":"stroke-linejoin", + "stroke-dashoffset":"stroke-dashoffset", + "stroke-linecap":"stroke-linecap" + }, + STROKE_DASHARRAY = "stroke-dasharray", + DASHSTYLE = "dashstyle", + LINEAR_GRADIENT = "linearGradient", + RADIAL_GRADIENT = "radialGradient", + FILL = "fill", + STOP = "stop", + STROKE = "stroke", + STROKE_WIDTH = "stroke-width", + STYLE = "style", + NONE = "none", + JSPLUMB_GRADIENT = "jsplumb_gradient_", + LINE_WIDTH = "lineWidth", + ns = { + svg:"http://www.w3.org/2000/svg", + xhtml:"http://www.w3.org/1999/xhtml" + }, + _attr = function(node, attributes) { + for (var i in attributes) + node.setAttribute(i, "" + attributes[i]); + }, + _node = function(name, attributes) { + var n = document.createElementNS(ns.svg, name); + attributes = attributes || {}; + attributes["version"] = "1.1"; + attributes["xmlns"] = ns.xhtml; + _attr(n, attributes); + return n; + }, + _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; }, + _clearGradient = function(parent) { + for (var i = 0; i < parent.childNodes.length; i++) { + if (parent.childNodes[i].tagName == LINEAR_GRADIENT || parent.childNodes[i].tagName == RADIAL_GRADIENT) + parent.removeChild(parent.childNodes[i]); + } + }, + _updateGradient = function(parent, node, style, dimensions, uiComponent) { + var id = JSPLUMB_GRADIENT + uiComponent._jsPlumb.idstamp(); + // first clear out any existing gradient + _clearGradient(parent); + // this checks for an 'offset' property in the gradient, and in the absence of it, assumes + // we want a linear gradient. if it's there, we create a radial gradient. + // it is possible that a more explicit means of defining the gradient type would be + // better. relying on 'offset' means that we can never have a radial gradient that uses + // some default offset, for instance. + if (!style.gradient.offset) { + var g = _node(LINEAR_GRADIENT, {id:id}); + parent.appendChild(g); + } + else { + var g = _node(RADIAL_GRADIENT, { + id:id + }); + parent.appendChild(g); + } + + // the svg radial gradient seems to treat stops in the reverse + // order to how canvas does it. so we want to keep all the maths the same, but + // iterate the actual style declarations in reverse order, if the x indexes are not in order. + for (var i = 0; i < style.gradient.stops.length; i++) { + // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of + // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments. + // so, not too concerned about leaving this in for now. + var styleToUse = i; + if (dimensions.length == 8) + styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i; + else + styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i; + var stopColor = jsPlumb.util.convertStyle(style.gradient.stops[styleToUse][1], true); + var s = _node(STOP, {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor}); + g.appendChild(s); + } + var applyGradientTo = style.strokeStyle ? STROKE : FILL; + node.setAttribute(STYLE, applyGradientTo + ":url(#" + id + ")"); + }, + _applyStyles = function(parent, node, style, dimensions, uiComponent) { + + if (style.gradient) { + _updateGradient(parent, node, style, dimensions, uiComponent); + } + else { + // make sure we clear any existing gradient + _clearGradient(parent); + node.setAttribute(STYLE, ""); + } + + node.setAttribute(FILL, style.fillStyle ? jsPlumb.util.convertStyle(style.fillStyle, true) : NONE); + node.setAttribute(STROKE, style.strokeStyle ? jsPlumb.util.convertStyle(style.strokeStyle, true) : NONE); + if (style.lineWidth) { + node.setAttribute(STROKE_WIDTH, style.lineWidth); + } + + // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like + // the syntax in VML but is actually kind of nasty: values are given in the pixel + // coordinate space, whereas in VML they are multiples of the width of the stroked + // line, which makes a lot more sense. for that reason, jsPlumb is supporting both + // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from + // VML, which will be the preferred method. the code below this converts a dashstyle + // attribute given in terms of stroke width into a pixel representation, by using the + // stroke's lineWidth. + if (style[DASHSTYLE] && style[LINE_WIDTH] && !style[STROKE_DASHARRAY]) { + var sep = style[DASHSTYLE].indexOf(",") == -1 ? " " : ",", + parts = style[DASHSTYLE].split(sep), + styleToUse = ""; + parts.forEach(function(p) { + styleToUse += (Math.floor(p * style.lineWidth) + sep); + }); + node.setAttribute(STROKE_DASHARRAY, styleToUse); + } + else if(style[STROKE_DASHARRAY]) { + node.setAttribute(STROKE_DASHARRAY, style[STROKE_DASHARRAY]); + } + + // extra attributes such as join type, dash offset. + for (var i in svgAttributeMap) { + if (style[i]) { + node.setAttribute(svgAttributeMap[i], style[i]); + } + } + }, + _decodeFont = function(f) { + var r = /([0-9].)(p[xt])\s(.*)/; + var bits = f.match(r); + return {size:bits[1] + bits[2], font:bits[3]}; + }, + _classManip = function(el, add, clazz) { + var classesToAddOrRemove = clazz.split(" "), + className = el.className, + curClasses = className.baseVal.split(" "); + + for (var i = 0; i < classesToAddOrRemove.length; i++) { + if (add) { + if (curClasses.indexOf(classesToAddOrRemove[i]) == -1) + curClasses.push(classesToAddOrRemove[i]); + } + else { + var idx = curClasses.indexOf(classesToAddOrRemove[i]); + if (idx != -1) + curClasses.splice(idx, 1); + } + } + + el.className.baseVal = curClasses.join(" "); + }, + _addClass = function(el, clazz) { + _classManip(el, true, clazz); + }, + _removeClass = function(el, clazz) { + _classManip(el, false, clazz); + }; + + /** + utility methods for other objects to use. + */ + jsPlumb.util.svg = { + addClass:_addClass, + removeClass:_removeClass + }; + + /* + * Base class for SVG components. + */ + //var SvgComponent = function(cssClass, originalArgs, pointerEventsSpec) { + var SvgComponent = function(params) { + var self = this, + pointerEventsSpec = params.pointerEventsSpec || "all"; + jsPlumb.jsPlumbUIComponent.apply(this, params.originalArgs); + self.canvas = null, self.path = null, self.svg = null; + + var clazz = params.cssClass + " " + (params.originalArgs[0].cssClass || ""), + svgParams = { + "style":"", + "width":0, + "height":0, + "pointer-events":pointerEventsSpec, + "position":"absolute" + }; + if (self.tooltip) svgParams["title"] = self.tooltip; + self.svg = _node("svg", svgParams); + if (params.useDivWrapper) { + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.sizeCanvas(self.canvas,0,0,1,1); + self.canvas.className = clazz; + if (self.tooltip) self.canvas.setAttribute("title", self.tooltip); + } + else { + _attr(self.svg, { "class":clazz }); + self.canvas = self.svg; + } + + params._jsPlumb.appendElement(self.canvas, params.originalArgs[0]["parent"]); + if (params.useDivWrapper) self.canvas.appendChild(self.svg); + + // TODO this displayElement stuff is common between all components, across all + // renderers. would be best moved to jsPlumbUIComponent. + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + this.paint = function(d, style, anchor) { + if (style != null) { + var x = d[0], y = d[1]; + if (params.useDivWrapper) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + x = 0, y = 0; + } + _attr(self.svg, { + "style":_pos([x, y, d[2], d[3]]), + "width": d[2], + "height": d[3] + }); + self._paint.apply(this, arguments); + } + }; + }; + + /* + * Base class for SVG connectors. + */ + var SvgConnector = jsPlumb.SvgConnector = function(params) { + var self = this; + SvgComponent.apply(this, [ { + cssClass:params["_jsPlumb"].connectorClass, + originalArgs:arguments, + pointerEventsSpec:"none", + tooltip:params.tooltip, + _jsPlumb:params["_jsPlumb"] + } ]); + this._paint = function(d, style) { + var p = self.getPath(d), a = { "d":p }, outlineStyle = null; + a["pointer-events"] = "all"; + + // outline style. actually means drawing an svg object underneath the main one. + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = jsPlumb.CurrentLibrary.extend({}, style); + outlineStyle.strokeStyle = jsPlumb.util.convertStyle(style.outlineColor); + outlineStyle.lineWidth = outlineStrokeWidth; + + if (self.bgPath == null) { + self.bgPath = _node("path", a); + self.svg.appendChild(self.bgPath); + self.attachListeners(self.bgPath, self); + } + else { + _attr(self.bgPath, a); + } + + _applyStyles(self.svg, self.bgPath, outlineStyle, d, self); + } + + + // test - see below + // a["clip-path"]= "url(#testClip)"; + + if (self.path == null) { + self.path = _node("path", a); + self.svg.appendChild(self.path); + self.attachListeners(self.path, self); + + /* + this is a test of a clip path. i'm looking into using one of these to animate a jsplumb connection. + you could do this by walking along the line, stepping along a little at a time, and setting the clip + path to extend as far as that point. + + self.clip = _node("clipPath", {id:"testClip", clipPathUnits:"objectBoundingBox"}); + self.svg.appendChild(self.clip); + self.clip.appendChild(_node("rect", { + x:"0",y:"0",width:"0.5",height:"1" + })); + */ + } + else { + _attr(self.path, a); + } + + _applyStyles(self.svg, self.path, style, d, self); + }; + + this.reattachListeners = function() { + if (self.bgPath) self.reattachListenersForElement(self.bgPath, self); + if (self.path) self.reattachListenersForElement(self.path, self); + }; + + }; + + /* + * SVG Bezier Connector + */ + jsPlumb.Connectors.svg.Bezier = function(params) { + jsPlumb.Connectors.Bezier.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { + var _p = "M " + d[4] + " " + d[5]; + _p += (" C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]); + return _p; + }; + }; + + /* + * SVG straight line Connector + */ + jsPlumb.Connectors.svg.Straight = function(params) { + jsPlumb.Connectors.Straight.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; }; + }; + + jsPlumb.Connectors.svg.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "M " + dimensions[4] + "," + dimensions[5]; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)]; + } + // finally draw a line to the end + p = p + " " + dimensions[6] + "," + dimensions[7]; + return p; + }; + }; + + /* + * Base class for SVG endpoints. + */ + var SvgEndpoint = function(params) { + var self = this; + SvgComponent.apply(this, [ { + cssClass:params["_jsPlumb"].endpointClass, + originalArgs:arguments, + pointerEventsSpec:"all", + useDivWrapper:true, + _jsPlumb:params["_jsPlumb"] + } ]); + this._paint = function(d, style) { + var s = jsPlumb.extend({}, style); + if (s.outlineColor) { + s.strokeWidth = s.outlineWidth; + s.strokeStyle = jsPlumb.util.convertStyle(s.outlineColor, true); + } + + if (self.node == null) { + self.node = self.makeNode(d, s); + self.svg.appendChild(self.node); + self.attachListeners(self.node, self); + } + _applyStyles(self.svg, self.node, s, d, self); + _pos(self.node, d); + }; + + this.reattachListeners = function() { + if (self.node) self.reattachListenersForElement(self.node, self); + }; + }; + + /* + * SVG Dot Endpoint + */ + jsPlumb.Endpoints.svg.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("circle", { + "cx" : d[2] / 2, + "cy" : d[3] / 2, + "r" : d[2] / 2 + }); + }; + }; + + /* + * SVG Rectangle Endpoint + */ + jsPlumb.Endpoints.svg.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("rect", { + "width":d[2], + "height":d[3] + }); + }; + }; + + /* + * SVG Image Endpoint is the default image endpoint. + */ + jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image; + /* + * Blank endpoint in svg renderer is the default Blank endpoint. + */ + jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank; + /* + * Label endpoint in svg renderer is the default Label endpoint. + */ + jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label; + + var AbstractSvgArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + this.isAppendedAtTopLevel = false; + var self = this, path =null; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + } + var clazz = originalArgs && (originalArgs.length == 1) ? (originalArgs[0].cssClass || "") : ""; + + _attr(path, { + "d" : makePath(d), + "class" : clazz, + stroke : strokeStyle ? strokeStyle : null, + fill : fillStyle ? fillStyle : null + }); + }; + var makePath = function(d) { + return "M" + d.hxy.x + "," + d.hxy.y + + " L" + d.tail[0].x + "," + d.tail[0].y + + " L" + d.cxy.x + "," + d.cxy.y + + " L" + d.tail[1].x + "," + d.tail[1].y + + " L" + d.hxy.x + "," + d.hxy.y; + }; + this.reattachListeners = function() { + if (path) self.reattachListenersForElement(path, self); + }; + }; + + jsPlumb.Overlays.svg.Arrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.svg.PlainArrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.svg.Diamond = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; + + // a test + jsPlumb.Overlays.svg.GuideLines = function() { + var path = null, self = this, path2 = null, p1_1, p1_2; + jsPlumb.Overlays.GuideLines.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + + p1_1 = _node("path"); + connector.svg.appendChild(p1_1); + self.attachListeners(p1_1, connector); + self.attachListeners(p1_1, self); + + p1_2 = _node("path"); + connector.svg.appendChild(p1_2); + self.attachListeners(p1_2, connector); + self.attachListeners(p1_2, self); + + } + + _attr(path, { + "d" : makePath(d[0], d[1]), + stroke : "red", + fill : null + }); + + _attr(p1_1, { + "d" : makePath(d[2][0], d[2][1]), + stroke : "blue", + fill : null + }); + + _attr(p1_2, { + "d" : makePath(d[3][0], d[3][1]), + stroke : "green", + fill : null + }); + }; + + var makePath = function(d1, d2) { + return "M " + d1.x + "," + d1.y + + " L" + d2.x + "," + d2.y; + }; + + }; +})(); \ No newline at end of file diff --git a/archive/1.3.6/jsPlumb-renderers-vml-1.3.6-RC1.js b/archive/1.3.6/jsPlumb-renderers-vml-1.3.6-RC1.js new file mode 100644 index 000000000..ff26ff79e --- /dev/null +++ b/archive/1.3.6/jsPlumb-renderers-vml-1.3.6-RC1.js @@ -0,0 +1,435 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.6 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the VML renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + // http://ajaxian.com/archives/the-vml-changes-in-ie-8 + // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/ + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + + var vmlAttributeMap = { + "stroke-linejoin":"joinstyle", + "joinstyle":"joinstyle", + "endcap":"endcap", + "miterlimit":"miterlimit" + }; + + if (document.createStyleSheet) { + + // this is the style rule for IE7/6: it uses a CSS class, tidy. + document.createStyleSheet().addRule(".jsplumb_vml", "behavior:url(#default#VML);position:absolute;"); + + // these are for VML in IE8. you have to explicitly call out which elements + // you're going to expect to support VML! + // + document.createStyleSheet().addRule("jsplumb\\:textbox", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:oval", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:rect", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:stroke", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:shape", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:group", "behavior:url(#default#VML);position:absolute;"); + + // in this page it is also mentioned that IE requires the extra arg to the namespace + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either. + // var iev = document.documentMode; + //if (!iev || iev < 8) + document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); + //else + // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML"); + } + + jsPlumb.vml = {}; + + var scale = 1000, + + _groupMap = {}, + _getGroup = function(container, connectorClass) { + var id = jsPlumb.getId(container), + g = _groupMap[id]; + if(!g) { + g = _node("group", [0,0,scale, scale], {"class":connectorClass}); + //g.style.position=absolute; + //g["coordsize"] = "1000,1000"; + g.style.backgroundColor="red"; + _groupMap[id] = g; + jsPlumb.appendElement(g, container); // todo if this gets reinstated, remember to use the current jsplumb instance. + //document.body.appendChild(g); + } + return g; + }, + _atts = function(o, atts) { + for (var i in atts) { + // IE8 fix: setattribute does not work after an element has been added to the dom! + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + //o.setAttribute(i, atts[i]); + + o[i] = atts[i]; + } + }, + _node = function(name, d, atts) { + atts = atts || {}; + var o = document.createElement("jsplumb:" + name); + o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml"; + _pos(o, d); + _atts(o, atts); + return o; + }, + _pos = function(o,d) { + o.style.left = d[0] + "px"; + o.style.top = d[1] + "px"; + o.style.width= d[2] + "px"; + o.style.height= d[3] + "px"; + o.style.position = "absolute"; + }, + _conv = jsPlumb.vml.convertValue = function(v) { + return Math.floor(v * scale); + }, + // tests if the given style is "transparent" and then sets the appropriate opacity node to 0 if so, + // or 1 if not. TODO in the future, support variable opacity. + _maybeSetOpacity = function(styleToWrite, styleToCheck, type, component) { + if ("transparent" === styleToCheck) + component.setOpacity(type, "0.0"); + else + component.setOpacity(type, "1.0"); + }, + _applyStyles = function(node, style, component) { + var styleToWrite = {}; + if (style.strokeStyle) { + styleToWrite["stroked"] = "true"; + var strokeColor = jsPlumb.util.convertStyle(style.strokeStyle, true); + styleToWrite["strokecolor"] = strokeColor; + _maybeSetOpacity(styleToWrite, strokeColor, "stroke", component); + styleToWrite["strokeweight"] = style.lineWidth + "px"; + } + else styleToWrite["stroked"] = "false"; + + if (style.fillStyle) { + styleToWrite["filled"] = "true"; + var fillColor = jsPlumb.util.convertStyle(style.fillStyle, true); + styleToWrite["fillcolor"] = fillColor; + _maybeSetOpacity(styleToWrite, fillColor, "fill", component); + } + else styleToWrite["filled"] = "false"; + + if(style["dashstyle"]) { + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = style["dashstyle"]; + } + else if (style["stroke-dasharray"] && style["lineWidth"]) { + var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",", + parts = style["stroke-dasharray"].split(sep), + styleToUse = ""; + for(var i = 0; i < parts.length; i++) { + styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep); + } + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = styleToUse; + } + + _atts(node, styleToWrite); + }, + /* + * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. + */ + VmlComponent = function() { + var self = this; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.opacityNodes = { + "stroke":null, + "fill":null + }; + this.initOpacityNodes = function(vml) { + self.opacityNodes["stroke"] = _node("stroke", [0,0,1,1], {opacity:"0.0"}); + self.opacityNodes["fill"] = _node("fill", [0,0,1,1], {opacity:"0.0"}); + vml.appendChild(self.opacityNodes["stroke"]); + vml.appendChild(self.opacityNodes["fill"]); + }; + this.setOpacity = function(type, value) { + var node = self.opacityNodes[type]; + if (node) node["opacity"] = "" + value; + }; + var displayElements = [ ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el, doNotAppendToCanvas) { + if (!doNotAppendToCanvas) self.canvas.parentNode.appendChild(el); + displayElements.push(el); + }; + }, + /* + * Base class for Vml connectors. extends VmlComponent. + */ + VmlConnector = jsPlumb.VmlConnector = function(params) { + var self = this; + self.strokeNode = null; + self.canvas = null; + VmlComponent.apply(this, arguments); + var clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : ""); + this.paint = function(d, style, anchor) { + if (style != null) { + var path = self.getPath(d), p = { "path":path }; + + //* + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle : jsPlumb.util.convertStyle(style.outlineColor), + lineWidth : outlineStrokeWidth + }; + for (var aa in vmlAttributeMap) outlineStyle[aa] = style[aa]; + + if (self.bgCanvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.bgCanvas = _node("shape", d, p); + params["_jsPlumb"].appendElement(self.bgCanvas, params.parent); + _pos(self.bgCanvas, d); + self.appendDisplayElement(self.bgCanvas, true); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.bgCanvas, d); + _atts(self.bgCanvas, p); + } + + _applyStyles(self.bgCanvas, outlineStyle, self); + } + //*/ + + if (self.canvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + if (self.tooltip) p["label"] = self.tooltip; + self.canvas = _node("shape", d, p); + + //var group = _getGroup(params.parent); // test of append everything to a group + //group.appendChild(self.canvas); // sort of works but not exactly; + params["_jsPlumb"].appendElement(self.canvas, params.parent); //before introduction of groups + + self.appendDisplayElement(self.canvas, true); + + self.attachListeners(self.canvas, self); + + self.initOpacityNodes(self.canvas, ["stroke"]); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.canvas, d); + _atts(self.canvas, p); + } + + _applyStyles(self.canvas, style, self); + } + }; + + //self.appendDisplayElement(self.canvas); + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + }, + /* + * + * Base class for Vml Endpoints. extends VmlComponent. + * + */ + VmlEndpoint = function(params) { + VmlComponent.apply(this, arguments); + var vml = null, self = this, opacityStrokeNode = null, opacityFillNode = null; + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + + //var group = _getGroup(params.parent); + //group.appendChild(self.canvas); + params["_jsPlumb"].appendElement(self.canvas, params.parent); + + if (self.tooltip) self.canvas.setAttribute("label", self.tooltip); + + this.paint = function(d, style, anchor) { + var p = { }; + + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (vml == null) { + p["class"] = jsPlumb.endpointClass; + vml = self.getVml([0,0, d[2], d[3]], p, anchor); + self.canvas.appendChild(vml); + self.attachListeners(vml, self); + + self.appendDisplayElement(vml, true); + self.appendDisplayElement(self.canvas, true); + + self.initOpacityNodes(vml, ["fill"]); + } + else { + //p["coordsize"] = "1,1";//(d[2] * scale) + "," + (d[3] * scale); again, unsure. + _pos(vml, [0,0, d[2], d[3]]); + _atts(vml, p); + } + + _applyStyles(vml, style, self); + }; + + this.reattachListeners = function() { + if (vml) self.reattachListenersForElement(vml, self); + }; + }; + + jsPlumb.Connectors.vml.Bezier = function() { + jsPlumb.Connectors.Bezier.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Straight = function() { + jsPlumb.Connectors.Straight.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Flowchart = function() { + jsPlumb.Connectors.Flowchart.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l"; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]); + } + // finally draw a line to the end + p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e"; + return p; + }; + }; + + jsPlumb.Endpoints.vml.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("oval", d, atts); }; + }; + + jsPlumb.Endpoints.vml.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("rect", d, atts); }; + }; + + /* + * VML Image Endpoint is the same as the default image endpoint. + */ + jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image; + + /** + * placeholder for Blank endpoint in vml renderer. + */ + jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank; + + /** + * VML Label renderer. uses the default label renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label; + + var AbstractVmlArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + VmlComponent.apply(this, arguments); + var self = this, path = null; + self.canvas = null; + var getPath = function(d, connectorDimensions) { + return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) + + " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + + " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + + " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + + " x e"; + }; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) { + var p = {}; + if (strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] = jsPlumb.util.convertStyle(strokeStyle, true); + } + if (lineWidth) p["strokeweight"] = lineWidth + "px"; + if (fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = fillStyle; + } + var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + w = Math.abs(xmax - xmin), + h = Math.abs(ymax - ymin), + dim = [xmin, ymin, w, h]; + + // for VML, we create overlays using shapes that have the same dimensions and + // coordsize as their connector - overlays calculate themselves relative to the + // connector (it's how it's been done since the original canvas implementation, because + // for canvas that makes sense). + p["path"] = getPath(d, connectorDimensions); + p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale); + + dim[0] = connectorDimensions[0]; + dim[1] = connectorDimensions[1]; + dim[2] = connectorDimensions[2]; + dim[3] = connectorDimensions[3]; + + if (self.canvas == null) { + //p["class"] = jsPlumb.overlayClass; // TODO currentInstance? + self.canvas = _node("shape", dim, p); + connector.appendDisplayElement(self.canvas); + self.attachListeners(self.canvas, connector); + } + else { + _pos(self.canvas, dim); + _atts(self.canvas, p); + } + }; + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + }; + + jsPlumb.Overlays.vml.Arrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.vml.PlainArrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.vml.Diamond = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.6/mootools.jsPlumb-1.3.6-RC1.js b/archive/1.3.6/mootools.jsPlumb-1.3.6-RC1.js new file mode 100644 index 000000000..1d04a0015 --- /dev/null +++ b/archive/1.3.6/mootools.jsPlumb-1.3.6-RC1.js @@ -0,0 +1,455 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.6 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the MooTools adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function(now) { + this.parent(now); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, _scope, value) { + var l = list[_scope]; + if (!l) { + l = []; + list[_scope] = l; + } + l.push(value); + }; + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + var _getElementObject = function(el) { + return $(el); + }, + + _removeNonPermanentDroppables = function(drag) { + /* + // remove non-permanent droppables from all arrays + var dbs = _droppables[drag.scope], d = []; + if (dbs) { + var d = []; + for (var i=0; i < dbs.length; i++) { + var isPermanent = dbs[i].getAttribute("_isPermanentDroppable"); + if (isPermanent === "true") d.push(dbs[i]); + } + dbs.splice(0, dbs.length); + _droppables[drag.scope] = d; + } + d = []; + // clear out transient droppables from the drag itself + for(var i = 0; i < drag.droppables.length; i++) { + var isPermanent = drag.droppables[i].getAttribute("_isPermanentDroppable"); + if (isPermanent === "true") d.push(drag.droppables[i]); + } + drag.droppables.splice(0, drag.droppables.length); // release old ones + drag.droppables = d; + //*/ + }; + + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el) + try { + if (el.className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.addClass(el, clazz); + } + else el.addClass(clazz); + } + catch (e) { + // SVGAnimatedString not supported; no problem. + el.addClass(clazz); + } + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + m.start(properties); + }, + + appendElement : function(child, parent) { + _getElementObject(parent).grab(child); + }, + + bind : function(el, event, callback) { + el = _getElementObject(el); + el.addEvent(event, callback); + }, + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.event.clientX, eventObject.event.clientY]; + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el), + drags = _draggablesById[id]; + return drags[0].scope; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getDOMElement : function(el) { + // MooTools just decorates the DOM elements. so we have either an ID or an Element here. + return typeof(el) == "String" ? document.getElementById(el) : el; + }, + + getElementObject : _getElementObject, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + getPageXY : function(eventObject) { + return [eventObject.event.pageX, eventObject.event.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).getParent(); + }, + + getScrollLeft : function(el) { + return null; + }, + + getScrollTop : function(el) { + return null; + }, + + getSelector : function(spec) { + return $$(spec); + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e != null ? e.tagName : null; + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[0], + p = jsPlumb.CurrentLibrary.getElementObject(ui).getPosition(); + return { left:p.x, top:p.y }; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options, isPlumbedComponent) { + var id = jsPlumb.getId(el); + var drag = _draggablesById[id]; + if (!drag) { + var originalZIndex = 0, + originalCursor = null, + dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + + options['onStart'] = jsPlumb.wrap(options['onStart'], function() { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + drag.originalZIndex = originalZIndex; + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + + _removeNonPermanentDroppables(drag); + }); + + // DROPPABLES - only relevant if this is a plumbed component, ie. not just the result of the user making some DOM element + // draggable. this is the only library adapter that has to care about this parameter. + var scope = "" + (options["scope"] || jsPlumb.Defaults.Scope), + filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }, + droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + + if (isPlumbedComponent) { + + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop'); + } + }; + } + else + options["droppables"] = []; + + drag = new Drag.Move(el, options); + drag.scope = scope; + drag.originalZIndex = originalZIndex; + _add(_draggablesById, el.get("id"), drag); + // again, only keep a record of this for scope stuff if this is a plumbed component (an endpoint) + if (isPlumbedComponent) { + _add(_draggablesByScope, scope, drag); + } + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + initDroppable : function(el, options, isPlumbedComponent, isPermanent) { + var scope = options["scope"]; + _add(_droppables, scope, el); + var id = jsPlumb.getId(el); + + el.setAttribute("_isPermanentDroppable", isPermanent); // we use this to clear out droppables on drag complete. + _droppableOptions[id] = options; + _droppableScopesById[id] = scope; + var filterFunc = function(entry) { return entry.element != el; }, + draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + isAlreadyDraggable : function(el) { + return _draggablesById[jsPlumb.getId(el)] != null; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + return (typeof Drag != undefined && typeof Drag.Move != undefined); + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el.className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.removeClass(el, clazz); + } + else el.removeClass(clazz); + } + catch (e) { + // SVGAnimatedString not supported; no problem. + el.removeClass(clazz); + } + }, + + removeElement : function(element, parent) { + var el = _getElementObject(element); + if (el) el.dispose(); // ?? + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + setDragScope : function(el, scope) { + var drag = _draggablesById[el.get("id")]; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + drag[0].droppables = droppables; + }, + + setOffset : function(el, o) { + _getElementObject(el).setPosition({x:o.left, y:o.top}); + }, + + stopDrag : function() { + for (var i in _draggablesById) { + for (var j = 0; j < _draggablesById[i].length; j++) { + var d = _draggablesById[i][j]; + d.stop(); + if (d.originalZIndex != 0) + d.element.setStyle("z-index", d.originalZIndex); + } + } + }, + + /** + * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself. + * the other libraries do not. yui, in fact, cannot even pass an original event. we have to pull out stuff + * from the originalEvent to put in an options object for YUI. + * @param el + * @param event + * @param originalEvent + */ + trigger : function(el, event, originalEvent) { + originalEvent.stopPropagation(); + _getElementObject(el).fireEvent(event, originalEvent); + }, + + unbind : function(el, event, callback) { + el = _getElementObject(el); + el.removeEvent(event, callback); + } + }; + + window.addEvent('domready', jsPlumb.init); +})(); diff --git a/build/1.3.6/tests/qunit-all.html b/archive/1.3.6/tests/qunit-all.html similarity index 100% rename from build/1.3.6/tests/qunit-all.html rename to archive/1.3.6/tests/qunit-all.html diff --git a/build/1.3.6/tests/qunit-canvas-jquery-instance.html b/archive/1.3.6/tests/qunit-canvas-jquery-instance.html similarity index 100% rename from build/1.3.6/tests/qunit-canvas-jquery-instance.html rename to archive/1.3.6/tests/qunit-canvas-jquery-instance.html diff --git a/build/1.3.6/tests/qunit-canvas-jquery.html b/archive/1.3.6/tests/qunit-canvas-jquery.html similarity index 100% rename from build/1.3.6/tests/qunit-canvas-jquery.html rename to archive/1.3.6/tests/qunit-canvas-jquery.html diff --git a/build/1.3.6/tests/qunit-canvas-mootools.html b/archive/1.3.6/tests/qunit-canvas-mootools.html similarity index 100% rename from build/1.3.6/tests/qunit-canvas-mootools.html rename to archive/1.3.6/tests/qunit-canvas-mootools.html diff --git a/build/1.3.6/tests/qunit-svg-jquery-instance.html b/archive/1.3.6/tests/qunit-svg-jquery-instance.html similarity index 100% rename from build/1.3.6/tests/qunit-svg-jquery-instance.html rename to archive/1.3.6/tests/qunit-svg-jquery-instance.html diff --git a/build/1.3.6/tests/qunit-svg-jquery.html b/archive/1.3.6/tests/qunit-svg-jquery.html similarity index 100% rename from build/1.3.6/tests/qunit-svg-jquery.html rename to archive/1.3.6/tests/qunit-svg-jquery.html diff --git a/build/1.3.6/tests/qunit-vml-jquery-instance.html b/archive/1.3.6/tests/qunit-vml-jquery-instance.html similarity index 100% rename from build/1.3.6/tests/qunit-vml-jquery-instance.html rename to archive/1.3.6/tests/qunit-vml-jquery-instance.html diff --git a/build/1.3.6/tests/qunit-vml-jquery.html b/archive/1.3.6/tests/qunit-vml-jquery.html similarity index 100% rename from build/1.3.6/tests/qunit-vml-jquery.html rename to archive/1.3.6/tests/qunit-vml-jquery.html diff --git a/build/1.3.6/tests/qunit.css b/archive/1.3.6/tests/qunit.css similarity index 100% rename from build/1.3.6/tests/qunit.css rename to archive/1.3.6/tests/qunit.css diff --git a/archive/1.3.6/yui.jsPlumb-1.3.6-RC1.js b/archive/1.3.6/yui.jsPlumb-1.3.6-RC1.js new file mode 100644 index 000000000..8e6b722aa --- /dev/null +++ b/archive/1.3.6/yui.jsPlumb-1.3.6-RC1.js @@ -0,0 +1,372 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.6 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the YUI3 adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +/** + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + */ +(function() { + + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function( v, b, s ) { + for( var i = +b || 0, l = this.length; i < l; i++ ) { + if( this[i]===v || s && this[i]==v ) { return i; } + } + return -1; + }; + } + + var Y; + + YUI().use('node', 'dd', 'anim', 'node-event-simulate', function(_Y) { + Y = _Y; + Y.on("domready", function() { jsPlumb.init(); }); + }); + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }, + ddEvents = [ "drag:mouseDown", "drag:afterMouseDown", "drag:mouseup", + "drag:align", "drag:removeHandle", "drag:addHandle", "drag:removeInvalid", "drag:addInvalid", + "drag:start", "drag:end", "drag:drag", "drag:over", "drag:enter", + "drag:exit", "drag:drophit", "drag:dropmiss", "drop:over", "drop:enter", "drop:exit", "drop:hit" + ], + animEvents = [ "tween" ], + /** + * helper function to curry callbacks for some element. + */ + _wrapper = function(fn) { + return function() { + try { + return fn.apply(this, arguments); + } + catch (e) { } + }; + }, + /** + * extracts options from the given options object, leaving out event handlers. + */ + _getDDOptions = function(options) { + var o = {}; + for (var i in options) if (ddEvents.indexOf(i) == -1) o[i] = options[i]; + return o; + }, + /** + * attaches all event handlers found in options to the given dragdrop object, and registering + * the given el as the element of interest. + */ + _attachListeners = function(dd, options, eventList) { + for (var ev in options) { + if (eventList.indexOf(ev) != -1) { + var w = _wrapper(options[ev]); + dd.on(ev, w); + } + } + }, + _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}, + _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }, + _lastDragObject = null, + _extend = function(o1, o2) { + for (var i in o2) + o1[i] = o2[i]; + return o1; + }, + _getAttribute = function(el, attributeId) { + return el.getAttribute(attributeId); + }, + _getElementObject = function(el) { + if (el == null) return null; + var eee = null; + eee = typeof el == 'string' ? Y.one('#' + el) : el._node ? el : Y.one(el); + return eee; + }; + + jsPlumb.CurrentLibrary = { + + addClass : function(el, clazz) { + jsPlumb.CurrentLibrary.getElementObject(el).addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + var o = _extend({node:el, to:properties}, options); + var id = _getAttribute(el, "id"); + o["tween"] = jsPlumb.wrap(properties["tween"], function() { + jsPlumb.repaint(id); + }); + var a = new Y.Anim(o); + _attachListeners(a, o, animEvents); + a.run(); + }, + + appendElement : function(child, parent) { + _getElementObject (parent).append(child); + }, + + /** + * event binding wrapper. + */ + bind : function(el, event, callback) { + _getElementObject(el).on(event, callback); + }, + + dragEvents : { + "start":"drag:start", "stop":"drag:end", "drag":"drag:drag", "step":"step", + "over":"drop:enter", "out":"drop:exit", "drop":"drop:hit" + }, + + extend : _extend, + + getAttribute : _getAttribute, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + // this is a workaround for the unfortunate fact that in YUI3, the 'drop:exit' event does + // not contain a reference to the drag that just exited. single-threaded js to the + // rescue: we'll just keep it for ourselves. + if (eventArgs[0].drag) _lastDragObject = eventArgs[0].drag.el; + return _lastDragObject; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + return dd.scope; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getDOMElement : function(el) { + if (typeof(el) == "String") + return document.getElementById(el); + else if (el._node) + return el._node; + else return el; + }, + + getElementObject : _getElementObject, + + getOffset : function(el) { + var o = Y.DOM.getXY(el._node); + return {left:o[0], top:o[1]}; + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).get("parentNode"); + }, + + getScrollLeft : function(el) { + return 0; + }, + + getScrollTop : function(el) { + return 0; + }, + + getSelector : function(spec) { + var s = Y.all(spec); + return s && s ._nodes ? s._nodes : []; + }, + + getSize : function(el) { + return [ el._node.offsetWidth, el._node.offsetHeight ]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e != null && e._node != null ? e._node.tagName : null; + }, + + getUIPosition : function(args) { + var n = args[0].currentTarget.el._node, + o = Y.DOM.getXY(n); + return {left:o[0], top:o[1]}; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options) { + var _opts = _getDDOptions(options); + var id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drag(_opts); + dd.el = el; + + var scope = options['scope'] || jsPlumb.Defaults.Scope; + dd.scope = scope; + + _draggablesById[id] = dd; + _add(_draggablesByScope, scope, dd); + + _attachListeners(dd, options, ddEvents); + }, + + initDroppable : function(el, options) { + var _opts = _getDDOptions(options); + var id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drop(_opts); + + _droppableOptions[id] = options; + + options = _extend({}, options); + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _droppableScopesById[id] = scope; + + options["drop:enter"] = jsPlumb.wrap(options["drop:enter"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, true); + }, true); + options["drop:exit"] = jsPlumb.wrap(options["drop:exit"], function(e) { + _checkHover(el, false); + }); + options["drop:hit"] = jsPlumb.wrap(options["drop:hit"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, false); + }, true); + + _attachListeners(dd, options, ddEvents); + }, + + isAlreadyDraggable : function(el) { + el = _getElementObject(el); + return el.hasClass("yui3-dd-draggable"); + }, + + isDragSupported : function(el) { return true; }, + isDropSupported : function(el) { return true; }, + removeClass : function(el, clazz) { + jsPlumb.CurrentLibrary.getElementObject(el).removeClass(clazz); + }, + removeElement : function(el) { _getElementObject(el).remove(); }, + + setAttribute : function(el, attributeName, attributeValue) { + el.setAttribute(attributeName, attributeValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + if (dd) dd.set("lock", !draggable); + }, + + setDragScope : function(el, scope) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + if (dd) dd.scope = scope; + }, + + setOffset : function(el, o) { + el = _getElementObject(el); + el.set("top", o.top); + el.set("left", o.left); + }, + + stopDrag : function() { + Y.DD.DDM.stopDrag(); + }, + + trigger : function(el, event, originalEvent) { + originalEvent.stopPropagation(); + _getElementObject(el).simulate(event, { + pageX:originalEvent.pageX, + pageY:originalEvent.pageY, + clientX:originalEvent.clientX, + clientY:originalEvent.clientY + }); + }, + + /** + * event unbinding wrapper. + */ + unbind : function(el, event, callback) { + _getElementObject(el).detach(event, callback); + } + }; +})(); \ No newline at end of file diff --git a/build/1.3.7/demo/apidocs/files/jquery-jsPlumb-1-3-6-all-js.html b/archive/1.3.7/demo/apidocs/files/jquery-jsPlumb-1-3-6-all-js.html similarity index 100% rename from build/1.3.7/demo/apidocs/files/jquery-jsPlumb-1-3-6-all-js.html rename to archive/1.3.7/demo/apidocs/files/jquery-jsPlumb-1-3-6-all-js.html diff --git a/build/1.3.7/demo/apidocs/files/jquery-jsPlumb-1-3-7-all-js.html b/archive/1.3.7/demo/apidocs/files/jquery-jsPlumb-1-3-7-all-js.html similarity index 100% rename from build/1.3.7/demo/apidocs/files/jquery-jsPlumb-1-3-7-all-js.html rename to archive/1.3.7/demo/apidocs/files/jquery-jsPlumb-1-3-7-all-js.html diff --git a/build/1.3.7/demo/apidocs/index.html b/archive/1.3.7/demo/apidocs/index.html similarity index 100% rename from build/1.3.7/demo/apidocs/index.html rename to archive/1.3.7/demo/apidocs/index.html diff --git a/build/1.3.7/demo/apidocs/index/Classes.html b/archive/1.3.7/demo/apidocs/index/Classes.html similarity index 100% rename from build/1.3.7/demo/apidocs/index/Classes.html rename to archive/1.3.7/demo/apidocs/index/Classes.html diff --git a/build/1.3.7/demo/apidocs/index/Files.html b/archive/1.3.7/demo/apidocs/index/Files.html similarity index 100% rename from build/1.3.7/demo/apidocs/index/Files.html rename to archive/1.3.7/demo/apidocs/index/Files.html diff --git a/build/1.3.7/demo/apidocs/index/Functions.html b/archive/1.3.7/demo/apidocs/index/Functions.html similarity index 100% rename from build/1.3.7/demo/apidocs/index/Functions.html rename to archive/1.3.7/demo/apidocs/index/Functions.html diff --git a/build/1.3.7/demo/apidocs/index/Functions2.html b/archive/1.3.7/demo/apidocs/index/Functions2.html similarity index 100% rename from build/1.3.7/demo/apidocs/index/Functions2.html rename to archive/1.3.7/demo/apidocs/index/Functions2.html diff --git a/build/1.3.7/demo/apidocs/index/General.html b/archive/1.3.7/demo/apidocs/index/General.html similarity index 100% rename from build/1.3.7/demo/apidocs/index/General.html rename to archive/1.3.7/demo/apidocs/index/General.html diff --git a/build/1.3.7/demo/apidocs/index/General2.html b/archive/1.3.7/demo/apidocs/index/General2.html similarity index 100% rename from build/1.3.7/demo/apidocs/index/General2.html rename to archive/1.3.7/demo/apidocs/index/General2.html diff --git a/build/1.3.7/demo/apidocs/index/Properties.html b/archive/1.3.7/demo/apidocs/index/Properties.html similarity index 100% rename from build/1.3.7/demo/apidocs/index/Properties.html rename to archive/1.3.7/demo/apidocs/index/Properties.html diff --git a/build/1.3.7/demo/apidocs/javascript/main.js b/archive/1.3.7/demo/apidocs/javascript/main.js similarity index 100% rename from build/1.3.7/demo/apidocs/javascript/main.js rename to archive/1.3.7/demo/apidocs/javascript/main.js diff --git a/build/1.3.7/demo/apidocs/javascript/prettify.js b/archive/1.3.7/demo/apidocs/javascript/prettify.js similarity index 100% rename from build/1.3.7/demo/apidocs/javascript/prettify.js rename to archive/1.3.7/demo/apidocs/javascript/prettify.js diff --git a/build/1.3.7/demo/apidocs/javascript/searchdata.js b/archive/1.3.7/demo/apidocs/javascript/searchdata.js similarity index 100% rename from build/1.3.7/demo/apidocs/javascript/searchdata.js rename to archive/1.3.7/demo/apidocs/javascript/searchdata.js diff --git a/build/1.3.7/demo/apidocs/search/ClassesC.html b/archive/1.3.7/demo/apidocs/search/ClassesC.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/ClassesC.html rename to archive/1.3.7/demo/apidocs/search/ClassesC.html diff --git a/build/1.3.7/demo/apidocs/search/ClassesE.html b/archive/1.3.7/demo/apidocs/search/ClassesE.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/ClassesE.html rename to archive/1.3.7/demo/apidocs/search/ClassesE.html diff --git a/build/1.3.7/demo/apidocs/search/ClassesO.html b/archive/1.3.7/demo/apidocs/search/ClassesO.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/ClassesO.html rename to archive/1.3.7/demo/apidocs/search/ClassesO.html diff --git a/build/1.3.7/demo/apidocs/search/FilesJ.html b/archive/1.3.7/demo/apidocs/search/FilesJ.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/FilesJ.html rename to archive/1.3.7/demo/apidocs/search/FilesJ.html diff --git a/build/1.3.7/demo/apidocs/search/FunctionsA.html b/archive/1.3.7/demo/apidocs/search/FunctionsA.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/FunctionsA.html rename to archive/1.3.7/demo/apidocs/search/FunctionsA.html diff --git a/build/1.3.7/demo/apidocs/search/FunctionsB.html b/archive/1.3.7/demo/apidocs/search/FunctionsB.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/FunctionsB.html rename to archive/1.3.7/demo/apidocs/search/FunctionsB.html diff --git a/build/1.3.7/demo/apidocs/search/FunctionsC.html b/archive/1.3.7/demo/apidocs/search/FunctionsC.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/FunctionsC.html rename to archive/1.3.7/demo/apidocs/search/FunctionsC.html diff --git a/build/1.3.7/demo/apidocs/search/FunctionsD.html b/archive/1.3.7/demo/apidocs/search/FunctionsD.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/FunctionsD.html rename to archive/1.3.7/demo/apidocs/search/FunctionsD.html diff --git a/build/1.3.7/demo/apidocs/search/FunctionsE.html b/archive/1.3.7/demo/apidocs/search/FunctionsE.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/FunctionsE.html rename to archive/1.3.7/demo/apidocs/search/FunctionsE.html diff --git a/build/1.3.7/demo/apidocs/search/FunctionsG.html b/archive/1.3.7/demo/apidocs/search/FunctionsG.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/FunctionsG.html rename to archive/1.3.7/demo/apidocs/search/FunctionsG.html diff --git a/build/1.3.7/demo/apidocs/search/FunctionsH.html b/archive/1.3.7/demo/apidocs/search/FunctionsH.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/FunctionsH.html rename to archive/1.3.7/demo/apidocs/search/FunctionsH.html diff --git a/build/1.3.7/demo/apidocs/search/FunctionsI.html b/archive/1.3.7/demo/apidocs/search/FunctionsI.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/FunctionsI.html rename to archive/1.3.7/demo/apidocs/search/FunctionsI.html diff --git a/build/1.3.7/demo/apidocs/search/FunctionsM.html b/archive/1.3.7/demo/apidocs/search/FunctionsM.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/FunctionsM.html rename to archive/1.3.7/demo/apidocs/search/FunctionsM.html diff --git a/build/1.3.7/demo/apidocs/search/FunctionsP.html b/archive/1.3.7/demo/apidocs/search/FunctionsP.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/FunctionsP.html rename to archive/1.3.7/demo/apidocs/search/FunctionsP.html diff --git a/build/1.3.7/demo/apidocs/search/FunctionsR.html b/archive/1.3.7/demo/apidocs/search/FunctionsR.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/FunctionsR.html rename to archive/1.3.7/demo/apidocs/search/FunctionsR.html diff --git a/build/1.3.7/demo/apidocs/search/FunctionsS.html b/archive/1.3.7/demo/apidocs/search/FunctionsS.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/FunctionsS.html rename to archive/1.3.7/demo/apidocs/search/FunctionsS.html diff --git a/build/1.3.7/demo/apidocs/search/FunctionsT.html b/archive/1.3.7/demo/apidocs/search/FunctionsT.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/FunctionsT.html rename to archive/1.3.7/demo/apidocs/search/FunctionsT.html diff --git a/build/1.3.7/demo/apidocs/search/FunctionsU.html b/archive/1.3.7/demo/apidocs/search/FunctionsU.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/FunctionsU.html rename to archive/1.3.7/demo/apidocs/search/FunctionsU.html diff --git a/build/1.3.7/demo/apidocs/search/GeneralA.html b/archive/1.3.7/demo/apidocs/search/GeneralA.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/GeneralA.html rename to archive/1.3.7/demo/apidocs/search/GeneralA.html diff --git a/build/1.3.7/demo/apidocs/search/GeneralB.html b/archive/1.3.7/demo/apidocs/search/GeneralB.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/GeneralB.html rename to archive/1.3.7/demo/apidocs/search/GeneralB.html diff --git a/build/1.3.7/demo/apidocs/search/GeneralC.html b/archive/1.3.7/demo/apidocs/search/GeneralC.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/GeneralC.html rename to archive/1.3.7/demo/apidocs/search/GeneralC.html diff --git a/build/1.3.7/demo/apidocs/search/GeneralD.html b/archive/1.3.7/demo/apidocs/search/GeneralD.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/GeneralD.html rename to archive/1.3.7/demo/apidocs/search/GeneralD.html diff --git a/build/1.3.7/demo/apidocs/search/GeneralE.html b/archive/1.3.7/demo/apidocs/search/GeneralE.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/GeneralE.html rename to archive/1.3.7/demo/apidocs/search/GeneralE.html diff --git a/build/1.3.7/demo/apidocs/search/GeneralF.html b/archive/1.3.7/demo/apidocs/search/GeneralF.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/GeneralF.html rename to archive/1.3.7/demo/apidocs/search/GeneralF.html diff --git a/build/1.3.7/demo/apidocs/search/GeneralG.html b/archive/1.3.7/demo/apidocs/search/GeneralG.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/GeneralG.html rename to archive/1.3.7/demo/apidocs/search/GeneralG.html diff --git a/build/1.3.7/demo/apidocs/search/GeneralH.html b/archive/1.3.7/demo/apidocs/search/GeneralH.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/GeneralH.html rename to archive/1.3.7/demo/apidocs/search/GeneralH.html diff --git a/build/1.3.7/demo/apidocs/search/GeneralI.html b/archive/1.3.7/demo/apidocs/search/GeneralI.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/GeneralI.html rename to archive/1.3.7/demo/apidocs/search/GeneralI.html diff --git a/build/1.3.7/demo/apidocs/search/GeneralJ.html b/archive/1.3.7/demo/apidocs/search/GeneralJ.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/GeneralJ.html rename to archive/1.3.7/demo/apidocs/search/GeneralJ.html diff --git a/build/1.3.7/demo/apidocs/search/GeneralM.html b/archive/1.3.7/demo/apidocs/search/GeneralM.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/GeneralM.html rename to archive/1.3.7/demo/apidocs/search/GeneralM.html diff --git a/build/1.3.7/demo/apidocs/search/GeneralO.html b/archive/1.3.7/demo/apidocs/search/GeneralO.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/GeneralO.html rename to archive/1.3.7/demo/apidocs/search/GeneralO.html diff --git a/build/1.3.7/demo/apidocs/search/GeneralP.html b/archive/1.3.7/demo/apidocs/search/GeneralP.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/GeneralP.html rename to archive/1.3.7/demo/apidocs/search/GeneralP.html diff --git a/build/1.3.7/demo/apidocs/search/GeneralR.html b/archive/1.3.7/demo/apidocs/search/GeneralR.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/GeneralR.html rename to archive/1.3.7/demo/apidocs/search/GeneralR.html diff --git a/build/1.3.7/demo/apidocs/search/GeneralS.html b/archive/1.3.7/demo/apidocs/search/GeneralS.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/GeneralS.html rename to archive/1.3.7/demo/apidocs/search/GeneralS.html diff --git a/build/1.3.7/demo/apidocs/search/GeneralT.html b/archive/1.3.7/demo/apidocs/search/GeneralT.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/GeneralT.html rename to archive/1.3.7/demo/apidocs/search/GeneralT.html diff --git a/build/1.3.7/demo/apidocs/search/GeneralU.html b/archive/1.3.7/demo/apidocs/search/GeneralU.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/GeneralU.html rename to archive/1.3.7/demo/apidocs/search/GeneralU.html diff --git a/build/1.3.7/demo/apidocs/search/NoResults.html b/archive/1.3.7/demo/apidocs/search/NoResults.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/NoResults.html rename to archive/1.3.7/demo/apidocs/search/NoResults.html diff --git a/build/1.3.7/demo/apidocs/search/PropertiesC.html b/archive/1.3.7/demo/apidocs/search/PropertiesC.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/PropertiesC.html rename to archive/1.3.7/demo/apidocs/search/PropertiesC.html diff --git a/build/1.3.7/demo/apidocs/search/PropertiesD.html b/archive/1.3.7/demo/apidocs/search/PropertiesD.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/PropertiesD.html rename to archive/1.3.7/demo/apidocs/search/PropertiesD.html diff --git a/build/1.3.7/demo/apidocs/search/PropertiesE.html b/archive/1.3.7/demo/apidocs/search/PropertiesE.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/PropertiesE.html rename to archive/1.3.7/demo/apidocs/search/PropertiesE.html diff --git a/build/1.3.7/demo/apidocs/search/PropertiesO.html b/archive/1.3.7/demo/apidocs/search/PropertiesO.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/PropertiesO.html rename to archive/1.3.7/demo/apidocs/search/PropertiesO.html diff --git a/build/1.3.7/demo/apidocs/search/PropertiesS.html b/archive/1.3.7/demo/apidocs/search/PropertiesS.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/PropertiesS.html rename to archive/1.3.7/demo/apidocs/search/PropertiesS.html diff --git a/build/1.3.7/demo/apidocs/search/PropertiesT.html b/archive/1.3.7/demo/apidocs/search/PropertiesT.html similarity index 100% rename from build/1.3.7/demo/apidocs/search/PropertiesT.html rename to archive/1.3.7/demo/apidocs/search/PropertiesT.html diff --git a/build/1.3.7/demo/apidocs/styles/main.css b/archive/1.3.7/demo/apidocs/styles/main.css similarity index 100% rename from build/1.3.7/demo/apidocs/styles/main.css rename to archive/1.3.7/demo/apidocs/styles/main.css diff --git a/build/1.3.7/demo/css/anchorDemo.css b/archive/1.3.7/demo/css/anchorDemo.css similarity index 100% rename from build/1.3.7/demo/css/anchorDemo.css rename to archive/1.3.7/demo/css/anchorDemo.css diff --git a/build/1.3.7/demo/css/chartDemo.css b/archive/1.3.7/demo/css/chartDemo.css similarity index 100% rename from build/1.3.7/demo/css/chartDemo.css rename to archive/1.3.7/demo/css/chartDemo.css diff --git a/build/1.3.7/demo/css/demo.css b/archive/1.3.7/demo/css/demo.css similarity index 100% rename from build/1.3.7/demo/css/demo.css rename to archive/1.3.7/demo/css/demo.css diff --git a/build/1.3.7/demo/css/dragAnimDemo.css b/archive/1.3.7/demo/css/dragAnimDemo.css similarity index 100% rename from build/1.3.7/demo/css/dragAnimDemo.css rename to archive/1.3.7/demo/css/dragAnimDemo.css diff --git a/build/1.3.7/demo/css/draggableConnectorsDemo.css b/archive/1.3.7/demo/css/draggableConnectorsDemo.css similarity index 100% rename from build/1.3.7/demo/css/draggableConnectorsDemo.css rename to archive/1.3.7/demo/css/draggableConnectorsDemo.css diff --git a/build/1.3.7/demo/css/dynamicAnchorsDemo.css b/archive/1.3.7/demo/css/dynamicAnchorsDemo.css similarity index 100% rename from build/1.3.7/demo/css/dynamicAnchorsDemo.css rename to archive/1.3.7/demo/css/dynamicAnchorsDemo.css diff --git a/build/1.3.7/demo/css/flowchartDemo.css b/archive/1.3.7/demo/css/flowchartDemo.css similarity index 100% rename from build/1.3.7/demo/css/flowchartDemo.css rename to archive/1.3.7/demo/css/flowchartDemo.css diff --git a/build/1.3.7/demo/css/jsPlumbDemo.css b/archive/1.3.7/demo/css/jsPlumbDemo.css similarity index 100% rename from build/1.3.7/demo/css/jsPlumbDemo.css rename to archive/1.3.7/demo/css/jsPlumbDemo.css diff --git a/build/1.3.7/demo/css/makeTargetDemo.css b/archive/1.3.7/demo/css/makeTargetDemo.css similarity index 100% rename from build/1.3.7/demo/css/makeTargetDemo.css rename to archive/1.3.7/demo/css/makeTargetDemo.css diff --git a/build/1.3.7/demo/css/multipleJsPlumbDemo.css b/archive/1.3.7/demo/css/multipleJsPlumbDemo.css similarity index 100% rename from build/1.3.7/demo/css/multipleJsPlumbDemo.css rename to archive/1.3.7/demo/css/multipleJsPlumbDemo.css diff --git a/build/1.3.7/demo/css/selectDemo.css b/archive/1.3.7/demo/css/selectDemo.css similarity index 100% rename from build/1.3.7/demo/css/selectDemo.css rename to archive/1.3.7/demo/css/selectDemo.css diff --git a/build/1.3.7/demo/css/stateMachineDemo.css b/archive/1.3.7/demo/css/stateMachineDemo.css similarity index 100% rename from build/1.3.7/demo/css/stateMachineDemo.css rename to archive/1.3.7/demo/css/stateMachineDemo.css diff --git a/build/1.3.7/demo/doc/archive/1.2.6/content.html b/archive/1.3.7/demo/doc/archive/1.2.6/content.html similarity index 100% rename from build/1.3.7/demo/doc/archive/1.2.6/content.html rename to archive/1.3.7/demo/doc/archive/1.2.6/content.html diff --git a/build/1.3.7/demo/doc/archive/1.2.6/index.html b/archive/1.3.7/demo/doc/archive/1.2.6/index.html similarity index 100% rename from build/1.3.7/demo/doc/archive/1.2.6/index.html rename to archive/1.3.7/demo/doc/archive/1.2.6/index.html diff --git a/build/1.3.7/demo/doc/archive/1.2.6/usage.html b/archive/1.3.7/demo/doc/archive/1.2.6/usage.html similarity index 100% rename from build/1.3.7/demo/doc/archive/1.2.6/usage.html rename to archive/1.3.7/demo/doc/archive/1.2.6/usage.html diff --git a/build/1.3.7/demo/doc/archive/1.3.2/content.html b/archive/1.3.7/demo/doc/archive/1.3.2/content.html similarity index 100% rename from build/1.3.7/demo/doc/archive/1.3.2/content.html rename to archive/1.3.7/demo/doc/archive/1.3.2/content.html diff --git a/build/1.3.7/demo/doc/archive/1.3.2/index.html b/archive/1.3.7/demo/doc/archive/1.3.2/index.html similarity index 100% rename from build/1.3.7/demo/doc/archive/1.3.2/index.html rename to archive/1.3.7/demo/doc/archive/1.3.2/index.html diff --git a/build/1.3.7/demo/doc/archive/1.3.2/usage.html b/archive/1.3.7/demo/doc/archive/1.3.2/usage.html similarity index 100% rename from build/1.3.7/demo/doc/archive/1.3.2/usage.html rename to archive/1.3.7/demo/doc/archive/1.3.2/usage.html diff --git a/build/1.3.7/demo/doc/archive/1.3.3/content.html b/archive/1.3.7/demo/doc/archive/1.3.3/content.html similarity index 100% rename from build/1.3.7/demo/doc/archive/1.3.3/content.html rename to archive/1.3.7/demo/doc/archive/1.3.3/content.html diff --git a/build/1.3.7/demo/doc/archive/1.3.3/index.html b/archive/1.3.7/demo/doc/archive/1.3.3/index.html similarity index 100% rename from build/1.3.7/demo/doc/archive/1.3.3/index.html rename to archive/1.3.7/demo/doc/archive/1.3.3/index.html diff --git a/build/1.3.7/demo/doc/archive/1.3.3/jsPlumbDoc.css b/archive/1.3.7/demo/doc/archive/1.3.3/jsPlumbDoc.css similarity index 100% rename from build/1.3.7/demo/doc/archive/1.3.3/jsPlumbDoc.css rename to archive/1.3.7/demo/doc/archive/1.3.3/jsPlumbDoc.css diff --git a/build/1.3.7/demo/doc/archive/1.3.3/usage.html b/archive/1.3.7/demo/doc/archive/1.3.3/usage.html similarity index 100% rename from build/1.3.7/demo/doc/archive/1.3.3/usage.html rename to archive/1.3.7/demo/doc/archive/1.3.3/usage.html diff --git a/build/1.3.7/demo/doc/archive/1.3.4/content.html b/archive/1.3.7/demo/doc/archive/1.3.4/content.html similarity index 100% rename from build/1.3.7/demo/doc/archive/1.3.4/content.html rename to archive/1.3.7/demo/doc/archive/1.3.4/content.html diff --git a/build/1.3.7/demo/doc/archive/1.3.4/index.html b/archive/1.3.7/demo/doc/archive/1.3.4/index.html similarity index 100% rename from build/1.3.7/demo/doc/archive/1.3.4/index.html rename to archive/1.3.7/demo/doc/archive/1.3.4/index.html diff --git a/build/1.3.7/demo/doc/archive/1.3.4/jsPlumbDoc.css b/archive/1.3.7/demo/doc/archive/1.3.4/jsPlumbDoc.css similarity index 100% rename from build/1.3.7/demo/doc/archive/1.3.4/jsPlumbDoc.css rename to archive/1.3.7/demo/doc/archive/1.3.4/jsPlumbDoc.css diff --git a/build/1.3.7/demo/doc/archive/1.3.4/usage.html b/archive/1.3.7/demo/doc/archive/1.3.4/usage.html similarity index 100% rename from build/1.3.7/demo/doc/archive/1.3.4/usage.html rename to archive/1.3.7/demo/doc/archive/1.3.4/usage.html diff --git a/build/1.3.7/demo/doc/archive/1.3.5/content.html b/archive/1.3.7/demo/doc/archive/1.3.5/content.html similarity index 100% rename from build/1.3.7/demo/doc/archive/1.3.5/content.html rename to archive/1.3.7/demo/doc/archive/1.3.5/content.html diff --git a/build/1.3.7/demo/doc/archive/1.3.5/index.html b/archive/1.3.7/demo/doc/archive/1.3.5/index.html similarity index 100% rename from build/1.3.7/demo/doc/archive/1.3.5/index.html rename to archive/1.3.7/demo/doc/archive/1.3.5/index.html diff --git a/build/1.3.7/demo/doc/archive/1.3.5/jsPlumbDoc.css b/archive/1.3.7/demo/doc/archive/1.3.5/jsPlumbDoc.css similarity index 100% rename from build/1.3.7/demo/doc/archive/1.3.5/jsPlumbDoc.css rename to archive/1.3.7/demo/doc/archive/1.3.5/jsPlumbDoc.css diff --git a/build/1.3.7/demo/doc/archive/1.3.5/usage.html b/archive/1.3.7/demo/doc/archive/1.3.5/usage.html similarity index 100% rename from build/1.3.7/demo/doc/archive/1.3.5/usage.html rename to archive/1.3.7/demo/doc/archive/1.3.5/usage.html diff --git a/build/1.3.7/demo/doc/archive/1.3.6/content.html b/archive/1.3.7/demo/doc/archive/1.3.6/content.html similarity index 100% rename from build/1.3.7/demo/doc/archive/1.3.6/content.html rename to archive/1.3.7/demo/doc/archive/1.3.6/content.html diff --git a/build/1.3.7/demo/doc/archive/1.3.6/index.html b/archive/1.3.7/demo/doc/archive/1.3.6/index.html similarity index 100% rename from build/1.3.7/demo/doc/archive/1.3.6/index.html rename to archive/1.3.7/demo/doc/archive/1.3.6/index.html diff --git a/build/1.3.7/demo/doc/archive/1.3.6/jsPlumbDoc.css b/archive/1.3.7/demo/doc/archive/1.3.6/jsPlumbDoc.css similarity index 100% rename from build/1.3.7/demo/doc/archive/1.3.6/jsPlumbDoc.css rename to archive/1.3.7/demo/doc/archive/1.3.6/jsPlumbDoc.css diff --git a/build/1.3.7/demo/doc/archive/1.3.6/usage.html b/archive/1.3.7/demo/doc/archive/1.3.6/usage.html similarity index 100% rename from build/1.3.7/demo/doc/archive/1.3.6/usage.html rename to archive/1.3.7/demo/doc/archive/1.3.6/usage.html diff --git a/build/1.3.7/demo/doc/content.html b/archive/1.3.7/demo/doc/content.html similarity index 100% rename from build/1.3.7/demo/doc/content.html rename to archive/1.3.7/demo/doc/content.html diff --git a/build/1.3.7/demo/doc/index.html b/archive/1.3.7/demo/doc/index.html similarity index 100% rename from build/1.3.7/demo/doc/index.html rename to archive/1.3.7/demo/doc/index.html diff --git a/build/1.3.7/demo/doc/jsPlumbDoc.css b/archive/1.3.7/demo/doc/jsPlumbDoc.css similarity index 100% rename from build/1.3.7/demo/doc/jsPlumbDoc.css rename to archive/1.3.7/demo/doc/jsPlumbDoc.css diff --git a/build/1.3.7/demo/doc/usage.html b/archive/1.3.7/demo/doc/usage.html similarity index 100% rename from build/1.3.7/demo/doc/usage.html rename to archive/1.3.7/demo/doc/usage.html diff --git a/build/1.3.7/demo/img/bigdot.jpg b/archive/1.3.7/demo/img/bigdot.jpg similarity index 100% rename from build/1.3.7/demo/img/bigdot.jpg rename to archive/1.3.7/demo/img/bigdot.jpg diff --git a/build/1.3.7/demo/img/bigdot.png b/archive/1.3.7/demo/img/bigdot.png similarity index 100% rename from build/1.3.7/demo/img/bigdot.png rename to archive/1.3.7/demo/img/bigdot.png diff --git a/build/1.3.7/demo/img/bigdot.xcf b/archive/1.3.7/demo/img/bigdot.xcf similarity index 100% rename from build/1.3.7/demo/img/bigdot.xcf rename to archive/1.3.7/demo/img/bigdot.xcf diff --git a/build/1.3.7/demo/img/dragging_1.jpg b/archive/1.3.7/demo/img/dragging_1.jpg similarity index 100% rename from build/1.3.7/demo/img/dragging_1.jpg rename to archive/1.3.7/demo/img/dragging_1.jpg diff --git a/build/1.3.7/demo/img/dragging_2.jpg b/archive/1.3.7/demo/img/dragging_2.jpg similarity index 100% rename from build/1.3.7/demo/img/dragging_2.jpg rename to archive/1.3.7/demo/img/dragging_2.jpg diff --git a/build/1.3.7/demo/img/dragging_3.jpg b/archive/1.3.7/demo/img/dragging_3.jpg similarity index 100% rename from build/1.3.7/demo/img/dragging_3.jpg rename to archive/1.3.7/demo/img/dragging_3.jpg diff --git a/build/1.3.7/demo/img/dynamicAnchorBg.jpg b/archive/1.3.7/demo/img/dynamicAnchorBg.jpg similarity index 100% rename from build/1.3.7/demo/img/dynamicAnchorBg.jpg rename to archive/1.3.7/demo/img/dynamicAnchorBg.jpg diff --git a/build/1.3.7/demo/img/endpointTest1.png b/archive/1.3.7/demo/img/endpointTest1.png similarity index 100% rename from build/1.3.7/demo/img/endpointTest1.png rename to archive/1.3.7/demo/img/endpointTest1.png diff --git a/build/1.3.7/demo/img/endpointTest1.xcf b/archive/1.3.7/demo/img/endpointTest1.xcf similarity index 100% rename from build/1.3.7/demo/img/endpointTest1.xcf rename to archive/1.3.7/demo/img/endpointTest1.xcf diff --git a/build/1.3.7/demo/img/index-bg.gif b/archive/1.3.7/demo/img/index-bg.gif similarity index 100% rename from build/1.3.7/demo/img/index-bg.gif rename to archive/1.3.7/demo/img/index-bg.gif diff --git a/build/1.3.7/demo/img/issue4_final.jpg b/archive/1.3.7/demo/img/issue4_final.jpg similarity index 100% rename from build/1.3.7/demo/img/issue4_final.jpg rename to archive/1.3.7/demo/img/issue4_final.jpg diff --git a/build/1.3.7/demo/img/littledot.png b/archive/1.3.7/demo/img/littledot.png similarity index 100% rename from build/1.3.7/demo/img/littledot.png rename to archive/1.3.7/demo/img/littledot.png diff --git a/build/1.3.7/demo/img/littledot.xcf b/archive/1.3.7/demo/img/littledot.xcf similarity index 100% rename from build/1.3.7/demo/img/littledot.xcf rename to archive/1.3.7/demo/img/littledot.xcf diff --git a/build/1.3.7/demo/img/pattern.jpg b/archive/1.3.7/demo/img/pattern.jpg similarity index 100% rename from build/1.3.7/demo/img/pattern.jpg rename to archive/1.3.7/demo/img/pattern.jpg diff --git a/build/1.3.7/demo/img/swappedAnchors.jpg b/archive/1.3.7/demo/img/swappedAnchors.jpg similarity index 100% rename from build/1.3.7/demo/img/swappedAnchors.jpg rename to archive/1.3.7/demo/img/swappedAnchors.jpg diff --git a/build/1.3.7/demo/index.html b/archive/1.3.7/demo/index.html similarity index 100% rename from build/1.3.7/demo/index.html rename to archive/1.3.7/demo/index.html diff --git a/build/1.3.7/demo/jquery/anchorDemo.html b/archive/1.3.7/demo/jquery/anchorDemo.html similarity index 100% rename from build/1.3.7/demo/jquery/anchorDemo.html rename to archive/1.3.7/demo/jquery/anchorDemo.html diff --git a/build/1.3.7/demo/jquery/chartDemo.html b/archive/1.3.7/demo/jquery/chartDemo.html similarity index 100% rename from build/1.3.7/demo/jquery/chartDemo.html rename to archive/1.3.7/demo/jquery/chartDemo.html diff --git a/build/1.3.7/demo/jquery/demo.html b/archive/1.3.7/demo/jquery/demo.html similarity index 100% rename from build/1.3.7/demo/jquery/demo.html rename to archive/1.3.7/demo/jquery/demo.html diff --git a/build/1.3.7/demo/jquery/dragAnimDemo.html b/archive/1.3.7/demo/jquery/dragAnimDemo.html similarity index 100% rename from build/1.3.7/demo/jquery/dragAnimDemo.html rename to archive/1.3.7/demo/jquery/dragAnimDemo.html diff --git a/build/1.3.7/demo/jquery/draggableConnectorsDemo.html b/archive/1.3.7/demo/jquery/draggableConnectorsDemo.html similarity index 100% rename from build/1.3.7/demo/jquery/draggableConnectorsDemo.html rename to archive/1.3.7/demo/jquery/draggableConnectorsDemo.html diff --git a/build/1.3.7/demo/jquery/dynamicAnchorsDemo.html b/archive/1.3.7/demo/jquery/dynamicAnchorsDemo.html similarity index 100% rename from build/1.3.7/demo/jquery/dynamicAnchorsDemo.html rename to archive/1.3.7/demo/jquery/dynamicAnchorsDemo.html diff --git a/build/1.3.7/demo/jquery/flowchartConnectorsDemo.html b/archive/1.3.7/demo/jquery/flowchartConnectorsDemo.html similarity index 100% rename from build/1.3.7/demo/jquery/flowchartConnectorsDemo.html rename to archive/1.3.7/demo/jquery/flowchartConnectorsDemo.html diff --git a/build/1.3.7/demo/jquery/loadTest.html b/archive/1.3.7/demo/jquery/loadTest.html similarity index 100% rename from build/1.3.7/demo/jquery/loadTest.html rename to archive/1.3.7/demo/jquery/loadTest.html diff --git a/build/1.3.7/demo/jquery/makeSourceDemo.html b/archive/1.3.7/demo/jquery/makeSourceDemo.html similarity index 100% rename from build/1.3.7/demo/jquery/makeSourceDemo.html rename to archive/1.3.7/demo/jquery/makeSourceDemo.html diff --git a/build/1.3.7/demo/jquery/makeTargetDemo.html b/archive/1.3.7/demo/jquery/makeTargetDemo.html similarity index 100% rename from build/1.3.7/demo/jquery/makeTargetDemo.html rename to archive/1.3.7/demo/jquery/makeTargetDemo.html diff --git a/build/1.3.7/demo/jquery/stateMachineDemo.html b/archive/1.3.7/demo/jquery/stateMachineDemo.html similarity index 100% rename from build/1.3.7/demo/jquery/stateMachineDemo.html rename to archive/1.3.7/demo/jquery/stateMachineDemo.html diff --git a/build/1.3.7/demo/js/anchorDemo.js b/archive/1.3.7/demo/js/anchorDemo.js similarity index 100% rename from build/1.3.7/demo/js/anchorDemo.js rename to archive/1.3.7/demo/js/anchorDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.0/anchorDemo.js b/archive/1.3.7/demo/js/archive/1.3.0/anchorDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.0/anchorDemo.js rename to archive/1.3.7/demo/js/archive/1.3.0/anchorDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.0/chartDemo.js b/archive/1.3.7/demo/js/archive/1.3.0/chartDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.0/chartDemo.js rename to archive/1.3.7/demo/js/archive/1.3.0/chartDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.0/demo-helper-jquery.js b/archive/1.3.7/demo/js/archive/1.3.0/demo-helper-jquery.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.0/demo-helper-jquery.js rename to archive/1.3.7/demo/js/archive/1.3.0/demo-helper-jquery.js diff --git a/build/1.3.7/demo/js/archive/1.3.0/demo-helper-mootools.js b/archive/1.3.7/demo/js/archive/1.3.0/demo-helper-mootools.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.0/demo-helper-mootools.js rename to archive/1.3.7/demo/js/archive/1.3.0/demo-helper-mootools.js diff --git a/build/1.3.7/demo/js/archive/1.3.0/demo-helper-yui3.js b/archive/1.3.7/demo/js/archive/1.3.0/demo-helper-yui3.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.0/demo-helper-yui3.js rename to archive/1.3.7/demo/js/archive/1.3.0/demo-helper-yui3.js diff --git a/build/1.3.7/demo/js/archive/1.3.0/demo.js b/archive/1.3.7/demo/js/archive/1.3.0/demo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.0/demo.js rename to archive/1.3.7/demo/js/archive/1.3.0/demo.js diff --git a/build/1.3.7/demo/js/archive/1.3.0/dragAnimDemo-jquery.js b/archive/1.3.7/demo/js/archive/1.3.0/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.0/dragAnimDemo-jquery.js rename to archive/1.3.7/demo/js/archive/1.3.0/dragAnimDemo-jquery.js diff --git a/build/1.3.7/demo/js/archive/1.3.0/dragAnimDemo-mootools.js b/archive/1.3.7/demo/js/archive/1.3.0/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.0/dragAnimDemo-mootools.js rename to archive/1.3.7/demo/js/archive/1.3.0/dragAnimDemo-mootools.js diff --git a/build/1.3.7/demo/js/archive/1.3.0/dragAnimDemo-yui3.js b/archive/1.3.7/demo/js/archive/1.3.0/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.0/dragAnimDemo-yui3.js rename to archive/1.3.7/demo/js/archive/1.3.0/dragAnimDemo-yui3.js diff --git a/build/1.3.7/demo/js/archive/1.3.0/dragAnimDemo.js b/archive/1.3.7/demo/js/archive/1.3.0/dragAnimDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.0/dragAnimDemo.js rename to archive/1.3.7/demo/js/archive/1.3.0/dragAnimDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js b/archive/1.3.7/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js rename to archive/1.3.7/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.7/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js b/archive/1.3.7/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js rename to archive/1.3.7/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.7/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js b/archive/1.3.7/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js rename to archive/1.3.7/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.7/demo/js/archive/1.3.0/draggableConnectorsDemo.js b/archive/1.3.7/demo/js/archive/1.3.0/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.0/draggableConnectorsDemo.js rename to archive/1.3.7/demo/js/archive/1.3.0/draggableConnectorsDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.0/dynamicAnchorsDemo.js b/archive/1.3.7/demo/js/archive/1.3.0/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.0/dynamicAnchorsDemo.js rename to archive/1.3.7/demo/js/archive/1.3.0/dynamicAnchorsDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.0/flowchartConnectorsDemo.js b/archive/1.3.7/demo/js/archive/1.3.0/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.0/flowchartConnectorsDemo.js rename to archive/1.3.7/demo/js/archive/1.3.0/flowchartConnectorsDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.1/anchorDemo.js b/archive/1.3.7/demo/js/archive/1.3.1/anchorDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.1/anchorDemo.js rename to archive/1.3.7/demo/js/archive/1.3.1/anchorDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.1/chartDemo.js b/archive/1.3.7/demo/js/archive/1.3.1/chartDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.1/chartDemo.js rename to archive/1.3.7/demo/js/archive/1.3.1/chartDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.1/demo-helper-jquery.js b/archive/1.3.7/demo/js/archive/1.3.1/demo-helper-jquery.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.1/demo-helper-jquery.js rename to archive/1.3.7/demo/js/archive/1.3.1/demo-helper-jquery.js diff --git a/build/1.3.7/demo/js/archive/1.3.1/demo-helper-mootools.js b/archive/1.3.7/demo/js/archive/1.3.1/demo-helper-mootools.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.1/demo-helper-mootools.js rename to archive/1.3.7/demo/js/archive/1.3.1/demo-helper-mootools.js diff --git a/build/1.3.7/demo/js/archive/1.3.1/demo-helper-yui3.js b/archive/1.3.7/demo/js/archive/1.3.1/demo-helper-yui3.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.1/demo-helper-yui3.js rename to archive/1.3.7/demo/js/archive/1.3.1/demo-helper-yui3.js diff --git a/build/1.3.7/demo/js/archive/1.3.1/demo.js b/archive/1.3.7/demo/js/archive/1.3.1/demo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.1/demo.js rename to archive/1.3.7/demo/js/archive/1.3.1/demo.js diff --git a/build/1.3.7/demo/js/archive/1.3.1/dragAnimDemo-jquery.js b/archive/1.3.7/demo/js/archive/1.3.1/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.1/dragAnimDemo-jquery.js rename to archive/1.3.7/demo/js/archive/1.3.1/dragAnimDemo-jquery.js diff --git a/build/1.3.7/demo/js/archive/1.3.1/dragAnimDemo-mootools.js b/archive/1.3.7/demo/js/archive/1.3.1/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.1/dragAnimDemo-mootools.js rename to archive/1.3.7/demo/js/archive/1.3.1/dragAnimDemo-mootools.js diff --git a/build/1.3.7/demo/js/archive/1.3.1/dragAnimDemo-yui3.js b/archive/1.3.7/demo/js/archive/1.3.1/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.1/dragAnimDemo-yui3.js rename to archive/1.3.7/demo/js/archive/1.3.1/dragAnimDemo-yui3.js diff --git a/build/1.3.7/demo/js/archive/1.3.1/dragAnimDemo.js b/archive/1.3.7/demo/js/archive/1.3.1/dragAnimDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.1/dragAnimDemo.js rename to archive/1.3.7/demo/js/archive/1.3.1/dragAnimDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js b/archive/1.3.7/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js rename to archive/1.3.7/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.7/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js b/archive/1.3.7/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js rename to archive/1.3.7/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.7/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js b/archive/1.3.7/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js rename to archive/1.3.7/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.7/demo/js/archive/1.3.1/draggableConnectorsDemo.js b/archive/1.3.7/demo/js/archive/1.3.1/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.1/draggableConnectorsDemo.js rename to archive/1.3.7/demo/js/archive/1.3.1/draggableConnectorsDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.1/dynamicAnchorsDemo.js b/archive/1.3.7/demo/js/archive/1.3.1/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.1/dynamicAnchorsDemo.js rename to archive/1.3.7/demo/js/archive/1.3.1/dynamicAnchorsDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.1/flowchartConnectorsDemo.js b/archive/1.3.7/demo/js/archive/1.3.1/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.1/flowchartConnectorsDemo.js rename to archive/1.3.7/demo/js/archive/1.3.1/flowchartConnectorsDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.2/anchorDemo.js b/archive/1.3.7/demo/js/archive/1.3.2/anchorDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.2/anchorDemo.js rename to archive/1.3.7/demo/js/archive/1.3.2/anchorDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.2/chartDemo.js b/archive/1.3.7/demo/js/archive/1.3.2/chartDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.2/chartDemo.js rename to archive/1.3.7/demo/js/archive/1.3.2/chartDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.2/demo-helper-jquery.js b/archive/1.3.7/demo/js/archive/1.3.2/demo-helper-jquery.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.2/demo-helper-jquery.js rename to archive/1.3.7/demo/js/archive/1.3.2/demo-helper-jquery.js diff --git a/build/1.3.7/demo/js/archive/1.3.2/demo-helper-mootools.js b/archive/1.3.7/demo/js/archive/1.3.2/demo-helper-mootools.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.2/demo-helper-mootools.js rename to archive/1.3.7/demo/js/archive/1.3.2/demo-helper-mootools.js diff --git a/build/1.3.7/demo/js/archive/1.3.2/demo-helper-yui3.js b/archive/1.3.7/demo/js/archive/1.3.2/demo-helper-yui3.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.2/demo-helper-yui3.js rename to archive/1.3.7/demo/js/archive/1.3.2/demo-helper-yui3.js diff --git a/build/1.3.7/demo/js/archive/1.3.2/demo.js b/archive/1.3.7/demo/js/archive/1.3.2/demo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.2/demo.js rename to archive/1.3.7/demo/js/archive/1.3.2/demo.js diff --git a/build/1.3.7/demo/js/archive/1.3.2/dragAnimDemo-jquery.js b/archive/1.3.7/demo/js/archive/1.3.2/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.2/dragAnimDemo-jquery.js rename to archive/1.3.7/demo/js/archive/1.3.2/dragAnimDemo-jquery.js diff --git a/build/1.3.7/demo/js/archive/1.3.2/dragAnimDemo-mootools.js b/archive/1.3.7/demo/js/archive/1.3.2/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.2/dragAnimDemo-mootools.js rename to archive/1.3.7/demo/js/archive/1.3.2/dragAnimDemo-mootools.js diff --git a/build/1.3.7/demo/js/archive/1.3.2/dragAnimDemo-yui3.js b/archive/1.3.7/demo/js/archive/1.3.2/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.2/dragAnimDemo-yui3.js rename to archive/1.3.7/demo/js/archive/1.3.2/dragAnimDemo-yui3.js diff --git a/build/1.3.7/demo/js/archive/1.3.2/dragAnimDemo.js b/archive/1.3.7/demo/js/archive/1.3.2/dragAnimDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.2/dragAnimDemo.js rename to archive/1.3.7/demo/js/archive/1.3.2/dragAnimDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js b/archive/1.3.7/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js rename to archive/1.3.7/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.7/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js b/archive/1.3.7/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js rename to archive/1.3.7/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.7/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js b/archive/1.3.7/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js rename to archive/1.3.7/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.7/demo/js/archive/1.3.2/draggableConnectorsDemo.js b/archive/1.3.7/demo/js/archive/1.3.2/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.2/draggableConnectorsDemo.js rename to archive/1.3.7/demo/js/archive/1.3.2/draggableConnectorsDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.2/dynamicAnchorsDemo.js b/archive/1.3.7/demo/js/archive/1.3.2/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.2/dynamicAnchorsDemo.js rename to archive/1.3.7/demo/js/archive/1.3.2/dynamicAnchorsDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.2/flowchartConnectorsDemo.js b/archive/1.3.7/demo/js/archive/1.3.2/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.2/flowchartConnectorsDemo.js rename to archive/1.3.7/demo/js/archive/1.3.2/flowchartConnectorsDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.3/anchorDemo.js b/archive/1.3.7/demo/js/archive/1.3.3/anchorDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.3/anchorDemo.js rename to archive/1.3.7/demo/js/archive/1.3.3/anchorDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.3/chartDemo.js b/archive/1.3.7/demo/js/archive/1.3.3/chartDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.3/chartDemo.js rename to archive/1.3.7/demo/js/archive/1.3.3/chartDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.3/demo-helper-jquery.js b/archive/1.3.7/demo/js/archive/1.3.3/demo-helper-jquery.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.3/demo-helper-jquery.js rename to archive/1.3.7/demo/js/archive/1.3.3/demo-helper-jquery.js diff --git a/build/1.3.7/demo/js/archive/1.3.3/demo-helper-mootools.js b/archive/1.3.7/demo/js/archive/1.3.3/demo-helper-mootools.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.3/demo-helper-mootools.js rename to archive/1.3.7/demo/js/archive/1.3.3/demo-helper-mootools.js diff --git a/build/1.3.7/demo/js/archive/1.3.3/demo-helper-yui3.js b/archive/1.3.7/demo/js/archive/1.3.3/demo-helper-yui3.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.3/demo-helper-yui3.js rename to archive/1.3.7/demo/js/archive/1.3.3/demo-helper-yui3.js diff --git a/build/1.3.7/demo/js/archive/1.3.3/demo.js b/archive/1.3.7/demo/js/archive/1.3.3/demo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.3/demo.js rename to archive/1.3.7/demo/js/archive/1.3.3/demo.js diff --git a/build/1.3.7/demo/js/archive/1.3.3/dragAnimDemo-jquery.js b/archive/1.3.7/demo/js/archive/1.3.3/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.3/dragAnimDemo-jquery.js rename to archive/1.3.7/demo/js/archive/1.3.3/dragAnimDemo-jquery.js diff --git a/build/1.3.7/demo/js/archive/1.3.3/dragAnimDemo-mootools.js b/archive/1.3.7/demo/js/archive/1.3.3/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.3/dragAnimDemo-mootools.js rename to archive/1.3.7/demo/js/archive/1.3.3/dragAnimDemo-mootools.js diff --git a/build/1.3.7/demo/js/archive/1.3.3/dragAnimDemo-yui3.js b/archive/1.3.7/demo/js/archive/1.3.3/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.3/dragAnimDemo-yui3.js rename to archive/1.3.7/demo/js/archive/1.3.3/dragAnimDemo-yui3.js diff --git a/build/1.3.7/demo/js/archive/1.3.3/dragAnimDemo.js b/archive/1.3.7/demo/js/archive/1.3.3/dragAnimDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.3/dragAnimDemo.js rename to archive/1.3.7/demo/js/archive/1.3.3/dragAnimDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js b/archive/1.3.7/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js rename to archive/1.3.7/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.7/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js b/archive/1.3.7/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js rename to archive/1.3.7/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.7/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js b/archive/1.3.7/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js rename to archive/1.3.7/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.7/demo/js/archive/1.3.3/draggableConnectorsDemo.js b/archive/1.3.7/demo/js/archive/1.3.3/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.3/draggableConnectorsDemo.js rename to archive/1.3.7/demo/js/archive/1.3.3/draggableConnectorsDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.3/dynamicAnchorsDemo.js b/archive/1.3.7/demo/js/archive/1.3.3/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.3/dynamicAnchorsDemo.js rename to archive/1.3.7/demo/js/archive/1.3.3/dynamicAnchorsDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.3/flowchartConnectorsDemo.js b/archive/1.3.7/demo/js/archive/1.3.3/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.3/flowchartConnectorsDemo.js rename to archive/1.3.7/demo/js/archive/1.3.3/flowchartConnectorsDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js b/archive/1.3.7/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js rename to archive/1.3.7/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js diff --git a/build/1.3.8/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all.js b/archive/1.3.7/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all.js rename to archive/1.3.7/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all.js diff --git a/build/1.3.8/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js b/archive/1.3.7/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js rename to archive/1.3.7/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js diff --git a/build/1.3.8/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js b/archive/1.3.7/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js rename to archive/1.3.7/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js diff --git a/build/1.3.8/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js b/archive/1.3.7/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js rename to archive/1.3.7/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js diff --git a/build/1.3.8/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js b/archive/1.3.7/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js rename to archive/1.3.7/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js diff --git a/build/1.3.8/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js b/archive/1.3.7/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js rename to archive/1.3.7/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js diff --git a/build/1.3.7/demo/js/archive/1.3.4/anchorDemo.js b/archive/1.3.7/demo/js/archive/1.3.4/anchorDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.4/anchorDemo.js rename to archive/1.3.7/demo/js/archive/1.3.4/anchorDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.4/chartDemo.js b/archive/1.3.7/demo/js/archive/1.3.4/chartDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.4/chartDemo.js rename to archive/1.3.7/demo/js/archive/1.3.4/chartDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.4/demo-helper-jquery.js b/archive/1.3.7/demo/js/archive/1.3.4/demo-helper-jquery.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.4/demo-helper-jquery.js rename to archive/1.3.7/demo/js/archive/1.3.4/demo-helper-jquery.js diff --git a/build/1.3.7/demo/js/archive/1.3.4/demo-helper-mootools.js b/archive/1.3.7/demo/js/archive/1.3.4/demo-helper-mootools.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.4/demo-helper-mootools.js rename to archive/1.3.7/demo/js/archive/1.3.4/demo-helper-mootools.js diff --git a/build/1.3.7/demo/js/archive/1.3.4/demo-helper-yui3.js b/archive/1.3.7/demo/js/archive/1.3.4/demo-helper-yui3.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.4/demo-helper-yui3.js rename to archive/1.3.7/demo/js/archive/1.3.4/demo-helper-yui3.js diff --git a/build/1.3.7/demo/js/archive/1.3.4/demo-list.js b/archive/1.3.7/demo/js/archive/1.3.4/demo-list.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.4/demo-list.js rename to archive/1.3.7/demo/js/archive/1.3.4/demo-list.js diff --git a/build/1.3.7/demo/js/archive/1.3.4/demo.js b/archive/1.3.7/demo/js/archive/1.3.4/demo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.4/demo.js rename to archive/1.3.7/demo/js/archive/1.3.4/demo.js diff --git a/build/1.3.7/demo/js/archive/1.3.4/dragAnimDemo-jquery.js b/archive/1.3.7/demo/js/archive/1.3.4/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.4/dragAnimDemo-jquery.js rename to archive/1.3.7/demo/js/archive/1.3.4/dragAnimDemo-jquery.js diff --git a/build/1.3.7/demo/js/archive/1.3.4/dragAnimDemo-mootools.js b/archive/1.3.7/demo/js/archive/1.3.4/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.4/dragAnimDemo-mootools.js rename to archive/1.3.7/demo/js/archive/1.3.4/dragAnimDemo-mootools.js diff --git a/build/1.3.7/demo/js/archive/1.3.4/dragAnimDemo-yui3.js b/archive/1.3.7/demo/js/archive/1.3.4/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.4/dragAnimDemo-yui3.js rename to archive/1.3.7/demo/js/archive/1.3.4/dragAnimDemo-yui3.js diff --git a/build/1.3.7/demo/js/archive/1.3.4/dragAnimDemo.js b/archive/1.3.7/demo/js/archive/1.3.4/dragAnimDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.4/dragAnimDemo.js rename to archive/1.3.7/demo/js/archive/1.3.4/dragAnimDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.4/draggableConnectorsDemo-jquery.js b/archive/1.3.7/demo/js/archive/1.3.4/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.4/draggableConnectorsDemo-jquery.js rename to archive/1.3.7/demo/js/archive/1.3.4/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.7/demo/js/archive/1.3.4/draggableConnectorsDemo-mootools.js b/archive/1.3.7/demo/js/archive/1.3.4/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.4/draggableConnectorsDemo-mootools.js rename to archive/1.3.7/demo/js/archive/1.3.4/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.7/demo/js/archive/1.3.4/draggableConnectorsDemo-yui3.js b/archive/1.3.7/demo/js/archive/1.3.4/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.4/draggableConnectorsDemo-yui3.js rename to archive/1.3.7/demo/js/archive/1.3.4/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.7/demo/js/archive/1.3.4/draggableConnectorsDemo.js b/archive/1.3.7/demo/js/archive/1.3.4/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.4/draggableConnectorsDemo.js rename to archive/1.3.7/demo/js/archive/1.3.4/draggableConnectorsDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.4/dynamicAnchorsDemo.js b/archive/1.3.7/demo/js/archive/1.3.4/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.4/dynamicAnchorsDemo.js rename to archive/1.3.7/demo/js/archive/1.3.4/dynamicAnchorsDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.4/flowchartConnectorsDemo.js b/archive/1.3.7/demo/js/archive/1.3.4/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.4/flowchartConnectorsDemo.js rename to archive/1.3.7/demo/js/archive/1.3.4/flowchartConnectorsDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.4/makeSourceDemo.js b/archive/1.3.7/demo/js/archive/1.3.4/makeSourceDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.4/makeSourceDemo.js rename to archive/1.3.7/demo/js/archive/1.3.4/makeSourceDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.4/makeTargetDemo.js b/archive/1.3.7/demo/js/archive/1.3.4/makeTargetDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.4/makeTargetDemo.js rename to archive/1.3.7/demo/js/archive/1.3.4/makeTargetDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.4/stateMachineDemo-jquery.js b/archive/1.3.7/demo/js/archive/1.3.4/stateMachineDemo-jquery.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.4/stateMachineDemo-jquery.js rename to archive/1.3.7/demo/js/archive/1.3.4/stateMachineDemo-jquery.js diff --git a/build/1.3.7/demo/js/archive/1.3.4/stateMachineDemo-mootools.js b/archive/1.3.7/demo/js/archive/1.3.4/stateMachineDemo-mootools.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.4/stateMachineDemo-mootools.js rename to archive/1.3.7/demo/js/archive/1.3.4/stateMachineDemo-mootools.js diff --git a/build/1.3.7/demo/js/archive/1.3.4/stateMachineDemo-yui3.js b/archive/1.3.7/demo/js/archive/1.3.4/stateMachineDemo-yui3.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.4/stateMachineDemo-yui3.js rename to archive/1.3.7/demo/js/archive/1.3.4/stateMachineDemo-yui3.js diff --git a/build/1.3.7/demo/js/archive/1.3.4/stateMachineDemo.js b/archive/1.3.7/demo/js/archive/1.3.4/stateMachineDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.4/stateMachineDemo.js rename to archive/1.3.7/demo/js/archive/1.3.4/stateMachineDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.5/anchorDemo.js b/archive/1.3.7/demo/js/archive/1.3.5/anchorDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.5/anchorDemo.js rename to archive/1.3.7/demo/js/archive/1.3.5/anchorDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.5/chartDemo.js b/archive/1.3.7/demo/js/archive/1.3.5/chartDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.5/chartDemo.js rename to archive/1.3.7/demo/js/archive/1.3.5/chartDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.5/demo-helper-jquery.js b/archive/1.3.7/demo/js/archive/1.3.5/demo-helper-jquery.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.5/demo-helper-jquery.js rename to archive/1.3.7/demo/js/archive/1.3.5/demo-helper-jquery.js diff --git a/build/1.3.7/demo/js/archive/1.3.5/demo-helper-mootools.js b/archive/1.3.7/demo/js/archive/1.3.5/demo-helper-mootools.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.5/demo-helper-mootools.js rename to archive/1.3.7/demo/js/archive/1.3.5/demo-helper-mootools.js diff --git a/build/1.3.7/demo/js/archive/1.3.5/demo-helper-yui3.js b/archive/1.3.7/demo/js/archive/1.3.5/demo-helper-yui3.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.5/demo-helper-yui3.js rename to archive/1.3.7/demo/js/archive/1.3.5/demo-helper-yui3.js diff --git a/build/1.3.7/demo/js/archive/1.3.5/demo-list.js b/archive/1.3.7/demo/js/archive/1.3.5/demo-list.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.5/demo-list.js rename to archive/1.3.7/demo/js/archive/1.3.5/demo-list.js diff --git a/build/1.3.7/demo/js/archive/1.3.5/demo.js b/archive/1.3.7/demo/js/archive/1.3.5/demo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.5/demo.js rename to archive/1.3.7/demo/js/archive/1.3.5/demo.js diff --git a/build/1.3.7/demo/js/archive/1.3.5/dragAnimDemo-jquery.js b/archive/1.3.7/demo/js/archive/1.3.5/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.5/dragAnimDemo-jquery.js rename to archive/1.3.7/demo/js/archive/1.3.5/dragAnimDemo-jquery.js diff --git a/build/1.3.7/demo/js/archive/1.3.5/dragAnimDemo-mootools.js b/archive/1.3.7/demo/js/archive/1.3.5/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.5/dragAnimDemo-mootools.js rename to archive/1.3.7/demo/js/archive/1.3.5/dragAnimDemo-mootools.js diff --git a/build/1.3.7/demo/js/archive/1.3.5/dragAnimDemo-yui3.js b/archive/1.3.7/demo/js/archive/1.3.5/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.5/dragAnimDemo-yui3.js rename to archive/1.3.7/demo/js/archive/1.3.5/dragAnimDemo-yui3.js diff --git a/build/1.3.7/demo/js/archive/1.3.5/dragAnimDemo.js b/archive/1.3.7/demo/js/archive/1.3.5/dragAnimDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.5/dragAnimDemo.js rename to archive/1.3.7/demo/js/archive/1.3.5/dragAnimDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.5/draggableConnectorsDemo-jquery.js b/archive/1.3.7/demo/js/archive/1.3.5/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.5/draggableConnectorsDemo-jquery.js rename to archive/1.3.7/demo/js/archive/1.3.5/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.7/demo/js/archive/1.3.5/draggableConnectorsDemo-mootools.js b/archive/1.3.7/demo/js/archive/1.3.5/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.5/draggableConnectorsDemo-mootools.js rename to archive/1.3.7/demo/js/archive/1.3.5/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.7/demo/js/archive/1.3.5/draggableConnectorsDemo-yui3.js b/archive/1.3.7/demo/js/archive/1.3.5/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.5/draggableConnectorsDemo-yui3.js rename to archive/1.3.7/demo/js/archive/1.3.5/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.7/demo/js/archive/1.3.5/draggableConnectorsDemo.js b/archive/1.3.7/demo/js/archive/1.3.5/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.5/draggableConnectorsDemo.js rename to archive/1.3.7/demo/js/archive/1.3.5/draggableConnectorsDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.5/dynamicAnchorsDemo.js b/archive/1.3.7/demo/js/archive/1.3.5/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.5/dynamicAnchorsDemo.js rename to archive/1.3.7/demo/js/archive/1.3.5/dynamicAnchorsDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.5/flowchartConnectorsDemo.js b/archive/1.3.7/demo/js/archive/1.3.5/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.5/flowchartConnectorsDemo.js rename to archive/1.3.7/demo/js/archive/1.3.5/flowchartConnectorsDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.5/loadTest.js b/archive/1.3.7/demo/js/archive/1.3.5/loadTest.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.5/loadTest.js rename to archive/1.3.7/demo/js/archive/1.3.5/loadTest.js diff --git a/build/1.3.7/demo/js/archive/1.3.5/makeSourceDemo.js b/archive/1.3.7/demo/js/archive/1.3.5/makeSourceDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.5/makeSourceDemo.js rename to archive/1.3.7/demo/js/archive/1.3.5/makeSourceDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.5/makeTargetDemo.js b/archive/1.3.7/demo/js/archive/1.3.5/makeTargetDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.5/makeTargetDemo.js rename to archive/1.3.7/demo/js/archive/1.3.5/makeTargetDemo.js diff --git a/build/1.3.7/demo/js/archive/1.3.5/stateMachineDemo-jquery.js b/archive/1.3.7/demo/js/archive/1.3.5/stateMachineDemo-jquery.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.5/stateMachineDemo-jquery.js rename to archive/1.3.7/demo/js/archive/1.3.5/stateMachineDemo-jquery.js diff --git a/build/1.3.7/demo/js/archive/1.3.5/stateMachineDemo-mootools.js b/archive/1.3.7/demo/js/archive/1.3.5/stateMachineDemo-mootools.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.5/stateMachineDemo-mootools.js rename to archive/1.3.7/demo/js/archive/1.3.5/stateMachineDemo-mootools.js diff --git a/build/1.3.7/demo/js/archive/1.3.5/stateMachineDemo-yui3.js b/archive/1.3.7/demo/js/archive/1.3.5/stateMachineDemo-yui3.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.5/stateMachineDemo-yui3.js rename to archive/1.3.7/demo/js/archive/1.3.5/stateMachineDemo-yui3.js diff --git a/build/1.3.7/demo/js/archive/1.3.5/stateMachineDemo.js b/archive/1.3.7/demo/js/archive/1.3.5/stateMachineDemo.js similarity index 100% rename from build/1.3.7/demo/js/archive/1.3.5/stateMachineDemo.js rename to archive/1.3.7/demo/js/archive/1.3.5/stateMachineDemo.js diff --git a/build/1.3.7/demo/js/chartDemo.js b/archive/1.3.7/demo/js/chartDemo.js similarity index 100% rename from build/1.3.7/demo/js/chartDemo.js rename to archive/1.3.7/demo/js/chartDemo.js diff --git a/build/1.3.7/demo/js/demo-helper-jquery.js b/archive/1.3.7/demo/js/demo-helper-jquery.js similarity index 100% rename from build/1.3.7/demo/js/demo-helper-jquery.js rename to archive/1.3.7/demo/js/demo-helper-jquery.js diff --git a/build/1.3.7/demo/js/demo-helper-mootools.js b/archive/1.3.7/demo/js/demo-helper-mootools.js similarity index 100% rename from build/1.3.7/demo/js/demo-helper-mootools.js rename to archive/1.3.7/demo/js/demo-helper-mootools.js diff --git a/build/1.3.7/demo/js/demo-helper-yui3.js b/archive/1.3.7/demo/js/demo-helper-yui3.js similarity index 100% rename from build/1.3.7/demo/js/demo-helper-yui3.js rename to archive/1.3.7/demo/js/demo-helper-yui3.js diff --git a/build/1.3.7/demo/js/demo-list.js b/archive/1.3.7/demo/js/demo-list.js similarity index 100% rename from build/1.3.7/demo/js/demo-list.js rename to archive/1.3.7/demo/js/demo-list.js diff --git a/build/1.3.7/demo/js/demo.js b/archive/1.3.7/demo/js/demo.js similarity index 100% rename from build/1.3.7/demo/js/demo.js rename to archive/1.3.7/demo/js/demo.js diff --git a/build/1.3.7/demo/js/dragAnimDemo-jquery.js b/archive/1.3.7/demo/js/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.7/demo/js/dragAnimDemo-jquery.js rename to archive/1.3.7/demo/js/dragAnimDemo-jquery.js diff --git a/build/1.3.7/demo/js/dragAnimDemo-mootools.js b/archive/1.3.7/demo/js/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.7/demo/js/dragAnimDemo-mootools.js rename to archive/1.3.7/demo/js/dragAnimDemo-mootools.js diff --git a/build/1.3.7/demo/js/dragAnimDemo-yui3.js b/archive/1.3.7/demo/js/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.7/demo/js/dragAnimDemo-yui3.js rename to archive/1.3.7/demo/js/dragAnimDemo-yui3.js diff --git a/build/1.3.7/demo/js/dragAnimDemo.js b/archive/1.3.7/demo/js/dragAnimDemo.js similarity index 100% rename from build/1.3.7/demo/js/dragAnimDemo.js rename to archive/1.3.7/demo/js/dragAnimDemo.js diff --git a/build/1.3.7/demo/js/draggableConnectorsDemo-jquery.js b/archive/1.3.7/demo/js/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.7/demo/js/draggableConnectorsDemo-jquery.js rename to archive/1.3.7/demo/js/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.7/demo/js/draggableConnectorsDemo-mootools.js b/archive/1.3.7/demo/js/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.7/demo/js/draggableConnectorsDemo-mootools.js rename to archive/1.3.7/demo/js/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.7/demo/js/draggableConnectorsDemo-yui3.js b/archive/1.3.7/demo/js/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.7/demo/js/draggableConnectorsDemo-yui3.js rename to archive/1.3.7/demo/js/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.7/demo/js/draggableConnectorsDemo.js b/archive/1.3.7/demo/js/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.7/demo/js/draggableConnectorsDemo.js rename to archive/1.3.7/demo/js/draggableConnectorsDemo.js diff --git a/build/1.3.7/demo/js/dynamicAnchorsDemo.js b/archive/1.3.7/demo/js/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.7/demo/js/dynamicAnchorsDemo.js rename to archive/1.3.7/demo/js/dynamicAnchorsDemo.js diff --git a/build/1.3.7/demo/js/flowchartConnectorsDemo.js b/archive/1.3.7/demo/js/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.7/demo/js/flowchartConnectorsDemo.js rename to archive/1.3.7/demo/js/flowchartConnectorsDemo.js diff --git a/build/1.3.7/demo/js/jquery.jsPlumb-1.3.7-all-min.js b/archive/1.3.7/demo/js/jquery.jsPlumb-1.3.7-all-min.js similarity index 100% rename from build/1.3.7/demo/js/jquery.jsPlumb-1.3.7-all-min.js rename to archive/1.3.7/demo/js/jquery.jsPlumb-1.3.7-all-min.js diff --git a/build/1.3.7/demo/js/jquery.jsPlumb-1.3.7-all.js b/archive/1.3.7/demo/js/jquery.jsPlumb-1.3.7-all.js similarity index 100% rename from build/1.3.7/demo/js/jquery.jsPlumb-1.3.7-all.js rename to archive/1.3.7/demo/js/jquery.jsPlumb-1.3.7-all.js diff --git a/build/1.3.7/demo/js/loadTest.js b/archive/1.3.7/demo/js/loadTest.js similarity index 100% rename from build/1.3.7/demo/js/loadTest.js rename to archive/1.3.7/demo/js/loadTest.js diff --git a/build/1.3.7/demo/js/makeSourceDemo.js b/archive/1.3.7/demo/js/makeSourceDemo.js similarity index 100% rename from build/1.3.7/demo/js/makeSourceDemo.js rename to archive/1.3.7/demo/js/makeSourceDemo.js diff --git a/build/1.3.7/demo/js/makeTargetDemo.js b/archive/1.3.7/demo/js/makeTargetDemo.js similarity index 100% rename from build/1.3.7/demo/js/makeTargetDemo.js rename to archive/1.3.7/demo/js/makeTargetDemo.js diff --git a/build/1.3.7/demo/js/mootools-1.3.2.1-more.js b/archive/1.3.7/demo/js/mootools-1.3.2.1-more.js similarity index 100% rename from build/1.3.7/demo/js/mootools-1.3.2.1-more.js rename to archive/1.3.7/demo/js/mootools-1.3.2.1-more.js diff --git a/build/1.3.7/demo/js/mootools.jsPlumb-1.3.7-all-min.js b/archive/1.3.7/demo/js/mootools.jsPlumb-1.3.7-all-min.js similarity index 100% rename from build/1.3.7/demo/js/mootools.jsPlumb-1.3.7-all-min.js rename to archive/1.3.7/demo/js/mootools.jsPlumb-1.3.7-all-min.js diff --git a/build/1.3.7/demo/js/mootools.jsPlumb-1.3.7-all.js b/archive/1.3.7/demo/js/mootools.jsPlumb-1.3.7-all.js similarity index 100% rename from build/1.3.7/demo/js/mootools.jsPlumb-1.3.7-all.js rename to archive/1.3.7/demo/js/mootools.jsPlumb-1.3.7-all.js diff --git a/build/1.3.7/demo/js/stateMachineDemo-jquery.js b/archive/1.3.7/demo/js/stateMachineDemo-jquery.js similarity index 100% rename from build/1.3.7/demo/js/stateMachineDemo-jquery.js rename to archive/1.3.7/demo/js/stateMachineDemo-jquery.js diff --git a/build/1.3.7/demo/js/stateMachineDemo-mootools.js b/archive/1.3.7/demo/js/stateMachineDemo-mootools.js similarity index 100% rename from build/1.3.7/demo/js/stateMachineDemo-mootools.js rename to archive/1.3.7/demo/js/stateMachineDemo-mootools.js diff --git a/build/1.3.7/demo/js/stateMachineDemo-yui3.js b/archive/1.3.7/demo/js/stateMachineDemo-yui3.js similarity index 100% rename from build/1.3.7/demo/js/stateMachineDemo-yui3.js rename to archive/1.3.7/demo/js/stateMachineDemo-yui3.js diff --git a/build/1.3.7/demo/js/stateMachineDemo.js b/archive/1.3.7/demo/js/stateMachineDemo.js similarity index 100% rename from build/1.3.7/demo/js/stateMachineDemo.js rename to archive/1.3.7/demo/js/stateMachineDemo.js diff --git a/build/1.3.7/demo/js/yui-3.3.0-min.js b/archive/1.3.7/demo/js/yui-3.3.0-min.js similarity index 100% rename from build/1.3.7/demo/js/yui-3.3.0-min.js rename to archive/1.3.7/demo/js/yui-3.3.0-min.js diff --git a/build/1.3.7/demo/js/yui.jsPlumb-1.3.7-all-min.js b/archive/1.3.7/demo/js/yui.jsPlumb-1.3.7-all-min.js similarity index 100% rename from build/1.3.7/demo/js/yui.jsPlumb-1.3.7-all-min.js rename to archive/1.3.7/demo/js/yui.jsPlumb-1.3.7-all-min.js diff --git a/build/1.3.7/demo/js/yui.jsPlumb-1.3.7-all.js b/archive/1.3.7/demo/js/yui.jsPlumb-1.3.7-all.js similarity index 100% rename from build/1.3.7/demo/js/yui.jsPlumb-1.3.7-all.js rename to archive/1.3.7/demo/js/yui.jsPlumb-1.3.7-all.js diff --git a/build/1.3.7/demo/mootools/anchorDemo.html b/archive/1.3.7/demo/mootools/anchorDemo.html similarity index 100% rename from build/1.3.7/demo/mootools/anchorDemo.html rename to archive/1.3.7/demo/mootools/anchorDemo.html diff --git a/build/1.3.7/demo/mootools/chartDemo.html b/archive/1.3.7/demo/mootools/chartDemo.html similarity index 100% rename from build/1.3.7/demo/mootools/chartDemo.html rename to archive/1.3.7/demo/mootools/chartDemo.html diff --git a/build/1.3.7/demo/mootools/demo.html b/archive/1.3.7/demo/mootools/demo.html similarity index 100% rename from build/1.3.7/demo/mootools/demo.html rename to archive/1.3.7/demo/mootools/demo.html diff --git a/build/1.3.7/demo/mootools/dragAnimDemo.html b/archive/1.3.7/demo/mootools/dragAnimDemo.html similarity index 100% rename from build/1.3.7/demo/mootools/dragAnimDemo.html rename to archive/1.3.7/demo/mootools/dragAnimDemo.html diff --git a/build/1.3.7/demo/mootools/draggableConnectorsDemo.html b/archive/1.3.7/demo/mootools/draggableConnectorsDemo.html similarity index 100% rename from build/1.3.7/demo/mootools/draggableConnectorsDemo.html rename to archive/1.3.7/demo/mootools/draggableConnectorsDemo.html diff --git a/build/1.3.7/demo/mootools/dynamicAnchorsDemo.html b/archive/1.3.7/demo/mootools/dynamicAnchorsDemo.html similarity index 100% rename from build/1.3.7/demo/mootools/dynamicAnchorsDemo.html rename to archive/1.3.7/demo/mootools/dynamicAnchorsDemo.html diff --git a/build/1.3.7/demo/mootools/flowchartConnectorsDemo.html b/archive/1.3.7/demo/mootools/flowchartConnectorsDemo.html similarity index 100% rename from build/1.3.7/demo/mootools/flowchartConnectorsDemo.html rename to archive/1.3.7/demo/mootools/flowchartConnectorsDemo.html diff --git a/build/1.3.7/demo/mootools/stateMachineDemo.html b/archive/1.3.7/demo/mootools/stateMachineDemo.html similarity index 100% rename from build/1.3.7/demo/mootools/stateMachineDemo.html rename to archive/1.3.7/demo/mootools/stateMachineDemo.html diff --git a/build/1.3.7/demo/yui3/anchorDemo.html b/archive/1.3.7/demo/yui3/anchorDemo.html similarity index 100% rename from build/1.3.7/demo/yui3/anchorDemo.html rename to archive/1.3.7/demo/yui3/anchorDemo.html diff --git a/build/1.3.7/demo/yui3/chartDemo.html b/archive/1.3.7/demo/yui3/chartDemo.html similarity index 100% rename from build/1.3.7/demo/yui3/chartDemo.html rename to archive/1.3.7/demo/yui3/chartDemo.html diff --git a/build/1.3.7/demo/yui3/demo.html b/archive/1.3.7/demo/yui3/demo.html similarity index 100% rename from build/1.3.7/demo/yui3/demo.html rename to archive/1.3.7/demo/yui3/demo.html diff --git a/build/1.3.7/demo/yui3/dragAnimDemo.html b/archive/1.3.7/demo/yui3/dragAnimDemo.html similarity index 100% rename from build/1.3.7/demo/yui3/dragAnimDemo.html rename to archive/1.3.7/demo/yui3/dragAnimDemo.html diff --git a/build/1.3.7/demo/yui3/draggableConnectorsDemo.html b/archive/1.3.7/demo/yui3/draggableConnectorsDemo.html similarity index 100% rename from build/1.3.7/demo/yui3/draggableConnectorsDemo.html rename to archive/1.3.7/demo/yui3/draggableConnectorsDemo.html diff --git a/build/1.3.7/demo/yui3/dynamicAnchorsDemo.html b/archive/1.3.7/demo/yui3/dynamicAnchorsDemo.html similarity index 100% rename from build/1.3.7/demo/yui3/dynamicAnchorsDemo.html rename to archive/1.3.7/demo/yui3/dynamicAnchorsDemo.html diff --git a/build/1.3.7/demo/yui3/flowchartConnectorsDemo.html b/archive/1.3.7/demo/yui3/flowchartConnectorsDemo.html similarity index 100% rename from build/1.3.7/demo/yui3/flowchartConnectorsDemo.html rename to archive/1.3.7/demo/yui3/flowchartConnectorsDemo.html diff --git a/build/1.3.7/demo/yui3/stateMachineDemo.html b/archive/1.3.7/demo/yui3/stateMachineDemo.html similarity index 100% rename from build/1.3.7/demo/yui3/stateMachineDemo.html rename to archive/1.3.7/demo/yui3/stateMachineDemo.html diff --git a/archive/1.3.7/jquery.jsPlumb-1.3.7-RC1.js b/archive/1.3.7/jquery.jsPlumb-1.3.7-RC1.js new file mode 100644 index 000000000..dea58e9a7 --- /dev/null +++ b/archive/1.3.7/jquery.jsPlumb-1.3.7-RC1.js @@ -0,0 +1,359 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.7 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the jQuery adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ +/* + * the library specific functions, such as find offset, get id, get attribute, extend etc. + * the full list is: + * + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getDropScope gets the drop scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getPageXY gets the page event's xy location. + * getParent gets the parent of some element. + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * hasClass returns whether or not the given element has the given class. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + * trigger triggers some event on an element. + * unbind unbinds some listener from some element. + */ +(function($) { + + //var getBoundingClientRectSupported = "getBoundingClientRect" in document.documentElement; + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el[0].className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.addClass(el[0], clazz); + } + } + catch (e) { + // SVGAnimatedString not supported; no problem. + } + el.addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * appends the given child to the given parent. + */ + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).append(child); + }, + + /** + * event binding wrapper. it just so happens that jQuery uses 'bind' also. yui3, for example, + * uses 'on'. + */ + bind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.bind(event, callback); + }, + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step', + 'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete' + }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + getDocumentElement : function() { return document; }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + }, + + getDragScope : function(el) { + return el.draggable("option", "scope"); + }, + + getDropEvent : function(args) { + return args[0]; + }, + + getDropScope : function(el) { + return el.droppable("option", "scope"); + }, + + /** + * gets a DOM element from the given input, which might be a string (in which case we just do document.getElementById), + * a selector (in which case we return el[0]), or a DOM element already (we assume this if it's not either of the other + * two cases). this is the opposite of getElementObject below. + */ + getDOMElement : function(el) { + if (typeof(el) == "string") return document.getElementById(el); + else if (el.context) return el[0]; + else return el; + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + * + */ + getElementObject : function(el) { + return typeof(el) == "string" ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).parent(); + }, + + getScrollLeft : function(el) { + return el.scrollLeft(); + }, + + getScrollTop : function(el) { + return el.scrollTop(); + }, + + getSelector : function(spec) { + return $(spec); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e.length > 0 ? e[0].tagName : null; + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + + // this code is a workaround for the case that the element being dragged has a margin set on it. jquery UI passes + // in the wrong offset if the element has a margin (it doesn't take the margin into account). the getBoundingClientRect + // method, which is in pretty much all browsers now, reports the right numbers. but it introduces a noticeable lag, which + // i don't like. + + /*if ( getBoundingClientRectSupported ) { + var r = eventArgs[1].helper[0].getBoundingClientRect(); + return { left : r.left, top: r.top }; + } else {*/ + if (eventArgs.length == 1) { + ret = { left: eventArgs[0].pageX, top:eventArgs[0].pageY }; + } + else { + var ui = eventArgs[1], _offset = ui.offset; + ret = _offset || ui.absolutePosition; + } + return ret; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options) { + options = options || {}; + // remove helper directive if present. + options.helper = null; + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.draggable(options); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.droppable(options); + }, + + isAlreadyDraggable : function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return el.hasClass("ui-draggable"); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el[0].className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.removeClass(el[0], clazz); + } + } + catch (e) { + // SVGAnimatedString not supported; no problem. + } + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).remove(); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * sets the drag scope. probably time for a setDragOption method (roll this and the one above together) + * @param el + * @param scope + */ + setDragScope : function(el, scope) { + el.draggable("option", "scope", scope); + }, + + setOffset : function(el, o) { + jsPlumb.CurrentLibrary.getElementObject(el).offset(o); + }, + + /** + * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself. + * the other libraries do not. yui, in fact, cannot even pass an original event. we have to pull out stuff + * from the originalEvent to put in an options object for YUI. + * @param el + * @param event + * @param originalEvent + */ + trigger : function(el, event, originalEvent) { + //originalEvent.stopPropagation(); + //jsPlumb.CurrentLibrary.getElementObject(el).trigger(originalEvent); + var h = jQuery._data(jsPlumb.CurrentLibrary.getElementObject(el)[0], "handle"); + h(originalEvent); + //originalEvent.stopPropagation(); + }, + + /** + * event unbinding wrapper. it just so happens that jQuery uses 'unbind' also. yui3, for example, + * uses..something else. + */ + unbind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.unbind(event, callback); + } + }; + + $(document).ready(jsPlumb.init); + +})(jQuery); + diff --git a/build/1.3.7/js/jquery.jsPlumb-1.3.7-all-min.js b/archive/1.3.7/js/jquery.jsPlumb-1.3.7-all-min.js similarity index 100% rename from build/1.3.7/js/jquery.jsPlumb-1.3.7-all-min.js rename to archive/1.3.7/js/jquery.jsPlumb-1.3.7-all-min.js diff --git a/build/1.3.7/js/jquery.jsPlumb-1.3.7-all.js b/archive/1.3.7/js/jquery.jsPlumb-1.3.7-all.js similarity index 100% rename from build/1.3.7/js/jquery.jsPlumb-1.3.7-all.js rename to archive/1.3.7/js/jquery.jsPlumb-1.3.7-all.js diff --git a/build/1.3.7/js/jsPlumb-1.3.7-tests.js b/archive/1.3.7/js/jsPlumb-1.3.7-tests.js similarity index 100% rename from build/1.3.7/js/jsPlumb-1.3.7-tests.js rename to archive/1.3.7/js/jsPlumb-1.3.7-tests.js diff --git a/build/1.3.7/js/lib/qunit.js b/archive/1.3.7/js/lib/qunit.js similarity index 100% rename from build/1.3.7/js/lib/qunit.js rename to archive/1.3.7/js/lib/qunit.js diff --git a/build/1.3.7/js/mootools.jsPlumb-1.3.7-all-min.js b/archive/1.3.7/js/mootools.jsPlumb-1.3.7-all-min.js similarity index 100% rename from build/1.3.7/js/mootools.jsPlumb-1.3.7-all-min.js rename to archive/1.3.7/js/mootools.jsPlumb-1.3.7-all-min.js diff --git a/build/1.3.7/js/mootools.jsPlumb-1.3.7-all.js b/archive/1.3.7/js/mootools.jsPlumb-1.3.7-all.js similarity index 100% rename from build/1.3.7/js/mootools.jsPlumb-1.3.7-all.js rename to archive/1.3.7/js/mootools.jsPlumb-1.3.7-all.js diff --git a/build/1.3.7/js/yui.jsPlumb-1.3.7-all-min.js b/archive/1.3.7/js/yui.jsPlumb-1.3.7-all-min.js similarity index 100% rename from build/1.3.7/js/yui.jsPlumb-1.3.7-all-min.js rename to archive/1.3.7/js/yui.jsPlumb-1.3.7-all-min.js diff --git a/build/1.3.7/js/yui.jsPlumb-1.3.7-all.js b/archive/1.3.7/js/yui.jsPlumb-1.3.7-all.js similarity index 100% rename from build/1.3.7/js/yui.jsPlumb-1.3.7-all.js rename to archive/1.3.7/js/yui.jsPlumb-1.3.7-all.js diff --git a/archive/1.3.7/jsPlumb-1.3.7-RC1.js b/archive/1.3.7/jsPlumb-1.3.7-RC1.js new file mode 100644 index 000000000..2e4d305cc --- /dev/null +++ b/archive/1.3.7/jsPlumb-1.3.7-RC1.js @@ -0,0 +1,5316 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.7 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the jsPlumb core code. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + /** + * Class:jsPlumb + * The jsPlumb engine, registered as a static object in the window. This object contains all of the methods you will use to + * create and maintain Connections and Endpoints. + */ + + var canvasAvailable = !!document.createElement('canvas').getContext, + svgAvailable = !!window.SVGAngle || document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"), + // TODO what is a good test for VML availability? aside from just assuming its there because nothing else is. + vmlAvailable = !(canvasAvailable | svgAvailable); + + var _findWithFunction = function(a, f) { + if (a) + for (var i = 0; i < a.length; i++) if (f(a[i])) return i; + return -1; + }, + _indexOf = function(l, v) { + return _findWithFunction(l, function(_v) { return _v == v; }); + }, + _removeWithFunction = function(a, f) { + var idx = _findWithFunction(a, f); + if (idx > -1) a.splice(idx, 1); + return idx != -1; + }, + _remove = function(l, v) { + var idx = _indexOf(l, v); + if (idx > -1) l.splice(idx, 1); + return idx != -1; + }, + // TODO support insert index + _addWithFunction = function(list, item, hashFunction) { + if (_findWithFunction(list, hashFunction) == -1) list.push(item); + }, + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = [], map[key] = l; + } + l.push(value); + return l; + }; + + // for those browsers that dont have it. they still don't have it! but at least they won't crash. + if (!window.console) + window.console = { time:function(){}, timeEnd:function(){}, group:function(){}, groupEnd:function(){}, log:function(){} }; + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + var _connectionBeingDragged = null, + _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }, + _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }, + _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }, + _logEnabled = true, + _log = function() { + if (_logEnabled && typeof console != "undefined") { + try { + var msg = arguments[arguments.length - 1]; + console.log(msg); + } + catch (e) {} + } + }, + _group = function(g) { if (_logEnabled && typeof console != "undefined") console.group(g); }, + _groupEnd = function(g) { if (_logEnabled && typeof console != "undefined") console.groupEnd(g); }, + _time = function(t) { if (_logEnabled && typeof console != "undefined") console.time(t); }, + _timeEnd = function(t) { if (_logEnabled && typeof console != "undefined") console.timeEnd(t); }; + + /** + * EventGenerator + * Superclass for objects that generate events - jsPlumb extends this, as does jsPlumbUIComponent, which all the UI elements extend. + */ + EventGenerator = function() { + var _listeners = {}, self = this; + + // this is a list of events that should re-throw any errors that occur during their dispatch. as of 1.3.0 this is private to + // jsPlumb, but it seems feasible that people might want to manipulate this list. the thinking is that we don't want event + // listeners to bring down jsPlumb - or do we. i can't make up my mind about this, but i know i want to hear about it if the "ready" + // event fails, because then my page has most likely not initialised. so i have this halfway-house solution. it will be interesting + // to hear what other people think. + var eventsToDieOn = [ "ready" ]; + + /* + * Binds a listener to an event. + * + * Parameters: + * event - name of the event to bind to. + * listener - function to execute. + */ + this.bind = function(event, listener) { + _addToList(_listeners, event, listener); + }; + /* + * Fires an update for the given event. + * + * Parameters: + * event - event to fire + * value - value to pass to the event listener(s). + * originalEvent - the original event from the browser + */ + this.fire = function(event, value, originalEvent) { + if (_listeners[event]) { + for ( var i = 0; i < _listeners[event].length; i++) { + // doing it this way rather than catching and then possibly re-throwing means that an error propagated by this + // method will have the whole call stack available in the debugger. + //if (_findIndex(eventsToDieOn, event) != -1) + if (_findWithFunction(eventsToDieOn, function(e) { return e === event}) != -1) + _listeners[event][i](value, originalEvent); + else { + // for events we don't want to die on, catch and log. + try { + _listeners[event][i](value, originalEvent); + } catch (e) { + _log("jsPlumb: fire failed for event " + event + " : " + e); + } + } + } + } + }; + /* + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + this.clearListeners = function(event) { + if (event) + delete _listeners[event]; + else { + delete _listeners; + _listeners = {}; + } + }; + + this.getListener = function(forEvent) { + return _listeners[forEvent]; + }; + }, + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + _timestamp = function() { return "" + (new Date()).getTime(); }, + + /* + * Class:jsPlumbUIComponent + * Abstract superclass for UI components Endpoint and Connection. Provides the abstraction of paintStyle/hoverPaintStyle, + * and also extends EventGenerator to provide the bind and fire methods. + */ + jsPlumbUIComponent = function(params) { + var self = this, a = arguments, _hover = false, parameters = params.parameters || {}, idPrefix = self.idPrefix, + id = idPrefix + (new Date()).getTime(); + self._jsPlumb = params["_jsPlumb"]; + self.getId = function() { return id; }; + self.tooltip = params.tooltip; + self.hoverClass = params.hoverClass; + + // all components can generate events + EventGenerator.apply(this); + // all components get this clone function. + // TODO issue 116 showed a problem with this - it seems 'a' that is in + // the clone function's scope is shared by all invocations of it, the classic + // JS closure problem. for now, jsPlumb does a version of this inline where + // it used to call clone. but it would be nice to find some time to look + // further at this. + this.clone = function() { + var o = new Object(); + self.constructor.apply(o, a); + return o; + }; + + this.getParameter = function(name) { return parameters[name]; }, + this.getParameters = function() { return parameters; }, + this.setParameter = function(name, value) { parameters[name] = value; }, + this.setParameters = function(p) { parameters = p; }, + this.overlayPlacements = [], + this.paintStyle = null, + this.hoverPaintStyle = null; + + // user can supply a beforeDetach callback, which will be executed before a detach + // is performed; returning false prevents the detach. + var beforeDetach = params.beforeDetach; + this.isDetachAllowed = function(connection) { + var r = self._jsPlumb.checkCondition("beforeDetach", connection ); + if (beforeDetach) { + try { + r = beforeDetach(connection); + } + catch (e) { _log("jsPlumb: beforeDetach callback failed", e); } + } + return r; + }; + + // user can supply a beforeDrop callback, which will be executed before a dropped + // connection is confirmed. user can return false to reject connection. + var beforeDrop = params.beforeDrop; + this.isDropAllowed = function(sourceId, targetId, scope) { + var r = self._jsPlumb.checkCondition("beforeDrop", { sourceId:sourceId, targetId:targetId, scope:scope }); + if (beforeDrop) { + try { + r = beforeDrop({ sourceId:sourceId, targetId:targetId, scope:scope }); + } + catch (e) { _log("jsPlumb: beforeDrop callback failed", e); } + } + return r; + }; + + // helper method to update the hover style whenever it, or paintStyle, changes. + // we use paintStyle as the foundation and merge hoverPaintStyle over the + // top. + var _updateHoverStyle = function() { + if (self.paintStyle && self.hoverPaintStyle) { + var mergedHoverStyle = {}; + jsPlumb.extend(mergedHoverStyle, self.paintStyle); + jsPlumb.extend(mergedHoverStyle, self.hoverPaintStyle); + delete self["hoverPaintStyle"]; + // we want the fillStyle of paintStyle to override a gradient, if possible. + if (mergedHoverStyle.gradient && self.paintStyle.fillStyle) + delete mergedHoverStyle["gradient"]; + self.hoverPaintStyle = mergedHoverStyle; + } + }; + + /* + * Sets the paint style and then repaints the element. + * + * Parameters: + * style - Style to use. + */ + this.setPaintStyle = function(style, doNotRepaint) { + self.paintStyle = style; + self.paintStyleInUse = self.paintStyle; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /* + * Sets the paint style to use when the mouse is hovering over the element. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the component will not be repainted. useful when setting things up initially. + */ + this.setHoverPaintStyle = function(style, doNotRepaint) { + self.hoverPaintStyle = style; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /* + * sets/unsets the hover state of this element. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + this.setHover = function(hover, ignoreAttachedElements, timestamp) { + // while dragging, we ignore these events. this keeps the UI from flashing and + // swishing and whatevering. + if (!self._jsPlumb.currentlyDragging && !self._jsPlumb.isHoverSuspended()) { + + _hover = hover; + if (self.hoverClass != null && self.canvas != null) { + if (hover) + jpcl.addClass(self.canvas, self.hoverClass); + else + jpcl.removeClass(self.canvas, self.hoverClass); + } + if (self.hoverPaintStyle != null) { + self.paintStyleInUse = hover ? self.hoverPaintStyle : self.paintStyle; + timestamp = timestamp || _timestamp(); + self.repaint({timestamp:timestamp, recalc:false}); + } + // get the list of other affected elements, if supported by this component. + // for a connection, its the endpoints. for an endpoint, its the connections! surprise. + if (self.getAttachedElements && !ignoreAttachedElements) + _updateAttachedElements(hover, _timestamp(), self); + } + }; + + this.isHover = function() { return _hover; }; + + var jpcl = jsPlumb.CurrentLibrary, + events = [ "click", "dblclick", "mouseenter", "mouseout", "mousemove", "mousedown", "mouseup", "contextmenu" ], + eventFilters = { "mouseout":"mouseexit" }, + bindOne = function(o, c, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.bind(o, evt, function(ee) { + c.fire(filteredEvent, c, ee); + }); + }, + unbindOne = function(o, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.unbind(o, evt); + }; + + this.attachListeners = function(o, c) { + for (var i = 0; i < events.length; i++) { + bindOne(o, c, events[i]); + } + }; + + var _updateAttachedElements = function(state, timestamp, sourceElement) { + var affectedElements = self.getAttachedElements(); // implemented in subclasses + if (affectedElements) { + for (var i = 0; i < affectedElements.length; i++) { + if (!sourceElement || sourceElement != affectedElements[i]) + affectedElements[i].setHover(state, true, timestamp); // tell the attached elements not to inform their own attached elements. + } + } + }; + + this.reattachListenersForElement = function(o) { + if (arguments.length > 1) { + for (var i = 0; i < events.length; i++) + unbindOne(o, events[i]); + for (var i = 1; i < arguments.length; i++) + self.attachListeners(o, arguments[i]); + } + }; + }, + + overlayCapableJsPlumbUIComponent = function(params) { + jsPlumbUIComponent.apply(this, arguments); + var self = this; + /* + * Property: overlays + * List of Overlays for this component. + */ + this.overlays = []; + + var processOverlay = function(o) { + var _newOverlay = null; + if (o.constructor == Array) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0], + // make a copy of the object so as not to mess up anyone else's reference... + p = jsPlumb.extend({component:self, _jsPlumb:self._jsPlumb}, o[1]); + if (o.length == 3) jsPlumb.extend(p, o[2]); + _newOverlay = new jsPlumb.Overlays[self._jsPlumb.getRenderMode()][type](p); + if (p.events) { + for (var evt in p.events) { + _newOverlay.bind(evt, p.events[evt]); + } + } + } else if (o.constructor == String) { + _newOverlay = new jsPlumb.Overlays[self._jsPlumb.getRenderMode()][o]({component:self, _jsPlumb:self._jsPlumb}); + } else { + _newOverlay = o; + } + + self.overlays.push(_newOverlay); + }, + calculateOverlaysToAdd = function(params) { + var defaultKeys = self.defaultOverlayKeys || [], + o = params.overlays, + checkKey = function(k) { + return self._jsPlumb.Defaults[k] || jsPlumb.Defaults[k] || []; + }; + + if (!o) o = []; + + for (var i = 0; i < defaultKeys.length; i++) + o.unshift.apply(o, checkKey(defaultKeys[i])); + + return o; + } + + var _overlays = calculateOverlaysToAdd(params);//params.overlays || self._jsPlumb.Defaults.Overlays; + if (_overlays) { + for (var i = 0; i < _overlays.length; i++) { + processOverlay(_overlays[i]); + } + } + + // overlay finder helper method + var _getOverlayIndex = function(id) { + var idx = -1; + for (var i = 0; i < self.overlays.length; i++) { + if (id === self.overlays[i].id) { + idx = i; + break; + } + } + return idx; + }; + + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + this.addOverlay = function(overlay) { + processOverlay(overlay); + self.repaint(); + }; + + /* + * Function: getOverlay + * Gets an overlay, by ID. Note: by ID. You would pass an 'id' parameter + * in to the Overlay's constructor arguments, and then use that to retrieve + * it via this method. + */ + this.getOverlay = function(id) { + var idx = _getOverlayIndex(id); + return idx >= 0 ? self.overlays[idx] : null; + }; + + /* + * Function:getOverlays + * Gets all the overlays for this component. + */ + this.getOverlays = function() { + return self.overlays; + }; + + /* + * Function: hideOverlay + * Hides the overlay specified by the given id. + */ + this.hideOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.hide(); + }; + + this.hideOverlays = function() { + for (var i = 0; i < self.overlays.length; i++) + self.overlays[i].hide(); + }; + + /* + * Function: showOverlay + * Shows the overlay specified by the given id. + */ + this.showOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.show(); + }; + + this.showOverlays = function() { + for (var i = 0; i < self.overlays.length; i++) + self.overlays[i].show(); + }; + + /** + * Function: removeAllOverlays + * Removes all overlays from the Connection, and then repaints. + */ + this.removeAllOverlays = function() { + self.overlays.splice(0, self.overlays.length); + self.repaint(); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + this.removeOverlay = function(overlayId) { + var idx = _getOverlayIndex(overlayId); + if (idx != -1) { + var o = self.overlays[idx]; + o.cleanup(); + self.overlays.splice(idx, 1); + } + }; + + /** + * Function:removeOverlays + * Removes a set of overlays by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + this.removeOverlays = function() { + for (var i = 0; i < arguments.length; i++) + self.removeOverlay(arguments[i]); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + var _internalLabelOverlayId = "__label", + _makeLabelOverlay = function(params) { + + var _params = { + cssClass:params.cssClass, + labelStyle : this.labelStyle, + id:_internalLabelOverlayId, + component:self, + _jsPlumb:self._jsPlumb + }, + mergedParams = jsPlumb.extend(_params, params); + + return new jsPlumb.Overlays[self._jsPlumb.getRenderMode()].Label( mergedParams ); + }; + if (params.label) { + var loc = params.labelLocation || self.defaultLabelLocation || 0.5, + labelStyle = params.labelStyle || self._jsPlumb.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.overlays.push(_makeLabelOverlay({ + label:params.label, + location:loc, + labelStyle:labelStyle + })); + } + + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String, a Function that returns a String, or a params object containing { "label", "labelStyle", "location", "cssClass" } + */ + this.setLabel = function(l) { + var lo = self.getOverlay(_internalLabelOverlayId); + if (!lo) { + var params = l.constructor == String || l.constructor == Function ? { label:l } : l; + lo = _makeLabelOverlay(params); + this.overlays.push(lo); + } + else { + if (l.constructor == String || l.constructor == Function) lo.setLabel(l); + else { + if (l.label) lo.setLabel(l.label); + if (l.location) lo.setLocation(l.location); + } + } + + self.repaint(); + }; + + /* + Function:getLabel + Returns the label text for this component (or a function if you are labelling with a function). + This does not return the overlay itself; this is a convenience method which is a pair with + setLabel; together they allow you to add and access a Label Overlay without having to create the + Overlay object itself. For access to the underlying label overlay that jsPlumb has created, + use getLabelOverlay. + */ + this.getLabel = function() { + var lo = self.getOverlay(_internalLabelOverlayId); + return lo != null ? lo.getLabel() : null; + }; + + /* + Function:getLabelOverlay + Returns the underlying internal label overlay, which will exist if you specified a label on + a connect or addEndpoint call, or have called setLabel at any stage. + */ + this.getLabelOverlay = function() { + return self.getOverlay(_internalLabelOverlayId); + } + }, + + _bindListeners = function(obj, _self, _hoverFunction) { + obj.bind("click", function(ep, e) { _self.fire("click", _self, e); }); + obj.bind("dblclick", function(ep, e) { _self.fire("dblclick", _self, e); }); + obj.bind("contextmenu", function(ep, e) { _self.fire("contextmenu", _self, e); }); + obj.bind("mouseenter", function(ep, e) { + if (!_self.isHover()) { + _hoverFunction(true); + _self.fire("mouseenter", _self, e); + } + }); + obj.bind("mouseexit", function(ep, e) { + if (_self.isHover()) { + _hoverFunction(false); + _self.fire("mouseexit", _self, e); + } + }); + }; + + var _jsPlumbInstanceIndex = 0, + getInstanceIndex = function() { + var i = _jsPlumbInstanceIndex + 1; + _jsPlumbInstanceIndex++; + return i; + }; + + var jsPlumbInstance = function(_defaults) { + + /* + * Property: Defaults + * + * These are the default settings for jsPlumb. They are what will be used if you do not supply specific pieces of information + * to the various API calls. A convenient way to implement your own look and feel can be to override these defaults + * by including a script somewhere after the jsPlumb include, but before you make any calls to jsPlumb. + * + * Properties: + * - *Anchor* The default anchor to use for all connections (both source and target). Default is "BottomCenter". + * - *Anchors* The default anchors to use ([source, target]) for all connections. Defaults are ["BottomCenter", "BottomCenter"]. + * - *ConnectionsDetachable* Whether or not connections are detachable by default (using the mouse). Defults to true. + * - *ConnectionOverlays* The default overlay definitions for Connections. Defaults to an empty list. + * - *Connector* The default connector definition to use for all connections. Default is "Bezier". + * - *Container* Optional selector or element id that instructs jsPlumb to append elements it creates to a specific element. + * - *DragOptions* The default drag options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *DropOptions* The default drop options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *Endpoint* The default endpoint definition to use for all connections (both source and target). Default is "Dot". + * - *EndpointOverlays* The default overlay definitions for Endpoints. Defaults to an empty list. + * - *Endpoints* The default endpoint definitions ([ source, target ]) to use for all connections. Defaults are ["Dot", "Dot"]. + * - *EndpointStyle* The default style definition to use for all endpoints. Default is fillStyle:"#456". + * - *EndpointStyles* The default style definitions ([ source, target ]) to use for all endpoints. Defaults are empty. + * - *EndpointHoverStyle* The default hover style definition to use for all endpoints. Default is null. + * - *EndpointHoverStyles* The default hover style definitions ([ source, target ]) to use for all endpoints. Defaults are null. + * - *HoverPaintStyle* The default hover style definition to use for all connections. Defaults are null. + * - *LabelStyle* The default style to use for label overlays on connections. + * - *LogEnabled* Whether or not the jsPlumb log is enabled. defaults to false. + * - *Overlays* The default overlay definitions (for both Connections and Endpoint). Defaults to an empty list. + * - *MaxConnections* The default maximum number of connections for an Endpoint. Defaults to 1. + * - *PaintStyle* The default paint style for a connection. Default is line width of 8 pixels, with color "#456". + * - *RenderMode* What mode to use to paint with. If you're on IE<9, you don't really get to choose this. You'll just get VML. Otherwise, the jsPlumb default is to use SVG. + * - *Scope* The default "scope" to use for connections. Scope lets you assign connections to different categories. + */ + this.Defaults = { + Anchor : "BottomCenter", + Anchors : [ null, null ], + ConnectionsDetachable : true, + ConnectionOverlays : [ ], + Connector : "Bezier", + Container : null, + DragOptions : { }, + DropOptions : { }, + Endpoint : "Dot", + EndpointOverlays : [ ], + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : "#456" }, + EndpointStyles : [ null, null ], + EndpointHoverStyle : null, + EndpointHoverStyles : [ null, null ], + HoverPaintStyle : null, + LabelStyle : { color : "black" }, + LogEnabled : false, + Overlays : [ ], + MaxConnections : 1, + PaintStyle : { lineWidth : 8, strokeStyle : "#456" }, + //Reattach:false, + RenderMode : "svg", + Scope : "jsPlumb_DefaultScope" + }; + if (_defaults) jsPlumb.extend(this.Defaults, _defaults); + + this.logEnabled = this.Defaults.LogEnabled; + + EventGenerator.apply(this); + var _currentInstance = this, + _instanceIndex = getInstanceIndex(), + _bb = _currentInstance.bind, + _initialDefaults = {}; + + for (var i in this.Defaults) + _initialDefaults[i] = this.Defaults[i]; + + this.bind = function(event, fn) { + if ("ready" === event && initialized) fn(); + else _bb.apply(_currentInstance,[event, fn]); + }; + + /* + Function: importDefaults + Imports all the given defaults into this instance of jsPlumb. + */ + _currentInstance.importDefaults = function(d) { + for (var i in d) { + _currentInstance.Defaults[i] = d[i]; + } + }; + + /* + Function:restoreDefaults + Restores the default settings to "factory" values. + */ + _currentInstance.restoreDefaults = function() { + _currentInstance.Defaults = jsPlumb.extend({}, _initialDefaults); + }; + + var log = null, + repaintFunction = function() { + jsPlumb.repaintEverything(); + }, + automaticRepaint = true, + repaintEverything = function() { + if (automaticRepaint) + repaintFunction(); + }, + resizeTimer = null, + initialized = false, + connectionsByScope = {}, + /** + * map of element id -> endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + endpointsByElement = {}, + endpointsByUUID = {}, + offsets = {}, + offsetTimestamps = {}, + floatingConnections = {}, + draggableStates = {}, + canvasList = [], + sizes = [], + //listeners = {}, // a map: keys are event types, values are lists of listeners. + DEFAULT_SCOPE = this.Defaults.Scope, + renderMode = null, // will be set in init() + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }, + + /** + * appends an element to some other element, which is calculated as follows: + * + * 1. if _currentInstance.Defaults.Container exists, use that element. + * 2. if the 'parent' parameter exists, use that. + * 3. otherwise just use the document body. + * + */ + _appendElement = function(el, parent) { + if (_currentInstance.Defaults.Container) + jsPlumb.CurrentLibrary.appendElement(el, _currentInstance.Defaults.Container); + else if (!parent) + document.body.appendChild(el); + else + jsPlumb.CurrentLibrary.appendElement(el, parent); + }, + + _curIdStamp = 1, + _idstamp = function() { return "" + _curIdStamp++; }, + + /** + * YUI, for some reason, put the result of a Y.all call into an object that contains + * a '_nodes' array, instead of handing back an array-like object like the other + * libraries do. + */ + _convertYUICollection = function(c) { + return c._nodes ? c._nodes : c; + }, + + _suspendDrawing = false, + /* + sets whether or not to suspend drawing. you should use this if you need to connect a whole load of things in one go. + it will save you a lot of time. + */ + _setSuspendDrawing = function(val, repaintAfterwards) { + _suspendDrawing = val; + if (repaintAfterwards) _currentInstance.repaintEverything(); + }, + + /** + * Draws an endpoint and its connections. this is the main entry point into drawing connections as well + * as endpoints, since jsPlumb is endpoint-centric under the hood. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + _draw = function(element, ui, timestamp) { + if (!_suspendDrawing) { + var id = _getAttribute(element, "id"), + repaintEls = _currentInstance.dragManager.getElementsForDraggable(id); + + if (timestamp == null) timestamp = _timestamp(); + + _currentInstance.anchorManager.redraw(id, ui, timestamp); + + if (repaintEls) { + for (var i in repaintEls) { + _currentInstance.anchorManager.redraw(repaintEls[i].id, ui, timestamp, repaintEls[i].offset); + } + } + } + }, + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }, + + /** + * gets an Endpoint by uuid. + */ + _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }, + + /** + * inits a draggable if it's not already initialised. + */ + _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? false : isDraggable, + jpcl = jsPlumb.CurrentLibrary; + if (draggable) { + if (jpcl.isDragSupported(element) && !jpcl.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jpcl.dragEvents["drag"], + stopEvent = jpcl.dragEvents["stop"], + startEvent = jpcl.dragEvents["start"]; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jpcl.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jpcl.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + draggableStates[_getId(element)] = true; + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jpcl.initDraggable(element, options, false); + _currentInstance.dragManager.register(element); + } + } + }, + + /* + * prepares a final params object that can be passed to _newConnection, taking into account defaults, events, etc. + */ + _prepareConnectionParams = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + // hotwire endpoints passed as source or target to sourceEndpoint/targetEndpoint, respectively. + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not full. + // source: + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + // target: + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + // copy in any connectorOverlays that were specified on the source endpoint. + // it doesnt copy target endpoint overlays. i'm not sure if we want it to or not. + if (_p.sourceEndpoint && _p.sourceEndpoint.connectorOverlays) { + _p.overlays = _p.overlays || []; + for (var i = 0; i < _p.sourceEndpoint.connectorOverlays.length; i++) { + _p.overlays.push(_p.sourceEndpoint.connectorOverlays[i]); + } + } + + // tooltip. params.tooltip takes precedence, then sourceEndpoint.connectorTooltip. + _p.tooltip = params.tooltip; + if (!_p.tooltip && _p.sourceEndpoint && _p.sourceEndpoint.connectorTooltip) + _p.tooltip = _p.sourceEndpoint.connectorTooltip; + + // if there's a target specified (which of course there should be), and there is no + // target endpoint specified, and 'newConnection' was not set to true, then we check to + // see if a prior call to makeTarget has provided us with the specs for the target endpoint, and + // we use those if so. additionally, if the makeTarget call was specified with 'uniqueEndpoint' set + // to true, then if that target endpoint has already been created, we re-use it. + if (_p.target && !_p.target.endpoint && !_p.targetEndpoint && !_p.newConnection) { + var tid = _getId(_p.target), + tep =_targetEndpointDefinitions[tid], + existingUniqueEndpoint = _targetEndpoints[tid]; + + if (tep) { + + var newEndpoint = existingUniqueEndpoint != null ? existingUniqueEndpoint : _currentInstance.addEndpoint(_p.target, tep); + if (_targetEndpointsUnique[tid]) _targetEndpoints[tid] = newEndpoint; + _p.targetEndpoint = newEndpoint; + } + } + + // same thing, but for source. + if (_p.source && !_p.source.endpoint && !_p.sourceEndpoint && !_p.newConnection) { + var tid = _getId(_p.source), + tep = _sourceEndpointDefinitions[tid], + existingUniqueEndpoint = _sourceEndpoints[tid]; + + if (tep) { + + var newEndpoint = existingUniqueEndpoint != null ? existingUniqueEndpoint : _currentInstance.addEndpoint(_p.source, tep); + if (_sourceEndpointsUnique[tid]) _sourceEndpoints[tid] = newEndpoint; + _p.sourceEndpoint = newEndpoint; + } + } + + return _p; + }, + + _newConnection = function(params) { + var connectionFunc = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + endpointFunc = _currentInstance.Defaults.EndpointType || Endpoint, + parent = jsPlumb.CurrentLibrary.getParent; + + if (params.container) + params["parent"] = params.container; + else { + if (params.sourceEndpoint) + params["parent"] = params.sourceEndpoint.parent; + else if (params.source.constructor == endpointFunc) + params["parent"] = params.source.parent; + else params["parent"] = parent(params.source); + } + + params["_jsPlumb"] = _currentInstance; + var con = new connectionFunc(params); + con.id = "con_" + _idstamp(); + _eventFireProxy("click", "click", con); + _eventFireProxy("dblclick", "dblclick", con); + _eventFireProxy("contextmenu", "contextmenu", con); + return con; + }, + + /** + * adds the connection to the backing model, fires an event if necessary and then redraws + */ + _finaliseConnection = function(jpc, params, originalEvent) { + params = params || {}; + // add to list of connections (by scope). + if (!jpc.suspendedEndpoint) + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + if (!params.doNotFireConnectionEvent && params.fireEvent !== false) { + _currentInstance.fire("jsPlumbConnection", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }, originalEvent); + } + // always inform the anchor manager + // except that if jpc has a suspended endpoint it's not true to say the + // connection is new; it has just (possibly) moved. the question is whether + // to make that call here or in the anchor manager. i think perhaps here. + _currentInstance.anchorManager.newConnection(jpc); + // force a paint + _draw(jpc.source); + }, + + _eventFireProxy = function(event, proxyEvent, obj) { + obj.bind(event, function(originalObject, originalEvent) { + _currentInstance.fire(proxyEvent, obj, originalEvent); + }); + }, + + /** + * for the given endpoint params, returns an appropriate parent element for the UI elements that will be added. + * this function is used by _newEndpoint (directly below), and also in the makeSource function in jsPlumb. + * + * the logic is to first look for a "container" member of params, and pass that back if found. otherwise we + * handoff to the 'getParent' function in the current library. + */ + _getParentFromParams = function(params) { + if (params.container) + return params.container; + else { + var tag = jsPlumb.CurrentLibrary.getTagName(params.source), + p = jsPlumb.CurrentLibrary.getParent(params.source); + if (tag && tag.toLowerCase() === "td") + return jsPlumb.CurrentLibrary.getParent(p); + else return p; + } + }, + + /** + factory method to prepare a new endpoint. this should always be used instead of creating Endpoints + manually, since this method attaches event listeners and an id. + */ + _newEndpoint = function(params) { + var endpointFunc = _currentInstance.Defaults.EndpointType || Endpoint; + params.parent = _getParentFromParams(params); + params["_jsPlumb"] = _currentInstance; + var ep = new endpointFunc(params); + ep.id = "ep_" + _idstamp(); + _eventFireProxy("click", "endpointClick", ep); + _eventFireProxy("dblclick", "endpointDblClick", ep); + _eventFireProxy("contextmenu", "contextmenu", ep); + return ep; + }, + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + _operation = function(elId, func, endpointFunc) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + if (endpointFunc) endpointFunc(endpoints[i]); + } + } + }, + /** + * perform an operation on all elements. + */ + _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }, + + /** + * helper to remove an element from the DOM. + */ + _removeElement = function(element, parent) { + if (element != null && element.parentNode != null) { + element.parentNode.removeChild(element); + } + }, + /** + * helper to remove a list of elements from the DOM. + */ + _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }, + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }, + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + _setVisible = function(el, state, alsoChangeEndpoints) { + state = state === "block"; + var endpointFunc = null; + if (alsoChangeEndpoints) { + if (state) endpointFunc = function(ep) { + ep.setVisible(true, true, true); + }; + else endpointFunc = function(ep) { + ep.setVisible(false, true, true); + }; + } + var id = _getAttribute(el, "id"); + _operation(id, function(jpc) { + if (state && alsoChangeEndpoints) { + // this test is necessary because this functionality is new, and i wanted to maintain backwards compatibility. + // this block will only set a connection to be visible if the other endpoint in the connection is also visible. + var oidx = jpc.sourceId === id ? 1 : 0; + if (jpc.endpoints[oidx].isVisible()) jpc.setVisible(true); + } + else // the default behaviour for show, and what always happens for hide, is to just set the visibility without getting clever. + jpc.setVisible(state); + }, endpointFunc); + }, + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? false : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }, + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + _toggleVisible = function(elId, changeEndpoints) { + var endpointFunc = null; + if (changeEndpoints) { + endpointFunc = function(ep) { + var state = ep.isVisible(); + ep.setVisible(!state); + }; + } + _operation(elId, function(jpc) { + var state = jpc.isVisible(); + jpc.setVisible(!state); + }, endpointFunc); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }, + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return offsets[elId]; + } + if (recalc || !offset) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + if (sizes[elId] == null) { + var s = _getElementObject(elId); + if (s != null) + sizes[elId] = _getSize(s); + } + } + + if(offsets[elId] && !offsets[elId].right) { + offsets[elId].right = offsets[elId].left + sizes[elId][0]; + offsets[elId].bottom = offsets[elId].top + sizes[elId][1]; + offsets[elId].width = sizes[elId][0]; + offsets[elId].height = sizes[elId][1]; + offsets[elId].centerx = offsets[elId].left + (offsets[elId].width / 2); + offsets[elId].centery = offsets[elId].top + (offsets[elId].height / 2); + } + return offsets[elId]; + }, + + // TODO comparison performance + _getCachedData = function(elId) { + var o = offsets[elId]; + if (!o) o = _updateOffset({elId:elId}); + return {o:o, s:sizes[elId]}; + }, + + /** + * gets an id for the given element, creating and setting one if + * necessary. the id is of the form + * + * jsPlumb__ + * + * where "index in instance" is a monotonically increasing integer that starts at 0, + * for each instance. this method is used not only to assign ids to elements that do not + * have them but also to connections and endpoints. + */ + _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else + id = "jsPlumb_" + _instanceIndex + "_" + _idstamp(); + _setAttribute(ele, "id", id); + } + return id; + }, + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, "jsPlumb function failed : " + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, "wrapped function failed : " + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = "_jsPlumb_connector"; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = "_jsPlumb_endpoint"; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = "_jsPlumb_overlay"; + + this.Anchors = {}; + + this.Connectors = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Endpoints = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Overlays = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + +// ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***************************************** + /* + * Function: bind + * Bind to an event on jsPlumb. + * + * Parameters: + * event - the event to bind. Available events on jsPlumb are: + * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback. + * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback. + * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback. + * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback. + * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback. + * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback. + * + * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event. + */ + + /* + * Function: clearListeners + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + +// *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *********************************************************** + + +// --------------------------- jsPLumbInstance public API --------------------------------------------------------- + + /* + Function: addClass + + Helper method to abstract out differences in setting css classes on the different renderer types. + */ + this.addClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.addClass(el, clazz); + }; + + /* + Function: removeClass + + Helper method to abstract out differences in setting css classes on the different renderer types. + */ + this.removeClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.removeClass(el, clazz); + }; + + /* + Function: hasClass + + Helper method to abstract out differences in testing for css classes on the different renderer types. + */ + this.hasClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.hasClass(el, clazz); + }; + + /* + Function: addEndpoint + + Adds an to a given element or elements. + + Parameters: + + el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + params - Object containing Endpoint constructor arguments. For more information, see . + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some + shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in + this object are anything that 'params' can contain. See . + + Returns: + The newly created , if el referred to a single element. Otherwise, an array of newly created s. + + See Also: + + */ + this.addEndpoint = function(el, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // YUI wrapper + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + var _el = _getElementObject(inputs[i]), id = _getId(_el); + p.source = _el; + _updateOffset({ elId : id }); + var e = _newEndpoint(p); + if (p.parentAnchor) e.parentAnchor = p.parentAnchor; + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + results.push(e); + _currentInstance.dragManager.endpointAdded(_el); + } + + return results.length == 1 ? results[0] : results; + }; + + /* + Function: addEndpoints + Adds a list of s to a given element or elements. + + Parameters: + target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See 's constructor documentation. + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created s, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(el, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams); + if (e.constructor == Array) + Array.prototype.push.apply(results, e); + else results.push(e); + } + return results; + }; + + /* + Function: animate + This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating + the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // onComplete repaints, just to make sure everything looks good at the end of the animation. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /** + * checks for a listener for the given condition, executing it if found, passing in the given value. + * condition listeners would have been attached using "bind" (which is, you could argue, now overloaded, since + * firing click events etc is a bit different to what this does). i thought about adding a "bindCondition" + * or something, but decided against it, for the sake of simplicity. jsPlumb will never fire one of these + * condition events anyway. + */ + this.checkCondition = function(conditionName, value) { + var l = _currentInstance.getListener(conditionName); + var r = true; + if (l && l.length > 0) { + try { + for (var i = 0 ; i < l.length; i++) { + r = r && l[i](value); + } + } + catch (e) { + _log(_currentInstance, "cannot check condition [" + conditionName + "]" + e); + } + } + return r; + }; + + /* + Function: connect + Establishes a between two elements (or s, which are themselves registered to elements). + + Parameters: + params - Object containing constructor arguments for the Connection. See 's constructor documentation. + referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of + Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection. + + Returns: + The newly created . + */ + this.connect = function(params, referenceParams) { + // prepare a final set of parameters to create connection with + var _p = _prepareConnectionParams(params, referenceParams); + // TODO probably a nicer return value if the connection was not made. _prepareConnectionParams + // will return null (and log something) if either endpoint was full. what would be nicer is to + // create a dedicated 'error' object. + if (_p) { + // a connect call will delete its created endpoints on detach, unless otherwise specified. + // this is because the endpoints belong to this connection only, and are no use to + // anyone else, so they hang around like a bad smell. + if (_p.deleteEndpointsOnDetach == null) + _p.deleteEndpointsOnDetach = true; + + // create the connection. it is not yet registered + var jpc = _newConnection(_p); + // now add it the model, fire an event, and redraw + _finaliseConnection(jpc, _p); + return jpc; + } + }; + + /* + Function: deleteEndpoint + Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too) + + Parameters: + object - either an object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElements(endpoint.endpoint.getDisplayElements()); + _currentInstance.anchorManager.deleteEndpoint(endpoint); + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + _currentInstance.dragManager.endpointDeleted(endpoint); + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every , and their associated s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc, doFireEvent, originalEvent) { + // may have been given a connection, or in special cases, an object + var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + argIsConnection = jpc.constructor == connType, + params = argIsConnection ? { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + } : jpc; + + if (doFireEvent) _currentInstance.fire("jsPlumbConnectionDetached", params, originalEvent); + _currentInstance.anchorManager.connectionDetached(params); + }, + /** + fires an event to indicate an existing connection is being dragged. + */ + fireConnectionDraggingEvent = function(jpc) { + _currentInstance.fire("connectionDrag", jpc); + }, + fireConnectionDragStopEvent = function(jpc) { + _currentInstance.fire("connectionDragStop", jpc); + }; + + + /* + Function: detach + Detaches and then removes a . From 1.3.5 this method has been altered to remove support for + specifying Connections by various parameters; you can now pass in a Connection as the first argument and + an optional parameters object as a second argument. If you need the functionality this method provided + before 1.3.5 then you should use the getConnections method to get the list of Connections to detach, and + then iterate through them, calling this for each one. + + Parameters: + connection - the to detach + params - optional parameters to the detach call. valid values here are + fireEvent : defaults to false; indicates you want jsPlumb to fire a connection + detached event. The thinking behind this is that if you made a programmatic + call to detach an event, you probably don't need the callback. + forceDetach : defaults to false. allows you to override any beforeDetach listeners that may be registered. + + Returns: + true if successful, false if not. + */ + this.detach = function() { + + if (arguments.length == 0) return; + var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + firstArgIsConnection = arguments[0].constructor == connType, + params = arguments.length == 2 ? firstArgIsConnection ? (arguments[1] || {}) : arguments[0] : arguments[0], + fireEvent = (params.fireEvent !== false), + forceDetach = params.forceDetach, + connection = firstArgIsConnection ? arguments[0] : params.connection; + + if (connection) { + if (forceDetach || (connection.isDetachAllowed(connection) + && connection.endpoints[0].isDetachAllowed(connection) + && connection.endpoints[1].isDetachAllowed(connection))) { + if (forceDetach || _currentInstance.checkCondition("beforeDetach", connection)) + connection.endpoints[0].detach(connection, false, true, fireEvent); // TODO check this param iscorrect for endpoint's detach method + } + } + else { + var _p = jsPlumb.extend( {}, params); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1]), fireEvent); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source), + targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + if (_currentInstance.checkCondition("beforeDetach", jpc)) { + jpc.endpoints[0].detach(jpc, false, true, fireEvent); + } + } + }); + } + } + }; + + /* + Function: detachAllConnections + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + params - optional parameters. alowed values: + fireEvent : defaults to true, whether or not to fire the detach event. + + Returns: + void + */ + this.detachAllConnections = function(el, params) { + params = params || {}; + el = _getElementObject(el); + var id = _getAttribute(el, "id"), + endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + }; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Parameters: + params - optional params object containing: + fireEvent : whether or not to fire detach events. defaults to true. + + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function(params) { + params = params || {}; + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of y + our library's draggable method so that jsPlumb can setup the appropriate callbacks. Your + underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + // TODO it would be nice if this supported a selector string, instead of an id. + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + // helpers for select/selectEndpoints + var _setOperation = function(list, func, args, selector) { + for (var i = 0; i < list.length; i++) { + list[i][func].apply(list[i], args); + } + return selector(list); + }, + _getOperation = function(list, func, args) { + var out = []; + for (var i = 0; i < list.length; i++) { + out.push([ list[i][func].apply(list[i], args), list[i] ]); + } + return out; + }, + setter = function(list, func, selector) { + return function() { + return _setOperation(list, func, arguments, selector); + }; + }, + getter = function(list, func) { + return function() { + return _getOperation(list, func, arguments); + }; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method, + * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the + * default scope). If multiple scopes are passed in, the return value will be a map of { scope -> [ connection... ] }. + * + * Parameters + * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list + * of connections that are in the given scope. use '*' for all scopes. + * options - if the argument is a JS object, you can specify a finer-grained filter: + * + * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes. + * - *source* either a string representing an element id, or a selector. constrains the result to connections having this source. + * - *target* either a string representing an element id, or a selector. constrains the result to connections having this target. + * flat - return results in a flat array (don't return an object whose keys are scopes and whose values are lists per scope). + * + */ + this.getConnections = function(options, flat) { + if (!options) { + options = {}; + } else if (options.constructor == String) { + options = { "scope": options }; + } + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') { + if (input === "*") return input; + r.push(input); + } + else + r = input; + } + return r; + }, + scope = options.scope || _currentInstance.getDefaultScope(), + scopes = prepareList(scope), + sources = prepareList(options.source), + targets = prepareList(options.target), + filter = function(list, value) { + if (list === "*") return true; + return list.length > 0 ? _indexOf(list, value) != -1 : true; + }, + results = (!flat && scopes.length > 1) ? {} : [], + _addOne = function(scope, obj) { + if (!flat && scopes.length > 1) { + var ss = results[scope]; + if (ss == null) { + ss = []; results[scope] = ss; + } + ss.push(obj); + } else results.push(obj); + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + _addOne(i, c); + } + } + } + return results; + }; + + var _makeConnectionSelectHandler = function(list) { + //var + return { + // setters + setHover:setter(list, "setHover", _makeConnectionSelectHandler), + removeAllOverlays:setter(list, "removeAllOverlays", _makeConnectionSelectHandler), + setLabel:setter(list, "setLabel", _makeConnectionSelectHandler), + addOverlay:setter(list, "addOverlay", _makeConnectionSelectHandler), + removeOverlay:setter(list, "removeOverlay", _makeConnectionSelectHandler), + removeOverlays:setter(list, "removeOverlays", _makeConnectionSelectHandler), + showOverlay:setter(list, "showOverlay", _makeConnectionSelectHandler), + hideOverlay:setter(list, "hideOverlay", _makeConnectionSelectHandler), + showOverlays:setter(list, "showOverlays", _makeConnectionSelectHandler), + hideOverlays:setter(list, "hideOverlays", _makeConnectionSelectHandler), + setPaintStyle:setter(list, "setPaintStyle", _makeConnectionSelectHandler), + setHoverPaintStyle:setter(list, "setHoverPaintStyle", _makeConnectionSelectHandler), + setDetachable:setter(list, "setDetachable", _makeConnectionSelectHandler), + setConnector:setter(list, "setConnector", _makeConnectionSelectHandler), + setParameter:setter(list, "setParameter", _makeConnectionSelectHandler), + setParameters:setter(list, "setParameters", _makeConnectionSelectHandler), + + detach:function() { + for (var i = 0; i < list.length; i++) + _currentInstance.detach(list[i]); + }, + + // getters + getLabel:getter(list, "getLabel"), + getOverlay:getter(list, "getOverlay"), + isHover:getter(list, "isHover"), + isDetachable:getter(list, "isDetachable"), + getParameter:getter(list, "getParameter"), + getParameters:getter(list, "getParameters"), + + // util + length:list.length, + each:function(f) { + for (var i = 0; i < list.length; i++) { + f(list[i]); + } + return _makeConnectionSelectHandler(list); + }, + get:function(idx) { + return list[idx]; + } + + }; + }; + + this.select = function(params) { + params = params || {}; + params.scope = params.scope || "*"; + var c = _currentInstance.getConnections(params, true); + return _makeConnectionSelectHandler(c); + }; + + /* + * Function: getAllConnections + * Gets all connections, as a map of { scope -> [ connection... ] }. + */ + this.getAllConnections = function() { + return connectionsByScope; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /** + * Function:getEndpoints + * Gets the list of Endpoints for a given selector, or element id. + * @param el + * @return + */ + this.getEndpoints = function(el) { + return endpointsByElement[_getId(el)]; + }; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + this.getOffset = function(id) { + var o = offsets[id]; + return _updateOffset({elId:id}); + }; + + this.getSelector = function(spec) { + return jsPlumb.CurrentLibrary.getSelector(spec); + }; + + this.getSize = function(id) { + var s = sizes[id]; + if (!s) _updateOffset({elId:id}); + return sizes[id]; + }; + + this.appendElement = _appendElement; + + var _hoverSuspended = false; + this.isHoverSuspended = function() { return _hoverSuspended; }; + this.setHoverSuspended = function(s) { _hoverSuspended = s; }; + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + changeEndpoints - whether not to also hide endpoints on the element. by default this is false. + + Returns: + void + */ + this.hide = function(el, changeEndpoints) { + _setVisible(el, "none", changeEndpoints); + }; + + // exposed for other objects to use to get a unique id. + this.idstamp = _idstamp; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + if (!initialized) { + _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run. + + var bindOne = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && renderMode == jsPlumb.CANVAS) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + var t = c[i].connector[event](e); + if (t) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i].endpoint[event](e)) return; + } + } + } + }); + }; + bindOne("click");bindOne("dblclick");bindOne("mousemove");bindOne("mousedown");bindOne("mouseup");bindOne("contextmenu"); + + initialized = true; + _currentInstance.fire("ready"); + } + }; + + this.log = log; + this.jsPlumbUIComponent = jsPlumbUIComponent; + this.EventGenerator = EventGenerator; + + /* + * Creates an anchor with the given params. + * + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function() { + if (arguments.length == 0) return null; + var specimen = arguments[0], elementId = arguments[1], jsPlumbInstance = arguments[2], newAnchor = null; + if (!jsPlumbInstance) + throw "NO JSPLUMB SET"; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; //TODO hazy here about whether it should be added or is already added somehow. + // is it the name of an anchor type? + else if (typeof specimen == "string") { + newAnchor = jsPlumb.Anchors[arguments[0]]({elementId:elementId, jsPlumbInstance:_currentInstance}); + } + // is it an array? it will be one of: + // an array of [name, params] - this defines a single anchor + // an array of arrays - this defines some dynamic anchors + // an array of numbers - this defines a single anchor. + else if (specimen.constructor == Array) { + if (specimen[0].constructor == Array || specimen[0].constructor == String) { + if (specimen.length == 2 && specimen[0].constructor == String && specimen[1].constructor == Object) { + var pp = jsPlumb.extend({elementId:elementId, jsPlumbInstance:_currentInstance}, specimen[1]); + newAnchor = jsPlumb.Anchors[specimen[0]](pp); + } + else + newAnchor = new DynamicAnchor(specimen, null, elementId); + } + else { + var anchorParams = { + x:specimen[0], y:specimen[1], + orientation : (specimen.length >= 4) ? [ specimen[2], specimen[3] ] : [0,0], + offsets : (specimen.length == 6) ? [ specimen[4], specimen[5] ] : [ 0, 0 ], + elementId:elementId + }; + newAnchor = new Anchor(anchorParams); + newAnchor.clone = function() { return new Anchor(anchorParams); }; + } + } + + if (!newAnchor.id) newAnchor.id = "anchor_" + _idstamp(); + return newAnchor; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types, elementId, jsPlumbInstance) { + var r = []; + for ( var i = 0; i < types.length; i++) { + if (typeof types[i] == "string") + r.push(jsPlumb.Anchors[types[i]]({elementId:elementId, jsPlumbInstance:jsPlumbInstance})); + else if (types[i].constructor == Array) + r.push(_currentInstance.makeAnchor(types[i], elementId, jsPlumbInstance)); + } + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /** + * Function: makeTarget + * Makes some DOM element a Connection target, allowing you to drag connections to it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a target. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * scope optional. scope for the drop zone. + * dropOptions optional. same stuff as you would pass to dropOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to true. whether or not to delete + * any Endpoints created by a connection to this target if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + var _targetEndpointDefinitions = {}, + _targetEndpoints = {}, + _targetEndpointsUnique = {}, + _setEndpointPaintStylesAndAnchor = function(ep, epIndex) { + ep.paintStyle = ep.paintStyle || + _currentInstance.Defaults.EndpointStyles[epIndex] || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyles[epIndex] || + jsPlumb.Defaults.EndpointStyle; + ep.hoverPaintStyle = ep.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyles[epIndex] || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyles[epIndex] || + jsPlumb.Defaults.EndpointHoverStyle; + + ep.anchor = ep.anchor || + _currentInstance.Defaults.Anchors[epIndex] || + _currentInstance.Defaults.Anchor || + jsPlumb.Defaults.Anchors[epIndex] || + jsPlumb.Defaults.Anchor; + + ep.endpoint = ep.endpoint || + _currentInstance.Defaults.Endpoints[epIndex] || + _currentInstance.Defaults.Endpoint || + jsPlumb.Defaults.Endpoints[epIndex] || + jsPlumb.Defaults.Endpoint; + }; + this.makeTarget = function(el, params, referenceParams) { + + var p = jsPlumb.extend({_jsPlumb:_currentInstance}, referenceParams); + jsPlumb.extend(p, params); + _setEndpointPaintStylesAndAnchor(p, 1); + var jpcl = jsPlumb.CurrentLibrary, + targetScope = p.scope || _currentInstance.Defaults.Scope, + deleteEndpointsOnDetach = !(p.deleteEndpointsOnDetach === false), + _doOne = function(_el) { + + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el); + _targetEndpointDefinitions[elid] = p; + _targetEndpointsUnique[elid] = p.uniqueEndpoint, + proxyComponent = new jsPlumbUIComponent(p); + + var dropOptions = jsPlumb.extend({}, p.dropOptions || {}), + _drop = function() { + + var originalEvent = jsPlumb.CurrentLibrary.getDropEvent(arguments); + + _currentInstance.currentlyDragging = false; + var draggable = _getElementObject(jpcl.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + // restore the original scope if necessary (issue 57) + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id], + source = jpc.endpoints[0], + _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : {}; + + // unlock the source anchor to allow it to refresh its position if necessary + source.anchor.locked = false; + + if (scope) jpcl.setDragScope(draggable, scope); + + // check if drop is allowed here. + //var _continue = jpc.isDropAllowed(jpc.sourceId, _getId(_el), jpc.scope); + var _continue = proxyComponent.isDropAllowed(jpc.sourceId, _getId(_el), jpc.scope); + + // regardless of whether the connection is ok, reconfigure the existing connection to + // point at the current info. we need this to be correct for the detach event that will follow. + // clear the source endpoint from the list to detach. we will detach this connection at this + // point, but we want to keep the source endpoint. the target is a floating endpoint and should + // be removed. TODO need to figure out whether this code can result in endpoints kicking around + // when they shouldnt be. like is this a full detach of a connection? can it be? + if (jpc.endpointsToDeleteOnDetach) { + if (source === jpc.endpointsToDeleteOnDetach[0]) + jpc.endpointsToDeleteOnDetach[0] = null; + else if (source === jpc.endpointsToDeleteOnDetach[1]) + jpc.endpointsToDeleteOnDetach[1] = null; + } + // reinstate any suspended endpoint; this just puts the connection back into + // a state in which it will report sensible values if someone asks it about + // its target. we're going to throw this connection away shortly so it doesnt matter + // if we manipulate it a bit. + if (jpc.suspendedEndpoint) { + jpc.targetId = jpc.suspendedEndpoint.elementId; + jpc.target = jpcl.getElementObject(jpc.suspendedEndpoint.elementId); + jpc.endpoints[1] = jpc.suspendedEndpoint; + } + + if (_continue) { + + // detach this connection from the source. + source.detach(jpc, false, true, false);//source.endpointWillMoveAfterConnection); + + // make a new Endpoint for the target + //var newEndpoint = _currentInstance.addEndpoint(_el, _endpoint); + + var newEndpoint = _targetEndpoints[elid] || _currentInstance.addEndpoint(_el, p); + if (p.uniqueEndpoint) _targetEndpoints[elid] = newEndpoint; // may of course just store what it just pulled out. that's ok. + + // if the anchor has a 'positionFinder' set, then delegate to that function to find + // out where to locate the anchor. + if (newEndpoint.anchor.positionFinder != null) { + var dropPosition = jpcl.getUIPosition(arguments), + elPosition = jpcl.getOffset(_el), + elSize = jpcl.getSize(_el), + ap = newEndpoint.anchor.positionFinder(dropPosition, elPosition, elSize, newEndpoint.anchor.constructorParams); + newEndpoint.anchor.x = ap[0]; + newEndpoint.anchor.y = ap[1]; + // now figure an orientation for it..kind of hard to know what to do actually. probably the best thing i can do is to + // support specifying an orientation in the anchor's spec. if one is not supplied then i will make the orientation + // be what will cause the most natural link to the source: it will be pointing at the source, but it needs to be + // specified in one axis only, and so how to make that choice? i think i will use whichever axis is the one in which + // the target is furthest away from the source. + } + var c = _currentInstance.connect({ + source:source, + target:newEndpoint, + scope:scope, + previousConnection:jpc, + container:jpc.parent, + deleteEndpointsOnDetach:deleteEndpointsOnDetach, + // 'endpointWillMoveAfterConnection' is set by the makeSource function, and it indicates that the + // given endpoint will actually transfer from the element it is currently attached to to some other + // element after a connection has been established. in that case, we do not want to fire the + // connection event, since it will have the wrong data in it; makeSource will do it for us. + // this is controlled by the 'parent' parameter on a makeSource call. + doNotFireConnectionEvent:source.endpointWillMoveAfterConnection + }); + if (deleteEndpointsOnDetach) + c.endpointsToDeleteOnDetach = [ source, newEndpoint ]; + + c.repaint(); + } + // if not allowed to drop... + else { + // TODO this code is identical (pretty much) to what happens when a connection + // dragged from a normal endpoint is in this situation. refactor. + // is this an existing connection, and will we reattach? + if (jpc.suspendedEndpoint) { + if (source.isReattach) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + _currentInstance.repaint(source.elementId); + } + else + source.detach(jpc, false, true, true, originalEvent); // otherwise, detach the connection and tell everyone about it. + } + + } + }; + + var dropEvent = jpcl.dragEvents['drop']; + dropOptions["scope"] = dropOptions["scope"] || targetScope; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop); + + jpcl.initDroppable(_el, dropOptions, true); + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements drop targets. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeTargets = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeTarget(els[i], params, referenceParams); + } + }; + + /** + * Function: makeSource + * Makes some DOM element a Connection source, allowing you to drag connections from it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a source. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * parent optional. the element to add Endpoints to when a Connection is established. if you omit this, + * Endpoints will be added to 'el'. + * scope optional. scope for the connections dragged from this element. + * dragOptions optional. same stuff as you would pass to dragOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to false. whether or not to delete + * any Endpoints created by a connection from this source if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + var _sourceEndpointDefinitions = {}, + _sourceEndpoints = {}, + _sourceEndpointsUnique = {}; + + this.makeSource = function(el, params, referenceParams) { + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + _setEndpointPaintStylesAndAnchor(p, 0); + var jpcl = jsPlumb.CurrentLibrary, + _doOne = function(_el) { + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el), + parent = p.parent, + idToRegisterAgainst = parent != null ? _currentInstance.getId(jpcl.getElementObject(parent)) : elid; + + _sourceEndpointDefinitions[idToRegisterAgainst] = p; + _sourceEndpointsUnique[idToRegisterAgainst] = p.uniqueEndpoint; + + var stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"], + dragOptions = jsPlumb.extend({ }, p.dragOptions || {}), + existingDrag = dragOptions.drag, + existingStop = dragOptions.stop, + ep = null, + endpointAddedButNoDragYet = false; + + // set scope if its not set in dragOptions but was passed in in params + dragOptions["scope"] = dragOptions["scope"] || p.scope; + + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + if (existingDrag) existingDrag.apply(this, arguments); + endpointAddedButNoDragYet = false; + }); + + dragOptions[stopEvent] = function() { + if (existingStop) existingStop.apply(this, arguments); + + //_currentlyDown = false; + _currentInstance.currentlyDragging = false; + + if (ep.connections.length == 0) + _currentInstance.deleteEndpoint(ep); + else { + + jpcl.unbind(ep.canvas, "mousedown"); + + // reset the anchor to the anchor that was initially provided. the one we were using to drag + // the connection was just a placeholder that was located at the place the user pressed the + // mouse button to initiate the drag. + var anchorDef = p.anchor || _currentInstance.Defaults.Anchor, + oldAnchor = ep.anchor; + + ep.anchor = _currentInstance.makeAnchor(anchorDef, elid, _currentInstance); + + + if (p.parent) { + var parent = jpcl.getElementObject(p.parent); + if (parent) { + var currentId = ep.elementId; + ep.setElement(parent); + ep.endpointWillMoveAfterConnection = false; + _currentInstance.anchorManager.rehomeEndpoint(currentId, parent); + ep.connections[0].previousConnection = null; + _currentInstance.anchorManager.connectionDetached({ + sourceId:ep.connections[0].sourceId, + targetId:ep.connections[0].targetId, + connection:ep.connections[0] + }); + _finaliseConnection(ep.connections[0]); + } + } + + ep.repaint(); + _currentInstance.repaint(ep.elementId); + _currentInstance.repaint(ep.connections[0].targetId); + + } + }; + // when the user presses the mouse, add an Endpoint + var mouseDownListener = function(e) { + // make sure we have the latest offset for this div + var myOffsetInfo = _updateOffset({elId:elid}); + + var x = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width, + y = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height, + parentX = x, + parentY = y; + + + // if there is a parent, the endpoint will actually be added to it now, rather than the div + // that was the source. in that case, we have to adjust the anchor position so it refers to + // the parent. + if (p.parent) { + var pEl = jsPlumb.CurrentLibrary.getElementObject(p.parent), + pId = _getId(pEl); + myOffsetInfo = _updateOffset({elId:pId}); + parentX = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width, + parentY = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height; + } + + // we need to override the anchor in here, and force 'isSource', but we don't want to mess with + // the params passed in, because after a connection is established we're going to reset the endpoint + // to have the anchor we were given. + var tempEndpointParams = {}; + jsPlumb.extend(tempEndpointParams, p); + tempEndpointParams.isSource = true; + tempEndpointParams.anchor = [x,y,0,0]; + tempEndpointParams.parentAnchor = [ parentX, parentY, 0, 0 ]; + tempEndpointParams.dragOptions = dragOptions; + // if a parent was given we need to turn that into a "container" argument. this is, by default, + // the parent of the element we will move to, so parent of p.parent in this case. however, if + // the user has specified a 'container' on the endpoint definition or on + // the defaults, we should use that. + if (p.parent) { + var potentialParent = tempEndpointParams.container || _currentInstance.Defaults.Container; + if (potentialParent) + tempEndpointParams.container = potentialParent; + else + tempEndpointParams.container = jsPlumb.CurrentLibrary.getParent(p.parent); + } + + ep = _currentInstance.addEndpoint(elid, tempEndpointParams); + + endpointAddedButNoDragYet = true; + // we set this to prevent connections from firing attach events before this function has had a chance + // to move the endpoint. + ep.endpointWillMoveAfterConnection = p.parent != null; + ep.endpointWillMoveTo = p.parent ? jpcl.getElementObject(p.parent) : null; + + var _delTempEndpoint = function() { + // this mouseup event is fired only if no dragging occurred, by jquery and yui, but for mootools + // it is fired even if dragging has occurred, in which case we would blow away a perfectly + // legitimate endpoint, were it not for this check. the flag is set after adding an + // endpoint and cleared in a drag listener we set in the dragOptions above. + if(endpointAddedButNoDragYet) { + _currentInstance.deleteEndpoint(ep); + } + }; + + _currentInstance.registerListener(ep.canvas, "mouseup", _delTempEndpoint); + _currentInstance.registerListener(_el, "mouseup", _delTempEndpoint); + + // and then trigger its mousedown event, which will kick off a drag, which will start dragging + // a new connection from this endpoint. + jpcl.trigger(ep.canvas, "mousedown", e); + }; + + // register this on jsPlumb so that it can be cleared by a reset. + _currentInstance.registerListener(_el, "mousedown", mouseDownListener); + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements connection sources. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeSources = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeSource(els[i], params, referenceParams); + } + }; + + /* + Function: ready + Helper method to bind a function to jsPlumb's ready event. + */ + this.ready = function(fn) { + _currentInstance.bind("ready", fn); + }, + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"), + ebe = endpointsByElement[elId]; + if (ebe) { + for ( var i = 0; i < ebe.length; i++) + _currentInstance.deleteEndpoint(ebe[i]); + } + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + var _registeredListeners = {}, + _unbindRegisteredListeners = function() { + for (var i in _registeredListeners) { + for (var j = 0; j < _registeredListeners[i].length; j++) { + var info = _registeredListeners[i][j]; + jsPlumb.CurrentLibrary.unbind(info.el, info.event, info.listener); + } + } + _registeredListeners = {}; + }; + + // internal register listener method. gives us a hook to clean things up + // with if the user calls jsPlumb.reset. + this.registerListener = function(el, type, listener) { + jsPlumb.CurrentLibrary.bind(el, type, listener); + _addToList(_registeredListeners, type, {el:el, event:type, listener:listener}); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + _currentInstance.deleteEveryEndpoint(); + _currentInstance.clearListeners(); + _targetEndpointDefinitions = {}; + _targetEndpoints = {}; + _targetEndpointsUnique = {}; + _sourceEndpointDefinitions = {}; + _sourceEndpoints = {}; + _sourceEndpointsUnique = {}; + _unbindRegisteredListeners(); + _currentInstance.anchorManager.reset(); + _currentInstance.dragManager.reset(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + + Parameters: + value - whether or not to automatically repaint when the window is resized. + + Returns: void + * + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + };*/ + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setId + * Changes the id of some element, adjusting all connections and endpoints + * + * Parameters: + * el - a selector, a DOM element, or a string. + * newId - string. + */ + this.setId = function(el, newId, doNotSetAttribute) { + + var id = el.constructor == String ? el : _currentInstance.getId(el), + sConns = _currentInstance.getConnections({source:id, scope:'*'}, true), + tConns = _currentInstance.getConnections({target:id, scope:'*'}, true); + + newId = "" + newId; + + if (!doNotSetAttribute) { + el = jsPlumb.CurrentLibrary.getElementObject(id); + jsPlumb.CurrentLibrary.setAttribute(el, "id", newId); + } + + el = jsPlumb.CurrentLibrary.getElementObject(newId); + + + endpointsByElement[newId] = endpointsByElement[id] || []; + for (var i = 0; i < endpointsByElement[newId].length; i++) { + endpointsByElement[newId][i].elementId = newId; + endpointsByElement[newId][i].element = el; + endpointsByElement[newId][i].anchor.elementId = newId; + } + delete endpointsByElement[id]; + + _currentInstance.anchorManager.changeId(id, newId); + + var _conns = function(list, epIdx, type) { + for (var i = 0; i < list.length; i++) { + list[i].endpoints[epIdx].elementId = newId; + list[i].endpoints[epIdx].element = el; + list[i][type + "Id"] = newId; + list[i][type] = el; + } + }; + _conns(sConns, 0, "source"); + _conns(tConns, 1, "target"); + }; + + /* + * Function: setIdChanged + * Notify jsPlumb that the element with oldId has had its id changed to newId. + */ + this.setIdChanged = function(oldId, newId) { + _currentInstance.setId(oldId, newId, true); + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction + * Sets the function to fire when the window size has changed and a repaint was fired. + * + * Parameters: + * f - Function to execute. + * + * Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: setSuspendDrawing + * Suspends drawing operations. This can be used when you have a lot of connections to make or endpoints to register; + * it will save you a lot of time. + */ + this.setSuspendDrawing = _setSuspendDrawing; + + /* + * Constant for use with the setRenderMode method + */ + this.CANVAS = "canvas"; + + /* + * Constant for use with the setRenderMode method + */ + this.SVG = "svg"; + + this.VML = "vml"; + + /* + * Function: setRenderMode + * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that + * what you asked for is not supported (and that VML is). If you asked for VML but the browser does + * not support it, jsPlumb uses SVG. + * + * Returns: + * the render mode that jsPlumb set, which of course may be different from that requested. + */ + this.setRenderMode = function(mode) { + if (mode) + mode = mode.toLowerCase(); + else + return; + if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); + // now test we actually have the capability to do this. + if (mode === jsPlumb.CANVAS && canvasAvailable) + renderMode = jsPlumb.CANVAS; + else if (mode === jsPlumb.SVG && svgAvailable) + renderMode = jsPlumb.SVG; + else if (vmlAvailable) + renderMode = jsPlumb.VML; + + return renderMode; + }; + + this.getRenderMode = function() { return renderMode; }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * changeEndpoints - whether or not to also change the visible state of the endpoints on the element. this also has a bearing on + * other connections on those endpoints: if their other endpoint is also visible, the connections are made visible. + * + * Returns: + * void + */ + this.show = function(el, changeEndpoints) { + _setVisible(el, "block", changeEndpoints); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + //findIndex : _findIndex, + getId : _getId, + makeAnchor:self.makeAnchor, + makeDynamicAnchor:self.makeDynamicAnchor + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * changeEndpoints - whether or not to also toggle the endpoints on the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload + * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element. + * + * Returns: + * void + */ + this.unload = function() { + // this used to do something, but it turns out that what it did was nothing. + // now it exists only for backwards compatibility. + }; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + var adjustForParentOffsetAndScroll = function(xy, el) { + + var offsetParent = null, result = xy; + if (el.tagName.toLowerCase() === "svg" && el.parentNode) { + offsetParent = el.parentNode; + } + else if (el.offsetParent) { + offsetParent = el.offsetParent; + } + if (offsetParent != null) { + var po = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(offsetParent), + so = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : {left:offsetParent.scrollLeft, top:offsetParent.scrollTop}; + + + // i thought it might be cool to do this: + // lastReturnValue[0] = lastReturnValue[0] - offsetParent.offsetLeft + offsetParent.scrollLeft; + // lastReturnValue[1] = lastReturnValue[1] - offsetParent.offsetTop + offsetParent.scrollTop; + // but i think it ignores margins. my reasoning was that it's quicker to not hand off to some underlying + // library. + + result[0] = xy[0] - po.left + so.left; + result[1] = xy[1] - po.top + so.top; + } + + return result; + + }; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + this.elementId = params.elementId; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + + if (timestamp && timestamp === self.timestamp) + return lastReturnValue; + + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + + // adjust loc if there is an offsetParent + lastReturnValue = adjustForParentOffsetAndScroll(lastReturnValue, element.canvas); + + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function(_endpoint) { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + * + * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference, + // the canvas this refers to. + refCanvas = params.referenceCanvas, + size = _getSize(_getElementObject(refCanvas)), + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + xDir = 0, yDir = 0, + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + orientation = null, + _lastResult = null; + + // set these to 0 each; they are used by certain types of connectors in the loopback case, + // when the connector is trying to clear the element it is on. but for floating anchor it's not + // very important. + this.x = 0; this.y = 0; + + this.isFloating = true; + + this.compute = function(params) { + var xy = params.xy, element = params.element, + result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + + // adjust loc if there is an offsetParent + result = adjustForParentOffsetAndScroll(result, element.canvas); + + _lastResult = result; + return result; + }; + + this.getOrientation = function(_endpoint) { + if (orientation) return orientation; + else { + var o = ref.getOrientation(_endpoint); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchor is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector, elementId) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = [], self = this, + _convert = function(anchor) { + return anchor.constructor == Anchor ? anchor: _currentInstance.makeAnchor(anchor, elementId, _currentInstance); + }; + for (var i = 0; i < anchors.length; i++) + _anchors[i] = _convert(anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + this.locked = false; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null, + _curIndex = _anchors.length > 0 ? 0 : -1, + self = this, + + // helper method to calculate the distance between the centers of the two elements. + _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }, + + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + self.x = _curAnchor.x; + self.y = _curAnchor.y; + + return _curAnchor.compute(params); + }; + + this.getCurrentLocation = function() { + return _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + }; + + this.getOrientation = function(_endpoint) { return _curAnchor != null ? _curAnchor.getOrientation(_endpoint) : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + manages anchors for all elements. + */ + // "continuous" anchors: anchors that pick their location based on how many connections the given element has. + // this requires looking at a lot more elements than normal - anything that has had a Continuous anchor applied has + // to be recalculated. so this manager is used as a reference point. the first time, with a new timestamp, that + // a continuous anchor is asked to compute, it calls this guy. or maybe, even, this guy gets called somewhere else + // and compute only ever returns pre-computed values. either way, this is the central point, and we want it to + // be called as few times as possible. + var continuousAnchors = {}, + continuousAnchorLocations = {}, + continuousAnchorOrientations = {}, + Orientation = { HORIZONTAL : "horizontal", VERTICAL : "vertical", DIAGONAL : "diagonal", IDENTITY:"identity" }, + + // TODO this functions uses a crude method of determining orientation between two elements. + // 'diagonal' should be chosen when the angle of the line between the two centers is around + // one of 45, 135, 225 and 315 degrees. maybe +- 15 degrees. + calculateOrientation = function(sourceId, targetId, sd, td) { + + if (sourceId === targetId) return { + orientation:Orientation.IDENTITY, + a:["top", "top"] + }; + + var theta = Math.atan2((td.centery - sd.centery) , (td.centerx - sd.centerx)), + theta2 = Math.atan2((sd.centery - td.centery) , (sd.centerx - td.centerx)), + h = ((sd.left <= td.left && sd.right >= td.left) || (sd.left <= td.right && sd.right >= td.right) || + (sd.left <= td.left && sd.right >= td.right) || (td.left <= sd.left && td.right >= sd.right)), + v = ((sd.top <= td.top && sd.bottom >= td.top) || (sd.top <= td.bottom && sd.bottom >= td.bottom) || + (sd.top <= td.top && sd.bottom >= td.bottom) || (td.top <= sd.top && td.bottom >= sd.bottom)); + + if (! (h || v)) { + var a = null, rls = false, rrs = false, sortValue = null; + if (td.left > sd.left && td.top > sd.top) + a = ["right", "top"]; + else if (td.left > sd.left && sd.top > td.top) + a = [ "top", "left"]; + else if (td.left < sd.left && td.top < sd.top) + a = [ "top", "right"]; + else if (td.left < sd.left && td.top > sd.top) + a = ["left", "top" ]; + + return { orientation:Orientation.DIAGONAL, a:a, theta:theta, theta2:theta2 }; + } + else if (h) return { + orientation:Orientation.HORIZONTAL, + a:sd.top < td.top ? ["bottom", "top"] : ["top", "bottom"], + theta:theta, theta2:theta2 + } + else return { + orientation:Orientation.VERTICAL, + a:sd.left < td.left ? ["right", "left"] : ["left", "right"], + theta:theta, theta2:theta2 + } + }, + placeAnchorsOnLine = function(desc, elementDimensions, elementPosition, + connections, horizontal, otherMultiplier, reverse) { + var a = [], step = elementDimensions[horizontal ? 0 : 1] / (connections.length + 1); + + for (var i = 0; i < connections.length; i++) { + var val = (i + 1) * step, other = otherMultiplier * elementDimensions[horizontal ? 1 : 0]; + if (reverse) + val = elementDimensions[horizontal ? 0 : 1] - val; + + var dx = (horizontal ? val : other), x = elementPosition[0] + dx, xp = dx / elementDimensions[0], + dy = (horizontal ? other : val), y = elementPosition[1] + dy, yp = dy / elementDimensions[1]; + + a.push([ x, y, xp, yp, connections[i][1], connections[i][2] ]); + } + + return a; + }, + standardEdgeSort = function(a, b) { return a[0] > b[0] ? 1 : -1 }, + currySort = function(reverseAngles) { + return function(a,b) { + var r = true; + if (reverseAngles) { + if (a[0][0] < b[0][0]) + r = true; + else + r = a[0][1] > b[0][1]; + } + else { + if (a[0][0] > b[0][0]) + r= true; + else + r =a[0][1] > b[0][1]; + } + return r === false ? -1 : 1; + }; + }, + leftSort = function(a,b) { + // first get adjusted values + var p1 = a[0][0] < 0 ? -Math.PI - a[0][0] : Math.PI - a[0][0], + p2 = b[0][0] < 0 ? -Math.PI - b[0][0] : Math.PI - b[0][0]; + if (p1 > p2) return 1; + else return a[0][1] > b[0][1] ? 1 : -1; + }, + edgeSortFunctions = { + "top":standardEdgeSort, + "right":currySort(true), + "bottom":currySort(true), + "left":leftSort + }, + _sortHelper = function(_array, _fn) { + return _array.sort(_fn); + }, + placeAnchors = function(elementId, _anchorLists) { + var sS = sizes[elementId], sO = offsets[elementId], + placeSomeAnchors = function(desc, elementDimensions, elementPosition, unsortedConnections, isHorizontal, otherMultiplier, orientation) { + if (unsortedConnections.length > 0) { + var sc = _sortHelper(unsortedConnections, edgeSortFunctions[desc]), // puts them in order based on the target element's pos on screen + reverse = desc === "right" || desc === "top", + anchors = placeAnchorsOnLine(desc, elementDimensions, + elementPosition, sc, + isHorizontal, otherMultiplier, reverse ); + + // takes a computed anchor position and adjusts it for parent offset and scroll, then stores it. + var _setAnchorLocation = function(endpoint, anchorPos) { + var a = adjustForParentOffsetAndScroll([anchorPos[0], anchorPos[1]], endpoint.canvas); + continuousAnchorLocations[endpoint.id] = [ a[0], a[1], anchorPos[2], anchorPos[3] ]; + continuousAnchorOrientations[endpoint.id] = orientation; + }; + + for (var i = 0; i < anchors.length; i++) { + var c = anchors[i][4], weAreSource = c.endpoints[0].elementId === elementId, weAreTarget = c.endpoints[1].elementId === elementId; + if (weAreSource) + _setAnchorLocation(c.endpoints[0], anchors[i]); + else if (weAreTarget) + _setAnchorLocation(c.endpoints[1], anchors[i]); + } + } + }; + + placeSomeAnchors("bottom", sS, [sO.left,sO.top], _anchorLists.bottom, true, 1, [0,1]); + placeSomeAnchors("top", sS, [sO.left,sO.top], _anchorLists.top, true, 0, [0,-1]); + placeSomeAnchors("left", sS, [sO.left,sO.top], _anchorLists.left, false, 0, [-1,0]); + placeSomeAnchors("right", sS, [sO.left,sO.top], _anchorLists.right, false, 1, [1,0]); + }, + AnchorManager = function() { + var _amEndpoints = {}, + connectionsByElementId = {}, + self = this, + anchorLists = {}; + + this.reset = function() { + _amEndpoints = {}; + connectionsByElementId = {}; + anchorLists = {}; + }; + this.newConnection = function(conn) { + var sourceId = conn.sourceId, targetId = conn.targetId, + ep = conn.endpoints, + doRegisterTarget = true, + registerConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + + if ((sourceId == targetId) && otherAnchor.isContinuous){ + // remove the target endpoint's canvas. we dont need it. + jsPlumb.CurrentLibrary.removeElement(ep[1].canvas); + doRegisterTarget = false; + } + _addToList(connectionsByElementId, elId, [c, otherEndpoint, otherAnchor.constructor == DynamicAnchor]); + }; + + registerConnection(0, ep[0], ep[0].anchor, targetId, conn); + if (doRegisterTarget) + registerConnection(1, ep[1], ep[1].anchor, sourceId, conn); + }; + this.connectionDetached = function(connInfo) { + var connection = connInfo.connection || connInfo; + var sourceId = connection.sourceId, + targetId = connection.targetId, + ep = connection.endpoints, + removeConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + if (otherAnchor.constructor == FloatingAnchor) { + // no-op + } + else { + _removeWithFunction(connectionsByElementId[elId], function(_c) { + return _c[0].id == c.id; + }); + } + }; + + removeConnection(1, ep[1], ep[1].anchor, sourceId, connection); + removeConnection(0, ep[0], ep[0].anchor, targetId, connection); + + // remove from anchorLists + var sEl = connection.sourceId, + tEl = connection.targetId, + sE = connection.endpoints[0].id, + tE = connection.endpoints[1].id, + _remove = function(list, eId) { + if (list) { // transient anchors dont get entries in this list. + var f = function(e) { return e[4] == eId; }; + _removeWithFunction(list["top"], f); + _removeWithFunction(list["left"], f); + _removeWithFunction(list["bottom"], f); + _removeWithFunction(list["right"], f); + } + }; + + _remove(anchorLists[sEl], sE); + _remove(anchorLists[tEl], tE); + self.redraw(sEl); + self.redraw(tEl); + }; + this.add = function(endpoint, elementId) { + _addToList(_amEndpoints, elementId, endpoint); + }; + this.changeId = function(oldId, newId) { + connectionsByElementId[newId] = connectionsByElementId[oldId]; + _amEndpoints[newId] = _amEndpoints[oldId]; + delete connectionsByElementId[oldId]; + delete _amEndpoints[oldId]; + }; + this.getConnectionsFor = function(elementId) { + return connectionsByElementId[elementId] || []; + }; + this.getEndpointsFor = function(elementId) { + return _amEndpoints[elementId] || []; + }; + this.deleteEndpoint = function(endpoint) { + _removeWithFunction(_amEndpoints[endpoint.elementId], function(e) { + return e.id == endpoint.id; + }); + }; + this.clearFor = function(elementId) { + delete _amEndpoints[elementId]; + _amEndpoints[elementId] = []; + }; + // updates the given anchor list by either updating an existing anchor's info, or adding it. this function + // also removes the anchor from its previous list, if the edge it is on has changed. + // all connections found along the way (those that are connected to one of the faces this function + // operates on) are added to the connsToPaint list, as are their endpoints. in this way we know to repaint + // them wthout having to calculate anything else about them. + var _updateAnchorList = function(lists, theta, order, conn, aBoolean, otherElId, idx, reverse, edgeId, elId, connsToPaint, endpointsToPaint) { + // first try to find the exact match, but keep track of the first index of a matching element id along the way.s + var exactIdx = -1, + firstMatchingElIdx = -1, + endpoint = conn.endpoints[idx], + endpointId = endpoint.id, + oIdx = [1,0][idx], + values = [ [ theta, order ], conn, aBoolean, otherElId, endpointId ], + listToAddTo = lists[edgeId], + listToRemoveFrom = endpoint._continuousAnchorEdge ? lists[endpoint._continuousAnchorEdge] : null; + + if (listToRemoveFrom) { + var rIdx = _findWithFunction(listToRemoveFrom, function(e) { return e[4] == endpointId }); + if (rIdx != -1) { + listToRemoveFrom.splice(rIdx, 1); + // get all connections from this list + for (var i = 0; i < listToRemoveFrom.length; i++) { + _addWithFunction(connsToPaint, listToRemoveFrom[i][1], function(c) { return c.id == listToRemoveFrom[i][1].id }); + _addWithFunction(endpointsToPaint, listToRemoveFrom[i][1].endpoints[idx], function(e) { return e.id == listToRemoveFrom[i][1].endpoints[idx].id }); + } + } + } + + for (var i = 0; i < listToAddTo.length; i++) { + if (idx == 1 && listToAddTo[i][3] === otherElId && firstMatchingElIdx == -1) + firstMatchingElIdx = i; + _addWithFunction(connsToPaint, listToAddTo[i][1], function(c) { return c.id == listToAddTo[i][1].id }); + _addWithFunction(endpointsToPaint, listToAddTo[i][1].endpoints[idx], function(e) { return e.id == listToAddTo[i][1].endpoints[idx].id }); + } + if (exactIdx != -1) { + listToAddTo[exactIdx] = values; + } + else { + var insertIdx = reverse ? firstMatchingElIdx != -1 ? firstMatchingElIdx : 0 : listToAddTo.length; // of course we will get this from having looked through the array shortly. + listToAddTo.splice(insertIdx, 0, values); + } + + // store this for next time. + endpoint._continuousAnchorEdge = edgeId; + }; + this.redraw = function(elementId, ui, timestamp, offsetToUI) { + // get all the endpoints for this element + var ep = _amEndpoints[elementId] || [], + endpointConnections = connectionsByElementId[elementId] || [], + connectionsToPaint = [], + endpointsToPaint = [], + anchorsToUpdate = []; + + timestamp = timestamp || _timestamp(); + // offsetToUI are values that would have been calculated in the dragManager when registering + // an endpoint for an element that had a parent (somewhere in the hierarchy) that had been + // registered as draggable. + offsetToUI = offsetToUI || {left:0, top:0}; + if (ui) { + ui = { + left:ui.left + offsetToUI.left, + top:ui.top + offsetToUI.top + } + } + + _updateOffset( { elId : elementId, offset : ui, recalc : false, timestamp : timestamp }); + // valid for one paint cycle. + var myOffset = offsets[elementId], + myWH = sizes[elementId], + orientationCache = {}; + + // actually, first we should compute the orientation of this element to all other elements to which + // this element is connected with a continuous anchor (whether both ends of the connection have + // a continuous anchor or just one) + //for (var i = 0; i < continuousAnchorConnections.length; i++) { + for (var i = 0; i < endpointConnections.length; i++) { + var conn = endpointConnections[i][0], + sourceId = conn.sourceId, + targetId = conn.targetId, + sourceContinuous = conn.endpoints[0].anchor.isContinuous, + targetContinuous = conn.endpoints[1].anchor.isContinuous; + + if (sourceContinuous || targetContinuous) { + var oKey = sourceId + "_" + targetId, + oKey2 = targetId + "_" + sourceId, + o = orientationCache[oKey], + oIdx = conn.sourceId == elementId ? 1 : 0; + + if (sourceContinuous && !anchorLists[sourceId]) anchorLists[sourceId] = { top:[], right:[], bottom:[], left:[] }; + if (targetContinuous && !anchorLists[targetId]) anchorLists[targetId] = { top:[], right:[], bottom:[], left:[] }; + + if (elementId != targetId) _updateOffset( { elId : targetId, timestamp : timestamp }); + if (elementId != sourceId) _updateOffset( { elId : sourceId, timestamp : timestamp }); + + var td = _getCachedData(targetId), + sd = _getCachedData(sourceId); + + if (targetId == sourceId && (sourceContinuous || targetContinuous)) { + // here we may want to improve this by somehow determining the face we'd like + // to put the connector on. ideally, when drawing, the face should be calculated + // by determining which face is closest to the point at which the mouse button + // was released. for now, we're putting it on the top face. + _updateAnchorList(anchorLists[sourceId], -Math.PI / 2, 0, conn, false, targetId, 0, false, "top", sourceId, connectionsToPaint, endpointsToPaint) + } + else { + if (!o) { + o = calculateOrientation(sourceId, targetId, sd.o, td.o); + orientationCache[oKey] = o; + // this would be a performance enhancement, but the computed angles need to be clamped to + //the (-PI/2 -> PI/2) range in order for the sorting to work properly. + /* orientationCache[oKey2] = { + orientation:o.orientation, + a:[o.a[1], o.a[0]], + theta:o.theta + Math.PI, + theta2:o.theta2 + Math.PI + };*/ + } + if (sourceContinuous) _updateAnchorList(anchorLists[sourceId], o.theta, 0, conn, false, targetId, 0, false, o.a[0], sourceId, connectionsToPaint, endpointsToPaint); + if (targetContinuous) _updateAnchorList(anchorLists[targetId], o.theta2, -1, conn, true, sourceId, 1, true, o.a[1], targetId, connectionsToPaint, endpointsToPaint); + } + + if (sourceContinuous) _addWithFunction(anchorsToUpdate, sourceId, function(a) { return a === sourceId; }); + if (targetContinuous) _addWithFunction(anchorsToUpdate, targetId, function(a) { return a === targetId; }); + _addWithFunction(connectionsToPaint, conn, function(c) { return c.id == conn.id; }); + if ((sourceContinuous && oIdx == 0) || (targetContinuous && oIdx == 1)) + _addWithFunction(endpointsToPaint, conn.endpoints[oIdx], function(e) { return e.id == conn.endpoints[oIdx].id; }); + } + } + + // now place all the continuous anchors we need to; + for (var i = 0; i < anchorsToUpdate.length; i++) { + placeAnchors(anchorsToUpdate[i], anchorLists[anchorsToUpdate[i]]); + } + + // now that continuous anchors have been placed, paint all the endpoints for this element + // TODO performance: add the endpoint ids to a temp array, and then when iterating in the next + // loop, check that we didn't just paint that endpoint. we can probably shave off a few more milliseconds this way. + for (var i = 0; i < ep.length; i++) { + ep[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + // ... and any other endpoints we came across as a result of the continuous anchors. + for (var i = 0; i < endpointsToPaint.length; i++) { + endpointsToPaint[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + + // paint all the standard and "dynamic connections", which are connections whose other anchor is + // static and therefore does need to be recomputed; we make sure that happens only one time. + + // TODO we could have compiled a list of these in the first pass through connections; might save some time. + for (var i = 0; i < endpointConnections.length; i++) { + var otherEndpoint = endpointConnections[i][1]; + if (otherEndpoint.anchor.constructor == DynamicAnchor) { + otherEndpoint.paint({ elementWithPrecedence:elementId }); + _addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; }); + // all the connections for the other endpoint now need to be repainted + for (var k = 0; k < otherEndpoint.connections.length; k++) { + if (otherEndpoint.connections[k] !== endpointConnections[i][0]) + _addWithFunction(connectionsToPaint, otherEndpoint.connections[k], function(c) { return c.id == otherEndpoint.connections[k].id; }); + } + } else if (otherEndpoint.anchor.constructor == Anchor) { + _addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; }); + } + } + // paint current floating connection for this element, if there is one. + var fc = floatingConnections[elementId]; + if (fc) + fc.paint({timestamp:timestamp, recalc:false, elId:elementId}); + + // paint all the connections + for (var i = 0; i < connectionsToPaint.length; i++) { + connectionsToPaint[i].paint({elId:elementId, timestamp:timestamp, recalc:false}); + } + }; + this.rehomeEndpoint = function(currentId, element) { + var eps = _amEndpoints[currentId] || [], //, + elementId = _currentInstance.getId(element); + for (var i = 0; i < eps.length; i++) { + self.add(eps[i], elementId); + } + eps.splice(0, eps.length); + }; + }; + _currentInstance.anchorManager = new AnchorManager(); + _currentInstance.continuousAnchorFactory = { + get:function(params) { + var existing = continuousAnchors[params.elementId]; + if (!existing) { + existing = { + type:"Continuous", + compute : function(params) { + return continuousAnchorLocations[params.element.id] || [0,0]; + }, + getCurrentLocation : function(endpoint) { + return continuousAnchorLocations[endpoint.id] || [0,0]; + }, + getOrientation : function(endpoint) { + return continuousAnchorOrientations[endpoint.id] || [0,0]; + }, + isDynamic : true, + isContinuous : true + }; + continuousAnchors[params.elementId] = existing; + } + return existing; + } + }; + + /** + Manages dragging for some instance of jsPlumb. + + */ + var DragManager = function() { + + var _draggables = {}, _dlist = [], _delements = {}, _elementsWithEndpoints = {}; + + /** + register some element as draggable. right now the drag init stuff is done elsewhere, and it is + possible that will continue to be the case. + */ + this.register = function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + var id = _currentInstance.getId(el), + domEl = jsPlumb.CurrentLibrary.getDOMElement(el); + if (!_draggables[id]) { + _draggables[id] = el; + _dlist.push(el); + _delements[id] = {}; + } + + // look for child elements that have endpoints and register them against this draggable. + var _oneLevel = function(p) { + var pEl = jsPlumb.CurrentLibrary.getElementObject(p), + pOff = jsPlumb.CurrentLibrary.getOffset(pEl); + + for (var i = 0; i < p.childNodes.length; i++) { + if (p.childNodes[i].nodeType != 3) { + var cEl = jsPlumb.CurrentLibrary.getElementObject(p.childNodes[i]), + cid = _currentInstance.getId(cEl); + if (_elementsWithEndpoints[cid] && _elementsWithEndpoints[cid] > 0) { + var cOff = jsPlumb.CurrentLibrary.getOffset(cEl); + _delements[id][cid] = { + id:cid, + offset:{ + left:cOff.left - pOff.left, + top:cOff.top - pOff.top + } + }; + } + } + } + }; + + _oneLevel(domEl); + }; + + /** + notification that an endpoint was added to the given el. we go up from that el's parent + node, looking for a parent that has been registered as a draggable. if we find one, we add this + el to that parent's list of elements to update on drag (if it is not there already) + */ + this.endpointAdded = function(el) { + var jpcl = jsPlumb.CurrentLibrary, b = document.body, id = _currentInstance.getId(el), c = jpcl.getDOMElement(el), + p = c.parentNode, done = p == b; + + _elementsWithEndpoints[id] = _elementsWithEndpoints[id] ? _elementsWithEndpoints[id] + 1 : 1; + + while (p != b) { + var pid = _currentInstance.getId(p); + if (_draggables[pid]) { + var idx = -1, pEl = jpcl.getElementObject(p), pLoc = jsPlumb.CurrentLibrary.getOffset(pEl); + + if (_delements[pid][id] == null) { + var cLoc = jsPlumb.CurrentLibrary.getOffset(el); + _delements[pid][id] = { + id:id, + offset:{ + left:cLoc.left - pLoc.left, + top:cLoc.top - pLoc.top + } + }; + } + break; + } + p = p.parentNode; + } + }; + + this.endpointDeleted = function(endpoint) { + if (_elementsWithEndpoints[endpoint.elementId]) { + _elementsWithEndpoints[endpoint.elementId]--; + if (_elementsWithEndpoints[endpoint.elementId] <= 0) { + for (var i in _delements) { + delete _delements[i][endpoint.elementId]; + } + } + } + }; + + this.getElementsForDraggable = function(id) { + return _delements[id]; + }; + + this.reset = function() { + _draggables = {}; + _dlist = []; + _delements = {}; + _elementsWithEndpoints = {}; + }; + + }; + _currentInstance.dragManager = new DragManager(); + + + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * container - optional id or selector instructing jsPlumb where to attach all the elements it creates for this connection. you should read the documentation for a full discussion of this. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * overlays - Optional array of Overlay definitions to appear on this Connection. + * drawEndpoints - if false, instructs jsPlumb to not draw the endpoints for this Connection. Be careful with this: it only really works when you tell jsPlumb to attach elements to the document body. Read the documentation for a full discussion of this. + */ + var Connection = function(params) { + var self = this, visible = true; + self.idPrefix = "_jsplumb_c_"; + self.defaultLabelLocation = 0.5; + self.defaultOverlayKeys = ["Overlays", "ConnectionOverlays"]; + this.parent = params.parent; + overlayCapableJsPlumbUIComponent.apply(this, arguments); + // ************** get the source and target and register the connection. ******************* + + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + self[v ? "showOverlays" : "hideOverlays"](); + if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none"; + }; + + /** + Property: source + The source element for this Connection. + */ + this.source = _getElementObject(params.source); + /** + Property:target + The target element for this Connection. + */ + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. but + // source is not overridden if the Endpoint has declared it is not the final target of a connection; + // instead we use the source that the Endpoint declares will be the final source element. + if (params.sourceEndpoint) { + this.source = params.sourceEndpoint.endpointWillMoveTo || params.sourceEndpoint.getElement(); + } + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + + // if a new connection is the result of moving some existing connection, params.previousConnection + // will have that Connection in it. listeners for the jsPlumbConnection event can look for that + // member and take action if they need to. + self.previousConnection = params.previousConnection; + + var _cost = params.cost; + self.getCost = function() { return _cost; }; + self.setCost = function(c) { _cost = c; }; + + var _bidirectional = params.bidirectional === false ? false : true; + self.isBidirectional = function() { return _bidirectional; }; + + /* + * Property: sourceId + * Id of the source element in the connection. + */ + this.sourceId = _getAttribute(this.source, "id"); + /* + * Property: targetId + * Id of the target element in the connection. + */ + this.targetId = _getAttribute(this.target, "id"); + + /** + * implementation of abstract method in EventGenerator + * @return list of attached elements. in our case, a list of Endpoints. + */ + this.getAttachedElements = function() { + return self.endpoints; + }; + + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams, elementId) { + if (anchorParams) + return _currentInstance.makeAnchor(anchorParams, elementId, _currentInstance); + }, + prepareEndpoint = function(existing, index, params, element, elementId, connectorPaintStyle, connectorHoverPaintStyle) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] + || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint; + + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified. + if (es.fillStyle == null && connectorPaintStyle != null) + es.fillStyle = connectorPaintStyle.strokeStyle; + + // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does: + //* + if (es.outlineColor == null && connectorPaintStyle != null) + es.outlineColor = connectorPaintStyle.outlineColor; + if (es.outlineWidth == null && connectorPaintStyle != null) + es.outlineWidth = connectorPaintStyle.outlineWidth; + //*/ + + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure? + if (connectorHoverPaintStyle != null) { + if (ehs == null) ehs = {}; + if (ehs.fillStyle == null) { + ehs.fillStyle = connectorHoverPaintStyle.strokeStyle; + } + } + var a = params.anchors ? params.anchors[index] : + params.anchor ? params.anchor : + _makeAnchor(_currentInstance.Defaults.Anchors[index], elementId) || + _makeAnchor(jsPlumb.Defaults.Anchors[index], elementId) || + _makeAnchor(_currentInstance.Defaults.Anchor, elementId) || + _makeAnchor(jsPlumb.Defaults.Anchor, elementId), + u = params.uuids ? params.uuids[index] : null, + e = _newEndpoint({ + paintStyle : es, + hoverPaintStyle:ehs, + endpoint : ep, + connections : [ self ], + uuid : u, + anchor : a, + source : element, + scope : params.scope, + container:params.container, + reattach:params.reattach, + detachable:params.detachable + }); + self.endpoints[index] = e; + + + if (params.drawEndpoints === false) e.setVisible(false, true, true); + + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, + 0, + params, + self.source, + self.sourceId, + params.paintStyle, + params.hoverPaintStyle); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + + // if there were no endpoints supplied and the source element is the target element, we will reuse the source + // endpoint that was just created. + var existingTargetEndpoint = ((self.sourceId == self.targetId) && params.targetEndpoint == null) ? eS : params.targetEndpoint, + eT = prepareEndpoint(existingTargetEndpoint, + 1, + params, + self.target, + self.targetId, + params.paintStyle, + params.hoverPaintStyle); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + // if delete endpoints on detach, keep a record of just exactly which endpoints they are. + if (params.deleteEndpointsOnDetach) + self.endpointsToDeleteOnDetach = [eS, eT]; + + var _detachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.detachable === false) _detachable = false; + if(self.endpoints[0].connectionsDetachable === false) _detachable = false; + if(self.endpoints[1].connectionsDetachable === false) _detachable = false; + + // inherit connectin cost if it was set on source endpoint + if (_cost == null) _cost = self.endpoints[0].getConnectionCost(); + // inherit bidirectional flag if set no source endpoint + if (params.bidirectional == null) _bidirectional = self.endpoints[0].areConnectionsBidirectional(); + + /* + Function: isDetachable + Returns whether or not this connection can be detached from its target/source endpoint. by default this + is false; use it in conjunction with the 'reattach' parameter. + */ + this.isDetachable = function() { + return _detachable === true; + }; + + /* + Function: setDetachable + Sets whether or not this connection is detachable. + */ + this.setDetachable = function(detachable) { + _detachable = detachable === true; + }; + + // merge all the parameters objects into the connection. parameters set + // on the connection take precedence; then target endpoint params, then + // finally source endpoint params. + // TODO jsPlumb.extend could be made to take more than two args, and it would + // apply the second through nth args in order. + var _p = jsPlumb.extend({}, this.endpoints[0].getParameters()); + jsPlumb.extend(_p, this.endpoints[1].getParameters()); + jsPlumb.extend(_p, self.getParameters()); + self.setParameters(_p); + + // override setHover to pass it down to the underlying connector + var _sh = self.setHover; + + self.setHover = function() { + self.connector.setHover.apply(self.connector, arguments); + _sh.apply(self, arguments); + }; + + var _internalHover = function(state) { + if (_connectionBeingDragged == null) { + self.setHover(state, false); + } + }; + + /* + * Function: setConnector + * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method - the same + * thing that you would set as the 'connector' property on a jsPlumb.connect call. + * + * Parameters: + * connector - Connector definition + */ + this.setConnector = function(connector, doNotRepaint) { + if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent); + var connectorArgs = { + _jsPlumb:self._jsPlumb, + parent:params.parent, + cssClass:params.cssClass, + container:params.container, + tooltip:self.tooltip + }; + if (connector.constructor == String) + this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand. + else if (connector.constructor == Array) + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs)); + self.canvas = self.connector.canvas; + // binds mouse listeners to the current connector. + _bindListeners(self.connector, self, _internalHover); + if (!doNotRepaint) self.repaint(); + }; + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc) + */ + + self.setConnector(this.endpoints[0].connector || + this.endpoints[1].connector || + params.connector || + _currentInstance.Defaults.Connector || + jsPlumb.Defaults.Connector, true); + + + this.setPaintStyle(this.endpoints[0].connectorStyle || + this.endpoints[1].connectorStyle || + params.paintStyle || + _currentInstance.Defaults.PaintStyle || + jsPlumb.Defaults.PaintStyle, true); + + this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || + this.endpoints[1].connectorHoverStyle || + params.hoverPaintStyle || + _currentInstance.Defaults.HoverPaintStyle || + jsPlumb.Defaults.HoverPaintStyle, true); + + this.paintStyleInUse = this.paintStyle; + + + this.moveParent = function(newParent) { + var jpcl = jsPlumb.CurrentLibrary, curParent = jpcl.getParent(self.connector.canvas); + jpcl.removeElement(self.connector.canvas, curParent); + jpcl.appendElement(self.connector.canvas, newParent); + if (self.connector.bgCanvas) { + jpcl.removeElement(self.connector.bgCanvas, curParent); + jpcl.appendElement(self.connector.bgCanvas, newParent); + } + // this only applies for DOMOverlays + for (var i = 0; i < self.overlays.length; i++) { + if (self.overlays[i].isAppendedAtTopLevel) { + jpcl.removeElement(self.overlays[i].canvas, curParent); + jpcl.appendElement(self.overlays[i].canvas, newParent); + if (self.overlays[i].reattachListeners) + self.overlays[i].reattachListeners(self.connector); + } + } + if (self.connector.reattachListeners) // this is for SVG/VML; change an element's parent and you have to reinit its listeners. + self.connector.reattachListeners(); // the Canvas implementation doesn't have to care about this + }; + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Connection. + * + * Parameters: + * event - the event to bind. Available events on a Connection are: + * - *click* : notification that a Connection was clicked. + * - *dblclick* : notification that a Connection was double clicked. + * - *mouseenter* : notification that the mouse is over a Connection. + * - *mouseexit* : notification that the mouse exited a Connection. + * + * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Connection's paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Connection will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Connection. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId], + otherOffset = offsets[this.targetId], + otherWH = sizes[this.targetId], + initialTimestamp = _timestamp(), + anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + elementId:this.endpoints[0].elementId, + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1], + timestamp:initialTimestamp + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc, timestamp:initialTimestamp }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + elementId:this.endpoints[1].elementId, + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0], + timestamp:initialTimestamp + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc, timestamp:initialTimestamp }); + + /* + * Paints the Connection. Not exposed for public usage. + * + * Parameters: + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp, + // if the moving object is not the source we must transpose the two references. + swap = false, + tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId, + tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + + var sourceInfo = _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }), + targetInfo = _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var sE = this.endpoints[sIdx], tE = this.endpoints[tIdx], + sAnchorP = sE.anchor.getCurrentLocation(sE), + tAnchorP = tE.anchor.getCurrentLocation(tE); + + /* paint overlays*/ + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible()) maxSize = Math.max(maxSize, o.computeMaxSize(self.connector)); + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, + this.endpoints[sIdx], this.endpoints[tIdx], + this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, + self.paintStyleInUse.lineWidth, maxSize, + sourceInfo, + targetInfo); + + self.connector.paint(dim, self.paintStyleInUse); + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim); + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function(params) { + params = params || {}; + var recalc = !(params.recalc === false); + this.paint({ elId : this.sourceId, recalc : recalc, timestamp:params.timestamp }); + }; + + }; + +// ENDPOINT HELPER FUNCTIONS + var _makeConnectionDragHandler = function(placeholder) { + var stopped = false; + return { + + drag : function() { + if (stopped) { + stopped = false; + return true; + } + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments), + el = placeholder.element; + if (el) { + jsPlumb.CurrentLibrary.setOffset(el, _ui); + _draw(_getElementObject(el), _ui); + } + }, + stopDrag : function() { + stopped = true; + } + }; + }; + + var _makeFloatingEndpoint = function(paintStyle, referenceAnchor, endpoint, referenceCanvas, sourceElement) { + var floatingAnchor = new FloatingAnchor( { reference : referenceAnchor, referenceCanvas : referenceCanvas }); + + //setting the scope here should not be the way to fix that mootools issue. it should be fixed by not + // adding the floating endpoint as a droppable. that makes more sense anyway! + + return _newEndpoint({ paintStyle : paintStyle, endpoint : endpoint, anchor : floatingAnchor, source : sourceElement, scope:"__floating" }); + }; + + /** + * creates a placeholder div for dragging purposes, adds it to the DOM, and pre-computes its offset. then returns + * both the element id and a selector for the element. + */ + var _makeDraggablePlaceholder = function(placeholder, parent) { + var n = document.createElement("div"); + n.style.position = "absolute"; + var placeholderDragElement = _getElementObject(n); + _appendElement(n, parent); + var id = _getId(placeholderDragElement); + _updateOffset( { elId : id }); + // create and assign an id, and initialize the offset. + placeholder.id = id; + placeholder.element = placeholderDragElement; + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1 + * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't + * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you + * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or + * 'target' Endpoints for Connections. + * + * + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * enabled - optional, defaults to true. Indicates whether or not the Endpoint should be enabled for mouse events (drag/drop). + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * container - optional id or selector instructing jsPlumb where to attach the element it creates for this endpoint. you should read the documentation for a full discussion of this. + * connections - optional list of Connections to configure the Endpoint with. + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + var self = this; + self.idPrefix = "_jsplumb_e_"; + self.defaultLabelLocation = [ 0.5, 0.5 ]; + self.defaultOverlayKeys = ["Overlays", "EndpointOverlays"]; + this.parent = params.parent; + overlayCapableJsPlumbUIComponent.apply(this, arguments); + params = params || {}; + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Endpoint. + * + * Parameters: + * event - the event to bind. Available events on an Endpoint are: + * - *click* : notification that a Endpoint was clicked. + * - *dblclick* : notification that a Endpoint was double clicked. + * - *mouseenter* : notification that the mouse is over a Endpoint. + * - *mouseexit* : notification that the mouse exited a Endpoint. + * + * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Endpoint's paint style and then repaints the Endpoint. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Endpoint will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Endpoint. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + var visible = true, __enabled = !(params.enabled === false); + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + self[v ? "showOverlays" : "hideOverlays"](); + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + + /* + Function: isEnabled + Returns whether or not the Endpoint is enabled for drag/drop connections. + */ + this.isEnabled = function() { return __enabled; }; + + /* + Function: setEnabled + Sets whether or not the Endpoint is enabled for drag/drop connections. + */ + this.setEnabled = function(e) { __enabled = e; }; + + var _element = params.source, _uuid = params.uuid, floatingEndpoint = null, inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + this.element = _element; + + var _connectionCost = params.connectionCost; + this.getConnectionCost = function() { return _connectionCost; }; + this.setConnectionCost = function(c) { + _connectionCost = c; + }; + + var _connectionsBidirectional = params.connectionsBidirectional === false ? false : true; + this.areConnectionsBidirectional = function() { return _connectionsBidirectional; }; + this.setConnectionsBidirectional = function(b) { _connectionsBidirectional = b; }; + + self.anchor = params.anchor ? _currentInstance.makeAnchor(params.anchor, _elementId, _currentInstance) : params.anchors ? _currentInstance.makeAnchor(params.anchors, _elementId, _currentInstance) : _currentInstance.makeAnchor("TopCenter", _elementId, _currentInstance); + + // ANCHOR MANAGER + if (!params._transient) // in place copies, for example, are transient. they will never need to be retrieved during a paint cycle, because they dont move, and then they are deleted. + _currentInstance.anchorManager.add(self, _elementId); + + var _endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot", + endpointArgs = { + _jsPlumb:self._jsPlumb, + parent:params.parent, + container:params.container, + tooltip:params.tooltip, + connectorTooltip:params.connectorTooltip, + endpoint:self + }; + if (_endpoint.constructor == String) + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint](endpointArgs); + else if (_endpoint.constructor == Array) { + endpointArgs = jsPlumb.extend(_endpoint[1], endpointArgs); + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint[0]](endpointArgs); + } + else { + _endpoint = _endpoint.clone(); + } + + // assign a clone function using a copy of endpointArgs. this is used when a drag starts: the endpoint that was dragged is cloned, + // and the clone is left in its place while the original one goes off on a magical journey. + // the copy is to get around a closure problem, in which endpointArgs ends up getting shared by + // the whole world. + var argsForClone = jsPlumb.extend({}, endpointArgs); + _endpoint.clone = function() { + var o = new Object(); + _endpoint.constructor.apply(o, [argsForClone]); + return o; + }; + + self.endpoint = _endpoint; + self.type = self.endpoint.type; + // override setHover to pass it down to the underlying endpoint + var _sh = self.setHover; + self.setHover = function() { + self.endpoint.setHover.apply(self.endpoint, arguments); + _sh.apply(self, arguments); + }; + // endpoint delegates to first connection for hover, if there is one. + var internalHover = function(state) { + if (self.connections.length > 0) + self.connections[0].setHover(state, false); + else + self.setHover(state); + }; + + // bind listeners from endpoint to self, with the internal hover function defined above. + _bindListeners(self.endpoint, self, internalHover); + + this.setPaintStyle(params.paintStyle || + params.style || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyle, true); + this.setHoverPaintStyle(params.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyle, true); + this.paintStyleInUse = this.paintStyle; + this.connectorStyle = params.connectorStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.connectorTooltip = params.connectorTooltip; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + + var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + /* + * Property: canvas + * The Endpoint's Canvas. + */ + this.canvas = this.endpoint.canvas; + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + this.connections = params.connections || []; + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + self.isReattach = params.reattach || false; + self.connectionsDetachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.connectionsDetachable === false || params.detachable === false) + self.connectionsDetachable = false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget, forceDetach, fireEvent, originalEvent) { + var idx = _findWithFunction(self.connections, function(c) { return c.id == connection.id}), + actuallyDetached = false; + fireEvent = (fireEvent !== false); + if (idx >= 0) { + // 1. does the connection have a before detach (note this also checks jsPlumb's bound + // detach handlers; but then Endpoint's check will, too, hmm.) + if (forceDetach || connection._forceDetach || connection.isDetachable() || connection.isDetachAllowed(connection)) { + // get the target endpoint + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + // it would be nice to check with both endpoints that it is ok to detach. but + // for this we'll have to get a bit fancier: right now if you use the same beforeDetach + // interceptor for two endpoints (which is kind of common, because it's part of the + // endpoint definition), then it gets fired twice. so in fact we need to loop through + // each beforeDetach and see if it returns false, at which point we exit. but if it + // returns true, we have to check the next one. however we need to track which ones + // have already been run, and not run them again. + if (forceDetach || connection._forceDetach || (self.isDetachAllowed(connection) /*&& t.isDetachAllowed(connection)*/)) { + + self.connections.splice(idx, 1); + + // this avoids a circular loop + if (!ignoreTarget) { + + t.detach(connection, true, forceDetach); + // check connection to see if we want to delete the endpoints associated with it. + // we only detach those that have just this connection; this scenario is most + // likely if we got to this bit of code because it is set by the methods that + // create their own endpoints, like .connect or .makeTarget. the user is + // not likely to have interacted with those endpoints. + if (connection.endpointsToDeleteOnDetach){ + for (var i = 0; i < connection.endpointsToDeleteOnDetach.length; i++) { + var cde = connection.endpointsToDeleteOnDetach[i]; + if (cde && cde.connections.length == 0) + _currentInstance.deleteEndpoint(cde); + } + } + } + _removeElements(connection.connector.getDisplayElements(), connection.parent); + _removeWithFunction(connectionsByScope[connection.scope], function(c) { + return c.id == connection.id; + }); + actuallyDetached = true; + var doFireEvent = (!ignoreTarget && fireEvent) + fireDetachEvent(connection, doFireEvent, originalEvent); + } + } + } + return actuallyDetached; + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + * + * Parameters: + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachAll = function(fireEvent, originalEvent) { + while (self.connections.length > 0) { + self.detach(self.connections[0], false, true, fireEvent, originalEvent); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachFrom = function(targetEndpoint, fireEvent, originalEvent) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + if (self.detach(c[i], false, true, fireEvent, originalEvent)) + c[i].setHover(false, false); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findWithFunction(self.connections, function(c) { return c.id == connection.id}); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: setElement + * Sets the DOM element this Endpoint is attached to. + */ + this.setElement = function(el) { + + // TODO possibly have this object take charge of moving the UI components into the appropriate + // parent. this is used only by makeSource right now, and that function takes care of + // moving the UI bits and pieces. however it would s + var parentId = _getId(el); + // remove the endpoint from the list for the current endpoint's element + _removeWithFunction(endpointsByElement[_elementId], function(e) { + return e.id == self.id; + }); + _element = _getElementObject(el); + _elementId = _getId(_element); + self.elementId = _elementId; + // need to get the new parent now + var newParentElement = _getParentFromParams({source:parentId}), + curParent = jpcl.getParent(self.canvas); + jpcl.removeElement(self.canvas, curParent); + jpcl.appendElement(self.canvas, newParentElement); + + // now move connection(s)...i would expect there to be only one but we will iterate. + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].moveParent(newParentElement); + self.connections[i].sourceId = _elementId; + self.connections[i].source = _element; + } + _addToList(endpointsByElement, parentId, self); + //_currentInstance.repaint(parentId); + + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + return _newEndpoint( { + anchor : self.anchor, + source : _element, + paintStyle : this.paintStyle, + endpoint : _endpoint, + _transient:true, + scope:self.scope + }); + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + var candidate = self.connections[0]; + if (self.isTarget && candidate) return candidate; + else { + return (self.connections.length < _maxConnections) || _maxConnections == -1 ? null : candidate; + } + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + /* + * Function: setStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to + * setStyle and deprecate it if so. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + * + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + // a helper function that tries to find a connection to the given element, and returns it if so. if elementWithPrecedence is null, + // or no connection to it is found, we return the first connection in our list. + var findConnectionToUseForDynamicAnchor = function(elementWithPrecedence) { + var idx = 0; + if (elementWithPrecedence != null) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].sourceId == elementWithPrecedence || self.connections[i].targetId == elementWithPrecedence) { + idx = i; + break; + } + } + } + + return self.connections[idx]; + }; + + /* + * Function: paint + * Paints the Endpoint, recalculating offset and anchor positions if necessary. This does NOT paint + * any of the Endpoint's connections. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + params = params || {}; + var timestamp = params.timestamp, + recalc = !(params.recalc === false); + if (!timestamp || self.timestamp !== timestamp) { + _updateOffset({ elId:_elementId, timestamp:timestamp, recalc:recalc }); + var xy = params.offset || offsets[_elementId]; + if(xy) { + var ap = params.anchorPoint,connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (recalc && self.anchor.isDynamic && self.connections.length > 0) { + var c = findConnectionToUseForDynamicAnchor(params.elementWithPrecedence), + oIdx = c.endpoints[0] == self ? 1 : 0, + oId = oIdx == 0 ? c.sourceId : c.targetId, + oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + ap = self.anchor.compute(anchorParams); + } + + var d = _endpoint.compute(ap, self.anchor.getOrientation(_endpoint), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + _endpoint.paint(d, self.paintStyleInUse, self.anchor); + self.timestamp = timestamp; + + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) self.overlayPlacements[i] = o.draw(self.endpoint, self.paintStyleInUse, d); + } + } + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var placeholderInfo = { id:null, element:null }, + jpc = null, + existingJpc = false, + existingJpcParams = null, + _dragHandler = _makeConnectionDragHandler(placeholderInfo); + + var start = function() { + // drag might have started on an endpoint that is not actually a source, but which has + // one or more connections. + jpc = self.connectorSelector(); + var _continue = true; + // if not enabled, return + if (!self.isEnabled()) _continue = false; + // if no connection and we're not a source, return. + if (jpc == null && !params.isSource) _continue = false; + // otherwise if we're full and not allowed to drag, also return false. + if (params.isSource && self.isFull() && !dragAllowedWhenFull) _continue = false; + // if the connection was setup as not detachable or one of its endpoints + // was setup as connectionsDetachable = false, or Defaults.ConnectionsDetachable + // is set to false... + if (jpc != null && !jpc.isDetachable()) _continue = false; + + if (_continue === false) { + // this is for mootools and yui. returning false from this causes jquery to stop drag. + // the events are wrapped in both mootools and yui anyway, but i don't think returning + // false from the start callback would stop a drag. + if (jsPlumb.CurrentLibrary.stopDrag) jsPlumb.CurrentLibrary.stopDrag(); + _dragHandler.stopDrag(); + return false; + } + + // if we're not full but there was a connection, make it null. we'll create a new one. + if (jpc && !self.isFull() && params.isSource) jpc = null; + + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + _makeDraggablePlaceholder(placeholderInfo, self.parent); + + // set the offset of this div to be where 'inPlaceCopy' is, to start with. + // TODO merge this code with the code in both Anchor and FloatingAnchor, because it + // does the same stuff. + var ipcoel = _getElementObject(inPlaceCopy.canvas), + ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel), + po = adjustForParentOffsetAndScroll([ipco.left, ipco.top], inPlaceCopy.canvas); + jsPlumb.CurrentLibrary.setOffset(placeholderInfo.element, {left:po[0], top:po[1]}); + + // when using makeSource and a parent, we first draw the source anchor on the source element, then + // move it to the parent. note that this happens after drawing the placeholder for the + // first time. + if (self.parentAnchor) self.anchor = _currentInstance.makeAnchor(self.parentAnchor, self.elementId, _currentInstance); + + + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", placeholderInfo.id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + floatingEndpoint = _makeFloatingEndpoint(self.paintStyle, self.anchor, _endpoint, self.canvas, placeholderInfo.element); + + if (jpc == null) { + self.anchor.locked = true; + self.setHover(false, false); + // TODO the hover call above does not reset any target endpoint's hover + // states. + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : self.endpointWillMoveTo || _getElementObject(_element), // for makeSource with parent option. ensure source element is represented correctly. + target : placeholderInfo.element, + anchors : [ self.anchor, floatingEndpoint.anchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays + }); + + } else { + existingJpc = true; + jpc.connector.setHover(false, false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas), false, true); + // new anchor idx + var anchorIdx = jpc.endpoints[0].id == self.id ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas), + dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // now we want to get this endpoint's DROP scope, and set it for now: we can only be dropped on drop zones + // that have our drop scope (issue 57). + var dropScope = jsPlumb.CurrentLibrary.getDropScope(c); + jsPlumb.CurrentLibrary.setDragScope(c, dropScope); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = placeholderInfo.element; + jpc.sourceId = placeholderInfo.id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = placeholderInfo.element; + jpc.targetId = placeholderInfo.id; + } + + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.suspendedEndpoint.setHover(false); + jpc.endpoints[anchorIdx] = floatingEndpoint; + + // fire an event that informs that a connection is being dragged + fireConnectionDraggingEvent(jpc); + + } + // register it and register connection on it. + floatingConnections[placeholderInfo.id] = jpc; + floatingEndpoint.addConnection(jpc); + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, placeholderInfo.id, floatingEndpoint); + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var jpcl = jsPlumb.CurrentLibrary, + dragOptions = params.dragOptions || {}, + defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions), + startEvent = jpcl.dragEvents["start"], + stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"]; + + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + // extracted drag handler function so can be used by makeSource + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], _dragHandler.drag); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _currentInstance.currentlyDragging = false; + _removeWithFunction(endpointsByElement[placeholderInfo.id], function(e) { + return e.id == floatingEndpoint.id; + }); + _removeElements( [ placeholderInfo.element[0], floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + _currentInstance.anchorManager.clearFor(placeholderInfo.id); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (self.isReattach || jpc._forceDetach || !jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc)) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + _currentInstance.repaint(existingJpcParams[1]); + } + jpc._forceDetach = null; + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElements(jpc.connector.getDisplayElements(), self.parent); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint({recalc:false}); + jpc.setHover(false, false); + + fireConnectionDragStopEvent(jpc); + + jpc = null; + inPlaceCopy = null; + delete endpointsByElement[floatingEndpoint.elementId]; + floatingEndpoint.anchor = null; + floatingEndpoint = null; + _currentInstance.currentlyDragging = false; + + + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions, true); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas, forceInit, isTransient, endpoint) { + if ((params.isTarget || forceInit) && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop'], + overEvent = jsPlumb.CurrentLibrary.dragEvents['over'], + outEvent = jsPlumb.CurrentLibrary.dragEvents['out'], + drop = function() { + + var originalEvent = jsPlumb.CurrentLibrary.getDropEvent(arguments); + + var draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + elId = _getAttribute(draggable, "elId"), + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id]; + + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + + // restore the original scope if necessary (issue 57) + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + var endpointEnabled = endpoint != null ? endpoint.isEnabled() : true; + + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget) && endpointEnabled) { + + var _doContinue = true; + + // the second check here is for the case that the user is dropping it back + // where it came from. + if (jpc.suspendedEndpoint && jpc.suspendedEndpoint.id != self.id) { + if (idx == 0) { + jpc.source = jpc.suspendedEndpoint.element; + jpc.sourceId = jpc.suspendedEndpoint.elementId; + } else { + jpc.target = jpc.suspendedEndpoint.element; + jpc.targetId = jpc.suspendedEndpoint.elementId; + } + + if (!jpc.isDetachAllowed(jpc) || !jpc.endpoints[idx].isDetachAllowed(jpc) || !jpc.suspendedEndpoint.isDetachAllowed(jpc) || !_currentInstance.checkCondition("beforeDetach", jpc)) + _doContinue = false; + } + + // these have to be set before testing for beforeDrop. + if (idx == 0) { + jpc.source = self.element; + jpc.sourceId = self.elementId; + } else { + jpc.target = self.element; + jpc.targetId = self.elementId; + } + + + // now check beforeDrop. this will be available only on Endpoints that are setup to + // have a beforeDrop condition (although, secretly, under the hood all Endpoints and + // the Connection have them, because they are on jsPlumbUIComponent. shhh!), because + // it only makes sense to have it on a target endpoint. + _doContinue = _doContinue && self.isDropAllowed(jpc.sourceId, jpc.targetId, jpc.scope); + + if (_doContinue) { + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + if (!jpc.suspendedEndpoint) { + //_addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(self.element, params.draggable, {}); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + fireDetachEvent({ + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }, true, originalEvent); + } + + // finalise will inform the anchor manager and also add to + // connectionsByScope if necessary. + _finaliseConnection(jpc, null, originalEvent); + } + else { + // otherwise just put it back on the endpoint it was on before the drag. + if (jpc.suspendedEndpoint) { + // self.detachFrom(jpc); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.setHover(false); + jpc._forceDetach = true; + if (idx == 0) { + jpc.source = jpc.suspendedEndpoint.element; + jpc.sourceId = jpc.suspendedEndpoint.elementId; + } else { + jpc.target = jpc.suspendedEndpoint.element; + jpc.targetId = jpc.suspendedEndpoint.elementId;; + } + jpc.suspendedEndpoint.addConnection(jpc); + + jpc.endpoints[0].repaint(); + jpc.repaint(); + _currentInstance.repaint(jpc.source.elementId); + jpc._forceDetach = false; + } + } + + jpc.floatingAnchorIndex = null; + } + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + } + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], function() { + if (self.isTarget) { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + } + } + }); + dropOptions[outEvent] = _wrap(dropOptions[outEvent], function() { + if (self.isTarget) { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + } + } + }); + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions, true, isTransient); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas), true, !(params._transient || self.anchor.isFloating), self); + + return self; + }; + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + j.init(); + return j; + }; + jsPlumb.util = { + convertStyle : function(s, ignoreAlpha) { + // TODO: jsPlumb should support a separate 'opacity' style member. + if ("transparent" === s) return s; + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + return o; + }, + gradient : function(p1, p2) { + p1 = p1.constructor == Array ? p1 : [p1.x, p1.y]; + p2 = p2.constructor == Array ? p2 : [p2.x, p2.y]; + return (p2[1] - p1[1]) / (p2[0] - p1[0]); + }, + normal : function(p1, p2) { + return -1 / jsPlumb.util.gradient(p1,p2); + }, + segment : function(p1, p2) { + p1 = p1.constructor == Array ? p1 : [p1.x, p1.y]; + p2 = p2.constructor == Array ? p2 : [p2.x, p2.y]; + if (p2[0] > p1[0]) { + return (p2[1] > p1[1]) ? 2 : 1; + } + else { + return (p2[1] > p1[1]) ? 3 : 4; + } + }, + intersects : function(r1, r2) { + var x1 = r1.x, x2 = r1.x + r1.w, y1 = r1.y, y2 = r1.y + r1.h, + a1 = r2.x, a2 = r2.x + r2.w, b1 = r2.y, b2 = r2.y + r2.h; + + return ( (x1 < a1 && a1 < x2) && (y1 < b1 && b1 < y2) ) || + ( (x1 < a2 && a2 < x2) && (y1 < b1 && b1 < y2) ) || + ( (x1 < a1 && a1 < x2) && (y1 < b2 && b2 < y2) ) || + ( (x1 < a2 && a1 < x2) && (y1 < b2 && b2 < y2) ) || + + ( (a1 < x1 && x1 < a2) && (b1 < y1 && y1 < b2) ) || + ( (a1 < x2 && x2 < a2) && (b1 < y1 && y1 < b2) ) || + ( (a1 < x1 && x1 < a2) && (b1 < y2 && y2 < b2) ) || + ( (a1 < x2 && x1 < a2) && (b1 < y2 && y2 < b2) ); + }, + segmentMultipliers : [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ], + inverseSegmentMultipliers : [null, [-1, -1], [-1, 1], [1, 1], [1, -1] ], + pointOnLine : function(fromPoint, toPoint, distance) { + var m = jsPlumb.util.gradient(fromPoint, toPoint), + s = jsPlumb.util.segment(fromPoint, toPoint), + segmentMultiplier = distance > 0 ? jsPlumb.util.segmentMultipliers[s] : jsPlumb.util.inverseSegmentMultipliers[s], + theta = Math.atan(m), + y = Math.abs(distance * Math.sin(theta)) * segmentMultiplier[1], + x = Math.abs(distance * Math.cos(theta)) * segmentMultiplier[0]; + return { x:fromPoint.x + x, y:fromPoint.y + y }; + }, + /** + * calculates a perpendicular to the line fromPoint->toPoint, that passes through toPoint and is 'length' long. + * @param fromPoint + * @param toPoint + * @param length + */ + perpendicularLineTo : function(fromPoint, toPoint, length) { + var m = jsPlumb.util.gradient(fromPoint, toPoint), + theta2 = Math.atan(-1 / m), + y = length / 2 * Math.sin(theta2), + x = length / 2 * Math.cos(theta2); + return [{x:toPoint.x + x, y:toPoint.y + y}, {x:toPoint.x - x, y:toPoint.y - y}]; + } + }; + + var _curryAnchor = function(x, y, ox, oy, type, fnInit) { + return function(params) { + params = params || {}; + //var a = jsPlumb.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance); + var a = params.jsPlumbInstance.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance); + a.type = type; + if (fnInit) fnInit(a, params); + return a; + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1, "TopCenter"); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1, "BottomCenter"); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0, "LeftMiddle"); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0, "RightMiddle"); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0, "Center"); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1, "TopRight"); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1, "BottomRight"); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1, "TopLeft"); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1, "BottomLeft"); + + // TODO test that this does not break with the current instance idea + jsPlumb.Defaults.DynamicAnchors = function(params) { + return params.jsPlumbInstance.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"], params.elementId, params.jsPlumbInstance); + }; + jsPlumb.Anchors["AutoDefault"] = function(params) { + var a = params.jsPlumbInstance.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors(params)); + a.type = "AutoDefault"; + return a; + }; + + jsPlumb.Anchors["Assign"] = _curryAnchor(0,0,0,0,"Assign", function(anchor, params) { + // find what to use as the "position finder". the user may have supplied a String which represents + // the id of a position finder in jsPlumb.AnchorPositionFinders, or the user may have supplied the + // position finder as a function. we find out what to use and then set it on the anchor. + var pf = params.position || "Fixed"; + anchor.positionFinder = pf.constructor == String ? params.jsPlumbInstance.AnchorPositionFinders[pf] : pf; + // always set the constructor params; the position finder might need them later (the Grid one does, + // for example) + anchor.constructorParams = params; + }); + + // Continuous anchor is just curried through to the 'get' method of the continuous anchor + // factory. + jsPlumb.Anchors["Continuous"] = function(params) { + return params.jsPlumbInstance.continuousAnchorFactory.get(params); + }; + + // these are the default anchor positions finders, which are used by the makeTarget function. supply + // a position finder argument to that function allows you to specify where the resulting anchor will + // be located + jsPlumb.AnchorPositionFinders = { + "Fixed": function(dp, ep, es, params) { + return [ (dp.left - ep.left) / es[0], (dp.top - ep.top) / es[1] ]; + }, + "Grid":function(dp, ep, es, params) { + var dx = dp.left - ep.left, dy = dp.top - ep.top, + gx = es[0] / (params.grid[0]), gy = es[1] / (params.grid[1]), + mx = Math.floor(dx / gx), my = Math.floor(dy / gy); + return [ ((mx * gx) + (gx / 2)) / es[0], ((my * gy) + (gy / 2)) / es[1] ]; + } + }; +})(); diff --git a/archive/1.3.7/jsPlumb-1.3.7-tests.js b/archive/1.3.7/jsPlumb-1.3.7-tests.js new file mode 100644 index 000000000..b725e13c5 --- /dev/null +++ b/archive/1.3.7/jsPlumb-1.3.7-tests.js @@ -0,0 +1,3620 @@ +// _jsPlumb qunit tests. + +QUnit.config.reorder = false; + +var _getContextNode = function() { + return $("#container"); +}; + +var assertContextExists = function() { + ok(_getContextNode().length == 1, "context node exists"); +}; + +var assertContextSize = function(elementCount) { + //equals(_getContextNode().children().length - _divs.length, elementCount, 'context has ' + elementCount + ' children'); +}; + +var assertContextEmpty = function() { + equals(_getContextNode().children.length, 0, "context empty"); +}; + +var assertEndpointCount = function(elId, count, _jsPlumb) { + equals(_jsPlumb.getTestHarness().endpointCount(elId), count, elId + " has " + count + (count > 1) ? "endpoints" : "endpoint"); + equals(_jsPlumb.anchorManager.getEndpointsFor(elId).length, count, "anchor manager has " + count + (count > 1) ? "endpoints" : "endpoint"); +}; + +var assertConnectionCount = function(endpoint, count) { + equals(endpoint.connections.length, count, "endpoint has " + count + " connections"); +}; + +var assertConnectionByScopeCount = function(scope, count, _jsPlumb) { + equals(_jsPlumb.getTestHarness().connectionCount(scope), count, 'Scope ' + scope + " has " + count + (count > 1) ? "connections" : "connection"); + +}; + +var _divs = []; +var _addDiv = function(id, parent) { + var d1 = document.createElement("div"); + if (parent) parent.append(d1); else _getContextNode().append(d1); + d1 = jsPlumb.CurrentLibrary.getElementObject(d1); + jsPlumb.CurrentLibrary.setAttribute(d1, "id", id); + _divs.push(id); + return d1; +}; + +var defaults = null, + _cleanup = function(_jsPlumb) { + + _jsPlumb.reset(); + _jsPlumb.Defaults = defaults; + + for (var i in _divs) { + $("#" + _divs[i]).remove(); + } + _divs.splice(0, _divs.length - 1); + + $("#container").empty(); +}; + +var testSuite = function(renderMode, _jsPlumb) { + + + module("jsPlumb", { + teardown: function() { + _cleanup(_jsPlumb); + }, + setup:function() { + defaults = jsPlumb.extend({}, _jsPlumb.Defaults); + } + }); + + // setup the container + var container = document.createElement("div"); + container.id = "container"; + document.body.appendChild(container); + + var jpcl = jsPlumb.CurrentLibrary; + + _jsPlumb.setRenderMode(renderMode); + + test(renderMode + " : getElementObject", function() { + var e = document.createElement("div"); + e.id = "FOO"; + document.body.appendChild(e); + var el = jpcl.getElementObject(e); + equals(jpcl.getAttribute(el, "id"), "FOO"); + }); + + test(renderMode + " : getDOMElement", function() { + var e = document.createElement("div"); + e.id = "FOO"; + document.body.appendChild(e); + var el = jpcl.getElementObject(e); + var e2 = jpcl.getDOMElement(el); + equals(e2.id, "FOO"); + }); + + test(renderMode + ': _jsPlumb setup', function() { + ok(_jsPlumb, "loaded"); + }); + + test(renderMode + ': getId', function() { + var d10 = _addDiv('d10'); + equals(_jsPlumb.getTestHarness().getId(d10), "d10"); + }); + + test(renderMode + ': create a simple endpoint', function() { + var d1 = _addDiv("d1"); + var e = _jsPlumb.addEndpoint($("#d1"), {}); + ok(e, 'endpoint exists'); + assertEndpointCount("d1", 1, _jsPlumb); + ok(e.id != null, "endpoint has had an id assigned"); + }); + + test(renderMode + ': create and remove a simple endpoint', function() { + var d1 = _addDiv("d1"); + var ee = _jsPlumb.addEndpoint(d1, {uuid:"78978597593"}); + ok(ee != null, "endpoint exists"); + var e = _jsPlumb.getEndpoint("78978597593"); + ok(e != null, "the endpoint could be retrieved by UUID"); + ok(e.id != null, "the endpoint has had an id assigned to it"); + assertEndpointCount("d1", 1, _jsPlumb); + assertContextSize(1); // one Endpoint canvas. + _jsPlumb.removeEndpoint(d1, ee); + assertEndpointCount("d1", 0, _jsPlumb); + e = _jsPlumb.getEndpoint("78978597593"); + equals(e, null, "the endpoint has been deleted"); + assertContextSize(0); // no Endpoint canvases. + }); + + test(renderMode + ': create two simple endpoints, registered using a selector', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + d1.addClass("window");d2.addClass("window"); + var endpoints = _jsPlumb.addEndpoint($(".window"), {}); + equals(endpoints.length, 2, "endpoint added to both windows"); + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + }); + + test(renderMode + ': create two simple endpoints, registered using an array of element ids', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + d1.addClass("window");d2.addClass("window"); + var endpoints = _jsPlumb.addEndpoint(["d1", "d2"], {}); + equals(endpoints.length, 2, "endpoint added to both windows"); + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + }); + + test(renderMode + ': draggable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1"); + var e = _jsPlumb.addEndpoint(d1, {isSource:true}); + ok(e, 'endpoint exists'); + }); + + test(renderMode + ': droppable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1") + var e = _jsPlumb.addEndpoint(d1, {isTarget:true}); + ok(e, 'endpoint exists'); + }); + + test(renderMode + ': defaultEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = _jsPlumb.addEndpoint(d3, {isSource:true}); + ok(e3.anchor, 'endpoint 3 has an anchor'); + var e4 = _jsPlumb.addEndpoint(d4, {isSource:true}); + assertEndpointCount("d3", 1, _jsPlumb); + assertEndpointCount("d4", 1, _jsPlumb); + assertContextSize(2); // one canvas for each of our two endpoints. + ok(!e3.isFull(), "endpoint 3 is not full."); + _jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // we have one connection + _jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // should have refused the connection; default max is 1. + assertContextSize(3); // now we have one more canvas, for the Connection that was accepted. + }); + + test(renderMode + ': specifiedEndpointMaxConnections', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = _jsPlumb.addEndpoint(d5, {isSource:true, maxConnections:3}); + ok(e5.anchor, 'endpoint 5 has an anchor'); + var e6 = _jsPlumb.addEndpoint(d6, {isSource:true, maxConnections:2}); // this one has max TWO + assertEndpointCount("d5", 1, _jsPlumb); assertEndpointCount("d6", 1, _jsPlumb); + assertContextSize(2); + ok(!e5.isFull(), "endpoint 5 is not full."); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 1); // we have one connection + assertContextSize(3); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // two connections + assertContextSize(4); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // should have refused; max is 2, for d4. + assertContextSize(4); + }); + + test(renderMode + ': noEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = _jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + var e4 = _jsPlumb.addEndpoint(d4, {isSource:true, maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertContextSize(3); + assertConnectionCount(e3, 1); // we have one connection + _jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 2); // we have two. etc (default was one. this proves max is working). + assertContextSize(4); + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = _jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e4}); + assertConnectionCount(e4, 3); + assertContextSize(6); + }); + + test(renderMode + ': anchors equal', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + ok(a1.equals(a2), "anchors are the same"); + }); + + test(renderMode + ': anchors equal with offsets', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb); + ok(a1.equals(a2), "anchors are the same"); + }); + + test(renderMode + ': anchors not equal', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 0, 1], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + ok(!a1.equals(a2), "anchors are different"); + }); + + test(renderMode + ': anchor not equal with offsets', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + ok(!a1.equals(a2), "anchors are different"); + }); + + test(renderMode + ': detach does not fail when no arguments are provided', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + _jsPlumb.connect({source:d3, target:d4}); + _jsPlumb.detach(); + }); + + // test that detach does not fire an event by default + test(renderMode + ': _jsPlumb.detach should fire detach event by default', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach(conn); + equals(eventCount, 1); + }); + + // test that detach does not fire an event by default + test(renderMode + ': _jsPlumb.detach should fire detach event by default, using params object', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({connection:conn}); + equals(eventCount, 1); + }); + + // test that detach fires an event when instructed to do so + test(renderMode + ': _jsPlumb.detach should not fire detach event when instructed to not do so', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach(conn, {fireEvent:false}); + equals(eventCount, 0); + }); + + // issue 81 + test(renderMode + ': _jsPlumb.detach should fire only one detach event (pass Connection as argument)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach(conn); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': _jsPlumb.detach should fire only one detach event (pass Connection as param in argument)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({connection:conn}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': detach should fire only one detach event (pass source and targets as strings as arguments in params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({source:"d5", target:"d6"}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': detach should fire only one detach event (pass source and targets as divs as arguments in params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({source:d5, target:d6, fireEvent:true}); + equals(eventCount, 1); + }); + + //TODO make sure you run this test with a single detach call, to ensure that + // single detach calls result in the connection being removed. detachEveryConnection can + // just blow away the connectionsByScope array and recreate it. + test(renderMode + ': getConnections (simple case, default scope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + _jsPlumb.connect({source:d5, target:d6}); + assertContextSize(3); + var c = _jsPlumb.getConnections(); // will get all connections in the default scope. + equals(c.length, 1, "there is one connection"); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by element id using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + _jsPlumb.connect({source:d5, target:d6}); + _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach({source:'d5', target:'d6'}); + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by id using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + _jsPlumb.connect({source:d5, target:d6}); + _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach({source:"d5", target:"d6"}); + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by element object using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + _jsPlumb.connect({source:d5, target:d6}); + _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach({source:d5, target:d6}); + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by Connection)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + var c56 = _jsPlumb.connect({source:d5, target:d6}); + var c67 = _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach(c56); + assertContextSize(5); // check that the connection canvas was removed. + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + }); + +// beforeDetach functionality + + test(renderMode + ": detach; beforeDetach on connect call returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { return false; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": detach; beforeDetach on connect call returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { return true; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": detach; beforeDetach on connect call throws an exception; we treat it with the contempt it deserves and pretend it said the detach was ok.", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { throw "i am an example of badly coded beforeDetach, but i don't break jsPlumb "; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": detach; beforeDetach on addEndpoint call to source Endpoint returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return false; } }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": detach; beforeDetach on addEndpoint call to source Endpoint returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return true; } }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + + test(renderMode + ": Endpoint.detach; beforeDetach on addEndpoint call to source Endpoint returns false; Endpoint.detach returns false too (the UI needs it to)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return false; } }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + var success = e1.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + ok(!success, "Endpoint reported detach did not execute"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns false but detach is forced", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c, {forceDetach:true}); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was denied but forced anyway"); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was denied but forced anyway"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return true; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach bound to _jsPlumb returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + _jsPlumb.bind("beforeDetach", function(connection) { + ok(connection.sourceId === "d1", "connection is provided and configured with correct source"); + ok(connection.targetId === "d2", "connection is provided and configured with correct target"); + return false; + }); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach bound to _jsPlumb returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + _jsPlumb.bind("beforeDetach", function(connection) { + ok(connection.sourceId === "d1", "connection is provided and configured with correct source"); + ok(connection.targetId === "d2", "connection is provided and configured with correct target"); + return true; + }); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was denied"); + }); + + test(renderMode + ": _jsPlumb.detachAllConnections ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detachAllConnections(d1); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachAll was called"); + }); + + test(renderMode + ": _jsPlumb.detachEveryConnection ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detachEveryConnection(); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachEveryConnection was called"); + }); + + test(renderMode + ": Endpoint.detachAll ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + e1.detachAll(); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachAllConnections was called"); + }); + +// ******** end of beforeDetach tests ************** + +// detachEveryConnection/detachAllConnections fireEvent overrides tests + + test(renderMode + ": _jsPlumb.detachEveryConnection fires events", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0; + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachEveryConnection(); + equals(connCount, 0, "no connections registered"); + }); + + test(renderMode + ": _jsPlumb.detachEveryConnection doesn't fire events when instructed not to", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0; + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachEveryConnection({fireEvent:false}); + equals(connCount, 2, "two connections registered"); + }); + + test(renderMode + ": _jsPlumb.detachAllConnections fires events", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0, + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachAllConnections("d1"); + equals(connCount, 0, "no connections registered"); + }); + + test(renderMode + ": _jsPlumb.detachAllConnections doesn't fire events when instructed not to", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0, + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachAllConnections("d1", {fireEvent:false}); + equals(connCount, 2, "two connections registered"); + }); + + test(renderMode + ': getConnections (scope testScope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + _jsPlumb.connect({source:d5, target:d6, scope:'testScope'}); + var c = _jsPlumb.getConnections("testScope"); // will get all connections in testScope + equals(c.length, 1, "there is one connection"); + equals(c[0].sourceId, 'd5', "the connection's source is d5"); + equals(c[0].targetId, 'd6', "the connection's source is d6"); + c = _jsPlumb.getConnections(); // will get all connections in default scope; should be none. + equals(c.length, 0, "there are no connections in the default scope"); + }); + + test(renderMode + ': _jsPlumb.getAllConnections (filtered by scope)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + _jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + _jsPlumb.connect({source:d9, target:d10}); // default scope + var c = _jsPlumb.getAllConnections(); // will get all connections + equals(c[_jsPlumb.getDefaultScope()].length, 1); + equals(c['testScope'].length, 1); + // now supply a list of scopes + c = _jsPlumb.getConnections(); + equals(c.length, 1); + c = _jsPlumb.getConnections("testScope"); + equals(c.length, 1, "there is one connection in 'testScope'"); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by scope and sourceId)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + _jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + _jsPlumb.connect({source:d9, target:d8, scope:'testScope'}); + _jsPlumb.connect({source:d9, target:d10}); // default scope + var c = _jsPlumb.getConnections({scope:'testScope', source:'d8'}); // will get all connections with sourceId 'd8' + equals(c.length, 1, "there is one connection in 'testScope' from d8"); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by scope, source id and target id)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope'}); + var c = _jsPlumb.getConnections({scope:'testScope', source:'d11', target:'d13'}); + equals(c.length, 1, "there is one connection from d11 to d13"); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = _jsPlumb.getConnections({scope:['testScope','testScope3']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes and source ids)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = _jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes, source ids and target ids)', function() { + _jsPlumb.detachEveryConnection(); + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'), d14=_addDiv("d14"), d15=_addDiv("d15"); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d11, target:d14, scope:'testScope'}); + _jsPlumb.connect({source:d11, target:d15, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + assertContextSize(18); + var c = _jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11'], target:['d14', 'd15']}); + equals(c['testScope'].length, 2, 'there are two connections in testScope'); + equals(c['testScope3'], null, 'there are no connections in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + var anEntry = c['testScope'][0]; + ok(anEntry.endpoints[0] != null, "Source endpoint is set"); + ok(anEntry.endpoints[1] != null, "Target endpoint is set"); + equals(anEntry.source.attr("id"), "d11", "Source is div d11"); + equals(anEntry.target.attr("id"), "d14", "Target is div d14"); + }); + + test(renderMode + ': getEndpoints, one Endpoint added by addEndpoint, get Endpoints by selector', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + _jsPlumb.addEndpoint(d1); + var e = _jsPlumb.getEndpoints(d1); + equals(e.length, 1, "there is one endpoint for element d1"); + }); + + test(renderMode + ': getEndpoints, one Endpoint added by addEndpoint, get Endpoints by id', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + _jsPlumb.addEndpoint(d1); + var e = _jsPlumb.getEndpoints("d1"); + equals(e.length, 1, "there is one endpoint for element d1"); + }); + + test(renderMode + ': connection event listeners', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null/*, returnedParams2 = null*/; + _jsPlumb.bind("jsPlumbConnection", function(params) { + returnedParams = $.extend({}, params); + }); + var c = _jsPlumb.connect({source:d1, target:d2}); + ok(returnedParams != null, "new connection listener event was fired"); + ok(returnedParams.connection != null, 'connection is set'); + equals(returnedParams.sourceId, "d1", 'sourceid is set'); + equals(returnedParams.targetId, "d2", 'targetid is set'); + equals(returnedParams.source.attr("id"), "d1", 'source is set'); + equals(returnedParams.target.attr("id"), "d2" , 'target is set'); + ok(returnedParams.sourceEndpoint != null, "source endpoint is not null"); + ok(returnedParams.targetEndpoint != null, "target endpoint is not null"); + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.detach(c); + ok(returnedParams.connection != null, 'connection is set'); + //_jsPlumb.detachAll("d1"); + //ok(returnedParams2 != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (detach by connection)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + ok(returnedParams.connection != null, "removed connection listener event passed in connection"); + }); + + test(renderMode + ': detach event listeners (detach by element ids)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach({source:"d1",target:"d2"}); + ok(returnedParams != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (detach by elements)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach({source:d1,target:d2}); + ok(returnedParams != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detach method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detachFrom method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachFrom(e2); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detachAll method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachAll(); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via _jsPlumb.deleteEndpoint method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + _jsPlumb.deleteEndpoint(e1); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(1); + }); + + test(renderMode + ': detach event listeners (ensure cleared by _jsPlumb.reset)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach({source:d1,target:d2, fireEvent:true}); + ok(returnedParams != null, "removed connection listener event was fired"); + returnedParams = null; + + _jsPlumb.reset(); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach(d1,d2); + ok(returnedParams == null, "connection listener was cleared by _jsPlumb.reset()"); + }); + + test(renderMode + ': connection events that throw errors', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null, returnedParams2 = null; + _jsPlumb.bind("jsPlumbConnection", function(params) { + returnedParams = $.extend({}, params); + throw "oh no!"; + }); + _jsPlumb.connect({source:d1, target:d2}); + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + _jsPlumb.connect({source:d3, target:d4}); + ok(returnedParams != null, "new connection listener event was fired; we threw an error, _jsPlumb survived."); + }); + + test(renderMode + ": Endpoint.detachFrom", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + e16.detachFrom(e17); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by _jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": Endpoint.detachFromConnection", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + e16.detachFromConnection(conn); + assertContextSize(3); // all canvases should remain; the connection was not removed. + // but endpoint e16 should have no connections now. + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": Endpoint.detachAll", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + var e16 = _jsPlumb.addEndpoint($("#d16"), {isSource:true,maxConnections:-1}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint($("#d17"), {isSource:true}); + var e18 = _jsPlumb.addEndpoint($("#d18"), {isSource:true}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e18}); + assertConnectionCount(e16, 2); + assertConnectionCount(e17, 1); + assertConnectionCount(e18, 1); + assertContextSize(5); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 2, _jsPlumb); + e16.detachAll(); + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": Endpoint.detach", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + e16.detach(conn); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by _jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": Endpoint.isConnectedTo", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.isConnectedTo(e17), true, "e16 and e17 are connected"); + }); + + test(renderMode + ": setting endpoint uuid", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + equals(e16.getUuid(), uuid, "endpoint's uuid was set correctly"); + }); + + test(renderMode + ": _jsPlumb.getEndpoint (by uuid)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by uuid, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + _jsPlumb.deleteEndpoint(uuid); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + var ebe = _jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by uuid, connections too)", function() { + // create two endpoints (one with a uuid), add them to two divs and then connect them. + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + // check that the connection was ok. + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + // delete the endpoint that has a uuid. verify that the endpoint cannot be retrieved and that the connection has been removed, but that + // element d17 still has its Endpoint. + _jsPlumb.deleteEndpoint(uuid); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + var ebe = _jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + ebe = _jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 1, "element d17 still has its Endpoint"); + assertContextSize(1); + + // now delete d17's endpoint and check that it has gone. + _jsPlumb.deleteEndpoint(e17); + f = _jsPlumb.getEndpoint(e17); + equals(f, null, "endpoint has been deleted"); + ebe = _jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 0, "element d17 no longer has any Endpoints"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by reference, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + _jsPlumb.deleteEndpoint(e16); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by reference, connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + _jsPlumb.deleteEndpoint(e16); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + assertContextSize(1); + }); + + test(renderMode + ": _jsPlumb.deleteEveryEndpoint", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + assertContextSize(2); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + _jsPlumb.deleteEveryEndpoint(); + + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = _jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEveryEndpoint (connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + _jsPlumb.deleteEveryEndpoint(); + + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = _jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); // no canvases. so all connection canvases have been cleaned up too. + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchor:[0,0.5,0,-1]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:"TopCenter"}); + assertContextSize(2); + equals(e16.anchor.x, 0); + equals(e16.anchor.y, 0.5); + equals(e17.anchor.x, 0.5); + equals(e17.anchor.y, 0); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (tooltip)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {tooltip:"FOO"}), + e17 = _jsPlumb.addEndpoint(d17, {tooltip:"BAZ"}); + assertContextSize(2); + equals(e16.canvas.getAttribute("title"), "FOO"); + equals(e17.canvas.getAttribute("title"), "BAZ"); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case, dynamic anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + assertContextSize(2); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case, two arg method)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchor:[0,0.5,0,-1]}); + var e17 = _jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchor:"TopCenter"}); + assertContextSize(2); + equals(e16.anchor.x, 0); + equals(e16.anchor.y, 0.5); + equals(false, e16.isTarget); + equals(true, e16.isSource); + equals(e17.anchor.x, 0.5); + equals(e17.anchor.y, 0); + equals(true, e17.isTarget); + equals(false, e17.isSource); + }); + + + test(renderMode + ": _jsPlumb.addEndpoints (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false, anchor:[0,0.5,0,-1] }, { isTarget:true, isSource:false, anchor:"TopCenter" }]); + assertContextSize(2); + equals(e16[0].anchor.x, 0); + equals(e16[0].anchor.y, 0.5); + equals(false, e16[0].isTarget); + equals(true, e16[0].isSource); + equals(e16[1].anchor.x, 0.5); + equals(e16[1].anchor.y, 0); + equals(true, e16[1].isTarget); + equals(false, e16[1].isSource); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (with reference params)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var refParams = {anchor:"RightMiddle"}; + var e16 = _jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false}, { isTarget:true, isSource:false }], refParams); + assertContextSize(2); + equals(e16[0].anchor.x, 1); + equals(e16[0].anchor.y, 0.5); + equals(false, e16[0].isTarget); + equals(true, e16[0].isSource); + equals(e16[1].anchor.x, 1); + equals(e16[1].anchor.y, 0.5); + equals(true, e16[1].isTarget); + equals(false, e16[1].isSource); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case, dynamic anchors, two arg method)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchors:["TopCenter", "BottomCenter"]}); + assertContextSize(2); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (default overlays)", function() { + _jsPlumb.Defaults.Overlays = [ + [ "Label", { id:"label" } ] + ]; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16), + e2 = _jsPlumb.addEndpoint(d17); + + ok(e1.getOverlay("label") != null, "endpoint 1 has overlay from defaults"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (default overlays)", function() { + _jsPlumb.Defaults.Overlays = [ + [ "Label", { id:"label" } ] + ]; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16, { + overlays:[ + ["Label", { id:"label2", location:[ 0.5, 1 ] } ] + ] + }), + e2 = _jsPlumb.addEndpoint(d17); + + ok(e1.getOverlay("label") != null, "endpoint 1 has overlay from defaults"); + ok(e1.getOverlay("label2") != null, "endpoint 1 has overlay from addEndpoint call"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (end point set label)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16), + e2 = _jsPlumb.addEndpoint(d17); + + e1.setLabel("FOO"); + equals(e1.getLabel(), "FOO", "endpoint's label is correct"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (end point set label in constructor, as string)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16, {label:"FOO"}), + e2 = _jsPlumb.addEndpoint(d17); + + equals(e1.getLabel(), "FOO", "endpoint's label is correct"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (end point set label in constructor, as function)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16, {label:function() { return "BAZ"; }, labelLocation:0.1}), + e2 = _jsPlumb.addEndpoint(d17); + + equals(e1.getLabel()(), "BAZ", "endpoint's label is correct"); + equals(e1.getLabelOverlay().getLocation(), 0.1, "endpoint's label's location is correct"); + }); + + test(renderMode + ": _jsPlumb.makeTarget (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.makeTarget (specify two divs in an array)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.makeTarget([d16, d17], { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.makeTarget (specify two divs by id in an array)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.makeTarget(["d16", "d17"], { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.makeTarget (specify divs by selector)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + d16.addClass("FOO");d17.addClass("FOO"); + _jsPlumb.makeTarget($(".FOO"), { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e.length, 1, "d17 has one endpoint"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case, two connect calls)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17"}); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 2, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case, two connect calls, uniqueEndpoint set)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true, anchor:"LeftMiddle", uniqueEndpoint:true, maxConnections:-1 }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17"}); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].connections.length, 2, "endpoint on d17 has two connections"); + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (newConnection:true specified)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17", newConnection:true}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0.5, "anchor is BottomCenter"); //here we should be seeing the default anchor + equals(e[0].anchor.y, 1, "anchor is BottomCenter"); //here we should be seeing the default anchor + }); + +// jsPlumb.connect, after makeSource has been called on some element + test(renderMode + ": _jsPlumb.connect after makeSource (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeSource (simple case, two connect calls)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 2, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case, two connect calls, uniqueEndpoint set)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true, anchor:"LeftMiddle", uniqueEndpoint:true, maxConnections:-1 }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].connections.length, 2, "endpoint on d17 has two connections"); + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (newConnection:true specified)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16, newConnection:true}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0.5, "anchor is BottomCenter"); //here we should be seeing the default anchor + equals(e[0].anchor.y, 1, "anchor is BottomCenter"); //here we should be seeing the default anchor + }); + + test(renderMode + ": _jsPlumb.connect after makeSource on child; wth parent set (parent should be recognised)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18", d17); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d18, { isSource:true,anchor:"LeftMiddle", parent:d17 }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + assertEndpointCount("d18", 0, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + +// *********************************************** + + test(renderMode + ': _jsPlumb.connect (between two Endpoints)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1, _jsPlumb); // no new endpoint should have been added + assertEndpointCount("d2", 1, _jsPlumb); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. + ok(c.id != null, "connection has had an id assigned"); + }); + + test(renderMode + ': _jsPlumb.connect (Endpoint connectorTooltip parameter)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1, {connectorTooltip:"FOO"}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + equals(c.connector.canvas.getAttribute("title"), "FOO", "connector canvas has label attribute set"); + }); + + test(renderMode + ': _jsPlumb.connect (tooltip parameter)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2, tooltip:"FOO"}); + equals(c.connector.canvas.getAttribute("title"), "FOO", "connector canvas has label attribute set"); + }); + + test(renderMode + ': _jsPlumb.connect (between two Endpoints, and dont supply any parameters to the Endpoints.)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1, _jsPlumb); // no new endpoint should have been added + assertEndpointCount("d2", 1, _jsPlumb); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. + }); + + test(renderMode + " : _jsPlumb.connect, passing 'anchors' array" ,function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, anchors:["LeftMiddle", "RightMiddle"]}); + + equals(c.endpoints[0].anchor.x, 0, "source anchor is at x=0"); + equals(c.endpoints[0].anchor.y, 0.5, "source anchor is at y=0.5"); + equals(c.endpoints[1].anchor.x, 1, "target anchor is at x=1"); + equals(c.endpoints[1].anchor.y, 0.5, "target anchor is at y=0.5"); + }); + + test(renderMode + ': _jsPlumb.connect (by endpoint)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + }); + + test(renderMode + ': _jsPlumb.connect (cost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17, cost:567}); + assertContextSize(3); + equals(c.getCost(), 567, "connection cost is 567"); + }); + + test(renderMode + ': _jsPlumb.connect (default cost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), undefined, "default connection cost is 1"); + }); + + test(renderMode + ': _jsPlumb.connect (set cost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), undefined, "default connection cost is 1"); + c.setCost(8989); + equals(c.getCost(), 8989, "connection cost is 8989"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (connectionCost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionCost:567}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), 567, "connection cost is 567"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (change connectionCost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionCost:567, maxConnections:-1}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1}); + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), 567, "connection cost is 567"); + e16.setConnectionCost(23); + var c2 = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(c2.getCost(), 23, "connection cost is 23 after change on endpoint"); + }); + + test(renderMode + ': _jsPlumb.connect (default bidirectional)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true}), + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.isBidirectional(), true, "default connection is bidirectional"); + }); + + test(renderMode + ': _jsPlumb.connect (bidirectional false)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true}), + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17, bidirectional:false}); + assertContextSize(3); + equals(c.isBidirectional(), false, "connection is not bidirectional"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (connectionsBidirectional)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionsBidirectional:true, maxConnections:-1}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.isBidirectional(), true, "connection is bidrectional"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (change connectionsBidirectional)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionsBidirectional:true, maxConnections:-1}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1}); + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.isBidirectional(), true, "connection is bidirectional"); + e16.setConnectionsBidirectional(false); + var c2 = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(c2.isBidirectional(), false, "connection is not bidirectional"); + }); + + test(renderMode + ": _jsPlumb.connect (two Endpoints - that have been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = _jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1, uuid:srcEndpointUuid}); + var e2 = _jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1, uuid:dstEndpointUuid}); + _jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ] }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertContextSize(3); + }); + + test(renderMode + ": _jsPlumb.connect (two Endpoints - that have not been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ], source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + var e1 = _jsPlumb.getEndpoint(srcEndpointUuid); + ok(e1 != null, "endpoint with src uuid added"); + ok(e1.canvas != null); + var e2 = _jsPlumb.getEndpoint(dstEndpointUuid); + ok(e2 != null, "endpoint with target uuid added"); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.connect (two Endpoints - that have been already added - by endpoint reference)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = _jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1}); + var e2 = _jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1}); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.connect (two elements)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.connect({ source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, straight)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, bezier, no params)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Bezier" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 150, "Bezier connector chose 150 curviness"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, bezier, curviness as int)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", { curviness:200 }] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Canvas Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 200, "Bezier connector chose 200 curviness"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, bezier, curviness as named option)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", {curviness:300}] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 300, "Bezier connector chose 300 curviness"); + }); + + test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Canvas Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as strings)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:["LeftMiddle", "RightMiddle"] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.5, conn.endpoints[0].anchor.y, "source anchor y"); + equals(1, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.5, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + + test(renderMode + ": _jsPlumb.connect (two argument method in which some data is reused across connections)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"), d19 = _addDiv("d19"); + var sharedData = { connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }; + var conn = _jsPlumb.connect({ source:d16, target:d17}, sharedData); + var conn2 = _jsPlumb.connect({ source:d18, target:d19}, sharedData); + assertContextSize(6); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 2, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(conn2.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + equals(0.3, conn2.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn2.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn2.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn2.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoint test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoints test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Dot, "Dot endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Blank Endpoint specified via 'endpoint' param)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Blank" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Blank Endpoint specified via 'endpoints' param)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Blank", "Blank" ] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Dot, "Dot endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, connector test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {}); + var e17 = _jsPlumb.addEndpoint(d17, {}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {}); + var e17 = _jsPlumb.addEndpoint(d17, {}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, anchors as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = "TopCenter", a17 = "BottomCenter"; + var e16 = _jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, 0.5, "endpoint 16 is at top center");equals(e16.anchor.y, 0, "endpoint 16 is at top center"); + equals(e17.anchor.x, 0.5, "endpoint 17 is at bottom center");equals(e17.anchor.y, 1, "endpoint 17 is at bottom center"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = [0,0.5,0,-1], a17 = [1,0.0,-1,-1]; + var e16 = _jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, a16[0]);equals(e16.anchor.y, a16[1]); + equals(e17.anchor.x, a17[0]);equals(e17.anchor.y, a17[1]); + equals(e16.anchor.getOrientation()[0], a16[2]); equals(e16.anchor.getOrientation()[1], a16[3]); + equals(e17.anchor.getOrientation()[0], a17[2]); equals(e17.anchor.getOrientation()[1], a17[3]); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchor')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchor:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:["TopCenter", "BottomCenter"]}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchors')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, default endpoint, supplied dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var anchors = [ [0.25, 0, 0, -1], [1, 0.25, 1, 0], [0.75, 1, 0, 1], [0, 0.75, -1, 0] ]; + _jsPlumb.connect({source:d1, target:d2, dynamicAnchors:anchors}); // auto connect with default endpoint and provided anchors + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.detach({source:d1, target:d2}); + // this changed in 1.3.5, because auto generated endpoints are now removed by detach. so i added the test below this one + // to check that the deleteEndpointsOnDetach flag is honoured. + assertEndpointCount("d1", 0, _jsPlumb);assertEndpointCount("d2", 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, default endpoint, supplied dynamic anchors, delete on detach false)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var anchors = [ [0.25, 0, 0, -1], [1, 0.25, 1, 0], [0.75, 1, 0, 1], [0, 0.75, -1, 0] ]; + _jsPlumb.connect({ + source:d1, + target:d2, + dynamicAnchors:anchors, + deleteEndpointsOnDetach:false + }); // auto connect with default endpoint and provided anchors + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.detach({source:d1, target:d2}); + // this changed in 1.3.5, because auto generated endpoints are now removed by detach. so i added this test + // to check that the deleteEndpointsOnDetach flag is honoured. + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, supplied endpoint and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = _jsPlumb.addEndpoint(d1, endpoint); + var e2 = _jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(3); + _jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, supplied endpoints using 'source' and 'target' (this test is identical to the one above apart from the param names), and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = _jsPlumb.addEndpoint(d1, endpoint); + var e2 = _jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + _jsPlumb.connect({source:e1, target:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(3); + _jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + " detachable parameter defaults to true on _jsPlumb.connect", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), true, "connections detachable by default"); + }); + + test(renderMode + " detachable parameter set to false on _jsPlumb.connect", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, detachable:false}); + equals(c.isDetachable(), false, "connection detachable"); + }); + + test(renderMode + " setDetachable on initially detachable connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), true, "connection initially detachable"); + c.setDetachable(false); + equals(c.isDetachable(), false, "connection not detachable"); + }); + + test(renderMode + " setDetachable on initially not detachable connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, detachable:false }); + equals(c.isDetachable(), false, "connection not initially detachable"); + c.setDetachable(true); + equals(c.isDetachable(), true, "connection now detachable"); + }); + + test(renderMode + " _jsPlumb.Defaults.ConnectionsDetachable", function() { + _jsPlumb.Defaults.ConnectionsDetachable = false; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), false, "connections not detachable by default (overrode the defaults)"); + _jsPlumb.Defaults.ConnectionsDetachable = true; + }); + + + test(renderMode + ": _jsPlumb.connect (testing for connection event callback)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var connectCallback = null, detachCallback = null; + _jsPlumb.bind("jsPlumbConnection", function(params) { + connectCallback = $.extend({}, params); + }); + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + detachCallback = $.extend({}, params); + }); + _jsPlumb.connect({source:d1, target:d2}); // auto connect with default endpoint and anchor set + ok(connectCallback != null, "connect callback was made"); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.detach({source:d1, target:d2}); + assertContextSize(2); + ok(detachCallback != null, "detach callback was made"); + }); + + test(renderMode + ": _jsPlumb.connect (setting cssClass on Connector)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2,cssClass:"CSS"}); + var has = function(clazz) { + var cn = c.connector.canvas.className, + cns = cn.constructor == String ? cn : cn.baseVal; + + return cns.indexOf(clazz) != -1; + }; + ok(has("CSS"), "custom cssClass set correctly"); + ok(has(_jsPlumb.connectorClass), "basic connector class set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (overlays, long-hand version)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = {width:40,length:40,location:0.7, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"}}; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + }); + + test(renderMode + ": _jsPlumb.connect (overlays, long-hand version, IDs specified)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + equals("aLabel", connection1.overlays[0].id); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + equals("anArrow", connection1.overlays[1].id); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays)", function() { + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays + overlays specified in connect call)", function() { + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, overlays:[ + ["Label", {id:"label"}] + ]}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("label") != null, "Label overlay created from connect call"); + }); + + test(renderMode + ": _jsPlumb.connect (default connection overlays)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + }); + + test(renderMode + ": _jsPlumb.connect (default connection overlays + overlays specified in connect call)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, overlays:[ + ["Label", {id:"label"}] + ]}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("label") != null, "Label overlay created from connect call"); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays + default connection overlays)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow2" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("arrow2") != null, "Arrow overlay created from connection defaults"); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays + default connection overlays)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow2" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, overlays:[ + ["Label", {id:"label"}] + ]}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("arrow2") != null, "Arrow overlay created from connection defaults"); + ok(c.getOverlay("label") != null, "Label overlay created from connect call"); + }); + + test(renderMode + ": _jsPlumb.connect (label overlay set using 'label')", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO" + }); + + equals(c.getLabel(), "FOO", "label is set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction, with string)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2}); + + c.setLabel("FOO"); + equals(c.getLabel(), "FOO", "label is set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction, with function)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2}); + + c.setLabel(function() { return "BAR"; }); + equals(c.getLabel()(), "BAR", "label is set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction, with params object)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2}); + + c.setLabel({ + label:"BAZ", + cssClass:"CLASSY", + location:0.9 + }); + var lo = c.getLabelOverlay(); + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "BAZ", "label overlay has correct value"); + equals(lo.getLocation(), 0.9, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction with existing label set, with params object)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO", + labelLocation:0.2 + }); + + var lo = c.getLabelOverlay(); + equals(lo.getLocation(), 0.2, "label overlay has correct location"); + + c.setLabel({ + label:"BAZ", + cssClass:"CLASSY", + location:0.9 + }); + + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "BAZ", "label overlay has correct value"); + equals(lo.getLocation(), 0.9, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (getLabelOverlay, label on connect call)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO" + }), + lo = c.getLabelOverlay(); + + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "FOO", "label overlay has correct value"); + equals(lo.getLocation(), 0.5, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (getLabelOverlay, label on connect call, location set)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO", + labelLocation:0.2 + }), + lo = c.getLabelOverlay(); + + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "FOO", "label overlay has correct value"); + equals(lo.getLocation(), 0.2, "label overlay has correct location"); + }); + + + test(renderMode + ": _jsPlumb.connect (remove single overlay by id)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + connection1.removeOverlay("aLabel"); + equals(1, connection1.overlays.length); + equals("anArrow", connection1.overlays[0].id); + }); + + test(renderMode + ": _jsPlumb.connect (remove multiple overlays by id)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + connection1.removeOverlays("aLabel", "anArrow"); + equals(0, connection1.overlays.length); + }); + + test(renderMode + ": _jsPlumb.connect (overlays, short-hand version)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var loc = { location:0.7 }; + var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label", {label:"CONNECTION 1", location:0.3}], + ["Arrow", arrowSpec, loc] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + }); + + test(renderMode + ": _jsPlumb.connect (removeAllOverlays)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var loc = { location:0.7 }; + var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label", {label:"CONNECTION 1", location:0.3}], + ["Arrow", arrowSpec, loc] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + + connection1.removeAllOverlays(); + equals(0, connection1.overlays.length); + }); + + test(renderMode + ": _jsPlumb.connect, specify arrow overlay using string identifier only", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:["Arrow"]}); + equals(jsPlumb.Overlays[renderMode].Arrow, conn.overlays[0].constructor); + }); + + test(renderMode + ": Connection.getOverlay method, existing overlay", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("arrowOverlay"); + ok(overlay != null); + }); + + test(renderMode + ": Connection.getOverlay method, non-existent overlay", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("IDONTEXIST"); + ok(overlay == null); + }); + + test(renderMode + ": Overlay.setVisible method", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("arrowOverlay"); + ok(overlay.isVisible()); + overlay.setVisible(false); + ok(!overlay.isVisible()); + overlay.setVisible(true); + ok(overlay.isVisible()); + }); + + // this test is for the original detach function; it should stay working after i mess with it + // a little. + test(renderMode + ": _jsPlumb.detach (by element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + var e3 = _jsPlumb.addEndpoint(d1); + var e4 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + _jsPlumb.connect({ sourceEndpoint:e3, targetEndpoint:e4 }); // make two connections to be sure this works ;) + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionCount(e3, 1); + assertConnectionCount(e4, 1); + + _jsPlumb.detach({source:"d1", target:"d2"}); + + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionCount(e3, 0); + assertConnectionCount(e4, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + // detach is being made to operate more like connect - by taking one argument with a whole + // bunch of possible params in it. if two args are passed in it will continue working + // in the old way. + test(renderMode + ": _jsPlumb.detach (params object, using element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({source:"d1", target:"d2"}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); +/* + test(renderMode + ": _jsPlumb.detach (params object, using target only)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e1 = _jsPlumb.addEndpoint(d1, {maxConnections:2}), + e2 = _jsPlumb.addEndpoint(d2), + e3 = _jsPlumb.addEndpoint(d3); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e3 }); + assertConnectionCount(e1, 2); + assertConnectionCount(e2, 1); + assertConnectionCount(e3, 1); + _jsPlumb.detach({target:"d2"}); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 0); + assertConnectionCount(e3, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1); + }); +*/ + test(renderMode + ": _jsPlumb.detach (params object, using element objects)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({source:d1, target:d2}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.detach (source and target as endpoint UUIDs)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {uuid:"abcdefg"}); + ok(_jsPlumb.getEndpoint("abcdefg") != null, "e1 exists"); + var e2 = _jsPlumb.addEndpoint(d2, {uuid:"hijklmn"}); + ok(_jsPlumb.getEndpoint("hijklmn") != null, "e2 exists"); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({uuids:["abcdefg", "hijklmn"]}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.detach (sourceEndpoint and targetEndpoint supplied)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.makeDynamicAnchors (longhand)", function() { + var anchors = [_jsPlumb.makeAnchor([0.2, 0, 0, -1], null, _jsPlumb), _jsPlumb.makeAnchor([1, 0.2, 1, 0], null, _jsPlumb), + _jsPlumb.makeAnchor([0.8, 1, 0, 1], null, _jsPlumb), _jsPlumb.makeAnchor([0, 0.8, -1, 0], null, _jsPlumb) ]; + var dynamicAnchor = _jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); + }); + + test(renderMode + ": _jsPlumb.makeDynamicAnchors (shorthand)", function() { + var anchors = [[0.2, 0, 0, -1], [1, 0.2, 1, 0], + [0.8, 1, 0, 1], [0, 0.8, -1, 0] ]; + var dynamicAnchor = _jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); + }); + + test(renderMode + ": Connection.isVisible/setVisible", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c1 = _jsPlumb.connect({source:d1,target:d2}); + equals(true, c1.isVisible(), "Connection is visible after creation."); + c1.setVisible(false); + equals(false, c1.isVisible(), "Connection is not visible after calling setVisible(false)."); + equals($(c1.connector.canvas).css("display"), "none"); + c1.setVisible(true); + equals(true, c1.isVisible(), "Connection is visible after calling setVisible(true)."); + equals($(c1.connector.canvas).css("display"), "block"); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible basic test (no connections)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals($(e1.canvas).css("display"), "none"); + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals($(e1.canvas).css("display"), "block"); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should track changes in the source, because it has only this connection.)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + var c1 = _jsPlumb.connect({source:e1, target:e2}); + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals(false, e2.isVisible(), "other Endpoint is not visible either."); + equals(false, c1.isVisible(), "connection between the two is not visible either."); + + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals(true, e2.isVisible(), "other Endpoint is visible too"); + equals(true, c1.isVisible(), "connection between the two is visible too."); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should not track changes in the source, because it has another connection.)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2, { maxConnections:2 }), e3 = _jsPlumb.addEndpoint(d3); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + var c1 = _jsPlumb.connect({source:e1, target:e2}); + var c2 = _jsPlumb.connect({source:e2, target:e3}); + + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals(true, e2.isVisible(), "other Endpoint should still be visible."); + equals(true, e3.isVisible(), "third Endpoint should still be visible."); + equals(false, c1.isVisible(), "connection between the two is not visible either."); + equals(true, c2.isVisible(), "other connection is visible."); + + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals(true, e2.isVisible(), "other Endpoint is visible too"); + equals(true, c1.isVisible(), "connection between the two is visible too."); + equals(true, c2.isVisible(), "other connection is visible."); + }); + + // tests of the functionality that allows a user to specify that they want elements appended to the document body + test(renderMode + " _jsPlumb.Defaults.Container, specified with a selector", function() { + _jsPlumb.Defaults.Container = $("body"); + equals($("#container")[0].childNodes.length, 0, "container has no nodes"); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals($("#container")[0].childNodes.length, 2, "container has two div elements"); // the divs we added have been added to the 'container' div. + // but we have told _jsPlumb to add its canvas to the body, so this connect call should not add another few elements to the container: + _jsPlumb.connect({source:d1, target:d2}); + equals($("#container")[0].childNodes.length, 2, "container still has two div elements"); + }); + + // tests of the functionality that allows a user to specify that they want elements appended to some specific container. + test(renderMode + " _jsPlumb.Defaults.Container, specified with DOM element", function() { + _jsPlumb.Defaults.Container = document.getElementsByTagName("body")[0]; + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length, "two divs added to the container"); // the divs we added have been added to the 'container' div. + // but we have told _jsPlumb to add its canvas to the body, so this connect call should not add another few elements to the container: + var bodyElementCount = $("body")[0].childNodes.length; + _jsPlumb.connect({source:d1, target:d2}); + equals(2, $("#container")[0].childNodes.length, "still only two children in container; elements were added to the body by _jsPlumb"); + // test to see if 3 elements have been added + equals(bodyElementCount + 3, $("body")[0].childNodes.length, "3 new elements added to the document body"); + }); + + test(renderMode + " container specified to connect call, with a selector", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length); // the divs we added have been added to the 'container' div. + var bodyElementCount = $("body")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to the body, so this connect call should not add another few elements to the container: + _jsPlumb.connect({source:d1, target:d2, container:$("body")}); + equals(2, $("#container")[0].childNodes.length); + equals(bodyElementCount + 3, $("body")[0].childNodes.length, "3 new elements added to the document body"); + }); + + test(renderMode + " container specified to connect call, with a string ID", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + equals(3, $("#container")[0].childNodes.length, "container has divs we added"); // the divs we added have been added to the 'container' div. + var d3ElementCount = $("#d3")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to "d3", so this connect call should not add another few elements to the container: + _jsPlumb.connect({source:d1, target:d2, container:"d3"}); + equals(3, $("#container")[0].childNodes.length, "container still has only the divs we added"); + equals(d3ElementCount + 3, $("#d3")[0].childNodes.length, "3 new elements added to div d3"); + }); + + test(renderMode + " container specified to addEndpoint call, with a selector", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length); // the divs we added have been added to the 'container' div. + var bodyElementCount = $("body")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to the body, so this connect call should not add another few elements to the container: + _jsPlumb.addEndpoint(d1, {container:$("body")}); + equals(2, $("#container")[0].childNodes.length); + equals(bodyElementCount + 1, $("body")[0].childNodes.length, "1 new element added to the document body"); + }); + + test(renderMode + " container specified to addEndpoint call, with a string ID", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + equals(3, $("#container")[0].childNodes.length, "container has divs we added"); // the divs we added have been added to the 'container' div. + var d3ElementCount = $("#d3")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to "d3", so this connect call should not add another few elements to the container: + _jsPlumb.addEndpoint(d1, { container:"d3" }); + equals(3, $("#container")[0].childNodes.length, "container still has only the divs we added"); + equals(d3ElementCount + 1, $("#d3")[0].childNodes.length, "1 new element added to div d3"); + }); + + test(renderMode + " detachable defaults to true when connection made between two endpoints", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2), + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection not detachable"); + }); + + test(renderMode + " connection detachable when target endpoint has connectionsDetachable set to true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2, {connectionsDetachable:true}), + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection detachable because connectionsDetachable was set on target endpoint"); + }); + + test(renderMode + " connection detachable when source endpoint has connectionsDetachable set to true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {connectionsDetachable:true}), e2 = _jsPlumb.addEndpoint(d2), + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection detachable because connectionsDetachable was set on source endpoint"); + }); + + test(renderMode + " Connector has 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.connector.type, "Bezier", "Bezier connector has type set"); + + var c2 = _jsPlumb.connect({source:d1, target:d2, connector:"Straight"}); + equals(c2.connector.type, "Straight", "Straight connector has type set"); + + var c3 = _jsPlumb.connect({source:d1, target:d2, connector:"Flowchart"}); + equals(c3.connector.type, "Flowchart", "Flowchart connector has type set"); + }); + + test(renderMode + " Endpoints have 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.endpoints[0].type, "Dot", "Dot endpoint has type set"); + + var c2 = _jsPlumb.connect({source:d1, target:d2, endpoints:["Rectangle", "Blank"]}); + equals(c2.endpoints[1].type, "Blank", "Blank endpoint has type set"); + equals(c2.endpoints[0].type, "Rectangle", "Rectangle endpoint has type set"); + }); + + test(renderMode + " Overlays have 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = _jsPlumb.connect({ + source:d1, + target:d2, + overlays:[ "Arrow", "Label", "PlainArrow", "Diamond" ] + }); + equals(c.overlays[0].type, "Arrow", "Arrow overlay has type set"); + equals(c.overlays[1].type, "Label", "Label overlay has type set"); + equals(c.overlays[2].type, "PlainArrow", "PlainArrow overlay has type set"); + equals(c.overlays[3].type, "Diamond", "Diamond overlay has type set"); + }); + + test(renderMode + " _jsPlumb.hide, original one-arg version", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + _jsPlumb.hide(d1); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(true, e1.isVisible(), "endpoint 1 is still visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + _jsPlumb.show(d1); + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + }); + + test(renderMode + " _jsPlumb.hide, two-arg version, endpoints should also be hidden", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + _jsPlumb.show(d1); // now show d1, but do not alter the endpoints. e1 should still be hidden + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + test(renderMode + " _jsPlumb.show, two-arg version, endpoints should become visible", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + _jsPlumb.show(d1, true); // now show d1, and alter the endpoints. e1 should be visible. + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(true, e1.isVisible(), "endpoint 1 is visible again."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + test(renderMode + " _jsPlumb.show, two-arg version, endpoints should become visible, but not all connections, because some other endpoints are not visible.", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e11 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + e3 = _jsPlumb.addEndpoint(d3, e), + c1 = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e11, target:e3}); + + // we now have d1 connected to both d3 and d2. we'll hide d1, and everything on d1 should be hidden. + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "connection 1 is no longer visible."); + equals(false, c2.isVisible(), "connection 2 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(false, e11.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + equals(true, e3.isVisible(), "endpoint 3 is still visible."); + + // now, we will also hide d3. making d1 visible again should NOT result in c2 becoming visible, because the other endpoint + // for c2 is e3, which is not visible. + _jsPlumb.hide(d3, true); + equals(false, e3.isVisible(), "endpoint 3 is no longer visible."); + + _jsPlumb.show(d1, true); // now show d1, and alter the endpoints. e1 should be visible, c1 should be visible, but c2 should not. + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(false, c2.isVisible(), "Connection 2 is not visible."); + equals(true, e1.isVisible(), "endpoint 1 is visible again."); + equals(true, e11.isVisible(), "endpoint 11 is visible again."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + equals(false, e3.isVisible(), "endpoint 3 is still not visible."); + }); + + /* + test(renderMode + " _jsPlumb.hide, two-arg version, endpoints should also be hidden", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + /** + * test for issue 132: label leaves its element in the DOM after it has been + * removed from a connection. + */ + test(renderMode + " label cleans itself up properly", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label", cssClass:"foo"}] + ]}); + ok($(".foo").length == 1, "label element exists in DOM"); + c.removeOverlay("label"); + ok($(".foo").length == 0, "label element does not exist in DOM"); + }); + + test(renderMode + " arrow cleans itself up properly", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Arrow", {id:"arrow"}] + ]}); + ok(c.getOverlay("arrow") != null, "arrow overlay exists"); + c.removeOverlay("arrow"); + ok(c.getOverlay("arrow") == null, "arrow overlay has been removed"); + }); + + test(renderMode + " label overlay getElement function", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label"}] + ]}); + ok(c.getOverlay("label").getElement() != null, "label overlay exposes element via getElement method"); + }); + + test(renderMode + " label overlay provides getLabel and setLabel methods", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label", label:"foo"}] + ]}); + var o = c.getOverlay("label"), e = o.getElement(); + ok(e.innerHTML == "foo", "label text is set to original value"); + o.setLabel("baz"); + ok(e.innerHTML == "baz", "label text is set to new value 'baz'"); + ok(o.getLabel() === "baz", "getLabel function works correctly with String"); + // now try functions + var aFunction = function() { return "aFunction"; }; + o.setLabel(aFunction); + ok(e.innerHTML == "aFunction", "label text is set to new value from Function"); + ok(o.getLabel() === aFunction, "getLabel function works correctly with Function"); + }); + + test(renderMode + " parameters object works for Endpoint", function() { + var d1 = _addDiv("d1"), + f = function() { alert("FOO!"); }, + e = _jsPlumb.addEndpoint(d1, { + isSource:true, + parameters:{ + "string":"param1", + "int":4, + "function":f + } + }); + ok(e.getParameter("string") === "param1", "getParameter(String) works correctly"); + ok(e.getParameter("int") === 4, "getParameter(int) works correctly"); + ok(e.getParameter("function") == f, "getParameter(Function) works correctly"); + }); + + test(renderMode + " parameters object works for Connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + f = function() { alert("FOO!"); }; + var c = _jsPlumb.connect({ + source:d1, + target:d2, + parameters:{ + "string":"param1", + "int":4, + "function":f + } + }); + ok(c.getParameter("string") === "param1", "getParameter(String) works correctly"); + ok(c.getParameter("int") === 4, "getParameter(int) works correctly"); + ok(c.getParameter("function") == f, "getParameter(Function) works correctly"); + }); + + test(renderMode + " parameters set on Endpoints and Connections are all merged, and merged correctly at that.", function() { + var d1 = _addDiv("d1"), + d2 = _addDiv("d2"), + e = _jsPlumb.addEndpoint(d1, { + isSource:true, + parameters:{ + "string":"sourceEndpoint", + "int":0, + "function":function() { return "sourceEndpoint"; } + } + }), + e2 = _jsPlumb.addEndpoint(d2, { + isTarget:true, + parameters:{ + "int":1, + "function":function() { return "targetEndpoint"; } + } + }), + c = _jsPlumb.connect({source:e, target:e2, parameters:{ + "function":function() { return "connection"; } + }}); + + ok(c.getParameter("string") === "sourceEndpoint", "getParameter(String) works correctly"); + ok(c.getParameter("int") === 1, "getParameter(int) works correctly"); + ok(c.getParameter("function")() == "connection", "getParameter(Function) works correctly"); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers standard connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2}); + equals(_jsPlumb.anchorManager.getConnectionsFor("d1").length, 1); + equals(_jsPlumb.anchorManager.getEndpointsFor("d1").length, 1); + equals(_jsPlumb.anchorManager.getConnectionsFor("d2").length, 1); + equals(_jsPlumb.anchorManager.getEndpointsFor("d2").length, 1); + var c2 = _jsPlumb.connect({source:d1, target:d2}); + equals(_jsPlumb.anchorManager.getConnectionsFor("d1").length, 2); + equals(_jsPlumb.anchorManager.getConnectionsFor("d2").length, 2); + equals(_jsPlumb.anchorManager.getEndpointsFor("d1").length, 2); + equals(_jsPlumb.anchorManager.getEndpointsFor("d2").length, 2); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers dynamic anchor connection, and removes it.", function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var c = _jsPlumb.connect({source:d3, target:d4, anchors:["AutoDefault", "AutoDefault"]}); + + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 1); + + var c2 = _jsPlumb.connect({source:d3, target:d4}); + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 2); + equals(_jsPlumb.anchorManager.getConnectionsFor("d4").length, 2); + + equals(_jsPlumb.anchorManager.getEndpointsFor("d3").length, 2); + _jsPlumb.detach(c); + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 1); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers continuous anchor connection, and removes it.", function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var c = _jsPlumb.connect({source:d3, target:d4, anchors:["Continuous", "Continuous"]}); + + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 1); + equals(_jsPlumb.anchorManager.getConnectionsFor("d4").length, 1); + + _jsPlumb.detach(c); + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 0); + equals(_jsPlumb.anchorManager.getConnectionsFor("d4").length, 0); + + _jsPlumb.reset(); + equals(_jsPlumb.anchorManager.getEndpointsFor("d4").length, 0); + }); + +// ------------- utility functions - math stuff, mostly -------------------------- + + var tolerance = 0.00000005, withinTolerance = function(v1, v2, msg) { + if (Math.abs(v1 - v2) < tolerance) ok(true, msg + "; expected " + v1 + " and got it"); + else { + ok(false, msg + "; expected " + v1 + " got " + v2); + } + }; + test(renderMode + " jsPlumb.util.gradient, segment 1", function() { + var p1 = [2,2], p2 = [3,1], m = jsPlumb.util.gradient(p1,p2); + equals(m, -1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumb.util.normal, segment 1", function() { + var p1 = [2,2], p2 = [3,1], m = jsPlumb.util.normal(p1,p2); + equals(m, 1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumb.util.gradient, segment 2", function() { + var p1 = [2,2], p2 = [3,3], m = jsPlumb.util.gradient(p1,p2); + equals(m, 1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumb.util.normal, segment 2", function() { + var p1 = [2,2], p2 = [3,3], m = jsPlumb.util.normal(p1,p2); + equals(m, -1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumb.util.gradient, segment 3", function() { + var p1 = [2,2], p2 = [1,3], m = jsPlumb.util.gradient(p1,p2); + equals(m, -1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumb.util.normal, segment 3", function() { + var p1 = [2,2], p2 = [1,3], m = jsPlumb.util.normal(p1,p2); + equals(m, 1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumb.util.gradient, segment 4", function() { + var p1 = [2,2], p2 = [1,1], m = jsPlumb.util.gradient(p1,p2); + equals(m, 1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumb.util.normal, segment 4", function() { + var p1 = [2,2], p2 = [1,1], m = jsPlumb.util.normal(p1,p2); + equals(m, -1, "normal calculated correctly for simple case"); + }); + test(renderMode + "jsPlumb.util.pointOnLine, segment 1", function() { + var p1 = {x:2,y:2}, p2={x:3, y:1}, + target = jsPlumb.util.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumb.util.pointOnLine, segment 2", function() { + var p1 = {x:2,y:2}, p2={x:3, y:3}, + target = jsPlumb.util.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumb.util.pointOnLine, segment 3", function() { + var p1 = {x:2,y:2}, p2={x:1, y:3}, + target = jsPlumb.util.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumb.util.pointOnLine, segment 4", function() { + var p1 = {x:2,y:2}, p2={x:1, y:1}, + target = jsPlumb.util.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumb.util.perpendicularLineTo, segment 1", function() { + var p1 = {x:2, y:2}, p2={x:3, y:1}, m = jsPlumb.util.gradient(p1, p2), + l = jsPlumb.util.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(4, l[0].x, "point 1 x is correct"); + withinTolerance(2, l[0].y, "point 1 y is correct"); + + withinTolerance(2, l[1].x, "point 2 x is correct"); + withinTolerance(0, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumb.util.perpendicularLineTo, segment 2", function() { + var p1 = {x:2, y:2}, p2={x:3, y:3}, m = jsPlumb.util.gradient(p1, p2), + l = jsPlumb.util.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(4, l[0].x, "point 1 x is correct"); + withinTolerance(2, l[0].y, "point 1 y is correct"); + + withinTolerance(2, l[1].x, "point 2 x is correct"); + withinTolerance(4, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumb.util.perpendicularLineTo, segment 3", function() { + var p1 = {x:2, y:2}, p2={x:1, y:3}, m = jsPlumb.util.gradient(p1, p2), + l = jsPlumb.util.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(2, l[0].x, "point 1 x is correct"); + withinTolerance(4, l[0].y, "point 1 y is correct"); + + withinTolerance(0, l[1].x, "point 2 x is correct"); + withinTolerance(2, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumb.util.perpendicularLineTo, segment 4", function() { + var p1 = {x:2, y:2}, p2={x:1, y:1}, m = jsPlumb.util.gradient(p1, p2), + l = jsPlumb.util.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(2, l[0].x, "point 1 x is correct"); + withinTolerance(0, l[0].y, "point 1 y is correct"); + + withinTolerance(0, l[1].x, "point 2 x is correct"); + withinTolerance(2, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumb.util.intersects, with intersection", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 3, y:4, w:3, h:3}; + + ok(jsPlumb.util.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumb.util.intersects, with no intersection", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 13, y:4, w:3, h:3}; + + ok(!jsPlumb.util.intersects(r1, r2), "r1 and r2 do not intersect"); + }); + test(renderMode + "jsPlumb.util.intersects, corners touch but no intersection", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 6, y:8, w:3, h:3}; + + ok(!jsPlumb.util.intersects(r1, r2), "r1 and r2 do not intersect"); + }); + test(renderMode + "jsPlumb.util.intersects, one rectangle contained within the other", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 0, y:0, w:23, h:23}; + + ok(jsPlumb.util.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumb.util.setImage on Endpoint", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { + endpoint:[ "Image", { src:"../../img/endpointTest1.png" } ] + }, + ep = _jsPlumb.addEndpoint(d1, e); + ep.setImage("../../img/littledot.png"); + }); + test(renderMode + "jsPlumb.util.setImage on Endpoint, with supplied onload", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { + endpoint:[ "Image", { + src:"../../img/endpointTest1.png", + onload:function(imgEp) { + equals("../../img/endpointTest1.png", imgEp.img.src); + } + } ] + }, + ep = _jsPlumb.addEndpoint(d1, e); + ep.setImage("../../img/littledot.png", function(imgEp) { + equals("../../img/littledot.png", imgEp.img.src); + }); + }); + + test(renderMode + "attach endpoint to table when desired element was td", function() { + var table = document.createElement("table"), + tr = document.createElement("tr"), + td = document.createElement("td"); + table.appendChild(tr); tr.appendChild(td); + document.body.appendChild(table); + var ep = _jsPlumb.addEndpoint(td); + equals(ep.canvas.parentNode.tagName.toLowerCase(), "table"); + }); + +// issue 190 - regressions with getInstance. these tests ensure that generated ids are unique across +// instances. + + test(renderMode + " id clashes between instances", function() { + var d1 = document.createElement("div"), + d2 = document.createElement("div"), + _jsp2 = jsPlumb.getInstance(); + + document.body.appendChild(d1); + document.body.appendChild(d2); + + _jsPlumb.addEndpoint(d1); + _jsp2.addEndpoint(d2); + + var id1 = d1.getAttribute("id"), + id2 = d2.getAttribute("id"); + + var idx = id1.indexOf("_"), idx2 = id1.lastIndexOf("_"), v1 = id1.substring(idx, idx2); + var idx3 = id2.indexOf("_"), idx4 = id2.lastIndexOf("_"), v2 = id2.substring(idx3, idx4); + + ok (v1 != v2, "instance versions are different : " + v1 + " : " + v2); + }); + + test(renderMode + " id clashes between instances", function() { + var d1 = document.createElement("div"), + d2 = document.createElement("div"), + _jsp2 = jsPlumb.getInstance(); + + document.body.appendChild(d1); + document.body.appendChild(d2); + + _jsPlumb.addEndpoint(d1); + _jsPlumb.addEndpoint(d2); + + var id1 = d1.getAttribute("id"), + id2 = d2.getAttribute("id"); + + var idx = id1.indexOf("_"), idx2 = id1.lastIndexOf("_"), v1 = id1.substring(idx, idx2); + var idx3 = id2.indexOf("_"), idx4 = id2.lastIndexOf("_"), v2 = id2.substring(idx3, idx4); + + ok (v1 == v2, "instance versions are the same : " + v1 + " : " + v2); + }); + + test(renderMode + " importDefaults", function() { + _jsPlumb.Defaults.Anchors = ["LeftMiddle", "RightMiddle"]; + var d1 = _addDiv("d1"), + d2 = _addDiv(d2), + c = _jsPlumb.connect({source:d1, target:d2}), + e = c.endpoints[0]; + + equals(e.anchor.x, 0, "left middle anchor"); + equals(e.anchor.y, 0.5, "left middle anchor"); + + _jsPlumb.importDefaults({ + Anchors:["TopLeft", "TopRight"] + }); + + var conn = _jsPlumb.connect({source:d1, target:d2}), + e1 = conn.endpoints[0], e2 = conn.endpoints[1]; + + equals(e1.anchor.x, 0, "top leftanchor"); + equals(e2.anchor.y, 0, "top left anchor"); + equals(e2.anchor.x, 1, "top right anchor"); + equals(e2.anchor.y, 0, "top right anchor"); + + }); + + test(renderMode + " restoreDefaults", function() { + _jsPlumb.importDefaults({ + Anchors:["TopLeft", "TopRight"] + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), conn = _jsPlumb.connect({source:d1, target:d2}), + e1 = conn.endpoints[0], e2 = conn.endpoints[1]; + + equals(e1.anchor.x, 0, "top leftanchor"); + equals(e2.anchor.y, 0, "top left anchor"); + equals(e2.anchor.x, 1, "top right anchor"); + equals(e2.anchor.y, 0, "top right anchor"); + + _jsPlumb.restoreDefaults(); + + var conn2 = _jsPlumb.connect({source:d1, target:d2}), + e3 = conn2.endpoints[0], e4 = conn2.endpoints[1]; + + equals(e3.anchor.x, 0.5, "bottom center anchor"); + equals(e3.anchor.y, 1, "bottom center anchor"); + equals(e4.anchor.x, 0.5, "bottom center anchor"); + equals(e4.anchor.y, 1, "bottom center anchor"); + }); + +// setId function + + test(renderMode + " setId, taking two strings, only default scope", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId("d1", "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a selector and a string, only default scope", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1"), "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a DOM element and a string, only default scope", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1")[0], "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking two strings, mix of scopes", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2, scope:"FOO"}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId("d1", "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a selector and a string, mix of scopes", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2, scope:"FOO"}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1"), "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a DOM element and a string, mix of scopes", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2, scope:"FOO"}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1")[0], "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setIdChanged, ", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + $("#d1").attr("id", "d3"); + + _jsPlumb.setIdChanged("d1", "d3"); + + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " endpoint hide/show should hide/show overlays", function() { + _addDiv("d1"); + var e1 = _jsPlumb.addEndpoint("d1", { + overlays:[ + [ "Label", { id:"label", label:"foo" } ] + ] + }), + o = e1.getOverlay("label"); + + ok(o.isVisible(), "overlay is initially visible"); + _jsPlumb.hide("d1", true); + ok(!o.isVisible(), "overlay is no longer visible"); + }); + + test(renderMode + " connection hide/show should hide/show overlays", function() { + _addDiv("d1");_addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", + overlays:[ + [ "Label", { id:"label", label:"foo" } ] + ] + }), + o = c.getOverlay("label"); + + ok(o.isVisible(), "overlay is initially visible"); + _jsPlumb.hide("d1", true); + ok(!o.isVisible(), "overlay is no longer visible"); + }); + + test(renderMode + " select, basic test", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2"}), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 1, "one connection selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; dont filter on scope.", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 2, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scope", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + s = _jsPlumb.select({source:"d1", scope:"FOO"}); + + equals(s.length, 1, "one connection selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scopes", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + c3 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAZ"}) + s = _jsPlumb.select({source:"d1", scope:["FOO","BAR"]}); + + equals(s.length, 2, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; scope but no scope filter; single source id", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + c3 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAZ"}), + c4 = _jsPlumb.connect({source:"d2", target:"d1", scope:"BOZ"}), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 3, "three connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scopes; single source id", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + c3 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAZ"}), + c4 = _jsPlumb.connect({source:"d2", target:"d1", scope:"BOZ"}), + s = _jsPlumb.select({source:"d1", scope:["FOO","BAR", "BOZ"]}); + + equals(s.length, 2, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scope; dont supply sourceid", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + s = _jsPlumb.select({ scope:"FOO" }); + + equals(s.length, 1, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scope; dont supply sourceid", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d2", target:"d1", scope:"BAR"}), + s = _jsPlumb.select({ scope:"FOO" }); + + equals(s.length, 1, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, two connections, with overlays", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({ + source:"d1", + target:"d2", + overlays:[ + ["Label", {id:"l"}] + ] + }), + c2 = _jsPlumb.connect({ + source:"d1", + target:"d2", + overlays:[ + ["Label", {id:"l"}] + ] + }), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 2, "two connections selected"); + ok(s.get(0).getOverlay("l") != null, "connection has overlay"); + ok(s.get(1).getOverlay("l") != null, "connection has overlay"); + }); + + test(renderMode + " select, chaining with setHover and hideOverlay", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({ + source:"d1", + target:"d2", + overlays:[ + ["Label", {id:"l"}] + ] + }); + s = _jsPlumb.select({source:"d1"}); + + s.setHover(false).hideOverlay("l"); + + ok(!(s.get(0).isHover()), "connection is not hover"); + ok(!(s.get(0).getOverlay("l").isVisible()), "overlay is not visible"); + }); + + test(renderMode + " select, .each function", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10) + }); + } + + var s = _jsPlumb.select(); + equals(s.length, 5, "there are five connections"); + + var t = ""; + s.each(function(connection) { + t += "f"; + }); + equals("fffff", t, ".each is working"); + }); + + test(renderMode + " select, multiple connections + chaining", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + overlays:[ + ["Arrow", {location:0.3}], + ["Arrow", {location:0.7}] + ] + }); + } + + var s = _jsPlumb.select().removeAllOverlays().setParameter("foo", "bar").setHover(false).setLabel("baz"); + equals(s.length, 5, "there are five connections"); + + for (var j = 0; j < 5; j++) { + equals(s.get(j).getOverlays().length, 1, "one overlay: the label"); + equals(s.get(j).getParameter("foo"), "bar", "parameter foo has value 'bar'"); + ok(!(s.get(j).isHover()), "hover is set to false"); + equals(s.get(j).getLabel(), "baz", "label is set to 'baz'"); + } + }); + + test(renderMode + " select, simple getter", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + label:"FOO" + }); + } + + var lbls = _jsPlumb.select().getLabel(); + equals(lbls.length, 5, "there are five labels"); + + for (var j = 0; j < 5; j++) { + equals(lbls[j][0], "FOO", "label has value 'FOO'"); + } + }); + + test(renderMode + " select, getter + chaining", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + label:"FOO" + }); + } + + var params = _jsPlumb.select().removeAllOverlays().setParameter("foo", "bar").getParameter("foo"); + equals(params.length, 5, "there are five params"); + + for (var j = 0; j < 5; j++) { + equals(params[j][0], "bar", "parameter has value 'bar'"); + } + }); + + + test(renderMode + " select, detach method", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + label:"FOO" + }); + } + + var params = _jsPlumb.select().detach(); + + equals(jsPlumb.select().length, 0, "there are no connections"); + }); + +// ******************* getEndpoints ************************************************ + + test(renderMode + " getEndpoints", function() { + _addDiv("d1");_addDiv("d2"); + + _jsPlumb.addEndpoint("d1"); + _jsPlumb.addEndpoint("d2"); + _jsPlumb.addEndpoint("d1"); + + var e = _jsPlumb.getEndpoints("d1"), + e2 = _jsPlumb.getEndpoints("d2"); + equals(e.length, 2, "two endpoints on d1"); + equals(e2.length, 1, "one endpoint on d2"); + }); + + /** + * leave this test at the bottom! + */ + test(renderMode + ': unload test', function() { + _jsPlumb.unload(); + var contextNode = $(".__jsPlumb_context"); + ok(contextNode.length == 0, 'context node unloaded'); + }); +}; + diff --git a/archive/1.3.7/jsPlumb-connectors-statemachine-1.3.7-RC1.js b/archive/1.3.7/jsPlumb-connectors-statemachine-1.3.7-RC1.js new file mode 100644 index 000000000..14df4c16f --- /dev/null +++ b/archive/1.3.7/jsPlumb-connectors-statemachine-1.3.7-RC1.js @@ -0,0 +1,442 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.7 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the state machine connectors. + * + * Thanks to Brainstorm Mobile Solutions for supporting the development of these. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + var Line = function(x1, y1, x2, y2) { + + this.m = (y2 - y1) / (x2 - x1); + this.b = -1 * ((this.m * x1) - y1); + + this.rectIntersect = function(x,y,w,h) { + var results = []; + + // try top face + // the equation of the top face is y = (0 * x) + b; y = b. + var xInt = (y - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try right face + var yInt = (this.m * (x + w)) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + // bottom face + var xInt = ((y + h) - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try left face + var yInt = (this.m * x) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + if (results.length == 2) { + var midx = (results[0][0] + results[1][0]) / 2, midy = (results[0][1] + results[1][1]) / 2; + results.push([ midx,midy ]); + // now calculate the segment inside the rectangle where the midpoint lies. + var xseg = midx <= x + (w / 2) ? -1 : 1, + yseg = midy <= y + (h / 2) ? -1 : 1; + results.push([xseg, yseg]); + return results; + } + + return null; + + }; + }, + _segment = function(x1, y1, x2, y2) { + if (x1 <= x2 && y2 <= y1) return 1; + else if (x1 <= x2 && y1 <= y2) return 2; + else if (x2 <= x1 && y2 >= y1) return 3; + return 4; + }, + + // the control point we will use depends on the faces to which each end of the connection is assigned, specifically whether or not the + // two faces are parallel or perpendicular. if they are parallel then the control point lies on the midpoint of the axis in which they + // are parellel and varies only in the other axis; this variation is proportional to the distance that the anchor points lie from the + // center of that face. if the two faces are perpendicular then the control point is at some distance from both the midpoints; the amount and + // direction are dependent on the orientation of the two elements. 'seg', passed in to this method, tells you which segment the target element + // lies in with respect to the source: 1 is top right, 2 is bottom right, 3 is bottom left, 4 is top left. + // + // sourcePos and targetPos are arrays of info about where on the source and target each anchor is located. their contents are: + // + // 0 - absolute x + // 1 - absolute y + // 2 - proportional x in element (0 is left edge, 1 is right edge) + // 3 - proportional y in element (0 is top edge, 1 is bottom edge) + // + _findControlPoint = function(midx, midy, segment, sourceEdge, targetEdge, dx, dy, distance, proximityLimit) { + + // TODO (maybe) + // - if anchor pos is 0.5, make the control point take into account the relative position of the elements. + if (distance <= proximityLimit) return [midx, midy]; + + if (segment == 1) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 2) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 3) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 4) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + }; + + /* + Function: StateMachine constructor + + Allowed parameters: + curviness - measure of how "curvy" the connectors will be. this is translated as the distance that the + Bezier curve's control point is from the midpoint of the straight line connecting the two + endpoints, and does not mean that the connector is this wide. The Bezier curve never reaches + its control points; they act as gravitational masses. defaults to 10. + margin - distance from element to start and end connectors, in pixels. defaults to 5. + proximityLimit - sets the distance beneath which the elements are consider too close together to bother with fancy + curves. by default this is 80 pixels. + loopbackRadius - the radius of a loopback connector. optional; defaults to 25. + */ + jsPlumb.Connectors.StateMachine = function(params) { + var self = this, + currentPoints = null, + _sx, _sy, _tx, _ty, _controlPoint = [], + curviness = params.curviness || 10, + margin = params.margin || 5, + proximityLimit = params.proximityLimit || 80, + clockwise = params.orientation && params.orientation == "clockwise", + loopbackRadius = params.loopbackRadius || 25, + isLoopback = false; + + this.type = "StateMachine"; + params = params || {}; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *= 1.9; + //ensure at least one pixel width + lineWidth = lineWidth || 1; + var x = Math.min(sourcePos[0], targetPos[0]) - xo, + y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (sourceEndpoint.elementId != targetEndpoint.elementId) { + + isLoopback = false; + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + + // now adjust for the margin + if (sourcePos[2] == 0) _sx -= margin; + if (sourcePos[2] == 1) _sx += margin; + if (sourcePos[3] == 0) _sy -= margin; + if (sourcePos[3] == 1) _sy += margin; + if (targetPos[2] == 0) _tx -= margin; + if (targetPos[2] == 1) _tx += margin; + if (targetPos[3] == 0) _ty -= margin; + if (targetPos[3] == 1) _ty += margin; + + // + // these connectors are quadratic bezier curves, having a single control point. if both anchors + // are located at 0.5 on their respective faces, the control point is set to the midpoint and you + // get a straight line. this is also the case if the two anchors are within 'proximityLimit', since + // it seems to make good aesthetic sense to do that. outside of that, the control point is positioned + // at 'curviness' pixels away along the normal to the straight line connecting the two anchors. + // + // there may be two improvements to this. firstly, we might actually support the notion of avoiding nodes + // in the UI, or at least making a good effort at doing so. if a connection would pass underneath some node, + // for example, we might increase the distance the control point is away from the midpoint in a bid to + // steer it around that node. this will work within limits, but i think those limits would also be the likely + // limits for, once again, aesthetic good sense in the layout of a chart using these connectors. + // + // the second possible change is actually two possible changes: firstly, it is possible we should gradually + // decrease the 'curviness' as the distance between the anchors decreases; start tailing it off to 0 at some + // point (which should be configurable). secondly, we might slightly increase the 'curviness' for connectors + // with respect to how far their anchor is from the center of its respective face. this could either look cool, + // or stupid, and may indeed work only in a way that is so subtle as to have been a waste of time. + // + + var _midx = (_sx + _tx) / 2, _midy = (_sy + _ty) / 2, + m2 = (-1 * _midx) / _midy, theta2 = Math.atan(m2), + dy = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.sin(theta2)), + dx = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.cos(theta2)), + segment = _segment(_sx, _sy, _tx, _ty), + distance = Math.sqrt(Math.pow(_tx - _sx, 2) + Math.pow(_ty - _sy, 2)); + + // calculate the control point. this code will be where we'll put in a rudimentary element avoidance scheme; it + // will work by extending the control point to force the curve to be, um, curvier. + _controlPoint = _findControlPoint(_midx, + _midy, + segment, + sourcePos, + targetPos, + curviness, curviness, + distance, + proximityLimit); + + + var requiredWidth = Math.max(Math.abs(_controlPoint[0] - _sx) * 3, Math.abs(_controlPoint[0] - _tx) * 3, Math.abs(_tx-_sx), 2 * lineWidth, minWidth), + requiredHeight = Math.max(Math.abs(_controlPoint[1] - _sy) * 3, Math.abs(_controlPoint[1] - _ty) * 3, Math.abs(_ty-_sy), 2 * lineWidth, minWidth); + + if (w < requiredWidth) { + var dw = requiredWidth - w; + x -= (dw / 2); + _sx += (dw / 2); + _tx += (dw / 2); + w = requiredWidth; + _controlPoint[0] += (dw / 2); + } + + if (h < requiredHeight) { + var dh = requiredHeight - h; + y -= (dh / 2); + _sy += (dh / 2); + _ty += (dh / 2); + h = requiredHeight; + _controlPoint[1] += (dh / 2); + } + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty, _controlPoint[0], _controlPoint[1] ]; + } + else { + isLoopback = true; + // a loopback connector. draw an arc from one anchor to the other. + // i guess we'll do this the same way as the others. just the control point will be a fair distance away. + var x1 = sourcePos[0], x2 = sourcePos[0], y1 = sourcePos[1] - margin, y2 = sourcePos[1] - margin, + cx = x1, cy = y1 - loopbackRadius; + + // canvas sizing stuff, to ensure the whole painted area is visible. + w = ((2 * lineWidth) + (4 * loopbackRadius)), h = ((2 * lineWidth) + (4 * loopbackRadius)); + x = cx - loopbackRadius - lineWidth - loopbackRadius, y = cy - loopbackRadius - lineWidth - loopbackRadius; + currentPoints = [ x, y, w, h, cx-x, cy-y, loopbackRadius, clockwise, x1-x, y1-y, x2-x, y2-y]; + } + + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_tx, y:_ty }, + { x:_controlPoint[0], y:_controlPoint[1] }, + { x:_controlPoint[0] + 1, y:_controlPoint[1] + 1}, + { x:_sx, y:_sy } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + if (isLoopback) { + + if (location > 0 && location < 1) location = 1- location; + +// current points are [ x, y, width, height, center x, center y, radius, clockwise, startx, starty, endx, endy ] + // so the path length is the circumference of the circle + //var len = 2 * Math.PI * currentPoints[6], + // map 'location' to an angle. 0 is PI/2 when the connector is on the top face; if we + // support other faces it will have to be calculated for each one. 1 is also PI/2. + // 0.5 is -PI/2. + var startAngle = (location * 2 * Math.PI) + (Math.PI / 2), + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + + } + else return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + if (isLoopback) + return Math.atan(location * 2 * Math.PI); + else + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + if (isLoopback) { + + if (location > 0 && location < 1) location = 1- location; + + var circumference = 2 * Math.PI * currentPoints[6], + arcSpan = distance / circumference * 2 * Math.PI, + startAngle = (location * 2 * Math.PI) - arcSpan + (Math.PI / 2), + + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + } + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + }; + + /* + * Canvas state machine renderer. + */ + jsPlumb.Connectors.canvas.StateMachine = function(params) { + params = params || {}; + var self = this, drawGuideline = params.drawGuideline || true, avoidSelector = params.avoidSelector; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.CanvasConnector.apply(this, arguments); + + + this._paint = function(dimensions) { + + if (dimensions.length == 10) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.quadraticCurveTo(dimensions[8], dimensions[9], dimensions[6], dimensions[7]); + self.ctx.stroke(); + + /*/ draw the guideline + if (drawGuideline) { + self.ctx.save(); + self.ctx.beginPath(); + self.ctx.strokeStyle = "silver"; + self.ctx.lineWidth = 1; + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + self.ctx.restore(); + } + //*/ + } + else { + // a loopback connector + self.ctx.save(); + self.ctx.beginPath(); + var startAngle = 0, // Starting point on circle + endAngle = 2 * Math.PI, // End point on circle + clockwise = dimensions[7]; // clockwise or anticlockwise + self.ctx.arc(dimensions[4],dimensions[5],dimensions[6],0, endAngle, clockwise); + self.ctx.stroke(); + self.ctx.closePath(); + self.ctx.restore(); + } + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + /* + * SVG State Machine renderer + */ + jsPlumb.Connectors.svg.StateMachine = function() { + var self = this; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.SvgConnector.apply(this, arguments); + this.getPath = function(d) { + + if (d.length == 10) + return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[8] + " " + d[9] + " " + d[6] + " " + d[7]; + else { + // loopback + return "M" + (d[8] + 4) + " " + d[9] + " A " + d[6] + " " + d[6] + " 0 1,0 " + (d[8]-4) + " " + d[9]; + } + }; + }; + + /* + * VML state machine renderer + */ + jsPlumb.Connectors.vml.StateMachine = function() { + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.VmlConnector.apply(this, arguments); + var _conv = jsPlumb.vml.convertValue; + this.getPath = function(d) { + if (d.length == 10) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + } + else { + // loopback + var left = _conv(d[8] - d[6]), + top = _conv(d[9] - (2 * d[6])), + right = left + _conv(2 * d[6]), + bottom = top + _conv(2 * d[6]), + posString = left + "," + top + "," + right + "," + bottom; + + var o = "ar " + posString + "," + _conv(d[8]) + "," + + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + " e"; + + return o; + } + }; + }; + +})(); + +/* + // now for a rudimentary avoidance scheme. TODO: how to set this in a cross-library way? + // if (avoidSelector) { + // var testLine = new Line(sourcePos[0] + _sx,sourcePos[1] + _sy,sourcePos[0] + _tx,sourcePos[1] + _ty); + // var sel = jsPlumb.getSelector(avoidSelector); + // for (var i = 0; i < sel.length; i++) { + // var id = jsPlumb.getId(sel[i]); + // if (id != sourceEndpoint.elementId && id != targetEndpoint.elementId) { + // o = jsPlumb.getOffset(id), s = jsPlumb.getSize(id); +// +// if (o && s) { +// var collision = testLine.rectIntersect(o.left,o.top,s[0],s[1]); +// if (collision) { + // set the control point to be a certain distance from the midpoint of the two points that + // the line crosses on the rectangle. + // TODO where will this 75 number come from? + // _controlX = collision[2][0] + (75 * collision[3][0]); + // / _controlY = collision[2][1] + (75 * collision[3][1]); +// } +// } + // } + // } + //} + */ \ No newline at end of file diff --git a/archive/1.3.7/jsPlumb-defaults-1.3.7-RC1.js b/archive/1.3.7/jsPlumb-defaults-1.3.7-RC1.js new file mode 100644 index 000000000..75432c18f --- /dev/null +++ b/archive/1.3.7/jsPlumb-defaults-1.3.7-RC1.js @@ -0,0 +1,1119 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.7 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the default Connectors, Endpoint and Overlay definitions. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +(function() { + + /** + * + * Helper class to consume unused mouse events by components that are DOM elements and + * are used by all of the different rendering modes. + * + */ + jsPlumb.DOMElementComponent = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + // when render mode is canvas, these functions may be called by the canvas mouse handler. + // this component is safe to pipe this stuff to /dev/null. + this.mousemove = + this.dblclick = + this.click = + this.mousedown = + this.mouseup = function(e) { }; + }; + + /** + * Class: Connectors.Straight + * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters. + */ + jsPlumb.Connectors.Straight = function() { + this.type = "Straight"; + var self = this, + currentPoints = null, + _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty, _segment, _length; + + /** + * Computes the new size and position of the canvas. + */ + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + _dx = _tx - _sx, _dy = _ty - _sy; + //_m = _dy / _dx, _m2 = -1 / _m; + _m = jsPlumb.util.gradient({x:_sx, y:_sy}, {x:_tx, y:_ty}), _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + //_segment = jsPlumb.util.segment({x:_sx, y:_sy}, {x:_tx, y:_ty}); + _length = Math.sqrt((_dx * _dx) + (_dy * _dy)); + + return currentPoints; + }; + + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + if (location == 0) + return { x:_sx, y:_sy }; + else if (location == 1) + return { x:_tx, y:_ty }; + else + return jsPlumb.util.pointOnLine({x:_sx, y:_sy}, {x:_tx, y:_ty}, location * _length); + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { + return _m; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + * this hands off to jsPlumb.util to do the maths, supplying two points and the distance. + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location), + farAwayPoint = location == 1 ? { + x:_sx + ((_tx - _sx) * 10), + y:_sy + ((_sy - _ty) * 10) + } : {x:_tx, y:_ty }; + + return jsPlumb.util.pointOnLine(p, farAwayPoint, distance); + }; + }; + + + /** + * Class:Connectors.Bezier + * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's + * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below. + */ + /** + * Function:Constructor + * + * Parameters: + * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line. + * Optional; defaults to 150. + * stub - optional value for a distance to travel from the connector's endpoint before beginning the Bezier curve. defaults to 0. + * + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + params = params || {}; + this.majorAnchor = params.curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + this.type = "Bezier"; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(sourceEndpoint), + too = targetAnchor.getOrientation(targetEndpoint), + perpendicular = soo[0] != too[0] || soo[1] == too[1], + p = [], + ma = self.majorAnchor, mi = self.minorAnchor; + + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h, _sStubX, _sStubY, _tStubX, _tStubY; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, sourceEndpoint, targetEndpoint, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx), minx2 = Math.min(_CP[0], _CP2[0]), minx = Math.min(minx1,minx2), + maxx1 = Math.max(_sx,_tx), maxx2 = Math.max(_CP[0], _CP2[0]), maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty), miny2 = Math.min(_CP[1], _CP2[1]), miny = Math.min(miny1,miny2), + maxy1 = Math.max(_sy,_ty), maxy2 = Math.max(_CP[1], _CP2[1]), maxy = Math.max(maxy1,maxy2); + + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, + _sx, _sy, _tx, _ty, + _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + }; + + + /** + * Class: Connectors.Flowchart + * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments. + */ + /** + * Function: Constructor + * + * Parameters: + * stub - minimum length for the stub at each end of the connector. defaults to 30 pixels. + */ + jsPlumb.Connectors.Flowchart = function(params) { + this.type = "Flowchart"; + params = params || {}; + var self = this, + minStubLength = params.stub || params.minStubLength /* bwds compat. */ || 30, + segments = [], + totalLength = 0, + segmentProportions = [], + segmentProportionalLengths = [], + points = [], + swapX, swapY, + maxX = 0, maxY = 0, + /** + * recalculates the points at which the segments begin and end, proportional to the total length travelled + * by all the segments that constitute the connector. we use this to help with pointOnPath calculations. + */ + updateSegmentProportions = function(startX, startY, endX, endY) { + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segments[i][5] / totalLength; + segmentProportions[i] = [curLoc, (curLoc += (segments[i][5] / totalLength)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0], + ly = segments.length == 0 ? sy : segments[segments.length - 1][1], + m = x == lx ? Infinity : 0, + l = Math.abs(x == lx ? y - ly : x - lx); + segments.push([x, y, lx, ly, m, l]); + totalLength += l; + + maxX = Math.max(maxX, x); + maxY = Math.max(maxY, y); + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment + * that contains the point which is 'location' distance along the entire path, where + * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths + * are made up of a list of segments, each of which contributes some fraction to + * the total length. + */ + findSegmentForLocation = function(location) { + var idx = segmentProportions.length - 1, inSegmentProportion = 1; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, + sourceAnchor, targetAnchor, lineWidth, minWidth, sourceInfo, targetInfo) { + + segments = []; + totalLength = 0; + segmentProportionalLengths = []; + maxX = maxY = 0; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + offx = (lw / 2) + (minStubLength * 2), + offy = (lw / 2) + (minStubLength * 2), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(sourceEndpoint), + to = targetAnchor.orientation || targetAnchor.getOrientation(targetEndpoint), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx, + h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy; + + // if either anchor does not have an orientation set, we derive one from their relative + // positions. we fix the axis to be the one in which the two elements are further apart, and + // point each anchor at the other element. this is also used when dragging a new connection. + if (so[0] == 0 && so[1] == 0 || to[0] == 0 && to[1] == 0) { + var index = w > h ? 0 : 1, oIndex = [1,0][index]; + so = []; to = []; + so[index] = sourcePos[index] > targetPos[index] ? -1 : 1; + to[index] = sourcePos[index] > targetPos[index] ? 1 : -1; + so[oIndex] = 0; + to[oIndex] = 0; + } + + if (w < minWidth) { + offx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + offy += (minWidth - h) / 2; + h = minWidth; + } + + var sx = swapX ? w-offx : offx, + sy = swapY ? h-offy : offy, + tx = swapX ? offx : w-offx , + ty = swapY ? offy : h-offy, + startStubX = sx + (so[0] * minStubLength), + startStubY = sy + (so[1] * minStubLength), + endStubX = tx + (to[0] * minStubLength), + endStubY = ty + (to[1] * minStubLength), + isXGreaterThanStubTimes2 = Math.abs(sx - tx) > 2 * minStubLength, + isYGreaterThanStubTimes2 = Math.abs(sy - ty) > 2 * minStubLength, + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2), + oProduct = ((so[0] * to[0]) + (so[1] * to[1])), + opposite = oProduct == -1, + perpendicular = oProduct == 0, + orthogonal = oProduct == 1; + + x -= offx; y -= offy; + points = [x, y, w, h, sx, sy, tx, ty]; + var extraPoints = []; + + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + var sourceAxis = so[0] == 0 ? "y" : "x", + anchorOrientation = opposite ? "opposite" : orthogonal ? "orthogonal" : "perpendicular", + segment = jsPlumb.util.segment([sx, sy], [tx, ty]), + flipSourceSegments = so[sourceAxis == "x" ? 0 : 1] == -1, + flipSegments = { + "x":[null, 4, 3, 2, 1], + "y":[null, 2, 1, 4, 3] + } + + if (flipSourceSegments) + segment = flipSegments[sourceAxis][segment]; + + var findClearedLine = function(start, mult, anchorPos, dimension) { + return start + (mult * (( 1 - anchorPos) * dimension) + minStubLength); + //mx = so[0] == 0 ? startStubX + ((1 - sourceAnchor.x) * sourceInfo.width) + minStubLength : startStubX, + }, + + lineCalculators = { + oppositex : function() { + if (sourceEndpoint.elementId == targetEndpoint.elementId) { + var _y = startStubY + ((1 - sourceAnchor.y) * sourceInfo.height) + minStubLength; + return [ [ startStubX, _y ], [ endStubX, _y ]]; + } + else if (isXGreaterThanStubTimes2 && (segment == 1 || segment == 2)) { + return [[ midx, sy ], [ midx, ty ]]; + } + else { + return [[ startStubX, midy ], [endStubX, midy ]]; + } + }, + orthogonalx : function() { + if (segment == 1 || segment == 2) { + return [ [ endStubX, startStubY ]]; + } + else { + return [ [ startStubX, endStubY ]]; + } + }, + perpendicularx : function() { + var my = (ty + sy) / 2; + if ((segment == 1 && to[1] == 1) || (segment == 2 && to[1] == -1)) { + if (Math.abs(tx - sx) > minStubLength) + return [ [endStubX, startStubY ]]; + else + return [ [startStubX, startStubY ], [ startStubX, my ], [ endStubX, my ]]; + } + else if ((segment == 3 && to[1] == -1) || (segment == 4 && to[1] == 1)) { + return [ [ startStubX, my ], [ endStubX, my ]]; + } + else if ((segment == 3 && to[1] == 1) || (segment == 4 && to[1] == -1)) { + return [ [ startStubX, endStubY ]]; + } + else if ((segment == 1 && to[1] == -1) || (segment == 2 && to[1] == 1)) { + if (Math.abs(tx - sx) > minStubLength) + return [ [ midx, startStubY ], [ midx, endStubY ]]; + else + return [ [ startStubX, endStubY ]]; + } + }, + oppositey : function() { + if (sourceEndpoint.elementId == targetEndpoint.elementId) { + var _x = startStubX + ((1 - sourceAnchor.x) * sourceInfo.width) + minStubLength; + return [ [ _x, startStubY ], [ _x, endStubY ]]; + } + else if (isYGreaterThanStubTimes2 && (segment == 2 || segment == 3)) { + return [[ sx, midy ], [ tx, midy ]]; + } + else { + return [[ midx, startStubY ], [midx, endStubY ]]; + } + }, + orthogonaly : function() { + if (segment == 2 || segment == 3) { + return [ [ startStubX, endStubY ]]; + } + else { + return [ [ endStubX, startStubY ]]; + } + }, + perpendiculary : function() { + var mx = (tx + sx) / 2; + if ((segment == 2 && to[0] == -1) || (segment == 3 && to[0] == 1)) { + if (Math.abs(tx - sx) > minStubLength) + return [ [startStubX, endStubY ]]; + else + return [ [startStubX, midy ], [ endStubX, midy ]]; + } + else if ((segment == 1 && to[0] == -1) || (segment == 4 && to[0] == 1)) { + var mx = (tx + sx) / 2; + return [ [ mx, startStubY ], [ mx, endStubY ]]; + } + else if ((segment == 1 && to[0] == 1) || (segment == 4 && to[0] == -1)) { + return [ [ endStubX, startStubY ]]; + } + else if ((segment == 2 && to[0] == 1) || (segment == 3 && to[0] == -1)) { + if (Math.abs(ty - sy) > minStubLength) + return [ [ startStubX, midy ], [ endStubX, midy ]]; + else + return [ [ endStubX, startStubY ]]; + } + } + }; + + var p = lineCalculators[anchorOrientation + sourceAxis](); + if (p) { + for (var i = 0; i < p.length; i++) { + addSegment(p[i][0], p[i][1], sx, sy, tx, ty); + } + } + + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentProportions(sx, sy, tx, ty); + + // adjust the max values of the canvas if we have a value that is larger than what we previously set. + // + if (maxY > points[3]) points[3] = maxY + (lineWidth * 2); + if (maxX > points[2]) points[2] = maxX + (lineWidth * 2); + + return points; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location) { + return self.pointAlongPathFrom(location, 0); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location) { + return segments[findSegmentForLocation(location)["index"]][4]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance) { + var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segments[s.index][5], m = segments[s.index][4]; + var e = { + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + return e; + }; + }; + + // ********************************* END OF CONNECTOR TYPES ******************************************************************* + + // ********************************* ENDPOINT TYPES ******************************************************************* + + /** + * Class: Endpoints.Dot + * A round endpoint, with default radius 10 pixels. + */ + + /** + * Function: Constructor + * + * Parameters: + * + * radius - radius of the endpoint. defaults to 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + this.type = "Dot"; + var self = this; + params = params || {}; + this.radius = params.radius || 10; + this.defaultOffset = 0.5 * this.radius; + this.defaultInnerRadius = this.radius / 3; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var r = endpointStyle.radius || self.radius, + x = anchorPoint[0] - r, + y = anchorPoint[1] - r; + return [ x, y, r * 2, r * 2, r ]; + }; + }; + + /** + * Class: Endpoints.Rectangle + * A Rectangular Endpoint, with default size 20x20. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the endpoint. defaults to 20 pixels. + * height - height of the endpoint. defaults to 20 pixels. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + this.type = "Rectangle"; + var self = this; + params = params || {}; + this.width = params.width || 20; + this.height = params.height || 20; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height]; + }; + }; + + + var DOMElementEndpoint = function(params) { + jsPlumb.DOMElementComponent.apply(this, arguments); + var self = this; + + var displayElements = [ ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + }; + /** + * Class: Endpoints.Image + * Draws an image as the Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * src - location of the image to use. + */ + jsPlumb.Endpoints.Image = function(params) { + + this.type = "Image"; + DOMElementEndpoint.apply(this, arguments); + + var self = this, + initialized = false, + widthToUse = params.width, + heightToUse = params.height, + _onload = null, + _endpoint = params.endpoint; + + this.img = new Image(); + self.ready = false; + + this.img.onload = function() { + self.ready = true; + widthToUse = widthToUse || self.img.width; + heightToUse = heightToUse || self.img.height; + if (_onload) { + _onload(self); + } + }; + + _endpoint.setImage = function(img, onload) { + var s = img.constructor == String ? img : img.src; + _onload = onload; + self.img.src = img; + }; + + _endpoint.setImage(params.src || params.url, params.onload); + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + self.anchorPoint = anchorPoint; + if (self.ready) return [anchorPoint[0] - widthToUse / 2, anchorPoint[1] - heightToUse / 2, + widthToUse, heightToUse]; + else return [0,0,0,0]; + }; + + self.canvas = document.createElement("img"), initialized = false; + self.canvas.style["margin"] = 0; + self.canvas.style["padding"] = 0; + self.canvas.style["outline"] = 0; + self.canvas.style["position"] = "absolute"; + var clazz = params.cssClass ? " " + params.cssClass : ""; + self.canvas.className = jsPlumb.endpointClass + clazz; + if (widthToUse) self.canvas.setAttribute("width", widthToUse); + if (heightToUse) self.canvas.setAttribute("height", heightToUse); + jsPlumb.appendElement(self.canvas, params.parent); + self.attachListeners(self.canvas, self); + + var actuallyPaint = function(d, style, anchor) { + if (!initialized) { + self.canvas.setAttribute("src", self.img.src); + initialized = true; + } + var x = self.anchorPoint[0] - (widthToUse / 2), + y = self.anchorPoint[1] - (heightToUse / 2); + jsPlumb.sizeCanvas(self.canvas, x, y, widthToUse, heightToUse); + }; + + this.paint = function(d, style, anchor) { + if (self.ready) { + actuallyPaint(d, style, anchor); + } + else { + window.setTimeout(function() { + self.paint(d, style, anchor); + }, 200); + } + }; + }; + + /** + * Class: Endpoints.Blank + * An Endpoint that paints nothing (visible) on the screen. Supports cssClass and hoverClass parameters like all Endpoints. + */ + jsPlumb.Endpoints.Blank = function(params) { + var self = this; + this.type = "Blank"; + DOMElementEndpoint.apply(this, arguments); + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + return [anchorPoint[0], anchorPoint[1],10,0]; + }; + + self.canvas = document.createElement("div"); + self.canvas.style.display = "block"; + self.canvas.style.width = "1px"; + self.canvas.style.height = "1px"; + self.canvas.style.background = "transparent"; + self.canvas.style.position = "absolute"; + self.canvas.className = self._jsPlumb.endpointClass; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + }; + }; + + /** + * Class: Endpoints.Triangle + * A triangular Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the triangle's base. defaults to 55 pixels. + * height - height of the triangle from base to apex. defaults to 55 pixels. + */ + jsPlumb.Endpoints.Triangle = function(params) { + this.type = "Triangle"; + params = params || { }; + params.width = params.width || 55; + params.height = params.height || 55; + this.width = params.width; + this.height = params.height; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height ]; + }; + }; +// ********************************* END OF ENDPOINT TYPES ******************************************************************* + + +// ********************************* OVERLAY DEFINITIONS *********************************************************************** + + var AbstractOverlay = function(params) { + var visible = true, self = this; + this.isAppendedAtTopLevel = true; + this.component = params.component; + this.loc = params.location == null ? 0.5 : params.location; + this.endpointLoc = params.endpointLocation == null ? [ 0.5, 0.5] : params.endpointLocation; + this.setVisible = function(val) { + visible = val; + self.component.repaint(); + }; + this.isVisible = function() { return visible; }; + this.hide = function() { self.setVisible(false); }; + this.show = function() { self.setVisible(true); }; + + this.incrementLocation = function(amount) { + self.loc += amount; + self.component.repaint(); + }; + this.setLocation = function(l) { + self.loc = l; + self.component.repaint(); + }; + this.getLocation = function() { + return self.loc; + }; + }; + + + /** + * Class: Overlays.Arrow + * + * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length + * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction + * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line + * across the tail. + */ + /** + * Function: Constructor + * + * Parameters: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + this.type = "Arrow"; + AbstractOverlay.apply(this, arguments); + this.isAppendedAtTopLevel = false; + params = params || {}; + var self = this; + + this.length = params.length || 20; + this.width = params.width || 20; + this.id = params.id; + var direction = (params.direction || 1) < 0 ? -1 : 1, + paintStyle = params.paintStyle || { lineWidth:1 }, + // how far along the arrow the lines folding back in come to. default is 62.3%. + foldback = params.foldback || 0.623; + + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.cleanup = function() { }; // nothing to clean up for Arrows + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var hxy, mid, txy, tail, cxy; + if (connector.pointAlongPathFrom) { + + if (self.loc == 1) { + hxy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, -1); + txy = jsPlumb.util.pointOnLine(hxy, mid, self.length); + } + else if (self.loc == 0) { + txy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, 1); + hxy = jsPlumb.util.pointOnLine(txy, mid, self.length); + } + else { + hxy = connector.pointAlongPathFrom(self.loc, direction * self.length / 2), + mid = connector.pointOnPath(self.loc), + txy = jsPlumb.util.pointOnLine(hxy, mid, self.length); + } + + tail = jsPlumb.util.perpendicularLineTo(hxy, txy, self.width); + cxy = jsPlumb.util.pointOnLine(hxy, txy, foldback * self.length); + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x), + maxx = Math.max(hxy.x, tail[0].x, tail[1].x), + miny = Math.min(hxy.y, tail[0].y, tail[1].y), + maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + var d = { hxy:hxy, tail:tail, cxy:cxy }, + strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle, + fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle, + lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth; + + self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions); + + return [ minx, maxx, miny, maxy]; + } + else return [0,0,0,0]; + }; + }; + + /** + * Class: Overlays.PlainArrow + * + * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "PlainArrow"; + }; + + /** + * Class: Overlays.Diamond + * + * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40, + p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "Diamond"; + }; + + + + /** + * Class: Overlays.Label + * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb + * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter + * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it + * puts on the Label's 'style' attribute, so the end result is the same. + */ + /** + * Function: Constructor + * + * Parameters: + * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes + * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle. + * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * + */ + jsPlumb.Overlays.Label = function(params) { + this.type = "Label"; + jsPlumb.DOMElementComponent.apply(this, arguments); + AbstractOverlay.apply(this, arguments); + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.id = params.id; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var label = params.label || "", + self = this, + initialised = false, + div = document.createElement("div"), + labelText = null; + div.style["position"] = "absolute"; + + var clazz = params["_jsPlumb"].overlayClass + " " + + (self.labelStyle.cssClass ? self.labelStyle.cssClass : + params.cssClass ? params.cssClass : ""); + + div.className = clazz; + + jsPlumb.appendElement(div, params.component.parent); + jsPlumb.getId(div); + self.attachListeners(div, self); + self.canvas = div; + + //override setVisible + var osv = self.setVisible; + self.setVisible = function(state) { + osv(state); // call superclass + div.style.display = state ? "block" : "none"; + }; + + this.getElement = function() { + return div; + }; + + this.cleanup = function() { + if (div != null) jsPlumb.CurrentLibrary.removeElement(div); + }; + + /* + * Function: setLabel + * sets the label's, um, label. you would think i'd call this function + * 'setText', but you can pass either a Function or a String to this, so + * it makes more sense as 'setLabel'. + */ + this.setLabel = function(l) { + label = l; + labelText = null; + self.component.repaint(); + }; + + this.getLabel = function() { + return label; + }; + + this.paint = function(component, d, componentDimensions) { + if (!initialised) { + component.appendDisplayElement(div); + self.attachListeners(div, component); + initialised = true; + } + div.style.left = (componentDimensions[0] + d.minx) + "px"; + div.style.top = (componentDimensions[1] + d.miny) + "px"; + }; + + this.getTextDimensions = function() { + if (typeof label == "function") { + var lt = label(self); + div.innerHTML = lt.replace(/\r\n/g, "
"); + } + else { + if (labelText == null) { + labelText = label; + div.innerHTML = labelText.replace(/\r\n/g, "
"); + } + } + var de = jsPlumb.CurrentLibrary.getElementObject(div), + s = jsPlumb.CurrentLibrary.getSize(de); + return {width:s[0], height:s[1]}; + }; + + this.computeMaxSize = function(connector) { + var td = self.getTextDimensions(connector); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + + this.draw = function(component, currentConnectionPaintStyle, componentDimensions) { + var td = self.getTextDimensions(component); + if (td.width != null) { + var cxy = {x:0,y:0}; + if (component.pointOnPath) + cxy = component.pointOnPath(self.loc); // a connection + else { + var locToUse = self.loc.constructor == Array ? self.loc : self.endpointLoc; + cxy = { x:locToUse[0] * componentDimensions[2], + y:locToUse[1] * componentDimensions[3] }; + } + + minx = cxy.x - (td.width / 2), + miny = cxy.y - (td.height / 2); + + self.paint(component, { + minx:minx, + miny:miny, + td:td, + cxy:cxy + }, componentDimensions); + + return [minx, minx+td.width, miny, miny+td.height]; + } + else return [0,0,0,0]; + }; + + this.reattachListeners = function(connector) { + if (div) { + self.reattachListenersForElement(div, self, connector); + } + }; + }; + + // this is really just a test overlay, so its undocumented and doesnt take any parameters. but i was loth to delete it. + jsPlumb.Overlays.GuideLines = function() { + var self = this; + self.length = 50; + self.lineWidth = 5; + this.type = "GuideLines"; + AbstractOverlay.apply(this, arguments); + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var head = connector.pointAlongPathFrom(self.loc, self.length / 2), + mid = connector.pointOnPath(self.loc), + tail = jsPlumb.util.pointOnLine(head, mid, self.length), + tailLine = jsPlumb.util.perpendicularLineTo(head, tail, 40), + headLine = jsPlumb.util.perpendicularLineTo(tail, head, 20); + + self.paint(connector, [head, tail, tailLine, headLine], self.lineWidth, "red", null, connectorDimensions); + + return [Math.min(head.x, tail.x), Math.min(head.y, tail.y), Math.max(head.x, tail.x), Math.max(head.y,tail.y)]; + }; + + this.computeMaxSize = function() { return 50; }; + + this.cleanup = function() { }; // nothing to clean up for GuideLines + }; + + // ********************************* END OF OVERLAY DEFINITIONS *********************************************************************** + + // ********************************* OVERLAY CANVAS RENDERERS*********************************************************************** + + // ********************************* END OF OVERLAY CANVAS RENDERERS *********************************************************************** +})(); \ No newline at end of file diff --git a/archive/1.3.7/jsPlumb-renderers-canvas-1.3.7-RC1.js b/archive/1.3.7/jsPlumb-renderers-canvas-1.3.7-RC1.js new file mode 100644 index 000000000..24cb35321 --- /dev/null +++ b/archive/1.3.7/jsPlumb-renderers-canvas-1.3.7-RC1.js @@ -0,0 +1,456 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.7 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the HTML5 canvas renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + +// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS ******************************************************************* + + // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. + var _connectionBeingDragged = null, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, + _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }; + + /* + * Class:CanvasMouseAdapter + * Provides support for mouse events on canvases. + */ + var CanvasMouseAdapter = function() { + var self = this; + self.overlayPlacements = []; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + jsPlumb.EventGenerator.apply(this, arguments); + /** + * returns whether or not the given event is ojver a painted area of the canvas. + */ + this._over = function(e) { + var o = _getOffset(_getElementObject(self.canvas)), + pageXY = _pageXY(e), + x = pageXY[0] - o.left, y = pageXY[1] - o.top; + if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + return false; + }; + + var _mouseover = false, _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false, + _nullSafeHasClass = function(el, clazz) { + return el != null && _hasClass(el, clazz); + }; + this.mousemove = function(e) { + var pageXY = _pageXY(e), clientXY = _clientXY(e), + ee = document.elementFromPoint(clientXY[0], clientXY[1]), + eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); + var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); + if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.fire("mouseenter", self, e); + return true; + } + // TODO here there is a remote chance that the overlay the mouse moved onto + // is actually not an overlay for the current component. a more thorough check would + // be to ensure the overlay belonged to the current component. + else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { + _mouseover = false; + self.fire("mouseexit", self, e); + } + self.fire("mousemove", self, e); + }; + + this.click = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); + self.fire("mousedown", self, e); + } + }; + + this.mouseup = function(e) { + _mouseDown = false; + self.fire("mouseup", self, e); + }; + + this.contextmenu = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("contextmenu", self, e); + _mouseWasDown = false; + }; + }; + + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + params["_jsPlumb"].appendElement(canvas, params.parent); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + params["_jsPlumb"].getId(canvas, params.uuid); + if (params.tooltip) canvas.setAttribute("title", params.tooltip); + + return canvas; + }; + + var CanvasComponent = function(params) { + CanvasMouseAdapter.apply(this, arguments); + + var displayElements = [ ]; + this.getDisplayElements = function() { return displayElements; }; + this.appendDisplayElement = function(el) { displayElements.push(el); }; + } + + /** + * Class:CanvasConnector + * Superclass for Canvas Connector renderers. + */ + var CanvasConnector = jsPlumb.CanvasConnector = function(params) { + + CanvasComponent.apply(this, arguments); + + var _paintOneStyle = function(dim, aStyle) { + self.ctx.save(); + jsPlumb.extend(self.ctx, aStyle); + if (aStyle.gradient) { + var g = self.createGradient(dim, self.ctx); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + self.ctx.strokeStyle = g; + } + self._paint(dim); + self.ctx.restore(); + }; + + var self = this, + clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:params.tooltip + }); + self.ctx = self.canvas.getContext("2d"); + + self.appendDisplayElement(self.canvas); + + self.paint = function(dim, style) { + if (style != null) { + jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + _paintOneStyle(dim, outlineStyle); + } + _paintOneStyle(dim, style); + } + }; + }; + + /** + * Class:CanvasEndpoint + * Superclass for Canvas Endpoint renderers. + */ + var CanvasEndpoint = function(params) { + var self = this; + CanvasComponent.apply(this, arguments); + var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""), + canvasParams = { + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:self.tooltip + }; + self.canvas = _newCanvas(canvasParams); + self.ctx = self.canvas.getContext("2d"); + + self.appendDisplayElement(self.canvas); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + } + + self._paint.apply(this, arguments); + }; + }; + + jsPlumb.Endpoints.canvas.Dot = function(params) { + jsPlumb.Endpoints.Dot.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + var self = this, + parseValue = function(value) { + try { return parseInt(value); } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }, + calculateAdjustments = function(gradient) { + var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius; + gradient.offset && (offsetAdjustment = parseValue(gradient.offset)); + gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius)); + return [offsetAdjustment, innerRadius]; + }; + this._paint = function(d, style, anchor) { + if (style != null) { + var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + if (style.gradient) { + var adjustments = calculateAdjustments(style.gradient), + yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0], + xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0], + g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + ctx.beginPath(); + ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + } + }; + }; + + jsPlumb.Endpoints.canvas.Rectangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) { + + var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + + /* canvas gradient */ + if (style.gradient) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, d[2], d[3]); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + jsPlumb.Endpoints.canvas.Triangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Triangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) + { + var width = d[2], height = d[3], x = d[0], y = d[1], + ctx = self.canvas.getContext('2d'), + offsetX = 0, offsetY = 0, angle = 0, + orientation = anchor.getOrientation(self); + + if( orientation[0] == 1 ) { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = style.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + /* + * Canvas Image Endpoint: uses the default version, which creates an tag. + */ + jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image; + + /* + * Blank endpoint in all renderers is just the default Blank endpoint. + */ + jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank; + + /* + * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Bezier = function() { + var self = this; + jsPlumb.Connectors.Bezier.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO i doubt this handles the case that source and target are swapped. + this.createGradient = function(dim, ctx, swap) { + return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + /* + * Canvas straight line Connector. Draws a straight line onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Straight = function() { + var self = this; + jsPlumb.Connectors.Straight.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO this does not handle the case that src and target are swapped. + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + jsPlumb.Connectors.canvas.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + +// ********************************* END OF CANVAS RENDERERS ******************************************************************* + + jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label; + + /** + * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. + */ + var CanvasOverlay = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }; + + var AbstractCanvasArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + CanvasOverlay.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + var ctx = connector.ctx; + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(d.hxy.x, d.hxy.y); + ctx.lineTo(d.tail[0].x, d.tail[0].y); + ctx.lineTo(d.cxy.x, d.cxy.y); + ctx.lineTo(d.tail[1].x, d.tail[1].y); + ctx.lineTo(d.hxy.x, d.hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + }; + + jsPlumb.Overlays.canvas.Arrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.canvas.PlainArrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.canvas.Diamond = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.7/jsPlumb-renderers-svg-1.3.7-RC1.js b/archive/1.3.7/jsPlumb-renderers-svg-1.3.7-RC1.js new file mode 100644 index 000000000..af1ae0e6c --- /dev/null +++ b/archive/1.3.7/jsPlumb-renderers-svg-1.3.7-RC1.js @@ -0,0 +1,539 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.7 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the SVG renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +/** + * SVG support for jsPlumb. + * + * things to investigate: + * + * gradients: https://developer.mozilla.org/en/svg_in_html_introduction + * css:http://tutorials.jenkov.com/svg/svg-and-css.html + * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath + * pointer events: https://developer.mozilla.org/en/css/pointer-events + * + * IE9 hover jquery: http://forum.jquery.com/topic/1-6-2-broke-svg-hover-events + * + */ +;(function() { + + var svgAttributeMap = { + "joinstyle":"stroke-linejoin", + "stroke-linejoin":"stroke-linejoin", + "stroke-dashoffset":"stroke-dashoffset", + "stroke-linecap":"stroke-linecap" + }, + STROKE_DASHARRAY = "stroke-dasharray", + DASHSTYLE = "dashstyle", + LINEAR_GRADIENT = "linearGradient", + RADIAL_GRADIENT = "radialGradient", + FILL = "fill", + STOP = "stop", + STROKE = "stroke", + STROKE_WIDTH = "stroke-width", + STYLE = "style", + NONE = "none", + JSPLUMB_GRADIENT = "jsplumb_gradient_", + LINE_WIDTH = "lineWidth", + ns = { + svg:"http://www.w3.org/2000/svg", + xhtml:"http://www.w3.org/1999/xhtml" + }, + _attr = function(node, attributes) { + for (var i in attributes) + node.setAttribute(i, "" + attributes[i]); + }, + _node = function(name, attributes) { + var n = document.createElementNS(ns.svg, name); + attributes = attributes || {}; + attributes["version"] = "1.1"; + attributes["xmlns"] = ns.xhtml; + _attr(n, attributes); + return n; + }, + _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; }, + _clearGradient = function(parent) { + for (var i = 0; i < parent.childNodes.length; i++) { + if (parent.childNodes[i].tagName == LINEAR_GRADIENT || parent.childNodes[i].tagName == RADIAL_GRADIENT) + parent.removeChild(parent.childNodes[i]); + } + }, + _updateGradient = function(parent, node, style, dimensions, uiComponent) { + var id = JSPLUMB_GRADIENT + uiComponent._jsPlumb.idstamp(); + // first clear out any existing gradient + _clearGradient(parent); + // this checks for an 'offset' property in the gradient, and in the absence of it, assumes + // we want a linear gradient. if it's there, we create a radial gradient. + // it is possible that a more explicit means of defining the gradient type would be + // better. relying on 'offset' means that we can never have a radial gradient that uses + // some default offset, for instance. + if (!style.gradient.offset) { + var g = _node(LINEAR_GRADIENT, {id:id}); + parent.appendChild(g); + } + else { + var g = _node(RADIAL_GRADIENT, { + id:id + }); + parent.appendChild(g); + } + + // the svg radial gradient seems to treat stops in the reverse + // order to how canvas does it. so we want to keep all the maths the same, but + // iterate the actual style declarations in reverse order, if the x indexes are not in order. + for (var i = 0; i < style.gradient.stops.length; i++) { + // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of + // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments. + // so, not too concerned about leaving this in for now. + var styleToUse = i; + if (dimensions.length == 8) + styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i; + else + styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i; + var stopColor = jsPlumb.util.convertStyle(style.gradient.stops[styleToUse][1], true); + var s = _node(STOP, {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor}); + g.appendChild(s); + } + var applyGradientTo = style.strokeStyle ? STROKE : FILL; + node.setAttribute(STYLE, applyGradientTo + ":url(#" + id + ")"); + }, + _applyStyles = function(parent, node, style, dimensions, uiComponent) { + + if (style.gradient) { + _updateGradient(parent, node, style, dimensions, uiComponent); + } + else { + // make sure we clear any existing gradient + _clearGradient(parent); + node.setAttribute(STYLE, ""); + } + + node.setAttribute(FILL, style.fillStyle ? jsPlumb.util.convertStyle(style.fillStyle, true) : NONE); + node.setAttribute(STROKE, style.strokeStyle ? jsPlumb.util.convertStyle(style.strokeStyle, true) : NONE); + if (style.lineWidth) { + node.setAttribute(STROKE_WIDTH, style.lineWidth); + } + + // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like + // the syntax in VML but is actually kind of nasty: values are given in the pixel + // coordinate space, whereas in VML they are multiples of the width of the stroked + // line, which makes a lot more sense. for that reason, jsPlumb is supporting both + // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from + // VML, which will be the preferred method. the code below this converts a dashstyle + // attribute given in terms of stroke width into a pixel representation, by using the + // stroke's lineWidth. + if (style[DASHSTYLE] && style[LINE_WIDTH] && !style[STROKE_DASHARRAY]) { + var sep = style[DASHSTYLE].indexOf(",") == -1 ? " " : ",", + parts = style[DASHSTYLE].split(sep), + styleToUse = ""; + parts.forEach(function(p) { + styleToUse += (Math.floor(p * style.lineWidth) + sep); + }); + node.setAttribute(STROKE_DASHARRAY, styleToUse); + } + else if(style[STROKE_DASHARRAY]) { + node.setAttribute(STROKE_DASHARRAY, style[STROKE_DASHARRAY]); + } + + // extra attributes such as join type, dash offset. + for (var i in svgAttributeMap) { + if (style[i]) { + node.setAttribute(svgAttributeMap[i], style[i]); + } + } + }, + _decodeFont = function(f) { + var r = /([0-9].)(p[xt])\s(.*)/; + var bits = f.match(r); + return {size:bits[1] + bits[2], font:bits[3]}; + }, + _classManip = function(el, add, clazz) { + var classesToAddOrRemove = clazz.split(" "), + className = el.className, + curClasses = className.baseVal.split(" "); + + for (var i = 0; i < classesToAddOrRemove.length; i++) { + if (add) { + if (curClasses.indexOf(classesToAddOrRemove[i]) == -1) + curClasses.push(classesToAddOrRemove[i]); + } + else { + var idx = curClasses.indexOf(classesToAddOrRemove[i]); + if (idx != -1) + curClasses.splice(idx, 1); + } + } + + el.className.baseVal = curClasses.join(" "); + }, + _addClass = function(el, clazz) { + _classManip(el, true, clazz); + }, + _removeClass = function(el, clazz) { + _classManip(el, false, clazz); + }; + + /** + utility methods for other objects to use. + */ + jsPlumb.util.svg = { + addClass:_addClass, + removeClass:_removeClass + }; + + /* + * Base class for SVG components. + */ + //var SvgComponent = function(cssClass, originalArgs, pointerEventsSpec) { + var SvgComponent = function(params) { + var self = this, + pointerEventsSpec = params.pointerEventsSpec || "all"; + jsPlumb.jsPlumbUIComponent.apply(this, params.originalArgs); + self.canvas = null, self.path = null, self.svg = null; + + var clazz = params.cssClass + " " + (params.originalArgs[0].cssClass || ""), + svgParams = { + "style":"", + "width":0, + "height":0, + "pointer-events":pointerEventsSpec, + "position":"absolute" + }; + if (self.tooltip) svgParams["title"] = self.tooltip; + self.svg = _node("svg", svgParams); + if (params.useDivWrapper) { + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.sizeCanvas(self.canvas,0,0,1,1); + self.canvas.className = clazz; + if (self.tooltip) self.canvas.setAttribute("title", self.tooltip); + } + else { + _attr(self.svg, { "class":clazz }); + self.canvas = self.svg; + } + + params._jsPlumb.appendElement(self.canvas, params.originalArgs[0]["parent"]); + if (params.useDivWrapper) self.canvas.appendChild(self.svg); + + // TODO this displayElement stuff is common between all components, across all + // renderers. would be best moved to jsPlumbUIComponent. + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + this.paint = function(d, style, anchor) { + if (style != null) { + var x = d[0], y = d[1]; + if (params.useDivWrapper) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + x = 0, y = 0; + } + _attr(self.svg, { + "style":_pos([x, y, d[2], d[3]]), + "width": d[2], + "height": d[3] + }); + self._paint.apply(this, arguments); + } + }; + }; + + /* + * Base class for SVG connectors. + */ + var SvgConnector = jsPlumb.SvgConnector = function(params) { + var self = this; + SvgComponent.apply(this, [ { + cssClass:params["_jsPlumb"].connectorClass, + originalArgs:arguments, + pointerEventsSpec:"none", + tooltip:params.tooltip, + _jsPlumb:params["_jsPlumb"] + } ]); + this._paint = function(d, style) { + var p = self.getPath(d), a = { "d":p }, outlineStyle = null; + a["pointer-events"] = "all"; + + // outline style. actually means drawing an svg object underneath the main one. + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = jsPlumb.CurrentLibrary.extend({}, style); + outlineStyle.strokeStyle = jsPlumb.util.convertStyle(style.outlineColor); + outlineStyle.lineWidth = outlineStrokeWidth; + + if (self.bgPath == null) { + self.bgPath = _node("path", a); + self.svg.appendChild(self.bgPath); + self.attachListeners(self.bgPath, self); + } + else { + _attr(self.bgPath, a); + } + + _applyStyles(self.svg, self.bgPath, outlineStyle, d, self); + } + + + // test - see below + // a["clip-path"]= "url(#testClip)"; + + if (self.path == null) { + self.path = _node("path", a); + self.svg.appendChild(self.path); + self.attachListeners(self.path, self); + + /* + this is a test of a clip path. i'm looking into using one of these to animate a jsplumb connection. + you could do this by walking along the line, stepping along a little at a time, and setting the clip + path to extend as far as that point. + + self.clip = _node("clipPath", {id:"testClip", clipPathUnits:"objectBoundingBox"}); + self.svg.appendChild(self.clip); + self.clip.appendChild(_node("rect", { + x:"0",y:"0",width:"0.5",height:"1" + })); + */ + } + else { + _attr(self.path, a); + } + + _applyStyles(self.svg, self.path, style, d, self); + }; + + this.reattachListeners = function() { + if (self.bgPath) self.reattachListenersForElement(self.bgPath, self); + if (self.path) self.reattachListenersForElement(self.path, self); + }; + + }; + + /* + * SVG Bezier Connector + */ + jsPlumb.Connectors.svg.Bezier = function(params) { + jsPlumb.Connectors.Bezier.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { + var _p = "M " + d[4] + " " + d[5]; + _p += (" C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]); + return _p; + }; + }; + + /* + * SVG straight line Connector + */ + jsPlumb.Connectors.svg.Straight = function(params) { + jsPlumb.Connectors.Straight.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; }; + }; + + jsPlumb.Connectors.svg.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "M " + dimensions[4] + "," + dimensions[5]; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)]; + } + // finally draw a line to the end + p = p + " " + dimensions[6] + "," + dimensions[7]; + return p; + }; + }; + + /* + * Base class for SVG endpoints. + */ + var SvgEndpoint = function(params) { + var self = this; + SvgComponent.apply(this, [ { + cssClass:params["_jsPlumb"].endpointClass, + originalArgs:arguments, + pointerEventsSpec:"all", + useDivWrapper:true, + _jsPlumb:params["_jsPlumb"] + } ]); + this._paint = function(d, style) { + var s = jsPlumb.extend({}, style); + if (s.outlineColor) { + s.strokeWidth = s.outlineWidth; + s.strokeStyle = jsPlumb.util.convertStyle(s.outlineColor, true); + } + + if (self.node == null) { + self.node = self.makeNode(d, s); + self.svg.appendChild(self.node); + self.attachListeners(self.node, self); + } + _applyStyles(self.svg, self.node, s, d, self); + _pos(self.node, d); + }; + + this.reattachListeners = function() { + if (self.node) self.reattachListenersForElement(self.node, self); + }; + }; + + /* + * SVG Dot Endpoint + */ + jsPlumb.Endpoints.svg.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("circle", { + "cx" : d[2] / 2, + "cy" : d[3] / 2, + "r" : d[2] / 2 + }); + }; + }; + + /* + * SVG Rectangle Endpoint + */ + jsPlumb.Endpoints.svg.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("rect", { + "width":d[2], + "height":d[3] + }); + }; + }; + + /* + * SVG Image Endpoint is the default image endpoint. + */ + jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image; + /* + * Blank endpoint in svg renderer is the default Blank endpoint. + */ + jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank; + /* + * Label endpoint in svg renderer is the default Label endpoint. + */ + jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label; + + var AbstractSvgArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + this.isAppendedAtTopLevel = false; + var self = this, path =null; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + } + var clazz = originalArgs && (originalArgs.length == 1) ? (originalArgs[0].cssClass || "") : ""; + + _attr(path, { + "d" : makePath(d), + "class" : clazz, + stroke : strokeStyle ? strokeStyle : null, + fill : fillStyle ? fillStyle : null + }); + }; + var makePath = function(d) { + return "M" + d.hxy.x + "," + d.hxy.y + + " L" + d.tail[0].x + "," + d.tail[0].y + + " L" + d.cxy.x + "," + d.cxy.y + + " L" + d.tail[1].x + "," + d.tail[1].y + + " L" + d.hxy.x + "," + d.hxy.y; + }; + this.reattachListeners = function() { + if (path) self.reattachListenersForElement(path, self); + }; + }; + + jsPlumb.Overlays.svg.Arrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.svg.PlainArrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.svg.Diamond = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; + + // a test + jsPlumb.Overlays.svg.GuideLines = function() { + var path = null, self = this, path2 = null, p1_1, p1_2; + jsPlumb.Overlays.GuideLines.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + + p1_1 = _node("path"); + connector.svg.appendChild(p1_1); + self.attachListeners(p1_1, connector); + self.attachListeners(p1_1, self); + + p1_2 = _node("path"); + connector.svg.appendChild(p1_2); + self.attachListeners(p1_2, connector); + self.attachListeners(p1_2, self); + + } + + _attr(path, { + "d" : makePath(d[0], d[1]), + stroke : "red", + fill : null + }); + + _attr(p1_1, { + "d" : makePath(d[2][0], d[2][1]), + stroke : "blue", + fill : null + }); + + _attr(p1_2, { + "d" : makePath(d[3][0], d[3][1]), + stroke : "green", + fill : null + }); + }; + + var makePath = function(d1, d2) { + return "M " + d1.x + "," + d1.y + + " L" + d2.x + "," + d2.y; + }; + + }; +})(); \ No newline at end of file diff --git a/archive/1.3.7/jsPlumb-renderers-vml-1.3.7-RC1.js b/archive/1.3.7/jsPlumb-renderers-vml-1.3.7-RC1.js new file mode 100644 index 000000000..b0c12951d --- /dev/null +++ b/archive/1.3.7/jsPlumb-renderers-vml-1.3.7-RC1.js @@ -0,0 +1,435 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.7 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the VML renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + // http://ajaxian.com/archives/the-vml-changes-in-ie-8 + // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/ + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + + var vmlAttributeMap = { + "stroke-linejoin":"joinstyle", + "joinstyle":"joinstyle", + "endcap":"endcap", + "miterlimit":"miterlimit" + }; + + if (document.createStyleSheet) { + + // this is the style rule for IE7/6: it uses a CSS class, tidy. + document.createStyleSheet().addRule(".jsplumb_vml", "behavior:url(#default#VML);position:absolute;"); + + // these are for VML in IE8. you have to explicitly call out which elements + // you're going to expect to support VML! + // + document.createStyleSheet().addRule("jsplumb\\:textbox", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:oval", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:rect", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:stroke", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:shape", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:group", "behavior:url(#default#VML);position:absolute;"); + + // in this page it is also mentioned that IE requires the extra arg to the namespace + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either. + // var iev = document.documentMode; + //if (!iev || iev < 8) + document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); + //else + // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML"); + } + + jsPlumb.vml = {}; + + var scale = 1000, + + _groupMap = {}, + _getGroup = function(container, connectorClass) { + var id = jsPlumb.getId(container), + g = _groupMap[id]; + if(!g) { + g = _node("group", [0,0,scale, scale], {"class":connectorClass}); + //g.style.position=absolute; + //g["coordsize"] = "1000,1000"; + g.style.backgroundColor="red"; + _groupMap[id] = g; + jsPlumb.appendElement(g, container); // todo if this gets reinstated, remember to use the current jsplumb instance. + //document.body.appendChild(g); + } + return g; + }, + _atts = function(o, atts) { + for (var i in atts) { + // IE8 fix: setattribute does not work after an element has been added to the dom! + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + //o.setAttribute(i, atts[i]); + + o[i] = atts[i]; + } + }, + _node = function(name, d, atts) { + atts = atts || {}; + var o = document.createElement("jsplumb:" + name); + o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml"; + _pos(o, d); + _atts(o, atts); + return o; + }, + _pos = function(o,d) { + o.style.left = d[0] + "px"; + o.style.top = d[1] + "px"; + o.style.width= d[2] + "px"; + o.style.height= d[3] + "px"; + o.style.position = "absolute"; + }, + _conv = jsPlumb.vml.convertValue = function(v) { + return Math.floor(v * scale); + }, + // tests if the given style is "transparent" and then sets the appropriate opacity node to 0 if so, + // or 1 if not. TODO in the future, support variable opacity. + _maybeSetOpacity = function(styleToWrite, styleToCheck, type, component) { + if ("transparent" === styleToCheck) + component.setOpacity(type, "0.0"); + else + component.setOpacity(type, "1.0"); + }, + _applyStyles = function(node, style, component) { + var styleToWrite = {}; + if (style.strokeStyle) { + styleToWrite["stroked"] = "true"; + var strokeColor = jsPlumb.util.convertStyle(style.strokeStyle, true); + styleToWrite["strokecolor"] = strokeColor; + _maybeSetOpacity(styleToWrite, strokeColor, "stroke", component); + styleToWrite["strokeweight"] = style.lineWidth + "px"; + } + else styleToWrite["stroked"] = "false"; + + if (style.fillStyle) { + styleToWrite["filled"] = "true"; + var fillColor = jsPlumb.util.convertStyle(style.fillStyle, true); + styleToWrite["fillcolor"] = fillColor; + _maybeSetOpacity(styleToWrite, fillColor, "fill", component); + } + else styleToWrite["filled"] = "false"; + + if(style["dashstyle"]) { + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = style["dashstyle"]; + } + else if (style["stroke-dasharray"] && style["lineWidth"]) { + var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",", + parts = style["stroke-dasharray"].split(sep), + styleToUse = ""; + for(var i = 0; i < parts.length; i++) { + styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep); + } + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = styleToUse; + } + + _atts(node, styleToWrite); + }, + /* + * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. + */ + VmlComponent = function() { + var self = this; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.opacityNodes = { + "stroke":null, + "fill":null + }; + this.initOpacityNodes = function(vml) { + self.opacityNodes["stroke"] = _node("stroke", [0,0,1,1], {opacity:"0.0"}); + self.opacityNodes["fill"] = _node("fill", [0,0,1,1], {opacity:"0.0"}); + vml.appendChild(self.opacityNodes["stroke"]); + vml.appendChild(self.opacityNodes["fill"]); + }; + this.setOpacity = function(type, value) { + var node = self.opacityNodes[type]; + if (node) node["opacity"] = "" + value; + }; + var displayElements = [ ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el, doNotAppendToCanvas) { + if (!doNotAppendToCanvas) self.canvas.parentNode.appendChild(el); + displayElements.push(el); + }; + }, + /* + * Base class for Vml connectors. extends VmlComponent. + */ + VmlConnector = jsPlumb.VmlConnector = function(params) { + var self = this; + self.strokeNode = null; + self.canvas = null; + VmlComponent.apply(this, arguments); + var clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : ""); + this.paint = function(d, style, anchor) { + if (style != null) { + var path = self.getPath(d), p = { "path":path }; + + //* + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle : jsPlumb.util.convertStyle(style.outlineColor), + lineWidth : outlineStrokeWidth + }; + for (var aa in vmlAttributeMap) outlineStyle[aa] = style[aa]; + + if (self.bgCanvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.bgCanvas = _node("shape", d, p); + params["_jsPlumb"].appendElement(self.bgCanvas, params.parent); + _pos(self.bgCanvas, d); + self.appendDisplayElement(self.bgCanvas, true); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.bgCanvas, d); + _atts(self.bgCanvas, p); + } + + _applyStyles(self.bgCanvas, outlineStyle, self); + } + //*/ + + if (self.canvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + if (self.tooltip) p["label"] = self.tooltip; + self.canvas = _node("shape", d, p); + + //var group = _getGroup(params.parent); // test of append everything to a group + //group.appendChild(self.canvas); // sort of works but not exactly; + params["_jsPlumb"].appendElement(self.canvas, params.parent); //before introduction of groups + + self.appendDisplayElement(self.canvas, true); + + self.attachListeners(self.canvas, self); + + self.initOpacityNodes(self.canvas, ["stroke"]); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.canvas, d); + _atts(self.canvas, p); + } + + _applyStyles(self.canvas, style, self); + } + }; + + //self.appendDisplayElement(self.canvas); + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + }, + /* + * + * Base class for Vml Endpoints. extends VmlComponent. + * + */ + VmlEndpoint = function(params) { + VmlComponent.apply(this, arguments); + var vml = null, self = this, opacityStrokeNode = null, opacityFillNode = null; + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + + //var group = _getGroup(params.parent); + //group.appendChild(self.canvas); + params["_jsPlumb"].appendElement(self.canvas, params.parent); + + if (self.tooltip) self.canvas.setAttribute("label", self.tooltip); + + this.paint = function(d, style, anchor) { + var p = { }; + + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (vml == null) { + p["class"] = jsPlumb.endpointClass; + vml = self.getVml([0,0, d[2], d[3]], p, anchor); + self.canvas.appendChild(vml); + self.attachListeners(vml, self); + + self.appendDisplayElement(vml, true); + self.appendDisplayElement(self.canvas, true); + + self.initOpacityNodes(vml, ["fill"]); + } + else { + //p["coordsize"] = "1,1";//(d[2] * scale) + "," + (d[3] * scale); again, unsure. + _pos(vml, [0,0, d[2], d[3]]); + _atts(vml, p); + } + + _applyStyles(vml, style, self); + }; + + this.reattachListeners = function() { + if (vml) self.reattachListenersForElement(vml, self); + }; + }; + + jsPlumb.Connectors.vml.Bezier = function() { + jsPlumb.Connectors.Bezier.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Straight = function() { + jsPlumb.Connectors.Straight.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Flowchart = function() { + jsPlumb.Connectors.Flowchart.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l"; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]); + } + // finally draw a line to the end + p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e"; + return p; + }; + }; + + jsPlumb.Endpoints.vml.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("oval", d, atts); }; + }; + + jsPlumb.Endpoints.vml.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("rect", d, atts); }; + }; + + /* + * VML Image Endpoint is the same as the default image endpoint. + */ + jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image; + + /** + * placeholder for Blank endpoint in vml renderer. + */ + jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank; + + /** + * VML Label renderer. uses the default label renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label; + + var AbstractVmlArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + VmlComponent.apply(this, arguments); + var self = this, path = null; + self.canvas = null; + var getPath = function(d, connectorDimensions) { + return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) + + " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + + " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + + " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + + " x e"; + }; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) { + var p = {}; + if (strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] = jsPlumb.util.convertStyle(strokeStyle, true); + } + if (lineWidth) p["strokeweight"] = lineWidth + "px"; + if (fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = fillStyle; + } + var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + w = Math.abs(xmax - xmin), + h = Math.abs(ymax - ymin), + dim = [xmin, ymin, w, h]; + + // for VML, we create overlays using shapes that have the same dimensions and + // coordsize as their connector - overlays calculate themselves relative to the + // connector (it's how it's been done since the original canvas implementation, because + // for canvas that makes sense). + p["path"] = getPath(d, connectorDimensions); + p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale); + + dim[0] = connectorDimensions[0]; + dim[1] = connectorDimensions[1]; + dim[2] = connectorDimensions[2]; + dim[3] = connectorDimensions[3]; + + if (self.canvas == null) { + //p["class"] = jsPlumb.overlayClass; // TODO currentInstance? + self.canvas = _node("shape", dim, p); + connector.appendDisplayElement(self.canvas); + self.attachListeners(self.canvas, connector); + } + else { + _pos(self.canvas, dim); + _atts(self.canvas, p); + } + }; + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + }; + + jsPlumb.Overlays.vml.Arrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.vml.PlainArrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.vml.Diamond = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.7/mootools.jsPlumb-1.3.7-RC1.js b/archive/1.3.7/mootools.jsPlumb-1.3.7-RC1.js new file mode 100644 index 000000000..0266e0867 --- /dev/null +++ b/archive/1.3.7/mootools.jsPlumb-1.3.7-RC1.js @@ -0,0 +1,459 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.7 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the MooTools adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function(now) { + this.parent(now); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event, originalEvent) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr, originalEvent); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, _scope, value) { + var l = list[_scope]; + if (!l) { + l = []; + list[_scope] = l; + } + l.push(value); + }; + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + var _getElementObject = function(el) { + return $(el); + }, + + _removeNonPermanentDroppables = function(drag) { + /* + // remove non-permanent droppables from all arrays + var dbs = _droppables[drag.scope], d = []; + if (dbs) { + var d = []; + for (var i=0; i < dbs.length; i++) { + var isPermanent = dbs[i].getAttribute("_isPermanentDroppable"); + if (isPermanent === "true") d.push(dbs[i]); + } + dbs.splice(0, dbs.length); + _droppables[drag.scope] = d; + } + d = []; + // clear out transient droppables from the drag itself + for(var i = 0; i < drag.droppables.length; i++) { + var isPermanent = drag.droppables[i].getAttribute("_isPermanentDroppable"); + if (isPermanent === "true") d.push(drag.droppables[i]); + } + drag.droppables.splice(0, drag.droppables.length); // release old ones + drag.droppables = d; + //*/ + }; + + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el) + try { + if (el.className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.addClass(el, clazz); + } + else el.addClass(clazz); + } + catch (e) { + // SVGAnimatedString not supported; no problem. + el.addClass(clazz); + } + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + m.start(properties); + }, + + appendElement : function(child, parent) { + _getElementObject(parent).grab(child); + }, + + bind : function(el, event, callback) { + el = _getElementObject(el); + el.addEvent(event, callback); + }, + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.event.clientX, eventObject.event.clientY]; + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el), + drags = _draggablesById[id]; + return drags[0].scope; + }, + + getDropEvent : function(args) { + return args[2]; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getDOMElement : function(el) { + // MooTools just decorates the DOM elements. so we have either an ID or an Element here. + return typeof(el) == "String" ? document.getElementById(el) : el; + }, + + getElementObject : _getElementObject, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + getPageXY : function(eventObject) { + return [eventObject.event.pageX, eventObject.event.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).getParent(); + }, + + getScrollLeft : function(el) { + return null; + }, + + getScrollTop : function(el) { + return null; + }, + + getSelector : function(spec) { + return $$(spec); + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e != null ? e.tagName : null; + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[0], + p = jsPlumb.CurrentLibrary.getElementObject(ui).getPosition(); + return { left:p.x, top:p.y }; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options, isPlumbedComponent) { + var id = jsPlumb.getId(el); + var drag = _draggablesById[id]; + if (!drag) { + var originalZIndex = 0, + originalCursor = null, + dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + + options['onStart'] = jsPlumb.wrap(options['onStart'], function() { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + drag.originalZIndex = originalZIndex; + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + + _removeNonPermanentDroppables(drag); + }); + + // DROPPABLES - only relevant if this is a plumbed component, ie. not just the result of the user making some DOM element + // draggable. this is the only library adapter that has to care about this parameter. + var scope = "" + (options["scope"] || jsPlumb.Defaults.Scope), + filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }, + droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + + if (isPlumbedComponent) { + + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr, event) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop', event); + } + }; + } + else + options["droppables"] = []; + + drag = new Drag.Move(el, options); + drag.scope = scope; + drag.originalZIndex = originalZIndex; + _add(_draggablesById, el.get("id"), drag); + // again, only keep a record of this for scope stuff if this is a plumbed component (an endpoint) + if (isPlumbedComponent) { + _add(_draggablesByScope, scope, drag); + } + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + initDroppable : function(el, options, isPlumbedComponent, isPermanent) { + var scope = options["scope"]; + _add(_droppables, scope, el); + var id = jsPlumb.getId(el); + + el.setAttribute("_isPermanentDroppable", isPermanent); // we use this to clear out droppables on drag complete. + _droppableOptions[id] = options; + _droppableScopesById[id] = scope; + var filterFunc = function(entry) { return entry.element != el; }, + draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + isAlreadyDraggable : function(el) { + return _draggablesById[jsPlumb.getId(el)] != null; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + return (typeof Drag != undefined && typeof Drag.Move != undefined); + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el.className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.removeClass(el, clazz); + } + else el.removeClass(clazz); + } + catch (e) { + // SVGAnimatedString not supported; no problem. + el.removeClass(clazz); + } + }, + + removeElement : function(element, parent) { + var el = _getElementObject(element); + if (el) el.dispose(); // ?? + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + setDragScope : function(el, scope) { + var drag = _draggablesById[el.get("id")]; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + drag[0].droppables = droppables; + }, + + setOffset : function(el, o) { + _getElementObject(el).setPosition({x:o.left, y:o.top}); + }, + + stopDrag : function() { + for (var i in _draggablesById) { + for (var j = 0; j < _draggablesById[i].length; j++) { + var d = _draggablesById[i][j]; + d.stop(); + if (d.originalZIndex != 0) + d.element.setStyle("z-index", d.originalZIndex); + } + } + }, + + /** + * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself. + * the other libraries do not. yui, in fact, cannot even pass an original event. we have to pull out stuff + * from the originalEvent to put in an options object for YUI. + * @param el + * @param event + * @param originalEvent + */ + trigger : function(el, event, originalEvent) { + originalEvent.stopPropagation(); + _getElementObject(el).fireEvent(event, originalEvent); + }, + + unbind : function(el, event, callback) { + el = _getElementObject(el); + el.removeEvent(event, callback); + } + }; + + window.addEvent('domready', jsPlumb.init); +})(); diff --git a/build/1.3.7/tests/qunit-all.html b/archive/1.3.7/tests/qunit-all.html similarity index 100% rename from build/1.3.7/tests/qunit-all.html rename to archive/1.3.7/tests/qunit-all.html diff --git a/build/1.3.7/tests/qunit-canvas-jquery-instance.html b/archive/1.3.7/tests/qunit-canvas-jquery-instance.html similarity index 100% rename from build/1.3.7/tests/qunit-canvas-jquery-instance.html rename to archive/1.3.7/tests/qunit-canvas-jquery-instance.html diff --git a/build/1.3.7/tests/qunit-canvas-jquery.html b/archive/1.3.7/tests/qunit-canvas-jquery.html similarity index 100% rename from build/1.3.7/tests/qunit-canvas-jquery.html rename to archive/1.3.7/tests/qunit-canvas-jquery.html diff --git a/build/1.3.7/tests/qunit-canvas-mootools.html b/archive/1.3.7/tests/qunit-canvas-mootools.html similarity index 100% rename from build/1.3.7/tests/qunit-canvas-mootools.html rename to archive/1.3.7/tests/qunit-canvas-mootools.html diff --git a/build/1.3.7/tests/qunit-svg-jquery-instance.html b/archive/1.3.7/tests/qunit-svg-jquery-instance.html similarity index 100% rename from build/1.3.7/tests/qunit-svg-jquery-instance.html rename to archive/1.3.7/tests/qunit-svg-jquery-instance.html diff --git a/build/1.3.7/tests/qunit-svg-jquery.html b/archive/1.3.7/tests/qunit-svg-jquery.html similarity index 100% rename from build/1.3.7/tests/qunit-svg-jquery.html rename to archive/1.3.7/tests/qunit-svg-jquery.html diff --git a/build/1.3.7/tests/qunit-vml-jquery-instance.html b/archive/1.3.7/tests/qunit-vml-jquery-instance.html similarity index 100% rename from build/1.3.7/tests/qunit-vml-jquery-instance.html rename to archive/1.3.7/tests/qunit-vml-jquery-instance.html diff --git a/build/1.3.7/tests/qunit-vml-jquery.html b/archive/1.3.7/tests/qunit-vml-jquery.html similarity index 100% rename from build/1.3.7/tests/qunit-vml-jquery.html rename to archive/1.3.7/tests/qunit-vml-jquery.html diff --git a/build/1.3.7/tests/qunit.css b/archive/1.3.7/tests/qunit.css similarity index 100% rename from build/1.3.7/tests/qunit.css rename to archive/1.3.7/tests/qunit.css diff --git a/archive/1.3.7/yui.jsPlumb-1.3.7-RC1.js b/archive/1.3.7/yui.jsPlumb-1.3.7-RC1.js new file mode 100644 index 000000000..574af281b --- /dev/null +++ b/archive/1.3.7/yui.jsPlumb-1.3.7-RC1.js @@ -0,0 +1,376 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.7 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the YUI3 adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +/** + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + */ +(function() { + + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function( v, b, s ) { + for( var i = +b || 0, l = this.length; i < l; i++ ) { + if( this[i]===v || s && this[i]==v ) { return i; } + } + return -1; + }; + } + + var Y; + + YUI().use('node', 'dd', 'anim', 'node-event-simulate', function(_Y) { + Y = _Y; + Y.on("domready", function() { jsPlumb.init(); }); + }); + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }, + ddEvents = [ "drag:mouseDown", "drag:afterMouseDown", "drag:mouseup", + "drag:align", "drag:removeHandle", "drag:addHandle", "drag:removeInvalid", "drag:addInvalid", + "drag:start", "drag:end", "drag:drag", "drag:over", "drag:enter", + "drag:exit", "drag:drophit", "drag:dropmiss", "drop:over", "drop:enter", "drop:exit", "drop:hit" + ], + animEvents = [ "tween" ], + /** + * helper function to curry callbacks for some element. + */ + _wrapper = function(fn) { + return function() { + try { + return fn.apply(this, arguments); + } + catch (e) { } + }; + }, + /** + * extracts options from the given options object, leaving out event handlers. + */ + _getDDOptions = function(options) { + var o = {}; + for (var i in options) if (ddEvents.indexOf(i) == -1) o[i] = options[i]; + return o; + }, + /** + * attaches all event handlers found in options to the given dragdrop object, and registering + * the given el as the element of interest. + */ + _attachListeners = function(dd, options, eventList) { + for (var ev in options) { + if (eventList.indexOf(ev) != -1) { + var w = _wrapper(options[ev]); + dd.on(ev, w); + } + } + }, + _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}, + _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }, + _lastDragObject = null, + _extend = function(o1, o2) { + for (var i in o2) + o1[i] = o2[i]; + return o1; + }, + _getAttribute = function(el, attributeId) { + return el.getAttribute(attributeId); + }, + _getElementObject = function(el) { + if (el == null) return null; + var eee = null; + eee = typeof el == 'string' ? Y.one('#' + el) : el._node ? el : Y.one(el); + return eee; + }; + + jsPlumb.CurrentLibrary = { + + addClass : function(el, clazz) { + jsPlumb.CurrentLibrary.getElementObject(el).addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + var o = _extend({node:el, to:properties}, options); + var id = _getAttribute(el, "id"); + o["tween"] = jsPlumb.wrap(properties["tween"], function() { + jsPlumb.repaint(id); + }); + var a = new Y.Anim(o); + _attachListeners(a, o, animEvents); + a.run(); + }, + + appendElement : function(child, parent) { + _getElementObject (parent).append(child); + }, + + /** + * event binding wrapper. + */ + bind : function(el, event, callback) { + _getElementObject(el).on(event, callback); + }, + + dragEvents : { + "start":"drag:start", "stop":"drag:end", "drag":"drag:drag", "step":"step", + "over":"drop:enter", "out":"drop:exit", "drop":"drop:hit" + }, + + extend : _extend, + + getAttribute : _getAttribute, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + // this is a workaround for the unfortunate fact that in YUI3, the 'drop:exit' event does + // not contain a reference to the drag that just exited. single-threaded js to the + // rescue: we'll just keep it for ourselves. + if (eventArgs[0].drag) _lastDragObject = eventArgs[0].drag.el; + return _lastDragObject; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + return dd.scope; + }, + + getDropEvent : function(args) { + return args[0]; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getDOMElement : function(el) { + if (typeof(el) == "String") + return document.getElementById(el); + else if (el._node) + return el._node; + else return el; + }, + + getElementObject : _getElementObject, + + getOffset : function(el) { + var o = Y.DOM.getXY(el._node); + return {left:o[0], top:o[1]}; + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).get("parentNode"); + }, + + getScrollLeft : function(el) { + return 0; + }, + + getScrollTop : function(el) { + return 0; + }, + + getSelector : function(spec) { + var s = Y.all(spec); + return s && s ._nodes ? s._nodes : []; + }, + + getSize : function(el) { + return [ el._node.offsetWidth, el._node.offsetHeight ]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e != null && e._node != null ? e._node.tagName : null; + }, + + getUIPosition : function(args) { + var n = args[0].currentTarget.el._node, + o = Y.DOM.getXY(n); + return {left:o[0], top:o[1]}; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options) { + var _opts = _getDDOptions(options); + var id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drag(_opts); + dd.el = el; + + var scope = options['scope'] || jsPlumb.Defaults.Scope; + dd.scope = scope; + + _draggablesById[id] = dd; + _add(_draggablesByScope, scope, dd); + + _attachListeners(dd, options, ddEvents); + }, + + initDroppable : function(el, options) { + var _opts = _getDDOptions(options); + var id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drop(_opts); + + _droppableOptions[id] = options; + + options = _extend({}, options); + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _droppableScopesById[id] = scope; + + options["drop:enter"] = jsPlumb.wrap(options["drop:enter"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, true); + }, true); + options["drop:exit"] = jsPlumb.wrap(options["drop:exit"], function(e) { + _checkHover(el, false); + }); + options["drop:hit"] = jsPlumb.wrap(options["drop:hit"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, false); + }, true); + + _attachListeners(dd, options, ddEvents); + }, + + isAlreadyDraggable : function(el) { + el = _getElementObject(el); + return el.hasClass("yui3-dd-draggable"); + }, + + isDragSupported : function(el) { return true; }, + isDropSupported : function(el) { return true; }, + removeClass : function(el, clazz) { + jsPlumb.CurrentLibrary.getElementObject(el).removeClass(clazz); + }, + removeElement : function(el) { _getElementObject(el).remove(); }, + + setAttribute : function(el, attributeName, attributeValue) { + el.setAttribute(attributeName, attributeValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + if (dd) dd.set("lock", !draggable); + }, + + setDragScope : function(el, scope) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + if (dd) dd.scope = scope; + }, + + setOffset : function(el, o) { + el = _getElementObject(el); + el.set("top", o.top); + el.set("left", o.left); + }, + + stopDrag : function() { + Y.DD.DDM.stopDrag(); + }, + + trigger : function(el, event, originalEvent) { + originalEvent.stopPropagation(); + _getElementObject(el).simulate(event, { + pageX:originalEvent.pageX, + pageY:originalEvent.pageY, + clientX:originalEvent.clientX, + clientY:originalEvent.clientY + }); + }, + + /** + * event unbinding wrapper. + */ + unbind : function(el, event, callback) { + _getElementObject(el).detach(event, callback); + } + }; +})(); \ No newline at end of file diff --git a/build/1.3.8/demo/apidocs/files/jquery-jsPlumb-1-3-7-all-js.html b/archive/1.3.8/demo/apidocs/files/jquery-jsPlumb-1-3-7-all-js.html similarity index 100% rename from build/1.3.8/demo/apidocs/files/jquery-jsPlumb-1-3-7-all-js.html rename to archive/1.3.8/demo/apidocs/files/jquery-jsPlumb-1-3-7-all-js.html diff --git a/build/1.3.8/demo/apidocs/files/jquery-jsPlumb-1-3-8-all-js.html b/archive/1.3.8/demo/apidocs/files/jquery-jsPlumb-1-3-8-all-js.html similarity index 100% rename from build/1.3.8/demo/apidocs/files/jquery-jsPlumb-1-3-8-all-js.html rename to archive/1.3.8/demo/apidocs/files/jquery-jsPlumb-1-3-8-all-js.html diff --git a/build/1.3.8/demo/apidocs/index.html b/archive/1.3.8/demo/apidocs/index.html similarity index 100% rename from build/1.3.8/demo/apidocs/index.html rename to archive/1.3.8/demo/apidocs/index.html diff --git a/build/1.3.8/demo/apidocs/index/Classes.html b/archive/1.3.8/demo/apidocs/index/Classes.html similarity index 100% rename from build/1.3.8/demo/apidocs/index/Classes.html rename to archive/1.3.8/demo/apidocs/index/Classes.html diff --git a/build/1.3.8/demo/apidocs/index/Files.html b/archive/1.3.8/demo/apidocs/index/Files.html similarity index 100% rename from build/1.3.8/demo/apidocs/index/Files.html rename to archive/1.3.8/demo/apidocs/index/Files.html diff --git a/build/1.3.8/demo/apidocs/index/Functions.html b/archive/1.3.8/demo/apidocs/index/Functions.html similarity index 100% rename from build/1.3.8/demo/apidocs/index/Functions.html rename to archive/1.3.8/demo/apidocs/index/Functions.html diff --git a/build/1.3.8/demo/apidocs/index/Functions2.html b/archive/1.3.8/demo/apidocs/index/Functions2.html similarity index 100% rename from build/1.3.8/demo/apidocs/index/Functions2.html rename to archive/1.3.8/demo/apidocs/index/Functions2.html diff --git a/build/1.3.8/demo/apidocs/index/General.html b/archive/1.3.8/demo/apidocs/index/General.html similarity index 100% rename from build/1.3.8/demo/apidocs/index/General.html rename to archive/1.3.8/demo/apidocs/index/General.html diff --git a/build/1.3.8/demo/apidocs/index/General2.html b/archive/1.3.8/demo/apidocs/index/General2.html similarity index 100% rename from build/1.3.8/demo/apidocs/index/General2.html rename to archive/1.3.8/demo/apidocs/index/General2.html diff --git a/build/1.3.8/demo/apidocs/index/Properties.html b/archive/1.3.8/demo/apidocs/index/Properties.html similarity index 100% rename from build/1.3.8/demo/apidocs/index/Properties.html rename to archive/1.3.8/demo/apidocs/index/Properties.html diff --git a/build/1.3.8/demo/apidocs/javascript/main.js b/archive/1.3.8/demo/apidocs/javascript/main.js similarity index 100% rename from build/1.3.8/demo/apidocs/javascript/main.js rename to archive/1.3.8/demo/apidocs/javascript/main.js diff --git a/build/1.3.8/demo/apidocs/javascript/prettify.js b/archive/1.3.8/demo/apidocs/javascript/prettify.js similarity index 100% rename from build/1.3.8/demo/apidocs/javascript/prettify.js rename to archive/1.3.8/demo/apidocs/javascript/prettify.js diff --git a/build/1.3.8/demo/apidocs/javascript/searchdata.js b/archive/1.3.8/demo/apidocs/javascript/searchdata.js similarity index 100% rename from build/1.3.8/demo/apidocs/javascript/searchdata.js rename to archive/1.3.8/demo/apidocs/javascript/searchdata.js diff --git a/build/1.3.8/demo/apidocs/search/ClassesC.html b/archive/1.3.8/demo/apidocs/search/ClassesC.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/ClassesC.html rename to archive/1.3.8/demo/apidocs/search/ClassesC.html diff --git a/build/1.3.8/demo/apidocs/search/ClassesE.html b/archive/1.3.8/demo/apidocs/search/ClassesE.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/ClassesE.html rename to archive/1.3.8/demo/apidocs/search/ClassesE.html diff --git a/build/1.3.8/demo/apidocs/search/ClassesO.html b/archive/1.3.8/demo/apidocs/search/ClassesO.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/ClassesO.html rename to archive/1.3.8/demo/apidocs/search/ClassesO.html diff --git a/build/1.3.8/demo/apidocs/search/FilesJ.html b/archive/1.3.8/demo/apidocs/search/FilesJ.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/FilesJ.html rename to archive/1.3.8/demo/apidocs/search/FilesJ.html diff --git a/build/1.3.8/demo/apidocs/search/FunctionsA.html b/archive/1.3.8/demo/apidocs/search/FunctionsA.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/FunctionsA.html rename to archive/1.3.8/demo/apidocs/search/FunctionsA.html diff --git a/build/1.3.8/demo/apidocs/search/FunctionsB.html b/archive/1.3.8/demo/apidocs/search/FunctionsB.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/FunctionsB.html rename to archive/1.3.8/demo/apidocs/search/FunctionsB.html diff --git a/build/1.3.8/demo/apidocs/search/FunctionsC.html b/archive/1.3.8/demo/apidocs/search/FunctionsC.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/FunctionsC.html rename to archive/1.3.8/demo/apidocs/search/FunctionsC.html diff --git a/build/1.3.8/demo/apidocs/search/FunctionsD.html b/archive/1.3.8/demo/apidocs/search/FunctionsD.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/FunctionsD.html rename to archive/1.3.8/demo/apidocs/search/FunctionsD.html diff --git a/build/1.3.8/demo/apidocs/search/FunctionsE.html b/archive/1.3.8/demo/apidocs/search/FunctionsE.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/FunctionsE.html rename to archive/1.3.8/demo/apidocs/search/FunctionsE.html diff --git a/build/1.3.8/demo/apidocs/search/FunctionsG.html b/archive/1.3.8/demo/apidocs/search/FunctionsG.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/FunctionsG.html rename to archive/1.3.8/demo/apidocs/search/FunctionsG.html diff --git a/build/1.3.8/demo/apidocs/search/FunctionsH.html b/archive/1.3.8/demo/apidocs/search/FunctionsH.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/FunctionsH.html rename to archive/1.3.8/demo/apidocs/search/FunctionsH.html diff --git a/build/1.3.8/demo/apidocs/search/FunctionsI.html b/archive/1.3.8/demo/apidocs/search/FunctionsI.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/FunctionsI.html rename to archive/1.3.8/demo/apidocs/search/FunctionsI.html diff --git a/build/1.3.8/demo/apidocs/search/FunctionsM.html b/archive/1.3.8/demo/apidocs/search/FunctionsM.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/FunctionsM.html rename to archive/1.3.8/demo/apidocs/search/FunctionsM.html diff --git a/build/1.3.8/demo/apidocs/search/FunctionsP.html b/archive/1.3.8/demo/apidocs/search/FunctionsP.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/FunctionsP.html rename to archive/1.3.8/demo/apidocs/search/FunctionsP.html diff --git a/build/1.3.8/demo/apidocs/search/FunctionsR.html b/archive/1.3.8/demo/apidocs/search/FunctionsR.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/FunctionsR.html rename to archive/1.3.8/demo/apidocs/search/FunctionsR.html diff --git a/build/1.3.8/demo/apidocs/search/FunctionsS.html b/archive/1.3.8/demo/apidocs/search/FunctionsS.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/FunctionsS.html rename to archive/1.3.8/demo/apidocs/search/FunctionsS.html diff --git a/build/1.3.8/demo/apidocs/search/FunctionsT.html b/archive/1.3.8/demo/apidocs/search/FunctionsT.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/FunctionsT.html rename to archive/1.3.8/demo/apidocs/search/FunctionsT.html diff --git a/build/1.3.8/demo/apidocs/search/FunctionsU.html b/archive/1.3.8/demo/apidocs/search/FunctionsU.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/FunctionsU.html rename to archive/1.3.8/demo/apidocs/search/FunctionsU.html diff --git a/build/1.3.8/demo/apidocs/search/GeneralA.html b/archive/1.3.8/demo/apidocs/search/GeneralA.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/GeneralA.html rename to archive/1.3.8/demo/apidocs/search/GeneralA.html diff --git a/build/1.3.8/demo/apidocs/search/GeneralB.html b/archive/1.3.8/demo/apidocs/search/GeneralB.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/GeneralB.html rename to archive/1.3.8/demo/apidocs/search/GeneralB.html diff --git a/build/1.3.8/demo/apidocs/search/GeneralC.html b/archive/1.3.8/demo/apidocs/search/GeneralC.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/GeneralC.html rename to archive/1.3.8/demo/apidocs/search/GeneralC.html diff --git a/build/1.3.8/demo/apidocs/search/GeneralD.html b/archive/1.3.8/demo/apidocs/search/GeneralD.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/GeneralD.html rename to archive/1.3.8/demo/apidocs/search/GeneralD.html diff --git a/build/1.3.8/demo/apidocs/search/GeneralE.html b/archive/1.3.8/demo/apidocs/search/GeneralE.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/GeneralE.html rename to archive/1.3.8/demo/apidocs/search/GeneralE.html diff --git a/build/1.3.8/demo/apidocs/search/GeneralF.html b/archive/1.3.8/demo/apidocs/search/GeneralF.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/GeneralF.html rename to archive/1.3.8/demo/apidocs/search/GeneralF.html diff --git a/build/1.3.8/demo/apidocs/search/GeneralG.html b/archive/1.3.8/demo/apidocs/search/GeneralG.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/GeneralG.html rename to archive/1.3.8/demo/apidocs/search/GeneralG.html diff --git a/build/1.3.8/demo/apidocs/search/GeneralH.html b/archive/1.3.8/demo/apidocs/search/GeneralH.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/GeneralH.html rename to archive/1.3.8/demo/apidocs/search/GeneralH.html diff --git a/build/1.3.8/demo/apidocs/search/GeneralI.html b/archive/1.3.8/demo/apidocs/search/GeneralI.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/GeneralI.html rename to archive/1.3.8/demo/apidocs/search/GeneralI.html diff --git a/build/1.3.8/demo/apidocs/search/GeneralJ.html b/archive/1.3.8/demo/apidocs/search/GeneralJ.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/GeneralJ.html rename to archive/1.3.8/demo/apidocs/search/GeneralJ.html diff --git a/build/1.3.8/demo/apidocs/search/GeneralM.html b/archive/1.3.8/demo/apidocs/search/GeneralM.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/GeneralM.html rename to archive/1.3.8/demo/apidocs/search/GeneralM.html diff --git a/build/1.3.8/demo/apidocs/search/GeneralO.html b/archive/1.3.8/demo/apidocs/search/GeneralO.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/GeneralO.html rename to archive/1.3.8/demo/apidocs/search/GeneralO.html diff --git a/build/1.3.8/demo/apidocs/search/GeneralP.html b/archive/1.3.8/demo/apidocs/search/GeneralP.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/GeneralP.html rename to archive/1.3.8/demo/apidocs/search/GeneralP.html diff --git a/build/1.3.8/demo/apidocs/search/GeneralR.html b/archive/1.3.8/demo/apidocs/search/GeneralR.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/GeneralR.html rename to archive/1.3.8/demo/apidocs/search/GeneralR.html diff --git a/build/1.3.8/demo/apidocs/search/GeneralS.html b/archive/1.3.8/demo/apidocs/search/GeneralS.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/GeneralS.html rename to archive/1.3.8/demo/apidocs/search/GeneralS.html diff --git a/build/1.3.8/demo/apidocs/search/GeneralT.html b/archive/1.3.8/demo/apidocs/search/GeneralT.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/GeneralT.html rename to archive/1.3.8/demo/apidocs/search/GeneralT.html diff --git a/build/1.3.8/demo/apidocs/search/GeneralU.html b/archive/1.3.8/demo/apidocs/search/GeneralU.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/GeneralU.html rename to archive/1.3.8/demo/apidocs/search/GeneralU.html diff --git a/build/1.3.8/demo/apidocs/search/NoResults.html b/archive/1.3.8/demo/apidocs/search/NoResults.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/NoResults.html rename to archive/1.3.8/demo/apidocs/search/NoResults.html diff --git a/build/1.3.8/demo/apidocs/search/PropertiesC.html b/archive/1.3.8/demo/apidocs/search/PropertiesC.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/PropertiesC.html rename to archive/1.3.8/demo/apidocs/search/PropertiesC.html diff --git a/build/1.3.8/demo/apidocs/search/PropertiesD.html b/archive/1.3.8/demo/apidocs/search/PropertiesD.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/PropertiesD.html rename to archive/1.3.8/demo/apidocs/search/PropertiesD.html diff --git a/build/1.3.8/demo/apidocs/search/PropertiesE.html b/archive/1.3.8/demo/apidocs/search/PropertiesE.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/PropertiesE.html rename to archive/1.3.8/demo/apidocs/search/PropertiesE.html diff --git a/build/1.3.8/demo/apidocs/search/PropertiesO.html b/archive/1.3.8/demo/apidocs/search/PropertiesO.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/PropertiesO.html rename to archive/1.3.8/demo/apidocs/search/PropertiesO.html diff --git a/build/1.3.8/demo/apidocs/search/PropertiesS.html b/archive/1.3.8/demo/apidocs/search/PropertiesS.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/PropertiesS.html rename to archive/1.3.8/demo/apidocs/search/PropertiesS.html diff --git a/build/1.3.8/demo/apidocs/search/PropertiesT.html b/archive/1.3.8/demo/apidocs/search/PropertiesT.html similarity index 100% rename from build/1.3.8/demo/apidocs/search/PropertiesT.html rename to archive/1.3.8/demo/apidocs/search/PropertiesT.html diff --git a/build/1.3.8/demo/apidocs/styles/main.css b/archive/1.3.8/demo/apidocs/styles/main.css similarity index 100% rename from build/1.3.8/demo/apidocs/styles/main.css rename to archive/1.3.8/demo/apidocs/styles/main.css diff --git a/build/1.3.8/demo/css/anchorDemo.css b/archive/1.3.8/demo/css/anchorDemo.css similarity index 100% rename from build/1.3.8/demo/css/anchorDemo.css rename to archive/1.3.8/demo/css/anchorDemo.css diff --git a/build/1.3.8/demo/css/chartDemo.css b/archive/1.3.8/demo/css/chartDemo.css similarity index 100% rename from build/1.3.8/demo/css/chartDemo.css rename to archive/1.3.8/demo/css/chartDemo.css diff --git a/build/1.3.8/demo/css/demo.css b/archive/1.3.8/demo/css/demo.css similarity index 100% rename from build/1.3.8/demo/css/demo.css rename to archive/1.3.8/demo/css/demo.css diff --git a/build/1.3.8/demo/css/dragAnimDemo.css b/archive/1.3.8/demo/css/dragAnimDemo.css similarity index 100% rename from build/1.3.8/demo/css/dragAnimDemo.css rename to archive/1.3.8/demo/css/dragAnimDemo.css diff --git a/build/1.3.8/demo/css/draggableConnectorsDemo.css b/archive/1.3.8/demo/css/draggableConnectorsDemo.css similarity index 100% rename from build/1.3.8/demo/css/draggableConnectorsDemo.css rename to archive/1.3.8/demo/css/draggableConnectorsDemo.css diff --git a/build/1.3.8/demo/css/dynamicAnchorsDemo.css b/archive/1.3.8/demo/css/dynamicAnchorsDemo.css similarity index 100% rename from build/1.3.8/demo/css/dynamicAnchorsDemo.css rename to archive/1.3.8/demo/css/dynamicAnchorsDemo.css diff --git a/build/1.3.8/demo/css/flowchartDemo.css b/archive/1.3.8/demo/css/flowchartDemo.css similarity index 100% rename from build/1.3.8/demo/css/flowchartDemo.css rename to archive/1.3.8/demo/css/flowchartDemo.css diff --git a/build/1.3.8/demo/css/jsPlumbDemo.css b/archive/1.3.8/demo/css/jsPlumbDemo.css similarity index 100% rename from build/1.3.8/demo/css/jsPlumbDemo.css rename to archive/1.3.8/demo/css/jsPlumbDemo.css diff --git a/build/1.3.8/demo/css/makeTargetDemo.css b/archive/1.3.8/demo/css/makeTargetDemo.css similarity index 100% rename from build/1.3.8/demo/css/makeTargetDemo.css rename to archive/1.3.8/demo/css/makeTargetDemo.css diff --git a/build/1.3.8/demo/css/multipleJsPlumbDemo.css b/archive/1.3.8/demo/css/multipleJsPlumbDemo.css similarity index 100% rename from build/1.3.8/demo/css/multipleJsPlumbDemo.css rename to archive/1.3.8/demo/css/multipleJsPlumbDemo.css diff --git a/build/1.3.8/demo/css/selectDemo.css b/archive/1.3.8/demo/css/selectDemo.css similarity index 100% rename from build/1.3.8/demo/css/selectDemo.css rename to archive/1.3.8/demo/css/selectDemo.css diff --git a/build/1.3.8/demo/css/stateMachineDemo.css b/archive/1.3.8/demo/css/stateMachineDemo.css similarity index 100% rename from build/1.3.8/demo/css/stateMachineDemo.css rename to archive/1.3.8/demo/css/stateMachineDemo.css diff --git a/build/1.3.8/demo/doc/archive/1.2.6/content.html b/archive/1.3.8/demo/doc/archive/1.2.6/content.html similarity index 100% rename from build/1.3.8/demo/doc/archive/1.2.6/content.html rename to archive/1.3.8/demo/doc/archive/1.2.6/content.html diff --git a/build/1.3.8/demo/doc/archive/1.2.6/index.html b/archive/1.3.8/demo/doc/archive/1.2.6/index.html similarity index 100% rename from build/1.3.8/demo/doc/archive/1.2.6/index.html rename to archive/1.3.8/demo/doc/archive/1.2.6/index.html diff --git a/build/1.3.8/demo/doc/archive/1.2.6/usage.html b/archive/1.3.8/demo/doc/archive/1.2.6/usage.html similarity index 100% rename from build/1.3.8/demo/doc/archive/1.2.6/usage.html rename to archive/1.3.8/demo/doc/archive/1.2.6/usage.html diff --git a/build/1.3.8/demo/doc/archive/1.3.2/content.html b/archive/1.3.8/demo/doc/archive/1.3.2/content.html similarity index 100% rename from build/1.3.8/demo/doc/archive/1.3.2/content.html rename to archive/1.3.8/demo/doc/archive/1.3.2/content.html diff --git a/build/1.3.8/demo/doc/archive/1.3.2/index.html b/archive/1.3.8/demo/doc/archive/1.3.2/index.html similarity index 100% rename from build/1.3.8/demo/doc/archive/1.3.2/index.html rename to archive/1.3.8/demo/doc/archive/1.3.2/index.html diff --git a/build/1.3.8/demo/doc/archive/1.3.2/usage.html b/archive/1.3.8/demo/doc/archive/1.3.2/usage.html similarity index 100% rename from build/1.3.8/demo/doc/archive/1.3.2/usage.html rename to archive/1.3.8/demo/doc/archive/1.3.2/usage.html diff --git a/build/1.3.8/demo/doc/archive/1.3.3/content.html b/archive/1.3.8/demo/doc/archive/1.3.3/content.html similarity index 100% rename from build/1.3.8/demo/doc/archive/1.3.3/content.html rename to archive/1.3.8/demo/doc/archive/1.3.3/content.html diff --git a/build/1.3.8/demo/doc/archive/1.3.3/index.html b/archive/1.3.8/demo/doc/archive/1.3.3/index.html similarity index 100% rename from build/1.3.8/demo/doc/archive/1.3.3/index.html rename to archive/1.3.8/demo/doc/archive/1.3.3/index.html diff --git a/build/1.3.8/demo/doc/archive/1.3.3/jsPlumbDoc.css b/archive/1.3.8/demo/doc/archive/1.3.3/jsPlumbDoc.css similarity index 100% rename from build/1.3.8/demo/doc/archive/1.3.3/jsPlumbDoc.css rename to archive/1.3.8/demo/doc/archive/1.3.3/jsPlumbDoc.css diff --git a/build/1.3.8/demo/doc/archive/1.3.3/usage.html b/archive/1.3.8/demo/doc/archive/1.3.3/usage.html similarity index 100% rename from build/1.3.8/demo/doc/archive/1.3.3/usage.html rename to archive/1.3.8/demo/doc/archive/1.3.3/usage.html diff --git a/build/1.3.8/demo/doc/archive/1.3.4/content.html b/archive/1.3.8/demo/doc/archive/1.3.4/content.html similarity index 100% rename from build/1.3.8/demo/doc/archive/1.3.4/content.html rename to archive/1.3.8/demo/doc/archive/1.3.4/content.html diff --git a/build/1.3.8/demo/doc/archive/1.3.4/index.html b/archive/1.3.8/demo/doc/archive/1.3.4/index.html similarity index 100% rename from build/1.3.8/demo/doc/archive/1.3.4/index.html rename to archive/1.3.8/demo/doc/archive/1.3.4/index.html diff --git a/build/1.3.8/demo/doc/archive/1.3.4/jsPlumbDoc.css b/archive/1.3.8/demo/doc/archive/1.3.4/jsPlumbDoc.css similarity index 100% rename from build/1.3.8/demo/doc/archive/1.3.4/jsPlumbDoc.css rename to archive/1.3.8/demo/doc/archive/1.3.4/jsPlumbDoc.css diff --git a/build/1.3.8/demo/doc/archive/1.3.4/usage.html b/archive/1.3.8/demo/doc/archive/1.3.4/usage.html similarity index 100% rename from build/1.3.8/demo/doc/archive/1.3.4/usage.html rename to archive/1.3.8/demo/doc/archive/1.3.4/usage.html diff --git a/build/1.3.8/demo/doc/archive/1.3.5/content.html b/archive/1.3.8/demo/doc/archive/1.3.5/content.html similarity index 100% rename from build/1.3.8/demo/doc/archive/1.3.5/content.html rename to archive/1.3.8/demo/doc/archive/1.3.5/content.html diff --git a/build/1.3.8/demo/doc/archive/1.3.5/index.html b/archive/1.3.8/demo/doc/archive/1.3.5/index.html similarity index 100% rename from build/1.3.8/demo/doc/archive/1.3.5/index.html rename to archive/1.3.8/demo/doc/archive/1.3.5/index.html diff --git a/build/1.3.8/demo/doc/archive/1.3.5/jsPlumbDoc.css b/archive/1.3.8/demo/doc/archive/1.3.5/jsPlumbDoc.css similarity index 100% rename from build/1.3.8/demo/doc/archive/1.3.5/jsPlumbDoc.css rename to archive/1.3.8/demo/doc/archive/1.3.5/jsPlumbDoc.css diff --git a/build/1.3.8/demo/doc/archive/1.3.5/usage.html b/archive/1.3.8/demo/doc/archive/1.3.5/usage.html similarity index 100% rename from build/1.3.8/demo/doc/archive/1.3.5/usage.html rename to archive/1.3.8/demo/doc/archive/1.3.5/usage.html diff --git a/build/1.3.8/demo/doc/archive/1.3.6/content.html b/archive/1.3.8/demo/doc/archive/1.3.6/content.html similarity index 100% rename from build/1.3.8/demo/doc/archive/1.3.6/content.html rename to archive/1.3.8/demo/doc/archive/1.3.6/content.html diff --git a/build/1.3.8/demo/doc/archive/1.3.6/index.html b/archive/1.3.8/demo/doc/archive/1.3.6/index.html similarity index 100% rename from build/1.3.8/demo/doc/archive/1.3.6/index.html rename to archive/1.3.8/demo/doc/archive/1.3.6/index.html diff --git a/build/1.3.8/demo/doc/archive/1.3.6/jsPlumbDoc.css b/archive/1.3.8/demo/doc/archive/1.3.6/jsPlumbDoc.css similarity index 100% rename from build/1.3.8/demo/doc/archive/1.3.6/jsPlumbDoc.css rename to archive/1.3.8/demo/doc/archive/1.3.6/jsPlumbDoc.css diff --git a/build/1.3.8/demo/doc/archive/1.3.6/usage.html b/archive/1.3.8/demo/doc/archive/1.3.6/usage.html similarity index 100% rename from build/1.3.8/demo/doc/archive/1.3.6/usage.html rename to archive/1.3.8/demo/doc/archive/1.3.6/usage.html diff --git a/build/1.3.8/demo/doc/archive/1.3.7/content.html b/archive/1.3.8/demo/doc/archive/1.3.7/content.html similarity index 100% rename from build/1.3.8/demo/doc/archive/1.3.7/content.html rename to archive/1.3.8/demo/doc/archive/1.3.7/content.html diff --git a/build/1.3.8/demo/doc/archive/1.3.7/index.html b/archive/1.3.8/demo/doc/archive/1.3.7/index.html similarity index 100% rename from build/1.3.8/demo/doc/archive/1.3.7/index.html rename to archive/1.3.8/demo/doc/archive/1.3.7/index.html diff --git a/build/1.3.8/demo/doc/archive/1.3.7/jsPlumbDoc.css b/archive/1.3.8/demo/doc/archive/1.3.7/jsPlumbDoc.css similarity index 100% rename from build/1.3.8/demo/doc/archive/1.3.7/jsPlumbDoc.css rename to archive/1.3.8/demo/doc/archive/1.3.7/jsPlumbDoc.css diff --git a/build/1.3.8/demo/doc/archive/1.3.7/usage.html b/archive/1.3.8/demo/doc/archive/1.3.7/usage.html similarity index 100% rename from build/1.3.8/demo/doc/archive/1.3.7/usage.html rename to archive/1.3.8/demo/doc/archive/1.3.7/usage.html diff --git a/build/1.3.8/demo/doc/content.html b/archive/1.3.8/demo/doc/content.html similarity index 100% rename from build/1.3.8/demo/doc/content.html rename to archive/1.3.8/demo/doc/content.html diff --git a/build/1.3.8/demo/doc/index.html b/archive/1.3.8/demo/doc/index.html similarity index 100% rename from build/1.3.8/demo/doc/index.html rename to archive/1.3.8/demo/doc/index.html diff --git a/build/1.3.8/demo/doc/jsPlumbDoc.css b/archive/1.3.8/demo/doc/jsPlumbDoc.css similarity index 100% rename from build/1.3.8/demo/doc/jsPlumbDoc.css rename to archive/1.3.8/demo/doc/jsPlumbDoc.css diff --git a/build/1.3.8/demo/doc/usage.html b/archive/1.3.8/demo/doc/usage.html similarity index 100% rename from build/1.3.8/demo/doc/usage.html rename to archive/1.3.8/demo/doc/usage.html diff --git a/build/1.3.8/demo/img/bigdot.jpg b/archive/1.3.8/demo/img/bigdot.jpg similarity index 100% rename from build/1.3.8/demo/img/bigdot.jpg rename to archive/1.3.8/demo/img/bigdot.jpg diff --git a/build/1.3.8/demo/img/bigdot.png b/archive/1.3.8/demo/img/bigdot.png similarity index 100% rename from build/1.3.8/demo/img/bigdot.png rename to archive/1.3.8/demo/img/bigdot.png diff --git a/build/1.3.8/demo/img/bigdot.xcf b/archive/1.3.8/demo/img/bigdot.xcf similarity index 100% rename from build/1.3.8/demo/img/bigdot.xcf rename to archive/1.3.8/demo/img/bigdot.xcf diff --git a/build/1.3.8/demo/img/dragging_1.jpg b/archive/1.3.8/demo/img/dragging_1.jpg similarity index 100% rename from build/1.3.8/demo/img/dragging_1.jpg rename to archive/1.3.8/demo/img/dragging_1.jpg diff --git a/build/1.3.8/demo/img/dragging_2.jpg b/archive/1.3.8/demo/img/dragging_2.jpg similarity index 100% rename from build/1.3.8/demo/img/dragging_2.jpg rename to archive/1.3.8/demo/img/dragging_2.jpg diff --git a/build/1.3.8/demo/img/dragging_3.jpg b/archive/1.3.8/demo/img/dragging_3.jpg similarity index 100% rename from build/1.3.8/demo/img/dragging_3.jpg rename to archive/1.3.8/demo/img/dragging_3.jpg diff --git a/build/1.3.8/demo/img/dynamicAnchorBg.jpg b/archive/1.3.8/demo/img/dynamicAnchorBg.jpg similarity index 100% rename from build/1.3.8/demo/img/dynamicAnchorBg.jpg rename to archive/1.3.8/demo/img/dynamicAnchorBg.jpg diff --git a/build/1.3.8/demo/img/endpointTest1.png b/archive/1.3.8/demo/img/endpointTest1.png similarity index 100% rename from build/1.3.8/demo/img/endpointTest1.png rename to archive/1.3.8/demo/img/endpointTest1.png diff --git a/build/1.3.8/demo/img/endpointTest1.xcf b/archive/1.3.8/demo/img/endpointTest1.xcf similarity index 100% rename from build/1.3.8/demo/img/endpointTest1.xcf rename to archive/1.3.8/demo/img/endpointTest1.xcf diff --git a/build/1.3.8/demo/img/index-bg.gif b/archive/1.3.8/demo/img/index-bg.gif similarity index 100% rename from build/1.3.8/demo/img/index-bg.gif rename to archive/1.3.8/demo/img/index-bg.gif diff --git a/build/1.3.8/demo/img/issue4_final.jpg b/archive/1.3.8/demo/img/issue4_final.jpg similarity index 100% rename from build/1.3.8/demo/img/issue4_final.jpg rename to archive/1.3.8/demo/img/issue4_final.jpg diff --git a/build/1.3.8/demo/img/littledot.png b/archive/1.3.8/demo/img/littledot.png similarity index 100% rename from build/1.3.8/demo/img/littledot.png rename to archive/1.3.8/demo/img/littledot.png diff --git a/build/1.3.8/demo/img/littledot.xcf b/archive/1.3.8/demo/img/littledot.xcf similarity index 100% rename from build/1.3.8/demo/img/littledot.xcf rename to archive/1.3.8/demo/img/littledot.xcf diff --git a/build/1.3.8/demo/img/pattern.jpg b/archive/1.3.8/demo/img/pattern.jpg similarity index 100% rename from build/1.3.8/demo/img/pattern.jpg rename to archive/1.3.8/demo/img/pattern.jpg diff --git a/build/1.3.8/demo/img/swappedAnchors.jpg b/archive/1.3.8/demo/img/swappedAnchors.jpg similarity index 100% rename from build/1.3.8/demo/img/swappedAnchors.jpg rename to archive/1.3.8/demo/img/swappedAnchors.jpg diff --git a/build/1.3.8/demo/index.html b/archive/1.3.8/demo/index.html similarity index 100% rename from build/1.3.8/demo/index.html rename to archive/1.3.8/demo/index.html diff --git a/build/1.3.8/demo/jquery/anchorDemo.html b/archive/1.3.8/demo/jquery/anchorDemo.html similarity index 100% rename from build/1.3.8/demo/jquery/anchorDemo.html rename to archive/1.3.8/demo/jquery/anchorDemo.html diff --git a/build/1.3.8/demo/jquery/chartDemo.html b/archive/1.3.8/demo/jquery/chartDemo.html similarity index 100% rename from build/1.3.8/demo/jquery/chartDemo.html rename to archive/1.3.8/demo/jquery/chartDemo.html diff --git a/build/1.3.8/demo/jquery/demo.html b/archive/1.3.8/demo/jquery/demo.html similarity index 100% rename from build/1.3.8/demo/jquery/demo.html rename to archive/1.3.8/demo/jquery/demo.html diff --git a/build/1.3.8/demo/jquery/dragAnimDemo.html b/archive/1.3.8/demo/jquery/dragAnimDemo.html similarity index 100% rename from build/1.3.8/demo/jquery/dragAnimDemo.html rename to archive/1.3.8/demo/jquery/dragAnimDemo.html diff --git a/build/1.3.8/demo/jquery/draggableConnectorsDemo.html b/archive/1.3.8/demo/jquery/draggableConnectorsDemo.html similarity index 100% rename from build/1.3.8/demo/jquery/draggableConnectorsDemo.html rename to archive/1.3.8/demo/jquery/draggableConnectorsDemo.html diff --git a/build/1.3.8/demo/jquery/dynamicAnchorsDemo.html b/archive/1.3.8/demo/jquery/dynamicAnchorsDemo.html similarity index 100% rename from build/1.3.8/demo/jquery/dynamicAnchorsDemo.html rename to archive/1.3.8/demo/jquery/dynamicAnchorsDemo.html diff --git a/build/1.3.8/demo/jquery/flowchartConnectorsDemo.html b/archive/1.3.8/demo/jquery/flowchartConnectorsDemo.html similarity index 100% rename from build/1.3.8/demo/jquery/flowchartConnectorsDemo.html rename to archive/1.3.8/demo/jquery/flowchartConnectorsDemo.html diff --git a/build/1.3.8/demo/jquery/loadTest.html b/archive/1.3.8/demo/jquery/loadTest.html similarity index 100% rename from build/1.3.8/demo/jquery/loadTest.html rename to archive/1.3.8/demo/jquery/loadTest.html diff --git a/build/1.3.8/demo/jquery/makeSourceDemo.html b/archive/1.3.8/demo/jquery/makeSourceDemo.html similarity index 100% rename from build/1.3.8/demo/jquery/makeSourceDemo.html rename to archive/1.3.8/demo/jquery/makeSourceDemo.html diff --git a/build/1.3.8/demo/jquery/makeTargetDemo.html b/archive/1.3.8/demo/jquery/makeTargetDemo.html similarity index 100% rename from build/1.3.8/demo/jquery/makeTargetDemo.html rename to archive/1.3.8/demo/jquery/makeTargetDemo.html diff --git a/build/1.3.8/demo/jquery/stateMachineDemo.html b/archive/1.3.8/demo/jquery/stateMachineDemo.html similarity index 100% rename from build/1.3.8/demo/jquery/stateMachineDemo.html rename to archive/1.3.8/demo/jquery/stateMachineDemo.html diff --git a/build/1.3.8/demo/js/anchorDemo.js b/archive/1.3.8/demo/js/anchorDemo.js similarity index 100% rename from build/1.3.8/demo/js/anchorDemo.js rename to archive/1.3.8/demo/js/anchorDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.0/anchorDemo.js b/archive/1.3.8/demo/js/archive/1.3.0/anchorDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.0/anchorDemo.js rename to archive/1.3.8/demo/js/archive/1.3.0/anchorDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.0/chartDemo.js b/archive/1.3.8/demo/js/archive/1.3.0/chartDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.0/chartDemo.js rename to archive/1.3.8/demo/js/archive/1.3.0/chartDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.0/demo-helper-jquery.js b/archive/1.3.8/demo/js/archive/1.3.0/demo-helper-jquery.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.0/demo-helper-jquery.js rename to archive/1.3.8/demo/js/archive/1.3.0/demo-helper-jquery.js diff --git a/build/1.3.8/demo/js/archive/1.3.0/demo-helper-mootools.js b/archive/1.3.8/demo/js/archive/1.3.0/demo-helper-mootools.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.0/demo-helper-mootools.js rename to archive/1.3.8/demo/js/archive/1.3.0/demo-helper-mootools.js diff --git a/build/1.3.8/demo/js/archive/1.3.0/demo-helper-yui3.js b/archive/1.3.8/demo/js/archive/1.3.0/demo-helper-yui3.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.0/demo-helper-yui3.js rename to archive/1.3.8/demo/js/archive/1.3.0/demo-helper-yui3.js diff --git a/build/1.3.8/demo/js/archive/1.3.0/demo.js b/archive/1.3.8/demo/js/archive/1.3.0/demo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.0/demo.js rename to archive/1.3.8/demo/js/archive/1.3.0/demo.js diff --git a/build/1.3.8/demo/js/archive/1.3.0/dragAnimDemo-jquery.js b/archive/1.3.8/demo/js/archive/1.3.0/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.0/dragAnimDemo-jquery.js rename to archive/1.3.8/demo/js/archive/1.3.0/dragAnimDemo-jquery.js diff --git a/build/1.3.8/demo/js/archive/1.3.0/dragAnimDemo-mootools.js b/archive/1.3.8/demo/js/archive/1.3.0/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.0/dragAnimDemo-mootools.js rename to archive/1.3.8/demo/js/archive/1.3.0/dragAnimDemo-mootools.js diff --git a/build/1.3.8/demo/js/archive/1.3.0/dragAnimDemo-yui3.js b/archive/1.3.8/demo/js/archive/1.3.0/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.0/dragAnimDemo-yui3.js rename to archive/1.3.8/demo/js/archive/1.3.0/dragAnimDemo-yui3.js diff --git a/build/1.3.8/demo/js/archive/1.3.0/dragAnimDemo.js b/archive/1.3.8/demo/js/archive/1.3.0/dragAnimDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.0/dragAnimDemo.js rename to archive/1.3.8/demo/js/archive/1.3.0/dragAnimDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js b/archive/1.3.8/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js rename to archive/1.3.8/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.8/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js b/archive/1.3.8/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js rename to archive/1.3.8/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.8/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js b/archive/1.3.8/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js rename to archive/1.3.8/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.8/demo/js/archive/1.3.0/draggableConnectorsDemo.js b/archive/1.3.8/demo/js/archive/1.3.0/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.0/draggableConnectorsDemo.js rename to archive/1.3.8/demo/js/archive/1.3.0/draggableConnectorsDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.0/dynamicAnchorsDemo.js b/archive/1.3.8/demo/js/archive/1.3.0/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.0/dynamicAnchorsDemo.js rename to archive/1.3.8/demo/js/archive/1.3.0/dynamicAnchorsDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.0/flowchartConnectorsDemo.js b/archive/1.3.8/demo/js/archive/1.3.0/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.0/flowchartConnectorsDemo.js rename to archive/1.3.8/demo/js/archive/1.3.0/flowchartConnectorsDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.1/anchorDemo.js b/archive/1.3.8/demo/js/archive/1.3.1/anchorDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.1/anchorDemo.js rename to archive/1.3.8/demo/js/archive/1.3.1/anchorDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.1/chartDemo.js b/archive/1.3.8/demo/js/archive/1.3.1/chartDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.1/chartDemo.js rename to archive/1.3.8/demo/js/archive/1.3.1/chartDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.1/demo-helper-jquery.js b/archive/1.3.8/demo/js/archive/1.3.1/demo-helper-jquery.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.1/demo-helper-jquery.js rename to archive/1.3.8/demo/js/archive/1.3.1/demo-helper-jquery.js diff --git a/build/1.3.8/demo/js/archive/1.3.1/demo-helper-mootools.js b/archive/1.3.8/demo/js/archive/1.3.1/demo-helper-mootools.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.1/demo-helper-mootools.js rename to archive/1.3.8/demo/js/archive/1.3.1/demo-helper-mootools.js diff --git a/build/1.3.8/demo/js/archive/1.3.1/demo-helper-yui3.js b/archive/1.3.8/demo/js/archive/1.3.1/demo-helper-yui3.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.1/demo-helper-yui3.js rename to archive/1.3.8/demo/js/archive/1.3.1/demo-helper-yui3.js diff --git a/build/1.3.8/demo/js/archive/1.3.1/demo.js b/archive/1.3.8/demo/js/archive/1.3.1/demo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.1/demo.js rename to archive/1.3.8/demo/js/archive/1.3.1/demo.js diff --git a/build/1.3.8/demo/js/archive/1.3.1/dragAnimDemo-jquery.js b/archive/1.3.8/demo/js/archive/1.3.1/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.1/dragAnimDemo-jquery.js rename to archive/1.3.8/demo/js/archive/1.3.1/dragAnimDemo-jquery.js diff --git a/build/1.3.8/demo/js/archive/1.3.1/dragAnimDemo-mootools.js b/archive/1.3.8/demo/js/archive/1.3.1/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.1/dragAnimDemo-mootools.js rename to archive/1.3.8/demo/js/archive/1.3.1/dragAnimDemo-mootools.js diff --git a/build/1.3.8/demo/js/archive/1.3.1/dragAnimDemo-yui3.js b/archive/1.3.8/demo/js/archive/1.3.1/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.1/dragAnimDemo-yui3.js rename to archive/1.3.8/demo/js/archive/1.3.1/dragAnimDemo-yui3.js diff --git a/build/1.3.8/demo/js/archive/1.3.1/dragAnimDemo.js b/archive/1.3.8/demo/js/archive/1.3.1/dragAnimDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.1/dragAnimDemo.js rename to archive/1.3.8/demo/js/archive/1.3.1/dragAnimDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js b/archive/1.3.8/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js rename to archive/1.3.8/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.8/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js b/archive/1.3.8/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js rename to archive/1.3.8/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.8/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js b/archive/1.3.8/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js rename to archive/1.3.8/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.8/demo/js/archive/1.3.1/draggableConnectorsDemo.js b/archive/1.3.8/demo/js/archive/1.3.1/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.1/draggableConnectorsDemo.js rename to archive/1.3.8/demo/js/archive/1.3.1/draggableConnectorsDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.1/dynamicAnchorsDemo.js b/archive/1.3.8/demo/js/archive/1.3.1/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.1/dynamicAnchorsDemo.js rename to archive/1.3.8/demo/js/archive/1.3.1/dynamicAnchorsDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.1/flowchartConnectorsDemo.js b/archive/1.3.8/demo/js/archive/1.3.1/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.1/flowchartConnectorsDemo.js rename to archive/1.3.8/demo/js/archive/1.3.1/flowchartConnectorsDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.2/anchorDemo.js b/archive/1.3.8/demo/js/archive/1.3.2/anchorDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.2/anchorDemo.js rename to archive/1.3.8/demo/js/archive/1.3.2/anchorDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.2/chartDemo.js b/archive/1.3.8/demo/js/archive/1.3.2/chartDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.2/chartDemo.js rename to archive/1.3.8/demo/js/archive/1.3.2/chartDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.2/demo-helper-jquery.js b/archive/1.3.8/demo/js/archive/1.3.2/demo-helper-jquery.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.2/demo-helper-jquery.js rename to archive/1.3.8/demo/js/archive/1.3.2/demo-helper-jquery.js diff --git a/build/1.3.8/demo/js/archive/1.3.2/demo-helper-mootools.js b/archive/1.3.8/demo/js/archive/1.3.2/demo-helper-mootools.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.2/demo-helper-mootools.js rename to archive/1.3.8/demo/js/archive/1.3.2/demo-helper-mootools.js diff --git a/build/1.3.8/demo/js/archive/1.3.2/demo-helper-yui3.js b/archive/1.3.8/demo/js/archive/1.3.2/demo-helper-yui3.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.2/demo-helper-yui3.js rename to archive/1.3.8/demo/js/archive/1.3.2/demo-helper-yui3.js diff --git a/build/1.3.8/demo/js/archive/1.3.2/demo.js b/archive/1.3.8/demo/js/archive/1.3.2/demo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.2/demo.js rename to archive/1.3.8/demo/js/archive/1.3.2/demo.js diff --git a/build/1.3.8/demo/js/archive/1.3.2/dragAnimDemo-jquery.js b/archive/1.3.8/demo/js/archive/1.3.2/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.2/dragAnimDemo-jquery.js rename to archive/1.3.8/demo/js/archive/1.3.2/dragAnimDemo-jquery.js diff --git a/build/1.3.8/demo/js/archive/1.3.2/dragAnimDemo-mootools.js b/archive/1.3.8/demo/js/archive/1.3.2/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.2/dragAnimDemo-mootools.js rename to archive/1.3.8/demo/js/archive/1.3.2/dragAnimDemo-mootools.js diff --git a/build/1.3.8/demo/js/archive/1.3.2/dragAnimDemo-yui3.js b/archive/1.3.8/demo/js/archive/1.3.2/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.2/dragAnimDemo-yui3.js rename to archive/1.3.8/demo/js/archive/1.3.2/dragAnimDemo-yui3.js diff --git a/build/1.3.8/demo/js/archive/1.3.2/dragAnimDemo.js b/archive/1.3.8/demo/js/archive/1.3.2/dragAnimDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.2/dragAnimDemo.js rename to archive/1.3.8/demo/js/archive/1.3.2/dragAnimDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js b/archive/1.3.8/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js rename to archive/1.3.8/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.8/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js b/archive/1.3.8/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js rename to archive/1.3.8/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.8/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js b/archive/1.3.8/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js rename to archive/1.3.8/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.8/demo/js/archive/1.3.2/draggableConnectorsDemo.js b/archive/1.3.8/demo/js/archive/1.3.2/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.2/draggableConnectorsDemo.js rename to archive/1.3.8/demo/js/archive/1.3.2/draggableConnectorsDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.2/dynamicAnchorsDemo.js b/archive/1.3.8/demo/js/archive/1.3.2/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.2/dynamicAnchorsDemo.js rename to archive/1.3.8/demo/js/archive/1.3.2/dynamicAnchorsDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.2/flowchartConnectorsDemo.js b/archive/1.3.8/demo/js/archive/1.3.2/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.2/flowchartConnectorsDemo.js rename to archive/1.3.8/demo/js/archive/1.3.2/flowchartConnectorsDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.3/anchorDemo.js b/archive/1.3.8/demo/js/archive/1.3.3/anchorDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.3/anchorDemo.js rename to archive/1.3.8/demo/js/archive/1.3.3/anchorDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.3/chartDemo.js b/archive/1.3.8/demo/js/archive/1.3.3/chartDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.3/chartDemo.js rename to archive/1.3.8/demo/js/archive/1.3.3/chartDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.3/demo-helper-jquery.js b/archive/1.3.8/demo/js/archive/1.3.3/demo-helper-jquery.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.3/demo-helper-jquery.js rename to archive/1.3.8/demo/js/archive/1.3.3/demo-helper-jquery.js diff --git a/build/1.3.8/demo/js/archive/1.3.3/demo-helper-mootools.js b/archive/1.3.8/demo/js/archive/1.3.3/demo-helper-mootools.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.3/demo-helper-mootools.js rename to archive/1.3.8/demo/js/archive/1.3.3/demo-helper-mootools.js diff --git a/build/1.3.8/demo/js/archive/1.3.3/demo-helper-yui3.js b/archive/1.3.8/demo/js/archive/1.3.3/demo-helper-yui3.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.3/demo-helper-yui3.js rename to archive/1.3.8/demo/js/archive/1.3.3/demo-helper-yui3.js diff --git a/build/1.3.8/demo/js/archive/1.3.3/demo.js b/archive/1.3.8/demo/js/archive/1.3.3/demo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.3/demo.js rename to archive/1.3.8/demo/js/archive/1.3.3/demo.js diff --git a/build/1.3.8/demo/js/archive/1.3.3/dragAnimDemo-jquery.js b/archive/1.3.8/demo/js/archive/1.3.3/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.3/dragAnimDemo-jquery.js rename to archive/1.3.8/demo/js/archive/1.3.3/dragAnimDemo-jquery.js diff --git a/build/1.3.8/demo/js/archive/1.3.3/dragAnimDemo-mootools.js b/archive/1.3.8/demo/js/archive/1.3.3/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.3/dragAnimDemo-mootools.js rename to archive/1.3.8/demo/js/archive/1.3.3/dragAnimDemo-mootools.js diff --git a/build/1.3.8/demo/js/archive/1.3.3/dragAnimDemo-yui3.js b/archive/1.3.8/demo/js/archive/1.3.3/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.3/dragAnimDemo-yui3.js rename to archive/1.3.8/demo/js/archive/1.3.3/dragAnimDemo-yui3.js diff --git a/build/1.3.8/demo/js/archive/1.3.3/dragAnimDemo.js b/archive/1.3.8/demo/js/archive/1.3.3/dragAnimDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.3/dragAnimDemo.js rename to archive/1.3.8/demo/js/archive/1.3.3/dragAnimDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js b/archive/1.3.8/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js rename to archive/1.3.8/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.8/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js b/archive/1.3.8/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js rename to archive/1.3.8/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.8/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js b/archive/1.3.8/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js rename to archive/1.3.8/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.8/demo/js/archive/1.3.3/draggableConnectorsDemo.js b/archive/1.3.8/demo/js/archive/1.3.3/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.3/draggableConnectorsDemo.js rename to archive/1.3.8/demo/js/archive/1.3.3/draggableConnectorsDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.3/dynamicAnchorsDemo.js b/archive/1.3.8/demo/js/archive/1.3.3/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.3/dynamicAnchorsDemo.js rename to archive/1.3.8/demo/js/archive/1.3.3/dynamicAnchorsDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.3/flowchartConnectorsDemo.js b/archive/1.3.8/demo/js/archive/1.3.3/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.3/flowchartConnectorsDemo.js rename to archive/1.3.8/demo/js/archive/1.3.3/flowchartConnectorsDemo.js diff --git a/build/1.3.9/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js b/archive/1.3.8/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js rename to archive/1.3.8/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js diff --git a/build/1.3.9/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all.js b/archive/1.3.8/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all.js rename to archive/1.3.8/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all.js diff --git a/build/1.3.9/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js b/archive/1.3.8/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js rename to archive/1.3.8/demo/js/archive/1.3.3/jsPlumb-1.3.3-tests.js diff --git a/build/1.3.9/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js b/archive/1.3.8/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js rename to archive/1.3.8/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js diff --git a/build/1.3.9/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js b/archive/1.3.8/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js rename to archive/1.3.8/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all.js diff --git a/build/1.3.9/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js b/archive/1.3.8/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js rename to archive/1.3.8/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all-min.js diff --git a/build/1.3.9/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js b/archive/1.3.8/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js rename to archive/1.3.8/demo/js/archive/1.3.3/yui.jsPlumb-1.3.3-all.js diff --git a/build/1.3.8/demo/js/archive/1.3.4/anchorDemo.js b/archive/1.3.8/demo/js/archive/1.3.4/anchorDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.4/anchorDemo.js rename to archive/1.3.8/demo/js/archive/1.3.4/anchorDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.4/chartDemo.js b/archive/1.3.8/demo/js/archive/1.3.4/chartDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.4/chartDemo.js rename to archive/1.3.8/demo/js/archive/1.3.4/chartDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.4/demo-helper-jquery.js b/archive/1.3.8/demo/js/archive/1.3.4/demo-helper-jquery.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.4/demo-helper-jquery.js rename to archive/1.3.8/demo/js/archive/1.3.4/demo-helper-jquery.js diff --git a/build/1.3.8/demo/js/archive/1.3.4/demo-helper-mootools.js b/archive/1.3.8/demo/js/archive/1.3.4/demo-helper-mootools.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.4/demo-helper-mootools.js rename to archive/1.3.8/demo/js/archive/1.3.4/demo-helper-mootools.js diff --git a/build/1.3.8/demo/js/archive/1.3.4/demo-helper-yui3.js b/archive/1.3.8/demo/js/archive/1.3.4/demo-helper-yui3.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.4/demo-helper-yui3.js rename to archive/1.3.8/demo/js/archive/1.3.4/demo-helper-yui3.js diff --git a/build/1.3.8/demo/js/archive/1.3.4/demo-list.js b/archive/1.3.8/demo/js/archive/1.3.4/demo-list.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.4/demo-list.js rename to archive/1.3.8/demo/js/archive/1.3.4/demo-list.js diff --git a/build/1.3.8/demo/js/archive/1.3.4/demo.js b/archive/1.3.8/demo/js/archive/1.3.4/demo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.4/demo.js rename to archive/1.3.8/demo/js/archive/1.3.4/demo.js diff --git a/build/1.3.8/demo/js/archive/1.3.4/dragAnimDemo-jquery.js b/archive/1.3.8/demo/js/archive/1.3.4/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.4/dragAnimDemo-jquery.js rename to archive/1.3.8/demo/js/archive/1.3.4/dragAnimDemo-jquery.js diff --git a/build/1.3.8/demo/js/archive/1.3.4/dragAnimDemo-mootools.js b/archive/1.3.8/demo/js/archive/1.3.4/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.4/dragAnimDemo-mootools.js rename to archive/1.3.8/demo/js/archive/1.3.4/dragAnimDemo-mootools.js diff --git a/build/1.3.8/demo/js/archive/1.3.4/dragAnimDemo-yui3.js b/archive/1.3.8/demo/js/archive/1.3.4/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.4/dragAnimDemo-yui3.js rename to archive/1.3.8/demo/js/archive/1.3.4/dragAnimDemo-yui3.js diff --git a/build/1.3.8/demo/js/archive/1.3.4/dragAnimDemo.js b/archive/1.3.8/demo/js/archive/1.3.4/dragAnimDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.4/dragAnimDemo.js rename to archive/1.3.8/demo/js/archive/1.3.4/dragAnimDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.4/draggableConnectorsDemo-jquery.js b/archive/1.3.8/demo/js/archive/1.3.4/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.4/draggableConnectorsDemo-jquery.js rename to archive/1.3.8/demo/js/archive/1.3.4/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.8/demo/js/archive/1.3.4/draggableConnectorsDemo-mootools.js b/archive/1.3.8/demo/js/archive/1.3.4/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.4/draggableConnectorsDemo-mootools.js rename to archive/1.3.8/demo/js/archive/1.3.4/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.8/demo/js/archive/1.3.4/draggableConnectorsDemo-yui3.js b/archive/1.3.8/demo/js/archive/1.3.4/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.4/draggableConnectorsDemo-yui3.js rename to archive/1.3.8/demo/js/archive/1.3.4/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.8/demo/js/archive/1.3.4/draggableConnectorsDemo.js b/archive/1.3.8/demo/js/archive/1.3.4/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.4/draggableConnectorsDemo.js rename to archive/1.3.8/demo/js/archive/1.3.4/draggableConnectorsDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.4/dynamicAnchorsDemo.js b/archive/1.3.8/demo/js/archive/1.3.4/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.4/dynamicAnchorsDemo.js rename to archive/1.3.8/demo/js/archive/1.3.4/dynamicAnchorsDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.4/flowchartConnectorsDemo.js b/archive/1.3.8/demo/js/archive/1.3.4/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.4/flowchartConnectorsDemo.js rename to archive/1.3.8/demo/js/archive/1.3.4/flowchartConnectorsDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.4/makeSourceDemo.js b/archive/1.3.8/demo/js/archive/1.3.4/makeSourceDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.4/makeSourceDemo.js rename to archive/1.3.8/demo/js/archive/1.3.4/makeSourceDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.4/makeTargetDemo.js b/archive/1.3.8/demo/js/archive/1.3.4/makeTargetDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.4/makeTargetDemo.js rename to archive/1.3.8/demo/js/archive/1.3.4/makeTargetDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.4/stateMachineDemo-jquery.js b/archive/1.3.8/demo/js/archive/1.3.4/stateMachineDemo-jquery.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.4/stateMachineDemo-jquery.js rename to archive/1.3.8/demo/js/archive/1.3.4/stateMachineDemo-jquery.js diff --git a/build/1.3.8/demo/js/archive/1.3.4/stateMachineDemo-mootools.js b/archive/1.3.8/demo/js/archive/1.3.4/stateMachineDemo-mootools.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.4/stateMachineDemo-mootools.js rename to archive/1.3.8/demo/js/archive/1.3.4/stateMachineDemo-mootools.js diff --git a/build/1.3.8/demo/js/archive/1.3.4/stateMachineDemo-yui3.js b/archive/1.3.8/demo/js/archive/1.3.4/stateMachineDemo-yui3.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.4/stateMachineDemo-yui3.js rename to archive/1.3.8/demo/js/archive/1.3.4/stateMachineDemo-yui3.js diff --git a/build/1.3.8/demo/js/archive/1.3.4/stateMachineDemo.js b/archive/1.3.8/demo/js/archive/1.3.4/stateMachineDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.4/stateMachineDemo.js rename to archive/1.3.8/demo/js/archive/1.3.4/stateMachineDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.5/anchorDemo.js b/archive/1.3.8/demo/js/archive/1.3.5/anchorDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.5/anchorDemo.js rename to archive/1.3.8/demo/js/archive/1.3.5/anchorDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.5/chartDemo.js b/archive/1.3.8/demo/js/archive/1.3.5/chartDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.5/chartDemo.js rename to archive/1.3.8/demo/js/archive/1.3.5/chartDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.5/demo-helper-jquery.js b/archive/1.3.8/demo/js/archive/1.3.5/demo-helper-jquery.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.5/demo-helper-jquery.js rename to archive/1.3.8/demo/js/archive/1.3.5/demo-helper-jquery.js diff --git a/build/1.3.8/demo/js/archive/1.3.5/demo-helper-mootools.js b/archive/1.3.8/demo/js/archive/1.3.5/demo-helper-mootools.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.5/demo-helper-mootools.js rename to archive/1.3.8/demo/js/archive/1.3.5/demo-helper-mootools.js diff --git a/build/1.3.8/demo/js/archive/1.3.5/demo-helper-yui3.js b/archive/1.3.8/demo/js/archive/1.3.5/demo-helper-yui3.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.5/demo-helper-yui3.js rename to archive/1.3.8/demo/js/archive/1.3.5/demo-helper-yui3.js diff --git a/build/1.3.8/demo/js/archive/1.3.5/demo-list.js b/archive/1.3.8/demo/js/archive/1.3.5/demo-list.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.5/demo-list.js rename to archive/1.3.8/demo/js/archive/1.3.5/demo-list.js diff --git a/build/1.3.8/demo/js/archive/1.3.5/demo.js b/archive/1.3.8/demo/js/archive/1.3.5/demo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.5/demo.js rename to archive/1.3.8/demo/js/archive/1.3.5/demo.js diff --git a/build/1.3.8/demo/js/archive/1.3.5/dragAnimDemo-jquery.js b/archive/1.3.8/demo/js/archive/1.3.5/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.5/dragAnimDemo-jquery.js rename to archive/1.3.8/demo/js/archive/1.3.5/dragAnimDemo-jquery.js diff --git a/build/1.3.8/demo/js/archive/1.3.5/dragAnimDemo-mootools.js b/archive/1.3.8/demo/js/archive/1.3.5/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.5/dragAnimDemo-mootools.js rename to archive/1.3.8/demo/js/archive/1.3.5/dragAnimDemo-mootools.js diff --git a/build/1.3.8/demo/js/archive/1.3.5/dragAnimDemo-yui3.js b/archive/1.3.8/demo/js/archive/1.3.5/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.5/dragAnimDemo-yui3.js rename to archive/1.3.8/demo/js/archive/1.3.5/dragAnimDemo-yui3.js diff --git a/build/1.3.8/demo/js/archive/1.3.5/dragAnimDemo.js b/archive/1.3.8/demo/js/archive/1.3.5/dragAnimDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.5/dragAnimDemo.js rename to archive/1.3.8/demo/js/archive/1.3.5/dragAnimDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.5/draggableConnectorsDemo-jquery.js b/archive/1.3.8/demo/js/archive/1.3.5/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.5/draggableConnectorsDemo-jquery.js rename to archive/1.3.8/demo/js/archive/1.3.5/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.8/demo/js/archive/1.3.5/draggableConnectorsDemo-mootools.js b/archive/1.3.8/demo/js/archive/1.3.5/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.5/draggableConnectorsDemo-mootools.js rename to archive/1.3.8/demo/js/archive/1.3.5/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.8/demo/js/archive/1.3.5/draggableConnectorsDemo-yui3.js b/archive/1.3.8/demo/js/archive/1.3.5/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.5/draggableConnectorsDemo-yui3.js rename to archive/1.3.8/demo/js/archive/1.3.5/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.8/demo/js/archive/1.3.5/draggableConnectorsDemo.js b/archive/1.3.8/demo/js/archive/1.3.5/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.5/draggableConnectorsDemo.js rename to archive/1.3.8/demo/js/archive/1.3.5/draggableConnectorsDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.5/dynamicAnchorsDemo.js b/archive/1.3.8/demo/js/archive/1.3.5/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.5/dynamicAnchorsDemo.js rename to archive/1.3.8/demo/js/archive/1.3.5/dynamicAnchorsDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.5/flowchartConnectorsDemo.js b/archive/1.3.8/demo/js/archive/1.3.5/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.5/flowchartConnectorsDemo.js rename to archive/1.3.8/demo/js/archive/1.3.5/flowchartConnectorsDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.5/loadTest.js b/archive/1.3.8/demo/js/archive/1.3.5/loadTest.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.5/loadTest.js rename to archive/1.3.8/demo/js/archive/1.3.5/loadTest.js diff --git a/build/1.3.8/demo/js/archive/1.3.5/makeSourceDemo.js b/archive/1.3.8/demo/js/archive/1.3.5/makeSourceDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.5/makeSourceDemo.js rename to archive/1.3.8/demo/js/archive/1.3.5/makeSourceDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.5/makeTargetDemo.js b/archive/1.3.8/demo/js/archive/1.3.5/makeTargetDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.5/makeTargetDemo.js rename to archive/1.3.8/demo/js/archive/1.3.5/makeTargetDemo.js diff --git a/build/1.3.8/demo/js/archive/1.3.5/stateMachineDemo-jquery.js b/archive/1.3.8/demo/js/archive/1.3.5/stateMachineDemo-jquery.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.5/stateMachineDemo-jquery.js rename to archive/1.3.8/demo/js/archive/1.3.5/stateMachineDemo-jquery.js diff --git a/build/1.3.8/demo/js/archive/1.3.5/stateMachineDemo-mootools.js b/archive/1.3.8/demo/js/archive/1.3.5/stateMachineDemo-mootools.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.5/stateMachineDemo-mootools.js rename to archive/1.3.8/demo/js/archive/1.3.5/stateMachineDemo-mootools.js diff --git a/build/1.3.8/demo/js/archive/1.3.5/stateMachineDemo-yui3.js b/archive/1.3.8/demo/js/archive/1.3.5/stateMachineDemo-yui3.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.5/stateMachineDemo-yui3.js rename to archive/1.3.8/demo/js/archive/1.3.5/stateMachineDemo-yui3.js diff --git a/build/1.3.8/demo/js/archive/1.3.5/stateMachineDemo.js b/archive/1.3.8/demo/js/archive/1.3.5/stateMachineDemo.js similarity index 100% rename from build/1.3.8/demo/js/archive/1.3.5/stateMachineDemo.js rename to archive/1.3.8/demo/js/archive/1.3.5/stateMachineDemo.js diff --git a/build/1.3.8/demo/js/chartDemo.js b/archive/1.3.8/demo/js/chartDemo.js similarity index 100% rename from build/1.3.8/demo/js/chartDemo.js rename to archive/1.3.8/demo/js/chartDemo.js diff --git a/build/1.3.8/demo/js/demo-helper-jquery.js b/archive/1.3.8/demo/js/demo-helper-jquery.js similarity index 100% rename from build/1.3.8/demo/js/demo-helper-jquery.js rename to archive/1.3.8/demo/js/demo-helper-jquery.js diff --git a/build/1.3.8/demo/js/demo-helper-mootools.js b/archive/1.3.8/demo/js/demo-helper-mootools.js similarity index 100% rename from build/1.3.8/demo/js/demo-helper-mootools.js rename to archive/1.3.8/demo/js/demo-helper-mootools.js diff --git a/build/1.3.8/demo/js/demo-helper-yui3.js b/archive/1.3.8/demo/js/demo-helper-yui3.js similarity index 100% rename from build/1.3.8/demo/js/demo-helper-yui3.js rename to archive/1.3.8/demo/js/demo-helper-yui3.js diff --git a/build/1.3.8/demo/js/demo-list.js b/archive/1.3.8/demo/js/demo-list.js similarity index 100% rename from build/1.3.8/demo/js/demo-list.js rename to archive/1.3.8/demo/js/demo-list.js diff --git a/build/1.3.8/demo/js/demo.js b/archive/1.3.8/demo/js/demo.js similarity index 100% rename from build/1.3.8/demo/js/demo.js rename to archive/1.3.8/demo/js/demo.js diff --git a/build/1.3.8/demo/js/dragAnimDemo-jquery.js b/archive/1.3.8/demo/js/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.8/demo/js/dragAnimDemo-jquery.js rename to archive/1.3.8/demo/js/dragAnimDemo-jquery.js diff --git a/build/1.3.8/demo/js/dragAnimDemo-mootools.js b/archive/1.3.8/demo/js/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.8/demo/js/dragAnimDemo-mootools.js rename to archive/1.3.8/demo/js/dragAnimDemo-mootools.js diff --git a/build/1.3.8/demo/js/dragAnimDemo-yui3.js b/archive/1.3.8/demo/js/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.8/demo/js/dragAnimDemo-yui3.js rename to archive/1.3.8/demo/js/dragAnimDemo-yui3.js diff --git a/build/1.3.8/demo/js/dragAnimDemo.js b/archive/1.3.8/demo/js/dragAnimDemo.js similarity index 100% rename from build/1.3.8/demo/js/dragAnimDemo.js rename to archive/1.3.8/demo/js/dragAnimDemo.js diff --git a/build/1.3.8/demo/js/draggableConnectorsDemo-jquery.js b/archive/1.3.8/demo/js/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.8/demo/js/draggableConnectorsDemo-jquery.js rename to archive/1.3.8/demo/js/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.8/demo/js/draggableConnectorsDemo-mootools.js b/archive/1.3.8/demo/js/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.8/demo/js/draggableConnectorsDemo-mootools.js rename to archive/1.3.8/demo/js/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.8/demo/js/draggableConnectorsDemo-yui3.js b/archive/1.3.8/demo/js/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.8/demo/js/draggableConnectorsDemo-yui3.js rename to archive/1.3.8/demo/js/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.8/demo/js/draggableConnectorsDemo.js b/archive/1.3.8/demo/js/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.8/demo/js/draggableConnectorsDemo.js rename to archive/1.3.8/demo/js/draggableConnectorsDemo.js diff --git a/build/1.3.8/demo/js/dynamicAnchorsDemo.js b/archive/1.3.8/demo/js/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.8/demo/js/dynamicAnchorsDemo.js rename to archive/1.3.8/demo/js/dynamicAnchorsDemo.js diff --git a/build/1.3.8/demo/js/flowchartConnectorsDemo.js b/archive/1.3.8/demo/js/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.8/demo/js/flowchartConnectorsDemo.js rename to archive/1.3.8/demo/js/flowchartConnectorsDemo.js diff --git a/build/1.3.8/demo/js/jquery.jsPlumb-1.3.8-all-min.js b/archive/1.3.8/demo/js/jquery.jsPlumb-1.3.8-all-min.js similarity index 100% rename from build/1.3.8/demo/js/jquery.jsPlumb-1.3.8-all-min.js rename to archive/1.3.8/demo/js/jquery.jsPlumb-1.3.8-all-min.js diff --git a/build/1.3.8/demo/js/jquery.jsPlumb-1.3.8-all.js b/archive/1.3.8/demo/js/jquery.jsPlumb-1.3.8-all.js similarity index 100% rename from build/1.3.8/demo/js/jquery.jsPlumb-1.3.8-all.js rename to archive/1.3.8/demo/js/jquery.jsPlumb-1.3.8-all.js diff --git a/build/1.3.8/demo/js/loadTest.js b/archive/1.3.8/demo/js/loadTest.js similarity index 100% rename from build/1.3.8/demo/js/loadTest.js rename to archive/1.3.8/demo/js/loadTest.js diff --git a/build/1.3.8/demo/js/makeSourceDemo.js b/archive/1.3.8/demo/js/makeSourceDemo.js similarity index 100% rename from build/1.3.8/demo/js/makeSourceDemo.js rename to archive/1.3.8/demo/js/makeSourceDemo.js diff --git a/build/1.3.8/demo/js/makeTargetDemo.js b/archive/1.3.8/demo/js/makeTargetDemo.js similarity index 100% rename from build/1.3.8/demo/js/makeTargetDemo.js rename to archive/1.3.8/demo/js/makeTargetDemo.js diff --git a/build/1.3.8/demo/js/mootools-1.3.2.1-more.js b/archive/1.3.8/demo/js/mootools-1.3.2.1-more.js similarity index 100% rename from build/1.3.8/demo/js/mootools-1.3.2.1-more.js rename to archive/1.3.8/demo/js/mootools-1.3.2.1-more.js diff --git a/build/1.3.8/demo/js/mootools.jsPlumb-1.3.8-all-min.js b/archive/1.3.8/demo/js/mootools.jsPlumb-1.3.8-all-min.js similarity index 100% rename from build/1.3.8/demo/js/mootools.jsPlumb-1.3.8-all-min.js rename to archive/1.3.8/demo/js/mootools.jsPlumb-1.3.8-all-min.js diff --git a/build/1.3.8/demo/js/mootools.jsPlumb-1.3.8-all.js b/archive/1.3.8/demo/js/mootools.jsPlumb-1.3.8-all.js similarity index 100% rename from build/1.3.8/demo/js/mootools.jsPlumb-1.3.8-all.js rename to archive/1.3.8/demo/js/mootools.jsPlumb-1.3.8-all.js diff --git a/build/1.3.8/demo/js/stateMachineDemo-jquery.js b/archive/1.3.8/demo/js/stateMachineDemo-jquery.js similarity index 100% rename from build/1.3.8/demo/js/stateMachineDemo-jquery.js rename to archive/1.3.8/demo/js/stateMachineDemo-jquery.js diff --git a/build/1.3.8/demo/js/stateMachineDemo-mootools.js b/archive/1.3.8/demo/js/stateMachineDemo-mootools.js similarity index 100% rename from build/1.3.8/demo/js/stateMachineDemo-mootools.js rename to archive/1.3.8/demo/js/stateMachineDemo-mootools.js diff --git a/build/1.3.8/demo/js/stateMachineDemo-yui3.js b/archive/1.3.8/demo/js/stateMachineDemo-yui3.js similarity index 100% rename from build/1.3.8/demo/js/stateMachineDemo-yui3.js rename to archive/1.3.8/demo/js/stateMachineDemo-yui3.js diff --git a/build/1.3.8/demo/js/stateMachineDemo.js b/archive/1.3.8/demo/js/stateMachineDemo.js similarity index 100% rename from build/1.3.8/demo/js/stateMachineDemo.js rename to archive/1.3.8/demo/js/stateMachineDemo.js diff --git a/build/1.3.8/demo/js/yui-3.3.0-min.js b/archive/1.3.8/demo/js/yui-3.3.0-min.js similarity index 100% rename from build/1.3.8/demo/js/yui-3.3.0-min.js rename to archive/1.3.8/demo/js/yui-3.3.0-min.js diff --git a/build/1.3.8/demo/js/yui.jsPlumb-1.3.8-all-min.js b/archive/1.3.8/demo/js/yui.jsPlumb-1.3.8-all-min.js similarity index 100% rename from build/1.3.8/demo/js/yui.jsPlumb-1.3.8-all-min.js rename to archive/1.3.8/demo/js/yui.jsPlumb-1.3.8-all-min.js diff --git a/build/1.3.8/demo/js/yui.jsPlumb-1.3.8-all.js b/archive/1.3.8/demo/js/yui.jsPlumb-1.3.8-all.js similarity index 100% rename from build/1.3.8/demo/js/yui.jsPlumb-1.3.8-all.js rename to archive/1.3.8/demo/js/yui.jsPlumb-1.3.8-all.js diff --git a/build/1.3.8/demo/mootools/anchorDemo.html b/archive/1.3.8/demo/mootools/anchorDemo.html similarity index 100% rename from build/1.3.8/demo/mootools/anchorDemo.html rename to archive/1.3.8/demo/mootools/anchorDemo.html diff --git a/build/1.3.8/demo/mootools/chartDemo.html b/archive/1.3.8/demo/mootools/chartDemo.html similarity index 100% rename from build/1.3.8/demo/mootools/chartDemo.html rename to archive/1.3.8/demo/mootools/chartDemo.html diff --git a/build/1.3.8/demo/mootools/demo.html b/archive/1.3.8/demo/mootools/demo.html similarity index 100% rename from build/1.3.8/demo/mootools/demo.html rename to archive/1.3.8/demo/mootools/demo.html diff --git a/build/1.3.8/demo/mootools/dragAnimDemo.html b/archive/1.3.8/demo/mootools/dragAnimDemo.html similarity index 100% rename from build/1.3.8/demo/mootools/dragAnimDemo.html rename to archive/1.3.8/demo/mootools/dragAnimDemo.html diff --git a/build/1.3.8/demo/mootools/draggableConnectorsDemo.html b/archive/1.3.8/demo/mootools/draggableConnectorsDemo.html similarity index 100% rename from build/1.3.8/demo/mootools/draggableConnectorsDemo.html rename to archive/1.3.8/demo/mootools/draggableConnectorsDemo.html diff --git a/build/1.3.8/demo/mootools/dynamicAnchorsDemo.html b/archive/1.3.8/demo/mootools/dynamicAnchorsDemo.html similarity index 100% rename from build/1.3.8/demo/mootools/dynamicAnchorsDemo.html rename to archive/1.3.8/demo/mootools/dynamicAnchorsDemo.html diff --git a/build/1.3.8/demo/mootools/flowchartConnectorsDemo.html b/archive/1.3.8/demo/mootools/flowchartConnectorsDemo.html similarity index 100% rename from build/1.3.8/demo/mootools/flowchartConnectorsDemo.html rename to archive/1.3.8/demo/mootools/flowchartConnectorsDemo.html diff --git a/build/1.3.8/demo/mootools/stateMachineDemo.html b/archive/1.3.8/demo/mootools/stateMachineDemo.html similarity index 100% rename from build/1.3.8/demo/mootools/stateMachineDemo.html rename to archive/1.3.8/demo/mootools/stateMachineDemo.html diff --git a/build/1.3.8/demo/yui3/anchorDemo.html b/archive/1.3.8/demo/yui3/anchorDemo.html similarity index 100% rename from build/1.3.8/demo/yui3/anchorDemo.html rename to archive/1.3.8/demo/yui3/anchorDemo.html diff --git a/build/1.3.8/demo/yui3/chartDemo.html b/archive/1.3.8/demo/yui3/chartDemo.html similarity index 100% rename from build/1.3.8/demo/yui3/chartDemo.html rename to archive/1.3.8/demo/yui3/chartDemo.html diff --git a/build/1.3.8/demo/yui3/demo.html b/archive/1.3.8/demo/yui3/demo.html similarity index 100% rename from build/1.3.8/demo/yui3/demo.html rename to archive/1.3.8/demo/yui3/demo.html diff --git a/build/1.3.8/demo/yui3/dragAnimDemo.html b/archive/1.3.8/demo/yui3/dragAnimDemo.html similarity index 100% rename from build/1.3.8/demo/yui3/dragAnimDemo.html rename to archive/1.3.8/demo/yui3/dragAnimDemo.html diff --git a/build/1.3.8/demo/yui3/draggableConnectorsDemo.html b/archive/1.3.8/demo/yui3/draggableConnectorsDemo.html similarity index 100% rename from build/1.3.8/demo/yui3/draggableConnectorsDemo.html rename to archive/1.3.8/demo/yui3/draggableConnectorsDemo.html diff --git a/build/1.3.8/demo/yui3/dynamicAnchorsDemo.html b/archive/1.3.8/demo/yui3/dynamicAnchorsDemo.html similarity index 100% rename from build/1.3.8/demo/yui3/dynamicAnchorsDemo.html rename to archive/1.3.8/demo/yui3/dynamicAnchorsDemo.html diff --git a/build/1.3.8/demo/yui3/flowchartConnectorsDemo.html b/archive/1.3.8/demo/yui3/flowchartConnectorsDemo.html similarity index 100% rename from build/1.3.8/demo/yui3/flowchartConnectorsDemo.html rename to archive/1.3.8/demo/yui3/flowchartConnectorsDemo.html diff --git a/build/1.3.8/demo/yui3/stateMachineDemo.html b/archive/1.3.8/demo/yui3/stateMachineDemo.html similarity index 100% rename from build/1.3.8/demo/yui3/stateMachineDemo.html rename to archive/1.3.8/demo/yui3/stateMachineDemo.html diff --git a/archive/1.3.8/jquery.jsPlumb-1.3.8-RC1.js b/archive/1.3.8/jquery.jsPlumb-1.3.8-RC1.js new file mode 100644 index 000000000..a32e79e92 --- /dev/null +++ b/archive/1.3.8/jquery.jsPlumb-1.3.8-RC1.js @@ -0,0 +1,368 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.8 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the jQuery adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ +/* + * the library specific functions, such as find offset, get id, get attribute, extend etc. + * the full list is: + * + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getDropScope gets the drop scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getPageXY gets the page event's xy location. + * getParent gets the parent of some element. + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * hasClass returns whether or not the given element has the given class. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + * trigger triggers some event on an element. + * unbind unbinds some listener from some element. + */ +(function($) { + + //var getBoundingClientRectSupported = "getBoundingClientRect" in document.documentElement; + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el[0].className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.addClass(el[0], clazz); + } + } + catch (e) { + // SVGAnimatedString not supported; no problem. + } + el.addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * appends the given child to the given parent. + */ + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).append(child); + }, + + /** + * executes ana ajax call. + */ + ajax : function(params) { + params = params || {}; + params.type = params.type || "get"; + $.ajax(params); + }, + + /** + * event binding wrapper. it just so happens that jQuery uses 'bind' also. yui3, for example, + * uses 'on'. + */ + bind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.bind(event, callback); + }, + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step', + 'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete' + }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + getDocumentElement : function() { return document; }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + }, + + getDragScope : function(el) { + return el.draggable("option", "scope"); + }, + + getDropEvent : function(args) { + return args[0]; + }, + + getDropScope : function(el) { + return el.droppable("option", "scope"); + }, + + /** + * gets a DOM element from the given input, which might be a string (in which case we just do document.getElementById), + * a selector (in which case we return el[0]), or a DOM element already (we assume this if it's not either of the other + * two cases). this is the opposite of getElementObject below. + */ + getDOMElement : function(el) { + if (typeof(el) == "string") return document.getElementById(el); + else if (el.context) return el[0]; + else return el; + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + * + */ + getElementObject : function(el) { + return typeof(el) == "string" ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).parent(); + }, + + getScrollLeft : function(el) { + return el.scrollLeft(); + }, + + getScrollTop : function(el) { + return el.scrollTop(); + }, + + getSelector : function(spec) { + return $(spec); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e.length > 0 ? e[0].tagName : null; + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + + // this code is a workaround for the case that the element being dragged has a margin set on it. jquery UI passes + // in the wrong offset if the element has a margin (it doesn't take the margin into account). the getBoundingClientRect + // method, which is in pretty much all browsers now, reports the right numbers. but it introduces a noticeable lag, which + // i don't like. + + /*if ( getBoundingClientRectSupported ) { + var r = eventArgs[1].helper[0].getBoundingClientRect(); + return { left : r.left, top: r.top }; + } else {*/ + if (eventArgs.length == 1) { + ret = { left: eventArgs[0].pageX, top:eventArgs[0].pageY }; + } + else { + var ui = eventArgs[1], _offset = ui.offset; + ret = _offset || ui.absolutePosition; + } + return ret; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options) { + options = options || {}; + // remove helper directive if present. + options.helper = null; + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.draggable(options); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.droppable(options); + }, + + isAlreadyDraggable : function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return el.hasClass("ui-draggable"); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el[0].className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.removeClass(el[0], clazz); + } + } + catch (e) { + // SVGAnimatedString not supported; no problem. + } + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).remove(); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * sets the drag scope. probably time for a setDragOption method (roll this and the one above together) + * @param el + * @param scope + */ + setDragScope : function(el, scope) { + el.draggable("option", "scope", scope); + }, + + setOffset : function(el, o) { + jsPlumb.CurrentLibrary.getElementObject(el).offset(o); + }, + + /** + * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself. + * the other libraries do not. yui, in fact, cannot even pass an original event. we have to pull out stuff + * from the originalEvent to put in an options object for YUI. + * @param el + * @param event + * @param originalEvent + */ + trigger : function(el, event, originalEvent) { + //originalEvent.stopPropagation(); + //jsPlumb.CurrentLibrary.getElementObject(el).trigger(originalEvent); + var h = jQuery._data(jsPlumb.CurrentLibrary.getElementObject(el)[0], "handle"); + h(originalEvent); + //originalEvent.stopPropagation(); + }, + + /** + * event unbinding wrapper. it just so happens that jQuery uses 'unbind' also. yui3, for example, + * uses..something else. + */ + unbind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.unbind(event, callback); + } + }; + + $(document).ready(jsPlumb.init); + +})(jQuery); + diff --git a/build/1.3.8/js/jquery.jsPlumb-1.3.8-all-min.js b/archive/1.3.8/js/jquery.jsPlumb-1.3.8-all-min.js similarity index 100% rename from build/1.3.8/js/jquery.jsPlumb-1.3.8-all-min.js rename to archive/1.3.8/js/jquery.jsPlumb-1.3.8-all-min.js diff --git a/build/1.3.8/js/jquery.jsPlumb-1.3.8-all.js b/archive/1.3.8/js/jquery.jsPlumb-1.3.8-all.js similarity index 100% rename from build/1.3.8/js/jquery.jsPlumb-1.3.8-all.js rename to archive/1.3.8/js/jquery.jsPlumb-1.3.8-all.js diff --git a/build/1.3.8/js/jsPlumb-1.3.8-tests.js b/archive/1.3.8/js/jsPlumb-1.3.8-tests.js similarity index 100% rename from build/1.3.8/js/jsPlumb-1.3.8-tests.js rename to archive/1.3.8/js/jsPlumb-1.3.8-tests.js diff --git a/build/1.3.8/js/lib/qunit.js b/archive/1.3.8/js/lib/qunit.js similarity index 100% rename from build/1.3.8/js/lib/qunit.js rename to archive/1.3.8/js/lib/qunit.js diff --git a/build/1.3.8/js/mootools.jsPlumb-1.3.8-all-min.js b/archive/1.3.8/js/mootools.jsPlumb-1.3.8-all-min.js similarity index 100% rename from build/1.3.8/js/mootools.jsPlumb-1.3.8-all-min.js rename to archive/1.3.8/js/mootools.jsPlumb-1.3.8-all-min.js diff --git a/build/1.3.8/js/mootools.jsPlumb-1.3.8-all.js b/archive/1.3.8/js/mootools.jsPlumb-1.3.8-all.js similarity index 100% rename from build/1.3.8/js/mootools.jsPlumb-1.3.8-all.js rename to archive/1.3.8/js/mootools.jsPlumb-1.3.8-all.js diff --git a/build/1.3.8/js/yui.jsPlumb-1.3.8-all-min.js b/archive/1.3.8/js/yui.jsPlumb-1.3.8-all-min.js similarity index 100% rename from build/1.3.8/js/yui.jsPlumb-1.3.8-all-min.js rename to archive/1.3.8/js/yui.jsPlumb-1.3.8-all-min.js diff --git a/build/1.3.8/js/yui.jsPlumb-1.3.8-all.js b/archive/1.3.8/js/yui.jsPlumb-1.3.8-all.js similarity index 100% rename from build/1.3.8/js/yui.jsPlumb-1.3.8-all.js rename to archive/1.3.8/js/yui.jsPlumb-1.3.8-all.js diff --git a/archive/1.3.8/jsPlumb-1.3.8-RC1.js b/archive/1.3.8/jsPlumb-1.3.8-RC1.js new file mode 100644 index 000000000..86d3c8a2a --- /dev/null +++ b/archive/1.3.8/jsPlumb-1.3.8-RC1.js @@ -0,0 +1,5383 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.8 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the jsPlumb core code. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + /** + * Class:jsPlumb + * The jsPlumb engine, registered as a static object in the window. This object contains all of the methods you will use to + * create and maintain Connections and Endpoints. + */ + + var canvasAvailable = !!document.createElement('canvas').getContext, + svgAvailable = !!window.SVGAngle || document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"), + // http://stackoverflow.com/questions/654112/how-do-you-detect-support-for-vml-or-svg-in-a-browser + vmlAvailable = function() { + if(vmlAvailable.vml == undefined) { + var a = document.body.appendChild(document.createElement('div')); + a.innerHTML = ''; + var b = a.firstChild; + b.style.behavior = "url(#default#VML)"; + vmlAvailable.vml = b ? typeof b.adj == "object": true; + a.parentNode.removeChild(a); + } + return vmlAvailable.vml; + }; + + var _findWithFunction = function(a, f) { + if (a) + for (var i = 0; i < a.length; i++) if (f(a[i])) return i; + return -1; + }, + _indexOf = function(l, v) { + return _findWithFunction(l, function(_v) { return _v == v; }); + }, + _removeWithFunction = function(a, f) { + var idx = _findWithFunction(a, f); + if (idx > -1) a.splice(idx, 1); + return idx != -1; + }, + _remove = function(l, v) { + var idx = _indexOf(l, v); + if (idx > -1) l.splice(idx, 1); + return idx != -1; + }, + // TODO support insert index + _addWithFunction = function(list, item, hashFunction) { + if (_findWithFunction(list, hashFunction) == -1) list.push(item); + }, + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = [], map[key] = l; + } + l.push(value); + return l; + }, + /** + an isArray function that even works across iframes...see here: + + http://tobyho.com/2011/01/28/checking-types-in-javascript/ + + i was originally using "a.constructor == Array" as a test. + */ + _isArray = function(a) { + return Object.prototype.toString.call(a) === "[object Array]"; + }, + _isString = function(s) { + return typeof s === "string"; + }, + _isObject = function(o) { + return Object.prototype.toString.call(o) === "[object Object]"; + }; + + // for those browsers that dont have it. they still don't have it! but at least they won't crash. + if (!window.console) + window.console = { time:function(){}, timeEnd:function(){}, group:function(){}, groupEnd:function(){}, log:function(){} }; + + var _connectionBeingDragged = null, + _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }, + _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }, + _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }, + _logEnabled = true, + _log = function() { + if (_logEnabled && typeof console != "undefined") { + try { + var msg = arguments[arguments.length - 1]; + console.log(msg); + } + catch (e) {} + } + }, + _group = function(g) { if (_logEnabled && typeof console != "undefined") console.group(g); }, + _groupEnd = function(g) { if (_logEnabled && typeof console != "undefined") console.groupEnd(g); }, + _time = function(t) { if (_logEnabled && typeof console != "undefined") console.time(t); }, + _timeEnd = function(t) { if (_logEnabled && typeof console != "undefined") console.timeEnd(t); }; + + /** + * EventGenerator + * Superclass for objects that generate events - jsPlumb extends this, as does jsPlumbUIComponent, which all the UI elements extend. + */ + EventGenerator = function() { + var _listeners = {}, self = this; + + // this is a list of events that should re-throw any errors that occur during their dispatch. as of 1.3.0 this is private to + // jsPlumb, but it seems feasible that people might want to manipulate this list. the thinking is that we don't want event + // listeners to bring down jsPlumb - or do we. i can't make up my mind about this, but i know i want to hear about it if the "ready" + // event fails, because then my page has most likely not initialised. so i have this halfway-house solution. it will be interesting + // to hear what other people think. + var eventsToDieOn = [ "ready" ]; + + /* + * Binds a listener to an event. + * + * Parameters: + * event - name of the event to bind to. + * listener - function to execute. + */ + this.bind = function(event, listener) { + _addToList(_listeners, event, listener); + }; + /* + * Fires an update for the given event. + * + * Parameters: + * event - event to fire + * value - value to pass to the event listener(s). + * originalEvent - the original event from the browser + */ + this.fire = function(event, value, originalEvent) { + if (_listeners[event]) { + for ( var i = 0; i < _listeners[event].length; i++) { + // doing it this way rather than catching and then possibly re-throwing means that an error propagated by this + // method will have the whole call stack available in the debugger. + //if (_findIndex(eventsToDieOn, event) != -1) + if (_findWithFunction(eventsToDieOn, function(e) { return e === event}) != -1) + _listeners[event][i](value, originalEvent); + else { + // for events we don't want to die on, catch and log. + try { + _listeners[event][i](value, originalEvent); + } catch (e) { + _log("jsPlumb: fire failed for event " + event + " : " + e); + } + } + } + } + }; + /* + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + this.clearListeners = function(event) { + if (event) + delete _listeners[event]; + else { + delete _listeners; + _listeners = {}; + } + }; + + this.getListener = function(forEvent) { + return _listeners[forEvent]; + }; + }, + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + _timestamp = function() { return "" + (new Date()).getTime(); }, + + /* + * Class:jsPlumbUIComponent + * Abstract superclass for UI components Endpoint and Connection. Provides the abstraction of paintStyle/hoverPaintStyle, + * and also extends EventGenerator to provide the bind and fire methods. + */ + jsPlumbUIComponent = function(params) { + var self = this, a = arguments, _hover = false, parameters = params.parameters || {}, idPrefix = self.idPrefix, + id = idPrefix + (new Date()).getTime(); + self._jsPlumb = params["_jsPlumb"]; + self.getId = function() { return id; }; + self.tooltip = params.tooltip; + self.hoverClass = params.hoverClass; + + // all components can generate events + EventGenerator.apply(this); + // all components get this clone function. + // TODO issue 116 showed a problem with this - it seems 'a' that is in + // the clone function's scope is shared by all invocations of it, the classic + // JS closure problem. for now, jsPlumb does a version of this inline where + // it used to call clone. but it would be nice to find some time to look + // further at this. + this.clone = function() { + var o = new Object(); + self.constructor.apply(o, a); + return o; + }; + + this.getParameter = function(name) { return parameters[name]; }, + this.getParameters = function() { + return parameters; + }, + this.setParameter = function(name, value) { parameters[name] = value; }, + this.setParameters = function(p) { parameters = p; }, + this.overlayPlacements = [], + this.paintStyle = null, + this.hoverPaintStyle = null; + + // user can supply a beforeDetach callback, which will be executed before a detach + // is performed; returning false prevents the detach. + var beforeDetach = params.beforeDetach; + this.isDetachAllowed = function(connection) { + var r = self._jsPlumb.checkCondition("beforeDetach", connection ); + if (beforeDetach) { + try { + r = beforeDetach(connection); + } + catch (e) { _log("jsPlumb: beforeDetach callback failed", e); } + } + return r; + }; + + // user can supply a beforeDrop callback, which will be executed before a dropped + // connection is confirmed. user can return false to reject connection. + var beforeDrop = params.beforeDrop; + this.isDropAllowed = function(sourceId, targetId, scope) { + var r = self._jsPlumb.checkCondition("beforeDrop", { sourceId:sourceId, targetId:targetId, scope:scope }); + if (beforeDrop) { + try { + r = beforeDrop({ sourceId:sourceId, targetId:targetId, scope:scope }); + } + catch (e) { _log("jsPlumb: beforeDrop callback failed", e); } + } + return r; + }; + + // helper method to update the hover style whenever it, or paintStyle, changes. + // we use paintStyle as the foundation and merge hoverPaintStyle over the + // top. + var _updateHoverStyle = function() { + if (self.paintStyle && self.hoverPaintStyle) { + var mergedHoverStyle = {}; + jsPlumb.extend(mergedHoverStyle, self.paintStyle); + jsPlumb.extend(mergedHoverStyle, self.hoverPaintStyle); + delete self["hoverPaintStyle"]; + // we want the fillStyle of paintStyle to override a gradient, if possible. + if (mergedHoverStyle.gradient && self.paintStyle.fillStyle) + delete mergedHoverStyle["gradient"]; + self.hoverPaintStyle = mergedHoverStyle; + } + }; + + /* + * Sets the paint style and then repaints the element. + * + * Parameters: + * style - Style to use. + */ + this.setPaintStyle = function(style, doNotRepaint) { + self.paintStyle = style; + self.paintStyleInUse = self.paintStyle; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /* + * Sets the paint style to use when the mouse is hovering over the element. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the component will not be repainted. useful when setting things up initially. + */ + this.setHoverPaintStyle = function(style, doNotRepaint) { + self.hoverPaintStyle = style; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /* + * sets/unsets the hover state of this element. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + this.setHover = function(hover, ignoreAttachedElements, timestamp) { + // while dragging, we ignore these events. this keeps the UI from flashing and + // swishing and whatevering. + if (!self._jsPlumb.currentlyDragging && !self._jsPlumb.isHoverSuspended()) { + + _hover = hover; + if (self.hoverClass != null && self.canvas != null) { + if (hover) + jpcl.addClass(self.canvas, self.hoverClass); + else + jpcl.removeClass(self.canvas, self.hoverClass); + } + if (self.hoverPaintStyle != null) { + self.paintStyleInUse = hover ? self.hoverPaintStyle : self.paintStyle; + timestamp = timestamp || _timestamp(); + self.repaint({timestamp:timestamp, recalc:false}); + } + // get the list of other affected elements, if supported by this component. + // for a connection, its the endpoints. for an endpoint, its the connections! surprise. + if (self.getAttachedElements && !ignoreAttachedElements) + _updateAttachedElements(hover, _timestamp(), self); + } + }; + + this.isHover = function() { return _hover; }; + + var jpcl = jsPlumb.CurrentLibrary, + events = [ "click", "dblclick", "mouseenter", "mouseout", "mousemove", "mousedown", "mouseup", "contextmenu" ], + eventFilters = { "mouseout":"mouseexit" }, + bindOne = function(o, c, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.bind(o, evt, function(ee) { + c.fire(filteredEvent, c, ee); + }); + }, + unbindOne = function(o, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.unbind(o, evt); + }; + + this.attachListeners = function(o, c) { + for (var i = 0; i < events.length; i++) { + bindOne(o, c, events[i]); + } + }; + + var _updateAttachedElements = function(state, timestamp, sourceElement) { + var affectedElements = self.getAttachedElements(); // implemented in subclasses + if (affectedElements) { + for (var i = 0; i < affectedElements.length; i++) { + if (!sourceElement || sourceElement != affectedElements[i]) + affectedElements[i].setHover(state, true, timestamp); // tell the attached elements not to inform their own attached elements. + } + } + }; + + this.reattachListenersForElement = function(o) { + if (arguments.length > 1) { + for (var i = 0; i < events.length; i++) + unbindOne(o, events[i]); + for (var i = 1; i < arguments.length; i++) + self.attachListeners(o, arguments[i]); + } + }; + }, + + overlayCapableJsPlumbUIComponent = function(params) { + jsPlumbUIComponent.apply(this, arguments); + var self = this; + /* + * Property: overlays + * List of Overlays for this component. + */ + this.overlays = []; + + var processOverlay = function(o) { + var _newOverlay = null; + if (_isArray(o)) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0], + // make a copy of the object so as not to mess up anyone else's reference... + p = jsPlumb.extend({component:self, _jsPlumb:self._jsPlumb}, o[1]); + if (o.length == 3) jsPlumb.extend(p, o[2]); + _newOverlay = new jsPlumb.Overlays[self._jsPlumb.getRenderMode()][type](p); + if (p.events) { + for (var evt in p.events) { + _newOverlay.bind(evt, p.events[evt]); + } + } + } else if (o.constructor == String) { + _newOverlay = new jsPlumb.Overlays[self._jsPlumb.getRenderMode()][o]({component:self, _jsPlumb:self._jsPlumb}); + } else { + _newOverlay = o; + } + + self.overlays.push(_newOverlay); + }, + calculateOverlaysToAdd = function(params) { + var defaultKeys = self.defaultOverlayKeys || [], + o = params.overlays, + checkKey = function(k) { + return self._jsPlumb.Defaults[k] || jsPlumb.Defaults[k] || []; + }; + + if (!o) o = []; + + for (var i = 0; i < defaultKeys.length; i++) + o.unshift.apply(o, checkKey(defaultKeys[i])); + + return o; + } + + var _overlays = calculateOverlaysToAdd(params);//params.overlays || self._jsPlumb.Defaults.Overlays; + if (_overlays) { + for (var i = 0; i < _overlays.length; i++) { + processOverlay(_overlays[i]); + } + } + + // overlay finder helper method + var _getOverlayIndex = function(id) { + var idx = -1; + for (var i = 0; i < self.overlays.length; i++) { + if (id === self.overlays[i].id) { + idx = i; + break; + } + } + return idx; + }; + + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + this.addOverlay = function(overlay) { + processOverlay(overlay); + self.repaint(); + }; + + /* + * Function: getOverlay + * Gets an overlay, by ID. Note: by ID. You would pass an 'id' parameter + * in to the Overlay's constructor arguments, and then use that to retrieve + * it via this method. + */ + this.getOverlay = function(id) { + var idx = _getOverlayIndex(id); + return idx >= 0 ? self.overlays[idx] : null; + }; + + /* + * Function:getOverlays + * Gets all the overlays for this component. + */ + this.getOverlays = function() { + return self.overlays; + }; + + /* + * Function: hideOverlay + * Hides the overlay specified by the given id. + */ + this.hideOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.hide(); + }; + + this.hideOverlays = function() { + for (var i = 0; i < self.overlays.length; i++) + self.overlays[i].hide(); + }; + + /* + * Function: showOverlay + * Shows the overlay specified by the given id. + */ + this.showOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.show(); + }; + + this.showOverlays = function() { + for (var i = 0; i < self.overlays.length; i++) + self.overlays[i].show(); + }; + + /** + * Function: removeAllOverlays + * Removes all overlays from the Connection, and then repaints. + */ + this.removeAllOverlays = function() { + self.overlays.splice(0, self.overlays.length); + self.repaint(); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + this.removeOverlay = function(overlayId) { + var idx = _getOverlayIndex(overlayId); + if (idx != -1) { + var o = self.overlays[idx]; + o.cleanup(); + self.overlays.splice(idx, 1); + } + }; + + /** + * Function:removeOverlays + * Removes a set of overlays by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + this.removeOverlays = function() { + for (var i = 0; i < arguments.length; i++) + self.removeOverlay(arguments[i]); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + var _internalLabelOverlayId = "__label", + _makeLabelOverlay = function(params) { + + var _params = { + cssClass:params.cssClass, + labelStyle : this.labelStyle, + id:_internalLabelOverlayId, + component:self, + _jsPlumb:self._jsPlumb + }, + mergedParams = jsPlumb.extend(_params, params); + + return new jsPlumb.Overlays[self._jsPlumb.getRenderMode()].Label( mergedParams ); + }; + if (params.label) { + var loc = params.labelLocation || self.defaultLabelLocation || 0.5, + labelStyle = params.labelStyle || self._jsPlumb.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.overlays.push(_makeLabelOverlay({ + label:params.label, + location:loc, + labelStyle:labelStyle + })); + } + + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String, a Function that returns a String, or a params object containing { "label", "labelStyle", "location", "cssClass" } + */ + this.setLabel = function(l) { + var lo = self.getOverlay(_internalLabelOverlayId); + if (!lo) { + var params = l.constructor == String || l.constructor == Function ? { label:l } : l; + lo = _makeLabelOverlay(params); + this.overlays.push(lo); + } + else { + if (l.constructor == String || l.constructor == Function) lo.setLabel(l); + else { + if (l.label) lo.setLabel(l.label); + if (l.location) lo.setLocation(l.location); + } + } + + self.repaint(); + }; + + /* + Function:getLabel + Returns the label text for this component (or a function if you are labelling with a function). + This does not return the overlay itself; this is a convenience method which is a pair with + setLabel; together they allow you to add and access a Label Overlay without having to create the + Overlay object itself. For access to the underlying label overlay that jsPlumb has created, + use getLabelOverlay. + */ + this.getLabel = function() { + var lo = self.getOverlay(_internalLabelOverlayId); + return lo != null ? lo.getLabel() : null; + }; + + /* + Function:getLabelOverlay + Returns the underlying internal label overlay, which will exist if you specified a label on + a connect or addEndpoint call, or have called setLabel at any stage. + */ + this.getLabelOverlay = function() { + return self.getOverlay(_internalLabelOverlayId); + } + }, + + _bindListeners = function(obj, _self, _hoverFunction) { + obj.bind("click", function(ep, e) { _self.fire("click", _self, e); }); + obj.bind("dblclick", function(ep, e) { _self.fire("dblclick", _self, e); }); + obj.bind("contextmenu", function(ep, e) { _self.fire("contextmenu", _self, e); }); + obj.bind("mouseenter", function(ep, e) { + if (!_self.isHover()) { + _hoverFunction(true); + _self.fire("mouseenter", _self, e); + } + }); + obj.bind("mouseexit", function(ep, e) { + if (_self.isHover()) { + _hoverFunction(false); + _self.fire("mouseexit", _self, e); + } + }); + }; + + var _jsPlumbInstanceIndex = 0, + getInstanceIndex = function() { + var i = _jsPlumbInstanceIndex + 1; + _jsPlumbInstanceIndex++; + return i; + }; + + var jsPlumbInstance = function(_defaults) { + + /* + * Property: Defaults + * + * These are the default settings for jsPlumb. They are what will be used if you do not supply specific pieces of information + * to the various API calls. A convenient way to implement your own look and feel can be to override these defaults + * by including a script somewhere after the jsPlumb include, but before you make any calls to jsPlumb. + * + * Properties: + * - *Anchor* The default anchor to use for all connections (both source and target). Default is "BottomCenter". + * - *Anchors* The default anchors to use ([source, target]) for all connections. Defaults are ["BottomCenter", "BottomCenter"]. + * - *ConnectionsDetachable* Whether or not connections are detachable by default (using the mouse). Defults to true. + * - *ConnectionOverlays* The default overlay definitions for Connections. Defaults to an empty list. + * - *Connector* The default connector definition to use for all connections. Default is "Bezier". + * - *Container* Optional selector or element id that instructs jsPlumb to append elements it creates to a specific element. + * - *DragOptions* The default drag options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *DropOptions* The default drop options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *Endpoint* The default endpoint definition to use for all connections (both source and target). Default is "Dot". + * - *EndpointOverlays* The default overlay definitions for Endpoints. Defaults to an empty list. + * - *Endpoints* The default endpoint definitions ([ source, target ]) to use for all connections. Defaults are ["Dot", "Dot"]. + * - *EndpointStyle* The default style definition to use for all endpoints. Default is fillStyle:"#456". + * - *EndpointStyles* The default style definitions ([ source, target ]) to use for all endpoints. Defaults are empty. + * - *EndpointHoverStyle* The default hover style definition to use for all endpoints. Default is null. + * - *EndpointHoverStyles* The default hover style definitions ([ source, target ]) to use for all endpoints. Defaults are null. + * - *HoverPaintStyle* The default hover style definition to use for all connections. Defaults are null. + * - *LabelStyle* The default style to use for label overlays on connections. + * - *LogEnabled* Whether or not the jsPlumb log is enabled. defaults to false. + * - *Overlays* The default overlay definitions (for both Connections and Endpoint). Defaults to an empty list. + * - *MaxConnections* The default maximum number of connections for an Endpoint. Defaults to 1. + * - *PaintStyle* The default paint style for a connection. Default is line width of 8 pixels, with color "#456". + * - *RenderMode* What mode to use to paint with. If you're on IE<9, you don't really get to choose this. You'll just get VML. Otherwise, the jsPlumb default is to use SVG. + * - *Scope* The default "scope" to use for connections. Scope lets you assign connections to different categories. + */ + this.Defaults = { + Anchor : "BottomCenter", + Anchors : [ null, null ], + ConnectionsDetachable : true, + ConnectionOverlays : [ ], + Connector : "Bezier", + Container : null, + DragOptions : { }, + DropOptions : { }, + Endpoint : "Dot", + EndpointOverlays : [ ], + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : "#456" }, + EndpointStyles : [ null, null ], + EndpointHoverStyle : null, + EndpointHoverStyles : [ null, null ], + HoverPaintStyle : null, + LabelStyle : { color : "black" }, + LogEnabled : false, + Overlays : [ ], + MaxConnections : 1, + PaintStyle : { lineWidth : 8, strokeStyle : "#456" }, + //Reattach:false, + RenderMode : "svg", + Scope : "jsPlumb_DefaultScope" + }; + if (_defaults) jsPlumb.extend(this.Defaults, _defaults); + + this.logEnabled = this.Defaults.LogEnabled; + + EventGenerator.apply(this); + var _currentInstance = this, + _instanceIndex = getInstanceIndex(), + _bb = _currentInstance.bind, + _initialDefaults = {}; + + for (var i in this.Defaults) + _initialDefaults[i] = this.Defaults[i]; + + this.bind = function(event, fn) { + if ("ready" === event && initialized) fn(); + else _bb.apply(_currentInstance,[event, fn]); + }; + + /* + Function: importDefaults + Imports all the given defaults into this instance of jsPlumb. + */ + _currentInstance.importDefaults = function(d) { + for (var i in d) { + _currentInstance.Defaults[i] = d[i]; + } + }; + + /* + Function:restoreDefaults + Restores the default settings to "factory" values. + */ + _currentInstance.restoreDefaults = function() { + _currentInstance.Defaults = jsPlumb.extend({}, _initialDefaults); + }; + + var log = null, + repaintFunction = function() { + jsPlumb.repaintEverything(); + }, + automaticRepaint = true, + repaintEverything = function() { + if (automaticRepaint) + repaintFunction(); + }, + resizeTimer = null, + initialized = false, + connectionsByScope = {}, + /** + * map of element id -> endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + endpointsByElement = {}, + endpointsByUUID = {}, + offsets = {}, + offsetTimestamps = {}, + floatingConnections = {}, + draggableStates = {}, + canvasList = [], + sizes = [], + //listeners = {}, // a map: keys are event types, values are lists of listeners. + DEFAULT_SCOPE = this.Defaults.Scope, + renderMode = null, // will be set in init() + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }, + + /** + * appends an element to some other element, which is calculated as follows: + * + * 1. if _currentInstance.Defaults.Container exists, use that element. + * 2. if the 'parent' parameter exists, use that. + * 3. otherwise just use the document body. + * + */ + _appendElement = function(el, parent) { + if (_currentInstance.Defaults.Container) + jsPlumb.CurrentLibrary.appendElement(el, _currentInstance.Defaults.Container); + else if (!parent) + document.body.appendChild(el); + else + jsPlumb.CurrentLibrary.appendElement(el, parent); + }, + + _curIdStamp = 1, + _idstamp = function() { return "" + _curIdStamp++; }, + + /** + * YUI, for some reason, put the result of a Y.all call into an object that contains + * a '_nodes' array, instead of handing back an array-like object like the other + * libraries do. + */ + _convertYUICollection = function(c) { + return c._nodes ? c._nodes : c; + }, + + _suspendDrawing = false, + /* + sets whether or not to suspend drawing. you should use this if you need to connect a whole load of things in one go. + it will save you a lot of time. + */ + _setSuspendDrawing = function(val, repaintAfterwards) { + _suspendDrawing = val; + if (repaintAfterwards) _currentInstance.repaintEverything(); + }, + + /** + * Draws an endpoint and its connections. this is the main entry point into drawing connections as well + * as endpoints, since jsPlumb is endpoint-centric under the hood. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + _draw = function(element, ui, timestamp) { + if (!_suspendDrawing) { + var id = _getAttribute(element, "id"), + repaintEls = _currentInstance.dragManager.getElementsForDraggable(id); + + if (timestamp == null) timestamp = _timestamp(); + + _currentInstance.anchorManager.redraw(id, ui, timestamp); + + if (repaintEls) { + for (var i in repaintEls) { + _currentInstance.anchorManager.redraw(repaintEls[i].id, ui, timestamp, repaintEls[i].offset); + } + } + } + }, + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + _elementProxy = function(element, fn) { + var retVal = null; + if (_isArray(element)) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }, + + /** + * gets an Endpoint by uuid. + */ + _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }, + + /** + * inits a draggable if it's not already initialised. + */ + _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? false : isDraggable, + jpcl = jsPlumb.CurrentLibrary; + if (draggable) { + if (jpcl.isDragSupported(element) && !jpcl.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jpcl.dragEvents["drag"], + stopEvent = jpcl.dragEvents["stop"], + startEvent = jpcl.dragEvents["start"]; + + options[startEvent] = _wrap(options[startEvent], function() { + _currentInstance.setHoverSuspended(true); + }); + + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jpcl.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jpcl.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + _currentInstance.setHoverSuspended(false); + }); + draggableStates[_getId(element)] = true; + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jpcl.initDraggable(element, options, false); + _currentInstance.dragManager.register(element); + } + } + }, + + /* + * prepares a final params object that can be passed to _newConnection, taking into account defaults, events, etc. + */ + _prepareConnectionParams = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + // hotwire endpoints passed as source or target to sourceEndpoint/targetEndpoint, respectively. + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not full. + // source: + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + // target: + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + // copy in any connectorOverlays that were specified on the source endpoint. + // it doesnt copy target endpoint overlays. i'm not sure if we want it to or not. + if (_p.sourceEndpoint && _p.sourceEndpoint.connectorOverlays) { + _p.overlays = _p.overlays || []; + for (var i = 0; i < _p.sourceEndpoint.connectorOverlays.length; i++) { + _p.overlays.push(_p.sourceEndpoint.connectorOverlays[i]); + } + } + + // tooltip. params.tooltip takes precedence, then sourceEndpoint.connectorTooltip. + _p.tooltip = params.tooltip; + if (!_p.tooltip && _p.sourceEndpoint && _p.sourceEndpoint.connectorTooltip) + _p.tooltip = _p.sourceEndpoint.connectorTooltip; + + // if there's a target specified (which of course there should be), and there is no + // target endpoint specified, and 'newConnection' was not set to true, then we check to + // see if a prior call to makeTarget has provided us with the specs for the target endpoint, and + // we use those if so. additionally, if the makeTarget call was specified with 'uniqueEndpoint' set + // to true, then if that target endpoint has already been created, we re-use it. + if (_p.target && !_p.target.endpoint && !_p.targetEndpoint && !_p.newConnection) { + var tid = _getId(_p.target), + tep =_targetEndpointDefinitions[tid], + existingUniqueEndpoint = _targetEndpoints[tid]; + + if (tep) { + // check for max connections?? + var newEndpoint = existingUniqueEndpoint != null ? existingUniqueEndpoint : _currentInstance.addEndpoint(_p.target, tep); + if (_targetEndpointsUnique[tid]) _targetEndpoints[tid] = newEndpoint; + _p.targetEndpoint = newEndpoint; + newEndpoint._makeTargetCreator = true; + } + } + + // same thing, but for source. + if (_p.source && !_p.source.endpoint && !_p.sourceEndpoint && !_p.newConnection) { + var tid = _getId(_p.source), + tep = _sourceEndpointDefinitions[tid], + existingUniqueEndpoint = _sourceEndpoints[tid]; + + if (tep) { + + var newEndpoint = existingUniqueEndpoint != null ? existingUniqueEndpoint : _currentInstance.addEndpoint(_p.source, tep); + if (_sourceEndpointsUnique[tid]) _sourceEndpoints[tid] = newEndpoint; + _p.sourceEndpoint = newEndpoint; + } + } + + return _p; + }, + + _newConnection = function(params) { + var connectionFunc = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + endpointFunc = _currentInstance.Defaults.EndpointType || Endpoint, + parent = jsPlumb.CurrentLibrary.getParent; + + if (params.container) + params["parent"] = params.container; + else { + if (params.sourceEndpoint) + params["parent"] = params.sourceEndpoint.parent; + else if (params.source.constructor == endpointFunc) + params["parent"] = params.source.parent; + else params["parent"] = parent(params.source); + } + + params["_jsPlumb"] = _currentInstance; + var con = new connectionFunc(params); + con.id = "con_" + _idstamp(); + _eventFireProxy("click", "click", con); + _eventFireProxy("dblclick", "dblclick", con); + _eventFireProxy("contextmenu", "contextmenu", con); + return con; + }, + + /** + * adds the connection to the backing model, fires an event if necessary and then redraws + */ + _finaliseConnection = function(jpc, params, originalEvent) { + params = params || {}; + // add to list of connections (by scope). + if (!jpc.suspendedEndpoint) + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + if (!params.doNotFireConnectionEvent && params.fireEvent !== false) { + _currentInstance.fire("jsPlumbConnection", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }, originalEvent); + } + // always inform the anchor manager + // except that if jpc has a suspended endpoint it's not true to say the + // connection is new; it has just (possibly) moved. the question is whether + // to make that call here or in the anchor manager. i think perhaps here. + _currentInstance.anchorManager.newConnection(jpc); + // force a paint + _draw(jpc.source); + }, + + _eventFireProxy = function(event, proxyEvent, obj) { + obj.bind(event, function(originalObject, originalEvent) { + _currentInstance.fire(proxyEvent, obj, originalEvent); + }); + }, + + /** + * for the given endpoint params, returns an appropriate parent element for the UI elements that will be added. + * this function is used by _newEndpoint (directly below), and also in the makeSource function in jsPlumb. + * + * the logic is to first look for a "container" member of params, and pass that back if found. otherwise we + * handoff to the 'getParent' function in the current library. + */ + _getParentFromParams = function(params) { + if (params.container) + return params.container; + else { + var tag = jsPlumb.CurrentLibrary.getTagName(params.source), + p = jsPlumb.CurrentLibrary.getParent(params.source); + if (tag && tag.toLowerCase() === "td") + return jsPlumb.CurrentLibrary.getParent(p); + else return p; + } + }, + + /** + factory method to prepare a new endpoint. this should always be used instead of creating Endpoints + manually, since this method attaches event listeners and an id. + */ + _newEndpoint = function(params) { + var endpointFunc = _currentInstance.Defaults.EndpointType || Endpoint; + params.parent = _getParentFromParams(params); + params["_jsPlumb"] = _currentInstance; + var ep = new endpointFunc(params); + ep.id = "ep_" + _idstamp(); + _eventFireProxy("click", "endpointClick", ep); + _eventFireProxy("dblclick", "endpointDblClick", ep); + _eventFireProxy("contextmenu", "contextmenu", ep); + return ep; + }, + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + _operation = function(elId, func, endpointFunc) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + if (endpointFunc) endpointFunc(endpoints[i]); + } + } + }, + /** + * perform an operation on all elements. + */ + _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }, + + /** + * helper to remove an element from the DOM. + */ + _removeElement = function(element, parent) { + if (element != null && element.parentNode != null) { + element.parentNode.removeChild(element); + } + }, + /** + * helper to remove a list of elements from the DOM. + */ + _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }, + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }, + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + _setVisible = function(el, state, alsoChangeEndpoints) { + state = state === "block"; + var endpointFunc = null; + if (alsoChangeEndpoints) { + if (state) endpointFunc = function(ep) { + ep.setVisible(true, true, true); + }; + else endpointFunc = function(ep) { + ep.setVisible(false, true, true); + }; + } + var id = _getAttribute(el, "id"); + _operation(id, function(jpc) { + if (state && alsoChangeEndpoints) { + // this test is necessary because this functionality is new, and i wanted to maintain backwards compatibility. + // this block will only set a connection to be visible if the other endpoint in the connection is also visible. + var oidx = jpc.sourceId === id ? 1 : 0; + if (jpc.endpoints[oidx].isVisible()) jpc.setVisible(true); + } + else // the default behaviour for show, and what always happens for hide, is to just set the visibility without getting clever. + jpc.setVisible(state); + }, endpointFunc); + }, + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? false : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }, + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + _toggleVisible = function(elId, changeEndpoints) { + var endpointFunc = null; + if (changeEndpoints) { + endpointFunc = function(ep) { + var state = ep.isVisible(); + ep.setVisible(!state); + }; + } + _operation(elId, function(jpc) { + var state = jpc.isVisible(); + jpc.setVisible(!state); + }, endpointFunc); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }, + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return offsets[elId]; + } + if (recalc || !offset) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + if (sizes[elId] == null) { + var s = _getElementObject(elId); + if (s != null) + sizes[elId] = _getSize(s); + } + } + + if(offsets[elId] && !offsets[elId].right) { + offsets[elId].right = offsets[elId].left + sizes[elId][0]; + offsets[elId].bottom = offsets[elId].top + sizes[elId][1]; + offsets[elId].width = sizes[elId][0]; + offsets[elId].height = sizes[elId][1]; + offsets[elId].centerx = offsets[elId].left + (offsets[elId].width / 2); + offsets[elId].centery = offsets[elId].top + (offsets[elId].height / 2); + } + return offsets[elId]; + }, + + // TODO comparison performance + _getCachedData = function(elId) { + var o = offsets[elId]; + if (!o) o = _updateOffset({elId:elId}); + return {o:o, s:sizes[elId]}; + }, + + /** + * gets an id for the given element, creating and setting one if + * necessary. the id is of the form + * + * jsPlumb__ + * + * where "index in instance" is a monotonically increasing integer that starts at 0, + * for each instance. this method is used not only to assign ids to elements that do not + * have them but also to connections and endpoints. + */ + _getId = function(element, uuid, doNotCreateIfNotFound) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else if (arguments.length == 1 || (arguments.length == 3 && arguments[2])) + id = "jsPlumb_" + _instanceIndex + "_" + _idstamp(); + _setAttribute(ele, "id", id); + } + return id; + }, + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, "jsPlumb function failed : " + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, "wrapped function failed : " + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = "_jsPlumb_connector"; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = "_jsPlumb_endpoint"; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = "_jsPlumb_overlay"; + + this.Anchors = {}; + + this.Connectors = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Endpoints = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Overlays = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + +// ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***************************************** + /* + * Function: bind + * Bind to an event on jsPlumb. + * + * Parameters: + * event - the event to bind. Available events on jsPlumb are: + * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback. + * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback. + * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback. + * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback. + * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback. + * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback. + * + * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event. + */ + + /* + * Function: clearListeners + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + +// *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *********************************************************** + + +// --------------------------- jsPLumbInstance public API --------------------------------------------------------- + + /* + Function: addClass + + Helper method to abstract out differences in setting css classes on the different renderer types. + */ + this.addClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.addClass(el, clazz); + }; + + /* + Function: removeClass + + Helper method to abstract out differences in setting css classes on the different renderer types. + */ + this.removeClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.removeClass(el, clazz); + }; + + /* + Function: hasClass + + Helper method to abstract out differences in testing for css classes on the different renderer types. + */ + this.hasClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.hasClass(el, clazz); + }; + + /* + Function: addEndpoint + + Adds an to a given element or elements. + + Parameters: + + el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + params - Object containing Endpoint constructor arguments. For more information, see . + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some + shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in + this object are anything that 'params' can contain. See . + + Returns: + The newly created , if el referred to a single element. Otherwise, an array of newly created s. + + See Also: + + */ + this.addEndpoint = function(el, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // YUI wrapper + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + var _el = _getElementObject(inputs[i]), id = _getId(_el); + p.source = _el; + _updateOffset({ elId : id }); + var e = _newEndpoint(p); + if (p.parentAnchor) e.parentAnchor = p.parentAnchor; + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + results.push(e); + _currentInstance.dragManager.endpointAdded(_el); + } + + return results.length == 1 ? results[0] : results; + }; + + /* + Function: addEndpoints + Adds a list of s to a given element or elements. + + Parameters: + target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See 's constructor documentation. + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created s, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(el, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams); + if (_isArray(e)) + Array.prototype.push.apply(results, e); + else results.push(e); + } + return results; + }; + + /* + Function: animate + This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating + the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // onComplete repaints, just to make sure everything looks good at the end of the animation. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /** + * checks for a listener for the given condition, executing it if found, passing in the given value. + * condition listeners would have been attached using "bind" (which is, you could argue, now overloaded, since + * firing click events etc is a bit different to what this does). i thought about adding a "bindCondition" + * or something, but decided against it, for the sake of simplicity. jsPlumb will never fire one of these + * condition events anyway. + */ + this.checkCondition = function(conditionName, value) { + var l = _currentInstance.getListener(conditionName); + var r = true; + if (l && l.length > 0) { + try { + for (var i = 0 ; i < l.length; i++) { + r = r && l[i](value); + } + } + catch (e) { + _log(_currentInstance, "cannot check condition [" + conditionName + "]" + e); + } + } + return r; + }; + + /* + Function: connect + Establishes a between two elements (or s, which are themselves registered to elements). + + Parameters: + params - Object containing constructor arguments for the Connection. See 's constructor documentation. + referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of + Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection. + + Returns: + The newly created . + */ + this.connect = function(params, referenceParams) { + // prepare a final set of parameters to create connection with + var _p = _prepareConnectionParams(params, referenceParams); + // TODO probably a nicer return value if the connection was not made. _prepareConnectionParams + // will return null (and log something) if either endpoint was full. what would be nicer is to + // create a dedicated 'error' object. + if (_p) { + // a connect call will delete its created endpoints on detach, unless otherwise specified. + // this is because the endpoints belong to this connection only, and are no use to + // anyone else, so they hang around like a bad smell. + if (_p.deleteEndpointsOnDetach == null) + _p.deleteEndpointsOnDetach = true; + + // create the connection. it is not yet registered + var jpc = _newConnection(_p); + // now add it the model, fire an event, and redraw + _finaliseConnection(jpc, _p); + return jpc; + } + }; + + /* + Function: deleteEndpoint + Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too) + + Parameters: + object - either an object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElements(endpoint.endpoint.getDisplayElements()); + _currentInstance.anchorManager.deleteEndpoint(endpoint); + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + _currentInstance.dragManager.endpointDeleted(endpoint); + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every , and their associated s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc, doFireEvent, originalEvent) { + // may have been given a connection, or in special cases, an object + var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + argIsConnection = jpc.constructor == connType, + params = argIsConnection ? { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + } : jpc; + + if (doFireEvent) _currentInstance.fire("jsPlumbConnectionDetached", params, originalEvent); + _currentInstance.anchorManager.connectionDetached(params); + }, + /** + fires an event to indicate an existing connection is being dragged. + */ + fireConnectionDraggingEvent = function(jpc) { + _currentInstance.fire("connectionDrag", jpc); + }, + fireConnectionDragStopEvent = function(jpc) { + _currentInstance.fire("connectionDragStop", jpc); + }; + + + /* + Function: detach + Detaches and then removes a . From 1.3.5 this method has been altered to remove support for + specifying Connections by various parameters; you can now pass in a Connection as the first argument and + an optional parameters object as a second argument. If you need the functionality this method provided + before 1.3.5 then you should use the getConnections method to get the list of Connections to detach, and + then iterate through them, calling this for each one. + + Parameters: + connection - the to detach + params - optional parameters to the detach call. valid values here are + fireEvent : defaults to false; indicates you want jsPlumb to fire a connection + detached event. The thinking behind this is that if you made a programmatic + call to detach an event, you probably don't need the callback. + forceDetach : defaults to false. allows you to override any beforeDetach listeners that may be registered. + + Returns: + true if successful, false if not. + */ + this.detach = function() { + + if (arguments.length == 0) return; + var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + firstArgIsConnection = arguments[0].constructor == connType, + params = arguments.length == 2 ? firstArgIsConnection ? (arguments[1] || {}) : arguments[0] : arguments[0], + fireEvent = (params.fireEvent !== false), + forceDetach = params.forceDetach, + connection = firstArgIsConnection ? arguments[0] : params.connection; + + if (connection) { + if (forceDetach || (connection.isDetachAllowed(connection) + && connection.endpoints[0].isDetachAllowed(connection) + && connection.endpoints[1].isDetachAllowed(connection))) { + if (forceDetach || _currentInstance.checkCondition("beforeDetach", connection)) + connection.endpoints[0].detach(connection, false, true, fireEvent); // TODO check this param iscorrect for endpoint's detach method + } + } + else { + var _p = jsPlumb.extend( {}, params); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1]), fireEvent); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source), + targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + if (_currentInstance.checkCondition("beforeDetach", jpc)) { + jpc.endpoints[0].detach(jpc, false, true, fireEvent); + } + } + }); + } + } + }; + + /* + Function: detachAllConnections + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + params - optional parameters. alowed values: + fireEvent : defaults to true, whether or not to fire the detach event. + + Returns: + void + */ + this.detachAllConnections = function(el, params) { + params = params || {}; + el = _getElementObject(el); + var id = _getAttribute(el, "id"), + endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + }; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Parameters: + params - optional params object containing: + fireEvent : whether or not to fire detach events. defaults to true. + + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function(params) { + params = params || {}; + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of your + library's draggable method so that jsPlumb can setup the appropriate callbacks. Your + underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + // helpers for select/selectEndpoints + var _setOperation = function(list, func, args, selector) { + for (var i = 0; i < list.length; i++) { + list[i][func].apply(list[i], args); + } + return selector(list); + }, + _getOperation = function(list, func, args) { + var out = []; + for (var i = 0; i < list.length; i++) { + out.push([ list[i][func].apply(list[i], args), list[i] ]); + } + return out; + }, + setter = function(list, func, selector) { + return function() { + return _setOperation(list, func, arguments, selector); + }; + }, + getter = function(list, func) { + return function() { + return _getOperation(list, func, arguments); + }; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method, + * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the + * default scope). If multiple scopes are passed in, the return value will be a map of { scope -> [ connection... ] }. + * + * Parameters + * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list + * of connections that are in the given scope. use '*' for all scopes. + * options - if the argument is a JS object, you can specify a finer-grained filter: + * + * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes. + * - *source* either a string representing an element id, or a selector. constrains the result to connections having this source. + * - *target* either a string representing an element id, or a selector. constrains the result to connections having this target. + * flat - return results in a flat array (don't return an object whose keys are scopes and whose values are lists per scope). + * + */ + this.getConnections = function(options, flat) { + if (!options) { + options = {}; + } else if (options.constructor == String) { + options = { "scope": options }; + } + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') { + if (input === "*") return input; + r.push(input); + } + else + r = input; + } + return r; + }, + scope = options.scope || _currentInstance.getDefaultScope(), + scopes = prepareList(scope), + sources = prepareList(options.source), + targets = prepareList(options.target), + filter = function(list, value) { + if (list === "*") return true; + return list.length > 0 ? _indexOf(list, value) != -1 : true; + }, + results = (!flat && scopes.length > 1) ? {} : [], + _addOne = function(scope, obj) { + if (!flat && scopes.length > 1) { + var ss = results[scope]; + if (ss == null) { + ss = []; results[scope] = ss; + } + ss.push(obj); + } else results.push(obj); + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + _addOne(i, c); + } + } + } + return results; + }; + + var _makeConnectionSelectHandler = function(list) { + //var + return { + // setters + setHover:setter(list, "setHover", _makeConnectionSelectHandler), + removeAllOverlays:setter(list, "removeAllOverlays", _makeConnectionSelectHandler), + setLabel:setter(list, "setLabel", _makeConnectionSelectHandler), + addOverlay:setter(list, "addOverlay", _makeConnectionSelectHandler), + removeOverlay:setter(list, "removeOverlay", _makeConnectionSelectHandler), + removeOverlays:setter(list, "removeOverlays", _makeConnectionSelectHandler), + showOverlay:setter(list, "showOverlay", _makeConnectionSelectHandler), + hideOverlay:setter(list, "hideOverlay", _makeConnectionSelectHandler), + showOverlays:setter(list, "showOverlays", _makeConnectionSelectHandler), + hideOverlays:setter(list, "hideOverlays", _makeConnectionSelectHandler), + setPaintStyle:setter(list, "setPaintStyle", _makeConnectionSelectHandler), + setHoverPaintStyle:setter(list, "setHoverPaintStyle", _makeConnectionSelectHandler), + setDetachable:setter(list, "setDetachable", _makeConnectionSelectHandler), + setConnector:setter(list, "setConnector", _makeConnectionSelectHandler), + setParameter:setter(list, "setParameter", _makeConnectionSelectHandler), + setParameters:setter(list, "setParameters", _makeConnectionSelectHandler), + + detach:function() { + for (var i = 0; i < list.length; i++) + _currentInstance.detach(list[i]); + }, + + // getters + getLabel:getter(list, "getLabel"), + getOverlay:getter(list, "getOverlay"), + isHover:getter(list, "isHover"), + isDetachable:getter(list, "isDetachable"), + getParameter:getter(list, "getParameter"), + getParameters:getter(list, "getParameters"), + + // util + length:list.length, + each:function(f) { + for (var i = 0; i < list.length; i++) { + f(list[i]); + } + return _makeConnectionSelectHandler(list); + }, + get:function(idx) { + return list[idx]; + } + + }; + }; + + this.select = function(params) { + params = params || {}; + params.scope = params.scope || "*"; + var c = _currentInstance.getConnections(params, true); + return _makeConnectionSelectHandler(c); + }; + + /* + * Function: getAllConnections + * Gets all connections, as a map of { scope -> [ connection... ] }. + */ + this.getAllConnections = function() { + return connectionsByScope; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /** + * Function:getEndpoints + * Gets the list of Endpoints for a given selector, or element id. + * @param el + * @return + */ + this.getEndpoints = function(el) { + return endpointsByElement[_getId(el)]; + }; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + this.getOffset = function(id) { + var o = offsets[id]; + return _updateOffset({elId:id}); + }; + + this.getSelector = function(spec) { + return jsPlumb.CurrentLibrary.getSelector(spec); + }; + + this.getSize = function(id) { + var s = sizes[id]; + if (!s) _updateOffset({elId:id}); + return sizes[id]; + }; + + this.appendElement = _appendElement; + + var _hoverSuspended = false; + this.isHoverSuspended = function() { return _hoverSuspended; }; + this.setHoverSuspended = function(s) { _hoverSuspended = s; }; + + this.isCanvasAvailable = function() { return canvasAvailable; }; + this.isSVGAvailable = function() { return svgAvailable; }; + this.isVMLAvailable = vmlAvailable; + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + changeEndpoints - whether not to also hide endpoints on the element. by default this is false. + + Returns: + void + */ + this.hide = function(el, changeEndpoints) { + _setVisible(el, "none", changeEndpoints); + }; + + // exposed for other objects to use to get a unique id. + this.idstamp = _idstamp; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + if (!initialized) { + _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run. + + var bindOne = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && renderMode == jsPlumb.CANVAS) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + var t = c[i].connector[event](e); + if (t) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i].endpoint[event](e)) return; + } + } + } + }); + }; + bindOne("click");bindOne("dblclick");bindOne("mousemove");bindOne("mousedown");bindOne("mouseup");bindOne("contextmenu"); + + initialized = true; + _currentInstance.fire("ready"); + } + }; + + this.log = log; + this.jsPlumbUIComponent = jsPlumbUIComponent; + this.EventGenerator = EventGenerator; + + /* + * Creates an anchor with the given params. + * + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function() { + if (arguments.length == 0) return null; + var specimen = arguments[0], elementId = arguments[1], jsPlumbInstance = arguments[2], newAnchor = null; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; //TODO hazy here about whether it should be added or is already added somehow. + // is it the name of an anchor type? + else if (typeof specimen == "string") { + newAnchor = jsPlumb.Anchors[arguments[0]]({elementId:elementId, jsPlumbInstance:_currentInstance}); + } + // is it an array? it will be one of: + // an array of [name, params] - this defines a single anchor + // an array of arrays - this defines some dynamic anchors + // an array of numbers - this defines a single anchor. + else if (_isArray(specimen)) { + if (_isArray(specimen[0]) || _isString(specimen[0])) { + if (specimen.length == 2 && _isString(specimen[0]) && _isObject(specimen[1])) { + var pp = jsPlumb.extend({elementId:elementId, jsPlumbInstance:_currentInstance}, specimen[1]); + newAnchor = jsPlumb.Anchors[specimen[0]](pp); + } + else + newAnchor = new DynamicAnchor(specimen, null, elementId); + } + else { + var anchorParams = { + x:specimen[0], y:specimen[1], + orientation : (specimen.length >= 4) ? [ specimen[2], specimen[3] ] : [0,0], + offsets : (specimen.length == 6) ? [ specimen[4], specimen[5] ] : [ 0, 0 ], + elementId:elementId + }; + newAnchor = new Anchor(anchorParams); + newAnchor.clone = function() { return new Anchor(anchorParams); }; + } + } + + if (!newAnchor.id) newAnchor.id = "anchor_" + _idstamp(); + return newAnchor; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types, elementId, jsPlumbInstance) { + var r = []; + for ( var i = 0; i < types.length; i++) { + if (typeof types[i] == "string") + r.push(jsPlumb.Anchors[types[i]]({elementId:elementId, jsPlumbInstance:jsPlumbInstance})); + else if (_isArray(types[i])) + r.push(_currentInstance.makeAnchor(types[i], elementId, jsPlumbInstance)); + } + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /** + * Function: makeTarget + * Makes some DOM element a Connection target, allowing you to drag connections to it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a target. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * scope optional. scope for the drop zone. + * dropOptions optional. same stuff as you would pass to dropOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to true. whether or not to delete + * any Endpoints created by a connection to this target if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + var _targetEndpointDefinitions = {}, + _targetEndpoints = {}, + _targetEndpointsUnique = {}, + _targetMaxConnections = {}, + _setEndpointPaintStylesAndAnchor = function(ep, epIndex) { + ep.paintStyle = ep.paintStyle || + _currentInstance.Defaults.EndpointStyles[epIndex] || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyles[epIndex] || + jsPlumb.Defaults.EndpointStyle; + ep.hoverPaintStyle = ep.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyles[epIndex] || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyles[epIndex] || + jsPlumb.Defaults.EndpointHoverStyle; + + ep.anchor = ep.anchor || + _currentInstance.Defaults.Anchors[epIndex] || + _currentInstance.Defaults.Anchor || + jsPlumb.Defaults.Anchors[epIndex] || + jsPlumb.Defaults.Anchor; + + ep.endpoint = ep.endpoint || + _currentInstance.Defaults.Endpoints[epIndex] || + _currentInstance.Defaults.Endpoint || + jsPlumb.Defaults.Endpoints[epIndex] || + jsPlumb.Defaults.Endpoint; + }; + this.makeTarget = function(el, params, referenceParams) { + + var p = jsPlumb.extend({_jsPlumb:_currentInstance}, referenceParams); + jsPlumb.extend(p, params); + _setEndpointPaintStylesAndAnchor(p, 1); + var jpcl = jsPlumb.CurrentLibrary, + targetScope = p.scope || _currentInstance.Defaults.Scope, + deleteEndpointsOnDetach = !(p.deleteEndpointsOnDetach === false), + maxConnections = p.maxConnections || -1, + _doOne = function(_el) { + + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el); + _targetEndpointDefinitions[elid] = p; + _targetEndpointsUnique[elid] = p.uniqueEndpoint, + _targetMaxConnections[elid] = maxConnections, + proxyComponent = new jsPlumbUIComponent(p); + + var dropOptions = jsPlumb.extend({}, p.dropOptions || {}), + _drop = function() { + + var originalEvent = jsPlumb.CurrentLibrary.getDropEvent(arguments), + targetCount = _currentInstance.select({target:elid}).length; + + if (_targetMaxConnections[elid] > 0 && targetCount >= _targetMaxConnections[elid]){ + console.log("target element " + elid + " is full."); + return false; + } + + _currentInstance.currentlyDragging = false; + var draggable = _getElementObject(jpcl.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + // restore the original scope if necessary (issue 57) + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id], + source = jpc.endpoints[0], + _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : {}; + + // unlock the source anchor to allow it to refresh its position if necessary + source.anchor.locked = false; + + if (scope) jpcl.setDragScope(draggable, scope); + + // check if drop is allowed here. + //var _continue = jpc.isDropAllowed(jpc.sourceId, _getId(_el), jpc.scope); + var _continue = proxyComponent.isDropAllowed(jpc.sourceId, _getId(_el), jpc.scope); + + // regardless of whether the connection is ok, reconfigure the existing connection to + // point at the current info. we need this to be correct for the detach event that will follow. + // clear the source endpoint from the list to detach. we will detach this connection at this + // point, but we want to keep the source endpoint. the target is a floating endpoint and should + // be removed. TODO need to figure out whether this code can result in endpoints kicking around + // when they shouldnt be. like is this a full detach of a connection? can it be? + if (jpc.endpointsToDeleteOnDetach) { + if (source === jpc.endpointsToDeleteOnDetach[0]) + jpc.endpointsToDeleteOnDetach[0] = null; + else if (source === jpc.endpointsToDeleteOnDetach[1]) + jpc.endpointsToDeleteOnDetach[1] = null; + } + // reinstate any suspended endpoint; this just puts the connection back into + // a state in which it will report sensible values if someone asks it about + // its target. we're going to throw this connection away shortly so it doesnt matter + // if we manipulate it a bit. + if (jpc.suspendedEndpoint) { + jpc.targetId = jpc.suspendedEndpoint.elementId; + jpc.target = jpcl.getElementObject(jpc.suspendedEndpoint.elementId); + jpc.endpoints[1] = jpc.suspendedEndpoint; + } + + if (_continue) { + + // detach this connection from the source. + source.detach(jpc, false, true, false);//source.endpointWillMoveAfterConnection); + + // make a new Endpoint for the target + //var newEndpoint = _currentInstance.addEndpoint(_el, _endpoint); + + var newEndpoint = _targetEndpoints[elid] || _currentInstance.addEndpoint(_el, p); + if (p.uniqueEndpoint) _targetEndpoints[elid] = newEndpoint; // may of course just store what it just pulled out. that's ok. + newEndpoint._makeTargetCreator = true; + + // if the anchor has a 'positionFinder' set, then delegate to that function to find + // out where to locate the anchor. + if (newEndpoint.anchor.positionFinder != null) { + var dropPosition = jpcl.getUIPosition(arguments), + elPosition = jpcl.getOffset(_el), + elSize = jpcl.getSize(_el), + ap = newEndpoint.anchor.positionFinder(dropPosition, elPosition, elSize, newEndpoint.anchor.constructorParams); + newEndpoint.anchor.x = ap[0]; + newEndpoint.anchor.y = ap[1]; + // now figure an orientation for it..kind of hard to know what to do actually. probably the best thing i can do is to + // support specifying an orientation in the anchor's spec. if one is not supplied then i will make the orientation + // be what will cause the most natural link to the source: it will be pointing at the source, but it needs to be + // specified in one axis only, and so how to make that choice? i think i will use whichever axis is the one in which + // the target is furthest away from the source. + } + var c = _currentInstance.connect({ + source:source, + target:newEndpoint, + scope:scope, + previousConnection:jpc, + container:jpc.parent, + deleteEndpointsOnDetach:deleteEndpointsOnDetach, + // 'endpointWillMoveAfterConnection' is set by the makeSource function, and it indicates that the + // given endpoint will actually transfer from the element it is currently attached to to some other + // element after a connection has been established. in that case, we do not want to fire the + // connection event, since it will have the wrong data in it; makeSource will do it for us. + // this is controlled by the 'parent' parameter on a makeSource call. + doNotFireConnectionEvent:source.endpointWillMoveAfterConnection + }); + + // delete the original target endpoint. but only want to do this if the endpoint was created + // automatically and has no other connections. + if (jpc.endpoints[1]._makeTargetCreator && jpc.endpoints[1].connections.length < 2) + _currentInstance.deleteEndpoint(jpc.endpoints[1]); + + if (deleteEndpointsOnDetach) + c.endpointsToDeleteOnDetach = [ source, newEndpoint ]; + + c.repaint(); + } + // if not allowed to drop... + else { + // TODO this code is identical (pretty much) to what happens when a connection + // dragged from a normal endpoint is in this situation. refactor. + // is this an existing connection, and will we reattach? + if (jpc.suspendedEndpoint) { + if (source.isReattach) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + _currentInstance.repaint(source.elementId); + } + else + source.detach(jpc, false, true, true, originalEvent); // otherwise, detach the connection and tell everyone about it. + } + + } + }; + + var dropEvent = jpcl.dragEvents['drop']; + dropOptions["scope"] = dropOptions["scope"] || targetScope; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop); + + jpcl.initDroppable(_el, dropOptions, true); + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements drop targets. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeTargets = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeTarget(els[i], params, referenceParams); + } + }; + + /** + * Function: makeSource + * Makes some DOM element a Connection source, allowing you to drag connections from it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a source. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * parent optional. the element to add Endpoints to when a Connection is established. if you omit this, + * Endpoints will be added to 'el'. + * scope optional. scope for the connections dragged from this element. + * dragOptions optional. same stuff as you would pass to dragOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to false. whether or not to delete + * any Endpoints created by a connection from this source if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + var _sourceEndpointDefinitions = {}, + _sourceEndpoints = {}, + _sourceEndpointsUnique = {}; + + this.makeSource = function(el, params, referenceParams) { + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + _setEndpointPaintStylesAndAnchor(p, 0); + var jpcl = jsPlumb.CurrentLibrary, + _doOne = function(_el) { + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el), + parent = p.parent, + idToRegisterAgainst = parent != null ? _currentInstance.getId(jpcl.getElementObject(parent)) : elid; + + _sourceEndpointDefinitions[idToRegisterAgainst] = p; + _sourceEndpointsUnique[idToRegisterAgainst] = p.uniqueEndpoint; + + var stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"], + dragOptions = jsPlumb.extend({ }, p.dragOptions || {}), + existingDrag = dragOptions.drag, + existingStop = dragOptions.stop, + ep = null, + endpointAddedButNoDragYet = false; + + // set scope if its not set in dragOptions but was passed in in params + dragOptions["scope"] = dragOptions["scope"] || p.scope; + + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + if (existingDrag) existingDrag.apply(this, arguments); + endpointAddedButNoDragYet = false; + }); + + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], function() { + if (existingStop) existingStop.apply(this, arguments); + + //_currentlyDown = false; + _currentInstance.currentlyDragging = false; + + if (ep.connections.length == 0) + _currentInstance.deleteEndpoint(ep); + else { + + jpcl.unbind(ep.canvas, "mousedown"); + + // reset the anchor to the anchor that was initially provided. the one we were using to drag + // the connection was just a placeholder that was located at the place the user pressed the + // mouse button to initiate the drag. + var anchorDef = p.anchor || _currentInstance.Defaults.Anchor, + oldAnchor = ep.anchor, + oldConnection = ep.connections[0]; + + ep.anchor = _currentInstance.makeAnchor(anchorDef, elid, _currentInstance); + + if (p.parent) { + var parent = jpcl.getElementObject(p.parent); + if (parent) { + var currentId = ep.elementId; + + ep.setElement(parent); + ep.endpointWillMoveAfterConnection = false; + _currentInstance.anchorManager.rehomeEndpoint(currentId, parent); + oldConnection.previousConnection = null; + // remove from connectionsByScope + _removeWithFunction(connectionsByScope[oldConnection.scope], function(c) { + return c.id === oldConnection.id; + }); + _currentInstance.anchorManager.connectionDetached({ + sourceId:oldConnection.sourceId, + targetId:oldConnection.targetId, + connection:oldConnection + }); + _finaliseConnection(oldConnection); + } + } + + ep.repaint(); + _currentInstance.repaint(ep.elementId); + _currentInstance.repaint(oldConnection.targetId); + + } + }); + // when the user presses the mouse, add an Endpoint + var mouseDownListener = function(e) { + // make sure we have the latest offset for this div + var myOffsetInfo = _updateOffset({elId:elid}); + + var x = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width, + y = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height, + parentX = x, + parentY = y; + + + // if there is a parent, the endpoint will actually be added to it now, rather than the div + // that was the source. in that case, we have to adjust the anchor position so it refers to + // the parent. + if (p.parent) { + var pEl = jsPlumb.CurrentLibrary.getElementObject(p.parent), + pId = _getId(pEl); + myOffsetInfo = _updateOffset({elId:pId}); + parentX = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width, + parentY = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height; + } + + // we need to override the anchor in here, and force 'isSource', but we don't want to mess with + // the params passed in, because after a connection is established we're going to reset the endpoint + // to have the anchor we were given. + var tempEndpointParams = {}; + jsPlumb.extend(tempEndpointParams, p); + tempEndpointParams.isSource = true; + tempEndpointParams.anchor = [x,y,0,0]; + tempEndpointParams.parentAnchor = [ parentX, parentY, 0, 0 ]; + tempEndpointParams.dragOptions = dragOptions; + // if a parent was given we need to turn that into a "container" argument. this is, by default, + // the parent of the element we will move to, so parent of p.parent in this case. however, if + // the user has specified a 'container' on the endpoint definition or on + // the defaults, we should use that. + if (p.parent) { + var potentialParent = tempEndpointParams.container || _currentInstance.Defaults.Container; + if (potentialParent) + tempEndpointParams.container = potentialParent; + else + tempEndpointParams.container = jsPlumb.CurrentLibrary.getParent(p.parent); + } + + ep = _currentInstance.addEndpoint(elid, tempEndpointParams); + + endpointAddedButNoDragYet = true; + // we set this to prevent connections from firing attach events before this function has had a chance + // to move the endpoint. + ep.endpointWillMoveAfterConnection = p.parent != null; + ep.endpointWillMoveTo = p.parent ? jpcl.getElementObject(p.parent) : null; + + var _delTempEndpoint = function() { + // this mouseup event is fired only if no dragging occurred, by jquery and yui, but for mootools + // it is fired even if dragging has occurred, in which case we would blow away a perfectly + // legitimate endpoint, were it not for this check. the flag is set after adding an + // endpoint and cleared in a drag listener we set in the dragOptions above. + if(endpointAddedButNoDragYet) { + _currentInstance.deleteEndpoint(ep); + } + }; + + _currentInstance.registerListener(ep.canvas, "mouseup", _delTempEndpoint); + _currentInstance.registerListener(_el, "mouseup", _delTempEndpoint); + + // and then trigger its mousedown event, which will kick off a drag, which will start dragging + // a new connection from this endpoint. + jpcl.trigger(ep.canvas, "mousedown", e); + }; + + // register this on jsPlumb so that it can be cleared by a reset. + _currentInstance.registerListener(_el, "mousedown", mouseDownListener); + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements connection sources. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeSources = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeSource(els[i], params, referenceParams); + } + }; + + /* + Function: ready + Helper method to bind a function to jsPlumb's ready event. + */ + this.ready = function(fn) { + _currentInstance.bind("ready", fn); + }, + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, null); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"), + ebe = endpointsByElement[elId]; + if (ebe) { + for ( var i = 0; i < ebe.length; i++) + _currentInstance.deleteEndpoint(ebe[i]); + } + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + var _registeredListeners = {}, + _unbindRegisteredListeners = function() { + for (var i in _registeredListeners) { + for (var j = 0; j < _registeredListeners[i].length; j++) { + var info = _registeredListeners[i][j]; + jsPlumb.CurrentLibrary.unbind(info.el, info.event, info.listener); + } + } + _registeredListeners = {}; + }; + + // internal register listener method. gives us a hook to clean things up + // with if the user calls jsPlumb.reset. + this.registerListener = function(el, type, listener) { + jsPlumb.CurrentLibrary.bind(el, type, listener); + _addToList(_registeredListeners, type, {el:el, event:type, listener:listener}); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + _currentInstance.deleteEveryEndpoint(); + _currentInstance.clearListeners(); + _targetEndpointDefinitions = {}; + _targetEndpoints = {}; + _targetEndpointsUnique = {}; + _targetMaxConnections = {}; + _sourceEndpointDefinitions = {}; + _sourceEndpoints = {}; + _sourceEndpointsUnique = {}; + _unbindRegisteredListeners(); + _currentInstance.anchorManager.reset(); + _currentInstance.dragManager.reset(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + + Parameters: + value - whether or not to automatically repaint when the window is resized. + + Returns: void + * + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + };*/ + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setId + * Changes the id of some element, adjusting all connections and endpoints + * + * Parameters: + * el - a selector, a DOM element, or a string. + * newId - string. + */ + this.setId = function(el, newId, doNotSetAttribute) { + + var id = el.constructor == String ? el : _currentInstance.getId(el), + sConns = _currentInstance.getConnections({source:id, scope:'*'}, true), + tConns = _currentInstance.getConnections({target:id, scope:'*'}, true); + + newId = "" + newId; + + if (!doNotSetAttribute) { + el = jsPlumb.CurrentLibrary.getElementObject(id); + jsPlumb.CurrentLibrary.setAttribute(el, "id", newId); + } + + el = jsPlumb.CurrentLibrary.getElementObject(newId); + + + endpointsByElement[newId] = endpointsByElement[id] || []; + for (var i = 0; i < endpointsByElement[newId].length; i++) { + endpointsByElement[newId][i].elementId = newId; + endpointsByElement[newId][i].element = el; + endpointsByElement[newId][i].anchor.elementId = newId; + } + delete endpointsByElement[id]; + + _currentInstance.anchorManager.changeId(id, newId); + + var _conns = function(list, epIdx, type) { + for (var i = 0; i < list.length; i++) { + list[i].endpoints[epIdx].elementId = newId; + list[i].endpoints[epIdx].element = el; + list[i][type + "Id"] = newId; + list[i][type] = el; + } + }; + _conns(sConns, 0, "source"); + _conns(tConns, 1, "target"); + }; + + /* + * Function: setIdChanged + * Notify jsPlumb that the element with oldId has had its id changed to newId. + */ + this.setIdChanged = function(oldId, newId) { + _currentInstance.setId(oldId, newId, true); + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction + * Sets the function to fire when the window size has changed and a repaint was fired. + * + * Parameters: + * f - Function to execute. + * + * Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: setSuspendDrawing + * Suspends drawing operations. This can be used when you have a lot of connections to make or endpoints to register; + * it will save you a lot of time. + */ + this.setSuspendDrawing = _setSuspendDrawing; + + /* + * Constant for use with the setRenderMode method + */ + this.CANVAS = "canvas"; + + /* + * Constant for use with the setRenderMode method + */ + this.SVG = "svg"; + + this.VML = "vml"; + + /* + * Function: setRenderMode + * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that + * what you asked for is not supported (and that VML is). If you asked for VML but the browser does + * not support it, jsPlumb uses SVG. + * + * Returns: + * the render mode that jsPlumb set, which of course may be different from that requested. + */ + this.setRenderMode = function(mode) { + if (mode) + mode = mode.toLowerCase(); + else + return; + if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); + // now test we actually have the capability to do this. + if (mode === jsPlumb.CANVAS && canvasAvailable) + renderMode = jsPlumb.CANVAS; + else if (mode === jsPlumb.SVG && svgAvailable) + renderMode = jsPlumb.SVG; + else if (vmlAvailable()) + renderMode = jsPlumb.VML; + + return renderMode; + }; + + this.getRenderMode = function() { return renderMode; }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * changeEndpoints - whether or not to also change the visible state of the endpoints on the element. this also has a bearing on + * other connections on those endpoints: if their other endpoint is also visible, the connections are made visible. + * + * Returns: + * void + */ + this.show = function(el, changeEndpoints) { + _setVisible(el, "block", changeEndpoints); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + //findIndex : _findIndex, + getId : _getId, + makeAnchor:self.makeAnchor, + makeDynamicAnchor:self.makeDynamicAnchor + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * changeEndpoints - whether or not to also toggle the endpoints on the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload + * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element. + * + * Returns: + * void + */ + this.unload = function() { + // this used to do something, but it turns out that what it did was nothing. + // now it exists only for backwards compatibility. + }; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + var adjustForParentOffsetAndScroll = function(xy, el) { + + var offsetParent = null, result = xy; + if (el.tagName.toLowerCase() === "svg" && el.parentNode) { + offsetParent = el.parentNode; + } + else if (el.offsetParent) { + offsetParent = el.offsetParent; + } + if (offsetParent != null) { + var po = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(offsetParent), + so = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : {left:offsetParent.scrollLeft, top:offsetParent.scrollTop}; + + + // i thought it might be cool to do this: + // lastReturnValue[0] = lastReturnValue[0] - offsetParent.offsetLeft + offsetParent.scrollLeft; + // lastReturnValue[1] = lastReturnValue[1] - offsetParent.offsetTop + offsetParent.scrollTop; + // but i think it ignores margins. my reasoning was that it's quicker to not hand off to some underlying + // library. + + result[0] = xy[0] - po.left + so.left; + result[1] = xy[1] - po.top + so.top; + } + + return result; + + }; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + this.elementId = params.elementId; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + + if (timestamp && timestamp === self.timestamp) + return lastReturnValue; + + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + + // adjust loc if there is an offsetParent + lastReturnValue = adjustForParentOffsetAndScroll(lastReturnValue, element.canvas); + + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function(_endpoint) { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + * + * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference, + // the canvas this refers to. + refCanvas = params.referenceCanvas, + size = _getSize(_getElementObject(refCanvas)), + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + xDir = 0, yDir = 0, + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + orientation = null, + _lastResult = null; + + // set these to 0 each; they are used by certain types of connectors in the loopback case, + // when the connector is trying to clear the element it is on. but for floating anchor it's not + // very important. + this.x = 0; this.y = 0; + + this.isFloating = true; + + this.compute = function(params) { + var xy = params.xy, element = params.element, + result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + + // adjust loc if there is an offsetParent + result = adjustForParentOffsetAndScroll(result, element.canvas); + + _lastResult = result; + return result; + }; + + this.getOrientation = function(_endpoint) { + if (orientation) return orientation; + else { + var o = ref.getOrientation(_endpoint); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchor is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector, elementId) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = [], self = this, + _convert = function(anchor) { + return anchor.constructor == Anchor ? anchor: _currentInstance.makeAnchor(anchor, elementId, _currentInstance); + }; + for (var i = 0; i < anchors.length; i++) + _anchors[i] = _convert(anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + this.locked = false; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null, + _curIndex = _anchors.length > 0 ? 0 : -1, + self = this, + + // helper method to calculate the distance between the centers of the two elements. + _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }, + + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + self.x = _curAnchor.x; + self.y = _curAnchor.y; + + return _curAnchor.compute(params); + }; + + this.getCurrentLocation = function() { + return _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + }; + + this.getOrientation = function(_endpoint) { return _curAnchor != null ? _curAnchor.getOrientation(_endpoint) : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + manages anchors for all elements. + */ + // "continuous" anchors: anchors that pick their location based on how many connections the given element has. + // this requires looking at a lot more elements than normal - anything that has had a Continuous anchor applied has + // to be recalculated. so this manager is used as a reference point. the first time, with a new timestamp, that + // a continuous anchor is asked to compute, it calls this guy. or maybe, even, this guy gets called somewhere else + // and compute only ever returns pre-computed values. either way, this is the central point, and we want it to + // be called as few times as possible. + var continuousAnchors = {}, + continuousAnchorLocations = {}, + continuousAnchorOrientations = {}, + Orientation = { HORIZONTAL : "horizontal", VERTICAL : "vertical", DIAGONAL : "diagonal", IDENTITY:"identity" }, + + // TODO this functions uses a crude method of determining orientation between two elements. + // 'diagonal' should be chosen when the angle of the line between the two centers is around + // one of 45, 135, 225 and 315 degrees. maybe +- 15 degrees. + calculateOrientation = function(sourceId, targetId, sd, td) { + + if (sourceId === targetId) return { + orientation:Orientation.IDENTITY, + a:["top", "top"] + }; + + var theta = Math.atan2((td.centery - sd.centery) , (td.centerx - sd.centerx)), + theta2 = Math.atan2((sd.centery - td.centery) , (sd.centerx - td.centerx)), + h = ((sd.left <= td.left && sd.right >= td.left) || (sd.left <= td.right && sd.right >= td.right) || + (sd.left <= td.left && sd.right >= td.right) || (td.left <= sd.left && td.right >= sd.right)), + v = ((sd.top <= td.top && sd.bottom >= td.top) || (sd.top <= td.bottom && sd.bottom >= td.bottom) || + (sd.top <= td.top && sd.bottom >= td.bottom) || (td.top <= sd.top && td.bottom >= sd.bottom)); + + if (! (h || v)) { + var a = null, rls = false, rrs = false, sortValue = null; + if (td.left > sd.left && td.top > sd.top) + a = ["right", "top"]; + else if (td.left > sd.left && sd.top > td.top) + a = [ "top", "left"]; + else if (td.left < sd.left && td.top < sd.top) + a = [ "top", "right"]; + else if (td.left < sd.left && td.top > sd.top) + a = ["left", "top" ]; + + return { orientation:Orientation.DIAGONAL, a:a, theta:theta, theta2:theta2 }; + } + else if (h) return { + orientation:Orientation.HORIZONTAL, + a:sd.top < td.top ? ["bottom", "top"] : ["top", "bottom"], + theta:theta, theta2:theta2 + } + else return { + orientation:Orientation.VERTICAL, + a:sd.left < td.left ? ["right", "left"] : ["left", "right"], + theta:theta, theta2:theta2 + } + }, + placeAnchorsOnLine = function(desc, elementDimensions, elementPosition, + connections, horizontal, otherMultiplier, reverse) { + var a = [], step = elementDimensions[horizontal ? 0 : 1] / (connections.length + 1); + + for (var i = 0; i < connections.length; i++) { + var val = (i + 1) * step, other = otherMultiplier * elementDimensions[horizontal ? 1 : 0]; + if (reverse) + val = elementDimensions[horizontal ? 0 : 1] - val; + + var dx = (horizontal ? val : other), x = elementPosition[0] + dx, xp = dx / elementDimensions[0], + dy = (horizontal ? other : val), y = elementPosition[1] + dy, yp = dy / elementDimensions[1]; + + a.push([ x, y, xp, yp, connections[i][1], connections[i][2] ]); + } + + return a; + }, + standardEdgeSort = function(a, b) { return a[0] > b[0] ? 1 : -1 }, + currySort = function(reverseAngles) { + return function(a,b) { + var r = true; + if (reverseAngles) { + if (a[0][0] < b[0][0]) + r = true; + else + r = a[0][1] > b[0][1]; + } + else { + if (a[0][0] > b[0][0]) + r= true; + else + r =a[0][1] > b[0][1]; + } + return r === false ? -1 : 1; + }; + }, + leftSort = function(a,b) { + // first get adjusted values + var p1 = a[0][0] < 0 ? -Math.PI - a[0][0] : Math.PI - a[0][0], + p2 = b[0][0] < 0 ? -Math.PI - b[0][0] : Math.PI - b[0][0]; + if (p1 > p2) return 1; + else return a[0][1] > b[0][1] ? 1 : -1; + }, + edgeSortFunctions = { + "top":standardEdgeSort, + "right":currySort(true), + "bottom":currySort(true), + "left":leftSort + }, + _sortHelper = function(_array, _fn) { + return _array.sort(_fn); + }, + placeAnchors = function(elementId, _anchorLists) { + var sS = sizes[elementId], sO = offsets[elementId], + placeSomeAnchors = function(desc, elementDimensions, elementPosition, unsortedConnections, isHorizontal, otherMultiplier, orientation) { + if (unsortedConnections.length > 0) { + var sc = _sortHelper(unsortedConnections, edgeSortFunctions[desc]), // puts them in order based on the target element's pos on screen + reverse = desc === "right" || desc === "top", + anchors = placeAnchorsOnLine(desc, elementDimensions, + elementPosition, sc, + isHorizontal, otherMultiplier, reverse ); + + // takes a computed anchor position and adjusts it for parent offset and scroll, then stores it. + var _setAnchorLocation = function(endpoint, anchorPos) { + var a = adjustForParentOffsetAndScroll([anchorPos[0], anchorPos[1]], endpoint.canvas); + continuousAnchorLocations[endpoint.id] = [ a[0], a[1], anchorPos[2], anchorPos[3] ]; + continuousAnchorOrientations[endpoint.id] = orientation; + }; + + for (var i = 0; i < anchors.length; i++) { + var c = anchors[i][4], weAreSource = c.endpoints[0].elementId === elementId, weAreTarget = c.endpoints[1].elementId === elementId; + if (weAreSource) + _setAnchorLocation(c.endpoints[0], anchors[i]); + else if (weAreTarget) + _setAnchorLocation(c.endpoints[1], anchors[i]); + } + } + }; + + placeSomeAnchors("bottom", sS, [sO.left,sO.top], _anchorLists.bottom, true, 1, [0,1]); + placeSomeAnchors("top", sS, [sO.left,sO.top], _anchorLists.top, true, 0, [0,-1]); + placeSomeAnchors("left", sS, [sO.left,sO.top], _anchorLists.left, false, 0, [-1,0]); + placeSomeAnchors("right", sS, [sO.left,sO.top], _anchorLists.right, false, 1, [1,0]); + }, + AnchorManager = function() { + var _amEndpoints = {}, + connectionsByElementId = {}, + self = this, + anchorLists = {}; + + this.reset = function() { + _amEndpoints = {}; + connectionsByElementId = {}; + anchorLists = {}; + }; + this.newConnection = function(conn) { + var sourceId = conn.sourceId, targetId = conn.targetId, + ep = conn.endpoints, + doRegisterTarget = true, + registerConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + + if ((sourceId == targetId) && otherAnchor.isContinuous){ + // remove the target endpoint's canvas. we dont need it. + jsPlumb.CurrentLibrary.removeElement(ep[1].canvas); + doRegisterTarget = false; + } + _addToList(connectionsByElementId, elId, [c, otherEndpoint, otherAnchor.constructor == DynamicAnchor]); + }; + + registerConnection(0, ep[0], ep[0].anchor, targetId, conn); + if (doRegisterTarget) + registerConnection(1, ep[1], ep[1].anchor, sourceId, conn); + }; + this.connectionDetached = function(connInfo) { + var connection = connInfo.connection || connInfo; + var sourceId = connection.sourceId, + targetId = connection.targetId, + ep = connection.endpoints, + removeConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + if (otherAnchor.constructor == FloatingAnchor) { + // no-op + } + else { + _removeWithFunction(connectionsByElementId[elId], function(_c) { + return _c[0].id == c.id; + }); + } + }; + + removeConnection(1, ep[1], ep[1].anchor, sourceId, connection); + removeConnection(0, ep[0], ep[0].anchor, targetId, connection); + + // remove from anchorLists + var sEl = connection.sourceId, + tEl = connection.targetId, + sE = connection.endpoints[0].id, + tE = connection.endpoints[1].id, + _remove = function(list, eId) { + if (list) { // transient anchors dont get entries in this list. + var f = function(e) { return e[4] == eId; }; + _removeWithFunction(list["top"], f); + _removeWithFunction(list["left"], f); + _removeWithFunction(list["bottom"], f); + _removeWithFunction(list["right"], f); + } + }; + + _remove(anchorLists[sEl], sE); + _remove(anchorLists[tEl], tE); + self.redraw(sEl); + self.redraw(tEl); + }; + this.add = function(endpoint, elementId) { + _addToList(_amEndpoints, elementId, endpoint); + }; + this.changeId = function(oldId, newId) { + connectionsByElementId[newId] = connectionsByElementId[oldId]; + _amEndpoints[newId] = _amEndpoints[oldId]; + delete connectionsByElementId[oldId]; + delete _amEndpoints[oldId]; + }; + this.getConnectionsFor = function(elementId) { + return connectionsByElementId[elementId] || []; + }; + this.getEndpointsFor = function(elementId) { + return _amEndpoints[elementId] || []; + }; + this.deleteEndpoint = function(endpoint) { + _removeWithFunction(_amEndpoints[endpoint.elementId], function(e) { + return e.id == endpoint.id; + }); + }; + this.clearFor = function(elementId) { + delete _amEndpoints[elementId]; + _amEndpoints[elementId] = []; + }; + // updates the given anchor list by either updating an existing anchor's info, or adding it. this function + // also removes the anchor from its previous list, if the edge it is on has changed. + // all connections found along the way (those that are connected to one of the faces this function + // operates on) are added to the connsToPaint list, as are their endpoints. in this way we know to repaint + // them wthout having to calculate anything else about them. + var _updateAnchorList = function(lists, theta, order, conn, aBoolean, otherElId, idx, reverse, edgeId, elId, connsToPaint, endpointsToPaint) { + // first try to find the exact match, but keep track of the first index of a matching element id along the way.s + var exactIdx = -1, + firstMatchingElIdx = -1, + endpoint = conn.endpoints[idx], + endpointId = endpoint.id, + oIdx = [1,0][idx], + values = [ [ theta, order ], conn, aBoolean, otherElId, endpointId ], + listToAddTo = lists[edgeId], + listToRemoveFrom = endpoint._continuousAnchorEdge ? lists[endpoint._continuousAnchorEdge] : null; + + if (listToRemoveFrom) { + var rIdx = _findWithFunction(listToRemoveFrom, function(e) { return e[4] == endpointId }); + if (rIdx != -1) { + listToRemoveFrom.splice(rIdx, 1); + // get all connections from this list + for (var i = 0; i < listToRemoveFrom.length; i++) { + _addWithFunction(connsToPaint, listToRemoveFrom[i][1], function(c) { return c.id == listToRemoveFrom[i][1].id }); + _addWithFunction(endpointsToPaint, listToRemoveFrom[i][1].endpoints[idx], function(e) { return e.id == listToRemoveFrom[i][1].endpoints[idx].id }); + } + } + } + + for (var i = 0; i < listToAddTo.length; i++) { + if (idx == 1 && listToAddTo[i][3] === otherElId && firstMatchingElIdx == -1) + firstMatchingElIdx = i; + _addWithFunction(connsToPaint, listToAddTo[i][1], function(c) { return c.id == listToAddTo[i][1].id }); + _addWithFunction(endpointsToPaint, listToAddTo[i][1].endpoints[idx], function(e) { return e.id == listToAddTo[i][1].endpoints[idx].id }); + } + if (exactIdx != -1) { + listToAddTo[exactIdx] = values; + } + else { + var insertIdx = reverse ? firstMatchingElIdx != -1 ? firstMatchingElIdx : 0 : listToAddTo.length; // of course we will get this from having looked through the array shortly. + listToAddTo.splice(insertIdx, 0, values); + } + + // store this for next time. + endpoint._continuousAnchorEdge = edgeId; + }; + this.redraw = function(elementId, ui, timestamp, offsetToUI) { + // get all the endpoints for this element + var ep = _amEndpoints[elementId] || [], + endpointConnections = connectionsByElementId[elementId] || [], + connectionsToPaint = [], + endpointsToPaint = [], + anchorsToUpdate = []; + + timestamp = timestamp || _timestamp(); + // offsetToUI are values that would have been calculated in the dragManager when registering + // an endpoint for an element that had a parent (somewhere in the hierarchy) that had been + // registered as draggable. + offsetToUI = offsetToUI || {left:0, top:0}; + if (ui) { + ui = { + left:ui.left + offsetToUI.left, + top:ui.top + offsetToUI.top + } + } + + _updateOffset( { elId : elementId, offset : ui, recalc : false, timestamp : timestamp }); + // valid for one paint cycle. + var myOffset = offsets[elementId], + myWH = sizes[elementId], + orientationCache = {}; + + // actually, first we should compute the orientation of this element to all other elements to which + // this element is connected with a continuous anchor (whether both ends of the connection have + // a continuous anchor or just one) + //for (var i = 0; i < continuousAnchorConnections.length; i++) { + for (var i = 0; i < endpointConnections.length; i++) { + var conn = endpointConnections[i][0], + sourceId = conn.sourceId, + targetId = conn.targetId, + sourceContinuous = conn.endpoints[0].anchor.isContinuous, + targetContinuous = conn.endpoints[1].anchor.isContinuous; + + if (sourceContinuous || targetContinuous) { + var oKey = sourceId + "_" + targetId, + oKey2 = targetId + "_" + sourceId, + o = orientationCache[oKey], + oIdx = conn.sourceId == elementId ? 1 : 0; + + if (sourceContinuous && !anchorLists[sourceId]) anchorLists[sourceId] = { top:[], right:[], bottom:[], left:[] }; + if (targetContinuous && !anchorLists[targetId]) anchorLists[targetId] = { top:[], right:[], bottom:[], left:[] }; + + if (elementId != targetId) _updateOffset( { elId : targetId, timestamp : timestamp }); + if (elementId != sourceId) _updateOffset( { elId : sourceId, timestamp : timestamp }); + + var td = _getCachedData(targetId), + sd = _getCachedData(sourceId); + + if (targetId == sourceId && (sourceContinuous || targetContinuous)) { + // here we may want to improve this by somehow determining the face we'd like + // to put the connector on. ideally, when drawing, the face should be calculated + // by determining which face is closest to the point at which the mouse button + // was released. for now, we're putting it on the top face. + _updateAnchorList(anchorLists[sourceId], -Math.PI / 2, 0, conn, false, targetId, 0, false, "top", sourceId, connectionsToPaint, endpointsToPaint) + } + else { + if (!o) { + o = calculateOrientation(sourceId, targetId, sd.o, td.o); + orientationCache[oKey] = o; + // this would be a performance enhancement, but the computed angles need to be clamped to + //the (-PI/2 -> PI/2) range in order for the sorting to work properly. + /* orientationCache[oKey2] = { + orientation:o.orientation, + a:[o.a[1], o.a[0]], + theta:o.theta + Math.PI, + theta2:o.theta2 + Math.PI + };*/ + } + if (sourceContinuous) _updateAnchorList(anchorLists[sourceId], o.theta, 0, conn, false, targetId, 0, false, o.a[0], sourceId, connectionsToPaint, endpointsToPaint); + if (targetContinuous) _updateAnchorList(anchorLists[targetId], o.theta2, -1, conn, true, sourceId, 1, true, o.a[1], targetId, connectionsToPaint, endpointsToPaint); + } + + if (sourceContinuous) _addWithFunction(anchorsToUpdate, sourceId, function(a) { return a === sourceId; }); + if (targetContinuous) _addWithFunction(anchorsToUpdate, targetId, function(a) { return a === targetId; }); + _addWithFunction(connectionsToPaint, conn, function(c) { return c.id == conn.id; }); + if ((sourceContinuous && oIdx == 0) || (targetContinuous && oIdx == 1)) + _addWithFunction(endpointsToPaint, conn.endpoints[oIdx], function(e) { return e.id == conn.endpoints[oIdx].id; }); + } + } + + // now place all the continuous anchors we need to; + for (var i = 0; i < anchorsToUpdate.length; i++) { + placeAnchors(anchorsToUpdate[i], anchorLists[anchorsToUpdate[i]]); + } + + // now that continuous anchors have been placed, paint all the endpoints for this element + // TODO performance: add the endpoint ids to a temp array, and then when iterating in the next + // loop, check that we didn't just paint that endpoint. we can probably shave off a few more milliseconds this way. + for (var i = 0; i < ep.length; i++) { + ep[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + // ... and any other endpoints we came across as a result of the continuous anchors. + for (var i = 0; i < endpointsToPaint.length; i++) { + endpointsToPaint[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + + // paint all the standard and "dynamic connections", which are connections whose other anchor is + // static and therefore does need to be recomputed; we make sure that happens only one time. + + // TODO we could have compiled a list of these in the first pass through connections; might save some time. + for (var i = 0; i < endpointConnections.length; i++) { + var otherEndpoint = endpointConnections[i][1]; + if (otherEndpoint.anchor.constructor == DynamicAnchor) { + otherEndpoint.paint({ elementWithPrecedence:elementId }); + _addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; }); + // all the connections for the other endpoint now need to be repainted + for (var k = 0; k < otherEndpoint.connections.length; k++) { + if (otherEndpoint.connections[k] !== endpointConnections[i][0]) + _addWithFunction(connectionsToPaint, otherEndpoint.connections[k], function(c) { return c.id == otherEndpoint.connections[k].id; }); + } + } else if (otherEndpoint.anchor.constructor == Anchor) { + _addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; }); + } + } + // paint current floating connection for this element, if there is one. + var fc = floatingConnections[elementId]; + if (fc) + fc.paint({timestamp:timestamp, recalc:false, elId:elementId}); + + // paint all the connections + for (var i = 0; i < connectionsToPaint.length; i++) { + connectionsToPaint[i].paint({elId:elementId, timestamp:timestamp, recalc:false}); + } + }; + this.rehomeEndpoint = function(currentId, element) { + var eps = _amEndpoints[currentId] || [], //, + elementId = _currentInstance.getId(element); + for (var i = 0; i < eps.length; i++) { + self.add(eps[i], elementId); + } + eps.splice(0, eps.length); + }; + }; + _currentInstance.anchorManager = new AnchorManager(); + _currentInstance.continuousAnchorFactory = { + get:function(params) { + var existing = continuousAnchors[params.elementId]; + if (!existing) { + existing = { + type:"Continuous", + compute : function(params) { + return continuousAnchorLocations[params.element.id] || [0,0]; + }, + getCurrentLocation : function(endpoint) { + return continuousAnchorLocations[endpoint.id] || [0,0]; + }, + getOrientation : function(endpoint) { + return continuousAnchorOrientations[endpoint.id] || [0,0]; + }, + isDynamic : true, + isContinuous : true + }; + continuousAnchors[params.elementId] = existing; + } + return existing; + } + }; + + /** + Manages dragging for some instance of jsPlumb. + + */ + var DragManager = function() { + + var _draggables = {}, _dlist = [], _delements = {}, _elementsWithEndpoints = {}; + + /** + register some element as draggable. right now the drag init stuff is done elsewhere, and it is + possible that will continue to be the case. + */ + this.register = function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + var id = _currentInstance.getId(el), + domEl = jsPlumb.CurrentLibrary.getDOMElement(el); + if (!_draggables[id]) { + _draggables[id] = el; + _dlist.push(el); + _delements[id] = {}; + } + + // look for child elements that have endpoints and register them against this draggable. + var _oneLevel = function(p) { + var pEl = jsPlumb.CurrentLibrary.getElementObject(p), + pOff = jsPlumb.CurrentLibrary.getOffset(pEl); + + for (var i = 0; i < p.childNodes.length; i++) { + if (p.childNodes[i].nodeType != 3) { + var cEl = jsPlumb.CurrentLibrary.getElementObject(p.childNodes[i]), + cid = _currentInstance.getId(cEl, null, true); + if (cid && _elementsWithEndpoints[cid] && _elementsWithEndpoints[cid] > 0) { + var cOff = jsPlumb.CurrentLibrary.getOffset(cEl); + _delements[id][cid] = { + id:cid, + offset:{ + left:cOff.left - pOff.left, + top:cOff.top - pOff.top + } + }; + } + } + } + }; + + _oneLevel(domEl); + }; + + /** + notification that an endpoint was added to the given el. we go up from that el's parent + node, looking for a parent that has been registered as a draggable. if we find one, we add this + el to that parent's list of elements to update on drag (if it is not there already) + */ + this.endpointAdded = function(el) { + var jpcl = jsPlumb.CurrentLibrary, b = document.body, id = _currentInstance.getId(el), c = jpcl.getDOMElement(el), + p = c.parentNode, done = p == b; + + _elementsWithEndpoints[id] = _elementsWithEndpoints[id] ? _elementsWithEndpoints[id] + 1 : 1; + + while (p != b) { + var pid = _currentInstance.getId(p); + if (_draggables[pid]) { + var idx = -1, pEl = jpcl.getElementObject(p), pLoc = jsPlumb.CurrentLibrary.getOffset(pEl); + + if (_delements[pid][id] == null) { + var cLoc = jsPlumb.CurrentLibrary.getOffset(el); + _delements[pid][id] = { + id:id, + offset:{ + left:cLoc.left - pLoc.left, + top:cLoc.top - pLoc.top + } + }; + } + break; + } + p = p.parentNode; + } + }; + + this.endpointDeleted = function(endpoint) { + if (_elementsWithEndpoints[endpoint.elementId]) { + _elementsWithEndpoints[endpoint.elementId]--; + if (_elementsWithEndpoints[endpoint.elementId] <= 0) { + for (var i in _delements) { + delete _delements[i][endpoint.elementId]; + } + } + } + }; + + this.getElementsForDraggable = function(id) { + return _delements[id]; + }; + + this.reset = function() { + _draggables = {}; + _dlist = []; + _delements = {}; + _elementsWithEndpoints = {}; + }; + + }; + _currentInstance.dragManager = new DragManager(); + + + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * container - optional id or selector instructing jsPlumb where to attach all the elements it creates for this connection. you should read the documentation for a full discussion of this. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * overlays - Optional array of Overlay definitions to appear on this Connection. + * drawEndpoints - if false, instructs jsPlumb to not draw the endpoints for this Connection. Be careful with this: it only really works when you tell jsPlumb to attach elements to the document body. Read the documentation for a full discussion of this. + */ + var Connection = function(params) { + var self = this, visible = true; + self.idPrefix = "_jsplumb_c_"; + self.defaultLabelLocation = 0.5; + self.defaultOverlayKeys = ["Overlays", "ConnectionOverlays"]; + this.parent = params.parent; + overlayCapableJsPlumbUIComponent.apply(this, arguments); + // ************** get the source and target and register the connection. ******************* + + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + self[v ? "showOverlays" : "hideOverlays"](); + if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none"; + }; + + /** + Property: source + The source element for this Connection. + */ + this.source = _getElementObject(params.source); + /** + Property:target + The target element for this Connection. + */ + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. but + // source is not overridden if the Endpoint has declared it is not the final target of a connection; + // instead we use the source that the Endpoint declares will be the final source element. + if (params.sourceEndpoint) { + this.source = params.sourceEndpoint.endpointWillMoveTo || params.sourceEndpoint.getElement(); + } + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + + // if a new connection is the result of moving some existing connection, params.previousConnection + // will have that Connection in it. listeners for the jsPlumbConnection event can look for that + // member and take action if they need to. + self.previousConnection = params.previousConnection; + + var _cost = params.cost; + self.getCost = function() { return _cost; }; + self.setCost = function(c) { _cost = c; }; + + var _bidirectional = params.bidirectional === false ? false : true; + self.isBidirectional = function() { return _bidirectional; }; + + /* + * Property: sourceId + * Id of the source element in the connection. + */ + this.sourceId = _getAttribute(this.source, "id"); + /* + * Property: targetId + * Id of the target element in the connection. + */ + this.targetId = _getAttribute(this.target, "id"); + + /** + * implementation of abstract method in EventGenerator + * @return list of attached elements. in our case, a list of Endpoints. + */ + this.getAttachedElements = function() { + return self.endpoints; + }; + + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams, elementId) { + if (anchorParams) + return _currentInstance.makeAnchor(anchorParams, elementId, _currentInstance); + }, + prepareEndpoint = function(existing, index, params, element, elementId, connectorPaintStyle, connectorHoverPaintStyle) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] + || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint; + + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified. + if (es.fillStyle == null && connectorPaintStyle != null) + es.fillStyle = connectorPaintStyle.strokeStyle; + + // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does: + //* + if (es.outlineColor == null && connectorPaintStyle != null) + es.outlineColor = connectorPaintStyle.outlineColor; + if (es.outlineWidth == null && connectorPaintStyle != null) + es.outlineWidth = connectorPaintStyle.outlineWidth; + //*/ + + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure? + if (connectorHoverPaintStyle != null) { + if (ehs == null) ehs = {}; + if (ehs.fillStyle == null) { + ehs.fillStyle = connectorHoverPaintStyle.strokeStyle; + } + } + var a = params.anchors ? params.anchors[index] : + params.anchor ? params.anchor : + _makeAnchor(_currentInstance.Defaults.Anchors[index], elementId) || + _makeAnchor(jsPlumb.Defaults.Anchors[index], elementId) || + _makeAnchor(_currentInstance.Defaults.Anchor, elementId) || + _makeAnchor(jsPlumb.Defaults.Anchor, elementId), + u = params.uuids ? params.uuids[index] : null, + e = _newEndpoint({ + paintStyle : es, + hoverPaintStyle:ehs, + endpoint : ep, + connections : [ self ], + uuid : u, + anchor : a, + source : element, + scope : params.scope, + container:params.container, + reattach:params.reattach, + detachable:params.detachable + }); + self.endpoints[index] = e; + + + if (params.drawEndpoints === false) e.setVisible(false, true, true); + + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, + 0, + params, + self.source, + self.sourceId, + params.paintStyle, + params.hoverPaintStyle); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + + // if there were no endpoints supplied and the source element is the target element, we will reuse the source + // endpoint that was just created. + var existingTargetEndpoint = ((self.sourceId == self.targetId) && params.targetEndpoint == null) ? eS : params.targetEndpoint, + eT = prepareEndpoint(existingTargetEndpoint, + 1, + params, + self.target, + self.targetId, + params.paintStyle, + params.hoverPaintStyle); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + // if delete endpoints on detach, keep a record of just exactly which endpoints they are. + if (params.deleteEndpointsOnDetach) + self.endpointsToDeleteOnDetach = [eS, eT]; + + var _detachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.detachable === false) _detachable = false; + if(self.endpoints[0].connectionsDetachable === false) _detachable = false; + if(self.endpoints[1].connectionsDetachable === false) _detachable = false; + + // inherit connectin cost if it was set on source endpoint + if (_cost == null) _cost = self.endpoints[0].getConnectionCost(); + // inherit bidirectional flag if set no source endpoint + if (params.bidirectional == null) _bidirectional = self.endpoints[0].areConnectionsBidirectional(); + + /* + Function: isDetachable + Returns whether or not this connection can be detached from its target/source endpoint. by default this + is false; use it in conjunction with the 'reattach' parameter. + */ + this.isDetachable = function() { + return _detachable === true; + }; + + /* + Function: setDetachable + Sets whether or not this connection is detachable. + */ + this.setDetachable = function(detachable) { + _detachable = detachable === true; + }; + + // merge all the parameters objects into the connection. parameters set + // on the connection take precedence; then target endpoint params, then + // finally source endpoint params. + // TODO jsPlumb.extend could be made to take more than two args, and it would + // apply the second through nth args in order. + var _p = jsPlumb.extend({}, this.endpoints[0].getParameters()); + jsPlumb.extend(_p, this.endpoints[1].getParameters()); + jsPlumb.extend(_p, self.getParameters()); + self.setParameters(_p); + + // override setHover to pass it down to the underlying connector + var _sh = self.setHover; + + self.setHover = function() { + self.connector.setHover.apply(self.connector, arguments); + _sh.apply(self, arguments); + }; + + var _internalHover = function(state) { + if (_connectionBeingDragged == null) { + self.setHover(state, false); + } + }; + + /* + * Function: setConnector + * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method - the same + * thing that you would set as the 'connector' property on a jsPlumb.connect call. + * + * Parameters: + * connector - Connector definition + */ + this.setConnector = function(connector, doNotRepaint) { + if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent); + var connectorArgs = { + _jsPlumb:self._jsPlumb, + parent:params.parent, + cssClass:params.cssClass, + container:params.container, + tooltip:self.tooltip + }; + if (_isString(connector)) + this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand. + else if (_isArray(connector)) + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs)); + self.canvas = self.connector.canvas; + // binds mouse listeners to the current connector. + _bindListeners(self.connector, self, _internalHover); + if (!doNotRepaint) self.repaint(); + }; + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc) + */ + + self.setConnector(this.endpoints[0].connector || + this.endpoints[1].connector || + params.connector || + _currentInstance.Defaults.Connector || + jsPlumb.Defaults.Connector, true); + + this.setPaintStyle(this.endpoints[0].connectorStyle || + this.endpoints[1].connectorStyle || + params.paintStyle || + _currentInstance.Defaults.PaintStyle || + jsPlumb.Defaults.PaintStyle, true); + + this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || + this.endpoints[1].connectorHoverStyle || + params.hoverPaintStyle || + _currentInstance.Defaults.HoverPaintStyle || + jsPlumb.Defaults.HoverPaintStyle, true); + + this.paintStyleInUse = this.paintStyle; + + + this.moveParent = function(newParent) { + var jpcl = jsPlumb.CurrentLibrary, curParent = jpcl.getParent(self.connector.canvas); + jpcl.removeElement(self.connector.canvas, curParent); + jpcl.appendElement(self.connector.canvas, newParent); + if (self.connector.bgCanvas) { + jpcl.removeElement(self.connector.bgCanvas, curParent); + jpcl.appendElement(self.connector.bgCanvas, newParent); + } + // this only applies for DOMOverlays + for (var i = 0; i < self.overlays.length; i++) { + if (self.overlays[i].isAppendedAtTopLevel) { + jpcl.removeElement(self.overlays[i].canvas, curParent); + jpcl.appendElement(self.overlays[i].canvas, newParent); + if (self.overlays[i].reattachListeners) + self.overlays[i].reattachListeners(self.connector); + } + } + if (self.connector.reattachListeners) // this is for SVG/VML; change an element's parent and you have to reinit its listeners. + self.connector.reattachListeners(); // the Canvas implementation doesn't have to care about this + }; + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Connection. + * + * Parameters: + * event - the event to bind. Available events on a Connection are: + * - *click* : notification that a Connection was clicked. + * - *dblclick* : notification that a Connection was double clicked. + * - *mouseenter* : notification that the mouse is over a Connection. + * - *mouseexit* : notification that the mouse exited a Connection. + * + * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Connection's paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Connection will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Connection. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId], + otherOffset = offsets[this.targetId], + otherWH = sizes[this.targetId], + initialTimestamp = _timestamp(), + anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + elementId:this.endpoints[0].elementId, + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1], + timestamp:initialTimestamp + }); + + this.endpoints[0].paint( { anchorLoc : anchorLoc, timestamp:initialTimestamp }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + elementId:this.endpoints[1].elementId, + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0], + timestamp:initialTimestamp + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc, timestamp:initialTimestamp }); + + /* + * Paints the Connection. Not exposed for public usage. + * + * Parameters: + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp, + // if the moving object is not the source we must transpose the two references. + swap = false, + tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId, + tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + + var sourceInfo = _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }), + targetInfo = _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var sE = this.endpoints[sIdx], tE = this.endpoints[tIdx], + sAnchorP = sE.anchor.getCurrentLocation(sE), + tAnchorP = tE.anchor.getCurrentLocation(tE); + + /* paint overlays*/ + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible()) maxSize = Math.max(maxSize, o.computeMaxSize(self.connector)); + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, + this.endpoints[sIdx], this.endpoints[tIdx], + this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, + self.paintStyleInUse.lineWidth, maxSize, + sourceInfo, + targetInfo); + + self.connector.paint(dim, self.paintStyleInUse); + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim); + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function(params) { + params = params || {}; + var recalc = !(params.recalc === false); + this.paint({ elId : this.sourceId, recalc : recalc, timestamp:params.timestamp }); + }; + + }; + +// ENDPOINT HELPER FUNCTIONS + var _makeConnectionDragHandler = function(placeholder) { + var stopped = false; + return { + + drag : function() { + if (stopped) { + stopped = false; + return true; + } + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments), + el = placeholder.element; + if (el) { + jsPlumb.CurrentLibrary.setOffset(el, _ui); + _draw(_getElementObject(el), _ui); + } + }, + stopDrag : function() { + stopped = true; + } + }; + }; + + var _makeFloatingEndpoint = function(paintStyle, referenceAnchor, endpoint, referenceCanvas, sourceElement) { + var floatingAnchor = new FloatingAnchor( { reference : referenceAnchor, referenceCanvas : referenceCanvas }); + + //setting the scope here should not be the way to fix that mootools issue. it should be fixed by not + // adding the floating endpoint as a droppable. that makes more sense anyway! + + return _newEndpoint({ paintStyle : paintStyle, endpoint : endpoint, anchor : floatingAnchor, source : sourceElement, scope:"__floating" }); + }; + + /** + * creates a placeholder div for dragging purposes, adds it to the DOM, and pre-computes its offset. then returns + * both the element id and a selector for the element. + */ + var _makeDraggablePlaceholder = function(placeholder, parent) { + var n = document.createElement("div"); + n.style.position = "absolute"; + var placeholderDragElement = _getElementObject(n); + _appendElement(n, parent); + var id = _getId(placeholderDragElement); + _updateOffset( { elId : id }); + // create and assign an id, and initialize the offset. + placeholder.id = id; + placeholder.element = placeholderDragElement; + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1 + * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't + * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you + * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or + * 'target' Endpoints for Connections. + * + * + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * enabled - optional, defaults to true. Indicates whether or not the Endpoint should be enabled for mouse events (drag/drop). + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * container - optional id or selector instructing jsPlumb where to attach the element it creates for this endpoint. you should read the documentation for a full discussion of this. + * connections - optional list of Connections to configure the Endpoint with. + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + var self = this; + self.idPrefix = "_jsplumb_e_"; + self.defaultLabelLocation = [ 0.5, 0.5 ]; + self.defaultOverlayKeys = ["Overlays", "EndpointOverlays"]; + this.parent = params.parent; + overlayCapableJsPlumbUIComponent.apply(this, arguments); + params = params || {}; + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Endpoint. + * + * Parameters: + * event - the event to bind. Available events on an Endpoint are: + * - *click* : notification that a Endpoint was clicked. + * - *dblclick* : notification that a Endpoint was double clicked. + * - *mouseenter* : notification that the mouse is over a Endpoint. + * - *mouseexit* : notification that the mouse exited a Endpoint. + * + * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Endpoint's paint style and then repaints the Endpoint. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Endpoint will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Endpoint. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + var visible = true, __enabled = !(params.enabled === false); + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + self[v ? "showOverlays" : "hideOverlays"](); + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + + /* + Function: isEnabled + Returns whether or not the Endpoint is enabled for drag/drop connections. + */ + this.isEnabled = function() { return __enabled; }; + + /* + Function: setEnabled + Sets whether or not the Endpoint is enabled for drag/drop connections. + */ + this.setEnabled = function(e) { __enabled = e; }; + + var _element = params.source, _uuid = params.uuid, floatingEndpoint = null, inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + this.element = _element; + + var _connectionCost = params.connectionCost; + this.getConnectionCost = function() { return _connectionCost; }; + this.setConnectionCost = function(c) { + _connectionCost = c; + }; + + var _connectionsBidirectional = params.connectionsBidirectional === false ? false : true; + this.areConnectionsBidirectional = function() { return _connectionsBidirectional; }; + this.setConnectionsBidirectional = function(b) { _connectionsBidirectional = b; }; + + self.anchor = params.anchor ? _currentInstance.makeAnchor(params.anchor, _elementId, _currentInstance) : params.anchors ? _currentInstance.makeAnchor(params.anchors, _elementId, _currentInstance) : _currentInstance.makeAnchor("TopCenter", _elementId, _currentInstance); + + // ANCHOR MANAGER + if (!params._transient) // in place copies, for example, are transient. they will never need to be retrieved during a paint cycle, because they dont move, and then they are deleted. + _currentInstance.anchorManager.add(self, _elementId); + + var _endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot", + endpointArgs = { + _jsPlumb:self._jsPlumb, + parent:params.parent, + container:params.container, + tooltip:params.tooltip, + connectorTooltip:params.connectorTooltip, + endpoint:self + }; + if (_isString(_endpoint)) + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint](endpointArgs); + else if (_isArray(_endpoint)) { + endpointArgs = jsPlumb.extend(_endpoint[1], endpointArgs); + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint[0]](endpointArgs); + } + else { + _endpoint = _endpoint.clone(); + } + + // assign a clone function using a copy of endpointArgs. this is used when a drag starts: the endpoint that was dragged is cloned, + // and the clone is left in its place while the original one goes off on a magical journey. + // the copy is to get around a closure problem, in which endpointArgs ends up getting shared by + // the whole world. + var argsForClone = jsPlumb.extend({}, endpointArgs); + _endpoint.clone = function() { + var o = new Object(); + _endpoint.constructor.apply(o, [argsForClone]); + return o; + }; + + self.endpoint = _endpoint; + self.type = self.endpoint.type; + // override setHover to pass it down to the underlying endpoint + var _sh = self.setHover; + self.setHover = function() { + self.endpoint.setHover.apply(self.endpoint, arguments); + _sh.apply(self, arguments); + }; + // endpoint delegates to first connection for hover, if there is one. + var internalHover = function(state) { + if (self.connections.length > 0) + self.connections[0].setHover(state, false); + else + self.setHover(state); + }; + + // bind listeners from endpoint to self, with the internal hover function defined above. + _bindListeners(self.endpoint, self, internalHover); + + this.setPaintStyle(params.paintStyle || + params.style || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyle, true); + this.setHoverPaintStyle(params.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyle, true); + this.paintStyleInUse = this.paintStyle; + this.connectorStyle = params.connectorStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.connectorTooltip = params.connectorTooltip; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + + var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + /* + * Property: canvas + * The Endpoint's Canvas. + */ + this.canvas = this.endpoint.canvas; + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + this.connections = params.connections || []; + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + self.isReattach = params.reattach || false; + self.connectionsDetachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.connectionsDetachable === false || params.detachable === false) + self.connectionsDetachable = false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget, forceDetach, fireEvent, originalEvent) { + var idx = _findWithFunction(self.connections, function(c) { return c.id == connection.id}), + actuallyDetached = false; + fireEvent = (fireEvent !== false); + if (idx >= 0) { + // 1. does the connection have a before detach (note this also checks jsPlumb's bound + // detach handlers; but then Endpoint's check will, too, hmm.) + if (forceDetach || connection._forceDetach || connection.isDetachable() || connection.isDetachAllowed(connection)) { + // get the target endpoint + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + // it would be nice to check with both endpoints that it is ok to detach. but + // for this we'll have to get a bit fancier: right now if you use the same beforeDetach + // interceptor for two endpoints (which is kind of common, because it's part of the + // endpoint definition), then it gets fired twice. so in fact we need to loop through + // each beforeDetach and see if it returns false, at which point we exit. but if it + // returns true, we have to check the next one. however we need to track which ones + // have already been run, and not run them again. + if (forceDetach || connection._forceDetach || (self.isDetachAllowed(connection) /*&& t.isDetachAllowed(connection)*/)) { + + self.connections.splice(idx, 1); + + // this avoids a circular loop + if (!ignoreTarget) { + + t.detach(connection, true, forceDetach); + // check connection to see if we want to delete the endpoints associated with it. + // we only detach those that have just this connection; this scenario is most + // likely if we got to this bit of code because it is set by the methods that + // create their own endpoints, like .connect or .makeTarget. the user is + // not likely to have interacted with those endpoints. + if (connection.endpointsToDeleteOnDetach){ + for (var i = 0; i < connection.endpointsToDeleteOnDetach.length; i++) { + var cde = connection.endpointsToDeleteOnDetach[i]; + if (cde && cde.connections.length == 0) + _currentInstance.deleteEndpoint(cde); + } + } + } + _removeElements(connection.connector.getDisplayElements(), connection.parent); + _removeWithFunction(connectionsByScope[connection.scope], function(c) { + return c.id == connection.id; + }); + actuallyDetached = true; + var doFireEvent = (!ignoreTarget && fireEvent) + fireDetachEvent(connection, doFireEvent, originalEvent); + } + } + } + return actuallyDetached; + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + * + * Parameters: + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachAll = function(fireEvent, originalEvent) { + while (self.connections.length > 0) { + self.detach(self.connections[0], false, true, fireEvent, originalEvent); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachFrom = function(targetEndpoint, fireEvent, originalEvent) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + if (self.detach(c[i], false, true, fireEvent, originalEvent)) + c[i].setHover(false, false); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findWithFunction(self.connections, function(c) { return c.id == connection.id}); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: setElement + * Sets the DOM element this Endpoint is attached to. + */ + this.setElement = function(el) { + + // TODO possibly have this object take charge of moving the UI components into the appropriate + // parent. this is used only by makeSource right now, and that function takes care of + // moving the UI bits and pieces. however it would s + var parentId = _getId(el); + // remove the endpoint from the list for the current endpoint's element + _removeWithFunction(endpointsByElement[self.elementId], function(e) { + return e.id == self.id; + }); + _element = _getElementObject(el); + _elementId = _getId(_element); + self.elementId = _elementId; + // need to get the new parent now + var newParentElement = _getParentFromParams({source:parentId}), + curParent = jpcl.getParent(self.canvas); + jpcl.removeElement(self.canvas, curParent); + jpcl.appendElement(self.canvas, newParentElement); + + // now move connection(s)...i would expect there to be only one but we will iterate. + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].moveParent(newParentElement); + self.connections[i].sourceId = _elementId; + self.connections[i].source = _element; + } + _addToList(endpointsByElement, parentId, self); + //_currentInstance.repaint(parentId); + + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + var loc = self.anchor.getCurrentLocation(self), + o = self.anchor.getOrientation(self), + inPlaceAnchor = { + compute:function() { return [ loc[0], loc[1] ]}, + getCurrentLocation : function() { return [ loc[0], loc[1] ]}, + getOrientation:function() { return o; } + }; + + return _newEndpoint( { + anchor : inPlaceAnchor, + source : _element, + paintStyle : this.paintStyle, + endpoint : _endpoint, + _transient:true, + scope:self.scope + }); + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + var candidate = self.connections[0]; + if (self.isTarget && candidate) return candidate; + else { + return (self.connections.length < _maxConnections) || _maxConnections == -1 ? null : candidate; + } + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + /* + * Function: setStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to + * setStyle and deprecate it if so. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + * + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + // a helper function that tries to find a connection to the given element, and returns it if so. if elementWithPrecedence is null, + // or no connection to it is found, we return the first connection in our list. + var findConnectionToUseForDynamicAnchor = function(elementWithPrecedence) { + var idx = 0; + if (elementWithPrecedence != null) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].sourceId == elementWithPrecedence || self.connections[i].targetId == elementWithPrecedence) { + idx = i; + break; + } + } + } + + return self.connections[idx]; + }; + + /* + * Function: paint + * Paints the Endpoint, recalculating offset and anchor positions if necessary. This does NOT paint + * any of the Endpoint's connections. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + params = params || {}; + var timestamp = params.timestamp, + recalc = !(params.recalc === false); + if (!timestamp || self.timestamp !== timestamp) { + _updateOffset({ elId:_elementId, timestamp:timestamp, recalc:recalc }); + var xy = params.offset || offsets[_elementId]; + if(xy) { + var ap = params.anchorPoint,connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (recalc && self.anchor.isDynamic && self.connections.length > 0) { + var c = findConnectionToUseForDynamicAnchor(params.elementWithPrecedence), + oIdx = c.endpoints[0] == self ? 1 : 0, + oId = oIdx == 0 ? c.sourceId : c.targetId, + oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + ap = self.anchor.compute(anchorParams); + } + + var d = _endpoint.compute(ap, self.anchor.getOrientation(_endpoint), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + _endpoint.paint(d, self.paintStyleInUse, self.anchor); + self.timestamp = timestamp; + + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) self.overlayPlacements[i] = o.draw(self.endpoint, self.paintStyleInUse, d); + } + } + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var placeholderInfo = { id:null, element:null }, + jpc = null, + existingJpc = false, + existingJpcParams = null, + _dragHandler = _makeConnectionDragHandler(placeholderInfo); + + var start = function() { + // drag might have started on an endpoint that is not actually a source, but which has + // one or more connections. + jpc = self.connectorSelector(); + var _continue = true; + // if not enabled, return + if (!self.isEnabled()) _continue = false; + // if no connection and we're not a source, return. + if (jpc == null && !params.isSource) _continue = false; + // otherwise if we're full and not allowed to drag, also return false. + if (params.isSource && self.isFull() && !dragAllowedWhenFull) _continue = false; + // if the connection was setup as not detachable or one of its endpoints + // was setup as connectionsDetachable = false, or Defaults.ConnectionsDetachable + // is set to false... + if (jpc != null && !jpc.isDetachable()) _continue = false; + + if (_continue === false) { + // this is for mootools and yui. returning false from this causes jquery to stop drag. + // the events are wrapped in both mootools and yui anyway, but i don't think returning + // false from the start callback would stop a drag. + if (jsPlumb.CurrentLibrary.stopDrag) jsPlumb.CurrentLibrary.stopDrag(); + _dragHandler.stopDrag(); + return false; + } + + // if we're not full but there was a connection, make it null. we'll create a new one. + if (jpc && !self.isFull() && params.isSource) jpc = null; + + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + _makeDraggablePlaceholder(placeholderInfo, self.parent); + + // set the offset of this div to be where 'inPlaceCopy' is, to start with. + // TODO merge this code with the code in both Anchor and FloatingAnchor, because it + // does the same stuff. + var ipcoel = _getElementObject(inPlaceCopy.canvas), + ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel), + po = adjustForParentOffsetAndScroll([ipco.left, ipco.top], inPlaceCopy.canvas); + jsPlumb.CurrentLibrary.setOffset(placeholderInfo.element, {left:po[0], top:po[1]}); + + // when using makeSource and a parent, we first draw the source anchor on the source element, then + // move it to the parent. note that this happens after drawing the placeholder for the + // first time. + if (self.parentAnchor) self.anchor = _currentInstance.makeAnchor(self.parentAnchor, self.elementId, _currentInstance); + + + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", placeholderInfo.id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + floatingEndpoint = _makeFloatingEndpoint(self.paintStyle, self.anchor, _endpoint, self.canvas, placeholderInfo.element); + + if (jpc == null) { + self.anchor.locked = true; + self.setHover(false, false); + // TODO the hover call above does not reset any target endpoint's hover + // states. + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : self.endpointWillMoveTo || _getElementObject(_element), // for makeSource with parent option. ensure source element is represented correctly. + target : placeholderInfo.element, + anchors : [ self.anchor, floatingEndpoint.anchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays + }); + + } else { + existingJpc = true; + jpc.connector.setHover(false, false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas), false, true); + // new anchor idx + var anchorIdx = jpc.endpoints[0].id == self.id ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas), + dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // now we want to get this endpoint's DROP scope, and set it for now: we can only be dropped on drop zones + // that have our drop scope (issue 57). + var dropScope = jsPlumb.CurrentLibrary.getDropScope(c); + jsPlumb.CurrentLibrary.setDragScope(c, dropScope); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = placeholderInfo.element; + jpc.sourceId = placeholderInfo.id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = placeholderInfo.element; + jpc.targetId = placeholderInfo.id; + } + + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.suspendedEndpoint.setHover(false); + jpc.endpoints[anchorIdx] = floatingEndpoint; + + // fire an event that informs that a connection is being dragged + fireConnectionDraggingEvent(jpc); + + } + // register it and register connection on it. + floatingConnections[placeholderInfo.id] = jpc; + floatingEndpoint.addConnection(jpc); + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, placeholderInfo.id, floatingEndpoint); + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var jpcl = jsPlumb.CurrentLibrary, + dragOptions = params.dragOptions || {}, + defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions), + startEvent = jpcl.dragEvents["start"], + stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"]; + + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + // extracted drag handler function so can be used by makeSource + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], _dragHandler.drag); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + var originalEvent = jpcl.getDropEvent(arguments); + _currentInstance.currentlyDragging = false; + _removeWithFunction(endpointsByElement[placeholderInfo.id], function(e) { + return e.id == floatingEndpoint.id; + }); + _removeElements( [ placeholderInfo.element[0], floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + _currentInstance.anchorManager.clearFor(placeholderInfo.id); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (self.isReattach || jpc._forceDetach || !jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc, false, false, true, originalEvent)) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + _currentInstance.repaint(existingJpcParams[1]); + } + jpc._forceDetach = null; + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElements(jpc.connector.getDisplayElements(), self.parent); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint({recalc:false}); + jpc.setHover(false, false); + + fireConnectionDragStopEvent(jpc); + + jpc = null; + inPlaceCopy = null; + delete endpointsByElement[floatingEndpoint.elementId]; + floatingEndpoint.anchor = null; + floatingEndpoint = null; + _currentInstance.currentlyDragging = false; + + + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions, true); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas, forceInit, isTransient, endpoint) { + if ((params.isTarget || forceInit) && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop'], + overEvent = jsPlumb.CurrentLibrary.dragEvents['over'], + outEvent = jsPlumb.CurrentLibrary.dragEvents['out'], + drop = function() { + + var originalEvent = jsPlumb.CurrentLibrary.getDropEvent(arguments), + draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + elId = _getAttribute(draggable, "elId"), + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id]; + + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + + // restore the original scope if necessary (issue 57) + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + var endpointEnabled = endpoint != null ? endpoint.isEnabled() : true; + + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget) && endpointEnabled) { + + var _doContinue = true; + + // the second check here is for the case that the user is dropping it back + // where it came from. + if (jpc.suspendedEndpoint && jpc.suspendedEndpoint.id != self.id) { + if (idx == 0) { + jpc.source = jpc.suspendedEndpoint.element; + jpc.sourceId = jpc.suspendedEndpoint.elementId; + } else { + jpc.target = jpc.suspendedEndpoint.element; + jpc.targetId = jpc.suspendedEndpoint.elementId; + } + + if (!jpc.isDetachAllowed(jpc) || !jpc.endpoints[idx].isDetachAllowed(jpc) || !jpc.suspendedEndpoint.isDetachAllowed(jpc) || !_currentInstance.checkCondition("beforeDetach", jpc)) + _doContinue = false; + } + + // these have to be set before testing for beforeDrop. + if (idx == 0) { + jpc.source = self.element; + jpc.sourceId = self.elementId; + } else { + jpc.target = self.element; + jpc.targetId = self.elementId; + } + + + // now check beforeDrop. this will be available only on Endpoints that are setup to + // have a beforeDrop condition (although, secretly, under the hood all Endpoints and + // the Connection have them, because they are on jsPlumbUIComponent. shhh!), because + // it only makes sense to have it on a target endpoint. + _doContinue = _doContinue && self.isDropAllowed(jpc.sourceId, jpc.targetId, jpc.scope); + + if (_doContinue) { + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + + // copy our parameters in to the connection: + var params = self.getParameters(); + for (var aParam in params) + jpc.setParameter(aParam, params[aParam]); + + if (!jpc.suspendedEndpoint) { + //_addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(self.element, params.draggable, {}); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + fireDetachEvent({ + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }, true, originalEvent); + } + + // finalise will inform the anchor manager and also add to + // connectionsByScope if necessary. + _finaliseConnection(jpc, null, originalEvent); + } + else { + // otherwise just put it back on the endpoint it was on before the drag. + if (jpc.suspendedEndpoint) { + // self.detachFrom(jpc); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.setHover(false); + jpc._forceDetach = true; + if (idx == 0) { + jpc.source = jpc.suspendedEndpoint.element; + jpc.sourceId = jpc.suspendedEndpoint.elementId; + } else { + jpc.target = jpc.suspendedEndpoint.element; + jpc.targetId = jpc.suspendedEndpoint.elementId;; + } + jpc.suspendedEndpoint.addConnection(jpc); + + jpc.endpoints[0].repaint(); + jpc.repaint(); + _currentInstance.repaint(jpc.source.elementId); + jpc._forceDetach = false; + } + } + + jpc.floatingAnchorIndex = null; + } + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + } + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], function() { + if (self.isTarget) { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + } + } + }); + dropOptions[outEvent] = _wrap(dropOptions[outEvent], function() { + if (self.isTarget) { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + } + } + }); + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions, true, isTransient); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas), true, !(params._transient || self.anchor.isFloating), self); + + return self; + }; + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + j.init(); + return j; + }; + jsPlumb.util = { + convertStyle : function(s, ignoreAlpha) { + // TODO: jsPlumb should support a separate 'opacity' style member. + if ("transparent" === s) return s; + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + return o; + }, + gradient : function(p1, p2) { + p1 = _isArray(p1) ? p1 : [p1.x, p1.y]; + p2 = _isArray(p2) ? p2 : [p2.x, p2.y]; + return (p2[1] - p1[1]) / (p2[0] - p1[0]); + }, + normal : function(p1, p2) { + return -1 / jsPlumb.util.gradient(p1,p2); + }, + segment : function(p1, p2) { + p1 = _isArray(p1) ? p1 : [p1.x, p1.y]; + p2 = _isArray(p2) ? p2 : [p2.x, p2.y]; + if (p2[0] > p1[0]) { + return (p2[1] > p1[1]) ? 2 : 1; + } + else { + return (p2[1] > p1[1]) ? 3 : 4; + } + }, + intersects : function(r1, r2) { + var x1 = r1.x, x2 = r1.x + r1.w, y1 = r1.y, y2 = r1.y + r1.h, + a1 = r2.x, a2 = r2.x + r2.w, b1 = r2.y, b2 = r2.y + r2.h; + + return ( (x1 <= a1 && a1 <= x2) && (y1 <= b1 && b1 <= y2) ) || + ( (x1 <= a2 && a2 <= x2) && (y1 <= b1 && b1 <= y2) ) || + ( (x1 <= a1 && a1 <= x2) && (y1 <= b2 && b2 <= y2) ) || + ( (x1 <= a2 && a1 <= x2) && (y1 <= b2 && b2 <= y2) ) || + + ( (a1 <= x1 && x1 <= a2) && (b1 <= y1 && y1 <= b2) ) || + ( (a1 <= x2 && x2 <= a2) && (b1 <= y1 && y1 <= b2) ) || + ( (a1 <= x1 && x1 <= a2) && (b1 <= y2 && y2 <= b2) ) || + ( (a1 <= x2 && x1 <= a2) && (b1 <= y2 && y2 <= b2) ); + }, + segmentMultipliers : [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ], + inverseSegmentMultipliers : [null, [-1, -1], [-1, 1], [1, 1], [1, -1] ], + pointOnLine : function(fromPoint, toPoint, distance) { + var m = jsPlumb.util.gradient(fromPoint, toPoint), + s = jsPlumb.util.segment(fromPoint, toPoint), + segmentMultiplier = distance > 0 ? jsPlumb.util.segmentMultipliers[s] : jsPlumb.util.inverseSegmentMultipliers[s], + theta = Math.atan(m), + y = Math.abs(distance * Math.sin(theta)) * segmentMultiplier[1], + x = Math.abs(distance * Math.cos(theta)) * segmentMultiplier[0]; + return { x:fromPoint.x + x, y:fromPoint.y + y }; + }, + /** + * calculates a perpendicular to the line fromPoint->toPoint, that passes through toPoint and is 'length' long. + * @param fromPoint + * @param toPoint + * @param length + */ + perpendicularLineTo : function(fromPoint, toPoint, length) { + var m = jsPlumb.util.gradient(fromPoint, toPoint), + theta2 = Math.atan(-1 / m), + y = length / 2 * Math.sin(theta2), + x = length / 2 * Math.cos(theta2); + return [{x:toPoint.x + x, y:toPoint.y + y}, {x:toPoint.x - x, y:toPoint.y - y}]; + } + }; + + var _curryAnchor = function(x, y, ox, oy, type, fnInit) { + return function(params) { + params = params || {}; + //var a = jsPlumb.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance); + var a = params.jsPlumbInstance.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance); + a.type = type; + if (fnInit) fnInit(a, params); + return a; + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1, "TopCenter"); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1, "BottomCenter"); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0, "LeftMiddle"); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0, "RightMiddle"); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0, "Center"); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1, "TopRight"); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1, "BottomRight"); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1, "TopLeft"); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1, "BottomLeft"); + + // TODO test that this does not break with the current instance idea + jsPlumb.Defaults.DynamicAnchors = function(params) { + return params.jsPlumbInstance.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"], params.elementId, params.jsPlumbInstance); + }; + jsPlumb.Anchors["AutoDefault"] = function(params) { + var a = params.jsPlumbInstance.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors(params)); + a.type = "AutoDefault"; + return a; + }; + + jsPlumb.Anchors["Assign"] = _curryAnchor(0,0,0,0,"Assign", function(anchor, params) { + // find what to use as the "position finder". the user may have supplied a String which represents + // the id of a position finder in jsPlumb.AnchorPositionFinders, or the user may have supplied the + // position finder as a function. we find out what to use and then set it on the anchor. + var pf = params.position || "Fixed"; + anchor.positionFinder = pf.constructor == String ? params.jsPlumbInstance.AnchorPositionFinders[pf] : pf; + // always set the constructor params; the position finder might need them later (the Grid one does, + // for example) + anchor.constructorParams = params; + }); + + // Continuous anchor is just curried through to the 'get' method of the continuous anchor + // factory. + jsPlumb.Anchors["Continuous"] = function(params) { + return params.jsPlumbInstance.continuousAnchorFactory.get(params); + }; + + // these are the default anchor positions finders, which are used by the makeTarget function. supply + // a position finder argument to that function allows you to specify where the resulting anchor will + // be located + jsPlumb.AnchorPositionFinders = { + "Fixed": function(dp, ep, es, params) { + return [ (dp.left - ep.left) / es[0], (dp.top - ep.top) / es[1] ]; + }, + "Grid":function(dp, ep, es, params) { + var dx = dp.left - ep.left, dy = dp.top - ep.top, + gx = es[0] / (params.grid[0]), gy = es[1] / (params.grid[1]), + mx = Math.floor(dx / gx), my = Math.floor(dy / gy); + return [ ((mx * gx) + (gx / 2)) / es[0], ((my * gy) + (gy / 2)) / es[1] ]; + } + }; +})(); diff --git a/archive/1.3.8/jsPlumb-1.3.8-tests.js b/archive/1.3.8/jsPlumb-1.3.8-tests.js new file mode 100644 index 000000000..cbc73c5c7 --- /dev/null +++ b/archive/1.3.8/jsPlumb-1.3.8-tests.js @@ -0,0 +1,3660 @@ +// _jsPlumb qunit tests. + +QUnit.config.reorder = false; + +var _getContextNode = function() { + return $("#container"); +}; + +var assertContextExists = function() { + ok(_getContextNode().length == 1, "context node exists"); +}; + +var assertContextSize = function(elementCount) { + //equals(_getContextNode().children().length - _divs.length, elementCount, 'context has ' + elementCount + ' children'); +}; + +var assertContextEmpty = function() { + equals(_getContextNode().children.length, 0, "context empty"); +}; + +var assertEndpointCount = function(elId, count, _jsPlumb) { + equals(_jsPlumb.getTestHarness().endpointCount(elId), count, elId + " has " + count + (count > 1) ? "endpoints" : "endpoint"); + equals(_jsPlumb.anchorManager.getEndpointsFor(elId).length, count, "anchor manager has " + count + (count > 1) ? "endpoints" : "endpoint"); +}; + +var assertConnectionCount = function(endpoint, count) { + equals(endpoint.connections.length, count, "endpoint has " + count + " connections"); +}; + +var assertConnectionByScopeCount = function(scope, count, _jsPlumb) { + equals(_jsPlumb.getTestHarness().connectionCount(scope), count, 'Scope ' + scope + " has " + count + (count > 1) ? "connections" : "connection"); + +}; + +var _divs = []; +var _addDiv = function(id, parent) { + var d1 = document.createElement("div"); + if (parent) parent.append(d1); else _getContextNode().append(d1); + d1 = jsPlumb.CurrentLibrary.getElementObject(d1); + jsPlumb.CurrentLibrary.setAttribute(d1, "id", id); + _divs.push(id); + return d1; +}; + +var defaults = null, + _cleanup = function(_jsPlumb) { + + _jsPlumb.reset(); + _jsPlumb.Defaults = defaults; + + for (var i in _divs) { + $("#" + _divs[i]).remove(); + } + _divs.splice(0, _divs.length - 1); + + $("#container").empty(); +}; + +var testSuite = function(renderMode, _jsPlumb) { + + + module("jsPlumb", { + teardown: function() { + _cleanup(_jsPlumb); + }, + setup:function() { + defaults = jsPlumb.extend({}, _jsPlumb.Defaults); + } + }); + + // setup the container + var container = document.createElement("div"); + container.id = "container"; + document.body.appendChild(container); + + var jpcl = jsPlumb.CurrentLibrary; + + _jsPlumb.setRenderMode(renderMode); + + test(renderMode + " : getElementObject", function() { + var e = document.createElement("div"); + e.id = "FOO"; + document.body.appendChild(e); + var el = jpcl.getElementObject(e); + equals(jpcl.getAttribute(el, "id"), "FOO"); + }); + + test(renderMode + " : getDOMElement", function() { + var e = document.createElement("div"); + e.id = "FOO"; + document.body.appendChild(e); + var el = jpcl.getElementObject(e); + var e2 = jpcl.getDOMElement(el); + equals(e2.id, "FOO"); + }); + + test(renderMode + ': _jsPlumb setup', function() { + ok(_jsPlumb, "loaded"); + }); + + test(renderMode + ': getId', function() { + var d10 = _addDiv('d10'); + equals(_jsPlumb.getTestHarness().getId(d10), "d10"); + }); + + test(renderMode + ': create a simple endpoint', function() { + var d1 = _addDiv("d1"); + var e = _jsPlumb.addEndpoint($("#d1"), {}); + ok(e, 'endpoint exists'); + assertEndpointCount("d1", 1, _jsPlumb); + ok(e.id != null, "endpoint has had an id assigned"); + }); + + test(renderMode + ': create and remove a simple endpoint', function() { + var d1 = _addDiv("d1"); + var ee = _jsPlumb.addEndpoint(d1, {uuid:"78978597593"}); + ok(ee != null, "endpoint exists"); + var e = _jsPlumb.getEndpoint("78978597593"); + ok(e != null, "the endpoint could be retrieved by UUID"); + ok(e.id != null, "the endpoint has had an id assigned to it"); + assertEndpointCount("d1", 1, _jsPlumb); + assertContextSize(1); // one Endpoint canvas. + _jsPlumb.removeEndpoint(d1, ee); + assertEndpointCount("d1", 0, _jsPlumb); + e = _jsPlumb.getEndpoint("78978597593"); + equals(e, null, "the endpoint has been deleted"); + assertContextSize(0); // no Endpoint canvases. + }); + + test(renderMode + ': create two simple endpoints, registered using a selector', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + d1.addClass("window");d2.addClass("window"); + var endpoints = _jsPlumb.addEndpoint($(".window"), {}); + equals(endpoints.length, 2, "endpoint added to both windows"); + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + }); + + test(renderMode + ': create two simple endpoints, registered using an array of element ids', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + d1.addClass("window");d2.addClass("window"); + var endpoints = _jsPlumb.addEndpoint(["d1", "d2"], {}); + equals(endpoints.length, 2, "endpoint added to both windows"); + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + }); + + test(renderMode + ': draggable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1"); + var e = _jsPlumb.addEndpoint(d1, {isSource:true}); + ok(e, 'endpoint exists'); + }); + + test(renderMode + ': droppable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1") + var e = _jsPlumb.addEndpoint(d1, {isTarget:true}); + ok(e, 'endpoint exists'); + }); + + test(renderMode + ': defaultEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = _jsPlumb.addEndpoint(d3, {isSource:true}); + ok(e3.anchor, 'endpoint 3 has an anchor'); + var e4 = _jsPlumb.addEndpoint(d4, {isSource:true}); + assertEndpointCount("d3", 1, _jsPlumb); + assertEndpointCount("d4", 1, _jsPlumb); + assertContextSize(2); // one canvas for each of our two endpoints. + ok(!e3.isFull(), "endpoint 3 is not full."); + _jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // we have one connection + _jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // should have refused the connection; default max is 1. + assertContextSize(3); // now we have one more canvas, for the Connection that was accepted. + }); + + test(renderMode + ': specifiedEndpointMaxConnections', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = _jsPlumb.addEndpoint(d5, {isSource:true, maxConnections:3}); + ok(e5.anchor, 'endpoint 5 has an anchor'); + var e6 = _jsPlumb.addEndpoint(d6, {isSource:true, maxConnections:2}); // this one has max TWO + assertEndpointCount("d5", 1, _jsPlumb); assertEndpointCount("d6", 1, _jsPlumb); + assertContextSize(2); + ok(!e5.isFull(), "endpoint 5 is not full."); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 1); // we have one connection + assertContextSize(3); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // two connections + assertContextSize(4); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // should have refused; max is 2, for d4. + assertContextSize(4); + }); + + test(renderMode + ': noEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = _jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + var e4 = _jsPlumb.addEndpoint(d4, {isSource:true, maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertContextSize(3); + assertConnectionCount(e3, 1); // we have one connection + _jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 2); // we have two. etc (default was one. this proves max is working). + assertContextSize(4); + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = _jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e4}); + assertConnectionCount(e4, 3); + assertContextSize(6); + }); + + test(renderMode + ': anchors equal', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + ok(a1.equals(a2), "anchors are the same"); + }); + + test(renderMode + ': anchors equal with offsets', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb); + ok(a1.equals(a2), "anchors are the same"); + }); + + test(renderMode + ': anchors not equal', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 0, 1], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + ok(!a1.equals(a2), "anchors are different"); + }); + + test(renderMode + ': anchor not equal with offsets', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + ok(!a1.equals(a2), "anchors are different"); + }); + + test(renderMode + ': detach does not fail when no arguments are provided', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + _jsPlumb.connect({source:d3, target:d4}); + _jsPlumb.detach(); + }); + + // test that detach does not fire an event by default + test(renderMode + ': _jsPlumb.detach should fire detach event by default', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach(conn); + equals(eventCount, 1); + }); + + // test that detach does not fire an event by default + test(renderMode + ': _jsPlumb.detach should fire detach event by default, using params object', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({connection:conn}); + equals(eventCount, 1); + }); + + // test that detach fires an event when instructed to do so + test(renderMode + ': _jsPlumb.detach should not fire detach event when instructed to not do so', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach(conn, {fireEvent:false}); + equals(eventCount, 0); + }); + + // issue 81 + test(renderMode + ': _jsPlumb.detach should fire only one detach event (pass Connection as argument)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach(conn); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': _jsPlumb.detach should fire only one detach event (pass Connection as param in argument)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({connection:conn}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': detach should fire only one detach event (pass source and targets as strings as arguments in params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({source:"d5", target:"d6"}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': detach should fire only one detach event (pass source and targets as divs as arguments in params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({source:d5, target:d6, fireEvent:true}); + equals(eventCount, 1); + }); + + //TODO make sure you run this test with a single detach call, to ensure that + // single detach calls result in the connection being removed. detachEveryConnection can + // just blow away the connectionsByScope array and recreate it. + test(renderMode + ': getConnections (simple case, default scope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + _jsPlumb.connect({source:d5, target:d6}); + assertContextSize(3); + var c = _jsPlumb.getConnections(); // will get all connections in the default scope. + equals(c.length, 1, "there is one connection"); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by element id using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + _jsPlumb.connect({source:d5, target:d6}); + _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach({source:'d5', target:'d6'}); + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by id using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + _jsPlumb.connect({source:d5, target:d6}); + _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach({source:"d5", target:"d6"}); + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by element object using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + _jsPlumb.connect({source:d5, target:d6}); + _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach({source:d5, target:d6}); + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by Connection)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + var c56 = _jsPlumb.connect({source:d5, target:d6}); + var c67 = _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach(c56); + assertContextSize(5); // check that the connection canvas was removed. + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + }); + +// beforeDetach functionality + + test(renderMode + ": detach; beforeDetach on connect call returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { return false; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": detach; beforeDetach on connect call returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { return true; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": detach; beforeDetach on connect call throws an exception; we treat it with the contempt it deserves and pretend it said the detach was ok.", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { throw "i am an example of badly coded beforeDetach, but i don't break jsPlumb "; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": detach; beforeDetach on addEndpoint call to source Endpoint returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return false; } }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": detach; beforeDetach on addEndpoint call to source Endpoint returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return true; } }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + + test(renderMode + ": Endpoint.detach; beforeDetach on addEndpoint call to source Endpoint returns false; Endpoint.detach returns false too (the UI needs it to)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return false; } }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + var success = e1.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + ok(!success, "Endpoint reported detach did not execute"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns false but detach is forced", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c, {forceDetach:true}); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was denied but forced anyway"); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was denied but forced anyway"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return true; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach bound to _jsPlumb returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + _jsPlumb.bind("beforeDetach", function(connection) { + ok(connection.sourceId === "d1", "connection is provided and configured with correct source"); + ok(connection.targetId === "d2", "connection is provided and configured with correct target"); + return false; + }); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach bound to _jsPlumb returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + _jsPlumb.bind("beforeDetach", function(connection) { + ok(connection.sourceId === "d1", "connection is provided and configured with correct source"); + ok(connection.targetId === "d2", "connection is provided and configured with correct target"); + return true; + }); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was denied"); + }); + + test(renderMode + ": _jsPlumb.detachAllConnections ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detachAllConnections(d1); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachAll was called"); + }); + + test(renderMode + ": _jsPlumb.detachEveryConnection ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detachEveryConnection(); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachEveryConnection was called"); + }); + + test(renderMode + ": Endpoint.detachAll ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + e1.detachAll(); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachAllConnections was called"); + }); + +// ******** end of beforeDetach tests ************** + +// detachEveryConnection/detachAllConnections fireEvent overrides tests + + test(renderMode + ": _jsPlumb.detachEveryConnection fires events", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0; + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachEveryConnection(); + equals(connCount, 0, "no connections registered"); + }); + + test(renderMode + ": _jsPlumb.detachEveryConnection doesn't fire events when instructed not to", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0; + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachEveryConnection({fireEvent:false}); + equals(connCount, 2, "two connections registered"); + }); + + test(renderMode + ": _jsPlumb.detachAllConnections fires events", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0, + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachAllConnections("d1"); + equals(connCount, 0, "no connections registered"); + }); + + test(renderMode + ": _jsPlumb.detachAllConnections doesn't fire events when instructed not to", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0, + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachAllConnections("d1", {fireEvent:false}); + equals(connCount, 2, "two connections registered"); + }); + + test(renderMode + ': getConnections (scope testScope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + _jsPlumb.connect({source:d5, target:d6, scope:'testScope'}); + var c = _jsPlumb.getConnections("testScope"); // will get all connections in testScope + equals(c.length, 1, "there is one connection"); + equals(c[0].sourceId, 'd5', "the connection's source is d5"); + equals(c[0].targetId, 'd6', "the connection's source is d6"); + c = _jsPlumb.getConnections(); // will get all connections in default scope; should be none. + equals(c.length, 0, "there are no connections in the default scope"); + }); + + test(renderMode + ': _jsPlumb.getAllConnections (filtered by scope)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + _jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + _jsPlumb.connect({source:d9, target:d10}); // default scope + var c = _jsPlumb.getAllConnections(); // will get all connections + equals(c[_jsPlumb.getDefaultScope()].length, 1); + equals(c['testScope'].length, 1); + // now supply a list of scopes + c = _jsPlumb.getConnections(); + equals(c.length, 1); + c = _jsPlumb.getConnections("testScope"); + equals(c.length, 1, "there is one connection in 'testScope'"); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by scope and sourceId)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + _jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + _jsPlumb.connect({source:d9, target:d8, scope:'testScope'}); + _jsPlumb.connect({source:d9, target:d10}); // default scope + var c = _jsPlumb.getConnections({scope:'testScope', source:'d8'}); // will get all connections with sourceId 'd8' + equals(c.length, 1, "there is one connection in 'testScope' from d8"); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by scope, source id and target id)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope'}); + var c = _jsPlumb.getConnections({scope:'testScope', source:'d11', target:'d13'}); + equals(c.length, 1, "there is one connection from d11 to d13"); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = _jsPlumb.getConnections({scope:['testScope','testScope3']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes and source ids)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = _jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes, source ids and target ids)', function() { + _jsPlumb.detachEveryConnection(); + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'), d14=_addDiv("d14"), d15=_addDiv("d15"); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d11, target:d14, scope:'testScope'}); + _jsPlumb.connect({source:d11, target:d15, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + assertContextSize(18); + var c = _jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11'], target:['d14', 'd15']}); + equals(c['testScope'].length, 2, 'there are two connections in testScope'); + equals(c['testScope3'], null, 'there are no connections in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + var anEntry = c['testScope'][0]; + ok(anEntry.endpoints[0] != null, "Source endpoint is set"); + ok(anEntry.endpoints[1] != null, "Target endpoint is set"); + equals(anEntry.source.attr("id"), "d11", "Source is div d11"); + equals(anEntry.target.attr("id"), "d14", "Target is div d14"); + }); + + test(renderMode + ': getEndpoints, one Endpoint added by addEndpoint, get Endpoints by selector', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + _jsPlumb.addEndpoint(d1); + var e = _jsPlumb.getEndpoints(d1); + equals(e.length, 1, "there is one endpoint for element d1"); + }); + + test(renderMode + ': getEndpoints, one Endpoint added by addEndpoint, get Endpoints by id', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + _jsPlumb.addEndpoint(d1); + var e = _jsPlumb.getEndpoints("d1"); + equals(e.length, 1, "there is one endpoint for element d1"); + }); + + test(renderMode + ': connection event listeners', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null/*, returnedParams2 = null*/; + _jsPlumb.bind("jsPlumbConnection", function(params) { + returnedParams = $.extend({}, params); + }); + var c = _jsPlumb.connect({source:d1, target:d2}); + ok(returnedParams != null, "new connection listener event was fired"); + ok(returnedParams.connection != null, 'connection is set'); + equals(returnedParams.sourceId, "d1", 'sourceid is set'); + equals(returnedParams.targetId, "d2", 'targetid is set'); + equals(returnedParams.source.attr("id"), "d1", 'source is set'); + equals(returnedParams.target.attr("id"), "d2" , 'target is set'); + ok(returnedParams.sourceEndpoint != null, "source endpoint is not null"); + ok(returnedParams.targetEndpoint != null, "target endpoint is not null"); + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.detach(c); + ok(returnedParams.connection != null, 'connection is set'); + }); + + test(renderMode + ': detach event listeners (detach by connection)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + ok(returnedParams.connection != null, "removed connection listener event passed in connection"); + }); + + test(renderMode + ': detach event listeners (detach by element ids)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach({source:"d1",target:"d2"}); + ok(returnedParams != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (detach by elements)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach({source:d1,target:d2}); + ok(returnedParams != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detach method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detachFrom method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachFrom(e2); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detachAll method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachAll(); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via _jsPlumb.deleteEndpoint method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + _jsPlumb.deleteEndpoint(e1); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(1); + }); + + test(renderMode + ': detach event listeners (ensure cleared by _jsPlumb.reset)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach({source:d1,target:d2, fireEvent:true}); + ok(returnedParams != null, "removed connection listener event was fired"); + returnedParams = null; + + _jsPlumb.reset(); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach(d1,d2); + ok(returnedParams == null, "connection listener was cleared by _jsPlumb.reset()"); + }); + + test(renderMode + ': connection events that throw errors', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null, returnedParams2 = null; + _jsPlumb.bind("jsPlumbConnection", function(params) { + returnedParams = $.extend({}, params); + throw "oh no!"; + }); + _jsPlumb.connect({source:d1, target:d2}); + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + _jsPlumb.connect({source:d3, target:d4}); + ok(returnedParams != null, "new connection listener event was fired; we threw an error, _jsPlumb survived."); + }); + + test(renderMode + ": Endpoint.detachFrom", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + e16.detachFrom(e17); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by _jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": Endpoint.detachFromConnection", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + e16.detachFromConnection(conn); + assertContextSize(3); // all canvases should remain; the connection was not removed. + // but endpoint e16 should have no connections now. + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": Endpoint.detachAll", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + var e16 = _jsPlumb.addEndpoint($("#d16"), {isSource:true,maxConnections:-1}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint($("#d17"), {isSource:true}); + var e18 = _jsPlumb.addEndpoint($("#d18"), {isSource:true}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e18}); + assertConnectionCount(e16, 2); + assertConnectionCount(e17, 1); + assertConnectionCount(e18, 1); + assertContextSize(5); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 2, _jsPlumb); + e16.detachAll(); + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": Endpoint.detach", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + e16.detach(conn); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by _jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": Endpoint.isConnectedTo", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.isConnectedTo(e17), true, "e16 and e17 are connected"); + }); + + asyncTest(renderMode + "jsPlumb.util.setImage on Endpoint, with supplied onload", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { + endpoint:[ "Image", { + src:"../img/endpointTest1.png", + onload:function(imgEp) { + _jsPlumb.repaint("d1"); + ok(imgEp.img.src.indexOf("endpointTest1.png") != -1, "image source is correct"); + ok(imgEp.img.src.indexOf("endpointTest1.png") != -1, "image elementsource is correct"); + + imgEp.canvas.setAttribute("id", "iwilllookforthis"); + + ok(document.getElementById("iwilllookforthis") != null, "image element is present"); + _jsPlumb.removeAllEndpoints("d1"); + ok(document.getElementById("iwilllookforthis") == null, "image element was removed after remove endpoint"); + } + } ] + }; + start(); + _jsPlumb.addEndpoint(d1, e); + }); + + test(renderMode + ": setting endpoint uuid", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + equals(e16.getUuid(), uuid, "endpoint's uuid was set correctly"); + }); + + test(renderMode + ": _jsPlumb.getEndpoint (by uuid)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by uuid, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + _jsPlumb.deleteEndpoint(uuid); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + var ebe = _jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by uuid, connections too)", function() { + // create two endpoints (one with a uuid), add them to two divs and then connect them. + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + // check that the connection was ok. + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + // delete the endpoint that has a uuid. verify that the endpoint cannot be retrieved and that the connection has been removed, but that + // element d17 still has its Endpoint. + _jsPlumb.deleteEndpoint(uuid); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + var ebe = _jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + ebe = _jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 1, "element d17 still has its Endpoint"); + assertContextSize(1); + + // now delete d17's endpoint and check that it has gone. + _jsPlumb.deleteEndpoint(e17); + f = _jsPlumb.getEndpoint(e17); + equals(f, null, "endpoint has been deleted"); + ebe = _jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 0, "element d17 no longer has any Endpoints"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by reference, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + _jsPlumb.deleteEndpoint(e16); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by reference, connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + _jsPlumb.deleteEndpoint(e16); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + assertContextSize(1); + }); + + test(renderMode + ": _jsPlumb.deleteEveryEndpoint", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + assertContextSize(2); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + _jsPlumb.deleteEveryEndpoint(); + + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = _jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEveryEndpoint (connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + _jsPlumb.deleteEveryEndpoint(); + + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = _jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); // no canvases. so all connection canvases have been cleaned up too. + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchor:[0,0.5,0,-1]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:"TopCenter"}); + assertContextSize(2); + equals(e16.anchor.x, 0); + equals(e16.anchor.y, 0.5); + equals(e17.anchor.x, 0.5); + equals(e17.anchor.y, 0); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (tooltip)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {tooltip:"FOO"}), + e17 = _jsPlumb.addEndpoint(d17, {tooltip:"BAZ"}); + assertContextSize(2); + equals(e16.canvas.getAttribute("title"), "FOO"); + equals(e17.canvas.getAttribute("title"), "BAZ"); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case, dynamic anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + assertContextSize(2); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case, two arg method)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchor:[0,0.5,0,-1]}); + var e17 = _jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchor:"TopCenter"}); + assertContextSize(2); + equals(e16.anchor.x, 0); + equals(e16.anchor.y, 0.5); + equals(false, e16.isTarget); + equals(true, e16.isSource); + equals(e17.anchor.x, 0.5); + equals(e17.anchor.y, 0); + equals(true, e17.isTarget); + equals(false, e17.isSource); + }); + + + test(renderMode + ": _jsPlumb.addEndpoints (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false, anchor:[0,0.5,0,-1] }, { isTarget:true, isSource:false, anchor:"TopCenter" }]); + assertContextSize(2); + equals(e16[0].anchor.x, 0); + equals(e16[0].anchor.y, 0.5); + equals(false, e16[0].isTarget); + equals(true, e16[0].isSource); + equals(e16[1].anchor.x, 0.5); + equals(e16[1].anchor.y, 0); + equals(true, e16[1].isTarget); + equals(false, e16[1].isSource); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (with reference params)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var refParams = {anchor:"RightMiddle"}; + var e16 = _jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false}, { isTarget:true, isSource:false }], refParams); + assertContextSize(2); + equals(e16[0].anchor.x, 1); + equals(e16[0].anchor.y, 0.5); + equals(false, e16[0].isTarget); + equals(true, e16[0].isSource); + equals(e16[1].anchor.x, 1); + equals(e16[1].anchor.y, 0.5); + equals(true, e16[1].isTarget); + equals(false, e16[1].isSource); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case, dynamic anchors, two arg method)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchors:["TopCenter", "BottomCenter"]}); + assertContextSize(2); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (default overlays)", function() { + _jsPlumb.Defaults.Overlays = [ + [ "Label", { id:"label" } ] + ]; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16), + e2 = _jsPlumb.addEndpoint(d17); + + ok(e1.getOverlay("label") != null, "endpoint 1 has overlay from defaults"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (default overlays)", function() { + _jsPlumb.Defaults.Overlays = [ + [ "Label", { id:"label" } ] + ]; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16, { + overlays:[ + ["Label", { id:"label2", location:[ 0.5, 1 ] } ] + ] + }), + e2 = _jsPlumb.addEndpoint(d17); + + ok(e1.getOverlay("label") != null, "endpoint 1 has overlay from defaults"); + ok(e1.getOverlay("label2") != null, "endpoint 1 has overlay from addEndpoint call"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (end point set label)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16), + e2 = _jsPlumb.addEndpoint(d17); + + e1.setLabel("FOO"); + equals(e1.getLabel(), "FOO", "endpoint's label is correct"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (end point set label in constructor, as string)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16, {label:"FOO"}), + e2 = _jsPlumb.addEndpoint(d17); + + equals(e1.getLabel(), "FOO", "endpoint's label is correct"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (end point set label in constructor, as function)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16, {label:function() { return "BAZ"; }, labelLocation:0.1}), + e2 = _jsPlumb.addEndpoint(d17); + + equals(e1.getLabel()(), "BAZ", "endpoint's label is correct"); + equals(e1.getLabelOverlay().getLocation(), 0.1, "endpoint's label's location is correct"); + }); + + test(renderMode + ": _jsPlumb.makeTarget (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.makeTarget (specify two divs in an array)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.makeTarget([d16, d17], { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.makeTarget (specify two divs by id in an array)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.makeTarget(["d16", "d17"], { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.makeTarget (specify divs by selector)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + d16.addClass("FOO");d17.addClass("FOO"); + _jsPlumb.makeTarget($(".FOO"), { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e.length, 1, "d17 has one endpoint"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case, two connect calls)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17"}); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 2, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case, two connect calls, uniqueEndpoint set)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true, anchor:"LeftMiddle", uniqueEndpoint:true, maxConnections:-1 }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17"}); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].connections.length, 2, "endpoint on d17 has two connections"); + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (newConnection:true specified)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17", newConnection:true}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0.5, "anchor is BottomCenter"); //here we should be seeing the default anchor + equals(e[0].anchor.y, 1, "anchor is BottomCenter"); //here we should be seeing the default anchor + }); + +// jsPlumb.connect, after makeSource has been called on some element + test(renderMode + ": _jsPlumb.connect after makeSource (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeSource (simple case, two connect calls)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 2, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case, two connect calls, uniqueEndpoint set)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true, anchor:"LeftMiddle", uniqueEndpoint:true, maxConnections:-1 }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].connections.length, 2, "endpoint on d17 has two connections"); + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (newConnection:true specified)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16, newConnection:true}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0.5, "anchor is BottomCenter"); //here we should be seeing the default anchor + equals(e[0].anchor.y, 1, "anchor is BottomCenter"); //here we should be seeing the default anchor + }); + + test(renderMode + ": _jsPlumb.connect after makeSource on child; wth parent set (parent should be recognised)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18", d17); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d18, { isSource:true,anchor:"LeftMiddle", parent:d17 }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + assertEndpointCount("d18", 0, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + +// *********************************************** + + test(renderMode + ': _jsPlumb.connect (between two Endpoints)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1, _jsPlumb); // no new endpoint should have been added + assertEndpointCount("d2", 1, _jsPlumb); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. + ok(c.id != null, "connection has had an id assigned"); + }); + + test(renderMode + ': _jsPlumb.connect (Endpoint connectorTooltip parameter)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1, {connectorTooltip:"FOO"}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + equals(c.connector.canvas.getAttribute("title"), "FOO", "connector canvas has label attribute set"); + }); + + test(renderMode + ': _jsPlumb.connect (tooltip parameter)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2, tooltip:"FOO"}); + equals(c.connector.canvas.getAttribute("title"), "FOO", "connector canvas has label attribute set"); + }); + + test(renderMode + ': _jsPlumb.connect (between two Endpoints, and dont supply any parameters to the Endpoints.)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1, _jsPlumb); // no new endpoint should have been added + assertEndpointCount("d2", 1, _jsPlumb); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. + }); + + test(renderMode + " : _jsPlumb.connect, passing 'anchors' array" ,function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, anchors:["LeftMiddle", "RightMiddle"]}); + + equals(c.endpoints[0].anchor.x, 0, "source anchor is at x=0"); + equals(c.endpoints[0].anchor.y, 0.5, "source anchor is at y=0.5"); + equals(c.endpoints[1].anchor.x, 1, "target anchor is at x=1"); + equals(c.endpoints[1].anchor.y, 0.5, "target anchor is at y=0.5"); + }); + + test(renderMode + ': _jsPlumb.connect (by endpoint)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + }); + + test(renderMode + ': _jsPlumb.connect (cost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17, cost:567}); + assertContextSize(3); + equals(c.getCost(), 567, "connection cost is 567"); + }); + + test(renderMode + ': _jsPlumb.connect (default cost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), undefined, "default connection cost is 1"); + }); + + test(renderMode + ': _jsPlumb.connect (set cost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), undefined, "default connection cost is 1"); + c.setCost(8989); + equals(c.getCost(), 8989, "connection cost is 8989"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (connectionCost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionCost:567}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), 567, "connection cost is 567"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (change connectionCost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionCost:567, maxConnections:-1}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1}); + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), 567, "connection cost is 567"); + e16.setConnectionCost(23); + var c2 = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(c2.getCost(), 23, "connection cost is 23 after change on endpoint"); + }); + + test(renderMode + ': _jsPlumb.connect (default bidirectional)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true}), + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.isBidirectional(), true, "default connection is bidirectional"); + }); + + test(renderMode + ': _jsPlumb.connect (bidirectional false)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true}), + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17, bidirectional:false}); + assertContextSize(3); + equals(c.isBidirectional(), false, "connection is not bidirectional"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (connectionsBidirectional)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionsBidirectional:true, maxConnections:-1}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.isBidirectional(), true, "connection is bidrectional"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (change connectionsBidirectional)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionsBidirectional:true, maxConnections:-1}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1}); + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.isBidirectional(), true, "connection is bidirectional"); + e16.setConnectionsBidirectional(false); + var c2 = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(c2.isBidirectional(), false, "connection is not bidirectional"); + }); + + test(renderMode + ": _jsPlumb.connect (two Endpoints - that have been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = _jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1, uuid:srcEndpointUuid}); + var e2 = _jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1, uuid:dstEndpointUuid}); + _jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ] }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertContextSize(3); + }); + + test(renderMode + ": _jsPlumb.connect (two Endpoints - that have not been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ], source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + var e1 = _jsPlumb.getEndpoint(srcEndpointUuid); + ok(e1 != null, "endpoint with src uuid added"); + ok(e1.canvas != null); + var e2 = _jsPlumb.getEndpoint(dstEndpointUuid); + ok(e2 != null, "endpoint with target uuid added"); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.connect (two Endpoints - that have been already added - by endpoint reference)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = _jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1}); + var e2 = _jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1}); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.connect (two elements)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.connect({ source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, straight)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, bezier, no params)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Bezier" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 150, "Bezier connector chose 150 curviness"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, bezier, curviness as int)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", { curviness:200 }] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Canvas Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 200, "Bezier connector chose 200 curviness"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, bezier, curviness as named option)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", {curviness:300}] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 300, "Bezier connector chose 300 curviness"); + }); + + test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Canvas Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as strings)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:["LeftMiddle", "RightMiddle"] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.5, conn.endpoints[0].anchor.y, "source anchor y"); + equals(1, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.5, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + + test(renderMode + ": _jsPlumb.connect (two argument method in which some data is reused across connections)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"), d19 = _addDiv("d19"); + var sharedData = { connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }; + var conn = _jsPlumb.connect({ source:d16, target:d17}, sharedData); + var conn2 = _jsPlumb.connect({ source:d18, target:d19}, sharedData); + assertContextSize(6); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 2, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(conn2.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + equals(0.3, conn2.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn2.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn2.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn2.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoint test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoints test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Dot, "Dot endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Blank Endpoint specified via 'endpoint' param)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Blank" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Blank Endpoint specified via 'endpoints' param)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Blank", "Blank" ] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Dot, "Dot endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, connector test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {}); + var e17 = _jsPlumb.addEndpoint(d17, {}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {}); + var e17 = _jsPlumb.addEndpoint(d17, {}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, anchors as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = "TopCenter", a17 = "BottomCenter"; + var e16 = _jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, 0.5, "endpoint 16 is at top center");equals(e16.anchor.y, 0, "endpoint 16 is at top center"); + equals(e17.anchor.x, 0.5, "endpoint 17 is at bottom center");equals(e17.anchor.y, 1, "endpoint 17 is at bottom center"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = [0,0.5,0,-1], a17 = [1,0.0,-1,-1]; + var e16 = _jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, a16[0]);equals(e16.anchor.y, a16[1]); + equals(e17.anchor.x, a17[0]);equals(e17.anchor.y, a17[1]); + equals(e16.anchor.getOrientation()[0], a16[2]); equals(e16.anchor.getOrientation()[1], a16[3]); + equals(e17.anchor.getOrientation()[0], a17[2]); equals(e17.anchor.getOrientation()[1], a17[3]); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchor')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchor:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:["TopCenter", "BottomCenter"]}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchors')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, default endpoint, supplied dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var anchors = [ [0.25, 0, 0, -1], [1, 0.25, 1, 0], [0.75, 1, 0, 1], [0, 0.75, -1, 0] ]; + _jsPlumb.connect({source:d1, target:d2, dynamicAnchors:anchors}); // auto connect with default endpoint and provided anchors + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.detach({source:d1, target:d2}); + // this changed in 1.3.5, because auto generated endpoints are now removed by detach. so i added the test below this one + // to check that the deleteEndpointsOnDetach flag is honoured. + assertEndpointCount("d1", 0, _jsPlumb);assertEndpointCount("d2", 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, default endpoint, supplied dynamic anchors, delete on detach false)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var anchors = [ [0.25, 0, 0, -1], [1, 0.25, 1, 0], [0.75, 1, 0, 1], [0, 0.75, -1, 0] ]; + _jsPlumb.connect({ + source:d1, + target:d2, + dynamicAnchors:anchors, + deleteEndpointsOnDetach:false + }); // auto connect with default endpoint and provided anchors + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.detach({source:d1, target:d2}); + // this changed in 1.3.5, because auto generated endpoints are now removed by detach. so i added this test + // to check that the deleteEndpointsOnDetach flag is honoured. + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, supplied endpoint and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = _jsPlumb.addEndpoint(d1, endpoint); + var e2 = _jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(3); + _jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, supplied endpoints using 'source' and 'target' (this test is identical to the one above apart from the param names), and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = _jsPlumb.addEndpoint(d1, endpoint); + var e2 = _jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + _jsPlumb.connect({source:e1, target:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(3); + _jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + " detachable parameter defaults to true on _jsPlumb.connect", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), true, "connections detachable by default"); + }); + + test(renderMode + " detachable parameter set to false on _jsPlumb.connect", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, detachable:false}); + equals(c.isDetachable(), false, "connection detachable"); + }); + + test(renderMode + " setDetachable on initially detachable connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), true, "connection initially detachable"); + c.setDetachable(false); + equals(c.isDetachable(), false, "connection not detachable"); + }); + + test(renderMode + " setDetachable on initially not detachable connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, detachable:false }); + equals(c.isDetachable(), false, "connection not initially detachable"); + c.setDetachable(true); + equals(c.isDetachable(), true, "connection now detachable"); + }); + + test(renderMode + " _jsPlumb.Defaults.ConnectionsDetachable", function() { + _jsPlumb.Defaults.ConnectionsDetachable = false; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), false, "connections not detachable by default (overrode the defaults)"); + _jsPlumb.Defaults.ConnectionsDetachable = true; + }); + + + test(renderMode + ": _jsPlumb.connect (testing for connection event callback)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var connectCallback = null, detachCallback = null; + _jsPlumb.bind("jsPlumbConnection", function(params) { + connectCallback = $.extend({}, params); + }); + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + detachCallback = $.extend({}, params); + }); + _jsPlumb.connect({source:d1, target:d2}); // auto connect with default endpoint and anchor set + ok(connectCallback != null, "connect callback was made"); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.detach({source:d1, target:d2}); + assertContextSize(2); + ok(detachCallback != null, "detach callback was made"); + }); + + test(renderMode + ": _jsPlumb.connect (setting cssClass on Connector)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2,cssClass:"CSS"}); + var has = function(clazz) { + var cn = c.connector.canvas.className, + cns = cn.constructor == String ? cn : cn.baseVal; + + return cns.indexOf(clazz) != -1; + }; + ok(has("CSS"), "custom cssClass set correctly"); + ok(has(_jsPlumb.connectorClass), "basic connector class set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (overlays, long-hand version)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = {width:40,length:40,location:0.7, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"}}; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + }); + + test(renderMode + ": _jsPlumb.connect (overlays, long-hand version, IDs specified)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + equals("aLabel", connection1.overlays[0].id); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + equals("anArrow", connection1.overlays[1].id); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays)", function() { + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays + overlays specified in connect call)", function() { + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, overlays:[ + ["Label", {id:"label"}] + ]}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("label") != null, "Label overlay created from connect call"); + }); + + test(renderMode + ": _jsPlumb.connect (default connection overlays)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + }); + + test(renderMode + ": _jsPlumb.connect (default connection overlays + overlays specified in connect call)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, overlays:[ + ["Label", {id:"label"}] + ]}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("label") != null, "Label overlay created from connect call"); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays + default connection overlays)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow2" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("arrow2") != null, "Arrow overlay created from connection defaults"); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays + default connection overlays)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow2" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, overlays:[ + ["Label", {id:"label"}] + ]}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("arrow2") != null, "Arrow overlay created from connection defaults"); + ok(c.getOverlay("label") != null, "Label overlay created from connect call"); + }); + + test(renderMode + ": _jsPlumb.connect (label overlay set using 'label')", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO" + }); + + equals(c.getLabel(), "FOO", "label is set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction, with string)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2}); + + c.setLabel("FOO"); + equals(c.getLabel(), "FOO", "label is set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction, with function)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2}); + + c.setLabel(function() { return "BAR"; }); + equals(c.getLabel()(), "BAR", "label is set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction, with params object)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2}); + + c.setLabel({ + label:"BAZ", + cssClass:"CLASSY", + location:0.9 + }); + var lo = c.getLabelOverlay(); + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "BAZ", "label overlay has correct value"); + equals(lo.getLocation(), 0.9, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction with existing label set, with params object)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO", + labelLocation:0.2 + }); + + var lo = c.getLabelOverlay(); + equals(lo.getLocation(), 0.2, "label overlay has correct location"); + + c.setLabel({ + label:"BAZ", + cssClass:"CLASSY", + location:0.9 + }); + + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "BAZ", "label overlay has correct value"); + equals(lo.getLocation(), 0.9, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (getLabelOverlay, label on connect call)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO" + }), + lo = c.getLabelOverlay(); + + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "FOO", "label overlay has correct value"); + equals(lo.getLocation(), 0.5, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (getLabelOverlay, label on connect call, location set)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO", + labelLocation:0.2 + }), + lo = c.getLabelOverlay(); + + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "FOO", "label overlay has correct value"); + equals(lo.getLocation(), 0.2, "label overlay has correct location"); + }); + + + test(renderMode + ": _jsPlumb.connect (remove single overlay by id)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + connection1.removeOverlay("aLabel"); + equals(1, connection1.overlays.length); + equals("anArrow", connection1.overlays[0].id); + }); + + test(renderMode + ": _jsPlumb.connect (remove multiple overlays by id)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + connection1.removeOverlays("aLabel", "anArrow"); + equals(0, connection1.overlays.length); + }); + + test(renderMode + ": _jsPlumb.connect (overlays, short-hand version)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var loc = { location:0.7 }; + var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label", {label:"CONNECTION 1", location:0.3}], + ["Arrow", arrowSpec, loc] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + }); + + test(renderMode + ": _jsPlumb.connect (removeAllOverlays)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var loc = { location:0.7 }; + var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label", {label:"CONNECTION 1", location:0.3}], + ["Arrow", arrowSpec, loc] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + + connection1.removeAllOverlays(); + equals(0, connection1.overlays.length); + }); + + test(renderMode + ": _jsPlumb.connect, specify arrow overlay using string identifier only", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:["Arrow"]}); + equals(jsPlumb.Overlays[renderMode].Arrow, conn.overlays[0].constructor); + }); + + test(renderMode + ": Connection.getOverlay method, existing overlay", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("arrowOverlay"); + ok(overlay != null); + }); + + test(renderMode + ": Connection.getOverlay method, non-existent overlay", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("IDONTEXIST"); + ok(overlay == null); + }); + + test(renderMode + ": Overlay.setVisible method", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("arrowOverlay"); + ok(overlay.isVisible()); + overlay.setVisible(false); + ok(!overlay.isVisible()); + overlay.setVisible(true); + ok(overlay.isVisible()); + }); + + // this test is for the original detach function; it should stay working after i mess with it + // a little. + test(renderMode + ": _jsPlumb.detach (by element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + var e3 = _jsPlumb.addEndpoint(d1); + var e4 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + _jsPlumb.connect({ sourceEndpoint:e3, targetEndpoint:e4 }); // make two connections to be sure this works ;) + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionCount(e3, 1); + assertConnectionCount(e4, 1); + + _jsPlumb.detach({source:"d1", target:"d2"}); + + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionCount(e3, 0); + assertConnectionCount(e4, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + // detach is being made to operate more like connect - by taking one argument with a whole + // bunch of possible params in it. if two args are passed in it will continue working + // in the old way. + test(renderMode + ": _jsPlumb.detach (params object, using element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({source:"d1", target:"d2"}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); +/* + test(renderMode + ": _jsPlumb.detach (params object, using target only)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e1 = _jsPlumb.addEndpoint(d1, {maxConnections:2}), + e2 = _jsPlumb.addEndpoint(d2), + e3 = _jsPlumb.addEndpoint(d3); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e3 }); + assertConnectionCount(e1, 2); + assertConnectionCount(e2, 1); + assertConnectionCount(e3, 1); + _jsPlumb.detach({target:"d2"}); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 0); + assertConnectionCount(e3, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1); + }); +*/ + test(renderMode + ": _jsPlumb.detach (params object, using element objects)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({source:d1, target:d2}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.detach (source and target as endpoint UUIDs)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {uuid:"abcdefg"}); + ok(_jsPlumb.getEndpoint("abcdefg") != null, "e1 exists"); + var e2 = _jsPlumb.addEndpoint(d2, {uuid:"hijklmn"}); + ok(_jsPlumb.getEndpoint("hijklmn") != null, "e2 exists"); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({uuids:["abcdefg", "hijklmn"]}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.detach (sourceEndpoint and targetEndpoint supplied)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.makeDynamicAnchors (longhand)", function() { + var anchors = [_jsPlumb.makeAnchor([0.2, 0, 0, -1], null, _jsPlumb), _jsPlumb.makeAnchor([1, 0.2, 1, 0], null, _jsPlumb), + _jsPlumb.makeAnchor([0.8, 1, 0, 1], null, _jsPlumb), _jsPlumb.makeAnchor([0, 0.8, -1, 0], null, _jsPlumb) ]; + var dynamicAnchor = _jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); + }); + + test(renderMode + ": _jsPlumb.makeDynamicAnchors (shorthand)", function() { + var anchors = [[0.2, 0, 0, -1], [1, 0.2, 1, 0], + [0.8, 1, 0, 1], [0, 0.8, -1, 0] ]; + var dynamicAnchor = _jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); + }); + + test(renderMode + ": Connection.isVisible/setVisible", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c1 = _jsPlumb.connect({source:d1,target:d2}); + equals(true, c1.isVisible(), "Connection is visible after creation."); + c1.setVisible(false); + equals(false, c1.isVisible(), "Connection is not visible after calling setVisible(false)."); + equals($(c1.connector.canvas).css("display"), "none"); + c1.setVisible(true); + equals(true, c1.isVisible(), "Connection is visible after calling setVisible(true)."); + equals($(c1.connector.canvas).css("display"), "block"); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible basic test (no connections)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals($(e1.canvas).css("display"), "none"); + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals($(e1.canvas).css("display"), "block"); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should track changes in the source, because it has only this connection.)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + var c1 = _jsPlumb.connect({source:e1, target:e2}); + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals(false, e2.isVisible(), "other Endpoint is not visible either."); + equals(false, c1.isVisible(), "connection between the two is not visible either."); + + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals(true, e2.isVisible(), "other Endpoint is visible too"); + equals(true, c1.isVisible(), "connection between the two is visible too."); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should not track changes in the source, because it has another connection.)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2, { maxConnections:2 }), e3 = _jsPlumb.addEndpoint(d3); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + var c1 = _jsPlumb.connect({source:e1, target:e2}); + var c2 = _jsPlumb.connect({source:e2, target:e3}); + + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals(true, e2.isVisible(), "other Endpoint should still be visible."); + equals(true, e3.isVisible(), "third Endpoint should still be visible."); + equals(false, c1.isVisible(), "connection between the two is not visible either."); + equals(true, c2.isVisible(), "other connection is visible."); + + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals(true, e2.isVisible(), "other Endpoint is visible too"); + equals(true, c1.isVisible(), "connection between the two is visible too."); + equals(true, c2.isVisible(), "other connection is visible."); + }); + + // tests of the functionality that allows a user to specify that they want elements appended to the document body + test(renderMode + " _jsPlumb.Defaults.Container, specified with a selector", function() { + _jsPlumb.Defaults.Container = $("body"); + equals($("#container")[0].childNodes.length, 0, "container has no nodes"); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals($("#container")[0].childNodes.length, 2, "container has two div elements"); // the divs we added have been added to the 'container' div. + // but we have told _jsPlumb to add its canvas to the body, so this connect call should not add another few elements to the container: + _jsPlumb.connect({source:d1, target:d2}); + equals($("#container")[0].childNodes.length, 2, "container still has two div elements"); + }); + + // tests of the functionality that allows a user to specify that they want elements appended to some specific container. + test(renderMode + " _jsPlumb.Defaults.Container, specified with DOM element", function() { + _jsPlumb.Defaults.Container = document.getElementsByTagName("body")[0]; + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length, "two divs added to the container"); // the divs we added have been added to the 'container' div. + // but we have told _jsPlumb to add its canvas to the body, so this connect call should not add another few elements to the container: + var bodyElementCount = $("body")[0].childNodes.length; + _jsPlumb.connect({source:d1, target:d2}); + equals(2, $("#container")[0].childNodes.length, "still only two children in container; elements were added to the body by _jsPlumb"); + // test to see if 3 elements have been added + equals(bodyElementCount + 3, $("body")[0].childNodes.length, "3 new elements added to the document body"); + }); + + test(renderMode + " container specified to connect call, with a selector", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length); // the divs we added have been added to the 'container' div. + var bodyElementCount = $("body")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to the body, so this connect call should not add another few elements to the container: + _jsPlumb.connect({source:d1, target:d2, container:$("body")}); + equals(2, $("#container")[0].childNodes.length); + equals(bodyElementCount + 3, $("body")[0].childNodes.length, "3 new elements added to the document body"); + }); + + test(renderMode + " container specified to connect call, with a string ID", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + equals(3, $("#container")[0].childNodes.length, "container has divs we added"); // the divs we added have been added to the 'container' div. + var d3ElementCount = $("#d3")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to "d3", so this connect call should not add another few elements to the container: + _jsPlumb.connect({source:d1, target:d2, container:"d3"}); + equals(3, $("#container")[0].childNodes.length, "container still has only the divs we added"); + equals(d3ElementCount + 3, $("#d3")[0].childNodes.length, "3 new elements added to div d3"); + }); + + test(renderMode + " container specified to addEndpoint call, with a selector", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length); // the divs we added have been added to the 'container' div. + var bodyElementCount = $("body")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to the body, so this connect call should not add another few elements to the container: + _jsPlumb.addEndpoint(d1, {container:$("body")}); + equals(2, $("#container")[0].childNodes.length); + equals(bodyElementCount + 1, $("body")[0].childNodes.length, "1 new element added to the document body"); + }); + + test(renderMode + " container specified to addEndpoint call, with a string ID", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + equals(3, $("#container")[0].childNodes.length, "container has divs we added"); // the divs we added have been added to the 'container' div. + var d3ElementCount = $("#d3")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to "d3", so this connect call should not add another few elements to the container: + _jsPlumb.addEndpoint(d1, { container:"d3" }); + equals(3, $("#container")[0].childNodes.length, "container still has only the divs we added"); + equals(d3ElementCount + 1, $("#d3")[0].childNodes.length, "1 new element added to div d3"); + }); + + test(renderMode + " detachable defaults to true when connection made between two endpoints", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2), + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection not detachable"); + }); + + test(renderMode + " connection detachable when target endpoint has connectionsDetachable set to true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2, {connectionsDetachable:true}), + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection detachable because connectionsDetachable was set on target endpoint"); + }); + + test(renderMode + " connection detachable when source endpoint has connectionsDetachable set to true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {connectionsDetachable:true}), e2 = _jsPlumb.addEndpoint(d2), + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection detachable because connectionsDetachable was set on source endpoint"); + }); + + test(renderMode + " Connector has 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.connector.type, "Bezier", "Bezier connector has type set"); + + var c2 = _jsPlumb.connect({source:d1, target:d2, connector:"Straight"}); + equals(c2.connector.type, "Straight", "Straight connector has type set"); + + var c3 = _jsPlumb.connect({source:d1, target:d2, connector:"Flowchart"}); + equals(c3.connector.type, "Flowchart", "Flowchart connector has type set"); + }); + + test(renderMode + " Endpoints have 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.endpoints[0].type, "Dot", "Dot endpoint has type set"); + + var c2 = _jsPlumb.connect({source:d1, target:d2, endpoints:["Rectangle", "Blank"]}); + equals(c2.endpoints[1].type, "Blank", "Blank endpoint has type set"); + equals(c2.endpoints[0].type, "Rectangle", "Rectangle endpoint has type set"); + }); + + test(renderMode + " Overlays have 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = _jsPlumb.connect({ + source:d1, + target:d2, + overlays:[ "Arrow", "Label", "PlainArrow", "Diamond" ] + }); + equals(c.overlays[0].type, "Arrow", "Arrow overlay has type set"); + equals(c.overlays[1].type, "Label", "Label overlay has type set"); + equals(c.overlays[2].type, "PlainArrow", "PlainArrow overlay has type set"); + equals(c.overlays[3].type, "Diamond", "Diamond overlay has type set"); + }); + + test(renderMode + " _jsPlumb.hide, original one-arg version", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + _jsPlumb.hide(d1); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(true, e1.isVisible(), "endpoint 1 is still visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + _jsPlumb.show(d1); + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + }); + + test(renderMode + " _jsPlumb.hide, two-arg version, endpoints should also be hidden", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + _jsPlumb.show(d1); // now show d1, but do not alter the endpoints. e1 should still be hidden + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + test(renderMode + " _jsPlumb.show, two-arg version, endpoints should become visible", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + _jsPlumb.show(d1, true); // now show d1, and alter the endpoints. e1 should be visible. + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(true, e1.isVisible(), "endpoint 1 is visible again."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + test(renderMode + " _jsPlumb.show, two-arg version, endpoints should become visible, but not all connections, because some other endpoints are not visible.", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e11 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + e3 = _jsPlumb.addEndpoint(d3, e), + c1 = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e11, target:e3}); + + // we now have d1 connected to both d3 and d2. we'll hide d1, and everything on d1 should be hidden. + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "connection 1 is no longer visible."); + equals(false, c2.isVisible(), "connection 2 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(false, e11.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + equals(true, e3.isVisible(), "endpoint 3 is still visible."); + + // now, we will also hide d3. making d1 visible again should NOT result in c2 becoming visible, because the other endpoint + // for c2 is e3, which is not visible. + _jsPlumb.hide(d3, true); + equals(false, e3.isVisible(), "endpoint 3 is no longer visible."); + + _jsPlumb.show(d1, true); // now show d1, and alter the endpoints. e1 should be visible, c1 should be visible, but c2 should not. + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(false, c2.isVisible(), "Connection 2 is not visible."); + equals(true, e1.isVisible(), "endpoint 1 is visible again."); + equals(true, e11.isVisible(), "endpoint 11 is visible again."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + equals(false, e3.isVisible(), "endpoint 3 is still not visible."); + }); + + /* + test(renderMode + " _jsPlumb.hide, two-arg version, endpoints should also be hidden", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + /** + * test for issue 132: label leaves its element in the DOM after it has been + * removed from a connection. + */ + test(renderMode + " label cleans itself up properly", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label", cssClass:"foo"}] + ]}); + ok($(".foo").length == 1, "label element exists in DOM"); + c.removeOverlay("label"); + ok($(".foo").length == 0, "label element does not exist in DOM"); + }); + + test(renderMode + " arrow cleans itself up properly", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Arrow", {id:"arrow"}] + ]}); + ok(c.getOverlay("arrow") != null, "arrow overlay exists"); + c.removeOverlay("arrow"); + ok(c.getOverlay("arrow") == null, "arrow overlay has been removed"); + }); + + test(renderMode + " label overlay getElement function", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label"}] + ]}); + ok(c.getOverlay("label").getElement() != null, "label overlay exposes element via getElement method"); + }); + + test(renderMode + " label overlay provides getLabel and setLabel methods", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label", label:"foo"}] + ]}); + var o = c.getOverlay("label"), e = o.getElement(); + ok(e.innerHTML == "foo", "label text is set to original value"); + o.setLabel("baz"); + ok(e.innerHTML == "baz", "label text is set to new value 'baz'"); + ok(o.getLabel() === "baz", "getLabel function works correctly with String"); + // now try functions + var aFunction = function() { return "aFunction"; }; + o.setLabel(aFunction); + ok(e.innerHTML == "aFunction", "label text is set to new value from Function"); + ok(o.getLabel() === aFunction, "getLabel function works correctly with Function"); + }); + + test(renderMode + " parameters object works for Endpoint", function() { + var d1 = _addDiv("d1"), + f = function() { alert("FOO!"); }, + e = _jsPlumb.addEndpoint(d1, { + isSource:true, + parameters:{ + "string":"param1", + "int":4, + "function":f + } + }); + ok(e.getParameter("string") === "param1", "getParameter(String) works correctly"); + ok(e.getParameter("int") === 4, "getParameter(int) works correctly"); + ok(e.getParameter("function") == f, "getParameter(Function) works correctly"); + }); + + test(renderMode + " parameters object works for Connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + f = function() { alert("FOO!"); }; + var c = _jsPlumb.connect({ + source:d1, + target:d2, + parameters:{ + "string":"param1", + "int":4, + "function":f + } + }); + ok(c.getParameter("string") === "param1", "getParameter(String) works correctly"); + ok(c.getParameter("int") === 4, "getParameter(int) works correctly"); + ok(c.getParameter("function") == f, "getParameter(Function) works correctly"); + }); + + test(renderMode + " parameters set on Endpoints and Connections are all merged, and merged correctly at that.", function() { + var d1 = _addDiv("d1"), + d2 = _addDiv("d2"), + e = _jsPlumb.addEndpoint(d1, { + isSource:true, + parameters:{ + "string":"sourceEndpoint", + "int":0, + "function":function() { return "sourceEndpoint"; } + } + }), + e2 = _jsPlumb.addEndpoint(d2, { + isTarget:true, + parameters:{ + "int":1, + "function":function() { return "targetEndpoint"; } + } + }), + c = _jsPlumb.connect({source:e, target:e2, parameters:{ + "function":function() { return "connection"; } + }}); + + ok(c.getParameter("string") === "sourceEndpoint", "getParameter(String) works correctly"); + ok(c.getParameter("int") === 1, "getParameter(int) works correctly"); + ok(c.getParameter("function")() == "connection", "getParameter(Function) works correctly"); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers standard connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2}); + equals(_jsPlumb.anchorManager.getConnectionsFor("d1").length, 1); + equals(_jsPlumb.anchorManager.getEndpointsFor("d1").length, 1); + equals(_jsPlumb.anchorManager.getConnectionsFor("d2").length, 1); + equals(_jsPlumb.anchorManager.getEndpointsFor("d2").length, 1); + var c2 = _jsPlumb.connect({source:d1, target:d2}); + equals(_jsPlumb.anchorManager.getConnectionsFor("d1").length, 2); + equals(_jsPlumb.anchorManager.getConnectionsFor("d2").length, 2); + equals(_jsPlumb.anchorManager.getEndpointsFor("d1").length, 2); + equals(_jsPlumb.anchorManager.getEndpointsFor("d2").length, 2); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers dynamic anchor connection, and removes it.", function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var c = _jsPlumb.connect({source:d3, target:d4, anchors:["AutoDefault", "AutoDefault"]}); + + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 1); + + var c2 = _jsPlumb.connect({source:d3, target:d4}); + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 2); + equals(_jsPlumb.anchorManager.getConnectionsFor("d4").length, 2); + + equals(_jsPlumb.anchorManager.getEndpointsFor("d3").length, 2); + _jsPlumb.detach(c); + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 1); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers continuous anchor connection, and removes it.", function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var c = _jsPlumb.connect({source:d3, target:d4, anchors:["Continuous", "Continuous"]}); + + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 1); + equals(_jsPlumb.anchorManager.getConnectionsFor("d4").length, 1); + + _jsPlumb.detach(c); + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 0); + equals(_jsPlumb.anchorManager.getConnectionsFor("d4").length, 0); + + _jsPlumb.reset(); + equals(_jsPlumb.anchorManager.getEndpointsFor("d4").length, 0); + }); + +// ------------- utility functions - math stuff, mostly -------------------------- + + var tolerance = 0.00000005, withinTolerance = function(v1, v2, msg) { + if (Math.abs(v1 - v2) < tolerance) ok(true, msg + "; expected " + v1 + " and got it"); + else { + ok(false, msg + "; expected " + v1 + " got " + v2); + } + }; + test(renderMode + " jsPlumb.util.gradient, segment 1", function() { + var p1 = [2,2], p2 = [3,1], m = jsPlumb.util.gradient(p1,p2); + equals(m, -1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumb.util.normal, segment 1", function() { + var p1 = [2,2], p2 = [3,1], m = jsPlumb.util.normal(p1,p2); + equals(m, 1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumb.util.gradient, segment 2", function() { + var p1 = [2,2], p2 = [3,3], m = jsPlumb.util.gradient(p1,p2); + equals(m, 1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumb.util.normal, segment 2", function() { + var p1 = [2,2], p2 = [3,3], m = jsPlumb.util.normal(p1,p2); + equals(m, -1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumb.util.gradient, segment 3", function() { + var p1 = [2,2], p2 = [1,3], m = jsPlumb.util.gradient(p1,p2); + equals(m, -1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumb.util.normal, segment 3", function() { + var p1 = [2,2], p2 = [1,3], m = jsPlumb.util.normal(p1,p2); + equals(m, 1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumb.util.gradient, segment 4", function() { + var p1 = [2,2], p2 = [1,1], m = jsPlumb.util.gradient(p1,p2); + equals(m, 1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumb.util.normal, segment 4", function() { + var p1 = [2,2], p2 = [1,1], m = jsPlumb.util.normal(p1,p2); + equals(m, -1, "normal calculated correctly for simple case"); + }); + test(renderMode + "jsPlumb.util.pointOnLine, segment 1", function() { + var p1 = {x:2,y:2}, p2={x:3, y:1}, + target = jsPlumb.util.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumb.util.pointOnLine, segment 2", function() { + var p1 = {x:2,y:2}, p2={x:3, y:3}, + target = jsPlumb.util.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumb.util.pointOnLine, segment 3", function() { + var p1 = {x:2,y:2}, p2={x:1, y:3}, + target = jsPlumb.util.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumb.util.pointOnLine, segment 4", function() { + var p1 = {x:2,y:2}, p2={x:1, y:1}, + target = jsPlumb.util.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumb.util.perpendicularLineTo, segment 1", function() { + var p1 = {x:2, y:2}, p2={x:3, y:1}, m = jsPlumb.util.gradient(p1, p2), + l = jsPlumb.util.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(4, l[0].x, "point 1 x is correct"); + withinTolerance(2, l[0].y, "point 1 y is correct"); + + withinTolerance(2, l[1].x, "point 2 x is correct"); + withinTolerance(0, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumb.util.perpendicularLineTo, segment 2", function() { + var p1 = {x:2, y:2}, p2={x:3, y:3}, m = jsPlumb.util.gradient(p1, p2), + l = jsPlumb.util.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(4, l[0].x, "point 1 x is correct"); + withinTolerance(2, l[0].y, "point 1 y is correct"); + + withinTolerance(2, l[1].x, "point 2 x is correct"); + withinTolerance(4, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumb.util.perpendicularLineTo, segment 3", function() { + var p1 = {x:2, y:2}, p2={x:1, y:3}, m = jsPlumb.util.gradient(p1, p2), + l = jsPlumb.util.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(2, l[0].x, "point 1 x is correct"); + withinTolerance(4, l[0].y, "point 1 y is correct"); + + withinTolerance(0, l[1].x, "point 2 x is correct"); + withinTolerance(2, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumb.util.perpendicularLineTo, segment 4", function() { + var p1 = {x:2, y:2}, p2={x:1, y:1}, m = jsPlumb.util.gradient(p1, p2), + l = jsPlumb.util.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(2, l[0].x, "point 1 x is correct"); + withinTolerance(0, l[0].y, "point 1 y is correct"); + + withinTolerance(0, l[1].x, "point 2 x is correct"); + withinTolerance(2, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumb.util.intersects, with intersection", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 3, y:4, w:3, h:3}; + + ok(jsPlumb.util.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumb.util.intersects, with no intersection", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 13, y:4, w:3, h:3}; + + ok(!jsPlumb.util.intersects(r1, r2), "r1 and r2 do not intersect"); + }); + test(renderMode + "jsPlumb.util.intersects, with intersection, equal Y", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 1, y:2, w:3, h:6}; + + ok(jsPlumb.util.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumb.util.intersects, with intersection, equal X", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 2, y:1, w:4, h:6}; + + ok(jsPlumb.util.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumb.util.intersects, identical rectangles", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 2, y:2, w:4, h:6}; + + ok(jsPlumb.util.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumb.util.intersects, corners touch (intersection)", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 6, y:8, w:3, h:3}; + + ok(jsPlumb.util.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumb.util.intersects, one rectangle contained within the other", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 0, y:0, w:23, h:23}; + + ok(jsPlumb.util.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumb.util.setImage on Endpoint", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + originalUrl = "../../img/endpointTest1.png", + e = { + endpoint:[ "Image", { src:originalUrl } ] + }, + ep = _jsPlumb.addEndpoint(d1, e); + }); + test(renderMode + "jsPlumb.util.setImage on Endpoint, with supplied onload", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { + endpoint:[ "Image", { + src:"../../img/endpointTest1.png", + onload:function(imgEp) { + equals("../../img/endpointTest1.png", imgEp.img.src); + equals(ep.endpoint.canvas.getAttribute("src"), imgEp.img.src); + } + } ] + }, + ep = _jsPlumb.addEndpoint(d1, e); + ep.setImage("../../img/littledot.png", function(imgEp) { + equals("../../img/littledot.png", imgEp.img.src); + equals(ep.endpoint.canvas.getAttribute("src"), imgEp.img.src); + }); + }); + + test(renderMode + "attach endpoint to table when desired element was td", function() { + var table = document.createElement("table"), + tr = document.createElement("tr"), + td = document.createElement("td"); + table.appendChild(tr); tr.appendChild(td); + document.body.appendChild(table); + var ep = _jsPlumb.addEndpoint(td); + equals(ep.canvas.parentNode.tagName.toLowerCase(), "table"); + }); + +// issue 190 - regressions with getInstance. these tests ensure that generated ids are unique across +// instances. + + test(renderMode + " id clashes between instances", function() { + var d1 = document.createElement("div"), + d2 = document.createElement("div"), + _jsp2 = jsPlumb.getInstance(); + + document.body.appendChild(d1); + document.body.appendChild(d2); + + _jsPlumb.addEndpoint(d1); + _jsp2.addEndpoint(d2); + + var id1 = d1.getAttribute("id"), + id2 = d2.getAttribute("id"); + + var idx = id1.indexOf("_"), idx2 = id1.lastIndexOf("_"), v1 = id1.substring(idx, idx2); + var idx3 = id2.indexOf("_"), idx4 = id2.lastIndexOf("_"), v2 = id2.substring(idx3, idx4); + + ok (v1 != v2, "instance versions are different : " + v1 + " : " + v2); + }); + + test(renderMode + " id clashes between instances", function() { + var d1 = document.createElement("div"), + d2 = document.createElement("div"), + _jsp2 = jsPlumb.getInstance(); + + document.body.appendChild(d1); + document.body.appendChild(d2); + + _jsPlumb.addEndpoint(d1); + _jsPlumb.addEndpoint(d2); + + var id1 = d1.getAttribute("id"), + id2 = d2.getAttribute("id"); + + var idx = id1.indexOf("_"), idx2 = id1.lastIndexOf("_"), v1 = id1.substring(idx, idx2); + var idx3 = id2.indexOf("_"), idx4 = id2.lastIndexOf("_"), v2 = id2.substring(idx3, idx4); + + ok (v1 == v2, "instance versions are the same : " + v1 + " : " + v2); + }); + + test(renderMode + " importDefaults", function() { + _jsPlumb.Defaults.Anchors = ["LeftMiddle", "RightMiddle"]; + var d1 = _addDiv("d1"), + d2 = _addDiv(d2), + c = _jsPlumb.connect({source:d1, target:d2}), + e = c.endpoints[0]; + + equals(e.anchor.x, 0, "left middle anchor"); + equals(e.anchor.y, 0.5, "left middle anchor"); + + _jsPlumb.importDefaults({ + Anchors:["TopLeft", "TopRight"] + }); + + var conn = _jsPlumb.connect({source:d1, target:d2}), + e1 = conn.endpoints[0], e2 = conn.endpoints[1]; + + equals(e1.anchor.x, 0, "top leftanchor"); + equals(e2.anchor.y, 0, "top left anchor"); + equals(e2.anchor.x, 1, "top right anchor"); + equals(e2.anchor.y, 0, "top right anchor"); + + }); + + test(renderMode + " restoreDefaults", function() { + _jsPlumb.importDefaults({ + Anchors:["TopLeft", "TopRight"] + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), conn = _jsPlumb.connect({source:d1, target:d2}), + e1 = conn.endpoints[0], e2 = conn.endpoints[1]; + + equals(e1.anchor.x, 0, "top leftanchor"); + equals(e2.anchor.y, 0, "top left anchor"); + equals(e2.anchor.x, 1, "top right anchor"); + equals(e2.anchor.y, 0, "top right anchor"); + + _jsPlumb.restoreDefaults(); + + var conn2 = _jsPlumb.connect({source:d1, target:d2}), + e3 = conn2.endpoints[0], e4 = conn2.endpoints[1]; + + equals(e3.anchor.x, 0.5, "bottom center anchor"); + equals(e3.anchor.y, 1, "bottom center anchor"); + equals(e4.anchor.x, 0.5, "bottom center anchor"); + equals(e4.anchor.y, 1, "bottom center anchor"); + }); + +// setId function + + test(renderMode + " setId, taking two strings, only default scope", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId("d1", "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a selector and a string, only default scope", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1"), "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a DOM element and a string, only default scope", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1")[0], "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking two strings, mix of scopes", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2, scope:"FOO"}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId("d1", "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a selector and a string, mix of scopes", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2, scope:"FOO"}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1"), "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a DOM element and a string, mix of scopes", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2, scope:"FOO"}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1")[0], "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setIdChanged, ", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + $("#d1").attr("id", "d3"); + + _jsPlumb.setIdChanged("d1", "d3"); + + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " endpoint hide/show should hide/show overlays", function() { + _addDiv("d1"); + var e1 = _jsPlumb.addEndpoint("d1", { + overlays:[ + [ "Label", { id:"label", label:"foo" } ] + ] + }), + o = e1.getOverlay("label"); + + ok(o.isVisible(), "overlay is initially visible"); + _jsPlumb.hide("d1", true); + ok(!o.isVisible(), "overlay is no longer visible"); + }); + + test(renderMode + " connection hide/show should hide/show overlays", function() { + _addDiv("d1");_addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", + overlays:[ + [ "Label", { id:"label", label:"foo" } ] + ] + }), + o = c.getOverlay("label"); + + ok(o.isVisible(), "overlay is initially visible"); + _jsPlumb.hide("d1", true); + ok(!o.isVisible(), "overlay is no longer visible"); + }); + + test(renderMode + " select, basic test", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2"}), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 1, "one connection selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; dont filter on scope.", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 2, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scope", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + s = _jsPlumb.select({source:"d1", scope:"FOO"}); + + equals(s.length, 1, "one connection selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scopes", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + c3 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAZ"}) + s = _jsPlumb.select({source:"d1", scope:["FOO","BAR"]}); + + equals(s.length, 2, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; scope but no scope filter; single source id", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + c3 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAZ"}), + c4 = _jsPlumb.connect({source:"d2", target:"d1", scope:"BOZ"}), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 3, "three connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scopes; single source id", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + c3 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAZ"}), + c4 = _jsPlumb.connect({source:"d2", target:"d1", scope:"BOZ"}), + s = _jsPlumb.select({source:"d1", scope:["FOO","BAR", "BOZ"]}); + + equals(s.length, 2, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scope; dont supply sourceid", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + s = _jsPlumb.select({ scope:"FOO" }); + + equals(s.length, 1, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scope; dont supply sourceid", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d2", target:"d1", scope:"BAR"}), + s = _jsPlumb.select({ scope:"FOO" }); + + equals(s.length, 1, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, two connections, with overlays", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({ + source:"d1", + target:"d2", + overlays:[ + ["Label", {id:"l"}] + ] + }), + c2 = _jsPlumb.connect({ + source:"d1", + target:"d2", + overlays:[ + ["Label", {id:"l"}] + ] + }), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 2, "two connections selected"); + ok(s.get(0).getOverlay("l") != null, "connection has overlay"); + ok(s.get(1).getOverlay("l") != null, "connection has overlay"); + }); + + test(renderMode + " select, chaining with setHover and hideOverlay", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({ + source:"d1", + target:"d2", + overlays:[ + ["Label", {id:"l"}] + ] + }); + s = _jsPlumb.select({source:"d1"}); + + s.setHover(false).hideOverlay("l"); + + ok(!(s.get(0).isHover()), "connection is not hover"); + ok(!(s.get(0).getOverlay("l").isVisible()), "overlay is not visible"); + }); + + test(renderMode + " select, .each function", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10) + }); + } + + var s = _jsPlumb.select(); + equals(s.length, 5, "there are five connections"); + + var t = ""; + s.each(function(connection) { + t += "f"; + }); + equals("fffff", t, ".each is working"); + }); + + test(renderMode + " select, multiple connections + chaining", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + overlays:[ + ["Arrow", {location:0.3}], + ["Arrow", {location:0.7}] + ] + }); + } + + var s = _jsPlumb.select().removeAllOverlays().setParameter("foo", "bar").setHover(false).setLabel("baz"); + equals(s.length, 5, "there are five connections"); + + for (var j = 0; j < 5; j++) { + equals(s.get(j).getOverlays().length, 1, "one overlay: the label"); + equals(s.get(j).getParameter("foo"), "bar", "parameter foo has value 'bar'"); + ok(!(s.get(j).isHover()), "hover is set to false"); + equals(s.get(j).getLabel(), "baz", "label is set to 'baz'"); + } + }); + + test(renderMode + " select, simple getter", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + label:"FOO" + }); + } + + var lbls = _jsPlumb.select().getLabel(); + equals(lbls.length, 5, "there are five labels"); + + for (var j = 0; j < 5; j++) { + equals(lbls[j][0], "FOO", "label has value 'FOO'"); + } + }); + + test(renderMode + " select, getter + chaining", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + label:"FOO" + }); + } + + var params = _jsPlumb.select().removeAllOverlays().setParameter("foo", "bar").getParameter("foo"); + equals(params.length, 5, "there are five params"); + + for (var j = 0; j < 5; j++) { + equals(params[j][0], "bar", "parameter has value 'bar'"); + } + }); + + + test(renderMode + " select, detach method", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + label:"FOO" + }); + } + + var params = _jsPlumb.select().detach(); + + equals(jsPlumb.select().length, 0, "there are no connections"); + }); + +// ******************* getEndpoints ************************************************ + + test(renderMode + " getEndpoints", function() { + _addDiv("d1");_addDiv("d2"); + + _jsPlumb.addEndpoint("d1"); + _jsPlumb.addEndpoint("d2"); + _jsPlumb.addEndpoint("d1"); + + var e = _jsPlumb.getEndpoints("d1"), + e2 = _jsPlumb.getEndpoints("d2"); + equals(e.length, 2, "two endpoints on d1"); + equals(e2.length, 1, "one endpoint on d2"); + }); + + /** + * leave this test at the bottom! + */ + test(renderMode + ': unload test', function() { + _jsPlumb.unload(); + var contextNode = $(".__jsPlumb_context"); + ok(contextNode.length == 0, 'context node unloaded'); + }); +}; + diff --git a/archive/1.3.8/jsPlumb-connectors-statemachine-1.3.8-RC1.js b/archive/1.3.8/jsPlumb-connectors-statemachine-1.3.8-RC1.js new file mode 100644 index 000000000..4c5eff90b --- /dev/null +++ b/archive/1.3.8/jsPlumb-connectors-statemachine-1.3.8-RC1.js @@ -0,0 +1,442 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.8 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the state machine connectors. + * + * Thanks to Brainstorm Mobile Solutions for supporting the development of these. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + var Line = function(x1, y1, x2, y2) { + + this.m = (y2 - y1) / (x2 - x1); + this.b = -1 * ((this.m * x1) - y1); + + this.rectIntersect = function(x,y,w,h) { + var results = []; + + // try top face + // the equation of the top face is y = (0 * x) + b; y = b. + var xInt = (y - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try right face + var yInt = (this.m * (x + w)) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + // bottom face + var xInt = ((y + h) - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try left face + var yInt = (this.m * x) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + if (results.length == 2) { + var midx = (results[0][0] + results[1][0]) / 2, midy = (results[0][1] + results[1][1]) / 2; + results.push([ midx,midy ]); + // now calculate the segment inside the rectangle where the midpoint lies. + var xseg = midx <= x + (w / 2) ? -1 : 1, + yseg = midy <= y + (h / 2) ? -1 : 1; + results.push([xseg, yseg]); + return results; + } + + return null; + + }; + }, + _segment = function(x1, y1, x2, y2) { + if (x1 <= x2 && y2 <= y1) return 1; + else if (x1 <= x2 && y1 <= y2) return 2; + else if (x2 <= x1 && y2 >= y1) return 3; + return 4; + }, + + // the control point we will use depends on the faces to which each end of the connection is assigned, specifically whether or not the + // two faces are parallel or perpendicular. if they are parallel then the control point lies on the midpoint of the axis in which they + // are parellel and varies only in the other axis; this variation is proportional to the distance that the anchor points lie from the + // center of that face. if the two faces are perpendicular then the control point is at some distance from both the midpoints; the amount and + // direction are dependent on the orientation of the two elements. 'seg', passed in to this method, tells you which segment the target element + // lies in with respect to the source: 1 is top right, 2 is bottom right, 3 is bottom left, 4 is top left. + // + // sourcePos and targetPos are arrays of info about where on the source and target each anchor is located. their contents are: + // + // 0 - absolute x + // 1 - absolute y + // 2 - proportional x in element (0 is left edge, 1 is right edge) + // 3 - proportional y in element (0 is top edge, 1 is bottom edge) + // + _findControlPoint = function(midx, midy, segment, sourceEdge, targetEdge, dx, dy, distance, proximityLimit) { + + // TODO (maybe) + // - if anchor pos is 0.5, make the control point take into account the relative position of the elements. + if (distance <= proximityLimit) return [midx, midy]; + + if (segment == 1) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 2) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 3) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 4) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + }; + + /* + Function: StateMachine constructor + + Allowed parameters: + curviness - measure of how "curvy" the connectors will be. this is translated as the distance that the + Bezier curve's control point is from the midpoint of the straight line connecting the two + endpoints, and does not mean that the connector is this wide. The Bezier curve never reaches + its control points; they act as gravitational masses. defaults to 10. + margin - distance from element to start and end connectors, in pixels. defaults to 5. + proximityLimit - sets the distance beneath which the elements are consider too close together to bother with fancy + curves. by default this is 80 pixels. + loopbackRadius - the radius of a loopback connector. optional; defaults to 25. + */ + jsPlumb.Connectors.StateMachine = function(params) { + var self = this, + currentPoints = null, + _sx, _sy, _tx, _ty, _controlPoint = [], + curviness = params.curviness || 10, + margin = params.margin || 5, + proximityLimit = params.proximityLimit || 80, + clockwise = params.orientation && params.orientation == "clockwise", + loopbackRadius = params.loopbackRadius || 25, + isLoopback = false; + + this.type = "StateMachine"; + params = params || {}; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *= 1.9; + //ensure at least one pixel width + lineWidth = lineWidth || 1; + var x = Math.min(sourcePos[0], targetPos[0]) - xo, + y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (sourceEndpoint.elementId != targetEndpoint.elementId) { + + isLoopback = false; + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + + // now adjust for the margin + if (sourcePos[2] == 0) _sx -= margin; + if (sourcePos[2] == 1) _sx += margin; + if (sourcePos[3] == 0) _sy -= margin; + if (sourcePos[3] == 1) _sy += margin; + if (targetPos[2] == 0) _tx -= margin; + if (targetPos[2] == 1) _tx += margin; + if (targetPos[3] == 0) _ty -= margin; + if (targetPos[3] == 1) _ty += margin; + + // + // these connectors are quadratic bezier curves, having a single control point. if both anchors + // are located at 0.5 on their respective faces, the control point is set to the midpoint and you + // get a straight line. this is also the case if the two anchors are within 'proximityLimit', since + // it seems to make good aesthetic sense to do that. outside of that, the control point is positioned + // at 'curviness' pixels away along the normal to the straight line connecting the two anchors. + // + // there may be two improvements to this. firstly, we might actually support the notion of avoiding nodes + // in the UI, or at least making a good effort at doing so. if a connection would pass underneath some node, + // for example, we might increase the distance the control point is away from the midpoint in a bid to + // steer it around that node. this will work within limits, but i think those limits would also be the likely + // limits for, once again, aesthetic good sense in the layout of a chart using these connectors. + // + // the second possible change is actually two possible changes: firstly, it is possible we should gradually + // decrease the 'curviness' as the distance between the anchors decreases; start tailing it off to 0 at some + // point (which should be configurable). secondly, we might slightly increase the 'curviness' for connectors + // with respect to how far their anchor is from the center of its respective face. this could either look cool, + // or stupid, and may indeed work only in a way that is so subtle as to have been a waste of time. + // + + var _midx = (_sx + _tx) / 2, _midy = (_sy + _ty) / 2, + m2 = (-1 * _midx) / _midy, theta2 = Math.atan(m2), + dy = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.sin(theta2)), + dx = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.cos(theta2)), + segment = _segment(_sx, _sy, _tx, _ty), + distance = Math.sqrt(Math.pow(_tx - _sx, 2) + Math.pow(_ty - _sy, 2)); + + // calculate the control point. this code will be where we'll put in a rudimentary element avoidance scheme; it + // will work by extending the control point to force the curve to be, um, curvier. + _controlPoint = _findControlPoint(_midx, + _midy, + segment, + sourcePos, + targetPos, + curviness, curviness, + distance, + proximityLimit); + + + var requiredWidth = Math.max(Math.abs(_controlPoint[0] - _sx) * 3, Math.abs(_controlPoint[0] - _tx) * 3, Math.abs(_tx-_sx), 2 * lineWidth, minWidth), + requiredHeight = Math.max(Math.abs(_controlPoint[1] - _sy) * 3, Math.abs(_controlPoint[1] - _ty) * 3, Math.abs(_ty-_sy), 2 * lineWidth, minWidth); + + if (w < requiredWidth) { + var dw = requiredWidth - w; + x -= (dw / 2); + _sx += (dw / 2); + _tx += (dw / 2); + w = requiredWidth; + _controlPoint[0] += (dw / 2); + } + + if (h < requiredHeight) { + var dh = requiredHeight - h; + y -= (dh / 2); + _sy += (dh / 2); + _ty += (dh / 2); + h = requiredHeight; + _controlPoint[1] += (dh / 2); + } + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty, _controlPoint[0], _controlPoint[1] ]; + } + else { + isLoopback = true; + // a loopback connector. draw an arc from one anchor to the other. + // i guess we'll do this the same way as the others. just the control point will be a fair distance away. + var x1 = sourcePos[0], x2 = sourcePos[0], y1 = sourcePos[1] - margin, y2 = sourcePos[1] - margin, + cx = x1, cy = y1 - loopbackRadius; + + // canvas sizing stuff, to ensure the whole painted area is visible. + w = ((2 * lineWidth) + (4 * loopbackRadius)), h = ((2 * lineWidth) + (4 * loopbackRadius)); + x = cx - loopbackRadius - lineWidth - loopbackRadius, y = cy - loopbackRadius - lineWidth - loopbackRadius; + currentPoints = [ x, y, w, h, cx-x, cy-y, loopbackRadius, clockwise, x1-x, y1-y, x2-x, y2-y]; + } + + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_tx, y:_ty }, + { x:_controlPoint[0], y:_controlPoint[1] }, + { x:_controlPoint[0] + 1, y:_controlPoint[1] + 1}, + { x:_sx, y:_sy } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + if (isLoopback) { + + if (location > 0 && location < 1) location = 1- location; + +// current points are [ x, y, width, height, center x, center y, radius, clockwise, startx, starty, endx, endy ] + // so the path length is the circumference of the circle + //var len = 2 * Math.PI * currentPoints[6], + // map 'location' to an angle. 0 is PI/2 when the connector is on the top face; if we + // support other faces it will have to be calculated for each one. 1 is also PI/2. + // 0.5 is -PI/2. + var startAngle = (location * 2 * Math.PI) + (Math.PI / 2), + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + + } + else return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + if (isLoopback) + return Math.atan(location * 2 * Math.PI); + else + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + if (isLoopback) { + + if (location > 0 && location < 1) location = 1- location; + + var circumference = 2 * Math.PI * currentPoints[6], + arcSpan = distance / circumference * 2 * Math.PI, + startAngle = (location * 2 * Math.PI) - arcSpan + (Math.PI / 2), + + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + } + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + }; + + /* + * Canvas state machine renderer. + */ + jsPlumb.Connectors.canvas.StateMachine = function(params) { + params = params || {}; + var self = this, drawGuideline = params.drawGuideline || true, avoidSelector = params.avoidSelector; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.CanvasConnector.apply(this, arguments); + + + this._paint = function(dimensions) { + + if (dimensions.length == 10) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.quadraticCurveTo(dimensions[8], dimensions[9], dimensions[6], dimensions[7]); + self.ctx.stroke(); + + /*/ draw the guideline + if (drawGuideline) { + self.ctx.save(); + self.ctx.beginPath(); + self.ctx.strokeStyle = "silver"; + self.ctx.lineWidth = 1; + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + self.ctx.restore(); + } + //*/ + } + else { + // a loopback connector + self.ctx.save(); + self.ctx.beginPath(); + var startAngle = 0, // Starting point on circle + endAngle = 2 * Math.PI, // End point on circle + clockwise = dimensions[7]; // clockwise or anticlockwise + self.ctx.arc(dimensions[4],dimensions[5],dimensions[6],0, endAngle, clockwise); + self.ctx.stroke(); + self.ctx.closePath(); + self.ctx.restore(); + } + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + /* + * SVG State Machine renderer + */ + jsPlumb.Connectors.svg.StateMachine = function() { + var self = this; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.SvgConnector.apply(this, arguments); + this.getPath = function(d) { + + if (d.length == 10) + return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[8] + " " + d[9] + " " + d[6] + " " + d[7]; + else { + // loopback + return "M" + (d[8] + 4) + " " + d[9] + " A " + d[6] + " " + d[6] + " 0 1,0 " + (d[8]-4) + " " + d[9]; + } + }; + }; + + /* + * VML state machine renderer + */ + jsPlumb.Connectors.vml.StateMachine = function() { + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.VmlConnector.apply(this, arguments); + var _conv = jsPlumb.vml.convertValue; + this.getPath = function(d) { + if (d.length == 10) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + } + else { + // loopback + var left = _conv(d[8] - d[6]), + top = _conv(d[9] - (2 * d[6])), + right = left + _conv(2 * d[6]), + bottom = top + _conv(2 * d[6]), + posString = left + "," + top + "," + right + "," + bottom; + + var o = "ar " + posString + "," + _conv(d[8]) + "," + + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + " e"; + + return o; + } + }; + }; + +})(); + +/* + // now for a rudimentary avoidance scheme. TODO: how to set this in a cross-library way? + // if (avoidSelector) { + // var testLine = new Line(sourcePos[0] + _sx,sourcePos[1] + _sy,sourcePos[0] + _tx,sourcePos[1] + _ty); + // var sel = jsPlumb.getSelector(avoidSelector); + // for (var i = 0; i < sel.length; i++) { + // var id = jsPlumb.getId(sel[i]); + // if (id != sourceEndpoint.elementId && id != targetEndpoint.elementId) { + // o = jsPlumb.getOffset(id), s = jsPlumb.getSize(id); +// +// if (o && s) { +// var collision = testLine.rectIntersect(o.left,o.top,s[0],s[1]); +// if (collision) { + // set the control point to be a certain distance from the midpoint of the two points that + // the line crosses on the rectangle. + // TODO where will this 75 number come from? + // _controlX = collision[2][0] + (75 * collision[3][0]); + // / _controlY = collision[2][1] + (75 * collision[3][1]); +// } +// } + // } + // } + //} + */ \ No newline at end of file diff --git a/archive/1.3.8/jsPlumb-defaults-1.3.8-RC1.js b/archive/1.3.8/jsPlumb-defaults-1.3.8-RC1.js new file mode 100644 index 000000000..6717166f3 --- /dev/null +++ b/archive/1.3.8/jsPlumb-defaults-1.3.8-RC1.js @@ -0,0 +1,1132 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.8 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the default Connectors, Endpoint and Overlay definitions. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +(function() { + + /** + * + * Helper class to consume unused mouse events by components that are DOM elements and + * are used by all of the different rendering modes. + * + */ + jsPlumb.DOMElementComponent = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + // when render mode is canvas, these functions may be called by the canvas mouse handler. + // this component is safe to pipe this stuff to /dev/null. + this.mousemove = + this.dblclick = + this.click = + this.mousedown = + this.mouseup = function(e) { }; + }; + + /** + * Class: Connectors.Straight + * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters. + */ + jsPlumb.Connectors.Straight = function() { + this.type = "Straight"; + var self = this, + currentPoints = null, + _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty, _segment, _length; + + /** + * Computes the new size and position of the canvas. + */ + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + _dx = _tx - _sx, _dy = _ty - _sy; + //_m = _dy / _dx, _m2 = -1 / _m; + _m = jsPlumb.util.gradient({x:_sx, y:_sy}, {x:_tx, y:_ty}), _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + //_segment = jsPlumb.util.segment({x:_sx, y:_sy}, {x:_tx, y:_ty}); + _length = Math.sqrt((_dx * _dx) + (_dy * _dy)); + + return currentPoints; + }; + + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + if (location == 0) + return { x:_sx, y:_sy }; + else if (location == 1) + return { x:_tx, y:_ty }; + else + return jsPlumb.util.pointOnLine({x:_sx, y:_sy}, {x:_tx, y:_ty}, location * _length); + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { + return _m; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + * this hands off to jsPlumb.util to do the maths, supplying two points and the distance. + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location), + farAwayPoint = location == 1 ? { + x:_sx + ((_tx - _sx) * 10), + y:_sy + ((_sy - _ty) * 10) + } : {x:_tx, y:_ty }; + + return jsPlumb.util.pointOnLine(p, farAwayPoint, distance); + }; + }; + + + /** + * Class:Connectors.Bezier + * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's + * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below. + */ + /** + * Function:Constructor + * + * Parameters: + * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line. + * Optional; defaults to 150. + * stub - optional value for a distance to travel from the connector's endpoint before beginning the Bezier curve. defaults to 0. + * + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + params = params || {}; + this.majorAnchor = params.curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + this.type = "Bezier"; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(sourceEndpoint), + too = targetAnchor.getOrientation(targetEndpoint), + perpendicular = soo[0] != too[0] || soo[1] == too[1], + p = [], + ma = self.majorAnchor, mi = self.minorAnchor; + + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h, _sStubX, _sStubY, _tStubX, _tStubY; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, sourceEndpoint, targetEndpoint, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx), minx2 = Math.min(_CP[0], _CP2[0]), minx = Math.min(minx1,minx2), + maxx1 = Math.max(_sx,_tx), maxx2 = Math.max(_CP[0], _CP2[0]), maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty), miny2 = Math.min(_CP[1], _CP2[1]), miny = Math.min(miny1,miny2), + maxy1 = Math.max(_sy,_ty), maxy2 = Math.max(_CP[1], _CP2[1]), maxy = Math.max(maxy1,maxy2); + + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, + _sx, _sy, _tx, _ty, + _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + }; + + + /** + * Class: Connectors.Flowchart + * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments. + */ + /** + * Function: Constructor + * + * Parameters: + * stub - minimum length for the stub at each end of the connector. defaults to 30 pixels. + */ + jsPlumb.Connectors.Flowchart = function(params) { + this.type = "Flowchart"; + params = params || {}; + var self = this, + minStubLength = params.stub || params.minStubLength /* bwds compat. */ || 30, + segments = [], + totalLength = 0, + segmentProportions = [], + segmentProportionalLengths = [], + points = [], + swapX, swapY, + maxX = 0, maxY = 0, + /** + * recalculates the points at which the segments begin and end, proportional to the total length travelled + * by all the segments that constitute the connector. we use this to help with pointOnPath calculations. + */ + updateSegmentProportions = function(startX, startY, endX, endY) { + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segments[i][5] / totalLength; + segmentProportions[i] = [curLoc, (curLoc += (segments[i][5] / totalLength)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0], + ly = segments.length == 0 ? sy : segments[segments.length - 1][1], + m = x == lx ? Infinity : 0, + l = Math.abs(x == lx ? y - ly : x - lx); + segments.push([x, y, lx, ly, m, l]); + totalLength += l; + + maxX = Math.max(maxX, x); + maxY = Math.max(maxY, y); + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment + * that contains the point which is 'location' distance along the entire path, where + * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths + * are made up of a list of segments, each of which contributes some fraction to + * the total length. + */ + findSegmentForLocation = function(location) { + var idx = segmentProportions.length - 1, inSegmentProportion = 1; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, + sourceAnchor, targetAnchor, lineWidth, minWidth, sourceInfo, targetInfo) { + + segments = []; + segmentProportions = []; + totalLength = 0; + segmentProportionalLengths = []; + maxX = maxY = 0; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + offx = (lw / 2) + (minStubLength * 2), + offy = (lw / 2) + (minStubLength * 2), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(sourceEndpoint), + to = targetAnchor.orientation || targetAnchor.getOrientation(targetEndpoint), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx, + h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy; + + // if either anchor does not have an orientation set, we derive one from their relative + // positions. we fix the axis to be the one in which the two elements are further apart, and + // point each anchor at the other element. this is also used when dragging a new connection. + if (so[0] == 0 && so[1] == 0 || to[0] == 0 && to[1] == 0) { + var index = w > h ? 0 : 1, oIndex = [1,0][index]; + so = []; to = []; + so[index] = sourcePos[index] > targetPos[index] ? -1 : 1; + to[index] = sourcePos[index] > targetPos[index] ? 1 : -1; + so[oIndex] = 0; + to[oIndex] = 0; + } + + if (w < minWidth) { + offx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + offy += (minWidth - h) / 2; + h = minWidth; + } + + var sx = swapX ? w-offx : offx, + sy = swapY ? h-offy : offy, + tx = swapX ? offx : w-offx , + ty = swapY ? offy : h-offy, + startStubX = sx + (so[0] * minStubLength), + startStubY = sy + (so[1] * minStubLength), + endStubX = tx + (to[0] * minStubLength), + endStubY = ty + (to[1] * minStubLength), + isXGreaterThanStubTimes2 = Math.abs(sx - tx) > 2 * minStubLength, + isYGreaterThanStubTimes2 = Math.abs(sy - ty) > 2 * minStubLength, + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2), + oProduct = ((so[0] * to[0]) + (so[1] * to[1])), + opposite = oProduct == -1, + perpendicular = oProduct == 0, + orthogonal = oProduct == 1; + + x -= offx; y -= offy; + points = [x, y, w, h, sx, sy, tx, ty]; + var extraPoints = []; + + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + var sourceAxis = so[0] == 0 ? "y" : "x", + anchorOrientation = opposite ? "opposite" : orthogonal ? "orthogonal" : "perpendicular", + segment = jsPlumb.util.segment([sx, sy], [tx, ty]), + flipSourceSegments = so[sourceAxis == "x" ? 0 : 1] == -1, + flipSegments = { + "x":[null, 4, 3, 2, 1], + "y":[null, 2, 1, 4, 3] + } + + if (flipSourceSegments) + segment = flipSegments[sourceAxis][segment]; + + var findClearedLine = function(start, mult, anchorPos, dimension) { + return start + (mult * (( 1 - anchorPos) * dimension) + minStubLength); + //mx = so[0] == 0 ? startStubX + ((1 - sourceAnchor.x) * sourceInfo.width) + minStubLength : startStubX, + }, + + lineCalculators = { + oppositex : function() { + if (sourceEndpoint.elementId == targetEndpoint.elementId) { + var _y = startStubY + ((1 - sourceAnchor.y) * sourceInfo.height) + minStubLength; + return [ [ startStubX, _y ], [ endStubX, _y ]]; + } + else if (isXGreaterThanStubTimes2 && (segment == 1 || segment == 2)) { + return [[ midx, sy ], [ midx, ty ]]; + } + else { + return [[ startStubX, midy ], [endStubX, midy ]]; + } + }, + orthogonalx : function() { + if (segment == 1 || segment == 2) { + return [ [ endStubX, startStubY ]]; + } + else { + return [ [ startStubX, endStubY ]]; + } + }, + perpendicularx : function() { + var my = (ty + sy) / 2; + if ((segment == 1 && to[1] == 1) || (segment == 2 && to[1] == -1)) { + if (Math.abs(tx - sx) > minStubLength) + return [ [endStubX, startStubY ]]; + else + return [ [startStubX, startStubY ], [ startStubX, my ], [ endStubX, my ]]; + } + else if ((segment == 3 && to[1] == -1) || (segment == 4 && to[1] == 1)) { + return [ [ startStubX, my ], [ endStubX, my ]]; + } + else if ((segment == 3 && to[1] == 1) || (segment == 4 && to[1] == -1)) { + return [ [ startStubX, endStubY ]]; + } + else if ((segment == 1 && to[1] == -1) || (segment == 2 && to[1] == 1)) { + if (Math.abs(tx - sx) > minStubLength) + return [ [ midx, startStubY ], [ midx, endStubY ]]; + else + return [ [ startStubX, endStubY ]]; + } + }, + oppositey : function() { + if (sourceEndpoint.elementId == targetEndpoint.elementId) { + var _x = startStubX + ((1 - sourceAnchor.x) * sourceInfo.width) + minStubLength; + return [ [ _x, startStubY ], [ _x, endStubY ]]; + } + else if (isYGreaterThanStubTimes2 && (segment == 2 || segment == 3)) { + return [[ sx, midy ], [ tx, midy ]]; + } + else { + return [[ midx, startStubY ], [midx, endStubY ]]; + } + }, + orthogonaly : function() { + if (segment == 2 || segment == 3) { + return [ [ startStubX, endStubY ]]; + } + else { + return [ [ endStubX, startStubY ]]; + } + }, + perpendiculary : function() { + var mx = (tx + sx) / 2; + if ((segment == 2 && to[0] == -1) || (segment == 3 && to[0] == 1)) { + if (Math.abs(tx - sx) > minStubLength) + return [ [startStubX, endStubY ]]; + else + return [ [startStubX, midy ], [ endStubX, midy ]]; + } + else if ((segment == 1 && to[0] == -1) || (segment == 4 && to[0] == 1)) { + var mx = (tx + sx) / 2; + return [ [ mx, startStubY ], [ mx, endStubY ]]; + } + else if ((segment == 1 && to[0] == 1) || (segment == 4 && to[0] == -1)) { + return [ [ endStubX, startStubY ]]; + } + else if ((segment == 2 && to[0] == 1) || (segment == 3 && to[0] == -1)) { + if (Math.abs(ty - sy) > minStubLength) + return [ [ startStubX, midy ], [ endStubX, midy ]]; + else + return [ [ endStubX, startStubY ]]; + } + } + }; + + var p = lineCalculators[anchorOrientation + sourceAxis](); + if (p) { + for (var i = 0; i < p.length; i++) { + addSegment(p[i][0], p[i][1], sx, sy, tx, ty); + } + } + + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentProportions(sx, sy, tx, ty); + + // adjust the max values of the canvas if we have a value that is larger than what we previously set. + // + if (maxY > points[3]) points[3] = maxY + (lineWidth * 2); + if (maxX > points[2]) points[2] = maxX + (lineWidth * 2); + + return points; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location) { + return self.pointAlongPathFrom(location, 0); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location) { + return segments[findSegmentForLocation(location)["index"]][4]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance) { + var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segments[s.index][5], m = segments[s.index][4]; + var e = { + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + return e; + }; + }; + + // ********************************* END OF CONNECTOR TYPES ******************************************************************* + + // ********************************* ENDPOINT TYPES ******************************************************************* + + /** + * Class: Endpoints.Dot + * A round endpoint, with default radius 10 pixels. + */ + + /** + * Function: Constructor + * + * Parameters: + * + * radius - radius of the endpoint. defaults to 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + this.type = "Dot"; + var self = this; + params = params || {}; + this.radius = params.radius || 10; + this.defaultOffset = 0.5 * this.radius; + this.defaultInnerRadius = this.radius / 3; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var r = endpointStyle.radius || self.radius, + x = anchorPoint[0] - r, + y = anchorPoint[1] - r; + return [ x, y, r * 2, r * 2, r ]; + }; + }; + + /** + * Class: Endpoints.Rectangle + * A Rectangular Endpoint, with default size 20x20. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the endpoint. defaults to 20 pixels. + * height - height of the endpoint. defaults to 20 pixels. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + this.type = "Rectangle"; + var self = this; + params = params || {}; + this.width = params.width || 20; + this.height = params.height || 20; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height]; + }; + }; + + + var DOMElementEndpoint = function(params) { + jsPlumb.DOMElementComponent.apply(this, arguments); + var self = this; + + var displayElements = [ ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + }; + /** + * Class: Endpoints.Image + * Draws an image as the Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * src - location of the image to use. + */ + jsPlumb.Endpoints.Image = function(params) { + + this.type = "Image"; + DOMElementEndpoint.apply(this, arguments); + + var self = this, + initialized = false, + widthToUse = params.width, + heightToUse = params.height, + _onload = null, + _endpoint = params.endpoint; + + this.img = new Image(); + self.ready = false; + + this.img.onload = function() { + self.ready = true; + widthToUse = widthToUse || self.img.width; + heightToUse = heightToUse || self.img.height; + if (_onload) { + _onload(self); + } + }; + + /* + Function: setImage + Sets the Image to use in this Endpoint. + + Parameters: + img - may be a URL or an Image object + onload - optional; a callback to execute once the image has loaded. + */ + _endpoint.setImage = function(img, onload) { + var s = img.constructor == String ? img : img.src; + _onload = onload; + self.img.src = img; + + if (self.canvas != null) + self.canvas.setAttribute("src", img); + }; + + _endpoint.setImage(params.src || params.url, params.onload); + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + self.anchorPoint = anchorPoint; + if (self.ready) return [anchorPoint[0] - widthToUse / 2, anchorPoint[1] - heightToUse / 2, + widthToUse, heightToUse]; + else return [0,0,0,0]; + }; + + self.canvas = document.createElement("img"), initialized = false; + self.canvas.style["margin"] = 0; + self.canvas.style["padding"] = 0; + self.canvas.style["outline"] = 0; + self.canvas.style["position"] = "absolute"; + var clazz = params.cssClass ? " " + params.cssClass : ""; + self.canvas.className = jsPlumb.endpointClass + clazz; + if (widthToUse) self.canvas.setAttribute("width", widthToUse); + if (heightToUse) self.canvas.setAttribute("height", heightToUse); + jsPlumb.appendElement(self.canvas, params.parent); + self.attachListeners(self.canvas, self); + + var actuallyPaint = function(d, style, anchor) { + if (!initialized) { + self.canvas.setAttribute("src", self.img.src); + self.appendDisplayElement(self.canvas); + initialized = true; + } + var x = self.anchorPoint[0] - (widthToUse / 2), + y = self.anchorPoint[1] - (heightToUse / 2); + jsPlumb.sizeCanvas(self.canvas, x, y, widthToUse, heightToUse); + }; + + this.paint = function(d, style, anchor) { + if (self.ready) { + actuallyPaint(d, style, anchor); + } + else { + window.setTimeout(function() { + self.paint(d, style, anchor); + }, 200); + } + }; + }; + + /** + * Class: Endpoints.Blank + * An Endpoint that paints nothing (visible) on the screen. Supports cssClass and hoverClass parameters like all Endpoints. + */ + jsPlumb.Endpoints.Blank = function(params) { + var self = this; + this.type = "Blank"; + DOMElementEndpoint.apply(this, arguments); + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + return [anchorPoint[0], anchorPoint[1],10,0]; + }; + + self.canvas = document.createElement("div"); + self.canvas.style.display = "block"; + self.canvas.style.width = "1px"; + self.canvas.style.height = "1px"; + self.canvas.style.background = "transparent"; + self.canvas.style.position = "absolute"; + self.canvas.className = self._jsPlumb.endpointClass; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + }; + }; + + /** + * Class: Endpoints.Triangle + * A triangular Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the triangle's base. defaults to 55 pixels. + * height - height of the triangle from base to apex. defaults to 55 pixels. + */ + jsPlumb.Endpoints.Triangle = function(params) { + this.type = "Triangle"; + params = params || { }; + params.width = params.width || 55; + params.height = params.height || 55; + this.width = params.width; + this.height = params.height; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height ]; + }; + }; +// ********************************* END OF ENDPOINT TYPES ******************************************************************* + + +// ********************************* OVERLAY DEFINITIONS *********************************************************************** + + var AbstractOverlay = function(params) { + var visible = true, self = this; + this.isAppendedAtTopLevel = true; + this.component = params.component; + this.loc = params.location == null ? 0.5 : params.location; + this.endpointLoc = params.endpointLocation == null ? [ 0.5, 0.5] : params.endpointLocation; + this.setVisible = function(val) { + visible = val; + self.component.repaint(); + }; + this.isVisible = function() { return visible; }; + this.hide = function() { self.setVisible(false); }; + this.show = function() { self.setVisible(true); }; + + this.incrementLocation = function(amount) { + self.loc += amount; + self.component.repaint(); + }; + this.setLocation = function(l) { + self.loc = l; + self.component.repaint(); + }; + this.getLocation = function() { + return self.loc; + }; + }; + + + /** + * Class: Overlays.Arrow + * + * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length + * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction + * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line + * across the tail. + */ + /** + * Function: Constructor + * + * Parameters: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + this.type = "Arrow"; + AbstractOverlay.apply(this, arguments); + this.isAppendedAtTopLevel = false; + params = params || {}; + var self = this; + + this.length = params.length || 20; + this.width = params.width || 20; + this.id = params.id; + var direction = (params.direction || 1) < 0 ? -1 : 1, + paintStyle = params.paintStyle || { lineWidth:1 }, + // how far along the arrow the lines folding back in come to. default is 62.3%. + foldback = params.foldback || 0.623; + + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.cleanup = function() { }; // nothing to clean up for Arrows + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var hxy, mid, txy, tail, cxy; + if (connector.pointAlongPathFrom) { + + if (self.loc == 1) { + hxy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, -1); + txy = jsPlumb.util.pointOnLine(hxy, mid, self.length); + } + else if (self.loc == 0) { + txy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, 1); + hxy = jsPlumb.util.pointOnLine(txy, mid, self.length); + } + else { + hxy = connector.pointAlongPathFrom(self.loc, direction * self.length / 2), + mid = connector.pointOnPath(self.loc), + txy = jsPlumb.util.pointOnLine(hxy, mid, self.length); + } + + tail = jsPlumb.util.perpendicularLineTo(hxy, txy, self.width); + cxy = jsPlumb.util.pointOnLine(hxy, txy, foldback * self.length); + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x), + maxx = Math.max(hxy.x, tail[0].x, tail[1].x), + miny = Math.min(hxy.y, tail[0].y, tail[1].y), + maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + var d = { hxy:hxy, tail:tail, cxy:cxy }, + strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle, + fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle, + lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth; + + self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions); + + return [ minx, maxx, miny, maxy]; + } + else return [0,0,0,0]; + }; + }; + + /** + * Class: Overlays.PlainArrow + * + * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "PlainArrow"; + }; + + /** + * Class: Overlays.Diamond + * + * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40, + p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "Diamond"; + }; + + + + /** + * Class: Overlays.Label + * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb + * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter + * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it + * puts on the Label's 'style' attribute, so the end result is the same. + */ + /** + * Function: Constructor + * + * Parameters: + * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes + * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle. + * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * + */ + jsPlumb.Overlays.Label = function(params) { + this.type = "Label"; + jsPlumb.DOMElementComponent.apply(this, arguments); + AbstractOverlay.apply(this, arguments); + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.id = params.id; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var label = params.label || "", + self = this, + initialised = false, + div = document.createElement("div"), + labelText = null; + div.style["position"] = "absolute"; + + var clazz = params["_jsPlumb"].overlayClass + " " + + (self.labelStyle.cssClass ? self.labelStyle.cssClass : + params.cssClass ? params.cssClass : ""); + + div.className = clazz; + + jsPlumb.appendElement(div, params.component.parent); + jsPlumb.getId(div); + self.attachListeners(div, self); + self.canvas = div; + + //override setVisible + var osv = self.setVisible; + self.setVisible = function(state) { + osv(state); // call superclass + div.style.display = state ? "block" : "none"; + }; + + this.getElement = function() { + return div; + }; + + this.cleanup = function() { + if (div != null) jsPlumb.CurrentLibrary.removeElement(div); + }; + + /* + * Function: setLabel + * sets the label's, um, label. you would think i'd call this function + * 'setText', but you can pass either a Function or a String to this, so + * it makes more sense as 'setLabel'. + */ + this.setLabel = function(l) { + label = l; + labelText = null; + self.component.repaint(); + }; + + this.getLabel = function() { + return label; + }; + + this.paint = function(component, d, componentDimensions) { + if (!initialised) { + component.appendDisplayElement(div); + self.attachListeners(div, component); + initialised = true; + } + div.style.left = (componentDimensions[0] + d.minx) + "px"; + div.style.top = (componentDimensions[1] + d.miny) + "px"; + }; + + this.getTextDimensions = function() { + if (typeof label == "function") { + var lt = label(self); + div.innerHTML = lt.replace(/\r\n/g, "
"); + } + else { + if (labelText == null) { + labelText = label; + div.innerHTML = labelText.replace(/\r\n/g, "
"); + } + } + var de = jsPlumb.CurrentLibrary.getElementObject(div), + s = jsPlumb.CurrentLibrary.getSize(de); + return {width:s[0], height:s[1]}; + }; + + this.computeMaxSize = function(connector) { + var td = self.getTextDimensions(connector); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + + this.draw = function(component, currentConnectionPaintStyle, componentDimensions) { + var td = self.getTextDimensions(component); + if (td.width != null) { + var cxy = {x:0,y:0}; + if (component.pointOnPath) + cxy = component.pointOnPath(self.loc); // a connection + else { + var locToUse = self.loc.constructor == Array ? self.loc : self.endpointLoc; + cxy = { x:locToUse[0] * componentDimensions[2], + y:locToUse[1] * componentDimensions[3] }; + } + + minx = cxy.x - (td.width / 2), + miny = cxy.y - (td.height / 2); + + self.paint(component, { + minx:minx, + miny:miny, + td:td, + cxy:cxy + }, componentDimensions); + + return [minx, minx+td.width, miny, miny+td.height]; + } + else return [0,0,0,0]; + }; + + this.reattachListeners = function(connector) { + if (div) { + self.reattachListenersForElement(div, self, connector); + } + }; + }; + + // this is really just a test overlay, so its undocumented and doesnt take any parameters. but i was loth to delete it. + jsPlumb.Overlays.GuideLines = function() { + var self = this; + self.length = 50; + self.lineWidth = 5; + this.type = "GuideLines"; + AbstractOverlay.apply(this, arguments); + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var head = connector.pointAlongPathFrom(self.loc, self.length / 2), + mid = connector.pointOnPath(self.loc), + tail = jsPlumb.util.pointOnLine(head, mid, self.length), + tailLine = jsPlumb.util.perpendicularLineTo(head, tail, 40), + headLine = jsPlumb.util.perpendicularLineTo(tail, head, 20); + + self.paint(connector, [head, tail, tailLine, headLine], self.lineWidth, "red", null, connectorDimensions); + + return [Math.min(head.x, tail.x), Math.min(head.y, tail.y), Math.max(head.x, tail.x), Math.max(head.y,tail.y)]; + }; + + this.computeMaxSize = function() { return 50; }; + + this.cleanup = function() { }; // nothing to clean up for GuideLines + }; + + // ********************************* END OF OVERLAY DEFINITIONS *********************************************************************** + + // ********************************* OVERLAY CANVAS RENDERERS*********************************************************************** + + // ********************************* END OF OVERLAY CANVAS RENDERERS *********************************************************************** +})(); \ No newline at end of file diff --git a/archive/1.3.8/jsPlumb-renderers-canvas-1.3.8-RC1.js b/archive/1.3.8/jsPlumb-renderers-canvas-1.3.8-RC1.js new file mode 100644 index 000000000..1c014c013 --- /dev/null +++ b/archive/1.3.8/jsPlumb-renderers-canvas-1.3.8-RC1.js @@ -0,0 +1,456 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.8 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the HTML5 canvas renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + +// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS ******************************************************************* + + // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. + var _connectionBeingDragged = null, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, + _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }; + + /* + * Class:CanvasMouseAdapter + * Provides support for mouse events on canvases. + */ + var CanvasMouseAdapter = function() { + var self = this; + self.overlayPlacements = []; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + jsPlumb.EventGenerator.apply(this, arguments); + /** + * returns whether or not the given event is ojver a painted area of the canvas. + */ + this._over = function(e) { + var o = _getOffset(_getElementObject(self.canvas)), + pageXY = _pageXY(e), + x = pageXY[0] - o.left, y = pageXY[1] - o.top; + if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + return false; + }; + + var _mouseover = false, _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false, + _nullSafeHasClass = function(el, clazz) { + return el != null && _hasClass(el, clazz); + }; + this.mousemove = function(e) { + var pageXY = _pageXY(e), clientXY = _clientXY(e), + ee = document.elementFromPoint(clientXY[0], clientXY[1]), + eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); + var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); + if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.fire("mouseenter", self, e); + return true; + } + // TODO here there is a remote chance that the overlay the mouse moved onto + // is actually not an overlay for the current component. a more thorough check would + // be to ensure the overlay belonged to the current component. + else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { + _mouseover = false; + self.fire("mouseexit", self, e); + } + self.fire("mousemove", self, e); + }; + + this.click = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); + self.fire("mousedown", self, e); + } + }; + + this.mouseup = function(e) { + _mouseDown = false; + self.fire("mouseup", self, e); + }; + + this.contextmenu = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("contextmenu", self, e); + _mouseWasDown = false; + }; + }; + + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + params["_jsPlumb"].appendElement(canvas, params.parent); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + params["_jsPlumb"].getId(canvas, params.uuid); + if (params.tooltip) canvas.setAttribute("title", params.tooltip); + + return canvas; + }; + + var CanvasComponent = function(params) { + CanvasMouseAdapter.apply(this, arguments); + + var displayElements = [ ]; + this.getDisplayElements = function() { return displayElements; }; + this.appendDisplayElement = function(el) { displayElements.push(el); }; + } + + /** + * Class:CanvasConnector + * Superclass for Canvas Connector renderers. + */ + var CanvasConnector = jsPlumb.CanvasConnector = function(params) { + + CanvasComponent.apply(this, arguments); + + var _paintOneStyle = function(dim, aStyle) { + self.ctx.save(); + jsPlumb.extend(self.ctx, aStyle); + if (aStyle.gradient) { + var g = self.createGradient(dim, self.ctx); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + self.ctx.strokeStyle = g; + } + self._paint(dim); + self.ctx.restore(); + }; + + var self = this, + clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:params.tooltip + }); + self.ctx = self.canvas.getContext("2d"); + + self.appendDisplayElement(self.canvas); + + self.paint = function(dim, style) { + if (style != null) { + jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + _paintOneStyle(dim, outlineStyle); + } + _paintOneStyle(dim, style); + } + }; + }; + + /** + * Class:CanvasEndpoint + * Superclass for Canvas Endpoint renderers. + */ + var CanvasEndpoint = function(params) { + var self = this; + CanvasComponent.apply(this, arguments); + var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""), + canvasParams = { + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:self.tooltip + }; + self.canvas = _newCanvas(canvasParams); + self.ctx = self.canvas.getContext("2d"); + + self.appendDisplayElement(self.canvas); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + } + + self._paint.apply(this, arguments); + }; + }; + + jsPlumb.Endpoints.canvas.Dot = function(params) { + jsPlumb.Endpoints.Dot.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + var self = this, + parseValue = function(value) { + try { return parseInt(value); } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }, + calculateAdjustments = function(gradient) { + var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius; + gradient.offset && (offsetAdjustment = parseValue(gradient.offset)); + gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius)); + return [offsetAdjustment, innerRadius]; + }; + this._paint = function(d, style, anchor) { + if (style != null) { + var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + if (style.gradient) { + var adjustments = calculateAdjustments(style.gradient), + yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0], + xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0], + g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + ctx.beginPath(); + ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + } + }; + }; + + jsPlumb.Endpoints.canvas.Rectangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) { + + var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + + /* canvas gradient */ + if (style.gradient) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, d[2], d[3]); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + jsPlumb.Endpoints.canvas.Triangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Triangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) + { + var width = d[2], height = d[3], x = d[0], y = d[1], + ctx = self.canvas.getContext('2d'), + offsetX = 0, offsetY = 0, angle = 0, + orientation = anchor.getOrientation(self); + + if( orientation[0] == 1 ) { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = style.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + /* + * Canvas Image Endpoint: uses the default version, which creates an tag. + */ + jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image; + + /* + * Blank endpoint in all renderers is just the default Blank endpoint. + */ + jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank; + + /* + * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Bezier = function() { + var self = this; + jsPlumb.Connectors.Bezier.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO i doubt this handles the case that source and target are swapped. + this.createGradient = function(dim, ctx, swap) { + return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + /* + * Canvas straight line Connector. Draws a straight line onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Straight = function() { + var self = this; + jsPlumb.Connectors.Straight.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO this does not handle the case that src and target are swapped. + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + jsPlumb.Connectors.canvas.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + +// ********************************* END OF CANVAS RENDERERS ******************************************************************* + + jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label; + + /** + * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. + */ + var CanvasOverlay = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }; + + var AbstractCanvasArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + CanvasOverlay.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + var ctx = connector.ctx; + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(d.hxy.x, d.hxy.y); + ctx.lineTo(d.tail[0].x, d.tail[0].y); + ctx.lineTo(d.cxy.x, d.cxy.y); + ctx.lineTo(d.tail[1].x, d.tail[1].y); + ctx.lineTo(d.hxy.x, d.hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + }; + + jsPlumb.Overlays.canvas.Arrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.canvas.PlainArrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.canvas.Diamond = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.8/jsPlumb-renderers-svg-1.3.8-RC1.js b/archive/1.3.8/jsPlumb-renderers-svg-1.3.8-RC1.js new file mode 100644 index 000000000..5421d45ba --- /dev/null +++ b/archive/1.3.8/jsPlumb-renderers-svg-1.3.8-RC1.js @@ -0,0 +1,539 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.8 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the SVG renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +/** + * SVG support for jsPlumb. + * + * things to investigate: + * + * gradients: https://developer.mozilla.org/en/svg_in_html_introduction + * css:http://tutorials.jenkov.com/svg/svg-and-css.html + * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath + * pointer events: https://developer.mozilla.org/en/css/pointer-events + * + * IE9 hover jquery: http://forum.jquery.com/topic/1-6-2-broke-svg-hover-events + * + */ +;(function() { + + var svgAttributeMap = { + "joinstyle":"stroke-linejoin", + "stroke-linejoin":"stroke-linejoin", + "stroke-dashoffset":"stroke-dashoffset", + "stroke-linecap":"stroke-linecap" + }, + STROKE_DASHARRAY = "stroke-dasharray", + DASHSTYLE = "dashstyle", + LINEAR_GRADIENT = "linearGradient", + RADIAL_GRADIENT = "radialGradient", + FILL = "fill", + STOP = "stop", + STROKE = "stroke", + STROKE_WIDTH = "stroke-width", + STYLE = "style", + NONE = "none", + JSPLUMB_GRADIENT = "jsplumb_gradient_", + LINE_WIDTH = "lineWidth", + ns = { + svg:"http://www.w3.org/2000/svg", + xhtml:"http://www.w3.org/1999/xhtml" + }, + _attr = function(node, attributes) { + for (var i in attributes) + node.setAttribute(i, "" + attributes[i]); + }, + _node = function(name, attributes) { + var n = document.createElementNS(ns.svg, name); + attributes = attributes || {}; + attributes["version"] = "1.1"; + attributes["xmlns"] = ns.xhtml; + _attr(n, attributes); + return n; + }, + _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; }, + _clearGradient = function(parent) { + for (var i = 0; i < parent.childNodes.length; i++) { + if (parent.childNodes[i].tagName == LINEAR_GRADIENT || parent.childNodes[i].tagName == RADIAL_GRADIENT) + parent.removeChild(parent.childNodes[i]); + } + }, + _updateGradient = function(parent, node, style, dimensions, uiComponent) { + var id = JSPLUMB_GRADIENT + uiComponent._jsPlumb.idstamp(); + // first clear out any existing gradient + _clearGradient(parent); + // this checks for an 'offset' property in the gradient, and in the absence of it, assumes + // we want a linear gradient. if it's there, we create a radial gradient. + // it is possible that a more explicit means of defining the gradient type would be + // better. relying on 'offset' means that we can never have a radial gradient that uses + // some default offset, for instance. + if (!style.gradient.offset) { + var g = _node(LINEAR_GRADIENT, {id:id}); + parent.appendChild(g); + } + else { + var g = _node(RADIAL_GRADIENT, { + id:id + }); + parent.appendChild(g); + } + + // the svg radial gradient seems to treat stops in the reverse + // order to how canvas does it. so we want to keep all the maths the same, but + // iterate the actual style declarations in reverse order, if the x indexes are not in order. + for (var i = 0; i < style.gradient.stops.length; i++) { + // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of + // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments. + // so, not too concerned about leaving this in for now. + var styleToUse = i; + if (dimensions.length == 8) + styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i; + else + styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i; + var stopColor = jsPlumb.util.convertStyle(style.gradient.stops[styleToUse][1], true); + var s = _node(STOP, {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor}); + g.appendChild(s); + } + var applyGradientTo = style.strokeStyle ? STROKE : FILL; + node.setAttribute(STYLE, applyGradientTo + ":url(#" + id + ")"); + }, + _applyStyles = function(parent, node, style, dimensions, uiComponent) { + + if (style.gradient) { + _updateGradient(parent, node, style, dimensions, uiComponent); + } + else { + // make sure we clear any existing gradient + _clearGradient(parent); + node.setAttribute(STYLE, ""); + } + + node.setAttribute(FILL, style.fillStyle ? jsPlumb.util.convertStyle(style.fillStyle, true) : NONE); + node.setAttribute(STROKE, style.strokeStyle ? jsPlumb.util.convertStyle(style.strokeStyle, true) : NONE); + if (style.lineWidth) { + node.setAttribute(STROKE_WIDTH, style.lineWidth); + } + + // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like + // the syntax in VML but is actually kind of nasty: values are given in the pixel + // coordinate space, whereas in VML they are multiples of the width of the stroked + // line, which makes a lot more sense. for that reason, jsPlumb is supporting both + // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from + // VML, which will be the preferred method. the code below this converts a dashstyle + // attribute given in terms of stroke width into a pixel representation, by using the + // stroke's lineWidth. + if (style[DASHSTYLE] && style[LINE_WIDTH] && !style[STROKE_DASHARRAY]) { + var sep = style[DASHSTYLE].indexOf(",") == -1 ? " " : ",", + parts = style[DASHSTYLE].split(sep), + styleToUse = ""; + parts.forEach(function(p) { + styleToUse += (Math.floor(p * style.lineWidth) + sep); + }); + node.setAttribute(STROKE_DASHARRAY, styleToUse); + } + else if(style[STROKE_DASHARRAY]) { + node.setAttribute(STROKE_DASHARRAY, style[STROKE_DASHARRAY]); + } + + // extra attributes such as join type, dash offset. + for (var i in svgAttributeMap) { + if (style[i]) { + node.setAttribute(svgAttributeMap[i], style[i]); + } + } + }, + _decodeFont = function(f) { + var r = /([0-9].)(p[xt])\s(.*)/; + var bits = f.match(r); + return {size:bits[1] + bits[2], font:bits[3]}; + }, + _classManip = function(el, add, clazz) { + var classesToAddOrRemove = clazz.split(" "), + className = el.className, + curClasses = className.baseVal.split(" "); + + for (var i = 0; i < classesToAddOrRemove.length; i++) { + if (add) { + if (curClasses.indexOf(classesToAddOrRemove[i]) == -1) + curClasses.push(classesToAddOrRemove[i]); + } + else { + var idx = curClasses.indexOf(classesToAddOrRemove[i]); + if (idx != -1) + curClasses.splice(idx, 1); + } + } + + el.className.baseVal = curClasses.join(" "); + }, + _addClass = function(el, clazz) { + _classManip(el, true, clazz); + }, + _removeClass = function(el, clazz) { + _classManip(el, false, clazz); + }; + + /** + utility methods for other objects to use. + */ + jsPlumb.util.svg = { + addClass:_addClass, + removeClass:_removeClass + }; + + /* + * Base class for SVG components. + */ + //var SvgComponent = function(cssClass, originalArgs, pointerEventsSpec) { + var SvgComponent = function(params) { + var self = this, + pointerEventsSpec = params.pointerEventsSpec || "all"; + jsPlumb.jsPlumbUIComponent.apply(this, params.originalArgs); + self.canvas = null, self.path = null, self.svg = null; + + var clazz = params.cssClass + " " + (params.originalArgs[0].cssClass || ""), + svgParams = { + "style":"", + "width":0, + "height":0, + "pointer-events":pointerEventsSpec, + "position":"absolute" + }; + if (self.tooltip) svgParams["title"] = self.tooltip; + self.svg = _node("svg", svgParams); + if (params.useDivWrapper) { + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.sizeCanvas(self.canvas,0,0,1,1); + self.canvas.className = clazz; + if (self.tooltip) self.canvas.setAttribute("title", self.tooltip); + } + else { + _attr(self.svg, { "class":clazz }); + self.canvas = self.svg; + } + + params._jsPlumb.appendElement(self.canvas, params.originalArgs[0]["parent"]); + if (params.useDivWrapper) self.canvas.appendChild(self.svg); + + // TODO this displayElement stuff is common between all components, across all + // renderers. would be best moved to jsPlumbUIComponent. + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + this.paint = function(d, style, anchor) { + if (style != null) { + var x = d[0], y = d[1]; + if (params.useDivWrapper) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + x = 0, y = 0; + } + _attr(self.svg, { + "style":_pos([x, y, d[2], d[3]]), + "width": d[2], + "height": d[3] + }); + self._paint.apply(this, arguments); + } + }; + }; + + /* + * Base class for SVG connectors. + */ + var SvgConnector = jsPlumb.SvgConnector = function(params) { + var self = this; + SvgComponent.apply(this, [ { + cssClass:params["_jsPlumb"].connectorClass, + originalArgs:arguments, + pointerEventsSpec:"none", + tooltip:params.tooltip, + _jsPlumb:params["_jsPlumb"] + } ]); + this._paint = function(d, style) { + var p = self.getPath(d), a = { "d":p }, outlineStyle = null; + a["pointer-events"] = "all"; + + // outline style. actually means drawing an svg object underneath the main one. + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = jsPlumb.CurrentLibrary.extend({}, style); + outlineStyle.strokeStyle = jsPlumb.util.convertStyle(style.outlineColor); + outlineStyle.lineWidth = outlineStrokeWidth; + + if (self.bgPath == null) { + self.bgPath = _node("path", a); + self.svg.appendChild(self.bgPath); + self.attachListeners(self.bgPath, self); + } + else { + _attr(self.bgPath, a); + } + + _applyStyles(self.svg, self.bgPath, outlineStyle, d, self); + } + + + // test - see below + // a["clip-path"]= "url(#testClip)"; + + if (self.path == null) { + self.path = _node("path", a); + self.svg.appendChild(self.path); + self.attachListeners(self.path, self); + + /* + this is a test of a clip path. i'm looking into using one of these to animate a jsplumb connection. + you could do this by walking along the line, stepping along a little at a time, and setting the clip + path to extend as far as that point. + + self.clip = _node("clipPath", {id:"testClip", clipPathUnits:"objectBoundingBox"}); + self.svg.appendChild(self.clip); + self.clip.appendChild(_node("rect", { + x:"0",y:"0",width:"0.5",height:"1" + })); + */ + } + else { + _attr(self.path, a); + } + + _applyStyles(self.svg, self.path, style, d, self); + }; + + this.reattachListeners = function() { + if (self.bgPath) self.reattachListenersForElement(self.bgPath, self); + if (self.path) self.reattachListenersForElement(self.path, self); + }; + + }; + + /* + * SVG Bezier Connector + */ + jsPlumb.Connectors.svg.Bezier = function(params) { + jsPlumb.Connectors.Bezier.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { + var _p = "M " + d[4] + " " + d[5]; + _p += (" C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]); + return _p; + }; + }; + + /* + * SVG straight line Connector + */ + jsPlumb.Connectors.svg.Straight = function(params) { + jsPlumb.Connectors.Straight.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; }; + }; + + jsPlumb.Connectors.svg.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "M " + dimensions[4] + "," + dimensions[5]; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)]; + } + // finally draw a line to the end + p = p + " " + dimensions[6] + "," + dimensions[7]; + return p; + }; + }; + + /* + * Base class for SVG endpoints. + */ + var SvgEndpoint = function(params) { + var self = this; + SvgComponent.apply(this, [ { + cssClass:params["_jsPlumb"].endpointClass, + originalArgs:arguments, + pointerEventsSpec:"all", + useDivWrapper:true, + _jsPlumb:params["_jsPlumb"] + } ]); + this._paint = function(d, style) { + var s = jsPlumb.extend({}, style); + if (s.outlineColor) { + s.strokeWidth = s.outlineWidth; + s.strokeStyle = jsPlumb.util.convertStyle(s.outlineColor, true); + } + + if (self.node == null) { + self.node = self.makeNode(d, s); + self.svg.appendChild(self.node); + self.attachListeners(self.node, self); + } + _applyStyles(self.svg, self.node, s, d, self); + _pos(self.node, d); + }; + + this.reattachListeners = function() { + if (self.node) self.reattachListenersForElement(self.node, self); + }; + }; + + /* + * SVG Dot Endpoint + */ + jsPlumb.Endpoints.svg.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("circle", { + "cx" : d[2] / 2, + "cy" : d[3] / 2, + "r" : d[2] / 2 + }); + }; + }; + + /* + * SVG Rectangle Endpoint + */ + jsPlumb.Endpoints.svg.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("rect", { + "width":d[2], + "height":d[3] + }); + }; + }; + + /* + * SVG Image Endpoint is the default image endpoint. + */ + jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image; + /* + * Blank endpoint in svg renderer is the default Blank endpoint. + */ + jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank; + /* + * Label endpoint in svg renderer is the default Label endpoint. + */ + jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label; + + var AbstractSvgArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + this.isAppendedAtTopLevel = false; + var self = this, path =null; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + } + var clazz = originalArgs && (originalArgs.length == 1) ? (originalArgs[0].cssClass || "") : ""; + + _attr(path, { + "d" : makePath(d), + "class" : clazz, + stroke : strokeStyle ? strokeStyle : null, + fill : fillStyle ? fillStyle : null + }); + }; + var makePath = function(d) { + return "M" + d.hxy.x + "," + d.hxy.y + + " L" + d.tail[0].x + "," + d.tail[0].y + + " L" + d.cxy.x + "," + d.cxy.y + + " L" + d.tail[1].x + "," + d.tail[1].y + + " L" + d.hxy.x + "," + d.hxy.y; + }; + this.reattachListeners = function() { + if (path) self.reattachListenersForElement(path, self); + }; + }; + + jsPlumb.Overlays.svg.Arrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.svg.PlainArrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.svg.Diamond = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; + + // a test + jsPlumb.Overlays.svg.GuideLines = function() { + var path = null, self = this, path2 = null, p1_1, p1_2; + jsPlumb.Overlays.GuideLines.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + + p1_1 = _node("path"); + connector.svg.appendChild(p1_1); + self.attachListeners(p1_1, connector); + self.attachListeners(p1_1, self); + + p1_2 = _node("path"); + connector.svg.appendChild(p1_2); + self.attachListeners(p1_2, connector); + self.attachListeners(p1_2, self); + + } + + _attr(path, { + "d" : makePath(d[0], d[1]), + stroke : "red", + fill : null + }); + + _attr(p1_1, { + "d" : makePath(d[2][0], d[2][1]), + stroke : "blue", + fill : null + }); + + _attr(p1_2, { + "d" : makePath(d[3][0], d[3][1]), + stroke : "green", + fill : null + }); + }; + + var makePath = function(d1, d2) { + return "M " + d1.x + "," + d1.y + + " L" + d2.x + "," + d2.y; + }; + + }; +})(); \ No newline at end of file diff --git a/archive/1.3.8/jsPlumb-renderers-vml-1.3.8-RC1.js b/archive/1.3.8/jsPlumb-renderers-vml-1.3.8-RC1.js new file mode 100644 index 000000000..a035dcc32 --- /dev/null +++ b/archive/1.3.8/jsPlumb-renderers-vml-1.3.8-RC1.js @@ -0,0 +1,439 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.8 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the VML renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + // http://ajaxian.com/archives/the-vml-changes-in-ie-8 + // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/ + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + + var vmlAttributeMap = { + "stroke-linejoin":"joinstyle", + "joinstyle":"joinstyle", + "endcap":"endcap", + "miterlimit":"miterlimit" + }, + jsPlumbStylesheet = null; + + if (document.createStyleSheet) { + + var ruleClasses = [ + ".jsplumb_vml", "jsplumb\\:textbox", "jsplumb\\:oval", "jsplumb\\:rect", + "jsplumb\\:stroke", "jsplumb\\:shape", "jsplumb\\:group" + ], + rule = "behavior:url(#default#VML);position:absolute;"; + + jsPlumbStylesheet = document.createStyleSheet(); + + for (var i = 0; i < ruleClasses.length; i++) + jsPlumbStylesheet.addRule(ruleClasses[i], rule); + + // in this page it is also mentioned that IE requires the extra arg to the namespace + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either. + // var iev = document.documentMode; + //if (!iev || iev < 8) + document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); + //else + // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML"); + } + + jsPlumb.vml = {}; + + var scale = 1000, + + _groupMap = {}, + _getGroup = function(container, connectorClass) { + var id = jsPlumb.getId(container), + g = _groupMap[id]; + if(!g) { + g = _node("group", [0,0,scale, scale], {"class":connectorClass}); + //g.style.position=absolute; + //g["coordsize"] = "1000,1000"; + g.style.backgroundColor="red"; + _groupMap[id] = g; + jsPlumb.appendElement(g, container); // todo if this gets reinstated, remember to use the current jsplumb instance. + //document.body.appendChild(g); + } + return g; + }, + _atts = function(o, atts) { + for (var i in atts) { + // IE8 fix: setattribute does not work after an element has been added to the dom! + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + //o.setAttribute(i, atts[i]); + + /*There is an additional problem when accessing VML elements by using get/setAttribute. The simple solution is following: + + if (document.documentMode==8) { + ele.opacity=1; + } else { + ele.setAttribute(‘opacity’,1); + } + */ + + o[i] = atts[i]; + } + }, + _node = function(name, d, atts, parent, _jsPlumb) { + atts = atts || {}; + var o = document.createElement("jsplumb:" + name); + _jsPlumb.appendElement(o, parent); + o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml"; + _pos(o, d); + _atts(o, atts); + return o; + }, + _pos = function(o,d) { + o.style.left = d[0] + "px"; + o.style.top = d[1] + "px"; + o.style.width= d[2] + "px"; + o.style.height= d[3] + "px"; + o.style.position = "absolute"; + }, + _conv = jsPlumb.vml.convertValue = function(v) { + return Math.floor(v * scale); + }, + // tests if the given style is "transparent" and then sets the appropriate opacity node to 0 if so, + // or 1 if not. TODO in the future, support variable opacity. + _maybeSetOpacity = function(styleToWrite, styleToCheck, type, component) { + if ("transparent" === styleToCheck) + component.setOpacity(type, "0.0"); + else + component.setOpacity(type, "1.0"); + }, + _applyStyles = function(node, style, component, _jsPlumb) { + var styleToWrite = {}; + if (style.strokeStyle) { + styleToWrite["stroked"] = "true"; + var strokeColor = jsPlumb.util.convertStyle(style.strokeStyle, true); + styleToWrite["strokecolor"] = strokeColor; + _maybeSetOpacity(styleToWrite, strokeColor, "stroke", component); + styleToWrite["strokeweight"] = style.lineWidth + "px"; + } + else styleToWrite["stroked"] = "false"; + + if (style.fillStyle) { + styleToWrite["filled"] = "true"; + var fillColor = jsPlumb.util.convertStyle(style.fillStyle, true); + styleToWrite["fillcolor"] = fillColor; + _maybeSetOpacity(styleToWrite, fillColor, "fill", component); + } + else styleToWrite["filled"] = "false"; + + if(style["dashstyle"]) { + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }, node, _jsPlumb); + } + else + component.strokeNode.dashstyle = style["dashstyle"]; + } + else if (style["stroke-dasharray"] && style["lineWidth"]) { + var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",", + parts = style["stroke-dasharray"].split(sep), + styleToUse = ""; + for(var i = 0; i < parts.length; i++) { + styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep); + } + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }, node, _jsPlumb); + //node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = styleToUse; + } + + _atts(node, styleToWrite); + }, + /* + * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. + */ + VmlComponent = function() { + var self = this; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.opacityNodes = { + "stroke":null, + "fill":null + }; + this.initOpacityNodes = function(vml) { + self.opacityNodes["stroke"] = _node("stroke", [0,0,1,1], {opacity:"0.0"}, vml, self._jsPlumb); + self.opacityNodes["fill"] = _node("fill", [0,0,1,1], {opacity:"0.0"}, vml, self._jsPlumb); + }; + this.setOpacity = function(type, value) { + var node = self.opacityNodes[type]; + if (node) node["opacity"] = "" + value; + }; + var displayElements = [ ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el, doNotAppendToCanvas) { + if (!doNotAppendToCanvas) self.canvas.parentNode.appendChild(el); + displayElements.push(el); + }; + }, + /* + * Base class for Vml connectors. extends VmlComponent. + */ + VmlConnector = jsPlumb.VmlConnector = function(params) { + var self = this; + self.strokeNode = null; + self.canvas = null; + VmlComponent.apply(this, arguments); + var clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : ""); + this.paint = function(d, style, anchor) { + if (style != null) { + var path = self.getPath(d), p = { "path":path }; + + //* + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle : jsPlumb.util.convertStyle(style.outlineColor), + lineWidth : outlineStrokeWidth + }; + for (var aa in vmlAttributeMap) outlineStyle[aa] = style[aa]; + + if (self.bgCanvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.bgCanvas = _node("shape", d, p, params.parent, self._jsPlumb); + _pos(self.bgCanvas, d); + self.appendDisplayElement(self.bgCanvas, true); + self.attachListeners(self.bgCanvas, self); + self.initOpacityNodes(self.bgCanvas, ["stroke"]); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.bgCanvas, d); + _atts(self.bgCanvas, p); + } + + _applyStyles(self.bgCanvas, outlineStyle, self); + } + //*/ + + if (self.canvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + if (self.tooltip) p["label"] = self.tooltip; + self.canvas = _node("shape", d, p, params.parent, self._jsPlumb); + //var group = _getGroup(params.parent); // test of append everything to a group + //group.appendChild(self.canvas); // sort of works but not exactly; + //params["_jsPlumb"].appendElement(self.canvas, params.parent); //before introduction of groups + + self.appendDisplayElement(self.canvas, true); + self.attachListeners(self.canvas, self); + self.initOpacityNodes(self.canvas, ["stroke"]); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.canvas, d); + _atts(self.canvas, p); + } + + _applyStyles(self.canvas, style, self, self._jsPlumb); + } + }; + + //self.appendDisplayElement(self.canvas); + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + }, + /* + * + * Base class for Vml Endpoints. extends VmlComponent. + * + */ + VmlEndpoint = function(params) { + VmlComponent.apply(this, arguments); + var vml = null, self = this, opacityStrokeNode = null, opacityFillNode = null; + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + + var clazz = self._jsPlumb.endpointClass + (params.cssClass ? (" " + params.cssClass) : ""); + + //var group = _getGroup(params.parent); + //group.appendChild(self.canvas); + params["_jsPlumb"].appendElement(self.canvas, params.parent); + + if (self.tooltip) self.canvas.setAttribute("label", self.tooltip); + + this.paint = function(d, style, anchor) { + var p = { }; + + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (vml == null) { + p["class"] = clazz; + vml = self.getVml([0,0, d[2], d[3]], p, anchor, self.canvas, self._jsPlumb); + self.attachListeners(vml, self); + + self.appendDisplayElement(vml, true); + self.appendDisplayElement(self.canvas, true); + + self.initOpacityNodes(vml, ["fill"]); + } + else { + _pos(vml, [0,0, d[2], d[3]]); + _atts(vml, p); + } + + _applyStyles(vml, style, self); + }; + + this.reattachListeners = function() { + if (vml) self.reattachListenersForElement(vml, self); + }; + }; + + jsPlumb.Connectors.vml.Bezier = function() { + jsPlumb.Connectors.Bezier.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Straight = function() { + jsPlumb.Connectors.Straight.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Flowchart = function() { + jsPlumb.Connectors.Flowchart.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l"; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]); + } + // finally draw a line to the end + p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e"; + return p; + }; + }; + + jsPlumb.Endpoints.vml.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor, parent, _jsPlumb) { return _node("oval", d, atts, parent, _jsPlumb); }; + }; + + jsPlumb.Endpoints.vml.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor, parent, _jsPlumb) { return _node("rect", d, atts, parent, _jsPlumb); }; + }; + + /* + * VML Image Endpoint is the same as the default image endpoint. + */ + jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image; + + /** + * placeholder for Blank endpoint in vml renderer. + */ + jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank; + + /** + * VML Label renderer. uses the default label renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label; + + var AbstractVmlArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + VmlComponent.apply(this, arguments); + var self = this, path = null; + self.canvas = null; + var getPath = function(d, connectorDimensions) { + return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) + + " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + + " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + + " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + + " x e"; + }; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) { + var p = {}; + if (strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] = jsPlumb.util.convertStyle(strokeStyle, true); + } + if (lineWidth) p["strokeweight"] = lineWidth + "px"; + if (fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = fillStyle; + } + var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + w = Math.abs(xmax - xmin), + h = Math.abs(ymax - ymin), + dim = [xmin, ymin, w, h]; + + // for VML, we create overlays using shapes that have the same dimensions and + // coordsize as their connector - overlays calculate themselves relative to the + // connector (it's how it's been done since the original canvas implementation, because + // for canvas that makes sense). + p["path"] = getPath(d, connectorDimensions); + p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale); + + dim[0] = connectorDimensions[0]; + dim[1] = connectorDimensions[1]; + dim[2] = connectorDimensions[2]; + dim[3] = connectorDimensions[3]; + + if (self.canvas == null) { + //p["class"] = jsPlumb.overlayClass; // TODO currentInstance? + self.canvas = _node("shape", dim, p, connector.canvas.parentNode, connector._jsPlumb); + connector.appendDisplayElement(self.canvas, true); + self.attachListeners(self.canvas, connector); + } + else { + _pos(self.canvas, dim); + _atts(self.canvas, p); + } + }; + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + }; + + jsPlumb.Overlays.vml.Arrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.vml.PlainArrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.vml.Diamond = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.8/mootools.jsPlumb-1.3.8-RC1.js b/archive/1.3.8/mootools.jsPlumb-1.3.8-RC1.js new file mode 100644 index 000000000..b1c2c09bd --- /dev/null +++ b/archive/1.3.8/mootools.jsPlumb-1.3.8-RC1.js @@ -0,0 +1,459 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.8 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the MooTools adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function(now) { + this.parent(now); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event, originalEvent) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr, originalEvent); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, _scope, value) { + var l = list[_scope]; + if (!l) { + l = []; + list[_scope] = l; + } + l.push(value); + }; + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + var _getElementObject = function(el) { + return $(el); + }, + + _removeNonPermanentDroppables = function(drag) { + /* + // remove non-permanent droppables from all arrays + var dbs = _droppables[drag.scope], d = []; + if (dbs) { + var d = []; + for (var i=0; i < dbs.length; i++) { + var isPermanent = dbs[i].getAttribute("_isPermanentDroppable"); + if (isPermanent === "true") d.push(dbs[i]); + } + dbs.splice(0, dbs.length); + _droppables[drag.scope] = d; + } + d = []; + // clear out transient droppables from the drag itself + for(var i = 0; i < drag.droppables.length; i++) { + var isPermanent = drag.droppables[i].getAttribute("_isPermanentDroppable"); + if (isPermanent === "true") d.push(drag.droppables[i]); + } + drag.droppables.splice(0, drag.droppables.length); // release old ones + drag.droppables = d; + //*/ + }; + + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el) + try { + if (el.className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.addClass(el, clazz); + } + else el.addClass(clazz); + } + catch (e) { + // SVGAnimatedString not supported; no problem. + el.addClass(clazz); + } + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + m.start(properties); + }, + + appendElement : function(child, parent) { + _getElementObject(parent).grab(child); + }, + + bind : function(el, event, callback) { + el = _getElementObject(el); + el.addEvent(event, callback); + }, + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.event.clientX, eventObject.event.clientY]; + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el), + drags = _draggablesById[id]; + return drags[0].scope; + }, + + getDropEvent : function(args) { + return args[2]; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getDOMElement : function(el) { + // MooTools just decorates the DOM elements. so we have either an ID or an Element here. + return typeof(el) == "String" ? document.getElementById(el) : el; + }, + + getElementObject : _getElementObject, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + getPageXY : function(eventObject) { + return [eventObject.event.pageX, eventObject.event.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).getParent(); + }, + + getScrollLeft : function(el) { + return null; + }, + + getScrollTop : function(el) { + return null; + }, + + getSelector : function(spec) { + return $$(spec); + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e != null ? e.tagName : null; + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[0], + p = jsPlumb.CurrentLibrary.getElementObject(ui).getPosition(); + return { left:p.x, top:p.y }; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options, isPlumbedComponent) { + var id = jsPlumb.getId(el); + var drag = _draggablesById[id]; + if (!drag) { + var originalZIndex = 0, + originalCursor = null, + dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + + options['onStart'] = jsPlumb.wrap(options['onStart'], function() { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + drag.originalZIndex = originalZIndex; + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + + _removeNonPermanentDroppables(drag); + }); + + // DROPPABLES - only relevant if this is a plumbed component, ie. not just the result of the user making some DOM element + // draggable. this is the only library adapter that has to care about this parameter. + var scope = "" + (options["scope"] || jsPlumb.Defaults.Scope), + filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }, + droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + + if (isPlumbedComponent) { + + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr, event) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop', event); + } + }; + } + else + options["droppables"] = []; + + drag = new Drag.Move(el, options); + drag.scope = scope; + drag.originalZIndex = originalZIndex; + _add(_draggablesById, el.get("id"), drag); + // again, only keep a record of this for scope stuff if this is a plumbed component (an endpoint) + if (isPlumbedComponent) { + _add(_draggablesByScope, scope, drag); + } + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + initDroppable : function(el, options, isPlumbedComponent, isPermanent) { + var scope = options["scope"]; + _add(_droppables, scope, el); + var id = jsPlumb.getId(el); + + el.setAttribute("_isPermanentDroppable", isPermanent); // we use this to clear out droppables on drag complete. + _droppableOptions[id] = options; + _droppableScopesById[id] = scope; + var filterFunc = function(entry) { return entry.element != el; }, + draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + isAlreadyDraggable : function(el) { + return _draggablesById[jsPlumb.getId(el)] != null; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + return (typeof Drag != undefined && typeof Drag.Move != undefined); + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el.className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.removeClass(el, clazz); + } + else el.removeClass(clazz); + } + catch (e) { + // SVGAnimatedString not supported; no problem. + el.removeClass(clazz); + } + }, + + removeElement : function(element, parent) { + var el = _getElementObject(element); + if (el) el.dispose(); // ?? + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + setDragScope : function(el, scope) { + var drag = _draggablesById[el.get("id")]; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + drag[0].droppables = droppables; + }, + + setOffset : function(el, o) { + _getElementObject(el).setPosition({x:o.left, y:o.top}); + }, + + stopDrag : function() { + for (var i in _draggablesById) { + for (var j = 0; j < _draggablesById[i].length; j++) { + var d = _draggablesById[i][j]; + d.stop(); + if (d.originalZIndex != 0) + d.element.setStyle("z-index", d.originalZIndex); + } + } + }, + + /** + * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself. + * the other libraries do not. yui, in fact, cannot even pass an original event. we have to pull out stuff + * from the originalEvent to put in an options object for YUI. + * @param el + * @param event + * @param originalEvent + */ + trigger : function(el, event, originalEvent) { + originalEvent.stopPropagation(); + _getElementObject(el).fireEvent(event, originalEvent); + }, + + unbind : function(el, event, callback) { + el = _getElementObject(el); + el.removeEvent(event, callback); + } + }; + + window.addEvent('domready', jsPlumb.init); +})(); diff --git a/build/1.3.8/tests/qunit-all.html b/archive/1.3.8/tests/qunit-all.html similarity index 100% rename from build/1.3.8/tests/qunit-all.html rename to archive/1.3.8/tests/qunit-all.html diff --git a/build/1.3.8/tests/qunit-canvas-jquery-instance.html b/archive/1.3.8/tests/qunit-canvas-jquery-instance.html similarity index 100% rename from build/1.3.8/tests/qunit-canvas-jquery-instance.html rename to archive/1.3.8/tests/qunit-canvas-jquery-instance.html diff --git a/build/1.3.8/tests/qunit-canvas-jquery.html b/archive/1.3.8/tests/qunit-canvas-jquery.html similarity index 100% rename from build/1.3.8/tests/qunit-canvas-jquery.html rename to archive/1.3.8/tests/qunit-canvas-jquery.html diff --git a/build/1.3.8/tests/qunit-canvas-mootools.html b/archive/1.3.8/tests/qunit-canvas-mootools.html similarity index 100% rename from build/1.3.8/tests/qunit-canvas-mootools.html rename to archive/1.3.8/tests/qunit-canvas-mootools.html diff --git a/build/1.3.8/tests/qunit-svg-jquery-instance.html b/archive/1.3.8/tests/qunit-svg-jquery-instance.html similarity index 100% rename from build/1.3.8/tests/qunit-svg-jquery-instance.html rename to archive/1.3.8/tests/qunit-svg-jquery-instance.html diff --git a/build/1.3.8/tests/qunit-svg-jquery.html b/archive/1.3.8/tests/qunit-svg-jquery.html similarity index 100% rename from build/1.3.8/tests/qunit-svg-jquery.html rename to archive/1.3.8/tests/qunit-svg-jquery.html diff --git a/build/1.3.8/tests/qunit-vml-jquery-instance.html b/archive/1.3.8/tests/qunit-vml-jquery-instance.html similarity index 100% rename from build/1.3.8/tests/qunit-vml-jquery-instance.html rename to archive/1.3.8/tests/qunit-vml-jquery-instance.html diff --git a/build/1.3.8/tests/qunit-vml-jquery.html b/archive/1.3.8/tests/qunit-vml-jquery.html similarity index 100% rename from build/1.3.8/tests/qunit-vml-jquery.html rename to archive/1.3.8/tests/qunit-vml-jquery.html diff --git a/build/1.3.8/tests/qunit.css b/archive/1.3.8/tests/qunit.css similarity index 100% rename from build/1.3.8/tests/qunit.css rename to archive/1.3.8/tests/qunit.css diff --git a/archive/1.3.8/yui.jsPlumb-1.3.8-RC1.js b/archive/1.3.8/yui.jsPlumb-1.3.8-RC1.js new file mode 100644 index 000000000..48d3edbb6 --- /dev/null +++ b/archive/1.3.8/yui.jsPlumb-1.3.8-RC1.js @@ -0,0 +1,377 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.8 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the YUI3 adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +/** + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + */ +(function() { + + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function( v, b, s ) { + for( var i = +b || 0, l = this.length; i < l; i++ ) { + if( this[i]===v || s && this[i]==v ) { return i; } + } + return -1; + }; + } + + var Y; + + YUI().use('node', 'dd', 'anim', 'node-event-simulate', function(_Y) { + Y = _Y; + Y.on("domready", function() { jsPlumb.init(); }); + }); + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }, + ddEvents = [ "drag:mouseDown", "drag:afterMouseDown", "drag:mouseup", + "drag:align", "drag:removeHandle", "drag:addHandle", "drag:removeInvalid", "drag:addInvalid", + "drag:start", "drag:end", "drag:drag", "drag:over", "drag:enter", + "drag:exit", "drag:drophit", "drag:dropmiss", "drop:over", "drop:enter", "drop:exit", "drop:hit" + ], + animEvents = [ "tween" ], + /** + * helper function to curry callbacks for some element. + */ + _wrapper = function(fn) { + return function() { + try { + return fn.apply(this, arguments); + } + catch (e) { } + }; + }, + /** + * extracts options from the given options object, leaving out event handlers. + */ + _getDDOptions = function(options) { + var o = {}; + for (var i in options) if (ddEvents.indexOf(i) == -1) o[i] = options[i]; + return o; + }, + /** + * attaches all event handlers found in options to the given dragdrop object, and registering + * the given el as the element of interest. + */ + _attachListeners = function(dd, options, eventList) { + for (var ev in options) { + if (eventList.indexOf(ev) != -1) { + var w = _wrapper(options[ev]); + dd.on(ev, w); + } + } + }, + _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}, + _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }, + _lastDragObject = null, + _extend = function(o1, o2) { + for (var i in o2) + o1[i] = o2[i]; + return o1; + }, + _getAttribute = function(el, attributeId) { + return el.getAttribute(attributeId); + }, + _getElementObject = function(el) { + if (el == null) return null; + var eee = null; + eee = typeof el == 'string' ? Y.one('#' + el) : el._node ? el : Y.one(el); + return eee; + }; + + jsPlumb.CurrentLibrary = { + + addClass : function(el, clazz) { + jsPlumb.CurrentLibrary.getElementObject(el).addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + var o = _extend({node:el, to:properties}, options), + id = _getAttribute(el, "id"); + o["tween"] = jsPlumb.wrap(properties["tween"], function() { + // TODO should use a current instance. + jsPlumb.repaint(id); + }); + var a = new Y.Anim(o); + _attachListeners(a, o, animEvents); + a.run(); + }, + + appendElement : function(child, parent) { + _getElementObject (parent).append(child); + }, + + /** + * event binding wrapper. + */ + bind : function(el, event, callback) { + _getElementObject(el).on(event, callback); + }, + + dragEvents : { + "start":"drag:start", "stop":"drag:end", "drag":"drag:drag", "step":"step", + "over":"drop:enter", "out":"drop:exit", "drop":"drop:hit" + }, + + extend : _extend, + + getAttribute : _getAttribute, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + // this is a workaround for the unfortunate fact that in YUI3, the 'drop:exit' event does + // not contain a reference to the drag that just exited. single-threaded js to the + // rescue: we'll just keep it for ourselves. + if (eventArgs[0].drag) _lastDragObject = eventArgs[0].drag.el; + return _lastDragObject; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el), + dd = _draggablesById[id]; + return dd.scope; + }, + + getDropEvent : function(args) { + return args[0]; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getDOMElement : function(el) { + if (typeof(el) == "String") + return document.getElementById(el); + else if (el._node) + return el._node; + else return el; + }, + + getElementObject : _getElementObject, + + getOffset : function(el) { + var o = Y.DOM.getXY(el._node); + return {left:o[0], top:o[1]}; + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).get("parentNode"); + }, + + getScrollLeft : function(el) { + return 0; + }, + + getScrollTop : function(el) { + return 0; + }, + + getSelector : function(spec) { + var s = Y.all(spec); + return s && s ._nodes ? s._nodes : []; + }, + + getSize : function(el) { + return [ el._node.offsetWidth, el._node.offsetHeight ]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e != null && e._node != null ? e._node.tagName : null; + }, + + getUIPosition : function(args) { + var n = args[0].currentTarget.el._node, + o = Y.DOM.getXY(n); + return {left:o[0], top:o[1]}; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options) { + var _opts = _getDDOptions(options), + id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drag(_opts); + dd.el = el; + + var scope = options['scope'] || jsPlumb.Defaults.Scope; + dd.scope = scope; + + _draggablesById[id] = dd; + _add(_draggablesByScope, scope, dd); + + _attachListeners(dd, options, ddEvents); + }, + + initDroppable : function(el, options) { + var _opts = _getDDOptions(options), + id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drop(_opts); + + _droppableOptions[id] = options; + + options = _extend({}, options); + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _droppableScopesById[id] = scope; + + options["drop:enter"] = jsPlumb.wrap(options["drop:enter"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, true); + }, true); + options["drop:exit"] = jsPlumb.wrap(options["drop:exit"], function(e) { + _checkHover(el, false); + }); + options["drop:hit"] = jsPlumb.wrap(options["drop:hit"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, false); + }, true); + + _attachListeners(dd, options, ddEvents); + }, + + isAlreadyDraggable : function(el) { + el = _getElementObject(el); + return el.hasClass("yui3-dd-draggable"); + }, + + isDragSupported : function(el) { return true; }, + isDropSupported : function(el) { return true; }, + removeClass : function(el, clazz) { + jsPlumb.CurrentLibrary.getElementObject(el).removeClass(clazz); + }, + removeElement : function(el) { _getElementObject(el).remove(); }, + + setAttribute : function(el, attributeName, attributeValue) { + el.setAttribute(attributeName, attributeValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + var id = jsPlumb.getId(el), + dd = _draggablesById[id]; + if (dd) dd.set("lock", !draggable); + }, + + setDragScope : function(el, scope) { + var id = jsPlumb.getId(el), + dd = _draggablesById[id]; + if (dd) dd.scope = scope; + }, + + setOffset : function(el, o) { + el = _getElementObject(el); + el.set("top", o.top); + el.set("left", o.left); + }, + + stopDrag : function() { + Y.DD.DDM.stopDrag(); + }, + + trigger : function(el, event, originalEvent) { + originalEvent.stopPropagation(); + _getElementObject(el).simulate(event, { + pageX:originalEvent.pageX, + pageY:originalEvent.pageY, + clientX:originalEvent.clientX, + clientY:originalEvent.clientY + }); + }, + + /** + * event unbinding wrapper. + */ + unbind : function(el, event, callback) { + _getElementObject(el).detach(event, callback); + } + }; +})(); \ No newline at end of file diff --git a/build/1.3.9/demo/apidocs/files/jquery-jsPlumb-1-3-9-all-js.html b/archive/1.3.9/demo/apidocs/files/jquery-jsPlumb-1-3-9-all-js.html similarity index 100% rename from build/1.3.9/demo/apidocs/files/jquery-jsPlumb-1-3-9-all-js.html rename to archive/1.3.9/demo/apidocs/files/jquery-jsPlumb-1-3-9-all-js.html diff --git a/build/1.3.9/demo/apidocs/index.html b/archive/1.3.9/demo/apidocs/index.html similarity index 100% rename from build/1.3.9/demo/apidocs/index.html rename to archive/1.3.9/demo/apidocs/index.html diff --git a/build/1.3.9/demo/apidocs/index/Classes.html b/archive/1.3.9/demo/apidocs/index/Classes.html similarity index 100% rename from build/1.3.9/demo/apidocs/index/Classes.html rename to archive/1.3.9/demo/apidocs/index/Classes.html diff --git a/build/1.3.9/demo/apidocs/index/Files.html b/archive/1.3.9/demo/apidocs/index/Files.html similarity index 100% rename from build/1.3.9/demo/apidocs/index/Files.html rename to archive/1.3.9/demo/apidocs/index/Files.html diff --git a/build/1.3.9/demo/apidocs/index/Functions.html b/archive/1.3.9/demo/apidocs/index/Functions.html similarity index 100% rename from build/1.3.9/demo/apidocs/index/Functions.html rename to archive/1.3.9/demo/apidocs/index/Functions.html diff --git a/build/1.3.9/demo/apidocs/index/Functions2.html b/archive/1.3.9/demo/apidocs/index/Functions2.html similarity index 100% rename from build/1.3.9/demo/apidocs/index/Functions2.html rename to archive/1.3.9/demo/apidocs/index/Functions2.html diff --git a/build/1.3.9/demo/apidocs/index/General.html b/archive/1.3.9/demo/apidocs/index/General.html similarity index 100% rename from build/1.3.9/demo/apidocs/index/General.html rename to archive/1.3.9/demo/apidocs/index/General.html diff --git a/build/1.3.9/demo/apidocs/index/General2.html b/archive/1.3.9/demo/apidocs/index/General2.html similarity index 100% rename from build/1.3.9/demo/apidocs/index/General2.html rename to archive/1.3.9/demo/apidocs/index/General2.html diff --git a/build/1.3.9/demo/apidocs/index/Properties.html b/archive/1.3.9/demo/apidocs/index/Properties.html similarity index 100% rename from build/1.3.9/demo/apidocs/index/Properties.html rename to archive/1.3.9/demo/apidocs/index/Properties.html diff --git a/build/1.3.9/demo/apidocs/javascript/main.js b/archive/1.3.9/demo/apidocs/javascript/main.js similarity index 100% rename from build/1.3.9/demo/apidocs/javascript/main.js rename to archive/1.3.9/demo/apidocs/javascript/main.js diff --git a/build/1.3.9/demo/apidocs/javascript/prettify.js b/archive/1.3.9/demo/apidocs/javascript/prettify.js similarity index 100% rename from build/1.3.9/demo/apidocs/javascript/prettify.js rename to archive/1.3.9/demo/apidocs/javascript/prettify.js diff --git a/build/1.3.9/demo/apidocs/javascript/searchdata.js b/archive/1.3.9/demo/apidocs/javascript/searchdata.js similarity index 100% rename from build/1.3.9/demo/apidocs/javascript/searchdata.js rename to archive/1.3.9/demo/apidocs/javascript/searchdata.js diff --git a/build/1.3.9/demo/apidocs/search/ClassesC.html b/archive/1.3.9/demo/apidocs/search/ClassesC.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/ClassesC.html rename to archive/1.3.9/demo/apidocs/search/ClassesC.html diff --git a/build/1.3.9/demo/apidocs/search/ClassesE.html b/archive/1.3.9/demo/apidocs/search/ClassesE.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/ClassesE.html rename to archive/1.3.9/demo/apidocs/search/ClassesE.html diff --git a/build/1.3.9/demo/apidocs/search/ClassesO.html b/archive/1.3.9/demo/apidocs/search/ClassesO.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/ClassesO.html rename to archive/1.3.9/demo/apidocs/search/ClassesO.html diff --git a/build/1.3.9/demo/apidocs/search/FilesJ.html b/archive/1.3.9/demo/apidocs/search/FilesJ.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/FilesJ.html rename to archive/1.3.9/demo/apidocs/search/FilesJ.html diff --git a/build/1.3.9/demo/apidocs/search/FunctionsA.html b/archive/1.3.9/demo/apidocs/search/FunctionsA.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/FunctionsA.html rename to archive/1.3.9/demo/apidocs/search/FunctionsA.html diff --git a/build/1.3.9/demo/apidocs/search/FunctionsB.html b/archive/1.3.9/demo/apidocs/search/FunctionsB.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/FunctionsB.html rename to archive/1.3.9/demo/apidocs/search/FunctionsB.html diff --git a/build/1.3.9/demo/apidocs/search/FunctionsC.html b/archive/1.3.9/demo/apidocs/search/FunctionsC.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/FunctionsC.html rename to archive/1.3.9/demo/apidocs/search/FunctionsC.html diff --git a/build/1.3.9/demo/apidocs/search/FunctionsD.html b/archive/1.3.9/demo/apidocs/search/FunctionsD.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/FunctionsD.html rename to archive/1.3.9/demo/apidocs/search/FunctionsD.html diff --git a/build/1.3.9/demo/apidocs/search/FunctionsE.html b/archive/1.3.9/demo/apidocs/search/FunctionsE.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/FunctionsE.html rename to archive/1.3.9/demo/apidocs/search/FunctionsE.html diff --git a/build/1.3.9/demo/apidocs/search/FunctionsG.html b/archive/1.3.9/demo/apidocs/search/FunctionsG.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/FunctionsG.html rename to archive/1.3.9/demo/apidocs/search/FunctionsG.html diff --git a/build/1.3.9/demo/apidocs/search/FunctionsH.html b/archive/1.3.9/demo/apidocs/search/FunctionsH.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/FunctionsH.html rename to archive/1.3.9/demo/apidocs/search/FunctionsH.html diff --git a/build/1.3.9/demo/apidocs/search/FunctionsI.html b/archive/1.3.9/demo/apidocs/search/FunctionsI.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/FunctionsI.html rename to archive/1.3.9/demo/apidocs/search/FunctionsI.html diff --git a/build/1.3.9/demo/apidocs/search/FunctionsM.html b/archive/1.3.9/demo/apidocs/search/FunctionsM.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/FunctionsM.html rename to archive/1.3.9/demo/apidocs/search/FunctionsM.html diff --git a/build/1.3.9/demo/apidocs/search/FunctionsP.html b/archive/1.3.9/demo/apidocs/search/FunctionsP.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/FunctionsP.html rename to archive/1.3.9/demo/apidocs/search/FunctionsP.html diff --git a/build/1.3.9/demo/apidocs/search/FunctionsR.html b/archive/1.3.9/demo/apidocs/search/FunctionsR.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/FunctionsR.html rename to archive/1.3.9/demo/apidocs/search/FunctionsR.html diff --git a/build/1.3.9/demo/apidocs/search/FunctionsS.html b/archive/1.3.9/demo/apidocs/search/FunctionsS.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/FunctionsS.html rename to archive/1.3.9/demo/apidocs/search/FunctionsS.html diff --git a/build/1.3.9/demo/apidocs/search/FunctionsT.html b/archive/1.3.9/demo/apidocs/search/FunctionsT.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/FunctionsT.html rename to archive/1.3.9/demo/apidocs/search/FunctionsT.html diff --git a/build/1.3.9/demo/apidocs/search/FunctionsU.html b/archive/1.3.9/demo/apidocs/search/FunctionsU.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/FunctionsU.html rename to archive/1.3.9/demo/apidocs/search/FunctionsU.html diff --git a/build/1.3.9/demo/apidocs/search/GeneralA.html b/archive/1.3.9/demo/apidocs/search/GeneralA.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/GeneralA.html rename to archive/1.3.9/demo/apidocs/search/GeneralA.html diff --git a/build/1.3.9/demo/apidocs/search/GeneralB.html b/archive/1.3.9/demo/apidocs/search/GeneralB.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/GeneralB.html rename to archive/1.3.9/demo/apidocs/search/GeneralB.html diff --git a/build/1.3.9/demo/apidocs/search/GeneralC.html b/archive/1.3.9/demo/apidocs/search/GeneralC.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/GeneralC.html rename to archive/1.3.9/demo/apidocs/search/GeneralC.html diff --git a/build/1.3.9/demo/apidocs/search/GeneralD.html b/archive/1.3.9/demo/apidocs/search/GeneralD.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/GeneralD.html rename to archive/1.3.9/demo/apidocs/search/GeneralD.html diff --git a/build/1.3.9/demo/apidocs/search/GeneralE.html b/archive/1.3.9/demo/apidocs/search/GeneralE.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/GeneralE.html rename to archive/1.3.9/demo/apidocs/search/GeneralE.html diff --git a/build/1.3.9/demo/apidocs/search/GeneralF.html b/archive/1.3.9/demo/apidocs/search/GeneralF.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/GeneralF.html rename to archive/1.3.9/demo/apidocs/search/GeneralF.html diff --git a/build/1.3.9/demo/apidocs/search/GeneralG.html b/archive/1.3.9/demo/apidocs/search/GeneralG.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/GeneralG.html rename to archive/1.3.9/demo/apidocs/search/GeneralG.html diff --git a/build/1.3.9/demo/apidocs/search/GeneralH.html b/archive/1.3.9/demo/apidocs/search/GeneralH.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/GeneralH.html rename to archive/1.3.9/demo/apidocs/search/GeneralH.html diff --git a/build/1.3.9/demo/apidocs/search/GeneralI.html b/archive/1.3.9/demo/apidocs/search/GeneralI.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/GeneralI.html rename to archive/1.3.9/demo/apidocs/search/GeneralI.html diff --git a/build/1.3.9/demo/apidocs/search/GeneralJ.html b/archive/1.3.9/demo/apidocs/search/GeneralJ.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/GeneralJ.html rename to archive/1.3.9/demo/apidocs/search/GeneralJ.html diff --git a/build/1.3.9/demo/apidocs/search/GeneralM.html b/archive/1.3.9/demo/apidocs/search/GeneralM.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/GeneralM.html rename to archive/1.3.9/demo/apidocs/search/GeneralM.html diff --git a/build/1.3.9/demo/apidocs/search/GeneralO.html b/archive/1.3.9/demo/apidocs/search/GeneralO.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/GeneralO.html rename to archive/1.3.9/demo/apidocs/search/GeneralO.html diff --git a/build/1.3.9/demo/apidocs/search/GeneralP.html b/archive/1.3.9/demo/apidocs/search/GeneralP.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/GeneralP.html rename to archive/1.3.9/demo/apidocs/search/GeneralP.html diff --git a/build/1.3.9/demo/apidocs/search/GeneralR.html b/archive/1.3.9/demo/apidocs/search/GeneralR.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/GeneralR.html rename to archive/1.3.9/demo/apidocs/search/GeneralR.html diff --git a/build/1.3.9/demo/apidocs/search/GeneralS.html b/archive/1.3.9/demo/apidocs/search/GeneralS.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/GeneralS.html rename to archive/1.3.9/demo/apidocs/search/GeneralS.html diff --git a/build/1.3.9/demo/apidocs/search/GeneralT.html b/archive/1.3.9/demo/apidocs/search/GeneralT.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/GeneralT.html rename to archive/1.3.9/demo/apidocs/search/GeneralT.html diff --git a/build/1.3.9/demo/apidocs/search/GeneralU.html b/archive/1.3.9/demo/apidocs/search/GeneralU.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/GeneralU.html rename to archive/1.3.9/demo/apidocs/search/GeneralU.html diff --git a/build/1.3.9/demo/apidocs/search/NoResults.html b/archive/1.3.9/demo/apidocs/search/NoResults.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/NoResults.html rename to archive/1.3.9/demo/apidocs/search/NoResults.html diff --git a/build/1.3.9/demo/apidocs/search/PropertiesC.html b/archive/1.3.9/demo/apidocs/search/PropertiesC.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/PropertiesC.html rename to archive/1.3.9/demo/apidocs/search/PropertiesC.html diff --git a/build/1.3.9/demo/apidocs/search/PropertiesD.html b/archive/1.3.9/demo/apidocs/search/PropertiesD.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/PropertiesD.html rename to archive/1.3.9/demo/apidocs/search/PropertiesD.html diff --git a/build/1.3.9/demo/apidocs/search/PropertiesE.html b/archive/1.3.9/demo/apidocs/search/PropertiesE.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/PropertiesE.html rename to archive/1.3.9/demo/apidocs/search/PropertiesE.html diff --git a/build/1.3.9/demo/apidocs/search/PropertiesO.html b/archive/1.3.9/demo/apidocs/search/PropertiesO.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/PropertiesO.html rename to archive/1.3.9/demo/apidocs/search/PropertiesO.html diff --git a/build/1.3.9/demo/apidocs/search/PropertiesS.html b/archive/1.3.9/demo/apidocs/search/PropertiesS.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/PropertiesS.html rename to archive/1.3.9/demo/apidocs/search/PropertiesS.html diff --git a/build/1.3.9/demo/apidocs/search/PropertiesT.html b/archive/1.3.9/demo/apidocs/search/PropertiesT.html similarity index 100% rename from build/1.3.9/demo/apidocs/search/PropertiesT.html rename to archive/1.3.9/demo/apidocs/search/PropertiesT.html diff --git a/build/1.3.9/demo/apidocs/styles/main.css b/archive/1.3.9/demo/apidocs/styles/main.css similarity index 100% rename from build/1.3.9/demo/apidocs/styles/main.css rename to archive/1.3.9/demo/apidocs/styles/main.css diff --git a/build/1.3.9/demo/css/anchorDemo.css b/archive/1.3.9/demo/css/anchorDemo.css similarity index 100% rename from build/1.3.9/demo/css/anchorDemo.css rename to archive/1.3.9/demo/css/anchorDemo.css diff --git a/build/1.3.9/demo/css/chartDemo.css b/archive/1.3.9/demo/css/chartDemo.css similarity index 100% rename from build/1.3.9/demo/css/chartDemo.css rename to archive/1.3.9/demo/css/chartDemo.css diff --git a/build/1.3.9/demo/css/demo.css b/archive/1.3.9/demo/css/demo.css similarity index 100% rename from build/1.3.9/demo/css/demo.css rename to archive/1.3.9/demo/css/demo.css diff --git a/build/1.3.9/demo/css/dragAnimDemo.css b/archive/1.3.9/demo/css/dragAnimDemo.css similarity index 100% rename from build/1.3.9/demo/css/dragAnimDemo.css rename to archive/1.3.9/demo/css/dragAnimDemo.css diff --git a/build/1.3.9/demo/css/draggableConnectorsDemo.css b/archive/1.3.9/demo/css/draggableConnectorsDemo.css similarity index 100% rename from build/1.3.9/demo/css/draggableConnectorsDemo.css rename to archive/1.3.9/demo/css/draggableConnectorsDemo.css diff --git a/build/1.3.9/demo/css/dynamicAnchorsDemo.css b/archive/1.3.9/demo/css/dynamicAnchorsDemo.css similarity index 100% rename from build/1.3.9/demo/css/dynamicAnchorsDemo.css rename to archive/1.3.9/demo/css/dynamicAnchorsDemo.css diff --git a/build/1.3.9/demo/css/flowchartDemo.css b/archive/1.3.9/demo/css/flowchartDemo.css similarity index 100% rename from build/1.3.9/demo/css/flowchartDemo.css rename to archive/1.3.9/demo/css/flowchartDemo.css diff --git a/build/1.3.9/demo/css/jsPlumbDemo.css b/archive/1.3.9/demo/css/jsPlumbDemo.css similarity index 100% rename from build/1.3.9/demo/css/jsPlumbDemo.css rename to archive/1.3.9/demo/css/jsPlumbDemo.css diff --git a/build/1.3.9/demo/css/makeTargetDemo.css b/archive/1.3.9/demo/css/makeTargetDemo.css similarity index 100% rename from build/1.3.9/demo/css/makeTargetDemo.css rename to archive/1.3.9/demo/css/makeTargetDemo.css diff --git a/build/1.3.9/demo/css/multipleJsPlumbDemo.css b/archive/1.3.9/demo/css/multipleJsPlumbDemo.css similarity index 100% rename from build/1.3.9/demo/css/multipleJsPlumbDemo.css rename to archive/1.3.9/demo/css/multipleJsPlumbDemo.css diff --git a/build/1.3.9/demo/css/selectDemo.css b/archive/1.3.9/demo/css/selectDemo.css similarity index 100% rename from build/1.3.9/demo/css/selectDemo.css rename to archive/1.3.9/demo/css/selectDemo.css diff --git a/build/1.3.9/demo/css/stateMachineDemo.css b/archive/1.3.9/demo/css/stateMachineDemo.css similarity index 100% rename from build/1.3.9/demo/css/stateMachineDemo.css rename to archive/1.3.9/demo/css/stateMachineDemo.css diff --git a/build/1.3.9/demo/doc/archive/1.2.6/content.html b/archive/1.3.9/demo/doc/archive/1.2.6/content.html similarity index 100% rename from build/1.3.9/demo/doc/archive/1.2.6/content.html rename to archive/1.3.9/demo/doc/archive/1.2.6/content.html diff --git a/build/1.3.9/demo/doc/archive/1.2.6/index.html b/archive/1.3.9/demo/doc/archive/1.2.6/index.html similarity index 100% rename from build/1.3.9/demo/doc/archive/1.2.6/index.html rename to archive/1.3.9/demo/doc/archive/1.2.6/index.html diff --git a/build/1.3.9/demo/doc/archive/1.2.6/usage.html b/archive/1.3.9/demo/doc/archive/1.2.6/usage.html similarity index 100% rename from build/1.3.9/demo/doc/archive/1.2.6/usage.html rename to archive/1.3.9/demo/doc/archive/1.2.6/usage.html diff --git a/build/1.3.9/demo/doc/archive/1.3.2/content.html b/archive/1.3.9/demo/doc/archive/1.3.2/content.html similarity index 100% rename from build/1.3.9/demo/doc/archive/1.3.2/content.html rename to archive/1.3.9/demo/doc/archive/1.3.2/content.html diff --git a/build/1.3.9/demo/doc/archive/1.3.2/index.html b/archive/1.3.9/demo/doc/archive/1.3.2/index.html similarity index 100% rename from build/1.3.9/demo/doc/archive/1.3.2/index.html rename to archive/1.3.9/demo/doc/archive/1.3.2/index.html diff --git a/build/1.3.9/demo/doc/archive/1.3.2/usage.html b/archive/1.3.9/demo/doc/archive/1.3.2/usage.html similarity index 100% rename from build/1.3.9/demo/doc/archive/1.3.2/usage.html rename to archive/1.3.9/demo/doc/archive/1.3.2/usage.html diff --git a/build/1.3.9/demo/doc/archive/1.3.3/content.html b/archive/1.3.9/demo/doc/archive/1.3.3/content.html similarity index 100% rename from build/1.3.9/demo/doc/archive/1.3.3/content.html rename to archive/1.3.9/demo/doc/archive/1.3.3/content.html diff --git a/build/1.3.9/demo/doc/archive/1.3.3/index.html b/archive/1.3.9/demo/doc/archive/1.3.3/index.html similarity index 100% rename from build/1.3.9/demo/doc/archive/1.3.3/index.html rename to archive/1.3.9/demo/doc/archive/1.3.3/index.html diff --git a/build/1.3.9/demo/doc/archive/1.3.3/jsPlumbDoc.css b/archive/1.3.9/demo/doc/archive/1.3.3/jsPlumbDoc.css similarity index 100% rename from build/1.3.9/demo/doc/archive/1.3.3/jsPlumbDoc.css rename to archive/1.3.9/demo/doc/archive/1.3.3/jsPlumbDoc.css diff --git a/build/1.3.9/demo/doc/archive/1.3.3/usage.html b/archive/1.3.9/demo/doc/archive/1.3.3/usage.html similarity index 100% rename from build/1.3.9/demo/doc/archive/1.3.3/usage.html rename to archive/1.3.9/demo/doc/archive/1.3.3/usage.html diff --git a/build/1.3.9/demo/doc/archive/1.3.4/content.html b/archive/1.3.9/demo/doc/archive/1.3.4/content.html similarity index 100% rename from build/1.3.9/demo/doc/archive/1.3.4/content.html rename to archive/1.3.9/demo/doc/archive/1.3.4/content.html diff --git a/build/1.3.9/demo/doc/archive/1.3.4/index.html b/archive/1.3.9/demo/doc/archive/1.3.4/index.html similarity index 100% rename from build/1.3.9/demo/doc/archive/1.3.4/index.html rename to archive/1.3.9/demo/doc/archive/1.3.4/index.html diff --git a/build/1.3.9/demo/doc/archive/1.3.4/jsPlumbDoc.css b/archive/1.3.9/demo/doc/archive/1.3.4/jsPlumbDoc.css similarity index 100% rename from build/1.3.9/demo/doc/archive/1.3.4/jsPlumbDoc.css rename to archive/1.3.9/demo/doc/archive/1.3.4/jsPlumbDoc.css diff --git a/build/1.3.9/demo/doc/archive/1.3.4/usage.html b/archive/1.3.9/demo/doc/archive/1.3.4/usage.html similarity index 100% rename from build/1.3.9/demo/doc/archive/1.3.4/usage.html rename to archive/1.3.9/demo/doc/archive/1.3.4/usage.html diff --git a/build/1.3.9/demo/doc/archive/1.3.5/content.html b/archive/1.3.9/demo/doc/archive/1.3.5/content.html similarity index 100% rename from build/1.3.9/demo/doc/archive/1.3.5/content.html rename to archive/1.3.9/demo/doc/archive/1.3.5/content.html diff --git a/build/1.3.9/demo/doc/archive/1.3.5/index.html b/archive/1.3.9/demo/doc/archive/1.3.5/index.html similarity index 100% rename from build/1.3.9/demo/doc/archive/1.3.5/index.html rename to archive/1.3.9/demo/doc/archive/1.3.5/index.html diff --git a/build/1.3.9/demo/doc/archive/1.3.5/jsPlumbDoc.css b/archive/1.3.9/demo/doc/archive/1.3.5/jsPlumbDoc.css similarity index 100% rename from build/1.3.9/demo/doc/archive/1.3.5/jsPlumbDoc.css rename to archive/1.3.9/demo/doc/archive/1.3.5/jsPlumbDoc.css diff --git a/build/1.3.9/demo/doc/archive/1.3.5/usage.html b/archive/1.3.9/demo/doc/archive/1.3.5/usage.html similarity index 100% rename from build/1.3.9/demo/doc/archive/1.3.5/usage.html rename to archive/1.3.9/demo/doc/archive/1.3.5/usage.html diff --git a/build/1.3.9/demo/doc/archive/1.3.6/content.html b/archive/1.3.9/demo/doc/archive/1.3.6/content.html similarity index 100% rename from build/1.3.9/demo/doc/archive/1.3.6/content.html rename to archive/1.3.9/demo/doc/archive/1.3.6/content.html diff --git a/build/1.3.9/demo/doc/archive/1.3.6/index.html b/archive/1.3.9/demo/doc/archive/1.3.6/index.html similarity index 100% rename from build/1.3.9/demo/doc/archive/1.3.6/index.html rename to archive/1.3.9/demo/doc/archive/1.3.6/index.html diff --git a/build/1.3.9/demo/doc/archive/1.3.6/jsPlumbDoc.css b/archive/1.3.9/demo/doc/archive/1.3.6/jsPlumbDoc.css similarity index 100% rename from build/1.3.9/demo/doc/archive/1.3.6/jsPlumbDoc.css rename to archive/1.3.9/demo/doc/archive/1.3.6/jsPlumbDoc.css diff --git a/build/1.3.9/demo/doc/archive/1.3.6/usage.html b/archive/1.3.9/demo/doc/archive/1.3.6/usage.html similarity index 100% rename from build/1.3.9/demo/doc/archive/1.3.6/usage.html rename to archive/1.3.9/demo/doc/archive/1.3.6/usage.html diff --git a/build/1.3.9/demo/doc/archive/1.3.7/content.html b/archive/1.3.9/demo/doc/archive/1.3.7/content.html similarity index 100% rename from build/1.3.9/demo/doc/archive/1.3.7/content.html rename to archive/1.3.9/demo/doc/archive/1.3.7/content.html diff --git a/build/1.3.9/demo/doc/archive/1.3.7/index.html b/archive/1.3.9/demo/doc/archive/1.3.7/index.html similarity index 100% rename from build/1.3.9/demo/doc/archive/1.3.7/index.html rename to archive/1.3.9/demo/doc/archive/1.3.7/index.html diff --git a/build/1.3.9/demo/doc/archive/1.3.7/jsPlumbDoc.css b/archive/1.3.9/demo/doc/archive/1.3.7/jsPlumbDoc.css similarity index 100% rename from build/1.3.9/demo/doc/archive/1.3.7/jsPlumbDoc.css rename to archive/1.3.9/demo/doc/archive/1.3.7/jsPlumbDoc.css diff --git a/build/1.3.9/demo/doc/archive/1.3.7/usage.html b/archive/1.3.9/demo/doc/archive/1.3.7/usage.html similarity index 100% rename from build/1.3.9/demo/doc/archive/1.3.7/usage.html rename to archive/1.3.9/demo/doc/archive/1.3.7/usage.html diff --git a/build/1.3.9/demo/doc/archive/1.3.8/content.html b/archive/1.3.9/demo/doc/archive/1.3.8/content.html similarity index 100% rename from build/1.3.9/demo/doc/archive/1.3.8/content.html rename to archive/1.3.9/demo/doc/archive/1.3.8/content.html diff --git a/build/1.3.9/demo/doc/archive/1.3.8/index.html b/archive/1.3.9/demo/doc/archive/1.3.8/index.html similarity index 100% rename from build/1.3.9/demo/doc/archive/1.3.8/index.html rename to archive/1.3.9/demo/doc/archive/1.3.8/index.html diff --git a/build/1.3.9/demo/doc/archive/1.3.8/jsPlumbDoc.css b/archive/1.3.9/demo/doc/archive/1.3.8/jsPlumbDoc.css similarity index 100% rename from build/1.3.9/demo/doc/archive/1.3.8/jsPlumbDoc.css rename to archive/1.3.9/demo/doc/archive/1.3.8/jsPlumbDoc.css diff --git a/build/1.3.9/demo/doc/archive/1.3.8/usage.html b/archive/1.3.9/demo/doc/archive/1.3.8/usage.html similarity index 100% rename from build/1.3.9/demo/doc/archive/1.3.8/usage.html rename to archive/1.3.9/demo/doc/archive/1.3.8/usage.html diff --git a/build/1.3.9/demo/doc/content.html b/archive/1.3.9/demo/doc/content.html similarity index 100% rename from build/1.3.9/demo/doc/content.html rename to archive/1.3.9/demo/doc/content.html diff --git a/build/1.3.9/demo/doc/index.html b/archive/1.3.9/demo/doc/index.html similarity index 100% rename from build/1.3.9/demo/doc/index.html rename to archive/1.3.9/demo/doc/index.html diff --git a/build/1.3.9/demo/doc/jsPlumbDoc.css b/archive/1.3.9/demo/doc/jsPlumbDoc.css similarity index 100% rename from build/1.3.9/demo/doc/jsPlumbDoc.css rename to archive/1.3.9/demo/doc/jsPlumbDoc.css diff --git a/build/1.3.9/demo/doc/usage.html b/archive/1.3.9/demo/doc/usage.html similarity index 100% rename from build/1.3.9/demo/doc/usage.html rename to archive/1.3.9/demo/doc/usage.html diff --git a/build/1.3.9/demo/img/bigdot.jpg b/archive/1.3.9/demo/img/bigdot.jpg similarity index 100% rename from build/1.3.9/demo/img/bigdot.jpg rename to archive/1.3.9/demo/img/bigdot.jpg diff --git a/build/1.3.9/demo/img/bigdot.png b/archive/1.3.9/demo/img/bigdot.png similarity index 100% rename from build/1.3.9/demo/img/bigdot.png rename to archive/1.3.9/demo/img/bigdot.png diff --git a/build/1.3.9/demo/img/bigdot.xcf b/archive/1.3.9/demo/img/bigdot.xcf similarity index 100% rename from build/1.3.9/demo/img/bigdot.xcf rename to archive/1.3.9/demo/img/bigdot.xcf diff --git a/build/1.3.9/demo/img/dragging_1.jpg b/archive/1.3.9/demo/img/dragging_1.jpg similarity index 100% rename from build/1.3.9/demo/img/dragging_1.jpg rename to archive/1.3.9/demo/img/dragging_1.jpg diff --git a/build/1.3.9/demo/img/dragging_2.jpg b/archive/1.3.9/demo/img/dragging_2.jpg similarity index 100% rename from build/1.3.9/demo/img/dragging_2.jpg rename to archive/1.3.9/demo/img/dragging_2.jpg diff --git a/build/1.3.9/demo/img/dragging_3.jpg b/archive/1.3.9/demo/img/dragging_3.jpg similarity index 100% rename from build/1.3.9/demo/img/dragging_3.jpg rename to archive/1.3.9/demo/img/dragging_3.jpg diff --git a/build/1.3.9/demo/img/dynamicAnchorBg.jpg b/archive/1.3.9/demo/img/dynamicAnchorBg.jpg similarity index 100% rename from build/1.3.9/demo/img/dynamicAnchorBg.jpg rename to archive/1.3.9/demo/img/dynamicAnchorBg.jpg diff --git a/build/1.3.9/demo/img/endpointTest1.png b/archive/1.3.9/demo/img/endpointTest1.png similarity index 100% rename from build/1.3.9/demo/img/endpointTest1.png rename to archive/1.3.9/demo/img/endpointTest1.png diff --git a/build/1.3.9/demo/img/endpointTest1.xcf b/archive/1.3.9/demo/img/endpointTest1.xcf similarity index 100% rename from build/1.3.9/demo/img/endpointTest1.xcf rename to archive/1.3.9/demo/img/endpointTest1.xcf diff --git a/build/1.3.9/demo/img/index-bg.gif b/archive/1.3.9/demo/img/index-bg.gif similarity index 100% rename from build/1.3.9/demo/img/index-bg.gif rename to archive/1.3.9/demo/img/index-bg.gif diff --git a/build/1.3.9/demo/img/issue4_final.jpg b/archive/1.3.9/demo/img/issue4_final.jpg similarity index 100% rename from build/1.3.9/demo/img/issue4_final.jpg rename to archive/1.3.9/demo/img/issue4_final.jpg diff --git a/build/1.3.9/demo/img/littledot.png b/archive/1.3.9/demo/img/littledot.png similarity index 100% rename from build/1.3.9/demo/img/littledot.png rename to archive/1.3.9/demo/img/littledot.png diff --git a/build/1.3.9/demo/img/littledot.xcf b/archive/1.3.9/demo/img/littledot.xcf similarity index 100% rename from build/1.3.9/demo/img/littledot.xcf rename to archive/1.3.9/demo/img/littledot.xcf diff --git a/build/1.3.9/demo/img/pattern.jpg b/archive/1.3.9/demo/img/pattern.jpg similarity index 100% rename from build/1.3.9/demo/img/pattern.jpg rename to archive/1.3.9/demo/img/pattern.jpg diff --git a/build/1.3.9/demo/img/swappedAnchors.jpg b/archive/1.3.9/demo/img/swappedAnchors.jpg similarity index 100% rename from build/1.3.9/demo/img/swappedAnchors.jpg rename to archive/1.3.9/demo/img/swappedAnchors.jpg diff --git a/build/1.3.9/demo/index.html b/archive/1.3.9/demo/index.html similarity index 100% rename from build/1.3.9/demo/index.html rename to archive/1.3.9/demo/index.html diff --git a/build/1.3.9/demo/jquery/anchorDemo.html b/archive/1.3.9/demo/jquery/anchorDemo.html similarity index 100% rename from build/1.3.9/demo/jquery/anchorDemo.html rename to archive/1.3.9/demo/jquery/anchorDemo.html diff --git a/build/1.3.9/demo/jquery/chartDemo.html b/archive/1.3.9/demo/jquery/chartDemo.html similarity index 100% rename from build/1.3.9/demo/jquery/chartDemo.html rename to archive/1.3.9/demo/jquery/chartDemo.html diff --git a/build/1.3.9/demo/jquery/demo.html b/archive/1.3.9/demo/jquery/demo.html similarity index 100% rename from build/1.3.9/demo/jquery/demo.html rename to archive/1.3.9/demo/jquery/demo.html diff --git a/build/1.3.9/demo/jquery/dragAnimDemo.html b/archive/1.3.9/demo/jquery/dragAnimDemo.html similarity index 100% rename from build/1.3.9/demo/jquery/dragAnimDemo.html rename to archive/1.3.9/demo/jquery/dragAnimDemo.html diff --git a/build/1.3.9/demo/jquery/draggableConnectorsDemo.html b/archive/1.3.9/demo/jquery/draggableConnectorsDemo.html similarity index 100% rename from build/1.3.9/demo/jquery/draggableConnectorsDemo.html rename to archive/1.3.9/demo/jquery/draggableConnectorsDemo.html diff --git a/build/1.3.9/demo/jquery/dynamicAnchorsDemo.html b/archive/1.3.9/demo/jquery/dynamicAnchorsDemo.html similarity index 100% rename from build/1.3.9/demo/jquery/dynamicAnchorsDemo.html rename to archive/1.3.9/demo/jquery/dynamicAnchorsDemo.html diff --git a/build/1.3.9/demo/jquery/flowchartConnectorsDemo.html b/archive/1.3.9/demo/jquery/flowchartConnectorsDemo.html similarity index 100% rename from build/1.3.9/demo/jquery/flowchartConnectorsDemo.html rename to archive/1.3.9/demo/jquery/flowchartConnectorsDemo.html diff --git a/build/1.3.9/demo/jquery/loadTest.html b/archive/1.3.9/demo/jquery/loadTest.html similarity index 100% rename from build/1.3.9/demo/jquery/loadTest.html rename to archive/1.3.9/demo/jquery/loadTest.html diff --git a/build/1.3.9/demo/jquery/makeSourceDemo.html b/archive/1.3.9/demo/jquery/makeSourceDemo.html similarity index 100% rename from build/1.3.9/demo/jquery/makeSourceDemo.html rename to archive/1.3.9/demo/jquery/makeSourceDemo.html diff --git a/build/1.3.9/demo/jquery/makeTargetDemo.html b/archive/1.3.9/demo/jquery/makeTargetDemo.html similarity index 100% rename from build/1.3.9/demo/jquery/makeTargetDemo.html rename to archive/1.3.9/demo/jquery/makeTargetDemo.html diff --git a/build/1.3.9/demo/jquery/stateMachineDemo.html b/archive/1.3.9/demo/jquery/stateMachineDemo.html similarity index 100% rename from build/1.3.9/demo/jquery/stateMachineDemo.html rename to archive/1.3.9/demo/jquery/stateMachineDemo.html diff --git a/build/1.3.9/demo/js/anchorDemo-jquery.js b/archive/1.3.9/demo/js/anchorDemo-jquery.js similarity index 100% rename from build/1.3.9/demo/js/anchorDemo-jquery.js rename to archive/1.3.9/demo/js/anchorDemo-jquery.js diff --git a/build/1.3.9/demo/js/anchorDemo-mootools.js b/archive/1.3.9/demo/js/anchorDemo-mootools.js similarity index 100% rename from build/1.3.9/demo/js/anchorDemo-mootools.js rename to archive/1.3.9/demo/js/anchorDemo-mootools.js diff --git a/build/1.3.9/demo/js/anchorDemo-yui3.js b/archive/1.3.9/demo/js/anchorDemo-yui3.js similarity index 100% rename from build/1.3.9/demo/js/anchorDemo-yui3.js rename to archive/1.3.9/demo/js/anchorDemo-yui3.js diff --git a/build/1.3.9/demo/js/anchorDemo.js b/archive/1.3.9/demo/js/anchorDemo.js similarity index 100% rename from build/1.3.9/demo/js/anchorDemo.js rename to archive/1.3.9/demo/js/anchorDemo.js diff --git a/build/1.3.9/demo/js/archive/1.3.0/anchorDemo.js b/archive/1.3.9/demo/js/archive/1.3.0/anchorDemo.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.0/anchorDemo.js rename to archive/1.3.9/demo/js/archive/1.3.0/anchorDemo.js diff --git a/build/1.3.9/demo/js/archive/1.3.0/chartDemo.js b/archive/1.3.9/demo/js/archive/1.3.0/chartDemo.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.0/chartDemo.js rename to archive/1.3.9/demo/js/archive/1.3.0/chartDemo.js diff --git a/build/1.3.9/demo/js/archive/1.3.0/demo-helper-jquery.js b/archive/1.3.9/demo/js/archive/1.3.0/demo-helper-jquery.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.0/demo-helper-jquery.js rename to archive/1.3.9/demo/js/archive/1.3.0/demo-helper-jquery.js diff --git a/build/1.3.9/demo/js/archive/1.3.0/demo-helper-mootools.js b/archive/1.3.9/demo/js/archive/1.3.0/demo-helper-mootools.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.0/demo-helper-mootools.js rename to archive/1.3.9/demo/js/archive/1.3.0/demo-helper-mootools.js diff --git a/build/1.3.9/demo/js/archive/1.3.0/demo-helper-yui3.js b/archive/1.3.9/demo/js/archive/1.3.0/demo-helper-yui3.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.0/demo-helper-yui3.js rename to archive/1.3.9/demo/js/archive/1.3.0/demo-helper-yui3.js diff --git a/build/1.3.9/demo/js/archive/1.3.0/demo.js b/archive/1.3.9/demo/js/archive/1.3.0/demo.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.0/demo.js rename to archive/1.3.9/demo/js/archive/1.3.0/demo.js diff --git a/build/1.3.9/demo/js/archive/1.3.0/dragAnimDemo-jquery.js b/archive/1.3.9/demo/js/archive/1.3.0/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.0/dragAnimDemo-jquery.js rename to archive/1.3.9/demo/js/archive/1.3.0/dragAnimDemo-jquery.js diff --git a/build/1.3.9/demo/js/archive/1.3.0/dragAnimDemo-mootools.js b/archive/1.3.9/demo/js/archive/1.3.0/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.0/dragAnimDemo-mootools.js rename to archive/1.3.9/demo/js/archive/1.3.0/dragAnimDemo-mootools.js diff --git a/build/1.3.9/demo/js/archive/1.3.0/dragAnimDemo-yui3.js b/archive/1.3.9/demo/js/archive/1.3.0/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.0/dragAnimDemo-yui3.js rename to archive/1.3.9/demo/js/archive/1.3.0/dragAnimDemo-yui3.js diff --git a/build/1.3.9/demo/js/archive/1.3.0/dragAnimDemo.js b/archive/1.3.9/demo/js/archive/1.3.0/dragAnimDemo.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.0/dragAnimDemo.js rename to archive/1.3.9/demo/js/archive/1.3.0/dragAnimDemo.js diff --git a/build/1.3.9/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js b/archive/1.3.9/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js rename to archive/1.3.9/demo/js/archive/1.3.0/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.9/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js b/archive/1.3.9/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js rename to archive/1.3.9/demo/js/archive/1.3.0/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.9/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js b/archive/1.3.9/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js rename to archive/1.3.9/demo/js/archive/1.3.0/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.9/demo/js/archive/1.3.0/draggableConnectorsDemo.js b/archive/1.3.9/demo/js/archive/1.3.0/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.0/draggableConnectorsDemo.js rename to archive/1.3.9/demo/js/archive/1.3.0/draggableConnectorsDemo.js diff --git a/build/1.3.9/demo/js/archive/1.3.0/dynamicAnchorsDemo.js b/archive/1.3.9/demo/js/archive/1.3.0/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.0/dynamicAnchorsDemo.js rename to archive/1.3.9/demo/js/archive/1.3.0/dynamicAnchorsDemo.js diff --git a/build/1.3.9/demo/js/archive/1.3.0/flowchartConnectorsDemo.js b/archive/1.3.9/demo/js/archive/1.3.0/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.0/flowchartConnectorsDemo.js rename to archive/1.3.9/demo/js/archive/1.3.0/flowchartConnectorsDemo.js diff --git a/build/1.3.9/demo/js/archive/1.3.1/anchorDemo.js b/archive/1.3.9/demo/js/archive/1.3.1/anchorDemo.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.1/anchorDemo.js rename to archive/1.3.9/demo/js/archive/1.3.1/anchorDemo.js diff --git a/build/1.3.9/demo/js/archive/1.3.1/chartDemo.js b/archive/1.3.9/demo/js/archive/1.3.1/chartDemo.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.1/chartDemo.js rename to archive/1.3.9/demo/js/archive/1.3.1/chartDemo.js diff --git a/build/1.3.9/demo/js/archive/1.3.1/demo-helper-jquery.js b/archive/1.3.9/demo/js/archive/1.3.1/demo-helper-jquery.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.1/demo-helper-jquery.js rename to archive/1.3.9/demo/js/archive/1.3.1/demo-helper-jquery.js diff --git a/build/1.3.9/demo/js/archive/1.3.1/demo-helper-mootools.js b/archive/1.3.9/demo/js/archive/1.3.1/demo-helper-mootools.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.1/demo-helper-mootools.js rename to archive/1.3.9/demo/js/archive/1.3.1/demo-helper-mootools.js diff --git a/build/1.3.9/demo/js/archive/1.3.1/demo-helper-yui3.js b/archive/1.3.9/demo/js/archive/1.3.1/demo-helper-yui3.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.1/demo-helper-yui3.js rename to archive/1.3.9/demo/js/archive/1.3.1/demo-helper-yui3.js diff --git a/build/1.3.9/demo/js/archive/1.3.1/demo.js b/archive/1.3.9/demo/js/archive/1.3.1/demo.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.1/demo.js rename to archive/1.3.9/demo/js/archive/1.3.1/demo.js diff --git a/build/1.3.9/demo/js/archive/1.3.1/dragAnimDemo-jquery.js b/archive/1.3.9/demo/js/archive/1.3.1/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.1/dragAnimDemo-jquery.js rename to archive/1.3.9/demo/js/archive/1.3.1/dragAnimDemo-jquery.js diff --git a/build/1.3.9/demo/js/archive/1.3.1/dragAnimDemo-mootools.js b/archive/1.3.9/demo/js/archive/1.3.1/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.1/dragAnimDemo-mootools.js rename to archive/1.3.9/demo/js/archive/1.3.1/dragAnimDemo-mootools.js diff --git a/build/1.3.9/demo/js/archive/1.3.1/dragAnimDemo-yui3.js b/archive/1.3.9/demo/js/archive/1.3.1/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.1/dragAnimDemo-yui3.js rename to archive/1.3.9/demo/js/archive/1.3.1/dragAnimDemo-yui3.js diff --git a/build/1.3.9/demo/js/archive/1.3.1/dragAnimDemo.js b/archive/1.3.9/demo/js/archive/1.3.1/dragAnimDemo.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.1/dragAnimDemo.js rename to archive/1.3.9/demo/js/archive/1.3.1/dragAnimDemo.js diff --git a/build/1.3.9/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js b/archive/1.3.9/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js rename to archive/1.3.9/demo/js/archive/1.3.1/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.9/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js b/archive/1.3.9/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js rename to archive/1.3.9/demo/js/archive/1.3.1/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.9/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js b/archive/1.3.9/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js rename to archive/1.3.9/demo/js/archive/1.3.1/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.9/demo/js/archive/1.3.1/draggableConnectorsDemo.js b/archive/1.3.9/demo/js/archive/1.3.1/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.1/draggableConnectorsDemo.js rename to archive/1.3.9/demo/js/archive/1.3.1/draggableConnectorsDemo.js diff --git a/build/1.3.9/demo/js/archive/1.3.1/dynamicAnchorsDemo.js b/archive/1.3.9/demo/js/archive/1.3.1/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.1/dynamicAnchorsDemo.js rename to archive/1.3.9/demo/js/archive/1.3.1/dynamicAnchorsDemo.js diff --git a/build/1.3.9/demo/js/archive/1.3.1/flowchartConnectorsDemo.js b/archive/1.3.9/demo/js/archive/1.3.1/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.1/flowchartConnectorsDemo.js rename to archive/1.3.9/demo/js/archive/1.3.1/flowchartConnectorsDemo.js diff --git a/build/1.3.9/demo/js/archive/1.3.2/anchorDemo.js b/archive/1.3.9/demo/js/archive/1.3.2/anchorDemo.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.2/anchorDemo.js rename to archive/1.3.9/demo/js/archive/1.3.2/anchorDemo.js diff --git a/build/1.3.9/demo/js/archive/1.3.2/chartDemo.js b/archive/1.3.9/demo/js/archive/1.3.2/chartDemo.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.2/chartDemo.js rename to archive/1.3.9/demo/js/archive/1.3.2/chartDemo.js diff --git a/build/1.3.9/demo/js/archive/1.3.2/demo-helper-jquery.js b/archive/1.3.9/demo/js/archive/1.3.2/demo-helper-jquery.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.2/demo-helper-jquery.js rename to archive/1.3.9/demo/js/archive/1.3.2/demo-helper-jquery.js diff --git a/build/1.3.9/demo/js/archive/1.3.2/demo-helper-mootools.js b/archive/1.3.9/demo/js/archive/1.3.2/demo-helper-mootools.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.2/demo-helper-mootools.js rename to archive/1.3.9/demo/js/archive/1.3.2/demo-helper-mootools.js diff --git a/build/1.3.9/demo/js/archive/1.3.2/demo-helper-yui3.js b/archive/1.3.9/demo/js/archive/1.3.2/demo-helper-yui3.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.2/demo-helper-yui3.js rename to archive/1.3.9/demo/js/archive/1.3.2/demo-helper-yui3.js diff --git a/build/1.3.9/demo/js/archive/1.3.2/demo.js b/archive/1.3.9/demo/js/archive/1.3.2/demo.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.2/demo.js rename to archive/1.3.9/demo/js/archive/1.3.2/demo.js diff --git a/build/1.3.9/demo/js/archive/1.3.2/dragAnimDemo-jquery.js b/archive/1.3.9/demo/js/archive/1.3.2/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.2/dragAnimDemo-jquery.js rename to archive/1.3.9/demo/js/archive/1.3.2/dragAnimDemo-jquery.js diff --git a/build/1.3.9/demo/js/archive/1.3.2/dragAnimDemo-mootools.js b/archive/1.3.9/demo/js/archive/1.3.2/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.2/dragAnimDemo-mootools.js rename to archive/1.3.9/demo/js/archive/1.3.2/dragAnimDemo-mootools.js diff --git a/build/1.3.9/demo/js/archive/1.3.2/dragAnimDemo-yui3.js b/archive/1.3.9/demo/js/archive/1.3.2/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.2/dragAnimDemo-yui3.js rename to archive/1.3.9/demo/js/archive/1.3.2/dragAnimDemo-yui3.js diff --git a/build/1.3.9/demo/js/archive/1.3.2/dragAnimDemo.js b/archive/1.3.9/demo/js/archive/1.3.2/dragAnimDemo.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.2/dragAnimDemo.js rename to archive/1.3.9/demo/js/archive/1.3.2/dragAnimDemo.js diff --git a/build/1.3.9/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js b/archive/1.3.9/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js rename to archive/1.3.9/demo/js/archive/1.3.2/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.9/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js b/archive/1.3.9/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js rename to archive/1.3.9/demo/js/archive/1.3.2/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.9/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js b/archive/1.3.9/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js rename to archive/1.3.9/demo/js/archive/1.3.2/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.9/demo/js/archive/1.3.2/draggableConnectorsDemo.js b/archive/1.3.9/demo/js/archive/1.3.2/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.2/draggableConnectorsDemo.js rename to archive/1.3.9/demo/js/archive/1.3.2/draggableConnectorsDemo.js diff --git a/build/1.3.9/demo/js/archive/1.3.2/dynamicAnchorsDemo.js b/archive/1.3.9/demo/js/archive/1.3.2/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.2/dynamicAnchorsDemo.js rename to archive/1.3.9/demo/js/archive/1.3.2/dynamicAnchorsDemo.js diff --git a/build/1.3.9/demo/js/archive/1.3.2/flowchartConnectorsDemo.js b/archive/1.3.9/demo/js/archive/1.3.2/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.2/flowchartConnectorsDemo.js rename to archive/1.3.9/demo/js/archive/1.3.2/flowchartConnectorsDemo.js diff --git a/build/1.3.9/demo/js/archive/1.3.3/anchorDemo.js b/archive/1.3.9/demo/js/archive/1.3.3/anchorDemo.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.3/anchorDemo.js rename to archive/1.3.9/demo/js/archive/1.3.3/anchorDemo.js diff --git a/build/1.3.9/demo/js/archive/1.3.3/chartDemo.js b/archive/1.3.9/demo/js/archive/1.3.3/chartDemo.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.3/chartDemo.js rename to archive/1.3.9/demo/js/archive/1.3.3/chartDemo.js diff --git a/build/1.3.9/demo/js/archive/1.3.3/demo-helper-jquery.js b/archive/1.3.9/demo/js/archive/1.3.3/demo-helper-jquery.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.3/demo-helper-jquery.js rename to archive/1.3.9/demo/js/archive/1.3.3/demo-helper-jquery.js diff --git a/build/1.3.9/demo/js/archive/1.3.3/demo-helper-mootools.js b/archive/1.3.9/demo/js/archive/1.3.3/demo-helper-mootools.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.3/demo-helper-mootools.js rename to archive/1.3.9/demo/js/archive/1.3.3/demo-helper-mootools.js diff --git a/build/1.3.9/demo/js/archive/1.3.3/demo-helper-yui3.js b/archive/1.3.9/demo/js/archive/1.3.3/demo-helper-yui3.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.3/demo-helper-yui3.js rename to archive/1.3.9/demo/js/archive/1.3.3/demo-helper-yui3.js diff --git a/build/1.3.9/demo/js/archive/1.3.3/demo.js b/archive/1.3.9/demo/js/archive/1.3.3/demo.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.3/demo.js rename to archive/1.3.9/demo/js/archive/1.3.3/demo.js diff --git a/build/1.3.9/demo/js/archive/1.3.3/dragAnimDemo-jquery.js b/archive/1.3.9/demo/js/archive/1.3.3/dragAnimDemo-jquery.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.3/dragAnimDemo-jquery.js rename to archive/1.3.9/demo/js/archive/1.3.3/dragAnimDemo-jquery.js diff --git a/build/1.3.9/demo/js/archive/1.3.3/dragAnimDemo-mootools.js b/archive/1.3.9/demo/js/archive/1.3.3/dragAnimDemo-mootools.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.3/dragAnimDemo-mootools.js rename to archive/1.3.9/demo/js/archive/1.3.3/dragAnimDemo-mootools.js diff --git a/build/1.3.9/demo/js/archive/1.3.3/dragAnimDemo-yui3.js b/archive/1.3.9/demo/js/archive/1.3.3/dragAnimDemo-yui3.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.3/dragAnimDemo-yui3.js rename to archive/1.3.9/demo/js/archive/1.3.3/dragAnimDemo-yui3.js diff --git a/build/1.3.9/demo/js/archive/1.3.3/dragAnimDemo.js b/archive/1.3.9/demo/js/archive/1.3.3/dragAnimDemo.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.3/dragAnimDemo.js rename to archive/1.3.9/demo/js/archive/1.3.3/dragAnimDemo.js diff --git a/build/1.3.9/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js b/archive/1.3.9/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js rename to archive/1.3.9/demo/js/archive/1.3.3/draggableConnectorsDemo-jquery.js diff --git a/build/1.3.9/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js b/archive/1.3.9/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js rename to archive/1.3.9/demo/js/archive/1.3.3/draggableConnectorsDemo-mootools.js diff --git a/build/1.3.9/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js b/archive/1.3.9/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js rename to archive/1.3.9/demo/js/archive/1.3.3/draggableConnectorsDemo-yui3.js diff --git a/build/1.3.9/demo/js/archive/1.3.3/draggableConnectorsDemo.js b/archive/1.3.9/demo/js/archive/1.3.3/draggableConnectorsDemo.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.3/draggableConnectorsDemo.js rename to archive/1.3.9/demo/js/archive/1.3.3/draggableConnectorsDemo.js diff --git a/build/1.3.9/demo/js/archive/1.3.3/dynamicAnchorsDemo.js b/archive/1.3.9/demo/js/archive/1.3.3/dynamicAnchorsDemo.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.3/dynamicAnchorsDemo.js rename to archive/1.3.9/demo/js/archive/1.3.3/dynamicAnchorsDemo.js diff --git a/build/1.3.9/demo/js/archive/1.3.3/flowchartConnectorsDemo.js b/archive/1.3.9/demo/js/archive/1.3.3/flowchartConnectorsDemo.js similarity index 100% rename from build/1.3.9/demo/js/archive/1.3.3/flowchartConnectorsDemo.js rename to archive/1.3.9/demo/js/archive/1.3.3/flowchartConnectorsDemo.js diff --git a/archive/1.3.9/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js b/archive/1.3.9/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js new file mode 100644 index 000000000..c89b7d07e --- /dev/null +++ b/archive/1.3.9/demo/js/archive/1.3.3/jquery.jsPlumb-1.3.3-all-min.js @@ -0,0 +1 @@ +(function(){var r=!!document.createElement("canvas").getContext;var d=!!window.SVGAngle||document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1");var a=!(r|d);var l=function(y,z,w,C){var B=function(E,D){if(E===D){return true}else{if(typeof E=="object"&&typeof D=="object"){var F=true;for(var v in E){if(!B(E[v],D[v])){F=false;break}}for(var v in D){if(!B(D[v],E[v])){F=false;break}}return F}}};for(var A=+w||0,x=y.length;A=0){delete (ax[ay]);ax.splice(ay,1);return true}}}return false},v=function(ay,ax){return I(ay,function(az,aA){ag[aA]=ax;if(i.CurrentLibrary.isDragSupported(az)){i.CurrentLibrary.setDraggable(az,ax)}})},ao=function(az,aA,ax){aA=aA==="block";var ay=null;if(ax){if(aA){ay=function(aC){aC.setVisible(true,true,true)}}else{ay=function(aC){aC.setVisible(false,true,true)}}}var aB=c(az,"id");U(aB,function(aD){if(aA&&ax){var aC=aD.sourceId===aB?1:0;if(aD.endpoints[aC].isVisible()){aD.setVisible(true)}}else{aD.setVisible(aA)}},ay)},N=function(ax){return I(ax,function(az,ay){var aA=ag[ay]==null?Y:ag[ay];aA=!aA;ag[ay]=aA;i.CurrentLibrary.setDraggable(az,aA);return aA})},x=function(ax,az){var ay=null;if(az){ay=function(aA){var aB=aA.isVisible();aA.setVisible(!aB)}}U(ax,function(aB){var aA=aB.isVisible();aB.setVisible(!aA)},ay)},E=function(aC){var aA=aC.timestamp,ax=aC.recalc,aB=aC.offset,ay=aC.elId;if(!ax){if(aA&&aA===C[ay]){return V[ay]}}if(ax||aB==null){var az=u(ay);if(az!=null){P[ay]=b(az);V[ay]=n(az);C[ay]=aA}}else{V[ay]=aB}return V[ay]},aw=function(ax,ay){var az=u(ax);var aA=c(az,"id");if(!aA||aA=="undefined"){if(arguments.length==2&&arguments[1]!=undefined){aA=ay}else{aA="jsPlumb_"+ar()}e(az,"id",aA)}return aA},am=function(az,ax,ay){az=az||function(){};ax=ax||function(){};return function(){var aA=null;try{aA=ax.apply(this,arguments)}catch(aB){k(D,"jsPlumb function failed : "+aB)}if(ay==null||(aA!==ay)){try{az.apply(this,arguments)}catch(aB){k(D,"wrapped function failed : "+aB)}}return aA}};this.connectorClass="_jsPlumb_connector";this.endpointClass="_jsPlumb_endpoint";this.overlayClass="_jsPlumb_overlay";this.Anchors={};this.Connectors={canvas:{},svg:{},vml:{}};this.Endpoints={canvas:{},svg:{},vml:{}};this.Overlays={canvas:{},svg:{},vml:{}};this.addEndpoint=function(az,aA,aJ){aJ=aJ||{};var ay=i.extend({},aJ);i.extend(ay,aA);ay.endpoint=ay.endpoint||D.Defaults.Endpoint||i.Defaults.Endpoint;ay.paintStyle=ay.paintStyle||D.Defaults.EndpointStyle||i.Defaults.EndpointStyle;az=w(az);var aB=[],aE=az.length&&az.constructor!=String?az:[az];for(var aC=0;aC0?l(aK,aJ)!=-1:true},aB=aE.length>1?{}:[],aH=function(aK,aL){if(aE.length>1){var aJ=aB[aK];if(aJ==null){aJ=[];aB[aK]=aJ}aJ.push(aL)}else{aB.push(aL)}};for(var aA in L){if(ay(aE,aA)){for(var az=0;az=4){ay.orientation=[arguments[2],arguments[3]]}if(arguments.length==6){ay.offsets=[arguments[4],arguments[5]]}}var aD=new ac(ay);aD.clone=function(){return new ac(ay)};return aD};this.makeAnchors=function(ay){var az=[];for(var ax=0;ax0?aG[0]:null,aC=aG.length>0?0:-1,aF=this,aB=function(aJ,aH,aN,aM,aI){var aL=aM[0]+(aJ.x*aI[0]),aK=aM[1]+(aJ.y*aI[1]);return Math.sqrt(Math.pow(aH-aL,2)+Math.pow(aN-aK,2))},ax=ay||function(aR,aI,aJ,aK,aH){var aM=aJ[0]+(aK[0]/2),aL=aJ[1]+(aK[1]/2);var aO=-1,aQ=Infinity;for(var aN=0;aN=0?aG.overlays[aS]:null};this.hideOverlay=function(aT){var aS=aG.getOverlay(aT);if(aS){aS.hide()}};this.showOverlay=function(aT){var aS=aG.getOverlay(aT);if(aS){aS.show()}};this.removeAllOverlays=function(){aG.overlays.splice(0,aG.overlays.length);aG.repaint()};this.removeOverlay=function(aT){var aS=aQ(aT);if(aS!=-1){aG.overlays.splice(aS,1)}};this.removeOverlays=function(){for(var aS=0;aSa6){a6=a8}}}var aZ=this.connector.compute(a1,aY,this.endpoints[a7].anchor,this.endpoints[a3].anchor,aG.paintStyleInUse.lineWidth,a6);aG.connector.paint(aZ,aG.paintStyleInUse);for(var aX=0;aX=0){aN.connections.splice(aZ,1);if(!a2){var a1=a0.endpoints[0]==aN?a0.endpoints[1]:a0.endpoints[0];a1.detach(a0,true);if(a0.endpointToDeleteOnDetach&&a0.endpointToDeleteOnDetach.connections.length==0){i.deleteEndpoint(a0.endpointToDeleteOnDetach)}}Q(a0.connector.getDisplayElements(),a0.parent);M(L,a0.scope,a0);if(!a2){ad(a0)}}};this.detachAll=function(){while(aN.connections.length>0){aN.detach(aN.connections[0])}};this.detachFrom=function(a0){var a1=[];for(var aZ=0;aZ=0){aN.connections.splice(aZ,1)}};this.getElement=function(){return aM};this.getUuid=function(){return aH};this.makeInPlaceCopy=function(){return an({anchor:aN.anchor,source:aM,paintStyle:this.paintStyle,endpoint:aL})};this.isConnectedTo=function(a1){var a0=false;if(a1){for(var aZ=0;aZ0){var a9=aK(a2.elementWithPrecedence);var bb=a9.endpoints[0]==aN?1:0;var a4=bb==0?a9.sourceId:a9.targetId;var a8=V[a4],ba=P[a4];a0.txy=[a8.left,a8.top];a0.twh=ba;a0.tElement=a9.endpoints[bb]}}a5=aN.anchor.compute(a0)}var a7=aL.compute(a5,aN.anchor.getOrientation(),aN.paintStyleInUse,a3||aN.paintStyleInUse);aL.paint(a7,aN.paintStyleInUse,aN.anchor);aN.timestamp=a6}};this.repaint=this.paint;this.removeConnection=this.detach;if(aY.isSource&&i.CurrentLibrary.isDragSupported(aM)){var aS=null,aO=null,aR=null,ax=false,aA=null;var aC=function(){aR=aN.connectorSelector();if(aN.isFull()&&!aI){return false}E({elId:aE});aB=aN.makeInPlaceCopy();aB.paint();aS=document.createElement("div");aS.style.position="absolute";var a6=u(aS);R(aS,aN.parent);var a0=aw(a6);var a7=u(aB.canvas),a5=i.CurrentLibrary.getOffset(a7),a2=aB.canvas.offsetParent!=null?aB.canvas.offsetParent.tagName.toLowerCase()==="body"?{left:0,top:0}:n(aB.canvas.offsetParent):{left:0,top:0};i.CurrentLibrary.setOffset(aS,{left:a5.left-a2.left,top:a5.top-a2.top});E({elId:a0});e(u(aN.canvas),"dragId",a0);e(u(aN.canvas),"elId",aE);var a8=new H({reference:aN.anchor,referenceCanvas:aN.canvas});aW=an({paintStyle:aN.paintStyle,endpoint:aL,anchor:a8,source:a6});if(aR==null){aN.anchor.locked=true;aR=au({sourceEndpoint:aN,targetEndpoint:aW,source:u(aM),target:u(aS),anchors:[aN.anchor,a8],paintStyle:aY.connectorStyle,hoverPaintStyle:aY.connectorHoverStyle,connector:aY.connector,overlays:aY.connectorOverlays});aR.connector.setHover(false)}else{ax=true;aR.connector.setHover(false);aD(u(aB.canvas));var a1=aR.sourceId==aE?0:1;aR.floatingAnchorIndex=a1;aN.detachFromConnection(aR);var a4=u(aN.canvas);var a3=i.CurrentLibrary.getDragScope(a4);e(a4,"originalScope",a3);var aZ=i.CurrentLibrary.getDropScope(a4);i.CurrentLibrary.setDragScope(a4,aZ);if(a1==0){aA=[aR.source,aR.sourceId,aV,a3];aR.source=u(aS);aR.sourceId=a0}else{aA=[aR.target,aR.targetId,aV,a3];aR.target=u(aS);aR.targetId=a0}aR.endpoints[a1==0?1:0].anchor.locked=true;aR.suspendedEndpoint=aR.endpoints[a1];aR.endpoints[a1]=aW}Z[a0]=aR;aW.addConnection(aR);S(ah,a0,aW);D.currentlyDragging=true};var ay=i.CurrentLibrary,aU=aY.dragOptions||{},aP=i.extend({},ay.defaultDragOptions),aQ=ay.dragEvents.start,aX=ay.dragEvents.stop,aG=ay.dragEvents.drag;aU=i.extend(aP,aU);aU.scope=aU.scope||aN.scope;aU[aQ]=am(aU[aQ],aC);aU[aG]=am(aU[aG],function(){var aZ=i.CurrentLibrary.getUIPosition(arguments);i.CurrentLibrary.setOffset(aS,aZ);at(u(aS),aZ)});aU[aX]=am(aU[aX],function(){M(ah,aO,aW);Q([aS,aW.canvas],aM);ae(aB.canvas,aM);var aZ=aR.floatingAnchorIndex==null?1:aR.floatingAnchorIndex;aR.endpoints[aZ==0?1:0].anchor.locked=false;if(aR.endpoints[aZ]==aW){if(ax&&aR.suspendedEndpoint){if(aZ==0){aR.source=aA[0];aR.sourceId=aA[1]}else{aR.target=aA[0];aR.targetId=aA[1]}i.CurrentLibrary.setDragScope(aA[2],aA[3]);aR.endpoints[aZ]=aR.suspendedEndpoint;if(aJ){aR.floatingAnchorIndex=null;aR.suspendedEndpoint.addConnection(aR);i.repaint(aA[1])}else{aR.endpoints[aZ==0?1:0].detach(aR)}}else{Q(aR.connector.getDisplayElements(),aN.parent);aN.detachFromConnection(aR)}}aN.anchor.locked=false;aN.paint();aR.setHover(false);aR.repaint();aR=null;delete aB;delete ah[aW.elementId];aW=null;delete aW;D.currentlyDragging=false});var aV=u(aN.canvas);i.CurrentLibrary.initDraggable(aV,aU)}var aD=function(a2){if(aY.isTarget&&i.CurrentLibrary.isDropSupported(aM)){var aZ=aY.dropOptions||D.Defaults.DropOptions||i.Defaults.DropOptions;aZ=i.extend({},aZ);aZ.scope=aZ.scope||aN.scope;var a5=null;var a3=i.CurrentLibrary.dragEvents.drop;var a4=i.CurrentLibrary.dragEvents.over;var a0=i.CurrentLibrary.dragEvents.out;var a1=function(){var be=u(i.CurrentLibrary.getDragObject(arguments));var a6=c(be,"dragId");var a8=c(be,"elId");var bd=c(be,"originalScope");if(bd){i.CurrentLibrary.setDragScope(be,bd)}var ba=Z[a6];var bb=ba.floatingAnchorIndex==null?1:ba.floatingAnchorIndex,bc=bb==0?1:0;if(!aN.isFull()&&!(bb==0&&!aN.isSource)&&!(bb==1&&!aN.isTarget)){if(bb==0){ba.source=aM;ba.sourceId=aE}else{ba.target=aM;ba.targetId=aE}ba.endpoints[bb].detachFromConnection(ba);if(ba.suspendedEndpoint){ba.suspendedEndpoint.detachFromConnection(ba)}ba.endpoints[bb]=aN;aN.addConnection(ba);if(!ba.suspendedEndpoint){S(L,ba.scope,ba);O(aM,aY.draggable,{})}else{var a9=ba.suspendedEndpoint.getElement(),a7=ba.suspendedEndpoint.elementId;D.fire("jsPlumbConnectionDetached",{source:bb==0?a9:ba.source,target:bb==1?a9:ba.target,sourceId:bb==0?a7:ba.sourceId,targetId:bb==1?a7:ba.targetId,sourceEndpoint:bb==0?ba.suspendedEndpoint:ba.endpoints[0],targetEndpoint:bb==1?ba.suspendedEndpoint:ba.endpoints[1],connection:ba})}i.repaint(a8);D.fire("jsPlumbConnection",{source:ba.source,target:ba.target,sourceId:ba.sourceId,targetId:ba.targetId,sourceEndpoint:ba.endpoints[0],targetEndpoint:ba.endpoints[1],connection:ba})}D.currentlyDragging=false;delete Z[a6]};aZ[a3]=am(aZ[a3],a1);aZ[a4]=am(aZ[a4],function(){var a7=i.CurrentLibrary.getDragObject(arguments);var a9=c(u(a7),"dragId");var a8=Z[a9];if(a8!=null){var a6=a8.floatingAnchorIndex==null?1:a8.floatingAnchorIndex;a8.endpoints[a6].anchor.over(aN.anchor)}});aZ[a0]=am(aZ[a0],function(){var a7=i.CurrentLibrary.getDragObject(arguments),a9=c(u(a7),"dragId"),a8=Z[a9];if(a8!=null){var a6=a8.floatingAnchorIndex==null?1:a8.floatingAnchorIndex;a8.endpoints[a6].anchor.out()}});i.CurrentLibrary.initDroppable(a2,aZ)}};aD(u(aN.canvas));return aN}};var i=window.jsPlumb=new q();i.getInstance=function(w){var v=new q(w);v.init();return v};var m=function(v,A,z,w){return function(){return i.makeAnchor(v,A,z,w)}};i.Anchors.TopCenter=m(0.5,0,0,-1);i.Anchors.BottomCenter=m(0.5,1,0,1);i.Anchors.LeftMiddle=m(0,0.5,-1,0);i.Anchors.RightMiddle=m(1,0.5,1,0);i.Anchors.Center=m(0.5,0.5,0,0);i.Anchors.TopRight=m(1,0,0,-1);i.Anchors.BottomRight=m(1,1,0,1);i.Anchors.TopLeft=m(0,0,0,-1);i.Anchors.BottomLeft=m(0,1,0,1);i.Defaults.DynamicAnchors=function(){return i.makeAnchors(["TopCenter","RightMiddle","BottomCenter","LeftMiddle"])};i.Anchors.AutoDefault=function(){return i.makeDynamicAnchor(i.Defaults.DynamicAnchors())}})();(function(){jsPlumb.DOMElementComponent=function(b){jsPlumb.jsPlumbUIComponent.apply(this,arguments);this.mousemove=this.dblclick=this.click=this.mousedown=this.mouseup=function(c){}};jsPlumb.Connectors.Straight=function(){this.type="Straight";var o=this;var h=null;var d,i,m,l,k,e,n,g,f,c,b;this.compute=function(s,G,C,p,z,r){var F=Math.abs(s[0]-G[0]);var v=Math.abs(s[1]-G[1]);var A=false,t=false;var u=0.45*F,q=0.45*v;F*=1.9;v*=1.9;var D=Math.min(s[0],G[0])-u;var B=Math.min(s[1],G[1])-q;var E=Math.max(2*z,r);if(F0?1:-1;var u=Math.abs(v*Math.sin(e));if(f>b){u=u*-1}var q=Math.abs(v*Math.cos(e));if(g>c){q=q*-1}return{x:t.x+(s*q),y:t.y+(s*u)}};this.perpendicularToPathAt=function(t,u,z){var v=o.pointAlongPathFrom(t,z);var s=o.gradientAtPoint(v.location);var r=Math.atan(-1/s);var w=u/2*Math.sin(r);var q=u/2*Math.cos(r);return[{x:v.x+q,y:v.y+w},{x:v.x-q,y:v.y-w}]}};jsPlumb.Connectors.Bezier=function(f){var p=this;f=f||{};this.majorAnchor=f.curviness||150;this.minorAnchor=10;var i=null;this.type="Bezier";this._findControlPoint=function(z,q,u,x,r){var w=x.getOrientation(),y=r.getOrientation();var t=w[0]!=y[0]||w[1]==y[1];var s=[];var A=p.majorAnchor,v=p.minorAnchor;if(!t){if(w[0]==0){s.push(q[0]m){m=v}if(y<0){e+=y;var z=Math.abs(y);m+=z;o[0]+=z;k+=z;c+=z;n[0]+=z}var H=Math.min(g,b);var F=Math.min(o[1],n[1]);var u=Math.min(H,F);var A=Math.max(g,b);var x=Math.max(o[1],n[1]);var s=Math.max(A,x);if(s>h){h=s}if(u<0){d+=u;var w=Math.abs(u);h+=w;o[1]+=w;g+=w;b+=w;n[1]+=w}if(E&&m=t){r=u;s=(t-m[u][0])/b[u];break}}return{segment:k[r],proportion:s,index:r}};this.compute=function(R,z,Q,s,r,I){k=[];i=[];b=[];h=[];segmentProportionals=[];e=z[0]u[0]?u[0]+((1-y)*t)-z:u[2]+(y*t)+z,y:r==0?u[3]:u[3]>u[1]?u[1]+((1-y)*t)-z:u[3]+(y*t)+z,segmentInfo:w};return x};this.perpendicularToPathAt=function(u,v,A){var w=o.pointAlongPathFrom(u,A);var t=i[w.segmentInfo.index];var s=Math.atan(-1/t);var z=v/2*Math.sin(s);var r=v/2*Math.cos(s);return[{x:w.x+r,y:w.y+z},{x:w.x-r,y:w.y-z}]}};jsPlumb.Endpoints.Dot=function(c){this.type="Dot";var b=this;c=c||{};this.radius=c.radius||10;this.defaultOffset=0.5*this.radius;this.defaultInnerRadius=this.radius/3;this.compute=function(h,e,k,g){var f=k.radius||b.radius;var d=h[0]-f;var i=h[1]-f;return[d,i,f*2,f*2,f]}};jsPlumb.Endpoints.Rectangle=function(c){this.type="Rectangle";var b=this;c=c||{};this.width=c.width||20;this.height=c.height||20;this.compute=function(i,f,l,h){var g=l.width||b.width;var e=l.height||b.height;var d=i[0]-(g/2);var k=i[1]-(e/2);return[d,k,g,e]}};jsPlumb.Endpoints.Image=function(e){this.type="Image";jsPlumb.DOMElementComponent.apply(this,arguments);var b=this,d=false;this.img=new Image();b.ready=false;this.img.onload=function(){b.ready=true};this.img.src=e.src||e.url;this.compute=function(h,f,i,g){b.anchorPoint=h;if(b.ready){return[h[0]-b.img.width/2,h[1]-b.img.height/2,b.img.width,b.img.height]}else{return[0,0,0,0]}};b.canvas=document.createElement("img"),d=false;b.canvas.style.margin=0;b.canvas.style.padding=0;b.canvas.style.outline=0;b.canvas.style.position="absolute";b.canvas.className=jsPlumb.endpointClass;jsPlumb.appendElement(b.canvas,e.parent);b.attachListeners(b.canvas,b);var c=function(l,k,h){if(!d){b.canvas.setAttribute("src",b.img.src);d=true}var i=b.img.width,g=b.img.height,f=b.anchorPoint[0]-(i/2),m=b.anchorPoint[1]-(g/2);jsPlumb.sizeCanvas(b.canvas,f,m,i,g)};this.paint=function(h,g,f){if(b.ready){c(h,g,f)}else{window.setTimeout(function(){b.paint(h,g,f)},200)}}};jsPlumb.Endpoints.Blank=function(c){var b=this;this.type="Blank";jsPlumb.DOMElementComponent.apply(this,arguments);this.compute=function(){return[0,0,10,0]};b.canvas=document.createElement("div");b.canvas.style.display="block";b.canvas.style.width="1px";b.canvas.style.height="1px";b.canvas.style.background="transparent";b.canvas.style.position="absolute";jsPlumb.appendElement(b.canvas,c.parent);this.paint=function(){}};jsPlumb.Endpoints.Triangle=function(b){this.type="Triangle";b=b||{};b.width=b.width||55;param.height=b.height||55;this.width=b.width;this.height=b.height;this.compute=function(h,e,k,g){var f=k.width||self.width;var d=k.height||self.height;var c=h[0]-(f/2);var i=h[1]-(d/2);return[c,i,f,d]}};var a=function(){var c=true,b=this;this.setVisible=function(d){c=d;b.connection.repaint()};this.isVisible=function(){return c};this.hide=function(){b.setVisible(false)};this.show=function(){b.setVisible(true)}};jsPlumb.Overlays.Arrow=function(g){this.type="Arrow";a.apply(this);g=g||{};var c=this;this.length=g.length||20;this.width=g.width||20;this.id=g.id;this.connection=g.connection;var f=(g.direction||1)<0?-1:1;var d=g.paintStyle||{lineWidth:1};this.loc=g.location==null?0.5:g.location;var b=g.foldback||0.623;var e=function(h,k){if(b==0.5){return h.pointOnPath(k)}else{var i=0.5-b;return h.pointAlongPathFrom(k,f*c.length*i)}};this.computeMaxSize=function(){return c.width*1.5};this.draw=function(l,r,x){var z=l.pointAlongPathFrom(c.loc,f*(c.length/2));var u=l.pointAlongPathFrom(c.loc,-1*f*(c.length/2)),C=u.x,B=u.y;var s=l.perpendicularToPathAt(c.loc,c.width,-1*f*(c.length/2));var k=e(l,c.loc);if(c.loc==1){var i=l.pointOnPath(c.loc);var w=(i.x-z.x)*f,v=(i.y-z.y)*f;k.x+=w;k.y+=v;u.x+=w;u.y+=v;s[0].x+=w;s[0].y+=v;s[1].x+=w;s[1].y+=v;z.x+=w;z.y+=v}if(c.loc==0){var i=l.pointOnPath(c.loc);var t=b>1?k:{x:s[0].x+((s[1].x-s[0].x)/2),y:s[0].y+((s[1].y-s[0].y)/2)};var w=(i.x-t.x)*f,v=(i.y-t.y)*f;k.x+=w;k.y+=v;u.x+=w;u.y+=v;s[0].x+=w;s[0].y+=v;s[1].x+=w;s[1].y+=v;z.x+=w;z.y+=v}var p=Math.min(z.x,s[0].x,s[1].x);var o=Math.max(z.x,s[0].x,s[1].x);var n=Math.min(z.y,s[0].y,s[1].y);var m=Math.max(z.y,s[0].y,s[1].y);var A={hxy:z,tail:s,cxy:k},y=d.strokeStyle||r.strokeStyle,q=d.fillStyle||r.strokeStyle,h=d.lineWidth||r.lineWidth;c.paint(l,A,h,y,q,x);return[p,o,n,m]}};jsPlumb.Overlays.PlainArrow=function(c){c=c||{};var b=jsPlumb.extend(c,{foldback:1});jsPlumb.Overlays.Arrow.call(this,b);this.type="PlainArrow"};jsPlumb.Overlays.Diamond=function(d){d=d||{};var b=d.length||40;var c=jsPlumb.extend(d,{length:b/2,foldback:2});jsPlumb.Overlays.Arrow.call(this,c);this.type="Diamond"};jsPlumb.Overlays.Label=function(e){this.type="Label";jsPlumb.DOMElementComponent.apply(this,arguments);a.apply(this);this.labelStyle=e.labelStyle||jsPlumb.Defaults.LabelStyle;this.labelStyle.font=this.labelStyle.font||"12px sans-serif";this.label=e.label||"banana";this.connection=e.connection;this.id=e.id;var l=this;var i=null,f=null,d=null,c=null;this.location=e.location||0.5;this.cachedDimensions=null;var k=false,d=null,b=document.createElement("div");b.style.position="absolute";b.style.font=l.labelStyle.font;b.style.color=l.labelStyle.color||"black";if(l.labelStyle.fillStyle){b.style.background=l.labelStyle.fillStyle}if(l.labelStyle.borderWidth>0){var h=l.labelStyle.borderStyle?l.labelStyle.borderStyle:"black";b.style.border=l.labelStyle.borderWidth+"px solid "+h}if(l.labelStyle.padding){b.style.padding=l.labelStyle.padding}var g=e._jsPlumb.overlayClass+" "+(l.labelStyle.cssClass?l.labelStyle.cssClass:e.cssClass?e.cssClass:"");b.className=g;jsPlumb.appendElement(b,e.connection.parent);jsPlumb.getId(b);l.attachListeners(b,l);var m=l.setVisible;l.setVisible=function(n){m(n);b.style.display=n?"block":"none"};this.paint=function(n,p,o){if(!k){n.appendDisplayElement(b);l.attachListeners(b,n);k=true}b.style.left=(o[0]+p.minx)+"px";b.style.top=(o[1]+p.miny)+"px"};this.getTextDimensions=function(n){d=typeof l.label=="function"?l.label(l):l.label;b.innerHTML=d.replace(/\r\n/g,"
");var p=jsPlumb.CurrentLibrary.getElementObject(b),o=jsPlumb.CurrentLibrary.getSize(p);return{width:o[0],height:o[1]}};this.computeMaxSize=function(n){var o=l.getTextDimensions(n);return o.width?Math.max(o.width,o.height)*1.5:0};this.draw=function(p,r,q){var t=l.getTextDimensions(p);if(t.width!=null){var s=p.pointOnPath(l.location);var o=s.x-(t.width/2);var n=s.y-(t.height/2);l.paint(p,{minx:o,miny:n,td:t,cxy:s},q);return[o,o+t.width,n,n+t.height]}else{return[0,0,0,0]}}}})();(function(){var h={"stroke-linejoin":"joinstyle",joinstyle:"joinstyle",endcap:"endcap",miterlimit:"miterlimit"};if(document.createStyleSheet){document.createStyleSheet().addRule(".jsplumb_vml","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:textbox","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:oval","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:rect","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:stroke","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:shape","behavior:url(#default#VML);position:absolute;");document.namespaces.add("jsplumb","urn:schemas-microsoft-com:vml")}var c=1000,d=function(p,q){for(var n in q){p[n]=q[n]}},m=function(n,q,r){r=r||{};var p=document.createElement("jsplumb:"+n);p.className=(r["class"]?r["class"]+" ":"")+"jsplumb_vml";l(p,q);d(p,r);return p},l=function(p,n){p.style.left=n[0]+"px";p.style.top=n[1]+"px";p.style.width=n[2]+"px";p.style.height=n[3]+"px";p.style.position="absolute"},g=function(n){return Math.floor(n*c)},a=function(p,n){var v=p,u=function(o){return o.length==1?"0"+o:o},q=function(o){return u(Number(o).toString(16))},r=/(rgb[a]?\()(.*)(\))/;if(p.match(r)){var t=p.match(r)[2].split(",");v="#"+q(t[0])+q(t[1])+q(t[2]);if(!n&&t.length==4){v=v+q(t[3])}}return v},f=function(s,r,p){var u={};if(r.strokeStyle){u.stroked="true";u.strokecolor=a(r.strokeStyle,true);u.strokeweight=r.lineWidth+"px"}else{u.stroked="false"}if(r.fillStyle){u.filled="true";u.fillcolor=a(r.fillStyle,true)}else{u.filled="false"}if(r.dashstyle){if(p.strokeNode==null){p.strokeNode=m("stroke",[0,0,0,0],{dashstyle:r.dashstyle});s.appendChild(p.strokeNode)}else{p.strokeNode.dashstyle=r.dashstyle}}else{if(r["stroke-dasharray"]&&r.lineWidth){var o=r["stroke-dasharray"].indexOf(",")==-1?" ":",",t=r["stroke-dasharray"].split(o),n="";for(var q=0;q0&&F>0&&z=z&&B[2]<=F&&B[3]>=F)){return true}}var D=v.canvas.getContext("2d").getImageData(parseInt(z),parseInt(F),1,1);return D.data[0]!=0||D.data[1]!=0||D.data[2]!=0||D.data[3]!=0}return false};var u=false;var t=false,y=null,x=false;var w=function(A,z){return A!=null&&f(A,z)};this.mousemove=function(C){var E=e(C),B=i(C),A=document.elementFromPoint(B[0],B[1]),D=w(A,"_jsPlumb_overlay");var z=o==null&&(w(A,"_jsPlumb_endpoint")||w(A,"_jsPlumb_connector"));if(!u&&z&&v._over(C)){u=true;v.fire("mouseenter",v,C);return true}else{if(u&&(!v._over(C)||!z)&&!D){u=false;v.fire("mouseexit",v,C)}}v.fire("mousemove",v,C)};this.click=function(z){if(u&&v._over(z)&&!x){v.fire("click",v,z)}x=false};this.dblclick=function(z){if(u&&v._over(z)&&!x){v.fire("dblclick",v,z)}x=false};this.mousedown=function(z){if(v._over(z)&&!t){t=true;y=m(s(v.canvas));v.fire("mousedown",v,z)}};this.mouseup=function(z){t=false;v.fire("mouseup",v,z)}};var p=function(u){var t=document.createElement("canvas");jsPlumb.appendElement(t,u.parent);t.style.position="absolute";if(u["class"]){t.className=u["class"]}u._jsPlumb.getId(t,u.uuid);return t};var d=jsPlumb.CanvasConnector=function(x){n.apply(this,arguments);var t=function(B,z){u.ctx.save();jsPlumb.extend(u.ctx,z);if(z.gradient){var A=u.createGradient(B,u.ctx);for(var y=0;y0?1:-1}}var b={subtract:function(m,l){return{x:m.x-l.x,y:m.y-l.y}},dotProduct:function(m,l){return m.x*l.x+m.y*l.y},square:function(l){return Math.sqrt(l.x*l.x+l.y*l.y)},scale:function(m,l){return{x:m.x*l,y:m.y*l}}},d=Math.pow(2,-65),h=function(y,x){for(var s=[],v=x.length-1,r=2*v-1,t=[],w=[],o=[],p=[],q=[[1,0.6,0.3,0.1],[0.4,0.6,0.6,0.4],[0.1,0.3,0.6,1]],u=0;u<=v;u++){t[u]=b.subtract(x[u],y)}for(u=0;u<=v-1;u++){w[u]=b.subtract(x[u+1],x[u]);w[u]=b.scale(w[u],3)}for(u=0;u<=v-1;u++){for(var n=0;n<=v;n++){o[u]||(o[u]=[]);o[u][n]=b.dotProduct(w[u],t[n])}}for(u=0;u<=r;u++){p[u]||(p[u]=[]);p[u].y=0;p[u].x=parseFloat(u)/r}r=v-1;for(t=0;t<=v+r;t++){w=Math.min(t,v);for(u=Math.max(0,t-r);u<=w;u++){j=t-u;p[u+j].y+=o[j][u]*q[j][u]}}v=x.length-1;p=k(p,2*v-1,s,0);r=b.subtract(y,x[0]);o=b.square(r);for(u=q=0;u=64){y[0]=(E[0].x+E[D].x)/2;return 1}var s,r,p;v=E[0].y-E[D].y;w=E[D].x-E[0].x;A=E[0].x*E[D].y-E[D].x*E[0].y;t=max_distance_below=0;for(r=1;rt){t=p}else{if(p0?1:-1,r=null;n endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + endpointsByElement = {}, + endpointsByUUID = {}, + offsets = {}, + offsetTimestamps = {}, + floatingConnections = {}, + draggableStates = {}, + _mouseEventsEnabled = this.Defaults.MouseEventsEnabled, + _draggableByDefault = true, + canvasList = [], + sizes = [], + listeners = {}, // a map: keys are event types, values are lists of listeners. + DEFAULT_SCOPE = this.Defaults.Scope, + renderMode = null, // will be set in init() + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }, + + /** + * appends an element to some other element, which is calculated as follows: + * + * 1. if jsPlumb.Defaults.Container exists, use that element. + * 2. if the 'parent' parameter exists, use that. + * 3. otherwise just use the document body. + * + */ + _appendElement = function(el, parent) { + if (_currentInstance.Defaults.Container) + jsPlumb.CurrentLibrary.appendElement(el, _currentInstance.Defaults.Container); + else if (!parent) + document.body.appendChild(el); + else + jsPlumb.CurrentLibrary.appendElement(el, parent); + }, + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + _timestamp = function() { return "" + (new Date()).getTime(); }, + + /** + * YUI, for some reason, put the result of a Y.all call into an object that contains + * a '_nodes' array, instead of handing back an array-like object like the other + * libraries do. + */ + _convertYUICollection = function(c) { + return c._nodes ? c._nodes : c; + }, + + /** + * Draws an endpoint and its connections. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + _draw = function(element, ui, timestamp) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (!timestamp) timestamp = _timestamp(); + if (endpoints) { + _updateOffset( { elId : id, offset : ui, recalc : false, timestamp : timestamp }); // timestamp is checked against last update cache; it is + // valid for one paint cycle. + var myOffset = offsets[id], myWH = sizes[id]; + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + var l = endpoints[i].connections; + for ( var j = 0; j < l.length; j++) { + l[j].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); // ...paint each connection. + // then, check for dynamic endpoint; need to repaint it. + var oIdx = l[j].endpoints[0] == endpoints[i] ? 1 : 0, + otherEndpoint = l[j].endpoints[oIdx]; + if (otherEndpoint.anchor.isDynamic && !otherEndpoint.isFloating()) { + _updateOffset( { elId : otherEndpoint.elementId, timestamp : timestamp }); + otherEndpoint.paint({ elementWithPrecedence:id }); + // all the connections for the other endpoint now need to be repainted + for (var k = 0; k < otherEndpoint.connections.length; k++) { + if (otherEndpoint.connections[k] !== l) + otherEndpoint.connections[k].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); + } + } + } + } + } + }, + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }, + + /** + * gets an Endpoint by uuid. + */ + _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }, + + /** + * inits a draggable if it's not already initialised. + */ + _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element) && !jsPlumb.CurrentLibrary.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + } + } + }, + + _newConnection = function(params) { + var connectionFunc = jsPlumb.Defaults.ConnectionType || Connection, + endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint, + parent = jsPlumb.CurrentLibrary.getParent; + + if (params.container) + params["parent"] = params.container; + else { + if (params.sourceEndpoint) + params["parent"] = params.sourceEndpoint.parent; + else if (params.source.constructor == endpointFunc) + params["parent"] = params.source.parent; + else params["parent"] = parent(params.source); + } + + params["_jsPlumb"] = _currentInstance; + var con = new connectionFunc(params); + _eventFireProxy("click", "click", con); + _eventFireProxy("dblclick", "dblclick", con); + return con; + }, + + _eventFireProxy = function(event, proxyEvent, obj) { + obj.bind(event, function(originalObject, originalEvent) { + _currentInstance.fire(proxyEvent, obj, originalEvent); + }); + }, + + _newEndpoint = function(params) { + var endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint; + if (params.container) + params.parent = params.container; + else + params["parent"] = jsPlumb.CurrentLibrary.getParent(params.source); + params["_jsPlumb"] = _currentInstance, + ep = new endpointFunc(params); + _eventFireProxy("click", "endpointClick", ep); + _eventFireProxy("dblclick", "endpointDblClick", ep); + return ep; + }, + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + _operation = function(elId, func, endpointFunc) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + if (endpointFunc) endpointFunc(endpoints[i]); + } + } + }, + /** + * perform an operation on all elements. + */ + _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }, + + /** + * helper to remove an element from the DOM. + */ + _removeElement = function(element, parent) { + if (element != null && element.parentNode != null) { + element.parentNode.removeChild(element); + } + }, + /** + * helper to remove a list of elements from the DOM. + */ + _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }, + /** + * helper method to remove an item from a list. + */ + _removeFromList = function(map, key, value) { + if (key != null) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete (l[i]); + l.splice(i, 1); + return true; + } + } + } + return false; + }, + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }, + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + _setVisible = function(el, state, alsoChangeEndpoints) { + state = state === "block"; + var endpointFunc = null; + if (alsoChangeEndpoints) { + if (state) endpointFunc = function(ep) { + ep.setVisible(true, true, true); + }; + else endpointFunc = function(ep) { + ep.setVisible(false, true, true); + }; + } + var id = _getAttribute(el, "id"); + _operation(id, function(jpc) { + if (state && alsoChangeEndpoints) { + // this test is necessary because this functionality is new, and i wanted to maintain backwards compatibility. + // this block will only set a connection to be visible if the other endpoint in the connection is also visible. + var oidx = jpc.sourceId === id ? 1 : 0; + if (jpc.endpoints[oidx].isVisible()) jpc.setVisible(true); + } + else // the default behaviour for show, and what always happens for hide, is to just set the visibility without getting clever. + jpc.setVisible(state); + }, endpointFunc); + }, + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }, + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + _toggleVisible = function(elId, changeEndpoints) { + var endpointFunc = null; + if (changeEndpoints) { + endpointFunc = function(ep) { + var state = ep.isVisible(); + ep.setVisible(!state); + }; + } + _operation(elId, function(jpc) { + var state = jpc.isVisible(); + jpc.setVisible(!state); + }, endpointFunc); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }, + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return offsets[elId]; + } + if (recalc || offset == null) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + } + return offsets[elId]; + }, + +/** + * gets an id for the given element, creating and setting one if + * necessary. + */ + _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else + id = "jsPlumb_" + _timestamp(); + _setAttribute(ele, "id", id); + } + return id; + }, + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'jsPlumb function failed : ' + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'wrapped function failed : ' + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = "_jsPlumb_connector"; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = "_jsPlumb_endpoint"; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = "_jsPlumb_overlay"; + + this.Anchors = {}; + + this.Connectors = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Endpoints = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Overlays = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + +// ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***************************************** + /* + * Function: bind + * Bind to an event on jsPlumb. + * + * Parameters: + * event - the event to bind. Available events on jsPlumb are: + * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback. + * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback. + * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback. + * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback. + * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback. + * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback. + * + * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event. + */ + + /* + * Function: clearListeners + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + +// *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *********************************************************** + + /* + Function: addEndpoint + + Adds an to a given element or elements. + + Parameters: + + el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + params - Object containing Endpoint constructor arguments. For more information, see . + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some + shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in + this object are anything that 'params' can contain. See . + + Returns: + The newly created , if el referred to a single element. Otherwise, an array of newly created s. + + See Also: + + */ + this.addEndpoint = function(el, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + + // YUI wrapper + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + var _el = _getElementObject(inputs[i]), id = _getId(_el); + p.source = _el; + _updateOffset({ elId : id }); + var e = _newEndpoint(p); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + results.push(e); + } + + return results.length == 1 ? results[0] : results; + }; + + /* + Function: addEndpoints + Adds a list of s to a given element or elements. + + Parameters: + target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See 's constructor documentation. + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created s, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(el, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams); + if (e.constructor == Array) + Array.prototype.push.apply(results, e); + else results.push(e); + } + return results; + }; + + /* + Function: animate + This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating + the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // onComplete repaints, just to make sure everything looks good at the end of the animation. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /* + Function: connect + Establishes a between two elements (or s, which are themselves registered to elements). + + Parameters: + params - Object containing constructor arguments for the Connection. See 's constructor documentation. + referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of + Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection. + + Returns: + The newly created . + */ + this.connect = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + if (_p.target && !_p.target.endpoint) { + var tid = _getId(_p.target), + tep =_targetEndpointDefinitions[tid]; + + var overrideOne = function(singlePropertyName, pluralPropertyName, tepProperty, tep) { + if (tep[tepProperty]) { + if (_p[pluralPropertyName]) _p[pluralPropertyName][1] = tep[tepProperty]; + else if (_p[singlePropertyName]) { + _p[pluralPropertyName] = [ _p[singlePropertyName], tep[tepProperty] ]; + _p[singlePropertyName] = null; + } + else _p[pluralPropertyName] = [ null, tep[tepProperty] ]; + } + }; + + if (tep) { + overrideOne("endpoint", "endpoints", "endpoint", tep); + overrideOne("endpointStyle", "endpointStyles", "paintStyle", tep); + overrideOne("endpointHoverStyle", "endpointHoverStyles", "hoverPaintStyle", tep); + } + } + + // dynamic anchors. backwards compatibility here: from 1.2.6 onwards you don't need to specify "dynamicAnchors". the fact that some anchor consists + // of multiple definitions is enough to tell jsPlumb you want it to be dynamic. + if (_p.dynamicAnchors) { + // these can either be an array of anchor coords, which we will use for both source and target, or an object with {source:[anchors], target:[anchors]}, in which + // case we will use a different set for each element. + var a = _p.dynamicAnchors.constructor == Array; + var sa = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.source)); + var ta = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.target)); + _p.anchors = [sa,ta]; + } + + var jpc = _newConnection(_p); + // add to list of connections (by scope). + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + _currentInstance.fire("jsPlumbConnection", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + // force a paint + _draw(jpc.source); + + return jpc; + }; + + /* + Function: deleteEndpoint + Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too) + + Parameters: + object - either an object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElement(endpoint.canvas, endpoint.parent); + // remove from endpointsbyElement + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + delete endpoint; + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every , and their associated s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc) { + _currentInstance.fire("jsPlumbConnectionDetached", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + }; + + /* + Function: detach + Detaches and then removes a . Takes either (source, target) (the old way, maintained for backwards compatibility), or a params + object with various possible values. + + Parameters: + source - id or element object of the first element in the Connection. + target - id or element object of the second element in the Connection. + params - a JS object containing the same parameters as you pass to jsPlumb.connect. If this is present then neither source nor + target should be present; it should be the only argument to the method. See the docs for 's constructor for information +about the parameters allowed in the params object. + Returns: + true if successful, false if not. + */ + this.detach = function(source, target) { + if (arguments.length == 2) { + var s = _getElementObject(source), sId = _getId(s); + var t = _getElementObject(target), tId = _getId(t); + _operation(sId, function(jpc) { + if ((jpc.sourceId == sId && jpc.targetId == tId) || (jpc.targetId == sId && jpc.sourceId == tId)) { + _removeElements(jpc.connector.getDisplayElements(), jpc.parent); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + // this is the new version of the method, taking a JS object like + // the connect method does. + else if (arguments.length == 1) { + // TODO investigate whether or not this code still works when a user has supplied their own subclass of Connection. i suspect it may not. + if (arguments[0].constructor == Connection) { + arguments[0].endpoints[0].detachFrom(arguments[0].endpoints[1]); + } + else if (arguments[0].connection) { + arguments[0].connection.endpoints[0].detachFrom(arguments[0].connection.endpoints[1]); + } + else { + var _p = jsPlumb.extend( {}, source); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1])); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source); + var targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElements(jpc.connector.getDisplayElements(), jpc.parent); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + } + } + }; + + /* + Function: detachAll + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + + Returns: + void + */ + this.detachAllConnections = function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + }; + + /** + * @deprecated Use detachAllConnections instead. this will be removed in jsPlumb 1.3. + */ + this.detachAll = this.detachAllConnections; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + /** + * @deprecated use detachEveryConnection instead. this will be removed in jsPlumb 1.3. + */ + this.detachEverything = this.detachEveryConnection; + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of you library's draggable method so that jsPlumb can setup the appropriate callbacks. Your underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method, + * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the + * default scope). If multiple scopes are passed in, the return value will be a map of { scope -> [ connection... ] }. + * + * Parameters + * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list + * of connections that are in the given scope. + * options - if the argument is a JS object, you can specify a finer-grained filter: + * + * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes. + * - *source* either a string representing an element id, or a selector. constrains the result to connections having this source. + * - *target* either a string representing an element id, or a selector. constrains the result to connections having this target. + * + */ + this.getConnections = function(options) { + if (!options) { + options = {}; + } else if (options.constructor == String) { + options = { "scope": options }; + } + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') + r.push(input); + else + r = input; + } + return r; + }; + var scope = options.scope || jsPlumb.getDefaultScope(), + scopes = prepareList(scope), + sources = prepareList(options.source), + targets = prepareList(options.target), + filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }, + results = scopes.length > 1 ? {} : [], + _addOne = function(scope, obj) { + if (scopes.length > 1) { + var ss = results[scope]; + if (ss == null) { + ss = []; results[scope] = ss; + } + ss.push(obj); + } else results.push(obj); + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + _addOne(i, c); + } + } + } + return results; + }; + + /* + * Function: getAllConnections + * Gets all connections, as a map of { scope -> [ connection... ] }. + */ + this.getAllConnections = function() { + return connectionsByScope; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /** + * Function:getEndpoints + * Gets the list of Endpoints for a given selector, or element id. + * @param el + * @return + */ + this.getEndpoints = function(el) { + return endpointsByElement[_getId(el)]; + }; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + + this.appendElement = _appendElement; + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + changeEndpoints - whether not to also hide endpoints on the element. by default this is false. + + Returns: + void + */ + this.hide = function(el, changeEndpoints) { + _setVisible(el, "none", changeEndpoints); + }; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + if (!initialized) { + _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run. + + var _bind = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && _mouseEventsEnabled && renderMode == jsPlumb.CANVAS) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + var t = c[i].connector[event](e); + if (t) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i].endpoint[event](e)) return; + } + } + } + }); + }; + _bind("click"); + _bind("dblclick"); + _bind("mousemove"); + _bind("mousedown"); + _bind("mouseup"); + + initialized = true; + _currentInstance.fire("ready"); + } + }; + + this.jsPlumbUIComponent = jsPlumbUIComponent; + this.EventGenerator = EventGenerator; + + /* + * Creates an anchor with the given params. + * + * You do not need to use this method. It is exposed because of the way jsPlumb is + * split into three scripts; this will change in the future. + * + * x - the x location of the anchor as a fraction of the + * total width. y - the y location of the anchor as a fraction of the + * total height. xOrientation - value indicating the general direction a + * connection from the anchor should go in, in the x direction. + * yOrientation - value indicating the general direction a connection + * from the anchor should go in, in the y direction. xOffset - a fixed + * offset that should be applied in the x direction that should be + * applied after the x position has been figured out. optional. defaults + * to 0. yOffset - a fixed offset that should be applied in the y + * direction that should be applied after the y position has been + * figured out. optional. defaults to 0. + * -- OR -- + * + * params - {x:..., y:..., xOrientation etc } + * -- OR FROM 1.2.4 --- + * + * name - the name of some Anchor in the _currentInstance.Anchors array. + * -- OR FROM 1.2.4 --- + * + * coords - a list of coords for the anchor, like you would pass to + * jsPlumb.makeAnchor (eg [0.5,0.5,0,-1] - an anchor in the center of + * some element, oriented towards the top of the screen) + * -- OR FROM 1.2.4 --- + * + * anchor - an existing anchor. just gets passed back. it's handy + * internally to have this functionality. + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed + // in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards + // compatibility if we are given only one value we assume it's a + // call in the old form. + if (arguments.length == 0) return null; + var params = {}; + if (arguments.length == 1) { + var specimen = arguments[0]; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; + // is it the name of an anchor type? + else if (typeof specimen == "string") return _currentInstance.Anchors[arguments[0]](); + // is it an array of coordinates? + else if (specimen.constructor == Array) { + if (specimen[0].constructor == Array || specimen[0].constructor == String) + return new DynamicAnchor(specimen); + else + return jsPlumb.makeAnchor.apply(this, specimen); + } + // last we try the backwards compatibility stuff. + else if (typeof arguments[0] == "object") jsPlumb.extend(params, x); + } else { + params = { x : x, y : y }; + if (arguments.length >= 4) params.orientation = [ arguments[2], arguments[3] ]; + if (arguments.length == 6) params.offsets = [ arguments[4], arguments[5] ]; + } + var a = new Anchor(params); + a.clone = function() { + return new Anchor(params); + }; + return a; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types) { + var r = []; + for ( var i = 0; i < types.length; i++) + if (typeof types[i] == "string") + r.push(_currentInstance.Anchors[types[i]]()); + else if (types[i].constructor == Array) + r.push(jsPlumb.makeAnchor(types[i])); + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /** + * Function: makeTarget + * Makes some DOM element a Connection target, allowing you to drag connections to it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a target. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * scope optional. scope for the drop zone. + * dropOptions optional. same stuff as you would pass to dropOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to false. whether or not to delete + * any Endpoints created by a connection to this target if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + var _targetEndpointDefinitions = {}; + this.makeTarget = function(el, params, referenceParams) { + + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + var jpcl = jsPlumb.CurrentLibrary, + scope = p.scope || _currentInstance.Defaults.Scope, + deleteEndpointsOnDetach = p.deleteEndpointsOnDetach || false, + _doOne = function(_el) { + + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el); + _targetEndpointDefinitions[elid] = p.endpoint; + + var dropOptions = jsPlumb.extend({}, p.dropOptions || {}); + var _drop = function() { + var draggable = _getElementObject(jpcl.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + // restore the original scope if necessary (issue 57) + scope = _getAttribute(draggable, "originalScope"); + + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + // get the connection, to then get its endpoint + var jpc = floatingConnections[id], + source = jpc.endpoints[0], + _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : null, + // make a new Endpoint + newEndpoint = jsPlumb.addEndpoint(_el, _endpoint); + + var c = jsPlumb.connect({ + source:source, + target:newEndpoint, + scope:scope + }); + if (deleteEndpointsOnDetach) + c.endpointToDeleteOnDetach = newEndpoint; + }; + + var dropEvent = jpcl.dragEvents['drop']; + dropOptions["scope"] = dropOptions["scope"] || scope; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop); + + jpcl.initDroppable(_el, dropOptions); + }; + + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements drop targets. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeTargets = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeTarget(els[i], params, referenceParams); + } + }; + + /* + Function: ready + Helper method to bind a function to jsPlumb's ready event. + */ + this.ready = function(fn) { + _currentInstance.bind("ready", fn); + }, + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + for ( var i in ebe) + _currentInstance.deleteEndpoint(ebe[i]); + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + this.deleteEveryEndpoint(); + this.clearListeners(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + + Parameters: + value - whether or not to automatically repaint when the window is resized. + + Returns: void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setDraggableByDefault + * Sets whether or not elements are draggable by default. Default for this is true. + * + * Parameters: + * draggable - value to set + * + * Returns: + * void + */ + this.setDraggableByDefault = function(draggable) { + _draggableByDefault = draggable; + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction + * Sets the function to fire when the window size has changed and a repaint was fired. + * + * Parameters: + * f - Function to execute. + * + * Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: setMouseEventsEnabled + * Sets whether or not mouse events are enabled. Default is true. + * + * Parameters: + * enabled - whether or not mouse events should be enabled. + * + * Returns: + * void + */ + this.setMouseEventsEnabled = function(enabled) { + _mouseEventsEnabled = enabled; + }; + + /* + * Constant for use with the setRenderMode method + */ + this.CANVAS = "canvas"; + + /* + * Constant for use with the setRenderMode method + */ + this.SVG = "svg"; + + this.VML = "vml"; + + /* + * Function: setRenderMode + * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that + * what you asked for is not supported (and that VML is). If you asked for VML but the browser does + * not support it, jsPlumb uses SVG. + * + * Returns: + * the render mode that jsPlumb set, which of course may be different from that requested. + */ + this.setRenderMode = function(mode) { + if (mode) + mode = mode.toLowerCase(); + else + return; + if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); + // now test we actually have the capability to do this. + if (mode === jsPlumb.CANVAS && canvasAvailable) + renderMode = jsPlumb.CANVAS; + else if (mode === jsPlumb.SVG && svgAvailable) + renderMode = jsPlumb.SVG; + else if (vmlAvailable) + renderMode = jsPlumb.VML; + + return renderMode; + }; + + this.getRenderMode = function() { return renderMode; }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * changeEndpoints - whether or not to also change the visible state of the endpoints on the element. this also has a bearing on + * other connections on those endpoints: if their other endpoint is also visible, the connections are made visible. + * + * Returns: + * void + */ + this.show = function(el, changeEndpoints) { + _setVisible(el, "block", changeEndpoints); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId, + makeAnchor:self.makeAnchor, + makeDynamicAnchor:self.makeDynamicAnchor + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * changeEndpoints - whether or not to also toggle the endpoints on the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload + * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element. + * + * Returns: + * void + */ + this.unload = function() { + delete endpointsByElement; + delete endpointsByUUID; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + delete canvasList; + }; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + if (timestamp && timestamp === self.timestamp) { + return lastReturnValue; + } + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + // adjust loc if there is an offsetParent + if (element.canvas && element.canvas.offsetParent) { + var po = element.canvas.offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(element.canvas.offsetParent); + lastReturnValue[0] = lastReturnValue[0] - po.left; + lastReturnValue[1] = lastReturnValue[1] - po.top; + } + + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + * + * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(_getElementObject(refCanvas)); + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + var orientation = null; + var _lastResult = null; + + this.compute = function(params) { + var xy = params.xy, element = params.element; + var result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + + // adjust loc if there is an offsetParent + if (element.canvas && element.canvas.offsetParent) { + var po = element.canvas.offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(element.canvas.offsetParent); + result[0] = result[0] - po.left; + result[1] = result[1] - po.top; + } + + _lastResult = result; + return result; + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { orientation = anchor.getOrientation(); }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchors is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = [], + _convert = function(anchor) { return anchor.constructor == Anchor ? anchor: jsPlumb.makeAnchor(anchor); }; + for (var i = 0; i < anchors.length; i++) _anchors[i] = _convert(anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + this.locked = false; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null, + _curIndex = _anchors.length > 0 ? 0 : -1, + self = this, + + // helper method to calculate the distance between the centers of the two elements. + _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }, + + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + + var pos = _curAnchor.compute(params); + return pos; + }; + + this.getCurrentLocation = function() { + var cl = _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + return cl; + }; + + this.getOrientation = function() { return _curAnchor != null ? _curAnchor.getOrientation() : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * container - optional id or selector instructing jsPlumb where to attach all the elements it creates for this connection. you should read the documentation for a full discussion of this. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * overlays - Optional array of Overlay definitions to appear on this Connection. + * drawEndpoints - if false, instructs jsPlumb to not draw the endpoints for this Connection. Be careful with this: it only really works when you tell jsPlumb to attach elements to the document body. Read the documentation for a full discussion of this. + */ + var Connection = function(params) { + + jsPlumbUIComponent.apply(this, arguments); + // ************** get the source and target and register the connection. ******************* + var self = this; + var visible = true; + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none"; + }; + var id = new String('_jsplumb_c_' + (new Date()).getTime()); + this.getId = function() { return id; }; + this.parent = params.parent; + /** + Property: source + The source element for this Connection. + */ + this.source = _getElementObject(params.source); + /** + Property:target + The target element for this Connection. + */ + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. + if (params.sourceEndpoint) this.source = params.sourceEndpoint.getElement(); + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + /* + * Property: sourceId + * Id of the source element in the connection. + */ + this.sourceId = _getAttribute(this.source, "id"); + /* + * Property: targetId + * Id of the target element in the connection. + */ + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + + /** + * implementation of abstract method in EventGenerator + * @return list of attached elements. in our case, a list of Endpoints. + */ + this.getAttachedElements = function() { + return self.endpoints; + }; + + /** + * implementation of abstract method in EventGenerator + */ + this.savePosition = function() { + srcWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.source)); + targetWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.target)); + }; + + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams) { + if (anchorParams) + return jsPlumb.makeAnchor(anchorParams); + }; + var prepareEndpoint = function(existing, index, params, element, connectorPaintStyle, connectorHoverPaintStyle) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] + || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint; + + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified. + if (es.fillStyle == null && connectorPaintStyle != null) + es.fillStyle = connectorPaintStyle.strokeStyle; + + // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does: + //* + if (es.outlineColor == null && connectorPaintStyle != null) + es.outlineColor = connectorPaintStyle.outlineColor; + if (es.outlineWidth == null && connectorPaintStyle != null) + es.outlineWidth = connectorPaintStyle.outlineWidth; + //*/ + + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure? + if (connectorHoverPaintStyle != null) { + if (ehs == null) ehs = {}; + if (ehs.fillStyle == null) { + ehs.fillStyle = connectorHoverPaintStyle.strokeStyle; + } + } + var a = params.anchors ? params.anchors[index] : _makeAnchor(_currentInstance.Defaults.Anchors[index]) || _makeAnchor(jsPlumb.Defaults.Anchors[index]) || _makeAnchor(_currentInstance.Defaults.Anchor) || _makeAnchor(jsPlumb.Defaults.Anchor), + u = params.uuids ? params.uuids[index] : null, + e = _newEndpoint({ + paintStyle : es, + hoverPaintStyle:ehs, + endpoint : ep, + connections : [ self ], + uuid : u, + anchor : a, + source : element, + container:params.container + }); + self.endpoints[index] = e; + + if (params.drawEndpoints === false) e.setVisible(false, true, true); + + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source, params.paintStyle, params.hoverPaintStyle); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target, params.paintStyle, params.hoverPaintStyle); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + /* + * Function: setConnector + * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method - the same + * thing that you would set as the 'connector' property on a jsPlumb.connect call. + * + * Parameters: + * connector - Connector definition + */ + this.setConnector = function(connector, doNotRepaint) { + if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent); + var connectorArgs = { _jsPlumb:self._jsPlumb, parent:params.parent, cssClass:params.cssClass, container:params.container }; + if (connector.constructor == String) + this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand. + else if (connector.constructor == Array) + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs)); + this.canvas = this.connector.canvas; + // add mouse events + this.connector.bind("click", function(con, e) { + _mouseWasDown = false; + self.fire("click", self, e); + }); + this.connector.bind("dblclick", function(con, e) { _mouseWasDown = false;self.fire("dblclick", self, e); }); + this.connector.bind("mouseenter", function(con, e) { + if (!self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(true); + } + self.fire("mouseenter", self, e); + } + }); + this.connector.bind("mouseexit", function(con, e) { + if (self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(false); + } + self.fire("mouseexit", self, e); + } + }); + this.connector.bind("mousedown", function(con, e) { + _mouseDown = true; + _mouseDownAt = jsPlumb.CurrentLibrary.getPageXY(e); + self.savePosition(); + }); + this.connector.bind("mouseup", function(con, e) { + _mouseDown = false; + if (self.connector == _connectionBeingDragged) _connectionBeingDragged = null; + }); + + if (!doNotRepaint) self.repaint(); + }; + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc) + */ + + self.setConnector(this.endpoints[0].connector || + this.endpoints[1].connector || + params.connector || + _currentInstance.Defaults.Connector || + jsPlumb.Defaults.Connector, true); + + this.setPaintStyle(this.endpoints[0].connectorStyle || + this.endpoints[1].connectorStyle || + params.paintStyle || + _currentInstance.Defaults.PaintStyle || + jsPlumb.Defaults.PaintStyle, true); + + this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || + this.endpoints[1].connectorHoverStyle || + params.hoverPaintStyle || + _currentInstance.Defaults.HoverPaintStyle || + jsPlumb.Defaults.HoverPaintStyle, true); + + this.paintStyleInUse = this.paintStyle; + + /* + * Property: overlays + * List of Overlays for this Connection. + */ + this.overlays = []; + var _overlays = params.overlays || _currentInstance.Defaults.Overlays; + if (_overlays) { + for (var i = 0; i < _overlays.length; i++) { + var o = _overlays[i], _newOverlay = null; + if (o.constructor == Array) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0]; + var p = jsPlumb.CurrentLibrary.extend({connection:self, _jsPlumb:_currentInstance}, o[1]); // make a copy of the object so as not to mess up anyone else's reference... + if (o.length == 3) jsPlumb.CurrentLibrary.extend(p, o[2]); + _newOverlay = new jsPlumb.Overlays[renderMode][type](p); + if (p.events) { + for (var evt in p.events) { + _newOverlay.bind(evt, p.events[evt]); + } + } + } else if (o.constructor == String) { + _newOverlay = new jsPlumb.Overlays[renderMode][o]({connection:self, _jsPlumb:_currentInstance}); + } else { + _newOverlay = o; + } + + + + this.overlays.push(_newOverlay); + } + } + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Connection. + * + * Parameters: + * event - the event to bind. Available events on a Connection are: + * - *click* : notification that a Connection was clicked. + * - *dblclick* : notification that a Connection was double clicked. + * - *mouseenter* : notification that the mouse is over a Connection. + * - *mouseexit* : notification that the mouse exited a Connection. + * + * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Connection's paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Connection will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Connection. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + // overlay finder helper method + var _getOverlayIndex = function(id) { + var idx = -1; + for (var i = 0; i < self.overlays.length; i++) { + if (id === self.overlays[i].id) { + idx = i; + break; + } + } + return idx; + }; + + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + this.addOverlay = function(overlay) { self.overlays.push(overlay); }; + + /* + * Function: getOverlay + * Gets an overlay, by ID. Note: by ID. You would pass an 'id' parameter + * in to the Overlay's constructor arguments, and then use that to retrieve + * it via this method. + */ + this.getOverlay = function(id) { + var idx = _getOverlayIndex(id); + return idx >= 0 ? self.overlays[idx] : null; + }; + + /* + * Function: hideOverlay + * Hides the overlay specified by the given id. + */ + this.hideOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.hide(); + }; + + /* + * Function: showOverlay + * Shows the overlay specified by the given id. + */ + this.showOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.show(); + }; + + /** + * Function: removeAllOverlays + * Removes all overlays from the Connection, and then repaints. + */ + this.removeAllOverlays = function() { + self.overlays.splice(0, self.overlays.length); + self.repaint(); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + this.removeOverlay = function(overlayId) { + var idx = _getOverlayIndex(overlayId); + if (idx != -1) self.overlays.splice(idx, 1); + }; + + /** + * Function:removeOverlays + * Removes a set of overlays by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + this.removeOverlays = function() { + for (var i = 0; i < arguments.length; i++) + self.removeOverlay(arguments[i]); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + this.labelStyle = params.labelStyle || _currentInstance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + if (this.label) { + this.overlays.push(new jsPlumb.Overlays[renderMode].Label( { + cssClass:params.cssClass, + labelStyle : this.labelStyle, + label : this.label, + connection:self, + _jsPlumb:_currentInstance + })); + } + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String or a Function that returns a String. + */ + this.setLabel = function(l) { + self.label = l; + _currentInstance.repaint(self.source); + }; + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var otherOffset = offsets[this.targetId]; + var otherWH = sizes[this.targetId]; + var anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1] + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0] + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc }); + + /* + * Paints the Connection. Not exposed for public usage. + * + * Parameters: + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp, + // if the moving object is not the source we must transpose the two references. + swap = false, + tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId, + tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + + _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }); + _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var sAnchorP = this.endpoints[sIdx].anchor.getCurrentLocation(), + tAnchorP = this.endpoints[tIdx].anchor.getCurrentLocation(); + + /* paint overlays*/ + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible()) { + var s = o.computeMaxSize(self.connector); + if (s > maxSize) + maxSize = s; + } + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, self.paintStyleInUse.lineWidth, maxSize); + + self.connector.paint(dim, self.paintStyleInUse); + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) + self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim); + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function() { + this.paint({ elId : this.sourceId, recalc : true }); + }; + + _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide + // whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + + // just to make sure the UI gets initialised fully on all browsers. + self.repaint(); + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1 + * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't + * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you + * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or + * 'target' Endpoints for Connections. + * + * + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * container - optional id or selector instructing jsPlumb where to attach the element it creates for this endpoint. you should read the documentation for a full discussion of this. + * connections - optional list of Connections to configure the Endpoint with. + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + params = params || {}; + var self = this; +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Endpoint. + * + * Parameters: + * event - the event to bind. Available events on an Endpoint are: + * - *click* : notification that a Endpoint was clicked. + * - *dblclick* : notification that a Endpoint was double clicked. + * - *mouseenter* : notification that the mouse is over a Endpoint. + * - *mouseexit* : notification that the mouse exited a Endpoint. + * + * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Endpoint's paint style and then repaints the Endpoint. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Endpoint will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Endpoint. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + var visible = true; + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + var id = new String('_jsplumb_e_' + (new Date()).getTime()); + this.getId = function() { return id; }; + if (params.dynamicAnchors) + self.anchor = new DynamicAnchor(jsPlumb.makeAnchors(params.dynamicAnchors)); + else + self.anchor = params.anchor ? jsPlumb.makeAnchor(params.anchor) : params.anchors ? jsPlumb.makeAnchor(params.anchors) : jsPlumb.makeAnchor("TopCenter"); + var _endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot", + endpointArgs = { _jsPlumb:self._jsPlumb, parent:params.parent, container:params.container }; + if (_endpoint.constructor == String) + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint](endpointArgs); + else if (_endpoint.constructor == Array) { + endpointArgs = jsPlumb.extend(_endpoint[1], endpointArgs); + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint[0]](endpointArgs); + } + else + _endpoint = _endpoint.clone(); + + // assign a clone function using our derived endpointArgs. this is used when a drag starts: the endpoint that was dragged is cloned, + // and the clone is left in its place while the original one goes off on a magical journey. + this.clone = function() { + var o = new Object(); + _endpoint.constructor.apply(o, [endpointArgs]); + return o; + }; + + self.endpoint = _endpoint; + self.type = self.endpoint.type; + + // TODO this event listener registration code is identical to what Connection does: it should be refactored. + this.endpoint.bind("click", function(e) { self.fire("click", self, e); }); + this.endpoint.bind("dblclick", function(e) { self.fire("dblclick", self, e); }); + this.endpoint.bind("mouseenter", function(con, e) { + if (!self.isHover()) { + self.setHover(true); + self.fire("mouseenter", self, e); + } + }); + this.endpoint.bind("mouseexit", function(con, e) { + if (self.isHover()) { + self.setHover(false); + self.fire("mouseexit", self, e); + } + }); + // TODO this event listener registration code above is identical to what Connection does: it should be refactored. + + this.setPaintStyle(params.paintStyle || + params.style || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyle, true); + this.setHoverPaintStyle(params.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyle, true); + this.paintStyleInUse = this.paintStyle; + this.connectorStyle = params.connectorStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.parent = params.parent; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + var _element = params.source, + _uuid = params.uuid, + floatingEndpoint = null, + inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + this.element = _element; + var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + /* + * Property: canvas + * The Endpoint's Canvas. + */ + this.canvas = this.endpoint.canvas; + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + this.connections = params.connections || []; + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + var _reattach = params.reattach || false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + + // this avoids a circular loop + if (!ignoreTarget) { + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + t.detach(connection, true); + // check connection to see if we want to delete the other endpoint. + // if the user uses makeTarget to make some element a target for connections, + // it is possible that they will have set 'endpointToDeleteOnDetach': when + // you make a connection to an element that acts as a target (note: NOT an + // Endpoint; just some div as a target), Endpoints are created for that + // connection. so if you then delete that Connection, it is feasible you + // will want these auto-generated endpoints to be removed. + if (connection.endpointToDeleteOnDetach && connection.endpointToDeleteOnDetach.connections.length == 0) + jsPlumb.deleteEndpoint(connection.endpointToDeleteOnDetach); + } + _removeElements(connection.connector.getDisplayElements(), connection.parent); + _removeFromList(connectionsByScope, connection.scope, connection); + if(!ignoreTarget) fireDetachEvent(connection); + } + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + */ + this.detachAll = function() { + while (self.connections.length > 0) { + self.detach(self.connections[0]); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + */ + this.detachFrom = function(targetEndpoint) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + c[i].setHover(false); + self.detach(c[i]); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + return _newEndpoint( { anchor : self.anchor, source : _element, paintStyle : this.paintStyle, endpoint : _endpoint }); + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + return (self.connections.length < _maxConnections) || _maxConnections == -1 ? null : self.connections[0]; + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + /* + * Function: setStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to + * setStyle and deprecate it if so. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + * + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + // a helper function that tries to find a connection to the given element, and returns it if so. if elementWithPrecedence is null, + // or no connection to it is found, we return the first connection in our list. + var findConnectionToUseForDynamicAnchor = function(elementWithPrecedence) { + var idx = 0; + if (elementWithPrecedence != null) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].sourceId == elementWithPrecedence || self.connections[i].targetId == elementWithPrecedence) { + idx = i; + break; + } + } + } + + return self.connections[idx]; + }; + + /* + * Function: paint + * Paints the Endpoint, recalculating offset and anchor positions if necessary. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + + params = params || {}; + var timestamp = params.timestamp; + if (!timestamp || self.timestamp !== timestamp) { + var ap = params.anchorPoint, canvas = params.canvas, connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + var xy = params.offset || offsets[_elementId]; + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (self.anchor.isDynamic) { + if (self.connections.length > 0) { + //var c = self.connections[0]; + var c = findConnectionToUseForDynamicAnchor(params.elementWithPrecedence); + var oIdx = c.endpoints[0] == self ? 1 : 0; + var oId = oIdx == 0 ? c.sourceId : c.targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + } + ap = self.anchor.compute(anchorParams); + } + + var d = _endpoint.compute(ap, self.anchor.getOrientation(), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + _endpoint.paint(d, self.paintStyleInUse, self.anchor); + + self.timestamp = timestamp; + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + var start = function() { + jpc = self.connectorSelector(); + if (self.isFull() && !dragAllowedWhenFull) return false; + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + n.style.position = "absolute"; + var nE = _getElementObject(n); + _appendElement(n, self.parent); + // create and assign an id, and initialize the offset. + var id = _getId(nE); + + // set the offset of this div to be where 'inPlaceCopy' is, to start with. + var ipcoel = _getElementObject(inPlaceCopy.canvas), + ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel), + po = inPlaceCopy.canvas.offsetParent != null ? + inPlaceCopy.canvas.offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(inPlaceCopy.canvas.offsetParent) + : { left:0, top: 0}; + jsPlumb.CurrentLibrary.setOffset(n, {left:ipco.left - po.left, top:ipco.top-po.top}); + + _updateOffset( { elId : id }); + + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor( { reference : self.anchor, referenceCanvas : self.canvas }); + floatingEndpoint = _newEndpoint({ paintStyle : self.paintStyle, endpoint : _endpoint, anchor : floatingAnchor, source : nE }); + + if (jpc == null) { + self.anchor.locked = true; + // create a connection. one end is this endpoint, the + // other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : _getElementObject(_element), + target : _getElementObject(n), + anchors : [ self.anchor, floatingAnchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays + }); + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.connector.setHover(false); + } else { + existingJpc = true; + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.connector.setHover(false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas)); + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; // are we the source or the target? + + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas); + var dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // now we want to get this endpoint's DROP scope, and set it for now: we can only be dropped on drop zones + // that have our drop scope (issue 57). + var dropScope = jsPlumb.CurrentLibrary.getDropScope(c);//jpc.endpoints[anchorIdx == 0 ? 1 : 0].getDropScope(); + jsPlumb.CurrentLibrary.setDragScope(c, dropScope); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it and register connection on it. + floatingConnections[id] = jpc; + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var jpcl = jsPlumb.CurrentLibrary, + dragOptions = params.dragOptions || {}, + defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions), + startEvent = jpcl.dragEvents['start'], + stopEvent = jpcl.dragEvents['stop'], + dragEvent = jpcl.dragEvents['drag']; + + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], + function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + jsPlumb.CurrentLibrary.setOffset(n, _ui); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements( [ n, floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (_reattach) { + + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } else { + jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc); // the main endpoint will inform the floating endpoint + // to disconnect, and also post the detached event. + } + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElements(jpc.connector.getDisplayElements(), self.parent); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint(); + jpc.setHover(false); + jpc.repaint(); + jpc = null; + delete inPlaceCopy; + delete endpointsByElement[floatingEndpoint.elementId]; + floatingEndpoint = null; + delete floatingEndpoint; + + _currentInstance.currentlyDragging = false; + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas) { + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + var drop = function() { + var draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)); + var id = _getAttribute(draggable, "dragId"); + var elId = _getAttribute(draggable, "elId"); + + // restore the original scope if necessary (issue 57) + var scope = _getAttribute(draggable, "originalScope"); + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + var jpc = floatingConnections[id]; + + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // todo test that the target is not full. + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + if (!jpc.suspendedEndpoint) { + _addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + _currentInstance.fire("jsPlumbConnectionDetached", { + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }); + } + + jsPlumb.repaint(elId); + + _currentInstance.fire("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], + targetEndpoint : jpc.endpoints[1], + connection:jpc + }); + } + + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute( _getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + } + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute(_getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + } + }); + + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas)); + + return self; + }; + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + j.init(); + return j; + }; + + var _curryAnchor = function(x,y,ox,oy) { + return function() { + return jsPlumb.makeAnchor(x,y,ox,oy); + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1); + + jsPlumb.Defaults.DynamicAnchors = function() { + return jsPlumb.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"]); + }; + jsPlumb.Anchors["AutoDefault"] = function() { return jsPlumb.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors()); }; + + +})(); +/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the default Connectors, Endpoint and Overlay definitions. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +(function() { + + /** + * + * Helper class to consume unused mouse events by components that are DOM elements and + * are used by all of the different rendering modes. + * + */ + jsPlumb.DOMElementComponent = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + // when render mode is canvas, these functions may be called by the canvas mouse handler. + // this component is safe to pipe this stuff to /dev/null. + this.mousemove = + this.dblclick = + this.click = + this.mousedown = + this.mouseup = function(e) { }; + }; + + /** + * Class: Connectors.Straight + * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters. + */ + jsPlumb.Connectors.Straight = function() { + this.type = "Straight"; + var self = this; + var currentPoints = null; + var _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + _dx = _tx - _sx, _dy = (_ty - _sy); + _m = _dy / _dx, _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + + return currentPoints; + }; + + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + var xp = _sx + (location * _dx); + var yp = (_m == Infinity || _m == -Infinity) ? _sy + (location * (_ty - _sy)) : (_m * xp) + _b; + return {x:xp, y:yp}; + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { return _m; }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + var orientation = distance > 0 ? 1 : -1; + var y = Math.abs(distance * Math.sin(_theta)); + if (_sy > _ty) y = y * -1; + var x = Math.abs(distance * Math.cos(_theta)); + if (_sx > _tx) x = x * -1; + return {x:p.x + (orientation * x), y:p.y + (orientation * y)}; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = self.gradientAtPoint(p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + }; + }; + + + /** + * Class:Connectors.Bezier + * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's + * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below. + */ + /** + * Function:Constructor + * + * Parameters: + * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line. + * Optional; defaults to 150. + * + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + params = params || {}; + this.majorAnchor = params.curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + this.type = "Bezier"; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h; + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) + { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx); var minx2 = Math.min(_CP[0], _CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(_sx,_tx); var maxx2 = Math.max(_CP[0], _CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty); var miny2 = Math.min(_CP[1], _CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(_sy,_ty); var maxy2 = Math.max(_CP[1], _CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + return jsBezier.perpendicularToCurveAt(_makeCurve(), location, length, distance); + }; + + }; + + + /** + * Class: Connectors.Flowchart + * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments. + */ + /** + * Function: Constructor + * + * Parameters: + * stub - minimum length for the stub at each end of the connector. defaults to 30 pixels. + */ + jsPlumb.Connectors.Flowchart = function(params) { + this.type = "Flowchart"; + params = params || {}; + var self = this, + minStubLength = params.stub || params.minStubLength /* bwds compat. */ || 30, + segments = [], + segmentGradients = [], + segmentProportions = [], + segmentLengths = [], + segmentProportionalLengths = [], + points = [], + swapX, + swapY, + /** + * recalculates the gradients of each segment, and the points at which the segments begin, proportional to the total length travelled + * by all the segments that constitute the connector. + */ + updateSegmentGradientsAndProportions = function(startX, startY, endX, endY) { + var total = 0; + for (var i = 0; i < segments.length; i++) { + var sx = i == 0 ? startX : segments[i][2], + sy = i == 0 ? startY : segments[i][3], + ex = segments[i][0], + ey = segments[i][1]; + + segmentGradients[i] = sx == ex ? Infinity : 0; + segmentLengths[i] = Math.abs(sx == ex ? ey - sy : ex - sx); + total += segmentLengths[i]; + } + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segmentLengths[i] / total; + segmentProportions[i] = [curLoc, (curLoc += (segmentLengths[i] / total)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0]; + var ly = segments.length == 0 ? sy : segments[segments.length - 1][1]; + segments.push([x, y, lx, ly]); + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment + * that contains the point which is 'location' distance along the entire path, where + * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths + * are made up of a list of segments, each of which contributes some fraction to + * the total length. + */ + findSegmentForLocation = function(location) { + var idx = segmentProportions.length - 1, inSegmentProportion = 0; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + segments = []; + segmentGradients = []; + segmentProportionalLengths = []; + segmentLengths = []; + segmentProportionals = []; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + offx = (lw / 2) + (minStubLength * 2), + offy = (lw / 2) + (minStubLength * 2), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(), + to = targetAnchor.orientation || targetAnchor.getOrientation(), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx, + h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy; + if (w < minWidth) { + offx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + offy += (minWidth - h) / 2; + h = minWidth; + } + var sx = swapX ? w-offx : offx, + sy = swapY ? h-offy : offy, + tx = swapX ? offx : w-offx , + ty = swapY ? offy : h-offy, + startStubX = sx + (so[0] * minStubLength), + startStubY = sy + (so[1] * minStubLength), + endStubX = tx + (to[0] * minStubLength), + endStubY = ty + (to[1] * minStubLength), + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2); + + x -= offx; y -= offy; + points = [x, y, w, h, sx, sy, tx, ty], extraPoints = []; + + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + if (so[0] == 0) { + var startStubIsBeforeEndStub = startStubY < endStubY; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } + } + else { + var startStubIsBeforeEndStub = startStubX < endStubX; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } + } + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentGradientsAndProportions(sx, sy, tx, ty); + + return points; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location) { + return self.pointAlongPathFrom(location, 0); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location) { + return segmentGradients[findSegmentForLocation(location)["index"]]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance) { + var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segmentLengths[s.index], m = segmentGradients[s.index]; + var e = { + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + return e; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = segmentGradients[p.segmentInfo.index]; + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + + }; + + }; + + // ********************************* END OF CONNECTOR TYPES ******************************************************************* + + // ********************************* ENDPOINT TYPES ******************************************************************* + + /** + * Class: Endpoints.Dot + * A round endpoint, with default radius 10 pixels. + */ + + /** + * Function: Constructor + * + * Parameters: + * + * radius - radius of the endpoint. defaults to 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + this.type = "Dot"; + var self = this; + params = params || {}; + this.radius = params.radius || 10; + this.defaultOffset = 0.5 * this.radius; + this.defaultInnerRadius = this.radius / 3; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var r = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - r; + var y = anchorPoint[1] - r; + return [ x, y, r * 2, r * 2, r ]; + }; + }; + + /** + * Class: Endpoints.Rectangle + * A Rectangular Endpoint, with default size 20x20. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the endpoint. defaults to 20 pixels. + * height - height of the endpoint. defaults to 20 pixels. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + this.type = "Rectangle"; + var self = this; + params = params || {}; + this.width = params.width || 20; + this.height = params.height || 20; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + return [ x, y, width, height]; + }; + }; + + /** + * Class: Endpoints.Image + * Draws an image as the Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * src - location of the image to use. + */ + jsPlumb.Endpoints.Image = function(params) { + + this.type = "Image"; + jsPlumb.DOMElementComponent.apply(this, arguments); + + var self = this, initialized = false; + this.img = new Image(); + self.ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.src || params.url; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + self.anchorPoint = anchorPoint; + if (self.ready) return [anchorPoint[0] - self.img.width / 2, anchorPoint[1] - self.img.height/ 2, self.img.width, self.img.height]; + else return [0,0,0,0]; + }; + + self.canvas = document.createElement("img"), initialized = false; + self.canvas.style["margin"] = 0; + self.canvas.style["padding"] = 0; + self.canvas.style["outline"] = 0; + self.canvas.style["position"] = "absolute"; + self.canvas.className = jsPlumb.endpointClass; + jsPlumb.appendElement(self.canvas, params.parent); + self.attachListeners(self.canvas, self); + + var actuallyPaint = function(d, style, anchor) { + if (!initialized) { + self.canvas.setAttribute("src", self.img.src); + initialized = true; + } + var width = self.img.width, + height = self.img.height, + x = self.anchorPoint[0] - (width/2), + y = self.anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(self.canvas, x, y, width, height); + }; + + this.paint = function(d, style, anchor) { + if (self.ready) { + actuallyPaint(d, style, anchor); + } + else { + window.setTimeout(function() { + self.paint(d, style, anchor); + }, 200); + } + }; + }; + + /** + * Class: Endpoints.Blank + * An Endpoint that paints nothing on the screen, and cannot be interacted with using the mouse. There are no constructor parameters for this Endpoint. + */ + jsPlumb.Endpoints.Blank = function(params) { + var self = this; + this.type = "Blank"; + jsPlumb.DOMElementComponent.apply(this, arguments); + this.compute = function() { + return [0,0,10,0]; + }; + + self.canvas = document.createElement("div"); + self.canvas.style.display = "block"; + self.canvas.style.width = "1px"; + self.canvas.style.height = "1px"; + self.canvas.style.background = "transparent"; + self.canvas.style.position = "absolute"; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function() { }; + }; + + /** + * Class: Endpoints.Triangle + * A triangular Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the triangle's base. defaults to 55 pixels. + * height - height of the triangle from base to apex. defaults to 55 pixels. + */ + jsPlumb.Endpoints.Triangle = function(params) { + this.type = "Triangle"; + params = params || { }; + params.width = params.width || 55; + param.height = params.height || 55; + this.width = params.width; + this.height = params.height; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + return [ x, y, width, height ]; + }; + }; +// ********************************* END OF ENDPOINT TYPES ******************************************************************* + + +// ********************************* OVERLAY DEFINITIONS *********************************************************************** + + var AbstractOverlay = function() { + var visible = true, self = this; + this.setVisible = function(val) { + visible = val; + self.connection.repaint(); + }; + this.isVisible = function() { return visible; }; + this.hide = function() { self.setVisible(false); }; + this.show = function() { self.setVisible(true); }; + }; + + + /** + * Class: Overlays.Arrow + * + * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length + * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction + * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line + * across the tail. + */ + /** + * Function: Constructor + * + * Parameters: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + this.type = "Arrow"; + AbstractOverlay.apply(this); + params = params || {}; + var self = this; + + this.length = params.length || 20; + this.width = params.width || 20; + this.id = params.id; + this.connection = params.connection; + var direction = (params.direction || 1) < 0 ? -1 : 1; + var paintStyle = params.paintStyle || { lineWidth:1 }; + this.loc = params.location == null ? 0.5 : params.location; + // how far along the arrow the lines folding back in come to. default is 62.3%. + var foldback = params.foldback || 0.623; + var _getFoldBackPoint = function(connector, loc) { + if (foldback == 0.5) return connector.pointOnPath(loc); + else { + var adj = 0.5 - foldback; // we calculate relative to the center + return connector.pointAlongPathFrom(loc, direction * self.length * adj); + } + }; + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + // this is the arrow head position + var hxy = connector.pointAlongPathFrom(self.loc, direction * (self.length / 2)); + // this is the center of the tail + var txy = connector.pointAlongPathFrom(self.loc, -1 * direction * (self.length / 2)), tx = txy.x, ty = txy.y; + // this is the tail vector + var tail = connector.perpendicularToPathAt(self.loc, self.width, -1 * direction * (self.length / 2)); + // this is the point the tail goes in to + var cxy = _getFoldBackPoint(connector, self.loc); + + // if loc = 1, then hxy should be flush with the element, or if direction == -1, the tail midpoint. + if (self.loc == 1) { + var lxy = connector.pointOnPath(self.loc); + // TODO determine why the 1.2.6 released version does not + // use 'direction' in the two equations below, yet both + // that and 1.3.0 still paint the arrows correctly. + var dx = (lxy.x - hxy.x) * direction, dy = (lxy.y - hxy.y) * direction; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + // if loc = 0, then tail midpoint should be flush with the element, or, if direction == -1, hxy should be. + if (self.loc == 0) { + var lxy = connector.pointOnPath(self.loc); + var tailMid = foldback > 1 ? cxy : { + x:tail[0].x + ((tail[1].x - tail[0].x) / 2), + y:tail[0].y + ((tail[1].y - tail[0].y) / 2) + }; + var dx = (lxy.x - tailMid.x) * direction, dy = (lxy.y - tailMid.y) * direction; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x); + var maxx = Math.max(hxy.x, tail[0].x, tail[1].x); + var miny = Math.min(hxy.y, tail[0].y, tail[1].y); + var maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + var d = { hxy:hxy, tail:tail, cxy:cxy }, + strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle, + fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle, + lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth; + + self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions); + + return [ minx, maxx, miny, maxy]; + }; + }; + + /** + * Class: Overlays.PlainArrow + * + * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "PlainArrow"; + }; + + /** + * Class: Overlays.Diamond + * + * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40; + var p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "Diamond"; + }; + + + + /** + * Class: Overlays.Label + * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb + * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter + * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it + * puts on the Label's 'style' attribute, so the end result is the same. + */ + /** + * Function: Constructor + * + * Parameters: + * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes + * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle. + * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * labelStyle - (deprecated) js object containing style instructions for the label. defaults to jsPlumb.Defaults.LabelStyle. + * borderWidth - (deprecated) width of a border to paint. defaults to zero. + * borderStyle - (deprecated) strokeStyle to use when painting the border, if necessary. + * + */ + jsPlumb.Overlays.Label = function(params) { + this.type = "Label"; + jsPlumb.DOMElementComponent.apply(this, arguments); + AbstractOverlay.apply(this); + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.labelStyle.font = this.labelStyle.font || "12px sans-serif"; + this.label = params.label || "banana"; + this.connection = params.connection; + this.id = params.id; + var self = this; + var labelWidth = null, labelHeight = null, labelText = null, labelPadding = null; + this.location = params.location || 0.5; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var initialised = false, + labelText = null, + div = document.createElement("div"); + div.style["position"] = "absolute"; + div.style["font"] = self.labelStyle.font; + div.style["color"] = self.labelStyle.color || "black"; + if (self.labelStyle.fillStyle) div.style["background"] = self.labelStyle.fillStyle;//_convertStyle(self.labelStyle.fillStyle, true); + if (self.labelStyle.borderWidth > 0) { + var dStyle = self.labelStyle.borderStyle ? self.labelStyle.borderStyle/*_convertStyle(self.labelStyle.borderStyle, true)*/ : "black"; + div.style["border"] = self.labelStyle.borderWidth + "px solid " + dStyle; + } + if (self.labelStyle.padding) div.style["padding"] = self.labelStyle.padding; + + var clazz = params["_jsPlumb"].overlayClass + " " + + (self.labelStyle.cssClass ? self.labelStyle.cssClass : + params.cssClass ? params.cssClass : ""); + + div.className = clazz; + + jsPlumb.appendElement(div, params.connection.parent); + jsPlumb.getId(div); + self.attachListeners(div, self); + + //override setVisible + var osv = self.setVisible; + self.setVisible = function(state) { + osv(state); // call superclass + div.style.display = state ? "block" : "none"; + }; + + this.paint = function(connector, d, connectorDimensions) { + if (!initialised) { + connector.appendDisplayElement(div); + self.attachListeners(div, connector); + initialised = true; + } + div.style.left = (connectorDimensions[0] + d.minx) + "px"; + div.style.top = (connectorDimensions[1] + d.miny) + "px"; + }; + + this.getTextDimensions = function(connector) { + labelText = typeof self.label == 'function' ? self.label(self) : self.label; + div.innerHTML = labelText.replace(/\r\n/g, "
"); + var de = jsPlumb.CurrentLibrary.getElementObject(div), + s = jsPlumb.CurrentLibrary.getSize(de); + return {width:s[0], height:s[1]}; + }; + + this.computeMaxSize = function(connector) { + var td = self.getTextDimensions(connector); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + var td = self.getTextDimensions(connector); + if (td.width != null) { + var cxy = connector.pointOnPath(self.location); + + var minx = cxy.x - (td.width / 2); + var miny = cxy.y - (td.height / 2); + + self.paint(connector, { + minx:minx, + miny:miny, + td:td, + cxy:cxy + }, connectorDimensions); + + return [minx, minx+td.width, miny, miny+td.height]; + } + else return [0,0,0,0]; + }; + }; + + // ********************************* END OF OVERLAY DEFINITIONS *********************************************************************** + + // ********************************* OVERLAY CANVAS RENDERERS*********************************************************************** + + // ********************************* END OF OVERLAY CANVAS RENDERERS *********************************************************************** +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the VML renderers. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +;(function() { + + // http://ajaxian.com/archives/the-vml-changes-in-ie-8 + // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/ + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + + var vmlAttributeMap = { + "stroke-linejoin":"joinstyle", + "joinstyle":"joinstyle", + "endcap":"endcap", + "miterlimit":"miterlimit" + }; + + if (document.createStyleSheet) { + + // this is the style rule for IE7/6: it uses a CSS class, tidy. + document.createStyleSheet().addRule(".jsplumb_vml", "behavior:url(#default#VML);position:absolute;"); + + // these are for VML in IE8. you have to explicitly call out which elements + // you're going to expect to support VML! + // + // try to avoid IE8. it is recommended you set X-UA-Compatible="IE=7" if you can. + // + document.createStyleSheet().addRule("jsplumb\\:textbox", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:oval", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:rect", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:stroke", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:shape", "behavior:url(#default#VML);position:absolute;"); + + // in this page it is also mentioned that IE requires the extra arg to the namespace + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either. + // var iev = document.documentMode; + //if (!iev || iev < 8) + document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); + //else + // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML"); + } + + var scale = 1000, + _atts = function(o, atts) { + for (var i in atts) { + // IE8 fix: setattribute does not work after an element has been added to the dom! + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + //o.setAttribute(i, atts[i]); + o[i] = atts[i]; + } + }, + _node = function(name, d, atts) { + atts = atts || {}; + var o = document.createElement("jsplumb:" + name); + o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml"; + _pos(o, d); + _atts(o, atts); + return o; + }, + _pos = function(o,d) { + o.style.left = d[0] + "px"; + o.style.top = d[1] + "px"; + o.style.width= d[2] + "px"; + o.style.height= d[3] + "px"; + o.style.position = "absolute"; + }, + _conv = function(v) { + return Math.floor(v * scale); + }, + _convertStyle = function(s, ignoreAlpha) { + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + + return o; + }, + _applyStyles = function(node, style, component) { + var styleToWrite = {}; + if (style.strokeStyle) { + styleToWrite["stroked"] = "true"; + styleToWrite["strokecolor"] =_convertStyle(style.strokeStyle, true); + styleToWrite["strokeweight"] = style.lineWidth + "px"; + } + else styleToWrite["stroked"] = "false"; + + if (style.fillStyle) { + styleToWrite["filled"] = "true"; + styleToWrite["fillcolor"] = _convertStyle(style.fillStyle, true); + } + else styleToWrite["filled"] = "false"; + + if(style["dashstyle"]) { + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = style["dashstyle"]; + } + else if (style["stroke-dasharray"] && style["lineWidth"]) { + var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",", + parts = style["stroke-dasharray"].split(sep), + styleToUse = ""; + for(var i = 0; i < parts.length; i++) { + styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep); + } + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = styleToUse; + } + + _atts(node, styleToWrite); + }, + /* + * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. + */ + VmlComponent = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }, + /* + * Base class for Vml connectors. extends VmlComponent. + */ + VmlConnector = function(params) { + var self = this; + self.strokeNode = null; + self.canvas = null; + VmlComponent.apply(this, arguments); + clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : ""); + this.paint = function(d, style, anchor) { + if (style != null) { + var path = self.getPath(d), p = { "path":path }; + + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + outlineStyle = { + strokeStyle:_convertStyle(style.outlineColor), + lineWidth:outlineStrokeWidth + }; + + if (self.bgCanvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.bgCanvas = _node("shape", d, p); + jsPlumb.appendElement(self.bgCanvas, params.parent); + _pos(self.bgCanvas, d); + displayElements.push(self.bgCanvas); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.bgCanvas, d); + _atts(self.bgCanvas, p); + } + + _applyStyles(self.bgCanvas, outlineStyle, self); + } + + if (self.canvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.canvas = _node("shape", d, p); + jsPlumb.appendElement(self.canvas, params.parent); + displayElements.push(self.canvas); + + self.attachListeners(self.canvas, self); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.canvas, d); + _atts(self.canvas, p); + } + + _applyStyles(self.canvas, style, self); + } + }; + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + self.canvas.parentNode.appendChild(el); + displayElements.push(el); + }; + }, + /* + * + * Base class for Vml Endpoints. extends VmlComponent. + * + */ + VmlEndpoint = function(params) { + VmlComponent.apply(this, arguments); + var vml = null, self = this; + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function(d, style, anchor) { + var p = { }; + + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (vml == null) { + p["class"] = jsPlumb.endpointClass; + vml = self.getVml([0,0, d[2], d[3]], p, anchor); + self.canvas.appendChild(vml); + self.attachListeners(vml, self); + } + else { + //p["coordsize"] = "1,1";//(d[2] * scale) + "," + (d[3] * scale); again, unsure. + _pos(vml, [0,0, d[2], d[3]]); + _atts(vml, p); + } + + _applyStyles(vml, style); + }; + }; + + jsPlumb.Connectors.vml.Bezier = function() { + jsPlumb.Connectors.Bezier.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Straight = function() { + jsPlumb.Connectors.Straight.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Flowchart = function() { + jsPlumb.Connectors.Flowchart.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l"; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]); + } + // finally draw a line to the end + p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e"; + return p; + }; + }; + + jsPlumb.Endpoints.vml.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("oval", d, atts); }; + }; + + jsPlumb.Endpoints.vml.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("rect", d, atts); }; + }; + + /* + * VML Image Endpoint is the same as the default image endpoint. + */ + jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image; + + /** + * placeholder for Blank endpoint in vml renderer. + */ + jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank; + + /** + * VML Label renderer. uses the default label renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label; + + var AbstractVmlArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + VmlComponent.apply(this, arguments); + var self = this, canvas = null, path =null; + var getPath = function(d, connectorDimensions) { + return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) + + " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + + " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + + " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + + " x e"; + }; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) { + var p = {}; + if (strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] =_convertStyle(strokeStyle, true); + } + if (lineWidth) p["strokeweight"] = lineWidth + "px"; + if (fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = fillStyle; + } + var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + w = Math.abs(xmax - xmin), + h = Math.abs(ymax - ymin), + dim = [xmin, ymin, w, h]; + + // for VML, we create overlays using shapes that have the same dimensions and + // coordsize as their connector - overlays calculate themselves relative to the + // connector (it's how it's been done since the original canvas implementation, because + // for canvas that makes sense). + p["path"] = getPath(d, connectorDimensions); + p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale); + + dim[0] = connectorDimensions[0]; + dim[1] = connectorDimensions[1]; + dim[2] = connectorDimensions[2]; + dim[3] = connectorDimensions[3]; + + if (canvas == null) { + //p["class"] = jsPlumb.overlayClass; // TODO currentInstance? + canvas = _node("shape", dim, p); + connector.appendDisplayElement(canvas); + self.attachListeners(canvas, connector); + } + else { + _pos(canvas, dim); + _atts(canvas, p); + } + }; + }; + + jsPlumb.Overlays.vml.Arrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.vml.PlainArrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.vml.Diamond = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the SVG renderers. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +/** + * SVG support for jsPlumb. + * + * things to investigate: + * + * gradients: https://developer.mozilla.org/en/svg_in_html_introduction + * css:http://tutorials.jenkov.com/svg/svg-and-css.html + * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath + * pointer events: https://developer.mozilla.org/en/css/pointer-events + * + */ +;(function() { + + var svgAttributeMap = { + "stroke-linejoin":"stroke-linejoin", + "joinstyle":"stroke-linejoin", + "stroke-dashoffset":"stroke-dashoffset" + }; + + var ns = { + svg:"http://www.w3.org/2000/svg", + xhtml:"http://www.w3.org/1999/xhtml" + }, + _attr = function(node, attributes) { + for (var i in attributes) + node.setAttribute(i, "" + attributes[i]); + }, + _node = function(name, attributes) { + var n = document.createElementNS(ns.svg, name); + attributes = attributes || {}; + attributes["version"] = "1.1"; + attributes["xmnls"] = ns.xhtml; + _attr(n, attributes); + return n; + }, + _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; }, + _convertStyle = function(s, ignoreAlpha) { + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + + return o; + }, + _clearGradient = function(parent) { + for (var i = 0; i < parent.childNodes.length; i++) { + if (parent.childNodes[i].tagName == "linearGradient" || parent.childNodes[i].tagName == "radialGradient") + parent.removeChild(parent.childNodes[i]); + } + }, + _updateGradient = function(parent, node, style, dimensions) { + var id = "jsplumb_gradient_" + (new Date()).getTime(); + // first clear out any existing gradient + _clearGradient(parent); + // this checks for an 'offset' property in the gradient, and in the absence of it, assumes + // we want a linear gradient. if it's there, we create a radial gradient. + // it is possible that a more explicit means of defining the gradient type would be + // better. relying on 'offset' means that we can never have a radial gradient that uses + // some default offset, for instance. + if (!style.gradient.offset) { + var g = _node("linearGradient", {id:id}); + parent.appendChild(g); + } + else { + var g = _node("radialGradient", { + id:id + }); + parent.appendChild(g); + } + + // the svg radial gradient seems to treat stops in the reverse + // order to how canvas does it. so we want to keep all the maths the same, but + // iterate the actual style declarations in reverse order, if the x indexes are not in order. + for (var i = 0; i < style.gradient.stops.length; i++) { + // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of + // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments. + // so, not too concerned about leaving this in for now. + var styleToUse = i; + if (dimensions.length == 8) + styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i; + else + styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i; + var stopColor = _convertStyle(style.gradient.stops[styleToUse][1], true); + var s = _node("stop", {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor}); + g.appendChild(s); + } + var applyGradientTo = style.strokeStyle ? "stroke" : "fill"; + node.setAttribute("style", applyGradientTo + ":url(#" + id + ")"); + }, + _applyStyles = function(parent, node, style, dimensions) { + + if (style.gradient) { + _updateGradient(parent, node, style, dimensions); + } + else { + // make sure we clear any existing gradient + _clearGradient(parent); + node.setAttribute("style", ""); + } + + node.setAttribute("fill", style.fillStyle ? _convertStyle(style.fillStyle, true) : "none"); + node.setAttribute("stroke", style.strokeStyle ? _convertStyle(style.strokeStyle, true) : "none"); + if (style.lineWidth) { + node.setAttribute("stroke-width", style.lineWidth); + } + + // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like + // the syntax in VML but is actually kind of nasty: values are given in the pixel + // coordinate space, whereas in VML they are multiples of the width of the stroked + // line, which makes a lot more sense. for that reason, jsPlumb is supporting both + // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from + // VML, which will be the preferred method. the code below this converts a dashstyle + // attribute given in terms of stroke width into a pixel representation, by using the + // stroke's lineWidth. + if(style["stroke-dasharray"]) { + node.setAttribute("stroke-dasharray", style["stroke-dasharray"]); + } + if (style["dashstyle"] && style["lineWidth"]) { + var sep = style["dashstyle"].indexOf(",") == -1 ? " " : ",", + parts = style["dashstyle"].split(sep), + styleToUse = ""; + parts.forEach(function(p) { + styleToUse += (Math.floor(p * style.lineWidth) + sep); + }); + node.setAttribute("stroke-dasharray", styleToUse); + } + + // extra attributes such as join type, dash offset. + for (var i in svgAttributeMap) { + if (style[i]) { + node.setAttribute(svgAttributeMap[i], style[i]); + } + } + }, + _decodeFont = function(f) { + var r = /([0-9].)(p[xt])\s(.*)/; + var bits = f.match(r); + return {size:bits[1] + bits[2], font:bits[3]}; + }; + + /* + * Base class for SVG components. + */ + var SvgComponent = function(cssClass, originalArgs, pointerEventsSpec) { + var self = this; + pointerEventsSpec = pointerEventsSpec || "all"; + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + self.canvas = null, self.path = null, self.svg = null; + + this.setHover = function() { }; + + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.sizeCanvas(self.canvas,0,0,1,1); + + var clazz = cssClass + " " + (originalArgs[0].cssClass || ""); + self.canvas.className = clazz; + + self.svg = _node("svg", { + "style":"", + "width":0, + "height":0, + "pointer-events":pointerEventsSpec/*, + "class": clazz*/ + }); + + jsPlumb.appendElement(self.canvas, originalArgs[0]["parent"]); + self.canvas.appendChild(self.svg); + + // TODO this displayElement stuff is common between all components, across all + // renderers. would be best moved to jsPlumbUIComponent. + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + this.paint = function(d, style, anchor) { + if (style != null) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + _attr(self.svg, { + "style":_pos([0,0,d[2], d[3]]), + "width": d[2], + "height": d[3] + }); + self._paint.apply(this, arguments); + } + }; + }; + + /* + * Base class for SVG connectors. + */ + var SvgConnector = function(params) { + var self = this; + SvgComponent.apply(this, [ params["_jsPlumb"].connectorClass, arguments, "none" ]); + this._paint = function(d, style) { + var p = self.getPath(d), a = { "d":p }, outlineStyle = null; + a["pointer-events"] = "all"; + + // outline style. actually means drawing an svg object underneath the main one. + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + outlineStyle = { + strokeStyle:_convertStyle(style.outlineColor), + lineWidth:outlineStrokeWidth + }; + + if (self.bgPath == null) { + self.bgPath = _node("path", a); + self.svg.appendChild(self.bgPath); + self.attachListeners(self.bgPath, self); + } + else { + _attr(self.bgPath, a); + } + + _applyStyles(self.svg, self.bgPath, outlineStyle, d); + } + + + if (self.path == null) { + self.path = _node("path", a); + self.svg.appendChild(self.path); + self.attachListeners(self.path, self); + } + else { + _attr(self.path, a); + } + + _applyStyles(self.svg, self.path, style, d); + }; + }; + + /* + * SVG Bezier Connector + */ + jsPlumb.Connectors.svg.Bezier = function(params) { + jsPlumb.Connectors.Bezier.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]; }; + }; + + /* + * SVG straight line Connector + */ + jsPlumb.Connectors.svg.Straight = function(params) { + jsPlumb.Connectors.Straight.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; }; + }; + + jsPlumb.Connectors.svg.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "M " + dimensions[4] + "," + dimensions[5]; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)]; + } + // finally draw a line to the end + p = p + " " + dimensions[6] + "," + dimensions[7]; + return p; + }; + }; + + /* + * Base class for SVG endpoints. + */ + var SvgEndpoint = function(params) { + var self = this; + SvgComponent.apply(this, [ params["_jsPlumb"].endpointClass, arguments, "all" ]); + this._paint = function(d, style) { + var s = jsPlumb.extend({}, style); + if (s.outlineColor) { + s.strokeWidth = s.outlineWidth; + s.strokeStyle = _convertStyle(s.outlineColor, true); + } + + if (self.node == null) { + self.node = self.makeNode(d, s); + self.svg.appendChild(self.node); + self.attachListeners(self.node, self); + } + _applyStyles(self.svg, self.node, s, d); + _pos(self.node, d); + }; + }; + + /* + * SVG Dot Endpoint + */ + jsPlumb.Endpoints.svg.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("circle", { + "cx" : d[2] / 2, + "cy" : d[3] / 2, + "r" : d[2] / 2 + }); + }; + }; + + /* + * SVG Rectangle Endpoint + */ + jsPlumb.Endpoints.svg.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("rect", { + "width":d[2], + "height":d[3] + }); + }; + }; + + /* + * SVG Image Endpoint is the default image endpoint. + */ + jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image; + /* + * Blank endpoint in svg renderer is the default Blank endpoint. + */ + jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank; + /* + * Label endpoint in svg renderer is the default Label endpoint. + */ + jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label; + + + var AbstractSvgArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + var self = this, path =null; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + } + + _attr(path, { + "d" : makePath(d), + stroke : strokeStyle ? strokeStyle : null, + fill : fillStyle ? fillStyle : null + }); + }; + var makePath = function(d) { + return "M" + d.hxy.x + "," + d.hxy.y + + " L" + d.tail[0].x + "," + d.tail[0].y + + " L" + d.cxy.x + "," + d.cxy.y + + " L" + d.tail[1].x + "," + d.tail[1].y + + " L" + d.hxy.x + "," + d.hxy.y; + }; + }; + + jsPlumb.Overlays.svg.Arrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.svg.PlainArrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.svg.Diamond = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the HTML5 canvas renderers. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +;(function() { + +// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS ******************************************************************* + + // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. + var _connectionBeingDragged = null, + _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }, + _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }, + _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }, + _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, + _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }, + _setOffset = function(el, o) { jsPlumb.CurrentLibrary.setOffset(el, o); }; + + /* + * Class:CanvasMouseAdapter + * Provides support for mouse events on canvases. + */ + var CanvasMouseAdapter = function() { + var self = this; + self.overlayPlacements = []; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + jsPlumb.EventGenerator.apply(this, arguments); + /** + * returns whether or not the given event is ojver a painted area of the canvas. + */ + this._over = function(e) { + var o = _getOffset(_getElementObject(self.canvas)), + pageXY = _pageXY(e), + x = pageXY[0] - o.left, y = pageXY[1] - o.top; + if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + return false; + }; + + var _mouseover = false; + var _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false; + var _nullSafeHasClass = function(el, clazz) { + return el != null && _hasClass(el, clazz); + }; + this.mousemove = function(e) { + var pageXY = _pageXY(e), clientXY = _clientXY(e), + ee = document.elementFromPoint(clientXY[0], clientXY[1]), + eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); + var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); + if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.fire("mouseenter", self, e); + return true; + } + // TODO here there is a remote chance that the overlay the mouse moved onto + // is actually not an overlay for the current component. a more thorough check would + // be to ensure the overlay belonged to the current component. + else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { + _mouseover = false; + self.fire("mouseexit", self, e); + } + self.fire("mousemove", self, e); + }; + + this.click = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); + self.fire("mousedown", self, e); + } + }; + + this.mouseup = function(e) { + //if (self == _connectionBeingDragged) _connectionBeingDragged = null; + _mouseDown = false; + self.fire("mouseup", self, e); + }; + }; + + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + jsPlumb.appendElement(canvas, params.parent); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + params["_jsPlumb"].getId(canvas, params.uuid); + + return canvas; + }; + + /** + * Class:CanvasConnector + * Superclass for Canvas Connector renderers. + */ + var CanvasConnector = jsPlumb.CanvasConnector = function(params) { + + CanvasMouseAdapter.apply(this, arguments); + + var _paintOneStyle = function(dim, aStyle) { + self.ctx.save(); + jsPlumb.extend(self.ctx, aStyle); + if (aStyle.gradient) { + var g = self.createGradient(dim, self.ctx); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + self.ctx.strokeStyle = g; + } + self._paint(dim); + self.ctx.restore(); + }; + + var self = this, + clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent + }); + self.ctx = self.canvas.getContext("2d"); + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + self.paint = function(dim, style) { + if (style != null) { + + jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); + + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + _paintOneStyle(dim, outlineStyle); + } + _paintOneStyle(dim, style); + } + }; + }; + + /** + * Class:CanvasEndpoint + * Superclass for Canvas Endpoint renderers. + */ + var CanvasEndpoint = function(params) { + var self = this; + CanvasMouseAdapter.apply(this, arguments); + var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent + }); + self.ctx = self.canvas.getContext("2d"); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + // _paintOneStyle(d, outlineStyle); + } + + self._paint.apply(this, arguments); + }; + }; + + jsPlumb.Endpoints.canvas.Dot = function(params) { + var self = this; + jsPlumb.Endpoints.Dot.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }; + var calculateAdjustments = function(gradient) { + var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius; + gradient.offset && (offsetAdjustment = parseValue(gradient.offset)); + gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius)); + return [offsetAdjustment, innerRadius]; + }; + this._paint = function(d, style, anchor) { + if (style != null) { + var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(); + jsPlumb.extend(ctx, style); + if (style.gradient) { + var adjustments = calculateAdjustments(style.gradient), + yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0], + xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0], + g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + ctx.beginPath(); + ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + } + }; + }; + + jsPlumb.Endpoints.canvas.Rectangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) { + + var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(); + jsPlumb.extend(ctx, style); + + /* canvas gradient */ + if (style.gradient) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, d[2], d[3]); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + jsPlumb.Endpoints.canvas.Triangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Triangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) + { + var width = d[2], height = d[3], x = d[0], y = d[1]; + + var ctx = self.canvas.getContext('2d'); + var offsetX = 0, offsetY = 0, angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = style.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + /* + * Canvas Image Endpoint: uses the default version, which creates an tag. + */ + jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image; + + /* + * Blank endpoint in all renderers is just the default Blank endpoint. + */ + jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank; + + /* + * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Bezier = function() { + var self = this; + jsPlumb.Connectors.Bezier.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO i doubt this handles the case that source and target are swapped. + this.createGradient = function(dim, ctx, swap) { + return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + /* + * Canvas straight line Connector. Draws a straight line onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Straight = function() { + var self = this; + jsPlumb.Connectors.Straight.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO this does not handle the case that src and target are swapped. + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + jsPlumb.Connectors.canvas.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + +// ********************************* END OF CANVAS RENDERERS ******************************************************************* + + jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label; + + /** + * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. + */ + var CanvasOverlay = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }; + + var AbstractCanvasArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + CanvasOverlay.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + var ctx = connector.ctx; + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(d.hxy.x, d.hxy.y); + ctx.lineTo(d.tail[0].x, d.tail[0].y); + ctx.lineTo(d.cxy.x, d.cxy.y); + ctx.lineTo(d.tail[1].x, d.tail[1].y); + ctx.lineTo(d.hxy.x, d.hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + }; + + jsPlumb.Overlays.canvas.Arrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.canvas.PlainArrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.canvas.Diamond = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the jQuery adapter. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ +/* + * the library specific functions, such as find offset, get id, get attribute, extend etc. + * the full list is: + * + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getDropScope gets the drop scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getPageXY gets the page event's xy location. + * getParent gets the parent of some element. + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * hasClass returns whether or not the given element has the given class. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + */ +(function($) { + + //var getBoundingClientRectSupported = "getBoundingClientRect" in document.documentElement; + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * appends the given child to the given parent. + */ + appendElement : function(child, parent) { + jsPlumb.CurrentLibrary.getElementObject(parent).append(child); + }, + + /** + * event binding wrapper. it just so happens that jQuery uses 'bind' also. yui3, for example, + * uses 'on'. + */ + bind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.bind(event, callback); + }, + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step', + 'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete' + }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + getDocumentElement : function() { return document; }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + }, + + getDragScope : function(el) { + return el.draggable("option", "scope"); + }, + + getDropScope : function(el) { + return el.droppable("option", "scope"); + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + * + */ + getElementObject : function(el) { + return typeof(el)=='string' ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).parent(); + }, + + getScrollLeft : function(el) { + return el.scrollLeft(); + }, + + getScrollTop : function(el) { + return el.scrollTop(); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + + // this code is a workaround for the case that the element being dragged has a margin set on it. jquery UI passes + // in the wrong offset if the element has a margin (it doesn't take the margin into account). the getBoundingClientRect + // method, which is in pretty much all browsers now, reports the right numbers. but it introduces a noticeable lag, which + // i don't like. + + /*if ( getBoundingClientRectSupported ) { + var r = eventArgs[1].helper[0].getBoundingClientRect(); + return { left : r.left, top: r.top }; + } else {*/ + var ui = eventArgs[1], _offset = ui.offset; + return _offset || ui.absolutePosition; + //} + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options) { + // remove helper directive if present. + options.helper = null; + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.draggable(options); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.droppable(options); + }, + + isAlreadyDraggable : function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return el.hasClass("ui-draggable"); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).remove(); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * sets the drag scope. probably time for a setDragOption method (roll this and the one above together) + * @param el + * @param scope + */ + setDragScope : function(el, scope) { + el.draggable("option", "scope", scope); + }, + + setOffset : function(el, o) { + jsPlumb.CurrentLibrary.getElementObject(el).offset(o); + } + }; + + $(document).ready(jsPlumb.init); + +})(jQuery); +(function(){if(typeof Math.sgn=="undefined")Math.sgn=function(a){return a==0?0:a>0?1:-1};var p={subtract:function(a,b){return{x:a.x-b.x,y:a.y-b.y}},dotProduct:function(a,b){return a.x*b.x+a.y*b.y},square:function(a){return Math.sqrt(a.x*a.x+a.y*a.y)},scale:function(a,b){return{x:a.x*b,y:a.y*b}}},y=Math.pow(2,-65),u=function(a,b){for(var g=[],d=b.length-1,h=2*d-1,f=[],c=[],l=[],k=[],i=[[1,0.6,0.3,0.1],[0.4,0.6,0.6,0.4],[0.1,0.3,0.6,1]],e=0;e<=d;e++)f[e]=p.subtract(b[e],a);for(e=0;e<=d-1;e++){c[e]= +p.subtract(b[e+1],b[e]);c[e]=p.scale(c[e],3)}for(e=0;e<=d-1;e++)for(var m=0;m<=d;m++){l[e]||(l[e]=[]);l[e][m]=p.dotProduct(c[e],f[m])}for(e=0;e<=h;e++){k[e]||(k[e]=[]);k[e].y=0;k[e].x=parseFloat(e)/h}h=d-1;for(f=0;f<=d+h;f++){c=Math.min(f,d);for(e=Math.max(0,f-h);e<=c;e++){j=f-e;k[e+j].y+=l[j][e]*i[j][e]}}d=b.length-1;k=s(k,2*d-1,g,0);h=p.subtract(a,b[0]);l=p.square(h);for(e=i=0;e=64){g[0]=(a[0].x+a[b].x)/2;return 1}var n,o,q;k=a[0].y-a[b].y;i=a[b].x-a[0].x;e=a[0].x*a[b].y-a[b].x*a[0].y;m=max_distance_below=0;for(o=1;om)m=q;else if(q0?1:-1,c=null;h 1) ? "endpoints" : "endpoint"); +}; + +var assertConnectionCount = function(endpoint, count) { + equals(endpoint.connections.length, count, "endpoint has " + count + " connections"); +}; + +var assertConnectionByScopeCount = function(scope, count) { + equals(jsPlumb.getTestHarness().connectionCount(scope), count, 'Scope ' + scope + " has " + count + (count > 1) ? "connections" : "connection"); + +}; + +var _divs = []; +var _addDiv = function(id) { + var d1 = document.createElement("div"); + _getContextNode().append(d1); + d1 = jsPlumb.CurrentLibrary.getElementObject(d1); + jsPlumb.CurrentLibrary.setAttribute(d1, "id", id); + _divs.push(id); + return d1; +}; + +var _cleanup = function() { + + jsPlumb.reset(); + jsPlumb.Defaults.Container = null; + + for (var i in _divs) { + $("#" + _divs[i]).remove(); + } + _divs.splice(0, _divs.length - 1); +}; + +var testSuite = function(renderMode) { + + module("jsPlumb", {teardown: _cleanup}); + + // setup the container + var container = document.createElement("div"); + container.id = "container"; + document.body.appendChild(container); + + jsPlumb.setRenderMode(renderMode); + + test(renderMode + ': findIndex method', function() { + var array = [ 1,2,3, "test", "a string", { 'foo':'bar', 'baz':1 }, { 'ding':'dong' } ]; + equals(jsPlumb.getTestHarness().findIndex(array, 1), 0, "find works for integers"); + equals(jsPlumb.getTestHarness().findIndex(array, "test"), 3, "find works for strings"); + equals(jsPlumb.getTestHarness().findIndex(array, { 'foo':'bar', 'baz':1 }), 5, "find works for objects"); + equals(jsPlumb.getTestHarness().findIndex(array, { 'ding':'dong', 'baz':1 }), -1, "find works properly for objects (objects have different length but some same properties)"); + }); + + test(renderMode + ': jsPlumb setup', function() { + ok(jsPlumb, "loaded"); + }); + + test(renderMode + ': getId', function() { + var d10 = _addDiv('d10'); + equals(jsPlumb.getTestHarness().getId(d10), "d10"); + }); + + test(renderMode + ': create a simple endpoint', function() { + var d1 = _addDiv("d1"); + var e = jsPlumb.addEndpoint($("#d1"), {}); + ok(e, 'endpoint exists'); + assertEndpointCount("d1", 1); + }); + + test(renderMode + ': create and remove a simple endpoint', function() { + var d1 = _addDiv("d1"); + var ee = jsPlumb.addEndpoint(d1, {uuid:"78978597593"}); + ok(ee != null, "endpoint exists"); + var e = jsPlumb.getEndpoint("78978597593"); + ok(e != null, "the endpoint could be retrieved by UUID"); + assertEndpointCount("d1", 1); + assertContextSize(1); // one Endpoint canvas. + jsPlumb.removeEndpoint(d1, ee); + assertEndpointCount("d1", 0); + e = jsPlumb.getEndpoint("78978597593"); + equals(e, null, "the endpoint has been deleted"); + assertContextSize(0); // no Endpoint canvases. + }); + + test(renderMode + ': create two simple endpoints, registered using a selector', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + d1.addClass("window");d2.addClass("window"); + var endpoints = jsPlumb.addEndpoint($(".window"), {}); + equals(endpoints.length, 2, "endpoint added to both windows"); + assertEndpointCount("d1", 1); + assertEndpointCount("d2", 1); + }); + + test(renderMode + ': create two simple endpoints, registered using an array of element ids', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + d1.addClass("window");d2.addClass("window"); + var endpoints = jsPlumb.addEndpoint(["d1", "d2"], {}); + equals(endpoints.length, 2, "endpoint added to both windows"); + assertEndpointCount("d1", 1); + assertEndpointCount("d2", 1); + }); + + test(renderMode + ': draggable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1"); + var e = jsPlumb.addEndpoint(d1, {isSource:true}); + ok(e, 'endpoint exists'); + }); + + test(renderMode + ': droppable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1") + var e = jsPlumb.addEndpoint(d1, {isTarget:true}); + ok(e, 'endpoint exists'); + }); + + test(renderMode + ': defaultEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = jsPlumb.addEndpoint(d3, {isSource:true}); + ok(e3.anchor, 'endpoint 3 has an anchor'); + var e4 = jsPlumb.addEndpoint(d4, {isSource:true}); + assertEndpointCount("d3", 1); + assertEndpointCount("d4", 1); + assertContextSize(2); // one canvas for each of our two endpoints. + ok(!e3.isFull(), "endpoint 3 is not full."); + jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // we have one connection + jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // should have refused the connection; default max is 1. + assertContextSize(3); // now we have one more canvas, for the Connection that was accepted. + }); + + test(renderMode + ': specifiedEndpointMaxConnections', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = jsPlumb.addEndpoint(d5, {isSource:true, maxConnections:3}); + ok(e5.anchor, 'endpoint 5 has an anchor'); + var e6 = jsPlumb.addEndpoint(d6, {isSource:true, maxConnections:2}); // this one has max TWO + assertEndpointCount("d5", 1); assertEndpointCount("d6", 1); + assertContextSize(2); + ok(!e5.isFull(), "endpoint 5 is not full."); + jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 1); // we have one connection + assertContextSize(3); + jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // two connections + assertContextSize(4); + jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // should have refused; max is 2, for d4. + assertContextSize(4); + }); + + test(renderMode + ': noEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + var e4 = jsPlumb.addEndpoint(d4, {isSource:true, maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertContextSize(3); + assertConnectionCount(e3, 1); // we have one connection + jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 2); // we have two. etc (default was one. this proves max is working). + assertContextSize(4); + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e4}); + assertConnectionCount(e4, 3); + assertContextSize(6); + }); + + test(renderMode + ': anchors equal', function() { + var a1 = jsPlumb.makeAnchor(0, 1, 1, 1); + var a2 = jsPlumb.makeAnchor(0, 1, 1, 1); + ok(a1.equals(a2), "anchors are the same"); + }); + + test(renderMode + ': anchors equal with offsets', function() { + var a1 = jsPlumb.makeAnchor(0, 1, 1, 1, 10, 13); + var a2 = jsPlumb.makeAnchor(0, 1, 1, 1, 10, 13); + ok(a1.equals(a2), "anchors are the same"); + }); + + test(renderMode + ': anchors not equal', function() { + var a1 = jsPlumb.makeAnchor(0, 1, 0, 1); + var a2 = jsPlumb.makeAnchor(0, 1, 1, 1); + ok(!a1.equals(a2), "anchors are different"); + }); + + test(renderMode + ': anchor not equal with offsets', function() { + var a1 = jsPlumb.makeAnchor(0, 1, 1, 1, 10, 13); + var a2 = jsPlumb.makeAnchor(0, 1, 1, 1); + ok(!a1.equals(a2), "anchors are different"); + }); + + test(renderMode + ': detach plays nice when no target given', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + jsPlumb.connect({source:d3, target:d4}); + jsPlumb.detach(); + }); + + // issue 81 + test(renderMode + ': jsPlumb.detach should fire only one detach event (pass Connection as argument)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + jsPlumb.detach(conn); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': jsPlumb.detach should fire only one detach event (pass Connection as param in argument)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + jsPlumb.detach({connection:conn}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': jsPlumb.detach should fire only one detach event (pass source and targets as strings as arguments in params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + jsPlumb.detach({source:"d5", target:"d6"}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': jsPlumb.detach should fire only one detach event (pass source and targets as divs as arguments in params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + jsPlumb.detach({source:d5, target:d6}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': jsPlumb.detach should fire only one detach event (pass source and targets as divs as arguments)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + jsPlumb.detach(d5, d6); + equals(eventCount, 1); + }); + + //TODO make sure you run this test with a single detach call, to ensure that + // single detach calls result in the connection being removed. detachEverything can + // just blow away the connectionsByScope array and recreate it. + test(renderMode + ': jsPlumb.getConnections (simple case, default scope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + jsPlumb.connect({source:d5, target:d6}); + assertContextSize(3); + var c = jsPlumb.getConnections(); // will get all connections in the default scope. + equals(c.length, 1, "there is one connection"); + }); + + test(renderMode + ': jsPlumb.getConnections (simple case, default scope; detach by element id)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + jsPlumb.connect({source:d5, target:d6}); + jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections in the default scope + equals(c.length, 2, "there are two connections initially"); + jsPlumb.detach('d5', 'd6'); + c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': jsPlumb.getConnections (simple case, default scope; detach by element id using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + jsPlumb.connect({source:d5, target:d6}); + jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + jsPlumb.detach({source:'d5', target:'d6'}); + c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': jsPlumb.getConnections (simple case, default scope; detach by element object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + jsPlumb.connect({source:d5, target:d6}); + jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + jsPlumb.detach(d5, d6); + c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': jsPlumb.getConnections (simple case, default scope; detach by element object using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + jsPlumb.connect({source:d5, target:d6}); + jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + jsPlumb.detach({source:d5, target:d6}); + c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': jsPlumb.getConnections (simple case, default scope; detach by Connection)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + var c56 = jsPlumb.connect({source:d5, target:d6}); + var c67 = jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + jsPlumb.detach(c56); + assertContextSize(5); // check that the connection canvas was removed. + c = jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + + }); + + test(renderMode + ': jsPlumb.getConnections (scope testScope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + jsPlumb.connect({source:d5, target:d6, scope:'testScope'}); + var c = jsPlumb.getConnections("testScope"); // will get all connections in testScope + equals(c.length, 1, "there is one connection"); + equals(c[0].sourceId, 'd5', "the connection's source is d5"); + equals(c[0].targetId, 'd6', "the connection's source is d6"); + c = jsPlumb.getConnections(); // will get all connections in default scope; should be none. + equals(c.length, 0, "there are no connections in the default scope"); + }); + + test(renderMode + ': jsPlumb.getAllConnections (filtered by scope)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + jsPlumb.connect({source:d9, target:d10}); // default scope + var c = jsPlumb.getAllConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 1); + equals(c['testScope'].length, 1); + // now supply a list of scopes + c = jsPlumb.getConnections(); + equals(c.length, 1); + c = jsPlumb.getConnections("testScope"); + equals(c.length, 1, "there is one connection in 'testScope'"); + }); + + test(renderMode + ': jsPlumb.getConnections (filtered by scope and sourceId)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + jsPlumb.connect({source:d9, target:d8, scope:'testScope'}); + jsPlumb.connect({source:d9, target:d10}); // default scope + var c = jsPlumb.getConnections({scope:'testScope', source:'d8'}); // will get all connections with sourceId 'd8' + equals(c.length, 1, "there is one connection in 'testScope' from d8"); + }); + + test(renderMode + ': jsPlumb.getConnections (filtered by scope, source id and target id)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d12, target:d13, scope:'testScope'}); + jsPlumb.connect({source:d11, target:d13, scope:'testScope'}); + var c = jsPlumb.getConnections({scope:'testScope', source:'d11', target:'d13'}); + equals(c.length, 1, "there is one connection from d11 to d13"); + }); + + test(renderMode + ': jsPlumb.getConnections (filtered by a list of scopes)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = jsPlumb.getConnections({scope:['testScope','testScope3']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + }); + + test(renderMode + ': jsPlumb.getConnections (filtered by a list of scopes and source ids)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + }); + + test(renderMode + ': jsPlumb.getConnections (filtered by a list of scopes, source ids and target ids)', function() { + jsPlumb.detachEverything(); + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'), d14=_addDiv("d14"), d15=_addDiv("d15"); + jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d11, target:d14, scope:'testScope'}); + jsPlumb.connect({source:d11, target:d15, scope:'testScope'}); + jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + assertContextSize(18); + var c = jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11'], target:['d14', 'd15']}); + equals(c['testScope'].length, 2, 'there are two connections in testScope'); + equals(c['testScope3'], null, 'there are no connections in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + var anEntry = c['testScope'][0]; + ok(anEntry.endpoints[0] != null, "Source endpoint is set"); + ok(anEntry.endpoints[1] != null, "Target endpoint is set"); + equals(anEntry.source.attr("id"), "d11", "Source is div d11"); + equals(anEntry.target.attr("id"), "d14", "Target is div d14"); + }); + + test(renderMode + ': getEndpoints, one Endpoint added by addEndpoint, get Endpoints by selector', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + jsPlumb.addEndpoint(d1); + var e = jsPlumb.getEndpoints(d1); + equals(e.length, 1, "there is one endpoint for element d1"); + }); + + test(renderMode + ': getEndpoints, one Endpoint added by addEndpoint, get Endpoints by id', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + jsPlumb.addEndpoint(d1); + var e = jsPlumb.getEndpoints("d1"); + equals(e.length, 1, "there is one endpoint for element d1"); + }); + + test(renderMode + ': connection event listeners', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null/*, returnedParams2 = null*/; + jsPlumb.bind("jsPlumbConnection", function(params) { + returnedParams = $.extend({}, params); + }); + var c = jsPlumb.connect({source:d1, target:d2}); + ok(returnedParams != null, "new connection listener event was fired"); + ok(returnedParams.connection != null, 'connection is set'); + equals(returnedParams.sourceId, "d1", 'sourceid is set'); + equals(returnedParams.targetId, "d2", 'targetid is set'); + equals(returnedParams.source.attr("id"), "d1", 'source is set'); + equals(returnedParams.target.attr("id"), "d2" , 'target is set'); + ok(returnedParams.sourceEndpoint != null, "source endpoint is not null"); + ok(returnedParams.targetEndpoint != null, "target endpoint is not null"); + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + jsPlumb.detach(c); + ok(returnedParams.connection != null, 'connection is set'); + //jsPlumb.detachAll("d1"); + //ok(returnedParams2 != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (detach by connection)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + ok(returnedParams.connection != null, "removed connection listener event passed in connection"); + }); + + test(renderMode + ': detach event listeners (detach by element ids)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach("d1","d2"); + ok(returnedParams != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (detach by elements)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach(d1,d2); + ok(returnedParams != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detach method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detachFrom method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachFrom(e2); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detachAll method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachAll(); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via jsPlumb.deleteEndpoint method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + jsPlumb.deleteEndpoint(e1); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(1); + }); + + test(renderMode + ': detach event listeners (ensure cleared by jsPlumb.reset)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach(d1,d2); + ok(returnedParams != null, "removed connection listener event was fired"); + returnedParams = null; + + jsPlumb.reset(); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach(d1,d2); + ok(returnedParams == null, "connection listener was cleared by jsPlumb.reset()"); + }); + + test(renderMode + ': connection events that throw errors', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null, returnedParams2 = null; + jsPlumb.bind("jsPlumbConnection", function(params) { + returnedParams = $.extend({}, params); + throw "oh no!"; + }); + jsPlumb.connect({source:d1, target:d2}); + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + jsPlumb.connect({source:d3, target:d4}); + ok(returnedParams != null, "new connection listener event was fired; we threw an error, jsPlumb survived."); + }); + + test(renderMode + ": Endpoint.detachFrom", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + e16.detachFrom(e17); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + }); + + test(renderMode + ": Endpoint.detachFromConnection", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + e16.detachFromConnection(conn); + assertContextSize(3); // all canvases should remain; the connection was not removed. + // but endpoint e16 should have no connections now. + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 1); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + }); + + test(renderMode + ": Endpoint.detachAll", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + var e16 = jsPlumb.addEndpoint($("#d16"), {isSource:true,maxConnections:-1}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint($("#d17"), {isSource:true}); + var e18 = jsPlumb.addEndpoint($("#d18"), {isSource:true}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e18}); + assertConnectionCount(e16, 2); + assertConnectionCount(e17, 1); + assertConnectionCount(e18, 1); + assertContextSize(5); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 2); + e16.detachAll(); + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + }); + + test(renderMode + ": Endpoint.detach", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + e16.detach(conn); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + }); + + test(renderMode + ": Endpoint.isConnectedTo", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.isConnectedTo(e17), true, "e16 and e17 are connected"); + }); + + test(renderMode + ": setting endpoint uuid", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + equals(e16.getUuid(), uuid, "endpoint's uuid was set correctly"); + }); + + test(renderMode + ": jsPlumb.getEndpoint (by uuid)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + }); + + test(renderMode + ": jsPlumb.deleteEndpoint (by uuid, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + jsPlumb.deleteEndpoint(uuid); + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + var ebe = jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + assertContextSize(0); + }); + + test(renderMode + ": jsPlumb.deleteEndpoint (by uuid, connections too)", function() { + // create two endpoints (one with a uuid), add them to two divs and then connect them. + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + // check that the connection was ok. + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + // delete the endpoint that has a uuid. verify that the endpoint cannot be retrieved and that the connection has been removed, but that + // element d17 still has its Endpoint. + jsPlumb.deleteEndpoint(uuid); + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + var ebe = jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + ebe = jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 1, "element d17 still has its Endpoint"); + assertContextSize(1); + + // now delete d17's endpoint and check that it has gone. + jsPlumb.deleteEndpoint(e17); + f = jsPlumb.getEndpoint(e17); + equals(f, null, "endpoint has been deleted"); + ebe = jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 0, "element d17 no longer has any Endpoints"); + assertContextSize(0); + }); + + test(renderMode + ": jsPlumb.deleteEndpoint (by reference, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + jsPlumb.deleteEndpoint(e16); + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + assertContextSize(0); + }); + + test(renderMode + ": jsPlumb.deleteEndpoint (by reference, connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + jsPlumb.deleteEndpoint(e16); + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + assertContextSize(1); + }); + + test(renderMode + ": jsPlumb.deleteEveryEndpoint", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + assertContextSize(2); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + jsPlumb.deleteEveryEndpoint(); + + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); + }); + + test(renderMode + ": jsPlumb.deleteEveryEndpoint (connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertContextSize(3); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + jsPlumb.deleteEveryEndpoint(); + + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); // no canvases. so all connection canvases have been cleaned up too. + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + }); + + test(renderMode + ": jsPlumb.addEndpoint (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {anchor:[0,0.5,0,-1]}); + var e17 = jsPlumb.addEndpoint(d17, {anchor:"TopCenter"}); + assertContextSize(2); + equals(e16.anchor.x, 0); + equals(e16.anchor.y, 0.5); + equals(e17.anchor.x, 0.5); + equals(e17.anchor.y, 0); + }); + + test(renderMode + ": jsPlumb.addEndpoint (simple case, dynamic anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + assertContextSize(2); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": jsPlumb.addEndpoint (simple case, two arg method)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchor:[0,0.5,0,-1]}); + var e17 = jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchor:"TopCenter"}); + assertContextSize(2); + equals(e16.anchor.x, 0); + equals(e16.anchor.y, 0.5); + equals(false, e16.isTarget); + equals(true, e16.isSource); + equals(e17.anchor.x, 0.5); + equals(e17.anchor.y, 0); + equals(true, e17.isTarget); + equals(false, e17.isSource); + }); + + + test(renderMode + ": jsPlumb.addEndpoints (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false, anchor:[0,0.5,0,-1] }, { isTarget:true, isSource:false, anchor:"TopCenter" }]); + assertContextSize(2); + equals(e16[0].anchor.x, 0); + equals(e16[0].anchor.y, 0.5); + equals(false, e16[0].isTarget); + equals(true, e16[0].isSource); + equals(e16[1].anchor.x, 0.5); + equals(e16[1].anchor.y, 0); + equals(true, e16[1].isTarget); + equals(false, e16[1].isSource); + }); + + test(renderMode + ": jsPlumb.addEndpoints (with reference params)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var refParams = {anchor:"RightMiddle"}; + var e16 = jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false}, { isTarget:true, isSource:false }], refParams); + assertContextSize(2); + equals(e16[0].anchor.x, 1); + equals(e16[0].anchor.y, 0.5); + equals(false, e16[0].isTarget); + equals(true, e16[0].isSource); + equals(e16[1].anchor.x, 1); + equals(e16[1].anchor.y, 0.5); + equals(true, e16[1].isTarget); + equals(false, e16[1].isSource); + }); + + test(renderMode + ": jsPlumb.addEndpoint (simple case, dynamic anchors, two arg method)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchors:["TopCenter", "BottomCenter"]}); + assertContextSize(2); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": jsPlumb.makeTarget (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + jsPlumb.makeTarget(d17, { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": jsPlumb.makeTarget (specify two divs in an array)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + jsPlumb.makeTarget([d16, d17], { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": jsPlumb.makeTarget (specify two divs by id in an array)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + jsPlumb.makeTarget(["d16", "d17"], { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": jsPlumb.makeTarget (specify divs by selector)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + d16.addClass("FOO");d17.addClass("FOO"); + jsPlumb.makeTarget($(".FOO"), { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ': jsPlumb.connect (between two Endpoints)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1); + assertEndpointCount("d2", 1); + jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1); // no new endpoint should have been added + assertEndpointCount("d2", 1); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. + }); + + test(renderMode + ': jsPlumb.connect (between two Endpoints, and dont supply any parameters to the Endpoints.)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1); + assertEndpointCount("d2", 1); + jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1); // no new endpoint should have been added + assertEndpointCount("d2", 1); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. + }); + + test(renderMode + ': jsPlumb.connect (by endpoint)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + }); + + test(renderMode + ": jsPlumb.connect (two Endpoints - that have been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1, uuid:srcEndpointUuid}); + var e2 = jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1, uuid:dstEndpointUuid}); + jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ] }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertContextSize(3); + }); + + test(renderMode + ": jsPlumb.connect (two Endpoints - that have not been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ], source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + var e1 = jsPlumb.getEndpoint(srcEndpointUuid); + ok(e1 != null, "endpoint with src uuid added"); + ok(e1.canvas != null); + var e2 = jsPlumb.getEndpoint(dstEndpointUuid); + ok(e2 != null, "endpoint with target uuid added"); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + }); + + test(renderMode + ": jsPlumb.connect (two Endpoints - that have been already added - by endpoint reference)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1}); + var e2 = jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1}); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + }); + + test(renderMode + ": jsPlumb.connect (two elements)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + jsPlumb.connect({ source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + }); + + test(renderMode + ": jsPlumb.connect (Connector test, straight)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": jsPlumb.connect (Connector test, bezier, no params)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Bezier" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 150, "Bezier connector chose 150 curviness"); + }); + + test(renderMode + ": jsPlumb.connect (Connector test, bezier, curviness as int)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", { curviness:200 }] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Canvas Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 200, "Bezier connector chose 200 curviness"); + }); + + /*jsPlumb.setRenderMode(jsPlumb.SVG); + test(renderMode + ": jsPlumb.connect (Connector test, bezier, curviness as int)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", 200] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors.svg.Bezier, "SVG Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 200, "Bezier connector chose 200 curviness"); + });*/ + + test(renderMode + ": jsPlumb.connect (Connector test, bezier, curviness as named option)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", {curviness:300}] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 300, "Bezier connector chose 300 curviness"); + }); + + test(renderMode + ": jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Canvas Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": jsPlumb.connect (anchors registered correctly; source and target anchors given, as strings)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:["LeftMiddle", "RightMiddle"] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.5, conn.endpoints[0].anchor.y, "source anchor y"); + equals(1, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.5, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + + test(renderMode + ": jsPlumb.connect (two argument method in which some data is reused across connections)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"), d19 = _addDiv("d19"); + var sharedData = { connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }; + var conn = jsPlumb.connect({ source:d16, target:d17}, sharedData); + var conn2 = jsPlumb.connect({ source:d18, target:d19}, sharedData); + assertContextSize(6); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 2); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(conn2.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + equals(0.3, conn2.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn2.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn2.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn2.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": jsPlumb.connect (Connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": jsPlumb.connect (Endpoint test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection target"); + }); + + test(renderMode + ": jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection target"); + }); + + test(renderMode + ": jsPlumb.connect (Endpoints test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Dot, "Dot endpoint chosen for connection target"); + }); + + test(renderMode + ": jsPlumb.connect (Blank Endpoint specified via 'endpoint' param)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoint:"Blank" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection target"); + }); + + test(renderMode + ": jsPlumb.connect (Blank Endpoint specified via 'endpoints' param)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoints:["Blank", "Blank" ] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection target"); + }); + + test(renderMode + ": jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Dot, "Dot endpoint chosen for connection target"); + }); + + test(renderMode + ": jsPlumb.connect (by Endpoints, connector test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {}); + var e17 = jsPlumb.addEndpoint(d17, {}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": jsPlumb.connect (by Endpoints, connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {}); + var e17 = jsPlumb.addEndpoint(d17, {}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": jsPlumb.connect (by Endpoints, anchors as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = "TopCenter", a17 = "BottomCenter"; + var e16 = jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, 0.5, "endpoint 16 is at top center");equals(e16.anchor.y, 0, "endpoint 16 is at top center"); + equals(e17.anchor.x, 0.5, "endpoint 17 is at bottom center");equals(e17.anchor.y, 1, "endpoint 17 is at bottom center"); + }); + + test(renderMode + ": jsPlumb.connect (by Endpoints, endpoints create anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = [0,0.5,0,-1], a17 = [1,0.0,-1,-1]; + var e16 = jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, a16[0]);equals(e16.anchor.y, a16[1]); + equals(e17.anchor.x, a17[0]);equals(e17.anchor.y, a17[1]); + equals(e16.anchor.getOrientation()[0], a16[2]); equals(e16.anchor.getOrientation()[1], a16[3]); + equals(e17.anchor.getOrientation()[0], a17[2]); equals(e17.anchor.getOrientation()[1], a17[3]); + }); + + test(renderMode + ": jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchor')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {anchor:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = jsPlumb.addEndpoint(d17, {anchor:["TopCenter", "BottomCenter"]}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchors')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": jsPlumb.connect (connect by element, default endpoint, supplied dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var anchors = [ [0.25, 0, 0, -1], [1, 0.25, 1, 0], [0.75, 1, 0, 1], [0, 0.75, -1, 0] ]; + jsPlumb.connect({source:d1, target:d2, dynamicAnchors:anchors}); // auto connect with default endpoint and provided anchors + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + assertContextSize(2); + }); + + test(renderMode + ": jsPlumb.connect (connect by element, supplied endpoint and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = jsPlumb.addEndpoint(d1, endpoint); + var e2 = jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + assertContextSize(3); + jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + assertContextSize(2); + }); + + test(renderMode + ": jsPlumb.connect (connect by element, supplied endpoints using 'source' and 'target' (this test is identical to the one above apart from the param names), and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = jsPlumb.addEndpoint(d1, endpoint); + var e2 = jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + jsPlumb.connect({source:e1, target:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + assertContextSize(3); + jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + assertContextSize(2); + }); + + + test(renderMode + ": jsPlumb.connect (testing for connection event callback)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var connectCallback = null, detachCallback = null; + jsPlumb.bind("jsPlumbConnection", function(params) { + connectCallback = $.extend({}, params); + }); + jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + detachCallback = $.extend({}, params); + }); + jsPlumb.connect({source:d1, target:d2}); // auto connect with default endpoint and anchor set + ok(connectCallback != null, "connect callback was made"); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + jsPlumb.detach({source:d1, target:d2}); + assertContextSize(2); + ok(detachCallback != null, "detach callback was made"); + }); + + test(renderMode + ": jsPlumb.connect (setting cssClass on Connector)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = jsPlumb.connect({source:d1,target:d2,cssClass:"CSS"}); + var has = function(clazz) { return $(c.connector.canvas).hasClass(clazz); }; + ok(has("CSS"), "custom cssClass set correctly"); + ok(has(jsPlumb.connectorClass), "basic connector class set correctly"); + }); + + test(renderMode + ": jsPlumb.connect (overlays, long-hand version)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = {width:40,length:40,location:0.7, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"}}; + var connection1 = jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + }); + + test(renderMode + ": jsPlumb.connect (overlays, long-hand version, IDs specified)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + equals("aLabel", connection1.overlays[0].id); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + equals("anArrow", connection1.overlays[1].id); + }); + + test(renderMode + ": jsPlumb.connect (remove single overlay by id)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + connection1.removeOverlay("aLabel"); + equals(1, connection1.overlays.length); + equals("anArrow", connection1.overlays[0].id); + }); + + test(renderMode + ": jsPlumb.connect (remove multiple overlays by id)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + connection1.removeOverlays("aLabel", "anArrow"); + equals(0, connection1.overlays.length); + }); + + test(renderMode + ": jsPlumb.connect (overlays, short-hand version)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var loc = { location:0.7 }; + var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} }; + var connection1 = jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label", {label:"CONNECTION 1", location:0.3}], + ["Arrow", arrowSpec, loc] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + }); + + test(renderMode + ": jsPlumb.connect (removeAllOverlays)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var loc = { location:0.7 }; + var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} }; + var connection1 = jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label", {label:"CONNECTION 1", location:0.3}], + ["Arrow", arrowSpec, loc] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + + connection1.removeAllOverlays(); + equals(0, connection1.overlays.length); + }); + + test(renderMode + ": jsPlumb.connect, specify arrow overlay using string identifier only", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var conn = jsPlumb.connect({source:d1,target:d2,overlays:["Arrow"]}); + equals(jsPlumb.Overlays[renderMode].Arrow, conn.overlays[0].constructor); + }); + + test(renderMode + ": Connection.getOverlay method, existing overlay", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("arrowOverlay"); + ok(overlay != null); + }); + + test(renderMode + ": Connection.getOverlay method, non-existent overlay", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("IDONTEXIST"); + ok(overlay == null); + }); + + test(renderMode + ": Overlay.setVisible method", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("arrowOverlay"); + ok(overlay.isVisible()); + overlay.setVisible(false); + ok(!overlay.isVisible()); + overlay.setVisible(true); + ok(overlay.isVisible()); + }); + + // this test is for the original detach function; it should stay working after i mess with it + // a little. + test(renderMode + ": jsPlumb.detach (by element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + var e3 = jsPlumb.addEndpoint(d1); + var e4 = jsPlumb.addEndpoint(d2); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + jsPlumb.connect({ sourceEndpoint:e3, targetEndpoint:e4 }); // make two connections to be sure this works ;) + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionCount(e3, 1); + assertConnectionCount(e4, 1); + + jsPlumb.detach("d1", "d2"); + + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionCount(e3, 0); + assertConnectionCount(e4, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + }); + + // detach is being made to operate more like connect - by taking one argument with a whole + // bunch of possible params in it. if two args are passed in it will continue working + // in the old way. + test(renderMode + ": jsPlumb.detach (params object, using element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + jsPlumb.detach({source:"d1", target:"d2"}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + }); + + test(renderMode + ": jsPlumb.detach (params object, using element objects)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + jsPlumb.detach({source:d1, target:d2}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + assertContextSize(2); + }); + + test(renderMode + ": jsPlumb.detach (source and target as endpoint UUIDs)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {uuid:"abcdefg"}); + ok(jsPlumb.getEndpoint("abcdefg") != null, "e1 exists"); + var e2 = jsPlumb.addEndpoint(d2, {uuid:"hijklmn"}); + ok(jsPlumb.getEndpoint("hijklmn") != null, "e2 exists"); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + jsPlumb.detach({uuids:["abcdefg", "hijklmn"]}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + assertContextSize(2); + }); + + test(renderMode + ": jsPlumb.detach (sourceEndpoint and targetEndpoint supplied)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + jsPlumb.detach({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + assertContextSize(2); + }); + + test(renderMode + ": jsPlumb.makeDynamicAnchors (longhand)", function() { + var anchors = [jsPlumb.makeAnchor(0.2, 0, 0, -1), jsPlumb.makeAnchor(1, 0.2, 1, 0), + jsPlumb.makeAnchor(0.8, 1, 0, 1), jsPlumb.makeAnchor(0, 0.8, -1, 0) ]; + var dynamicAnchor = jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); + }); + + test(renderMode + ": jsPlumb.makeDynamicAnchors (shorthand)", function() { + var anchors = [[0.2, 0, 0, -1], [1, 0.2, 1, 0], + [0.8, 1, 0, 1], [0, 0.8, -1, 0] ]; + var dynamicAnchor = jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); + }); + + test(renderMode + ": Connection.isVisible/setVisible", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c1 = jsPlumb.connect({source:d1,target:d2}); + equals(true, c1.isVisible(), "Connection is visible after creation."); + c1.setVisible(false); + equals(false, c1.isVisible(), "Connection is not visible after calling setVisible(false)."); + equals($(c1.connector.canvas).css("display"), "none"); + c1.setVisible(true); + equals(true, c1.isVisible(), "Connection is visible after calling setVisible(true)."); + equals($(c1.connector.canvas).css("display"), "block"); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible basic test (no connections)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals($(e1.canvas).css("display"), "none"); + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals($(e1.canvas).css("display"), "block"); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should track changes in the source, because it has only this connection.)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1), e2 = jsPlumb.addEndpoint(d2); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + var c1 = jsPlumb.connect({source:e1, target:e2}); + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals(false, e2.isVisible(), "other Endpoint is not visible either."); + equals(false, c1.isVisible(), "connection between the two is not visible either."); + + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals(true, e2.isVisible(), "other Endpoint is visible too"); + equals(true, c1.isVisible(), "connection between the two is visible too."); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should not track changes in the source, because it has another connection.)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var e1 = jsPlumb.addEndpoint(d1), e2 = jsPlumb.addEndpoint(d2, { maxConnections:2 }), e3 = jsPlumb.addEndpoint(d3); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + var c1 = jsPlumb.connect({source:e1, target:e2}); + var c2 = jsPlumb.connect({source:e2, target:e3}); + + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals(true, e2.isVisible(), "other Endpoint should still be visible."); + equals(true, e3.isVisible(), "third Endpoint should still be visible."); + equals(false, c1.isVisible(), "connection between the two is not visible either."); + equals(true, c2.isVisible(), "other connection is visible."); + + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals(true, e2.isVisible(), "other Endpoint is visible too"); + equals(true, c1.isVisible(), "connection between the two is visible too."); + equals(true, c2.isVisible(), "other connection is visible."); + }); + + // tests of the functionality that allows a user to specify that they want elements appended to the document body + test(renderMode + " jsPlumb.Defaults.Container, specified with a selector", function() { + jsPlumb.Defaults.Container = $("body"); + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length); // the divs we added have been added to the 'container' div. + // but we have told jsPlumb to add its canvas to the body, so this connect call should not add another few elements to the container: + jsPlumb.connect({source:d1, target:d2}); + equals(2, $("#container")[0].childNodes.length); + }); + + // tests of the functionality that allows a user to specify that they want elements appended to some specific container. + test(renderMode + " jsPlumb.Defaults.Container, specified with DOM element", function() { + jsPlumb.Defaults.Container = document.getElementsByTagName("body")[0]; + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length, "two divs added to the container"); // the divs we added have been added to the 'container' div. + // but we have told jsPlumb to add its canvas to the body, so this connect call should not add another few elements to the container: + var bodyElementCount = $("body")[0].childNodes.length; + jsPlumb.connect({source:d1, target:d2}); + equals(2, $("#container")[0].childNodes.length, "still only two children in container; elements were added to the body by jsPlumb"); + // test to see if 3 elements have been added + equals(bodyElementCount + 3, $("body")[0].childNodes.length, "3 new elements added to the document body"); + }); + + test(renderMode + " container specified to connect call, with a selector", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length); // the divs we added have been added to the 'container' div. + var bodyElementCount = $("body")[0].childNodes.length; + // but here we tell jsPlumb to add its elements to the body, so this connect call should not add another few elements to the container: + jsPlumb.connect({source:d1, target:d2, container:$("body")}); + equals(2, $("#container")[0].childNodes.length); + equals(bodyElementCount + 3, $("body")[0].childNodes.length, "3 new elements added to the document body"); + }); + + test(renderMode + " container specified to connect call, with a string ID", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + equals(3, $("#container")[0].childNodes.length, "container has divs we added"); // the divs we added have been added to the 'container' div. + var d3ElementCount = $("#d3")[0].childNodes.length; + // but here we tell jsPlumb to add its elements to "d3", so this connect call should not add another few elements to the container: + jsPlumb.connect({source:d1, target:d2, container:"d3"}); + equals(3, $("#container")[0].childNodes.length, "container still has only the divs we added"); + equals(d3ElementCount + 3, $("#d3")[0].childNodes.length, "3 new elements added to div d3"); + }); + + test(renderMode + " container specified to addEndpoint call, with a selector", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length); // the divs we added have been added to the 'container' div. + var bodyElementCount = $("body")[0].childNodes.length; + // but here we tell jsPlumb to add its elements to the body, so this connect call should not add another few elements to the container: + jsPlumb.addEndpoint(d1, {container:$("body")}); + equals(2, $("#container")[0].childNodes.length); + equals(bodyElementCount + 1, $("body")[0].childNodes.length, "1 new element added to the document body"); + }); + + test(renderMode + " container specified to addEndpoint call, with a string ID", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + equals(3, $("#container")[0].childNodes.length, "container has divs we added"); // the divs we added have been added to the 'container' div. + var d3ElementCount = $("#d3")[0].childNodes.length; + // but here we tell jsPlumb to add its elements to "d3", so this connect call should not add another few elements to the container: + jsPlumb.addEndpoint(d1, { container:"d3" }); + equals(3, $("#container")[0].childNodes.length, "container still has only the divs we added"); + equals(d3ElementCount + 1, $("#d3")[0].childNodes.length, "1 new element added to div d3"); + }); + + test(renderMode + " Connector has 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = jsPlumb.connect({source:d1, target:d2}); + equals(c.connector.type, "Bezier", "Bezier connector has type set"); + + var c2 = jsPlumb.connect({source:d1, target:d2, connector:"Straight"}); + equals(c2.connector.type, "Straight", "Straight connector has type set"); + + var c3 = jsPlumb.connect({source:d1, target:d2, connector:"Flowchart"}); + equals(c3.connector.type, "Flowchart", "Flowchart connector has type set"); + }); + + test(renderMode + " Endpoints have 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = jsPlumb.connect({source:d1, target:d2}); + equals(c.endpoints[0].type, "Dot", "Dot endpoint has type set"); + + var c2 = jsPlumb.connect({source:d1, target:d2, endpoints:["Rectangle", "Blank"]}); + equals(c2.endpoints[1].type, "Blank", "Blank endpoint has type set"); + equals(c2.endpoints[0].type, "Rectangle", "Rectangle endpoint has type set"); + }); + + test(renderMode + " Overlays have 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = jsPlumb.connect({ + source:d1, + target:d2, + overlays:[ "Arrow", "Label", "PlainArrow", "Diamond" ] + }); + equals(c.overlays[0].type, "Arrow", "Arrow overlay has type set"); + equals(c.overlays[1].type, "Label", "Label overlay has type set"); + equals(c.overlays[2].type, "PlainArrow", "PlainArrow overlay has type set"); + equals(c.overlays[3].type, "Diamond", "Diamond overlay has type set"); + }); + + test(renderMode + " jsPlumb.hide, original one-arg version", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = jsPlumb.addEndpoint(d1, e), + e2 = jsPlumb.addEndpoint(d2, e), + c1 = jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + jsPlumb.hide(d1); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(true, e1.isVisible(), "endpoint 1 is still visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + jsPlumb.show(d1); + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + }); + + test(renderMode + " jsPlumb.hide, two-arg version, endpoints should also be hidden", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = jsPlumb.addEndpoint(d1, e), + e2 = jsPlumb.addEndpoint(d2, e), + c1 = jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + jsPlumb.show(d1); // now show d1, but do not alter the endpoints. e1 should still be hidden + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + test(renderMode + " jsPlumb.show, two-arg version, endpoints should become visible", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = jsPlumb.addEndpoint(d1, e), + e2 = jsPlumb.addEndpoint(d2, e), + c1 = jsPlumb.connect({source:e1, target:e2}); + + jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + jsPlumb.show(d1, true); // now show d1, and alter the endpoints. e1 should be visible. + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(true, e1.isVisible(), "endpoint 1 is visible again."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + test(renderMode + " jsPlumb.show, two-arg version, endpoints should become visible, but not all connections, because some other endpoints are not visible.", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = jsPlumb.addEndpoint(d1, e), + e11 = jsPlumb.addEndpoint(d1, e), + e2 = jsPlumb.addEndpoint(d2, e), + e3 = jsPlumb.addEndpoint(d3, e), + c1 = jsPlumb.connect({source:e1, target:e2}); + c2 = jsPlumb.connect({source:e11, target:e3}); + + // we now have d1 connected to both d3 and d2. we'll hide d1, and everything on d1 should be hidden. + + jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "connection 1 is no longer visible."); + equals(false, c2.isVisible(), "connection 2 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(false, e11.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + equals(true, e3.isVisible(), "endpoint 3 is still visible."); + + // now, we will also hide d3. making d1 visible again should NOT result in c2 becoming visible, because the other endpoint + // for c2 is e3, which is not visible. + jsPlumb.hide(d3, true); + equals(false, e3.isVisible(), "endpoint 3 is no longer visible."); + + jsPlumb.show(d1, true); // now show d1, and alter the endpoints. e1 should be visible, c1 should be visible, but c2 should not. + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(false, c2.isVisible(), "Connection 2 is not visible."); + equals(true, e1.isVisible(), "endpoint 1 is visible again."); + equals(true, e11.isVisible(), "endpoint 11 is visible again."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + equals(false, e3.isVisible(), "endpoint 3 is still not visible."); + }); + + /* + test(renderMode + " jsPlumb.hide, two-arg version, endpoints should also be hidden", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = jsPlumb.addEndpoint(d1, e), + e2 = jsPlumb.addEndpoint(d2, e), + c1 = jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + /** + * leave this test at the bottom! + */ + test(renderMode + ': unload test', function() { + jsPlumb.unload(); + var contextNode = $("._jsPlumb_context"); + ok(contextNode.length == 0, 'context node unloaded'); + }); +}; + diff --git a/archive/1.3.9/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js b/archive/1.3.9/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js new file mode 100644 index 000000000..a923e9992 --- /dev/null +++ b/archive/1.3.9/demo/js/archive/1.3.3/mootools.jsPlumb-1.3.3-all-min.js @@ -0,0 +1 @@ +(function(){var r=!!document.createElement("canvas").getContext;var d=!!window.SVGAngle||document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1");var a=!(r|d);var l=function(y,z,w,C){var B=function(E,D){if(E===D){return true}else{if(typeof E=="object"&&typeof D=="object"){var F=true;for(var v in E){if(!B(E[v],D[v])){F=false;break}}for(var v in D){if(!B(D[v],E[v])){F=false;break}}return F}}};for(var A=+w||0,x=y.length;A=0){delete (ax[ay]);ax.splice(ay,1);return true}}}return false},v=function(ay,ax){return I(ay,function(az,aA){ag[aA]=ax;if(i.CurrentLibrary.isDragSupported(az)){i.CurrentLibrary.setDraggable(az,ax)}})},ao=function(az,aA,ax){aA=aA==="block";var ay=null;if(ax){if(aA){ay=function(aC){aC.setVisible(true,true,true)}}else{ay=function(aC){aC.setVisible(false,true,true)}}}var aB=c(az,"id");U(aB,function(aD){if(aA&&ax){var aC=aD.sourceId===aB?1:0;if(aD.endpoints[aC].isVisible()){aD.setVisible(true)}}else{aD.setVisible(aA)}},ay)},N=function(ax){return I(ax,function(az,ay){var aA=ag[ay]==null?Y:ag[ay];aA=!aA;ag[ay]=aA;i.CurrentLibrary.setDraggable(az,aA);return aA})},x=function(ax,az){var ay=null;if(az){ay=function(aA){var aB=aA.isVisible();aA.setVisible(!aB)}}U(ax,function(aB){var aA=aB.isVisible();aB.setVisible(!aA)},ay)},E=function(aC){var aA=aC.timestamp,ax=aC.recalc,aB=aC.offset,ay=aC.elId;if(!ax){if(aA&&aA===C[ay]){return V[ay]}}if(ax||aB==null){var az=u(ay);if(az!=null){P[ay]=b(az);V[ay]=n(az);C[ay]=aA}}else{V[ay]=aB}return V[ay]},aw=function(ax,ay){var az=u(ax);var aA=c(az,"id");if(!aA||aA=="undefined"){if(arguments.length==2&&arguments[1]!=undefined){aA=ay}else{aA="jsPlumb_"+ar()}e(az,"id",aA)}return aA},am=function(az,ax,ay){az=az||function(){};ax=ax||function(){};return function(){var aA=null;try{aA=ax.apply(this,arguments)}catch(aB){k(D,"jsPlumb function failed : "+aB)}if(ay==null||(aA!==ay)){try{az.apply(this,arguments)}catch(aB){k(D,"wrapped function failed : "+aB)}}return aA}};this.connectorClass="_jsPlumb_connector";this.endpointClass="_jsPlumb_endpoint";this.overlayClass="_jsPlumb_overlay";this.Anchors={};this.Connectors={canvas:{},svg:{},vml:{}};this.Endpoints={canvas:{},svg:{},vml:{}};this.Overlays={canvas:{},svg:{},vml:{}};this.addEndpoint=function(az,aA,aJ){aJ=aJ||{};var ay=i.extend({},aJ);i.extend(ay,aA);ay.endpoint=ay.endpoint||D.Defaults.Endpoint||i.Defaults.Endpoint;ay.paintStyle=ay.paintStyle||D.Defaults.EndpointStyle||i.Defaults.EndpointStyle;az=w(az);var aB=[],aE=az.length&&az.constructor!=String?az:[az];for(var aC=0;aC0?l(aK,aJ)!=-1:true},aB=aE.length>1?{}:[],aH=function(aK,aL){if(aE.length>1){var aJ=aB[aK];if(aJ==null){aJ=[];aB[aK]=aJ}aJ.push(aL)}else{aB.push(aL)}};for(var aA in L){if(ay(aE,aA)){for(var az=0;az=4){ay.orientation=[arguments[2],arguments[3]]}if(arguments.length==6){ay.offsets=[arguments[4],arguments[5]]}}var aD=new ac(ay);aD.clone=function(){return new ac(ay)};return aD};this.makeAnchors=function(ay){var az=[];for(var ax=0;ax0?aG[0]:null,aC=aG.length>0?0:-1,aF=this,aB=function(aJ,aH,aN,aM,aI){var aL=aM[0]+(aJ.x*aI[0]),aK=aM[1]+(aJ.y*aI[1]);return Math.sqrt(Math.pow(aH-aL,2)+Math.pow(aN-aK,2))},ax=ay||function(aR,aI,aJ,aK,aH){var aM=aJ[0]+(aK[0]/2),aL=aJ[1]+(aK[1]/2);var aO=-1,aQ=Infinity;for(var aN=0;aN=0?aG.overlays[aS]:null};this.hideOverlay=function(aT){var aS=aG.getOverlay(aT);if(aS){aS.hide()}};this.showOverlay=function(aT){var aS=aG.getOverlay(aT);if(aS){aS.show()}};this.removeAllOverlays=function(){aG.overlays.splice(0,aG.overlays.length);aG.repaint()};this.removeOverlay=function(aT){var aS=aQ(aT);if(aS!=-1){aG.overlays.splice(aS,1)}};this.removeOverlays=function(){for(var aS=0;aSa6){a6=a8}}}var aZ=this.connector.compute(a1,aY,this.endpoints[a7].anchor,this.endpoints[a3].anchor,aG.paintStyleInUse.lineWidth,a6);aG.connector.paint(aZ,aG.paintStyleInUse);for(var aX=0;aX=0){aN.connections.splice(aZ,1);if(!a2){var a1=a0.endpoints[0]==aN?a0.endpoints[1]:a0.endpoints[0];a1.detach(a0,true);if(a0.endpointToDeleteOnDetach&&a0.endpointToDeleteOnDetach.connections.length==0){i.deleteEndpoint(a0.endpointToDeleteOnDetach)}}Q(a0.connector.getDisplayElements(),a0.parent);M(L,a0.scope,a0);if(!a2){ad(a0)}}};this.detachAll=function(){while(aN.connections.length>0){aN.detach(aN.connections[0])}};this.detachFrom=function(a0){var a1=[];for(var aZ=0;aZ=0){aN.connections.splice(aZ,1)}};this.getElement=function(){return aM};this.getUuid=function(){return aH};this.makeInPlaceCopy=function(){return an({anchor:aN.anchor,source:aM,paintStyle:this.paintStyle,endpoint:aL})};this.isConnectedTo=function(a1){var a0=false;if(a1){for(var aZ=0;aZ0){var a9=aK(a2.elementWithPrecedence);var bb=a9.endpoints[0]==aN?1:0;var a4=bb==0?a9.sourceId:a9.targetId;var a8=V[a4],ba=P[a4];a0.txy=[a8.left,a8.top];a0.twh=ba;a0.tElement=a9.endpoints[bb]}}a5=aN.anchor.compute(a0)}var a7=aL.compute(a5,aN.anchor.getOrientation(),aN.paintStyleInUse,a3||aN.paintStyleInUse);aL.paint(a7,aN.paintStyleInUse,aN.anchor);aN.timestamp=a6}};this.repaint=this.paint;this.removeConnection=this.detach;if(aY.isSource&&i.CurrentLibrary.isDragSupported(aM)){var aS=null,aO=null,aR=null,ax=false,aA=null;var aC=function(){aR=aN.connectorSelector();if(aN.isFull()&&!aI){return false}E({elId:aE});aB=aN.makeInPlaceCopy();aB.paint();aS=document.createElement("div");aS.style.position="absolute";var a6=u(aS);R(aS,aN.parent);var a0=aw(a6);var a7=u(aB.canvas),a5=i.CurrentLibrary.getOffset(a7),a2=aB.canvas.offsetParent!=null?aB.canvas.offsetParent.tagName.toLowerCase()==="body"?{left:0,top:0}:n(aB.canvas.offsetParent):{left:0,top:0};i.CurrentLibrary.setOffset(aS,{left:a5.left-a2.left,top:a5.top-a2.top});E({elId:a0});e(u(aN.canvas),"dragId",a0);e(u(aN.canvas),"elId",aE);var a8=new H({reference:aN.anchor,referenceCanvas:aN.canvas});aW=an({paintStyle:aN.paintStyle,endpoint:aL,anchor:a8,source:a6});if(aR==null){aN.anchor.locked=true;aR=au({sourceEndpoint:aN,targetEndpoint:aW,source:u(aM),target:u(aS),anchors:[aN.anchor,a8],paintStyle:aY.connectorStyle,hoverPaintStyle:aY.connectorHoverStyle,connector:aY.connector,overlays:aY.connectorOverlays});aR.connector.setHover(false)}else{ax=true;aR.connector.setHover(false);aD(u(aB.canvas));var a1=aR.sourceId==aE?0:1;aR.floatingAnchorIndex=a1;aN.detachFromConnection(aR);var a4=u(aN.canvas);var a3=i.CurrentLibrary.getDragScope(a4);e(a4,"originalScope",a3);var aZ=i.CurrentLibrary.getDropScope(a4);i.CurrentLibrary.setDragScope(a4,aZ);if(a1==0){aA=[aR.source,aR.sourceId,aV,a3];aR.source=u(aS);aR.sourceId=a0}else{aA=[aR.target,aR.targetId,aV,a3];aR.target=u(aS);aR.targetId=a0}aR.endpoints[a1==0?1:0].anchor.locked=true;aR.suspendedEndpoint=aR.endpoints[a1];aR.endpoints[a1]=aW}Z[a0]=aR;aW.addConnection(aR);S(ah,a0,aW);D.currentlyDragging=true};var ay=i.CurrentLibrary,aU=aY.dragOptions||{},aP=i.extend({},ay.defaultDragOptions),aQ=ay.dragEvents.start,aX=ay.dragEvents.stop,aG=ay.dragEvents.drag;aU=i.extend(aP,aU);aU.scope=aU.scope||aN.scope;aU[aQ]=am(aU[aQ],aC);aU[aG]=am(aU[aG],function(){var aZ=i.CurrentLibrary.getUIPosition(arguments);i.CurrentLibrary.setOffset(aS,aZ);at(u(aS),aZ)});aU[aX]=am(aU[aX],function(){M(ah,aO,aW);Q([aS,aW.canvas],aM);ae(aB.canvas,aM);var aZ=aR.floatingAnchorIndex==null?1:aR.floatingAnchorIndex;aR.endpoints[aZ==0?1:0].anchor.locked=false;if(aR.endpoints[aZ]==aW){if(ax&&aR.suspendedEndpoint){if(aZ==0){aR.source=aA[0];aR.sourceId=aA[1]}else{aR.target=aA[0];aR.targetId=aA[1]}i.CurrentLibrary.setDragScope(aA[2],aA[3]);aR.endpoints[aZ]=aR.suspendedEndpoint;if(aJ){aR.floatingAnchorIndex=null;aR.suspendedEndpoint.addConnection(aR);i.repaint(aA[1])}else{aR.endpoints[aZ==0?1:0].detach(aR)}}else{Q(aR.connector.getDisplayElements(),aN.parent);aN.detachFromConnection(aR)}}aN.anchor.locked=false;aN.paint();aR.setHover(false);aR.repaint();aR=null;delete aB;delete ah[aW.elementId];aW=null;delete aW;D.currentlyDragging=false});var aV=u(aN.canvas);i.CurrentLibrary.initDraggable(aV,aU)}var aD=function(a2){if(aY.isTarget&&i.CurrentLibrary.isDropSupported(aM)){var aZ=aY.dropOptions||D.Defaults.DropOptions||i.Defaults.DropOptions;aZ=i.extend({},aZ);aZ.scope=aZ.scope||aN.scope;var a5=null;var a3=i.CurrentLibrary.dragEvents.drop;var a4=i.CurrentLibrary.dragEvents.over;var a0=i.CurrentLibrary.dragEvents.out;var a1=function(){var be=u(i.CurrentLibrary.getDragObject(arguments));var a6=c(be,"dragId");var a8=c(be,"elId");var bd=c(be,"originalScope");if(bd){i.CurrentLibrary.setDragScope(be,bd)}var ba=Z[a6];var bb=ba.floatingAnchorIndex==null?1:ba.floatingAnchorIndex,bc=bb==0?1:0;if(!aN.isFull()&&!(bb==0&&!aN.isSource)&&!(bb==1&&!aN.isTarget)){if(bb==0){ba.source=aM;ba.sourceId=aE}else{ba.target=aM;ba.targetId=aE}ba.endpoints[bb].detachFromConnection(ba);if(ba.suspendedEndpoint){ba.suspendedEndpoint.detachFromConnection(ba)}ba.endpoints[bb]=aN;aN.addConnection(ba);if(!ba.suspendedEndpoint){S(L,ba.scope,ba);O(aM,aY.draggable,{})}else{var a9=ba.suspendedEndpoint.getElement(),a7=ba.suspendedEndpoint.elementId;D.fire("jsPlumbConnectionDetached",{source:bb==0?a9:ba.source,target:bb==1?a9:ba.target,sourceId:bb==0?a7:ba.sourceId,targetId:bb==1?a7:ba.targetId,sourceEndpoint:bb==0?ba.suspendedEndpoint:ba.endpoints[0],targetEndpoint:bb==1?ba.suspendedEndpoint:ba.endpoints[1],connection:ba})}i.repaint(a8);D.fire("jsPlumbConnection",{source:ba.source,target:ba.target,sourceId:ba.sourceId,targetId:ba.targetId,sourceEndpoint:ba.endpoints[0],targetEndpoint:ba.endpoints[1],connection:ba})}D.currentlyDragging=false;delete Z[a6]};aZ[a3]=am(aZ[a3],a1);aZ[a4]=am(aZ[a4],function(){var a7=i.CurrentLibrary.getDragObject(arguments);var a9=c(u(a7),"dragId");var a8=Z[a9];if(a8!=null){var a6=a8.floatingAnchorIndex==null?1:a8.floatingAnchorIndex;a8.endpoints[a6].anchor.over(aN.anchor)}});aZ[a0]=am(aZ[a0],function(){var a7=i.CurrentLibrary.getDragObject(arguments),a9=c(u(a7),"dragId"),a8=Z[a9];if(a8!=null){var a6=a8.floatingAnchorIndex==null?1:a8.floatingAnchorIndex;a8.endpoints[a6].anchor.out()}});i.CurrentLibrary.initDroppable(a2,aZ)}};aD(u(aN.canvas));return aN}};var i=window.jsPlumb=new q();i.getInstance=function(w){var v=new q(w);v.init();return v};var m=function(v,A,z,w){return function(){return i.makeAnchor(v,A,z,w)}};i.Anchors.TopCenter=m(0.5,0,0,-1);i.Anchors.BottomCenter=m(0.5,1,0,1);i.Anchors.LeftMiddle=m(0,0.5,-1,0);i.Anchors.RightMiddle=m(1,0.5,1,0);i.Anchors.Center=m(0.5,0.5,0,0);i.Anchors.TopRight=m(1,0,0,-1);i.Anchors.BottomRight=m(1,1,0,1);i.Anchors.TopLeft=m(0,0,0,-1);i.Anchors.BottomLeft=m(0,1,0,1);i.Defaults.DynamicAnchors=function(){return i.makeAnchors(["TopCenter","RightMiddle","BottomCenter","LeftMiddle"])};i.Anchors.AutoDefault=function(){return i.makeDynamicAnchor(i.Defaults.DynamicAnchors())}})();(function(){jsPlumb.DOMElementComponent=function(b){jsPlumb.jsPlumbUIComponent.apply(this,arguments);this.mousemove=this.dblclick=this.click=this.mousedown=this.mouseup=function(c){}};jsPlumb.Connectors.Straight=function(){this.type="Straight";var o=this;var h=null;var d,i,m,l,k,e,n,g,f,c,b;this.compute=function(s,G,C,p,z,r){var F=Math.abs(s[0]-G[0]);var v=Math.abs(s[1]-G[1]);var A=false,t=false;var u=0.45*F,q=0.45*v;F*=1.9;v*=1.9;var D=Math.min(s[0],G[0])-u;var B=Math.min(s[1],G[1])-q;var E=Math.max(2*z,r);if(F0?1:-1;var u=Math.abs(v*Math.sin(e));if(f>b){u=u*-1}var q=Math.abs(v*Math.cos(e));if(g>c){q=q*-1}return{x:t.x+(s*q),y:t.y+(s*u)}};this.perpendicularToPathAt=function(t,u,z){var v=o.pointAlongPathFrom(t,z);var s=o.gradientAtPoint(v.location);var r=Math.atan(-1/s);var w=u/2*Math.sin(r);var q=u/2*Math.cos(r);return[{x:v.x+q,y:v.y+w},{x:v.x-q,y:v.y-w}]}};jsPlumb.Connectors.Bezier=function(f){var p=this;f=f||{};this.majorAnchor=f.curviness||150;this.minorAnchor=10;var i=null;this.type="Bezier";this._findControlPoint=function(z,q,u,x,r){var w=x.getOrientation(),y=r.getOrientation();var t=w[0]!=y[0]||w[1]==y[1];var s=[];var A=p.majorAnchor,v=p.minorAnchor;if(!t){if(w[0]==0){s.push(q[0]m){m=v}if(y<0){e+=y;var z=Math.abs(y);m+=z;o[0]+=z;k+=z;c+=z;n[0]+=z}var H=Math.min(g,b);var F=Math.min(o[1],n[1]);var u=Math.min(H,F);var A=Math.max(g,b);var x=Math.max(o[1],n[1]);var s=Math.max(A,x);if(s>h){h=s}if(u<0){d+=u;var w=Math.abs(u);h+=w;o[1]+=w;g+=w;b+=w;n[1]+=w}if(E&&m=t){r=u;s=(t-m[u][0])/b[u];break}}return{segment:k[r],proportion:s,index:r}};this.compute=function(R,z,Q,s,r,I){k=[];i=[];b=[];h=[];segmentProportionals=[];e=z[0]u[0]?u[0]+((1-y)*t)-z:u[2]+(y*t)+z,y:r==0?u[3]:u[3]>u[1]?u[1]+((1-y)*t)-z:u[3]+(y*t)+z,segmentInfo:w};return x};this.perpendicularToPathAt=function(u,v,A){var w=o.pointAlongPathFrom(u,A);var t=i[w.segmentInfo.index];var s=Math.atan(-1/t);var z=v/2*Math.sin(s);var r=v/2*Math.cos(s);return[{x:w.x+r,y:w.y+z},{x:w.x-r,y:w.y-z}]}};jsPlumb.Endpoints.Dot=function(c){this.type="Dot";var b=this;c=c||{};this.radius=c.radius||10;this.defaultOffset=0.5*this.radius;this.defaultInnerRadius=this.radius/3;this.compute=function(h,e,k,g){var f=k.radius||b.radius;var d=h[0]-f;var i=h[1]-f;return[d,i,f*2,f*2,f]}};jsPlumb.Endpoints.Rectangle=function(c){this.type="Rectangle";var b=this;c=c||{};this.width=c.width||20;this.height=c.height||20;this.compute=function(i,f,l,h){var g=l.width||b.width;var e=l.height||b.height;var d=i[0]-(g/2);var k=i[1]-(e/2);return[d,k,g,e]}};jsPlumb.Endpoints.Image=function(e){this.type="Image";jsPlumb.DOMElementComponent.apply(this,arguments);var b=this,d=false;this.img=new Image();b.ready=false;this.img.onload=function(){b.ready=true};this.img.src=e.src||e.url;this.compute=function(h,f,i,g){b.anchorPoint=h;if(b.ready){return[h[0]-b.img.width/2,h[1]-b.img.height/2,b.img.width,b.img.height]}else{return[0,0,0,0]}};b.canvas=document.createElement("img"),d=false;b.canvas.style.margin=0;b.canvas.style.padding=0;b.canvas.style.outline=0;b.canvas.style.position="absolute";b.canvas.className=jsPlumb.endpointClass;jsPlumb.appendElement(b.canvas,e.parent);b.attachListeners(b.canvas,b);var c=function(l,k,h){if(!d){b.canvas.setAttribute("src",b.img.src);d=true}var i=b.img.width,g=b.img.height,f=b.anchorPoint[0]-(i/2),m=b.anchorPoint[1]-(g/2);jsPlumb.sizeCanvas(b.canvas,f,m,i,g)};this.paint=function(h,g,f){if(b.ready){c(h,g,f)}else{window.setTimeout(function(){b.paint(h,g,f)},200)}}};jsPlumb.Endpoints.Blank=function(c){var b=this;this.type="Blank";jsPlumb.DOMElementComponent.apply(this,arguments);this.compute=function(){return[0,0,10,0]};b.canvas=document.createElement("div");b.canvas.style.display="block";b.canvas.style.width="1px";b.canvas.style.height="1px";b.canvas.style.background="transparent";b.canvas.style.position="absolute";jsPlumb.appendElement(b.canvas,c.parent);this.paint=function(){}};jsPlumb.Endpoints.Triangle=function(b){this.type="Triangle";b=b||{};b.width=b.width||55;param.height=b.height||55;this.width=b.width;this.height=b.height;this.compute=function(h,e,k,g){var f=k.width||self.width;var d=k.height||self.height;var c=h[0]-(f/2);var i=h[1]-(d/2);return[c,i,f,d]}};var a=function(){var c=true,b=this;this.setVisible=function(d){c=d;b.connection.repaint()};this.isVisible=function(){return c};this.hide=function(){b.setVisible(false)};this.show=function(){b.setVisible(true)}};jsPlumb.Overlays.Arrow=function(g){this.type="Arrow";a.apply(this);g=g||{};var c=this;this.length=g.length||20;this.width=g.width||20;this.id=g.id;this.connection=g.connection;var f=(g.direction||1)<0?-1:1;var d=g.paintStyle||{lineWidth:1};this.loc=g.location==null?0.5:g.location;var b=g.foldback||0.623;var e=function(h,k){if(b==0.5){return h.pointOnPath(k)}else{var i=0.5-b;return h.pointAlongPathFrom(k,f*c.length*i)}};this.computeMaxSize=function(){return c.width*1.5};this.draw=function(l,r,x){var z=l.pointAlongPathFrom(c.loc,f*(c.length/2));var u=l.pointAlongPathFrom(c.loc,-1*f*(c.length/2)),C=u.x,B=u.y;var s=l.perpendicularToPathAt(c.loc,c.width,-1*f*(c.length/2));var k=e(l,c.loc);if(c.loc==1){var i=l.pointOnPath(c.loc);var w=(i.x-z.x)*f,v=(i.y-z.y)*f;k.x+=w;k.y+=v;u.x+=w;u.y+=v;s[0].x+=w;s[0].y+=v;s[1].x+=w;s[1].y+=v;z.x+=w;z.y+=v}if(c.loc==0){var i=l.pointOnPath(c.loc);var t=b>1?k:{x:s[0].x+((s[1].x-s[0].x)/2),y:s[0].y+((s[1].y-s[0].y)/2)};var w=(i.x-t.x)*f,v=(i.y-t.y)*f;k.x+=w;k.y+=v;u.x+=w;u.y+=v;s[0].x+=w;s[0].y+=v;s[1].x+=w;s[1].y+=v;z.x+=w;z.y+=v}var p=Math.min(z.x,s[0].x,s[1].x);var o=Math.max(z.x,s[0].x,s[1].x);var n=Math.min(z.y,s[0].y,s[1].y);var m=Math.max(z.y,s[0].y,s[1].y);var A={hxy:z,tail:s,cxy:k},y=d.strokeStyle||r.strokeStyle,q=d.fillStyle||r.strokeStyle,h=d.lineWidth||r.lineWidth;c.paint(l,A,h,y,q,x);return[p,o,n,m]}};jsPlumb.Overlays.PlainArrow=function(c){c=c||{};var b=jsPlumb.extend(c,{foldback:1});jsPlumb.Overlays.Arrow.call(this,b);this.type="PlainArrow"};jsPlumb.Overlays.Diamond=function(d){d=d||{};var b=d.length||40;var c=jsPlumb.extend(d,{length:b/2,foldback:2});jsPlumb.Overlays.Arrow.call(this,c);this.type="Diamond"};jsPlumb.Overlays.Label=function(e){this.type="Label";jsPlumb.DOMElementComponent.apply(this,arguments);a.apply(this);this.labelStyle=e.labelStyle||jsPlumb.Defaults.LabelStyle;this.labelStyle.font=this.labelStyle.font||"12px sans-serif";this.label=e.label||"banana";this.connection=e.connection;this.id=e.id;var l=this;var i=null,f=null,d=null,c=null;this.location=e.location||0.5;this.cachedDimensions=null;var k=false,d=null,b=document.createElement("div");b.style.position="absolute";b.style.font=l.labelStyle.font;b.style.color=l.labelStyle.color||"black";if(l.labelStyle.fillStyle){b.style.background=l.labelStyle.fillStyle}if(l.labelStyle.borderWidth>0){var h=l.labelStyle.borderStyle?l.labelStyle.borderStyle:"black";b.style.border=l.labelStyle.borderWidth+"px solid "+h}if(l.labelStyle.padding){b.style.padding=l.labelStyle.padding}var g=e._jsPlumb.overlayClass+" "+(l.labelStyle.cssClass?l.labelStyle.cssClass:e.cssClass?e.cssClass:"");b.className=g;jsPlumb.appendElement(b,e.connection.parent);jsPlumb.getId(b);l.attachListeners(b,l);var m=l.setVisible;l.setVisible=function(n){m(n);b.style.display=n?"block":"none"};this.paint=function(n,p,o){if(!k){n.appendDisplayElement(b);l.attachListeners(b,n);k=true}b.style.left=(o[0]+p.minx)+"px";b.style.top=(o[1]+p.miny)+"px"};this.getTextDimensions=function(n){d=typeof l.label=="function"?l.label(l):l.label;b.innerHTML=d.replace(/\r\n/g,"
");var p=jsPlumb.CurrentLibrary.getElementObject(b),o=jsPlumb.CurrentLibrary.getSize(p);return{width:o[0],height:o[1]}};this.computeMaxSize=function(n){var o=l.getTextDimensions(n);return o.width?Math.max(o.width,o.height)*1.5:0};this.draw=function(p,r,q){var t=l.getTextDimensions(p);if(t.width!=null){var s=p.pointOnPath(l.location);var o=s.x-(t.width/2);var n=s.y-(t.height/2);l.paint(p,{minx:o,miny:n,td:t,cxy:s},q);return[o,o+t.width,n,n+t.height]}else{return[0,0,0,0]}}}})();(function(){var h={"stroke-linejoin":"joinstyle",joinstyle:"joinstyle",endcap:"endcap",miterlimit:"miterlimit"};if(document.createStyleSheet){document.createStyleSheet().addRule(".jsplumb_vml","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:textbox","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:oval","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:rect","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:stroke","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:shape","behavior:url(#default#VML);position:absolute;");document.namespaces.add("jsplumb","urn:schemas-microsoft-com:vml")}var c=1000,d=function(p,q){for(var n in q){p[n]=q[n]}},m=function(n,q,r){r=r||{};var p=document.createElement("jsplumb:"+n);p.className=(r["class"]?r["class"]+" ":"")+"jsplumb_vml";l(p,q);d(p,r);return p},l=function(p,n){p.style.left=n[0]+"px";p.style.top=n[1]+"px";p.style.width=n[2]+"px";p.style.height=n[3]+"px";p.style.position="absolute"},g=function(n){return Math.floor(n*c)},a=function(p,n){var v=p,u=function(o){return o.length==1?"0"+o:o},q=function(o){return u(Number(o).toString(16))},r=/(rgb[a]?\()(.*)(\))/;if(p.match(r)){var t=p.match(r)[2].split(",");v="#"+q(t[0])+q(t[1])+q(t[2]);if(!n&&t.length==4){v=v+q(t[3])}}return v},f=function(s,r,p){var u={};if(r.strokeStyle){u.stroked="true";u.strokecolor=a(r.strokeStyle,true);u.strokeweight=r.lineWidth+"px"}else{u.stroked="false"}if(r.fillStyle){u.filled="true";u.fillcolor=a(r.fillStyle,true)}else{u.filled="false"}if(r.dashstyle){if(p.strokeNode==null){p.strokeNode=m("stroke",[0,0,0,0],{dashstyle:r.dashstyle});s.appendChild(p.strokeNode)}else{p.strokeNode.dashstyle=r.dashstyle}}else{if(r["stroke-dasharray"]&&r.lineWidth){var o=r["stroke-dasharray"].indexOf(",")==-1?" ":",",t=r["stroke-dasharray"].split(o),n="";for(var q=0;q0&&F>0&&z=z&&B[2]<=F&&B[3]>=F)){return true}}var D=v.canvas.getContext("2d").getImageData(parseInt(z),parseInt(F),1,1);return D.data[0]!=0||D.data[1]!=0||D.data[2]!=0||D.data[3]!=0}return false};var u=false;var t=false,y=null,x=false;var w=function(A,z){return A!=null&&f(A,z)};this.mousemove=function(C){var E=e(C),B=i(C),A=document.elementFromPoint(B[0],B[1]),D=w(A,"_jsPlumb_overlay");var z=o==null&&(w(A,"_jsPlumb_endpoint")||w(A,"_jsPlumb_connector"));if(!u&&z&&v._over(C)){u=true;v.fire("mouseenter",v,C);return true}else{if(u&&(!v._over(C)||!z)&&!D){u=false;v.fire("mouseexit",v,C)}}v.fire("mousemove",v,C)};this.click=function(z){if(u&&v._over(z)&&!x){v.fire("click",v,z)}x=false};this.dblclick=function(z){if(u&&v._over(z)&&!x){v.fire("dblclick",v,z)}x=false};this.mousedown=function(z){if(v._over(z)&&!t){t=true;y=m(s(v.canvas));v.fire("mousedown",v,z)}};this.mouseup=function(z){t=false;v.fire("mouseup",v,z)}};var p=function(u){var t=document.createElement("canvas");jsPlumb.appendElement(t,u.parent);t.style.position="absolute";if(u["class"]){t.className=u["class"]}u._jsPlumb.getId(t,u.uuid);return t};var d=jsPlumb.CanvasConnector=function(x){n.apply(this,arguments);var t=function(B,z){u.ctx.save();jsPlumb.extend(u.ctx,z);if(z.gradient){var A=u.createGradient(B,u.ctx);for(var y=0;y0?1:-1}}var b={subtract:function(m,l){return{x:m.x-l.x,y:m.y-l.y}},dotProduct:function(m,l){return m.x*l.x+m.y*l.y},square:function(l){return Math.sqrt(l.x*l.x+l.y*l.y)},scale:function(m,l){return{x:m.x*l,y:m.y*l}}},d=Math.pow(2,-65),h=function(y,x){for(var s=[],v=x.length-1,r=2*v-1,t=[],w=[],o=[],p=[],q=[[1,0.6,0.3,0.1],[0.4,0.6,0.6,0.4],[0.1,0.3,0.6,1]],u=0;u<=v;u++){t[u]=b.subtract(x[u],y)}for(u=0;u<=v-1;u++){w[u]=b.subtract(x[u+1],x[u]);w[u]=b.scale(w[u],3)}for(u=0;u<=v-1;u++){for(var n=0;n<=v;n++){o[u]||(o[u]=[]);o[u][n]=b.dotProduct(w[u],t[n])}}for(u=0;u<=r;u++){p[u]||(p[u]=[]);p[u].y=0;p[u].x=parseFloat(u)/r}r=v-1;for(t=0;t<=v+r;t++){w=Math.min(t,v);for(u=Math.max(0,t-r);u<=w;u++){j=t-u;p[u+j].y+=o[j][u]*q[j][u]}}v=x.length-1;p=k(p,2*v-1,s,0);r=b.subtract(y,x[0]);o=b.square(r);for(u=q=0;u=64){y[0]=(E[0].x+E[D].x)/2;return 1}var s,r,p;v=E[0].y-E[D].y;w=E[D].x-E[0].x;A=E[0].x*E[D].y-E[D].x*E[0].y;t=max_distance_below=0;for(r=1;rt){t=p}else{if(p0?1:-1,r=null;n endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + endpointsByElement = {}, + endpointsByUUID = {}, + offsets = {}, + offsetTimestamps = {}, + floatingConnections = {}, + draggableStates = {}, + _mouseEventsEnabled = this.Defaults.MouseEventsEnabled, + _draggableByDefault = true, + canvasList = [], + sizes = [], + listeners = {}, // a map: keys are event types, values are lists of listeners. + DEFAULT_SCOPE = this.Defaults.Scope, + renderMode = null, // will be set in init() + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }, + + /** + * appends an element to some other element, which is calculated as follows: + * + * 1. if jsPlumb.Defaults.Container exists, use that element. + * 2. if the 'parent' parameter exists, use that. + * 3. otherwise just use the document body. + * + */ + _appendElement = function(el, parent) { + if (_currentInstance.Defaults.Container) + jsPlumb.CurrentLibrary.appendElement(el, _currentInstance.Defaults.Container); + else if (!parent) + document.body.appendChild(el); + else + jsPlumb.CurrentLibrary.appendElement(el, parent); + }, + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + _timestamp = function() { return "" + (new Date()).getTime(); }, + + /** + * YUI, for some reason, put the result of a Y.all call into an object that contains + * a '_nodes' array, instead of handing back an array-like object like the other + * libraries do. + */ + _convertYUICollection = function(c) { + return c._nodes ? c._nodes : c; + }, + + /** + * Draws an endpoint and its connections. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + _draw = function(element, ui, timestamp) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (!timestamp) timestamp = _timestamp(); + if (endpoints) { + _updateOffset( { elId : id, offset : ui, recalc : false, timestamp : timestamp }); // timestamp is checked against last update cache; it is + // valid for one paint cycle. + var myOffset = offsets[id], myWH = sizes[id]; + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + var l = endpoints[i].connections; + for ( var j = 0; j < l.length; j++) { + l[j].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); // ...paint each connection. + // then, check for dynamic endpoint; need to repaint it. + var oIdx = l[j].endpoints[0] == endpoints[i] ? 1 : 0, + otherEndpoint = l[j].endpoints[oIdx]; + if (otherEndpoint.anchor.isDynamic && !otherEndpoint.isFloating()) { + _updateOffset( { elId : otherEndpoint.elementId, timestamp : timestamp }); + otherEndpoint.paint({ elementWithPrecedence:id }); + // all the connections for the other endpoint now need to be repainted + for (var k = 0; k < otherEndpoint.connections.length; k++) { + if (otherEndpoint.connections[k] !== l) + otherEndpoint.connections[k].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); + } + } + } + } + } + }, + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }, + + /** + * gets an Endpoint by uuid. + */ + _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }, + + /** + * inits a draggable if it's not already initialised. + */ + _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element) && !jsPlumb.CurrentLibrary.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + } + } + }, + + _newConnection = function(params) { + var connectionFunc = jsPlumb.Defaults.ConnectionType || Connection, + endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint, + parent = jsPlumb.CurrentLibrary.getParent; + + if (params.container) + params["parent"] = params.container; + else { + if (params.sourceEndpoint) + params["parent"] = params.sourceEndpoint.parent; + else if (params.source.constructor == endpointFunc) + params["parent"] = params.source.parent; + else params["parent"] = parent(params.source); + } + + params["_jsPlumb"] = _currentInstance; + var con = new connectionFunc(params); + _eventFireProxy("click", "click", con); + _eventFireProxy("dblclick", "dblclick", con); + return con; + }, + + _eventFireProxy = function(event, proxyEvent, obj) { + obj.bind(event, function(originalObject, originalEvent) { + _currentInstance.fire(proxyEvent, obj, originalEvent); + }); + }, + + _newEndpoint = function(params) { + var endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint; + if (params.container) + params.parent = params.container; + else + params["parent"] = jsPlumb.CurrentLibrary.getParent(params.source); + params["_jsPlumb"] = _currentInstance, + ep = new endpointFunc(params); + _eventFireProxy("click", "endpointClick", ep); + _eventFireProxy("dblclick", "endpointDblClick", ep); + return ep; + }, + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + _operation = function(elId, func, endpointFunc) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + if (endpointFunc) endpointFunc(endpoints[i]); + } + } + }, + /** + * perform an operation on all elements. + */ + _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }, + + /** + * helper to remove an element from the DOM. + */ + _removeElement = function(element, parent) { + if (element != null && element.parentNode != null) { + element.parentNode.removeChild(element); + } + }, + /** + * helper to remove a list of elements from the DOM. + */ + _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }, + /** + * helper method to remove an item from a list. + */ + _removeFromList = function(map, key, value) { + if (key != null) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete (l[i]); + l.splice(i, 1); + return true; + } + } + } + return false; + }, + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }, + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + _setVisible = function(el, state, alsoChangeEndpoints) { + state = state === "block"; + var endpointFunc = null; + if (alsoChangeEndpoints) { + if (state) endpointFunc = function(ep) { + ep.setVisible(true, true, true); + }; + else endpointFunc = function(ep) { + ep.setVisible(false, true, true); + }; + } + var id = _getAttribute(el, "id"); + _operation(id, function(jpc) { + if (state && alsoChangeEndpoints) { + // this test is necessary because this functionality is new, and i wanted to maintain backwards compatibility. + // this block will only set a connection to be visible if the other endpoint in the connection is also visible. + var oidx = jpc.sourceId === id ? 1 : 0; + if (jpc.endpoints[oidx].isVisible()) jpc.setVisible(true); + } + else // the default behaviour for show, and what always happens for hide, is to just set the visibility without getting clever. + jpc.setVisible(state); + }, endpointFunc); + }, + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }, + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + _toggleVisible = function(elId, changeEndpoints) { + var endpointFunc = null; + if (changeEndpoints) { + endpointFunc = function(ep) { + var state = ep.isVisible(); + ep.setVisible(!state); + }; + } + _operation(elId, function(jpc) { + var state = jpc.isVisible(); + jpc.setVisible(!state); + }, endpointFunc); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }, + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return offsets[elId]; + } + if (recalc || offset == null) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + } + return offsets[elId]; + }, + +/** + * gets an id for the given element, creating and setting one if + * necessary. + */ + _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else + id = "jsPlumb_" + _timestamp(); + _setAttribute(ele, "id", id); + } + return id; + }, + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'jsPlumb function failed : ' + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'wrapped function failed : ' + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = "_jsPlumb_connector"; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = "_jsPlumb_endpoint"; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = "_jsPlumb_overlay"; + + this.Anchors = {}; + + this.Connectors = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Endpoints = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Overlays = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + +// ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***************************************** + /* + * Function: bind + * Bind to an event on jsPlumb. + * + * Parameters: + * event - the event to bind. Available events on jsPlumb are: + * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback. + * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback. + * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback. + * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback. + * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback. + * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback. + * + * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event. + */ + + /* + * Function: clearListeners + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + +// *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *********************************************************** + + /* + Function: addEndpoint + + Adds an to a given element or elements. + + Parameters: + + el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + params - Object containing Endpoint constructor arguments. For more information, see . + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some + shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in + this object are anything that 'params' can contain. See . + + Returns: + The newly created , if el referred to a single element. Otherwise, an array of newly created s. + + See Also: + + */ + this.addEndpoint = function(el, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + + // YUI wrapper + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + var _el = _getElementObject(inputs[i]), id = _getId(_el); + p.source = _el; + _updateOffset({ elId : id }); + var e = _newEndpoint(p); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + results.push(e); + } + + return results.length == 1 ? results[0] : results; + }; + + /* + Function: addEndpoints + Adds a list of s to a given element or elements. + + Parameters: + target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See 's constructor documentation. + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created s, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(el, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams); + if (e.constructor == Array) + Array.prototype.push.apply(results, e); + else results.push(e); + } + return results; + }; + + /* + Function: animate + This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating + the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // onComplete repaints, just to make sure everything looks good at the end of the animation. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /* + Function: connect + Establishes a between two elements (or s, which are themselves registered to elements). + + Parameters: + params - Object containing constructor arguments for the Connection. See 's constructor documentation. + referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of + Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection. + + Returns: + The newly created . + */ + this.connect = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + if (_p.target && !_p.target.endpoint) { + var tid = _getId(_p.target), + tep =_targetEndpointDefinitions[tid]; + + var overrideOne = function(singlePropertyName, pluralPropertyName, tepProperty, tep) { + if (tep[tepProperty]) { + if (_p[pluralPropertyName]) _p[pluralPropertyName][1] = tep[tepProperty]; + else if (_p[singlePropertyName]) { + _p[pluralPropertyName] = [ _p[singlePropertyName], tep[tepProperty] ]; + _p[singlePropertyName] = null; + } + else _p[pluralPropertyName] = [ null, tep[tepProperty] ]; + } + }; + + if (tep) { + overrideOne("endpoint", "endpoints", "endpoint", tep); + overrideOne("endpointStyle", "endpointStyles", "paintStyle", tep); + overrideOne("endpointHoverStyle", "endpointHoverStyles", "hoverPaintStyle", tep); + } + } + + // dynamic anchors. backwards compatibility here: from 1.2.6 onwards you don't need to specify "dynamicAnchors". the fact that some anchor consists + // of multiple definitions is enough to tell jsPlumb you want it to be dynamic. + if (_p.dynamicAnchors) { + // these can either be an array of anchor coords, which we will use for both source and target, or an object with {source:[anchors], target:[anchors]}, in which + // case we will use a different set for each element. + var a = _p.dynamicAnchors.constructor == Array; + var sa = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.source)); + var ta = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.target)); + _p.anchors = [sa,ta]; + } + + var jpc = _newConnection(_p); + // add to list of connections (by scope). + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + _currentInstance.fire("jsPlumbConnection", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + // force a paint + _draw(jpc.source); + + return jpc; + }; + + /* + Function: deleteEndpoint + Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too) + + Parameters: + object - either an object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElement(endpoint.canvas, endpoint.parent); + // remove from endpointsbyElement + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + delete endpoint; + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every , and their associated s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc) { + _currentInstance.fire("jsPlumbConnectionDetached", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + }; + + /* + Function: detach + Detaches and then removes a . Takes either (source, target) (the old way, maintained for backwards compatibility), or a params + object with various possible values. + + Parameters: + source - id or element object of the first element in the Connection. + target - id or element object of the second element in the Connection. + params - a JS object containing the same parameters as you pass to jsPlumb.connect. If this is present then neither source nor + target should be present; it should be the only argument to the method. See the docs for 's constructor for information +about the parameters allowed in the params object. + Returns: + true if successful, false if not. + */ + this.detach = function(source, target) { + if (arguments.length == 2) { + var s = _getElementObject(source), sId = _getId(s); + var t = _getElementObject(target), tId = _getId(t); + _operation(sId, function(jpc) { + if ((jpc.sourceId == sId && jpc.targetId == tId) || (jpc.targetId == sId && jpc.sourceId == tId)) { + _removeElements(jpc.connector.getDisplayElements(), jpc.parent); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + // this is the new version of the method, taking a JS object like + // the connect method does. + else if (arguments.length == 1) { + // TODO investigate whether or not this code still works when a user has supplied their own subclass of Connection. i suspect it may not. + if (arguments[0].constructor == Connection) { + arguments[0].endpoints[0].detachFrom(arguments[0].endpoints[1]); + } + else if (arguments[0].connection) { + arguments[0].connection.endpoints[0].detachFrom(arguments[0].connection.endpoints[1]); + } + else { + var _p = jsPlumb.extend( {}, source); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1])); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source); + var targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElements(jpc.connector.getDisplayElements(), jpc.parent); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + } + } + }; + + /* + Function: detachAll + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + + Returns: + void + */ + this.detachAllConnections = function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + }; + + /** + * @deprecated Use detachAllConnections instead. this will be removed in jsPlumb 1.3. + */ + this.detachAll = this.detachAllConnections; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + /** + * @deprecated use detachEveryConnection instead. this will be removed in jsPlumb 1.3. + */ + this.detachEverything = this.detachEveryConnection; + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of you library's draggable method so that jsPlumb can setup the appropriate callbacks. Your underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method, + * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the + * default scope). If multiple scopes are passed in, the return value will be a map of { scope -> [ connection... ] }. + * + * Parameters + * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list + * of connections that are in the given scope. + * options - if the argument is a JS object, you can specify a finer-grained filter: + * + * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes. + * - *source* either a string representing an element id, or a selector. constrains the result to connections having this source. + * - *target* either a string representing an element id, or a selector. constrains the result to connections having this target. + * + */ + this.getConnections = function(options) { + if (!options) { + options = {}; + } else if (options.constructor == String) { + options = { "scope": options }; + } + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') + r.push(input); + else + r = input; + } + return r; + }; + var scope = options.scope || jsPlumb.getDefaultScope(), + scopes = prepareList(scope), + sources = prepareList(options.source), + targets = prepareList(options.target), + filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }, + results = scopes.length > 1 ? {} : [], + _addOne = function(scope, obj) { + if (scopes.length > 1) { + var ss = results[scope]; + if (ss == null) { + ss = []; results[scope] = ss; + } + ss.push(obj); + } else results.push(obj); + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + _addOne(i, c); + } + } + } + return results; + }; + + /* + * Function: getAllConnections + * Gets all connections, as a map of { scope -> [ connection... ] }. + */ + this.getAllConnections = function() { + return connectionsByScope; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /** + * Function:getEndpoints + * Gets the list of Endpoints for a given selector, or element id. + * @param el + * @return + */ + this.getEndpoints = function(el) { + return endpointsByElement[_getId(el)]; + }; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + + this.appendElement = _appendElement; + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + changeEndpoints - whether not to also hide endpoints on the element. by default this is false. + + Returns: + void + */ + this.hide = function(el, changeEndpoints) { + _setVisible(el, "none", changeEndpoints); + }; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + if (!initialized) { + _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run. + + var _bind = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && _mouseEventsEnabled && renderMode == jsPlumb.CANVAS) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + var t = c[i].connector[event](e); + if (t) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i].endpoint[event](e)) return; + } + } + } + }); + }; + _bind("click"); + _bind("dblclick"); + _bind("mousemove"); + _bind("mousedown"); + _bind("mouseup"); + + initialized = true; + _currentInstance.fire("ready"); + } + }; + + this.jsPlumbUIComponent = jsPlumbUIComponent; + this.EventGenerator = EventGenerator; + + /* + * Creates an anchor with the given params. + * + * You do not need to use this method. It is exposed because of the way jsPlumb is + * split into three scripts; this will change in the future. + * + * x - the x location of the anchor as a fraction of the + * total width. y - the y location of the anchor as a fraction of the + * total height. xOrientation - value indicating the general direction a + * connection from the anchor should go in, in the x direction. + * yOrientation - value indicating the general direction a connection + * from the anchor should go in, in the y direction. xOffset - a fixed + * offset that should be applied in the x direction that should be + * applied after the x position has been figured out. optional. defaults + * to 0. yOffset - a fixed offset that should be applied in the y + * direction that should be applied after the y position has been + * figured out. optional. defaults to 0. + * -- OR -- + * + * params - {x:..., y:..., xOrientation etc } + * -- OR FROM 1.2.4 --- + * + * name - the name of some Anchor in the _currentInstance.Anchors array. + * -- OR FROM 1.2.4 --- + * + * coords - a list of coords for the anchor, like you would pass to + * jsPlumb.makeAnchor (eg [0.5,0.5,0,-1] - an anchor in the center of + * some element, oriented towards the top of the screen) + * -- OR FROM 1.2.4 --- + * + * anchor - an existing anchor. just gets passed back. it's handy + * internally to have this functionality. + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed + // in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards + // compatibility if we are given only one value we assume it's a + // call in the old form. + if (arguments.length == 0) return null; + var params = {}; + if (arguments.length == 1) { + var specimen = arguments[0]; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; + // is it the name of an anchor type? + else if (typeof specimen == "string") return _currentInstance.Anchors[arguments[0]](); + // is it an array of coordinates? + else if (specimen.constructor == Array) { + if (specimen[0].constructor == Array || specimen[0].constructor == String) + return new DynamicAnchor(specimen); + else + return jsPlumb.makeAnchor.apply(this, specimen); + } + // last we try the backwards compatibility stuff. + else if (typeof arguments[0] == "object") jsPlumb.extend(params, x); + } else { + params = { x : x, y : y }; + if (arguments.length >= 4) params.orientation = [ arguments[2], arguments[3] ]; + if (arguments.length == 6) params.offsets = [ arguments[4], arguments[5] ]; + } + var a = new Anchor(params); + a.clone = function() { + return new Anchor(params); + }; + return a; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types) { + var r = []; + for ( var i = 0; i < types.length; i++) + if (typeof types[i] == "string") + r.push(_currentInstance.Anchors[types[i]]()); + else if (types[i].constructor == Array) + r.push(jsPlumb.makeAnchor(types[i])); + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /** + * Function: makeTarget + * Makes some DOM element a Connection target, allowing you to drag connections to it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a target. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * scope optional. scope for the drop zone. + * dropOptions optional. same stuff as you would pass to dropOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to false. whether or not to delete + * any Endpoints created by a connection to this target if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + var _targetEndpointDefinitions = {}; + this.makeTarget = function(el, params, referenceParams) { + + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + var jpcl = jsPlumb.CurrentLibrary, + scope = p.scope || _currentInstance.Defaults.Scope, + deleteEndpointsOnDetach = p.deleteEndpointsOnDetach || false, + _doOne = function(_el) { + + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el); + _targetEndpointDefinitions[elid] = p.endpoint; + + var dropOptions = jsPlumb.extend({}, p.dropOptions || {}); + var _drop = function() { + var draggable = _getElementObject(jpcl.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + // restore the original scope if necessary (issue 57) + scope = _getAttribute(draggable, "originalScope"); + + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + // get the connection, to then get its endpoint + var jpc = floatingConnections[id], + source = jpc.endpoints[0], + _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : null, + // make a new Endpoint + newEndpoint = jsPlumb.addEndpoint(_el, _endpoint); + + var c = jsPlumb.connect({ + source:source, + target:newEndpoint, + scope:scope + }); + if (deleteEndpointsOnDetach) + c.endpointToDeleteOnDetach = newEndpoint; + }; + + var dropEvent = jpcl.dragEvents['drop']; + dropOptions["scope"] = dropOptions["scope"] || scope; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop); + + jpcl.initDroppable(_el, dropOptions); + }; + + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements drop targets. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeTargets = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeTarget(els[i], params, referenceParams); + } + }; + + /* + Function: ready + Helper method to bind a function to jsPlumb's ready event. + */ + this.ready = function(fn) { + _currentInstance.bind("ready", fn); + }, + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + for ( var i in ebe) + _currentInstance.deleteEndpoint(ebe[i]); + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + this.deleteEveryEndpoint(); + this.clearListeners(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + + Parameters: + value - whether or not to automatically repaint when the window is resized. + + Returns: void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setDraggableByDefault + * Sets whether or not elements are draggable by default. Default for this is true. + * + * Parameters: + * draggable - value to set + * + * Returns: + * void + */ + this.setDraggableByDefault = function(draggable) { + _draggableByDefault = draggable; + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction + * Sets the function to fire when the window size has changed and a repaint was fired. + * + * Parameters: + * f - Function to execute. + * + * Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: setMouseEventsEnabled + * Sets whether or not mouse events are enabled. Default is true. + * + * Parameters: + * enabled - whether or not mouse events should be enabled. + * + * Returns: + * void + */ + this.setMouseEventsEnabled = function(enabled) { + _mouseEventsEnabled = enabled; + }; + + /* + * Constant for use with the setRenderMode method + */ + this.CANVAS = "canvas"; + + /* + * Constant for use with the setRenderMode method + */ + this.SVG = "svg"; + + this.VML = "vml"; + + /* + * Function: setRenderMode + * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that + * what you asked for is not supported (and that VML is). If you asked for VML but the browser does + * not support it, jsPlumb uses SVG. + * + * Returns: + * the render mode that jsPlumb set, which of course may be different from that requested. + */ + this.setRenderMode = function(mode) { + if (mode) + mode = mode.toLowerCase(); + else + return; + if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); + // now test we actually have the capability to do this. + if (mode === jsPlumb.CANVAS && canvasAvailable) + renderMode = jsPlumb.CANVAS; + else if (mode === jsPlumb.SVG && svgAvailable) + renderMode = jsPlumb.SVG; + else if (vmlAvailable) + renderMode = jsPlumb.VML; + + return renderMode; + }; + + this.getRenderMode = function() { return renderMode; }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * changeEndpoints - whether or not to also change the visible state of the endpoints on the element. this also has a bearing on + * other connections on those endpoints: if their other endpoint is also visible, the connections are made visible. + * + * Returns: + * void + */ + this.show = function(el, changeEndpoints) { + _setVisible(el, "block", changeEndpoints); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId, + makeAnchor:self.makeAnchor, + makeDynamicAnchor:self.makeDynamicAnchor + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * changeEndpoints - whether or not to also toggle the endpoints on the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload + * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element. + * + * Returns: + * void + */ + this.unload = function() { + delete endpointsByElement; + delete endpointsByUUID; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + delete canvasList; + }; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + if (timestamp && timestamp === self.timestamp) { + return lastReturnValue; + } + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + // adjust loc if there is an offsetParent + if (element.canvas && element.canvas.offsetParent) { + var po = element.canvas.offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(element.canvas.offsetParent); + lastReturnValue[0] = lastReturnValue[0] - po.left; + lastReturnValue[1] = lastReturnValue[1] - po.top; + } + + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + * + * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(_getElementObject(refCanvas)); + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + var orientation = null; + var _lastResult = null; + + this.compute = function(params) { + var xy = params.xy, element = params.element; + var result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + + // adjust loc if there is an offsetParent + if (element.canvas && element.canvas.offsetParent) { + var po = element.canvas.offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(element.canvas.offsetParent); + result[0] = result[0] - po.left; + result[1] = result[1] - po.top; + } + + _lastResult = result; + return result; + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { orientation = anchor.getOrientation(); }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchors is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = [], + _convert = function(anchor) { return anchor.constructor == Anchor ? anchor: jsPlumb.makeAnchor(anchor); }; + for (var i = 0; i < anchors.length; i++) _anchors[i] = _convert(anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + this.locked = false; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null, + _curIndex = _anchors.length > 0 ? 0 : -1, + self = this, + + // helper method to calculate the distance between the centers of the two elements. + _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }, + + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + + var pos = _curAnchor.compute(params); + return pos; + }; + + this.getCurrentLocation = function() { + var cl = _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + return cl; + }; + + this.getOrientation = function() { return _curAnchor != null ? _curAnchor.getOrientation() : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * container - optional id or selector instructing jsPlumb where to attach all the elements it creates for this connection. you should read the documentation for a full discussion of this. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * overlays - Optional array of Overlay definitions to appear on this Connection. + * drawEndpoints - if false, instructs jsPlumb to not draw the endpoints for this Connection. Be careful with this: it only really works when you tell jsPlumb to attach elements to the document body. Read the documentation for a full discussion of this. + */ + var Connection = function(params) { + + jsPlumbUIComponent.apply(this, arguments); + // ************** get the source and target and register the connection. ******************* + var self = this; + var visible = true; + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none"; + }; + var id = new String('_jsplumb_c_' + (new Date()).getTime()); + this.getId = function() { return id; }; + this.parent = params.parent; + /** + Property: source + The source element for this Connection. + */ + this.source = _getElementObject(params.source); + /** + Property:target + The target element for this Connection. + */ + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. + if (params.sourceEndpoint) this.source = params.sourceEndpoint.getElement(); + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + /* + * Property: sourceId + * Id of the source element in the connection. + */ + this.sourceId = _getAttribute(this.source, "id"); + /* + * Property: targetId + * Id of the target element in the connection. + */ + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + + /** + * implementation of abstract method in EventGenerator + * @return list of attached elements. in our case, a list of Endpoints. + */ + this.getAttachedElements = function() { + return self.endpoints; + }; + + /** + * implementation of abstract method in EventGenerator + */ + this.savePosition = function() { + srcWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.source)); + targetWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.target)); + }; + + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams) { + if (anchorParams) + return jsPlumb.makeAnchor(anchorParams); + }; + var prepareEndpoint = function(existing, index, params, element, connectorPaintStyle, connectorHoverPaintStyle) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] + || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint; + + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified. + if (es.fillStyle == null && connectorPaintStyle != null) + es.fillStyle = connectorPaintStyle.strokeStyle; + + // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does: + //* + if (es.outlineColor == null && connectorPaintStyle != null) + es.outlineColor = connectorPaintStyle.outlineColor; + if (es.outlineWidth == null && connectorPaintStyle != null) + es.outlineWidth = connectorPaintStyle.outlineWidth; + //*/ + + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure? + if (connectorHoverPaintStyle != null) { + if (ehs == null) ehs = {}; + if (ehs.fillStyle == null) { + ehs.fillStyle = connectorHoverPaintStyle.strokeStyle; + } + } + var a = params.anchors ? params.anchors[index] : _makeAnchor(_currentInstance.Defaults.Anchors[index]) || _makeAnchor(jsPlumb.Defaults.Anchors[index]) || _makeAnchor(_currentInstance.Defaults.Anchor) || _makeAnchor(jsPlumb.Defaults.Anchor), + u = params.uuids ? params.uuids[index] : null, + e = _newEndpoint({ + paintStyle : es, + hoverPaintStyle:ehs, + endpoint : ep, + connections : [ self ], + uuid : u, + anchor : a, + source : element, + container:params.container + }); + self.endpoints[index] = e; + + if (params.drawEndpoints === false) e.setVisible(false, true, true); + + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source, params.paintStyle, params.hoverPaintStyle); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target, params.paintStyle, params.hoverPaintStyle); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + /* + * Function: setConnector + * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method - the same + * thing that you would set as the 'connector' property on a jsPlumb.connect call. + * + * Parameters: + * connector - Connector definition + */ + this.setConnector = function(connector, doNotRepaint) { + if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent); + var connectorArgs = { _jsPlumb:self._jsPlumb, parent:params.parent, cssClass:params.cssClass, container:params.container }; + if (connector.constructor == String) + this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand. + else if (connector.constructor == Array) + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs)); + this.canvas = this.connector.canvas; + // add mouse events + this.connector.bind("click", function(con, e) { + _mouseWasDown = false; + self.fire("click", self, e); + }); + this.connector.bind("dblclick", function(con, e) { _mouseWasDown = false;self.fire("dblclick", self, e); }); + this.connector.bind("mouseenter", function(con, e) { + if (!self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(true); + } + self.fire("mouseenter", self, e); + } + }); + this.connector.bind("mouseexit", function(con, e) { + if (self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(false); + } + self.fire("mouseexit", self, e); + } + }); + this.connector.bind("mousedown", function(con, e) { + _mouseDown = true; + _mouseDownAt = jsPlumb.CurrentLibrary.getPageXY(e); + self.savePosition(); + }); + this.connector.bind("mouseup", function(con, e) { + _mouseDown = false; + if (self.connector == _connectionBeingDragged) _connectionBeingDragged = null; + }); + + if (!doNotRepaint) self.repaint(); + }; + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc) + */ + + self.setConnector(this.endpoints[0].connector || + this.endpoints[1].connector || + params.connector || + _currentInstance.Defaults.Connector || + jsPlumb.Defaults.Connector, true); + + this.setPaintStyle(this.endpoints[0].connectorStyle || + this.endpoints[1].connectorStyle || + params.paintStyle || + _currentInstance.Defaults.PaintStyle || + jsPlumb.Defaults.PaintStyle, true); + + this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || + this.endpoints[1].connectorHoverStyle || + params.hoverPaintStyle || + _currentInstance.Defaults.HoverPaintStyle || + jsPlumb.Defaults.HoverPaintStyle, true); + + this.paintStyleInUse = this.paintStyle; + + /* + * Property: overlays + * List of Overlays for this Connection. + */ + this.overlays = []; + var _overlays = params.overlays || _currentInstance.Defaults.Overlays; + if (_overlays) { + for (var i = 0; i < _overlays.length; i++) { + var o = _overlays[i], _newOverlay = null; + if (o.constructor == Array) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0]; + var p = jsPlumb.CurrentLibrary.extend({connection:self, _jsPlumb:_currentInstance}, o[1]); // make a copy of the object so as not to mess up anyone else's reference... + if (o.length == 3) jsPlumb.CurrentLibrary.extend(p, o[2]); + _newOverlay = new jsPlumb.Overlays[renderMode][type](p); + if (p.events) { + for (var evt in p.events) { + _newOverlay.bind(evt, p.events[evt]); + } + } + } else if (o.constructor == String) { + _newOverlay = new jsPlumb.Overlays[renderMode][o]({connection:self, _jsPlumb:_currentInstance}); + } else { + _newOverlay = o; + } + + + + this.overlays.push(_newOverlay); + } + } + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Connection. + * + * Parameters: + * event - the event to bind. Available events on a Connection are: + * - *click* : notification that a Connection was clicked. + * - *dblclick* : notification that a Connection was double clicked. + * - *mouseenter* : notification that the mouse is over a Connection. + * - *mouseexit* : notification that the mouse exited a Connection. + * + * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Connection's paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Connection will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Connection. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + // overlay finder helper method + var _getOverlayIndex = function(id) { + var idx = -1; + for (var i = 0; i < self.overlays.length; i++) { + if (id === self.overlays[i].id) { + idx = i; + break; + } + } + return idx; + }; + + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + this.addOverlay = function(overlay) { self.overlays.push(overlay); }; + + /* + * Function: getOverlay + * Gets an overlay, by ID. Note: by ID. You would pass an 'id' parameter + * in to the Overlay's constructor arguments, and then use that to retrieve + * it via this method. + */ + this.getOverlay = function(id) { + var idx = _getOverlayIndex(id); + return idx >= 0 ? self.overlays[idx] : null; + }; + + /* + * Function: hideOverlay + * Hides the overlay specified by the given id. + */ + this.hideOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.hide(); + }; + + /* + * Function: showOverlay + * Shows the overlay specified by the given id. + */ + this.showOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.show(); + }; + + /** + * Function: removeAllOverlays + * Removes all overlays from the Connection, and then repaints. + */ + this.removeAllOverlays = function() { + self.overlays.splice(0, self.overlays.length); + self.repaint(); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + this.removeOverlay = function(overlayId) { + var idx = _getOverlayIndex(overlayId); + if (idx != -1) self.overlays.splice(idx, 1); + }; + + /** + * Function:removeOverlays + * Removes a set of overlays by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + this.removeOverlays = function() { + for (var i = 0; i < arguments.length; i++) + self.removeOverlay(arguments[i]); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + this.labelStyle = params.labelStyle || _currentInstance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + if (this.label) { + this.overlays.push(new jsPlumb.Overlays[renderMode].Label( { + cssClass:params.cssClass, + labelStyle : this.labelStyle, + label : this.label, + connection:self, + _jsPlumb:_currentInstance + })); + } + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String or a Function that returns a String. + */ + this.setLabel = function(l) { + self.label = l; + _currentInstance.repaint(self.source); + }; + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var otherOffset = offsets[this.targetId]; + var otherWH = sizes[this.targetId]; + var anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1] + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0] + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc }); + + /* + * Paints the Connection. Not exposed for public usage. + * + * Parameters: + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp, + // if the moving object is not the source we must transpose the two references. + swap = false, + tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId, + tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + + _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }); + _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var sAnchorP = this.endpoints[sIdx].anchor.getCurrentLocation(), + tAnchorP = this.endpoints[tIdx].anchor.getCurrentLocation(); + + /* paint overlays*/ + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible()) { + var s = o.computeMaxSize(self.connector); + if (s > maxSize) + maxSize = s; + } + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, self.paintStyleInUse.lineWidth, maxSize); + + self.connector.paint(dim, self.paintStyleInUse); + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) + self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim); + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function() { + this.paint({ elId : this.sourceId, recalc : true }); + }; + + _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide + // whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + + // just to make sure the UI gets initialised fully on all browsers. + self.repaint(); + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1 + * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't + * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you + * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or + * 'target' Endpoints for Connections. + * + * + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * container - optional id or selector instructing jsPlumb where to attach the element it creates for this endpoint. you should read the documentation for a full discussion of this. + * connections - optional list of Connections to configure the Endpoint with. + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + params = params || {}; + var self = this; +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Endpoint. + * + * Parameters: + * event - the event to bind. Available events on an Endpoint are: + * - *click* : notification that a Endpoint was clicked. + * - *dblclick* : notification that a Endpoint was double clicked. + * - *mouseenter* : notification that the mouse is over a Endpoint. + * - *mouseexit* : notification that the mouse exited a Endpoint. + * + * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Endpoint's paint style and then repaints the Endpoint. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Endpoint will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Endpoint. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + var visible = true; + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + var id = new String('_jsplumb_e_' + (new Date()).getTime()); + this.getId = function() { return id; }; + if (params.dynamicAnchors) + self.anchor = new DynamicAnchor(jsPlumb.makeAnchors(params.dynamicAnchors)); + else + self.anchor = params.anchor ? jsPlumb.makeAnchor(params.anchor) : params.anchors ? jsPlumb.makeAnchor(params.anchors) : jsPlumb.makeAnchor("TopCenter"); + var _endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot", + endpointArgs = { _jsPlumb:self._jsPlumb, parent:params.parent, container:params.container }; + if (_endpoint.constructor == String) + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint](endpointArgs); + else if (_endpoint.constructor == Array) { + endpointArgs = jsPlumb.extend(_endpoint[1], endpointArgs); + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint[0]](endpointArgs); + } + else + _endpoint = _endpoint.clone(); + + // assign a clone function using our derived endpointArgs. this is used when a drag starts: the endpoint that was dragged is cloned, + // and the clone is left in its place while the original one goes off on a magical journey. + this.clone = function() { + var o = new Object(); + _endpoint.constructor.apply(o, [endpointArgs]); + return o; + }; + + self.endpoint = _endpoint; + self.type = self.endpoint.type; + + // TODO this event listener registration code is identical to what Connection does: it should be refactored. + this.endpoint.bind("click", function(e) { self.fire("click", self, e); }); + this.endpoint.bind("dblclick", function(e) { self.fire("dblclick", self, e); }); + this.endpoint.bind("mouseenter", function(con, e) { + if (!self.isHover()) { + self.setHover(true); + self.fire("mouseenter", self, e); + } + }); + this.endpoint.bind("mouseexit", function(con, e) { + if (self.isHover()) { + self.setHover(false); + self.fire("mouseexit", self, e); + } + }); + // TODO this event listener registration code above is identical to what Connection does: it should be refactored. + + this.setPaintStyle(params.paintStyle || + params.style || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyle, true); + this.setHoverPaintStyle(params.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyle, true); + this.paintStyleInUse = this.paintStyle; + this.connectorStyle = params.connectorStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.parent = params.parent; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + var _element = params.source, + _uuid = params.uuid, + floatingEndpoint = null, + inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + this.element = _element; + var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + /* + * Property: canvas + * The Endpoint's Canvas. + */ + this.canvas = this.endpoint.canvas; + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + this.connections = params.connections || []; + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + var _reattach = params.reattach || false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + + // this avoids a circular loop + if (!ignoreTarget) { + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + t.detach(connection, true); + // check connection to see if we want to delete the other endpoint. + // if the user uses makeTarget to make some element a target for connections, + // it is possible that they will have set 'endpointToDeleteOnDetach': when + // you make a connection to an element that acts as a target (note: NOT an + // Endpoint; just some div as a target), Endpoints are created for that + // connection. so if you then delete that Connection, it is feasible you + // will want these auto-generated endpoints to be removed. + if (connection.endpointToDeleteOnDetach && connection.endpointToDeleteOnDetach.connections.length == 0) + jsPlumb.deleteEndpoint(connection.endpointToDeleteOnDetach); + } + _removeElements(connection.connector.getDisplayElements(), connection.parent); + _removeFromList(connectionsByScope, connection.scope, connection); + if(!ignoreTarget) fireDetachEvent(connection); + } + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + */ + this.detachAll = function() { + while (self.connections.length > 0) { + self.detach(self.connections[0]); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + */ + this.detachFrom = function(targetEndpoint) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + c[i].setHover(false); + self.detach(c[i]); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + return _newEndpoint( { anchor : self.anchor, source : _element, paintStyle : this.paintStyle, endpoint : _endpoint }); + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + return (self.connections.length < _maxConnections) || _maxConnections == -1 ? null : self.connections[0]; + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + /* + * Function: setStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to + * setStyle and deprecate it if so. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + * + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + // a helper function that tries to find a connection to the given element, and returns it if so. if elementWithPrecedence is null, + // or no connection to it is found, we return the first connection in our list. + var findConnectionToUseForDynamicAnchor = function(elementWithPrecedence) { + var idx = 0; + if (elementWithPrecedence != null) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].sourceId == elementWithPrecedence || self.connections[i].targetId == elementWithPrecedence) { + idx = i; + break; + } + } + } + + return self.connections[idx]; + }; + + /* + * Function: paint + * Paints the Endpoint, recalculating offset and anchor positions if necessary. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + + params = params || {}; + var timestamp = params.timestamp; + if (!timestamp || self.timestamp !== timestamp) { + var ap = params.anchorPoint, canvas = params.canvas, connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + var xy = params.offset || offsets[_elementId]; + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (self.anchor.isDynamic) { + if (self.connections.length > 0) { + //var c = self.connections[0]; + var c = findConnectionToUseForDynamicAnchor(params.elementWithPrecedence); + var oIdx = c.endpoints[0] == self ? 1 : 0; + var oId = oIdx == 0 ? c.sourceId : c.targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + } + ap = self.anchor.compute(anchorParams); + } + + var d = _endpoint.compute(ap, self.anchor.getOrientation(), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + _endpoint.paint(d, self.paintStyleInUse, self.anchor); + + self.timestamp = timestamp; + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + var start = function() { + jpc = self.connectorSelector(); + if (self.isFull() && !dragAllowedWhenFull) return false; + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + n.style.position = "absolute"; + var nE = _getElementObject(n); + _appendElement(n, self.parent); + // create and assign an id, and initialize the offset. + var id = _getId(nE); + + // set the offset of this div to be where 'inPlaceCopy' is, to start with. + var ipcoel = _getElementObject(inPlaceCopy.canvas), + ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel), + po = inPlaceCopy.canvas.offsetParent != null ? + inPlaceCopy.canvas.offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(inPlaceCopy.canvas.offsetParent) + : { left:0, top: 0}; + jsPlumb.CurrentLibrary.setOffset(n, {left:ipco.left - po.left, top:ipco.top-po.top}); + + _updateOffset( { elId : id }); + + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor( { reference : self.anchor, referenceCanvas : self.canvas }); + floatingEndpoint = _newEndpoint({ paintStyle : self.paintStyle, endpoint : _endpoint, anchor : floatingAnchor, source : nE }); + + if (jpc == null) { + self.anchor.locked = true; + // create a connection. one end is this endpoint, the + // other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : _getElementObject(_element), + target : _getElementObject(n), + anchors : [ self.anchor, floatingAnchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays + }); + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.connector.setHover(false); + } else { + existingJpc = true; + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.connector.setHover(false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas)); + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; // are we the source or the target? + + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas); + var dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // now we want to get this endpoint's DROP scope, and set it for now: we can only be dropped on drop zones + // that have our drop scope (issue 57). + var dropScope = jsPlumb.CurrentLibrary.getDropScope(c);//jpc.endpoints[anchorIdx == 0 ? 1 : 0].getDropScope(); + jsPlumb.CurrentLibrary.setDragScope(c, dropScope); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it and register connection on it. + floatingConnections[id] = jpc; + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var jpcl = jsPlumb.CurrentLibrary, + dragOptions = params.dragOptions || {}, + defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions), + startEvent = jpcl.dragEvents['start'], + stopEvent = jpcl.dragEvents['stop'], + dragEvent = jpcl.dragEvents['drag']; + + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], + function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + jsPlumb.CurrentLibrary.setOffset(n, _ui); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements( [ n, floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (_reattach) { + + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } else { + jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc); // the main endpoint will inform the floating endpoint + // to disconnect, and also post the detached event. + } + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElements(jpc.connector.getDisplayElements(), self.parent); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint(); + jpc.setHover(false); + jpc.repaint(); + jpc = null; + delete inPlaceCopy; + delete endpointsByElement[floatingEndpoint.elementId]; + floatingEndpoint = null; + delete floatingEndpoint; + + _currentInstance.currentlyDragging = false; + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas) { + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + var drop = function() { + var draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)); + var id = _getAttribute(draggable, "dragId"); + var elId = _getAttribute(draggable, "elId"); + + // restore the original scope if necessary (issue 57) + var scope = _getAttribute(draggable, "originalScope"); + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + var jpc = floatingConnections[id]; + + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // todo test that the target is not full. + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + if (!jpc.suspendedEndpoint) { + _addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + _currentInstance.fire("jsPlumbConnectionDetached", { + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }); + } + + jsPlumb.repaint(elId); + + _currentInstance.fire("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], + targetEndpoint : jpc.endpoints[1], + connection:jpc + }); + } + + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute( _getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + } + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute(_getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + } + }); + + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas)); + + return self; + }; + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + j.init(); + return j; + }; + + var _curryAnchor = function(x,y,ox,oy) { + return function() { + return jsPlumb.makeAnchor(x,y,ox,oy); + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1); + + jsPlumb.Defaults.DynamicAnchors = function() { + return jsPlumb.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"]); + }; + jsPlumb.Anchors["AutoDefault"] = function() { return jsPlumb.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors()); }; + + +})(); +/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the default Connectors, Endpoint and Overlay definitions. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +(function() { + + /** + * + * Helper class to consume unused mouse events by components that are DOM elements and + * are used by all of the different rendering modes. + * + */ + jsPlumb.DOMElementComponent = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + // when render mode is canvas, these functions may be called by the canvas mouse handler. + // this component is safe to pipe this stuff to /dev/null. + this.mousemove = + this.dblclick = + this.click = + this.mousedown = + this.mouseup = function(e) { }; + }; + + /** + * Class: Connectors.Straight + * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters. + */ + jsPlumb.Connectors.Straight = function() { + this.type = "Straight"; + var self = this; + var currentPoints = null; + var _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + _dx = _tx - _sx, _dy = (_ty - _sy); + _m = _dy / _dx, _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + + return currentPoints; + }; + + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + var xp = _sx + (location * _dx); + var yp = (_m == Infinity || _m == -Infinity) ? _sy + (location * (_ty - _sy)) : (_m * xp) + _b; + return {x:xp, y:yp}; + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { return _m; }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + var orientation = distance > 0 ? 1 : -1; + var y = Math.abs(distance * Math.sin(_theta)); + if (_sy > _ty) y = y * -1; + var x = Math.abs(distance * Math.cos(_theta)); + if (_sx > _tx) x = x * -1; + return {x:p.x + (orientation * x), y:p.y + (orientation * y)}; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = self.gradientAtPoint(p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + }; + }; + + + /** + * Class:Connectors.Bezier + * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's + * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below. + */ + /** + * Function:Constructor + * + * Parameters: + * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line. + * Optional; defaults to 150. + * + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + params = params || {}; + this.majorAnchor = params.curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + this.type = "Bezier"; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h; + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) + { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx); var minx2 = Math.min(_CP[0], _CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(_sx,_tx); var maxx2 = Math.max(_CP[0], _CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty); var miny2 = Math.min(_CP[1], _CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(_sy,_ty); var maxy2 = Math.max(_CP[1], _CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + return jsBezier.perpendicularToCurveAt(_makeCurve(), location, length, distance); + }; + + }; + + + /** + * Class: Connectors.Flowchart + * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments. + */ + /** + * Function: Constructor + * + * Parameters: + * stub - minimum length for the stub at each end of the connector. defaults to 30 pixels. + */ + jsPlumb.Connectors.Flowchart = function(params) { + this.type = "Flowchart"; + params = params || {}; + var self = this, + minStubLength = params.stub || params.minStubLength /* bwds compat. */ || 30, + segments = [], + segmentGradients = [], + segmentProportions = [], + segmentLengths = [], + segmentProportionalLengths = [], + points = [], + swapX, + swapY, + /** + * recalculates the gradients of each segment, and the points at which the segments begin, proportional to the total length travelled + * by all the segments that constitute the connector. + */ + updateSegmentGradientsAndProportions = function(startX, startY, endX, endY) { + var total = 0; + for (var i = 0; i < segments.length; i++) { + var sx = i == 0 ? startX : segments[i][2], + sy = i == 0 ? startY : segments[i][3], + ex = segments[i][0], + ey = segments[i][1]; + + segmentGradients[i] = sx == ex ? Infinity : 0; + segmentLengths[i] = Math.abs(sx == ex ? ey - sy : ex - sx); + total += segmentLengths[i]; + } + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segmentLengths[i] / total; + segmentProportions[i] = [curLoc, (curLoc += (segmentLengths[i] / total)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0]; + var ly = segments.length == 0 ? sy : segments[segments.length - 1][1]; + segments.push([x, y, lx, ly]); + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment + * that contains the point which is 'location' distance along the entire path, where + * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths + * are made up of a list of segments, each of which contributes some fraction to + * the total length. + */ + findSegmentForLocation = function(location) { + var idx = segmentProportions.length - 1, inSegmentProportion = 0; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + segments = []; + segmentGradients = []; + segmentProportionalLengths = []; + segmentLengths = []; + segmentProportionals = []; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + offx = (lw / 2) + (minStubLength * 2), + offy = (lw / 2) + (minStubLength * 2), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(), + to = targetAnchor.orientation || targetAnchor.getOrientation(), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx, + h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy; + if (w < minWidth) { + offx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + offy += (minWidth - h) / 2; + h = minWidth; + } + var sx = swapX ? w-offx : offx, + sy = swapY ? h-offy : offy, + tx = swapX ? offx : w-offx , + ty = swapY ? offy : h-offy, + startStubX = sx + (so[0] * minStubLength), + startStubY = sy + (so[1] * minStubLength), + endStubX = tx + (to[0] * minStubLength), + endStubY = ty + (to[1] * minStubLength), + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2); + + x -= offx; y -= offy; + points = [x, y, w, h, sx, sy, tx, ty], extraPoints = []; + + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + if (so[0] == 0) { + var startStubIsBeforeEndStub = startStubY < endStubY; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } + } + else { + var startStubIsBeforeEndStub = startStubX < endStubX; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } + } + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentGradientsAndProportions(sx, sy, tx, ty); + + return points; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location) { + return self.pointAlongPathFrom(location, 0); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location) { + return segmentGradients[findSegmentForLocation(location)["index"]]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance) { + var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segmentLengths[s.index], m = segmentGradients[s.index]; + var e = { + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + return e; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = segmentGradients[p.segmentInfo.index]; + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + + }; + + }; + + // ********************************* END OF CONNECTOR TYPES ******************************************************************* + + // ********************************* ENDPOINT TYPES ******************************************************************* + + /** + * Class: Endpoints.Dot + * A round endpoint, with default radius 10 pixels. + */ + + /** + * Function: Constructor + * + * Parameters: + * + * radius - radius of the endpoint. defaults to 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + this.type = "Dot"; + var self = this; + params = params || {}; + this.radius = params.radius || 10; + this.defaultOffset = 0.5 * this.radius; + this.defaultInnerRadius = this.radius / 3; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var r = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - r; + var y = anchorPoint[1] - r; + return [ x, y, r * 2, r * 2, r ]; + }; + }; + + /** + * Class: Endpoints.Rectangle + * A Rectangular Endpoint, with default size 20x20. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the endpoint. defaults to 20 pixels. + * height - height of the endpoint. defaults to 20 pixels. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + this.type = "Rectangle"; + var self = this; + params = params || {}; + this.width = params.width || 20; + this.height = params.height || 20; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + return [ x, y, width, height]; + }; + }; + + /** + * Class: Endpoints.Image + * Draws an image as the Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * src - location of the image to use. + */ + jsPlumb.Endpoints.Image = function(params) { + + this.type = "Image"; + jsPlumb.DOMElementComponent.apply(this, arguments); + + var self = this, initialized = false; + this.img = new Image(); + self.ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.src || params.url; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + self.anchorPoint = anchorPoint; + if (self.ready) return [anchorPoint[0] - self.img.width / 2, anchorPoint[1] - self.img.height/ 2, self.img.width, self.img.height]; + else return [0,0,0,0]; + }; + + self.canvas = document.createElement("img"), initialized = false; + self.canvas.style["margin"] = 0; + self.canvas.style["padding"] = 0; + self.canvas.style["outline"] = 0; + self.canvas.style["position"] = "absolute"; + self.canvas.className = jsPlumb.endpointClass; + jsPlumb.appendElement(self.canvas, params.parent); + self.attachListeners(self.canvas, self); + + var actuallyPaint = function(d, style, anchor) { + if (!initialized) { + self.canvas.setAttribute("src", self.img.src); + initialized = true; + } + var width = self.img.width, + height = self.img.height, + x = self.anchorPoint[0] - (width/2), + y = self.anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(self.canvas, x, y, width, height); + }; + + this.paint = function(d, style, anchor) { + if (self.ready) { + actuallyPaint(d, style, anchor); + } + else { + window.setTimeout(function() { + self.paint(d, style, anchor); + }, 200); + } + }; + }; + + /** + * Class: Endpoints.Blank + * An Endpoint that paints nothing on the screen, and cannot be interacted with using the mouse. There are no constructor parameters for this Endpoint. + */ + jsPlumb.Endpoints.Blank = function(params) { + var self = this; + this.type = "Blank"; + jsPlumb.DOMElementComponent.apply(this, arguments); + this.compute = function() { + return [0,0,10,0]; + }; + + self.canvas = document.createElement("div"); + self.canvas.style.display = "block"; + self.canvas.style.width = "1px"; + self.canvas.style.height = "1px"; + self.canvas.style.background = "transparent"; + self.canvas.style.position = "absolute"; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function() { }; + }; + + /** + * Class: Endpoints.Triangle + * A triangular Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the triangle's base. defaults to 55 pixels. + * height - height of the triangle from base to apex. defaults to 55 pixels. + */ + jsPlumb.Endpoints.Triangle = function(params) { + this.type = "Triangle"; + params = params || { }; + params.width = params.width || 55; + param.height = params.height || 55; + this.width = params.width; + this.height = params.height; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + return [ x, y, width, height ]; + }; + }; +// ********************************* END OF ENDPOINT TYPES ******************************************************************* + + +// ********************************* OVERLAY DEFINITIONS *********************************************************************** + + var AbstractOverlay = function() { + var visible = true, self = this; + this.setVisible = function(val) { + visible = val; + self.connection.repaint(); + }; + this.isVisible = function() { return visible; }; + this.hide = function() { self.setVisible(false); }; + this.show = function() { self.setVisible(true); }; + }; + + + /** + * Class: Overlays.Arrow + * + * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length + * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction + * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line + * across the tail. + */ + /** + * Function: Constructor + * + * Parameters: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + this.type = "Arrow"; + AbstractOverlay.apply(this); + params = params || {}; + var self = this; + + this.length = params.length || 20; + this.width = params.width || 20; + this.id = params.id; + this.connection = params.connection; + var direction = (params.direction || 1) < 0 ? -1 : 1; + var paintStyle = params.paintStyle || { lineWidth:1 }; + this.loc = params.location == null ? 0.5 : params.location; + // how far along the arrow the lines folding back in come to. default is 62.3%. + var foldback = params.foldback || 0.623; + var _getFoldBackPoint = function(connector, loc) { + if (foldback == 0.5) return connector.pointOnPath(loc); + else { + var adj = 0.5 - foldback; // we calculate relative to the center + return connector.pointAlongPathFrom(loc, direction * self.length * adj); + } + }; + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + // this is the arrow head position + var hxy = connector.pointAlongPathFrom(self.loc, direction * (self.length / 2)); + // this is the center of the tail + var txy = connector.pointAlongPathFrom(self.loc, -1 * direction * (self.length / 2)), tx = txy.x, ty = txy.y; + // this is the tail vector + var tail = connector.perpendicularToPathAt(self.loc, self.width, -1 * direction * (self.length / 2)); + // this is the point the tail goes in to + var cxy = _getFoldBackPoint(connector, self.loc); + + // if loc = 1, then hxy should be flush with the element, or if direction == -1, the tail midpoint. + if (self.loc == 1) { + var lxy = connector.pointOnPath(self.loc); + // TODO determine why the 1.2.6 released version does not + // use 'direction' in the two equations below, yet both + // that and 1.3.0 still paint the arrows correctly. + var dx = (lxy.x - hxy.x) * direction, dy = (lxy.y - hxy.y) * direction; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + // if loc = 0, then tail midpoint should be flush with the element, or, if direction == -1, hxy should be. + if (self.loc == 0) { + var lxy = connector.pointOnPath(self.loc); + var tailMid = foldback > 1 ? cxy : { + x:tail[0].x + ((tail[1].x - tail[0].x) / 2), + y:tail[0].y + ((tail[1].y - tail[0].y) / 2) + }; + var dx = (lxy.x - tailMid.x) * direction, dy = (lxy.y - tailMid.y) * direction; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x); + var maxx = Math.max(hxy.x, tail[0].x, tail[1].x); + var miny = Math.min(hxy.y, tail[0].y, tail[1].y); + var maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + var d = { hxy:hxy, tail:tail, cxy:cxy }, + strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle, + fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle, + lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth; + + self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions); + + return [ minx, maxx, miny, maxy]; + }; + }; + + /** + * Class: Overlays.PlainArrow + * + * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "PlainArrow"; + }; + + /** + * Class: Overlays.Diamond + * + * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40; + var p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "Diamond"; + }; + + + + /** + * Class: Overlays.Label + * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb + * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter + * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it + * puts on the Label's 'style' attribute, so the end result is the same. + */ + /** + * Function: Constructor + * + * Parameters: + * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes + * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle. + * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * labelStyle - (deprecated) js object containing style instructions for the label. defaults to jsPlumb.Defaults.LabelStyle. + * borderWidth - (deprecated) width of a border to paint. defaults to zero. + * borderStyle - (deprecated) strokeStyle to use when painting the border, if necessary. + * + */ + jsPlumb.Overlays.Label = function(params) { + this.type = "Label"; + jsPlumb.DOMElementComponent.apply(this, arguments); + AbstractOverlay.apply(this); + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.labelStyle.font = this.labelStyle.font || "12px sans-serif"; + this.label = params.label || "banana"; + this.connection = params.connection; + this.id = params.id; + var self = this; + var labelWidth = null, labelHeight = null, labelText = null, labelPadding = null; + this.location = params.location || 0.5; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var initialised = false, + labelText = null, + div = document.createElement("div"); + div.style["position"] = "absolute"; + div.style["font"] = self.labelStyle.font; + div.style["color"] = self.labelStyle.color || "black"; + if (self.labelStyle.fillStyle) div.style["background"] = self.labelStyle.fillStyle;//_convertStyle(self.labelStyle.fillStyle, true); + if (self.labelStyle.borderWidth > 0) { + var dStyle = self.labelStyle.borderStyle ? self.labelStyle.borderStyle/*_convertStyle(self.labelStyle.borderStyle, true)*/ : "black"; + div.style["border"] = self.labelStyle.borderWidth + "px solid " + dStyle; + } + if (self.labelStyle.padding) div.style["padding"] = self.labelStyle.padding; + + var clazz = params["_jsPlumb"].overlayClass + " " + + (self.labelStyle.cssClass ? self.labelStyle.cssClass : + params.cssClass ? params.cssClass : ""); + + div.className = clazz; + + jsPlumb.appendElement(div, params.connection.parent); + jsPlumb.getId(div); + self.attachListeners(div, self); + + //override setVisible + var osv = self.setVisible; + self.setVisible = function(state) { + osv(state); // call superclass + div.style.display = state ? "block" : "none"; + }; + + this.paint = function(connector, d, connectorDimensions) { + if (!initialised) { + connector.appendDisplayElement(div); + self.attachListeners(div, connector); + initialised = true; + } + div.style.left = (connectorDimensions[0] + d.minx) + "px"; + div.style.top = (connectorDimensions[1] + d.miny) + "px"; + }; + + this.getTextDimensions = function(connector) { + labelText = typeof self.label == 'function' ? self.label(self) : self.label; + div.innerHTML = labelText.replace(/\r\n/g, "
"); + var de = jsPlumb.CurrentLibrary.getElementObject(div), + s = jsPlumb.CurrentLibrary.getSize(de); + return {width:s[0], height:s[1]}; + }; + + this.computeMaxSize = function(connector) { + var td = self.getTextDimensions(connector); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + var td = self.getTextDimensions(connector); + if (td.width != null) { + var cxy = connector.pointOnPath(self.location); + + var minx = cxy.x - (td.width / 2); + var miny = cxy.y - (td.height / 2); + + self.paint(connector, { + minx:minx, + miny:miny, + td:td, + cxy:cxy + }, connectorDimensions); + + return [minx, minx+td.width, miny, miny+td.height]; + } + else return [0,0,0,0]; + }; + }; + + // ********************************* END OF OVERLAY DEFINITIONS *********************************************************************** + + // ********************************* OVERLAY CANVAS RENDERERS*********************************************************************** + + // ********************************* END OF OVERLAY CANVAS RENDERERS *********************************************************************** +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the VML renderers. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +;(function() { + + // http://ajaxian.com/archives/the-vml-changes-in-ie-8 + // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/ + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + + var vmlAttributeMap = { + "stroke-linejoin":"joinstyle", + "joinstyle":"joinstyle", + "endcap":"endcap", + "miterlimit":"miterlimit" + }; + + if (document.createStyleSheet) { + + // this is the style rule for IE7/6: it uses a CSS class, tidy. + document.createStyleSheet().addRule(".jsplumb_vml", "behavior:url(#default#VML);position:absolute;"); + + // these are for VML in IE8. you have to explicitly call out which elements + // you're going to expect to support VML! + // + // try to avoid IE8. it is recommended you set X-UA-Compatible="IE=7" if you can. + // + document.createStyleSheet().addRule("jsplumb\\:textbox", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:oval", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:rect", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:stroke", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:shape", "behavior:url(#default#VML);position:absolute;"); + + // in this page it is also mentioned that IE requires the extra arg to the namespace + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either. + // var iev = document.documentMode; + //if (!iev || iev < 8) + document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); + //else + // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML"); + } + + var scale = 1000, + _atts = function(o, atts) { + for (var i in atts) { + // IE8 fix: setattribute does not work after an element has been added to the dom! + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + //o.setAttribute(i, atts[i]); + o[i] = atts[i]; + } + }, + _node = function(name, d, atts) { + atts = atts || {}; + var o = document.createElement("jsplumb:" + name); + o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml"; + _pos(o, d); + _atts(o, atts); + return o; + }, + _pos = function(o,d) { + o.style.left = d[0] + "px"; + o.style.top = d[1] + "px"; + o.style.width= d[2] + "px"; + o.style.height= d[3] + "px"; + o.style.position = "absolute"; + }, + _conv = function(v) { + return Math.floor(v * scale); + }, + _convertStyle = function(s, ignoreAlpha) { + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + + return o; + }, + _applyStyles = function(node, style, component) { + var styleToWrite = {}; + if (style.strokeStyle) { + styleToWrite["stroked"] = "true"; + styleToWrite["strokecolor"] =_convertStyle(style.strokeStyle, true); + styleToWrite["strokeweight"] = style.lineWidth + "px"; + } + else styleToWrite["stroked"] = "false"; + + if (style.fillStyle) { + styleToWrite["filled"] = "true"; + styleToWrite["fillcolor"] = _convertStyle(style.fillStyle, true); + } + else styleToWrite["filled"] = "false"; + + if(style["dashstyle"]) { + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = style["dashstyle"]; + } + else if (style["stroke-dasharray"] && style["lineWidth"]) { + var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",", + parts = style["stroke-dasharray"].split(sep), + styleToUse = ""; + for(var i = 0; i < parts.length; i++) { + styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep); + } + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = styleToUse; + } + + _atts(node, styleToWrite); + }, + /* + * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. + */ + VmlComponent = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }, + /* + * Base class for Vml connectors. extends VmlComponent. + */ + VmlConnector = function(params) { + var self = this; + self.strokeNode = null; + self.canvas = null; + VmlComponent.apply(this, arguments); + clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : ""); + this.paint = function(d, style, anchor) { + if (style != null) { + var path = self.getPath(d), p = { "path":path }; + + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + outlineStyle = { + strokeStyle:_convertStyle(style.outlineColor), + lineWidth:outlineStrokeWidth + }; + + if (self.bgCanvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.bgCanvas = _node("shape", d, p); + jsPlumb.appendElement(self.bgCanvas, params.parent); + _pos(self.bgCanvas, d); + displayElements.push(self.bgCanvas); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.bgCanvas, d); + _atts(self.bgCanvas, p); + } + + _applyStyles(self.bgCanvas, outlineStyle, self); + } + + if (self.canvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.canvas = _node("shape", d, p); + jsPlumb.appendElement(self.canvas, params.parent); + displayElements.push(self.canvas); + + self.attachListeners(self.canvas, self); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.canvas, d); + _atts(self.canvas, p); + } + + _applyStyles(self.canvas, style, self); + } + }; + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + self.canvas.parentNode.appendChild(el); + displayElements.push(el); + }; + }, + /* + * + * Base class for Vml Endpoints. extends VmlComponent. + * + */ + VmlEndpoint = function(params) { + VmlComponent.apply(this, arguments); + var vml = null, self = this; + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function(d, style, anchor) { + var p = { }; + + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (vml == null) { + p["class"] = jsPlumb.endpointClass; + vml = self.getVml([0,0, d[2], d[3]], p, anchor); + self.canvas.appendChild(vml); + self.attachListeners(vml, self); + } + else { + //p["coordsize"] = "1,1";//(d[2] * scale) + "," + (d[3] * scale); again, unsure. + _pos(vml, [0,0, d[2], d[3]]); + _atts(vml, p); + } + + _applyStyles(vml, style); + }; + }; + + jsPlumb.Connectors.vml.Bezier = function() { + jsPlumb.Connectors.Bezier.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Straight = function() { + jsPlumb.Connectors.Straight.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Flowchart = function() { + jsPlumb.Connectors.Flowchart.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l"; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]); + } + // finally draw a line to the end + p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e"; + return p; + }; + }; + + jsPlumb.Endpoints.vml.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("oval", d, atts); }; + }; + + jsPlumb.Endpoints.vml.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("rect", d, atts); }; + }; + + /* + * VML Image Endpoint is the same as the default image endpoint. + */ + jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image; + + /** + * placeholder for Blank endpoint in vml renderer. + */ + jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank; + + /** + * VML Label renderer. uses the default label renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label; + + var AbstractVmlArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + VmlComponent.apply(this, arguments); + var self = this, canvas = null, path =null; + var getPath = function(d, connectorDimensions) { + return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) + + " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + + " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + + " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + + " x e"; + }; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) { + var p = {}; + if (strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] =_convertStyle(strokeStyle, true); + } + if (lineWidth) p["strokeweight"] = lineWidth + "px"; + if (fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = fillStyle; + } + var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + w = Math.abs(xmax - xmin), + h = Math.abs(ymax - ymin), + dim = [xmin, ymin, w, h]; + + // for VML, we create overlays using shapes that have the same dimensions and + // coordsize as their connector - overlays calculate themselves relative to the + // connector (it's how it's been done since the original canvas implementation, because + // for canvas that makes sense). + p["path"] = getPath(d, connectorDimensions); + p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale); + + dim[0] = connectorDimensions[0]; + dim[1] = connectorDimensions[1]; + dim[2] = connectorDimensions[2]; + dim[3] = connectorDimensions[3]; + + if (canvas == null) { + //p["class"] = jsPlumb.overlayClass; // TODO currentInstance? + canvas = _node("shape", dim, p); + connector.appendDisplayElement(canvas); + self.attachListeners(canvas, connector); + } + else { + _pos(canvas, dim); + _atts(canvas, p); + } + }; + }; + + jsPlumb.Overlays.vml.Arrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.vml.PlainArrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.vml.Diamond = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the SVG renderers. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +/** + * SVG support for jsPlumb. + * + * things to investigate: + * + * gradients: https://developer.mozilla.org/en/svg_in_html_introduction + * css:http://tutorials.jenkov.com/svg/svg-and-css.html + * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath + * pointer events: https://developer.mozilla.org/en/css/pointer-events + * + */ +;(function() { + + var svgAttributeMap = { + "stroke-linejoin":"stroke-linejoin", + "joinstyle":"stroke-linejoin", + "stroke-dashoffset":"stroke-dashoffset" + }; + + var ns = { + svg:"http://www.w3.org/2000/svg", + xhtml:"http://www.w3.org/1999/xhtml" + }, + _attr = function(node, attributes) { + for (var i in attributes) + node.setAttribute(i, "" + attributes[i]); + }, + _node = function(name, attributes) { + var n = document.createElementNS(ns.svg, name); + attributes = attributes || {}; + attributes["version"] = "1.1"; + attributes["xmnls"] = ns.xhtml; + _attr(n, attributes); + return n; + }, + _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; }, + _convertStyle = function(s, ignoreAlpha) { + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + + return o; + }, + _clearGradient = function(parent) { + for (var i = 0; i < parent.childNodes.length; i++) { + if (parent.childNodes[i].tagName == "linearGradient" || parent.childNodes[i].tagName == "radialGradient") + parent.removeChild(parent.childNodes[i]); + } + }, + _updateGradient = function(parent, node, style, dimensions) { + var id = "jsplumb_gradient_" + (new Date()).getTime(); + // first clear out any existing gradient + _clearGradient(parent); + // this checks for an 'offset' property in the gradient, and in the absence of it, assumes + // we want a linear gradient. if it's there, we create a radial gradient. + // it is possible that a more explicit means of defining the gradient type would be + // better. relying on 'offset' means that we can never have a radial gradient that uses + // some default offset, for instance. + if (!style.gradient.offset) { + var g = _node("linearGradient", {id:id}); + parent.appendChild(g); + } + else { + var g = _node("radialGradient", { + id:id + }); + parent.appendChild(g); + } + + // the svg radial gradient seems to treat stops in the reverse + // order to how canvas does it. so we want to keep all the maths the same, but + // iterate the actual style declarations in reverse order, if the x indexes are not in order. + for (var i = 0; i < style.gradient.stops.length; i++) { + // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of + // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments. + // so, not too concerned about leaving this in for now. + var styleToUse = i; + if (dimensions.length == 8) + styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i; + else + styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i; + var stopColor = _convertStyle(style.gradient.stops[styleToUse][1], true); + var s = _node("stop", {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor}); + g.appendChild(s); + } + var applyGradientTo = style.strokeStyle ? "stroke" : "fill"; + node.setAttribute("style", applyGradientTo + ":url(#" + id + ")"); + }, + _applyStyles = function(parent, node, style, dimensions) { + + if (style.gradient) { + _updateGradient(parent, node, style, dimensions); + } + else { + // make sure we clear any existing gradient + _clearGradient(parent); + node.setAttribute("style", ""); + } + + node.setAttribute("fill", style.fillStyle ? _convertStyle(style.fillStyle, true) : "none"); + node.setAttribute("stroke", style.strokeStyle ? _convertStyle(style.strokeStyle, true) : "none"); + if (style.lineWidth) { + node.setAttribute("stroke-width", style.lineWidth); + } + + // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like + // the syntax in VML but is actually kind of nasty: values are given in the pixel + // coordinate space, whereas in VML they are multiples of the width of the stroked + // line, which makes a lot more sense. for that reason, jsPlumb is supporting both + // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from + // VML, which will be the preferred method. the code below this converts a dashstyle + // attribute given in terms of stroke width into a pixel representation, by using the + // stroke's lineWidth. + if(style["stroke-dasharray"]) { + node.setAttribute("stroke-dasharray", style["stroke-dasharray"]); + } + if (style["dashstyle"] && style["lineWidth"]) { + var sep = style["dashstyle"].indexOf(",") == -1 ? " " : ",", + parts = style["dashstyle"].split(sep), + styleToUse = ""; + parts.forEach(function(p) { + styleToUse += (Math.floor(p * style.lineWidth) + sep); + }); + node.setAttribute("stroke-dasharray", styleToUse); + } + + // extra attributes such as join type, dash offset. + for (var i in svgAttributeMap) { + if (style[i]) { + node.setAttribute(svgAttributeMap[i], style[i]); + } + } + }, + _decodeFont = function(f) { + var r = /([0-9].)(p[xt])\s(.*)/; + var bits = f.match(r); + return {size:bits[1] + bits[2], font:bits[3]}; + }; + + /* + * Base class for SVG components. + */ + var SvgComponent = function(cssClass, originalArgs, pointerEventsSpec) { + var self = this; + pointerEventsSpec = pointerEventsSpec || "all"; + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + self.canvas = null, self.path = null, self.svg = null; + + this.setHover = function() { }; + + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.sizeCanvas(self.canvas,0,0,1,1); + + var clazz = cssClass + " " + (originalArgs[0].cssClass || ""); + self.canvas.className = clazz; + + self.svg = _node("svg", { + "style":"", + "width":0, + "height":0, + "pointer-events":pointerEventsSpec/*, + "class": clazz*/ + }); + + jsPlumb.appendElement(self.canvas, originalArgs[0]["parent"]); + self.canvas.appendChild(self.svg); + + // TODO this displayElement stuff is common between all components, across all + // renderers. would be best moved to jsPlumbUIComponent. + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + this.paint = function(d, style, anchor) { + if (style != null) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + _attr(self.svg, { + "style":_pos([0,0,d[2], d[3]]), + "width": d[2], + "height": d[3] + }); + self._paint.apply(this, arguments); + } + }; + }; + + /* + * Base class for SVG connectors. + */ + var SvgConnector = function(params) { + var self = this; + SvgComponent.apply(this, [ params["_jsPlumb"].connectorClass, arguments, "none" ]); + this._paint = function(d, style) { + var p = self.getPath(d), a = { "d":p }, outlineStyle = null; + a["pointer-events"] = "all"; + + // outline style. actually means drawing an svg object underneath the main one. + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + outlineStyle = { + strokeStyle:_convertStyle(style.outlineColor), + lineWidth:outlineStrokeWidth + }; + + if (self.bgPath == null) { + self.bgPath = _node("path", a); + self.svg.appendChild(self.bgPath); + self.attachListeners(self.bgPath, self); + } + else { + _attr(self.bgPath, a); + } + + _applyStyles(self.svg, self.bgPath, outlineStyle, d); + } + + + if (self.path == null) { + self.path = _node("path", a); + self.svg.appendChild(self.path); + self.attachListeners(self.path, self); + } + else { + _attr(self.path, a); + } + + _applyStyles(self.svg, self.path, style, d); + }; + }; + + /* + * SVG Bezier Connector + */ + jsPlumb.Connectors.svg.Bezier = function(params) { + jsPlumb.Connectors.Bezier.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]; }; + }; + + /* + * SVG straight line Connector + */ + jsPlumb.Connectors.svg.Straight = function(params) { + jsPlumb.Connectors.Straight.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; }; + }; + + jsPlumb.Connectors.svg.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "M " + dimensions[4] + "," + dimensions[5]; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)]; + } + // finally draw a line to the end + p = p + " " + dimensions[6] + "," + dimensions[7]; + return p; + }; + }; + + /* + * Base class for SVG endpoints. + */ + var SvgEndpoint = function(params) { + var self = this; + SvgComponent.apply(this, [ params["_jsPlumb"].endpointClass, arguments, "all" ]); + this._paint = function(d, style) { + var s = jsPlumb.extend({}, style); + if (s.outlineColor) { + s.strokeWidth = s.outlineWidth; + s.strokeStyle = _convertStyle(s.outlineColor, true); + } + + if (self.node == null) { + self.node = self.makeNode(d, s); + self.svg.appendChild(self.node); + self.attachListeners(self.node, self); + } + _applyStyles(self.svg, self.node, s, d); + _pos(self.node, d); + }; + }; + + /* + * SVG Dot Endpoint + */ + jsPlumb.Endpoints.svg.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("circle", { + "cx" : d[2] / 2, + "cy" : d[3] / 2, + "r" : d[2] / 2 + }); + }; + }; + + /* + * SVG Rectangle Endpoint + */ + jsPlumb.Endpoints.svg.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("rect", { + "width":d[2], + "height":d[3] + }); + }; + }; + + /* + * SVG Image Endpoint is the default image endpoint. + */ + jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image; + /* + * Blank endpoint in svg renderer is the default Blank endpoint. + */ + jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank; + /* + * Label endpoint in svg renderer is the default Label endpoint. + */ + jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label; + + + var AbstractSvgArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + var self = this, path =null; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + } + + _attr(path, { + "d" : makePath(d), + stroke : strokeStyle ? strokeStyle : null, + fill : fillStyle ? fillStyle : null + }); + }; + var makePath = function(d) { + return "M" + d.hxy.x + "," + d.hxy.y + + " L" + d.tail[0].x + "," + d.tail[0].y + + " L" + d.cxy.x + "," + d.cxy.y + + " L" + d.tail[1].x + "," + d.tail[1].y + + " L" + d.hxy.x + "," + d.hxy.y; + }; + }; + + jsPlumb.Overlays.svg.Arrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.svg.PlainArrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.svg.Diamond = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the HTML5 canvas renderers. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +;(function() { + +// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS ******************************************************************* + + // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. + var _connectionBeingDragged = null, + _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }, + _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }, + _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }, + _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, + _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }, + _setOffset = function(el, o) { jsPlumb.CurrentLibrary.setOffset(el, o); }; + + /* + * Class:CanvasMouseAdapter + * Provides support for mouse events on canvases. + */ + var CanvasMouseAdapter = function() { + var self = this; + self.overlayPlacements = []; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + jsPlumb.EventGenerator.apply(this, arguments); + /** + * returns whether or not the given event is ojver a painted area of the canvas. + */ + this._over = function(e) { + var o = _getOffset(_getElementObject(self.canvas)), + pageXY = _pageXY(e), + x = pageXY[0] - o.left, y = pageXY[1] - o.top; + if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + return false; + }; + + var _mouseover = false; + var _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false; + var _nullSafeHasClass = function(el, clazz) { + return el != null && _hasClass(el, clazz); + }; + this.mousemove = function(e) { + var pageXY = _pageXY(e), clientXY = _clientXY(e), + ee = document.elementFromPoint(clientXY[0], clientXY[1]), + eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); + var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); + if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.fire("mouseenter", self, e); + return true; + } + // TODO here there is a remote chance that the overlay the mouse moved onto + // is actually not an overlay for the current component. a more thorough check would + // be to ensure the overlay belonged to the current component. + else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { + _mouseover = false; + self.fire("mouseexit", self, e); + } + self.fire("mousemove", self, e); + }; + + this.click = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); + self.fire("mousedown", self, e); + } + }; + + this.mouseup = function(e) { + //if (self == _connectionBeingDragged) _connectionBeingDragged = null; + _mouseDown = false; + self.fire("mouseup", self, e); + }; + }; + + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + jsPlumb.appendElement(canvas, params.parent); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + params["_jsPlumb"].getId(canvas, params.uuid); + + return canvas; + }; + + /** + * Class:CanvasConnector + * Superclass for Canvas Connector renderers. + */ + var CanvasConnector = jsPlumb.CanvasConnector = function(params) { + + CanvasMouseAdapter.apply(this, arguments); + + var _paintOneStyle = function(dim, aStyle) { + self.ctx.save(); + jsPlumb.extend(self.ctx, aStyle); + if (aStyle.gradient) { + var g = self.createGradient(dim, self.ctx); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + self.ctx.strokeStyle = g; + } + self._paint(dim); + self.ctx.restore(); + }; + + var self = this, + clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent + }); + self.ctx = self.canvas.getContext("2d"); + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + self.paint = function(dim, style) { + if (style != null) { + + jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); + + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + _paintOneStyle(dim, outlineStyle); + } + _paintOneStyle(dim, style); + } + }; + }; + + /** + * Class:CanvasEndpoint + * Superclass for Canvas Endpoint renderers. + */ + var CanvasEndpoint = function(params) { + var self = this; + CanvasMouseAdapter.apply(this, arguments); + var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent + }); + self.ctx = self.canvas.getContext("2d"); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + // _paintOneStyle(d, outlineStyle); + } + + self._paint.apply(this, arguments); + }; + }; + + jsPlumb.Endpoints.canvas.Dot = function(params) { + var self = this; + jsPlumb.Endpoints.Dot.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }; + var calculateAdjustments = function(gradient) { + var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius; + gradient.offset && (offsetAdjustment = parseValue(gradient.offset)); + gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius)); + return [offsetAdjustment, innerRadius]; + }; + this._paint = function(d, style, anchor) { + if (style != null) { + var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(); + jsPlumb.extend(ctx, style); + if (style.gradient) { + var adjustments = calculateAdjustments(style.gradient), + yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0], + xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0], + g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + ctx.beginPath(); + ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + } + }; + }; + + jsPlumb.Endpoints.canvas.Rectangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) { + + var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(); + jsPlumb.extend(ctx, style); + + /* canvas gradient */ + if (style.gradient) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, d[2], d[3]); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + jsPlumb.Endpoints.canvas.Triangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Triangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) + { + var width = d[2], height = d[3], x = d[0], y = d[1]; + + var ctx = self.canvas.getContext('2d'); + var offsetX = 0, offsetY = 0, angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = style.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + /* + * Canvas Image Endpoint: uses the default version, which creates an tag. + */ + jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image; + + /* + * Blank endpoint in all renderers is just the default Blank endpoint. + */ + jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank; + + /* + * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Bezier = function() { + var self = this; + jsPlumb.Connectors.Bezier.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO i doubt this handles the case that source and target are swapped. + this.createGradient = function(dim, ctx, swap) { + return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + /* + * Canvas straight line Connector. Draws a straight line onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Straight = function() { + var self = this; + jsPlumb.Connectors.Straight.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO this does not handle the case that src and target are swapped. + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + jsPlumb.Connectors.canvas.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + +// ********************************* END OF CANVAS RENDERERS ******************************************************************* + + jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label; + + /** + * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. + */ + var CanvasOverlay = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }; + + var AbstractCanvasArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + CanvasOverlay.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + var ctx = connector.ctx; + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(d.hxy.x, d.hxy.y); + ctx.lineTo(d.tail[0].x, d.tail[0].y); + ctx.lineTo(d.cxy.x, d.cxy.y); + ctx.lineTo(d.tail[1].x, d.tail[1].y); + ctx.lineTo(d.hxy.x, d.hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + }; + + jsPlumb.Overlays.canvas.Arrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.canvas.PlainArrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.canvas.Diamond = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the MooTools adapter. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +;(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function(now) { + this.parent(now); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }; + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + var _getElementObject = function(el) { + return $(el); + }; + + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + m.start(properties); + }, + + appendElement : function(child, parent) { + _getElementObject(parent).grab(child); + }, + + bind : function(el, event, callback) { + el = _getElementObject(el); + el.addEvent(event, callback); + }, + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.event.clientX, eventObject.event.clientY]; + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el); + var drags = _draggablesById[id]; + return drags[0].scope; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getElementObject : _getElementObject, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + getPageXY : function(eventObject) { + return [eventObject.event.pageX, eventObject.event.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).getParent(); + }, + + getScrollLeft : function(el) { + return null; + }, + + getScrollTop : function(el) { + return null; + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[0], + p = jsPlumb.CurrentLibrary.getElementObject(ui).getPosition(); + return { left:p.x, top:p.y }; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options) { + var id = jsPlumb.getId(el); + var drag = _draggablesById[id]; + if (!drag) { + var originalZIndex = 0, originalCursor = null; + var dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + options['onStart'] = jsPlumb.wrap(options['onStart'], function() + { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() + { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + }); + + // DROPPABLES: + var scope = options['scope'] || jsPlumb.Defaults.Scope; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop'); + } + }; + + drag = new Drag.Move(el, options); + drag.scope = scope; + //console.log("drag scope initialized to ", scope); + _add(_draggablesByScope, scope, drag); + _add(_draggablesById, el.get("id"), drag); + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + initDroppable : function(el, options) { + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _add(_droppables, scope, el); + var id = jsPlumb.getId(el); + _droppableOptions[id] = options; + _droppableScopesById[id] = scope; + var filterFunc = function(entry) { return entry.element != el; }; + var draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + isAlreadyDraggable : function(el) { + return _draggablesById[jsPlumb.getId(el)] != null; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + return (typeof Drag != undefined && typeof Drag.Move != undefined); + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + _getElementObject(element).dispose(); // ?? + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + setDragScope : function(el, scope) { + var drag = _draggablesById[el.get("id")]; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + drag[0].droppables = droppables; + }, + + setOffset : function(el, o) { + _getElementObject(el).setPosition({x:o.left, y:o.top}); + } + }; + + window.addEvent('domready', jsPlumb.init); +})(); +(function(){if(typeof Math.sgn=="undefined")Math.sgn=function(a){return a==0?0:a>0?1:-1};var p={subtract:function(a,b){return{x:a.x-b.x,y:a.y-b.y}},dotProduct:function(a,b){return a.x*b.x+a.y*b.y},square:function(a){return Math.sqrt(a.x*a.x+a.y*a.y)},scale:function(a,b){return{x:a.x*b,y:a.y*b}}},y=Math.pow(2,-65),u=function(a,b){for(var g=[],d=b.length-1,h=2*d-1,f=[],c=[],l=[],k=[],i=[[1,0.6,0.3,0.1],[0.4,0.6,0.6,0.4],[0.1,0.3,0.6,1]],e=0;e<=d;e++)f[e]=p.subtract(b[e],a);for(e=0;e<=d-1;e++){c[e]= +p.subtract(b[e+1],b[e]);c[e]=p.scale(c[e],3)}for(e=0;e<=d-1;e++)for(var m=0;m<=d;m++){l[e]||(l[e]=[]);l[e][m]=p.dotProduct(c[e],f[m])}for(e=0;e<=h;e++){k[e]||(k[e]=[]);k[e].y=0;k[e].x=parseFloat(e)/h}h=d-1;for(f=0;f<=d+h;f++){c=Math.min(f,d);for(e=Math.max(0,f-h);e<=c;e++){j=f-e;k[e+j].y+=l[j][e]*i[j][e]}}d=b.length-1;k=s(k,2*d-1,g,0);h=p.subtract(a,b[0]);l=p.square(h);for(e=i=0;e=64){g[0]=(a[0].x+a[b].x)/2;return 1}var n,o,q;k=a[0].y-a[b].y;i=a[b].x-a[0].x;e=a[0].x*a[b].y-a[b].x*a[0].y;m=max_distance_below=0;for(o=1;om)m=q;else if(q0?1:-1,c=null;h=0){delete (ax[ay]);ax.splice(ay,1);return true}}}return false},v=function(ay,ax){return I(ay,function(az,aA){ag[aA]=ax;if(i.CurrentLibrary.isDragSupported(az)){i.CurrentLibrary.setDraggable(az,ax)}})},ao=function(az,aA,ax){aA=aA==="block";var ay=null;if(ax){if(aA){ay=function(aC){aC.setVisible(true,true,true)}}else{ay=function(aC){aC.setVisible(false,true,true)}}}var aB=c(az,"id");U(aB,function(aD){if(aA&&ax){var aC=aD.sourceId===aB?1:0;if(aD.endpoints[aC].isVisible()){aD.setVisible(true)}}else{aD.setVisible(aA)}},ay)},N=function(ax){return I(ax,function(az,ay){var aA=ag[ay]==null?Y:ag[ay];aA=!aA;ag[ay]=aA;i.CurrentLibrary.setDraggable(az,aA);return aA})},x=function(ax,az){var ay=null;if(az){ay=function(aA){var aB=aA.isVisible();aA.setVisible(!aB)}}U(ax,function(aB){var aA=aB.isVisible();aB.setVisible(!aA)},ay)},E=function(aC){var aA=aC.timestamp,ax=aC.recalc,aB=aC.offset,ay=aC.elId;if(!ax){if(aA&&aA===C[ay]){return V[ay]}}if(ax||aB==null){var az=u(ay);if(az!=null){P[ay]=b(az);V[ay]=n(az);C[ay]=aA}}else{V[ay]=aB}return V[ay]},aw=function(ax,ay){var az=u(ax);var aA=c(az,"id");if(!aA||aA=="undefined"){if(arguments.length==2&&arguments[1]!=undefined){aA=ay}else{aA="jsPlumb_"+ar()}e(az,"id",aA)}return aA},am=function(az,ax,ay){az=az||function(){};ax=ax||function(){};return function(){var aA=null;try{aA=ax.apply(this,arguments)}catch(aB){k(D,"jsPlumb function failed : "+aB)}if(ay==null||(aA!==ay)){try{az.apply(this,arguments)}catch(aB){k(D,"wrapped function failed : "+aB)}}return aA}};this.connectorClass="_jsPlumb_connector";this.endpointClass="_jsPlumb_endpoint";this.overlayClass="_jsPlumb_overlay";this.Anchors={};this.Connectors={canvas:{},svg:{},vml:{}};this.Endpoints={canvas:{},svg:{},vml:{}};this.Overlays={canvas:{},svg:{},vml:{}};this.addEndpoint=function(az,aA,aJ){aJ=aJ||{};var ay=i.extend({},aJ);i.extend(ay,aA);ay.endpoint=ay.endpoint||D.Defaults.Endpoint||i.Defaults.Endpoint;ay.paintStyle=ay.paintStyle||D.Defaults.EndpointStyle||i.Defaults.EndpointStyle;az=w(az);var aB=[],aE=az.length&&az.constructor!=String?az:[az];for(var aC=0;aC0?l(aK,aJ)!=-1:true},aB=aE.length>1?{}:[],aH=function(aK,aL){if(aE.length>1){var aJ=aB[aK];if(aJ==null){aJ=[];aB[aK]=aJ}aJ.push(aL)}else{aB.push(aL)}};for(var aA in L){if(ay(aE,aA)){for(var az=0;az=4){ay.orientation=[arguments[2],arguments[3]]}if(arguments.length==6){ay.offsets=[arguments[4],arguments[5]]}}var aD=new ac(ay);aD.clone=function(){return new ac(ay)};return aD};this.makeAnchors=function(ay){var az=[];for(var ax=0;ax0?aG[0]:null,aC=aG.length>0?0:-1,aF=this,aB=function(aJ,aH,aN,aM,aI){var aL=aM[0]+(aJ.x*aI[0]),aK=aM[1]+(aJ.y*aI[1]);return Math.sqrt(Math.pow(aH-aL,2)+Math.pow(aN-aK,2))},ax=ay||function(aR,aI,aJ,aK,aH){var aM=aJ[0]+(aK[0]/2),aL=aJ[1]+(aK[1]/2);var aO=-1,aQ=Infinity;for(var aN=0;aN=0?aG.overlays[aS]:null};this.hideOverlay=function(aT){var aS=aG.getOverlay(aT);if(aS){aS.hide()}};this.showOverlay=function(aT){var aS=aG.getOverlay(aT);if(aS){aS.show()}};this.removeAllOverlays=function(){aG.overlays.splice(0,aG.overlays.length);aG.repaint()};this.removeOverlay=function(aT){var aS=aQ(aT);if(aS!=-1){aG.overlays.splice(aS,1)}};this.removeOverlays=function(){for(var aS=0;aSa6){a6=a8}}}var aZ=this.connector.compute(a1,aY,this.endpoints[a7].anchor,this.endpoints[a3].anchor,aG.paintStyleInUse.lineWidth,a6);aG.connector.paint(aZ,aG.paintStyleInUse);for(var aX=0;aX=0){aN.connections.splice(aZ,1);if(!a2){var a1=a0.endpoints[0]==aN?a0.endpoints[1]:a0.endpoints[0];a1.detach(a0,true);if(a0.endpointToDeleteOnDetach&&a0.endpointToDeleteOnDetach.connections.length==0){i.deleteEndpoint(a0.endpointToDeleteOnDetach)}}Q(a0.connector.getDisplayElements(),a0.parent);M(L,a0.scope,a0);if(!a2){ad(a0)}}};this.detachAll=function(){while(aN.connections.length>0){aN.detach(aN.connections[0])}};this.detachFrom=function(a0){var a1=[];for(var aZ=0;aZ=0){aN.connections.splice(aZ,1)}};this.getElement=function(){return aM};this.getUuid=function(){return aH};this.makeInPlaceCopy=function(){return an({anchor:aN.anchor,source:aM,paintStyle:this.paintStyle,endpoint:aL})};this.isConnectedTo=function(a1){var a0=false;if(a1){for(var aZ=0;aZ0){var a9=aK(a2.elementWithPrecedence);var bb=a9.endpoints[0]==aN?1:0;var a4=bb==0?a9.sourceId:a9.targetId;var a8=V[a4],ba=P[a4];a0.txy=[a8.left,a8.top];a0.twh=ba;a0.tElement=a9.endpoints[bb]}}a5=aN.anchor.compute(a0)}var a7=aL.compute(a5,aN.anchor.getOrientation(),aN.paintStyleInUse,a3||aN.paintStyleInUse);aL.paint(a7,aN.paintStyleInUse,aN.anchor);aN.timestamp=a6}};this.repaint=this.paint;this.removeConnection=this.detach;if(aY.isSource&&i.CurrentLibrary.isDragSupported(aM)){var aS=null,aO=null,aR=null,ax=false,aA=null;var aC=function(){aR=aN.connectorSelector();if(aN.isFull()&&!aI){return false}E({elId:aE});aB=aN.makeInPlaceCopy();aB.paint();aS=document.createElement("div");aS.style.position="absolute";var a6=u(aS);R(aS,aN.parent);var a0=aw(a6);var a7=u(aB.canvas),a5=i.CurrentLibrary.getOffset(a7),a2=aB.canvas.offsetParent!=null?aB.canvas.offsetParent.tagName.toLowerCase()==="body"?{left:0,top:0}:n(aB.canvas.offsetParent):{left:0,top:0};i.CurrentLibrary.setOffset(aS,{left:a5.left-a2.left,top:a5.top-a2.top});E({elId:a0});e(u(aN.canvas),"dragId",a0);e(u(aN.canvas),"elId",aE);var a8=new H({reference:aN.anchor,referenceCanvas:aN.canvas});aW=an({paintStyle:aN.paintStyle,endpoint:aL,anchor:a8,source:a6});if(aR==null){aN.anchor.locked=true;aR=au({sourceEndpoint:aN,targetEndpoint:aW,source:u(aM),target:u(aS),anchors:[aN.anchor,a8],paintStyle:aY.connectorStyle,hoverPaintStyle:aY.connectorHoverStyle,connector:aY.connector,overlays:aY.connectorOverlays});aR.connector.setHover(false)}else{ax=true;aR.connector.setHover(false);aD(u(aB.canvas));var a1=aR.sourceId==aE?0:1;aR.floatingAnchorIndex=a1;aN.detachFromConnection(aR);var a4=u(aN.canvas);var a3=i.CurrentLibrary.getDragScope(a4);e(a4,"originalScope",a3);var aZ=i.CurrentLibrary.getDropScope(a4);i.CurrentLibrary.setDragScope(a4,aZ);if(a1==0){aA=[aR.source,aR.sourceId,aV,a3];aR.source=u(aS);aR.sourceId=a0}else{aA=[aR.target,aR.targetId,aV,a3];aR.target=u(aS);aR.targetId=a0}aR.endpoints[a1==0?1:0].anchor.locked=true;aR.suspendedEndpoint=aR.endpoints[a1];aR.endpoints[a1]=aW}Z[a0]=aR;aW.addConnection(aR);S(ah,a0,aW);D.currentlyDragging=true};var ay=i.CurrentLibrary,aU=aY.dragOptions||{},aP=i.extend({},ay.defaultDragOptions),aQ=ay.dragEvents.start,aX=ay.dragEvents.stop,aG=ay.dragEvents.drag;aU=i.extend(aP,aU);aU.scope=aU.scope||aN.scope;aU[aQ]=am(aU[aQ],aC);aU[aG]=am(aU[aG],function(){var aZ=i.CurrentLibrary.getUIPosition(arguments);i.CurrentLibrary.setOffset(aS,aZ);at(u(aS),aZ)});aU[aX]=am(aU[aX],function(){M(ah,aO,aW);Q([aS,aW.canvas],aM);ae(aB.canvas,aM);var aZ=aR.floatingAnchorIndex==null?1:aR.floatingAnchorIndex;aR.endpoints[aZ==0?1:0].anchor.locked=false;if(aR.endpoints[aZ]==aW){if(ax&&aR.suspendedEndpoint){if(aZ==0){aR.source=aA[0];aR.sourceId=aA[1]}else{aR.target=aA[0];aR.targetId=aA[1]}i.CurrentLibrary.setDragScope(aA[2],aA[3]);aR.endpoints[aZ]=aR.suspendedEndpoint;if(aJ){aR.floatingAnchorIndex=null;aR.suspendedEndpoint.addConnection(aR);i.repaint(aA[1])}else{aR.endpoints[aZ==0?1:0].detach(aR)}}else{Q(aR.connector.getDisplayElements(),aN.parent);aN.detachFromConnection(aR)}}aN.anchor.locked=false;aN.paint();aR.setHover(false);aR.repaint();aR=null;delete aB;delete ah[aW.elementId];aW=null;delete aW;D.currentlyDragging=false});var aV=u(aN.canvas);i.CurrentLibrary.initDraggable(aV,aU)}var aD=function(a2){if(aY.isTarget&&i.CurrentLibrary.isDropSupported(aM)){var aZ=aY.dropOptions||D.Defaults.DropOptions||i.Defaults.DropOptions;aZ=i.extend({},aZ);aZ.scope=aZ.scope||aN.scope;var a5=null;var a3=i.CurrentLibrary.dragEvents.drop;var a4=i.CurrentLibrary.dragEvents.over;var a0=i.CurrentLibrary.dragEvents.out;var a1=function(){var be=u(i.CurrentLibrary.getDragObject(arguments));var a6=c(be,"dragId");var a8=c(be,"elId");var bd=c(be,"originalScope");if(bd){i.CurrentLibrary.setDragScope(be,bd)}var ba=Z[a6];var bb=ba.floatingAnchorIndex==null?1:ba.floatingAnchorIndex,bc=bb==0?1:0;if(!aN.isFull()&&!(bb==0&&!aN.isSource)&&!(bb==1&&!aN.isTarget)){if(bb==0){ba.source=aM;ba.sourceId=aE}else{ba.target=aM;ba.targetId=aE}ba.endpoints[bb].detachFromConnection(ba);if(ba.suspendedEndpoint){ba.suspendedEndpoint.detachFromConnection(ba)}ba.endpoints[bb]=aN;aN.addConnection(ba);if(!ba.suspendedEndpoint){S(L,ba.scope,ba);O(aM,aY.draggable,{})}else{var a9=ba.suspendedEndpoint.getElement(),a7=ba.suspendedEndpoint.elementId;D.fire("jsPlumbConnectionDetached",{source:bb==0?a9:ba.source,target:bb==1?a9:ba.target,sourceId:bb==0?a7:ba.sourceId,targetId:bb==1?a7:ba.targetId,sourceEndpoint:bb==0?ba.suspendedEndpoint:ba.endpoints[0],targetEndpoint:bb==1?ba.suspendedEndpoint:ba.endpoints[1],connection:ba})}i.repaint(a8);D.fire("jsPlumbConnection",{source:ba.source,target:ba.target,sourceId:ba.sourceId,targetId:ba.targetId,sourceEndpoint:ba.endpoints[0],targetEndpoint:ba.endpoints[1],connection:ba})}D.currentlyDragging=false;delete Z[a6]};aZ[a3]=am(aZ[a3],a1);aZ[a4]=am(aZ[a4],function(){var a7=i.CurrentLibrary.getDragObject(arguments);var a9=c(u(a7),"dragId");var a8=Z[a9];if(a8!=null){var a6=a8.floatingAnchorIndex==null?1:a8.floatingAnchorIndex;a8.endpoints[a6].anchor.over(aN.anchor)}});aZ[a0]=am(aZ[a0],function(){var a7=i.CurrentLibrary.getDragObject(arguments),a9=c(u(a7),"dragId"),a8=Z[a9];if(a8!=null){var a6=a8.floatingAnchorIndex==null?1:a8.floatingAnchorIndex;a8.endpoints[a6].anchor.out()}});i.CurrentLibrary.initDroppable(a2,aZ)}};aD(u(aN.canvas));return aN}};var i=window.jsPlumb=new q();i.getInstance=function(w){var v=new q(w);v.init();return v};var m=function(v,A,z,w){return function(){return i.makeAnchor(v,A,z,w)}};i.Anchors.TopCenter=m(0.5,0,0,-1);i.Anchors.BottomCenter=m(0.5,1,0,1);i.Anchors.LeftMiddle=m(0,0.5,-1,0);i.Anchors.RightMiddle=m(1,0.5,1,0);i.Anchors.Center=m(0.5,0.5,0,0);i.Anchors.TopRight=m(1,0,0,-1);i.Anchors.BottomRight=m(1,1,0,1);i.Anchors.TopLeft=m(0,0,0,-1);i.Anchors.BottomLeft=m(0,1,0,1);i.Defaults.DynamicAnchors=function(){return i.makeAnchors(["TopCenter","RightMiddle","BottomCenter","LeftMiddle"])};i.Anchors.AutoDefault=function(){return i.makeDynamicAnchor(i.Defaults.DynamicAnchors())}})();(function(){jsPlumb.DOMElementComponent=function(b){jsPlumb.jsPlumbUIComponent.apply(this,arguments);this.mousemove=this.dblclick=this.click=this.mousedown=this.mouseup=function(c){}};jsPlumb.Connectors.Straight=function(){this.type="Straight";var o=this;var h=null;var d,i,m,l,k,e,n,g,f,c,b;this.compute=function(s,G,C,p,z,r){var F=Math.abs(s[0]-G[0]);var v=Math.abs(s[1]-G[1]);var A=false,t=false;var u=0.45*F,q=0.45*v;F*=1.9;v*=1.9;var D=Math.min(s[0],G[0])-u;var B=Math.min(s[1],G[1])-q;var E=Math.max(2*z,r);if(F0?1:-1;var u=Math.abs(v*Math.sin(e));if(f>b){u=u*-1}var q=Math.abs(v*Math.cos(e));if(g>c){q=q*-1}return{x:t.x+(s*q),y:t.y+(s*u)}};this.perpendicularToPathAt=function(t,u,z){var v=o.pointAlongPathFrom(t,z);var s=o.gradientAtPoint(v.location);var r=Math.atan(-1/s);var w=u/2*Math.sin(r);var q=u/2*Math.cos(r);return[{x:v.x+q,y:v.y+w},{x:v.x-q,y:v.y-w}]}};jsPlumb.Connectors.Bezier=function(f){var p=this;f=f||{};this.majorAnchor=f.curviness||150;this.minorAnchor=10;var i=null;this.type="Bezier";this._findControlPoint=function(z,q,u,x,r){var w=x.getOrientation(),y=r.getOrientation();var t=w[0]!=y[0]||w[1]==y[1];var s=[];var A=p.majorAnchor,v=p.minorAnchor;if(!t){if(w[0]==0){s.push(q[0]m){m=v}if(y<0){e+=y;var z=Math.abs(y);m+=z;o[0]+=z;k+=z;c+=z;n[0]+=z}var H=Math.min(g,b);var F=Math.min(o[1],n[1]);var u=Math.min(H,F);var A=Math.max(g,b);var x=Math.max(o[1],n[1]);var s=Math.max(A,x);if(s>h){h=s}if(u<0){d+=u;var w=Math.abs(u);h+=w;o[1]+=w;g+=w;b+=w;n[1]+=w}if(E&&m=t){r=u;s=(t-m[u][0])/b[u];break}}return{segment:k[r],proportion:s,index:r}};this.compute=function(R,z,Q,s,r,I){k=[];i=[];b=[];h=[];segmentProportionals=[];e=z[0]u[0]?u[0]+((1-y)*t)-z:u[2]+(y*t)+z,y:r==0?u[3]:u[3]>u[1]?u[1]+((1-y)*t)-z:u[3]+(y*t)+z,segmentInfo:w};return x};this.perpendicularToPathAt=function(u,v,A){var w=o.pointAlongPathFrom(u,A);var t=i[w.segmentInfo.index];var s=Math.atan(-1/t);var z=v/2*Math.sin(s);var r=v/2*Math.cos(s);return[{x:w.x+r,y:w.y+z},{x:w.x-r,y:w.y-z}]}};jsPlumb.Endpoints.Dot=function(c){this.type="Dot";var b=this;c=c||{};this.radius=c.radius||10;this.defaultOffset=0.5*this.radius;this.defaultInnerRadius=this.radius/3;this.compute=function(h,e,k,g){var f=k.radius||b.radius;var d=h[0]-f;var i=h[1]-f;return[d,i,f*2,f*2,f]}};jsPlumb.Endpoints.Rectangle=function(c){this.type="Rectangle";var b=this;c=c||{};this.width=c.width||20;this.height=c.height||20;this.compute=function(i,f,l,h){var g=l.width||b.width;var e=l.height||b.height;var d=i[0]-(g/2);var k=i[1]-(e/2);return[d,k,g,e]}};jsPlumb.Endpoints.Image=function(e){this.type="Image";jsPlumb.DOMElementComponent.apply(this,arguments);var b=this,d=false;this.img=new Image();b.ready=false;this.img.onload=function(){b.ready=true};this.img.src=e.src||e.url;this.compute=function(h,f,i,g){b.anchorPoint=h;if(b.ready){return[h[0]-b.img.width/2,h[1]-b.img.height/2,b.img.width,b.img.height]}else{return[0,0,0,0]}};b.canvas=document.createElement("img"),d=false;b.canvas.style.margin=0;b.canvas.style.padding=0;b.canvas.style.outline=0;b.canvas.style.position="absolute";b.canvas.className=jsPlumb.endpointClass;jsPlumb.appendElement(b.canvas,e.parent);b.attachListeners(b.canvas,b);var c=function(l,k,h){if(!d){b.canvas.setAttribute("src",b.img.src);d=true}var i=b.img.width,g=b.img.height,f=b.anchorPoint[0]-(i/2),m=b.anchorPoint[1]-(g/2);jsPlumb.sizeCanvas(b.canvas,f,m,i,g)};this.paint=function(h,g,f){if(b.ready){c(h,g,f)}else{window.setTimeout(function(){b.paint(h,g,f)},200)}}};jsPlumb.Endpoints.Blank=function(c){var b=this;this.type="Blank";jsPlumb.DOMElementComponent.apply(this,arguments);this.compute=function(){return[0,0,10,0]};b.canvas=document.createElement("div");b.canvas.style.display="block";b.canvas.style.width="1px";b.canvas.style.height="1px";b.canvas.style.background="transparent";b.canvas.style.position="absolute";jsPlumb.appendElement(b.canvas,c.parent);this.paint=function(){}};jsPlumb.Endpoints.Triangle=function(b){this.type="Triangle";b=b||{};b.width=b.width||55;param.height=b.height||55;this.width=b.width;this.height=b.height;this.compute=function(h,e,k,g){var f=k.width||self.width;var d=k.height||self.height;var c=h[0]-(f/2);var i=h[1]-(d/2);return[c,i,f,d]}};var a=function(){var c=true,b=this;this.setVisible=function(d){c=d;b.connection.repaint()};this.isVisible=function(){return c};this.hide=function(){b.setVisible(false)};this.show=function(){b.setVisible(true)}};jsPlumb.Overlays.Arrow=function(g){this.type="Arrow";a.apply(this);g=g||{};var c=this;this.length=g.length||20;this.width=g.width||20;this.id=g.id;this.connection=g.connection;var f=(g.direction||1)<0?-1:1;var d=g.paintStyle||{lineWidth:1};this.loc=g.location==null?0.5:g.location;var b=g.foldback||0.623;var e=function(h,k){if(b==0.5){return h.pointOnPath(k)}else{var i=0.5-b;return h.pointAlongPathFrom(k,f*c.length*i)}};this.computeMaxSize=function(){return c.width*1.5};this.draw=function(l,r,x){var z=l.pointAlongPathFrom(c.loc,f*(c.length/2));var u=l.pointAlongPathFrom(c.loc,-1*f*(c.length/2)),C=u.x,B=u.y;var s=l.perpendicularToPathAt(c.loc,c.width,-1*f*(c.length/2));var k=e(l,c.loc);if(c.loc==1){var i=l.pointOnPath(c.loc);var w=(i.x-z.x)*f,v=(i.y-z.y)*f;k.x+=w;k.y+=v;u.x+=w;u.y+=v;s[0].x+=w;s[0].y+=v;s[1].x+=w;s[1].y+=v;z.x+=w;z.y+=v}if(c.loc==0){var i=l.pointOnPath(c.loc);var t=b>1?k:{x:s[0].x+((s[1].x-s[0].x)/2),y:s[0].y+((s[1].y-s[0].y)/2)};var w=(i.x-t.x)*f,v=(i.y-t.y)*f;k.x+=w;k.y+=v;u.x+=w;u.y+=v;s[0].x+=w;s[0].y+=v;s[1].x+=w;s[1].y+=v;z.x+=w;z.y+=v}var p=Math.min(z.x,s[0].x,s[1].x);var o=Math.max(z.x,s[0].x,s[1].x);var n=Math.min(z.y,s[0].y,s[1].y);var m=Math.max(z.y,s[0].y,s[1].y);var A={hxy:z,tail:s,cxy:k},y=d.strokeStyle||r.strokeStyle,q=d.fillStyle||r.strokeStyle,h=d.lineWidth||r.lineWidth;c.paint(l,A,h,y,q,x);return[p,o,n,m]}};jsPlumb.Overlays.PlainArrow=function(c){c=c||{};var b=jsPlumb.extend(c,{foldback:1});jsPlumb.Overlays.Arrow.call(this,b);this.type="PlainArrow"};jsPlumb.Overlays.Diamond=function(d){d=d||{};var b=d.length||40;var c=jsPlumb.extend(d,{length:b/2,foldback:2});jsPlumb.Overlays.Arrow.call(this,c);this.type="Diamond"};jsPlumb.Overlays.Label=function(e){this.type="Label";jsPlumb.DOMElementComponent.apply(this,arguments);a.apply(this);this.labelStyle=e.labelStyle||jsPlumb.Defaults.LabelStyle;this.labelStyle.font=this.labelStyle.font||"12px sans-serif";this.label=e.label||"banana";this.connection=e.connection;this.id=e.id;var l=this;var i=null,f=null,d=null,c=null;this.location=e.location||0.5;this.cachedDimensions=null;var k=false,d=null,b=document.createElement("div");b.style.position="absolute";b.style.font=l.labelStyle.font;b.style.color=l.labelStyle.color||"black";if(l.labelStyle.fillStyle){b.style.background=l.labelStyle.fillStyle}if(l.labelStyle.borderWidth>0){var h=l.labelStyle.borderStyle?l.labelStyle.borderStyle:"black";b.style.border=l.labelStyle.borderWidth+"px solid "+h}if(l.labelStyle.padding){b.style.padding=l.labelStyle.padding}var g=e._jsPlumb.overlayClass+" "+(l.labelStyle.cssClass?l.labelStyle.cssClass:e.cssClass?e.cssClass:"");b.className=g;jsPlumb.appendElement(b,e.connection.parent);jsPlumb.getId(b);l.attachListeners(b,l);var m=l.setVisible;l.setVisible=function(n){m(n);b.style.display=n?"block":"none"};this.paint=function(n,p,o){if(!k){n.appendDisplayElement(b);l.attachListeners(b,n);k=true}b.style.left=(o[0]+p.minx)+"px";b.style.top=(o[1]+p.miny)+"px"};this.getTextDimensions=function(n){d=typeof l.label=="function"?l.label(l):l.label;b.innerHTML=d.replace(/\r\n/g,"
");var p=jsPlumb.CurrentLibrary.getElementObject(b),o=jsPlumb.CurrentLibrary.getSize(p);return{width:o[0],height:o[1]}};this.computeMaxSize=function(n){var o=l.getTextDimensions(n);return o.width?Math.max(o.width,o.height)*1.5:0};this.draw=function(p,r,q){var t=l.getTextDimensions(p);if(t.width!=null){var s=p.pointOnPath(l.location);var o=s.x-(t.width/2);var n=s.y-(t.height/2);l.paint(p,{minx:o,miny:n,td:t,cxy:s},q);return[o,o+t.width,n,n+t.height]}else{return[0,0,0,0]}}}})();(function(){var h={"stroke-linejoin":"joinstyle",joinstyle:"joinstyle",endcap:"endcap",miterlimit:"miterlimit"};if(document.createStyleSheet){document.createStyleSheet().addRule(".jsplumb_vml","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:textbox","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:oval","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:rect","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:stroke","behavior:url(#default#VML);position:absolute;");document.createStyleSheet().addRule("jsplumb\\:shape","behavior:url(#default#VML);position:absolute;");document.namespaces.add("jsplumb","urn:schemas-microsoft-com:vml")}var c=1000,d=function(p,q){for(var n in q){p[n]=q[n]}},m=function(n,q,r){r=r||{};var p=document.createElement("jsplumb:"+n);p.className=(r["class"]?r["class"]+" ":"")+"jsplumb_vml";l(p,q);d(p,r);return p},l=function(p,n){p.style.left=n[0]+"px";p.style.top=n[1]+"px";p.style.width=n[2]+"px";p.style.height=n[3]+"px";p.style.position="absolute"},g=function(n){return Math.floor(n*c)},a=function(p,n){var v=p,u=function(o){return o.length==1?"0"+o:o},q=function(o){return u(Number(o).toString(16))},r=/(rgb[a]?\()(.*)(\))/;if(p.match(r)){var t=p.match(r)[2].split(",");v="#"+q(t[0])+q(t[1])+q(t[2]);if(!n&&t.length==4){v=v+q(t[3])}}return v},f=function(s,r,p){var u={};if(r.strokeStyle){u.stroked="true";u.strokecolor=a(r.strokeStyle,true);u.strokeweight=r.lineWidth+"px"}else{u.stroked="false"}if(r.fillStyle){u.filled="true";u.fillcolor=a(r.fillStyle,true)}else{u.filled="false"}if(r.dashstyle){if(p.strokeNode==null){p.strokeNode=m("stroke",[0,0,0,0],{dashstyle:r.dashstyle});s.appendChild(p.strokeNode)}else{p.strokeNode.dashstyle=r.dashstyle}}else{if(r["stroke-dasharray"]&&r.lineWidth){var o=r["stroke-dasharray"].indexOf(",")==-1?" ":",",t=r["stroke-dasharray"].split(o),n="";for(var q=0;q0&&F>0&&z=z&&B[2]<=F&&B[3]>=F)){return true}}var D=v.canvas.getContext("2d").getImageData(parseInt(z),parseInt(F),1,1);return D.data[0]!=0||D.data[1]!=0||D.data[2]!=0||D.data[3]!=0}return false};var u=false;var t=false,y=null,x=false;var w=function(A,z){return A!=null&&f(A,z)};this.mousemove=function(C){var E=e(C),B=i(C),A=document.elementFromPoint(B[0],B[1]),D=w(A,"_jsPlumb_overlay");var z=o==null&&(w(A,"_jsPlumb_endpoint")||w(A,"_jsPlumb_connector"));if(!u&&z&&v._over(C)){u=true;v.fire("mouseenter",v,C);return true}else{if(u&&(!v._over(C)||!z)&&!D){u=false;v.fire("mouseexit",v,C)}}v.fire("mousemove",v,C)};this.click=function(z){if(u&&v._over(z)&&!x){v.fire("click",v,z)}x=false};this.dblclick=function(z){if(u&&v._over(z)&&!x){v.fire("dblclick",v,z)}x=false};this.mousedown=function(z){if(v._over(z)&&!t){t=true;y=m(s(v.canvas));v.fire("mousedown",v,z)}};this.mouseup=function(z){t=false;v.fire("mouseup",v,z)}};var p=function(u){var t=document.createElement("canvas");jsPlumb.appendElement(t,u.parent);t.style.position="absolute";if(u["class"]){t.className=u["class"]}u._jsPlumb.getId(t,u.uuid);return t};var d=jsPlumb.CanvasConnector=function(x){n.apply(this,arguments);var t=function(B,z){u.ctx.save();jsPlumb.extend(u.ctx,z);if(z.gradient){var A=u.createGradient(B,u.ctx);for(var y=0;y0?1:-1}}var b={subtract:function(m,l){return{x:m.x-l.x,y:m.y-l.y}},dotProduct:function(m,l){return m.x*l.x+m.y*l.y},square:function(l){return Math.sqrt(l.x*l.x+l.y*l.y)},scale:function(m,l){return{x:m.x*l,y:m.y*l}}},d=Math.pow(2,-65),h=function(y,x){for(var s=[],v=x.length-1,r=2*v-1,t=[],w=[],o=[],p=[],q=[[1,0.6,0.3,0.1],[0.4,0.6,0.6,0.4],[0.1,0.3,0.6,1]],u=0;u<=v;u++){t[u]=b.subtract(x[u],y)}for(u=0;u<=v-1;u++){w[u]=b.subtract(x[u+1],x[u]);w[u]=b.scale(w[u],3)}for(u=0;u<=v-1;u++){for(var n=0;n<=v;n++){o[u]||(o[u]=[]);o[u][n]=b.dotProduct(w[u],t[n])}}for(u=0;u<=r;u++){p[u]||(p[u]=[]);p[u].y=0;p[u].x=parseFloat(u)/r}r=v-1;for(t=0;t<=v+r;t++){w=Math.min(t,v);for(u=Math.max(0,t-r);u<=w;u++){j=t-u;p[u+j].y+=o[j][u]*q[j][u]}}v=x.length-1;p=k(p,2*v-1,s,0);r=b.subtract(y,x[0]);o=b.square(r);for(u=q=0;u=64){y[0]=(E[0].x+E[D].x)/2;return 1}var s,r,p;v=E[0].y-E[D].y;w=E[D].x-E[0].x;A=E[0].x*E[D].y-E[D].x*E[0].y;t=max_distance_below=0;for(r=1;rt){t=p}else{if(p0?1:-1,r=null;n endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + endpointsByElement = {}, + endpointsByUUID = {}, + offsets = {}, + offsetTimestamps = {}, + floatingConnections = {}, + draggableStates = {}, + _mouseEventsEnabled = this.Defaults.MouseEventsEnabled, + _draggableByDefault = true, + canvasList = [], + sizes = [], + listeners = {}, // a map: keys are event types, values are lists of listeners. + DEFAULT_SCOPE = this.Defaults.Scope, + renderMode = null, // will be set in init() + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }, + + /** + * appends an element to some other element, which is calculated as follows: + * + * 1. if jsPlumb.Defaults.Container exists, use that element. + * 2. if the 'parent' parameter exists, use that. + * 3. otherwise just use the document body. + * + */ + _appendElement = function(el, parent) { + if (_currentInstance.Defaults.Container) + jsPlumb.CurrentLibrary.appendElement(el, _currentInstance.Defaults.Container); + else if (!parent) + document.body.appendChild(el); + else + jsPlumb.CurrentLibrary.appendElement(el, parent); + }, + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + _timestamp = function() { return "" + (new Date()).getTime(); }, + + /** + * YUI, for some reason, put the result of a Y.all call into an object that contains + * a '_nodes' array, instead of handing back an array-like object like the other + * libraries do. + */ + _convertYUICollection = function(c) { + return c._nodes ? c._nodes : c; + }, + + /** + * Draws an endpoint and its connections. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + _draw = function(element, ui, timestamp) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (!timestamp) timestamp = _timestamp(); + if (endpoints) { + _updateOffset( { elId : id, offset : ui, recalc : false, timestamp : timestamp }); // timestamp is checked against last update cache; it is + // valid for one paint cycle. + var myOffset = offsets[id], myWH = sizes[id]; + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + var l = endpoints[i].connections; + for ( var j = 0; j < l.length; j++) { + l[j].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); // ...paint each connection. + // then, check for dynamic endpoint; need to repaint it. + var oIdx = l[j].endpoints[0] == endpoints[i] ? 1 : 0, + otherEndpoint = l[j].endpoints[oIdx]; + if (otherEndpoint.anchor.isDynamic && !otherEndpoint.isFloating()) { + _updateOffset( { elId : otherEndpoint.elementId, timestamp : timestamp }); + otherEndpoint.paint({ elementWithPrecedence:id }); + // all the connections for the other endpoint now need to be repainted + for (var k = 0; k < otherEndpoint.connections.length; k++) { + if (otherEndpoint.connections[k] !== l) + otherEndpoint.connections[k].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); + } + } + } + } + } + }, + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + _elementProxy = function(element, fn) { + var retVal = null; + if (element.constructor == Array) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }, + + /** + * gets an Endpoint by uuid. + */ + _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }, + + /** + * inits a draggable if it's not already initialised. + */ + _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable) { + if (jsPlumb.CurrentLibrary.isDragSupported(element) && !jsPlumb.CurrentLibrary.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + }); + var draggable = draggableStates[_getId(element)]; + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + } + } + }, + + _newConnection = function(params) { + var connectionFunc = jsPlumb.Defaults.ConnectionType || Connection, + endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint, + parent = jsPlumb.CurrentLibrary.getParent; + + if (params.container) + params["parent"] = params.container; + else { + if (params.sourceEndpoint) + params["parent"] = params.sourceEndpoint.parent; + else if (params.source.constructor == endpointFunc) + params["parent"] = params.source.parent; + else params["parent"] = parent(params.source); + } + + params["_jsPlumb"] = _currentInstance; + var con = new connectionFunc(params); + _eventFireProxy("click", "click", con); + _eventFireProxy("dblclick", "dblclick", con); + return con; + }, + + _eventFireProxy = function(event, proxyEvent, obj) { + obj.bind(event, function(originalObject, originalEvent) { + _currentInstance.fire(proxyEvent, obj, originalEvent); + }); + }, + + _newEndpoint = function(params) { + var endpointFunc = jsPlumb.Defaults.EndpointType || Endpoint; + if (params.container) + params.parent = params.container; + else + params["parent"] = jsPlumb.CurrentLibrary.getParent(params.source); + params["_jsPlumb"] = _currentInstance, + ep = new endpointFunc(params); + _eventFireProxy("click", "endpointClick", ep); + _eventFireProxy("dblclick", "endpointDblClick", ep); + return ep; + }, + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + _operation = function(elId, func, endpointFunc) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + if (endpointFunc) endpointFunc(endpoints[i]); + } + } + }, + /** + * perform an operation on all elements. + */ + _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }, + + /** + * helper to remove an element from the DOM. + */ + _removeElement = function(element, parent) { + if (element != null && element.parentNode != null) { + element.parentNode.removeChild(element); + } + }, + /** + * helper to remove a list of elements from the DOM. + */ + _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }, + /** + * helper method to remove an item from a list. + */ + _removeFromList = function(map, key, value) { + if (key != null) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete (l[i]); + l.splice(i, 1); + return true; + } + } + } + return false; + }, + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }, + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + _setVisible = function(el, state, alsoChangeEndpoints) { + state = state === "block"; + var endpointFunc = null; + if (alsoChangeEndpoints) { + if (state) endpointFunc = function(ep) { + ep.setVisible(true, true, true); + }; + else endpointFunc = function(ep) { + ep.setVisible(false, true, true); + }; + } + var id = _getAttribute(el, "id"); + _operation(id, function(jpc) { + if (state && alsoChangeEndpoints) { + // this test is necessary because this functionality is new, and i wanted to maintain backwards compatibility. + // this block will only set a connection to be visible if the other endpoint in the connection is also visible. + var oidx = jpc.sourceId === id ? 1 : 0; + if (jpc.endpoints[oidx].isVisible()) jpc.setVisible(true); + } + else // the default behaviour for show, and what always happens for hide, is to just set the visibility without getting clever. + jpc.setVisible(state); + }, endpointFunc); + }, + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }, + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + _toggleVisible = function(elId, changeEndpoints) { + var endpointFunc = null; + if (changeEndpoints) { + endpointFunc = function(ep) { + var state = ep.isVisible(); + ep.setVisible(!state); + }; + } + _operation(elId, function(jpc) { + var state = jpc.isVisible(); + jpc.setVisible(!state); + }, endpointFunc); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }, + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return offsets[elId]; + } + if (recalc || offset == null) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + } + return offsets[elId]; + }, + +/** + * gets an id for the given element, creating and setting one if + * necessary. + */ + _getId = function(element, uuid) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else + id = "jsPlumb_" + _timestamp(); + _setAttribute(ele, "id", id); + } + return id; + }, + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'jsPlumb function failed : ' + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, 'wrapped function failed : ' + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = "_jsPlumb_connector"; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = "_jsPlumb_endpoint"; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = "_jsPlumb_overlay"; + + this.Anchors = {}; + + this.Connectors = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Endpoints = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Overlays = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + +// ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***************************************** + /* + * Function: bind + * Bind to an event on jsPlumb. + * + * Parameters: + * event - the event to bind. Available events on jsPlumb are: + * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback. + * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback. + * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback. + * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback. + * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback. + * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback. + * + * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event. + */ + + /* + * Function: clearListeners + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + +// *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *********************************************************** + + /* + Function: addEndpoint + + Adds an to a given element or elements. + + Parameters: + + el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + params - Object containing Endpoint constructor arguments. For more information, see . + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some + shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in + this object are anything that 'params' can contain. See . + + Returns: + The newly created , if el referred to a single element. Otherwise, an array of newly created s. + + See Also: + + */ + this.addEndpoint = function(el, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + + // YUI wrapper + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + var _el = _getElementObject(inputs[i]), id = _getId(_el); + p.source = _el; + _updateOffset({ elId : id }); + var e = _newEndpoint(p); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + results.push(e); + } + + return results.length == 1 ? results[0] : results; + }; + + /* + Function: addEndpoints + Adds a list of s to a given element or elements. + + Parameters: + target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See 's constructor documentation. + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created s, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(el, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams); + if (e.constructor == Array) + Array.prototype.push.apply(results, e); + else results.push(e); + } + return results; + }; + + /* + Function: animate + This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating + the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // onComplete repaints, just to make sure everything looks good at the end of the animation. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /* + Function: connect + Establishes a between two elements (or s, which are themselves registered to elements). + + Parameters: + params - Object containing constructor arguments for the Connection. See 's constructor documentation. + referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of + Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection. + + Returns: + The newly created . + */ + this.connect = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not full. + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + if (_p.target && !_p.target.endpoint) { + var tid = _getId(_p.target), + tep =_targetEndpointDefinitions[tid]; + + var overrideOne = function(singlePropertyName, pluralPropertyName, tepProperty, tep) { + if (tep[tepProperty]) { + if (_p[pluralPropertyName]) _p[pluralPropertyName][1] = tep[tepProperty]; + else if (_p[singlePropertyName]) { + _p[pluralPropertyName] = [ _p[singlePropertyName], tep[tepProperty] ]; + _p[singlePropertyName] = null; + } + else _p[pluralPropertyName] = [ null, tep[tepProperty] ]; + } + }; + + if (tep) { + overrideOne("endpoint", "endpoints", "endpoint", tep); + overrideOne("endpointStyle", "endpointStyles", "paintStyle", tep); + overrideOne("endpointHoverStyle", "endpointHoverStyles", "hoverPaintStyle", tep); + } + } + + // dynamic anchors. backwards compatibility here: from 1.2.6 onwards you don't need to specify "dynamicAnchors". the fact that some anchor consists + // of multiple definitions is enough to tell jsPlumb you want it to be dynamic. + if (_p.dynamicAnchors) { + // these can either be an array of anchor coords, which we will use for both source and target, or an object with {source:[anchors], target:[anchors]}, in which + // case we will use a different set for each element. + var a = _p.dynamicAnchors.constructor == Array; + var sa = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.source)); + var ta = a ? new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors)) : new DynamicAnchor(jsPlumb.makeAnchors(_p.dynamicAnchors.target)); + _p.anchors = [sa,ta]; + } + + var jpc = _newConnection(_p); + // add to list of connections (by scope). + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + _currentInstance.fire("jsPlumbConnection", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + // force a paint + _draw(jpc.source); + + return jpc; + }; + + /* + Function: deleteEndpoint + Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too) + + Parameters: + object - either an object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElement(endpoint.canvas, endpoint.parent); + // remove from endpointsbyElement + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + delete endpoint; + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every , and their associated s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc) { + _currentInstance.fire("jsPlumbConnectionDetached", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }); + }; + + /* + Function: detach + Detaches and then removes a . Takes either (source, target) (the old way, maintained for backwards compatibility), or a params + object with various possible values. + + Parameters: + source - id or element object of the first element in the Connection. + target - id or element object of the second element in the Connection. + params - a JS object containing the same parameters as you pass to jsPlumb.connect. If this is present then neither source nor + target should be present; it should be the only argument to the method. See the docs for 's constructor for information +about the parameters allowed in the params object. + Returns: + true if successful, false if not. + */ + this.detach = function(source, target) { + if (arguments.length == 2) { + var s = _getElementObject(source), sId = _getId(s); + var t = _getElementObject(target), tId = _getId(t); + _operation(sId, function(jpc) { + if ((jpc.sourceId == sId && jpc.targetId == tId) || (jpc.targetId == sId && jpc.sourceId == tId)) { + _removeElements(jpc.connector.getDisplayElements(), jpc.parent); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + // this is the new version of the method, taking a JS object like + // the connect method does. + else if (arguments.length == 1) { + // TODO investigate whether or not this code still works when a user has supplied their own subclass of Connection. i suspect it may not. + if (arguments[0].constructor == Connection) { + arguments[0].endpoints[0].detachFrom(arguments[0].endpoints[1]); + } + else if (arguments[0].connection) { + arguments[0].connection.endpoints[0].detachFrom(arguments[0].connection.endpoints[1]); + } + else { + var _p = jsPlumb.extend( {}, source); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1])); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source); + var targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElements(jpc.connector.getDisplayElements(), jpc.parent); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeFromList(connectionsByScope, jpc.scope, jpc); + } + }); + } + } + } + }; + + /* + Function: detachAll + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + + Returns: + void + */ + this.detachAllConnections = function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + }; + + /** + * @deprecated Use detachAllConnections instead. this will be removed in jsPlumb 1.3. + */ + this.detachAll = this.detachAllConnections; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + /** + * @deprecated use detachEveryConnection instead. this will be removed in jsPlumb 1.3. + */ + this.detachEverything = this.detachEveryConnection; + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of you library's draggable method so that jsPlumb can setup the appropriate callbacks. Your underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method, + * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the + * default scope). If multiple scopes are passed in, the return value will be a map of { scope -> [ connection... ] }. + * + * Parameters + * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list + * of connections that are in the given scope. + * options - if the argument is a JS object, you can specify a finer-grained filter: + * + * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes. + * - *source* either a string representing an element id, or a selector. constrains the result to connections having this source. + * - *target* either a string representing an element id, or a selector. constrains the result to connections having this target. + * + */ + this.getConnections = function(options) { + if (!options) { + options = {}; + } else if (options.constructor == String) { + options = { "scope": options }; + } + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') + r.push(input); + else + r = input; + } + return r; + }; + var scope = options.scope || jsPlumb.getDefaultScope(), + scopes = prepareList(scope), + sources = prepareList(options.source), + targets = prepareList(options.target), + filter = function(list, value) { + return list.length > 0 ? _findIndex(list, value) != -1 : true; + }, + results = scopes.length > 1 ? {} : [], + _addOne = function(scope, obj) { + if (scopes.length > 1) { + var ss = results[scope]; + if (ss == null) { + ss = []; results[scope] = ss; + } + ss.push(obj); + } else results.push(obj); + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + _addOne(i, c); + } + } + } + return results; + }; + + /* + * Function: getAllConnections + * Gets all connections, as a map of { scope -> [ connection... ] }. + */ + this.getAllConnections = function() { + return connectionsByScope; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /** + * Function:getEndpoints + * Gets the list of Endpoints for a given selector, or element id. + * @param el + * @return + */ + this.getEndpoints = function(el) { + return endpointsByElement[_getId(el)]; + }; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + + this.appendElement = _appendElement; + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + changeEndpoints - whether not to also hide endpoints on the element. by default this is false. + + Returns: + void + */ + this.hide = function(el, changeEndpoints) { + _setVisible(el, "none", changeEndpoints); + }; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + if (!initialized) { + _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run. + + var _bind = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && _mouseEventsEnabled && renderMode == jsPlumb.CANVAS) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + var t = c[i].connector[event](e); + if (t) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i].endpoint[event](e)) return; + } + } + } + }); + }; + _bind("click"); + _bind("dblclick"); + _bind("mousemove"); + _bind("mousedown"); + _bind("mouseup"); + + initialized = true; + _currentInstance.fire("ready"); + } + }; + + this.jsPlumbUIComponent = jsPlumbUIComponent; + this.EventGenerator = EventGenerator; + + /* + * Creates an anchor with the given params. + * + * You do not need to use this method. It is exposed because of the way jsPlumb is + * split into three scripts; this will change in the future. + * + * x - the x location of the anchor as a fraction of the + * total width. y - the y location of the anchor as a fraction of the + * total height. xOrientation - value indicating the general direction a + * connection from the anchor should go in, in the x direction. + * yOrientation - value indicating the general direction a connection + * from the anchor should go in, in the y direction. xOffset - a fixed + * offset that should be applied in the x direction that should be + * applied after the x position has been figured out. optional. defaults + * to 0. yOffset - a fixed offset that should be applied in the y + * direction that should be applied after the y position has been + * figured out. optional. defaults to 0. + * -- OR -- + * + * params - {x:..., y:..., xOrientation etc } + * -- OR FROM 1.2.4 --- + * + * name - the name of some Anchor in the _currentInstance.Anchors array. + * -- OR FROM 1.2.4 --- + * + * coords - a list of coords for the anchor, like you would pass to + * jsPlumb.makeAnchor (eg [0.5,0.5,0,-1] - an anchor in the center of + * some element, oriented towards the top of the screen) + * -- OR FROM 1.2.4 --- + * + * anchor - an existing anchor. just gets passed back. it's handy + * internally to have this functionality. + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed + // in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards + // compatibility if we are given only one value we assume it's a + // call in the old form. + if (arguments.length == 0) return null; + var params = {}; + if (arguments.length == 1) { + var specimen = arguments[0]; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; + // is it the name of an anchor type? + else if (typeof specimen == "string") return _currentInstance.Anchors[arguments[0]](); + // is it an array of coordinates? + else if (specimen.constructor == Array) { + if (specimen[0].constructor == Array || specimen[0].constructor == String) + return new DynamicAnchor(specimen); + else + return jsPlumb.makeAnchor.apply(this, specimen); + } + // last we try the backwards compatibility stuff. + else if (typeof arguments[0] == "object") jsPlumb.extend(params, x); + } else { + params = { x : x, y : y }; + if (arguments.length >= 4) params.orientation = [ arguments[2], arguments[3] ]; + if (arguments.length == 6) params.offsets = [ arguments[4], arguments[5] ]; + } + var a = new Anchor(params); + a.clone = function() { + return new Anchor(params); + }; + return a; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types) { + var r = []; + for ( var i = 0; i < types.length; i++) + if (typeof types[i] == "string") + r.push(_currentInstance.Anchors[types[i]]()); + else if (types[i].constructor == Array) + r.push(jsPlumb.makeAnchor(types[i])); + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /** + * Function: makeTarget + * Makes some DOM element a Connection target, allowing you to drag connections to it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a target. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * scope optional. scope for the drop zone. + * dropOptions optional. same stuff as you would pass to dropOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to false. whether or not to delete + * any Endpoints created by a connection to this target if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + var _targetEndpointDefinitions = {}; + this.makeTarget = function(el, params, referenceParams) { + + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + var jpcl = jsPlumb.CurrentLibrary, + scope = p.scope || _currentInstance.Defaults.Scope, + deleteEndpointsOnDetach = p.deleteEndpointsOnDetach || false, + _doOne = function(_el) { + + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el); + _targetEndpointDefinitions[elid] = p.endpoint; + + var dropOptions = jsPlumb.extend({}, p.dropOptions || {}); + var _drop = function() { + var draggable = _getElementObject(jpcl.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + // restore the original scope if necessary (issue 57) + scope = _getAttribute(draggable, "originalScope"); + + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + // get the connection, to then get its endpoint + var jpc = floatingConnections[id], + source = jpc.endpoints[0], + _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : null, + // make a new Endpoint + newEndpoint = jsPlumb.addEndpoint(_el, _endpoint); + + var c = jsPlumb.connect({ + source:source, + target:newEndpoint, + scope:scope + }); + if (deleteEndpointsOnDetach) + c.endpointToDeleteOnDetach = newEndpoint; + }; + + var dropEvent = jpcl.dragEvents['drop']; + dropOptions["scope"] = dropOptions["scope"] || scope; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop); + + jpcl.initDroppable(_el, dropOptions); + }; + + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + }; + + /** + * helper method to make a list of elements drop targets. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeTargets = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeTarget(els[i], params, referenceParams); + } + }; + + /* + Function: ready + Helper method to bind a function to jsPlumb's ready event. + */ + this.ready = function(fn) { + _currentInstance.bind("ready", fn); + }, + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + var timestamp = _timestamp(); + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, timestamp); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + for ( var i in ebe) + _currentInstance.deleteEndpoint(ebe[i]); + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + this.deleteEveryEndpoint(); + this.clearListeners(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + + Parameters: + value - whether or not to automatically repaint when the window is resized. + + Returns: void + */ + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + }; + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setDraggableByDefault + * Sets whether or not elements are draggable by default. Default for this is true. + * + * Parameters: + * draggable - value to set + * + * Returns: + * void + */ + this.setDraggableByDefault = function(draggable) { + _draggableByDefault = draggable; + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction + * Sets the function to fire when the window size has changed and a repaint was fired. + * + * Parameters: + * f - Function to execute. + * + * Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: setMouseEventsEnabled + * Sets whether or not mouse events are enabled. Default is true. + * + * Parameters: + * enabled - whether or not mouse events should be enabled. + * + * Returns: + * void + */ + this.setMouseEventsEnabled = function(enabled) { + _mouseEventsEnabled = enabled; + }; + + /* + * Constant for use with the setRenderMode method + */ + this.CANVAS = "canvas"; + + /* + * Constant for use with the setRenderMode method + */ + this.SVG = "svg"; + + this.VML = "vml"; + + /* + * Function: setRenderMode + * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that + * what you asked for is not supported (and that VML is). If you asked for VML but the browser does + * not support it, jsPlumb uses SVG. + * + * Returns: + * the render mode that jsPlumb set, which of course may be different from that requested. + */ + this.setRenderMode = function(mode) { + if (mode) + mode = mode.toLowerCase(); + else + return; + if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); + // now test we actually have the capability to do this. + if (mode === jsPlumb.CANVAS && canvasAvailable) + renderMode = jsPlumb.CANVAS; + else if (mode === jsPlumb.SVG && svgAvailable) + renderMode = jsPlumb.SVG; + else if (vmlAvailable) + renderMode = jsPlumb.VML; + + return renderMode; + }; + + this.getRenderMode = function() { return renderMode; }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * changeEndpoints - whether or not to also change the visible state of the endpoints on the element. this also has a bearing on + * other connections on those endpoints: if their other endpoint is also visible, the connections are made visible. + * + * Returns: + * void + */ + this.show = function(el, changeEndpoints) { + _setVisible(el, "block", changeEndpoints); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + findIndex : _findIndex, + getId : _getId, + makeAnchor:self.makeAnchor, + makeDynamicAnchor:self.makeDynamicAnchor + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * changeEndpoints - whether or not to also toggle the endpoints on the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload + * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element. + * + * Returns: + * void + */ + this.unload = function() { + delete endpointsByElement; + delete endpointsByUUID; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + delete canvasList; + }; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + if (timestamp && timestamp === self.timestamp) { + return lastReturnValue; + } + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + // adjust loc if there is an offsetParent + if (element.canvas && element.canvas.offsetParent) { + var po = element.canvas.offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(element.canvas.offsetParent); + lastReturnValue[0] = lastReturnValue[0] - po.left; + lastReturnValue[1] = lastReturnValue[1] - po.top; + } + + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function() { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + * + * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(_getElementObject(refCanvas)); + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + var orientation = null; + var _lastResult = null; + + this.compute = function(params) { + var xy = params.xy, element = params.element; + var result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + + // adjust loc if there is an offsetParent + if (element.canvas && element.canvas.offsetParent) { + var po = element.canvas.offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(element.canvas.offsetParent); + result[0] = result[0] - po.left; + result[1] = result[1] - po.top; + } + + _lastResult = result; + return result; + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { orientation = anchor.getOrientation(); }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchors is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = [], + _convert = function(anchor) { return anchor.constructor == Anchor ? anchor: jsPlumb.makeAnchor(anchor); }; + for (var i = 0; i < anchors.length; i++) _anchors[i] = _convert(anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + this.locked = false; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null, + _curIndex = _anchors.length > 0 ? 0 : -1, + self = this, + + // helper method to calculate the distance between the centers of the two elements. + _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }, + + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + + var pos = _curAnchor.compute(params); + return pos; + }; + + this.getCurrentLocation = function() { + var cl = _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + return cl; + }; + + this.getOrientation = function() { return _curAnchor != null ? _curAnchor.getOrientation() : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * container - optional id or selector instructing jsPlumb where to attach all the elements it creates for this connection. you should read the documentation for a full discussion of this. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * overlays - Optional array of Overlay definitions to appear on this Connection. + * drawEndpoints - if false, instructs jsPlumb to not draw the endpoints for this Connection. Be careful with this: it only really works when you tell jsPlumb to attach elements to the document body. Read the documentation for a full discussion of this. + */ + var Connection = function(params) { + + jsPlumbUIComponent.apply(this, arguments); + // ************** get the source and target and register the connection. ******************* + var self = this; + var visible = true; + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none"; + }; + var id = new String('_jsplumb_c_' + (new Date()).getTime()); + this.getId = function() { return id; }; + this.parent = params.parent; + /** + Property: source + The source element for this Connection. + */ + this.source = _getElementObject(params.source); + /** + Property:target + The target element for this Connection. + */ + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. + if (params.sourceEndpoint) this.source = params.sourceEndpoint.getElement(); + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + /* + * Property: sourceId + * Id of the source element in the connection. + */ + this.sourceId = _getAttribute(this.source, "id"); + /* + * Property: targetId + * Id of the target element in the connection. + */ + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + + /** + * implementation of abstract method in EventGenerator + * @return list of attached elements. in our case, a list of Endpoints. + */ + this.getAttachedElements = function() { + return self.endpoints; + }; + + /** + * implementation of abstract method in EventGenerator + */ + this.savePosition = function() { + srcWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.source)); + targetWhenMouseDown = jsPlumb.CurrentLibrary.getOffset(jsPlumb.CurrentLibrary.getElementObject(self.target)); + }; + + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams) { + if (anchorParams) + return jsPlumb.makeAnchor(anchorParams); + }; + var prepareEndpoint = function(existing, index, params, element, connectorPaintStyle, connectorHoverPaintStyle) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] + || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint; + + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified. + if (es.fillStyle == null && connectorPaintStyle != null) + es.fillStyle = connectorPaintStyle.strokeStyle; + + // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does: + //* + if (es.outlineColor == null && connectorPaintStyle != null) + es.outlineColor = connectorPaintStyle.outlineColor; + if (es.outlineWidth == null && connectorPaintStyle != null) + es.outlineWidth = connectorPaintStyle.outlineWidth; + //*/ + + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure? + if (connectorHoverPaintStyle != null) { + if (ehs == null) ehs = {}; + if (ehs.fillStyle == null) { + ehs.fillStyle = connectorHoverPaintStyle.strokeStyle; + } + } + var a = params.anchors ? params.anchors[index] : _makeAnchor(_currentInstance.Defaults.Anchors[index]) || _makeAnchor(jsPlumb.Defaults.Anchors[index]) || _makeAnchor(_currentInstance.Defaults.Anchor) || _makeAnchor(jsPlumb.Defaults.Anchor), + u = params.uuids ? params.uuids[index] : null, + e = _newEndpoint({ + paintStyle : es, + hoverPaintStyle:ehs, + endpoint : ep, + connections : [ self ], + uuid : u, + anchor : a, + source : element, + container:params.container + }); + self.endpoints[index] = e; + + if (params.drawEndpoints === false) e.setVisible(false, true, true); + + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source, params.paintStyle, params.hoverPaintStyle); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target, params.paintStyle, params.hoverPaintStyle); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + /* + * Function: setConnector + * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method - the same + * thing that you would set as the 'connector' property on a jsPlumb.connect call. + * + * Parameters: + * connector - Connector definition + */ + this.setConnector = function(connector, doNotRepaint) { + if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent); + var connectorArgs = { _jsPlumb:self._jsPlumb, parent:params.parent, cssClass:params.cssClass, container:params.container }; + if (connector.constructor == String) + this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand. + else if (connector.constructor == Array) + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs)); + this.canvas = this.connector.canvas; + // add mouse events + this.connector.bind("click", function(con, e) { + _mouseWasDown = false; + self.fire("click", self, e); + }); + this.connector.bind("dblclick", function(con, e) { _mouseWasDown = false;self.fire("dblclick", self, e); }); + this.connector.bind("mouseenter", function(con, e) { + if (!self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(true); + } + self.fire("mouseenter", self, e); + } + }); + this.connector.bind("mouseexit", function(con, e) { + if (self.isHover()) { + if (_connectionBeingDragged == null) { + self.setHover(false); + } + self.fire("mouseexit", self, e); + } + }); + this.connector.bind("mousedown", function(con, e) { + _mouseDown = true; + _mouseDownAt = jsPlumb.CurrentLibrary.getPageXY(e); + self.savePosition(); + }); + this.connector.bind("mouseup", function(con, e) { + _mouseDown = false; + if (self.connector == _connectionBeingDragged) _connectionBeingDragged = null; + }); + + if (!doNotRepaint) self.repaint(); + }; + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc) + */ + + self.setConnector(this.endpoints[0].connector || + this.endpoints[1].connector || + params.connector || + _currentInstance.Defaults.Connector || + jsPlumb.Defaults.Connector, true); + + this.setPaintStyle(this.endpoints[0].connectorStyle || + this.endpoints[1].connectorStyle || + params.paintStyle || + _currentInstance.Defaults.PaintStyle || + jsPlumb.Defaults.PaintStyle, true); + + this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || + this.endpoints[1].connectorHoverStyle || + params.hoverPaintStyle || + _currentInstance.Defaults.HoverPaintStyle || + jsPlumb.Defaults.HoverPaintStyle, true); + + this.paintStyleInUse = this.paintStyle; + + /* + * Property: overlays + * List of Overlays for this Connection. + */ + this.overlays = []; + var _overlays = params.overlays || _currentInstance.Defaults.Overlays; + if (_overlays) { + for (var i = 0; i < _overlays.length; i++) { + var o = _overlays[i], _newOverlay = null; + if (o.constructor == Array) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0]; + var p = jsPlumb.CurrentLibrary.extend({connection:self, _jsPlumb:_currentInstance}, o[1]); // make a copy of the object so as not to mess up anyone else's reference... + if (o.length == 3) jsPlumb.CurrentLibrary.extend(p, o[2]); + _newOverlay = new jsPlumb.Overlays[renderMode][type](p); + if (p.events) { + for (var evt in p.events) { + _newOverlay.bind(evt, p.events[evt]); + } + } + } else if (o.constructor == String) { + _newOverlay = new jsPlumb.Overlays[renderMode][o]({connection:self, _jsPlumb:_currentInstance}); + } else { + _newOverlay = o; + } + + + + this.overlays.push(_newOverlay); + } + } + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Connection. + * + * Parameters: + * event - the event to bind. Available events on a Connection are: + * - *click* : notification that a Connection was clicked. + * - *dblclick* : notification that a Connection was double clicked. + * - *mouseenter* : notification that the mouse is over a Connection. + * - *mouseexit* : notification that the mouse exited a Connection. + * + * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Connection's paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Connection will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Connection. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + // overlay finder helper method + var _getOverlayIndex = function(id) { + var idx = -1; + for (var i = 0; i < self.overlays.length; i++) { + if (id === self.overlays[i].id) { + idx = i; + break; + } + } + return idx; + }; + + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + this.addOverlay = function(overlay) { self.overlays.push(overlay); }; + + /* + * Function: getOverlay + * Gets an overlay, by ID. Note: by ID. You would pass an 'id' parameter + * in to the Overlay's constructor arguments, and then use that to retrieve + * it via this method. + */ + this.getOverlay = function(id) { + var idx = _getOverlayIndex(id); + return idx >= 0 ? self.overlays[idx] : null; + }; + + /* + * Function: hideOverlay + * Hides the overlay specified by the given id. + */ + this.hideOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.hide(); + }; + + /* + * Function: showOverlay + * Shows the overlay specified by the given id. + */ + this.showOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.show(); + }; + + /** + * Function: removeAllOverlays + * Removes all overlays from the Connection, and then repaints. + */ + this.removeAllOverlays = function() { + self.overlays.splice(0, self.overlays.length); + self.repaint(); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + this.removeOverlay = function(overlayId) { + var idx = _getOverlayIndex(overlayId); + if (idx != -1) self.overlays.splice(idx, 1); + }; + + /** + * Function:removeOverlays + * Removes a set of overlays by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + this.removeOverlays = function() { + for (var i = 0; i < arguments.length; i++) + self.removeOverlay(arguments[i]); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + this.labelStyle = params.labelStyle || _currentInstance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.label = params.label; + if (this.label) { + this.overlays.push(new jsPlumb.Overlays[renderMode].Label( { + cssClass:params.cssClass, + labelStyle : this.labelStyle, + label : this.label, + connection:self, + _jsPlumb:_currentInstance + })); + } + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String or a Function that returns a String. + */ + this.setLabel = function(l) { + self.label = l; + _currentInstance.repaint(self.source); + }; + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var otherOffset = offsets[this.targetId]; + var otherWH = sizes[this.targetId]; + var anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1] + }); + this.endpoints[0].paint( { anchorLoc : anchorLoc }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0] + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc }); + + /* + * Paints the Connection. Not exposed for public usage. + * + * Parameters: + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp, + // if the moving object is not the source we must transpose the two references. + swap = false, + tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId, + tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + + _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }); + _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var sAnchorP = this.endpoints[sIdx].anchor.getCurrentLocation(), + tAnchorP = this.endpoints[tIdx].anchor.getCurrentLocation(); + + /* paint overlays*/ + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible()) { + var s = o.computeMaxSize(self.connector); + if (s > maxSize) + maxSize = s; + } + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, self.paintStyleInUse.lineWidth, maxSize); + + self.connector.paint(dim, self.paintStyleInUse); + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) + self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim); + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function() { + this.paint({ elId : this.sourceId, recalc : true }); + }; + + _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide + // whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + + // just to make sure the UI gets initialised fully on all browsers. + self.repaint(); + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1 + * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't + * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you + * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or + * 'target' Endpoints for Connections. + * + * + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * container - optional id or selector instructing jsPlumb where to attach the element it creates for this endpoint. you should read the documentation for a full discussion of this. + * connections - optional list of Connections to configure the Endpoint with. + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + params = params || {}; + var self = this; +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Endpoint. + * + * Parameters: + * event - the event to bind. Available events on an Endpoint are: + * - *click* : notification that a Endpoint was clicked. + * - *dblclick* : notification that a Endpoint was double clicked. + * - *mouseenter* : notification that the mouse is over a Endpoint. + * - *mouseexit* : notification that the mouse exited a Endpoint. + * + * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Endpoint's paint style and then repaints the Endpoint. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Endpoint will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Endpoint. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + var visible = true; + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + var id = new String('_jsplumb_e_' + (new Date()).getTime()); + this.getId = function() { return id; }; + if (params.dynamicAnchors) + self.anchor = new DynamicAnchor(jsPlumb.makeAnchors(params.dynamicAnchors)); + else + self.anchor = params.anchor ? jsPlumb.makeAnchor(params.anchor) : params.anchors ? jsPlumb.makeAnchor(params.anchors) : jsPlumb.makeAnchor("TopCenter"); + var _endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot", + endpointArgs = { _jsPlumb:self._jsPlumb, parent:params.parent, container:params.container }; + if (_endpoint.constructor == String) + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint](endpointArgs); + else if (_endpoint.constructor == Array) { + endpointArgs = jsPlumb.extend(_endpoint[1], endpointArgs); + _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint[0]](endpointArgs); + } + else + _endpoint = _endpoint.clone(); + + // assign a clone function using our derived endpointArgs. this is used when a drag starts: the endpoint that was dragged is cloned, + // and the clone is left in its place while the original one goes off on a magical journey. + this.clone = function() { + var o = new Object(); + _endpoint.constructor.apply(o, [endpointArgs]); + return o; + }; + + self.endpoint = _endpoint; + self.type = self.endpoint.type; + + // TODO this event listener registration code is identical to what Connection does: it should be refactored. + this.endpoint.bind("click", function(e) { self.fire("click", self, e); }); + this.endpoint.bind("dblclick", function(e) { self.fire("dblclick", self, e); }); + this.endpoint.bind("mouseenter", function(con, e) { + if (!self.isHover()) { + self.setHover(true); + self.fire("mouseenter", self, e); + } + }); + this.endpoint.bind("mouseexit", function(con, e) { + if (self.isHover()) { + self.setHover(false); + self.fire("mouseexit", self, e); + } + }); + // TODO this event listener registration code above is identical to what Connection does: it should be refactored. + + this.setPaintStyle(params.paintStyle || + params.style || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyle, true); + this.setHoverPaintStyle(params.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyle, true); + this.paintStyleInUse = this.paintStyle; + this.connectorStyle = params.connectorStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.parent = params.parent; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + var _element = params.source, + _uuid = params.uuid, + floatingEndpoint = null, + inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + this.element = _element; + var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + /* + * Property: canvas + * The Endpoint's Canvas. + */ + this.canvas = this.endpoint.canvas; + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + this.connections = params.connections || []; + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + var _reattach = params.reattach || false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + + // this avoids a circular loop + if (!ignoreTarget) { + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + t.detach(connection, true); + // check connection to see if we want to delete the other endpoint. + // if the user uses makeTarget to make some element a target for connections, + // it is possible that they will have set 'endpointToDeleteOnDetach': when + // you make a connection to an element that acts as a target (note: NOT an + // Endpoint; just some div as a target), Endpoints are created for that + // connection. so if you then delete that Connection, it is feasible you + // will want these auto-generated endpoints to be removed. + if (connection.endpointToDeleteOnDetach && connection.endpointToDeleteOnDetach.connections.length == 0) + jsPlumb.deleteEndpoint(connection.endpointToDeleteOnDetach); + } + _removeElements(connection.connector.getDisplayElements(), connection.parent); + _removeFromList(connectionsByScope, connection.scope, connection); + if(!ignoreTarget) fireDetachEvent(connection); + } + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + */ + this.detachAll = function() { + while (self.connections.length > 0) { + self.detach(self.connections[0]); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + */ + this.detachFrom = function(targetEndpoint) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + c[i].setHover(false); + self.detach(c[i]); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + return _newEndpoint( { anchor : self.anchor, source : _element, paintStyle : this.paintStyle, endpoint : _endpoint }); + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + return (self.connections.length < _maxConnections) || _maxConnections == -1 ? null : self.connections[0]; + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + /* + * Function: setStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to + * setStyle and deprecate it if so. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + * + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + // a helper function that tries to find a connection to the given element, and returns it if so. if elementWithPrecedence is null, + // or no connection to it is found, we return the first connection in our list. + var findConnectionToUseForDynamicAnchor = function(elementWithPrecedence) { + var idx = 0; + if (elementWithPrecedence != null) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].sourceId == elementWithPrecedence || self.connections[i].targetId == elementWithPrecedence) { + idx = i; + break; + } + } + } + + return self.connections[idx]; + }; + + /* + * Function: paint + * Paints the Endpoint, recalculating offset and anchor positions if necessary. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + + params = params || {}; + var timestamp = params.timestamp; + if (!timestamp || self.timestamp !== timestamp) { + var ap = params.anchorPoint, canvas = params.canvas, connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + var xy = params.offset || offsets[_elementId]; + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (self.anchor.isDynamic) { + if (self.connections.length > 0) { + //var c = self.connections[0]; + var c = findConnectionToUseForDynamicAnchor(params.elementWithPrecedence); + var oIdx = c.endpoints[0] == self ? 1 : 0; + var oId = oIdx == 0 ? c.sourceId : c.targetId; + var oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + } + ap = self.anchor.compute(anchorParams); + } + + var d = _endpoint.compute(ap, self.anchor.getOrientation(), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + _endpoint.paint(d, self.paintStyleInUse, self.anchor); + + self.timestamp = timestamp; + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + var start = function() { + jpc = self.connectorSelector(); + if (self.isFull() && !dragAllowedWhenFull) return false; + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + n.style.position = "absolute"; + var nE = _getElementObject(n); + _appendElement(n, self.parent); + // create and assign an id, and initialize the offset. + var id = _getId(nE); + + // set the offset of this div to be where 'inPlaceCopy' is, to start with. + var ipcoel = _getElementObject(inPlaceCopy.canvas), + ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel), + po = inPlaceCopy.canvas.offsetParent != null ? + inPlaceCopy.canvas.offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(inPlaceCopy.canvas.offsetParent) + : { left:0, top: 0}; + jsPlumb.CurrentLibrary.setOffset(n, {left:ipco.left - po.left, top:ipco.top-po.top}); + + _updateOffset( { elId : id }); + + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor( { reference : self.anchor, referenceCanvas : self.canvas }); + floatingEndpoint = _newEndpoint({ paintStyle : self.paintStyle, endpoint : _endpoint, anchor : floatingAnchor, source : nE }); + + if (jpc == null) { + self.anchor.locked = true; + // create a connection. one end is this endpoint, the + // other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : _getElementObject(_element), + target : _getElementObject(n), + anchors : [ self.anchor, floatingAnchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays + }); + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.connector.setHover(false); + } else { + existingJpc = true; + // TODO determine whether or not we wish to do de-select hover when dragging a connection. + // it may be the case that we actually want to set it, since it provides a good + // visual cue. + jpc.connector.setHover(false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas)); + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; // are we the source or the target? + + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas); + var dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // now we want to get this endpoint's DROP scope, and set it for now: we can only be dropped on drop zones + // that have our drop scope (issue 57). + var dropScope = jsPlumb.CurrentLibrary.getDropScope(c);//jpc.endpoints[anchorIdx == 0 ? 1 : 0].getDropScope(); + jsPlumb.CurrentLibrary.setDragScope(c, dropScope); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it and register connection on it. + floatingConnections[id] = jpc; + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var jpcl = jsPlumb.CurrentLibrary, + dragOptions = params.dragOptions || {}, + defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions), + startEvent = jpcl.dragEvents['start'], + stopEvent = jpcl.dragEvents['stop'], + dragEvent = jpcl.dragEvents['drag']; + + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], + function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + jsPlumb.CurrentLibrary.setOffset(n, _ui); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements( [ n, floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (_reattach) { + + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } else { + jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc); // the main endpoint will inform the floating endpoint + // to disconnect, and also post the detached event. + } + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElements(jpc.connector.getDisplayElements(), self.parent); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint(); + jpc.setHover(false); + jpc.repaint(); + jpc = null; + delete inPlaceCopy; + delete endpointsByElement[floatingEndpoint.elementId]; + floatingEndpoint = null; + delete floatingEndpoint; + + _currentInstance.currentlyDragging = false; + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas) { + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + var drop = function() { + var draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)); + var id = _getAttribute(draggable, "dragId"); + var elId = _getAttribute(draggable, "elId"); + + // restore the original scope if necessary (issue 57) + var scope = _getAttribute(draggable, "originalScope"); + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + var jpc = floatingConnections[id]; + + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) { + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // todo test that the target is not full. + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + if (!jpc.suspendedEndpoint) { + _addToList(connectionsByScope, jpc.scope, jpc); + _initDraggableIfNecessary(_element, params.draggable, {}); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + _currentInstance.fire("jsPlumbConnectionDetached", { + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }); + } + + jsPlumb.repaint(elId); + + _currentInstance.fire("jsPlumbConnection", { + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], + targetEndpoint : jpc.endpoints[1], + connection:jpc + }); + } + + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute( _getElementObject(draggable), "dragId"); + var jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + } + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], + function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute(_getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + } + }); + + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas)); + + return self; + }; + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + j.init(); + return j; + }; + + var _curryAnchor = function(x,y,ox,oy) { + return function() { + return jsPlumb.makeAnchor(x,y,ox,oy); + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1); + + jsPlumb.Defaults.DynamicAnchors = function() { + return jsPlumb.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"]); + }; + jsPlumb.Anchors["AutoDefault"] = function() { return jsPlumb.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors()); }; + + +})(); +/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the default Connectors, Endpoint and Overlay definitions. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +(function() { + + /** + * + * Helper class to consume unused mouse events by components that are DOM elements and + * are used by all of the different rendering modes. + * + */ + jsPlumb.DOMElementComponent = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + // when render mode is canvas, these functions may be called by the canvas mouse handler. + // this component is safe to pipe this stuff to /dev/null. + this.mousemove = + this.dblclick = + this.click = + this.mousedown = + this.mouseup = function(e) { }; + }; + + /** + * Class: Connectors.Straight + * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters. + */ + jsPlumb.Connectors.Straight = function() { + this.type = "Straight"; + var self = this; + var currentPoints = null; + var _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + _dx = _tx - _sx, _dy = (_ty - _sy); + _m = _dy / _dx, _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + + return currentPoints; + }; + + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + var xp = _sx + (location * _dx); + var yp = (_m == Infinity || _m == -Infinity) ? _sy + (location * (_ty - _sy)) : (_m * xp) + _b; + return {x:xp, y:yp}; + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { return _m; }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location); + var orientation = distance > 0 ? 1 : -1; + var y = Math.abs(distance * Math.sin(_theta)); + if (_sy > _ty) y = y * -1; + var x = Math.abs(distance * Math.cos(_theta)); + if (_sx > _tx) x = x * -1; + return {x:p.x + (orientation * x), y:p.y + (orientation * y)}; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = self.gradientAtPoint(p.location); + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + }; + }; + + + /** + * Class:Connectors.Bezier + * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's + * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below. + */ + /** + * Function:Constructor + * + * Parameters: + * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line. + * Optional; defaults to 150. + * + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + params = params || {}; + this.majorAnchor = params.curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + this.type = "Bezier"; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h; + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) + { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx); var minx2 = Math.min(_CP[0], _CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(_sx,_tx); var maxx2 = Math.max(_CP[0], _CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty); var miny2 = Math.min(_CP[1], _CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(_sy,_ty); var maxy2 = Math.max(_CP[1], _CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + return jsBezier.perpendicularToCurveAt(_makeCurve(), location, length, distance); + }; + + }; + + + /** + * Class: Connectors.Flowchart + * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments. + */ + /** + * Function: Constructor + * + * Parameters: + * stub - minimum length for the stub at each end of the connector. defaults to 30 pixels. + */ + jsPlumb.Connectors.Flowchart = function(params) { + this.type = "Flowchart"; + params = params || {}; + var self = this, + minStubLength = params.stub || params.minStubLength /* bwds compat. */ || 30, + segments = [], + segmentGradients = [], + segmentProportions = [], + segmentLengths = [], + segmentProportionalLengths = [], + points = [], + swapX, + swapY, + /** + * recalculates the gradients of each segment, and the points at which the segments begin, proportional to the total length travelled + * by all the segments that constitute the connector. + */ + updateSegmentGradientsAndProportions = function(startX, startY, endX, endY) { + var total = 0; + for (var i = 0; i < segments.length; i++) { + var sx = i == 0 ? startX : segments[i][2], + sy = i == 0 ? startY : segments[i][3], + ex = segments[i][0], + ey = segments[i][1]; + + segmentGradients[i] = sx == ex ? Infinity : 0; + segmentLengths[i] = Math.abs(sx == ex ? ey - sy : ex - sx); + total += segmentLengths[i]; + } + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segmentLengths[i] / total; + segmentProportions[i] = [curLoc, (curLoc += (segmentLengths[i] / total)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0]; + var ly = segments.length == 0 ? sy : segments[segments.length - 1][1]; + segments.push([x, y, lx, ly]); + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment + * that contains the point which is 'location' distance along the entire path, where + * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths + * are made up of a list of segments, each of which contributes some fraction to + * the total length. + */ + findSegmentForLocation = function(location) { + var idx = segmentProportions.length - 1, inSegmentProportion = 0; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + segments = []; + segmentGradients = []; + segmentProportionalLengths = []; + segmentLengths = []; + segmentProportionals = []; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + offx = (lw / 2) + (minStubLength * 2), + offy = (lw / 2) + (minStubLength * 2), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(), + to = targetAnchor.orientation || targetAnchor.getOrientation(), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx, + h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy; + if (w < minWidth) { + offx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + offy += (minWidth - h) / 2; + h = minWidth; + } + var sx = swapX ? w-offx : offx, + sy = swapY ? h-offy : offy, + tx = swapX ? offx : w-offx , + ty = swapY ? offy : h-offy, + startStubX = sx + (so[0] * minStubLength), + startStubY = sy + (so[1] * minStubLength), + endStubX = tx + (to[0] * minStubLength), + endStubY = ty + (to[1] * minStubLength), + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2); + + x -= offx; y -= offy; + points = [x, y, w, h, sx, sy, tx, ty], extraPoints = []; + + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + if (so[0] == 0) { + var startStubIsBeforeEndStub = startStubY < endStubY; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } + } + else { + var startStubIsBeforeEndStub = startStubX < endStubX; + // when start point's stub is less than endpoint's stub + if (startStubIsBeforeEndStub) { + addSegment(midx, startStubY, sx, sy, tx, ty); + addSegment(midx, midy, sx, sy, tx, ty); + addSegment(midx, endStubY, sx, sy, tx, ty); + } else { + // when start point's stub is greater than endpoint's stub + addSegment(startStubX, midy, sx, sy, tx, ty); + addSegment(endStubX, midy, sx, sy, tx, ty); + } + } + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentGradientsAndProportions(sx, sy, tx, ty); + + return points; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location) { + return self.pointAlongPathFrom(location, 0); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location) { + return segmentGradients[findSegmentForLocation(location)["index"]]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance) { + var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segmentLengths[s.index], m = segmentGradients[s.index]; + var e = { + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + return e; + }; + + /** + * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location. + * the line is 'length' pixels long. + */ + this.perpendicularToPathAt = function(location, length, distance) { + var p = self.pointAlongPathFrom(location, distance); + var m = segmentGradients[p.segmentInfo.index]; + var _theta2 = Math.atan(-1 / m); + var y = length / 2 * Math.sin(_theta2); + var x = length / 2 * Math.cos(_theta2); + return [{x:p.x + x, y:p.y + y}, {x:p.x - x, y:p.y - y}]; + + }; + + }; + + // ********************************* END OF CONNECTOR TYPES ******************************************************************* + + // ********************************* ENDPOINT TYPES ******************************************************************* + + /** + * Class: Endpoints.Dot + * A round endpoint, with default radius 10 pixels. + */ + + /** + * Function: Constructor + * + * Parameters: + * + * radius - radius of the endpoint. defaults to 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + this.type = "Dot"; + var self = this; + params = params || {}; + this.radius = params.radius || 10; + this.defaultOffset = 0.5 * this.radius; + this.defaultInnerRadius = this.radius / 3; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var r = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - r; + var y = anchorPoint[1] - r; + return [ x, y, r * 2, r * 2, r ]; + }; + }; + + /** + * Class: Endpoints.Rectangle + * A Rectangular Endpoint, with default size 20x20. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the endpoint. defaults to 20 pixels. + * height - height of the endpoint. defaults to 20 pixels. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + this.type = "Rectangle"; + var self = this; + params = params || {}; + this.width = params.width || 20; + this.height = params.height || 20; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + return [ x, y, width, height]; + }; + }; + + /** + * Class: Endpoints.Image + * Draws an image as the Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * src - location of the image to use. + */ + jsPlumb.Endpoints.Image = function(params) { + + this.type = "Image"; + jsPlumb.DOMElementComponent.apply(this, arguments); + + var self = this, initialized = false; + this.img = new Image(); + self.ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.src || params.url; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + self.anchorPoint = anchorPoint; + if (self.ready) return [anchorPoint[0] - self.img.width / 2, anchorPoint[1] - self.img.height/ 2, self.img.width, self.img.height]; + else return [0,0,0,0]; + }; + + self.canvas = document.createElement("img"), initialized = false; + self.canvas.style["margin"] = 0; + self.canvas.style["padding"] = 0; + self.canvas.style["outline"] = 0; + self.canvas.style["position"] = "absolute"; + self.canvas.className = jsPlumb.endpointClass; + jsPlumb.appendElement(self.canvas, params.parent); + self.attachListeners(self.canvas, self); + + var actuallyPaint = function(d, style, anchor) { + if (!initialized) { + self.canvas.setAttribute("src", self.img.src); + initialized = true; + } + var width = self.img.width, + height = self.img.height, + x = self.anchorPoint[0] - (width/2), + y = self.anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(self.canvas, x, y, width, height); + }; + + this.paint = function(d, style, anchor) { + if (self.ready) { + actuallyPaint(d, style, anchor); + } + else { + window.setTimeout(function() { + self.paint(d, style, anchor); + }, 200); + } + }; + }; + + /** + * Class: Endpoints.Blank + * An Endpoint that paints nothing on the screen, and cannot be interacted with using the mouse. There are no constructor parameters for this Endpoint. + */ + jsPlumb.Endpoints.Blank = function(params) { + var self = this; + this.type = "Blank"; + jsPlumb.DOMElementComponent.apply(this, arguments); + this.compute = function() { + return [0,0,10,0]; + }; + + self.canvas = document.createElement("div"); + self.canvas.style.display = "block"; + self.canvas.style.width = "1px"; + self.canvas.style.height = "1px"; + self.canvas.style.background = "transparent"; + self.canvas.style.position = "absolute"; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function() { }; + }; + + /** + * Class: Endpoints.Triangle + * A triangular Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the triangle's base. defaults to 55 pixels. + * height - height of the triangle from base to apex. defaults to 55 pixels. + */ + jsPlumb.Endpoints.Triangle = function(params) { + this.type = "Triangle"; + params = params || { }; + params.width = params.width || 55; + param.height = params.height || 55; + this.width = params.width; + this.height = params.height; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + return [ x, y, width, height ]; + }; + }; +// ********************************* END OF ENDPOINT TYPES ******************************************************************* + + +// ********************************* OVERLAY DEFINITIONS *********************************************************************** + + var AbstractOverlay = function() { + var visible = true, self = this; + this.setVisible = function(val) { + visible = val; + self.connection.repaint(); + }; + this.isVisible = function() { return visible; }; + this.hide = function() { self.setVisible(false); }; + this.show = function() { self.setVisible(true); }; + }; + + + /** + * Class: Overlays.Arrow + * + * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length + * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction + * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line + * across the tail. + */ + /** + * Function: Constructor + * + * Parameters: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + this.type = "Arrow"; + AbstractOverlay.apply(this); + params = params || {}; + var self = this; + + this.length = params.length || 20; + this.width = params.width || 20; + this.id = params.id; + this.connection = params.connection; + var direction = (params.direction || 1) < 0 ? -1 : 1; + var paintStyle = params.paintStyle || { lineWidth:1 }; + this.loc = params.location == null ? 0.5 : params.location; + // how far along the arrow the lines folding back in come to. default is 62.3%. + var foldback = params.foldback || 0.623; + var _getFoldBackPoint = function(connector, loc) { + if (foldback == 0.5) return connector.pointOnPath(loc); + else { + var adj = 0.5 - foldback; // we calculate relative to the center + return connector.pointAlongPathFrom(loc, direction * self.length * adj); + } + }; + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + // this is the arrow head position + var hxy = connector.pointAlongPathFrom(self.loc, direction * (self.length / 2)); + // this is the center of the tail + var txy = connector.pointAlongPathFrom(self.loc, -1 * direction * (self.length / 2)), tx = txy.x, ty = txy.y; + // this is the tail vector + var tail = connector.perpendicularToPathAt(self.loc, self.width, -1 * direction * (self.length / 2)); + // this is the point the tail goes in to + var cxy = _getFoldBackPoint(connector, self.loc); + + // if loc = 1, then hxy should be flush with the element, or if direction == -1, the tail midpoint. + if (self.loc == 1) { + var lxy = connector.pointOnPath(self.loc); + // TODO determine why the 1.2.6 released version does not + // use 'direction' in the two equations below, yet both + // that and 1.3.0 still paint the arrows correctly. + var dx = (lxy.x - hxy.x) * direction, dy = (lxy.y - hxy.y) * direction; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + // if loc = 0, then tail midpoint should be flush with the element, or, if direction == -1, hxy should be. + if (self.loc == 0) { + var lxy = connector.pointOnPath(self.loc); + var tailMid = foldback > 1 ? cxy : { + x:tail[0].x + ((tail[1].x - tail[0].x) / 2), + y:tail[0].y + ((tail[1].y - tail[0].y) / 2) + }; + var dx = (lxy.x - tailMid.x) * direction, dy = (lxy.y - tailMid.y) * direction; + cxy.x += dx; cxy.y += dy; + txy.x += dx; txy.y += dy; + tail[0].x += dx; tail[0].y += dy; + tail[1].x += dx; tail[1].y += dy; + hxy.x += dx; hxy.y += dy; + } + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x); + var maxx = Math.max(hxy.x, tail[0].x, tail[1].x); + var miny = Math.min(hxy.y, tail[0].y, tail[1].y); + var maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + var d = { hxy:hxy, tail:tail, cxy:cxy }, + strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle, + fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle, + lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth; + + self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions); + + return [ minx, maxx, miny, maxy]; + }; + }; + + /** + * Class: Overlays.PlainArrow + * + * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "PlainArrow"; + }; + + /** + * Class: Overlays.Diamond + * + * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40; + var p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "Diamond"; + }; + + + + /** + * Class: Overlays.Label + * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb + * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter + * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it + * puts on the Label's 'style' attribute, so the end result is the same. + */ + /** + * Function: Constructor + * + * Parameters: + * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes + * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle. + * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * labelStyle - (deprecated) js object containing style instructions for the label. defaults to jsPlumb.Defaults.LabelStyle. + * borderWidth - (deprecated) width of a border to paint. defaults to zero. + * borderStyle - (deprecated) strokeStyle to use when painting the border, if necessary. + * + */ + jsPlumb.Overlays.Label = function(params) { + this.type = "Label"; + jsPlumb.DOMElementComponent.apply(this, arguments); + AbstractOverlay.apply(this); + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.labelStyle.font = this.labelStyle.font || "12px sans-serif"; + this.label = params.label || "banana"; + this.connection = params.connection; + this.id = params.id; + var self = this; + var labelWidth = null, labelHeight = null, labelText = null, labelPadding = null; + this.location = params.location || 0.5; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var initialised = false, + labelText = null, + div = document.createElement("div"); + div.style["position"] = "absolute"; + div.style["font"] = self.labelStyle.font; + div.style["color"] = self.labelStyle.color || "black"; + if (self.labelStyle.fillStyle) div.style["background"] = self.labelStyle.fillStyle;//_convertStyle(self.labelStyle.fillStyle, true); + if (self.labelStyle.borderWidth > 0) { + var dStyle = self.labelStyle.borderStyle ? self.labelStyle.borderStyle/*_convertStyle(self.labelStyle.borderStyle, true)*/ : "black"; + div.style["border"] = self.labelStyle.borderWidth + "px solid " + dStyle; + } + if (self.labelStyle.padding) div.style["padding"] = self.labelStyle.padding; + + var clazz = params["_jsPlumb"].overlayClass + " " + + (self.labelStyle.cssClass ? self.labelStyle.cssClass : + params.cssClass ? params.cssClass : ""); + + div.className = clazz; + + jsPlumb.appendElement(div, params.connection.parent); + jsPlumb.getId(div); + self.attachListeners(div, self); + + //override setVisible + var osv = self.setVisible; + self.setVisible = function(state) { + osv(state); // call superclass + div.style.display = state ? "block" : "none"; + }; + + this.paint = function(connector, d, connectorDimensions) { + if (!initialised) { + connector.appendDisplayElement(div); + self.attachListeners(div, connector); + initialised = true; + } + div.style.left = (connectorDimensions[0] + d.minx) + "px"; + div.style.top = (connectorDimensions[1] + d.miny) + "px"; + }; + + this.getTextDimensions = function(connector) { + labelText = typeof self.label == 'function' ? self.label(self) : self.label; + div.innerHTML = labelText.replace(/\r\n/g, "
"); + var de = jsPlumb.CurrentLibrary.getElementObject(div), + s = jsPlumb.CurrentLibrary.getSize(de); + return {width:s[0], height:s[1]}; + }; + + this.computeMaxSize = function(connector) { + var td = self.getTextDimensions(connector); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + var td = self.getTextDimensions(connector); + if (td.width != null) { + var cxy = connector.pointOnPath(self.location); + + var minx = cxy.x - (td.width / 2); + var miny = cxy.y - (td.height / 2); + + self.paint(connector, { + minx:minx, + miny:miny, + td:td, + cxy:cxy + }, connectorDimensions); + + return [minx, minx+td.width, miny, miny+td.height]; + } + else return [0,0,0,0]; + }; + }; + + // ********************************* END OF OVERLAY DEFINITIONS *********************************************************************** + + // ********************************* OVERLAY CANVAS RENDERERS*********************************************************************** + + // ********************************* END OF OVERLAY CANVAS RENDERERS *********************************************************************** +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the VML renderers. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +;(function() { + + // http://ajaxian.com/archives/the-vml-changes-in-ie-8 + // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/ + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + + var vmlAttributeMap = { + "stroke-linejoin":"joinstyle", + "joinstyle":"joinstyle", + "endcap":"endcap", + "miterlimit":"miterlimit" + }; + + if (document.createStyleSheet) { + + // this is the style rule for IE7/6: it uses a CSS class, tidy. + document.createStyleSheet().addRule(".jsplumb_vml", "behavior:url(#default#VML);position:absolute;"); + + // these are for VML in IE8. you have to explicitly call out which elements + // you're going to expect to support VML! + // + // try to avoid IE8. it is recommended you set X-UA-Compatible="IE=7" if you can. + // + document.createStyleSheet().addRule("jsplumb\\:textbox", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:oval", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:rect", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:stroke", "behavior:url(#default#VML);position:absolute;"); + document.createStyleSheet().addRule("jsplumb\\:shape", "behavior:url(#default#VML);position:absolute;"); + + // in this page it is also mentioned that IE requires the extra arg to the namespace + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either. + // var iev = document.documentMode; + //if (!iev || iev < 8) + document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); + //else + // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML"); + } + + var scale = 1000, + _atts = function(o, atts) { + for (var i in atts) { + // IE8 fix: setattribute does not work after an element has been added to the dom! + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + //o.setAttribute(i, atts[i]); + o[i] = atts[i]; + } + }, + _node = function(name, d, atts) { + atts = atts || {}; + var o = document.createElement("jsplumb:" + name); + o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml"; + _pos(o, d); + _atts(o, atts); + return o; + }, + _pos = function(o,d) { + o.style.left = d[0] + "px"; + o.style.top = d[1] + "px"; + o.style.width= d[2] + "px"; + o.style.height= d[3] + "px"; + o.style.position = "absolute"; + }, + _conv = function(v) { + return Math.floor(v * scale); + }, + _convertStyle = function(s, ignoreAlpha) { + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + + return o; + }, + _applyStyles = function(node, style, component) { + var styleToWrite = {}; + if (style.strokeStyle) { + styleToWrite["stroked"] = "true"; + styleToWrite["strokecolor"] =_convertStyle(style.strokeStyle, true); + styleToWrite["strokeweight"] = style.lineWidth + "px"; + } + else styleToWrite["stroked"] = "false"; + + if (style.fillStyle) { + styleToWrite["filled"] = "true"; + styleToWrite["fillcolor"] = _convertStyle(style.fillStyle, true); + } + else styleToWrite["filled"] = "false"; + + if(style["dashstyle"]) { + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = style["dashstyle"]; + } + else if (style["stroke-dasharray"] && style["lineWidth"]) { + var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",", + parts = style["stroke-dasharray"].split(sep), + styleToUse = ""; + for(var i = 0; i < parts.length; i++) { + styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep); + } + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }); + node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = styleToUse; + } + + _atts(node, styleToWrite); + }, + /* + * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. + */ + VmlComponent = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }, + /* + * Base class for Vml connectors. extends VmlComponent. + */ + VmlConnector = function(params) { + var self = this; + self.strokeNode = null; + self.canvas = null; + VmlComponent.apply(this, arguments); + clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : ""); + this.paint = function(d, style, anchor) { + if (style != null) { + var path = self.getPath(d), p = { "path":path }; + + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + outlineStyle = { + strokeStyle:_convertStyle(style.outlineColor), + lineWidth:outlineStrokeWidth + }; + + if (self.bgCanvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.bgCanvas = _node("shape", d, p); + jsPlumb.appendElement(self.bgCanvas, params.parent); + _pos(self.bgCanvas, d); + displayElements.push(self.bgCanvas); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.bgCanvas, d); + _atts(self.bgCanvas, p); + } + + _applyStyles(self.bgCanvas, outlineStyle, self); + } + + if (self.canvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.canvas = _node("shape", d, p); + jsPlumb.appendElement(self.canvas, params.parent); + displayElements.push(self.canvas); + + self.attachListeners(self.canvas, self); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.canvas, d); + _atts(self.canvas, p); + } + + _applyStyles(self.canvas, style, self); + } + }; + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + self.canvas.parentNode.appendChild(el); + displayElements.push(el); + }; + }, + /* + * + * Base class for Vml Endpoints. extends VmlComponent. + * + */ + VmlEndpoint = function(params) { + VmlComponent.apply(this, arguments); + var vml = null, self = this; + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function(d, style, anchor) { + var p = { }; + + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (vml == null) { + p["class"] = jsPlumb.endpointClass; + vml = self.getVml([0,0, d[2], d[3]], p, anchor); + self.canvas.appendChild(vml); + self.attachListeners(vml, self); + } + else { + //p["coordsize"] = "1,1";//(d[2] * scale) + "," + (d[3] * scale); again, unsure. + _pos(vml, [0,0, d[2], d[3]]); + _atts(vml, p); + } + + _applyStyles(vml, style); + }; + }; + + jsPlumb.Connectors.vml.Bezier = function() { + jsPlumb.Connectors.Bezier.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Straight = function() { + jsPlumb.Connectors.Straight.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Flowchart = function() { + jsPlumb.Connectors.Flowchart.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l"; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]); + } + // finally draw a line to the end + p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e"; + return p; + }; + }; + + jsPlumb.Endpoints.vml.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("oval", d, atts); }; + }; + + jsPlumb.Endpoints.vml.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor) { return _node("rect", d, atts); }; + }; + + /* + * VML Image Endpoint is the same as the default image endpoint. + */ + jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image; + + /** + * placeholder for Blank endpoint in vml renderer. + */ + jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank; + + /** + * VML Label renderer. uses the default label renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label; + + var AbstractVmlArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + VmlComponent.apply(this, arguments); + var self = this, canvas = null, path =null; + var getPath = function(d, connectorDimensions) { + return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) + + " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + + " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + + " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + + " x e"; + }; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) { + var p = {}; + if (strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] =_convertStyle(strokeStyle, true); + } + if (lineWidth) p["strokeweight"] = lineWidth + "px"; + if (fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = fillStyle; + } + var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + w = Math.abs(xmax - xmin), + h = Math.abs(ymax - ymin), + dim = [xmin, ymin, w, h]; + + // for VML, we create overlays using shapes that have the same dimensions and + // coordsize as their connector - overlays calculate themselves relative to the + // connector (it's how it's been done since the original canvas implementation, because + // for canvas that makes sense). + p["path"] = getPath(d, connectorDimensions); + p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale); + + dim[0] = connectorDimensions[0]; + dim[1] = connectorDimensions[1]; + dim[2] = connectorDimensions[2]; + dim[3] = connectorDimensions[3]; + + if (canvas == null) { + //p["class"] = jsPlumb.overlayClass; // TODO currentInstance? + canvas = _node("shape", dim, p); + connector.appendDisplayElement(canvas); + self.attachListeners(canvas, connector); + } + else { + _pos(canvas, dim); + _atts(canvas, p); + } + }; + }; + + jsPlumb.Overlays.vml.Arrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.vml.PlainArrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.vml.Diamond = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the SVG renderers. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +/** + * SVG support for jsPlumb. + * + * things to investigate: + * + * gradients: https://developer.mozilla.org/en/svg_in_html_introduction + * css:http://tutorials.jenkov.com/svg/svg-and-css.html + * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath + * pointer events: https://developer.mozilla.org/en/css/pointer-events + * + */ +;(function() { + + var svgAttributeMap = { + "stroke-linejoin":"stroke-linejoin", + "joinstyle":"stroke-linejoin", + "stroke-dashoffset":"stroke-dashoffset" + }; + + var ns = { + svg:"http://www.w3.org/2000/svg", + xhtml:"http://www.w3.org/1999/xhtml" + }, + _attr = function(node, attributes) { + for (var i in attributes) + node.setAttribute(i, "" + attributes[i]); + }, + _node = function(name, attributes) { + var n = document.createElementNS(ns.svg, name); + attributes = attributes || {}; + attributes["version"] = "1.1"; + attributes["xmnls"] = ns.xhtml; + _attr(n, attributes); + return n; + }, + _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; }, + _convertStyle = function(s, ignoreAlpha) { + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + + return o; + }, + _clearGradient = function(parent) { + for (var i = 0; i < parent.childNodes.length; i++) { + if (parent.childNodes[i].tagName == "linearGradient" || parent.childNodes[i].tagName == "radialGradient") + parent.removeChild(parent.childNodes[i]); + } + }, + _updateGradient = function(parent, node, style, dimensions) { + var id = "jsplumb_gradient_" + (new Date()).getTime(); + // first clear out any existing gradient + _clearGradient(parent); + // this checks for an 'offset' property in the gradient, and in the absence of it, assumes + // we want a linear gradient. if it's there, we create a radial gradient. + // it is possible that a more explicit means of defining the gradient type would be + // better. relying on 'offset' means that we can never have a radial gradient that uses + // some default offset, for instance. + if (!style.gradient.offset) { + var g = _node("linearGradient", {id:id}); + parent.appendChild(g); + } + else { + var g = _node("radialGradient", { + id:id + }); + parent.appendChild(g); + } + + // the svg radial gradient seems to treat stops in the reverse + // order to how canvas does it. so we want to keep all the maths the same, but + // iterate the actual style declarations in reverse order, if the x indexes are not in order. + for (var i = 0; i < style.gradient.stops.length; i++) { + // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of + // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments. + // so, not too concerned about leaving this in for now. + var styleToUse = i; + if (dimensions.length == 8) + styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i; + else + styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i; + var stopColor = _convertStyle(style.gradient.stops[styleToUse][1], true); + var s = _node("stop", {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor}); + g.appendChild(s); + } + var applyGradientTo = style.strokeStyle ? "stroke" : "fill"; + node.setAttribute("style", applyGradientTo + ":url(#" + id + ")"); + }, + _applyStyles = function(parent, node, style, dimensions) { + + if (style.gradient) { + _updateGradient(parent, node, style, dimensions); + } + else { + // make sure we clear any existing gradient + _clearGradient(parent); + node.setAttribute("style", ""); + } + + node.setAttribute("fill", style.fillStyle ? _convertStyle(style.fillStyle, true) : "none"); + node.setAttribute("stroke", style.strokeStyle ? _convertStyle(style.strokeStyle, true) : "none"); + if (style.lineWidth) { + node.setAttribute("stroke-width", style.lineWidth); + } + + // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like + // the syntax in VML but is actually kind of nasty: values are given in the pixel + // coordinate space, whereas in VML they are multiples of the width of the stroked + // line, which makes a lot more sense. for that reason, jsPlumb is supporting both + // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from + // VML, which will be the preferred method. the code below this converts a dashstyle + // attribute given in terms of stroke width into a pixel representation, by using the + // stroke's lineWidth. + if(style["stroke-dasharray"]) { + node.setAttribute("stroke-dasharray", style["stroke-dasharray"]); + } + if (style["dashstyle"] && style["lineWidth"]) { + var sep = style["dashstyle"].indexOf(",") == -1 ? " " : ",", + parts = style["dashstyle"].split(sep), + styleToUse = ""; + parts.forEach(function(p) { + styleToUse += (Math.floor(p * style.lineWidth) + sep); + }); + node.setAttribute("stroke-dasharray", styleToUse); + } + + // extra attributes such as join type, dash offset. + for (var i in svgAttributeMap) { + if (style[i]) { + node.setAttribute(svgAttributeMap[i], style[i]); + } + } + }, + _decodeFont = function(f) { + var r = /([0-9].)(p[xt])\s(.*)/; + var bits = f.match(r); + return {size:bits[1] + bits[2], font:bits[3]}; + }; + + /* + * Base class for SVG components. + */ + var SvgComponent = function(cssClass, originalArgs, pointerEventsSpec) { + var self = this; + pointerEventsSpec = pointerEventsSpec || "all"; + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + self.canvas = null, self.path = null, self.svg = null; + + this.setHover = function() { }; + + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.sizeCanvas(self.canvas,0,0,1,1); + + var clazz = cssClass + " " + (originalArgs[0].cssClass || ""); + self.canvas.className = clazz; + + self.svg = _node("svg", { + "style":"", + "width":0, + "height":0, + "pointer-events":pointerEventsSpec/*, + "class": clazz*/ + }); + + jsPlumb.appendElement(self.canvas, originalArgs[0]["parent"]); + self.canvas.appendChild(self.svg); + + // TODO this displayElement stuff is common between all components, across all + // renderers. would be best moved to jsPlumbUIComponent. + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + this.paint = function(d, style, anchor) { + if (style != null) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + _attr(self.svg, { + "style":_pos([0,0,d[2], d[3]]), + "width": d[2], + "height": d[3] + }); + self._paint.apply(this, arguments); + } + }; + }; + + /* + * Base class for SVG connectors. + */ + var SvgConnector = function(params) { + var self = this; + SvgComponent.apply(this, [ params["_jsPlumb"].connectorClass, arguments, "none" ]); + this._paint = function(d, style) { + var p = self.getPath(d), a = { "d":p }, outlineStyle = null; + a["pointer-events"] = "all"; + + // outline style. actually means drawing an svg object underneath the main one. + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + outlineStyle = { + strokeStyle:_convertStyle(style.outlineColor), + lineWidth:outlineStrokeWidth + }; + + if (self.bgPath == null) { + self.bgPath = _node("path", a); + self.svg.appendChild(self.bgPath); + self.attachListeners(self.bgPath, self); + } + else { + _attr(self.bgPath, a); + } + + _applyStyles(self.svg, self.bgPath, outlineStyle, d); + } + + + if (self.path == null) { + self.path = _node("path", a); + self.svg.appendChild(self.path); + self.attachListeners(self.path, self); + } + else { + _attr(self.path, a); + } + + _applyStyles(self.svg, self.path, style, d); + }; + }; + + /* + * SVG Bezier Connector + */ + jsPlumb.Connectors.svg.Bezier = function(params) { + jsPlumb.Connectors.Bezier.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]; }; + }; + + /* + * SVG straight line Connector + */ + jsPlumb.Connectors.svg.Straight = function(params) { + jsPlumb.Connectors.Straight.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; }; + }; + + jsPlumb.Connectors.svg.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "M " + dimensions[4] + "," + dimensions[5]; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)]; + } + // finally draw a line to the end + p = p + " " + dimensions[6] + "," + dimensions[7]; + return p; + }; + }; + + /* + * Base class for SVG endpoints. + */ + var SvgEndpoint = function(params) { + var self = this; + SvgComponent.apply(this, [ params["_jsPlumb"].endpointClass, arguments, "all" ]); + this._paint = function(d, style) { + var s = jsPlumb.extend({}, style); + if (s.outlineColor) { + s.strokeWidth = s.outlineWidth; + s.strokeStyle = _convertStyle(s.outlineColor, true); + } + + if (self.node == null) { + self.node = self.makeNode(d, s); + self.svg.appendChild(self.node); + self.attachListeners(self.node, self); + } + _applyStyles(self.svg, self.node, s, d); + _pos(self.node, d); + }; + }; + + /* + * SVG Dot Endpoint + */ + jsPlumb.Endpoints.svg.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("circle", { + "cx" : d[2] / 2, + "cy" : d[3] / 2, + "r" : d[2] / 2 + }); + }; + }; + + /* + * SVG Rectangle Endpoint + */ + jsPlumb.Endpoints.svg.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("rect", { + "width":d[2], + "height":d[3] + }); + }; + }; + + /* + * SVG Image Endpoint is the default image endpoint. + */ + jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image; + /* + * Blank endpoint in svg renderer is the default Blank endpoint. + */ + jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank; + /* + * Label endpoint in svg renderer is the default Label endpoint. + */ + jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label; + + + var AbstractSvgArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + var self = this, path =null; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + } + + _attr(path, { + "d" : makePath(d), + stroke : strokeStyle ? strokeStyle : null, + fill : fillStyle ? fillStyle : null + }); + }; + var makePath = function(d) { + return "M" + d.hxy.x + "," + d.hxy.y + + " L" + d.tail[0].x + "," + d.tail[0].y + + " L" + d.cxy.x + "," + d.cxy.y + + " L" + d.tail[1].x + "," + d.tail[1].y + + " L" + d.hxy.x + "," + d.hxy.y; + }; + }; + + jsPlumb.Overlays.svg.Arrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.svg.PlainArrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.svg.Diamond = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the HTML5 canvas renderers. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +;(function() { + +// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS ******************************************************************* + + // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. + var _connectionBeingDragged = null, + _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }, + _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }, + _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }, + _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, + _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }, + _setOffset = function(el, o) { jsPlumb.CurrentLibrary.setOffset(el, o); }; + + /* + * Class:CanvasMouseAdapter + * Provides support for mouse events on canvases. + */ + var CanvasMouseAdapter = function() { + var self = this; + self.overlayPlacements = []; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + jsPlumb.EventGenerator.apply(this, arguments); + /** + * returns whether or not the given event is ojver a painted area of the canvas. + */ + this._over = function(e) { + var o = _getOffset(_getElementObject(self.canvas)), + pageXY = _pageXY(e), + x = pageXY[0] - o.left, y = pageXY[1] - o.top; + if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + return false; + }; + + var _mouseover = false; + var _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false; + var _nullSafeHasClass = function(el, clazz) { + return el != null && _hasClass(el, clazz); + }; + this.mousemove = function(e) { + var pageXY = _pageXY(e), clientXY = _clientXY(e), + ee = document.elementFromPoint(clientXY[0], clientXY[1]), + eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); + var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); + if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.fire("mouseenter", self, e); + return true; + } + // TODO here there is a remote chance that the overlay the mouse moved onto + // is actually not an overlay for the current component. a more thorough check would + // be to ensure the overlay belonged to the current component. + else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { + _mouseover = false; + self.fire("mouseexit", self, e); + } + self.fire("mousemove", self, e); + }; + + this.click = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); + self.fire("mousedown", self, e); + } + }; + + this.mouseup = function(e) { + //if (self == _connectionBeingDragged) _connectionBeingDragged = null; + _mouseDown = false; + self.fire("mouseup", self, e); + }; + }; + + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + jsPlumb.appendElement(canvas, params.parent); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + params["_jsPlumb"].getId(canvas, params.uuid); + + return canvas; + }; + + /** + * Class:CanvasConnector + * Superclass for Canvas Connector renderers. + */ + var CanvasConnector = jsPlumb.CanvasConnector = function(params) { + + CanvasMouseAdapter.apply(this, arguments); + + var _paintOneStyle = function(dim, aStyle) { + self.ctx.save(); + jsPlumb.extend(self.ctx, aStyle); + if (aStyle.gradient) { + var g = self.createGradient(dim, self.ctx); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + self.ctx.strokeStyle = g; + } + self._paint(dim); + self.ctx.restore(); + }; + + var self = this, + clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent + }); + self.ctx = self.canvas.getContext("2d"); + + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + self.paint = function(dim, style) { + if (style != null) { + + jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); + + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + _paintOneStyle(dim, outlineStyle); + } + _paintOneStyle(dim, style); + } + }; + }; + + /** + * Class:CanvasEndpoint + * Superclass for Canvas Endpoint renderers. + */ + var CanvasEndpoint = function(params) { + var self = this; + CanvasMouseAdapter.apply(this, arguments); + var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent + }); + self.ctx = self.canvas.getContext("2d"); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + // _paintOneStyle(d, outlineStyle); + } + + self._paint.apply(this, arguments); + }; + }; + + jsPlumb.Endpoints.canvas.Dot = function(params) { + var self = this; + jsPlumb.Endpoints.Dot.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }; + var calculateAdjustments = function(gradient) { + var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius; + gradient.offset && (offsetAdjustment = parseValue(gradient.offset)); + gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius)); + return [offsetAdjustment, innerRadius]; + }; + this._paint = function(d, style, anchor) { + if (style != null) { + var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(); + jsPlumb.extend(ctx, style); + if (style.gradient) { + var adjustments = calculateAdjustments(style.gradient), + yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0], + xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0], + g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + ctx.beginPath(); + ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + } + }; + }; + + jsPlumb.Endpoints.canvas.Rectangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) { + + var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(); + jsPlumb.extend(ctx, style); + + /* canvas gradient */ + if (style.gradient) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, d[2], d[3]); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + jsPlumb.Endpoints.canvas.Triangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Triangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) + { + var width = d[2], height = d[3], x = d[0], y = d[1]; + + var ctx = self.canvas.getContext('2d'); + var offsetX = 0, offsetY = 0, angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = style.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + /* + * Canvas Image Endpoint: uses the default version, which creates an tag. + */ + jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image; + + /* + * Blank endpoint in all renderers is just the default Blank endpoint. + */ + jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank; + + /* + * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Bezier = function() { + var self = this; + jsPlumb.Connectors.Bezier.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO i doubt this handles the case that source and target are swapped. + this.createGradient = function(dim, ctx, swap) { + return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + /* + * Canvas straight line Connector. Draws a straight line onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Straight = function() { + var self = this; + jsPlumb.Connectors.Straight.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO this does not handle the case that src and target are swapped. + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + jsPlumb.Connectors.canvas.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + +// ********************************* END OF CANVAS RENDERERS ******************************************************************* + + jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label; + + /** + * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. + */ + var CanvasOverlay = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }; + + var AbstractCanvasArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + CanvasOverlay.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + var ctx = connector.ctx; + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(d.hxy.x, d.hxy.y); + ctx.lineTo(d.tail[0].x, d.tail[0].y); + ctx.lineTo(d.cxy.x, d.cxy.y); + ctx.lineTo(d.tail[1].x, d.tail[1].y); + ctx.lineTo(d.hxy.x, d.hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + }; + + jsPlumb.Overlays.canvas.Arrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.canvas.PlainArrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.canvas.Diamond = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})();/* + * jsPlumb + * + * Title:jsPlumb 1.3.2 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the YUI3 adapter. + * + * Copyright (c) 2010 - 2011 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://code.google.com/p/jsplumb + * + * Triple licensed under the MIT, GPL2 and Beer licenses. + */ + +/** + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + */ +(function() { + + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function( v, b, s ) { + for( var i = +b || 0, l = this.length; i < l; i++ ) { + if( this[i]===v || s && this[i]==v ) { return i; } + } + return -1; + }; + } + + var Y; + + YUI().use('node', 'dd', 'anim', function(_Y) { + Y = _Y; + Y.on("domready", function() { jsPlumb.init(); }); + }); + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }, + ddEvents = [ "drag:mouseDown", "drag:afterMouseDown", "drag:mouseup", + "drag:align", "drag:removeHandle", "drag:addHandle", "drag:removeInvalid", "drag:addInvalid", + "drag:start", "drag:end", "drag:drag", "drag:over", "drag:enter", + "drag:exit", "drag:drophit", "drag:dropmiss", "drop:over", "drop:enter", "drop:exit", "drop:hit" + ], + animEvents = [ "tween" ], + /** + * helper function to curry callbacks for some element. + */ + _wrapper = function(fn) { + return function() { + try { + fn.apply(this, arguments); + } + catch (e) { + console.log("wrap fail", e); + } + }; + }, + /** + * extracts options from the given options object, leaving out event handlers. + */ + _getDDOptions = function(options) { + var o = {}; + for (var i in options) if (ddEvents.indexOf(i) == -1) o[i] = options[i]; + return o; + }, + /** + * attaches all event handlers found in options to the given dragdrop object, and registering + * the given el as the element of interest. + */ + _attachListeners = function(dd, options, eventList) { + for (var ev in options) { + if (eventList.indexOf(ev) != -1) { + var w = _wrapper(options[ev]); + dd.on(ev, w); + } + } + }, + _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}, + _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }, + _lastDragObject = null, + _extend = function(o1, o2) { + for (var i in o2) + o1[i] = o2[i]; + return o1; + }, + _getAttribute = function(el, attributeId) { + return el.getAttribute(attributeId); + }, + _getElementObject = function(el) { + return typeof el == 'string' ? Y.one('#' + el) : Y.one(el); + }; + + jsPlumb.CurrentLibrary = { + + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + var o = _extend({node:el, to:properties}, options); + var id = _getAttribute(el, "id"); + o["tween"] = jsPlumb.wrap(properties["tween"], function() { + jsPlumb.repaint(id); + }); + var a = new Y.Anim(o); + _attachListeners(a, o, animEvents); + a.run(); + }, + + appendElement : function(child, parent) { + _getElementObject (parent).append(child); + }, + + /** + * event binding wrapper. + */ + bind : function(el, event, callback) { + _getElementObject(el).on(event, callback); + }, + + dragEvents : { + "start":"drag:start", "stop":"drag:end", "drag":"drag:drag", "step":"step", + "over":"drop:enter", "out":"drop:exit", "drop":"drop:hit" + }, + + extend : _extend, + + getAttribute : _getAttribute, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + // this is a workaround for the unfortunate fact that in YUI3, the 'drop:exit' event does + // not contain a reference to the drag that just exited. single-threaded js to the + // rescue: we'll just keep it for ourselves. + if (eventArgs[0].drag) _lastDragObject = eventArgs[0].drag.el; + return _lastDragObject; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + return dd.scope; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getElementObject : _getElementObject, + + getOffset : function(el) { + var o = Y.DOM.getXY(el._node); + return {left:o[0], top:o[1]}; + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).get("parentNode"); + }, + + getScrollLeft : function(el) { + return 0; + }, + + getScrollTop : function(el) { + return 0; + }, + + getSize : function(el) { + return [ el._node.offsetWidth, el._node.offsetHeight ]; + }, + + getUIPosition : function(args) { + var n = args[0].currentTarget.el._node, + o = Y.DOM.getXY(n); + return {left:o[0], top:o[1]}; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options) { + var _opts = _getDDOptions(options); + var id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drag(_opts); + dd.el = el; + + var scope = options['scope'] || jsPlumb.Defaults.Scope; + dd.scope = scope; + + _draggablesById[id] = dd; + _add(_draggablesByScope, scope, dd); + + _attachListeners(dd, options, ddEvents); + }, + + initDroppable : function(el, options) { + var _opts = _getDDOptions(options); + var id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drop(_opts); + + _droppableOptions[id] = options; + + options = _extend({}, options); + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _droppableScopesById[id] = scope; + + options["drop:enter"] = jsPlumb.wrap(options["drop:enter"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, true); + }, true); + options["drop:exit"] = jsPlumb.wrap(options["drop:exit"], function(e) { + _checkHover(el, false); + }); + options["drop:hit"] = jsPlumb.wrap(options["drop:hit"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, false); + }, true); + + _attachListeners(dd, options, ddEvents); + }, + + isAlreadyDraggable : function(el) { + el = _getElementObject(el); + return el.hasClass("yui3-dd-draggable"); + }, + + isDragSupported : function(el) { return true; }, + isDropSupported : function(el) { return true; }, + removeClass : function(el, clazz) { el.removeClass(clazz); }, + removeElement : function(el) { _getElementObject(el).remove(); }, + + setAttribute : function(el, attributeName, attributeValue) { + el.setAttribute(attributeName, attributeValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + if (dd) dd.set("lock", !draggable); + }, + + setDragScope : function(el, scope) { + var id = jsPlumb.getId(el); + var dd = _draggablesById[id]; + if (dd) dd.scope = scope; + }, + + setOffset : function(el, o) { + el = _getElementObject(el); + el.set("top", o.top); + el.set("left", o.left); + } + }; +})();(function(){if(typeof Math.sgn=="undefined")Math.sgn=function(a){return a==0?0:a>0?1:-1};var p={subtract:function(a,b){return{x:a.x-b.x,y:a.y-b.y}},dotProduct:function(a,b){return a.x*b.x+a.y*b.y},square:function(a){return Math.sqrt(a.x*a.x+a.y*a.y)},scale:function(a,b){return{x:a.x*b,y:a.y*b}}},y=Math.pow(2,-65),u=function(a,b){for(var g=[],d=b.length-1,h=2*d-1,f=[],c=[],l=[],k=[],i=[[1,0.6,0.3,0.1],[0.4,0.6,0.6,0.4],[0.1,0.3,0.6,1]],e=0;e<=d;e++)f[e]=p.subtract(b[e],a);for(e=0;e<=d-1;e++){c[e]= +p.subtract(b[e+1],b[e]);c[e]=p.scale(c[e],3)}for(e=0;e<=d-1;e++)for(var m=0;m<=d;m++){l[e]||(l[e]=[]);l[e][m]=p.dotProduct(c[e],f[m])}for(e=0;e<=h;e++){k[e]||(k[e]=[]);k[e].y=0;k[e].x=parseFloat(e)/h}h=d-1;for(f=0;f<=d+h;f++){c=Math.min(f,d);for(e=Math.max(0,f-h);e<=c;e++){j=f-e;k[e+j].y+=l[j][e]*i[j][e]}}d=b.length-1;k=s(k,2*d-1,g,0);h=p.subtract(a,b[0]);l=p.square(h);for(e=i=0;e=64){g[0]=(a[0].x+a[b].x)/2;return 1}var n,o,q;k=a[0].y-a[b].y;i=a[b].x-a[0].x;e=a[0].x*a[b].y-a[b].x*a[0].y;m=max_distance_below=0;for(o=1;om)m=q;else if(q0?1:-1,c=null;h 0 ? e[0].tagName : null; + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + + // this code is a workaround for the case that the element being dragged has a margin set on it. jquery UI passes + // in the wrong offset if the element has a margin (it doesn't take the margin into account). the getBoundingClientRect + // method, which is in pretty much all browsers now, reports the right numbers. but it introduces a noticeable lag, which + // i don't like. + + /*if ( getBoundingClientRectSupported ) { + var r = eventArgs[1].helper[0].getBoundingClientRect(); + return { left : r.left, top: r.top }; + } else {*/ + if (eventArgs.length == 1) { + ret = { left: eventArgs[0].pageX, top:eventArgs[0].pageY }; + } + else { + var ui = eventArgs[1], _offset = ui.offset; + ret = _offset || ui.absolutePosition; + } + return ret; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options, isPlumbedComponent) { + options = options || {}; + // remove helper directive if present. + options.helper = null; + if (isPlumbedComponent) + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.draggable(options); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.droppable(options); + }, + + isAlreadyDraggable : function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return el.hasClass("ui-draggable"); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el[0].className.constructor == SVGAnimatedString) { + jsPlumb.util.svg.removeClass(el[0], clazz); + } + } + catch (e) { + // SVGAnimatedString not supported; no problem. + } + el.removeClass(clazz); + }, + + removeElement : function(element, parent) { + jsPlumb.CurrentLibrary.getElementObject(element).remove(); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * sets the drag scope. probably time for a setDragOption method (roll this and the one above together) + * @param el + * @param scope + */ + setDragScope : function(el, scope) { + el.draggable("option", "scope", scope); + }, + + setOffset : function(el, o) { + jsPlumb.CurrentLibrary.getElementObject(el).offset(o); + }, + + /** + * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself. + * the other libraries do not. yui, in fact, cannot even pass an original event. we have to pull out stuff + * from the originalEvent to put in an options object for YUI. + * @param el + * @param event + * @param originalEvent + */ + trigger : function(el, event, originalEvent) { + //originalEvent.stopPropagation(); + //jsPlumb.CurrentLibrary.getElementObject(el).trigger(originalEvent); + var h = jQuery._data(jsPlumb.CurrentLibrary.getElementObject(el)[0], "handle"); + h(originalEvent); + //originalEvent.stopPropagation(); + }, + + /** + * event unbinding wrapper. it just so happens that jQuery uses 'unbind' also. yui3, for example, + * uses..something else. + */ + unbind : function(el, event, callback) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + el.unbind(event, callback); + } + }; + + $(document).ready(jsPlumb.init); + +})(jQuery); + diff --git a/build/1.3.9/js/jquery.jsPlumb-1.3.9-all-min.js b/archive/1.3.9/js/jquery.jsPlumb-1.3.9-all-min.js similarity index 100% rename from build/1.3.9/js/jquery.jsPlumb-1.3.9-all-min.js rename to archive/1.3.9/js/jquery.jsPlumb-1.3.9-all-min.js diff --git a/build/1.3.9/js/jquery.jsPlumb-1.3.9-all.js b/archive/1.3.9/js/jquery.jsPlumb-1.3.9-all.js similarity index 100% rename from build/1.3.9/js/jquery.jsPlumb-1.3.9-all.js rename to archive/1.3.9/js/jquery.jsPlumb-1.3.9-all.js diff --git a/build/1.3.9/js/jsPlumb-1.3.9-tests.js b/archive/1.3.9/js/jsPlumb-1.3.9-tests.js similarity index 100% rename from build/1.3.9/js/jsPlumb-1.3.9-tests.js rename to archive/1.3.9/js/jsPlumb-1.3.9-tests.js diff --git a/build/1.3.9/js/lib/qunit.js b/archive/1.3.9/js/lib/qunit.js similarity index 100% rename from build/1.3.9/js/lib/qunit.js rename to archive/1.3.9/js/lib/qunit.js diff --git a/build/1.3.9/js/mootools.jsPlumb-1.3.9-all-min.js b/archive/1.3.9/js/mootools.jsPlumb-1.3.9-all-min.js similarity index 100% rename from build/1.3.9/js/mootools.jsPlumb-1.3.9-all-min.js rename to archive/1.3.9/js/mootools.jsPlumb-1.3.9-all-min.js diff --git a/build/1.3.9/js/mootools.jsPlumb-1.3.9-all.js b/archive/1.3.9/js/mootools.jsPlumb-1.3.9-all.js similarity index 100% rename from build/1.3.9/js/mootools.jsPlumb-1.3.9-all.js rename to archive/1.3.9/js/mootools.jsPlumb-1.3.9-all.js diff --git a/build/1.3.9/js/yui.jsPlumb-1.3.9-all-min.js b/archive/1.3.9/js/yui.jsPlumb-1.3.9-all-min.js similarity index 100% rename from build/1.3.9/js/yui.jsPlumb-1.3.9-all-min.js rename to archive/1.3.9/js/yui.jsPlumb-1.3.9-all-min.js diff --git a/build/1.3.9/js/yui.jsPlumb-1.3.9-all.js b/archive/1.3.9/js/yui.jsPlumb-1.3.9-all.js similarity index 100% rename from build/1.3.9/js/yui.jsPlumb-1.3.9-all.js rename to archive/1.3.9/js/yui.jsPlumb-1.3.9-all.js diff --git a/archive/1.3.9/jsPlumb-1.3.9-RC1.js b/archive/1.3.9/jsPlumb-1.3.9-RC1.js new file mode 100644 index 000000000..04a5079a7 --- /dev/null +++ b/archive/1.3.9/jsPlumb-1.3.9-RC1.js @@ -0,0 +1,5630 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.9 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the jsPlumb core code. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + /** + * Class:jsPlumb + * The jsPlumb engine, registered as a static object in the window. This object contains all of the methods you will use to + * create and maintain Connections and Endpoints. + */ + + var canvasAvailable = !!document.createElement('canvas').getContext, + svgAvailable = !!window.SVGAngle || document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"), + // http://stackoverflow.com/questions/654112/how-do-you-detect-support-for-vml-or-svg-in-a-browser + vmlAvailable = function() { + if(vmlAvailable.vml == undefined) { + var a = document.body.appendChild(document.createElement('div')); + a.innerHTML = ''; + var b = a.firstChild; + b.style.behavior = "url(#default#VML)"; + vmlAvailable.vml = b ? typeof b.adj == "object": true; + a.parentNode.removeChild(a); + } + return vmlAvailable.vml; + }; + + var _findWithFunction = jsPlumbUtil.findWithFunction, + _indexOf = jsPlumbUtil.indexOf, + _removeWithFunction = jsPlumbUtil.removeWithFunction, + _remove = jsPlumbUtil.remove, + // TODO support insert index + _addWithFunction = jsPlumbUtil.addWithFunction, + _addToList = jsPlumbUtil.addToList, + /** + an isArray function that even works across iframes...see here: + + http://tobyho.com/2011/01/28/checking-types-in-javascript/ + + i was originally using "a.constructor == Array" as a test. + */ + _isArray = jsPlumbUtil.isArray, + _isString = jsPlumbUtil.isString, + _isObject = jsPlumbUtil.isObject; + + // for those browsers that dont have it. they still don't have it! but at least they won't crash. + if (!window.console) + window.console = { time:function(){}, timeEnd:function(){}, group:function(){}, groupEnd:function(){}, log:function(){} }; + + var _connectionBeingDragged = null, + _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); }, + _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); }, + _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); }, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); }, + _log = jsPlumbUtil.log, + _group = jsPlumbUtil.group, + _groupEnd = jsPlumbUtil.groupEnd, + _time = jsPlumbUtil.time, + _timeEnd = jsPlumbUtil.timeEnd, + + /** + * creates a timestamp, using milliseconds since 1970, but as a string. + */ + _timestamp = function() { return "" + (new Date()).getTime(); }, + + /* + * Class:jsPlumbUIComponent + * Abstract superclass for UI components Endpoint and Connection. Provides the abstraction of paintStyle/hoverPaintStyle, + * and also extends jsPlumbUtil.EventGenerator to provide the bind and fire methods. + */ + jsPlumbUIComponent = function(params) { + var self = this, + a = arguments, + _hover = false, + parameters = params.parameters || {}, + idPrefix = self.idPrefix, + id = idPrefix + (new Date()).getTime(), + paintStyle = null, + hoverPaintStyle = null; + + self._jsPlumb = params["_jsPlumb"]; + self.getId = function() { return id; }; + self.tooltip = params.tooltip; + self.hoverClass = params.hoverClass || self._jsPlumb.Defaults.HoverClass || jsPlumb.Defaults.HoverClass; + + // all components can generate events + jsPlumbUtil.EventGenerator.apply(this); + // all components get this clone function. + // TODO issue 116 showed a problem with this - it seems 'a' that is in + // the clone function's scope is shared by all invocations of it, the classic + // JS closure problem. for now, jsPlumb does a version of this inline where + // it used to call clone. but it would be nice to find some time to look + // further at this. + this.clone = function() { + var o = new Object(); + self.constructor.apply(o, a); + return o; + }; + + this.getParameter = function(name) { return parameters[name]; }, + this.getParameters = function() { + return parameters; + }, + this.setParameter = function(name, value) { parameters[name] = value; }, + this.setParameters = function(p) { parameters = p; }, + this.overlayPlacements = []; + + // user can supply a beforeDetach callback, which will be executed before a detach + // is performed; returning false prevents the detach. + var beforeDetach = params.beforeDetach; + this.isDetachAllowed = function(connection) { + var r = self._jsPlumb.checkCondition("beforeDetach", connection ); + if (beforeDetach) { + try { + r = beforeDetach(connection); + } + catch (e) { _log("jsPlumb: beforeDetach callback failed", e); } + } + return r; + }; + + // user can supply a beforeDrop callback, which will be executed before a dropped + // connection is confirmed. user can return false to reject connection. + var beforeDrop = params.beforeDrop; + this.isDropAllowed = function(sourceId, targetId, scope, connection, dropEndpoint) { + var r = self._jsPlumb.checkCondition("beforeDrop", { + sourceId:sourceId, + targetId:targetId, + scope:scope, + connection:connection, + dropEndpoint:dropEndpoint + }); + if (beforeDrop) { + try { + r = beforeDrop({ + sourceId:sourceId, + targetId:targetId, + scope:scope, + connection:connection, + dropEndpoint:dropEndpoint + }); + } + catch (e) { _log("jsPlumb: beforeDrop callback failed", e); } + } + return r; + }; + + // helper method to update the hover style whenever it, or paintStyle, changes. + // we use paintStyle as the foundation and merge hoverPaintStyle over the + // top. + var _updateHoverStyle = function() { + if (paintStyle && hoverPaintStyle) { + var mergedHoverStyle = {}; + jsPlumb.extend(mergedHoverStyle, paintStyle); + jsPlumb.extend(mergedHoverStyle, hoverPaintStyle); + delete self["hoverPaintStyle"]; + // we want the fillStyle of paintStyle to override a gradient, if possible. + if (mergedHoverStyle.gradient && paintStyle.fillStyle) + delete mergedHoverStyle["gradient"]; + hoverPaintStyle = mergedHoverStyle; + } + }; + + /* + * Sets the paint style and then repaints the element. + * + * Parameters: + * style - Style to use. + */ + this.setPaintStyle = function(style, doNotRepaint) { + paintStyle = style; + self.paintStyleInUse = paintStyle; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /** + * Gets the component's paint style. + * + * Returns: + * the component's paint style. if there is no hoverPaintStyle set then this will be the paint style used all the time, otherwise this is the style used when the mouse is not hovering. + */ + this.getPaintStyle = function() { + return paintStyle; + }; + + /* + * Sets the paint style to use when the mouse is hovering over the element. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the component will not be repainted. useful when setting things up initially. + */ + this.setHoverPaintStyle = function(style, doNotRepaint) { + hoverPaintStyle = style; + _updateHoverStyle(); + if (!doNotRepaint) self.repaint(); + }; + + /** + * Gets the component's hover paint style. + * + * Returns: + * the component's hover paint style. may be null. + */ + this.getHoverPaintStyle = function() { + return hoverPaintStyle; + }; + + /* + * sets/unsets the hover state of this element. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + this.setHover = function(hover, ignoreAttachedElements, timestamp) { + // while dragging, we ignore these events. this keeps the UI from flashing and + // swishing and whatevering. + if (!self._jsPlumb.currentlyDragging && !self._jsPlumb.isHoverSuspended()) { + + _hover = hover; + if (self.hoverClass != null && self.canvas != null) { + if (hover) + jpcl.addClass(self.canvas, self.hoverClass); + else + jpcl.removeClass(self.canvas, self.hoverClass); + } + if (hoverPaintStyle != null) { + self.paintStyleInUse = hover ? hoverPaintStyle : paintStyle; + timestamp = timestamp || _timestamp(); + self.repaint({timestamp:timestamp, recalc:false}); + } + // get the list of other affected elements, if supported by this component. + // for a connection, its the endpoints. for an endpoint, its the connections! surprise. + if (self.getAttachedElements && !ignoreAttachedElements) + _updateAttachedElements(hover, _timestamp(), self); + } + }; + + this.isHover = function() { return _hover; }; + + var jpcl = jsPlumb.CurrentLibrary, + events = [ "click", "dblclick", "mouseenter", "mouseout", "mousemove", "mousedown", "mouseup", "contextmenu" ], + eventFilters = { "mouseout":"mouseexit" }, + bindOne = function(o, c, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.bind(o, evt, function(ee) { + c.fire(filteredEvent, c, ee); + }); + }, + unbindOne = function(o, evt) { + var filteredEvent = eventFilters[evt] || evt; + jpcl.unbind(o, evt); + }; + + this.attachListeners = function(o, c) { + for (var i = 0; i < events.length; i++) { + bindOne(o, c, events[i]); + } + }; + + var _updateAttachedElements = function(state, timestamp, sourceElement) { + var affectedElements = self.getAttachedElements(); // implemented in subclasses + if (affectedElements) { + for (var i = 0; i < affectedElements.length; i++) { + if (!sourceElement || sourceElement != affectedElements[i]) + affectedElements[i].setHover(state, true, timestamp); // tell the attached elements not to inform their own attached elements. + } + } + }; + + this.reattachListenersForElement = function(o) { + if (arguments.length > 1) { + for (var i = 0; i < events.length; i++) + unbindOne(o, events[i]); + for (var i = 1; i < arguments.length; i++) + self.attachListeners(o, arguments[i]); + } + }; + }, + + overlayCapableJsPlumbUIComponent = function(params) { + jsPlumbUIComponent.apply(this, arguments); + var self = this; + /* + * Property: overlays + * List of Overlays for this component. + */ + this.overlays = []; + + var processOverlay = function(o) { + var _newOverlay = null; + if (_isArray(o)) { // this is for the shorthand ["Arrow", { width:50 }] syntax + // there's also a three arg version: + // ["Arrow", { width:50 }, {location:0.7}] + // which merges the 3rd arg into the 2nd. + var type = o[0], + // make a copy of the object so as not to mess up anyone else's reference... + p = jsPlumb.extend({component:self, _jsPlumb:self._jsPlumb}, o[1]); + if (o.length == 3) jsPlumb.extend(p, o[2]); + _newOverlay = new jsPlumb.Overlays[self._jsPlumb.getRenderMode()][type](p); + if (p.events) { + for (var evt in p.events) { + _newOverlay.bind(evt, p.events[evt]); + } + } + } else if (o.constructor == String) { + _newOverlay = new jsPlumb.Overlays[self._jsPlumb.getRenderMode()][o]({component:self, _jsPlumb:self._jsPlumb}); + } else { + _newOverlay = o; + } + + self.overlays.push(_newOverlay); + }, + calculateOverlaysToAdd = function(params) { + var defaultKeys = self.defaultOverlayKeys || [], + o = params.overlays, + checkKey = function(k) { + return self._jsPlumb.Defaults[k] || jsPlumb.Defaults[k] || []; + }; + + if (!o) o = []; + + for (var i = 0; i < defaultKeys.length; i++) + o.unshift.apply(o, checkKey(defaultKeys[i])); + + return o; + } + + var _overlays = calculateOverlaysToAdd(params);//params.overlays || self._jsPlumb.Defaults.Overlays; + if (_overlays) { + for (var i = 0; i < _overlays.length; i++) { + processOverlay(_overlays[i]); + } + } + + // overlay finder helper method + var _getOverlayIndex = function(id) { + var idx = -1; + for (var i = 0; i < self.overlays.length; i++) { + if (id === self.overlays[i].id) { + idx = i; + break; + } + } + return idx; + }; + + /* + * Function: addOverlay + * Adds an Overlay to the Connection. + * + * Parameters: + * overlay - Overlay to add. + */ + this.addOverlay = function(overlay) { + processOverlay(overlay); + self.repaint(); + }; + + /* + * Function: getOverlay + * Gets an overlay, by ID. Note: by ID. You would pass an 'id' parameter + * in to the Overlay's constructor arguments, and then use that to retrieve + * it via this method. + */ + this.getOverlay = function(id) { + var idx = _getOverlayIndex(id); + return idx >= 0 ? self.overlays[idx] : null; + }; + + /* + * Function:getOverlays + * Gets all the overlays for this component. + */ + this.getOverlays = function() { + return self.overlays; + }; + + /* + * Function: hideOverlay + * Hides the overlay specified by the given id. + */ + this.hideOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.hide(); + }; + + this.hideOverlays = function() { + for (var i = 0; i < self.overlays.length; i++) + self.overlays[i].hide(); + }; + + /* + * Function: showOverlay + * Shows the overlay specified by the given id. + */ + this.showOverlay = function(id) { + var o = self.getOverlay(id); + if (o) o.show(); + }; + + this.showOverlays = function() { + for (var i = 0; i < self.overlays.length; i++) + self.overlays[i].show(); + }; + + /** + * Function: removeAllOverlays + * Removes all overlays from the Connection, and then repaints. + */ + this.removeAllOverlays = function() { + for (var i in self.overlays) + self.overlays[i].cleanup(); + + self.overlays.splice(0, self.overlays.length); + self.repaint(); + }; + + /** + * Function:removeOverlay + * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayId - id of the overlay to remove. + */ + this.removeOverlay = function(overlayId) { + var idx = _getOverlayIndex(overlayId); + if (idx != -1) { + var o = self.overlays[idx]; + o.cleanup(); + self.overlays.splice(idx, 1); + } + }; + + /** + * Function:removeOverlays + * Removes a set of overlays by ID. Note: by ID. this is a string you set in the overlay spec. + * Parameters: + * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id. + */ + this.removeOverlays = function() { + for (var i = 0; i < arguments.length; i++) + self.removeOverlay(arguments[i]); + }; + + // this is a shortcut helper method to let people add a label as + // overlay. + var _internalLabelOverlayId = "__label", + _makeLabelOverlay = function(params) { + + var _params = { + cssClass:params.cssClass, + labelStyle : this.labelStyle, + id:_internalLabelOverlayId, + component:self, + _jsPlumb:self._jsPlumb + }, + mergedParams = jsPlumb.extend(_params, params); + + return new jsPlumb.Overlays[self._jsPlumb.getRenderMode()].Label( mergedParams ); + }; + if (params.label) { + var loc = params.labelLocation || self.defaultLabelLocation || 0.5, + labelStyle = params.labelStyle || self._jsPlumb.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle; + this.overlays.push(_makeLabelOverlay({ + label:params.label, + location:loc, + labelStyle:labelStyle + })); + } + + /* + * Function: setLabel + * Sets the Connection's label. + * + * Parameters: + * l - label to set. May be a String, a Function that returns a String, or a params object containing { "label", "labelStyle", "location", "cssClass" } + */ + this.setLabel = function(l) { + var lo = self.getOverlay(_internalLabelOverlayId); + if (!lo) { + var params = l.constructor == String || l.constructor == Function ? { label:l } : l; + lo = _makeLabelOverlay(params); + this.overlays.push(lo); + } + else { + if (l.constructor == String || l.constructor == Function) lo.setLabel(l); + else { + if (l.label) lo.setLabel(l.label); + if (l.location) lo.setLocation(l.location); + } + } + + self.repaint(); + }; + + /* + Function:getLabel + Returns the label text for this component (or a function if you are labelling with a function). + This does not return the overlay itself; this is a convenience method which is a pair with + setLabel; together they allow you to add and access a Label Overlay without having to create the + Overlay object itself. For access to the underlying label overlay that jsPlumb has created, + use getLabelOverlay. + */ + this.getLabel = function() { + var lo = self.getOverlay(_internalLabelOverlayId); + return lo != null ? lo.getLabel() : null; + }; + + /* + Function:getLabelOverlay + Returns the underlying internal label overlay, which will exist if you specified a label on + a connect or addEndpoint call, or have called setLabel at any stage. + */ + this.getLabelOverlay = function() { + return self.getOverlay(_internalLabelOverlayId); + } + }, + + _bindListeners = function(obj, _self, _hoverFunction) { + obj.bind("click", function(ep, e) { _self.fire("click", _self, e); }); + obj.bind("dblclick", function(ep, e) { _self.fire("dblclick", _self, e); }); + obj.bind("contextmenu", function(ep, e) { _self.fire("contextmenu", _self, e); }); + obj.bind("mouseenter", function(ep, e) { + if (!_self.isHover()) { + _hoverFunction(true); + _self.fire("mouseenter", _self, e); + } + }); + obj.bind("mouseexit", function(ep, e) { + if (_self.isHover()) { + _hoverFunction(false); + _self.fire("mouseexit", _self, e); + } + }); + }; + + var _jsPlumbInstanceIndex = 0, + getInstanceIndex = function() { + var i = _jsPlumbInstanceIndex + 1; + _jsPlumbInstanceIndex++; + return i; + }; + + var jsPlumbInstance = function(_defaults) { + + /* + * Property: Defaults + * + * These are the default settings for jsPlumb. They are what will be used if you do not supply specific pieces of information + * to the various API calls. A convenient way to implement your own look and feel can be to override these defaults + * by including a script somewhere after the jsPlumb include, but before you make any calls to jsPlumb. + * + * Properties: + * - *Anchor* The default anchor to use for all connections (both source and target). Default is "BottomCenter". + * - *Anchors* The default anchors to use ([source, target]) for all connections. Defaults are ["BottomCenter", "BottomCenter"]. + * - *ConnectionsDetachable* Whether or not connections are detachable by default (using the mouse). Defaults to true. + * - *ConnectionOverlays* The default overlay definitions for Connections. Defaults to an empty list. + * - *Connector* The default connector definition to use for all connections. Default is "Bezier". + * - *Container* Optional selector or element id that instructs jsPlumb to append elements it creates to a specific element. + * - *DragOptions* The default drag options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *DropOptions* The default drop options to pass in to connect, makeTarget and addEndpoint calls. Default is empty. + * - *Endpoint* The default endpoint definition to use for all connections (both source and target). Default is "Dot". + * - *EndpointOverlays* The default overlay definitions for Endpoints. Defaults to an empty list. + * - *Endpoints* The default endpoint definitions ([ source, target ]) to use for all connections. Defaults are ["Dot", "Dot"]. + * - *EndpointStyle* The default style definition to use for all endpoints. Default is fillStyle:"#456". + * - *EndpointStyles* The default style definitions ([ source, target ]) to use for all endpoints. Defaults are empty. + * - *EndpointHoverStyle* The default hover style definition to use for all endpoints. Default is null. + * - *EndpointHoverStyles* The default hover style definitions ([ source, target ]) to use for all endpoints. Defaults are null. + * - *HoverPaintStyle* The default hover style definition to use for all connections. Defaults are null. + * - *LabelStyle* The default style to use for label overlays on connections. + * - *LogEnabled* Whether or not the jsPlumb log is enabled. defaults to false. + * - *Overlays* The default overlay definitions (for both Connections and Endpoint). Defaults to an empty list. + * - *MaxConnections* The default maximum number of connections for an Endpoint. Defaults to 1. + * - *PaintStyle* The default paint style for a connection. Default is line width of 8 pixels, with color "#456". + * - *RenderMode* What mode to use to paint with. If you're on IE<9, you don't really get to choose this. You'll just get VML. Otherwise, the jsPlumb default is to use SVG. + * - *Scope* The default "scope" to use for connections. Scope lets you assign connections to different categories. + */ + this.Defaults = { + Anchor : "BottomCenter", + Anchors : [ null, null ], + ConnectionsDetachable : true, + ConnectionOverlays : [ ], + Connector : "Bezier", + Container : null, + DragOptions : { }, + DropOptions : { }, + Endpoint : "Dot", + EndpointOverlays : [ ], + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : "#456" }, + EndpointStyles : [ null, null ], + EndpointHoverStyle : null, + EndpointHoverStyles : [ null, null ], + HoverPaintStyle : null, + LabelStyle : { color : "black" }, + LogEnabled : false, + Overlays : [ ], + MaxConnections : 1, + PaintStyle : { lineWidth : 8, strokeStyle : "#456" }, + //Reattach:false, + RenderMode : "svg", + Scope : "jsPlumb_DefaultScope" + }; + if (_defaults) jsPlumb.extend(this.Defaults, _defaults); + + this.logEnabled = this.Defaults.LogEnabled; + + jsPlumbUtil.EventGenerator.apply(this); + var _currentInstance = this, + _instanceIndex = getInstanceIndex(), + _bb = _currentInstance.bind, + _initialDefaults = {}; + + for (var i in this.Defaults) + _initialDefaults[i] = this.Defaults[i]; + + this.bind = function(event, fn) { + if ("ready" === event && initialized) fn(); + else _bb.apply(_currentInstance,[event, fn]); + }; + + /* + Function: importDefaults + Imports all the given defaults into this instance of jsPlumb. + */ + _currentInstance.importDefaults = function(d) { + for (var i in d) { + _currentInstance.Defaults[i] = d[i]; + } + }; + + /* + Function:restoreDefaults + Restores the default settings to "factory" values. + */ + _currentInstance.restoreDefaults = function() { + _currentInstance.Defaults = jsPlumb.extend({}, _initialDefaults); + }; + + var log = null, + repaintFunction = function() { + jsPlumb.repaintEverything(); + }, + automaticRepaint = true, + repaintEverything = function() { + if (automaticRepaint) + repaintFunction(); + }, + resizeTimer = null, + initialized = false, + connectionsByScope = {}, + /** + * map of element id -> endpoint lists. an element can have an arbitrary + * number of endpoints on it, and not all of them have to be connected + * to anything. + */ + endpointsByElement = {}, + endpointsByUUID = {}, + offsets = {}, + offsetTimestamps = {}, + floatingConnections = {}, + draggableStates = {}, + canvasList = [], + sizes = [], + //listeners = {}, // a map: keys are event types, values are lists of listeners. + DEFAULT_SCOPE = this.Defaults.Scope, + renderMode = null, // will be set in init() + + /** + * helper method to add an item to a list, creating the list if it does + * not yet exist. + */ + _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + return l; + }, + + /** + * appends an element to some other element, which is calculated as follows: + * + * 1. if _currentInstance.Defaults.Container exists, use that element. + * 2. if the 'parent' parameter exists, use that. + * 3. otherwise just use the document body. + * + */ + _appendElement = function(el, parent) { + if (_currentInstance.Defaults.Container) + jsPlumb.CurrentLibrary.appendElement(el, _currentInstance.Defaults.Container); + else if (!parent) + document.body.appendChild(el); + else + jsPlumb.CurrentLibrary.appendElement(el, parent); + }, + + _curIdStamp = 1, + _idstamp = function() { return "" + _curIdStamp++; }, + + /** + * YUI, for some reason, put the result of a Y.all call into an object that contains + * a '_nodes' array, instead of handing back an array-like object like the other + * libraries do. + */ + _convertYUICollection = function(c) { + return c._nodes ? c._nodes : c; + }, + + _suspendDrawing = false, + /* + sets whether or not to suspend drawing. you should use this if you need to connect a whole load of things in one go. + it will save you a lot of time. + */ + _setSuspendDrawing = function(val, repaintAfterwards) { + _suspendDrawing = val; + if (repaintAfterwards) _currentInstance.repaintEverything(); + }, + + /** + * Draws an endpoint and its connections. this is the main entry point into drawing connections as well + * as endpoints, since jsPlumb is endpoint-centric under the hood. + * + * @param element element to draw (of type library specific element object) + * @param ui UI object from current library's event system. optional. + * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do. + */ + _draw = function(element, ui, timestamp) { + if (!_suspendDrawing) { + var id = _getAttribute(element, "id"), + repaintEls = _currentInstance.dragManager.getElementsForDraggable(id); + + if (timestamp == null) timestamp = _timestamp(); + + _currentInstance.anchorManager.redraw(id, ui, timestamp); + + if (repaintEls) { + for (var i in repaintEls) { + _currentInstance.anchorManager.redraw(repaintEls[i].id, ui, timestamp, repaintEls[i].offset); + } + } + } + }, + + /** + * executes the given function against the given element if the first + * argument is an object, or the list of elements, if the first argument + * is a list. the function passed in takes (element, elementId) as + * arguments. + */ + _elementProxy = function(element, fn) { + var retVal = null; + if (_isArray(element)) { + retVal = []; + for ( var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]), id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } else { + var el = _getElementObject(element), id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + return retVal; + }, + + /** + * gets an Endpoint by uuid. + */ + _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; }, + + /** + * inits a draggable if it's not already initialised. + */ + _initDraggableIfNecessary = function(element, isDraggable, dragOptions) { + var draggable = isDraggable == null ? false : isDraggable, + jpcl = jsPlumb.CurrentLibrary; + if (draggable) { + if (jpcl.isDragSupported(element) && !jpcl.isAlreadyDraggable(element)) { + var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend( {}, options); // make a copy. + var dragEvent = jpcl.dragEvents["drag"], + stopEvent = jpcl.dragEvents["stop"], + startEvent = jpcl.dragEvents["start"]; + + options[startEvent] = _wrap(options[startEvent], function() { + _currentInstance.setHoverSuspended(true); + }); + + options[dragEvent] = _wrap(options[dragEvent], function() { + var ui = jpcl.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + options[stopEvent] = _wrap(options[stopEvent], function() { + var ui = jpcl.getUIPosition(arguments); + _draw(element, ui); + _removeClass(element, "jsPlumb_dragged"); + _currentInstance.setHoverSuspended(false); + }); + var elId = _getId(element); // need ID + draggableStates[elId] = true; + var draggable = draggableStates[elId]; + options.disabled = draggable == null ? false : !draggable; + jpcl.initDraggable(element, options, false); + _currentInstance.dragManager.register(element); + } + } + }, + + /* + * prepares a final params object that can be passed to _newConnection, taking into account defaults, events, etc. + */ + _prepareConnectionParams = function(params, referenceParams) { + var _p = jsPlumb.extend( {}, params); + if (referenceParams) jsPlumb.extend(_p, referenceParams); + + // hotwire endpoints passed as source or target to sourceEndpoint/targetEndpoint, respectively. + if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source; + if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target; + + // test for endpoint uuids to connect + if (params.uuids) { + _p.sourceEndpoint = _getEndpoint(params.uuids[0]); + _p.targetEndpoint = _getEndpoint(params.uuids[1]); + } + + // now ensure that if we do have Endpoints already, they're not full. + // source: + if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; source endpoint is full"); + return; + } + + // target: + if (_p.targetEndpoint && _p.targetEndpoint.isFull()) { + _log(_currentInstance, "could not add connection; target endpoint is full"); + return; + } + + // copy in any connectorOverlays that were specified on the source endpoint. + // it doesnt copy target endpoint overlays. i'm not sure if we want it to or not. + if (_p.sourceEndpoint && _p.sourceEndpoint.connectorOverlays) { + _p.overlays = _p.overlays || []; + for (var i = 0; i < _p.sourceEndpoint.connectorOverlays.length; i++) { + _p.overlays.push(_p.sourceEndpoint.connectorOverlays[i]); + } + } + + // tooltip. params.tooltip takes precedence, then sourceEndpoint.connectorTooltip. + _p.tooltip = params.tooltip; + if (!_p.tooltip && _p.sourceEndpoint && _p.sourceEndpoint.connectorTooltip) + _p.tooltip = _p.sourceEndpoint.connectorTooltip; + + // if there's a target specified (which of course there should be), and there is no + // target endpoint specified, and 'newConnection' was not set to true, then we check to + // see if a prior call to makeTarget has provided us with the specs for the target endpoint, and + // we use those if so. additionally, if the makeTarget call was specified with 'uniqueEndpoint' set + // to true, then if that target endpoint has already been created, we re-use it. + if (_p.target && !_p.target.endpoint && !_p.targetEndpoint && !_p.newConnection) { + var tid = _getId(_p.target), + tep =_targetEndpointDefinitions[tid], + existingUniqueEndpoint = _targetEndpoints[tid]; + + if (tep) { + // if target not enabled, return. + if (!_targetsEnabled[tid]) return; + + // check for max connections?? + var newEndpoint = existingUniqueEndpoint != null ? existingUniqueEndpoint : _currentInstance.addEndpoint(_p.target, tep); + if (_targetEndpointsUnique[tid]) _targetEndpoints[tid] = newEndpoint; + _p.targetEndpoint = newEndpoint; + newEndpoint._makeTargetCreator = true; + } + } + + // same thing, but for source. + if (_p.source && !_p.source.endpoint && !_p.sourceEndpoint && !_p.newConnection) { + var tid = _getId(_p.source), + tep = _sourceEndpointDefinitions[tid], + existingUniqueEndpoint = _sourceEndpoints[tid]; + + if (tep) { + // if source not enabled, return. + if (!_sourcesEnabled[tid]) return; + + var newEndpoint = existingUniqueEndpoint != null ? existingUniqueEndpoint : _currentInstance.addEndpoint(_p.source, tep); + if (_sourceEndpointsUnique[tid]) _sourceEndpoints[tid] = newEndpoint; + _p.sourceEndpoint = newEndpoint; + } + } + + return _p; + }, + + _newConnection = function(params) { + var connectionFunc = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + endpointFunc = _currentInstance.Defaults.EndpointType || Endpoint, + parent = jsPlumb.CurrentLibrary.getParent; + + if (params.container) + params["parent"] = params.container; + else { + if (params.sourceEndpoint) + params["parent"] = params.sourceEndpoint.parent; + else if (params.source.constructor == endpointFunc) + params["parent"] = params.source.parent; + else params["parent"] = parent(params.source); + } + + params["_jsPlumb"] = _currentInstance; + var con = new connectionFunc(params); + con.id = "con_" + _idstamp(); + _eventFireProxy("click", "click", con); + _eventFireProxy("dblclick", "dblclick", con); + _eventFireProxy("contextmenu", "contextmenu", con); + return con; + }, + + /** + * adds the connection to the backing model, fires an event if necessary and then redraws + */ + _finaliseConnection = function(jpc, params, originalEvent) { + params = params || {}; + // add to list of connections (by scope). + if (!jpc.suspendedEndpoint) + _addToList(connectionsByScope, jpc.scope, jpc); + // fire an event + if (!params.doNotFireConnectionEvent && params.fireEvent !== false) { + _currentInstance.fire("jsPlumbConnection", { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + }, originalEvent); + } + // always inform the anchor manager + // except that if jpc has a suspended endpoint it's not true to say the + // connection is new; it has just (possibly) moved. the question is whether + // to make that call here or in the anchor manager. i think perhaps here. + _currentInstance.anchorManager.newConnection(jpc); + // force a paint + _draw(jpc.source); + }, + + _eventFireProxy = function(event, proxyEvent, obj) { + obj.bind(event, function(originalObject, originalEvent) { + _currentInstance.fire(proxyEvent, obj, originalEvent); + }); + }, + + /** + * for the given endpoint params, returns an appropriate parent element for the UI elements that will be added. + * this function is used by _newEndpoint (directly below), and also in the makeSource function in jsPlumb. + * + * the logic is to first look for a "container" member of params, and pass that back if found. otherwise we + * handoff to the 'getParent' function in the current library. + */ + _getParentFromParams = function(params) { + if (params.container) + return params.container; + else { + var tag = jsPlumb.CurrentLibrary.getTagName(params.source), + p = jsPlumb.CurrentLibrary.getParent(params.source); + if (tag && tag.toLowerCase() === "td") + return jsPlumb.CurrentLibrary.getParent(p); + else return p; + } + }, + + /** + factory method to prepare a new endpoint. this should always be used instead of creating Endpoints + manually, since this method attaches event listeners and an id. + */ + _newEndpoint = function(params) { + var endpointFunc = _currentInstance.Defaults.EndpointType || Endpoint; + params.parent = _getParentFromParams(params); + params["_jsPlumb"] = _currentInstance; + var ep = new endpointFunc(params); + ep.id = "ep_" + _idstamp(); + _eventFireProxy("click", "endpointClick", ep); + _eventFireProxy("dblclick", "endpointDblClick", ep); + _eventFireProxy("contextmenu", "contextmenu", ep); + return ep; + }, + + /** + * performs the given function operation on all the connections found + * for the given element id; this means we find all the endpoints for + * the given element, and then for each endpoint find the connectors + * connected to it. then we pass each connection in to the given + * function. + */ + _operation = function(elId, func, endpointFunc) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + for ( var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + if (endpointFunc) endpointFunc(endpoints[i]); + } + } + }, + /** + * perform an operation on all elements. + */ + _operationOnAll = function(func) { + for ( var elId in endpointsByElement) { + _operation(elId, func); + } + }, + + /** + * helper to remove an element from the DOM. + */ + _removeElement = function(element, parent) { + if (element != null && element.parentNode != null) { + element.parentNode.removeChild(element); + } + }, + /** + * helper to remove a list of elements from the DOM. + */ + _removeElements = function(elements, parent) { + for ( var i = 0; i < elements.length; i++) + _removeElement(elements[i], parent); + }, + /** + * Sets whether or not the given element(s) should be draggable, + * regardless of what a particular plumb command may request. + * + * @param element + * May be a string, a element objects, or a list of + * strings/elements. + * @param draggable + * Whether or not the given element(s) should be draggable. + */ + _setDraggable = function(element, draggable) { + return _elementProxy(element, function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }); + }, + /** + * private method to do the business of hiding/showing. + * + * @param el + * either Id of the element in question or a library specific + * object for the element. + * @param state + * String specifying a value for the css 'display' property + * ('block' or 'none'). + */ + _setVisible = function(el, state, alsoChangeEndpoints) { + state = state === "block"; + var endpointFunc = null; + if (alsoChangeEndpoints) { + if (state) endpointFunc = function(ep) { + ep.setVisible(true, true, true); + }; + else endpointFunc = function(ep) { + ep.setVisible(false, true, true); + }; + } + var id = _getAttribute(el, "id"); + _operation(id, function(jpc) { + if (state && alsoChangeEndpoints) { + // this test is necessary because this functionality is new, and i wanted to maintain backwards compatibility. + // this block will only set a connection to be visible if the other endpoint in the connection is also visible. + var oidx = jpc.sourceId === id ? 1 : 0; + if (jpc.endpoints[oidx].isVisible()) jpc.setVisible(true); + } + else // the default behaviour for show, and what always happens for hide, is to just set the visibility without getting clever. + jpc.setVisible(state); + }, endpointFunc); + }, + /** + * toggles the draggable state of the given element(s). + * + * @param el + * either an id, or an element object, or a list of + * ids/element objects. + */ + _toggleDraggable = function(el) { + return _elementProxy(el, function(el, elId) { + var state = draggableStates[elId] == null ? false : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }); + }, + /** + * private method to do the business of toggling hiding/showing. + * + * @param elId + * Id of the element in question + */ + _toggleVisible = function(elId, changeEndpoints) { + var endpointFunc = null; + if (changeEndpoints) { + endpointFunc = function(ep) { + var state = ep.isVisible(); + ep.setVisible(!state); + }; + } + _operation(elId, function(jpc) { + var state = jpc.isVisible(); + jpc.setVisible(!state); + }, endpointFunc); + // todo this should call _elementProxy, and pass in the + // _operation(elId, f) call as a function. cos _toggleDraggable does + // that. + }, + /** + * updates the offset and size for a given element, and stores the + * values. if 'offset' is not null we use that (it would have been + * passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we get the current values. + */ + _updateOffset = function(params) { + var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId; + if (!recalc) { + if (timestamp && timestamp === offsetTimestamps[elId]) + return offsets[elId]; + } + if (recalc || !offset) { // if forced repaint or no offset + // available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + if (s != null) { + sizes[elId] = _getSize(s); + offsets[elId] = _getOffset(s); + offsetTimestamps[elId] = timestamp; + } + } else { + offsets[elId] = offset; + if (sizes[elId] == null) { + var s = _getElementObject(elId); + if (s != null) + sizes[elId] = _getSize(s); + } + } + + if(offsets[elId] && !offsets[elId].right) { + offsets[elId].right = offsets[elId].left + sizes[elId][0]; + offsets[elId].bottom = offsets[elId].top + sizes[elId][1]; + offsets[elId].width = sizes[elId][0]; + offsets[elId].height = sizes[elId][1]; + offsets[elId].centerx = offsets[elId].left + (offsets[elId].width / 2); + offsets[elId].centery = offsets[elId].top + (offsets[elId].height / 2); + } + return offsets[elId]; + }, + + // TODO comparison performance + _getCachedData = function(elId) { + var o = offsets[elId]; + if (!o) o = _updateOffset({elId:elId}); + return {o:o, s:sizes[elId]}; + }, + + /** + * gets an id for the given element, creating and setting one if + * necessary. the id is of the form + * + * jsPlumb__ + * + * where "index in instance" is a monotonically increasing integer that starts at 0, + * for each instance. this method is used not only to assign ids to elements that do not + * have them but also to connections and endpoints. + */ + _getId = function(element, uuid, doNotCreateIfNotFound) { + var ele = _getElementObject(element); + var id = _getAttribute(ele, "id"); + if (!id || id == "undefined") { + // check if fixed uuid parameter is given + if (arguments.length == 2 && arguments[1] != undefined) + id = uuid; + else if (arguments.length == 1 || (arguments.length == 3 && !arguments[2])) + id = "jsPlumb_" + _instanceIndex + "_" + _idstamp(); + _setAttribute(ele, "id", id); + } + return id; + }, + + /** + * wraps one function with another, creating a placeholder for the + * wrapped function if it was null. this is used to wrap the various + * drag/drop event functions - to allow jsPlumb to be notified of + * important lifecycle events without imposing itself on the user's + * drag/drop functionality. TODO: determine whether or not we should + * support an error handler concept, if one of the functions fails. + * + * @param wrappedFunction original function to wrap; may be null. + * @param newFunction function to wrap the original with. + * @param returnOnThisValue Optional. Indicates that the wrappedFunction should + * not be executed if the newFunction returns a value matching 'returnOnThisValue'. + * note that this is a simple comparison and only works for primitives right now. + */ + _wrap = function(wrappedFunction, newFunction, returnOnThisValue) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + var r = null; + try { + r = newFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, "jsPlumb function failed : " + e); + } + if (returnOnThisValue == null || (r !== returnOnThisValue)) { + try { + wrappedFunction.apply(this, arguments); + } catch (e) { + _log(_currentInstance, "wrapped function failed : " + e); + } + } + return r; + }; + }; + + /* + * Property: connectorClass + * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.connectorClass = "_jsPlumb_connector"; + + /* + * Property: endpointClass + * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.endpointClass = "_jsPlumb_endpoint"; + + /* + * Property: overlayClass + * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + this.overlayClass = "_jsPlumb_overlay"; + + this.Anchors = {}; + + this.Connectors = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Endpoints = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + + this.Overlays = { + "canvas":{}, + "svg":{}, + "vml":{} + }; + +// ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***************************************** + /* + * Function: bind + * Bind to an event on jsPlumb. + * + * Parameters: + * event - the event to bind. Available events on jsPlumb are: + * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback. + * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback. + * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback. + * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback. + * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback. + * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback. + * - *beforeDrop* : notification that a Connection is about to be dropped. Returning false from this method cancels the drop. jsPlumb passes { sourceId, targetId, scope, connection, dropEndpoint } to your callback. For more information, refer to the jsPlumb documentation. + * - *beforeDetach* : notification that a Connection is about to be detached. Returning false from this method cancels the detach. jsPlumb passes the Connection to your callback. For more information, refer to the jsPlumb documentation. + * - *connectionDrag* : notification that an existing Connection is being dragged. jsPlumb passes the Connection to your callback function. + * - *connectionDragEnd* : notification that the drag of an existing Connection has ended. jsPlumb passes the Connection to your callback function. + * + * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event. + */ + + /* + * Function: clearListeners + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + +// *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *********************************************************** + + +// --------------------------- jsPLumbInstance public API --------------------------------------------------------- + + /* + Function: addClass + + Helper method to abstract out differences in setting css classes on the different renderer types. + */ + this.addClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.addClass(el, clazz); + }; + + /* + Function: removeClass + + Helper method to abstract out differences in setting css classes on the different renderer types. + */ + this.removeClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.removeClass(el, clazz); + }; + + /* + Function: hasClass + + Helper method to abstract out differences in testing for css classes on the different renderer types. + */ + this.hasClass = function(el, clazz) { + return jsPlumb.CurrentLibrary.hasClass(el, clazz); + }; + + /* + Function: addEndpoint + + Adds an to a given element or elements. + + Parameters: + + el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + params - Object containing Endpoint constructor arguments. For more information, see . + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some + shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in + this object are anything that 'params' can contain. See . + + Returns: + The newly created , if el referred to a single element. Otherwise, an array of newly created s. + + See Also: + + */ + this.addEndpoint = function(el, params, referenceParams) { + referenceParams = referenceParams || {}; + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint; + p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // YUI wrapper + el = _convertYUICollection(el); + + var results = [], inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + var _el = _getElementObject(inputs[i]), id = _getId(_el); + p.source = _el; + _updateOffset({ elId : id }); + var e = _newEndpoint(p); + if (p.parentAnchor) e.parentAnchor = p.parentAnchor; + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id], myWH = sizes[id]; + var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e }); + e.paint({ anchorLoc : anchorLoc }); + results.push(e); + _currentInstance.dragManager.endpointAdded(_el); + } + + return results.length == 1 ? results[0] : results; + }; + + /* + Function: addEndpoints + Adds a list of s to a given element or elements. + + Parameters: + target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. + endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See 's constructor documentation. + referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements. + + Returns: + List of newly created s, one for each entry in the 'endpoints' argument. + + See Also: + + */ + this.addEndpoints = function(el, endpoints, referenceParams) { + var results = []; + for ( var i = 0; i < endpoints.length; i++) { + var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams); + if (_isArray(e)) + Array.prototype.push.apply(results, e); + else results.push(e); + } + return results; + }; + + /* + Function: animate + This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating + the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as + the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI. + + Parameters: + el - Element to animate. Either an id, or a selector representing the element. + properties - The 'properties' argument you want passed to the library's animate call. + options - The 'options' argument you want passed to the library's animate call. + + Returns: + void + */ + this.animate = function(el, properties, options) { + var ele = _getElementObject(el), id = _getAttribute(el, "id"); + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete']; + options[stepFunction] = _wrap(options[stepFunction], function() { + _currentInstance.repaint(id); + }); + + // onComplete repaints, just to make sure everything looks good at the end of the animation. + options[completeFunction] = _wrap(options[completeFunction], + function() { + _currentInstance.repaint(id); + }); + + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }; + + /** + * checks for a listener for the given condition, executing it if found, passing in the given value. + * condition listeners would have been attached using "bind" (which is, you could argue, now overloaded, since + * firing click events etc is a bit different to what this does). i thought about adding a "bindCondition" + * or something, but decided against it, for the sake of simplicity. jsPlumb will never fire one of these + * condition events anyway. + */ + this.checkCondition = function(conditionName, value) { + var l = _currentInstance.getListener(conditionName); + var r = true; + if (l && l.length > 0) { + try { + for (var i = 0 ; i < l.length; i++) { + r = r && l[i](value); + } + } + catch (e) { + _log(_currentInstance, "cannot check condition [" + conditionName + "]" + e); + } + } + return r; + }; + + /* + Function: connect + Establishes a between two elements (or s, which are themselves registered to elements). + + Parameters: + params - Object containing constructor arguments for the Connection. See 's constructor documentation. + referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of + Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection. + + Returns: + The newly created . + */ + this.connect = function(params, referenceParams) { + // prepare a final set of parameters to create connection with + var _p = _prepareConnectionParams(params, referenceParams); + // TODO probably a nicer return value if the connection was not made. _prepareConnectionParams + // will return null (and log something) if either endpoint was full. what would be nicer is to + // create a dedicated 'error' object. + if (_p) { + // a connect call will delete its created endpoints on detach, unless otherwise specified. + // this is because the endpoints belong to this connection only, and are no use to + // anyone else, so they hang around like a bad smell. + if (_p.deleteEndpointsOnDetach == null) + _p.deleteEndpointsOnDetach = true; + + // create the connection. it is not yet registered + var jpc = _newConnection(_p); + // now add it the model, fire an event, and redraw + _finaliseConnection(jpc, _p); + return jpc; + } + }; + + /* + Function: deleteEndpoint + Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too) + + Parameters: + object - either an object (such as from an addEndpoint call), or a String UUID. + + Returns: + void + */ + this.deleteEndpoint = function(object) { + var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object; + if (endpoint) { + var uuid = endpoint.getUuid(); + if (uuid) endpointsByUUID[uuid] = null; + endpoint.detachAll(); + _removeElements(endpoint.endpoint.getDisplayElements()); + _currentInstance.anchorManager.deleteEndpoint(endpoint); + for (var e in endpointsByElement) { + var endpoints = endpointsByElement[e]; + if (endpoints) { + var newEndpoints = []; + for (var i = 0; i < endpoints.length; i++) + if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]); + + endpointsByElement[e] = newEndpoints; + } + } + _currentInstance.dragManager.endpointDeleted(endpoint); + } + }; + + /* + Function: deleteEveryEndpoint + Deletes every , and their associated s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference +between this method and jsPlumb.reset). + + Returns: + void + */ + this.deleteEveryEndpoint = function() { + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + _currentInstance.deleteEndpoint(endpoints[i]); + } + } + } + delete endpointsByElement; + endpointsByElement = {}; + delete endpointsByUUID; + endpointsByUUID = {}; + }; + + var fireDetachEvent = function(jpc, doFireEvent, originalEvent) { + // may have been given a connection, or in special cases, an object + var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + argIsConnection = jpc.constructor == connType, + params = argIsConnection ? { + connection:jpc, + source : jpc.source, target : jpc.target, + sourceId : jpc.sourceId, targetId : jpc.targetId, + sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1] + } : jpc; + + if (doFireEvent) _currentInstance.fire("jsPlumbConnectionDetached", params, originalEvent); + _currentInstance.anchorManager.connectionDetached(params); + }, + /** + fires an event to indicate an existing connection is being dragged. + */ + fireConnectionDraggingEvent = function(jpc) { + _currentInstance.fire("connectionDrag", jpc); + }, + fireConnectionDragStopEvent = function(jpc) { + _currentInstance.fire("connectionDragStop", jpc); + }; + + + /* + Function: detach + Detaches and then removes a . From 1.3.5 this method has been altered to remove support for + specifying Connections by various parameters; you can now pass in a Connection as the first argument and + an optional parameters object as a second argument. If you need the functionality this method provided + before 1.3.5 then you should use the getConnections method to get the list of Connections to detach, and + then iterate through them, calling this for each one. + + Parameters: + connection - the to detach + params - optional parameters to the detach call. valid values here are + fireEvent : defaults to false; indicates you want jsPlumb to fire a connection + detached event. The thinking behind this is that if you made a programmatic + call to detach an event, you probably don't need the callback. + forceDetach : defaults to false. allows you to override any beforeDetach listeners that may be registered. + + Returns: + true if successful, false if not. + */ + this.detach = function() { + + if (arguments.length == 0) return; + var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(), + firstArgIsConnection = arguments[0].constructor == connType, + params = arguments.length == 2 ? firstArgIsConnection ? (arguments[1] || {}) : arguments[0] : arguments[0], + fireEvent = (params.fireEvent !== false), + forceDetach = params.forceDetach, + connection = firstArgIsConnection ? arguments[0] : params.connection; + + if (connection) { + if (forceDetach || (connection.isDetachAllowed(connection) + && connection.endpoints[0].isDetachAllowed(connection) + && connection.endpoints[1].isDetachAllowed(connection))) { + if (forceDetach || _currentInstance.checkCondition("beforeDetach", connection)) + connection.endpoints[0].detach(connection, false, true, fireEvent); // TODO check this param iscorrect for endpoint's detach method + } + } + else { + var _p = jsPlumb.extend( {}, params); // a backwards compatibility hack: source should be thought of as 'params' in this case. + // test for endpoint uuids to detach + if (_p.uuids) { + _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1]), fireEvent); + } else if (_p.sourceEndpoint && _p.targetEndpoint) { + _p.sourceEndpoint.detachFrom(_p.targetEndpoint); + } else { + var sourceId = _getId(_p.source), + targetId = _getId(_p.target); + _operation(sourceId, function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + if (_currentInstance.checkCondition("beforeDetach", jpc)) { + jpc.endpoints[0].detach(jpc, false, true, fireEvent); + } + } + }); + } + } + }; + + /* + Function: detachAllConnections + Removes all an element's Connections. + + Parameters: + el - either the id of the element, or a selector for the element. + params - optional parameters. alowed values: + fireEvent : defaults to true, whether or not to fire the detach event. + + Returns: + void + */ + this.detachAllConnections = function(el, params) { + params = params || {}; + el = _getElementObject(el); + var id = _getAttribute(el, "id"), + endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + }; + + /* + Function: detachEveryConnection + Remove all Connections from all elements, but leaves Endpoints in place. + + Parameters: + params - optional params object containing: + fireEvent : whether or not to fire detach events. defaults to true. + + + Returns: + void + + See Also: + + */ + this.detachEveryConnection = function(params) { + params = params || {}; + for ( var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for ( var i = 0; i < endpoints.length; i++) { + endpoints[i].detachAll(params.fireEvent); + } + } + } + delete connectionsByScope; + connectionsByScope = {}; + }; + + + /* + Function: draggable + Initialises the draggability of some element or elements. You should use this instead of your + library's draggable method so that jsPlumb can setup the appropriate callbacks. Your + underlying library's drag method is always called from this method. + + Parameters: + el - either an element id, a list of element ids, or a selector. + options - options to pass through to the underlying library + + Returns: + void + */ + this.draggable = function(el, options) { + if (typeof el == 'object' && el.length) { + for ( var i = 0; i < el.length; i++) { + var ele = _getElementObject(el[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced + // into the library adapters (for jquery and mootools aswell) + for ( var i = 0; i < el._nodes.length; i++) { + var ele = _getElementObject(el._nodes[i]); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + } + else { + var ele = _getElementObject(el); + if (ele) _initDraggableIfNecessary(ele, true, options); + } + }; + + /* + Function: extend + Wraps the underlying library's extend functionality. + + Parameters: + o1 - object to extend + o2 - object to extend o1 with + + Returns: + o1, extended with all properties from o2. + */ + this.extend = function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }; + + /* + * Function: getDefaultEndpointType + * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultEndpointType().apply(this, arguments); + * + * Returns: + * the default Endpoint function used by jsPlumb. + */ + this.getDefaultEndpointType = function() { + return Endpoint; + }; + + /* + * Function: getDefaultConnectionType + * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass. + * you would make a call like this in your class's constructor: + * jsPlumb.getDefaultConnectionType().apply(this, arguments); + * + * Returns: + * the default Connection function used by jsPlumb. + */ + this.getDefaultConnectionType = function() { + return Connection; + }; + + // helpers for select/selectEndpoints + var _setOperation = function(list, func, args, selector) { + for (var i = 0; i < list.length; i++) { + list[i][func].apply(list[i], args); + } + return selector(list); + }, + _getOperation = function(list, func, args) { + var out = []; + for (var i = 0; i < list.length; i++) { + out.push([ list[i][func].apply(list[i], args), list[i] ]); + } + return out; + }, + setter = function(list, func, selector) { + return function() { + return _setOperation(list, func, arguments, selector); + }; + }, + getter = function(list, func) { + return function() { + return _getOperation(list, func, arguments); + }; + }; + + /* + * Function: getConnections + * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method, + * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the + * default scope). If multiple scopes are passed in, the return value will be a map of { scope -> [ connection... ] }. + * + * Parameters: + * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list + * of connections that are in the given scope. use '*' for all scopes. + * options - if the argument is a JS object, you can specify a finer-grained filter: + * + * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes. Also may have the value '*', indicating any scope. + * - *source* either a string representing an element id, a selector, or an array of ids. Also may have the value '*', indicating any source. Constrains the result to connections having this/these element(s) as source. + * - *target* either a string representing an element id, a selector, or an array of ids. Also may have the value '*', indicating any target. Constrains the result to connections having this/these element(s) as target. + * flat - return results in a flat array (don't return an object whose keys are scopes and whose values are lists per scope). + * + */ + this.getConnections = function(options, flat) { + if (!options) { + options = {}; + } else if (options.constructor == String) { + options = { "scope": options }; + } + var prepareList = function(input) { + var r = []; + if (input) { + if (typeof input == 'string') { + if (input === "*") return input; + r.push(input); + } + else + r = input; + } + return r; + }, + scope = options.scope || _currentInstance.getDefaultScope(), + scopes = prepareList(scope), + sources = prepareList(options.source), + targets = prepareList(options.target), + filter = function(list, value) { + if (list === "*") return true; + return list.length > 0 ? _indexOf(list, value) != -1 : true; + }, + results = (!flat && scopes.length > 1) ? {} : [], + _addOne = function(scope, obj) { + if (!flat && scopes.length > 1) { + var ss = results[scope]; + if (ss == null) { + ss = []; results[scope] = ss; + } + ss.push(obj); + } else results.push(obj); + }; + for ( var i in connectionsByScope) { + if (filter(scopes, i)) { + for ( var j = 0; j < connectionsByScope[i].length; j++) { + var c = connectionsByScope[i][j]; + if (filter(sources, c.sourceId) && filter(targets, c.targetId)) + _addOne(i, c); + } + } + } + return results; + }; + + var _makeConnectionSelectHandler = function(list) { + //var + return { + // setters + setHover:setter(list, "setHover", _makeConnectionSelectHandler), + removeAllOverlays:setter(list, "removeAllOverlays", _makeConnectionSelectHandler), + setLabel:setter(list, "setLabel", _makeConnectionSelectHandler), + addOverlay:setter(list, "addOverlay", _makeConnectionSelectHandler), + removeOverlay:setter(list, "removeOverlay", _makeConnectionSelectHandler), + removeOverlays:setter(list, "removeOverlays", _makeConnectionSelectHandler), + showOverlay:setter(list, "showOverlay", _makeConnectionSelectHandler), + hideOverlay:setter(list, "hideOverlay", _makeConnectionSelectHandler), + showOverlays:setter(list, "showOverlays", _makeConnectionSelectHandler), + hideOverlays:setter(list, "hideOverlays", _makeConnectionSelectHandler), + setPaintStyle:setter(list, "setPaintStyle", _makeConnectionSelectHandler), + setHoverPaintStyle:setter(list, "setHoverPaintStyle", _makeConnectionSelectHandler), + setDetachable:setter(list, "setDetachable", _makeConnectionSelectHandler), + setConnector:setter(list, "setConnector", _makeConnectionSelectHandler), + setParameter:setter(list, "setParameter", _makeConnectionSelectHandler), + setParameters:setter(list, "setParameters", _makeConnectionSelectHandler), + + detach:function() { + for (var i = 0; i < list.length; i++) + _currentInstance.detach(list[i]); + }, + + // getters + getLabel:getter(list, "getLabel"), + getOverlay:getter(list, "getOverlay"), + isHover:getter(list, "isHover"), + isDetachable:getter(list, "isDetachable"), + getParameter:getter(list, "getParameter"), + getParameters:getter(list, "getParameters"), + getPaintStyle:getter(list, "getPaintStyle"), + getHoverPaintStyle:getter(list, "getHoverPaintStyle"), + + // util + length:list.length, + each:function(f) { + for (var i = 0; i < list.length; i++) { + f(list[i]); + } + return _makeConnectionSelectHandler(list); + }, + get:function(idx) { + return list[idx]; + } + + }; + }; + + /* + * Function: select + * Selects a set of Connections, using the filter options from the getConnections method, and returns an object + * that allows you to perform an operation on all of the Connections at once. The return value from any of these + * operations is the original list of Connections, allowing operations to be chained (for 'setter' type operations). + * 'getter' type operations return an array of values, where each entry is a [Connection, return value] pair. + * + * Parameters: + * scope - see getConnections + * source - see getConnections + * target - see getConnections + * + * Returns: + * A list of Connections on which operations may be executed. 'Setter' type operations can be chained; 'getter' type operations + * return an array of [Connection, value] pairs, one entry for each Connection in the list returned. The full list of operations + * is as follows (where not specified, the operation's effect or return value is the same as the corresponding method on Connection) : + * - setHover + * - removeAllOverlays + * - setLabel + * - addOverlay + * - removeOverlay + * - removeOverlays + * - showOverlay + * - hideOverlay + * - showOverlays + * - hideOverlays + * - setPaintStyle + * - setHoverPaintStyle + * - setDetachable + * - setConnector + * - setParameter + * - setParameters + * - getLabel + * - getOverlay + * - isHover + * - isDetachable + * - getParameter + * - getParameters + * - getPaintStyle + * - getHoverPaintStyle + * - detach detaches all the connections in the list. not chainable and does not return anything. + * - length : returns the length of the list. + * - get(index) : returns the Connection at 'index' in the list. + * - each(function(connection)...) : allows you to specify your own function to execute; this function is chainable. + */ + this.select = function(params) { + params = params || {}; + params.scope = params.scope || "*"; + var c = _currentInstance.getConnections(params, true); + return _makeConnectionSelectHandler(c); + }; + + /* + * Function: getAllConnections + * Gets all connections, as a map of { scope -> [ connection... ] }. + */ + this.getAllConnections = function() { + return connectionsByScope; + }; + + /* + * Function: getDefaultScope + * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a + * scope to an endpoint or connection allows you to support different + * types of connections in the same UI. but if you're only interested in + * one type of connection, you don't need to supply a scope. this method + * will probably be used by very few people; it's good for testing + * though. + */ + this.getDefaultScope = function() { + return DEFAULT_SCOPE; + }; + + /* + Function: getEndpoint + Gets an Endpoint by UUID + + Parameters: + uuid - the UUID for the Endpoint + + Returns: + Endpoint with the given UUID, null if nothing found. + */ + this.getEndpoint = _getEndpoint; + + /** + * Function:getEndpoints + * Gets the list of Endpoints for a given selector, or element id. + * @param el + * @return + */ + this.getEndpoints = function(el) { + return endpointsByElement[_getId(el)]; + }; + + /* + * Gets an element's id, creating one if necessary. really only exposed + * for the lib-specific functionality to access; would be better to pass + * the current instance into the lib-specific code (even though this is + * a static call. i just don't want to expose it to the public API). + */ + this.getId = _getId; + this.getOffset = function(id) { + var o = offsets[id]; + return _updateOffset({elId:id}); + }; + + this.getSelector = function(spec) { + return jsPlumb.CurrentLibrary.getSelector(spec); + }; + + this.getSize = function(id) { + var s = sizes[id]; + if (!s) _updateOffset({elId:id}); + return sizes[id]; + }; + + this.appendElement = _appendElement; + + var _hoverSuspended = false; + this.isHoverSuspended = function() { return _hoverSuspended; }; + this.setHoverSuspended = function(s) { _hoverSuspended = s; }; + + this.isCanvasAvailable = function() { return canvasAvailable; }; + this.isSVGAvailable = function() { return svgAvailable; }; + this.isVMLAvailable = vmlAvailable; + + /* + Function: hide + Sets an element's connections to be hidden. + + Parameters: + el - either the id of the element, or a selector for the element. + changeEndpoints - whether not to also hide endpoints on the element. by default this is false. + + Returns: + void + */ + this.hide = function(el, changeEndpoints) { + _setVisible(el, "none", changeEndpoints); + }; + + // exposed for other objects to use to get a unique id. + this.idstamp = _idstamp; + + /** + * callback from the current library to tell us to prepare ourselves (attach + * mouse listeners etc; can't do that until the library has provided a bind method) + * @return + */ + this.init = function() { + if (!initialized) { + _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run. + + var bindOne = function(event) { + jsPlumb.CurrentLibrary.bind(document, event, function(e) { + if (!_currentInstance.currentlyDragging && renderMode == jsPlumb.CANVAS) { + // try connections first + for (var scope in connectionsByScope) { + var c = connectionsByScope[scope]; + for (var i = 0; i < c.length; i++) { + var t = c[i].connector[event](e); + if (t) return; + } + } + for (var el in endpointsByElement) { + var ee = endpointsByElement[el]; + for (var i = 0; i < ee.length; i++) { + if (ee[i].endpoint[event](e)) return; + } + } + } + }); + }; + bindOne("click");bindOne("dblclick");bindOne("mousemove");bindOne("mousedown");bindOne("mouseup");bindOne("contextmenu"); + + initialized = true; + _currentInstance.fire("ready"); + } + }; + + this.log = log; + this.jsPlumbUIComponent = jsPlumbUIComponent; + + /* + * Creates an anchor with the given params. + * + * + * Returns: The newly created Anchor. + */ + this.makeAnchor = function() { + if (arguments.length == 0) return null; + var specimen = arguments[0], elementId = arguments[1], jsPlumbInstance = arguments[2], newAnchor = null; + // if it appears to be an anchor already... + if (specimen.compute && specimen.getOrientation) return specimen; //TODO hazy here about whether it should be added or is already added somehow. + // is it the name of an anchor type? + else if (typeof specimen == "string") { + newAnchor = jsPlumb.Anchors[arguments[0]]({elementId:elementId, jsPlumbInstance:_currentInstance}); + } + // is it an array? it will be one of: + // an array of [name, params] - this defines a single anchor + // an array of arrays - this defines some dynamic anchors + // an array of numbers - this defines a single anchor. + else if (_isArray(specimen)) { + if (_isArray(specimen[0]) || _isString(specimen[0])) { + if (specimen.length == 2 && _isString(specimen[0]) && _isObject(specimen[1])) { + var pp = jsPlumb.extend({elementId:elementId, jsPlumbInstance:_currentInstance}, specimen[1]); + newAnchor = jsPlumb.Anchors[specimen[0]](pp); + } + else + newAnchor = new DynamicAnchor(specimen, null, elementId); + } + else { + var anchorParams = { + x:specimen[0], y:specimen[1], + orientation : (specimen.length >= 4) ? [ specimen[2], specimen[3] ] : [0,0], + offsets : (specimen.length == 6) ? [ specimen[4], specimen[5] ] : [ 0, 0 ], + elementId:elementId + }; + newAnchor = new Anchor(anchorParams); + newAnchor.clone = function() { return new Anchor(anchorParams); }; + } + } + + if (!newAnchor.id) newAnchor.id = "anchor_" + _idstamp(); + return newAnchor; + }; + + /** + * makes a list of anchors from the given list of types or coords, eg + * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ] + */ + this.makeAnchors = function(types, elementId, jsPlumbInstance) { + var r = []; + for ( var i = 0; i < types.length; i++) { + if (typeof types[i] == "string") + r.push(jsPlumb.Anchors[types[i]]({elementId:elementId, jsPlumbInstance:jsPlumbInstance})); + else if (_isArray(types[i])) + r.push(_currentInstance.makeAnchor(types[i], elementId, jsPlumbInstance)); + } + return r; + }; + + /** + * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor + * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will + * not need to provide this - i think). + */ + this.makeDynamicAnchor = function(anchors, anchorSelector) { + return new DynamicAnchor(anchors, anchorSelector); + }; + + /** + * Function: makeTarget + * Makes some DOM element a Connection target, allowing you to drag connections to it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a target. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * scope optional. scope for the drop zone. + * dropOptions optional. same stuff as you would pass to dropOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to true. whether or not to delete + * any Endpoints created by a connection to this target if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + var _targetEndpointDefinitions = {}, + _targetEndpoints = {}, + _targetEndpointsUnique = {}, + _targetMaxConnections = {}, + _setEndpointPaintStylesAndAnchor = function(ep, epIndex) { + ep.paintStyle = ep.paintStyle || + _currentInstance.Defaults.EndpointStyles[epIndex] || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyles[epIndex] || + jsPlumb.Defaults.EndpointStyle; + ep.hoverPaintStyle = ep.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyles[epIndex] || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyles[epIndex] || + jsPlumb.Defaults.EndpointHoverStyle; + + ep.anchor = ep.anchor || + _currentInstance.Defaults.Anchors[epIndex] || + _currentInstance.Defaults.Anchor || + jsPlumb.Defaults.Anchors[epIndex] || + jsPlumb.Defaults.Anchor; + + ep.endpoint = ep.endpoint || + _currentInstance.Defaults.Endpoints[epIndex] || + _currentInstance.Defaults.Endpoint || + jsPlumb.Defaults.Endpoints[epIndex] || + jsPlumb.Defaults.Endpoint; + }; + + this.makeTarget = function(el, params, referenceParams) { + + var p = jsPlumb.extend({_jsPlumb:_currentInstance}, referenceParams); + jsPlumb.extend(p, params); + _setEndpointPaintStylesAndAnchor(p, 1); + var jpcl = jsPlumb.CurrentLibrary, + targetScope = p.scope || _currentInstance.Defaults.Scope, + deleteEndpointsOnDetach = !(p.deleteEndpointsOnDetach === false), + maxConnections = p.maxConnections || -1, + _doOne = function(_el) { + + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el); + _targetEndpointDefinitions[elid] = p; + _targetEndpointsUnique[elid] = p.uniqueEndpoint, + _targetMaxConnections[elid] = maxConnections, + _targetsEnabled[elid] = true, + proxyComponent = new jsPlumbUIComponent(p); + + var dropOptions = jsPlumb.extend({}, p.dropOptions || {}), + _drop = function() { + + var originalEvent = jsPlumb.CurrentLibrary.getDropEvent(arguments), + targetCount = _currentInstance.select({target:elid}).length; + + if (!_targetsEnabled[elid] || _targetMaxConnections[elid] > 0 && targetCount >= _targetMaxConnections[elid]){ + console.log("target element " + elid + " is full."); + return false; + } + + _currentInstance.currentlyDragging = false; + var draggable = _getElementObject(jpcl.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + // restore the original scope if necessary (issue 57) + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id], + source = jpc.endpoints[0], + _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : {}; + + // unlock the source anchor to allow it to refresh its position if necessary + source.anchor.locked = false; + + if (scope) jpcl.setDragScope(draggable, scope); + + // check if drop is allowed here. + //var _continue = jpc.isDropAllowed(jpc.sourceId, _getId(_el), jpc.scope); + var _continue = proxyComponent.isDropAllowed(jpc.sourceId, _getId(_el), jpc.scope, jpc, null); + + // regardless of whether the connection is ok, reconfigure the existing connection to + // point at the current info. we need this to be correct for the detach event that will follow. + // clear the source endpoint from the list to detach. we will detach this connection at this + // point, but we want to keep the source endpoint. the target is a floating endpoint and should + // be removed. TODO need to figure out whether this code can result in endpoints kicking around + // when they shouldnt be. like is this a full detach of a connection? can it be? + if (jpc.endpointsToDeleteOnDetach) { + if (source === jpc.endpointsToDeleteOnDetach[0]) + jpc.endpointsToDeleteOnDetach[0] = null; + else if (source === jpc.endpointsToDeleteOnDetach[1]) + jpc.endpointsToDeleteOnDetach[1] = null; + } + // reinstate any suspended endpoint; this just puts the connection back into + // a state in which it will report sensible values if someone asks it about + // its target. we're going to throw this connection away shortly so it doesnt matter + // if we manipulate it a bit. + if (jpc.suspendedEndpoint) { + jpc.targetId = jpc.suspendedEndpoint.elementId; + jpc.target = jpcl.getElementObject(jpc.suspendedEndpoint.elementId); + jpc.endpoints[1] = jpc.suspendedEndpoint; + } + + if (_continue) { + + // detach this connection from the source. + source.detach(jpc, false, true, false); + + // make a new Endpoint for the target + var newEndpoint = _targetEndpoints[elid] || _currentInstance.addEndpoint(_el, p); + if (p.uniqueEndpoint) _targetEndpoints[elid] = newEndpoint; // may of course just store what it just pulled out. that's ok. + newEndpoint._makeTargetCreator = true; + + // if the anchor has a 'positionFinder' set, then delegate to that function to find + // out where to locate the anchor. + if (newEndpoint.anchor.positionFinder != null) { + var dropPosition = jpcl.getUIPosition(arguments), + elPosition = jpcl.getOffset(_el), + elSize = jpcl.getSize(_el), + ap = newEndpoint.anchor.positionFinder(dropPosition, elPosition, elSize, newEndpoint.anchor.constructorParams); + newEndpoint.anchor.x = ap[0]; + newEndpoint.anchor.y = ap[1]; + // now figure an orientation for it..kind of hard to know what to do actually. probably the best thing i can do is to + // support specifying an orientation in the anchor's spec. if one is not supplied then i will make the orientation + // be what will cause the most natural link to the source: it will be pointing at the source, but it needs to be + // specified in one axis only, and so how to make that choice? i think i will use whichever axis is the one in which + // the target is furthest away from the source. + } + var c = _currentInstance.connect({ + source:source, + target:newEndpoint, + scope:scope, + previousConnection:jpc, + container:jpc.parent, + deleteEndpointsOnDetach:deleteEndpointsOnDetach, + // 'endpointWillMoveAfterConnection' is set by the makeSource function, and it indicates that the + // given endpoint will actually transfer from the element it is currently attached to to some other + // element after a connection has been established. in that case, we do not want to fire the + // connection event, since it will have the wrong data in it; makeSource will do it for us. + // this is controlled by the 'parent' parameter on a makeSource call. + doNotFireConnectionEvent:source.endpointWillMoveAfterConnection + }); + + // delete the original target endpoint. but only want to do this if the endpoint was created + // automatically and has no other connections. + if (jpc.endpoints[1]._makeTargetCreator && jpc.endpoints[1].connections.length < 2) + _currentInstance.deleteEndpoint(jpc.endpoints[1]); + + if (deleteEndpointsOnDetach) + c.endpointsToDeleteOnDetach = [ source, newEndpoint ]; + + c.repaint(); + } + // if not allowed to drop... + else { + // TODO this code is identical (pretty much) to what happens when a connection + // dragged from a normal endpoint is in this situation. refactor. + // is this an existing connection, and will we reattach? + if (jpc.suspendedEndpoint) { + if (source.isReattach) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + _currentInstance.repaint(source.elementId); + } + else + source.detach(jpc, false, true, true, originalEvent); // otherwise, detach the connection and tell everyone about it. + } + + } + }; + + var dropEvent = jpcl.dragEvents['drop']; + dropOptions["scope"] = dropOptions["scope"] || targetScope; + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop); + + jpcl.initDroppable(_el, dropOptions, true); + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + + return _currentInstance; + }; + + /* + * Function: unmakeTarget + * Sets the given element to no longer be a connection target. + * Parameters: + * el - either a string id or a selector representing the element. + * Returns: + * The current jsPlumb instance. + */ + this.unmakeTarget = function(el, doNotClearArrays) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + var elid = _getId(el); + + // TODO this is not an exhaustive unmake of a target, since it does not remove the droppable stuff from + // the element. the effect will be to prevent it form behaving as a target, but it's not completely purged. + if (!doNotClearArrays) { + delete _targetEndpointDefinitions[elid]; + delete _targetEndpointsUnique[elid]; + delete _targetMaxConnections[elid]; + delete _targetsEnabled[elid]; + } + + return _currentInstance; + }; + + /** + * helper method to make a list of elements drop targets. + * @param els + * @param params + * @param referenceParams + * @return + */ + this.makeTargets = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeTarget(els[i], params, referenceParams); + } + }; + + /** + * Function: makeSource + * Makes some DOM element a Connection source, allowing you to drag connections from it + * without having to register any Endpoints on it first. When a Connection is established, + * the endpoint spec that was passed in to this method is used to create a suitable + * Endpoint (the default will be used if you do not provide one). + * + * Parameters: + * el - string id or element selector for the element to make a source. + * params - JS object containing parameters: + * endpoint optional. specification of an endpoint to create when a connection is created. + * parent optional. the element to add Endpoints to when a Connection is established. if you omit this, + * Endpoints will be added to 'el'. + * scope optional. scope for the connections dragged from this element. + * dragOptions optional. same stuff as you would pass to dragOptions of an Endpoint definition. + * deleteEndpointsOnDetach optional, defaults to false. whether or not to delete + * any Endpoints created by a connection from this source if + * the connection is subsequently detached. this will not + * remove Endpoints that have had more Connections attached + * to them after they were created. + * + * + */ + var _sourceEndpointDefinitions = {}, + _sourceEndpoints = {}, + _sourceEndpointsUnique = {}, + _sourcesEnabled = {}, + _sourceTriggers = {}, + _targetsEnabled = {}; + + this.makeSource = function(el, params, referenceParams) { + var p = jsPlumb.extend({}, referenceParams); + jsPlumb.extend(p, params); + _setEndpointPaintStylesAndAnchor(p, 0); + var jpcl = jsPlumb.CurrentLibrary, + _doOne = function(_el) { + // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these, + // and use the endpoint definition if found. + var elid = _getId(_el), + parent = p.parent, + idToRegisterAgainst = parent != null ? _currentInstance.getId(jpcl.getElementObject(parent)) : elid; + + _sourceEndpointDefinitions[idToRegisterAgainst] = p; + _sourceEndpointsUnique[idToRegisterAgainst] = p.uniqueEndpoint; + _sourcesEnabled[idToRegisterAgainst] = true; + + var stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"], + dragOptions = jsPlumb.extend({ }, p.dragOptions || {}), + existingDrag = dragOptions.drag, + existingStop = dragOptions.stop, + ep = null, + endpointAddedButNoDragYet = false; + + // set scope if its not set in dragOptions but was passed in in params + dragOptions["scope"] = dragOptions["scope"] || p.scope; + + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + if (existingDrag) existingDrag.apply(this, arguments); + endpointAddedButNoDragYet = false; + }); + + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], function() { + if (existingStop) existingStop.apply(this, arguments); + + //_currentlyDown = false; + _currentInstance.currentlyDragging = false; + + if (ep.connections.length == 0) + _currentInstance.deleteEndpoint(ep); + else { + + jpcl.unbind(ep.canvas, "mousedown"); + + // reset the anchor to the anchor that was initially provided. the one we were using to drag + // the connection was just a placeholder that was located at the place the user pressed the + // mouse button to initiate the drag. + var anchorDef = p.anchor || _currentInstance.Defaults.Anchor, + oldAnchor = ep.anchor, + oldConnection = ep.connections[0]; + + ep.anchor = _currentInstance.makeAnchor(anchorDef, elid, _currentInstance); + + if (p.parent) { + var parent = jpcl.getElementObject(p.parent); + if (parent) { + var currentId = ep.elementId; + + ep.setElement(parent); + ep.endpointWillMoveAfterConnection = false; + _currentInstance.anchorManager.rehomeEndpoint(currentId, parent); + oldConnection.previousConnection = null; + // remove from connectionsByScope + _removeWithFunction(connectionsByScope[oldConnection.scope], function(c) { + return c.id === oldConnection.id; + }); + _currentInstance.anchorManager.connectionDetached({ + sourceId:oldConnection.sourceId, + targetId:oldConnection.targetId, + connection:oldConnection + }); + _finaliseConnection(oldConnection); + } + } + + ep.repaint(); + _currentInstance.repaint(ep.elementId); + _currentInstance.repaint(oldConnection.targetId); + + } + }); + // when the user presses the mouse, add an Endpoint, if we are enabled. + var mouseDownListener = function(e) { + + if (!_sourcesEnabled[idToRegisterAgainst]) return; + + // make sure we have the latest offset for this div + var myOffsetInfo = _updateOffset({elId:elid}); + + var x = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width, + y = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height, + parentX = x, + parentY = y; + + // if there is a parent, the endpoint will actually be added to it now, rather than the div + // that was the source. in that case, we have to adjust the anchor position so it refers to + // the parent. + if (p.parent) { + var pEl = jsPlumb.CurrentLibrary.getElementObject(p.parent), + pId = _getId(pEl); + myOffsetInfo = _updateOffset({elId:pId}); + parentX = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width, + parentY = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height; + } + + // we need to override the anchor in here, and force 'isSource', but we don't want to mess with + // the params passed in, because after a connection is established we're going to reset the endpoint + // to have the anchor we were given. + var tempEndpointParams = {}; + jsPlumb.extend(tempEndpointParams, p); + tempEndpointParams.isSource = true; + tempEndpointParams.anchor = [x,y,0,0]; + tempEndpointParams.parentAnchor = [ parentX, parentY, 0, 0 ]; + tempEndpointParams.dragOptions = dragOptions; + // if a parent was given we need to turn that into a "container" argument. this is, by default, + // the parent of the element we will move to, so parent of p.parent in this case. however, if + // the user has specified a 'container' on the endpoint definition or on + // the defaults, we should use that. + if (p.parent) { + var potentialParent = tempEndpointParams.container || _currentInstance.Defaults.Container; + if (potentialParent) + tempEndpointParams.container = potentialParent; + else + tempEndpointParams.container = jsPlumb.CurrentLibrary.getParent(p.parent); + } + + ep = _currentInstance.addEndpoint(elid, tempEndpointParams); + + endpointAddedButNoDragYet = true; + // we set this to prevent connections from firing attach events before this function has had a chance + // to move the endpoint. + ep.endpointWillMoveAfterConnection = p.parent != null; + ep.endpointWillMoveTo = p.parent ? jpcl.getElementObject(p.parent) : null; + + var _delTempEndpoint = function() { + // this mouseup event is fired only if no dragging occurred, by jquery and yui, but for mootools + // it is fired even if dragging has occurred, in which case we would blow away a perfectly + // legitimate endpoint, were it not for this check. the flag is set after adding an + // endpoint and cleared in a drag listener we set in the dragOptions above. + if(endpointAddedButNoDragYet) { + _currentInstance.deleteEndpoint(ep); + } + }; + + _currentInstance.registerListener(ep.canvas, "mouseup", _delTempEndpoint); + _currentInstance.registerListener(_el, "mouseup", _delTempEndpoint); + + // and then trigger its mousedown event, which will kick off a drag, which will start dragging + // a new connection from this endpoint. + jpcl.trigger(ep.canvas, "mousedown", e); + }; + + // register this on jsPlumb so that it can be cleared by a reset. + _currentInstance.registerListener(_el, "mousedown", mouseDownListener); + _sourceTriggers[elid] = mouseDownListener; + }; + + el = _convertYUICollection(el); + + var inputs = el.length && el.constructor != String ? el : [ el ]; + + for (var i = 0; i < inputs.length; i++) { + _doOne(_getElementObject(inputs[i])); + } + + return _currentInstance; + }; + + /** + * Function: unmakeSource + * Sets the given element to no longer be a connection source. + * Parameters: + * el - either a string id or a selector representing the element. + * Returns: + * The current jsPlumb instance. + */ + this.unmakeSource = function(el, doNotClearArrays) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + var id = _getId(el), + mouseDownListener = _sourceTriggers[id]; + + if (mouseDownListener) + _currentInstance.unregisterListener(_el, "mousedown", mouseDownListener); + + if (!doNotClearArrays) { + delete _sourceEndpointDefinitions[id]; + delete _sourceEndpointsUnique[id]; + delete _sourcesEnabled[id]; + delete _sourceTriggers[id]; + } + + return _currentInstance; + }; + + /* + * Function: unmakeEverySource + * Resets all elements in this instance of jsPlumb so that none of them are connection sources. + * Returns: + * The current jsPlumb instance. + */ + this.unmakeEverySource = function() { + for (var i in _sourcesEnabled) + _currentInstance.unmakeSource(i, true); + + _sourceEndpointDefinitions = {}; + _sourceEndpointsUnique = {}; + _sourcesEnabled = {}; + _sourceTriggers = {}; + }; + + /* + * Function: unmakeEveryTarget + * Resets all elements in this instance of jsPlumb so that none of them are connection targets. + * Returns: + * The current jsPlumb instance. + */ + this.unmakeEveryTarget = function() { + for (var i in _targetsEnabled) + _currentInstance.unmakeTarget(i, true); + + _targetEndpointDefinitions = {}; + _targetEndpointsUnique = {}; + _targetMaxConnections = {}; + _targetsEnabled = {}; + + return _currentInstance; + }; + + /* + * Function: makeSources + * Makes all elements in some array or a selector connection sources. + * Parameters: + * els - either an array of ids or a selector + * params - parameters to configure each element as a source with + * referenceParams - extra parameters to configure each element as a source with. + * + * Returns: + * The current jsPlumb instance. + */ + this.makeSources = function(els, params, referenceParams) { + for ( var i = 0; i < els.length; i++) { + _currentInstance.makeSource(els[i], params, referenceParams); + } + + return _currentInstance; + }; + + // does the work of setting a source enabled or disabled. + var _setEnabled = function(type, el, state, toggle) { + var a = type == "source" ? _sourcesEnabled : _targetsEnabled; + + if (_isString(el)) a[el] = toggle ? !a[el] : state; + else if (el.length) { + el = _convertYUICollection(el); + for (var i = 0; i < el.length; i++) { + var id = _el = jsPlumb.CurrentLibrary.getElementObject(el[i]), id = _getId(_el); + a[id] = toggle ? !a[id] : state; + } + } + return _currentInstance; + }; + + /* + Function: setSourceEnabled + Sets the enabled state of one or more elements that were previously made a connection source with the makeSource + method. + + Parameters: + el - either a string representing some element's id, or an array of ids, or a selector. + state - true to enable the element(s), false to disable it. + + Returns: + The current jsPlumb instance. + */ + this.setSourceEnabled = function(el, state) { + return _setEnabled("source", el, state); + }; + + /* + * Function: toggleSourceEnabled + * Toggles the source enabled state of the given element or elements. + * + * Parameters: + * el - either a string representing some element's id, or an array of ids, or a selector. + * state - true to enable the element(s), false to disable it. + * + * Returns: + * The current enabled state of the source. + */ + this.toggleSourceEnabled = function(el) { + _setEnabled("source", el, null, true); + return _currentInstance.isSourceEnabled(el); + }; + + /* + * Function: isSource + * Returns whether or not the given element is registered as a connection source. + * + * Parameters: + * el - either a string id, or a selector representing a single element. + * + * Returns: + * True if source, false if not. + */ + this.isSource = function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return _sourcesEnabled[_getId(el)] != null; + }; + + /* + * Function: isSourceEnabled + * Returns whether or not the given connection source is enabled. + * + * Parameters: + * el - either a string id, or a selector representing a single element. + * + * Returns: + * True if enabled, false if not. + */ + this.isSourceEnabled = function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return _sourcesEnabled[_getId(el)] === true; + }; + + /* + * Function: setTargetEnabled + * Sets the enabled state of one or more elements that were previously made a connection target with the makeTarget method. + * method. + * + * Parameters: + * el - either a string representing some element's id, or an array of ids, or a selector. + * state - true to enable the element(s), false to disable it. + * + * Returns: + * The current jsPlumb instance. + */ + this.setTargetEnabled = function(el, state) { + return _setEnabled("target", el, state); + }; + + /* + * Function: toggleTargetEnabled + * Toggles the target enabled state of the given element or elements. + * + * Parameters: + * el - either a string representing some element's id, or an array of ids, or a selector. + * state - true to enable the element(s), false to disable it. + * + * Returns: + * The current enabled state of the target. + */ + this.toggleTargetEnabled = function(el) { + return _setEnabled("target", el, null, true); + return _currentInstance.isTargetEnabled(el); + }; + + /* + Function: isTarget + Returns whether or not the given element is registered as a connection target. + + Parameters: + el - either a string id, or a selector representing a single element. + + Returns: + True if source, false if not. + */ + this.isTarget = function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return _targetsEnabled[_getId(el)] != null; + }; + + /* + Function: isTargetEnabled + Returns whether or not the given connection target is enabled. + + Parameters: + el - either a string id, or a selector representing a single element. + + Returns: + True if enabled, false if not. + */ + this.isTargetEnabled = function(el) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + return _targetsEnabled[_getId(el)] === true; + }; + + /* + Function: ready + Helper method to bind a function to jsPlumb's ready event. + */ + this.ready = function(fn) { + _currentInstance.bind("ready", fn); + }, + + /* + Function: repaint + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + el - either the id of the element or a selector representing the element. + + Returns: + void + + See Also: + + */ + this.repaint = function(el) { + var _processElement = function(el) { _draw(_getElementObject(el)); }; + // support both lists... + if (typeof el == 'object') + for ( var i = 0; i < el.length; i++) _processElement(el[i]); + else // ...and single strings. + _processElement(el); + }; + + /* + Function: repaintEverything + Repaints all connections. + + Returns: + void + + See Also: + + */ + this.repaintEverything = function() { + for ( var elId in endpointsByElement) { + _draw(_getElementObject(elId), null, null); + } + }; + + /* + Function: removeAllEndpoints + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + el - either an element id, or a selector for an element. + + Returns: + void + + See Also: + + */ + this.removeAllEndpoints = function(el) { + var elId = _getAttribute(el, "id"), + ebe = endpointsByElement[elId]; + if (ebe) { + for ( var i = 0; i < ebe.length; i++) + _currentInstance.deleteEndpoint(ebe[i]); + } + endpointsByElement[elId] = []; + }; + + /* + Removes every Endpoint in this instance of jsPlumb. + @deprecated use deleteEveryEndpoint instead + */ + this.removeEveryEndpoint = this.deleteEveryEndpoint; + + /* + Removes the given Endpoint from the given element. + @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant). + */ + this.removeEndpoint = function(el, endpoint) { + _currentInstance.deleteEndpoint(endpoint); + }; + + var _registeredListeners = {}, + _unbindRegisteredListeners = function() { + for (var i in _registeredListeners) { + for (var j = 0; j < _registeredListeners[i].length; j++) { + var info = _registeredListeners[i][j]; + jsPlumb.CurrentLibrary.unbind(info.el, info.event, info.listener); + } + } + _registeredListeners = {}; + }; + + // internal register listener method. gives us a hook to clean things up + // with if the user calls jsPlumb.reset. + this.registerListener = function(el, type, listener) { + jsPlumb.CurrentLibrary.bind(el, type, listener); + _addToList(_registeredListeners, type, {el:el, event:type, listener:listener}); + }; + + this.unregisterListener = function(el, type, listener) { + jsPlumb.CurrentLibrary.unbind(el, type, listener); + _removeWithFunction(_registeredListeners, function(rl) { + return rl.type == type && rl.listener == listener; + }); + }; + + /* + Function:reset + Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this. + */ + this.reset = function() { + _currentInstance.deleteEveryEndpoint(); + _currentInstance.clearListeners(); + _targetEndpointDefinitions = {}; + _targetEndpoints = {}; + _targetEndpointsUnique = {}; + _targetMaxConnections = {}; + _sourceEndpointDefinitions = {}; + _sourceEndpoints = {}; + _sourceEndpointsUnique = {}; + _unbindRegisteredListeners(); + _currentInstance.anchorManager.reset(); + _currentInstance.dragManager.reset(); + }; + + /* + Function: setAutomaticRepaint + Sets/unsets automatic repaint on window resize. + + Parameters: + value - whether or not to automatically repaint when the window is resized. + + Returns: void + * + this.setAutomaticRepaint = function(value) { + automaticRepaint = value; + };*/ + + /* + * Function: setDefaultScope + * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a + * scope to an Endpoint or Connection allows you to support different + * types of Connections in the same UI. If you're only interested in + * one type of Connection, you don't need to supply a scope. This method + * will probably be used by very few people; it just instructs jsPlumb + * to use a different key for the default scope. + * + * Parameters: + * scope - scope to set as default. + */ + this.setDefaultScope = function(scope) { + DEFAULT_SCOPE = scope; + }; + + /* + * Function: setDraggable + * Sets whether or not a given element is + * draggable, regardless of what any jsPlumb command may request. + * + * Parameters: + * el - either the id for the element, or a selector representing the element. + * + * Returns: + * void + */ + this.setDraggable = _setDraggable; + + /* + * Function: setId + * Changes the id of some element, adjusting all connections and endpoints + * + * Parameters: + * el - a selector, a DOM element, or a string. + * newId - string. + */ + this.setId = function(el, newId, doNotSetAttribute) { + + var id = el.constructor == String ? el : _currentInstance.getId(el), + sConns = _currentInstance.getConnections({source:id, scope:'*'}, true), + tConns = _currentInstance.getConnections({target:id, scope:'*'}, true); + + newId = "" + newId; + + if (!doNotSetAttribute) { + el = jsPlumb.CurrentLibrary.getElementObject(id); + jsPlumb.CurrentLibrary.setAttribute(el, "id", newId); + } + + el = jsPlumb.CurrentLibrary.getElementObject(newId); + + + endpointsByElement[newId] = endpointsByElement[id] || []; + for (var i = 0; i < endpointsByElement[newId].length; i++) { + endpointsByElement[newId][i].elementId = newId; + endpointsByElement[newId][i].element = el; + endpointsByElement[newId][i].anchor.elementId = newId; + } + delete endpointsByElement[id]; + + _currentInstance.anchorManager.changeId(id, newId); + + var _conns = function(list, epIdx, type) { + for (var i = 0; i < list.length; i++) { + list[i].endpoints[epIdx].elementId = newId; + list[i].endpoints[epIdx].element = el; + list[i][type + "Id"] = newId; + list[i][type] = el; + } + }; + _conns(sConns, 0, "source"); + _conns(tConns, 1, "target"); + }; + + /* + * Function: setIdChanged + * Notify jsPlumb that the element with oldId has had its id changed to newId. + */ + this.setIdChanged = function(oldId, newId) { + _currentInstance.setId(oldId, newId, true); + }; + + this.setDebugLog = function(debugLog) { + log = debugLog; + }; + + /* + * Function: setRepaintFunction + * Sets the function to fire when the window size has changed and a repaint was fired. + * + * Parameters: + * f - Function to execute. + * + * Returns: void + */ + this.setRepaintFunction = function(f) { + repaintFunction = f; + }; + + /* + * Function: setSuspendDrawing + * Suspends drawing operations. This can be used when you have a lot of connections to make or endpoints to register; + * it will save you a lot of time. + */ + this.setSuspendDrawing = _setSuspendDrawing; + + /* + * Constant for use with the setRenderMode method + */ + this.CANVAS = "canvas"; + + /* + * Constant for use with the setRenderMode method + */ + this.SVG = "svg"; + + this.VML = "vml"; + + /* + * Function: setRenderMode + * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that + * what you asked for is not supported (and that VML is). If you asked for VML but the browser does + * not support it, jsPlumb uses SVG. + * + * Returns: + * the render mode that jsPlumb set, which of course may be different from that requested. + */ + this.setRenderMode = function(mode) { + if (mode) + mode = mode.toLowerCase(); + else + return; + if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML"); + // now test we actually have the capability to do this. + if (mode === jsPlumb.CANVAS && canvasAvailable) + renderMode = jsPlumb.CANVAS; + else if (mode === jsPlumb.SVG && svgAvailable) + renderMode = jsPlumb.SVG; + else if (vmlAvailable()) + renderMode = jsPlumb.VML; + + return renderMode; + }; + + this.getRenderMode = function() { return renderMode; }; + + /* + * Function: show + * Sets an element's connections to be visible. + * + * Parameters: + * el - either the id of the element, or a selector for the element. + * changeEndpoints - whether or not to also change the visible state of the endpoints on the element. this also has a bearing on + * other connections on those endpoints: if their other endpoint is also visible, the connections are made visible. + * + * Returns: + * void + */ + this.show = function(el, changeEndpoints) { + _setVisible(el, "block", changeEndpoints); + }; + + /* + * Function: sizeCanvas + * Helper to size a canvas. You would typically use + * this when writing your own Connector or Endpoint implementation. + * + * Parameters: + * x - [int] x position for the Canvas origin + * y - [int] y position for the Canvas origin + * w - [int] width of the canvas + * h - [int] height of the canvas + * + * Returns: + * void + */ + this.sizeCanvas = function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; + canvas.height = h; + canvas.style.width = w + "px"; + canvas.width = w; + canvas.style.left = x + "px"; + canvas.style.top = y + "px"; + } + }; + + /** + * gets some test hooks. nothing writable. + */ + this.getTestHarness = function() { + return { + endpointsByElement : endpointsByElement, + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + connectionCount : function(scope) { + scope = scope || DEFAULT_SCOPE; + var c = connectionsByScope[scope]; + return c ? c.length : 0; + }, + //findIndex : _findIndex, + getId : _getId, + makeAnchor:self.makeAnchor, + makeDynamicAnchor:self.makeDynamicAnchor + }; + }; + + /** + * Toggles visibility of an element's connections. kept for backwards + * compatibility + */ + this.toggle = _toggleVisible; + + /* + * Function: toggleVisible + * Toggles visibility of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * changeEndpoints - whether or not to also toggle the endpoints on the element. + * + * Returns: + * void, but should be updated to return the current state + */ + // TODO: update this method to return the current state. + this.toggleVisible = _toggleVisible; + + /* + * Function: toggleDraggable + * Toggles draggability (sic?) of an element's Connections. + * + * Parameters: + * el - either the element's id, or a selector representing the element. + * + * Returns: + * The current draggable state. + */ + this.toggleDraggable = _toggleDraggable; + + /* + * Function: unload + * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element. + * + * Returns: + * void + */ + this.unload = function() { + // this used to do something, but it turns out that what it did was nothing. + // now it exists only for backwards compatibility. + }; + + /* + * Helper method to wrap an existing function with one of + * your own. This is used by the various implementations to wrap event + * callbacks for drag/drop etc; it allows jsPlumb to be transparent in + * its handling of these things. If a user supplies their own event + * callback, for anything, it will always be called. + */ + this.wrap = _wrap; + this.addListener = this.bind; + + var adjustForParentOffsetAndScroll = function(xy, el) { + + var offsetParent = null, result = xy; + if (el.tagName.toLowerCase() === "svg" && el.parentNode) { + offsetParent = el.parentNode; + } + else if (el.offsetParent) { + offsetParent = el.offsetParent; + } + if (offsetParent != null) { + var po = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(offsetParent), + so = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : {left:offsetParent.scrollLeft, top:offsetParent.scrollTop}; + + + // i thought it might be cool to do this: + // lastReturnValue[0] = lastReturnValue[0] - offsetParent.offsetLeft + offsetParent.scrollLeft; + // lastReturnValue[1] = lastReturnValue[1] - offsetParent.offsetTop + offsetParent.scrollTop; + // but i think it ignores margins. my reasoning was that it's quicker to not hand off to some underlying + // library. + + result[0] = xy[0] - po.left + so.left; + result[1] = xy[1] - po.top + so.top; + } + + return result; + + }; + + /** + * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user + * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"), + * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the + * creation of Anchors without user intervention. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; + this.y = params.y || 0; + this.elementId = params.elementId; + var orientation = params.orientation || [ 0, 0 ]; + var lastTimestamp = null, lastReturnValue = null; + this.offsets = params.offsets || [ 0, 0 ]; + self.timestamp = null; + this.compute = function(params) { + var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; + + if (timestamp && timestamp === self.timestamp) + return lastReturnValue; + + lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + + // adjust loc if there is an offsetParent + lastReturnValue = adjustForParentOffsetAndScroll(lastReturnValue, element.canvas); + + self.timestamp = timestamp; + return lastReturnValue; + }; + + this.getOrientation = function(_endpoint) { return orientation; }; + + this.equals = function(anchor) { + if (!anchor) return false; + var ao = anchor.getOrientation(); + var o = this.getOrientation(); + return this.x == anchor.x && this.y == anchor.y + && this.offsets[0] == anchor.offsets[0] + && this.offsets[1] == anchor.offsets[1] + && o[0] == ao[0] && o[1] == ao[1]; + }; + + this.getCurrentLocation = function() { return lastReturnValue; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from + * its position relative to the anchor it is floating relative to. It is used when creating + * a connection through drag and drop. + * + * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for + // purposes of calculating the orientation. + var ref = params.reference, + // the canvas this refers to. + refCanvas = params.referenceCanvas, + size = _getSize(_getElementObject(refCanvas)), + + // these are used to store the current relative position of our + // anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these + // values are written by the compute method, and read + // by the getOrientation method. + xDir = 0, yDir = 0, + // temporary member used to store an orientation when the floating + // anchor is hovering over another anchor. + orientation = null, + _lastResult = null; + + // set these to 0 each; they are used by certain types of connectors in the loopback case, + // when the connector is trying to clear the element it is on. but for floating anchor it's not + // very important. + this.x = 0; this.y = 0; + + this.isFloating = true; + + this.compute = function(params) { + var xy = params.xy, element = params.element, + result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + + // adjust loc if there is an offsetParent + result = adjustForParentOffsetAndScroll(result, element.canvas); + + _lastResult = result; + return result; + }; + + this.getOrientation = function(_endpoint) { + if (orientation) return orientation; + else { + var o = ref.getOrientation(_endpoint); + // here we take into account the orientation of the other + // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come + // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense. + return [ Math.abs(o[0]) * xDir * -1, + Math.abs(o[1]) * yDir * -1 ]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering + * over another anchor; we want to assume that anchor's orientation + * for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no + * longer hovering over another anchor; we should resume calculating + * orientation as we normally do. + */ + this.out = function() { orientation = null; }; + + this.getCurrentLocation = function() { return _lastResult; }; + }; + + /* + * A DynamicAnchor is an Anchor that contains a list of other Anchors, which it cycles + * through at compute time to find the one that is located closest to + * the center of the target element, and returns that Anchor's compute + * method result. this causes endpoints to follow each other with + * respect to the orientation of their target elements, which is a useful + * feature for some applications. + * + */ + var DynamicAnchor = function(anchors, anchorSelector, elementId) { + this.isSelective = true; + this.isDynamic = true; + var _anchors = [], self = this, + _convert = function(anchor) { + return anchor.constructor == Anchor ? anchor: _currentInstance.makeAnchor(anchor, elementId, _currentInstance); + }; + for (var i = 0; i < anchors.length; i++) + _anchors[i] = _convert(anchors[i]); + this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); }; + this.getAnchors = function() { return _anchors; }; + this.locked = false; + var _curAnchor = _anchors.length > 0 ? _anchors[0] : null, + _curIndex = _anchors.length > 0 ? 0 : -1, + self = this, + + // helper method to calculate the distance between the centers of the two elements. + _distance = function(anchor, cx, cy, xy, wh) { + var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]); + return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)); + }, + + // default method uses distance between element centers. you can provide your own method in the dynamic anchor + // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: + // xy - xy loc of the anchor's element + // wh - anchor's element's dimensions + // txy - xy loc of the element of the other anchor in the connection + // twh - dimensions of the element of the other anchor in the connection. + // anchors - the list of selectable anchors + _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) { + var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2); + var minIdx = -1, minDist = Infinity; + for ( var i = 0; i < anchors.length; i++) { + var d = _distance(anchors[i], cx, cy, xy, wh); + if (d < minDist) { + minIdx = i + 0; + minDist = d; + } + } + return anchors[minIdx]; + }; + + this.compute = function(params) { + var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh; + // if anchor is locked or an opposite element was not given, we + // maintain our state. anchor will be locked + // if it is the source of a drag and drop. + if (self.locked || txy == null || twh == null) + return _curAnchor.compute(params); + else + params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute. + + _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors); + self.x = _curAnchor.x; + self.y = _curAnchor.y; + + return _curAnchor.compute(params); + }; + + this.getCurrentLocation = function() { + return _curAnchor != null ? _curAnchor.getCurrentLocation() : null; + }; + + this.getOrientation = function(_endpoint) { return _curAnchor != null ? _curAnchor.getOrientation(_endpoint) : [ 0, 0 ]; }; + this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); }; + this.out = function() { if (_curAnchor != null) _curAnchor.out(); }; + }; + + /* + manages anchors for all elements. + */ + // "continuous" anchors: anchors that pick their location based on how many connections the given element has. + // this requires looking at a lot more elements than normal - anything that has had a Continuous anchor applied has + // to be recalculated. so this manager is used as a reference point. the first time, with a new timestamp, that + // a continuous anchor is asked to compute, it calls this guy. or maybe, even, this guy gets called somewhere else + // and compute only ever returns pre-computed values. either way, this is the central point, and we want it to + // be called as few times as possible. + var continuousAnchors = {}, + continuousAnchorLocations = {}, + continuousAnchorOrientations = {}, + Orientation = { HORIZONTAL : "horizontal", VERTICAL : "vertical", DIAGONAL : "diagonal", IDENTITY:"identity" }, + + // TODO this functions uses a crude method of determining orientation between two elements. + // 'diagonal' should be chosen when the angle of the line between the two centers is around + // one of 45, 135, 225 and 315 degrees. maybe +- 15 degrees. + calculateOrientation = function(sourceId, targetId, sd, td) { + + if (sourceId === targetId) return { + orientation:Orientation.IDENTITY, + a:["top", "top"] + }; + + var theta = Math.atan2((td.centery - sd.centery) , (td.centerx - sd.centerx)), + theta2 = Math.atan2((sd.centery - td.centery) , (sd.centerx - td.centerx)), + h = ((sd.left <= td.left && sd.right >= td.left) || (sd.left <= td.right && sd.right >= td.right) || + (sd.left <= td.left && sd.right >= td.right) || (td.left <= sd.left && td.right >= sd.right)), + v = ((sd.top <= td.top && sd.bottom >= td.top) || (sd.top <= td.bottom && sd.bottom >= td.bottom) || + (sd.top <= td.top && sd.bottom >= td.bottom) || (td.top <= sd.top && td.bottom >= sd.bottom)); + + if (! (h || v)) { + var a = null, rls = false, rrs = false, sortValue = null; + if (td.left > sd.left && td.top > sd.top) + a = ["right", "top"]; + else if (td.left > sd.left && sd.top > td.top) + a = [ "top", "left"]; + else if (td.left < sd.left && td.top < sd.top) + a = [ "top", "right"]; + else if (td.left < sd.left && td.top > sd.top) + a = ["left", "top" ]; + + return { orientation:Orientation.DIAGONAL, a:a, theta:theta, theta2:theta2 }; + } + else if (h) return { + orientation:Orientation.HORIZONTAL, + a:sd.top < td.top ? ["bottom", "top"] : ["top", "bottom"], + theta:theta, theta2:theta2 + } + else return { + orientation:Orientation.VERTICAL, + a:sd.left < td.left ? ["right", "left"] : ["left", "right"], + theta:theta, theta2:theta2 + } + }, + placeAnchorsOnLine = function(desc, elementDimensions, elementPosition, + connections, horizontal, otherMultiplier, reverse) { + var a = [], step = elementDimensions[horizontal ? 0 : 1] / (connections.length + 1); + + for (var i = 0; i < connections.length; i++) { + var val = (i + 1) * step, other = otherMultiplier * elementDimensions[horizontal ? 1 : 0]; + if (reverse) + val = elementDimensions[horizontal ? 0 : 1] - val; + + var dx = (horizontal ? val : other), x = elementPosition[0] + dx, xp = dx / elementDimensions[0], + dy = (horizontal ? other : val), y = elementPosition[1] + dy, yp = dy / elementDimensions[1]; + + a.push([ x, y, xp, yp, connections[i][1], connections[i][2] ]); + } + + return a; + }, + standardEdgeSort = function(a, b) { return a[0] > b[0] ? 1 : -1 }, + currySort = function(reverseAngles) { + return function(a,b) { + var r = true; + if (reverseAngles) { + if (a[0][0] < b[0][0]) + r = true; + else + r = a[0][1] > b[0][1]; + } + else { + if (a[0][0] > b[0][0]) + r= true; + else + r =a[0][1] > b[0][1]; + } + return r === false ? -1 : 1; + }; + }, + leftSort = function(a,b) { + // first get adjusted values + var p1 = a[0][0] < 0 ? -Math.PI - a[0][0] : Math.PI - a[0][0], + p2 = b[0][0] < 0 ? -Math.PI - b[0][0] : Math.PI - b[0][0]; + if (p1 > p2) return 1; + else return a[0][1] > b[0][1] ? 1 : -1; + }, + edgeSortFunctions = { + "top":standardEdgeSort, + "right":currySort(true), + "bottom":currySort(true), + "left":leftSort + }, + _sortHelper = function(_array, _fn) { + return _array.sort(_fn); + }, + placeAnchors = function(elementId, _anchorLists) { + var sS = sizes[elementId], sO = offsets[elementId], + placeSomeAnchors = function(desc, elementDimensions, elementPosition, unsortedConnections, isHorizontal, otherMultiplier, orientation) { + if (unsortedConnections.length > 0) { + var sc = _sortHelper(unsortedConnections, edgeSortFunctions[desc]), // puts them in order based on the target element's pos on screen + reverse = desc === "right" || desc === "top", + anchors = placeAnchorsOnLine(desc, elementDimensions, + elementPosition, sc, + isHorizontal, otherMultiplier, reverse ); + + // takes a computed anchor position and adjusts it for parent offset and scroll, then stores it. + var _setAnchorLocation = function(endpoint, anchorPos) { + var a = adjustForParentOffsetAndScroll([anchorPos[0], anchorPos[1]], endpoint.canvas); + continuousAnchorLocations[endpoint.id] = [ a[0], a[1], anchorPos[2], anchorPos[3] ]; + continuousAnchorOrientations[endpoint.id] = orientation; + }; + + for (var i = 0; i < anchors.length; i++) { + var c = anchors[i][4], weAreSource = c.endpoints[0].elementId === elementId, weAreTarget = c.endpoints[1].elementId === elementId; + if (weAreSource) + _setAnchorLocation(c.endpoints[0], anchors[i]); + else if (weAreTarget) + _setAnchorLocation(c.endpoints[1], anchors[i]); + } + } + }; + + placeSomeAnchors("bottom", sS, [sO.left,sO.top], _anchorLists.bottom, true, 1, [0,1]); + placeSomeAnchors("top", sS, [sO.left,sO.top], _anchorLists.top, true, 0, [0,-1]); + placeSomeAnchors("left", sS, [sO.left,sO.top], _anchorLists.left, false, 0, [-1,0]); + placeSomeAnchors("right", sS, [sO.left,sO.top], _anchorLists.right, false, 1, [1,0]); + }, + AnchorManager = function() { + var _amEndpoints = {}, + connectionsByElementId = {}, + self = this, + anchorLists = {}; + + this.reset = function() { + _amEndpoints = {}; + connectionsByElementId = {}; + anchorLists = {}; + }; + this.newConnection = function(conn) { + var sourceId = conn.sourceId, targetId = conn.targetId, + ep = conn.endpoints, + doRegisterTarget = true, + registerConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + + if ((sourceId == targetId) && otherAnchor.isContinuous){ + // remove the target endpoint's canvas. we dont need it. + jsPlumb.CurrentLibrary.removeElement(ep[1].canvas); + doRegisterTarget = false; + } + _addToList(connectionsByElementId, elId, [c, otherEndpoint, otherAnchor.constructor == DynamicAnchor]); + }; + + registerConnection(0, ep[0], ep[0].anchor, targetId, conn); + if (doRegisterTarget) + registerConnection(1, ep[1], ep[1].anchor, sourceId, conn); + }; + this.connectionDetached = function(connInfo) { + var connection = connInfo.connection || connInfo; + var sourceId = connection.sourceId, + targetId = connection.targetId, + ep = connection.endpoints, + removeConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) { + if (otherAnchor.constructor == FloatingAnchor) { + // no-op + } + else { + _removeWithFunction(connectionsByElementId[elId], function(_c) { + return _c[0].id == c.id; + }); + } + }; + + removeConnection(1, ep[1], ep[1].anchor, sourceId, connection); + removeConnection(0, ep[0], ep[0].anchor, targetId, connection); + + // remove from anchorLists + var sEl = connection.sourceId, + tEl = connection.targetId, + sE = connection.endpoints[0].id, + tE = connection.endpoints[1].id, + _remove = function(list, eId) { + if (list) { // transient anchors dont get entries in this list. + var f = function(e) { return e[4] == eId; }; + _removeWithFunction(list["top"], f); + _removeWithFunction(list["left"], f); + _removeWithFunction(list["bottom"], f); + _removeWithFunction(list["right"], f); + } + }; + + _remove(anchorLists[sEl], sE); + _remove(anchorLists[tEl], tE); + self.redraw(sEl); + self.redraw(tEl); + }; + this.add = function(endpoint, elementId) { + _addToList(_amEndpoints, elementId, endpoint); + }; + this.changeId = function(oldId, newId) { + connectionsByElementId[newId] = connectionsByElementId[oldId]; + _amEndpoints[newId] = _amEndpoints[oldId]; + delete connectionsByElementId[oldId]; + delete _amEndpoints[oldId]; + }; + this.getConnectionsFor = function(elementId) { + return connectionsByElementId[elementId] || []; + }; + this.getEndpointsFor = function(elementId) { + return _amEndpoints[elementId] || []; + }; + this.deleteEndpoint = function(endpoint) { + _removeWithFunction(_amEndpoints[endpoint.elementId], function(e) { + return e.id == endpoint.id; + }); + }; + this.clearFor = function(elementId) { + delete _amEndpoints[elementId]; + _amEndpoints[elementId] = []; + }; + // updates the given anchor list by either updating an existing anchor's info, or adding it. this function + // also removes the anchor from its previous list, if the edge it is on has changed. + // all connections found along the way (those that are connected to one of the faces this function + // operates on) are added to the connsToPaint list, as are their endpoints. in this way we know to repaint + // them wthout having to calculate anything else about them. + var _updateAnchorList = function(lists, theta, order, conn, aBoolean, otherElId, idx, reverse, edgeId, elId, connsToPaint, endpointsToPaint) { + // first try to find the exact match, but keep track of the first index of a matching element id along the way.s + var exactIdx = -1, + firstMatchingElIdx = -1, + endpoint = conn.endpoints[idx], + endpointId = endpoint.id, + oIdx = [1,0][idx], + values = [ [ theta, order ], conn, aBoolean, otherElId, endpointId ], + listToAddTo = lists[edgeId], + listToRemoveFrom = endpoint._continuousAnchorEdge ? lists[endpoint._continuousAnchorEdge] : null; + + if (listToRemoveFrom) { + var rIdx = _findWithFunction(listToRemoveFrom, function(e) { return e[4] == endpointId }); + if (rIdx != -1) { + listToRemoveFrom.splice(rIdx, 1); + // get all connections from this list + for (var i = 0; i < listToRemoveFrom.length; i++) { + _addWithFunction(connsToPaint, listToRemoveFrom[i][1], function(c) { return c.id == listToRemoveFrom[i][1].id }); + _addWithFunction(endpointsToPaint, listToRemoveFrom[i][1].endpoints[idx], function(e) { return e.id == listToRemoveFrom[i][1].endpoints[idx].id }); + } + } + } + + for (var i = 0; i < listToAddTo.length; i++) { + if (idx == 1 && listToAddTo[i][3] === otherElId && firstMatchingElIdx == -1) + firstMatchingElIdx = i; + _addWithFunction(connsToPaint, listToAddTo[i][1], function(c) { return c.id == listToAddTo[i][1].id }); + _addWithFunction(endpointsToPaint, listToAddTo[i][1].endpoints[idx], function(e) { return e.id == listToAddTo[i][1].endpoints[idx].id }); + } + if (exactIdx != -1) { + listToAddTo[exactIdx] = values; + } + else { + var insertIdx = reverse ? firstMatchingElIdx != -1 ? firstMatchingElIdx : 0 : listToAddTo.length; // of course we will get this from having looked through the array shortly. + listToAddTo.splice(insertIdx, 0, values); + } + + // store this for next time. + endpoint._continuousAnchorEdge = edgeId; + }; + this.redraw = function(elementId, ui, timestamp, offsetToUI) { + // get all the endpoints for this element + var ep = _amEndpoints[elementId] || [], + endpointConnections = connectionsByElementId[elementId] || [], + connectionsToPaint = [], + endpointsToPaint = [], + anchorsToUpdate = []; + + timestamp = timestamp || _timestamp(); + // offsetToUI are values that would have been calculated in the dragManager when registering + // an endpoint for an element that had a parent (somewhere in the hierarchy) that had been + // registered as draggable. + offsetToUI = offsetToUI || {left:0, top:0}; + if (ui) { + ui = { + left:ui.left + offsetToUI.left, + top:ui.top + offsetToUI.top + } + } + + _updateOffset( { elId : elementId, offset : ui, recalc : false, timestamp : timestamp }); + // valid for one paint cycle. + var myOffset = offsets[elementId], + myWH = sizes[elementId], + orientationCache = {}; + + // actually, first we should compute the orientation of this element to all other elements to which + // this element is connected with a continuous anchor (whether both ends of the connection have + // a continuous anchor or just one) + //for (var i = 0; i < continuousAnchorConnections.length; i++) { + for (var i = 0; i < endpointConnections.length; i++) { + var conn = endpointConnections[i][0], + sourceId = conn.sourceId, + targetId = conn.targetId, + sourceContinuous = conn.endpoints[0].anchor.isContinuous, + targetContinuous = conn.endpoints[1].anchor.isContinuous; + + if (sourceContinuous || targetContinuous) { + var oKey = sourceId + "_" + targetId, + oKey2 = targetId + "_" + sourceId, + o = orientationCache[oKey], + oIdx = conn.sourceId == elementId ? 1 : 0; + + if (sourceContinuous && !anchorLists[sourceId]) anchorLists[sourceId] = { top:[], right:[], bottom:[], left:[] }; + if (targetContinuous && !anchorLists[targetId]) anchorLists[targetId] = { top:[], right:[], bottom:[], left:[] }; + + if (elementId != targetId) _updateOffset( { elId : targetId, timestamp : timestamp }); + if (elementId != sourceId) _updateOffset( { elId : sourceId, timestamp : timestamp }); + + var td = _getCachedData(targetId), + sd = _getCachedData(sourceId); + + if (targetId == sourceId && (sourceContinuous || targetContinuous)) { + // here we may want to improve this by somehow determining the face we'd like + // to put the connector on. ideally, when drawing, the face should be calculated + // by determining which face is closest to the point at which the mouse button + // was released. for now, we're putting it on the top face. + _updateAnchorList(anchorLists[sourceId], -Math.PI / 2, 0, conn, false, targetId, 0, false, "top", sourceId, connectionsToPaint, endpointsToPaint) + } + else { + if (!o) { + o = calculateOrientation(sourceId, targetId, sd.o, td.o); + orientationCache[oKey] = o; + // this would be a performance enhancement, but the computed angles need to be clamped to + //the (-PI/2 -> PI/2) range in order for the sorting to work properly. + /* orientationCache[oKey2] = { + orientation:o.orientation, + a:[o.a[1], o.a[0]], + theta:o.theta + Math.PI, + theta2:o.theta2 + Math.PI + };*/ + } + if (sourceContinuous) _updateAnchorList(anchorLists[sourceId], o.theta, 0, conn, false, targetId, 0, false, o.a[0], sourceId, connectionsToPaint, endpointsToPaint); + if (targetContinuous) _updateAnchorList(anchorLists[targetId], o.theta2, -1, conn, true, sourceId, 1, true, o.a[1], targetId, connectionsToPaint, endpointsToPaint); + } + + if (sourceContinuous) _addWithFunction(anchorsToUpdate, sourceId, function(a) { return a === sourceId; }); + if (targetContinuous) _addWithFunction(anchorsToUpdate, targetId, function(a) { return a === targetId; }); + _addWithFunction(connectionsToPaint, conn, function(c) { return c.id == conn.id; }); + if ((sourceContinuous && oIdx == 0) || (targetContinuous && oIdx == 1)) + _addWithFunction(endpointsToPaint, conn.endpoints[oIdx], function(e) { return e.id == conn.endpoints[oIdx].id; }); + } + } + + // now place all the continuous anchors we need to; + for (var i = 0; i < anchorsToUpdate.length; i++) { + placeAnchors(anchorsToUpdate[i], anchorLists[anchorsToUpdate[i]]); + } + + // now that continuous anchors have been placed, paint all the endpoints for this element + // TODO performance: add the endpoint ids to a temp array, and then when iterating in the next + // loop, check that we didn't just paint that endpoint. we can probably shave off a few more milliseconds this way. + for (var i = 0; i < ep.length; i++) { + ep[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + // ... and any other endpoints we came across as a result of the continuous anchors. + for (var i = 0; i < endpointsToPaint.length; i++) { + endpointsToPaint[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH }); + } + + // paint all the standard and "dynamic connections", which are connections whose other anchor is + // static and therefore does need to be recomputed; we make sure that happens only one time. + + // TODO we could have compiled a list of these in the first pass through connections; might save some time. + for (var i = 0; i < endpointConnections.length; i++) { + var otherEndpoint = endpointConnections[i][1]; + if (otherEndpoint.anchor.constructor == DynamicAnchor) { + otherEndpoint.paint({ elementWithPrecedence:elementId }); + _addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; }); + // all the connections for the other endpoint now need to be repainted + for (var k = 0; k < otherEndpoint.connections.length; k++) { + if (otherEndpoint.connections[k] !== endpointConnections[i][0]) + _addWithFunction(connectionsToPaint, otherEndpoint.connections[k], function(c) { return c.id == otherEndpoint.connections[k].id; }); + } + } else if (otherEndpoint.anchor.constructor == Anchor) { + _addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; }); + } + } + // paint current floating connection for this element, if there is one. + var fc = floatingConnections[elementId]; + if (fc) + fc.paint({timestamp:timestamp, recalc:false, elId:elementId}); + + // paint all the connections + for (var i = 0; i < connectionsToPaint.length; i++) { + connectionsToPaint[i].paint({elId:elementId, timestamp:timestamp, recalc:false}); + } + }; + this.rehomeEndpoint = function(currentId, element) { + var eps = _amEndpoints[currentId] || [], //, + elementId = _currentInstance.getId(element); + for (var i = 0; i < eps.length; i++) { + self.add(eps[i], elementId); + } + eps.splice(0, eps.length); + }; + }; + _currentInstance.anchorManager = new AnchorManager(); + _currentInstance.continuousAnchorFactory = { + get:function(params) { + var existing = continuousAnchors[params.elementId]; + if (!existing) { + existing = { + type:"Continuous", + compute : function(params) { + return continuousAnchorLocations[params.element.id] || [0,0]; + }, + getCurrentLocation : function(endpoint) { + return continuousAnchorLocations[endpoint.id] || [0,0]; + }, + getOrientation : function(endpoint) { + return continuousAnchorOrientations[endpoint.id] || [0,0]; + }, + isDynamic : true, + isContinuous : true + }; + continuousAnchors[params.elementId] = existing; + } + return existing; + } + }; + + /** + Manages dragging for some instance of jsPlumb. + + */ + var DragManager = function() { + + var _draggables = {}, _dlist = [], _delements = {}, _elementsWithEndpoints = {}; + + /** + register some element as draggable. right now the drag init stuff is done elsewhere, and it is + possible that will continue to be the case. + */ + this.register = function(el) { + var jpcl = jsPlumb.CurrentLibrary; + el = jpcl.getElementObject(el); + var id = _currentInstance.getId(el), + domEl = jpcl.getDOMElement(el); + if (!_draggables[id]) { + _draggables[id] = el; + _dlist.push(el); + _delements[id] = {}; + } + + // look for child elements that have endpoints and register them against this draggable. + var _oneLevel = function(p) { + var pEl = jpcl.getElementObject(p), + pOff = jpcl.getOffset(pEl); + + for (var i = 0; i < p.childNodes.length; i++) { + if (p.childNodes[i].nodeType != 3) { + var cEl = jpcl.getElementObject(p.childNodes[i]), + cid = _currentInstance.getId(cEl, null, true); + if (cid && _elementsWithEndpoints[cid] && _elementsWithEndpoints[cid] > 0) { + var cOff = jpcl.getOffset(cEl); + _delements[id][cid] = { + id:cid, + offset:{ + left:cOff.left - pOff.left, + top:cOff.top - pOff.top + } + }; + } + } + } + }; + + _oneLevel(domEl); + }; + + /** + notification that an endpoint was added to the given el. we go up from that el's parent + node, looking for a parent that has been registered as a draggable. if we find one, we add this + el to that parent's list of elements to update on drag (if it is not there already) + */ + this.endpointAdded = function(el) { + var jpcl = jsPlumb.CurrentLibrary, b = document.body, id = _currentInstance.getId(el), c = jpcl.getDOMElement(el), + p = c.parentNode, done = p == b; + + _elementsWithEndpoints[id] = _elementsWithEndpoints[id] ? _elementsWithEndpoints[id] + 1 : 1; + + while (p != b) { + var pid = _currentInstance.getId(p); + if (_draggables[pid]) { + var idx = -1, pEl = jpcl.getElementObject(p), pLoc = jpcl.getOffset(pEl); + + if (_delements[pid][id] == null) { + var cLoc = jsPlumb.CurrentLibrary.getOffset(el); + _delements[pid][id] = { + id:id, + offset:{ + left:cLoc.left - pLoc.left, + top:cLoc.top - pLoc.top + } + }; + } + break; + } + p = p.parentNode; + } + }; + + this.endpointDeleted = function(endpoint) { + if (_elementsWithEndpoints[endpoint.elementId]) { + _elementsWithEndpoints[endpoint.elementId]--; + if (_elementsWithEndpoints[endpoint.elementId] <= 0) { + for (var i in _delements) { + delete _delements[i][endpoint.elementId]; + } + } + } + }; + + this.getElementsForDraggable = function(id) { + return _delements[id]; + }; + + this.reset = function() { + _draggables = {}; + _dlist = []; + _delements = {}; + _elementsWithEndpoints = {}; + }; + + }; + _currentInstance.dragManager = new DragManager(); + + + + /* + * Class: Connection + * The connecting line between two Endpoints. + */ + /* + * Function: Connection + * Connection constructor. You should not ever create one of these directly. + * + * Parameters: + * source - either an element id, a selector for an element, or an Endpoint. + * target - either an element id, a selector for an element, or an Endpoint + * scope - scope descriptor for this connection. optional. + * container - optional id or selector instructing jsPlumb where to attach all the elements it creates for this connection. you should read the documentation for a full discussion of this. + * detachable - optional, defaults to true. Defines whether or not the connection may be detached using the mouse. + * endpoint - Optional. Endpoint definition to use for both ends of the connection. + * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection. + * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters. + * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here. + * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null). + * overlays - Optional array of Overlay definitions to appear on this Connection. + * drawEndpoints - if false, instructs jsPlumb to not draw the endpoints for this Connection. Be careful with this: it only really works when you tell jsPlumb to attach elements to the document body. Read the documentation for a full discussion of this. + * parameters - Optional JS object containing parameters to set on the Connection. These parameters are then available via the getParameter method. + */ + var Connection = function(params) { + var self = this, visible = true; + self.idPrefix = "_jsplumb_c_"; + self.defaultLabelLocation = 0.5; + self.defaultOverlayKeys = ["Overlays", "ConnectionOverlays"]; + this.parent = params.parent; + overlayCapableJsPlumbUIComponent.apply(this, arguments); + // ************** get the source and target and register the connection. ******************* + + /** + Function:isVisible + Returns whether or not the Connection is currently visible. + */ + this.isVisible = function() { return visible; }; + /** + Function: setVisible + Sets whether or not the Connection should be visible. + + Parameters: + visible - boolean indicating desired visible state. + */ + this.setVisible = function(v) { + visible = v; + self[v ? "showOverlays" : "hideOverlays"](); + if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none"; + }; + + /** + Property: source + The source element for this Connection. + */ + this.source = _getElementObject(params.source); + /** + Property:target + The target element for this Connection. + */ + this.target = _getElementObject(params.target); + // sourceEndpoint and targetEndpoint override source/target, if they are present. but + // source is not overridden if the Endpoint has declared it is not the final target of a connection; + // instead we use the source that the Endpoint declares will be the final source element. + if (params.sourceEndpoint) { + this.source = params.sourceEndpoint.endpointWillMoveTo || params.sourceEndpoint.getElement(); + } + if (params.targetEndpoint) this.target = params.targetEndpoint.getElement(); + + // if a new connection is the result of moving some existing connection, params.previousConnection + // will have that Connection in it. listeners for the jsPlumbConnection event can look for that + // member and take action if they need to. + self.previousConnection = params.previousConnection; + + var _cost = params.cost; + self.getCost = function() { return _cost; }; + self.setCost = function(c) { _cost = c; }; + + var _bidirectional = params.bidirectional === false ? false : true; + self.isBidirectional = function() { return _bidirectional; }; + + /* + * Property: sourceId + * Id of the source element in the connection. + */ + this.sourceId = _getAttribute(this.source, "id"); + /* + * Property: targetId + * Id of the target element in the connection. + */ + this.targetId = _getAttribute(this.target, "id"); + + /** + * implementation of abstract method in jsPlumbUtil.EventGenerator + * @return list of attached elements. in our case, a list of Endpoints. + */ + this.getAttachedElements = function() { + return self.endpoints; + }; + + /* + * Property: scope + * Optional scope descriptor for the connection. + */ + this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. + /* + * Property: endpoints + * Array of [source, target] Endpoint objects. + */ + this.endpoints = []; + this.endpointStyles = []; + // wrapped the main function to return null if no input given. this lets us cascade defaults properly. + var _makeAnchor = function(anchorParams, elementId) { + if (anchorParams) + return _currentInstance.makeAnchor(anchorParams, elementId, _currentInstance); + }, + prepareEndpoint = function(existing, index, params, element, elementId, connectorPaintStyle, connectorHoverPaintStyle) { + if (existing) { + self.endpoints[index] = existing; + existing.addConnection(self); + } else { + if (!params.endpoints) params.endpoints = [ null, null ]; + var ep = params.endpoints[index] + || params.endpoint + || _currentInstance.Defaults.Endpoints[index] + || jsPlumb.Defaults.Endpoints[index] + || _currentInstance.Defaults.Endpoint + || jsPlumb.Defaults.Endpoint; + + if (!params.endpointStyles) params.endpointStyles = [ null, null ]; + if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ]; + var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle; + // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified. + if (es.fillStyle == null && connectorPaintStyle != null) + es.fillStyle = connectorPaintStyle.strokeStyle; + + // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does: + //* + if (es.outlineColor == null && connectorPaintStyle != null) + es.outlineColor = connectorPaintStyle.outlineColor; + if (es.outlineWidth == null && connectorPaintStyle != null) + es.outlineWidth = connectorPaintStyle.outlineWidth; + //*/ + + var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle; + // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure? + if (connectorHoverPaintStyle != null) { + if (ehs == null) ehs = {}; + if (ehs.fillStyle == null) { + ehs.fillStyle = connectorHoverPaintStyle.strokeStyle; + } + } + var a = params.anchors ? params.anchors[index] : + params.anchor ? params.anchor : + _makeAnchor(_currentInstance.Defaults.Anchors[index], elementId) || + _makeAnchor(jsPlumb.Defaults.Anchors[index], elementId) || + _makeAnchor(_currentInstance.Defaults.Anchor, elementId) || + _makeAnchor(jsPlumb.Defaults.Anchor, elementId), + u = params.uuids ? params.uuids[index] : null, + e = _newEndpoint({ + paintStyle : es, + hoverPaintStyle:ehs, + endpoint : ep, + connections : [ self ], + uuid : u, + anchor : a, + source : element, + scope : params.scope, + container:params.container, + reattach:params.reattach, + detachable:params.detachable + }); + self.endpoints[index] = e; + + + if (params.drawEndpoints === false) e.setVisible(false, true, true); + + return e; + } + }; + + var eS = prepareEndpoint(params.sourceEndpoint, + 0, + params, + self.source, + self.sourceId, + params.paintStyle, + params.hoverPaintStyle); + if (eS) _addToList(endpointsByElement, this.sourceId, eS); + + // if there were no endpoints supplied and the source element is the target element, we will reuse the source + // endpoint that was just created. + var existingTargetEndpoint = ((self.sourceId == self.targetId) && params.targetEndpoint == null) ? eS : params.targetEndpoint, + eT = prepareEndpoint(existingTargetEndpoint, + 1, + params, + self.target, + self.targetId, + params.paintStyle, + params.hoverPaintStyle); + if (eT) _addToList(endpointsByElement, this.targetId, eT); + // if scope not set, set it to be the scope for the source endpoint. + if (!this.scope) this.scope = this.endpoints[0].scope; + + // if delete endpoints on detach, keep a record of just exactly which endpoints they are. + if (params.deleteEndpointsOnDetach) + self.endpointsToDeleteOnDetach = [eS, eT]; + + var _detachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.detachable === false) _detachable = false; + if(self.endpoints[0].connectionsDetachable === false) _detachable = false; + if(self.endpoints[1].connectionsDetachable === false) _detachable = false; + + // inherit connectin cost if it was set on source endpoint + if (_cost == null) _cost = self.endpoints[0].getConnectionCost(); + // inherit bidirectional flag if set no source endpoint + if (params.bidirectional == null) _bidirectional = self.endpoints[0].areConnectionsBidirectional(); + + /* + Function: isDetachable + Returns whether or not this connection can be detached from its target/source endpoint. by default this + is false; use it in conjunction with the 'reattach' parameter. + */ + this.isDetachable = function() { + return _detachable === true; + }; + + /* + Function: setDetachable + Sets whether or not this connection is detachable. + */ + this.setDetachable = function(detachable) { + _detachable = detachable === true; + }; + + // merge all the parameters objects into the connection. parameters set + // on the connection take precedence; then target endpoint params, then + // finally source endpoint params. + // TODO jsPlumb.extend could be made to take more than two args, and it would + // apply the second through nth args in order. + var _p = jsPlumb.extend({}, this.endpoints[0].getParameters()); + jsPlumb.extend(_p, this.endpoints[1].getParameters()); + jsPlumb.extend(_p, self.getParameters()); + self.setParameters(_p); + + // override setHover to pass it down to the underlying connector + var _sh = self.setHover; + + self.setHover = function(state) { + self.connector.setHover.apply(self.connector, arguments); + _sh.apply(self, arguments); + }; + + var _internalHover = function(state) { + if (_connectionBeingDragged == null) { + self.setHover(state, false); + } + }; + + /* + * Function: setConnector + * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method - the same + * thing that you would set as the 'connector' property on a jsPlumb.connect call. + * + * Parameters: + * connector - Connector definition + */ + this.setConnector = function(connector, doNotRepaint) { + if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent); + var connectorArgs = { + _jsPlumb:self._jsPlumb, + parent:params.parent, + cssClass:params.cssClass, + container:params.container, + tooltip:self.tooltip + }; + if (_isString(connector)) + this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand. + else if (_isArray(connector)) + this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs)); + self.canvas = self.connector.canvas; + // binds mouse listeners to the current connector. + _bindListeners(self.connector, self, _internalHover); + if (!doNotRepaint) self.repaint(); + }; + /* + * Property: connector + * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc) + */ + + self.setConnector(this.endpoints[0].connector || + this.endpoints[1].connector || + params.connector || + _currentInstance.Defaults.Connector || + jsPlumb.Defaults.Connector, true); + + this.setPaintStyle(this.endpoints[0].connectorStyle || + this.endpoints[1].connectorStyle || + params.paintStyle || + _currentInstance.Defaults.PaintStyle || + jsPlumb.Defaults.PaintStyle, true); + + this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || + this.endpoints[1].connectorHoverStyle || + params.hoverPaintStyle || + _currentInstance.Defaults.HoverPaintStyle || + jsPlumb.Defaults.HoverPaintStyle, true); + + this.paintStyleInUse = this.getPaintStyle(); + + + this.moveParent = function(newParent) { + var jpcl = jsPlumb.CurrentLibrary, curParent = jpcl.getParent(self.connector.canvas); + if (self.connector.bgCanvas) { + jpcl.removeElement(self.connector.bgCanvas, curParent); + jpcl.appendElement(self.connector.bgCanvas, newParent); + } + jpcl.removeElement(self.connector.canvas, curParent); + jpcl.appendElement(self.connector.canvas, newParent); + // this only applies for DOMOverlays + for (var i = 0; i < self.overlays.length; i++) { + if (self.overlays[i].isAppendedAtTopLevel) { + jpcl.removeElement(self.overlays[i].canvas, curParent); + jpcl.appendElement(self.overlays[i].canvas, newParent); + if (self.overlays[i].reattachListeners) + self.overlays[i].reattachListeners(self.connector); + } + } + if (self.connector.reattachListeners) // this is for SVG/VML; change an element's parent and you have to reinit its listeners. + self.connector.reattachListeners(); // the Canvas implementation doesn't have to care about this + }; + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Connection. + * + * Parameters: + * event - the event to bind. Available events on a Connection are: + * - *click* : notification that a Connection was clicked. + * - *dblclick* : notification that a Connection was double clicked. + * - *mouseenter* : notification that the mouse is over a Connection. + * - *mouseexit* : notification that the mouse exited a Connection. + * - *contextmenu* : notification that the user right-clicked on the Connection. + * + * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Connection's paint style and then repaints the Connection. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: getPaintStyle + * Gets the Connection's paint style. This is not necessarily the paint style in use at the time; + * this is the paint style for the connection when the mouse it not hovering over it. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Connection will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Connection. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + + /* + * Function: getParameter + * Gets the named parameter; returns null if no parameter with that name is set. Parameter values may have been supplied to a 'connect' or 'addEndpoint' call (connections made with the mouse get a copy of all parameters set on each of their Endpoints), or the parameter value may have been set with setParameter. + * + * Parameters: + * key - Parameter name. + */ + + /* + * Function: setParameter + * Sets the named parameter to the given value. + * + * Parameters: + * key - Parameter name. + * value - Parameter value. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + _updateOffset( { elId : this.sourceId }); + _updateOffset( { elId : this.targetId }); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId], + otherOffset = offsets[this.targetId], + otherWH = sizes[this.targetId], + initialTimestamp = _timestamp(), + anchorLoc = this.endpoints[0].anchor.compute( { + xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0], + elementId:this.endpoints[0].elementId, + txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1], + timestamp:initialTimestamp + }); + + this.endpoints[0].paint( { anchorLoc : anchorLoc, timestamp:initialTimestamp }); + + anchorLoc = this.endpoints[1].anchor.compute( { + xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1], + elementId:this.endpoints[1].elementId, + txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0], + timestamp:initialTimestamp + }); + this.endpoints[1].paint({ anchorLoc : anchorLoc, timestamp:initialTimestamp }); + + /* + * Paints the Connection. Not exposed for public usage. + * + * Parameters: + * elId - Id of the element that is in motion. + * ui - current library's event system ui object (present if we came from a drag to get here). + * recalc - whether or not to recalculate all anchors etc before painting. + * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again. + */ + this.paint = function(params) { + params = params || {}; + var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp, + // if the moving object is not the source we must transpose the two references. + swap = false, + tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId, + tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + + var sourceInfo = _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }), + targetInfo = _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var sE = this.endpoints[sIdx], tE = this.endpoints[tIdx], + sAnchorP = sE.anchor.getCurrentLocation(sE), + tAnchorP = tE.anchor.getCurrentLocation(tE); + + /* paint overlays*/ + var maxSize = 0; + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible()) maxSize = Math.max(maxSize, o.computeMaxSize(self.connector)); + } + + var dim = this.connector.compute(sAnchorP, tAnchorP, + this.endpoints[sIdx], this.endpoints[tIdx], + this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, + self.paintStyleInUse.lineWidth, maxSize, + sourceInfo, + targetInfo); + + self.connector.paint(dim, self.paintStyleInUse); + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim); + } + }; + + /* + * Function: repaint + * Repaints the Connection. + */ + this.repaint = function(params) { + params = params || {}; + var recalc = !(params.recalc === false); + this.paint({ elId : this.sourceId, recalc : recalc, timestamp:params.timestamp }); + }; + + }; + +// ENDPOINT HELPER FUNCTIONS + var _makeConnectionDragHandler = function(placeholder) { + var stopped = false; + return { + + drag : function() { + if (stopped) { + stopped = false; + return true; + } + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments), + el = placeholder.element; + if (el) { + jsPlumb.CurrentLibrary.setOffset(el, _ui); + _draw(_getElementObject(el), _ui); + } + }, + stopDrag : function() { + stopped = true; + } + }; + }; + + var _makeFloatingEndpoint = function(paintStyle, referenceAnchor, endpoint, referenceCanvas, sourceElement) { + var floatingAnchor = new FloatingAnchor( { reference : referenceAnchor, referenceCanvas : referenceCanvas }); + + //setting the scope here should not be the way to fix that mootools issue. it should be fixed by not + // adding the floating endpoint as a droppable. that makes more sense anyway! + + return _newEndpoint({ paintStyle : paintStyle, endpoint : endpoint, anchor : floatingAnchor, source : sourceElement, scope:"__floating" }); + }; + + /** + * creates a placeholder div for dragging purposes, adds it to the DOM, and pre-computes its offset. then returns + * both the element id and a selector for the element. + */ + var _makeDraggablePlaceholder = function(placeholder, parent) { + var n = document.createElement("div"); + n.style.position = "absolute"; + var placeholderDragElement = _getElementObject(n); + _appendElement(n, parent); + var id = _getId(placeholderDragElement); + _updateOffset( { elId : id }); + // create and assign an id, and initialize the offset. + placeholder.id = id; + placeholder.element = placeholderDragElement; + }; + + /* + * Class: Endpoint + * + * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1 + * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't + * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you + * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or + * 'target' Endpoints for Connections. + * + * + */ + + /* + * Function: Endpoint + * + * Endpoint constructor. + * + * Parameters: + * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions. + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ]. + * enabled - optional, defaults to true. Indicates whether or not the Endpoint should be enabled for mouse events (drag/drop). + * paintStyle - endpoint style, a js object. may be null. + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null. + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required. + * canvas - canvas element to use. may be, and most often is, null. + * container - optional id or selector instructing jsPlumb where to attach the element it creates for this endpoint. you should read the documentation for a full discussion of this. + * connections - optional list of Connections to configure the Endpoint with. + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false. + * maxConnections - integer; defaults to 1. a value of -1 means no upper limit. + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null. + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null. + * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ]. + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. + * connectionsDetachable - optional, defaults to true. Sets whether connections to/from this Endpoint should be detachable or not. + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false. + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted. + * parameters - Optional JS object containing parameters to set on the Endpoint. These parameters are then available via the getParameter method. When a connection is made involving this Endpoint, the parameters from this Endpoint are copied into that Connection. Source Endpoint parameters override target Endpoint parameters if they both have a parameter with the same name. + */ + var Endpoint = function(params) { + var self = this; + self.idPrefix = "_jsplumb_e_"; + self.defaultLabelLocation = [ 0.5, 0.5 ]; + self.defaultOverlayKeys = ["Overlays", "EndpointOverlays"]; + this.parent = params.parent; + overlayCapableJsPlumbUIComponent.apply(this, arguments); + params = params || {}; + +// ***************************** PLACEHOLDERS FOR NATURAL DOCS ************************************************* + /* + * Function: bind + * Bind to an event on the Endpoint. + * + * Parameters: + * event - the event to bind. Available events on an Endpoint are: + * - *click* : notification that a Endpoint was clicked. + * - *dblclick* : notification that a Endpoint was double clicked. + * - *mouseenter* : notification that the mouse is over a Endpoint. + * - *mouseexit* : notification that the mouse exited a Endpoint. + * - *contextmenu* : notification that the user right-clicked on the Endpoint. + * + * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event. + */ + + /* + * Function: setPaintStyle + * Sets the Endpoint's paint style and then repaints the Endpoint. + * + * Parameters: + * style - Style to use. + */ + + /* + * Function: getPaintStyle + * Gets the Endpoint's paint style. This is not necessarily the paint style in use at the time; + * this is the paint style for the Endpoint when the mouse it not hovering over it. + */ + + /* + * Function: setHoverPaintStyle + * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default. + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace + * it. This is because people will most likely want to change just one thing when hovering, say the + * color for example, but leave the rest of the appearance the same. + * + * Parameters: + * style - Style to use when the mouse is hovering. + * doNotRepaint - if true, the Endpoint will not be repainted. useful when setting things up initially. + */ + + /* + * Function: setHover + * Sets/unsets the hover state of this Endpoint. + * + * Parameters: + * hover - hover state boolean + * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops. + */ + + /* + * Function: getParameter + * Gets the named parameter; returns null if no parameter with that name is set. Parameters may have been set on the Endpoint in the 'addEndpoint' call, or they may have been set with the setParameter function. + * + * Parameters: + * key - Parameter name. + */ + + /* + * Function: setParameter + * Sets the named parameter to the given value. + * + * Parameters: + * key - Parameter name. + * value - Parameter value. + */ + +// ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS ************************************************* + + var visible = true, __enabled = !(params.enabled === false); + /* + Function: isVisible + Returns whether or not the Endpoint is currently visible. + */ + this.isVisible = function() { return visible; }; + /* + Function: setVisible + Sets whether or not the Endpoint is currently visible. + + Parameters: + visible - whether or not the Endpoint should be visible. + doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false. + doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. + */ + this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) { + visible = v; + if (self.canvas) self.canvas.style.display = v ? "block" : "none"; + self[v ? "showOverlays" : "hideOverlays"](); + if (!doNotChangeConnections) { + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].setVisible(v); + if (!doNotNotifyOtherEndpoint) { + var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0; + // only change the other endpoint if this is its only connection. + if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true); + } + } + } + }; + + /* + Function: isEnabled + Returns whether or not the Endpoint is enabled for drag/drop connections. + */ + this.isEnabled = function() { return __enabled; }; + + /* + Function: setEnabled + Sets whether or not the Endpoint is enabled for drag/drop connections. + */ + this.setEnabled = function(e) { __enabled = e; }; + + var _element = params.source, _uuid = params.uuid, floatingEndpoint = null, inPlaceCopy = null; + if (_uuid) endpointsByUUID[_uuid] = self; + var _elementId = _getAttribute(_element, "id"); + this.elementId = _elementId; + this.element = _element; + + var _connectionCost = params.connectionCost; + this.getConnectionCost = function() { return _connectionCost; }; + this.setConnectionCost = function(c) { + _connectionCost = c; + }; + + var _connectionsBidirectional = params.connectionsBidirectional === false ? false : true; + this.areConnectionsBidirectional = function() { return _connectionsBidirectional; }; + this.setConnectionsBidirectional = function(b) { _connectionsBidirectional = b; }; + + self.anchor = params.anchor ? _currentInstance.makeAnchor(params.anchor, _elementId, _currentInstance) : params.anchors ? _currentInstance.makeAnchor(params.anchors, _elementId, _currentInstance) : _currentInstance.makeAnchor("TopCenter", _elementId, _currentInstance); + + // ANCHOR MANAGER + if (!params._transient) // in place copies, for example, are transient. they will never need to be retrieved during a paint cycle, because they dont move, and then they are deleted. + _currentInstance.anchorManager.add(self, _elementId); + + var _endpoint = null, originalEndpoint = null; + this.setEndpoint = function(ep) { + var endpointArgs = { + _jsPlumb:self._jsPlumb, + parent:params.parent, + container:params.container, + tooltip:params.tooltip, + connectorTooltip:params.connectorTooltip, + endpoint:self + }; + if (_isString(ep)) + _endpoint = new jsPlumb.Endpoints[renderMode][ep](endpointArgs); + else if (_isArray(ep)) { + endpointArgs = jsPlumb.extend(ep[1], endpointArgs); + _endpoint = new jsPlumb.Endpoints[renderMode][ep[0]](endpointArgs); + } + else { + _endpoint = ep.clone(); + } + + // assign a clone function using a copy of endpointArgs. this is used when a drag starts: the endpoint that was dragged is cloned, + // and the clone is left in its place while the original one goes off on a magical journey. + // the copy is to get around a closure problem, in which endpointArgs ends up getting shared by + // the whole world. + var argsForClone = jsPlumb.extend({}, endpointArgs); + _endpoint.clone = function() { + var o = new Object(); + _endpoint.constructor.apply(o, [argsForClone]); + return o; + }; + + self.endpoint = _endpoint; + self.type = self.endpoint.type; + }; + + this.setEndpoint(params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot"); + originalEndpoint = _endpoint; + + // override setHover to pass it down to the underlying endpoint + var _sh = self.setHover; + self.setHover = function() { + self.endpoint.setHover.apply(self.endpoint, arguments); + _sh.apply(self, arguments); + }; + // endpoint delegates to first connection for hover, if there is one. + var internalHover = function(state) { + if (self.connections.length > 0) + self.connections[0].setHover(state, false); + else + self.setHover(state); + }; + + // bind listeners from endpoint to self, with the internal hover function defined above. + _bindListeners(self.endpoint, self, internalHover); + + this.setPaintStyle(params.paintStyle || + params.style || + _currentInstance.Defaults.EndpointStyle || + jsPlumb.Defaults.EndpointStyle, true); + this.setHoverPaintStyle(params.hoverPaintStyle || + _currentInstance.Defaults.EndpointHoverStyle || + jsPlumb.Defaults.EndpointHoverStyle, true); + this.paintStyleInUse = this.getPaintStyle(); + var originalPaintStyle = this.getPaintStyle(); + this.connectorStyle = params.connectorStyle; + this.connectorHoverStyle = params.connectorHoverStyle; + this.connectorOverlays = params.connectorOverlays; + this.connector = params.connector; + this.connectorTooltip = params.connectorTooltip; + this.isSource = params.isSource || false; + this.isTarget = params.isTarget || false; + + var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of. + + this.getAttachedElements = function() { + return self.connections; + }; + + /* + * Property: canvas + * The Endpoint's Canvas. + */ + this.canvas = this.endpoint.canvas; + /* + * Property: connections + * List of Connections this Endpoint is attached to. + */ + this.connections = params.connections || []; + /* + * Property: scope + * Scope descriptor for this Endpoint. + */ + this.scope = params.scope || DEFAULT_SCOPE; + this.timestamp = null; + self.isReattach = params.reattach || false; + self.connectionsDetachable = _currentInstance.Defaults.ConnectionsDetachable; + if (params.connectionsDetachable === false || params.detachable === false) + self.connectionsDetachable = false; + var dragAllowedWhenFull = params.dragAllowedWhenFull || true; + + this.computeAnchor = function(params) { + return self.anchor.compute(params); + }; + /* + * Function: addConnection + * Adds a Connection to this Endpoint. + * + * Parameters: + * connection - the Connection to add. + */ + this.addConnection = function(connection) { + self.connections.push(connection); + }; + /* + * Function: detach + * Detaches the given Connection from this Endpoint. + * + * Parameters: + * connection - the Connection to detach. + * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target. + */ + this.detach = function(connection, ignoreTarget, forceDetach, fireEvent, originalEvent) { + var idx = _findWithFunction(self.connections, function(c) { return c.id == connection.id}), + actuallyDetached = false; + fireEvent = (fireEvent !== false); + if (idx >= 0) { + // 1. does the connection have a before detach (note this also checks jsPlumb's bound + // detach handlers; but then Endpoint's check will, too, hmm.) + if (forceDetach || connection._forceDetach || connection.isDetachable() || connection.isDetachAllowed(connection)) { + // get the target endpoint + var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0]; + // it would be nice to check with both endpoints that it is ok to detach. but + // for this we'll have to get a bit fancier: right now if you use the same beforeDetach + // interceptor for two endpoints (which is kind of common, because it's part of the + // endpoint definition), then it gets fired twice. so in fact we need to loop through + // each beforeDetach and see if it returns false, at which point we exit. but if it + // returns true, we have to check the next one. however we need to track which ones + // have already been run, and not run them again. + if (forceDetach || connection._forceDetach || (self.isDetachAllowed(connection) /*&& t.isDetachAllowed(connection)*/)) { + + self.connections.splice(idx, 1); + + // this avoids a circular loop + if (!ignoreTarget) { + + t.detach(connection, true, forceDetach); + // check connection to see if we want to delete the endpoints associated with it. + // we only detach those that have just this connection; this scenario is most + // likely if we got to this bit of code because it is set by the methods that + // create their own endpoints, like .connect or .makeTarget. the user is + // not likely to have interacted with those endpoints. + if (connection.endpointsToDeleteOnDetach){ + for (var i = 0; i < connection.endpointsToDeleteOnDetach.length; i++) { + var cde = connection.endpointsToDeleteOnDetach[i]; + if (cde && cde.connections.length == 0) + _currentInstance.deleteEndpoint(cde); + } + } + } + _removeElements(connection.connector.getDisplayElements(), connection.parent); + _removeWithFunction(connectionsByScope[connection.scope], function(c) { + return c.id == connection.id; + }); + actuallyDetached = true; + var doFireEvent = (!ignoreTarget && fireEvent) + fireDetachEvent(connection, doFireEvent, originalEvent); + } + } + } + return actuallyDetached; + }; + + /* + * Function: detachAll + * Detaches all Connections this Endpoint has. + * + * Parameters: + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachAll = function(fireEvent, originalEvent) { + while (self.connections.length > 0) { + self.detach(self.connections[0], false, true, fireEvent, originalEvent); + } + }; + /* + * Function: detachFrom + * Removes any connections from this Endpoint that are connected to the given target endpoint. + * + * Parameters: + * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint. + * fireEvent - whether or not to fire the detach event. defaults to false. + */ + this.detachFrom = function(targetEndpoint, fireEvent, originalEvent) { + var c = []; + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == targetEndpoint + || self.connections[i].endpoints[0] == targetEndpoint) { + c.push(self.connections[i]); + } + } + for ( var i = 0; i < c.length; i++) { + if (self.detach(c[i], false, true, fireEvent, originalEvent)) + c[i].setHover(false, false); + } + }; + /* + * Function: detachFromConnection + * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging. + * + * Parameters: + * connection - Connection to detach from. + */ + this.detachFromConnection = function(connection) { + var idx = _findWithFunction(self.connections, function(c) { return c.id == connection.id}); + if (idx >= 0) { + self.connections.splice(idx, 1); + } + }; + + /* + * Function: getElement + * Returns the DOM element this Endpoint is attached to. + */ + this.getElement = function() { + return _element; + }; + + /* + * Function: setElement + * Sets the DOM element this Endpoint is attached to. + */ + this.setElement = function(el) { + + // TODO possibly have this object take charge of moving the UI components into the appropriate + // parent. this is used only by makeSource right now, and that function takes care of + // moving the UI bits and pieces. however it would s + var parentId = _getId(el); + // remove the endpoint from the list for the current endpoint's element + _removeWithFunction(endpointsByElement[self.elementId], function(e) { + return e.id == self.id; + }); + _element = _getElementObject(el); + _elementId = _getId(_element); + self.elementId = _elementId; + // need to get the new parent now + var newParentElement = _getParentFromParams({source:parentId}), + curParent = jpcl.getParent(self.canvas); + jpcl.removeElement(self.canvas, curParent); + jpcl.appendElement(self.canvas, newParentElement); + + // now move connection(s)...i would expect there to be only one but we will iterate. + for (var i = 0; i < self.connections.length; i++) { + self.connections[i].moveParent(newParentElement); + self.connections[i].sourceId = _elementId; + self.connections[i].source = _element; + } + _addToList(endpointsByElement, parentId, self); + //_currentInstance.repaint(parentId); + + }; + + /* + * Function: getUuid + * Returns the UUID for this Endpoint, if there is one. Otherwise returns null. + */ + this.getUuid = function() { + return _uuid; + }; + /** + * private but must be exposed. + */ + this.makeInPlaceCopy = function() { + var loc = self.anchor.getCurrentLocation(self), + o = self.anchor.getOrientation(self), + inPlaceAnchor = { + compute:function() { return [ loc[0], loc[1] ]}, + getCurrentLocation : function() { return [ loc[0], loc[1] ]}, + getOrientation:function() { return o; } + }; + + return _newEndpoint( { + anchor : inPlaceAnchor, + source : _element, + paintStyle : this.getPaintStyle(), + endpoint : _endpoint, + _transient:true, + scope:self.scope + }); + }; + /* + * Function: isConnectedTo + * Returns whether or not this endpoint is connected to the given Endpoint. + * + * Parameters: + * endpoint - Endpoint to test. + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for ( var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + /** + * private but needs to be exposed. + */ + this.isFloating = function() { + return floatingEndpoint != null; + }; + + /** + * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can. + */ + this.connectorSelector = function() { + var candidate = self.connections[0]; + if (self.isTarget && candidate) return candidate; + else { + return (self.connections.length < _maxConnections) || _maxConnections == -1 ? null : candidate; + } + }; + + /* + * Function: isFull + * Returns whether or not the Endpoint can accept any more Connections. + */ + this.isFull = function() { + return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections); + }; + /* + * Function: setDragAllowedWhenFull + * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in + * which you're going to provide some other way of breaking connections, if you need to break them at all. This property + * is by default true; use it in conjunction with the 'reattach' option on a connect call. + * + * Parameters: + * allowed - whether drag is allowed or not when the Endpoint is full. + */ + this.setDragAllowedWhenFull = function(allowed) { + dragAllowedWhenFull = allowed; + }; + /* + * Function: setStyle + * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call. + * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to + * setStyle and deprecate it if so. + * + * Parameters: + * style - Style object to set, for example {fillStyle:"blue"}. + * + * @deprecated use setPaintStyle instead. + */ + this.setStyle = self.setPaintStyle; + + /** + * a deep equals check. everything must match, including the anchor, + * styles, everything. TODO: finish Endpoint.equals + */ + this.equals = function(endpoint) { + return this.anchor.equals(endpoint.anchor); + }; + + // a helper function that tries to find a connection to the given element, and returns it if so. if elementWithPrecedence is null, + // or no connection to it is found, we return the first connection in our list. + var findConnectionToUseForDynamicAnchor = function(elementWithPrecedence) { + var idx = 0; + if (elementWithPrecedence != null) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].sourceId == elementWithPrecedence || self.connections[i].targetId == elementWithPrecedence) { + idx = i; + break; + } + } + } + + return self.connections[idx]; + }; + + /* + * Function: paint + * Paints the Endpoint, recalculating offset and anchor positions if necessary. This does NOT paint + * any of the Endpoint's connections. + * + * Parameters: + * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again. + * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations. + * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied. + */ + this.paint = function(params) { + params = params || {}; + var timestamp = params.timestamp, + recalc = !(params.recalc === false); + if (!timestamp || self.timestamp !== timestamp) { + _updateOffset({ elId:_elementId, timestamp:timestamp, recalc:recalc }); + var xy = params.offset || offsets[_elementId]; + if(xy) { + var ap = params.anchorPoint,connectorPaintStyle = params.connectorPaintStyle; + if (ap == null) { + var wh = params.dimensions || sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset( { elId : _elementId, timestamp : timestamp }); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp }; + if (recalc && self.anchor.isDynamic && self.connections.length > 0) { + var c = findConnectionToUseForDynamicAnchor(params.elementWithPrecedence), + oIdx = c.endpoints[0] == self ? 1 : 0, + oId = oIdx == 0 ? c.sourceId : c.targetId, + oOffset = offsets[oId], oWH = sizes[oId]; + anchorParams.txy = [ oOffset.left, oOffset.top ]; + anchorParams.twh = oWH; + anchorParams.tElement = c.endpoints[oIdx]; + } + ap = self.anchor.compute(anchorParams); + } + + // switched _endpoint to self here; continuous anchors, for instance, look for the + // endpoint's id, but here "_endpoint" is the renderer, not the actual endpoint. + // TODO need to verify this does not break anything. + //var d = _endpoint.compute(ap, self.anchor.getOrientation(_endpoint), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + var d = _endpoint.compute(ap, self.anchor.getOrientation(self), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse); + _endpoint.paint(d, self.paintStyleInUse, self.anchor); + self.timestamp = timestamp; + + /* paint overlays*/ + for ( var i = 0; i < self.overlays.length; i++) { + var o = self.overlays[i]; + if (o.isVisible) self.overlayPlacements[i] = o.draw(self.endpoint, self.paintStyleInUse, d); + } + } + } + }; + + this.repaint = this.paint; + + /** + * @deprecated + */ + this.removeConnection = this.detach; // backwards compatibility + + // is this a connection source? we make it draggable and have the + // drag listener maintain a connection with a floating endpoint. + if (jsPlumb.CurrentLibrary.isDragSupported(_element)) { + var placeholderInfo = { id:null, element:null }, + jpc = null, + existingJpc = false, + existingJpcParams = null, + _dragHandler = _makeConnectionDragHandler(placeholderInfo); + + var start = function() { + // drag might have started on an endpoint that is not actually a source, but which has + // one or more connections. + jpc = self.connectorSelector(); + var _continue = true; + // if not enabled, return + if (!self.isEnabled()) _continue = false; + // if no connection and we're not a source, return. + if (jpc == null && !params.isSource) _continue = false; + // otherwise if we're full and not allowed to drag, also return false. + if (params.isSource && self.isFull() && !dragAllowedWhenFull) _continue = false; + // if the connection was setup as not detachable or one of its endpoints + // was setup as connectionsDetachable = false, or Defaults.ConnectionsDetachable + // is set to false... + if (jpc != null && !jpc.isDetachable()) _continue = false; + + if (_continue === false) { + // this is for mootools and yui. returning false from this causes jquery to stop drag. + // the events are wrapped in both mootools and yui anyway, but i don't think returning + // false from the start callback would stop a drag. + if (jsPlumb.CurrentLibrary.stopDrag) jsPlumb.CurrentLibrary.stopDrag(); + _dragHandler.stopDrag(); + return false; + } + + // if we're not full but there was a connection, make it null. we'll create a new one. + if (jpc && !self.isFull() && params.isSource) jpc = null; + + _updateOffset( { elId : _elementId }); + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + _makeDraggablePlaceholder(placeholderInfo, self.parent); + + // set the offset of this div to be where 'inPlaceCopy' is, to start with. + // TODO merge this code with the code in both Anchor and FloatingAnchor, because it + // does the same stuff. + var ipcoel = _getElementObject(inPlaceCopy.canvas), + ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel), + po = adjustForParentOffsetAndScroll([ipco.left, ipco.top], inPlaceCopy.canvas); + jsPlumb.CurrentLibrary.setOffset(placeholderInfo.element, {left:po[0], top:po[1]}); + + // when using makeSource and a parent, we first draw the source anchor on the source element, then + // move it to the parent. note that this happens after drawing the placeholder for the + // first time. + if (self.parentAnchor) self.anchor = _currentInstance.makeAnchor(self.parentAnchor, self.elementId, _currentInstance); + + // store the id of the dragging div and the source element. the drop function will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", placeholderInfo.id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + + // create a floating endpoint. + // here we test to see if a dragProxy was specified in this endpoint's constructor params, and + // if so, we create that endpoint instead of cloning ourselves. + if (params.proxy) { + /* var floatingAnchor = new FloatingAnchor( { reference : self.anchor, referenceCanvas : self.canvas }); + + floatingEndpoint = _newEndpoint({ + paintStyle : params.proxy.paintStyle, + endpoint : params.proxy.endpoint, + anchor : floatingAnchor, + source : placeholderInfo.element, + scope:"__floating" + }); + + //$(self.canvas).hide(); + */ + self.setPaintStyle(params.proxy.paintStyle); + // if we do this, we have to cleanup the old one. like just remove its display parts + //self.setEndpoint(params.proxy.endpoint); + + } + // else { + floatingEndpoint = _makeFloatingEndpoint(self.getPaintStyle(), self.anchor, _endpoint, self.canvas, placeholderInfo.element); + // } + + if (jpc == null) { + self.anchor.locked = true; + self.setHover(false, false); + // TODO the hover call above does not reset any target endpoint's hover + // states. + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = _newConnection({ + sourceEndpoint : self, + targetEndpoint : floatingEndpoint, + source : self.endpointWillMoveTo || _getElementObject(_element), // for makeSource with parent option. ensure source element is represented correctly. + target : placeholderInfo.element, + anchors : [ self.anchor, floatingEndpoint.anchor ], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + hoverPaintStyle:params.connectorHoverStyle, + connector : params.connector, // this can also be null. Connection will use the default. + overlays : params.connectorOverlays + }); + + } else { + existingJpc = true; + jpc.connector.setHover(false, false); + // if existing connection, allow to be dropped back on the source endpoint (issue 51). + _initDropTarget(_getElementObject(inPlaceCopy.canvas), false, true); + // new anchor idx + var anchorIdx = jpc.endpoints[0].id == self.id ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index. + self.detachFromConnection(jpc); // detach from the connection while dragging is occurring. + + // store the original scope (issue 57) + var c = _getElementObject(self.canvas), + dragScope = jsPlumb.CurrentLibrary.getDragScope(c); + _setAttribute(c, "originalScope", dragScope); + // now we want to get this endpoint's DROP scope, and set it for now: we can only be dropped on drop zones + // that have our drop scope (issue 57). + var dropScope = jsPlumb.CurrentLibrary.getDropScope(c); + jsPlumb.CurrentLibrary.setDragScope(c, dropScope); + + // now we replace ourselves with the temporary div we created above: + if (anchorIdx == 0) { + existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ]; + jpc.source = placeholderInfo.element; + jpc.sourceId = placeholderInfo.id; + } else { + existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ]; + jpc.target = placeholderInfo.element; + jpc.targetId = placeholderInfo.id; + } + + // lock the other endpoint; if it is dynamic it will not move while the drag is occurring. + jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true; + // store the original endpoint and assign the new floating endpoint for the drag. + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.suspendedEndpoint.setHover(false); + jpc.endpoints[anchorIdx] = floatingEndpoint; + + // fire an event that informs that a connection is being dragged + fireConnectionDraggingEvent(jpc); + + } + // register it and register connection on it. + floatingConnections[placeholderInfo.id] = jpc; + floatingEndpoint.addConnection(jpc); + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, placeholderInfo.id, floatingEndpoint); + // tell jsplumb about it + _currentInstance.currentlyDragging = true; + }; + + var jpcl = jsPlumb.CurrentLibrary, + dragOptions = params.dragOptions || {}, + defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions), + startEvent = jpcl.dragEvents["start"], + stopEvent = jpcl.dragEvents["stop"], + dragEvent = jpcl.dragEvents["drag"]; + + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + dragOptions.scope = dragOptions.scope || self.scope; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + // extracted drag handler function so can be used by makeSource + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], _dragHandler.drag); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + var originalEvent = jpcl.getDropEvent(arguments); + _currentInstance.currentlyDragging = false; + _removeWithFunction(endpointsByElement[placeholderInfo.id], function(e) { + return e.id == floatingEndpoint.id; + }); + _removeElements( [ placeholderInfo.element[0], floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted) + _removeElement(inPlaceCopy.canvas, _element); + _currentInstance.anchorManager.clearFor(placeholderInfo.id); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false; + self.setPaintStyle(originalPaintStyle); // reset to original; may have been changed by drag proxy. + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the + // floating endpoint has been replaced. + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + + // restore the original scope (issue 57) + jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + if (self.isReattach || jpc._forceDetach || !jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc, false, false, true, originalEvent)) { + jpc.setHover(false); + jpc.floatingAnchorIndex = null; + jpc.suspendedEndpoint.addConnection(jpc); + _currentInstance.repaint(existingJpcParams[1]); + } + jpc._forceDetach = null; + } else { + // TODO this looks suspiciously kind of like an Endpoint.detach call too. + // i wonder if this one should post an event though. maybe this is good like this. + _removeElements(jpc.connector.getDisplayElements(), self.parent); + self.detachFromConnection(jpc); + } + } + self.anchor.locked = false; + self.paint({recalc:false}); + jpc.setHover(false, false); + + fireConnectionDragStopEvent(jpc); + + jpc = null; + inPlaceCopy = null; + delete endpointsByElement[floatingEndpoint.elementId]; + floatingEndpoint.anchor = null; + floatingEndpoint = null; + _currentInstance.currentlyDragging = false; + + + }); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions, true); + } + + // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections + // back onto the endpoint you detached it from. + var _initDropTarget = function(canvas, forceInit, isTransient, endpoint) { + if ((params.isTarget || forceInit) && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend( {}, dropOptions); + dropOptions.scope = dropOptions.scope || self.scope; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop'], + overEvent = jsPlumb.CurrentLibrary.dragEvents['over'], + outEvent = jsPlumb.CurrentLibrary.dragEvents['out'], + drop = function() { + + var originalEvent = jsPlumb.CurrentLibrary.getDropEvent(arguments), + draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)), + id = _getAttribute(draggable, "dragId"), + elId = _getAttribute(draggable, "elId"), + scope = _getAttribute(draggable, "originalScope"), + jpc = floatingConnections[id]; + + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0; + + // restore the original scope if necessary (issue 57) + if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope); + + var endpointEnabled = endpoint != null ? endpoint.isEnabled() : true; + + if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget) && endpointEnabled) { + + var _doContinue = true; + + // the second check here is for the case that the user is dropping it back + // where it came from. + if (jpc.suspendedEndpoint && jpc.suspendedEndpoint.id != self.id) { + if (idx == 0) { + jpc.source = jpc.suspendedEndpoint.element; + jpc.sourceId = jpc.suspendedEndpoint.elementId; + } else { + jpc.target = jpc.suspendedEndpoint.element; + jpc.targetId = jpc.suspendedEndpoint.elementId; + } + + if (!jpc.isDetachAllowed(jpc) || !jpc.endpoints[idx].isDetachAllowed(jpc) || !jpc.suspendedEndpoint.isDetachAllowed(jpc) || !_currentInstance.checkCondition("beforeDetach", jpc)) + _doContinue = false; + } + + // these have to be set before testing for beforeDrop. + if (idx == 0) { + jpc.source = self.element; + jpc.sourceId = self.elementId; + } else { + jpc.target = self.element; + jpc.targetId = self.elementId; + } + + // now check beforeDrop. this will be available only on Endpoints that are setup to + // have a beforeDrop condition (although, secretly, under the hood all Endpoints and + // the Connection have them, because they are on jsPlumbUIComponent. shhh!), because + // it only makes sense to have it on a target endpoint. + _doContinue = _doContinue && self.isDropAllowed(jpc.sourceId, jpc.targetId, jpc.scope, jpc, self); + + if (_doContinue) { + // remove this jpc from the current endpoint + jpc.endpoints[idx].detachFromConnection(jpc); + if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + + // copy our parameters in to the connection: + var params = self.getParameters(); + for (var aParam in params) + jpc.setParameter(aParam, params[aParam]); + + if (!jpc.suspendedEndpoint) { + //_initDraggableIfNecessary(self.element, params.draggable, {}); + if (params.draggable) + jsPlumb.CurrentLibrary.initDraggable(self.element, dragOptions, true); + } + else { + var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId; + // fire a detach event + fireDetachEvent({ + source : idx == 0 ? suspendedElement : jpc.source, + target : idx == 1 ? suspendedElement : jpc.target, + sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, + targetId : idx == 1 ? suspendedElementId : jpc.targetId, + sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], + targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1], + connection : jpc + }, true, originalEvent); + } + + // finalise will inform the anchor manager and also add to + // connectionsByScope if necessary. + _finaliseConnection(jpc, null, originalEvent); + } + else { + // otherwise just put it back on the endpoint it was on before the drag. + if (jpc.suspendedEndpoint) { + // self.detachFrom(jpc); + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.setHover(false); + jpc._forceDetach = true; + if (idx == 0) { + jpc.source = jpc.suspendedEndpoint.element; + jpc.sourceId = jpc.suspendedEndpoint.elementId; + } else { + jpc.target = jpc.suspendedEndpoint.element; + jpc.targetId = jpc.suspendedEndpoint.elementId;; + } + jpc.suspendedEndpoint.addConnection(jpc); + + jpc.endpoints[0].repaint(); + jpc.repaint(); + _currentInstance.repaint(jpc.source.elementId); + jpc._forceDetach = false; + } + } + + jpc.floatingAnchorIndex = null; + } + _currentInstance.currentlyDragging = false; + delete floatingConnections[id]; + } + }; + + dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop); + dropOptions[overEvent] = _wrap(dropOptions[overEvent], function() { + if (self.isTarget) { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + } + } + }); + dropOptions[outEvent] = _wrap(dropOptions[outEvent], function() { + if (self.isTarget) { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments), + id = _getAttribute( _getElementObject(draggable), "dragId"), + jpc = floatingConnections[id]; + if (jpc != null) { + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + } + } + }); + jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions, true, isTransient); + } + }; + + // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported. + _initDropTarget(_getElementObject(self.canvas), true, !(params._transient || self.anchor.isFloating), self); + + return self; + }; + }; + + var jsPlumb = window.jsPlumb = new jsPlumbInstance(); + jsPlumb.getInstance = function(_defaults) { + var j = new jsPlumbInstance(_defaults); + j.init(); + return j; + }; + + var _curryAnchor = function(x, y, ox, oy, type, fnInit) { + return function(params) { + params = params || {}; + //var a = jsPlumb.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance); + var a = params.jsPlumbInstance.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance); + a.type = type; + if (fnInit) fnInit(a, params); + return a; + }; + }; + jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1, "TopCenter"); + jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1, "BottomCenter"); + jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0, "LeftMiddle"); + jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0, "RightMiddle"); + jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0, "Center"); + jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1, "TopRight"); + jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1, "BottomRight"); + jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1, "TopLeft"); + jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1, "BottomLeft"); + + // TODO test that this does not break with the current instance idea + jsPlumb.Defaults.DynamicAnchors = function(params) { + return params.jsPlumbInstance.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"], params.elementId, params.jsPlumbInstance); + }; + jsPlumb.Anchors["AutoDefault"] = function(params) { + var a = params.jsPlumbInstance.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors(params)); + a.type = "AutoDefault"; + return a; + }; + + jsPlumb.Anchors["Assign"] = _curryAnchor(0,0,0,0,"Assign", function(anchor, params) { + // find what to use as the "position finder". the user may have supplied a String which represents + // the id of a position finder in jsPlumb.AnchorPositionFinders, or the user may have supplied the + // position finder as a function. we find out what to use and then set it on the anchor. + var pf = params.position || "Fixed"; + anchor.positionFinder = pf.constructor == String ? params.jsPlumbInstance.AnchorPositionFinders[pf] : pf; + // always set the constructor params; the position finder might need them later (the Grid one does, + // for example) + anchor.constructorParams = params; + }); + + // Continuous anchor is just curried through to the 'get' method of the continuous anchor + // factory. + jsPlumb.Anchors["Continuous"] = function(params) { + return params.jsPlumbInstance.continuousAnchorFactory.get(params); + }; + + // these are the default anchor positions finders, which are used by the makeTarget function. supply + // a position finder argument to that function allows you to specify where the resulting anchor will + // be located + jsPlumb.AnchorPositionFinders = { + "Fixed": function(dp, ep, es, params) { + return [ (dp.left - ep.left) / es[0], (dp.top - ep.top) / es[1] ]; + }, + "Grid":function(dp, ep, es, params) { + var dx = dp.left - ep.left, dy = dp.top - ep.top, + gx = es[0] / (params.grid[0]), gy = es[1] / (params.grid[1]), + mx = Math.floor(dx / gx), my = Math.floor(dy / gy); + return [ ((mx * gx) + (gx / 2)) / es[0], ((my * gy) + (gy / 2)) / es[1] ]; + } + }; +})(); diff --git a/archive/1.3.9/jsPlumb-1.3.9-tests.js b/archive/1.3.9/jsPlumb-1.3.9-tests.js new file mode 100644 index 000000000..05dfd297d --- /dev/null +++ b/archive/1.3.9/jsPlumb-1.3.9-tests.js @@ -0,0 +1,3854 @@ +// _jsPlumb qunit tests. + +QUnit.config.reorder = false; + +var _getContextNode = function() { + return $("#container"); +}; + +var assertContextExists = function() { + ok(_getContextNode().length == 1, "context node exists"); +}; + +var assertContextSize = function(elementCount) { + //equals(_getContextNode().children().length - _divs.length, elementCount, 'context has ' + elementCount + ' children'); +}; + +var assertContextEmpty = function() { + equals(_getContextNode().children.length, 0, "context empty"); +}; + +var assertEndpointCount = function(elId, count, _jsPlumb) { + equals(_jsPlumb.getTestHarness().endpointCount(elId), count, elId + " has " + count + (count > 1) ? "endpoints" : "endpoint"); + equals(_jsPlumb.anchorManager.getEndpointsFor(elId).length, count, "anchor manager has " + count + (count > 1) ? "endpoints" : "endpoint"); +}; + +var assertConnectionCount = function(endpoint, count) { + equals(endpoint.connections.length, count, "endpoint has " + count + " connections"); +}; + +var assertConnectionByScopeCount = function(scope, count, _jsPlumb) { + equals(_jsPlumb.getTestHarness().connectionCount(scope), count, 'Scope ' + scope + " has " + count + (count > 1) ? "connections" : "connection"); + +}; + +var _divs = []; +var _addDiv = function(id, parent) { + var d1 = document.createElement("div"); + if (parent) parent.append(d1); else _getContextNode().append(d1); + d1 = jsPlumb.CurrentLibrary.getElementObject(d1); + jsPlumb.CurrentLibrary.setAttribute(d1, "id", id); + _divs.push(id); + return d1; +}; + +var defaults = null, + _cleanup = function(_jsPlumb) { + + _jsPlumb.reset(); + _jsPlumb.Defaults = defaults; + + for (var i in _divs) { + $("#" + _divs[i]).remove(); + } + _divs.splice(0, _divs.length - 1); + + $("#container").empty(); +}; + +var testSuite = function(renderMode, _jsPlumb) { + + + module("jsPlumb", { + teardown: function() { + _cleanup(_jsPlumb); + }, + setup:function() { + defaults = jsPlumb.extend({}, _jsPlumb.Defaults); + } + }); + + // setup the container + var container = document.createElement("div"); + container.id = "container"; + document.body.appendChild(container); + + var jpcl = jsPlumb.CurrentLibrary; + + _jsPlumb.setRenderMode(renderMode); + + test(renderMode + " : getElementObject", function() { + var e = document.createElement("div"); + e.id = "FOO"; + document.body.appendChild(e); + var el = jpcl.getElementObject(e); + equals(jpcl.getAttribute(el, "id"), "FOO"); + }); + + test(renderMode + " : getDOMElement", function() { + var e = document.createElement("div"); + e.id = "FOO"; + document.body.appendChild(e); + var el = jpcl.getElementObject(e); + var e2 = jpcl.getDOMElement(el); + equals(e2.id, "FOO"); + }); + + test(renderMode + ': _jsPlumb setup', function() { + ok(_jsPlumb, "loaded"); + }); + + test(renderMode + ': getId', function() { + var d10 = _addDiv('d10'); + equals(_jsPlumb.getTestHarness().getId(d10), "d10"); + }); + + test(renderMode + ': create a simple endpoint', function() { + var d1 = _addDiv("d1"); + var e = _jsPlumb.addEndpoint($("#d1"), {}); + ok(e, 'endpoint exists'); + assertEndpointCount("d1", 1, _jsPlumb); + ok(e.id != null, "endpoint has had an id assigned"); + }); + + test(renderMode + ': create and remove a simple endpoint', function() { + var d1 = _addDiv("d1"); + var ee = _jsPlumb.addEndpoint(d1, {uuid:"78978597593"}); + ok(ee != null, "endpoint exists"); + var e = _jsPlumb.getEndpoint("78978597593"); + ok(e != null, "the endpoint could be retrieved by UUID"); + ok(e.id != null, "the endpoint has had an id assigned to it"); + assertEndpointCount("d1", 1, _jsPlumb); + assertContextSize(1); // one Endpoint canvas. + _jsPlumb.removeEndpoint(d1, ee); + assertEndpointCount("d1", 0, _jsPlumb); + e = _jsPlumb.getEndpoint("78978597593"); + equals(e, null, "the endpoint has been deleted"); + assertContextSize(0); // no Endpoint canvases. + }); + + test(renderMode + ': create two simple endpoints, registered using a selector', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + d1.addClass("window");d2.addClass("window"); + var endpoints = _jsPlumb.addEndpoint($(".window"), {}); + equals(endpoints.length, 2, "endpoint added to both windows"); + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + }); + + test(renderMode + ': create two simple endpoints, registered using an array of element ids', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + d1.addClass("window");d2.addClass("window"); + var endpoints = _jsPlumb.addEndpoint(["d1", "d2"], {}); + equals(endpoints.length, 2, "endpoint added to both windows"); + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + }); + + test(renderMode + ': draggable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1"); + var e = _jsPlumb.addEndpoint(d1, {isSource:true}); + ok(e, 'endpoint exists'); + }); + + test(renderMode + ': droppable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1") + var e = _jsPlumb.addEndpoint(d1, {isTarget:true}); + ok(e, 'endpoint exists'); + }); + + test(renderMode + ': defaultEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = _jsPlumb.addEndpoint(d3, {isSource:true}); + ok(e3.anchor, 'endpoint 3 has an anchor'); + var e4 = _jsPlumb.addEndpoint(d4, {isSource:true}); + assertEndpointCount("d3", 1, _jsPlumb); + assertEndpointCount("d4", 1, _jsPlumb); + assertContextSize(2); // one canvas for each of our two endpoints. + ok(!e3.isFull(), "endpoint 3 is not full."); + _jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // we have one connection + _jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // should have refused the connection; default max is 1. + assertContextSize(3); // now we have one more canvas, for the Connection that was accepted. + }); + + test(renderMode + ': specifiedEndpointMaxConnections', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = _jsPlumb.addEndpoint(d5, {isSource:true, maxConnections:3}); + ok(e5.anchor, 'endpoint 5 has an anchor'); + var e6 = _jsPlumb.addEndpoint(d6, {isSource:true, maxConnections:2}); // this one has max TWO + assertEndpointCount("d5", 1, _jsPlumb); assertEndpointCount("d6", 1, _jsPlumb); + assertContextSize(2); + ok(!e5.isFull(), "endpoint 5 is not full."); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 1); // we have one connection + assertContextSize(3); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // two connections + assertContextSize(4); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // should have refused; max is 2, for d4. + assertContextSize(4); + }); + + test(renderMode + ': noEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = _jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + var e4 = _jsPlumb.addEndpoint(d4, {isSource:true, maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertContextSize(3); + assertConnectionCount(e3, 1); // we have one connection + _jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 2); // we have two. etc (default was one. this proves max is working). + assertContextSize(4); + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = _jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e4}); + assertConnectionCount(e4, 3); + assertContextSize(6); + }); + + test(renderMode + ': anchors equal', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + ok(a1.equals(a2), "anchors are the same"); + }); + + test(renderMode + ': anchors equal with offsets', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb); + ok(a1.equals(a2), "anchors are the same"); + }); + + test(renderMode + ': anchors not equal', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 0, 1], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + ok(!a1.equals(a2), "anchors are different"); + }); + + test(renderMode + ': anchor not equal with offsets', function() { + var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb); + var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb); + ok(!a1.equals(a2), "anchors are different"); + }); + + test(renderMode + ': detach does not fail when no arguments are provided', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + _jsPlumb.connect({source:d3, target:d4}); + _jsPlumb.detach(); + }); + + // test that detach does not fire an event by default + test(renderMode + ': _jsPlumb.detach should fire detach event by default', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach(conn); + equals(eventCount, 1); + }); + + // test that detach does not fire an event by default + test(renderMode + ': _jsPlumb.detach should fire detach event by default, using params object', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({connection:conn}); + equals(eventCount, 1); + }); + + // test that detach fires an event when instructed to do so + test(renderMode + ': _jsPlumb.detach should not fire detach event when instructed to not do so', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach(conn, {fireEvent:false}); + equals(eventCount, 0); + }); + + // issue 81 + test(renderMode + ': _jsPlumb.detach should fire only one detach event (pass Connection as argument)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach(conn); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': _jsPlumb.detach should fire only one detach event (pass Connection as param in argument)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({connection:conn}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': detach should fire only one detach event (pass source and targets as strings as arguments in params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({source:"d5", target:"d6"}); + equals(eventCount, 1); + }); + + // issue 81 + test(renderMode + ': detach should fire only one detach event (pass source and targets as divs as arguments in params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var conn = _jsPlumb.connect({source:d5, target:d6}); + var eventCount = 0; + _jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; }); + _jsPlumb.detach({source:d5, target:d6, fireEvent:true}); + equals(eventCount, 1); + }); + + //TODO make sure you run this test with a single detach call, to ensure that + // single detach calls result in the connection being removed. detachEveryConnection can + // just blow away the connectionsByScope array and recreate it. + test(renderMode + ': getConnections (simple case, default scope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + _jsPlumb.connect({source:d5, target:d6}); + assertContextSize(3); + var c = _jsPlumb.getConnections(); // will get all connections in the default scope. + equals(c.length, 1, "there is one connection"); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by element id using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + _jsPlumb.connect({source:d5, target:d6}); + _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach({source:'d5', target:'d6'}); + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by id using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + _jsPlumb.connect({source:d5, target:d6}); + _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach({source:"d5", target:"d6"}); + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by element object using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + _jsPlumb.connect({source:d5, target:d6}); + _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach({source:d5, target:d6}); + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); + }); + + test(renderMode + ': getConnections (simple case, default scope; detach by Connection)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + var c56 = _jsPlumb.connect({source:d5, target:d6}); + var c67 = _jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 2, "there are two connections initially"); + _jsPlumb.detach(c56); + assertContextSize(5); // check that the connection canvas was removed. + c = _jsPlumb.getConnections(); // will get all connections + equals(c.length, 1, "after detaching one, there is now one connection."); + }); + +// beforeDetach functionality + + test(renderMode + ": detach; beforeDetach on connect call returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { return false; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": detach; beforeDetach on connect call returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { return true; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": detach; beforeDetach on connect call throws an exception; we treat it with the contempt it deserves and pretend it said the detach was ok.", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { throw "i am an example of badly coded beforeDetach, but i don't break jsPlumb "; }}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": detach; beforeDetach on addEndpoint call to source Endpoint returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return false; } }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": detach; beforeDetach on addEndpoint call to source Endpoint returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return true; } }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed"); + }); + + + test(renderMode + ": Endpoint.detach; beforeDetach on addEndpoint call to source Endpoint returns false; Endpoint.detach returns false too (the UI needs it to)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return false; } }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially"); + var success = e1.detach(c); + equals(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied"); + ok(!success, "Endpoint reported detach did not execute"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns false but detach is forced", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c, {forceDetach:true}); + equals(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was denied but forced anyway"); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was denied but forced anyway"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return true; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was allowed"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach bound to _jsPlumb returns false", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + _jsPlumb.bind("beforeDetach", function(connection) { + ok(connection.sourceId === "d1", "connection is provided and configured with correct source"); + ok(connection.targetId === "d2", "connection is provided and configured with correct target"); + return false; + }); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection after detach call was denied"); + }); + + test(renderMode + ": _jsPlumb.detach; beforeDetach bound to _jsPlumb returns true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true }); + var c = _jsPlumb.connect({source:e1,target:e2}); + _jsPlumb.bind("beforeDetach", function(connection) { + ok(connection.sourceId === "d1", "connection is provided and configured with correct source"); + ok(connection.targetId === "d2", "connection is provided and configured with correct target"); + return true; + }); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detach(c); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was denied"); + }); + + test(renderMode + ": _jsPlumb.detachAllConnections ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detachAllConnections(d1); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachAll was called"); + }); + + test(renderMode + ": _jsPlumb.detachEveryConnection ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + _jsPlumb.detachEveryConnection(); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachEveryConnection was called"); + }); + + test(renderMode + ": Endpoint.detachAll ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }), + e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } }); + var c = _jsPlumb.connect({source:e1,target:e2}); + equals(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially"); + e1.detachAll(); + equals(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachAllConnections was called"); + }); + +// ******** end of beforeDetach tests ************** + +// detachEveryConnection/detachAllConnections fireEvent overrides tests + + test(renderMode + ": _jsPlumb.detachEveryConnection fires events", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0; + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachEveryConnection(); + equals(connCount, 0, "no connections registered"); + }); + + test(renderMode + ": _jsPlumb.detachEveryConnection doesn't fire events when instructed not to", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0; + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachEveryConnection({fireEvent:false}); + equals(connCount, 2, "two connections registered"); + }); + + test(renderMode + ": _jsPlumb.detachAllConnections fires events", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0, + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachAllConnections("d1"); + equals(connCount, 0, "no connections registered"); + }); + + test(renderMode + ": _jsPlumb.detachAllConnections doesn't fire events when instructed not to", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0, + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.bind("jsPlumbConnection", function() { connCount++; }); + _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; }); + _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.connect({source:d1, target:d2}); + equals(connCount, 2, "two connections registered"); + _jsPlumb.detachAllConnections("d1", {fireEvent:false}); + equals(connCount, 2, "two connections registered"); + }); + + test(renderMode + ': getConnections (scope testScope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + _jsPlumb.connect({source:d5, target:d6, scope:'testScope'}); + var c = _jsPlumb.getConnections("testScope"); // will get all connections in testScope + equals(c.length, 1, "there is one connection"); + equals(c[0].sourceId, 'd5', "the connection's source is d5"); + equals(c[0].targetId, 'd6', "the connection's source is d6"); + c = _jsPlumb.getConnections(); // will get all connections in default scope; should be none. + equals(c.length, 0, "there are no connections in the default scope"); + }); + + test(renderMode + ': _jsPlumb.getAllConnections (filtered by scope)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + _jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + _jsPlumb.connect({source:d9, target:d10}); // default scope + var c = _jsPlumb.getAllConnections(); // will get all connections + equals(c[_jsPlumb.getDefaultScope()].length, 1); + equals(c['testScope'].length, 1); + // now supply a list of scopes + c = _jsPlumb.getConnections(); + equals(c.length, 1); + c = _jsPlumb.getConnections("testScope"); + equals(c.length, 1, "there is one connection in 'testScope'"); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by scope and sourceId)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + _jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + _jsPlumb.connect({source:d9, target:d8, scope:'testScope'}); + _jsPlumb.connect({source:d9, target:d10}); // default scope + var c = _jsPlumb.getConnections({scope:'testScope', source:'d8'}); // will get all connections with sourceId 'd8' + equals(c.length, 1, "there is one connection in 'testScope' from d8"); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by scope, source id and target id)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope'}); + var c = _jsPlumb.getConnections({scope:'testScope', source:'d11', target:'d13'}); + equals(c.length, 1, "there is one connection from d11 to d13"); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = _jsPlumb.getConnections({scope:['testScope','testScope3']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes and source ids)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = _jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + }); + + test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes, source ids and target ids)', function() { + _jsPlumb.detachEveryConnection(); + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'), d14=_addDiv("d14"), d15=_addDiv("d15"); + _jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + _jsPlumb.connect({source:d11, target:d14, scope:'testScope'}); + _jsPlumb.connect({source:d11, target:d15, scope:'testScope'}); + _jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + _jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + assertContextSize(18); + var c = _jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11'], target:['d14', 'd15']}); + equals(c['testScope'].length, 2, 'there are two connections in testScope'); + equals(c['testScope3'], null, 'there are no connections in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + var anEntry = c['testScope'][0]; + ok(anEntry.endpoints[0] != null, "Source endpoint is set"); + ok(anEntry.endpoints[1] != null, "Target endpoint is set"); + equals(anEntry.source.attr("id"), "d11", "Source is div d11"); + equals(anEntry.target.attr("id"), "d14", "Target is div d14"); + }); + + test(renderMode + ': getEndpoints, one Endpoint added by addEndpoint, get Endpoints by selector', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + _jsPlumb.addEndpoint(d1); + var e = _jsPlumb.getEndpoints(d1); + equals(e.length, 1, "there is one endpoint for element d1"); + }); + + test(renderMode + ': getEndpoints, one Endpoint added by addEndpoint, get Endpoints by id', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + _jsPlumb.addEndpoint(d1); + var e = _jsPlumb.getEndpoints("d1"); + equals(e.length, 1, "there is one endpoint for element d1"); + }); + + test(renderMode + ': connection event listeners', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null/*, returnedParams2 = null*/; + _jsPlumb.bind("jsPlumbConnection", function(params) { + returnedParams = $.extend({}, params); + }); + var c = _jsPlumb.connect({source:d1, target:d2}); + ok(returnedParams != null, "new connection listener event was fired"); + ok(returnedParams.connection != null, 'connection is set'); + equals(returnedParams.sourceId, "d1", 'sourceid is set'); + equals(returnedParams.targetId, "d2", 'targetid is set'); + equals(returnedParams.source.attr("id"), "d1", 'source is set'); + equals(returnedParams.target.attr("id"), "d2" , 'target is set'); + ok(returnedParams.sourceEndpoint != null, "source endpoint is not null"); + ok(returnedParams.targetEndpoint != null, "target endpoint is not null"); + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.detach(c); + ok(returnedParams.connection != null, 'connection is set'); + }); + + test(renderMode + ': detach event listeners (detach by connection)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + ok(returnedParams.connection != null, "removed connection listener event passed in connection"); + }); + + test(renderMode + ': detach event listeners (detach by element ids)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach({source:"d1",target:"d2"}); + ok(returnedParams != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (detach by elements)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach({source:d1,target:d2}); + ok(returnedParams != null, "removed connection listener event was fired"); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detach method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detachFrom method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachFrom(e2); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via Endpoint.detachAll method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachAll(); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); + }); + + test(renderMode + ': detach event listeners (via _jsPlumb.deleteEndpoint method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + _jsPlumb.deleteEndpoint(e1); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(1); + }); + + test(renderMode + ': detach event listeners (ensure cleared by _jsPlumb.reset)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + returnedParams = $.extend({}, params); + }); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach({source:d1,target:d2, fireEvent:true}); + ok(returnedParams != null, "removed connection listener event was fired"); + returnedParams = null; + + _jsPlumb.reset(); + var conn = _jsPlumb.connect({source:d1, target:d2}); + _jsPlumb.detach(d1,d2); + ok(returnedParams == null, "connection listener was cleared by _jsPlumb.reset()"); + }); + + test(renderMode + ': connection events that throw errors', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null, returnedParams2 = null; + _jsPlumb.bind("jsPlumbConnection", function(params) { + returnedParams = $.extend({}, params); + throw "oh no!"; + }); + _jsPlumb.connect({source:d1, target:d2}); + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + _jsPlumb.connect({source:d3, target:d4}); + ok(returnedParams != null, "new connection listener event was fired; we threw an error, _jsPlumb survived."); + }); + + test(renderMode + ": Endpoint.detachFrom", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + e16.detachFrom(e17); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by _jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": Endpoint.detachFromConnection", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + e16.detachFromConnection(conn); + assertContextSize(3); // all canvases should remain; the connection was not removed. + // but endpoint e16 should have no connections now. + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": Endpoint.detachAll", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + var e16 = _jsPlumb.addEndpoint($("#d16"), {isSource:true,maxConnections:-1}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint($("#d17"), {isSource:true}); + var e18 = _jsPlumb.addEndpoint($("#d18"), {isSource:true}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e18}); + assertConnectionCount(e16, 2); + assertConnectionCount(e17, 1); + assertConnectionCount(e18, 1); + assertContextSize(5); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 2, _jsPlumb); + e16.detachAll(); + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": Endpoint.detach", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + e16.detach(conn); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by _jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": Endpoint.isConnectedTo", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.isConnectedTo(e17), true, "e16 and e17 are connected"); + }); + + asyncTest(renderMode + "jsPlumbUtil.setImage on Endpoint, with supplied onload", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { + endpoint:[ "Image", { + src:"../img/endpointTest1.png", + onload:function(imgEp) { + _jsPlumb.repaint("d1"); + ok(imgEp.img.src.indexOf("endpointTest1.png") != -1, "image source is correct"); + ok(imgEp.img.src.indexOf("endpointTest1.png") != -1, "image elementsource is correct"); + + imgEp.canvas.setAttribute("id", "iwilllookforthis"); + + ok(document.getElementById("iwilllookforthis") != null, "image element is present"); + _jsPlumb.removeAllEndpoints("d1"); + ok(document.getElementById("iwilllookforthis") == null, "image element was removed after remove endpoint"); + } + } ] + }; + start(); + _jsPlumb.addEndpoint(d1, e); + }); + + test(renderMode + ": setting endpoint uuid", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + equals(e16.getUuid(), uuid, "endpoint's uuid was set correctly"); + }); + + test(renderMode + ": _jsPlumb.getEndpoint (by uuid)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by uuid, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + _jsPlumb.deleteEndpoint(uuid); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + var ebe = _jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by uuid, connections too)", function() { + // create two endpoints (one with a uuid), add them to two divs and then connect them. + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + // check that the connection was ok. + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + // delete the endpoint that has a uuid. verify that the endpoint cannot be retrieved and that the connection has been removed, but that + // element d17 still has its Endpoint. + _jsPlumb.deleteEndpoint(uuid); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + var ebe = _jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + ebe = _jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 1, "element d17 still has its Endpoint"); + assertContextSize(1); + + // now delete d17's endpoint and check that it has gone. + _jsPlumb.deleteEndpoint(e17); + f = _jsPlumb.getEndpoint(e17); + equals(f, null, "endpoint has been deleted"); + ebe = _jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 0, "element d17 no longer has any Endpoints"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by reference, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + _jsPlumb.deleteEndpoint(e16); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEndpoint (by reference, connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + _jsPlumb.deleteEndpoint(e16); + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + assertContextSize(1); + }); + + test(renderMode + ": _jsPlumb.deleteEveryEndpoint", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + assertContextSize(2); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + _jsPlumb.deleteEveryEndpoint(); + + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = _jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); + }); + + test(renderMode + ": _jsPlumb.deleteEveryEndpoint (connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + var e = _jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + _jsPlumb.deleteEveryEndpoint(); + + var f = _jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = _jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); // no canvases. so all connection canvases have been cleaned up too. + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchor:[0,0.5,0,-1]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:"TopCenter"}); + assertContextSize(2); + equals(e16.anchor.x, 0); + equals(e16.anchor.y, 0.5); + equals(e17.anchor.x, 0.5); + equals(e17.anchor.y, 0); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (tooltip)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {tooltip:"FOO"}), + e17 = _jsPlumb.addEndpoint(d17, {tooltip:"BAZ"}); + assertContextSize(2); + equals(e16.canvas.getAttribute("title"), "FOO"); + equals(e17.canvas.getAttribute("title"), "BAZ"); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case, dynamic anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + assertContextSize(2); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case, two arg method)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchor:[0,0.5,0,-1]}); + var e17 = _jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchor:"TopCenter"}); + assertContextSize(2); + equals(e16.anchor.x, 0); + equals(e16.anchor.y, 0.5); + equals(false, e16.isTarget); + equals(true, e16.isSource); + equals(e17.anchor.x, 0.5); + equals(e17.anchor.y, 0); + equals(true, e17.isTarget); + equals(false, e17.isSource); + }); + + + test(renderMode + ": _jsPlumb.addEndpoints (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false, anchor:[0,0.5,0,-1] }, { isTarget:true, isSource:false, anchor:"TopCenter" }]); + assertContextSize(2); + equals(e16[0].anchor.x, 0); + equals(e16[0].anchor.y, 0.5); + equals(false, e16[0].isTarget); + equals(true, e16[0].isSource); + equals(e16[1].anchor.x, 0.5); + equals(e16[1].anchor.y, 0); + equals(true, e16[1].isTarget); + equals(false, e16[1].isSource); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (with reference params)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var refParams = {anchor:"RightMiddle"}; + var e16 = _jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false}, { isTarget:true, isSource:false }], refParams); + assertContextSize(2); + equals(e16[0].anchor.x, 1); + equals(e16[0].anchor.y, 0.5); + equals(false, e16[0].isTarget); + equals(true, e16[0].isSource); + equals(e16[1].anchor.x, 1); + equals(e16[1].anchor.y, 0.5); + equals(true, e16[1].isTarget); + equals(false, e16[1].isSource); + }); + + test(renderMode + ": _jsPlumb.addEndpoint (simple case, dynamic anchors, two arg method)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchors:["TopCenter", "BottomCenter"]}); + assertContextSize(2); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (default overlays)", function() { + _jsPlumb.Defaults.Overlays = [ + [ "Label", { id:"label" } ] + ]; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16), + e2 = _jsPlumb.addEndpoint(d17); + + ok(e1.getOverlay("label") != null, "endpoint 1 has overlay from defaults"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (default overlays)", function() { + _jsPlumb.Defaults.Overlays = [ + [ "Label", { id:"label" } ] + ]; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16, { + overlays:[ + ["Label", { id:"label2", location:[ 0.5, 1 ] } ] + ] + }), + e2 = _jsPlumb.addEndpoint(d17); + + ok(e1.getOverlay("label") != null, "endpoint 1 has overlay from defaults"); + ok(e1.getOverlay("label2") != null, "endpoint 1 has overlay from addEndpoint call"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (end point set label)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16), + e2 = _jsPlumb.addEndpoint(d17); + + e1.setLabel("FOO"); + equals(e1.getLabel(), "FOO", "endpoint's label is correct"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (end point set label in constructor, as string)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16, {label:"FOO"}), + e2 = _jsPlumb.addEndpoint(d17); + + equals(e1.getLabel(), "FOO", "endpoint's label is correct"); + }); + + test(renderMode + ": _jsPlumb.addEndpoints (end point set label in constructor, as function)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e1 = _jsPlumb.addEndpoint(d16, {label:function() { return "BAZ"; }, labelLocation:0.1}), + e2 = _jsPlumb.addEndpoint(d17); + + equals(e1.getLabel()(), "BAZ", "endpoint's label is correct"); + equals(e1.getLabelOverlay().getLocation(), 0.1, "endpoint's label's location is correct"); + }); + +// ****************** makeTarget (and associated methods) tests ******************************************** + + test(renderMode + ": _jsPlumb.makeTarget (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.makeTarget (specify two divs in an array)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.makeTarget([d16, d17], { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.makeTarget (specify two divs by id in an array)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.makeTarget(["d16", "d17"], { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.makeTarget (specify divs by selector)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + d16.addClass("FOO");d17.addClass("FOO"); + _jsPlumb.makeTarget($(".FOO"), { isTarget:true,anchor:"TopCenter" }); + equals(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable")); + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e.length, 1, "d17 has one endpoint"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case, two connect calls)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17"}); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 2, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case, two connect calls, uniqueEndpoint set)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true, anchor:"LeftMiddle", uniqueEndpoint:true, maxConnections:-1 }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17"}); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].connections.length, 2, "endpoint on d17 has two connections"); + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (newConnection:true specified)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isTarget:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + equals(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable")); + _jsPlumb.connect({source:e16, target:"d17", newConnection:true}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0.5, "anchor is BottomCenter"); //here we should be seeing the default anchor + equals(e[0].anchor.y, 1, "anchor is BottomCenter"); //here we should be seeing the default anchor + }); + +// jsPlumb.connect, after makeSource has been called on some element + test(renderMode + ": _jsPlumb.connect after makeSource (simple case)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeSource (simple case, two connect calls)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 2, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (simple case, two connect calls, uniqueEndpoint set)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true, anchor:"LeftMiddle", uniqueEndpoint:true, maxConnections:-1 }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].connections.length, 2, "endpoint on d17 has two connections"); + }); + + test(renderMode + ": _jsPlumb.connect after makeTarget (newConnection:true specified)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16, newConnection:true}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0.5, "anchor is BottomCenter"); //here we should be seeing the default anchor + equals(e[0].anchor.y, 1, "anchor is BottomCenter"); //here we should be seeing the default anchor + }); + + test(renderMode + ": _jsPlumb.connect after makeSource on child; wth parent set (parent should be recognised)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18", d17); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d18, { isSource:true,anchor:"LeftMiddle", parent:d17 }); // give it a non-default anchor, we will check this below. + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + assertEndpointCount("d18", 0, _jsPlumb); + var e = _jsPlumb.getEndpoints("d17"); + equals(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + equals(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget + }); + + // makeSource, then disable it. should not be able to make a connection from it. + test(renderMode + ": _jsPlumb.connect after makeSource and setSourceEnabled(false) (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.setSourceEnabled(d17, false); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + }); + + // makeSource, then disable it. should not be able to make a connection from it. + test(renderMode + ": _jsPlumb.connect after makeSource and setSourceEnabled(false) (selector as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.setSourceEnabled($("div"), false); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + }); + + // makeSource, then toggle its enabled state. should not be able to make a connection from it. + test(renderMode + ": _jsPlumb.connect after makeSource and toggleSourceEnabled() (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.toggleSourceEnabled(d17); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + + _jsPlumb.toggleSourceEnabled(d17); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + // makeSource, then disable it. should not be able to make a connection from it. + test(renderMode + ": _jsPlumb.connect after makeSource and toggleSourceEnabled() (selector as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.toggleSourceEnabled($("div")); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + _jsPlumb.toggleSourceEnabled($("div")); + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + test(renderMode + ": jsPlumb.isSource and jsPlumb.isSourceEnabled", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + ok(_jsPlumb.isSource(d17) == true, "d17 is recognised as connection source"); + ok(_jsPlumb.isSourceEnabled(d17) == true, "d17 is recognised as enabled"); + _jsPlumb.setSourceEnabled(d17, false); + ok(_jsPlumb.isSourceEnabled(d17) == false, "d17 is recognised as disabled"); + }); + + // makeSource, then disable it. should not be able to make a connection to it. + test(renderMode + ": _jsPlumb.connect after makeTarget and setTargetEnabled(false) (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.setTargetEnabled(d17, false); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + }); + + // makeTarget, then disable it. should not be able to make a connection to it. + test(renderMode + ": _jsPlumb.connect after makeTarget and setTargetEnabled(false) (selector as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.setTargetEnabled($("div"), false); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + }); + + // makeTarget, then toggle its enabled state. should not be able to make a connection to it. + test(renderMode + ": _jsPlumb.connect after makeTarget and toggleTargetEnabled() (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.toggleTargetEnabled(d17); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + + _jsPlumb.toggleTargetEnabled(d17); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + // makeTarget, then disable it. should not be able to make a connection to it. + test(renderMode + ": _jsPlumb.connect after makeTarget and toggleTargetEnabled() (selector as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + _jsPlumb.toggleTargetEnabled($("div")); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 0, _jsPlumb); + _jsPlumb.toggleTargetEnabled($("div")); + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + test(renderMode + ": jsPlumb.isTarget and jsPlumb.isTargetEnabled", function() { + var d17 = _addDiv("d17"); + _jsPlumb.makeTarget(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + ok(_jsPlumb.isTarget(d17) == true, "d17 is recognised as connection target"); + ok(_jsPlumb.isTargetEnabled(d17) == true, "d17 is recognised as enabled"); + _jsPlumb.setTargetEnabled(d17, false); + ok(_jsPlumb.isTargetEnabled(d17) == false, "d17 is recognised as disabled"); + }); + + // makeSource, then unmake it. should not be able to make a connection from it. then connect to it, which should succeed, + // because jsPlumb will just add a new endpoint. + test(renderMode + ": jsPlumb.unmakeSource (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + ok(_jsPlumb.isSource(d17) == true, "d17 is currently a source"); + // unmake source + _jsPlumb.unmakeSource(d17); + ok(_jsPlumb.isSource(d17) == false, "d17 is no longer a source"); + + // this should succeed, because d17 is no longer a source and so jsPlumb will just create and add a new endpoint to d17. + _jsPlumb.connect({source:"d17", target:e16}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + // maketarget, then unmake it. should not be able to make a connection to it. + test(renderMode + ": jsPlumb.unmakeTarget (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + _jsPlumb.makeTarget(d17, { isSource:true,anchor:"LeftMiddle" }); // give it a non-default anchor, we will check this below. + ok(_jsPlumb.isTarget(d17) == true, "d17 is currently a target"); + // unmake target + _jsPlumb.unmakeTarget(d17); + ok(_jsPlumb.isTarget(d17) == false, "d17 is no longer a target"); + + // this should succeed, because d17 is no longer a target and so jsPlumb will just create and add a new endpoint to d17. + _jsPlumb.connect({source:e16, target:"d17"}); + assertEndpointCount("d16", 1, _jsPlumb); + assertEndpointCount("d17", 1, _jsPlumb); + }); + + + test(renderMode + ": jsPlumb.removeEverySource and removeEveryTarget (string id as argument)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"); + _jsPlumb.makeSource(d16).makeTarget(d17).makeSource(d18); + ok(_jsPlumb.isSource(d16) == true, "d16 is a source"); + ok(_jsPlumb.isTarget(d17) == true, "d17 is a target"); + ok(_jsPlumb.isSource(d18) == true, "d18 is a source"); + + _jsPlumb.unmakeEverySource(); + _jsPlumb.unmakeEveryTarget(); + + ok(_jsPlumb.isSource(d16) == false, "d16 is no longer a source"); + ok(_jsPlumb.isTarget(d17) == false, "d17 is no longer a target"); + ok(_jsPlumb.isSource(d18) == false, "d18 is no longer a source"); + }); + +// *********************** end of makeTarget (and associated methods) tests ************************ + + test(renderMode + ': _jsPlumb.connect (between two Endpoints)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1, _jsPlumb); // no new endpoint should have been added + assertEndpointCount("d2", 1, _jsPlumb); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. + ok(c.id != null, "connection has had an id assigned"); + }); + + test(renderMode + ': _jsPlumb.connect (Endpoint connectorTooltip parameter)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1, {connectorTooltip:"FOO"}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + equals(c.connector.canvas.getAttribute("title"), "FOO", "connector canvas has label attribute set"); + }); + + test(renderMode + ': _jsPlumb.connect (tooltip parameter)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1, {}); + var e2 = _jsPlumb.addEndpoint(d2, {}); + var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2, tooltip:"FOO"}); + equals(c.connector.canvas.getAttribute("title"), "FOO", "connector canvas has label attribute set"); + }); + + test(renderMode + ': _jsPlumb.connect (between two Endpoints, and dont supply any parameters to the Endpoints.)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1, _jsPlumb); + assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1, _jsPlumb); // no new endpoint should have been added + assertEndpointCount("d2", 1, _jsPlumb); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. + }); + + test(renderMode + " : _jsPlumb.connect, passing 'anchors' array" ,function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, anchors:["LeftMiddle", "RightMiddle"]}); + + equals(c.endpoints[0].anchor.x, 0, "source anchor is at x=0"); + equals(c.endpoints[0].anchor.y, 0.5, "source anchor is at y=0.5"); + equals(c.endpoints[1].anchor.x, 1, "target anchor is at x=1"); + equals(c.endpoints[1].anchor.y, 0.5, "target anchor is at y=0.5"); + }); + + test(renderMode + ': _jsPlumb.connect (by endpoint)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + }); + + test(renderMode + ': _jsPlumb.connect (cost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17, cost:567}); + assertContextSize(3); + equals(c.getCost(), 567, "connection cost is 567"); + }); + + test(renderMode + ': _jsPlumb.connect (default cost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), undefined, "default connection cost is 1"); + }); + + test(renderMode + ': _jsPlumb.connect (set cost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), undefined, "default connection cost is 1"); + c.setCost(8989); + equals(c.getCost(), 8989, "connection cost is 8989"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (connectionCost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionCost:567}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), 567, "connection cost is 567"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (change connectionCost)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionCost:567, maxConnections:-1}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1}); + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.getCost(), 567, "connection cost is 567"); + e16.setConnectionCost(23); + var c2 = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(c2.getCost(), 23, "connection cost is 23 after change on endpoint"); + }); + + test(renderMode + ': _jsPlumb.connect (default bidirectional)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true}), + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.isBidirectional(), true, "default connection is bidirectional"); + }); + + test(renderMode + ': _jsPlumb.connect (bidirectional false)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true}), + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17, bidirectional:false}); + assertContextSize(3); + equals(c.isBidirectional(), false, "connection is not bidirectional"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (connectionsBidirectional)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionsBidirectional:true, maxConnections:-1}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1}); + var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.isBidirectional(), true, "connection is bidrectional"); + }); + + test(renderMode + ': _jsPlumb.connect two endpoints (change connectionsBidirectional)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), + e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionsBidirectional:true, maxConnections:-1}), + e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1}); + c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); + equals(c.isBidirectional(), true, "connection is bidirectional"); + e16.setConnectionsBidirectional(false); + var c2 = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(c2.isBidirectional(), false, "connection is not bidirectional"); + }); + + test(renderMode + ": _jsPlumb.connect (two Endpoints - that have been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = _jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1, uuid:srcEndpointUuid}); + var e2 = _jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1, uuid:dstEndpointUuid}); + _jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ] }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertContextSize(3); + }); + + test(renderMode + ": _jsPlumb.connect (two Endpoints - that have not been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ], source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + var e1 = _jsPlumb.getEndpoint(srcEndpointUuid); + ok(e1 != null, "endpoint with src uuid added"); + ok(e1.canvas != null); + var e2 = _jsPlumb.getEndpoint(dstEndpointUuid); + ok(e2 != null, "endpoint with target uuid added"); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.connect (two Endpoints - that have been already added - by endpoint reference)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = _jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1}); + var e2 = _jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1}); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.connect (two elements)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + _jsPlumb.connect({ source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, straight)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, bezier, no params)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Bezier" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 150, "Bezier connector chose 150 curviness"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, bezier, curviness as int)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", { curviness:200 }] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Canvas Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 200, "Bezier connector chose 200 curviness"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector test, bezier, curviness as named option)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", {curviness:300}] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Bezier, "Bezier connector chosen for connection"); + equals(conn.connector.majorAnchor, 300, "Bezier connector chose 300 curviness"); + }); + + test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Canvas Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as strings)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:["LeftMiddle", "RightMiddle"] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.5, conn.endpoints[0].anchor.y, "source anchor y"); + equals(1, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.5, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + }); + + + test(renderMode + ": _jsPlumb.connect (two argument method in which some data is reused across connections)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"), d19 = _addDiv("d19"); + var sharedData = { connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] }; + var conn = _jsPlumb.connect({ source:d16, target:d17}, sharedData); + var conn2 = _jsPlumb.connect({ source:d18, target:d19}, sharedData); + assertContextSize(6); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 2, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(conn2.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(0.3, conn.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn.endpoints[1].anchor.y, "target anchor y"); + equals(0.3, conn2.endpoints[0].anchor.x, "source anchor x"); + equals(0.3, conn2.endpoints[0].anchor.y, "source anchor y"); + equals(0.7, conn2.endpoints[1].anchor.x, "target anchor x"); + equals(0.7, conn2.endpoints[1].anchor.y, "target anchor y"); + }); + + test(renderMode + ": _jsPlumb.connect (Connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoint test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoints test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Dot, "Dot endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Blank Endpoint specified via 'endpoint' param)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Blank" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Blank Endpoint specified via 'endpoints' param)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Blank", "Blank" ] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Dot, "Dot endpoint chosen for connection target"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, connector test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {}); + var e17 = _jsPlumb.addEndpoint(d17, {}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {}); + var e17 = _jsPlumb.addEndpoint(d17, {}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, anchors as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = "TopCenter", a17 = "BottomCenter"; + var e16 = _jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, 0.5, "endpoint 16 is at top center");equals(e16.anchor.y, 0, "endpoint 16 is at top center"); + equals(e17.anchor.x, 0.5, "endpoint 17 is at bottom center");equals(e17.anchor.y, 1, "endpoint 17 is at bottom center"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = [0,0.5,0,-1], a17 = [1,0.0,-1,-1]; + var e16 = _jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, a16[0]);equals(e16.anchor.y, a16[1]); + equals(e17.anchor.x, a17[0]);equals(e17.anchor.y, a17[1]); + equals(e16.anchor.getOrientation()[0], a16[2]); equals(e16.anchor.getOrientation()[1], a16[3]); + equals(e17.anchor.getOrientation()[0], a17[2]); equals(e17.anchor.getOrientation()[1], a17[3]); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchor')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchor:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchor:["TopCenter", "BottomCenter"]}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchors')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = _jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = _jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors[renderMode].Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, default endpoint, supplied dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var anchors = [ [0.25, 0, 0, -1], [1, 0.25, 1, 0], [0.75, 1, 0, 1], [0, 0.75, -1, 0] ]; + _jsPlumb.connect({source:d1, target:d2, dynamicAnchors:anchors}); // auto connect with default endpoint and provided anchors + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.detach({source:d1, target:d2}); + // this changed in 1.3.5, because auto generated endpoints are now removed by detach. so i added the test below this one + // to check that the deleteEndpointsOnDetach flag is honoured. + assertEndpointCount("d1", 0, _jsPlumb);assertEndpointCount("d2", 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, default endpoint, supplied dynamic anchors, delete on detach false)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var anchors = [ [0.25, 0, 0, -1], [1, 0.25, 1, 0], [0.75, 1, 0, 1], [0, 0.75, -1, 0] ]; + _jsPlumb.connect({ + source:d1, + target:d2, + dynamicAnchors:anchors, + deleteEndpointsOnDetach:false + }); // auto connect with default endpoint and provided anchors + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.detach({source:d1, target:d2}); + // this changed in 1.3.5, because auto generated endpoints are now removed by detach. so i added this test + // to check that the deleteEndpointsOnDetach flag is honoured. + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, supplied endpoint and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = _jsPlumb.addEndpoint(d1, endpoint); + var e2 = _jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(3); + _jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.connect (connect by element, supplied endpoints using 'source' and 'target' (this test is identical to the one above apart from the param names), and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = _jsPlumb.addEndpoint(d1, endpoint); + var e2 = _jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + _jsPlumb.connect({source:e1, target:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(3); + _jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + " detachable parameter defaults to true on _jsPlumb.connect", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), true, "connections detachable by default"); + }); + + test(renderMode + " detachable parameter set to false on _jsPlumb.connect", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, detachable:false}); + equals(c.isDetachable(), false, "connection detachable"); + }); + + test(renderMode + " setDetachable on initially detachable connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), true, "connection initially detachable"); + c.setDetachable(false); + equals(c.isDetachable(), false, "connection not detachable"); + }); + + test(renderMode + " setDetachable on initially not detachable connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, detachable:false }); + equals(c.isDetachable(), false, "connection not initially detachable"); + c.setDetachable(true); + equals(c.isDetachable(), true, "connection now detachable"); + }); + + test(renderMode + " _jsPlumb.Defaults.ConnectionsDetachable", function() { + _jsPlumb.Defaults.ConnectionsDetachable = false; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.isDetachable(), false, "connections not detachable by default (overrode the defaults)"); + _jsPlumb.Defaults.ConnectionsDetachable = true; + }); + + + test(renderMode + ": _jsPlumb.connect (testing for connection event callback)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var connectCallback = null, detachCallback = null; + _jsPlumb.bind("jsPlumbConnection", function(params) { + connectCallback = $.extend({}, params); + }); + _jsPlumb.bind("jsPlumbConnectionDetached", function(params) { + detachCallback = $.extend({}, params); + }); + _jsPlumb.connect({source:d1, target:d2}); // auto connect with default endpoint and anchor set + ok(connectCallback != null, "connect callback was made"); + assertContextSize(3); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb); + _jsPlumb.detach({source:d1, target:d2}); + assertContextSize(2); + ok(detachCallback != null, "detach callback was made"); + }); + + test(renderMode + ": _jsPlumb.connect (setting cssClass on Connector)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2,cssClass:"CSS"}); + var has = function(clazz) { + var cn = c.connector.canvas.className, + cns = cn.constructor == String ? cn : cn.baseVal; + + return cns.indexOf(clazz) != -1; + }; + ok(has("CSS"), "custom cssClass set correctly"); + ok(has(_jsPlumb.connectorClass), "basic connector class set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (overlays, long-hand version)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = {width:40,length:40,location:0.7, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"}}; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + }); + + test(renderMode + ": _jsPlumb.connect (overlays, long-hand version, IDs specified)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + equals("aLabel", connection1.overlays[0].id); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + equals("anArrow", connection1.overlays[1].id); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays)", function() { + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays + overlays specified in connect call)", function() { + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, overlays:[ + ["Label", {id:"label"}] + ]}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("label") != null, "Label overlay created from connect call"); + }); + + test(renderMode + ": _jsPlumb.connect (default connection overlays)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + }); + + test(renderMode + ": _jsPlumb.connect (default connection overlays + overlays specified in connect call)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, overlays:[ + ["Label", {id:"label"}] + ]}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("label") != null, "Label overlay created from connect call"); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays + default connection overlays)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow2" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("arrow2") != null, "Arrow overlay created from connection defaults"); + }); + + test(renderMode + ": _jsPlumb.connect (default overlays + default connection overlays)", function() { + _jsPlumb.Defaults.ConnectionOverlays = [ + ["Arrow",{ location:0.1, id:"arrow" }] + ]; + _jsPlumb.Defaults.Overlays = [ + ["Arrow",{ location:0.1, id:"arrow2" }] + ]; + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({source:d1, target:d2, overlays:[ + ["Label", {id:"label"}] + ]}); + + ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults"); + ok(c.getOverlay("arrow2") != null, "Arrow overlay created from connection defaults"); + ok(c.getOverlay("label") != null, "Label overlay created from connect call"); + }); + + test(renderMode + ": _jsPlumb.connect (label overlay set using 'label')", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO" + }); + + equals(c.getLabel(), "FOO", "label is set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction, with string)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2}); + + c.setLabel("FOO"); + equals(c.getLabel(), "FOO", "label is set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction, with function)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2}); + + c.setLabel(function() { return "BAR"; }); + equals(c.getLabel()(), "BAR", "label is set correctly"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction, with params object)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2}); + + c.setLabel({ + label:"BAZ", + cssClass:"CLASSY", + location:0.9 + }); + var lo = c.getLabelOverlay(); + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "BAZ", "label overlay has correct value"); + equals(lo.getLocation(), 0.9, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (set label after construction with existing label set, with params object)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO", + labelLocation:0.2 + }); + + var lo = c.getLabelOverlay(); + equals(lo.getLocation(), 0.2, "label overlay has correct location"); + + c.setLabel({ + label:"BAZ", + cssClass:"CLASSY", + location:0.9 + }); + + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "BAZ", "label overlay has correct value"); + equals(lo.getLocation(), 0.9, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (getLabelOverlay, label on connect call)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO" + }), + lo = c.getLabelOverlay(); + + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "FOO", "label overlay has correct value"); + equals(lo.getLocation(), 0.5, "label overlay has correct location"); + }); + + test(renderMode + ": _jsPlumb.connect (getLabelOverlay, label on connect call, location set)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + c = _jsPlumb.connect({ + source:d1, + target:d2, + label:"FOO", + labelLocation:0.2 + }), + lo = c.getLabelOverlay(); + + ok(lo != null, "label overlay exists"); + equals(lo.getLabel(), "FOO", "label overlay has correct value"); + equals(lo.getLocation(), 0.2, "label overlay has correct location"); + }); + + + test(renderMode + ": _jsPlumb.connect (remove single overlay by id)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + connection1.removeOverlay("aLabel"); + equals(1, connection1.overlays.length); + equals("anArrow", connection1.overlays[0].id); + }); + + test(renderMode + ": _jsPlumb.connect (remove multiple overlays by id)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var arrowSpec = { + width:40, + length:40, + location:0.7, + foldback:0, + paintStyle:{lineWidth:1, strokeStyle:"#000000"}, + id:"anArrow" + }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}], + ["Arrow",arrowSpec ] ] + }); + equals(2, connection1.overlays.length); + connection1.removeOverlays("aLabel", "anArrow"); + equals(0, connection1.overlays.length); + }); + + test(renderMode + ": _jsPlumb.connect (overlays, short-hand version)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var loc = { location:0.7 }; + var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label", {label:"CONNECTION 1", location:0.3}], + ["Arrow", arrowSpec, loc] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + }); + + test(renderMode + ": _jsPlumb.connect (removeAllOverlays)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var imageEventListener = function() { }; + var loc = { location:0.7 }; + var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} }; + var connection1 = _jsPlumb.connect({ + source:d1, + target:d2, + anchors:["BottomCenter", [ 0.75,0,0,-1 ]], + overlays : [ ["Label", {label:"CONNECTION 1", location:0.3, cssClass:"PPPP"}], + ["Arrow", arrowSpec, loc] ] + }); + equals(2, connection1.overlays.length); + equals(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor); + + equals(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor); + equals(0.7, connection1.overlays[1].loc); + equals(40, connection1.overlays[1].width); + equals(40, connection1.overlays[1].length); + + connection1.removeAllOverlays(); + equals(0, connection1.overlays.length); + equals(0, $(".PPPP").length); + }); + + test(renderMode + ": _jsPlumb.connect, specify arrow overlay using string identifier only", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:["Arrow"]}); + equals(jsPlumb.Overlays[renderMode].Arrow, conn.overlays[0].constructor); + }); + + test(renderMode + ": Connection.getOverlay method, existing overlay", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("arrowOverlay"); + ok(overlay != null); + }); + + test(renderMode + ": Connection.getOverlay method, non-existent overlay", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("IDONTEXIST"); + ok(overlay == null); + }); + + test(renderMode + ": Overlay.setVisible method", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] }); + var overlay = conn.getOverlay("arrowOverlay"); + ok(overlay.isVisible()); + overlay.setVisible(false); + ok(!overlay.isVisible()); + overlay.setVisible(true); + ok(overlay.isVisible()); + }); + + // this test is for the original detach function; it should stay working after i mess with it + // a little. + test(renderMode + ": _jsPlumb.detach (by element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + var e3 = _jsPlumb.addEndpoint(d1); + var e4 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + _jsPlumb.connect({ sourceEndpoint:e3, targetEndpoint:e4 }); // make two connections to be sure this works ;) + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionCount(e3, 1); + assertConnectionCount(e4, 1); + + _jsPlumb.detach({source:"d1", target:"d2"}); + + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionCount(e3, 0); + assertConnectionCount(e4, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); + + // detach is being made to operate more like connect - by taking one argument with a whole + // bunch of possible params in it. if two args are passed in it will continue working + // in the old way. + test(renderMode + ": _jsPlumb.detach (params object, using element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({source:"d1", target:"d2"}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + }); +/* + test(renderMode + ": _jsPlumb.detach (params object, using target only)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e1 = _jsPlumb.addEndpoint(d1, {maxConnections:2}), + e2 = _jsPlumb.addEndpoint(d2), + e3 = _jsPlumb.addEndpoint(d3); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e3 }); + assertConnectionCount(e1, 2); + assertConnectionCount(e2, 1); + assertConnectionCount(e3, 1); + _jsPlumb.detach({target:"d2"}); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 0); + assertConnectionCount(e3, 1); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1); + }); +*/ + test(renderMode + ": _jsPlumb.detach (params object, using element objects)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({source:d1, target:d2}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.detach (source and target as endpoint UUIDs)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1, {uuid:"abcdefg"}); + ok(_jsPlumb.getEndpoint("abcdefg") != null, "e1 exists"); + var e2 = _jsPlumb.addEndpoint(d2, {uuid:"hijklmn"}); + ok(_jsPlumb.getEndpoint("hijklmn") != null, "e2 exists"); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({uuids:["abcdefg", "hijklmn"]}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.detach (sourceEndpoint and targetEndpoint supplied)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + var e2 = _jsPlumb.addEndpoint(d2); + _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + _jsPlumb.detach({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb); + assertContextSize(2); + }); + + test(renderMode + ": _jsPlumb.makeDynamicAnchors (longhand)", function() { + var anchors = [_jsPlumb.makeAnchor([0.2, 0, 0, -1], null, _jsPlumb), _jsPlumb.makeAnchor([1, 0.2, 1, 0], null, _jsPlumb), + _jsPlumb.makeAnchor([0.8, 1, 0, 1], null, _jsPlumb), _jsPlumb.makeAnchor([0, 0.8, -1, 0], null, _jsPlumb) ]; + var dynamicAnchor = _jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); + }); + + test(renderMode + ": _jsPlumb.makeDynamicAnchors (shorthand)", function() { + var anchors = [[0.2, 0, 0, -1], [1, 0.2, 1, 0], + [0.8, 1, 0, 1], [0, 0.8, -1, 0] ]; + var dynamicAnchor = _jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); + }); + + test(renderMode + ": Connection.isVisible/setVisible", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c1 = _jsPlumb.connect({source:d1,target:d2}); + equals(true, c1.isVisible(), "Connection is visible after creation."); + c1.setVisible(false); + equals(false, c1.isVisible(), "Connection is not visible after calling setVisible(false)."); + equals($(c1.connector.canvas).css("display"), "none"); + c1.setVisible(true); + equals(true, c1.isVisible(), "Connection is visible after calling setVisible(true)."); + equals($(c1.connector.canvas).css("display"), "block"); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible basic test (no connections)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals($(e1.canvas).css("display"), "none"); + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals($(e1.canvas).css("display"), "block"); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should track changes in the source, because it has only this connection.)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + var c1 = _jsPlumb.connect({source:e1, target:e2}); + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals(false, e2.isVisible(), "other Endpoint is not visible either."); + equals(false, c1.isVisible(), "connection between the two is not visible either."); + + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals(true, e2.isVisible(), "other Endpoint is visible too"); + equals(true, c1.isVisible(), "connection between the two is visible too."); + }); + + test(renderMode + ": Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should not track changes in the source, because it has another connection.)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2, { maxConnections:2 }), e3 = _jsPlumb.addEndpoint(d3); + equals(true, e1.isVisible(), "Endpoint is visible after creation."); + var c1 = _jsPlumb.connect({source:e1, target:e2}); + var c2 = _jsPlumb.connect({source:e2, target:e3}); + + e1.setVisible(false); + equals(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false)."); + equals(true, e2.isVisible(), "other Endpoint should still be visible."); + equals(true, e3.isVisible(), "third Endpoint should still be visible."); + equals(false, c1.isVisible(), "connection between the two is not visible either."); + equals(true, c2.isVisible(), "other connection is visible."); + + e1.setVisible(true); + equals(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true)."); + equals(true, e2.isVisible(), "other Endpoint is visible too"); + equals(true, c1.isVisible(), "connection between the two is visible too."); + equals(true, c2.isVisible(), "other connection is visible."); + }); + + // tests of the functionality that allows a user to specify that they want elements appended to the document body + test(renderMode + " _jsPlumb.Defaults.Container, specified with a selector", function() { + _jsPlumb.Defaults.Container = $("body"); + equals($("#container")[0].childNodes.length, 0, "container has no nodes"); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals($("#container")[0].childNodes.length, 2, "container has two div elements"); // the divs we added have been added to the 'container' div. + // but we have told _jsPlumb to add its canvas to the body, so this connect call should not add another few elements to the container: + _jsPlumb.connect({source:d1, target:d2}); + equals($("#container")[0].childNodes.length, 2, "container still has two div elements"); + }); + + // tests of the functionality that allows a user to specify that they want elements appended to some specific container. + test(renderMode + " _jsPlumb.Defaults.Container, specified with DOM element", function() { + _jsPlumb.Defaults.Container = document.getElementsByTagName("body")[0]; + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length, "two divs added to the container"); // the divs we added have been added to the 'container' div. + // but we have told _jsPlumb to add its canvas to the body, so this connect call should not add another few elements to the container: + var bodyElementCount = $("body")[0].childNodes.length; + _jsPlumb.connect({source:d1, target:d2}); + equals(2, $("#container")[0].childNodes.length, "still only two children in container; elements were added to the body by _jsPlumb"); + // test to see if 3 elements have been added + equals(bodyElementCount + 3, $("body")[0].childNodes.length, "3 new elements added to the document body"); + }); + + test(renderMode + " container specified to connect call, with a selector", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length); // the divs we added have been added to the 'container' div. + var bodyElementCount = $("body")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to the body, so this connect call should not add another few elements to the container: + _jsPlumb.connect({source:d1, target:d2, container:$("body")}); + equals(2, $("#container")[0].childNodes.length); + equals(bodyElementCount + 3, $("body")[0].childNodes.length, "3 new elements added to the document body"); + }); + + test(renderMode + " container specified to connect call, with a string ID", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + equals(3, $("#container")[0].childNodes.length, "container has divs we added"); // the divs we added have been added to the 'container' div. + var d3ElementCount = $("#d3")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to "d3", so this connect call should not add another few elements to the container: + _jsPlumb.connect({source:d1, target:d2, container:"d3"}); + equals(3, $("#container")[0].childNodes.length, "container still has only the divs we added"); + equals(d3ElementCount + 3, $("#d3")[0].childNodes.length, "3 new elements added to div d3"); + }); + + test(renderMode + " container specified to addEndpoint call, with a selector", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + equals(2, $("#container")[0].childNodes.length); // the divs we added have been added to the 'container' div. + var bodyElementCount = $("body")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to the body, so this connect call should not add another few elements to the container: + _jsPlumb.addEndpoint(d1, {container:$("body")}); + equals(2, $("#container")[0].childNodes.length); + equals(bodyElementCount + 1, $("body")[0].childNodes.length, "1 new element added to the document body"); + }); + + test(renderMode + " container specified to addEndpoint call, with a string ID", function() { + equals(0, $("#container")[0].childNodes.length); + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + equals(3, $("#container")[0].childNodes.length, "container has divs we added"); // the divs we added have been added to the 'container' div. + var d3ElementCount = $("#d3")[0].childNodes.length; + // but here we tell _jsPlumb to add its elements to "d3", so this connect call should not add another few elements to the container: + _jsPlumb.addEndpoint(d1, { container:"d3" }); + equals(3, $("#container")[0].childNodes.length, "container still has only the divs we added"); + equals(d3ElementCount + 1, $("#d3")[0].childNodes.length, "1 new element added to div d3"); + }); + + test(renderMode + " detachable defaults to true when connection made between two endpoints", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2), + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection not detachable"); + }); + + test(renderMode + " connection detachable when target endpoint has connectionsDetachable set to true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2, {connectionsDetachable:true}), + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection detachable because connectionsDetachable was set on target endpoint"); + }); + + test(renderMode + " connection detachable when source endpoint has connectionsDetachable set to true", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e1 = _jsPlumb.addEndpoint(d1, {connectionsDetachable:true}), e2 = _jsPlumb.addEndpoint(d2), + c = _jsPlumb.connect({source:e1, target:e2}); + equals(c.isDetachable(), true, "connection detachable because connectionsDetachable was set on source endpoint"); + }); + + test(renderMode + " Connector has 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.connector.type, "Bezier", "Bezier connector has type set"); + + var c2 = _jsPlumb.connect({source:d1, target:d2, connector:"Straight"}); + equals(c2.connector.type, "Straight", "Straight connector has type set"); + + var c3 = _jsPlumb.connect({source:d1, target:d2, connector:"Flowchart"}); + equals(c3.connector.type, "Flowchart", "Flowchart connector has type set"); + }); + + test(renderMode + " Endpoints have 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = _jsPlumb.connect({source:d1, target:d2}); + equals(c.endpoints[0].type, "Dot", "Dot endpoint has type set"); + + var c2 = _jsPlumb.connect({source:d1, target:d2, endpoints:["Rectangle", "Blank"]}); + equals(c2.endpoints[1].type, "Blank", "Blank endpoint has type set"); + equals(c2.endpoints[0].type, "Rectangle", "Rectangle endpoint has type set"); + }); + + test(renderMode + " Overlays have 'type' member set", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + + var c = _jsPlumb.connect({ + source:d1, + target:d2, + overlays:[ "Arrow", "Label", "PlainArrow", "Diamond" ] + }); + equals(c.overlays[0].type, "Arrow", "Arrow overlay has type set"); + equals(c.overlays[1].type, "Label", "Label overlay has type set"); + equals(c.overlays[2].type, "PlainArrow", "PlainArrow overlay has type set"); + equals(c.overlays[3].type, "Diamond", "Diamond overlay has type set"); + }); + + test(renderMode + " _jsPlumb.hide, original one-arg version", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + _jsPlumb.hide(d1); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(true, e1.isVisible(), "endpoint 1 is still visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + _jsPlumb.show(d1); + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + }); + + test(renderMode + " _jsPlumb.hide, two-arg version, endpoints should also be hidden", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + _jsPlumb.show(d1); // now show d1, but do not alter the endpoints. e1 should still be hidden + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + test(renderMode + " _jsPlumb.show, two-arg version, endpoints should become visible", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + + _jsPlumb.show(d1, true); // now show d1, and alter the endpoints. e1 should be visible. + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(true, e1.isVisible(), "endpoint 1 is visible again."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + test(renderMode + " _jsPlumb.show, two-arg version, endpoints should become visible, but not all connections, because some other endpoints are not visible.", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e11 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + e3 = _jsPlumb.addEndpoint(d3, e), + c1 = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e11, target:e3}); + + // we now have d1 connected to both d3 and d2. we'll hide d1, and everything on d1 should be hidden. + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "connection 1 is no longer visible."); + equals(false, c2.isVisible(), "connection 2 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(false, e11.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + equals(true, e3.isVisible(), "endpoint 3 is still visible."); + + // now, we will also hide d3. making d1 visible again should NOT result in c2 becoming visible, because the other endpoint + // for c2 is e3, which is not visible. + _jsPlumb.hide(d3, true); + equals(false, e3.isVisible(), "endpoint 3 is no longer visible."); + + _jsPlumb.show(d1, true); // now show d1, and alter the endpoints. e1 should be visible, c1 should be visible, but c2 should not. + + equals(true, c1.isVisible(), "Connection 1 is visible again."); + equals(false, c2.isVisible(), "Connection 2 is not visible."); + equals(true, e1.isVisible(), "endpoint 1 is visible again."); + equals(true, e11.isVisible(), "endpoint 11 is visible again."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + equals(false, e3.isVisible(), "endpoint 3 is still not visible."); + }); + + /* + test(renderMode + " _jsPlumb.hide, two-arg version, endpoints should also be hidden", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { isSource:true, isTarget:true, maxConnections:-1 }, + e1 = _jsPlumb.addEndpoint(d1, e), + e2 = _jsPlumb.addEndpoint(d2, e), + c1 = _jsPlumb.connect({source:e1, target:e2}); + + equals(true, c1.isVisible(), "Connection 1 is visible after creation."); + equals(true, e1.isVisible(), "endpoint 1 is visible after creation."); + equals(true, e2.isVisible(), "endpoint 2 is visible after creation."); + + _jsPlumb.hide("d1", true); + + equals(false, c1.isVisible(), "Connection 1 is no longer visible."); + equals(false, e1.isVisible(), "endpoint 1 is no longer visible."); + equals(true, e2.isVisible(), "endpoint 2 is still visible."); + }); + + /** + * test for issue 132: label leaves its element in the DOM after it has been + * removed from a connection. + */ + test(renderMode + " label cleans itself up properly", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label", cssClass:"foo"}] + ]}); + ok($(".foo").length == 1, "label element exists in DOM"); + c.removeOverlay("label"); + ok($(".foo").length == 0, "label element does not exist in DOM"); + }); + + test(renderMode + " arrow cleans itself up properly", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Arrow", {id:"arrow"}] + ]}); + ok(c.getOverlay("arrow") != null, "arrow overlay exists"); + c.removeOverlay("arrow"); + ok(c.getOverlay("arrow") == null, "arrow overlay has been removed"); + }); + + test(renderMode + " label overlay getElement function", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label"}] + ]}); + ok(c.getOverlay("label").getElement() != null, "label overlay exposes element via getElement method"); + }); + + test(renderMode + " label overlay provides getLabel and setLabel methods", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1,target:d2, overlays:[ + [ "Label", {id:"label", label:"foo"}] + ]}); + var o = c.getOverlay("label"), e = o.getElement(); + ok(e.innerHTML == "foo", "label text is set to original value"); + o.setLabel("baz"); + ok(e.innerHTML == "baz", "label text is set to new value 'baz'"); + ok(o.getLabel() === "baz", "getLabel function works correctly with String"); + // now try functions + var aFunction = function() { return "aFunction"; }; + o.setLabel(aFunction); + ok(e.innerHTML == "aFunction", "label text is set to new value from Function"); + ok(o.getLabel() === aFunction, "getLabel function works correctly with Function"); + }); + + test(renderMode + " parameters object works for Endpoint", function() { + var d1 = _addDiv("d1"), + f = function() { alert("FOO!"); }, + e = _jsPlumb.addEndpoint(d1, { + isSource:true, + parameters:{ + "string":"param1", + "int":4, + "function":f + } + }); + ok(e.getParameter("string") === "param1", "getParameter(String) works correctly"); + ok(e.getParameter("int") === 4, "getParameter(int) works correctly"); + ok(e.getParameter("function") == f, "getParameter(Function) works correctly"); + }); + + test(renderMode + " parameters object works for Connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + f = function() { alert("FOO!"); }; + var c = _jsPlumb.connect({ + source:d1, + target:d2, + parameters:{ + "string":"param1", + "int":4, + "function":f + } + }); + ok(c.getParameter("string") === "param1", "getParameter(String) works correctly"); + ok(c.getParameter("int") === 4, "getParameter(int) works correctly"); + ok(c.getParameter("function") == f, "getParameter(Function) works correctly"); + }); + + test(renderMode + " parameters set on Endpoints and Connections are all merged, and merged correctly at that.", function() { + var d1 = _addDiv("d1"), + d2 = _addDiv("d2"), + e = _jsPlumb.addEndpoint(d1, { + isSource:true, + parameters:{ + "string":"sourceEndpoint", + "int":0, + "function":function() { return "sourceEndpoint"; } + } + }), + e2 = _jsPlumb.addEndpoint(d2, { + isTarget:true, + parameters:{ + "int":1, + "function":function() { return "targetEndpoint"; } + } + }), + c = _jsPlumb.connect({source:e, target:e2, parameters:{ + "function":function() { return "connection"; } + }}); + + ok(c.getParameter("string") === "sourceEndpoint", "getParameter(String) works correctly"); + ok(c.getParameter("int") === 1, "getParameter(int) works correctly"); + ok(c.getParameter("function")() == "connection", "getParameter(Function) works correctly"); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers standard connection", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var c = _jsPlumb.connect({source:d1, target:d2}); + equals(_jsPlumb.anchorManager.getConnectionsFor("d1").length, 1); + equals(_jsPlumb.anchorManager.getEndpointsFor("d1").length, 1); + equals(_jsPlumb.anchorManager.getConnectionsFor("d2").length, 1); + equals(_jsPlumb.anchorManager.getEndpointsFor("d2").length, 1); + var c2 = _jsPlumb.connect({source:d1, target:d2}); + equals(_jsPlumb.anchorManager.getConnectionsFor("d1").length, 2); + equals(_jsPlumb.anchorManager.getConnectionsFor("d2").length, 2); + equals(_jsPlumb.anchorManager.getEndpointsFor("d1").length, 2); + equals(_jsPlumb.anchorManager.getEndpointsFor("d2").length, 2); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers dynamic anchor connection, and removes it.", function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var c = _jsPlumb.connect({source:d3, target:d4, anchors:["AutoDefault", "AutoDefault"]}); + + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 1); + + var c2 = _jsPlumb.connect({source:d3, target:d4}); + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 2); + equals(_jsPlumb.anchorManager.getConnectionsFor("d4").length, 2); + + equals(_jsPlumb.anchorManager.getEndpointsFor("d3").length, 2); + _jsPlumb.detach(c); + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 1); + }); + + // anchor manager tests. a new and more comprehensive way of managing the paint, introduced in 1.3.5 + test(renderMode + " anchorManager registers continuous anchor connection, and removes it.", function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var c = _jsPlumb.connect({source:d3, target:d4, anchors:["Continuous", "Continuous"]}); + + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 1); + equals(_jsPlumb.anchorManager.getConnectionsFor("d4").length, 1); + + _jsPlumb.detach(c); + equals(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 0); + equals(_jsPlumb.anchorManager.getConnectionsFor("d4").length, 0); + + _jsPlumb.reset(); + equals(_jsPlumb.anchorManager.getEndpointsFor("d4").length, 0); + }); + +// ------------- utility functions - math stuff, mostly -------------------------- + + var tolerance = 0.00000005, withinTolerance = function(v1, v2, msg) { + if (Math.abs(v1 - v2) < tolerance) ok(true, msg + "; expected " + v1 + " and got it"); + else { + ok(false, msg + "; expected " + v1 + " got " + v2); + } + }; + test(renderMode + " jsPlumbUtil.gradient, segment 1", function() { + var p1 = [2,2], p2 = [3,1], m = jsPlumbUtil.gradient(p1,p2); + equals(m, -1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.normal, segment 1", function() { + var p1 = [2,2], p2 = [3,1], m = jsPlumbUtil.normal(p1,p2); + equals(m, 1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.gradient, segment 2", function() { + var p1 = [2,2], p2 = [3,3], m = jsPlumbUtil.gradient(p1,p2); + equals(m, 1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.normal, segment 2", function() { + var p1 = [2,2], p2 = [3,3], m = jsPlumbUtil.normal(p1,p2); + equals(m, -1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.gradient, segment 3", function() { + var p1 = [2,2], p2 = [1,3], m = jsPlumbUtil.gradient(p1,p2); + equals(m, -1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.normal, segment 3", function() { + var p1 = [2,2], p2 = [1,3], m = jsPlumbUtil.normal(p1,p2); + equals(m, 1, "normal calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.gradient, segment 4", function() { + var p1 = [2,2], p2 = [1,1], m = jsPlumbUtil.gradient(p1,p2); + equals(m, 1, "gradient calculated correctly for simple case"); + }); + test(renderMode + " jsPlumbUtil.normal, segment 4", function() { + var p1 = [2,2], p2 = [1,1], m = jsPlumbUtil.normal(p1,p2); + equals(m, -1, "normal calculated correctly for simple case"); + }); + test(renderMode + "jsPlumbUtil.pointOnLine, segment 1", function() { + var p1 = {x:2,y:2}, p2={x:3, y:1}, + target = jsPlumbUtil.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumbUtil.pointOnLine, segment 2", function() { + var p1 = {x:2,y:2}, p2={x:3, y:3}, + target = jsPlumbUtil.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumbUtil.pointOnLine, segment 3", function() { + var p1 = {x:2,y:2}, p2={x:1, y:3}, + target = jsPlumbUtil.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumbUtil.pointOnLine, segment 4", function() { + var p1 = {x:2,y:2}, p2={x:1, y:1}, + target = jsPlumbUtil.pointOnLine(p1, p2, Math.sqrt(2)); + withinTolerance(p2.x, target.x, "x is calculated correctly"); + withinTolerance(p2.y, target.y, "y is calculated correctly"); + }); + test(renderMode + "jsPlumbUtil.perpendicularLineTo, segment 1", function() { + var p1 = {x:2, y:2}, p2={x:3, y:1}, m = jsPlumbUtil.gradient(p1, p2), + l = jsPlumbUtil.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(4, l[0].x, "point 1 x is correct"); + withinTolerance(2, l[0].y, "point 1 y is correct"); + + withinTolerance(2, l[1].x, "point 2 x is correct"); + withinTolerance(0, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumbUtil.perpendicularLineTo, segment 2", function() { + var p1 = {x:2, y:2}, p2={x:3, y:3}, m = jsPlumbUtil.gradient(p1, p2), + l = jsPlumbUtil.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(4, l[0].x, "point 1 x is correct"); + withinTolerance(2, l[0].y, "point 1 y is correct"); + + withinTolerance(2, l[1].x, "point 2 x is correct"); + withinTolerance(4, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumbUtil.perpendicularLineTo, segment 3", function() { + var p1 = {x:2, y:2}, p2={x:1, y:3}, m = jsPlumbUtil.gradient(p1, p2), + l = jsPlumbUtil.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(2, l[0].x, "point 1 x is correct"); + withinTolerance(4, l[0].y, "point 1 y is correct"); + + withinTolerance(0, l[1].x, "point 2 x is correct"); + withinTolerance(2, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumbUtil.perpendicularLineTo, segment 4", function() { + var p1 = {x:2, y:2}, p2={x:1, y:1}, m = jsPlumbUtil.gradient(p1, p2), + l = jsPlumbUtil.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2)); + + withinTolerance(2, l[0].x, "point 1 x is correct"); + withinTolerance(0, l[0].y, "point 1 y is correct"); + + withinTolerance(0, l[1].x, "point 2 x is correct"); + withinTolerance(2, l[1].y, "point 2 y is correct"); + }); + test(renderMode + "jsPlumbUtil.intersects, with intersection", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 3, y:4, w:3, h:3}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, with no intersection", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 13, y:4, w:3, h:3}; + + ok(!jsPlumbUtil.intersects(r1, r2), "r1 and r2 do not intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, with intersection, equal Y", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 1, y:2, w:3, h:6}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, with intersection, equal X", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 2, y:1, w:4, h:6}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, identical rectangles", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 2, y:2, w:4, h:6}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, corners touch (intersection)", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 6, y:8, w:3, h:3}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "jsPlumbUtil.intersects, one rectangle contained within the other", function() { + var r1 = { x: 2, y:2, w:4, h:6}, + r2 = { x: 0, y:0, w:23, h:23}; + + ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect"); + }); + test(renderMode + "setImage on Endpoint", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + originalUrl = "../../img/endpointTest1.png", + e = { + endpoint:[ "Image", { src:originalUrl } ] + }, + ep = _jsPlumb.addEndpoint(d1, e); + }); + test(renderMode + "setImage on Endpoint, with supplied onload", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), + e = { + endpoint:[ "Image", { + src:"../../img/endpointTest1.png", + onload:function(imgEp) { + equals("../../img/endpointTest1.png", imgEp.img.src); + equals(ep.endpoint.canvas.getAttribute("src"), imgEp.img.src); + } + } ] + }, + ep = _jsPlumb.addEndpoint(d1, e); + ep.setImage("../../img/littledot.png", function(imgEp) { + equals("../../img/littledot.png", imgEp.img.src); + equals(ep.endpoint.canvas.getAttribute("src"), imgEp.img.src); + }); + }); + + test(renderMode + "attach endpoint to table when desired element was td", function() { + var table = document.createElement("table"), + tr = document.createElement("tr"), + td = document.createElement("td"); + table.appendChild(tr); tr.appendChild(td); + document.body.appendChild(table); + var ep = _jsPlumb.addEndpoint(td); + equals(ep.canvas.parentNode.tagName.toLowerCase(), "table"); + }); + +// issue 190 - regressions with getInstance. these tests ensure that generated ids are unique across +// instances. + + test(renderMode + " id clashes between instances", function() { + var d1 = document.createElement("div"), + d2 = document.createElement("div"), + _jsp2 = jsPlumb.getInstance(); + + document.body.appendChild(d1); + document.body.appendChild(d2); + + _jsPlumb.addEndpoint(d1); + _jsp2.addEndpoint(d2); + + var id1 = d1.getAttribute("id"), + id2 = d2.getAttribute("id"); + + var idx = id1.indexOf("_"), idx2 = id1.lastIndexOf("_"), v1 = id1.substring(idx, idx2); + var idx3 = id2.indexOf("_"), idx4 = id2.lastIndexOf("_"), v2 = id2.substring(idx3, idx4); + + ok (v1 != v2, "instance versions are different : " + v1 + " : " + v2); + }); + + test(renderMode + " id clashes between instances", function() { + var d1 = document.createElement("div"), + d2 = document.createElement("div"), + _jsp2 = jsPlumb.getInstance(); + + document.body.appendChild(d1); + document.body.appendChild(d2); + + _jsPlumb.addEndpoint(d1); + _jsPlumb.addEndpoint(d2); + + var id1 = d1.getAttribute("id"), + id2 = d2.getAttribute("id"); + + var idx = id1.indexOf("_"), idx2 = id1.lastIndexOf("_"), v1 = id1.substring(idx, idx2); + var idx3 = id2.indexOf("_"), idx4 = id2.lastIndexOf("_"), v2 = id2.substring(idx3, idx4); + + ok (v1 == v2, "instance versions are the same : " + v1 + " : " + v2); + }); + + test(renderMode + " importDefaults", function() { + _jsPlumb.Defaults.Anchors = ["LeftMiddle", "RightMiddle"]; + var d1 = _addDiv("d1"), + d2 = _addDiv(d2), + c = _jsPlumb.connect({source:d1, target:d2}), + e = c.endpoints[0]; + + equals(e.anchor.x, 0, "left middle anchor"); + equals(e.anchor.y, 0.5, "left middle anchor"); + + _jsPlumb.importDefaults({ + Anchors:["TopLeft", "TopRight"] + }); + + var conn = _jsPlumb.connect({source:d1, target:d2}), + e1 = conn.endpoints[0], e2 = conn.endpoints[1]; + + equals(e1.anchor.x, 0, "top leftanchor"); + equals(e2.anchor.y, 0, "top left anchor"); + equals(e2.anchor.x, 1, "top right anchor"); + equals(e2.anchor.y, 0, "top right anchor"); + + }); + + test(renderMode + " restoreDefaults", function() { + _jsPlumb.importDefaults({ + Anchors:["TopLeft", "TopRight"] + }); + + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), conn = _jsPlumb.connect({source:d1, target:d2}), + e1 = conn.endpoints[0], e2 = conn.endpoints[1]; + + equals(e1.anchor.x, 0, "top leftanchor"); + equals(e2.anchor.y, 0, "top left anchor"); + equals(e2.anchor.x, 1, "top right anchor"); + equals(e2.anchor.y, 0, "top right anchor"); + + _jsPlumb.restoreDefaults(); + + var conn2 = _jsPlumb.connect({source:d1, target:d2}), + e3 = conn2.endpoints[0], e4 = conn2.endpoints[1]; + + equals(e3.anchor.x, 0.5, "bottom center anchor"); + equals(e3.anchor.y, 1, "bottom center anchor"); + equals(e4.anchor.x, 0.5, "bottom center anchor"); + equals(e4.anchor.y, 1, "bottom center anchor"); + }); + +// setId function + + test(renderMode + " setId, taking two strings, only default scope", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId("d1", "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a selector and a string, only default scope", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1"), "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a DOM element and a string, only default scope", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1")[0], "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking two strings, mix of scopes", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2, scope:"FOO"}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId("d1", "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a selector and a string, mix of scopes", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2, scope:"FOO"}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1"), "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setId, taking a DOM element and a string, mix of scopes", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2, scope:"FOO"}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + _jsPlumb.setId($("#d1")[0], "d3"); + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " setIdChanged, ", function() { + _addDiv("d1"); _addDiv("d2"); + + _jsPlumb.Defaults.MaxConnections = -1; + var e1 = _jsPlumb.addEndpoint("d1"), + e2 = _jsPlumb.addEndpoint("d2"), + e3 = _jsPlumb.addEndpoint("d1"); + + assertEndpointCount("d1", 2, _jsPlumb); + equals(e1.elementId, "d1", "endpoint has correct element id"); + equals(e3.elementId, "d1", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d1", "anchor has correct element id"); + equals(e3.anchor.elementId, "d1", "anchor has correct element id"); + + var c = _jsPlumb.connect({source:e1, target:e2}), + c2 = _jsPlumb.connect({source:e2, target:e1}); + + $("#d1").attr("id", "d3"); + + _jsPlumb.setIdChanged("d1", "d3"); + + assertEndpointCount("d3", 2, _jsPlumb); + assertEndpointCount("d1", 0, _jsPlumb); + + equals(e1.elementId, "d3", "endpoint has correct element id"); + equals(e3.elementId, "d3", "endpoint has correct element id"); + equals(e1.anchor.elementId, "d3", "anchor has correct element id"); + equals(e3.anchor.elementId, "d3", "anchor has correct element id"); + + equals(c.sourceId, "d3", "connection's sourceId has changed"); + equals(c.source.attr("id"), "d3", "connection's source has changed"); + equals(c2.targetId, "d3", "connection's targetId has changed"); + equals(c2.target.attr("id"), "d3", "connection's target has changed"); + }); + + test(renderMode + " endpoint hide/show should hide/show overlays", function() { + _addDiv("d1"); + var e1 = _jsPlumb.addEndpoint("d1", { + overlays:[ + [ "Label", { id:"label", label:"foo" } ] + ] + }), + o = e1.getOverlay("label"); + + ok(o.isVisible(), "overlay is initially visible"); + _jsPlumb.hide("d1", true); + ok(!o.isVisible(), "overlay is no longer visible"); + }); + + test(renderMode + " connection hide/show should hide/show overlays", function() { + _addDiv("d1");_addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", + overlays:[ + [ "Label", { id:"label", label:"foo" } ] + ] + }), + o = c.getOverlay("label"); + + ok(o.isVisible(), "overlay is initially visible"); + _jsPlumb.hide("d1", true); + ok(!o.isVisible(), "overlay is no longer visible"); + }); + + test(renderMode + " select, basic test", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2"}), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 1, "one connection selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; dont filter on scope.", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 2, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scope", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + s = _jsPlumb.select({source:"d1", scope:"FOO"}); + + equals(s.length, 1, "one connection selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scopes", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + c3 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAZ"}) + s = _jsPlumb.select({source:"d1", scope:["FOO","BAR"]}); + + equals(s.length, 2, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; scope but no scope filter; single source id", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + c3 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAZ"}), + c4 = _jsPlumb.connect({source:"d2", target:"d1", scope:"BOZ"}), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 3, "three connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scopes; single source id", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + c3 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAZ"}), + c4 = _jsPlumb.connect({source:"d2", target:"d1", scope:"BOZ"}), + s = _jsPlumb.select({source:"d1", scope:["FOO","BAR", "BOZ"]}); + + equals(s.length, 2, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scope; dont supply sourceid", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}), + s = _jsPlumb.select({ scope:"FOO" }); + + equals(s.length, 1, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, basic test with multiple scopes; filter on scope; dont supply sourceid", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}), + c2 = _jsPlumb.connect({source:"d2", target:"d1", scope:"BAR"}), + s = _jsPlumb.select({ scope:"FOO" }); + + equals(s.length, 1, "two connections selected"); + equals(s.get(0).sourceId, "d1", "d1 is connection source"); + + s.setHover(true); + ok(s.get(0).isHover(), "connection has had hover set to true"); + s.setHover(false); + ok(!(s.get(0).isHover()), "connection has had hover set to false"); + }); + + test(renderMode + " select, two connections, with overlays", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({ + source:"d1", + target:"d2", + overlays:[ + ["Label", {id:"l"}] + ] + }), + c2 = _jsPlumb.connect({ + source:"d1", + target:"d2", + overlays:[ + ["Label", {id:"l"}] + ] + }), + s = _jsPlumb.select({source:"d1"}); + + equals(s.length, 2, "two connections selected"); + ok(s.get(0).getOverlay("l") != null, "connection has overlay"); + ok(s.get(1).getOverlay("l") != null, "connection has overlay"); + }); + + test(renderMode + " select, chaining with setHover and hideOverlay", function() { + _addDiv("d1"); _addDiv("d2"); + var c = _jsPlumb.connect({ + source:"d1", + target:"d2", + overlays:[ + ["Label", {id:"l"}] + ] + }); + s = _jsPlumb.select({source:"d1"}); + + s.setHover(false).hideOverlay("l"); + + ok(!(s.get(0).isHover()), "connection is not hover"); + ok(!(s.get(0).getOverlay("l").isVisible()), "overlay is not visible"); + }); + + test(renderMode + " select, .each function", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10) + }); + } + + var s = _jsPlumb.select(); + equals(s.length, 5, "there are five connections"); + + var t = ""; + s.each(function(connection) { + t += "f"; + }); + equals("fffff", t, ".each is working"); + }); + + test(renderMode + " select, multiple connections + chaining", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + overlays:[ + ["Arrow", {location:0.3}], + ["Arrow", {location:0.7}] + ] + }); + } + + var s = _jsPlumb.select().removeAllOverlays().setParameter("foo", "bar").setHover(false).setLabel("baz"); + equals(s.length, 5, "there are five connections"); + + for (var j = 0; j < 5; j++) { + equals(s.get(j).getOverlays().length, 1, "one overlay: the label"); + equals(s.get(j).getParameter("foo"), "bar", "parameter foo has value 'bar'"); + ok(!(s.get(j).isHover()), "hover is set to false"); + equals(s.get(j).getLabel(), "baz", "label is set to 'baz'"); + } + }); + + test(renderMode + " select, simple getter", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + label:"FOO" + }); + } + + var lbls = _jsPlumb.select().getLabel(); + equals(lbls.length, 5, "there are five labels"); + + for (var j = 0; j < 5; j++) { + equals(lbls[j][0], "FOO", "label has value 'FOO'"); + } + }); + + test(renderMode + " select, getter + chaining", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + label:"FOO" + }); + } + + var params = _jsPlumb.select().removeAllOverlays().setParameter("foo", "bar").getParameter("foo"); + equals(params.length, 5, "there are five params"); + + for (var j = 0; j < 5; j++) { + equals(params[j][0], "bar", "parameter has value 'bar'"); + } + }); + + + test(renderMode + " select, detach method", function() { + for (var i = 1; i < 6; i++) { + _addDiv("d" + i); _addDiv("d" + (i * 10)); + _jsPlumb.connect({ + source:"d" + i, + target:"d" + (i*10), + label:"FOO" + }); + } + + var params = _jsPlumb.select().detach(); + + equals(jsPlumb.select().length, 0, "there are no connections"); + }); + +// setPaintStyle/getPaintStyle tests + + test(renderMode + " setPaintStyle", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), c = _jsPlumb.connect({source:d1, target:d2}); + c.setPaintStyle({strokeStyle:"FOO", lineWidth:999}); + equals(c.paintStyleInUse.strokeStyle, "FOO", "strokeStyle was set"); + equals(c.paintStyleInUse.lineWidth, 999, "lineWidth was set"); + + c.setHoverPaintStyle({strokeStyle:"BAZ", lineWidth:444}); + c.setHover(true); + equals(c.paintStyleInUse.strokeStyle, "BAZ", "strokeStyle was set"); + equals(c.paintStyleInUse.lineWidth, 444, "lineWidth was set"); + + equals(c.getPaintStyle().strokeStyle, "FOO", "getPaintStyle returns correct value"); + equals(c.getHoverPaintStyle().strokeStyle, "BAZ", "getHoverPaintStyle returns correct value"); + }); + +// ******************* getEndpoints ************************************************ + + test(renderMode + " getEndpoints", function() { + _addDiv("d1");_addDiv("d2"); + + _jsPlumb.addEndpoint("d1"); + _jsPlumb.addEndpoint("d2"); + _jsPlumb.addEndpoint("d1"); + + var e = _jsPlumb.getEndpoints("d1"), + e2 = _jsPlumb.getEndpoints("d2"); + equals(e.length, 2, "two endpoints on d1"); + equals(e2.length, 1, "one endpoint on d2"); + }); + + /** + * leave this test at the bottom! + */ + test(renderMode + ': unload test', function() { + _jsPlumb.unload(); + var contextNode = $(".__jsPlumb_context"); + ok(contextNode.length == 0, 'context node unloaded'); + }); +}; + diff --git a/archive/1.3.9/jsPlumb-connectors-statemachine-1.3.9-RC1.js b/archive/1.3.9/jsPlumb-connectors-statemachine-1.3.9-RC1.js new file mode 100644 index 000000000..b9dbdcb08 --- /dev/null +++ b/archive/1.3.9/jsPlumb-connectors-statemachine-1.3.9-RC1.js @@ -0,0 +1,429 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.9 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the state machine connectors. + * + * Thanks to Brainstorm Mobile Solutions for supporting the development of these. + * + * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + var Line = function(x1, y1, x2, y2) { + + this.m = (y2 - y1) / (x2 - x1); + this.b = -1 * ((this.m * x1) - y1); + + this.rectIntersect = function(x,y,w,h) { + var results = []; + + // try top face + // the equation of the top face is y = (0 * x) + b; y = b. + var xInt = (y - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try right face + var yInt = (this.m * (x + w)) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + // bottom face + var xInt = ((y + h) - this.b) / this.m; + // test that the X value is in the line's range. + if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]); + + // try left face + var yInt = (this.m * x) + this.b; + if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]); + + if (results.length == 2) { + var midx = (results[0][0] + results[1][0]) / 2, midy = (results[0][1] + results[1][1]) / 2; + results.push([ midx,midy ]); + // now calculate the segment inside the rectangle where the midpoint lies. + var xseg = midx <= x + (w / 2) ? -1 : 1, + yseg = midy <= y + (h / 2) ? -1 : 1; + results.push([xseg, yseg]); + return results; + } + + return null; + + }; + }, + _segment = function(x1, y1, x2, y2) { + if (x1 <= x2 && y2 <= y1) return 1; + else if (x1 <= x2 && y1 <= y2) return 2; + else if (x2 <= x1 && y2 >= y1) return 3; + return 4; + }, + + // the control point we will use depends on the faces to which each end of the connection is assigned, specifically whether or not the + // two faces are parallel or perpendicular. if they are parallel then the control point lies on the midpoint of the axis in which they + // are parellel and varies only in the other axis; this variation is proportional to the distance that the anchor points lie from the + // center of that face. if the two faces are perpendicular then the control point is at some distance from both the midpoints; the amount and + // direction are dependent on the orientation of the two elements. 'seg', passed in to this method, tells you which segment the target element + // lies in with respect to the source: 1 is top right, 2 is bottom right, 3 is bottom left, 4 is top left. + // + // sourcePos and targetPos are arrays of info about where on the source and target each anchor is located. their contents are: + // + // 0 - absolute x + // 1 - absolute y + // 2 - proportional x in element (0 is left edge, 1 is right edge) + // 3 - proportional y in element (0 is top edge, 1 is bottom edge) + // + _findControlPoint = function(midx, midy, segment, sourceEdge, targetEdge, dx, dy, distance, proximityLimit) { + + // TODO (maybe) + // - if anchor pos is 0.5, make the control point take into account the relative position of the elements. + if (distance <= proximityLimit) return [midx, midy]; + + if (segment == 1) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 2) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 3) { + if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (-1 * dx) , midy + (-1 * dy) ]; + } + else if (segment == 4) { + if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ]; + else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ]; + else return [ midx + (1 * dx) , midy + (-1 * dy) ]; + } + }; + + /* + Function: StateMachine constructor + + Allowed parameters: + curviness - measure of how "curvy" the connectors will be. this is translated as the distance that the + Bezier curve's control point is from the midpoint of the straight line connecting the two + endpoints, and does not mean that the connector is this wide. The Bezier curve never reaches + its control points; they act as gravitational masses. defaults to 10. + margin - distance from element to start and end connectors, in pixels. defaults to 5. + proximityLimit - sets the distance beneath which the elements are consider too close together to bother with fancy + curves. by default this is 80 pixels. + loopbackRadius - the radius of a loopback connector. optional; defaults to 25. + */ + jsPlumb.Connectors.StateMachine = function(params) { + var self = this, + currentPoints = null, + _sx, _sy, _tx, _ty, _controlPoint = [], + curviness = params.curviness || 10, + margin = params.margin || 5, + proximityLimit = params.proximityLimit || 80, + clockwise = params.orientation && params.orientation == "clockwise", + loopbackRadius = params.loopbackRadius || 25, + isLoopback = false; + + this.type = "StateMachine"; + params = params || {}; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *= 1.9; + //ensure at least one pixel width + lineWidth = lineWidth || 1; + var x = Math.min(sourcePos[0], targetPos[0]) - xo, + y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (sourceEndpoint.elementId != targetEndpoint.elementId) { + + isLoopback = false; + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + + // now adjust for the margin + if (sourcePos[2] == 0) _sx -= margin; + if (sourcePos[2] == 1) _sx += margin; + if (sourcePos[3] == 0) _sy -= margin; + if (sourcePos[3] == 1) _sy += margin; + if (targetPos[2] == 0) _tx -= margin; + if (targetPos[2] == 1) _tx += margin; + if (targetPos[3] == 0) _ty -= margin; + if (targetPos[3] == 1) _ty += margin; + + // + // these connectors are quadratic bezier curves, having a single control point. if both anchors + // are located at 0.5 on their respective faces, the control point is set to the midpoint and you + // get a straight line. this is also the case if the two anchors are within 'proximityLimit', since + // it seems to make good aesthetic sense to do that. outside of that, the control point is positioned + // at 'curviness' pixels away along the normal to the straight line connecting the two anchors. + // + // there may be two improvements to this. firstly, we might actually support the notion of avoiding nodes + // in the UI, or at least making a good effort at doing so. if a connection would pass underneath some node, + // for example, we might increase the distance the control point is away from the midpoint in a bid to + // steer it around that node. this will work within limits, but i think those limits would also be the likely + // limits for, once again, aesthetic good sense in the layout of a chart using these connectors. + // + // the second possible change is actually two possible changes: firstly, it is possible we should gradually + // decrease the 'curviness' as the distance between the anchors decreases; start tailing it off to 0 at some + // point (which should be configurable). secondly, we might slightly increase the 'curviness' for connectors + // with respect to how far their anchor is from the center of its respective face. this could either look cool, + // or stupid, and may indeed work only in a way that is so subtle as to have been a waste of time. + // + + var _midx = (_sx + _tx) / 2, _midy = (_sy + _ty) / 2, + m2 = (-1 * _midx) / _midy, theta2 = Math.atan(m2), + dy = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.sin(theta2)), + dx = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.cos(theta2)), + segment = _segment(_sx, _sy, _tx, _ty), + distance = Math.sqrt(Math.pow(_tx - _sx, 2) + Math.pow(_ty - _sy, 2)); + + // calculate the control point. this code will be where we'll put in a rudimentary element avoidance scheme; it + // will work by extending the control point to force the curve to be, um, curvier. + _controlPoint = _findControlPoint(_midx, + _midy, + segment, + sourcePos, + targetPos, + curviness, curviness, + distance, + proximityLimit); + + + var requiredWidth = Math.max(Math.abs(_controlPoint[0] - _sx) * 3, Math.abs(_controlPoint[0] - _tx) * 3, Math.abs(_tx-_sx), 2 * lineWidth, minWidth), + requiredHeight = Math.max(Math.abs(_controlPoint[1] - _sy) * 3, Math.abs(_controlPoint[1] - _ty) * 3, Math.abs(_ty-_sy), 2 * lineWidth, minWidth); + + if (w < requiredWidth) { + var dw = requiredWidth - w; + x -= (dw / 2); + _sx += (dw / 2); + _tx += (dw / 2); + w = requiredWidth; + _controlPoint[0] += (dw / 2); + } + + if (h < requiredHeight) { + var dh = requiredHeight - h; + y -= (dh / 2); + _sy += (dh / 2); + _ty += (dh / 2); + h = requiredHeight; + _controlPoint[1] += (dh / 2); + } + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty, _controlPoint[0], _controlPoint[1] ]; + } + else { + isLoopback = true; + // a loopback connector. draw an arc from one anchor to the other. + // i guess we'll do this the same way as the others. just the control point will be a fair distance away. + var x1 = sourcePos[0], x2 = sourcePos[0], y1 = sourcePos[1] - margin, y2 = sourcePos[1] - margin, + cx = x1, cy = y1 - loopbackRadius; + + // canvas sizing stuff, to ensure the whole painted area is visible. + w = ((2 * lineWidth) + (4 * loopbackRadius)), h = ((2 * lineWidth) + (4 * loopbackRadius)); + x = cx - loopbackRadius - lineWidth - loopbackRadius, y = cy - loopbackRadius - lineWidth - loopbackRadius; + currentPoints = [ x, y, w, h, cx-x, cy-y, loopbackRadius, clockwise, x1-x, y1-y, x2-x, y2-y]; + } + + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_tx, y:_ty }, + { x:_controlPoint[0], y:_controlPoint[1] }, + { x:_controlPoint[0] + 1, y:_controlPoint[1] + 1}, + { x:_sx, y:_sy } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + if (isLoopback) { + + if (location > 0 && location < 1) location = 1- location; + +// current points are [ x, y, width, height, center x, center y, radius, clockwise, startx, starty, endx, endy ] + // so the path length is the circumference of the circle + //var len = 2 * Math.PI * currentPoints[6], + // map 'location' to an angle. 0 is PI/2 when the connector is on the top face; if we + // support other faces it will have to be calculated for each one. 1 is also PI/2. + // 0.5 is -PI/2. + var startAngle = (location * 2 * Math.PI) + (Math.PI / 2), + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + + } + else return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + if (isLoopback) + return Math.atan(location * 2 * Math.PI); + else + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + if (isLoopback) { + + if (location > 0 && location < 1) location = 1- location; + + var circumference = 2 * Math.PI * currentPoints[6], + arcSpan = distance / circumference * 2 * Math.PI, + startAngle = (location * 2 * Math.PI) - arcSpan + (Math.PI / 2), + + startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)), + startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle)); + + return {x:startX, y:startY}; + } + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + + }; + + /* + * Canvas state machine renderer. + */ + jsPlumb.Connectors.canvas.StateMachine = function(params) { + params = params || {}; + var self = this, drawGuideline = params.drawGuideline || true, avoidSelector = params.avoidSelector; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.CanvasConnector.apply(this, arguments); + + + this._paint = function(dimensions) { + + if (dimensions.length == 10) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[8], dimensions[9], dimensions[6], dimensions[7]); + self.ctx.stroke(); + } + else { + // a loopback connector + self.ctx.save(); + self.ctx.beginPath(); + var startAngle = 0, // Starting point on circle + endAngle = 2 * Math.PI, // End point on circle + clockwise = dimensions[7]; // clockwise or anticlockwise + self.ctx.arc(dimensions[4],dimensions[5],dimensions[6],0, endAngle, clockwise); + self.ctx.stroke(); + self.ctx.closePath(); + self.ctx.restore(); + } + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + /* + * SVG State Machine renderer + */ + jsPlumb.Connectors.svg.StateMachine = function() { + var self = this; + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.SvgConnector.apply(this, arguments); + this.getPath = function(d) { + + if (d.length == 10) + return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[8] + " " + d[9] + " " + d[6] + " " + d[7]; + else { + // loopback + return "M" + (d[8] + 4) + " " + d[9] + " A " + d[6] + " " + d[6] + " 0 1,0 " + (d[8]-4) + " " + d[9]; + } + }; + }; + + /* + * VML state machine renderer + */ + jsPlumb.Connectors.vml.StateMachine = function() { + jsPlumb.Connectors.StateMachine.apply(this, arguments); + jsPlumb.VmlConnector.apply(this, arguments); + var _conv = jsPlumb.vml.convertValue; + this.getPath = function(d) { + if (d.length == 10) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + } + else { + // loopback + var left = _conv(d[8] - d[6]), + top = _conv(d[9] - (2 * d[6])), + right = left + _conv(2 * d[6]), + bottom = top + _conv(2 * d[6]), + posString = left + "," + top + "," + right + "," + bottom; + + var o = "ar " + posString + "," + _conv(d[8]) + "," + + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + " e"; + + return o; + } + }; + }; + +})(); + +/* + // now for a rudimentary avoidance scheme. TODO: how to set this in a cross-library way? + // if (avoidSelector) { + // var testLine = new Line(sourcePos[0] + _sx,sourcePos[1] + _sy,sourcePos[0] + _tx,sourcePos[1] + _ty); + // var sel = jsPlumb.getSelector(avoidSelector); + // for (var i = 0; i < sel.length; i++) { + // var id = jsPlumb.getId(sel[i]); + // if (id != sourceEndpoint.elementId && id != targetEndpoint.elementId) { + // o = jsPlumb.getOffset(id), s = jsPlumb.getSize(id); +// +// if (o && s) { +// var collision = testLine.rectIntersect(o.left,o.top,s[0],s[1]); +// if (collision) { + // set the control point to be a certain distance from the midpoint of the two points that + // the line crosses on the rectangle. + // TODO where will this 75 number come from? + // _controlX = collision[2][0] + (75 * collision[3][0]); + // / _controlY = collision[2][1] + (75 * collision[3][1]); +// } +// } + // } + // } + //} + */ \ No newline at end of file diff --git a/archive/1.3.9/jsPlumb-defaults-1.3.9-RC1.js b/archive/1.3.9/jsPlumb-defaults-1.3.9-RC1.js new file mode 100644 index 000000000..70ec8d622 --- /dev/null +++ b/archive/1.3.9/jsPlumb-defaults-1.3.9-RC1.js @@ -0,0 +1,1134 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.9 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the default Connectors, Endpoint and Overlay definitions. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +(function() { + + /** + * + * Helper class to consume unused mouse events by components that are DOM elements and + * are used by all of the different rendering modes. + * + */ + jsPlumb.DOMElementComponent = function(params) { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + // when render mode is canvas, these functions may be called by the canvas mouse handler. + // this component is safe to pipe this stuff to /dev/null. + this.mousemove = + this.dblclick = + this.click = + this.mousedown = + this.mouseup = function(e) { }; + }; + + /** + * Class: Connectors.Straight + * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters. + */ + jsPlumb.Connectors.Straight = function() { + this.type = "Straight"; + var self = this, + currentPoints = null, + _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty, _segment, _length; + + /** + * Computes the new size and position of the canvas. + */ + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]), + h = Math.abs(sourcePos[1] - targetPos[1]), + // these are padding to ensure the whole connector line appears + xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + // minimum size is 2 * line Width if minWidth was not given. + var calculatedMinWidth = Math.max(2 * lineWidth, minWidth); + + if (w < calculatedMinWidth) { + w = calculatedMinWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2); + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < calculatedMinWidth) { + h = calculatedMinWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2); + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + _sx = sourcePos[0] < targetPos[0] ? xo : w-xo; + _sy = sourcePos[1] < targetPos[1] ? yo:h-yo; + _tx = sourcePos[0] < targetPos[0] ? w-xo : xo; + _ty = sourcePos[1] < targetPos[1] ? h-yo : yo; + currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ]; + _dx = _tx - _sx, _dy = _ty - _sy; + //_m = _dy / _dx, _m2 = -1 / _m; + _m = jsPlumbUtil.gradient({x:_sx, y:_sy}, {x:_tx, y:_ty}), _m2 = -1 / _m; + _b = -1 * ((_m * _sx) - _sy); + _theta = Math.atan(_m); _theta2 = Math.atan(_m2); + //_segment = jsPlumbUtil.segment({x:_sx, y:_sy}, {x:_tx, y:_ty}); + _length = Math.sqrt((_dx * _dx) + (_dy * _dy)); + + return currentPoints; + }; + + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + if (location == 0) + return { x:_sx, y:_sy }; + else if (location == 1) + return { x:_tx, y:_ty }; + else + return jsPlumbUtil.pointOnLine({x:_sx, y:_sy}, {x:_tx, y:_ty}, location * _length); + }; + + /** + * returns the gradient of the connector at the given point - which for us is constant. + */ + this.gradientAtPoint = function(location) { + return _m; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. + * this hands off to jsPlumbUtil to do the maths, supplying two points and the distance. + */ + this.pointAlongPathFrom = function(location, distance) { + var p = self.pointOnPath(location), + farAwayPoint = location == 1 ? { + x:_sx + ((_tx - _sx) * 10), + y:_sy + ((_sy - _ty) * 10) + } : {x:_tx, y:_ty }; + + return jsPlumbUtil.pointOnLine(p, farAwayPoint, distance); + }; + }; + + + /** + * Class:Connectors.Bezier + * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's + * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below. + */ + /** + * Function:Constructor + * + * Parameters: + * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line. + * Optional; defaults to 150. + * stub - optional value for a distance to travel from the connector's endpoint before beginning the Bezier curve. defaults to 0. + * + */ + jsPlumb.Connectors.Bezier = function(params) { + var self = this; + params = params || {}; + this.majorAnchor = params.curviness || 150; + this.minorAnchor = 10; + var currentPoints = null; + this.type = "Bezier"; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(sourceEndpoint), + too = targetAnchor.getOrientation(targetEndpoint), + perpendicular = soo[0] != too[0] || soo[1] == too[1], + p = [], + ma = self.majorAnchor, mi = self.minorAnchor; + + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h, _sStubX, _sStubY, _tStubX, _tStubY; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) { + lineWidth = lineWidth || 0; + _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth; + _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2); + _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2); + _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2); + _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2); + _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2); + + _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor); + _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetEndpoint, sourceEndpoint, targetAnchor, sourceAnchor); + var minx1 = Math.min(_sx,_tx), minx2 = Math.min(_CP[0], _CP2[0]), minx = Math.min(minx1,minx2), + maxx1 = Math.max(_sx,_tx), maxx2 = Math.max(_CP[0], _CP2[0]), maxx = Math.max(maxx1,maxx2); + + if (maxx > _w) _w = maxx; + if (minx < 0) { + _canvasX += minx; var ox = Math.abs(minx); + _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox; + } + + var miny1 = Math.min(_sy,_ty), miny2 = Math.min(_CP[1], _CP2[1]), miny = Math.min(miny1,miny2), + maxy1 = Math.max(_sy,_ty), maxy2 = Math.max(_CP[1], _CP2[1]), maxy = Math.max(maxy1,maxy2); + + if (maxy > _h) _h = maxy; + if (miny < 0) { + _canvasY += miny; var oy = Math.abs(miny); + _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy; + } + + if (minWidth && _w < minWidth) { + var posAdjust = (minWidth - _w) / 2; + _w = minWidth; + _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust; + } + + if (minWidth && _h < minWidth) { + var posAdjust = (minWidth - _h) / 2; + _h = minWidth; + _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust; + } + + currentPoints = [_canvasX, _canvasY, _w, _h, + _sx, _sy, _tx, _ty, + _CP[0], _CP[1], _CP2[0], _CP2[1] ]; + + return currentPoints; + }; + + var _makeCurve = function() { + return [ + { x:_sx, y:_sy }, + { x:_CP[0], y:_CP[1] }, + { x:_CP2[0], y:_CP2[1] }, + { x:_tx, y:_ty } + ]; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much. + */ + this.pointOnPath = function(location) { + return jsBezier.pointOnCurve(_makeCurve(), location); + }; + + /** + * returns the gradient of the connector at the given point. + */ + this.gradientAtPoint = function(location) { + return jsBezier.gradientAtPoint(_makeCurve(), location); + }; + + /** + * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult. + * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous + * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see. + * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller + * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to + * calculate the step as a function of distance/distance between endpoints. + */ + this.pointAlongPathFrom = function(location, distance) { + return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance); + }; + }; + + + /** + * Class: Connectors.Flowchart + * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments. + */ + /** + * Function: Constructor + * + * Parameters: + * stub - minimum length for the stub at each end of the connector. defaults to 30 pixels. + * gap - gap to leave between the end of the connector and the element on which the endpoint resides. if you make this larger than stub then you will see some odd looking behaviour. defaults to 0 pixels. + */ + jsPlumb.Connectors.Flowchart = function(params) { + this.type = "Flowchart"; + params = params || {}; + var self = this, + minStubLength = params.stub || params.minStubLength /* bwds compat. */ || 30, + gap = params.gap || 0, + segments = [], + totalLength = 0, + segmentProportions = [], + segmentProportionalLengths = [], + points = [], + swapX, swapY, + maxX = 0, maxY = 0, + /** + * recalculates the points at which the segments begin and end, proportional to the total length travelled + * by all the segments that constitute the connector. we use this to help with pointOnPath calculations. + */ + updateSegmentProportions = function(startX, startY, endX, endY) { + var curLoc = 0; + for (var i = 0; i < segments.length; i++) { + segmentProportionalLengths[i] = segments[i][5] / totalLength; + segmentProportions[i] = [curLoc, (curLoc += (segments[i][5] / totalLength)) ]; + } + }, + appendSegmentsToPoints = function() { + points.push(segments.length); + for (var i = 0; i < segments.length; i++) { + points.push(segments[i][0]); + points.push(segments[i][1]); + } + }, + /** + * helper method to add a segment. + */ + addSegment = function(x, y, sx, sy, tx, ty) { + var lx = segments.length == 0 ? sx : segments[segments.length - 1][0], + ly = segments.length == 0 ? sy : segments[segments.length - 1][1], + m = x == lx ? Infinity : 0, + l = Math.abs(x == lx ? y - ly : x - lx); + segments.push([x, y, lx, ly, m, l]); + totalLength += l; + + maxX = Math.max(maxX, x); + maxY = Math.max(maxY, y); + }, + /** + * returns [segment, proportion of travel in segment, segment index] for the segment + * that contains the point which is 'location' distance along the entire path, where + * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths + * are made up of a list of segments, each of which contributes some fraction to + * the total length. + */ + findSegmentForLocation = function(location) { + var idx = segmentProportions.length - 1, inSegmentProportion = 1; + for (var i = 0; i < segmentProportions.length; i++) { + if (segmentProportions[i][1] >= location) { + idx = i; + inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i]; + break; + } + } + return { segment:segments[idx], proportion:inSegmentProportion, index:idx }; + }; + + this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, + sourceAnchor, targetAnchor, lineWidth, minWidth, sourceInfo, targetInfo) { + + segments = []; + segmentProportions = []; + totalLength = 0; + segmentProportionalLengths = []; + maxX = maxY = 0; + + swapX = targetPos[0] < sourcePos[0]; + swapY = targetPos[1] < sourcePos[1]; + + var lw = lineWidth || 1, + offx = (lw / 2) + (minStubLength * 2), + offy = (lw / 2) + (minStubLength * 2), + so = sourceAnchor.orientation || sourceAnchor.getOrientation(sourceEndpoint), + to = targetAnchor.orientation || targetAnchor.getOrientation(targetEndpoint), + x = swapX ? targetPos[0] : sourcePos[0], + y = swapY ? targetPos[1] : sourcePos[1], + w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx, + h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy; + + // if either anchor does not have an orientation set, we derive one from their relative + // positions. we fix the axis to be the one in which the two elements are further apart, and + // point each anchor at the other element. this is also used when dragging a new connection. + if (so[0] == 0 && so[1] == 0 || to[0] == 0 && to[1] == 0) { + var index = w > h ? 0 : 1, oIndex = [1,0][index]; + so = []; to = []; + so[index] = sourcePos[index] > targetPos[index] ? -1 : 1; + to[index] = sourcePos[index] > targetPos[index] ? 1 : -1; + so[oIndex] = 0; + to[oIndex] = 0; + } + + if (w < minWidth) { + offx += (minWidth - w) / 2; + w = minWidth; + } + if (h < minWidth) { + offy += (minWidth - h) / 2; + h = minWidth; + } + + var sx = swapX ? (w - offx) +( gap * so[0]) : offx + (gap * so[0]), + sy = swapY ? (h - offy) + (gap * so[1]) : offy + (gap * so[1]), + tx = swapX ? offx + (gap * to[0]) : (w - offx) + (gap * to[0]), + ty = swapY ? offy + (gap * to[1]) : (h - offy) + (gap * to[1]), + startStubX = sx + (so[0] * minStubLength), + startStubY = sy + (so[1] * minStubLength), + endStubX = tx + (to[0] * minStubLength), + endStubY = ty + (to[1] * minStubLength), + isXGreaterThanStubTimes2 = Math.abs(sx - tx) > 2 * minStubLength, + isYGreaterThanStubTimes2 = Math.abs(sy - ty) > 2 * minStubLength, + midx = startStubX + ((endStubX - startStubX) / 2), + midy = startStubY + ((endStubY - startStubY) / 2), + oProduct = ((so[0] * to[0]) + (so[1] * to[1])), + opposite = oProduct == -1, + perpendicular = oProduct == 0, + orthogonal = oProduct == 1; + + x -= offx; y -= offy; + points = [x, y, w, h, sx, sy, tx, ty]; + var extraPoints = []; + + addSegment(startStubX, startStubY, sx, sy, tx, ty); + + var sourceAxis = so[0] == 0 ? "y" : "x", + anchorOrientation = opposite ? "opposite" : orthogonal ? "orthogonal" : "perpendicular", + segment = jsPlumbUtil.segment([sx, sy], [tx, ty]), + flipSourceSegments = so[sourceAxis == "x" ? 0 : 1] == -1, + flipSegments = { + "x":[null, 4, 3, 2, 1], + "y":[null, 2, 1, 4, 3] + } + + if (flipSourceSegments) + segment = flipSegments[sourceAxis][segment]; + + var findClearedLine = function(start, mult, anchorPos, dimension) { + return start + (mult * (( 1 - anchorPos) * dimension) + minStubLength); + //mx = so[0] == 0 ? startStubX + ((1 - sourceAnchor.x) * sourceInfo.width) + minStubLength : startStubX, + }, + + lineCalculators = { + oppositex : function() { + if (sourceEndpoint.elementId == targetEndpoint.elementId) { + var _y = startStubY + ((1 - sourceAnchor.y) * sourceInfo.height) + minStubLength; + return [ [ startStubX, _y ], [ endStubX, _y ]]; + } + else if (isXGreaterThanStubTimes2 && (segment == 1 || segment == 2)) { + return [[ midx, sy ], [ midx, ty ]]; + } + else { + return [[ startStubX, midy ], [endStubX, midy ]]; + } + }, + orthogonalx : function() { + if (segment == 1 || segment == 2) { + return [ [ endStubX, startStubY ]]; + } + else { + return [ [ startStubX, endStubY ]]; + } + }, + perpendicularx : function() { + var my = (ty + sy) / 2; + if ((segment == 1 && to[1] == 1) || (segment == 2 && to[1] == -1)) { + if (Math.abs(tx - sx) > minStubLength) + return [ [endStubX, startStubY ]]; + else + return [ [startStubX, startStubY ], [ startStubX, my ], [ endStubX, my ]]; + } + else if ((segment == 3 && to[1] == -1) || (segment == 4 && to[1] == 1)) { + return [ [ startStubX, my ], [ endStubX, my ]]; + } + else if ((segment == 3 && to[1] == 1) || (segment == 4 && to[1] == -1)) { + return [ [ startStubX, endStubY ]]; + } + else if ((segment == 1 && to[1] == -1) || (segment == 2 && to[1] == 1)) { + if (Math.abs(tx - sx) > minStubLength) + return [ [ midx, startStubY ], [ midx, endStubY ]]; + else + return [ [ startStubX, endStubY ]]; + } + }, + oppositey : function() { + if (sourceEndpoint.elementId == targetEndpoint.elementId) { + var _x = startStubX + ((1 - sourceAnchor.x) * sourceInfo.width) + minStubLength; + return [ [ _x, startStubY ], [ _x, endStubY ]]; + } + else if (isYGreaterThanStubTimes2 && (segment == 2 || segment == 3)) { + return [[ sx, midy ], [ tx, midy ]]; + } + else { + return [[ midx, startStubY ], [midx, endStubY ]]; + } + }, + orthogonaly : function() { + if (segment == 2 || segment == 3) { + return [ [ startStubX, endStubY ]]; + } + else { + return [ [ endStubX, startStubY ]]; + } + }, + perpendiculary : function() { + var mx = (tx + sx) / 2; + if ((segment == 2 && to[0] == -1) || (segment == 3 && to[0] == 1)) { + if (Math.abs(tx - sx) > minStubLength) + return [ [startStubX, endStubY ]]; + else + return [ [startStubX, midy ], [ endStubX, midy ]]; + } + else if ((segment == 1 && to[0] == -1) || (segment == 4 && to[0] == 1)) { + var mx = (tx + sx) / 2; + return [ [ mx, startStubY ], [ mx, endStubY ]]; + } + else if ((segment == 1 && to[0] == 1) || (segment == 4 && to[0] == -1)) { + return [ [ endStubX, startStubY ]]; + } + else if ((segment == 2 && to[0] == 1) || (segment == 3 && to[0] == -1)) { + if (Math.abs(ty - sy) > minStubLength) + return [ [ startStubX, midy ], [ endStubX, midy ]]; + else + return [ [ endStubX, startStubY ]]; + } + } + }; + + var p = lineCalculators[anchorOrientation + sourceAxis](); + if (p) { + for (var i = 0; i < p.length; i++) { + addSegment(p[i][0], p[i][1], sx, sy, tx, ty); + } + } + + + addSegment(endStubX, endStubY, sx, sy, tx, ty); + addSegment(tx, ty, sx, sy, tx, ty); + + appendSegmentsToPoints(); + updateSegmentProportions(sx, sy, tx, ty); + + // adjust the max values of the canvas if we have a value that is larger than what we previously set. + // + if (maxY > points[3]) points[3] = maxY + (lineWidth * 2); + if (maxX > points[2]) points[2] = maxX + (lineWidth * 2); + + return points; + }; + + /** + * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from + * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position + * from our knowledge of the segment's start and end points. + */ + this.pointOnPath = function(location) { + return self.pointAlongPathFrom(location, 0); + }; + + /** + * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the + * segment the point falls in. segment gradients are calculated in the compute method. + */ + this.gradientAtPoint = function(location) { + return segments[findSegmentForLocation(location)["index"]][4]; + }; + + /** + * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where + * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view + * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several + * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is + * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better + * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along + * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a + * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional + * advantage is, of course, that there's less computation involved doing it that way. + */ + this.pointAlongPathFrom = function(location, distance) { + var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segments[s.index][5], m = segments[s.index][4]; + var e = { + x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance, + y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance, + segmentInfo : s + }; + + return e; + }; + }; + + // ********************************* END OF CONNECTOR TYPES ******************************************************************* + + // ********************************* ENDPOINT TYPES ******************************************************************* + + /** + * Class: Endpoints.Dot + * A round endpoint, with default radius 10 pixels. + */ + + /** + * Function: Constructor + * + * Parameters: + * + * radius - radius of the endpoint. defaults to 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + this.type = "Dot"; + var self = this; + params = params || {}; + this.radius = params.radius || 10; + this.defaultOffset = 0.5 * this.radius; + this.defaultInnerRadius = this.radius / 3; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var r = endpointStyle.radius || self.radius, + x = anchorPoint[0] - r, + y = anchorPoint[1] - r; + return [ x, y, r * 2, r * 2, r ]; + }; + }; + + /** + * Class: Endpoints.Rectangle + * A Rectangular Endpoint, with default size 20x20. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the endpoint. defaults to 20 pixels. + * height - height of the endpoint. defaults to 20 pixels. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + this.type = "Rectangle"; + var self = this; + params = params || {}; + this.width = params.width || 20; + this.height = params.height || 20; + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height]; + }; + }; + + + var DOMElementEndpoint = function(params) { + jsPlumb.DOMElementComponent.apply(this, arguments); + var self = this; + + var displayElements = [ ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + }; + /** + * Class: Endpoints.Image + * Draws an image as the Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * src - location of the image to use. + */ + jsPlumb.Endpoints.Image = function(params) { + + this.type = "Image"; + DOMElementEndpoint.apply(this, arguments); + + var self = this, + initialized = false, + widthToUse = params.width, + heightToUse = params.height, + _onload = null, + _endpoint = params.endpoint; + + this.img = new Image(); + self.ready = false; + + this.img.onload = function() { + self.ready = true; + widthToUse = widthToUse || self.img.width; + heightToUse = heightToUse || self.img.height; + if (_onload) { + _onload(self); + } + }; + + /* + Function: setImage + Sets the Image to use in this Endpoint. + + Parameters: + img - may be a URL or an Image object + onload - optional; a callback to execute once the image has loaded. + */ + _endpoint.setImage = function(img, onload) { + var s = img.constructor == String ? img : img.src; + _onload = onload; + self.img.src = img; + + if (self.canvas != null) + self.canvas.setAttribute("src", img); + }; + + _endpoint.setImage(params.src || params.url, params.onload); + + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + self.anchorPoint = anchorPoint; + if (self.ready) return [anchorPoint[0] - widthToUse / 2, anchorPoint[1] - heightToUse / 2, + widthToUse, heightToUse]; + else return [0,0,0,0]; + }; + + self.canvas = document.createElement("img"), initialized = false; + self.canvas.style["margin"] = 0; + self.canvas.style["padding"] = 0; + self.canvas.style["outline"] = 0; + self.canvas.style["position"] = "absolute"; + var clazz = params.cssClass ? " " + params.cssClass : ""; + self.canvas.className = jsPlumb.endpointClass + clazz; + if (widthToUse) self.canvas.setAttribute("width", widthToUse); + if (heightToUse) self.canvas.setAttribute("height", heightToUse); + jsPlumb.appendElement(self.canvas, params.parent); + self.attachListeners(self.canvas, self); + + var actuallyPaint = function(d, style, anchor) { + if (!initialized) { + self.canvas.setAttribute("src", self.img.src); + self.appendDisplayElement(self.canvas); + initialized = true; + } + var x = self.anchorPoint[0] - (widthToUse / 2), + y = self.anchorPoint[1] - (heightToUse / 2); + jsPlumb.sizeCanvas(self.canvas, x, y, widthToUse, heightToUse); + }; + + this.paint = function(d, style, anchor) { + if (self.ready) { + actuallyPaint(d, style, anchor); + } + else { + window.setTimeout(function() { + self.paint(d, style, anchor); + }, 200); + } + }; + }; + + /** + * Class: Endpoints.Blank + * An Endpoint that paints nothing (visible) on the screen. Supports cssClass and hoverClass parameters like all Endpoints. + */ + jsPlumb.Endpoints.Blank = function(params) { + var self = this; + this.type = "Blank"; + DOMElementEndpoint.apply(this, arguments); + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + return [anchorPoint[0], anchorPoint[1],10,0]; + }; + + self.canvas = document.createElement("div"); + self.canvas.style.display = "block"; + self.canvas.style.width = "1px"; + self.canvas.style.height = "1px"; + self.canvas.style.background = "transparent"; + self.canvas.style.position = "absolute"; + self.canvas.className = self._jsPlumb.endpointClass; + jsPlumb.appendElement(self.canvas, params.parent); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + }; + }; + + /** + * Class: Endpoints.Triangle + * A triangular Endpoint. + */ + /** + * Function: Constructor + * + * Parameters: + * + * width - width of the triangle's base. defaults to 55 pixels. + * height - height of the triangle from base to apex. defaults to 55 pixels. + */ + jsPlumb.Endpoints.Triangle = function(params) { + this.type = "Triangle"; + params = params || { }; + params.width = params.width || 55; + params.height = params.height || 55; + this.width = params.width; + this.height = params.height; + this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width, + height = endpointStyle.height || self.height, + x = anchorPoint[0] - (width/2), + y = anchorPoint[1] - (height/2); + return [ x, y, width, height ]; + }; + }; +// ********************************* END OF ENDPOINT TYPES ******************************************************************* + + +// ********************************* OVERLAY DEFINITIONS *********************************************************************** + + var AbstractOverlay = function(params) { + var visible = true, self = this; + this.isAppendedAtTopLevel = true; + this.component = params.component; + this.loc = params.location == null ? 0.5 : params.location; + this.endpointLoc = params.endpointLocation == null ? [ 0.5, 0.5] : params.endpointLocation; + this.setVisible = function(val) { + visible = val; + self.component.repaint(); + }; + this.isVisible = function() { return visible; }; + this.hide = function() { self.setVisible(false); }; + this.show = function() { self.setVisible(true); }; + + this.incrementLocation = function(amount) { + self.loc += amount; + self.component.repaint(); + }; + this.setLocation = function(l) { + self.loc = l; + self.component.repaint(); + }; + this.getLocation = function() { + return self.loc; + }; + }; + + + /** + * Class: Overlays.Arrow + * + * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length + * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction + * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line + * across the tail. + */ + /** + * Function: Constructor + * + * Parameters: + * + * length - distance in pixels from head to tail baseline. default 20. + * width - width in pixels of the tail baseline. default 20. + * fillStyle - style to use when filling the arrow. defaults to "black". + * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked. + * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null. + * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5. + * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default. + */ + jsPlumb.Overlays.Arrow = function(params) { + this.type = "Arrow"; + AbstractOverlay.apply(this, arguments); + this.isAppendedAtTopLevel = false; + params = params || {}; + var self = this; + + this.length = params.length || 20; + this.width = params.width || 20; + this.id = params.id; + var direction = (params.direction || 1) < 0 ? -1 : 1, + paintStyle = params.paintStyle || { lineWidth:1 }, + // how far along the arrow the lines folding back in come to. default is 62.3%. + foldback = params.foldback || 0.623; + + + this.computeMaxSize = function() { return self.width * 1.5; }; + + this.cleanup = function() { }; // nothing to clean up for Arrows + + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var hxy, mid, txy, tail, cxy; + if (connector.pointAlongPathFrom) { + + if (self.loc == 1) { + hxy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, -1); + txy = jsPlumbUtil.pointOnLine(hxy, mid, self.length); + } + else if (self.loc == 0) { + txy = connector.pointOnPath(self.loc); + mid = connector.pointAlongPathFrom(self.loc, 1); + hxy = jsPlumbUtil.pointOnLine(txy, mid, self.length); + } + else { + hxy = connector.pointAlongPathFrom(self.loc, direction * self.length / 2), + mid = connector.pointOnPath(self.loc), + txy = jsPlumbUtil.pointOnLine(hxy, mid, self.length); + } + + tail = jsPlumbUtil.perpendicularLineTo(hxy, txy, self.width); + cxy = jsPlumbUtil.pointOnLine(hxy, txy, foldback * self.length); + + var minx = Math.min(hxy.x, tail[0].x, tail[1].x), + maxx = Math.max(hxy.x, tail[0].x, tail[1].x), + miny = Math.min(hxy.y, tail[0].y, tail[1].y), + maxy = Math.max(hxy.y, tail[0].y, tail[1].y); + + var d = { hxy:hxy, tail:tail, cxy:cxy }, + strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle, + fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle, + lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth; + + self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions); + + return [ minx, maxx, miny, maxy]; + } + else return [0,0,0,0]; + }; + }; + + /** + * Class: Overlays.PlainArrow + * + * A basic arrow. This is in fact just one instance of the more generic case in which the tail folds back on itself to some + * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does + * a 'call' to Arrow with foldback set appropriately. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.PlainArrow = function(params) { + params = params || {}; + var p = jsPlumb.extend(params, {foldback:1}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "PlainArrow"; + }; + + /** + * Class: Overlays.Diamond + * + * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just + * happens that in this case, that point is greater than the length of the the arrow. + * + * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the + * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of + * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value + * would be -l/4 in this case - move along one quarter of the total length. + */ + /** + * Function: Constructor + * See for allowed parameters for this overlay. + */ + jsPlumb.Overlays.Diamond = function(params) { + params = params || {}; + var l = params.length || 40, + p = jsPlumb.extend(params, {length:l/2, foldback:2}); + jsPlumb.Overlays.Arrow.call(this, p); + this.type = "Diamond"; + }; + + + + /** + * Class: Overlays.Label + * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb + * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter + * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it + * puts on the Label's 'style' attribute, so the end result is the same. + */ + /** + * Function: Constructor + * + * Parameters: + * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes + * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle. + * label - the label to paint. May be a string or a function that returns a string. Nothing will be painted if your label is null or your + * label function returns null. empty strings _will_ be painted. + * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5. + * + */ + jsPlumb.Overlays.Label = function(params) { + this.type = "Label"; + jsPlumb.DOMElementComponent.apply(this, arguments); + AbstractOverlay.apply(this, arguments); + this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle; + this.id = params.id; + this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it! + var label = params.label || "", + self = this, + initialised = false, + div = document.createElement("div"), + labelText = null; + div.style["position"] = "absolute"; + + var clazz = params["_jsPlumb"].overlayClass + " " + + (self.labelStyle.cssClass ? self.labelStyle.cssClass : + params.cssClass ? params.cssClass : ""); + + div.className = clazz; + + jsPlumb.appendElement(div, params.component.parent); + jsPlumb.getId(div); + self.attachListeners(div, self); + self.canvas = div; + + //override setVisible + var osv = self.setVisible; + self.setVisible = function(state) { + osv(state); // call superclass + div.style.display = state ? "block" : "none"; + }; + + this.getElement = function() { + return div; + }; + + this.cleanup = function() { + if (div != null) jsPlumb.CurrentLibrary.removeElement(div); + }; + + /* + * Function: setLabel + * sets the label's, um, label. you would think i'd call this function + * 'setText', but you can pass either a Function or a String to this, so + * it makes more sense as 'setLabel'. + */ + this.setLabel = function(l) { + label = l; + labelText = null; + self.component.repaint(); + }; + + this.getLabel = function() { + return label; + }; + + this.paint = function(component, d, componentDimensions) { + if (!initialised) { + component.appendDisplayElement(div); + self.attachListeners(div, component); + initialised = true; + } + div.style.left = (componentDimensions[0] + d.minx) + "px"; + div.style.top = (componentDimensions[1] + d.miny) + "px"; + }; + + this.getTextDimensions = function() { + if (typeof label == "function") { + var lt = label(self); + div.innerHTML = lt.replace(/\r\n/g, "
"); + } + else { + if (labelText == null) { + labelText = label; + div.innerHTML = labelText.replace(/\r\n/g, "
"); + } + } + var de = jsPlumb.CurrentLibrary.getElementObject(div), + s = jsPlumb.CurrentLibrary.getSize(de); + return {width:s[0], height:s[1]}; + }; + + this.computeMaxSize = function(connector) { + var td = self.getTextDimensions(connector); + return td.width ? Math.max(td.width, td.height) * 1.5 : 0; + }; + + this.draw = function(component, currentConnectionPaintStyle, componentDimensions) { + var td = self.getTextDimensions(component); + if (td.width != null) { + var cxy = {x:0,y:0}; + if (component.pointOnPath) + cxy = component.pointOnPath(self.loc); // a connection + else { + var locToUse = self.loc.constructor == Array ? self.loc : self.endpointLoc; + cxy = { x:locToUse[0] * componentDimensions[2], + y:locToUse[1] * componentDimensions[3] }; + } + + minx = cxy.x - (td.width / 2), + miny = cxy.y - (td.height / 2); + + self.paint(component, { + minx:minx, + miny:miny, + td:td, + cxy:cxy + }, componentDimensions); + + return [minx, minx+td.width, miny, miny+td.height]; + } + else return [0,0,0,0]; + }; + + this.reattachListeners = function(connector) { + if (div) { + self.reattachListenersForElement(div, self, connector); + } + }; + }; + + // this is really just a test overlay, so its undocumented and doesnt take any parameters. but i was loth to delete it. + jsPlumb.Overlays.GuideLines = function() { + var self = this; + self.length = 50; + self.lineWidth = 5; + this.type = "GuideLines"; + AbstractOverlay.apply(this, arguments); + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) { + + var head = connector.pointAlongPathFrom(self.loc, self.length / 2), + mid = connector.pointOnPath(self.loc), + tail = jsPlumbUtil.pointOnLine(head, mid, self.length), + tailLine = jsPlumbUtil.perpendicularLineTo(head, tail, 40), + headLine = jsPlumbUtil.perpendicularLineTo(tail, head, 20); + + self.paint(connector, [head, tail, tailLine, headLine], self.lineWidth, "red", null, connectorDimensions); + + return [Math.min(head.x, tail.x), Math.min(head.y, tail.y), Math.max(head.x, tail.x), Math.max(head.y,tail.y)]; + }; + + this.computeMaxSize = function() { return 50; }; + + this.cleanup = function() { }; // nothing to clean up for GuideLines + }; + + // ********************************* END OF OVERLAY DEFINITIONS *********************************************************************** + + // ********************************* OVERLAY CANVAS RENDERERS*********************************************************************** + + // ********************************* END OF OVERLAY CANVAS RENDERERS *********************************************************************** +})(); \ No newline at end of file diff --git a/archive/1.3.9/jsPlumb-renderers-canvas-1.3.9-RC1.js b/archive/1.3.9/jsPlumb-renderers-canvas-1.3.9-RC1.js new file mode 100644 index 000000000..e011c923e --- /dev/null +++ b/archive/1.3.9/jsPlumb-renderers-canvas-1.3.9-RC1.js @@ -0,0 +1,507 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.9 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the HTML5 canvas renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + +// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS ******************************************************************* + + // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too. + var _connectionBeingDragged = null, + _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); }, + _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); }, + _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); }, + _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); }, + _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); }; + + /* + * Class:CanvasMouseAdapter + * Provides support for mouse events on canvases. + */ + var CanvasMouseAdapter = function() { + var self = this; + self.overlayPlacements = []; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + jsPlumbUtil.EventGenerator.apply(this, arguments); + /** + * returns whether or not the given event is ojver a painted area of the canvas. + */ + this._over = function(e) { + var o = _getOffset(_getElementObject(self.canvas)), + pageXY = _pageXY(e), + x = pageXY[0] - o.left, y = pageXY[1] - o.top; + if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) { + // first check overlays + for ( var i = 0; i < self.overlayPlacements.length; i++) { + var p = self.overlayPlacements[i]; + if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y)) + return true; + } + + // then the canvas + var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1); + return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0; + } + return false; + }; + + var _mouseover = false, _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false, + _nullSafeHasClass = function(el, clazz) { + return el != null && _hasClass(el, clazz); + }; + this.mousemove = function(e) { + var pageXY = _pageXY(e), clientXY = _clientXY(e), + ee = document.elementFromPoint(clientXY[0], clientXY[1]), + eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay"); + var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector")); + if (!_mouseover && _continue && self._over(e)) { + _mouseover = true; + self.fire("mouseenter", self, e); + return true; + } + // TODO here there is a remote chance that the overlay the mouse moved onto + // is actually not an overlay for the current component. a more thorough check would + // be to ensure the overlay belonged to the current component. + else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) { + _mouseover = false; + self.fire("mouseexit", self, e); + } + self.fire("mousemove", self, e); + }; + + this.click = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("click", self, e); + _mouseWasDown = false; + }; + + this.dblclick = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("dblclick", self, e); + _mouseWasDown = false; + }; + + this.mousedown = function(e) { + if(self._over(e) && !_mouseDown) { + _mouseDown = true; + _posWhenMouseDown = _getOffset(_getElementObject(self.canvas)); + self.fire("mousedown", self, e); + } + }; + + this.mouseup = function(e) { + _mouseDown = false; + self.fire("mouseup", self, e); + }; + + this.contextmenu = function(e) { + if (_mouseover && self._over(e) && !_mouseWasDown) + self.fire("contextmenu", self, e); + _mouseWasDown = false; + }; + }; + + var _newCanvas = function(params) { + var canvas = document.createElement("canvas"); + params["_jsPlumb"].appendElement(canvas, params.parent); + canvas.style.position = "absolute"; + if (params["class"]) canvas.className = params["class"]; + // set an id. if no id on the element and if uuid was supplied it + // will be used, otherwise we'll create one. + params["_jsPlumb"].getId(canvas, params.uuid); + if (params.tooltip) canvas.setAttribute("title", params.tooltip); + + return canvas; + }; + + var CanvasComponent = function(params) { + CanvasMouseAdapter.apply(this, arguments); + + var displayElements = [ ]; + this.getDisplayElements = function() { return displayElements; }; + this.appendDisplayElement = function(el) { displayElements.push(el); }; + }; + + /** + * Class:CanvasConnector + * Superclass for Canvas Connector renderers. + */ + var CanvasConnector = jsPlumb.CanvasConnector = function(params) { + + CanvasComponent.apply(this, arguments); + + var _paintOneStyle = function(dim, aStyle) { + self.ctx.save(); + jsPlumb.extend(self.ctx, aStyle); + if (aStyle.gradient) { + var g = self.createGradient(dim, self.ctx); + for ( var i = 0; i < aStyle.gradient.stops.length; i++) + g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]); + self.ctx.strokeStyle = g; + } + self._paint(dim, aStyle); + self.ctx.restore(); + }; + + var self = this, + clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || ""); + self.canvas = _newCanvas({ + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:params.tooltip + }); + self.ctx = self.canvas.getContext("2d"); + + self.appendDisplayElement(self.canvas); + + self.paint = function(dim, style) { + if (style != null) { + jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + _paintOneStyle(dim, outlineStyle); + } + _paintOneStyle(dim, style); + } + }; + }; + + /** + * Class:CanvasEndpoint + * Superclass for Canvas Endpoint renderers. + */ + var CanvasEndpoint = function(params) { + var self = this; + CanvasComponent.apply(this, arguments); + var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""), + canvasParams = { + "class":clazz, + _jsPlumb:self._jsPlumb, + parent:params.parent, + tooltip:self.tooltip + }; + self.canvas = _newCanvas(canvasParams); + self.ctx = self.canvas.getContext("2d"); + + self.appendDisplayElement(self.canvas); + + this.paint = function(d, style, anchor) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (style.outlineColor != null) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth); + var outlineStyle = { + strokeStyle:style.outlineColor, + lineWidth:outlineStrokeWidth + }; + } + + self._paint.apply(this, arguments); + }; + }; + + jsPlumb.Endpoints.canvas.Dot = function(params) { + jsPlumb.Endpoints.Dot.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + var self = this, + parseValue = function(value) { + try { return parseInt(value); } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + }, + calculateAdjustments = function(gradient) { + var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius; + gradient.offset && (offsetAdjustment = parseValue(gradient.offset)); + gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius)); + return [offsetAdjustment, innerRadius]; + }; + this._paint = function(d, style, anchor) { + if (style != null) { + var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + if (style.gradient) { + var adjustments = calculateAdjustments(style.gradient), + yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0], + xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0], + g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + ctx.beginPath(); + ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + } + }; + }; + + jsPlumb.Endpoints.canvas.Rectangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) { + + var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(self); + jsPlumb.extend(ctx, style); + + /* canvas gradient */ + if (style.gradient) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0; + var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < style.gradient.stops.length; i++) + g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, d[2], d[3]); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + jsPlumb.Endpoints.canvas.Triangle = function(params) { + + var self = this; + jsPlumb.Endpoints.Triangle.apply(this, arguments); + CanvasEndpoint.apply(this, arguments); + + this._paint = function(d, style, anchor) + { + var width = d[2], height = d[3], x = d[0], y = d[1], + ctx = self.canvas.getContext('2d'), + offsetX = 0, offsetY = 0, angle = 0, + orientation = anchor.getOrientation(self); + + if( orientation[0] == 1 ) { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = style.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + if (style.fillStyle || style.gradient) ctx.fill(); + if (style.strokeStyle) ctx.stroke(); + }; + }; + + /* + * Canvas Image Endpoint: uses the default version, which creates an tag. + */ + jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image; + + /* + * Blank endpoint in all renderers is just the default Blank endpoint. + */ + jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank; + + /* + * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Bezier = function() { + var self = this; + jsPlumb.Connectors.Bezier.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions, style) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + // TODO i doubt this handles the case that source and target are swapped. + this.createGradient = function(dim, ctx, swap) { + return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + }; + }; + + /* + * Canvas straight line Connector. Draws a straight line onto a Canvas element. + */ + jsPlumb.Connectors.canvas.Straight = function() { + var self = this, + segmentMultipliers = [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ]; + + jsPlumb.Connectors.Straight.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions, style) { + + self.ctx.beginPath(); + + if (style.dashstyle && style.dashstyle.split(" ").length == 2) { + // only a very simple dashed style is supported - having two values, which define the stroke length + // (as a multiple of the stroke width) and then the space length (also as a multiple of stroke width). + var ds = style.dashstyle.split(" "); + if (ds.length != 2) ds = [2, 2]; + var dss = [ ds[0] * style.lineWidth, ds[1] * style.lineWidth ], + m = (dimensions[6] - dimensions[4]) / (dimensions[7] - dimensions[5]), + s = jsPlumbUtil.segment([dimensions[4], dimensions[5]], [ dimensions[6], dimensions[7] ]), + sm = segmentMultipliers[s], + theta = Math.atan(m), + l = Math.sqrt(Math.pow(dimensions[6] - dimensions[4], 2) + Math.pow(dimensions[7] - dimensions[5], 2)), + repeats = Math.floor(l / (dss[0] + dss[1])), + curPos = [dimensions[4], dimensions[5]]; + + + // TODO: the question here is why could we not support this in all connector types? it's really + // just a case of going along and asking jsPlumb for the next point on the path a few times, until it + // reaches the end. every type of connector supports that method, after all. but right now its only the + // bezier connector that gives you back the new location on the path along with the x,y coordinates, which + // we would need. we'd start out at loc=0 and ask for the point along the path that is dss[0] pixels away. + // we then ask for the point that is (dss[0] + dss[1]) pixels away; and from that one we need not just the + // x,y but the location, cos we're gonna plug that location back in in order to find where that dash ends. + // + // it also strikes me that it should be trivial to support arbitrary dash styles (having more or less than two + // entries). you'd just iterate that array using a step size of 2, and generify the (rss[0] + rss[1]) + // computation to be sum(rss[0]..rss[n]). + + for (var i = 0; i < repeats; i++) { + self.ctx.moveTo(curPos[0], curPos[1]); + + var nextEndX = curPos[0] + (Math.abs(Math.sin(theta) * dss[0]) * sm[0]), + nextEndY = curPos[1] + (Math.abs(Math.cos(theta) * dss[0]) * sm[1]), + nextStartX = curPos[0] + (Math.abs(Math.sin(theta) * (dss[0] + dss[1])) * sm[0]), + nextStartY = curPos[1] + (Math.abs(Math.cos(theta) * (dss[0] + dss[1])) * sm[1]) + + self.ctx.lineTo(nextEndX, nextEndY); + curPos = [nextStartX, nextStartY]; + } + + // now draw the last bit + self.ctx.moveTo(curPos[0], curPos[1]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + + } + else { + self.ctx.moveTo(dimensions[4], dimensions[5]); + self.ctx.lineTo(dimensions[6], dimensions[7]); + } + + self.ctx.stroke(); + }; + + // TODO this does not handle the case that src and target are swapped. + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + + jsPlumb.Connectors.canvas.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + CanvasConnector.apply(this, arguments); + this._paint = function(dimensions, style) { + self.ctx.beginPath(); + self.ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + self.ctx.lineTo(dimensions[6], dimensions[7]); + self.ctx.stroke(); + }; + + this.createGradient = function(dim, ctx) { + return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]); + }; + }; + +// ********************************* END OF CANVAS RENDERERS ******************************************************************* + + jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label; + + /** + * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. + */ + var CanvasOverlay = function() { + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + }; + + var AbstractCanvasArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + CanvasOverlay.apply(this, originalArgs); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + var ctx = connector.ctx; + + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(d.hxy.x, d.hxy.y); + ctx.lineTo(d.tail[0].x, d.tail[0].y); + ctx.lineTo(d.cxy.x, d.cxy.y); + ctx.lineTo(d.tail[1].x, d.tail[1].y); + ctx.lineTo(d.hxy.x, d.hxy.y); + ctx.closePath(); + + if (strokeStyle) { + ctx.strokeStyle = strokeStyle; + ctx.stroke(); + } + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + }; + + jsPlumb.Overlays.canvas.Arrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.canvas.PlainArrow = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.canvas.Diamond = function() { + AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.9/jsPlumb-renderers-svg-1.3.9-RC1.js b/archive/1.3.9/jsPlumb-renderers-svg-1.3.9-RC1.js new file mode 100644 index 000000000..fb538370d --- /dev/null +++ b/archive/1.3.9/jsPlumb-renderers-svg-1.3.9-RC1.js @@ -0,0 +1,550 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.9 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the SVG renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +/** + * SVG support for jsPlumb. + * + * things to investigate: + * + * gradients: https://developer.mozilla.org/en/svg_in_html_introduction + * css:http://tutorials.jenkov.com/svg/svg-and-css.html + * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath + * pointer events: https://developer.mozilla.org/en/css/pointer-events + * + * IE9 hover jquery: http://forum.jquery.com/topic/1-6-2-broke-svg-hover-events + * + */ +;(function() { + + var svgAttributeMap = { + "joinstyle":"stroke-linejoin", + "stroke-linejoin":"stroke-linejoin", + "stroke-dashoffset":"stroke-dashoffset", + "stroke-linecap":"stroke-linecap" + }, + STROKE_DASHARRAY = "stroke-dasharray", + DASHSTYLE = "dashstyle", + LINEAR_GRADIENT = "linearGradient", + RADIAL_GRADIENT = "radialGradient", + FILL = "fill", + STOP = "stop", + STROKE = "stroke", + STROKE_WIDTH = "stroke-width", + STYLE = "style", + NONE = "none", + JSPLUMB_GRADIENT = "jsplumb_gradient_", + LINE_WIDTH = "lineWidth", + ns = { + svg:"http://www.w3.org/2000/svg", + xhtml:"http://www.w3.org/1999/xhtml" + }, + _attr = function(node, attributes) { + for (var i in attributes) + node.setAttribute(i, "" + attributes[i]); + }, + _node = function(name, attributes) { + var n = document.createElementNS(ns.svg, name); + attributes = attributes || {}; + attributes["version"] = "1.1"; + attributes["xmlns"] = ns.xhtml; + _attr(n, attributes); + return n; + }, + _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; }, + _clearGradient = function(parent) { + for (var i = 0; i < parent.childNodes.length; i++) { + if (parent.childNodes[i].tagName == LINEAR_GRADIENT || parent.childNodes[i].tagName == RADIAL_GRADIENT) + parent.removeChild(parent.childNodes[i]); + } + }, + _updateGradient = function(parent, node, style, dimensions, uiComponent) { + var id = JSPLUMB_GRADIENT + uiComponent._jsPlumb.idstamp(); + // first clear out any existing gradient + _clearGradient(parent); + // this checks for an 'offset' property in the gradient, and in the absence of it, assumes + // we want a linear gradient. if it's there, we create a radial gradient. + // it is possible that a more explicit means of defining the gradient type would be + // better. relying on 'offset' means that we can never have a radial gradient that uses + // some default offset, for instance. + // issue 244 suggested the 'gradientUnits' attribute; without this, straight/flowchart connectors with gradients would + // not show gradients when the line was perfectly horizontal or vertical. + if (!style.gradient.offset) { + var g = _node(LINEAR_GRADIENT, {id:id, gradientUnits:"userSpaceOnUse"}); + parent.appendChild(g); + } + else { + var g = _node(RADIAL_GRADIENT, { + id:id + }); + parent.appendChild(g); + } + + // the svg radial gradient seems to treat stops in the reverse + // order to how canvas does it. so we want to keep all the maths the same, but + // iterate the actual style declarations in reverse order, if the x indexes are not in order. + for (var i = 0; i < style.gradient.stops.length; i++) { + // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of + // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments. + // so, not too concerned about leaving this in for now. + var styleToUse = i; + if (dimensions.length == 8) + styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i; + else + styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i; + var stopColor = jsPlumbUtil.convertStyle(style.gradient.stops[styleToUse][1], true); + var s = _node(STOP, {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor}); + g.appendChild(s); + } + var applyGradientTo = style.strokeStyle ? STROKE : FILL; + node.setAttribute(STYLE, applyGradientTo + ":url(#" + id + ")"); + }, + _applyStyles = function(parent, node, style, dimensions, uiComponent) { + + if (style.gradient) { + _updateGradient(parent, node, style, dimensions, uiComponent); + } + else { + // make sure we clear any existing gradient + _clearGradient(parent); + node.setAttribute(STYLE, ""); + } + + node.setAttribute(FILL, style.fillStyle ? jsPlumbUtil.convertStyle(style.fillStyle, true) : NONE); + node.setAttribute(STROKE, style.strokeStyle ? jsPlumbUtil.convertStyle(style.strokeStyle, true) : NONE); + if (style.lineWidth) { + node.setAttribute(STROKE_WIDTH, style.lineWidth); + } + + // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like + // the syntax in VML but is actually kind of nasty: values are given in the pixel + // coordinate space, whereas in VML they are multiples of the width of the stroked + // line, which makes a lot more sense. for that reason, jsPlumb is supporting both + // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from + // VML, which will be the preferred method. the code below this converts a dashstyle + // attribute given in terms of stroke width into a pixel representation, by using the + // stroke's lineWidth. + if (style[DASHSTYLE] && style[LINE_WIDTH] && !style[STROKE_DASHARRAY]) { + var sep = style[DASHSTYLE].indexOf(",") == -1 ? " " : ",", + parts = style[DASHSTYLE].split(sep), + styleToUse = ""; + parts.forEach(function(p) { + styleToUse += (Math.floor(p * style.lineWidth) + sep); + }); + node.setAttribute(STROKE_DASHARRAY, styleToUse); + } + else if(style[STROKE_DASHARRAY]) { + node.setAttribute(STROKE_DASHARRAY, style[STROKE_DASHARRAY]); + } + + // extra attributes such as join type, dash offset. + for (var i in svgAttributeMap) { + if (style[i]) { + node.setAttribute(svgAttributeMap[i], style[i]); + } + } + }, + _decodeFont = function(f) { + var r = /([0-9].)(p[xt])\s(.*)/; + var bits = f.match(r); + return {size:bits[1] + bits[2], font:bits[3]}; + }, + _classManip = function(el, add, clazz) { + var classesToAddOrRemove = clazz.split(" "), + className = el.className, + curClasses = className.baseVal.split(" "); + + for (var i = 0; i < classesToAddOrRemove.length; i++) { + if (add) { + if (curClasses.indexOf(classesToAddOrRemove[i]) == -1) + curClasses.push(classesToAddOrRemove[i]); + } + else { + var idx = curClasses.indexOf(classesToAddOrRemove[i]); + if (idx != -1) + curClasses.splice(idx, 1); + } + } + + el.className.baseVal = curClasses.join(" "); + }, + _addClass = function(el, clazz) { + _classManip(el, true, clazz); + }, + _removeClass = function(el, clazz) { + _classManip(el, false, clazz); + }; + + /** + utility methods for other objects to use. + */ + jsPlumbUtil.svg = { + addClass:_addClass, + removeClass:_removeClass, + node:_node, + attr:_attr, + pos:_pos + }; + + /* + * Base class for SVG components. + */ + //var SvgComponent = function(cssClass, originalArgs, pointerEventsSpec) { + var SvgComponent = function(params) { + var self = this, + pointerEventsSpec = params.pointerEventsSpec || "all"; + jsPlumb.jsPlumbUIComponent.apply(this, params.originalArgs); + self.canvas = null, self.path = null, self.svg = null; + + var clazz = params.cssClass + " " + (params.originalArgs[0].cssClass || ""), + svgParams = { + "style":"", + "width":0, + "height":0, + "pointer-events":pointerEventsSpec, + "position":"absolute" + }; + if (self.tooltip) svgParams["title"] = self.tooltip; + self.svg = _node("svg", svgParams); + if (params.useDivWrapper) { + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + jsPlumb.sizeCanvas(self.canvas,0,0,1,1); + self.canvas.className = clazz; + if (self.tooltip) self.canvas.setAttribute("title", self.tooltip); + } + else { + _attr(self.svg, { "class":clazz }); + self.canvas = self.svg; + } + + params._jsPlumb.appendElement(self.canvas, params.originalArgs[0]["parent"]); + if (params.useDivWrapper) self.canvas.appendChild(self.svg); + + // TODO this displayElement stuff is common between all components, across all + // renderers. would be best moved to jsPlumbUIComponent. + var displayElements = [ self.canvas ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el) { + displayElements.push(el); + }; + + this.paint = function(d, style, anchor) { + if (style != null) { + var x = d[0], y = d[1]; + if (params.useDivWrapper) { + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + x = 0, y = 0; + } + _attr(self.svg, { + "style":_pos([x, y, d[2], d[3]]), + "width": d[2], + "height": d[3] + }); + self._paint.apply(this, arguments); + } + }; + }; + + /* + * Base class for SVG connectors. + */ + var SvgConnector = jsPlumb.SvgConnector = function(params) { + var self = this; + SvgComponent.apply(this, [ { + cssClass:params["_jsPlumb"].connectorClass, + originalArgs:arguments, + pointerEventsSpec:"none", + tooltip:params.tooltip, + _jsPlumb:params["_jsPlumb"] + } ]); + this._paint = function(d, style) { + var p = self.getPath(d), a = { "d":p }, outlineStyle = null; + a["pointer-events"] = "all"; + + // outline style. actually means drawing an svg object underneath the main one. + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = jsPlumb.CurrentLibrary.extend({}, style); + outlineStyle.strokeStyle = jsPlumbUtil.convertStyle(style.outlineColor); + outlineStyle.lineWidth = outlineStrokeWidth; + + if (self.bgPath == null) { + self.bgPath = _node("path", a); + self.svg.appendChild(self.bgPath); + self.attachListeners(self.bgPath, self); + } + else { + _attr(self.bgPath, a); + } + + _applyStyles(self.svg, self.bgPath, outlineStyle, d, self); + } + + + // test - see below + // a["clip-path"]= "url(#testClip)"; + + if (self.path == null) { + self.path = _node("path", a); + self.svg.appendChild(self.path); + self.attachListeners(self.path, self); + + /* + this is a test of a clip path. i'm looking into using one of these to animate a jsplumb connection. + you could do this by walking along the line, stepping along a little at a time, and setting the clip + path to extend as far as that point. + + self.clip = _node("clipPath", {id:"testClip", clipPathUnits:"objectBoundingBox"}); + self.svg.appendChild(self.clip); + self.clip.appendChild(_node("rect", { + x:"0",y:"0",width:"0.5",height:"1" + })); + */ + } + else { + _attr(self.path, a); + } + + _applyStyles(self.svg, self.path, style, d, self); + }; + + this.reattachListeners = function() { + if (self.bgPath) self.reattachListenersForElement(self.bgPath, self); + if (self.path) self.reattachListenersForElement(self.path, self); + }; + + }; + + /* + * SVG Bezier Connector + */ + jsPlumb.Connectors.svg.Bezier = function(params) { + jsPlumb.Connectors.Bezier.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { + var _p = "M " + d[4] + " " + d[5]; + _p += (" C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]); + return _p; + }; + }; + + /* + * SVG straight line Connector + */ + jsPlumb.Connectors.svg.Straight = function(params) { + jsPlumb.Connectors.Straight.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; }; + }; + + jsPlumb.Connectors.svg.Flowchart = function() { + var self = this; + jsPlumb.Connectors.Flowchart.apply(this, arguments); + SvgConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "M " + dimensions[4] + "," + dimensions[5]; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)]; + } + // finally draw a line to the end + p = p + " " + dimensions[6] + "," + dimensions[7]; + return p; + }; + }; + + /* + * Base class for SVG endpoints. + */ + var SvgEndpoint = window.SvgEndpoint = function(params) { + var self = this; + SvgComponent.apply(this, [ { + cssClass:params["_jsPlumb"].endpointClass, + originalArgs:arguments, + pointerEventsSpec:"all", + useDivWrapper:true, + _jsPlumb:params["_jsPlumb"] + } ]); + this._paint = function(d, style) { + var s = jsPlumb.extend({}, style); + if (s.outlineColor) { + s.strokeWidth = s.outlineWidth; + s.strokeStyle = jsPlumbUtil.convertStyle(s.outlineColor, true); + } + + if (self.node == null) { + self.node = self.makeNode(d, s); + self.svg.appendChild(self.node); + self.attachListeners(self.node, self); + } + _applyStyles(self.svg, self.node, s, d, self); + _pos(self.node, d); + }; + + this.reattachListeners = function() { + if (self.node) self.reattachListenersForElement(self.node, self); + }; + }; + + /* + * SVG Dot Endpoint + */ + jsPlumb.Endpoints.svg.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("circle", { + "cx" : d[2] / 2, + "cy" : d[3] / 2, + "r" : d[2] / 2 + }); + }; + }; + + /* + * SVG Rectangle Endpoint + */ + jsPlumb.Endpoints.svg.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + SvgEndpoint.apply(this, arguments); + this.makeNode = function(d, style) { + return _node("rect", { + "width":d[2], + "height":d[3] + }); + }; + }; + + /* + * SVG Image Endpoint is the default image endpoint. + */ + jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image; + /* + * Blank endpoint in svg renderer is the default Blank endpoint. + */ + jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank; + /* + * Label endpoint in svg renderer is the default Label endpoint. + */ + jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label; + + var AbstractSvgArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + jsPlumb.jsPlumbUIComponent.apply(this, originalArgs); + this.isAppendedAtTopLevel = false; + var self = this, path = null; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path", { + "pointer-events":"all" + }); + connector.svg.appendChild(path); + + self.attachListeners(path, connector); + self.attachListeners(path, self); + } + var clazz = originalArgs && (originalArgs.length == 1) ? (originalArgs[0].cssClass || "") : ""; + + _attr(path, { + "d" : makePath(d), + "class" : clazz, + stroke : strokeStyle ? strokeStyle : null, + fill : fillStyle ? fillStyle : null + }); + }; + var makePath = function(d) { + return "M" + d.hxy.x + "," + d.hxy.y + + " L" + d.tail[0].x + "," + d.tail[0].y + + " L" + d.cxy.x + "," + d.cxy.y + + " L" + d.tail[1].x + "," + d.tail[1].y + + " L" + d.hxy.x + "," + d.hxy.y; + }; + this.reattachListeners = function() { + if (path) self.reattachListenersForElement(path, self); + }; + this.cleanup = function() { + if (path != null) jsPlumb.CurrentLibrary.removeElement(path); + }; + }; + + jsPlumb.Overlays.svg.Arrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.svg.PlainArrow = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.svg.Diamond = function() { + AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; + + // a test + jsPlumb.Overlays.svg.GuideLines = function() { + var path = null, self = this, path2 = null, p1_1, p1_2; + jsPlumb.Overlays.GuideLines.apply(this, arguments); + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) { + if (path == null) { + path = _node("path"); + connector.svg.appendChild(path); + self.attachListeners(path, connector); + self.attachListeners(path, self); + + p1_1 = _node("path"); + connector.svg.appendChild(p1_1); + self.attachListeners(p1_1, connector); + self.attachListeners(p1_1, self); + + p1_2 = _node("path"); + connector.svg.appendChild(p1_2); + self.attachListeners(p1_2, connector); + self.attachListeners(p1_2, self); + + } + + _attr(path, { + "d" : makePath(d[0], d[1]), + stroke : "red", + fill : null + }); + + _attr(p1_1, { + "d" : makePath(d[2][0], d[2][1]), + stroke : "blue", + fill : null + }); + + _attr(p1_2, { + "d" : makePath(d[3][0], d[3][1]), + stroke : "green", + fill : null + }); + }; + + var makePath = function(d1, d2) { + return "M " + d1.x + "," + d1.y + + " L" + d2.x + "," + d2.y; + }; + + }; +})(); \ No newline at end of file diff --git a/archive/1.3.9/jsPlumb-renderers-vml-1.3.9-RC1.js b/archive/1.3.9/jsPlumb-renderers-vml-1.3.9-RC1.js new file mode 100644 index 000000000..9960bea9b --- /dev/null +++ b/archive/1.3.9/jsPlumb-renderers-vml-1.3.9-RC1.js @@ -0,0 +1,445 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.9 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the VML renderers. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + // http://ajaxian.com/archives/the-vml-changes-in-ie-8 + // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/ + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + + var vmlAttributeMap = { + "stroke-linejoin":"joinstyle", + "joinstyle":"joinstyle", + "endcap":"endcap", + "miterlimit":"miterlimit" + }, + jsPlumbStylesheet = null; + + if (document.createStyleSheet) { + + var ruleClasses = [ + ".jsplumb_vml", "jsplumb\\:textbox", "jsplumb\\:oval", "jsplumb\\:rect", + "jsplumb\\:stroke", "jsplumb\\:shape", "jsplumb\\:group" + ], + rule = "behavior:url(#default#VML);position:absolute;"; + + jsPlumbStylesheet = document.createStyleSheet(); + + for (var i = 0; i < ruleClasses.length; i++) + jsPlumbStylesheet.addRule(ruleClasses[i], rule); + + // in this page it is also mentioned that IE requires the extra arg to the namespace + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either. + // var iev = document.documentMode; + //if (!iev || iev < 8) + document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml"); + //else + // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML"); + } + + jsPlumb.vml = {}; + + var scale = 1000, + + _groupMap = {}, + _getGroup = function(container, connectorClass) { + var id = jsPlumb.getId(container), + g = _groupMap[id]; + if(!g) { + g = _node("group", [0,0,scale, scale], {"class":connectorClass}); + //g.style.position=absolute; + //g["coordsize"] = "1000,1000"; + g.style.backgroundColor="red"; + _groupMap[id] = g; + jsPlumb.appendElement(g, container); // todo if this gets reinstated, remember to use the current jsplumb instance. + //document.body.appendChild(g); + } + return g; + }, + _atts = function(o, atts) { + for (var i in atts) { + // IE8 fix: setattribute does not work after an element has been added to the dom! + // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/ + //o.setAttribute(i, atts[i]); + + /*There is an additional problem when accessing VML elements by using get/setAttribute. The simple solution is following: + + if (document.documentMode==8) { + ele.opacity=1; + } else { + ele.setAttribute(‘opacity’,1); + } + */ + + o[i] = atts[i]; + } + }, + _node = function(name, d, atts, parent, _jsPlumb) { + atts = atts || {}; + var o = document.createElement("jsplumb:" + name); + _jsPlumb.appendElement(o, parent); + o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml"; + _pos(o, d); + _atts(o, atts); + return o; + }, + _pos = function(o,d) { + o.style.left = d[0] + "px"; + o.style.top = d[1] + "px"; + o.style.width= d[2] + "px"; + o.style.height= d[3] + "px"; + o.style.position = "absolute"; + }, + _conv = jsPlumb.vml.convertValue = function(v) { + return Math.floor(v * scale); + }, + // tests if the given style is "transparent" and then sets the appropriate opacity node to 0 if so, + // or 1 if not. TODO in the future, support variable opacity. + _maybeSetOpacity = function(styleToWrite, styleToCheck, type, component) { + if ("transparent" === styleToCheck) + component.setOpacity(type, "0.0"); + else + component.setOpacity(type, "1.0"); + }, + _applyStyles = function(node, style, component, _jsPlumb) { + var styleToWrite = {}; + if (style.strokeStyle) { + styleToWrite["stroked"] = "true"; + var strokeColor = jsPlumbUtil.convertStyle(style.strokeStyle, true); + styleToWrite["strokecolor"] = strokeColor; + _maybeSetOpacity(styleToWrite, strokeColor, "stroke", component); + styleToWrite["strokeweight"] = style.lineWidth + "px"; + } + else styleToWrite["stroked"] = "false"; + + if (style.fillStyle) { + styleToWrite["filled"] = "true"; + var fillColor = jsPlumbUtil.convertStyle(style.fillStyle, true); + styleToWrite["fillcolor"] = fillColor; + _maybeSetOpacity(styleToWrite, fillColor, "fill", component); + } + else styleToWrite["filled"] = "false"; + + if(style["dashstyle"]) { + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }, node, _jsPlumb); + } + else + component.strokeNode.dashstyle = style["dashstyle"]; + } + else if (style["stroke-dasharray"] && style["lineWidth"]) { + var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",", + parts = style["stroke-dasharray"].split(sep), + styleToUse = ""; + for(var i = 0; i < parts.length; i++) { + styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep); + } + if (component.strokeNode == null) { + component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }, node, _jsPlumb); + //node.appendChild(component.strokeNode); + } + else + component.strokeNode.dashstyle = styleToUse; + } + + _atts(node, styleToWrite); + }, + /* + * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. + */ + VmlComponent = function() { + var self = this; + jsPlumb.jsPlumbUIComponent.apply(this, arguments); + this.opacityNodes = { + "stroke":null, + "fill":null + }; + this.initOpacityNodes = function(vml) { + self.opacityNodes["stroke"] = _node("stroke", [0,0,1,1], {opacity:"0.0"}, vml, self._jsPlumb); + self.opacityNodes["fill"] = _node("fill", [0,0,1,1], {opacity:"0.0"}, vml, self._jsPlumb); + }; + this.setOpacity = function(type, value) { + var node = self.opacityNodes[type]; + if (node) node["opacity"] = "" + value; + }; + var displayElements = [ ]; + this.getDisplayElements = function() { + return displayElements; + }; + + this.appendDisplayElement = function(el, doNotAppendToCanvas) { + if (!doNotAppendToCanvas) self.canvas.parentNode.appendChild(el); + displayElements.push(el); + }; + }, + /* + * Base class for Vml connectors. extends VmlComponent. + */ + VmlConnector = jsPlumb.VmlConnector = function(params) { + var self = this; + self.strokeNode = null; + self.canvas = null; + VmlComponent.apply(this, arguments); + var clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : ""); + this.paint = function(d, style, anchor) { + if (style != null) { + var path = self.getPath(d), p = { "path":path }; + + //* + if (style.outlineColor) { + var outlineWidth = style.outlineWidth || 1, + outlineStrokeWidth = style.lineWidth + (2 * outlineWidth), + outlineStyle = { + strokeStyle : jsPlumbUtil.convertStyle(style.outlineColor), + lineWidth : outlineStrokeWidth + }; + for (var aa in vmlAttributeMap) outlineStyle[aa] = style[aa]; + + if (self.bgCanvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + self.bgCanvas = _node("shape", d, p, params.parent, self._jsPlumb); + _pos(self.bgCanvas, d); + self.appendDisplayElement(self.bgCanvas, true); + self.attachListeners(self.bgCanvas, self); + self.initOpacityNodes(self.bgCanvas, ["stroke"]); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.bgCanvas, d); + _atts(self.bgCanvas, p); + } + + _applyStyles(self.bgCanvas, outlineStyle, self); + } + //*/ + + if (self.canvas == null) { + p["class"] = clazz; + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + if (self.tooltip) p["label"] = self.tooltip; + self.canvas = _node("shape", d, p, params.parent, self._jsPlumb); + //var group = _getGroup(params.parent); // test of append everything to a group + //group.appendChild(self.canvas); // sort of works but not exactly; + //params["_jsPlumb"].appendElement(self.canvas, params.parent); //before introduction of groups + + self.appendDisplayElement(self.canvas, true); + self.attachListeners(self.canvas, self); + self.initOpacityNodes(self.canvas, ["stroke"]); + } + else { + p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale); + _pos(self.canvas, d); + _atts(self.canvas, p); + } + + _applyStyles(self.canvas, style, self, self._jsPlumb); + } + }; + + //self.appendDisplayElement(self.canvas); + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + }, + /* + * + * Base class for Vml Endpoints. extends VmlComponent. + * + */ + VmlEndpoint = window.VmlEndpoint = function(params) { + VmlComponent.apply(this, arguments); + var vml = null, self = this, opacityStrokeNode = null, opacityFillNode = null; + self.canvas = document.createElement("div"); + self.canvas.style["position"] = "absolute"; + + var clazz = self._jsPlumb.endpointClass + (params.cssClass ? (" " + params.cssClass) : ""); + + //var group = _getGroup(params.parent); + //group.appendChild(self.canvas); + params["_jsPlumb"].appendElement(self.canvas, params.parent); + + if (self.tooltip) self.canvas.setAttribute("label", self.tooltip); + + this.paint = function(d, style, anchor) { + var p = { }; + + jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]); + if (vml == null) { + p["class"] = clazz; + vml = self.getVml([0,0, d[2], d[3]], p, anchor, self.canvas, self._jsPlumb); + self.attachListeners(vml, self); + + self.appendDisplayElement(vml, true); + self.appendDisplayElement(self.canvas, true); + + self.initOpacityNodes(vml, ["fill"]); + } + else { + _pos(vml, [0,0, d[2], d[3]]); + _atts(vml, p); + } + + _applyStyles(vml, style, self); + }; + + this.reattachListeners = function() { + if (vml) self.reattachListenersForElement(vml, self); + }; + }; + + jsPlumb.Connectors.vml.Bezier = function() { + jsPlumb.Connectors.Bezier.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + + " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Straight = function() { + jsPlumb.Connectors.Straight.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(d) { + return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e"; + }; + }; + + jsPlumb.Connectors.vml.Flowchart = function() { + jsPlumb.Connectors.Flowchart.apply(this, arguments); + VmlConnector.apply(this, arguments); + this.getPath = function(dimensions) { + var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l"; + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]); + } + // finally draw a line to the end + p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e"; + return p; + }; + }; + + jsPlumb.Endpoints.vml.Dot = function() { + jsPlumb.Endpoints.Dot.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor, parent, _jsPlumb) { return _node("oval", d, atts, parent, _jsPlumb); }; + }; + + jsPlumb.Endpoints.vml.Rectangle = function() { + jsPlumb.Endpoints.Rectangle.apply(this, arguments); + VmlEndpoint.apply(this, arguments); + this.getVml = function(d, atts, anchor, parent, _jsPlumb) { return _node("rect", d, atts, parent, _jsPlumb); }; + }; + + /* + * VML Image Endpoint is the same as the default image endpoint. + */ + jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image; + + /** + * placeholder for Blank endpoint in vml renderer. + */ + jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank; + + /** + * VML Label renderer. uses the default label renderer (which adds an element to the DOM) + */ + jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label; + + var AbstractVmlArrowOverlay = function(superclass, originalArgs) { + superclass.apply(this, originalArgs); + VmlComponent.apply(this, originalArgs); + var self = this, path = null; + self.canvas = null; + self.isAppendedAtTopLevel = true; + var getPath = function(d, connectorDimensions) { + return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) + + " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + + " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + + " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + + " x e"; + }; + this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) { + var p = {}; + if (strokeStyle) { + p["stroked"] = "true"; + p["strokecolor"] = jsPlumbUtil.convertStyle(strokeStyle, true); + } + if (lineWidth) p["strokeweight"] = lineWidth + "px"; + if (fillStyle) { + p["filled"] = "true"; + p["fillcolor"] = fillStyle; + } + var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x), + ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y), + w = Math.abs(xmax - xmin), + h = Math.abs(ymax - ymin), + dim = [xmin, ymin, w, h]; + + // for VML, we create overlays using shapes that have the same dimensions and + // coordsize as their connector - overlays calculate themselves relative to the + // connector (it's how it's been done since the original canvas implementation, because + // for canvas that makes sense). + p["path"] = getPath(d, connectorDimensions); + p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale); + + dim[0] = connectorDimensions[0]; + dim[1] = connectorDimensions[1]; + dim[2] = connectorDimensions[2]; + dim[3] = connectorDimensions[3]; + + if (self.canvas == null) { + //p["class"] = jsPlumb.overlayClass; // TODO currentInstance? + self.canvas = _node("shape", dim, p, connector.canvas.parentNode, connector._jsPlumb); + connector.appendDisplayElement(self.canvas, true); + self.attachListeners(self.canvas, connector); + self.attachListeners(self.canvas, self); + } + else { + _pos(self.canvas, dim); + _atts(self.canvas, p); + } + }; + + this.reattachListeners = function() { + if (self.canvas) self.reattachListenersForElement(self.canvas, self); + }; + + this.cleanup = function() { + if (self.canvas != null) jsPlumb.CurrentLibrary.removeElement(self.canvas); + }; + }; + + jsPlumb.Overlays.vml.Arrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]); + }; + + jsPlumb.Overlays.vml.PlainArrow = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]); + }; + + jsPlumb.Overlays.vml.Diamond = function() { + AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]); + }; +})(); \ No newline at end of file diff --git a/archive/1.3.9/jsPlumb-util-1.3.9-RC1.js b/archive/1.3.9/jsPlumb-util-1.3.9-RC1.js new file mode 100644 index 000000000..7738c5c2c --- /dev/null +++ b/archive/1.3.9/jsPlumb-util-1.3.9-RC1.js @@ -0,0 +1,221 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.9 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the util functions + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ +jsPlumbUtil = { + isArray : function(a) { + return Object.prototype.toString.call(a) === "[object Array]"; + }, + isString : function(s) { + return typeof s === "string"; + }, + isObject : function(o) { + return Object.prototype.toString.call(o) === "[object Object]"; + }, + convertStyle : function(s, ignoreAlpha) { + // TODO: jsPlumb should support a separate 'opacity' style member. + if ("transparent" === s) return s; + var o = s, + pad = function(n) { return n.length == 1 ? "0" + n : n; }, + hex = function(k) { return pad(Number(k).toString(16)); }, + pattern = /(rgb[a]?\()(.*)(\))/; + if (s.match(pattern)) { + var parts = s.match(pattern)[2].split(","); + o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]); + if (!ignoreAlpha && parts.length == 4) + o = o + hex(parts[3]); + } + return o; + }, + gradient : function(p1, p2) { + p1 = jsPlumbUtil.isArray(p1) ? p1 : [p1.x, p1.y]; + p2 = jsPlumbUtil.isArray(p2) ? p2 : [p2.x, p2.y]; + return (p2[1] - p1[1]) / (p2[0] - p1[0]); + }, + normal : function(p1, p2) { + return -1 / jsPlumbUtil.gradient(p1,p2); + }, + lineLength : function(p1, p2) { + p1 = jsPlumbUtil.isArray(p1) ? p1 : [p1.x, p1.y]; + p2 = jsPlumbUtil.isArray(p2) ? p2 : [p2.x, p2.y]; + return Math.sqrt(Math.pow(p2[1] - p1[1], 2) + Math.pow(p2[0] - p1[0], 2)); + }, + segment : function(p1, p2) { + p1 = jsPlumbUtil.isArray(p1) ? p1 : [p1.x, p1.y]; + p2 = jsPlumbUtil.isArray(p2) ? p2 : [p2.x, p2.y]; + if (p2[0] > p1[0]) { + return (p2[1] > p1[1]) ? 2 : 1; + } + else { + return (p2[1] > p1[1]) ? 3 : 4; + } + }, + intersects : function(r1, r2) { + var x1 = r1.x, x2 = r1.x + r1.w, y1 = r1.y, y2 = r1.y + r1.h, + a1 = r2.x, a2 = r2.x + r2.w, b1 = r2.y, b2 = r2.y + r2.h; + + return ( (x1 <= a1 && a1 <= x2) && (y1 <= b1 && b1 <= y2) ) || + ( (x1 <= a2 && a2 <= x2) && (y1 <= b1 && b1 <= y2) ) || + ( (x1 <= a1 && a1 <= x2) && (y1 <= b2 && b2 <= y2) ) || + ( (x1 <= a2 && a1 <= x2) && (y1 <= b2 && b2 <= y2) ) || + + ( (a1 <= x1 && x1 <= a2) && (b1 <= y1 && y1 <= b2) ) || + ( (a1 <= x2 && x2 <= a2) && (b1 <= y1 && y1 <= b2) ) || + ( (a1 <= x1 && x1 <= a2) && (b1 <= y2 && y2 <= b2) ) || + ( (a1 <= x2 && x1 <= a2) && (b1 <= y2 && y2 <= b2) ); + }, + segmentMultipliers : [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ], + inverseSegmentMultipliers : [null, [-1, -1], [-1, 1], [1, 1], [1, -1] ], + pointOnLine : function(fromPoint, toPoint, distance) { + var m = jsPlumbUtil.gradient(fromPoint, toPoint), + s = jsPlumbUtil.segment(fromPoint, toPoint), + segmentMultiplier = distance > 0 ? jsPlumbUtil.segmentMultipliers[s] : jsPlumbUtil.inverseSegmentMultipliers[s], + theta = Math.atan(m), + y = Math.abs(distance * Math.sin(theta)) * segmentMultiplier[1], + x = Math.abs(distance * Math.cos(theta)) * segmentMultiplier[0]; + return { x:fromPoint.x + x, y:fromPoint.y + y }; + }, + /** + * calculates a perpendicular to the line fromPoint->toPoint, that passes through toPoint and is 'length' long. + * @param fromPoint + * @param toPoint + * @param length + */ + perpendicularLineTo : function(fromPoint, toPoint, length) { + var m = jsPlumbUtil.gradient(fromPoint, toPoint), + theta2 = Math.atan(-1 / m), + y = length / 2 * Math.sin(theta2), + x = length / 2 * Math.cos(theta2); + return [{x:toPoint.x + x, y:toPoint.y + y}, {x:toPoint.x - x, y:toPoint.y - y}]; + }, + findWithFunction : function(a, f) { + if (a) + for (var i = 0; i < a.length; i++) if (f(a[i])) return i; + return -1; + }, + indexOf : function(l, v) { + return jsPlumbUtil.findWithFunction(l, function(_v) { return _v == v; }); + }, + removeWithFunction : function(a, f) { + var idx = jsPlumbUtil.findWithFunction(a, f); + if (idx > -1) a.splice(idx, 1); + return idx != -1; + }, + remove : function(l, v) { + var idx = jsPlumbUtil.indexOf(l, v); + if (idx > -1) l.splice(idx, 1); + return idx != -1; + }, + // TODO support insert index + addWithFunction : function(list, item, hashFunction) { + if (jsPlumbUtil.findWithFunction(list, hashFunction) == -1) list.push(item); + }, + addToList : function(map, key, value) { + var l = map[key]; + if (l == null) { + l = [], map[key] = l; + } + l.push(value); + return l; + }, + /** + * EventGenerator + * Superclass for objects that generate events - jsPlumb extends this, as does jsPlumbUIComponent, which all the UI elements extend. + */ + EventGenerator : function() { + var _listeners = {}, self = this; + + // this is a list of events that should re-throw any errors that occur during their dispatch. as of 1.3.0 this is private to + // jsPlumb, but it seems feasible that people might want to manipulate this list. the thinking is that we don't want event + // listeners to bring down jsPlumb - or do we. i can't make up my mind about this, but i know i want to hear about it if the "ready" + // event fails, because then my page has most likely not initialised. so i have this halfway-house solution. it will be interesting + // to hear what other people think. + var eventsToDieOn = [ "ready" ]; + + /* + * Binds a listener to an event. + * + * Parameters: + * event - name of the event to bind to. + * listener - function to execute. + */ + this.bind = function(event, listener) { + jsPlumbUtil.addToList(_listeners, event, listener); + return self; + }; + /* + * Fires an update for the given event. + * + * Parameters: + * event - event to fire + * value - value to pass to the event listener(s). + * originalEvent - the original event from the browser + */ + this.fire = function(event, value, originalEvent) { + if (_listeners[event]) { + for ( var i = 0; i < _listeners[event].length; i++) { + // doing it this way rather than catching and then possibly re-throwing means that an error propagated by this + // method will have the whole call stack available in the debugger. + if (jsPlumbUtil.findWithFunction(eventsToDieOn, function(e) { return e === event}) != -1) + _listeners[event][i](value, originalEvent); + else { + // for events we don't want to die on, catch and log. + try { + _listeners[event][i](value, originalEvent); + } catch (e) { + jsPlumbUtil.log("jsPlumb: fire failed for event " + event + " : " + e); + } + } + } + } + return self; + }; + /* + * Clears either all listeners, or listeners for some specific event. + * + * Parameters: + * event - optional. constrains the clear to just listeners for this event. + */ + this.clearListeners = function(event) { + if (event) + delete _listeners[event]; + else { + delete _listeners; + _listeners = {}; + } + return self; + }; + + this.getListener = function(forEvent) { + return _listeners[forEvent]; + }; + }, + logEnabled : true, + log : function() { + if (jsPlumbUtil.logEnabled && typeof console != "undefined") { + try { + var msg = arguments[arguments.length - 1]; + console.log(msg); + } + catch (e) {} + } + }, + group : function(g) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.group(g); }, + groupEnd : function(g) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.groupEnd(g); }, + time : function(t) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.time(t); }, + timeEnd : function(t) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.timeEnd(t); } +}; \ No newline at end of file diff --git a/archive/1.3.9/mootools.jsPlumb-1.3.9-RC1.js b/archive/1.3.9/mootools.jsPlumb-1.3.9-RC1.js new file mode 100644 index 000000000..daa4423ca --- /dev/null +++ b/archive/1.3.9/mootools.jsPlumb-1.3.9-RC1.js @@ -0,0 +1,459 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.9 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the MooTools adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +;(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function(now) { + this.parent(now); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event, originalEvent) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr, originalEvent); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, _scope, value) { + var l = list[_scope]; + if (!l) { + l = []; + list[_scope] = l; + } + l.push(value); + }; + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + var _getElementObject = function(el) { + return $(el); + }, + + _removeNonPermanentDroppables = function(drag) { + /* + // remove non-permanent droppables from all arrays + var dbs = _droppables[drag.scope], d = []; + if (dbs) { + var d = []; + for (var i=0; i < dbs.length; i++) { + var isPermanent = dbs[i].getAttribute("_isPermanentDroppable"); + if (isPermanent === "true") d.push(dbs[i]); + } + dbs.splice(0, dbs.length); + _droppables[drag.scope] = d; + } + d = []; + // clear out transient droppables from the drag itself + for(var i = 0; i < drag.droppables.length; i++) { + var isPermanent = drag.droppables[i].getAttribute("_isPermanentDroppable"); + if (isPermanent === "true") d.push(drag.droppables[i]); + } + drag.droppables.splice(0, drag.droppables.length); // release old ones + drag.droppables = d; + //*/ + }; + + + jsPlumb.CurrentLibrary = { + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el) + try { + if (el.className.constructor == SVGAnimatedString) { + jsPlumbUtil.svg.addClass(el, clazz); + } + else el.addClass(clazz); + } + catch (e) { + // SVGAnimatedString not supported; no problem. + el.addClass(clazz); + } + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + m.start(properties); + }, + + appendElement : function(child, parent) { + _getElementObject(parent).grab(child); + }, + + bind : function(el, event, callback) { + el = _getElementObject(el); + el.addEvent(event, callback); + }, + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + getClientXY : function(eventObject) { + return [eventObject.event.clientX, eventObject.event.clientY]; + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el), + drags = _draggablesById[id]; + return drags[0].scope; + }, + + getDropEvent : function(args) { + return args[2]; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getDOMElement : function(el) { + // MooTools just decorates the DOM elements. so we have either an ID or an Element here. + return typeof(el) == "String" ? document.getElementById(el) : el; + }, + + getElementObject : _getElementObject, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + getPageXY : function(eventObject) { + return [eventObject.event.pageX, eventObject.event.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).getParent(); + }, + + getScrollLeft : function(el) { + return null; + }, + + getScrollTop : function(el) { + return null; + }, + + getSelector : function(spec) { + return $$(spec); + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e != null ? e.tagName : null; + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[0], + p = jsPlumb.CurrentLibrary.getElementObject(ui).getPosition(); + return { left:p.x, top:p.y }; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options, isPlumbedComponent) { + var id = jsPlumb.getId(el); + var drag = _draggablesById[id]; + if (!drag) { + var originalZIndex = 0, + originalCursor = null, + dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + + options['onStart'] = jsPlumb.wrap(options['onStart'], function() { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + drag.originalZIndex = originalZIndex; + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + + _removeNonPermanentDroppables(drag); + }); + + // DROPPABLES - only relevant if this is a plumbed component, ie. not just the result of the user making some DOM element + // draggable. this is the only library adapter that has to care about this parameter. + var scope = "" + (options["scope"] || jsPlumb.Defaults.Scope), + filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }, + droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + + if (isPlumbedComponent) { + + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr, event) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop', event); + } + }; + } + else + options["droppables"] = []; + + drag = new Drag.Move(el, options); + drag.scope = scope; + drag.originalZIndex = originalZIndex; + _add(_draggablesById, el.get("id"), drag); + // again, only keep a record of this for scope stuff if this is a plumbed component (an endpoint) + if (isPlumbedComponent) { + _add(_draggablesByScope, scope, drag); + } + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + initDroppable : function(el, options, isPlumbedComponent, isPermanent) { + var scope = options["scope"]; + _add(_droppables, scope, el); + var id = jsPlumb.getId(el); + + el.setAttribute("_isPermanentDroppable", isPermanent); // we use this to clear out droppables on drag complete. + _droppableOptions[id] = options; + _droppableScopesById[id] = scope; + var filterFunc = function(entry) { return entry.element != el; }, + draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + isAlreadyDraggable : function(el) { + return _draggablesById[jsPlumb.getId(el)] != null; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + return (typeof Drag != undefined && typeof Drag.Move != undefined); + }, + + /** + * removes the given class from the element object. + */ + removeClass : function(el, clazz) { + el = jsPlumb.CurrentLibrary.getElementObject(el); + try { + if (el.className.constructor == SVGAnimatedString) { + jsPlumbUtil.svg.removeClass(el, clazz); + } + else el.removeClass(clazz); + } + catch (e) { + // SVGAnimatedString not supported; no problem. + el.removeClass(clazz); + } + }, + + removeElement : function(element, parent) { + var el = _getElementObject(element); + if (el) el.dispose(); // ?? + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + setDragScope : function(el, scope) { + var drag = _draggablesById[el.get("id")]; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + drag[0].droppables = droppables; + }, + + setOffset : function(el, o) { + _getElementObject(el).setPosition({x:o.left, y:o.top}); + }, + + stopDrag : function() { + for (var i in _draggablesById) { + for (var j = 0; j < _draggablesById[i].length; j++) { + var d = _draggablesById[i][j]; + d.stop(); + if (d.originalZIndex != 0) + d.element.setStyle("z-index", d.originalZIndex); + } + } + }, + + /** + * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself. + * the other libraries do not. yui, in fact, cannot even pass an original event. we have to pull out stuff + * from the originalEvent to put in an options object for YUI. + * @param el + * @param event + * @param originalEvent + */ + trigger : function(el, event, originalEvent) { + originalEvent.stopPropagation(); + _getElementObject(el).fireEvent(event, originalEvent); + }, + + unbind : function(el, event, callback) { + el = _getElementObject(el); + el.removeEvent(event, callback); + } + }; + + window.addEvent('domready', jsPlumb.init); +})(); diff --git a/build/1.3.9/tests/qunit-all.html b/archive/1.3.9/tests/qunit-all.html similarity index 100% rename from build/1.3.9/tests/qunit-all.html rename to archive/1.3.9/tests/qunit-all.html diff --git a/build/1.3.9/tests/qunit-canvas-jquery-instance.html b/archive/1.3.9/tests/qunit-canvas-jquery-instance.html similarity index 100% rename from build/1.3.9/tests/qunit-canvas-jquery-instance.html rename to archive/1.3.9/tests/qunit-canvas-jquery-instance.html diff --git a/build/1.3.9/tests/qunit-canvas-jquery.html b/archive/1.3.9/tests/qunit-canvas-jquery.html similarity index 100% rename from build/1.3.9/tests/qunit-canvas-jquery.html rename to archive/1.3.9/tests/qunit-canvas-jquery.html diff --git a/build/1.3.9/tests/qunit-canvas-mootools.html b/archive/1.3.9/tests/qunit-canvas-mootools.html similarity index 100% rename from build/1.3.9/tests/qunit-canvas-mootools.html rename to archive/1.3.9/tests/qunit-canvas-mootools.html diff --git a/build/1.3.9/tests/qunit-svg-jquery-instance.html b/archive/1.3.9/tests/qunit-svg-jquery-instance.html similarity index 100% rename from build/1.3.9/tests/qunit-svg-jquery-instance.html rename to archive/1.3.9/tests/qunit-svg-jquery-instance.html diff --git a/build/1.3.9/tests/qunit-svg-jquery.html b/archive/1.3.9/tests/qunit-svg-jquery.html similarity index 100% rename from build/1.3.9/tests/qunit-svg-jquery.html rename to archive/1.3.9/tests/qunit-svg-jquery.html diff --git a/build/1.3.9/tests/qunit-vml-jquery-instance.html b/archive/1.3.9/tests/qunit-vml-jquery-instance.html similarity index 100% rename from build/1.3.9/tests/qunit-vml-jquery-instance.html rename to archive/1.3.9/tests/qunit-vml-jquery-instance.html diff --git a/build/1.3.9/tests/qunit-vml-jquery.html b/archive/1.3.9/tests/qunit-vml-jquery.html similarity index 100% rename from build/1.3.9/tests/qunit-vml-jquery.html rename to archive/1.3.9/tests/qunit-vml-jquery.html diff --git a/build/1.3.9/tests/qunit.css b/archive/1.3.9/tests/qunit.css similarity index 100% rename from build/1.3.9/tests/qunit.css rename to archive/1.3.9/tests/qunit.css diff --git a/archive/1.3.9/yui.jsPlumb-1.3.9-RC1.js b/archive/1.3.9/yui.jsPlumb-1.3.9-RC1.js new file mode 100644 index 000000000..241b52950 --- /dev/null +++ b/archive/1.3.9/yui.jsPlumb-1.3.9-RC1.js @@ -0,0 +1,379 @@ +/* + * jsPlumb + * + * Title:jsPlumb 1.3.9 + * + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas + * elements, or VML. + * + * This file contains the YUI3 adapter. + * + * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org) + * + * http://jsplumb.org + * http://github.com/sporritt/jsplumb + * http://code.google.com/p/jsplumb + * + * Dual licensed under the MIT and GPL2 licenses. + */ + +/** + * addClass adds a class to the given element + * animate calls the underlying library's animate functionality + * appendElement appends a child element to a parent element. + * bind binds some event to an element + * dragEvents a dictionary of event names + * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally. + * getAttribute gets some attribute from an element + * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback + * getDragScope gets the drag scope for a given element. + * getElementObject turns an id or dom element into an element object of the underlying library's type. + * getOffset gets an element's offset + * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be? + * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be? + * getSize gets an element's size. + * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback. + * initDraggable initializes an element to be draggable + * initDroppable initializes an element to be droppable + * isDragSupported returns whether or not drag is supported for some element. + * isDropSupported returns whether or not drop is supported for some element. + * removeClass removes a class from a given element. + * removeElement removes some element completely from the DOM. + * setAttribute sets an attribute on some element. + * setDraggable sets whether or not some element should be draggable. + * setDragScope sets the drag scope for a given element. + * setOffset sets the offset of some element. + */ +(function() { + + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function( v, b, s ) { + for( var i = +b || 0, l = this.length; i < l; i++ ) { + if( this[i]===v || s && this[i]==v ) { return i; } + } + return -1; + }; + } + + var Y; + + YUI().use('node', 'dd', 'anim', 'node-event-simulate', function(_Y) { + Y = _Y; + Y.on("domready", function() { jsPlumb.init(); }); + }); + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }, + ddEvents = [ "drag:mouseDown", "drag:afterMouseDown", "drag:mouseup", + "drag:align", "drag:removeHandle", "drag:addHandle", "drag:removeInvalid", "drag:addInvalid", + "drag:start", "drag:end", "drag:drag", "drag:over", "drag:enter", + "drag:exit", "drag:drophit", "drag:dropmiss", "drop:over", "drop:enter", "drop:exit", "drop:hit" + ], + animEvents = [ "tween" ], + /** + * helper function to curry callbacks for some element. + */ + _wrapper = function(fn) { + return function() { + try { + return fn.apply(this, arguments); + } + catch (e) { } + }; + }, + /** + * extracts options from the given options object, leaving out event handlers. + */ + _getDDOptions = function(options) { + var o = {}; + for (var i in options) if (ddEvents.indexOf(i) == -1) o[i] = options[i]; + return o; + }, + /** + * attaches all event handlers found in options to the given dragdrop object, and registering + * the given el as the element of interest. + */ + _attachListeners = function(dd, options, eventList) { + for (var ev in options) { + if (eventList.indexOf(ev) != -1) { + var w = _wrapper(options[ev]); + dd.on(ev, w); + } + } + }, + _droppables = {}, + _droppableOptions = {}, + _draggablesByScope = {}, + _draggablesById = {}, + _droppableScopesById = {}, + _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }, + _lastDragObject = null, + _extend = function(o1, o2) { + for (var i in o2) + o1[i] = o2[i]; + return o1; + }, + _getAttribute = function(el, attributeId) { + return el.getAttribute(attributeId); + }, + _getElementObject = function(el) { + if (el == null) return null; + var eee = null; + eee = typeof el == 'string' ? Y.one('#' + el) : el._node ? el : Y.one(el); + return eee; + }; + + jsPlumb.CurrentLibrary = { + + addClass : function(el, clazz) { + jsPlumb.CurrentLibrary.getElementObject(el).addClass(clazz); + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + var o = _extend({node:el, to:properties}, options), + id = _getAttribute(el, "id"); + o["tween"] = jsPlumb.wrap(properties["tween"], function() { + // TODO should use a current instance. + jsPlumb.repaint(id); + }); + var a = new Y.Anim(o); + _attachListeners(a, o, animEvents); + a.run(); + }, + + appendElement : function(child, parent) { + _getElementObject (parent).append(child); + }, + + /** + * event binding wrapper. + */ + bind : function(el, event, callback) { + _getElementObject(el).on(event, callback); + }, + + dragEvents : { + "start":"drag:start", "stop":"drag:end", "drag":"drag:drag", "step":"step", + "over":"drop:enter", "out":"drop:exit", "drop":"drop:hit" + }, + + extend : _extend, + + getAttribute : _getAttribute, + + getClientXY : function(eventObject) { + return [eventObject.clientX, eventObject.clientY]; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + // this is a workaround for the unfortunate fact that in YUI3, the 'drop:exit' event does + // not contain a reference to the drag that just exited. single-threaded js to the + // rescue: we'll just keep it for ourselves. + if (eventArgs[0].drag) _lastDragObject = eventArgs[0].drag.el; + return _lastDragObject; + }, + + getDragScope : function(el) { + var id = jsPlumb.getId(el), + dd = _draggablesById[id]; + return dd.scope; + }, + + getDropEvent : function(args) { + return args[0]; + }, + + getDropScope : function(el) { + var id = jsPlumb.getId(el); + return _droppableScopesById[id]; + }, + + getDOMElement : function(el) { + if (typeof(el) == "String") + return document.getElementById(el); + else if (el._node) + return el._node; + else return el; + }, + + getElementObject : _getElementObject, + + getOffset : function(el) { + var o = Y.DOM.getXY(el._node); + return {left:o[0], top:o[1]}; + }, + + getPageXY : function(eventObject) { + return [eventObject.pageX, eventObject.pageY]; + }, + + getParent : function(el) { + return jsPlumb.CurrentLibrary.getElementObject(el).get("parentNode"); + }, + + getScrollLeft : function(el) { + return 0; + }, + + getScrollTop : function(el) { + return 0; + }, + + getSelector : function(spec) { + var s = Y.all(spec); + return s && s ._nodes ? s._nodes : []; + }, + + getSize : function(el) { + return [ el._node.offsetWidth, el._node.offsetHeight ]; + }, + + getTagName : function(el) { + var e = jsPlumb.CurrentLibrary.getElementObject(el); + return e != null && e._node != null ? e._node.tagName : null; + }, + + getUIPosition : function(args) { + var n = args[0].currentTarget.el._node, + o = Y.DOM.getXY(n); + return {left:o[0], top:o[1]}; + }, + + hasClass : function(el, clazz) { + return el.hasClass(clazz); + }, + + initDraggable : function(el, options, isPlumbedComponent) { + var _opts = _getDDOptions(options), + id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drag(_opts); + dd.el = el; + + if (isPlumbedComponent) { + var scope = options['scope'] || jsPlumb.Defaults.Scope; + dd.scope = scope; + _add(_draggablesByScope, scope, dd); + } + + _draggablesById[id] = dd; + + _attachListeners(dd, options, ddEvents); + }, + + initDroppable : function(el, options) { + var _opts = _getDDOptions(options), + id = jsPlumb.getId(el); + _opts.node = "#" + id; + var dd = new Y.DD.Drop(_opts); + + _droppableOptions[id] = options; + + options = _extend({}, options); + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _droppableScopesById[id] = scope; + + options["drop:enter"] = jsPlumb.wrap(options["drop:enter"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, true); + }, true); + options["drop:exit"] = jsPlumb.wrap(options["drop:exit"], function(e) { + _checkHover(el, false); + }); + options["drop:hit"] = jsPlumb.wrap(options["drop:hit"], function(e) { + if (e.drag.scope !== scope) return true; + _checkHover(el, false); + }, true); + + _attachListeners(dd, options, ddEvents); + }, + + isAlreadyDraggable : function(el) { + el = _getElementObject(el); + return el.hasClass("yui3-dd-draggable"); + }, + + isDragSupported : function(el) { return true; }, + isDropSupported : function(el) { return true; }, + removeClass : function(el, clazz) { + jsPlumb.CurrentLibrary.getElementObject(el).removeClass(clazz); + }, + removeElement : function(el) { _getElementObject(el).remove(); }, + + setAttribute : function(el, attributeName, attributeValue) { + el.setAttribute(attributeName, attributeValue); + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + var id = jsPlumb.getId(el), + dd = _draggablesById[id]; + if (dd) dd.set("lock", !draggable); + }, + + setDragScope : function(el, scope) { + var id = jsPlumb.getId(el), + dd = _draggablesById[id]; + if (dd) dd.scope = scope; + }, + + setOffset : function(el, o) { + el = _getElementObject(el); + el.set("top", o.top); + el.set("left", o.left); + }, + + stopDrag : function() { + Y.DD.DDM.stopDrag(); + }, + + trigger : function(el, event, originalEvent) { + originalEvent.stopPropagation(); + _getElementObject(el).simulate(event, { + pageX:originalEvent.pageX, + pageY:originalEvent.pageY, + clientX:originalEvent.clientX, + clientY:originalEvent.clientY + }); + }, + + /** + * event unbinding wrapper. + */ + unbind : function(el, event, callback) { + _getElementObject(el).detach(event, callback); + } + }; +})(); \ No newline at end of file diff --git a/archive/NearestPoint.js b/archive/NearestPoint.js new file mode 100644 index 000000000..c80c29f40 --- /dev/null +++ b/archive/NearestPoint.js @@ -0,0 +1,201 @@ +(function() { +var MAXDEPTH = 64, EPSILON = Math.pow(2.0,-MAXDEPTH-1), DEGREE = 3, W_DEGREE = 5; +var V2Sub = function(from, p) { return {x:from.x - p.x, y:from.y - p.y }; }; +var V2Dot = function(v1, v2) { return (v1.x * v2.x) + (v1.y * v2.y); }; +var V2SquaredLength = function(v) { return (v.x * v.x) + (v.y * v.y); }; +var V2ScaleII = function(v, s) { return {x:v.x * s, y:v.y * s }; }; +var DistanceFromCurve = function(P, V) { + var w, n_solutions, t; + var t_candidate = new Array(W_DEGREE); + w = ConvertToBezierForm(P, V); + n_solutions = FindRoots(w, W_DEGREE, t_candidate, 0); + var dist, new_dist, p, v, i; + v = V2Sub(P, V[0]); + dist = V2SquaredLength(v); + t = 0.0; + for (i = 0; i < n_solutions; i++) { + p = Bezier(V, DEGREE, t_candidate[i], + null, null); + v = V2Sub(P, p); + new_dist = V2SquaredLength(v); + if (new_dist < dist) { + dist = new_dist; + t = t_candidate[i]; + } + } + v = V2Sub(P, V[DEGREE]); + new_dist = V2SquaredLength(v); + if (new_dist < dist) { + dist = new_dist; + t = 1.0; + } + return {t:t,d:dist}; +}; +var NearestPointOnCurve = function(P, V) { + var td = DistanceFromCurve(P, V); + return Bezier(V, DEGREE, td.t, null, null); +}; +var ConvertToBezierForm = function(P, V) { + var i, j, k, m, n, ub, lb, w, row, column; + var c = new Array(DEGREE+1), d = new Array(DEGREE); + var cdTable = []; + var z = [ [1.0, 0.6, 0.3, 0.1], [0.4, 0.6, 0.6, 0.4], [0.1, 0.3, 0.6, 1.0] ]; + for (i = 0; i <= DEGREE; i++) + c[i] = V2Sub(V[i], P); + for (i = 0; i <= DEGREE - 1; i++) { + d[i] = V2Sub(V[i+1], V[i]); + d[i] = V2ScaleII(d[i], 3.0); + } + for (row = 0; row <= DEGREE - 1; row++) { + for (column = 0; column <= DEGREE; column++) { + if (!cdTable[row]) cdTable[row] = []; + cdTable[row][column] = V2Dot(d[row], c[column]); + } + } + w = []; + for (i = 0; i <= W_DEGREE; i++) { + if (!w[i]) w[i] = []; + w[i].y = 0.0; + w[i].x = parseFloat(i) / W_DEGREE; + } + n = DEGREE; + m = DEGREE-1; + for (k = 0; k <= n + m; k++) { + lb = Math.max(0, k - m); + ub = Math.min(k, n); + for (i = lb; i <= ub; i++) { + j = k - i; + w[i+j].y += cdTable[j][i] * z[j][i]; + } + } + return (w); +}; +var FindRoots = function(w, degree, t, depth) { + var i; + var Left = new Array(W_DEGREE+1), Right = new Array(W_DEGREE+1); + var left_count, right_count; + var left_t = new Array(W_DEGREE+1), right_t = new Array(W_DEGREE+1); + switch (CrossingCount(w, degree)) { + case 0 : { + return 0; + } + case 1 : { + if (depth >= MAXDEPTH) { + t[0] = (w[0].x + w[W_DEGREE].x) / 2.0; + return 1; + } + if (ControlPolygonFlatEnough(w, degree)) { + t[0] = ComputeXIntercept(w, degree); + return 1; + } + break; + } + } + Bezier(w, degree, 0.5, Left, Right); + left_count = FindRoots(Left, degree, left_t, depth+1); + right_count = FindRoots(Right, degree, right_t, depth+1); + for (i = 0; i < left_count; i++) t[i] = left_t[i]; + for (i = 0; i < right_count; i++) t[i+left_count] = right_t[i]; + return (left_count+right_count); +}; +var CrossingCount = function(V, degree) { + var n_crossings = 0; + var sign, old_sign; + var SGN = function(x) { return x == 0 ? 0 : x > 0 ? 1 :-1; }; + sign = old_sign = SGN(V[0].y); + for (var i = 1; i <= degree; i++) { + sign = SGN(V[i].y); + if (sign != old_sign) n_crossings++; + old_sign = sign; + } + return n_crossings; +}; +var ControlPolygonFlatEnough = function(V, degree) { + var value, error; + var intercept_1, intercept_2, left_intercept, right_intercept; + var a, b, c, det, dInv, a1, b1, c1, a2, b2, c2; + a = V[0].y - V[degree].y; + b = V[degree].x - V[0].x; + c = V[0].x * V[degree].y - V[degree].x * V[0].y; + + var max_distance_above = max_distance_below = 0.0; + + for (var i = 1; i < degree; i++) + { + value = a * V[i].x + b * V[i].y + c; + + if (value > max_distance_above) + { + max_distance_above = value; + } + else if (value < max_distance_below) + { + max_distance_below = value; + } + } + a1 = 0.0; + b1 = 1.0; + c1 = 0.0; + a2 = a; + b2 = b; + c2 = c - max_distance_above; + det = a1 * b2 - a2 * b1; + dInv = 1.0/det; + intercept_1 = (b1 * c2 - b2 * c1) * dInv; + a2 = a; + b2 = b; + c2 = c - max_distance_below; + det = a1 * b2 - a2 * b1; + dInv = 1.0/det; + intercept_2 = (b1 * c2 - b2 * c1) * dInv; + left_intercept = Math.min(intercept_1, intercept_2); + right_intercept = Math.max(intercept_1, intercept_2); + error = right_intercept - left_intercept; + return (error < EPSILON)? 1 : 0; +}; +var ComputeXIntercept = function(V, degree) { + var XLK, YLK, XNM, YNM, XMK, YMK, det, detInv, S, T, X, Y; + XLK = 1.0 - 0.0; + YLK = 0.0 - 0.0; + XNM = V[degree].x - V[0].x; + YNM = V[degree].y - V[0].y; + XMK = V[0].x - 0.0; + YMK = V[0].y - 0.0; + det = XNM*YLK - YNM*XLK; + detInv = 1.0/det; + S = (XNM*YMK - YNM*XMK) * detInv; + X = 0.0 + XLK * S; + return X; +}; +var Bezier = function(V, degree, t, Left, Right) { + var Vtemp = new Array(); + for (var j =0; j <= degree; j++) { + if (!Vtemp[0]) Vtemp[0] = []; + Vtemp[0][j] = V[j]; + } + for (var i = 1; i <= degree; i++) { + for (var j =0 ; j <= degree - i; j++) { + if (!Vtemp[i]) Vtemp[i] = []; + if (!Vtemp[i][j]) Vtemp[i][j] = {}; + Vtemp[i][j].x = (1.0 - t) * Vtemp[i-1][j].x + t * Vtemp[i-1][j+1].x; + Vtemp[i][j].y = (1.0 - t) * Vtemp[i-1][j].y + t * Vtemp[i-1][j+1].y; + } + } + if (Left != null) + for (j = 0; j <= degree; j++) Left[j] = Vtemp[j][0]; + if (Right != null) + for (j = 0; j <= degree; j++) Right[j] = Vtemp[degree-j][j]; + return (Vtemp[degree][0]); +}; +var bezCurve = [ + { x:0.0, y:0.0 }, + { x:1.0, y:2.0 }, + { x:3.0, y:3.0 }, + { x:4.0, y:2.0 } +]; +var arbPoint = { x:3.5, y:2.0 }; +var pointOnCurve; +pointOnCurve = NearestPointOnCurve(arbPoint, bezCurve); +jsPlumb.DistanceFromCurve = DistanceFromCurve; +jsPlumb.NearestPointOnCurve = NearestPointOnCurve; +})(); \ No newline at end of file diff --git a/archive/jquery.ba-resize.js b/archive/jquery.ba-resize.js new file mode 100644 index 000000000..1f41d3791 --- /dev/null +++ b/archive/jquery.ba-resize.js @@ -0,0 +1,246 @@ +/*! + * jQuery resize event - v1.1 - 3/14/2010 + * http://benalman.com/projects/jquery-resize-plugin/ + * + * Copyright (c) 2010 "Cowboy" Ben Alman + * Dual licensed under the MIT and GPL licenses. + * http://benalman.com/about/license/ + */ + +// Script: jQuery resize event +// +// *Version: 1.1, Last updated: 3/14/2010* +// +// Project Home - http://benalman.com/projects/jquery-resize-plugin/ +// GitHub - http://github.com/cowboy/jquery-resize/ +// Source - http://github.com/cowboy/jquery-resize/raw/master/jquery.ba-resize.js +// (Minified) - http://github.com/cowboy/jquery-resize/raw/master/jquery.ba-resize.min.js (1.0kb) +// +// About: License +// +// Copyright (c) 2010 "Cowboy" Ben Alman, +// Dual licensed under the MIT and GPL licenses. +// http://benalman.com/about/license/ +// +// About: Examples +// +// This working example, complete with fully commented code, illustrates a few +// ways in which this plugin can be used. +// +// resize event - http://benalman.com/code/projects/jquery-resize/examples/resize/ +// +// About: Support and Testing +// +// Information about what version or versions of jQuery this plugin has been +// tested with, what browsers it has been tested in, and where the unit tests +// reside (so you can test it yourself). +// +// jQuery Versions - 1.3.2, 1.4.1, 1.4.2 +// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.6, Safari 3-4, Chrome, Opera 9.6-10.1. +// Unit Tests - http://benalman.com/code/projects/jquery-resize/unit/ +// +// About: Release History +// +// 1.1 - (3/14/2010) Fixed a minor bug that was causing the event to trigger +// immediately after bind in some circumstances. Also changed $.fn.data +// to $.data to improve performance. +// 1.0 - (2/10/2010) Initial release + +(function($,window,undefined){ + '$:nomunge'; // Used by YUI compressor. + + // A jQuery object containing all non-window elements to which the resize + // event is bound. + var elems = $([]), + + // Extend $.resize if it already exists, otherwise create it. + jq_resize = $.resize = $.extend( $.resize, {} ), + + timeout_id, + + // Reused strings. + str_setTimeout = 'setTimeout', + str_resize = 'resize', + str_data = str_resize + '-special-event', + str_delay = 'delay', + str_throttle = 'throttleWindow'; + + // Property: jQuery.resize.delay + // + // The numeric interval (in milliseconds) at which the resize event polling + // loop executes. Defaults to 250. + + jq_resize[ str_delay ] = 250; + + // Property: jQuery.resize.throttleWindow + // + // Throttle the native window object resize event to fire no more than once + // every milliseconds. Defaults to true. + // + // Because the window object has its own resize event, it doesn't need to be + // provided by this plugin, and its execution can be left entirely up to the + // browser. However, since certain browsers fire the resize event continuously + // while others do not, enabling this will throttle the window resize event, + // making event behavior consistent across all elements in all browsers. + // + // While setting this property to false will disable window object resize + // event throttling, please note that this property must be changed before any + // window object resize event callbacks are bound. + + jq_resize[ str_throttle ] = true; + + // Event: resize event + // + // Fired when an element's width or height changes. Because browsers only + // provide this event for the window element, for other elements a polling + // loop is initialized, running every milliseconds + // to see if elements' dimensions have changed. You may bind with either + // .resize( fn ) or .bind( "resize", fn ), and unbind with .unbind( "resize" ). + // + // Usage: + // + // > jQuery('selector').bind( 'resize', function(e) { + // > // element's width or height has changed! + // > ... + // > }); + // + // Additional Notes: + // + // * The polling loop is not created until at least one callback is actually + // bound to the 'resize' event, and this single polling loop is shared + // across all elements. + // + // Double firing issue in jQuery 1.3.2: + // + // While this plugin works in jQuery 1.3.2, if an element's event callbacks + // are manually triggered via .trigger( 'resize' ) or .resize() those + // callbacks may double-fire, due to limitations in the jQuery 1.3.2 special + // events system. This is not an issue when using jQuery 1.4+. + // + // > // While this works in jQuery 1.4+ + // > $(elem).css({ width: new_w, height: new_h }).resize(); + // > + // > // In jQuery 1.3.2, you need to do this: + // > var elem = $(elem); + // > elem.css({ width: new_w, height: new_h }); + // > elem.data( 'resize-special-event', { width: elem.width(), height: elem.height() } ); + // > elem.resize(); + + $.event.special[ str_resize ] = { + + // Called only when the first 'resize' event callback is bound per element. + setup: function() { + // Since window has its own native 'resize' event, return false so that + // jQuery will bind the event using DOM methods. Since only 'window' + // objects have a .setTimeout method, this should be a sufficient test. + // Unless, of course, we're throttling the 'resize' event for window. + if ( !jq_resize[ str_throttle ] && this[ str_setTimeout ] ) { return false; } + + var elem = $(this); + + // Add this element to the list of internal elements to monitor. + elems = elems.add( elem ); + + // Initialize data store on the element. + $.data( this, str_data, { w: elem.width(), h: elem.height() } ); + + // If this is the first element added, start the polling loop. + if ( elems.length === 1 ) { + loopy(); + } + }, + + // Called only when the last 'resize' event callback is unbound per element. + teardown: function() { + // Since window has its own native 'resize' event, return false so that + // jQuery will unbind the event using DOM methods. Since only 'window' + // objects have a .setTimeout method, this should be a sufficient test. + // Unless, of course, we're throttling the 'resize' event for window. + if ( !jq_resize[ str_throttle ] && this[ str_setTimeout ] ) { return false; } + + var elem = $(this); + + // Remove this element from the list of internal elements to monitor. + elems = elems.not( elem ); + + // Remove any data stored on the element. + elem.removeData( str_data ); + + // If this is the last element removed, stop the polling loop. + if ( !elems.length ) { + clearTimeout( timeout_id ); + } + }, + + // Called every time a 'resize' event callback is bound per element (new in + // jQuery 1.4). + add: function( handleObj ) { + // Since window has its own native 'resize' event, return false so that + // jQuery doesn't modify the event object. Unless, of course, we're + // throttling the 'resize' event for window. + if ( !jq_resize[ str_throttle ] && this[ str_setTimeout ] ) { return false; } + + var old_handler; + + // The new_handler function is executed every time the event is triggered. + // This is used to update the internal element data store with the width + // and height when the event is triggered manually, to avoid double-firing + // of the event callback. See the "Double firing issue in jQuery 1.3.2" + // comments above for more information. + + function new_handler( e, w, h ) { + var elem = $(this), + data = $.data( this, str_data ); + + // If called from the polling loop, w and h will be passed in as + // arguments. If called manually, via .trigger( 'resize' ) or .resize(), + // those values will need to be computed. + data.w = w !== undefined ? w : elem.width(); + data.h = h !== undefined ? h : elem.height(); + + old_handler.apply( this, arguments ); + }; + + // This may seem a little complicated, but it normalizes the special event + // .add method between jQuery 1.4/1.4.1 and 1.4.2+ + if ( $.isFunction( handleObj ) ) { + // 1.4, 1.4.1 + old_handler = handleObj; + return new_handler; + } else { + // 1.4.2+ + old_handler = handleObj.handler; + handleObj.handler = new_handler; + } + } + + }; + + function loopy() { + + // Start the polling loop, asynchronously. + timeout_id = window[ str_setTimeout ](function(){ + + // Iterate over all elements to which the 'resize' event is bound. + elems.each(function(){ + var elem = $(this), + width = elem.width(), + height = elem.height(), + data = $.data( this, str_data ); + + // If element size has changed since the last time, update the element + // data store and trigger the 'resize' event. + if ( width !== data.w || height !== data.h ) { + elem.trigger( str_resize, [ data.w = width, data.h = height ] ); + } + + }); + + // Loop. + loopy(); + + }, jq_resize[ str_delay ] ); + + }; + +})(jQuery,this); diff --git a/archive/jquery.jsPlumb-1.0.4-RC1.js b/archive/jquery.jsPlumb-1.0.4-RC1.js new file mode 100644 index 000000000..6c98c1697 --- /dev/null +++ b/archive/jquery.jsPlumb-1.0.4-RC1.js @@ -0,0 +1,957 @@ +/* + * jsPlumb 1.0.4-RC1 + * + * Provides a way to visually connect elements on an HTML page. + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + */ +if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function( v, b, s ) { + for( var i = +b || 0, l = this.length; i < l; i++ ) { + if( this[i]===v || s && this[i]==v ) { return i; } + } + return -1; + }; +} +(function() { + + var repaintFunction = function() { jsPlumb.repaintEverything(); }; + var automaticRepaint = true; + function repaintEverything() { + if (automaticRepaint) + repaintFunction(); + }; + var resizeTimer = null; + $(window).bind('resize', function() { + if (resizeTimer) clearTimeout(resizeTimer); + resizeTimer = setTimeout(repaintEverything, 100); + }); + + var connections = {}; + var draggableStates = {}; + var _draggableByDefault = true; + var offsets = []; + var sizes = []; + + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + /** + * applies all the styles to the given context. this just wraps the $.extend function. + * + * @param context + * @param styles + */ + var applyPaintStyle = function(context, styles) { + $.extend(context, styles); + }; + + /** + * Handles the drawing of an element. + * @param element jQuery element + * @param ui UI object from jQuery's event system. may be null. + */ + var _draw = function(element, ui) { + var id = element.attr("id"); + var l = connections[id]; + for (var i = 0; i < l.length; i++) + l[i].paint(id, ui); + }; + + /** + * Sets whether or not the given element(s) should be draggable, regardless of what a particular + * plumb command may request. + * + * @param element May be a string, an jQuery object, or a list of strings. + * @param draggable Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + + var _helper = function(el, id) { + draggableStates[id] = draggable; + if (el.draggable) { + el.draggable("option", "disabled", !draggable); + } + }; + + if (typeof element == 'object' && element.length) { + for (var i = 0; i < element.length; i++) { + _helper($(element[i]), element[i]); + } + } + else { + var el = typeof element == 'string' ? $("#" + element) : element; + var id = el.attr("id"); + _helper(el, id); + } + }; + + /** + * private method to do the business of hiding/showing. + * @param elId Id of the element in question + * @param state String specifying a value for the css 'display' property ('block' or 'none'). + */ + var _setVisible = function(elId, state) { + var jpcs = connections[elId]; + for (var i = 0; i < jpcs.length; i++) { + jpcs[i].canvas.style.display=state; + if (jpcs[i].drawEndpoints) { + jpcs[i].sourceEndpointCanvas.style.display=state; + jpcs[i].targetEndpointCanvas.style.display=state; + } + } + }; + + /** + * helper to create a canvas. + * @param clazz optional class name for the canvas. + */ + var _newCanvas = function(clazz) { + var canvas = document.createElement("canvas"); + document.body.appendChild(canvas); + canvas.style.position="absolute"; + if (clazz) { canvas.className=clazz; } + + if (/MSIE/.test(navigator.userAgent) && !window.opera) { + // for IE we have to set a big canvas size. actually you can override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + + /** + * helper to remove a canvas from the DOM. + */ + var _removeCanvas = function(canvas) { + if (canvas != null) { + try { document.body.removeChild(canvas); } + catch (e) { } + } + + }; + + /** + * generic anchor - can be situated anywhere. params should contain three values, and may optionally have an 'offsets' argument: + * + * x - the x location of the anchor as a percentage of the total width. + * y - the y location of the anchor as a percentage of the total height. + * orientation - an [x,y] array indicating the general direction a connection from the anchor should go in. + * offsets - an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. may be null. + * + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; this.y = params.y || 0; this.orientation = params.orientation || [0,0]; this.offsets = params.offsets || [0,0]; + this.compute = function(xy, wh, txy, twh) { + return [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + } + }; + + /** + * jsPlumb public API + */ + var jsPlumb = window.jsPlumb = { + + connectorClass : '_jsPlumb_connector', + endpointClass : '_jsPlumb_endpoint', + DEFAULT_PAINT_STYLE : { lineWidth : 10, strokeStyle : "red" }, + DEFAULT_ENDPOINT_STYLE : { fillStyle : null }, // meaning it will be derived from the stroke style of the connector. + DEFAULT_ENDPOINT_STYLES : [ null, null ], // meaning it will be derived from the stroke style of the connector. + DEFAULT_DRAG_OPTIONS : { }, + DEFAULT_CONNECTOR : null, + DEFAULT_ENDPOINT : null, + DEFAULT_ENDPOINTS : [null, null], // new in 0.0.4, the ability to specify diff. endpoints. DEFAULT_ENDPOINT is here for backwards compatibility. + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * orientation : [ox, oy] + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + Anchors : + { + TopCenter : new Anchor({x:0.5, y:0, orientation:[0,-1] }), + BottomCenter : new Anchor({x:0.5, y:1, orientation:[0, 1] }), + LeftMiddle: new Anchor({x:0, y:0.5, orientation:[-1,0] }), + RightMiddle : new Anchor({x:1, y:0.5, orientation:[1,0] }), + Center : new Anchor({x:0.5, y:0.5, orientation:[0,0] }), + TopRight : new Anchor({x:1, y:0, orientation:[0,-1] }), + BottomRight : new Anchor({x:1, y:1, orientation:[0,1] }), + TopLeft : new Anchor({x:0, y:0, orientation:[0,-1] }), + BottomLeft : new Anchor({x:0, y:1, orientation:[0,1] }) + }, + + /** + * Types of connectors, eg. Straight, Bezier. + */ + Connectors : + { + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + Straight : function() { + + var self = this; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (w < 2 * lineWidth) { + // minimum size is 2 * line Width + w = 2 * lineWidth; + // if we set this then we also have to place the canvas + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - lineWidth; + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2;; + } + if (h < 2 * lineWidth) { + // minimum size is 2 * line Width + h = 2 * lineWidth; + // if we set this then we also have to place the canvas + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - lineWidth; + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2;; + } + + var sx = sourcePos[0] < targetPos[0] ? w-xo : xo; + var sy = sourcePos[1] < targetPos[1] ? h-yo : yo; + var tx = sourcePos[0] < targetPos[0] ? xo : w-xo; + var ty = sourcePos[1] < targetPos[1] ? yo : h-yo; + var retVal = [ x, y, w, h, sx, sy, tx, ty ]; + + // return [canvasX, canvasY, canvasWidth, canvasHeight, + // sourceX, sourceY, targetX, targetY] + return retVal; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + }, + + /** + * This Connector draws a Bezier curve with two control points. + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + Bezier : function(curviness) { + + var self = this; + this.majorAnchor = curviness || 150; + this.minorAnchor = 10; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var perpendicular = sourceAnchor.orientation[0] != targetAnchor.orientation[0] || sourceAnchor.orientation[1] == targetAnchor.orientation[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (sourceAnchor.orientation[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * sourceAnchor.orientation[0])); + + if (sourceAnchor.orientation[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * targetAnchor.orientation[1])); + } + else { + if (targetAnchor.orientation[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * targetAnchor.orientation[0])); + + if (targetAnchor.orientation[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * sourceAnchor.orientation[1])); + } + + return p; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) + { + lineWidth = lineWidth || 0; + var w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth, h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + var canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2), canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + var sx = sourcePos[0] < targetPos[0] ? w - (lineWidth/2): (lineWidth/2), sy = sourcePos[1] < targetPos[1] ? h-(lineWidth/2) : (lineWidth/2); + var tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : w-(lineWidth/2), ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : h-(lineWidth/2); + var CP = self._findControlPoint([sx,sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + var CP2 = self._findControlPoint([tx,ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(sx,tx); var minx2 = Math.min(CP[0], CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(sx,tx); var maxx2 = Math.max(CP[0], CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > w) w = maxx; + if (minx < 0) { + canvasX += minx; var ox = Math.abs(minx); + w += ox; CP[0] += ox; sx += ox; tx +=ox; CP2[0] += ox; + } + + var miny1 = Math.min(sy,ty); var miny2 = Math.min(CP[1], CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(sy,ty); var maxy2 = Math.max(CP[1], CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > h) h = maxy; + if (miny < 0) { + canvasY += miny; var oy = Math.abs(miny); + h += oy; CP[1] += oy; sy += oy; ty +=oy; CP2[1] += oy; + } + + // return [ canvasx, canvasy, canvasWidth, canvasHeight, + // sourceX, sourceY, targetX, targetY, + // controlPoint1_X, controlPoint1_Y, controlPoint2_X, controlPoint2_Y + return [canvasX, canvasY, w, h, sx, sy, tx, ty, CP[0], CP[1], CP2[0], CP2[1] ]; + }; + + this.paint = function(d, ctx) { + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + } + } + }, + + + /** + * Types of endpoint UIs. we supply three - a circle of default radius 10px, a rectangle of + * default size 20x20, and an image (with no default). you can supply others of these if you want to - see the documentation + * for a howto. + */ + Endpoints : { + + /** + * a round endpoint, with default radius 10 pixels. + */ + Dot : function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + } + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = {}; + applyPaintStyle(style, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + applyPaintStyle(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }, + + /** + * A Rectangular endpoint, with default size 20x20. + */ + Rectangle : function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + //todo: the fillStyle needs some thought. we want to support a few options: + // 1. nothing supplied; use the stroke color or the default if no stroke color. + // 2. a fill color supplied - use it + // 3. a gradient supplied - use it + // 4. setting the endpoint to the same color as the bg of the element it is attached to. + var style = {}; + applyPaintStyle(style, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + applyPaintStyle(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }, + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' property in the params object.. + */ + Image : function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + } + }, + + /** + * establishes a connection between two elements. + * @param params object containing setup for the connection. see documentation. + */ + connect : function(params) { + var jpc = new jsPlumbConnection(params); + var key = jpc.sourceId + "_" + jpc.targetId; + connections[key] = jpc; + var addToList = function(elId, jpc) { + var l = connections[elId]; + if (l == null) { + l = []; + connections[elId] = l; + } + l.push(jpc); + }; + + // register this connection. + addToList(jpc.sourceId, jpc); + addToList(jpc.targetId, jpc); + }, + + /** + * Remove one connection to an element. + * @param sourceId id of the first window in the connection + * @param targetId id of the second window in the connection + */ + detach : function(sourceId, targetId) { + var jpcs = connections[sourceId]; + var idx = -1; + for (var i = 0; i < jpcs.length; i++) { + if ((jpcs[i].sourceId == sourceId && jpcs[i].targetId == targetId) || (jpcs[i].targetId == sourceId && jpcs[i].sourceId == targetId)) { + _removeCanvas(jpcs[i].canvas); + if (jpcs[i].drawEndpoints) { + _removeCanvas(jpcs[i].targetEndpointCanvas); + _removeCanvas(jpcs[i].sourceEndpointCanvas); + } + idx = i; + break; + } + } + if (idx != -1) + jpcs.splice(idx, 1); + + // todo - dragging? if no more connections for an object turn off dragging by default, but + // allow an override on it? + }, + + /** + * remove all an element's connections. + */ + detachAll : function(elId) { + var jpcs = connections[elId]; + for (var i = 0; i < jpcs.length; i++) { + _removeCanvas(jpcs[i].canvas); + if (jpcs[i].drawEndpoints) { + _removeCanvas(jpcs[i].targetEndpointCanvas); + _removeCanvas(jpcs[i].sourceEndpointCanvas); + } + } + delete connections[elId]; + connections[elId] = []; + }, + + /** + * remove all connections. + */ + detachEverything : function() { + for (var elId in connections) { + var jpcs = connections[elId]; + if (jpcs.length) { + try { + for (var i = 0; i < jpcs.length; i++) { + + _removeCanvas(jpcs[i].canvas); + if (jpcs[i].drawEndpoints) { + _removeCanvas(jpcs[i].targetEndpointCanvas); + _removeCanvas(jpcs[i].sourceEndpointCanvas); + } + + } + } catch (e) { } + } + } + delete connections; + connections = []; + }, + + getConnections : function(elId) { + return connections[elId]; + }, + + /** + * Set an element's connections to be hidden. + */ + hide : function(elId) { + _setVisible(elId, "none"); + }, + + /** + * Creates an anchor with the given params. + * x - the x location of the anchor as a percentage of the total width. + * y - the y location of the anchor as a percentage of the total height. + * orientation - an [x,y] array indicating the general direction a connection from the anchor should go in. + * offsets - an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. optional. defaults to [0,0]. + */ + makeAnchor : function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed in but that makes the call very verbose. easier to use + // by just passing in four values. but for backwards compatibility if we are given only one value we assume it's a call in the old form. + var params = {}; + if (arguments.length == 1) $.extend(params, x); + else { + params = {x:x, y:y}; + if (arguments.length >= 4) { + params.orientation = [arguments[2], arguments[3]]; + } + if (arguments.length == 6) params.offsets = [arguments[4], arguments[5]]; + } + return new Anchor(params); + }, + + + /** + * repaint element and its connections. element may be an id or the actual jQuery object. + * this method gets new sizes for the elements before painting anything. + */ + repaint : function(el) { + var _repaint = function(el, elId) { + var jpcs = connections[elId]; + var idx = -1; + var loc = {'absolutePosition': el.offset()}; + for (var i = 0; i < jpcs.length; i++) { + jpcs[i].paint(elId, loc, true); + } + }; + + var _processElement = function(el) { + var ele = typeof(el)=='string' ? $("#" + el) : el; + var eleId = ele.attr("id"); + _repaint(ele, eleId); + }; + + // TODO: support a jQuery result object too! + + // support both lists... + if (typeof el =='object') { + for (var i = 0; i < el.length; i++) + _processElement(el[i]); + } // ...and single strings. + else _processElement(el); + }, + + /** + * repaint all connections. + */ + repaintEverything : function() { + for (var elId in connections) { + var jpcs = connections[elId]; + if (jpcs.length) { + try { + for (var i = 0; i < jpcs.length; i++) { + jpcs[i].repaint(); + } + } catch (e) { } + } + } + }, + + /** + * sets/unsets automatic repaint on window resize. + */ + setAutomaticRepaint : function(value) { + automaticRepaint = value; + }, + + /** + * Sets the default size jsPlumb will use for a new canvas (we create a square canvas so + * one value is all that is required). This is a hack for IE, because ExplorerCanvas seems + * to need for a canvas to be larger than what you are going to draw on it at initialisation + * time. The default value of this is 1200 pixels, which is quite large, but if for some + * reason you're drawing connectors that are bigger, you should adjust this value appropriately. + */ + setDefaultNewCanvasSize : function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }, + + /** + * Sets whether or not a given element is draggable, regardless of what any plumb command + * may request. + */ + setDraggable: function(element, draggable) { + _setDraggable(element, draggable); + }, + + /** + * Sets whether or not elements are draggable by default. Default for this is true. + */ + setDraggableByDefault: function(draggable) { + _draggableByDefault = draggable; + }, + + /** + * Sets the function to fire when the window size has changed and a repaint was fired. + */ + setRepaintFunction : function(f) { + repaintFunction = f; + }, + + /** + * Set an element's connections to be visible. + */ + show : function(elId) { + _setVisible(elId, "block"); + }, + + /** + * helper to size a canvas. + */ + sizeCanvas : function(canvas, x, y, w, h) { + canvas.style.height = h + "px"; canvas.height = h; + canvas.style.width = w + "px"; canvas.width = w; + canvas.style.left = x + "px"; canvas.style.top = y + "px"; + }, + + /** + * Toggles visibility of an element's connections. + */ + toggle : function(elId) { + var jpcs = connections[elId]; + if (jpcs.length > 0) + _setVisible(elId, "none" == jpcs[0].canvas.style.display ? "block" : "none"); + }, + + /** + * Unloads jsPlumb, deleting all storage. You should call this + */ + unload : function() { + delete connections; + delete offsets; + delete sizes; + delete draggableStates; + } +}; + +// ************** connection +// **************************************** +/** +* allowed params: +* source: source element (string or a jQuery element) (required) +* target: target element (string or a jQuery element) (required) +* anchors: optional array of anchor placements. defaults to BottomCenter for source +* and TopCenter for target. +*/ +var jsPlumbConnection = function(params) { + +// ************** get the source and target and register the connection. ******************* + var self = this; + // get source and target as jQuery objects + this.source = (typeof params.source == 'string') ? $("#" + params.source) : params.source; + this.target = (typeof params.target == 'string') ? $("#" + params.target) : params.target; + this.sourceId = $(this.source).attr("id"); + this.targetId = $(this.target).attr("id"); + this.drawEndpoints = params.drawEndpoints != null ? params.drawEndpoints : true; + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + + // get anchor + this.anchors = params.anchors || jsPlumb.DEFAULT_ANCHORS || [jsPlumb.Anchors.BottomCenter, jsPlumb.Anchors.TopCenter]; + // make connector + this.connector = params.connector || jsPlumb.DEFAULT_CONNECTOR || new jsPlumb.Connectors.Bezier(); + this.paintStyle = params.paintStyle || jsPlumb.DEFAULT_PAINT_STYLE; + // init endpoints + this.endpoints = []; + if(!params.endpoints) params.endpoints = [null,null]; + this.endpoints[0] = params.endpoints[0] || params.endpoint || jsPlumb.DEFAULT_ENDPOINTS[0] || jsPlumb.DEFAULT_ENDPOINT || new jsPlumb.Endpoints.Dot(); + this.endpoints[1] = params.endpoints[1] || params.endpoint || jsPlumb.DEFAULT_ENDPOINTS[1] ||jsPlumb.DEFAULT_ENDPOINT || new jsPlumb.Endpoints.Dot(); + this.endpointStyles = []; + if (!params.endpointStyles) params.endpointStyles = [null,null]; + this.endpointStyles[0] = params.endpointStyles[0] || params.endpointStyle || jsPlumb.DEFAULT_ENDPOINT_STYLES[0] || jsPlumb.DEFAULT_ENDPOINT_STYLE; + this.endpointStyles[1] = params.endpointStyles[1] || params.endpointStyle || jsPlumb.DEFAULT_ENDPOINT_STYLES[1] || jsPlumb.DEFAULT_ENDPOINT_STYLE; + + offsets[this.sourceId] = this.source.offset(); + sizes[this.sourceId] = [this.source.outerWidth(), this.source.outerHeight()]; + offsets[this.targetId] = this.target.offset(); + sizes[this.targetId] = [this.target.outerWidth(), this.target.outerHeight()]; + +// *************** create canvases on which the connection will be drawn ************ + var canvas = _newCanvas(jsPlumb.connectorClass); + this.canvas = canvas; + // create endpoint canvases + if (this.drawEndpoints) { + this.sourceEndpointCanvas = _newCanvas(jsPlumb.endpointClass); + this.targetEndpointCanvas = _newCanvas(jsPlumb.endpointClass); + // sit them on top of the underlying element? + var setZIndex = function(canvas, source, above) { + var adj = above ? 1 : -1; + var zIndex = $(source).css("zIndex"); + $(canvas).css("zIndex", zIndex != "auto" ? zIndex + adj : "auto"); + }; + + setZIndex(this.sourceEndpointCanvas, this.source, this.endpointsOnTop); + setZIndex(this.targetEndpointCanvas, this.target, this.endpointsOnTop); + } +// ************** store the anchors + + /** + * paints the connection. + * @param elId Id of the element that is in motion + * @param ui jQuery's event system ui object (present if we came from a drag to get here) + * @param recalc whether or not to recalculate element sizes. this is true if a repaint caused this to be painted. + */ + this.paint = function(elId, ui, recalc) { + // if the moving object is not the source we must transpose the two references. + var swap = !(elId == this.sourceId); + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + + if (this.canvas.getContext) { + + if (recalc) { + // get the current sizes of the two elements. + var s = $("#" + elId); + var t = $("#" + tId); + sizes[elId] = [s.outerWidth(), s.outerHeight()]; + sizes[tId] = [t.outerWidth(), t.outerHeight()]; + offsets[elId] = s.offset(); + offsets[tId] = t.offset(); + } else { + // faster to use the ui element if it was passed in. offset is a fallback. + // fix for change in 1.8 (absolutePosition renamed to offset). plugin is compatible with + // 1.8 and 1.7. + var pos = ui.absolutePosition || ui.offset; + var anOffset = ui != null ? pos : $("#" + elId).offset(); + offsets[elId] = anOffset; + } + + var myOffset = offsets[elId]; + var otherOffset = offsets[tId]; + var myWH = sizes[elId]; + var otherWH = sizes[tId]; + + var ctx = canvas.getContext('2d'); + var sAnchorP = this.anchors[sIdx].compute([myOffset.left, myOffset.top], myWH, [otherOffset.left, otherOffset.top], otherWH); + var sAnchorO = this.anchors[sIdx].orientation; + var tAnchorP = this.anchors[tIdx].compute([otherOffset.left, otherOffset.top], otherWH, [myOffset.left, myOffset.top], myWH); + var tAnchorO = this.anchors[tIdx].orientation; + var dim = this.connector.compute(sAnchorP, tAnchorP, this.anchors[sIdx], this.anchors[tIdx], this.paintStyle.lineWidth); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + applyPaintStyle(ctx, this.paintStyle); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (this.paintStyle.gradient && !ie) { + var g = swap ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + for (var i = 0; i < this.paintStyle.gradient.stops.length; i++) + g.addColorStop(this.paintStyle.gradient.stops[i][0],this.paintStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + + this.connector.paint(dim, ctx); + + if (this.drawEndpoints) { + var style = this.endpointStyle || this.paintStyle; + var sourceCanvas = swap ? this.targetEndpointCanvas : this.sourceEndpointCanvas; + var targetCanvas = swap ? this.sourceEndpointCanvas : this.targetEndpointCanvas; + this.endpoints[swap ? 1 : 0].paint(sAnchorP, sAnchorO, sourceCanvas, this.endpointStyles[swap ? 1 : 0] || this.paintStyle, this.paintStyle); + this.endpoints[swap ? 0 : 1].paint(tAnchorP, tAnchorO, targetCanvas, this.endpointStyles[swap ? 0 : 1] || this.paintStyle, this.paintStyle); + } + } + }; + + this.repaint = function() { + this.paint(this.sourceId, null, true); + }; + + // dragging + var draggable = params.draggable == null ? _draggableByDefault : params.draggable; + if (draggable && self.source.draggable) { + var dragOptions = params.dragOptions || jsPlumb.DEFAULT_DRAG_OPTIONS; + var dragCascade = dragOptions.drag || function(e,u) {}; + var initDrag = function(element, dragFunc) { + var opts = $.extend({drag:dragFunc}, dragOptions); + var draggable = draggableStates[element.attr("id")]; + opts.disabled = draggable == null ? false : !draggable; + element.draggable(opts); + }; + initDrag(this.source, function(event, ui) { + _draw(self.source, ui); + dragCascade(event, ui); + }); + initDrag(this.target, function(event, ui) { + _draw(self.target, ui); + dragCascade(event, ui); + }); + } + + // resizing (using the jquery.ba-resize plugin). todo: decide whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + + // finally, draw it. + var o = this.source.offset(); + this.paint(this.sourceId, {'absolutePosition': this.source.offset()}); +}; + +})(); + +// jQuery plugin code +(function($){ + $.fn.plumb = function(options) { + var defaults = { }; + var options = $.extend(defaults, options); + + return this.each(function() + { + var obj = $(this); + var params = {}; + params.source = obj; + for (var i in options) { + params[i] = options[i]; + } + jsPlumb.connect(params); + }); + }; + + $.fn.detach = function(options) { + return this.each(function() + { + var id = $(this).attr("id"); + if (typeof options == 'string') options = [options]; + for (var i = 0; i < options.length; i++) + jsPlumb.detach(id, options[i]); + }); + }; + + $.fn.detachAll = function(options) { + return this.each(function() + { + var id = $(this).attr("id"); + jsPlumb.detachAll(id); + }); + }; +})(jQuery); diff --git a/archive/jquery.jsPlumb-1.0.4-min.js b/archive/jquery.jsPlumb-1.0.4-min.js new file mode 100644 index 000000000..de166b54d --- /dev/null +++ b/archive/jquery.jsPlumb-1.0.4-min.js @@ -0,0 +1,25 @@ +if(!Array.prototype.indexOf)Array.prototype.indexOf=function(s,n,w){n=+n||0;for(var r=this.length;ng)g=f;if(c<0){j+=c;c=Math.abs(c);g+=c;p[0]+=c;k+=c;t+=c;b[0]+=c}c=Math.min(Math.min(q,e),Math.min(p[1], +b[1]));f=Math.max(Math.max(q,e),Math.max(p[1],b[1]));if(f>i)i=f;if(c<0){m+=c;c=Math.abs(c);i+=c;p[1]+=c;q+=c;e+=c;b[1]+=c}return[j,m,g,i,k,q,t,e,p[0],p[1],b[0],b[1]]};this.paint=function(b,c){c.beginPath();c.moveTo(b[4],b[5]);c.bezierCurveTo(b[8],b[9],b[10],b[11],b[6],b[7]);c.stroke()}}},Endpoints:{Dot:function(a){a=a||{radius:10};var d=this;this.radius=a.radius;var b=0.5*this.radius,c=this.radius/3,f=function(h){try{return parseInt(h)}catch(e){if(h.substring(h.length-1)=="%")return parseInt(h.substring(0, +h-1))}};this.paint=function(h,e,g,i,j){var m=i.radius||d.radius;l.sizeCanvas(g,h[0]-m,h[1]-m,m*2,m*2);h=g.getContext("2d");g={};$.extend(g,i);if(g.fillStyle==null)g.fillStyle=j.strokeStyle;$.extend(h,g);j=/MSIE/.test(navigator.userAgent)&&!window.opera;if(i.gradient&&!j){j=i.gradient;g=b;var k=c;if(j.offset)g=f(j.offset);if(j.innerRadius)k=f(j.innerRadius);j=[g,k];e=h.createRadialGradient(m,m,m,m+(e[0]==1?j[0]*-1:j[0]),m+(e[1]==1?j[0]*-1:j[0]),j[1]);for(j=0;j=4)b.orientation=[arguments[2],arguments[3]];if(arguments.length==6)b.offsets=[arguments[4],arguments[5]]}return new u(b)},repaint:function(a){var d=function(c){var f=typeof c=="string"?$("#"+c):c;c=f.attr("id");var h=o[c];f={absolutePosition:f.offset()};for(var e=0;e0)B(a,"none"==d[0].canvas.style.display? +"block":"none")},unload:function(){delete o;delete x;delete y;delete z}},H=function(a){var d=this;this.source=typeof a.source=="string"?$("#"+a.source):a.source;this.target=typeof a.target=="string"?$("#"+a.target):a.target;this.sourceId=$(this.source).attr("id");this.targetId=$(this.target).attr("id");this.drawEndpoints=a.drawEndpoints!=null?a.drawEndpoints:true;this.endpointsOnTop=a.endpointsOnTop!=null?a.endpointsOnTop:true;this.anchors=a.anchors||l.DEFAULT_ANCHORS||[l.Anchors.BottomCenter,l.Anchors.TopCenter]; +this.connector=a.connector||l.DEFAULT_CONNECTOR||new l.Connectors.Bezier;this.paintStyle=a.paintStyle||l.DEFAULT_PAINT_STYLE;this.endpoints=[];if(!a.endpoints)a.endpoints=[null,null];this.endpoints[0]=a.endpoints[0]||a.endpoint||l.DEFAULT_ENDPOINTS[0]||l.DEFAULT_ENDPOINT||new l.Endpoints.Dot;this.endpoints[1]=a.endpoints[1]||a.endpoint||l.DEFAULT_ENDPOINTS[1]||l.DEFAULT_ENDPOINT||new l.Endpoints.Dot;this.endpointStyles=[];if(!a.endpointStyles)a.endpointStyles=[null,null];this.endpointStyles[0]=a.endpointStyles[0]|| +a.endpointStyle||l.DEFAULT_ENDPOINT_STYLES[0]||l.DEFAULT_ENDPOINT_STYLE;this.endpointStyles[1]=a.endpointStyles[1]||a.endpointStyle||l.DEFAULT_ENDPOINT_STYLES[1]||l.DEFAULT_ENDPOINT_STYLE;x[this.sourceId]=this.source.offset();y[this.sourceId]=[this.source.outerWidth(),this.source.outerHeight()];x[this.targetId]=this.target.offset();y[this.targetId]=[this.target.outerWidth(),this.target.outerHeight()];var b=C(l.connectorClass);this.canvas=b;if(this.drawEndpoints){this.sourceEndpointCanvas=C(l.endpointClass); +this.targetEndpointCanvas=C(l.endpointClass);var c=function(e,g,i){i=i?1:-1;g=$(g).css("zIndex");$(e).css("zIndex",g!="auto"?g+i:"auto")};c(this.sourceEndpointCanvas,this.source,this.endpointsOnTop);c(this.targetEndpointCanvas,this.target,this.endpointsOnTop)}this.paint=function(e,g,i){var j=e!=this.sourceId,m=j?this.sourceId:this.targetId,k=j?0:1,q=j?1:0;if(this.canvas.getContext){if(i){g=$("#"+e);i=$("#"+m);y[e]=[g.outerWidth(),g.outerHeight()];y[m]=[i.outerWidth(),i.outerHeight()];x[e]=g.offset(); +x[m]=i.offset()}else{i=g.absolutePosition||g.offset;g=g!=null?i:$("#"+e).offset();x[e]=g}i=x[e];var t=x[m],p=y[e],F=y[m];g=b.getContext("2d");m=this.anchors[q].compute([i.left,i.top],p,[t.left,t.top],F);e=this.anchors[q].orientation;i=this.anchors[k].compute([t.left,t.top],F,[i.left,i.top],p);t=this.anchors[k].orientation;k=this.connector.compute(m,i,this.anchors[q],this.anchors[k],this.paintStyle.lineWidth);l.sizeCanvas(b,k[0],k[1],k[2],k[3]);$.extend(g,this.paintStyle);q=/MSIE/.test(navigator.userAgent)&& +!window.opera;if(this.paintStyle.gradient&&!q){q=j?g.createLinearGradient(k[4],k[5],k[6],k[7]):g.createLinearGradient(k[6],k[7],k[4],k[5]);for(p=0;p w) w = maxx; + if (minx < 0) { + canvasX += minx; var ox = Math.abs(minx); + w += ox; CP[0] += ox; sx += ox; tx +=ox; CP2[0] += ox; + } + + var miny1 = Math.min(sy,ty); var miny2 = Math.min(CP[1], CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(sy,ty); var maxy2 = Math.max(CP[1], CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > h) h = maxy; + if (miny < 0) { + canvasY += miny; var oy = Math.abs(miny); + h += oy; CP[1] += oy; sy += oy; ty +=oy; CP2[1] += oy; + } + + // return [ canvasx, canvasy, canvasWidth, canvasHeight, + // sourceX, sourceY, targetX, targetY, + // controlPoint1_X, controlPoint1_Y, controlPoint2_X, controlPoint2_Y + return [canvasX, canvasY, w, h, sx, sy, tx, ty, CP[0], CP[1], CP2[0], CP2[1] ]; + }; + + this.paint = function(d, ctx) { + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + } + } + }, + + + /** + * Types of endpoint UIs. we supply three - a circle of default radius 10px, a rectangle of + * default size 20x20, and an image (with no default). you can supply others of these if you want to - see the documentation + * for a howto. + */ + Endpoints : { + + /** + * a round endpoint, with default radius 10 pixels. + */ + Dot : function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + } + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = {}; + applyPaintStyle(style, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + applyPaintStyle(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }, + + /** + * A Rectangular endpoint, with default size 20x20. + */ + Rectangle : function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + //todo: the fillStyle needs some thought. we want to support a few options: + // 1. nothing supplied; use the stroke color or the default if no stroke color. + // 2. a fill color supplied - use it + // 3. a gradient supplied - use it + // 4. setting the endpoint to the same color as the bg of the element it is attached to. + var style = {}; + applyPaintStyle(style, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + applyPaintStyle(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }, + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' property in the params object.. + */ + Image : function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + } + }, + + /** + * establishes a connection between two elements. + * @param params object containing setup for the connection. see documentation. + */ + connect : function(params) { + var jpc = new jsPlumbConnection(params); + var key = jpc.sourceId + "_" + jpc.targetId; + connections[key] = jpc; + var addToList = function(elId, jpc) { + var l = connections[elId]; + if (l == null) { + l = []; + connections[elId] = l; + } + l.push(jpc); + }; + + // register this connection. + addToList(jpc.sourceId, jpc); + addToList(jpc.targetId, jpc); + }, + + /** + * Remove one connection to an element. + * @param sourceId id of the first window in the connection + * @param targetId id of the second window in the connection + */ + detach : function(sourceId, targetId) { + var jpcs = connections[sourceId]; + var idx = -1; + for (var i = 0; i < jpcs.length; i++) { + if ((jpcs[i].sourceId == sourceId && jpcs[i].targetId == targetId) || (jpcs[i].targetId == sourceId && jpcs[i].sourceId == targetId)) { + _removeCanvas(jpcs[i].canvas); + if (jpcs[i].drawEndpoints) { + _removeCanvas(jpcs[i].targetEndpointCanvas); + _removeCanvas(jpcs[i].sourceEndpointCanvas); + } + idx = i; + break; + } + } + if (idx != -1) + jpcs.splice(idx, 1); + + // todo - dragging? if no more connections for an object turn off dragging by default, but + // allow an override on it? + }, + + /** + * remove all an element's connections. + */ + detachAll : function(elId) { + var jpcs = connections[elId]; + for (var i = 0; i < jpcs.length; i++) { + _removeCanvas(jpcs[i].canvas); + if (jpcs[i].drawEndpoints) { + _removeCanvas(jpcs[i].targetEndpointCanvas); + _removeCanvas(jpcs[i].sourceEndpointCanvas); + } + } + delete connections[elId]; + connections[elId] = []; + }, + + /** + * remove all connections. + */ + detachEverything : function() { + for (var elId in connections) { + var jpcs = connections[elId]; + if (jpcs.length) { + try { + for (var i = 0; i < jpcs.length; i++) { + + _removeCanvas(jpcs[i].canvas); + if (jpcs[i].drawEndpoints) { + _removeCanvas(jpcs[i].targetEndpointCanvas); + _removeCanvas(jpcs[i].sourceEndpointCanvas); + } + + } + } catch (e) { } + } + } + delete connections; + connections = []; + }, + + getConnections : function(elId) { + return connections[elId]; + }, + + /** + * Set an element's connections to be hidden. + */ + hide : function(elId) { + _setVisible(elId, "none"); + }, + + /** + * Creates an anchor with the given params. + * x - the x location of the anchor as a percentage of the total width. + * y - the y location of the anchor as a percentage of the total height. + * orientation - an [x,y] array indicating the general direction a connection from the anchor should go in. + * offsets - an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. optional. defaults to [0,0]. + */ + makeAnchor : function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed in but that makes the call very verbose. easier to use + // by just passing in four values. but for backwards compatibility if we are given only one value we assume it's a call in the old form. + var params = {}; + if (arguments.length == 1) $.extend(params, x); + else { + params = {x:x, y:y}; + if (arguments.length >= 4) { + params.orientation = [arguments[2], arguments[3]]; + } + if (arguments.length == 6) params.offsets = [arguments[4], arguments[5]]; + } + return new Anchor(params); + }, + + + /** + * repaint element and its connections. element may be an id or the actual jQuery object. + * this method gets new sizes for the elements before painting anything. + */ + repaint : function(el) { + var _repaint = function(el, elId) { + var jpcs = connections[elId]; + var idx = -1; + var loc = {'absolutePosition': el.offset()}; + for (var i = 0; i < jpcs.length; i++) { + jpcs[i].paint(elId, loc, true); + } + }; + + var _processElement = function(el) { + var ele = typeof(el)=='string' ? $("#" + el) : el; + var eleId = ele.attr("id"); + _repaint(ele, eleId); + }; + + // TODO: support a jQuery result object too! + + // support both lists... + if (typeof el =='object') { + for (var i = 0; i < el.length; i++) + _processElement(el[i]); + } // ...and single strings. + else _processElement(el); + }, + + /** + * repaint all connections. + */ + repaintEverything : function() { + for (var elId in connections) { + var jpcs = connections[elId]; + if (jpcs.length) { + try { + for (var i = 0; i < jpcs.length; i++) { + jpcs[i].repaint(); + } + } catch (e) { } + } + } + }, + + /** + * sets/unsets automatic repaint on window resize. + */ + setAutomaticRepaint : function(value) { + automaticRepaint = value; + }, + + /** + * Sets the default size jsPlumb will use for a new canvas (we create a square canvas so + * one value is all that is required). This is a hack for IE, because ExplorerCanvas seems + * to need for a canvas to be larger than what you are going to draw on it at initialisation + * time. The default value of this is 1200 pixels, which is quite large, but if for some + * reason you're drawing connectors that are bigger, you should adjust this value appropriately. + */ + setDefaultNewCanvasSize : function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }, + + /** + * Sets whether or not a given element is draggable, regardless of what any plumb command + * may request. + */ + setDraggable: function(element, draggable) { + _setDraggable(element, draggable); + }, + + /** + * Sets whether or not elements are draggable by default. Default for this is true. + */ + setDraggableByDefault: function(draggable) { + _draggableByDefault = draggable; + }, + + /** + * Sets the function to fire when the window size has changed and a repaint was fired. + */ + setRepaintFunction : function(f) { + repaintFunction = f; + }, + + /** + * Set an element's connections to be visible. + */ + show : function(elId) { + _setVisible(elId, "block"); + }, + + /** + * helper to size a canvas. + */ + sizeCanvas : function(canvas, x, y, w, h) { + canvas.style.height = h + "px"; canvas.height = h; + canvas.style.width = w + "px"; canvas.width = w; + canvas.style.left = x + "px"; canvas.style.top = y + "px"; + }, + + /** + * Toggles visibility of an element's connections. + */ + toggle : function(elId) { + var jpcs = connections[elId]; + if (jpcs.length > 0) + _setVisible(elId, "none" == jpcs[0].canvas.style.display ? "block" : "none"); + }, + + /** + * Unloads jsPlumb, deleting all storage. You should call this + */ + unload : function() { + delete connections; + delete offsets; + delete sizes; + delete draggableStates; + } +}; + +// ************** connection +// **************************************** +/** +* allowed params: +* source: source element (string or a jQuery element) (required) +* target: target element (string or a jQuery element) (required) +* anchors: optional array of anchor placements. defaults to BottomCenter for source +* and TopCenter for target. +*/ +var jsPlumbConnection = function(params) { + +// ************** get the source and target and register the connection. ******************* + var self = this; + // get source and target as jQuery objects + this.source = (typeof params.source == 'string') ? $("#" + params.source) : params.source; + this.target = (typeof params.target == 'string') ? $("#" + params.target) : params.target; + this.sourceId = $(this.source).attr("id"); + this.targetId = $(this.target).attr("id"); + this.drawEndpoints = params.drawEndpoints != null ? params.drawEndpoints : true; + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + + // get anchor + this.anchors = params.anchors || jsPlumb.DEFAULT_ANCHORS || [jsPlumb.Anchors.BottomCenter, jsPlumb.Anchors.TopCenter]; + // make connector + this.connector = params.connector || jsPlumb.DEFAULT_CONNECTOR || new jsPlumb.Connectors.Bezier(); + this.paintStyle = params.paintStyle || jsPlumb.DEFAULT_PAINT_STYLE; + // init endpoints + this.endpoints = []; + if(!params.endpoints) params.endpoints = [null,null]; + this.endpoints[0] = params.endpoints[0] || params.endpoint || jsPlumb.DEFAULT_ENDPOINTS[0] || jsPlumb.DEFAULT_ENDPOINT || new jsPlumb.Endpoints.Dot(); + this.endpoints[1] = params.endpoints[1] || params.endpoint || jsPlumb.DEFAULT_ENDPOINTS[1] ||jsPlumb.DEFAULT_ENDPOINT || new jsPlumb.Endpoints.Dot(); + this.endpointStyles = []; + if (!params.endpointStyles) params.endpointStyles = [null,null]; + this.endpointStyles[0] = params.endpointStyles[0] || params.endpointStyle || jsPlumb.DEFAULT_ENDPOINT_STYLES[0] || jsPlumb.DEFAULT_ENDPOINT_STYLE; + this.endpointStyles[1] = params.endpointStyles[1] || params.endpointStyle || jsPlumb.DEFAULT_ENDPOINT_STYLES[1] || jsPlumb.DEFAULT_ENDPOINT_STYLE; + + offsets[this.sourceId] = this.source.offset(); + sizes[this.sourceId] = [this.source.outerWidth(), this.source.outerHeight()]; + offsets[this.targetId] = this.target.offset(); + sizes[this.targetId] = [this.target.outerWidth(), this.target.outerHeight()]; + +// *************** create canvases on which the connection will be drawn ************ + var canvas = _newCanvas(jsPlumb.connectorClass); + this.canvas = canvas; + // create endpoint canvases + if (this.drawEndpoints) { + this.sourceEndpointCanvas = _newCanvas(jsPlumb.endpointClass); + this.targetEndpointCanvas = _newCanvas(jsPlumb.endpointClass); + // sit them on top of the underlying element? + var setZIndex = function(canvas, source, above) { + var adj = above ? 1 : -1; + var zIndex = $(source).css("zIndex"); + $(canvas).css("zIndex", zIndex != "auto" ? zIndex + adj : "auto"); + }; + + setZIndex(this.sourceEndpointCanvas, this.source, this.endpointsOnTop); + setZIndex(this.targetEndpointCanvas, this.target, this.endpointsOnTop); + } +// ************** store the anchors + + /** + * paints the connection. + * @param elId Id of the element that is in motion + * @param ui jQuery's event system ui object (present if we came from a drag to get here) + * @param recalc whether or not to recalculate element sizes. this is true if a repaint caused this to be painted. + */ + this.paint = function(elId, ui, recalc) { + // if the moving object is not the source we must transpose the two references. + var swap = !(elId == this.sourceId); + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + + if (this.canvas.getContext) { + + if (recalc) { + // get the current sizes of the two elements. + var s = $("#" + elId); + var t = $("#" + tId); + sizes[elId] = [s.outerWidth(), s.outerHeight()]; + sizes[tId] = [t.outerWidth(), t.outerHeight()]; + offsets[elId] = s.offset(); + offsets[tId] = t.offset(); + } else { + // faster to use the ui element if it was passed in. offset is a fallback. + // fix for change in 1.8 (absolutePosition renamed to offset). plugin is compatible with + // 1.8 and 1.7. + var pos = ui.absolutePosition || ui.offset; + var anOffset = ui != null ? pos : $("#" + elId).offset(); + offsets[elId] = anOffset; + } + + var myOffset = offsets[elId]; + var otherOffset = offsets[tId]; + var myWH = sizes[elId]; + var otherWH = sizes[tId]; + + var ctx = canvas.getContext('2d'); + var sAnchorP = this.anchors[sIdx].compute([myOffset.left, myOffset.top], myWH, [otherOffset.left, otherOffset.top], otherWH); + var sAnchorO = this.anchors[sIdx].orientation; + var tAnchorP = this.anchors[tIdx].compute([otherOffset.left, otherOffset.top], otherWH, [myOffset.left, myOffset.top], myWH); + var tAnchorO = this.anchors[tIdx].orientation; + var dim = this.connector.compute(sAnchorP, tAnchorP, this.anchors[sIdx], this.anchors[tIdx], this.paintStyle.lineWidth); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + applyPaintStyle(ctx, this.paintStyle); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (this.paintStyle.gradient && !ie) { + var g = swap ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + for (var i = 0; i < this.paintStyle.gradient.stops.length; i++) + g.addColorStop(this.paintStyle.gradient.stops[i][0],this.paintStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + + this.connector.paint(dim, ctx); + + if (this.drawEndpoints) { + var style = this.endpointStyle || this.paintStyle; + var sourceCanvas = swap ? this.targetEndpointCanvas : this.sourceEndpointCanvas; + var targetCanvas = swap ? this.sourceEndpointCanvas : this.targetEndpointCanvas; + this.endpoints[swap ? 1 : 0].paint(sAnchorP, sAnchorO, sourceCanvas, this.endpointStyles[swap ? 1 : 0] || this.paintStyle, this.paintStyle); + this.endpoints[swap ? 0 : 1].paint(tAnchorP, tAnchorO, targetCanvas, this.endpointStyles[swap ? 0 : 1] || this.paintStyle, this.paintStyle); + } + } + }; + + this.repaint = function() { + this.paint(this.sourceId, null, true); + }; + + // dragging + var draggable = params.draggable == null ? _draggableByDefault : params.draggable; + if (draggable && self.source.draggable) { + var dragOptions = params.dragOptions || jsPlumb.DEFAULT_DRAG_OPTIONS; + var dragCascade = dragOptions.drag || function(e,u) {}; + var initDrag = function(element, dragFunc) { + var opts = $.extend({drag:dragFunc}, dragOptions); + var draggable = draggableStates[element.attr("id")]; + opts.disabled = draggable == null ? false : !draggable; + element.draggable(opts); + }; + initDrag(this.source, function(event, ui) { + _draw(self.source, ui); + dragCascade(event, ui); + }); + initDrag(this.target, function(event, ui) { + _draw(self.target, ui); + dragCascade(event, ui); + }); + } + + // resizing (using the jquery.ba-resize plugin). todo: decide whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + + // finally, draw it. + var o = this.source.offset(); + this.paint(this.sourceId, {'absolutePosition': this.source.offset()}); +}; + +})(); + +// jQuery plugin code +(function($){ + $.fn.plumb = function(options) { + var defaults = { }; + var options = $.extend(defaults, options); + + return this.each(function() + { + var obj = $(this); + var params = {}; + params.source = obj; + for (var i in options) { + params[i] = options[i]; + } + jsPlumb.connect(params); + }); + }; + + $.fn.detach = function(options) { + return this.each(function() + { + var id = $(this).attr("id"); + if (typeof options == 'string') options = [options]; + for (var i = 0; i < options.length; i++) + jsPlumb.detach(id, options[i]); + }); + }; + + $.fn.detachAll = function(options) { + return this.each(function() + { + var id = $(this).attr("id"); + jsPlumb.detachAll(id); + }); + }; +})(jQuery); diff --git a/archive/jquery.jsPlumb-1.1.0-RC1.js b/archive/jquery.jsPlumb-1.1.0-RC1.js new file mode 100644 index 000000000..d83633d76 --- /dev/null +++ b/archive/jquery.jsPlumb-1.1.0-RC1.js @@ -0,0 +1,1277 @@ +/* + * jsPlumb 1.1.0-RC1 + * + * Provides a way to visually connect elements on an HTML page. + * + * 1.1.0-RC1 contains the first pass at supporting connection editing. the mechanism used to register + * and retrieve connections has been turned around to be endpoint centric, rather than + * connection centric. this will allow us to create endpoints that have 0-N connections. + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + */ +if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function( v, b, s ) { + for( var i = +b || 0, l = this.length; i < l; i++ ) { + if( this[i]===v || s && this[i]==v ) { return i; } + } + return -1; + }; +} +(function() { + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + + var log = null; + + var repaintFunction = function() { jsPlumb.repaintEverything(); }; + var automaticRepaint = true; + function repaintEverything() { + if (automaticRepaint) + repaintFunction(); + }; + var resizeTimer = null; + $(window).bind('resize', function() { + if (resizeTimer) clearTimeout(resizeTimer); + resizeTimer = setTimeout(repaintEverything, 100); + }); + + /** + * map of element id -> endpoint lists. an element can have an arbitrary number of endpoints on it, + * and not all of them have to be connected to anything. + */ + var endpointsByElement = {}; + var offsets = []; + var floatingConnections = {}; + var draggableStates = {}; + var _draggableByDefault = true; + var sizes = []; + var _jsPlumbContextNode = null; + + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + /** + * helper method to add an item to a list, creating the list if it does not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + }; + + /** + * Handles the dragging of an element. + * @param element jQuery element + * @param ui UI object from jQuery's event system + */ + var _draw = function(element, ui) { + var id = $(element).attr("id"); + var endpoints = endpointsByElement[id]; + if (endpoints) { + //if (ui == null) _updateOffset(id, ui); + _updateOffset(id, ui); + var myOffset = offsets[id]; + var myWH = sizes[id]; + // loop through endpoints for this element + for (var i = 0; i < endpoints.length; i++) { + var e = endpoints[i]; + // first, paint the endpoint + + var anchorLoc = endpoints[i].anchor.compute([myOffset.left, myOffset.top], myWH); + // var anchorOrientation = endpoints[i].anchor.getOrientation(); + + //todo...connector paint style? we have lost that with the move to endpoint-centric. + // perhaps we only paint the endpoint here if it has no connections; it can use its own style. + //if (!e.connections || e.connections.length == 0) + e.paint(anchorLoc); + + // else { + //if (e.connections && e.connections.length > 0) { + // get all connections for the endpoint... + var l = e.connections; + for (var j = 0; j < l.length; j++) + l[j].paint(id, ui); // ...and paint them. + //} + } + } + }; + + /** + * helper function: the second argument is a function taking two args - the first is a + * jquery element, and the second is the element's id. + * + * the first argument may be one of three things: + * + * 1. a string, in the form "window1", for example. an element's id. if your id starts with a + * hash then jsPlumb does not append its own hash too... + * 2. a jquery element, already resolved using $(...). + * 3. a list of strings/jquery elements. + */ + var _elementProxy = function(element, fn) { + var retVal = null; + if (typeof element == 'object' && element.length) { + retVal = []; + for (var i = 0; i < element.length; i++) { + var el = typeof element[i] == 'string' ? $("#" + element[i]) : element[i]; + var id = el.attr("id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } + else { + var el = typeof element == 'string' ? + element.indexOf("#") == 0 ? $(element) : $("#" + element) + : element; + var id = el.attr("id"); + retVal = fn(el, id); + } + + return retVal; + }; + + /** + * Returns (creating if necessary) the DIV element that jsPlumb uses as the context for all of its + * canvases. having this makes it possible to makes calls like $("selector", context), which are + * faster than if you provide no context. also we can clear out everything easily like this, either + * on a detachEverything() call or during unload(). + */ + var _getContextNode = function() { + if (_jsPlumbContextNode == null) { + _jsPlumbContextNode= document.createElement("div"); + document.body.appendChild(_jsPlumbContextNode); + _jsPlumbContextNode.className = "_jsPlumb_context"; + } + return $(_jsPlumbContextNode); + }; + + /** + * gets an id for the given element, creating and setting one if necessary. + */ + var _getId = function(element) { + var id = $(element).attr("id"); + if (!id) { + id = "_jsPlumb_" + new String((new Date()).getTime()); + $(element).attr("id", id); + } + return id; + }; + + /** + * inits a draggable if it's not already initialised. + * todo: if the element was draggable already, like from some non-jsPlumb call, wrap the drag function. + */ + var _initDraggableIfNecessary = function(element, elementId, isDraggable, dragOptions) { + // dragging + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable && element.draggable) { + var options = dragOptions || jsPlumb.Defaults.DragOptions; + var dragCascade = options.drag || function(e,u) {}; + var initDrag = function(element, elementId, dragFunc) { + var opts = $.extend({drag:dragFunc}, options); + var draggable = draggableStates[elementId]; + opts.disabled = draggable == null ? false : !draggable; + element.draggable(opts); + }; + initDrag(element, elementId, function(event, ui) { + _draw(element, ui); + $(element).addClass("jsPlumb_dragged"); + dragCascade(event, ui); + }); + } + + }; + + var _log = function(msg) { + // not implemented. yet. + } + + /** + * helper to create a canvas. + * @param clazz optional class name for the canvas. + */ + var _newCanvas = function(clazz) { + var canvas = document.createElement("canvas"); + _getContextNode().append(canvas); + canvas.style.position="absolute"; + if (clazz) { canvas.className=clazz; } + + if (/MSIE/.test(navigator.userAgent) && !window.opera) { + // for IE we have to set a big canvas size. actually you can override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + /** + * performs the given function operation on all the connections found for the given element + * id; this means we find all the endpoints for the given element, and then for each endpoint + * find the connectors connected to it. then we pass each connection in to the given + * function. + */ + var _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + for (var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }; + /** + * perform an operation on all elements. + */ + var _operationOnAll = function(func) { + for (var elId in endpointsByElement) { + _operation(elId, func); + } + }; + /** + * helper to remove an element from the DOM. + */ + var _removeElement = function(element) { + if (element != null) { + try { _jsPlumbContextNode.removeChild(element); } + catch (e) { } + } + }; + /** + * helper to remove a list of elements from the DOM. + */ + var _removeElements = function(elements) { + for (var i in elements) + _removeElement(elements[i]); + }; + /** + * helper method to remove an item from a list. + */ + var _removeFromList = function(map, key, value) { + var l = map[key]; + if (l != null) { + var i = l.indexOf(value); + if (i >= 0) { + delete( l[i] ); + l.splice( i, 1 ); + return true; + } + } + return false; + }; + /** + * Sets whether or not the given element(s) should be draggable, regardless of what a particular + * plumb command may request. + * + * @param element May be a string, a jQuery elements, or a list of strings/jquery elements. + * @param draggable Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + var _helper = function(el, id) { + draggableStates[id] = draggable; + if (el.draggable) { + el.draggable("option", "disabled", !draggable); + } + }; + + return _elementProxy(element, _helper); + }; + /** + * private method to do the business of hiding/showing. + * @param elId Id of the element in question + * @param state String specifying a value for the css 'display' property ('block' or 'none'). + */ + var _setVisible = function(elId, state) { + var f = function(jpc) { + //todo should we find all the endpoints instead of going by connection? this will + jpc.canvas.style.display = state; + /*jpc.sourceEndpointCanvas.style.display = state; + jpc.targetEndpointCanvas.style.display = state;*/ + }; + + _operation(elId, f); + }; + /** + * toggles the draggable state of the element with the given id. + */ + var _toggleDraggable = function(el) { + var fn = function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + el.draggable("option", "disabled", !state); + return state; + }; + return _elementProxy(el, fn); + }; + /** + * private method to do the business of toggling hiding/showing. + * @param elId Id of the element in question + */ + var _toggleVisible = function(elId) { + var f = function(jpc) {; + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + /*jpc.sourceEndpointCanvas.style.display = state; + jpc.targetEndpointCanvas.style.display = state;*/ + }; + + _operation(elId, f); + }; + /** + * updates the offset and size for a given element, and stores the values. + * if 'ui' is not null we use that (it would have been passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we use the offset, outerWidth and outerHeight methods to get + * the current values. + */ + var _updateOffset = function(elId, ui, recalc) { + + if (log) log.debug("updating offset for element [" + elId + "]; ui is [" + ui + "]; recalc is [" + recalc + "]"); + + if (recalc || ui == null) { // if forced repaint or no ui helper available, we recalculate. + // get the current size and offset, and store them + var s = $("#" + elId); + sizes[elId] = [s.outerWidth(), s.outerHeight()]; + offsets[elId] = s.offset(); + } else { + // faster to use the ui element if it was passed in. + // fix for change in 1.8 (absolutePosition renamed to offset). plugin is compatible with + // 1.8 and 1.7. + + // todo: when the drag axis is supplied, the ui object passed in has incorrect values + // for the other axis - like say you set axis='x'. when you move the mouse up and down + // while dragging, the y values are for where the window would be if it was not + // just constrained to x. not sure if this is a jquery bug or whether there's a known + // trick or whatever. + var pos = ui.absolutePosition || ui.offset; + var anOffset = ui != null ? pos : $("#" + elId).offset(); + offsets[elId] = anOffset; + } + }; + /** + * wraps one function with another, creating a placeholder for the wrapped function + * if it was null. this is used to wrap the various drag/drop event functions - to allow + * jsPlumb to be notified of important lifecycle events without imposing itself on the user's + * drap/drop functionality. + * TODO: determine whether or not we should try/catch the plumb function, so that the cascade function is always executed. + */ + var _wrap = function(cascadeFunction, plumbFunction) { + cascadeFunction = cascadeFunction || function(e, ui) { }; + return function(e, ui) { + plumbFunction(e, ui); + cascadeFunction(e, ui); + }; + } + /** + * Anchor class. Anchors can be situated anywhere. + * params should contain three values, and may optionally have an 'offsets' argument: + * + * x : the x location of the anchor as a fraction of the total width. + * + * y : the y location of the anchor as a fraction of the total height. + * + * orientation : an [x,y] array indicating the general direction a connection + * from the anchor should go in. for more info on this, see the documentation, + * or the docs in jquery-jsPlumb-defaults-XXX.js for the default Anchors. + * + * offsets : an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. may be null. + * + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; this.y = params.y || 0; + var orientation = params.orientation || [0,0]; + this.offsets = params.offsets || [0,0]; + this.compute = function(xy, wh, txy, twh) { + return [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + } + this.getOrientation = function() { return orientation; }; + }; + + /** + * an anchor that floats. its orientation is computed dynamically from its position relative + * to the anchor it is floating relative to. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for purposes of calculating the orientation. + var ref = params.reference; + // these are used to store the current relative position of our anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating anchor is hovering over another anchor. + var orientation = null; + + this.compute = function(xy, wh, txy, twh) { + // set these for the getOrientation method to use. + xDir = 0;//xy[0] < txy[0] ? -1 : xy[0] == txy[0] ? 0 : 1; + yDir = 0;//xy[1] < txy[1] ? -1 : xy[1] == txy[1] ? 0 : 1; + return [xy[0], xy[1]]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other anchor: if it declares zero for some direction, we declare zero too. + // this might not be the most awesome. perhaps we can come up with a better way. it's just so that the line we draw looks + // like it makes sense. maybe this wont make sense. + return [Math.abs(o[0]) * xDir * -1, Math.abs(o[1]) * yDir * -1]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering over another anchor; + * we want to assume that anchor's orientation for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no longer hovering + * over another anchor; we should resume calculating orientation as we normally do. + */ + this.out = function() { + orientation = null; + }; + }; + + // ************** connection + // **************************************** + /** + * allowed params: + * source: source element (string or a jQuery element) (required) + * target: target element (string or a jQuery element) (required) + * + * anchors: optional array of anchor placements. defaults to BottomCenter for source + * and TopCenter for target. + */ + var Connection = function(params) { + + // ************** get the source and target and register the connection. ******************* + var self = this; + // get source and target as jQuery objects + this.source = (typeof params.source == 'string') ? $("#" + params.source) : params.source; + this.target = (typeof params.target == 'string') ? $("#" + params.target) : params.target; + this.sourceId = $(this.source).attr("id"); + this.targetId = $(this.target).attr("id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + + // init endpoints + this.endpoints = []; + this.endpointStyles = []; + var prepareEndpoint = function(existing, index, params) { + if (existing) self.endpoints[index] = existing; + else { + if(!params.endpoints) params.endpoints = [null,null]; + var ep = params.endpoints[index] || params.endpoint || jsPlumb.Defaults.Endpoints[index] || jsPlumb.Defaults.Endpoint|| new jsPlumb.Endpoints.Dot(); + if (!params.endpointStyles) params.endpointStyles = [null,null]; + var es = params.endpointStyles[index] || params.endpointStyle || jsPlumb.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyle; + var a = params.anchors ? params.anchors[index] : jsPlumb.Defaults.Anchors[index] || jsPlumb.Anchors.BottomCenter; + self.endpoints[index] = new Endpoint({style:es, endpoint:ep, connections:[self], anchor:a }); + } + }; + + prepareEndpoint(params.sourceEndpoint, 0, params); + prepareEndpoint(params.targetEndpoint, 1, params); + + // make connector. if an endpoint has a connector + paintstyle to use, we use that. + // otherwise we use sensible defaults. + //this.connector = params.connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + //this.paintStyle = params.paintStyle || jsPlumb.Defaults.PaintStyle; + this.paintStyle = this.endpoints[0].connectionStyle || this.endpoints[1].connectionStyle || params.paintStyle || jsPlumb.Defaults.PaintStyle; + + _updateOffset(this.sourceId); + _updateOffset(this.targetId); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var anchorLoc = this.endpoints[0].anchor.compute([myOffset.left, myOffset.top], myWH); + this.endpoints[0].paint(anchorLoc); + myOffset = offsets[this.targetId]; myWH = sizes[this.targetId]; + anchorLoc = this.endpoints[1].anchor.compute([myOffset.left, myOffset.top], myWH); + this.endpoints[1].paint(anchorLoc); + + // *************** create canvas on which the connection will be drawn ************ + var canvas = _newCanvas(jsPlumb.connectorClass); + this.canvas = canvas; + + /** + * paints the connection. + * @param elId Id of the element that is in motion + * @param ui jQuery's event system ui object (present if we came from a drag to get here) + * @param recalc whether or not to recalculate element sizes. this is true if a repaint caused this to be painted. + */ + this.paint = function(elId, ui, recalc) { + + if (log) log.debug("Painting Connection; element in motion is " + elId + "; ui is [" + ui + "]; recalc is [" + recalc + "]"); + + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = !(elId == this.sourceId); + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + if (this.canvas.getContext) { + + _updateOffset(elId, ui, recalc); + if (recalc) _updateOffset(tId); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var myOffset = offsets[elId]; + var otherOffset = offsets[tId]; + var myWH = sizes[elId]; + var otherWH = sizes[tId]; + + var ctx = canvas.getContext('2d'); + var sAnchorP = this.endpoints[sIdx].anchor.compute([myOffset.left, myOffset.top], myWH, [otherOffset.left, otherOffset.top], otherWH); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.compute([otherOffset.left, otherOffset.top], otherWH, [myOffset.left, myOffset.top], myWH); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, this.paintStyle.lineWidth); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + $.extend(ctx, this.paintStyle); + + if (this.paintStyle.gradient && !ie) { + var g = swap ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + for (var i = 0; i < this.paintStyle.gradient.stops.length; i++) + g.addColorStop(this.paintStyle.gradient.stops[i][0],this.paintStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + + this.connector.paint(dim, ctx); + + // this.endpoints[swap ? 1 : 0].paint(sAnchorP, this.paintStyle); + //this.endpoints[swap ? 0 : 1].paint(tAnchorP, this.paintStyle); + } + }; + + this.repaint = function() { + this.paint(this.sourceId, null, true); + }; + + _initDraggableIfNecessary(self.source, self.sourceId, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, self.targetId, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + }; + + /** + * models an endpoint. can have one to N connections emanating from it (although how to handle that in the UI is + * a very good question). also has a Canvas and paint style. + * + * params: + * + * anchor : anchor for the endpoint, of type jsPlumb.Anchor. may be null. + * endpoint : endpoint object, of type jsPlumb.Endpoint. may be null. + * style : endpoint style, a js object. may be null. + * source : element the endpoint is attached to, of type jquery object. Required. + * canvas : canvas element to use. may be, and most often is, null. + * connections : optional list of connections to configure the endpoint with. + * isSource : boolean. indicates the endpoint can act as a source of new connections. optional. + * dragOptions : if isSource is set to true, you can supply arguments for the jquery draggable method. optional. + * connectionStyle : if isSource is set to true, this is the paint style for connections from this endpoint. optional. + * connector : optional connector type to use. + * isTarget : boolean. indicates the endpoint can act as a target of new connections. optional. + * dropOptions : if isTarget is set to true, you can supply arguments for the jquery droppable method. optional. + * reattach : optional boolean that determines whether or not the connections reattach after they + * have been dragged off an endpoint and left floating. defaults to false - connections + * dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + params = params || {}; + // make a copy. then we can use the wrapper function. + params = $.extend({}, params); + var self = this; + self.anchor = params.anchor || jsPlumb.Anchors.TopCenter; + var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot(); + var _style = params.style || jsPlumb.Defaults.EndpointStyle; + this.connectionStyle = params.connectionStyle; + this.connector = params.connector; + var _element = params.source; + var _elementId = $(_element).attr("id"); + var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of. + this.canvas = params.canvas || _newCanvas(jsPlumb.endpointClass); + this.connections = params.connections || []; + var _reattach = params.reattach || false; + var floatingEndpoint = null; + this.addConnection = function(connection) { + self.connections.push(connection); + }; + this.removeConnection = function(connection) { + var idx = self.connections.indexOf(connection); + if (idx >= 0) + self.connections.splice(idx, 1); + }; + this.isFloating = function() { return floatingEndpoint != null; }; + /** + * first pass at default ConnectorSelector: returns the first connection, if we have any. + * modified a little, 5/10/2010: now it only returns a connector if we have not got equal to or more than _maxConnector connectors + * attached. otherwise it is assumed a new connector is ok. but note with this setup we can't select any other connection than the first + * one. what if this could return a list? that implies everything should work with a list - dragging etc. could be nasty. could also + * be cool. + */ + var connectorSelector = function() { + return self.connections.length == 0 || self.connections.length < _maxConnections ? null : self.connections[0]; + }; + + // get the jsplumb context...lookups are faster with a context. + var contextNode = _getContextNode(); + + this.isFull = function() { return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); }; + + /** + * paints the Endpoint, recalculating offset and anchor positions if necessary. + */ + this.paint = function(anchorPoint, connectorPaintStyle, canvas) { + + if (log) log.debug("Painting Endpoint with elementId [" + _elementId + "]; anchorPoint is [" + anchorPoint + "]"); + + if (anchorPoint == null) { + // do we always want to force a repaint here? i dont think so! + var xy = offsets[_elementId]; + var wh = sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset(_elementId); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + anchorPoint = self.anchor.compute([xy.left, xy.top], wh); + } + _endpoint.paint(anchorPoint, self.anchor.getOrientation(), canvas || self.canvas, _style, connectorPaintStyle || _style); + }; + + + // is this a connection source? we make it draggable and have the drag listener + // maintain a connection with a floating endpoint. + if (params.isSource && _element.draggable) { + + var n = null, id = null, + jpc = null, + existingJpc = false, existingJpcParams = null; + + // first the question is, how many connections are on this endpoint? if it's only one, then excellent. otherwise we will either need a way + // to select one connection from the list, or drag them all. if we had a pluggable 'ConnectorSelector' interface we could probably + // provide a way for people to implement their own UI components to do the connector selection. the question in that particular case would be how much + // information the interface needs from jsPlumb at execution time. if, however, we leave out the connector selection, and drag them all, + // that wouldn't be too hard to organise. perhaps that behaviour would be on a switch for the endpoint, or perhaps the ConnectorSelector + // interface returns a List, with the default implementation just returning everything. i think i like that. + // + // let's say for now that there is just one endpoint, cos we need to get this going before we can consider a list of them anyway. + // the major difference between that case and the case of a new endpoint is actually quite small - it's a question of where the + // Connection comes from. for a new one, we create a new one. obviously. otherwise we just get the jpc from the Endpoint + // (remember we're only assuming one connection right now). so all of the UI stuff we do to create the floating endpoint etc + // will still be valid, but when we stop dragging, we'll have to do something different. if we stop with a valid drop i think it will + // be the same process. but if we stop with an invalid drop we have to reset the Connection to how it was when we got it. + var start = function(e, ui) { + //if (!isFull()) { + n = document.createElement("div"); + contextNode.append(n); + // create and assign an id, and initialize the offset. + id = new String(new Date().getTime()); + $(n, contextNode).attr("id", id); + _updateOffset(id); + // store the id of the dragging div and the source element. the drop function + // will pick these up. + $(self.canvas, contextNode).attr("dragId", id); + $(self.canvas, contextNode).attr("elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor({reference:self.anchor}); + floatingEndpoint = new Endpoint({ + style:_style, + endpoint:_endpoint, + anchor:floatingAnchor, + source:n + }); + //floatingEndpoint.originalAnchor = + + jpc = connectorSelector(); + if (jpc == null) { + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = new Connection({ + sourceEndpoint:self, + targetEndpoint:floatingEndpoint, + source:$(_element), + target:$(n, contextNode), + anchors:[self.anchor, floatingAnchor], + paintStyle : params.connectionStyle, // this can be null. Connection will use the default. + connector: params.connector + }); + // todo ...unregister on stop + self.addConnection(jpc); + } else { + existingJpc = true; + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; + // probably we should remove the connection? and add it back if the user + // does not drop it somewhere proper. + self.removeConnection(jpc); + if (anchorIdx == 0){ + existingJpcParams = [jpc.source, jpc.sourceId]; + jpc.source = $(n, contextNode); + jpc.sourceId = id; + }else { + existingJpcParams = [jpc.target, jpc.targetId]; + jpc.target = $(n, contextNode); + jpc.targetId = id; + } + + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // todo unregister on stop + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + } + //}; + + var dragOptions = params.dragOptions || { }; + dragOptions = $.extend({ opacity:0.5, revert:true, helper:'clone' }, dragOptions); + + dragOptions.start = _wrap(dragOptions.start, start); + dragOptions.drag = _wrap(dragOptions.drag, function(e, ui) { + _draw($(n, contextNode), ui); + }); + dragOptions.stop = _wrap(dragOptions.stop, + function(e, ui) { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements([floatingEndpoint.canvas, n]); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (jpc.endpoints[idx] == floatingEndpoint) { + + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + if (_reattach) { + jpc.floatingAnchorIndex = null; + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } + else { + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeElement(jpc.canvas); + } + } else { + _removeElement(jpc.canvas); + self.removeConnection(jpc); + } + } + jpc = null; + delete floatingEndpoint; + } + ); + + $(self.canvas, contextNode).draggable(dragOptions); + } + + // connector target + if (params.isTarget && _element.droppable) { + var dropOptions = params.dropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = $.extend({}, dropOptions); + var originalAnchor = null; + dropOptions.drop = _wrap(dropOptions.drop, function(e, ui) { + var id = $(ui.draggable, contextNode).attr("dragId"); + var elId = $(ui.draggable, contextNode).attr("elId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // remove this jpc from the current endpoint + jpc.endpoints[idx].removeConnection(jpc); + if (jpc.suspendedEndpoint) + jpc.suspendedEndpoint.removeConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + _initDraggableIfNecessary(_element, _elementId, params.draggable, {}); + jsPlumb.repaint($(ui.draggable, contextNode).attr("elId")); + delete floatingConnections[id]; + }); + // what to do when something is dropped. + // 1. find the jpc that is being dragged. the target endpoint of the jpc will be the + // one that is being dragged. + // 2. arrange for the floating endpoint to be replaced with this endpoint; make sure + // everything gets registered ok etc. + // 3. arrange for the floating endpoint to be deleted. + // 4. make sure that the stop method of the drag does not cause the jpc to be cleaned up. we want to keep it now. + + // other considerations: when in the hover mode, we should switch the floating endpoint's + // orientation to be the same as the drop target. this will cause the connector to snap + // into the shape it will take if the user drops at that point. + + dropOptions.over = _wrap(dropOptions.over, function(event, ui) { + var id = $(ui.draggable, contextNode).attr("dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + }); + + dropOptions.out = _wrap(dropOptions.out, function(event, ui) { + var id = $(ui.draggable, contextNode).attr("dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + }); + + $(self.canvas, contextNode).droppable(dropOptions); + } + + // woo...add a plumb command to Endpoint. + this.plumb = function(params) { + // not much to think about. the target should be an Endpoint, but what else? + // all the style stuff is on the endpoint itself already. + //todo this should call the main plumb method, just with some different args. + + }; + + return self; + }; + + /** + * jsPlumb public API + */ + var jsPlumb = window.jsPlumb = { + + Defaults : { + Anchors : [ null, null ], + Connector : null, + DragOptions: { }, + DropOptions: { }, + Endpoint : null, + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : null }, + EndpointStyles : [ null, null ], + MaxConnections : null, + PaintStyle : { lineWidth : 10, strokeStyle : 'red' } + }, + + connectorClass : '_jsPlumb_connector', + endpointClass : '_jsPlumb_endpoint', + + Anchors : {}, + Connectors : {}, + Endpoints : {}, + + /** + * adds an endpoint to the element + */ + addEndpoint : function(target, params) { + params = $.extend({}, params); + var el = typeof target == 'string' ? $("#" + target) : target; + var id = $(el).attr("id"); + params.source = el; + _updateOffset(id); + var e = new Endpoint(params); + _addToList(endpointsByElement, id, e); + + var myOffset = offsets[id]; + var myWH = sizes[id]; + + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH); + e.paint(anchorLoc); + return e; + }, + + /** + * wrapper around standard jquery animate function; injects a call to jsPlumb in the + * 'step' function (creating it if necessary). this only supports the two-arg version + * of the animate call in jquery - the one that takes an object as the second arg. + */ + animate : function(el, properties, options) { + var ele = typeof(el)=='string' ? $("#" + el) : el; + var id = ele.attr("id"); + options = options || {}; + options.step = _wrap(options.step, function() { jsPlumb.repaint(id); }); + ele.animate(properties, options); + }, + + /** + * adds a list of endpoints to the element + */ + addEndpoints : function(target, endpoints) { + var results = []; + for (var i = 0; i < endpoints.length; i++) { + results.push(jsPlumb.addEndpoint(target, endpoints[i])); + } + return results; + }, + + /** + * establishes a connection between two elements. + * @param params object containing setup for the connection. see documentation. + */ + connect : function(params) { + if (params.sourceEndpoint && params.sourceEndpoint.isFull()) { + _log("could not add connection; source endpoint is full"); + return; + } + + if (params.targetEndpoint && params.targetEndpoint.isFull()) { + _log("could not add connection; target endpoint is full"); + return; + } + + var jpc = new Connection(params); + + // register endpoints for the element + _addToList(endpointsByElement, jpc.sourceId, jpc.endpoints[0]); + _addToList(endpointsByElement, jpc.targetId, jpc.endpoints[1]); + + jpc.endpoints[0].addConnection(jpc); + jpc.endpoints[1].addConnection(jpc); + + // force a paint + _draw(jpc.source); + + }, + + connectEndpoints : function(params) { + var jpc = Connection(params); + + }, + + /** + * Remove one connection to an element. + * @param sourceId id of the first window in the connection + * @param targetId id of the second window in the connection + * @return true if successful, false if not. + */ + detach : function(sourceId, targetId) { + var f = function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas); + /*_removeElement(jpc.targetEndpointCanvas); + _removeElement(jpc.sourceEndpointCanvas);*/ + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + return true; + } + }; + + // todo: how to cleanup the actual storage? a third arg to _operation? + _operation(sourceId, f); + }, + + /** + * remove all an element's connections. + * @param elId id of the + */ + detachAll : function(elId) { + + var f = function(jpc) { + // todo replace with _cleanupConnection call here. + _removeElement(jpc.canvas); + /*_removeElement(jpc.targetEndpointCanvas); + _removeElement(jpc.sourceEndpointCanvas);*/ + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + }; + _operation(elId, f); + //delete endpointsByElement[elId]; + }, + + /** + * remove all connections. and endpoints? probably not. + */ + detachEverything : function() { + var f = function(jpc) { + _removeElement(jpc.canvas); + /*_removeElement(jpc.targetEndpointCanvas); + _removeElement(jpc.sourceEndpointCanvas);*/ + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + }; + + _operationOnAll(f); + + /*delete endpointsByElement; + endpointsByElement = {};*/ + }, + + /** + * Set an element's connections to be hidden. + */ + hide : function(elId) { + _setVisible(elId, "none"); + }, + + /** + * Creates an anchor with the given params. + * x - the x location of the anchor as a fraction of the total width. + * y - the y location of the anchor as a fraction of the total height. + * orientation - an [x,y] array indicating the general direction a connection from the anchor should go in. + * offsets - an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. optional. defaults to [0,0]. + */ + makeAnchor : function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards compatibility if we are given only one value we assume it's a call in the old form. + var params = {}; + if (arguments.length == 1) $.extend(params, x); + else { + params = {x:x, y:y}; + if (arguments.length >= 4) { + params.orientation = [arguments[2], arguments[3]]; + } + if (arguments.length == 6) params.offsets = [arguments[4], arguments[5]]; + } + return new Anchor(params); + }, + + + /** + * repaint element and its connections. element may be an id or the actual jQuery object. + * this method gets new sizes for the elements before painting anything. + */ + repaint : function(el) { + + var _processElement = function(el) { + var ele = typeof(el)=='string' ? $("#" + el) : el; + _draw(ele); + }; + + // TODO: support a jQuery result object too! + + // support both lists... + if (typeof el =='object') { + for (var i = 0; i < el.length; i++) + _processElement(el[i]); + } // ...and single strings. + else _processElement(el); + }, + + /** + * repaint all connections. + */ + repaintEverything : function() { + for (var elId in endpointsByElement) { + _draw($("#" + elId)); + } + }, + + removeEndpoint : function(elId, endpoint) { + var ebe = endpointsByElement[elId]; + if (ebe) { + // var i = ebe.indexOf(endpoint); + // alert('element has ' + ebe.length); + //if (i > -1) { + //ebe.splice(i, 1); + if(_removeFromList(endpointsByElement, elId, endpoint)) + _removeElement(endpoint.canvas); + //} + } + }, + + /** + * sets/unsets automatic repaint on window resize. + */ + setAutomaticRepaint : function(value) { + automaticRepaint = value; + }, + + /** + * Sets the default size jsPlumb will use for a new canvas (we create a square canvas so + * one value is all that is required). This is a hack for IE, because ExplorerCanvas seems + * to need for a canvas to be larger than what you are going to draw on it at initialisation + * time. The default value of this is 1200 pixels, which is quite large, but if for some + * reason you're drawing connectors that are bigger, you should adjust this value appropriately. + */ + setDefaultNewCanvasSize : function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }, + + /** + * Sets whether or not a given element is draggable, regardless of what any plumb command + * may request. + */ + setDraggable: _setDraggable, + + /** + * Sets whether or not elements are draggable by default. Default for this is true. + */ + setDraggableByDefault: function(draggable) { + _draggableByDefault = draggable; + }, + + setDebugLog: function(debugLog) { + log = debugLog; + }, + + /** + * Sets the function to fire when the window size has changed and a repaint was fired. + */ + setRepaintFunction : function(f) { + repaintFunction = f; + }, + + /** + * Set an element's connections to be visible. + */ + show : function(elId) { + _setVisible(elId, "block"); + }, + + /** + * helper to size a canvas. + */ + sizeCanvas : function(canvas, x, y, w, h) { + canvas.style.height = h + "px"; canvas.height = h; + canvas.style.width = w + "px"; canvas.width = w; + canvas.style.left = x + "px"; canvas.style.top = y + "px"; + }, + + /** + * gets some test hooks. nothing writable. + */ + getTestHarness : function() { + return { + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + } + }; + }, + + /** + * Toggles visibility of an element's connections. kept for backwards compatibility + */ + toggle : _toggleVisible, + + /** + * new name for the old toggle method. + */ + toggleVisible : _toggleVisible, + + /** + * Toggles draggability (sic) of an element. + */ + toggleDraggable : _toggleDraggable, + + /** + * Unloads jsPlumb, deleting all storage. You should call this + */ + unload : function() { + delete endpointsByElement; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + document.body.removeChild(_jsPlumbContextNode); + } + }; + +})(); + +// jQuery plugin code +(function($){ + /** + * plumbs the results of the selector to some target, using the given options if supplied, + * or the defaults otherwise. + */ + $.fn.plumb = function(options) { + var options = $.extend({}, options); + + return this.each(function() + { + var params = $.extend({source:$(this)}, options); + jsPlumb.connect(params); + }); + }; + + /** + * detaches the results of the selector from the given target or list of targets - 'target' + * may be a String or a List. + */ + $.fn.detach = function(target) { + return this.each(function() + { + var id = $(this).attr("id"); + if (typeof target == 'string') target = [target]; + for (var i = 0; i < target.length; i++) + jsPlumb.detach(id, target[i]); + }); + }; + + /** + * detaches the results from the selector from all connections. + */ + $.fn.detachAll = function() { + return this.each(function() + { + var id = $(this).attr("id"); + jsPlumb.detachAll(id); + }); + }; + + /** + * adds an endpoint to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoint = function(options) { + var addedEndpoints = []; + this.each(function() + { + //var params = $.extend({source:$(this)}, options); + addedEndpoints.push(jsPlumb.addEndpoint($(this).attr("id"), options)); + }); + return addedEndpoints; + }; + + /** + * adds a list of endpoints to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoints = function(endpoints) { + var addedEndpoints = []; + return this.each(function() + { + var e = jsPlumb.addEndpoints($(this).attr("id"), endpoints); + for (var i = 0; i < e.length; i++) addedEndpoints.push(e[i]); + }); + return addedEndpoints; + }; + + /** + * remove the endpoint, if it exists, deleting its UI elements etc. + */ + $.fn.removeEndpoint = function(endpoint) { + this.each(function() + { + jsPlumb.removeEndpoint($(this).attr("id"), endpoint); + }); + }; + +})(jQuery); diff --git a/archive/jquery.jsPlumb-1.1.0.js b/archive/jquery.jsPlumb-1.1.0.js new file mode 100644 index 000000000..b27b586e9 --- /dev/null +++ b/archive/jquery.jsPlumb-1.1.0.js @@ -0,0 +1,1275 @@ +/* + * jsPlumb 1.1.0 + * + * Provides a way to visually connect elements on an HTML page. + * + * 1.1.0 contains the first released version of the code that supports draggable connectors. + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + */ +if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function( v, b, s ) { + for( var i = +b || 0, l = this.length; i < l; i++ ) { + if( this[i]===v || s && this[i]==v ) { return i; } + } + return -1; + }; +} +(function() { + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + + var log = null; + + var repaintFunction = function() { jsPlumb.repaintEverything(); }; + var automaticRepaint = true; + function repaintEverything() { + if (automaticRepaint) + repaintFunction(); + }; + var resizeTimer = null; + $(window).bind('resize', function() { + if (resizeTimer) clearTimeout(resizeTimer); + resizeTimer = setTimeout(repaintEverything, 100); + }); + + /** + * map of element id -> endpoint lists. an element can have an arbitrary number of endpoints on it, + * and not all of them have to be connected to anything. + */ + var endpointsByElement = {}; + var offsets = []; + var floatingConnections = {}; + var draggableStates = {}; + var _draggableByDefault = true; + var sizes = []; + var _jsPlumbContextNode = null; + + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + /** + * helper method to add an item to a list, creating the list if it does not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + }; + + /** + * Handles the dragging of an element. + * @param element jQuery element + * @param ui UI object from jQuery's event system + */ + var _draw = function(element, ui) { + var id = $(element).attr("id"); + var endpoints = endpointsByElement[id]; + if (endpoints) { + //if (ui == null) _updateOffset(id, ui); + _updateOffset(id, ui); + var myOffset = offsets[id]; + var myWH = sizes[id]; + // loop through endpoints for this element + for (var i = 0; i < endpoints.length; i++) { + var e = endpoints[i]; + // first, paint the endpoint + + var anchorLoc = endpoints[i].anchor.compute([myOffset.left, myOffset.top], myWH); + // var anchorOrientation = endpoints[i].anchor.getOrientation(); + + //todo...connector paint style? we have lost that with the move to endpoint-centric. + // perhaps we only paint the endpoint here if it has no connections; it can use its own style. + //if (!e.connections || e.connections.length == 0) + e.paint(anchorLoc); + + // else { + //if (e.connections && e.connections.length > 0) { + // get all connections for the endpoint... + var l = e.connections; + for (var j = 0; j < l.length; j++) + l[j].paint(id, ui); // ...and paint them. + //} + } + } + }; + + /** + * helper function: the second argument is a function taking two args - the first is a + * jquery element, and the second is the element's id. + * + * the first argument may be one of three things: + * + * 1. a string, in the form "window1", for example. an element's id. if your id starts with a + * hash then jsPlumb does not append its own hash too... + * 2. a jquery element, already resolved using $(...). + * 3. a list of strings/jquery elements. + */ + var _elementProxy = function(element, fn) { + var retVal = null; + if (typeof element == 'object' && element.length) { + retVal = []; + for (var i = 0; i < element.length; i++) { + var el = typeof element[i] == 'string' ? $("#" + element[i]) : element[i]; + var id = el.attr("id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } + else { + var el = typeof element == 'string' ? + element.indexOf("#") == 0 ? $(element) : $("#" + element) + : element; + var id = el.attr("id"); + retVal = fn(el, id); + } + + return retVal; + }; + + /** + * Returns (creating if necessary) the DIV element that jsPlumb uses as the context for all of its + * canvases. having this makes it possible to makes calls like $("selector", context), which are + * faster than if you provide no context. also we can clear out everything easily like this, either + * on a detachEverything() call or during unload(). + */ + var _getContextNode = function() { + if (_jsPlumbContextNode == null) { + _jsPlumbContextNode= document.createElement("div"); + document.body.appendChild(_jsPlumbContextNode); + _jsPlumbContextNode.className = "_jsPlumb_context"; + } + return $(_jsPlumbContextNode); + }; + + /** + * gets an id for the given element, creating and setting one if necessary. + */ + var _getId = function(element) { + var id = $(element).attr("id"); + if (!id) { + id = "_jsPlumb_" + new String((new Date()).getTime()); + $(element).attr("id", id); + } + return id; + }; + + /** + * inits a draggable if it's not already initialised. + * todo: if the element was draggable already, like from some non-jsPlumb call, wrap the drag function. + */ + var _initDraggableIfNecessary = function(element, elementId, isDraggable, dragOptions) { + // dragging + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable && element.draggable) { + var options = dragOptions || jsPlumb.Defaults.DragOptions; + var dragCascade = options.drag || function(e,u) {}; + var initDrag = function(element, elementId, dragFunc) { + var opts = $.extend({drag:dragFunc}, options); + var draggable = draggableStates[elementId]; + opts.disabled = draggable == null ? false : !draggable; + element.draggable(opts); + }; + initDrag(element, elementId, function(event, ui) { + _draw(element, ui); + $(element).addClass("jsPlumb_dragged"); + dragCascade(event, ui); + }); + } + + }; + + var _log = function(msg) { + // not implemented. yet. + } + + /** + * helper to create a canvas. + * @param clazz optional class name for the canvas. + */ + var _newCanvas = function(clazz) { + var canvas = document.createElement("canvas"); + _getContextNode().append(canvas); + canvas.style.position="absolute"; + if (clazz) { canvas.className=clazz; } + + if (/MSIE/.test(navigator.userAgent) && !window.opera) { + // for IE we have to set a big canvas size. actually you can override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + /** + * performs the given function operation on all the connections found for the given element + * id; this means we find all the endpoints for the given element, and then for each endpoint + * find the connectors connected to it. then we pass each connection in to the given + * function. + */ + var _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + for (var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }; + /** + * perform an operation on all elements. + */ + var _operationOnAll = function(func) { + for (var elId in endpointsByElement) { + _operation(elId, func); + } + }; + /** + * helper to remove an element from the DOM. + */ + var _removeElement = function(element) { + if (element != null) { + try { _jsPlumbContextNode.removeChild(element); } + catch (e) { } + } + }; + /** + * helper to remove a list of elements from the DOM. + */ + var _removeElements = function(elements) { + for (var i in elements) + _removeElement(elements[i]); + }; + /** + * helper method to remove an item from a list. + */ + var _removeFromList = function(map, key, value) { + var l = map[key]; + if (l != null) { + var i = l.indexOf(value); + if (i >= 0) { + delete( l[i] ); + l.splice( i, 1 ); + return true; + } + } + return false; + }; + /** + * Sets whether or not the given element(s) should be draggable, regardless of what a particular + * plumb command may request. + * + * @param element May be a string, a jQuery elements, or a list of strings/jquery elements. + * @param draggable Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + var _helper = function(el, id) { + draggableStates[id] = draggable; + if (el.draggable) { + el.draggable("option", "disabled", !draggable); + } + }; + + return _elementProxy(element, _helper); + }; + /** + * private method to do the business of hiding/showing. + * @param elId Id of the element in question + * @param state String specifying a value for the css 'display' property ('block' or 'none'). + */ + var _setVisible = function(elId, state) { + var f = function(jpc) { + //todo should we find all the endpoints instead of going by connection? this will + jpc.canvas.style.display = state; + /*jpc.sourceEndpointCanvas.style.display = state; + jpc.targetEndpointCanvas.style.display = state;*/ + }; + + _operation(elId, f); + }; + /** + * toggles the draggable state of the element with the given id. + */ + var _toggleDraggable = function(el) { + var fn = function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + el.draggable("option", "disabled", !state); + return state; + }; + return _elementProxy(el, fn); + }; + /** + * private method to do the business of toggling hiding/showing. + * @param elId Id of the element in question + */ + var _toggleVisible = function(elId) { + var f = function(jpc) {; + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + /*jpc.sourceEndpointCanvas.style.display = state; + jpc.targetEndpointCanvas.style.display = state;*/ + }; + + _operation(elId, f); + }; + /** + * updates the offset and size for a given element, and stores the values. + * if 'ui' is not null we use that (it would have been passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we use the offset, outerWidth and outerHeight methods to get + * the current values. + */ + var _updateOffset = function(elId, ui, recalc) { + + if (log) log.debug("updating offset for element [" + elId + "]; ui is [" + ui + "]; recalc is [" + recalc + "]"); + + if (recalc || ui == null) { // if forced repaint or no ui helper available, we recalculate. + // get the current size and offset, and store them + var s = $("#" + elId); + sizes[elId] = [s.outerWidth(), s.outerHeight()]; + offsets[elId] = s.offset(); + } else { + // faster to use the ui element if it was passed in. + // fix for change in 1.8 (absolutePosition renamed to offset). plugin is compatible with + // 1.8 and 1.7. + + // todo: when the drag axis is supplied, the ui object passed in has incorrect values + // for the other axis - like say you set axis='x'. when you move the mouse up and down + // while dragging, the y values are for where the window would be if it was not + // just constrained to x. not sure if this is a jquery bug or whether there's a known + // trick or whatever. + var pos = ui.absolutePosition || ui.offset; + var anOffset = ui != null ? pos : $("#" + elId).offset(); + offsets[elId] = anOffset; + } + }; + /** + * wraps one function with another, creating a placeholder for the wrapped function + * if it was null. this is used to wrap the various drag/drop event functions - to allow + * jsPlumb to be notified of important lifecycle events without imposing itself on the user's + * drap/drop functionality. + * TODO: determine whether or not we should try/catch the plumb function, so that the cascade function is always executed. + */ + var _wrap = function(cascadeFunction, plumbFunction) { + cascadeFunction = cascadeFunction || function(e, ui) { }; + return function(e, ui) { + plumbFunction(e, ui); + cascadeFunction(e, ui); + }; + } + /** + * Anchor class. Anchors can be situated anywhere. + * params should contain three values, and may optionally have an 'offsets' argument: + * + * x : the x location of the anchor as a fraction of the total width. + * + * y : the y location of the anchor as a fraction of the total height. + * + * orientation : an [x,y] array indicating the general direction a connection + * from the anchor should go in. for more info on this, see the documentation, + * or the docs in jquery-jsPlumb-defaults-XXX.js for the default Anchors. + * + * offsets : an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. may be null. + * + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; this.y = params.y || 0; + var orientation = params.orientation || [0,0]; + this.offsets = params.offsets || [0,0]; + this.compute = function(xy, wh, txy, twh) { + return [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + } + this.getOrientation = function() { return orientation; }; + }; + + /** + * an anchor that floats. its orientation is computed dynamically from its position relative + * to the anchor it is floating relative to. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for purposes of calculating the orientation. + var ref = params.reference; + // these are used to store the current relative position of our anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating anchor is hovering over another anchor. + var orientation = null; + + this.compute = function(xy, wh, txy, twh) { + // set these for the getOrientation method to use. + xDir = 0;//xy[0] < txy[0] ? -1 : xy[0] == txy[0] ? 0 : 1; + yDir = 0;//xy[1] < txy[1] ? -1 : xy[1] == txy[1] ? 0 : 1; + return [xy[0], xy[1]]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other anchor: if it declares zero for some direction, we declare zero too. + // this might not be the most awesome. perhaps we can come up with a better way. it's just so that the line we draw looks + // like it makes sense. maybe this wont make sense. + return [Math.abs(o[0]) * xDir * -1, Math.abs(o[1]) * yDir * -1]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering over another anchor; + * we want to assume that anchor's orientation for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no longer hovering + * over another anchor; we should resume calculating orientation as we normally do. + */ + this.out = function() { + orientation = null; + }; + }; + + // ************** connection + // **************************************** + /** + * allowed params: + * source: source element (string or a jQuery element) (required) + * target: target element (string or a jQuery element) (required) + * + * anchors: optional array of anchor placements. defaults to BottomCenter for source + * and TopCenter for target. + */ + var Connection = function(params) { + + // ************** get the source and target and register the connection. ******************* + var self = this; + // get source and target as jQuery objects + this.source = (typeof params.source == 'string') ? $("#" + params.source) : params.source; + this.target = (typeof params.target == 'string') ? $("#" + params.target) : params.target; + this.sourceId = $(this.source).attr("id"); + this.targetId = $(this.target).attr("id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + + // init endpoints + this.endpoints = []; + this.endpointStyles = []; + var prepareEndpoint = function(existing, index, params) { + if (existing) self.endpoints[index] = existing; + else { + if(!params.endpoints) params.endpoints = [null,null]; + var ep = params.endpoints[index] || params.endpoint || jsPlumb.Defaults.Endpoints[index] || jsPlumb.Defaults.Endpoint|| new jsPlumb.Endpoints.Dot(); + if (!params.endpointStyles) params.endpointStyles = [null,null]; + var es = params.endpointStyles[index] || params.endpointStyle || jsPlumb.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyle; + var a = params.anchors ? params.anchors[index] : jsPlumb.Defaults.Anchors[index] || jsPlumb.Anchors.BottomCenter; + self.endpoints[index] = new Endpoint({style:es, endpoint:ep, connections:[self], anchor:a }); + } + }; + + prepareEndpoint(params.sourceEndpoint, 0, params); + prepareEndpoint(params.targetEndpoint, 1, params); + + // make connector. if an endpoint has a connector + paintstyle to use, we use that. + // otherwise we use sensible defaults. + //this.connector = params.connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + //this.paintStyle = params.paintStyle || jsPlumb.Defaults.PaintStyle; + this.paintStyle = this.endpoints[0].connectionStyle || this.endpoints[1].connectionStyle || params.paintStyle || jsPlumb.Defaults.PaintStyle; + + _updateOffset(this.sourceId); + _updateOffset(this.targetId); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var anchorLoc = this.endpoints[0].anchor.compute([myOffset.left, myOffset.top], myWH); + this.endpoints[0].paint(anchorLoc); + myOffset = offsets[this.targetId]; myWH = sizes[this.targetId]; + anchorLoc = this.endpoints[1].anchor.compute([myOffset.left, myOffset.top], myWH); + this.endpoints[1].paint(anchorLoc); + + // *************** create canvas on which the connection will be drawn ************ + var canvas = _newCanvas(jsPlumb.connectorClass); + this.canvas = canvas; + + /** + * paints the connection. + * @param elId Id of the element that is in motion + * @param ui jQuery's event system ui object (present if we came from a drag to get here) + * @param recalc whether or not to recalculate element sizes. this is true if a repaint caused this to be painted. + */ + this.paint = function(elId, ui, recalc) { + + if (log) log.debug("Painting Connection; element in motion is " + elId + "; ui is [" + ui + "]; recalc is [" + recalc + "]"); + + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = !(elId == this.sourceId); + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + if (this.canvas.getContext) { + + _updateOffset(elId, ui, recalc); + if (recalc) _updateOffset(tId); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var myOffset = offsets[elId]; + var otherOffset = offsets[tId]; + var myWH = sizes[elId]; + var otherWH = sizes[tId]; + + var ctx = canvas.getContext('2d'); + var sAnchorP = this.endpoints[sIdx].anchor.compute([myOffset.left, myOffset.top], myWH, [otherOffset.left, otherOffset.top], otherWH); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.compute([otherOffset.left, otherOffset.top], otherWH, [myOffset.left, myOffset.top], myWH); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, this.paintStyle.lineWidth); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + $.extend(ctx, this.paintStyle); + + if (this.paintStyle.gradient && !ie) { + var g = swap ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + for (var i = 0; i < this.paintStyle.gradient.stops.length; i++) + g.addColorStop(this.paintStyle.gradient.stops[i][0],this.paintStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + + this.connector.paint(dim, ctx); + + // this.endpoints[swap ? 1 : 0].paint(sAnchorP, this.paintStyle); + //this.endpoints[swap ? 0 : 1].paint(tAnchorP, this.paintStyle); + } + }; + + this.repaint = function() { + this.paint(this.sourceId, null, true); + }; + + _initDraggableIfNecessary(self.source, self.sourceId, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, self.targetId, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + }; + + /** + * models an endpoint. can have one to N connections emanating from it (although how to handle that in the UI is + * a very good question). also has a Canvas and paint style. + * + * params: + * + * anchor : anchor for the endpoint, of type jsPlumb.Anchor. may be null. + * endpoint : endpoint object, of type jsPlumb.Endpoint. may be null. + * style : endpoint style, a js object. may be null. + * source : element the endpoint is attached to, of type jquery object. Required. + * canvas : canvas element to use. may be, and most often is, null. + * connections : optional list of connections to configure the endpoint with. + * isSource : boolean. indicates the endpoint can act as a source of new connections. optional. + * dragOptions : if isSource is set to true, you can supply arguments for the jquery draggable method. optional. + * connectionStyle : if isSource is set to true, this is the paint style for connections from this endpoint. optional. + * connector : optional connector type to use. + * isTarget : boolean. indicates the endpoint can act as a target of new connections. optional. + * dropOptions : if isTarget is set to true, you can supply arguments for the jquery droppable method. optional. + * reattach : optional boolean that determines whether or not the connections reattach after they + * have been dragged off an endpoint and left floating. defaults to false - connections + * dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + params = params || {}; + // make a copy. then we can use the wrapper function. + params = $.extend({}, params); + var self = this; + self.anchor = params.anchor || jsPlumb.Anchors.TopCenter; + var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot(); + var _style = params.style || jsPlumb.Defaults.EndpointStyle; + this.connectionStyle = params.connectionStyle; + this.connector = params.connector; + var _element = params.source; + var _elementId = $(_element).attr("id"); + var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of. + this.canvas = params.canvas || _newCanvas(jsPlumb.endpointClass); + this.connections = params.connections || []; + var _reattach = params.reattach || false; + var floatingEndpoint = null; + this.addConnection = function(connection) { + self.connections.push(connection); + }; + this.removeConnection = function(connection) { + var idx = self.connections.indexOf(connection); + if (idx >= 0) + self.connections.splice(idx, 1); + }; + this.isFloating = function() { return floatingEndpoint != null; }; + /** + * first pass at default ConnectorSelector: returns the first connection, if we have any. + * modified a little, 5/10/2010: now it only returns a connector if we have not got equal to or more than _maxConnector connectors + * attached. otherwise it is assumed a new connector is ok. but note with this setup we can't select any other connection than the first + * one. what if this could return a list? that implies everything should work with a list - dragging etc. could be nasty. could also + * be cool. + */ + var connectorSelector = function() { + return self.connections.length == 0 || self.connections.length < _maxConnections ? null : self.connections[0]; + }; + + // get the jsplumb context...lookups are faster with a context. + var contextNode = _getContextNode(); + + this.isFull = function() { return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); }; + + /** + * paints the Endpoint, recalculating offset and anchor positions if necessary. + */ + this.paint = function(anchorPoint, connectorPaintStyle, canvas) { + + if (log) log.debug("Painting Endpoint with elementId [" + _elementId + "]; anchorPoint is [" + anchorPoint + "]"); + + if (anchorPoint == null) { + // do we always want to force a repaint here? i dont think so! + var xy = offsets[_elementId]; + var wh = sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset(_elementId); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + anchorPoint = self.anchor.compute([xy.left, xy.top], wh); + } + _endpoint.paint(anchorPoint, self.anchor.getOrientation(), canvas || self.canvas, _style, connectorPaintStyle || _style); + }; + + + // is this a connection source? we make it draggable and have the drag listener + // maintain a connection with a floating endpoint. + if (params.isSource && _element.draggable) { + + var n = null, id = null, + jpc = null, + existingJpc = false, existingJpcParams = null; + + // first the question is, how many connections are on this endpoint? if it's only one, then excellent. otherwise we will either need a way + // to select one connection from the list, or drag them all. if we had a pluggable 'ConnectorSelector' interface we could probably + // provide a way for people to implement their own UI components to do the connector selection. the question in that particular case would be how much + // information the interface needs from jsPlumb at execution time. if, however, we leave out the connector selection, and drag them all, + // that wouldn't be too hard to organise. perhaps that behaviour would be on a switch for the endpoint, or perhaps the ConnectorSelector + // interface returns a List, with the default implementation just returning everything. i think i like that. + // + // let's say for now that there is just one endpoint, cos we need to get this going before we can consider a list of them anyway. + // the major difference between that case and the case of a new endpoint is actually quite small - it's a question of where the + // Connection comes from. for a new one, we create a new one. obviously. otherwise we just get the jpc from the Endpoint + // (remember we're only assuming one connection right now). so all of the UI stuff we do to create the floating endpoint etc + // will still be valid, but when we stop dragging, we'll have to do something different. if we stop with a valid drop i think it will + // be the same process. but if we stop with an invalid drop we have to reset the Connection to how it was when we got it. + var start = function(e, ui) { + //if (!isFull()) { + n = document.createElement("div"); + contextNode.append(n); + // create and assign an id, and initialize the offset. + id = new String(new Date().getTime()); + $(n, contextNode).attr("id", id); + _updateOffset(id); + // store the id of the dragging div and the source element. the drop function + // will pick these up. + $(self.canvas, contextNode).attr("dragId", id); + $(self.canvas, contextNode).attr("elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor({reference:self.anchor}); + floatingEndpoint = new Endpoint({ + style:_style, + endpoint:_endpoint, + anchor:floatingAnchor, + source:n + }); + //floatingEndpoint.originalAnchor = + + jpc = connectorSelector(); + if (jpc == null) { + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = new Connection({ + sourceEndpoint:self, + targetEndpoint:floatingEndpoint, + source:$(_element), + target:$(n, contextNode), + anchors:[self.anchor, floatingAnchor], + paintStyle : params.connectionStyle, // this can be null. Connection will use the default. + connector: params.connector + }); + // todo ...unregister on stop + self.addConnection(jpc); + } else { + existingJpc = true; + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; + // probably we should remove the connection? and add it back if the user + // does not drop it somewhere proper. + self.removeConnection(jpc); + if (anchorIdx == 0){ + existingJpcParams = [jpc.source, jpc.sourceId]; + jpc.source = $(n, contextNode); + jpc.sourceId = id; + }else { + existingJpcParams = [jpc.target, jpc.targetId]; + jpc.target = $(n, contextNode); + jpc.targetId = id; + } + + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // todo unregister on stop + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + } + //}; + + var dragOptions = params.dragOptions || { }; + dragOptions = $.extend({ opacity:0.5, revert:true, helper:'clone' }, dragOptions); + + dragOptions.start = _wrap(dragOptions.start, start); + dragOptions.drag = _wrap(dragOptions.drag, function(e, ui) { + _draw($(n, contextNode), ui); + }); + dragOptions.stop = _wrap(dragOptions.stop, + function(e, ui) { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements([floatingEndpoint.canvas, n]); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (jpc.endpoints[idx] == floatingEndpoint) { + + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + if (_reattach) { + jpc.floatingAnchorIndex = null; + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } + else { + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeElement(jpc.canvas); + } + } else { + _removeElement(jpc.canvas); + self.removeConnection(jpc); + } + } + jpc = null; + delete floatingEndpoint; + } + ); + + $(self.canvas, contextNode).draggable(dragOptions); + } + + // connector target + if (params.isTarget && _element.droppable) { + var dropOptions = params.dropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = $.extend({}, dropOptions); + var originalAnchor = null; + dropOptions.drop = _wrap(dropOptions.drop, function(e, ui) { + var id = $(ui.draggable, contextNode).attr("dragId"); + var elId = $(ui.draggable, contextNode).attr("elId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // remove this jpc from the current endpoint + jpc.endpoints[idx].removeConnection(jpc); + if (jpc.suspendedEndpoint) + jpc.suspendedEndpoint.removeConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + _initDraggableIfNecessary(_element, _elementId, params.draggable, {}); + jsPlumb.repaint($(ui.draggable, contextNode).attr("elId")); + delete floatingConnections[id]; + }); + // what to do when something is dropped. + // 1. find the jpc that is being dragged. the target endpoint of the jpc will be the + // one that is being dragged. + // 2. arrange for the floating endpoint to be replaced with this endpoint; make sure + // everything gets registered ok etc. + // 3. arrange for the floating endpoint to be deleted. + // 4. make sure that the stop method of the drag does not cause the jpc to be cleaned up. we want to keep it now. + + // other considerations: when in the hover mode, we should switch the floating endpoint's + // orientation to be the same as the drop target. this will cause the connector to snap + // into the shape it will take if the user drops at that point. + + dropOptions.over = _wrap(dropOptions.over, function(event, ui) { + var id = $(ui.draggable, contextNode).attr("dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + }); + + dropOptions.out = _wrap(dropOptions.out, function(event, ui) { + var id = $(ui.draggable, contextNode).attr("dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + }); + + $(self.canvas, contextNode).droppable(dropOptions); + } + + // woo...add a plumb command to Endpoint. + this.plumb = function(params) { + // not much to think about. the target should be an Endpoint, but what else? + // all the style stuff is on the endpoint itself already. + //todo this should call the main plumb method, just with some different args. + + }; + + return self; + }; + + /** + * jsPlumb public API + */ + var jsPlumb = window.jsPlumb = { + + Defaults : { + Anchors : [ null, null ], + Connector : null, + DragOptions: { }, + DropOptions: { }, + Endpoint : null, + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : null }, + EndpointStyles : [ null, null ], + MaxConnections : null, + PaintStyle : { lineWidth : 10, strokeStyle : 'red' } + }, + + connectorClass : '_jsPlumb_connector', + endpointClass : '_jsPlumb_endpoint', + + Anchors : {}, + Connectors : {}, + Endpoints : {}, + + /** + * adds an endpoint to the element + */ + addEndpoint : function(target, params) { + params = $.extend({}, params); + var el = typeof target == 'string' ? $("#" + target) : target; + var id = $(el).attr("id"); + params.source = el; + _updateOffset(id); + var e = new Endpoint(params); + _addToList(endpointsByElement, id, e); + + var myOffset = offsets[id]; + var myWH = sizes[id]; + + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH); + e.paint(anchorLoc); + return e; + }, + + /** + * wrapper around standard jquery animate function; injects a call to jsPlumb in the + * 'step' function (creating it if necessary). this only supports the two-arg version + * of the animate call in jquery - the one that takes an object as the second arg. + */ + animate : function(el, properties, options) { + var ele = typeof(el)=='string' ? $("#" + el) : el; + var id = ele.attr("id"); + options = options || {}; + options.step = _wrap(options.step, function() { jsPlumb.repaint(id); }); + ele.animate(properties, options); + }, + + /** + * adds a list of endpoints to the element + */ + addEndpoints : function(target, endpoints) { + var results = []; + for (var i = 0; i < endpoints.length; i++) { + results.push(jsPlumb.addEndpoint(target, endpoints[i])); + } + return results; + }, + + /** + * establishes a connection between two elements. + * @param params object containing setup for the connection. see documentation. + */ + connect : function(params) { + if (params.sourceEndpoint && params.sourceEndpoint.isFull()) { + _log("could not add connection; source endpoint is full"); + return; + } + + if (params.targetEndpoint && params.targetEndpoint.isFull()) { + _log("could not add connection; target endpoint is full"); + return; + } + + var jpc = new Connection(params); + + // register endpoints for the element + _addToList(endpointsByElement, jpc.sourceId, jpc.endpoints[0]); + _addToList(endpointsByElement, jpc.targetId, jpc.endpoints[1]); + + jpc.endpoints[0].addConnection(jpc); + jpc.endpoints[1].addConnection(jpc); + + // force a paint + _draw(jpc.source); + + }, + + connectEndpoints : function(params) { + var jpc = Connection(params); + + }, + + /** + * Remove one connection to an element. + * @param sourceId id of the first window in the connection + * @param targetId id of the second window in the connection + * @return true if successful, false if not. + */ + detach : function(sourceId, targetId) { + var f = function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas); + /*_removeElement(jpc.targetEndpointCanvas); + _removeElement(jpc.sourceEndpointCanvas);*/ + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + return true; + } + }; + + // todo: how to cleanup the actual storage? a third arg to _operation? + _operation(sourceId, f); + }, + + /** + * remove all an element's connections. + * @param elId id of the + */ + detachAll : function(elId) { + + var f = function(jpc) { + // todo replace with _cleanupConnection call here. + _removeElement(jpc.canvas); + /*_removeElement(jpc.targetEndpointCanvas); + _removeElement(jpc.sourceEndpointCanvas);*/ + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + }; + _operation(elId, f); + //delete endpointsByElement[elId]; + }, + + /** + * remove all connections. and endpoints? probably not. + */ + detachEverything : function() { + var f = function(jpc) { + _removeElement(jpc.canvas); + /*_removeElement(jpc.targetEndpointCanvas); + _removeElement(jpc.sourceEndpointCanvas);*/ + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + }; + + _operationOnAll(f); + + /*delete endpointsByElement; + endpointsByElement = {};*/ + }, + + /** + * Set an element's connections to be hidden. + */ + hide : function(elId) { + _setVisible(elId, "none"); + }, + + /** + * Creates an anchor with the given params. + * x - the x location of the anchor as a fraction of the total width. + * y - the y location of the anchor as a fraction of the total height. + * orientation - an [x,y] array indicating the general direction a connection from the anchor should go in. + * offsets - an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. optional. defaults to [0,0]. + */ + makeAnchor : function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards compatibility if we are given only one value we assume it's a call in the old form. + var params = {}; + if (arguments.length == 1) $.extend(params, x); + else { + params = {x:x, y:y}; + if (arguments.length >= 4) { + params.orientation = [arguments[2], arguments[3]]; + } + if (arguments.length == 6) params.offsets = [arguments[4], arguments[5]]; + } + return new Anchor(params); + }, + + + /** + * repaint element and its connections. element may be an id or the actual jQuery object. + * this method gets new sizes for the elements before painting anything. + */ + repaint : function(el) { + + var _processElement = function(el) { + var ele = typeof(el)=='string' ? $("#" + el) : el; + _draw(ele); + }; + + // TODO: support a jQuery result object too! + + // support both lists... + if (typeof el =='object') { + for (var i = 0; i < el.length; i++) + _processElement(el[i]); + } // ...and single strings. + else _processElement(el); + }, + + /** + * repaint all connections. + */ + repaintEverything : function() { + for (var elId in endpointsByElement) { + _draw($("#" + elId)); + } + }, + + removeEndpoint : function(elId, endpoint) { + var ebe = endpointsByElement[elId]; + if (ebe) { + // var i = ebe.indexOf(endpoint); + // alert('element has ' + ebe.length); + //if (i > -1) { + //ebe.splice(i, 1); + if(_removeFromList(endpointsByElement, elId, endpoint)) + _removeElement(endpoint.canvas); + //} + } + }, + + /** + * sets/unsets automatic repaint on window resize. + */ + setAutomaticRepaint : function(value) { + automaticRepaint = value; + }, + + /** + * Sets the default size jsPlumb will use for a new canvas (we create a square canvas so + * one value is all that is required). This is a hack for IE, because ExplorerCanvas seems + * to need for a canvas to be larger than what you are going to draw on it at initialisation + * time. The default value of this is 1200 pixels, which is quite large, but if for some + * reason you're drawing connectors that are bigger, you should adjust this value appropriately. + */ + setDefaultNewCanvasSize : function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }, + + /** + * Sets whether or not a given element is draggable, regardless of what any plumb command + * may request. + */ + setDraggable: _setDraggable, + + /** + * Sets whether or not elements are draggable by default. Default for this is true. + */ + setDraggableByDefault: function(draggable) { + _draggableByDefault = draggable; + }, + + setDebugLog: function(debugLog) { + log = debugLog; + }, + + /** + * Sets the function to fire when the window size has changed and a repaint was fired. + */ + setRepaintFunction : function(f) { + repaintFunction = f; + }, + + /** + * Set an element's connections to be visible. + */ + show : function(elId) { + _setVisible(elId, "block"); + }, + + /** + * helper to size a canvas. + */ + sizeCanvas : function(canvas, x, y, w, h) { + canvas.style.height = h + "px"; canvas.height = h; + canvas.style.width = w + "px"; canvas.width = w; + canvas.style.left = x + "px"; canvas.style.top = y + "px"; + }, + + /** + * gets some test hooks. nothing writable. + */ + getTestHarness : function() { + return { + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + } + }; + }, + + /** + * Toggles visibility of an element's connections. kept for backwards compatibility + */ + toggle : _toggleVisible, + + /** + * new name for the old toggle method. + */ + toggleVisible : _toggleVisible, + + /** + * Toggles draggability (sic) of an element. + */ + toggleDraggable : _toggleDraggable, + + /** + * Unloads jsPlumb, deleting all storage. You should call this + */ + unload : function() { + delete endpointsByElement; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + document.body.removeChild(_jsPlumbContextNode); + } + }; + +})(); + +// jQuery plugin code +(function($){ + /** + * plumbs the results of the selector to some target, using the given options if supplied, + * or the defaults otherwise. + */ + $.fn.plumb = function(options) { + var options = $.extend({}, options); + + return this.each(function() + { + var params = $.extend({source:$(this)}, options); + jsPlumb.connect(params); + }); + }; + + /** + * detaches the results of the selector from the given target or list of targets - 'target' + * may be a String or a List. + */ + $.fn.detach = function(target) { + return this.each(function() + { + var id = $(this).attr("id"); + if (typeof target == 'string') target = [target]; + for (var i = 0; i < target.length; i++) + jsPlumb.detach(id, target[i]); + }); + }; + + /** + * detaches the results from the selector from all connections. + */ + $.fn.detachAll = function() { + return this.each(function() + { + var id = $(this).attr("id"); + jsPlumb.detachAll(id); + }); + }; + + /** + * adds an endpoint to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoint = function(options) { + var addedEndpoints = []; + this.each(function() + { + //var params = $.extend({source:$(this)}, options); + addedEndpoints.push(jsPlumb.addEndpoint($(this).attr("id"), options)); + }); + return addedEndpoints; + }; + + /** + * adds a list of endpoints to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoints = function(endpoints) { + var addedEndpoints = []; + return this.each(function() + { + var e = jsPlumb.addEndpoints($(this).attr("id"), endpoints); + for (var i = 0; i < e.length; i++) addedEndpoints.push(e[i]); + }); + return addedEndpoints; + }; + + /** + * remove the endpoint, if it exists, deleting its UI elements etc. + */ + $.fn.removeEndpoint = function(endpoint) { + this.each(function() + { + jsPlumb.removeEndpoint($(this).attr("id"), endpoint); + }); + }; + +})(jQuery); diff --git a/archive/jquery.jsPlumb-1.1.1-RC1.js b/archive/jquery.jsPlumb-1.1.1-RC1.js new file mode 100644 index 000000000..1a3300c46 --- /dev/null +++ b/archive/jquery.jsPlumb-1.1.1-RC1.js @@ -0,0 +1,1785 @@ +/* + * jsPlumb 1.1.1-RC1 + * + * Provides a way to visually connect elements on an HTML page. + * + * 1.1.1 contains bugfixes and API additions on 1.1.0. + * + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + */ + +(function() { + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + + var log = null; + + var repaintFunction = function() { jsPlumb.repaintEverything(); }; + var automaticRepaint = true; + function repaintEverything() { + if (automaticRepaint) + repaintFunction(); + }; + var resizeTimer = null; + + /*TODO: abstract this out from jQuery too! but how...because jsPlumb is not loaded yet. + $(window).bind('resize', function() { + if (resizeTimer) clearTimeout(resizeTimer); + resizeTimer = setTimeout(repaintEverything, 100); + });*/ + + /** + * map of element id -> endpoint lists. an element can have an arbitrary number of endpoints on it, + * and not all of them have to be connected to anything. + */ + var endpointsByElement = {}; + var offsets = []; + var floatingConnections = {}; + var draggableStates = {}; + var _draggableByDefault = true; + var sizes = []; + + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + var _findIndex = function( a, v, b, s ) { + + var _eq = function(o1, o2) { + if (o1 === o2) return true; + else if (typeof o1 == 'object' && typeof o2 == 'object') { + var same = true; + for(var propertyName in o1) { + if(!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for(var propertyName in o2) { + if(!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for( var i = +b || 0, l = a.length; i < l; i++ ) { + if( _eq(a[i], v)) return i; + } + return -1; + }; + + /** + * helper method to add an item to a list, creating the list if it does not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + }; + + /** + * Handles the dragging of an element. + * @param element jQuery element + * @param ui UI object from current library's event system + */ + var _draw = function(element, ui) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints) { + _updateOffset(id, ui); + var myOffset = offsets[id]; + var myWH = sizes[id]; + // loop through endpoints for this element + for (var i = 0; i < endpoints.length; i++) { + var e = endpoints[i]; + var anchorLoc = endpoints[i].anchor.compute([myOffset.left, myOffset.top], myWH); + e.paint(anchorLoc); + var l = e.connections; + for (var j = 0; j < l.length; j++) + l[j].paint(id, ui); // ...and paint them. + } + } + }; + + /** + * helper function: the second argument is a function taking two args - the first is a + * jquery element, and the second is the element's id. + * + * the first argument may be one of three things: + * + * 1. a string, in the form "window1", for example. an element's id. if your id starts with a + * hash then jsPlumb does not append its own hash too... + * 2. a jquery element, already resolved using $(...). + * 3. a list of strings/jquery elements. + */ + var _elementProxy = function(element, fn) { + var retVal = null; + if (typeof element == 'object' && element.length) { + retVal = []; + for (var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]); + var id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } + else { + var el = _getElementObject(element); + var id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + + return retVal; + }; + + /** + * gets the named attribute from the given element (id or element object) + */ + var _getAttribute = function(el, attName) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + return jsPlumb.CurrentLibrary.getAttribute(ele, attName); + }; + + var _setAttribute = function(el, attName, attValue) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + jsPlumb.CurrentLibrary.setAttribute(ele, attName, attValue); + }; + + var _addClass = function(el, clazz) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + jsPlumb.CurrentLibrary.addClass(ele, clazz); + }; + + var _getElementObject = function(elId) { + return jsPlumb.CurrentLibrary.getElementObject(elId); + }; + + var _getOffset = function(el) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + return jsPlumb.CurrentLibrary.getOffset(ele); + }; + + var _getSize = function(el) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + return jsPlumb.CurrentLibrary.getSize(ele); + }; + + var _initDraggable = function(el, options) { + return jsPlumb.CurrentLibrary.initDraggable(el, options); + }; + + var _isDragSupported = function(el) { + return jsPlumb.CurrentLibrary.isDragSupported(el); + }; + + var _initDroppable = function(el, options) { + return jsPlumb.CurrentLibrary.initDroppable(el, options); + }; + + var _isDropSupported = function(el) { + return jsPlumb.CurrentLibrary.isDropSupported(el); + }; + + var _animate= function(el, properties, options) { + return jsPlumb.CurrentLibrary.animate(el, properties, options); + }; + + var _appendCanvas = function(canvas) { + document.body.appendChild(canvas); + }; + + /** + * gets an id for the given element, creating and setting one if necessary. + */ + var _getId = function(element) { + var id = _getAttribute(element, "id"); + if (!id) { + id = "_jsPlumb_" + new String((new Date()).getTime()); + _setAttribute(element, "id", id); + } + return id; + }; + + /** + * inits a draggable if it's not already initialised. + * todo: if the element was draggable already, like from some non-jsPlumb call, wrap the drag function. + */ + var _initDraggableIfNecessary = function(element, elementId, isDraggable, dragOptions) { + // dragging + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable && _isDragSupported(element)) { + var options = dragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend({}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var initDrag = function(element, elementId, dragFunc) { + options[dragEvent] = _wrap(options[dragEvent], dragFunc); + var draggable = draggableStates[elementId]; + options.disabled = draggable == null ? false : !draggable; + _initDraggable(element, options); + }; + initDrag(element, elementId, function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + } + }; + + var _log = function(msg) { + // not implemented. yet. + } + + /** + * helper to create a canvas. + * @param clazz optional class name for the canvas. + */ + var _newCanvas = function(clazz) { + var canvas = document.createElement("canvas"); + _appendCanvas(canvas); + canvas.style.position="absolute"; + if (clazz) { canvas.className=clazz; } + _getId(canvas); // set an id. + + if (ie) { + // for IE we have to set a big canvas size. actually you can override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + /** + * performs the given function operation on all the connections found for the given element + * id; this means we find all the endpoints for the given element, and then for each endpoint + * find the connectors connected to it. then we pass each connection in to the given + * function. + */ + var _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + for (var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }; + /** + * perform an operation on all elements. + */ + var _operationOnAll = function(func) { + for (var elId in endpointsByElement) { + _operation(elId, func); + } + }; + /** + * helper to remove an element from the DOM. + */ + var _removeElement = function(element) { + if (element != null) { + try { document.body.removeChild(element); } + catch (e) { } + } + }; + /** + * helper to remove a list of elements from the DOM. + */ + var _removeElements = function(elements) { + for (var i in elements) + _removeElement(elements[i]); + }; + /** + * helper method to remove an item from a list. + */ + var _removeFromList = function(map, key, value) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete( l[i] ); + l.splice( i, 1 ); + return true; + } + } + return false; + }; + /** + * Sets whether or not the given element(s) should be draggable, regardless of what a particular + * plumb command may request. + * + * @param element May be a string, a jQuery elements, or a list of strings/jquery elements. + * @param draggable Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + var _helper = function(el, id) { + draggableStates[id] = draggable; + if (_isDragSupported(el)) { + // TODO: not library agnostic yet. + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + //el.draggable("option", "disabled", !draggable); + } + }; + + return _elementProxy(element, _helper); + }; + /** + * private method to do the business of hiding/showing. + * @param el either Id of the element in question or a jquery object for the element. + * @param state String specifying a value for the css 'display' property ('block' or 'none'). + */ + var _setVisible = function(el, state) { + var elId = _getAttribute(el, "id"); + var f = function(jpc) { + jpc.canvas.style.display = state; + }; + _operation(elId, f); + }; + /** + * toggles the draggable state of the given element(s). + * @param el either an id, or a jquery object, or a list of ids/jquery objects. + */ + var _toggleDraggable = function(el) { + var fn = function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + //el.draggable("option", "disabled", !state); + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }; + return _elementProxy(el, fn); + }; + /** + * private method to do the business of toggling hiding/showing. + * @param elId Id of the element in question + */ + var _toggleVisible = function(elId) { + var f = function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }; + + _operation(elId, f); + + //todo this should call _elementProxy, and pass in the _operation(elId, f) call as a function. cos _toggleDraggable does that. + }; + /** + * updates the offset and size for a given element, and stores the values. + * if 'offset' is not null we use that (it would have been passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we use the offset, outerWidth and outerHeight methods to get + * the current values. + */ + var _updateOffset = function(elId, offset, recalc) { + if (recalc || offset == null) { // if forced repaint or no offset available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + sizes[elId] = _getSize(elId); + offsets[elId] = _getOffset(elId); + } else { + offsets[elId] = offset; + } + }; + + /** + * wraps one function with another, creating a placeholder for the wrapped function + * if it was null. this is used to wrap the various drag/drop event functions - to allow + * jsPlumb to be notified of important lifecycle events without imposing itself on the user's + * drap/drop functionality. + * TODO: determine whether or not we should try/catch the plumb function, so that the cascade function is always executed. + */ + var _wrap = function(cascadeFunction, plumbFunction) { + cascadeFunction = cascadeFunction || function() { }; + return function() { + plumbFunction.apply(this, arguments); + cascadeFunction.apply(this, arguments); + }; + } + + /* + Class: Anchor + Models a position relative to the origin of some element that an Endpoint can be located. + */ + /* + Function: Anchor + + Constructor for the Anchor class + + Parameters: + params - Anchor parameters. This should contain three values, and may optionally have an 'offsets' argument: + + - x : the x location of the anchor as a fraction of the total width. + - y : the y location of the anchor as a fraction of the total height. + - orientation : an [x,y] array indicating the general direction a connection from the anchor should go in. for more info on this, see the documentation, or the docs in jquery-jsPlumb-defaults-XXX.js for the default Anchors. + - offsets : an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. may be null. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; this.y = params.y || 0; + var orientation = params.orientation || [0,0]; + this.offsets = params.offsets || [0,0]; + this.compute = function(xy, wh, txy, twh) { + return [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + } + this.getOrientation = function() { return orientation; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from its position relative to the anchor it is floating relative to. + * + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for purposes of calculating the orientation. + var ref = params.reference; + // these are used to store the current relative position of our anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating anchor is hovering over another anchor. + var orientation = null; + + this.compute = function(xy, wh, txy, twh) { + // set these for the getOrientation method to use. + xDir = 0;//xy[0] < txy[0] ? -1 : xy[0] == txy[0] ? 0 : 1; + yDir = 0;//xy[1] < txy[1] ? -1 : xy[1] == txy[1] ? 0 : 1; + return [xy[0], xy[1]]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other anchor: if it declares zero for some direction, we declare zero too. + // this might not be the most awesome. perhaps we can come up with a better way. it's just so that the line we draw looks + // like it makes sense. maybe this wont make sense. + return [Math.abs(o[0]) * xDir * -1, Math.abs(o[1]) * yDir * -1]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering over another anchor; + * we want to assume that anchor's orientation for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no longer hovering + * over another anchor; we should resume calculating orientation as we normally do. + */ + this.out = function() { + orientation = null; + }; + }; + + // ************** connection + // **************************************** + /** + * allowed params: + * source: source element (string or a jQuery element) (required) + * target: target element (string or a jQuery element) (required) + * + * anchors: optional array of anchor placements. defaults to BottomCenter for source + * and TopCenter for target. + */ + var Connection = function(params) { + + // ************** get the source and target and register the connection. ******************* + var self = this; + // get source and target as jQuery objects + this.source = _getElementObject(params.source); + this.target = _getElementObject(params.target); + this.sourceId = _getAttribute(this.source, "id"); + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + + // init endpoints + this.endpoints = []; + this.endpointStyles = []; + var prepareEndpoint = function(existing, index, params) { + if (existing) self.endpoints[index] = existing; + else { + if(!params.endpoints) params.endpoints = [null,null]; + var ep = params.endpoints[index] || params.endpoint || jsPlumb.Defaults.Endpoints[index] || jsPlumb.Defaults.Endpoint|| new jsPlumb.Endpoints.Dot(); + if (!params.endpointStyles) params.endpointStyles = [null,null]; + var es = params.endpointStyles[index] || params.endpointStyle || jsPlumb.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyle; + var a = params.anchors ? params.anchors[index] : jsPlumb.Defaults.Anchors[index] || jsPlumb.Anchors.BottomCenter; + self.endpoints[index] = new Endpoint({style:es, endpoint:ep, connections:[self], anchor:a }); + } + }; + + prepareEndpoint(params.sourceEndpoint, 0, params); + prepareEndpoint(params.targetEndpoint, 1, params); + + this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + this.paintStyle = this.endpoints[0].connectionStyle || this.endpoints[1].connectionStyle || params.paintStyle || jsPlumb.Defaults.PaintStyle; + + _updateOffset(this.sourceId); + _updateOffset(this.targetId); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var anchorLoc = this.endpoints[0].anchor.compute([myOffset.left, myOffset.top], myWH); + this.endpoints[0].paint(anchorLoc); + myOffset = offsets[this.targetId]; myWH = sizes[this.targetId]; + anchorLoc = this.endpoints[1].anchor.compute([myOffset.left, myOffset.top], myWH); + this.endpoints[1].paint(anchorLoc); + + // *************** create canvas on which the connection will be drawn ************ + var canvas = _newCanvas(jsPlumb.connectorClass); + this.canvas = canvas; + + /** + * paints the connection. + * @param elId Id of the element that is in motion + * @param ui current library's event system ui object (present if we came from a drag to get here) + * @param recalc whether or not to recalculate element sizes. this is true if a repaint caused this to be painted. + */ + this.paint = function(elId, ui, recalc) { + + if (log) log.debug("Painting Connection; element in motion is " + elId + "; ui is [" + ui + "]; recalc is [" + recalc + "]"); + + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = !(elId == this.sourceId); + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + if (this.canvas.getContext) { + + _updateOffset(elId, ui, recalc); + if (recalc) _updateOffset(tId); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var myOffset = offsets[elId]; + var otherOffset = offsets[tId]; + var myWH = sizes[elId]; + var otherWH = sizes[tId]; + + var ctx = canvas.getContext('2d'); + var sAnchorP = this.endpoints[sIdx].anchor.compute([myOffset.left, myOffset.top], myWH, [otherOffset.left, otherOffset.top], otherWH); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.compute([otherOffset.left, otherOffset.top], otherWH, [myOffset.left, myOffset.top], myWH); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, this.paintStyle.lineWidth); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + jsPlumb.extend(ctx, this.paintStyle); + + if (this.paintStyle.gradient && !ie) { + var g = swap ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + for (var i = 0; i < this.paintStyle.gradient.stops.length; i++) + g.addColorStop(this.paintStyle.gradient.stops[i][0],this.paintStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + + this.connector.paint(dim, ctx); + } + }; + + this.repaint = function() { + this.paint(this.sourceId, null, true); + }; + + _initDraggableIfNecessary(self.source, self.sourceId, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, self.targetId, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + }; + + /* + Class: Endpoint + + Models an endpoint. Can have one to N connections emanating from it (although how to handle that in the UI is + a very good question). also has a Canvas and paint style. + + */ + + /* + Function: Endpoint + + This is the Endpoint class constructor. + + Parameters: + + anchor - anchor for the endpoint, of type jsPlumb.Anchor. may be null. + endpoint - endpoint object, of type jsPlumb.Endpoint. may be null. + style - endpoint style, a js object. may be null. + source - element the endpoint is attached to, of type jquery object. Required. + canvas - canvas element to use. may be, and most often is, null. + connections - optional list of connections to configure the endpoint with. + isSource - boolean. indicates the endpoint can act as a source of new connections. optional. + dragOptions - if isSource is set to true, you can supply arguments for the jquery draggable method. optional. + connectionStyle - if isSource is set to true, this is the paint style for connections from this endpoint. optional. + connector - optional connector type to use. + isTarget - boolean. indicates the endpoint can act as a target of new connections. optional. + dropOptions - if isTarget is set to true, you can supply arguments for the jquery droppable method. optional. + reattach - optional boolean that determines whether or not the connections reattach after they + have been dragged off an endpoint and left floating. defaults to false: connections + dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + params = params || {}; + // make a copy. then we can use the wrapper function. + jsPlumb.extend({}, params); + var self = this; + self.anchor = params.anchor || jsPlumb.Anchors.TopCenter; + var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot(); + var _style = params.style || jsPlumb.Defaults.EndpointStyle; + this.connectionStyle = params.connectionStyle; + this.connector = params.connector; + var _element = params.source; + var _elementId = _getAttribute(_element, "id"); + var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of. + this.canvas = params.canvas || _newCanvas(jsPlumb.endpointClass); + this.connections = params.connections || []; + var _reattach = params.reattach || false; + var floatingEndpoint = null; + this.addConnection = function(connection) { + self.connections.push(connection); + }; + this.removeConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) + self.connections.splice(idx, 1); + }; + /** + * returns whether or not this endpoint is connected to the given endpoint. + * @param endpoint Endpoint to test. + * @since 1.1.1 + * + * todo: needs testing. will this work if the endpoint passed in is the source? + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + this.isFloating = function() { return floatingEndpoint != null; }; + /** + * first pass at default ConnectorSelector: returns the first connection, if we have any. + * modified a little, 5/10/2010: now it only returns a connector if we have not got equal to or more than _maxConnector connectors + * attached. otherwise it is assumed a new connector is ok. but note with this setup we can't select any other connection than the first + * one. what if this could return a list? that implies everything should work with a list - dragging etc. could be nasty. could also + * be cool. + */ + var connectorSelector = function() { + return self.connections.length == 0 || self.connections.length < _maxConnections ? null : self.connections[0]; + }; + + this.isFull = function() { return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); }; + + /** + * paints the Endpoint, recalculating offset and anchor positions if necessary. + */ + this.paint = function(anchorPoint, connectorPaintStyle, canvas) { + + if (log) log.debug("Painting Endpoint with elementId [" + _elementId + "]; anchorPoint is [" + anchorPoint + "]"); + + if (anchorPoint == null) { + // do we always want to force a repaint here? i dont think so! + var xy = offsets[_elementId]; + var wh = sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset(_elementId); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + anchorPoint = self.anchor.compute([xy.left, xy.top], wh); + } + _endpoint.paint(anchorPoint, self.anchor.getOrientation(), canvas || self.canvas, _style, connectorPaintStyle || _style); + }; + + + // is this a connection source? we make it draggable and have the drag listener + // maintain a connection with a floating endpoint. + if (params.isSource && _isDragSupported(_element)) { + + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + + // first the question is, how many connections are on this endpoint? if it's only one, then excellent. otherwise we will either need a way + // to select one connection from the list, or drag them all. if we had a pluggable 'ConnectorSelector' interface we could probably + // provide a way for people to implement their own UI components to do the connector selection. the question in that particular case would be how much + // information the interface needs from jsPlumb at execution time. if, however, we leave out the connector selection, and drag them all, + // that wouldn't be too hard to organise. perhaps that behaviour would be on a switch for the endpoint, or perhaps the ConnectorSelector + // interface returns a List, with the default implementation just returning everything. i think i like that. + // + // let's say for now that there is just one endpoint, cos we need to get this going before we can consider a list of them anyway. + // the major difference between that case and the case of a new endpoint is actually quite small - it's a question of where the + // Connection comes from. for a new one, we create a new one. obviously. otherwise we just get the jpc from the Endpoint + // (remember we're only assuming one connection right now). so all of the UI stuff we do to create the floating endpoint etc + // will still be valid, but when we stop dragging, we'll have to do something different. if we stop with a valid drop i think it will + // be the same process. but if we stop with an invalid drop we have to reset the Connection to how it was when we got it. + var start = function() { + n = document.createElement("div"); + _appendCanvas(n); + // create and assign an id, and initialize the offset. + var id = "" + new String(new Date().getTime()); + _setAttribute(_getElementObject(n), "id", id); + _updateOffset(id); + // store the id of the dragging div and the source element. the drop function + // will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor({reference:self.anchor}); + floatingEndpoint = new Endpoint({ + style:_style, + endpoint:_endpoint, + anchor:floatingAnchor, + source:n + }); + + jpc = connectorSelector(); + if (jpc == null) { + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = new Connection({ + sourceEndpoint:self, + targetEndpoint:floatingEndpoint, + source:_getElementObject(_element), + target:_getElementObject(n), + anchors:[self.anchor, floatingAnchor], + paintStyle : params.connectionStyle, // this can be null. Connection will use the default. + connector: params.connector + }); + // todo ...unregister on stop + self.addConnection(jpc); + } else { + existingJpc = true; + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; + // probably we should remove the connection? and add it back if the user + // does not drop it somewhere proper. + self.removeConnection(jpc); + if (anchorIdx == 0){ + existingJpcParams = [jpc.source, jpc.sourceId]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + }else { + existingJpcParams = [jpc.target, jpc.targetId]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // todo unregister on stop + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + }; + + var dragOptions = params.dragOptions || { }; + + var defaultOpts = jsPlumb.extend({}, jsPlumb.CurrentLibrary.defaultDragOptions); + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + var startEvent = jsPlumb.CurrentLibrary.dragEvents['start']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements([floatingEndpoint.canvas, n]); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + if (_reattach) { + jpc.floatingAnchorIndex = null; + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } + else { + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeElement(jpc.canvas); + } + } else { + _removeElement(jpc.canvas); + self.removeConnection(jpc); + } + } + jpc = null; + delete floatingEndpoint; + } + ); + + var i = _getElementObject(self.canvas); + _initDraggable(i, dragOptions); + }; + + // connector target + if (params.isTarget && _isDropSupported(_element)) { + var dropOptions = params.dropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend({}, dropOptions); + var originalAnchor = null; + dropOptions.drop = _wrap(dropOptions.drop, function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable), "dragId"); + var elId = _getAttribute(_getElementObject(draggable),"elId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // remove this jpc from the current endpoint + jpc.endpoints[idx].removeConnection(jpc); + if (jpc.suspendedEndpoint) + jpc.suspendedEndpoint.removeConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + _initDraggableIfNecessary(_element, _elementId, params.draggable, {}); + jsPlumb.repaint(elId); + + delete floatingConnections[id]; + }); + // what to do when something is dropped. + // 1. find the jpc that is being dragged. the target endpoint of the jpc will be the + // one that is being dragged. + // 2. arrange for the floating endpoint to be replaced with this endpoint; make sure + // everything gets registered ok etc. + // 3. arrange for the floating endpoint to be deleted. + // 4. make sure that the stop method of the drag does not cause the jpc to be cleaned up. we want to keep it now. + + // other considerations: when in the hover mode, we should switch the floating endpoint's + // orientation to be the same as the drop target. this will cause the connector to snap + // into the shape it will take if the user drops at that point. + + dropOptions.over = _wrap(dropOptions.over, function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + }); + + dropOptions.out = _wrap(dropOptions.out, function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + }); + + _initDroppable(_getElementObject(self.canvas), dropOptions); + } + + return self; + }; + + /* + Class: jsPlumb + + This is the main entry point to jsPlumb. There are a bunch of static methods you can + use to connect/disconnect etc. Some methods are also added to jQuery's "$.fn" object itself, but + in general jsPlumb is moving away from that model, preferring instead the static class + approach. One of the reasons for this is that with no methods added to the jQuery $.fn object, + it will be easier to provide support for other libraries such as MooTools. + */ + var jsPlumb = window.jsPlumb = { + + /* + Property: Defaults + + These are the default settings for jsPlumb, that is what will be used if you do not supply specific pieces of information + to the various API calls. A convenient way to implement your own look and feel can be to override these defaults by including a script + somewhere after the jsPlumb include, but before you make any calls to jsPlumb, for instance in this example we set the PaintStyle to be + a blue line of 27 pixels: + + > jsPlumb.Defaults.PaintStyle = { lineWidth:27, strokeStyle:'blue' } + + */ + Defaults : { + Anchors : [ null, null ], + Connector : null, + DragOptions: { }, + DropOptions: { }, + Endpoint : null, + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : null }, + EndpointStyles : [ null, null ], + MaxConnections : null, + PaintStyle : { lineWidth : 10, strokeStyle : 'red' } + }, + + /* + Property: connectorClass + + The CSS class to set on Connection canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + connectorClass : '_jsPlumb_connector', + + /* + Property: endpointClass + + The CSS class to set on Endpoint canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + endpointClass : '_jsPlumb_endpoint', + + /* + Property: Anchors + + Default jsPlumb Anchors. These are supplied in the file jquery.jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form jquery.jsPlumb-all-x.x.x.js. You can provide your own Anchors by supplying them in a script that is loaded after jsPlumb, for instance: + + > jsPlumb.Anchors.MyAnchor = { ....anchor code here. see the documentation. } + */ + Anchors : {}, + + /* + Property: Connectors + + Default jsPlumb Connectors. These are supplied in the file jquery.jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form jquery.jsPlumb-all-x.x.x.js. You can provide your own Connectors by supplying them in a script that is loaded after jsPlumb, for instance: + + > jsPlumb.Connectors.MyConnector = { ....connector code here. see the documentation. } + */ + Connectors : {}, + + /* + Property: Endpoints + + Default jsPlumb Endpoints. These are supplied in the file jquery.jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form jquery.jsPlumb-all-x.x.x.js. You can provide your own Endpoints by supplying them in a script that is loaded after jsPlumb, for instance: + + > jsPlumb.Endpoints.MyEndpoint = { ....endpoint code here. see the documentation. } + */ + Endpoints : {}, + + /* + Function: addEndpoint + + Adds an Endpoint to a given element. + + Parameters: + target - Element to add the endpoint to. either an element id, or a jQuery object representing some element. + params - Object containing Endpoint options (more info required) + + Returns: + + The newly created Endpoint. + + See Also: + + + */ + addEndpoint : function(target, params) { + params = jsPlumb.extend({}, params); + var el = _getElementObject(target); + var id = _getAttribute(target,"id"); + params.source = el; + _updateOffset(id); + var e = new Endpoint(params); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id]; + var myWH = sizes[id]; + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH); + e.paint(anchorLoc); + return e; + }, + + /* + Function: addEndpoint + + Adds a list of Endpoints to a given element. + + Parameters: + + target - element to add the endpoint to. either an element id, or a jQuery object representing some element. + endpoints - List of objects containing Endpoint options. one Endpoint is created for each entry in this list. + + Returns: + + List of newly created Endpoints, one for each entry in the 'endpoints' argument. + + See Also: + + + */ + addEndpoints : function(target, endpoints) { + var results = []; + for (var i = 0; i < endpoints.length; i++) { + results.push(jsPlumb.addEndpoint(target, endpoints[i])); + } + return results; + }, + + /* + Function: animate + + Wrapper around standard jQuery animate function; injects a call to jsPlumb in the 'step' function (creating it if necessary). + This only supports the two-arg version of the animate call in jQuery - the one that takes an 'options' object as the second arg. + + Parameters: + + el - Element to animate. Either an id, or a jQuery object representing the element. + properties - The 'properties' argument you want passed to the standard jQuery animate call. + options - The 'options' argument you want passed to the standard jQuery animate call. + + Returns: + + void + */ + animate : function(el, properties, options) { + var ele = _getElementObject(el); + var id = _getAttribute(el,"id"); + options = options || {}; + options.step = _wrap(options.step, function() { jsPlumb.repaint(id); }); + _animate(ele, properties, options); + }, + + /* + Function: connect + + Establishes a connection between two elements. + + Parameters: + + params - Object containing setup for the connection. see documentation. + + Returns: + + The newly created Connection. + + */ + connect : function(params) { + if (params.sourceEndpoint && params.sourceEndpoint.isFull()) { + _log("could not add connection; source endpoint is full"); + return; + } + + if (params.targetEndpoint && params.targetEndpoint.isFull()) { + _log("could not add connection; target endpoint is full"); + return; + } + + var jpc = new Connection(params); + + // register endpoints for the element. todo: is the test below sufficient? or should we test if the endpoint is already in the list, + // and add it only then? perhaps _addToList could be overloaded with a a 'test for existence first' parameter? + //_addToList(endpointsByElement, jpc.sourceId, jpc.endpoints[0]); + //_addToList(endpointsByElement, jpc.targetId, jpc.endpoints[1]); + + if (!params.sourceEndpoint) _addToList(endpointsByElement, jpc.sourceId, jpc.endpoints[0]); + if (!params.targetEndpoint) _addToList(endpointsByElement, jpc.targetId, jpc.endpoints[1]); + + jpc.endpoints[0].addConnection(jpc); + jpc.endpoints[1].addConnection(jpc); + + // force a paint + _draw(jpc.source); + + return jpc; + + }, + + /** + * not implemented yet. params object will have sourceEndpoint and targetEndpoint members; these will be Endpoints. + connectEndpoints : function(params) { + var jpc = Connection(params); + + },*/ + + /* + Function: detach + + Removes a connection. + + Parameters: + + sourceId - Id of the first element in the connection. A String. + targetId - iI of the second element in the connection. A String. + + Returns: + + true if successful, false if not. + */ + detach : function(sourceId, targetId) { + var f = function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + return true; + } + }; + + // todo: how to cleanup the actual storage? a third arg to _operation? + _operation(sourceId, f); + }, + + /* + Function: detachAll + + Removes all an element's connections. + + Parameters: + + el - either the id of the element, or a jQuery object for the element. + + Returns: + + void + */ + detachAll : function(el) { + var id = _getAttribute(el, "id"); + var f = function(jpc) { + // todo replace with _cleanupConnection call here. + _removeElement(jpc.canvas); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + }; + _operation(id, f); + //delete endpointsByElement[id]; ?? + }, + + /* + Function: detachEverything + + Remove all Connections from all elements, but leaves Endpoints in place. + + Returns: + + void + + See Also: + + + */ + detachEverything : function() { + var f = function(jpc) { + _removeElement(jpc.canvas); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + }; + + _operationOnAll(f); + + /*delete endpointsByElement; //?? + endpointsByElement = {};*/ //?? + }, + + extend : function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }, + + /* + Function: hide + + Sets an element's connections to be hidden. + + Parameters: + + el - either the id of the element, or a jQuery object for the element. + + Returns: + + void + */ + hide : function(el) { + _setVisible(el, "none"); + }, + + /* + Function: makeAnchor + + Creates an anchor with the given params. + + Parameters: + + x - the x location of the anchor as a fraction of the total width. + y - the y location of the anchor as a fraction of the total height. + xOrientation - value indicating the general direction a connection from the anchor should go in, in the x direction. + yOrientation - value indicating the general direction a connection from the anchor should go in, in the y direction. + xOffset - a fixed offset that should be applied in the x direction that should be applied after the x position has been figured out. optional. defaults to 0. + yOffset - a fixed offset that should be applied in the y direction that should be applied after the y position has been figured out. optional. defaults to 0. + + Returns: + + The newly created Anchor. + */ + makeAnchor : function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards compatibility if we are given only one value we assume it's a call in the old form. + var params = {}; + if (arguments.length == 1) jsPlumb.extend(params, x); + else { + params = {x:x, y:y}; + if (arguments.length >= 4) { + params.orientation = [arguments[2], arguments[3]]; + } + if (arguments.length == 6) params.offsets = [arguments[4], arguments[5]]; + } + return new Anchor(params); + }, + + + /* + Function: repaint + + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + + el - either the id of the element or a jQuery object representing the element. + + Returns: + + void + + See Also: + + + */ + repaint : function(el) { + + var _processElement = function(el) { + var ele = _getElementObject(el); + _draw(ele); + }; + + // TODO: support a jQuery result object too! + + // support both lists... + if (typeof el =='object') { + for (var i = 0; i < el.length; i++) + _processElement(el[i]); + } // ...and single strings. + else _processElement(el); + }, + + /* + Function: repaintEverything + + Repaints all connections. + + Returns: + + void + + See Also: + + + */ + repaintEverything : function() { + for (var elId in endpointsByElement) { + _draw(_getElementObject(elId)); + } + }, + + /* + Function: removeAllEndpoints + + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + + el - either an element id, or a jQuery object for an element. + + Returns: + + void + + See Also: + + + */ + removeAllEndpoints : function(el) { + var elId = _getAttribute(el, "id"); + // first remove all Connections. + jsPlumb.detachAll(elId); + var ebe = endpointsByElement[elId]; + for (var i in ebe) { + _removeElement(ebe[i].canvas); + } + endpointsByElement[elId] = []; + }, + + /* + Function: removeEndpoint + + Removes the given Endpoint from the given element. + + Parameters: + + el - either an element id, or a jQuery object for an element. + endpoint - Endpoint to remove. this is an Endpoint object, such as would have been returned from a call to addEndpoint. + + Returns: + + void + + See Also: + + + */ + removeEndpoint : function(el, endpoint) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + if (ebe) { + if(_removeFromList(endpointsByElement, elId, endpoint)) + _removeElement(endpoint.canvas); + } + }, + + /* + Function: setAutomaticRepaint + + Sets/unsets automatic repaint on window resize. + + Parameters: + + value - whether or not to automatically repaint when the window is resized. + + Returns: + + void + */ + setAutomaticRepaint : function(value) { + automaticRepaint = value; + }, + + /* + Function: setDefaultNewCanvasSize + + Sets the default size jsPlumb will use for a new canvas (we create a square canvas so one value is all that is required). + This is a hack for IE, because ExplorerCanvas seems to need for a canvas to be larger than what you are going to draw on + it at initialisation time. The default value of this is 1200 pixels, which is quite large, but if for some reason you're + drawing connectors that are bigger, you should adjust this value appropriately. + + Parameters: + + size - The default size to use. jsPlumb will use a square canvas so you need only supply one value. + + Returns: + + void + */ + setDefaultNewCanvasSize : function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }, + + /* + Function: setDraggable + + Sets whether or not a given element is draggable, regardless of what any plumb command may request. + + Parameters: + + el - either the id for the element, or a jQuery object representing the element. + + Returns: + + void + */ + setDraggable: _setDraggable, + + /* + Function: setDraggableByDefault + + Sets whether or not elements are draggable by default. Default for this is true. + + Parameters: + draggable - value to set + + Returns: + + void + */ + setDraggableByDefault: function(draggable) { + _draggableByDefault = draggable; + }, + + setDebugLog: function(debugLog) { + log = debugLog; + }, + + /* + Function: setRepaintFunction + + Sets the function to fire when the window size has changed and a repaint was fired. + + Parameters: + f - Function to execute. + + Returns: + void + */ + setRepaintFunction : function(f) { + repaintFunction = f; + }, + + /* + Function: show + + Sets an element's connections to be visible. + + Parameters: + el - either the id of the element, or a jQuery object for the element. + + Returns: + void + */ + show : function(el) { + _setVisible(el, "block"); + }, + + /* + Function: sizeCanvas + + Helper to size a canvas. You would typically use this when writing your own Connector or Endpoint implementation. + + Parameters: + x - [int] x position for the Canvas origin + y - [int] y position for the Canvas origin + w - [int] width of the canvas + h - [int] height of the canvas + + Returns: + void + */ + sizeCanvas : function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; canvas.height = h; + canvas.style.width = w + "px"; canvas.width = w; + canvas.style.left = x + "px"; canvas.style.top = y + "px"; + } + }, + + /** + * gets some test hooks. nothing writable. + */ + getTestHarness : function() { + return { + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + } + }; + }, + + /** + * Toggles visibility of an element's connections. kept for backwards compatibility + */ + toggle : _toggleVisible, + + /* + Function: toggleVisible + + Toggles visibility of an element's connections. + + Parameters: + el - either the element's id, or a jQuery object representing the element. + + Returns: + void, but should be updated to return the current state + */ + //TODO: update this method to return the current state. + toggleVisible : _toggleVisible, + + /* + Function: toggleDraggable + + Toggles draggability (sic) of an element's connections. + + Parameters: + el - either the element's id, or a jQuery object representing the element. + + Returns: + The current draggable state. + */ + toggleDraggable : _toggleDraggable, + + /* + Function: unload + + Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element + + Returns: + void + */ + unload : function() { + delete endpointsByElement; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + } + }; + +})(); + +// jQuery plugin code +(function($){ + /** + * plumbs the results of the selector to some target, using the given options if supplied, + * or the defaults otherwise. this is DEPRECATED. just use jsPlumb.connect(..) instead. + * @deprecated + */ + $.fn.plumb = function(options) { + var options = $.extend({}, options); + + return this.each(function() + { + var params = $.extend({source:$(this)}, options); + jsPlumb.connect(params); + }); + }; + + /** + * detaches the results of the selector from the given target or list of targets - 'target' + * may be a String or a List. + */ + $.fn.detach = function(target) { + return this.each(function() + { + var id = $(this).attr("id"); + if (typeof target == 'string') target = [target]; + for (var i = 0; i < target.length; i++) + jsPlumb.detach(id, target[i]); + }); + }; + + /** + * detaches the results from the selector from all connections. + */ + $.fn.detachAll = function() { + return this.each(function() + { + var id = $(this).attr("id"); + jsPlumb.detachAll(id); + }); + }; + + /** + * adds an endpoint to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoint = function(options) { + var addedEndpoints = []; + this.each(function() + { + addedEndpoints.push(jsPlumb.addEndpoint($(this).attr("id"), options)); + }); + return addedEndpoints[0]; + }; + + /** + * adds a list of endpoints to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoints = function(endpoints) { + var addedEndpoints = []; + return this.each(function() + { + var e = jsPlumb.addEndpoints($(this).attr("id"), endpoints); + for (var i = 0; i < e.length; i++) addedEndpoints.push(e[i]); + }); + return addedEndpoints; + }; + + /** + * remove the endpoint, if it exists, deleting its UI elements etc. + */ + $.fn.removeEndpoint = function(endpoint) { + this.each(function() + { + jsPlumb.removeEndpoint($(this).attr("id"), endpoint); + }); + }; + +})(jQuery); + + +/* the library agnostic functions, such as find offset, get id, get attribute, extend etc. currently +hardcoded to jQuery here; will be extracted to separate impls for different libraries. + +* much more work needs to be done on this. some things that spring to mind: animation, drag, drop. +* +* you should _never_ call these methods directly. jsPlumb uses them when it needs them. +* +*/ +(function() { + + jsPlumb.CurrentLibrary = { + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag' + }, + + /** + * default drag options for jQuery. + */ + defaultDragOptions : { opacity:0.5, revert:true, helper:'clone' }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + getElementObject : function(el) { + return typeof(el)=='string' ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options) { + if (el.length > 1) alert('poo'); + el.draggable(options); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + el.droppable(options); + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[1]; + return ui.absolutePosition || ui.offset; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + } + }; +})(); \ No newline at end of file diff --git a/archive/jquery.jsPlumb-1.2-RC1.js b/archive/jquery.jsPlumb-1.2-RC1.js new file mode 100644 index 000000000..8c760bb91 --- /dev/null +++ b/archive/jquery.jsPlumb-1.2-RC1.js @@ -0,0 +1,222 @@ +// jQuery plugin code +(function($){ + /** + * plumbs the results of the selector to some target, using the given options if supplied, + * or the defaults otherwise. + */ + $.fn.plumb = function(options) { + var options = $.extend({}, options); + + return this.each(function() + { + var params = $.extend({source:$(this)}, options); + jsPlumb.connect(params); + }); + }; + + /** + * detaches the results of the selector from the given target or list of targets - 'target' + * may be a String or a List. + */ + $.fn.detach = function(target) { + return this.each(function() + { + var id = $(this).attr("id"); + if (typeof target == 'string') target = [target]; + for (var i = 0; i < target.length; i++) + jsPlumb.detach(id, target[i]); + }); + }; + + /** + * detaches the results from the selector from all connections. + */ + $.fn.detachAll = function() { + return this.each(function() + { + var id = $(this).attr("id"); + jsPlumb.detachAll(id); + }); + }; + + /** + * adds an endpoint to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoint = function(options) { + var addedEndpoints = []; + this.each(function() + { + addedEndpoints.push(jsPlumb.addEndpoint($(this).attr("id"), options)); + }); + return addedEndpoints[0]; + }; + + /** + * adds a list of endpoints to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoints = function(endpoints) { + var addedEndpoints = []; + return this.each(function() + { + var e = jsPlumb.addEndpoints($(this).attr("id"), endpoints); + for (var i = 0; i < e.length; i++) addedEndpoints.push(e[i]); + }); + return addedEndpoints; + }; + + /** + * remove the endpoint, if it exists, deleting its UI elements etc. + */ + $.fn.removeEndpoint = function(endpoint) { + this.each(function() + { + jsPlumb.removeEndpoint($(this).attr("id"), endpoint); + }); + }; + +})(jQuery); + + +/* + * the library agnostic functions, such as find offset, get id, get attribute, extend etc. + */ +(function() { + + jsPlumb.CurrentLibrary = { + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step', + 'over':'over', 'out':'out', 'drop':'drop' + }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + getElementObject : function(el) { + return typeof(el)=='string' ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options) { + // remove helper directive if present. we know what's best! + options.helper = null; + //TODO: if 'revert' is set on the options it causes end points to animate back to + // where they came from, if the connection is aborted. do we care? probably not. + // the todo is to decide whether we care or not. + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.draggable(options); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.droppable(options); + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[1]; + return ui.absolutePosition || ui.offset; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + } + }; +})(); diff --git a/archive/jquery.jsPlumb-1.2-all-min.js b/archive/jquery.jsPlumb-1.2-all-min.js new file mode 100644 index 000000000..3ffad3cbd --- /dev/null +++ b/archive/jquery.jsPlumb-1.2-all-min.js @@ -0,0 +1,40 @@ +(function(){var e=/MSIE/.test(navigator.userAgent)&&!window.opera,g=null,d=function(){j.repaintEverything()},f=true,k={},l=[],i={},o={},t=true,r=[],y=1200,E=function(a,b,c){var h=function(p,s){if(p===s)return true;else if(typeof p=="object"&&typeof s=="object"){var u=true;for(var v in p)if(!h(p[v],s[v])){u=false;break}for(v in s)if(!h(s[v],p[v])){u=false;break}return u}};c=+c||0;for(var m=a.length;c=0){delete a[c];a.splice(c,1);return true}}return false},Y=function(a,b){var c=B(a,"id");U(c,function(h){h.canvas.style.display=b})},Z=function(a){U(a,function(b){b.canvas.style.display="none"==b.canvas.style.display?"block":"none"})},L=function(a,b,c){if(c||b==null){x(a);r[a]=V(a);b=j.CurrentLibrary.getElementObject(a); +b=j.CurrentLibrary.getOffset(b);l[a]=b}else l[a]=b},I=function(a,b){a=a||function(){};b=b||function(){};return function(){try{b.apply(this,arguments)}catch(c){}try{a.apply(this,arguments)}catch(h){}}},ba=function(a){var b=this;this.x=a.x||0;this.y=a.y||0;var c=a.orientation||[0,0];this.offsets=a.offsets||[0,0];this.compute=function(h,m){return[h[0]+b.x*m[0]+b.offsets[0],h[1]+b.y*m[1]+b.offsets[1]]};this.getOrientation=function(){return c}},ca=function(a){var b=a.reference,c=V(a.referenceCanvas),h= +0,m=0,p=null;this.compute=function(s){m=h=0;return[s[0]+c[0]/2,s[1]+c[1]/2]};this.getOrientation=function(){if(p)return p;else{var s=b.getOrientation();return[Math.abs(s[0])*h*-1,Math.abs(s[1])*m*-1]}};this.over=function(s){p=s.getOrientation()};this.out=function(){p=null}},aa=function(a){var b=this;this.source=x(a.source);this.target=x(a.target);this.sourceId=B(this.source,"id");this.targetId=B(this.target,"id");this.endpointsOnTop=a.endpointsOnTop!=null?a.endpointsOnTop:true;this.endpoints=[];this.endpointStyles= +[];var c=function(p,s,u){if(p)b.endpoints[s]=p;else{if(!u.endpoints)u.endpoints=[null,null];p=u.endpoints[s]||u.endpoint||j.Defaults.Endpoints[s]||j.Defaults.Endpoint||new j.Endpoints.Dot;if(!u.endpointStyles)u.endpointStyles=[null,null];b.endpoints[s]=new S({style:u.endpointStyles[s]||u.endpointStyle||j.Defaults.EndpointStyles[s]||j.Defaults.EndpointStyle,endpoint:p,connections:[b],anchor:u.anchors?u.anchors[s]:j.Defaults.Anchors[s]||j.Anchors.BottomCenter,source:b.source})}};c(a.sourceEndpoint, +0,a);c(a.targetEndpoint,1,a);this.connector=this.endpoints[0].connector||this.endpoints[1].connector||a.connector||j.Defaults.Connector||new j.Connectors.Bezier;this.paintStyle=this.endpoints[0].connectorStyle||this.endpoints[1].connectorStyle||a.paintStyle||j.Defaults.PaintStyle;L(this.sourceId);L(this.targetId);c=l[this.sourceId];var h=r[this.sourceId];c=this.endpoints[0].anchor.compute([c.left,c.top],h);this.endpoints[0].paint(c);c=l[this.targetId];h=r[this.targetId];c=this.endpoints[1].anchor.compute([c.left, +c.top],h);this.endpoints[1].paint(c);var m=W(j.connectorClass);this.canvas=m;this.paint=function(p,s,u){g&&g.debug("Painting Connection; element in motion is "+p+"; ui is ["+s+"]; recalc is ["+u+"]");var v=p!=this.sourceId,D=v?this.sourceId:this.targetId,w=v?0:1,q=v?1:0;if(this.canvas.getContext){L(p,s,u);u&&L(D);s=l[p];u=l[D];p=r[p];var O=r[D];D=m.getContext("2d");var J=this.endpoints[q].anchor.compute([s.left,s.top],p,[u.left,u.top],O);this.endpoints[q].anchor.getOrientation();s=this.endpoints[w].anchor.compute([u.left, +u.top],O,[s.left,s.top],p);this.endpoints[w].anchor.getOrientation();w=this.connector.compute(J,s,this.endpoints[q].anchor,this.endpoints[w].anchor,this.paintStyle.lineWidth);j.sizeCanvas(m,w[0],w[1],w[2],w[3]);j.extend(D,this.paintStyle);if(this.paintStyle.gradient&&!e){v=v?D.createLinearGradient(w[4],w[5],w[6],w[7]):D.createLinearGradient(w[6],w[7],w[4],w[5]);for(q=0;q=0&&b.connections.splice(n,1)};this.makeInPlaceCopy=function(){return new S({anchor:b.anchor,source:m,style:h,endpoint:c})};this.isConnectedTo=function(n){var C=false;if(n)for(var z=0;z=s};this.paint=function(n,C,z){g&&g.debug("Painting Endpoint with elementId ["+p+"]; anchorPoint is ["+n+"]");if(n==null){n=l[p];var N=r[p];if(n==null||N==null){L(p);n=l[p];N=r[p]}n=b.anchor.compute([n.left,n.top],N)}c.paint(n,b.anchor.getOrientation(),z||b.canvas,h,C||h)};if(a.isSource&&j.CurrentLibrary.isDragSupported(m)){var w=null,q=null,O=false,J=null,A=a.dragOptions|| +{},K=j.extend({},j.CurrentLibrary.defaultDragOptions);A=j.extend(K,A);K=j.CurrentLibrary.dragEvents.start;var P=j.CurrentLibrary.dragEvents.stop,Q=j.CurrentLibrary.dragEvents.drag;A[K]=I(A[K],function(){D=b.makeInPlaceCopy();D.paint();w=document.createElement("div");document.body.appendChild(w);var n=""+new String((new Date).getTime());R(x(w),"id",n);L(n);R(x(b.canvas),"dragId",n);R(x(b.canvas),"elId",p);var C=new ca({reference:b.anchor,referenceCanvas:b.canvas});v=new S({style:{fillStyle:"rgba(0,0,0,0)"}, +endpoint:c,anchor:C,source:w});q=b.connections.length==0||b.connections.length0)for(var h=0;h0)for(var m=0;m=4)c.orientation=[arguments[2],arguments[3]];if(arguments.length==6)c.offsets=[arguments[4],arguments[5]]}return new ba(c)},repaint:function(a){var b=function(h){h=x(h);F(h)};if(typeof a=="object")for(var c=0;co)o=k;if(f<0){r+=f;f=Math.abs(f);o+=f;G[0]+=f;E+=f;F+=f;d[0]+=f}f=Math.min(Math.min(H,i),Math.min(G[1], +d[1]));k=Math.max(Math.max(H,i),Math.max(G[1],d[1]));if(k>t)t=k;if(f<0){y+=f;f=Math.abs(f);t+=f;G[1]+=f;H+=f;i+=f;d[1]+=f}return[r,y,o,t,E,H,F,i,G[0],G[1],d[0],d[1]]};this.paint=function(d,f){f.beginPath();f.moveTo(d[4],d[5]);f.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]);f.stroke()}};jsPlumb.Endpoints.Dot=function(e){e=e||{radius:10};var g=this;this.radius=e.radius;var d=0.5*this.radius,f=this.radius/3,k=function(l){try{return parseInt(l)}catch(i){if(l.substring(l.length-1)=="%")return parseInt(l.substring(0, +l-1))}};this.paint=function(l,i,o,t,r){var y=t.radius||g.radius;jsPlumb.sizeCanvas(o,l[0]-y,l[1]-y,y*2,y*2);l=o.getContext("2d");o=jsPlumb.extend({},t);if(o.fillStyle==null)o.fillStyle=r.strokeStyle;jsPlumb.extend(l,o);r=/MSIE/.test(navigator.userAgent)&&!window.opera;if(t.gradient&&!r){r=t.gradient;o=d;var E=f;if(r.offset)o=k(r.offset);if(r.innerRadius)E=k(r.innerRadius);r=[o,E];i=l.createRadialGradient(y,y,y,y+(i[0]==1?r[0]*-1:r[0]),y+(i[1]==1?r[0]*-1:r[0]),r[1]);for(r=0;r endpoint lists. an element can have an arbitrary number of endpoints on it, + * and not all of them have to be connected to anything. + */ + var endpointsByElement = {}; + var offsets = []; + var floatingConnections = {}; + var draggableStates = {}; + var _draggableByDefault = true; + var sizes = []; + + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + var _findIndex = function( a, v, b, s ) { + var _eq = function(o1, o2) { + if (o1 === o2) return true; + else if (typeof o1 == 'object' && typeof o2 == 'object') { + var same = true; + for(var propertyName in o1) { + if(!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for(var propertyName in o2) { + if(!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for( var i = +b || 0, l = a.length; i < l; i++ ) { + if( _eq(a[i], v)) return i; + } + return -1; + }; + + /** + * helper method to add an item to a list, creating the list if it does not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + }; + + /** + * Handles the dragging of an element. + * @param element jQuery element + * @param ui UI object from current library's event system + */ + var _draw = function(element, ui) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints) { + _updateOffset(id, ui); + var myOffset = offsets[id]; + var myWH = sizes[id]; + // loop through endpoints for this element + for (var i = 0; i < endpoints.length; i++) { + var e = endpoints[i]; + var anchorLoc = endpoints[i].anchor.compute([myOffset.left, myOffset.top], myWH); + e.paint(anchorLoc); + var l = e.connections; + for (var j = 0; j < l.length; j++) + l[j].paint(id, ui); // ...and paint them. + } + } + }; + + /** + * helper function: the second argument is a function taking two args - the first is an id + * ui element from the current library (or list of them), and the second is the function + * to run. + */ + var _elementProxy = function(element, fn) { + var retVal = null; + if (typeof element == 'object' && element.length) { + retVal = []; + for (var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]); + var id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } + else { + var el = _getElementObject(element); + var id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + + return retVal; + }; + + /** + * gets the named attribute from the given element (id or element object) + */ + var _getAttribute = function(el, attName) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + return jsPlumb.CurrentLibrary.getAttribute(ele, attName); + }; + + var _setAttribute = function(el, attName, attValue) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + jsPlumb.CurrentLibrary.setAttribute(ele, attName, attValue); + }; + + var _addClass = function(el, clazz) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + jsPlumb.CurrentLibrary.addClass(ele, clazz); + }; + + var _getElementObject = function(elId) { + return jsPlumb.CurrentLibrary.getElementObject(elId); + }; + + var _getOffset = function(el) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + return jsPlumb.CurrentLibrary.getOffset(ele); + }; + + var _getSize = function(el) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + return jsPlumb.CurrentLibrary.getSize(ele); + }; + + var _appendCanvas = function(canvas) { + document.body.appendChild(canvas); + }; + + /** + * gets an id for the given element, creating and setting one if necessary. + */ + var _getId = function(element) { + var id = _getAttribute(element, "id"); + if (!id) { + id = "_jsPlumb_" + new String((new Date()).getTime()); + _setAttribute(element, "id", id); + } + return id; + }; + + /** + * inits a draggable if it's not already initialised. + * + * TODO: we need to hook in each library to this method. they need to be given the opportunity to + * wrap/insert lifecycle functions, because each library does different things. for instance, jQuery + * supports the notion of 'revert', which will clean up a dragged endpoint; MooTools does not. jQuery + * also supports 'opacity' and MooTools does not; jQuery supports z-index of the draggable; MooTools + * does not. i could go on. the point is...oh. initDraggable can do this. ok. + */ + var _initDraggableIfNecessary = function(element, elementId, isDraggable, dragOptions) { + // dragging + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable && jsPlumb.CurrentLibrary.isDragSupported(element)) { + var options = dragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend({}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var initDrag = function(element, elementId, dragFunc) { + options[dragEvent] = _wrap(options[dragEvent], dragFunc); + var draggable = draggableStates[elementId]; + // TODO: this is still jQuery specific. + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + }; + initDrag(element, elementId, function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + } + }; + + var _log = function(msg) { + // not implemented. yet. + } + + /** + * helper to create a canvas. + * @param clazz optional class name for the canvas. + */ + var _newCanvas = function(clazz) { + var canvas = document.createElement("canvas"); + _appendCanvas(canvas); + canvas.style.position="absolute"; + if (clazz) { canvas.className=clazz; } + _getId(canvas); // set an id. + + if (ie) { + // for IE we have to set a big canvas size. actually you can override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + /** + * performs the given function operation on all the connections found for the given element + * id; this means we find all the endpoints for the given element, and then for each endpoint + * find the connectors connected to it. then we pass each connection in to the given + * function. + */ + var _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + for (var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }; + /** + * perform an operation on all elements. + */ + var _operationOnAll = function(func) { + for (var elId in endpointsByElement) { + _operation(elId, func); + } + }; + /** + * helper to remove an element from the DOM. + */ + var _removeElement = function(element) { + if (element != null) { + try { document.body.removeChild(element); } + catch (e) { } + } + }; + /** + * helper to remove a list of elements from the DOM. + */ + var _removeElements = function(elements) { + for (var i in elements) + _removeElement(elements[i]); + }; + /** + * helper method to remove an item from a list. + */ + var _removeFromList = function(map, key, value) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete( l[i] ); + l.splice( i, 1 ); + return true; + } + } + return false; + }; + /** + * Sets whether or not the given element(s) should be draggable, regardless of what a particular + * plumb command may request. + * + * @param element May be a string, a jQuery elements, or a list of strings/jquery elements. + * @param draggable Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + var _helper = function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }; + + return _elementProxy(element, _helper); + }; + /** + * private method to do the business of hiding/showing. + * @param el either Id of the element in question or a jquery object for the element. + * @param state String specifying a value for the css 'display' property ('block' or 'none'). + */ + var _setVisible = function(el, state) { + var elId = _getAttribute(el, "id"); + var f = function(jpc) { + jpc.canvas.style.display = state; + }; + _operation(elId, f); + }; + /** + * toggles the draggable state of the given element(s). + * @param el either an id, or a jquery object, or a list of ids/jquery objects. + */ + var _toggleDraggable = function(el) { + var fn = function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + //el.draggable("option", "disabled", !state); + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }; + return _elementProxy(el, fn); + }; + /** + * private method to do the business of toggling hiding/showing. + * @param elId Id of the element in question + */ + var _toggleVisible = function(elId) { + var f = function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }; + + _operation(elId, f); + + //todo this should call _elementProxy, and pass in the _operation(elId, f) call as a function. cos _toggleDraggable does that. + }; + /** + * updates the offset and size for a given element, and stores the values. + * if 'offset' is not null we use that (it would have been passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we use the offset, outerWidth and outerHeight methods to get + * the current values. + */ + var _updateOffset = function(elId, offset, recalc) { + if (recalc || offset == null) { // if forced repaint or no offset available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + sizes[elId] = _getSize(elId); + offsets[elId] = _getOffset(elId); + } else { + offsets[elId] = offset; + } + }; + + /** + * wraps one function with another, creating a placeholder for the wrapped function + * if it was null. this is used to wrap the various drag/drop event functions - to allow + * jsPlumb to be notified of important lifecycle events without imposing itself on the user's + * drap/drop functionality. + * TODO: determine whether or not we should support an error handler concept, if one of the functions fails. + */ + var _wrap = function(wrappedFunction, newFunction) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + try { newFunction.apply(this, arguments); } + catch (e) { } + try { wrappedFunction.apply(this, arguments); } + catch (e) { } + }; + } + + /* + Class: Anchor + Models a position relative to the origin of some element that an Endpoint can be located. + */ + /* + Function: Anchor + + Constructor for the Anchor class + + Parameters: + params - Anchor parameters. This should contain three values, and may optionally have an 'offsets' argument: + + - x : the x location of the anchor as a fraction of the total width. + - y : the y location of the anchor as a fraction of the total height. + - orientation : an [x,y] array indicating the general direction a connection from the anchor should go in. for more info on this, see the documentation, or the docs in jquery-jsPlumb-defaults-XXX.js for the default Anchors. + - offsets : an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. may be null. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; this.y = params.y || 0; + var orientation = params.orientation || [0,0]; + this.offsets = params.offsets || [0,0]; + this.compute = function(xy, wh, txy, twh) { + return [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + } + this.getOrientation = function() { return orientation; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from its position relative to the anchor it is floating relative to. + * + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(refCanvas); + + // these are used to store the current relative position of our anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating anchor is hovering over another anchor. + var orientation = null; + + this.compute = function(xy, wh, txy, twh) { + // set these for the getOrientation method to use. + xDir = 0;//xy[0] < txy[0] ? -1 : xy[0] == txy[0] ? 0 : 1; + yDir = 0;//xy[1] < txy[1] ? -1 : xy[1] == txy[1] ? 0 : 1; + return [xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other anchor: if it declares zero for some direction, we declare zero too. + // this might not be the most awesome. perhaps we can come up with a better way. it's just so that the line we draw looks + // like it makes sense. maybe this wont make sense. + return [Math.abs(o[0]) * xDir * -1, Math.abs(o[1]) * yDir * -1]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering over another anchor; + * we want to assume that anchor's orientation for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no longer hovering + * over another anchor; we should resume calculating orientation as we normally do. + */ + this.out = function() { + orientation = null; + }; + }; + + // ************** connection + // **************************************** + /** + * allowed params: + * source: source element (string or a jQuery element) (required) + * target: target element (string or a jQuery element) (required) + * + * anchors: optional array of anchor placements. defaults to BottomCenter for source + * and TopCenter for target. + */ + var Connection = function(params) { + + // ************** get the source and target and register the connection. ******************* + var self = this; + // get source and target as jQuery objects + this.source = _getElementObject(params.source); + this.target = _getElementObject(params.target); + this.sourceId = _getAttribute(this.source, "id"); + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + + // init endpoints + this.endpoints = []; + this.endpointStyles = []; + var prepareEndpoint = function(existing, index, params) { + if (existing) self.endpoints[index] = existing; + else { + if(!params.endpoints) params.endpoints = [null,null]; + var ep = params.endpoints[index] || params.endpoint || jsPlumb.Defaults.Endpoints[index] || jsPlumb.Defaults.Endpoint|| new jsPlumb.Endpoints.Dot(); + if (!params.endpointStyles) params.endpointStyles = [null,null]; + var es = params.endpointStyles[index] || params.endpointStyle || jsPlumb.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyle; + var a = params.anchors ? params.anchors[index] : jsPlumb.Defaults.Anchors[index] || jsPlumb.Anchors.BottomCenter; + self.endpoints[index] = new Endpoint({style:es, endpoint:ep, connections:[self], anchor:a, source:self.source }); + } + }; + + prepareEndpoint(params.sourceEndpoint, 0, params); + prepareEndpoint(params.targetEndpoint, 1, params); + + this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + this.paintStyle = this.endpoints[0].connectorStyle || this.endpoints[1].connectorStyle || params.paintStyle || jsPlumb.Defaults.PaintStyle; + + _updateOffset(this.sourceId); + _updateOffset(this.targetId); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var anchorLoc = this.endpoints[0].anchor.compute([myOffset.left, myOffset.top], myWH); + this.endpoints[0].paint(anchorLoc); + myOffset = offsets[this.targetId]; myWH = sizes[this.targetId]; + anchorLoc = this.endpoints[1].anchor.compute([myOffset.left, myOffset.top], myWH); + this.endpoints[1].paint(anchorLoc); + + // *************** create canvas on which the connection will be drawn ************ + var canvas = _newCanvas(jsPlumb.connectorClass); + this.canvas = canvas; + + /** + * paints the connection. + * @param elId Id of the element that is in motion + * @param ui current library's event system ui object (present if we came from a drag to get here) + * @param recalc whether or not to recalculate element sizes. this is true if a repaint caused this to be painted. + */ + this.paint = function(elId, ui, recalc) { + + if (log) log.debug("Painting Connection; element in motion is " + elId + "; ui is [" + ui + "]; recalc is [" + recalc + "]"); + + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = !(elId == this.sourceId); + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + if (this.canvas.getContext) { + + _updateOffset(elId, ui, recalc); + if (recalc) _updateOffset(tId); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var myOffset = offsets[elId]; + var otherOffset = offsets[tId]; + var myWH = sizes[elId]; + var otherWH = sizes[tId]; + + var ctx = canvas.getContext('2d'); + var sAnchorP = this.endpoints[sIdx].anchor.compute([myOffset.left, myOffset.top], myWH, [otherOffset.left, otherOffset.top], otherWH); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.compute([otherOffset.left, otherOffset.top], otherWH, [myOffset.left, myOffset.top], myWH); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, this.paintStyle.lineWidth); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + jsPlumb.extend(ctx, this.paintStyle); + + if (this.paintStyle.gradient && !ie) { + var g = swap ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + for (var i = 0; i < this.paintStyle.gradient.stops.length; i++) + g.addColorStop(this.paintStyle.gradient.stops[i][0],this.paintStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + + this.connector.paint(dim, ctx); + } + }; + + this.repaint = function() { + this.paint(this.sourceId, null, true); + }; + + _initDraggableIfNecessary(self.source, self.sourceId, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, self.targetId, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + }; + + /* + Class: Endpoint + + Models an endpoint. Can have one to N connections emanating from it (although how to handle that in the UI is + a very good question). also has a Canvas and paint style. + + */ + + /* + Function: Endpoint + + This is the Endpoint class constructor. + + Parameters: + + anchor - anchor for the endpoint, of type jsPlumb.Anchor. may be null. + endpoint - endpoint object, of type jsPlumb.Endpoint. may be null. + style - endpoint style, a js object. may be null. + source - element the endpoint is attached to, of type jquery object. Required. + canvas - canvas element to use. may be, and most often is, null. + connections - optional list of connections to configure the endpoint with. + isSource - boolean. indicates the endpoint can act as a source of new connections. optional. + dragOptions - if isSource is set to true, you can supply arguments for the jquery draggable method. optional. + connectionStyle - if isSource is set to true, this is the paint style for connections from this endpoint. optional. + connector - optional connector type to use. + isTarget - boolean. indicates the endpoint can act as a target of new connections. optional. + dropOptions - if isTarget is set to true, you can supply arguments for the jquery droppable method. optional. + reattach - optional boolean that determines whether or not the connections reattach after they + have been dragged off an endpoint and left floating. defaults to false: connections + dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + params = params || {}; + // make a copy. then we can use the wrapper function. + jsPlumb.extend({}, params); + var self = this; + self.anchor = params.anchor || jsPlumb.Anchors.TopCenter; + var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot(); + var _style = params.style || jsPlumb.Defaults.EndpointStyle; + this.connectorStyle = params.connectorStyle; + this.connector = params.connector; + var _element = params.source; + var _elementId = _getAttribute(_element, "id"); + var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of. + this.canvas = params.canvas || _newCanvas(jsPlumb.endpointClass); + this.connections = params.connections || []; + var _reattach = params.reattach || false; + var floatingEndpoint = null; + var inPlaceCopy = null; + this.addConnection = function(connection) { + + self.connections.push(connection); + }; + this.removeConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) + self.connections.splice(idx, 1); + }; + this.makeInPlaceCopy = function() { + var e = new Endpoint({anchor:self.anchor, source:_element, style:_style, endpoint:_endpoint}); + return e; + }; + /** + * returns whether or not this endpoint is connected to the given endpoint. + * @param endpoint Endpoint to test. + * @since 1.1.1 + * + * todo: needs testing. will this work if the endpoint passed in is the source? + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + this.isFloating = function() { return floatingEndpoint != null; }; + /** + * first pass at default ConnectorSelector: returns the first connection, if we have any. + * modified a little, 5/10/2010: now it only returns a connector if we have not got equal to or more than _maxConnector connectors + * attached. otherwise it is assumed a new connector is ok. but note with this setup we can't select any other connection than the first + * one. what if this could return a list? that implies everything should work with a list - dragging etc. could be nasty. could also + * be cool. + */ + var connectorSelector = function() { + return self.connections.length == 0 || self.connections.length < _maxConnections ? null : self.connections[0]; + }; + + this.isFull = function() { return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); }; + + /** + * paints the Endpoint, recalculating offset and anchor positions if necessary. + */ + this.paint = function(anchorPoint, connectorPaintStyle, canvas) { + + if (log) log.debug("Painting Endpoint with elementId [" + _elementId + "]; anchorPoint is [" + anchorPoint + "]"); + + if (anchorPoint == null) { + // do we always want to force a repaint here? i dont think so! + var xy = offsets[_elementId]; + var wh = sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset(_elementId); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + anchorPoint = self.anchor.compute([xy.left, xy.top], wh); + } + _endpoint.paint(anchorPoint, self.anchor.getOrientation(), canvas || self.canvas, _style, connectorPaintStyle || _style); + }; + + + // is this a connection source? we make it draggable and have the drag listener + // maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + + // first the question is, how many connections are on this endpoint? if it's only one, then excellent. otherwise we will either need a way + // to select one connection from the list, or drag them all. if we had a pluggable 'ConnectorSelector' interface we could probably + // provide a way for people to implement their own UI components to do the connector selection. the question in that particular case would be how much + // information the interface needs from jsPlumb at execution time. if, however, we leave out the connector selection, and drag them all, + // that wouldn't be too hard to organise. perhaps that behaviour would be on a switch for the endpoint, or perhaps the ConnectorSelector + // interface returns a List, with the default implementation just returning everything. i think i like that. + // + // let's say for now that there is just one endpoint, cos we need to get this going before we can consider a list of them anyway. + // the major difference between that case and the case of a new endpoint is actually quite small - it's a question of where the + // Connection comes from. for a new one, we create a new one. obviously. otherwise we just get the jpc from the Endpoint + // (remember we're only assuming one connection right now). so all of the UI stuff we do to create the floating endpoint etc + // will still be valid, but when we stop dragging, we'll have to do something different. if we stop with a valid drop i think it will + // be the same process. but if we stop with an invalid drop we have to reset the Connection to how it was when we got it. + var start = function() { + + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + _appendCanvas(n); + // create and assign an id, and initialize the offset. + var id = "" + new String(new Date().getTime()); + _setAttribute(_getElementObject(n), "id", id); + _updateOffset(id); + // store the id of the dragging div and the source element. the drop function + // will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor({reference:self.anchor, referenceCanvas:self.canvas}); + floatingEndpoint = new Endpoint({ + style:{fillStyle:'rgba(0,0,0,0)'}, + endpoint:_endpoint, + anchor:floatingAnchor, + source:n + }); + + jpc = connectorSelector(); + if (jpc == null) { + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = new Connection({ + sourceEndpoint:self, + targetEndpoint:floatingEndpoint, + source:_getElementObject(_element), + target:_getElementObject(n), + anchors:[self.anchor, floatingAnchor], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + connector: params.connector + }); + // todo ...unregister on stop + self.addConnection(jpc); + } else { + existingJpc = true; + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; + // probably we should remove the connection? and add it back if the user + // does not drop it somewhere proper. + self.removeConnection(jpc); + if (anchorIdx == 0){ + existingJpcParams = [jpc.source, jpc.sourceId]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + }else { + existingJpcParams = [jpc.target, jpc.targetId]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // todo unregister on stop + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + }; + + var dragOptions = params.dragOptions || { }; + + var defaultOpts = jsPlumb.extend({}, jsPlumb.CurrentLibrary.defaultDragOptions); + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + var startEvent = jsPlumb.CurrentLibrary.dragEvents['start']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements([floatingEndpoint.canvas, n, inPlaceCopy.canvas]); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + if (_reattach) { + jpc.floatingAnchorIndex = null; + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } + else { + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeElement(jpc.canvas); + } + } else { + _removeElement(jpc.canvas); + self.removeConnection(jpc); + } + } + jpc = null; + delete floatingEndpoint; + delete inPlaceCopy; + self.paint(); + } + ); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + }; + + // connector target + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend({}, dropOptions); + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + dropOptions[dropEvent]= _wrap(dropOptions[dropEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable), "dragId"); + var elId = _getAttribute(_getElementObject(draggable),"elId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // remove this jpc from the current endpoint + jpc.endpoints[idx].removeConnection(jpc); + if (jpc.suspendedEndpoint) + jpc.suspendedEndpoint.removeConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + _initDraggableIfNecessary(_element, _elementId, params.draggable, {}); + jsPlumb.repaint(elId); + + delete floatingConnections[id]; + }); + + dropOptions[overEvent]= _wrap(dropOptions[overEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + }); + + jsPlumb.CurrentLibrary.initDroppable(_getElementObject(self.canvas), dropOptions); + } + + return self; + }; + + /* + Class: jsPlumb + + This is the main entry point to jsPlumb. There are a bunch of static methods you can + use to connect/disconnect etc. Some methods are also added to jQuery's "$.fn" object itself, but + in general jsPlumb is moving away from that model, preferring instead the static class + approach. One of the reasons for this is that with no methods added to the jQuery $.fn object, + it will be easier to provide support for other libraries such as MooTools. + */ + var jsPlumb = window.jsPlumb = { + + /* + Property: Defaults + + These are the default settings for jsPlumb, that is what will be used if you do not supply specific pieces of information + to the various API calls. A convenient way to implement your own look and feel can be to override these defaults by including a script + somewhere after the jsPlumb include, but before you make any calls to jsPlumb, for instance in this example we set the PaintStyle to be + a blue line of 27 pixels: + + > jsPlumb.Defaults.PaintStyle = { lineWidth:27, strokeStyle:'blue' } + + */ + Defaults : { + Anchors : [ null, null ], + Connector : null, + DragOptions: { }, + DropOptions: { }, + Endpoint : null, + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : null }, + EndpointStyles : [ null, null ], + MaxConnections : null, + PaintStyle : { lineWidth : 10, strokeStyle : 'red' }, + Scope : "_jsPlumb_DefaultScope" + }, + + /* + Property: connectorClass + + The CSS class to set on Connection canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + connectorClass : '_jsPlumb_connector', + + /* + Property: endpointClass + + The CSS class to set on Endpoint canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + endpointClass : '_jsPlumb_endpoint', + + /* + Property: Anchors + + Default jsPlumb Anchors. These are supplied in the file jquery.jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form jquery.jsPlumb-all-x.x.x.js. You can provide your own Anchors by supplying them in a script that is loaded after jsPlumb, for instance: + + > jsPlumb.Anchors.MyAnchor = { ....anchor code here. see the documentation. } + */ + Anchors : {}, + + /* + Property: Connectors + + Default jsPlumb Connectors. These are supplied in the file jquery.jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form jquery.jsPlumb-all-x.x.x.js. You can provide your own Connectors by supplying them in a script that is loaded after jsPlumb, for instance: + + > jsPlumb.Connectors.MyConnector = { ....connector code here. see the documentation. } + */ + Connectors : {}, + + /* + Property: Endpoints + + Default jsPlumb Endpoints. These are supplied in the file jquery.jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form jquery.jsPlumb-all-x.x.x.js. You can provide your own Endpoints by supplying them in a script that is loaded after jsPlumb, for instance: + + > jsPlumb.Endpoints.MyEndpoint = { ....endpoint code here. see the documentation. } + */ + Endpoints : {}, + + /* + Function: addEndpoint + + Adds an Endpoint to a given element. + + Parameters: + target - Element to add the endpoint to. either an element id, or a jQuery object representing some element. + params - Object containing Endpoint options (more info required) + + Returns: + + The newly created Endpoint. + + See Also: + + + */ + addEndpoint : function(target, params) { + params = jsPlumb.extend({}, params); + var el = _getElementObject(target); + var id = _getAttribute(target,"id"); + params.source = el; + _updateOffset(id); + var e = new Endpoint(params); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id]; + var myWH = sizes[id]; + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH); + e.paint(anchorLoc); + return e; + }, + + /* + Function: addEndpoint + + Adds a list of Endpoints to a given element. + + Parameters: + + target - element to add the endpoint to. either an element id, or a jQuery object representing some element. + endpoints - List of objects containing Endpoint options. one Endpoint is created for each entry in this list. + + Returns: + + List of newly created Endpoints, one for each entry in the 'endpoints' argument. + + See Also: + + + */ + addEndpoints : function(target, endpoints) { + var results = []; + for (var i = 0; i < endpoints.length; i++) { + results.push(jsPlumb.addEndpoint(target, endpoints[i])); + } + return results; + }, + + /* + Function: animate + + Wrapper around standard jQuery animate function; injects a call to jsPlumb in the 'step' function (creating it if necessary). + This only supports the two-arg version of the animate call in jQuery - the one that takes an 'options' object as the second arg. + + Parameters: + + el - Element to animate. Either an id, or a jQuery object representing the element. + properties - The 'properties' argument you want passed to the standard jQuery animate call. + options - The 'options' argument you want passed to the standard jQuery animate call. + + Returns: + + void + */ + animate : function(el, properties, options) { + var ele = _getElementObject(el); + var id = _getAttribute(el,"id"); + //TODO this is not agnostic yet. + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + options[stepFunction] = _wrap(options[stepFunction], function() + { + jsPlumb.repaint(id); + }); + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }, + + /* + Function: connect + + Establishes a connection between two elements. + + Parameters: + + params - Object containing setup for the connection. see documentation. + + Returns: + + The newly created Connection. + + */ + connect : function(params) { + if (params.sourceEndpoint && params.sourceEndpoint.isFull()) { + _log("could not add connection; source endpoint is full"); + return; + } + + if (params.targetEndpoint && params.targetEndpoint.isFull()) { + _log("could not add connection; target endpoint is full"); + return; + } + + var jpc = new Connection(params); + + // register endpoints for the element. todo: is the test below sufficient? or should we test if the endpoint is already in the list, + // and add it only then? perhaps _addToList could be overloaded with a a 'test for existence first' parameter? + //_addToList(endpointsByElement, jpc.sourceId, jpc.endpoints[0]); + //_addToList(endpointsByElement, jpc.targetId, jpc.endpoints[1]); + + if (!params.sourceEndpoint) _addToList(endpointsByElement, jpc.sourceId, jpc.endpoints[0]); + if (!params.targetEndpoint) _addToList(endpointsByElement, jpc.targetId, jpc.endpoints[1]); + + jpc.endpoints[0].addConnection(jpc); + jpc.endpoints[1].addConnection(jpc); + + // force a paint + _draw(jpc.source); + + return jpc; + + }, + + /** + * not implemented yet. params object will have sourceEndpoint and targetEndpoint members; these will be Endpoints. + connectEndpoints : function(params) { + var jpc = Connection(params); + + },*/ + + /* + Function: detach + + Removes a connection. + + Parameters: + + sourceId - Id of the first element in the connection. A String. + targetId - iI of the second element in the connection. A String. + + Returns: + + true if successful, false if not. + */ + detach : function(sourceId, targetId) { + var f = function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + return true; + } + }; + + // todo: how to cleanup the actual storage? a third arg to _operation? + _operation(sourceId, f); + }, + + /* + Function: detachAll + + Removes all an element's connections. + + Parameters: + + el - either the id of the element, or a jQuery object for the element. + + Returns: + + void + */ + detachAll : function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + var c = endpoints[i].connections.length; + if (c > 0) { + for (var j = 0; j < c; j++) { + var jpc = endpoints[i].connections[0]; + _removeElement(jpc.canvas); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + } + } + } + } + }, + + /* + Function: detachEverything + + Remove all Connections from all elements, but leaves Endpoints in place. + + Returns: + + void + + See Also: + + + */ + detachEverything : function() { + for(var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + var c = endpoints[i].connections.length; + if (c > 0) { + for (var j = 0; j < c; j++) { + var jpc = endpoints[i].connections[0]; + _removeElement(jpc.canvas); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + } + } + } + } + } + }, + + extend : function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }, + + /* + Function: hide + + Sets an element's connections to be hidden. + + Parameters: + + el - either the id of the element, or a jQuery object for the element. + + Returns: + + void + */ + hide : function(el) { + _setVisible(el, "none"); + }, + + /* + Function: makeAnchor + + Creates an anchor with the given params. + + Parameters: + + x - the x location of the anchor as a fraction of the total width. + y - the y location of the anchor as a fraction of the total height. + xOrientation - value indicating the general direction a connection from the anchor should go in, in the x direction. + yOrientation - value indicating the general direction a connection from the anchor should go in, in the y direction. + xOffset - a fixed offset that should be applied in the x direction that should be applied after the x position has been figured out. optional. defaults to 0. + yOffset - a fixed offset that should be applied in the y direction that should be applied after the y position has been figured out. optional. defaults to 0. + + Returns: + + The newly created Anchor. + */ + makeAnchor : function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards compatibility if we are given only one value we assume it's a call in the old form. + var params = {}; + if (arguments.length == 1) jsPlumb.extend(params, x); + else { + params = {x:x, y:y}; + if (arguments.length >= 4) { + params.orientation = [arguments[2], arguments[3]]; + } + if (arguments.length == 6) params.offsets = [arguments[4], arguments[5]]; + } + return new Anchor(params); + }, + + + /* + Function: repaint + + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + + el - either the id of the element or a jQuery object representing the element. + + Returns: + + void + + See Also: + + + */ + repaint : function(el) { + + var _processElement = function(el) { + var ele = _getElementObject(el); + _draw(ele); + }; + + // TODO: support a jQuery result object too! + + // support both lists... + if (typeof el =='object') { + for (var i = 0; i < el.length; i++) + _processElement(el[i]); + } // ...and single strings. + else _processElement(el); + }, + + /* + Function: repaintEverything + + Repaints all connections. + + Returns: + + void + + See Also: + + + */ + repaintEverything : function() { + for (var elId in endpointsByElement) { + _draw(_getElementObject(elId)); + } + }, + + /* + Function: removeAllEndpoints + + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + + el - either an element id, or a jQuery object for an element. + + Returns: + + void + + See Also: + + + */ + removeAllEndpoints : function(el) { + var elId = _getAttribute(el, "id"); + // first remove all Connections. + jsPlumb.detachAll(elId); + var ebe = endpointsByElement[elId]; + for (var i in ebe) { + _removeElement(ebe[i].canvas); + } + endpointsByElement[elId] = []; + }, + + /* + Function: removeEndpoint + + Removes the given Endpoint from the given element. + + Parameters: + + el - either an element id, or a jQuery object for an element. + endpoint - Endpoint to remove. this is an Endpoint object, such as would have been returned from a call to addEndpoint. + + Returns: + + void + + See Also: + + + */ + removeEndpoint : function(el, endpoint) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + if (ebe) { + if(_removeFromList(endpointsByElement, elId, endpoint)) + _removeElement(endpoint.canvas); + } + }, + + /* + Function: setAutomaticRepaint + + Sets/unsets automatic repaint on window resize. + + Parameters: + + value - whether or not to automatically repaint when the window is resized. + + Returns: + + void + */ + setAutomaticRepaint : function(value) { + automaticRepaint = value; + }, + + /* + Function: setDefaultNewCanvasSize + + Sets the default size jsPlumb will use for a new canvas (we create a square canvas so one value is all that is required). + This is a hack for IE, because ExplorerCanvas seems to need for a canvas to be larger than what you are going to draw on + it at initialisation time. The default value of this is 1200 pixels, which is quite large, but if for some reason you're + drawing connectors that are bigger, you should adjust this value appropriately. + + Parameters: + + size - The default size to use. jsPlumb will use a square canvas so you need only supply one value. + + Returns: + + void + */ + setDefaultNewCanvasSize : function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }, + + /* + Function: setDraggable + + Sets whether or not a given element is draggable, regardless of what any plumb command may request. + + Parameters: + + el - either the id for the element, or a jQuery object representing the element. + + Returns: + + void + */ + setDraggable: _setDraggable, + + /* + Function: setDraggableByDefault + + Sets whether or not elements are draggable by default. Default for this is true. + + Parameters: + draggable - value to set + + Returns: + + void + */ + setDraggableByDefault: function(draggable) { + _draggableByDefault = draggable; + }, + + setDebugLog: function(debugLog) { + log = debugLog; + }, + + /* + Function: setRepaintFunction + + Sets the function to fire when the window size has changed and a repaint was fired. + + Parameters: + f - Function to execute. + + Returns: + void + */ + setRepaintFunction : function(f) { + repaintFunction = f; + }, + + /* + Function: show + + Sets an element's connections to be visible. + + Parameters: + el - either the id of the element, or a jQuery object for the element. + + Returns: + void + */ + show : function(el) { + _setVisible(el, "block"); + }, + + /* + Function: sizeCanvas + + Helper to size a canvas. You would typically use this when writing your own Connector or Endpoint implementation. + + Parameters: + x - [int] x position for the Canvas origin + y - [int] y position for the Canvas origin + w - [int] width of the canvas + h - [int] height of the canvas + + Returns: + void + */ + sizeCanvas : function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; canvas.height = h; + canvas.style.width = w + "px"; canvas.width = w; + canvas.style.left = x + "px"; canvas.style.top = y + "px"; + } + }, + + /** + * gets some test hooks. nothing writable. + */ + getTestHarness : function() { + return { + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + + findIndex : _findIndex + }; + }, + + /** + * Toggles visibility of an element's connections. kept for backwards compatibility + */ + toggle : _toggleVisible, + + /* + Function: toggleVisible + + Toggles visibility of an element's connections. + + Parameters: + el - either the element's id, or a jQuery object representing the element. + + Returns: + void, but should be updated to return the current state + */ + //TODO: update this method to return the current state. + toggleVisible : _toggleVisible, + + /* + Function: toggleDraggable + + Toggles draggability (sic) of an element's connections. + + Parameters: + el - either the element's id, or a jQuery object representing the element. + + Returns: + The current draggable state. + */ + toggleDraggable : _toggleDraggable, + + /* + Function: unload + + Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element + + Returns: + void + */ + unload : function() { + delete endpointsByElement; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + }, + + /* + Function: wrap + Helper method to wrap an existing function with one of your own. This is used by the various + implementations to wrap event callbacks for drag/drop etc; it allows jsPlumb to be + transparent in its handling of these things. If a user supplies their own event callback, + for anything, it will always be called. + Parameters: + + */ + wrap : _wrap + }; + +})(); + + +//jQuery plugin code +(function($){ + /** + * plumbs the results of the selector to some target, using the given options if supplied, + * or the defaults otherwise. + */ + $.fn.plumb = function(options) { + var options = $.extend({}, options); + + return this.each(function() + { + var params = $.extend({source:$(this)}, options); + jsPlumb.connect(params); + }); + }; + + /** + * detaches the results of the selector from the given target or list of targets - 'target' + * may be a String or a List. + */ + $.fn.detach = function(target) { + return this.each(function() + { + var id = $(this).attr("id"); + if (typeof target == 'string') target = [target]; + for (var i = 0; i < target.length; i++) + jsPlumb.detach(id, target[i]); + }); + }; + + /** + * detaches the results from the selector from all connections. + */ + $.fn.detachAll = function() { + return this.each(function() + { + var id = $(this).attr("id"); + jsPlumb.detachAll(id); + }); + }; + + /** + * adds an endpoint to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoint = function(options) { + var addedEndpoints = []; + this.each(function() + { + addedEndpoints.push(jsPlumb.addEndpoint($(this).attr("id"), options)); + }); + return addedEndpoints[0]; + }; + + /** + * adds a list of endpoints to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoints = function(endpoints) { + var addedEndpoints = []; + return this.each(function() + { + var e = jsPlumb.addEndpoints($(this).attr("id"), endpoints); + for (var i = 0; i < e.length; i++) addedEndpoints.push(e[i]); + }); + return addedEndpoints; + }; + + /** + * remove the endpoint, if it exists, deleting its UI elements etc. + */ + $.fn.removeEndpoint = function(endpoint) { + this.each(function() + { + jsPlumb.removeEndpoint($(this).attr("id"), endpoint); + }); + }; + +})(jQuery); + + +/* + * the library agnostic functions, such as find offset, get id, get attribute, extend etc. + */ +(function() { + + jsPlumb.CurrentLibrary = { + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step', + 'over':'over', 'out':'out', 'drop':'drop' + }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + getElementObject : function(el) { + return typeof(el)=='string' ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options) { + // remove helper directive if present. we know what's best! + options.helper = null; + //TODO: if 'revert' is set on the options it causes end points to animate back to + // where they came from, if the connection is aborted. do we care? probably not. + // the todo is to decide whether we care or not. + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.draggable(options); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + options['scope'] = options['scope'] || jsPlumb.Defaults.Scope; + el.droppable(options); + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[1]; + return ui.absolutePosition || ui.offset; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + } + }; +})(); + +(function() { + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * getOrientation : function() { return [ox, oy]; } + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + jsPlumb.Anchors.TopCenter = jsPlumb.makeAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors.BottomCenter = jsPlumb.makeAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors.LeftMiddle = jsPlumb.makeAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors.RightMiddle = jsPlumb.makeAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors.Center = jsPlumb.makeAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors.TopRight = jsPlumb.makeAnchor(1, 0, 0,-1); + jsPlumb.Anchors.BottomRight = jsPlumb.makeAnchor(1, 1, 0, 1); + jsPlumb.Anchors.TopLeft = jsPlumb.makeAnchor(0, 0, 0, -1); + jsPlumb.Anchors.BottomLeft = jsPlumb.makeAnchor(0, 1, 0, 1); + + + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (w < 2 * lineWidth) { + w = 2 * lineWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - lineWidth; + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < 2 * lineWidth) { + // minimum size is 2 * line Width + h = 2 * lineWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - lineWidth; + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + var sx = sourcePos[0] < targetPos[0] ? w-xo : xo; + var sy = sourcePos[1] < targetPos[1] ? h-yo : yo; + var tx = sourcePos[0] < targetPos[0] ? xo : w-xo; + var ty = sourcePos[1] < targetPos[1] ? yo : h-yo; + var retVal = [ x, y, w, h, sx, sy, tx, ty ]; + + return retVal; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + }; + + /** + * This Connector draws a Bezier curve with two control points. + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + jsPlumb.Connectors.Bezier = function(curviness) { + + var self = this; + this.majorAnchor = curviness || 150; + this.minorAnchor = 10; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) + { + lineWidth = lineWidth || 0; + var w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth, h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + var canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2), canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + var sx = sourcePos[0] < targetPos[0] ? w - (lineWidth/2): (lineWidth/2), sy = sourcePos[1] < targetPos[1] ? h-(lineWidth/2) : (lineWidth/2); + var tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : w-(lineWidth/2), ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : h-(lineWidth/2); + var CP = self._findControlPoint([sx,sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + var CP2 = self._findControlPoint([tx,ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(sx,tx); var minx2 = Math.min(CP[0], CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(sx,tx); var maxx2 = Math.max(CP[0], CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > w) w = maxx; + if (minx < 0) { + canvasX += minx; var ox = Math.abs(minx); + w += ox; CP[0] += ox; sx += ox; tx +=ox; CP2[0] += ox; + } + + var miny1 = Math.min(sy,ty); var miny2 = Math.min(CP[1], CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(sy,ty); var maxy2 = Math.max(CP[1], CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > h) h = maxy; + if (miny < 0) { + canvasY += miny; var oy = Math.abs(miny); + h += oy; CP[1] += oy; sy += oy; ty +=oy; CP2[1] += oy; + } + + // return [ canvasx, canvasy, canvasWidth, canvasHeight, + // sourceX, sourceY, targetX, targetY, + // controlPoint1_X, controlPoint1_Y, controlPoint2_X, controlPoint2_Y + return [canvasX, canvasY, w, h, sx, sy, tx, ty, CP[0], CP[1], CP2[0], CP2[1] ]; + }; + + this.paint = function(d, ctx) { + + /*var img = new Image(); + img.src = "../img/pattern.jpg"; + ctx.fillStyle = ctx.createPattern(img, 'repeat-y');*/ + + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + //ctx.fill(); + } + }; + + + /** + * Types of endpoint UIs. we supply four - a circle of default radius 10px, a rectangle of + * default size 20x20, an image (with no default), and a Triangle, of default size 15. + * you can supply others of these if you want to - see the documentation for a howto. + */ + + /** + * a round endpoint, with default radius 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + } + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * A Rectangular endpoint, with default size 20x20. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + //todo: the fillStyle needs some thought. we want to support a few options: + // 1. nothing supplied; use the stroke color or the default if no stroke color. + // 2. a fill color supplied - use it + // 3. a gradient supplied - use it + // 4. setting the endpoint to the same color as the bg of the element it is attached to. + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + jsPlumb.Endpoints.Triangle = function(params) { + + params = params || { width:15, height:15 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - width/2; + var y = anchorPoint[1] - height/2; + + jsPlumb.sizeCanvas(canvas, x, y, width, height); + + var ctx = canvas.getContext('2d'); + var + offsetX = 0, + offsetY = 0, + angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = endpointStyle.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + ctx.fill(); + + }; + }; + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' property in the params object.. + */ + jsPlumb.Endpoints.Image = function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + }; +})(); diff --git a/archive/jquery.jsPlumb-all-1.1.0-min.js b/archive/jquery.jsPlumb-all-1.1.0-min.js new file mode 100644 index 000000000..03fc09a7e --- /dev/null +++ b/archive/jquery.jsPlumb-all-1.1.0-min.js @@ -0,0 +1,35 @@ +if(!Array.prototype.indexOf)Array.prototype.indexOf=function(j,h,d){h=+h||0;for(var e=this.length;h=0){delete a[c];a.splice(c,1);return true}}return false},X=function(a,b){N(a,function(c){c.canvas.style.display=b})},Y=function(a){N(a,function(b){b.canvas.style.display="none"==b.canvas.style.display?"block":"none"})},I=function(a,b,c){d&&d.debug("updating offset for element ["+a+"]; ui is ["+ +b+"]; recalc is ["+c+"]");if(c||b==null){b=$("#"+a);z[a]=[b.outerWidth(),b.outerHeight()];k[a]=b.offset()}else{c=b.absolutePosition||b.offset;b=b!=null?c:$("#"+a).offset();k[a]=b}},K=function(a,b){a=a||function(){};return function(c,g){b(c,g);a(c,g)}},aa=function(a){var b=this;this.x=a.x||0;this.y=a.y||0;var c=a.orientation||[0,0];this.offsets=a.offsets||[0,0];this.compute=function(g,n){return[g[0]+b.x*n[0]+b.offsets[0],g[1]+b.y*n[1]+b.offsets[1]]};this.getOrientation=function(){return c}},ba=function(a){var b= +a.reference,c=0,g=0,n=null;this.compute=function(o){g=c=0;return[o[0],o[1]]};this.getOrientation=function(){if(n)return n;else{var o=b.getOrientation();return[Math.abs(o[0])*c*-1,Math.abs(o[1])*g*-1]}};this.over=function(o){n=o.getOrientation()};this.out=function(){n=null}},R=function(a){var b=this;this.source=typeof a.source=="string"?$("#"+a.source):a.source;this.target=typeof a.target=="string"?$("#"+a.target):a.target;this.sourceId=$(this.source).attr("id");this.targetId=$(this.target).attr("id"); +this.endpointsOnTop=a.endpointsOnTop!=null?a.endpointsOnTop:true;this.endpoints=[];this.endpointStyles=[];var c=function(o,t,r){if(o)b.endpoints[t]=o;else{if(!r.endpoints)r.endpoints=[null,null];o=r.endpoints[t]||r.endpoint||w.Defaults.Endpoints[t]||w.Defaults.Endpoint||new w.Endpoints.Dot;if(!r.endpointStyles)r.endpointStyles=[null,null];b.endpoints[t]=new Q({style:r.endpointStyles[t]||r.endpointStyle||w.Defaults.EndpointStyles[t]||w.Defaults.EndpointStyle,endpoint:o,connections:[b],anchor:r.anchors? +r.anchors[t]:w.Defaults.Anchors[t]||w.Anchors.BottomCenter})}};c(a.sourceEndpoint,0,a);c(a.targetEndpoint,1,a);this.connector=this.endpoints[0].connector||this.endpoints[1].connector||a.connector||w.Defaults.Connector||new w.Connectors.Bezier;this.paintStyle=this.endpoints[0].connectionStyle||this.endpoints[1].connectionStyle||a.paintStyle||w.Defaults.PaintStyle;I(this.sourceId);I(this.targetId);c=k[this.sourceId];var g=z[this.sourceId];c=this.endpoints[0].anchor.compute([c.left,c.top],g);this.endpoints[0].paint(c); +c=k[this.targetId];g=z[this.targetId];c=this.endpoints[1].anchor.compute([c.left,c.top],g);this.endpoints[1].paint(c);var n=V(w.connectorClass);this.canvas=n;this.paint=function(o,t,r){d&&d.debug("Painting Connection; element in motion is "+o+"; ui is ["+t+"]; recalc is ["+r+"]");var u=o!=this.sourceId,x=u?this.sourceId:this.targetId,v=u?0:1,B=u?1:0;if(this.canvas.getContext){I(o,t,r);r&&I(x);t=k[o];r=k[x];o=z[o];var m=z[x];x=n.getContext("2d");var O=this.endpoints[B].anchor.compute([t.left,t.top], +o,[r.left,r.top],m);this.endpoints[B].anchor.getOrientation();t=this.endpoints[v].anchor.compute([r.left,r.top],m,[t.left,t.top],o);this.endpoints[v].anchor.getOrientation();v=this.connector.compute(O,t,this.endpoints[B].anchor,this.endpoints[v].anchor,this.paintStyle.lineWidth);w.sizeCanvas(n,v[0],v[1],v[2],v[3]);$.extend(x,this.paintStyle);if(this.paintStyle.gradient&&!h){u=u?x.createLinearGradient(v[4],v[5],v[6],v[7]):x.createLinearGradient(v[6],v[7],v[4],v[5]);for(B=0;B=0&&b.connections.splice(s,1)};this.isFloating=function(){return u!=null};var x=U();this.isFull=function(){return t<1?false:b.connections.length>=t};this.paint=function(s,G,D){d&&d.debug("Painting Endpoint with elementId ["+ +o+"]; anchorPoint is ["+s+"]");if(s==null){s=k[o];var C=z[o];if(s==null||C==null){I(o);s=k[o];C=z[o]}s=b.anchor.compute([s.left,s.top],C)}c.paint(s,b.anchor.getOrientation(),D||b.canvas,g,G||g)};if(a.isSource&&n.draggable){var v=null,B=null,m=null,O=false,L=null,A=a.dragOptions||{};A=$.extend({opacity:0.5,revert:true,helper:"clone"},A);A.start=K(A.start,function(){v=document.createElement("div");x.append(v);B=new String((new Date).getTime());$(v,x).attr("id",B);I(B);$(b.canvas,x).attr("dragId",B); +$(b.canvas,x).attr("elId",o);var s=new ba({reference:b.anchor});u=new Q({style:g,endpoint:c,anchor:s,source:v});m=b.connections.length==0||b.connections.length=4)c.orientation=[arguments[2],arguments[3]];if(arguments.length==6)c.offsets=[arguments[4],arguments[5]]}return new aa(c)},repaint:function(a){var b=function(g){g=typeof g=="string"?$("#"+g):g;M(g)};if(typeof a=="object")for(var c=0;c< +a.length;c++)b(a[c]);else b(a)},repaintEverything:function(){for(var a in f)M($("#"+a))},removeEndpoint:function(a,b){f[a]&&W(f,a,b)&&J(b.canvas)},setAutomaticRepaint:function(a){l=a},setDefaultNewCanvasSize:function(a){H=a},setDraggable:function(a,b){return T(a,function(c,g){q[g]=b;c.draggable&&c.draggable("option","disabled",!b)})},setDraggableByDefault:function(a){y=a},setDebugLog:function(a){d=a},setRepaintFunction:function(a){e=a},show:function(a){X(a,"block")},sizeCanvas:function(a,b,c,g,n){a.style.height= +n+"px";a.height=n;a.style.width=g+"px";a.width=g;a.style.left=b+"px";a.style.top=c+"px"},getTestHarness:function(){return{endpointCount:function(a){return(a=f[a])?a.length:0}}},toggle:Y,toggleVisible:Y,toggleDraggable:function(a){return T(a,function(b,c){var g=q[c]==null?y:q[c];g=!g;q[c]=g;b.draggable("option","disabled",!g);return g})},unload:function(){delete f;delete k;delete z;delete p;delete q;document.body.removeChild(E)}}})(); +(function(j){j.fn.plumb=function(h){h=j.extend({},h);return this.each(function(){var d=j.extend({source:j(this)},h);jsPlumb.connect(d)})};j.fn.detach=function(h){return this.each(function(){var d=j(this).attr("id");if(typeof h=="string")h=[h];for(var e=0;ek)k=l;if(e<0){q+=e;e=Math.abs(e);k+=e;F[0]+=e;z+=e;H+=e;d[0]+=e}e=Math.min(Math.min(E,f),Math.min(F[1], +d[1]));l=Math.max(Math.max(E,f),Math.max(F[1],d[1]));if(l>p)p=l;if(e<0){y+=e;e=Math.abs(e);p+=e;F[1]+=e;E+=e;f+=e;d[1]+=e}return[q,y,k,p,z,E,H,f,F[0],F[1],d[0],d[1]]};this.paint=function(d,e){e.beginPath();e.moveTo(d[4],d[5]);e.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]);e.stroke()}};jsPlumb.Endpoints.Dot=function(j){j=j||{radius:10};var h=this;this.radius=j.radius;var d=0.5*this.radius,e=this.radius/3,l=function(i){try{return parseInt(i)}catch(f){if(i.substring(i.length-1)=="%")return parseInt(i.substring(0, +i-1))}};this.paint=function(i,f,k,p,q){var y=p.radius||h.radius;jsPlumb.sizeCanvas(k,i[0]-y,i[1]-y,y*2,y*2);i=k.getContext("2d");k=$.extend({},p);if(k.fillStyle==null)k.fillStyle=q.strokeStyle;$.extend(i,k);q=/MSIE/.test(navigator.userAgent)&&!window.opera;if(p.gradient&&!q){q=p.gradient;k=d;var z=e;if(q.offset)k=l(q.offset);if(q.innerRadius)z=l(q.innerRadius);q=[k,z];f=i.createRadialGradient(y,y,y,y+(f[0]==1?q[0]*-1:q[0]),y+(f[1]==1?q[0]*-1:q[0]),q[1]);for(q=0;q endpoint lists. an element can have an arbitrary number of endpoints on it, + * and not all of them have to be connected to anything. + */ + var endpointsByElement = {}; + var offsets = []; + var floatingConnections = {}; + var draggableStates = {}; + var _draggableByDefault = true; + var sizes = []; + var _jsPlumbContextNode = null; + + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + /** + * helper method to add an item to a list, creating the list if it does not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + }; + + /** + * Handles the dragging of an element. + * @param element jQuery element + * @param ui UI object from jQuery's event system + */ + var _draw = function(element, ui) { + var id = $(element).attr("id"); + var endpoints = endpointsByElement[id]; + if (endpoints) { + //if (ui == null) _updateOffset(id, ui); + _updateOffset(id, ui); + var myOffset = offsets[id]; + var myWH = sizes[id]; + // loop through endpoints for this element + for (var i = 0; i < endpoints.length; i++) { + var e = endpoints[i]; + // first, paint the endpoint + + var anchorLoc = endpoints[i].anchor.compute([myOffset.left, myOffset.top], myWH); + // var anchorOrientation = endpoints[i].anchor.getOrientation(); + + //todo...connector paint style? we have lost that with the move to endpoint-centric. + // perhaps we only paint the endpoint here if it has no connections; it can use its own style. + //if (!e.connections || e.connections.length == 0) + e.paint(anchorLoc); + + // else { + //if (e.connections && e.connections.length > 0) { + // get all connections for the endpoint... + var l = e.connections; + for (var j = 0; j < l.length; j++) + l[j].paint(id, ui); // ...and paint them. + //} + } + } + }; + + /** + * helper function: the second argument is a function taking two args - the first is a + * jquery element, and the second is the element's id. + * + * the first argument may be one of three things: + * + * 1. a string, in the form "window1", for example. an element's id. if your id starts with a + * hash then jsPlumb does not append its own hash too... + * 2. a jquery element, already resolved using $(...). + * 3. a list of strings/jquery elements. + */ + var _elementProxy = function(element, fn) { + var retVal = null; + if (typeof element == 'object' && element.length) { + retVal = []; + for (var i = 0; i < element.length; i++) { + var el = typeof element[i] == 'string' ? $("#" + element[i]) : element[i]; + var id = el.attr("id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } + else { + var el = typeof element == 'string' ? + element.indexOf("#") == 0 ? $(element) : $("#" + element) + : element; + var id = el.attr("id"); + retVal = fn(el, id); + } + + return retVal; + }; + + /** + * Returns (creating if necessary) the DIV element that jsPlumb uses as the context for all of its + * canvases. having this makes it possible to makes calls like $("selector", context), which are + * faster than if you provide no context. also we can clear out everything easily like this, either + * on a detachEverything() call or during unload(). + */ + var _getContextNode = function() { + if (_jsPlumbContextNode == null) { + _jsPlumbContextNode= document.createElement("div"); + document.body.appendChild(_jsPlumbContextNode); + _jsPlumbContextNode.className = "_jsPlumb_context"; + } + return $(_jsPlumbContextNode); + }; + + /** + * gets an id for the given element, creating and setting one if necessary. + */ + var _getId = function(element) { + var id = $(element).attr("id"); + if (!id) { + id = "_jsPlumb_" + new String((new Date()).getTime()); + $(element).attr("id", id); + } + return id; + }; + + /** + * inits a draggable if it's not already initialised. + * todo: if the element was draggable already, like from some non-jsPlumb call, wrap the drag function. + */ + var _initDraggableIfNecessary = function(element, elementId, isDraggable, dragOptions) { + // dragging + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable && element.draggable) { + var options = dragOptions || jsPlumb.Defaults.DragOptions; + var dragCascade = options.drag || function(e,u) {}; + var initDrag = function(element, elementId, dragFunc) { + var opts = $.extend({drag:dragFunc}, options); + var draggable = draggableStates[elementId]; + opts.disabled = draggable == null ? false : !draggable; + element.draggable(opts); + }; + initDrag(element, elementId, function(event, ui) { + _draw(element, ui); + $(element).addClass("jsPlumb_dragged"); + dragCascade(event, ui); + }); + } + + }; + + var _log = function(msg) { + // not implemented. yet. + } + + /** + * helper to create a canvas. + * @param clazz optional class name for the canvas. + */ + var _newCanvas = function(clazz) { + var canvas = document.createElement("canvas"); + _getContextNode().append(canvas); + canvas.style.position="absolute"; + if (clazz) { canvas.className=clazz; } + + if (/MSIE/.test(navigator.userAgent) && !window.opera) { + // for IE we have to set a big canvas size. actually you can override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + /** + * performs the given function operation on all the connections found for the given element + * id; this means we find all the endpoints for the given element, and then for each endpoint + * find the connectors connected to it. then we pass each connection in to the given + * function. + */ + var _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + for (var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }; + /** + * perform an operation on all elements. + */ + var _operationOnAll = function(func) { + for (var elId in endpointsByElement) { + _operation(elId, func); + } + }; + /** + * helper to remove an element from the DOM. + */ + var _removeElement = function(element) { + if (element != null) { + try { _jsPlumbContextNode.removeChild(element); } + catch (e) { } + } + }; + /** + * helper to remove a list of elements from the DOM. + */ + var _removeElements = function(elements) { + for (var i in elements) + _removeElement(elements[i]); + }; + /** + * helper method to remove an item from a list. + */ + var _removeFromList = function(map, key, value) { + var l = map[key]; + if (l != null) { + var i = l.indexOf(value); + if (i >= 0) { + delete( l[i] ); + l.splice( i, 1 ); + return true; + } + } + return false; + }; + /** + * Sets whether or not the given element(s) should be draggable, regardless of what a particular + * plumb command may request. + * + * @param element May be a string, a jQuery elements, or a list of strings/jquery elements. + * @param draggable Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + var _helper = function(el, id) { + draggableStates[id] = draggable; + if (el.draggable) { + el.draggable("option", "disabled", !draggable); + } + }; + + return _elementProxy(element, _helper); + }; + /** + * private method to do the business of hiding/showing. + * @param elId Id of the element in question + * @param state String specifying a value for the css 'display' property ('block' or 'none'). + */ + var _setVisible = function(elId, state) { + var f = function(jpc) { + //todo should we find all the endpoints instead of going by connection? this will + jpc.canvas.style.display = state; + /*jpc.sourceEndpointCanvas.style.display = state; + jpc.targetEndpointCanvas.style.display = state;*/ + }; + + _operation(elId, f); + }; + /** + * toggles the draggable state of the element with the given id. + */ + var _toggleDraggable = function(el) { + var fn = function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + el.draggable("option", "disabled", !state); + return state; + }; + return _elementProxy(el, fn); + }; + /** + * private method to do the business of toggling hiding/showing. + * @param elId Id of the element in question + */ + var _toggleVisible = function(elId) { + var f = function(jpc) {; + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + /*jpc.sourceEndpointCanvas.style.display = state; + jpc.targetEndpointCanvas.style.display = state;*/ + }; + + _operation(elId, f); + }; + /** + * updates the offset and size for a given element, and stores the values. + * if 'ui' is not null we use that (it would have been passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we use the offset, outerWidth and outerHeight methods to get + * the current values. + */ + var _updateOffset = function(elId, ui, recalc) { + + if (log) log.debug("updating offset for element [" + elId + "]; ui is [" + ui + "]; recalc is [" + recalc + "]"); + + if (recalc || ui == null) { // if forced repaint or no ui helper available, we recalculate. + // get the current size and offset, and store them + var s = $("#" + elId); + sizes[elId] = [s.outerWidth(), s.outerHeight()]; + offsets[elId] = s.offset(); + } else { + // faster to use the ui element if it was passed in. + // fix for change in 1.8 (absolutePosition renamed to offset). plugin is compatible with + // 1.8 and 1.7. + + // todo: when the drag axis is supplied, the ui object passed in has incorrect values + // for the other axis - like say you set axis='x'. when you move the mouse up and down + // while dragging, the y values are for where the window would be if it was not + // just constrained to x. not sure if this is a jquery bug or whether there's a known + // trick or whatever. + var pos = ui.absolutePosition || ui.offset; + var anOffset = ui != null ? pos : $("#" + elId).offset(); + offsets[elId] = anOffset; + } + }; + /** + * wraps one function with another, creating a placeholder for the wrapped function + * if it was null. this is used to wrap the various drag/drop event functions - to allow + * jsPlumb to be notified of important lifecycle events without imposing itself on the user's + * drap/drop functionality. + * TODO: determine whether or not we should try/catch the plumb function, so that the cascade function is always executed. + */ + var _wrap = function(cascadeFunction, plumbFunction) { + cascadeFunction = cascadeFunction || function(e, ui) { }; + return function(e, ui) { + plumbFunction(e, ui); + cascadeFunction(e, ui); + }; + } + /** + * Anchor class. Anchors can be situated anywhere. + * params should contain three values, and may optionally have an 'offsets' argument: + * + * x : the x location of the anchor as a fraction of the total width. + * + * y : the y location of the anchor as a fraction of the total height. + * + * orientation : an [x,y] array indicating the general direction a connection + * from the anchor should go in. for more info on this, see the documentation, + * or the docs in jquery-jsPlumb-defaults-XXX.js for the default Anchors. + * + * offsets : an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. may be null. + * + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; this.y = params.y || 0; + var orientation = params.orientation || [0,0]; + this.offsets = params.offsets || [0,0]; + this.compute = function(xy, wh, txy, twh) { + return [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + } + this.getOrientation = function() { return orientation; }; + }; + + /** + * an anchor that floats. its orientation is computed dynamically from its position relative + * to the anchor it is floating relative to. + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for purposes of calculating the orientation. + var ref = params.reference; + // these are used to store the current relative position of our anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating anchor is hovering over another anchor. + var orientation = null; + + this.compute = function(xy, wh, txy, twh) { + // set these for the getOrientation method to use. + xDir = 0;//xy[0] < txy[0] ? -1 : xy[0] == txy[0] ? 0 : 1; + yDir = 0;//xy[1] < txy[1] ? -1 : xy[1] == txy[1] ? 0 : 1; + return [xy[0], xy[1]]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other anchor: if it declares zero for some direction, we declare zero too. + // this might not be the most awesome. perhaps we can come up with a better way. it's just so that the line we draw looks + // like it makes sense. maybe this wont make sense. + return [Math.abs(o[0]) * xDir * -1, Math.abs(o[1]) * yDir * -1]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering over another anchor; + * we want to assume that anchor's orientation for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no longer hovering + * over another anchor; we should resume calculating orientation as we normally do. + */ + this.out = function() { + orientation = null; + }; + }; + + // ************** connection + // **************************************** + /** + * allowed params: + * source: source element (string or a jQuery element) (required) + * target: target element (string or a jQuery element) (required) + * + * anchors: optional array of anchor placements. defaults to BottomCenter for source + * and TopCenter for target. + */ + var Connection = function(params) { + + // ************** get the source and target and register the connection. ******************* + var self = this; + // get source and target as jQuery objects + this.source = (typeof params.source == 'string') ? $("#" + params.source) : params.source; + this.target = (typeof params.target == 'string') ? $("#" + params.target) : params.target; + this.sourceId = $(this.source).attr("id"); + this.targetId = $(this.target).attr("id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + + // init endpoints + this.endpoints = []; + this.endpointStyles = []; + var prepareEndpoint = function(existing, index, params) { + if (existing) self.endpoints[index] = existing; + else { + if(!params.endpoints) params.endpoints = [null,null]; + var ep = params.endpoints[index] || params.endpoint || jsPlumb.Defaults.Endpoints[index] || jsPlumb.Defaults.Endpoint|| new jsPlumb.Endpoints.Dot(); + if (!params.endpointStyles) params.endpointStyles = [null,null]; + var es = params.endpointStyles[index] || params.endpointStyle || jsPlumb.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyle; + var a = params.anchors ? params.anchors[index] : jsPlumb.Defaults.Anchors[index] || jsPlumb.Anchors.BottomCenter; + self.endpoints[index] = new Endpoint({style:es, endpoint:ep, connections:[self], anchor:a }); + } + }; + + prepareEndpoint(params.sourceEndpoint, 0, params); + prepareEndpoint(params.targetEndpoint, 1, params); + + // make connector. if an endpoint has a connector + paintstyle to use, we use that. + // otherwise we use sensible defaults. + //this.connector = params.connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + //this.paintStyle = params.paintStyle || jsPlumb.Defaults.PaintStyle; + this.paintStyle = this.endpoints[0].connectionStyle || this.endpoints[1].connectionStyle || params.paintStyle || jsPlumb.Defaults.PaintStyle; + + _updateOffset(this.sourceId); + _updateOffset(this.targetId); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var anchorLoc = this.endpoints[0].anchor.compute([myOffset.left, myOffset.top], myWH); + this.endpoints[0].paint(anchorLoc); + myOffset = offsets[this.targetId]; myWH = sizes[this.targetId]; + anchorLoc = this.endpoints[1].anchor.compute([myOffset.left, myOffset.top], myWH); + this.endpoints[1].paint(anchorLoc); + + // *************** create canvas on which the connection will be drawn ************ + var canvas = _newCanvas(jsPlumb.connectorClass); + this.canvas = canvas; + + /** + * paints the connection. + * @param elId Id of the element that is in motion + * @param ui jQuery's event system ui object (present if we came from a drag to get here) + * @param recalc whether or not to recalculate element sizes. this is true if a repaint caused this to be painted. + */ + this.paint = function(elId, ui, recalc) { + + if (log) log.debug("Painting Connection; element in motion is " + elId + "; ui is [" + ui + "]; recalc is [" + recalc + "]"); + + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = !(elId == this.sourceId); + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + if (this.canvas.getContext) { + + _updateOffset(elId, ui, recalc); + if (recalc) _updateOffset(tId); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var myOffset = offsets[elId]; + var otherOffset = offsets[tId]; + var myWH = sizes[elId]; + var otherWH = sizes[tId]; + + var ctx = canvas.getContext('2d'); + var sAnchorP = this.endpoints[sIdx].anchor.compute([myOffset.left, myOffset.top], myWH, [otherOffset.left, otherOffset.top], otherWH); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.compute([otherOffset.left, otherOffset.top], otherWH, [myOffset.left, myOffset.top], myWH); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, this.paintStyle.lineWidth); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + $.extend(ctx, this.paintStyle); + + if (this.paintStyle.gradient && !ie) { + var g = swap ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + for (var i = 0; i < this.paintStyle.gradient.stops.length; i++) + g.addColorStop(this.paintStyle.gradient.stops[i][0],this.paintStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + + this.connector.paint(dim, ctx); + + // this.endpoints[swap ? 1 : 0].paint(sAnchorP, this.paintStyle); + //this.endpoints[swap ? 0 : 1].paint(tAnchorP, this.paintStyle); + } + }; + + this.repaint = function() { + this.paint(this.sourceId, null, true); + }; + + _initDraggableIfNecessary(self.source, self.sourceId, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, self.targetId, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + }; + + /** + * models an endpoint. can have one to N connections emanating from it (although how to handle that in the UI is + * a very good question). also has a Canvas and paint style. + * + * params: + * + * anchor : anchor for the endpoint, of type jsPlumb.Anchor. may be null. + * endpoint : endpoint object, of type jsPlumb.Endpoint. may be null. + * style : endpoint style, a js object. may be null. + * source : element the endpoint is attached to, of type jquery object. Required. + * canvas : canvas element to use. may be, and most often is, null. + * connections : optional list of connections to configure the endpoint with. + * isSource : boolean. indicates the endpoint can act as a source of new connections. optional. + * dragOptions : if isSource is set to true, you can supply arguments for the jquery draggable method. optional. + * connectionStyle : if isSource is set to true, this is the paint style for connections from this endpoint. optional. + * connector : optional connector type to use. + * isTarget : boolean. indicates the endpoint can act as a target of new connections. optional. + * dropOptions : if isTarget is set to true, you can supply arguments for the jquery droppable method. optional. + * reattach : optional boolean that determines whether or not the connections reattach after they + * have been dragged off an endpoint and left floating. defaults to false - connections + * dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + params = params || {}; + // make a copy. then we can use the wrapper function. + params = $.extend({}, params); + var self = this; + self.anchor = params.anchor || jsPlumb.Anchors.TopCenter; + var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot(); + var _style = params.style || jsPlumb.Defaults.EndpointStyle; + this.connectionStyle = params.connectionStyle; + this.connector = params.connector; + var _element = params.source; + var _elementId = $(_element).attr("id"); + var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of. + this.canvas = params.canvas || _newCanvas(jsPlumb.endpointClass); + this.connections = params.connections || []; + var _reattach = params.reattach || false; + var floatingEndpoint = null; + this.addConnection = function(connection) { + self.connections.push(connection); + }; + this.removeConnection = function(connection) { + var idx = self.connections.indexOf(connection); + if (idx >= 0) + self.connections.splice(idx, 1); + }; + this.isFloating = function() { return floatingEndpoint != null; }; + /** + * first pass at default ConnectorSelector: returns the first connection, if we have any. + * modified a little, 5/10/2010: now it only returns a connector if we have not got equal to or more than _maxConnector connectors + * attached. otherwise it is assumed a new connector is ok. but note with this setup we can't select any other connection than the first + * one. what if this could return a list? that implies everything should work with a list - dragging etc. could be nasty. could also + * be cool. + */ + var connectorSelector = function() { + return self.connections.length == 0 || self.connections.length < _maxConnections ? null : self.connections[0]; + }; + + // get the jsplumb context...lookups are faster with a context. + var contextNode = _getContextNode(); + + this.isFull = function() { return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); }; + + /** + * paints the Endpoint, recalculating offset and anchor positions if necessary. + */ + this.paint = function(anchorPoint, connectorPaintStyle, canvas) { + + if (log) log.debug("Painting Endpoint with elementId [" + _elementId + "]; anchorPoint is [" + anchorPoint + "]"); + + if (anchorPoint == null) { + // do we always want to force a repaint here? i dont think so! + var xy = offsets[_elementId]; + var wh = sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset(_elementId); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + anchorPoint = self.anchor.compute([xy.left, xy.top], wh); + } + _endpoint.paint(anchorPoint, self.anchor.getOrientation(), canvas || self.canvas, _style, connectorPaintStyle || _style); + }; + + + // is this a connection source? we make it draggable and have the drag listener + // maintain a connection with a floating endpoint. + if (params.isSource && _element.draggable) { + + var n = null, id = null, + jpc = null, + existingJpc = false, existingJpcParams = null; + + // first the question is, how many connections are on this endpoint? if it's only one, then excellent. otherwise we will either need a way + // to select one connection from the list, or drag them all. if we had a pluggable 'ConnectorSelector' interface we could probably + // provide a way for people to implement their own UI components to do the connector selection. the question in that particular case would be how much + // information the interface needs from jsPlumb at execution time. if, however, we leave out the connector selection, and drag them all, + // that wouldn't be too hard to organise. perhaps that behaviour would be on a switch for the endpoint, or perhaps the ConnectorSelector + // interface returns a List, with the default implementation just returning everything. i think i like that. + // + // let's say for now that there is just one endpoint, cos we need to get this going before we can consider a list of them anyway. + // the major difference between that case and the case of a new endpoint is actually quite small - it's a question of where the + // Connection comes from. for a new one, we create a new one. obviously. otherwise we just get the jpc from the Endpoint + // (remember we're only assuming one connection right now). so all of the UI stuff we do to create the floating endpoint etc + // will still be valid, but when we stop dragging, we'll have to do something different. if we stop with a valid drop i think it will + // be the same process. but if we stop with an invalid drop we have to reset the Connection to how it was when we got it. + var start = function(e, ui) { + //if (!isFull()) { + n = document.createElement("div"); + contextNode.append(n); + // create and assign an id, and initialize the offset. + id = new String(new Date().getTime()); + $(n, contextNode).attr("id", id); + _updateOffset(id); + // store the id of the dragging div and the source element. the drop function + // will pick these up. + $(self.canvas, contextNode).attr("dragId", id); + $(self.canvas, contextNode).attr("elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor({reference:self.anchor}); + floatingEndpoint = new Endpoint({ + style:_style, + endpoint:_endpoint, + anchor:floatingAnchor, + source:n + }); + //floatingEndpoint.originalAnchor = + + jpc = connectorSelector(); + if (jpc == null) { + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = new Connection({ + sourceEndpoint:self, + targetEndpoint:floatingEndpoint, + source:$(_element), + target:$(n, contextNode), + anchors:[self.anchor, floatingAnchor], + paintStyle : params.connectionStyle, // this can be null. Connection will use the default. + connector: params.connector + }); + // todo ...unregister on stop + self.addConnection(jpc); + } else { + existingJpc = true; + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; + // probably we should remove the connection? and add it back if the user + // does not drop it somewhere proper. + self.removeConnection(jpc); + if (anchorIdx == 0){ + existingJpcParams = [jpc.source, jpc.sourceId]; + jpc.source = $(n, contextNode); + jpc.sourceId = id; + }else { + existingJpcParams = [jpc.target, jpc.targetId]; + jpc.target = $(n, contextNode); + jpc.targetId = id; + } + + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // todo unregister on stop + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + } + //}; + + var dragOptions = params.dragOptions || { }; + dragOptions = $.extend({ opacity:0.5, revert:true, helper:'clone' }, dragOptions); + + dragOptions.start = _wrap(dragOptions.start, start); + dragOptions.drag = _wrap(dragOptions.drag, function(e, ui) { + _draw($(n, contextNode), ui); + }); + dragOptions.stop = _wrap(dragOptions.stop, + function(e, ui) { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements([floatingEndpoint.canvas, n]); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (jpc.endpoints[idx] == floatingEndpoint) { + + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + if (_reattach) { + jpc.floatingAnchorIndex = null; + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } + else { + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeElement(jpc.canvas); + } + } else { + _removeElement(jpc.canvas); + self.removeConnection(jpc); + } + } + jpc = null; + delete floatingEndpoint; + } + ); + + $(self.canvas, contextNode).draggable(dragOptions); + } + + // connector target + if (params.isTarget && _element.droppable) { + var dropOptions = params.dropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = $.extend({}, dropOptions); + var originalAnchor = null; + dropOptions.drop = _wrap(dropOptions.drop, function(e, ui) { + var id = $(ui.draggable, contextNode).attr("dragId"); + var elId = $(ui.draggable, contextNode).attr("elId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // remove this jpc from the current endpoint + jpc.endpoints[idx].removeConnection(jpc); + if (jpc.suspendedEndpoint) + jpc.suspendedEndpoint.removeConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + _initDraggableIfNecessary(_element, _elementId, params.draggable, {}); + jsPlumb.repaint($(ui.draggable, contextNode).attr("elId")); + delete floatingConnections[id]; + }); + // what to do when something is dropped. + // 1. find the jpc that is being dragged. the target endpoint of the jpc will be the + // one that is being dragged. + // 2. arrange for the floating endpoint to be replaced with this endpoint; make sure + // everything gets registered ok etc. + // 3. arrange for the floating endpoint to be deleted. + // 4. make sure that the stop method of the drag does not cause the jpc to be cleaned up. we want to keep it now. + + // other considerations: when in the hover mode, we should switch the floating endpoint's + // orientation to be the same as the drop target. this will cause the connector to snap + // into the shape it will take if the user drops at that point. + + dropOptions.over = _wrap(dropOptions.over, function(event, ui) { + var id = $(ui.draggable, contextNode).attr("dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + }); + + dropOptions.out = _wrap(dropOptions.out, function(event, ui) { + var id = $(ui.draggable, contextNode).attr("dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + }); + + $(self.canvas, contextNode).droppable(dropOptions); + } + + // woo...add a plumb command to Endpoint. + this.plumb = function(params) { + // not much to think about. the target should be an Endpoint, but what else? + // all the style stuff is on the endpoint itself already. + //todo this should call the main plumb method, just with some different args. + + }; + + return self; + }; + + /** + * jsPlumb public API + */ + var jsPlumb = window.jsPlumb = { + + Defaults : { + Anchors : [ null, null ], + Connector : null, + DragOptions: { }, + DropOptions: { }, + Endpoint : null, + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : null }, + EndpointStyles : [ null, null ], + MaxConnections : null, + PaintStyle : { lineWidth : 10, strokeStyle : 'red' } + }, + + connectorClass : '_jsPlumb_connector', + endpointClass : '_jsPlumb_endpoint', + + Anchors : {}, + Connectors : {}, + Endpoints : {}, + + /** + * adds an endpoint to the element + */ + addEndpoint : function(target, params) { + params = $.extend({}, params); + var el = typeof target == 'string' ? $("#" + target) : target; + var id = $(el).attr("id"); + params.source = el; + _updateOffset(id); + var e = new Endpoint(params); + _addToList(endpointsByElement, id, e); + + var myOffset = offsets[id]; + var myWH = sizes[id]; + + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH); + e.paint(anchorLoc); + return e; + }, + + /** + * wrapper around standard jquery animate function; injects a call to jsPlumb in the + * 'step' function (creating it if necessary). this only supports the two-arg version + * of the animate call in jquery - the one that takes an object as the second arg. + */ + animate : function(el, properties, options) { + var ele = typeof(el)=='string' ? $("#" + el) : el; + var id = ele.attr("id"); + options = options || {}; + options.step = _wrap(options.step, function() { jsPlumb.repaint(id); }); + ele.animate(properties, options); + }, + + /** + * adds a list of endpoints to the element + */ + addEndpoints : function(target, endpoints) { + var results = []; + for (var i = 0; i < endpoints.length; i++) { + results.push(jsPlumb.addEndpoint(target, endpoints[i])); + } + return results; + }, + + /** + * establishes a connection between two elements. + * @param params object containing setup for the connection. see documentation. + */ + connect : function(params) { + if (params.sourceEndpoint && params.sourceEndpoint.isFull()) { + _log("could not add connection; source endpoint is full"); + return; + } + + if (params.targetEndpoint && params.targetEndpoint.isFull()) { + _log("could not add connection; target endpoint is full"); + return; + } + + var jpc = new Connection(params); + + // register endpoints for the element + _addToList(endpointsByElement, jpc.sourceId, jpc.endpoints[0]); + _addToList(endpointsByElement, jpc.targetId, jpc.endpoints[1]); + + jpc.endpoints[0].addConnection(jpc); + jpc.endpoints[1].addConnection(jpc); + + // force a paint + _draw(jpc.source); + + }, + + connectEndpoints : function(params) { + var jpc = Connection(params); + + }, + + /** + * Remove one connection to an element. + * @param sourceId id of the first window in the connection + * @param targetId id of the second window in the connection + * @return true if successful, false if not. + */ + detach : function(sourceId, targetId) { + var f = function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas); + /*_removeElement(jpc.targetEndpointCanvas); + _removeElement(jpc.sourceEndpointCanvas);*/ + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + return true; + } + }; + + // todo: how to cleanup the actual storage? a third arg to _operation? + _operation(sourceId, f); + }, + + /** + * remove all an element's connections. + * @param elId id of the + */ + detachAll : function(elId) { + + var f = function(jpc) { + // todo replace with _cleanupConnection call here. + _removeElement(jpc.canvas); + /*_removeElement(jpc.targetEndpointCanvas); + _removeElement(jpc.sourceEndpointCanvas);*/ + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + }; + _operation(elId, f); + //delete endpointsByElement[elId]; + }, + + /** + * remove all connections. and endpoints? probably not. + */ + detachEverything : function() { + var f = function(jpc) { + _removeElement(jpc.canvas); + /*_removeElement(jpc.targetEndpointCanvas); + _removeElement(jpc.sourceEndpointCanvas);*/ + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + }; + + _operationOnAll(f); + + /*delete endpointsByElement; + endpointsByElement = {};*/ + }, + + /** + * Set an element's connections to be hidden. + */ + hide : function(elId) { + _setVisible(elId, "none"); + }, + + /** + * Creates an anchor with the given params. + * x - the x location of the anchor as a fraction of the total width. + * y - the y location of the anchor as a fraction of the total height. + * orientation - an [x,y] array indicating the general direction a connection from the anchor should go in. + * offsets - an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. optional. defaults to [0,0]. + */ + makeAnchor : function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards compatibility if we are given only one value we assume it's a call in the old form. + var params = {}; + if (arguments.length == 1) $.extend(params, x); + else { + params = {x:x, y:y}; + if (arguments.length >= 4) { + params.orientation = [arguments[2], arguments[3]]; + } + if (arguments.length == 6) params.offsets = [arguments[4], arguments[5]]; + } + return new Anchor(params); + }, + + + /** + * repaint element and its connections. element may be an id or the actual jQuery object. + * this method gets new sizes for the elements before painting anything. + */ + repaint : function(el) { + + var _processElement = function(el) { + var ele = typeof(el)=='string' ? $("#" + el) : el; + _draw(ele); + }; + + // TODO: support a jQuery result object too! + + // support both lists... + if (typeof el =='object') { + for (var i = 0; i < el.length; i++) + _processElement(el[i]); + } // ...and single strings. + else _processElement(el); + }, + + /** + * repaint all connections. + */ + repaintEverything : function() { + for (var elId in endpointsByElement) { + _draw($("#" + elId)); + } + }, + + removeEndpoint : function(elId, endpoint) { + var ebe = endpointsByElement[elId]; + if (ebe) { + // var i = ebe.indexOf(endpoint); + // alert('element has ' + ebe.length); + //if (i > -1) { + //ebe.splice(i, 1); + if(_removeFromList(endpointsByElement, elId, endpoint)) + _removeElement(endpoint.canvas); + //} + } + }, + + /** + * sets/unsets automatic repaint on window resize. + */ + setAutomaticRepaint : function(value) { + automaticRepaint = value; + }, + + /** + * Sets the default size jsPlumb will use for a new canvas (we create a square canvas so + * one value is all that is required). This is a hack for IE, because ExplorerCanvas seems + * to need for a canvas to be larger than what you are going to draw on it at initialisation + * time. The default value of this is 1200 pixels, which is quite large, but if for some + * reason you're drawing connectors that are bigger, you should adjust this value appropriately. + */ + setDefaultNewCanvasSize : function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }, + + /** + * Sets whether or not a given element is draggable, regardless of what any plumb command + * may request. + */ + setDraggable: _setDraggable, + + /** + * Sets whether or not elements are draggable by default. Default for this is true. + */ + setDraggableByDefault: function(draggable) { + _draggableByDefault = draggable; + }, + + setDebugLog: function(debugLog) { + log = debugLog; + }, + + /** + * Sets the function to fire when the window size has changed and a repaint was fired. + */ + setRepaintFunction : function(f) { + repaintFunction = f; + }, + + /** + * Set an element's connections to be visible. + */ + show : function(elId) { + _setVisible(elId, "block"); + }, + + /** + * helper to size a canvas. + */ + sizeCanvas : function(canvas, x, y, w, h) { + canvas.style.height = h + "px"; canvas.height = h; + canvas.style.width = w + "px"; canvas.width = w; + canvas.style.left = x + "px"; canvas.style.top = y + "px"; + }, + + /** + * gets some test hooks. nothing writable. + */ + getTestHarness : function() { + return { + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + } + }; + }, + + /** + * Toggles visibility of an element's connections. kept for backwards compatibility + */ + toggle : _toggleVisible, + + /** + * new name for the old toggle method. + */ + toggleVisible : _toggleVisible, + + /** + * Toggles draggability (sic) of an element. + */ + toggleDraggable : _toggleDraggable, + + /** + * Unloads jsPlumb, deleting all storage. You should call this + */ + unload : function() { + delete endpointsByElement; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + document.body.removeChild(_jsPlumbContextNode); + } + }; + +})(); + +// jQuery plugin code +(function($){ + /** + * plumbs the results of the selector to some target, using the given options if supplied, + * or the defaults otherwise. + */ + $.fn.plumb = function(options) { + var options = $.extend({}, options); + + return this.each(function() + { + var params = $.extend({source:$(this)}, options); + jsPlumb.connect(params); + }); + }; + + /** + * detaches the results of the selector from the given target or list of targets - 'target' + * may be a String or a List. + */ + $.fn.detach = function(target) { + return this.each(function() + { + var id = $(this).attr("id"); + if (typeof target == 'string') target = [target]; + for (var i = 0; i < target.length; i++) + jsPlumb.detach(id, target[i]); + }); + }; + + /** + * detaches the results from the selector from all connections. + */ + $.fn.detachAll = function() { + return this.each(function() + { + var id = $(this).attr("id"); + jsPlumb.detachAll(id); + }); + }; + + /** + * adds an endpoint to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoint = function(options) { + var addedEndpoints = []; + this.each(function() + { + //var params = $.extend({source:$(this)}, options); + addedEndpoints.push(jsPlumb.addEndpoint($(this).attr("id"), options)); + }); + return addedEndpoints; + }; + + /** + * adds a list of endpoints to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoints = function(endpoints) { + var addedEndpoints = []; + return this.each(function() + { + var e = jsPlumb.addEndpoints($(this).attr("id"), endpoints); + for (var i = 0; i < e.length; i++) addedEndpoints.push(e[i]); + }); + return addedEndpoints; + }; + + /** + * remove the endpoint, if it exists, deleting its UI elements etc. + */ + $.fn.removeEndpoint = function(endpoint) { + this.each(function() + { + jsPlumb.removeEndpoint($(this).attr("id"), endpoint); + }); + }; + +})(jQuery); + + +/** +* jsPlumb-defaults-1.1.0 +* +* This script contains the default Anchors, Endpoints and Connectors for jsPlumb. It should be used with jsPlumb 1.1.0 and above; +* prior to version 1.1.0 of jsPlumb the defaults were included inside the main script. +* +* NOTE: for production usage you should use jsPlumb-all-1.1.0-min.js, which contains the main jsPlumb script and this script together, +* in a minified file. +*/ + +(function() { + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * getOrientation : function() { return [ox, oy]; } + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + jsPlumb.Anchors.TopCenter = jsPlumb.makeAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors.BottomCenter = jsPlumb.makeAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors.LeftMiddle = jsPlumb.makeAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors.RightMiddle = jsPlumb.makeAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors.Center = jsPlumb.makeAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors.TopRight = jsPlumb.makeAnchor(1, 0, 0,-1); + jsPlumb.Anchors.BottomRight = jsPlumb.makeAnchor(1, 1, 0, 1); + jsPlumb.Anchors.TopLeft = jsPlumb.makeAnchor(0, 0, 0, -1); + jsPlumb.Anchors.BottomLeft = jsPlumb.makeAnchor(0, 1, 0, 1); + + + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (w < 2 * lineWidth) { + w = 2 * lineWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - lineWidth; + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < 2 * lineWidth) { + // minimum size is 2 * line Width + h = 2 * lineWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - lineWidth; + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + var sx = sourcePos[0] < targetPos[0] ? w-xo : xo; + var sy = sourcePos[1] < targetPos[1] ? h-yo : yo; + var tx = sourcePos[0] < targetPos[0] ? xo : w-xo; + var ty = sourcePos[1] < targetPos[1] ? yo : h-yo; + var retVal = [ x, y, w, h, sx, sy, tx, ty ]; + + return retVal; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + }; + + /** + * This Connector draws a Bezier curve with two control points. + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + jsPlumb.Connectors.Bezier = function(curviness) { + + var self = this; + this.majorAnchor = curviness || 150; + this.minorAnchor = 10; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) + { + lineWidth = lineWidth || 0; + var w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth, h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + var canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2), canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + var sx = sourcePos[0] < targetPos[0] ? w - (lineWidth/2): (lineWidth/2), sy = sourcePos[1] < targetPos[1] ? h-(lineWidth/2) : (lineWidth/2); + var tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : w-(lineWidth/2), ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : h-(lineWidth/2); + var CP = self._findControlPoint([sx,sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + var CP2 = self._findControlPoint([tx,ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(sx,tx); var minx2 = Math.min(CP[0], CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(sx,tx); var maxx2 = Math.max(CP[0], CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > w) w = maxx; + if (minx < 0) { + canvasX += minx; var ox = Math.abs(minx); + w += ox; CP[0] += ox; sx += ox; tx +=ox; CP2[0] += ox; + } + + var miny1 = Math.min(sy,ty); var miny2 = Math.min(CP[1], CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(sy,ty); var maxy2 = Math.max(CP[1], CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > h) h = maxy; + if (miny < 0) { + canvasY += miny; var oy = Math.abs(miny); + h += oy; CP[1] += oy; sy += oy; ty +=oy; CP2[1] += oy; + } + + // return [ canvasx, canvasy, canvasWidth, canvasHeight, + // sourceX, sourceY, targetX, targetY, + // controlPoint1_X, controlPoint1_Y, controlPoint2_X, controlPoint2_Y + return [canvasX, canvasY, w, h, sx, sy, tx, ty, CP[0], CP[1], CP2[0], CP2[1] ]; + }; + + this.paint = function(d, ctx) { + + /*var img = new Image(); + img.src = "../img/pattern.jpg"; + ctx.fillStyle = ctx.createPattern(img, 'repeat-y');*/ + + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + //ctx.fill(); + } + }; + + + /** + * Types of endpoint UIs. we supply three - a circle of default radius 10px, a rectangle of + * default size 20x20, and an image (with no default). you can supply others of these if you want to - see the documentation + * for a howto. + */ + + /** + * a round endpoint, with default radius 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + } + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = $.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + $.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * A Rectangular endpoint, with default size 20x20. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + //todo: the fillStyle needs some thought. we want to support a few options: + // 1. nothing supplied; use the stroke color or the default if no stroke color. + // 2. a fill color supplied - use it + // 3. a gradient supplied - use it + // 4. setting the endpoint to the same color as the bg of the element it is attached to. + var style = $.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + $.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' property in the params object.. + */ + jsPlumb.Endpoints.Image = function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + }; +})(); diff --git a/archive/jquery.jsPlumb-all-1.1.1-min.js b/archive/jquery.jsPlumb-all-1.1.1-min.js new file mode 100644 index 000000000..5b3d24a72 --- /dev/null +++ b/archive/jquery.jsPlumb-all-1.1.1-min.js @@ -0,0 +1,39 @@ +(function(){function e(){o&&f()}var h=/MSIE/.test(navigator.userAgent)&&!window.opera,d=null,f=function(){j.repaintEverything()},o=true,m=null;$(window).bind("resize",function(){m&&clearTimeout(m);m=setTimeout(e,100)});var g={},l=[],s={},r={},y=true,z=[],H=1200,I=function(a,b,c){var i=function(q,t){if(q===t)return true;else if(typeof q=="object"&&typeof t=="object"){var u=true;for(var v in q)if(!i(q[v],t[v])){u=false;break}for(v in t)if(!i(t[v],q[v])){u=false;break}return u}};c=+c||0;for(var p=a.length;c< +p;c++)if(i(a[c],b))return c;return-1},E=function(a,b,c){var i=a[b];if(i==null){i=[];a[b]=i}i.push(c)},O=function(a,b){var c=C(a,"id"),i=g[c];if(i){J(c,b);for(var p=l[c],q=z[c],t=0;t=0){delete a[c];a.splice(c,1);return true}}return false},W=function(a,b){var c=C(a,"id");P(c,function(i){i.canvas.style.display=b})},X=function(a){P(a,function(b){b.canvas.style.display="none"==b.canvas.style.display? +"block":"none"})},J=function(a,b,c){if(c||b==null){w(a);b=j.CurrentLibrary.getElementObject(a);b=j.CurrentLibrary.getSize(b);z[a]=b;b=j.CurrentLibrary.getElementObject(a);b=j.CurrentLibrary.getOffset(b);l[a]=b}else l[a]=b},K=function(a,b){a=a||function(){};return function(){b.apply(this,arguments);a.apply(this,arguments)}},da=function(a){var b=this;this.x=a.x||0;this.y=a.y||0;var c=a.orientation||[0,0];this.offsets=a.offsets||[0,0];this.compute=function(i,p){return[i[0]+b.x*p[0]+b.offsets[0],i[1]+ +b.y*p[1]+b.offsets[1]]};this.getOrientation=function(){return c}},ea=function(a){var b=a.reference,c=0,i=0,p=null;this.compute=function(q){i=c=0;return[q[0],q[1]]};this.getOrientation=function(){if(p)return p;else{var q=b.getOrientation();return[Math.abs(q[0])*c*-1,Math.abs(q[1])*i*-1]}};this.over=function(q){p=q.getOrientation()};this.out=function(){p=null}},Y=function(a){var b=this;this.source=w(a.source);this.target=w(a.target);this.sourceId=C(this.source,"id");this.targetId=C(this.target,"id"); +this.endpointsOnTop=a.endpointsOnTop!=null?a.endpointsOnTop:true;this.endpoints=[];this.endpointStyles=[];var c=function(q,t,u){if(q)b.endpoints[t]=q;else{if(!u.endpoints)u.endpoints=[null,null];q=u.endpoints[t]||u.endpoint||j.Defaults.Endpoints[t]||j.Defaults.Endpoint||new j.Endpoints.Dot;if(!u.endpointStyles)u.endpointStyles=[null,null];b.endpoints[t]=new S({style:u.endpointStyles[t]||u.endpointStyle||j.Defaults.EndpointStyles[t]||j.Defaults.EndpointStyle,endpoint:q,connections:[b],anchor:u.anchors? +u.anchors[t]:j.Defaults.Anchors[t]||j.Anchors.BottomCenter})}};c(a.sourceEndpoint,0,a);c(a.targetEndpoint,1,a);this.connector=this.endpoints[0].connector||this.endpoints[1].connector||a.connector||j.Defaults.Connector||new j.Connectors.Bezier;this.paintStyle=this.endpoints[0].connectionStyle||this.endpoints[1].connectionStyle||a.paintStyle||j.Defaults.PaintStyle;J(this.sourceId);J(this.targetId);c=l[this.sourceId];var i=z[this.sourceId];c=this.endpoints[0].anchor.compute([c.left,c.top],i);this.endpoints[0].paint(c); +c=l[this.targetId];i=z[this.targetId];c=this.endpoints[1].anchor.compute([c.left,c.top],i);this.endpoints[1].paint(c);var p=U(j.connectorClass);this.canvas=p;this.paint=function(q,t,u){d&&d.debug("Painting Connection; element in motion is "+q+"; ui is ["+t+"]; recalc is ["+u+"]");var v=q!=this.sourceId,A=v?this.sourceId:this.targetId,k=v?0:1,F=v?1:0;if(this.canvas.getContext){J(q,t,u);u&&J(A);t=l[q];u=l[A];q=z[q];var G=z[A];A=p.getContext("2d");var x=this.endpoints[F].anchor.compute([t.left,t.top], +q,[u.left,u.top],G);this.endpoints[F].anchor.getOrientation();t=this.endpoints[k].anchor.compute([u.left,u.top],G,[t.left,t.top],q);this.endpoints[k].anchor.getOrientation();k=this.connector.compute(x,t,this.endpoints[F].anchor,this.endpoints[k].anchor,this.paintStyle.lineWidth);j.sizeCanvas(p,k[0],k[1],k[2],k[3]);j.extend(A,this.paintStyle);if(this.paintStyle.gradient&&!h){v=v?A.createLinearGradient(k[4],k[5],k[6],k[7]):A.createLinearGradient(k[6],k[7],k[4],k[5]);for(F=0;F=0&&b.connections.splice(n,1)};this.isConnectedTo=function(n){var D=false;if(n)for(var B=0;B=t};this.paint=function(n,D,B){d&&d.debug("Painting Endpoint with elementId ["+q+"]; anchorPoint is ["+n+"]");if(n==null){n=l[q];var M=z[q];if(n==null||M==null){J(q);n=l[q];M=z[q]}n=b.anchor.compute([n.left,n.top],M)}c.paint(n,b.anchor.getOrientation(),B||b.canvas,i,D||i)};if(a.isSource&&j.CurrentLibrary.isDragSupported(p)){var A=null,k=null,F=false,G=null,x=a.dragOptions||{},N=j.extend({},j.CurrentLibrary.defaultDragOptions);x=j.extend(N, +x);N=j.CurrentLibrary.dragEvents.start;var Z=j.CurrentLibrary.dragEvents.stop,aa=j.CurrentLibrary.dragEvents.drag;x[N]=K(x[N],function(){A=document.createElement("div");document.body.appendChild(A);var n=""+new String((new Date).getTime());Q(w(A),"id",n);J(n);Q(w(b.canvas),"dragId",n);Q(w(b.canvas),"elId",q);var D=new ea({reference:b.anchor});v=new S({style:i,endpoint:c,anchor:D,source:A});k=b.connections.length==0||b.connections.length=4)c.orientation=[arguments[2],arguments[3]];if(arguments.length==6)c.offsets=[arguments[4],arguments[5]]}return new da(c)},repaint:function(a){var b=function(i){i=w(i);O(i)};if(typeof a=="object")for(var c=0;c1&&alert("poo");e.draggable(h)},isDragSupported:function(e){return e.draggable},setDraggable:function(e,h){e.draggable("option","disabled",!h)},initDroppable:function(e,h){e.droppable(h)},isDropSupported:function(e){return e.droppable},animate:function(e,h,d){e.animate(h,d)},getUIPosition:function(e){e=e[1];return e.absolutePosition||e.offset},getDragObject:function(e){return e[1].draggable}}})(); +(function(){jsPlumb.Anchors.TopCenter=jsPlumb.makeAnchor(0.5,0,0,-1);jsPlumb.Anchors.BottomCenter=jsPlumb.makeAnchor(0.5,1,0,1);jsPlumb.Anchors.LeftMiddle=jsPlumb.makeAnchor(0,0.5,-1,0);jsPlumb.Anchors.RightMiddle=jsPlumb.makeAnchor(1,0.5,1,0);jsPlumb.Anchors.Center=jsPlumb.makeAnchor(0.5,0.5,0,0);jsPlumb.Anchors.TopRight=jsPlumb.makeAnchor(1,0,0,-1);jsPlumb.Anchors.BottomRight=jsPlumb.makeAnchor(1,1,0,1);jsPlumb.Anchors.TopLeft=jsPlumb.makeAnchor(0,0,0,-1);jsPlumb.Anchors.BottomLeft=jsPlumb.makeAnchor(0, +1,0,1);jsPlumb.Connectors.Straight=function(){this.compute=function(e,h,d,f,o){d=Math.abs(e[0]-h[0]);f=Math.abs(e[1]-h[1]);var m=0.45*d,g=0.45*f;d*=1.9;f*=1.9;var l=Math.min(e[0],h[0])-m,s=Math.min(e[1],h[1])-g;if(d<2*o){d=2*o;l=e[0]+(h[0]-e[0])/2-o;m=(d-Math.abs(e[0]-h[0]))/2}if(f<2*o){f=2*o;s=e[1]+(h[1]-e[1])/2-o;g=(f-Math.abs(e[1]-h[1]))/2}return[l,s,d,f,e[0]l)l=o;if(f<0){r+=f;f=Math.abs(f);l+=f;E[0]+=f;z+=f;I+=f;d[0]+=f}f=Math.min(Math.min(H,g),Math.min(E[1], +d[1]));o=Math.max(Math.max(H,g),Math.max(E[1],d[1]));if(o>s)s=o;if(f<0){y+=f;f=Math.abs(f);s+=f;E[1]+=f;H+=f;g+=f;d[1]+=f}return[r,y,l,s,z,H,I,g,E[0],E[1],d[0],d[1]]};this.paint=function(d,f){f.beginPath();f.moveTo(d[4],d[5]);f.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]);f.stroke()}};jsPlumb.Endpoints.Dot=function(e){e=e||{radius:10};var h=this;this.radius=e.radius;var d=0.5*this.radius,f=this.radius/3,o=function(m){try{return parseInt(m)}catch(g){if(m.substring(m.length-1)=="%")return parseInt(m.substring(0, +m-1))}};this.paint=function(m,g,l,s,r){var y=s.radius||h.radius;jsPlumb.sizeCanvas(l,m[0]-y,m[1]-y,y*2,y*2);m=l.getContext("2d");l=jsPlumb.extend({},s);if(l.fillStyle==null)l.fillStyle=r.strokeStyle;jsPlumb.extend(m,l);r=/MSIE/.test(navigator.userAgent)&&!window.opera;if(s.gradient&&!r){r=s.gradient;l=d;var z=f;if(r.offset)l=o(r.offset);if(r.innerRadius)z=o(r.innerRadius);r=[l,z];g=m.createRadialGradient(y,y,y,y+(g[0]==1?r[0]*-1:r[0]),y+(g[1]==1?r[0]*-1:r[0]),r[1]);for(r=0;r endpoint lists. an element can have an arbitrary number of endpoints on it, + * and not all of them have to be connected to anything. + */ + var endpointsByElement = {}; + var offsets = []; + var floatingConnections = {}; + var draggableStates = {}; + var _draggableByDefault = true; + var sizes = []; + + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + var _findIndex = function( a, v, b, s ) { + + var _eq = function(o1, o2) { + if (o1 === o2) return true; + else if (typeof o1 == 'object' && typeof o2 == 'object') { + var same = true; + for(var propertyName in o1) { + if(!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for(var propertyName in o2) { + if(!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for( var i = +b || 0, l = a.length; i < l; i++ ) { + if( _eq(a[i], v)) return i; + } + return -1; + }; + + /** + * helper method to add an item to a list, creating the list if it does not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + }; + + /** + * Handles the dragging of an element. + * @param element jQuery element + * @param ui UI object from current library's event system + */ + var _draw = function(element, ui) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints) { + _updateOffset(id, ui); + var myOffset = offsets[id]; + var myWH = sizes[id]; + // loop through endpoints for this element + for (var i = 0; i < endpoints.length; i++) { + var e = endpoints[i]; + var anchorLoc = endpoints[i].anchor.compute([myOffset.left, myOffset.top], myWH); + e.paint(anchorLoc); + var l = e.connections; + for (var j = 0; j < l.length; j++) + l[j].paint(id, ui); // ...and paint them. + } + } + }; + + /** + * helper function: the second argument is a function taking two args - the first is a + * jquery element, and the second is the element's id. + * + * the first argument may be one of three things: + * + * 1. a string, in the form "window1", for example. an element's id. if your id starts with a + * hash then jsPlumb does not append its own hash too... + * 2. a jquery element, already resolved using $(...). + * 3. a list of strings/jquery elements. + */ + var _elementProxy = function(element, fn) { + var retVal = null; + if (typeof element == 'object' && element.length) { + retVal = []; + for (var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]); + var id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } + else { + var el = _getElementObject(element); + var id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + + return retVal; + }; + + /** + * gets the named attribute from the given element (id or element object) + */ + var _getAttribute = function(el, attName) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + return jsPlumb.CurrentLibrary.getAttribute(ele, attName); + }; + + var _setAttribute = function(el, attName, attValue) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + jsPlumb.CurrentLibrary.setAttribute(ele, attName, attValue); + }; + + var _addClass = function(el, clazz) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + jsPlumb.CurrentLibrary.addClass(ele, clazz); + }; + + var _getElementObject = function(elId) { + return jsPlumb.CurrentLibrary.getElementObject(elId); + }; + + var _getOffset = function(el) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + return jsPlumb.CurrentLibrary.getOffset(ele); + }; + + var _getSize = function(el) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + return jsPlumb.CurrentLibrary.getSize(ele); + }; + + var _initDraggable = function(el, options) { + return jsPlumb.CurrentLibrary.initDraggable(el, options); + }; + + var _isDragSupported = function(el) { + return jsPlumb.CurrentLibrary.isDragSupported(el); + }; + + var _initDroppable = function(el, options) { + return jsPlumb.CurrentLibrary.initDroppable(el, options); + }; + + var _isDropSupported = function(el) { + return jsPlumb.CurrentLibrary.isDropSupported(el); + }; + + var _animate= function(el, properties, options) { + return jsPlumb.CurrentLibrary.animate(el, properties, options); + }; + + var _appendCanvas = function(canvas) { + document.body.appendChild(canvas); + }; + + /** + * gets an id for the given element, creating and setting one if necessary. + */ + var _getId = function(element) { + var id = _getAttribute(element, "id"); + if (!id) { + id = "_jsPlumb_" + new String((new Date()).getTime()); + _setAttribute(element, "id", id); + } + return id; + }; + + /** + * inits a draggable if it's not already initialised. + * todo: if the element was draggable already, like from some non-jsPlumb call, wrap the drag function. + */ + var _initDraggableIfNecessary = function(element, elementId, isDraggable, dragOptions) { + // dragging + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable && _isDragSupported(element)) { + var options = dragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend({}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var initDrag = function(element, elementId, dragFunc) { + options[dragEvent] = _wrap(options[dragEvent], dragFunc); + var draggable = draggableStates[elementId]; + options.disabled = draggable == null ? false : !draggable; + _initDraggable(element, options); + }; + initDrag(element, elementId, function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + } + }; + + var _log = function(msg) { + // not implemented. yet. + } + + /** + * helper to create a canvas. + * @param clazz optional class name for the canvas. + */ + var _newCanvas = function(clazz) { + var canvas = document.createElement("canvas"); + _appendCanvas(canvas); + canvas.style.position="absolute"; + if (clazz) { canvas.className=clazz; } + _getId(canvas); // set an id. + + if (ie) { + // for IE we have to set a big canvas size. actually you can override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + /** + * performs the given function operation on all the connections found for the given element + * id; this means we find all the endpoints for the given element, and then for each endpoint + * find the connectors connected to it. then we pass each connection in to the given + * function. + */ + var _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + for (var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }; + /** + * perform an operation on all elements. + */ + var _operationOnAll = function(func) { + for (var elId in endpointsByElement) { + _operation(elId, func); + } + }; + /** + * helper to remove an element from the DOM. + */ + var _removeElement = function(element) { + if (element != null) { + try { document.body.removeChild(element); } + catch (e) { } + } + }; + /** + * helper to remove a list of elements from the DOM. + */ + var _removeElements = function(elements) { + for (var i in elements) + _removeElement(elements[i]); + }; + /** + * helper method to remove an item from a list. + */ + var _removeFromList = function(map, key, value) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete( l[i] ); + l.splice( i, 1 ); + return true; + } + } + return false; + }; + /** + * Sets whether or not the given element(s) should be draggable, regardless of what a particular + * plumb command may request. + * + * @param element May be a string, a jQuery elements, or a list of strings/jquery elements. + * @param draggable Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + var _helper = function(el, id) { + draggableStates[id] = draggable; + if (_isDragSupported(el)) { + // TODO: not library agnostic yet. + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + //el.draggable("option", "disabled", !draggable); + } + }; + + return _elementProxy(element, _helper); + }; + /** + * private method to do the business of hiding/showing. + * @param el either Id of the element in question or a jquery object for the element. + * @param state String specifying a value for the css 'display' property ('block' or 'none'). + */ + var _setVisible = function(el, state) { + var elId = _getAttribute(el, "id"); + var f = function(jpc) { + jpc.canvas.style.display = state; + }; + _operation(elId, f); + }; + /** + * toggles the draggable state of the given element(s). + * @param el either an id, or a jquery object, or a list of ids/jquery objects. + */ + var _toggleDraggable = function(el) { + var fn = function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + //el.draggable("option", "disabled", !state); + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }; + return _elementProxy(el, fn); + }; + /** + * private method to do the business of toggling hiding/showing. + * @param elId Id of the element in question + */ + var _toggleVisible = function(elId) { + var f = function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }; + + _operation(elId, f); + + //todo this should call _elementProxy, and pass in the _operation(elId, f) call as a function. cos _toggleDraggable does that. + }; + /** + * updates the offset and size for a given element, and stores the values. + * if 'offset' is not null we use that (it would have been passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we use the offset, outerWidth and outerHeight methods to get + * the current values. + */ + var _updateOffset = function(elId, offset, recalc) { + if (recalc || offset == null) { // if forced repaint or no offset available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + sizes[elId] = _getSize(elId); + offsets[elId] = _getOffset(elId); + } else { + offsets[elId] = offset; + } + }; + + /** + * wraps one function with another, creating a placeholder for the wrapped function + * if it was null. this is used to wrap the various drag/drop event functions - to allow + * jsPlumb to be notified of important lifecycle events without imposing itself on the user's + * drap/drop functionality. + * TODO: determine whether or not we should try/catch the plumb function, so that the cascade function is always executed. + */ + var _wrap = function(cascadeFunction, plumbFunction) { + cascadeFunction = cascadeFunction || function() { }; + return function() { + plumbFunction.apply(this, arguments); + cascadeFunction.apply(this, arguments); + }; + } + + /* + Class: Anchor + Models a position relative to the origin of some element that an Endpoint can be located. + */ + /* + Function: Anchor + + Constructor for the Anchor class + + Parameters: + params - Anchor parameters. This should contain three values, and may optionally have an 'offsets' argument: + + - x : the x location of the anchor as a fraction of the total width. + - y : the y location of the anchor as a fraction of the total height. + - orientation : an [x,y] array indicating the general direction a connection from the anchor should go in. for more info on this, see the documentation, or the docs in jquery-jsPlumb-defaults-XXX.js for the default Anchors. + - offsets : an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. may be null. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; this.y = params.y || 0; + var orientation = params.orientation || [0,0]; + this.offsets = params.offsets || [0,0]; + this.compute = function(xy, wh, txy, twh) { + return [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + } + this.getOrientation = function() { return orientation; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from its position relative to the anchor it is floating relative to. + * + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for purposes of calculating the orientation. + var ref = params.reference; + // these are used to store the current relative position of our anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating anchor is hovering over another anchor. + var orientation = null; + + this.compute = function(xy, wh, txy, twh) { + // set these for the getOrientation method to use. + xDir = 0;//xy[0] < txy[0] ? -1 : xy[0] == txy[0] ? 0 : 1; + yDir = 0;//xy[1] < txy[1] ? -1 : xy[1] == txy[1] ? 0 : 1; + return [xy[0], xy[1]]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other anchor: if it declares zero for some direction, we declare zero too. + // this might not be the most awesome. perhaps we can come up with a better way. it's just so that the line we draw looks + // like it makes sense. maybe this wont make sense. + return [Math.abs(o[0]) * xDir * -1, Math.abs(o[1]) * yDir * -1]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering over another anchor; + * we want to assume that anchor's orientation for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no longer hovering + * over another anchor; we should resume calculating orientation as we normally do. + */ + this.out = function() { + orientation = null; + }; + }; + + // ************** connection + // **************************************** + /** + * allowed params: + * source: source element (string or a jQuery element) (required) + * target: target element (string or a jQuery element) (required) + * + * anchors: optional array of anchor placements. defaults to BottomCenter for source + * and TopCenter for target. + */ + var Connection = function(params) { + + // ************** get the source and target and register the connection. ******************* + var self = this; + // get source and target as jQuery objects + this.source = _getElementObject(params.source); + this.target = _getElementObject(params.target); + this.sourceId = _getAttribute(this.source, "id"); + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + + // init endpoints + this.endpoints = []; + this.endpointStyles = []; + var prepareEndpoint = function(existing, index, params) { + if (existing) self.endpoints[index] = existing; + else { + if(!params.endpoints) params.endpoints = [null,null]; + var ep = params.endpoints[index] || params.endpoint || jsPlumb.Defaults.Endpoints[index] || jsPlumb.Defaults.Endpoint|| new jsPlumb.Endpoints.Dot(); + if (!params.endpointStyles) params.endpointStyles = [null,null]; + var es = params.endpointStyles[index] || params.endpointStyle || jsPlumb.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyle; + var a = params.anchors ? params.anchors[index] : jsPlumb.Defaults.Anchors[index] || jsPlumb.Anchors.BottomCenter; + self.endpoints[index] = new Endpoint({style:es, endpoint:ep, connections:[self], anchor:a }); + } + }; + + prepareEndpoint(params.sourceEndpoint, 0, params); + prepareEndpoint(params.targetEndpoint, 1, params); + + this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + this.paintStyle = this.endpoints[0].connectionStyle || this.endpoints[1].connectionStyle || params.paintStyle || jsPlumb.Defaults.PaintStyle; + + _updateOffset(this.sourceId); + _updateOffset(this.targetId); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var anchorLoc = this.endpoints[0].anchor.compute([myOffset.left, myOffset.top], myWH); + this.endpoints[0].paint(anchorLoc); + myOffset = offsets[this.targetId]; myWH = sizes[this.targetId]; + anchorLoc = this.endpoints[1].anchor.compute([myOffset.left, myOffset.top], myWH); + this.endpoints[1].paint(anchorLoc); + + // *************** create canvas on which the connection will be drawn ************ + var canvas = _newCanvas(jsPlumb.connectorClass); + this.canvas = canvas; + + /** + * paints the connection. + * @param elId Id of the element that is in motion + * @param ui current library's event system ui object (present if we came from a drag to get here) + * @param recalc whether or not to recalculate element sizes. this is true if a repaint caused this to be painted. + */ + this.paint = function(elId, ui, recalc) { + + if (log) log.debug("Painting Connection; element in motion is " + elId + "; ui is [" + ui + "]; recalc is [" + recalc + "]"); + + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = !(elId == this.sourceId); + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + if (this.canvas.getContext) { + + _updateOffset(elId, ui, recalc); + if (recalc) _updateOffset(tId); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var myOffset = offsets[elId]; + var otherOffset = offsets[tId]; + var myWH = sizes[elId]; + var otherWH = sizes[tId]; + + var ctx = canvas.getContext('2d'); + var sAnchorP = this.endpoints[sIdx].anchor.compute([myOffset.left, myOffset.top], myWH, [otherOffset.left, otherOffset.top], otherWH); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.compute([otherOffset.left, otherOffset.top], otherWH, [myOffset.left, myOffset.top], myWH); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, this.paintStyle.lineWidth); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + jsPlumb.extend(ctx, this.paintStyle); + + if (this.paintStyle.gradient && !ie) { + var g = swap ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + for (var i = 0; i < this.paintStyle.gradient.stops.length; i++) + g.addColorStop(this.paintStyle.gradient.stops[i][0],this.paintStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + + this.connector.paint(dim, ctx); + } + }; + + this.repaint = function() { + this.paint(this.sourceId, null, true); + }; + + _initDraggableIfNecessary(self.source, self.sourceId, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, self.targetId, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + }; + + /* + Class: Endpoint + + Models an endpoint. Can have one to N connections emanating from it (although how to handle that in the UI is + a very good question). also has a Canvas and paint style. + + */ + + /* + Function: Endpoint + + This is the Endpoint class constructor. + + Parameters: + + anchor - anchor for the endpoint, of type jsPlumb.Anchor. may be null. + endpoint - endpoint object, of type jsPlumb.Endpoint. may be null. + style - endpoint style, a js object. may be null. + source - element the endpoint is attached to, of type jquery object. Required. + canvas - canvas element to use. may be, and most often is, null. + connections - optional list of connections to configure the endpoint with. + isSource - boolean. indicates the endpoint can act as a source of new connections. optional. + dragOptions - if isSource is set to true, you can supply arguments for the jquery draggable method. optional. + connectionStyle - if isSource is set to true, this is the paint style for connections from this endpoint. optional. + connector - optional connector type to use. + isTarget - boolean. indicates the endpoint can act as a target of new connections. optional. + dropOptions - if isTarget is set to true, you can supply arguments for the jquery droppable method. optional. + reattach - optional boolean that determines whether or not the connections reattach after they + have been dragged off an endpoint and left floating. defaults to false: connections + dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + params = params || {}; + // make a copy. then we can use the wrapper function. + jsPlumb.extend({}, params); + var self = this; + self.anchor = params.anchor || jsPlumb.Anchors.TopCenter; + var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot(); + var _style = params.style || jsPlumb.Defaults.EndpointStyle; + this.connectionStyle = params.connectionStyle; + this.connector = params.connector; + var _element = params.source; + var _elementId = _getAttribute(_element, "id"); + var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of. + this.canvas = params.canvas || _newCanvas(jsPlumb.endpointClass); + this.connections = params.connections || []; + var _reattach = params.reattach || false; + var floatingEndpoint = null; + this.addConnection = function(connection) { + self.connections.push(connection); + }; + this.removeConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) + self.connections.splice(idx, 1); + }; + /** + * returns whether or not this endpoint is connected to the given endpoint. + * @param endpoint Endpoint to test. + * @since 1.1.1 + * + * todo: needs testing. will this work if the endpoint passed in is the source? + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + this.isFloating = function() { return floatingEndpoint != null; }; + /** + * first pass at default ConnectorSelector: returns the first connection, if we have any. + * modified a little, 5/10/2010: now it only returns a connector if we have not got equal to or more than _maxConnector connectors + * attached. otherwise it is assumed a new connector is ok. but note with this setup we can't select any other connection than the first + * one. what if this could return a list? that implies everything should work with a list - dragging etc. could be nasty. could also + * be cool. + */ + var connectorSelector = function() { + return self.connections.length == 0 || self.connections.length < _maxConnections ? null : self.connections[0]; + }; + + this.isFull = function() { return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); }; + + /** + * paints the Endpoint, recalculating offset and anchor positions if necessary. + */ + this.paint = function(anchorPoint, connectorPaintStyle, canvas) { + + if (log) log.debug("Painting Endpoint with elementId [" + _elementId + "]; anchorPoint is [" + anchorPoint + "]"); + + if (anchorPoint == null) { + // do we always want to force a repaint here? i dont think so! + var xy = offsets[_elementId]; + var wh = sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset(_elementId); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + anchorPoint = self.anchor.compute([xy.left, xy.top], wh); + } + _endpoint.paint(anchorPoint, self.anchor.getOrientation(), canvas || self.canvas, _style, connectorPaintStyle || _style); + }; + + + // is this a connection source? we make it draggable and have the drag listener + // maintain a connection with a floating endpoint. + if (params.isSource && _isDragSupported(_element)) { + + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + + // first the question is, how many connections are on this endpoint? if it's only one, then excellent. otherwise we will either need a way + // to select one connection from the list, or drag them all. if we had a pluggable 'ConnectorSelector' interface we could probably + // provide a way for people to implement their own UI components to do the connector selection. the question in that particular case would be how much + // information the interface needs from jsPlumb at execution time. if, however, we leave out the connector selection, and drag them all, + // that wouldn't be too hard to organise. perhaps that behaviour would be on a switch for the endpoint, or perhaps the ConnectorSelector + // interface returns a List, with the default implementation just returning everything. i think i like that. + // + // let's say for now that there is just one endpoint, cos we need to get this going before we can consider a list of them anyway. + // the major difference between that case and the case of a new endpoint is actually quite small - it's a question of where the + // Connection comes from. for a new one, we create a new one. obviously. otherwise we just get the jpc from the Endpoint + // (remember we're only assuming one connection right now). so all of the UI stuff we do to create the floating endpoint etc + // will still be valid, but when we stop dragging, we'll have to do something different. if we stop with a valid drop i think it will + // be the same process. but if we stop with an invalid drop we have to reset the Connection to how it was when we got it. + var start = function() { + n = document.createElement("div"); + _appendCanvas(n); + // create and assign an id, and initialize the offset. + var id = "" + new String(new Date().getTime()); + _setAttribute(_getElementObject(n), "id", id); + _updateOffset(id); + // store the id of the dragging div and the source element. the drop function + // will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor({reference:self.anchor}); + floatingEndpoint = new Endpoint({ + style:_style, + endpoint:_endpoint, + anchor:floatingAnchor, + source:n + }); + + jpc = connectorSelector(); + if (jpc == null) { + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = new Connection({ + sourceEndpoint:self, + targetEndpoint:floatingEndpoint, + source:_getElementObject(_element), + target:_getElementObject(n), + anchors:[self.anchor, floatingAnchor], + paintStyle : params.connectionStyle, // this can be null. Connection will use the default. + connector: params.connector + }); + // todo ...unregister on stop + self.addConnection(jpc); + } else { + existingJpc = true; + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; + // probably we should remove the connection? and add it back if the user + // does not drop it somewhere proper. + self.removeConnection(jpc); + if (anchorIdx == 0){ + existingJpcParams = [jpc.source, jpc.sourceId]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + }else { + existingJpcParams = [jpc.target, jpc.targetId]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // todo unregister on stop + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + }; + + var dragOptions = params.dragOptions || { }; + + var defaultOpts = jsPlumb.extend({}, jsPlumb.CurrentLibrary.defaultDragOptions); + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + var startEvent = jsPlumb.CurrentLibrary.dragEvents['start']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements([floatingEndpoint.canvas, n]); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + if (_reattach) { + jpc.floatingAnchorIndex = null; + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } + else { + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeElement(jpc.canvas); + } + } else { + _removeElement(jpc.canvas); + self.removeConnection(jpc); + } + } + jpc = null; + delete floatingEndpoint; + } + ); + + var i = _getElementObject(self.canvas); + _initDraggable(i, dragOptions); + }; + + // connector target + if (params.isTarget && _isDropSupported(_element)) { + var dropOptions = params.dropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend({}, dropOptions); + var originalAnchor = null; + dropOptions.drop = _wrap(dropOptions.drop, function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable), "dragId"); + var elId = _getAttribute(_getElementObject(draggable),"elId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // remove this jpc from the current endpoint + jpc.endpoints[idx].removeConnection(jpc); + if (jpc.suspendedEndpoint) + jpc.suspendedEndpoint.removeConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + _initDraggableIfNecessary(_element, _elementId, params.draggable, {}); + jsPlumb.repaint(elId); + + delete floatingConnections[id]; + }); + // what to do when something is dropped. + // 1. find the jpc that is being dragged. the target endpoint of the jpc will be the + // one that is being dragged. + // 2. arrange for the floating endpoint to be replaced with this endpoint; make sure + // everything gets registered ok etc. + // 3. arrange for the floating endpoint to be deleted. + // 4. make sure that the stop method of the drag does not cause the jpc to be cleaned up. we want to keep it now. + + // other considerations: when in the hover mode, we should switch the floating endpoint's + // orientation to be the same as the drop target. this will cause the connector to snap + // into the shape it will take if the user drops at that point. + + dropOptions.over = _wrap(dropOptions.over, function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + }); + + dropOptions.out = _wrap(dropOptions.out, function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + }); + + _initDroppable(_getElementObject(self.canvas), dropOptions); + } + + return self; + }; + + /* + Class: jsPlumb + + This is the main entry point to jsPlumb. There are a bunch of static methods you can + use to connect/disconnect etc. Some methods are also added to jQuery's "$.fn" object itself, but + in general jsPlumb is moving away from that model, preferring instead the static class + approach. One of the reasons for this is that with no methods added to the jQuery $.fn object, + it will be easier to provide support for other libraries such as MooTools. + */ + var jsPlumb = window.jsPlumb = { + + /* + Property: Defaults + + These are the default settings for jsPlumb, that is what will be used if you do not supply specific pieces of information + to the various API calls. A convenient way to implement your own look and feel can be to override these defaults by including a script + somewhere after the jsPlumb include, but before you make any calls to jsPlumb, for instance in this example we set the PaintStyle to be + a blue line of 27 pixels: + + > jsPlumb.Defaults.PaintStyle = { lineWidth:27, strokeStyle:'blue' } + + */ + Defaults : { + Anchors : [ null, null ], + Connector : null, + DragOptions: { }, + DropOptions: { }, + Endpoint : null, + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : null }, + EndpointStyles : [ null, null ], + MaxConnections : null, + PaintStyle : { lineWidth : 10, strokeStyle : 'red' } + }, + + /* + Property: connectorClass + + The CSS class to set on Connection canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + connectorClass : '_jsPlumb_connector', + + /* + Property: endpointClass + + The CSS class to set on Endpoint canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + endpointClass : '_jsPlumb_endpoint', + + /* + Property: Anchors + + Default jsPlumb Anchors. These are supplied in the file jquery.jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form jquery.jsPlumb-all-x.x.x.js. You can provide your own Anchors by supplying them in a script that is loaded after jsPlumb, for instance: + + > jsPlumb.Anchors.MyAnchor = { ....anchor code here. see the documentation. } + */ + Anchors : {}, + + /* + Property: Connectors + + Default jsPlumb Connectors. These are supplied in the file jquery.jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form jquery.jsPlumb-all-x.x.x.js. You can provide your own Connectors by supplying them in a script that is loaded after jsPlumb, for instance: + + > jsPlumb.Connectors.MyConnector = { ....connector code here. see the documentation. } + */ + Connectors : {}, + + /* + Property: Endpoints + + Default jsPlumb Endpoints. These are supplied in the file jquery.jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form jquery.jsPlumb-all-x.x.x.js. You can provide your own Endpoints by supplying them in a script that is loaded after jsPlumb, for instance: + + > jsPlumb.Endpoints.MyEndpoint = { ....endpoint code here. see the documentation. } + */ + Endpoints : {}, + + /* + Function: addEndpoint + + Adds an Endpoint to a given element. + + Parameters: + target - Element to add the endpoint to. either an element id, or a jQuery object representing some element. + params - Object containing Endpoint options (more info required) + + Returns: + + The newly created Endpoint. + + See Also: + + + */ + addEndpoint : function(target, params) { + params = jsPlumb.extend({}, params); + var el = _getElementObject(target); + var id = _getAttribute(target,"id"); + params.source = el; + _updateOffset(id); + var e = new Endpoint(params); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id]; + var myWH = sizes[id]; + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH); + e.paint(anchorLoc); + return e; + }, + + /* + Function: addEndpoint + + Adds a list of Endpoints to a given element. + + Parameters: + + target - element to add the endpoint to. either an element id, or a jQuery object representing some element. + endpoints - List of objects containing Endpoint options. one Endpoint is created for each entry in this list. + + Returns: + + List of newly created Endpoints, one for each entry in the 'endpoints' argument. + + See Also: + + + */ + addEndpoints : function(target, endpoints) { + var results = []; + for (var i = 0; i < endpoints.length; i++) { + results.push(jsPlumb.addEndpoint(target, endpoints[i])); + } + return results; + }, + + /* + Function: animate + + Wrapper around standard jQuery animate function; injects a call to jsPlumb in the 'step' function (creating it if necessary). + This only supports the two-arg version of the animate call in jQuery - the one that takes an 'options' object as the second arg. + + Parameters: + + el - Element to animate. Either an id, or a jQuery object representing the element. + properties - The 'properties' argument you want passed to the standard jQuery animate call. + options - The 'options' argument you want passed to the standard jQuery animate call. + + Returns: + + void + */ + animate : function(el, properties, options) { + var ele = _getElementObject(el); + var id = _getAttribute(el,"id"); + options = options || {}; + options.step = _wrap(options.step, function() { jsPlumb.repaint(id); }); + _animate(ele, properties, options); + }, + + /* + Function: connect + + Establishes a connection between two elements. + + Parameters: + + params - Object containing setup for the connection. see documentation. + + Returns: + + The newly created Connection. + + */ + connect : function(params) { + if (params.sourceEndpoint && params.sourceEndpoint.isFull()) { + _log("could not add connection; source endpoint is full"); + return; + } + + if (params.targetEndpoint && params.targetEndpoint.isFull()) { + _log("could not add connection; target endpoint is full"); + return; + } + + var jpc = new Connection(params); + + // register endpoints for the element. todo: is the test below sufficient? or should we test if the endpoint is already in the list, + // and add it only then? perhaps _addToList could be overloaded with a a 'test for existence first' parameter? + //_addToList(endpointsByElement, jpc.sourceId, jpc.endpoints[0]); + //_addToList(endpointsByElement, jpc.targetId, jpc.endpoints[1]); + + if (!params.sourceEndpoint) _addToList(endpointsByElement, jpc.sourceId, jpc.endpoints[0]); + if (!params.targetEndpoint) _addToList(endpointsByElement, jpc.targetId, jpc.endpoints[1]); + + jpc.endpoints[0].addConnection(jpc); + jpc.endpoints[1].addConnection(jpc); + + // force a paint + _draw(jpc.source); + + return jpc; + + }, + + /** + * not implemented yet. params object will have sourceEndpoint and targetEndpoint members; these will be Endpoints. + connectEndpoints : function(params) { + var jpc = Connection(params); + + },*/ + + /* + Function: detach + + Removes a connection. + + Parameters: + + sourceId - Id of the first element in the connection. A String. + targetId - iI of the second element in the connection. A String. + + Returns: + + true if successful, false if not. + */ + detach : function(sourceId, targetId) { + var f = function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + return true; + } + }; + + // todo: how to cleanup the actual storage? a third arg to _operation? + _operation(sourceId, f); + }, + + /* + Function: detachAll + + Removes all an element's connections. + + Parameters: + + el - either the id of the element, or a jQuery object for the element. + + Returns: + + void + */ + detachAll : function(el) { + var id = _getAttribute(el, "id"); + var f = function(jpc) { + // todo replace with _cleanupConnection call here. + _removeElement(jpc.canvas); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + }; + _operation(id, f); + //delete endpointsByElement[id]; ?? + }, + + /* + Function: detachEverything + + Remove all Connections from all elements, but leaves Endpoints in place. + + Returns: + + void + + See Also: + + + */ + detachEverything : function() { + var f = function(jpc) { + _removeElement(jpc.canvas); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + }; + + _operationOnAll(f); + + /*delete endpointsByElement; //?? + endpointsByElement = {};*/ //?? + }, + + extend : function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }, + + /* + Function: hide + + Sets an element's connections to be hidden. + + Parameters: + + el - either the id of the element, or a jQuery object for the element. + + Returns: + + void + */ + hide : function(el) { + _setVisible(el, "none"); + }, + + /* + Function: makeAnchor + + Creates an anchor with the given params. + + Parameters: + + x - the x location of the anchor as a fraction of the total width. + y - the y location of the anchor as a fraction of the total height. + xOrientation - value indicating the general direction a connection from the anchor should go in, in the x direction. + yOrientation - value indicating the general direction a connection from the anchor should go in, in the y direction. + xOffset - a fixed offset that should be applied in the x direction that should be applied after the x position has been figured out. optional. defaults to 0. + yOffset - a fixed offset that should be applied in the y direction that should be applied after the y position has been figured out. optional. defaults to 0. + + Returns: + + The newly created Anchor. + */ + makeAnchor : function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards compatibility if we are given only one value we assume it's a call in the old form. + var params = {}; + if (arguments.length == 1) jsPlumb.extend(params, x); + else { + params = {x:x, y:y}; + if (arguments.length >= 4) { + params.orientation = [arguments[2], arguments[3]]; + } + if (arguments.length == 6) params.offsets = [arguments[4], arguments[5]]; + } + return new Anchor(params); + }, + + + /* + Function: repaint + + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + + el - either the id of the element or a jQuery object representing the element. + + Returns: + + void + + See Also: + + + */ + repaint : function(el) { + + var _processElement = function(el) { + var ele = _getElementObject(el); + _draw(ele); + }; + + // TODO: support a jQuery result object too! + + // support both lists... + if (typeof el =='object') { + for (var i = 0; i < el.length; i++) + _processElement(el[i]); + } // ...and single strings. + else _processElement(el); + }, + + /* + Function: repaintEverything + + Repaints all connections. + + Returns: + + void + + See Also: + + + */ + repaintEverything : function() { + for (var elId in endpointsByElement) { + _draw(_getElementObject(elId)); + } + }, + + /* + Function: removeAllEndpoints + + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + + el - either an element id, or a jQuery object for an element. + + Returns: + + void + + See Also: + + + */ + removeAllEndpoints : function(el) { + var elId = _getAttribute(el, "id"); + // first remove all Connections. + jsPlumb.detachAll(elId); + var ebe = endpointsByElement[elId]; + for (var i in ebe) { + _removeElement(ebe[i].canvas); + } + endpointsByElement[elId] = []; + }, + + /* + Function: removeEndpoint + + Removes the given Endpoint from the given element. + + Parameters: + + el - either an element id, or a jQuery object for an element. + endpoint - Endpoint to remove. this is an Endpoint object, such as would have been returned from a call to addEndpoint. + + Returns: + + void + + See Also: + + + */ + removeEndpoint : function(el, endpoint) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + if (ebe) { + if(_removeFromList(endpointsByElement, elId, endpoint)) + _removeElement(endpoint.canvas); + } + }, + + /* + Function: setAutomaticRepaint + + Sets/unsets automatic repaint on window resize. + + Parameters: + + value - whether or not to automatically repaint when the window is resized. + + Returns: + + void + */ + setAutomaticRepaint : function(value) { + automaticRepaint = value; + }, + + /* + Function: setDefaultNewCanvasSize + + Sets the default size jsPlumb will use for a new canvas (we create a square canvas so one value is all that is required). + This is a hack for IE, because ExplorerCanvas seems to need for a canvas to be larger than what you are going to draw on + it at initialisation time. The default value of this is 1200 pixels, which is quite large, but if for some reason you're + drawing connectors that are bigger, you should adjust this value appropriately. + + Parameters: + + size - The default size to use. jsPlumb will use a square canvas so you need only supply one value. + + Returns: + + void + */ + setDefaultNewCanvasSize : function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }, + + /* + Function: setDraggable + + Sets whether or not a given element is draggable, regardless of what any plumb command may request. + + Parameters: + + el - either the id for the element, or a jQuery object representing the element. + + Returns: + + void + */ + setDraggable: _setDraggable, + + /* + Function: setDraggableByDefault + + Sets whether or not elements are draggable by default. Default for this is true. + + Parameters: + draggable - value to set + + Returns: + + void + */ + setDraggableByDefault: function(draggable) { + _draggableByDefault = draggable; + }, + + setDebugLog: function(debugLog) { + log = debugLog; + }, + + /* + Function: setRepaintFunction + + Sets the function to fire when the window size has changed and a repaint was fired. + + Parameters: + f - Function to execute. + + Returns: + void + */ + setRepaintFunction : function(f) { + repaintFunction = f; + }, + + /* + Function: show + + Sets an element's connections to be visible. + + Parameters: + el - either the id of the element, or a jQuery object for the element. + + Returns: + void + */ + show : function(el) { + _setVisible(el, "block"); + }, + + /* + Function: sizeCanvas + + Helper to size a canvas. You would typically use this when writing your own Connector or Endpoint implementation. + + Parameters: + x - [int] x position for the Canvas origin + y - [int] y position for the Canvas origin + w - [int] width of the canvas + h - [int] height of the canvas + + Returns: + void + */ + sizeCanvas : function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; canvas.height = h; + canvas.style.width = w + "px"; canvas.width = w; + canvas.style.left = x + "px"; canvas.style.top = y + "px"; + } + }, + + /** + * gets some test hooks. nothing writable. + */ + getTestHarness : function() { + return { + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + } + }; + }, + + /** + * Toggles visibility of an element's connections. kept for backwards compatibility + */ + toggle : _toggleVisible, + + /* + Function: toggleVisible + + Toggles visibility of an element's connections. + + Parameters: + el - either the element's id, or a jQuery object representing the element. + + Returns: + void, but should be updated to return the current state + */ + //TODO: update this method to return the current state. + toggleVisible : _toggleVisible, + + /* + Function: toggleDraggable + + Toggles draggability (sic) of an element's connections. + + Parameters: + el - either the element's id, or a jQuery object representing the element. + + Returns: + The current draggable state. + */ + toggleDraggable : _toggleDraggable, + + /* + Function: unload + + Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element + + Returns: + void + */ + unload : function() { + delete endpointsByElement; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + } + }; + +})(); + +// jQuery plugin code +(function($){ + /** + * plumbs the results of the selector to some target, using the given options if supplied, + * or the defaults otherwise. this is DEPRECATED. just use jsPlumb.connect(..) instead. + * @deprecated + */ + $.fn.plumb = function(options) { + var options = $.extend({}, options); + + return this.each(function() + { + var params = $.extend({source:$(this)}, options); + jsPlumb.connect(params); + }); + }; + + /** + * detaches the results of the selector from the given target or list of targets - 'target' + * may be a String or a List. + */ + $.fn.detach = function(target) { + return this.each(function() + { + var id = $(this).attr("id"); + if (typeof target == 'string') target = [target]; + for (var i = 0; i < target.length; i++) + jsPlumb.detach(id, target[i]); + }); + }; + + /** + * detaches the results from the selector from all connections. + */ + $.fn.detachAll = function() { + return this.each(function() + { + var id = $(this).attr("id"); + jsPlumb.detachAll(id); + }); + }; + + /** + * adds an endpoint to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoint = function(options) { + var addedEndpoints = []; + this.each(function() + { + addedEndpoints.push(jsPlumb.addEndpoint($(this).attr("id"), options)); + }); + return addedEndpoints[0]; + }; + + /** + * adds a list of endpoints to the elements resulting from the selector. options may be null, + * in which case jsPlumb will use the default options. see documentation. + */ + $.fn.addEndpoints = function(endpoints) { + var addedEndpoints = []; + this.each(function() + { + var e = jsPlumb.addEndpoints($(this).attr("id"), endpoints); + for (var i = 0; i < e.length; i++) addedEndpoints.push(e[i]); + }); + return addedEndpoints; + }; + + /** + * remove the endpoint, if it exists, deleting its UI elements etc. + */ + $.fn.removeEndpoint = function(endpoint) { + this.each(function() + { + jsPlumb.removeEndpoint($(this).attr("id"), endpoint); + }); + }; + +})(jQuery); + + +/* the library agnostic functions, such as find offset, get id, get attribute, extend etc. currently +hardcoded to jQuery here; will be extracted to separate impls for different libraries. + +* much more work needs to be done on this. some things that spring to mind: animation, drag, drop. +* +* you should _never_ call these methods directly. jsPlumb uses them when it needs them. +* +*/ +(function() { + + jsPlumb.CurrentLibrary = { + + /** + * mapping of drag events for jQuery + */ + dragEvents : { + 'start':'start', 'stop':'stop', 'drag':'drag' + }, + + /** + * default drag options for jQuery. + */ + defaultDragOptions : { opacity:0.5, revert:true, helper:'clone' }, + + /** + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $.extend(o1, o2); + }, + + /** + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + getElementObject : function(el) { + return typeof(el)=='string' ? $("#" + el) : $(el); + }, + + /** + * gets the offset for the element object. this should return a js object like this: + * + * { left:xxx, top: xxx } + */ + getOffset : function(el) { + return el.offset(); + }, + + /** + * gets the size for the element object, in an array : [ width, height ]. + */ + getSize : function(el) { + return [el.outerWidth(), el.outerHeight()]; + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.attr(attName); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.attr(attName, attValue); + }, + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + /** + * initialises the given element to be draggable. + */ + initDraggable : function(el, options) { + if (el.length > 1) alert('poo'); + el.draggable(options); + }, + + /** + * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDragSupported : function(el, options) { + return el.draggable; + }, + + /** + * sets the draggable state for the given element + */ + setDraggable : function(el, draggable) { + el.draggable("option", "disabled", !draggable); + }, + + /** + * initialises the given element to be droppable. + */ + initDroppable : function(el, options) { + el.droppable(options); + }, + + /** + * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element. + */ + isDropSupported : function(el, options) { + return el.droppable; + }, + + /** + * animates the given element. + */ + animate : function(el, properties, options) { + el.animate(properties, options); + }, + + /** + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + * + * different libraries have different signatures for their event callbacks. + * see getDragObject as well + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[1]; + return ui.absolutePosition || ui.offset; + }, + + /** + * takes the args passed to an event function and returns you an object representing that which is being dragged. + */ + getDragObject : function(eventArgs) { + return eventArgs[1].draggable; + } + }; +})(); + +/** +* jsPlumb-defaults-1.1.1-RC1 +* +* This script contains the default Anchors, Endpoints and Connectors for jsPlumb. It should be used with jsPlumb 1.1.0 and above; +* prior to version 1.1.0 of jsPlumb the defaults were included inside the main script. +* +* Version 1.1.1 of this script adds the Triangle Endpoint, written by __________ and featured on this demo: +* +* http://www.mintdesign.ru/blog/javascript-jsplumb-making-graph +* http://www.mintdesign.ru/site/samples/jsplumb/jsPlumb-graph-sample.htm +* +* NOTE: for production usage you should use jsPlumb-all-1.1.1-min.js, which contains the main jsPlumb script and this script together, +* in a minified file. +*/ + +(function() { + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * getOrientation : function() { return [ox, oy]; } + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + jsPlumb.Anchors.TopCenter = jsPlumb.makeAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors.BottomCenter = jsPlumb.makeAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors.LeftMiddle = jsPlumb.makeAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors.RightMiddle = jsPlumb.makeAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors.Center = jsPlumb.makeAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors.TopRight = jsPlumb.makeAnchor(1, 0, 0,-1); + jsPlumb.Anchors.BottomRight = jsPlumb.makeAnchor(1, 1, 0, 1); + jsPlumb.Anchors.TopLeft = jsPlumb.makeAnchor(0, 0, 0, -1); + jsPlumb.Anchors.BottomLeft = jsPlumb.makeAnchor(0, 1, 0, 1); + + + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (w < 2 * lineWidth) { + w = 2 * lineWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - lineWidth; + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < 2 * lineWidth) { + // minimum size is 2 * line Width + h = 2 * lineWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - lineWidth; + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + var sx = sourcePos[0] < targetPos[0] ? w-xo : xo; + var sy = sourcePos[1] < targetPos[1] ? h-yo : yo; + var tx = sourcePos[0] < targetPos[0] ? xo : w-xo; + var ty = sourcePos[1] < targetPos[1] ? yo : h-yo; + var retVal = [ x, y, w, h, sx, sy, tx, ty ]; + + return retVal; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + }; + + /** + * This Connector draws a Bezier curve with two control points. + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + jsPlumb.Connectors.Bezier = function(curviness) { + + var self = this; + this.majorAnchor = curviness || 150; + this.minorAnchor = 10; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) + { + lineWidth = lineWidth || 0; + var w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth, h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + var canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2), canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + var sx = sourcePos[0] < targetPos[0] ? w - (lineWidth/2): (lineWidth/2), sy = sourcePos[1] < targetPos[1] ? h-(lineWidth/2) : (lineWidth/2); + var tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : w-(lineWidth/2), ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : h-(lineWidth/2); + var CP = self._findControlPoint([sx,sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + var CP2 = self._findControlPoint([tx,ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(sx,tx); var minx2 = Math.min(CP[0], CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(sx,tx); var maxx2 = Math.max(CP[0], CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > w) w = maxx; + if (minx < 0) { + canvasX += minx; var ox = Math.abs(minx); + w += ox; CP[0] += ox; sx += ox; tx +=ox; CP2[0] += ox; + } + + var miny1 = Math.min(sy,ty); var miny2 = Math.min(CP[1], CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(sy,ty); var maxy2 = Math.max(CP[1], CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > h) h = maxy; + if (miny < 0) { + canvasY += miny; var oy = Math.abs(miny); + h += oy; CP[1] += oy; sy += oy; ty +=oy; CP2[1] += oy; + } + + // return [ canvasx, canvasy, canvasWidth, canvasHeight, + // sourceX, sourceY, targetX, targetY, + // controlPoint1_X, controlPoint1_Y, controlPoint2_X, controlPoint2_Y + return [canvasX, canvasY, w, h, sx, sy, tx, ty, CP[0], CP[1], CP2[0], CP2[1] ]; + }; + + this.paint = function(d, ctx) { + + /*var img = new Image(); + img.src = "../img/pattern.jpg"; + ctx.fillStyle = ctx.createPattern(img, 'repeat-y');*/ + + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + //ctx.fill(); + } + }; + + + /** + * Types of endpoint UIs. we supply three - a circle of default radius 10px, a rectangle of + * default size 20x20, and an image (with no default). you can supply others of these if you want to - see the documentation + * for a howto. + */ + + /** + * a round endpoint, with default radius 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + } + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * A Rectangular endpoint, with default size 20x20. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + //todo: the fillStyle needs some thought. we want to support a few options: + // 1. nothing supplied; use the stroke color or the default if no stroke color. + // 2. a fill color supplied - use it + // 3. a gradient supplied - use it + // 4. setting the endpoint to the same color as the bg of the element it is attached to. + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + jsPlumb.Endpoints.Triangle = function(params) { + + params = params || { width:15, height:15 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - width/2; + var y = anchorPoint[1] - height/2; + + jsPlumb.sizeCanvas(canvas, x, y, width, height); + + var ctx = canvas.getContext('2d'); + var + offsetX = 0, + offsetY = 0, + angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = endpointStyle.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + ctx.fill(); + + }; + }; + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' property in the params object.. + */ + jsPlumb.Endpoints.Image = function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + }; +})(); \ No newline at end of file diff --git a/archive/jquery.jsPlumb-defaults-1.1.0-RC1.js b/archive/jquery.jsPlumb-defaults-1.1.0-RC1.js new file mode 100644 index 000000000..8deab52e8 --- /dev/null +++ b/archive/jquery.jsPlumb-defaults-1.1.0-RC1.js @@ -0,0 +1,337 @@ +(function() { + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * getOrientation : function() { return [ox, oy]; } + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + jsPlumb.Anchors.TopCenter = jsPlumb.makeAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors.BottomCenter = jsPlumb.makeAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors.LeftMiddle = jsPlumb.makeAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors.RightMiddle = jsPlumb.makeAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors.Center = jsPlumb.makeAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors.TopRight = jsPlumb.makeAnchor(1, 0, 0,-1); + jsPlumb.Anchors.BottomRight = jsPlumb.makeAnchor(1, 1, 0, 1); + jsPlumb.Anchors.TopLeft = jsPlumb.makeAnchor(0, 0, 0, -1); + jsPlumb.Anchors.BottomLeft = jsPlumb.makeAnchor(0, 1, 0, 1); + + + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (w < 2 * lineWidth) { + w = 2 * lineWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - lineWidth; + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < 2 * lineWidth) { + // minimum size is 2 * line Width + h = 2 * lineWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - lineWidth; + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + var sx = sourcePos[0] < targetPos[0] ? w-xo : xo; + var sy = sourcePos[1] < targetPos[1] ? h-yo : yo; + var tx = sourcePos[0] < targetPos[0] ? xo : w-xo; + var ty = sourcePos[1] < targetPos[1] ? yo : h-yo; + var retVal = [ x, y, w, h, sx, sy, tx, ty ]; + + return retVal; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + }; + + /** + * This Connector draws a Bezier curve with two control points. + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + jsPlumb.Connectors.Bezier = function(curviness) { + + var self = this; + this.majorAnchor = curviness || 150; + this.minorAnchor = 10; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) + { + lineWidth = lineWidth || 0; + var w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth, h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + var canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2), canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + var sx = sourcePos[0] < targetPos[0] ? w - (lineWidth/2): (lineWidth/2), sy = sourcePos[1] < targetPos[1] ? h-(lineWidth/2) : (lineWidth/2); + var tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : w-(lineWidth/2), ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : h-(lineWidth/2); + var CP = self._findControlPoint([sx,sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + var CP2 = self._findControlPoint([tx,ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(sx,tx); var minx2 = Math.min(CP[0], CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(sx,tx); var maxx2 = Math.max(CP[0], CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > w) w = maxx; + if (minx < 0) { + canvasX += minx; var ox = Math.abs(minx); + w += ox; CP[0] += ox; sx += ox; tx +=ox; CP2[0] += ox; + } + + var miny1 = Math.min(sy,ty); var miny2 = Math.min(CP[1], CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(sy,ty); var maxy2 = Math.max(CP[1], CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > h) h = maxy; + if (miny < 0) { + canvasY += miny; var oy = Math.abs(miny); + h += oy; CP[1] += oy; sy += oy; ty +=oy; CP2[1] += oy; + } + + // return [ canvasx, canvasy, canvasWidth, canvasHeight, + // sourceX, sourceY, targetX, targetY, + // controlPoint1_X, controlPoint1_Y, controlPoint2_X, controlPoint2_Y + return [canvasX, canvasY, w, h, sx, sy, tx, ty, CP[0], CP[1], CP2[0], CP2[1] ]; + }; + + this.paint = function(d, ctx) { + + /*var img = new Image(); + img.src = "../img/pattern.jpg"; + ctx.fillStyle = ctx.createPattern(img, 'repeat-y');*/ + + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + //ctx.fill(); + } + }; + + + /** + * Types of endpoint UIs. we supply three - a circle of default radius 10px, a rectangle of + * default size 20x20, and an image (with no default). you can supply others of these if you want to - see the documentation + * for a howto. + */ + + /** + * a round endpoint, with default radius 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + } + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = $.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + $.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * A Rectangular endpoint, with default size 20x20. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + //todo: the fillStyle needs some thought. we want to support a few options: + // 1. nothing supplied; use the stroke color or the default if no stroke color. + // 2. a fill color supplied - use it + // 3. a gradient supplied - use it + // 4. setting the endpoint to the same color as the bg of the element it is attached to. + var style = $.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + $.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' property in the params object.. + */ + jsPlumb.Endpoints.Image = function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + }; +})(); \ No newline at end of file diff --git a/archive/jquery.jsPlumb-defaults-1.1.0.js b/archive/jquery.jsPlumb-defaults-1.1.0.js new file mode 100644 index 000000000..c71add762 --- /dev/null +++ b/archive/jquery.jsPlumb-defaults-1.1.0.js @@ -0,0 +1,347 @@ +/** +* jsPlumb-defaults-1.1.0 +* +* This script contains the default Anchors, Endpoints and Connectors for jsPlumb. It should be used with jsPlumb 1.1.0 and above; +* prior to version 1.1.0 of jsPlumb the defaults were included inside the main script. +* +* NOTE: for production usage you should use jsPlumb-all-1.1.0-min.js, which contains the main jsPlumb script and this script together, +* in a minified file. +*/ + +(function() { + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * getOrientation : function() { return [ox, oy]; } + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + jsPlumb.Anchors.TopCenter = jsPlumb.makeAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors.BottomCenter = jsPlumb.makeAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors.LeftMiddle = jsPlumb.makeAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors.RightMiddle = jsPlumb.makeAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors.Center = jsPlumb.makeAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors.TopRight = jsPlumb.makeAnchor(1, 0, 0,-1); + jsPlumb.Anchors.BottomRight = jsPlumb.makeAnchor(1, 1, 0, 1); + jsPlumb.Anchors.TopLeft = jsPlumb.makeAnchor(0, 0, 0, -1); + jsPlumb.Anchors.BottomLeft = jsPlumb.makeAnchor(0, 1, 0, 1); + + + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (w < 2 * lineWidth) { + w = 2 * lineWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - lineWidth; + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < 2 * lineWidth) { + // minimum size is 2 * line Width + h = 2 * lineWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - lineWidth; + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + var sx = sourcePos[0] < targetPos[0] ? w-xo : xo; + var sy = sourcePos[1] < targetPos[1] ? h-yo : yo; + var tx = sourcePos[0] < targetPos[0] ? xo : w-xo; + var ty = sourcePos[1] < targetPos[1] ? yo : h-yo; + var retVal = [ x, y, w, h, sx, sy, tx, ty ]; + + return retVal; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + }; + + /** + * This Connector draws a Bezier curve with two control points. + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + jsPlumb.Connectors.Bezier = function(curviness) { + + var self = this; + this.majorAnchor = curviness || 150; + this.minorAnchor = 10; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) + { + lineWidth = lineWidth || 0; + var w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth, h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + var canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2), canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + var sx = sourcePos[0] < targetPos[0] ? w - (lineWidth/2): (lineWidth/2), sy = sourcePos[1] < targetPos[1] ? h-(lineWidth/2) : (lineWidth/2); + var tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : w-(lineWidth/2), ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : h-(lineWidth/2); + var CP = self._findControlPoint([sx,sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + var CP2 = self._findControlPoint([tx,ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(sx,tx); var minx2 = Math.min(CP[0], CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(sx,tx); var maxx2 = Math.max(CP[0], CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > w) w = maxx; + if (minx < 0) { + canvasX += minx; var ox = Math.abs(minx); + w += ox; CP[0] += ox; sx += ox; tx +=ox; CP2[0] += ox; + } + + var miny1 = Math.min(sy,ty); var miny2 = Math.min(CP[1], CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(sy,ty); var maxy2 = Math.max(CP[1], CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > h) h = maxy; + if (miny < 0) { + canvasY += miny; var oy = Math.abs(miny); + h += oy; CP[1] += oy; sy += oy; ty +=oy; CP2[1] += oy; + } + + // return [ canvasx, canvasy, canvasWidth, canvasHeight, + // sourceX, sourceY, targetX, targetY, + // controlPoint1_X, controlPoint1_Y, controlPoint2_X, controlPoint2_Y + return [canvasX, canvasY, w, h, sx, sy, tx, ty, CP[0], CP[1], CP2[0], CP2[1] ]; + }; + + this.paint = function(d, ctx) { + + /*var img = new Image(); + img.src = "../img/pattern.jpg"; + ctx.fillStyle = ctx.createPattern(img, 'repeat-y');*/ + + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + //ctx.fill(); + } + }; + + + /** + * Types of endpoint UIs. we supply three - a circle of default radius 10px, a rectangle of + * default size 20x20, and an image (with no default). you can supply others of these if you want to - see the documentation + * for a howto. + */ + + /** + * a round endpoint, with default radius 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + } + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = $.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + $.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * A Rectangular endpoint, with default size 20x20. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + //todo: the fillStyle needs some thought. we want to support a few options: + // 1. nothing supplied; use the stroke color or the default if no stroke color. + // 2. a fill color supplied - use it + // 3. a gradient supplied - use it + // 4. setting the endpoint to the same color as the bg of the element it is attached to. + var style = $.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + $.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' property in the params object.. + */ + jsPlumb.Endpoints.Image = function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + }; +})(); \ No newline at end of file diff --git a/archive/jquery.jsPlumb-defaults-1.1.1-RC1.js b/archive/jquery.jsPlumb-defaults-1.1.1-RC1.js new file mode 100644 index 000000000..829f7c743 --- /dev/null +++ b/archive/jquery.jsPlumb-defaults-1.1.1-RC1.js @@ -0,0 +1,406 @@ +/** +* jsPlumb-defaults-1.1.1-RC1 +* +* This script contains the default Anchors, Endpoints and Connectors for jsPlumb. It should be used with jsPlumb 1.1.0 and above; +* prior to version 1.1.0 of jsPlumb the defaults were included inside the main script. +* +* Version 1.1.1 of this script adds the Triangle Endpoint, written by __________ and featured on this demo: +* +* http://www.mintdesign.ru/blog/javascript-jsplumb-making-graph +* http://www.mintdesign.ru/site/samples/jsplumb/jsPlumb-graph-sample.htm +* +* NOTE: for production usage you should use jsPlumb-all-1.1.1-min.js, which contains the main jsPlumb script and this script together, +* in a minified file. +*/ + +(function() { + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * getOrientation : function() { return [ox, oy]; } + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + jsPlumb.Anchors.TopCenter = jsPlumb.makeAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors.BottomCenter = jsPlumb.makeAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors.LeftMiddle = jsPlumb.makeAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors.RightMiddle = jsPlumb.makeAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors.Center = jsPlumb.makeAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors.TopRight = jsPlumb.makeAnchor(1, 0, 0,-1); + jsPlumb.Anchors.BottomRight = jsPlumb.makeAnchor(1, 1, 0, 1); + jsPlumb.Anchors.TopLeft = jsPlumb.makeAnchor(0, 0, 0, -1); + jsPlumb.Anchors.BottomLeft = jsPlumb.makeAnchor(0, 1, 0, 1); + + + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (w < 2 * lineWidth) { + w = 2 * lineWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - lineWidth; + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < 2 * lineWidth) { + // minimum size is 2 * line Width + h = 2 * lineWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - lineWidth; + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + var sx = sourcePos[0] < targetPos[0] ? w-xo : xo; + var sy = sourcePos[1] < targetPos[1] ? h-yo : yo; + var tx = sourcePos[0] < targetPos[0] ? xo : w-xo; + var ty = sourcePos[1] < targetPos[1] ? yo : h-yo; + var retVal = [ x, y, w, h, sx, sy, tx, ty ]; + + return retVal; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + }; + + /** + * This Connector draws a Bezier curve with two control points. + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + jsPlumb.Connectors.Bezier = function(curviness) { + + var self = this; + this.majorAnchor = curviness || 150; + this.minorAnchor = 10; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) + { + lineWidth = lineWidth || 0; + var w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth, h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + var canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2), canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + var sx = sourcePos[0] < targetPos[0] ? w - (lineWidth/2): (lineWidth/2), sy = sourcePos[1] < targetPos[1] ? h-(lineWidth/2) : (lineWidth/2); + var tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : w-(lineWidth/2), ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : h-(lineWidth/2); + var CP = self._findControlPoint([sx,sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + var CP2 = self._findControlPoint([tx,ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(sx,tx); var minx2 = Math.min(CP[0], CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(sx,tx); var maxx2 = Math.max(CP[0], CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > w) w = maxx; + if (minx < 0) { + canvasX += minx; var ox = Math.abs(minx); + w += ox; CP[0] += ox; sx += ox; tx +=ox; CP2[0] += ox; + } + + var miny1 = Math.min(sy,ty); var miny2 = Math.min(CP[1], CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(sy,ty); var maxy2 = Math.max(CP[1], CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > h) h = maxy; + if (miny < 0) { + canvasY += miny; var oy = Math.abs(miny); + h += oy; CP[1] += oy; sy += oy; ty +=oy; CP2[1] += oy; + } + + // return [ canvasx, canvasy, canvasWidth, canvasHeight, + // sourceX, sourceY, targetX, targetY, + // controlPoint1_X, controlPoint1_Y, controlPoint2_X, controlPoint2_Y + return [canvasX, canvasY, w, h, sx, sy, tx, ty, CP[0], CP[1], CP2[0], CP2[1] ]; + }; + + this.paint = function(d, ctx) { + + /*var img = new Image(); + img.src = "../img/pattern.jpg"; + ctx.fillStyle = ctx.createPattern(img, 'repeat-y');*/ + + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + //ctx.fill(); + } + }; + + + /** + * Types of endpoint UIs. we supply three - a circle of default radius 10px, a rectangle of + * default size 20x20, and an image (with no default). you can supply others of these if you want to - see the documentation + * for a howto. + */ + + /** + * a round endpoint, with default radius 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + } + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * A Rectangular endpoint, with default size 20x20. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + //todo: the fillStyle needs some thought. we want to support a few options: + // 1. nothing supplied; use the stroke color or the default if no stroke color. + // 2. a fill color supplied - use it + // 3. a gradient supplied - use it + // 4. setting the endpoint to the same color as the bg of the element it is attached to. + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + jsPlumb.Endpoints.Triangle = function(params) { + + params = params || { width:15, height:15 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - width/2; + var y = anchorPoint[1] - height/2; + + jsPlumb.sizeCanvas(canvas, x, y, width, height); + + var ctx = canvas.getContext('2d'); + var + offsetX = 0, + offsetY = 0, + angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = endpointStyle.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + ctx.fill(); + + }; + }; + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' property in the params object.. + */ + jsPlumb.Endpoints.Image = function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + }; +})(); \ No newline at end of file diff --git a/archive/jquery.jsPlumb-flowchart-RC1.js b/archive/jquery.jsPlumb-flowchart-RC1.js new file mode 100644 index 000000000..11a398f9e --- /dev/null +++ b/archive/jquery.jsPlumb-flowchart-RC1.js @@ -0,0 +1,361 @@ +/** + +a flowchart connector for jsPlumb. + +this is NOT FINISHED. it doesnt support all combinations of anchor orientations, and the code that is here can definitely be refactored. + +*/ + +(function() { + + + var perpendicular = function(o1, o2) { + + }; + + var inPhase = function (o1, o2) { + var r = [o1[0] + o2[0], o1[1] + o2[1]]; + return r[0] == 0 && r[1] == 0; + }; + + /** + * Returns whether or not either of 'o' or 'o2' have an orientation that is defined in + * more than one direction. in that case we just draw a straight line. + */ + var notSuitableOrientations = function(o, o2) { + var _nso = function(o) { + return Math.abs(o[0]) == Math.abs(o[1]); + }; + return _nso(o) || _nso(o2); + }; + + jsPlumb.Connectors.Flowchart = function(params) { + params = params || {}; + var minStubLength = params.minStubLength || 30; + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) { + // setup default for linewidth + lineWidth = lineWidth || 1; + // and make offsets + var offx = lineWidth / 2, offy = lineWidth / 2; + // get points list ready + var points = []; + // short vars for access to orientation + var so = sourceAnchor.orientation || sourceAnchor.getOrientation(), to = targetAnchor.orientation || targetAnchor.getOrientation(); + var swapX = targetPos[0] < sourcePos[0]; + var swapY = targetPos[1] < sourcePos[1]; + var x = swapX ? targetPos[0] : sourcePos[0], y = swapY ? targetPos[1] : sourcePos[1]; + x -= offx; + y -= offy; + var w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx; + var h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy; + var sx = swapX ? w-offx : offx, sy = swapY ? h-offy : offy, tx = swapX ? offx : w-offx , ty = swapY ? offy : h-offy; + params.offsetLine=[0,0,0]; + + + if (!notSuitableOrientations(so, to)) { + if (perpendicular(so,to)) { + } + else { + + var stubLength = h / 2, stubWidth = w / 2; + + if (so[0] == 0 && so[1] == 1 && to[0] == 0 && to[1] == -1) { + sy = 0; + ty = h; + if (sourcePos[1] < targetPos[1]) { + // a three line connection + points.push(sx);points.push(sy + stubLength); + points.push(tx);points.push(ty - stubLength); + + } + else { + // a five line connection + h = minStubLength * 2 + (sourcePos[1] - targetPos[1]) + 2*offy; + y = targetPos[1] - minStubLength; + sy = sourcePos[1] - y - offy; + ty = minStubLength + 2*offy; + points.push(sx);points.push(sy + minStubLength+params.offsetLine[0]); + points.push(w/2+params.offsetLine[1]);points.push(sy + minStubLength+params.offsetLine[0]); + points.push(w/2+params.offsetLine[1]);points.push(offy+params.offsetLine[2]); + points.push(tx);points.push(offy+params.offsetLine[2]); + + } + } + else if (so[0] == 0 && so[1] == -1 && to[0] == 0 && to[1] == 1) { + sy = h; + ty = 0; + y = targetPos[1]; + if (sourcePos[1] > targetPos[1]) { + // a three line connection + points.push(sx);points.push(sy - stubLength); + points.push(tx);points.push(ty + stubLength); + } + else { + // a five line connection + h = minStubLength * 2 + (targetPos[1] - sourcePos[1]) + offy; + y = sourcePos[1] - minStubLength - offy; + sy = minStubLength + offy; + ty = h - minStubLength - 2*offy; + points.push(sx);points.push(sy - minStubLength + offy+params.offsetLine[0]); + points.push(w/2+params.offsetLine[1]);points.push(sy - minStubLength + offy+params.offsetLine[0]); + points.push(w/2+params.offsetLine[1]);points.push(h - offy+params.offsetLine[2]); + points.push(tx);points.push(h - offy+params.offsetLine[2]); + } + } + + // X + else if (so[0] == 1 && so[1] == 0 && to[0] == -1 && to[1] == 0) { + sx = 0; + tx = w; + if (sourcePos[0] < targetPos[0]) { + points.push(sx+stubWidth);points.push(sy); + points.push(tx-stubWidth);points.push(ty); + } + else { + w = minStubLength * 2 + (sourcePos[0] - targetPos[0]) + 2*offx; + x = targetPos[0] - minStubLength - offx; + sx = sourcePos[0] - x; + tx = minStubLength + offx; + points.push(sx + minStubLength+params.offsetLine[0]);points.push(sy); + points.push(sx + minStubLength+params.offsetLine[0]);points.push(h/2+params.offsetLine[1]); + points.push(offx+params.offsetLine[2]);points.push(h/2+params.offsetLine[1]); + points.push(offx+params.offsetLine[2]);points.push(ty); + } + } + else if (so[0] == -1 && so[1] == 0 && to[0] == 1 && to[1] == 0) { + sx = w; + tx = 0; + x = targetPos[0]; + if (sourcePos[0] > targetPos[0]) { + points.push(sx-stubWidth);points.push(sy); + points.push(tx+stubWidth);points.push(ty); + } + else { + w = minStubLength * 2 + (targetPos[0] - sourcePos[0]) + 2*offx; + x = sourcePos[0] - minStubLength - offx; + sx = minStubLength + offx; + tx = w - minStubLength - 2*offx; + points.push(sx - minStubLength+params.offsetLine[0]);points.push(sy); + points.push(sx - minStubLength+params.offsetLine[0]);points.push(h/2+params.offsetLine[1]); + points.push(w-offx+params.offsetLine[2]);points.push(h/2+params.offsetLine[1]); + points.push(w-offx+params.offsetLine[2]);points.push(ty); + } + }else if(so[0] == 0 && so[1] == 1 && to[0]==1 && to[1]==0){ + + sy = 0; + ty = h; + if (sourcePos[1] < targetPos[1]) { + + // a three line connection + points.push(sx);points.push(sy + stubLength); + points.push(tx+20+params.offsetLine[0]);points.push(ty - stubLength); + points.push(tx+20+params.offsetLine[0]);points.push(ty); + } + else { + h = minStubLength * 2 + (sourcePos[1] - targetPos[1]) + 2*offy; + y = targetPos[1] - minStubLength; + sy = sourcePos[1] - y - offy; + ty = minStubLength + 2*offy; + //points.push(sx);points.push(sy + minStubLength+params.offsetLine[0]); + //points.push(w+20+params.offsetLine[1]);points.push(sy + minStubLength+params.offsetLine[0]); + //points.push(w+20+params.offsetLine[1]);points.push(offy+30); + + points.push(sx);points.push(sy + 20+params.offsetLine[0]); + if(sx>tx){ + + points.push((sx+tx)/2);points.push(sy+20); + points.push((sx+tx)/2);points.push(ty); + + }else{ + points.push(tx+20);points.push(sy+20); + } + points.push(tx+20);points.push(ty); + } + }else if(so[0] == 0 && so[1] == -1 && to[0]==1 && to[1]==0){ + + sy = 0; + ty = h; + if (sourcePos[1] < targetPos[1]) { + sy+=20; + ty+=20; + h+=20; + y-=20; + points.push(sx);points.push(sy-20+params.offsetLine[0]); + points.push(tx+20+params.offsetLine[1]);points.push(sy-20+params.offsetLine[0]); + points.push(tx+20+params.offsetLine[1]);points.push(ty); + + } + else { + h = minStubLength * 2 + (sourcePos[1] - targetPos[1]) + 2*offy; + y = targetPos[1] - minStubLength; + sy = sourcePos[1] - y - offy; + ty = minStubLength + 2*offy; + points.push(sx);points.push(sy -20+params.offsetLine[0]); + points.push(w+20+params.offsetLine[1]);points.push(sy -20+params.offsetLine[0]); + points.push(w+20+params.offsetLine[1]);points.push(offy+30); + } + }else if(so[0] == 1 && so[1] == 0 && to[0]==1 && to[1]==0){ + + sy = 0; + ty = h; + if (sourcePos[1] < targetPos[1]) { + points.push(sx+20+params.offsetLine[0]);points.push(sy); + points.push(sx+20+params.offsetLine[0]);points.push(ty); + } + else { + h = minStubLength * 2 + (sourcePos[1] - targetPos[1]) + 2*offy; + y = targetPos[1] - minStubLength; + sy = sourcePos[1] - y - offy; + ty = minStubLength + 2*offy; + points.push(w+20+params.offsetLine[0]);points.push(sy); + points.push(w+20+params.offsetLine[0]);points.push(offy+30); + } + }else if(so[0] == -1 && so[1] == 0 && to[0]==0 && to[1]==-1){ + + sy = 0; + ty = h; + if (sourcePos[1] < targetPos[1]) { + points.push(sx);points.push(sy+h/2+params.offsetLine[0]); + points.push(tx);points.push(sy+h/2+params.offsetLine[0]); + } + else { + h = minStubLength * 2 + (sourcePos[1] - targetPos[1]) + 2*offy; + y = targetPos[1] - minStubLength; + sy = sourcePos[1] - y - offy; + ty = minStubLength + 2*offy; + + points.push(sx);points.push(ty-20+params.offsetLine[0]); + points.push(tx);points.push(ty-20+params.offsetLine[0]); + + } + }else if(so[0] == 1 && so[1] == 0 && to[0]==0 && to[1]==-1){ + + sy = 0; + ty = h; + if (sourcePos[1] < targetPos[1]) { + + points.push(sx+20+params.offsetLine[0]);points.push(sy); + points.push(sx+20+params.offsetLine[0]);points.push(sy+h/2+params.offsetLine[1]); + points.push(tx);points.push(sy+h/2+params.offsetLine[1]); + } + else { + h = minStubLength * 2 + (sourcePos[1] - targetPos[1]) + 2*offy; + y = targetPos[1] - minStubLength; + sy = sourcePos[1] - y - offy; + ty = minStubLength + 2*offy; + + points.push(sx+20+params.offsetLine[0]);points.push(sy); + points.push(sx+20+params.offsetLine[0]);points.push(ty-20+params.offsetLine[1]); + points.push(tx);points.push(ty-20+params.offsetLine[1]); + } + }else if(so[0] == 0 && so[1] == -1 && to[0]==0 && to[1]==-1){ + + sy = 0; + ty = h; + + if (sourcePos[1] < targetPos[1]) { + + + points.push(sx);points.push(sy+params.offsetLine[0]); + points.push(tx+params.offsetLine[1]);points.push(sy+params.offsetLine[0]); + points.push(tx+params.offsetLine[1]);points.push(sy+h/2); + sy+=20; + ty+=20; + h+=20; + y-=20; + } + else { + + h = minStubLength * 2 + (sourcePos[1] - targetPos[1]) + 2*offy; + y = targetPos[1] - minStubLength; + sy = sourcePos[1] - y - offy; + ty = minStubLength + 2*offy; + + + points.push(sx);points.push(ty-20+params.offsetLine[0]); + points.push(tx);points.push(ty-20+params.offsetLine[0]); + + } + }else if(so[0] == 0 && so[1] == -1 && to[0]==-1 && to[1]==0){ + + sy = 0; + ty = h; + + if (sourcePos[1] < targetPos[1]) { + sy+=20; + ty+=20; + h+=20; + y-=20; + x-=20; + sx+=20; + tx+=20; + points.push(sx);points.push(sy-20+params.offsetLine[0]); + points.push(tx-20+params.offsetLine[1]);points.push(sy-20+params.offsetLine[0]); + points.push(tx-20+params.offsetLine[1]);points.push(ty); + } + else { + + h = minStubLength * 2 + (sourcePos[1] - targetPos[1]) + 2*offy; + y = targetPos[1] - minStubLength; + sy = sourcePos[1] - y - offy; + ty = minStubLength + 2*offy; + + + x-=20; + sx+=20; + tx+=20; + points.push(sx);points.push(sy-20+params.offsetLine[0]); + points.push(tx-20+params.offsetLine[1]);points.push(sy-20+params.offsetLine[0]); + points.push(tx-20+params.offsetLine[1]);points.push(ty); + } + } + + + + + else { + } + + } + } + + w+=20; + // first define the basic points - location, width, height, and start/end points. + var retVal = [x, y, w, h, sx, sy, tx, ty]; + // then store how many intermediate points we calculated + retVal.push(points.length / 2); + // add the intermediate points at the end. + for (var i = 0; i < points.length; i++) + retVal.push(points[i]); + return retVal; + }; + + this.paint = function(dimensions, ctx) { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + // loop through extra points + for (var i = 0; i < dimensions[8]; i++) { + ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]); + } + // finally draw a line to the end + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + + }; + + /** + * Set of Endpoints for Flowcharts. Currently has one - an arrow, which takes the anchor orientation into account when painting. + */ + jsPlumb.Endpoints.Flowchart = { + + Arrow : function(params) { + var width = params.width || 15; + var length = params.length || 15; + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + // use the orientation array to determine the rotation of the endpoint. + }; + } + }; + +})(); diff --git a/archive/jsPlumb-1.2-RC1.js b/archive/jsPlumb-1.2-RC1.js new file mode 100644 index 000000000..6083f0e49 --- /dev/null +++ b/archive/jsPlumb-1.2-RC1.js @@ -0,0 +1,1577 @@ +/* + * jsPlumb 1.2-RC1 + * + * Provides a way to visually connect elements on an HTML page. + * + * 1.1.1 contains bugfixes and API additions on 1.1.0. + * + * + * http://morrisonpitt.com/jsPlumb/demo.html + * http://code.google.com/p/jsPlumb + * + */ + +(function() { + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + + var log = null; + + var repaintFunction = function() { jsPlumb.repaintEverything(); }; + var automaticRepaint = true; + function repaintEverything() { + if (automaticRepaint) + repaintFunction(); + }; + var resizeTimer = null; + + /*TODO: abstract this out from jQuery too! but how...because jsPlumb is not loaded yet. + $(window).bind('resize', function() { + if (resizeTimer) clearTimeout(resizeTimer); + resizeTimer = setTimeout(repaintEverything, 100); + });*/ + + /** + * map of element id -> endpoint lists. an element can have an arbitrary number of endpoints on it, + * and not all of them have to be connected to anything. + */ + var endpointsByElement = {}; + var offsets = []; + var floatingConnections = {}; + var draggableStates = {}; + var _draggableByDefault = true; + var sizes = []; + + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + var _findIndex = function( a, v, b, s ) { + var _eq = function(o1, o2) { + if (o1 === o2) return true; + else if (typeof o1 == 'object' && typeof o2 == 'object') { + var same = true; + for(var propertyName in o1) { + if(!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for(var propertyName in o2) { + if(!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for( var i = +b || 0, l = a.length; i < l; i++ ) { + if( _eq(a[i], v)) return i; + } + return -1; + }; + + /** + * helper method to add an item to a list, creating the list if it does not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + }; + + /** + * Handles the dragging of an element. + * @param element jQuery element + * @param ui UI object from current library's event system + */ + var _draw = function(element, ui) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints) { + _updateOffset(id, ui); + var myOffset = offsets[id]; + var myWH = sizes[id]; + // loop through endpoints for this element + for (var i = 0; i < endpoints.length; i++) { + var e = endpoints[i]; + var anchorLoc = endpoints[i].anchor.compute([myOffset.left, myOffset.top], myWH); + e.paint(anchorLoc); + var l = e.connections; + for (var j = 0; j < l.length; j++) + l[j].paint(id, ui); // ...and paint them. + } + } + }; + + /** + * helper function: the second argument is a function taking two args - the first is an id + * ui element from the current library (or list of them), and the second is the function + * to run. + */ + var _elementProxy = function(element, fn) { + var retVal = null; + if (typeof element == 'object' && element.length) { + retVal = []; + for (var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]); + var id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } + else { + var el = _getElementObject(element); + var id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + + return retVal; + }; + + /** + * gets the named attribute from the given element (id or element object) + */ + var _getAttribute = function(el, attName) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + return jsPlumb.CurrentLibrary.getAttribute(ele, attName); + }; + + var _setAttribute = function(el, attName, attValue) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + jsPlumb.CurrentLibrary.setAttribute(ele, attName, attValue); + }; + + var _addClass = function(el, clazz) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + jsPlumb.CurrentLibrary.addClass(ele, clazz); + }; + + var _getElementObject = function(elId) { + return jsPlumb.CurrentLibrary.getElementObject(elId); + }; + + var _getOffset = function(el) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + return jsPlumb.CurrentLibrary.getOffset(ele); + }; + + var _getSize = function(el) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + return jsPlumb.CurrentLibrary.getSize(ele); + }; + + var _appendCanvas = function(canvas) { + document.body.appendChild(canvas); + }; + + /** + * gets an id for the given element, creating and setting one if necessary. + */ + var _getId = function(element) { + var id = _getAttribute(element, "id"); + if (!id) { + id = "_jsPlumb_" + new String((new Date()).getTime()); + _setAttribute(element, "id", id); + } + return id; + }; + + /** + * inits a draggable if it's not already initialised. + * + * TODO: we need to hook in each library to this method. they need to be given the opportunity to + * wrap/insert lifecycle functions, because each library does different things. for instance, jQuery + * supports the notion of 'revert', which will clean up a dragged endpoint; MooTools does not. jQuery + * also supports 'opacity' and MooTools does not; jQuery supports z-index of the draggable; MooTools + * does not. i could go on. the point is...oh. initDraggable can do this. ok. + */ + var _initDraggableIfNecessary = function(element, elementId, isDraggable, dragOptions) { + // dragging + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable && jsPlumb.CurrentLibrary.isDragSupported(element)) { + var options = dragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend({}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var initDrag = function(element, elementId, dragFunc) { + options[dragEvent] = _wrap(options[dragEvent], dragFunc); + var draggable = draggableStates[elementId]; + // TODO: this is still jQuery specific. + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + }; + initDrag(element, elementId, function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + } + }; + + var _log = function(msg) { + // not implemented. yet. + } + + /** + * helper to create a canvas. + * @param clazz optional class name for the canvas. + */ + var _newCanvas = function(clazz) { + var canvas = document.createElement("canvas"); + _appendCanvas(canvas); + canvas.style.position="absolute"; + if (clazz) { canvas.className=clazz; } + _getId(canvas); // set an id. + + if (ie) { + // for IE we have to set a big canvas size. actually you can override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + /** + * performs the given function operation on all the connections found for the given element + * id; this means we find all the endpoints for the given element, and then for each endpoint + * find the connectors connected to it. then we pass each connection in to the given + * function. + */ + var _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + for (var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }; + /** + * perform an operation on all elements. + */ + var _operationOnAll = function(func) { + for (var elId in endpointsByElement) { + _operation(elId, func); + } + }; + /** + * helper to remove an element from the DOM. + */ + var _removeElement = function(element) { + if (element != null) { + try { document.body.removeChild(element); } + catch (e) { } + } + }; + /** + * helper to remove a list of elements from the DOM. + */ + var _removeElements = function(elements) { + for (var i in elements) + _removeElement(elements[i]); + }; + /** + * helper method to remove an item from a list. + */ + var _removeFromList = function(map, key, value) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete( l[i] ); + l.splice( i, 1 ); + return true; + } + } + return false; + }; + /** + * Sets whether or not the given element(s) should be draggable, regardless of what a particular + * plumb command may request. + * + * @param element May be a string, a jQuery elements, or a list of strings/jquery elements. + * @param draggable Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + var _helper = function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }; + + return _elementProxy(element, _helper); + }; + /** + * private method to do the business of hiding/showing. + * @param el either Id of the element in question or a jquery object for the element. + * @param state String specifying a value for the css 'display' property ('block' or 'none'). + */ + var _setVisible = function(el, state) { + var elId = _getAttribute(el, "id"); + var f = function(jpc) { + jpc.canvas.style.display = state; + }; + _operation(elId, f); + }; + /** + * toggles the draggable state of the given element(s). + * @param el either an id, or a jquery object, or a list of ids/jquery objects. + */ + var _toggleDraggable = function(el) { + var fn = function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + //el.draggable("option", "disabled", !state); + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }; + return _elementProxy(el, fn); + }; + /** + * private method to do the business of toggling hiding/showing. + * @param elId Id of the element in question + */ + var _toggleVisible = function(elId) { + var f = function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }; + + _operation(elId, f); + + //todo this should call _elementProxy, and pass in the _operation(elId, f) call as a function. cos _toggleDraggable does that. + }; + /** + * updates the offset and size for a given element, and stores the values. + * if 'offset' is not null we use that (it would have been passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we use the offset, outerWidth and outerHeight methods to get + * the current values. + */ + var _updateOffset = function(elId, offset, recalc) { + if (recalc || offset == null) { // if forced repaint or no offset available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + sizes[elId] = _getSize(elId); + offsets[elId] = _getOffset(elId); + } else { + offsets[elId] = offset; + } + }; + + /** + * wraps one function with another, creating a placeholder for the wrapped function + * if it was null. this is used to wrap the various drag/drop event functions - to allow + * jsPlumb to be notified of important lifecycle events without imposing itself on the user's + * drap/drop functionality. + * TODO: determine whether or not we should support an error handler concept, if one of the functions fails. + */ + var _wrap = function(wrappedFunction, newFunction) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + try { newFunction.apply(this, arguments); } + catch (e) { } + try { wrappedFunction.apply(this, arguments); } + catch (e) { } + }; + } + + /* + Class: Anchor + Models a position relative to the origin of some element that an Endpoint can be located. + */ + /* + Function: Anchor + + Constructor for the Anchor class + + Parameters: + params - Anchor parameters. This should contain three values, and may optionally have an 'offsets' argument: + + - x : the x location of the anchor as a fraction of the total width. + - y : the y location of the anchor as a fraction of the total height. + - orientation : an [x,y] array indicating the general direction a connection from the anchor should go in. for more info on this, see the documentation, or the docs in jquery-jsPlumb-defaults-XXX.js for the default Anchors. + - offsets : an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. may be null. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; this.y = params.y || 0; + var orientation = params.orientation || [0,0]; + this.offsets = params.offsets || [0,0]; + this.compute = function(xy, wh, txy, twh) { + return [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + } + this.getOrientation = function() { return orientation; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from its position relative to the anchor it is floating relative to. + * + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(refCanvas); + + // these are used to store the current relative position of our anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating anchor is hovering over another anchor. + var orientation = null; + + this.compute = function(xy, wh, txy, twh) { + // set these for the getOrientation method to use. + xDir = 0;//xy[0] < txy[0] ? -1 : xy[0] == txy[0] ? 0 : 1; + yDir = 0;//xy[1] < txy[1] ? -1 : xy[1] == txy[1] ? 0 : 1; + return [xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other anchor: if it declares zero for some direction, we declare zero too. + // this might not be the most awesome. perhaps we can come up with a better way. it's just so that the line we draw looks + // like it makes sense. maybe this wont make sense. + return [Math.abs(o[0]) * xDir * -1, Math.abs(o[1]) * yDir * -1]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering over another anchor; + * we want to assume that anchor's orientation for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no longer hovering + * over another anchor; we should resume calculating orientation as we normally do. + */ + this.out = function() { + orientation = null; + }; + }; + + // ************** connection + // **************************************** + /** + * allowed params: + * source: source element (string or a jQuery element) (required) + * target: target element (string or a jQuery element) (required) + * + * anchors: optional array of anchor placements. defaults to BottomCenter for source + * and TopCenter for target. + */ + var Connection = function(params) { + + // ************** get the source and target and register the connection. ******************* + var self = this; + // get source and target as jQuery objects + this.source = _getElementObject(params.source); + this.target = _getElementObject(params.target); + this.sourceId = _getAttribute(this.source, "id"); + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + + // init endpoints + this.endpoints = []; + this.endpointStyles = []; + var prepareEndpoint = function(existing, index, params) { + if (existing) self.endpoints[index] = existing; + else { + if(!params.endpoints) params.endpoints = [null,null]; + var ep = params.endpoints[index] || params.endpoint || jsPlumb.Defaults.Endpoints[index] || jsPlumb.Defaults.Endpoint|| new jsPlumb.Endpoints.Dot(); + if (!params.endpointStyles) params.endpointStyles = [null,null]; + var es = params.endpointStyles[index] || params.endpointStyle || jsPlumb.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyle; + var a = params.anchors ? params.anchors[index] : jsPlumb.Defaults.Anchors[index] || jsPlumb.Anchors.BottomCenter; + self.endpoints[index] = new Endpoint({style:es, endpoint:ep, connections:[self], anchor:a, source:self.source }); + } + }; + + prepareEndpoint(params.sourceEndpoint, 0, params); + prepareEndpoint(params.targetEndpoint, 1, params); + + this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + this.paintStyle = this.endpoints[0].connectorStyle || this.endpoints[1].connectorStyle || params.paintStyle || jsPlumb.Defaults.PaintStyle; + + _updateOffset(this.sourceId); + _updateOffset(this.targetId); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var anchorLoc = this.endpoints[0].anchor.compute([myOffset.left, myOffset.top], myWH); + this.endpoints[0].paint(anchorLoc); + myOffset = offsets[this.targetId]; myWH = sizes[this.targetId]; + anchorLoc = this.endpoints[1].anchor.compute([myOffset.left, myOffset.top], myWH); + this.endpoints[1].paint(anchorLoc); + + // *************** create canvas on which the connection will be drawn ************ + var canvas = _newCanvas(jsPlumb.connectorClass); + this.canvas = canvas; + + /** + * paints the connection. + * @param elId Id of the element that is in motion + * @param ui current library's event system ui object (present if we came from a drag to get here) + * @param recalc whether or not to recalculate element sizes. this is true if a repaint caused this to be painted. + */ + this.paint = function(elId, ui, recalc) { + + if (log) log.debug("Painting Connection; element in motion is " + elId + "; ui is [" + ui + "]; recalc is [" + recalc + "]"); + + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = !(elId == this.sourceId); + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + if (this.canvas.getContext) { + + _updateOffset(elId, ui, recalc); + if (recalc) _updateOffset(tId); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var myOffset = offsets[elId]; + var otherOffset = offsets[tId]; + var myWH = sizes[elId]; + var otherWH = sizes[tId]; + + var ctx = canvas.getContext('2d'); + var sAnchorP = this.endpoints[sIdx].anchor.compute([myOffset.left, myOffset.top], myWH, [otherOffset.left, otherOffset.top], otherWH); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.compute([otherOffset.left, otherOffset.top], otherWH, [myOffset.left, myOffset.top], myWH); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, this.paintStyle.lineWidth); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + jsPlumb.extend(ctx, this.paintStyle); + + if (this.paintStyle.gradient && !ie) { + var g = swap ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + for (var i = 0; i < this.paintStyle.gradient.stops.length; i++) + g.addColorStop(this.paintStyle.gradient.stops[i][0],this.paintStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + + this.connector.paint(dim, ctx); + } + }; + + this.repaint = function() { + this.paint(this.sourceId, null, true); + }; + + _initDraggableIfNecessary(self.source, self.sourceId, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, self.targetId, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + }; + + /* + Class: Endpoint + + Models an endpoint. Can have one to N connections emanating from it (although how to handle that in the UI is + a very good question). also has a Canvas and paint style. + + */ + + /* + Function: Endpoint + + This is the Endpoint class constructor. + + Parameters: + + anchor - anchor for the endpoint, of type jsPlumb.Anchor. may be null. + endpoint - endpoint object, of type jsPlumb.Endpoint. may be null. + style - endpoint style, a js object. may be null. + source - element the endpoint is attached to, of type jquery object. Required. + canvas - canvas element to use. may be, and most often is, null. + connections - optional list of connections to configure the endpoint with. + isSource - boolean. indicates the endpoint can act as a source of new connections. optional. + dragOptions - if isSource is set to true, you can supply arguments for the jquery draggable method. optional. + connectionStyle - if isSource is set to true, this is the paint style for connections from this endpoint. optional. + connector - optional connector type to use. + isTarget - boolean. indicates the endpoint can act as a target of new connections. optional. + dropOptions - if isTarget is set to true, you can supply arguments for the jquery droppable method. optional. + reattach - optional boolean that determines whether or not the connections reattach after they + have been dragged off an endpoint and left floating. defaults to false: connections + dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + params = params || {}; + // make a copy. then we can use the wrapper function. + jsPlumb.extend({}, params); + var self = this; + self.anchor = params.anchor || jsPlumb.Anchors.TopCenter; + var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot(); + var _style = params.style || jsPlumb.Defaults.EndpointStyle; + this.connectorStyle = params.connectorStyle; + this.connector = params.connector; + var _element = params.source; + var _elementId = _getAttribute(_element, "id"); + var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of. + this.canvas = params.canvas || _newCanvas(jsPlumb.endpointClass); + this.connections = params.connections || []; + var _reattach = params.reattach || false; + var floatingEndpoint = null; + var inPlaceCopy = null; + this.addConnection = function(connection) { + + self.connections.push(connection); + }; + this.removeConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) + self.connections.splice(idx, 1); + }; + this.makeInPlaceCopy = function() { + var e = new Endpoint({anchor:self.anchor, source:_element, style:_style, endpoint:_endpoint}); + return e; + }; + /** + * returns whether or not this endpoint is connected to the given endpoint. + * @param endpoint Endpoint to test. + * @since 1.1.1 + * + * todo: needs testing. will this work if the endpoint passed in is the source? + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + this.isFloating = function() { return floatingEndpoint != null; }; + /** + * first pass at default ConnectorSelector: returns the first connection, if we have any. + * modified a little, 5/10/2010: now it only returns a connector if we have not got equal to or more than _maxConnector connectors + * attached. otherwise it is assumed a new connector is ok. but note with this setup we can't select any other connection than the first + * one. what if this could return a list? that implies everything should work with a list - dragging etc. could be nasty. could also + * be cool. + */ + var connectorSelector = function() { + return self.connections.length == 0 || self.connections.length < _maxConnections ? null : self.connections[0]; + }; + + this.isFull = function() { return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); }; + + /** + * paints the Endpoint, recalculating offset and anchor positions if necessary. + */ + this.paint = function(anchorPoint, connectorPaintStyle, canvas) { + + if (log) log.debug("Painting Endpoint with elementId [" + _elementId + "]; anchorPoint is [" + anchorPoint + "]"); + + if (anchorPoint == null) { + // do we always want to force a repaint here? i dont think so! + var xy = offsets[_elementId]; + var wh = sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset(_elementId); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + anchorPoint = self.anchor.compute([xy.left, xy.top], wh); + } + _endpoint.paint(anchorPoint, self.anchor.getOrientation(), canvas || self.canvas, _style, connectorPaintStyle || _style); + }; + + + // is this a connection source? we make it draggable and have the drag listener + // maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + + // first the question is, how many connections are on this endpoint? if it's only one, then excellent. otherwise we will either need a way + // to select one connection from the list, or drag them all. if we had a pluggable 'ConnectorSelector' interface we could probably + // provide a way for people to implement their own UI components to do the connector selection. the question in that particular case would be how much + // information the interface needs from jsPlumb at execution time. if, however, we leave out the connector selection, and drag them all, + // that wouldn't be too hard to organise. perhaps that behaviour would be on a switch for the endpoint, or perhaps the ConnectorSelector + // interface returns a List, with the default implementation just returning everything. i think i like that. + // + // let's say for now that there is just one endpoint, cos we need to get this going before we can consider a list of them anyway. + // the major difference between that case and the case of a new endpoint is actually quite small - it's a question of where the + // Connection comes from. for a new one, we create a new one. obviously. otherwise we just get the jpc from the Endpoint + // (remember we're only assuming one connection right now). so all of the UI stuff we do to create the floating endpoint etc + // will still be valid, but when we stop dragging, we'll have to do something different. if we stop with a valid drop i think it will + // be the same process. but if we stop with an invalid drop we have to reset the Connection to how it was when we got it. + var start = function() { + + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + _appendCanvas(n); + // create and assign an id, and initialize the offset. + var id = "" + new String(new Date().getTime()); + _setAttribute(_getElementObject(n), "id", id); + _updateOffset(id); + // store the id of the dragging div and the source element. the drop function + // will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor({reference:self.anchor, referenceCanvas:self.canvas}); + floatingEndpoint = new Endpoint({ + style:{fillStyle:'rgba(0,0,0,0)'}, + endpoint:_endpoint, + anchor:floatingAnchor, + source:n + }); + + jpc = connectorSelector(); + if (jpc == null) { + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = new Connection({ + sourceEndpoint:self, + targetEndpoint:floatingEndpoint, + source:_getElementObject(_element), + target:_getElementObject(n), + anchors:[self.anchor, floatingAnchor], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + connector: params.connector + }); + // todo ...unregister on stop + self.addConnection(jpc); + } else { + existingJpc = true; + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; + // probably we should remove the connection? and add it back if the user + // does not drop it somewhere proper. + self.removeConnection(jpc); + if (anchorIdx == 0){ + existingJpcParams = [jpc.source, jpc.sourceId]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + }else { + existingJpcParams = [jpc.target, jpc.targetId]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // todo unregister on stop + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + }; + + var dragOptions = params.dragOptions || { }; + + var defaultOpts = jsPlumb.extend({}, jsPlumb.CurrentLibrary.defaultDragOptions); + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + var startEvent = jsPlumb.CurrentLibrary.dragEvents['start']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements([floatingEndpoint.canvas, n, inPlaceCopy.canvas]); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + if (_reattach) { + jpc.floatingAnchorIndex = null; + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } + else { + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeElement(jpc.canvas); + } + } else { + _removeElement(jpc.canvas); + self.removeConnection(jpc); + } + } + jpc = null; + delete floatingEndpoint; + delete inPlaceCopy; + self.paint(); + } + ); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + }; + + // connector target + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend({}, dropOptions); + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + dropOptions[dropEvent]= _wrap(dropOptions[dropEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable), "dragId"); + var elId = _getAttribute(_getElementObject(draggable),"elId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // remove this jpc from the current endpoint + jpc.endpoints[idx].removeConnection(jpc); + if (jpc.suspendedEndpoint) + jpc.suspendedEndpoint.removeConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + _initDraggableIfNecessary(_element, _elementId, params.draggable, {}); + jsPlumb.repaint(elId); + + delete floatingConnections[id]; + }); + + dropOptions[overEvent]= _wrap(dropOptions[overEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + }); + + jsPlumb.CurrentLibrary.initDroppable(_getElementObject(self.canvas), dropOptions); + } + + return self; + }; + + /* + Class: jsPlumb + + This is the main entry point to jsPlumb. There are a bunch of static methods you can + use to connect/disconnect etc. Some methods are also added to jQuery's "$.fn" object itself, but + in general jsPlumb is moving away from that model, preferring instead the static class + approach. One of the reasons for this is that with no methods added to the jQuery $.fn object, + it will be easier to provide support for other libraries such as MooTools. + */ + var jsPlumb = window.jsPlumb = { + + /* + Property: Defaults + + These are the default settings for jsPlumb, that is what will be used if you do not supply specific pieces of information + to the various API calls. A convenient way to implement your own look and feel can be to override these defaults by including a script + somewhere after the jsPlumb include, but before you make any calls to jsPlumb, for instance in this example we set the PaintStyle to be + a blue line of 27 pixels: + + > jsPlumb.Defaults.PaintStyle = { lineWidth:27, strokeStyle:'blue' } + + */ + Defaults : { + Anchors : [ null, null ], + Connector : null, + DragOptions: { }, + DropOptions: { }, + Endpoint : null, + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : null }, + EndpointStyles : [ null, null ], + MaxConnections : null, + PaintStyle : { lineWidth : 10, strokeStyle : 'red' }, + Scope : "_jsPlumb_DefaultScope" + }, + + /* + Property: connectorClass + + The CSS class to set on Connection canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + connectorClass : '_jsPlumb_connector', + + /* + Property: endpointClass + + The CSS class to set on Endpoint canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + endpointClass : '_jsPlumb_endpoint', + + /* + Property: Anchors + + Default jsPlumb Anchors. These are supplied in the file jquery.jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form jquery.jsPlumb-all-x.x.x.js. You can provide your own Anchors by supplying them in a script that is loaded after jsPlumb, for instance: + + > jsPlumb.Anchors.MyAnchor = { ....anchor code here. see the documentation. } + */ + Anchors : {}, + + /* + Property: Connectors + + Default jsPlumb Connectors. These are supplied in the file jquery.jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form jquery.jsPlumb-all-x.x.x.js. You can provide your own Connectors by supplying them in a script that is loaded after jsPlumb, for instance: + + > jsPlumb.Connectors.MyConnector = { ....connector code here. see the documentation. } + */ + Connectors : {}, + + /* + Property: Endpoints + + Default jsPlumb Endpoints. These are supplied in the file jquery.jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form jquery.jsPlumb-all-x.x.x.js. You can provide your own Endpoints by supplying them in a script that is loaded after jsPlumb, for instance: + + > jsPlumb.Endpoints.MyEndpoint = { ....endpoint code here. see the documentation. } + */ + Endpoints : {}, + + /* + Function: addEndpoint + + Adds an Endpoint to a given element. + + Parameters: + target - Element to add the endpoint to. either an element id, or a jQuery object representing some element. + params - Object containing Endpoint options (more info required) + + Returns: + + The newly created Endpoint. + + See Also: + + + */ + addEndpoint : function(target, params) { + params = jsPlumb.extend({}, params); + var el = _getElementObject(target); + var id = _getAttribute(target,"id"); + params.source = el; + _updateOffset(id); + var e = new Endpoint(params); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id]; + var myWH = sizes[id]; + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH); + e.paint(anchorLoc); + return e; + }, + + /* + Function: addEndpoint + + Adds a list of Endpoints to a given element. + + Parameters: + + target - element to add the endpoint to. either an element id, or a jQuery object representing some element. + endpoints - List of objects containing Endpoint options. one Endpoint is created for each entry in this list. + + Returns: + + List of newly created Endpoints, one for each entry in the 'endpoints' argument. + + See Also: + + + */ + addEndpoints : function(target, endpoints) { + var results = []; + for (var i = 0; i < endpoints.length; i++) { + results.push(jsPlumb.addEndpoint(target, endpoints[i])); + } + return results; + }, + + /* + Function: animate + + Wrapper around standard jQuery animate function; injects a call to jsPlumb in the 'step' function (creating it if necessary). + This only supports the two-arg version of the animate call in jQuery - the one that takes an 'options' object as the second arg. + + Parameters: + + el - Element to animate. Either an id, or a jQuery object representing the element. + properties - The 'properties' argument you want passed to the standard jQuery animate call. + options - The 'options' argument you want passed to the standard jQuery animate call. + + Returns: + + void + */ + animate : function(el, properties, options) { + var ele = _getElementObject(el); + var id = _getAttribute(el,"id"); + //TODO this is not agnostic yet. + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + options[stepFunction] = _wrap(options[stepFunction], function() + { + jsPlumb.repaint(id); + }); + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }, + + /* + Function: connect + + Establishes a connection between two elements. + + Parameters: + + params - Object containing setup for the connection. see documentation. + + Returns: + + The newly created Connection. + + */ + connect : function(params) { + if (params.sourceEndpoint && params.sourceEndpoint.isFull()) { + _log("could not add connection; source endpoint is full"); + return; + } + + if (params.targetEndpoint && params.targetEndpoint.isFull()) { + _log("could not add connection; target endpoint is full"); + return; + } + + var jpc = new Connection(params); + + // register endpoints for the element. todo: is the test below sufficient? or should we test if the endpoint is already in the list, + // and add it only then? perhaps _addToList could be overloaded with a a 'test for existence first' parameter? + //_addToList(endpointsByElement, jpc.sourceId, jpc.endpoints[0]); + //_addToList(endpointsByElement, jpc.targetId, jpc.endpoints[1]); + + if (!params.sourceEndpoint) _addToList(endpointsByElement, jpc.sourceId, jpc.endpoints[0]); + if (!params.targetEndpoint) _addToList(endpointsByElement, jpc.targetId, jpc.endpoints[1]); + + jpc.endpoints[0].addConnection(jpc); + jpc.endpoints[1].addConnection(jpc); + + // force a paint + _draw(jpc.source); + + return jpc; + + }, + + /** + * not implemented yet. params object will have sourceEndpoint and targetEndpoint members; these will be Endpoints. + connectEndpoints : function(params) { + var jpc = Connection(params); + + },*/ + + /* + Function: detach + + Removes a connection. + + Parameters: + + sourceId - Id of the first element in the connection. A String. + targetId - iI of the second element in the connection. A String. + + Returns: + + true if successful, false if not. + */ + detach : function(sourceId, targetId) { + var f = function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + return true; + } + }; + + // todo: how to cleanup the actual storage? a third arg to _operation? + _operation(sourceId, f); + }, + + /* + Function: detachAll + + Removes all an element's connections. + + Parameters: + + el - either the id of the element, or a jQuery object for the element. + + Returns: + + void + */ + detachAll : function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + var c = endpoints[i].connections.length; + if (c > 0) { + for (var j = 0; j < c; j++) { + var jpc = endpoints[i].connections[0]; + _removeElement(jpc.canvas); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + } + } + } + } + }, + + /* + Function: detachEverything + + Remove all Connections from all elements, but leaves Endpoints in place. + + Returns: + + void + + See Also: + + + */ + detachEverything : function() { + for(var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + var c = endpoints[i].connections.length; + if (c > 0) { + for (var j = 0; j < c; j++) { + var jpc = endpoints[i].connections[0]; + _removeElement(jpc.canvas); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + } + } + } + } + } + }, + + extend : function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }, + + /* + Function: hide + + Sets an element's connections to be hidden. + + Parameters: + + el - either the id of the element, or a jQuery object for the element. + + Returns: + + void + */ + hide : function(el) { + _setVisible(el, "none"); + }, + + /* + Function: makeAnchor + + Creates an anchor with the given params. + + Parameters: + + x - the x location of the anchor as a fraction of the total width. + y - the y location of the anchor as a fraction of the total height. + xOrientation - value indicating the general direction a connection from the anchor should go in, in the x direction. + yOrientation - value indicating the general direction a connection from the anchor should go in, in the y direction. + xOffset - a fixed offset that should be applied in the x direction that should be applied after the x position has been figured out. optional. defaults to 0. + yOffset - a fixed offset that should be applied in the y direction that should be applied after the y position has been figured out. optional. defaults to 0. + + Returns: + + The newly created Anchor. + */ + makeAnchor : function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards compatibility if we are given only one value we assume it's a call in the old form. + var params = {}; + if (arguments.length == 1) jsPlumb.extend(params, x); + else { + params = {x:x, y:y}; + if (arguments.length >= 4) { + params.orientation = [arguments[2], arguments[3]]; + } + if (arguments.length == 6) params.offsets = [arguments[4], arguments[5]]; + } + return new Anchor(params); + }, + + + /* + Function: repaint + + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + + el - either the id of the element or a jQuery object representing the element. + + Returns: + + void + + See Also: + + + */ + repaint : function(el) { + + var _processElement = function(el) { + var ele = _getElementObject(el); + _draw(ele); + }; + + // TODO: support a jQuery result object too! + + // support both lists... + if (typeof el =='object') { + for (var i = 0; i < el.length; i++) + _processElement(el[i]); + } // ...and single strings. + else _processElement(el); + }, + + /* + Function: repaintEverything + + Repaints all connections. + + Returns: + + void + + See Also: + + + */ + repaintEverything : function() { + for (var elId in endpointsByElement) { + _draw(_getElementObject(elId)); + } + }, + + /* + Function: removeAllEndpoints + + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + + el - either an element id, or a jQuery object for an element. + + Returns: + + void + + See Also: + + + */ + removeAllEndpoints : function(el) { + var elId = _getAttribute(el, "id"); + // first remove all Connections. + jsPlumb.detachAll(elId); + var ebe = endpointsByElement[elId]; + for (var i in ebe) { + _removeElement(ebe[i].canvas); + } + endpointsByElement[elId] = []; + }, + + /* + Function: removeEndpoint + + Removes the given Endpoint from the given element. + + Parameters: + + el - either an element id, or a jQuery object for an element. + endpoint - Endpoint to remove. this is an Endpoint object, such as would have been returned from a call to addEndpoint. + + Returns: + + void + + See Also: + + + */ + removeEndpoint : function(el, endpoint) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + if (ebe) { + if(_removeFromList(endpointsByElement, elId, endpoint)) + _removeElement(endpoint.canvas); + } + }, + + /* + Function: setAutomaticRepaint + + Sets/unsets automatic repaint on window resize. + + Parameters: + + value - whether or not to automatically repaint when the window is resized. + + Returns: + + void + */ + setAutomaticRepaint : function(value) { + automaticRepaint = value; + }, + + /* + Function: setDefaultNewCanvasSize + + Sets the default size jsPlumb will use for a new canvas (we create a square canvas so one value is all that is required). + This is a hack for IE, because ExplorerCanvas seems to need for a canvas to be larger than what you are going to draw on + it at initialisation time. The default value of this is 1200 pixels, which is quite large, but if for some reason you're + drawing connectors that are bigger, you should adjust this value appropriately. + + Parameters: + + size - The default size to use. jsPlumb will use a square canvas so you need only supply one value. + + Returns: + + void + */ + setDefaultNewCanvasSize : function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }, + + /* + Function: setDraggable + + Sets whether or not a given element is draggable, regardless of what any plumb command may request. + + Parameters: + + el - either the id for the element, or a jQuery object representing the element. + + Returns: + + void + */ + setDraggable: _setDraggable, + + /* + Function: setDraggableByDefault + + Sets whether or not elements are draggable by default. Default for this is true. + + Parameters: + draggable - value to set + + Returns: + + void + */ + setDraggableByDefault: function(draggable) { + _draggableByDefault = draggable; + }, + + setDebugLog: function(debugLog) { + log = debugLog; + }, + + /* + Function: setRepaintFunction + + Sets the function to fire when the window size has changed and a repaint was fired. + + Parameters: + f - Function to execute. + + Returns: + void + */ + setRepaintFunction : function(f) { + repaintFunction = f; + }, + + /* + Function: show + + Sets an element's connections to be visible. + + Parameters: + el - either the id of the element, or a jQuery object for the element. + + Returns: + void + */ + show : function(el) { + _setVisible(el, "block"); + }, + + /* + Function: sizeCanvas + + Helper to size a canvas. You would typically use this when writing your own Connector or Endpoint implementation. + + Parameters: + x - [int] x position for the Canvas origin + y - [int] y position for the Canvas origin + w - [int] width of the canvas + h - [int] height of the canvas + + Returns: + void + */ + sizeCanvas : function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; canvas.height = h; + canvas.style.width = w + "px"; canvas.width = w; + canvas.style.left = x + "px"; canvas.style.top = y + "px"; + } + }, + + /** + * gets some test hooks. nothing writable. + */ + getTestHarness : function() { + return { + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + + findIndex : _findIndex + }; + }, + + /** + * Toggles visibility of an element's connections. kept for backwards compatibility + */ + toggle : _toggleVisible, + + /* + Function: toggleVisible + + Toggles visibility of an element's connections. + + Parameters: + el - either the element's id, or a jQuery object representing the element. + + Returns: + void, but should be updated to return the current state + */ + //TODO: update this method to return the current state. + toggleVisible : _toggleVisible, + + /* + Function: toggleDraggable + + Toggles draggability (sic) of an element's connections. + + Parameters: + el - either the element's id, or a jQuery object representing the element. + + Returns: + The current draggable state. + */ + toggleDraggable : _toggleDraggable, + + /* + Function: unload + + Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element + + Returns: + void + */ + unload : function() { + delete endpointsByElement; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + }, + + /* + Function: wrap + Helper method to wrap an existing function with one of your own. This is used by the various + implementations to wrap event callbacks for drag/drop etc; it allows jsPlumb to be + transparent in its handling of these things. If a user supplies their own event callback, + for anything, it will always be called. + Parameters: + + */ + wrap : _wrap + }; + +})(); \ No newline at end of file diff --git a/archive/jsPlumb-defaults-1.2-RC1.js b/archive/jsPlumb-defaults-1.2-RC1.js new file mode 100644 index 000000000..179bfab03 --- /dev/null +++ b/archive/jsPlumb-defaults-1.2-RC1.js @@ -0,0 +1,406 @@ +/** +* jsPlumb-defaults-1.2-RC1 +* +* This script contains the default Anchors, Endpoints and Connectors for jsPlumb. It should be used with jsPlumb 1.1.0 and above; +* prior to version 1.1.0 of jsPlumb the defaults were included inside the main script. +* +* Version 1.1.1 of this script adds the Triangle Endpoint, written by __________ and featured on this demo: +* +* http://www.mintdesign.ru/blog/javascript-jsplumb-making-graph +* http://www.mintdesign.ru/site/samples/jsplumb/jsPlumb-graph-sample.htm +* +* NOTE: for production usage you should use jsPlumb-all-1.1.1-min.js, which contains the main jsPlumb script and this script together, +* in a minified file. +*/ + +(function() { + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * getOrientation : function() { return [ox, oy]; } + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + jsPlumb.Anchors.TopCenter = jsPlumb.makeAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors.BottomCenter = jsPlumb.makeAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors.LeftMiddle = jsPlumb.makeAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors.RightMiddle = jsPlumb.makeAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors.Center = jsPlumb.makeAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors.TopRight = jsPlumb.makeAnchor(1, 0, 0,-1); + jsPlumb.Anchors.BottomRight = jsPlumb.makeAnchor(1, 1, 0, 1); + jsPlumb.Anchors.TopLeft = jsPlumb.makeAnchor(0, 0, 0, -1); + jsPlumb.Anchors.BottomLeft = jsPlumb.makeAnchor(0, 1, 0, 1); + + + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (w < 2 * lineWidth) { + w = 2 * lineWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - lineWidth; + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < 2 * lineWidth) { + // minimum size is 2 * line Width + h = 2 * lineWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - lineWidth; + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + var sx = sourcePos[0] < targetPos[0] ? w-xo : xo; + var sy = sourcePos[1] < targetPos[1] ? h-yo : yo; + var tx = sourcePos[0] < targetPos[0] ? xo : w-xo; + var ty = sourcePos[1] < targetPos[1] ? yo : h-yo; + var retVal = [ x, y, w, h, sx, sy, tx, ty ]; + + return retVal; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + }; + + /** + * This Connector draws a Bezier curve with two control points. + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + jsPlumb.Connectors.Bezier = function(curviness) { + + var self = this; + this.majorAnchor = curviness || 150; + this.minorAnchor = 10; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) + { + lineWidth = lineWidth || 0; + var w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth, h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + var canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2), canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + var sx = sourcePos[0] < targetPos[0] ? w - (lineWidth/2): (lineWidth/2), sy = sourcePos[1] < targetPos[1] ? h-(lineWidth/2) : (lineWidth/2); + var tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : w-(lineWidth/2), ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : h-(lineWidth/2); + var CP = self._findControlPoint([sx,sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + var CP2 = self._findControlPoint([tx,ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(sx,tx); var minx2 = Math.min(CP[0], CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(sx,tx); var maxx2 = Math.max(CP[0], CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > w) w = maxx; + if (minx < 0) { + canvasX += minx; var ox = Math.abs(minx); + w += ox; CP[0] += ox; sx += ox; tx +=ox; CP2[0] += ox; + } + + var miny1 = Math.min(sy,ty); var miny2 = Math.min(CP[1], CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(sy,ty); var maxy2 = Math.max(CP[1], CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > h) h = maxy; + if (miny < 0) { + canvasY += miny; var oy = Math.abs(miny); + h += oy; CP[1] += oy; sy += oy; ty +=oy; CP2[1] += oy; + } + + // return [ canvasx, canvasy, canvasWidth, canvasHeight, + // sourceX, sourceY, targetX, targetY, + // controlPoint1_X, controlPoint1_Y, controlPoint2_X, controlPoint2_Y + return [canvasX, canvasY, w, h, sx, sy, tx, ty, CP[0], CP[1], CP2[0], CP2[1] ]; + }; + + this.paint = function(d, ctx) { + + /*var img = new Image(); + img.src = "../img/pattern.jpg"; + ctx.fillStyle = ctx.createPattern(img, 'repeat-y');*/ + + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + //ctx.fill(); + } + }; + + + /** + * Types of endpoint UIs. we supply four - a circle of default radius 10px, a rectangle of + * default size 20x20, an image (with no default), and a Triangle, of default size 15. + * you can supply others of these if you want to - see the documentation for a howto. + */ + + /** + * a round endpoint, with default radius 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + } + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * A Rectangular endpoint, with default size 20x20. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + //todo: the fillStyle needs some thought. we want to support a few options: + // 1. nothing supplied; use the stroke color or the default if no stroke color. + // 2. a fill color supplied - use it + // 3. a gradient supplied - use it + // 4. setting the endpoint to the same color as the bg of the element it is attached to. + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + jsPlumb.Endpoints.Triangle = function(params) { + + params = params || { width:15, height:15 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - width/2; + var y = anchorPoint[1] - height/2; + + jsPlumb.sizeCanvas(canvas, x, y, width, height); + + var ctx = canvas.getContext('2d'); + var + offsetX = 0, + offsetY = 0, + angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = endpointStyle.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + ctx.fill(); + + }; + }; + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' property in the params object.. + */ + jsPlumb.Endpoints.Image = function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + }; +})(); \ No newline at end of file diff --git a/archive/jsPlumb-tests.js b/archive/jsPlumb-tests.js new file mode 100644 index 000000000..b5991d6d3 --- /dev/null +++ b/archive/jsPlumb-tests.js @@ -0,0 +1,1109 @@ + +var _getContextNode = function() { + return $("#container"); +}; + +var assertContextExists = function() { + ok(_getContextNode().length == 1, 'context node exists'); +}; + +var assertContextSize = function(elementCount) { + equals(_getContextNode().children().length, elementCount, 'context has ' + elementCount + ' children'); +}; + +var assertContextEmpty = function() { + equals(_getContextNode().children.length, 0, 'context empty'); +}; + +var assertEndpointCount = function(elId, count) { + equals(jsPlumb.getTestHarness().endpointCount(elId), count, elId + " has " + count + (count > 1) ? "endpoints" : "endpoint"); +}; + +var assertConnectionCount = function(endpoint, count) { + equals(endpoint.connections.length, count, "endpoint has " + count + " connections"); +}; + +var assertConnectionByScopeCount = function(scope, count) { + equals(jsPlumb.getTestHarness().connectionCount(scope), count, 'Scope ' + scope + " has " + count + (count > 1) ? "connections" : "connection"); + +}; + +var _divs = []; +var _addDiv = function(id) { + var d1 = document.createElement("div"); + document.body.appendChild(d1); + $(d1).attr("id", id); + _divs.push(id); + return $(d1); +}; + +var _cleanup = function() { + for (var i in _divs) { + $("#" + _divs[i]).remove(); + } + _divs.splice(0, _divs.length - 1); + + jsPlumb.reset(); +}; + +$(document).ready(function() { + +module("jsPlumb", {teardown: _cleanup}); + +// setup the container +var container = document.createElement("div"); +container.id = "container"; +document.body.appendChild(container); +jsPlumb.Defaults.Container = "container"; + +test('findIndex method', function() { + var array = [ 1,2,3, "test", "a string", { 'foo':'bar', 'baz':1 }, { 'ding':'dong' } ]; + equals(jsPlumb.getTestHarness().findIndex(array, 1), 0, "find works for integers"); + equals(jsPlumb.getTestHarness().findIndex(array, "test"), 3, "find works for strings"); + equals(jsPlumb.getTestHarness().findIndex(array, { 'foo':'bar', 'baz':1 }), 5, "find works for objects"); + equals(jsPlumb.getTestHarness().findIndex(array, { 'ding':'dong', 'baz':1 }), -1, "find works properly for objects (objects have different length but some same properties)"); +}); + +test('jsPlumb setup', function() { + ok(jsPlumb, "loaded"); + ok($.fn.plumb, "plumb function"); + ok($.fn.detach, "detach"); + ok($.fn.detachAll, "detachAll"); + ok($.fn.addEndpoint, "addEndpoint"); + ok($.fn.addEndpoints, "addEndpoints"); +}); + +test('getId', function() { + var d10 = _addDiv('d10'); + equals(jsPlumb.getTestHarness().getId(d10), "d10"); +}); + +test('plumb two divs with default options', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + $(d2).plumb({target:"d1"}); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertContextSize(3); // two endpoint canvases and a connection. + assertEndpointCount("d1", 1); + assertEndpointCount("d2", 1); +}); + +test('create a simple endpoint', function() { + var d1 = _addDiv("d1"); + var e = $("#d1").addEndpoint({}); + ok(e, 'endpoint exists'); + assertEndpointCount("d1", 1); +}); + +test('create and remove a simple endpoint', function() { + var d1 = _addDiv("d1"); + var ee = $("#d1").addEndpoint({uuid:"78978597593"}); + ok(ee != null, "endpoint exists"); + assertEndpointCount("d1", 1); + assertContextSize(1); // one Endpoint canvas. + jsPlumb.removeEndpoint(d1, ee); + assertEndpointCount("d1", 0); + var e = jsPlumb.getEndpoint("78978597593"); + equals(e, null, "the endpoint has been deleted"); + assertContextSize(0); // no Endpoint canvases. +}); + +test('draggable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1"); + var e = jsPlumb.addEndpoint(d1, {isSource:true}); + ok(e, 'endpoint exists'); +}); + +test('droppable silently ignored when jquery ui not present', function() { + var d1 = _addDiv("d1") + var e = jsPlumb.addEndpoint(d1, {isTarget:true}); + ok(e, 'endpoint exists'); +}); + +test('defaultEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = jsPlumb.addEndpoint(d3, {isSource:true}); + ok(e3.anchor, 'endpoint 3 has an anchor'); + var e4 = jsPlumb.addEndpoint(d4, {isSource:true}); + assertEndpointCount("d3", 1); + assertEndpointCount("d4", 1); + assertContextSize(2); // one canvas for each of our two endpoints. + ok(!e3.isFull(), "endpoint 3 is not full."); + jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // we have one connection + jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 1); // should have refused the connection; default max is 1. + assertContextSize(3); // now we have one more canvas, for the Connection that was accepted. +}); + +test('specifiedEndpointMaxConnections', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = jsPlumb.addEndpoint(d5, {isSource:true, maxConnections:3}); + ok(e5.anchor, 'endpoint 5 has an anchor'); + var e6 = jsPlumb.addEndpoint(d6, {isSource:true, maxConnections:2}); // this one has max TWO + assertEndpointCount("d5", 1); assertEndpointCount("d6", 1); + assertContextSize(2); + ok(!e5.isFull(), "endpoint 5 is not full."); + jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 1); // we have one connection + assertContextSize(3); + jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // two connections + assertContextSize(4); + jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6}); + assertConnectionCount(e5, 2); // should have refused; max is 2, for d4. + assertContextSize(4); +}); + +test('noEndpointMaxConnections', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + var e3 = jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + var e4 = jsPlumb.addEndpoint(d4, {isSource:true, maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertContextSize(3); + assertConnectionCount(e3, 1); // we have one connection + jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4}); + assertConnectionCount(e3, 2); // we have two. etc (default was one. this proves max is working). + assertContextSize(4); + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + var e5 = jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e4}); + assertConnectionCount(e4, 3); + assertContextSize(6); +}); + +test('anchors equal', function() { + var a1 = jsPlumb.makeAnchor(0, 1, 1, 1); + var a2 = jsPlumb.makeAnchor(0, 1, 1, 1); + ok(a1.equals(a2), "anchors are the same"); +}); + +test('anchors equal with offsets', function() { + var a1 = jsPlumb.makeAnchor(0, 1, 1, 1, 10, 13); + var a2 = jsPlumb.makeAnchor(0, 1, 1, 1, 10, 13); + ok(a1.equals(a2), "anchors are the same"); +}); + +test('anchors not equal', function() { + var a1 = jsPlumb.makeAnchor(0, 1, 0, 1); + var a2 = jsPlumb.makeAnchor(0, 1, 1, 1); + ok(!a1.equals(a2), "anchors are different"); +}); + +test('anchor not equal with offsets', function() { + var a1 = jsPlumb.makeAnchor(0, 1, 1, 1, 10, 13); + var a2 = jsPlumb.makeAnchor(0, 1, 1, 1); + ok(!a1.equals(a2), "anchors are different"); +}); + +test('detach plays nice when no target given', function() { + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + jsPlumb.connect({source:d3, target:d4}); + jsPlumb.detach(); +}); + +//TODO make sure you run this test with a single detach call, to ensure that +// single detach calls result in the connection being removed. detachEverything can +// just blow away the connectionsByScope array and recreate it. +test('jsPlumb.getConnections (simple case, default scope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + jsPlumb.connect({source:d5, target:d6}); + assertContextSize(3); + var c = jsPlumb.getConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 1, "there is one connection"); +}); + +test('jsPlumb.getConnections (simple case, default scope; detach by element id)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + jsPlumb.connect({source:d5, target:d6}); + jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 2, "there are two connections initially"); + jsPlumb.detach('d5', 'd6'); + c = jsPlumb.getConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); +}); + +test('jsPlumb.getConnections (simple case, default scope; detach by element id using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + jsPlumb.connect({source:d5, target:d6}); + jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 2, "there are two connections initially"); + jsPlumb.detach({source:'d5', target:'d6'}); + c = jsPlumb.getConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); +}); + +test('jsPlumb.getConnections (simple case, default scope; detach by element object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + jsPlumb.connect({source:d5, target:d6}); + jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 2, "there are two connections initially"); + jsPlumb.detach(d5, d6); + c = jsPlumb.getConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); +}); + +test('jsPlumb.getConnections (simple case, default scope; detach by element object using params object)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + jsPlumb.connect({source:d5, target:d6}); + jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 2, "there are two connections initially"); + jsPlumb.detach({source:d5, target:d6}); + c = jsPlumb.getConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 1, "after detaching one, there is now one connection."); + assertContextSize(5); +}); + +test('jsPlumb.getConnections (simple case, default scope; detach by Connection)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7"); + var c56 = jsPlumb.connect({source:d5, target:d6}); + var c67 = jsPlumb.connect({source:d6, target:d7}); + assertContextSize(6); + var c = jsPlumb.getConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 2, "there are two connections initially"); + jsPlumb.detach(c56); + assertContextSize(5); // check that the connection canvas was removed. + c = jsPlumb.getConnections(); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 1, "after detaching one, there is now one connection."); + +}); + +test('jsPlumb.getConnections (scope testScope)', function() { + var d5 = _addDiv("d5"), d6 = _addDiv("d6"); + jsPlumb.connect({source:d5, target:d6, scope:'testScope'}); + var c = jsPlumb.getConnections(); // will get all connections + equals(c['testScope'].length, 1, "there is one connection"); + equals(c['testScope'][0].sourceId, 'd5', "the connection's source is d5"); + equals(c['testScope'][0].targetId, 'd6', "the connection's source is d6"); +}); + +test('jsPlumb.getConnections (filtered by scope)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + jsPlumb.connect({source:d9, target:d10}); // default scope + var c = jsPlumb.getConnections({scope:'testScope'}); // will get all connections + equals(c[jsPlumb.getDefaultScope()], null); + equals(c['testScope'].length, 1); + // now supply a list of scopes + c = jsPlumb.getConnections({scope:[jsPlumb.getDefaultScope(),'testScope']}); // will get all connections + equals(c[jsPlumb.getDefaultScope()].length, 1); + equals(c['testScope'].length, 1, "there is one connection in 'testScope'"); +}); + +test('jsPlumb.getConnections (filtered by scope and sourceId)', function() { + var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10'); + jsPlumb.connect({source:d8, target:d9, scope:'testScope'}); + jsPlumb.connect({source:d9, target:d8, scope:'testScope'}); + jsPlumb.connect({source:d9, target:d10}); // default scope + var c = jsPlumb.getConnections({scope:'testScope', source:'d8'}); // will get all connections with sourceId 'd8' + equals(c[jsPlumb.getDefaultScope()], null); + equals(c['testScope'].length, 1, "there is one connection in 'testScope' from d8"); +}); + +test('jsPlumb.getConnections (filtered by scope, source id and target id)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d12, target:d13, scope:'testScope'}); + jsPlumb.connect({source:d11, target:d13, scope:'testScope'}); + var c = jsPlumb.getConnections({scope:'testScope', source:'d11', target:'d13'}); + equals(c['testScope'].length, 1, "there is one connection from d11 to d13"); +}); + +test('jsPlumb.getConnections (filtered by a list of scopes)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = jsPlumb.getConnections({scope:['testScope','testScope3']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); +}); + +test('jsPlumb.getConnections (filtered by a list of scopes and source ids)', function() { + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'); + jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + var c = jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11']}); + equals(c['testScope'].length, 1, 'there is one connection in testScope'); + equals(c['testScope3'].length, 1, 'there is one connection in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); +}); + +test('jsPlumb.getConnections (filtered by a list of scopes, source ids and target ids)', function() { + jsPlumb.detachEverything(); + var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'), d14=_addDiv("d14"), d15=_addDiv("d15"); + jsPlumb.connect({source:d11, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d13, target:d12, scope:'testScope'}); + jsPlumb.connect({source:d11, target:d14, scope:'testScope'}); + jsPlumb.connect({source:d11, target:d15, scope:'testScope'}); + jsPlumb.connect({source:d12, target:d13, scope:'testScope2'}); + jsPlumb.connect({source:d11, target:d13, scope:'testScope3'}); + assertContextSize(18); + var c = jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11'], target:['d14', 'd15']}); + equals(c['testScope'].length, 2, 'there are two connections in testScope'); + equals(c['testScope3'].length, 0, 'there is no connections in testScope3'); + equals(c['testScope2'], null, 'there are no connections in testScope2'); + var anEntry = c['testScope'][0]; + ok(anEntry.sourceEndpoint != null, "Source endpoint is set"); + ok(anEntry.targetEndpoint != null, "Target endpoint is set"); + equals(anEntry.source.attr("id"), "d11", "Source is div d11"); + equals(anEntry.target.attr("id"), "d14", "Target is div d14"); +}); + +test('connection event listeners', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null/*, returnedParams2 = null*/; + jsPlumb.addListener(["jsPlumbConnection"], { + // signature of the 'interface' method is jsPlumbConnection. params + // has source, target, sourceId, targetId, sourceEndpoint, targetEndpoint + jsPlumbConnection : function(params) { + returnedParams = $.extend({}, params); + } + }); + jsPlumb.connect({source:d1, target:d2}); + ok(returnedParams != null, "new connection listener event was fired"); + equals(returnedParams.sourceId, "d1", 'sourceid is set'); + equals(returnedParams.targetId, "d2", 'targetid is set'); + equals(returnedParams.source.attr("id"), "d1", 'source is set'); + equals(returnedParams.target.attr("id"), "d2" , 'target is set'); + ok(returnedParams.sourceEndpoint != null, "source endpoint is not null"); + ok(returnedParams.targetEndpoint != null, "target endpoint is not null"); + //jsPlumb.detachAll("d1"); + //ok(returnedParams2 != null, "removed connection listener event was fired"); +}); + +test('detach event listeners (detach by connection)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + jsPlumb.addListener(["jsPlumbConnectionDetached"], { + jsPlumbConnectionDetached : function(params) { + returnedParams = $.extend({}, params); + } + }); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); +}); + +test('detach event listeners (detach by element ids)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + jsPlumb.addListener(["jsPlumbConnectionDetached"], { + jsPlumbConnectionDetached : function(params) { + returnedParams = $.extend({}, params); + } + }); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach("d1","d2"); + ok(returnedParams != null, "removed connection listener event was fired"); +}); + +test('detach event listeners (detach by elements)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + jsPlumb.addListener(["jsPlumbConnectionDetached"], { + jsPlumbConnectionDetached : function(params) { + returnedParams = $.extend({}, params); + } + }); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach(d1,d2); + ok(returnedParams != null, "removed connection listener event was fired"); +}); + +test('detach event listeners (via Endpoint.detach method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + jsPlumb.addListener(["jsPlumbConnectionDetached"], { + jsPlumbConnectionDetached : function(params) { + returnedParams = $.extend({}, params); + } + }); + var conn = jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detach(conn); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); +}); + +test('detach event listeners (via Endpoint.detachFrom method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + jsPlumb.addListener(["jsPlumbConnectionDetached"], { + jsPlumbConnectionDetached : function(params) { + returnedParams = $.extend({}, params); + } + }); + jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachFrom(e2); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); +}); + +test('detach event listeners (via Endpoint.detachAll method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + jsPlumb.addListener(["jsPlumbConnectionDetached"], { + jsPlumbConnectionDetached : function(params) { + returnedParams = $.extend({}, params); + } + }); + jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + e1.detachAll(); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(2); +}); + +test('detach event listeners (via jsPlumb.deleteEndpoint method)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + var returnedParams = null; + jsPlumb.addListener(["jsPlumbConnectionDetached"], { + jsPlumbConnectionDetached : function(params) { + returnedParams = $.extend({}, params); + } + }); + jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2}); + assertContextSize(3); + jsPlumb.deleteEndpoint(e1); + ok(returnedParams != null, "removed connection listener event was fired"); + assertContextSize(1); +}); + +test('detach event listeners (ensure cleared by jsPlumb.reset)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null; + jsPlumb.addListener(["jsPlumbConnectionDetached"], { + jsPlumbConnectionDetached : function(params) { + returnedParams = $.extend({}, params); + } + }); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach(d1,d2); + ok(returnedParams != null, "removed connection listener event was fired"); + returnedParams = null; + + jsPlumb.reset(); + var conn = jsPlumb.connect({source:d1, target:d2}); + jsPlumb.detach(d1,d2); + ok(returnedParams == null, "connection listener was cleared by jsPlumb.reset()"); +}); + +test('connection events that throw errors', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var returnedParams = null, returnedParams2 = null; + jsPlumb.addListener(["jsPlumbConnection"], { + // signature of the 'interface' method is jsPlumbConnection. params + // has source, target, sourceId, targetId, sourceEndpoint, targetEndpoint + jsPlumbConnection : function(params) { + returnedParams = $.extend({}, params); + throw "oh no!"; + } + }); + jsPlumb.connect({source:d1, target:d2}); + var d3 = _addDiv("d3"), d4 = _addDiv("d4"); + jsPlumb.connect({source:d3, target:d4}); + ok(returnedParams != null, "new connection listener event was fired; we threw an error, jsPlumb survived."); +}); + +test("Endpoint.detachFrom", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + e16.detachFrom(e17); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); +}); + +test("Endpoint.detachFromConnection", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + e16.detachFromConnection(conn); + assertContextSize(3); // all canvases should remain; the connection was not removed. + // but endpoint e16 should have no connections now. + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 1); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); +}); + +test("Endpoint.detachAll", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); d18 = _addDiv("d18"); + var e16 = $("#d16").addEndpoint({isSource:true,maxConnections:-1}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = $("#d17").addEndpoint({isSource:true}); + var e18 = $("#d18").addEndpoint({isSource:true}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e18}); + assertConnectionCount(e16, 2); + assertConnectionCount(e17, 1); + assertConnectionCount(e18, 1); + assertContextSize(5); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 2); + e16.detachAll(); + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); +}); + +test("Endpoint.detach", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionCount(e16, 1); + assertConnectionCount(e17, 1); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + e16.detach(conn); + assertContextSize(2); // the endpoint canvases should remain + // but the connection should be gone, meaning not registered by jsPlumb and not registered on either Endpoint: + assertConnectionCount(e16, 0); + assertConnectionCount(e17, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); +}); + +test("Endpoint.isConnectedTo", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + var conn = jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.isConnectedTo(e17), true, "e16 and e17 are connected"); +}); + +test("setting endpoint uuid", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); d18 = _addDiv("d18"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + equals(e16.getUuid(), uuid, "endpoint's uuid was set correctly"); +}); + +test("jsPlumb.getEndpoint (by uuid)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); +}); + +test("jsPlumb.deleteEndpoint (by uuid, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + jsPlumb.deleteEndpoint(uuid); + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + var ebe = jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + assertContextSize(0); +}); + +test("jsPlumb.deleteEndpoint (by uuid, connections too)", function() { + // create two endpoints (one with a uuid), add them to two divs and then connect them. + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + // check that the connection was ok. + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + // delete the endpoint that has a uuid. verify that the endpoint cannot be retrieved and that the connection has been removed, but that + // element d17 still has its Endpoint. + jsPlumb.deleteEndpoint(uuid); + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + var ebe = jsPlumb.getTestHarness().endpointsByElement["d16"]; + equals(ebe.length, 0, "no endpoints registered for element d16 anymore"); + ebe = jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 1, "element d17 still has its Endpoint"); + assertContextSize(1); + + // now delete d17's endpoint and check that it has gone. + jsPlumb.deleteEndpoint(e17); + f = jsPlumb.getEndpoint(e17); + equals(f, null, "endpoint has been deleted"); + ebe = jsPlumb.getTestHarness().endpointsByElement["d17"]; + equals(ebe.length, 0, "element d17 no longer has any Endpoints"); + assertContextSize(0); +}); + +test("jsPlumb.deleteEndpoint (by reference, simple case)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + jsPlumb.deleteEndpoint(e16); + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + assertContextSize(0); +}); + +test("jsPlumb.deleteEndpoint (by reference, connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + equals(e16.connections.length, 1, "e16 has one connection"); + equals(e17.connections.length, 1, "e17 has one connection"); + assertContextSize(3); + + jsPlumb.deleteEndpoint(e16); + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint has been deleted"); + equals(e16.connections.length, 0, "e16 has no connections"); + equals(e17.connections.length, 0, "e17 has no connections"); + assertContextSize(1); +}); + +test("jsPlumb.deleteEveryEndpoint", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + assertContextSize(2); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + jsPlumb.deleteEveryEndpoint(); + + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); +}); + +test("jsPlumb.deleteEveryEndpoint (connections too)", function() { + var uuid = "14785937583175927504313"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid}); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertContextSize(3); + var e = jsPlumb.getEndpoint(uuid); + equals(e.getUuid(), uuid, "retrieved endpoint by uuid"); + + jsPlumb.deleteEveryEndpoint(); + + var f = jsPlumb.getEndpoint(uuid); + equals(f, null, "endpoint e16 has been deleted"); + var g = jsPlumb.getEndpoint(e17); + equals(g, null, "endpoint e17 has been deleted"); + assertContextSize(0); // no canvases. so all connection canvases have been cleaned up too. + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); +}); + +test('jsPlumb.connect (between two Endpoints)', function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e = jsPlumb.addEndpoint(d1, {}); + var e2 = jsPlumb.addEndpoint(d2, {}); + ok(e, 'endpoint e exists'); + ok(e2, 'endpoint e2 exists'); + assertContextSize(2); // should have a canvas for each endpoint now. + assertEndpointCount("d1", 1); + assertEndpointCount("d2", 1); + jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2}); + assertEndpointCount("d1", 1); // no new endpoint should have been added + assertEndpointCount("d2", 1); // no new endpoint should have been added + assertContextSize(3); // now we should also have a canvas for the connection. +}); + +test('jsPlumb.connect (by endpoint)', function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {isSource:true}); + ok(e16.anchor, 'endpoint 16 has an anchor'); + var e17 = jsPlumb.addEndpoint(d17, {isSource:true}); + jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17}); + assertContextSize(3); +}); + +test("jsPlumb.connect (two Endpoints - that have been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1, uuid:srcEndpointUuid}); + var e2 = jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1, uuid:dstEndpointUuid}); + jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ] }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertContextSize(3); +}); + +test("jsPlumb.connect (two Endpoints - that have not been already added - by UUID)", function() { + var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325"; + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + jsPlumb.connect({ uuids: [ srcEndpointUuid, dstEndpointUuid ], source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + var e1 = jsPlumb.getEndpoint(srcEndpointUuid); + ok(e1 != null, "endpoint with src uuid added"); + ok(e1.canvas != null); + var e2 = jsPlumb.getEndpoint(dstEndpointUuid); + ok(e2 != null, "endpoint with target uuid added"); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); +}); + +test("jsPlumb.connect (two Endpoints - that have been already added - by endpoint reference)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e1 = jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1}); + var e2 = jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1}); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); +}); + +test("jsPlumb.connect (two elements)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + jsPlumb.connect({ source:d16, target:d17 }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); +}); + +test("jsPlumb.connect (Connector test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:new jsPlumb.Connectors.Straight() }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors.Straight, "Straight connector chosen for connection"); +}); + +test("jsPlumb.connect (Connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.connector.constructor, jsPlumb.Connectors.Straight, "Straight connector chosen for connection"); +}); + +test("jsPlumb.connect (Endpoint test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoint:new jsPlumb.Endpoints.Rectangle() }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints.Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints.Rectangle, "Rectangle endpoint chosen for connection target"); +}); + +test("jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints.Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints.Rectangle, "Rectangle endpoint chosen for connection target"); +}); + +test("jsPlumb.connect (Endpoints test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoints:[new jsPlumb.Endpoints.Rectangle(),new jsPlumb.Endpoints.Dot()] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints.Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints.Dot, "Dot endpoint chosen for connection target"); +}); + +test("jsPlumb.connect (Endpoint as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var conn = jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints.Rectangle, "Rectangle endpoint chosen for connection source"); + equals(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints.Dot, "Dot endpoint chosen for connection target"); +}); + +test("jsPlumb.connect (by Endpoints, connector test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {}); + var e17 = jsPlumb.addEndpoint(d17, {}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:new jsPlumb.Connectors.Straight() }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors.Straight, "Straight connector chosen for connection"); +}); + +test("jsPlumb.connect (by Endpoints, connector as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {}); + var e17 = jsPlumb.addEndpoint(d17, {}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors.Straight, "Straight connector chosen for connection"); +}); + +test("jsPlumb.connect (by Endpoints, anchors as string test)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = "TopCenter", a17 = "BottomCenter"; + var e16 = jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors.Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, 0.5, "endpoint 16 is at top center");equals(e16.anchor.y, 0, "endpoint 16 is at top center"); + equals(e17.anchor.x, 0.5, "endpoint 17 is at bottom center");equals(e17.anchor.y, 1, "endpoint 17 is at bottom center"); +}); + +test("jsPlumb.connect (by Endpoints, endpoints create anchors)", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var a16 = [0,0.5,0,-1], a17 = [1,0.0,-1,-1]; + var e16 = jsPlumb.addEndpoint(d16, {anchor:a16}); + var e17 = jsPlumb.addEndpoint(d17, {anchor:a17}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors.Straight, "Straight connector chosen for connection"); + equals(e16.anchor.x, a16[0]);equals(e16.anchor.y, a16[1]); + equals(e17.anchor.x, a17[0]);equals(e17.anchor.y, a17[1]); + equals(e16.anchor.getOrientation()[0], a16[2]); equals(e16.anchor.getOrientation()[1], a16[3]); + equals(e17.anchor.getOrientation()[0], a17[2]); equals(e17.anchor.getOrientation()[1], a17[3]); +}); + +test("jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchor')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {anchor:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = jsPlumb.addEndpoint(d17, {anchor:["TopCenter", "BottomCenter"]}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors.Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); +}); + +test("jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchors')", function() { + var d16 = _addDiv("d16"), d17 = _addDiv("d17"); + var e16 = jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]}); + var e17 = jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]}); + var conn = jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" }); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + equals(e16.connections[0].connector.constructor, jsPlumb.Connectors.Straight, "Straight connector chosen for connection"); + equals(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor"); + equals(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor"); +}); + + +// this test is for the original detach function; it should stay working after i mess with it +// a little. +test("jsPlumb.detach (by element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + var e3 = jsPlumb.addEndpoint(d1); + var e4 = jsPlumb.addEndpoint(d2); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + jsPlumb.connect({ sourceEndpoint:e3, targetEndpoint:e4 }); // make two connections to be sure this works ;) + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + assertConnectionCount(e3, 1); + assertConnectionCount(e4, 1); + + jsPlumb.detach("d1", "d2"); + + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionCount(e3, 0); + assertConnectionCount(e4, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); +}); + +// detach is being made to operate more like connect - by taking one argument with a whole +// bunch of possible params in it. if two args are passed in it will continue working +// in the old way. +test("jsPlumb.detach (params object, using element ids)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + jsPlumb.detach({source:"d1", target:"d2"}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); +}); + +test("jsPlumb.detach (params object, using element objects)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + jsPlumb.detach({source:d1, target:d2}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + assertContextSize(2); +}); + +test("jsPlumb.detach (source and target as endpoint UUIDs)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1, {uuid:"abcdefg"}); + ok(jsPlumb.getEndpoint("abcdefg") != null, "e1 exists"); + var e2 = jsPlumb.addEndpoint(d2, {uuid:"hijklmn"}); + ok(jsPlumb.getEndpoint("hijklmn") != null, "e2 exists"); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + jsPlumb.detach({uuids:["abcdefg", "hijklmn"]}); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + assertContextSize(2); +}); + +test("jsPlumb.detach (sourceEndpoint and targetEndpoint supplied)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"); + var e1 = jsPlumb.addEndpoint(d1); + var e2 = jsPlumb.addEndpoint(d2); + jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertContextSize(3); + assertConnectionCount(e1, 1); + assertConnectionCount(e2, 1); + jsPlumb.detach({ sourceEndpoint:e1, targetEndpoint:e2 }); + assertConnectionCount(e1, 0); + assertConnectionCount(e2, 0); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 0); + assertContextSize(2); +}); + + +test("jsPlumb.connect (connect by element, default endpoint, supplied dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var anchors = [ [0.25, 0, 0, -1], [1, 0.25, 1, 0], [0.75, 1, 0, 1], [0, 0.75, -1, 0] ]; + jsPlumb.connect({source:d1, target:d2, dynamicAnchors:anchors}); // auto connect with default endpoint and provided anchors + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + assertContextSize(2); +}); + +test("jsPlumb.connect (connect by element, supplied endpoint and dynamic anchors)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var endpoint = { isSource:true }; + var e1 = jsPlumb.addEndpoint(d1, endpoint); + var e2 = jsPlumb.addEndpoint(d2, endpoint); + var anchors = [ "TopCenter", "BottomCenter" ]; + jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2, dynamicAnchors:anchors}); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + assertContextSize(3); + jsPlumb.detach({source:d1, target:d2}); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + assertContextSize(2); +}); + + +test("jsPlumb.connect (testing for connection event callback)", function() { + var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"); + var connectCallback = null, detachCallback = null; + jsPlumb.bind(["jsPlumbConnection", "jsPlumbConnectionDetached"], { + jsPlumbConnection : function(params) { + connectCallback = $.extend({}, params); + }, + jsPlumbConnectionDetached : function(params) { + detachCallback = $.extend({}, params); + } + }); + jsPlumb.connect({source:d1, target:d2}); // auto connect with default endpoint and anchor set + ok(connectCallback != null, "connect callback was made"); + assertContextSize(3); + assertConnectionByScopeCount(jsPlumb.getDefaultScope(), 1); + assertEndpointCount("d1", 1);assertEndpointCount("d2", 1); + jsPlumb.detach({source:d1, target:d2}); + assertContextSize(2); + ok(detachCallback != null, "detach callback was made"); +}); + +test("jsPlumb.makeDynamicAnchors (longhand)", function() { + var anchors = [jsPlumb.makeAnchor(0.2, 0, 0, -1), jsPlumb.makeAnchor(1, 0.2, 1, 0), + jsPlumb.makeAnchor(0.8, 1, 0, 1), jsPlumb.makeAnchor(0, 0.8, -1, 0) ]; + var dynamicAnchor = jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); +}); + +test("jsPlumb.makeDynamicAnchors (shorthand)", function() { + var anchors = [[0.2, 0, 0, -1], [1, 0.2, 1, 0], + [0.8, 1, 0, 1], [0, 0.8, -1, 0] ]; + var dynamicAnchor = jsPlumb.makeDynamicAnchor(anchors); + var a = dynamicAnchor.getAnchors(); + equals(a.length, 4, "Dynamic Anchors has four anchors"); + for (var i = 0; i < a.length; i++) + ok(a[i].compute.constructor == Function, "anchor " + i + " well formed"); +}); + +/** + * leave this test at the bottom! + */ +test('unload test', function() { + jsPlumb.unload(); + var contextNode = $("._jsPlumb_context"); + ok(contextNode.length == 0, 'context node unloaded'); +}); + + +}); \ No newline at end of file diff --git a/archive/mootools.jsPlumb-1.2-RC1.js b/archive/mootools.jsPlumb-1.2-RC1.js new file mode 100644 index 000000000..3e39f3bbc --- /dev/null +++ b/archive/mootools.jsPlumb-1.2-RC1.js @@ -0,0 +1,254 @@ +(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function() { + this.parent(); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}; + var _droppableOptions = {}; + var _draggablesByScope = {}; + var _draggablesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }; + + + jsPlumb.CurrentLibrary = { + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete' + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + getElementObject : function(el) { + return $(el); + }, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + initDraggable : function(el, options) { + var drag = _draggablesById[el.get("id")]; + if (!drag) { + var originalZIndex = 0, originalCursor = null; + var dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + options['onStart'] = jsPlumb.wrap(options['onStart'], function() + { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() + { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + }); + + // DROPPABLES: + var scope = options['scope'] || jsPlumb.Defaults.Scope; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop'); + } + }; + + drag = new Drag.Move(el, options); + _add(_draggablesByScope, scope, drag); + _add(_draggablesById, el.get("id"), drag); + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + initDroppable : function(el, options) { + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _add(_droppables, scope, el); + _droppableOptions[el.get("id")] = options; + var filterFunc = function(entry) { + return entry.element != el; + }; + var draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + if (typeof Drag != undefined) + return typeof Drag.Move != undefined; + return false; + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + m.start(properties); + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[0]; + return { left: ui.offsetLeft, top: ui.offsetTop }; + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + } + }; +})(); diff --git a/archive/mootools.jsPlumb-1.2-all-min.js b/archive/mootools.jsPlumb-1.2-all-min.js new file mode 100644 index 000000000..d4fc9d00e --- /dev/null +++ b/archive/mootools.jsPlumb-1.2-all-min.js @@ -0,0 +1,41 @@ +(function(){var u=/MSIE/.test(navigator.userAgent)&&!window.opera,s=null,f=function(){j.repaintEverything()},g=true,l={},k=[],h={},m={},d=true,e=[],p=1200,w=function(a,b,c){var i=function(q,t){if(q===t)return true;else if(typeof q=="object"&&typeof t=="object"){var v=true;for(var x in q)if(!i(q[x],t[x])){v=false;break}for(x in t)if(!i(t[x],q[x])){v=false;break}return v}};c=+c||0;for(var n=a.length;c=0){delete a[c];a.splice(c,1);return true}}return false},Y=function(a,b){var c=z(a,"id");U(c,function(i){i.canvas.style.display=b})},Z=function(a){U(a,function(b){b.canvas.style.display="none"==b.canvas.style.display?"block":"none"})},M=function(a,b,c){if(c||b==null){A(a);e[a]=V(a);b=j.CurrentLibrary.getElementObject(a); +b=j.CurrentLibrary.getOffset(b);k[a]=b}else k[a]=b},J=function(a,b){a=a||function(){};b=b||function(){};return function(){try{b.apply(this,arguments)}catch(c){}try{a.apply(this,arguments)}catch(i){}}},ba=function(a){var b=this;this.x=a.x||0;this.y=a.y||0;var c=a.orientation||[0,0];this.offsets=a.offsets||[0,0];this.compute=function(i,n){return[i[0]+b.x*n[0]+b.offsets[0],i[1]+b.y*n[1]+b.offsets[1]]};this.getOrientation=function(){return c}},ca=function(a){var b=a.reference,c=V(a.referenceCanvas),i= +0,n=0,q=null;this.compute=function(t){n=i=0;return[t[0]+c[0]/2,t[1]+c[1]/2]};this.getOrientation=function(){if(q)return q;else{var t=b.getOrientation();return[Math.abs(t[0])*i*-1,Math.abs(t[1])*n*-1]}};this.over=function(t){q=t.getOrientation()};this.out=function(){q=null}},aa=function(a){var b=this;this.source=A(a.source);this.target=A(a.target);this.sourceId=z(this.source,"id");this.targetId=z(this.target,"id");this.endpointsOnTop=a.endpointsOnTop!=null?a.endpointsOnTop:true;this.endpoints=[];this.endpointStyles= +[];var c=function(q,t,v){if(q)b.endpoints[t]=q;else{if(!v.endpoints)v.endpoints=[null,null];q=v.endpoints[t]||v.endpoint||j.Defaults.Endpoints[t]||j.Defaults.Endpoint||new j.Endpoints.Dot;if(!v.endpointStyles)v.endpointStyles=[null,null];b.endpoints[t]=new S({style:v.endpointStyles[t]||v.endpointStyle||j.Defaults.EndpointStyles[t]||j.Defaults.EndpointStyle,endpoint:q,connections:[b],anchor:v.anchors?v.anchors[t]:j.Defaults.Anchors[t]||j.Anchors.BottomCenter,source:b.source})}};c(a.sourceEndpoint, +0,a);c(a.targetEndpoint,1,a);this.connector=this.endpoints[0].connector||this.endpoints[1].connector||a.connector||j.Defaults.Connector||new j.Connectors.Bezier;this.paintStyle=this.endpoints[0].connectorStyle||this.endpoints[1].connectorStyle||a.paintStyle||j.Defaults.PaintStyle;M(this.sourceId);M(this.targetId);c=k[this.sourceId];var i=e[this.sourceId];c=this.endpoints[0].anchor.compute([c.left,c.top],i);this.endpoints[0].paint(c);c=k[this.targetId];i=e[this.targetId];c=this.endpoints[1].anchor.compute([c.left, +c.top],i);this.endpoints[1].paint(c);var n=W(j.connectorClass);this.canvas=n;this.paint=function(q,t,v){s&&s.debug("Painting Connection; element in motion is "+q+"; ui is ["+t+"]; recalc is ["+v+"]");var x=q!=this.sourceId,H=x?this.sourceId:this.targetId,y=x?0:1,r=x?1:0;if(this.canvas.getContext){M(q,t,v);v&&M(H);t=k[q];v=k[H];q=e[q];var P=e[H];H=n.getContext("2d");var K=this.endpoints[r].anchor.compute([t.left,t.top],q,[v.left,v.top],P);this.endpoints[r].anchor.getOrientation();t=this.endpoints[y].anchor.compute([v.left, +v.top],P,[t.left,t.top],q);this.endpoints[y].anchor.getOrientation();y=this.connector.compute(K,t,this.endpoints[r].anchor,this.endpoints[y].anchor,this.paintStyle.lineWidth);j.sizeCanvas(n,y[0],y[1],y[2],y[3]);j.extend(H,this.paintStyle);if(this.paintStyle.gradient&&!u){x=x?H.createLinearGradient(y[4],y[5],y[6],y[7]):H.createLinearGradient(y[6],y[7],y[4],y[5]);for(r=0;r=0&&b.connections.splice(o,1)};this.makeInPlaceCopy=function(){return new S({anchor:b.anchor,source:n,style:i,endpoint:c})};this.isConnectedTo=function(o){var D=false;if(o)for(var B=0;B=t};this.paint=function(o,D,B){s&&s.debug("Painting Endpoint with elementId ["+q+"]; anchorPoint is ["+o+"]");if(o==null){o=k[q];var O=e[q];if(o==null||O==null){M(q);o=k[q];O=e[q]}o=b.anchor.compute([o.left,o.top],O)}c.paint(o,b.anchor.getOrientation(),B||b.canvas,i,D||i)};if(a.isSource&&j.CurrentLibrary.isDragSupported(n)){var y=null,r=null,P=false,K=null,C=a.dragOptions|| +{},L=j.extend({},j.CurrentLibrary.defaultDragOptions);C=j.extend(L,C);L=j.CurrentLibrary.dragEvents.start;var Q=j.CurrentLibrary.dragEvents.stop,R=j.CurrentLibrary.dragEvents.drag;C[L]=J(C[L],function(){H=b.makeInPlaceCopy();H.paint();y=document.createElement("div");document.body.appendChild(y);var o=""+new String((new Date).getTime());F(A(y),"id",o);M(o);F(A(b.canvas),"dragId",o);F(A(b.canvas),"elId",q);var D=new ca({reference:b.anchor,referenceCanvas:b.canvas});x=new S({style:{fillStyle:"rgba(0,0,0,0)"}, +endpoint:c,anchor:D,source:y});r=b.connections.length==0||b.connections.length0)for(var i=0;i0)for(var n=0;n=4)c.orientation=[arguments[2],arguments[3]];if(arguments.length==6)c.offsets=[arguments[4],arguments[5]]}return new ba(c)},repaint:function(a){var b=function(i){i=A(i);I(i)};if(typeof a=="object")for(var c=0;cm)m=l;if(g<0){e+=g;g=Math.abs(g);m+=g;G[0]+=g;w+=g;I+=g;f[0]+=g}g=Math.min(Math.min(E,h),Math.min(G[1], +f[1]));l=Math.max(Math.max(E,h),Math.max(G[1],f[1]));if(l>d)d=l;if(g<0){p+=g;g=Math.abs(g);d+=g;G[1]+=g;E+=g;h+=g;f[1]+=g}return[e,p,m,d,w,E,I,h,G[0],G[1],f[0],f[1]]};this.paint=function(f,g){g.beginPath();g.moveTo(f[4],f[5]);g.bezierCurveTo(f[8],f[9],f[10],f[11],f[6],f[7]);g.stroke()}};jsPlumb.Endpoints.Dot=function(u){u=u||{radius:10};var s=this;this.radius=u.radius;var f=0.5*this.radius,g=this.radius/3,l=function(k){try{return parseInt(k)}catch(h){if(k.substring(k.length-1)=="%")return parseInt(k.substring(0, +k-1))}};this.paint=function(k,h,m,d,e){var p=d.radius||s.radius;jsPlumb.sizeCanvas(m,k[0]-p,k[1]-p,p*2,p*2);k=m.getContext("2d");m=jsPlumb.extend({},d);if(m.fillStyle==null)m.fillStyle=e.strokeStyle;jsPlumb.extend(k,m);e=/MSIE/.test(navigator.userAgent)&&!window.opera;if(d.gradient&&!e){e=d.gradient;m=f;var w=g;if(e.offset)m=l(e.offset);if(e.innerRadius)w=l(e.innerRadius);e=[m,w];h=k.createRadialGradient(p,p,p,p+(h[0]==1?e[0]*-1:e[0]),p+(h[1]==1?e[0]*-1:e[0]),e[1]);for(e=0;e endpoint lists. an element can have an arbitrary number of endpoints on it, + * and not all of them have to be connected to anything. + */ + var endpointsByElement = {}; + var offsets = []; + var floatingConnections = {}; + var draggableStates = {}; + var _draggableByDefault = true; + var sizes = []; + + var DEFAULT_NEW_CANVAS_SIZE = 1200; // only used for IE; a canvas needs a size before the init call to excanvas (for some reason. no idea why.) + + var _findIndex = function( a, v, b, s ) { + var _eq = function(o1, o2) { + if (o1 === o2) return true; + else if (typeof o1 == 'object' && typeof o2 == 'object') { + var same = true; + for(var propertyName in o1) { + if(!_eq(o1[propertyName], o2[propertyName])) { + same = false; + break; + } + } + for(var propertyName in o2) { + if(!_eq(o2[propertyName], o1[propertyName])) { + same = false; + break; + } + } + return same; + } + }; + + for( var i = +b || 0, l = a.length; i < l; i++ ) { + if( _eq(a[i], v)) return i; + } + return -1; + }; + + /** + * helper method to add an item to a list, creating the list if it does not yet exist. + */ + var _addToList = function(map, key, value) { + var l = map[key]; + if (l == null) { + l = []; + map[key] = l; + } + l.push(value); + }; + + /** + * Handles the dragging of an element. + * @param element jQuery element + * @param ui UI object from current library's event system + */ + var _draw = function(element, ui) { + var id = _getAttribute(element, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints) { + _updateOffset(id, ui); + var myOffset = offsets[id]; + var myWH = sizes[id]; + // loop through endpoints for this element + for (var i = 0; i < endpoints.length; i++) { + var e = endpoints[i]; + var anchorLoc = endpoints[i].anchor.compute([myOffset.left, myOffset.top], myWH); + e.paint(anchorLoc); + var l = e.connections; + for (var j = 0; j < l.length; j++) + l[j].paint(id, ui); // ...and paint them. + } + } + }; + + /** + * helper function: the second argument is a function taking two args - the first is an id + * ui element from the current library (or list of them), and the second is the function + * to run. + */ + var _elementProxy = function(element, fn) { + var retVal = null; + if (typeof element == 'object' && element.length) { + retVal = []; + for (var i = 0; i < element.length; i++) { + var el = _getElementObject(element[i]); + var id = _getAttribute(el, "id"); + retVal.push(fn(el, id)); // append return values to what we will return + } + } + else { + var el = _getElementObject(element); + var id = _getAttribute(el, "id"); + retVal = fn(el, id); + } + + return retVal; + }; + + /** + * gets the named attribute from the given element (id or element object) + */ + var _getAttribute = function(el, attName) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + return jsPlumb.CurrentLibrary.getAttribute(ele, attName); + }; + + var _setAttribute = function(el, attName, attValue) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + jsPlumb.CurrentLibrary.setAttribute(ele, attName, attValue); + }; + + var _addClass = function(el, clazz) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + jsPlumb.CurrentLibrary.addClass(ele, clazz); + }; + + var _getElementObject = function(elId) { + return jsPlumb.CurrentLibrary.getElementObject(elId); + }; + + var _getOffset = function(el) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + return jsPlumb.CurrentLibrary.getOffset(ele); + }; + + var _getSize = function(el) { + var ele = jsPlumb.CurrentLibrary.getElementObject(el); + return jsPlumb.CurrentLibrary.getSize(ele); + }; + + var _appendCanvas = function(canvas) { + document.body.appendChild(canvas); + }; + + /** + * gets an id for the given element, creating and setting one if necessary. + */ + var _getId = function(element) { + var id = _getAttribute(element, "id"); + if (!id) { + id = "_jsPlumb_" + new String((new Date()).getTime()); + _setAttribute(element, "id", id); + } + return id; + }; + + /** + * inits a draggable if it's not already initialised. + * + * TODO: we need to hook in each library to this method. they need to be given the opportunity to + * wrap/insert lifecycle functions, because each library does different things. for instance, jQuery + * supports the notion of 'revert', which will clean up a dragged endpoint; MooTools does not. jQuery + * also supports 'opacity' and MooTools does not; jQuery supports z-index of the draggable; MooTools + * does not. i could go on. the point is...oh. initDraggable can do this. ok. + */ + var _initDraggableIfNecessary = function(element, elementId, isDraggable, dragOptions) { + // dragging + var draggable = isDraggable == null ? _draggableByDefault : isDraggable; + if (draggable && jsPlumb.CurrentLibrary.isDragSupported(element)) { + var options = dragOptions || jsPlumb.Defaults.DragOptions; + options = jsPlumb.extend({}, options); // make a copy. + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + var initDrag = function(element, elementId, dragFunc) { + options[dragEvent] = _wrap(options[dragEvent], dragFunc); + var draggable = draggableStates[elementId]; + // TODO: this is still jQuery specific. + options.disabled = draggable == null ? false : !draggable; + jsPlumb.CurrentLibrary.initDraggable(element, options); + }; + initDrag(element, elementId, function() { + var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(element, ui); + _addClass(element, "jsPlumb_dragged"); + }); + } + }; + + var _log = function(msg) { + // not implemented. yet. + } + + /** + * helper to create a canvas. + * @param clazz optional class name for the canvas. + */ + var _newCanvas = function(clazz) { + var canvas = document.createElement("canvas"); + _appendCanvas(canvas); + canvas.style.position="absolute"; + if (clazz) { canvas.className=clazz; } + _getId(canvas); // set an id. + + if (ie) { + // for IE we have to set a big canvas size. actually you can override this, too, if 1200 pixels + // is not big enough for the biggest connector/endpoint canvas you have at startup. + jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE); + canvas = G_vmlCanvasManager.initElement(canvas); + } + + return canvas; + }; + /** + * performs the given function operation on all the connections found for the given element + * id; this means we find all the endpoints for the given element, and then for each endpoint + * find the connectors connected to it. then we pass each connection in to the given + * function. + */ + var _operation = function(elId, func) { + var endpoints = endpointsByElement[elId]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + for (var j = 0; j < endpoints[i].connections.length; j++) { + var retVal = func(endpoints[i].connections[j]); + // if the function passed in returns true, we exit. + // most functions return false. + if (retVal) return; + } + } + } + }; + /** + * perform an operation on all elements. + */ + var _operationOnAll = function(func) { + for (var elId in endpointsByElement) { + _operation(elId, func); + } + }; + /** + * helper to remove an element from the DOM. + */ + var _removeElement = function(element) { + if (element != null) { + try { document.body.removeChild(element); } + catch (e) { } + } + }; + /** + * helper to remove a list of elements from the DOM. + */ + var _removeElements = function(elements) { + for (var i in elements) + _removeElement(elements[i]); + }; + /** + * helper method to remove an item from a list. + */ + var _removeFromList = function(map, key, value) { + var l = map[key]; + if (l != null) { + var i = _findIndex(l, value); + if (i >= 0) { + delete( l[i] ); + l.splice( i, 1 ); + return true; + } + } + return false; + }; + /** + * Sets whether or not the given element(s) should be draggable, regardless of what a particular + * plumb command may request. + * + * @param element May be a string, a jQuery elements, or a list of strings/jquery elements. + * @param draggable Whether or not the given element(s) should be draggable. + */ + var _setDraggable = function(element, draggable) { + var _helper = function(el, id) { + draggableStates[id] = draggable; + if (jsPlumb.CurrentLibrary.isDragSupported(el)) { + jsPlumb.CurrentLibrary.setDraggable(el, draggable); + } + }; + + return _elementProxy(element, _helper); + }; + /** + * private method to do the business of hiding/showing. + * @param el either Id of the element in question or a jquery object for the element. + * @param state String specifying a value for the css 'display' property ('block' or 'none'). + */ + var _setVisible = function(el, state) { + var elId = _getAttribute(el, "id"); + var f = function(jpc) { + jpc.canvas.style.display = state; + }; + _operation(elId, f); + }; + /** + * toggles the draggable state of the given element(s). + * @param el either an id, or a jquery object, or a list of ids/jquery objects. + */ + var _toggleDraggable = function(el) { + var fn = function(el, elId) { + var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId]; + state = !state; + draggableStates[elId] = state; + //el.draggable("option", "disabled", !state); + jsPlumb.CurrentLibrary.setDraggable(el, state); + return state; + }; + return _elementProxy(el, fn); + }; + /** + * private method to do the business of toggling hiding/showing. + * @param elId Id of the element in question + */ + var _toggleVisible = function(elId) { + var f = function(jpc) { + var state = ('none' == jpc.canvas.style.display); + jpc.canvas.style.display = state ? "block" : "none"; + }; + + _operation(elId, f); + + //todo this should call _elementProxy, and pass in the _operation(elId, f) call as a function. cos _toggleDraggable does that. + }; + /** + * updates the offset and size for a given element, and stores the values. + * if 'offset' is not null we use that (it would have been passed in from a drag call) because it's faster; but if it is null, + * or if 'recalc' is true in order to force a recalculation, we use the offset, outerWidth and outerHeight methods to get + * the current values. + */ + var _updateOffset = function(elId, offset, recalc) { + if (recalc || offset == null) { // if forced repaint or no offset available, we recalculate. + // get the current size and offset, and store them + var s = _getElementObject(elId); + sizes[elId] = _getSize(elId); + offsets[elId] = _getOffset(elId); + } else { + offsets[elId] = offset; + } + }; + + /** + * wraps one function with another, creating a placeholder for the wrapped function + * if it was null. this is used to wrap the various drag/drop event functions - to allow + * jsPlumb to be notified of important lifecycle events without imposing itself on the user's + * drap/drop functionality. + * TODO: determine whether or not we should support an error handler concept, if one of the functions fails. + */ + var _wrap = function(wrappedFunction, newFunction) { + wrappedFunction = wrappedFunction || function() { }; + newFunction = newFunction || function() { }; + return function() { + try { newFunction.apply(this, arguments); } + catch (e) { } + try { wrappedFunction.apply(this, arguments); } + catch (e) { } + }; + } + + /* + Class: Anchor + Models a position relative to the origin of some element that an Endpoint can be located. + */ + /* + Function: Anchor + + Constructor for the Anchor class + + Parameters: + params - Anchor parameters. This should contain three values, and may optionally have an 'offsets' argument: + + - x : the x location of the anchor as a fraction of the total width. + - y : the y location of the anchor as a fraction of the total height. + - orientation : an [x,y] array indicating the general direction a connection from the anchor should go in. for more info on this, see the documentation, or the docs in jquery-jsPlumb-defaults-XXX.js for the default Anchors. + - offsets : an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. may be null. + */ + var Anchor = function(params) { + var self = this; + this.x = params.x || 0; this.y = params.y || 0; + var orientation = params.orientation || [0,0]; + this.offsets = params.offsets || [0,0]; + this.compute = function(xy, wh, txy, twh) { + return [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ]; + } + this.getOrientation = function() { return orientation; }; + }; + + /** + * An Anchor that floats. its orientation is computed dynamically from its position relative to the anchor it is floating relative to. + * + */ + var FloatingAnchor = function(params) { + + // this is the anchor that this floating anchor is referenced to for purposes of calculating the orientation. + var ref = params.reference; + // the canvas this refers to. + var refCanvas = params.referenceCanvas; + var size = _getSize(refCanvas); + + // these are used to store the current relative position of our anchor wrt the reference anchor. they only indicate + // direction, so have a value of 1 or -1 (or, very rarely, 0). these values are written by the compute method, and read + // by the getOrientation method. + var xDir = 0, yDir = 0; + // temporary member used to store an orientation when the floating anchor is hovering over another anchor. + var orientation = null; + + this.compute = function(xy, wh, txy, twh) { + // set these for the getOrientation method to use. + xDir = 0;//xy[0] < txy[0] ? -1 : xy[0] == txy[0] ? 0 : 1; + yDir = 0;//xy[1] < txy[1] ? -1 : xy[1] == txy[1] ? 0 : 1; + return [xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy. + }; + + this.getOrientation = function() { + if (orientation) return orientation; + else { + var o = ref.getOrientation(); + // here we take into account the orientation of the other anchor: if it declares zero for some direction, we declare zero too. + // this might not be the most awesome. perhaps we can come up with a better way. it's just so that the line we draw looks + // like it makes sense. maybe this wont make sense. + return [Math.abs(o[0]) * xDir * -1, Math.abs(o[1]) * yDir * -1]; + } + }; + + /** + * notification the endpoint associated with this anchor is hovering over another anchor; + * we want to assume that anchor's orientation for the duration of the hover. + */ + this.over = function(anchor) { + orientation = anchor.getOrientation(); + }; + + /** + * notification the endpoint associated with this anchor is no longer hovering + * over another anchor; we should resume calculating orientation as we normally do. + */ + this.out = function() { + orientation = null; + }; + }; + + // ************** connection + // **************************************** + /** + * allowed params: + * source: source element (string or a jQuery element) (required) + * target: target element (string or a jQuery element) (required) + * + * anchors: optional array of anchor placements. defaults to BottomCenter for source + * and TopCenter for target. + */ + var Connection = function(params) { + + // ************** get the source and target and register the connection. ******************* + var self = this; + // get source and target as jQuery objects + this.source = _getElementObject(params.source); + this.target = _getElementObject(params.target); + this.sourceId = _getAttribute(this.source, "id"); + this.targetId = _getAttribute(this.target, "id"); + this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true; + + // init endpoints + this.endpoints = []; + this.endpointStyles = []; + var prepareEndpoint = function(existing, index, params) { + if (existing) self.endpoints[index] = existing; + else { + if(!params.endpoints) params.endpoints = [null,null]; + var ep = params.endpoints[index] || params.endpoint || jsPlumb.Defaults.Endpoints[index] || jsPlumb.Defaults.Endpoint|| new jsPlumb.Endpoints.Dot(); + if (!params.endpointStyles) params.endpointStyles = [null,null]; + var es = params.endpointStyles[index] || params.endpointStyle || jsPlumb.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyle; + var a = params.anchors ? params.anchors[index] : jsPlumb.Defaults.Anchors[index] || jsPlumb.Anchors.BottomCenter; + self.endpoints[index] = new Endpoint({style:es, endpoint:ep, connections:[self], anchor:a, source:self.source }); + } + }; + + prepareEndpoint(params.sourceEndpoint, 0, params); + prepareEndpoint(params.targetEndpoint, 1, params); + + this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier(); + this.paintStyle = this.endpoints[0].connectorStyle || this.endpoints[1].connectorStyle || params.paintStyle || jsPlumb.Defaults.PaintStyle; + + _updateOffset(this.sourceId); + _updateOffset(this.targetId); + + // paint the endpoints + var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId]; + var anchorLoc = this.endpoints[0].anchor.compute([myOffset.left, myOffset.top], myWH); + this.endpoints[0].paint(anchorLoc); + myOffset = offsets[this.targetId]; myWH = sizes[this.targetId]; + anchorLoc = this.endpoints[1].anchor.compute([myOffset.left, myOffset.top], myWH); + this.endpoints[1].paint(anchorLoc); + + // *************** create canvas on which the connection will be drawn ************ + var canvas = _newCanvas(jsPlumb.connectorClass); + this.canvas = canvas; + + /** + * paints the connection. + * @param elId Id of the element that is in motion + * @param ui current library's event system ui object (present if we came from a drag to get here) + * @param recalc whether or not to recalculate element sizes. this is true if a repaint caused this to be painted. + */ + this.paint = function(elId, ui, recalc) { + + if (log) log.debug("Painting Connection; element in motion is " + elId + "; ui is [" + ui + "]; recalc is [" + recalc + "]"); + + var fai = self.floatingAnchorIndex; + // if the moving object is not the source we must transpose the two references. + var swap = !(elId == this.sourceId); + var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId; + var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0; + var el = swap ? this.target : this.source; + + if (this.canvas.getContext) { + + _updateOffset(elId, ui, recalc); + if (recalc) _updateOffset(tId); // update the target if this is a forced repaint. otherwise, only the source has been moved. + + var myOffset = offsets[elId]; + var otherOffset = offsets[tId]; + var myWH = sizes[elId]; + var otherWH = sizes[tId]; + + var ctx = canvas.getContext('2d'); + var sAnchorP = this.endpoints[sIdx].anchor.compute([myOffset.left, myOffset.top], myWH, [otherOffset.left, otherOffset.top], otherWH); + var sAnchorO = this.endpoints[sIdx].anchor.getOrientation(); + var tAnchorP = this.endpoints[tIdx].anchor.compute([otherOffset.left, otherOffset.top], otherWH, [myOffset.left, myOffset.top], myWH); + var tAnchorO = this.endpoints[tIdx].anchor.getOrientation(); + var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, this.paintStyle.lineWidth); + jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]); + jsPlumb.extend(ctx, this.paintStyle); + + if (this.paintStyle.gradient && !ie) { + var g = swap ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]); + for (var i = 0; i < this.paintStyle.gradient.stops.length; i++) + g.addColorStop(this.paintStyle.gradient.stops[i][0],this.paintStyle.gradient.stops[i][1]); + ctx.strokeStyle = g; + } + + this.connector.paint(dim, ctx); + } + }; + + this.repaint = function() { + this.paint(this.sourceId, null, true); + }; + + _initDraggableIfNecessary(self.source, self.sourceId, params.draggable, params.dragOptions); + _initDraggableIfNecessary(self.target, self.targetId, params.draggable, params.dragOptions); + + // resizing (using the jquery.ba-resize plugin). todo: decide whether to include or not. + if (this.source.resize) { + this.source.resize(function(e) { + jsPlumb.repaint(self.sourceId); + }); + } + }; + + /* + Class: Endpoint + + Models an endpoint. Can have one to N connections emanating from it (although how to handle that in the UI is + a very good question). also has a Canvas and paint style. + + */ + + /* + Function: Endpoint + + This is the Endpoint class constructor. + + Parameters: + + anchor - anchor for the endpoint, of type jsPlumb.Anchor. may be null. + endpoint - endpoint object, of type jsPlumb.Endpoint. may be null. + style - endpoint style, a js object. may be null. + source - element the endpoint is attached to, of type jquery object. Required. + canvas - canvas element to use. may be, and most often is, null. + connections - optional list of connections to configure the endpoint with. + isSource - boolean. indicates the endpoint can act as a source of new connections. optional. + dragOptions - if isSource is set to true, you can supply arguments for the jquery draggable method. optional. + connectionStyle - if isSource is set to true, this is the paint style for connections from this endpoint. optional. + connector - optional connector type to use. + isTarget - boolean. indicates the endpoint can act as a target of new connections. optional. + dropOptions - if isTarget is set to true, you can supply arguments for the jquery droppable method. optional. + reattach - optional boolean that determines whether or not the connections reattach after they + have been dragged off an endpoint and left floating. defaults to false: connections + dropped in this way will just be deleted. + */ + var Endpoint = function(params) { + params = params || {}; + // make a copy. then we can use the wrapper function. + jsPlumb.extend({}, params); + var self = this; + self.anchor = params.anchor || jsPlumb.Anchors.TopCenter; + var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot(); + var _style = params.style || jsPlumb.Defaults.EndpointStyle; + this.connectorStyle = params.connectorStyle; + this.connector = params.connector; + var _element = params.source; + var _elementId = _getAttribute(_element, "id"); + var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of. + this.canvas = params.canvas || _newCanvas(jsPlumb.endpointClass); + this.connections = params.connections || []; + var _reattach = params.reattach || false; + var floatingEndpoint = null; + var inPlaceCopy = null; + this.addConnection = function(connection) { + + self.connections.push(connection); + }; + this.removeConnection = function(connection) { + var idx = _findIndex(self.connections, connection); + if (idx >= 0) + self.connections.splice(idx, 1); + }; + this.makeInPlaceCopy = function() { + var e = new Endpoint({anchor:self.anchor, source:_element, style:_style, endpoint:_endpoint}); + return e; + }; + /** + * returns whether or not this endpoint is connected to the given endpoint. + * @param endpoint Endpoint to test. + * @since 1.1.1 + * + * todo: needs testing. will this work if the endpoint passed in is the source? + */ + this.isConnectedTo = function(endpoint) { + var found = false; + if (endpoint) { + for (var i = 0; i < self.connections.length; i++) { + if (self.connections[i].endpoints[1] == endpoint) { + found = true; + break; + } + } + } + return found; + }; + + this.isFloating = function() { return floatingEndpoint != null; }; + /** + * first pass at default ConnectorSelector: returns the first connection, if we have any. + * modified a little, 5/10/2010: now it only returns a connector if we have not got equal to or more than _maxConnector connectors + * attached. otherwise it is assumed a new connector is ok. but note with this setup we can't select any other connection than the first + * one. what if this could return a list? that implies everything should work with a list - dragging etc. could be nasty. could also + * be cool. + */ + var connectorSelector = function() { + return self.connections.length == 0 || self.connections.length < _maxConnections ? null : self.connections[0]; + }; + + this.isFull = function() { return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); }; + + /** + * paints the Endpoint, recalculating offset and anchor positions if necessary. + */ + this.paint = function(anchorPoint, connectorPaintStyle, canvas) { + + if (log) log.debug("Painting Endpoint with elementId [" + _elementId + "]; anchorPoint is [" + anchorPoint + "]"); + + if (anchorPoint == null) { + // do we always want to force a repaint here? i dont think so! + var xy = offsets[_elementId]; + var wh = sizes[_elementId]; + if (xy == null || wh == null) { + _updateOffset(_elementId); + xy = offsets[_elementId]; + wh = sizes[_elementId]; + } + anchorPoint = self.anchor.compute([xy.left, xy.top], wh); + } + _endpoint.paint(anchorPoint, self.anchor.getOrientation(), canvas || self.canvas, _style, connectorPaintStyle || _style); + }; + + + // is this a connection source? we make it draggable and have the drag listener + // maintain a connection with a floating endpoint. + if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) { + + var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null; + + // first the question is, how many connections are on this endpoint? if it's only one, then excellent. otherwise we will either need a way + // to select one connection from the list, or drag them all. if we had a pluggable 'ConnectorSelector' interface we could probably + // provide a way for people to implement their own UI components to do the connector selection. the question in that particular case would be how much + // information the interface needs from jsPlumb at execution time. if, however, we leave out the connector selection, and drag them all, + // that wouldn't be too hard to organise. perhaps that behaviour would be on a switch for the endpoint, or perhaps the ConnectorSelector + // interface returns a List, with the default implementation just returning everything. i think i like that. + // + // let's say for now that there is just one endpoint, cos we need to get this going before we can consider a list of them anyway. + // the major difference between that case and the case of a new endpoint is actually quite small - it's a question of where the + // Connection comes from. for a new one, we create a new one. obviously. otherwise we just get the jpc from the Endpoint + // (remember we're only assuming one connection right now). so all of the UI stuff we do to create the floating endpoint etc + // will still be valid, but when we stop dragging, we'll have to do something different. if we stop with a valid drop i think it will + // be the same process. but if we stop with an invalid drop we have to reset the Connection to how it was when we got it. + var start = function() { + + inPlaceCopy = self.makeInPlaceCopy(); + inPlaceCopy.paint(); + + n = document.createElement("div"); + _appendCanvas(n); + // create and assign an id, and initialize the offset. + var id = "" + new String(new Date().getTime()); + _setAttribute(_getElementObject(n), "id", id); + _updateOffset(id); + // store the id of the dragging div and the source element. the drop function + // will pick these up. + _setAttribute(_getElementObject(self.canvas), "dragId", id); + _setAttribute(_getElementObject(self.canvas), "elId", _elementId); + // create a floating anchor + var floatingAnchor = new FloatingAnchor({reference:self.anchor, referenceCanvas:self.canvas}); + floatingEndpoint = new Endpoint({ + style:{fillStyle:'rgba(0,0,0,0)'}, + endpoint:_endpoint, + anchor:floatingAnchor, + source:n + }); + + jpc = connectorSelector(); + if (jpc == null) { + // create a connection. one end is this endpoint, the other is a floating endpoint. + jpc = new Connection({ + sourceEndpoint:self, + targetEndpoint:floatingEndpoint, + source:_getElementObject(_element), + target:_getElementObject(n), + anchors:[self.anchor, floatingAnchor], + paintStyle : params.connectorStyle, // this can be null. Connection will use the default. + connector: params.connector + }); + // todo ...unregister on stop + self.addConnection(jpc); + } else { + existingJpc = true; + var anchorIdx = jpc.sourceId == _elementId ? 0 : 1; + jpc.floatingAnchorIndex = anchorIdx; + // probably we should remove the connection? and add it back if the user + // does not drop it somewhere proper. + self.removeConnection(jpc); + if (anchorIdx == 0){ + existingJpcParams = [jpc.source, jpc.sourceId]; + jpc.source = _getElementObject(n); + jpc.sourceId = id; + }else { + existingJpcParams = [jpc.target, jpc.targetId]; + jpc.target = _getElementObject(n); + jpc.targetId = id; + } + + jpc.suspendedEndpoint = jpc.endpoints[anchorIdx]; + jpc.endpoints[anchorIdx] = floatingEndpoint; + } + + // register it. + floatingConnections[id] = jpc; + + // todo unregister on stop + floatingEndpoint.addConnection(jpc); + + // only register for the target endpoint; we will not be dragging the source at any time + // before this connection is either discarded or made into a permanent connection. + _addToList(endpointsByElement, id, floatingEndpoint); + + }; + + var dragOptions = params.dragOptions || { }; + + var defaultOpts = jsPlumb.extend({}, jsPlumb.CurrentLibrary.defaultDragOptions); + dragOptions = jsPlumb.extend(defaultOpts, dragOptions); + var startEvent = jsPlumb.CurrentLibrary.dragEvents['start']; + var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop']; + var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag']; + dragOptions[startEvent] = _wrap(dragOptions[startEvent], start); + dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() { + var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments); + _draw(_getElementObject(n), _ui); + }); + dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], + function() { + _removeFromList(endpointsByElement, id, floatingEndpoint); + _removeElements([floatingEndpoint.canvas, n, inPlaceCopy.canvas]); + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (jpc.endpoints[idx] == floatingEndpoint) { + // if the connection was an existing one: + if (existingJpc && jpc.suspendedEndpoint) { + if (_reattach) { + jpc.floatingAnchorIndex = null; + if (idx == 0) { + jpc.source = existingJpcParams[0]; + jpc.sourceId = existingJpcParams[1]; + } else { + jpc.target = existingJpcParams[0]; + jpc.targetId = existingJpcParams[1]; + } + jpc.endpoints[idx] = jpc.suspendedEndpoint; + jpc.suspendedEndpoint.addConnection(jpc); + jsPlumb.repaint(existingJpcParams[1]); + } + else { + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + _removeElement(jpc.canvas); + } + } else { + _removeElement(jpc.canvas); + self.removeConnection(jpc); + } + } + jpc = null; + delete floatingEndpoint; + delete inPlaceCopy; + self.paint(); + } + ); + + var i = _getElementObject(self.canvas); + jsPlumb.CurrentLibrary.initDraggable(i, dragOptions); + }; + + // connector target + if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) { + var dropOptions = params.dropOptions || jsPlumb.Defaults.DropOptions; + dropOptions = jsPlumb.extend({}, dropOptions); + var originalAnchor = null; + var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop']; + var overEvent = jsPlumb.CurrentLibrary.dragEvents['over']; + var outEvent = jsPlumb.CurrentLibrary.dragEvents['out']; + dropOptions[dropEvent]= _wrap(dropOptions[dropEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable), "dragId"); + var elId = _getAttribute(_getElementObject(draggable),"elId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + if (idx == 0) { + jpc.source = _element; + jpc.sourceId = _elementId; + } else { + jpc.target = _element; + jpc.targetId = _elementId; + } + // remove this jpc from the current endpoint + jpc.endpoints[idx].removeConnection(jpc); + if (jpc.suspendedEndpoint) + jpc.suspendedEndpoint.removeConnection(jpc); + jpc.endpoints[idx] = self; + self.addConnection(jpc); + _initDraggableIfNecessary(_element, _elementId, params.draggable, {}); + jsPlumb.repaint(elId); + + delete floatingConnections[id]; + }); + + dropOptions[overEvent]= _wrap(dropOptions[overEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.over(self.anchor); + }); + + dropOptions[outEvent] = _wrap(dropOptions[outEvent], function() { + var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments); + var id = _getAttribute(_getElementObject(draggable),"dragId"); + var jpc = floatingConnections[id]; + var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex; + jpc.endpoints[idx].anchor.out(); + }); + + jsPlumb.CurrentLibrary.initDroppable(_getElementObject(self.canvas), dropOptions); + } + + return self; + }; + + /* + Class: jsPlumb + + This is the main entry point to jsPlumb. There are a bunch of static methods you can + use to connect/disconnect etc. Some methods are also added to jQuery's "$.fn" object itself, but + in general jsPlumb is moving away from that model, preferring instead the static class + approach. One of the reasons for this is that with no methods added to the jQuery $.fn object, + it will be easier to provide support for other libraries such as MooTools. + */ + var jsPlumb = window.jsPlumb = { + + /* + Property: Defaults + + These are the default settings for jsPlumb, that is what will be used if you do not supply specific pieces of information + to the various API calls. A convenient way to implement your own look and feel can be to override these defaults by including a script + somewhere after the jsPlumb include, but before you make any calls to jsPlumb, for instance in this example we set the PaintStyle to be + a blue line of 27 pixels: + + > jsPlumb.Defaults.PaintStyle = { lineWidth:27, strokeStyle:'blue' } + + */ + Defaults : { + Anchors : [ null, null ], + Connector : null, + DragOptions: { }, + DropOptions: { }, + Endpoint : null, + Endpoints : [ null, null ], + EndpointStyle : { fillStyle : null }, + EndpointStyles : [ null, null ], + MaxConnections : null, + PaintStyle : { lineWidth : 10, strokeStyle : 'red' }, + Scope : "_jsPlumb_DefaultScope" + }, + + /* + Property: connectorClass + + The CSS class to set on Connection canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + connectorClass : '_jsPlumb_connector', + + /* + Property: endpointClass + + The CSS class to set on Endpoint canvas elements. This value is a String and can have multiple classes; the entire String is appended as-is. + */ + endpointClass : '_jsPlumb_endpoint', + + /* + Property: Anchors + + Default jsPlumb Anchors. These are supplied in the file jquery.jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form jquery.jsPlumb-all-x.x.x.js. You can provide your own Anchors by supplying them in a script that is loaded after jsPlumb, for instance: + + > jsPlumb.Anchors.MyAnchor = { ....anchor code here. see the documentation. } + */ + Anchors : {}, + + /* + Property: Connectors + + Default jsPlumb Connectors. These are supplied in the file jquery.jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form jquery.jsPlumb-all-x.x.x.js. You can provide your own Connectors by supplying them in a script that is loaded after jsPlumb, for instance: + + > jsPlumb.Connectors.MyConnector = { ....connector code here. see the documentation. } + */ + Connectors : {}, + + /* + Property: Endpoints + + Default jsPlumb Endpoints. These are supplied in the file jquery.jsPlumb-defaults-x.x.x.js, which is merged in with the main jsPlumb script + to form jquery.jsPlumb-all-x.x.x.js. You can provide your own Endpoints by supplying them in a script that is loaded after jsPlumb, for instance: + + > jsPlumb.Endpoints.MyEndpoint = { ....endpoint code here. see the documentation. } + */ + Endpoints : {}, + + /* + Function: addEndpoint + + Adds an Endpoint to a given element. + + Parameters: + target - Element to add the endpoint to. either an element id, or a jQuery object representing some element. + params - Object containing Endpoint options (more info required) + + Returns: + + The newly created Endpoint. + + See Also: + + + */ + addEndpoint : function(target, params) { + params = jsPlumb.extend({}, params); + var el = _getElementObject(target); + var id = _getAttribute(target,"id"); + params.source = el; + _updateOffset(id); + var e = new Endpoint(params); + _addToList(endpointsByElement, id, e); + var myOffset = offsets[id]; + var myWH = sizes[id]; + var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH); + e.paint(anchorLoc); + return e; + }, + + /* + Function: addEndpoint + + Adds a list of Endpoints to a given element. + + Parameters: + + target - element to add the endpoint to. either an element id, or a jQuery object representing some element. + endpoints - List of objects containing Endpoint options. one Endpoint is created for each entry in this list. + + Returns: + + List of newly created Endpoints, one for each entry in the 'endpoints' argument. + + See Also: + + + */ + addEndpoints : function(target, endpoints) { + var results = []; + for (var i = 0; i < endpoints.length; i++) { + results.push(jsPlumb.addEndpoint(target, endpoints[i])); + } + return results; + }, + + /* + Function: animate + + Wrapper around standard jQuery animate function; injects a call to jsPlumb in the 'step' function (creating it if necessary). + This only supports the two-arg version of the animate call in jQuery - the one that takes an 'options' object as the second arg. + + Parameters: + + el - Element to animate. Either an id, or a jQuery object representing the element. + properties - The 'properties' argument you want passed to the standard jQuery animate call. + options - The 'options' argument you want passed to the standard jQuery animate call. + + Returns: + + void + */ + animate : function(el, properties, options) { + var ele = _getElementObject(el); + var id = _getAttribute(el,"id"); + //TODO this is not agnostic yet. + options = options || {}; + var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step']; + options[stepFunction] = _wrap(options[stepFunction], function() + { + jsPlumb.repaint(id); + }); + jsPlumb.CurrentLibrary.animate(ele, properties, options); + }, + + /* + Function: connect + + Establishes a connection between two elements. + + Parameters: + + params - Object containing setup for the connection. see documentation. + + Returns: + + The newly created Connection. + + */ + connect : function(params) { + if (params.sourceEndpoint && params.sourceEndpoint.isFull()) { + _log("could not add connection; source endpoint is full"); + return; + } + + if (params.targetEndpoint && params.targetEndpoint.isFull()) { + _log("could not add connection; target endpoint is full"); + return; + } + + var jpc = new Connection(params); + + // register endpoints for the element. todo: is the test below sufficient? or should we test if the endpoint is already in the list, + // and add it only then? perhaps _addToList could be overloaded with a a 'test for existence first' parameter? + //_addToList(endpointsByElement, jpc.sourceId, jpc.endpoints[0]); + //_addToList(endpointsByElement, jpc.targetId, jpc.endpoints[1]); + + if (!params.sourceEndpoint) _addToList(endpointsByElement, jpc.sourceId, jpc.endpoints[0]); + if (!params.targetEndpoint) _addToList(endpointsByElement, jpc.targetId, jpc.endpoints[1]); + + jpc.endpoints[0].addConnection(jpc); + jpc.endpoints[1].addConnection(jpc); + + // force a paint + _draw(jpc.source); + + return jpc; + + }, + + /** + * not implemented yet. params object will have sourceEndpoint and targetEndpoint members; these will be Endpoints. + connectEndpoints : function(params) { + var jpc = Connection(params); + + },*/ + + /* + Function: detach + + Removes a connection. + + Parameters: + + sourceId - Id of the first element in the connection. A String. + targetId - iI of the second element in the connection. A String. + + Returns: + + true if successful, false if not. + */ + detach : function(sourceId, targetId) { + var f = function(jpc) { + if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) { + _removeElement(jpc.canvas); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + return true; + } + }; + + // todo: how to cleanup the actual storage? a third arg to _operation? + _operation(sourceId, f); + }, + + /* + Function: detachAll + + Removes all an element's connections. + + Parameters: + + el - either the id of the element, or a jQuery object for the element. + + Returns: + + void + */ + detachAll : function(el) { + var id = _getAttribute(el, "id"); + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + var c = endpoints[i].connections.length; + if (c > 0) { + for (var j = 0; j < c; j++) { + var jpc = endpoints[i].connections[0]; + _removeElement(jpc.canvas); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + } + } + } + } + }, + + /* + Function: detachEverything + + Remove all Connections from all elements, but leaves Endpoints in place. + + Returns: + + void + + See Also: + + + */ + detachEverything : function() { + for(var id in endpointsByElement) { + var endpoints = endpointsByElement[id]; + if (endpoints && endpoints.length) { + for (var i = 0; i < endpoints.length; i++) { + var c = endpoints[i].connections.length; + if (c > 0) { + for (var j = 0; j < c; j++) { + var jpc = endpoints[i].connections[0]; + _removeElement(jpc.canvas); + jpc.endpoints[0].removeConnection(jpc); + jpc.endpoints[1].removeConnection(jpc); + } + } + } + } + } + }, + + extend : function(o1, o2) { + return jsPlumb.CurrentLibrary.extend(o1, o2); + }, + + /* + Function: hide + + Sets an element's connections to be hidden. + + Parameters: + + el - either the id of the element, or a jQuery object for the element. + + Returns: + + void + */ + hide : function(el) { + _setVisible(el, "none"); + }, + + /* + Function: makeAnchor + + Creates an anchor with the given params. + + Parameters: + + x - the x location of the anchor as a fraction of the total width. + y - the y location of the anchor as a fraction of the total height. + xOrientation - value indicating the general direction a connection from the anchor should go in, in the x direction. + yOrientation - value indicating the general direction a connection from the anchor should go in, in the y direction. + xOffset - a fixed offset that should be applied in the x direction that should be applied after the x position has been figured out. optional. defaults to 0. + yOffset - a fixed offset that should be applied in the y direction that should be applied after the y position has been figured out. optional. defaults to 0. + + Returns: + + The newly created Anchor. + */ + makeAnchor : function(x, y, xOrientation, yOrientation, xOffset, yOffset) { + // backwards compatibility here. we used to require an object passed in but that makes the call very verbose. easier to use + // by just passing in four/six values. but for backwards compatibility if we are given only one value we assume it's a call in the old form. + var params = {}; + if (arguments.length == 1) jsPlumb.extend(params, x); + else { + params = {x:x, y:y}; + if (arguments.length >= 4) { + params.orientation = [arguments[2], arguments[3]]; + } + if (arguments.length == 6) params.offsets = [arguments[4], arguments[5]]; + } + return new Anchor(params); + }, + + + /* + Function: repaint + + Repaints an element and its connections. This method gets new sizes for the elements before painting anything. + + Parameters: + + el - either the id of the element or a jQuery object representing the element. + + Returns: + + void + + See Also: + + + */ + repaint : function(el) { + + var _processElement = function(el) { + var ele = _getElementObject(el); + _draw(ele); + }; + + // TODO: support a jQuery result object too! + + // support both lists... + if (typeof el =='object') { + for (var i = 0; i < el.length; i++) + _processElement(el[i]); + } // ...and single strings. + else _processElement(el); + }, + + /* + Function: repaintEverything + + Repaints all connections. + + Returns: + + void + + See Also: + + + */ + repaintEverything : function() { + for (var elId in endpointsByElement) { + _draw(_getElementObject(elId)); + } + }, + + /* + Function: removeAllEndpoints + + Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes. + + Parameters: + + el - either an element id, or a jQuery object for an element. + + Returns: + + void + + See Also: + + + */ + removeAllEndpoints : function(el) { + var elId = _getAttribute(el, "id"); + // first remove all Connections. + jsPlumb.detachAll(elId); + var ebe = endpointsByElement[elId]; + for (var i in ebe) { + _removeElement(ebe[i].canvas); + } + endpointsByElement[elId] = []; + }, + + /* + Function: removeEndpoint + + Removes the given Endpoint from the given element. + + Parameters: + + el - either an element id, or a jQuery object for an element. + endpoint - Endpoint to remove. this is an Endpoint object, such as would have been returned from a call to addEndpoint. + + Returns: + + void + + See Also: + + + */ + removeEndpoint : function(el, endpoint) { + var elId = _getAttribute(el, "id"); + var ebe = endpointsByElement[elId]; + if (ebe) { + if(_removeFromList(endpointsByElement, elId, endpoint)) + _removeElement(endpoint.canvas); + } + }, + + /* + Function: setAutomaticRepaint + + Sets/unsets automatic repaint on window resize. + + Parameters: + + value - whether or not to automatically repaint when the window is resized. + + Returns: + + void + */ + setAutomaticRepaint : function(value) { + automaticRepaint = value; + }, + + /* + Function: setDefaultNewCanvasSize + + Sets the default size jsPlumb will use for a new canvas (we create a square canvas so one value is all that is required). + This is a hack for IE, because ExplorerCanvas seems to need for a canvas to be larger than what you are going to draw on + it at initialisation time. The default value of this is 1200 pixels, which is quite large, but if for some reason you're + drawing connectors that are bigger, you should adjust this value appropriately. + + Parameters: + + size - The default size to use. jsPlumb will use a square canvas so you need only supply one value. + + Returns: + + void + */ + setDefaultNewCanvasSize : function(size) { + DEFAULT_NEW_CANVAS_SIZE = size; + }, + + /* + Function: setDraggable + + Sets whether or not a given element is draggable, regardless of what any plumb command may request. + + Parameters: + + el - either the id for the element, or a jQuery object representing the element. + + Returns: + + void + */ + setDraggable: _setDraggable, + + /* + Function: setDraggableByDefault + + Sets whether or not elements are draggable by default. Default for this is true. + + Parameters: + draggable - value to set + + Returns: + + void + */ + setDraggableByDefault: function(draggable) { + _draggableByDefault = draggable; + }, + + setDebugLog: function(debugLog) { + log = debugLog; + }, + + /* + Function: setRepaintFunction + + Sets the function to fire when the window size has changed and a repaint was fired. + + Parameters: + f - Function to execute. + + Returns: + void + */ + setRepaintFunction : function(f) { + repaintFunction = f; + }, + + /* + Function: show + + Sets an element's connections to be visible. + + Parameters: + el - either the id of the element, or a jQuery object for the element. + + Returns: + void + */ + show : function(el) { + _setVisible(el, "block"); + }, + + /* + Function: sizeCanvas + + Helper to size a canvas. You would typically use this when writing your own Connector or Endpoint implementation. + + Parameters: + x - [int] x position for the Canvas origin + y - [int] y position for the Canvas origin + w - [int] width of the canvas + h - [int] height of the canvas + + Returns: + void + */ + sizeCanvas : function(canvas, x, y, w, h) { + if (canvas) { + canvas.style.height = h + "px"; canvas.height = h; + canvas.style.width = w + "px"; canvas.width = w; + canvas.style.left = x + "px"; canvas.style.top = y + "px"; + } + }, + + /** + * gets some test hooks. nothing writable. + */ + getTestHarness : function() { + return { + endpointCount : function(elId) { + var e = endpointsByElement[elId]; + return e ? e.length : 0; + }, + + findIndex : _findIndex + }; + }, + + /** + * Toggles visibility of an element's connections. kept for backwards compatibility + */ + toggle : _toggleVisible, + + /* + Function: toggleVisible + + Toggles visibility of an element's connections. + + Parameters: + el - either the element's id, or a jQuery object representing the element. + + Returns: + void, but should be updated to return the current state + */ + //TODO: update this method to return the current state. + toggleVisible : _toggleVisible, + + /* + Function: toggleDraggable + + Toggles draggability (sic) of an element's connections. + + Parameters: + el - either the element's id, or a jQuery object representing the element. + + Returns: + The current draggable state. + */ + toggleDraggable : _toggleDraggable, + + /* + Function: unload + + Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the element + + Returns: + void + */ + unload : function() { + delete endpointsByElement; + delete offsets; + delete sizes; + delete floatingConnections; + delete draggableStates; + }, + + /* + Function: wrap + Helper method to wrap an existing function with one of your own. This is used by the various + implementations to wrap event callbacks for drag/drop etc; it allows jsPlumb to be + transparent in its handling of these things. If a user supplies their own event callback, + for anything, it will always be called. + Parameters: + + */ + wrap : _wrap + }; + +})(); + +(function() { + + /* + * overrides the FX class to inject 'step' functionality, which MooTools does not + * offer, and which makes me sad. they don't seem keen to add it, either, despite + * the fact that it could be useful: + * + * https://mootools.lighthouseapp.com/projects/2706/tickets/668 + * + */ + var jsPlumbMorph = new Class({ + Extends:Fx.Morph, + onStep : null, + initialize : function(el, options) { + this.parent(el, options); + if (options['onStep']) { + this.onStep = options['onStep']; + } + }, + step : function() { + this.parent(); + if (this.onStep) { + try { this.onStep(); } + catch(e) { } + } + } + }); + + var _droppables = {}; + var _droppableOptions = {}; + var _draggablesByScope = {}; + var _draggablesById = {}; + /* + * + */ + var _executeDroppableOption = function(el, dr, event) { + if (dr) { + var id = dr.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options[event]) { + options[event](el, dr); + } + } + } + } + }; + + var _checkHover = function(el, entering) { + if (el) { + var id = el.get("id"); + if (id) { + var options = _droppableOptions[id]; + if (options) { + if (options['hoverClass']) { + if (entering) el.addClass(options['hoverClass']); + else el.removeClass(options['hoverClass']); + } + } + } + } + }; + + /** + * adds the given value to the given list, with the given scope. creates the scoped list + * if necessary. + * used by initDraggable and initDroppable. + */ + var _add = function(list, scope, value) { + var l = list[scope]; + if (!l) { + l = []; + list[scope] = l; + } + l.push(value); + }; + + + jsPlumb.CurrentLibrary = { + + dragEvents : { + 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep', + 'over':'onEnter', 'out':'onLeave','drop':'onDrop' + }, + + /* + * wrapper around the library's 'extend' functionality (which it hopefully has. + * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you + * instead. it's not like its hard. + */ + extend : function(o1, o2) { + return $extend(o1, o2); + }, + + /* + * gets an "element object" from the given input. this means an object that is used by the + * underlying library on which jsPlumb is running. 'el' may already be one of these objects, + * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup + * function is used to find the element, using the given String as the element's id. + */ + getElementObject : function(el) { + return $(el); + }, + + /* + gets the offset for the element object. this should return a js object like this: + + { left:xxx, top: xxx} + */ + getOffset : function(el) { + var p = el.getPosition(); + return { left:p.x, top:p.y }; + }, + + getSize : function(el) { + var s = el.getSize(); + return [s.x, s.y]; + }, + + /** + * gets the named attribute from the given element object. + */ + getAttribute : function(el, attName) { + return el.get(attName); + }, + + /** + * sets the named attribute on the given element object. + */ + setAttribute : function(el, attName, attValue) { + el.set(attName, attValue); + }, + + /** + * adds the given class to the element object. + */ + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + initDraggable : function(el, options) { + var drag = _draggablesById[el.get("id")]; + if (!drag) { + var originalZIndex = 0, originalCursor = null; + var dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000; + options['onStart'] = jsPlumb.wrap(options['onStart'], function() + { + originalZIndex = this.element.getStyle('z-index'); + this.element.setStyle('z-index', dragZIndex); + if (jsPlumb.Defaults.DragOptions.cursor) { + originalCursor = this.element.getStyle('cursor'); + this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor); + } + }); + + options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() + { + this.element.setStyle('z-index', originalZIndex); + if (originalCursor) { + this.element.setStyle('cursor', originalCursor); + } + }); + + // DROPPABLES: + var scope = options['scope'] || jsPlumb.Defaults.Scope; + var filterFunc = function(entry) { + return entry.get("id") != el.get("id"); + }; + var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : []; + options['droppables'] = droppables; + options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onLeave'); + } + }); + options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) { + if (dr) { + _checkHover(dr, true); + _executeDroppableOption(el, dr, 'onEnter'); + } + }); + options['onDrop'] = function(el, dr) { + if (dr) { + _checkHover(dr, false); + _executeDroppableOption(el, dr, 'onDrop'); + } + }; + + drag = new Drag.Move(el, options); + _add(_draggablesByScope, scope, drag); + _add(_draggablesById, el.get("id"), drag); + // test for disabled. + if (options.disabled) drag.detach(); + } + return drag; + }, + + isDragSupported : function(el, options) { + return typeof Drag != 'undefined' ; + }, + + setDraggable : function(el, draggable) { + var draggables = _draggablesById[el.get("id")]; + if (draggables) { + draggables.each(function(d) { + if (draggable) d.attach(); else d.detach(); + }); + } + }, + + initDroppable : function(el, options) { + var scope = options['scope'] || jsPlumb.Defaults.Scope; + _add(_droppables, scope, el); + _droppableOptions[el.get("id")] = options; + var filterFunc = function(entry) { + return entry.element != el; + }; + var draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : []; + for (var i = 0; i < draggables.length; i++) { + draggables[i].droppables.push(el); + } + }, + + /* + * you need Drag.Move imported to make drop work. + */ + isDropSupported : function(el, options) { + if (typeof Drag != undefined) + return typeof Drag.Move != undefined; + return false; + }, + + animate : function(el, properties, options) { + var m = new jsPlumbMorph(el, options); + m.start(properties); + }, + + /* + * takes the args passed to an event function and returns you an object that gives the + * position of the object being moved, as a js object with the same params as the result of + * getOffset, ie: { left: xxx, top: xxx }. + */ + getUIPosition : function(eventArgs) { + var ui = eventArgs[0]; + return { left: ui.offsetLeft, top: ui.offsetTop }; + }, + + getDragObject : function(eventArgs) { + return eventArgs[0]; + } + }; +})(); + +(function() { + + /** + * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance + * of Anchor that has been configured appropriately. + * + * You can write your own one of these; you + * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this: + * + * jsPlumb.Anchors.MY_ANCHOR = { + * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; }, + * getOrientation : function() { return [ox, oy]; } + * }; + * + * compute takes the [x,y] position of the top left corner of the anchored element, + * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to, + * and returns where the anchor should be located. + * + * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor + * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference, + * -1 means go in a negative direction for the given axis, and 1 means go in a positive + * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1], + * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example, + * the curve would start out going in that direction, before bending towards the target anchor. + */ + jsPlumb.Anchors.TopCenter = jsPlumb.makeAnchor(0.5, 0, 0,-1); + jsPlumb.Anchors.BottomCenter = jsPlumb.makeAnchor(0.5, 1, 0, 1); + jsPlumb.Anchors.LeftMiddle = jsPlumb.makeAnchor(0, 0.5, -1, 0); + jsPlumb.Anchors.RightMiddle = jsPlumb.makeAnchor(1, 0.5, 1, 0); + jsPlumb.Anchors.Center = jsPlumb.makeAnchor(0.5, 0.5, 0, 0); + jsPlumb.Anchors.TopRight = jsPlumb.makeAnchor(1, 0, 0,-1); + jsPlumb.Anchors.BottomRight = jsPlumb.makeAnchor(1, 1, 0, 1); + jsPlumb.Anchors.TopLeft = jsPlumb.makeAnchor(0, 0, 0, -1); + jsPlumb.Anchors.BottomLeft = jsPlumb.makeAnchor(0, 1, 0, 1); + + + /** + * The Straight connector draws a simple straight line between the two anchor points. + */ + jsPlumb.Connectors.Straight = function() { + + var self = this; + + /** + * Computes the new size and position of the canvas. + * @param sourceAnchor Absolute position on screen of the source object's anchor. + * @param targetAnchor Absolute position on screen of the target object's anchor. + * @param positionMatrix Indicates the relative positions of the left,top of the + * two plumbed objects. so [0,0] indicates that the source is to the left of, and + * above, the target. [1,0] means the source is to the right and above. [0,1] means + * the source is to the left and below. [1,1] means the source is to the right + * and below. this is used to figure out which direction to draw the connector in. + * @returns an array of positioning information. the first two values are + * the [left, top] absolute position the canvas should be placed on screen. the + * next two values are the [width,height] the canvas should be. after that each + * Connector can put whatever it likes into the array:it will be passed back in + * to the paint call. This particular function stores the origin and destination of + * the line it is going to draw. a more involved implementation, like a Bezier curve, + * would store the control point info in this array too. + */ + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) { + var w = Math.abs(sourcePos[0] - targetPos[0]); + var h = Math.abs(sourcePos[1] - targetPos[1]); + var widthAdjusted = false, heightAdjusted = false; + // these are padding to ensure the whole connector line appears + var xo = 0.45 * w, yo = 0.45 * h; + // these are padding to ensure the whole connector line appears + w *= 1.9; h *=1.9; + + var x = Math.min(sourcePos[0], targetPos[0]) - xo; + var y = Math.min(sourcePos[1], targetPos[1]) - yo; + + if (w < 2 * lineWidth) { + w = 2 * lineWidth; + x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - lineWidth; + xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2; + } + if (h < 2 * lineWidth) { + // minimum size is 2 * line Width + h = 2 * lineWidth; + y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - lineWidth; + yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2; + } + + var sx = sourcePos[0] < targetPos[0] ? w-xo : xo; + var sy = sourcePos[1] < targetPos[1] ? h-yo : yo; + var tx = sourcePos[0] < targetPos[0] ? xo : w-xo; + var ty = sourcePos[1] < targetPos[1] ? yo : h-yo; + var retVal = [ x, y, w, h, sx, sy, tx, ty ]; + + return retVal; + }; + + this.paint = function(dimensions, ctx) + { + ctx.beginPath(); + ctx.moveTo(dimensions[4], dimensions[5]); + ctx.lineTo(dimensions[6], dimensions[7]); + ctx.stroke(); + }; + }; + + /** + * This Connector draws a Bezier curve with two control points. + * @param curviness How 'curvy' you want the curve to be! This is a directive for the + * placement of control points, not endpoints of the curve, so your curve does not + * actually touch the given point, but it has the tendency to lean towards it. the larger + * this value, the greater the curve is pulled from a straight line. + * + * a future implementation of this could take the control points as arguments, rather + * than fixing the curve to one basic shape. + */ + jsPlumb.Connectors.Bezier = function(curviness) { + + var self = this; + this.majorAnchor = curviness || 150; + this.minorAnchor = 10; + + this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) { + // determine if the two anchors are perpendicular to each other in their orientation. we swap the control + // points around if so (code could be tightened up) + var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation(); + var perpendicular = soo[0] != too[0] || soo[1] == too[1]; + var p = []; + var ma = self.majorAnchor, mi = self.minorAnchor; + if (!perpendicular) { + if (soo[0] == 0) // X + p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] - (ma * soo[0])); + + if (soo[1] == 0) // Y + p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * too[1])); + } + else { + if (too[0] == 0) // X + p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi); + else p.push(point[0] + (ma * too[0])); + + if (too[1] == 0) // Y + p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi); + else p.push(point[1] + (ma * soo[1])); + } + + return p; + }; + + this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) + { + lineWidth = lineWidth || 0; + var w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth, h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth; + var canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2), canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2); + var sx = sourcePos[0] < targetPos[0] ? w - (lineWidth/2): (lineWidth/2), sy = sourcePos[1] < targetPos[1] ? h-(lineWidth/2) : (lineWidth/2); + var tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : w-(lineWidth/2), ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : h-(lineWidth/2); + var CP = self._findControlPoint([sx,sy], sourcePos, targetPos, sourceAnchor, targetAnchor); + var CP2 = self._findControlPoint([tx,ty], targetPos, sourcePos, targetAnchor, sourceAnchor); + var minx1 = Math.min(sx,tx); var minx2 = Math.min(CP[0], CP2[0]); var minx = Math.min(minx1,minx2); + var maxx1 = Math.max(sx,tx); var maxx2 = Math.max(CP[0], CP2[0]); var maxx = Math.max(maxx1,maxx2); + + if (maxx > w) w = maxx; + if (minx < 0) { + canvasX += minx; var ox = Math.abs(minx); + w += ox; CP[0] += ox; sx += ox; tx +=ox; CP2[0] += ox; + } + + var miny1 = Math.min(sy,ty); var miny2 = Math.min(CP[1], CP2[1]); var miny = Math.min(miny1,miny2); + var maxy1 = Math.max(sy,ty); var maxy2 = Math.max(CP[1], CP2[1]); var maxy = Math.max(maxy1,maxy2); + if (maxy > h) h = maxy; + if (miny < 0) { + canvasY += miny; var oy = Math.abs(miny); + h += oy; CP[1] += oy; sy += oy; ty +=oy; CP2[1] += oy; + } + + // return [ canvasx, canvasy, canvasWidth, canvasHeight, + // sourceX, sourceY, targetX, targetY, + // controlPoint1_X, controlPoint1_Y, controlPoint2_X, controlPoint2_Y + return [canvasX, canvasY, w, h, sx, sy, tx, ty, CP[0], CP[1], CP2[0], CP2[1] ]; + }; + + this.paint = function(d, ctx) { + + /*var img = new Image(); + img.src = "../img/pattern.jpg"; + ctx.fillStyle = ctx.createPattern(img, 'repeat-y');*/ + + ctx.beginPath(); + ctx.moveTo(d[4],d[5]); + ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]); + ctx.stroke(); + //ctx.fill(); + } + }; + + + /** + * Types of endpoint UIs. we supply four - a circle of default radius 10px, a rectangle of + * default size 20x20, an image (with no default), and a Triangle, of default size 15. + * you can supply others of these if you want to - see the documentation for a howto. + */ + + /** + * a round endpoint, with default radius 10 pixels. + */ + jsPlumb.Endpoints.Dot = function(params) { + + params = params || { radius:10 }; + var self = this; + this.radius = params.radius; + var defaultOffset = 0.5 * this.radius; + var defaultInnerRadius = this.radius / 3; + + var parseValue = function(value) { + try { + return parseInt(value); + } + catch(e) { + if (value.substring(value.length - 1) == '%') + return parseInt(value.substring(0, value - 1)); + } + } + + var calculateAdjustments = function(gradient) { + var offsetAdjustment = defaultOffset; + var innerRadius = defaultInnerRadius; + if (gradient.offset) offsetAdjustment = parseValue(gradient.offset); + if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius); + return [offsetAdjustment, innerRadius]; + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var radius = endpointStyle.radius || self.radius; + var x = anchorPoint[0] - radius; + var y = anchorPoint[1] - radius; + jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2); + var ctx = canvas.getContext('2d'); + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + + var adjustments = calculateAdjustments(endpointStyle.gradient); + var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0]; + var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0]; + var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.arc(radius, radius, radius, 0, Math.PI*2, true); + ctx.closePath(); + ctx.fill(); + }; + }; + + /** + * A Rectangular endpoint, with default size 20x20. + */ + jsPlumb.Endpoints.Rectangle = function(params) { + + params = params || { width:20, height:20 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + //todo: the fillStyle needs some thought. we want to support a few options: + // 1. nothing supplied; use the stroke color or the default if no stroke color. + // 2. a fill color supplied - use it + // 3. a gradient supplied - use it + // 4. setting the endpoint to the same color as the bg of the element it is attached to. + var style = jsPlumb.extend({}, endpointStyle); + if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle; + jsPlumb.extend(ctx, style); + + var ie = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (endpointStyle.gradient && !ie) { + // first figure out which direction to run the gradient in (it depends on the orientation of the anchors) + var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0; + var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0; + var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0; + var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0; + var g = ctx.createLinearGradient(x1,y1,x2,y2); + for (var i = 0; i < endpointStyle.gradient.stops.length; i++) + g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]); + ctx.fillStyle = g; + } + + ctx.beginPath(); + ctx.rect(0, 0, width, height); + ctx.closePath(); + ctx.fill(); + }; + }; + + jsPlumb.Endpoints.Triangle = function(params) { + + params = params || { width:15, height:15 }; + var self = this; + this.width = params.width; + this.height = params.height; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + { + var width = endpointStyle.width || self.width; + var height = endpointStyle.height || self.height; + var x = anchorPoint[0] - width/2; + var y = anchorPoint[1] - height/2; + + jsPlumb.sizeCanvas(canvas, x, y, width, height); + + var ctx = canvas.getContext('2d'); + var + offsetX = 0, + offsetY = 0, + angle = 0; + + if( orientation[0] == 1 ) + { + offsetX = width; + offsetY = height; + angle = 180; + } + if( orientation[1] == -1 ) + { + offsetX = width; + angle = 90; + } + if( orientation[1] == 1 ) + { + offsetY = height; + angle = -90; + } + + ctx.fillStyle = endpointStyle.fillStyle; + + ctx.translate(offsetX, offsetY); + ctx.rotate(angle * Math.PI/180); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width/2, height/2); + ctx.lineTo(0, height); + ctx.closePath(); + ctx.fill(); + + }; + }; + + /** + * Image endpoint - draws an image as the endpoint. You must provide a 'url' property in the params object.. + */ + jsPlumb.Endpoints.Image = function(params) { + var self = this; + this.img = new Image(); + var ready = false; + this.img.onload = function() { + self.ready = true; + }; + this.img.src = params.url; + + var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + var width = self.img.width || endpointStyle.width; + var height = self.img.height || endpointStyle.height; + var x = anchorPoint[0] - (width/2); + var y = anchorPoint[1] - (height/2); + jsPlumb.sizeCanvas(canvas, x, y, width, height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(self.img,0,0); + }; + + this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { + if (self.ready) { + actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) + } + else + window.setTimeout(function() { + self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle); + }, 200); + }; + }; +})(); diff --git a/archive/scobie-0.1.js b/archive/scobie-0.1.js new file mode 100644 index 000000000..6830ebc3b --- /dev/null +++ b/archive/scobie-0.1.js @@ -0,0 +1,103 @@ +/** + * Scobie - light weight html templating in JS. + * + * in some html, include a script fragment like this: + * + * + */ +(function() { + var _ajax = function(url, success, error) { + var http = false; + if (navigator.appName == "Microsoft Internet Explorer") + http = new ActiveXObject("Microsoft.XMLHTTP"); + else + http = new XMLHttpRequest(); + + http.open("GET", url); + http.onreadystatechange=function() { + if(http.readyState == 4) { + if (http.status == 200) + success(http.responseText); + else error(); + } + } + http.send(null); + }; + + var _checkReady = function() { + templateCount -= 1; + if (templateCount == 0) { + ready = true; + window.setTimeout(function() { + for (var i = 0; i < readyFuncs.length; i++) + readyFuncs[i](); + }, 200); + } + }; + + var _finalise = function(s) { + s.parentNode.removeChild(s); + _checkReady(); + }; + + var _init = function(base, s, isScript, isCss) { + templateCount += 1; + var fId = s.getAttribute("src"), h = s.innerHTML.trim(); + var params = h.length > 0 ? params = eval("(" + h +")") : {}; + var replace = function(html) { + for (var i in params) { + html = html.replace(new RegExp("\\${" + i + "}", "g"), params[i]); + } + return html; + }; + var _finaliseThis = function() { _finalise(s); }; + _ajax(base + fId, function(html) { + var d = document.createElement(isScript ? "script" : isCss ? "style" : "div"); + if (isScript) { + d.type = "text/javascript"; + d.text = replace(html); + s.parentNode.insertBefore(d, s); + } else if (isCss) { + d.type = "text/css"; + d.innerHTML = replace(html); + s.parentNode.insertBefore(d, s); + } else { + d.innerHTML = replace(html); + for (var i = 0; i < d.childNodes.length; i++) + s.parentNode.insertBefore(d.childNodes[i], s); + } + _update(); + _finaliseThis(); + + }, _finaliseThis); // ajax error handler. we ignore the failure to log a template. could log it or something of course. + }; + + var _update = function() { + var tags = document.getElementsByTagName("script"); + for (var i = 0; i < tags.length ;i++) { + var type = tags[i].getAttribute("type"), isScript = type == "x-scobie-script", isCss = type == "x-scobie-css"; + var src = tags[i].getAttribute("src"), done = tags[i].getAttribute("done"); + if ((type == "x-scobie-tmpl" && !done) || (isScript && !_loadedScripts[src]) || (isCss && !_loadedCss[src])) { + tags[i].setAttribute("done", "true"); + if (isScript) _loadedScripts[src] = true; + if (isCss) _loadedCss[src] = true; + _init(base, tags[i], isScript, isCss); + } + } + }; + + var _loadedScripts = { }, _loadedCss = { }; + var templateCount = 0; + var base = /(.+\/)(.+\.?)/.exec(document.location)[1]; + + var jQueryReady = jQuery.fn.ready, readyFuncs = [], ready = false; + jQuery.fn.ready = function(fn) { // todo remove jQuery dependency entirely. but we want to override it dont we? + if (!ready) readyFuncs.push(fn); + }; + + var Scobie = window.Scobie = { + ready : jQuery.fn.ready + }; + + jQueryReady(_update); +})(); diff --git a/archive/yui.jsPlumb-1.2-RC1.js b/archive/yui.jsPlumb-1.2-RC1.js new file mode 100644 index 000000000..a92848e0c --- /dev/null +++ b/archive/yui.jsPlumb-1.2-RC1.js @@ -0,0 +1,73 @@ +(function() { + + var Y; + + YUI().use('node', function(_Y) { + Y = _Y; + }); + + jsPlumb.CurrentLibrary = { + + dragEvents : { + 'start':'drag:start', 'stop':'drag:stop', 'drag':'drag:drag', 'step':'step', + 'over':'over', 'out':'out', 'drop':'drop' + }, + + extend : function(o1, o2) { + for (var i in o2) + o1[i] = o2[i]; + return o1; + }, + + getElementObject : function(el) { + return typeof el == 'string' ? Y.one('#' + el) : el; + }, + + getAttribute : function(el, attributeId) { + return el.getAttribute(attributeId); + }, + + getSize : function(el) { + //TODO must be a proper way to get this. + var bcr = el._node.getBoundingClientRect(); + return [ bcr.width, bcr.height ]; + }, + + getOffset : function(el) { + var bcr = el._node.getBoundingClientRect(); + return { left:bcr.left, top:bcr.top }; + }, + + setAttribute : function(el, attributeName, attributeValue) { + el.setAttribute(attributeName, attributeValue); + }, + + isDragSupported : function(el) { return true; }, + isDropSupported : function(el) { return false; }, + + initDraggable : function(el, options) { + var drag = options['drag:drag']; + new YUI().use('dd-drag', function(Y) { + var dd = new Y.DD.Drag({ + node: '#' + el.getAttribute('id') + }); + dd.on('drag:drag', drag); + }); + }, + + addClass : function(el, clazz) { + el.addClass(clazz); + }, + + removeClass : function(el, clazz) { + el.removeClass(clazz); + }, + + getUIPosition : function(args) { + var t = args[0].target.nodeXY; + return { left:t[0], top:t[1] }; + } + + }; + +})(); \ No newline at end of file diff --git a/build.properties b/build.properties new file mode 100644 index 000000000..7dc9496ee --- /dev/null +++ b/build.properties @@ -0,0 +1 @@ +version=1.4.0 diff --git a/build.xml b/build.xml index d5bbcfd59..4301314ee 100644 --- a/build.xml +++ b/build.xml @@ -33,25 +33,29 @@ select="\0" casesensitive="false" /> - + - + - + - + - + - + + + + + + @@ -78,7 +82,7 @@ - Building Version : ${final.version} into directory ${output.dir} + Building Version : ${version} into directory ${output.dir} + + Copying documentation... @@ -211,8 +217,8 @@ - - + + @@ -223,7 +229,7 @@ - + @@ -248,8 +254,8 @@ - - + + + + + - + - + + + + - <html><head><meta http-equiv="Refresh" CONTENT="0; URL=./files/jquery-jsPlumb-${version.with.dashes}-all-js.html"></head></html> + <html><head><meta http-equiv="Refresh" CONTENT="0; URL=./files/jsPlumb-${final.version}-apidoc.html"></head></html> - + - + - - + + - - + + @@ -334,41 +346,40 @@ ${new.escaped} - + - + - + - - + @@ -380,7 +391,7 @@ - + diff --git a/demo/apidocs/index.html b/demo/apidocs/index.html deleted file mode 100644 index 29729e1dc..000000000 --- a/demo/apidocs/index.html +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/demo/apidocs/index/Classes.html b/demo/apidocs/index/Classes.html deleted file mode 100644 index 1d560f518..000000000 --- a/demo/apidocs/index/Classes.html +++ /dev/null @@ -1,41 +0,0 @@ - - -Class Index - - - - - - - - - -
Class Index
$#! · 0-9 · A · B · C · D · E · F · G · H · I · J · K · L · M · N · O · P · Q · R · S · T · U · V · W · X · Y · Z
C
 Connection
E
 Endpoint
J
 jsPlumb
- -
The connecting line between two elements.
- - - -
Models an endpoint.
- - - -
The jsPlumb engine, of which there is one instance registered as a static object in the window.
- -
- - - - - - - - - - - - \ No newline at end of file diff --git a/demo/apidocs/index/Functions.html b/demo/apidocs/index/Functions.html deleted file mode 100644 index 89ebdb136..000000000 --- a/demo/apidocs/index/Functions.html +++ /dev/null @@ -1,77 +0,0 @@ - - -Function Index - - - - - - - - - -
Function Index
$#! · 0-9 · A · B · C · D · E · F · G · H · I · J · K · L · M · N · O · P · Q · R · S · T · U · V · W · X · Y · Z
$#!
 )
A
 addClass(el,clazz), jsPlumb
 addConnection(connection), Endpoint
 addEndpoint(el,[params],[referenceParams]), jsPlumb
 addEndpoints(target,endpoints,[referenceParams]), jsPlumb
 addOverlay(overlaySpec)
 addType(typeId)
 animate, jsPlumb
B
 bind(event,callback)
C
 connect(params,[referenceParams]), jsPlumb
 Connection, Connection
D
 deleteEndpoint(object), jsPlumb
 deleteEveryEndpoint, jsPlumb
 detach(connection,[ignoreTarget]), Endpoint
 detach(connection,[params]), jsPlumb
 detachAll([fireEvent]), Endpoint
 detachAllConnections(el,[params]), jsPlumb
 detachEveryConnection([params]), jsPlumb
 detachFrom(targetEndpoint,[fireEvent]), Endpoint
 detachFromConnection(connection), Endpoint
 draggable(el,[options]), jsPlumb
E
 Endpoint, Endpoint
 extend(o1,o2), jsPlumb
G
 getAllConnections, jsPlumb
 getConnections(params), jsPlumb
 getConnector, Connection
 getDefaultConnectionType, jsPlumb
 getDefaultEndpointType, jsPlumb
 getDefaultScope, jsPlumb
 getElement, Endpoint
 getEndpoint(uuid), jsPlumb
 getEndpoints(el), jsPlumb
 getInstanceIndex, jsPlumb
 getLabel
 getLabelOverlay
 getOverlay(overlayId)
 getOverlays
 getPaintStyle
 getParameter(key)
 getParameters
 getSelector([context],spec), jsPlumb
 getType
 getType(id,typeDescriptor), jsPlumb
 getUuid, Endpoint
H
 hasClass(el,clazz), jsPlumb
 hasType(typeId)
 hide(el,[changeEndpoints]), jsPlumb
 hideOverlay(overlayId)
 hideOverlays
I
 importDefaults(defaults), jsPlumb
 isConnectedTo(endpoint), Endpoint
 isDetachable, Connection
 isEditable, Connection
 isEnabled, Endpoint
 isFull, Endpoint
 isHoverSuspended, jsPlumb
 isReattach, Connection
 isSource(el), jsPlumb
 isSourceEnabled(el), jsPlumb
 isSuspendDrawing, jsPlumb
 isTarget(el), jsPlumb
 isTargetEnabled(el), jsPlumb
 isVisible
M
 makeSource(el,[params]), jsPlumb
 makeSources(els,[params],[referenceParams]), jsPlumb
 makeTarget(el,[params]), jsPlumb
 makeTargets(els,[params],[referenceParams]), jsPlumb
P
 paint(params), Endpoint
R
 ready(fn), jsPlumb
 reapplyTypes(data,[doNotRepaint])
 recalculateOffsets(el), jsPlumb
 registerConnectionType(typeId,type), jsPlumb
 registerConnectionTypes(types), jsPlumb
 registerEndpointType(typeId,type), jsPlumb
 registerEndpointTypes(types), jsPlumb
 remove(el), jsPlumb
 removeAllEndpoints(el,[recurse]), jsPlumb
 removeAllOverlays
 removeClass(el,clazz), jsPlumb
 removeOverlay(overlayId)
 removeType(typeId)
 repaint(el), jsPlumb
 repaintEverything, jsPlumb
 restoreDefaults, jsPlumb
- -
Removes a set of overlays by ID.
Removes a set of overlays by ID.
- - - -
Add class(es) to some element(s).
Adds a Connection to this Endpoint.
Adds an Endpoint to a given element or elements.
Adds a list of Endpoints to a given element or elements.
Adds an Overlay to the Connection.
Adds an Overlay to the Endpoint.
Adds the specified type to the Connection.
Adds the specified type to the Endpoint.
This is a wrapper around the supporting library’s animate function; it injects a call to jsPlumb in the ‘step’ function (creating the ‘step’ function if necessary).
- - - -
Bind to an event on the Connection.
Bind to an event on the Endpoint.
Bind to an event on jsPlumb.
- - - -
Establishes a Connection between two elements (or Endpoints, which are themselves registered to elements).
Connection constructor.
- - - -
Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too)
Deletes every Endpoint, and their associated Connections, in this instance of jsPlumb.
Detaches the given Connection from this Endpoint.
Detaches and then removes a Connection.
Detaches all Connections this Endpoint has.
Removes all an element’s Connections.
Remove all Connections from all elements, but leaves Endpoints in place.
Removes any connections from this Endpoint that are connected to the given target endpoint.
Detach this Endpoint from the Connection, but leave the Connection alive.
Initialises the draggability of some element or elements.
- - - -
Endpoint constructor.
Wraps the underlying library’s extend functionality.
- - - -
All connections, as a map of the form:
Gets all or a subset of connections currently managed by this jsPlumb instance.
Gets The underlying Connector for this Connection (eg.
Returns the default Connection type.
Returns the default Endpoint type.
Gets the default scope for connections and endpoints.
The DOM element this Endpoint is attached to.
Gets an Endpoint by UUID
Gets the list of Endpoints for a given element.
The “instance index” for this instance of jsPlumb.
Returns the label text for this Connection (or a function if you are labelling with a function).
The label text for this Endpoint (or a function if you are labelling with a function).
The underlying internal label overlay, which will exist if you specified a label on a connect call, or have called setLabel at any stage.
The underlying internal label overlay, which will exist if you specified a label on an addEndpoint call, or have called setLabel at any stage.
Gets an overlay, by ID.
Gets an overlay, by ID.
Gets all the overlays for this component.
An array of all Overlays for the component.
Gets the Connection’s paint style.
The Endpoint’s paint style.
Gets the named parameter; returns null if no parameter with that name is set.
Gets the named parameter; returns null if no parameter with that name is set.
Gets all parameters for the Connection
All of the Endpoint’s parameters.
This method takes the given selector spec and, using the current underlying library, turns it into a selector from that library.
The Endpoint’s current type(s)
The Endpoint’s current type(s)
Gets the given type’s specification.
The UUID for this Endpoint, if there is one.
- - - -
Checks if an element has some class.
Whether or not the Endpoint has the given type.
Whether or not the Endpoint has the given type.
Sets an element’s connections to be hidden.
Hides the overlay specified by the given id.
Hides the overlay specified by the given id.
Hides all Overlays
Hides all Overlays
- - - -
Imports all the given defaults into this instance of jsPlumb.
Returns whether or not this connection can be detached from its target/source endpoint.
Returns whether or not the Connection is editable.
True if the Endpoint is enabled for drag/drop connections enabled, false otherwise.
True if the Endpoint cannot accept any more Connections, false otherwise.
whether or not hover effects are currently suspended.
Returns whether or not this connection will be reattached after having been detached via the mouse and dropped.
Returns whether or not the given element is registered as a connection source.
Returns whether or not the given connection source is enabled.
Whether or not drawing is currently suspended.
Returns whether or not the given element is registered as a connection target.
Returns whether or not the given connection target is enabled.
Returns whether or not the Connection is currently visible.
Whether or not the Endpoint is currently visible.
- - - -
Makes some DOM element a Connection source, allowing you to drag connections from it without having to register any Endpoints on it first.
Makes all elements in some array or a selector connection sources.
Makes some DOM element a Connection target, allowing you to drag connections to it without having to register any Endpoints on it first.
Makes all elements in some array or a selector connection targets.
- - - -
Paints the Endpoint, recalculating offset and anchor positions if necessary.
- - - -
Helper method to bind a function to jsPlumb’s ready event.
Reapplies all of the current types, but with the given data.
Reapplies all of the current types, but with the given data.
Recalculates the offsets of all child elements of some element.
Registers the given connection type on this instance of jsPlumb.
Registers the given map of Connection types on this instance of jsPlumb.
Registers the given endpoint type on this instance of jsPlumb.
Registers the given map of Endpoint types on this instance of jsPlumb.
Removes the given element from the DOM, along with all Endpoints associated with it, and their connections.
Removes all Endpoints associated with a given element.
Removes all overlays from the Connection, and then repaints.
Removes all overlays from the Endpoint, and then repaints.
Remove class from some element(s).
Removes an overlay by ID.
Removes an overlay by ID.
Removes the specified type from the Connection.
Removes the specified type from the Endpoint.
Repaints an element and its connections.
Repaints all connections and endpoints.
Restores the default settings to “factory” values.
- -
- - - - - - - - - - - - \ No newline at end of file diff --git a/demo/apidocs/index/Functions2.html b/demo/apidocs/index/Functions2.html deleted file mode 100644 index 4a281ef23..000000000 --- a/demo/apidocs/index/Functions2.html +++ /dev/null @@ -1,41 +0,0 @@ - - -Function Index - - - - - - - - - -
Function Index
$#! · 0-9 · A · B · C · D · E · F · G · H · I · J · K · L · M · N · O · P · Q · R · S · T · U · V · W · X · Y · Z
S
 select(params), jsPlumb
 selectEndpoints(params), jsPlumb
 setAnchor(anchorParams,[doNotRepaint]), Endpoint
 setConnector(connector), Connection
 setDefaultScope(scope), jsPlumb
 setDetachable(detachable), Connection
 setDragAllowedWhenFull(allowed), Endpoint
 setDraggable(el,draggable), jsPlumb
 setEditable(editable), Connection
 setElement(el,[container]), Endpoint
 setEnabled(enabled), Endpoint
 setEndpoint(endpointSpec), Endpoint
 setHover(hover,[ignoreAttachedElements])
 setHoverPaintStyle(style,[doNotRepaint])
 setHoverSuspended(s), jsPlumb
 setId(el,newId,[doNotSetAttribute]), jsPlumb
 setIdChanged(oldId,newId), jsPlumb
 setLabel(label)
 setPaintStyle(style)
 setParameter(key,value)
 setParameters(parameters)
 setReattach(reattach), Connection
 setSourceEnabled(el,state), jsPlumb
 setSuspendDrawing(val,[repaintAfterwards]), jsPlumb
 setTargetEnabled(el,state), jsPlumb
 setType(typeId)
 setVisible(visible), Connection
 setVisible(visible,[doNotChangeConnections],[doNotNotifyOtherEndpoint]), Endpoint
 show(el,[changeEndpoints]), jsPlumb
 showOverlay(overlayId)
 showOverlays
T
 toggleDraggable(el), jsPlumb
 toggleSourceEnabled(el), jsPlumb
 toggleTargetEnabled(el), jsPlumb
 toggleType(typeId)
 toggleVisible(el,[changeEndpoints]), jsPlumb
U
 unbind([event]), jsPlumb
 unmakeEverySource, jsPlumb
 unmakeEveryTarget, jsPlumb
 unmakeSource(el), jsPlumb
 unmakeTarget(el), jsPlumb
- -
Selects a set of Connections, using the filter options from the getConnections method, and returns an object that allows you to perform an operation on all of the Connections at once.
Selects a set of Endpoints and returns an object that allows you to execute various different methods on them at once.
Sets the Endpoint’s anchor.
Sets the Connection’s connector (eg “Bezier”, “Flowchart”, etc).
Sets the default scope for Connections and Endpoints.
Sets whether or not this connection is detachable.
Sets whether or not connections can be dragged from this Endpoint once it is full.
Sets whether or not a given element is draggable, regardless of what any jsPlumb command may request.
Sets whether or not the Connection is editable.
Sets the DOM element this Endpoint is attached to.
Sets whether or not the Endpoint is enabled for drag/drop connections.
Sets the Endpoint’s visual representation and behaviour.
Sets/unsets the hover state of this Connection.
Sets/unsets the hover state of this Endpoint.
Sets the paint style to use when the mouse is hovering over the Connection.
Sets the paint style to use when the mouse is hovering over the Endpoint.
Sets whether or not hover effects should be suspended.
Changes the id of some element, adjusting all connections and endpoints
Notify jsPlumb that the element with oldId has had its id changed to newId.
Sets the Connection’s label.
Sets the Endpoint’s label.
Sets the Connection’s paint style and then repaints the Connection.
Sets the Endpoint’s paint style and then repaints the Endpoint.
Sets the named parameter to the given value.
Sets the named parameter to the given value.
Sets the Connection’s parameters.
Sets the Endpoint’s parameters.
Sets whether or not this connection will reattach after having been detached via the mouse and dropped.
Sets the enabled state of one or more elements that were previously made a connection source with the makeSource method.
Suspends drawing operations.
Sets the enabled state of one or more elements that were previously made a connection target with the makeTarget method.
Sets the type of the Connection.
Sets the specified type of the Endpoint.
Sets whether or not the Connection should be visible.
Sets whether or not the Endpoint is currently visible.
Sets an element’s connections to be visible.
Shows the overlay specified by the given id.
Shows the overlay specified by the given id.
Shows all Overlays
Shows all Overlays
- - - -
Toggles draggability (sic?)
Toggles the source enabled state of the given element or elements.
Toggles the target enabled state of the given element or elements.
Toggles the specified type on the Connection.
Toggles the specified type on the Endpoint.
Toggles visibility of an element’s Connections.
- - - -
Clears either all listeners, or listeners for some specific event.
Resets all elements in this instance of jsPlumb so that none of them are connection sources.
Resets all elements in this instance of jsPlumb so that none of them are connection targets.
Sets the given element to no longer be a connection source.
Sets the given element to no longer be a connection target.
- -
- - - - - - - - - - - - \ No newline at end of file diff --git a/demo/apidocs/index/General.html b/demo/apidocs/index/General.html deleted file mode 100644 index 4ea1dba41..000000000 --- a/demo/apidocs/index/General.html +++ /dev/null @@ -1,85 +0,0 @@ - - -Index - - - - - - - - - -
Index
$#! · 0-9 · A · B · C · D · E · F · G · H · I · J · K · L · M · N · O · P · Q · R · S · T · U · V · W · X · Y · Z
$#!
 )
A
 addClass(el,clazz), jsPlumb
 addConnection(connection), Endpoint
 addEndpoint(el,[params],[referenceParams]), jsPlumb
 addEndpoints(target,endpoints,[referenceParams]), jsPlumb
 addOverlay(overlaySpec)
 addType(typeId)
 animate, jsPlumb
 Assign, jsPlumb.Anchors
 AutoDefault, jsPlumb.Anchors
B
 bind(event,callback)
 Bottom, jsPlumb.Anchors
 BottomLeft, jsPlumb.Anchors
 BottomRight, jsPlumb.Anchors
C
 canvas, Endpoint
 Center, jsPlumb.Anchors
 connect(params,[referenceParams]), jsPlumb
 Connection
 connections, Endpoint
 connectorClass, jsPlumb
 Continuous, jsPlumb.Anchors
 ContinuousBottom, jsPlumb.Anchors
 ContinuousLeft, jsPlumb.Anchors
 ContinuousRight, jsPlumb.Anchors
 ContinuousTop, jsPlumb.Anchors
D
 Defaults, jsPlumb
 deleteEndpoint(object), jsPlumb
 deleteEveryEndpoint, jsPlumb
 detach(connection,[ignoreTarget]), Endpoint
 detach(connection,[params]), jsPlumb
 detachAll([fireEvent]), Endpoint
 detachAllConnections(el,[params]), jsPlumb
 detachEveryConnection([params]), jsPlumb
 detachFrom(targetEndpoint,[fireEvent]), Endpoint
 detachFromConnection(connection), Endpoint
 draggable(el,[options]), jsPlumb
 draggingClass, jsPlumb
E
 elementDraggingClass, jsPlumb
 Endpoint
 endpointAnchorClassPrefix, jsPlumb
 endpointClass, jsPlumb
 endpointConnectedClass, jsPlumb
 endpointDropAllowedClass, jsPlumb
 endpointDropForbiddenClass, jsPlumb
 endpointFullClass, jsPlumb
 endpoints, Connection
 extend(o1,o2), jsPlumb
F
 Functions
G
 getAllConnections, jsPlumb
 getConnections(params), jsPlumb
 getConnector, Connection
 getDefaultConnectionType, jsPlumb
 getDefaultEndpointType, jsPlumb
 getDefaultScope, jsPlumb
 getElement, Endpoint
 getEndpoint(uuid), jsPlumb
 getEndpoints(el), jsPlumb
 getInstanceIndex, jsPlumb
 getLabel
 getLabelOverlay
 getOverlay(overlayId)
 getOverlays
 getPaintStyle
 getParameter(key)
 getParameters
 getSelector([context],spec), jsPlumb
 getType
 getType(id,typeDescriptor), jsPlumb
 getUuid, Endpoint
H
 hasClass(el,clazz), jsPlumb
 hasType(typeId)
 hide(el,[changeEndpoints]), jsPlumb
 hideOverlay(overlayId)
 hideOverlays
 hoverClass, jsPlumb
I
 importDefaults(defaults), jsPlumb
 isConnectedTo(endpoint), Endpoint
 isDetachable, Connection
 isEditable, Connection
 isEnabled, Endpoint
 isFull, Endpoint
 isHoverSuspended, jsPlumb
 isReattach, Connection
 isSource(el), jsPlumb
 isSourceEnabled(el), jsPlumb
 isSuspendDrawing, jsPlumb
 isTarget(el), jsPlumb
 isTargetEnabled(el), jsPlumb
 isVisible
J
 jsPlumb
L
 Left, jsPlumb.Anchors
M
 makeSource(el,[params]), jsPlumb
 makeSources(els,[params],[referenceParams]), jsPlumb
 makeTarget(el,[params]), jsPlumb
 makeTargets(els,[params],[referenceParams]), jsPlumb
O
 overlayClass, jsPlumb
 overlays
- -
Removes a set of overlays by ID.
Removes a set of overlays by ID.
- - - -
Add class(es) to some element(s).
Adds a Connection to this Endpoint.
Adds an Endpoint to a given element or elements.
Adds a list of Endpoints to a given element or elements.
Adds an Overlay to the Connection.
Adds an Overlay to the Endpoint.
Adds the specified type to the Connection.
Adds the specified type to the Endpoint.
This is a wrapper around the supporting library’s animate function; it injects a call to jsPlumb in the ‘step’ function (creating the ‘step’ function if necessary).
An Anchor whose location is assigned at connection time, through an AnchorPositionFinder.
- - - -
Bind to an event on the Connection.
Bind to an event on the Endpoint.
Bind to an event on jsPlumb.
An Anchor that is located at the bottom center of the element.
An Anchor that is located at the bototm left corner of the element.
An Anchor that is located at the bottom right corner of the element.
- - - -
The Endpoint’s drawing area.
An Anchor that is located at the center of the element.
Establishes a Connection between two elements (or Endpoints, which are themselves registered to elements).
The connecting line between two elements.
Connection constructor.
List of Connections this Endpoint is attached to.
The CSS class to set on Connection elements.
An Anchor that tracks the other element in the connection, choosing the closest face.
A continuous anchor that uses only the bottom edge of the element.
A continuous anchor that uses only the left edge of the element.
A continuous anchor that uses only the right edge of the element.
A continuous anchor that uses only the top edge of the element.
- - - -
These are the default settings for jsPlumb.
Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too)
Deletes every Endpoint, and their associated Connections, in this instance of jsPlumb.
Detaches the given Connection from this Endpoint.
Detaches and then removes a Connection.
Detaches all Connections this Endpoint has.
Removes all an element’s Connections.
Remove all Connections from all elements, but leaves Endpoints in place.
Removes any connections from this Endpoint that are connected to the given target endpoint.
Detach this Endpoint from the Connection, but leave the Connection alive.
Initialises the draggability of some element or elements.
The CSS class to set on connections that are being dragged.
- - - -
The CSS class to set on connections whose source or target element is being dragged, and on their endpoints too.
Models an endpoint.
Endpoint constructor.
The prefix for the CSS class to set on endpoints that have dynamic anchors whose individual locations have declared an associated CSS class.
The CSS class to set on Endpoint elements.
The CSS class to set on an Endpoint element when its Endpoint has at least one connection.
The CSS class to set on an Endpoint on which a drop will be allowed (during drag and drop).
The CSS class to set on an Endpoint on which a drop will be forbidden (during drag and drop).
The CSS class to set on a full Endpoint element.
Array of Endpoints.
Wraps the underlying library’s extend functionality.
- - - - - - - -
All connections, as a map of the form:
Gets all or a subset of connections currently managed by this jsPlumb instance.
Gets The underlying Connector for this Connection (eg.
Returns the default Connection type.
Returns the default Endpoint type.
Gets the default scope for connections and endpoints.
The DOM element this Endpoint is attached to.
Gets an Endpoint by UUID
Gets the list of Endpoints for a given element.
The “instance index” for this instance of jsPlumb.
Returns the label text for this Connection (or a function if you are labelling with a function).
The label text for this Endpoint (or a function if you are labelling with a function).
The underlying internal label overlay, which will exist if you specified a label on a connect call, or have called setLabel at any stage.
The underlying internal label overlay, which will exist if you specified a label on an addEndpoint call, or have called setLabel at any stage.
Gets an overlay, by ID.
Gets an overlay, by ID.
Gets all the overlays for this component.
An array of all Overlays for the component.
Gets the Connection’s paint style.
The Endpoint’s paint style.
Gets the named parameter; returns null if no parameter with that name is set.
Gets the named parameter; returns null if no parameter with that name is set.
Gets all parameters for the Connection
All of the Endpoint’s parameters.
This method takes the given selector spec and, using the current underlying library, turns it into a selector from that library.
The Endpoint’s current type(s)
The Endpoint’s current type(s)
Gets the given type’s specification.
The UUID for this Endpoint, if there is one.
- - - -
Checks if an element has some class.
Whether or not the Endpoint has the given type.
Whether or not the Endpoint has the given type.
Sets an element’s connections to be hidden.
Hides the overlay specified by the given id.
Hides the overlay specified by the given id.
Hides all Overlays
Hides all Overlays
The CSS class to set on Connection or Endpoint elements when hovering.
- - - -
Imports all the given defaults into this instance of jsPlumb.
Returns whether or not this connection can be detached from its target/source endpoint.
Returns whether or not the Connection is editable.
True if the Endpoint is enabled for drag/drop connections enabled, false otherwise.
True if the Endpoint cannot accept any more Connections, false otherwise.
whether or not hover effects are currently suspended.
Returns whether or not this connection will be reattached after having been detached via the mouse and dropped.
Returns whether or not the given element is registered as a connection source.
Returns whether or not the given connection source is enabled.
Whether or not drawing is currently suspended.
Returns whether or not the given element is registered as a connection target.
Returns whether or not the given connection target is enabled.
Returns whether or not the Connection is currently visible.
Whether or not the Endpoint is currently visible.
- - - -
The jsPlumb engine, of which there is one instance registered as a static object in the window.
- - - -
An Anchor that is located at the left middle of the element.
- - - -
Makes some DOM element a Connection source, allowing you to drag connections from it without having to register any Endpoints on it first.
Makes all elements in some array or a selector connection sources.
Makes some DOM element a Connection target, allowing you to drag connections to it without having to register any Endpoints on it first.
Makes all elements in some array or a selector connection targets.
- - - -
The CSS class to set on an Overlay that is an HTML element.
List of Overlays for this component.
List of Overlays for this Endpoint.
- -
- - - - - - - - - - - - \ No newline at end of file diff --git a/demo/apidocs/index/General2.html b/demo/apidocs/index/General2.html deleted file mode 100644 index 4b2523a2a..000000000 --- a/demo/apidocs/index/General2.html +++ /dev/null @@ -1,49 +0,0 @@ - - -Index - - - - - - - - - -
Index
$#! · 0-9 · A · B · C · D · E · F · G · H · I · J · K · L · M · N · O · P · Q · R · S · T · U · V · W · X · Y · Z
P
 paint(params), Endpoint
 Perimeter, jsPlumb.Anchors
 Properties
R
 ready(fn), jsPlumb
 reapplyTypes(data,[doNotRepaint])
 recalculateOffsets(el), jsPlumb
 registerConnectionType(typeId,type), jsPlumb
 registerConnectionTypes(types), jsPlumb
 registerEndpointType(typeId,type), jsPlumb
 registerEndpointTypes(types), jsPlumb
 remove(el), jsPlumb
 removeAllEndpoints(el,[recurse]), jsPlumb
 removeAllOverlays
 removeClass(el,clazz), jsPlumb
 removeOverlay(overlayId)
 removeType(typeId)
 repaint(el), jsPlumb
 repaintEverything, jsPlumb
 restoreDefaults, jsPlumb
 Right, jsPlumb.Anchors
S
 scope
 select(params), jsPlumb
 selectEndpoints(params), jsPlumb
 setAnchor(anchorParams,[doNotRepaint]), Endpoint
 setConnector(connector), Connection
 setDefaultScope(scope), jsPlumb
 setDetachable(detachable), Connection
 setDragAllowedWhenFull(allowed), Endpoint
 setDraggable(el,draggable), jsPlumb
 setEditable(editable), Connection
 setElement(el,[container]), Endpoint
 setEnabled(enabled), Endpoint
 setEndpoint(endpointSpec), Endpoint
 setHover(hover,[ignoreAttachedElements])
 setHoverPaintStyle(style,[doNotRepaint])
 setHoverSuspended(s), jsPlumb
 setId(el,newId,[doNotSetAttribute]), jsPlumb
 setIdChanged(oldId,newId), jsPlumb
 setLabel(label)
 setPaintStyle(style)
 setParameter(key,value)
 setParameters(parameters)
 setReattach(reattach), Connection
 setSourceEnabled(el,state), jsPlumb
 setSuspendDrawing(val,[repaintAfterwards]), jsPlumb
 setTargetEnabled(el,state), jsPlumb
 setType(typeId)
 setVisible(visible), Connection
 setVisible(visible,[doNotChangeConnections],[doNotNotifyOtherEndpoint]), Endpoint
 show(el,[changeEndpoints]), jsPlumb
 showOverlay(overlayId)
 showOverlays
 source, Connection
 sourceId, Connection
T
 target, Connection
 targetId, Connection
 toggleDraggable(el), jsPlumb
 toggleSourceEnabled(el), jsPlumb
 toggleTargetEnabled(el), jsPlumb
 toggleType(typeId)
 toggleVisible(el,[changeEndpoints]), jsPlumb
 Top, jsPlumb.Anchors
 TopLeft, jsPlumb.Anchors
 TopRight, jsPlumb.Anchors
U
 unbind([event]), jsPlumb
 unmakeEverySource, jsPlumb
 unmakeEveryTarget, jsPlumb
 unmakeSource(el), jsPlumb
 unmakeTarget(el), jsPlumb
- -
Paints the Endpoint, recalculating offset and anchor positions if necessary.
An Anchor that tracks the perimeter of some shape, approximating it with a given number of dynamically positioned locations.
- - - -
Helper method to bind a function to jsPlumb’s ready event.
Reapplies all of the current types, but with the given data.
Reapplies all of the current types, but with the given data.
Recalculates the offsets of all child elements of some element.
Registers the given connection type on this instance of jsPlumb.
Registers the given map of Connection types on this instance of jsPlumb.
Registers the given endpoint type on this instance of jsPlumb.
Registers the given map of Endpoint types on this instance of jsPlumb.
Removes the given element from the DOM, along with all Endpoints associated with it, and their connections.
Removes all Endpoints associated with a given element.
Removes all overlays from the Connection, and then repaints.
Removes all overlays from the Endpoint, and then repaints.
Remove class from some element(s).
Removes an overlay by ID.
Removes an overlay by ID.
Removes the specified type from the Connection.
Removes the specified type from the Endpoint.
Repaints an element and its connections.
Repaints all connections and endpoints.
Restores the default settings to “factory” values.
An Anchor that is located at the right middle of the element.
- - - -
Optional scope descriptor for the connection.
Scope descriptor for this Endpoint.
Selects a set of Connections, using the filter options from the getConnections method, and returns an object that allows you to perform an operation on all of the Connections at once.
Selects a set of Endpoints and returns an object that allows you to execute various different methods on them at once.
Sets the Endpoint’s anchor.
Sets the Connection’s connector (eg “Bezier”, “Flowchart”, etc).
Sets the default scope for Connections and Endpoints.
Sets whether or not this connection is detachable.
Sets whether or not connections can be dragged from this Endpoint once it is full.
Sets whether or not a given element is draggable, regardless of what any jsPlumb command may request.
Sets whether or not the Connection is editable.
Sets the DOM element this Endpoint is attached to.
Sets whether or not the Endpoint is enabled for drag/drop connections.
Sets the Endpoint’s visual representation and behaviour.
Sets/unsets the hover state of this Connection.
Sets/unsets the hover state of this Endpoint.
Sets the paint style to use when the mouse is hovering over the Connection.
Sets the paint style to use when the mouse is hovering over the Endpoint.
Sets whether or not hover effects should be suspended.
Changes the id of some element, adjusting all connections and endpoints
Notify jsPlumb that the element with oldId has had its id changed to newId.
Sets the Connection’s label.
Sets the Endpoint’s label.
Sets the Connection’s paint style and then repaints the Connection.
Sets the Endpoint’s paint style and then repaints the Endpoint.
Sets the named parameter to the given value.
Sets the named parameter to the given value.
Sets the Connection’s parameters.
Sets the Endpoint’s parameters.
Sets whether or not this connection will reattach after having been detached via the mouse and dropped.
Sets the enabled state of one or more elements that were previously made a connection source with the makeSource method.
Suspends drawing operations.
Sets the enabled state of one or more elements that were previously made a connection target with the makeTarget method.
Sets the type of the Connection.
Sets the specified type of the Endpoint.
Sets whether or not the Connection should be visible.
Sets whether or not the Endpoint is currently visible.
Sets an element’s connections to be visible.
Shows the overlay specified by the given id.
Shows the overlay specified by the given id.
Shows all Overlays
Shows all Overlays
The source element for this Connection.
Id of the source element in the connection.
- - - -
The target element for this Connection.
Id of the target element in the connection.
Toggles draggability (sic?)
Toggles the source enabled state of the given element or elements.
Toggles the target enabled state of the given element or elements.
Toggles the specified type on the Connection.
Toggles the specified type on the Endpoint.
Toggles visibility of an element’s Connections.
An Anchor that is located at the top center of the element.
An Anchor that is located at the top left corner of the element.
An Anchor that is located at the top right corner of the element.
- - - -
Clears either all listeners, or listeners for some specific event.
Resets all elements in this instance of jsPlumb so that none of them are connection sources.
Resets all elements in this instance of jsPlumb so that none of them are connection targets.
Sets the given element to no longer be a connection source.
Sets the given element to no longer be a connection target.
- -
- - - - - - - - - - - - \ No newline at end of file diff --git a/demo/apidocs/index/Properties.html b/demo/apidocs/index/Properties.html deleted file mode 100644 index 9098bf94a..000000000 --- a/demo/apidocs/index/Properties.html +++ /dev/null @@ -1,77 +0,0 @@ - - -Property Index - - - - - - - - - -
Property Index
$#! · 0-9 · A · B · C · D · E · F · G · H · I · J · K · L · M · N · O · P · Q · R · S · T · U · V · W · X · Y · Z
A
 Assign, jsPlumb.Anchors
 AutoDefault, jsPlumb.Anchors
B
 Bottom, jsPlumb.Anchors
 BottomLeft, jsPlumb.Anchors
 BottomRight, jsPlumb.Anchors
C
 canvas, Endpoint
 Center, jsPlumb.Anchors
 connections, Endpoint
 connectorClass, jsPlumb
 Continuous, jsPlumb.Anchors
 ContinuousBottom, jsPlumb.Anchors
 ContinuousLeft, jsPlumb.Anchors
 ContinuousRight, jsPlumb.Anchors
 ContinuousTop, jsPlumb.Anchors
D
 Defaults, jsPlumb
 draggingClass, jsPlumb
E
 elementDraggingClass, jsPlumb
 endpointAnchorClassPrefix, jsPlumb
 endpointClass, jsPlumb
 endpointConnectedClass, jsPlumb
 endpointDropAllowedClass, jsPlumb
 endpointDropForbiddenClass, jsPlumb
 endpointFullClass, jsPlumb
 endpoints, Connection
H
 hoverClass, jsPlumb
L
 Left, jsPlumb.Anchors
O
 overlayClass, jsPlumb
 overlays
P
 Perimeter, jsPlumb.Anchors
R
 Right, jsPlumb.Anchors
S
 scope
 source, Connection
 sourceId, Connection
T
 target, Connection
 targetId, Connection
 Top, jsPlumb.Anchors
 TopLeft, jsPlumb.Anchors
 TopRight, jsPlumb.Anchors
- -
An Anchor whose location is assigned at connection time, through an AnchorPositionFinder.
- - - -
An Anchor that is located at the bottom center of the element.
An Anchor that is located at the bototm left corner of the element.
An Anchor that is located at the bottom right corner of the element.
- - - -
The Endpoint’s drawing area.
An Anchor that is located at the center of the element.
List of Connections this Endpoint is attached to.
The CSS class to set on Connection elements.
An Anchor that tracks the other element in the connection, choosing the closest face.
A continuous anchor that uses only the bottom edge of the element.
A continuous anchor that uses only the left edge of the element.
A continuous anchor that uses only the right edge of the element.
A continuous anchor that uses only the top edge of the element.
- - - -
These are the default settings for jsPlumb.
The CSS class to set on connections that are being dragged.
- - - -
The CSS class to set on connections whose source or target element is being dragged, and on their endpoints too.
The prefix for the CSS class to set on endpoints that have dynamic anchors whose individual locations have declared an associated CSS class.
The CSS class to set on Endpoint elements.
The CSS class to set on an Endpoint element when its Endpoint has at least one connection.
The CSS class to set on an Endpoint on which a drop will be allowed (during drag and drop).
The CSS class to set on an Endpoint on which a drop will be forbidden (during drag and drop).
The CSS class to set on a full Endpoint element.
Array of Endpoints.
- - - -
The CSS class to set on Connection or Endpoint elements when hovering.
- - - -
An Anchor that is located at the left middle of the element.
- - - -
The CSS class to set on an Overlay that is an HTML element.
List of Overlays for this component.
List of Overlays for this Endpoint.
- - - -
An Anchor that tracks the perimeter of some shape, approximating it with a given number of dynamically positioned locations.
- - - -
An Anchor that is located at the right middle of the element.
- - - -
Optional scope descriptor for the connection.
Scope descriptor for this Endpoint.
The source element for this Connection.
Id of the source element in the connection.
- - - -
The target element for this Connection.
Id of the target element in the connection.
An Anchor that is located at the top center of the element.
An Anchor that is located at the top left corner of the element.
An Anchor that is located at the top right corner of the element.
- -
- - - - - - - - - - - - \ No newline at end of file diff --git a/demo/apidocs/javascript/main.js b/demo/apidocs/javascript/main.js deleted file mode 100644 index 3f42acde6..000000000 --- a/demo/apidocs/javascript/main.js +++ /dev/null @@ -1,841 +0,0 @@ -// This file is part of Natural Docs, which is Copyright © 2003-2010 Greg Valure -// Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL) -// Refer to License.txt for the complete details - -// This file may be distributed with documentation files generated by Natural Docs. -// Such documentation is not covered by Natural Docs' copyright and licensing, -// and may have its own copyright and distribution terms as decided by its author. - - -// -// Browser Styles -// ____________________________________________________________________________ - -var agt=navigator.userAgent.toLowerCase(); -var browserType; -var browserVer; - -if (agt.indexOf("opera") != -1) - { - browserType = "Opera"; - - if (agt.indexOf("opera 7") != -1 || agt.indexOf("opera/7") != -1) - { browserVer = "Opera7"; } - else if (agt.indexOf("opera 8") != -1 || agt.indexOf("opera/8") != -1) - { browserVer = "Opera8"; } - else if (agt.indexOf("opera 9") != -1 || agt.indexOf("opera/9") != -1) - { browserVer = "Opera9"; } - } - -else if (agt.indexOf("applewebkit") != -1) - { - browserType = "Safari"; - - if (agt.indexOf("version/3") != -1) - { browserVer = "Safari3"; } - else if (agt.indexOf("safari/4") != -1) - { browserVer = "Safari2"; } - } - -else if (agt.indexOf("khtml") != -1) - { - browserType = "Konqueror"; - } - -else if (agt.indexOf("msie") != -1) - { - browserType = "IE"; - - if (agt.indexOf("msie 6") != -1) - { browserVer = "IE6"; } - else if (agt.indexOf("msie 7") != -1) - { browserVer = "IE7"; } - } - -else if (agt.indexOf("gecko") != -1) - { - browserType = "Firefox"; - - if (agt.indexOf("rv:1.7") != -1) - { browserVer = "Firefox1"; } - else if (agt.indexOf("rv:1.8)") != -1 || agt.indexOf("rv:1.8.0") != -1) - { browserVer = "Firefox15"; } - else if (agt.indexOf("rv:1.8.1") != -1) - { browserVer = "Firefox2"; } - } - - -// -// Support Functions -// ____________________________________________________________________________ - - -function GetXPosition(item) - { - var position = 0; - - if (item.offsetWidth != null) - { - while (item != document.body && item != null) - { - position += item.offsetLeft; - item = item.offsetParent; - }; - }; - - return position; - }; - - -function GetYPosition(item) - { - var position = 0; - - if (item.offsetWidth != null) - { - while (item != document.body && item != null) - { - position += item.offsetTop; - item = item.offsetParent; - }; - }; - - return position; - }; - - -function MoveToPosition(item, x, y) - { - // Opera 5 chokes on the px extension, so it can use the Microsoft one instead. - - if (item.style.left != null) - { - item.style.left = x + "px"; - item.style.top = y + "px"; - } - else if (item.style.pixelLeft != null) - { - item.style.pixelLeft = x; - item.style.pixelTop = y; - }; - }; - - -// -// Menu -// ____________________________________________________________________________ - - -function ToggleMenu(id) - { - if (!window.document.getElementById) - { return; }; - - var display = window.document.getElementById(id).style.display; - - if (display == "none") - { display = "block"; } - else - { display = "none"; } - - window.document.getElementById(id).style.display = display; - } - -function HideAllBut(ids, max) - { - if (document.getElementById) - { - ids.sort( function(a,b) { return a - b; } ); - var number = 1; - - while (number < max) - { - if (ids.length > 0 && number == ids[0]) - { ids.shift(); } - else - { - document.getElementById("MGroupContent" + number).style.display = "none"; - }; - - number++; - }; - }; - } - - -// -// Tooltips -// ____________________________________________________________________________ - - -var tooltipTimer = 0; - -function ShowTip(event, tooltipID, linkID) - { - if (tooltipTimer) - { clearTimeout(tooltipTimer); }; - - var docX = event.clientX + window.pageXOffset; - var docY = event.clientY + window.pageYOffset; - - var showCommand = "ReallyShowTip('" + tooltipID + "', '" + linkID + "', " + docX + ", " + docY + ")"; - - tooltipTimer = setTimeout(showCommand, 1000); - } - -function ReallyShowTip(tooltipID, linkID, docX, docY) - { - tooltipTimer = 0; - - var tooltip; - var link; - - if (document.getElementById) - { - tooltip = document.getElementById(tooltipID); - link = document.getElementById(linkID); - } -/* else if (document.all) - { - tooltip = eval("document.all['" + tooltipID + "']"); - link = eval("document.all['" + linkID + "']"); - } -*/ - if (tooltip) - { - var left = GetXPosition(link); - var top = GetYPosition(link); - top += link.offsetHeight; - - - // The fallback method is to use the mouse X and Y relative to the document. We use a separate if and test if its a number - // in case some browser snuck through the above if statement but didn't support everything. - - if (!isFinite(top) || top == 0) - { - left = docX; - top = docY; - } - - // Some spacing to get it out from under the cursor. - - top += 10; - - // Make sure the tooltip doesnt get smushed by being too close to the edge, or in some browsers, go off the edge of the - // page. We do it here because Konqueror does get offsetWidth right even if it doesnt get the positioning right. - - if (tooltip.offsetWidth != null) - { - var width = tooltip.offsetWidth; - var docWidth = document.body.clientWidth; - - if (left + width > docWidth) - { left = docWidth - width - 1; } - - // If there's a horizontal scroll bar we could go past zero because it's using the page width, not the window width. - if (left < 0) - { left = 0; }; - } - - MoveToPosition(tooltip, left, top); - tooltip.style.visibility = "visible"; - } - } - -function HideTip(tooltipID) - { - if (tooltipTimer) - { - clearTimeout(tooltipTimer); - tooltipTimer = 0; - } - - var tooltip; - - if (document.getElementById) - { tooltip = document.getElementById(tooltipID); } - else if (document.all) - { tooltip = eval("document.all['" + tooltipID + "']"); } - - if (tooltip) - { tooltip.style.visibility = "hidden"; } - } - - -// -// Blockquote fix for IE -// ____________________________________________________________________________ - - -function NDOnLoad() - { - if (browserVer == "IE6") - { - var scrollboxes = document.getElementsByTagName('blockquote'); - - if (scrollboxes.item(0)) - { - NDDoResize(); - window.onresize=NDOnResize; - }; - }; - }; - - -var resizeTimer = 0; - -function NDOnResize() - { - if (resizeTimer != 0) - { clearTimeout(resizeTimer); }; - - resizeTimer = setTimeout(NDDoResize, 250); - }; - - -function NDDoResize() - { - var scrollboxes = document.getElementsByTagName('blockquote'); - - var i; - var item; - - i = 0; - while (item = scrollboxes.item(i)) - { - item.style.width = 100; - i++; - }; - - i = 0; - while (item = scrollboxes.item(i)) - { - item.style.width = item.parentNode.offsetWidth; - i++; - }; - - clearTimeout(resizeTimer); - resizeTimer = 0; - } - - - -/* ________________________________________________________________________________________________________ - - Class: SearchPanel - ________________________________________________________________________________________________________ - - A class handling everything associated with the search panel. - - Parameters: - - name - The name of the global variable that will be storing this instance. Is needed to be able to set timeouts. - mode - The mode the search is going to work in. Pass CommandLineOption()>, so the - value will be something like "HTML" or "FramedHTML". - - ________________________________________________________________________________________________________ -*/ - - -function SearchPanel(name, mode, resultsPath) - { - if (!name || !mode || !resultsPath) - { alert("Incorrect parameters to SearchPanel."); }; - - - // Group: Variables - // ________________________________________________________________________ - - /* - var: name - The name of the global variable that will be storing this instance of the class. - */ - this.name = name; - - /* - var: mode - The mode the search is going to work in, such as "HTML" or "FramedHTML". - */ - this.mode = mode; - - /* - var: resultsPath - The relative path from the current HTML page to the results page directory. - */ - this.resultsPath = resultsPath; - - /* - var: keyTimeout - The timeout used between a keystroke and when a search is performed. - */ - this.keyTimeout = 0; - - /* - var: keyTimeoutLength - The length of in thousandths of a second. - */ - this.keyTimeoutLength = 500; - - /* - var: lastSearchValue - The last search string executed, or an empty string if none. - */ - this.lastSearchValue = ""; - - /* - var: lastResultsPage - The last results page. The value is only relevant if is set. - */ - this.lastResultsPage = ""; - - /* - var: deactivateTimeout - - The timeout used between when a control is deactivated and when the entire panel is deactivated. Is necessary - because a control may be deactivated in favor of another control in the same panel, in which case it should stay - active. - */ - this.deactivateTimout = 0; - - /* - var: deactivateTimeoutLength - The length of in thousandths of a second. - */ - this.deactivateTimeoutLength = 200; - - - - - // Group: DOM Elements - // ________________________________________________________________________ - - - // Function: DOMSearchField - this.DOMSearchField = function() - { return document.getElementById("MSearchField"); }; - - // Function: DOMSearchType - this.DOMSearchType = function() - { return document.getElementById("MSearchType"); }; - - // Function: DOMPopupSearchResults - this.DOMPopupSearchResults = function() - { return document.getElementById("MSearchResults"); }; - - // Function: DOMPopupSearchResultsWindow - this.DOMPopupSearchResultsWindow = function() - { return document.getElementById("MSearchResultsWindow"); }; - - // Function: DOMSearchPanel - this.DOMSearchPanel = function() - { return document.getElementById("MSearchPanel"); }; - - - - - // Group: Event Handlers - // ________________________________________________________________________ - - - /* - Function: OnSearchFieldFocus - Called when focus is added or removed from the search field. - */ - this.OnSearchFieldFocus = function(isActive) - { - this.Activate(isActive); - }; - - - /* - Function: OnSearchFieldChange - Called when the content of the search field is changed. - */ - this.OnSearchFieldChange = function() - { - if (this.keyTimeout) - { - clearTimeout(this.keyTimeout); - this.keyTimeout = 0; - }; - - var searchValue = this.DOMSearchField().value.replace(/ +/g, ""); - - if (searchValue != this.lastSearchValue) - { - if (searchValue != "") - { - this.keyTimeout = setTimeout(this.name + ".Search()", this.keyTimeoutLength); - } - else - { - if (this.mode == "HTML") - { this.DOMPopupSearchResultsWindow().style.display = "none"; }; - this.lastSearchValue = ""; - }; - }; - }; - - - /* - Function: OnSearchTypeFocus - Called when focus is added or removed from the search type. - */ - this.OnSearchTypeFocus = function(isActive) - { - this.Activate(isActive); - }; - - - /* - Function: OnSearchTypeChange - Called when the search type is changed. - */ - this.OnSearchTypeChange = function() - { - var searchValue = this.DOMSearchField().value.replace(/ +/g, ""); - - if (searchValue != "") - { - this.Search(); - }; - }; - - - - // Group: Action Functions - // ________________________________________________________________________ - - - /* - Function: CloseResultsWindow - Closes the results window. - */ - this.CloseResultsWindow = function() - { - this.DOMPopupSearchResultsWindow().style.display = "none"; - this.Activate(false, true); - }; - - - /* - Function: Search - Performs a search. - */ - this.Search = function() - { - this.keyTimeout = 0; - - var searchValue = this.DOMSearchField().value.replace(/^ +/, ""); - var searchTopic = this.DOMSearchType().value; - - var pageExtension = searchValue.substr(0,1); - - if (pageExtension.match(/^[a-z]/i)) - { pageExtension = pageExtension.toUpperCase(); } - else if (pageExtension.match(/^[0-9]/)) - { pageExtension = 'Numbers'; } - else - { pageExtension = "Symbols"; }; - - var resultsPage; - var resultsPageWithSearch; - var hasResultsPage; - - // indexSectionsWithContent is defined in searchdata.js - if (indexSectionsWithContent[searchTopic][pageExtension] == true) - { - resultsPage = this.resultsPath + '/' + searchTopic + pageExtension + '.html'; - resultsPageWithSearch = resultsPage+'?'+escape(searchValue); - hasResultsPage = true; - } - else - { - resultsPage = this.resultsPath + '/NoResults.html'; - resultsPageWithSearch = resultsPage; - hasResultsPage = false; - }; - - var resultsFrame; - if (this.mode == "HTML") - { resultsFrame = window.frames.MSearchResults; } - else if (this.mode == "FramedHTML") - { resultsFrame = window.top.frames['Content']; }; - - - if (resultsPage != this.lastResultsPage || - - // Bug in IE. If everything becomes hidden in a run, none of them will be able to be reshown in the next for some - // reason. It counts the right number of results, and you can even read the display as "block" after setting it, but it - // just doesn't work in IE 6 or IE 7. So if we're on the right page but the previous search had no results, reload the - // page anyway to get around the bug. - (browserType == "IE" && hasResultsPage && - (!resultsFrame.searchResults || resultsFrame.searchResults.lastMatchCount == 0)) ) - - { - resultsFrame.location.href = resultsPageWithSearch; - } - - // So if the results page is right and there's no IE bug, reperform the search on the existing page. We have to check if there - // are results because NoResults.html doesn't have any JavaScript, and it would be useless to do anything on that page even - // if it did. - else if (hasResultsPage) - { - // We need to check if this exists in case the frame is present but didn't finish loading. - if (resultsFrame.searchResults) - { resultsFrame.searchResults.Search(searchValue); } - - // Otherwise just reload instead of waiting. - else - { resultsFrame.location.href = resultsPageWithSearch; }; - }; - - - var domPopupSearchResultsWindow = this.DOMPopupSearchResultsWindow(); - - if (this.mode == "HTML" && domPopupSearchResultsWindow.style.display != "block") - { - var domSearchType = this.DOMSearchType(); - - var left = GetXPosition(domSearchType); - var top = GetYPosition(domSearchType) + domSearchType.offsetHeight; - - MoveToPosition(domPopupSearchResultsWindow, left, top); - domPopupSearchResultsWindow.style.display = 'block'; - }; - - - this.lastSearchValue = searchValue; - this.lastResultsPage = resultsPage; - }; - - - - // Group: Activation Functions - // Functions that handle whether the entire panel is active or not. - // ________________________________________________________________________ - - - /* - Function: Activate - - Activates or deactivates the search panel, resetting things to their default values if necessary. You can call this on every - control's OnBlur() and it will handle not deactivating the entire panel when focus is just switching between them transparently. - - Parameters: - - isActive - Whether you're activating or deactivating the panel. - ignoreDeactivateDelay - Set if you're positive the action will deactivate the panel and thus want to skip the delay. - */ - this.Activate = function(isActive, ignoreDeactivateDelay) - { - // We want to ignore isActive being false while the results window is open. - if (isActive || (this.mode == "HTML" && this.DOMPopupSearchResultsWindow().style.display == "block")) - { - if (this.inactivateTimeout) - { - clearTimeout(this.inactivateTimeout); - this.inactivateTimeout = 0; - }; - - this.DOMSearchPanel().className = 'MSearchPanelActive'; - - var searchField = this.DOMSearchField(); - - if (searchField.value == 'Search') - { searchField.value = ""; } - } - else if (!ignoreDeactivateDelay) - { - this.inactivateTimeout = setTimeout(this.name + ".InactivateAfterTimeout()", this.inactivateTimeoutLength); - } - else - { - this.InactivateAfterTimeout(); - }; - }; - - - /* - Function: InactivateAfterTimeout - - Called by , which is set by . Inactivation occurs on a timeout because a control may - receive OnBlur() when focus is really transferring to another control in the search panel. In this case we don't want to - actually deactivate the panel because not only would that cause a visible flicker but it could also reset the search value. - So by doing it on a timeout instead, there's a short period where the second control's OnFocus() can cancel the deactivation. - */ - this.InactivateAfterTimeout = function() - { - this.inactivateTimeout = 0; - - this.DOMSearchPanel().className = 'MSearchPanelInactive'; - this.DOMSearchField().value = "Search"; - - this.lastSearchValue = ""; - this.lastResultsPage = ""; - }; - }; - - - - -/* ________________________________________________________________________________________________________ - - Class: SearchResults - _________________________________________________________________________________________________________ - - The class that handles everything on the search results page. - _________________________________________________________________________________________________________ -*/ - - -function SearchResults(name, mode) - { - /* - var: mode - The mode the search is going to work in, such as "HTML" or "FramedHTML". - */ - this.mode = mode; - - /* - var: lastMatchCount - The number of matches from the last run of . - */ - this.lastMatchCount = 0; - - - /* - Function: Toggle - Toggles the visibility of the passed element ID. - */ - this.Toggle = function(id) - { - if (this.mode == "FramedHTML") - { return; }; - - var parentElement = document.getElementById(id); - - var element = parentElement.firstChild; - - while (element && element != parentElement) - { - if (element.nodeName == 'DIV' && element.className == 'ISubIndex') - { - if (element.style.display == 'block') - { element.style.display = "none"; } - else - { element.style.display = 'block'; } - }; - - if (element.nodeName == 'DIV' && element.hasChildNodes()) - { element = element.firstChild; } - else if (element.nextSibling) - { element = element.nextSibling; } - else - { - do - { - element = element.parentNode; - } - while (element && element != parentElement && !element.nextSibling); - - if (element && element != parentElement) - { element = element.nextSibling; }; - }; - }; - }; - - - /* - Function: Search - - Searches for the passed string. If there is no parameter, it takes it from the URL query. - - Always returns true, since other documents may try to call it and that may or may not be possible. - */ - this.Search = function(search) - { - if (!search) - { - search = window.location.search; - search = search.substring(1); // Remove the leading ? - search = unescape(search); - }; - - search = search.replace(/^ +/, ""); - search = search.replace(/ +$/, ""); - search = search.toLowerCase(); - - if (search.match(/[^a-z0-9]/)) // Just a little speedup so it doesn't have to go through the below unnecessarily. - { - search = search.replace(/\_/g, "_und"); - search = search.replace(/\ +/gi, "_spc"); - search = search.replace(/\~/g, "_til"); - search = search.replace(/\!/g, "_exc"); - search = search.replace(/\@/g, "_att"); - search = search.replace(/\#/g, "_num"); - search = search.replace(/\$/g, "_dol"); - search = search.replace(/\%/g, "_pct"); - search = search.replace(/\^/g, "_car"); - search = search.replace(/\&/g, "_amp"); - search = search.replace(/\*/g, "_ast"); - search = search.replace(/\(/g, "_lpa"); - search = search.replace(/\)/g, "_rpa"); - search = search.replace(/\-/g, "_min"); - search = search.replace(/\+/g, "_plu"); - search = search.replace(/\=/g, "_equ"); - search = search.replace(/\{/g, "_lbc"); - search = search.replace(/\}/g, "_rbc"); - search = search.replace(/\[/g, "_lbk"); - search = search.replace(/\]/g, "_rbk"); - search = search.replace(/\:/g, "_col"); - search = search.replace(/\;/g, "_sco"); - search = search.replace(/\"/g, "_quo"); - search = search.replace(/\'/g, "_apo"); - search = search.replace(/\/g, "_ran"); - search = search.replace(/\,/g, "_com"); - search = search.replace(/\./g, "_per"); - search = search.replace(/\?/g, "_que"); - search = search.replace(/\//g, "_sla"); - search = search.replace(/[^a-z0-9\_]i/gi, "_zzz"); - }; - - var resultRows = document.getElementsByTagName("div"); - var matches = 0; - - var i = 0; - while (i < resultRows.length) - { - var row = resultRows.item(i); - - if (row.className == "SRResult") - { - var rowMatchName = row.id.toLowerCase(); - rowMatchName = rowMatchName.replace(/^sr\d*_/, ''); - - if (search.length <= rowMatchName.length && rowMatchName.substr(0, search.length) == search) - { - row.style.display = "block"; - matches++; - } - else - { row.style.display = "none"; }; - }; - - i++; - }; - - document.getElementById("Searching").style.display="none"; - - if (matches == 0) - { document.getElementById("NoMatches").style.display="block"; } - else - { document.getElementById("NoMatches").style.display="none"; } - - this.lastMatchCount = matches; - - return true; - }; - }; - diff --git a/demo/apidocs/javascript/prettify.js b/demo/apidocs/javascript/prettify.js deleted file mode 100644 index fda4bf1ed..000000000 --- a/demo/apidocs/javascript/prettify.js +++ /dev/null @@ -1,1526 +0,0 @@ - -// This code comes from the December 2009 release of Google Prettify, which is Copyright © 2006 Google Inc. -// Minor modifications are marked with "ND Change" comments. -// As part of Natural Docs, this code is licensed under version 3 of the GNU Affero General Public License (AGPL.) -// However, it may also be obtained separately under version 2.0 of the Apache License. -// Refer to License.txt for the complete details - - -// Main code -// ____________________________________________________________________________ - -// Copyright (C) 2006 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - - -/** - * @fileoverview - * some functions for browser-side pretty printing of code contained in html. - *

- * - * For a fairly comprehensive set of languages see the - * README - * file that came with this source. At a minimum, the lexer should work on a - * number of languages including C and friends, Java, Python, Bash, SQL, HTML, - * XML, CSS, Javascript, and Makefiles. It works passably on Ruby, PHP and Awk - * and a subset of Perl, but, because of commenting conventions, doesn't work on - * Smalltalk, Lisp-like, or CAML-like languages without an explicit lang class. - *

- * Usage:

    - *
  1. include this source file in an html page via - * {@code } - *
  2. define style rules. See the example page for examples. - *
  3. mark the {@code
    } and {@code } tags in your source with
    - *    {@code class=prettyprint.}
    - *    You can also use the (html deprecated) {@code } tag, but the pretty
    - *    printer needs to do more substantial DOM manipulations to support that, so
    - *    some css styles may not be preserved.
    - * </ol>
    - * That's it.  I wanted to keep the API as simple as possible, so there's no
    - * need to specify which language the code is in, but if you wish, you can add
    - * another class to the {@code <pre>} or {@code <code>} element to specify the
    - * language, as in {@code <pre class="prettyprint lang-java">}.  Any class that
    - * starts with "lang-" followed by a file extension, specifies the file type.
    - * See the "lang-*.js" files in this directory for code that implements
    - * per-language file handlers.
    - * <p>
    - * Change log:<br>
    - * cbeust, 2006/08/22
    - * <blockquote>
    - *   Java annotations (start with "@") are now captured as literals ("lit")
    - * </blockquote>
    - * @requires console
    - * @overrides window
    - */
    -
    -// JSLint declarations
    -/*global console, document, navigator, setTimeout, window */
    -
    -/**
    - * Split {@code prettyPrint} into multiple timeouts so as not to interfere with
    - * UI events.
    - * If set to {@code false}, {@code prettyPrint()} is synchronous.
    - */
    -window['PR_SHOULD_USE_CONTINUATION'] = true;
    -
    -/** the number of characters between tab columns */
    -window['PR_TAB_WIDTH'] = 8;
    -
    -/** Walks the DOM returning a properly escaped version of innerHTML.
    -  * @param {Node} node
    -  * @param {Array.<string>} out output buffer that receives chunks of HTML.
    -  */
    -window['PR_normalizedHtml']
    -
    -/** Contains functions for creating and registering new language handlers.
    -  * @type {Object}
    -  */
    -  = window['PR']
    -
    -/** Pretty print a chunk of code.
    -  *
    -  * @param {string} sourceCodeHtml code as html
    -  * @return {string} code as html, but prettier
    -  */
    -  = window['prettyPrintOne']
    -/** Find all the {@code <pre>} and {@code <code>} tags in the DOM with
    -  * {@code class=prettyprint} and prettify them.
    -  * @param {Function?} opt_whenDone if specified, called when the last entry
    -  *     has been finished.
    -  */
    -  = window['prettyPrint'] = void 0;
    -
    -/** browser detection. @extern @returns false if not IE, otherwise the major version. */
    -window['_pr_isIE6'] = function () {
    -  var ieVersion = navigator && navigator.userAgent &&
    -      navigator.userAgent.match(/\bMSIE ([678])\./);
    -  ieVersion = ieVersion ? +ieVersion[1] : false;
    -  window['_pr_isIE6'] = function () { return ieVersion; };
    -  return ieVersion;
    -};
    -
    -
    -(function () {
    -  // Keyword lists for various languages.
    -  var FLOW_CONTROL_KEYWORDS =
    -      "break continue do else for if return while ";
    -  var C_KEYWORDS = FLOW_CONTROL_KEYWORDS + "auto case char const default " +
    -      "double enum extern float goto int long register short signed sizeof " +
    -      "static struct switch typedef union unsigned void volatile ";
    -  var COMMON_KEYWORDS = C_KEYWORDS + "catch class delete false import " +
    -      "new operator private protected public this throw true try typeof ";
    -  var CPP_KEYWORDS = COMMON_KEYWORDS + "alignof align_union asm axiom bool " +
    -      "concept concept_map const_cast constexpr decltype " +
    -      "dynamic_cast explicit export friend inline late_check " +
    -      "mutable namespace nullptr reinterpret_cast static_assert static_cast " +
    -      "template typeid typename using virtual wchar_t where ";
    -  var JAVA_KEYWORDS = COMMON_KEYWORDS +
    -      "abstract boolean byte extends final finally implements import " +
    -      "instanceof null native package strictfp super synchronized throws " +
    -      "transient ";
    -  var CSHARP_KEYWORDS = JAVA_KEYWORDS +
    -      "as base by checked decimal delegate descending event " +
    -      "fixed foreach from group implicit in interface internal into is lock " +
    -      "object out override orderby params partial readonly ref sbyte sealed " +
    -      "stackalloc string select uint ulong unchecked unsafe ushort var ";
    -  var JSCRIPT_KEYWORDS = COMMON_KEYWORDS +
    -      "debugger eval export function get null set undefined var with " +
    -      "Infinity NaN ";
    -  var PERL_KEYWORDS = "caller delete die do dump elsif eval exit foreach for " +
    -      "goto if import last local my next no our print package redo require " +
    -      "sub undef unless until use wantarray while BEGIN END ";
    -  var PYTHON_KEYWORDS = FLOW_CONTROL_KEYWORDS + "and as assert class def del " +
    -      "elif except exec finally from global import in is lambda " +
    -      "nonlocal not or pass print raise try with yield " +
    -      "False True None ";
    -  var RUBY_KEYWORDS = FLOW_CONTROL_KEYWORDS + "alias and begin case class def" +
    -      " defined elsif end ensure false in module next nil not or redo rescue " +
    -      "retry self super then true undef unless until when yield BEGIN END ";
    -  var SH_KEYWORDS = FLOW_CONTROL_KEYWORDS + "case done elif esac eval fi " +
    -      "function in local set then until ";
    -  var ALL_KEYWORDS = (
    -      CPP_KEYWORDS + CSHARP_KEYWORDS + JSCRIPT_KEYWORDS + PERL_KEYWORDS +
    -      PYTHON_KEYWORDS + RUBY_KEYWORDS + SH_KEYWORDS);
    -
    -  // token style names.  correspond to css classes
    -  /** token style for a string literal */
    -  var PR_STRING = 'str';
    -  /** token style for a keyword */
    -  var PR_KEYWORD = 'kwd';
    -  /** token style for a comment */
    -  var PR_COMMENT = 'com';
    -  /** token style for a type */
    -  var PR_TYPE = 'typ';
    -  /** token style for a literal value.  e.g. 1, null, true. */
    -  var PR_LITERAL = 'lit';
    -  /** token style for a punctuation string. */
    -  var PR_PUNCTUATION = 'pun';
    -  /** token style for a punctuation string. */
    -  var PR_PLAIN = 'pln';
    -
    -  /** token style for an sgml tag. */
    -  var PR_TAG = 'tag';
    -  /** token style for a markup declaration such as a DOCTYPE. */
    -  var PR_DECLARATION = 'dec';
    -  /** token style for embedded source. */
    -  var PR_SOURCE = 'src';
    -  /** token style for an sgml attribute name. */
    -  var PR_ATTRIB_NAME = 'atn';
    -  /** token style for an sgml attribute value. */
    -  var PR_ATTRIB_VALUE = 'atv';
    -
    -  /**
    -   * A class that indicates a section of markup that is not code, e.g. to allow
    -   * embedding of line numbers within code listings.
    -   */
    -  var PR_NOCODE = 'nocode';
    -
    -  /** A set of tokens that can precede a regular expression literal in
    -    * javascript.
    -    * http://www.mozilla.org/js/language/js20/rationale/syntax.html has the full
    -    * list, but I've removed ones that might be problematic when seen in
    -    * languages that don't support regular expression literals.
    -    *
    -    * <p>Specifically, I've removed any keywords that can't precede a regexp
    -    * literal in a syntactically legal javascript program, and I've removed the
    -    * "in" keyword since it's not a keyword in many languages, and might be used
    -    * as a count of inches.
    -    *
    -    * <p>The link a above does not accurately describe EcmaScript rules since
    -    * it fails to distinguish between (a=++/b/i) and (a++/b/i) but it works
    -    * very well in practice.
    -    *
    -    * @private
    -    */
    -  var REGEXP_PRECEDER_PATTERN = function () {
    -      var preceders = [
    -          "!", "!=", "!==", "#", "%", "%=", "&", "&&", "&&=",
    -          "&=", "(", "*", "*=", /* "+", */ "+=", ",", /* "-", */ "-=",
    -          "->", /*".", "..", "...", handled below */ "/", "/=", ":", "::", ";",
    -          "<", "<<", "<<=", "<=", "=", "==", "===", ">",
    -          ">=", ">>", ">>=", ">>>", ">>>=", "?", "@", "[",
    -          "^", "^=", "^^", "^^=", "{", "|", "|=", "||",
    -          "||=", "~" /* handles =~ and !~ */,
    -          "break", "case", "continue", "delete",
    -          "do", "else", "finally", "instanceof",
    -          "return", "throw", "try", "typeof"
    -          ];
    -      var pattern = '(?:^^|[+-]';
    -      for (var i = 0; i < preceders.length; ++i) {
    -        pattern += '|' + preceders[i].replace(/([^=<>:&a-z])/g, '\\$1');
    -      }
    -      pattern += ')\\s*';  // matches at end, and matches empty string
    -      return pattern;
    -      // CAVEAT: this does not properly handle the case where a regular
    -      // expression immediately follows another since a regular expression may
    -      // have flags for case-sensitivity and the like.  Having regexp tokens
    -      // adjacent is not valid in any language I'm aware of, so I'm punting.
    -      // TODO: maybe style special characters inside a regexp as punctuation.
    -    }();
    -
    -  // Define regexps here so that the interpreter doesn't have to create an
    -  // object each time the function containing them is called.
    -  // The language spec requires a new object created even if you don't access
    -  // the $1 members.
    -  var pr_amp = /&/g;
    -  var pr_lt = /</g;
    -  var pr_gt = />/g;
    -  var pr_quot = /\"/g;
    -  /** like textToHtml but escapes double quotes to be attribute safe. */
    -  function attribToHtml(str) {
    -    return str.replace(pr_amp, '&amp;')
    -        .replace(pr_lt, '&lt;')
    -        .replace(pr_gt, '&gt;')
    -        .replace(pr_quot, '&quot;');
    -  }
    -
    -  /** escapest html special characters to html. */
    -  function textToHtml(str) {
    -    return str.replace(pr_amp, '&amp;')
    -        .replace(pr_lt, '&lt;')
    -        .replace(pr_gt, '&gt;');
    -  }
    -
    -
    -  var pr_ltEnt = /&lt;/g;
    -  var pr_gtEnt = /&gt;/g;
    -  var pr_aposEnt = /&apos;/g;
    -  var pr_quotEnt = /&quot;/g;
    -  var pr_ampEnt = /&amp;/g;
    -  var pr_nbspEnt = /&nbsp;/g;
    -  /** unescapes html to plain text. */
    -  function htmlToText(html) {
    -    var pos = html.indexOf('&');
    -    if (pos < 0) { return html; }
    -    // Handle numeric entities specially.  We can't use functional substitution
    -    // since that doesn't work in older versions of Safari.
    -    // These should be rare since most browsers convert them to normal chars.
    -    for (--pos; (pos = html.indexOf('&#', pos + 1)) >= 0;) {
    -      var end = html.indexOf(';', pos);
    -      if (end >= 0) {
    -        var num = html.substring(pos + 3, end);
    -        var radix = 10;
    -        if (num && num.charAt(0) === 'x') {
    -          num = num.substring(1);
    -          radix = 16;
    -        }
    -        var codePoint = parseInt(num, radix);
    -        if (!isNaN(codePoint)) {
    -          html = (html.substring(0, pos) + String.fromCharCode(codePoint) +
    -                  html.substring(end + 1));
    -        }
    -      }
    -    }
    -
    -    return html.replace(pr_ltEnt, '<')
    -        .replace(pr_gtEnt, '>')
    -        .replace(pr_aposEnt, "'")
    -        .replace(pr_quotEnt, '"')
    -        .replace(pr_nbspEnt, ' ')
    -        .replace(pr_ampEnt, '&');
    -  }
    -
    -  /** is the given node's innerHTML normally unescaped? */
    -  function isRawContent(node) {
    -    return 'XMP' === node.tagName;
    -  }
    -
    -  var newlineRe = /[\r\n]/g;
    -  /**
    -   * Are newlines and adjacent spaces significant in the given node's innerHTML?
    -   */
    -  function isPreformatted(node, content) {
    -    // PRE means preformatted, and is a very common case, so don't create
    -    // unnecessary computed style objects.
    -    if ('PRE' === node.tagName) { return true; }
    -    if (!newlineRe.test(content)) { return true; }  // Don't care
    -    var whitespace = '';
    -    // For disconnected nodes, IE has no currentStyle.
    -    if (node.currentStyle) {
    -      whitespace = node.currentStyle.whiteSpace;
    -    } else if (window.getComputedStyle) {
    -      // Firefox makes a best guess if node is disconnected whereas Safari
    -      // returns the empty string.
    -      whitespace = window.getComputedStyle(node, null).whiteSpace;
    -    }
    -    return !whitespace || whitespace === 'pre';
    -  }
    -
    -  function normalizedHtml(node, out) {
    -    switch (node.nodeType) {
    -      case 1:  // an element
    -        var name = node.tagName.toLowerCase();
    -        out.push('<', name);
    -        for (var i = 0; i < node.attributes.length; ++i) {
    -          var attr = node.attributes[i];
    -          if (!attr.specified) { continue; }
    -          out.push(' ');
    -          normalizedHtml(attr, out);
    -        }
    -        out.push('>');
    -        for (var child = node.firstChild; child; child = child.nextSibling) {
    -          normalizedHtml(child, out);
    -        }
    -        if (node.firstChild || !/^(?:br|link|img)$/.test(name)) {
    -          out.push('<\/', name, '>');
    -        }
    -        break;
    -      case 2: // an attribute
    -        out.push(node.name.toLowerCase(), '="', attribToHtml(node.value), '"');
    -        break;
    -      case 3: case 4: // text
    -        out.push(textToHtml(node.nodeValue));
    -        break;
    -    }
    -  }
    -
    -  /**
    -   * Given a group of {@link RegExp}s, returns a {@code RegExp} that globally
    -   * matches the union o the sets o strings matched d by the input RegExp.
    -   * Since it matches globally, if the input strings have a start-of-input
    -   * anchor (/^.../), it is ignored for the purposes of unioning.
    -   * @param {Array.<RegExp>} regexs non multiline, non-global regexs.
    -   * @return {RegExp} a global regex.
    -   */
    -  function combinePrefixPatterns(regexs) {
    -    var capturedGroupIndex = 0;
    -
    -    var needToFoldCase = false;
    -    var ignoreCase = false;
    -    for (var i = 0, n = regexs.length; i < n; ++i) {
    -      var regex = regexs[i];
    -      if (regex.ignoreCase) {
    -        ignoreCase = true;
    -      } else if (/[a-z]/i.test(regex.source.replace(
    -                     /\\u[0-9a-f]{4}|\\x[0-9a-f]{2}|\\[^ux]/gi, ''))) {
    -        needToFoldCase = true;
    -        ignoreCase = false;
    -        break;
    -      }
    -    }
    -
    -    function decodeEscape(charsetPart) {
    -      if (charsetPart.charAt(0) !== '\\') { return charsetPart.charCodeAt(0); }
    -      switch (charsetPart.charAt(1)) {
    -        case 'b': return 8;
    -        case 't': return 9;
    -        case 'n': return 0xa;
    -        case 'v': return 0xb;
    -        case 'f': return 0xc;
    -        case 'r': return 0xd;
    -        case 'u': case 'x':
    -          return parseInt(charsetPart.substring(2), 16)
    -              || charsetPart.charCodeAt(1);
    -        case '0': case '1': case '2': case '3': case '4':
    -        case '5': case '6': case '7':
    -          return parseInt(charsetPart.substring(1), 8);
    -        default: return charsetPart.charCodeAt(1);
    -      }
    -    }
    -
    -    function encodeEscape(charCode) {
    -      if (charCode < 0x20) {
    -        return (charCode < 0x10 ? '\\x0' : '\\x') + charCode.toString(16);
    -      }
    -      var ch = String.fromCharCode(charCode);
    -      if (ch === '\\' || ch === '-' || ch === '[' || ch === ']') {
    -        ch = '\\' + ch;
    -      }
    -      return ch;
    -    }
    -
    -    function caseFoldCharset(charSet) {
    -      var charsetParts = charSet.substring(1, charSet.length - 1).match(
    -          new RegExp(
    -              '\\\\u[0-9A-Fa-f]{4}'
    -              + '|\\\\x[0-9A-Fa-f]{2}'
    -              + '|\\\\[0-3][0-7]{0,2}'
    -              + '|\\\\[0-7]{1,2}'
    -              + '|\\\\[\\s\\S]'
    -              + '|-'
    -              + '|[^-\\\\]',
    -              'g'));
    -      var groups = [];
    -      var ranges = [];
    -      var inverse = charsetParts[0] === '^';
    -      for (var i = inverse ? 1 : 0, n = charsetParts.length; i < n; ++i) {
    -        var p = charsetParts[i];
    -        switch (p) {
    -          case '\\B': case '\\b':
    -          case '\\D': case '\\d':
    -          case '\\S': case '\\s':
    -          case '\\W': case '\\w':
    -            groups.push(p);
    -            continue;
    -        }
    -        var start = decodeEscape(p);
    -        var end;
    -        if (i + 2 < n && '-' === charsetParts[i + 1]) {
    -          end = decodeEscape(charsetParts[i + 2]);
    -          i += 2;
    -        } else {
    -          end = start;
    -        }
    -        ranges.push([start, end]);
    -        // If the range might intersect letters, then expand it.
    -        if (!(end < 65 || start > 122)) {
    -          if (!(end < 65 || start > 90)) {
    -            ranges.push([Math.max(65, start) | 32, Math.min(end, 90) | 32]);
    -          }
    -          if (!(end < 97 || start > 122)) {
    -            ranges.push([Math.max(97, start) & ~32, Math.min(end, 122) & ~32]);
    -          }
    -        }
    -      }
    -
    -      // [[1, 10], [3, 4], [8, 12], [14, 14], [16, 16], [17, 17]]
    -      // -> [[1, 12], [14, 14], [16, 17]]
    -      ranges.sort(function (a, b) { return (a[0] - b[0]) || (b[1]  - a[1]); });
    -      var consolidatedRanges = [];
    -      var lastRange = [NaN, NaN];
    -      for (var i = 0; i < ranges.length; ++i) {
    -        var range = ranges[i];
    -        if (range[0] <= lastRange[1] + 1) {
    -          lastRange[1] = Math.max(lastRange[1], range[1]);
    -        } else {
    -          consolidatedRanges.push(lastRange = range);
    -        }
    -      }
    -
    -      var out = ['['];
    -      if (inverse) { out.push('^'); }
    -      out.push.apply(out, groups);
    -      for (var i = 0; i < consolidatedRanges.length; ++i) {
    -        var range = consolidatedRanges[i];
    -        out.push(encodeEscape(range[0]));
    -        if (range[1] > range[0]) {
    -          if (range[1] + 1 > range[0]) { out.push('-'); }
    -          out.push(encodeEscape(range[1]));
    -        }
    -      }
    -      out.push(']');
    -      return out.join('');
    -    }
    -
    -    function allowAnywhereFoldCaseAndRenumberGroups(regex) {
    -      // Split into character sets, escape sequences, punctuation strings
    -      // like ('(', '(?:', ')', '^'), and runs of characters that do not
    -      // include any of the above.
    -      var parts = regex.source.match(
    -          new RegExp(
    -              '(?:'
    -              + '\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]'  // a character set
    -              + '|\\\\u[A-Fa-f0-9]{4}'  // a unicode escape
    -              + '|\\\\x[A-Fa-f0-9]{2}'  // a hex escape
    -              + '|\\\\[0-9]+'  // a back-reference or octal escape
    -              + '|\\\\[^ux0-9]'  // other escape sequence
    -              + '|\\(\\?[:!=]'  // start of a non-capturing group
    -              + '|[\\(\\)\\^]'  // start/emd of a group, or line start
    -              + '|[^\\x5B\\x5C\\(\\)\\^]+'  // run of other characters
    -              + ')',
    -              'g'));
    -      var n = parts.length;
    -
    -      // Maps captured group numbers to the number they will occupy in
    -      // the output or to -1 if that has not been determined, or to
    -      // undefined if they need not be capturing in the output.
    -      var capturedGroups = [];
    -
    -      // Walk over and identify back references to build the capturedGroups
    -      // mapping.
    -      for (var i = 0, groupIndex = 0; i < n; ++i) {
    -        var p = parts[i];
    -        if (p === '(') {
    -          // groups are 1-indexed, so max group index is count of '('
    -          ++groupIndex;
    -        } else if ('\\' === p.charAt(0)) {
    -          var decimalValue = +p.substring(1);
    -          if (decimalValue && decimalValue <= groupIndex) {
    -            capturedGroups[decimalValue] = -1;
    -          }
    -        }
    -      }
    -
    -      // Renumber groups and reduce capturing groups to non-capturing groups
    -      // where possible.
    -      for (var i = 1; i < capturedGroups.length; ++i) {
    -        if (-1 === capturedGroups[i]) {
    -          capturedGroups[i] = ++capturedGroupIndex;
    -        }
    -      }
    -      for (var i = 0, groupIndex = 0; i < n; ++i) {
    -        var p = parts[i];
    -        if (p === '(') {
    -          ++groupIndex;
    -          if (capturedGroups[groupIndex] === undefined) {
    -            parts[i] = '(?:';
    -          }
    -        } else if ('\\' === p.charAt(0)) {
    -          var decimalValue = +p.substring(1);
    -          if (decimalValue && decimalValue <= groupIndex) {
    -            parts[i] = '\\' + capturedGroups[groupIndex];
    -          }
    -        }
    -      }
    -
    -      // Remove any prefix anchors so that the output will match anywhere.
    -      // ^^ really does mean an anchored match though.
    -      for (var i = 0, groupIndex = 0; i < n; ++i) {
    -        if ('^' === parts[i] && '^' !== parts[i + 1]) { parts[i] = ''; }
    -      }
    -
    -      // Expand letters to groupts to handle mixing of case-sensitive and
    -      // case-insensitive patterns if necessary.
    -      if (regex.ignoreCase && needToFoldCase) {
    -        for (var i = 0; i < n; ++i) {
    -          var p = parts[i];
    -          var ch0 = p.charAt(0);
    -          if (p.length >= 2 && ch0 === '[') {
    -            parts[i] = caseFoldCharset(p);
    -          } else if (ch0 !== '\\') {
    -            // TODO: handle letters in numeric escapes.
    -            parts[i] = p.replace(
    -                /[a-zA-Z]/g,
    -                function (ch) {
    -                  var cc = ch.charCodeAt(0);
    -                  return '[' + String.fromCharCode(cc & ~32, cc | 32) + ']';
    -                });
    -          }
    -        }
    -      }
    -
    -      return parts.join('');
    -    }
    -
    -    var rewritten = [];
    -    for (var i = 0, n = regexs.length; i < n; ++i) {
    -      var regex = regexs[i];
    -      if (regex.global || regex.multiline) { throw new Error('' + regex); }
    -      rewritten.push(
    -          '(?:' + allowAnywhereFoldCaseAndRenumberGroups(regex) + ')');
    -    }
    -
    -    return new RegExp(rewritten.join('|'), ignoreCase ? 'gi' : 'g');
    -  }
    -
    -  var PR_innerHtmlWorks = null;
    -  function getInnerHtml(node) {
    -    // inner html is hopelessly broken in Safari 2.0.4 when the content is
    -    // an html description of well formed XML and the containing tag is a PRE
    -    // tag, so we detect that case and emulate innerHTML.
    -    if (null === PR_innerHtmlWorks) {
    -      var testNode = document.createElement('PRE');
    -      testNode.appendChild(
    -          document.createTextNode('<!DOCTYPE foo PUBLIC "foo bar">\n<foo />'));
    -      PR_innerHtmlWorks = !/</.test(testNode.innerHTML);
    -    }
    -
    -    if (PR_innerHtmlWorks) {
    -      var content = node.innerHTML;
    -      // XMP tags contain unescaped entities so require special handling.
    -      if (isRawContent(node)) {
    -        content = textToHtml(content);
    -      } else if (!isPreformatted(node, content)) {
    -        content = content.replace(/(<br\s*\/?>)[\r\n]+/g, '$1')
    -            .replace(/(?:[\r\n]+[ \t]*)+/g, ' ');
    -      }
    -      return content;
    -    }
    -
    -    var out = [];
    -    for (var child = node.firstChild; child; child = child.nextSibling) {
    -      normalizedHtml(child, out);
    -    }
    -    return out.join('');
    -  }
    -
    -  /** returns a function that expand tabs to spaces.  This function can be fed
    -    * successive chunks of text, and will maintain its own internal state to
    -    * keep track of how tabs are expanded.
    -    * @return {function (string) : string} a function that takes
    -    *   plain text and return the text with tabs expanded.
    -    * @private
    -    */
    -  function makeTabExpander(tabWidth) {
    -    var SPACES = '                ';
    -    var charInLine = 0;
    -
    -    return function (plainText) {
    -      // walk over each character looking for tabs and newlines.
    -      // On tabs, expand them.  On newlines, reset charInLine.
    -      // Otherwise increment charInLine
    -      var out = null;
    -      var pos = 0;
    -      for (var i = 0, n = plainText.length; i < n; ++i) {
    -        var ch = plainText.charAt(i);
    -
    -        switch (ch) {
    -          case '\t':
    -            if (!out) { out = []; }
    -            out.push(plainText.substring(pos, i));
    -            // calculate how much space we need in front of this part
    -            // nSpaces is the amount of padding -- the number of spaces needed
    -            // to move us to the next column, where columns occur at factors of
    -            // tabWidth.
    -            var nSpaces = tabWidth - (charInLine % tabWidth);
    -            charInLine += nSpaces;
    -            for (; nSpaces >= 0; nSpaces -= SPACES.length) {
    -              out.push(SPACES.substring(0, nSpaces));
    -            }
    -            pos = i + 1;
    -            break;
    -          case '\n':
    -            charInLine = 0;
    -            break;
    -          default:
    -            ++charInLine;
    -        }
    -      }
    -      if (!out) { return plainText; }
    -      out.push(plainText.substring(pos));
    -      return out.join('');
    -    };
    -  }
    -
    -  var pr_chunkPattern = new RegExp(
    -      '[^<]+'  // A run of characters other than '<'
    -      + '|<\!--[\\s\\S]*?--\>'  // an HTML comment
    -      + '|<!\\[CDATA\\[[\\s\\S]*?\\]\\]>'  // a CDATA section
    -      // a probable tag that should not be highlighted
    -      + '|<\/?[a-zA-Z](?:[^>\"\']|\'[^\']*\'|\"[^\"]*\")*>'
    -      + '|<',  // A '<' that does not begin a larger chunk
    -      'g');
    -  var pr_commentPrefix = /^<\!--/;
    -  var pr_cdataPrefix = /^<!\[CDATA\[/;
    -  var pr_brPrefix = /^<br\b/i;
    -  var pr_tagNameRe = /^<(\/?)([a-zA-Z][a-zA-Z0-9]*)/;
    -
    -  /** split markup into chunks of html tags (style null) and
    -    * plain text (style {@link #PR_PLAIN}), converting tags which are
    -    * significant for tokenization (<br>) into their textual equivalent.
    -    *
    -    * @param {string} s html where whitespace is considered significant.
    -    * @return {Object} source code and extracted tags.
    -    * @private
    -    */
    -  function extractTags(s) {
    -    // since the pattern has the 'g' modifier and defines no capturing groups,
    -    // this will return a list of all chunks which we then classify and wrap as
    -    // PR_Tokens
    -    var matches = s.match(pr_chunkPattern);
    -    var sourceBuf = [];
    -    var sourceBufLen = 0;
    -    var extractedTags = [];
    -    if (matches) {
    -      for (var i = 0, n = matches.length; i < n; ++i) {
    -        var match = matches[i];
    -        if (match.length > 1 && match.charAt(0) === '<') {
    -          if (pr_commentPrefix.test(match)) { continue; }
    -          if (pr_cdataPrefix.test(match)) {
    -            // strip CDATA prefix and suffix.  Don't unescape since it's CDATA
    -            sourceBuf.push(match.substring(9, match.length - 3));
    -            sourceBufLen += match.length - 12;
    -          } else if (pr_brPrefix.test(match)) {
    -            // <br> tags are lexically significant so convert them to text.
    -            // This is undone later.
    -            sourceBuf.push('\n');
    -            ++sourceBufLen;
    -          } else {
    -            if (match.indexOf(PR_NOCODE) >= 0 && isNoCodeTag(match)) {
    -              // A <span class="nocode"> will start a section that should be
    -              // ignored.  Continue walking the list until we see a matching end
    -              // tag.
    -              var name = match.match(pr_tagNameRe)[2];
    -              var depth = 1;
    -              var j;
    -              end_tag_loop:
    -              for (j = i + 1; j < n; ++j) {
    -                var name2 = matches[j].match(pr_tagNameRe);
    -                if (name2 && name2[2] === name) {
    -                  if (name2[1] === '/') {
    -                    if (--depth === 0) { break end_tag_loop; }
    -                  } else {
    -                    ++depth;
    -                  }
    -                }
    -              }
    -              if (j < n) {
    -                extractedTags.push(
    -                    sourceBufLen, matches.slice(i, j + 1).join(''));
    -                i = j;
    -              } else {  // Ignore unclosed sections.
    -                extractedTags.push(sourceBufLen, match);
    -              }
    -            } else {
    -              extractedTags.push(sourceBufLen, match);
    -            }
    -          }
    -        } else {
    -          var literalText = htmlToText(match);
    -          sourceBuf.push(literalText);
    -          sourceBufLen += literalText.length;
    -        }
    -      }
    -    }
    -    return { source: sourceBuf.join(''), tags: extractedTags };
    -  }
    -
    -  /** True if the given tag contains a class attribute with the nocode class. */
    -  function isNoCodeTag(tag) {
    -    return !!tag
    -        // First canonicalize the representation of attributes
    -        .replace(/\s(\w+)\s*=\s*(?:\"([^\"]*)\"|'([^\']*)'|(\S+))/g,
    -                 ' $1="$2$3$4"')
    -        // Then look for the attribute we want.
    -        .match(/[cC][lL][aA][sS][sS]=\"[^\"]*\bnocode\b/);
    -  }
    -
    -  /**
    -   * Apply the given language handler to sourceCode and add the resulting
    -   * decorations to out.
    -   * @param {number} basePos the index of sourceCode within the chunk of source
    -   *    whose decorations are already present on out.
    -   */
    -  function appendDecorations(basePos, sourceCode, langHandler, out) {
    -    if (!sourceCode) { return; }
    -    var job = {
    -      source: sourceCode,
    -      basePos: basePos
    -    };
    -    langHandler(job);
    -    out.push.apply(out, job.decorations);
    -  }
    -
    -  /** Given triples of [style, pattern, context] returns a lexing function,
    -    * The lexing function interprets the patterns to find token boundaries and
    -    * returns a decoration list of the form
    -    * [index_0, style_0, index_1, style_1, ..., index_n, style_n]
    -    * where index_n is an index into the sourceCode, and style_n is a style
    -    * constant like PR_PLAIN.  index_n-1 <= index_n, and style_n-1 applies to
    -    * all characters in sourceCode[index_n-1:index_n].
    -    *
    -    * The stylePatterns is a list whose elements have the form
    -    * [style : string, pattern : RegExp, DEPRECATED, shortcut : string].
    -    *
    -    * Style is a style constant like PR_PLAIN, or can be a string of the
    -    * form 'lang-FOO', where FOO is a language extension describing the
    -    * language of the portion of the token in $1 after pattern executes.
    -    * E.g., if style is 'lang-lisp', and group 1 contains the text
    -    * '(hello (world))', then that portion of the token will be passed to the
    -    * registered lisp handler for formatting.
    -    * The text before and after group 1 will be restyled using this decorator
    -    * so decorators should take care that this doesn't result in infinite
    -    * recursion.  For example, the HTML lexer rule for SCRIPT elements looks
    -    * something like ['lang-js', /<[s]cript>(.+?)<\/script>/].  This may match
    -    * '<script>foo()<\/script>', which would cause the current decorator to
    -    * be called with '<script>' which would not match the same rule since
    -    * group 1 must not be empty, so it would be instead styled as PR_TAG by
    -    * the generic tag rule.  The handler registered for the 'js' extension would
    -    * then be called with 'foo()', and finally, the current decorator would
    -    * be called with '<\/script>' which would not match the original rule and
    -    * so the generic tag rule would identify it as a tag.
    -    *
    -    * Pattern must only match prefixes, and if it matches a prefix, then that
    -    * match is considered a token with the same style.
    -    *
    -    * Context is applied to the last non-whitespace, non-comment token
    -    * recognized.
    -    *
    -    * Shortcut is an optional string of characters, any of which, if the first
    -    * character, gurantee that this pattern and only this pattern matches.
    -    *
    -    * @param {Array} shortcutStylePatterns patterns that always start with
    -    *   a known character.  Must have a shortcut string.
    -    * @param {Array} fallthroughStylePatterns patterns that will be tried in
    -    *   order if the shortcut ones fail.  May have shortcuts.
    -    *
    -    * @return {function (Object)} a
    -    *   function that takes source code and returns a list of decorations.
    -    */
    -  function createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns) {
    -    var shortcuts = {};
    -    var tokenizer;
    -    (function () {
    -      var allPatterns = shortcutStylePatterns.concat(fallthroughStylePatterns);
    -      var allRegexs = [];
    -      var regexKeys = {};
    -      for (var i = 0, n = allPatterns.length; i < n; ++i) {
    -        var patternParts = allPatterns[i];
    -        var shortcutChars = patternParts[3];
    -        if (shortcutChars) {
    -          for (var c = shortcutChars.length; --c >= 0;) {
    -            shortcuts[shortcutChars.charAt(c)] = patternParts;
    -          }
    -        }
    -        var regex = patternParts[1];
    -        var k = '' + regex;
    -        if (!regexKeys.hasOwnProperty(k)) {
    -          allRegexs.push(regex);
    -          regexKeys[k] = null;
    -        }
    -      }
    -      allRegexs.push(/[\0-\uffff]/);
    -      tokenizer = combinePrefixPatterns(allRegexs);
    -    })();
    -
    -    var nPatterns = fallthroughStylePatterns.length;
    -    var notWs = /\S/;
    -
    -    /**
    -     * Lexes job.source and produces an output array job.decorations of style
    -     * classes preceded by the position at which they start in job.source in
    -     * order.
    -     *
    -     * @param {Object} job an object like {@code
    -     *    source: {string} sourceText plain text,
    -     *    basePos: {int} position of job.source in the larger chunk of
    -     *        sourceCode.
    -     * }
    -     */
    -    var decorate = function (job) {
    -      var sourceCode = job.source, basePos = job.basePos;
    -      /** Even entries are positions in source in ascending order.  Odd enties
    -        * are style markers (e.g., PR_COMMENT) that run from that position until
    -        * the end.
    -        * @type {Array.<number|string>}
    -        */
    -      var decorations = [basePos, PR_PLAIN];
    -      var pos = 0;  // index into sourceCode
    -      var tokens = sourceCode.match(tokenizer) || [];
    -      var styleCache = {};
    -
    -      for (var ti = 0, nTokens = tokens.length; ti < nTokens; ++ti) {
    -        var token = tokens[ti];
    -        var style = styleCache[token];
    -        var match = void 0;
    -
    -        var isEmbedded;
    -        if (typeof style === 'string') {
    -          isEmbedded = false;
    -        } else {
    -          var patternParts = shortcuts[token.charAt(0)];
    -          if (patternParts) {
    -            match = token.match(patternParts[1]);
    -            style = patternParts[0];
    -          } else {
    -            for (var i = 0; i < nPatterns; ++i) {
    -              patternParts = fallthroughStylePatterns[i];
    -              match = token.match(patternParts[1]);
    -              if (match) {
    -                style = patternParts[0];
    -                break;
    -              }
    -            }
    -
    -            if (!match) {  // make sure that we make progress
    -              style = PR_PLAIN;
    -            }
    -          }
    -
    -          isEmbedded = style.length >= 5 && 'lang-' === style.substring(0, 5);
    -          if (isEmbedded && !(match && typeof match[1] === 'string')) {
    -            isEmbedded = false;
    -            style = PR_SOURCE;
    -          }
    -
    -          if (!isEmbedded) { styleCache[token] = style; }
    -        }
    -
    -        var tokenStart = pos;
    -        pos += token.length;
    -
    -        if (!isEmbedded) {
    -          decorations.push(basePos + tokenStart, style);
    -        } else {  // Treat group 1 as an embedded block of source code.
    -          var embeddedSource = match[1];
    -          var embeddedSourceStart = token.indexOf(embeddedSource);
    -          var embeddedSourceEnd = embeddedSourceStart + embeddedSource.length;
    -          if (match[2]) {
    -            // If embeddedSource can be blank, then it would match at the
    -            // beginning which would cause us to infinitely recurse on the
    -            // entire token, so we catch the right context in match[2].
    -            embeddedSourceEnd = token.length - match[2].length;
    -            embeddedSourceStart = embeddedSourceEnd - embeddedSource.length;
    -          }
    -          var lang = style.substring(5);
    -          // Decorate the left of the embedded source
    -          appendDecorations(
    -              basePos + tokenStart,
    -              token.substring(0, embeddedSourceStart),
    -              decorate, decorations);
    -          // Decorate the embedded source
    -          appendDecorations(
    -              basePos + tokenStart + embeddedSourceStart,
    -              embeddedSource,
    -              langHandlerForExtension(lang, embeddedSource),
    -              decorations);
    -          // Decorate the right of the embedded section
    -          appendDecorations(
    -              basePos + tokenStart + embeddedSourceEnd,
    -              token.substring(embeddedSourceEnd),
    -              decorate, decorations);
    -        }
    -      }
    -      job.decorations = decorations;
    -    };
    -    return decorate;
    -  }
    -
    -  /** returns a function that produces a list of decorations from source text.
    -    *
    -    * This code treats ", ', and ` as string delimiters, and \ as a string
    -    * escape.  It does not recognize perl's qq() style strings.
    -    * It has no special handling for double delimiter escapes as in basic, or
    -    * the tripled delimiters used in python, but should work on those regardless
    -    * although in those cases a single string literal may be broken up into
    -    * multiple adjacent string literals.
    -    *
    -    * It recognizes C, C++, and shell style comments.
    -    *
    -    * @param {Object} options a set of optional parameters.
    -    * @return {function (Object)} a function that examines the source code
    -    *     in the input job and builds the decoration list.
    -    */
    -  function sourceDecorator(options) {
    -    var shortcutStylePatterns = [], fallthroughStylePatterns = [];
    -    if (options['tripleQuotedStrings']) {
    -      // '''multi-line-string''', 'single-line-string', and double-quoted
    -      shortcutStylePatterns.push(
    -          [PR_STRING,  /^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,
    -           null, '\'"']);
    -    } else if (options['multiLineStrings']) {
    -      // 'multi-line-string', "multi-line-string"
    -      shortcutStylePatterns.push(
    -          [PR_STRING,  /^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,
    -           null, '\'"`']);
    -    } else {
    -      // 'single-line-string', "single-line-string"
    -      shortcutStylePatterns.push(
    -          [PR_STRING,
    -           /^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,
    -           null, '"\'']);
    -    }
    -    if (options['verbatimStrings']) {
    -      // verbatim-string-literal production from the C# grammar.  See issue 93.
    -      fallthroughStylePatterns.push(
    -          [PR_STRING, /^@\"(?:[^\"]|\"\")*(?:\"|$)/, null]);
    -    }
    -    if (options['hashComments']) {
    -      if (options['cStyleComments']) {
    -        // Stop C preprocessor declarations at an unclosed open comment
    -        shortcutStylePatterns.push(
    -            [PR_COMMENT, /^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,
    -             null, '#']);
    -        fallthroughStylePatterns.push(
    -            [PR_STRING,
    -             /^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,
    -             null]);
    -      } else {
    -        shortcutStylePatterns.push([PR_COMMENT, /^#[^\r\n]*/, null, '#']);
    -      }
    -    }
    -    if (options['cStyleComments']) {
    -      fallthroughStylePatterns.push([PR_COMMENT, /^\/\/[^\r\n]*/, null]);
    -      fallthroughStylePatterns.push(
    -          [PR_COMMENT, /^\/\*[\s\S]*?(?:\*\/|$)/, null]);
    -    }
    -    if (options['regexLiterals']) {
    -      var REGEX_LITERAL = (
    -          // A regular expression literal starts with a slash that is
    -          // not followed by * or / so that it is not confused with
    -          // comments.
    -          '/(?=[^/*])'
    -          // and then contains any number of raw characters,
    -          + '(?:[^/\\x5B\\x5C]'
    -          // escape sequences (\x5C),
    -          +    '|\\x5C[\\s\\S]'
    -          // or non-nesting character sets (\x5B\x5D);
    -          +    '|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+'
    -          // finally closed by a /.
    -          + '/');
    -      fallthroughStylePatterns.push(
    -          ['lang-regex',
    -           new RegExp('^' + REGEXP_PRECEDER_PATTERN + '(' + REGEX_LITERAL + ')')
    -           ]);
    -    }
    -
    -    var keywords = options['keywords'].replace(/^\s+|\s+$/g, '');
    -    if (keywords.length) {
    -      fallthroughStylePatterns.push(
    -          [PR_KEYWORD,
    -           new RegExp('^(?:' + keywords.replace(/\s+/g, '|') + ')\\b'), null]);
    -    }
    -
    -    shortcutStylePatterns.push([PR_PLAIN,       /^\s+/, null, ' \r\n\t\xA0']);
    -    fallthroughStylePatterns.push(
    -        // TODO(mikesamuel): recognize non-latin letters and numerals in idents
    -        [PR_LITERAL,     /^@[a-z_$][a-z_$@0-9]*/i, null],
    -        [PR_TYPE,        /^@?[A-Z]+[a-z][A-Za-z_$@0-9]*/, null],
    -        [PR_PLAIN,       /^[a-z_$][a-z_$@0-9]*/i, null],
    -        [PR_LITERAL,
    -         new RegExp(
    -             '^(?:'
    -             // A hex number
    -             + '0x[a-f0-9]+'
    -             // or an octal or decimal number,
    -             + '|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)'
    -             // possibly in scientific notation
    -             + '(?:e[+\\-]?\\d+)?'
    -             + ')'
    -             // with an optional modifier like UL for unsigned long
    -             + '[a-z]*', 'i'),
    -         null, '0123456789'],
    -        [PR_PUNCTUATION, /^.[^\s\w\.$@\'\"\`\/\#]*/, null]);
    -
    -    return createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns);
    -  }
    -
    -  var decorateSource = sourceDecorator({
    -        'keywords': ALL_KEYWORDS,
    -        'hashComments': true,
    -        'cStyleComments': true,
    -        'multiLineStrings': true,
    -        'regexLiterals': true
    -      });
    -
    -  /** Breaks {@code job.source} around style boundaries in
    -    * {@code job.decorations} while re-interleaving {@code job.extractedTags},
    -    * and leaves the result in {@code job.prettyPrintedHtml}.
    -    * @param {Object} job like {
    -    *    source: {string} source as plain text,
    -    *    extractedTags: {Array.<number|string>} extractedTags chunks of raw
    -    *                   html preceded by their position in {@code job.source}
    -    *                   in order
    -    *    decorations: {Array.<number|string} an array of style classes preceded
    -    *                 by the position at which they start in job.source in order
    -    * }
    -    * @private
    -    */
    -  function recombineTagsAndDecorations(job) {
    -    var sourceText = job.source;
    -    var extractedTags = job.extractedTags;
    -    var decorations = job.decorations;
    -
    -    var html = [];
    -    // index past the last char in sourceText written to html
    -    var outputIdx = 0;
    -
    -    var openDecoration = null;
    -    var currentDecoration = null;
    -    var tagPos = 0;  // index into extractedTags
    -    var decPos = 0;  // index into decorations
    -    var tabExpander = makeTabExpander(window['PR_TAB_WIDTH']);
    -
    -    var adjacentSpaceRe = /([\r\n ]) /g;
    -    var startOrSpaceRe = /(^| ) /gm;
    -    var newlineRe = /\r\n?|\n/g;
    -    var trailingSpaceRe = /[ \r\n]$/;
    -    var lastWasSpace = true;  // the last text chunk emitted ended with a space.
    -
    -    // A helper function that is responsible for opening sections of decoration
    -    // and outputing properly escaped chunks of source
    -    function emitTextUpTo(sourceIdx) {
    -      if (sourceIdx > outputIdx) {
    -        if (openDecoration && openDecoration !== currentDecoration) {
    -          // Close the current decoration
    -          html.push('</span>');
    -          openDecoration = null;
    -        }
    -        if (!openDecoration && currentDecoration) {
    -          openDecoration = currentDecoration;
    -          html.push('<span class="', openDecoration, '">');
    -        }
    -        // This interacts badly with some wikis which introduces paragraph tags
    -        // into pre blocks for some strange reason.
    -        // It's necessary for IE though which seems to lose the preformattedness
    -        // of <pre> tags when their innerHTML is assigned.
    -        // http://stud3.tuwien.ac.at/~e0226430/innerHtmlQuirk.html
    -        // and it serves to undo the conversion of <br>s to newlines done in
    -        // chunkify.
    -        var htmlChunk = textToHtml(
    -            tabExpander(sourceText.substring(outputIdx, sourceIdx)))
    -            .replace(lastWasSpace
    -                     ? startOrSpaceRe
    -                     : adjacentSpaceRe, '$1&nbsp;');
    -        // Keep track of whether we need to escape space at the beginning of the
    -        // next chunk.
    -        lastWasSpace = trailingSpaceRe.test(htmlChunk);
    -        // IE collapses multiple adjacient <br>s into 1 line break.
    -        // Prefix every <br> with '&nbsp;' can prevent such IE's behavior.
    -        var lineBreakHtml = window['_pr_isIE6']() ? '&nbsp;<br />' : '<br />';
    -        html.push(htmlChunk.replace(newlineRe, lineBreakHtml));
    -        outputIdx = sourceIdx;
    -      }
    -    }
    -
    -    while (true) {
    -      // Determine if we're going to consume a tag this time around.  Otherwise
    -      // we consume a decoration or exit.
    -      var outputTag;
    -      if (tagPos < extractedTags.length) {
    -        if (decPos < decorations.length) {
    -          // Pick one giving preference to extractedTags since we shouldn't open
    -          // a new style that we're going to have to immediately close in order
    -          // to output a tag.
    -          outputTag = extractedTags[tagPos] <= decorations[decPos];
    -        } else {
    -          outputTag = true;
    -        }
    -      } else {
    -        outputTag = false;
    -      }
    -      // Consume either a decoration or a tag or exit.
    -      if (outputTag) {
    -        emitTextUpTo(extractedTags[tagPos]);
    -        if (openDecoration) {
    -          // Close the current decoration
    -          html.push('</span>');
    -          openDecoration = null;
    -        }
    -        html.push(extractedTags[tagPos + 1]);
    -        tagPos += 2;
    -      } else if (decPos < decorations.length) {
    -        emitTextUpTo(decorations[decPos]);
    -        currentDecoration = decorations[decPos + 1];
    -        decPos += 2;
    -      } else {
    -        break;
    -      }
    -    }
    -    emitTextUpTo(sourceText.length);
    -    if (openDecoration) {
    -      html.push('</span>');
    -    }
    -    job.prettyPrintedHtml = html.join('');
    -  }
    -
    -  /** Maps language-specific file extensions to handlers. */
    -  var langHandlerRegistry = {};
    -  /** Register a language handler for the given file extensions.
    -    * @param {function (Object)} handler a function from source code to a list
    -    *      of decorations.  Takes a single argument job which describes the
    -    *      state of the computation.   The single parameter has the form
    -    *      {@code {
    -    *        source: {string} as plain text.
    -    *        decorations: {Array.<number|string>} an array of style classes
    -    *                     preceded by the position at which they start in
    -    *                     job.source in order.
    -    *                     The language handler should assigned this field.
    -    *        basePos: {int} the position of source in the larger source chunk.
    -    *                 All positions in the output decorations array are relative
    -    *                 to the larger source chunk.
    -    *      } }
    -    * @param {Array.<string>} fileExtensions
    -    */
    -  function registerLangHandler(handler, fileExtensions) {
    -    for (var i = fileExtensions.length; --i >= 0;) {
    -      var ext = fileExtensions[i];
    -      if (!langHandlerRegistry.hasOwnProperty(ext)) {
    -        langHandlerRegistry[ext] = handler;
    -      } else if ('console' in window) {
    -        console.warn('cannot override language handler %s', ext);
    -      }
    -    }
    -  }
    -  function langHandlerForExtension(extension, source) {
    -    if (!(extension && langHandlerRegistry.hasOwnProperty(extension))) {
    -      // Treat it as markup if the first non whitespace character is a < and
    -      // the last non-whitespace character is a >.
    -      extension = /^\s*</.test(source)
    -          ? 'default-markup'
    -          : 'default-code';
    -    }
    -    return langHandlerRegistry[extension];
    -  }
    -  registerLangHandler(decorateSource, ['default-code']);
    -  registerLangHandler(
    -      createSimpleLexer(
    -          [],
    -          [
    -           [PR_PLAIN,       /^[^<?]+/],
    -           [PR_DECLARATION, /^<!\w[^>]*(?:>|$)/],
    -           [PR_COMMENT,     /^<\!--[\s\S]*?(?:-\->|$)/],
    -           // Unescaped content in an unknown language
    -           ['lang-',        /^<\?([\s\S]+?)(?:\?>|$)/],
    -           ['lang-',        /^<%([\s\S]+?)(?:%>|$)/],
    -           [PR_PUNCTUATION, /^(?:<[%?]|[%?]>)/],
    -           ['lang-',        /^<xmp\b[^>]*>([\s\S]+?)<\/xmp\b[^>]*>/i],
    -           // Unescaped content in javascript.  (Or possibly vbscript).
    -           ['lang-js',      /^<script\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],
    -           // Contains unescaped stylesheet content
    -           ['lang-css',     /^<style\b[^>]*>([\s\S]*?)(<\/style\b[^>]*>)/i],
    -           ['lang-in.tag',  /^(<\/?[a-z][^<>]*>)/i]
    -          ]),
    -      ['default-markup', 'htm', 'html', 'mxml', 'xhtml', 'xml', 'xsl']);
    -  registerLangHandler(
    -      createSimpleLexer(
    -          [
    -           [PR_PLAIN,        /^[\s]+/, null, ' \t\r\n'],
    -           [PR_ATTRIB_VALUE, /^(?:\"[^\"]*\"?|\'[^\']*\'?)/, null, '\"\'']
    -           ],
    -          [
    -           [PR_TAG,          /^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],
    -           [PR_ATTRIB_NAME,  /^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],
    -           ['lang-uq.val',   /^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],
    -           [PR_PUNCTUATION,  /^[=<>\/]+/],
    -           ['lang-js',       /^on\w+\s*=\s*\"([^\"]+)\"/i],
    -           ['lang-js',       /^on\w+\s*=\s*\'([^\']+)\'/i],
    -           ['lang-js',       /^on\w+\s*=\s*([^\"\'>\s]+)/i],
    -           ['lang-css',      /^style\s*=\s*\"([^\"]+)\"/i],
    -           ['lang-css',      /^style\s*=\s*\'([^\']+)\'/i],
    -           ['lang-css',      /^style\s*=\s*([^\"\'>\s]+)/i]
    -           ]),
    -      ['in.tag']);
    -  registerLangHandler(
    -      createSimpleLexer([], [[PR_ATTRIB_VALUE, /^[\s\S]+/]]), ['uq.val']);
    -  registerLangHandler(sourceDecorator({
    -          'keywords': CPP_KEYWORDS,
    -          'hashComments': true,
    -          'cStyleComments': true
    -        }), ['c', 'cc', 'cpp', 'cxx', 'cyc', 'm']);
    -  registerLangHandler(sourceDecorator({
    -          'keywords': 'null true false'
    -        }), ['json']);
    -  registerLangHandler(sourceDecorator({
    -          'keywords': CSHARP_KEYWORDS,
    -          'hashComments': true,
    -          'cStyleComments': true,
    -          'verbatimStrings': true
    -        }), ['cs']);
    -  registerLangHandler(sourceDecorator({
    -          'keywords': JAVA_KEYWORDS,
    -          'cStyleComments': true
    -        }), ['java']);
    -  registerLangHandler(sourceDecorator({
    -          'keywords': SH_KEYWORDS,
    -          'hashComments': true,
    -          'multiLineStrings': true
    -        }), ['bsh', 'csh', 'sh']);
    -  registerLangHandler(sourceDecorator({
    -          'keywords': PYTHON_KEYWORDS,
    -          'hashComments': true,
    -          'multiLineStrings': true,
    -          'tripleQuotedStrings': true
    -        }), ['cv', 'py']);
    -  registerLangHandler(sourceDecorator({
    -          'keywords': PERL_KEYWORDS,
    -          'hashComments': true,
    -          'multiLineStrings': true,
    -          'regexLiterals': true
    -        }), ['perl', 'pl', 'pm']);
    -  registerLangHandler(sourceDecorator({
    -          'keywords': RUBY_KEYWORDS,
    -          'hashComments': true,
    -          'multiLineStrings': true,
    -          'regexLiterals': true
    -        }), ['rb']);
    -  registerLangHandler(sourceDecorator({
    -          'keywords': JSCRIPT_KEYWORDS,
    -          'cStyleComments': true,
    -          'regexLiterals': true
    -        }), ['js']);
    -  registerLangHandler(
    -      createSimpleLexer([], [[PR_STRING, /^[\s\S]+/]]), ['regex']);
    -
    -  function applyDecorator(job) {
    -    var sourceCodeHtml = job.sourceCodeHtml;
    -    var opt_langExtension = job.langExtension;
    -
    -    // Prepopulate output in case processing fails with an exception.
    -    job.prettyPrintedHtml = sourceCodeHtml;
    -
    -    try {
    -      // Extract tags, and convert the source code to plain text.
    -      var sourceAndExtractedTags = extractTags(sourceCodeHtml);
    -      /** Plain text. @type {string} */
    -      var source = sourceAndExtractedTags.source;
    -      job.source = source;
    -      job.basePos = 0;
    -
    -      /** Even entries are positions in source in ascending order.  Odd entries
    -        * are tags that were extracted at that position.
    -        * @type {Array.<number|string>}
    -        */
    -      job.extractedTags = sourceAndExtractedTags.tags;
    -
    -      // Apply the appropriate language handler
    -      langHandlerForExtension(opt_langExtension, source)(job);
    -      // Integrate the decorations and tags back into the source code to produce
    -      // a decorated html string which is left in job.prettyPrintedHtml.
    -      recombineTagsAndDecorations(job);
    -    } catch (e) {
    -      if ('console' in window) {
    -        console.log(e);
    -        console.trace();
    -      }
    -    }
    -  }
    -
    -  function prettyPrintOne(sourceCodeHtml, opt_langExtension) {
    -    var job = {
    -      sourceCodeHtml: sourceCodeHtml,
    -      langExtension: opt_langExtension
    -    };
    -    applyDecorator(job);
    -    return job.prettyPrintedHtml;
    -  }
    -
    -  function prettyPrint(opt_whenDone) {
    -    var isIE678 = window['_pr_isIE6']();
    -    var ieNewline = isIE678 === 6 ? '\r\n' : '\r';
    -    // See bug 71 and http://stackoverflow.com/questions/136443/why-doesnt-ie7-
    -
    -    // fetch a list of nodes to rewrite
    -    var codeSegments = [
    -        document.getElementsByTagName('pre'),
    -        document.getElementsByTagName('code'),
    -        document.getElementsByTagName('td'),  /* ND Change: Add tables to support prototypes. */
    -        document.getElementsByTagName('xmp') ];
    -    var elements = [];
    -    for (var i = 0; i < codeSegments.length; ++i) {
    -      for (var j = 0, n = codeSegments[i].length; j < n; ++j) {
    -        elements.push(codeSegments[i][j]);
    -      }
    -    }
    -    codeSegments = null;
    -
    -    var clock = Date;
    -    if (!clock['now']) {
    -      clock = { 'now': function () { return (new Date).getTime(); } };
    -    }
    -
    -    // The loop is broken into a series of continuations to make sure that we
    -    // don't make the browser unresponsive when rewriting a large page.
    -    var k = 0;
    -    var prettyPrintingJob;
    -
    -    function doWork() {
    -      var endTime = (window['PR_SHOULD_USE_CONTINUATION'] ?
    -                     clock.now() + 250 /* ms */ :
    -                     Infinity);
    -      for (; k < elements.length && clock.now() < endTime; k++) {
    -        var cs = elements[k];
    -        if (cs.className && cs.className.indexOf('prettyprint') >= 0) {
    -          // If the classes includes a language extensions, use it.
    -          // Language extensions can be specified like
    -          //     <pre class="prettyprint lang-cpp">
    -          // the language extension "cpp" is used to find a language handler as
    -          // passed to PR_registerLangHandler.
    -          var langExtension = cs.className.match(/\blang-(\w+)\b/);
    -          if (langExtension) { langExtension = langExtension[1]; }
    -
    -          // make sure this is not nested in an already prettified element
    -          var nested = false;
    -          for (var p = cs.parentNode; p; p = p.parentNode) {
    -            if ((p.tagName === 'pre' || p.tagName === 'code' ||
    -                 p.tagName === 'xmp' || p.tagName === 'td') &&  /* ND Change: Add tables to support prototypes */
    -                p.className && p.className.indexOf('prettyprint') >= 0) {
    -              nested = true;
    -              break;
    -            }
    -          }
    -          if (!nested) {
    -            // fetch the content as a snippet of properly escaped HTML.
    -            // Firefox adds newlines at the end.
    -            var content = getInnerHtml(cs);
    -            content = content.replace(/(?:\r\n?|\n)$/, '');
    -
    -	  		/* ND Change: we need to preserve &nbsp;s so change them to a special character instead of a space. */
    -			content = content.replace(/&nbsp;/g, '\x11');
    -
    -            // do the pretty printing
    -            prettyPrintingJob = {
    -              sourceCodeHtml: content,
    -              langExtension: langExtension,
    -              sourceNode: cs
    -            };
    -            applyDecorator(prettyPrintingJob);
    -            replaceWithPrettyPrintedHtml();
    -          }
    -        }
    -      }
    -      if (k < elements.length) {
    -        // finish up in a continuation
    -        setTimeout(doWork, 250);
    -      } else if (opt_whenDone) {
    -        opt_whenDone();
    -      }
    -    }
    -
    -    function replaceWithPrettyPrintedHtml() {
    -      var newContent = prettyPrintingJob.prettyPrintedHtml;
    -      if (!newContent) { return; }
    -
    -      /* ND Change: Restore the preserved &nbsp;s.  */
    -	  newContent = newContent.replace(/\x11/g, '&nbsp;');
    -
    -      var cs = prettyPrintingJob.sourceNode;
    -
    -      // push the prettified html back into the tag.
    -      if (!isRawContent(cs)) {
    -        // just replace the old html with the new
    -        cs.innerHTML = newContent;
    -      } else {
    -        // we need to change the tag to a <pre> since <xmp>s do not allow
    -        // embedded tags such as the span tags used to attach styles to
    -        // sections of source code.
    -        var pre = document.createElement('PRE');
    -        for (var i = 0; i < cs.attributes.length; ++i) {
    -          var a = cs.attributes[i];
    -          if (a.specified) {
    -            var aname = a.name.toLowerCase();
    -            if (aname === 'class') {
    -              pre.className = a.value;  // For IE 6
    -            } else {
    -              pre.setAttribute(a.name, a.value);
    -            }
    -          }
    -        }
    -        pre.innerHTML = newContent;
    -
    -        // remove the old
    -        cs.parentNode.replaceChild(pre, cs);
    -        cs = pre;
    -      }
    -
    -      // Replace <br>s with line-feeds so that copying and pasting works
    -      // on IE 6.
    -      // Doing this on other browsers breaks lots of stuff since \r\n is
    -      // treated as two newlines on Firefox, and doing this also slows
    -      // down rendering.
    -      if (isIE678 && cs.tagName === 'PRE') {
    -        var lineBreaks = cs.getElementsByTagName('br');
    -        for (var j = lineBreaks.length; --j >= 0;) {
    -          var lineBreak = lineBreaks[j];
    -          lineBreak.parentNode.replaceChild(
    -              document.createTextNode(ieNewline), lineBreak);
    -        }
    -      }
    -    }
    -
    -    doWork();
    -  }
    -
    -  window['PR_normalizedHtml'] = normalizedHtml;
    -  window['prettyPrintOne'] = prettyPrintOne;
    -  window['prettyPrint'] = prettyPrint;
    -  window['PR'] = {
    -        'combinePrefixPatterns': combinePrefixPatterns,
    -        'createSimpleLexer': createSimpleLexer,
    -        'registerLangHandler': registerLangHandler,
    -        'sourceDecorator': sourceDecorator,
    -        'PR_ATTRIB_NAME': PR_ATTRIB_NAME,
    -        'PR_ATTRIB_VALUE': PR_ATTRIB_VALUE,
    -        'PR_COMMENT': PR_COMMENT,
    -        'PR_DECLARATION': PR_DECLARATION,
    -        'PR_KEYWORD': PR_KEYWORD,
    -        'PR_LITERAL': PR_LITERAL,
    -        'PR_NOCODE': PR_NOCODE,
    -        'PR_PLAIN': PR_PLAIN,
    -        'PR_PUNCTUATION': PR_PUNCTUATION,
    -        'PR_SOURCE': PR_SOURCE,
    -        'PR_STRING': PR_STRING,
    -        'PR_TAG': PR_TAG,
    -        'PR_TYPE': PR_TYPE
    -      };
    -})();
    -
    -
    -// ____________________________________________________________________________
    -
    -
    -
    -// Lua extension
    -
    -PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[\t\n\r \xA0]+/,null,'	\n\r \xa0'],[PR.PR_STRING,/^(?:\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)|\'(?:[^\'\\]|\\[\s\S])*(?:\'|$))/,null,'\"\'']],[[PR.PR_COMMENT,/^--(?:\[(=*)\[[\s\S]*?(?:\]\1\]|$)|[^\r\n]*)/],[PR.PR_STRING,/^\[(=*)\[[\s\S]*?(?:\]\1\]|$)/],[PR.PR_KEYWORD,/^(?:and|break|do|else|elseif|end|false|for|function|if|in|local|nil|not|or|repeat|return|then|true|until|while)\b/,null],[PR.PR_LITERAL,/^[+-]?(?:0x[\da-f]+|(?:(?:\.\d+|\d+(?:\.\d*)?)(?:e[+\-]?\d+)?))/i],[PR.PR_PLAIN,/^[a-z_]\w*/i],[PR.PR_PUNCTUATION,/^[^\w\t\n\r \xA0][^\w\t\n\r \xA0\"\'\-\+=]*/]]),['lua'])
    -
    -
    -// Haskell extension
    -
    -PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[\t\n\x0B\x0C\r ]+/,null,'	\n\r '],[PR.PR_STRING,/^\"(?:[^\"\\\n\x0C\r]|\\[\s\S])*(?:\"|$)/,null,'\"'],[PR.PR_STRING,/^\'(?:[^\'\\\n\x0C\r]|\\[^&])\'?/,null,'\''],[PR.PR_LITERAL,/^(?:0o[0-7]+|0x[\da-f]+|\d+(?:\.\d+)?(?:e[+\-]?\d+)?)/i,null,'0123456789']],[[PR.PR_COMMENT,/^(?:(?:--+(?:[^\r\n\x0C]*)?)|(?:\{-(?:[^-]|-+[^-\}])*-\}))/],[PR.PR_KEYWORD,/^(?:case|class|data|default|deriving|do|else|if|import|in|infix|infixl|infixr|instance|let|module|newtype|of|then|type|where|_)(?=[^a-zA-Z0-9\']|$)/,null],[PR.PR_PLAIN,/^(?:[A-Z][\w\']*\.)*[a-zA-Z][\w\']*/],[PR.PR_PUNCTUATION,/^[^\t\n\x0B\x0C\r a-zA-Z0-9\'\"]+/]]),['hs'])
    -
    -
    -// ML extension
    -
    -PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[\t\n\r \xA0]+/,null,'	\n\r \xa0'],[PR.PR_COMMENT,/^#(?:if[\t\n\r \xA0]+(?:[a-z_$][\w\']*|``[^\r\n\t`]*(?:``|$))|else|endif|light)/i,null,'#'],[PR.PR_STRING,/^(?:\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)|\'(?:[^\'\\]|\\[\s\S])*(?:\'|$))/,null,'\"\'']],[[PR.PR_COMMENT,/^(?:\/\/[^\r\n]*|\(\*[\s\S]*?\*\))/],[PR.PR_KEYWORD,/^(?:abstract|and|as|assert|begin|class|default|delegate|do|done|downcast|downto|elif|else|end|exception|extern|false|finally|for|fun|function|if|in|inherit|inline|interface|internal|lazy|let|match|member|module|mutable|namespace|new|null|of|open|or|override|private|public|rec|return|static|struct|then|to|true|try|type|upcast|use|val|void|when|while|with|yield|asr|land|lor|lsl|lsr|lxor|mod|sig|atomic|break|checked|component|const|constraint|constructor|continue|eager|event|external|fixed|functor|global|include|method|mixin|object|parallel|process|protected|pure|sealed|trait|virtual|volatile)\b/],[PR.PR_LITERAL,/^[+\-]?(?:0x[\da-f]+|(?:(?:\.\d+|\d+(?:\.\d*)?)(?:e[+\-]?\d+)?))/i],[PR.PR_PLAIN,/^(?:[a-z_]\w*[!?#]?|``[^\r\n\t`]*(?:``|$))/i],[PR.PR_PUNCTUATION,/^[^\t\n\r \xA0\"\'\w]+/]]),['fs','ml'])
    -
    -
    -// SQL extension
    -
    -PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[\t\n\r \xA0]+/,null,'	\n\r \xa0'],[PR.PR_STRING,/^(?:"(?:[^\"\\]|\\.)*"|'(?:[^\'\\]|\\.)*')/,null,'\"\'']],[[PR.PR_COMMENT,/^(?:--[^\r\n]*|\/\*[\s\S]*?(?:\*\/|$))/],[PR.PR_KEYWORD,/^(?:ADD|ALL|ALTER|AND|ANY|AS|ASC|AUTHORIZATION|BACKUP|BEGIN|BETWEEN|BREAK|BROWSE|BULK|BY|CASCADE|CASE|CHECK|CHECKPOINT|CLOSE|CLUSTERED|COALESCE|COLLATE|COLUMN|COMMIT|COMPUTE|CONSTRAINT|CONTAINS|CONTAINSTABLE|CONTINUE|CONVERT|CREATE|CROSS|CURRENT|CURRENT_DATE|CURRENT_TIME|CURRENT_TIMESTAMP|CURRENT_USER|CURSOR|DATABASE|DBCC|DEALLOCATE|DECLARE|DEFAULT|DELETE|DENY|DESC|DISK|DISTINCT|DISTRIBUTED|DOUBLE|DROP|DUMMY|DUMP|ELSE|END|ERRLVL|ESCAPE|EXCEPT|EXEC|EXECUTE|EXISTS|EXIT|FETCH|FILE|FILLFACTOR|FOR|FOREIGN|FREETEXT|FREETEXTTABLE|FROM|FULL|FUNCTION|GOTO|GRANT|GROUP|HAVING|HOLDLOCK|IDENTITY|IDENTITYCOL|IDENTITY_INSERT|IF|IN|INDEX|INNER|INSERT|INTERSECT|INTO|IS|JOIN|KEY|KILL|LEFT|LIKE|LINENO|LOAD|NATIONAL|NOCHECK|NONCLUSTERED|NOT|NULL|NULLIF|OF|OFF|OFFSETS|ON|OPEN|OPENDATASOURCE|OPENQUERY|OPENROWSET|OPENXML|OPTION|OR|ORDER|OUTER|OVER|PERCENT|PLAN|PRECISION|PRIMARY|PRINT|PROC|PROCEDURE|PUBLIC|RAISERROR|READ|READTEXT|RECONFIGURE|REFERENCES|REPLICATION|RESTORE|RESTRICT|RETURN|REVOKE|RIGHT|ROLLBACK|ROWCOUNT|ROWGUIDCOL|RULE|SAVE|SCHEMA|SELECT|SESSION_USER|SET|SETUSER|SHUTDOWN|SOME|STATISTICS|SYSTEM_USER|TABLE|TEXTSIZE|THEN|TO|TOP|TRAN|TRANSACTION|TRIGGER|TRUNCATE|TSEQUAL|UNION|UNIQUE|UPDATE|UPDATETEXT|USE|USER|VALUES|VARYING|VIEW|WAITFOR|WHEN|WHERE|WHILE|WITH|WRITETEXT)(?=[^\w-]|$)/i,null],[PR.PR_LITERAL,/^[+-]?(?:0x[\da-f]+|(?:(?:\.\d+|\d+(?:\.\d*)?)(?:e[+\-]?\d+)?))/i],[PR.PR_PLAIN,/^[a-z_][\w-]*/i],[PR.PR_PUNCTUATION,/^[^\w\t\n\r \xA0\"\'][^\w\t\n\r \xA0+\-\"\']*/]]),['sql'])
    -
    -
    -// VB extension
    -
    -PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[\t\n\r \xA0\u2028\u2029]+/,null,'	\n\r \xa0\u2028\u2029'],[PR.PR_STRING,/^(?:[\"\u201C\u201D](?:[^\"\u201C\u201D]|[\"\u201C\u201D]{2})(?:[\"\u201C\u201D]c|$)|[\"\u201C\u201D](?:[^\"\u201C\u201D]|[\"\u201C\u201D]{2})*(?:[\"\u201C\u201D]|$))/i,null,'\"\u201c\u201d'],[PR.PR_COMMENT,/^[\'\u2018\u2019][^\r\n\u2028\u2029]*/,null,'\'\u2018\u2019']],[[PR.PR_KEYWORD,/^(?:AddHandler|AddressOf|Alias|And|AndAlso|Ansi|As|Assembly|Auto|Boolean|ByRef|Byte|ByVal|Call|Case|Catch|CBool|CByte|CChar|CDate|CDbl|CDec|Char|CInt|Class|CLng|CObj|Const|CShort|CSng|CStr|CType|Date|Decimal|Declare|Default|Delegate|Dim|DirectCast|Do|Double|Each|Else|ElseIf|End|EndIf|Enum|Erase|Error|Event|Exit|Finally|For|Friend|Function|Get|GetType|GoSub|GoTo|Handles|If|Implements|Imports|In|Inherits|Integer|Interface|Is|Let|Lib|Like|Long|Loop|Me|Mod|Module|MustInherit|MustOverride|MyBase|MyClass|Namespace|New|Next|Not|NotInheritable|NotOverridable|Object|On|Option|Optional|Or|OrElse|Overloads|Overridable|Overrides|ParamArray|Preserve|Private|Property|Protected|Public|RaiseEvent|ReadOnly|ReDim|RemoveHandler|Resume|Return|Select|Set|Shadows|Shared|Short|Single|Static|Step|Stop|String|Structure|Sub|SyncLock|Then|Throw|To|Try|TypeOf|Unicode|Until|Variant|Wend|When|While|With|WithEvents|WriteOnly|Xor|EndIf|GoSub|Let|Variant|Wend)\b/i,null],[PR.PR_COMMENT,/^REM[^\r\n\u2028\u2029]*/i],[PR.PR_LITERAL,/^(?:True\b|False\b|Nothing\b|\d+(?:E[+\-]?\d+[FRD]?|[FRDSIL])?|(?:&H[0-9A-F]+|&O[0-7]+)[SIL]?|\d*\.\d+(?:E[+\-]?\d+)?[FRD]?|#\s+(?:\d+[\-\/]\d+[\-\/]\d+(?:\s+\d+:\d+(?::\d+)?(\s*(?:AM|PM))?)?|\d+:\d+(?::\d+)?(\s*(?:AM|PM))?)\s+#)/i],[PR.PR_PLAIN,/^(?:(?:[a-z]|_\w)\w*|\[(?:[a-z]|_\w)\w*\])/i],[PR.PR_PUNCTUATION,/^[^\w\t\n\r \"\'\[\]\xA0\u2018\u2019\u201C\u201D\u2028\u2029]+/],[PR.PR_PUNCTUATION,/^(?:\[|\])/]]),['vb','vbs'])
    diff --git a/demo/apidocs/javascript/searchdata.js b/demo/apidocs/javascript/searchdata.js
    deleted file mode 100644
    index 489f934c1..000000000
    --- a/demo/apidocs/javascript/searchdata.js
    +++ /dev/null
    @@ -1,122 +0,0 @@
    -var indexSectionsWithContent = {
    -   "General": {
    -      "Symbols": true,
    -      "Numbers": false,
    -      "A": true,
    -      "B": true,
    -      "C": true,
    -      "D": true,
    -      "E": true,
    -      "F": true,
    -      "G": true,
    -      "H": true,
    -      "I": true,
    -      "J": true,
    -      "K": false,
    -      "L": true,
    -      "M": true,
    -      "N": false,
    -      "O": true,
    -      "P": true,
    -      "Q": false,
    -      "R": true,
    -      "S": true,
    -      "T": true,
    -      "U": true,
    -      "V": false,
    -      "W": false,
    -      "X": false,
    -      "Y": false,
    -      "Z": false
    -      },
    -   "Functions": {
    -      "Symbols": true,
    -      "Numbers": false,
    -      "A": true,
    -      "B": true,
    -      "C": true,
    -      "D": true,
    -      "E": true,
    -      "F": false,
    -      "G": true,
    -      "H": true,
    -      "I": true,
    -      "J": false,
    -      "K": false,
    -      "L": false,
    -      "M": true,
    -      "N": false,
    -      "O": false,
    -      "P": true,
    -      "Q": false,
    -      "R": true,
    -      "S": true,
    -      "T": true,
    -      "U": true,
    -      "V": false,
    -      "W": false,
    -      "X": false,
    -      "Y": false,
    -      "Z": false
    -      },
    -   "Classes": {
    -      "Symbols": false,
    -      "Numbers": false,
    -      "A": false,
    -      "B": false,
    -      "C": true,
    -      "D": false,
    -      "E": true,
    -      "F": false,
    -      "G": false,
    -      "H": false,
    -      "I": false,
    -      "J": true,
    -      "K": false,
    -      "L": false,
    -      "M": false,
    -      "N": false,
    -      "O": false,
    -      "P": false,
    -      "Q": false,
    -      "R": false,
    -      "S": false,
    -      "T": false,
    -      "U": false,
    -      "V": false,
    -      "W": false,
    -      "X": false,
    -      "Y": false,
    -      "Z": false
    -      },
    -   "Properties": {
    -      "Symbols": false,
    -      "Numbers": false,
    -      "A": true,
    -      "B": true,
    -      "C": true,
    -      "D": true,
    -      "E": true,
    -      "F": false,
    -      "G": false,
    -      "H": true,
    -      "I": false,
    -      "J": false,
    -      "K": false,
    -      "L": true,
    -      "M": false,
    -      "N": false,
    -      "O": true,
    -      "P": true,
    -      "Q": false,
    -      "R": true,
    -      "S": true,
    -      "T": true,
    -      "U": false,
    -      "V": false,
    -      "W": false,
    -      "X": false,
    -      "Y": false,
    -      "Z": false
    -      }
    -   }
    \ No newline at end of file
    diff --git a/demo/apidocs/search/ClassesC.html b/demo/apidocs/search/ClassesC.html
    deleted file mode 100644
    index 9c2668afd..000000000
    --- a/demo/apidocs/search/ClassesC.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_Connection><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection" target=_parent class=ISymbol>Connection</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/ClassesE.html b/demo/apidocs/search/ClassesE.html
    deleted file mode 100644
    index e9ecc753d..000000000
    --- a/demo/apidocs/search/ClassesE.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_Endpoint><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint" target=_parent class=ISymbol>Endpoint</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/FunctionsA.html b/demo/apidocs/search/FunctionsA.html
    deleted file mode 100644
    index ace0c7eee..000000000
    --- a/demo/apidocs/search/FunctionsA.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_addClass_lpael_comclazz_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.addClass(el,clazz)" target=_parent class=ISymbol>addClass(el,clazz)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_addConnection_lpaconnection_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.addConnection(connection)" target=_parent class=ISymbol>addConnection(connection)</a>, <span class=IParent>Endpoint</span></div></div><div class=SRResult id=SR_addEndpoint_lpael_com_lbkparams_rbk_com_lbkreferenceParams_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.addEndpoint(el,[params],[referenceParams])" target=_parent class=ISymbol>addEndpoint(el,[params],[referenceParams])</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_addEndpoints_lpatarget_comendpoints_com_lbkreferenceParams_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.addEndpoints(target,endpoints,[referenceParams])" target=_parent class=ISymbol>addEndpoints(target,endpoints,[referenceParams])</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_addOverlay_lpaoverlaySpec_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_addOverlay_lpaoverlaySpec_rpa')" class=ISymbol>addOverlay(overlaySpec)</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.addOverlay(overlaySpec)" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.addOverlay(overlaySpec)" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_addType_lpatypeId_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_addType_lpatypeId_rpa')" class=ISymbol>addType(typeId)</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.addType(typeId)" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.addType(typeId)" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_animate><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.animate" target=_parent class=ISymbol>animate</a>, <span class=IParent>jsPlumb</span></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/FunctionsB.html b/demo/apidocs/search/FunctionsB.html
    deleted file mode 100644
    index ab118bcd6..000000000
    --- a/demo/apidocs/search/FunctionsB.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_bind_lpaevent_comcallback_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_bind_lpaevent_comcallback_rpa')" class=ISymbol>bind(event,callback)</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.bind(event,callback)" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.bind(event,callback)" target=_parent class=IParent>Endpoint</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.bind(event,callback)" target=_parent class=IParent>jsPlumb</a></div></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/FunctionsC.html b/demo/apidocs/search/FunctionsC.html
    deleted file mode 100644
    index 3bfc93a49..000000000
    --- a/demo/apidocs/search/FunctionsC.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_connect_lpaparams_com_lbkreferenceParams_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.connect(params,[referenceParams])" target=_parent class=ISymbol>connect(params,[referenceParams])</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_Connection><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.Connection" target=_parent class=ISymbol>Connection</a>, <span class=IParent>Connection</span></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/FunctionsD.html b/demo/apidocs/search/FunctionsD.html
    deleted file mode 100644
    index 5cb26f077..000000000
    --- a/demo/apidocs/search/FunctionsD.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_deleteEndpoint_lpaobject_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.deleteEndpoint(object)" target=_parent class=ISymbol>deleteEndpoint(object)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_deleteEveryEndpoint><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.deleteEveryEndpoint" target=_parent class=ISymbol>deleteEveryEndpoint</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_detach_lpaconnection_com_lbkignoreTarget_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.detach(connection,[ignoreTarget])" target=_parent class=ISymbol>detach(connection,[ignoreTarget])</a>, <span class=IParent>Endpoint</span></div></div><div class=SRResult id=SR_detach_lpaconnection_com_lbkparams_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.detach(connection,[params])" target=_parent class=ISymbol>detach(connection,[params])</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_detachAll_lpa_lbkfireEvent_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.detachAll([fireEvent])" target=_parent class=ISymbol>detachAll([fireEvent])</a>, <span class=IParent>Endpoint</span></div></div><div class=SRResult id=SR_detachAllConnections_lpael_com_lbkparams_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.detachAllConnections(el,[params])" target=_parent class=ISymbol>detachAllConnections(el,[params])</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_detachEveryConnection_lpa_lbkparams_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.detachEveryConnection([params])" target=_parent class=ISymbol>detachEveryConnection([params])</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_detachFrom_lpatargetEndpoint_com_lbkfireEvent_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.detachFrom(targetEndpoint,[fireEvent])" target=_parent class=ISymbol>detachFrom(targetEndpoint,[fireEvent])</a>, <span class=IParent>Endpoint</span></div></div><div class=SRResult id=SR_detachFromConnection_lpaconnection_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.detachFromConnection(connection)" target=_parent class=ISymbol>detachFromConnection(connection)</a>, <span class=IParent>Endpoint</span></div></div><div class=SRResult id=SR_draggable_lpael_com_lbkoptions_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.draggable(el,[options])" target=_parent class=ISymbol>draggable(el,[options])</a>, <span class=IParent>jsPlumb</span></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/FunctionsE.html b/demo/apidocs/search/FunctionsE.html
    deleted file mode 100644
    index 68b041d0a..000000000
    --- a/demo/apidocs/search/FunctionsE.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_Endpoint><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.Endpoint" target=_parent class=ISymbol>Endpoint</a>, <span class=IParent>Endpoint</span></div></div><div class=SRResult id=SR_extend_lpao1_como2_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.extend(o1,o2)" target=_parent class=ISymbol>extend(o1,o2)</a>, <span class=IParent>jsPlumb</span></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/FunctionsG.html b/demo/apidocs/search/FunctionsG.html
    deleted file mode 100644
    index 4205a6d1a..000000000
    --- a/demo/apidocs/search/FunctionsG.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_getAllConnections><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.getAllConnections" target=_parent class=ISymbol>getAllConnections</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_getConnections_lpaparams_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.getConnections(params)" target=_parent class=ISymbol>getConnections(params)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_getConnector><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.getConnector" target=_parent class=ISymbol>getConnector</a>, <span class=IParent>Connection</span></div></div><div class=SRResult id=SR_getDefaultConnectionType><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.getDefaultConnectionType" target=_parent class=ISymbol>getDefaultConnectionType</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_getDefaultEndpointType><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.getDefaultEndpointType" target=_parent class=ISymbol>getDefaultEndpointType</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_getDefaultScope><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.getDefaultScope" target=_parent class=ISymbol>getDefaultScope</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_getElement><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.getElement" target=_parent class=ISymbol>getElement</a>, <span class=IParent>Endpoint</span></div></div><div class=SRResult id=SR_getEndpoint_lpauuid_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.getEndpoint(uuid)" target=_parent class=ISymbol>getEndpoint(uuid)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_getEndpoints_lpael_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.getEndpoints(el)" target=_parent class=ISymbol>getEndpoints(el)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_getInstanceIndex><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.getInstanceIndex" target=_parent class=ISymbol>getInstanceIndex</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_getLabel><div class=IEntry><a href="javascript:searchResults.Toggle('SR_getLabel')" class=ISymbol>getLabel</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.getLabel" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.getLabel" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_getLabelOverlay><div class=IEntry><a href="javascript:searchResults.Toggle('SR_getLabelOverlay')" class=ISymbol>getLabelOverlay</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.getLabelOverlay" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.getLabelOverlay" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_getOverlay_lpaoverlayId_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_getOverlay_lpaoverlayId_rpa')" class=ISymbol>getOverlay(overlayId)</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.getOverlay(overlayId)" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.getOverlay(overlayId)" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_getOverlays><div class=IEntry><a href="javascript:searchResults.Toggle('SR_getOverlays')" class=ISymbol>getOverlays</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.getOverlays" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.getOverlays" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_getPaintStyle><div class=IEntry><a href="javascript:searchResults.Toggle('SR_getPaintStyle')" class=ISymbol>getPaintStyle</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.getPaintStyle" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.getPaintStyle" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_getParameter_lpakey_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_getParameter_lpakey_rpa')" class=ISymbol>getParameter(key)</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.getParameter(key)" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.getParameter(key)" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_getParameters><div class=IEntry><a href="javascript:searchResults.Toggle('SR_getParameters')" class=ISymbol>getParameters</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.getParameters" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.getParameters" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_getSelector_lpa_lbkcontext_rbk_comspec_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.getSelector([context],spec)" target=_parent class=ISymbol>getSelector([context],spec)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_getType><div class=IEntry><a href="javascript:searchResults.Toggle('SR_getType')" class=ISymbol>getType</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.getType" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.getType" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_getType_lpaid_comtypeDescriptor_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.getType(id,typeDescriptor)" target=_parent class=ISymbol>getType(id,typeDescriptor)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_getUuid><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.getUuid" target=_parent class=ISymbol>getUuid</a>, <span class=IParent>Endpoint</span></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/FunctionsH.html b/demo/apidocs/search/FunctionsH.html
    deleted file mode 100644
    index a951ae86e..000000000
    --- a/demo/apidocs/search/FunctionsH.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_hasClass_lpael_comclazz_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.hasClass(el,clazz)" target=_parent class=ISymbol>hasClass(el,clazz)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_hasType_lpatypeId_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_hasType_lpatypeId_rpa')" class=ISymbol>hasType(typeId)</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.hasType(typeId)" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.hasType(typeId)" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_hide_lpael_com_lbkchangeEndpoints_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.hide(el,[changeEndpoints])" target=_parent class=ISymbol>hide(el,[changeEndpoints])</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_hideOverlay_lpaoverlayId_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_hideOverlay_lpaoverlayId_rpa')" class=ISymbol>hideOverlay(overlayId)</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.hideOverlay(overlayId)" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.hideOverlay(overlayId)" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_hideOverlays><div class=IEntry><a href="javascript:searchResults.Toggle('SR_hideOverlays')" class=ISymbol>hideOverlays</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.hideOverlays" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.hideOverlays" target=_parent class=IParent>Endpoint</a></div></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/FunctionsI.html b/demo/apidocs/search/FunctionsI.html
    deleted file mode 100644
    index 7b2545f73..000000000
    --- a/demo/apidocs/search/FunctionsI.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_importDefaults_lpadefaults_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.importDefaults(defaults)" target=_parent class=ISymbol>importDefaults(defaults)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_isConnectedTo_lpaendpoint_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.isConnectedTo(endpoint)" target=_parent class=ISymbol>isConnectedTo(endpoint)</a>, <span class=IParent>Endpoint</span></div></div><div class=SRResult id=SR_isDetachable><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.isDetachable" target=_parent class=ISymbol>isDetachable</a>, <span class=IParent>Connection</span></div></div><div class=SRResult id=SR_isEditable><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.isEditable" target=_parent class=ISymbol>isEditable</a>, <span class=IParent>Connection</span></div></div><div class=SRResult id=SR_isEnabled><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.isEnabled" target=_parent class=ISymbol>isEnabled</a>, <span class=IParent>Endpoint</span></div></div><div class=SRResult id=SR_isFull><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.isFull" target=_parent class=ISymbol>isFull</a>, <span class=IParent>Endpoint</span></div></div><div class=SRResult id=SR_isHoverSuspended><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.isHoverSuspended" target=_parent class=ISymbol>isHoverSuspended</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_isReattach><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.isReattach" target=_parent class=ISymbol>isReattach</a>, <span class=IParent>Connection</span></div></div><div class=SRResult id=SR_isSource_lpael_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.isSource(el)" target=_parent class=ISymbol>isSource(el)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_isSourceEnabled_lpael_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.isSourceEnabled(el)" target=_parent class=ISymbol>isSourceEnabled(el)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_isSuspendDrawing><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.isSuspendDrawing" target=_parent class=ISymbol>isSuspendDrawing</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_isTarget_lpael_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.isTarget(el)" target=_parent class=ISymbol>isTarget(el)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_isTargetEnabled_lpael_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.isTargetEnabled(el)" target=_parent class=ISymbol>isTargetEnabled(el)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_isVisible><div class=IEntry><a href="javascript:searchResults.Toggle('SR_isVisible')" class=ISymbol>isVisible</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.isVisible" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.isVisible" target=_parent class=IParent>Endpoint</a></div></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/FunctionsM.html b/demo/apidocs/search/FunctionsM.html
    deleted file mode 100644
    index e4deb7a76..000000000
    --- a/demo/apidocs/search/FunctionsM.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_makeSource_lpael_com_lbkparams_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.makeSource(el,[params])" target=_parent class=ISymbol>makeSource(el,[params])</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_makeSources_lpaels_com_lbkparams_rbk_com_lbkreferenceParams_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.makeSources(els,[params],[referenceParams])" target=_parent class=ISymbol>makeSources(els,[params],[referenceParams])</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_makeTarget_lpael_com_lbkparams_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.makeTarget(el,[params])" target=_parent class=ISymbol>makeTarget(el,[params])</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_makeTargets_lpaels_com_lbkparams_rbk_com_lbkreferenceParams_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.makeTargets(els,[params],[referenceParams])" target=_parent class=ISymbol>makeTargets(els,[params],[referenceParams])</a>, <span class=IParent>jsPlumb</span></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/FunctionsP.html b/demo/apidocs/search/FunctionsP.html
    deleted file mode 100644
    index ed116997a..000000000
    --- a/demo/apidocs/search/FunctionsP.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_paint_lpaparams_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.paint(params)" target=_parent class=ISymbol>paint(params)</a>, <span class=IParent>Endpoint</span></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/FunctionsR.html b/demo/apidocs/search/FunctionsR.html
    deleted file mode 100644
    index 5c86371fc..000000000
    --- a/demo/apidocs/search/FunctionsR.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_ready_lpafn_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.ready(fn)" target=_parent class=ISymbol>ready(fn)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_reapplyTypes_lpadata_com_lbkdoNotRepaint_rbk_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_reapplyTypes_lpadata_com_lbkdoNotRepaint_rbk_rpa')" class=ISymbol>reapplyTypes(data,[doNotRepaint])</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.reapplyTypes(data,[doNotRepaint])" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.reapplyTypes(data,[doNotRepaint])" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_recalculateOffsets_lpael_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.recalculateOffsets(el)" target=_parent class=ISymbol>recalculateOffsets(el)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_registerConnectionType_lpatypeId_comtype_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.registerConnectionType(typeId,type)" target=_parent class=ISymbol>registerConnectionType(typeId,type)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_registerConnectionTypes_lpatypes_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.registerConnectionTypes(types)" target=_parent class=ISymbol>registerConnectionTypes(types)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_registerEndpointType_lpatypeId_comtype_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.registerEndpointType(typeId,type)" target=_parent class=ISymbol>registerEndpointType(typeId,type)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_registerEndpointTypes_lpatypes_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.registerEndpointTypes(types)" target=_parent class=ISymbol>registerEndpointTypes(types)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_remove_lpael_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.remove(el)" target=_parent class=ISymbol>remove(el)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_removeAllEndpoints_lpael_com_lbkrecurse_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.removeAllEndpoints(el,[recurse])" target=_parent class=ISymbol>removeAllEndpoints(el,[recurse])</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_removeAllOverlays><div class=IEntry><a href="javascript:searchResults.Toggle('SR_removeAllOverlays')" class=ISymbol>removeAllOverlays</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.removeAllOverlays" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.removeAllOverlays" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_removeClass_lpael_comclazz_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.removeClass(el,clazz)" target=_parent class=ISymbol>removeClass(el,clazz)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_removeOverlay_lpaoverlayId_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_removeOverlay_lpaoverlayId_rpa')" class=ISymbol>removeOverlay(overlayId)</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.removeOverlay(overlayId)" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.removeOverlay(overlayId)" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_removeType_lpatypeId_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_removeType_lpatypeId_rpa')" class=ISymbol>removeType(typeId)</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.removeType(typeId)" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.removeType(typeId)" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_repaint_lpael_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.repaint(el)" target=_parent class=ISymbol>repaint(el)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_repaintEverything><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.repaintEverything" target=_parent class=ISymbol>repaintEverything</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_restoreDefaults><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.restoreDefaults" target=_parent class=ISymbol>restoreDefaults</a>, <span class=IParent>jsPlumb</span></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/FunctionsS.html b/demo/apidocs/search/FunctionsS.html
    deleted file mode 100644
    index b932e6985..000000000
    --- a/demo/apidocs/search/FunctionsS.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_select_lpaparams_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.select(params)" target=_parent class=ISymbol>select(params)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_selectEndpoints_lpaparams_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.selectEndpoints(params)" target=_parent class=ISymbol>selectEndpoints(params)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_setAnchor_lpaanchorParams_com_lbkdoNotRepaint_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.setAnchor(anchorParams,[doNotRepaint])" target=_parent class=ISymbol>setAnchor(anchorParams,[doNotRepaint])</a>, <span class=IParent>Endpoint</span></div></div><div class=SRResult id=SR_setConnector_lpaconnector_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.setConnector(connector)" target=_parent class=ISymbol>setConnector(connector)</a>, <span class=IParent>Connection</span></div></div><div class=SRResult id=SR_setDefaultScope_lpascope_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.setDefaultScope(scope)" target=_parent class=ISymbol>setDefaultScope(scope)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_setDetachable_lpadetachable_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.setDetachable(detachable)" target=_parent class=ISymbol>setDetachable(detachable)</a>, <span class=IParent>Connection</span></div></div><div class=SRResult id=SR_setDragAllowedWhenFull_lpaallowed_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.setDragAllowedWhenFull(allowed)" target=_parent class=ISymbol>setDragAllowedWhenFull(allowed)</a>, <span class=IParent>Endpoint</span></div></div><div class=SRResult id=SR_setDraggable_lpael_comdraggable_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.setDraggable(el,draggable)" target=_parent class=ISymbol>setDraggable(el,draggable)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_setEditable_lpaeditable_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.setEditable(editable)" target=_parent class=ISymbol>setEditable(editable)</a>, <span class=IParent>Connection</span></div></div><div class=SRResult id=SR_setElement_lpael_com_lbkcontainer_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.setElement(el,[container])" target=_parent class=ISymbol>setElement(el,[container])</a>, <span class=IParent>Endpoint</span></div></div><div class=SRResult id=SR_setEnabled_lpaenabled_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.setEnabled(enabled)" target=_parent class=ISymbol>setEnabled(enabled)</a>, <span class=IParent>Endpoint</span></div></div><div class=SRResult id=SR_setEndpoint_lpaendpointSpec_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.setEndpoint(endpointSpec)" target=_parent class=ISymbol>setEndpoint(endpointSpec)</a>, <span class=IParent>Endpoint</span></div></div><div class=SRResult id=SR_setHover_lpahover_com_lbkignoreAttachedElements_rbk_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_setHover_lpahover_com_lbkignoreAttachedElements_rbk_rpa')" class=ISymbol>setHover(hover,[ignoreAttachedElements])</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.setHover(hover,[ignoreAttachedElements])" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.setHover(hover,[ignoreAttachedElements])" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_setHoverPaintStyle_lpastyle_com_lbkdoNotRepaint_rbk_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_setHoverPaintStyle_lpastyle_com_lbkdoNotRepaint_rbk_rpa')" class=ISymbol>setHoverPaintStyle(style,[doNotRepaint])</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.setHoverPaintStyle(style,[doNotRepaint])" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.setHoverPaintStyle(style,[doNotRepaint])" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_setHoverSuspended_lpas_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.setHoverSuspended(s)" target=_parent class=ISymbol>setHoverSuspended(s)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_setId_lpael_comnewId_com_lbkdoNotSetAttribute_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.setId(el,newId,[doNotSetAttribute])" target=_parent class=ISymbol>setId(el,newId,[doNotSetAttribute])</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_setIdChanged_lpaoldId_comnewId_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.setIdChanged(oldId,newId)" target=_parent class=ISymbol>setIdChanged(oldId,newId)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_setLabel_lpalabel_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_setLabel_lpalabel_rpa')" class=ISymbol>setLabel(label)</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.setLabel(label)" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.setLabel(label)" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_setPaintStyle_lpastyle_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_setPaintStyle_lpastyle_rpa')" class=ISymbol>setPaintStyle(style)</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.setPaintStyle(style)" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.setPaintStyle(style)" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_setParameter_lpakey_comvalue_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_setParameter_lpakey_comvalue_rpa')" class=ISymbol>setParameter(key,value)</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.setParameter(key,value)" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.setParameter(key,value)" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_setParameters_lpaparameters_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_setParameters_lpaparameters_rpa')" class=ISymbol>setParameters(parameters)</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.setParameters(parameters)" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.setParameters(parameters)" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_setReattach_lpareattach_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.setReattach(reattach)" target=_parent class=ISymbol>setReattach(reattach)</a>, <span class=IParent>Connection</span></div></div><div class=SRResult id=SR_setSourceEnabled_lpael_comstate_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.setSourceEnabled(el,state)" target=_parent class=ISymbol>setSourceEnabled(el,state)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_setSuspendDrawing_lpaval_com_lbkrepaintAfterwards_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.setSuspendDrawing(val,[repaintAfterwards])" target=_parent class=ISymbol>setSuspendDrawing(val,[repaintAfterwards])</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_setTargetEnabled_lpael_comstate_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.setTargetEnabled(el,state)" target=_parent class=ISymbol>setTargetEnabled(el,state)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_setType_lpatypeId_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_setType_lpatypeId_rpa')" class=ISymbol>setType(typeId)</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.setType(typeId)" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.setType(typeId)" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_setVisible_lpavisible_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.setVisible(visible)" target=_parent class=ISymbol>setVisible(visible)</a>, <span class=IParent>Connection</span></div></div><div class=SRResult id=SR_setVisible_lpavisible_com_lbkdoNotChangeConnections_rbk_com_lbkdoNotNotifyOtherEndpoint_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.setVisible(visible,[doNotChangeConnections],[doNotNotifyOtherEndpoint])" target=_parent class=ISymbol>setVisible(visible,[doNotChangeConnections],[doNotNotifyOtherEndpoint])</a>, <span class=IParent>Endpoint</span></div></div><div class=SRResult id=SR_show_lpael_com_lbkchangeEndpoints_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.show(el,[changeEndpoints])" target=_parent class=ISymbol>show(el,[changeEndpoints])</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_showOverlay_lpaoverlayId_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_showOverlay_lpaoverlayId_rpa')" class=ISymbol>showOverlay(overlayId)</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.showOverlay(overlayId)" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.showOverlay(overlayId)" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_showOverlays><div class=IEntry><a href="javascript:searchResults.Toggle('SR_showOverlays')" class=ISymbol>showOverlays</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.showOverlays" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.showOverlays" target=_parent class=IParent>Endpoint</a></div></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/FunctionsT.html b/demo/apidocs/search/FunctionsT.html
    deleted file mode 100644
    index bff099dd0..000000000
    --- a/demo/apidocs/search/FunctionsT.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_toggleDraggable_lpael_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.toggleDraggable(el)" target=_parent class=ISymbol>toggleDraggable(el)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_toggleSourceEnabled_lpael_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.toggleSourceEnabled(el)" target=_parent class=ISymbol>toggleSourceEnabled(el)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_toggleTargetEnabled_lpael_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.toggleTargetEnabled(el)" target=_parent class=ISymbol>toggleTargetEnabled(el)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_toggleType_lpatypeId_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_toggleType_lpatypeId_rpa')" class=ISymbol>toggleType(typeId)</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.toggleType(typeId)" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.toggleType(typeId)" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_toggleVisible_lpael_com_lbkchangeEndpoints_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.toggleVisible(el,[changeEndpoints])" target=_parent class=ISymbol>toggleVisible(el,[changeEndpoints])</a>, <span class=IParent>jsPlumb</span></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/FunctionsU.html b/demo/apidocs/search/FunctionsU.html
    deleted file mode 100644
    index 30db18ef4..000000000
    --- a/demo/apidocs/search/FunctionsU.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_unbind_lpa_lbkevent_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.unbind([event])" target=_parent class=ISymbol>unbind([event])</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_unmakeEverySource><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.unmakeEverySource" target=_parent class=ISymbol>unmakeEverySource</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_unmakeEveryTarget><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.unmakeEveryTarget" target=_parent class=ISymbol>unmakeEveryTarget</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_unmakeSource_lpael_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.unmakeSource(el)" target=_parent class=ISymbol>unmakeSource(el)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_unmakeTarget_lpael_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.unmakeTarget(el)" target=_parent class=ISymbol>unmakeTarget(el)</a>, <span class=IParent>jsPlumb</span></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/GeneralA.html b/demo/apidocs/search/GeneralA.html
    deleted file mode 100644
    index 70871e795..000000000
    --- a/demo/apidocs/search/GeneralA.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_addClass_lpael_comclazz_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.addClass(el,clazz)" target=_parent class=ISymbol>addClass(el,clazz)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_addConnection_lpaconnection_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.addConnection(connection)" target=_parent class=ISymbol>addConnection(connection)</a>, <span class=IParent>Endpoint</span></div></div><div class=SRResult id=SR_addEndpoint_lpael_com_lbkparams_rbk_com_lbkreferenceParams_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.addEndpoint(el,[params],[referenceParams])" target=_parent class=ISymbol>addEndpoint(el,[params],[referenceParams])</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_addEndpoints_lpatarget_comendpoints_com_lbkreferenceParams_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.addEndpoints(target,endpoints,[referenceParams])" target=_parent class=ISymbol>addEndpoints(target,endpoints,[referenceParams])</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_addOverlay_lpaoverlaySpec_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_addOverlay_lpaoverlaySpec_rpa')" class=ISymbol>addOverlay(overlaySpec)</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.addOverlay(overlaySpec)" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.addOverlay(overlaySpec)" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_addType_lpatypeId_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_addType_lpatypeId_rpa')" class=ISymbol>addType(typeId)</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.addType(typeId)" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.addType(typeId)" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_animate><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.animate" target=_parent class=ISymbol>animate</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_Assign><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Anchors.Assign" target=_parent class=ISymbol>Assign</a>, <span class=IParent>jsPlumb.Anchors</span></div></div><div class=SRResult id=SR_AutoDefault><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Anchors.AutoDefault" target=_parent class=ISymbol>AutoDefault</a>, <span class=IParent>jsPlumb.Anchors</span></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/GeneralB.html b/demo/apidocs/search/GeneralB.html
    deleted file mode 100644
    index 76249f147..000000000
    --- a/demo/apidocs/search/GeneralB.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_bind_lpaevent_comcallback_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_bind_lpaevent_comcallback_rpa')" class=ISymbol>bind(event,callback)</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.bind(event,callback)" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.bind(event,callback)" target=_parent class=IParent>Endpoint</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.bind(event,callback)" target=_parent class=IParent>jsPlumb</a></div></div></div><div class=SRResult id=SR_Bottom><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Anchors.Bottom" target=_parent class=ISymbol>Bottom</a>, <span class=IParent>jsPlumb.Anchors</span></div></div><div class=SRResult id=SR_BottomLeft><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Anchors.BottomLeft" target=_parent class=ISymbol>BottomLeft</a>, <span class=IParent>jsPlumb.Anchors</span></div></div><div class=SRResult id=SR_BottomRight><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Anchors.BottomRight" target=_parent class=ISymbol>BottomRight</a>, <span class=IParent>jsPlumb.Anchors</span></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/GeneralC.html b/demo/apidocs/search/GeneralC.html
    deleted file mode 100644
    index 91d37f0c0..000000000
    --- a/demo/apidocs/search/GeneralC.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_canvas><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.canvas" target=_parent class=ISymbol>canvas</a>, <span class=IParent>Endpoint</span></div></div><div class=SRResult id=SR_Center><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Anchors.Center" target=_parent class=ISymbol>Center</a>, <span class=IParent>jsPlumb.Anchors</span></div></div><div class=SRResult id=SR_connect_lpaparams_com_lbkreferenceParams_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.connect(params,[referenceParams])" target=_parent class=ISymbol>connect(params,[referenceParams])</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_Connection><div class=IEntry><a href="javascript:searchResults.Toggle('SR_Connection')" class=ISymbol>Connection</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection" target=_parent class=IParent>Global</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.Connection" target=_parent class=IParent>Connection</a></div></div></div><div class=SRResult id=SR_connections><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.connections" target=_parent class=ISymbol>connections</a>, <span class=IParent>Endpoint</span></div></div><div class=SRResult id=SR_connectorClass><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.connectorClass" target=_parent class=ISymbol>connectorClass</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_Continuous><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Anchors.Continuous" target=_parent class=ISymbol>Continuous</a>, <span class=IParent>jsPlumb.Anchors</span></div></div><div class=SRResult id=SR_ContinuousBottom><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Anchors.ContinuousBottom" target=_parent class=ISymbol>ContinuousBottom</a>, <span class=IParent>jsPlumb.Anchors</span></div></div><div class=SRResult id=SR_ContinuousLeft><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Anchors.ContinuousLeft" target=_parent class=ISymbol>ContinuousLeft</a>, <span class=IParent>jsPlumb.Anchors</span></div></div><div class=SRResult id=SR_ContinuousRight><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Anchors.ContinuousRight" target=_parent class=ISymbol>ContinuousRight</a>, <span class=IParent>jsPlumb.Anchors</span></div></div><div class=SRResult id=SR_ContinuousTop><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Anchors.ContinuousTop" target=_parent class=ISymbol>ContinuousTop</a>, <span class=IParent>jsPlumb.Anchors</span></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/GeneralD.html b/demo/apidocs/search/GeneralD.html
    deleted file mode 100644
    index 765f6f4f3..000000000
    --- a/demo/apidocs/search/GeneralD.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_Defaults><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Defaults" target=_parent class=ISymbol>Defaults</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_deleteEndpoint_lpaobject_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.deleteEndpoint(object)" target=_parent class=ISymbol>deleteEndpoint(object)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_deleteEveryEndpoint><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.deleteEveryEndpoint" target=_parent class=ISymbol>deleteEveryEndpoint</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_detach_lpaconnection_com_lbkignoreTarget_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.detach(connection,[ignoreTarget])" target=_parent class=ISymbol>detach(connection,[ignoreTarget])</a>, <span class=IParent>Endpoint</span></div></div><div class=SRResult id=SR_detach_lpaconnection_com_lbkparams_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.detach(connection,[params])" target=_parent class=ISymbol>detach(connection,[params])</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_detachAll_lpa_lbkfireEvent_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.detachAll([fireEvent])" target=_parent class=ISymbol>detachAll([fireEvent])</a>, <span class=IParent>Endpoint</span></div></div><div class=SRResult id=SR_detachAllConnections_lpael_com_lbkparams_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.detachAllConnections(el,[params])" target=_parent class=ISymbol>detachAllConnections(el,[params])</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_detachEveryConnection_lpa_lbkparams_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.detachEveryConnection([params])" target=_parent class=ISymbol>detachEveryConnection([params])</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_detachFrom_lpatargetEndpoint_com_lbkfireEvent_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.detachFrom(targetEndpoint,[fireEvent])" target=_parent class=ISymbol>detachFrom(targetEndpoint,[fireEvent])</a>, <span class=IParent>Endpoint</span></div></div><div class=SRResult id=SR_detachFromConnection_lpaconnection_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.detachFromConnection(connection)" target=_parent class=ISymbol>detachFromConnection(connection)</a>, <span class=IParent>Endpoint</span></div></div><div class=SRResult id=SR_draggable_lpael_com_lbkoptions_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.draggable(el,[options])" target=_parent class=ISymbol>draggable(el,[options])</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_draggingClass><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.draggingClass" target=_parent class=ISymbol>draggingClass</a>, <span class=IParent>jsPlumb</span></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/GeneralE.html b/demo/apidocs/search/GeneralE.html
    deleted file mode 100644
    index cc46a2a13..000000000
    --- a/demo/apidocs/search/GeneralE.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_elementDraggingClass><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.elementDraggingClass" target=_parent class=ISymbol>elementDraggingClass</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_Endpoint><div class=IEntry><a href="javascript:searchResults.Toggle('SR_Endpoint')" class=ISymbol>Endpoint</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint" target=_parent class=IParent>Global</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.Endpoint" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_endpointAnchorClassPrefix><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.endpointAnchorClassPrefix" target=_parent class=ISymbol>endpointAnchorClassPrefix</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_endpointClass><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.endpointClass" target=_parent class=ISymbol>endpointClass</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_endpointConnectedClass><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.endpointConnectedClass" target=_parent class=ISymbol>endpointConnectedClass</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_endpointDropAllowedClass><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.endpointDropAllowedClass" target=_parent class=ISymbol>endpointDropAllowedClass</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_endpointDropForbiddenClass><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.endpointDropForbiddenClass" target=_parent class=ISymbol>endpointDropForbiddenClass</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_endpointFullClass><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.endpointFullClass" target=_parent class=ISymbol>endpointFullClass</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_endpoints><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.endpoints" target=_parent class=ISymbol>endpoints</a>, <span class=IParent>Connection</span></div></div><div class=SRResult id=SR_extend_lpao1_como2_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.extend(o1,o2)" target=_parent class=ISymbol>extend(o1,o2)</a>, <span class=IParent>jsPlumb</span></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/GeneralF.html b/demo/apidocs/search/GeneralF.html
    deleted file mode 100644
    index dd55a70b0..000000000
    --- a/demo/apidocs/search/GeneralF.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_Functions><div class=IEntry><a href="javascript:searchResults.Toggle('SR_Functions')" class=ISymbol>Functions</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.Functions" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.Functions" target=_parent class=IParent>Endpoint</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Functions" target=_parent class=IParent>jsPlumb</a></div></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/GeneralG.html b/demo/apidocs/search/GeneralG.html
    deleted file mode 100644
    index 4205a6d1a..000000000
    --- a/demo/apidocs/search/GeneralG.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_getAllConnections><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.getAllConnections" target=_parent class=ISymbol>getAllConnections</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_getConnections_lpaparams_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.getConnections(params)" target=_parent class=ISymbol>getConnections(params)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_getConnector><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.getConnector" target=_parent class=ISymbol>getConnector</a>, <span class=IParent>Connection</span></div></div><div class=SRResult id=SR_getDefaultConnectionType><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.getDefaultConnectionType" target=_parent class=ISymbol>getDefaultConnectionType</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_getDefaultEndpointType><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.getDefaultEndpointType" target=_parent class=ISymbol>getDefaultEndpointType</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_getDefaultScope><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.getDefaultScope" target=_parent class=ISymbol>getDefaultScope</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_getElement><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.getElement" target=_parent class=ISymbol>getElement</a>, <span class=IParent>Endpoint</span></div></div><div class=SRResult id=SR_getEndpoint_lpauuid_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.getEndpoint(uuid)" target=_parent class=ISymbol>getEndpoint(uuid)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_getEndpoints_lpael_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.getEndpoints(el)" target=_parent class=ISymbol>getEndpoints(el)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_getInstanceIndex><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.getInstanceIndex" target=_parent class=ISymbol>getInstanceIndex</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_getLabel><div class=IEntry><a href="javascript:searchResults.Toggle('SR_getLabel')" class=ISymbol>getLabel</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.getLabel" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.getLabel" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_getLabelOverlay><div class=IEntry><a href="javascript:searchResults.Toggle('SR_getLabelOverlay')" class=ISymbol>getLabelOverlay</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.getLabelOverlay" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.getLabelOverlay" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_getOverlay_lpaoverlayId_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_getOverlay_lpaoverlayId_rpa')" class=ISymbol>getOverlay(overlayId)</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.getOverlay(overlayId)" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.getOverlay(overlayId)" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_getOverlays><div class=IEntry><a href="javascript:searchResults.Toggle('SR_getOverlays')" class=ISymbol>getOverlays</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.getOverlays" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.getOverlays" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_getPaintStyle><div class=IEntry><a href="javascript:searchResults.Toggle('SR_getPaintStyle')" class=ISymbol>getPaintStyle</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.getPaintStyle" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.getPaintStyle" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_getParameter_lpakey_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_getParameter_lpakey_rpa')" class=ISymbol>getParameter(key)</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.getParameter(key)" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.getParameter(key)" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_getParameters><div class=IEntry><a href="javascript:searchResults.Toggle('SR_getParameters')" class=ISymbol>getParameters</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.getParameters" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.getParameters" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_getSelector_lpa_lbkcontext_rbk_comspec_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.getSelector([context],spec)" target=_parent class=ISymbol>getSelector([context],spec)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_getType><div class=IEntry><a href="javascript:searchResults.Toggle('SR_getType')" class=ISymbol>getType</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.getType" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.getType" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_getType_lpaid_comtypeDescriptor_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.getType(id,typeDescriptor)" target=_parent class=ISymbol>getType(id,typeDescriptor)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_getUuid><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.getUuid" target=_parent class=ISymbol>getUuid</a>, <span class=IParent>Endpoint</span></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/GeneralH.html b/demo/apidocs/search/GeneralH.html
    deleted file mode 100644
    index 90c0a0b18..000000000
    --- a/demo/apidocs/search/GeneralH.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_hasClass_lpael_comclazz_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.hasClass(el,clazz)" target=_parent class=ISymbol>hasClass(el,clazz)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_hasType_lpatypeId_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_hasType_lpatypeId_rpa')" class=ISymbol>hasType(typeId)</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.hasType(typeId)" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.hasType(typeId)" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_hide_lpael_com_lbkchangeEndpoints_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.hide(el,[changeEndpoints])" target=_parent class=ISymbol>hide(el,[changeEndpoints])</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_hideOverlay_lpaoverlayId_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_hideOverlay_lpaoverlayId_rpa')" class=ISymbol>hideOverlay(overlayId)</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.hideOverlay(overlayId)" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.hideOverlay(overlayId)" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_hideOverlays><div class=IEntry><a href="javascript:searchResults.Toggle('SR_hideOverlays')" class=ISymbol>hideOverlays</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.hideOverlays" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.hideOverlays" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_hoverClass><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.hoverClass" target=_parent class=ISymbol>hoverClass</a>, <span class=IParent>jsPlumb</span></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/GeneralI.html b/demo/apidocs/search/GeneralI.html
    deleted file mode 100644
    index 7b2545f73..000000000
    --- a/demo/apidocs/search/GeneralI.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_importDefaults_lpadefaults_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.importDefaults(defaults)" target=_parent class=ISymbol>importDefaults(defaults)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_isConnectedTo_lpaendpoint_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.isConnectedTo(endpoint)" target=_parent class=ISymbol>isConnectedTo(endpoint)</a>, <span class=IParent>Endpoint</span></div></div><div class=SRResult id=SR_isDetachable><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.isDetachable" target=_parent class=ISymbol>isDetachable</a>, <span class=IParent>Connection</span></div></div><div class=SRResult id=SR_isEditable><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.isEditable" target=_parent class=ISymbol>isEditable</a>, <span class=IParent>Connection</span></div></div><div class=SRResult id=SR_isEnabled><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.isEnabled" target=_parent class=ISymbol>isEnabled</a>, <span class=IParent>Endpoint</span></div></div><div class=SRResult id=SR_isFull><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.isFull" target=_parent class=ISymbol>isFull</a>, <span class=IParent>Endpoint</span></div></div><div class=SRResult id=SR_isHoverSuspended><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.isHoverSuspended" target=_parent class=ISymbol>isHoverSuspended</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_isReattach><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.isReattach" target=_parent class=ISymbol>isReattach</a>, <span class=IParent>Connection</span></div></div><div class=SRResult id=SR_isSource_lpael_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.isSource(el)" target=_parent class=ISymbol>isSource(el)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_isSourceEnabled_lpael_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.isSourceEnabled(el)" target=_parent class=ISymbol>isSourceEnabled(el)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_isSuspendDrawing><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.isSuspendDrawing" target=_parent class=ISymbol>isSuspendDrawing</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_isTarget_lpael_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.isTarget(el)" target=_parent class=ISymbol>isTarget(el)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_isTargetEnabled_lpael_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.isTargetEnabled(el)" target=_parent class=ISymbol>isTargetEnabled(el)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_isVisible><div class=IEntry><a href="javascript:searchResults.Toggle('SR_isVisible')" class=ISymbol>isVisible</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.isVisible" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.isVisible" target=_parent class=IParent>Endpoint</a></div></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/GeneralJ.html b/demo/apidocs/search/GeneralJ.html
    deleted file mode 100644
    index cdfb93ac1..000000000
    --- a/demo/apidocs/search/GeneralJ.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_jsPlumb><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb" target=_parent class=ISymbol>jsPlumb</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/GeneralL.html b/demo/apidocs/search/GeneralL.html
    deleted file mode 100644
    index 1f8ae8c40..000000000
    --- a/demo/apidocs/search/GeneralL.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_Left><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Anchors.Left" target=_parent class=ISymbol>Left</a>, <span class=IParent>jsPlumb.Anchors</span></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/GeneralM.html b/demo/apidocs/search/GeneralM.html
    deleted file mode 100644
    index e4deb7a76..000000000
    --- a/demo/apidocs/search/GeneralM.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_makeSource_lpael_com_lbkparams_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.makeSource(el,[params])" target=_parent class=ISymbol>makeSource(el,[params])</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_makeSources_lpaels_com_lbkparams_rbk_com_lbkreferenceParams_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.makeSources(els,[params],[referenceParams])" target=_parent class=ISymbol>makeSources(els,[params],[referenceParams])</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_makeTarget_lpael_com_lbkparams_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.makeTarget(el,[params])" target=_parent class=ISymbol>makeTarget(el,[params])</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_makeTargets_lpaels_com_lbkparams_rbk_com_lbkreferenceParams_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.makeTargets(els,[params],[referenceParams])" target=_parent class=ISymbol>makeTargets(els,[params],[referenceParams])</a>, <span class=IParent>jsPlumb</span></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/GeneralO.html b/demo/apidocs/search/GeneralO.html
    deleted file mode 100644
    index b8a0009d7..000000000
    --- a/demo/apidocs/search/GeneralO.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_overlayClass><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.overlayClass" target=_parent class=ISymbol>overlayClass</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_overlays><div class=IEntry><a href="javascript:searchResults.Toggle('SR_overlays')" class=ISymbol>overlays</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.overlays" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.overlays" target=_parent class=IParent>Endpoint</a></div></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/GeneralP.html b/demo/apidocs/search/GeneralP.html
    deleted file mode 100644
    index 510d37652..000000000
    --- a/demo/apidocs/search/GeneralP.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_paint_lpaparams_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.paint(params)" target=_parent class=ISymbol>paint(params)</a>, <span class=IParent>Endpoint</span></div></div><div class=SRResult id=SR_Perimeter><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Anchors.Perimeter" target=_parent class=ISymbol>Perimeter</a>, <span class=IParent>jsPlumb.Anchors</span></div></div><div class=SRResult id=SR_Properties><div class=IEntry><a href="javascript:searchResults.Toggle('SR_Properties')" class=ISymbol>Properties</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.Properties" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.Properties" target=_parent class=IParent>Endpoint</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Properties" target=_parent class=IParent>jsPlumb</a></div></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/GeneralR.html b/demo/apidocs/search/GeneralR.html
    deleted file mode 100644
    index 46f98d91e..000000000
    --- a/demo/apidocs/search/GeneralR.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_ready_lpafn_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.ready(fn)" target=_parent class=ISymbol>ready(fn)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_reapplyTypes_lpadata_com_lbkdoNotRepaint_rbk_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_reapplyTypes_lpadata_com_lbkdoNotRepaint_rbk_rpa')" class=ISymbol>reapplyTypes(data,[doNotRepaint])</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.reapplyTypes(data,[doNotRepaint])" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.reapplyTypes(data,[doNotRepaint])" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_recalculateOffsets_lpael_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.recalculateOffsets(el)" target=_parent class=ISymbol>recalculateOffsets(el)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_registerConnectionType_lpatypeId_comtype_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.registerConnectionType(typeId,type)" target=_parent class=ISymbol>registerConnectionType(typeId,type)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_registerConnectionTypes_lpatypes_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.registerConnectionTypes(types)" target=_parent class=ISymbol>registerConnectionTypes(types)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_registerEndpointType_lpatypeId_comtype_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.registerEndpointType(typeId,type)" target=_parent class=ISymbol>registerEndpointType(typeId,type)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_registerEndpointTypes_lpatypes_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.registerEndpointTypes(types)" target=_parent class=ISymbol>registerEndpointTypes(types)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_remove_lpael_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.remove(el)" target=_parent class=ISymbol>remove(el)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_removeAllEndpoints_lpael_com_lbkrecurse_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.removeAllEndpoints(el,[recurse])" target=_parent class=ISymbol>removeAllEndpoints(el,[recurse])</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_removeAllOverlays><div class=IEntry><a href="javascript:searchResults.Toggle('SR_removeAllOverlays')" class=ISymbol>removeAllOverlays</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.removeAllOverlays" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.removeAllOverlays" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_removeClass_lpael_comclazz_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.removeClass(el,clazz)" target=_parent class=ISymbol>removeClass(el,clazz)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_removeOverlay_lpaoverlayId_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_removeOverlay_lpaoverlayId_rpa')" class=ISymbol>removeOverlay(overlayId)</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.removeOverlay(overlayId)" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.removeOverlay(overlayId)" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_removeType_lpatypeId_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_removeType_lpatypeId_rpa')" class=ISymbol>removeType(typeId)</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.removeType(typeId)" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.removeType(typeId)" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_repaint_lpael_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.repaint(el)" target=_parent class=ISymbol>repaint(el)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_repaintEverything><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.repaintEverything" target=_parent class=ISymbol>repaintEverything</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_restoreDefaults><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.restoreDefaults" target=_parent class=ISymbol>restoreDefaults</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_Right><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Anchors.Right" target=_parent class=ISymbol>Right</a>, <span class=IParent>jsPlumb.Anchors</span></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/GeneralS.html b/demo/apidocs/search/GeneralS.html
    deleted file mode 100644
    index a8506974f..000000000
    --- a/demo/apidocs/search/GeneralS.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_scope><div class=IEntry><a href="javascript:searchResults.Toggle('SR_scope')" class=ISymbol>scope</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.scope" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.scope" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_select_lpaparams_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.select(params)" target=_parent class=ISymbol>select(params)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_selectEndpoints_lpaparams_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.selectEndpoints(params)" target=_parent class=ISymbol>selectEndpoints(params)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_setAnchor_lpaanchorParams_com_lbkdoNotRepaint_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.setAnchor(anchorParams,[doNotRepaint])" target=_parent class=ISymbol>setAnchor(anchorParams,[doNotRepaint])</a>, <span class=IParent>Endpoint</span></div></div><div class=SRResult id=SR_setConnector_lpaconnector_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.setConnector(connector)" target=_parent class=ISymbol>setConnector(connector)</a>, <span class=IParent>Connection</span></div></div><div class=SRResult id=SR_setDefaultScope_lpascope_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.setDefaultScope(scope)" target=_parent class=ISymbol>setDefaultScope(scope)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_setDetachable_lpadetachable_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.setDetachable(detachable)" target=_parent class=ISymbol>setDetachable(detachable)</a>, <span class=IParent>Connection</span></div></div><div class=SRResult id=SR_setDragAllowedWhenFull_lpaallowed_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.setDragAllowedWhenFull(allowed)" target=_parent class=ISymbol>setDragAllowedWhenFull(allowed)</a>, <span class=IParent>Endpoint</span></div></div><div class=SRResult id=SR_setDraggable_lpael_comdraggable_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.setDraggable(el,draggable)" target=_parent class=ISymbol>setDraggable(el,draggable)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_setEditable_lpaeditable_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.setEditable(editable)" target=_parent class=ISymbol>setEditable(editable)</a>, <span class=IParent>Connection</span></div></div><div class=SRResult id=SR_setElement_lpael_com_lbkcontainer_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.setElement(el,[container])" target=_parent class=ISymbol>setElement(el,[container])</a>, <span class=IParent>Endpoint</span></div></div><div class=SRResult id=SR_setEnabled_lpaenabled_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.setEnabled(enabled)" target=_parent class=ISymbol>setEnabled(enabled)</a>, <span class=IParent>Endpoint</span></div></div><div class=SRResult id=SR_setEndpoint_lpaendpointSpec_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.setEndpoint(endpointSpec)" target=_parent class=ISymbol>setEndpoint(endpointSpec)</a>, <span class=IParent>Endpoint</span></div></div><div class=SRResult id=SR_setHover_lpahover_com_lbkignoreAttachedElements_rbk_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_setHover_lpahover_com_lbkignoreAttachedElements_rbk_rpa')" class=ISymbol>setHover(hover,[ignoreAttachedElements])</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.setHover(hover,[ignoreAttachedElements])" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.setHover(hover,[ignoreAttachedElements])" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_setHoverPaintStyle_lpastyle_com_lbkdoNotRepaint_rbk_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_setHoverPaintStyle_lpastyle_com_lbkdoNotRepaint_rbk_rpa')" class=ISymbol>setHoverPaintStyle(style,[doNotRepaint])</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.setHoverPaintStyle(style,[doNotRepaint])" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.setHoverPaintStyle(style,[doNotRepaint])" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_setHoverSuspended_lpas_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.setHoverSuspended(s)" target=_parent class=ISymbol>setHoverSuspended(s)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_setId_lpael_comnewId_com_lbkdoNotSetAttribute_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.setId(el,newId,[doNotSetAttribute])" target=_parent class=ISymbol>setId(el,newId,[doNotSetAttribute])</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_setIdChanged_lpaoldId_comnewId_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.setIdChanged(oldId,newId)" target=_parent class=ISymbol>setIdChanged(oldId,newId)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_setLabel_lpalabel_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_setLabel_lpalabel_rpa')" class=ISymbol>setLabel(label)</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.setLabel(label)" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.setLabel(label)" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_setPaintStyle_lpastyle_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_setPaintStyle_lpastyle_rpa')" class=ISymbol>setPaintStyle(style)</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.setPaintStyle(style)" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.setPaintStyle(style)" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_setParameter_lpakey_comvalue_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_setParameter_lpakey_comvalue_rpa')" class=ISymbol>setParameter(key,value)</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.setParameter(key,value)" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.setParameter(key,value)" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_setParameters_lpaparameters_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_setParameters_lpaparameters_rpa')" class=ISymbol>setParameters(parameters)</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.setParameters(parameters)" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.setParameters(parameters)" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_setReattach_lpareattach_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.setReattach(reattach)" target=_parent class=ISymbol>setReattach(reattach)</a>, <span class=IParent>Connection</span></div></div><div class=SRResult id=SR_setSourceEnabled_lpael_comstate_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.setSourceEnabled(el,state)" target=_parent class=ISymbol>setSourceEnabled(el,state)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_setSuspendDrawing_lpaval_com_lbkrepaintAfterwards_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.setSuspendDrawing(val,[repaintAfterwards])" target=_parent class=ISymbol>setSuspendDrawing(val,[repaintAfterwards])</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_setTargetEnabled_lpael_comstate_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.setTargetEnabled(el,state)" target=_parent class=ISymbol>setTargetEnabled(el,state)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_setType_lpatypeId_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_setType_lpatypeId_rpa')" class=ISymbol>setType(typeId)</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.setType(typeId)" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.setType(typeId)" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_setVisible_lpavisible_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.setVisible(visible)" target=_parent class=ISymbol>setVisible(visible)</a>, <span class=IParent>Connection</span></div></div><div class=SRResult id=SR_setVisible_lpavisible_com_lbkdoNotChangeConnections_rbk_com_lbkdoNotNotifyOtherEndpoint_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.setVisible(visible,[doNotChangeConnections],[doNotNotifyOtherEndpoint])" target=_parent class=ISymbol>setVisible(visible,[doNotChangeConnections],[doNotNotifyOtherEndpoint])</a>, <span class=IParent>Endpoint</span></div></div><div class=SRResult id=SR_show_lpael_com_lbkchangeEndpoints_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.show(el,[changeEndpoints])" target=_parent class=ISymbol>show(el,[changeEndpoints])</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_showOverlay_lpaoverlayId_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_showOverlay_lpaoverlayId_rpa')" class=ISymbol>showOverlay(overlayId)</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.showOverlay(overlayId)" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.showOverlay(overlayId)" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_showOverlays><div class=IEntry><a href="javascript:searchResults.Toggle('SR_showOverlays')" class=ISymbol>showOverlays</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.showOverlays" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.showOverlays" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_source><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.source" target=_parent class=ISymbol>source</a>, <span class=IParent>Connection</span></div></div><div class=SRResult id=SR_sourceId><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.sourceId" target=_parent class=ISymbol>sourceId</a>, <span class=IParent>Connection</span></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/GeneralT.html b/demo/apidocs/search/GeneralT.html
    deleted file mode 100644
    index d3eef3d40..000000000
    --- a/demo/apidocs/search/GeneralT.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_target><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.target" target=_parent class=ISymbol>target</a>, <span class=IParent>Connection</span></div></div><div class=SRResult id=SR_targetId><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.targetId" target=_parent class=ISymbol>targetId</a>, <span class=IParent>Connection</span></div></div><div class=SRResult id=SR_toggleDraggable_lpael_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.toggleDraggable(el)" target=_parent class=ISymbol>toggleDraggable(el)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_toggleSourceEnabled_lpael_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.toggleSourceEnabled(el)" target=_parent class=ISymbol>toggleSourceEnabled(el)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_toggleTargetEnabled_lpael_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.toggleTargetEnabled(el)" target=_parent class=ISymbol>toggleTargetEnabled(el)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_toggleType_lpatypeId_rpa><div class=IEntry><a href="javascript:searchResults.Toggle('SR_toggleType_lpatypeId_rpa')" class=ISymbol>toggleType(typeId)</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.toggleType(typeId)" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.toggleType(typeId)" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_toggleVisible_lpael_com_lbkchangeEndpoints_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.toggleVisible(el,[changeEndpoints])" target=_parent class=ISymbol>toggleVisible(el,[changeEndpoints])</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_Top><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Anchors.Top" target=_parent class=ISymbol>Top</a>, <span class=IParent>jsPlumb.Anchors</span></div></div><div class=SRResult id=SR_TopLeft><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Anchors.TopLeft" target=_parent class=ISymbol>TopLeft</a>, <span class=IParent>jsPlumb.Anchors</span></div></div><div class=SRResult id=SR_TopRight><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Anchors.TopRight" target=_parent class=ISymbol>TopRight</a>, <span class=IParent>jsPlumb.Anchors</span></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/GeneralU.html b/demo/apidocs/search/GeneralU.html
    deleted file mode 100644
    index 30db18ef4..000000000
    --- a/demo/apidocs/search/GeneralU.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_unbind_lpa_lbkevent_rbk_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.unbind([event])" target=_parent class=ISymbol>unbind([event])</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_unmakeEverySource><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.unmakeEverySource" target=_parent class=ISymbol>unmakeEverySource</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_unmakeEveryTarget><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.unmakeEveryTarget" target=_parent class=ISymbol>unmakeEveryTarget</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_unmakeSource_lpael_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.unmakeSource(el)" target=_parent class=ISymbol>unmakeSource(el)</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_unmakeTarget_lpael_rpa><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.unmakeTarget(el)" target=_parent class=ISymbol>unmakeTarget(el)</a>, <span class=IParent>jsPlumb</span></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/NoResults.html b/demo/apidocs/search/NoResults.html
    deleted file mode 100644
    index 8c7249660..000000000
    --- a/demo/apidocs/search/NoResults.html
    +++ /dev/null
    @@ -1,15 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=NoMatches>No Matches</div></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/PropertiesA.html b/demo/apidocs/search/PropertiesA.html
    deleted file mode 100644
    index d6b76a551..000000000
    --- a/demo/apidocs/search/PropertiesA.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_Assign><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Anchors.Assign" target=_parent class=ISymbol>Assign</a>, <span class=IParent>jsPlumb.Anchors</span></div></div><div class=SRResult id=SR_AutoDefault><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Anchors.AutoDefault" target=_parent class=ISymbol>AutoDefault</a>, <span class=IParent>jsPlumb.Anchors</span></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/PropertiesB.html b/demo/apidocs/search/PropertiesB.html
    deleted file mode 100644
    index fe7cf33c1..000000000
    --- a/demo/apidocs/search/PropertiesB.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_Bottom><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Anchors.Bottom" target=_parent class=ISymbol>Bottom</a>, <span class=IParent>jsPlumb.Anchors</span></div></div><div class=SRResult id=SR_BottomLeft><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Anchors.BottomLeft" target=_parent class=ISymbol>BottomLeft</a>, <span class=IParent>jsPlumb.Anchors</span></div></div><div class=SRResult id=SR_BottomRight><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Anchors.BottomRight" target=_parent class=ISymbol>BottomRight</a>, <span class=IParent>jsPlumb.Anchors</span></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/PropertiesC.html b/demo/apidocs/search/PropertiesC.html
    deleted file mode 100644
    index 89465f27b..000000000
    --- a/demo/apidocs/search/PropertiesC.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_canvas><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.canvas" target=_parent class=ISymbol>canvas</a>, <span class=IParent>Endpoint</span></div></div><div class=SRResult id=SR_Center><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Anchors.Center" target=_parent class=ISymbol>Center</a>, <span class=IParent>jsPlumb.Anchors</span></div></div><div class=SRResult id=SR_connections><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.connections" target=_parent class=ISymbol>connections</a>, <span class=IParent>Endpoint</span></div></div><div class=SRResult id=SR_connectorClass><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.connectorClass" target=_parent class=ISymbol>connectorClass</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_Continuous><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Anchors.Continuous" target=_parent class=ISymbol>Continuous</a>, <span class=IParent>jsPlumb.Anchors</span></div></div><div class=SRResult id=SR_ContinuousBottom><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Anchors.ContinuousBottom" target=_parent class=ISymbol>ContinuousBottom</a>, <span class=IParent>jsPlumb.Anchors</span></div></div><div class=SRResult id=SR_ContinuousLeft><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Anchors.ContinuousLeft" target=_parent class=ISymbol>ContinuousLeft</a>, <span class=IParent>jsPlumb.Anchors</span></div></div><div class=SRResult id=SR_ContinuousRight><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Anchors.ContinuousRight" target=_parent class=ISymbol>ContinuousRight</a>, <span class=IParent>jsPlumb.Anchors</span></div></div><div class=SRResult id=SR_ContinuousTop><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Anchors.ContinuousTop" target=_parent class=ISymbol>ContinuousTop</a>, <span class=IParent>jsPlumb.Anchors</span></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/PropertiesD.html b/demo/apidocs/search/PropertiesD.html
    deleted file mode 100644
    index 59ce3ced7..000000000
    --- a/demo/apidocs/search/PropertiesD.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_Defaults><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Defaults" target=_parent class=ISymbol>Defaults</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_draggingClass><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.draggingClass" target=_parent class=ISymbol>draggingClass</a>, <span class=IParent>jsPlumb</span></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/PropertiesE.html b/demo/apidocs/search/PropertiesE.html
    deleted file mode 100644
    index 8029e187e..000000000
    --- a/demo/apidocs/search/PropertiesE.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_elementDraggingClass><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.elementDraggingClass" target=_parent class=ISymbol>elementDraggingClass</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_endpointAnchorClassPrefix><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.endpointAnchorClassPrefix" target=_parent class=ISymbol>endpointAnchorClassPrefix</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_endpointClass><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.endpointClass" target=_parent class=ISymbol>endpointClass</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_endpointConnectedClass><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.endpointConnectedClass" target=_parent class=ISymbol>endpointConnectedClass</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_endpointDropAllowedClass><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.endpointDropAllowedClass" target=_parent class=ISymbol>endpointDropAllowedClass</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_endpointDropForbiddenClass><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.endpointDropForbiddenClass" target=_parent class=ISymbol>endpointDropForbiddenClass</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_endpointFullClass><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.endpointFullClass" target=_parent class=ISymbol>endpointFullClass</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_endpoints><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.endpoints" target=_parent class=ISymbol>endpoints</a>, <span class=IParent>Connection</span></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/PropertiesL.html b/demo/apidocs/search/PropertiesL.html
    deleted file mode 100644
    index 1f8ae8c40..000000000
    --- a/demo/apidocs/search/PropertiesL.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_Left><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Anchors.Left" target=_parent class=ISymbol>Left</a>, <span class=IParent>jsPlumb.Anchors</span></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/PropertiesO.html b/demo/apidocs/search/PropertiesO.html
    deleted file mode 100644
    index b8a0009d7..000000000
    --- a/demo/apidocs/search/PropertiesO.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_overlayClass><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.overlayClass" target=_parent class=ISymbol>overlayClass</a>, <span class=IParent>jsPlumb</span></div></div><div class=SRResult id=SR_overlays><div class=IEntry><a href="javascript:searchResults.Toggle('SR_overlays')" class=ISymbol>overlays</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.overlays" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.overlays" target=_parent class=IParent>Endpoint</a></div></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/PropertiesP.html b/demo/apidocs/search/PropertiesP.html
    deleted file mode 100644
    index e7892c99e..000000000
    --- a/demo/apidocs/search/PropertiesP.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_Perimeter><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Anchors.Perimeter" target=_parent class=ISymbol>Perimeter</a>, <span class=IParent>jsPlumb.Anchors</span></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/PropertiesR.html b/demo/apidocs/search/PropertiesR.html
    deleted file mode 100644
    index 9bf5fd6bb..000000000
    --- a/demo/apidocs/search/PropertiesR.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_Right><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Anchors.Right" target=_parent class=ISymbol>Right</a>, <span class=IParent>jsPlumb.Anchors</span></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/PropertiesS.html b/demo/apidocs/search/PropertiesS.html
    deleted file mode 100644
    index bdbb2c16f..000000000
    --- a/demo/apidocs/search/PropertiesS.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_scope><div class=IEntry><a href="javascript:searchResults.Toggle('SR_scope')" class=ISymbol>scope</a><div class=ISubIndex><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.scope" target=_parent class=IParent>Connection</a><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Endpoint.scope" target=_parent class=IParent>Endpoint</a></div></div></div><div class=SRResult id=SR_source><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.source" target=_parent class=ISymbol>source</a>, <span class=IParent>Connection</span></div></div><div class=SRResult id=SR_sourceId><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.sourceId" target=_parent class=ISymbol>sourceId</a>, <span class=IParent>Connection</span></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/search/PropertiesT.html b/demo/apidocs/search/PropertiesT.html
    deleted file mode 100644
    index 86d8b9a1c..000000000
    --- a/demo/apidocs/search/PropertiesT.html
    +++ /dev/null
    @@ -1,20 +0,0 @@
    -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    -
    -<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    -if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    -
    -<!--  Generated by Natural Docs, version 1.52 -->
    -<!--  http://www.naturaldocs.org  -->
    -
    -<!-- saved from url=(0026)http://www.naturaldocs.org -->
    -
    -
    -
    -
    -<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_target><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.target" target=_parent class=ISymbol>target</a>, <span class=IParent>Connection</span></div></div><div class=SRResult id=SR_targetId><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#Connection.targetId" target=_parent class=ISymbol>targetId</a>, <span class=IParent>Connection</span></div></div><div class=SRResult id=SR_Top><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Anchors.Top" target=_parent class=ISymbol>Top</a>, <span class=IParent>jsPlumb.Anchors</span></div></div><div class=SRResult id=SR_TopLeft><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Anchors.TopLeft" target=_parent class=ISymbol>TopLeft</a>, <span class=IParent>jsPlumb.Anchors</span></div></div><div class=SRResult id=SR_TopRight><div class=IEntry><a href="../files/jsPlumb-apidoc-1-4-0-RC1-js.html#jsPlumb.Anchors.TopRight" target=_parent class=ISymbol>TopRight</a>, <span class=IParent>jsPlumb.Anchors</span></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    -document.getElementById("Loading").style.display="none";
    -document.getElementById("NoMatches").style.display="none";
    -var searchResults = new SearchResults("searchResults", "HTML");
    -searchResults.Search();
    ---></script></div><script language=JavaScript><!--
    -if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/demo/apidocs/styles/main.css b/demo/apidocs/styles/main.css
    deleted file mode 100644
    index 511703fc4..000000000
    --- a/demo/apidocs/styles/main.css
    +++ /dev/null
    @@ -1,828 +0,0 @@
    -/*
    -   IMPORTANT: If you're editing this file in the output directory of one of
    -   your projects, your changes will be overwritten the next time you run
    -   Natural Docs.  Instead, copy this file to your project directory, make your
    -   changes, and you can use it with -s.  Even better would be to make a CSS
    -   file in your project directory with only your changes, which you can then
    -   use with -s [original style] [your changes].
    -
    -   On the other hand, if you're editing this file in the Natural Docs styles
    -   directory, the changes will automatically be applied to all your projects
    -   that use this style the next time Natural Docs is run on them.
    -
    -   This file is part of Natural Docs, which is Copyright © 2003-2010 Greg Valure.
    -   Natural Docs is licensed under version 3 of the GNU Affero General Public
    -   License (AGPL).  Refer to License.txt for the complete details.
    -
    -   This file may be distributed with documentation files generated by Natural Docs.
    -   Such documentation is not covered by Natural Docs' copyright and licensing,
    -   and may have its own copyright and distribution terms as decided by its author.
    -*/
    -
    -body {
    -    font: 10pt Verdana, Arial, sans-serif;
    -    color: #000000;
    -    margin: 0; padding: 0;
    -    }
    -
    -.ContentPage,
    -.IndexPage,
    -.FramedMenuPage {
    -    background-color: #E8E8E8;
    -    }
    -.FramedContentPage,
    -.FramedIndexPage,
    -.FramedSearchResultsPage,
    -.PopupSearchResultsPage {
    -    background-color: #FFFFFF;
    -    }
    -
    -
    -a:link,
    -a:visited { color: #900000; text-decoration: none }
    -a:hover { color: #900000; text-decoration: underline }
    -a:active { color: #FF0000; text-decoration: underline }
    -
    -td {
    -    vertical-align: top }
    -
    -img { border: 0;  }
    -
    -
    -/*
    -    Comment out this line to use web-style paragraphs (blank line between
    -    paragraphs, no indent) instead of print-style paragraphs (no blank line,
    -    indented.)
    -*/
    -p {
    -    text-indent: 5ex; margin: 0 }
    -
    -
    -/*  Opera doesn't break with just wbr, but will if you add this.  */
    -.Opera wbr:after {
    -	content: "\00200B";
    -	}
    -
    -
    -/*  Blockquotes are used as containers for things that may need to scroll.  */
    -blockquote {
    -    padding: 0;
    -    margin: 0;
    -    overflow: auto;
    -    }
    -
    -
    -.Firefox1 blockquote {
    -    padding-bottom: .5em;
    -    }
    -
    -/*  Turn off scrolling when printing.  */
    -@media print {
    -    blockquote {
    -        overflow: visible;
    -        }
    -    .IE blockquote {
    -        width: auto;
    -        }
    -    }
    -
    -
    -
    -#Menu {
    -    font-size: 9pt;
    -    padding: 10px 0 0 0;
    -    }
    -.ContentPage #Menu,
    -.IndexPage #Menu {
    -    position: absolute;
    -    top: 0;
    -    left: 0;
    -    width: 31ex;
    -    overflow: hidden;
    -    }
    -.ContentPage .Firefox #Menu,
    -.IndexPage .Firefox #Menu {
    -    width: 27ex;
    -    }
    -
    -
    -    .MTitle {
    -        font-size: 16pt; font-weight: bold; font-variant: small-caps;
    -        text-align: center;
    -        padding: 5px 10px 15px 10px;
    -        border-bottom: 1px dotted #000000;
    -        margin-bottom: 15px }
    -
    -    .MSubTitle {
    -        font-size: 9pt; font-weight: normal; font-variant: normal;
    -        margin-top: 1ex; margin-bottom: 5px }
    -
    -
    -    .MEntry a:link,
    -    .MEntry a:hover,
    -    .MEntry a:visited { color: #606060; margin-right: 0 }
    -    .MEntry a:active { color: #A00000; margin-right: 0 }
    -
    -
    -    .MGroup {
    -        font-variant: small-caps; font-weight: bold;
    -        margin: 1em 0 1em 10px;
    -        }
    -
    -    .MGroupContent {
    -        font-variant: normal; font-weight: normal }
    -
    -    .MGroup a:link,
    -    .MGroup a:hover,
    -    .MGroup a:visited { color: #545454; margin-right: 10px }
    -    .MGroup a:active { color: #A00000; margin-right: 10px }
    -
    -
    -    .MFile,
    -    .MText,
    -    .MLink,
    -    .MIndex {
    -        padding: 1px 17px 2px 10px;
    -        margin: .25em 0 .25em 0;
    -        }
    -
    -    .MText {
    -        font-size: 8pt; font-style: italic }
    -
    -    .MLink {
    -        font-style: italic }
    -
    -    #MSelected {
    -        color: #000000; background-color: #FFFFFF;
    -        /*  Replace padding with border.  */
    -        padding: 0 10px 0 10px;
    -        border-width: 1px 2px 2px 0; border-style: solid; border-color: #000000;
    -        margin-right: 5px;
    -        }
    -
    -    /*  Close off the left side when its in a group.  */
    -    .MGroup #MSelected {
    -        padding-left: 9px; border-left-width: 1px }
    -
    -    /*  A treat for Mozilla users.  Blatantly non-standard.  Will be replaced with CSS 3 attributes when finalized/supported.  */
    -    .Firefox #MSelected {
    -        -moz-border-radius-topright: 10px;
    -        -moz-border-radius-bottomright: 10px }
    -    .Firefox .MGroup #MSelected {
    -        -moz-border-radius-topleft: 10px;
    -        -moz-border-radius-bottomleft: 10px }
    -
    -
    -    #MSearchPanel {
    -        padding: 0px 6px;
    -        margin: .25em 0;
    -        }
    -
    -
    -    #MSearchField {
    -        font: italic 9pt Verdana, sans-serif;
    -        color: #606060;
    -        background-color: #E8E8E8;
    -        border: none;
    -        padding: 2px 4px;
    -        width: 100%;
    -        }
    -    /* Only Opera gets it right. */
    -    .Firefox #MSearchField,
    -    .IE #MSearchField,
    -    .Safari #MSearchField {
    -        width: 94%;
    -        }
    -    .Opera9 #MSearchField,
    -    .Konqueror #MSearchField {
    -        width: 97%;
    -        }
    -    .FramedMenuPage .Firefox #MSearchField,
    -    .FramedMenuPage .Safari #MSearchField,
    -    .FramedMenuPage .Konqueror #MSearchField {
    -        width: 98%;
    -        }
    -
    -    /* Firefox doesn't do this right in frames without #MSearchPanel added on.
    -        It's presence doesn't hurt anything other browsers. */
    -    #MSearchPanel.MSearchPanelInactive:hover #MSearchField {
    -        background-color: #FFFFFF;
    -        border: 1px solid #C0C0C0;
    -        padding: 1px 3px;
    -        }
    -    .MSearchPanelActive #MSearchField {
    -        background-color: #FFFFFF;
    -        border: 1px solid #C0C0C0;
    -        font-style: normal;
    -        padding: 1px 3px;
    -        }
    -
    -    #MSearchType {
    -        visibility: hidden;
    -        font: 8pt Verdana, sans-serif;
    -        width: 98%;
    -        padding: 0;
    -        border: 1px solid #C0C0C0;
    -        }
    -    .MSearchPanelActive #MSearchType,
    -    /*  As mentioned above, Firefox doesn't do this right in frames without #MSearchPanel added on. */
    -    #MSearchPanel.MSearchPanelInactive:hover #MSearchType,
    -    #MSearchType:focus {
    -        visibility: visible;
    -        color: #606060;
    -        }
    -    #MSearchType option#MSearchEverything {
    -        font-weight: bold;
    -        }
    -
    -    .Opera8 .MSearchPanelInactive:hover,
    -    .Opera8 .MSearchPanelActive {
    -        margin-left: -1px;
    -        }
    -
    -
    -    iframe#MSearchResults {
    -        width: 60ex;
    -        height: 15em;
    -        }
    -    #MSearchResultsWindow {
    -        display: none;
    -        position: absolute;
    -        left: 0; top: 0;
    -        border: 1px solid #000000;
    -        background-color: #E8E8E8;
    -        }
    -    #MSearchResultsWindowClose {
    -        font-weight: bold;
    -        font-size: 8pt;
    -        display: block;
    -        padding: 2px 5px;
    -        }
    -    #MSearchResultsWindowClose:link,
    -    #MSearchResultsWindowClose:visited {
    -        color: #000000;
    -        text-decoration: none;
    -        }
    -    #MSearchResultsWindowClose:active,
    -    #MSearchResultsWindowClose:hover {
    -        color: #800000;
    -        text-decoration: none;
    -        background-color: #F4F4F4;
    -        }
    -
    -
    -
    -
    -#Content {
    -    padding-bottom: 15px;
    -    }
    -
    -.ContentPage #Content {
    -    border-width: 0 0 1px 1px;
    -    border-style: solid;
    -    border-color: #000000;
    -    background-color: #FFFFFF;
    -    font-size: 9pt;  /* To make 31ex match the menu's 31ex. */
    -    margin-left: 31ex;
    -    }
    -.ContentPage .Firefox #Content {
    -    margin-left: 27ex;
    -    }
    -
    -
    -
    -    .CTopic {
    -        font-size: 10pt;
    -        margin-bottom: 3em;
    -        }
    -
    -
    -    .CTitle {
    -        font-size: 12pt; font-weight: bold;
    -        border-width: 0 0 1px 0; border-style: solid; border-color: #A0A0A0;
    -        margin: 0 15px .5em 15px }
    -
    -    .CGroup .CTitle {
    -        font-size: 16pt; font-variant: small-caps;
    -        padding-left: 15px; padding-right: 15px;
    -        border-width: 0 0 2px 0; border-color: #000000;
    -        margin-left: 0; margin-right: 0 }
    -
    -    .CClass .CTitle,
    -    .CInterface .CTitle,
    -    .CDatabase .CTitle,
    -    .CDatabaseTable .CTitle,
    -    .CSection .CTitle {
    -        font-size: 18pt;
    -        color: #FFFFFF; background-color: #A0A0A0;
    -        padding: 10px 15px 10px 15px;
    -        border-width: 2px 0; border-color: #000000;
    -        margin-left: 0; margin-right: 0 }
    -
    -    #MainTopic .CTitle {
    -        font-size: 20pt;
    -        color: #FFFFFF; background-color: #7070C0;
    -        padding: 10px 15px 10px 15px;
    -        border-width: 0 0 3px 0; border-color: #000000;
    -        margin-left: 0; margin-right: 0 }
    -
    -    .CBody {
    -        margin-left: 15px; margin-right: 15px }
    -
    -
    -    .CToolTip {
    -        position: absolute; visibility: hidden;
    -        left: 0; top: 0;
    -        background-color: #FFFFE0;
    -        padding: 5px;
    -        border-width: 1px 2px 2px 1px; border-style: solid; border-color: #000000;
    -        font-size: 8pt;
    -        }
    -
    -    .Opera .CToolTip {
    -        max-width: 98%;
    -        }
    -
    -    /*  Scrollbars would be useless.  */
    -    .CToolTip blockquote {
    -        overflow: hidden;
    -        }
    -    .IE6 .CToolTip blockquote {
    -        overflow: visible;
    -        }
    -
    -    .CHeading {
    -        font-weight: bold; font-size: 10pt;
    -        margin: 1.5em 0 .5em 0;
    -        }
    -
    -    .CBody pre {
    -        font: 10pt "Courier New", Courier, monospace;
    -	    background-color: #FCFCFC;
    -	    margin: 1em 35px;
    -	    padding: 10px 15px 10px 10px;
    -	    border-color: #E0E0E0 #E0E0E0 #E0E0E0 #E4E4E4;
    -	    border-width: 1px 1px 1px 6px;
    -	    border-style: dashed dashed dashed solid;
    -        }
    -
    -    .CBody ul {
    -        /*  I don't know why CBody's margin doesn't apply, but it's consistent across browsers so whatever.
    -             Reapply it here as padding.  */
    -        padding-left: 15px; padding-right: 15px;
    -        margin: .5em 5ex .5em 5ex;
    -        }
    -
    -    .CDescriptionList {
    -        margin: .5em 5ex 0 5ex }
    -
    -        .CDLEntry {
    -            font: 10pt "Courier New", Courier, monospace; color: #808080;
    -            padding-bottom: .25em;
    -            white-space: nowrap }
    -
    -        .CDLDescription {
    -            font-size: 10pt;  /*  For browsers that don't inherit correctly, like Opera 5.  */
    -            padding-bottom: .5em; padding-left: 5ex }
    -
    -
    -    .CTopic img {
    -        text-align: center;
    -        display: block;
    -        margin: 1em auto;
    -        }
    -    .CImageCaption {
    -        font-variant: small-caps;
    -        font-size: 8pt;
    -        color: #808080;
    -        text-align: center;
    -        position: relative;
    -        top: 1em;
    -        }
    -
    -    .CImageLink {
    -        color: #808080;
    -        font-style: italic;
    -        }
    -    a.CImageLink:link,
    -    a.CImageLink:visited,
    -    a.CImageLink:hover { color: #808080 }
    -
    -
    -
    -
    -
    -.Prototype {
    -    font: 10pt "Courier New", Courier, monospace;
    -    padding: 5px 3ex;
    -    border-width: 1px; border-style: solid;
    -    margin: 0 5ex 1.5em 5ex;
    -    }
    -
    -    .Prototype td {
    -        font-size: 10pt;
    -        }
    -
    -    .PDefaultValue,
    -    .PDefaultValuePrefix,
    -    .PTypePrefix {
    -        color: #8F8F8F;
    -        }
    -    .PTypePrefix {
    -        text-align: right;
    -        }
    -    .PAfterParameters {
    -        vertical-align: bottom;
    -        }
    -
    -    .IE .Prototype table {
    -        padding: 0;
    -        }
    -
    -    .CFunction .Prototype {
    -        background-color: #F4F4F4; border-color: #D0D0D0 }
    -    .CProperty .Prototype {
    -        background-color: #F4F4FF; border-color: #C0C0E8 }
    -    .CVariable .Prototype {
    -        background-color: #FFFFF0; border-color: #E0E0A0 }
    -
    -    .CClass .Prototype {
    -        border-width: 1px 2px 2px 1px; border-style: solid; border-color: #A0A0A0;
    -        background-color: #F4F4F4;
    -        }
    -    .CInterface .Prototype {
    -        border-width: 1px 2px 2px 1px; border-style: solid; border-color: #A0A0D0;
    -        background-color: #F4F4FF;
    -        }
    -
    -    .CDatabaseIndex .Prototype,
    -    .CConstant .Prototype {
    -        background-color: #D0D0D0; border-color: #000000 }
    -    .CType .Prototype,
    -    .CEnumeration .Prototype {
    -        background-color: #FAF0F0; border-color: #E0B0B0;
    -        }
    -    .CDatabaseTrigger .Prototype,
    -    .CEvent .Prototype,
    -    .CDelegate .Prototype {
    -        background-color: #F0FCF0; border-color: #B8E4B8 }
    -
    -    .CToolTip .Prototype {
    -        margin: 0 0 .5em 0;
    -        white-space: nowrap;
    -        }
    -
    -
    -
    -
    -
    -.Summary {
    -    margin: 1.5em 5ex 0 5ex }
    -
    -    .STitle {
    -        font-size: 12pt; font-weight: bold;
    -        margin-bottom: .5em }
    -
    -
    -    .SBorder {
    -        background-color: #FFFFF0;
    -        padding: 15px;
    -        border: 1px solid #C0C060 }
    -
    -    /* In a frame IE 6 will make them too long unless you set the width to 100%.  Without frames it will be correct without a width
    -        or slightly too long (but not enough to scroll) with a width.  This arbitrary weirdness simply astounds me.  IE 7 has the same
    -        problem with frames, haven't tested it without.  */
    -    .FramedContentPage .IE .SBorder {
    -        width: 100% }
    -
    -    /*  A treat for Mozilla users.  Blatantly non-standard.  Will be replaced with CSS 3 attributes when finalized/supported.  */
    -    .Firefox .SBorder {
    -        -moz-border-radius: 20px }
    -
    -
    -    .STable {
    -        font-size: 9pt; width: 100% }
    -
    -    .SEntry {
    -        width: 30% }
    -    .SDescription {
    -        width: 70% }
    -
    -
    -    .SMarked {
    -        background-color: #F8F8D8 }
    -
    -    .SDescription { padding-left: 2ex }
    -    .SIndent1 .SEntry { padding-left: 1.5ex }   .SIndent1 .SDescription { padding-left: 3.5ex }
    -    .SIndent2 .SEntry { padding-left: 3.0ex }   .SIndent2 .SDescription { padding-left: 5.0ex }
    -    .SIndent3 .SEntry { padding-left: 4.5ex }   .SIndent3 .SDescription { padding-left: 6.5ex }
    -    .SIndent4 .SEntry { padding-left: 6.0ex }   .SIndent4 .SDescription { padding-left: 8.0ex }
    -    .SIndent5 .SEntry { padding-left: 7.5ex }   .SIndent5 .SDescription { padding-left: 9.5ex }
    -
    -    .SDescription a { color: #800000}
    -    .SDescription a:active { color: #A00000 }
    -
    -    .SGroup td {
    -        padding-top: .5em; padding-bottom: .25em }
    -
    -    .SGroup .SEntry {
    -        font-weight: bold; font-variant: small-caps }
    -
    -    .SGroup .SEntry a { color: #800000 }
    -    .SGroup .SEntry a:active { color: #F00000 }
    -
    -
    -    .SMain td,
    -    .SClass td,
    -    .SDatabase td,
    -    .SDatabaseTable td,
    -    .SSection td {
    -        font-size: 10pt;
    -        padding-bottom: .25em }
    -
    -    .SClass td,
    -    .SDatabase td,
    -    .SDatabaseTable td,
    -    .SSection td {
    -        padding-top: 1em }
    -
    -    .SMain .SEntry,
    -    .SClass .SEntry,
    -    .SDatabase .SEntry,
    -    .SDatabaseTable .SEntry,
    -    .SSection .SEntry {
    -        font-weight: bold;
    -        }
    -
    -    .SMain .SEntry a,
    -    .SClass .SEntry a,
    -    .SDatabase .SEntry a,
    -    .SDatabaseTable .SEntry a,
    -    .SSection .SEntry a { color: #000000 }
    -
    -    .SMain .SEntry a:active,
    -    .SClass .SEntry a:active,
    -    .SDatabase .SEntry a:active,
    -    .SDatabaseTable .SEntry a:active,
    -    .SSection .SEntry a:active { color: #A00000 }
    -
    -
    -
    -
    -
    -.ClassHierarchy {
    -    margin: 0 15px 1em 15px }
    -
    -    .CHEntry {
    -        border-width: 1px 2px 2px 1px; border-style: solid; border-color: #A0A0A0;
    -        margin-bottom: 3px;
    -        padding: 2px 2ex;
    -        font-size: 10pt;
    -        background-color: #F4F4F4; color: #606060;
    -        }
    -
    -    .Firefox .CHEntry {
    -        -moz-border-radius: 4px;
    -        }
    -
    -    .CHCurrent .CHEntry {
    -        font-weight: bold;
    -        border-color: #000000;
    -        color: #000000;
    -        }
    -
    -    .CHChildNote .CHEntry {
    -        font-style: italic;
    -        font-size: 8pt;
    -        }
    -
    -    .CHIndent {
    -        margin-left: 3ex;
    -        }
    -
    -    .CHEntry a:link,
    -    .CHEntry a:visited,
    -    .CHEntry a:hover {
    -        color: #606060;
    -        }
    -    .CHEntry a:active {
    -        color: #800000;
    -        }
    -
    -
    -
    -
    -
    -#Index {
    -    background-color: #FFFFFF;
    -    }
    -
    -/*  As opposed to .PopupSearchResultsPage #Index  */
    -.IndexPage #Index,
    -.FramedIndexPage #Index,
    -.FramedSearchResultsPage #Index {
    -    padding: 15px;
    -    }
    -
    -.IndexPage #Index {
    -    border-width: 0 0 1px 1px;
    -    border-style: solid;
    -    border-color: #000000;
    -    font-size: 9pt;  /* To make 27ex match the menu's 27ex. */
    -    margin-left: 27ex;
    -    }
    -
    -
    -    .IPageTitle {
    -        font-size: 20pt; font-weight: bold;
    -        color: #FFFFFF; background-color: #7070C0;
    -        padding: 10px 15px 10px 15px;
    -        border-width: 0 0 3px 0; border-color: #000000; border-style: solid;
    -        margin: -15px -15px 0 -15px }
    -
    -    .FramedSearchResultsPage .IPageTitle {
    -        margin-bottom: 15px;
    -        }
    -
    -    .INavigationBar {
    -        font-size: 10pt;
    -        text-align: center;
    -        background-color: #FFFFF0;
    -        padding: 5px;
    -        border-bottom: solid 1px black;
    -        margin: 0 -15px 15px -15px;
    -        }
    -
    -    .INavigationBar a {
    -        font-weight: bold }
    -
    -    .IHeading {
    -        font-size: 16pt; font-weight: bold;
    -        padding: 2.5em 0 .5em 0;
    -        text-align: center;
    -        width: 3.5ex;
    -        }
    -    #IFirstHeading {
    -        padding-top: 0;
    -        }
    -
    -    .IEntry {
    -        font-size: 10pt;
    -        padding-left: 1ex;
    -        }
    -    .PopupSearchResultsPage .IEntry {
    -        font-size: 8pt;
    -        padding: 1px 5px;
    -        }
    -    .PopupSearchResultsPage .Opera9 .IEntry,
    -    .FramedSearchResultsPage .Opera9 .IEntry {
    -        text-align: left;
    -        }
    -    .FramedSearchResultsPage .IEntry {
    -        padding: 0;
    -        }
    -
    -    .ISubIndex {
    -        padding-left: 3ex; padding-bottom: .5em }
    -    .PopupSearchResultsPage .ISubIndex {
    -        display: none;
    -        }
    -
    -    /*  While it may cause some entries to look like links when they aren't, I found it's much easier to read the
    -         index if everything's the same color.  */
    -    .ISymbol {
    -        font-weight: bold; color: #900000  }
    -
    -    .IndexPage .ISymbolPrefix,
    -    .FramedIndexPage .ISymbolPrefix {
    -        font-size: 10pt;
    -        text-align: right;
    -        color: #C47C7C;
    -        background-color: #F8F8F8;
    -        border-right: 3px solid #E0E0E0;
    -        border-left: 1px solid #E0E0E0;
    -        padding: 0 1px 0 2px;
    -        }
    -    .PopupSearchResultsPage .ISymbolPrefix,
    -    .FramedSearchResultsPage .ISymbolPrefix {
    -        color: #900000;
    -        }
    -    .PopupSearchResultsPage .ISymbolPrefix {
    -        font-size: 8pt;
    -        }
    -
    -    .IndexPage #IFirstSymbolPrefix,
    -    .FramedIndexPage #IFirstSymbolPrefix {
    -        border-top: 1px solid #E0E0E0;
    -        }
    -    .IndexPage #ILastSymbolPrefix,
    -    .FramedIndexPage #ILastSymbolPrefix {
    -        border-bottom: 1px solid #E0E0E0;
    -        }
    -    .IndexPage #IOnlySymbolPrefix,
    -    .FramedIndexPage #IOnlySymbolPrefix {
    -        border-top: 1px solid #E0E0E0;
    -        border-bottom: 1px solid #E0E0E0;
    -        }
    -
    -    a.IParent,
    -    a.IFile {
    -        display: block;
    -        }
    -
    -    .PopupSearchResultsPage .SRStatus {
    -        padding: 2px 5px;
    -        font-size: 8pt;
    -        font-style: italic;
    -        }
    -    .FramedSearchResultsPage .SRStatus {
    -        font-size: 10pt;
    -        font-style: italic;
    -        }
    -
    -    .SRResult {
    -        display: none;
    -        }
    -
    -
    -
    -#Footer {
    -    font-size: 8pt;
    -    color: #989898;
    -    text-align: right;
    -    }
    -
    -#Footer p {
    -    text-indent: 0;
    -    margin-bottom: .5em;
    -    }
    -
    -.ContentPage #Footer,
    -.IndexPage #Footer {
    -    text-align: right;
    -    margin: 2px;
    -    }
    -
    -.FramedMenuPage #Footer {
    -    text-align: center;
    -    margin: 5em 10px 10px 10px;
    -    padding-top: 1em;
    -    border-top: 1px solid #C8C8C8;
    -    }
    -
    -    #Footer a:link,
    -    #Footer a:hover,
    -    #Footer a:visited { color: #989898 }
    -    #Footer a:active { color: #A00000 }
    -
    -
    -
    -.prettyprint .kwd { color: #800000; }  /* keywords */
    -
    -    .prettyprint.PDefaultValue .kwd,
    -    .prettyprint.PDefaultValuePrefix .kwd,
    -    .prettyprint.PTypePrefix .kwd {
    -        color: #C88F8F;
    -        }
    -
    -.prettyprint .com { color: #008000; }  /* comments */
    -
    -    .prettyprint.PDefaultValue .com,
    -    .prettyprint.PDefaultValuePrefix .com,
    -    .prettyprint.PTypePrefix .com {
    -        color: #8FC88F;
    -        }
    -
    -.prettyprint .str { color: #0000B0; }  /* strings */
    -.prettyprint .lit { color: #0000B0; }  /* literals */
    -
    -    .prettyprint.PDefaultValue .str,
    -    .prettyprint.PDefaultValuePrefix .str,
    -    .prettyprint.PTypePrefix .str,
    -    .prettyprint.PDefaultValue .lit,
    -    .prettyprint.PDefaultValuePrefix .lit,
    -    .prettyprint.PTypePrefix .lit {
    -        color: #8F8FC0;
    -        }
    -
    -.prettyprint .typ { color: #000000; }  /* types */
    -.prettyprint .pun { color: #000000; }  /* punctuation */
    -.prettyprint .pln { color: #000000; }  /* punctuation */
    -
    -    .prettyprint.PDefaultValue .typ,
    -    .prettyprint.PDefaultValuePrefix .typ,
    -    .prettyprint.PTypePrefix .typ,
    -    .prettyprint.PDefaultValue .pun,
    -    .prettyprint.PDefaultValuePrefix .pun,
    -    .prettyprint.PTypePrefix .pun,
    -    .prettyprint.PDefaultValue .pln,
    -    .prettyprint.PDefaultValuePrefix .pln,
    -    .prettyprint.PTypePrefix .pln {
    -        color: #8F8F8F;
    -        }
    -
    -.prettyprint .tag { color: #008; }
    -.prettyprint .atn { color: #606; }
    -.prettyprint .atv { color: #080; }
    -.prettyprint .dec { color: #606; }
    -
    diff --git a/demo/css/multipleJsPlumbDemo.css b/demo/css/multipleJsPlumbDemo.css
    deleted file mode 100644
    index 21dccd290..000000000
    --- a/demo/css/multipleJsPlumbDemo.css
    +++ /dev/null
    @@ -1,27 +0,0 @@
    -body { font-family: Gill, Verdana, sans; }
    -.blip { z-index:4;cursor:pointer; }
    -.blip1 { width:1em; height:1em; border:1px solid blue; background-color:yellow; }
    -.blip2 { width:0.5em; height:0.5em; border:1px dotted red; background-color:gray; }
    -.blip3 { width:1.5em; height:0.7em; background-color:#0e66a4; border:0.01em dotted #ddd; }
    -#info { height:4em;font-size:90%;}
    -button { margin-top:25em;z-index:20;position:absolute;}
    -.redo { left:11em;}
    -.clear { left:4em; }
    -_jsPlumb_connector { z-index:1; }
    -.container {
    -border:2px dashed #567;
    -height:25em;
    -width:20em;
    -float:left;
    -margin-left:3em;
    -position:relative;
    -margin-top:3em;
    -overflow:scroll;
    -cursor:move;
    -background-color:#eee0fe;
    -opacity:0.9;
    -}
    -.container:hover { border:2px dashed #500; background-color:#fff0fe; }
    -.atestdiv {
    -width:4em;height:4em; background-color:yellow;margin:2em;
    -}
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.2.6/content.html b/demo/doc/archive/1.2.6/content.html
    deleted file mode 100644
    index f6e8a9fe3..000000000
    --- a/demo/doc/archive/1.2.6/content.html
    +++ /dev/null
    @@ -1,2254 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.2.6 - documentation</title>
    -		<link rel="stylesheet" href="/mp.css"></link>
    -		<link rel="stylesheet" href="../css/jsPlumbDemo.css"></link>
    -	</head>
    -	<body>
    -
    -<div class="menu"><a href="http://jsplumb.org" class="mplink" target="_top">view demo</a>&nbsp;|&nbsp;<a href="mailto:simon.porritt@gmail.com" class="mplink">contact me</a></div>
    -
    -<div class="content">
    -		<div class="section">
    -			<a id="summary"><h3>Summary</h3></a>
    -			jsPlumb allows you to connect elements on the screen with "plumbing", using a Canvas
    -			element when supported, and Google's <a class="mplink" href="http://excanvas.sourceforge.net/" target="_blank">ExplorerCanvas</a> script to support older browsers.
    -			<p>
    -			It can be used with jQuery, MooTools or YUI3 (or another library of your choice if you
    -			feel like implementing a plugin for it).  Required versions are as follows:
    -			<h4>jQuery</h4>
    -				<ul>
    -					<li>jQuery 1.3.x or higher.</li>
    -					<li>jQuery UI 1.7.x or 1.8.x (if you wish to support drag and drop). jsPlumb has been tested on 1.7.2, 1.8.0 and 1.8.13.</li>
    -				</ul>
    -			<h4>MooTools</h4>
    -				<ul>
    -					<li>MooTools Core 1.2.4 or higher. jsPlumb has been tested with 1.2.4 and 1.3.2.</li>
    -					<li>Drag.Move from MooTools More 1.2.4.4 or higher (if you wish to support drag and drop). jsPlumb has been tested with versions 1.2.4.4 and 1.3.2.1.</li>
    -				</ul>
    -			<h4>YUI3</h4>
    -				<ul>
    -					<li>YUI 3.3.x.  jsPlumb has been tested on 3.3.0 only; it is possible other 3.x.x versions will work.</li>
    -				</ul>
    -			</p>
    -			<p>
    -			For Canvas support in IE you also need to include Google's <a class="mplink" href="http://excanvas.sourceforge.net/" target="_blank">ExplorerCanvas</a> script.
    -			</p>
    -			<p>jsPlumb 1.2.6 has been tested on the following browsers:
    -				<ul>
    -					<li>IE 7 on Windows Vista</li>
    -					<li>IE 8 on Windows Vista (we force IE7 standards compatibility mode)</li>
    -					<li>Firefox 3.5.8 on Windows Vista</li>
    -					<li>Firefox 3.6.3 on Ubuntu 10.04</li>
    -					<li>Chrome on Ubuntu 10.04</li>
    -					<li>Safari 4 on Mac Tiger</li>
    -					<li>Safari 4 on Windows Vista</li>
    -					<li>Opera 10.54 on Windows Vista</li>
    -				</ul>
    -			</p>
    -		</div>
    -		
    -		<div class="section">
    -			<a id="changes">Changes since version 1.2.5</a>			
    -			<ul>
    -				<li>introduced support for mouse events (click/doubleclick/enter/exit) when using Canvas </li>
    -				<li>introduced shorthand syntax for specifying Endpointds and Overlays</li>
    -				<li>added Flowchart connector type</li>
    -			</ul>
    -			<p>
    -			These issues have been resolved:
    -			</p>    
    -			<ul>								
    -				<li>issue 54: crash on vertical straight connectors</li>
    -				<li>issue 57: wrong scope chosen for detached draggable connection</li>
    -				<li>issue 58: changing one connector's style changes others.</li>
    -				<li>issue 63: add access to the Connector in event API</li>
    -				<li>issue 65: expose Connection so it can be extended</li>
    -				<li>issue 70: right click on Connections</li>
    -				<li>issue 72: toggle visibility of endpoints</li>
    -				<li>issue 74: mootools drag error</li>
    -				<li>issue 76: arrow overlays flip and disappear with vertical straight connectors</li>
    -				<li>issue 80: support for setting an arrow overlay's direction</li>
    -				<li>issue 81: detach event fired twice</li>
    -			</ul>
    -		</div>
    -
    -		<div class="section">
    -			<a id="requirements"><h3>Imports</h3></a>
    -			<h4>jQuery</h4>
    -			<div class="code">
    -			<pre>&lt;script type="text/javascript" src="http://explorercanvas.googlecode.com/svn/trunk/excanvas.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.0/jquery.min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/jquery-ui.min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/jquery.jsPlumb-1.2.6-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			
    -			<h4>MooTools</h4>
    -			<div class="code">
    -			<pre>&lt;script type="text/javascript" src="http://explorercanvas.googlecode.com/svn/trunk/excanvas.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/mootools/1.3.2/mootools-yui-compressed.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO_MOO_TOOLS_MORE_1_3_2_1"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/mootools.jsPlumb-1.2.6-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			
    -			<h4>YUI3</h4>
    -			<div class="code">
    -			<pre>&lt;script type="text/javascript" src="http://explorercanvas.googlecode.com/svn/trunk/excanvas.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="http://yui.yahooapis.com/3.3.0/build/simpleyui/simpleyui-min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/yui.jsPlumb-1.2.6-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>			
    -		</div>
    -		
    -		<div class="section">
    -			<a id="init"><h3>Initializing jsPlumb</h3></a>
    -			You should not start making calls to jsPlumb until the DOM has been initialized - perhaps no surprises there.  With YUI, though, the asynchronous
    -			nature of the script loading process means that you are not guaranteed that jsPlumb is ready as soon as the DOM is.  To handle this,
    -			you should bind to the "ready" event on jsPlumb (or the instance of jsPlumb you are working with):
    -			<div class="code">
    -			<pre>jsPlumb.bind("ready", function() {
    -...	
    -		
    -// your jsPlumb related init code goes here
    -
    -...
    -
    -});						
    -			</pre>
    -			</div>
    -			<p>
    -			For jQuery and MooTools you should not actually need to do this; by the time the DOM ready event is fired in each of those libraries you can be sure 
    -			all the JS we need has been loaded.  But in terms of explicitly calling out that you are waiting for jsPlumb, it seems like a good practice to bind to the "ready" event.
    -			</p>
    -			<p>
    -			If you bind to the "ready" event after jsPlumb has already been initialized, your callback will be executed immediately.
    -			</p>
    -			
    -		</div>
    -		
    -		<div class="section">
    -			<a id="unload"><h3>Unloading jsPlumb</h3></a>
    -			jsPlumb offers a method you can call when your page is unloading.  You should do this to insure
    -			against memory leaks.  You configure it like this:
    -				<div class="code">
    -<pre>
    -&lt;body onunload="jsPlumb.unload();"&gt;
    -
    -...
    -
    -&lt;/body&gt;
    -</pre>
    -				</div>
    -		</div>
    -		
    -		<div class="section">
    -			<a id="jsPlumbBasics"><h3>jsPlumb Basic Concepts</h3></a>
    -
    -			jsPlumb provides two ways of establishing connections between elements: programmatically, via Javascript, and interactively, with drag and drop. To support this, jsPlumb has these five core concepts:
    -
    -			<ul>
    -				<li>Endpoint - the visual representation of one end of a Connection.  You can create and attach these to elements yourself, which you are required to do to support drag and drop, or have jsPlumb create them when creating a Connection programmatically using jsPlumb.connect(...).  You can also join two Endpoints programmatically, by passing them as arguments to jsPlumb.connect(...). This class, and Connection, discussed below, are the main objects you will interact with in jsPlumb.<br/><br/></li>
    -				<li>Anchor - a location, relative to an element's origin, at which an Endpoint can exist. You do not create these yourself; you supply hints to the various jsPlumb functions, which create them as needed.  They have no visual representation; they are a logical position only. Anchors can be referenced by name, for the Anchors that jsPlumb ships with, or a four element array describing [ x, y, x orientation, y orientation ]. See the <a href="#anchors">anchors</a> section for more detail.<br/><br/></li>
    -				<li>Connector - the visual representation of the line connecting two elements in the page.  jsPlumb has two types of these available as defaults - a Bezier curve, and a straight line. You do not interact with the Connector object.<br/><br/></li>
    -				<li>Overlay - a UI component that is used to decorate a Connector, such as a label, arrow, etc.<br/><br/></li>
    -				<li>Connection - an instance of Endpoints and a Connector with zero or more Overlays working together to join two elements. This, and Endpoint, discussed above, are the main objects you will interact with in jsPlumb.<br/><br/></li>
    -			</ul>
    -
    -			<a id="simpleConnections"><h4>Programmatic API</h4></a>
    -			This section discusses Connections made programmatically.  <br/><br/>
    -The most simple connection you can make looks like this:
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({source:'element1', target:'element2'});
    -</pre>			
    -			</div>
    -
    -		In this example we have created a Connection from 'element1' to 'element2'.  Everything has a default value in jsPlumb and does not necessarily need to be supplied, so in this example, behind the scenes jsPlumb used default values for all of these things:
    -		<ul>
    -			<li>the Anchors that define where the connection's Endpoints appear on each element</li>
    -			<li>the type and appearance of each Endpoint in the Connection</li>
    -			<li>whether or not each Endpoint can be a source or target for new Connections</li>
    -			<li>the type and appearance of the Connection's Connector</li>
    -		</ul>
    -		Default values are discussed in detail in the <a href="#defaults">Defaults</a> section below.<br/><br/>
    -		The <a href="#examples">Basic Examples</a> section below contains many more examples of how to create simple Connections.
    -		
    -		<a id="draggableConnections"><h4>Draggable Connections</h4></a>
    -		To support draggable Connections, you are required to first create Endpoints, as it is Endpoints
    -		that the user drags to create new Connections.  Endpoints are created by making an <strong>addEndpoint</strong> call,
    -		passing in the target element's id and an options object. There are quite a few things that can be set on the options object; all
    -		of these parameters are optional.
    -		<ul>
    -			<li>endpoint - the type of Endpoint, eg. Dot, Rectangle, Image.</li>
    -			<li>anchor - the Endpoint's Anchor, ie. where it will be located. this may be a string - "TopCenter", for example, or an array of coordinates - [ 0.5, 0, 0, -1 ], or
    -			an array of individual anchor specifications, in which case jsPlumb will make a 'dynamic' anchor for the Endpoint.</li>
    -			<li>paintStyle - the Endpoint's appearance</li>			
    -			<li>isSource - a boolean indicating whether or not the Endpoint can be a source for new Connections. Default is false.</li>
    -			<li>isTarget - a boolean indicating whether or not the Endpoint can be a target for new Connections. Default is false.</li>
    -			<li>connector - Optional. The type of Connector to use when dragging a new connection from this Endpoint. see <a href="#connectors">Connectors</a></li>
    -			<li>connectorStyle - the Connector's appearance (see above).</li>
    -			<li>dragOptions - options to pass to the supporting library's drag engine.</li>
    -			<li>dropOptions - options to pass to the supporting library's drop engine.</li>
    -			<li>connectorOverlays - a list of overlays that should be present on all Connections attached to this Endpoint.</li>			
    -		</ul>  
    -		
    -		Here's a simple example of how to create an Endpoint:
    -		<div class="code">
    -<pre>
    -var endpointOptions = { isSource:true, isTarget:true };
    -var endpoint = jsPlumb.addEndpoint('elementId', endpointOptions);
    -</pre>		
    -</div>  	
    -This Endpoint will act as a source and target for new Connections, and will use the jsPlumb defaults for its own appearance and that of any Connections that are drawn from it.							
    -		<h4>Tip: use the three-argument addEndpoint method for common data </h4>
    -		One thing that happens quite often is that you have an Endpoint whose appearance and behaviour is largely the
    -		same between usages on different elements, with just a few differences. 
    -		<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -	endpoint:"Rectangle",
    -	paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -	isSource:true,
    -	connectorStyle : { strokeStyle:"#666" },
    -	isTarget:true
    -};
    -</pre>		
    -</div>  		
    -		Notice there is no 'anchor' set.  Here we apply it to two elements, at a different location in each:
    -		<div class="code">
    -<pre>
    -jsPlumb.addEndpoint('element1', { anchor:"BottomCenter" }, exampleGreyEndpointOptions)); 
    -
    -jsPlumb.addEndpoint('element2', { anchor:"TopCenter" }, exampleGreyEndpointOptions));
    -</pre>		
    -</div>
    -<p>
    -Here's an example of specifying that you want an Arrow overlay halfway along any Connection dragged from this Endpoint:
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -	endpoint:"Rectangle",
    -	paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -	isSource:true,
    -	connectorStyle : { strokeStyle:"#666" },
    -	isTarget:true,
    -	connectorOverlays: [ [ "Arrow", { location:0.5 } ] ]
    -};
    -</pre>
    -</div>
    -This is an Endpoint that moves around the element it is attached to dependent on the location of other elements in the connections it is attached to
    -(a 'dynamic' anchor):
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -	endpoint:"Rectangle",
    -	paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -	isSource:true,
    -	connectorStyle : { strokeStyle:"#666" },
    -	isTarget:true,
    -	connectorOverlays: [ [ "Arrow", { location:0.5 } ] ]
    -	anchor:[ "TopCenter","RightMiddle","BottomCenter","LeftMiddle" ]
    -};
    -</pre>
    -</div>
    -</p>
    -
    -
    -<a id="dragOptions"><h4>Drag Options</h4></a>
    -	These are options that will be passed through to the supporting library's drag API.  jsPlumb passes everything you supply here through, inserting
    -	wrapping functions if necessary for the various lifecycle events that jsPlumb needs to know about.  So if, for example, you pass a function to be
    -	called when dragging starts, jsPlumb will wrap that function with a function that does what jsPlumb needs to do, then call yours.
    -	<p>
    -	At the time of writing, jsPlumb supports jQuery, MooTools and YUI3, and each of those libraries uses different terminology.  In addition, jQuery's API is
    -	more fully featured, providing easy support for setting the zIndex and opacity of elements being dragged, as well as the 'scope' for a drag/drop (allowing
    -	you to specify more than one type of drag/drop pair), and hover classes for when a draggable is on the move or
    -	over a droppable. If you're using jQuery you can of course just supply these values on the dragOptions; to make it easier, jsPlumb's MooTools and YUI3 adapters recognize 
    -	these options and add appropriate callbacks for you.  
    -	</p> 
    -	<p>
    -	Given that the options here are library-specific, and they are all well-documented, we're going to discuss just the three drag options
    -	that behave the same way in both (see below for hoverClass):
    -		<ul>
    -		<li>opacity - the opacity of an element that is being dragged.  Should be a fraction between 0 and 1 inclusive.</li>
    -		<li>zIndex - the zIndex of an element that is being dragged.</li>
    -		<li>scope - the scope of the draggable.  can only be dropped on a droppable with the same scope.  this is discussed below.</li>
    -		</ul> 
    -	</p>
    -	
    -	<p>For more information about drag options, take a look at the <a href="http://docs.jquery.com/UI/Draggable" target="_blank">jQuery</a>, <a href="http://mootools.net/docs/more/Drag/Drag.Move" target="_blank">MooTools</a>, or <a href="http://developer.yahoo.com/yui/3/dd/" target="_blank">YUI3</a> docs.	
    -	</p>
    -	<p><strong>NOTE: there is an issue in Chrome that affects the 'cursor' argument to drag options in jQuery. See these links:</strong>
    -	<p>
    -<a href="http://forum.jquery.com/topic/draggable-cursor-option-does-not-work" target="_blank">http://forum.jquery.com/topic/draggable-cursor-option-does-not-work</a><br/>
    -<a href="http://forum.jquery.com/topic/chrome-text-select-cursor-on-drag" target="_blank">http://forum.jquery.com/topic/chrome-text-select-cursor-on-drag</a>
    -</p>
    -You should put something like this at the top of your JS to avoid it (perhaps not something so drastic as a document-wide override):	
    -<div class="code">
    -<pre>document.onselectstart = function () { return false; };</pre>
    -</div>
    -				
    -	</p>
    -	
    -	<a id="dropOptions"><h4>Drop Options</h4></a>
    -	Drop options are treated by jsPlumb in the same way as drag options - they are passed through to the underlying library.  MooTools does not have drop options like jQuery and YUI3 do; droppable functionality in MooTools 
    -	is actually implemented by the Drag.Move class - the one used to initialise a draggable.  But when you setup an Endpoint in jsPlumb you should ignore
    -	that fact, and treat droppables like you would in jQuery or YUI3.  jsPlumb wires everything up for you under the hood.
    -	<p>There are two jQuery droppable options that jsPlumb treats as shortcuts in MooTools and YUI3, for the sake of consistency:
    -		<ul>
    -			<li>hoverClass - the CSS class to attach to a droppable when a draggable is hovering over it.</li>
    -			<li>scope - the scope of the draggable.  The draggable can only be dropped on a droppable with the same scope.  This is discussed below.</li>
    -		</ul>
    -	</p>   
    -	<p>For more information about drop options when using jQuery, see <a href="http://docs.jquery.com/UI/Droppable" target="_blank">here</a>.</p>
    -		
    -	<a id="dragAndDropScope"><h4>Scope</h4></a>
    -		jsPlumb borrowed the concept of 'scope' from jQuery's drag/drop implementation: the notion of
    -		which draggables can be dropped on which droppables.  In jsPlumb you can provide a 'scope' entry when 
    -		creating an Endpoint.  Here's the example grey Endpoint example with 'scope' added:
    -		
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -	endpoint:"Rectangle",
    -	paintStyle:{ width:25, height:21, fillStyle:"#666" },
    -	isSource:true,
    -	connectionStyle : { strokeStyle:"#666" },
    -	isTarget:true,
    -	scope:"exampleGreyConnection"
    -};</pre>		
    -</div>  		
    -  										
    -	If you do not provide a 'scope' entry, jsPlumb uses a default scope.  Its value is accessible through this method:
    -	<div class="code">
    -<pre>
    -jsPlumb.getDefaultScope();
    -</pre>	
    -	</div>				
    -	If you want to change it for some reason you can do so with this method:
    -	<div class="code">
    -<pre>
    -jsPlumb.setDefaultScope("mySpecialDefaultScope");
    -</pre>	
    -	</div>			
    -	You can also, should you want to, provide the scope value separately on the drag/drop options, like this:
    -	<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -	endpoint:"Rectangle",
    -	painStyle:{ width:25, height:21, fillStyle:'#666' },
    -	isSource:true,
    -	connectionStyle : { strokeStyle:"#666" },
    -	isTarget:true,
    -	dragOptions:{ scope:"dragScope" },
    -	dropOptions:{ scope:"dropScope" }
    -};</pre>		
    -</div>  
    -			
    -	</div>
    -
    -		<div class="section">
    -			<a id="multipleInstances"><h3>Multiple jsPlumb instances</h3></a>
    -			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 <strong>getInstance</strong> method, for example:
    -<div class="code">
    -<pre>
    -var firstInstance = jsPlumb.getInstance();
    -</pre>
    -</div>
    -The variable 'firstInstance' can now be treated exactly as you would treat the 'jsPlumb' variable - you can set
    -defaults, call the connect method, whatever:
    -<div class="code">
    -<pre>
    -firstInstance.Defaults.Connector = [ "Bezier", 150 ];
    -firstInstance.Defaults.Container = "container1";
    -firstInstance.Defaults.Anchors = [ "TopCenter", "BottomCenter" ];
    -
    -firstInstance.connect({source:'element1', target:'element2', scope:'someScope'});
    -</pre>
    -</div>
    -
    -<strong>getInstance</strong> optionally takes an object that provides the defaults:
    -<div class="code">
    -<pre>
    -var secondInstance = jsPlumb.getInstance({
    -	PaintStyle:{lineWidth:3, strokeStyle:color3},
    -	Connector:[ "Bezier", 30 ],
    -	Endpoint:[ "Dot", {radius:5} ],
    -	EndpointStyle : { fillStyle: color3  },
    -	Anchor : [ 0.5, 0.5, 1, 1 ],
    -	Container:"container2"
    -});
    -
    -secondInstance.connect({source:'element4', target:'element3', scope:'someScope'});
    -</pre>
    -</div>
    -Notice the <strong>container</strong> directives that are set on the defaults.  This tells jsPlumb
    -to draw everything inside the container with the given id, giving you cleaner separation between
    -instances.  While not actually required in order to make multiple instances work together, you might
    -find it easier.  Using this concept you can, for example, drag an entire drawing area around as one.  
    -		</div>
    -
    -		<div class="section">
    -			<a id="repaint"><h3>Automatic Repaint</h3></a>
    -			jsPlumb attaches a listener to the browser window and automatically repaints every connection
    -			when a window resize event occurs.  You can disable this functionality, if you want to, with
    -			the following call:
    -<div class="code">
    -<pre>jsPlumb.setAutomaticRepaint(false);</pre>
    -</div>
    -You can also provide your own function for jsPlumb to execute instead of its default behaviour:
    -<div class="code">
    -<pre>var repaint = function() {
    -	// do some things, perhaps, and then...
    -	jsPlumb.repaintEverything();
    -};
    -
    -jsPlumb.setRepaintFunction(repaint);</pre>
    -</div>
    -Notice the call to <strong>repaintEverything()</strong> here - a useful method.
    -<p>Another example:</p>
    -<div class="code">
    -<pre>var repaint = function() {
    -	// completely start over
    -	jsPlumb.detachEverything();
    -	// paint all your connections
    -};
    -
    -jsPlumb.setRepaintFunction(repaint);</pre>
    -</div>
    -		</div>						
    -
    -		<div class="section">
    -			<a id="options"><h3>jsPlumb.connect Options</h3></a>
    -			<p>These are the options you can specify on a call to the connect method, to establish a Connection programmatically:</p>
    -			<ul>
    -				<li><strong>source</strong>
    -					<p>This is a required argument: it identifies the source of the Connection.  It may be a string representing an element's id, a selector for an element, or an Endpoint.</p>
    -				</li>
    -				<li><strong>target</strong>
    -					<p>This is a required argument: it identifies the target element for the Connection.  It may be a string representing an element's id, a selector for an element, or an Endpoint.</p>
    -				</li>
    -				<li><strong>paintStyle</strong>
    -					<p>Optional; if not supplied jsPlumb uses the values defined in jsPlumb.Defaults.PaintStyle (see <a href="#defaults">Defaults</a>). This object allows you to specify five attributes of the connector (note that all of these except 'gradient'
    -					are just passed through to the Canvas element's paint context):
    -						<ul>
    -							<li>strokeStyle	- the color used to paint the connector.  NOTE: jsPlumb does not yet support patterns.</li>
    -							<li>lineWidth - the width of the connector in pixels</li>
    -							<li>lineCap - how the end of the line will be capped.</li>
    -							<li>miterLimit - the limit on how mitery the miters can miterate.</li>
    -							<li>gradient - you can specify a set of colors to use as a gradient for the Connector. see <a href="#gradients">Gradients</a></li>
    -						</ul>
    -					</p>
    -					<p>
    -					The arguments to the strokeStyle parameter can be anything that is a valid argument for the strokeStyle parameter of HTML Canvas element, which
    -					are CSS colors, patterns or gradients.
    -					</p>
    -					<p>
    -					This is the <a class="mplink" href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html" target="_blank">working group's page for the Canvas element</a>, where you can find information
    -					on painting in Canvas.
    -					</p>
    -					<p>
    -					Mozilla also has some good documentation <a class="mplink" href="https://developer.mozilla.org/en/Canvas_tutorial/Applying_styles_and_colors" target="_blank">here</a>.
    -					</p>
    -				</li>
    -				<li><strong>backgroundPaintStyle</strong>
    -					<p>Optional; if not supplied jsPlumb uses the values defined in jsPlumb.Defaults.BackgroundPaintStyle (see <a href="#defaults">Defaults</a>), which is, 
    -					by default, blank. 
    -					</p>
    -					<p>This object allows you to specify a background for the connector, allowing you to do things such as shadows etc.  It takes exactly the same arguments as paintStyle discussed above.</p>
    -				</li>
    -				<li><strong>hoverPaintStyle</strong>
    -					<p>Optional; if not supplied jsPlumb uses the values defined in jsPlumb.Defaults.HoverPaintStyle (see <a href="#defaults">Defaults</a>), which is, by default, blank. 
    -					</p>
    -					<p>This object allows you to specify a paint style for the connector when the mouse is hovering over it. It is only supported in browsers using Canvas. It takes exactly the same arguments as paintStyle discussed above.</p>
    -				</li>
    -				<li><strong>connector</strong>
    -					<p>Defines the appearance of the connector.  Optional; if not supplied jsPlumb uses a Bezier connector (see <a href="#defaults">Defaults</a>)</p>
    -					<p>Valid values for this are:
    -						<ul>
    -							<li>"Straight" or ["Straight",  params ] - a straight line directly connecting two anchors</li>
    -							<li>"Bezier" or ["Bezier", optional_curviness_value ] - a Bezier curve connecting two anchors.</li>
    -						</ul>
    -					</p>
    -					<p>You can also supply your own Connector implementation; for details on how to write a Connector see
    -					the <a href="#customConnectors">Custom Connectors</a> section below.</p>
    -				</li>
    -				<li><strong>anchors</strong>
    -					<p>Defines the location on each element to attach the Connection to.  Optional; if not supplied jsPlumb uses <strong>[ "BottomCenter", "TopCenter" ]</strong>  (see <a href="#defaults">defaults</a>)</p>
    -					<p>If you supply this, it must be in the form of a list with two elements - the first element is
    -					the anchor type for the source element, and the second is the anchor type for the target element.
    -					</p>
    -					<p>Valid values for this are either a four element array,  one of the default anchors provided by jsPlumb (see the section on <a href="#anchors">anchors</a> for more information:
    -						<ul>
    -							<li>"TopCenter"</li>
    -							<li>"TopRight"</li>
    -							<li>"RightMiddle"</li>
    -							<li>"BottomRight"</li>
    -							<li>"BottomCenter"</li>
    -							<li>"BottomLeft"</li>
    -							<li>"LeftMiddle"</li>
    -							<li>"TopLeft"</li>
    -							<li>"Center"</li>
    -							<li>"AutoDefault"</li>
    -						</ul>
    -					</p>
    -					<p>The locations of these are hopefully self-explanatory.  These are all just shorthand for the underlying array-based notation, which you 
    -					can also use, and which gives you greater control over the placement of an Anchor.  This declaration, for example:</p>   
    -<div class="code">
    -<pre>anchor:"TopLeft"</pre>
    -</div>
    -is the same as this:
    -<div class="code">
    -<pre>anchor:[0, 0, -1, -1]</pre>
    -</div>			
    -...where the array-based representation is an array of [ x location, y location, x orientation, y orientation ].  For more information, see the section on <a href="#anchors">Anchors</a>.	
    -
    -
    -					</p>
    -					<p>The "AutoDefault" Anchor is a Dynamic Anchor that uses four default positions - TopCenter, RightMiddle, BottomCenter and LeftMiddle.</p>
    -				</li>
    -				<li><strong>dynamicAnchors</strong>
    -					<p>Here you can supply a list of anchors that the connection should cycle through as elements move in relation to each other.</p>
    -					<p>Valid values are:
    -						<ul>
    -							<li>an array of Anchor definitions:
    -<div class="code">
    -<pre>
    -["TopCenter", "BottomCenter"]
    -</pre>
    -</div>			
    -or, using the Anchor coordinate syntax (these anchors are equivalent to the example above):
    -<div class="code">
    -<pre>
    -[ [ 0.5, 0, 0, -1], [ 0.5, 1, 0, 1] ]
    -</pre>
    -</div>				
    -							</li>
    -						</ul>
    -					</p>					
    -				</li>
    -				<li><strong>endpoint</strong>
    -					<p>Defines the appearance of both Endpoints in the Connection. Optional; if not supplied jsPlumb uses <strong>"Dot"</strong>, with the default size of 10  (see <a href="#defaults">Defaults</a>).</p>
    -					<p>Valid values for this are:
    -						<ul>
    -							<li>"Dot" or [ "Dot", params ]</li>
    -							<li>"Rectangle" or [ "Rectangle", params ]</li>
    -							<li>"Image" or [ "Image", params ]</li>
    -							<li>"Triangle" or ["Triangle", params ]</li>
    -						</ul>
    -					</p>
    -				</li>
    -				<li><strong>endpoints</strong>
    -					<p>Defines the appearance of each Endpoint separately.  Optional; this is similar to <strong>endpoint</strong> but should be used when you want to specify a different Endpoint for each end of the Connector.</p>
    -					<p>This should be supplied as a list, for example:
    -<div class="code">
    -<pre>endpoints:[  ..endpoint1.. ,  ..endpoint2..  ]</pre>
    -</div>
    -					</p>
    -				</li>
    -				<li><strong>endpointStyle</strong>
    -					<p>Defines the styles to apply to both Endpoints. Optional; if not supplied jsPlumb uses the values defined in <strong>jsPlumb.Defaults.EndpointStyle</strong>  (see <a href="#defaults">defaults</a>).</p>
    -					<p>This Javascript object allows you to specify the following arguments for the Endpoint:
    -						<ul>
    -							<li>fillStyle - the color to fill the endpoint with.</li>
    -							<li>gradient - you can specify a set of colors to use as a gradient for the Endpoint. see <a href="#gradients">Gradients</a></li>
    -						</ul>
    -						<br />
    -					</p>
    -				</li>
    -				<li><strong>endpointStyles</strong>
    -					<p>Defines the styles to apply to each Endpoint.  Optional; this is similar to <strong>endpointStyle</strong> but should be used when you want to specify a different style for each of the two Endpoints.</p>
    -					<p>This should be supplied as a list, for example:
    -<div class="code">
    -<pre>endpointStyles:[ { ..style1.. }, { ..style2.. } ]</pre>
    -</div>
    -					</p>
    -				</li>
    -				<li><strong>drawEndpoints</strong>
    -					<p>This is a boolean value that defaults to true.</p>
    -				</li>
    -				<li><strong>dragOptions</strong>
    -					<p>
    -						You can provide your own set of dragOptions to pass through to the underlying library's drag API  if you need to. jsPlumb will wrap any event
    -						methods you provide, since it needs to be aware of drag activity, but everything else is passed through as you specify it. You can do this either on
    -						each call you make:
    -						<div class="code">
    -							<pre>jsPlumb.connect({source:'someWindow', target:"otherWindow", dragOptions:{cursor: 'crosshair'}});</pre>
    -						</div>
    -				 		or for convenience you might want to override the defaults:
    -						<div class="code">
    -							<pre>jsPlumb.Defaults.DragOptions = { .. your drag options here. };</pre>
    -						</div>
    -						See the <a href="#dragOptions">Endpoints discussion</a> for more information about this.  dragOptions on an Endpoint are identical to dragOptions passed in to the 'connect' method.
    -					</p>
    -				</li>
    -				<li>
    -					<strong>uuids</strong>
    -					<p>
    -						Identifies the UUIDs of the source and target Endpoints for this connection.
    -						These Endpoints may already exist, having been added with a call to 
    -						jsPlumb.addEndpoint(..), in which case you do not need to provide information
    -						about the source or target elements for the connection, as the Endpoints know.
    -						However it is also possible to supply this array when you wish to make a 
    -						connection and have jsPlumb create the Endpoints for you:
    -<div class="code">
    -	<pre>
    -jsPlumb.connect({uuids:["uuid1", "uuid2"], source:"someElement", target:"someOtherElement"});	
    -	</pre>
    -</div>						
    -This call would result in jsPlumb creating two Endpoints and registering them with the given UUIDs, and
    -then creating a Connection between them.  Of course when you use this syntax you can supply all of the
    -other things you might wish to tell jsPlumb, like what sort of Endpoints you want, how to draw the
    -Connection, etc.
    -					</p>					
    -				</li>
    -				<li>
    -					<strong>label</strong>
    -					<p>
    -					Optional label for the connection.  This is a shortcut way to add a label as an Overlay, and will place the label in the middle
    -					of the connection.  If you want finer-grained control you should use the 'overlays' parameter (see below, and in the Overlays section)
    -					</p>
    -					<p>
    -					label may be either a String:: 
    -					
    -<div class="code">
    -	<pre>
    -jsPlumb.connect({label:"Static label", source:"someElement", target:"someOtherElement"});	
    -	</pre>				
    -</div>
    -...or a Function that returns a String:
    -<div class="code">
    -	<pre>
    -jsPlumb.connect({
    -	label:function(connection) { 
    -		return (new Date()).getTime();
    -	}, 
    -	source:"someElement", 
    -	target:"someOtherElement"
    -});	
    -	</pre>				
    -</div>
    -The Connection is passed in as an argument to your label generation function.  Be aware that if you use a function for the label text then jsPlumb will be unable to cache the text - or its dimensions -
    -so it will be have to be recomputed every time jsPlumb receives notification of a drag event.  This could lead to UI responsiveness issues in a complex setup.
    -	</p>
    -				</li>
    -				<li>
    -					<strong>labelStyle</strong>
    -					<p>
    -					Optional instructions for how to paint the label, if you supplied one.  Possible values for this are:
    -					<ul>
    -						<li>font - a font specification that HTML Canvas can understand</li>
    -						<li>fillStyle - a fillStyle specification that HTML Canvas can understand; paints the background of the label.</li>
    -						<li>color - color for the label text, in a format that HTML Canvas can understand.</li>
    -					</ul>									
    -					</p>
    -					A good reference for HTML Canvas can be found on <a href="https://developer.mozilla.org/en/Drawing_text_using_a_canvas">Mozilla's Developer documentation</a>
    -					<p>If you do not supply a labelStyle element, jsPlumb will use the values defined in jsPlumb.Defaults.LabelStyle. (see <a href="#defaults">Defaults</a>)</p>
    -					Example:
    -					<div class="code">
    -<pre>
    -jsPlumb.connect({
    -		label:"Static label",
    -		labelStyle: {
    -			fillStyle:"rgba(100,100,100,80)",
    -			color:"white",
    -			font:"12px sans-serif"
    -		}, 
    -		source:"someElement", 
    -		target:"someOtherElement"
    -});	
    -</pre>					
    -					</div>
    -					
    -				</li>
    -				<li>
    -					<strong>overlays</strong>
    -					<p>
    -					This optional parameter is a list of objects that implement the Overlay interface, which is discussed in more detail in the
    -					<a href="#overlays">Overlays</a> section below.  Here's a brief example, though, that shows the same example we just discussed
    -					for labels - but in this case there is also an arrow being painted:</a>
    -					</p>
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:"someElement",
    -	target:"someOtherElement",
    -	overlays: [ 
    -		[ "Label", {
    -			fillStyle:"rgba(100,100,100,80)",
    -			color:"white",
    -			font:"12px sans-serif",
    -			label:"Static label",
    -			borderStyle:"black",
    -			borderWidth:2
    -		}],
    -		[ "Arrow", { location:0.2 } ] 
    -	]
    -});
    -</pre>
    -</div>					
    -The order of painting of Overlays is the order in which they are found in this list.  So in this case, if the Arrow was at the same location as the Label, 
    -it would be painted on top.  But here we have said that the Arrow should be at '0.2' of the length of the connection, and the Label will be at 0.5,
    -which is the default.
    -				</li>
    -			</ul>
    -		</div>
    -
    -		<div class="section">
    -			<a id="addEndpointOptions"><h3>jsPlumb.addEndpoint Options</h3></a>
    -			<p>The simplest addEndpoint call looks like this:
    -			<div class="code">
    -				<pre>jsPlumb.addEndpoint(someEndpoint);</pre>
    -			</div>
    -			but you will almost always want to use the two argument method:
    -			<div class="code">
    -				<pre>jsPlumb.addEndpoint(someEndpoint, { parameters } );</pre>
    -			</div>
    -			<p>These are the options you can specify on a call to the addEndpoint method, to setup a UI for drag and drop Connections:</p>
    -			<ul>
    -				<li><strong>paintStyle</strong>
    -				<p>Optional.
    -				</p>
    -				</li>
    -				<li><strong>hoverPaintStyle</strong>
    -				<p>Optional.
    -				</p>
    -				</li>
    -				<li><strong>connectorPaintStyle</strong>
    -				<p>Optional.
    -				</p>
    -				</li>
    -				<li><strong>connectorHoverStyle</strong>
    -				<p>Optional.
    -				</p>
    -				</li>
    -				<li><strong>isSource</strong>
    -				<p>Optional.
    -				</p>
    -				</li>
    -				<li><strong>isTarget</strong>
    -				<p>Optional.
    -				</p>
    -				</li>
    -			</ul>
    -		</div>
    -		
    -		<div class="section">
    -			<a id="detachOptions"><h3>jsPlumb.detach Options</h3></a>
    -			<p>Both the jsPlumb object and also individual elements have a method called 'detach'. The jsPlumb.detach method is more
    -			versatile and allows you to specify elements, element ids, endpoint uuids or Endpoint objects, whereas the detach method on
    -			individual elements can only take the id of some element, or that element's selector.  This documentation refers to the
    -			jsPlumb.detach method.  It has several similarities with the connect method discussed above.
    -			 </p>
    -			 <p>These are the options you can specify on a call to jsPlumb.detach. You don't need to supply them all: jsPlumb needs 
    -			 either [connection], [source,target] or [sourceEndpoint, targetEndpoint] or uuids. </p>
    -			<ul>
    -				<li><strong>connection</strong>
    -				<p>This is a Connection object such as would have been returned from jsPlumb.connect({...})</p>
    -				</li>
    -				<li><strong>source</strong>
    -					<p>Identifies the source element to find and detach connections from.  It may be a string representing the element's id, or a selector for the element.</p>
    -				</li>
    -				<li><strong>target</strong>
    -					<p>Identifies the target element to find and detach connections from.  It may be a string representing the element's id, or a selector for the element.</p>
    -				</li>
    -				<li><strong>sourceEndpoint</strong>
    -					<p>Identifies the source Endpoint to find and detach connections from.  </p>
    -				</li>
    -				<li><strong>targetEndpoint</strong>
    -					<p>Identifies the target Endpoint to find and detach connections from.  </p>
    -				</li>
    -				<li><strong>uuids</strong>
    -					<p>An array of two Endpoint UUIDs, identifying the source and target Endpoints.</p>
    -				</li>				
    -			</ul>
    -		</div>
    -
    -		<div class="section">
    -			<a id="defaults"><h3>Defaults</h3></a>
    -			The easiest way to set a look and feel for your plumbing is to override the defaults that jsPlumb uses. If you
    -			do not do this you are forced to provide your overridden values on every call.  Every argument to the connect and addEndpoint methods has an
    -			associated default value in jsPlumb.<br/><br/>
    -
    -			The defaults that ship with jsPlumb are stored in <em>jsPlumb.Defaults</em>, which is a Javascript object.  Valid entries, and their initial values, are:
    -
    -			<div class="code">
    -<pre>
    -Anchor : null,
    -Anchors : [ null, null ],
    -BackgroundPaintStyle : null,
    -Connector : null,
    -Container : null,
    -DragOptions : {},
    -DropOptions : {},
    -Endpoint : null,
    -Endpoints : [ null, null ],
    -EndpointStyle : { fillStyle : null },
    -EndpointStyles : [ null, null ],
    -EndpointHoverStyle : null,
    -EndpointHoverStyles : [ null, null ],
    -HoverPaintStyle : null,
    -LabelStyle : { fillStyle : "rgba(0,0,0,0)", color : "black" },
    -LogEnabled : true,
    -MaxConnections : null,
    -PaintStyle : { lineWidth : 10, strokeStyle : 'red' },
    -Scope : "_jsPlumb_DefaultScope"
    -</pre>
    -			</div>
    -			<p>
    -			Note that in EndpointStyle, the default fillStyle is 'null'.  This instructs jsPlumb to use the strokeStyle
    -from the attached connector to fill the endpoint.</p>
    -<p>Note also that you can specify either or both (or neither) of 'EndpointStyle' and 'EndpointStyles'.  This allows you to specify a different
    -end point for each end of a connection.  'Endpoint' and 'Endpoints' use the same concept.  jsPlumb will look first in the
    -individual endpoint/endpoint style arrays, and then fall back to the single default version.</p>
    -
    -			you can override these defaults by including this in a script somewhere:
    -			<div class="code">
    -<pre>
    -jsPlumb.Defaults.PaintStyle = {
    -	lineWidth:13,
    -	strokeStyle: 'rgba(200,0,0,100)'
    -}
    -
    -jsPlumb.Defaults.DragOptions = { cursor: 'crosshair' };
    -
    -jsPlumb.Default.Endpoints = [ [ "Dot", 7 ], [ "Dot", 11 ] ];
    -
    -jsPlumb.Defaults.EndpointStyles = [{ fillStyle:'#225588' }, { fillStyle:'#558822' }];
    -</pre>
    -			</div>
    -			after the jsPlumb script has been loaded of course!  Here we have specified the following default behaviour:
    -			<ul>
    -				<li>connectors are 13 pixels wide and painted with a semi-transparent red line</li>
    -				<li>when dragging an element the crosshair cursor is used</li>
    -				<li>the source endpoint is a dot of radius 7; the target endpoint is a dot of radius 11</li>
    -				<li>the source endpoint is blue; the target endpoint is green</li>
    -			</ul>
    -
    -		</div>
    -
    -		<div class="section">
    -			<a id="anchors"><h3>Anchors</h3></a>			
    -			<p>An Anchor models the notion of where on an element a Connector should connect.  jsPlumb has nine default anchor locations you
    -			can use to specify where the Connectors connect to elements: these are the four corners of an element,
    -			the center of the element, and the midpoint of each edge of the element.  Each of these string representations is just a wrapper around
    -			the underlying array-based syntax, for example: 	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:"BottomCenter", ... }); 
    -</pre>
    -</div>
    -is identical to:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:[0.5, 1, 0, 1], ... }); 
    -</pre>
    -</div>						
    -			</p>
    -
    -
    -		<a id="dynamicAnchors"><h3>Dynamic Anchors</h3></a>
    -	These are Anchors that can be positioned in one of a number of locations, choosing the one that is most appropriate each time something moves or is painted in the UI.
    -	<p>
    -	There is no special syntax to creating a DynamicAnchor; you just provide an array of individual Anchor specifications, eg:
    -	</p>
    -<div class="code">
    -<pre>
    -var dynamicAnchors = [ [ 0.2, 0, 0, -1 ],  [ 1, 0.2, 1, 0 ], 
    -			   [ 0.8, 1, 0, 1 ], [ 0, 0.8, -1, 0 ] ];
    -</pre>
    -</div>
    -<br/><br/>	
    -You can then use that anchor as you would any other.  But note that you <strong>cannot share</strong> dynamic anchors between connections: dynamic anchors maintain 
    -state about the connection they belong to, so if you share one amongst two or more connections you will get unexpected results.
    -<h4>Default Dynamic Anchor</h4>
    -jsPlumb provides a 'default' dynamic anchor that chooses from TopCenter, RightMiddle, BottomCenter and LeftMiddle:
    -<div class="code">
    -<pre>var aDefaultDynamicAnchor = "AutoDefault";</pre>
    -</div>
    -
    -<h4>Location Selection</h4>
    -The initial implementation of the algorithm that decides which location to choose just calculates which location is closest to the center of the 
    -other element in the connection.  It is possible that future versions of jsPlumb could support more sophisticated choice algorithms, if the need arose.
    -<h4>Draggable Connections</h4>
    -Dynamic Anchors and Draggable Connections can interoperate: jsPlumb locks the position of a dynamic anchor when you start to drag a connection from it,
    -and unlocks it once the connection is either established or abandoned. At that point you may see the position of the dynamic anchor change, as jsPlumb
    -optimises the connection.  <p>You can see this behaviour in the <a href="../html/jquery/draggableConnectorsDemo.html" target="_blank">draggable connections</a> demonstration, when
    -you drag a connection from the blue endpoint on window 1 to the blue endpoint on window 3 - the connection is established and then window 1's blue
    -endpoint jumps down to a location that is closer to window 3.</p> 	  
    -					</div>
    -												
    -
    -		<div class="section">
    -			<a id="connectors"><h3>Connectors</h3></a>
    -			<p>Connectors are the lines that actually join elements of the UI.  jsPlumb has three connector implementations - a straight line, a Bezier curve, and
    -			"flowchart" connectors.  The default connector is the Bezier curve.  jsPlumb attaches the CSS class <strong>_jsPlumb_connector</strong> to the canvas elements that it generates for connectors.</p>
    -			<p>You optionally specify a Connector by setting the 'connector' property on a call to jsPlumb.connect or jsPlumb.addEndpoint(s). If you do not
    -			supply a value for 'connector', the default will be used (see <a href="#defaults">defaults</a>).</p>
    -			<p>To specify a Connector there are two syntaxes you can use:<br/><br/>			
    -					1. Specify the Connector by name. jsPlumb will create a Connector of that type, using its default options
    -					<div class="code">
    -						<pre>
    -jsPlumb.connect({
    -	...
    -	connector:"Straight",
    -	...
    -});
    -						</pre>
    -					</div>
    -2. Specify the Connector by an array of [ name, options ]. jsPlumb will create a Connector of type 'name', and pass in the given options to the connector's constructor
    -					<div class="code">
    -						<pre>
    -jsPlumb.connect({
    -	...
    -	connector:[ "Flowchart", { minStubLength:45 } ],
    -	...
    -});
    -						</pre>
    -					</div>								
    -			</p>
    -			Each Connector type supports different (or zero) constructor arguments.  These are described below.
    -			
    -				<h4><a id="bezierConnector">Bezier Connector</a></h4>
    -				<p>The Bezier Connector provides a cubic Bezier path between the two endpoints. It supports a single constructor argument:</p><p>
    -				<strong>curviness</strong> - Optional; defaults to 150. This defines the distance in pixels that
    -				the Bezier's control points are situated from the anchor points.  This does not mean that your
    -				connector will pass through a point at this distance from your curve.  It is a hint to how you want the
    -				curve to travel. Rather than discuss Bezier curves at length here, because they are a complex topic,
    -				we refer you to <a href="http://en.wikipedia.org/wiki/B%C3%A9zier_curve" target="_blank">Wikipedia.</a>
    -				</p>
    -				<h4><a id="straightConnector">Straight Connector</a></h4>
    -				<p>The Straight Connector draws a straight line between the two endpoints. No constructor arguments are supported; use the <strong>endpointStyle</strong>
    -				argument to a connect or addEndpoint call to control the appearance of one of these Connectors.  
    -				</p>
    -				<h4><a id="flowchartConnector">Flowchart Connector</a></h4>
    -				<p>This type of Connector draws a connection that consists of a series of vertical or horizontal segments - the classic flowchart look. A single constructor argument is supported:</p>
    -				<strong>minStubLength</strong> - this is the minimum length, in pixels, of the initial stub that emanates from an Endpoint.  This parameter is optional, and defaults to 30 pixels.
    -			<p>
    -
    -		</div>
    -		
    -		<div class="section">
    -			<a id="overlays"><h3>Overlays</h3></a>
    -			<p>
    -			Overlays are UI elements that are painted onto connections, such as labels or arrows.
    -			jsPlumb comes with four defaults:
    -			<ul>
    -				<li><strong>Arrow</strong> - a configurable arrow that is painted at some point along the connector.  You can control the length and width of the Arrow, the 'foldback' point - a point the tail points fold back into, and the direction (allowed values are 1 and -1; 1 is the default and means point in the direction of the connection)</li>
    -				<li><strong>Label</strong> - a configurable label that is painted at some point along the connector</li>
    -				<li><strong>PlainArrow</strong> - an Arrow shaped as a triangle, with no foldback.</li>
    -				<li><strong>Diamond</strong> - as the name suggests, a diamond.</li>
    -				<!-- li>Image - an image overlay.</li-->
    -			</ul> 
    -			The last two are actually just configured instances of the generic Arrow overlay (see examples). 
    -			</p>
    -			<p>You can specify one or more overlays either when making a call to jsPlumb.connect, or when calling jsPlumb.addEndpoint.  The two cases are discussed below:</p>
    -			1. Specifying one or more overlays on a jsPlumb.connect call. In this example we'll create an Arrow with the default options for an Arrow, and
    -			a label with the text "foo":
    -			<div class="code">
    -			<pre>jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 } ]
    -	],
    -	...
    -});			
    -</pre>
    -			</div>
    -			This connection will have an arrow located halfway along it, and the label "foo" one quarter of the way along.<br/><br/> 
    -			
    -			2. Specifying one or more overlays on a jsPlumb.addEndpoint call.  Note in this example that we use the parameter 'connectorOverlays' and not
    -			'overlays' as in the last example.  This is because 'overlays' will one day be used to support Endpoint Overlays:
    -			<div class="code">
    -			<pre>jsPlumb.addEndpoint("someDiv", {
    -	...
    -	connectorOverlays:[ 
    -		[ "Arrow", { width:10, length:30, location:1 } ], 
    -		[ "Label", { label:"foo" } ]
    -	],
    -	...
    -});			
    -</pre>
    -			</div>	
    -			This connection will have a 10x30 Arrow located right at the head of the connection, and the label "foo" located at the halfway point. 
    -	
    -					<h4><a id="arrowOverlay">Arrow Overlay</a></h4>
    -					<p>This overlay draws an arrow, using four points: the head and two tail points, and a 'foldback' point, which permits the tail
    -					of the arrow to be indented. Available constructor arguments for this Overlay are </p>
    -					<ul>
    -					<li><strong>width</strong> - width of the tail of the arrow</li>
    -					<li><strong>length</strong> - distance from the tail of the arrow to the head</li>
    -					<li><strong>location</strong> - where, as a proportional value from 0 to 1 inclusive, the Arrow should appear on the Connector</li>
    -					<li><strong>direction</strong> - which way to point. Allowed values are 1 (the default, meaning forwards) and -1, meaning backwards </li>
    -					<li><strong>foldback</strong> - how far along the axis of the arrow the tail points foldback in to. Default is 0.623.</li>
    -					<li><strong>paintStyle</strong> - a style object in the form used for paintStyle values for Endpoints and Connectors</li>
    -					</ul>
    -					<h4><a id="plainArrowOverlay">PlainArrow Overlay</a></h4>
    -					<p>This is just a specialized instance of Arrow in which jsPlumb hardcodes 'foldback' to 1, meaning the tail of the Arrow is a flat edge.  All of the constructor parameters from Arrow apply for PlainArrow.</p>
    -					<h4><a id="diamondOverlay">Diamond Overlay</a></h4>
    -					<p>This is a specialized instance of Arrow in which jsPlumb hardcodes 'foldback' to 2, meaning the Arrow turns into a Diamond.  All of the constructor parameters from Arrow apply for PlainArrow.</p>
    -					<h4><a id="labelOverlay">Label Overlay</a></h4>
    -					This provides a text label to decorate Connectors with.  The available constructor arguments are:
    -					<ul>
    -						<li><strong>label</strong> - The text to display.  You can provide a function here instead of plain text: it is passed the Connection as an argument, and it should return a String.</li>
    -						<li><strong>labelStyle</strong> - Optional arguments for the label's appearance.  Valid entries in this JS object are:
    -							<ul>
    -								<li><em>font</em> - a font string in a format suitable for the Canvas element</li>
    -								<li><em>fillStyle</em> - the color to fill the label's background with. Optional.</li>
    -								<li><em>color</em> - the color of the label's text. Optional.</li>
    -								<li><em>padding</em> - optional padding for the label. This is expressed as a proportion of the width of the label, not in pixels or ems.</li>
    -								<li><em>borderWidth</em> - optional width in pixels for the label's border. Defaults to 0.</li>
    -								<li><em>borderStyle</em> - optional. The color to paint the border, if there is one.</li>
    -								
    -							</ul>
    -						
    -						</li>
    -						<li><strong>location</strong> - As for Arrow Overlay.  Where, proportionally from 0 to 1 inclusive, the label should appear.</li>
    -					</ul>
    -	
    -		</div>
    -		<div class="section">
    -			<a id="endpoints"><h3>Endpoints</h3></a>
    -			<p>An Endpoint is the UI component that marks the location of an Anchor, ie. the place where a Connector joins an
    -			element.  jsPlumb comes with three Endpoint implementations - Dot, Rectangle and Image. You optionally specify Endpoint properties using the
    -			<strong>endpoint</strong> parameter in a call to either jsPlumb.connect or jsPlumb.addEndpoint.  The two ways you can specify an Endpoint are similar to that used for Connectors:<br/><br/>
    -			
    -			1. Specify an Endpoint by name:
    -			<div class="code">
    -			<pre>jsPlumb.connect({
    -	...
    -	endpoint:"Dot",
    -	...
    -});
    -</pre>
    -			</div>
    -			2. Specify an Endpoint by [ name, options ]:
    -			<div class="code">
    -			<pre>jsPlumb.addEndpoint("someDiv", {
    -	...
    -	endpoint:[ "Dot", { radius:67 } ],
    -	...					
    -});					
    -</pre>
    -			</div>
    -			
    -			The three available Endpoint types, and their constructor arguments, are as follows:
    -					<h4><a id="dotEndpoint">Dot Endpoint</a></h4>
    -						<p>This draws a dot.  Example:
    -<div class="code">
    -
    -<pre>{ 
    -...
    -endpoint: ["Dot", {radius:34} ];
    -...
    -}
    -</pre>
    -</div>
    -Here we created a dot with a radius of 34 pixels.  You do not need to supply the radius though - if you omit it, jsPlumb will assign a default of 10 pixels.  Note that you can also supply the radius in the endpointStyle object.
    -						</p><p>
    -						In the <em>endpointStyle</em> option of a <em>connect</em> call, you can set two values
    -						that this will pick up:
    -							<ul>
    -								<li>radius - the radius of the dot (in pixels)</li>
    -								<li>fillStyle - the style to use when filling the dot.  If this is blank, jsPlumb will
    -								attempt to use the strokeStyle from the associated Connector.</li>
    -							</ul>
    -							<br/>
    -						</p>
    -					</li>
    -					<h4><a id="rectangleEndpoint">Rectangle Endpoint</a></h4>
    -						<p>Draws a rectangle.  Example:
    -<div class="code">
    -<pre>{
    -...
    -endpoint:[ "Rectangle", {width:34, height:10} ];
    -...
    -}
    -</pre>
    -</div>
    -Here we created a rectangle of size 34x10.  If you omit the size when you create a Rectangle, jsPlumb will use defaults of 20x20.  Just like with the Dot endpoint, you can also provide this information in the endpointStyle.
    -</p><p>
    -						In the <em>endpointStyle</em> you can set the following for this:
    -							<ul>
    -								<li>width - optional, defaults to 20.</li>
    -								<li>height - optional, defaults to 20.</li>
    -								<li>fillStyle - the style to use when filling the rectangle.  If this is blank, jsPlumb will
    -								attempt to use the strokeStyle from the associated Connector.</li>
    -							</ul>
    -							<br/>
    -						</p>
    -					</li>
    -		<h4><a id="imageEndpoint">Image Endpoint</a></h4>
    -						<p>Draws an image from a given URL.  Example:
    -<div class="code">
    -<pre>{
    -...
    -endpoint:[ "Image", {url:"http://myserver.com/images/endpoint.png"} ];
    -...
    -}
    -</pre>
    -</div>
    -This creates an Image endpoint with the image at the given url.  You do not need to provide dimensions.  jsPlumb will figure that out for you.
    -						</p>
    -					</li>
    -				</ul>
    -			</p>
    -			
    -			<a id="endpointOperations"><h4>Endpoint Operations</h4></a>
    -			These are the methods supported by the Endpoint class:
    -			<ul>
    -<li><p>removeConnection</p>
    -This method is <em>deprecated</em>; it has been renamed to <strong>detach</strong>
    -</li>
    -<li><p>detach</p>
    -Removes a Connection from the Endpoint.  The Connection will have been returned from a call to <strong>jsPlumb.connect</strong>.  Detaching the
    -Connection has the effect of removing the Connection from the display.  
    -<div class="code">
    -<pre>
    -var c = myEndpoint.addConnection({...});
    -
    -...
    -time passes
    -...
    -
    -myEndpoint.detach(c);
    -</pre>
    -</div>
    -</li>
    -<li><p>detachAll</p>
    -Removes all Connections from the Endpoint (and from the display).  
    -<div class="code">
    -<pre>
    -var c = myEndpoint.addConnection({...});
    -var c2 = myEndpoint.addConnection({...});
    -
    -...
    -time passes
    -...
    -
    -myEndpoint.detachAll();
    -</pre>
    -</div>
    -</li>
    -<li><p>detachFrom</p>
    -Detaches all Connections from this Endpoint to some other Endpoint (from the display too).
    -<div class="code">
    -<pre>
    -var c = myEndpoint.addConnection({...});
    -var c2 = myOtherEndpoint.addConnection({...});
    -jsPlumb.connect({sourceEndpoint:c, targetEndpoint:c2});
    -
    -...
    -time passes
    -...
    -
    -myEndpoint.detachFrom(myOtherEndpoint);
    -</pre>
    -</div>
    -</li>
    -<li>
    -	<p>getElement</p>
    -Returns the DOM element the Endpoint is attached to.	
    -</li>
    -<li>
    -<p>isConnectedTo</p>
    -Returns whether or not this Endpoint is connected to some other Endpoint.
    -<div class="code">
    -<pre>
    -var c = myEndpoint.addConnection({...});
    -var c2 = myOtherEndpoint.addConnection({...});
    -jsPlumb.connect({sourceEndpoint:c, targetEndpoint:c2});
    -
    -var isConnected = c.isConnectedTo(c2);				// returns true
    -</pre>
    -</div>
    -</li>
    -<li>
    -	<p>isFull</p>
    -	Returns whether or not the Endpoint can accept any more Connections.  This is determined by the parameters
    -	passed in to the addEndpoint call, specifically the 'maxConnections' value: by default this is set to 1. You
    -	can provide any positive value for this, or -1 if you wish to have no maximum.
    -</li>
    -<li>
    -	<p>getUuid</p>
    -	Returns the Endpoint's UUID, if one was provided when the Endpoint was created.  See also
    -	<strong>jsPlumb.getEndpoint(uuid)</strong> and <strong>jsPlumb.connect</strong>.
    -</li>
    -<li>
    -	<p>setDragAllowedWhenFull</p>
    -	Sets whether or not connections can be dragged from the Endpoint once it is full.  Use this in conjunction
    -	with the 'reattach' option on a connect call - if this is true, you can specify whether or not dropped
    -	connections reattach or are removed.
    -</li>
    -			</ul>
    -	<a id="endpointUuids"><h4>Endpoint UUIDs</h4></a>
    -	Each Endpoint can have a UUID associated with it; these can be used to establish Connections and
    -	also to retrieve Endpoints from jsPlumb.  To assign a UUID to an Endpoint you can do one of two things:
    -	
    -	<ul>
    -	<li>
    -	Nominate the UUID in a call to jsPlumb.addEndpoint (when creating an Endpoint you will use later):
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("someElement", {uuid:"abcdefg"});
    -</pre>
    -</div>		
    -	</li>
    -	<li>Nominate two UUIDs in a call to jsPlumb.connect (Endpoints will be created and have these UUIDs assigned):
    -<div class="code">
    -<pre>
    -jsPlumb.connect({uuids:["abcdefg", "hijklmn"], source:"someElement", target:"someOtherElement"});
    -</pre>
    -</div>		
    -	</li>
    -	</ul>
    -	
    -	Once you have an Endpoint that has a UUID assigned, you can retrieve it from jsPlumb:
    -<div class="code">
    -<pre>
    -var e = jsPlumb.getEndpoint("abcdefg");
    -</pre>
    -</div>	
    -...and you can also use the UUIDs to connect Endpoints:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({uuids:["abcdefg", "hijklmn"});
    -</pre>
    -</div>		
    -
    -			
    -		</div>
    -
    -		<div class="section">
    -			<a id="gradients"><h3>Gradients</h3></a>
    -			The Canvas element supports gradients, and jsPlumb can take advantage of this when painting your Connectors
    -			and/or Endpoints.  <strong>Note:</strong> this does <strong>NOT WORK in IE</strong>, because we use ExplorerCanvas in IE
    -			and ExplorerCanvas does not support gradients.
    -			<p>There are two types of gradients available in Canvas - a 'linear' gradient, which consists of colored lines all
    -			going in one direction, and a 'radial' gradient, which consists of colored circles emanating from one circle to another.
    -			Because of their basic shape, jsPlumb supports only <em>linear</em> gradients for
    -			Connectors.  But for Endpoints, jsPlumb supports both <em>linear</em> and <em>radial</em> gradients.</p>
    -			<p>
    -				<h4>Connector gradients</h4>
    -				To specify a linear gradient to use in a Connector, you must add a <em>gradient</em> object to your
    -				Connector's <em>paintStyle</em>, for instance:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : "window2",
    -	target : "window3",
    -	paintStyle:{
    -		gradient:{
    -			stops:[[0,"green"], [1,"red"]]
    -		},
    -		lineWidth:15
    -	}
    -});
    -</pre>
    -</div>
    -Here we have connected window2 to window3 with a 15 pixel wide connector that has a gradient from green to red.</p><p> Notice the <strong>gradient</strong> object and the
    -<strong>stops</strong> list inside it - the gradient consists of an arbitrary number of these "color stops".  Each color stop is comprised
    -of two values - [position, color].  Position must be a decimal value between 0 and 1 (inclusive), and indicates where the color
    -stop is situated as a fraction of the length of the entire gradient.  Valid values for
    -the colors in the <strong>stops</strong> list are the same as those that are valid for <strong>strokeStyle</strong> when describing a color.
    -			</p>
    -			<p>As mentioned, the <strong>stops</strong> list can hold an arbitrary number of entries.  Here's an example of a gradient that goes from red to blue to green, and back again through blue to red:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle : {
    -		gradient:{
    -			stops:[[0,'red'], [0.33,'blue'], [0.66,'green'], [0.33,'blue'], [1,'red']]
    -		},
    -		lineWidth : 15
    -	}
    -});
    -</pre>
    -</div>
    -<strong>Note:</strong> jsPlumb uses ExplorerCanvas for IE, which does not support gradients.  On IE, jsPlumb will simply ignore the gradient directive
    -so it is best to ensure you also supply a <em>strokeStyle</em> in your paintStyle object, to give jsPlumb something to fall back on.  If you do not supply
    -a <em>strokeStyle</em> your Connectors will be painted black.  The previous example might look like this, for instance:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle:{
    -		strokeStyle:'red',
    -		gradient:{
    -			stops:[[0,'red'], [0.33,'blue'], [0.66,'green'], [0.33,'blue'], [1,'red']]
    -		},
    -		lineWidth:15
    -	}
    -});
    -</pre>
    -</div>
    -Notice the <strong>strokeStyle:'red'</strong> directive at the beginning of the parameter list in <strong>paintStyle</strong>.
    -			</p>
    -			<h4>Endpoint gradients</h4>
    -			Endpoint gradients are specified using the same syntax as Connector gradients.  You put the gradient specifier either in the
    -			<strong>endpoint</strong> member, or if you are specifying different Endpoints for each end of the Connector, in one or both of
    -			the values in the <strong>endpoints</strong> array.  Also, this information applies to the case that you are creating standalone
    -			Endpoints that you will be configuring for drag and drop creation of new Connections. 
    -			<p>
    -			This is an example of an Endpoint gradient that is different for each Endpoint in the Connector.  This comes from the main demo; it is
    -			the Connector joining Window 2 to Window 3:
    -			</p>
    -<div class="code">
    -<pre>var w23Stroke = 'rgb(189,11,11)';
    -jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle:{
    -		lineWidth:8,
    -		strokeStyle:w23Stroke
    -	},
    - 	anchors:[ [0.3,1,0,1], "TopCenter" ],
    - 	endpoint:"Rectangle",
    - 	endpointStyles:[
    - 		{ gradient : {stops:[[0, w23Stroke], [1, '#558822']] } },
    -    	{ gradient : {stops:[[0, w23Stroke], [1, '#882255']] } }
    -    ]
    -});
    -</pre>
    -</div>
    -The first entry in the gradient will be the one that is on the Connector end of the Endpoint.  You can of course have as many color stops as
    -you want in this gradient, just like with Connector gradients.
    -	<h4>Applying the gradient in Endpoints</h4>
    -	Only the Dot and Rectangle endpoints honour the presence of a gradient (and, remember, not in IE). The Image endpoint of course ignores a gradient
    -	as it does no painting of its own.
    -	<p>The type of gradient you will see depends on the Endpoint type:
    -		<ul>
    -			<li>Dot - renders a radial endpoint, with color stop 0 on the outside, progressing inwards as we move through color stops.
    -			<p>Radial gradients actually require more data than linear gradients - in a linear gradient we just move from one point to another, whereas
    -			in a radial gradient we move from one <em>circle</em> to another.  By default, jsPlumb will render a radial gradient using a source
    -			circle of the same radius as the Endpoint itself, and a target circle of 1/3 of the radius of the Endpoint (both circles share the
    -			same center as the Endpoint itself). This circle will be offset by radius/2 in each direction.</p>
    -			<p>You can supply your own values for these inside the gradient descriptor:</p>
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect({
    -	source : 'window3',
    -	target : 'window4',
    -    paintStyle:{
    -    	lineWidth:10,
    -    	strokeStyle:w34Stroke
    -    },
    -    anchors:[ "RightMiddle", "LeftMiddle" ],
    -    endpointStyle:{
    -    	gradient : {
    -    		stops:[ [0, w34Stroke], [1, w34HlStroke] ],
    -    		offset:37.5,
    -    		innerRadius:40
    -    	},
    -    	radius:55
    -    }
    - });
    -</pre>
    -</div>
    -Here we have instructed jsPlumb to make the gradient's inner radius 10px instead of the default 25/3 = 8 ish pixels, and the offset in each direction
    -will be 5px, instead of the default radius / 2 = 12.5 pixels.
    -<p>It is also possible to specify the offset and inner radius as percentages - enter the values as strings with a '%' symbol on the end:</p>
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect({
    -	source : 'window3', 
    -	target : 'window4',
    -    paintStyle:{
    -    	lineWidth:10,
    -    	strokeStyle:w34Stroke
    -    },
    -	anchors:[ "RightMiddle", "LeftMiddle" ],
    -	endpointStyle:{
    -		gradient : {
    -			stops:[ [0, w34Stroke], [1, w34HlStroke] ],
    -			offset:'68%',
    -			innerRadius:'73%'
    -		},
    -		radius:25
    -	}
    -});
    -</pre>
    -</div>
    -This will give roughly the same output as the example above (the percentages are not entirely exact).<br/>
    -			</li>
    -			<li>Rectangle - renders a linear endpoint, with color stop 0 closest to the end of the Connector</li>
    -		</ul>
    -	</p>
    -		</div>
    -		
    -		<div class="section">
    -			<a id="animation"><h3>Animation</h3></a>
    -			jsPlumb offers an 'animate' function, which wraps the underlying animation engine for whichever library you happen to be using and
    -			inserts a callback for jsPlumb to repaint whatever it needs to at each step.  You could of course do this yourself; it's a convenience
    -			method really.
    -			<p>The method signature is:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.animate : function(el, properties, options) 
    -</pre>
    -</div>			
    -The arguments are as follows:
    -	<ul>
    -		<li>el - element id, or element object from the library you're using.</li>
    -		<li>properties - properties for the animation, such as duration etc.</li>
    -		<li>options - options for the animation, such as callbacks etc.</li>
    -	</ul>
    -	
    -		</div>
    -		
    -		<div class="section">
    -			<a id="events"><h3>Events</h3></a>
    -			jsPlumb fires an event to registered listeners whenever a connection
    -			is made (we plan to support more events in the future).
    -			<h4>Registering Event Listeners</h4>
    -			To register an event listener you need to supply the event name and a listener object:
    -<div class="code">
    -<pre>
    -jsPlumb.bind("jsPlumbConnection", someListener); 
    -</pre>
    -</div>
    -(you can also use 'addListener', but it has been deprecated in 1.2.4. It does the same thing as bind.<br/><br/>
    -The listener object - in this case 'someListener' - must have a method with the same name as the event. For example, to define
    -a listener inline, you could do a call like this:
    -<div class="code">
    -<pre>
    -jsPlumb.bind("jsPlumbConnection", function(data) {
    -	console.log("a connection was made from " + data.sourceId + " to " + data.targetId);
    -}); 
    -jsPlumb.bind("jsPlumbConnectionDetached", function(data) {
    -	console.log("a connection was detached from " + data.sourceId + " and " + data.targetId);
    -}); 
    -</pre>
    -</div>
    -		<h4>Event Types</h4>
    -		<table>
    -			<tr><th>event name</th><th>function arguments</th><th>explanation</th></tr>
    -			<tr><td>jsPlumbConnection</td><td>params - JS object containing sourceId, targetId, source, target, sourceEndpoint, targetEndpoint</td><td>fired whenever a new connection is made, either programmatically via jsPlumb.connect, or drag and drop</td></tr>
    -			<tr><td>jsPlumbConnectionDetached</td><td>params - JS object containing sourceId, targetId, source, target, sourceEndpoint, targetEndpoint</td><td>fired whenever a connection is detached, either programmatically via one of the jsPlumb.detach methods, or drag and drop</td></tr>
    -		</table>
    -			
    -		
    -		</div>
    -
    -		<div class="section">
    -			<a id="cssclasses"><h3>CSS Class Reference</h3></a>
    -			jsPlumb attaches classes to each of the UI components it creates:
    -			<table>
    -				<tr><th>component</th><th>css class</th></tr>
    -				<tr><td>connector</td><td>_jsPlumb_connector</td></tr>
    -				<tr><td>endpoint</td><td>_jsPlumb_endpoint</td></tr>
    -				<!-- tr><td>image overlays</td><td>_jsPlumb_overlay</td></tr-->
    -			</table>
    -			You would typically use these to establish appropriate z-indices for your UI.
    -		</div>
    -
    -				
    -				<div class="section">
    -			<a id="examples"><h3>jsPlumb.connect Examples</h3></a>
    -This section provides examples of how to use the programmatic API to establish Connections. 
    -			<p>
    -			The basic syntax of a call is that you execute 'connect', providing a source and a target, and optionally a paintStyle and preferences for where you
    -			want the plumbing to be anchored on each element, as well as the type of connector to use. 
    -			</p>
    -			<ul>
    -				<li>Connect window1 to window2 with the default settings:
    -					<div class="code">
    -						<pre>jsPlumb.connect({source:"window1", target:"window2"});</pre>
    -					</div>
    -This connects 'window1' to 'window2' using the jsPlumb defaults - a red Bezier curve Connector and black Dot Endpoints.
    -				</li>
    -				<li>Connect window1 to window2 with a 15 pixel wide yellow Connector, and a slightly brighter endpoint (remember the default Endpoint is a Dot):
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -					</div>
    -				</li>
    -				
    -				<li>Connect window1 to window2 with a 15 pixel wide yellow Connector, and a slightly brighter endpoint:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -					</div>
    -				</li>				
    -				<li>Connect window3 to 'window4' with a 10 pixel wide, semi opaque blue Connector, anchored to the left middle of window3, and the right middle of window4, with a Rectangle endpoint of width 10 and height 8:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window3',
    -	target:'window4',
    -	paintStyle:{ lineWidth:10, strokeStyle:'rgba(0, 0, 200, 0.5)' },
    -	anchors:["RightMiddle", "LeftMiddle"],
    -	endpoint:[ "Rectangle", { width:10, height:8 } ]
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window2 to window3 with a default Connector from the top center of window2 to the bottom center of window3, and rectangular endpoints:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window2',
    -	target:'window3',
    -	paintStyle:{lineWidth:8, strokeStyle:'rgb(189,11,11)'},
    -	anchors:["BottomCenter", "TopCenter"],
    -	endpoint:"Rectangle"
    -});</pre>
    -					</div>
    -				</li>
    -
    -
    -				<li>Connect window1 to window2 with a 15 px wide yellow Bezier. endpoints are a slightly lighter shade of yellow.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	anchors:["BottomCenter", [0.75,0,0,-1]],
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -</div></li>
    -<li>Connect window3 to window4 with a 10px wide blue-ish half transparent Bezier. put endpoints underneath the element they attach to.
    -the endpoints have a radial gradient. both ways of specifying gradient positioning are shown here.
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect( {
    -	source:'window3',
    -	target:'window4',
    -		 paintStyle:{lineWidth:10, strokeStyle:w34Stroke},
    -		 anchors:["RightMiddle", "LeftMiddle"],
    -		 endpointStyle:{ gradient : {stops:[[0, w34Stroke], [1, w34HlStroke]], offset:17.5, innerRadius:15 }, radius:35},
    -		 //endpointStyle:{ gradient : {stops:[[0, w34Stroke], [1, w34HlStroke]], offset:'78%', innerRadius:'73%'}, radius:35 },
    -		 endpointsOnTop:false
    -	}
    -);</pre>
    -</div></li>
    -<li>Connect window2 to window3 with an 8px red Bezier and default rectangular endpoints.  see also how the first anchor is
    -specified here - this is how you create anchors in locations jsPlumb does not offer shortcuts for.
    -the endpoints in this example have linear gradients applied.
    -<div class="code">
    -<pre>var w23Stroke = 'rgb(189,11,11)';
    -jsPlumb.connect({
    -	source:'window2',
    -	target:'window3',
    -	paintStyle:{lineWidth:8,strokeStyle:w23Stroke},
    -	anchors:[[0.3,1,0,1], "TopCenter"],
    -	endpoint:"Rectangle",
    -	endpointStyles:[{ gradient : {stops:[[0, w23Stroke], [1, '#558822']] }},
    -       				{ gradient : {stops:[[0, w23Stroke], [1, '#882255']] }}]
    -});</pre>
    -</div></li>
    -
    -<li>Connect window5 to window6 from center to center, 5px wide line that is green and half transparent. the endpoints are
    -125px in radius and spill out from underneath their elements.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window5',
    -	target:'window6',
    -	anchors:["Center", "Center"],
    -	paintStyle:{lineWidth:5,strokeStyle:'rgba(0,255,0,0.5)'},
    -	endpointStyle:{radius:125}
    -});</pre>
    -</div></li>
    -
    -<li>Connect window4 to window5 from bottom right to top left, with a 7px straight line purple connector, and an image as the endpoint,
    -placed on top of the element it is connected to.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window4',
    -	target:'window5',
    -	anchors:["BottomRight","TopLeft"],
    -	paintStyle:{lineWidth:7,strokeStyle:'rgb(131,8,135)'},
    -	endpoint:[ "Image", {url:"http://morrisonpitt.com/jsPlumb/img/endpointTest1.png"} ],
    -	connector:"Straight"
    -});</pre>
    -</div></li>
    -
    -
    -				<li>Connect window5 to window6 between their center points with a semi-opaque connector, and 125px endpoints:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window5',
    -	target:'window6',
    -	anchors:["Center", "Center"],
    -	paintStyle:{lineWidth:5,strokeStyle:'rgba(0,255,0,0.5)'},
    -	endpointStyle:{radius:125}
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window7 to window8 with a 10 pixel wide blue Connector, anchored on the top left of window7 and the bottom right of window8:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window7',
    -	target:'window8',
    -	paintStyle:{lineWidth:10, strokeStyle:'blue'},
    -	anchors:["TopLeft", "BottomRight"]
    -});
    -</pre>
    -					</div>
    -				</li>
    -				<li>Connect the bottom right corner of window4 to the top left corner of window5, with rectangular endpoints of size 40x40:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window4',
    -	target:'window5',
    -	anchors:["BottomRight","TopLeft"],
    -	paintStyle:{lineWidth:7,strokeStyle:'rgb(131,8,135)'},
    -	endpointStyle:{width:40, height:40},
    -	endpoint:"Rectangle",
    -	connector:"Straight"
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window1 to window2 with the default paint settings but provide some drag options (which are passed through to the underlying library's draggable call):
    -					<div class="code">
    -						<pre>jsPlumb.connect({source:'window1', target:'window2', dragOptions:{cursor:'crosshair'}});</pre>
    -					</div>
    -				</li>
    -</div>
    -				<div class="section">
    -			<a id="utilityFunctions"><h3>Utility Functions</h3></a>
    -				<li>Detach window5 from all connections
    -					<div class="code">
    -						<pre>jsPlumb.detachAll("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Hide all window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.hide("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Show all window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.show("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Toggle the visibility of window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.toggle("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of all of window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.repaint("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of all of window5, window6 and window11's connections
    -					<div class="code">
    -						<pre>jsPlumb.repaint( [ "window5", "window6", "window11" ] );</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of every connection
    -					<div class="code">
    -						<pre>jsPlumb.repaintEverything();</pre>
    -					</div>
    -				</li>
    -				<li>Detach every connection
    -					<div class="code">
    -						<pre>jsPlumb.detachEverything();</pre>
    -					</div>
    -				</li>
    -				<li>Remove the given Endpoint from element "window1", deleting its Connections. 
    -					<div class="code">
    -						<pre>jsPlumb.removeEndpoint("window1", someEndpoint);</pre>
    -					</div>
    -				</li>
    -				<li>Remove all Endpoints for the element 'window1', deleting their Connections.
    -					<div class="code">
    -						<pre>jsPlumb.removeAllEndpoints("window1");</pre>
    -					</div>
    -				</li>	
    -				<li>Removes every Endpoint managed by this instance of jsPlumb, deleting all Connections.
    -				This is the same as jsPlumb.reset(), effectively, but it does not clear out the event listeners list. 
    -					<div class="code">
    -						<pre>jsPlumb.removeEveryEndpoint();</pre>
    -					</div>
    -				</li>
    -				<li>Deletes the given Endpoint and all its Connections. 
    -					<div class="code">
    -						<pre>jsPlumb.deleteEndpoint(endpoint);</pre>
    -					</div>
    -				</li>
    -				<li>Removes every endpoint, detaches every connection, and clears the event listeners list.  Returns jsPlumb instance to its initial state.  
    -					<div class="code">
    -						<pre>jsPlumb.reset();</pre>
    -					</div>
    -				</li>				
    -				<li>Set window1 to be not draggable, no matter what some jsPlumb command may request.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggable("window1", false);</pre>
    -					</div>
    -				</li>
    -				<li>Set window1 and window2 to be not draggable, no matter what some jsPlumb command may request.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggable(["window1","window2"], false);</pre>
    -					</div>
    -				</li>
    -				<li>Sets whether or not elements that are connected are draggable by default.
    -				The default for this is true.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggableByDefault(false);</pre>
    -					</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable("window1");</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 and window2 as draggable elements (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(["window1","window2"]);</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable("window1");</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (jQuery)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (MooTools)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($$(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (YIU3)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(Y.all(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (jQuery)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($("#window1"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (MooTools)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($("window1"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (YUI3)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(Y.one("window1"));</pre>
    -				</div>
    -				</li>
    -			</ul>
    -		</div>
    -		
    -		<div class="section">
    -			<a id="dragAndDropExamples"><h3>Draggable Connections Examples</h3></a>
    -			This is a list of examples of how to use jsPlumb to create Connections using drag and drop.
    -			<p>
    -			The basic procedure is:
    -			<ol>
    -				<li>Create Endpoints and register them on elements in your UI</li>
    -				<li>Drag and Drop</li>
    -			</ol>
    -			That's all there is to it.  Of course there are plenty of options you can set when doing this...it will be easier
    -			to show you some examples:
    -			</p>
    -			<ul>
    -				<li>Define an Endpoint with default appearance, that is both a source and target of new Connections:
    -<div class="code">
    -<pre>var endpointOptions = { isSource:true, isTarget:true }; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Register that Endpoint on window3, specifying that it should be located in the top center of the element:
    -<div class="code">
    -<pre>var window3Endpoint = jsPlumb.addEndpoint('window3', { anchor:"TopCenter" }, endpointOptions );  
    -</pre>
    -</div>
    -Notice here the usage of the new three-argument addEndpoint  - we can reuse 'endpointOptions' with a different Anchor for another element.  This is a useful
    -practice to get into.<br/><br/>
    -				</li>
    -				
    -				<li>Now register that Endpoint on window4, specifying that it should be located in the bottom center of the element:
    -<div class="code">
    -<pre>var window4Endpoint = jsPlumb.addEndpoint('window4', { anchor:"BottomCenter" }, endpointOptions );  
    -</pre>
    -</div>
    -Now we have two Endpoints, both of which support drag and drop of new Connections.  We can use these to make a programmatic Connection, too, though:<br/><br/>
    -				</li>				
    -				
    -				<li>Connect window3 to window4 with a 25px wide yellow Bezier that has a 'curviness' of 175:
    -<div class="code">
    -<pre>jsPlumb.connect({ 
    -	source:window3Endpoint,
    -	target:window4Endpoint,
    -	connector: [ "Bezier", 175 ],
    -	paintStyle:{ lineWidth:25, strokeStyle:'yellow' }
    -});  
    -</pre>
    -</div>
    -				</li>
    -				
    -				<li>Define an Endpoint that creates Connections that are 20px wide straight lines, that is both a source and target of new Connections,
    -				and that has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Define an Endpoint that will be anchored to "TopCenter".  It creates Connections that are 20px wide straight lines, that is both a source and target of new Connections,
    -				and that has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchor:"TopCenter", 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Define an Endpoint that will create a dynamic anchor which can be positioned at "TopCenter" or "BottomCenter".  It creates Connections that are 20px wide straight lines, it is both a source and target of new Connections,
    -				and it has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchor:[ "TopCenter", "BottomCenter" ], 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Exactly the same as before, but shows how you can use "anchors" instead of "anchor", if that makes you feel happier:
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchors:[ "TopCenter", "BottomCenter" ], 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>								
    -				
    -<li>Define an Endpoint that is a 30px blue dot, creates Connections that are 20px wide straight lines, is both a source and target of new Connections,
    -				has a 'scope' of 'blueline', and has an event handler that pops up an alert (note: the event handler name means this example is jQuery - MooTools
    -				and YUI3 use different event handler names):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{fillStyle:'blue'},
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ drop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -			<li>Same example as before, but this is for MooTools, and the Endpoint can support up to 5 connections (the default is 1):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{ fillStyle:'blue' },
    -	maxConnections:5,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ onDrop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Same example again, but maxConnections being set to -1 means that the Endpoint has no maximum limit of Connections:
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", {radius:30} ],
    -	style:{ fillStyle:'blue' },
    -	maxConnections:-1,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ onDrop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Same example again, but for YUI3.  Note the drop callback is "drop:hit":
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{fillStyle:'blue'},
    -	maxConnections:-1,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ "drop:hit":function(e, ui) { alert('drop!'); } }	 	
    -}; 
    -</pre>
    -</div>				 				
    -				</li>				
    -				
    -	<li>Assign a UUID to the endpoint options created above, and add as Endpoints to "window1" and "window2":
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("window1", { uuid:"abcdefg" }, endpointOptions );
    -jsPlumb.addEndpoint("window2", { uuid:"hijklmn" }, endpointOptions );
    -</pre>
    -</div>	
    -	</li>
    -	
    -	<li>Connect the two Endpoints we just registered on "window1" and "window2":
    -<div class="code">
    -<pre>
    -jsPlumb.connect({uuids:["abcdefg", "hijklmn"]});
    -</pre>
    -</div>	
    -	</li>
    -				
    -			</ul>
    -		</div>
    -		
    -		<div class="section">
    -			<a id="connectionInfo"><h3>Retrieving Connection Information</h3></a>
    -			jsPlumb offers one fairly versatile method - <strong>getConnections</strong> - to retrieve information about the
    -			currently managed connections.  
    -			<p>Before you use this method you should understand jsPlumb's notion of 'scope' - documentation
    -			is <a href="#dragAndDropScope">here</a> </p>
    -			<p>
    -			getConnections optionally takes an object specifying filter parameters, of which there are three:
    -			<ul>
    -				<li>scope - the scope of the connection type(s) you wish to retrieve</li>
    -				<li>source - limits the returned connections to those that have this source id</li>
    -				<li>target - limits the returned connections to those that have this target id</li>
    -			</ul>			
    -			Each of these three parameters may be supplied as a string, or a list of strings - see the examples below.
    -			</p>
    -			<p>
    -			The return value of a call to getConnection is a dictionary whose keys are scope names,
    -			and whose values are lists of sourceId/targetId pairs, for example:
    -			
    -<div class="code">
    -<pre>
    -{
    -	"_jsPlumb_DefaultScope" : [
    -		{sourceId:"window1", targetId:"window2", source:&lt;sourceElement&gt;, target:&lt;targetElement&gt;, sourceEndpoint:&lt;sourceEndpoint&gt;, targetEndpoint:&lt;targetEndpoint&gt;, connection:&lt;connection&gt; },
    -		{sourceId:"window5", targetId:"window3", source:&lt;sourceElement&gt;, target:&lt;targetElement&gt;, sourceEndpoint:&lt;sourceEndpoint&gt;, targetEndpoint:&lt;targetEndpoint&gt;, connection:&lt;connection&gt; }
    -	],
    -	"someCustomScope": [
    -		{sourceId:"window6", targetId:"window2", source:&lt;sourceElement&gt;, target:&lt;targetElement&gt;, sourceEndpoint:&lt;sourceEndpoint&gt;, targetEndpoint:&lt;targetEndpoint&gt;, connection:&lt;connection&gt; },
    -		{sourceId:"window4", targetId:"window13", source:&lt;sourceElement&gt;, target:&lt;targetElement&gt;, sourceEndpoint:&lt;sourceEndpoint&gt;, targetEndpoint:&lt;targetEndpoint&gt;, connection:&lt;connection&gt; },
    -		{sourceId:"window2", targetId:"window10", source:&lt;sourceElement&gt;, target:&lt;targetElement&gt;, sourceEndpoint:&lt;sourceEndpoint&gt;, targetEndpoint:&lt;targetEndpoint&gt;, connection:&lt;connection&gt; }
    -	]
    -}
    -</pre>
    -</div>			
    -			</p>
    -		The following examples show the various ways you can call this method:
    -	<ul>
    -		<li>Get all connections:
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections();  
    -</pre>
    -</div>						
    -		</li>
    -<li>Get all connections for the default scope only:
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections(jsPlumb.getDefaultScope());  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given scope:
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:"myTestScope"});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given scopes:
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:["myTestScope", "yourTestScope"]});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given source:
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:"mySourceElement"});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given sources:
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:["mySourceElement", "yourSourceElement"]});  
    -</pre>
    -</div>						
    -		</li>		
    -<li>Get all connections for the given target:
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({target:"myTargetElement"});  
    -</pre>
    -</div>						
    -		</li>				
    -<li>Get all connections for the given source and targets:
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:"mySourceElement", target:["target1", "target2"]});  
    -</pre>
    -</div>						
    -		</li>				
    -		
    -<li>Get all connections for the given scope, with the given source and target:
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:'myScope", source:"mySourceElement", target:"myTargetElement"});  
    -</pre>
    -</div>						
    -		</li>				
    -		
    -	</ul>	
    -	Note that the return value is always a dictionary and not an array, even if you specified a single
    -	scope in the getConnections call.  So you always have to get the array you need by looking it up in the dictionary:
    -	
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:"myScope", source:"mySourceElement"});
    -var conns = c["myScope"];  
    -</pre>
    -</div>					
    -
    -The array <strong>may be null</strong>.  If you have not registered any connections with that scope, it will be.  Code defensively!
    -
    -			
    -		</div>
    -		
    -		
    -		<div class="section">
    -			<a id="developingJsPlumb"><h3>Advanced Topics</h3></a>
    -			<h4><a id="fileBreakdown">Which files are which?</a></h4>
    -			In development, jsPlumb is broken up into four main scripts:
    -				<ul>
    -					<li>jsPlumb-X.X.js
    -					<p>This is the main jsPlumb engine.  It calls out to the underlying library implementation, and it has no Anchors, Endpoints or Connections specified.</p>					
    -					</li>
    -					<li>jsPlumb-defaults-x.x.js
    -					<p>This contains the default Anchor, Endpoints and Connections implementations</p>
    -					</li>
    -					<li>&lt;LIBRARY_PREFIX&gt;.jsPlumb-X.X.js
    -					<p>This contains library-specific helper methods.  jsPlumb ships with three of these - one each for jQuery, MooTools and YUI3. See below for information on how to create a new library implementation.</p>
    -					</li>
    -					<li>&lt;jsBezier-0.2-min.js&gt;</li>
    -					<p>These are the Bezier curve functions; they are maintained in a separate project called <a href="http://code.google.com/p/jsbezier/">jsBezier</a></p>
    -					</li>
    -				</ul>
    -			These four files are packaged together to form the scripts that people use, for example:
    -			<ul>
    -				<li>jquery.jsPlumb-1.2.6-all.js
    -				<p>Contains jsPlumb-1.2.6.js, jsPlumb-defaults-1.2.6.js, jquery.jsPlumb-1.2.6.js and jsBezier-0.2-min.js</p>
    -				</li>
    -				<li>jquery.jsPlumb-1.2.6-all-min.js
    -				<p>A minified version of the script above (minified using the YUI Compressor)</p>
    -				</li>
    -			</ul>
    -			<h4><a id="pluggableLibrarySupport">Pluggable Library Support</a></h4>
    -		Out of the box, jsPlumb can be run on top of jQuery, MooTools or YUI3.  This is achieved by
    -		delegating several core methods - tasks such as finding an element by id, finding an element's
    -		position or dimensions, initialising a draggable, etc - to the library in question.
    -		<p>To develop one of these, your test page should include the first two scripts discussed above, and
    -		then your own script containing your library specific functionality.  The two existing implementations
    -		should be documented well enough for you to create your own.  If you do this, it would be
    -		great to share it with everyone...</p>   
    -
    -			<h4><a id="customConnectors">Custom Connectors</a></h4>			
    -					
    -			You can provide your own connectors if you need to. A Connector consists of two functions, which work as a pair.  First a call is made to the <em>compute</em> function:
    -
    -			<div class="code">
    -<pre>
    -this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) {
    -	...
    -	return dimensions;
    -}
    -</pre>
    -			</div>
    -			which is expected to return a list that the <em>paint</em> function can make sense of.  The first four entries in the
    -			list <strong>must be</strong> the [x,y,width,height] values for the canvas that the connector will be drawn on; jsPlumb will
    -			use this information to size the canvas prior to calling the Connector's <em>paint</em> function.  Therefore it
    -			is the Connector's responsibility to ensure that the returned dimensions describe a large enough space for
    -			the line that will be drawn on it.</p><p>The next four elements <strong>must be</strong> the coordinates of the two endpoints of the line
    -			you are going to draw.</p><p>The remainder of the items in the returned list are arbitrary, and will
    -			vary between Connector implementations; this list is passed in to a Connector's <em>paint</em> function, so each
    -			implementation will put into the list whatever it needs to paint itself.  For instance, the straight line
    -			connector only needs the [x,y] location of each end of the line it will paint, and that is one of the required entries, so
    -			it does not have to do anything extra,  whereas the Bezier connector adds the location of the two control points.  Other types of
    -			Connectors will do whatever is appropriate for their particular situation.
    -			</p>
    -			This is the method signature for the <em>paint</em> function:
    -			<div class="code">
    -				<pre>this.paint = function(dimensions, ctx) { .. }</pre>
    -			</div>
    -
    -			here, the 'dimensions' argument to the 'paint' function is the return value of the 'compute' function.  The 'ctx' argument is the Canvas context; you
    -			will do all your drawing on this.
    -			</p>
    -			To change the connector from the default, specify it in your connect call:
    -			<div class="code">
    -				<pre>jsPlumb.connect({source:'someWindow', target:'otherWindow', connector:new jsPlumb.Connectors.Straight()});</pre>
    -			</div>
    -			note that you can use shorthand for connectors if you don't need to specify parameters to it:
    -			<div class="code">
    -				<pre>jsPlumb.connect({source:'someWindow', target:'otherWindow', connector:"Straight"});</pre>
    -			</div>
    -			This works in the same way as specifying Anchors as string does.
    -<p>
    -The section below this discusses Overlays, which allow you to decorate Connectors with
    -			things such as labels or arrows or whatever else you like. Overlays can only work with Connectors that implement a few helper functions.
    -			<h4>The concept of <strong>location</strong></h4>
    -			Before discussing the helper functions you first must be across the concept of 'location' as used by this mechanism.  The <em>location</em>
    -			on a connector can be a decimal value between 0 and 1 inclusive, which marks a proportionate amount of travel <em>along the path
    -			inscribed by the Connector</em>.  For a straight line connector this is a simple piece of maths, but for Bezier connectors it's a little
    -			bit more involved.
    -			<h4>Required Helper Methods</h4>
    -			<ul>
    -				<li><strong>pointOnPath(location)</strong> - returns an [x,y] point corresponding to the given location</li>
    -				<li><strong>pointAlongPathFrom(location, distance)</strong> - returns an [x,y] point corresponding to travelling 'distance' pixels along the connector from 'location'.</li>
    -				<li><strong>gradientAtPoint(location)</strong> - returns the connector's gradient at the given location.  For linear connectors such as Straight this is constant, but for Bezier connectors the gradient changes continually.</li>
    -				<li><strong>perpendicularToPathAt(location, distance, length)</strong> - returns a line that is perpendicular to (and centered on) the connector at 'distance' from the given location, with length 'length'.</li>
    -			</ul>
    -</p>
    -<h4><a id="customEndpoints">Custom Endpoints</a></h4>
    -<p>To create your own Endpoint implementation, you need to implement a single method:
    -				<div class="code"><pre>paint : function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { ... }</pre></div>
    -				The arguments to this method are as follows:
    -				<ul>
    -					<li>anchorPoint - [x,y] location of the anchor point on screen</li>
    -					<li>orientation - [x,y] hints for the general direction the anchor points to</li>
    -					<li>canvas - the canvas to draw into</li>
    -					<li>endpointStyle - Javascript object containing style directives as discussed above.  The contents of this are arbitrary, so if you write a new Endpoint that needs some extra settings, you can add them no hassle.</li>
    -					<li>connectorPaintStyle - the style being used to paint the associated Connector.</li>
    -				</ul>
    -			</p>
    -			<p>
    -				It is your responsibility to size and locate the canvas to suit your needs. jsPlumb provides the following
    -				helper method to assist you:
    -				<div class="code"><pre>jsPlumb.sizeCanvas(canvas, x, y, width, height);</pre></div>
    -				Allows you to locate the canvas on screen and to size it.
    -			</p>
    -<h4><a id="customOverlays">Custom Overlays</a></h4>
    -			Overlays can only work with Connectors that implement the methods <strong>pointOnPath</strong>, <strong>pointAlongPathFrom</strong>,
    -			<strong>gradientAtPoint</strong>, and <strong>perpendicularToPathAt</strong>.  These are discussed in more detail in the Custom Connectors section above.  Both of the Connectors
    -			that come with jsPlumb - Straight and Bezier -implement these methods; if you write a custom connector, or have written a custom connector, you will need to supply
    -			them.   
    -			<h4>Overlay Interface</h4>
    -			An Overlay is required to implement two methods in order to be usable by jsPlumb:
    -			<ul>
    -				<li><strong>computeMaxSize(connector, context)</strong> - returns an integer value indicating the larger of this overlay's width and height.  It is used by jsPlumb to ensure that the canvas is large enough to accomodate the overlay.  The examples below should help to clarify this.</li>
    -				<li><strong>draw(connector, ctx)</strong> - draws the overlay.  What happens in this method is up to the given implementation.</li>
    -			</ul>
    -			<h4>computeMaxSize methods</h4>
    -			This is the Arrow overlay's computeMaxSize method:
    -			<div class="code">
    -<pre>
    -this.computeMaxSize = function() { return width; }
    -</pre>			
    -			</div>
    -Here, <em>width</em> is a private member of Arrow that indicates the width of the arrow's tail.  So the Arrow overlay reports that figure as the width it needs.					
    -Contrast this with the Label Overlay's computeMaxSize method:
    -		<div class="code">
    -<pre>
    -this.computeMaxSize = function(connector, ctx) {
    -  	if (labelText) {
    -  		ctx.save();
    -        if (self.labelStyle.font) ctx.font = self.labelStyle.font;
    -         var t = ctx.measureText(labelText).width;			            
    -		// a fake text height measurement: use the width of upper case M
    -		var h = ctx.measureText("M").width;					
    -		labelPadding = self.labelStyle.padding || 0.25;
    -		labelWidth = t + (2 * t * labelPadding);
    -		labelHeight = h + (2 * h * labelPadding);
    -		ctx.restore();
    -		return Math.max(labelWidth, labelHeight);
    -  	}
    - 	return 0;
    - };
    -</pre>			
    -			</div>
    -The Label overlay has to use the context to determine how big it will be on screen.
    -
    -<h4>draw methods</h4>
    -To give you a taste for how you can interact with a connector, consider the first few lines of the Arrow overlay's draw method:
    -<div class="code">
    -<pre>
    -this.draw = function(connector, ctx) {
    -	// this is the arrow head position
    -	var hxy = connector.pointAlongPathFrom(self.loc, length / 2);		
    -	// this is the center of the tail
    -	var txy = connector.pointAlongPathFrom(self.loc, -length / 2), tx = txy[0], ty = txy[1];
    -	// this is the tail vector
    -	var tail = connector.perpendicularToPathAt(self.loc, -length / 2, width);
    -	
    -	...
    -</pre>
    -</div>
    -	<p>The first two calls to the connector use the <strong>pointAlongPathFrom</strong> method, which passes <em>self.loc</em> as the location of the
    -	point on the path, and <em>length / 2</em> as the number of pixels along the path to traverse.</p>
    -	<p><em>self.loc</em> is Arrow's internal member describing the location of the overlay, and it is a decimal (between 0 and 1 inclusive) that points to
    -	a location at some distance along the path inscribed by the connector.  So these first two calls get us [x,y] locations of points on 
    -	the connector that mark the head and tail points for the arrow.  
    -	</p>		
    -	<p>
    -	The <strong>connector.perpendicularToPathAt(self.loc, -length / 2, width)</strong> call returns a line description of a line that is perpendicular to, and centered on, the connector
    -	at "-length / 2" pixels from the given point, with the given width (width is an internal member of Arrow).  At this point, the Arrow object has the three main points it
    -	needs in order to draw itself.
    -	</p>
    -		</div>
    -</div>
    -
    -</body>
    -
    -</html>
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.2.6/index.html b/demo/doc/archive/1.2.6/index.html
    deleted file mode 100644
    index 3077d0f08..000000000
    --- a/demo/doc/archive/1.2.6/index.html
    +++ /dev/null
    @@ -1,54 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.2.6 documentation</title>
    -		<link rel="stylesheet" target="contentFrame" href="/mp.css"></link>
    -		<link rel="stylesheet" target="contentFrame" href="../css/jsPlumbDemo.css"></link>
    -	</head>
    -	<body>
    -<div class="index section">
    -			<h3>jsPlumb 1.2.6 - Index</h3>
    -			<ul>
    -				<li><a target="contentFrame" href="content.html#summary">Summary</a></li>
    -				<li><a target="contentFrame" href="content.html#changes">Changes since version 1.2.5</a></li>
    -				<li><a target="contentFrame" href="content.html#requirements">Imports</a></li>
    -				<li><a target="contentFrame" href="content.html#init">Initializing jsPlumb</a></li>
    -				<li><a target="contentFrame" href="content.html#unload">Unloading jsPlumb</a></li>
    -				<li><a target="contentFrame" href="content.html#jsPlumbBasics">jsPlumb Basic Concepts</a></li>
    -				<li><a target="contentFrame" href="content.html#simpleConnections">Simple Connections</a></li>
    -				<li><a target="contentFrame" href="content.html#draggableConnections">Draggable Connections</a></li>
    -				<li><a target="contentFrame" href="content.html#dragOptions">Drag Options</a></li>
    -				<li><a target="contentFrame" href="content.html#dropOptions">Drop Options</a></li>
    -				<li><a target="contentFrame" href="content.html#dragAndDropScope">Drag and Drop Scope</a></li>
    -				<li><a target="contentFrame" href="content.html#multipleInstances">Multiple Instances of jsPlumb</a></li>							
    -				<li><a target="contentFrame" href="content.html#repaint">Automatic repaint</a></li>				
    -				<li><a target="contentFrame" href="content.html#options">jsPlumb.connect</a></li>
    -				<li><a target="contentFrame" href="content.html#addEndpointOptions">jsPlumb.addEndpoint</a></li>
    -				<li><a target="contentFrame" href="content.html#detachOptions">jsPlumb.detach</a></li>
    -				<li><a target="contentFrame" href="content.html#defaults">Defaults</a></li>
    -				<li><a target="contentFrame" href="content.html#anchors">Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#dynamicAnchors">Dynamic Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#connectors">Connectors</a></li>
    -				<li><a target="contentFrame" href="content.html#overlays">Overlays</a></li>				
    -				<li><a target="contentFrame" href="content.html#endpoints">Endpoints</a></li>
    -				<li><a target="contentFrame" href="content.html#endpointOperations">Endpoint Operations</a></li>
    -				<li><a target="contentFrame" href="content.html#endpointUuids">Endpoint UUIDs</a></li>
    -				<li><a target="contentFrame" href="content.html#gradients">Gradients</a></li>
    -				<li><a target="contentFrame" href="content.html#animation">Animation</a></li>
    -				<li><a target="contentFrame" href="content.html#events">Events</a></li>
    -				<li><a target="contentFrame" href="content.html#cssclasses">CSS Class Reference</a></li>				
    -				<li><a target="contentFrame" href="content.html#examples">Programmatic API Examples</a></li>				
    -				<li><a target="contentFrame" href="content.html#utilityFunctions">Utility Functions</a></li>
    -				<li><a target="contentFrame" href="content.html#dragAndDropExamples">Draggable Connections Examples</a></li>
    -				<li><a target="contentFrame" href="content.html#connectionInfo">Retrieving Connection Information</a></li>
    -				<li><a target="contentFrame" href="content.html#developingJsPlumb">Advanced Topics</a></li>
    -				<li><a target="contentFrame" href="content.html#pluggableLibrarySupport">Pluggable Library Support</a></li>
    -				<li><a target="contentFrame" href="content.html#customConnectors">Custom Connectors</a></li>
    -				<li><a target="contentFrame" href="content.html#customEndpoints">Custom Endpoints</a></li>
    -				<li><a target="contentFrame" href="content.html#customOverlays">Custom Overlays</a></li>
    -			</ul>
    -			<strong>This document refers to release 1.2.6 of jsPlumb.</strong>  
    -			<strong>12 June 2011</strong>
    -		</div>
    -		
    -		</body>
    -		</html>
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.2.6/usage.html b/demo/doc/archive/1.2.6/usage.html
    deleted file mode 100644
    index 20f31d5b9..000000000
    --- a/demo/doc/archive/1.2.6/usage.html
    +++ /dev/null
    @@ -1,10 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.2.6 - documentation</title>		
    -	</head>
    -	
    -	<frameset cols="250,*">
    -  		<frame src="index.html" />
    -  		<frame src="content.html" name="contentFrame" />
    -	</frameset>	
    -</html>
    diff --git a/demo/doc/archive/1.3.10/content.html b/demo/doc/archive/1.3.10/content.html
    deleted file mode 100644
    index 944a67d92..000000000
    --- a/demo/doc/archive/1.3.10/content.html
    +++ /dev/null
    @@ -1,3257 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.4.0 - documentation</title>
    -		<link rel="stylesheet" href="/mp.css">
    -		<link rel="stylesheet" href="jsPlumbDoc.css">
    -	</head>
    -	<body>
    -
    -<div class="menu">
    -	<a href="http://jsplumb.org" class="mplink" target="_top">view demo</a>
    -	&nbsp;|&nbsp;<a href="mailto:simon.porritt@gmail.com" class="mplink">contact me</a>
    -	&nbsp;|&nbsp;<a href="http://jsplumb.tumblr.com" class="mplink">jsPlumb on Tumblr</a>	
    -</div>
    -
    -<!--
    -GWT
    -
    -http://fishfilosophy.blogspot.com/2011/12/how-to-connect-gwt-widgets-using.html?spref=tw
    -
    --->
    -
    -<div class="content">
    -		<div class="section">
    -			<h3><a id="summary">Summary</a></h3>
    -			jsPlumb allows you to connect elements on the screen using SVG, Canvas or VML, depending on the capabilities of the browser.
    -			<p>
    -				It can be used with jQuery, MooTools or YUI3 (or another library of your choice if you feel like implementing an adapter for it).
    -			</p>  
    -
    -			<h5>Browser Compatibility</h5>
    -			<p>jsPlumb 1.4.0 runs on everything from IE6 up.  There are some caveats, though, because of various browser/library bugs:
    -				<ul>
    -					<li class="bullet">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.
    -					<li class="bullet">Safari 5.1 has an SVG bug that prevents mouse events from being passed through the transparent area of an SVG element</li>
    -					<li class="bullet">MooTools has a bug when using SVG in Firefox 11</li>
    -				<ul>
    -				
    -			</p>
    -		</div>
    -		
    -		<div class="section">		
    -			<h3><a id="changes">Changes between 1.4.0 and 1.3.9</a></h3>
    -			
    -			<h5>Backwards Compatibility</h5>
    -			One issue in this release:
    -			<ul>
    -				<li>The 'clearListeners' method on Connection, Endpoint and jsPlumb has been renamed to 'unbind'</li>
    -			</ul>
    -			
    -			<h5>New Features</h5>
    -			<ul>
    -				<li class="bullet">makeSource can now be called with a 'filter' parameter which allows you to respond to mouse events only on the parts of the element you want to.</li>	
    -				<li class="bullet">Flowchart connector supports a different length stub at each end (supply an array of two integers instead of just a single integer)</li>
    -				<li class="bullet">The position of Connector overlays can now be specified as an absolute value, with positive values meaning distance from source and negative values meaning distance from target</li>
    -			</ul>
    -
    -			<h5>Issues Resolved</h5>
    -			<ul>
    -				<li class="bullet"><strong>224</strong> - 'absolute' overlay position</li>
    -				<li class="bullet"><strong>247</strong> - support non-loopback connections between endpoints on the same element (StateMachine connectors)</li>
    -				<li class="bullet"><strong>248</strong> - 'container' ignored when using parent with makeSource</li>
    -				<li class="bullet"><strong>250</strong> - SVG does not work in Android &lt; 3, but jsPlumb thinks it should</li>
    -				<li class="bullet"><strong>252</strong> - rename 'clearListeners' to 'unbind'</li>
    -				<li class="bullet"><strong>253</strong> - allow makeSource to ignore mouseclicks on selected elements and not start a drag</li>
    -				<li class="bullet"><strong>254</strong> - different stub lengths for each end of a flowchart connector</li>
    -				<li class="bullet"><strong>257</strong> - clarified setLabel documentation to point out that it uses innerHTML</li>
    -			</ul>
    -			
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="init">Setup</a></h3>
    -
    -            <h4><a id="imports">Required Imports</a></h4>
    -			<h4>jQuery</h4>
    -			    <ul>
    -				    <li class="bullet">jQuery 1.3.x or higher.</li>
    -				    <li class="bullet">jQuery UI 1.7.x or 1.8.x (if you wish to support drag and drop).</li>
    -			    </ul>
    -			    <div class="code">
    -<pre>&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/jquery-ui.min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/jquery.jsPlumb-1.4.0-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			<h4>MooTools</h4>
    -				<ul>
    -					<li class="bullet">MooTools Core 1.2.4 or higher. jsPlumb has been tested with 1.2.4 and 1.3.3.</li>
    -					<li class="bullet">Drag.Move from MooTools More 1.2.4.4 or higher (if you wish to support drag and drop).</li>
    -				</ul>
    -			<div class="code">
    -			<pre>&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/mootools/1.3.3/mootools-yui-compressed.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO_MOO_TOOLS_MORE_1_3_2_1"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/mootools.jsPlumb-1.4.0-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			<h4>YUI3</h4>
    -			<ul>
    -				<li class="bullet">YUI 3.3.x.  jsPlumb has been tested on 3.3.0 only; it is possible other 3.x.x versions will work.</li>
    -			</ul>
    -			<div class="code">
    -			<pre>&lt;script type="text/javascript" src="http://yui.yahooapis.com/3.3.0/build/simpleyui/simpleyui-min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/yui.jsPlumb-1.4.0-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			
    -			<h4><a id="renderMode">Render Mode</a></h4>
    -			jsPlumb can use SVG, HTML5 Canvas elements or VML to render the various objects it adds to the display.  Most modern browsers support Canvas and SVG; IE&lt;9 browsers support neither.
    -			<p>
    -				By default, jsPlumb attempts to use <strong>jsPlumb.SVG</strong> as the render mode (prior to 1.3.4, the default renderer was Canvas), falling back to <strong>jsPlumb.VML</strong> if the 
    -				browser is IE&lt;9.  You can change the render mode by making this call:  
    -			</p>
    -			<div class="code">
    -<pre>jsPlumb.setRenderMode(jsPlumb.CANVAS);</pre>			
    -			</div>
    -			if, for some reason, you set the render mode to be <strong>jsPlumb.VML</strong> but you're in any browser other than IE, jsPlumb will use SVG.
    -			<p>
    -			The <strong>jsPlumb.setRenderMode</strong> method returns you the render mode that jsPlumb actually ended up setting.  Valid values for setRenderMode are:
    -			</p>
    -			<ul>
    -				<li class="bullet">jsPlumb.CANVAS</li>
    -				<li class="bullet">jsPlumb.SVG</li>
    -				<li class="bullet">jsPlumb.VML</li>
    -			</ul>			
    -			<h5>Performance Considerations</h5>
    -			<p>
    -			SVG uses less memory than Canvas and has support for mouse events built in, so I recommend using
    -			SVG unless you have a really good reason for wanting Canvas. Of course, you might consider that horrendous Safari bug a really good reason for wanting Canvas.
    -			</p>
    -			
    -			<h4><a id="initializing">Initializing jsPlumb</a></h4>
    -			You should not start making calls to jsPlumb until the DOM has been initialized - perhaps no surprises there.  With YUI, though, the asynchronous
    -			nature of the script loading process means that you are not guaranteed that jsPlumb is ready as soon as the DOM is.  To handle this,
    -			you should bind to the "ready" event on jsPlumb (or the instance of jsPlumb you are working with):
    -			<div class="code">
    -			<pre>jsPlumb.bind("ready", function() {
    -...	
    -		
    -// your jsPlumb related init code goes here
    -
    -...
    -
    -});						
    -			</pre>
    -			</div>
    -			There's a helper method that can save you approximately 9 (count them!) characters:
    -			<div class="code">
    -			<pre>jsPlumb.ready(function() {
    -...	
    -		
    -// your jsPlumb related init code goes here
    -
    -...
    -
    -});						
    -			</pre>
    -			</div>
    -			<p>
    -			For jQuery and MooTools you do not actually need to do this; by the time the DOM ready event is fired in each of those libraries you can be sure 
    -			all the JS we need has been loaded.  But in terms of explicitly calling out that you are waiting for jsPlumb, it seems like a good practice to bind to the "ready" event.
    -			</p>
    -			<p>
    -			If you bind to the "ready" event after jsPlumb has already been initialized, your callback will be executed immediately.
    -			</p>			
    -
    -				
    -<h4><a id="multipleInstances">Multiple jsPlumb instances</a></h4>
    -			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 <strong>getInstance</strong> method, for example:
    -<div class="code">
    -<pre>
    -var firstInstance = jsPlumb.getInstance();
    -</pre>
    -</div>
    -The variable 'firstInstance' can now be treated exactly as you would treat the 'jsPlumb' variable - you can set
    -defaults, call the connect method, whatever:
    -<div class="code">
    -<pre>
    -firstInstance.importDefaults({
    -  Connector : [ "Bezier", { curviness: 150 } ],
    -  Anchors : [ "TopCenter", "BottomCenter" ]
    -});
    -
    -firstInstance.connect({source:"element1", target:"element2", scope:"someScope" });
    -</pre>
    -</div>
    -
    -<strong>getInstance</strong> optionally takes an object that provides the defaults:
    -<div class="code">
    -<pre>
    -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" });
    -</pre>
    -</div>
    -
    -		<h4><a id="zIndex">Z-index Considerations</a></h4>
    -		<p>
    -			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. 
    -		</p>  
    -		<p>
    -			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).  			
    -		</p>
    -		<p>
    -			To help you organise z-indices correctly, jsPlumb adds a CSS class to each type of
    -			element it adds. They are as follows:			
    -		</p>
    -		<table style="color:black;width:70%;font-size:90%;">
    -			<tr><td><strong>type of element</strong></td><td><strong>class added</strong></td></tr>
    -			<tr><td>Endpoint</td><td>_jsPlumb_endpoint</td></tr>
    -			<tr><td>Connector</td><td>_jsPlumb_connector</td></tr>
    -			<tr><td>Overlay</td><td>_jsPlumb_overlay</td></tr>
    -		</table>
    -
    -		<h4><a id="containerDefault">Where does jsPlumb add elements?</a></h4>
    -		<p>
    -			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.
    -		</p>
    -		<p>
    -			Prior to 1.3.0, jsPlumb added everything to the end of the <em>body</em> element. This
    -			has the advantage of being the most flexible arrangement in terms of supporting what
    -			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!   
    -		</p>
    -		<p>
    -			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 (<strong>not</strong> 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.
    -		</p>
    -		
    -		<p>
    -			In general, the default behaviour means that you cannot do things like this:
    -		</p>
    -		<div class="code">
    -<pre>
    -&lt;body&gt;
    -	&lt;div id="container0"&gt;
    -		&lt;div id="node0"&gt;&lt;/div&gt;
    -	&lt;/div&gt;
    -	&lt;div id="container1"&gt;
    -		&lt;div id="node1"&gt;&lt;/div&gt;
    -	&lt;/div&gt;
    -&lt;/body&gt;
    -
    -&lt;script type="text/javascript"&gt;
    -
    -	var e0 = jsPlumb.addEndpoint("node0"),
    -	e1 = jsPlumb.addEndpoint("node1");
    -
    -	jsPlumb.connect({ source:e0, target:e1 });
    -&lt;/script&gt;
    -</pre>		
    -		</div>
    -<p>
    -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):  
    -</p>
    -		<div class="code">
    -<pre>
    -&lt;body&gt;
    -	&lt;div id="container0"&gt;
    -	&lt;/div&gt;
    -	&lt;div id="container1"&gt;
    -	&lt;/div&gt;
    -&lt;/body&gt;
    -
    -&lt;script type="text/javascript"&gt;
    -
    -	var e0 = jsPlumb.addEndpoint("container0"),
    -	e1 = jsPlumb.addEndpoint("container1");
    -
    -	jsPlumb.connect({ source:e0, target:e1 });
    -&lt;/script&gt;
    -</pre>		
    -		</div>
    -	now 'e0' and 'e1' have the same parent - the <em>body</em> element. 
    -	
    -	<p>Remember that you can use the <strong>anchor</strong> parameter to an <strong>addEndpoint</strong>
    -	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. 
    -	</p>									
    -	<p><strong>Note regarding the drawEndpoints option on jsPlumb.connect</strong>: 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). 
    -	</p>
    -	
    -	<h5>Overriding the default behaviour</h5>
    -	<p>
    -From version 1.3.3, the <strong>container</strong> 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 <strong>jsPlumb.Defaults.Container</strong> property, or you can set a <strong>container</strong> parameter on calls to <strong>addEndpoint</strong> or <strong>connect</strong> (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:
    -	</p>
    -		
    -Set a container to use as the default container, using a jQuery selector (you can supply MooTools/YUI selectors if that's the library you're using), and then add an Endpoint.  The canvas (or SVG/VML element) created will be a child of the document body:
    -		<div class="code">
    -<pre>
    -jsPlumb.Defaults.Container = $("body");
    -...
    -jsPlumb.addEndpoint(someDiv, { endpoint options });
    -</pre>		
    -		</div>
    -		
    -			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":
    -		<div class="code">
    -<pre>
    -jsPlumb.Defaults.Container = "containerId";
    -...
    -jsPlumb.connect({ source:someDiv, target:someOtherDiv });
    -</pre>		
    -		</div>
    -		
    -		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":
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({ source:someDiv, target:someOtherDiv, container:$("#containerId") });
    -</pre>		
    -		</div>
    -				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":
    -		<div class="code">
    -<pre>
    -jsPlumb.addEndpoint(someDiv, { ..., container:"containerId" });
    -</pre>		
    -		</div>
    -
    -		<h4><a id="elementDragging">Element Dragging</a></h4>
    -		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 <strong>jsPlumb.draggable(...)</strong>:
    -<div class="code">
    -<pre>jsPlumb.draggable("elementId");</pre>
    -</div>
    -You can also pass a selector from your library in to this method:
    -<div class="code">
    -<pre>jsPlumb.draggable($(".someClass");</pre>
    -</div>
    -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.  A common requirement when using jQuery, for instance, is to set the containment of elements that you are making draggable.  Here's how you would tell jsPlumb to initialise some set of elements to be draggable but only inside their parent:
    -<div class="code">
    -<pre>jsPlumb.draggable($(".someClass", {
    -	containment:"parent"
    -});</pre>
    -</div>
    -		<h5>Dragging nested elements</h5>
    -jsPlumb takes nesting into account when handling draggable elements. For example, say you have this markup:
    -<div class="code">
    -<pre>
    -&lt;div id="container"&gt;
    -  &lt;div class="childType1"&gt;&lt;/div&gt;
    -  &lt;div class="childType2"&gt;&lt;/div&gt;
    -&lt;/div&gt;
    -</pre>
    -</div>
    -..and then you connect one of those child divs to something, and make "container" draggable:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:$("#container .childType1"),
    -	target:"somehere else"
    -});
    -
    -jsPlumb.draggable("container");
    -</pre>
    -</div>
    -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.
    -
    -
    -		</div>
    -				
    -		<div class="section">
    -			<h3><a id="jsPlumbBasics">Basic Concepts</a></h3>			
    -	
    -			jsPlumb is all about connecting things together, so the core abstraction in jsPlumb is the <strong>Connection</strong> object, which is itself
    -			broken down into these four concepts:
    -
    -			<ul>
    -				<li><strong>Anchor</strong> - a location, relative to an element's origin, at which an Endpoint can exist. You do not create these yourself; you 
    -				supply hints to the various jsPlumb functions, which create them as needed.  They have no visual representation; they are a logical position only. Anchors can be referenced by name, for the Anchors that jsPlumb ships with, or a four element array describing [ x, y, x orientation, y orientation ]. 
    -				See the <a href="#anchors">anchors</a> section for more detail.<br/><br/></li>
    -				<li><strong>Endpoint</strong> - the visual representation of one end of a Connection.  You can create and attach these to elements yourself, which you are required to do to support drag and drop, or have jsPlumb create them when creating a Connection programmatically using jsPlumb.connect(...).  You can also join two Endpoints programmatically, by passing them as arguments to jsPlumb.connect(...).<br/><br/></li>				
    -				<li><strong>Connector</strong> - the visual representation of the line connecting two elements in the page.  jsPlumb has four types of these 
    -				available as defaults - a Bezier curve, a straight line, 'flowchart' connectors and 'state machine connectors. You do not interact with Connectors; 
    -				you just specify definitions of them when you need to - see <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions</a> <br/><br/></li>
    -				<li><strong>Overlay</strong> - a UI component that is used to decorate a Connector, such as a Label, Arrow, etc.<br/><br/></li>
    -			</ul>
    -						
    -			<p>One Connection is made up of two Endpoints, a Connector, and zero or more Overlays working together to join two elements.</p>
    -			<p>jsPlumb's public API exposes only Connection and Endpoint, handling the creation and configuration of everything else internally. But you still need to be across the concepts encapsulated by Anchor, Connector and Overlay.
    -			</p>
    -			</div>
    -			
    -			<div class="section">
    -			<h3><a id="anchors">Anchors</a></h3>			
    -	
    -			An Anchor models the notion of where on an element a Connector should connect.  There are three main types of Anchors:
    -			<ul>
    -			<li/>
    -				<li class="bullet">'Static' anchors - these are fixed to some point on an element and do not move.  They can be specified using a string to
    -				identify one of the defaults that jsPlumb ships with, or an array describing the location (see below)<br/><br/></li>				
    -				<li class="bullet">'Dynamic' anchors - these are lists of Static anchors from which jsPlumb picks the most appropriate one each time a Connection
    -				is painted.  The algorithm used to determine the most appropriate anchor picks the one that is closest to the center of the other element in
    -				the Connection. A future version of jsPlumb might support a pluggable algorithm to make this decision.<br/><br/></li>
    -				<li class="bullet">'Continuous' anchors - These anchors, introduced in 1.3.4, are not fixed to any specific loction; they are assigned to one of
    -				the four faces of an element depending on that element's orientation to the other element in the associated Connection.  Continuous anchors are slightly more computationally intensive than Static or Dynamic anchors because jsPlumb is required to calculate the position of every Connection during a paint cycle, rather than just Connections belonging to the element in motion.  These anchors were designed for use with the StateMachine connector that was also introduced in 1.3.4, but will work with any combination of Connector and Endpoint.<br/><br/></li> 
    -			</ul>  
    -					
    -			<h4><a id="staticAnchors">Static Anchors</a></h4>		
    -			jsPlumb has nine default anchor locations you can use to specify where the Connectors connect to elements: these are the four corners of an element,
    -			the center of the element, and the midpoint of each edge of the element:  
    -			<ul>
    -				<li class="bullet">TopCenter</li>
    -				<li class="bullet">TopRight</li>
    -				<li class="bullet">RightMiddle</li>
    -				<li class="bullet">BottomRight</li>
    -				<li class="bullet">BottomCenter</li>
    -				<li class="bullet">BottomLeft</li>
    -				<li class="bullet">LeftMiddle</li>
    -				<li class="bullet">TopLeft</li>
    -			</ul>
    -			
    -Each of these string representations is just a wrapper around the underlying array-based syntax  [x, y, dx, dy], where x and y are coordinates in the interval [0,1] specifying the position of the
    -anchor, and dx and dy are coordinates in the interval [-1, 1] specifying the orientation of the curve incident to the anchor. For example, [0, 0.5, -1, 0] defines a "LeftCenter" anchor with a connector curve that emanates leftward from the anchor. Similarly, [0.5, 0, 0, -1] defines a "CenterTop" anchor with a connector curve emanating upwards. 	
    -
    -
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:"BottomCenter", ... }); 
    -</pre>
    -</div>
    -is identical to:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:[ 0.5, 1, 0, 1 ], ... }); 
    -</pre>
    -</div>		
    -In addition to supplying the location and orientation of an anchor, you can optionally supply two more parameters that define an offset in pixels from the given location.  Here's the anchor specified above, but with a 50 pixel offset below the element in the y axis:	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:[ 0.5, 1, 0, 1, 0, 50 ], ... }); 
    -</pre>
    -</div>		
    -
    -		<h4><a id="dynamicAnchors">Dynamic Anchors</a></h4>
    -	These are Anchors that can be positioned in one of a number of locations, choosing the one that is most appropriate each time something moves or is painted in the UI.
    -	<p>
    -	There is no special syntax to creating a DynamicAnchor; you just provide an array of individual Static Anchor specifications, eg:
    -	</p>
    -<div class="code">
    -<pre>
    -var dynamicAnchors = [ [ 0.2, 0, 0, -1 ],  [ 1, 0.2, 1, 0 ], 
    -			   [ 0.8, 1, 0, 1 ], [ 0, 0.8, -1, 0 ] ];
    -
    -jsPlumb.connect({...., anchor:dynamicAnchors, ... }); 			   
    -</pre>
    -</div>
    -<h4>	Default Dynamic Anchor</h4>
    -jsPlumb provides a dynamic anchor called "AutoDefault" that chooses from TopCenter, RightMiddle, BottomCenter and LeftMiddle:
    -<div class="code">
    -<pre>jsPlumb.connect({...., anchor:"AutoDefault", ... });</pre>
    -</div>
    -
    -<h4>	Location Selection</h4>
    -The initial implementation of the algorithm that decides which location to choose just calculates which location is closest to the center of the 
    -other element in the connection.  It is possible that future versions of jsPlumb could support more sophisticated choice algorithms, if the need arose.
    -<h4>Draggable Connections</h4>
    -Dynamic Anchors and Draggable Connections can interoperate: jsPlumb locks the position of a dynamic anchor when you start to drag a connection from it,
    -and unlocks it once the connection is either established or abandoned. At that point you may see the position of the dynamic anchor change, as jsPlumb
    -optimises the connection.  <p>You can see this behaviour in the <a href="../html/jquery/draggableConnectorsDemo.html" target="_blank">draggable connections</a> demonstration, when
    -you drag a connection from the blue endpoint on window 1 to the blue endpoint on window 3 - the connection is established and then window 1's blue
    -endpoint jumps down to a location that is closer to window 3.</p>
    -
    -
    -	<h4><a id="continuousAnchors">Continuous Anchors</a></h4>
    -	As discussed above, these are anchors whose positions are calculated by jsPlumb according to the orientation between elements in a Connection, and also how
    -	many other Continuous anchors happen to be sharing the element.  You specify that you want to use Continuous anchors using the string syntax you would use
    -	to specify one of the default Static Anchors, for example:
    -	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:someDiv,
    -	target:someOtherDiv,
    -	anchor:"Continuous"
    -});
    -</pre>
    -</div>
    -
    -Note in this example I specified only "anchor", rather than "anchors" - jsPlumb will use the same spec for both anchors.  But I could have said this:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:someDiv,
    -	target:someOtherDiv,
    -	anchors:["BottomCenter", "Continuous"]
    -});
    -</pre>
    -</div>	 	  
    -..which would have resulted in the source element having a Static Anchor at BottomCenter.  In practise, though, it seems the Continuous Anchors work best if both 
    -elements in a Connection are using them.
    -<p>Note also that Continuous Anchors can be specified on <strong>addEndpoint</strong> calls:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint(someDiv, {
    -	anchor:"Continuous",
    -	paintStyle:{ fillStyle:"red" }
    -});
    -</pre>
    -</div>	 	
    -...and in <strong>makeSource</strong>/<strong>makeTarget</strong>:
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource(someDiv, {
    -	anchor:"Continuous",
    -	paintStyle:{ fillStyle:"red" }
    -});
    -
    -jsPlumb.makeTarget(someDiv, {
    -	anchor:"Continuous",
    -	paintStyle:{ fillStyle:"red" }
    -});
    -</pre>
    -</div>	 
    -... and in the jsPlumb defaults:
    -<div class="code">
    -<pre>
    -jsPlumb.Defaults.Anchor = "Continuous";
    -</pre>
    -</div>  
    -					</div>		
    -												
    -<div class="section">
    -		<h4><a id="definitions">Connector, Endpoint &amp; Overlay Definitions</a></h4>
    -Before we discuss Connector, Endpoints and Overlays, a quick word on definitions:  whenever you need to define a Connector, Endpoint or Overlay, you must use a "definition" of it, rather than constructing one directly.  This definition can be either a string that nominates the artifact you want to create:
    -		<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:"Rectangle"
    -});
    -</pre>
    -		</div>
    -		or an array consisting of both the artifact's name and the arguments you want to pass to its constructor:
    -		<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Rectangle", { cssClass:"myEndpoint", width:30, height:10 } ]
    -});
    -</pre>
    -		</div>
    -There is also a three-argument method that allows you to specify two sets of parameters, which jsPlumb will merge together for you. The idea behind this is that you will often want to define common characteristics somewhere and reuse them across a bunch of different calls:
    -		<div class="code">
    -<pre>var common = {
    -	cssClass	:	"myCssClass",
    -	hoverClass	:	"myHoverClass"
    -};
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Rectangle", { width:30, height:10 }, common ]
    -});
    -</pre>
    -		</div>
    -	This syntax is supported for all Endpoint, Connector and Overlay definitions.  Here's an example using definitions for all three:	
    -		<div class="code">
    -<pre>var common = {
    -	cssClass:"myCssClass"
    -};
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Dot", { radius:5, hoverClass:"myEndpointHover" }, common ],
    -	connector:[ "Bezier", { curviness:100 }, common ],
    -	overlays: [
    -		[ "Arrow", { foldback:0.2 }, common ],
    -		[ "Label", { cssClass:"labelClass" } ]	
    -	]
    -});
    -</pre>
    -		</div>		
    -The allowed constructor parameters are different for each artifact you create, but every artifact takes a single JS object as argument, with the parameters as [key,value] pairs in that object.  See the relevant sections on <a href="#connectors">Connectors</a>, <a href="#endpoints">Endpoints</a> and <a href="#overlays">Overlays</a> for information on supported parameters.</a>
    -				
    -	</div>
    -
    -		<div class="section">
    -			<h3><a id="connectors">Connectors</a></h3>
    -			<p>Connectors are the lines that actually join elements of the UI.  jsPlumb has four connector implementations - a straight line, a Bezier curve, "flowchart", and "state machine".  The default connector is the Bezier curve.</p>
    -			<p>You optionally specify a Connector by setting the 'connector' property on a call to jsPlumb.connect, jsPlumb.addEndpoint(s), jsPlumb.makeSource or jsPlumb.makeTarget. If you do not supply a value for 'connector', the default will be used (see <a href="#defaults">defaults</a>).</p>
    -			<p>You specify Connectors using the syntax described above in <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions.</a> Allowed constructor values for each Connector type are described below:</p>		
    -			<h4><a id="bezierConnector">Bezier Connector</a></h4>
    -			<p>The Bezier Connector provides a cubic Bezier path between the two Endpoints. It supports a single constructor argument:</p>
    -			<p>
    -			<strong>curviness</strong> - Optional; defaults to 150. This defines the distance in pixels that
    -			the Bezier's control points are situated from the anchor points.  This does not mean that your
    -			connector will pass through a point at this distance from your curve.  It is a hint to how you want the
    -			curve to travel. Rather than discuss Bezier curves at length here, because they are a complex topic,
    -			refer instead to <a href="http://en.wikipedia.org/wiki/B%C3%A9zier_curve" target="_blank">Wikipedia.</a>
    -			</p>
    -			<h4><a id="straightConnector">Straight Connector</a></h4>
    -			<p>The Straight Connector draws a straight line between the two endpoints. No constructor arguments are supported; use the <strong>endpointStyle</strong> and/or <strong>cssClass</strong>/<strong>connectorClass</strong> to a <strong>connect</strong>, <strong>addEndpoint</strong>, <strong>makeSource</strong> or <strong>makeTarget</strong> call to control the appearance of one of these Connectors.  
    -			</p>
    -			<h4><a id="flowchartConnector">Flowchart Connector</a></h4>
    -			<p>This type of Connector draws a connection that consists of a series of vertical or horizontal segments - the classic flowchart look. Two constructor arguments are supported:</p>
    -			<strong>stub</strong> - this is the minimum length, in pixels, of the initial stub that emanates from an Endpoint.  This is an optional parameter, and can be either an integer, which specifies the stub fo each end of the connector, or an array of two integers, specifying the stub for the [source, target] endpoints in the connection.  This parameter is optional, and defaults to an integer with value 30 pixels.<br/>
    -			<strong>gap</strong> - optional, defaults to 0 pixels. Lets you specify a gap between the end of the connector and the elements to which it is attached.
    -			<p>This Connector supports Connection that start and end on the same element.</p>
    -			<h4><a id="stateMachineConnector">State Machine Connector</a></h4>
    -			<p>This Connector draws slightly curved lines (they are actually quadratic Bezier curves), similar to the State Machine connectors you may have seen in software like GraphViz.  Connections in which some element is both the source and the target (so-called 'loopback') are supported by these Connectors (as they are with Flowchart Connectors); in this case you get a circle. Supported parameters are:
    -			</p>
    -			<p>
    -			<strong>margin</strong> - Optional; defaults to 5.  Defines the distance from the element that the connector begins/ends.
    -			</p>
    -			<p>
    -			<strong>curviness</strong> - Optional, defaults to 10.  This has a similar effect to the curviness parameter on Bezier curves.
    -			</p>
    -			<p>
    -			<strong>proximityLimit</strong> - Optional, defaults to 80.  The minimum distance between the two ends of the Connector before it paints itself as 
    -			a straight line rather than a quadratic Bezier curve.
    -			</p>    
    -			
    -		</div>
    -		
    -			<div class="section">
    -			<h3><a id="endpoints">Endpoint Types</a></h3>
    -			<p>An Endpoint is the UI component that marks the location of an Anchor, ie. the place where a Connector joins an
    -			element.  jsPlumb comes with four Endpoint implementations - <strong>Dot</strong>, <strong>Rectangle</strong>, <strong>Blank</strong> and <strong>Image</strong>. You optionally specify Endpoint properties using the
    -			<strong>endpoint</strong> parameter in a call to jsPlumb.connect, jsPlumb.addEndpoint, jsPlumb.makeSource or jsPlumb.makeTarget.
    -			</p>  
    -				<p>As with Connectors and Overlays, you specify Endpoints using the syntax described above in <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions.</a></p>
    -			
    -			The four available Endpoint types, and their constructor arguments, are as follows:
    -					<h4><a id="dotEndpoint">Dot Endpoint</a></h4>
    -					This Endpoint draw a dot on the screen. It supports three constructor parameters:
    -					<p>
    -						<strong>radius</strong> - Optional; defaults to 10 pixels. Defines the radius of the dot.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="rectangleEndpoint">Rectangle Endpoint</a></h4>
    -						Draws a rectangle. Supported constructor parameters are:
    -					<p>
    -						<strong>width</strong> - Optional; defaults to 20 pixels. Defines the width of the rectangle.
    -					</p>
    -					<p>
    -						<strong>height</strong> - Optional; defaults to 20 pixels. Defines the height of the rectangle.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="imageEndpoint">Image Endpoint</a></h4>
    -						Draws an image from a given URL.  This Endpoint supports three constructor parameters:  
    -					<p>
    -						<strong>src</strong> - Required.  Specifies the URL of the image to use.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="blankEndpoint">Blank Endpoint</a></h4>
    -						Does not draw anything visible to the user.  As discussed previously, this Endpoint is probably not what you want if you need your users to be able to drag existing Connections - for that, use a Rectangle or Dot Endpoint ans assign to it a CSS class that causes it to be transparent.
    -																			
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="overlays">Overlay Types</a></h3>
    -			<p>
    -			Overlays are UI elements that are painted onto connections, such as labels or arrows.
    -			jsPlumb comes with four defaults:
    -			</p>
    -			<ul>
    -				<li><strong>Arrow</strong> - a configurable arrow that is painted at some point along the connector.  You can control the length and width of the Arrow, the 'foldback' point - a point the tail points fold back into, and the direction (allowed values are 1 and -1; 1 is the default and means point in the direction of the connection)</li>
    -				<li><strong>Label</strong> - a configurable label that is painted at some point along the connector.</li>
    -				<li><strong>PlainArrow</strong> - an Arrow shaped as a triangle, with no foldback.</li>
    -				<li><strong>Diamond</strong> - as the name suggests, a diamond.</li>
    -			</ul> 
    -			The last two are actually just configured instances of the generic Arrow overlay (see examples). 
    -			
    -			<p>All Overlays support these two methods for getting/setting their location (location is either a number between 0 and 1 inclusive, indicating some point along the path inscribed by the associated Connector, or an absolute number of pixels, where negative values mean distance from the target of the connection):</p>
    -			<ul>
    -				<li class="bullet"><strong>getLocation</strong> - returns the current location</li>
    -				<li class="bullet"><strong>setLocation</strong> - sets the current location; valid values are decimals in the range 0-1 inclusive, or an integer less than 0 (to express distance from the target), or an integer greater than 1 (to express distance from the source)</li> 
    -			</ul>
    -			
    -			<p>You can specify one or more overlays when making a call to jsPlumb.connect, jsPlumb.addEndpoint or jsPlumb.makeSource (but not jsPlumb.makeTarget; overlays
    -			are always derived from what the source of a Connection defines)  The three cases are discussed below:</p>
    -			1. Specifying one or more overlays on a jsPlumb.connect call. In this example we'll create an Arrow with the default options for an Arrow, and
    -			a label with the text "foo":
    -			<div class="code">
    -			<pre>jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});			
    -</pre>
    -			</div>
    -			This connection will have an arrow located halfway along it, and the label "foo" one quarter of the way along.  Notice the <strong>id</strong>
    -			parameter; it can be used later if you wish to remove the Overlay or change its visibility (see below).<br/><br/> 
    -			Another example, this time with an absolute location of 50 pixels from the source:
    -			<div class="code">
    -			<pre>jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:50 }, id:"myLabel" ]
    -	],
    -	...
    -});			
    -</pre>
    -			</div>
    -			
    -			2. Specifying one or more overlays on a jsPlumb.addEndpoint call.  Note in this example that we use the parameter 'connectorOverlays' and not
    -			'overlays' as in the last example.  This is because 'overlays' would refer to Endpoint Overlays:
    -			<div class="code">
    -			<pre>jsPlumb.addEndpoint("someDiv", {
    -	...
    -	connectorOverlays:[ 
    -		[ "Arrow", { width:10, length:30, location:1, id:"arrow" } ], 
    -		[ "Label", { label:"foo", id:"label" } ]
    -	],
    -	...
    -});</pre>
    -			</div>	
    -			This connection will have a 10x30 Arrow located right at the head of the connection, and the label "foo" located at the halfway point. Again,
    -			note that <strong>id</strong> parameter passed in to each Overlay spec.<br/><br/>
    -			
    -			3. Specifying one or more overlays on a jsPlumb.makeSource call.  Note in this example that we again use the parameter 'connectorOverlays' and not
    -			'overlays'.  The 'endpoint' parameter to jsPlumb.makeSource supports everything you might pass to the second argument of a jsPlumb.addEndpoint call:
    -			<div class="code">
    -			<pre>jsPlumb.makeSource("someDiv", {
    -	...
    -	endpoint:{
    -		connectorOverlays:[ 
    -			[ "Arrow", { width:10, length:30, location:1, id:"arrow" } ], 
    -			[ "Label", { label:"foo", id:"label" } ]
    -		]
    -	}
    -	...
    -});</pre>
    -			</div>	
    -			This connection will have a 10x30 Arrow located right at the head of the connection, and the label "foo" located at the halfway point. Again,
    -			note that <strong>id</strong> parameter passed in to each Overlay spec.<br/><br/>
    -			
    -			 	
    -					<h4><a id="arrowOverlay">Arrow Overlay</a></h4>
    -					<p>This overlay draws an arrow, using four points: the head and two tail points, and a 'foldback' point, which permits the tail
    -					of the arrow to be indented. Available constructor arguments for this Overlay are </p>
    -					<ul>
    -					<li><strong>width</strong> - width of the tail of the arrow</li>
    -					<li><strong>length</strong> - distance from the tail of the arrow to the head</li>
    -					<li><strong>location</strong> - where, either as a proportional value from 0 to 1 inclusive, or as an absolute value (negative values mean distance from target; positive values greater than 1 mean distance from source) the Arrow should appear on the Connector</li>
    -					<li><strong>direction</strong> - which way to point. Allowed values are 1 (the default, meaning forwards) and -1, meaning backwards </li>
    -					<li><strong>foldback</strong> - how far along the axis of the arrow the tail points foldback in to. Default is 0.623.</li>
    -					<li><strong>paintStyle</strong> - a style object in the form used for paintStyle values for Endpoints and Connectors</li>
    -					</ul>
    -					<h4><a id="plainArrowOverlay">PlainArrow Overlay</a></h4>
    -					<p>This is just a specialized instance of Arrow in which jsPlumb hardcodes 'foldback' to 1, meaning the tail of the Arrow is a flat edge.  All of the constructor parameters from Arrow apply for PlainArrow.</p>
    -					<h4><a id="diamondOverlay">Diamond Overlay</a></h4>
    -					<p>This is a specialized instance of Arrow in which jsPlumb hardcodes 'foldback' to 2, meaning the Arrow turns into a Diamond.  All of the constructor parameters from Arrow apply for Diamond.</p>
    -					<h4><a id="labelOverlay">Label Overlay</a></h4>
    -					This provides a text label to decorate Connectors with.  The available constructor arguments are:
    -					<ul>
    -						<li><strong>label</strong> - The text to display.  You can provide a function here instead of plain text: it is passed the Connection as an argument, and it should return a String.</li>
    -						<li><strong>cssClass</strong> - Optional css class to use for the Label.  This is now preferred over using the 'labelStyle' parameter.</li>
    -						<li><strong>labelStyle</strong> - (deprecated, use cssClass instead) Optional arguments for the label's appearance.  Valid entries in this JS object are:
    -							<ul>
    -								<li><em>font</em> - a font string in a format suitable for the Canvas element</li>
    -								<li><em>fillStyle</em> - the color to fill the label's background with. Optional.</li>
    -								<li><em>color</em> - the color of the label's text. Optional.</li>
    -								<li><em>padding</em> - optional padding for the label. This is expressed as a proportion of the width of the label, not in pixels or ems.</li>
    -								<li><em>borderWidth</em> - optional width in pixels for the label's border. Defaults to 0.</li>
    -								<li><em>borderStyle</em> - optional. The color to paint the border, if there is one.</li>
    -								
    -							</ul>
    -						
    -						</li>
    -						<li><strong>location</strong> - As for Arrow Overlay.  Where, either proportionally from 0 to 1 inclusive, or as an absolute offset from either source or target, the label should appear.</li>
    -					</ul>
    -
    -					The Label overlay offers two methods - getLabel and setLabel - for accessing/manipulating its content dynamically:
    -<div class="code">
    -<pre>
    -var c = jsPlumb.connect({
    -	source:"d1", 
    -	target:"d2", 
    -	overlays:[
    -		[ "Label", {label:"FOO", id:"label"}]
    -	]	
    -});					
    -
    -...
    -
    -var label = c.getOverlay("label");
    -console.log("Label is currently", label.getLabel());
    -label.setLabel("BAR");
    -console.log("Label is now", label.getLabel());
    -</pre>
    -</div>
    -In this example you can see that the Label Overlay is assigned an id of "label" in the connect call, and then retrieved using that id in the call to Connection's getOverlay method.
    -<p>Both Connections and Endpoints support Label Overlays, and because changing labels is quite a common operation, <strong>setLabel</strong> and <strong>getLabel</strong> methods have been added to these objects:
    -<div class="code">
    -<pre>
    -var conn = jsPlumb.connect({
    -	source:"d1", 
    -	target:"d2",
    -	label:"FOO"
    -});					
    -
    -...
    -
    -console.log("Label is currently", conn.getLabel());
    -conn.setLabel("BAR");
    -console.log("Label is now", conn.getLabel());
    -</pre>
    -</div>
    -These methods support passing in a Function instead of a String, and jsPlumb will create a label overlay for you if one does not yet exist when you call getLabel:
    -<div class="code">
    -<pre>
    -var conn = jsPlumb.connect({
    -	source:"d1", 
    -	target:"d2"
    -});					
    -
    -...
    -
    -conn.setLabel(function(c) {
    -	var s = new Date();
    -	return s.getTime() + "milliseconds have elapsed since 01/01/1970";
    -});
    -console.log("Label is now", conn.getLabel());
    -</pre>
    -</div>
    -					
    -					<h4><a id="hideshowOverlay">Hiding/Showing Overlays</a></h4>
    -					<p>You can control the visibility of Overlays using the <strong>setVisible</strong> method of Overlays themselves, or with
    -					<strong>showOverlay(id)</strong> or <strong>hideOverlay(id)</strong> on a Connection.</p>
    -					<p>Remember the <strong>id</strong> parameter that we specified in the examples above?  This can be used to retrieve the Overlay
    -					from a Connection:</p>
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -var overlay = connection.getOverlay("myLabel");
    -// now you can hide this Overlay:
    -overlay.setVisible(false);
    -// there are also hide/show methods:
    -overlay.show();
    -overlay.hide();
    -
    -</pre>					
    -					</div>
    -					<p>However, Connection and Endpoint also have two convenience methods you could use instead:
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:-30 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -connection.hideOverlay("myLabel");
    -
    -// more time passes
    -
    -connection.showOverlay("myLabel");
    -
    -</pre>					
    -					</div>
    -					
    -		<h4><a id="hideshowOverlay">Removing Overlays</a></h4>
    -		<p>Connection and Endpoint also have a <strong>removeOverlay</strong> method, that does what you might expect:
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -connection.removeOverlay("myLabel");
    -
    -
    -</pre>					
    -					</div>							
    -	
    -		</div>
    -	
    -			
    -			<div class="section">
    -			<h3><a id="defaults">Defaults</a></h3>
    -			The easiest way to set a look and feel for your plumbing is to override the defaults that jsPlumb uses. If you
    -			do not do this you are forced to provide your overridden values on every call.  Every argument to the connect and addEndpoint methods has an
    -			associated default value in jsPlumb.<br/><br/>
    -
    -			The defaults that ship with jsPlumb are stored in <em>jsPlumb.Defaults</em>, which is a Javascript object.  Valid entries, and their initial values, are:
    -
    -			<div class="code">
    -<pre>
    -Anchor : "BottomCenter",
    -Anchors : [ null, null ],
    -Connector : "Bezier",
    -ConnectionOverlays : [ ],
    -DragOptions : { },
    -DropOptions : { },
    -Endpoint : "Dot",
    -EndpointOverlays : [ ],
    -Endpoints : [ null, null ],
    -EndpointStyle : { fillStyle : null },
    -EndpointStyles : [ null, null ],
    -EndpointHoverStyle : null,
    -EndpointHoverStyles : [ null, null ],
    -HoverPaintStyle : null,
    -LabelStyle : { color : "black" },
    -LogEnabled : false,
    -Overlays : [ ],
    -MaxConnections : 1,
    -MouseEventsEnabled : true,
    -PaintStyle : { lineWidth : 8, strokeStyle : "#456" },
    -RenderMode : "svg",
    -Scope : "_jsPlumb_DefaultScope"
    -</pre>
    -			</div>
    -			<p>
    -			Note that in EndpointStyle, the default fillStyle is 'null'.  This instructs jsPlumb to use the strokeStyle
    -from the attached connector to fill the endpoint.</p>
    -<p>Note also that you can specify either or both (or neither) of 'EndpointStyle' and 'EndpointStyles'.  This allows you to specify a different
    -end point for each end of a connection.  'Endpoint' and 'Endpoints' use the same concept.  jsPlumb will look first in the
    -individual endpoint/endpoint style arrays, and then fall back to the single default version.</p>
    -
    -			you can override these defaults by including this in a script somewhere:
    -			<div class="code">
    -<pre>
    -jsPlumb.Defaults.PaintStyle = {
    -	lineWidth:13,
    -	strokeStyle: 'rgba(200,0,0,100)'
    -}
    -
    -jsPlumb.Defaults.DragOptions = { cursor: "crosshair" };
    -
    -jsPlumb.Default.Endpoints = [ [ "Dot", 7 ], [ "Dot", 11 ] ];
    -
    -jsPlumb.Defaults.EndpointStyles = [{ fillStyle:"#225588" }, { fillStyle:"#558822" }];</pre>
    -			</div>
    -			after the jsPlumb script has been loaded of course!  Here we have specified the following default behaviour:
    -			<ul>
    -				<li>- connectors are 13 pixels wide and painted with a semi-transparent red line</li>
    -				<li>- when dragging an element the crosshair cursor is used</li>
    -				<li>- the source endpoint is a dot of radius 7; the target endpoint is a dot of radius 11</li>
    -				<li>- the source endpoint is blue; the target endpoint is green</li>
    -			</ul>
    -
    -			<h5>jsPlumb.importDefaults</h5>
    -From version 1.4.0 onwards there is a helper method called <strong>importDefaults</strong> that allows you to import a set of values with just one call to jsPlumb.  Here's the previous example using importDefaults:
    -			<div class="code">
    -<pre>
    -jsPlumb.importDefaults({
    -	PaintStyle : {
    -		lineWidth:13,
    -		strokeStyle: 'rgba(200,0,0,100)'
    -	},
    -	DragOptions : { cursor: "crosshair" },
    -	Endpoints : [ [ "Dot", 7 ], [ "Dot", 11 ] ],
    -	EndpointStyles : [{ fillStyle:"#225588" }, { fillStyle:"#558822" }]
    -});</pre>
    -			</div>
    -
    -		</div> 					
    -
    -<div class="section">
    -			<h3><a id="simpleConnections">Connections</a></h3>
    -			<h4><a id="simpleConnections">Programmatic Connections</a></h4>
    -The most simple connection you can make with jsPlumb looks like this:
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({source:"element1", target:"element2"});</pre>			
    -			</div>
    -
    -		In this example we have created a Connection from 'element1' to 'element2'.  Remember that a Connection in jsPlumb consists of two Endpoints,
    -		a Connector, and zero or more Overlays.  But this call to 'connect' supplied none of those things, so jsPlumb uses the default values wherever it needs to.
    -		In this case, default values have been used for the following: 
    -		<ul>
    -			<li>- The type and appearance of each Endpoint in the Connection. jsPlumb's default for this is the "Dot" endpoint, of radius 10, with fill color "#456".</li>
    -			<li>- The Anchors that define where the connection's Endpoints appear on each element. The jsPlumb default is "BottomCenter"</li>			
    -			<li>- Whether or not each Endpoint can be a source or target for new Connections. The default is false.</li>
    -			<li>- The type and appearance of the Connection's Connector. The default is a "Bezier" connector of line width 8, and color "#456".</li>
    -		</ul>
    -		<p>
    -		So this call will result in an 8px Bezier, colored "#456", from the bottom center of 'element1' to the bottom center of 'element2', and each
    -		Endpoint will be a 10px radius Dot Endpoint, colored "#456".
    -		</p>
    -		<p>
    -		Let's beef up this call a little and tell jsPlumb what sort of Endpoints we want, and where we want them:		
    -		</p>
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:"element1", 
    -	target:"element2",
    -	anchors:["RightMiddle", "LeftMiddle" ],
    -	endpoint:"Rectangle",
    -	endpointStyle:{ fillStyle: "yellow" }
    -});
    -</pre>			
    -			</div>
    -			
    -		<p>This is what we have told jsPlumb we want this time:</p>
    -		<ul>
    -			<li><strong>anchors</strong> - this array tells jsPlumb where the source and target Endpoints should be located on their parent elements. In this case,
    -			we use the shorthand syntax to name one of jsPlumb's default anchors; you can also specify custom locations (see <a href="#anchors">anchors</a>).
    -			Instead of <strong>anchors</strong> you can use <strong>anchor</strong>, if you want the source and target Endpoints to be located at the
    -			same place on their parent elements.			<br/>
    -			</li>
    -			<li>
    -				<strong>endpoint</strong>  - this tells jsPlumb to use the "Rectangle" Endpoint for both the source and target of the Connection.  As with anchors, 
    -				<strong>endpoint</strong> has a plural version that allows you to specify a different Endpoint for each end of the Connection.<br/>  
    -			</li>
    -			<li>
    -				<strong>endpointStyle</strong> - this is the definition of the appearance of the Endpoint you specified above.  Again, there is a
    -				plural equivalent of this that allows you to specify a different style for each end of the Connection. For more information about
    -				allowed values for this value, see <a href="#paintstyles">Connector, Endpoint & Overlay Styles</a>. 
    -			</li>
    -		</ul>		
    -		
    -		<h5>Reusing common settings between jsPlumb.connect calls</h5>
    -		<p>
    -			A fairly common situation you will find yourself in is wanting to create a bunch of Connections that have only minor differences
    -			between them.  To support that, <strong>jsPlumb.connect</strong> takes an optional second argument. For example:			 
    -		</p>
    -		<div class="code">
    -<pre>
    -var common = {
    -	anchors:[ "BottomCenter", "TopCenter" ],
    -	endpoints:["Dot", "Blank" ]
    -};
    -
    -jsPlumb.connect({ source:"someElement", target:"someOtherElement" }, common);
    -
    -jsPlumb.connect({ source:"aThirdElement", target:"yetAnotherElement" }, common);
    -
    -</pre>			
    -			</div>		
    -			
    -		<h5>Endpoints created by jsPlumb.connect</h5>
    -		<p>If you supply an element id or selector for either the source or target , jsPlumb.connect will automatically create an Endpoint on the 
    -		given element.  These automatically created Endpoints are not marked as drag source or targets, and cannot
    -		be interacted with.  For some situations this behaviour is perfectly fine, but for more interactive UIs you should set things up using the drag and drop
    -		method discussed below.</p>
    -		<p>Given that jsPlumb.connect creates its own Endpoints in some circumstances, in order to avoid leaving orphaned Endpoints around the place, if the Connection is subsequently
    -		deleted, these created Endpoints are deleted too.  Should you want to, you can override this behaviour by setting <strong>deleteEndpointsOnDetach</strong> to
    -		false in the connect call:</p>
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({ 
    -	source:"aThirdElement", 
    -	target:"yetAnotherElement",
    -	deleteEndpointsOnDetach:false 
    -});
    -</pre>
    -</div>	
    -
    -	<h5>Detaching Connections</h5>
    -	By default, connections made with jsPlumb.connect will be detachable via the mouse.  You can prevent this by either setting an appropriate default value:
    -
    -		<div class="code">
    -<pre>
    -jsPlumb.importDefaults({ 
    -	...
    -	ConnectionsDetachable:false
    -	...
    -});
    -</pre>
    -</div>	
    -
    -	Or by specifying it on the connect call like this:
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({ 
    -	source:"aThirdElement", 
    -	target:"yetAnotherElement",
    -	detachable:false
    -});
    -</pre>
    -</div>		
    -								
    -					
    -		<h4><a id="draggableConnections">Connections using Drag and Drop</a></h4>
    -		To support drag and drop connections, you first need to set a few things up.  Every drag and drop connection needs at least a source
    -		Endpoint that the user can drag a connection from. Here's a simple example of how to create an Endpoint:
    -		<div class="code">
    -<pre>
    -var endpointOptions = { isSource:true };
    -var endpoint = jsPlumb.addEndpoint('elementId', endpointOptions);
    -</pre>		
    -</div>  	
    -	This Endpoint will act as a source for new Connections, and will use the jsPlumb defaults for its own appearance and that of any Connections that are drawn from it.							
    -	
    -		<h5>Tip: use the three-argument addEndpoint method for common data </h5>
    -		One thing that happens quite often is that you have an Endpoint whose appearance and behaviour is largely the
    -		same between usages on different elements, with just a few differences. 
    -		<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -	endpoint:"Rectangle",
    -	paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -	isSource:true,
    -	connectorStyle : { strokeStyle:"#666" },
    -	isTarget:true
    -};
    -</pre>		
    -</div>  		
    -		Notice there is no 'anchor' set.  Here we apply it to two elements, at a different location in each:
    -		<div class="code">
    -<pre>
    -jsPlumb.addEndpoint('element1', { anchor:"BottomCenter" }, exampleGreyEndpointOptions)); 
    -
    -jsPlumb.addEndpoint('element2', { anchor:"TopCenter" }, exampleGreyEndpointOptions));
    -</pre>		
    -</div>
    -
    -<p>
    -Now that you have a source Endpoint, you need to either create a target Endpoint on some element, or notify jsPlumb that you wish to make an
    -entire element a drop target.  Let's look at how to attach a target Endpoint first:
    -</p>
    -
    -		<div class="code">
    -<pre>var endpointOptions = { isTarget:true, endpoint:"Rectangle", paintStyle:{ fillStyle:"gray" } };
    -var endpoint = jsPlumb.addEndpoint("otherElementId", endpointOptions);
    -</pre>		
    -		</div>
    -		<p>
    -		This Endpoint, a gray rectangle, has declared that it can act as a drop target for Connections. 
    -</p>
    -<p>
    -jsPlumb also supports marking entire elements as drag sources or drag targets, using the <strong>makeSource</strong> and <strong>makeTarget</strong> methods.  This is a useful function for certain applications.  Each of these methods takes an Endpoint declaration which defines the Endpoint that jsPlumb will create after a Connection has been attached.  Let's take a look at makeTarget first:
    -</p>
    -<h4><a id="sourcesAndTargets">Elements as sources &amp; targets</a></h4>
    -jsPlumb also supports turning entire elements into Connection sources and targets, using the methods <strong>makeSource</strong> and <strong>makeTarget</strong>.  With these methods you mark an element as a source or target, and provide an Endpoint specification for jsPlumb to use when a Connection is established.  makeSource also gives you the ability to mark some child element as the place from which you wish to drag Connections, but still have the Connection on the main element after it has been established.
    -<p>These methods honour the jsPlumb defaults - if, for example you set up the default Anchors to be this:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.Defaults.Anchors = [ "LeftMiddle", "BottomRight" ];
    -</pre>
    -</div>
    -... and then used makeSource and makeTarget without specifying Anchor locations, jsPlumb would use "LeftMiddle" for the makeSource element and "BottomRight" for the makeTarget element.
    -<p>
    -A further thing to note about makeSource and makeTarget is that any prior calls to one of these methods is honoured by subsequent calls to jsPlumb.connect. This helps when you're building a UI that uses this functionality at runtime but which loads some initial data, and you want the statically loaded data to have the same appearance and behaviour as dynamically created connections (obviously quite a common use case).  
    -<p>For example:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -	anchor:"Continuous",
    -	endpoint:["Rectangle", { width:40, height:20 }]
    -});
    -
    -...
    -
    -jsPlumb.connect({source:"el1", target:"el2"});
    -</pre>
    -</div>
    -In this example, the source of the connection will be a Rectangle of size 40x20, having a Continuous Anchor.
    -<p>You can override this behaviour by setting a <strong>newConnection</strong> parameter on the connect call:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -	anchor:"Continuous",
    -	endpoint:["Rectangle", { width:40, height:20 }]
    -});
    -
    -...
    -
    -jsPlumb.connect({ source:"el1", target:"el2", newConnection:true });
    -</pre>
    -</div>
    -<h5><a id="makeTarget">jsPlumb.makeTarget</a></h5>
    -<p>
    -This method takes two arguments, the first of which specifies some element (or list of elements); the second specifies the Endpoint you wish to create on that element whenever a Connection is established on it.  In this example we will use the exact same target Endpoint we used before - the gray rectangle - but we will tell jsPlumb that the element "aTargetDiv" will be the drop target:
    -</p>
    -		<div class="code">
    -<pre>var endpointOptions = { 
    -  isTarget:true, 
    -  maxConnections:5,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" } 
    -};
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -<p>
    -	The allowed values in 'endpointOptions' are identical for both the <strong>jsPlumb.addEndpoint</strong> and <strong>jsPlumb.makeTarget</strong> methods, but makeTarget supports an extended 
    -	anchor syntax that allows you more control over the location of the target endpoint.  This is discussed below.	
    -</p>		
    -<p>Notice in the 'endpointOptions' object above there is a 'isTarget' parameter set - this may seem incongruous, since you know you're going to make some element a target.  Remember that the endpointOptions object is the information jsPlumb will use to create an Endpoint on the given target element each time a Connection is established to it. It takes the exact same format as you would pass to addEndpoint; makeTarget is essentially a deferred addEndpoint call followed by a connect call.  So in this case, we're telling jsPlumb that any Endpoints it happens to create on some element that was configured by the makeTarget call are themselves Connection targets.
    -</p>
    -<h5>Unique Endpoint per Target </h5>
    -<p>jsPlumb will create a new Endpoint using the supplied information every time a new Connection is established on the target element, by default, but you can override this behaviour and tell jsPlumb that it should create at most one Endpoint, which it should attempt to use for subsequent Connections:
    -</p>
    -		<div class="code">
    -<pre>var endpointOptions = { 
    -  isTarget:true, 
    -  uniqueEndpoint:true,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" } 
    -};
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -Here, the <strong>uniqueEndpoint</strong> parameter tells jsPlumb that there should be at most one Endpoint on this element.  Notice that 'maxConnections' is not set: the default is 1, so in this setup we have told jsPlumb that "aTargetDiv" can receive one Connection and no more.
    -<h5>Deleting Endpoints on Detach</h5>
    -By default, any Endpoints created using makeTarget have <strong>deleteEndpointsOnDetach</strong> set to true, which means that once all Connections to that Endpoint are removed, the Endpoint is deleted.  You can override this by setting the flag to true on the makeTarget call:
    -		<div class="code">
    -<pre>var endpointOptions = { 
    -  isTarget:true, 
    -  maxConnections:5,
    -  uniqueEndpoint:true,
    -  deleteEndpointsOnDetach:false,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" } 
    -};
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -In this setup we have told jsPlumb to create an Endpoint the first time "aTargetElement" is connected to, and to not delete it even if there are no longer any Connections to it.  The created Endpoint will be reused for subsequent Connections, and can support a maximum of 5.
    -
    -<h5>Detaching connections made with the mouse</h5>
    -As with jsPlumb.connect, connections made with the mouse after setting up Endpoints with one of the functions we've just covered will be, by default, detachable.  You can prevent this in the jsPlumb defaults, as previously mentioned:
    -		<div class="code">
    -<pre>jsPlumb.importDefaults({ 
    -	...
    -	ConnectionsDetachable:false,
    -	...
    -});
    -</pre>		
    -		</div>
    -
    -	And you can also set this on a per-endpoint (or source/target) level, like in these examples:
    -
    -		<div class="code">
    -<pre>jsPlumb.addEndpoint("someElementId", { 
    -	connectionsDetachable:false	
    -});
    -
    -jsPlumb.makeSource("someOtherElement", {
    -	...
    -	connectionsDetachable:false,
    -	...
    -});
    -
    -jsPlumb.makeTarget("yetAnotherElement", {
    -	...
    -	connectionsDetachable:false,
    -	...
    -});
    -</pre>		
    -		</div>
    -
    -Note that in the jsPlumb defaults, by convention each word in a parameter is capitalised ("ConnectionsDetachable"), whereas for a call to one of these methods, we use camel case ("connectionsDetachable").
    -
    -
    -<h5>Target Anchors positions with makeTarget</h5>
    -<p>When using the makeTarget method, jsPlumb allows you to provide a callback function to be used to determine
    -the appropriate location of a target anchor for every new Connection dropped on the given target.  It may be the
    -case that you want to take some special action rather than just relying on one of the standard anchor mechanisms.
    -</p>
    -<p>This is achieved through an extended anchor syntax (note that this syntax is <strong>not supported</strong> in
    -the <strong>jsPlumb.addEndpoint</strong> method) that supplies a "positionFinder" to the anchor specification.  jsPlumb 
    -provides two of these by default; you can register your own on jsPlumb and refer to them by name, or just 
    -supply a function.  Here's a few examples:</p>
    -Instruct jsPlumb to place the target anchor at the exact location at which the mouse button was released on the
    -target element. Note that you tell jsPlumb the anchor is of type "Assign", and you then provide a "position"
    -parameter, which can be the name of some position finder, or a position finder function itself:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {
    -  anchor:[ "Assign", { 
    -    position:"Fixed"
    -  }]
    -});
    -</pre>
    -</div>
    -<p><strong>Fixed</strong> is one of the two default position finders provided by jsPlumb. The other is <strong>Grid</strong>:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {
    -  anchor:[ "Assign", { 
    -    position:"Grid",
    -    grid:[3,3]
    -  }]
    -});
    -</pre>
    -</div>
    -The Grid position finder takes a "grid" parameter that defines the size of the grid required. [3,3] means
    -3 rows and 3 columns.
    -<h5>Supplying your own position finder</h5>
    -<p>To supply your own position finder to jsPlumb you first need to create the callback function. First let's take a look at what the source code for the Grid position finder looks like:</p>
    -<div class="code">
    -<pre>
    -function(eventOffset, elementOffset, elementSize, constructorParams) {
    -  var dx = eventOffset.left - elementOffset.left, dy = eventOffset.top - elementOffset.top,
    -      gx = elementSize[0] / (constructorParams.grid[0]), 
    -      gy = elementSize[1] / (constructorParams.grid[1]),
    -      mx = Math.floor(dx / gx), my = Math.floor(dy / gy);
    -
    -  return [ ((mx * gx) + (gx / 2)) / elementSize[0], ((my * gy) + (gy / 2)) / elementSize[1] ];
    -}
    -</pre>
    -</div>
    -The four arguments are:
    -<ul>
    -	<li class="bullet"><strong>eventOffset</strong> - Page left/top where the mouse button was released (a JS object containing left/top members like you get from a jQuery offset call)</li>
    -	<li class="bullet"><strong>elementOffset</strong> - JS offset object containing offsets for the element on which the Connection is to be created</li>
    -	<li class="bullet"><strong>elementSize</strong> - [width, height] array of the dimensions of the element on which the Connection is to be created</li>
    -	<li class="bullet"><strong>constructorParams</strong> - the parameters that were passed to the Anchor's constructor. In the example given above, those parameters are 'position' and 'grid'; you can pass arbitrary parameters.
    -</ul>
    -The return value of this function is an array of [x, y] - proportional values between 0 and 1 inclusive, such as you can pass to a static Anchor.
    -<p>To make your own position finder you need to create a function that takes those four arguments and returns an [x, y] position for the anchor, for example:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.AnchorPositionFinders.MyFinder = function(dp, ep, es, params) {
    -	...		
    -	return [ some maths ];	
    -};
    -</pre>
    -</div>
    -Then refer to it in a makeTarget call:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {  
    -  anchor:[ "Assign", { 
    -    position:"MyFinder",
    -    myCustomParameter:"foo",
    -    anInteger:5
    -  }]
    -});
    -</pre>
    -</div>
    -
    -<h5><a id="makeSource">jsPlumb.makeSource</a></h5>
    -There are two use cases supported by this method.  The first is the case that you want to drag a Connection from the element itself and have an Endpoint attached to the element when a Connection is established.  The second is a more specialised case: you want to drag a Connection from the element, but once the Connection is established you want jsPlumb to move it so that its source is on some other element.  For an example of this second case, check out the <a href="../demo/jquery/stateMachineDemo.html">State Machine Connectors</a> demonstration.
    -<p>Here's an example code snippet for the basic use case of makeSource:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource(someDiv, {
    -  paintStyle:{ fillStyle:"yellow" },
    -  endpoint:"Blank",
    -  anchor:"BottomCenter"
    -});
    -</pre>
    -</div>
    -<p>
    -Notice again that the second argument is the same as the second argument to an addEndpoint call.  makeSource is, essentially, a type of addEndpoint call.  In this example we have told jsPlumb that we will support dragging Connections directly from 'someDiv'.  Whenever a Connection is established between 'someDiv' and some other element, jsPlumb assigns an Endpoint at BottomCenter of 'someDiv', fills it yellow, and sets that Endpoint as the newly created Connection's source. 
    -</p>		
    -<p>Configuring an element to be an entire Connection source using makeSource means that the element cannnot itself be draggable.  There would be no way for jsPlumb to distinguish between the user attempting to drag the element and attempting to drag a Connection from the element.  If your UI works in such a way that that is acceptable then you'll be fine to use this method.  But if you want to drag Connections from some element but also have the element be draggable itself, you might want to consider the second use case.  First, a code snippet:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("someConnectionSourceDiv", {
    -  paintStyle:{ fillStyle:"yellow" },
    -  endpoint:"Blank",
    -  anchor:"BottomCenter",
    -  parent:"someDiv"
    -});
    -</pre>
    -</div>
    -<p>The only difference here is the inclusion of the 'parent' parameter.  It instructs jsPlumb to configure the source Endpoint on the element specified - in this case, "someDiv" (the value of this parameter may be a String id or a selector).</p>
    -<p>Again, I'd suggest taking a look at the <a href="../demo/jquery/stateMachineDemo.html">State Machine Connectors demonstration</a> for an example of this.</p>
    -
    -<h5>Filtering drag start based on element</h5>
    -You can supply a 'filter' parameter to the makeSource call in order to get a little more fine-grained control over which parts of the element will respond to a mousedown and start a drag. Consider this markup:
    -<div class="code">
    -<pre>
    -&lt;div id="foo"&gt;
    -  FOO
    -  &lt;button&gt;click me&lt;/button&gt;
    -&lt;/div&gt;
    -</pre>
    -</div>
    -Suppose we do not want to interfere with the operation of the 'click me' button. We can supply a filter to the makeSource call to do so:
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("foo", {
    -  filter:function(event, element) {
    -    return event.target.tagName !== "BUTTON";
    -  }
    -});
    -</pre>
    -</div>
    -If the filter returns anything other than a boolean false, the drag will begin. It's important to note that only boolean false will prevent a drag. False-y values will not.
    -
    -<h5>Endpoint options with makeSource</h5>
    -<p>
    -There are many things you can set in an Endpoint options object; for a thorough list see the API documentation for Endpoint.  
    -</p>
    -<p>Here's an example of specifying that you want an Arrow overlay halfway along any Connection dragged from this Endpoint:</p>
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -  isSource:true,
    -  connectorStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  connectorOverlays: [ [ "Arrow", { location:0.5 } ] ]
    -};
    -</pre>
    -</div>
    -This is an Endpoint that moves around the element it is attached to dependent on the location of other elements in the connections it is attached to (a 'dynamic' anchor):
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -  isSource:true,
    -  connectorStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  connectorOverlays: [ [ "Arrow", { location:0.5 } ] ]
    -  anchor:[ "TopCenter","RightMiddle","BottomCenter","LeftMiddle" ]
    -};
    -</pre>
    -</div>
    -<h5><a id="targetSourceTest">Testing if an element is a target or source</a></h5>
    -You can test if some element has been made a connection source or target using these methods:
    -<ul>
    -<li class="bullet">isTarget</li>
    -<li class="bullet">isSource</li>
    -</ul>
    -<p>The return value is a boolean.</p>
    -<h5><a id="targetSourceToggle">Toggling an element as connection target or source</a></h5>
    -You can toggle the state of some source or target using these methods:
    -<ul>
    -<li class="bullet">setTargetEnabled</li>
    -<li class="bullet">setSourceEnabled</li>
    -</ul>
    -<p>The return value from these methods is the current jsPlumb instance, allowing you to chain them:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.setTargetEnabled("aDivId").setSourceEnabled($(".aSelector"));
    -</pre>
    -</div>
    -<p>Note that if you call either of these methods on an element that was not originally configured as a target or source, nothing will happen.</p>
    -<p>You can check the enabled state of some target or source using these methods:
    -<ul>
    -<li class="bullet">isTargetEnabled</li>
    -<li class="bullet">isSourceEnabled</li>
    -</ul>
    -<h5><a id="targetSourceCancel">Canceling previous makeTarget and/or makeSource calls</a></h5>
    -jsPlumb offers four methods to let you cancel previous makeTarget or makeSource calls. Each of these methods returns the current jsPlumb instance, and so can be chained:
    -<ul>
    -<li class="bullet">unmakeTarget</li>
    -<li class="bullet">unmakeSource</li>
    -<li class="bullet">unmakeEveryTarget</li>
    -<li class="bullet">unmakeEverySource</li>
    -</ul>
    -<p>These last two are analogous to the <strong>removeEveryConnection</strong> and <strong>removeEveryEndpoint</strong> methods that have been in jsPlumb for a while now.</p>
    -<p>unmakeTarget and unmakeSource both take as argument the same sorts of values that makeTarget and makeSource accept - a string id, or a selector, or an array of either of these:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.unmakeTarget("aDivId").unmakeSource($(".aSelector"));
    -</pre>
    -</div>
    -
    -
    -<h5><a id="dragOptions">Drag Options</a></h5>
    -	These are options that will be passed through to the supporting library's drag API.  jsPlumb passes everything you supply here through, inserting
    -	wrapping functions if necessary for the various lifecycle events that jsPlumb needs to know about.  So if, for example, you pass a function to be
    -	called when dragging starts, jsPlumb will wrap that function with a function that does what jsPlumb needs to do, then call yours.
    -	<p>
    -	At the time of writing, jsPlumb supports jQuery, MooTools and YUI3, and each of those libraries uses different terminology.  In addition, jQuery's API is
    -	more fully featured, providing easy support for setting the zIndex and opacity of elements being dragged, as well as the 'scope' for a drag/drop (allowing
    -	you to specify more than one type of drag/drop pair), and hover classes for when a draggable is on the move or
    -	over a droppable. If you're using jQuery you can of course just supply these values on the dragOptions; to make it easier, jsPlumb's MooTools and YUI3 adapters recognize 
    -	these options and add appropriate callbacks for you.  
    -	</p> 
    -	<p>
    -	Given that the options here are library-specific, and they are all well-documented, we're going to discuss just the three drag options
    -	that behave the same way in all (see below for hoverClass):
    -		<ul>
    -		<li><strong>opacity</strong> - the opacity of an element that is being dragged.  Should be a fraction between 0 and 1 inclusive.</li>
    -		<li><strong>zIndex</strong> - the zIndex of an element that is being dragged.</li>
    -		<li><strong>scope</strong> - the scope of the draggable.  can only be dropped on a droppable with the same scope.  this is discussed below.</li>
    -		</ul> 
    -	</p>
    -	
    -	<p>For more information about drag options, take a look at the <a href="http://docs.jquery.com/UI/Draggable" target="_blank">jQuery</a>, <a href="http://mootools.net/docs/more/Drag/Drag.Move" target="_blank">MooTools</a>, or <a href="http://developer.yahoo.com/yui/3/dd/" target="_blank">YUI3</a> docs.	
    -	</p>
    -	<p><strong>NOTE: there is an issue in Chrome that affects the 'cursor' argument to drag options in jQuery. See these links:</strong>
    -	</p><p>
    -<a href="http://forum.jquery.com/topic/draggable-cursor-option-does-not-work" target="_blank">http://forum.jquery.com/topic/draggable-cursor-option-does-not-work</a><br/>
    -<a href="http://forum.jquery.com/topic/chrome-text-select-cursor-on-drag" target="_blank">http://forum.jquery.com/topic/chrome-text-select-cursor-on-drag</a>
    -</p>
    -You should put something like this at the top of your JS to avoid it (perhaps not something so drastic as a document-wide override):	
    -<div class="code">
    -<pre>document.onselectstart = function () { return false; };</pre>
    -</div>		
    -	
    -	<h5><a id="dropOptions">Drop Options</a></h5>
    -	Drop options are treated by jsPlumb in the same way as drag options - they are passed through to the underlying library.  MooTools does not have drop options like jQuery and YUI3 do; droppable functionality in MooTools 
    -	is actually implemented by the Drag.Move class - the one used to initialise a draggable.  But when you setup an Endpoint in jsPlumb you should ignore
    -	that fact, and treat droppables like you would in jQuery or YUI3.  jsPlumb wires everything up for you under the hood.
    -	<p>There are three jQuery droppable options that jsPlumb treats as shortcuts in MooTools and YUI3, for the sake of consistency:</p>
    -		<ul>
    -			<li><strong>hoverClass</strong> - the CSS class to attach to the droppable when a draggable is hovering over it.</li>
    -			<li><strong>activeClass</strong> - the CSS class to attach to the droppable when a draggable is, um, being dragged.</li>
    -			<li><strong>scope</strong> - the scope of the draggable.  The draggable can only be dropped on a droppable with the same scope.  This is discussed below.</li>
    -		</ul>   
    -	<p>For more information about drop options when using jQuery, see <a href="http://docs.jquery.com/UI/Droppable" target="_blank">here</a>.</p>
    -		
    -	<h5><a id="dragAndDropScope">Drag and Drop Scope</a></h5>
    -		jsPlumb borrowed the concept of 'scope' from jQuery's drag/drop implementation: the notion of
    -		which draggables can be dropped on which droppables.  In jsPlumb you can provide a 'scope' entry when 
    -		creating an Endpoint.  Here's the example grey Endpoint example with 'scope' added:
    -		
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:"#666" },
    -  isSource:true,
    -  connectionStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  scope:"exampleGreyConnection"
    -};</pre>		
    -</div>  		
    -  										
    -	If you do not provide a 'scope' entry, jsPlumb uses a default scope.  Its value is accessible through this method:
    -	<div class="code">
    -<pre>
    -jsPlumb.getDefaultScope();
    -</pre>	
    -	</div>				
    -	If you want to change it for some reason you can do so with this method:
    -	<div class="code">
    -<pre>
    -jsPlumb.setDefaultScope("mySpecialDefaultScope");
    -</pre>	
    -	</div>			
    -	You can also, should you want to, provide the scope value separately on the drag/drop options, like this:
    -	<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -  isSource:true,
    -  connectionStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  dragOptions:{ scope:"dragScope" },
    -  dropOptions:{ scope:"dropScope" }
    -};</pre>		
    -</div>  
    -			
    -	</div>	
    -
    -	<div class="section">
    -		<h3><a id="parameters">Connection Parameters</a></h3>
    -		jsPlumb has a mechanism that allows you to set parameters on a per-connection basis.  This can be achieved in a few different ways:
    -
    -		<ul>
    -			<li class="bullet">Providing parameters to a jsPlumb.connect call</li>
    -			<li class="bullet">Providing parameters to an addEndpoint call to/from which a connection is subsequently established using the mouse</li>
    -			<li class="bullet">using the <strong>setParameter</strong> or <strong>setParameters</strong> method on a Connection</li>
    -		</ul>
    -		Getting parameters is achieved through either the <strong>getParameter(key)</strong> or <strong>getParameters</strong> method on a Connection.
    -		<h4><a id="connectParameters">jsPlumb.connect</a></h4>
    -		Parameters can be passed in via an object literal to a jsPlumb.connect call:
    -		<div class="code">
    -<pre>var myConnection = jsPlumb.connect({
    -	source:"foo",
    -	target:"bar",
    -	parameters:{
    -		"p1":34,
    -		"p2":new Date(),
    -		"p3":function() { console.log("i am p3"); }
    -	}
    -});
    -</pre>
    -</div>
    -Note that they can be any valid Javascript objects - it's just an object literal.  You then access them like this:
    -		<div class="code">
    -<pre>myConnection.getParameter("p3")();     // prints 'i am p3' to the console.
    -</pre>
    -</div>
    -
    -<h4><a id="addEndpointParameters">jsPlumb.addEndpoint</a></h4>
    -<p>The information in this section also applies to the <strong>makeSource</strong> and <strong>makeTarget</strong> functions</p>
    -<p>Using jsPlumb.addEndpoint, you can set parameters that will be copied in to any Connections that are established from or to the given Endpoint using the mouse.  (If you set parameters on both a source and target Endpoints and then connect them, the parameters set on the target Endpoint are copied in first, followed by those on the source. So the source Endpoint's parameters take precedence if they happen to have one or more with the same keys as those in the target).</p>
    -Consider this example:
    -		<div class="code">
    -<pre>var e1 = jsPlumb.addEndpoint("d1", {
    -	isSource:true,
    -	parameters:{
    -		"p1":34,
    -		"p2":new Date(),
    -		"p3":function() { console.log("i am p3"); }
    -	}
    -});
    -
    -var e2 = jsPlumb.addEndpoint("d2", {
    -	isTarget:true,
    -	parameters:{
    -		"p5":343,
    -		"p3":function() { console.log("FOO FOO FOO"); }
    -	}
    -});
    -
    -var conn = jsPlumb.connect({source:e1, target:e2});
    -
    -
    -</pre>
    -</div>
    -
    -'conn' will have four parameters set on it, with the value for "p3" coming from the source Endpoint:
    -		<div class="code">
    -<pre>var params = conn.getParameters();
    -console.log(params.p1);   	// 34
    -console.log(params.p2);   	// Mon May 14 2012 12:57:12 GMT+1000 (EST) (or however your console prints out a Date)
    -console.log((params.p3)()); // "i am p3"  (note: we executed the function after retrieving it)
    -console.log(params.p5);   	// 343
    -
    -</pre>
    -</div>
    -
    -
    -</div> <!-- /section -->
    -	
    -	<div class="section">
    -		<h3><a id="beforeDrop">Interceptors</a></h3>
    -		<p>
    -		Interceptors were new in jsPlumb 1.3.4.  They are basically event handlers from which you can return a value that tells jsPlumb to abort what it is that
    -		it was doing.  There are currently two interceptors supported - <strong>beforeDrop</strong>, which is called when the user has dropped a Connection onto some target, 
    -		and <strong>beforeDetach</strong>, which is called when the user is attempting to detach a Connection. 
    -		</p>
    -		<p>Interceptors can be registered via the <strong>bind</strong> method on jsPlumb just like any other event listeners, and they can also be passed in to
    -		the <strong>jsPlumb.addEndpoint</strong>, <strong>jsPlumb.makeSource</strong> and <strong>jsPlumb.makeTarget</strong>
    -		methods.  A future version of jsPlumb will support registering interceptors on Endpoints using the <strong>bind</strong> method as you can now with the jsPlumb object.
    -		Note that binding 'beforeDrop' (as an example) on jsPlumb itself is like a catch-all: it will be called every time a Connection is dropped on _any_ 
    -		Endpoint.  But passing a 'beforeDrop' callback into an Endpoint (and in the future, registering one using 'bind') constrains that callback to just the
    -		Endpoint in question.  		
    -		<h4><a id="beforeDrop">beforeDrop</a></h4>
    -		This event is fired when a new or existing connection has been dropped. Your callback is passed a JS object with these fields:
    -		<ul>
    -			<li><strong>sourceId</strong> - the id of the source element in the connection</li>
    -			<li><strong>targetId</strong> - the id of the target element in the connection</li>
    -			<li><strong>scope</strong> - the scope of the connection</li>
    -			<li><strong>connection</strong> - the actual Connection object.  You can access the 'endpoints' array in a Connection to get the Endpoints involved in the Connection, but be aware that when a Connection is being dragged, one of these Endpoints will always be a transient Endpoint that exists only for the life of the drag. To get the Endpoint on which the Connection is being dropped, use the 'dropEndpoint' member.</li>
    -			<li><strong>dropEndpoint</strong> - this is the actual Endpoint on which the Connection is being dropped.  This <strong>may be null</strong>, because it will not be set if the Connection is being dropped on an element on which makeTarget has been called. </li>
    -		</ul>
    -
    -		If you return false (or nothing) from this callback, the new Connection is aborted and removed from the UI.
    -		<h4><a id="beforeDetach">beforeDetach</a></h4>
    -		This is called when the user has detached a Connection, which can happen for a number of reasons: by default, jsPlumb allows users to drag Connections off of target Endpoints, but this can also result from a programmatic 'detach' call.  Every case is treated the same by jsPlumb, so in fact it is possible for you to write code that attempts to detach a Connection but then denies itself!  You might want to be careful with that. 
    -		<p>Note that this interceptor is passed the actual Connection object; this is different from the beforeDrop
    -		interceptor discussed above: in this case, we've already got a Connection, but with beforeDrop we are yet 
    -		to confirm that a Connection should be created.</p>
    -		<p>Returning false - or nothing - from this callback will cause the detach to be abandoned, and the Connection will be reinstated or left on its current target.</p>
    -			
    -	</div>
    -
    -		<div class="section">
    -			<h3><a id="paintstyles">Paint Styles</a></h3>
    -			Defining the appearance of Connectors, Endpoints (and Overlays, but this is deprecated in favour of using CSS classes) is achieved through a 'paintStyle' (or a quite similar name)
    -			object passed as a parameter to one of jsPlumb.connect, jsPlumb.addEndpoint, jsPlumb.makeSource or jsPlumb.makeTarget. Depending on the method you are calling, the parameter names vary.
    -			<h5>Connector Paint Styles</h5>
    -			These are specified in a <em>paintStyle</em> parameter on a call to <strong>jsPlumb.connect</strong>:
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			or in the <em>connectorPaintStyle</em> parameter on a call to <strong>jsPlumb.addEndpoint</strong> or <strong>jsPlumb.makeSource</strong>:
    -			<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div> 			
    -			 
    -
    -Notice the <em>paintStyle</em> parameter in those examples: it is the paint style for the Endpoint, which we'll discuss below.
    -
    -			<h5>Endpoint Paint Styles</h5>
    -			These are specified in a <em>paintStyle</em> parameter on a call to <strong>addEndpoint</strong>. This is the example from just above:
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -</div> 
    -			...or as the <em>endpointStyle</em> parameter to a <strong>connect</strong> call:		
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			... or as an entry in the <em>endpointStyles</em> array passed to a <strong>jsPlumb.connect</strong> call:
    -					<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  endpointStyles:[ 
    -    { fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -    { fillStyle:"green" }
    -  ],
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			or as the <em>paintStyle</em> parameter passed to a <strong>makeTarget</strong> or <strong>makeSource</strong> call:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("el1", {
    -  ...
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  ...
    -});
    -</pre>			
    -</div>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 }
    -  parent:"someOtherDivIJustPutThisHereToRemindYouYouCanDoThis"
    -});
    -</pre>			
    -			</div>						
    -	In the first example we  made "el1" into a drop target, and defined a paint style for the Endpoint jsPlumb will create when a Connection is
    -	established.  In the second we made "el1" a connection source and assigned the same values for the Endpoint jsPlumb will create when a Connection
    -	is dragged from that element.
    -	
    -	<h5>Overlay Paint Styles</h5>
    -	The preferred way to set paint styles for Overlays is to use the <em>cssClass</em> parameter in the constructor arguments of an Overlay definition.
    -	
    -	<h4>Paint Style Parameters</h4>
    -	This is the full list of parameters you can set in a paintStyle object, but note that <strong>fillStyle</strong> is ignored by Connectors,
    -	and <strong>strokeStyle</strong> is ignored by Endpoints.  Also, if you create a Connection using jsPlumb.connect and do not specify any
    -	Endpoint styles, the Endpoints will derive their fillStyle from the Connector's strokeStyle.
    -	<p>
    -		fillStyle, strokeStyle and outlineColor can be specified using any valid CSS3 syntax.
    -	</p>
    -	<ul>
    -		<li><strong>fillStyle</strong> - color for an Endpoint, eg. rgba(100,100,100,50), "blue", "#456", "#993355", rgb(34, 56, 78).</li>
    -		<li><strong>strokeStyle</strong> - color for a Connector. see fillStyle examples.</li>
    -		<li><strong>lineWidth</strong> - width of a Connector's line. An integer.</li>
    -		<li><strong>outlineWidth</strong> - width of the outline for an Endpoint or Connector. An integer.</li>
    -		<li><strong>outlineColor</strong> - color of the outline for an Endpoint or Connector. see fillStyle examples.</li>
    -		<li><strong>dashstyle</strong> - VML and SVG only. This comes from VML, and allows you to create dashed or dotted lines.  It has a 
    -			better syntax than the equivalent attribute in SVG (<em>stroke-dasharray</em>, discussed below), so jsPlumb supports this for both renderers. 
    -			The <em>dashstyle</em> attribute is specified as an array of strokes and spaces, where each value is some multiple of <em>the width of 
    -			the Connector</em>, and that's where it's better than SVG, which uses pixels.
    -			<p>
    -			<a href="http://www.w3.org/TR/NOTE-VML" target="_blank">The VML spec</a> is a good place to find valid values for dashstyle. Note that
    -			jsPlumb does not support the string values for this attribute ("solid", "dashdot", etc).
    -			</p>
    -			<p>
    -			In SVG render mode, jsPlumb uses the <em>lineWidth</em> parameter in conjunction with the values in a <em>dashstyle</em> attribute
    -			to create an appropriate value for <em>stroke-dasharray</em>.
    -			</p>
    -		</li>
    -		<li><strong>stroke-dasharray</strong> - SVG only. This is the SVG equivalent of <em>dashstyle</em>.  
    -		<a href="http://www.w3.org/TR/SVG/painting.html" target="_blank">The SVG spec</a> discusses valid values for this parameter.  But be aware
    -		that jsPlumb does not convert this into a valid <em>dashstyle</em> attribute when using the VML renderer. Better to use <em>dashstyle</em>.
    -		</li>
    -		<li><strong>stroke-dashoffset</strong> - SVG only.  This is used in SVG to specify how far into the dash pattern to start painting.  For more information,
    -		see <a href="http://www.w3.org/TR/SVG/painting.html" target="_blank">the SVG spec.</a></li>
    -		<li><strong>joinstyle</strong> - VML and SVG only.  As with <em>dashstyle</em>, this is a VML attribute that jsPlumb supports for both VML
    -		and SVG - jsPlumb turns this into a <em>stroke-linejoin</em> attribute when rendering with SVG.  This attribute specifies how you want individual
    -		segments of connectors to be joined; the VML and SVG specs both have examples of this, of which many are the same between the two, which is
    -		why jsPlumb will automatically convert this attribute into the SVG equivalent.
    -		</li>
    -		<li><strong>stroke-linejoin</strong> - SVG only.  This is the equivalent of VML's <em>joinstyle</em> attribute, but as with <em>stroke-dasharray</em>,
    -		jsPlumb does not convert this into something approriate for VML.  So, using <em>joinstyle</em> will enable you to support more browsers with
    -		less effort.		
    -		</li>
    -	</ul>
    -	
    -	<h3><a id="hoverpaintstyles">Hover Paint Styles</a></h3>
    -	 		
    -	 		Connectors and Endpoints both support the concept of a "hover" paint style - a paint style to use when the mouse is hovering over
    -	 		the component.  These are specified in the exact same format as paint styles discussed above, but hover paint styles also inherit
    -	 		any values from the main paint style.  This is because you will typically want to just change the color, or perhaps outline color, of
    -	 		a Connector or Endpoint when the mouse is hovering, but leave everything else the same.  So having hover paint styles inherit their
    -	 		values precludes you from having to define things in more than one place.
    -	 		<p>
    -	 			The naming convention adopted for hover paint styles is pretty much to insert the word 'hover' into the corresponding main paint
    -	 			style parameters.  Here are a couple of examples:
    -	 		</p> 
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -  hoverPaintStyle:{ strokeStyle:"red" },
    -  endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  endpointHoverStyle:{ fillStyle:"red" }
    -});
    -</pre>			
    -</div>
    -			In this example we specified a hover style for both the Connector, and each of its Endpoints.  Here's the same thing, but using the plural version, to specify a different hover style for each Endpoint:	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -  hoverPaintStyle:{ strokeStyle:"red" },
    -  endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  endpointHoverStyles:[ { fillStyle:"red" }, { fillStyle:"yellow" } ]
    -});
    -</pre>			
    -</div>
    -	 		Calls to <strong>addEndpoint</strong>, <strong>makeSource</strong> and <strong>makeTarget</strong> can also specify various hover paint styles:
    -	 								<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  hoverPaintStyle:{ fillStyle:"red" },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -  connectorHoverPaintStyle:{ strokeStyle:"red", outlineColor:"yellow", outlineWidth:1 }
    -});
    -
    -jsPlumb.makeSource("el2", {
    -  paintStyle:{ fillStyle:"transparent", outlineColor:"yellow", outlineWidth:1 },
    -  hoverPaintStyle:{ fillStyle:"red" },
    -  connectorPaintStyle:{ strokeStyle:"green", lineWidth:3 },
    -  connectorHoverPaintStyle:{ strokeStyle:"#678", outlineColor:"yellow", outlineWidth:1 }
    -});
    -
    -jsPlumb.makeTarget("el3", {
    -  paintStyle:{ fillStyle:"transparent", outlineColor:"yellow", outlineWidth:1 },
    -  hoverPaintStyle:{ fillStyle:"red" }
    -});
    -</pre>			
    -			</div> 
    -	 		
    -	 		In these examples we specified a hover paint style for both the Endpoint we are adding, and any Connections to/from the Endpoint.
    -	 		<p>	
    -		Note that <strong>makeTarget</strong> does not support Connector parameters. It is for creating targets only; Connector parameters will be set by the source Endpoint in any Connections that are made to the element that you turned into a target by using this method.</p>
    -	 			 		
    -			<h4><a id="gradients">Gradients</a></h4>
    -			The Canvas and SVG renderers both support gradients. The VML renderer does not.  jsPlumb uses its own syntax to define gradients, to abstract out the differences between the syntax required by canvas and that required by SVG.  
    -			<p>There are two types of gradients available - a 'linear' gradient, which consists of colored lines all
    -			going in one direction, and a 'radial' gradient, which consists of colored circles emanating from one circle to another.
    -			Because of their basic shape, jsPlumb supports only <em>linear</em> gradients for
    -			Connectors.  But for Endpoints, jsPlumb supports both <em>linear</em> and <em>radial</em> gradients.</p>
    -			
    -				<h5>Connector gradients</h5>
    -				To specify a linear gradient to use in a Connector, you must add a <em>gradient</em> object to your
    -				Connector's <em>paintStyle</em>, for instance:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : "window2",
    -	target : "window3",
    -	paintStyle:{
    -		gradient:{
    -			stops:[[0,"green"], [1,"red"]]
    -		},
    -		lineWidth:15
    -	}
    -});
    -</pre>
    -</div>
    -Here we have connected window2 to window3 with a 15 pixel wide connector that has a gradient from green to red.</p><p> Notice the <strong>gradient</strong> object and the
    -<strong>stops</strong> list inside it - the gradient consists of an arbitrary number of these "color stops".  Each color stop is comprised
    -of two values - [position, color].  Position must be a decimal value between 0 and 1 (inclusive), and indicates where the color
    -stop is situated as a fraction of the length of the entire gradient.  Valid values for
    -the colors in the <strong>stops</strong> list are the same as those that are valid for <strong>strokeStyle</strong> when describing a color.
    -			</p>
    -			As mentioned, the <strong>stops</strong> list can hold an arbitrary number of entries.  Here's an example of a gradient that goes from red to blue to green, and back again through blue to red:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle : {
    -		gradient:{
    -			stops:[[0,'red'], [0.33,'blue'], [0.66,'green'], [0.33,'blue'], [1,'red']]
    -		},
    -		lineWidth : 15
    -	}
    -});
    -</pre>
    -</div>
    -<strong>Note:</strong> when using the VML renderer, jsPlumb will simply ignore the gradient directive
    -so it is best to ensure you also supply a <em>strokeStyle</em> in your paintStyle object, to give jsPlumb something to fall back on.  If you do not supply
    -a <em>strokeStyle</em> your Connectors will be painted black.  The previous example might look like this, for instance:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle:{
    -		strokeStyle:'red',
    -		gradient:{
    -			stops:[[0,'red'], [0.33,'blue'], [0.66,'green'], [0.33,'blue'], [1,'red']]
    -		},
    -		lineWidth:15
    -	}
    -});
    -</pre>
    -</div>
    -Notice the <strong>strokeStyle:'red'</strong> directive at the beginning of the parameter list in <strong>paintStyle</strong>.
    -			<h5>Endpoint gradients</h5>
    -			Endpoint gradients are specified using the same syntax as Connector gradients.  You put the gradient specifier either in the
    -			<strong>endpoint</strong> member, or if you are specifying different Endpoints for each end of the Connector, in one or both of
    -			the values in the <strong>endpoints</strong> array.  Also, this information applies to the case that you are creating standalone
    -			Endpoints that you will be configuring for drag and drop creation of new Connections. 
    -			<p>
    -			This is an example of an Endpoint gradient that is different for each Endpoint in the Connector.  This comes from the main demo; it is
    -			the Connector joining Window 2 to Window 3:
    -			</p>
    -<div class="code">
    -<pre>var w23Stroke = 'rgb(189,11,11)';
    -jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle:{
    -		lineWidth:8,
    -		strokeStyle:w23Stroke
    -	},
    - 	anchors:[ [0.3,1,0,1], "TopCenter" ],
    - 	endpoint:"Rectangle",
    - 	endpointStyles:[
    - 		{ gradient : {stops:[[0, w23Stroke], [1, '#558822']] } },
    -    	{ gradient : {stops:[[0, w23Stroke], [1, '#882255']] } }
    -    ]
    -});
    -</pre>
    -</div>
    -The first entry in the gradient will be the one that is on the Connector end of the Endpoint.  You can of course have as many color stops as
    -you want in this gradient, just like with Connector gradients.
    -	<h5>Applying the gradient in Endpoints</h5>
    -	Only the Dot and Rectangle endpoints honour the presence of a gradient (and, remember, not in VML). The Image endpoint of course ignores a gradient
    -	as it does no painting of its own.
    -	<p>The type of gradient you will see depends on the Endpoint type:</p>
    -		<ul>
    -			<li><strong>Dot</strong> - renders a radial endpoint, with color stop 0 on the outside, progressing inwards as we move through color stops.
    -			<p>Radial gradients actually require more data than linear gradients - in a linear gradient we just move from one point to another, whereas
    -			in a radial gradient we move from one <em>circle</em> to another.  By default, jsPlumb will render a radial gradient using a source
    -			circle of the same radius as the Endpoint itself, and a target circle of 1/3 of the radius of the Endpoint (both circles share the
    -			same center as the Endpoint itself). This circle will be offset by radius/2 in each direction.</p>
    -			<p>You can supply your own values for these inside the gradient descriptor:</p>
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect({
    -	source : 'window3',
    -	target : 'window4',
    -    paintStyle:{
    -    	lineWidth:10,
    -    	strokeStyle:w34Stroke
    -    },
    -    anchors:[ "RightMiddle", "LeftMiddle" ],
    -    endpointStyle:{
    -    	gradient : {
    -    		stops:[ [0, w34Stroke], [1, w34HlStroke] ],
    -    		offset:37.5,
    -    		innerRadius:40
    -    	},
    -    	radius:55
    -    }
    - });
    -</pre>
    -</div>
    -Here we have instructed jsPlumb to make the gradient's inner radius 10px instead of the default 25/3 = 8 ish pixels, and the offset in each direction
    -will be 5px, instead of the default radius / 2 = 12.5 pixels.
    -<p>It is also possible to specify the offset and inner radius as percentages - enter the values as strings with a '%' symbol on the end:</p>
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect({
    -	source : 'window3', 
    -	target : 'window4',
    -    paintStyle:{
    -    	lineWidth:10,
    -    	strokeStyle:w34Stroke
    -    },
    -	anchors:[ "RightMiddle", "LeftMiddle" ],
    -	endpointStyle:{
    -		gradient : {
    -			stops:[ [0, w34Stroke], [1, w34HlStroke] ],
    -			offset:'68%',
    -			innerRadius:'73%'
    -		},
    -		radius:25
    -	}
    -});
    -</pre>
    -</div>
    -This will give roughly the same output as the example above (the percentages are not entirely exact).<br/><br/>
    -			</li>
    -			<li><strong>Rectangle</strong> - renders a linear endpoint, with color stop 0 closest to the end of the Connector</li>
    -		</ul>
    -		
    -<h4><a id="svgvmlcss">Styling SVG/VML using CSS</a></h4>
    -	A nice way of controlling your UI's appearance is to make use of the fact that both SVG and VML can be styled using CSS.
    -	<p>This section will be expanded in the next few weeks to give some decent examples of how to do so</p>
    -			
    -		
    -		
    -		</div>
    -
    -		<div class="section">
    -			<h3><a id="cssclasses">CSS Class Reference</a></h3>
    -			jsPlumb attaches classes to each of the UI components it creates.
    -			<p>These class names are exposed on the jsPlumb object and can be overridden if you need to do so (see the third column in the table) </p>
    -			<table width="70%" class="table">
    -				<tr><th>Component</th><th>CSS Class</th><th>jsPlumb Member</th></tr>
    -				<tr><td>Connector</td><td>_jsPlumb_connector</td><td>connectorClass</td></tr>
    -				<tr><td>Endpoint</td><td>_jsPlumb_endpoint</td><td>endpointClass</td></tr>
    -				<tr><td>Overlay</td><td>_jsPlumb_overlay</td><td>overlayClass</td></tr>
    -			</table>
    -			<p>You would typically use these to establish appropriate z-indices for your UI.</p>			
    -		</div>
    -				
    -		<div class="section">
    -			<h3><a id="animation">Animation</a></h3>
    -			jsPlumb offers an 'animate' function, which wraps the underlying animation engine for whichever library you happen to be using and
    -			inserts a callback for jsPlumb to repaint whatever it needs to at each step.  You could of course do this yourself; it's a convenience
    -			method really.
    -			<p>The method signature is:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.animate : function(el, properties, options) 
    -</pre>
    -</div>			
    -The arguments are as follows:
    -	<ul>
    -		<li><strong>el</strong> - element id, or element object from the library you're using.</li>
    -		<li><strong>properties</strong> - properties for the animation, such as duration etc.</li>
    -		<li><strong>options</strong> - options for the animation, such as callbacks etc.</li>
    -	</ul>
    -	
    -		<h4>jQuery and the 'revert' option</h4>
    -		jQuery offers a 'revert' option that you can use to instruct it to revert a drag under some condition. This is
    -		rendered in the UI as an animation that returns the drag object from its current location to wherever it started
    -		out.  It's a nice feature.  Unfortunately, the animation that runs the revert does not offer any lifecycle
    -		callback methods - no 'step', no 'complete' - so its not possible for jsPlumb to know that the revert 
    -		animation is taking place.
    -	
    -		</div>
    -		
    -			<div class="section">
    -			<h3><a id="connectionInfo">Retrieving Connection Information</a></h3>
    -			There are two ways of retrieving connection information from jsPlumb. <strong>getConnections</strong>is the original method; from 1.4.0 onwards this method is supplemented by <a href="#selectConnections">jsPlumb.select</a>, a much more versatile variant.
    -			<h5>getConnections</h5>
    -			<p>Before you use this method you should understand jsPlumb's notion of 'scope' - documentation
    -			is <a href="#dragAndDropScope">here</a> </p>
    -			
    -			<h4>Retrieving connections for a single scope</h4>
    -			<p>To do this, you call getConnections with either no arguments, in which case jsPlumb uses the default scope, or with a string
    -			specifying one scope</p>
    -			<div class="code">
    -<pre>var connectionList = jsPlumb.getConnections();     // you get a list of Connection objects that are in the default scope.
    -</pre>			
    -			</div>
    -			Compare this with:
    -			<div class="code">
    -<pre>var connectionList = jsPlumb.getConnections("myScope");     // you get a list of Connection objects that are in "myScope".
    -</pre>			
    -			</div>
    -			<h4>More advanced filtering</h4>
    -			getConnections optionally takes a JS object specifying filter parameters, of which there are three:
    -			<ul>
    -				<li>scope - the scope(s) of the connection type(s) you wish to retrieve</li>
    -				<li>source - limits the returned connections to those that have this source id</li>
    -				<li>target - limits the returned connections to those that have this target id</li>
    -			</ul>			
    -			Each of these three parameters may be supplied as a string, which for source and target is an element id and for scope is the name of the scope, or a list of strings.  Also from 1.4.0 you can pass "*" in as the value for any of these - a wildcard, meaning any value.  See the examples below.
    -			<p>
    -			The return value of a call to getConnection using a JS object as parameter varies on how many scopes you defined.  If you defined only a single scope then jsPlumb returns you a list of Connections in that scope.  Otherwise the return value is a dictionary whose keys are  scope names, and whose values are lists of Connections. For example, the following call:
    -			</p>
    -			<div class="code">
    -<pre>
    -	jsPlumb.getConnections({scope:["someScope", "someCustomScope"]});
    -</pre>
    -</div>
    -would result in this output:			
    -<div class="code">
    -<pre>
    -{
    -	"someScope" : [ 1..n Connections ],
    -	"someCustomScope": [ 1..m Connections ]
    -}
    -</pre>
    -</div>	
    -
    -There is an optional second parameter that tells getConnections to flatten its output and just return you an array.  The previous example with this parameter would look like this:
    -			<div class="code">
    -<pre>
    -	jsPlumb.getConnections({scope:["someScope", "someCustomScope"]}, true);
    -</pre>
    -</div>
    -would result in this output:			
    -<div class="code">
    -<pre>
    -[ 1..n Connections ]
    -</pre>
    -</div>	
    -		
    -		The following examples show the various ways you can get connection information:
    -	<ul>
    -		<li>Get all connections:
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getAllConnections();  
    -</pre>
    -</div>						
    -		</li>
    -<li>Get all connections for the default scope only (return value is a list):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections();  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given scope (return value is a list):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:"myTestScope"});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given scopes (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:["myTestScope", "yourTestScope"]});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given source (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:"mySourceElement"});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given sources (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:["mySourceElement", "yourSourceElement"]});  
    -</pre>
    -</div>						
    -		</li>		
    -<li>Get all connections for the given target (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({target:"myTargetElement"});  
    -</pre>
    -</div>						
    -		</li>				
    -<li>Get all connections for the given source and targets (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:"mySourceElement", target:["target1", "target2"]});  
    -</pre>
    -</div>						
    -		</li>				
    -		
    -<li>Get all connections for the given scope, with the given source and target (return value is a list of connections):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:'myScope", source:"mySourceElement", target:"myTargetElement"});  
    -</pre>
    -</div>						
    -		</li>				
    -		
    -	</ul>	
    -
    -		</div>
    -
    -		<div class="section">
    -			<h3><a id="selectConnections">Selecting Connections</a></h3>
    -			Introduced in 1.4.0, <strong>jsPlumb.select</strong> provides a fluid interface for working with lists of Connections.  The syntax used to specify which Connections you want is identical to that which you use for getConnections, but the return value is an object that supports most operations that you can perform on a Connection, and which is also chainable, for setter methods. Certain getter methods are also supported, but these are not chainable; they return an array consisting of all the Connections in the selection along with the return value for that Connection.
    -			<p>
    -			This is the full list of setter operations supported by jsPlumb.select:
    -			<ul>
    -				<li class="bullet">addOverlay</li>
    -				<li class="bullet">removeOverlay</li>
    -				<li class="bullet">removeOverlays</li>
    -				<li class="bullet">showOverlay</li>
    -				<li class="bullet">hideOverlay</li>
    -				<li class="bullet">showOverlays</li>
    -				<li class="bullet">hideOverlays</li>
    -				<li class="bullet">removeAllOverlays</li>
    -				<li class="bullet">setLabel</li>
    -				<li class="bullet">setPaintStyle</li>
    -				<li class="bullet">setHoverPaintStyle</li>
    -				<li class="bullet">setDetachable</li>
    -				<li class="bullet">setConnector</li>
    -				<li class="bullet">setParameter</li>
    -				<li class="bullet">setParameters</li>
    -				<li class="bullet">detach</li>
    -			</ul>
    -
    -			Each of these operations returns a selector that can be chained.
    -			</p>
    -			These is the full list of getter operations supported by jsPlumb.select:
    -			<p>
    -				<ul>
    -				<li class="bullet">getLabel</li>
    -				<li class="bullet">getOverlay</li>
    -				<li class="bullet">isHover</li>
    -				<li class="bullet">isDetachable</li>
    -				<li class="bullet">getParameter</li>
    -				<li class="bullet">getParameters</li>
    -			</ul>
    -
    -			Each of these operations returns an array whose entries are [ value, Connection ] arrays, where 'value' is the return value from the given Connection.  Remember that the return values from a getter are not chainable, but a getter may be called at the end of a chain of setters.
    -			</p>
    -
    -			<p>Some examples:</p>
    -			Select all Connections and set their hover state to be false:
    -
    -<div class="code">
    -<pre>
    -jsPlumb.select().setHover(false);
    -</pre>
    -</div>
    -			Select all Connections from "d1" and remove all Overlays:
    -
    -<div class="code">
    -<pre>
    -jsPlumb.select({source:"d1"}).removeAllOverlays();
    -</pre>
    -</div>
    -
    -Select all connections in scope "foo" and set their paint style to be a thick blue line:
    -<div class="code">
    -<pre>
    -jsPlumb.select({scope:"foo"}).setPaintStyle({ strokeStyle:"blue", lineWidth:5 });
    -</pre>
    -</div>
    -Select all Connections from "d1" and detach them:
    -<div class="code">
    -<pre>
    -jsPlumb.select({source:"d1"}).detach();
    -</pre>
    -</div>
    -<h5>.each iterator</h5>
    -The return value of jsPlumb.select has a <strong>.each</strong> function that allows you to iterate through the list, performing an operation on each one:
    -<div class="code">
    -<pre>
    -jsPlumb.select({scope:"foo"}).each(function(connection) {
    -	
    -		// do something 
    -});
    -</pre>
    -</div>
    -.each is itself chainable:
    -<div class="code">
    -<pre>
    -jsPlumb.select({scope:"foo"}).each(function(connection) {
    -	
    -		// do something 
    -
    -}).setHover(true);
    -</pre>
    -</div>
    -
    -
    -<h5>Other properties/functions</h5>
    -<ul>
    -	<li class="bullet"><strong>length</strong> - this member reports the number of Connections in the selection</li>
    -	<li class="bullet"><strong>get(idx)</strong> - this function allows you to retrieve a Connection from the selection</li>
    -</ul>
    -
    -
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="events">Events</a></h3>
    -			jsPlumb supports binding to several different events on Connections, Endpoints and Overlays, and also on the jsPlumb object itself.  
    -			
    -			<h4><a id="jsPlumbEvents">jsPlumb Events</a></h4>
    -			<p>To bind an event to jsPlumb itself, use <strong>jsPlumb.bind(event, callback)</strong>:</p>
    -			<div class="code">
    -<pre>
    -jsPlumb.bind("jsPlumbConnection", function(info) {
    -   .. update your model in here, maybe.
    -});
    -</pre>
    -</div>
    -<p>These are the events you can bind to on the jsPlumb class:</p>
    -			<ul>
    -				<li class="bullet"><strong>jsPlumbConnection</strong> - notification a Connection was established.
    -					<p>
    -						The first argument to the callback is an object with the following properties:					
    -					</p>
    -					<div class="code">
    -<pre>
    -{
    -  connection 		: 	the new Connection.  you can register listeners on this etc.
    -  sourceId 		:	id of the source element in the Connection
    -  targetId		:	id of the target element in the Connection
    -  source		:	the source element in the Connection
    -  target		:	the target element in the Connection
    -  sourceEndpoint	:	the source Endpoint in the Connection
    -  targetEndpoint	:	the targetEndpoint in the Connection
    -}
    -</pre>
    -					</div>
    -The second argument is the original mouse event that caused the Connection, if any.
    -
    -					<p>
    -All of the source/target properties are actually available inside the Connection object, but - for one of those rubbish historical reasons - are provided separately because of a vagary of the <em>jsPlumbConnectionDetached</em> callback, which is discussed below.
    -</p>
    -				</li>
    -				<li class="bullet"><strong>jsPlumbConnectionDetached</strong> - notification a Connection was detached.  
    -					<p>
    -						As with <em>jsPlumbConnection</em>, the first argument to the callback is an object with the following properties:					
    -					</p>
    -					<div class="code">
    -<pre>
    -{
    -  connection		: 	the Connection that was detached.  
    -  sourceId		:	id of the source element in the Connection <em>before it was detached</em>
    -  targetId		:	id of the target element in the Connection before it was detached
    -  source		:	the source element in the Connection before it was detached
    -  target		:	the target element in the Connection before it was detached
    -  sourceEndpoint	:	the source Endpoint in the Connection before it was detached
    -  targetEndpoint	:	the targetEndpoint in the Connection before it was detached
    -}
    -</pre>
    -
    -					</div>	
    -The second argument is the original mouse event that caused the disconnection, if any. 					
    -								
    -					<p>
    -As mentioned above, the source/target properties are provided separately from the Connection, because this event is fired whenever
    -a Connection is either detached and abandoned, or detached from some Endpoint and attached to another.  In the latter case, the
    -Connection that is passed to this callback is in an indeterminate state (that is, the Endpoints are still in the state they are
    -in when dragging, and do not reflect static reality), and so the source/target properties give you the real story.
    -</p>
    -<p> Like I said above, this is really just for rubbish historical reasons.  I'm attempting to document my way out of it!</p>
    -				
    -				</li>
    -
    -				<li class="bullet"><strong>connectionDrag</strong> - notification an existing Connection is being dragged. The callback is passed the Connection.
    -				</li> 
    -
    -				<li class="bullet"><strong>connectionDragStop</strong> - notification a Connection drag has stopped. This is only fired for existing Connections. The callback is passed the Connection that was just dragged.
    -				</li> 
    -
    -				<li class="bullet"><strong>click</strong> - notification a Connection was clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification a Connection was double-clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>endpointClick</strong> - notification an Endpoint was clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>endpointDblClick</strong> - notification an Endpoint was double-clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>contextmenu</strong> - a right-click on some given component.  jsPlumb will report right clicks on both Connections and Endpoints; the callback is passed the component that received the right-click.				
    -				<li class="bullet"><strong>beforeDrop</strong>
    -					This event is fired when a new or existing connection has been dropped. Your callback is passed a JS object with these fields:
    -					<ul>
    -						<li><strong>sourceId</strong> - the id of the source element in the connection</li>
    -						<li><strong>targetId</strong> - the id of the target element in the connection</li>
    -						<li><strong>scope</strong> - the scope of the connection</li>
    -						<li><strong>connection</strong> - the actual Connection object.  You can access the 'endpoints' array in a Connection to get the Endpoints involved in the Connection, but be aware that when a Connection is being dragged, one of these Endpoints will always be a transient Endpoint that exists only for the life of the drag. To get the Endpoint on which the Connection is being dropped, use the 'dropEndpoint' member.</li>
    -						<li><strong>dropEndpoint</strong> - this is the actual Endpoint on which the Connection is being dropped.  This <strong>may be null</strong>, because it will not be set if the Connection is being dropped on an element on which makeTarget has been called. </li>
    -					</ul>
    -
    -					If you return false (or nothing) from this callback, the new Connection is aborted and removed from the UI.
    -				</li>					
    -				<!--li class="bullet"><strong>beforeDrag</strong>
    -					This event is fired when a new connection is about to be dragged.  Your callback function
    -					is passed the Connection that jsPlumb has just created.  If you return false (or nothing) 
    -					from this callback, the new Connection is aborted.
    -				</li-->
    -				<li class="bullet"><strong>beforeDetach</strong>
    -					This event is fired when a Connection is about to be detached, for whatever reason. Your callback function is passed the Connection that the user has just detached. Returning false from this interceptor aborts the Connection detach.
    -				</li>
    -							
    -			</ul>
    -			<h4><a id="connectionEvents">Connection Events</a></h4>		
    -			<p>To bind to an event on a Connection, you also use the <strong>bind</strong> method:</p>
    -			<div class="code">
    -<pre>
    -var connection = jsPlumb.connect({source:"d1", target:"d2"});
    -connection.bind("click", function(conn) {
    -	console.log("you clicked on ", conn);
    -});
    -</pre>
    -</div>
    -<p>These are the Connection events to which you can bind a listener:</p>
    -			<ul>
    -				<li class="bullet"><strong>click</strong> - notification a Connection was clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification a Connection was double-clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>contextmenu</strong> - a right-click on the Connection. The callback is passed the Connection and the original event.
    -				<li class="bullet"><strong>mouseenter</strong> - notification the mouse entered the Connection's path.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>mouseexit</strong> - notification the mouse exited the Connection's.  The callback is passed the Connection and the original mouse event.</li>
    -			</ul>
    -			<h4><a id="endpointEvents">Endpoint Events</a></h4>			
    -			<p>To bind to an event on a Endpoint, you again use the <strong>bind</strong> method:</p>
    -			<div class="code">
    -<pre>
    -var endpoint = jsPlumb.addEndpoint("d1", { someOptions } );
    -endpoint.bind("click", function(endpoint) {
    -	console.log("you clicked on ", endpoint);
    -});
    -</pre>
    -</div>
    -<p>These are the Endpoint events to which you can bind a listener:</p>
    -			<ul>
    -				<li class="bullet"><strong>click</strong> - notification an Endpoint was clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification an Endpoint was double-clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>contextmenu</strong> - a right-click on the Endpoint. The callback is passed the Endpoint and the original event.
    -				<li class="bullet"><strong>mouseenter</strong> - notification the mouse entered the Endpoint.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>mouseexit</strong> - notification the mouse exited the Endpoint.  The callback is passed the Endpoint and the original mouse event.</li>
    -			</ul>
    -			<h4><a id="overlayEvents">Overlay Events</a></h4>
    -			Registering event listeners on an Overlay is a slightly different procedure - you provide them as arguments to the Overlay's 
    -			constructor.  This is because you never actually act on an 'Overlay' object.  Here's how to register a click listener on an Overlay:
    -			<div class="code">
    -<pre>jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  overlays:[
    -    [ "Label", {
    -      events:{
    -        click:function(labelOverlay, originalEvent) { 
    -          console.log("click on label overlay for :" + labelOverlay.component); 
    -        }
    -      }
    -    }],
    -    [ "Diamond", {
    -      events:{
    -        dblclick:function(diamondOverlay, originalEvent) { 
    -          console.log("double click on diamond overlay for : " + diamondOverlay.component); 
    -        }
    -      }
    -    }] 	
    -  ]
    -});
    -</pre>			
    -			</div>		
    -		Note that events registered on Diamond, Arrow or PlainArrow overlays will not fire with the Canvas renderer - they work only with the SVG and VML renderers. 
    -
    -
    -		<h4><a id="jsPlumbEvents">Unbinding Events</a></h4>
    -		<p>On the jsPlumb object and on Connections and Endpoints, you can use the <strong>unbind</strong> method to remove a listener.  This method either takes the name of the event to unbind:
    -		<div class="code">
    -<pre>
    -jsPlumb.unbind("click");
    -</pre>
    -</div>		
    -...or no argument, meaning unbind all events:
    -	<div class="code">
    -<pre>
    -var e = jsPlumb.addEndpoint("someDiv");
    -e.bind("click", function() { ... });
    -e.bind("dblclick", function() { ... });
    -
    -...
    -
    -e.unbind("click");
    -</pre>
    -</div>
    -
    -		</div>
    -
    -				<div class="section">
    -			<a id="examples"><h3>jsPlumb.connect Examples</h3></a>
    -This section provides examples of how to use the programmatic API to establish Connections. 
    -			<p>
    -			The basic syntax of a call is that you execute 'connect', providing a source and a target, and optionally a paintStyle and preferences for where you
    -			want the plumbing to be anchored on each element, as well as the type of connector to use. 
    -			</p>
    -			<ul>
    -				<li>Connect window1 to window2 with the default settings:
    -					<div class="code">
    -						<pre>jsPlumb.connect({source:"window1", target:"window2"});</pre>
    -					</div>
    -This connects 'window1' to 'window2' using the jsPlumb defaults - a red Bezier curve Connector and black Dot Endpoints.
    -				</li>
    -				<li>Connect window1 to window2 with a 15 pixel wide yellow Connector, and a slightly brighter endpoint (remember the default Endpoint is a Dot):
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -					</div>
    -				</li>
    -				
    -				<li>Connect window1 to window2 with a 15 pixel wide yellow Connector, and a slightly brighter endpoint:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -					</div>
    -				</li>				
    -				<li>Connect window3 to 'window4' with a 10 pixel wide, semi opaque blue Connector, anchored to the left middle of window3, and the right middle of window4, with a Rectangle endpoint of width 10 and height 8:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window3',
    -	target:'window4',
    -	paintStyle:{ lineWidth:10, strokeStyle:'rgba(0, 0, 200, 0.5)' },
    -	anchors:["RightMiddle", "LeftMiddle"],
    -	endpoint:[ "Rectangle", { width:10, height:8 } ]
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window2 to window3 with a default Connector from the top center of window2 to the bottom center of window3, and rectangular endpoints:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window2',
    -	target:'window3',
    -	paintStyle:{lineWidth:8, strokeStyle:'rgb(189,11,11)'},
    -	anchors:["BottomCenter", "TopCenter"],
    -	endpoint:"Rectangle"
    -});</pre>
    -					</div>
    -				</li>
    -
    -
    -				<li>Connect window1 to window2 with a 15 px wide yellow Bezier. endpoints are a slightly lighter shade of yellow.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	anchors:["BottomCenter", [0.75,0,0,-1]],
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -</div></li>
    -<li>Connect window3 to window4 with a 10px wide blue-ish half transparent Bezier. put endpoints underneath the element they attach to.
    -the endpoints have a radial gradient. both ways of specifying gradient positioning are shown here.
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect( {
    -	source:'window3',
    -	target:'window4',
    -		 paintStyle:{lineWidth:10, strokeStyle:w34Stroke},
    -		 anchors:["RightMiddle", "LeftMiddle"],
    -		 endpointStyle:{ gradient : {stops:[[0, w34Stroke], [1, w34HlStroke]], offset:17.5, innerRadius:15 }, radius:35},
    -		 //endpointStyle:{ gradient : {stops:[[0, w34Stroke], [1, w34HlStroke]], offset:'78%', innerRadius:'73%'}, radius:35 },
    -		 endpointsOnTop:false
    -	}
    -);</pre>
    -</div></li>
    -<li>Connect window2 to window3 with an 8px red Bezier and default rectangular endpoints.  see also how the first anchor is
    -specified here - this is how you create anchors in locations jsPlumb does not offer shortcuts for.
    -the endpoints in this example have linear gradients applied.
    -<div class="code">
    -<pre>var w23Stroke = 'rgb(189,11,11)';
    -jsPlumb.connect({
    -	source:'window2',
    -	target:'window3',
    -	paintStyle:{lineWidth:8,strokeStyle:w23Stroke},
    -	anchors:[[0.3,1,0,1], "TopCenter"],
    -	endpoint:"Rectangle",
    -	endpointStyles:[{ gradient : {stops:[[0, w23Stroke], [1, '#558822']] }},
    -       				{ gradient : {stops:[[0, w23Stroke], [1, '#882255']] }}]
    -});</pre>
    -</div></li>
    -
    -<li>Connect window5 to window6 from center to center, 5px wide line that is green and half transparent. the endpoints are
    -125px in radius and spill out from underneath their elements.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window5',
    -	target:'window6',
    -	anchors:["Center", "Center"],
    -	paintStyle:{lineWidth:5,strokeStyle:'rgba(0,255,0,0.5)'},
    -	endpointStyle:{radius:125}
    -});</pre>
    -</div></li>
    -
    -<li>Connect window4 to window5 from bottom right to top left, with a 7px straight line purple connector, and an image as the endpoint,
    -placed on top of the element it is connected to.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window4",
    -	target:"window5",
    -	anchors:[ "BottomRight","TopLeft" ],
    -	paintStyle:{ lineWidth:7, strokeStyle:"rgb(131,8,135)" },
    -	endpoint:[ "Image", { src:"http://morrisonpitt.com/jsPlumb/img/endpointTest1.png" } ],
    -	connector:"Straight"
    -});</pre>
    -</div></li>
    -
    -
    -				<li>Connect window5 to window6 between their center points with a semi-opaque connector, and 125px endpoints:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window5",
    -	target:"window6",
    -	anchors:[ "Center", "Center" ],
    -	paintStyle:{ lineWidth:5, strokeStyle:"rgba(0,255,0,0.5)" },
    -	endpointStyle:{ radius:125 }
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window7 to window8 with a 10 pixel wide blue Connector, anchored on the top left of window7 and the bottom right of window8:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window7",
    -	target:"window8",
    -	paintStyle:{ lineWidth:10, strokeStyle:"blue" },
    -	anchors:[ "TopLeft", "BottomRight" ]
    -});
    -</pre>
    -					</div>
    -				</li>
    -				<li>Connect the bottom right corner of window4 to the top left corner of window5, with rectangular endpoints of size 40x40 and a hover color of light blue:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window4",
    -	target:"window5",
    -	anchors:["BottomRight","TopLeft"],
    -	paintStyle:{lineWidth:7,strokeStyle:'rgb(131,8,135)'},
    -	hoverPaintStyle:{ strokeStyle:"rgb(0, 0, 135)" },
    -	endpointStyle:{ width:40, height:40 },
    -	endpoint:"Rectangle",
    -	connector:"Straight"
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window1 to window2 with the default paint settings but provide some drag options (which are passed through to the underlying library's draggable call):
    -					<div class="code">
    -						<pre>jsPlumb.connect({source:'window1', target:'window2', dragOptions:{cursor:'crosshair'}});</pre>
    -					</div>
    -				</li>
    -</div>
    -		
    -		<div class="section">
    -			<a id="dragAndDropExamples"><h3>Draggable Connections Examples</h3></a>
    -			This is a list of examples of how to use jsPlumb to create Connections using drag and drop.
    -			<p>
    -			The basic procedure is:
    -			<ol>
    -				<li>1a. Create Endpoints and register them on elements in your UI</li>
    -				<li>1b. alternatively, create a source endpoint and then make some element a drop target </li>
    -				<li>2. Drag and Drop</li>
    -			</ol>
    -			That's all there is to it.  Of course there are plenty of options you can set when doing this...it will be easier
    -			to show you some examples:
    -			</p>
    -			<ul>
    -				<li>Define an Endpoint with default appearance, that is both a source and target of new Connections:
    -<div class="code">
    -<pre>var endpointOptions = { isSource:true, isTarget:true }; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Register that Endpoint on window3, specifying that it should be located in the top center of the element:
    -<div class="code">
    -<pre>var window3Endpoint = jsPlumb.addEndpoint('window3', { anchor:"TopCenter" }, endpointOptions );  
    -</pre>
    -</div>
    -Notice here the usage of the three-argument addEndpoint  - we can reuse 'endpointOptions' with a different Anchor for another element.  This is a useful
    -practice to get into.<br/><br/>
    -				</li>
    -				
    -				<li>Now register that Endpoint on window4, specifying that it should be located in the bottom center of the element:
    -<div class="code">
    -<pre>var window4Endpoint = jsPlumb.addEndpoint('window4', { anchor:"BottomCenter" }, endpointOptions );  
    -</pre>
    -</div>
    -Now we have two Endpoints, both of which support drag and drop of new Connections.  We can use these to make a programmatic Connection, too, though:<br/><br/>
    -				</li>				
    -				
    -				<li>Connect window3 to window4 with a 25px wide yellow Bezier that has a 'curviness' of 175:
    -<div class="code">
    -<pre>jsPlumb.connect({ 
    -	source:window3Endpoint,
    -	target:window4Endpoint,
    -	connector: [ "Bezier", 175 ],
    -	paintStyle:{ lineWidth:25, strokeStyle:'yellow' }
    -});  
    -</pre>
    -</div>
    -				</li>
    -				
    -				<li>Define an Endpoint that creates Connections that are 20px wide straight lines, that is both a source and target of new Connections,
    -				and that has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Define an Endpoint that will be anchored to "TopCenter".  It creates Connections that are 20px wide straight lines, that is both a source and target of new Connections,
    -				and that has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchor:"TopCenter", 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Define an Endpoint that will create a dynamic anchor which can be positioned at "TopCenter" or "BottomCenter".  It creates Connections that are 20px wide straight lines, it is both a source and target of new Connections,
    -				and it has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchor:[ "TopCenter", "BottomCenter" ], 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Exactly the same as before, but shows how you can use "anchors" instead of "anchor", if that makes you feel happier:
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchors:[ "TopCenter", "BottomCenter" ], 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>								
    -				
    -<li>Define an Endpoint that is a 30px blue dot, creates Connections that are 20px wide straight lines, is both a source and target of new Connections,
    -				has a 'scope' of 'blueline', and has an event handler that pops up an alert (note: the event handler name means this example is jQuery - MooTools
    -				and YUI3 use different event handler names):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{fillStyle:'blue'},
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ drop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -			<li>Same example as before, but this is for MooTools, and the Endpoint can support up to 5 connections (the default is 1):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{ fillStyle:'blue' },
    -	maxConnections:5,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ onDrop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Same example again, but maxConnections being set to -1 means that the Endpoint has no maximum limit of Connections:
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", {radius:30} ],
    -	style:{ fillStyle:'blue' },
    -	maxConnections:-1,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ onDrop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Same example again, but for YUI3.  Note the drop callback is "drop:hit":
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{fillStyle:'blue'},
    -	maxConnections:-1,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ "drop:hit":function(e, ui) { alert('drop!'); } }	 	
    -}; 
    -</pre>
    -</div>				 				
    -				</li>				
    -				
    -	<li>Assign a UUID to the endpoint options created above, and add as Endpoints to "window1" and "window2":
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("window1", { uuid:"abcdefg" }, endpointOptions );
    -jsPlumb.addEndpoint("window2", { uuid:"hijklmn" }, endpointOptions );
    -</pre>
    -</div>	
    -	</li>
    -	
    -	<li>Connect the two Endpoints we just registered on "window1" and "window2":
    -<div class="code">
    -<pre>
    -jsPlumb.connect({uuids:["abcdefg", "hijklmn"]});
    -</pre>
    -</div>	
    -	</li>
    -	
    -	<li>Create a source Endpoint, register it on some element, then make some other element a Connection target:
    -<div class="code">
    -<pre>
    -var sourceEndpoint = { isSource:true, endpoint:[ "Dot", { radius:50 } ] };
    -var targetEndpoint = { endpoint:[ "Rectangle", { width:10, height:10 } ] };
    -jsPlumb.addEndpoint( "window1", sourceEndpoint );
    -jsPlumb.makeTarget( "window2", targetEndpoint );
    -</pre>
    -</div>	
    -Notice that the endpoint definition we use on the target window does not include the "isTarget:true" directive.  jsPlumb ignores that flag when
    -creating a connection using an element as the target; but if you then tried to drag another connection to the Endpoint that was created, you would
    -not be able to.  To permit that, you would set 'isTarget:true' on the targetEndpoint options defined above.
    -	
    -	</li>
    -				
    -			</ul>
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="utilityFunctions">Utility Functions</a></h3>
    -			<ul>
    -				<li>Detach window5 from all connections
    -					<div class="code">
    -						<pre>jsPlumb.detachAll("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Hide all window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.hide("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Hide all window5's connections endpoints
    -					<div class="code">
    -						<pre>jsPlumb.hide("window5", true);</pre>
    -					</div>
    -				</li>
    -				<li>Show all window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.show("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Show all window5's connections and endpoints.  Note that in the case that you call jsPlumb.show with two arguments, jsPlumb
    -				will also not make a connection visible if it determines that the other endpoint in the connection is not visible.
    -					<div class="code">
    -						<pre>jsPlumb.show("window5", hide);</pre>
    -					</div>
    -				</li>				
    -				<li>Toggle the visibility of window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.toggle("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of all of window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.repaint("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of all of window5, window6 and window11's connections
    -					<div class="code">
    -						<pre>jsPlumb.repaint( [ "window5", "window6", "window11" ] );</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of every connection
    -					<div class="code">
    -						<pre>jsPlumb.repaintEverything();</pre>
    -					</div>
    -				</li>
    -				<li>Detach every connection that the given instance of jsPlumb is managing
    -					<div class="code">
    -						<pre>jsPlumb.detachEveryConnection();</pre>
    -					</div>
    -				</li>
    -				<li>Detach all connections from "window1"
    -					<div class="code">
    -						<pre>jsPlumb.detachAllConnections("window1");</pre>
    -					</div>
    -				</li>
    -				<li>Remove the given Endpoint from element "window1", deleting its Connections. 
    -					<div class="code">
    -						<pre>jsPlumb.removeEndpoint("window1", someEndpoint);</pre>
    -					</div>
    -				</li>
    -				<li>Remove all Endpoints for the element 'window1', deleting their Connections.
    -					<div class="code">
    -						<pre>jsPlumb.removeAllEndpoints("window1");</pre>
    -					</div>
    -				</li>	
    -				<li>Removes every Endpoint managed by this instance of jsPlumb, deleting all Connections.
    -				This is the same as jsPlumb.reset(), effectively, but it does not clear out the event listeners list. 
    -					<div class="code">
    -						<pre>jsPlumb.removeEveryEndpoint();</pre>
    -					</div>
    -				</li>
    -				<li>Deletes the given Endpoint and all its Connections. 
    -					<div class="code">
    -						<pre>jsPlumb.deleteEndpoint(endpoint);</pre>
    -					</div>
    -				</li>
    -				<li>Removes every endpoint, detaches every connection, and clears the event listeners list.  Returns jsPlumb instance to its initial state.  
    -					<div class="code">
    -						<pre>jsPlumb.reset();</pre>
    -					</div>
    -				</li>	
    -				<li>Library-agnostic method to get a selector from the given selector specification.  This function is used mostly by the jsPlumb demos, to cut down on repeating code across the different libraries.  But perhaps you are writing something with jsPlumb that you also wisht to be library agnostic - this function can help you. The returned object, in each of jQuery, MooTools and YUI, has a ".each" iterator.
    -					<div class="code">
    -						<pre>var selector = jsPlumb.getSelector(".someSelector .spec");</pre>
    -					</div>					
    -				</li>
    -				<li>Set window1 to be not draggable, no matter what some jsPlumb command may request.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggable("window1", false);</pre>
    -					</div>
    -				</li>
    -				<li>Set window1 and window2 to be not draggable, no matter what some jsPlumb command may request.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggable(["window1","window2"], false);</pre>
    -					</div>
    -				</li>				
    -				<li>Initialises window1 as a draggable element (all libraries). Passes in an on drag callback
    -				<div class="code">
    -					<pre>jsPlumb.draggable("window1");</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 and window2 as draggable elements (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(["window1","window2"]);</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable("window1");</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (jQuery)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (MooTools)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($$(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (YIU3)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(Y.all(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (jQuery)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($("#window1"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (MooTools)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($("window1"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (YUI3)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(Y.one("window1"));</pre>
    -				</div>
    -				</li>
    -			</ul>
    -		</div>					
    -		
    -		<div class="section">
    -			<h3><a id="developingJsPlumb">Advanced Topics</a></h3>
    -			<h4><a id="which">Which files are which?</a></h4>
    -			In development, jsPlumb is broken up into nine scripts:
    -				<ul>
    -					<li>- jsPlumb-util-x.x.x.js
    -					<p>There are the jsPlumb utility functions (and some base classes).</p>					
    -					</li>
    -					<li>- jsPlumb-x.x.x.js
    -					<p>This is the main jsPlumb engine.</p>					
    -					</li>
    -					<li>- jsPlumb-defaults-x.x.x.js
    -					<p>This contains the default Anchor, Endpoint, Connector and Overlay implementations.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-canvas-x.x.x.js
    -					<p>This contains the HTML5 canvas render code.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-svg-x.x.x.js
    -					<p>This contains the SVG render code.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-vml-x.x.x.js
    -					<p>This contains the VML render code.</p>
    -					</li>
    -					<li>- jsPlumb-connectors-statemachine-x.x.x.js
    -					<p>This State Machine connector.</p>
    -					</li>
    -					<li>- &lt;LIBRARY_PREFIX&gt;.jsPlumb-x.x.x.js
    -					<p>This contains library-specific helper methods.  jsPlumb ships with three of these - one each for jQuery, MooTools and YUI3. See below for information on how to create a new library implementation.</p>
    -					</li>
    -					<li>- jsBezier-0.3-min.js
    -					<p>These are the Bezier curve functions; they are maintained in a separate project called <a href="http://code.google.com/p/jsbezier/">jsBezier</a></p>
    -					</li>
    -				</ul>
    -			These seven files are packaged together to form the scripts that people use, for example:
    -			<ul>
    -				<li>jquery.jsPlumb-1.4.0-all.js
    -				<p>Contains jsPlumb-1.4.0.js, jsPlumb-defaults-1.4.0.js, jsPlumb-renderers-canvas-1.4.0.js, jsPlumb-renderers-svg-1.4.0.js, jsPlumb-renderers-vml-1.4.0.js, jsPlumb-connectors-statemachine-1.4.0.js, jquery.jsPlumb-1.4.0.js and jsBezier-0.3-min.js</p>
    -				</li>
    -				<li>jquery.jsPlumb-1.4.0-all-min.js
    -				<p>A minified version of the script above (minified using the YUI Compressor)</p>
    -				</li>
    -			</ul>
    -			<h4><a id="pluggableLibrarySupport">Pluggable Library Support</a></h4>
    -		Out of the box, jsPlumb can be run on top of jQuery, MooTools or YUI3.  This is achieved by
    -		delegating several core methods - tasks such as finding an element by id, finding an element's
    -		position or dimensions, initialising a draggable, etc - to the library in question.
    -		<p>To develop one of these, your test page should include the first two scripts discussed above, and
    -		then your own script containing your library specific functionality.  The existing implementations
    -		may be documented well enough for you to create your own, but contact me if you need assistance.  If you do this, it would be
    -		great to share it with everyone...</p>   
    -		<p>There is a group of people working on an ExtJS adapter currently.  There is no schedule for when this will be completed though.</p>
    -
    -			<!-- h4><a id="customConnectors">Custom Connectors</a></h4>			
    -					
    -			You can provide your own connectors if you need to. A Connector consists of two functions, which work as a pair.  First a call is made to the <em>compute</em> function:
    -
    -			<div class="code">
    -<pre>
    -this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) {
    -	...
    -	return dimensions;
    -}
    -</pre>
    -			</div>
    -			which is expected to return a list that the <em>paint</em> function can make sense of.  The first four entries in the
    -			list <strong>must be</strong> the [x,y,width,height] values for the canvas that the connector will be drawn on; jsPlumb will
    -			use this information to size the canvas prior to calling the Connector's <em>paint</em> function.  Therefore it
    -			is the Connector's responsibility to ensure that the returned dimensions describe a large enough space for
    -			the line that will be drawn on it.</p><p>The next four elements <strong>must be</strong> the coordinates of the two endpoints of the line
    -			you are going to draw.</p><p>The remainder of the items in the returned list are arbitrary, and will
    -			vary between Connector implementations; this list is passed in to a Connector's <em>paint</em> function, so each
    -			implementation will put into the list whatever it needs to paint itself.  For instance, the straight line
    -			connector only needs the [x,y] location of each end of the line it will paint, and that is one of the required entries, so
    -			it does not have to do anything extra,  whereas the Bezier connector adds the location of the two control points.  Other types of
    -			Connectors will do whatever is appropriate for their particular situation.
    -			</p>
    -			This is the method signature for the <em>paint</em> function:
    -			<div class="code">
    -				<pre>this.paint = function(dimensions, ctx) { .. }</pre>
    -			</div>
    -
    -			here, the 'dimensions' argument to the 'paint' function is the return value of the 'compute' function.  The 'ctx' argument is the Canvas context; you
    -			will do all your drawing on this.
    -			</p>
    -			To change the connector from the default, specify it in your connect call:
    -			<div class="code">
    -				<pre>jsPlumb.connect({source:'someWindow', target:'otherWindow', connector:new jsPlumb.Connectors.Straight()});</pre>
    -			</div>
    -			note that you can use shorthand for connectors if you don't need to specify parameters to it:
    -			<div class="code">
    -				<pre>jsPlumb.connect({source:'someWindow', target:'otherWindow', connector:"Straight"});</pre>
    -			</div>
    -			This works in the same way as specifying Anchors as string does.
    -<p>
    -The section below this discusses Overlays, which allow you to decorate Connectors with
    -			things such as labels or arrows or whatever else you like. Overlays can only work with Connectors that implement a few helper functions.
    -			<h4>The concept of <strong>location</strong></h4>
    -			Before discussing the helper functions you first must be across the concept of 'location' as used by this mechanism.  The <em>location</em>
    -			on a connector can be a decimal value between 0 and 1 inclusive, which marks a proportionate amount of travel <em>along the path
    -			inscribed by the Connector</em>.  For a straight line connector this is a simple piece of maths, but for Bezier connectors it's a little
    -			bit more involved.
    -			<h4>Required Helper Methods</h4>
    -			<ul>
    -				<li><strong>pointOnPath(location)</strong> - returns an [x,y] point corresponding to the given location</li>
    -				<li><strong>pointAlongPathFrom(location, distance)</strong> - returns an [x,y] point corresponding to travelling 'distance' pixels along the connector from 'location'.</li>
    -				<li><strong>gradientAtPoint(location)</strong> - returns the connector's gradient at the given location.  For linear connectors such as Straight this is constant, but for Bezier connectors the gradient changes continually.</li>
    -				<li><strong>perpendicularToPathAt(location, distance, length)</strong> - returns a line that is perpendicular to (and centered on) the connector at 'distance' from the given location, with length 'length'.</li>
    -			</ul>
    -</p>
    -<h4><a id="customEndpoints">Custom Endpoints</a></h4>
    -<p>To create your own Endpoint implementation, you need to implement a single method:
    -				<div class="code"><pre>paint : function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { ... }</pre></div>
    -				The arguments to this method are as follows:
    -				<ul>
    -					<li>anchorPoint - [x,y] location of the anchor point on screen</li>
    -					<li>orientation - [x,y] hints for the general direction the anchor points to</li>
    -					<li>canvas - the canvas to draw into</li>
    -					<li>endpointStyle - Javascript object containing style directives as discussed above.  The contents of this are arbitrary, so if you write a new Endpoint that needs some extra settings, you can add them no hassle.</li>
    -					<li>connectorPaintStyle - the style being used to paint the associated Connector.</li>
    -				</ul>
    -			</p>
    -			<p>
    -				It is your responsibility to size and locate the canvas to suit your needs. jsPlumb provides the following
    -				helper method to assist you:
    -				<div class="code"><pre>jsPlumb.sizeCanvas(canvas, x, y, width, height);</pre></div>
    -				Allows you to locate the canvas on screen and to size it.
    -			</p>
    -<h4><a id="customOverlays">Custom Overlays</a></h4>
    -			Overlays can only work with Connectors that implement the methods <strong>pointOnPath</strong>, <strong>pointAlongPathFrom</strong>,
    -			<strong>gradientAtPoint</strong>, and <strong>perpendicularToPathAt</strong>.  These are discussed in more detail in the Custom Connectors section above.  Both of the Connectors
    -			that come with jsPlumb - Straight and Bezier -implement these methods; if you write a custom connector, or have written a custom connector, you will need to supply
    -			them.   
    -			<h4>Overlay Interface</h4>
    -			An Overlay is required to implement two methods in order to be usable by jsPlumb:
    -			<ul>
    -				<li><strong>computeMaxSize(connector, context)</strong> - returns an integer value indicating the larger of this overlay's width and height.  It is used by jsPlumb to ensure that the canvas is large enough to accomodate the overlay.  The examples below should help to clarify this.</li>
    -				<li><strong>draw(connector, ctx)</strong> - draws the overlay.  What happens in this method is up to the given implementation.</li>
    -			</ul>
    -			<h4>computeMaxSize methods</h4>
    -			This is the Arrow overlay's computeMaxSize method:
    -			<div class="code">
    -<pre>
    -this.computeMaxSize = function() { return width; }
    -</pre>			
    -			</div>
    -Here, <em>width</em> is a private member of Arrow that indicates the width of the arrow's tail.  So the Arrow overlay reports that figure as the width it needs.					
    -Contrast this with the Label Overlay's computeMaxSize method:
    -		<div class="code">
    -<pre>
    -this.computeMaxSize = function(connector, ctx) {
    -  	if (labelText) {
    -  		ctx.save();
    -        if (self.labelStyle.font) ctx.font = self.labelStyle.font;
    -         var t = ctx.measureText(labelText).width;			            
    -		// a fake text height measurement: use the width of upper case M
    -		var h = ctx.measureText("M").width;					
    -		labelPadding = self.labelStyle.padding || 0.25;
    -		labelWidth = t + (2 * t * labelPadding);
    -		labelHeight = h + (2 * h * labelPadding);
    -		ctx.restore();
    -		return Math.max(labelWidth, labelHeight);
    -  	}
    - 	return 0;
    - };
    -</pre>			
    -			</div>
    -The Label overlay has to use the context to determine how big it will be on screen.
    -
    -<h4>draw methods</h4>
    -To give you a taste for how you can interact with a connector, consider the first few lines of the Arrow overlay's draw method:
    -<div class="code">
    -<pre>
    -this.draw = function(connector, ctx) {
    -	// this is the arrow head position
    -	var hxy = connector.pointAlongPathFrom(self.loc, length / 2);		
    -	// this is the center of the tail
    -	var txy = connector.pointAlongPathFrom(self.loc, -length / 2), tx = txy[0], ty = txy[1];
    -	// this is the tail vector
    -	var tail = connector.perpendicularToPathAt(self.loc, -length / 2, width);
    -	
    -	...
    -</pre>
    -</div>
    -	<p>The first two calls to the connector use the <strong>pointAlongPathFrom</strong> method, which passes <em>self.loc</em> as the location of the
    -	point on the path, and <em>length / 2</em> as the number of pixels along the path to traverse.</p>
    -	<p><em>self.loc</em> is Arrow's internal member describing the location of the overlay, and it is a decimal (between 0 and 1 inclusive) that points to
    -	a location at some distance along the path inscribed by the connector.  So these first two calls get us [x,y] locations of points on 
    -	the connector that mark the head and tail points for the arrow.  
    -	</p>		
    -	<p>
    -	The <strong>connector.perpendicularToPathAt(self.loc, -length / 2, width)</strong> call returns a line description of a line that is perpendicular to, and centered on, the connector
    -	at "-length / 2" pixels from the given point, with the given width (width is an internal member of Arrow).  At this point, the Arrow object has the three main points it
    -	needs in order to draw itself.
    -	</p>
    -		</div-->
    -</div>
    -
    -</body>
    -
    -</html>
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.10/index.html b/demo/doc/archive/1.3.10/index.html
    deleted file mode 100644
    index 2814a71b4..000000000
    --- a/demo/doc/archive/1.3.10/index.html
    +++ /dev/null
    @@ -1,86 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.4.0 documentation</title>
    -		<link rel="stylesheet" target="contentFrame" href="/mp.css"></link>
    -		<link rel="stylesheet" target="contentFrame" href="jsPlumbDoc.css"></link>
    -	</head>
    -	<body>
    -		<div class="index section">
    -			<h3>jsPlumb 1.4.0</h3>
    -			<ul>
    -				<li><a target="contentFrame" href="content.html#summary">Summary</a></li>
    -				<li><a target="contentFrame" href="content.html#changes">Changes since version 1.3.9</a></li>
    -				
    -				<li><h4>Setup</h4></li>
    -				<li><a target="contentFrame" href="content.html#imports">Required Imports</a></li>
    -				<li><a target="contentFrame" href="content.html#renderMode">Render Mode</a></li>
    -				<li><a target="contentFrame" href="content.html#initializing">Initializing jsPlumb</a></li>				
    -				<li><a target="contentFrame" href="content.html#multipleInstances">Multiple jsPlumb Instances</a></li>
    -				<li><a target="contentFrame" href="content.html#zIndex">Z-index Considerations</a></li>
    -				<li><a target="contentFrame" href="content.html#containerDefault">Where does jsPlumb add elements?</a></li>
    -				<li><a target="contentFrame" href="content.html#elementDragging">Element dragging</a></li>
    -				
    -				<li><h4>Introduction</h4></li>
    -				<li><a target="contentFrame" href="content.html#jsPlumbBasics">Basic Concepts</a></li>
    -				<li><a target="contentFrame" href="content.html#anchors">Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#staticAnchors">Static Anchors</a></li>				
    -				<li><a target="contentFrame" href="content.html#dynamicAnchors">Dynamic Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#continuousAnchors">Continuous Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#definitions">Connector, Endpoint &amp; Overlay Definitions</a></li>
    -				<li><a target="contentFrame" href="content.html#connectors">Connectors</a></li>
    -				<li><a target="contentFrame" href="content.html#endpoints">Endpoints</a></li>
    -				<li><a target="contentFrame" href="content.html#overlays">Overlays</a></li>
    -				<li><a target="contentFrame" href="content.html#defaults">Defaults</a></li>								
    -				
    -				<li><h4>Connections</h4></li>
    -				<li><a target="contentFrame" href="content.html#simpleConnections">Programmatic Connections</a></li>
    -				<li><a target="contentFrame" href="content.html#draggableConnections">Connections using Drag and Drop</a></li>
    -				<li><a target="contentFrame" href="content.html#sourcesAndTargets">Elements as Sources &amp; Targets</a></li>
    -				<li class="sub"><a target="contentFrame" href="content.html#makeTarget">makeTarget</a></li>
    -				<li class="sub"><a target="contentFrame" href="content.html#makeSource">makeSource</a></li>
    -				<li><a target="contentFrame" href="content.html#dragOptions">Drag Options</a></li>				
    -				<li><a target="contentFrame" href="content.html#dropOptions">Drop Options</a></li>
    -				<li><a target="contentFrame" href="content.html#dragAndDropScope">Drag and Drop Scope</a></li>
    -				
    -				<li><h4>Parameters</h4></li>		
    -				<li><a target="contentFrame" href="content.html#connectParameters">jsPlumb.connect</a></li>
    -				<li><a target="contentFrame" href="content.html#addEndpointParameters">jsPlumb.addEndpoint</a></li>
    -
    -				<li><h4>Interceptors</h4></li>				
    -				<li><a target="contentFrame" href="content.html#beforeDrop">Before Drop</a></li>
    -				<li><a target="contentFrame" href="content.html#beforeDetach">Before Detach</a></li>
    -				
    -				<li><h4>Appearance</h4></li>
    -				<li><a target="contentFrame" href="content.html#paintstyles">Paint Styles</a></li>
    -				<li><a target="contentFrame" href="content.html#hoverpaintstyles">Hover Paint Styles</a></li>
    -				<li><a target="contentFrame" href="content.html#gradients">Gradients</a></li>				
    -				
    -				<li><h4>Miscellaneous</h4></li>
    -				<li><a target="contentFrame" href="content.html#cssclasses">CSS Class Reference</a></li>
    -				<li><a target="contentFrame" href="content.html#animation">Animation</a></li>
    -				<li><a target="contentFrame" href="content.html#connectionInfo">Retrieving Connection Information</a></li>
    -				<li><a target="contentFrame" href="content.html#selectConnections">Selecting Connections</a></li>
    -				
    -				<li><h4>Events</h4></li>
    -				<li><a target="contentFrame" href="content.html#events">jsPlumb Events</a></li>
    -				<li><a target="contentFrame" href="content.html#connectionEvents">Connection Events</a></li>
    -				<li><a target="contentFrame" href="content.html#endpointEvents">Endpoint Events</a></li>
    -				<li><a target="contentFrame" href="content.html#overlayEvents">Overlay Events</a></li>
    -				
    -				<li><h4>Examples</h4></li>				
    -				<li><a target="contentFrame" href="content.html#examples">Programmatic API Examples</a></li>
    -				<li><a target="contentFrame" href="content.html#dragAndDropExamples">Draggable Connections Examples</a></li>
    -				<li><a target="contentFrame" href="content.html#utilityFunctions">Utility Functions</a></li>				
    -				
    -				<li><h4>Develop</h4></li>
    -				<li><a target="contentFrame" href="content.html#which">Which files are which?</a></li>
    -				<li><a target="contentFrame" href="content.html#pluggableLibrarySupport">Pluggable Library Support</a></li>
    -				<!-- li><a target="contentFrame" href="content.html#customConnectors">Custom Connectors</a></li>
    -				<li><a target="contentFrame" href="content.html#customEndpoints">Custom Endpoints</a></li>
    -				<li><a target="contentFrame" href="content.html#customOverlays">Custom Overlays</a></li-->
    -			</ul>
    -			<p><strong>This document refers to release 1.4.0 of jsPlumb.</strong></p>
    -			<strong>22 Jun 2012</strong>
    -		</div>		
    -	</body>
    -</html>
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.10/jsPlumbDoc.css b/demo/doc/archive/1.3.10/jsPlumbDoc.css
    deleted file mode 100644
    index bbe50c8ee..000000000
    --- a/demo/doc/archive/1.3.10/jsPlumbDoc.css
    +++ /dev/null
    @@ -1,126 +0,0 @@
    -body { 
    -	background-color:white; font-size:90%;
    -	background-image:url(../img/dynamicAnchorBg.jpg); 
    -}
    -/* docs */
    -.code { background-color:#e4e5ef; 
    -border:1px solid black; margin:1em; padding:0.8em; 
    -overflow:auto;
    -font-family:courier; 
    -font-size:8pt;
    -}
    -.section { border-bottom: 1px dotted black; padding:1em;font-family:helvetica;background-color:white;color:#333;}
    -.menu { 
    -	position:fixed; 
    -	right:10em; top:1em;
    -	background-color:white;
    -	padding:0.3em;
    -	opacity:0.8;
    -	z-index:5000;
    -	filter:alpha(opacity=80);
    -		box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -a {text-decoration:none;color:#05f;font-weight:bold;font-family:helvetica;}
    -a:hover {text-decoration:underline;}
    -.window, .label { 
    -	cursor:pointer;
    -	box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -#explanation { 
    -	background-color:white;
    -	color:black;
    -	border:0.25em solid #ddd; 
    -	position:absolute;
    -	right:3.5em;
    -	top:5em;
    -	width:22em;
    -	font-size:100%;
    -	-moz-border-radius:1em;
    -	border-radius:1em;
    -	padding:1em;
    -	z-index:1000;
    -	opacity:0.9;
    -	filter:alpha(opacity=90); 
    -	font-family:helvetica;
    -		box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -.selected {
    -	color:rgb(189,11,11);
    -}
    -
    -
    -/*  these styles are what jsPlumb attaches to the various elements it creates.
    -._jsPlumb_connector {
    -border:1px solid blue;
    -}
    -._jsPlumb_endpoint {
    -border:1px solid green;
    -}
    -._jsPlumb_overlay {
    -border:1px solid yellow;
    -}
    -*/
    -
    -
    -.section li {
    -list-style-type:none;
    -}
    -
    -li.bullet {
    -	list-style-type:circle;
    -	}
    -
    -
    -.section {
    -padding-left:2em;
    -font-size:10pt;
    -}
    -.index.section {
    -padding-left:0;
    -}
    -
    -.index {
    -width:20em;
    -font-size:9pt;
    -float:left;
    -background-color:#e4eeee;
    -overflow:auto;
    -padding-left:0.5em;
    -}
    -
    -.index li {
    -list-style-type:none;
    -}
    -
    -.index ul, .index h3 {
    -padding-left:1.5em;
    -
    -}
    -
    -#demo {
    -width:80%;
    -height:50em;
    -margin-left:3em;
    -margin-top:3em;
    -position:relative;
    -}
    -
    -.sub {
    -margin-left:1em;
    -}
    -
    -.table td {
    -	text-align:center;
    -}
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.10/usage.html b/demo/doc/archive/1.3.10/usage.html
    deleted file mode 100644
    index b5b154e16..000000000
    --- a/demo/doc/archive/1.3.10/usage.html
    +++ /dev/null
    @@ -1,10 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.4.0 - documentation</title>		
    -	</head>
    -	
    -	<frameset cols="250,*">
    -  		<frame src="index.html" />
    -  		<frame src="content.html" name="contentFrame" />
    -	</frameset>	
    -</html>
    diff --git a/demo/doc/archive/1.3.11/content.html b/demo/doc/archive/1.3.11/content.html
    deleted file mode 100644
    index 3c705d673..000000000
    --- a/demo/doc/archive/1.3.11/content.html
    +++ /dev/null
    @@ -1,3397 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.11 - documentation</title>
    -		<link rel="stylesheet" href="/mp.css">
    -		<link rel="stylesheet" href="jsPlumbDoc.css">
    -	</head>
    -	<body>
    -
    -<div class="menu">
    -	<a href="http://jsplumb.org" class="mplink" target="_top">view demo</a>
    -	&nbsp;|&nbsp;<a href="mailto:simon.porritt@gmail.com" class="mplink">contact me</a>
    -	&nbsp;|&nbsp;<a href="http://jsplumb.tumblr.com" class="mplink">jsPlumb on Tumblr</a>	
    -</div>
    -
    -<!--
    -GWT
    -
    -http://fishfilosophy.blogspot.com/2011/12/how-to-connect-gwt-widgets-using.html?spref=tw
    -
    --->
    -
    -<div class="content">
    -		<div class="section">
    -			<h3><a id="summary">Summary</a></h3>
    -			jsPlumb allows you to connect elements on the screen using SVG, Canvas or VML, depending on the capabilities of the browser.
    -			<p>
    -				It can be used with jQuery, MooTools or YUI3 (or another library of your choice if you feel like implementing an adapter for it).
    -			</p>  
    -
    -			<h5>Browser Compatibility</h5>
    -			<p>jsPlumb 1.3.11 runs on everything from IE6 up.  There are some caveats, though, because of various browser/library bugs:
    -				<ul>
    -					<li class="bullet">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.
    -					<li class="bullet">Safari 5.1 has an SVG bug that prevents mouse events from being passed through the transparent area of an SVG element</li>
    -					<li class="bullet">MooTools has a bug when using SVG in Firefox 11</li>
    -				<ul>
    -				
    -			</p>
    -		</div>
    -		
    -		<div class="section">		
    -			<h3><a id="changes">Changes between 1.3.11 and 1.3.10</a></h3>
    -			
    -			<h5>Backwards Compatibility</h5>
    -			No issues.
    -			
    -			<h5>New Features</h5>
    -			<ul>
    -				<li class="bullet">added 'selectEndpoints' method to jsPlumb. This functions in the same way as the existing 'select' method, but for Endpoints, providing you with a fluid interface to select and operate on sets of Endpoints.</li>
    -				<li class="bullet">makeSource now supports the 'maxConnections' parameter</li>
    -				<li class="bullet">makeSource and makeTarget now support an optional 'onMaxConnections' callback</li>
    -				<li class="bullet">added 'maxConnections' event to Endpoints.</li>
    -				<li class="bullet">add isSuspendDrawing method</li>
    -				<li class="bullet">added "connection" as alias to existing "jsPlumbConnection" event. BOTH EVENTS are fired.  So do not register for them both!</li>
    -				<li class="bullet">added "connectionDetached" as alias to "jsPlumbConnectionDetached" event.  As above - both events are fired.</li>
    -			</ul>
    -
    -			<h5>Issues Resolved</h5>
    -			<ul>
    -				<li class="bullet"><strong>261</strong> - add maxConnections event to Endpoint</li>
    -				<li class="bullet"><strong>264</strong> - selectEndpoints</li>
    -				<li class="bullet"><strong>266</strong> - makeSource does not honor maxConnections parameter</li>
    -				<li class="bullet"><strong>267</strong> - setLabel ignores suspendDrawing flag</li>
    -				<li class="bullet"><strong>268</strong> - deleteEveryEndpoint should suspend drawing before it begins and restore once its done</li>
    -			</ul>
    -			
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="init">Setup</a></h3>
    -
    -            <h4><a id="imports">Required Imports</a></h4>
    -			<h4>jQuery</h4>
    -			    <ul>
    -				    <li class="bullet">jQuery 1.3.x or higher.</li>
    -				    <li class="bullet">jQuery UI 1.7.x or 1.8.x (if you wish to support drag and drop).</li>
    -			    </ul>
    -			    <div class="code">
    -<pre>&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/jquery-ui.min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/jquery.jsPlumb-1.3.11-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			<h4>MooTools</h4>
    -				<ul>
    -					<li class="bullet">MooTools Core 1.2.4 or higher. jsPlumb has been tested with 1.2.4 and 1.3.3.</li>
    -					<li class="bullet">Drag.Move from MooTools More 1.2.4.4 or higher (if you wish to support drag and drop).</li>
    -				</ul>
    -			<div class="code">
    -			<pre>&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/mootools/1.3.3/mootools-yui-compressed.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO_MOO_TOOLS_MORE_1_3_2_1"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/mootools.jsPlumb-1.3.11-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			<h4>YUI3</h4>
    -			<ul>
    -				<li class="bullet">YUI 3.3.x.  jsPlumb has been tested on 3.3.0 only; it is possible other 3.x.x versions will work.</li>
    -			</ul>
    -			<div class="code">
    -			<pre>&lt;script type="text/javascript" src="http://yui.yahooapis.com/3.3.0/build/simpleyui/simpleyui-min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/yui.jsPlumb-1.3.11-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			
    -			<h4><a id="renderMode">Render Mode</a></h4>
    -			jsPlumb can use SVG, HTML5 Canvas elements or VML to render the various objects it adds to the display.  Most modern browsers support Canvas and SVG; IE&lt;9 browsers support neither.
    -			<p>
    -				By default, jsPlumb attempts to use <strong>jsPlumb.SVG</strong> as the render mode (prior to 1.3.4, the default renderer was Canvas), falling back to <strong>jsPlumb.VML</strong> if the 
    -				browser is IE&lt;9.  You can change the render mode by making this call:  
    -			</p>
    -			<div class="code">
    -<pre>jsPlumb.setRenderMode(jsPlumb.CANVAS);</pre>			
    -			</div>
    -			if, for some reason, you set the render mode to be <strong>jsPlumb.VML</strong> but you're in any browser other than IE, jsPlumb will use SVG.
    -			<p>
    -			The <strong>jsPlumb.setRenderMode</strong> method returns you the render mode that jsPlumb actually ended up setting.  Valid values for setRenderMode are:
    -			</p>
    -			<ul>
    -				<li class="bullet">jsPlumb.CANVAS</li>
    -				<li class="bullet">jsPlumb.SVG</li>
    -				<li class="bullet">jsPlumb.VML</li>
    -			</ul>			
    -			<h5>Performance Considerations</h5>
    -			<p>
    -			SVG uses less memory than Canvas and has support for mouse events built in, so I recommend using
    -			SVG unless you have a really good reason for wanting Canvas. Of course, you might consider that horrendous Safari bug a really good reason for wanting Canvas.
    -			</p>
    -			
    -			<h4><a id="initializing">Initializing jsPlumb</a></h4>
    -			You should not start making calls to jsPlumb until the DOM has been initialized - perhaps no surprises there.  With YUI, though, the asynchronous
    -			nature of the script loading process means that you are not guaranteed that jsPlumb is ready as soon as the DOM is.  To handle this,
    -			you should bind to the "ready" event on jsPlumb (or the instance of jsPlumb you are working with):
    -			<div class="code">
    -			<pre>jsPlumb.bind("ready", function() {
    -...	
    -		
    -// your jsPlumb related init code goes here
    -
    -...
    -
    -});						
    -			</pre>
    -			</div>
    -			There's a helper method that can save you approximately 9 (count them!) characters:
    -			<div class="code">
    -			<pre>jsPlumb.ready(function() {
    -...	
    -		
    -// your jsPlumb related init code goes here
    -
    -...
    -
    -});						
    -			</pre>
    -			</div>
    -			<p>
    -			For jQuery and MooTools you do not actually need to do this; by the time the DOM ready event is fired in each of those libraries you can be sure 
    -			all the JS we need has been loaded.  But in terms of explicitly calling out that you are waiting for jsPlumb, it seems like a good practice to bind to the "ready" event.
    -			</p>
    -			<p>
    -			If you bind to the "ready" event after jsPlumb has already been initialized, your callback will be executed immediately.
    -			</p>			
    -
    -				
    -<h4><a id="multipleInstances">Multiple jsPlumb instances</a></h4>
    -			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 <strong>getInstance</strong> method, for example:
    -<div class="code">
    -<pre>
    -var firstInstance = jsPlumb.getInstance();
    -</pre>
    -</div>
    -The variable 'firstInstance' can now be treated exactly as you would treat the 'jsPlumb' variable - you can set
    -defaults, call the connect method, whatever:
    -<div class="code">
    -<pre>
    -firstInstance.importDefaults({
    -  Connector : [ "Bezier", { curviness: 150 } ],
    -  Anchors : [ "TopCenter", "BottomCenter" ]
    -});
    -
    -firstInstance.connect({source:"element1", target:"element2", scope:"someScope" });
    -</pre>
    -</div>
    -
    -<strong>getInstance</strong> optionally takes an object that provides the defaults:
    -<div class="code">
    -<pre>
    -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" });
    -</pre>
    -</div>
    -
    -		<h4><a id="zIndex">Z-index Considerations</a></h4>
    -		<p>
    -			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. 
    -		</p>  
    -		<p>
    -			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).  			
    -		</p>
    -		<p>
    -			To help you organise z-indices correctly, jsPlumb adds a CSS class to each type of
    -			element it adds. They are as follows:			
    -		</p>
    -		<table style="color:black;width:70%;font-size:90%;">
    -			<tr><td><strong>type of element</strong></td><td><strong>class added</strong></td></tr>
    -			<tr><td>Endpoint</td><td>_jsPlumb_endpoint</td></tr>
    -			<tr><td>Connector</td><td>_jsPlumb_connector</td></tr>
    -			<tr><td>Overlay</td><td>_jsPlumb_overlay</td></tr>
    -		</table>
    -
    -		<h4><a id="containerDefault">Where does jsPlumb add elements?</a></h4>
    -		<p>
    -			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.
    -		</p>
    -		<p>
    -			Prior to 1.3.0, jsPlumb added everything to the end of the <em>body</em> element. This
    -			has the advantage of being the most flexible arrangement in terms of supporting what
    -			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!   
    -		</p>
    -		<p>
    -			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 (<strong>not</strong> 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.
    -		</p>
    -		
    -		<p>
    -			In general, the default behaviour means that you cannot do things like this:
    -		</p>
    -		<div class="code">
    -<pre>
    -&lt;body&gt;
    -	&lt;div id="container0"&gt;
    -		&lt;div id="node0"&gt;&lt;/div&gt;
    -	&lt;/div&gt;
    -	&lt;div id="container1"&gt;
    -		&lt;div id="node1"&gt;&lt;/div&gt;
    -	&lt;/div&gt;
    -&lt;/body&gt;
    -
    -&lt;script type="text/javascript"&gt;
    -
    -	var e0 = jsPlumb.addEndpoint("node0"),
    -	e1 = jsPlumb.addEndpoint("node1");
    -
    -	jsPlumb.connect({ source:e0, target:e1 });
    -&lt;/script&gt;
    -</pre>		
    -		</div>
    -<p>
    -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):  
    -</p>
    -		<div class="code">
    -<pre>
    -&lt;body&gt;
    -	&lt;div id="container0"&gt;
    -	&lt;/div&gt;
    -	&lt;div id="container1"&gt;
    -	&lt;/div&gt;
    -&lt;/body&gt;
    -
    -&lt;script type="text/javascript"&gt;
    -
    -	var e0 = jsPlumb.addEndpoint("container0"),
    -	e1 = jsPlumb.addEndpoint("container1");
    -
    -	jsPlumb.connect({ source:e0, target:e1 });
    -&lt;/script&gt;
    -</pre>		
    -		</div>
    -	now 'e0' and 'e1' have the same parent - the <em>body</em> element. 
    -	
    -	<p>Remember that you can use the <strong>anchor</strong> parameter to an <strong>addEndpoint</strong>
    -	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. 
    -	</p>									
    -	<p><strong>Note regarding the drawEndpoints option on jsPlumb.connect</strong>: 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). 
    -	</p>
    -	
    -	<h5>Overriding the default behaviour</h5>
    -	<p>
    -From version 1.3.3, the <strong>container</strong> 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 <strong>jsPlumb.Defaults.Container</strong> property, or you can set a <strong>container</strong> parameter on calls to <strong>addEndpoint</strong> or <strong>connect</strong> (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:
    -	</p>
    -		
    -Set a container to use as the default container, using a jQuery selector (you can supply MooTools/YUI selectors if that's the library you're using), and then add an Endpoint.  The canvas (or SVG/VML element) created will be a child of the document body:
    -		<div class="code">
    -<pre>
    -jsPlumb.Defaults.Container = $("body");
    -...
    -jsPlumb.addEndpoint(someDiv, { endpoint options });
    -</pre>		
    -		</div>
    -		
    -			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":
    -		<div class="code">
    -<pre>
    -jsPlumb.Defaults.Container = "containerId";
    -...
    -jsPlumb.connect({ source:someDiv, target:someOtherDiv });
    -</pre>		
    -		</div>
    -		
    -		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":
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({ source:someDiv, target:someOtherDiv, container:$("#containerId") });
    -</pre>		
    -		</div>
    -				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":
    -		<div class="code">
    -<pre>
    -jsPlumb.addEndpoint(someDiv, { ..., container:"containerId" });
    -</pre>		
    -		</div>
    -
    -		<h4><a id="elementDragging">Element Dragging</a></h4>
    -		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 <strong>jsPlumb.draggable(...)</strong>:
    -<div class="code">
    -<pre>jsPlumb.draggable("elementId");</pre>
    -</div>
    -You can also pass a selector from your library in to this method:
    -<div class="code">
    -<pre>jsPlumb.draggable($(".someClass");</pre>
    -</div>
    -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.  A common requirement when using jQuery, for instance, is to set the containment of elements that you are making draggable.  Here's how you would tell jsPlumb to initialise some set of elements to be draggable but only inside their parent:
    -<div class="code">
    -<pre>jsPlumb.draggable($(".someClass", {
    -	containment:"parent"
    -});</pre>
    -</div>
    -		<h5>Dragging nested elements</h5>
    -jsPlumb takes nesting into account when handling draggable elements. For example, say you have this markup:
    -<div class="code">
    -<pre>
    -&lt;div id="container"&gt;
    -  &lt;div class="childType1"&gt;&lt;/div&gt;
    -  &lt;div class="childType2"&gt;&lt;/div&gt;
    -&lt;/div&gt;
    -</pre>
    -</div>
    -..and then you connect one of those child divs to something, and make "container" draggable:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:$("#container .childType1"),
    -	target:"somehere else"
    -});
    -
    -jsPlumb.draggable("container");
    -</pre>
    -</div>
    -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.
    -
    -
    -		</div>
    -				
    -		<div class="section">
    -			<h3><a id="jsPlumbBasics">Basic Concepts</a></h3>			
    -	
    -			jsPlumb is all about connecting things together, so the core abstraction in jsPlumb is the <strong>Connection</strong> object, which is itself
    -			broken down into these four concepts:
    -
    -			<ul>
    -				<li><strong>Anchor</strong> - a location, relative to an element's origin, at which an Endpoint can exist. You do not create these yourself; you 
    -				supply hints to the various jsPlumb functions, which create them as needed.  They have no visual representation; they are a logical position only. Anchors can be referenced by name, for the Anchors that jsPlumb ships with, or a four element array describing [ x, y, x orientation, y orientation ]. 
    -				See the <a href="#anchors">anchors</a> section for more detail.<br/><br/></li>
    -				<li><strong>Endpoint</strong> - the visual representation of one end of a Connection.  You can create and attach these to elements yourself, which you are required to do to support drag and drop, or have jsPlumb create them when creating a Connection programmatically using jsPlumb.connect(...).  You can also join two Endpoints programmatically, by passing them as arguments to jsPlumb.connect(...).<br/><br/></li>				
    -				<li><strong>Connector</strong> - the visual representation of the line connecting two elements in the page.  jsPlumb has four types of these 
    -				available as defaults - a Bezier curve, a straight line, 'flowchart' connectors and 'state machine connectors. You do not interact with Connectors; 
    -				you just specify definitions of them when you need to - see <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions</a> <br/><br/></li>
    -				<li><strong>Overlay</strong> - a UI component that is used to decorate a Connector, such as a Label, Arrow, etc.<br/><br/></li>
    -			</ul>
    -						
    -			<p>One Connection is made up of two Endpoints, a Connector, and zero or more Overlays working together to join two elements.</p>
    -			<p>jsPlumb's public API exposes only Connection and Endpoint, handling the creation and configuration of everything else internally. But you still need to be across the concepts encapsulated by Anchor, Connector and Overlay.
    -			</p>
    -			</div>
    -			
    -			<div class="section">
    -			<h3><a id="anchors">Anchors</a></h3>			
    -	
    -			An Anchor models the notion of where on an element a Connector should connect.  There are three main types of Anchors:
    -			<ul>
    -			<li/>
    -				<li class="bullet">'Static' anchors - these are fixed to some point on an element and do not move.  They can be specified using a string to
    -				identify one of the defaults that jsPlumb ships with, or an array describing the location (see below)<br/><br/></li>				
    -				<li class="bullet">'Dynamic' anchors - these are lists of Static anchors from which jsPlumb picks the most appropriate one each time a Connection
    -				is painted.  The algorithm used to determine the most appropriate anchor picks the one that is closest to the center of the other element in
    -				the Connection. A future version of jsPlumb might support a pluggable algorithm to make this decision.<br/><br/></li>
    -				<li class="bullet">'Continuous' anchors - These anchors, introduced in 1.3.4, are not fixed to any specific loction; they are assigned to one of
    -				the four faces of an element depending on that element's orientation to the other element in the associated Connection.  Continuous anchors are slightly more computationally intensive than Static or Dynamic anchors because jsPlumb is required to calculate the position of every Connection during a paint cycle, rather than just Connections belonging to the element in motion.  These anchors were designed for use with the StateMachine connector that was also introduced in 1.3.4, but will work with any combination of Connector and Endpoint.<br/><br/></li> 
    -			</ul>  
    -					
    -			<h4><a id="staticAnchors">Static Anchors</a></h4>		
    -			jsPlumb has nine default anchor locations you can use to specify where the Connectors connect to elements: these are the four corners of an element,
    -			the center of the element, and the midpoint of each edge of the element:  
    -			<ul>
    -				<li class="bullet">TopCenter</li>
    -				<li class="bullet">TopRight</li>
    -				<li class="bullet">RightMiddle</li>
    -				<li class="bullet">BottomRight</li>
    -				<li class="bullet">BottomCenter</li>
    -				<li class="bullet">BottomLeft</li>
    -				<li class="bullet">LeftMiddle</li>
    -				<li class="bullet">TopLeft</li>
    -			</ul>
    -			
    -Each of these string representations is just a wrapper around the underlying array-based syntax  [x, y, dx, dy], where x and y are coordinates in the interval [0,1] specifying the position of the
    -anchor, and dx and dy are coordinates in the interval [-1, 1] specifying the orientation of the curve incident to the anchor. For example, [0, 0.5, -1, 0] defines a "LeftCenter" anchor with a connector curve that emanates leftward from the anchor. Similarly, [0.5, 0, 0, -1] defines a "CenterTop" anchor with a connector curve emanating upwards. 	
    -
    -
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:"BottomCenter", ... }); 
    -</pre>
    -</div>
    -is identical to:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:[ 0.5, 1, 0, 1 ], ... }); 
    -</pre>
    -</div>		
    -In addition to supplying the location and orientation of an anchor, you can optionally supply two more parameters that define an offset in pixels from the given location.  Here's the anchor specified above, but with a 50 pixel offset below the element in the y axis:	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:[ 0.5, 1, 0, 1, 0, 50 ], ... }); 
    -</pre>
    -</div>		
    -
    -		<h4><a id="dynamicAnchors">Dynamic Anchors</a></h4>
    -	These are Anchors that can be positioned in one of a number of locations, choosing the one that is most appropriate each time something moves or is painted in the UI.
    -	<p>
    -	There is no special syntax to creating a DynamicAnchor; you just provide an array of individual Static Anchor specifications, eg:
    -	</p>
    -<div class="code">
    -<pre>
    -var dynamicAnchors = [ [ 0.2, 0, 0, -1 ],  [ 1, 0.2, 1, 0 ], 
    -			   [ 0.8, 1, 0, 1 ], [ 0, 0.8, -1, 0 ] ];
    -
    -jsPlumb.connect({...., anchor:dynamicAnchors, ... }); 			   
    -</pre>
    -</div>
    -<h4>	Default Dynamic Anchor</h4>
    -jsPlumb provides a dynamic anchor called "AutoDefault" that chooses from TopCenter, RightMiddle, BottomCenter and LeftMiddle:
    -<div class="code">
    -<pre>jsPlumb.connect({...., anchor:"AutoDefault", ... });</pre>
    -</div>
    -
    -<h4>	Location Selection</h4>
    -The initial implementation of the algorithm that decides which location to choose just calculates which location is closest to the center of the 
    -other element in the connection.  It is possible that future versions of jsPlumb could support more sophisticated choice algorithms, if the need arose.
    -<h4>Draggable Connections</h4>
    -Dynamic Anchors and Draggable Connections can interoperate: jsPlumb locks the position of a dynamic anchor when you start to drag a connection from it,
    -and unlocks it once the connection is either established or abandoned. At that point you may see the position of the dynamic anchor change, as jsPlumb
    -optimises the connection.  <p>You can see this behaviour in the <a href="../html/jquery/draggableConnectorsDemo.html" target="_blank">draggable connections</a> demonstration, when
    -you drag a connection from the blue endpoint on window 1 to the blue endpoint on window 3 - the connection is established and then window 1's blue
    -endpoint jumps down to a location that is closer to window 3.</p>
    -
    -
    -	<h4><a id="continuousAnchors">Continuous Anchors</a></h4>
    -	As discussed above, these are anchors whose positions are calculated by jsPlumb according to the orientation between elements in a Connection, and also how
    -	many other Continuous anchors happen to be sharing the element.  You specify that you want to use Continuous anchors using the string syntax you would use
    -	to specify one of the default Static Anchors, for example:
    -	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:someDiv,
    -	target:someOtherDiv,
    -	anchor:"Continuous"
    -});
    -</pre>
    -</div>
    -
    -Note in this example I specified only "anchor", rather than "anchors" - jsPlumb will use the same spec for both anchors.  But I could have said this:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:someDiv,
    -	target:someOtherDiv,
    -	anchors:["BottomCenter", "Continuous"]
    -});
    -</pre>
    -</div>	 	  
    -..which would have resulted in the source element having a Static Anchor at BottomCenter.  In practise, though, it seems the Continuous Anchors work best if both 
    -elements in a Connection are using them.
    -<p>Note also that Continuous Anchors can be specified on <strong>addEndpoint</strong> calls:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint(someDiv, {
    -	anchor:"Continuous",
    -	paintStyle:{ fillStyle:"red" }
    -});
    -</pre>
    -</div>	 	
    -...and in <strong>makeSource</strong>/<strong>makeTarget</strong>:
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource(someDiv, {
    -	anchor:"Continuous",
    -	paintStyle:{ fillStyle:"red" }
    -});
    -
    -jsPlumb.makeTarget(someDiv, {
    -	anchor:"Continuous",
    -	paintStyle:{ fillStyle:"red" }
    -});
    -</pre>
    -</div>	 
    -... and in the jsPlumb defaults:
    -<div class="code">
    -<pre>
    -jsPlumb.Defaults.Anchor = "Continuous";
    -</pre>
    -</div>  
    -					</div>		
    -												
    -<div class="section">
    -		<h4><a id="definitions">Connector, Endpoint &amp; Overlay Definitions</a></h4>
    -Before we discuss Connector, Endpoints and Overlays, a quick word on definitions:  whenever you need to define a Connector, Endpoint or Overlay, you must use a "definition" of it, rather than constructing one directly.  This definition can be either a string that nominates the artifact you want to create:
    -		<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:"Rectangle"
    -});
    -</pre>
    -		</div>
    -		or an array consisting of both the artifact's name and the arguments you want to pass to its constructor:
    -		<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Rectangle", { cssClass:"myEndpoint", width:30, height:10 } ]
    -});
    -</pre>
    -		</div>
    -There is also a three-argument method that allows you to specify two sets of parameters, which jsPlumb will merge together for you. The idea behind this is that you will often want to define common characteristics somewhere and reuse them across a bunch of different calls:
    -		<div class="code">
    -<pre>var common = {
    -	cssClass	:	"myCssClass",
    -	hoverClass	:	"myHoverClass"
    -};
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Rectangle", { width:30, height:10 }, common ]
    -});
    -</pre>
    -		</div>
    -	This syntax is supported for all Endpoint, Connector and Overlay definitions.  Here's an example using definitions for all three:	
    -		<div class="code">
    -<pre>var common = {
    -	cssClass:"myCssClass"
    -};
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Dot", { radius:5, hoverClass:"myEndpointHover" }, common ],
    -	connector:[ "Bezier", { curviness:100 }, common ],
    -	overlays: [
    -		[ "Arrow", { foldback:0.2 }, common ],
    -		[ "Label", { cssClass:"labelClass" } ]	
    -	]
    -});
    -</pre>
    -		</div>		
    -The allowed constructor parameters are different for each artifact you create, but every artifact takes a single JS object as argument, with the parameters as [key,value] pairs in that object.  See the relevant sections on <a href="#connectors">Connectors</a>, <a href="#endpoints">Endpoints</a> and <a href="#overlays">Overlays</a> for information on supported parameters.</a>
    -				
    -	</div>
    -
    -		<div class="section">
    -			<h3><a id="connectors">Connectors</a></h3>
    -			<p>Connectors are the lines that actually join elements of the UI.  jsPlumb has four connector implementations - a straight line, a Bezier curve, "flowchart", and "state machine".  The default connector is the Bezier curve.</p>
    -			<p>You optionally specify a Connector by setting the 'connector' property on a call to jsPlumb.connect, jsPlumb.addEndpoint(s), jsPlumb.makeSource or jsPlumb.makeTarget. If you do not supply a value for 'connector', the default will be used (see <a href="#defaults">defaults</a>).</p>
    -			<p>You specify Connectors using the syntax described above in <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions.</a> Allowed constructor values for each Connector type are described below:</p>		
    -			<h4><a id="bezierConnector">Bezier Connector</a></h4>
    -			<p>The Bezier Connector provides a cubic Bezier path between the two Endpoints. It supports a single constructor argument:</p>
    -			<p>
    -			<strong>curviness</strong> - Optional; defaults to 150. This defines the distance in pixels that
    -			the Bezier's control points are situated from the anchor points.  This does not mean that your
    -			connector will pass through a point at this distance from your curve.  It is a hint to how you want the
    -			curve to travel. Rather than discuss Bezier curves at length here, because they are a complex topic,
    -			refer instead to <a href="http://en.wikipedia.org/wiki/B%C3%A9zier_curve" target="_blank">Wikipedia.</a>
    -			</p>
    -			<h4><a id="straightConnector">Straight Connector</a></h4>
    -			<p>The Straight Connector draws a straight line between the two endpoints. No constructor arguments are supported; use the <strong>endpointStyle</strong> and/or <strong>cssClass</strong>/<strong>connectorClass</strong> to a <strong>connect</strong>, <strong>addEndpoint</strong>, <strong>makeSource</strong> or <strong>makeTarget</strong> call to control the appearance of one of these Connectors.  
    -			</p>
    -			<h4><a id="flowchartConnector">Flowchart Connector</a></h4>
    -			<p>This type of Connector draws a connection that consists of a series of vertical or horizontal segments - the classic flowchart look. Two constructor arguments are supported:</p>
    -			<strong>stub</strong> - this is the minimum length, in pixels, of the initial stub that emanates from an Endpoint.  This is an optional parameter, and can be either an integer, which specifies the stub fo each end of the connector, or an array of two integers, specifying the stub for the [source, target] endpoints in the connection.  This parameter is optional, and defaults to an integer with value 30 pixels.<br/>
    -			<strong>gap</strong> - optional, defaults to 0 pixels. Lets you specify a gap between the end of the connector and the elements to which it is attached.
    -			<p>This Connector supports Connection that start and end on the same element.</p>
    -			<h4><a id="stateMachineConnector">State Machine Connector</a></h4>
    -			<p>This Connector draws slightly curved lines (they are actually quadratic Bezier curves), similar to the State Machine connectors you may have seen in software like GraphViz.  Connections in which some element is both the source and the target (so-called 'loopback') are supported by these Connectors (as they are with Flowchart Connectors); in this case you get a circle. Supported parameters are:
    -			</p>
    -			<p>
    -			<strong>margin</strong> - Optional; defaults to 5.  Defines the distance from the element that the connector begins/ends.
    -			</p>
    -			<p>
    -			<strong>curviness</strong> - Optional, defaults to 10.  This has a similar effect to the curviness parameter on Bezier curves.
    -			</p>
    -			<p>
    -			<strong>proximityLimit</strong> - Optional, defaults to 80.  The minimum distance between the two ends of the Connector before it paints itself as 
    -			a straight line rather than a quadratic Bezier curve.
    -			</p>    
    -			
    -		</div>
    -		
    -			<div class="section">
    -			<h3><a id="endpoints">Endpoint Types</a></h3>
    -			<p>An Endpoint is the UI component that marks the location of an Anchor, ie. the place where a Connector joins an
    -			element.  jsPlumb comes with four Endpoint implementations - <strong>Dot</strong>, <strong>Rectangle</strong>, <strong>Blank</strong> and <strong>Image</strong>. You optionally specify Endpoint properties using the
    -			<strong>endpoint</strong> parameter in a call to jsPlumb.connect, jsPlumb.addEndpoint, jsPlumb.makeSource or jsPlumb.makeTarget.
    -			</p>  
    -				<p>As with Connectors and Overlays, you specify Endpoints using the syntax described above in <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions.</a></p>
    -			
    -			The four available Endpoint types, and their constructor arguments, are as follows:
    -					<h4><a id="dotEndpoint">Dot Endpoint</a></h4>
    -					This Endpoint draw a dot on the screen. It supports three constructor parameters:
    -					<p>
    -						<strong>radius</strong> - Optional; defaults to 10 pixels. Defines the radius of the dot.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="rectangleEndpoint">Rectangle Endpoint</a></h4>
    -						Draws a rectangle. Supported constructor parameters are:
    -					<p>
    -						<strong>width</strong> - Optional; defaults to 20 pixels. Defines the width of the rectangle.
    -					</p>
    -					<p>
    -						<strong>height</strong> - Optional; defaults to 20 pixels. Defines the height of the rectangle.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="imageEndpoint">Image Endpoint</a></h4>
    -						Draws an image from a given URL.  This Endpoint supports three constructor parameters:  
    -					<p>
    -						<strong>src</strong> - Required.  Specifies the URL of the image to use.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="blankEndpoint">Blank Endpoint</a></h4>
    -						Does not draw anything visible to the user.  As discussed previously, this Endpoint is probably not what you want if you need your users to be able to drag existing Connections - for that, use a Rectangle or Dot Endpoint and assign to it a CSS class that causes it to be transparent.
    -																			
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="overlays">Overlay Types</a></h3>
    -			<p>
    -			Overlays are UI elements that are painted onto connections, such as labels or arrows.
    -			jsPlumb comes with four defaults:
    -			</p>
    -			<ul>
    -				<li><strong>Arrow</strong> - a configurable arrow that is painted at some point along the connector.  You can control the length and width of the Arrow, the 'foldback' point - a point the tail points fold back into, and the direction (allowed values are 1 and -1; 1 is the default and means point in the direction of the connection)</li>
    -				<li><strong>Label</strong> - a configurable label that is painted at some point along the connector.</li>
    -				<li><strong>PlainArrow</strong> - an Arrow shaped as a triangle, with no foldback.</li>
    -				<li><strong>Diamond</strong> - as the name suggests, a diamond.</li>
    -			</ul> 
    -			The last two are actually just configured instances of the generic Arrow overlay (see examples). 
    -			
    -			<p>All Overlays support these two methods for getting/setting their location (location is either a number between 0 and 1 inclusive, indicating some point along the path inscribed by the associated Connector, or an absolute number of pixels, where negative values mean distance from the target of the connection):</p>
    -			<ul>
    -				<li class="bullet"><strong>getLocation</strong> - returns the current location</li>
    -				<li class="bullet"><strong>setLocation</strong> - sets the current location; valid values are decimals in the range 0-1 inclusive, or an integer less than 0 (to express distance from the target), or an integer greater than 1 (to express distance from the source)</li> 
    -			</ul>
    -			
    -			<p>You can specify one or more overlays when making a call to jsPlumb.connect, jsPlumb.addEndpoint or jsPlumb.makeSource (but not jsPlumb.makeTarget; overlays
    -			are always derived from what the source of a Connection defines)  The three cases are discussed below:</p>
    -			1. Specifying one or more overlays on a jsPlumb.connect call. In this example we'll create an Arrow with the default options for an Arrow, and
    -			a label with the text "foo":
    -			<div class="code">
    -			<pre>jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});			
    -</pre>
    -			</div>
    -			This connection will have an arrow located halfway along it, and the label "foo" one quarter of the way along.  Notice the <strong>id</strong>
    -			parameter; it can be used later if you wish to remove the Overlay or change its visibility (see below).<br/><br/> 
    -			Another example, this time with an absolute location of 50 pixels from the source:
    -			<div class="code">
    -			<pre>jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:50 }, id:"myLabel" ]
    -	],
    -	...
    -});			
    -</pre>
    -			</div>
    -			
    -			2. Specifying one or more overlays on a jsPlumb.addEndpoint call.  Note in this example that we use the parameter 'connectorOverlays' and not
    -			'overlays' as in the last example.  This is because 'overlays' would refer to Endpoint Overlays:
    -			<div class="code">
    -			<pre>jsPlumb.addEndpoint("someDiv", {
    -	...
    -	connectorOverlays:[ 
    -		[ "Arrow", { width:10, length:30, location:1, id:"arrow" } ], 
    -		[ "Label", { label:"foo", id:"label" } ]
    -	],
    -	...
    -});</pre>
    -			</div>	
    -			This connection will have a 10x30 Arrow located right at the head of the connection, and the label "foo" located at the halfway point. Again,
    -			note that <strong>id</strong> parameter passed in to each Overlay spec.<br/><br/>
    -			
    -			3. Specifying one or more overlays on a jsPlumb.makeSource call.  Note in this example that we again use the parameter 'connectorOverlays' and not
    -			'overlays'.  The 'endpoint' parameter to jsPlumb.makeSource supports everything you might pass to the second argument of a jsPlumb.addEndpoint call:
    -			<div class="code">
    -			<pre>jsPlumb.makeSource("someDiv", {
    -	...
    -	endpoint:{
    -		connectorOverlays:[ 
    -			[ "Arrow", { width:10, length:30, location:1, id:"arrow" } ], 
    -			[ "Label", { label:"foo", id:"label" } ]
    -		]
    -	}
    -	...
    -});</pre>
    -			</div>	
    -			This connection will have a 10x30 Arrow located right at the head of the connection, and the label "foo" located at the halfway point. Again,
    -			note that <strong>id</strong> parameter passed in to each Overlay spec.<br/><br/>
    -			
    -			 	
    -					<h4><a id="arrowOverlay">Arrow Overlay</a></h4>
    -					<p>This overlay draws an arrow, using four points: the head and two tail points, and a 'foldback' point, which permits the tail
    -					of the arrow to be indented. Available constructor arguments for this Overlay are </p>
    -					<ul>
    -					<li><strong>width</strong> - width of the tail of the arrow</li>
    -					<li><strong>length</strong> - distance from the tail of the arrow to the head</li>
    -					<li><strong>location</strong> - where, either as a proportional value from 0 to 1 inclusive, or as an absolute value (negative values mean distance from target; positive values greater than 1 mean distance from source) the Arrow should appear on the Connector</li>
    -					<li><strong>direction</strong> - which way to point. Allowed values are 1 (the default, meaning forwards) and -1, meaning backwards </li>
    -					<li><strong>foldback</strong> - how far along the axis of the arrow the tail points foldback in to. Default is 0.623.</li>
    -					<li><strong>paintStyle</strong> - a style object in the form used for paintStyle values for Endpoints and Connectors</li>
    -					</ul>
    -					<h4><a id="plainArrowOverlay">PlainArrow Overlay</a></h4>
    -					<p>This is just a specialized instance of Arrow in which jsPlumb hardcodes 'foldback' to 1, meaning the tail of the Arrow is a flat edge.  All of the constructor parameters from Arrow apply for PlainArrow.</p>
    -					<h4><a id="diamondOverlay">Diamond Overlay</a></h4>
    -					<p>This is a specialized instance of Arrow in which jsPlumb hardcodes 'foldback' to 2, meaning the Arrow turns into a Diamond.  All of the constructor parameters from Arrow apply for Diamond.</p>
    -					<h4><a id="labelOverlay">Label Overlay</a></h4>
    -					This provides a text label to decorate Connectors with.  The available constructor arguments are:
    -					<ul>
    -						<li><strong>label</strong> - The text to display.  You can provide a function here instead of plain text: it is passed the Connection as an argument, and it should return a String.</li>
    -						<li><strong>cssClass</strong> - Optional css class to use for the Label.  This is now preferred over using the 'labelStyle' parameter.</li>
    -						<li><strong>labelStyle</strong> - (deprecated, use cssClass instead) Optional arguments for the label's appearance.  Valid entries in this JS object are:
    -							<ul>
    -								<li><em>font</em> - a font string in a format suitable for the Canvas element</li>
    -								<li><em>fillStyle</em> - the color to fill the label's background with. Optional.</li>
    -								<li><em>color</em> - the color of the label's text. Optional.</li>
    -								<li><em>padding</em> - optional padding for the label. This is expressed as a proportion of the width of the label, not in pixels or ems.</li>
    -								<li><em>borderWidth</em> - optional width in pixels for the label's border. Defaults to 0.</li>
    -								<li><em>borderStyle</em> - optional. The color to paint the border, if there is one.</li>
    -								
    -							</ul>
    -						
    -						</li>
    -						<li><strong>location</strong> - As for Arrow Overlay.  Where, either proportionally from 0 to 1 inclusive, or as an absolute offset from either source or target, the label should appear.</li>
    -					</ul>
    -
    -					The Label overlay offers two methods - getLabel and setLabel - for accessing/manipulating its content dynamically:
    -<div class="code">
    -<pre>
    -var c = jsPlumb.connect({
    -	source:"d1", 
    -	target:"d2", 
    -	overlays:[
    -		[ "Label", {label:"FOO", id:"label"}]
    -	]	
    -});					
    -
    -...
    -
    -var label = c.getOverlay("label");
    -console.log("Label is currently", label.getLabel());
    -label.setLabel("BAR");
    -console.log("Label is now", label.getLabel());
    -</pre>
    -</div>
    -In this example you can see that the Label Overlay is assigned an id of "label" in the connect call, and then retrieved using that id in the call to Connection's getOverlay method.
    -<p>Both Connections and Endpoints support Label Overlays, and because changing labels is quite a common operation, <strong>setLabel</strong> and <strong>getLabel</strong> methods have been added to these objects:
    -<div class="code">
    -<pre>
    -var conn = jsPlumb.connect({
    -	source:"d1", 
    -	target:"d2",
    -	label:"FOO"
    -});					
    -
    -...
    -
    -console.log("Label is currently", conn.getLabel());
    -conn.setLabel("BAR");
    -console.log("Label is now", conn.getLabel());
    -</pre>
    -</div>
    -These methods support passing in a Function instead of a String, and jsPlumb will create a label overlay for you if one does not yet exist when you call setLabel:
    -<div class="code">
    -<pre>
    -var conn = jsPlumb.connect({
    -	source:"d1", 
    -	target:"d2"
    -});					
    -
    -...
    -
    -conn.setLabel(function(c) {
    -	var s = new Date();
    -	return s.getTime() + "milliseconds have elapsed since 01/01/1970";
    -});
    -console.log("Label is now", conn.getLabel());
    -</pre>
    -</div>
    -					
    -					<h4><a id="hideshowOverlay">Hiding/Showing Overlays</a></h4>
    -					<p>You can control the visibility of Overlays using the <strong>setVisible</strong> method of Overlays themselves, or with
    -					<strong>showOverlay(id)</strong> or <strong>hideOverlay(id)</strong> on a Connection.</p>
    -					<p>Remember the <strong>id</strong> parameter that we specified in the examples above?  This can be used to retrieve the Overlay
    -					from a Connection:</p>
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -var overlay = connection.getOverlay("myLabel");
    -// now you can hide this Overlay:
    -overlay.setVisible(false);
    -// there are also hide/show methods:
    -overlay.show();
    -overlay.hide();
    -
    -</pre>					
    -					</div>
    -					<p>However, Connection and Endpoint also have two convenience methods you could use instead:
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:-30 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -connection.hideOverlay("myLabel");
    -
    -// more time passes
    -
    -connection.showOverlay("myLabel");
    -
    -</pre>					
    -					</div>
    -					
    -		<h4><a id="hideshowOverlay">Removing Overlays</a></h4>
    -		<p>Connection and Endpoint also have a <strong>removeOverlay</strong> method, that does what you might expect:
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -connection.removeOverlay("myLabel");
    -
    -
    -</pre>					
    -					</div>							
    -	
    -		</div>
    -	
    -			
    -			<div class="section">
    -			<h3><a id="defaults">Defaults</a></h3>
    -			The easiest way to set a look and feel for your plumbing is to override the defaults that jsPlumb uses. If you
    -			do not do this you are forced to provide your overridden values on every call.  Every argument to the connect and addEndpoint methods has an
    -			associated default value in jsPlumb.<br/><br/>
    -
    -			The defaults that ship with jsPlumb are stored in <em>jsPlumb.Defaults</em>, which is a Javascript object.  Valid entries, and their initial values, are:
    -
    -			<div class="code">
    -<pre>
    -Anchor : "BottomCenter",
    -Anchors : [ null, null ],
    -Connector : "Bezier",
    -ConnectionOverlays : [ ],
    -DragOptions : { },
    -DropOptions : { },
    -Endpoint : "Dot",
    -EndpointOverlays : [ ],
    -Endpoints : [ null, null ],
    -EndpointStyle : { fillStyle : null },
    -EndpointStyles : [ null, null ],
    -EndpointHoverStyle : null,
    -EndpointHoverStyles : [ null, null ],
    -HoverPaintStyle : null,
    -LabelStyle : { color : "black" },
    -LogEnabled : false,
    -Overlays : [ ],
    -MaxConnections : 1,
    -PaintStyle : { lineWidth : 8, strokeStyle : "#456" },
    -RenderMode : "svg",
    -Scope : "_jsPlumb_DefaultScope"
    -</pre>
    -			</div>
    -			<p>
    -			Note that in EndpointStyle, the default fillStyle is 'null'.  This instructs jsPlumb to use the strokeStyle
    -from the attached connector to fill the endpoint.</p>
    -<p>Note also that you can specify either or both (or neither) of 'EndpointStyle' and 'EndpointStyles'.  This allows you to specify a different
    -end point for each end of a connection.  'Endpoint' and 'Endpoints' use the same concept.  jsPlumb will look first in the
    -individual endpoint/endpoint style arrays, and then fall back to the single default version.</p>
    -
    -			you can override these defaults by including this in a script somewhere:
    -			<div class="code">
    -<pre>
    -jsPlumb.Defaults.PaintStyle = {
    -	lineWidth:13,
    -	strokeStyle: 'rgba(200,0,0,0.5)'
    -}
    -
    -jsPlumb.Defaults.DragOptions = { cursor: "crosshair" };
    -
    -jsPlumb.Default.Endpoints = [ [ "Dot", 7 ], [ "Dot", 11 ] ];
    -
    -jsPlumb.Defaults.EndpointStyles = [{ fillStyle:"#225588" }, { fillStyle:"#558822" }];</pre>
    -			</div>
    -			after the jsPlumb script has been loaded of course!  Here we have specified the following default behaviour:
    -			<ul>
    -				<li>- connectors are 13 pixels wide and painted with a semi-transparent red line</li>
    -				<li>- when dragging an element the crosshair cursor is used</li>
    -				<li>- the source endpoint is a dot of radius 7; the target endpoint is a dot of radius 11</li>
    -				<li>- the source endpoint is blue; the target endpoint is green</li>
    -			</ul>
    -
    -			<h5>jsPlumb.importDefaults</h5>
    -There is a helper method called <strong>importDefaults</strong> that allows you to import a set of values with just one call to jsPlumb.  Here's the previous example using importDefaults:
    -			<div class="code">
    -<pre>
    -jsPlumb.importDefaults({
    -	PaintStyle : {
    -		lineWidth:13,
    -		strokeStyle: 'rgba(200,0,0,100)'
    -	},
    -	DragOptions : { cursor: "crosshair" },
    -	Endpoints : [ [ "Dot", 7 ], [ "Dot", 11 ] ],
    -	EndpointStyles : [{ fillStyle:"#225588" }, { fillStyle:"#558822" }]
    -});</pre>
    -			</div>
    -
    -		</div> 					
    -
    -<div class="section">
    -			<h3><a id="simpleConnections">Connections</a></h3>
    -			<h4><a id="simpleConnections">Programmatic Connections</a></h4>
    -The most simple connection you can make with jsPlumb looks like this:
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({source:"element1", target:"element2"});</pre>			
    -			</div>
    -
    -		In this example we have created a Connection from 'element1' to 'element2'.  Remember that a Connection in jsPlumb consists of two Endpoints,
    -		a Connector, and zero or more Overlays.  But this call to 'connect' supplied none of those things, so jsPlumb uses the default values wherever it needs to.
    -		In this case, default values have been used for the following: 
    -		<ul>
    -			<li>- The type and appearance of each Endpoint in the Connection. jsPlumb's default for this is the "Dot" endpoint, of radius 10, with fill color "#456".</li>
    -			<li>- The Anchors that define where the connection's Endpoints appear on each element. The jsPlumb default is "BottomCenter"</li>			
    -			<li>- Whether or not each Endpoint can be a source or target for new Connections. The default is false.</li>
    -			<li>- The type and appearance of the Connection's Connector. The default is a "Bezier" connector of line width 8, and color "#456".</li>
    -		</ul>
    -		<p>
    -		So this call will result in an 8px Bezier, colored "#456", from the bottom center of 'element1' to the bottom center of 'element2', and each
    -		Endpoint will be a 10px radius Dot Endpoint, colored "#456".
    -		</p>
    -		<p>
    -		Let's beef up this call a little and tell jsPlumb what sort of Endpoints we want, and where we want them:		
    -		</p>
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:"element1", 
    -	target:"element2",
    -	anchors:["RightMiddle", "LeftMiddle" ],
    -	endpoint:"Rectangle",
    -	endpointStyle:{ fillStyle: "yellow" }
    -});
    -</pre>			
    -			</div>
    -			
    -		<p>This is what we have told jsPlumb we want this time:</p>
    -		<ul>
    -			<li><strong>anchors</strong> - this array tells jsPlumb where the source and target Endpoints should be located on their parent elements. In this case,
    -			we use the shorthand syntax to name one of jsPlumb's default anchors; you can also specify custom locations (see <a href="#anchors">anchors</a>).
    -			Instead of <strong>anchors</strong> you can use <strong>anchor</strong>, if you want the source and target Endpoints to be located at the
    -			same place on their parent elements.			<br/>
    -			</li>
    -			<li>
    -				<strong>endpoint</strong>  - this tells jsPlumb to use the "Rectangle" Endpoint for both the source and target of the Connection.  As with anchors, 
    -				<strong>endpoint</strong> has a plural version that allows you to specify a different Endpoint for each end of the Connection.<br/>  
    -			</li>
    -			<li>
    -				<strong>endpointStyle</strong> - this is the definition of the appearance of the Endpoint you specified above.  Again, there is a
    -				plural equivalent of this that allows you to specify a different style for each end of the Connection. For more information about
    -				allowed values for this value, see <a href="#paintstyles">Connector, Endpoint & Overlay Styles</a>. 
    -			</li>
    -		</ul>		
    -		
    -		<h5>Reusing common settings between jsPlumb.connect calls</h5>
    -		<p>
    -			A fairly common situation you will find yourself in is wanting to create a bunch of Connections that have only minor differences
    -			between them.  To support that, <strong>jsPlumb.connect</strong> takes an optional second argument. For example:			 
    -		</p>
    -		<div class="code">
    -<pre>
    -var common = {
    -	anchors:[ "BottomCenter", "TopCenter" ],
    -	endpoints:["Dot", "Blank" ]
    -};
    -
    -jsPlumb.connect({ source:"someElement", target:"someOtherElement" }, common);
    -
    -jsPlumb.connect({ source:"aThirdElement", target:"yetAnotherElement" }, common);
    -
    -</pre>			
    -			</div>		
    -			
    -		<h5>Endpoints created by jsPlumb.connect</h5>
    -		<p>If you supply an element id or selector for either the source or target , jsPlumb.connect will automatically create an Endpoint on the 
    -		given element.  These automatically created Endpoints are not marked as drag source or targets, and cannot
    -		be interacted with.  For some situations this behaviour is perfectly fine, but for more interactive UIs you should set things up using the drag and drop
    -		method discussed below.</p>
    -		<p>Given that jsPlumb.connect creates its own Endpoints in some circumstances, in order to avoid leaving orphaned Endpoints around the place, if the Connection is subsequently
    -		deleted, these created Endpoints are deleted too.  Should you want to, you can override this behaviour by setting <strong>deleteEndpointsOnDetach</strong> to
    -		false in the connect call:</p>
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({ 
    -	source:"aThirdElement", 
    -	target:"yetAnotherElement",
    -	deleteEndpointsOnDetach:false 
    -});
    -</pre>
    -</div>	
    -
    -	<h5>Detaching Connections</h5>
    -	By default, connections made with jsPlumb.connect will be detachable via the mouse.  You can prevent this by either setting an appropriate default value:
    -
    -		<div class="code">
    -<pre>
    -jsPlumb.importDefaults({ 
    -	...
    -	ConnectionsDetachable:false
    -	...
    -});
    -</pre>
    -</div>	
    -
    -	Or by specifying it on the connect call like this:
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({ 
    -	source:"aThirdElement", 
    -	target:"yetAnotherElement",
    -	detachable:false
    -});
    -</pre>
    -</div>		
    -								
    -					
    -		<h4><a id="draggableConnections">Connections using Drag and Drop</a></h4>
    -		To support drag and drop connections, you first need to set a few things up.  Every drag and drop connection needs at least a source
    -		Endpoint that the user can drag a connection from. Here's a simple example of how to create an Endpoint:
    -		<div class="code">
    -<pre>
    -var endpointOptions = { isSource:true };
    -var endpoint = jsPlumb.addEndpoint('elementId', endpointOptions);
    -</pre>		
    -</div>  	
    -	This Endpoint will act as a source for new Connections, and will use the jsPlumb defaults for its own appearance and that of any Connections that are drawn from it.							
    -	
    -		<h5>Tip: use the three-argument addEndpoint method for common data </h5>
    -		One thing that happens quite often is that you have an Endpoint whose appearance and behaviour is largely the
    -		same between usages on different elements, with just a few differences. 
    -		<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -	endpoint:"Rectangle",
    -	paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -	isSource:true,
    -	connectorStyle : { strokeStyle:"#666" },
    -	isTarget:true
    -};
    -</pre>		
    -</div>  		
    -		Notice there is no 'anchor' set.  Here we apply it to two elements, at a different location in each:
    -		<div class="code">
    -<pre>
    -jsPlumb.addEndpoint('element1', { anchor:"BottomCenter" }, exampleGreyEndpointOptions)); 
    -
    -jsPlumb.addEndpoint('element2', { anchor:"TopCenter" }, exampleGreyEndpointOptions));
    -</pre>		
    -</div>
    -
    -<p>
    -Now that you have a source Endpoint, you need to either create a target Endpoint on some element, or notify jsPlumb that you wish to make an
    -entire element a drop target.  Let's look at how to attach a target Endpoint first:
    -</p>
    -
    -		<div class="code">
    -<pre>var endpointOptions = { isTarget:true, endpoint:"Rectangle", paintStyle:{ fillStyle:"gray" } };
    -var endpoint = jsPlumb.addEndpoint("otherElementId", endpointOptions);
    -</pre>		
    -		</div>
    -		<p>
    -		This Endpoint, a gray rectangle, has declared that it can act as a drop target for Connections. 
    -</p>
    -<p>
    -jsPlumb also supports marking entire elements as drag sources or drag targets, using the <strong>makeSource</strong> and <strong>makeTarget</strong> methods.  This is a useful function for certain applications.  Each of these methods takes an Endpoint declaration which defines the Endpoint that jsPlumb will create after a Connection has been attached.  Let's take a look at makeTarget first:
    -</p>
    -<h4><a id="sourcesAndTargets">Elements as sources &amp; targets</a></h4>
    -jsPlumb also supports turning entire elements into Connection sources and targets, using the methods <strong>makeSource</strong> and <strong>makeTarget</strong>.  With these methods you mark an element as a source or target, and provide an Endpoint specification for jsPlumb to use when a Connection is established.  makeSource also gives you the ability to mark some child element as the place from which you wish to drag Connections, but still have the Connection on the main element after it has been established.
    -<p>These methods honour the jsPlumb defaults - if, for example you set up the default Anchors to be this:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.Defaults.Anchors = [ "LeftMiddle", "BottomRight" ];
    -</pre>
    -</div>
    -... and then used makeSource and makeTarget without specifying Anchor locations, jsPlumb would use "LeftMiddle" for the makeSource element and "BottomRight" for the makeTarget element.
    -<p>
    -A further thing to note about makeSource and makeTarget is that any prior calls to one of these methods is honoured by subsequent calls to jsPlumb.connect. This helps when you're building a UI that uses this functionality at runtime but which loads some initial data, and you want the statically loaded data to have the same appearance and behaviour as dynamically created connections (obviously quite a common use case).  
    -<p>For example:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -	anchor:"Continuous",
    -	endpoint:["Rectangle", { width:40, height:20 }],
    -	maxConnections:3
    -});
    -
    -...
    -
    -jsPlumb.connect({source:"el1", target:"el2"});
    -</pre>
    -</div>
    -In this example, the source of the connection will be a Rectangle of size 40x20, having a Continuous Anchor.  The source is also configured to allow a maximum of three connections.
    -<p>You can override this behaviour by setting a <strong>newConnection</strong> parameter on the connect call:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -	anchor:"Continuous",
    -	endpoint:["Rectangle", { width:40, height:20 }],
    -	maxConnections:1,
    -	onMaxConnections:function(info, originalEvent) {
    -		console.log("element is ", info.element, "maxConnections is", info.maxConnections);	
    -	}
    -});
    -
    -...
    -
    -jsPlumb.connect({ source:"el1", target:"el2", newConnection:true });
    -</pre>
    -</div>
    -Note the <strong>onMaxConnections</strong> parameter to this call - it allows you to supply a function to call if the user tries to drag a new Connection when the source has already reached capacity.
    -<h5><a id="makeTarget">jsPlumb.makeTarget</a></h5>
    -<p>
    -This method takes two arguments, the first of which specifies some element (or list of elements); the second specifies the Endpoint you wish to create on that element whenever a Connection is established on it.  In this example we will use the exact same target Endpoint we used before - the gray rectangle - but we will tell jsPlumb that the element "aTargetDiv" will be the drop target:
    -</p>
    -		<div class="code">
    -<pre>var endpointOptions = { 
    -  isTarget:true, 
    -  maxConnections:5,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" } 
    -};
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -<p>
    -	The allowed values in 'endpointOptions' are identical for both the <strong>jsPlumb.addEndpoint</strong> and <strong>jsPlumb.makeTarget</strong> methods, but makeTarget supports an extended 
    -	anchor syntax that allows you more control over the location of the target endpoint.  This is discussed below.	
    -</p>		
    -<p>Notice in the 'endpointOptions' object above there is a 'isTarget' parameter set - this may seem incongruous, since you know you're going to make some element a target.  Remember that the endpointOptions object is the information jsPlumb will use to create an Endpoint on the given target element each time a Connection is established to it. It takes the exact same format as you would pass to addEndpoint; makeTarget is essentially a deferred addEndpoint call followed by a connect call.  So in this case, we're telling jsPlumb that any Endpoints it happens to create on some element that was configured by the makeTarget call are themselves Connection targets.
    -</p>
    -<p>makeTarget also supports the maxConnections and onMaxConnections parameters, as makeSource does, but note that onMaxConnections is passed one extra parameter than its corresponding callback from makeSource - the Connection the user tried to drop:</p>
    -		<div class="code">
    -<pre>
    -jsPlumb.makeTarget("aTargetDiv", { 
    -  isTarget:true, 
    -  maxConnections:5,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" },
    -  maxConnections:3,
    -  onMaxConnections:function(info, originalEvent) {
    -	console.log("user tried to drop connection", info.connection, "on element", info.element, "with max connections", info.maxConnections);
    -  }
    -};
    -</pre>		
    -		</div>
    -
    -<h5>Unique Endpoint per Target </h5>
    -<p>jsPlumb will create a new Endpoint using the supplied information every time a new Connection is established on the target element, by default, but you can override this behaviour and tell jsPlumb that it should create at most one Endpoint, which it should attempt to use for subsequent Connections:
    -</p>
    -		<div class="code">
    -<pre>var endpointOptions = { 
    -  isTarget:true, 
    -  uniqueEndpoint:true,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" } 
    -};
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -Here, the <strong>uniqueEndpoint</strong> parameter tells jsPlumb that there should be at most one Endpoint on this element.  Notice that 'maxConnections' is not set: the default is 1, so in this setup we have told jsPlumb that "aTargetDiv" can receive one Connection and no more.
    -<h5>Deleting Endpoints on Detach</h5>
    -By default, any Endpoints created using makeTarget have <strong>deleteEndpointsOnDetach</strong> set to true, which means that once all Connections to that Endpoint are removed, the Endpoint is deleted.  You can override this by setting the flag to true on the makeTarget call:
    -		<div class="code">
    -<pre>var endpointOptions = { 
    -  isTarget:true, 
    -  maxConnections:5,
    -  uniqueEndpoint:true,
    -  deleteEndpointsOnDetach:false,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" } 
    -};
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -In this setup we have told jsPlumb to create an Endpoint the first time "aTargetElement" is connected to, and to not delete it even if there are no longer any Connections to it.  The created Endpoint will be reused for subsequent Connections, and can support a maximum of 5.
    -
    -<h5>Detaching connections made with the mouse</h5>
    -As with jsPlumb.connect, connections made with the mouse after setting up Endpoints with one of the functions we've just covered will be, by default, detachable.  You can prevent this in the jsPlumb defaults, as previously mentioned:
    -		<div class="code">
    -<pre>jsPlumb.importDefaults({ 
    -	...
    -	ConnectionsDetachable:false,
    -	...
    -});
    -</pre>		
    -		</div>
    -
    -	And you can also set this on a per-endpoint (or source/target) level, like in these examples:
    -
    -		<div class="code">
    -<pre>jsPlumb.addEndpoint("someElementId", { 
    -	connectionsDetachable:false	
    -});
    -
    -jsPlumb.makeSource("someOtherElement", {
    -	...
    -	connectionsDetachable:false,
    -	...
    -});
    -
    -jsPlumb.makeTarget("yetAnotherElement", {
    -	...
    -	connectionsDetachable:false,
    -	...
    -});
    -</pre>		
    -		</div>
    -
    -Note that in the jsPlumb defaults, by convention each word in a parameter is capitalised ("ConnectionsDetachable"), whereas for a call to one of these methods, we use camel case ("connectionsDetachable").
    -
    -
    -<h5>Target Anchors positions with makeTarget</h5>
    -<p>When using the makeTarget method, jsPlumb allows you to provide a callback function to be used to determine
    -the appropriate location of a target anchor for every new Connection dropped on the given target.  It may be the
    -case that you want to take some special action rather than just relying on one of the standard anchor mechanisms.
    -</p>
    -<p>This is achieved through an extended anchor syntax (note that this syntax is <strong>not supported</strong> in
    -the <strong>jsPlumb.addEndpoint</strong> method) that supplies a "positionFinder" to the anchor specification.  jsPlumb 
    -provides two of these by default; you can register your own on jsPlumb and refer to them by name, or just supply a function.  Here's a few examples:</p>
    -Instruct jsPlumb to place the target anchor at the exact location at which the mouse button was released on the
    -target element. Note that you tell jsPlumb the anchor is of type "Assign", and you then provide a "position"
    -parameter, which can be the name of some position finder, or a position finder function itself:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {
    -  anchor:[ "Assign", { 
    -    position:"Fixed"
    -  }]
    -});
    -</pre>
    -</div>
    -<p><strong>Fixed</strong> is one of the two default position finders provided by jsPlumb. The other is <strong>Grid</strong>:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {
    -  anchor:[ "Assign", { 
    -    position:"Grid",
    -    grid:[3,3]
    -  }]
    -});
    -</pre>
    -</div>
    -The Grid position finder takes a "grid" parameter that defines the size of the grid required. [3,3] means
    -3 rows and 3 columns.
    -<h5>Supplying your own position finder</h5>
    -<p>To supply your own position finder to jsPlumb you first need to create the callback function. First let's take a look at what the source code for the Grid position finder looks like:</p>
    -<div class="code">
    -<pre>
    -function(eventOffset, elementOffset, elementSize, constructorParams) {
    -  var dx = eventOffset.left - elementOffset.left, dy = eventOffset.top - elementOffset.top,
    -      gx = elementSize[0] / (constructorParams.grid[0]), 
    -      gy = elementSize[1] / (constructorParams.grid[1]),
    -      mx = Math.floor(dx / gx), my = Math.floor(dy / gy);
    -
    -  return [ ((mx * gx) + (gx / 2)) / elementSize[0], ((my * gy) + (gy / 2)) / elementSize[1] ];
    -}
    -</pre>
    -</div>
    -The four arguments are:
    -<ul>
    -	<li class="bullet"><strong>eventOffset</strong> - Page left/top where the mouse button was released (a JS object containing left/top members like you get from a jQuery offset call)</li>
    -	<li class="bullet"><strong>elementOffset</strong> - JS offset object containing offsets for the element on which the Connection is to be created</li>
    -	<li class="bullet"><strong>elementSize</strong> - [width, height] array of the dimensions of the element on which the Connection is to be created</li>
    -	<li class="bullet"><strong>constructorParams</strong> - the parameters that were passed to the Anchor's constructor. In the example given above, those parameters are 'position' and 'grid'; you can pass arbitrary parameters.
    -</ul>
    -The return value of this function is an array of [x, y] - proportional values between 0 and 1 inclusive, such as you can pass to a static Anchor.
    -<p>To make your own position finder you need to create a function that takes those four arguments and returns an [x, y] position for the anchor, for example:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.AnchorPositionFinders.MyFinder = function(dp, ep, es, params) {
    -	...		
    -	return [ some maths ];	
    -};
    -</pre>
    -</div>
    -Then refer to it in a makeTarget call:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {  
    -  anchor:[ "Assign", { 
    -    position:"MyFinder",
    -    myCustomParameter:"foo",
    -    anInteger:5
    -  }]
    -});
    -</pre>
    -</div>
    -
    -<h5><a id="makeSource">jsPlumb.makeSource</a></h5>
    -There are two use cases supported by this method.  The first is the case that you want to drag a Connection from the element itself and have an Endpoint attached to the element when a Connection is established.  The second is a more specialised case: you want to drag a Connection from the element, but once the Connection is established you want jsPlumb to move it so that its source is on some other element.  For an example of this second case, check out the <a href="../demo/jquery/stateMachineDemo.html">State Machine Connectors</a> demonstration.
    -<p>Here's an example code snippet for the basic use case of makeSource:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource(someDiv, {
    -  paintStyle:{ fillStyle:"yellow" },
    -  endpoint:"Blank",
    -  anchor:"BottomCenter"
    -});
    -</pre>
    -</div>
    -<p>
    -Notice again that the second argument is the same as the second argument to an addEndpoint call.  makeSource is, essentially, a type of addEndpoint call.  In this example we have told jsPlumb that we will support dragging Connections directly from 'someDiv'.  Whenever a Connection is established between 'someDiv' and some other element, jsPlumb assigns an Endpoint at BottomCenter of 'someDiv', fills it yellow, and sets that Endpoint as the newly created Connection's source. 
    -</p>		
    -<p>Configuring an element to be an entire Connection source using makeSource means that the element cannnot itself be draggable.  There would be no way for jsPlumb to distinguish between the user attempting to drag the element and attempting to drag a Connection from the element.  If your UI works in such a way that that is acceptable then you'll be fine to use this method.  But if you want to drag Connections from some element but also have the element be draggable itself, you might want to consider the second use case.  First, a code snippet:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("someConnectionSourceDiv", {
    -  paintStyle:{ fillStyle:"yellow" },
    -  endpoint:"Blank",
    -  anchor:"BottomCenter",
    -  parent:"someDiv"
    -});
    -</pre>
    -</div>
    -<p>The only difference here is the inclusion of the 'parent' parameter.  It instructs jsPlumb to configure the source Endpoint on the element specified - in this case, "someDiv" (the value of this parameter may be a String id or a selector).</p>
    -<p>Again, I'd suggest taking a look at the <a href="../demo/jquery/stateMachineDemo.html">State Machine Connectors demonstration</a> for an example of this.</p>
    -
    -<h5>Filtering drag start based on element</h5>
    -You can supply a 'filter' parameter to the makeSource call in order to get a little more fine-grained control over which parts of the element will respond to a mousedown and start a drag. Consider this markup:
    -<div class="code">
    -<pre>
    -&lt;div id="foo"&gt;
    -  FOO
    -  &lt;button&gt;click me&lt;/button&gt;
    -&lt;/div&gt;
    -</pre>
    -</div>
    -Suppose we do not want to interfere with the operation of the 'click me' button. We can supply a filter to the makeSource call to do so:
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("foo", {
    -  filter:function(event, element) {
    -    return event.target.tagName !== "BUTTON";
    -  }
    -});
    -</pre>
    -</div>
    -If the filter returns anything other than a boolean false, the drag will begin. It's important to note that only boolean false will prevent a drag. False-y values will not.
    -
    -<h5>Endpoint options with makeSource</h5>
    -<p>
    -There are many things you can set in an Endpoint options object; for a thorough list see the API documentation for Endpoint.  
    -</p>
    -<p>Here's an example of specifying that you want an Arrow overlay halfway along any Connection dragged from this Endpoint:</p>
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -  isSource:true,
    -  connectorStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  connectorOverlays: [ [ "Arrow", { location:0.5 } ] ]
    -};
    -</pre>
    -</div>
    -This is an Endpoint that moves around the element it is attached to dependent on the location of other elements in the connections it is attached to (a 'dynamic' anchor):
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -  isSource:true,
    -  connectorStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  connectorOverlays: [ [ "Arrow", { location:0.5 } ] ]
    -  anchor:[ "TopCenter","RightMiddle","BottomCenter","LeftMiddle" ]
    -};
    -</pre>
    -</div>
    -<h5><a id="targetSourceTest">Testing if an element is a target or source</a></h5>
    -You can test if some element has been made a connection source or target using these methods:
    -<ul>
    -<li class="bullet">isTarget</li>
    -<li class="bullet">isSource</li>
    -</ul>
    -<p>The return value is a boolean.</p>
    -<h5><a id="targetSourceToggle">Toggling an element as connection target or source</a></h5>
    -You can toggle the state of some source or target using these methods:
    -<ul>
    -<li class="bullet">setTargetEnabled</li>
    -<li class="bullet">setSourceEnabled</li>
    -</ul>
    -<p>The return value from these methods is the current jsPlumb instance, allowing you to chain them:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.setTargetEnabled("aDivId").setSourceEnabled($(".aSelector"));
    -</pre>
    -</div>
    -<p>Note that if you call either of these methods on an element that was not originally configured as a target or source, nothing will happen.</p>
    -<p>You can check the enabled state of some target or source using these methods:
    -<ul>
    -<li class="bullet">isTargetEnabled</li>
    -<li class="bullet">isSourceEnabled</li>
    -</ul>
    -<h5><a id="targetSourceCancel">Canceling previous makeTarget and/or makeSource calls</a></h5>
    -jsPlumb offers four methods to let you cancel previous makeTarget or makeSource calls. Each of these methods returns the current jsPlumb instance, and so can be chained:
    -<ul>
    -<li class="bullet">unmakeTarget</li>
    -<li class="bullet">unmakeSource</li>
    -<li class="bullet">unmakeEveryTarget</li>
    -<li class="bullet">unmakeEverySource</li>
    -</ul>
    -<p>These last two are analogous to the <strong>removeEveryConnection</strong> and <strong>removeEveryEndpoint</strong> methods that have been in jsPlumb for a while now.</p>
    -<p>unmakeTarget and unmakeSource both take as argument the same sorts of values that makeTarget and makeSource accept - a string id, or a selector, or an array of either of these:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.unmakeTarget("aDivId").unmakeSource($(".aSelector"));
    -</pre>
    -</div>
    -
    -
    -<h5><a id="dragOptions">Drag Options</a></h5>
    -	These are options that will be passed through to the supporting library's drag API.  jsPlumb passes everything you supply here through, inserting
    -	wrapping functions if necessary for the various lifecycle events that jsPlumb needs to know about.  So if, for example, you pass a function to be
    -	called when dragging starts, jsPlumb will wrap that function with a function that does what jsPlumb needs to do, then call yours.
    -	<p>
    -	At the time of writing, jsPlumb supports jQuery, MooTools and YUI3, and each of those libraries uses different terminology.  In addition, jQuery's API is
    -	more fully featured, providing easy support for setting the zIndex and opacity of elements being dragged, as well as the 'scope' for a drag/drop (allowing
    -	you to specify more than one type of drag/drop pair), and hover classes for when a draggable is on the move or
    -	over a droppable. If you're using jQuery you can of course just supply these values on the dragOptions; to make it easier, jsPlumb's MooTools and YUI3 adapters recognize 
    -	these options and add appropriate callbacks for you.  
    -	</p> 
    -	<p>
    -	Given that the options here are library-specific, and they are all well-documented, we're going to discuss just the three drag options
    -	that behave the same way in all (see below for hoverClass):
    -		<ul>
    -		<li><strong>opacity</strong> - the opacity of an element that is being dragged.  Should be a fraction between 0 and 1 inclusive.</li>
    -		<li><strong>zIndex</strong> - the zIndex of an element that is being dragged.</li>
    -		<li><strong>scope</strong> - the scope of the draggable.  can only be dropped on a droppable with the same scope.  this is discussed below.</li>
    -		</ul> 
    -	</p>
    -	
    -	<p>For more information about drag options, take a look at the <a href="http://docs.jquery.com/UI/Draggable" target="_blank">jQuery</a>, <a href="http://mootools.net/docs/more/Drag/Drag.Move" target="_blank">MooTools</a>, or <a href="http://developer.yahoo.com/yui/3/dd/" target="_blank">YUI3</a> docs.	
    -	</p>
    -	<p><strong>NOTE: there is an issue in Chrome that affects the 'cursor' argument to drag options in jQuery. See these links:</strong>
    -	</p><p>
    -<a href="http://forum.jquery.com/topic/draggable-cursor-option-does-not-work" target="_blank">http://forum.jquery.com/topic/draggable-cursor-option-does-not-work</a><br/>
    -<a href="http://forum.jquery.com/topic/chrome-text-select-cursor-on-drag" target="_blank">http://forum.jquery.com/topic/chrome-text-select-cursor-on-drag</a>
    -</p>
    -You should put something like this at the top of your JS to avoid it (perhaps not something so drastic as a document-wide override):	
    -<div class="code">
    -<pre>document.onselectstart = function () { return false; };</pre>
    -</div>		
    -	
    -	<h5><a id="dropOptions">Drop Options</a></h5>
    -	Drop options are treated by jsPlumb in the same way as drag options - they are passed through to the underlying library.  MooTools does not have drop options like jQuery and YUI3 do; droppable functionality in MooTools 
    -	is actually implemented by the Drag.Move class - the one used to initialise a draggable.  But when you setup an Endpoint in jsPlumb you should ignore
    -	that fact, and treat droppables like you would in jQuery or YUI3.  jsPlumb wires everything up for you under the hood.
    -	<p>There are three jQuery droppable options that jsPlumb treats as shortcuts in MooTools and YUI3, for the sake of consistency:</p>
    -		<ul>
    -			<li><strong>hoverClass</strong> - the CSS class to attach to the droppable when a draggable is hovering over it.</li>
    -			<li><strong>activeClass</strong> - the CSS class to attach to the droppable when a draggable is, um, being dragged.</li>
    -			<li><strong>scope</strong> - the scope of the draggable.  The draggable can only be dropped on a droppable with the same scope.  This is discussed below.</li>
    -		</ul>   
    -	<p>For more information about drop options when using jQuery, see <a href="http://docs.jquery.com/UI/Droppable" target="_blank">here</a>.</p>
    -		
    -	<h5><a id="dragAndDropScope">Drag and Drop Scope</a></h5>
    -		jsPlumb borrowed the concept of 'scope' from jQuery's drag/drop implementation: the notion of
    -		which draggables can be dropped on which droppables.  In jsPlumb you can provide a 'scope' entry when 
    -		creating an Endpoint.  Here's the example grey Endpoint example with 'scope' added:
    -		
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:"#666" },
    -  isSource:true,
    -  connectionStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  scope:"exampleGreyConnection"
    -};</pre>		
    -</div>  		
    -  										
    -	If you do not provide a 'scope' entry, jsPlumb uses a default scope.  Its value is accessible through this method:
    -	<div class="code">
    -<pre>
    -jsPlumb.getDefaultScope();
    -</pre>	
    -	</div>				
    -	If you want to change it for some reason you can do so with this method:
    -	<div class="code">
    -<pre>
    -jsPlumb.setDefaultScope("mySpecialDefaultScope");
    -</pre>	
    -	</div>			
    -	You can also, should you want to, provide the scope value separately on the drag/drop options, like this:
    -	<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -  isSource:true,
    -  connectionStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  dragOptions:{ scope:"dragScope" },
    -  dropOptions:{ scope:"dropScope" }
    -};</pre>		
    -</div>  
    -			
    -	</div>	
    -
    -	<div class="section">
    -		<h3><a id="parameters">Connection Parameters</a></h3>
    -		jsPlumb has a mechanism that allows you to set parameters on a per-connection basis.  This can be achieved in a few different ways:
    -
    -		<ul>
    -			<li class="bullet">Providing parameters to a jsPlumb.connect call</li>
    -			<li class="bullet">Providing parameters to an addEndpoint call to/from which a connection is subsequently established using the mouse</li>
    -			<li class="bullet">using the <strong>setParameter</strong> or <strong>setParameters</strong> method on a Connection</li>
    -		</ul>
    -		Getting parameters is achieved through either the <strong>getParameter(key)</strong> or <strong>getParameters</strong> method on a Connection.
    -		<h4><a id="connectParameters">jsPlumb.connect</a></h4>
    -		Parameters can be passed in via an object literal to a jsPlumb.connect call:
    -		<div class="code">
    -<pre>var myConnection = jsPlumb.connect({
    -	source:"foo",
    -	target:"bar",
    -	parameters:{
    -		"p1":34,
    -		"p2":new Date(),
    -		"p3":function() { console.log("i am p3"); }
    -	}
    -});
    -</pre>
    -</div>
    -Note that they can be any valid Javascript objects - it's just an object literal.  You then access them like this:
    -		<div class="code">
    -<pre>myConnection.getParameter("p3")();     // prints 'i am p3' to the console.
    -</pre>
    -</div>
    -
    -<h4><a id="addEndpointParameters">jsPlumb.addEndpoint</a></h4>
    -<p>The information in this section also applies to the <strong>makeSource</strong> and <strong>makeTarget</strong> functions</p>
    -<p>Using jsPlumb.addEndpoint, you can set parameters that will be copied in to any Connections that are established from or to the given Endpoint using the mouse.  (If you set parameters on both a source and target Endpoints and then connect them, the parameters set on the target Endpoint are copied in first, followed by those on the source. So the source Endpoint's parameters take precedence if they happen to have one or more with the same keys as those in the target).</p>
    -Consider this example:
    -		<div class="code">
    -<pre>var e1 = jsPlumb.addEndpoint("d1", {
    -	isSource:true,
    -	parameters:{
    -		"p1":34,
    -		"p2":new Date(),
    -		"p3":function() { console.log("i am p3"); }
    -	}
    -});
    -
    -var e2 = jsPlumb.addEndpoint("d2", {
    -	isTarget:true,
    -	parameters:{
    -		"p5":343,
    -		"p3":function() { console.log("FOO FOO FOO"); }
    -	}
    -});
    -
    -var conn = jsPlumb.connect({source:e1, target:e2});
    -
    -
    -</pre>
    -</div>
    -
    -'conn' will have four parameters set on it, with the value for "p3" coming from the source Endpoint:
    -		<div class="code">
    -<pre>var params = conn.getParameters();
    -console.log(params.p1);   	// 34
    -console.log(params.p2);   	// Mon May 14 2012 12:57:12 GMT+1000 (EST) (or however your console prints out a Date)
    -console.log((params.p3)()); // "i am p3"  (note: we executed the function after retrieving it)
    -console.log(params.p5);   	// 343
    -
    -</pre>
    -</div>
    -
    -
    -</div> <!-- /section -->
    -	
    -	<div class="section">
    -		<h3><a id="beforeDrop">Interceptors</a></h3>
    -		<p>
    -		Interceptors were new in jsPlumb 1.3.4.  They are basically event handlers from which you can return a value that tells jsPlumb to abort what it is that
    -		it was doing.  There are currently two interceptors supported - <strong>beforeDrop</strong>, which is called when the user has dropped a Connection onto some target, 
    -		and <strong>beforeDetach</strong>, which is called when the user is attempting to detach a Connection. 
    -		</p>
    -		<p>Interceptors can be registered via the <strong>bind</strong> method on jsPlumb just like any other event listeners, and they can also be passed in to
    -		the <strong>jsPlumb.addEndpoint</strong>, <strong>jsPlumb.makeSource</strong> and <strong>jsPlumb.makeTarget</strong>
    -		methods.  A future version of jsPlumb will support registering interceptors on Endpoints using the <strong>bind</strong> method as you can now with the jsPlumb object.
    -		Note that binding 'beforeDrop' (as an example) on jsPlumb itself is like a catch-all: it will be called every time a Connection is dropped on _any_ 
    -		Endpoint.  But passing a 'beforeDrop' callback into an Endpoint (and in the future, registering one using 'bind') constrains that callback to just the
    -		Endpoint in question.  		
    -		<h4><a id="beforeDrop">beforeDrop</a></h4>
    -		This event is fired when a new or existing connection has been dropped. Your callback is passed a JS object with these fields:
    -		<ul>
    -			<li><strong>sourceId</strong> - the id of the source element in the connection</li>
    -			<li><strong>targetId</strong> - the id of the target element in the connection</li>
    -			<li><strong>scope</strong> - the scope of the connection</li>
    -			<li><strong>connection</strong> - the actual Connection object.  You can access the 'endpoints' array in a Connection to get the Endpoints involved in the Connection, but be aware that when a Connection is being dragged, one of these Endpoints will always be a transient Endpoint that exists only for the life of the drag. To get the Endpoint on which the Connection is being dropped, use the 'dropEndpoint' member.</li>
    -			<li><strong>dropEndpoint</strong> - this is the actual Endpoint on which the Connection is being dropped.  This <strong>may be null</strong>, because it will not be set if the Connection is being dropped on an element on which makeTarget has been called. </li>
    -		</ul>
    -
    -		If you return false (or nothing) from this callback, the new Connection is aborted and removed from the UI.
    -		<h4><a id="beforeDetach">beforeDetach</a></h4>
    -		This is called when the user has detached a Connection, which can happen for a number of reasons: by default, jsPlumb allows users to drag Connections off of target Endpoints, but this can also result from a programmatic 'detach' call.  Every case is treated the same by jsPlumb, so in fact it is possible for you to write code that attempts to detach a Connection but then denies itself!  You might want to be careful with that. 
    -		<p>Note that this interceptor is passed the actual Connection object; this is different from the beforeDrop
    -		interceptor discussed above: in this case, we've already got a Connection, but with beforeDrop we are yet 
    -		to confirm that a Connection should be created.</p>
    -		<p>Returning false - or nothing - from this callback will cause the detach to be abandoned, and the Connection will be reinstated or left on its current target.</p>
    -			
    -	</div>
    -
    -		<div class="section">
    -			<h3><a id="paintstyles">Paint Styles</a></h3>
    -			Defining the appearance of Connectors, Endpoints (and Overlays, but this is deprecated in favour of using CSS classes) is achieved through a 'paintStyle' (or a quite similar name)
    -			object passed as a parameter to one of jsPlumb.connect, jsPlumb.addEndpoint, jsPlumb.makeSource or jsPlumb.makeTarget. Depending on the method you are calling, the parameter names vary.
    -			<h5>Connector Paint Styles</h5>
    -			These are specified in a <em>paintStyle</em> parameter on a call to <strong>jsPlumb.connect</strong>:
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			or in the <em>connectorPaintStyle</em> parameter on a call to <strong>jsPlumb.addEndpoint</strong> or <strong>jsPlumb.makeSource</strong>:
    -			<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div> 			
    -			 
    -
    -Notice the <em>paintStyle</em> parameter in those examples: it is the paint style for the Endpoint, which we'll discuss below.
    -
    -			<h5>Endpoint Paint Styles</h5>
    -			These are specified in a <em>paintStyle</em> parameter on a call to <strong>addEndpoint</strong>. This is the example from just above:
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -</div> 
    -			...or as the <em>endpointStyle</em> parameter to a <strong>connect</strong> call:		
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			... or as an entry in the <em>endpointStyles</em> array passed to a <strong>jsPlumb.connect</strong> call:
    -					<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  endpointStyles:[ 
    -    { fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -    { fillStyle:"green" }
    -  ],
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			or as the <em>paintStyle</em> parameter passed to a <strong>makeTarget</strong> or <strong>makeSource</strong> call:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("el1", {
    -  ...
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  ...
    -});
    -</pre>			
    -</div>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 }
    -  parent:"someOtherDivIJustPutThisHereToRemindYouYouCanDoThis"
    -});
    -</pre>			
    -			</div>						
    -	In the first example we  made "el1" into a drop target, and defined a paint style for the Endpoint jsPlumb will create when a Connection is
    -	established.  In the second we made "el1" a connection source and assigned the same values for the Endpoint jsPlumb will create when a Connection
    -	is dragged from that element.
    -	
    -	<h5>Overlay Paint Styles</h5>
    -	The preferred way to set paint styles for Overlays is to use the <em>cssClass</em> parameter in the constructor arguments of an Overlay definition.
    -	
    -	<h4>Paint Style Parameters</h4>
    -	This is the full list of parameters you can set in a paintStyle object, but note that <strong>fillStyle</strong> is ignored by Connectors,
    -	and <strong>strokeStyle</strong> is ignored by Endpoints.  Also, if you create a Connection using jsPlumb.connect and do not specify any
    -	Endpoint styles, the Endpoints will derive their fillStyle from the Connector's strokeStyle.
    -	<p>
    -		fillStyle, strokeStyle and outlineColor can be specified using any valid CSS3 syntax.
    -	</p>
    -	<ul>
    -		<li><strong>fillStyle</strong> - color for an Endpoint, eg. rgba(100,100,100,50), "blue", "#456", "#993355", rgb(34, 56, 78).</li>
    -		<li><strong>strokeStyle</strong> - color for a Connector. see fillStyle examples.</li>
    -		<li><strong>lineWidth</strong> - width of a Connector's line. An integer.</li>
    -		<li><strong>outlineWidth</strong> - width of the outline for an Endpoint or Connector. An integer.</li>
    -		<li><strong>outlineColor</strong> - color of the outline for an Endpoint or Connector. see fillStyle examples.</li>
    -		<li><strong>dashstyle</strong> - VML and SVG only. This comes from VML, and allows you to create dashed or dotted lines.  It has a 
    -			better syntax than the equivalent attribute in SVG (<em>stroke-dasharray</em>, discussed below), so jsPlumb supports this for both renderers. 
    -			The <em>dashstyle</em> attribute is specified as an array of strokes and spaces, where each value is some multiple of <em>the width of 
    -			the Connector</em>, and that's where it's better than SVG, which uses pixels.
    -			<p>
    -			<a href="http://www.w3.org/TR/NOTE-VML" target="_blank">The VML spec</a> is a good place to find valid values for dashstyle. Note that
    -			jsPlumb does not support the string values for this attribute ("solid", "dashdot", etc).
    -			</p>
    -			<p>
    -			In SVG render mode, jsPlumb uses the <em>lineWidth</em> parameter in conjunction with the values in a <em>dashstyle</em> attribute
    -			to create an appropriate value for <em>stroke-dasharray</em>.
    -			</p>
    -		</li>
    -		<li><strong>stroke-dasharray</strong> - SVG only. This is the SVG equivalent of <em>dashstyle</em>.  
    -		<a href="http://www.w3.org/TR/SVG/painting.html" target="_blank">The SVG spec</a> discusses valid values for this parameter.  But be aware
    -		that jsPlumb does not convert this into a valid <em>dashstyle</em> attribute when using the VML renderer. Better to use <em>dashstyle</em>.
    -		</li>
    -		<li><strong>stroke-dashoffset</strong> - SVG only.  This is used in SVG to specify how far into the dash pattern to start painting.  For more information,
    -		see <a href="http://www.w3.org/TR/SVG/painting.html" target="_blank">the SVG spec.</a></li>
    -		<li><strong>joinstyle</strong> - VML and SVG only.  As with <em>dashstyle</em>, this is a VML attribute that jsPlumb supports for both VML
    -		and SVG - jsPlumb turns this into a <em>stroke-linejoin</em> attribute when rendering with SVG.  This attribute specifies how you want individual
    -		segments of connectors to be joined; the VML and SVG specs both have examples of this, of which many are the same between the two, which is
    -		why jsPlumb will automatically convert this attribute into the SVG equivalent.
    -		</li>
    -		<li><strong>stroke-linejoin</strong> - SVG only.  This is the equivalent of VML's <em>joinstyle</em> attribute, but as with <em>stroke-dasharray</em>,
    -		jsPlumb does not convert this into something approriate for VML.  So, using <em>joinstyle</em> will enable you to support more browsers with
    -		less effort.		
    -		</li>
    -	</ul>
    -	
    -	<h3><a id="hoverpaintstyles">Hover Paint Styles</a></h3>
    -	 		
    -	 		Connectors and Endpoints both support the concept of a "hover" paint style - a paint style to use when the mouse is hovering over
    -	 		the component.  These are specified in the exact same format as paint styles discussed above, but hover paint styles also inherit
    -	 		any values from the main paint style.  This is because you will typically want to just change the color, or perhaps outline color, of
    -	 		a Connector or Endpoint when the mouse is hovering, but leave everything else the same.  So having hover paint styles inherit their
    -	 		values precludes you from having to define things in more than one place.
    -	 		<p>
    -	 			The naming convention adopted for hover paint styles is pretty much to insert the word 'hover' into the corresponding main paint
    -	 			style parameters.  Here are a couple of examples:
    -	 		</p> 
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -  hoverPaintStyle:{ strokeStyle:"red" },
    -  endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  endpointHoverStyle:{ fillStyle:"red" }
    -});
    -</pre>			
    -</div>
    -			In this example we specified a hover style for both the Connector, and each of its Endpoints.  Here's the same thing, but using the plural version, to specify a different hover style for each Endpoint:	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -  hoverPaintStyle:{ strokeStyle:"red" },
    -  endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  endpointHoverStyles:[ { fillStyle:"red" }, { fillStyle:"yellow" } ]
    -});
    -</pre>			
    -</div>
    -	 		Calls to <strong>addEndpoint</strong>, <strong>makeSource</strong> and <strong>makeTarget</strong> can also specify various hover paint styles:
    -	 								<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  hoverPaintStyle:{ fillStyle:"red" },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -  connectorHoverPaintStyle:{ strokeStyle:"red", outlineColor:"yellow", outlineWidth:1 }
    -});
    -
    -jsPlumb.makeSource("el2", {
    -  paintStyle:{ fillStyle:"transparent", outlineColor:"yellow", outlineWidth:1 },
    -  hoverPaintStyle:{ fillStyle:"red" },
    -  connectorPaintStyle:{ strokeStyle:"green", lineWidth:3 },
    -  connectorHoverPaintStyle:{ strokeStyle:"#678", outlineColor:"yellow", outlineWidth:1 }
    -});
    -
    -jsPlumb.makeTarget("el3", {
    -  paintStyle:{ fillStyle:"transparent", outlineColor:"yellow", outlineWidth:1 },
    -  hoverPaintStyle:{ fillStyle:"red" }
    -});
    -</pre>			
    -			</div> 
    -	 		
    -	 		In these examples we specified a hover paint style for both the Endpoint we are adding, and any Connections to/from the Endpoint.
    -	 		<p>	
    -		Note that <strong>makeTarget</strong> does not support Connector parameters. It is for creating targets only; Connector parameters will be set by the source Endpoint in any Connections that are made to the element that you turned into a target by using this method.</p>
    -	 			 		
    -			<h4><a id="gradients">Gradients</a></h4>
    -			The Canvas and SVG renderers both support gradients. The VML renderer does not.  jsPlumb uses its own syntax to define gradients, to abstract out the differences between the syntax required by canvas and that required by SVG.  
    -			<p>There are two types of gradients available - a 'linear' gradient, which consists of colored lines all
    -			going in one direction, and a 'radial' gradient, which consists of colored circles emanating from one circle to another.
    -			Because of their basic shape, jsPlumb supports only <em>linear</em> gradients for
    -			Connectors.  But for Endpoints, jsPlumb supports both <em>linear</em> and <em>radial</em> gradients.</p>
    -			
    -				<h5>Connector gradients</h5>
    -				To specify a linear gradient to use in a Connector, you must add a <em>gradient</em> object to your
    -				Connector's <em>paintStyle</em>, for instance:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : "window2",
    -	target : "window3",
    -	paintStyle:{
    -		gradient:{
    -			stops:[[0,"green"], [1,"red"]]
    -		},
    -		lineWidth:15
    -	}
    -});
    -</pre>
    -</div>
    -Here we have connected window2 to window3 with a 15 pixel wide connector that has a gradient from green to red.</p><p> Notice the <strong>gradient</strong> object and the
    -<strong>stops</strong> list inside it - the gradient consists of an arbitrary number of these "color stops".  Each color stop is comprised
    -of two values - [position, color].  Position must be a decimal value between 0 and 1 (inclusive), and indicates where the color
    -stop is situated as a fraction of the length of the entire gradient.  Valid values for
    -the colors in the <strong>stops</strong> list are the same as those that are valid for <strong>strokeStyle</strong> when describing a color.
    -			</p>
    -			As mentioned, the <strong>stops</strong> list can hold an arbitrary number of entries.  Here's an example of a gradient that goes from red to blue to green, and back again through blue to red:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle : {
    -		gradient:{
    -			stops:[[0,'red'], [0.33,'blue'], [0.66,'green'], [0.33,'blue'], [1,'red']]
    -		},
    -		lineWidth : 15
    -	}
    -});
    -</pre>
    -</div>
    -<strong>Note:</strong> when using the VML renderer, jsPlumb will simply ignore the gradient directive
    -so it is best to ensure you also supply a <em>strokeStyle</em> in your paintStyle object, to give jsPlumb something to fall back on.  If you do not supply
    -a <em>strokeStyle</em> your Connectors will be painted black.  The previous example might look like this, for instance:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle:{
    -		strokeStyle:'red',
    -		gradient:{
    -			stops:[[0,'red'], [0.33,'blue'], [0.66,'green'], [0.33,'blue'], [1,'red']]
    -		},
    -		lineWidth:15
    -	}
    -});
    -</pre>
    -</div>
    -Notice the <strong>strokeStyle:'red'</strong> directive at the beginning of the parameter list in <strong>paintStyle</strong>.
    -			<h5>Endpoint gradients</h5>
    -			Endpoint gradients are specified using the same syntax as Connector gradients.  You put the gradient specifier either in the
    -			<strong>endpoint</strong> member, or if you are specifying different Endpoints for each end of the Connector, in one or both of
    -			the values in the <strong>endpoints</strong> array.  Also, this information applies to the case that you are creating standalone
    -			Endpoints that you will be configuring for drag and drop creation of new Connections. 
    -			<p>
    -			This is an example of an Endpoint gradient that is different for each Endpoint in the Connector.  This comes from the main demo; it is
    -			the Connector joining Window 2 to Window 3:
    -			</p>
    -<div class="code">
    -<pre>var w23Stroke = 'rgb(189,11,11)';
    -jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle:{
    -		lineWidth:8,
    -		strokeStyle:w23Stroke
    -	},
    - 	anchors:[ [0.3,1,0,1], "TopCenter" ],
    - 	endpoint:"Rectangle",
    - 	endpointStyles:[
    - 		{ gradient : {stops:[[0, w23Stroke], [1, '#558822']] } },
    -    	{ gradient : {stops:[[0, w23Stroke], [1, '#882255']] } }
    -    ]
    -});
    -</pre>
    -</div>
    -The first entry in the gradient will be the one that is on the Connector end of the Endpoint.  You can of course have as many color stops as
    -you want in this gradient, just like with Connector gradients.
    -	<h5>Applying the gradient in Endpoints</h5>
    -	Only the Dot and Rectangle endpoints honour the presence of a gradient (and, remember, not in VML). The Image endpoint of course ignores a gradient
    -	as it does no painting of its own.
    -	<p>The type of gradient you will see depends on the Endpoint type:</p>
    -		<ul>
    -			<li><strong>Dot</strong> - renders a radial endpoint, with color stop 0 on the outside, progressing inwards as we move through color stops.
    -			<p>Radial gradients actually require more data than linear gradients - in a linear gradient we just move from one point to another, whereas
    -			in a radial gradient we move from one <em>circle</em> to another.  By default, jsPlumb will render a radial gradient using a source
    -			circle of the same radius as the Endpoint itself, and a target circle of 1/3 of the radius of the Endpoint (both circles share the
    -			same center as the Endpoint itself). This circle will be offset by radius/2 in each direction.</p>
    -			<p>You can supply your own values for these inside the gradient descriptor:</p>
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect({
    -	source : 'window3',
    -	target : 'window4',
    -    paintStyle:{
    -    	lineWidth:10,
    -    	strokeStyle:w34Stroke
    -    },
    -    anchors:[ "RightMiddle", "LeftMiddle" ],
    -    endpointStyle:{
    -    	gradient : {
    -    		stops:[ [0, w34Stroke], [1, w34HlStroke] ],
    -    		offset:37.5,
    -    		innerRadius:40
    -    	},
    -    	radius:55
    -    }
    - });
    -</pre>
    -</div>
    -Here we have instructed jsPlumb to make the gradient's inner radius 10px instead of the default 25/3 = 8 ish pixels, and the offset in each direction
    -will be 5px, instead of the default radius / 2 = 12.5 pixels.
    -<p>It is also possible to specify the offset and inner radius as percentages - enter the values as strings with a '%' symbol on the end:</p>
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect({
    -	source : 'window3', 
    -	target : 'window4',
    -    paintStyle:{
    -    	lineWidth:10,
    -    	strokeStyle:w34Stroke
    -    },
    -	anchors:[ "RightMiddle", "LeftMiddle" ],
    -	endpointStyle:{
    -		gradient : {
    -			stops:[ [0, w34Stroke], [1, w34HlStroke] ],
    -			offset:'68%',
    -			innerRadius:'73%'
    -		},
    -		radius:25
    -	}
    -});
    -</pre>
    -</div>
    -This will give roughly the same output as the example above (the percentages are not entirely exact).<br/><br/>
    -			</li>
    -			<li><strong>Rectangle</strong> - renders a linear endpoint, with color stop 0 closest to the end of the Connector</li>
    -		</ul>
    -		
    -<h4><a id="svgvmlcss">Styling SVG/VML using CSS</a></h4>
    -	A nice way of controlling your UI's appearance is to make use of the fact that both SVG and VML can be styled using CSS.
    -	<p>This section will be expanded in the next few weeks to give some decent examples of how to do so</p>
    -			
    -		
    -		
    -		</div>
    -
    -		<div class="section">
    -			<h3><a id="cssclasses">CSS Class Reference</a></h3>
    -			jsPlumb attaches classes to each of the UI components it creates.
    -			<p>These class names are exposed on the jsPlumb object and can be overridden if you need to do so (see the third column in the table) </p>
    -			<table width="70%" class="table">
    -				<tr><th>Component</th><th>CSS Class</th><th>jsPlumb Member</th></tr>
    -				<tr><td>Connector</td><td>_jsPlumb_connector</td><td>connectorClass</td></tr>
    -				<tr><td>Endpoint</td><td>_jsPlumb_endpoint</td><td>endpointClass</td></tr>
    -				<tr><td>Overlay</td><td>_jsPlumb_overlay</td><td>overlayClass</td></tr>
    -			</table>
    -			<p>You would typically use these to establish appropriate z-indices for your UI.</p>			
    -		</div>
    -				
    -		<div class="section">
    -			<h3><a id="animation">Animation</a></h3>
    -			jsPlumb offers an 'animate' function, which wraps the underlying animation engine for whichever library you happen to be using and
    -			inserts a callback for jsPlumb to repaint whatever it needs to at each step.  You could of course do this yourself; it's a convenience
    -			method really.
    -			<p>The method signature is:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.animate : function(el, properties, options) 
    -</pre>
    -</div>			
    -The arguments are as follows:
    -	<ul>
    -		<li><strong>el</strong> - element id, or element object from the library you're using.</li>
    -		<li><strong>properties</strong> - properties for the animation, such as duration etc.</li>
    -		<li><strong>options</strong> - options for the animation, such as callbacks etc.</li>
    -	</ul>
    -	
    -		<h4>jQuery and the 'revert' option</h4>
    -		jQuery offers a 'revert' option that you can use to instruct it to revert a drag under some condition. This is
    -		rendered in the UI as an animation that returns the drag object from its current location to wherever it started
    -		out.  It's a nice feature.  Unfortunately, the animation that runs the revert does not offer any lifecycle
    -		callback methods - no 'step', no 'complete' - so its not possible for jsPlumb to know that the revert 
    -		animation is taking place.
    -	
    -		</div>
    -		
    -			<div class="section">
    -			<h3><a id="connectionInfo">Retrieving Connection Information</a></h3>
    -			There are two ways of retrieving connection information from jsPlumb. <strong>getConnections</strong>is the original method; this method is supplemented by <a href="#selectConnections">jsPlumb.select</a>, a much more versatile variant.
    -			<h5>getConnections</h5>
    -			<p>Before you use this method you should understand jsPlumb's notion of 'scope' - documentation
    -			is <a href="#dragAndDropScope">here</a> </p>
    -			
    -			<h4>Retrieving connections for a single scope</h4>
    -			<p>To do this, you call getConnections with either no arguments, in which case jsPlumb uses the default scope, or with a string
    -			specifying one scope</p>
    -			<div class="code">
    -<pre>var connectionList = jsPlumb.getConnections();     // you get a list of Connection objects that are in the default scope.
    -</pre>			
    -			</div>
    -			Compare this with:
    -			<div class="code">
    -<pre>var connectionList = jsPlumb.getConnections("myScope");     // you get a list of Connection objects that are in "myScope".
    -</pre>			
    -			</div>
    -			<h4>More advanced filtering</h4>
    -			getConnections optionally takes a JS object specifying filter parameters, of which there are three:
    -			<ul>
    -				<li>scope - the scope(s) of the connection type(s) you wish to retrieve</li>
    -				<li>source - limits the returned connections to those that have this source id</li>
    -				<li>target - limits the returned connections to those that have this target id</li>
    -			</ul>			
    -			Each of these three parameters may be supplied as a string, which for source and target is an element id and for scope is the name of the scope, or a list of strings.  Als, you can pass "*" in as the value for any of these - a wildcard, meaning any value.  See the examples below.
    -			<p>
    -			The return value of a call to getConnection using a JS object as parameter varies on how many scopes you defined.  If you defined only a single scope then jsPlumb returns you a list of Connections in that scope.  Otherwise the return value is a dictionary whose keys are  scope names, and whose values are lists of Connections. For example, the following call:
    -			</p>
    -			<div class="code">
    -<pre>
    -	jsPlumb.getConnections({scope:["someScope", "someCustomScope"]});
    -</pre>
    -</div>
    -would result in this output:			
    -<div class="code">
    -<pre>
    -{
    -	"someScope" : [ 1..n Connections ],
    -	"someCustomScope": [ 1..m Connections ]
    -}
    -</pre>
    -</div>	
    -
    -There is an optional second parameter that tells getConnections to flatten its output and just return you an array.  The previous example with this parameter would look like this:
    -			<div class="code">
    -<pre>
    -	jsPlumb.getConnections({scope:["someScope", "someCustomScope"]}, true);
    -</pre>
    -</div>
    -would result in this output:			
    -<div class="code">
    -<pre>
    -[ 1..n Connections ]
    -</pre>
    -</div>	
    -		
    -		The following examples show the various ways you can get connection information:
    -	<ul>
    -		<li>Get all connections:
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getAllConnections();  
    -</pre>
    -</div>						
    -		</li>
    -<li>Get all connections for the default scope only (return value is a list):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections();  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given scope (return value is a list):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:"myTestScope"});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given scopes (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:["myTestScope", "yourTestScope"]});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given source (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:"mySourceElement"});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given sources (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:["mySourceElement", "yourSourceElement"]});  
    -</pre>
    -</div>						
    -		</li>		
    -<li>Get all connections for the given target (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({target:"myTargetElement"});  
    -</pre>
    -</div>						
    -		</li>				
    -<li>Get all connections for the given source and targets (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:"mySourceElement", target:["target1", "target2"]});  
    -</pre>
    -</div>						
    -		</li>				
    -		
    -<li>Get all connections for the given scope, with the given source and target (return value is a list of connections):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:'myScope", source:"mySourceElement", target:"myTargetElement"});  
    -</pre>
    -</div>						
    -		</li>				
    -		
    -	</ul>	
    -
    -		</div>
    -
    -		<div class="section">
    -			<h3><a id="selectConnections">Selecting Connections</a></h3>
    -			<strong>jsPlumb.select</strong> provides a fluid interface for working with lists of Connections.  The syntax used to specify which Connections you want is identical to that which you use for getConnections, but the return value is an object that supports most operations that you can perform on a Connection, and which is also chainable, for setter methods. Certain getter methods are also supported, but these are not chainable; they return an array consisting of all the Connections in the selection along with the return value for that Connection.
    -			<p>
    -			This is the full list of setter operations supported by jsPlumb.select:
    -			<ul>
    -				<li class="bullet">addOverlay</li>
    -				<li class="bullet">removeOverlay</li>
    -				<li class="bullet">removeOverlays</li>
    -				<li class="bullet">showOverlay</li>
    -				<li class="bullet">hideOverlay</li>
    -				<li class="bullet">showOverlays</li>
    -				<li class="bullet">hideOverlays</li>
    -				<li class="bullet">removeAllOverlays</li>
    -				<li class="bullet">setLabel</li>
    -				<li class="bullet">setPaintStyle</li>
    -				<li class="bullet">setHoverPaintStyle</li>
    -				<li class="bullet">setDetachable</li>
    -				<li class="bullet">setConnector</li>
    -				<li class="bullet">setParameter</li>
    -				<li class="bullet">setParameters</li>
    -				<li class="bullet">detach</li>
    -			</ul>
    -
    -			Each of these operations returns a selector that can be chained.
    -			</p>
    -			These is the full list of getter operations supported by jsPlumb.select:
    -			<p>
    -				<ul>
    -				<li class="bullet">getLabel</li>
    -				<li class="bullet">getOverlay</li>
    -				<li class="bullet">isHover</li>
    -				<li class="bullet">isDetachable</li>
    -				<li class="bullet">getParameter</li>
    -				<li class="bullet">getParameters</li>
    -			</ul>
    -
    -			Each of these operations returns an array whose entries are [ value, Connection ] arrays, where 'value' is the return value from the given Connection.  Remember that the return values from a getter are not chainable, but a getter may be called at the end of a chain of setters.
    -			</p>
    -
    -			<p>Some examples:</p>
    -			Select all Connections and set their hover state to be false:
    -
    -<div class="code">
    -<pre>
    -jsPlumb.select().setHover(false);
    -</pre>
    -</div>
    -			Select all Connections from "d1" and remove all Overlays:
    -
    -<div class="code">
    -<pre>
    -jsPlumb.select({source:"d1"}).removeAllOverlays();
    -</pre>
    -</div>
    -
    -Select all connections in scope "foo" and set their paint style to be a thick blue line:
    -<div class="code">
    -<pre>
    -jsPlumb.select({scope:"foo"}).setPaintStyle({ strokeStyle:"blue", lineWidth:5 });
    -</pre>
    -</div>
    -Select all Connections from "d1" and detach them:
    -<div class="code">
    -<pre>
    -jsPlumb.select({source:"d1"}).detach();
    -</pre>
    -</div>
    -<h5>.each iterator</h5>
    -The return value of jsPlumb.select has a <strong>.each</strong> function that allows you to iterate through the list, performing an operation on each one:
    -<div class="code">
    -<pre>
    -jsPlumb.select({scope:"foo"}).each(function(connection) {
    -	
    -		// do something 
    -});
    -</pre>
    -</div>
    -.each is itself chainable:
    -<div class="code">
    -<pre>
    -jsPlumb.select({scope:"foo"}).each(function(connection) {
    -	
    -		// do something 
    -
    -}).setHover(true);
    -</pre>
    -</div>
    -
    -
    -<h5>Other properties/functions</h5>
    -<ul>
    -	<li class="bullet"><strong>length</strong> - this member reports the number of Connections in the selection</li>
    -	<li class="bullet"><strong>get(idx)</strong> - this function allows you to retrieve a Connection from the selection</li>
    -</ul>
    -
    -
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="selectEndpoints">Selecting Endpoints</a></h3>
    -			<strong>jsPlumb.selectEndpoints</strong> provides a fluid interface for working with lists of Endpoints.
    -			The syntax used to specify which Endpoints you want is identical to that which you use for jsPlumb.select,
    -			and the return value is an object that supports most operations that you can perform on an Endpoint (and
    -			which is also chainable, for setter methods). Certain getter methods are also supported, but these are not
    -			chainable; they return an array consisting of all the Endpoints in the selection along with the return
    -			value for that Endpoint.
    -			<p>
    -			<p>
    -				Four parameters are supported by selectEndpoints - each of these except 'scope' can be provided as either
    -				a string, a selector, a DOM element, or an array of a mixture of these types.  'scope' can be provided as either
    -				a string or an array of strings:
    -				<ul>
    -					<li class="bullet">element - element(s) to get both source and target endpoints from</li>
    -					<li class="bullet">source - element(s) to get source endpoints from</li>
    -					<li class="bullet">target - element(s) to get target endpoints from</li>
    -					<li class="bullet">scope - scope(s) for endpoints to retrieve.</li>					
    -				</ul>
    -			</p>
    -			This is the full list of setter operations supported by jsPlumb.selectEndpoints:
    -			<ul>
    -				<li class="bullet">addOverlay</li>
    -				<li class="bullet">removeOverlay</li>
    -				<li class="bullet">removeOverlays</li>
    -				<li class="bullet">showOverlay</li>
    -				<li class="bullet">hideOverlay</li>
    -				<li class="bullet">showOverlays</li>
    -				<li class="bullet">hideOverlays</li>
    -				<li class="bullet">removeAllOverlays</li>
    -				<li class="bullet">setLabel</li>
    -				<li class="bullet">setPaintStyle</li>
    -				<li class="bullet">setHoverPaintStyle</li>				
    -				<li class="bullet">setConnector</li>
    -				<li class="bullet">setParameter</li>
    -				<li class="bullet">setParameters</li>				
    -			</ul>
    -
    -			Each of these operations returns a selector that can be chained.
    -			</p>
    -			These is the full list of getter operations supported by jsPlumb.select:
    -			<p>
    -				<ul>
    -					<li class="bullet">getLabel</li>
    -					<li class="bullet">getOverlay</li>
    -					<li class="bullet">isHover</li>				
    -					<li class="bullet">getParameter</li>
    -					<li class="bullet">getParameters</li>
    -				</ul>
    -
    -			Each of these operations returns an array whose entries are [ value, Endpoint ] arrays, where 'value'
    -			is the return value from the given Endpoint. Remember that the return values from a getter are not chainable,
    -			but a getter may be called at the end of a chain of setters.
    -			</p>
    -			<p>
    -				Other methods (not chainable):
    -				<ul>
    -					<li class="bullet">delete - deletes the Endpoints in the selection</li>
    -					<li class="bullet">detachAll - detaches all Connections from the Endpoints in the selection </li>
    -				</ul>
    -			</p>
    -
    -			<p>Some examples:</p>
    -			Select all Endpoints and set their hover state to be false:
    -
    -<div class="code">
    -<pre>
    -jsPlumb.selectEndpoints().setHover(false);
    -</pre>
    -</div>
    -			Select all source Endpoints on "d1" and remove all Overlays:
    -
    -<div class="code">
    -<pre>
    -jsPlumb.selectEndpoints({source:"d1"}).removeAllOverlays();
    -</pre>
    -</div>
    -
    -Select all Endpoints in scope "foo" and set their fill style to be blue:
    -<div class="code">
    -<pre>
    -jsPlumb.selectEndpoints({ scope:"foo" }).setPaintStyle({ fillStyle:"blue" });
    -</pre>
    -</div>
    -Select all Endpoints from "d1" and detach their Connections:
    -<div class="code">
    -<pre>
    -jsPlumb.selectEndpoints({source:"d1"}).detachAll();
    -</pre>
    -</div>
    -<h5>.each iterator</h5>
    -The return value of jsPlumb.selectEndpoints has a <strong>.each</strong> function that allows you to iterate through the list, performing an operation on each one:
    -<div class="code">
    -<pre>
    -jsPlumb.selectEndpoints({scope:"foo"}).each(function(endpoint) {
    -	
    -		// do something 
    -});
    -</pre>
    -</div>
    -.each is itself chainable:
    -<div class="code">
    -<pre>
    -jsPlumb.selectEndpoints({scope:"foo"}).each(function(endpoint) {
    -	
    -		// do something 
    -
    -}).setHover(true);
    -</pre>
    -</div>
    -
    -
    -<h5>Other properties/functions</h5>
    -<ul>
    -	<li class="bullet"><strong>length</strong> - this member reports the number of Endpoints in the selection</li>
    -	<li class="bullet"><strong>get(idx)</strong> - this function allows you to retrieve an Endpoint from the selection</li>
    -</ul>
    -
    -
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="events">Events</a></h3>
    -			jsPlumb supports binding to several different events on Connections, Endpoints and Overlays, and also on the jsPlumb object itself.  
    -			
    -			<h4><a id="jsPlumbEvents">jsPlumb Events</a></h4>
    -			<p>To bind an event to jsPlumb itself, use <strong>jsPlumb.bind(event, callback)</strong>:</p>
    -			<div class="code">
    -<pre>
    -jsPlumb.bind("jsPlumbConnection", function(info) {
    -   .. update your model in here, maybe.
    -});
    -</pre>
    -</div>
    -<p>These are the events you can bind to on the jsPlumb class:</p>
    -			<ul>
    -				<li class="bullet"><strong>jsPlumbConnection</strong> - notification a Connection was established.
    -					<p>
    -						The first argument to the callback is an object with the following properties:					
    -					</p>
    -					<div class="code">
    -<pre>
    -{
    -  connection 		: 	the new Connection.  you can register listeners on this etc.
    -  sourceId 		:	id of the source element in the Connection
    -  targetId		:	id of the target element in the Connection
    -  source		:	the source element in the Connection
    -  target		:	the target element in the Connection
    -  sourceEndpoint	:	the source Endpoint in the Connection
    -  targetEndpoint	:	the targetEndpoint in the Connection
    -}
    -</pre>
    -					</div>
    -The second argument is the original mouse event that caused the Connection, if any.
    -
    -					<p>
    -All of the source/target properties are actually available inside the Connection object, but - for one of those rubbish historical reasons - are provided separately because of a vagary of the <em>jsPlumbConnectionDetached</em> callback, which is discussed below.
    -</p>
    -				</li>
    -				<li class="bullet"><strong>jsPlumbConnectionDetached</strong> - notification a Connection was detached.  
    -					<p>
    -						As with <em>jsPlumbConnection</em>, the first argument to the callback is an object with the following properties:					
    -					</p>
    -					<div class="code">
    -<pre>
    -{
    -  connection		: 	the Connection that was detached.  
    -  sourceId		:	id of the source element in the Connection <em>before it was detached</em>
    -  targetId		:	id of the target element in the Connection before it was detached
    -  source		:	the source element in the Connection before it was detached
    -  target		:	the target element in the Connection before it was detached
    -  sourceEndpoint	:	the source Endpoint in the Connection before it was detached
    -  targetEndpoint	:	the targetEndpoint in the Connection before it was detached
    -}
    -</pre>
    -
    -					</div>	
    -The second argument is the original mouse event that caused the disconnection, if any. 					
    -								
    -					<p>
    -As mentioned above, the source/target properties are provided separately from the Connection, because this event is fired whenever
    -a Connection is either detached and abandoned, or detached from some Endpoint and attached to another.  In the latter case, the
    -Connection that is passed to this callback is in an indeterminate state (that is, the Endpoints are still in the state they are
    -in when dragging, and do not reflect static reality), and so the source/target properties give you the real story.
    -</p>
    -<p> Like I said above, this is really just for rubbish historical reasons.  I'm attempting to document my way out of it!</p>
    -				
    -				</li>
    -
    -				<li class="bullet"><strong>connectionDrag</strong> - notification an existing Connection is being dragged. The callback is passed the Connection.
    -				</li> 
    -
    -				<li class="bullet"><strong>connectionDragStop</strong> - notification a Connection drag has stopped. This is only fired for existing Connections. The callback is passed the Connection that was just dragged.
    -				</li> 
    -
    -				<li class="bullet"><strong>click</strong> - notification a Connection was clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification a Connection was double-clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>endpointClick</strong> - notification an Endpoint was clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>endpointDblClick</strong> - notification an Endpoint was double-clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>contextmenu</strong> - a right-click on some given component.  jsPlumb will report right clicks on both Connections and Endpoints; the callback is passed the component that received the right-click.				
    -				<li class="bullet"><strong>beforeDrop</strong>
    -					This event is fired when a new or existing connection has been dropped. Your callback is passed a JS object with these fields:
    -					<ul>
    -						<li><strong>sourceId</strong> - the id of the source element in the connection</li>
    -						<li><strong>targetId</strong> - the id of the target element in the connection</li>
    -						<li><strong>scope</strong> - the scope of the connection</li>
    -						<li><strong>connection</strong> - the actual Connection object.  You can access the 'endpoints' array in a Connection to get the Endpoints involved in the Connection, but be aware that when a Connection is being dragged, one of these Endpoints will always be a transient Endpoint that exists only for the life of the drag. To get the Endpoint on which the Connection is being dropped, use the 'dropEndpoint' member.</li>
    -						<li><strong>dropEndpoint</strong> - this is the actual Endpoint on which the Connection is being dropped.  This <strong>may be null</strong>, because it will not be set if the Connection is being dropped on an element on which makeTarget has been called. </li>
    -					</ul>
    -
    -					If you return false (or nothing) from this callback, the new Connection is aborted and removed from the UI.
    -				</li>					
    -				<!--li class="bullet"><strong>beforeDrag</strong>
    -					This event is fired when a new connection is about to be dragged.  Your callback function
    -					is passed the Connection that jsPlumb has just created.  If you return false (or nothing) 
    -					from this callback, the new Connection is aborted.
    -				</li-->
    -				<li class="bullet"><strong>beforeDetach</strong>
    -					This event is fired when a Connection is about to be detached, for whatever reason. Your callback function is passed the Connection that the user has just detached. Returning false from this interceptor aborts the Connection detach.
    -				</li>
    -							
    -			</ul>
    -			<h4><a id="connectionEvents">Connection Events</a></h4>		
    -			<p>To bind to an event on a Connection, you also use the <strong>bind</strong> method:</p>
    -			<div class="code">
    -<pre>
    -var connection = jsPlumb.connect({source:"d1", target:"d2"});
    -connection.bind("click", function(conn) {
    -	console.log("you clicked on ", conn);
    -});
    -</pre>
    -</div>
    -<p>These are the Connection events to which you can bind a listener:</p>
    -			<ul>
    -				<li class="bullet"><strong>click</strong> - notification a Connection was clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification a Connection was double-clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>contextmenu</strong> - a right-click on the Connection. The callback is passed the Connection and the original event.
    -				<li class="bullet"><strong>mouseenter</strong> - notification the mouse entered the Connection's path.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>mouseexit</strong> - notification the mouse exited the Connection's.  The callback is passed the Connection and the original mouse event.</li>
    -			</ul>
    -			<h4><a id="endpointEvents">Endpoint Events</a></h4>			
    -			<p>To bind to an event on a Endpoint, you again use the <strong>bind</strong> method:</p>
    -			<div class="code">
    -<pre>
    -var endpoint = jsPlumb.addEndpoint("d1", { someOptions } );
    -endpoint.bind("click", function(endpoint) {
    -	console.log("you clicked on ", endpoint);
    -});
    -</pre>
    -</div>
    -<p>These are the Endpoint events to which you can bind a listener:</p>
    -			<ul>
    -				<li class="bullet"><strong>click</strong> - notification an Endpoint was clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification an Endpoint was double-clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>contextmenu</strong> - a right-click on the Endpoint. The callback is passed the Endpoint and the original event.
    -				<li class="bullet"><strong>mouseenter</strong> - notification the mouse entered the Endpoint.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>mouseexit</strong> - notification the mouse exited the Endpoint.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>maxConnections</strong> - notification the user tried to drop a Connection on an Endpoint that already has the maximum number of Connections.  The callback is passed as first argument an object literal containing the Endpoint, the Connection the user tried to drop, and the value of maxConnections for the Endpoint, and as second argument the original mouse event.</li>
    -			</ul>
    -			<h4><a id="overlayEvents">Overlay Events</a></h4>
    -			Registering event listeners on an Overlay is a slightly different procedure - you provide them as arguments to the Overlay's 
    -			constructor.  This is because you never actually act on an 'Overlay' object.  Here's how to register a click listener on an Overlay:
    -			<div class="code">
    -<pre>jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  overlays:[
    -    [ "Label", {
    -      events:{
    -        click:function(labelOverlay, originalEvent) { 
    -          console.log("click on label overlay for :" + labelOverlay.component); 
    -        }
    -      }
    -    }],
    -    [ "Diamond", {
    -      events:{
    -        dblclick:function(diamondOverlay, originalEvent) { 
    -          console.log("double click on diamond overlay for : " + diamondOverlay.component); 
    -        }
    -      }
    -    }] 	
    -  ]
    -});
    -</pre>			
    -			</div>		
    -		Note that events registered on Diamond, Arrow or PlainArrow overlays will not fire with the Canvas renderer - they work only with the SVG and VML renderers. 
    -
    -
    -		<h4><a id="jsPlumbEvents">Unbinding Events</a></h4>
    -		<p>On the jsPlumb object and on Connections and Endpoints, you can use the <strong>unbind</strong> method to remove a listener.  This method either takes the name of the event to unbind:
    -		<div class="code">
    -<pre>
    -jsPlumb.unbind("click");
    -</pre>
    -</div>		
    -...or no argument, meaning unbind all events:
    -	<div class="code">
    -<pre>
    -var e = jsPlumb.addEndpoint("someDiv");
    -e.bind("click", function() { ... });
    -e.bind("dblclick", function() { ... });
    -
    -...
    -
    -e.unbind("click");
    -</pre>
    -</div>
    -
    -		</div>
    -
    -				<div class="section">
    -			<a id="examples"><h3>jsPlumb.connect Examples</h3></a>
    -This section provides examples of how to use the programmatic API to establish Connections. 
    -			<p>
    -			The basic syntax of a call is that you execute 'connect', providing a source and a target, and optionally a paintStyle and preferences for where you
    -			want the plumbing to be anchored on each element, as well as the type of connector to use. 
    -			</p>
    -			<ul>
    -				<li>Connect window1 to window2 with the default settings:
    -					<div class="code">
    -						<pre>jsPlumb.connect({source:"window1", target:"window2"});</pre>
    -					</div>
    -This connects 'window1' to 'window2' using the jsPlumb defaults - a red Bezier curve Connector and black Dot Endpoints.
    -				</li>
    -				<li>Connect window1 to window2 with a 15 pixel wide yellow Connector, and a slightly brighter endpoint (remember the default Endpoint is a Dot):
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -					</div>
    -				</li>
    -				
    -				<li>Connect window1 to window2 with a 15 pixel wide yellow Connector, and a slightly brighter endpoint:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -					</div>
    -				</li>				
    -				<li>Connect window3 to 'window4' with a 10 pixel wide, semi opaque blue Connector, anchored to the left middle of window3, and the right middle of window4, with a Rectangle endpoint of width 10 and height 8:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window3',
    -	target:'window4',
    -	paintStyle:{ lineWidth:10, strokeStyle:'rgba(0, 0, 200, 0.5)' },
    -	anchors:["RightMiddle", "LeftMiddle"],
    -	endpoint:[ "Rectangle", { width:10, height:8 } ]
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window2 to window3 with a default Connector from the top center of window2 to the bottom center of window3, and rectangular endpoints:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window2',
    -	target:'window3',
    -	paintStyle:{lineWidth:8, strokeStyle:'rgb(189,11,11)'},
    -	anchors:["BottomCenter", "TopCenter"],
    -	endpoint:"Rectangle"
    -});</pre>
    -					</div>
    -				</li>
    -
    -
    -				<li>Connect window1 to window2 with a 15 px wide yellow Bezier. endpoints are a slightly lighter shade of yellow.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	anchors:["BottomCenter", [0.75,0,0,-1]],
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -</div></li>
    -<li>Connect window3 to window4 with a 10px wide blue-ish half transparent Bezier. put endpoints underneath the element they attach to.
    -the endpoints have a radial gradient. both ways of specifying gradient positioning are shown here.
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect( {
    -	source:'window3',
    -	target:'window4',
    -		 paintStyle:{lineWidth:10, strokeStyle:w34Stroke},
    -		 anchors:["RightMiddle", "LeftMiddle"],
    -		 endpointStyle:{ gradient : {stops:[[0, w34Stroke], [1, w34HlStroke]], offset:17.5, innerRadius:15 }, radius:35},
    -		 //endpointStyle:{ gradient : {stops:[[0, w34Stroke], [1, w34HlStroke]], offset:'78%', innerRadius:'73%'}, radius:35 },
    -		 endpointsOnTop:false
    -	}
    -);</pre>
    -</div></li>
    -<li>Connect window2 to window3 with an 8px red Bezier and default rectangular endpoints.  see also how the first anchor is
    -specified here - this is how you create anchors in locations jsPlumb does not offer shortcuts for.
    -the endpoints in this example have linear gradients applied.
    -<div class="code">
    -<pre>var w23Stroke = 'rgb(189,11,11)';
    -jsPlumb.connect({
    -	source:'window2',
    -	target:'window3',
    -	paintStyle:{lineWidth:8,strokeStyle:w23Stroke},
    -	anchors:[[0.3,1,0,1], "TopCenter"],
    -	endpoint:"Rectangle",
    -	endpointStyles:[{ gradient : {stops:[[0, w23Stroke], [1, '#558822']] }},
    -       				{ gradient : {stops:[[0, w23Stroke], [1, '#882255']] }}]
    -});</pre>
    -</div></li>
    -
    -<li>Connect window5 to window6 from center to center, 5px wide line that is green and half transparent. the endpoints are
    -125px in radius and spill out from underneath their elements.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window5',
    -	target:'window6',
    -	anchors:["Center", "Center"],
    -	paintStyle:{lineWidth:5,strokeStyle:'rgba(0,255,0,0.5)'},
    -	endpointStyle:{radius:125}
    -});</pre>
    -</div></li>
    -
    -<li>Connect window4 to window5 from bottom right to top left, with a 7px straight line purple connector, and an image as the endpoint,
    -placed on top of the element it is connected to.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window4",
    -	target:"window5",
    -	anchors:[ "BottomRight","TopLeft" ],
    -	paintStyle:{ lineWidth:7, strokeStyle:"rgb(131,8,135)" },
    -	endpoint:[ "Image", { src:"http://morrisonpitt.com/jsPlumb/img/endpointTest1.png" } ],
    -	connector:"Straight"
    -});</pre>
    -</div></li>
    -
    -
    -				<li>Connect window5 to window6 between their center points with a semi-opaque connector, and 125px endpoints:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window5",
    -	target:"window6",
    -	anchors:[ "Center", "Center" ],
    -	paintStyle:{ lineWidth:5, strokeStyle:"rgba(0,255,0,0.5)" },
    -	endpointStyle:{ radius:125 }
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window7 to window8 with a 10 pixel wide blue Connector, anchored on the top left of window7 and the bottom right of window8:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window7",
    -	target:"window8",
    -	paintStyle:{ lineWidth:10, strokeStyle:"blue" },
    -	anchors:[ "TopLeft", "BottomRight" ]
    -});
    -</pre>
    -					</div>
    -				</li>
    -				<li>Connect the bottom right corner of window4 to the top left corner of window5, with rectangular endpoints of size 40x40 and a hover color of light blue:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window4",
    -	target:"window5",
    -	anchors:["BottomRight","TopLeft"],
    -	paintStyle:{lineWidth:7,strokeStyle:'rgb(131,8,135)'},
    -	hoverPaintStyle:{ strokeStyle:"rgb(0, 0, 135)" },
    -	endpointStyle:{ width:40, height:40 },
    -	endpoint:"Rectangle",
    -	connector:"Straight"
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window1 to window2 with the default paint settings but provide some drag options (which are passed through to the underlying library's draggable call):
    -					<div class="code">
    -						<pre>jsPlumb.connect({source:'window1', target:'window2', dragOptions:{cursor:'crosshair'}});</pre>
    -					</div>
    -				</li>
    -</div>
    -		
    -		<div class="section">
    -			<a id="dragAndDropExamples"><h3>Draggable Connections Examples</h3></a>
    -			This is a list of examples of how to use jsPlumb to create Connections using drag and drop.
    -			<p>
    -			The basic procedure is:
    -			<ol>
    -				<li>1a. Create Endpoints and register them on elements in your UI</li>
    -				<li>1b. alternatively, create a source endpoint and then make some element a drop target </li>
    -				<li>2. Drag and Drop</li>
    -			</ol>
    -			That's all there is to it.  Of course there are plenty of options you can set when doing this...it will be easier
    -			to show you some examples:
    -			</p>
    -			<ul>
    -				<li>Define an Endpoint with default appearance, that is both a source and target of new Connections:
    -<div class="code">
    -<pre>var endpointOptions = { isSource:true, isTarget:true }; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Register that Endpoint on window3, specifying that it should be located in the top center of the element:
    -<div class="code">
    -<pre>var window3Endpoint = jsPlumb.addEndpoint('window3', { anchor:"TopCenter" }, endpointOptions );  
    -</pre>
    -</div>
    -Notice here the usage of the three-argument addEndpoint  - we can reuse 'endpointOptions' with a different Anchor for another element.  This is a useful
    -practice to get into.<br/><br/>
    -				</li>
    -				
    -				<li>Now register that Endpoint on window4, specifying that it should be located in the bottom center of the element:
    -<div class="code">
    -<pre>var window4Endpoint = jsPlumb.addEndpoint('window4', { anchor:"BottomCenter" }, endpointOptions );  
    -</pre>
    -</div>
    -Now we have two Endpoints, both of which support drag and drop of new Connections.  We can use these to make a programmatic Connection, too, though:<br/><br/>
    -				</li>				
    -				
    -				<li>Connect window3 to window4 with a 25px wide yellow Bezier that has a 'curviness' of 175:
    -<div class="code">
    -<pre>jsPlumb.connect({ 
    -	source:window3Endpoint,
    -	target:window4Endpoint,
    -	connector: [ "Bezier", 175 ],
    -	paintStyle:{ lineWidth:25, strokeStyle:'yellow' }
    -});  
    -</pre>
    -</div>
    -				</li>
    -				
    -				<li>Define an Endpoint that creates Connections that are 20px wide straight lines, that is both a source and target of new Connections,
    -				and that has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Define an Endpoint that will be anchored to "TopCenter".  It creates Connections that are 20px wide straight lines, that is both a source and target of new Connections,
    -				and that has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchor:"TopCenter", 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Define an Endpoint that will create a dynamic anchor which can be positioned at "TopCenter" or "BottomCenter".  It creates Connections that are 20px wide straight lines, it is both a source and target of new Connections,
    -				and it has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchor:[ "TopCenter", "BottomCenter" ], 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Exactly the same as before, but shows how you can use "anchors" instead of "anchor", if that makes you feel happier:
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchors:[ "TopCenter", "BottomCenter" ], 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>								
    -				
    -<li>Define an Endpoint that is a 30px blue dot, creates Connections that are 20px wide straight lines, is both a source and target of new Connections,
    -				has a 'scope' of 'blueline', and has an event handler that pops up an alert (note: the event handler name means this example is jQuery - MooTools
    -				and YUI3 use different event handler names):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{fillStyle:'blue'},
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ drop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -			<li>Same example as before, but this is for MooTools, and the Endpoint can support up to 5 connections (the default is 1):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{ fillStyle:'blue' },
    -	maxConnections:5,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ onDrop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Same example again, but maxConnections being set to -1 means that the Endpoint has no maximum limit of Connections:
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", {radius:30} ],
    -	style:{ fillStyle:'blue' },
    -	maxConnections:-1,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ onDrop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Same example again, but for YUI3.  Note the drop callback is "drop:hit":
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{fillStyle:'blue'},
    -	maxConnections:-1,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ "drop:hit":function(e, ui) { alert('drop!'); } }	 	
    -}; 
    -</pre>
    -</div>				 				
    -				</li>				
    -				
    -	<li>Assign a UUID to the endpoint options created above, and add as Endpoints to "window1" and "window2":
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("window1", { uuid:"abcdefg" }, endpointOptions );
    -jsPlumb.addEndpoint("window2", { uuid:"hijklmn" }, endpointOptions );
    -</pre>
    -</div>	
    -	</li>
    -	
    -	<li>Connect the two Endpoints we just registered on "window1" and "window2":
    -<div class="code">
    -<pre>
    -jsPlumb.connect({uuids:["abcdefg", "hijklmn"]});
    -</pre>
    -</div>	
    -	</li>
    -	
    -	<li>Create a source Endpoint, register it on some element, then make some other element a Connection target:
    -<div class="code">
    -<pre>
    -var sourceEndpoint = { isSource:true, endpoint:[ "Dot", { radius:50 } ] };
    -var targetEndpoint = { endpoint:[ "Rectangle", { width:10, height:10 } ] };
    -jsPlumb.addEndpoint( "window1", sourceEndpoint );
    -jsPlumb.makeTarget( "window2", targetEndpoint );
    -</pre>
    -</div>	
    -Notice that the endpoint definition we use on the target window does not include the "isTarget:true" directive.  jsPlumb ignores that flag when
    -creating a connection using an element as the target; but if you then tried to drag another connection to the Endpoint that was created, you would
    -not be able to.  To permit that, you would set 'isTarget:true' on the targetEndpoint options defined above.
    -	
    -	</li>
    -				
    -			</ul>
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="utilityFunctions">Utility Functions</a></h3>
    -			<ul>
    -				<li>Detach window5 from all connections
    -					<div class="code">
    -						<pre>jsPlumb.detachAll("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Hide all window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.hide("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Hide all window5's connections endpoints
    -					<div class="code">
    -						<pre>jsPlumb.hide("window5", true);</pre>
    -					</div>
    -				</li>
    -				<li>Show all window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.show("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Show all window5's connections and endpoints.  Note that in the case that you call jsPlumb.show with two arguments, jsPlumb
    -				will also not make a connection visible if it determines that the other endpoint in the connection is not visible.
    -					<div class="code">
    -						<pre>jsPlumb.show("window5", hide);</pre>
    -					</div>
    -				</li>				
    -				<li>Toggle the visibility of window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.toggle("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of all of window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.repaint("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of all of window5, window6 and window11's connections
    -					<div class="code">
    -						<pre>jsPlumb.repaint( [ "window5", "window6", "window11" ] );</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of every connection
    -					<div class="code">
    -						<pre>jsPlumb.repaintEverything();</pre>
    -					</div>
    -				</li>
    -				<li>Detach every connection that the given instance of jsPlumb is managing
    -					<div class="code">
    -						<pre>jsPlumb.detachEveryConnection();</pre>
    -					</div>
    -				</li>
    -				<li>Detach all connections from "window1"
    -					<div class="code">
    -						<pre>jsPlumb.detachAllConnections("window1");</pre>
    -					</div>
    -				</li>
    -				<li>Remove the given Endpoint from element "window1", deleting its Connections. 
    -					<div class="code">
    -						<pre>jsPlumb.removeEndpoint("window1", someEndpoint);</pre>
    -					</div>
    -				</li>
    -				<li>Remove all Endpoints for the element 'window1', deleting their Connections.
    -					<div class="code">
    -						<pre>jsPlumb.removeAllEndpoints("window1");</pre>
    -					</div>
    -				</li>	
    -				<li>Removes every Endpoint managed by this instance of jsPlumb, deleting all Connections.
    -				This is the same as jsPlumb.reset(), effectively, but it does not clear out the event listeners list. 
    -					<div class="code">
    -						<pre>jsPlumb.removeEveryEndpoint();</pre>
    -					</div>
    -				</li>
    -				<li>Deletes the given Endpoint and all its Connections. 
    -					<div class="code">
    -						<pre>jsPlumb.deleteEndpoint(endpoint);</pre>
    -					</div>
    -				</li>
    -				<li>Removes every endpoint, detaches every connection, and clears the event listeners list.  Returns jsPlumb instance to its initial state.  
    -					<div class="code">
    -						<pre>jsPlumb.reset();</pre>
    -					</div>
    -				</li>	
    -				<li>Library-agnostic method to get a selector from the given selector specification.  This function is used mostly by the jsPlumb demos, to cut down on repeating code across the different libraries.  But perhaps you are writing something with jsPlumb that you also wisht to be library agnostic - this function can help you. The returned object, in each of jQuery, MooTools and YUI, has a ".each" iterator.
    -					<div class="code">
    -						<pre>var selector = jsPlumb.getSelector(".someSelector .spec");</pre>
    -					</div>					
    -				</li>
    -				<li>Set window1 to be not draggable, no matter what some jsPlumb command may request.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggable("window1", false);</pre>
    -					</div>
    -				</li>
    -				<li>Set window1 and window2 to be not draggable, no matter what some jsPlumb command may request.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggable(["window1","window2"], false);</pre>
    -					</div>
    -				</li>				
    -				<li>Initialises window1 as a draggable element (all libraries). Passes in an on drag callback
    -				<div class="code">
    -					<pre>jsPlumb.draggable("window1");</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 and window2 as draggable elements (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(["window1","window2"]);</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable("window1");</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (jQuery)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (MooTools)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($$(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (YIU3)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(Y.all(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (jQuery)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($("#window1"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (MooTools)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($("window1"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (YUI3)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(Y.one("window1"));</pre>
    -				</div>
    -				</li>
    -			</ul>
    -		</div>					
    -		
    -		<div class="section">
    -			<h3><a id="developingJsPlumb">Advanced Topics</a></h3>
    -			<h4><a id="which">Which files are which?</a></h4>
    -			In development, jsPlumb is broken up into nine scripts:
    -				<ul>
    -					<li>- jsPlumb-util-x.x.x.js
    -					<p>There are the jsPlumb utility functions (and some base classes).</p>					
    -					</li>
    -					<li>- jsPlumb-x.x.x.js
    -					<p>This is the main jsPlumb engine.</p>					
    -					</li>
    -					<li>- jsPlumb-defaults-x.x.x.js
    -					<p>This contains the default Anchor, Endpoint, Connector and Overlay implementations.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-canvas-x.x.x.js
    -					<p>This contains the HTML5 canvas render code.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-svg-x.x.x.js
    -					<p>This contains the SVG render code.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-vml-x.x.x.js
    -					<p>This contains the VML render code.</p>
    -					</li>
    -					<li>- jsPlumb-connectors-statemachine-x.x.x.js
    -					<p>This State Machine connector.</p>
    -					</li>
    -					<li>- &lt;LIBRARY_PREFIX&gt;.jsPlumb-x.x.x.js
    -					<p>This contains library-specific helper methods.  jsPlumb ships with three of these - one each for jQuery, MooTools and YUI3. See below for information on how to create a new library implementation.</p>
    -					</li>
    -					<li>- jsBezier-0.4-min.js
    -					<p>These are the Bezier curve functions; they are maintained in a separate project called <a href="http://code.google.com/p/jsbezier/">jsBezier</a></p>
    -					</li>
    -				</ul>
    -			These seven files are packaged together to form the scripts that people use, for example:
    -			<ul>
    -				<li>jquery.jsPlumb-1.3.11-all.js
    -				<p>Contains jsPlumb-1.3.11.js, jsPlumb-defaults-1.3.11.js, jsPlumb-renderers-canvas-1.3.11.js, jsPlumb-renderers-svg-1.3.11.js, jsPlumb-renderers-vml-1.3.11.js, jsPlumb-connectors-statemachine-1.3.11.js, jquery.jsPlumb-1.3.11.js and jsBezier-0.4-min.js</p>
    -				</li>
    -				<li>jquery.jsPlumb-1.3.11-all-min.js
    -				<p>A minified version of the script above (minified using the YUI Compressor)</p>
    -				</li>
    -			</ul>
    -			<h4><a id="pluggableLibrarySupport">Pluggable Library Support</a></h4>
    -		Out of the box, jsPlumb can be run on top of jQuery, MooTools or YUI3.  This is achieved by
    -		delegating several core methods - tasks such as finding an element by id, finding an element's
    -		position or dimensions, initialising a draggable, etc - to the library in question.
    -		<p>To develop one of these, your test page should include the first two scripts discussed above, and
    -		then your own script containing your library specific functionality.  The existing implementations
    -		may be documented well enough for you to create your own, but contact me if you need assistance.  If you do this, it would be
    -		great to share it with everyone...</p>   
    -		<p>There is a group of people working on an ExtJS adapter currently.  There is no schedule for when this will be completed though.</p>
    -
    -			<!-- h4><a id="customConnectors">Custom Connectors</a></h4>			
    -					
    -			You can provide your own connectors if you need to. A Connector consists of two functions, which work as a pair.  First a call is made to the <em>compute</em> function:
    -
    -			<div class="code">
    -<pre>
    -this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) {
    -	...
    -	return dimensions;
    -}
    -</pre>
    -			</div>
    -			which is expected to return a list that the <em>paint</em> function can make sense of.  The first four entries in the
    -			list <strong>must be</strong> the [x,y,width,height] values for the canvas that the connector will be drawn on; jsPlumb will
    -			use this information to size the canvas prior to calling the Connector's <em>paint</em> function.  Therefore it
    -			is the Connector's responsibility to ensure that the returned dimensions describe a large enough space for
    -			the line that will be drawn on it.</p><p>The next four elements <strong>must be</strong> the coordinates of the two endpoints of the line
    -			you are going to draw.</p><p>The remainder of the items in the returned list are arbitrary, and will
    -			vary between Connector implementations; this list is passed in to a Connector's <em>paint</em> function, so each
    -			implementation will put into the list whatever it needs to paint itself.  For instance, the straight line
    -			connector only needs the [x,y] location of each end of the line it will paint, and that is one of the required entries, so
    -			it does not have to do anything extra,  whereas the Bezier connector adds the location of the two control points.  Other types of
    -			Connectors will do whatever is appropriate for their particular situation.
    -			</p>
    -			This is the method signature for the <em>paint</em> function:
    -			<div class="code">
    -				<pre>this.paint = function(dimensions, ctx) { .. }</pre>
    -			</div>
    -
    -			here, the 'dimensions' argument to the 'paint' function is the return value of the 'compute' function.  The 'ctx' argument is the Canvas context; you
    -			will do all your drawing on this.
    -			</p>
    -			To change the connector from the default, specify it in your connect call:
    -			<div class="code">
    -				<pre>jsPlumb.connect({source:'someWindow', target:'otherWindow', connector:new jsPlumb.Connectors.Straight()});</pre>
    -			</div>
    -			note that you can use shorthand for connectors if you don't need to specify parameters to it:
    -			<div class="code">
    -				<pre>jsPlumb.connect({source:'someWindow', target:'otherWindow', connector:"Straight"});</pre>
    -			</div>
    -			This works in the same way as specifying Anchors as string does.
    -<p>
    -The section below this discusses Overlays, which allow you to decorate Connectors with
    -			things such as labels or arrows or whatever else you like. Overlays can only work with Connectors that implement a few helper functions.
    -			<h4>The concept of <strong>location</strong></h4>
    -			Before discussing the helper functions you first must be across the concept of 'location' as used by this mechanism.  The <em>location</em>
    -			on a connector can be a decimal value between 0 and 1 inclusive, which marks a proportionate amount of travel <em>along the path
    -			inscribed by the Connector</em>.  For a straight line connector this is a simple piece of maths, but for Bezier connectors it's a little
    -			bit more involved.
    -			<h4>Required Helper Methods</h4>
    -			<ul>
    -				<li><strong>pointOnPath(location)</strong> - returns an [x,y] point corresponding to the given location</li>
    -				<li><strong>pointAlongPathFrom(location, distance)</strong> - returns an [x,y] point corresponding to travelling 'distance' pixels along the connector from 'location'.</li>
    -				<li><strong>gradientAtPoint(location)</strong> - returns the connector's gradient at the given location.  For linear connectors such as Straight this is constant, but for Bezier connectors the gradient changes continually.</li>
    -				<li><strong>perpendicularToPathAt(location, distance, length)</strong> - returns a line that is perpendicular to (and centered on) the connector at 'distance' from the given location, with length 'length'.</li>
    -			</ul>
    -</p>
    -<h4><a id="customEndpoints">Custom Endpoints</a></h4>
    -<p>To create your own Endpoint implementation, you need to implement a single method:
    -				<div class="code"><pre>paint : function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { ... }</pre></div>
    -				The arguments to this method are as follows:
    -				<ul>
    -					<li>anchorPoint - [x,y] location of the anchor point on screen</li>
    -					<li>orientation - [x,y] hints for the general direction the anchor points to</li>
    -					<li>canvas - the canvas to draw into</li>
    -					<li>endpointStyle - Javascript object containing style directives as discussed above.  The contents of this are arbitrary, so if you write a new Endpoint that needs some extra settings, you can add them no hassle.</li>
    -					<li>connectorPaintStyle - the style being used to paint the associated Connector.</li>
    -				</ul>
    -			</p>
    -			<p>
    -				It is your responsibility to size and locate the canvas to suit your needs. jsPlumb provides the following
    -				helper method to assist you:
    -				<div class="code"><pre>jsPlumb.sizeCanvas(canvas, x, y, width, height);</pre></div>
    -				Allows you to locate the canvas on screen and to size it.
    -			</p>
    -<h4><a id="customOverlays">Custom Overlays</a></h4>
    -			Overlays can only work with Connectors that implement the methods <strong>pointOnPath</strong>, <strong>pointAlongPathFrom</strong>,
    -			<strong>gradientAtPoint</strong>, and <strong>perpendicularToPathAt</strong>.  These are discussed in more detail in the Custom Connectors section above.  Both of the Connectors
    -			that come with jsPlumb - Straight and Bezier -implement these methods; if you write a custom connector, or have written a custom connector, you will need to supply
    -			them.   
    -			<h4>Overlay Interface</h4>
    -			An Overlay is required to implement two methods in order to be usable by jsPlumb:
    -			<ul>
    -				<li><strong>computeMaxSize(connector, context)</strong> - returns an integer value indicating the larger of this overlay's width and height.  It is used by jsPlumb to ensure that the canvas is large enough to accomodate the overlay.  The examples below should help to clarify this.</li>
    -				<li><strong>draw(connector, ctx)</strong> - draws the overlay.  What happens in this method is up to the given implementation.</li>
    -			</ul>
    -			<h4>computeMaxSize methods</h4>
    -			This is the Arrow overlay's computeMaxSize method:
    -			<div class="code">
    -<pre>
    -this.computeMaxSize = function() { return width; }
    -</pre>			
    -			</div>
    -Here, <em>width</em> is a private member of Arrow that indicates the width of the arrow's tail.  So the Arrow overlay reports that figure as the width it needs.					
    -Contrast this with the Label Overlay's computeMaxSize method:
    -		<div class="code">
    -<pre>
    -this.computeMaxSize = function(connector, ctx) {
    -  	if (labelText) {
    -  		ctx.save();
    -        if (self.labelStyle.font) ctx.font = self.labelStyle.font;
    -         var t = ctx.measureText(labelText).width;			            
    -		// a fake text height measurement: use the width of upper case M
    -		var h = ctx.measureText("M").width;					
    -		labelPadding = self.labelStyle.padding || 0.25;
    -		labelWidth = t + (2 * t * labelPadding);
    -		labelHeight = h + (2 * h * labelPadding);
    -		ctx.restore();
    -		return Math.max(labelWidth, labelHeight);
    -  	}
    - 	return 0;
    - };
    -</pre>			
    -			</div>
    -The Label overlay has to use the context to determine how big it will be on screen.
    -
    -<h4>draw methods</h4>
    -To give you a taste for how you can interact with a connector, consider the first few lines of the Arrow overlay's draw method:
    -<div class="code">
    -<pre>
    -this.draw = function(connector, ctx) {
    -	// this is the arrow head position
    -	var hxy = connector.pointAlongPathFrom(self.loc, length / 2);		
    -	// this is the center of the tail
    -	var txy = connector.pointAlongPathFrom(self.loc, -length / 2), tx = txy[0], ty = txy[1];
    -	// this is the tail vector
    -	var tail = connector.perpendicularToPathAt(self.loc, -length / 2, width);
    -	
    -	...
    -</pre>
    -</div>
    -	<p>The first two calls to the connector use the <strong>pointAlongPathFrom</strong> method, which passes <em>self.loc</em> as the location of the
    -	point on the path, and <em>length / 2</em> as the number of pixels along the path to traverse.</p>
    -	<p><em>self.loc</em> is Arrow's internal member describing the location of the overlay, and it is a decimal (between 0 and 1 inclusive) that points to
    -	a location at some distance along the path inscribed by the connector.  So these first two calls get us [x,y] locations of points on 
    -	the connector that mark the head and tail points for the arrow.  
    -	</p>		
    -	<p>
    -	The <strong>connector.perpendicularToPathAt(self.loc, -length / 2, width)</strong> call returns a line description of a line that is perpendicular to, and centered on, the connector
    -	at "-length / 2" pixels from the given point, with the given width (width is an internal member of Arrow).  At this point, the Arrow object has the three main points it
    -	needs in order to draw itself.
    -	</p>
    -		</div-->
    -</div>
    -
    -</body>
    -
    -</html>
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.11/index.html b/demo/doc/archive/1.3.11/index.html
    deleted file mode 100644
    index da11d7389..000000000
    --- a/demo/doc/archive/1.3.11/index.html
    +++ /dev/null
    @@ -1,87 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.11 documentation</title>
    -		<link rel="stylesheet" target="contentFrame" href="/mp.css"></link>
    -		<link rel="stylesheet" target="contentFrame" href="jsPlumbDoc.css"></link>
    -	</head>
    -	<body>
    -		<div class="index section">
    -			<h3>jsPlumb 1.3.11</h3>
    -			<ul>
    -				<li><a target="contentFrame" href="content.html#summary">Summary</a></li>
    -				<li><a target="contentFrame" href="content.html#changes">Changes since version 1.3.10</a></li>
    -				
    -				<li><h4>Setup</h4></li>
    -				<li><a target="contentFrame" href="content.html#imports">Required Imports</a></li>
    -				<li><a target="contentFrame" href="content.html#renderMode">Render Mode</a></li>
    -				<li><a target="contentFrame" href="content.html#initializing">Initializing jsPlumb</a></li>				
    -				<li><a target="contentFrame" href="content.html#multipleInstances">Multiple jsPlumb Instances</a></li>
    -				<li><a target="contentFrame" href="content.html#zIndex">Z-index Considerations</a></li>
    -				<li><a target="contentFrame" href="content.html#containerDefault">Where does jsPlumb add elements?</a></li>
    -				<li><a target="contentFrame" href="content.html#elementDragging">Element dragging</a></li>
    -				
    -				<li><h4>Introduction</h4></li>
    -				<li><a target="contentFrame" href="content.html#jsPlumbBasics">Basic Concepts</a></li>
    -				<li><a target="contentFrame" href="content.html#anchors">Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#staticAnchors">Static Anchors</a></li>				
    -				<li><a target="contentFrame" href="content.html#dynamicAnchors">Dynamic Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#continuousAnchors">Continuous Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#definitions">Connector, Endpoint &amp; Overlay Definitions</a></li>
    -				<li><a target="contentFrame" href="content.html#connectors">Connectors</a></li>
    -				<li><a target="contentFrame" href="content.html#endpoints">Endpoints</a></li>
    -				<li><a target="contentFrame" href="content.html#overlays">Overlays</a></li>
    -				<li><a target="contentFrame" href="content.html#defaults">Defaults</a></li>								
    -				
    -				<li><h4>Connections</h4></li>
    -				<li><a target="contentFrame" href="content.html#simpleConnections">Programmatic Connections</a></li>
    -				<li><a target="contentFrame" href="content.html#draggableConnections">Connections using Drag and Drop</a></li>
    -				<li><a target="contentFrame" href="content.html#sourcesAndTargets">Elements as Sources &amp; Targets</a></li>
    -				<li class="sub"><a target="contentFrame" href="content.html#makeTarget">makeTarget</a></li>
    -				<li class="sub"><a target="contentFrame" href="content.html#makeSource">makeSource</a></li>
    -				<li><a target="contentFrame" href="content.html#dragOptions">Drag Options</a></li>				
    -				<li><a target="contentFrame" href="content.html#dropOptions">Drop Options</a></li>
    -				<li><a target="contentFrame" href="content.html#dragAndDropScope">Drag and Drop Scope</a></li>
    -				
    -				<li><h4>Parameters</h4></li>		
    -				<li><a target="contentFrame" href="content.html#connectParameters">jsPlumb.connect</a></li>
    -				<li><a target="contentFrame" href="content.html#addEndpointParameters">jsPlumb.addEndpoint</a></li>
    -
    -				<li><h4>Interceptors</h4></li>				
    -				<li><a target="contentFrame" href="content.html#beforeDrop">Before Drop</a></li>
    -				<li><a target="contentFrame" href="content.html#beforeDetach">Before Detach</a></li>
    -				
    -				<li><h4>Appearance</h4></li>
    -				<li><a target="contentFrame" href="content.html#paintstyles">Paint Styles</a></li>
    -				<li><a target="contentFrame" href="content.html#hoverpaintstyles">Hover Paint Styles</a></li>
    -				<li><a target="contentFrame" href="content.html#gradients">Gradients</a></li>				
    -				
    -				<li><h4>Miscellaneous</h4></li>
    -				<li><a target="contentFrame" href="content.html#cssclasses">CSS Class Reference</a></li>
    -				<li><a target="contentFrame" href="content.html#animation">Animation</a></li>
    -				<li><a target="contentFrame" href="content.html#connectionInfo">Retrieving Connection Information</a></li>
    -				<li><a target="contentFrame" href="content.html#selectConnections">Selecting Connections</a></li>
    -				<li><a target="contentFrame" href="content.html#selectEndpoints">Selecting Endpoints</a></li>
    -				
    -				<li><h4>Events</h4></li>
    -				<li><a target="contentFrame" href="content.html#events">jsPlumb Events</a></li>
    -				<li><a target="contentFrame" href="content.html#connectionEvents">Connection Events</a></li>
    -				<li><a target="contentFrame" href="content.html#endpointEvents">Endpoint Events</a></li>
    -				<li><a target="contentFrame" href="content.html#overlayEvents">Overlay Events</a></li>
    -				
    -				<li><h4>Examples</h4></li>				
    -				<li><a target="contentFrame" href="content.html#examples">Programmatic API Examples</a></li>
    -				<li><a target="contentFrame" href="content.html#dragAndDropExamples">Draggable Connections Examples</a></li>
    -				<li><a target="contentFrame" href="content.html#utilityFunctions">Utility Functions</a></li>				
    -				
    -				<li><h4>Develop</h4></li>
    -				<li><a target="contentFrame" href="content.html#which">Which files are which?</a></li>
    -				<li><a target="contentFrame" href="content.html#pluggableLibrarySupport">Pluggable Library Support</a></li>
    -				<!-- li><a target="contentFrame" href="content.html#customConnectors">Custom Connectors</a></li>
    -				<li><a target="contentFrame" href="content.html#customEndpoints">Custom Endpoints</a></li>
    -				<li><a target="contentFrame" href="content.html#customOverlays">Custom Overlays</a></li-->
    -			</ul>
    -			<p><strong>This document refers to release 1.3.11 of jsPlumb.</strong></p>
    -			<strong>26 Jul 2012</strong>
    -		</div>		
    -	</body>
    -</html>
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.11/jsPlumbDoc.css b/demo/doc/archive/1.3.11/jsPlumbDoc.css
    deleted file mode 100644
    index bbe50c8ee..000000000
    --- a/demo/doc/archive/1.3.11/jsPlumbDoc.css
    +++ /dev/null
    @@ -1,126 +0,0 @@
    -body { 
    -	background-color:white; font-size:90%;
    -	background-image:url(../img/dynamicAnchorBg.jpg); 
    -}
    -/* docs */
    -.code { background-color:#e4e5ef; 
    -border:1px solid black; margin:1em; padding:0.8em; 
    -overflow:auto;
    -font-family:courier; 
    -font-size:8pt;
    -}
    -.section { border-bottom: 1px dotted black; padding:1em;font-family:helvetica;background-color:white;color:#333;}
    -.menu { 
    -	position:fixed; 
    -	right:10em; top:1em;
    -	background-color:white;
    -	padding:0.3em;
    -	opacity:0.8;
    -	z-index:5000;
    -	filter:alpha(opacity=80);
    -		box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -a {text-decoration:none;color:#05f;font-weight:bold;font-family:helvetica;}
    -a:hover {text-decoration:underline;}
    -.window, .label { 
    -	cursor:pointer;
    -	box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -#explanation { 
    -	background-color:white;
    -	color:black;
    -	border:0.25em solid #ddd; 
    -	position:absolute;
    -	right:3.5em;
    -	top:5em;
    -	width:22em;
    -	font-size:100%;
    -	-moz-border-radius:1em;
    -	border-radius:1em;
    -	padding:1em;
    -	z-index:1000;
    -	opacity:0.9;
    -	filter:alpha(opacity=90); 
    -	font-family:helvetica;
    -		box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -.selected {
    -	color:rgb(189,11,11);
    -}
    -
    -
    -/*  these styles are what jsPlumb attaches to the various elements it creates.
    -._jsPlumb_connector {
    -border:1px solid blue;
    -}
    -._jsPlumb_endpoint {
    -border:1px solid green;
    -}
    -._jsPlumb_overlay {
    -border:1px solid yellow;
    -}
    -*/
    -
    -
    -.section li {
    -list-style-type:none;
    -}
    -
    -li.bullet {
    -	list-style-type:circle;
    -	}
    -
    -
    -.section {
    -padding-left:2em;
    -font-size:10pt;
    -}
    -.index.section {
    -padding-left:0;
    -}
    -
    -.index {
    -width:20em;
    -font-size:9pt;
    -float:left;
    -background-color:#e4eeee;
    -overflow:auto;
    -padding-left:0.5em;
    -}
    -
    -.index li {
    -list-style-type:none;
    -}
    -
    -.index ul, .index h3 {
    -padding-left:1.5em;
    -
    -}
    -
    -#demo {
    -width:80%;
    -height:50em;
    -margin-left:3em;
    -margin-top:3em;
    -position:relative;
    -}
    -
    -.sub {
    -margin-left:1em;
    -}
    -
    -.table td {
    -	text-align:center;
    -}
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.11/usage.html b/demo/doc/archive/1.3.11/usage.html
    deleted file mode 100644
    index 6c05c6772..000000000
    --- a/demo/doc/archive/1.3.11/usage.html
    +++ /dev/null
    @@ -1,10 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.11 - documentation</title>		
    -	</head>
    -	
    -	<frameset cols="250,*">
    -  		<frame src="index.html" />
    -  		<frame src="content.html" name="contentFrame" />
    -	</frameset>	
    -</html>
    diff --git a/demo/doc/archive/1.3.12/content.html b/demo/doc/archive/1.3.12/content.html
    deleted file mode 100644
    index 0251a21a0..000000000
    --- a/demo/doc/archive/1.3.12/content.html
    +++ /dev/null
    @@ -1,3652 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.12 - documentation</title>
    -		<link rel="stylesheet" href="/mp.css">
    -		<link rel="stylesheet" href="jsPlumbDoc.css">
    -	</head>
    -	<body>
    -
    -<div class="menu">
    -	<a href="http://jsplumb.org" class="mplink" target="_top">view demo</a>
    -	&nbsp;|&nbsp;<a href="mailto:simon.porritt@gmail.com" class="mplink">contact me</a>
    -	&nbsp;|&nbsp;<a href="http://jsplumb.tumblr.com" class="mplink">jsPlumb on Tumblr</a>	
    -</div>
    -
    -<!--
    -GWT
    -
    -http://fishfilosophy.blogspot.com/2011/12/how-to-connect-gwt-widgets-using.html?spref=tw
    -
    --->
    -
    -<div class="content">
    -		<div class="section">
    -			<h3><a id="summary">Summary</a></h3>
    -			jsPlumb allows you to connect elements on the screen using SVG, Canvas or VML, depending on the capabilities of the browser.
    -			<p>
    -				It can be used with jQuery, MooTools or YUI3 (or another library of your choice if you feel like implementing an adapter for it).
    -			</p>  
    -
    -			<h5>Browser Compatibility</h5>
    -			<p>jsPlumb 1.3.12 runs on everything from IE6 up.  There are some caveats, though, because of various browser/library bugs:
    -				<ul>
    -					<li class="bullet">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.
    -					<li class="bullet">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)</li>
    -					<li class="bullet">MooTools has a bug when using SVG in Firefox 11</li>
    -				<ul>
    -				
    -			</p>
    -		</div>
    -		
    -		<div class="section">		
    -			<h3><a id="changes">Changes between 1.3.12 and 1.3.11</a></h3>
    -			
    -			<h5>Backwards Compatibility</h5>
    -			No issues.
    -			
    -			<h5>New Features</h5>
    -			<ul>
    -				<li class="bullet">added the concept of Connection and Endpoint "type". A type is a collection of attributes such as paint style,
    -	hover paint style, overlays etc. For more information, see <a href="#connectionTypes">here</a></li>
    -				<li class="bullet">added 'Custom' Overlay type. You provide a 'create' method that returns an element, and jsPlumb positions it.</li>
    -				<li class="bullet">added 'setEnabled' / 'isEnabled' methods to selectEndpoint return value</li>
    -				<li class="bullet">added 'ConnectorZIndex' optional default. use this to get hovered connectors to appear on top of others.</li>
    -				<li class="bullet">added 'setVisible'/'isVisible' to both jsPlumb.select and jsPlumb.selectEndpoint return value</li>
    -				<li class="bullet">added "connection" as alias to existing "jsPlumbConnection" event. BOTH EVENTS are fired.  So do not register for them both!</li>
    -				<li class="bullet">added 'repaint' to return value from jsPlumb.select and jsPlumb.selectEndpoint</li>
    -				<li class="bullet">performance enhancements for label rendering</li>
    -			</ul>
    -
    -			<h5>Issues Resolved</h5>
    -			<ul>
    -				<li class="bullet"><strong>263</strong> - custom labels</li>
    -				<li class="bullet"><strong>269</strong> - Image Endpoint remains in DOM if removed quickly enough after creation</li>
    -				<li class="bullet"><strong>270</strong> - Connections repainted when setVisible(false) had been called</li>
    -			</ul>
    -			
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="init">Setup</a></h3>
    -
    -            <h4><a id="imports">Required Imports</a></h4>
    -			<h4>jQuery</h4>
    -			    <ul>
    -				    <li class="bullet">jQuery 1.3.x or higher.</li>
    -				    <li class="bullet">jQuery UI 1.7.x or 1.8.x (if you wish to support drag and drop).</li>
    -			    </ul>
    -			    <div class="code">
    -<pre>&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/jquery-ui.min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/jquery.jsPlumb-1.3.12-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			<h4>MooTools</h4>
    -				<ul>
    -					<li class="bullet">MooTools Core 1.2.4 or higher. jsPlumb has been tested with 1.2.4 and 1.3.3.</li>
    -					<li class="bullet">Drag.Move from MooTools More 1.2.4.4 or higher (if you wish to support drag and drop).</li>
    -				</ul>
    -			<div class="code">
    -			<pre>&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/mootools/1.3.3/mootools-yui-compressed.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO_MOO_TOOLS_MORE_1_3_2_1"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/mootools.jsPlumb-1.3.12-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			<h4>YUI3</h4>
    -			<ul>
    -				<li class="bullet">YUI 3.3.x.  jsPlumb has been tested on 3.3.0 only; it is possible other 3.x.x versions will work.</li>
    -			</ul>
    -			<div class="code">
    -			<pre>&lt;script type="text/javascript" src="http://yui.yahooapis.com/3.3.0/build/simpleyui/simpleyui-min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/yui.jsPlumb-1.3.12-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			
    -			<h4><a id="renderMode">Render Mode</a></h4>
    -			jsPlumb can use SVG, HTML5 Canvas elements or VML to render the various objects it adds to the display.  Most modern browsers support Canvas and SVG; IE&lt;9 browsers support neither.
    -			<p>
    -				By default, jsPlumb attempts to use <strong>jsPlumb.SVG</strong> as the render mode (prior to 1.3.4, the default renderer was Canvas), falling back to <strong>jsPlumb.VML</strong> if the 
    -				browser is IE&lt;9.  You can change the render mode by making this call:  
    -			</p>
    -			<div class="code">
    -<pre>jsPlumb.setRenderMode(jsPlumb.CANVAS);</pre>			
    -			</div>
    -			if, for some reason, you set the render mode to be <strong>jsPlumb.VML</strong> but you're in any browser other than IE, jsPlumb will use SVG.
    -			<p>
    -			The <strong>jsPlumb.setRenderMode</strong> method returns you the render mode that jsPlumb actually ended up setting.  Valid values for setRenderMode are:
    -			</p>
    -			<ul>
    -				<li class="bullet">jsPlumb.CANVAS</li>
    -				<li class="bullet">jsPlumb.SVG</li>
    -				<li class="bullet">jsPlumb.VML</li>
    -			</ul>			
    -			<h5>Performance Considerations</h5>
    -			<p>
    -			SVG uses less memory than Canvas and has support for mouse events built in, so I recommend using
    -			SVG unless you have a really good reason for wanting Canvas. Of course, you might consider that horrendous Safari bug a really good reason for wanting Canvas.
    -			</p>
    -			
    -			<h4><a id="initializing">Initializing jsPlumb</a></h4>
    -			You should not start making calls to jsPlumb until the DOM has been initialized - perhaps no surprises there.  With YUI, though, the asynchronous
    -			nature of the script loading process means that you are not guaranteed that jsPlumb is ready as soon as the DOM is.  To handle this,
    -			you should bind to the "ready" event on jsPlumb (or the instance of jsPlumb you are working with):
    -			<div class="code">
    -			<pre>jsPlumb.bind("ready", function() {
    -...	
    -		
    -// your jsPlumb related init code goes here
    -
    -...
    -
    -});						
    -			</pre>
    -			</div>
    -			There's a helper method that can save you approximately 9 (count them!) characters:
    -			<div class="code">
    -			<pre>jsPlumb.ready(function() {
    -...	
    -		
    -// your jsPlumb related init code goes here
    -
    -...
    -
    -});						
    -			</pre>
    -			</div>
    -			<p>
    -			For jQuery and MooTools you do not actually need to do this; by the time the DOM ready event is fired in each of those libraries you can be sure 
    -			all the JS we need has been loaded.  But in terms of explicitly calling out that you are waiting for jsPlumb, it seems like a good practice to bind to the "ready" event.
    -			</p>
    -			<p>
    -			If you bind to the "ready" event after jsPlumb has already been initialized, your callback will be executed immediately.
    -			</p>			
    -
    -				
    -<h4><a id="multipleInstances">Multiple jsPlumb instances</a></h4>
    -			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 <strong>getInstance</strong> method, for example:
    -<div class="code">
    -<pre>
    -var firstInstance = jsPlumb.getInstance();
    -</pre>
    -</div>
    -The variable 'firstInstance' can now be treated exactly as you would treat the 'jsPlumb' variable - you can set
    -defaults, call the connect method, whatever:
    -<div class="code">
    -<pre>
    -firstInstance.importDefaults({
    -  Connector : [ "Bezier", { curviness: 150 } ],
    -  Anchors : [ "TopCenter", "BottomCenter" ]
    -});
    -
    -firstInstance.connect({source:"element1", target:"element2", scope:"someScope" });
    -</pre>
    -</div>
    -
    -<strong>getInstance</strong> optionally takes an object that provides the defaults:
    -<div class="code">
    -<pre>
    -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" });
    -</pre>
    -</div>
    -
    -		<h4><a id="zIndex">Z-index Considerations</a></h4>
    -		<p>
    -			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. 
    -		</p>  
    -		<p>
    -			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).  			
    -		</p>
    -		<p>
    -			To help you organise z-indices correctly, jsPlumb adds a CSS class to each type of
    -			element it adds. They are as follows:			
    -		</p>
    -		<table style="color:black;width:70%;font-size:90%;">
    -			<tr><td><strong>type of element</strong></td><td><strong>class added</strong></td></tr>
    -			<tr><td>Endpoint</td><td>_jsPlumb_endpoint</td></tr>
    -			<tr><td>Connector</td><td>_jsPlumb_connector</td></tr>
    -			<tr><td>Overlay</td><td>_jsPlumb_overlay</td></tr>
    -		</table>
    -		
    -		<h5>Automatic z-index for Connections</h5>
    -		A common use case is the need to bring hovered Connections up on top of the other connections in your UI. From
    -		1.3.12 onwards, jsPlumb offers a way to do this:
    -		<div class="code">
    -<pre>
    -  jsPlumb.importDefaults({
    -    ...
    -    ConnectorZIndex:5
    -	...
    -  });
    -</pre>			
    -		</div>
    -		This tells jsPlumb to set the z-index of all Connections to be 5, and the z-index of any hovered Connections will
    -		be, in this case, 6.  You could argue that jsPlumb could figure out the z-index automatically, but z-index selection
    -		is typically a conscious decision in a webapp, and this approach formalises things a little better than trying
    -		to infer what the value is supposed to be.
    -		<p>To achieve this, there is actually a <strong>setZIndex</strong> method on Connections now, which is perfectly fine
    -		to call, but remember that if you change the z-index of some Connection, hover over it, and then mouse exit, resetting
    -		the hover state will mean that jsPlumb resets the z-index to the default value discussed above.</p>
    -
    -		<h4><a id="containerDefault">Where does jsPlumb add elements?</a></h4>
    -		<p>
    -			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.
    -		</p>
    -		<p>
    -			Prior to 1.3.0, jsPlumb added everything to the end of the <em>body</em> element. This
    -			has the advantage of being the most flexible arrangement in terms of supporting what
    -			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!   
    -		</p>
    -		<p>
    -			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 (<strong>not</strong> 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.
    -		</p>
    -		
    -		<p>
    -			In general, the default behaviour means that you cannot do things like this:
    -		</p>
    -		<div class="code">
    -<pre>
    -&lt;body&gt;
    -	&lt;div id="container0"&gt;
    -		&lt;div id="node0"&gt;&lt;/div&gt;
    -	&lt;/div&gt;
    -	&lt;div id="container1"&gt;
    -		&lt;div id="node1"&gt;&lt;/div&gt;
    -	&lt;/div&gt;
    -&lt;/body&gt;
    -
    -&lt;script type="text/javascript"&gt;
    -
    -	var e0 = jsPlumb.addEndpoint("node0"),
    -	e1 = jsPlumb.addEndpoint("node1");
    -
    -	jsPlumb.connect({ source:e0, target:e1 });
    -&lt;/script&gt;
    -</pre>		
    -		</div>
    -<p>
    -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):  
    -</p>
    -		<div class="code">
    -<pre>
    -&lt;body&gt;
    -	&lt;div id="container0"&gt;
    -	&lt;/div&gt;
    -	&lt;div id="container1"&gt;
    -	&lt;/div&gt;
    -&lt;/body&gt;
    -
    -&lt;script type="text/javascript"&gt;
    -
    -	var e0 = jsPlumb.addEndpoint("container0"),
    -	e1 = jsPlumb.addEndpoint("container1");
    -
    -	jsPlumb.connect({ source:e0, target:e1 });
    -&lt;/script&gt;
    -</pre>		
    -		</div>
    -	now 'e0' and 'e1' have the same parent - the <em>body</em> element. 
    -	
    -	<p>Remember that you can use the <strong>anchor</strong> parameter to an <strong>addEndpoint</strong>
    -	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. 
    -	</p>									
    -	<p><strong>Note regarding the drawEndpoints option on jsPlumb.connect</strong>: 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). 
    -	</p>
    -	
    -	<h5>Overriding the default behaviour</h5>
    -	<p>
    -From version 1.3.3, the <strong>container</strong> 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 <strong>jsPlumb.Defaults.Container</strong> property, or you can set a <strong>container</strong> parameter on calls to <strong>addEndpoint</strong> or <strong>connect</strong> (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:
    -	</p>
    -		
    -Set a container to use as the default container, using a jQuery selector (you can supply MooTools/YUI selectors if that's the library you're using), and then add an Endpoint.  The canvas (or SVG/VML element) created will be a child of the document body:
    -		<div class="code">
    -<pre>
    -jsPlumb.Defaults.Container = $("body");
    -...
    -jsPlumb.addEndpoint(someDiv, { endpoint options });
    -</pre>		
    -		</div>
    -		
    -			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":
    -		<div class="code">
    -<pre>
    -jsPlumb.Defaults.Container = "containerId";
    -...
    -jsPlumb.connect({ source:someDiv, target:someOtherDiv });
    -</pre>		
    -		</div>
    -		
    -		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":
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({ source:someDiv, target:someOtherDiv, container:$("#containerId") });
    -</pre>		
    -		</div>
    -				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":
    -		<div class="code">
    -<pre>
    -jsPlumb.addEndpoint(someDiv, { ..., container:"containerId" });
    -</pre>		
    -		</div>
    -
    -		<h4><a id="elementDragging">Element Dragging</a></h4>
    -		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 <strong>jsPlumb.draggable(...)</strong>:
    -<div class="code">
    -<pre>jsPlumb.draggable("elementId");</pre>
    -</div>
    -You can also pass a selector from your library in to this method:
    -<div class="code">
    -<pre>jsPlumb.draggable($(".someClass");</pre>
    -</div>
    -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.  A common requirement when using jQuery, for instance, is to set the containment of elements that you are making draggable.  Here's how you would tell jsPlumb to initialise some set of elements to be draggable but only inside their parent:
    -<div class="code">
    -<pre>jsPlumb.draggable($(".someClass", {
    -	containment:"parent"
    -});</pre>
    -</div>
    -		<h5>Dragging nested elements</h5>
    -jsPlumb takes nesting into account when handling draggable elements. For example, say you have this markup:
    -<div class="code">
    -<pre>
    -&lt;div id="container"&gt;
    -  &lt;div class="childType1"&gt;&lt;/div&gt;
    -  &lt;div class="childType2"&gt;&lt;/div&gt;
    -&lt;/div&gt;
    -</pre>
    -</div>
    -..and then you connect one of those child divs to something, and make "container" draggable:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:$("#container .childType1"),
    -	target:"somehere else"
    -});
    -
    -jsPlumb.draggable("container");
    -</pre>
    -</div>
    -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.
    -
    -
    -		</div>
    -				
    -		<div class="section">
    -			<h3><a id="jsPlumbBasics">Basic Concepts</a></h3>			
    -	
    -			jsPlumb is all about connecting things together, so the core abstraction in jsPlumb is the <strong>Connection</strong> object, which is itself
    -			broken down into these four concepts:
    -
    -			<ul>
    -				<li><strong>Anchor</strong> - a location, relative to an element's origin, at which an Endpoint can exist. You do not create these yourself; you 
    -				supply hints to the various jsPlumb functions, which create them as needed.  They have no visual representation; they are a logical position only. Anchors can be referenced by name, for the Anchors that jsPlumb ships with, or a four element array describing [ x, y, x orientation, y orientation ]. 
    -				See the <a href="#anchors">anchors</a> section for more detail.<br/><br/></li>
    -				<li><strong>Endpoint</strong> - the visual representation of one end of a Connection.  You can create and attach these to elements yourself, which you are required to do to support drag and drop, or have jsPlumb create them when creating a Connection programmatically using jsPlumb.connect(...).  You can also join two Endpoints programmatically, by passing them as arguments to jsPlumb.connect(...).<br/><br/></li>				
    -				<li><strong>Connector</strong> - the visual representation of the line connecting two elements in the page.  jsPlumb has four types of these 
    -				available as defaults - a Bezier curve, a straight line, 'flowchart' connectors and 'state machine connectors. You do not interact with Connectors; 
    -				you just specify definitions of them when you need to - see <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions</a> <br/><br/></li>
    -				<li><strong>Overlay</strong> - a UI component that is used to decorate a Connector, such as a Label, Arrow, etc.<br/><br/></li>
    -			</ul>
    -						
    -			<p>One Connection is made up of two Endpoints, a Connector, and zero or more Overlays working together to join two elements.</p>
    -			<p>jsPlumb's public API exposes only Connection and Endpoint, handling the creation and configuration of everything else internally. But you still need to be across the concepts encapsulated by Anchor, Connector and Overlay.
    -			</p>
    -			</div>
    -			
    -			<div class="section">
    -			<h3><a id="anchors">Anchors</a></h3>			
    -	
    -			An Anchor models the notion of where on an element a Connector should connect.  There are three main types of Anchors:
    -			<ul>
    -			<li/>
    -				<li class="bullet">'Static' anchors - these are fixed to some point on an element and do not move.  They can be specified using a string to
    -				identify one of the defaults that jsPlumb ships with, or an array describing the location (see below)<br/><br/></li>				
    -				<li class="bullet">'Dynamic' anchors - these are lists of Static anchors from which jsPlumb picks the most appropriate one each time a Connection
    -				is painted.  The algorithm used to determine the most appropriate anchor picks the one that is closest to the center of the other element in
    -				the Connection. A future version of jsPlumb might support a pluggable algorithm to make this decision.<br/><br/></li>
    -				<li class="bullet">'Continuous' anchors - These anchors, introduced in 1.3.4, are not fixed to any specific loction; they are assigned to one of
    -				the four faces of an element depending on that element's orientation to the other element in the associated Connection.  Continuous anchors are slightly more computationally intensive than Static or Dynamic anchors because jsPlumb is required to calculate the position of every Connection during a paint cycle, rather than just Connections belonging to the element in motion.  These anchors were designed for use with the StateMachine connector that was also introduced in 1.3.4, but will work with any combination of Connector and Endpoint.<br/><br/></li> 
    -			</ul>  
    -					
    -			<h4><a id="staticAnchors">Static Anchors</a></h4>		
    -			jsPlumb has nine default anchor locations you can use to specify where the Connectors connect to elements: these are the four corners of an element,
    -			the center of the element, and the midpoint of each edge of the element:  
    -			<ul>
    -				<li class="bullet">TopCenter</li>
    -				<li class="bullet">TopRight</li>
    -				<li class="bullet">RightMiddle</li>
    -				<li class="bullet">BottomRight</li>
    -				<li class="bullet">BottomCenter</li>
    -				<li class="bullet">BottomLeft</li>
    -				<li class="bullet">LeftMiddle</li>
    -				<li class="bullet">TopLeft</li>
    -			</ul>
    -			
    -Each of these string representations is just a wrapper around the underlying array-based syntax  [x, y, dx, dy], where x and y are coordinates in the interval [0,1] specifying the position of the
    -anchor, and dx and dy are coordinates in the interval [-1, 1] specifying the orientation of the curve incident to the anchor. For example, [0, 0.5, -1, 0] defines a "LeftCenter" anchor with a connector curve that emanates leftward from the anchor. Similarly, [0.5, 0, 0, -1] defines a "CenterTop" anchor with a connector curve emanating upwards. 	
    -
    -
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:"BottomCenter", ... }); 
    -</pre>
    -</div>
    -is identical to:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:[ 0.5, 1, 0, 1 ], ... }); 
    -</pre>
    -</div>		
    -In addition to supplying the location and orientation of an anchor, you can optionally supply two more parameters that define an offset in pixels from the given location.  Here's the anchor specified above, but with a 50 pixel offset below the element in the y axis:	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:[ 0.5, 1, 0, 1, 0, 50 ], ... }); 
    -</pre>
    -</div>		
    -
    -		<h4><a id="dynamicAnchors">Dynamic Anchors</a></h4>
    -	These are Anchors that can be positioned in one of a number of locations, choosing the one that is most appropriate each time something moves or is painted in the UI.
    -	<p>
    -	There is no special syntax to creating a DynamicAnchor; you just provide an array of individual Static Anchor specifications, eg:
    -	</p>
    -<div class="code">
    -<pre>
    -var dynamicAnchors = [ [ 0.2, 0, 0, -1 ],  [ 1, 0.2, 1, 0 ], 
    -			   [ 0.8, 1, 0, 1 ], [ 0, 0.8, -1, 0 ] ];
    -
    -jsPlumb.connect({...., anchor:dynamicAnchors, ... }); 			   
    -</pre>
    -</div>
    -<h4>	Default Dynamic Anchor</h4>
    -jsPlumb provides a dynamic anchor called "AutoDefault" that chooses from TopCenter, RightMiddle, BottomCenter and LeftMiddle:
    -<div class="code">
    -<pre>jsPlumb.connect({...., anchor:"AutoDefault", ... });</pre>
    -</div>
    -
    -<h4>	Location Selection</h4>
    -The initial implementation of the algorithm that decides which location to choose just calculates which location is closest to the center of the 
    -other element in the connection.  It is possible that future versions of jsPlumb could support more sophisticated choice algorithms, if the need arose.
    -<h4>Draggable Connections</h4>
    -Dynamic Anchors and Draggable Connections can interoperate: jsPlumb locks the position of a dynamic anchor when you start to drag a connection from it,
    -and unlocks it once the connection is either established or abandoned. At that point you may see the position of the dynamic anchor change, as jsPlumb
    -optimises the connection.  <p>You can see this behaviour in the <a href="../html/jquery/draggableConnectorsDemo.html" target="_blank">draggable connections</a> demonstration, when
    -you drag a connection from the blue endpoint on window 1 to the blue endpoint on window 3 - the connection is established and then window 1's blue
    -endpoint jumps down to a location that is closer to window 3.</p>
    -
    -
    -	<h4><a id="continuousAnchors">Continuous Anchors</a></h4>
    -	As discussed above, these are anchors whose positions are calculated by jsPlumb according to the orientation between elements in a Connection, and also how
    -	many other Continuous anchors happen to be sharing the element.  You specify that you want to use Continuous anchors using the string syntax you would use
    -	to specify one of the default Static Anchors, for example:
    -	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:someDiv,
    -	target:someOtherDiv,
    -	anchor:"Continuous"
    -});
    -</pre>
    -</div>
    -
    -Note in this example I specified only "anchor", rather than "anchors" - jsPlumb will use the same spec for both anchors.  But I could have said this:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:someDiv,
    -	target:someOtherDiv,
    -	anchors:["BottomCenter", "Continuous"]
    -});
    -</pre>
    -</div>	 	  
    -..which would have resulted in the source element having a Static Anchor at BottomCenter.  In practise, though, it seems the Continuous Anchors work best if both 
    -elements in a Connection are using them.
    -<p>Note also that Continuous Anchors can be specified on <strong>addEndpoint</strong> calls:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint(someDiv, {
    -	anchor:"Continuous",
    -	paintStyle:{ fillStyle:"red" }
    -});
    -</pre>
    -</div>	 	
    -...and in <strong>makeSource</strong>/<strong>makeTarget</strong>:
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource(someDiv, {
    -	anchor:"Continuous",
    -	paintStyle:{ fillStyle:"red" }
    -});
    -
    -jsPlumb.makeTarget(someDiv, {
    -	anchor:"Continuous",
    -	paintStyle:{ fillStyle:"red" }
    -});
    -</pre>
    -</div>	 
    -... and in the jsPlumb defaults:
    -<div class="code">
    -<pre>
    -jsPlumb.Defaults.Anchor = "Continuous";
    -</pre>
    -</div>  
    -					</div>		
    -												
    -<div class="section">
    -		<h4><a id="definitions">Connector, Endpoint &amp; Overlay Definitions</a></h4>
    -Before we discuss Connector, Endpoints and Overlays, a quick word on definitions:  whenever you need to define a Connector, Endpoint or Overlay, you must use a "definition" of it, rather than constructing one directly.  This definition can be either a string that nominates the artifact you want to create:
    -		<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:"Rectangle"
    -});
    -</pre>
    -		</div>
    -		or an array consisting of both the artifact's name and the arguments you want to pass to its constructor:
    -		<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Rectangle", { cssClass:"myEndpoint", width:30, height:10 } ]
    -});
    -</pre>
    -		</div>
    -There is also a three-argument method that allows you to specify two sets of parameters, which jsPlumb will merge together for you. The idea behind this is that you will often want to define common characteristics somewhere and reuse them across a bunch of different calls:
    -		<div class="code">
    -<pre>var common = {
    -	cssClass	:	"myCssClass",
    -	hoverClass	:	"myHoverClass"
    -};
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Rectangle", { width:30, height:10 }, common ]
    -});
    -</pre>
    -		</div>
    -	This syntax is supported for all Endpoint, Connector and Overlay definitions.  Here's an example using definitions for all three:	
    -		<div class="code">
    -<pre>var common = {
    -	cssClass:"myCssClass"
    -};
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Dot", { radius:5, hoverClass:"myEndpointHover" }, common ],
    -	connector:[ "Bezier", { curviness:100 }, common ],
    -	overlays: [
    -		[ "Arrow", { foldback:0.2 }, common ],
    -		[ "Label", { cssClass:"labelClass" } ]	
    -	]
    -});
    -</pre>
    -		</div>		
    -The allowed constructor parameters are different for each artifact you create, but every artifact takes a single JS object as argument, with the parameters as [key,value] pairs in that object.  See the relevant sections on <a href="#connectors">Connectors</a>, <a href="#endpoints">Endpoints</a> and <a href="#overlays">Overlays</a> for information on supported parameters.</a>
    -				
    -	</div>
    -
    -		<div class="section">
    -			<h3><a id="connectors">Connectors</a></h3>
    -			<p>Connectors are the lines that actually join elements of the UI.  jsPlumb has four connector implementations - a straight line, a Bezier curve, "flowchart", and "state machine".  The default connector is the Bezier curve.</p>
    -			<p>You optionally specify a Connector by setting the 'connector' property on a call to jsPlumb.connect, jsPlumb.addEndpoint(s), jsPlumb.makeSource or jsPlumb.makeTarget. If you do not supply a value for 'connector', the default will be used (see <a href="#defaults">defaults</a>).</p>
    -			<p>You specify Connectors using the syntax described above in <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions.</a> Allowed constructor values for each Connector type are described below:</p>		
    -			<h4><a id="bezierConnector">Bezier Connector</a></h4>
    -			<p>The Bezier Connector provides a cubic Bezier path between the two Endpoints. It supports a single constructor argument:</p>
    -			<p>
    -			<strong>curviness</strong> - Optional; defaults to 150. This defines the distance in pixels that
    -			the Bezier's control points are situated from the anchor points.  This does not mean that your
    -			connector will pass through a point at this distance from your curve.  It is a hint to how you want the
    -			curve to travel. Rather than discuss Bezier curves at length here, because they are a complex topic,
    -			refer instead to <a href="http://en.wikipedia.org/wiki/B%C3%A9zier_curve" target="_blank">Wikipedia.</a>
    -			</p>
    -			<h4><a id="straightConnector">Straight Connector</a></h4>
    -			<p>The Straight Connector draws a straight line between the two endpoints. No constructor arguments are supported; use the <strong>endpointStyle</strong> and/or <strong>cssClass</strong>/<strong>connectorClass</strong> to a <strong>connect</strong>, <strong>addEndpoint</strong>, <strong>makeSource</strong> or <strong>makeTarget</strong> call to control the appearance of one of these Connectors.  
    -			</p>
    -			<h4><a id="flowchartConnector">Flowchart Connector</a></h4>
    -			<p>This type of Connector draws a connection that consists of a series of vertical or horizontal segments - the classic flowchart look. Two constructor arguments are supported:</p>
    -			<strong>stub</strong> - this is the minimum length, in pixels, of the initial stub that emanates from an Endpoint.  This is an optional parameter, and can be either an integer, which specifies the stub fo each end of the connector, or an array of two integers, specifying the stub for the [source, target] endpoints in the connection.  This parameter is optional, and defaults to an integer with value 30 pixels.<br/>
    -			<strong>gap</strong> - optional, defaults to 0 pixels. Lets you specify a gap between the end of the connector and the elements to which it is attached.
    -			<p>This Connector supports Connection that start and end on the same element.</p>
    -			<h4><a id="stateMachineConnector">State Machine Connector</a></h4>
    -			<p>This Connector draws slightly curved lines (they are actually quadratic Bezier curves), similar to the State Machine connectors you may have seen in software like GraphViz.  Connections in which some element is both the source and the target (so-called 'loopback') are supported by these Connectors (as they are with Flowchart Connectors); in this case you get a circle. Supported parameters are:
    -			</p>
    -			<p>
    -			<strong>margin</strong> - Optional; defaults to 5.  Defines the distance from the element that the connector begins/ends.
    -			</p>
    -			<p>
    -			<strong>curviness</strong> - Optional, defaults to 10.  This has a similar effect to the curviness parameter on Bezier curves.
    -			</p>
    -			<p>
    -			<strong>proximityLimit</strong> - Optional, defaults to 80.  The minimum distance between the two ends of the Connector before it paints itself as 
    -			a straight line rather than a quadratic Bezier curve.
    -			</p>    
    -			
    -		</div>
    -		
    -			<div class="section">
    -			<h3><a id="endpoints">Endpoint Types</a></h3>
    -			<p>An Endpoint is the UI component that marks the location of an Anchor, ie. the place where a Connector joins an
    -			element.  jsPlumb comes with four Endpoint implementations - <strong>Dot</strong>, <strong>Rectangle</strong>, <strong>Blank</strong> and <strong>Image</strong>. You optionally specify Endpoint properties using the
    -			<strong>endpoint</strong> parameter in a call to jsPlumb.connect, jsPlumb.addEndpoint, jsPlumb.makeSource or jsPlumb.makeTarget.
    -			</p>  
    -				<p>As with Connectors and Overlays, you specify Endpoints using the syntax described above in <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions.</a></p>
    -			
    -			The four available Endpoint types, and their constructor arguments, are as follows:
    -					<h4><a id="dotEndpoint">Dot Endpoint</a></h4>
    -					This Endpoint draw a dot on the screen. It supports three constructor parameters:
    -					<p>
    -						<strong>radius</strong> - Optional; defaults to 10 pixels. Defines the radius of the dot.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="rectangleEndpoint">Rectangle Endpoint</a></h4>
    -						Draws a rectangle. Supported constructor parameters are:
    -					<p>
    -						<strong>width</strong> - Optional; defaults to 20 pixels. Defines the width of the rectangle.
    -					</p>
    -					<p>
    -						<strong>height</strong> - Optional; defaults to 20 pixels. Defines the height of the rectangle.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="imageEndpoint">Image Endpoint</a></h4>
    -						Draws an image from a given URL.  This Endpoint supports three constructor parameters:  
    -					<p>
    -						<strong>src</strong> - Required.  Specifies the URL of the image to use.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="blankEndpoint">Blank Endpoint</a></h4>
    -						Does not draw anything visible to the user.  As discussed previously, this Endpoint is probably not what you want if you need your users to be able to drag existing Connections - for that, use a Rectangle or Dot Endpoint and assign to it a CSS class that causes it to be transparent.
    -																			
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="overlays">Overlay Types</a></h3>
    -			<p>
    -			Overlays are UI elements that are painted onto connections, such as labels or arrows.
    -			jsPlumb comes with four defaults:
    -			</p>
    -			<ul>
    -				<li><strong>Arrow</strong> - a configurable arrow that is painted at some point along the connector.  You can control the length and width of the Arrow, the 'foldback' point - a point the tail points fold back into, and the direction (allowed values are 1 and -1; 1 is the default and means point in the direction of the connection)</li>
    -				<li><strong>Label</strong> - a configurable label that is painted at some point along the connector.</li>
    -				<li><strong>PlainArrow</strong> - an Arrow shaped as a triangle, with no foldback.</li>
    -				<li><strong>Diamond</strong> - as the name suggests, a diamond.</li>
    -			</ul> 
    -			The last two are actually just configured instances of the generic Arrow overlay (see examples). 
    -			
    -			<p>All Overlays support these two methods for getting/setting their location (location is either a number between 0 and 1 inclusive, indicating some point along the path inscribed by the associated Connector, or an absolute number of pixels, where negative values mean distance from the target of the connection):</p>
    -			<ul>
    -				<li class="bullet"><strong>getLocation</strong> - returns the current location</li>
    -				<li class="bullet"><strong>setLocation</strong> - sets the current location; valid values are decimals in the range 0-1 inclusive, or an integer less than 0 (to express distance from the target), or an integer greater than 1 (to express distance from the source)</li> 
    -			</ul>
    -			
    -			<p>You can specify one or more overlays when making a call to jsPlumb.connect, jsPlumb.addEndpoint or jsPlumb.makeSource (but not jsPlumb.makeTarget; overlays
    -			are always derived from what the source of a Connection defines)  The three cases are discussed below:</p>
    -			1. Specifying one or more overlays on a jsPlumb.connect call. In this example we'll create an Arrow with the default options for an Arrow, and
    -			a label with the text "foo":
    -			<div class="code">
    -			<pre>jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});			
    -</pre>
    -			</div>
    -			This connection will have an arrow located halfway along it, and the label "foo" one quarter of the way along.  Notice the <strong>id</strong>
    -			parameter; it can be used later if you wish to remove the Overlay or change its visibility (see below).<br/><br/> 
    -			Another example, this time with an absolute location of 50 pixels from the source:
    -			<div class="code">
    -			<pre>jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:50 }, id:"myLabel" ]
    -	],
    -	...
    -});			
    -</pre>
    -			</div>
    -			
    -			2. Specifying one or more overlays on a jsPlumb.addEndpoint call.  Note in this example that we use the parameter 'connectorOverlays' and not
    -			'overlays' as in the last example.  This is because 'overlays' would refer to Endpoint Overlays:
    -			<div class="code">
    -			<pre>jsPlumb.addEndpoint("someDiv", {
    -	...
    -	connectorOverlays:[ 
    -		[ "Arrow", { width:10, length:30, location:1, id:"arrow" } ], 
    -		[ "Label", { label:"foo", id:"label" } ]
    -	],
    -	...
    -});</pre>
    -			</div>	
    -			This connection will have a 10x30 Arrow located right at the head of the connection, and the label "foo" located at the halfway point. Again,
    -			note that <strong>id</strong> parameter passed in to each Overlay spec.<br/><br/>
    -			
    -			3. Specifying one or more overlays on a jsPlumb.makeSource call.  Note in this example that we again use the parameter 'connectorOverlays' and not
    -			'overlays'.  The 'endpoint' parameter to jsPlumb.makeSource supports everything you might pass to the second argument of a jsPlumb.addEndpoint call:
    -			<div class="code">
    -			<pre>jsPlumb.makeSource("someDiv", {
    -	...
    -	endpoint:{
    -		connectorOverlays:[ 
    -			[ "Arrow", { width:10, length:30, location:1, id:"arrow" } ], 
    -			[ "Label", { label:"foo", id:"label" } ]
    -		]
    -	}
    -	...
    -});</pre>
    -			</div>	
    -			This connection will have a 10x30 Arrow located right at the head of the connection, and the label "foo" located at the halfway point. Again,
    -			note that <strong>id</strong> parameter passed in to each Overlay spec.<br/><br/>
    -			
    -			 	
    -					<h4><a id="arrowOverlay">Arrow Overlay</a></h4>
    -					<p>This overlay draws an arrow, using four points: the head and two tail points, and a 'foldback' point, which permits the tail
    -					of the arrow to be indented. Available constructor arguments for this Overlay are </p>
    -					<ul>
    -					<li><strong>width</strong> - width of the tail of the arrow</li>
    -					<li><strong>length</strong> - distance from the tail of the arrow to the head</li>
    -					<li><strong>location</strong> - where, either as a proportional value from 0 to 1 inclusive, or as an absolute value (negative values mean distance from target; positive values greater than 1 mean distance from source) the Arrow should appear on the Connector</li>
    -					<li><strong>direction</strong> - which way to point. Allowed values are 1 (the default, meaning forwards) and -1, meaning backwards </li>
    -					<li><strong>foldback</strong> - how far along the axis of the arrow the tail points foldback in to. Default is 0.623.</li>
    -					<li><strong>paintStyle</strong> - a style object in the form used for paintStyle values for Endpoints and Connectors</li>
    -					</ul>
    -					<h4><a id="plainArrowOverlay">PlainArrow Overlay</a></h4>
    -					<p>This is just a specialized instance of Arrow in which jsPlumb hardcodes 'foldback' to 1, meaning the tail of the Arrow is a flat edge.  All of the constructor parameters from Arrow apply for PlainArrow.</p>
    -					<h4><a id="diamondOverlay">Diamond Overlay</a></h4>
    -					<p>This is a specialized instance of Arrow in which jsPlumb hardcodes 'foldback' to 2, meaning the Arrow turns into a Diamond.  All of the constructor parameters from Arrow apply for Diamond.</p>
    -					<h4><a id="labelOverlay">Label Overlay</a></h4>
    -					This provides a text label to decorate Connectors with.  The available constructor arguments are:
    -					<ul>
    -						<li><strong>label</strong> - The text to display.  You can provide a function here instead of plain text: it is passed the Connection as an argument, and it should return a String.</li>
    -						<li><strong>cssClass</strong> - Optional css class to use for the Label.  This is now preferred over using the 'labelStyle' parameter.</li>
    -						<li><strong>labelStyle</strong> - (deprecated, use cssClass instead) Optional arguments for the label's appearance.  Valid entries in this JS object are:
    -							<ul>
    -								<li><em>font</em> - a font string in a format suitable for the Canvas element</li>
    -								<li><em>fillStyle</em> - the color to fill the label's background with. Optional.</li>
    -								<li><em>color</em> - the color of the label's text. Optional.</li>
    -								<li><em>padding</em> - optional padding for the label. This is expressed as a proportion of the width of the label, not in pixels or ems.</li>
    -								<li><em>borderWidth</em> - optional width in pixels for the label's border. Defaults to 0.</li>
    -								<li><em>borderStyle</em> - optional. The color to paint the border, if there is one.</li>
    -								
    -							</ul>
    -						
    -						</li>
    -						<li><strong>location</strong> - As for Arrow Overlay.  Where, either proportionally from 0 to 1 inclusive, or as an absolute offset from either source or target, the label should appear.</li>
    -					</ul>
    -
    -					The Label overlay offers two methods - getLabel and setLabel - for accessing/manipulating its content dynamically:
    -<div class="code">
    -<pre>
    -var c = jsPlumb.connect({
    -	source:"d1", 
    -	target:"d2", 
    -	overlays:[
    -		[ "Label", {label:"FOO", id:"label"}]
    -	]	
    -});					
    -
    -...
    -
    -var label = c.getOverlay("label");
    -console.log("Label is currently", label.getLabel());
    -label.setLabel("BAR");
    -console.log("Label is now", label.getLabel());
    -</pre>
    -</div>
    -In this example you can see that the Label Overlay is assigned an id of "label" in the connect call, and then retrieved using that id in the call to Connection's getOverlay method.
    -<p>Both Connections and Endpoints support Label Overlays, and because changing labels is quite a common operation, <strong>setLabel</strong> and <strong>getLabel</strong> methods have been added to these objects:
    -<div class="code">
    -<pre>
    -var conn = jsPlumb.connect({
    -	source:"d1", 
    -	target:"d2",
    -	label:"FOO"
    -});					
    -
    -...
    -
    -console.log("Label is currently", conn.getLabel());
    -conn.setLabel("BAR");
    -console.log("Label is now", conn.getLabel());
    -</pre>
    -</div>
    -These methods support passing in a Function instead of a String, and jsPlumb will create a label overlay for you if one does not yet exist when you call setLabel:
    -<div class="code">
    -<pre>
    -var conn = jsPlumb.connect({
    -	source:"d1", 
    -	target:"d2"
    -});					
    -
    -...
    -
    -conn.setLabel(function(c) {
    -	var s = new Date();
    -	return s.getTime() + "milliseconds have elapsed since 01/01/1970";
    -});
    -console.log("Label is now", conn.getLabel());
    -</pre>
    -</div>
    -					
    -					<h4><a id="hideshowOverlay">Hiding/Showing Overlays</a></h4>
    -					<p>You can control the visibility of Overlays using the <strong>setVisible</strong> method of Overlays themselves, or with
    -					<strong>showOverlay(id)</strong> or <strong>hideOverlay(id)</strong> on a Connection.</p>
    -					<p>Remember the <strong>id</strong> parameter that we specified in the examples above?  This can be used to retrieve the Overlay
    -					from a Connection:</p>
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -var overlay = connection.getOverlay("myLabel");
    -// now you can hide this Overlay:
    -overlay.setVisible(false);
    -// there are also hide/show methods:
    -overlay.show();
    -overlay.hide();
    -
    -</pre>					
    -					</div>
    -					<p>However, Connection and Endpoint also have two convenience methods you could use instead:
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:-30 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -connection.hideOverlay("myLabel");
    -
    -// more time passes
    -
    -connection.showOverlay("myLabel");
    -
    -</pre>					
    -					</div>
    -					
    -		<h4><a id="hideshowOverlay">Removing Overlays</a></h4>
    -		<p>Connection and Endpoint also have a <strong>removeOverlay</strong> method, that does what you might expect:
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -connection.removeOverlay("myLabel");
    -
    -
    -</pre>					
    -					</div>							
    -	
    -		</div>
    -	
    -			
    -			<div class="section">
    -			<h3><a id="defaults">Defaults</a></h3>
    -			The easiest way to set a look and feel for your plumbing is to override the defaults that jsPlumb uses. If you
    -			do not do this you are forced to provide your overridden values on every call.  Every argument to the connect and addEndpoint methods has an
    -			associated default value in jsPlumb.<br/><br/>
    -
    -			The defaults that ship with jsPlumb are stored in <em>jsPlumb.Defaults</em>, which is a Javascript object.  Valid entries, and their initial values, are:
    -
    -			<div class="code">
    -<pre>
    -Anchor : "BottomCenter",
    -Anchors : [ null, null ],
    -Connector : "Bezier",
    -ConnectionOverlays : [ ],
    -DragOptions : { },
    -DropOptions : { },
    -Endpoint : "Dot",
    -EndpointOverlays : [ ],
    -Endpoints : [ null, null ],
    -EndpointStyle : { fillStyle : null },
    -EndpointStyles : [ null, null ],
    -EndpointHoverStyle : null,
    -EndpointHoverStyles : [ null, null ],
    -HoverPaintStyle : null,
    -LabelStyle : { color : "black" },
    -LogEnabled : false,
    -Overlays : [ ],
    -MaxConnections : 1,
    -PaintStyle : { lineWidth : 8, strokeStyle : "#456" },
    -RenderMode : "svg",
    -Scope : "_jsPlumb_DefaultScope"
    -</pre>
    -			</div>
    -			<p>
    -			Note that in EndpointStyle, the default fillStyle is 'null'.  This instructs jsPlumb to use the strokeStyle
    -from the attached connector to fill the endpoint.</p>
    -<p>Note also that you can specify either or both (or neither) of 'EndpointStyle' and 'EndpointStyles'.  This allows you to specify a different
    -end point for each end of a connection.  'Endpoint' and 'Endpoints' use the same concept.  jsPlumb will look first in the
    -individual endpoint/endpoint style arrays, and then fall back to the single default version.</p>
    -
    -			you can override these defaults by including this in a script somewhere:
    -			<div class="code">
    -<pre>
    -jsPlumb.Defaults.PaintStyle = {
    -	lineWidth:13,
    -	strokeStyle: 'rgba(200,0,0,0.5)'
    -}
    -
    -jsPlumb.Defaults.DragOptions = { cursor: "crosshair" };
    -
    -jsPlumb.Default.Endpoints = [ [ "Dot", 7 ], [ "Dot", 11 ] ];
    -
    -jsPlumb.Defaults.EndpointStyles = [{ fillStyle:"#225588" }, { fillStyle:"#558822" }];</pre>
    -			</div>
    -			after the jsPlumb script has been loaded of course!  Here we have specified the following default behaviour:
    -			<ul>
    -				<li>- connectors are 13 pixels wide and painted with a semi-transparent red line</li>
    -				<li>- when dragging an element the crosshair cursor is used</li>
    -				<li>- the source endpoint is a dot of radius 7; the target endpoint is a dot of radius 11</li>
    -				<li>- the source endpoint is blue; the target endpoint is green</li>
    -			</ul>
    -
    -			<h5>jsPlumb.importDefaults</h5>
    -There is a helper method called <strong>importDefaults</strong> that allows you to import a set of values with just one call to jsPlumb.  Here's the previous example using importDefaults:
    -			<div class="code">
    -<pre>
    -jsPlumb.importDefaults({
    -	PaintStyle : {
    -		lineWidth:13,
    -		strokeStyle: 'rgba(200,0,0,100)'
    -	},
    -	DragOptions : { cursor: "crosshair" },
    -	Endpoints : [ [ "Dot", 7 ], [ "Dot", 11 ] ],
    -	EndpointStyles : [{ fillStyle:"#225588" }, { fillStyle:"#558822" }]
    -});</pre>
    -			</div>
    -
    -		</div> 					
    -
    -<div class="section">
    -			<h3><a id="simpleConnections">Connections</a></h3>
    -			<h4><a id="simpleConnections">Programmatic Connections</a></h4>
    -The most simple connection you can make with jsPlumb looks like this:
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({source:"element1", target:"element2"});</pre>			
    -			</div>
    -
    -		In this example we have created a Connection from 'element1' to 'element2'.  Remember that a Connection in jsPlumb consists of two Endpoints,
    -		a Connector, and zero or more Overlays.  But this call to 'connect' supplied none of those things, so jsPlumb uses the default values wherever it needs to.
    -		In this case, default values have been used for the following: 
    -		<ul>
    -			<li>- The type and appearance of each Endpoint in the Connection. jsPlumb's default for this is the "Dot" endpoint, of radius 10, with fill color "#456".</li>
    -			<li>- The Anchors that define where the connection's Endpoints appear on each element. The jsPlumb default is "BottomCenter"</li>			
    -			<li>- Whether or not each Endpoint can be a source or target for new Connections. The default is false.</li>
    -			<li>- The type and appearance of the Connection's Connector. The default is a "Bezier" connector of line width 8, and color "#456".</li>
    -		</ul>
    -		<p>
    -		So this call will result in an 8px Bezier, colored "#456", from the bottom center of 'element1' to the bottom center of 'element2', and each
    -		Endpoint will be a 10px radius Dot Endpoint, colored "#456".
    -		</p>
    -		<p>
    -		Let's beef up this call a little and tell jsPlumb what sort of Endpoints we want, and where we want them:		
    -		</p>
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:"element1", 
    -	target:"element2",
    -	anchors:["RightMiddle", "LeftMiddle" ],
    -	endpoint:"Rectangle",
    -	endpointStyle:{ fillStyle: "yellow" }
    -});
    -</pre>			
    -			</div>
    -			
    -		<p>This is what we have told jsPlumb we want this time:</p>
    -		<ul>
    -			<li><strong>anchors</strong> - this array tells jsPlumb where the source and target Endpoints should be located on their parent elements. In this case,
    -			we use the shorthand syntax to name one of jsPlumb's default anchors; you can also specify custom locations (see <a href="#anchors">anchors</a>).
    -			Instead of <strong>anchors</strong> you can use <strong>anchor</strong>, if you want the source and target Endpoints to be located at the
    -			same place on their parent elements.			<br/>
    -			</li>
    -			<li>
    -				<strong>endpoint</strong>  - this tells jsPlumb to use the "Rectangle" Endpoint for both the source and target of the Connection.  As with anchors, 
    -				<strong>endpoint</strong> has a plural version that allows you to specify a different Endpoint for each end of the Connection.<br/>  
    -			</li>
    -			<li>
    -				<strong>endpointStyle</strong> - this is the definition of the appearance of the Endpoint you specified above.  Again, there is a
    -				plural equivalent of this that allows you to specify a different style for each end of the Connection. For more information about
    -				allowed values for this value, see <a href="#paintstyles">Connector, Endpoint & Overlay Styles</a>. 
    -			</li>
    -		</ul>		
    -		
    -		<h5>Reusing common settings between jsPlumb.connect calls</h5>
    -		<p>
    -			A fairly common situation you will find yourself in is wanting to create a bunch of Connections that have only minor differences
    -			between them.  To support that, <strong>jsPlumb.connect</strong> takes an optional second argument. For example:			 
    -		</p>
    -		<div class="code">
    -<pre>
    -var common = {
    -	anchors:[ "BottomCenter", "TopCenter" ],
    -	endpoints:["Dot", "Blank" ]
    -};
    -
    -jsPlumb.connect({ source:"someElement", target:"someOtherElement" }, common);
    -
    -jsPlumb.connect({ source:"aThirdElement", target:"yetAnotherElement" }, common);
    -
    -</pre>			
    -			</div>		
    -			
    -		<h5>Endpoints created by jsPlumb.connect</h5>
    -		<p>If you supply an element id or selector for either the source or target , jsPlumb.connect will automatically create an Endpoint on the 
    -		given element.  These automatically created Endpoints are not marked as drag source or targets, and cannot
    -		be interacted with.  For some situations this behaviour is perfectly fine, but for more interactive UIs you should set things up using the drag and drop
    -		method discussed below.</p>
    -		<p>Given that jsPlumb.connect creates its own Endpoints in some circumstances, in order to avoid leaving orphaned Endpoints around the place, if the Connection is subsequently
    -		deleted, these created Endpoints are deleted too.  Should you want to, you can override this behaviour by setting <strong>deleteEndpointsOnDetach</strong> to
    -		false in the connect call:</p>
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({ 
    -	source:"aThirdElement", 
    -	target:"yetAnotherElement",
    -	deleteEndpointsOnDetach:false 
    -});
    -</pre>
    -</div>	
    -
    -	<h5>Detaching Connections</h5>
    -	By default, connections made with jsPlumb.connect will be detachable via the mouse.  You can prevent this by either setting an appropriate default value:
    -
    -		<div class="code">
    -<pre>
    -jsPlumb.importDefaults({ 
    -	...
    -	ConnectionsDetachable:false
    -	...
    -});
    -</pre>
    -</div>	
    -
    -	Or by specifying it on the connect call like this:
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({ 
    -	source:"aThirdElement", 
    -	target:"yetAnotherElement",
    -	detachable:false
    -});
    -</pre>
    -</div>		
    -								
    -					
    -		<h4><a id="draggableConnections">Connections using Drag and Drop</a></h4>
    -		To support drag and drop connections, you first need to set a few things up.  Every drag and drop connection needs at least a source
    -		Endpoint that the user can drag a connection from. Here's a simple example of how to create an Endpoint:
    -		<div class="code">
    -<pre>
    -var endpointOptions = { isSource:true };
    -var endpoint = jsPlumb.addEndpoint('elementId', endpointOptions);
    -</pre>		
    -</div>  	
    -	This Endpoint will act as a source for new Connections, and will use the jsPlumb defaults for its own appearance and that of any Connections that are drawn from it.							
    -	
    -		<h5>Tip: use the three-argument addEndpoint method for common data </h5>
    -		One thing that happens quite often is that you have an Endpoint whose appearance and behaviour is largely the
    -		same between usages on different elements, with just a few differences. 
    -		<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -	endpoint:"Rectangle",
    -	paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -	isSource:true,
    -	connectorStyle : { strokeStyle:"#666" },
    -	isTarget:true
    -};
    -</pre>		
    -</div>  		
    -		Notice there is no 'anchor' set.  Here we apply it to two elements, at a different location in each:
    -		<div class="code">
    -<pre>
    -jsPlumb.addEndpoint('element1', { anchor:"BottomCenter" }, exampleGreyEndpointOptions)); 
    -
    -jsPlumb.addEndpoint('element2', { anchor:"TopCenter" }, exampleGreyEndpointOptions));
    -</pre>		
    -</div>
    -
    -<p>
    -Now that you have a source Endpoint, you need to either create a target Endpoint on some element, or notify jsPlumb that you wish to make an
    -entire element a drop target.  Let's look at how to attach a target Endpoint first:
    -</p>
    -
    -		<div class="code">
    -<pre>var endpointOptions = { isTarget:true, endpoint:"Rectangle", paintStyle:{ fillStyle:"gray" } };
    -var endpoint = jsPlumb.addEndpoint("otherElementId", endpointOptions);
    -</pre>		
    -		</div>
    -		<p>
    -		This Endpoint, a gray rectangle, has declared that it can act as a drop target for Connections. 
    -</p>
    -<p>
    -jsPlumb also supports marking entire elements as drag sources or drag targets, using the <strong>makeSource</strong> and <strong>makeTarget</strong> methods.  This is a useful function for certain applications.  Each of these methods takes an Endpoint declaration which defines the Endpoint that jsPlumb will create after a Connection has been attached.  Let's take a look at makeTarget first:
    -</p>
    -<h4><a id="sourcesAndTargets">Elements as sources &amp; targets</a></h4>
    -jsPlumb also supports turning entire elements into Connection sources and targets, using the methods <strong>makeSource</strong> and <strong>makeTarget</strong>.  With these methods you mark an element as a source or target, and provide an Endpoint specification for jsPlumb to use when a Connection is established.  makeSource also gives you the ability to mark some child element as the place from which you wish to drag Connections, but still have the Connection on the main element after it has been established.
    -<p>These methods honour the jsPlumb defaults - if, for example you set up the default Anchors to be this:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.Defaults.Anchors = [ "LeftMiddle", "BottomRight" ];
    -</pre>
    -</div>
    -... and then used makeSource and makeTarget without specifying Anchor locations, jsPlumb would use "LeftMiddle" for the makeSource element and "BottomRight" for the makeTarget element.
    -<p>
    -A further thing to note about makeSource and makeTarget is that any prior calls to one of these methods is honoured by subsequent calls to jsPlumb.connect. This helps when you're building a UI that uses this functionality at runtime but which loads some initial data, and you want the statically loaded data to have the same appearance and behaviour as dynamically created connections (obviously quite a common use case).  
    -<p>For example:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -	anchor:"Continuous",
    -	endpoint:["Rectangle", { width:40, height:20 }],
    -	maxConnections:3
    -});
    -
    -...
    -
    -jsPlumb.connect({source:"el1", target:"el2"});
    -</pre>
    -</div>
    -In this example, the source of the connection will be a Rectangle of size 40x20, having a Continuous Anchor.  The source is also configured to allow a maximum of three connections.
    -<p>You can override this behaviour by setting a <strong>newConnection</strong> parameter on the connect call:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -	anchor:"Continuous",
    -	endpoint:["Rectangle", { width:40, height:20 }],
    -	maxConnections:1,
    -	onMaxConnections:function(info, originalEvent) {
    -		console.log("element is ", info.element, "maxConnections is", info.maxConnections);	
    -	}
    -});
    -
    -...
    -
    -jsPlumb.connect({ source:"el1", target:"el2", newConnection:true });
    -</pre>
    -</div>
    -Note the <strong>onMaxConnections</strong> parameter to this call - it allows you to supply a function to call if the user tries to drag a new Connection when the source has already reached capacity.
    -<h5><a id="makeTarget">jsPlumb.makeTarget</a></h5>
    -<p>
    -This method takes two arguments, the first of which specifies some element (or list of elements); the second specifies the Endpoint you wish to create on that element whenever a Connection is established on it.  In this example we will use the exact same target Endpoint we used before - the gray rectangle - but we will tell jsPlumb that the element "aTargetDiv" will be the drop target:
    -</p>
    -		<div class="code">
    -<pre>var endpointOptions = { 
    -  isTarget:true, 
    -  maxConnections:5,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" } 
    -};
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -<p>
    -	The allowed values in 'endpointOptions' are identical for both the <strong>jsPlumb.addEndpoint</strong> and <strong>jsPlumb.makeTarget</strong> methods, but makeTarget supports an extended 
    -	anchor syntax that allows you more control over the location of the target endpoint.  This is discussed below.	
    -</p>		
    -<p>Notice in the 'endpointOptions' object above there is a 'isTarget' parameter set - this may seem incongruous, since you know you're going to make some element a target.  Remember that the endpointOptions object is the information jsPlumb will use to create an Endpoint on the given target element each time a Connection is established to it. It takes the exact same format as you would pass to addEndpoint; makeTarget is essentially a deferred addEndpoint call followed by a connect call.  So in this case, we're telling jsPlumb that any Endpoints it happens to create on some element that was configured by the makeTarget call are themselves Connection targets.
    -</p>
    -<p>makeTarget also supports the maxConnections and onMaxConnections parameters, as makeSource does, but note that onMaxConnections is passed one extra parameter than its corresponding callback from makeSource - the Connection the user tried to drop:</p>
    -		<div class="code">
    -<pre>
    -jsPlumb.makeTarget("aTargetDiv", { 
    -  isTarget:true, 
    -  maxConnections:5,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" },
    -  maxConnections:3,
    -  onMaxConnections:function(info, originalEvent) {
    -	console.log("user tried to drop connection", info.connection, "on element", info.element, "with max connections", info.maxConnections);
    -  }
    -};
    -</pre>		
    -		</div>
    -
    -<h5>Unique Endpoint per Target </h5>
    -<p>jsPlumb will create a new Endpoint using the supplied information every time a new Connection is established on the target element, by default, but you can override this behaviour and tell jsPlumb that it should create at most one Endpoint, which it should attempt to use for subsequent Connections:
    -</p>
    -		<div class="code">
    -<pre>var endpointOptions = { 
    -  isTarget:true, 
    -  uniqueEndpoint:true,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" } 
    -};
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -Here, the <strong>uniqueEndpoint</strong> parameter tells jsPlumb that there should be at most one Endpoint on this element.  Notice that 'maxConnections' is not set: the default is 1, so in this setup we have told jsPlumb that "aTargetDiv" can receive one Connection and no more.
    -<h5>Deleting Endpoints on Detach</h5>
    -By default, any Endpoints created using makeTarget have <strong>deleteEndpointsOnDetach</strong> set to true, which means that once all Connections to that Endpoint are removed, the Endpoint is deleted.  You can override this by setting the flag to true on the makeTarget call:
    -		<div class="code">
    -<pre>var endpointOptions = { 
    -  isTarget:true, 
    -  maxConnections:5,
    -  uniqueEndpoint:true,
    -  deleteEndpointsOnDetach:false,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" } 
    -};
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -In this setup we have told jsPlumb to create an Endpoint the first time "aTargetElement" is connected to, and to not delete it even if there are no longer any Connections to it.  The created Endpoint will be reused for subsequent Connections, and can support a maximum of 5.
    -
    -<h5>Detaching connections made with the mouse</h5>
    -As with jsPlumb.connect, connections made with the mouse after setting up Endpoints with one of the functions we've just covered will be, by default, detachable.  You can prevent this in the jsPlumb defaults, as previously mentioned:
    -		<div class="code">
    -<pre>jsPlumb.importDefaults({ 
    -	...
    -	ConnectionsDetachable:false,
    -	...
    -});
    -</pre>		
    -		</div>
    -
    -	And you can also set this on a per-endpoint (or source/target) level, like in these examples:
    -
    -		<div class="code">
    -<pre>jsPlumb.addEndpoint("someElementId", { 
    -	connectionsDetachable:false	
    -});
    -
    -jsPlumb.makeSource("someOtherElement", {
    -	...
    -	connectionsDetachable:false,
    -	...
    -});
    -
    -jsPlumb.makeTarget("yetAnotherElement", {
    -	...
    -	connectionsDetachable:false,
    -	...
    -});
    -</pre>		
    -		</div>
    -
    -Note that in the jsPlumb defaults, by convention each word in a parameter is capitalised ("ConnectionsDetachable"), whereas for a call to one of these methods, we use camel case ("connectionsDetachable").
    -
    -
    -<h5>Target Anchors positions with makeTarget</h5>
    -<p>When using the makeTarget method, jsPlumb allows you to provide a callback function to be used to determine
    -the appropriate location of a target anchor for every new Connection dropped on the given target.  It may be the
    -case that you want to take some special action rather than just relying on one of the standard anchor mechanisms.
    -</p>
    -<p>This is achieved through an extended anchor syntax (note that this syntax is <strong>not supported</strong> in
    -the <strong>jsPlumb.addEndpoint</strong> method) that supplies a "positionFinder" to the anchor specification.  jsPlumb 
    -provides two of these by default; you can register your own on jsPlumb and refer to them by name, or just supply a function.  Here's a few examples:</p>
    -Instruct jsPlumb to place the target anchor at the exact location at which the mouse button was released on the
    -target element. Note that you tell jsPlumb the anchor is of type "Assign", and you then provide a "position"
    -parameter, which can be the name of some position finder, or a position finder function itself:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {
    -  anchor:[ "Assign", { 
    -    position:"Fixed"
    -  }]
    -});
    -</pre>
    -</div>
    -<p><strong>Fixed</strong> is one of the two default position finders provided by jsPlumb. The other is <strong>Grid</strong>:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {
    -  anchor:[ "Assign", { 
    -    position:"Grid",
    -    grid:[3,3]
    -  }]
    -});
    -</pre>
    -</div>
    -The Grid position finder takes a "grid" parameter that defines the size of the grid required. [3,3] means
    -3 rows and 3 columns.
    -<h5>Supplying your own position finder</h5>
    -<p>To supply your own position finder to jsPlumb you first need to create the callback function. First let's take a look at what the source code for the Grid position finder looks like:</p>
    -<div class="code">
    -<pre>
    -function(eventOffset, elementOffset, elementSize, constructorParams) {
    -  var dx = eventOffset.left - elementOffset.left, dy = eventOffset.top - elementOffset.top,
    -      gx = elementSize[0] / (constructorParams.grid[0]), 
    -      gy = elementSize[1] / (constructorParams.grid[1]),
    -      mx = Math.floor(dx / gx), my = Math.floor(dy / gy);
    -
    -  return [ ((mx * gx) + (gx / 2)) / elementSize[0], ((my * gy) + (gy / 2)) / elementSize[1] ];
    -}
    -</pre>
    -</div>
    -The four arguments are:
    -<ul>
    -	<li class="bullet"><strong>eventOffset</strong> - Page left/top where the mouse button was released (a JS object containing left/top members like you get from a jQuery offset call)</li>
    -	<li class="bullet"><strong>elementOffset</strong> - JS offset object containing offsets for the element on which the Connection is to be created</li>
    -	<li class="bullet"><strong>elementSize</strong> - [width, height] array of the dimensions of the element on which the Connection is to be created</li>
    -	<li class="bullet"><strong>constructorParams</strong> - the parameters that were passed to the Anchor's constructor. In the example given above, those parameters are 'position' and 'grid'; you can pass arbitrary parameters.
    -</ul>
    -The return value of this function is an array of [x, y] - proportional values between 0 and 1 inclusive, such as you can pass to a static Anchor.
    -<p>To make your own position finder you need to create a function that takes those four arguments and returns an [x, y] position for the anchor, for example:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.AnchorPositionFinders.MyFinder = function(dp, ep, es, params) {
    -	...		
    -	return [ some maths ];	
    -};
    -</pre>
    -</div>
    -Then refer to it in a makeTarget call:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {  
    -  anchor:[ "Assign", { 
    -    position:"MyFinder",
    -    myCustomParameter:"foo",
    -    anInteger:5
    -  }]
    -});
    -</pre>
    -</div>
    -
    -<h5><a id="makeSource">jsPlumb.makeSource</a></h5>
    -There are two use cases supported by this method.  The first is the case that you want to drag a Connection from the element itself and have an Endpoint attached to the element when a Connection is established.  The second is a more specialised case: you want to drag a Connection from the element, but once the Connection is established you want jsPlumb to move it so that its source is on some other element.  For an example of this second case, check out the <a href="../demo/jquery/stateMachineDemo.html">State Machine Connectors</a> demonstration.
    -<p>Here's an example code snippet for the basic use case of makeSource:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource(someDiv, {
    -  paintStyle:{ fillStyle:"yellow" },
    -  endpoint:"Blank",
    -  anchor:"BottomCenter"
    -});
    -</pre>
    -</div>
    -<p>
    -Notice again that the second argument is the same as the second argument to an addEndpoint call.  makeSource is, essentially, a type of addEndpoint call.  In this example we have told jsPlumb that we will support dragging Connections directly from 'someDiv'.  Whenever a Connection is established between 'someDiv' and some other element, jsPlumb assigns an Endpoint at BottomCenter of 'someDiv', fills it yellow, and sets that Endpoint as the newly created Connection's source. 
    -</p>		
    -<p>Configuring an element to be an entire Connection source using makeSource means that the element cannnot itself be draggable.  There would be no way for jsPlumb to distinguish between the user attempting to drag the element and attempting to drag a Connection from the element.  If your UI works in such a way that that is acceptable then you'll be fine to use this method.  But if you want to drag Connections from some element but also have the element be draggable itself, you might want to consider the second use case.  First, a code snippet:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("someConnectionSourceDiv", {
    -  paintStyle:{ fillStyle:"yellow" },
    -  endpoint:"Blank",
    -  anchor:"BottomCenter",
    -  parent:"someDiv"
    -});
    -</pre>
    -</div>
    -<p>The only difference here is the inclusion of the 'parent' parameter.  It instructs jsPlumb to configure the source Endpoint on the element specified - in this case, "someDiv" (the value of this parameter may be a String id or a selector).</p>
    -<p>Again, I'd suggest taking a look at the <a href="../demo/jquery/stateMachineDemo.html">State Machine Connectors demonstration</a> for an example of this.</p>
    -
    -<h5>Filtering drag start based on element</h5>
    -You can supply a 'filter' parameter to the makeSource call in order to get a little more fine-grained control over which parts of the element will respond to a mousedown and start a drag. Consider this markup:
    -<div class="code">
    -<pre>
    -&lt;div id="foo"&gt;
    -  FOO
    -  &lt;button&gt;click me&lt;/button&gt;
    -&lt;/div&gt;
    -</pre>
    -</div>
    -Suppose we do not want to interfere with the operation of the 'click me' button. We can supply a filter to the makeSource call to do so:
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("foo", {
    -  filter:function(event, element) {
    -    return event.target.tagName !== "BUTTON";
    -  }
    -});
    -</pre>
    -</div>
    -If the filter returns anything other than a boolean false, the drag will begin. It's important to note that only boolean false will prevent a drag. False-y values will not.
    -
    -<h5>Endpoint options with makeSource</h5>
    -<p>
    -There are many things you can set in an Endpoint options object; for a thorough list see the API documentation for Endpoint.  
    -</p>
    -<p>Here's an example of specifying that you want an Arrow overlay halfway along any Connection dragged from this Endpoint:</p>
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -  isSource:true,
    -  connectorStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  connectorOverlays: [ [ "Arrow", { location:0.5 } ] ]
    -};
    -</pre>
    -</div>
    -This is an Endpoint that moves around the element it is attached to dependent on the location of other elements in the connections it is attached to (a 'dynamic' anchor):
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -  isSource:true,
    -  connectorStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  connectorOverlays: [ [ "Arrow", { location:0.5 } ] ]
    -  anchor:[ "TopCenter","RightMiddle","BottomCenter","LeftMiddle" ]
    -};
    -</pre>
    -</div>
    -<h5><a id="targetSourceTest">Testing if an element is a target or source</a></h5>
    -You can test if some element has been made a connection source or target using these methods:
    -<ul>
    -<li class="bullet">isTarget</li>
    -<li class="bullet">isSource</li>
    -</ul>
    -<p>The return value is a boolean.</p>
    -<h5><a id="targetSourceToggle">Toggling an element as connection target or source</a></h5>
    -You can toggle the state of some source or target using these methods:
    -<ul>
    -<li class="bullet">setTargetEnabled</li>
    -<li class="bullet">setSourceEnabled</li>
    -</ul>
    -<p>The return value from these methods is the current jsPlumb instance, allowing you to chain them:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.setTargetEnabled("aDivId").setSourceEnabled($(".aSelector"));
    -</pre>
    -</div>
    -<p>Note that if you call either of these methods on an element that was not originally configured as a target or source, nothing will happen.</p>
    -<p>You can check the enabled state of some target or source using these methods:
    -<ul>
    -<li class="bullet">isTargetEnabled</li>
    -<li class="bullet">isSourceEnabled</li>
    -</ul>
    -<h5><a id="targetSourceCancel">Canceling previous makeTarget and/or makeSource calls</a></h5>
    -jsPlumb offers four methods to let you cancel previous makeTarget or makeSource calls. Each of these methods returns the current jsPlumb instance, and so can be chained:
    -<ul>
    -<li class="bullet">unmakeTarget</li>
    -<li class="bullet">unmakeSource</li>
    -<li class="bullet">unmakeEveryTarget</li>
    -<li class="bullet">unmakeEverySource</li>
    -</ul>
    -<p>These last two are analogous to the <strong>removeEveryConnection</strong> and <strong>removeEveryEndpoint</strong> methods that have been in jsPlumb for a while now.</p>
    -<p>unmakeTarget and unmakeSource both take as argument the same sorts of values that makeTarget and makeSource accept - a string id, or a selector, or an array of either of these:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.unmakeTarget("aDivId").unmakeSource($(".aSelector"));
    -</pre>
    -</div>
    -
    -
    -<h5><a id="dragOptions">Drag Options</a></h5>
    -	These are options that will be passed through to the supporting library's drag API.  jsPlumb passes everything you supply here through, inserting
    -	wrapping functions if necessary for the various lifecycle events that jsPlumb needs to know about.  So if, for example, you pass a function to be
    -	called when dragging starts, jsPlumb will wrap that function with a function that does what jsPlumb needs to do, then call yours.
    -	<p>
    -	At the time of writing, jsPlumb supports jQuery, MooTools and YUI3, and each of those libraries uses different terminology.  In addition, jQuery's API is
    -	more fully featured, providing easy support for setting the zIndex and opacity of elements being dragged, as well as the 'scope' for a drag/drop (allowing
    -	you to specify more than one type of drag/drop pair), and hover classes for when a draggable is on the move or
    -	over a droppable. If you're using jQuery you can of course just supply these values on the dragOptions; to make it easier, jsPlumb's MooTools and YUI3 adapters recognize 
    -	these options and add appropriate callbacks for you.  
    -	</p> 
    -	<p>
    -	Given that the options here are library-specific, and they are all well-documented, we're going to discuss just the three drag options
    -	that behave the same way in all (see below for hoverClass):
    -		<ul>
    -		<li><strong>opacity</strong> - the opacity of an element that is being dragged.  Should be a fraction between 0 and 1 inclusive.</li>
    -		<li><strong>zIndex</strong> - the zIndex of an element that is being dragged.</li>
    -		<li><strong>scope</strong> - the scope of the draggable.  can only be dropped on a droppable with the same scope.  this is discussed below.</li>
    -		</ul> 
    -	</p>
    -	
    -	<p>For more information about drag options, take a look at the <a href="http://docs.jquery.com/UI/Draggable" target="_blank">jQuery</a>, <a href="http://mootools.net/docs/more/Drag/Drag.Move" target="_blank">MooTools</a>, or <a href="http://developer.yahoo.com/yui/3/dd/" target="_blank">YUI3</a> docs.	
    -	</p>
    -	<p><strong>NOTE: there is an issue in Chrome that affects the 'cursor' argument to drag options in jQuery. See these links:</strong>
    -	</p><p>
    -<a href="http://forum.jquery.com/topic/draggable-cursor-option-does-not-work" target="_blank">http://forum.jquery.com/topic/draggable-cursor-option-does-not-work</a><br/>
    -<a href="http://forum.jquery.com/topic/chrome-text-select-cursor-on-drag" target="_blank">http://forum.jquery.com/topic/chrome-text-select-cursor-on-drag</a>
    -</p>
    -You should put something like this at the top of your JS to avoid it (perhaps not something so drastic as a document-wide override):	
    -<div class="code">
    -<pre>document.onselectstart = function () { return false; };</pre>
    -</div>		
    -	
    -	<h5><a id="dropOptions">Drop Options</a></h5>
    -	Drop options are treated by jsPlumb in the same way as drag options - they are passed through to the underlying library.  MooTools does not have drop options like jQuery and YUI3 do; droppable functionality in MooTools 
    -	is actually implemented by the Drag.Move class - the one used to initialise a draggable.  But when you setup an Endpoint in jsPlumb you should ignore
    -	that fact, and treat droppables like you would in jQuery or YUI3.  jsPlumb wires everything up for you under the hood.
    -	<p>There are three jQuery droppable options that jsPlumb treats as shortcuts in MooTools and YUI3, for the sake of consistency:</p>
    -		<ul>
    -			<li><strong>hoverClass</strong> - the CSS class to attach to the droppable when a draggable is hovering over it.</li>
    -			<li><strong>activeClass</strong> - the CSS class to attach to the droppable when a draggable is, um, being dragged.</li>
    -			<li><strong>scope</strong> - the scope of the draggable.  The draggable can only be dropped on a droppable with the same scope.  This is discussed below.</li>
    -		</ul>   
    -	<p>For more information about drop options when using jQuery, see <a href="http://docs.jquery.com/UI/Droppable" target="_blank">here</a>.</p>
    -		
    -	<h5><a id="dragAndDropScope">Drag and Drop Scope</a></h5>
    -		jsPlumb borrowed the concept of 'scope' from jQuery's drag/drop implementation: the notion of
    -		which draggables can be dropped on which droppables.  In jsPlumb you can provide a 'scope' entry when 
    -		creating an Endpoint.  Here's the example grey Endpoint example with 'scope' added:
    -		
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:"#666" },
    -  isSource:true,
    -  connectionStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  scope:"exampleGreyConnection"
    -};</pre>		
    -</div>  		
    -  										
    -	If you do not provide a 'scope' entry, jsPlumb uses a default scope.  Its value is accessible through this method:
    -	<div class="code">
    -<pre>
    -jsPlumb.getDefaultScope();
    -</pre>	
    -	</div>				
    -	If you want to change it for some reason you can do so with this method:
    -	<div class="code">
    -<pre>
    -jsPlumb.setDefaultScope("mySpecialDefaultScope");
    -</pre>	
    -	</div>			
    -	You can also, should you want to, provide the scope value separately on the drag/drop options, like this:
    -	<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -  isSource:true,
    -  connectionStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  dragOptions:{ scope:"dragScope" },
    -  dropOptions:{ scope:"dropScope" }
    -};</pre>		
    -</div>  
    -			
    -	</div>	
    -
    -	<div class="section">
    -		<h3><a id="parameters">Connection Parameters</a></h3>
    -		jsPlumb has a mechanism that allows you to set parameters on a per-connection basis.  This can be achieved in a few different ways:
    -
    -		<ul>
    -			<li class="bullet">Providing parameters to a jsPlumb.connect call</li>
    -			<li class="bullet">Providing parameters to an addEndpoint call to/from which a connection is subsequently established using the mouse</li>
    -			<li class="bullet">using the <strong>setParameter</strong> or <strong>setParameters</strong> method on a Connection</li>
    -		</ul>
    -		Getting parameters is achieved through either the <strong>getParameter(key)</strong> or <strong>getParameters</strong> method on a Connection.
    -		<h4><a id="connectParameters">jsPlumb.connect</a></h4>
    -		Parameters can be passed in via an object literal to a jsPlumb.connect call:
    -		<div class="code">
    -<pre>var myConnection = jsPlumb.connect({
    -	source:"foo",
    -	target:"bar",
    -	parameters:{
    -		"p1":34,
    -		"p2":new Date(),
    -		"p3":function() { console.log("i am p3"); }
    -	}
    -});
    -</pre>
    -</div>
    -Note that they can be any valid Javascript objects - it's just an object literal.  You then access them like this:
    -		<div class="code">
    -<pre>myConnection.getParameter("p3")();     // prints 'i am p3' to the console.
    -</pre>
    -</div>
    -
    -<h4><a id="addEndpointParameters">jsPlumb.addEndpoint</a></h4>
    -<p>The information in this section also applies to the <strong>makeSource</strong> and <strong>makeTarget</strong> functions</p>
    -<p>Using jsPlumb.addEndpoint, you can set parameters that will be copied in to any Connections that are established from or to the given Endpoint using the mouse.  (If you set parameters on both a source and target Endpoints and then connect them, the parameters set on the target Endpoint are copied in first, followed by those on the source. So the source Endpoint's parameters take precedence if they happen to have one or more with the same keys as those in the target).</p>
    -Consider this example:
    -		<div class="code">
    -<pre>var e1 = jsPlumb.addEndpoint("d1", {
    -	isSource:true,
    -	parameters:{
    -		"p1":34,
    -		"p2":new Date(),
    -		"p3":function() { console.log("i am p3"); }
    -	}
    -});
    -
    -var e2 = jsPlumb.addEndpoint("d2", {
    -	isTarget:true,
    -	parameters:{
    -		"p5":343,
    -		"p3":function() { console.log("FOO FOO FOO"); }
    -	}
    -});
    -
    -var conn = jsPlumb.connect({source:e1, target:e2});
    -
    -
    -</pre>
    -</div>
    -
    -'conn' will have four parameters set on it, with the value for "p3" coming from the source Endpoint:
    -		<div class="code">
    -<pre>var params = conn.getParameters();
    -console.log(params.p1);   	// 34
    -console.log(params.p2);   	// Mon May 14 2012 12:57:12 GMT+1000 (EST) (or however your console prints out a Date)
    -console.log((params.p3)()); // "i am p3"  (note: we executed the function after retrieving it)
    -console.log(params.p5);   	// 343
    -
    -</pre>
    -</div>
    -
    -
    -</div> <!-- /section -->
    -
    -<div class="section">
    -	<h3><a id="connectionTypes">Connection and Endpoint Type</a></h3>
    -	<p>
    -	From version 1.3.12 onwards, jsPlumb supports the notion of "type" for both Connections and Endpoints. A type is a
    -	collection of attributes such as paint style, hover paint style, overlays etc - it is a subset, including most but not all, of the
    -	parameters you can set in an Endpoint or Connection definition. 
    -	</p>
    -	<p>
    -	An Endpoint or Connection can have zero or more types assigned; they are merged as granularly as possible, in the order
    -	in which they were assigned. There is a supporting API that works in the same way as the class stuff does in
    -	jQuery - hasType, addType, removeType, toggleType, setType, and each of these methods (except hasType) takes a space-separated
    -	string so you can add several at once.  Support for these methods has been added to the jsPlumb.select and
    -	jsPlumb.selectEndpoint methods, and you can also now specify a 'type' parameter to an Endpoint or Connection at create time.
    -	</p>
    -	<h4>Connection Type</h4>
    -	Probably the easiest way to explain types is with some code. In this snippet, we'll register a Connection type on jsPlumb,
    -	create a Connection, and then assign the type to it:
    -<div class="code">
    -<pre>
    -jsPlumb.registerConnectionType("example", {
    -	paintStyle:{ strokeStyle:"blue", lineWidth:5  },
    -	hoverPaintStyle:{ strokeStyle:"red", lineWidth:7 }
    -});
    -
    -var c = jsPlumb.connect({ source:"someDiv", target:"someOtherDiv" });
    -c.bind("click", function() {
    -	c.setType("example");
    -});	
    -</pre>		
    -</div>
    -<p>Another example - a better one, in fact.  Say you have a UI in which you can click to select or deselect Connections, and
    -you want a different appearance for each state.  Connection types to the rescue!</p>
    -<div class="code">
    -<pre>
    -jsPlumb.registerConnectionTypes({
    -	"basic": {
    -		paintStyle:{ strokeStyle:"blue", lineWidth:5  },
    -		hoverPaintStyle:{ strokeStyle:"red", lineWidth:7 }
    -	},
    -	"selected":{
    -		paintStyle:{ strokeStyle:"red", lineWidth:5 },
    -		hoverPaintStyle:{ lineWidth: 7 }
    -	}	
    -});
    -
    -var c = jsPlumb.connect({ source:"someDiv", target:"someOtherDiv", type:"basic" });
    -
    -c.bind("click", function() {
    -	c.toggleType("selected");
    -});	
    -</pre>		
    -</div>
    -<p>Notice here how we used a different method - <strong>registerConnectionTypes</strong> - to register a few types at once.
    -Notice also the hoverPaintStyle for the 'selected' type: it declares only a lineWidth.  As I mentioned above, types are
    -merged with as much granularity as possible, so that means that in this case the lineWidth from 'selected' will be merged
    -into the hoverPaintStyle from 'basic', and voila, red, 7 pixels.</p>
    -<p>These examples, of course, use the <strong>jsPlumb.connect</strong> method, but in many UIs Connections are created
    -via drag and drop.  How would you assign that 'basic' type to a Connection created with drag and drop? Provide it as
    -the Endpoint's connectorType parameter, like so:</p>
    -<div class="code">
    -<pre>
    -	var e1 = jsPlumb.addEndpoint("someDiv", {
    -		connectorType:"basic",
    -		isSource:true
    -	});
    -	
    -	var e2 = jsPlumb.addEndpoint("someOtherDiv", {
    -		isTarget:true
    -	});
    -		
    -	//... user then perhaps drags a connection...or we do it programmatically:
    -	
    -	var c = jsPlumb.connect({ source:e1, target:e2 });
    -	
    -	// now c has type 'basic'
    -	console.log(c.hasType("basic));   // -> true
    -</pre>	
    -</div>
    -<p>Note that the second Endpoint we created did not have a 'connectorType' parameter - we didn't need it, as the source
    -Endpoint in the Connection had one.  But we could have supplied one, and jsPlumb will use it, but only if the source
    -Endpoint has not declared connectorType.  This is the same way jsPlumb treats other connector parameters such as
    -paintStyle etc - the source Endpoint wins.</p>
    -<p>Here's a few examples showing you the full type API:</p>
    -<div class="code">
    -<pre>
    -	jsPlumb.registerConnectionTypes({
    -		"foo":{ paintStyle:{ strokeStyle:"yellow", lineWidth:5 } },
    -		"bar":{ paintStyle:{ strokeStyle:"blue", lineWidth:10 } },
    -		"baz":{ paintStyle:{ strokeStyle:"green", lineWidth:1 } }
    -	});
    -	
    -	var c = jsPlumb.connect({ source:"someDiv", target:"someOtherDiv", type:"foo" });
    -	
    -	// see what types the connection has.  
    -	console.log(c.hasType("foo"));  // -> true
    -	console.log(c.hasType("bar"));  // -> false
    -	
    -	// add type 'bar'
    -	c.addType("bar");
    -	
    -	// toggle both types (they will be removed in this case)
    -	c.toggleType("foo bar");
    -	
    -	// toggle them back
    -	c.toggleType("foo bar");
    -	
    -	// getType returns a list of current types.
    -	console.log(c.getType()); // -> [ "foo", "bar" ]
    -	
    -	// set type to be 'baz' only
    -	c.setType("baz");
    -	
    -	// add foo and bar back in
    -	c.addType("foo bar");
    -	
    -	// remove baz and bar
    -	c.removeType("baz bar");
    -	
    -	// what are we left with? good old foo.
    -	console.log(c.getType()); // -> [ "foo" ]
    -	
    -</pre>	
    -</div>
    -<p>Things to note here are that every method <strong>except hasType</strong> can take a space-delimited list of types
    -to work with. So types work like CSS classes, basically.</p>
    -<h5>Supported Parameters in Connection Type objects</h5>
    -Not every parameter from a Connection's constructor is supported in a Connection Type - as mentioned above, types act pretty
    -much like CSS classes, so the things are supported are related to behaviour or appearance. For instance, 'source' is
    -not supported.  You cannot make a Connection Type that is fixed to a specific source element. Here's the full list of
    -supported properties in Connection Type objects:
    -<ul>
    -	<li class="bullet"><strong>detachable</strong> - whether or not the Connection is detachable using the mouse</li>
    -	<li class="bullet"><strong>paintStyle</strong></li>
    -	<li class="bullet"><strong>hoverPaintStyle</strong></li>
    -	<li class="bullet"><strong>scope</strong> - remember, Connections support a single scope. So if you have multiple types applied, you will get the scope from the last type that defines one.</li>
    -	<li class="bullet"><strong>parameters</strong> - when you add/set a type that has parameters, any existing parameters with the same keys will be overwritten. When you remove a type that has parameters, its parameters are NOT removed from the Connection.</li>
    -	<li class="bullet"><strong>overlays</strong> - when you have multiple types applied to a Connection, you get the union of all the Overlays defined across the various types.</li>	
    -</ul>
    -
    -<h4><a id="endpointTypes">Endpoint Type</a></h4>
    -Endpoints can also be assigned one or more types, both at creation and programmatically using the API discussed above.
    -The only real differences between Endpoint and Connection types are the allowed parameters.  Here's the list for Endpoints:
    -<ul>
    -	<li class="bullet"><strong>paintStyle</strong></li>
    -	<li class="bullet"><strong>hoverPaintStyle</strong></li>
    -	<li class="bullet"><strong>maxConnections</strong></li>
    -	<li class="bullet"><strong>connectorStyle</strong> - paint style for any Connections that use this Endpoint.</li>
    -	<li class="bullet"><strong>connectorHoverStyle</strong> - hover paint style for Connections from this Endpoint.</li>
    -	<li class="bullet"><strong>connector</strong> - a Connector definition, like "StateMachine", or [ "Flowchart", { stub:50 } ] </li>
    -	<li class="bullet"><strong>connectionType</strong> - This allows you to specify the Connection Type for Connections made from this Endpoint.</li>
    -	<li class="bullet"><strong>scope</strong> - remember, Endpoints support a single scope. So if you have multiple types applied, you will get the scope from the last type that defines one.</li>
    -	<li class="bullet"><strong>parameters</strong> - when you add/set a type that has parameters, any existing parameters with the same keys will be overwritten. When you remove a type that has parameters, its parameters are NOT removed from the Connection.</li>
    -	<li class="bullet"><strong>overlays</strong> - when you have multiple types applied to an Endpoint, you get the union of all the Overlays defined across the various types.</li>
    -	
    -</ul>
    -<p>One thing to be aware of is that the parameters here that are passed to Connections are only passed from a source Endpoint,
    -not targets.</p>
    -Here's an example of using Endpoint types:
    -<div class="code">
    -<pre>
    -  jsPlumb.registerEndpointTypes({
    -    "basic":{			
    -      paintStyle:{fillStyle:"blue"}
    -    },
    -    "selected":{			
    -      paintStyle:{fillStyle:"red"}
    -    }
    -  });
    -
    -  var e = jsPlumb.addEndpoint("someElement", {
    -    anchor:"TopMiddle",
    -	type:"basic"
    -  });
    -  
    -  e.bind("click", function() {
    -    e.toggleType("selected");
    -  });
    -</pre>
    -</div>
    -So it works the same way as Connection Types.  There are several parameters allowed by an Endpoint Type that affect Connections
    -coming from that Endpoint. Note that this does not affect existing Connections.  It affects only Connections that are
    -created after you set the new type(s) on an Endpoint.
    -
    -<h4>Fluid Interface</h4>
    -As mentioned previously, all of the type operations are supported by the <strong>select</strong> and <strong>selectEndpoints</strong> methods.
    -So you can now do things like this:
    -<div class="code">
    -<pre>
    -	jsPlumb.selectEndpoints({scope:"terminal"}).toggleType("active");
    -	
    -	jsPlumb.select({source:"someElement"}).addType("highlighted available");
    -	
    -	etc
    -	
    -</pre>
    -</div>
    -Obviously, in these examples, 'available' and 'highlighted' would have previously been registered on jsPlumb using the appropriate
    -register methods.
    -
    -</div> <!-- / connection/endpoint types	-->
    -	
    -	<div class="section">
    -		<h3><a id="beforeDrop">Interceptors</a></h3>
    -		<p>
    -		Interceptors were new in jsPlumb 1.3.4.  They are basically event handlers from which you can return a value that tells jsPlumb to abort what it is that
    -		it was doing.  There are currently two interceptors supported - <strong>beforeDrop</strong>, which is called when the user has dropped a Connection onto some target, 
    -		and <strong>beforeDetach</strong>, which is called when the user is attempting to detach a Connection. 
    -		</p>
    -		<p>Interceptors can be registered via the <strong>bind</strong> method on jsPlumb just like any other event listeners, and they can also be passed in to
    -		the <strong>jsPlumb.addEndpoint</strong>, <strong>jsPlumb.makeSource</strong> and <strong>jsPlumb.makeTarget</strong>
    -		methods.  A future version of jsPlumb will support registering interceptors on Endpoints using the <strong>bind</strong> method as you can now with the jsPlumb object.
    -		Note that binding 'beforeDrop' (as an example) on jsPlumb itself is like a catch-all: it will be called every time a Connection is dropped on _any_ 
    -		Endpoint.  But passing a 'beforeDrop' callback into an Endpoint (and in the future, registering one using 'bind') constrains that callback to just the
    -		Endpoint in question.  		
    -		<h4><a id="beforeDrop">beforeDrop</a></h4>
    -		This event is fired when a new or existing connection has been dropped. Your callback is passed a JS object with these fields:
    -		<ul>
    -			<li><strong>sourceId</strong> - the id of the source element in the connection</li>
    -			<li><strong>targetId</strong> - the id of the target element in the connection</li>
    -			<li><strong>scope</strong> - the scope of the connection</li>
    -			<li><strong>connection</strong> - the actual Connection object.  You can access the 'endpoints' array in a Connection to get the Endpoints involved in the Connection, but be aware that when a Connection is being dragged, one of these Endpoints will always be a transient Endpoint that exists only for the life of the drag. To get the Endpoint on which the Connection is being dropped, use the 'dropEndpoint' member.</li>
    -			<li><strong>dropEndpoint</strong> - this is the actual Endpoint on which the Connection is being dropped.  This <strong>may be null</strong>, because it will not be set if the Connection is being dropped on an element on which makeTarget has been called. </li>
    -		</ul>
    -
    -		If you return false (or nothing) from this callback, the new Connection is aborted and removed from the UI.
    -		<h4><a id="beforeDetach">beforeDetach</a></h4>
    -		This is called when the user has detached a Connection, which can happen for a number of reasons: by default, jsPlumb allows users to drag Connections off of target Endpoints, but this can also result from a programmatic 'detach' call.  Every case is treated the same by jsPlumb, so in fact it is possible for you to write code that attempts to detach a Connection but then denies itself!  You might want to be careful with that. 
    -		<p>Note that this interceptor is passed the actual Connection object; this is different from the beforeDrop
    -		interceptor discussed above: in this case, we've already got a Connection, but with beforeDrop we are yet 
    -		to confirm that a Connection should be created.</p>
    -		<p>Returning false - or nothing - from this callback will cause the detach to be abandoned, and the Connection will be reinstated or left on its current target.</p>
    -			
    -	</div>
    -
    -		<div class="section">
    -			<h3><a id="paintstyles">Paint Styles</a></h3>
    -			Defining the appearance of Connectors, Endpoints (and Overlays, but this is deprecated in favour of using CSS classes) is achieved through a 'paintStyle' (or a quite similar name)
    -			object passed as a parameter to one of jsPlumb.connect, jsPlumb.addEndpoint, jsPlumb.makeSource or jsPlumb.makeTarget. Depending on the method you are calling, the parameter names vary.
    -			<h5>Connector Paint Styles</h5>
    -			These are specified in a <em>paintStyle</em> parameter on a call to <strong>jsPlumb.connect</strong>:
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			or in the <em>connectorPaintStyle</em> parameter on a call to <strong>jsPlumb.addEndpoint</strong> or <strong>jsPlumb.makeSource</strong>:
    -			<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div> 			
    -			 
    -
    -Notice the <em>paintStyle</em> parameter in those examples: it is the paint style for the Endpoint, which we'll discuss below.
    -
    -			<h5>Endpoint Paint Styles</h5>
    -			These are specified in a <em>paintStyle</em> parameter on a call to <strong>addEndpoint</strong>. This is the example from just above:
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -</div> 
    -			...or as the <em>endpointStyle</em> parameter to a <strong>connect</strong> call:		
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			... or as an entry in the <em>endpointStyles</em> array passed to a <strong>jsPlumb.connect</strong> call:
    -					<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  endpointStyles:[ 
    -    { fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -    { fillStyle:"green" }
    -  ],
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			or as the <em>paintStyle</em> parameter passed to a <strong>makeTarget</strong> or <strong>makeSource</strong> call:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("el1", {
    -  ...
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  ...
    -});
    -</pre>			
    -</div>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 }
    -  parent:"someOtherDivIJustPutThisHereToRemindYouYouCanDoThis"
    -});
    -</pre>			
    -			</div>						
    -	In the first example we  made "el1" into a drop target, and defined a paint style for the Endpoint jsPlumb will create when a Connection is
    -	established.  In the second we made "el1" a connection source and assigned the same values for the Endpoint jsPlumb will create when a Connection
    -	is dragged from that element.
    -	
    -	<h5>Overlay Paint Styles</h5>
    -	The preferred way to set paint styles for Overlays is to use the <em>cssClass</em> parameter in the constructor arguments of an Overlay definition.
    -	
    -	<h4>Paint Style Parameters</h4>
    -	This is the full list of parameters you can set in a paintStyle object, but note that <strong>fillStyle</strong> is ignored by Connectors,
    -	and <strong>strokeStyle</strong> is ignored by Endpoints.  Also, if you create a Connection using jsPlumb.connect and do not specify any
    -	Endpoint styles, the Endpoints will derive their fillStyle from the Connector's strokeStyle.
    -	<p>
    -		fillStyle, strokeStyle and outlineColor can be specified using any valid CSS3 syntax.
    -	</p>
    -	<ul>
    -		<li><strong>fillStyle</strong> - color for an Endpoint, eg. rgba(100,100,100,50), "blue", "#456", "#993355", rgb(34, 56, 78).</li>
    -		<li><strong>strokeStyle</strong> - color for a Connector. see fillStyle examples.</li>
    -		<li><strong>lineWidth</strong> - width of a Connector's line. An integer.</li>
    -		<li><strong>outlineWidth</strong> - width of the outline for an Endpoint or Connector. An integer.</li>
    -		<li><strong>outlineColor</strong> - color of the outline for an Endpoint or Connector. see fillStyle examples.</li>
    -		<li><strong>dashstyle</strong> - VML and SVG only. This comes from VML, and allows you to create dashed or dotted lines.  It has a 
    -			better syntax than the equivalent attribute in SVG (<em>stroke-dasharray</em>, discussed below), so jsPlumb supports this for both renderers. 
    -			The <em>dashstyle</em> attribute is specified as an array of strokes and spaces, where each value is some multiple of <em>the width of 
    -			the Connector</em>, and that's where it's better than SVG, which uses pixels.
    -			<p>
    -			<a href="http://www.w3.org/TR/NOTE-VML" target="_blank">The VML spec</a> is a good place to find valid values for dashstyle. Note that
    -			jsPlumb does not support the string values for this attribute ("solid", "dashdot", etc).
    -			</p>
    -			<p>
    -			In SVG render mode, jsPlumb uses the <em>lineWidth</em> parameter in conjunction with the values in a <em>dashstyle</em> attribute
    -			to create an appropriate value for <em>stroke-dasharray</em>.
    -			</p>
    -		</li>
    -		<li><strong>stroke-dasharray</strong> - SVG only. This is the SVG equivalent of <em>dashstyle</em>.  
    -		<a href="http://www.w3.org/TR/SVG/painting.html" target="_blank">The SVG spec</a> discusses valid values for this parameter.  But be aware
    -		that jsPlumb does not convert this into a valid <em>dashstyle</em> attribute when using the VML renderer. Better to use <em>dashstyle</em>.
    -		</li>
    -		<li><strong>stroke-dashoffset</strong> - SVG only.  This is used in SVG to specify how far into the dash pattern to start painting.  For more information,
    -		see <a href="http://www.w3.org/TR/SVG/painting.html" target="_blank">the SVG spec.</a></li>
    -		<li><strong>joinstyle</strong> - VML and SVG only.  As with <em>dashstyle</em>, this is a VML attribute that jsPlumb supports for both VML
    -		and SVG - jsPlumb turns this into a <em>stroke-linejoin</em> attribute when rendering with SVG.  This attribute specifies how you want individual
    -		segments of connectors to be joined; the VML and SVG specs both have examples of this, of which many are the same between the two, which is
    -		why jsPlumb will automatically convert this attribute into the SVG equivalent.
    -		</li>
    -		<li><strong>stroke-linejoin</strong> - SVG only.  This is the equivalent of VML's <em>joinstyle</em> attribute, but as with <em>stroke-dasharray</em>,
    -		jsPlumb does not convert this into something approriate for VML.  So, using <em>joinstyle</em> will enable you to support more browsers with
    -		less effort.		
    -		</li>
    -	</ul>
    -	
    -	<h3><a id="hoverpaintstyles">Hover Paint Styles</a></h3>
    -	 		
    -	 		Connectors and Endpoints both support the concept of a "hover" paint style - a paint style to use when the mouse is hovering over
    -	 		the component.  These are specified in the exact same format as paint styles discussed above, but hover paint styles also inherit
    -	 		any values from the main paint style.  This is because you will typically want to just change the color, or perhaps outline color, of
    -	 		a Connector or Endpoint when the mouse is hovering, but leave everything else the same.  So having hover paint styles inherit their
    -	 		values precludes you from having to define things in more than one place.
    -	 		<p>
    -	 			The naming convention adopted for hover paint styles is pretty much to insert the word 'hover' into the corresponding main paint
    -	 			style parameters.  Here are a couple of examples:
    -	 		</p> 
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -  hoverPaintStyle:{ strokeStyle:"red" },
    -  endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  endpointHoverStyle:{ fillStyle:"red" }
    -});
    -</pre>			
    -</div>
    -			In this example we specified a hover style for both the Connector, and each of its Endpoints.  Here's the same thing, but using the plural version, to specify a different hover style for each Endpoint:	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -  hoverPaintStyle:{ strokeStyle:"red" },
    -  endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  endpointHoverStyles:[ { fillStyle:"red" }, { fillStyle:"yellow" } ]
    -});
    -</pre>			
    -</div>
    -	 		Calls to <strong>addEndpoint</strong>, <strong>makeSource</strong> and <strong>makeTarget</strong> can also specify various hover paint styles:
    -	 								<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  hoverPaintStyle:{ fillStyle:"red" },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -  connectorHoverPaintStyle:{ strokeStyle:"red", outlineColor:"yellow", outlineWidth:1 }
    -});
    -
    -jsPlumb.makeSource("el2", {
    -  paintStyle:{ fillStyle:"transparent", outlineColor:"yellow", outlineWidth:1 },
    -  hoverPaintStyle:{ fillStyle:"red" },
    -  connectorPaintStyle:{ strokeStyle:"green", lineWidth:3 },
    -  connectorHoverPaintStyle:{ strokeStyle:"#678", outlineColor:"yellow", outlineWidth:1 }
    -});
    -
    -jsPlumb.makeTarget("el3", {
    -  paintStyle:{ fillStyle:"transparent", outlineColor:"yellow", outlineWidth:1 },
    -  hoverPaintStyle:{ fillStyle:"red" }
    -});
    -</pre>			
    -			</div> 
    -	 		
    -	 		In these examples we specified a hover paint style for both the Endpoint we are adding, and any Connections to/from the Endpoint.
    -	 		<p>	
    -		Note that <strong>makeTarget</strong> does not support Connector parameters. It is for creating targets only; Connector parameters will be set by the source Endpoint in any Connections that are made to the element that you turned into a target by using this method.</p>
    -	 			 		
    -			<h4><a id="gradients">Gradients</a></h4>
    -			The Canvas and SVG renderers both support gradients. The VML renderer does not.  jsPlumb uses its own syntax to define gradients, to abstract out the differences between the syntax required by canvas and that required by SVG.  
    -			<p>There are two types of gradients available - a 'linear' gradient, which consists of colored lines all
    -			going in one direction, and a 'radial' gradient, which consists of colored circles emanating from one circle to another.
    -			Because of their basic shape, jsPlumb supports only <em>linear</em> gradients for
    -			Connectors.  But for Endpoints, jsPlumb supports both <em>linear</em> and <em>radial</em> gradients.</p>
    -			
    -				<h5>Connector gradients</h5>
    -				To specify a linear gradient to use in a Connector, you must add a <em>gradient</em> object to your
    -				Connector's <em>paintStyle</em>, for instance:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : "window2",
    -	target : "window3",
    -	paintStyle:{
    -		gradient:{
    -			stops:[[0,"green"], [1,"red"]]
    -		},
    -		lineWidth:15
    -	}
    -});
    -</pre>
    -</div>
    -Here we have connected window2 to window3 with a 15 pixel wide connector that has a gradient from green to red.</p><p> Notice the <strong>gradient</strong> object and the
    -<strong>stops</strong> list inside it - the gradient consists of an arbitrary number of these "color stops".  Each color stop is comprised
    -of two values - [position, color].  Position must be a decimal value between 0 and 1 (inclusive), and indicates where the color
    -stop is situated as a fraction of the length of the entire gradient.  Valid values for
    -the colors in the <strong>stops</strong> list are the same as those that are valid for <strong>strokeStyle</strong> when describing a color.
    -			</p>
    -			As mentioned, the <strong>stops</strong> list can hold an arbitrary number of entries.  Here's an example of a gradient that goes from red to blue to green, and back again through blue to red:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle : {
    -		gradient:{
    -			stops:[[0,'red'], [0.33,'blue'], [0.66,'green'], [0.33,'blue'], [1,'red']]
    -		},
    -		lineWidth : 15
    -	}
    -});
    -</pre>
    -</div>
    -<strong>Note:</strong> when using the VML renderer, jsPlumb will simply ignore the gradient directive
    -so it is best to ensure you also supply a <em>strokeStyle</em> in your paintStyle object, to give jsPlumb something to fall back on.  If you do not supply
    -a <em>strokeStyle</em> your Connectors will be painted black.  The previous example might look like this, for instance:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle:{
    -		strokeStyle:'red',
    -		gradient:{
    -			stops:[[0,'red'], [0.33,'blue'], [0.66,'green'], [0.33,'blue'], [1,'red']]
    -		},
    -		lineWidth:15
    -	}
    -});
    -</pre>
    -</div>
    -Notice the <strong>strokeStyle:'red'</strong> directive at the beginning of the parameter list in <strong>paintStyle</strong>.
    -			<h5>Endpoint gradients</h5>
    -			Endpoint gradients are specified using the same syntax as Connector gradients.  You put the gradient specifier either in the
    -			<strong>endpoint</strong> member, or if you are specifying different Endpoints for each end of the Connector, in one or both of
    -			the values in the <strong>endpoints</strong> array.  Also, this information applies to the case that you are creating standalone
    -			Endpoints that you will be configuring for drag and drop creation of new Connections. 
    -			<p>
    -			This is an example of an Endpoint gradient that is different for each Endpoint in the Connector.  This comes from the main demo; it is
    -			the Connector joining Window 2 to Window 3:
    -			</p>
    -<div class="code">
    -<pre>var w23Stroke = 'rgb(189,11,11)';
    -jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle:{
    -		lineWidth:8,
    -		strokeStyle:w23Stroke
    -	},
    - 	anchors:[ [0.3,1,0,1], "TopCenter" ],
    - 	endpoint:"Rectangle",
    - 	endpointStyles:[
    - 		{ gradient : {stops:[[0, w23Stroke], [1, '#558822']] } },
    -    	{ gradient : {stops:[[0, w23Stroke], [1, '#882255']] } }
    -    ]
    -});
    -</pre>
    -</div>
    -The first entry in the gradient will be the one that is on the Connector end of the Endpoint.  You can of course have as many color stops as
    -you want in this gradient, just like with Connector gradients.
    -	<h5>Applying the gradient in Endpoints</h5>
    -	Only the Dot and Rectangle endpoints honour the presence of a gradient (and, remember, not in VML). The Image endpoint of course ignores a gradient
    -	as it does no painting of its own.
    -	<p>The type of gradient you will see depends on the Endpoint type:</p>
    -		<ul>
    -			<li><strong>Dot</strong> - renders a radial endpoint, with color stop 0 on the outside, progressing inwards as we move through color stops.
    -			<p>Radial gradients actually require more data than linear gradients - in a linear gradient we just move from one point to another, whereas
    -			in a radial gradient we move from one <em>circle</em> to another.  By default, jsPlumb will render a radial gradient using a source
    -			circle of the same radius as the Endpoint itself, and a target circle of 1/3 of the radius of the Endpoint (both circles share the
    -			same center as the Endpoint itself). This circle will be offset by radius/2 in each direction.</p>
    -			<p>You can supply your own values for these inside the gradient descriptor:</p>
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect({
    -	source : 'window3',
    -	target : 'window4',
    -    paintStyle:{
    -    	lineWidth:10,
    -    	strokeStyle:w34Stroke
    -    },
    -    anchors:[ "RightMiddle", "LeftMiddle" ],
    -    endpointStyle:{
    -    	gradient : {
    -    		stops:[ [0, w34Stroke], [1, w34HlStroke] ],
    -    		offset:37.5,
    -    		innerRadius:40
    -    	},
    -    	radius:55
    -    }
    - });
    -</pre>
    -</div>
    -Here we have instructed jsPlumb to make the gradient's inner radius 10px instead of the default 25/3 = 8 ish pixels, and the offset in each direction
    -will be 5px, instead of the default radius / 2 = 12.5 pixels.
    -<p>It is also possible to specify the offset and inner radius as percentages - enter the values as strings with a '%' symbol on the end:</p>
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect({
    -	source : 'window3', 
    -	target : 'window4',
    -    paintStyle:{
    -    	lineWidth:10,
    -    	strokeStyle:w34Stroke
    -    },
    -	anchors:[ "RightMiddle", "LeftMiddle" ],
    -	endpointStyle:{
    -		gradient : {
    -			stops:[ [0, w34Stroke], [1, w34HlStroke] ],
    -			offset:'68%',
    -			innerRadius:'73%'
    -		},
    -		radius:25
    -	}
    -});
    -</pre>
    -</div>
    -This will give roughly the same output as the example above (the percentages are not entirely exact).<br/><br/>
    -			</li>
    -			<li><strong>Rectangle</strong> - renders a linear endpoint, with color stop 0 closest to the end of the Connector</li>
    -		</ul>
    -		
    -<h4><a id="svgvmlcss">Styling SVG/VML using CSS</a></h4>
    -	A nice way of controlling your UI's appearance is to make use of the fact that both SVG and VML can be styled using CSS.
    -	<p>This section will be expanded in the next few weeks to give some decent examples of how to do so</p>
    -			
    -		
    -		
    -		</div>
    -
    -		<div class="section">
    -			<h3><a id="cssclasses">CSS</a></h3>
    -			In an ideal world, you'd be able to control the appearance of your UI using CSS only, but due to difference between
    -			renderers, this is not the case.  The Canvas renderer, for instance, is completely unaware of CSS, and VML can
    -			only be styled up to a point with CSS.  SVG behaves best, and perhaps at some stage in the future when all browsers
    -			in popular usage support SVG decently, we'll be able to switch over completely to CSS.
    -			<p>For now, though, you can - and should - still use CSS for one important consideration: z-indices.  Every Connection,
    -			Endpoint and Overlay in jsPlumb adds some element to the UI, and you should take care to establish appropriate z-indices for each
    -			of these, in conjunction with the nodes in your application. 
    -			</p>
    -			<p>By default, jsPlumb adds a specific class to each of the three types of elements it creates (These class names are
    -			exposed on the jsPlumb object and can be overridden if you need to do so - see the third column in the table) </p>
    -			<table width="90%" class="table" style="align:left;font-size:12px;">
    -				<tr><th>Component</th><th>CSS Class</th><th>jsPlumb Member</th></tr>
    -				<tr><td>Connector</td><td>_jsPlumb_connector</td><td>connectorClass</td></tr>
    -				<tr><td>Endpoint</td><td>_jsPlumb_endpoint</td><td>endpointClass</td></tr>
    -				<tr><td>Overlay</td><td>_jsPlumb_overlay</td><td>overlayClass</td></tr>
    -			</table>
    -			<p>In addition to these defaults, each of the main methods you use to configure Endpoints or make Connections
    -			in jsPlumb support the following two parameters:</p>
    -			<ul>
    -				<li class="bullet"><strong>cssClass</strong> - class(es) to set on the display elements</li>
    -				<li class="bullet"><strong>hoverClass</strong> - class(es) to set on the display elements when in hover mode</li>
    -			</ul>
    -			In addition, addEndpoint and makeSource allow you to specify what these classes will be for any Connections that are dragged from them:
    -			<ul>
    -				<li class="bullet"><strong>connectorClass</strong> - class(es) to set on the display elements of Connections</li>
    -				<li class="bullet"><strong>connectorHoverClass</strong> - class(es) to set on the display elements of Connections when in hover mode</li>
    -			</ul>
    -			<p>These parameters should be supplied as a String; they will be appended as-is to the class member, so feel free to
    -			include multiple classes.  jsPlumb won't even know.</p>
    -		</div>
    -				
    -		<div class="section">
    -			<h3><a id="animation">Animation</a></h3>
    -			jsPlumb offers an 'animate' function, which wraps the underlying animation engine for whichever library you happen to be using and
    -			inserts a callback for jsPlumb to repaint whatever it needs to at each step.  You could of course do this yourself; it's a convenience
    -			method really.
    -			<p>The method signature is:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.animate : function(el, properties, options) 
    -</pre>
    -</div>			
    -The arguments are as follows:
    -	<ul>
    -		<li><strong>el</strong> - element id, or element object from the library you're using.</li>
    -		<li><strong>properties</strong> - properties for the animation, such as duration etc.</li>
    -		<li><strong>options</strong> - options for the animation, such as callbacks etc.</li>
    -	</ul>
    -	
    -		<h4>jQuery and the 'revert' option</h4>
    -		jQuery offers a 'revert' option that you can use to instruct it to revert a drag under some condition. This is
    -		rendered in the UI as an animation that returns the drag object from its current location to wherever it started
    -		out.  It's a nice feature.  Unfortunately, the animation that runs the revert does not offer any lifecycle
    -		callback methods - no 'step', no 'complete' - so its not possible for jsPlumb to know that the revert 
    -		animation is taking place.
    -	
    -		</div>
    -		
    -			<div class="section">
    -			<h3><a id="connectionInfo">Retrieving Connection Information</a></h3>
    -			There are two ways of retrieving connection information from jsPlumb. <strong>getConnections</strong>is the original method; this method has since been supplanted by <a href="#selectConnections">jsPlumb.select</a>, a much more versatile variant.
    -			<h5>getConnections</h5>
    -			<p>Before you use this method you should understand jsPlumb's notion of 'scope' - documentation
    -			is <a href="#dragAndDropScope">here</a> </p>
    -			
    -			<h4>Retrieving connections for a single scope</h4>
    -			<p>To do this, you call getConnections with either no arguments, in which case jsPlumb uses the default scope, or with a string
    -			specifying one scope</p>
    -			<div class="code">
    -<pre>var connectionList = jsPlumb.getConnections();     // you get a list of Connection objects that are in the default scope.
    -</pre>			
    -			</div>
    -			Compare this with:
    -			<div class="code">
    -<pre>var connectionList = jsPlumb.getConnections("myScope");     // you get a list of Connection objects that are in "myScope".
    -</pre>			
    -			</div>
    -			<h4>More advanced filtering</h4>
    -			getConnections optionally takes a JS object specifying filter parameters, of which there are three:
    -			<ul>
    -				<li>scope - the scope(s) of the connection type(s) you wish to retrieve</li>
    -				<li>source - limits the returned connections to those that have this source id</li>
    -				<li>target - limits the returned connections to those that have this target id</li>
    -			</ul>			
    -			Each of these three parameters may be supplied as a string, which for source and target is an element id and for scope is the name of the scope, or a list of strings.  Als, you can pass "*" in as the value for any of these - a wildcard, meaning any value.  See the examples below.
    -			<p>
    -			The return value of a call to getConnection using a JS object as parameter varies on how many scopes you defined.  If you defined only a single scope then jsPlumb returns you a list of Connections in that scope.  Otherwise the return value is a dictionary whose keys are  scope names, and whose values are lists of Connections. For example, the following call:
    -			</p>
    -			<div class="code">
    -<pre>
    -	jsPlumb.getConnections({scope:["someScope", "someCustomScope"]});
    -</pre>
    -</div>
    -would result in this output:			
    -<div class="code">
    -<pre>
    -{
    -	"someScope" : [ 1..n Connections ],
    -	"someCustomScope": [ 1..m Connections ]
    -}
    -</pre>
    -</div>	
    -
    -There is an optional second parameter that tells getConnections to flatten its output and just return you an array.  The previous example with this parameter would look like this:
    -			<div class="code">
    -<pre>
    -	jsPlumb.getConnections({scope:["someScope", "someCustomScope"]}, true);
    -</pre>
    -</div>
    -would result in this output:			
    -<div class="code">
    -<pre>
    -[ 1..n Connections ]
    -</pre>
    -</div>	
    -		
    -		The following examples show the various ways you can get connection information:
    -	<ul>
    -		<li>Get all connections:
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getAllConnections();  
    -</pre>
    -</div>						
    -		</li>
    -<li>Get all connections for the default scope only (return value is a list):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections();  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given scope (return value is a list):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:"myTestScope"});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given scopes (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:["myTestScope", "yourTestScope"]});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given source (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:"mySourceElement"});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given sources (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:["mySourceElement", "yourSourceElement"]});  
    -</pre>
    -</div>						
    -		</li>		
    -<li>Get all connections for the given target (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({target:"myTargetElement"});  
    -</pre>
    -</div>						
    -		</li>				
    -<li>Get all connections for the given source and targets (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:"mySourceElement", target:["target1", "target2"]});  
    -</pre>
    -</div>						
    -		</li>				
    -		
    -<li>Get all connections for the given scope, with the given source and target (return value is a list of connections):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:'myScope", source:"mySourceElement", target:"myTargetElement"});  
    -</pre>
    -</div>						
    -		</li>				
    -		
    -	</ul>	
    -
    -		</div>
    -
    -		<div class="section">
    -			<h3><a id="selectConnections">Selecting Connections</a></h3>
    -			<strong>jsPlumb.select</strong> provides a fluid interface for working with lists of Connections.  The syntax used to specify which Connections you want is identical to that which you use for getConnections, but the return value is an object that supports most operations that you can perform on a Connection, and which is also chainable, for setter methods. Certain getter methods are also supported, but these are not chainable; they return an array consisting of all the Connections in the selection along with the return value for that Connection.
    -			<p>
    -			This is the full list of setter operations supported by jsPlumb.select:
    -			<ul>
    -				<li class="bullet">addOverlay</li>
    -				<li class="bullet">removeOverlay</li>
    -				<li class="bullet">removeOverlays</li>
    -				<li class="bullet">showOverlay</li>
    -				<li class="bullet">hideOverlay</li>
    -				<li class="bullet">showOverlays</li>
    -				<li class="bullet">hideOverlays</li>
    -				<li class="bullet">removeAllOverlays</li>
    -				<li class="bullet">setLabel</li>
    -				<li class="bullet">setPaintStyle</li>
    -				<li class="bullet">setHoverPaintStyle</li>
    -				<li class="bullet">setDetachable</li>
    -				<li class="bullet">setConnector</li>
    -				<li class="bullet">setParameter</li>
    -				<li class="bullet">setParameters</li>
    -				<li class="bullet">detach</li>
    -				<li class="bullet">repaint</li>
    -				<li class="bullet">setType</li>
    -				<li class="bullet">addType</li>
    -				<li class="bullet">removeType</li>
    -				<li class="bullet">toggleType</li>		
    -			</ul>
    -
    -			Each of these operations returns a selector that can be chained.
    -			</p>
    -			These is the full list of getter operations supported by jsPlumb.select:
    -			<p>
    -				<ul>
    -				<li class="bullet">getLabel</li>
    -				<li class="bullet">getOverlay</li>
    -				<li class="bullet">isHover</li>
    -				<li class="bullet">isDetachable</li>
    -				<li class="bullet">getParameter</li>
    -				<li class="bullet">getParameters</li>
    -				<li class="bullet">getType</li>
    -				<li class="bullet">hasType</li>
    -			</ul>
    -
    -			Each of these operations returns an array whose entries are [ value, Connection ] arrays, where 'value' is the return value from the given Connection.  Remember that the return values from a getter are not chainable, but a getter may be called at the end of a chain of setters.
    -			</p>
    -
    -			<p>Some examples:</p>
    -			Select all Connections and set their hover state to be false:
    -
    -<div class="code">
    -<pre>
    -jsPlumb.select().setHover(false);
    -</pre>
    -</div>
    -			Select all Connections from "d1" and remove all Overlays:
    -
    -<div class="code">
    -<pre>
    -jsPlumb.select({source:"d1"}).removeAllOverlays();
    -</pre>
    -</div>
    -
    -Select all connections in scope "foo" and set their paint style to be a thick blue line:
    -<div class="code">
    -<pre>
    -jsPlumb.select({scope:"foo"}).setPaintStyle({ strokeStyle:"blue", lineWidth:5 });
    -</pre>
    -</div>
    -Select all Connections from "d1" and detach them:
    -<div class="code">
    -<pre>
    -jsPlumb.select({source:"d1"}).detach();
    -</pre>
    -</div>
    -<h5>.each iterator</h5>
    -The return value of jsPlumb.select has a <strong>.each</strong> function that allows you to iterate through the list, performing an operation on each one:
    -<div class="code">
    -<pre>
    -jsPlumb.select({scope:"foo"}).each(function(connection) {
    -	
    -		// do something 
    -});
    -</pre>
    -</div>
    -.each is itself chainable:
    -<div class="code">
    -<pre>
    -jsPlumb.select({scope:"foo"}).each(function(connection) {
    -	
    -		// do something 
    -
    -}).setHover(true);
    -</pre>
    -</div>
    -
    -
    -<h5>Other properties/functions</h5>
    -<ul>
    -	<li class="bullet"><strong>length</strong> - this member reports the number of Connections in the selection</li>
    -	<li class="bullet"><strong>get(idx)</strong> - this function allows you to retrieve a Connection from the selection</li>
    -</ul>
    -
    -
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="selectEndpoints">Selecting Endpoints</a></h3>
    -			<strong>jsPlumb.selectEndpoints</strong> provides a fluid interface for working with lists of Endpoints.
    -			The syntax used to specify which Endpoints you want is identical to that which you use for jsPlumb.select,
    -			and the return value is an object that supports most operations that you can perform on an Endpoint (and
    -			which is also chainable, for setter methods). Certain getter methods are also supported, but these are not
    -			chainable; they return an array consisting of all the Endpoints in the selection along with the return
    -			value for that Endpoint.
    -			<p>
    -			<p>
    -				Four parameters are supported by selectEndpoints - each of these except 'scope' can be provided as either
    -				a string, a selector, a DOM element, or an array of a mixture of these types.  'scope' can be provided as either
    -				a string or an array of strings:
    -				<ul>
    -					<li class="bullet">element - element(s) to get both source and target endpoints from</li>
    -					<li class="bullet">source - element(s) to get source endpoints from</li>
    -					<li class="bullet">target - element(s) to get target endpoints from</li>
    -					<li class="bullet">scope - scope(s) for endpoints to retrieve.</li>					
    -				</ul>
    -			</p>
    -			This is the full list of setter operations supported by jsPlumb.selectEndpoints:
    -			<ul>
    -				<li class="bullet">addOverlay</li>
    -				<li class="bullet">removeOverlay</li>
    -				<li class="bullet">removeOverlays</li>
    -				<li class="bullet">showOverlay</li>
    -				<li class="bullet">hideOverlay</li>
    -				<li class="bullet">showOverlays</li>
    -				<li class="bullet">hideOverlays</li>
    -				<li class="bullet">removeAllOverlays</li>
    -				<li class="bullet">setLabel</li>
    -				<li class="bullet">setPaintStyle</li>
    -				<li class="bullet">setHoverPaintStyle</li>				
    -				<li class="bullet">setConnector</li>
    -				<li class="bullet">setParameter</li>
    -				<li class="bullet">setParameters</li>
    -				<li class="bullet">repaint</li>
    -				<li class="bullet">setType</li>
    -				<li class="bullet">addType</li>
    -				<li class="bullet">removeType</li>
    -				<li class="bullet">toggleType</li>
    -			</ul>
    -
    -			Each of these operations returns a selector that can be chained.
    -			</p>
    -			These is the full list of getter operations supported by jsPlumb.select:
    -			<p>
    -				<ul>
    -					<li class="bullet">getLabel</li>
    -					<li class="bullet">getOverlay</li>
    -					<li class="bullet">isHover</li>				
    -					<li class="bullet">getParameter</li>
    -					<li class="bullet">getParameters</li>
    -					<li class="bullet">getType</li>
    -					<li class="bullet">hasType</li>
    -				</ul>
    -
    -			Each of these operations returns an array whose entries are [ value, Endpoint ] arrays, where 'value'
    -			is the return value from the given Endpoint. Remember that the return values from a getter are not chainable,
    -			but a getter may be called at the end of a chain of setters.
    -			</p>
    -			<p>
    -				Other methods (not chainable):
    -				<ul>
    -					<li class="bullet">delete - deletes the Endpoints in the selection</li>
    -					<li class="bullet">detachAll - detaches all Connections from the Endpoints in the selection </li>
    -				</ul>
    -			</p>
    -
    -			<p>Some examples:</p>
    -			Select all Endpoints and set their hover state to be false:
    -
    -<div class="code">
    -<pre>
    -jsPlumb.selectEndpoints().setHover(false);
    -</pre>
    -</div>
    -			Select all source Endpoints on "d1" and remove all Overlays:
    -
    -<div class="code">
    -<pre>
    -jsPlumb.selectEndpoints({source:"d1"}).removeAllOverlays();
    -</pre>
    -</div>
    -
    -Select all Endpoints in scope "foo" and set their fill style to be blue:
    -<div class="code">
    -<pre>
    -jsPlumb.selectEndpoints({ scope:"foo" }).setPaintStyle({ fillStyle:"blue" });
    -</pre>
    -</div>
    -Select all Endpoints from "d1" and detach their Connections:
    -<div class="code">
    -<pre>
    -jsPlumb.selectEndpoints({source:"d1"}).detachAll();
    -</pre>
    -</div>
    -<h5>.each iterator</h5>
    -The return value of jsPlumb.selectEndpoints has a <strong>.each</strong> function that allows you to iterate through the list, performing an operation on each one:
    -<div class="code">
    -<pre>
    -jsPlumb.selectEndpoints({scope:"foo"}).each(function(endpoint) {
    -	
    -		// do something 
    -});
    -</pre>
    -</div>
    -.each is itself chainable:
    -<div class="code">
    -<pre>
    -jsPlumb.selectEndpoints({scope:"foo"}).each(function(endpoint) {
    -	
    -		// do something 
    -
    -}).setHover(true);
    -</pre>
    -</div>
    -
    -
    -<h5>Other properties/functions</h5>
    -<ul>
    -	<li class="bullet"><strong>length</strong> - this member reports the number of Endpoints in the selection</li>
    -	<li class="bullet"><strong>get(idx)</strong> - this function allows you to retrieve an Endpoint from the selection</li>
    -</ul>
    -
    -
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="events">Events</a></h3>
    -			jsPlumb supports binding to several different events on Connections, Endpoints and Overlays, and also on the jsPlumb object itself.  
    -			
    -			<h4><a id="jsPlumbEvents">jsPlumb Events</a></h4>
    -			<p>To bind an event to jsPlumb itself, use <strong>jsPlumb.bind(event, callback)</strong>:</p>
    -			<div class="code">
    -<pre>
    -jsPlumb.bind("jsPlumbConnection", function(info) {
    -   .. update your model in here, maybe.
    -});
    -</pre>
    -</div>
    -<p>These are the events you can bind to on the jsPlumb class:</p>
    -			<ul>
    -				<li class="bullet"><strong>jsPlumbConnection</strong> - notification a Connection was established.
    -					<p>
    -						The first argument to the callback is an object with the following properties:					
    -					</p>
    -					<div class="code">
    -<pre>
    -{
    -  connection 		: 	the new Connection.  you can register listeners on this etc.
    -  sourceId 		:	id of the source element in the Connection
    -  targetId		:	id of the target element in the Connection
    -  source		:	the source element in the Connection
    -  target		:	the target element in the Connection
    -  sourceEndpoint	:	the source Endpoint in the Connection
    -  targetEndpoint	:	the targetEndpoint in the Connection
    -}
    -</pre>
    -					</div>
    -The second argument is the original mouse event that caused the Connection, if any.
    -
    -					<p>
    -All of the source/target properties are actually available inside the Connection object, but - for one of those rubbish historical reasons - are provided separately because of a vagary of the <em>jsPlumbConnectionDetached</em> callback, which is discussed below.
    -</p>
    -				</li>
    -				<li class="bullet"><strong>jsPlumbConnectionDetached</strong> - notification a Connection was detached.  
    -					<p>
    -						As with <em>jsPlumbConnection</em>, the first argument to the callback is an object with the following properties:					
    -					</p>
    -					<div class="code">
    -<pre>
    -{
    -  connection		: 	the Connection that was detached.  
    -  sourceId		:	id of the source element in the Connection <em>before it was detached</em>
    -  targetId		:	id of the target element in the Connection before it was detached
    -  source		:	the source element in the Connection before it was detached
    -  target		:	the target element in the Connection before it was detached
    -  sourceEndpoint	:	the source Endpoint in the Connection before it was detached
    -  targetEndpoint	:	the targetEndpoint in the Connection before it was detached
    -}
    -</pre>
    -
    -					</div>	
    -The second argument is the original mouse event that caused the disconnection, if any. 					
    -								
    -					<p>
    -As mentioned above, the source/target properties are provided separately from the Connection, because this event is fired whenever
    -a Connection is either detached and abandoned, or detached from some Endpoint and attached to another.  In the latter case, the
    -Connection that is passed to this callback is in an indeterminate state (that is, the Endpoints are still in the state they are
    -in when dragging, and do not reflect static reality), and so the source/target properties give you the real story.
    -</p>
    -<p> Like I said above, this is really just for rubbish historical reasons.  I'm attempting to document my way out of it!</p>
    -				
    -				</li>
    -
    -				<li class="bullet"><strong>connectionDrag</strong> - notification an existing Connection is being dragged. The callback is passed the Connection.
    -				</li> 
    -
    -				<li class="bullet"><strong>connectionDragStop</strong> - notification a Connection drag has stopped. This is only fired for existing Connections. The callback is passed the Connection that was just dragged.
    -				</li> 
    -
    -				<li class="bullet"><strong>click</strong> - notification a Connection was clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification a Connection was double-clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>endpointClick</strong> - notification an Endpoint was clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>endpointDblClick</strong> - notification an Endpoint was double-clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>contextmenu</strong> - a right-click on some given component.  jsPlumb will report right clicks on both Connections and Endpoints; the callback is passed the component that received the right-click.				
    -				<li class="bullet"><strong>beforeDrop</strong>
    -					This event is fired when a new or existing connection has been dropped. Your callback is passed a JS object with these fields:
    -					<ul>
    -						<li><strong>sourceId</strong> - the id of the source element in the connection</li>
    -						<li><strong>targetId</strong> - the id of the target element in the connection</li>
    -						<li><strong>scope</strong> - the scope of the connection</li>
    -						<li><strong>connection</strong> - the actual Connection object.  You can access the 'endpoints' array in a Connection to get the Endpoints involved in the Connection, but be aware that when a Connection is being dragged, one of these Endpoints will always be a transient Endpoint that exists only for the life of the drag. To get the Endpoint on which the Connection is being dropped, use the 'dropEndpoint' member.</li>
    -						<li><strong>dropEndpoint</strong> - this is the actual Endpoint on which the Connection is being dropped.  This <strong>may be null</strong>, because it will not be set if the Connection is being dropped on an element on which makeTarget has been called. </li>
    -					</ul>
    -
    -					If you return false (or nothing) from this callback, the new Connection is aborted and removed from the UI.
    -				</li>					
    -				<!--li class="bullet"><strong>beforeDrag</strong>
    -					This event is fired when a new connection is about to be dragged.  Your callback function
    -					is passed the Connection that jsPlumb has just created.  If you return false (or nothing) 
    -					from this callback, the new Connection is aborted.
    -				</li-->
    -				<li class="bullet"><strong>beforeDetach</strong>
    -					This event is fired when a Connection is about to be detached, for whatever reason. Your callback function is passed the Connection that the user has just detached. Returning false from this interceptor aborts the Connection detach.
    -				</li>
    -							
    -			</ul>
    -			<h4><a id="connectionEvents">Connection Events</a></h4>		
    -			<p>To bind to an event on a Connection, you also use the <strong>bind</strong> method:</p>
    -			<div class="code">
    -<pre>
    -var connection = jsPlumb.connect({source:"d1", target:"d2"});
    -connection.bind("click", function(conn) {
    -	console.log("you clicked on ", conn);
    -});
    -</pre>
    -</div>
    -<p>These are the Connection events to which you can bind a listener:</p>
    -			<ul>
    -				<li class="bullet"><strong>click</strong> - notification a Connection was clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification a Connection was double-clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>contextmenu</strong> - a right-click on the Connection. The callback is passed the Connection and the original event.
    -				<li class="bullet"><strong>mouseenter</strong> - notification the mouse entered the Connection's path.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>mouseexit</strong> - notification the mouse exited the Connection's.  The callback is passed the Connection and the original mouse event.</li>
    -			</ul>
    -			<h4><a id="endpointEvents">Endpoint Events</a></h4>			
    -			<p>To bind to an event on a Endpoint, you again use the <strong>bind</strong> method:</p>
    -			<div class="code">
    -<pre>
    -var endpoint = jsPlumb.addEndpoint("d1", { someOptions } );
    -endpoint.bind("click", function(endpoint) {
    -	console.log("you clicked on ", endpoint);
    -});
    -</pre>
    -</div>
    -<p>These are the Endpoint events to which you can bind a listener:</p>
    -			<ul>
    -				<li class="bullet"><strong>click</strong> - notification an Endpoint was clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification an Endpoint was double-clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>contextmenu</strong> - a right-click on the Endpoint. The callback is passed the Endpoint and the original event.
    -				<li class="bullet"><strong>mouseenter</strong> - notification the mouse entered the Endpoint.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>mouseexit</strong> - notification the mouse exited the Endpoint.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>maxConnections</strong> - notification the user tried to drop a Connection on an Endpoint that already has the maximum number of Connections.  The callback is passed as first argument an object literal containing the Endpoint, the Connection the user tried to drop, and the value of maxConnections for the Endpoint, and as second argument the original mouse event.</li>
    -			</ul>
    -			<h4><a id="overlayEvents">Overlay Events</a></h4>
    -			Registering event listeners on an Overlay is a slightly different procedure - you provide them as arguments to the Overlay's 
    -			constructor.  This is because you never actually act on an 'Overlay' object.  Here's how to register a click listener on an Overlay:
    -			<div class="code">
    -<pre>jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  overlays:[
    -    [ "Label", {
    -      events:{
    -        click:function(labelOverlay, originalEvent) { 
    -          console.log("click on label overlay for :" + labelOverlay.component); 
    -        }
    -      }
    -    }],
    -    [ "Diamond", {
    -      events:{
    -        dblclick:function(diamondOverlay, originalEvent) { 
    -          console.log("double click on diamond overlay for : " + diamondOverlay.component); 
    -        }
    -      }
    -    }] 	
    -  ]
    -});
    -</pre>			
    -			</div>		
    -		Note that events registered on Diamond, Arrow or PlainArrow overlays will not fire with the Canvas renderer - they work only with the SVG and VML renderers. 
    -
    -
    -		<h4><a id="jsPlumbEvents">Unbinding Events</a></h4>
    -		<p>On the jsPlumb object and on Connections and Endpoints, you can use the <strong>unbind</strong> method to remove a listener.  This method either takes the name of the event to unbind:
    -		<div class="code">
    -<pre>
    -jsPlumb.unbind("click");
    -</pre>
    -</div>		
    -...or no argument, meaning unbind all events:
    -	<div class="code">
    -<pre>
    -var e = jsPlumb.addEndpoint("someDiv");
    -e.bind("click", function() { ... });
    -e.bind("dblclick", function() { ... });
    -
    -...
    -
    -e.unbind("click");
    -</pre>
    -</div>
    -
    -		</div>
    -
    -				<div class="section">
    -			<a id="examples"><h3>jsPlumb.connect Examples</h3></a>
    -This section provides examples of how to use the programmatic API to establish Connections. 
    -			<p>
    -			The basic syntax of a call is that you execute 'connect', providing a source and a target, and optionally a paintStyle and preferences for where you
    -			want the plumbing to be anchored on each element, as well as the type of connector to use. 
    -			</p>
    -			<ul>
    -				<li>Connect window1 to window2 with the default settings:
    -					<div class="code">
    -						<pre>jsPlumb.connect({source:"window1", target:"window2"});</pre>
    -					</div>
    -This connects 'window1' to 'window2' using the jsPlumb defaults - a red Bezier curve Connector and black Dot Endpoints.
    -				</li>
    -				<li>Connect window1 to window2 with a 15 pixel wide yellow Connector, and a slightly brighter endpoint (remember the default Endpoint is a Dot):
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -					</div>
    -				</li>
    -				
    -				<li>Connect window1 to window2 with a 15 pixel wide yellow Connector, and a slightly brighter endpoint:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -					</div>
    -				</li>				
    -				<li>Connect window3 to 'window4' with a 10 pixel wide, semi opaque blue Connector, anchored to the left middle of window3, and the right middle of window4, with a Rectangle endpoint of width 10 and height 8:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window3',
    -	target:'window4',
    -	paintStyle:{ lineWidth:10, strokeStyle:'rgba(0, 0, 200, 0.5)' },
    -	anchors:["RightMiddle", "LeftMiddle"],
    -	endpoint:[ "Rectangle", { width:10, height:8 } ]
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window2 to window3 with a default Connector from the top center of window2 to the bottom center of window3, and rectangular endpoints:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window2',
    -	target:'window3',
    -	paintStyle:{lineWidth:8, strokeStyle:'rgb(189,11,11)'},
    -	anchors:["BottomCenter", "TopCenter"],
    -	endpoint:"Rectangle"
    -});</pre>
    -					</div>
    -				</li>
    -
    -
    -				<li>Connect window1 to window2 with a 15 px wide yellow Bezier. endpoints are a slightly lighter shade of yellow.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	anchors:["BottomCenter", [0.75,0,0,-1]],
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -</div></li>
    -<li>Connect window3 to window4 with a 10px wide blue-ish half transparent Bezier. put endpoints underneath the element they attach to.
    -the endpoints have a radial gradient. both ways of specifying gradient positioning are shown here.
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect( {
    -	source:'window3',
    -	target:'window4',
    -		 paintStyle:{lineWidth:10, strokeStyle:w34Stroke},
    -		 anchors:["RightMiddle", "LeftMiddle"],
    -		 endpointStyle:{ gradient : {stops:[[0, w34Stroke], [1, w34HlStroke]], offset:17.5, innerRadius:15 }, radius:35},
    -		 //endpointStyle:{ gradient : {stops:[[0, w34Stroke], [1, w34HlStroke]], offset:'78%', innerRadius:'73%'}, radius:35 },
    -		 endpointsOnTop:false
    -	}
    -);</pre>
    -</div></li>
    -<li>Connect window2 to window3 with an 8px red Bezier and default rectangular endpoints.  see also how the first anchor is
    -specified here - this is how you create anchors in locations jsPlumb does not offer shortcuts for.
    -the endpoints in this example have linear gradients applied.
    -<div class="code">
    -<pre>var w23Stroke = 'rgb(189,11,11)';
    -jsPlumb.connect({
    -	source:'window2',
    -	target:'window3',
    -	paintStyle:{lineWidth:8,strokeStyle:w23Stroke},
    -	anchors:[[0.3,1,0,1], "TopCenter"],
    -	endpoint:"Rectangle",
    -	endpointStyles:[{ gradient : {stops:[[0, w23Stroke], [1, '#558822']] }},
    -       				{ gradient : {stops:[[0, w23Stroke], [1, '#882255']] }}]
    -});</pre>
    -</div></li>
    -
    -<li>Connect window5 to window6 from center to center, 5px wide line that is green and half transparent. the endpoints are
    -125px in radius and spill out from underneath their elements.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window5',
    -	target:'window6',
    -	anchors:["Center", "Center"],
    -	paintStyle:{lineWidth:5,strokeStyle:'rgba(0,255,0,0.5)'},
    -	endpointStyle:{radius:125}
    -});</pre>
    -</div></li>
    -
    -<li>Connect window4 to window5 from bottom right to top left, with a 7px straight line purple connector, and an image as the endpoint,
    -placed on top of the element it is connected to.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window4",
    -	target:"window5",
    -	anchors:[ "BottomRight","TopLeft" ],
    -	paintStyle:{ lineWidth:7, strokeStyle:"rgb(131,8,135)" },
    -	endpoint:[ "Image", { src:"http://morrisonpitt.com/jsPlumb/img/endpointTest1.png" } ],
    -	connector:"Straight"
    -});</pre>
    -</div></li>
    -
    -
    -				<li>Connect window5 to window6 between their center points with a semi-opaque connector, and 125px endpoints:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window5",
    -	target:"window6",
    -	anchors:[ "Center", "Center" ],
    -	paintStyle:{ lineWidth:5, strokeStyle:"rgba(0,255,0,0.5)" },
    -	endpointStyle:{ radius:125 }
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window7 to window8 with a 10 pixel wide blue Connector, anchored on the top left of window7 and the bottom right of window8:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window7",
    -	target:"window8",
    -	paintStyle:{ lineWidth:10, strokeStyle:"blue" },
    -	anchors:[ "TopLeft", "BottomRight" ]
    -});
    -</pre>
    -					</div>
    -				</li>
    -				<li>Connect the bottom right corner of window4 to the top left corner of window5, with rectangular endpoints of size 40x40 and a hover color of light blue:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window4",
    -	target:"window5",
    -	anchors:["BottomRight","TopLeft"],
    -	paintStyle:{lineWidth:7,strokeStyle:'rgb(131,8,135)'},
    -	hoverPaintStyle:{ strokeStyle:"rgb(0, 0, 135)" },
    -	endpointStyle:{ width:40, height:40 },
    -	endpoint:"Rectangle",
    -	connector:"Straight"
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window1 to window2 with the default paint settings but provide some drag options (which are passed through to the underlying library's draggable call):
    -					<div class="code">
    -						<pre>jsPlumb.connect({source:'window1', target:'window2', dragOptions:{cursor:'crosshair'}});</pre>
    -					</div>
    -				</li>
    -</div>
    -		
    -		<div class="section">
    -			<a id="dragAndDropExamples"><h3>Draggable Connections Examples</h3></a>
    -			This is a list of examples of how to use jsPlumb to create Connections using drag and drop.
    -			<p>
    -			The basic procedure is:
    -			<ol>
    -				<li>1a. Create Endpoints and register them on elements in your UI</li>
    -				<li>1b. alternatively, create a source endpoint and then make some element a drop target </li>
    -				<li>2. Drag and Drop</li>
    -			</ol>
    -			That's all there is to it.  Of course there are plenty of options you can set when doing this...it will be easier
    -			to show you some examples:
    -			</p>
    -			<ul>
    -				<li>Define an Endpoint with default appearance, that is both a source and target of new Connections:
    -<div class="code">
    -<pre>var endpointOptions = { isSource:true, isTarget:true }; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Register that Endpoint on window3, specifying that it should be located in the top center of the element:
    -<div class="code">
    -<pre>var window3Endpoint = jsPlumb.addEndpoint('window3', { anchor:"TopCenter" }, endpointOptions );  
    -</pre>
    -</div>
    -Notice here the usage of the three-argument addEndpoint  - we can reuse 'endpointOptions' with a different Anchor for another element.  This is a useful
    -practice to get into.<br/><br/>
    -				</li>
    -				
    -				<li>Now register that Endpoint on window4, specifying that it should be located in the bottom center of the element:
    -<div class="code">
    -<pre>var window4Endpoint = jsPlumb.addEndpoint('window4', { anchor:"BottomCenter" }, endpointOptions );  
    -</pre>
    -</div>
    -Now we have two Endpoints, both of which support drag and drop of new Connections.  We can use these to make a programmatic Connection, too, though:<br/><br/>
    -				</li>				
    -				
    -				<li>Connect window3 to window4 with a 25px wide yellow Bezier that has a 'curviness' of 175:
    -<div class="code">
    -<pre>jsPlumb.connect({ 
    -	source:window3Endpoint,
    -	target:window4Endpoint,
    -	connector: [ "Bezier", 175 ],
    -	paintStyle:{ lineWidth:25, strokeStyle:'yellow' }
    -});  
    -</pre>
    -</div>
    -				</li>
    -				
    -				<li>Define an Endpoint that creates Connections that are 20px wide straight lines, that is both a source and target of new Connections,
    -				and that has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Define an Endpoint that will be anchored to "TopCenter".  It creates Connections that are 20px wide straight lines, that is both a source and target of new Connections,
    -				and that has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchor:"TopCenter", 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Define an Endpoint that will create a dynamic anchor which can be positioned at "TopCenter" or "BottomCenter".  It creates Connections that are 20px wide straight lines, it is both a source and target of new Connections,
    -				and it has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchor:[ "TopCenter", "BottomCenter" ], 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Exactly the same as before, but shows how you can use "anchors" instead of "anchor", if that makes you feel happier:
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchors:[ "TopCenter", "BottomCenter" ], 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>								
    -				
    -<li>Define an Endpoint that is a 30px blue dot, creates Connections that are 20px wide straight lines, is both a source and target of new Connections,
    -				has a 'scope' of 'blueline', and has an event handler that pops up an alert (note: the event handler name means this example is jQuery - MooTools
    -				and YUI3 use different event handler names):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{fillStyle:'blue'},
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ drop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -			<li>Same example as before, but this is for MooTools, and the Endpoint can support up to 5 connections (the default is 1):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{ fillStyle:'blue' },
    -	maxConnections:5,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ onDrop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Same example again, but maxConnections being set to -1 means that the Endpoint has no maximum limit of Connections:
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", {radius:30} ],
    -	style:{ fillStyle:'blue' },
    -	maxConnections:-1,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ onDrop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Same example again, but for YUI3.  Note the drop callback is "drop:hit":
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{fillStyle:'blue'},
    -	maxConnections:-1,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ "drop:hit":function(e, ui) { alert('drop!'); } }	 	
    -}; 
    -</pre>
    -</div>				 				
    -				</li>				
    -				
    -	<li>Assign a UUID to the endpoint options created above, and add as Endpoints to "window1" and "window2":
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("window1", { uuid:"abcdefg" }, endpointOptions );
    -jsPlumb.addEndpoint("window2", { uuid:"hijklmn" }, endpointOptions );
    -</pre>
    -</div>	
    -	</li>
    -	
    -	<li>Connect the two Endpoints we just registered on "window1" and "window2":
    -<div class="code">
    -<pre>
    -jsPlumb.connect({uuids:["abcdefg", "hijklmn"]});
    -</pre>
    -</div>	
    -	</li>
    -	
    -	<li>Create a source Endpoint, register it on some element, then make some other element a Connection target:
    -<div class="code">
    -<pre>
    -var sourceEndpoint = { isSource:true, endpoint:[ "Dot", { radius:50 } ] };
    -var targetEndpoint = { endpoint:[ "Rectangle", { width:10, height:10 } ] };
    -jsPlumb.addEndpoint( "window1", sourceEndpoint );
    -jsPlumb.makeTarget( "window2", targetEndpoint );
    -</pre>
    -</div>	
    -Notice that the endpoint definition we use on the target window does not include the "isTarget:true" directive.  jsPlumb ignores that flag when
    -creating a connection using an element as the target; but if you then tried to drag another connection to the Endpoint that was created, you would
    -not be able to.  To permit that, you would set 'isTarget:true' on the targetEndpoint options defined above.
    -	
    -	</li>
    -				
    -			</ul>
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="utilityFunctions">Utility Functions</a></h3>
    -			<ul>
    -				<li>Detach window5 from all connections
    -					<div class="code">
    -						<pre>jsPlumb.detachAll("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Hide all window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.hide("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Hide all window5's connections endpoints
    -					<div class="code">
    -						<pre>jsPlumb.hide("window5", true);</pre>
    -					</div>
    -				</li>
    -				<li>Show all window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.show("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Show all window5's connections and endpoints.  Note that in the case that you call jsPlumb.show with two arguments, jsPlumb
    -				will also not make a connection visible if it determines that the other endpoint in the connection is not visible.
    -					<div class="code">
    -						<pre>jsPlumb.show("window5", hide);</pre>
    -					</div>
    -				</li>				
    -				<li>Toggle the visibility of window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.toggle("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of all of window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.repaint("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of all of window5, window6 and window11's connections
    -					<div class="code">
    -						<pre>jsPlumb.repaint( [ "window5", "window6", "window11" ] );</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of every connection
    -					<div class="code">
    -						<pre>jsPlumb.repaintEverything();</pre>
    -					</div>
    -				</li>
    -				<li>Detach every connection that the given instance of jsPlumb is managing
    -					<div class="code">
    -						<pre>jsPlumb.detachEveryConnection();</pre>
    -					</div>
    -				</li>
    -				<li>Detach all connections from "window1"
    -					<div class="code">
    -						<pre>jsPlumb.detachAllConnections("window1");</pre>
    -					</div>
    -				</li>
    -				<li>Remove the given Endpoint from element "window1", deleting its Connections. 
    -					<div class="code">
    -						<pre>jsPlumb.removeEndpoint("window1", someEndpoint);</pre>
    -					</div>
    -				</li>
    -				<li>Remove all Endpoints for the element 'window1', deleting their Connections.
    -					<div class="code">
    -						<pre>jsPlumb.removeAllEndpoints("window1");</pre>
    -					</div>
    -				</li>	
    -				<li>Removes every Endpoint managed by this instance of jsPlumb, deleting all Connections.
    -				This is the same as jsPlumb.reset(), effectively, but it does not clear out the event listeners list. 
    -					<div class="code">
    -						<pre>jsPlumb.removeEveryEndpoint();</pre>
    -					</div>
    -				</li>
    -				<li>Deletes the given Endpoint and all its Connections. 
    -					<div class="code">
    -						<pre>jsPlumb.deleteEndpoint(endpoint);</pre>
    -					</div>
    -				</li>
    -				<li>Removes every endpoint, detaches every connection, and clears the event listeners list.  Returns jsPlumb instance to its initial state.  
    -					<div class="code">
    -						<pre>jsPlumb.reset();</pre>
    -					</div>
    -				</li>	
    -				<li>Library-agnostic method to get a selector from the given selector specification.  This function is used mostly by the jsPlumb demos, to cut down on repeating code across the different libraries.  But perhaps you are writing something with jsPlumb that you also wisht to be library agnostic - this function can help you. The returned object, in each of jQuery, MooTools and YUI, has a ".each" iterator.
    -					<div class="code">
    -						<pre>var selector = jsPlumb.getSelector(".someSelector .spec");</pre>
    -					</div>					
    -				</li>
    -				<li>Set window1 to be not draggable, no matter what some jsPlumb command may request.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggable("window1", false);</pre>
    -					</div>
    -				</li>
    -				<li>Set window1 and window2 to be not draggable, no matter what some jsPlumb command may request.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggable(["window1","window2"], false);</pre>
    -					</div>
    -				</li>				
    -				<li>Initialises window1 as a draggable element (all libraries). Passes in an on drag callback
    -				<div class="code">
    -					<pre>jsPlumb.draggable("window1");</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 and window2 as draggable elements (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(["window1","window2"]);</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable("window1");</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (jQuery)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (MooTools)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($$(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (YIU3)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(Y.all(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (jQuery)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($("#window1"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (MooTools)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($("window1"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (YUI3)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(Y.one("window1"));</pre>
    -				</div>
    -				</li>
    -			</ul>
    -		</div>					
    -		
    -		<div class="section">
    -			<h3><a id="developingJsPlumb">Advanced Topics</a></h3>
    -			<h4><a id="which">Which files are which?</a></h4>
    -			In development, jsPlumb is broken up into nine scripts:
    -				<ul>
    -					<li>- jsPlumb-util-x.x.x.js
    -					<p>There are the jsPlumb utility functions (and some base classes).</p>					
    -					</li>
    -					<li>- jsPlumb-x.x.x.js
    -					<p>This is the main jsPlumb engine.</p>					
    -					</li>
    -					<li>- jsPlumb-defaults-x.x.x.js
    -					<p>This contains the default Anchor, Endpoint, Connector and Overlay implementations.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-canvas-x.x.x.js
    -					<p>This contains the HTML5 canvas render code.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-svg-x.x.x.js
    -					<p>This contains the SVG render code.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-vml-x.x.x.js
    -					<p>This contains the VML render code.</p>
    -					</li>
    -					<li>- jsPlumb-connectors-statemachine-x.x.x.js
    -					<p>This State Machine connector.</p>
    -					</li>
    -					<li>- &lt;LIBRARY_PREFIX&gt;.jsPlumb-x.x.x.js
    -					<p>This contains library-specific helper methods.  jsPlumb ships with three of these - one each for jQuery, MooTools and YUI3. See below for information on how to create a new library implementation.</p>
    -					</li>
    -					<li>- jsBezier-0.4-min.js
    -					<p>These are the Bezier curve functions; they are maintained in a separate project called <a href="http://code.google.com/p/jsbezier/">jsBezier</a></p>
    -					</li>
    -				</ul>
    -			These seven files are packaged together to form the scripts that people use, for example:
    -			<ul>
    -				<li>jquery.jsPlumb-1.3.12-all.js
    -				<p>Contains jsPlumb-1.3.12.js, jsPlumb-defaults-1.3.12.js, jsPlumb-renderers-canvas-1.3.12.js, jsPlumb-renderers-svg-1.3.12.js, jsPlumb-renderers-vml-1.3.12.js, jsPlumb-connectors-statemachine-1.3.12.js, jquery.jsPlumb-1.3.12.js and jsBezier-0.4-min.js</p>
    -				</li>
    -				<li>jquery.jsPlumb-1.3.12-all-min.js
    -				<p>A minified version of the script above (minified using the YUI Compressor)</p>
    -				</li>
    -			</ul>
    -			<h4><a id="pluggableLibrarySupport">Pluggable Library Support</a></h4>
    -		Out of the box, jsPlumb can be run on top of jQuery, MooTools or YUI3.  This is achieved by
    -		delegating several core methods - tasks such as finding an element by id, finding an element's
    -		position or dimensions, initialising a draggable, etc - to the library in question.
    -		<p>To develop one of these, your test page should include the first two scripts discussed above, and
    -		then your own script containing your library specific functionality.  The existing implementations
    -		may be documented well enough for you to create your own, but contact me if you need assistance.  If you do this, it would be
    -		great to share it with everyone...</p>   
    -		<p>There is a group of people working on an ExtJS adapter currently.  There is no schedule for when this will be completed though.</p>
    -
    -			<!-- h4><a id="customConnectors">Custom Connectors</a></h4>			
    -					
    -			You can provide your own connectors if you need to. A Connector consists of two functions, which work as a pair.  First a call is made to the <em>compute</em> function:
    -
    -			<div class="code">
    -<pre>
    -this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) {
    -	...
    -	return dimensions;
    -}
    -</pre>
    -			</div>
    -			which is expected to return a list that the <em>paint</em> function can make sense of.  The first four entries in the
    -			list <strong>must be</strong> the [x,y,width,height] values for the canvas that the connector will be drawn on; jsPlumb will
    -			use this information to size the canvas prior to calling the Connector's <em>paint</em> function.  Therefore it
    -			is the Connector's responsibility to ensure that the returned dimensions describe a large enough space for
    -			the line that will be drawn on it.</p><p>The next four elements <strong>must be</strong> the coordinates of the two endpoints of the line
    -			you are going to draw.</p><p>The remainder of the items in the returned list are arbitrary, and will
    -			vary between Connector implementations; this list is passed in to a Connector's <em>paint</em> function, so each
    -			implementation will put into the list whatever it needs to paint itself.  For instance, the straight line
    -			connector only needs the [x,y] location of each end of the line it will paint, and that is one of the required entries, so
    -			it does not have to do anything extra,  whereas the Bezier connector adds the location of the two control points.  Other types of
    -			Connectors will do whatever is appropriate for their particular situation.
    -			</p>
    -			This is the method signature for the <em>paint</em> function:
    -			<div class="code">
    -				<pre>this.paint = function(dimensions, ctx) { .. }</pre>
    -			</div>
    -
    -			here, the 'dimensions' argument to the 'paint' function is the return value of the 'compute' function.  The 'ctx' argument is the Canvas context; you
    -			will do all your drawing on this.
    -			</p>
    -			To change the connector from the default, specify it in your connect call:
    -			<div class="code">
    -				<pre>jsPlumb.connect({source:'someWindow', target:'otherWindow', connector:new jsPlumb.Connectors.Straight()});</pre>
    -			</div>
    -			note that you can use shorthand for connectors if you don't need to specify parameters to it:
    -			<div class="code">
    -				<pre>jsPlumb.connect({source:'someWindow', target:'otherWindow', connector:"Straight"});</pre>
    -			</div>
    -			This works in the same way as specifying Anchors as string does.
    -<p>
    -The section below this discusses Overlays, which allow you to decorate Connectors with
    -			things such as labels or arrows or whatever else you like. Overlays can only work with Connectors that implement a few helper functions.
    -			<h4>The concept of <strong>location</strong></h4>
    -			Before discussing the helper functions you first must be across the concept of 'location' as used by this mechanism.  The <em>location</em>
    -			on a connector can be a decimal value between 0 and 1 inclusive, which marks a proportionate amount of travel <em>along the path
    -			inscribed by the Connector</em>.  For a straight line connector this is a simple piece of maths, but for Bezier connectors it's a little
    -			bit more involved.
    -			<h4>Required Helper Methods</h4>
    -			<ul>
    -				<li><strong>pointOnPath(location)</strong> - returns an [x,y] point corresponding to the given location</li>
    -				<li><strong>pointAlongPathFrom(location, distance)</strong> - returns an [x,y] point corresponding to travelling 'distance' pixels along the connector from 'location'.</li>
    -				<li><strong>gradientAtPoint(location)</strong> - returns the connector's gradient at the given location.  For linear connectors such as Straight this is constant, but for Bezier connectors the gradient changes continually.</li>
    -				<li><strong>perpendicularToPathAt(location, distance, length)</strong> - returns a line that is perpendicular to (and centered on) the connector at 'distance' from the given location, with length 'length'.</li>
    -			</ul>
    -</p>
    -<h4><a id="customEndpoints">Custom Endpoints</a></h4>
    -<p>To create your own Endpoint implementation, you need to implement a single method:
    -				<div class="code"><pre>paint : function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { ... }</pre></div>
    -				The arguments to this method are as follows:
    -				<ul>
    -					<li>anchorPoint - [x,y] location of the anchor point on screen</li>
    -					<li>orientation - [x,y] hints for the general direction the anchor points to</li>
    -					<li>canvas - the canvas to draw into</li>
    -					<li>endpointStyle - Javascript object containing style directives as discussed above.  The contents of this are arbitrary, so if you write a new Endpoint that needs some extra settings, you can add them no hassle.</li>
    -					<li>connectorPaintStyle - the style being used to paint the associated Connector.</li>
    -				</ul>
    -			</p>
    -			<p>
    -				It is your responsibility to size and locate the canvas to suit your needs. jsPlumb provides the following
    -				helper method to assist you:
    -				<div class="code"><pre>jsPlumb.sizeCanvas(canvas, x, y, width, height);</pre></div>
    -				Allows you to locate the canvas on screen and to size it.
    -			</p>
    -<h4><a id="customOverlays">Custom Overlays</a></h4>
    -			Overlays can only work with Connectors that implement the methods <strong>pointOnPath</strong>, <strong>pointAlongPathFrom</strong>,
    -			<strong>gradientAtPoint</strong>, and <strong>perpendicularToPathAt</strong>.  These are discussed in more detail in the Custom Connectors section above.  Both of the Connectors
    -			that come with jsPlumb - Straight and Bezier -implement these methods; if you write a custom connector, or have written a custom connector, you will need to supply
    -			them.   
    -			<h4>Overlay Interface</h4>
    -			An Overlay is required to implement two methods in order to be usable by jsPlumb:
    -			<ul>
    -				<li><strong>computeMaxSize(connector, context)</strong> - returns an integer value indicating the larger of this overlay's width and height.  It is used by jsPlumb to ensure that the canvas is large enough to accomodate the overlay.  The examples below should help to clarify this.</li>
    -				<li><strong>draw(connector, ctx)</strong> - draws the overlay.  What happens in this method is up to the given implementation.</li>
    -			</ul>
    -			<h4>computeMaxSize methods</h4>
    -			This is the Arrow overlay's computeMaxSize method:
    -			<div class="code">
    -<pre>
    -this.computeMaxSize = function() { return width; }
    -</pre>			
    -			</div>
    -Here, <em>width</em> is a private member of Arrow that indicates the width of the arrow's tail.  So the Arrow overlay reports that figure as the width it needs.					
    -Contrast this with the Label Overlay's computeMaxSize method:
    -		<div class="code">
    -<pre>
    -this.computeMaxSize = function(connector, ctx) {
    -  	if (labelText) {
    -  		ctx.save();
    -        if (self.labelStyle.font) ctx.font = self.labelStyle.font;
    -         var t = ctx.measureText(labelText).width;			            
    -		// a fake text height measurement: use the width of upper case M
    -		var h = ctx.measureText("M").width;					
    -		labelPadding = self.labelStyle.padding || 0.25;
    -		labelWidth = t + (2 * t * labelPadding);
    -		labelHeight = h + (2 * h * labelPadding);
    -		ctx.restore();
    -		return Math.max(labelWidth, labelHeight);
    -  	}
    - 	return 0;
    - };
    -</pre>			
    -			</div>
    -The Label overlay has to use the context to determine how big it will be on screen.
    -
    -<h4>draw methods</h4>
    -To give you a taste for how you can interact with a connector, consider the first few lines of the Arrow overlay's draw method:
    -<div class="code">
    -<pre>
    -this.draw = function(connector, ctx) {
    -	// this is the arrow head position
    -	var hxy = connector.pointAlongPathFrom(self.loc, length / 2);		
    -	// this is the center of the tail
    -	var txy = connector.pointAlongPathFrom(self.loc, -length / 2), tx = txy[0], ty = txy[1];
    -	// this is the tail vector
    -	var tail = connector.perpendicularToPathAt(self.loc, -length / 2, width);
    -	
    -	...
    -</pre>
    -</div>
    -	<p>The first two calls to the connector use the <strong>pointAlongPathFrom</strong> method, which passes <em>self.loc</em> as the location of the
    -	point on the path, and <em>length / 2</em> as the number of pixels along the path to traverse.</p>
    -	<p><em>self.loc</em> is Arrow's internal member describing the location of the overlay, and it is a decimal (between 0 and 1 inclusive) that points to
    -	a location at some distance along the path inscribed by the connector.  So these first two calls get us [x,y] locations of points on 
    -	the connector that mark the head and tail points for the arrow.  
    -	</p>		
    -	<p>
    -	The <strong>connector.perpendicularToPathAt(self.loc, -length / 2, width)</strong> call returns a line description of a line that is perpendicular to, and centered on, the connector
    -	at "-length / 2" pixels from the given point, with the given width (width is an internal member of Arrow).  At this point, the Arrow object has the three main points it
    -	needs in order to draw itself.
    -	</p>
    -		</div-->
    -</div>
    -
    -</body>
    -
    -</html>
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.12/index.html b/demo/doc/archive/1.3.12/index.html
    deleted file mode 100644
    index 888f1dc8b..000000000
    --- a/demo/doc/archive/1.3.12/index.html
    +++ /dev/null
    @@ -1,91 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.12 documentation</title>
    -		<link rel="stylesheet" target="contentFrame" href="/mp.css"></link>
    -		<link rel="stylesheet" target="contentFrame" href="jsPlumbDoc.css"></link>
    -	</head>
    -	<body>
    -		<div class="index section">
    -			<h3>jsPlumb 1.3.12</h3>
    -			<ul>
    -				<li><a target="contentFrame" href="content.html#summary">Summary</a></li>
    -				<li><a target="contentFrame" href="content.html#changes">Changes since version 1.3.11</a></li>
    -				
    -				<li><h4>Setup</h4></li>
    -				<li><a target="contentFrame" href="content.html#imports">Required Imports</a></li>
    -				<li><a target="contentFrame" href="content.html#renderMode">Render Mode</a></li>
    -				<li><a target="contentFrame" href="content.html#initializing">Initializing jsPlumb</a></li>				
    -				<li><a target="contentFrame" href="content.html#multipleInstances">Multiple jsPlumb Instances</a></li>
    -				<li><a target="contentFrame" href="content.html#zIndex">Z-index Considerations</a></li>
    -				<li><a target="contentFrame" href="content.html#containerDefault">Where does jsPlumb add elements?</a></li>
    -				<li><a target="contentFrame" href="content.html#elementDragging">Element dragging</a></li>
    -				
    -				<li><h4>Introduction</h4></li>
    -				<li><a target="contentFrame" href="content.html#jsPlumbBasics">Basic Concepts</a></li>
    -				<li><a target="contentFrame" href="content.html#anchors">Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#staticAnchors">Static Anchors</a></li>				
    -				<li><a target="contentFrame" href="content.html#dynamicAnchors">Dynamic Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#continuousAnchors">Continuous Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#definitions">Connector, Endpoint &amp; Overlay Definitions</a></li>
    -				<li><a target="contentFrame" href="content.html#connectors">Connectors</a></li>
    -				<li><a target="contentFrame" href="content.html#endpoints">Endpoints</a></li>
    -				<li><a target="contentFrame" href="content.html#overlays">Overlays</a></li>
    -				<li><a target="contentFrame" href="content.html#defaults">Defaults</a></li>								
    -				
    -				<li><h4>Connections</h4></li>
    -				<li><a target="contentFrame" href="content.html#simpleConnections">Programmatic Connections</a></li>
    -				<li><a target="contentFrame" href="content.html#draggableConnections">Connections using Drag and Drop</a></li>
    -				<li><a target="contentFrame" href="content.html#sourcesAndTargets">Elements as Sources &amp; Targets</a></li>
    -				<li class="sub"><a target="contentFrame" href="content.html#makeTarget">makeTarget</a></li>
    -				<li class="sub"><a target="contentFrame" href="content.html#makeSource">makeSource</a></li>
    -				<li><a target="contentFrame" href="content.html#dragOptions">Drag Options</a></li>				
    -				<li><a target="contentFrame" href="content.html#dropOptions">Drop Options</a></li>
    -				<li><a target="contentFrame" href="content.html#dragAndDropScope">Drag and Drop Scope</a></li>
    -				
    -				<li><h4>Parameters</h4></li>		
    -				<li><a target="contentFrame" href="content.html#connectParameters">jsPlumb.connect</a></li>
    -				<li><a target="contentFrame" href="content.html#addEndpointParameters">jsPlumb.addEndpoint</a></li>
    -				
    -				<li><h4>Connection and Endpoint Types</h4></li>		
    -				<li><a target="contentFrame" href="content.html#connectionTypes">Connection Type</a></li>
    -				<li><a target="contentFrame" href="content.html#endpointTypes">Endpoint Type</a></li>
    -
    -				<li><h4>Interceptors</h4></li>				
    -				<li><a target="contentFrame" href="content.html#beforeDrop">Before Drop</a></li>
    -				<li><a target="contentFrame" href="content.html#beforeDetach">Before Detach</a></li>
    -				
    -				<li><h4>Appearance</h4></li>
    -				<li><a target="contentFrame" href="content.html#paintstyles">Paint Styles</a></li>
    -				<li><a target="contentFrame" href="content.html#hoverpaintstyles">Hover Paint Styles</a></li>
    -				<li><a target="contentFrame" href="content.html#gradients">Gradients</a></li>				
    -				
    -				<li><h4>Miscellaneous</h4></li>
    -				<li><a target="contentFrame" href="content.html#cssclasses">CSS</a></li>
    -				<li><a target="contentFrame" href="content.html#animation">Animation</a></li>
    -				<li><a target="contentFrame" href="content.html#connectionInfo">Retrieving Connection Information</a></li>
    -				<li><a target="contentFrame" href="content.html#selectConnections">Selecting Connections</a></li>
    -				<li><a target="contentFrame" href="content.html#selectEndpoints">Selecting Endpoints</a></li>
    -				
    -				<li><h4>Events</h4></li>
    -				<li><a target="contentFrame" href="content.html#events">jsPlumb Events</a></li>
    -				<li><a target="contentFrame" href="content.html#connectionEvents">Connection Events</a></li>
    -				<li><a target="contentFrame" href="content.html#endpointEvents">Endpoint Events</a></li>
    -				<li><a target="contentFrame" href="content.html#overlayEvents">Overlay Events</a></li>
    -				
    -				<li><h4>Examples</h4></li>				
    -				<li><a target="contentFrame" href="content.html#examples">Programmatic API Examples</a></li>
    -				<li><a target="contentFrame" href="content.html#dragAndDropExamples">Draggable Connections Examples</a></li>
    -				<li><a target="contentFrame" href="content.html#utilityFunctions">Utility Functions</a></li>				
    -				
    -				<li><h4>Develop</h4></li>
    -				<li><a target="contentFrame" href="content.html#which">Which files are which?</a></li>
    -				<li><a target="contentFrame" href="content.html#pluggableLibrarySupport">Pluggable Library Support</a></li>
    -				<!-- li><a target="contentFrame" href="content.html#customConnectors">Custom Connectors</a></li>
    -				<li><a target="contentFrame" href="content.html#customEndpoints">Custom Endpoints</a></li>
    -				<li><a target="contentFrame" href="content.html#customOverlays">Custom Overlays</a></li-->
    -			</ul>
    -			<p><strong>This document refers to release 1.3.12 of jsPlumb.</strong></p>
    -			<strong>12 Aug 2012</strong>
    -		</div>		
    -	</body>
    -</html>
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.12/jsPlumbDoc.css b/demo/doc/archive/1.3.12/jsPlumbDoc.css
    deleted file mode 100644
    index f2949bd91..000000000
    --- a/demo/doc/archive/1.3.12/jsPlumbDoc.css
    +++ /dev/null
    @@ -1,127 +0,0 @@
    -body { 
    -	background-color:white; font-size:90%;
    -	background-image:url(../img/dynamicAnchorBg.jpg); 
    -}
    -/* docs */
    -.code { background-color:#e4eeee;
    -border:1px solid black; margin:1em; padding:0.8em; 
    -overflow:auto;
    -font-family:courier; 
    -font-size:8pt;
    -border-radius:0.3em;
    -}
    -.section { border-bottom: 1px dotted black; padding:1em;font-family:helvetica;background-color:white;color:#333;}
    -.menu { 
    -	position:fixed; 
    -	right:10em; top:1em;
    -	background-color:white;
    -	padding:0.3em;
    -	opacity:0.8;
    -	z-index:5000;
    -	filter:alpha(opacity=80);
    -		box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -a {text-decoration:none;color:#05b;font-weight:bold;font-family:helvetica;}
    -a:hover {text-decoration:underline;}
    -.window, .label { 
    -	cursor:pointer;
    -	box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -#explanation { 
    -	background-color:white;
    -	color:black;
    -	border:0.25em solid #ddd; 
    -	position:absolute;
    -	right:3.5em;
    -	top:5em;
    -	width:22em;
    -	font-size:100%;
    -	-moz-border-radius:1em;
    -	border-radius:1em;
    -	padding:1em;
    -	z-index:1000;
    -	opacity:0.9;
    -	filter:alpha(opacity=90); 
    -	font-family:helvetica;
    -		box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -.selected {
    -	color:rgb(189,11,11);
    -}
    -
    -
    -/*  these styles are what jsPlumb attaches to the various elements it creates.
    -._jsPlumb_connector {
    -border:1px solid blue;
    -}
    -._jsPlumb_endpoint {
    -border:1px solid green;
    -}
    -._jsPlumb_overlay {
    -border:1px solid yellow;
    -}
    -*/
    -
    -
    -.section li {
    -list-style-type:none;
    -}
    -
    -li.bullet {
    -	list-style-type:circle;
    -	}
    -
    -
    -.section {
    -padding-left:2em;
    -font-size:10pt;
    -}
    -.index.section {
    -padding-left:0;
    -}
    -
    -.index {
    -width:20em;
    -font-size:9pt;
    -float:left;
    -background-color:#e4eeee;
    -overflow:auto;
    -padding-left:0.5em;
    -}
    -
    -.index li {
    -list-style-type:none;
    -}
    -
    -.index ul, .index h3 {
    -padding-left:1.5em;
    -
    -}
    -
    -#demo {
    -width:80%;
    -height:50em;
    -margin-left:3em;
    -margin-top:3em;
    -position:relative;
    -}
    -
    -.sub {
    -margin-left:1em;
    -}
    -
    -.table td {
    -	text-align:center;
    -}
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.12/usage.html b/demo/doc/archive/1.3.12/usage.html
    deleted file mode 100644
    index 1eae0ebf5..000000000
    --- a/demo/doc/archive/1.3.12/usage.html
    +++ /dev/null
    @@ -1,10 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.12 - documentation</title>		
    -	</head>
    -	
    -	<frameset cols="250,*">
    -  		<frame src="index.html" />
    -  		<frame src="content.html" name="contentFrame" />
    -	</frameset>	
    -</html>
    diff --git a/demo/doc/archive/1.3.13/content.html b/demo/doc/archive/1.3.13/content.html
    deleted file mode 100644
    index e6bffcebe..000000000
    --- a/demo/doc/archive/1.3.13/content.html
    +++ /dev/null
    @@ -1,3701 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.13 - documentation</title>
    -		<link rel="stylesheet" href="/mp.css">
    -		<link rel="stylesheet" href="jsPlumbDoc.css">
    -	</head>
    -	<body>
    -
    -<div class="menu">
    -	<a href="http://jsplumb.org" class="mplink" target="_top">view demo</a>
    -	&nbsp;|&nbsp;<a href="mailto:simon.porritt@gmail.com" class="mplink">contact me</a>
    -	&nbsp;|&nbsp;<a href="http://jsplumb.tumblr.com" class="mplink">jsPlumb on Tumblr</a>	
    -</div>
    -
    -<!--
    -GWT
    -
    -http://fishfilosophy.blogspot.com/2011/12/how-to-connect-gwt-widgets-using.html?spref=tw
    -
    --->
    -
    -<div class="content">
    -		<div class="section">
    -			<h3><a id="summary">Summary</a></h3>
    -			jsPlumb allows you to connect elements on the screen using SVG, Canvas or VML, depending on the capabilities of the browser.
    -			<p>
    -				It can be used with jQuery, MooTools or YUI3 (or another library of your choice if you feel like implementing an adapter for it).
    -			</p>  
    -
    -			<h5>Browser Compatibility</h5>
    -			<p>jsPlumb 1.3.13 runs on everything from IE6 up.  There are some caveats, though, because of various browser/library bugs:
    -				<ul>
    -					<li class="bullet">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.
    -					<li class="bullet">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)</li>
    -					<li class="bullet">MooTools has a bug when using SVG in Firefox 11</li>
    -				<ul>
    -				
    -			</p>
    -		</div>
    -		
    -		<div class="section">		
    -			<h3><a id="changes">Changes between 1.3.13 and 1.3.12</a></h3>
    -			
    -			<h5>Backwards Compatibility</h5>
    -			No issues.
    -			
    -			<h5>New Features</h5>
    -			<ul>
    -				<li class="bullet">"Perimeter" anchors. These are anchors that follow the path of the perimeter of some shape; they are designed for use in UIs that have non-rectangular nodes such as flowcharts.</li>
    -				<li class="bullet">performance enhancements for rendering</li>
    -			</ul>
    -
    -			<h5>Issues Resolved</h5>
    -			<ul>
    -				<li class="bullet"><strong>273</strong> - nested endpoint ignored by dragging</li>
    -				<li class="bullet"><strong>274</strong> - jsPlumb.connect does not register endpoints with drag manager</li>
    -			</ul>
    -			
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="init">Setup</a></h3>
    -
    -            <h4><a id="imports">Required Imports</a></h4>
    -			<h4>jQuery</h4>
    -			    <ul>
    -				    <li class="bullet">jQuery 1.3.x or higher.</li>
    -				    <li class="bullet">jQuery UI 1.7.x or 1.8.x (if you wish to support drag and drop).</li>
    -			    </ul>
    -			    <div class="code">
    -<pre>&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/jquery-ui.min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/jquery.jsPlumb-1.3.13-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			<h4>MooTools</h4>
    -				<ul>
    -					<li class="bullet">MooTools Core 1.2.4 or higher. jsPlumb has been tested with 1.2.4 and 1.3.3.</li>
    -					<li class="bullet">Drag.Move from MooTools More 1.2.4.4 or higher (if you wish to support drag and drop).</li>
    -				</ul>
    -			<div class="code">
    -			<pre>&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/mootools/1.3.3/mootools-yui-compressed.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO_MOO_TOOLS_MORE_1_3_2_1"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/mootools.jsPlumb-1.3.13-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			<h4>YUI3</h4>
    -			<ul>
    -				<li class="bullet">YUI 3.3.x.  jsPlumb has been tested on 3.3.0 only; it is possible other 3.x.x versions will work.</li>
    -			</ul>
    -			<div class="code">
    -			<pre>&lt;script type="text/javascript" src="http://yui.yahooapis.com/3.3.0/build/simpleyui/simpleyui-min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/yui.jsPlumb-1.3.13-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			
    -			<h4><a id="renderMode">Render Mode</a></h4>
    -			jsPlumb can use SVG, HTML5 Canvas elements or VML to render the various objects it adds to the display.  Most modern browsers support Canvas and SVG; IE&lt;9 browsers support neither.
    -			<p>
    -				By default, jsPlumb attempts to use <strong>jsPlumb.SVG</strong> as the render mode (prior to 1.3.4, the default renderer was Canvas), falling back to <strong>jsPlumb.VML</strong> if the 
    -				browser is IE&lt;9.  You can change the render mode by making this call:  
    -			</p>
    -			<div class="code">
    -<pre>jsPlumb.setRenderMode(jsPlumb.CANVAS);</pre>			
    -			</div>
    -			if, for some reason, you set the render mode to be <strong>jsPlumb.VML</strong> but you're in any browser other than IE, jsPlumb will use SVG.
    -			<p>
    -			The <strong>jsPlumb.setRenderMode</strong> method returns you the render mode that jsPlumb actually ended up setting.  Valid values for setRenderMode are:
    -			</p>
    -			<ul>
    -				<li class="bullet">jsPlumb.CANVAS</li>
    -				<li class="bullet">jsPlumb.SVG</li>
    -				<li class="bullet">jsPlumb.VML</li>
    -			</ul>			
    -			<h5>Performance Considerations</h5>
    -			<p>
    -			SVG uses less memory than Canvas and has support for mouse events built in, so I recommend using
    -			SVG unless you have a really good reason for wanting Canvas. Of course, you might consider that horrendous Safari bug a really good reason for wanting Canvas.
    -			</p>
    -			
    -			<h4><a id="initializing">Initializing jsPlumb</a></h4>
    -			You should not start making calls to jsPlumb until the DOM has been initialized - perhaps no surprises there.  With YUI, though, the asynchronous
    -			nature of the script loading process means that you are not guaranteed that jsPlumb is ready as soon as the DOM is.  To handle this,
    -			you should bind to the "ready" event on jsPlumb (or the instance of jsPlumb you are working with):
    -			<div class="code">
    -			<pre>jsPlumb.bind("ready", function() {
    -...	
    -		
    -// your jsPlumb related init code goes here
    -
    -...
    -
    -});						
    -			</pre>
    -			</div>
    -			There's a helper method that can save you approximately 9 (count them!) characters:
    -			<div class="code">
    -			<pre>jsPlumb.ready(function() {
    -...	
    -		
    -// your jsPlumb related init code goes here
    -
    -...
    -
    -});						
    -			</pre>
    -			</div>
    -			<p>
    -			For jQuery and MooTools you do not actually need to do this; by the time the DOM ready event is fired in each of those libraries you can be sure 
    -			all the JS we need has been loaded.  But in terms of explicitly calling out that you are waiting for jsPlumb, it seems like a good practice to bind to the "ready" event.
    -			</p>
    -			<p>
    -			If you bind to the "ready" event after jsPlumb has already been initialized, your callback will be executed immediately.
    -			</p>			
    -
    -				
    -<h4><a id="multipleInstances">Multiple jsPlumb instances</a></h4>
    -			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 <strong>getInstance</strong> method, for example:
    -<div class="code">
    -<pre>
    -var firstInstance = jsPlumb.getInstance();
    -</pre>
    -</div>
    -The variable 'firstInstance' can now be treated exactly as you would treat the 'jsPlumb' variable - you can set
    -defaults, call the connect method, whatever:
    -<div class="code">
    -<pre>
    -firstInstance.importDefaults({
    -  Connector : [ "Bezier", { curviness: 150 } ],
    -  Anchors : [ "TopCenter", "BottomCenter" ]
    -});
    -
    -firstInstance.connect({source:"element1", target:"element2", scope:"someScope" });
    -</pre>
    -</div>
    -
    -<strong>getInstance</strong> optionally takes an object that provides the defaults:
    -<div class="code">
    -<pre>
    -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" });
    -</pre>
    -</div>
    -
    -		<h4><a id="zIndex">Z-index Considerations</a></h4>
    -		<p>
    -			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. 
    -		</p>  
    -		<p>
    -			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).  			
    -		</p>
    -		<p>
    -			To help you organise z-indices correctly, jsPlumb adds a CSS class to each type of
    -			element it adds. They are as follows:			
    -		</p>
    -		<table style="color:black;width:70%;font-size:90%;">
    -			<tr><td><strong>type of element</strong></td><td><strong>class added</strong></td></tr>
    -			<tr><td>Endpoint</td><td>_jsPlumb_endpoint</td></tr>
    -			<tr><td>Connector</td><td>_jsPlumb_connector</td></tr>
    -			<tr><td>Overlay</td><td>_jsPlumb_overlay</td></tr>
    -		</table>
    -		
    -		<h5>Automatic z-index for Connections</h5>
    -		A common use case is the need to bring hovered Connections up on top of the other connections in your UI. From
    -		1.3.13 onwards, jsPlumb offers a way to do this:
    -		<div class="code">
    -<pre>
    -  jsPlumb.importDefaults({
    -    ...
    -    ConnectorZIndex:5
    -	...
    -  });
    -</pre>			
    -		</div>
    -		This tells jsPlumb to set the z-index of all Connections to be 5, and the z-index of any hovered Connections will
    -		be, in this case, 6.  You could argue that jsPlumb could figure out the z-index automatically, but z-index selection
    -		is typically a conscious decision in a webapp, and this approach formalises things a little better than trying
    -		to infer what the value is supposed to be.
    -		<p>To achieve this, there is actually a <strong>setZIndex</strong> method on Connections now, which is perfectly fine
    -		to call, but remember that if you change the z-index of some Connection, hover over it, and then mouse exit, resetting
    -		the hover state will mean that jsPlumb resets the z-index to the default value discussed above.</p>
    -
    -		<h4><a id="containerDefault">Where does jsPlumb add elements?</a></h4>
    -		<p>
    -			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.
    -		</p>
    -		<p>
    -			Prior to 1.3.0, jsPlumb added everything to the end of the <em>body</em> element. This
    -			has the advantage of being the most flexible arrangement in terms of supporting what
    -			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!   
    -		</p>
    -		<p>
    -			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 (<strong>not</strong> 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.
    -		</p>
    -		
    -		<p>
    -			In general, the default behaviour means that you cannot do things like this:
    -		</p>
    -		<div class="code">
    -<pre>
    -&lt;body&gt;
    -	&lt;div id="container0"&gt;
    -		&lt;div id="node0"&gt;&lt;/div&gt;
    -	&lt;/div&gt;
    -	&lt;div id="container1"&gt;
    -		&lt;div id="node1"&gt;&lt;/div&gt;
    -	&lt;/div&gt;
    -&lt;/body&gt;
    -
    -&lt;script type="text/javascript"&gt;
    -
    -	var e0 = jsPlumb.addEndpoint("node0"),
    -	e1 = jsPlumb.addEndpoint("node1");
    -
    -	jsPlumb.connect({ source:e0, target:e1 });
    -&lt;/script&gt;
    -</pre>		
    -		</div>
    -<p>
    -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):  
    -</p>
    -		<div class="code">
    -<pre>
    -&lt;body&gt;
    -	&lt;div id="container0"&gt;
    -	&lt;/div&gt;
    -	&lt;div id="container1"&gt;
    -	&lt;/div&gt;
    -&lt;/body&gt;
    -
    -&lt;script type="text/javascript"&gt;
    -
    -	var e0 = jsPlumb.addEndpoint("container0"),
    -	e1 = jsPlumb.addEndpoint("container1");
    -
    -	jsPlumb.connect({ source:e0, target:e1 });
    -&lt;/script&gt;
    -</pre>		
    -		</div>
    -	now 'e0' and 'e1' have the same parent - the <em>body</em> element. 
    -	
    -	<p>Remember that you can use the <strong>anchor</strong> parameter to an <strong>addEndpoint</strong>
    -	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. 
    -	</p>									
    -	<p><strong>Note regarding the drawEndpoints option on jsPlumb.connect</strong>: 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). 
    -	</p>
    -	
    -	<h5>Overriding the default behaviour</h5>
    -	<p>
    -From version 1.3.3, the <strong>container</strong> 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 <strong>jsPlumb.Defaults.Container</strong> property, or you can set a <strong>container</strong> parameter on calls to <strong>addEndpoint</strong> or <strong>connect</strong> (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:
    -	</p>
    -		
    -Set a container to use as the default container, using a jQuery selector (you can supply MooTools/YUI selectors if that's the library you're using), and then add an Endpoint.  The canvas (or SVG/VML element) created will be a child of the document body:
    -		<div class="code">
    -<pre>
    -jsPlumb.Defaults.Container = $("body");
    -...
    -jsPlumb.addEndpoint(someDiv, { endpoint options });
    -</pre>		
    -		</div>
    -		
    -			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":
    -		<div class="code">
    -<pre>
    -jsPlumb.Defaults.Container = "containerId";
    -...
    -jsPlumb.connect({ source:someDiv, target:someOtherDiv });
    -</pre>		
    -		</div>
    -		
    -		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":
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({ source:someDiv, target:someOtherDiv, container:$("#containerId") });
    -</pre>		
    -		</div>
    -				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":
    -		<div class="code">
    -<pre>
    -jsPlumb.addEndpoint(someDiv, { ..., container:"containerId" });
    -</pre>		
    -		</div>
    -
    -		<h4><a id="elementDragging">Element Dragging</a></h4>
    -		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 <strong>jsPlumb.draggable(...)</strong>:
    -<div class="code">
    -<pre>jsPlumb.draggable("elementId");</pre>
    -</div>
    -You can also pass a selector from your library in to this method:
    -<div class="code">
    -<pre>jsPlumb.draggable($(".someClass");</pre>
    -</div>
    -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.  A common requirement when using jQuery, for instance, is to set the containment of elements that you are making draggable.  Here's how you would tell jsPlumb to initialise some set of elements to be draggable but only inside their parent:
    -<div class="code">
    -<pre>jsPlumb.draggable($(".someClass", {
    -	containment:"parent"
    -});</pre>
    -</div>
    -		<h5>Dragging nested elements</h5>
    -jsPlumb takes nesting into account when handling draggable elements. For example, say you have this markup:
    -<div class="code">
    -<pre>
    -&lt;div id="container"&gt;
    -  &lt;div class="childType1"&gt;&lt;/div&gt;
    -  &lt;div class="childType2"&gt;&lt;/div&gt;
    -&lt;/div&gt;
    -</pre>
    -</div>
    -..and then you connect one of those child divs to something, and make "container" draggable:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:$("#container .childType1"),
    -	target:"somewhere else"
    -});
    -
    -jsPlumb.draggable("container");
    -</pre>
    -</div>
    -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.
    -
    -
    -		</div>
    -				
    -		<div class="section">
    -			<h3><a id="jsPlumbBasics">Basic Concepts</a></h3>			
    -	
    -			jsPlumb is all about connecting things together, so the core abstraction in jsPlumb is the <strong>Connection</strong> object, which is itself
    -			broken down into these four concepts:
    -
    -			<ul>
    -				<li><strong>Anchor</strong> - a location, relative to an element's origin, at which an Endpoint can exist. You do not create these yourself; you 
    -				supply hints to the various jsPlumb functions, which create them as needed.  They have no visual representation; they are a logical position only. Anchors can be referenced by name, for the Anchors that jsPlumb ships with, or a four element array describing [ x, y, x orientation, y orientation ]. 
    -				See the <a href="#anchors">anchors</a> section for more detail.<br/><br/></li>
    -				<li><strong>Endpoint</strong> - the visual representation of one end of a Connection.  You can create and attach these to elements yourself, which you are required to do to support drag and drop, or have jsPlumb create them when creating a Connection programmatically using jsPlumb.connect(...).  You can also join two Endpoints programmatically, by passing them as arguments to jsPlumb.connect(...).<br/><br/></li>				
    -				<li><strong>Connector</strong> - the visual representation of the line connecting two elements in the page.  jsPlumb has four types of these 
    -				available as defaults - a Bezier curve, a straight line, 'flowchart' connectors and 'state machine connectors. You do not interact with Connectors; 
    -				you just specify definitions of them when you need to - see <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions</a> <br/><br/></li>
    -				<li><strong>Overlay</strong> - a UI component that is used to decorate a Connector, such as a Label, Arrow, etc.<br/><br/></li>
    -			</ul>
    -						
    -			<p>One Connection is made up of two Endpoints, a Connector, and zero or more Overlays working together to join two elements.</p>
    -			<p>jsPlumb's public API exposes only Connection and Endpoint, handling the creation and configuration of everything else internally. But you still need to be across the concepts encapsulated by Anchor, Connector and Overlay.
    -			</p>
    -			</div>
    -			
    -			<div class="section">
    -			<h3><a id="anchors">Anchors</a></h3>			
    -	
    -			An Anchor models the notion of where on an element a Connector should connect.  There are three main types of Anchors:
    -			<ul>
    -			<li/>
    -				<li class="bullet">'Static' anchors - these are fixed to some point on an element and do not move.  They can be specified using a string to
    -				identify one of the defaults that jsPlumb ships with, or an array describing the location (see below)<br/><br/></li>				
    -				<li class="bullet">'Dynamic' anchors - these are lists of Static anchors from which jsPlumb picks the most appropriate one each time a Connection
    -				is painted.  The algorithm used to determine the most appropriate anchor picks the one that is closest to the center of the other element in
    -				the Connection. A future version of jsPlumb might support a pluggable algorithm to make this decision.<br/><br/></li>
    -				<li class="bullet">'Perimeter' anchors - these are anchors that follow the perimeter of some given shape. They are, in essence, Dynamic
    -				anchors whose locations are chosen from the perimeter of the underlying shape.<br/><br/></li>
    -				<li class="bullet">'Continuous' anchors - These anchors, introduced in 1.3.4, are not fixed to any specific loction; they are assigned to one of
    -				the four faces of an element depending on that element's orientation to the other element in the associated Connection.  Continuous anchors are slightly more computationally intensive than Static or Dynamic anchors because jsPlumb is required to calculate the position of every Connection during a paint cycle, rather than just Connections belonging to the element in motion.  These anchors were designed for use with the StateMachine connector that was also introduced in 1.3.4, but will work with any combination of Connector and Endpoint.<br/><br/></li> 
    -			</ul>  
    -					
    -			<h4><a id="staticAnchors">Static Anchors</a></h4>		
    -			jsPlumb has nine default anchor locations you can use to specify where the Connectors connect to elements: these are the four corners of an element,
    -			the center of the element, and the midpoint of each edge of the element:  
    -			<ul>
    -				<li class="bullet">TopCenter</li>
    -				<li class="bullet">TopRight</li>
    -				<li class="bullet">RightMiddle</li>
    -				<li class="bullet">BottomRight</li>
    -				<li class="bullet">BottomCenter</li>
    -				<li class="bullet">BottomLeft</li>
    -				<li class="bullet">LeftMiddle</li>
    -				<li class="bullet">TopLeft</li>
    -			</ul>
    -			
    -Each of these string representations is just a wrapper around the underlying array-based syntax  [x, y, dx, dy], where x and y are coordinates in the interval [0,1] specifying the position of the
    -anchor, and dx and dy are coordinates in the interval [-1, 1] specifying the orientation of the curve incident to the anchor. For example, [0, 0.5, -1, 0] defines a "LeftCenter" anchor with a connector curve that emanates leftward from the anchor. Similarly, [0.5, 0, 0, -1] defines a "CenterTop" anchor with a connector curve emanating upwards. 	
    -
    -
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:"BottomCenter", ... }); 
    -</pre>
    -</div>
    -is identical to:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:[ 0.5, 1, 0, 1 ], ... }); 
    -</pre>
    -</div>		
    -In addition to supplying the location and orientation of an anchor, you can optionally supply two more parameters that define an offset in pixels from the given location.  Here's the anchor specified above, but with a 50 pixel offset below the element in the y axis:	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:[ 0.5, 1, 0, 1, 0, 50 ], ... }); 
    -</pre>
    -</div>		
    -
    -		<h4><a id="dynamicAnchors">Dynamic Anchors</a></h4>
    -	These are Anchors that can be positioned in one of a number of locations, choosing the one that is most appropriate each time something moves or is painted in the UI.
    -	<p>
    -	There is no special syntax to creating a DynamicAnchor; you just provide an array of individual Static Anchor specifications, eg:
    -	</p>
    -<div class="code">
    -<pre>
    -var dynamicAnchors = [ [ 0.2, 0, 0, -1 ],  [ 1, 0.2, 1, 0 ], 
    -			   [ 0.8, 1, 0, 1 ], [ 0, 0.8, -1, 0 ] ];
    -
    -jsPlumb.connect({...., anchor:dynamicAnchors, ... }); 			   
    -</pre>
    -</div>
    -<h4>	Default Dynamic Anchor</h4>
    -jsPlumb provides a dynamic anchor called "AutoDefault" that chooses from TopCenter, RightMiddle, BottomCenter and LeftMiddle:
    -<div class="code">
    -<pre>jsPlumb.connect({...., anchor:"AutoDefault", ... });</pre>
    -</div>
    -
    -<h4>	Location Selection</h4>
    -The initial implementation of the algorithm that decides which location to choose just calculates which location is closest to the center of the 
    -other element in the connection.  It is possible that future versions of jsPlumb could support more sophisticated choice algorithms, if the need arose.
    -<h4>Draggable Connections</h4>
    -Dynamic Anchors and Draggable Connections can interoperate: jsPlumb locks the position of a dynamic anchor when you start to drag a connection from it,
    -and unlocks it once the connection is either established or abandoned. At that point you may see the position of the dynamic anchor change, as jsPlumb
    -optimises the connection.  <p>You can see this behaviour in the <a href="../html/jquery/draggableConnectorsDemo.html" target="_blank">draggable connections</a> demonstration, when
    -you drag a connection from the blue endpoint on window 1 to the blue endpoint on window 3 - the connection is established and then window 1's blue
    -endpoint jumps down to a location that is closer to window 3.</p>
    -
    -	<h4><a id="perimeterAnchors">Perimeter Anchors</a></h4>	
    -These are a form of Dynamic anchor in which the anchor locations are chosen from the perimeter of some given shape. jsPlumb
    -supports six shapes:
    -
    -<ul>
    -	<li class="bullet">circle</li>
    -	<li class="bullet">ellipse</li>
    -	<li class="bullet">triangle</li>
    -	<li class="bullet">diamond</li>
    -	<li class="bullet">rectangle</li>
    -	<li class="bullet">square</li>	
    -</ul>
    -
    -'Rectangle' and 'Square', are not, strictly speaking, necessary, since rectangular shapes are the norm in a web page. But they
    -are included for completeness.
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("someElement", {
    -	endpoint:"Dot",
    -	anchor:[ "Perimeter", { shape:"circle" } ]
    -});
    -</pre>
    -</div>
    -
    -In this example our anchor will travel around the path inscribed by a circle whose diameter is the width and height of the
    -underlying element. Note that the 'circle' shape is therefore identical to 'ellipse', since it is assumed the underlying
    -element will have equal width and height, and if it does not, you will get an ellipse. 'rectangle' and 'square' have the
    -same relationship.
    -<p>By default, jsPlumb approximates the perimeter with 60 anchor locations.  You can change this, though:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("someDiv", {
    -	endpoint:"Dot",
    -	anchor:[ "Perimeter", { shape:"square", anchorCount:150 }]
    -});
    -</pre>		
    -</div>
    -Obviously, the more points the smoother the operation. But also the more work your browser has to do.
    -<p>'triangle' and 'diamond' both assume that there is a point at the top center of the shape. This may or may not turn
    -out to be acceptable for all applications, in which case it might be possible to add support for a 'rotation' argument to the
    -anchor constructor.</p>
    -Here's a triangle and diamond example, just for kicks:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:"Dot",
    -	anchors:[
    -		[ "Perimeter", { shape:"triangle" } ],
    -		[ "Perimeter", { shape:"diamond" } ]
    -	]
    -});
    -</pre>		
    -</div>
    -
    -	<h4><a id="continuousAnchors">Continuous Anchors</a></h4>
    -	As discussed above, these are anchors whose positions are calculated by jsPlumb according to the orientation between elements in a Connection, and also how
    -	many other Continuous anchors happen to be sharing the element.  You specify that you want to use Continuous anchors using the string syntax you would use
    -	to specify one of the default Static Anchors, for example:
    -	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:someDiv,
    -	target:someOtherDiv,
    -	anchor:"Continuous"
    -});
    -</pre>
    -</div>
    -
    -Note in this example I specified only "anchor", rather than "anchors" - jsPlumb will use the same spec for both anchors.  But I could have said this:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:someDiv,
    -	target:someOtherDiv,
    -	anchors:["BottomCenter", "Continuous"]
    -});
    -</pre>
    -</div>	 	  
    -..which would have resulted in the source element having a Static Anchor at BottomCenter.  In practise, though, it seems the Continuous Anchors work best if both 
    -elements in a Connection are using them.
    -<p>Note also that Continuous Anchors can be specified on <strong>addEndpoint</strong> calls:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint(someDiv, {
    -	anchor:"Continuous",
    -	paintStyle:{ fillStyle:"red" }
    -});
    -</pre>
    -</div>	 	
    -...and in <strong>makeSource</strong>/<strong>makeTarget</strong>:
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource(someDiv, {
    -	anchor:"Continuous",
    -	paintStyle:{ fillStyle:"red" }
    -});
    -
    -jsPlumb.makeTarget(someDiv, {
    -	anchor:"Continuous",
    -	paintStyle:{ fillStyle:"red" }
    -});
    -</pre>
    -</div>	 
    -... and in the jsPlumb defaults:
    -<div class="code">
    -<pre>
    -jsPlumb.Defaults.Anchor = "Continuous";
    -</pre>
    -</div>  
    -					</div>		
    -												
    -<div class="section">
    -		<h4><a id="definitions">Connector, Endpoint &amp; Overlay Definitions</a></h4>
    -Before we discuss Connector, Endpoints and Overlays, a quick word on definitions:  whenever you need to define a Connector, Endpoint or Overlay, you must use a "definition" of it, rather than constructing one directly.  This definition can be either a string that nominates the artifact you want to create:
    -		<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:"Rectangle"
    -});
    -</pre>
    -		</div>
    -		or an array consisting of both the artifact's name and the arguments you want to pass to its constructor:
    -		<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Rectangle", { cssClass:"myEndpoint", width:30, height:10 } ]
    -});
    -</pre>
    -		</div>
    -There is also a three-argument method that allows you to specify two sets of parameters, which jsPlumb will merge together for you. The idea behind this is that you will often want to define common characteristics somewhere and reuse them across a bunch of different calls:
    -		<div class="code">
    -<pre>var common = {
    -	cssClass	:	"myCssClass",
    -	hoverClass	:	"myHoverClass"
    -};
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Rectangle", { width:30, height:10 }, common ]
    -});
    -</pre>
    -		</div>
    -	This syntax is supported for all Endpoint, Connector and Overlay definitions.  Here's an example using definitions for all three:	
    -		<div class="code">
    -<pre>var common = {
    -	cssClass:"myCssClass"
    -};
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Dot", { radius:5, hoverClass:"myEndpointHover" }, common ],
    -	connector:[ "Bezier", { curviness:100 }, common ],
    -	overlays: [
    -		[ "Arrow", { foldback:0.2 }, common ],
    -		[ "Label", { cssClass:"labelClass" } ]	
    -	]
    -});
    -</pre>
    -		</div>		
    -The allowed constructor parameters are different for each artifact you create, but every artifact takes a single JS object as argument, with the parameters as [key,value] pairs in that object.  See the relevant sections on <a href="#connectors">Connectors</a>, <a href="#endpoints">Endpoints</a> and <a href="#overlays">Overlays</a> for information on supported parameters.</a>
    -				
    -	</div>
    -
    -		<div class="section">
    -			<h3><a id="connectors">Connectors</a></h3>
    -			<p>Connectors are the lines that actually join elements of the UI.  jsPlumb has four connector implementations - a straight line, a Bezier curve, "flowchart", and "state machine".  The default connector is the Bezier curve.</p>
    -			<p>You optionally specify a Connector by setting the 'connector' property on a call to jsPlumb.connect, jsPlumb.addEndpoint(s), jsPlumb.makeSource or jsPlumb.makeTarget. If you do not supply a value for 'connector', the default will be used (see <a href="#defaults">defaults</a>).</p>
    -			<p>You specify Connectors using the syntax described above in <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions.</a> Allowed constructor values for each Connector type are described below:</p>		
    -			<h4><a id="bezierConnector">Bezier Connector</a></h4>
    -			<p>The Bezier Connector provides a cubic Bezier path between the two Endpoints. It supports a single constructor argument:</p>
    -			<p>
    -			<strong>curviness</strong> - Optional; defaults to 150. This defines the distance in pixels that
    -			the Bezier's control points are situated from the anchor points.  This does not mean that your
    -			connector will pass through a point at this distance from your curve.  It is a hint to how you want the
    -			curve to travel. Rather than discuss Bezier curves at length here, because they are a complex topic,
    -			refer instead to <a href="http://en.wikipedia.org/wiki/B%C3%A9zier_curve" target="_blank">Wikipedia.</a>
    -			</p>
    -			<h4><a id="straightConnector">Straight Connector</a></h4>
    -			<p>The Straight Connector draws a straight line between the two endpoints. No constructor arguments are supported; use the <strong>endpointStyle</strong> and/or <strong>cssClass</strong>/<strong>connectorClass</strong> to a <strong>connect</strong>, <strong>addEndpoint</strong>, <strong>makeSource</strong> or <strong>makeTarget</strong> call to control the appearance of one of these Connectors.  
    -			</p>
    -			<h4><a id="flowchartConnector">Flowchart Connector</a></h4>
    -			<p>This type of Connector draws a connection that consists of a series of vertical or horizontal segments - the classic flowchart look. Two constructor arguments are supported:</p>
    -			<strong>stub</strong> - this is the minimum length, in pixels, of the initial stub that emanates from an Endpoint.  This is an optional parameter, and can be either an integer, which specifies the stub fo each end of the connector, or an array of two integers, specifying the stub for the [source, target] endpoints in the connection.  This parameter is optional, and defaults to an integer with value 30 pixels.<br/>
    -			<strong>gap</strong> - optional, defaults to 0 pixels. Lets you specify a gap between the end of the connector and the elements to which it is attached.
    -			<p>This Connector supports Connection that start and end on the same element.</p>
    -			<h4><a id="stateMachineConnector">State Machine Connector</a></h4>
    -			<p>This Connector draws slightly curved lines (they are actually quadratic Bezier curves), similar to the State Machine connectors you may have seen in software like GraphViz.  Connections in which some element is both the source and the target (so-called 'loopback') are supported by these Connectors (as they are with Flowchart Connectors); in this case you get a circle. Supported parameters are:
    -			</p>
    -			<p>
    -			<strong>margin</strong> - Optional; defaults to 5.  Defines the distance from the element that the connector begins/ends.
    -			</p>
    -			<p>
    -			<strong>curviness</strong> - Optional, defaults to 10.  This has a similar effect to the curviness parameter on Bezier curves.
    -			</p>
    -			<p>
    -			<strong>proximityLimit</strong> - Optional, defaults to 80.  The minimum distance between the two ends of the Connector before it paints itself as 
    -			a straight line rather than a quadratic Bezier curve.
    -			</p>    
    -			
    -		</div>
    -		
    -			<div class="section">
    -			<h3><a id="endpoints">Endpoint Types</a></h3>
    -			<p>An Endpoint is the UI component that marks the location of an Anchor, ie. the place where a Connector joins an
    -			element.  jsPlumb comes with four Endpoint implementations - <strong>Dot</strong>, <strong>Rectangle</strong>, <strong>Blank</strong> and <strong>Image</strong>. You optionally specify Endpoint properties using the
    -			<strong>endpoint</strong> parameter in a call to jsPlumb.connect, jsPlumb.addEndpoint, jsPlumb.makeSource or jsPlumb.makeTarget.
    -			</p>  
    -				<p>As with Connectors and Overlays, you specify Endpoints using the syntax described above in <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions.</a></p>
    -			
    -			The four available Endpoint types, and their constructor arguments, are as follows:
    -					<h4><a id="dotEndpoint">Dot Endpoint</a></h4>
    -					This Endpoint draw a dot on the screen. It supports three constructor parameters:
    -					<p>
    -						<strong>radius</strong> - Optional; defaults to 10 pixels. Defines the radius of the dot.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="rectangleEndpoint">Rectangle Endpoint</a></h4>
    -						Draws a rectangle. Supported constructor parameters are:
    -					<p>
    -						<strong>width</strong> - Optional; defaults to 20 pixels. Defines the width of the rectangle.
    -					</p>
    -					<p>
    -						<strong>height</strong> - Optional; defaults to 20 pixels. Defines the height of the rectangle.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="imageEndpoint">Image Endpoint</a></h4>
    -						Draws an image from a given URL.  This Endpoint supports three constructor parameters:  
    -					<p>
    -						<strong>src</strong> - Required.  Specifies the URL of the image to use.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="blankEndpoint">Blank Endpoint</a></h4>
    -						Does not draw anything visible to the user.  As discussed previously, this Endpoint is probably not what you want if you need your users to be able to drag existing Connections - for that, use a Rectangle or Dot Endpoint and assign to it a CSS class that causes it to be transparent.
    -																			
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="overlays">Overlay Types</a></h3>
    -			<p>
    -			Overlays are UI elements that are painted onto connections, such as labels or arrows.
    -			jsPlumb comes with four defaults:
    -			</p>
    -			<ul>
    -				<li><strong>Arrow</strong> - a configurable arrow that is painted at some point along the connector.  You can control the length and width of the Arrow, the 'foldback' point - a point the tail points fold back into, and the direction (allowed values are 1 and -1; 1 is the default and means point in the direction of the connection)</li>
    -				<li><strong>Label</strong> - a configurable label that is painted at some point along the connector.</li>
    -				<li><strong>PlainArrow</strong> - an Arrow shaped as a triangle, with no foldback.</li>
    -				<li><strong>Diamond</strong> - as the name suggests, a diamond.</li>
    -			</ul> 
    -			The last two are actually just configured instances of the generic Arrow overlay (see examples). 
    -			
    -			<p>All Overlays support these two methods for getting/setting their location (location is either a number between 0 and 1 inclusive, indicating some point along the path inscribed by the associated Connector, or an absolute number of pixels, where negative values mean distance from the target of the connection):</p>
    -			<ul>
    -				<li class="bullet"><strong>getLocation</strong> - returns the current location</li>
    -				<li class="bullet"><strong>setLocation</strong> - sets the current location; valid values are decimals in the range 0-1 inclusive, or an integer less than 0 (to express distance from the target), or an integer greater than 1 (to express distance from the source)</li> 
    -			</ul>
    -			
    -			<p>You can specify one or more overlays when making a call to jsPlumb.connect, jsPlumb.addEndpoint or jsPlumb.makeSource (but not jsPlumb.makeTarget; overlays
    -			are always derived from what the source of a Connection defines)  The three cases are discussed below:</p>
    -			1. Specifying one or more overlays on a jsPlumb.connect call. In this example we'll create an Arrow with the default options for an Arrow, and
    -			a label with the text "foo":
    -			<div class="code">
    -			<pre>jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});			
    -</pre>
    -			</div>
    -			This connection will have an arrow located halfway along it, and the label "foo" one quarter of the way along.  Notice the <strong>id</strong>
    -			parameter; it can be used later if you wish to remove the Overlay or change its visibility (see below).<br/><br/> 
    -			Another example, this time with an absolute location of 50 pixels from the source:
    -			<div class="code">
    -			<pre>jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:50 }, id:"myLabel" ]
    -	],
    -	...
    -});			
    -</pre>
    -			</div>
    -			
    -			2. Specifying one or more overlays on a jsPlumb.addEndpoint call.  Note in this example that we use the parameter 'connectorOverlays' and not
    -			'overlays' as in the last example.  This is because 'overlays' would refer to Endpoint Overlays:
    -			<div class="code">
    -			<pre>jsPlumb.addEndpoint("someDiv", {
    -	...
    -	connectorOverlays:[ 
    -		[ "Arrow", { width:10, length:30, location:1, id:"arrow" } ], 
    -		[ "Label", { label:"foo", id:"label" } ]
    -	],
    -	...
    -});</pre>
    -			</div>	
    -			This connection will have a 10x30 Arrow located right at the head of the connection, and the label "foo" located at the halfway point. Again,
    -			note that <strong>id</strong> parameter passed in to each Overlay spec.<br/><br/>
    -			
    -			3. Specifying one or more overlays on a jsPlumb.makeSource call.  Note in this example that we again use the parameter 'connectorOverlays' and not
    -			'overlays'.  The 'endpoint' parameter to jsPlumb.makeSource supports everything you might pass to the second argument of a jsPlumb.addEndpoint call:
    -			<div class="code">
    -			<pre>jsPlumb.makeSource("someDiv", {
    -	...
    -	endpoint:{
    -		connectorOverlays:[ 
    -			[ "Arrow", { width:10, length:30, location:1, id:"arrow" } ], 
    -			[ "Label", { label:"foo", id:"label" } ]
    -		]
    -	}
    -	...
    -});</pre>
    -			</div>	
    -			This connection will have a 10x30 Arrow located right at the head of the connection, and the label "foo" located at the halfway point. Again,
    -			note that <strong>id</strong> parameter passed in to each Overlay spec.<br/><br/>
    -			
    -			 	
    -					<h4><a id="arrowOverlay">Arrow Overlay</a></h4>
    -					<p>This overlay draws an arrow, using four points: the head and two tail points, and a 'foldback' point, which permits the tail
    -					of the arrow to be indented. Available constructor arguments for this Overlay are </p>
    -					<ul>
    -					<li><strong>width</strong> - width of the tail of the arrow</li>
    -					<li><strong>length</strong> - distance from the tail of the arrow to the head</li>
    -					<li><strong>location</strong> - where, either as a proportional value from 0 to 1 inclusive, or as an absolute value (negative values mean distance from target; positive values greater than 1 mean distance from source) the Arrow should appear on the Connector</li>
    -					<li><strong>direction</strong> - which way to point. Allowed values are 1 (the default, meaning forwards) and -1, meaning backwards </li>
    -					<li><strong>foldback</strong> - how far along the axis of the arrow the tail points foldback in to. Default is 0.623.</li>
    -					<li><strong>paintStyle</strong> - a style object in the form used for paintStyle values for Endpoints and Connectors</li>
    -					</ul>
    -					<h4><a id="plainArrowOverlay">PlainArrow Overlay</a></h4>
    -					<p>This is just a specialized instance of Arrow in which jsPlumb hardcodes 'foldback' to 1, meaning the tail of the Arrow is a flat edge.  All of the constructor parameters from Arrow apply for PlainArrow.</p>
    -					<h4><a id="diamondOverlay">Diamond Overlay</a></h4>
    -					<p>This is a specialized instance of Arrow in which jsPlumb hardcodes 'foldback' to 2, meaning the Arrow turns into a Diamond.  All of the constructor parameters from Arrow apply for Diamond.</p>
    -					<h4><a id="labelOverlay">Label Overlay</a></h4>
    -					This provides a text label to decorate Connectors with.  The available constructor arguments are:
    -					<ul>
    -						<li><strong>label</strong> - The text to display.  You can provide a function here instead of plain text: it is passed the Connection as an argument, and it should return a String.</li>
    -						<li><strong>cssClass</strong> - Optional css class to use for the Label.  This is now preferred over using the 'labelStyle' parameter.</li>
    -						<li><strong>labelStyle</strong> - (deprecated, use cssClass instead) Optional arguments for the label's appearance.  Valid entries in this JS object are:
    -							<ul>
    -								<li><em>font</em> - a font string in a format suitable for the Canvas element</li>
    -								<li><em>fillStyle</em> - the color to fill the label's background with. Optional.</li>
    -								<li><em>color</em> - the color of the label's text. Optional.</li>
    -								<li><em>padding</em> - optional padding for the label. This is expressed as a proportion of the width of the label, not in pixels or ems.</li>
    -								<li><em>borderWidth</em> - optional width in pixels for the label's border. Defaults to 0.</li>
    -								<li><em>borderStyle</em> - optional. The color to paint the border, if there is one.</li>
    -								
    -							</ul>
    -						
    -						</li>
    -						<li><strong>location</strong> - As for Arrow Overlay.  Where, either proportionally from 0 to 1 inclusive, or as an absolute offset from either source or target, the label should appear.</li>
    -					</ul>
    -
    -					The Label overlay offers two methods - getLabel and setLabel - for accessing/manipulating its content dynamically:
    -<div class="code">
    -<pre>
    -var c = jsPlumb.connect({
    -	source:"d1", 
    -	target:"d2", 
    -	overlays:[
    -		[ "Label", {label:"FOO", id:"label"}]
    -	]	
    -});					
    -
    -...
    -
    -var label = c.getOverlay("label");
    -console.log("Label is currently", label.getLabel());
    -label.setLabel("BAR");
    -console.log("Label is now", label.getLabel());
    -</pre>
    -</div>
    -In this example you can see that the Label Overlay is assigned an id of "label" in the connect call, and then retrieved using that id in the call to Connection's getOverlay method.
    -<p>Both Connections and Endpoints support Label Overlays, and because changing labels is quite a common operation, <strong>setLabel</strong> and <strong>getLabel</strong> methods have been added to these objects:
    -<div class="code">
    -<pre>
    -var conn = jsPlumb.connect({
    -	source:"d1", 
    -	target:"d2",
    -	label:"FOO"
    -});					
    -
    -...
    -
    -console.log("Label is currently", conn.getLabel());
    -conn.setLabel("BAR");
    -console.log("Label is now", conn.getLabel());
    -</pre>
    -</div>
    -These methods support passing in a Function instead of a String, and jsPlumb will create a label overlay for you if one does not yet exist when you call setLabel:
    -<div class="code">
    -<pre>
    -var conn = jsPlumb.connect({
    -	source:"d1", 
    -	target:"d2"
    -});					
    -
    -...
    -
    -conn.setLabel(function(c) {
    -	var s = new Date();
    -	return s.getTime() + "milliseconds have elapsed since 01/01/1970";
    -});
    -console.log("Label is now", conn.getLabel());
    -</pre>
    -</div>
    -					
    -					<h4><a id="hideshowOverlay">Hiding/Showing Overlays</a></h4>
    -					<p>You can control the visibility of Overlays using the <strong>setVisible</strong> method of Overlays themselves, or with
    -					<strong>showOverlay(id)</strong> or <strong>hideOverlay(id)</strong> on a Connection.</p>
    -					<p>Remember the <strong>id</strong> parameter that we specified in the examples above?  This can be used to retrieve the Overlay
    -					from a Connection:</p>
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -var overlay = connection.getOverlay("myLabel");
    -// now you can hide this Overlay:
    -overlay.setVisible(false);
    -// there are also hide/show methods:
    -overlay.show();
    -overlay.hide();
    -
    -</pre>					
    -					</div>
    -					<p>However, Connection and Endpoint also have two convenience methods you could use instead:
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:-30 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -connection.hideOverlay("myLabel");
    -
    -// more time passes
    -
    -connection.showOverlay("myLabel");
    -
    -</pre>					
    -					</div>
    -					
    -		<h4><a id="hideshowOverlay">Removing Overlays</a></h4>
    -		<p>Connection and Endpoint also have a <strong>removeOverlay</strong> method, that does what you might expect:
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -connection.removeOverlay("myLabel");
    -
    -
    -</pre>					
    -					</div>							
    -	
    -		</div>
    -	
    -			
    -			<div class="section">
    -			<h3><a id="defaults">Defaults</a></h3>
    -			The easiest way to set a look and feel for your plumbing is to override the defaults that jsPlumb uses. If you
    -			do not do this you are forced to provide your overridden values on every call.  Every argument to the connect and addEndpoint methods has an
    -			associated default value in jsPlumb.<br/><br/>
    -
    -			The defaults that ship with jsPlumb are stored in <em>jsPlumb.Defaults</em>, which is a Javascript object.  Valid entries, and their initial values, are:
    -
    -			<div class="code">
    -<pre>
    -Anchor : "BottomCenter",
    -Anchors : [ null, null ],
    -Connector : "Bezier",
    -ConnectionOverlays : [ ],
    -DragOptions : { },
    -DropOptions : { },
    -Endpoint : "Dot",
    -EndpointOverlays : [ ],
    -Endpoints : [ null, null ],
    -EndpointStyle : { fillStyle : null },
    -EndpointStyles : [ null, null ],
    -EndpointHoverStyle : null,
    -EndpointHoverStyles : [ null, null ],
    -HoverPaintStyle : null,
    -LabelStyle : { color : "black" },
    -LogEnabled : false,
    -Overlays : [ ],
    -MaxConnections : 1,
    -PaintStyle : { lineWidth : 8, strokeStyle : "#456" },
    -RenderMode : "svg",
    -Scope : "_jsPlumb_DefaultScope"
    -</pre>
    -			</div>
    -			<p>
    -			Note that in EndpointStyle, the default fillStyle is 'null'.  This instructs jsPlumb to use the strokeStyle
    -from the attached connector to fill the endpoint.</p>
    -<p>Note also that you can specify either or both (or neither) of 'EndpointStyle' and 'EndpointStyles'.  This allows you to specify a different
    -end point for each end of a connection.  'Endpoint' and 'Endpoints' use the same concept.  jsPlumb will look first in the
    -individual endpoint/endpoint style arrays, and then fall back to the single default version.</p>
    -
    -			you can override these defaults by including this in a script somewhere:
    -			<div class="code">
    -<pre>
    -jsPlumb.Defaults.PaintStyle = {
    -	lineWidth:13,
    -	strokeStyle: 'rgba(200,0,0,0.5)'
    -}
    -
    -jsPlumb.Defaults.DragOptions = { cursor: "crosshair" };
    -
    -jsPlumb.Default.Endpoints = [ [ "Dot", 7 ], [ "Dot", 11 ] ];
    -
    -jsPlumb.Defaults.EndpointStyles = [{ fillStyle:"#225588" }, { fillStyle:"#558822" }];</pre>
    -			</div>
    -			after the jsPlumb script has been loaded of course!  Here we have specified the following default behaviour:
    -			<ul>
    -				<li>- connectors are 13 pixels wide and painted with a semi-transparent red line</li>
    -				<li>- when dragging an element the crosshair cursor is used</li>
    -				<li>- the source endpoint is a dot of radius 7; the target endpoint is a dot of radius 11</li>
    -				<li>- the source endpoint is blue; the target endpoint is green</li>
    -			</ul>
    -
    -			<h5>jsPlumb.importDefaults</h5>
    -There is a helper method called <strong>importDefaults</strong> that allows you to import a set of values with just one call to jsPlumb.  Here's the previous example using importDefaults:
    -			<div class="code">
    -<pre>
    -jsPlumb.importDefaults({
    -	PaintStyle : {
    -		lineWidth:13,
    -		strokeStyle: 'rgba(200,0,0,100)'
    -	},
    -	DragOptions : { cursor: "crosshair" },
    -	Endpoints : [ [ "Dot", 7 ], [ "Dot", 11 ] ],
    -	EndpointStyles : [{ fillStyle:"#225588" }, { fillStyle:"#558822" }]
    -});</pre>
    -			</div>
    -
    -		</div> 					
    -
    -<div class="section">
    -			<h3><a id="simpleConnections">Connections</a></h3>
    -			<h4><a id="simpleConnections">Programmatic Connections</a></h4>
    -The most simple connection you can make with jsPlumb looks like this:
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({source:"element1", target:"element2"});</pre>			
    -			</div>
    -
    -		In this example we have created a Connection from 'element1' to 'element2'.  Remember that a Connection in jsPlumb consists of two Endpoints,
    -		a Connector, and zero or more Overlays.  But this call to 'connect' supplied none of those things, so jsPlumb uses the default values wherever it needs to.
    -		In this case, default values have been used for the following: 
    -		<ul>
    -			<li>- The type and appearance of each Endpoint in the Connection. jsPlumb's default for this is the "Dot" endpoint, of radius 10, with fill color "#456".</li>
    -			<li>- The Anchors that define where the connection's Endpoints appear on each element. The jsPlumb default is "BottomCenter"</li>			
    -			<li>- Whether or not each Endpoint can be a source or target for new Connections. The default is false.</li>
    -			<li>- The type and appearance of the Connection's Connector. The default is a "Bezier" connector of line width 8, and color "#456".</li>
    -		</ul>
    -		<p>
    -		So this call will result in an 8px Bezier, colored "#456", from the bottom center of 'element1' to the bottom center of 'element2', and each
    -		Endpoint will be a 10px radius Dot Endpoint, colored "#456".
    -		</p>
    -		<p>
    -		Let's beef up this call a little and tell jsPlumb what sort of Endpoints we want, and where we want them:		
    -		</p>
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:"element1", 
    -	target:"element2",
    -	anchors:["RightMiddle", "LeftMiddle" ],
    -	endpoint:"Rectangle",
    -	endpointStyle:{ fillStyle: "yellow" }
    -});
    -</pre>			
    -			</div>
    -			
    -		<p>This is what we have told jsPlumb we want this time:</p>
    -		<ul>
    -			<li><strong>anchors</strong> - this array tells jsPlumb where the source and target Endpoints should be located on their parent elements. In this case,
    -			we use the shorthand syntax to name one of jsPlumb's default anchors; you can also specify custom locations (see <a href="#anchors">anchors</a>).
    -			Instead of <strong>anchors</strong> you can use <strong>anchor</strong>, if you want the source and target Endpoints to be located at the
    -			same place on their parent elements.			<br/>
    -			</li>
    -			<li>
    -				<strong>endpoint</strong>  - this tells jsPlumb to use the "Rectangle" Endpoint for both the source and target of the Connection.  As with anchors, 
    -				<strong>endpoint</strong> has a plural version that allows you to specify a different Endpoint for each end of the Connection.<br/>  
    -			</li>
    -			<li>
    -				<strong>endpointStyle</strong> - this is the definition of the appearance of the Endpoint you specified above.  Again, there is a
    -				plural equivalent of this that allows you to specify a different style for each end of the Connection. For more information about
    -				allowed values for this value, see <a href="#paintstyles">Connector, Endpoint & Overlay Styles</a>. 
    -			</li>
    -		</ul>		
    -		
    -		<h5>Reusing common settings between jsPlumb.connect calls</h5>
    -		<p>
    -			A fairly common situation you will find yourself in is wanting to create a bunch of Connections that have only minor differences
    -			between them.  To support that, <strong>jsPlumb.connect</strong> takes an optional second argument. For example:			 
    -		</p>
    -		<div class="code">
    -<pre>
    -var common = {
    -	anchors:[ "BottomCenter", "TopCenter" ],
    -	endpoints:["Dot", "Blank" ]
    -};
    -
    -jsPlumb.connect({ source:"someElement", target:"someOtherElement" }, common);
    -
    -jsPlumb.connect({ source:"aThirdElement", target:"yetAnotherElement" }, common);
    -
    -</pre>			
    -			</div>		
    -			
    -		<h5>Endpoints created by jsPlumb.connect</h5>
    -		<p>If you supply an element id or selector for either the source or target , jsPlumb.connect will automatically create an Endpoint on the 
    -		given element.  These automatically created Endpoints are not marked as drag source or targets, and cannot
    -		be interacted with.  For some situations this behaviour is perfectly fine, but for more interactive UIs you should set things up using the drag and drop
    -		method discussed below.</p>
    -		<p>Given that jsPlumb.connect creates its own Endpoints in some circumstances, in order to avoid leaving orphaned Endpoints around the place, if the Connection is subsequently
    -		deleted, these created Endpoints are deleted too.  Should you want to, you can override this behaviour by setting <strong>deleteEndpointsOnDetach</strong> to
    -		false in the connect call:</p>
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({ 
    -	source:"aThirdElement", 
    -	target:"yetAnotherElement",
    -	deleteEndpointsOnDetach:false 
    -});
    -</pre>
    -</div>	
    -
    -	<h5>Detaching Connections</h5>
    -	By default, connections made with jsPlumb.connect will be detachable via the mouse.  You can prevent this by either setting an appropriate default value:
    -
    -		<div class="code">
    -<pre>
    -jsPlumb.importDefaults({ 
    -	...
    -	ConnectionsDetachable:false
    -	...
    -});
    -</pre>
    -</div>	
    -
    -	Or by specifying it on the connect call like this:
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({ 
    -	source:"aThirdElement", 
    -	target:"yetAnotherElement",
    -	detachable:false
    -});
    -</pre>
    -</div>		
    -								
    -					
    -		<h4><a id="draggableConnections">Connections using Drag and Drop</a></h4>
    -		To support drag and drop connections, you first need to set a few things up.  Every drag and drop connection needs at least a source
    -		Endpoint that the user can drag a connection from. Here's a simple example of how to create an Endpoint:
    -		<div class="code">
    -<pre>
    -var endpointOptions = { isSource:true };
    -var endpoint = jsPlumb.addEndpoint('elementId', endpointOptions);
    -</pre>		
    -</div>  	
    -	This Endpoint will act as a source for new Connections, and will use the jsPlumb defaults for its own appearance and that of any Connections that are drawn from it.							
    -	
    -		<h5>Tip: use the three-argument addEndpoint method for common data </h5>
    -		One thing that happens quite often is that you have an Endpoint whose appearance and behaviour is largely the
    -		same between usages on different elements, with just a few differences. 
    -		<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -	endpoint:"Rectangle",
    -	paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -	isSource:true,
    -	connectorStyle : { strokeStyle:"#666" },
    -	isTarget:true
    -};
    -</pre>		
    -</div>  		
    -		Notice there is no 'anchor' set.  Here we apply it to two elements, at a different location in each:
    -		<div class="code">
    -<pre>
    -jsPlumb.addEndpoint('element1', { anchor:"BottomCenter" }, exampleGreyEndpointOptions)); 
    -
    -jsPlumb.addEndpoint('element2', { anchor:"TopCenter" }, exampleGreyEndpointOptions));
    -</pre>		
    -</div>
    -
    -<p>
    -Now that you have a source Endpoint, you need to either create a target Endpoint on some element, or notify jsPlumb that you wish to make an
    -entire element a drop target.  Let's look at how to attach a target Endpoint first:
    -</p>
    -
    -		<div class="code">
    -<pre>var endpointOptions = { isTarget:true, endpoint:"Rectangle", paintStyle:{ fillStyle:"gray" } };
    -var endpoint = jsPlumb.addEndpoint("otherElementId", endpointOptions);
    -</pre>		
    -		</div>
    -		<p>
    -		This Endpoint, a gray rectangle, has declared that it can act as a drop target for Connections. 
    -</p>
    -<p>
    -jsPlumb also supports marking entire elements as drag sources or drag targets, using the <strong>makeSource</strong> and <strong>makeTarget</strong> methods.  This is a useful function for certain applications.  Each of these methods takes an Endpoint declaration which defines the Endpoint that jsPlumb will create after a Connection has been attached.  Let's take a look at makeTarget first:
    -</p>
    -<h4><a id="sourcesAndTargets">Elements as sources &amp; targets</a></h4>
    -jsPlumb also supports turning entire elements into Connection sources and targets, using the methods <strong>makeSource</strong> and <strong>makeTarget</strong>.  With these methods you mark an element as a source or target, and provide an Endpoint specification for jsPlumb to use when a Connection is established.  makeSource also gives you the ability to mark some child element as the place from which you wish to drag Connections, but still have the Connection on the main element after it has been established.
    -<p>These methods honour the jsPlumb defaults - if, for example you set up the default Anchors to be this:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.Defaults.Anchors = [ "LeftMiddle", "BottomRight" ];
    -</pre>
    -</div>
    -... and then used makeSource and makeTarget without specifying Anchor locations, jsPlumb would use "LeftMiddle" for the makeSource element and "BottomRight" for the makeTarget element.
    -<p>
    -A further thing to note about makeSource and makeTarget is that any prior calls to one of these methods is honoured by subsequent calls to jsPlumb.connect. This helps when you're building a UI that uses this functionality at runtime but which loads some initial data, and you want the statically loaded data to have the same appearance and behaviour as dynamically created connections (obviously quite a common use case).  
    -<p>For example:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -	anchor:"Continuous",
    -	endpoint:["Rectangle", { width:40, height:20 }],
    -	maxConnections:3
    -});
    -
    -...
    -
    -jsPlumb.connect({source:"el1", target:"el2"});
    -</pre>
    -</div>
    -In this example, the source of the connection will be a Rectangle of size 40x20, having a Continuous Anchor.  The source is also configured to allow a maximum of three connections.
    -<p>You can override this behaviour by setting a <strong>newConnection</strong> parameter on the connect call:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -	anchor:"Continuous",
    -	endpoint:["Rectangle", { width:40, height:20 }],
    -	maxConnections:1,
    -	onMaxConnections:function(info, originalEvent) {
    -		console.log("element is ", info.element, "maxConnections is", info.maxConnections);	
    -	}
    -});
    -
    -...
    -
    -jsPlumb.connect({ source:"el1", target:"el2", newConnection:true });
    -</pre>
    -</div>
    -Note the <strong>onMaxConnections</strong> parameter to this call - it allows you to supply a function to call if the user tries to drag a new Connection when the source has already reached capacity.
    -<h5><a id="makeTarget">jsPlumb.makeTarget</a></h5>
    -<p>
    -This method takes two arguments, the first of which specifies some element (or list of elements); the second specifies the Endpoint you wish to create on that element whenever a Connection is established on it.  In this example we will use the exact same target Endpoint we used before - the gray rectangle - but we will tell jsPlumb that the element "aTargetDiv" will be the drop target:
    -</p>
    -		<div class="code">
    -<pre>var endpointOptions = { 
    -  isTarget:true, 
    -  maxConnections:5,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" } 
    -};
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -<p>
    -	The allowed values in 'endpointOptions' are identical for both the <strong>jsPlumb.addEndpoint</strong> and <strong>jsPlumb.makeTarget</strong> methods, but makeTarget supports an extended 
    -	anchor syntax that allows you more control over the location of the target endpoint.  This is discussed below.	
    -</p>		
    -<p>Notice in the 'endpointOptions' object above there is a 'isTarget' parameter set - this may seem incongruous, since you know you're going to make some element a target.  Remember that the endpointOptions object is the information jsPlumb will use to create an Endpoint on the given target element each time a Connection is established to it. It takes the exact same format as you would pass to addEndpoint; makeTarget is essentially a deferred addEndpoint call followed by a connect call.  So in this case, we're telling jsPlumb that any Endpoints it happens to create on some element that was configured by the makeTarget call are themselves Connection targets.
    -</p>
    -<p>makeTarget also supports the maxConnections and onMaxConnections parameters, as makeSource does, but note that onMaxConnections is passed one extra parameter than its corresponding callback from makeSource - the Connection the user tried to drop:</p>
    -		<div class="code">
    -<pre>
    -jsPlumb.makeTarget("aTargetDiv", { 
    -  isTarget:true, 
    -  maxConnections:5,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" },
    -  maxConnections:3,
    -  onMaxConnections:function(info, originalEvent) {
    -	console.log("user tried to drop connection", info.connection, "on element", info.element, "with max connections", info.maxConnections);
    -  }
    -};
    -</pre>		
    -		</div>
    -
    -<h5>Unique Endpoint per Target </h5>
    -<p>jsPlumb will create a new Endpoint using the supplied information every time a new Connection is established on the target element, by default, but you can override this behaviour and tell jsPlumb that it should create at most one Endpoint, which it should attempt to use for subsequent Connections:
    -</p>
    -		<div class="code">
    -<pre>var endpointOptions = { 
    -  isTarget:true, 
    -  uniqueEndpoint:true,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" } 
    -};
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -Here, the <strong>uniqueEndpoint</strong> parameter tells jsPlumb that there should be at most one Endpoint on this element.  Notice that 'maxConnections' is not set: the default is 1, so in this setup we have told jsPlumb that "aTargetDiv" can receive one Connection and no more.
    -<h5>Deleting Endpoints on Detach</h5>
    -By default, any Endpoints created using makeTarget have <strong>deleteEndpointsOnDetach</strong> set to true, which means that once all Connections to that Endpoint are removed, the Endpoint is deleted.  You can override this by setting the flag to true on the makeTarget call:
    -		<div class="code">
    -<pre>var endpointOptions = { 
    -  isTarget:true, 
    -  maxConnections:5,
    -  uniqueEndpoint:true,
    -  deleteEndpointsOnDetach:false,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" } 
    -};
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -In this setup we have told jsPlumb to create an Endpoint the first time "aTargetElement" is connected to, and to not delete it even if there are no longer any Connections to it.  The created Endpoint will be reused for subsequent Connections, and can support a maximum of 5.
    -
    -<h5>Detaching connections made with the mouse</h5>
    -As with jsPlumb.connect, connections made with the mouse after setting up Endpoints with one of the functions we've just covered will be, by default, detachable.  You can prevent this in the jsPlumb defaults, as previously mentioned:
    -		<div class="code">
    -<pre>jsPlumb.importDefaults({ 
    -	...
    -	ConnectionsDetachable:false,
    -	...
    -});
    -</pre>		
    -		</div>
    -
    -	And you can also set this on a per-endpoint (or source/target) level, like in these examples:
    -
    -		<div class="code">
    -<pre>jsPlumb.addEndpoint("someElementId", { 
    -	connectionsDetachable:false	
    -});
    -
    -jsPlumb.makeSource("someOtherElement", {
    -	...
    -	connectionsDetachable:false,
    -	...
    -});
    -
    -jsPlumb.makeTarget("yetAnotherElement", {
    -	...
    -	connectionsDetachable:false,
    -	...
    -});
    -</pre>		
    -		</div>
    -
    -Note that in the jsPlumb defaults, by convention each word in a parameter is capitalised ("ConnectionsDetachable"), whereas for a call to one of these methods, we use camel case ("connectionsDetachable").
    -
    -
    -<h5>Target Anchors positions with makeTarget</h5>
    -<p>When using the makeTarget method, jsPlumb allows you to provide a callback function to be used to determine
    -the appropriate location of a target anchor for every new Connection dropped on the given target.  It may be the
    -case that you want to take some special action rather than just relying on one of the standard anchor mechanisms.
    -</p>
    -<p>This is achieved through an extended anchor syntax (note that this syntax is <strong>not supported</strong> in
    -the <strong>jsPlumb.addEndpoint</strong> method) that supplies a "positionFinder" to the anchor specification.  jsPlumb 
    -provides two of these by default; you can register your own on jsPlumb and refer to them by name, or just supply a function.  Here's a few examples:</p>
    -Instruct jsPlumb to place the target anchor at the exact location at which the mouse button was released on the
    -target element. Note that you tell jsPlumb the anchor is of type "Assign", and you then provide a "position"
    -parameter, which can be the name of some position finder, or a position finder function itself:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {
    -  anchor:[ "Assign", { 
    -    position:"Fixed"
    -  }]
    -});
    -</pre>
    -</div>
    -<p><strong>Fixed</strong> is one of the two default position finders provided by jsPlumb. The other is <strong>Grid</strong>:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {
    -  anchor:[ "Assign", { 
    -    position:"Grid",
    -    grid:[3,3]
    -  }]
    -});
    -</pre>
    -</div>
    -The Grid position finder takes a "grid" parameter that defines the size of the grid required. [3,3] means
    -3 rows and 3 columns.
    -<h5>Supplying your own position finder</h5>
    -<p>To supply your own position finder to jsPlumb you first need to create the callback function. First let's take a look at what the source code for the Grid position finder looks like:</p>
    -<div class="code">
    -<pre>
    -function(eventOffset, elementOffset, elementSize, constructorParams) {
    -  var dx = eventOffset.left - elementOffset.left, dy = eventOffset.top - elementOffset.top,
    -      gx = elementSize[0] / (constructorParams.grid[0]), 
    -      gy = elementSize[1] / (constructorParams.grid[1]),
    -      mx = Math.floor(dx / gx), my = Math.floor(dy / gy);
    -
    -  return [ ((mx * gx) + (gx / 2)) / elementSize[0], ((my * gy) + (gy / 2)) / elementSize[1] ];
    -}
    -</pre>
    -</div>
    -The four arguments are:
    -<ul>
    -	<li class="bullet"><strong>eventOffset</strong> - Page left/top where the mouse button was released (a JS object containing left/top members like you get from a jQuery offset call)</li>
    -	<li class="bullet"><strong>elementOffset</strong> - JS offset object containing offsets for the element on which the Connection is to be created</li>
    -	<li class="bullet"><strong>elementSize</strong> - [width, height] array of the dimensions of the element on which the Connection is to be created</li>
    -	<li class="bullet"><strong>constructorParams</strong> - the parameters that were passed to the Anchor's constructor. In the example given above, those parameters are 'position' and 'grid'; you can pass arbitrary parameters.
    -</ul>
    -The return value of this function is an array of [x, y] - proportional values between 0 and 1 inclusive, such as you can pass to a static Anchor.
    -<p>To make your own position finder you need to create a function that takes those four arguments and returns an [x, y] position for the anchor, for example:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.AnchorPositionFinders.MyFinder = function(dp, ep, es, params) {
    -	...		
    -	return [ some maths ];	
    -};
    -</pre>
    -</div>
    -Then refer to it in a makeTarget call:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {  
    -  anchor:[ "Assign", { 
    -    position:"MyFinder",
    -    myCustomParameter:"foo",
    -    anInteger:5
    -  }]
    -});
    -</pre>
    -</div>
    -
    -<h5><a id="makeSource">jsPlumb.makeSource</a></h5>
    -There are two use cases supported by this method.  The first is the case that you want to drag a Connection from the element itself and have an Endpoint attached to the element when a Connection is established.  The second is a more specialised case: you want to drag a Connection from the element, but once the Connection is established you want jsPlumb to move it so that its source is on some other element.  For an example of this second case, check out the <a href="../demo/jquery/stateMachineDemo.html">State Machine Connectors</a> demonstration.
    -<p>Here's an example code snippet for the basic use case of makeSource:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource(someDiv, {
    -  paintStyle:{ fillStyle:"yellow" },
    -  endpoint:"Blank",
    -  anchor:"BottomCenter"
    -});
    -</pre>
    -</div>
    -<p>
    -Notice again that the second argument is the same as the second argument to an addEndpoint call.  makeSource is, essentially, a type of addEndpoint call.  In this example we have told jsPlumb that we will support dragging Connections directly from 'someDiv'.  Whenever a Connection is established between 'someDiv' and some other element, jsPlumb assigns an Endpoint at BottomCenter of 'someDiv', fills it yellow, and sets that Endpoint as the newly created Connection's source. 
    -</p>		
    -<p>Configuring an element to be an entire Connection source using makeSource means that the element cannnot itself be draggable.  There would be no way for jsPlumb to distinguish between the user attempting to drag the element and attempting to drag a Connection from the element.  If your UI works in such a way that that is acceptable then you'll be fine to use this method.  But if you want to drag Connections from some element but also have the element be draggable itself, you might want to consider the second use case.  First, a code snippet:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("someConnectionSourceDiv", {
    -  paintStyle:{ fillStyle:"yellow" },
    -  endpoint:"Blank",
    -  anchor:"BottomCenter",
    -  parent:"someDiv"
    -});
    -</pre>
    -</div>
    -<p>The only difference here is the inclusion of the 'parent' parameter.  It instructs jsPlumb to configure the source Endpoint on the element specified - in this case, "someDiv" (the value of this parameter may be a String id or a selector).</p>
    -<p>Again, I'd suggest taking a look at the <a href="../demo/jquery/stateMachineDemo.html">State Machine Connectors demonstration</a> for an example of this.</p>
    -
    -<h5>Filtering drag start based on element</h5>
    -You can supply a 'filter' parameter to the makeSource call in order to get a little more fine-grained control over which parts of the element will respond to a mousedown and start a drag. Consider this markup:
    -<div class="code">
    -<pre>
    -&lt;div id="foo"&gt;
    -  FOO
    -  &lt;button&gt;click me&lt;/button&gt;
    -&lt;/div&gt;
    -</pre>
    -</div>
    -Suppose we do not want to interfere with the operation of the 'click me' button. We can supply a filter to the makeSource call to do so:
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("foo", {
    -  filter:function(event, element) {
    -    return event.target.tagName !== "BUTTON";
    -  }
    -});
    -</pre>
    -</div>
    -If the filter returns anything other than a boolean false, the drag will begin. It's important to note that only boolean false will prevent a drag. False-y values will not.
    -
    -<h5>Endpoint options with makeSource</h5>
    -<p>
    -There are many things you can set in an Endpoint options object; for a thorough list see the API documentation for Endpoint.  
    -</p>
    -<p>Here's an example of specifying that you want an Arrow overlay halfway along any Connection dragged from this Endpoint:</p>
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -  isSource:true,
    -  connectorStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  connectorOverlays: [ [ "Arrow", { location:0.5 } ] ]
    -};
    -</pre>
    -</div>
    -This is an Endpoint that moves around the element it is attached to dependent on the location of other elements in the connections it is attached to (a 'dynamic' anchor):
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -  isSource:true,
    -  connectorStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  connectorOverlays: [ [ "Arrow", { location:0.5 } ] ]
    -  anchor:[ "TopCenter","RightMiddle","BottomCenter","LeftMiddle" ]
    -};
    -</pre>
    -</div>
    -<h5><a id="targetSourceTest">Testing if an element is a target or source</a></h5>
    -You can test if some element has been made a connection source or target using these methods:
    -<ul>
    -<li class="bullet">isTarget</li>
    -<li class="bullet">isSource</li>
    -</ul>
    -<p>The return value is a boolean.</p>
    -<h5><a id="targetSourceToggle">Toggling an element as connection target or source</a></h5>
    -You can toggle the state of some source or target using these methods:
    -<ul>
    -<li class="bullet">setTargetEnabled</li>
    -<li class="bullet">setSourceEnabled</li>
    -</ul>
    -<p>The return value from these methods is the current jsPlumb instance, allowing you to chain them:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.setTargetEnabled("aDivId").setSourceEnabled($(".aSelector"));
    -</pre>
    -</div>
    -<p>Note that if you call either of these methods on an element that was not originally configured as a target or source, nothing will happen.</p>
    -<p>You can check the enabled state of some target or source using these methods:
    -<ul>
    -<li class="bullet">isTargetEnabled</li>
    -<li class="bullet">isSourceEnabled</li>
    -</ul>
    -<h5><a id="targetSourceCancel">Canceling previous makeTarget and/or makeSource calls</a></h5>
    -jsPlumb offers four methods to let you cancel previous makeTarget or makeSource calls. Each of these methods returns the current jsPlumb instance, and so can be chained:
    -<ul>
    -<li class="bullet">unmakeTarget</li>
    -<li class="bullet">unmakeSource</li>
    -<li class="bullet">unmakeEveryTarget</li>
    -<li class="bullet">unmakeEverySource</li>
    -</ul>
    -<p>These last two are analogous to the <strong>removeEveryConnection</strong> and <strong>removeEveryEndpoint</strong> methods that have been in jsPlumb for a while now.</p>
    -<p>unmakeTarget and unmakeSource both take as argument the same sorts of values that makeTarget and makeSource accept - a string id, or a selector, or an array of either of these:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.unmakeTarget("aDivId").unmakeSource($(".aSelector"));
    -</pre>
    -</div>
    -
    -
    -<h5><a id="dragOptions">Drag Options</a></h5>
    -	These are options that will be passed through to the supporting library's drag API.  jsPlumb passes everything you supply here through, inserting
    -	wrapping functions if necessary for the various lifecycle events that jsPlumb needs to know about.  So if, for example, you pass a function to be
    -	called when dragging starts, jsPlumb will wrap that function with a function that does what jsPlumb needs to do, then call yours.
    -	<p>
    -	At the time of writing, jsPlumb supports jQuery, MooTools and YUI3, and each of those libraries uses different terminology.  In addition, jQuery's API is
    -	more fully featured, providing easy support for setting the zIndex and opacity of elements being dragged, as well as the 'scope' for a drag/drop (allowing
    -	you to specify more than one type of drag/drop pair), and hover classes for when a draggable is on the move or
    -	over a droppable. If you're using jQuery you can of course just supply these values on the dragOptions; to make it easier, jsPlumb's MooTools and YUI3 adapters recognize 
    -	these options and add appropriate callbacks for you.  
    -	</p> 
    -	<p>
    -	Given that the options here are library-specific, and they are all well-documented, we're going to discuss just the three drag options
    -	that behave the same way in all (see below for hoverClass):
    -		<ul>
    -		<li><strong>opacity</strong> - the opacity of an element that is being dragged.  Should be a fraction between 0 and 1 inclusive.</li>
    -		<li><strong>zIndex</strong> - the zIndex of an element that is being dragged.</li>
    -		<li><strong>scope</strong> - the scope of the draggable.  can only be dropped on a droppable with the same scope.  this is discussed below.</li>
    -		</ul> 
    -	</p>
    -	
    -	<p>For more information about drag options, take a look at the <a href="http://docs.jquery.com/UI/Draggable" target="_blank">jQuery</a>, <a href="http://mootools.net/docs/more/Drag/Drag.Move" target="_blank">MooTools</a>, or <a href="http://developer.yahoo.com/yui/3/dd/" target="_blank">YUI3</a> docs.	
    -	</p>
    -	<p><strong>NOTE: there is an issue in Chrome that affects the 'cursor' argument to drag options in jQuery. See these links:</strong>
    -	</p><p>
    -<a href="http://forum.jquery.com/topic/draggable-cursor-option-does-not-work" target="_blank">http://forum.jquery.com/topic/draggable-cursor-option-does-not-work</a><br/>
    -<a href="http://forum.jquery.com/topic/chrome-text-select-cursor-on-drag" target="_blank">http://forum.jquery.com/topic/chrome-text-select-cursor-on-drag</a>
    -</p>
    -You should put something like this at the top of your JS to avoid it (perhaps not something so drastic as a document-wide override):	
    -<div class="code">
    -<pre>document.onselectstart = function () { return false; };</pre>
    -</div>		
    -	
    -	<h5><a id="dropOptions">Drop Options</a></h5>
    -	Drop options are treated by jsPlumb in the same way as drag options - they are passed through to the underlying library.  MooTools does not have drop options like jQuery and YUI3 do; droppable functionality in MooTools 
    -	is actually implemented by the Drag.Move class - the one used to initialise a draggable.  But when you setup an Endpoint in jsPlumb you should ignore
    -	that fact, and treat droppables like you would in jQuery or YUI3.  jsPlumb wires everything up for you under the hood.
    -	<p>There are three jQuery droppable options that jsPlumb treats as shortcuts in MooTools and YUI3, for the sake of consistency:</p>
    -		<ul>
    -			<li><strong>hoverClass</strong> - the CSS class to attach to the droppable when a draggable is hovering over it.</li>
    -			<li><strong>activeClass</strong> - the CSS class to attach to the droppable when a draggable is, um, being dragged.</li>
    -			<li><strong>scope</strong> - the scope of the draggable.  The draggable can only be dropped on a droppable with the same scope.  This is discussed below.</li>
    -		</ul>   
    -	<p>For more information about drop options when using jQuery, see <a href="http://docs.jquery.com/UI/Droppable" target="_blank">here</a>.</p>
    -		
    -	<h5><a id="dragAndDropScope">Drag and Drop Scope</a></h5>
    -		jsPlumb borrowed the concept of 'scope' from jQuery's drag/drop implementation: the notion of
    -		which draggables can be dropped on which droppables.  In jsPlumb you can provide a 'scope' entry when 
    -		creating an Endpoint.  Here's the example grey Endpoint example with 'scope' added:
    -		
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:"#666" },
    -  isSource:true,
    -  connectionStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  scope:"exampleGreyConnection"
    -};</pre>		
    -</div>  		
    -  										
    -	If you do not provide a 'scope' entry, jsPlumb uses a default scope.  Its value is accessible through this method:
    -	<div class="code">
    -<pre>
    -jsPlumb.getDefaultScope();
    -</pre>	
    -	</div>				
    -	If you want to change it for some reason you can do so with this method:
    -	<div class="code">
    -<pre>
    -jsPlumb.setDefaultScope("mySpecialDefaultScope");
    -</pre>	
    -	</div>			
    -	You can also, should you want to, provide the scope value separately on the drag/drop options, like this:
    -	<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -  isSource:true,
    -  connectionStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  dragOptions:{ scope:"dragScope" },
    -  dropOptions:{ scope:"dropScope" }
    -};</pre>		
    -</div>  
    -			
    -	</div>	
    -
    -	<div class="section">
    -		<h3><a id="parameters">Connection Parameters</a></h3>
    -		jsPlumb has a mechanism that allows you to set parameters on a per-connection basis.  This can be achieved in a few different ways:
    -
    -		<ul>
    -			<li class="bullet">Providing parameters to a jsPlumb.connect call</li>
    -			<li class="bullet">Providing parameters to an addEndpoint call to/from which a connection is subsequently established using the mouse</li>
    -			<li class="bullet">using the <strong>setParameter</strong> or <strong>setParameters</strong> method on a Connection</li>
    -		</ul>
    -		Getting parameters is achieved through either the <strong>getParameter(key)</strong> or <strong>getParameters</strong> method on a Connection.
    -		<h4><a id="connectParameters">jsPlumb.connect</a></h4>
    -		Parameters can be passed in via an object literal to a jsPlumb.connect call:
    -		<div class="code">
    -<pre>var myConnection = jsPlumb.connect({
    -	source:"foo",
    -	target:"bar",
    -	parameters:{
    -		"p1":34,
    -		"p2":new Date(),
    -		"p3":function() { console.log("i am p3"); }
    -	}
    -});
    -</pre>
    -</div>
    -Note that they can be any valid Javascript objects - it's just an object literal.  You then access them like this:
    -		<div class="code">
    -<pre>myConnection.getParameter("p3")();     // prints 'i am p3' to the console.
    -</pre>
    -</div>
    -
    -<h4><a id="addEndpointParameters">jsPlumb.addEndpoint</a></h4>
    -<p>The information in this section also applies to the <strong>makeSource</strong> and <strong>makeTarget</strong> functions</p>
    -<p>Using jsPlumb.addEndpoint, you can set parameters that will be copied in to any Connections that are established from or to the given Endpoint using the mouse.  (If you set parameters on both a source and target Endpoints and then connect them, the parameters set on the target Endpoint are copied in first, followed by those on the source. So the source Endpoint's parameters take precedence if they happen to have one or more with the same keys as those in the target).</p>
    -Consider this example:
    -		<div class="code">
    -<pre>var e1 = jsPlumb.addEndpoint("d1", {
    -	isSource:true,
    -	parameters:{
    -		"p1":34,
    -		"p2":new Date(),
    -		"p3":function() { console.log("i am p3"); }
    -	}
    -});
    -
    -var e2 = jsPlumb.addEndpoint("d2", {
    -	isTarget:true,
    -	parameters:{
    -		"p5":343,
    -		"p3":function() { console.log("FOO FOO FOO"); }
    -	}
    -});
    -
    -var conn = jsPlumb.connect({source:e1, target:e2});
    -
    -
    -</pre>
    -</div>
    -
    -'conn' will have four parameters set on it, with the value for "p3" coming from the source Endpoint:
    -		<div class="code">
    -<pre>var params = conn.getParameters();
    -console.log(params.p1);   	// 34
    -console.log(params.p2);   	// Mon May 14 2012 12:57:12 GMT+1000 (EST) (or however your console prints out a Date)
    -console.log((params.p3)()); // "i am p3"  (note: we executed the function after retrieving it)
    -console.log(params.p5);   	// 343
    -
    -</pre>
    -</div>
    -
    -
    -</div> <!-- /section -->
    -
    -<div class="section">
    -	<h3><a id="connectionTypes">Connection and Endpoint Type</a></h3>
    -	<p>
    -	From version 1.3.13 onwards, jsPlumb supports the notion of "type" for both Connections and Endpoints. A type is a
    -	collection of attributes such as paint style, hover paint style, overlays etc - it is a subset, including most but not all, of the
    -	parameters you can set in an Endpoint or Connection definition. 
    -	</p>
    -	<p>
    -	An Endpoint or Connection can have zero or more types assigned; they are merged as granularly as possible, in the order
    -	in which they were assigned. There is a supporting API that works in the same way as the class stuff does in
    -	jQuery - hasType, addType, removeType, toggleType, setType, and each of these methods (except hasType) takes a space-separated
    -	string so you can add several at once.  Support for these methods has been added to the jsPlumb.select and
    -	jsPlumb.selectEndpoint methods, and you can also now specify a 'type' parameter to an Endpoint or Connection at create time.
    -	</p>
    -	<h4>Connection Type</h4>
    -	Probably the easiest way to explain types is with some code. In this snippet, we'll register a Connection type on jsPlumb,
    -	create a Connection, and then assign the type to it:
    -<div class="code">
    -<pre>
    -jsPlumb.registerConnectionType("example", {
    -	paintStyle:{ strokeStyle:"blue", lineWidth:5  },
    -	hoverPaintStyle:{ strokeStyle:"red", lineWidth:7 }
    -});
    -
    -var c = jsPlumb.connect({ source:"someDiv", target:"someOtherDiv" });
    -c.bind("click", function() {
    -	c.setType("example");
    -});	
    -</pre>		
    -</div>
    -<p>Another example - a better one, in fact.  Say you have a UI in which you can click to select or deselect Connections, and
    -you want a different appearance for each state.  Connection types to the rescue!</p>
    -<div class="code">
    -<pre>
    -jsPlumb.registerConnectionTypes({
    -	"basic": {
    -		paintStyle:{ strokeStyle:"blue", lineWidth:5  },
    -		hoverPaintStyle:{ strokeStyle:"red", lineWidth:7 }
    -	},
    -	"selected":{
    -		paintStyle:{ strokeStyle:"red", lineWidth:5 },
    -		hoverPaintStyle:{ lineWidth: 7 }
    -	}	
    -});
    -
    -var c = jsPlumb.connect({ source:"someDiv", target:"someOtherDiv", type:"basic" });
    -
    -c.bind("click", function() {
    -	c.toggleType("selected");
    -});	
    -</pre>		
    -</div>
    -<p>Notice here how we used a different method - <strong>registerConnectionTypes</strong> - to register a few types at once.
    -Notice also the hoverPaintStyle for the 'selected' type: it declares only a lineWidth.  As I mentioned above, types are
    -merged with as much granularity as possible, so that means that in this case the lineWidth from 'selected' will be merged
    -into the hoverPaintStyle from 'basic', and voila, red, 7 pixels.</p>
    -<p>These examples, of course, use the <strong>jsPlumb.connect</strong> method, but in many UIs Connections are created
    -via drag and drop.  How would you assign that 'basic' type to a Connection created with drag and drop? Provide it as
    -the Endpoint's connectorType parameter, like so:</p>
    -<div class="code">
    -<pre>
    -	var e1 = jsPlumb.addEndpoint("someDiv", {
    -		connectorType:"basic",
    -		isSource:true
    -	});
    -	
    -	var e2 = jsPlumb.addEndpoint("someOtherDiv", {
    -		isTarget:true
    -	});
    -		
    -	//... user then perhaps drags a connection...or we do it programmatically:
    -	
    -	var c = jsPlumb.connect({ source:e1, target:e2 });
    -	
    -	// now c has type 'basic'
    -	console.log(c.hasType("basic));   // -> true
    -</pre>	
    -</div>
    -<p>Note that the second Endpoint we created did not have a 'connectorType' parameter - we didn't need it, as the source
    -Endpoint in the Connection had one.  But we could have supplied one, and jsPlumb will use it, but only if the source
    -Endpoint has not declared connectorType.  This is the same way jsPlumb treats other connector parameters such as
    -paintStyle etc - the source Endpoint wins.</p>
    -<p>Here's a few examples showing you the full type API:</p>
    -<div class="code">
    -<pre>
    -	jsPlumb.registerConnectionTypes({
    -		"foo":{ paintStyle:{ strokeStyle:"yellow", lineWidth:5 } },
    -		"bar":{ paintStyle:{ strokeStyle:"blue", lineWidth:10 } },
    -		"baz":{ paintStyle:{ strokeStyle:"green", lineWidth:1 } }
    -	});
    -	
    -	var c = jsPlumb.connect({ source:"someDiv", target:"someOtherDiv", type:"foo" });
    -	
    -	// see what types the connection has.  
    -	console.log(c.hasType("foo"));  // -> true
    -	console.log(c.hasType("bar"));  // -> false
    -	
    -	// add type 'bar'
    -	c.addType("bar");
    -	
    -	// toggle both types (they will be removed in this case)
    -	c.toggleType("foo bar");
    -	
    -	// toggle them back
    -	c.toggleType("foo bar");
    -	
    -	// getType returns a list of current types.
    -	console.log(c.getType()); // -> [ "foo", "bar" ]
    -	
    -	// set type to be 'baz' only
    -	c.setType("baz");
    -	
    -	// add foo and bar back in
    -	c.addType("foo bar");
    -	
    -	// remove baz and bar
    -	c.removeType("baz bar");
    -	
    -	// what are we left with? good old foo.
    -	console.log(c.getType()); // -> [ "foo" ]
    -	
    -</pre>	
    -</div>
    -<p>Things to note here are that every method <strong>except hasType</strong> can take a space-delimited list of types
    -to work with. So types work like CSS classes, basically.</p>
    -<h5>Supported Parameters in Connection Type objects</h5>
    -Not every parameter from a Connection's constructor is supported in a Connection Type - as mentioned above, types act pretty
    -much like CSS classes, so the things are supported are related to behaviour or appearance. For instance, 'source' is
    -not supported.  You cannot make a Connection Type that is fixed to a specific source element. Here's the full list of
    -supported properties in Connection Type objects:
    -<ul>
    -	<li class="bullet"><strong>detachable</strong> - whether or not the Connection is detachable using the mouse</li>
    -	<li class="bullet"><strong>paintStyle</strong></li>
    -	<li class="bullet"><strong>hoverPaintStyle</strong></li>
    -	<li class="bullet"><strong>scope</strong> - remember, Connections support a single scope. So if you have multiple types applied, you will get the scope from the last type that defines one.</li>
    -	<li class="bullet"><strong>parameters</strong> - when you add/set a type that has parameters, any existing parameters with the same keys will be overwritten. When you remove a type that has parameters, its parameters are NOT removed from the Connection.</li>
    -	<li class="bullet"><strong>overlays</strong> - when you have multiple types applied to a Connection, you get the union of all the Overlays defined across the various types.</li>	
    -</ul>
    -
    -<h4><a id="endpointTypes">Endpoint Type</a></h4>
    -Endpoints can also be assigned one or more types, both at creation and programmatically using the API discussed above.
    -The only real differences between Endpoint and Connection types are the allowed parameters.  Here's the list for Endpoints:
    -<ul>
    -	<li class="bullet"><strong>paintStyle</strong></li>
    -	<li class="bullet"><strong>hoverPaintStyle</strong></li>
    -	<li class="bullet"><strong>maxConnections</strong></li>
    -	<li class="bullet"><strong>connectorStyle</strong> - paint style for any Connections that use this Endpoint.</li>
    -	<li class="bullet"><strong>connectorHoverStyle</strong> - hover paint style for Connections from this Endpoint.</li>
    -	<li class="bullet"><strong>connector</strong> - a Connector definition, like "StateMachine", or [ "Flowchart", { stub:50 } ] </li>
    -	<li class="bullet"><strong>connectionType</strong> - This allows you to specify the Connection Type for Connections made from this Endpoint.</li>
    -	<li class="bullet"><strong>scope</strong> - remember, Endpoints support a single scope. So if you have multiple types applied, you will get the scope from the last type that defines one.</li>
    -	<li class="bullet"><strong>parameters</strong> - when you add/set a type that has parameters, any existing parameters with the same keys will be overwritten. When you remove a type that has parameters, its parameters are NOT removed from the Connection.</li>
    -	<li class="bullet"><strong>overlays</strong> - when you have multiple types applied to an Endpoint, you get the union of all the Overlays defined across the various types.</li>
    -	
    -</ul>
    -<p>One thing to be aware of is that the parameters here that are passed to Connections are only passed from a source Endpoint,
    -not targets.</p>
    -Here's an example of using Endpoint types:
    -<div class="code">
    -<pre>
    -  jsPlumb.registerEndpointTypes({
    -    "basic":{			
    -      paintStyle:{fillStyle:"blue"}
    -    },
    -    "selected":{			
    -      paintStyle:{fillStyle:"red"}
    -    }
    -  });
    -
    -  var e = jsPlumb.addEndpoint("someElement", {
    -    anchor:"TopMiddle",
    -	type:"basic"
    -  });
    -  
    -  e.bind("click", function() {
    -    e.toggleType("selected");
    -  });
    -</pre>
    -</div>
    -So it works the same way as Connection Types.  There are several parameters allowed by an Endpoint Type that affect Connections
    -coming from that Endpoint. Note that this does not affect existing Connections.  It affects only Connections that are
    -created after you set the new type(s) on an Endpoint.
    -
    -<h4>Fluid Interface</h4>
    -As mentioned previously, all of the type operations are supported by the <strong>select</strong> and <strong>selectEndpoints</strong> methods.
    -So you can now do things like this:
    -<div class="code">
    -<pre>
    -	jsPlumb.selectEndpoints({scope:"terminal"}).toggleType("active");
    -	
    -	jsPlumb.select({source:"someElement"}).addType("highlighted available");
    -	
    -	etc
    -	
    -</pre>
    -</div>
    -Obviously, in these examples, 'available' and 'highlighted' would have previously been registered on jsPlumb using the appropriate
    -register methods.
    -
    -</div> <!-- / connection/endpoint types	-->
    -	
    -	<div class="section">
    -		<h3><a id="beforeDrop">Interceptors</a></h3>
    -		<p>
    -		Interceptors were new in jsPlumb 1.3.4.  They are basically event handlers from which you can return a value that tells jsPlumb to abort what it is that
    -		it was doing.  There are currently two interceptors supported - <strong>beforeDrop</strong>, which is called when the user has dropped a Connection onto some target, 
    -		and <strong>beforeDetach</strong>, which is called when the user is attempting to detach a Connection. 
    -		</p>
    -		<p>Interceptors can be registered via the <strong>bind</strong> method on jsPlumb just like any other event listeners, and they can also be passed in to
    -		the <strong>jsPlumb.addEndpoint</strong>, <strong>jsPlumb.makeSource</strong> and <strong>jsPlumb.makeTarget</strong>
    -		methods.  A future version of jsPlumb will support registering interceptors on Endpoints using the <strong>bind</strong> method as you can now with the jsPlumb object.
    -		Note that binding 'beforeDrop' (as an example) on jsPlumb itself is like a catch-all: it will be called every time a Connection is dropped on _any_ 
    -		Endpoint.  But passing a 'beforeDrop' callback into an Endpoint (and in the future, registering one using 'bind') constrains that callback to just the
    -		Endpoint in question.  		
    -		<h4><a id="beforeDrop">beforeDrop</a></h4>
    -		This event is fired when a new or existing connection has been dropped. Your callback is passed a JS object with these fields:
    -		<ul>
    -			<li><strong>sourceId</strong> - the id of the source element in the connection</li>
    -			<li><strong>targetId</strong> - the id of the target element in the connection</li>
    -			<li><strong>scope</strong> - the scope of the connection</li>
    -			<li><strong>connection</strong> - the actual Connection object.  You can access the 'endpoints' array in a Connection to get the Endpoints involved in the Connection, but be aware that when a Connection is being dragged, one of these Endpoints will always be a transient Endpoint that exists only for the life of the drag. To get the Endpoint on which the Connection is being dropped, use the 'dropEndpoint' member.</li>
    -			<li><strong>dropEndpoint</strong> - this is the actual Endpoint on which the Connection is being dropped.  This <strong>may be null</strong>, because it will not be set if the Connection is being dropped on an element on which makeTarget has been called. </li>
    -		</ul>
    -
    -		If you return false (or nothing) from this callback, the new Connection is aborted and removed from the UI.
    -		<h4><a id="beforeDetach">beforeDetach</a></h4>
    -		This is called when the user has detached a Connection, which can happen for a number of reasons: by default, jsPlumb allows users to drag Connections off of target Endpoints, but this can also result from a programmatic 'detach' call.  Every case is treated the same by jsPlumb, so in fact it is possible for you to write code that attempts to detach a Connection but then denies itself!  You might want to be careful with that. 
    -		<p>Note that this interceptor is passed the actual Connection object; this is different from the beforeDrop
    -		interceptor discussed above: in this case, we've already got a Connection, but with beforeDrop we are yet 
    -		to confirm that a Connection should be created.</p>
    -		<p>Returning false - or nothing - from this callback will cause the detach to be abandoned, and the Connection will be reinstated or left on its current target.</p>
    -			
    -	</div>
    -
    -		<div class="section">
    -			<h3><a id="paintstyles">Paint Styles</a></h3>
    -			Defining the appearance of Connectors, Endpoints (and Overlays, but this is deprecated in favour of using CSS classes) is achieved through a 'paintStyle' (or a quite similar name)
    -			object passed as a parameter to one of jsPlumb.connect, jsPlumb.addEndpoint, jsPlumb.makeSource or jsPlumb.makeTarget. Depending on the method you are calling, the parameter names vary.
    -			<h5>Connector Paint Styles</h5>
    -			These are specified in a <em>paintStyle</em> parameter on a call to <strong>jsPlumb.connect</strong>:
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			or in the <em>connectorPaintStyle</em> parameter on a call to <strong>jsPlumb.addEndpoint</strong> or <strong>jsPlumb.makeSource</strong>:
    -			<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div> 			
    -			 
    -
    -Notice the <em>paintStyle</em> parameter in those examples: it is the paint style for the Endpoint, which we'll discuss below.
    -
    -			<h5>Endpoint Paint Styles</h5>
    -			These are specified in a <em>paintStyle</em> parameter on a call to <strong>addEndpoint</strong>. This is the example from just above:
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -</div> 
    -			...or as the <em>endpointStyle</em> parameter to a <strong>connect</strong> call:		
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			... or as an entry in the <em>endpointStyles</em> array passed to a <strong>jsPlumb.connect</strong> call:
    -					<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  endpointStyles:[ 
    -    { fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -    { fillStyle:"green" }
    -  ],
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			or as the <em>paintStyle</em> parameter passed to a <strong>makeTarget</strong> or <strong>makeSource</strong> call:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("el1", {
    -  ...
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  ...
    -});
    -</pre>			
    -</div>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 }
    -  parent:"someOtherDivIJustPutThisHereToRemindYouYouCanDoThis"
    -});
    -</pre>			
    -			</div>						
    -	In the first example we  made "el1" into a drop target, and defined a paint style for the Endpoint jsPlumb will create when a Connection is
    -	established.  In the second we made "el1" a connection source and assigned the same values for the Endpoint jsPlumb will create when a Connection
    -	is dragged from that element.
    -	
    -	<h5>Overlay Paint Styles</h5>
    -	The preferred way to set paint styles for Overlays is to use the <em>cssClass</em> parameter in the constructor arguments of an Overlay definition.
    -	
    -	<h4>Paint Style Parameters</h4>
    -	This is the full list of parameters you can set in a paintStyle object, but note that <strong>fillStyle</strong> is ignored by Connectors,
    -	and <strong>strokeStyle</strong> is ignored by Endpoints.  Also, if you create a Connection using jsPlumb.connect and do not specify any
    -	Endpoint styles, the Endpoints will derive their fillStyle from the Connector's strokeStyle.
    -	<p>
    -		fillStyle, strokeStyle and outlineColor can be specified using any valid CSS3 syntax.
    -	</p>
    -	<ul>
    -		<li><strong>fillStyle</strong> - color for an Endpoint, eg. rgba(100,100,100,50), "blue", "#456", "#993355", rgb(34, 56, 78).</li>
    -		<li><strong>strokeStyle</strong> - color for a Connector. see fillStyle examples.</li>
    -		<li><strong>lineWidth</strong> - width of a Connector's line. An integer.</li>
    -		<li><strong>outlineWidth</strong> - width of the outline for an Endpoint or Connector. An integer.</li>
    -		<li><strong>outlineColor</strong> - color of the outline for an Endpoint or Connector. see fillStyle examples.</li>
    -		<li><strong>dashstyle</strong> - VML and SVG only. This comes from VML, and allows you to create dashed or dotted lines.  It has a 
    -			better syntax than the equivalent attribute in SVG (<em>stroke-dasharray</em>, discussed below), so jsPlumb supports this for both renderers. 
    -			The <em>dashstyle</em> attribute is specified as an array of strokes and spaces, where each value is some multiple of <em>the width of 
    -			the Connector</em>, and that's where it's better than SVG, which uses pixels.
    -			<p>
    -			<a href="http://www.w3.org/TR/NOTE-VML" target="_blank">The VML spec</a> is a good place to find valid values for dashstyle. Note that
    -			jsPlumb does not support the string values for this attribute ("solid", "dashdot", etc).
    -			</p>
    -			<p>
    -			In SVG render mode, jsPlumb uses the <em>lineWidth</em> parameter in conjunction with the values in a <em>dashstyle</em> attribute
    -			to create an appropriate value for <em>stroke-dasharray</em>.
    -			</p>
    -		</li>
    -		<li><strong>stroke-dasharray</strong> - SVG only. This is the SVG equivalent of <em>dashstyle</em>.  
    -		<a href="http://www.w3.org/TR/SVG/painting.html" target="_blank">The SVG spec</a> discusses valid values for this parameter.  But be aware
    -		that jsPlumb does not convert this into a valid <em>dashstyle</em> attribute when using the VML renderer. Better to use <em>dashstyle</em>.
    -		</li>
    -		<li><strong>stroke-dashoffset</strong> - SVG only.  This is used in SVG to specify how far into the dash pattern to start painting.  For more information,
    -		see <a href="http://www.w3.org/TR/SVG/painting.html" target="_blank">the SVG spec.</a></li>
    -		<li><strong>joinstyle</strong> - VML and SVG only.  As with <em>dashstyle</em>, this is a VML attribute that jsPlumb supports for both VML
    -		and SVG - jsPlumb turns this into a <em>stroke-linejoin</em> attribute when rendering with SVG.  This attribute specifies how you want individual
    -		segments of connectors to be joined; the VML and SVG specs both have examples of this, of which many are the same between the two, which is
    -		why jsPlumb will automatically convert this attribute into the SVG equivalent.
    -		</li>
    -		<li><strong>stroke-linejoin</strong> - SVG only.  This is the equivalent of VML's <em>joinstyle</em> attribute, but as with <em>stroke-dasharray</em>,
    -		jsPlumb does not convert this into something approriate for VML.  So, using <em>joinstyle</em> will enable you to support more browsers with
    -		less effort.		
    -		</li>
    -	</ul>
    -	
    -	<h3><a id="hoverpaintstyles">Hover Paint Styles</a></h3>
    -	 		
    -	 		Connectors and Endpoints both support the concept of a "hover" paint style - a paint style to use when the mouse is hovering over
    -	 		the component.  These are specified in the exact same format as paint styles discussed above, but hover paint styles also inherit
    -	 		any values from the main paint style.  This is because you will typically want to just change the color, or perhaps outline color, of
    -	 		a Connector or Endpoint when the mouse is hovering, but leave everything else the same.  So having hover paint styles inherit their
    -	 		values precludes you from having to define things in more than one place.
    -	 		<p>
    -	 			The naming convention adopted for hover paint styles is pretty much to insert the word 'hover' into the corresponding main paint
    -	 			style parameters.  Here are a couple of examples:
    -	 		</p> 
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -  hoverPaintStyle:{ strokeStyle:"red" },
    -  endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  endpointHoverStyle:{ fillStyle:"red" }
    -});
    -</pre>			
    -</div>
    -			In this example we specified a hover style for both the Connector, and each of its Endpoints.  Here's the same thing, but using the plural version, to specify a different hover style for each Endpoint:	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -  hoverPaintStyle:{ strokeStyle:"red" },
    -  endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  endpointHoverStyles:[ { fillStyle:"red" }, { fillStyle:"yellow" } ]
    -});
    -</pre>			
    -</div>
    -	 		Calls to <strong>addEndpoint</strong>, <strong>makeSource</strong> and <strong>makeTarget</strong> can also specify various hover paint styles:
    -	 								<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  hoverPaintStyle:{ fillStyle:"red" },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -  connectorHoverPaintStyle:{ strokeStyle:"red", outlineColor:"yellow", outlineWidth:1 }
    -});
    -
    -jsPlumb.makeSource("el2", {
    -  paintStyle:{ fillStyle:"transparent", outlineColor:"yellow", outlineWidth:1 },
    -  hoverPaintStyle:{ fillStyle:"red" },
    -  connectorPaintStyle:{ strokeStyle:"green", lineWidth:3 },
    -  connectorHoverPaintStyle:{ strokeStyle:"#678", outlineColor:"yellow", outlineWidth:1 }
    -});
    -
    -jsPlumb.makeTarget("el3", {
    -  paintStyle:{ fillStyle:"transparent", outlineColor:"yellow", outlineWidth:1 },
    -  hoverPaintStyle:{ fillStyle:"red" }
    -});
    -</pre>			
    -			</div> 
    -	 		
    -	 		In these examples we specified a hover paint style for both the Endpoint we are adding, and any Connections to/from the Endpoint.
    -	 		<p>	
    -		Note that <strong>makeTarget</strong> does not support Connector parameters. It is for creating targets only; Connector parameters will be set by the source Endpoint in any Connections that are made to the element that you turned into a target by using this method.</p>
    -	 			 		
    -			<h4><a id="gradients">Gradients</a></h4>
    -			The Canvas and SVG renderers both support gradients. The VML renderer does not.  jsPlumb uses its own syntax to define gradients, to abstract out the differences between the syntax required by canvas and that required by SVG.  
    -			<p>There are two types of gradients available - a 'linear' gradient, which consists of colored lines all
    -			going in one direction, and a 'radial' gradient, which consists of colored circles emanating from one circle to another.
    -			Because of their basic shape, jsPlumb supports only <em>linear</em> gradients for
    -			Connectors.  But for Endpoints, jsPlumb supports both <em>linear</em> and <em>radial</em> gradients.</p>
    -			
    -				<h5>Connector gradients</h5>
    -				To specify a linear gradient to use in a Connector, you must add a <em>gradient</em> object to your
    -				Connector's <em>paintStyle</em>, for instance:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : "window2",
    -	target : "window3",
    -	paintStyle:{
    -		gradient:{
    -			stops:[[0,"green"], [1,"red"]]
    -		},
    -		lineWidth:15
    -	}
    -});
    -</pre>
    -</div>
    -Here we have connected window2 to window3 with a 15 pixel wide connector that has a gradient from green to red.</p><p> Notice the <strong>gradient</strong> object and the
    -<strong>stops</strong> list inside it - the gradient consists of an arbitrary number of these "color stops".  Each color stop is comprised
    -of two values - [position, color].  Position must be a decimal value between 0 and 1 (inclusive), and indicates where the color
    -stop is situated as a fraction of the length of the entire gradient.  Valid values for
    -the colors in the <strong>stops</strong> list are the same as those that are valid for <strong>strokeStyle</strong> when describing a color.
    -			</p>
    -			As mentioned, the <strong>stops</strong> list can hold an arbitrary number of entries.  Here's an example of a gradient that goes from red to blue to green, and back again through blue to red:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle : {
    -		gradient:{
    -			stops:[[0,'red'], [0.33,'blue'], [0.66,'green'], [0.33,'blue'], [1,'red']]
    -		},
    -		lineWidth : 15
    -	}
    -});
    -</pre>
    -</div>
    -<strong>Note:</strong> when using the VML renderer, jsPlumb will simply ignore the gradient directive
    -so it is best to ensure you also supply a <em>strokeStyle</em> in your paintStyle object, to give jsPlumb something to fall back on.  If you do not supply
    -a <em>strokeStyle</em> your Connectors will be painted black.  The previous example might look like this, for instance:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle:{
    -		strokeStyle:'red',
    -		gradient:{
    -			stops:[[0,'red'], [0.33,'blue'], [0.66,'green'], [0.33,'blue'], [1,'red']]
    -		},
    -		lineWidth:15
    -	}
    -});
    -</pre>
    -</div>
    -Notice the <strong>strokeStyle:'red'</strong> directive at the beginning of the parameter list in <strong>paintStyle</strong>.
    -			<h5>Endpoint gradients</h5>
    -			Endpoint gradients are specified using the same syntax as Connector gradients.  You put the gradient specifier either in the
    -			<strong>endpoint</strong> member, or if you are specifying different Endpoints for each end of the Connector, in one or both of
    -			the values in the <strong>endpoints</strong> array.  Also, this information applies to the case that you are creating standalone
    -			Endpoints that you will be configuring for drag and drop creation of new Connections. 
    -			<p>
    -			This is an example of an Endpoint gradient that is different for each Endpoint in the Connector.  This comes from the main demo; it is
    -			the Connector joining Window 2 to Window 3:
    -			</p>
    -<div class="code">
    -<pre>var w23Stroke = 'rgb(189,11,11)';
    -jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle:{
    -		lineWidth:8,
    -		strokeStyle:w23Stroke
    -	},
    - 	anchors:[ [0.3,1,0,1], "TopCenter" ],
    - 	endpoint:"Rectangle",
    - 	endpointStyles:[
    - 		{ gradient : {stops:[[0, w23Stroke], [1, '#558822']] } },
    -    	{ gradient : {stops:[[0, w23Stroke], [1, '#882255']] } }
    -    ]
    -});
    -</pre>
    -</div>
    -The first entry in the gradient will be the one that is on the Connector end of the Endpoint.  You can of course have as many color stops as
    -you want in this gradient, just like with Connector gradients.
    -	<h5>Applying the gradient in Endpoints</h5>
    -	Only the Dot and Rectangle endpoints honour the presence of a gradient (and, remember, not in VML). The Image endpoint of course ignores a gradient
    -	as it does no painting of its own.
    -	<p>The type of gradient you will see depends on the Endpoint type:</p>
    -		<ul>
    -			<li><strong>Dot</strong> - renders a radial endpoint, with color stop 0 on the outside, progressing inwards as we move through color stops.
    -			<p>Radial gradients actually require more data than linear gradients - in a linear gradient we just move from one point to another, whereas
    -			in a radial gradient we move from one <em>circle</em> to another.  By default, jsPlumb will render a radial gradient using a source
    -			circle of the same radius as the Endpoint itself, and a target circle of 1/3 of the radius of the Endpoint (both circles share the
    -			same center as the Endpoint itself). This circle will be offset by radius/2 in each direction.</p>
    -			<p>You can supply your own values for these inside the gradient descriptor:</p>
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect({
    -	source : 'window3',
    -	target : 'window4',
    -    paintStyle:{
    -    	lineWidth:10,
    -    	strokeStyle:w34Stroke
    -    },
    -    anchors:[ "RightMiddle", "LeftMiddle" ],
    -    endpointStyle:{
    -    	gradient : {
    -    		stops:[ [0, w34Stroke], [1, w34HlStroke] ],
    -    		offset:37.5,
    -    		innerRadius:40
    -    	},
    -    	radius:55
    -    }
    - });
    -</pre>
    -</div>
    -Here we have instructed jsPlumb to make the gradient's inner radius 10px instead of the default 25/3 = 8 ish pixels, and the offset in each direction
    -will be 5px, instead of the default radius / 2 = 12.5 pixels.
    -<p>It is also possible to specify the offset and inner radius as percentages - enter the values as strings with a '%' symbol on the end:</p>
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect({
    -	source : 'window3', 
    -	target : 'window4',
    -    paintStyle:{
    -    	lineWidth:10,
    -    	strokeStyle:w34Stroke
    -    },
    -	anchors:[ "RightMiddle", "LeftMiddle" ],
    -	endpointStyle:{
    -		gradient : {
    -			stops:[ [0, w34Stroke], [1, w34HlStroke] ],
    -			offset:'68%',
    -			innerRadius:'73%'
    -		},
    -		radius:25
    -	}
    -});
    -</pre>
    -</div>
    -This will give roughly the same output as the example above (the percentages are not entirely exact).<br/><br/>
    -			</li>
    -			<li><strong>Rectangle</strong> - renders a linear endpoint, with color stop 0 closest to the end of the Connector</li>
    -		</ul>
    -		
    -<h4><a id="svgvmlcss">Styling SVG/VML using CSS</a></h4>
    -	A nice way of controlling your UI's appearance is to make use of the fact that both SVG and VML can be styled using CSS.
    -	<p>This section will be expanded in the next few weeks to give some decent examples of how to do so</p>
    -			
    -		
    -		
    -		</div>
    -
    -		<div class="section">
    -			<h3><a id="cssclasses">CSS</a></h3>
    -			In an ideal world, you'd be able to control the appearance of your UI using CSS only, but due to difference between
    -			renderers, this is not the case.  The Canvas renderer, for instance, is completely unaware of CSS, and VML can
    -			only be styled up to a point with CSS.  SVG behaves best, and perhaps at some stage in the future when all browsers
    -			in popular usage support SVG decently, we'll be able to switch over completely to CSS.
    -			<p>For now, though, you can - and should - still use CSS for one important consideration: z-indices.  Every Connection,
    -			Endpoint and Overlay in jsPlumb adds some element to the UI, and you should take care to establish appropriate z-indices for each
    -			of these, in conjunction with the nodes in your application. 
    -			</p>
    -			<p>By default, jsPlumb adds a specific class to each of the three types of elements it creates (These class names are
    -			exposed on the jsPlumb object and can be overridden if you need to do so - see the third column in the table) </p>
    -			<table width="90%" class="table" style="align:left;font-size:12px;">
    -				<tr><th>Component</th><th>CSS Class</th><th>jsPlumb Member</th></tr>
    -				<tr><td>Connector</td><td>_jsPlumb_connector</td><td>connectorClass</td></tr>
    -				<tr><td>Endpoint</td><td>_jsPlumb_endpoint</td><td>endpointClass</td></tr>
    -				<tr><td>Overlay</td><td>_jsPlumb_overlay</td><td>overlayClass</td></tr>
    -			</table>
    -			<p>In addition to these defaults, each of the main methods you use to configure Endpoints or make Connections
    -			in jsPlumb support the following two parameters:</p>
    -			<ul>
    -				<li class="bullet"><strong>cssClass</strong> - class(es) to set on the display elements</li>
    -				<li class="bullet"><strong>hoverClass</strong> - class(es) to set on the display elements when in hover mode</li>
    -			</ul>
    -			In addition, addEndpoint and makeSource allow you to specify what these classes will be for any Connections that are dragged from them:
    -			<ul>
    -				<li class="bullet"><strong>connectorClass</strong> - class(es) to set on the display elements of Connections</li>
    -				<li class="bullet"><strong>connectorHoverClass</strong> - class(es) to set on the display elements of Connections when in hover mode</li>
    -			</ul>
    -			<p>These parameters should be supplied as a String; they will be appended as-is to the class member, so feel free to
    -			include multiple classes.  jsPlumb won't even know.</p>
    -		</div>
    -				
    -		<div class="section">
    -			<h3><a id="animation">Animation</a></h3>
    -			jsPlumb offers an 'animate' function, which wraps the underlying animation engine for whichever library you happen to be using and
    -			inserts a callback for jsPlumb to repaint whatever it needs to at each step.  You could of course do this yourself; it's a convenience
    -			method really.
    -			<p>The method signature is:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.animate : function(el, properties, options) 
    -</pre>
    -</div>			
    -The arguments are as follows:
    -	<ul>
    -		<li><strong>el</strong> - element id, or element object from the library you're using.</li>
    -		<li><strong>properties</strong> - properties for the animation, such as duration etc.</li>
    -		<li><strong>options</strong> - options for the animation, such as callbacks etc.</li>
    -	</ul>
    -	
    -		<h4>jQuery and the 'revert' option</h4>
    -		jQuery offers a 'revert' option that you can use to instruct it to revert a drag under some condition. This is
    -		rendered in the UI as an animation that returns the drag object from its current location to wherever it started
    -		out.  It's a nice feature.  Unfortunately, the animation that runs the revert does not offer any lifecycle
    -		callback methods - no 'step', no 'complete' - so its not possible for jsPlumb to know that the revert 
    -		animation is taking place.
    -	
    -		</div>
    -		
    -			<div class="section">
    -			<h3><a id="connectionInfo">Retrieving Connection Information</a></h3>
    -			There are two ways of retrieving connection information from jsPlumb. <strong>getConnections</strong>is the original method; this method has since been supplanted by <a href="#selectConnections">jsPlumb.select</a>, a much more versatile variant.
    -			<h5>getConnections</h5>
    -			<p>Before you use this method you should understand jsPlumb's notion of 'scope' - documentation
    -			is <a href="#dragAndDropScope">here</a> </p>
    -			
    -			<h4>Retrieving connections for a single scope</h4>
    -			<p>To do this, you call getConnections with either no arguments, in which case jsPlumb uses the default scope, or with a string
    -			specifying one scope</p>
    -			<div class="code">
    -<pre>var connectionList = jsPlumb.getConnections();     // you get a list of Connection objects that are in the default scope.
    -</pre>			
    -			</div>
    -			Compare this with:
    -			<div class="code">
    -<pre>var connectionList = jsPlumb.getConnections("myScope");     // you get a list of Connection objects that are in "myScope".
    -</pre>			
    -			</div>
    -			<h4>More advanced filtering</h4>
    -			getConnections optionally takes a JS object specifying filter parameters, of which there are three:
    -			<ul>
    -				<li>scope - the scope(s) of the connection type(s) you wish to retrieve</li>
    -				<li>source - limits the returned connections to those that have this source id</li>
    -				<li>target - limits the returned connections to those that have this target id</li>
    -			</ul>			
    -			Each of these three parameters may be supplied as a string, which for source and target is an element id and for scope is the name of the scope, or a list of strings.  Als, you can pass "*" in as the value for any of these - a wildcard, meaning any value.  See the examples below.
    -			<p>
    -			The return value of a call to getConnection using a JS object as parameter varies on how many scopes you defined.  If you defined only a single scope then jsPlumb returns you a list of Connections in that scope.  Otherwise the return value is a dictionary whose keys are  scope names, and whose values are lists of Connections. For example, the following call:
    -			</p>
    -			<div class="code">
    -<pre>
    -	jsPlumb.getConnections({scope:["someScope", "someCustomScope"]});
    -</pre>
    -</div>
    -would result in this output:			
    -<div class="code">
    -<pre>
    -{
    -	"someScope" : [ 1..n Connections ],
    -	"someCustomScope": [ 1..m Connections ]
    -}
    -</pre>
    -</div>	
    -
    -There is an optional second parameter that tells getConnections to flatten its output and just return you an array.  The previous example with this parameter would look like this:
    -			<div class="code">
    -<pre>
    -	jsPlumb.getConnections({scope:["someScope", "someCustomScope"]}, true);
    -</pre>
    -</div>
    -would result in this output:			
    -<div class="code">
    -<pre>
    -[ 1..n Connections ]
    -</pre>
    -</div>	
    -		
    -		The following examples show the various ways you can get connection information:
    -	<ul>
    -		<li>Get all connections:
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getAllConnections();  
    -</pre>
    -</div>						
    -		</li>
    -<li>Get all connections for the default scope only (return value is a list):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections();  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given scope (return value is a list):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:"myTestScope"});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given scopes (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:["myTestScope", "yourTestScope"]});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given source (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:"mySourceElement"});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given sources (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:["mySourceElement", "yourSourceElement"]});  
    -</pre>
    -</div>						
    -		</li>		
    -<li>Get all connections for the given target (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({target:"myTargetElement"});  
    -</pre>
    -</div>						
    -		</li>				
    -<li>Get all connections for the given source and targets (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:"mySourceElement", target:["target1", "target2"]});  
    -</pre>
    -</div>						
    -		</li>				
    -		
    -<li>Get all connections for the given scope, with the given source and target (return value is a list of connections):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:'myScope", source:"mySourceElement", target:"myTargetElement"});  
    -</pre>
    -</div>						
    -		</li>				
    -		
    -	</ul>	
    -
    -		</div>
    -
    -		<div class="section">
    -			<h3><a id="selectConnections">Selecting Connections</a></h3>
    -			<strong>jsPlumb.select</strong> provides a fluid interface for working with lists of Connections.  The syntax used to specify which Connections you want is identical to that which you use for getConnections, but the return value is an object that supports most operations that you can perform on a Connection, and which is also chainable, for setter methods. Certain getter methods are also supported, but these are not chainable; they return an array consisting of all the Connections in the selection along with the return value for that Connection.
    -			<p>
    -			This is the full list of setter operations supported by jsPlumb.select:
    -			<ul>
    -				<li class="bullet">addOverlay</li>
    -				<li class="bullet">removeOverlay</li>
    -				<li class="bullet">removeOverlays</li>
    -				<li class="bullet">showOverlay</li>
    -				<li class="bullet">hideOverlay</li>
    -				<li class="bullet">showOverlays</li>
    -				<li class="bullet">hideOverlays</li>
    -				<li class="bullet">removeAllOverlays</li>
    -				<li class="bullet">setLabel</li>
    -				<li class="bullet">setPaintStyle</li>
    -				<li class="bullet">setHoverPaintStyle</li>
    -				<li class="bullet">setDetachable</li>
    -				<li class="bullet">setConnector</li>
    -				<li class="bullet">setParameter</li>
    -				<li class="bullet">setParameters</li>
    -				<li class="bullet">detach</li>
    -				<li class="bullet">repaint</li>
    -				<li class="bullet">setType</li>
    -				<li class="bullet">addType</li>
    -				<li class="bullet">removeType</li>
    -				<li class="bullet">toggleType</li>		
    -			</ul>
    -
    -			Each of these operations returns a selector that can be chained.
    -			</p>
    -			These is the full list of getter operations supported by jsPlumb.select:
    -			<p>
    -				<ul>
    -				<li class="bullet">getLabel</li>
    -				<li class="bullet">getOverlay</li>
    -				<li class="bullet">isHover</li>
    -				<li class="bullet">isDetachable</li>
    -				<li class="bullet">getParameter</li>
    -				<li class="bullet">getParameters</li>
    -				<li class="bullet">getType</li>
    -				<li class="bullet">hasType</li>
    -			</ul>
    -
    -			Each of these operations returns an array whose entries are [ value, Connection ] arrays, where 'value' is the return value from the given Connection.  Remember that the return values from a getter are not chainable, but a getter may be called at the end of a chain of setters.
    -			</p>
    -
    -			<p>Some examples:</p>
    -			Select all Connections and set their hover state to be false:
    -
    -<div class="code">
    -<pre>
    -jsPlumb.select().setHover(false);
    -</pre>
    -</div>
    -			Select all Connections from "d1" and remove all Overlays:
    -
    -<div class="code">
    -<pre>
    -jsPlumb.select({source:"d1"}).removeAllOverlays();
    -</pre>
    -</div>
    -
    -Select all connections in scope "foo" and set their paint style to be a thick blue line:
    -<div class="code">
    -<pre>
    -jsPlumb.select({scope:"foo"}).setPaintStyle({ strokeStyle:"blue", lineWidth:5 });
    -</pre>
    -</div>
    -Select all Connections from "d1" and detach them:
    -<div class="code">
    -<pre>
    -jsPlumb.select({source:"d1"}).detach();
    -</pre>
    -</div>
    -<h5>.each iterator</h5>
    -The return value of jsPlumb.select has a <strong>.each</strong> function that allows you to iterate through the list, performing an operation on each one:
    -<div class="code">
    -<pre>
    -jsPlumb.select({scope:"foo"}).each(function(connection) {
    -	
    -		// do something 
    -});
    -</pre>
    -</div>
    -.each is itself chainable:
    -<div class="code">
    -<pre>
    -jsPlumb.select({scope:"foo"}).each(function(connection) {
    -	
    -		// do something 
    -
    -}).setHover(true);
    -</pre>
    -</div>
    -
    -
    -<h5>Other properties/functions</h5>
    -<ul>
    -	<li class="bullet"><strong>length</strong> - this member reports the number of Connections in the selection</li>
    -	<li class="bullet"><strong>get(idx)</strong> - this function allows you to retrieve a Connection from the selection</li>
    -</ul>
    -
    -
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="selectEndpoints">Selecting Endpoints</a></h3>
    -			<strong>jsPlumb.selectEndpoints</strong> provides a fluid interface for working with lists of Endpoints.
    -			The syntax used to specify which Endpoints you want is identical to that which you use for jsPlumb.select,
    -			and the return value is an object that supports most operations that you can perform on an Endpoint (and
    -			which is also chainable, for setter methods). Certain getter methods are also supported, but these are not
    -			chainable; they return an array consisting of all the Endpoints in the selection along with the return
    -			value for that Endpoint.
    -			<p>
    -			<p>
    -				Four parameters are supported by selectEndpoints - each of these except 'scope' can be provided as either
    -				a string, a selector, a DOM element, or an array of a mixture of these types.  'scope' can be provided as either
    -				a string or an array of strings:
    -				<ul>
    -					<li class="bullet">element - element(s) to get both source and target endpoints from</li>
    -					<li class="bullet">source - element(s) to get source endpoints from</li>
    -					<li class="bullet">target - element(s) to get target endpoints from</li>
    -					<li class="bullet">scope - scope(s) for endpoints to retrieve.</li>					
    -				</ul>
    -			</p>
    -			This is the full list of setter operations supported by jsPlumb.selectEndpoints:
    -			<ul>
    -				<li class="bullet">addOverlay</li>
    -				<li class="bullet">removeOverlay</li>
    -				<li class="bullet">removeOverlays</li>
    -				<li class="bullet">showOverlay</li>
    -				<li class="bullet">hideOverlay</li>
    -				<li class="bullet">showOverlays</li>
    -				<li class="bullet">hideOverlays</li>
    -				<li class="bullet">removeAllOverlays</li>
    -				<li class="bullet">setLabel</li>
    -				<li class="bullet">setPaintStyle</li>
    -				<li class="bullet">setHoverPaintStyle</li>				
    -				<li class="bullet">setConnector</li>
    -				<li class="bullet">setParameter</li>
    -				<li class="bullet">setParameters</li>
    -				<li class="bullet">repaint</li>
    -				<li class="bullet">setType</li>
    -				<li class="bullet">addType</li>
    -				<li class="bullet">removeType</li>
    -				<li class="bullet">toggleType</li>
    -			</ul>
    -
    -			Each of these operations returns a selector that can be chained.
    -			</p>
    -			These is the full list of getter operations supported by jsPlumb.select:
    -			<p>
    -				<ul>
    -					<li class="bullet">getLabel</li>
    -					<li class="bullet">getOverlay</li>
    -					<li class="bullet">isHover</li>				
    -					<li class="bullet">getParameter</li>
    -					<li class="bullet">getParameters</li>
    -					<li class="bullet">getType</li>
    -					<li class="bullet">hasType</li>
    -				</ul>
    -
    -			Each of these operations returns an array whose entries are [ value, Endpoint ] arrays, where 'value'
    -			is the return value from the given Endpoint. Remember that the return values from a getter are not chainable,
    -			but a getter may be called at the end of a chain of setters.
    -			</p>
    -			<p>
    -				Other methods (not chainable):
    -				<ul>
    -					<li class="bullet">delete - deletes the Endpoints in the selection</li>
    -					<li class="bullet">detachAll - detaches all Connections from the Endpoints in the selection </li>
    -				</ul>
    -			</p>
    -
    -			<p>Some examples:</p>
    -			Select all Endpoints and set their hover state to be false:
    -
    -<div class="code">
    -<pre>
    -jsPlumb.selectEndpoints().setHover(false);
    -</pre>
    -</div>
    -			Select all source Endpoints on "d1" and remove all Overlays:
    -
    -<div class="code">
    -<pre>
    -jsPlumb.selectEndpoints({source:"d1"}).removeAllOverlays();
    -</pre>
    -</div>
    -
    -Select all Endpoints in scope "foo" and set their fill style to be blue:
    -<div class="code">
    -<pre>
    -jsPlumb.selectEndpoints({ scope:"foo" }).setPaintStyle({ fillStyle:"blue" });
    -</pre>
    -</div>
    -Select all Endpoints from "d1" and detach their Connections:
    -<div class="code">
    -<pre>
    -jsPlumb.selectEndpoints({source:"d1"}).detachAll();
    -</pre>
    -</div>
    -<h5>.each iterator</h5>
    -The return value of jsPlumb.selectEndpoints has a <strong>.each</strong> function that allows you to iterate through the list, performing an operation on each one:
    -<div class="code">
    -<pre>
    -jsPlumb.selectEndpoints({scope:"foo"}).each(function(endpoint) {
    -	
    -		// do something 
    -});
    -</pre>
    -</div>
    -.each is itself chainable:
    -<div class="code">
    -<pre>
    -jsPlumb.selectEndpoints({scope:"foo"}).each(function(endpoint) {
    -	
    -		// do something 
    -
    -}).setHover(true);
    -</pre>
    -</div>
    -
    -
    -<h5>Other properties/functions</h5>
    -<ul>
    -	<li class="bullet"><strong>length</strong> - this member reports the number of Endpoints in the selection</li>
    -	<li class="bullet"><strong>get(idx)</strong> - this function allows you to retrieve an Endpoint from the selection</li>
    -</ul>
    -
    -
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="events">Events</a></h3>
    -			jsPlumb supports binding to several different events on Connections, Endpoints and Overlays, and also on the jsPlumb object itself.  
    -			
    -			<h4><a id="jsPlumbEvents">jsPlumb Events</a></h4>
    -			<p>To bind an event to jsPlumb itself, use <strong>jsPlumb.bind(event, callback)</strong>:</p>
    -			<div class="code">
    -<pre>
    -jsPlumb.bind("jsPlumbConnection", function(info) {
    -   .. update your model in here, maybe.
    -});
    -</pre>
    -</div>
    -<p>These are the events you can bind to on the jsPlumb class:</p>
    -			<ul>
    -				<li class="bullet"><strong>jsPlumbConnection</strong> - notification a Connection was established.
    -					<p>
    -						The first argument to the callback is an object with the following properties:					
    -					</p>
    -					<div class="code">
    -<pre>
    -{
    -  connection 		: 	the new Connection.  you can register listeners on this etc.
    -  sourceId 		:	id of the source element in the Connection
    -  targetId		:	id of the target element in the Connection
    -  source		:	the source element in the Connection
    -  target		:	the target element in the Connection
    -  sourceEndpoint	:	the source Endpoint in the Connection
    -  targetEndpoint	:	the targetEndpoint in the Connection
    -}
    -</pre>
    -					</div>
    -The second argument is the original mouse event that caused the Connection, if any.
    -
    -					<p>
    -All of the source/target properties are actually available inside the Connection object, but - for one of those rubbish historical reasons - are provided separately because of a vagary of the <em>jsPlumbConnectionDetached</em> callback, which is discussed below.
    -</p>
    -				</li>
    -				<li class="bullet"><strong>jsPlumbConnectionDetached</strong> - notification a Connection was detached.  
    -					<p>
    -						As with <em>jsPlumbConnection</em>, the first argument to the callback is an object with the following properties:					
    -					</p>
    -					<div class="code">
    -<pre>
    -{
    -  connection		: 	the Connection that was detached.  
    -  sourceId		:	id of the source element in the Connection <em>before it was detached</em>
    -  targetId		:	id of the target element in the Connection before it was detached
    -  source		:	the source element in the Connection before it was detached
    -  target		:	the target element in the Connection before it was detached
    -  sourceEndpoint	:	the source Endpoint in the Connection before it was detached
    -  targetEndpoint	:	the targetEndpoint in the Connection before it was detached
    -}
    -</pre>
    -
    -					</div>	
    -The second argument is the original mouse event that caused the disconnection, if any. 					
    -								
    -					<p>
    -As mentioned above, the source/target properties are provided separately from the Connection, because this event is fired whenever
    -a Connection is either detached and abandoned, or detached from some Endpoint and attached to another.  In the latter case, the
    -Connection that is passed to this callback is in an indeterminate state (that is, the Endpoints are still in the state they are
    -in when dragging, and do not reflect static reality), and so the source/target properties give you the real story.
    -</p>
    -<p> Like I said above, this is really just for rubbish historical reasons.  I'm attempting to document my way out of it!</p>
    -				
    -				</li>
    -
    -				<li class="bullet"><strong>connectionDrag</strong> - notification an existing Connection is being dragged. The callback is passed the Connection.
    -				</li> 
    -
    -				<li class="bullet"><strong>connectionDragStop</strong> - notification a Connection drag has stopped. This is only fired for existing Connections. The callback is passed the Connection that was just dragged.
    -				</li> 
    -
    -				<li class="bullet"><strong>click</strong> - notification a Connection was clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification a Connection was double-clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>endpointClick</strong> - notification an Endpoint was clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>endpointDblClick</strong> - notification an Endpoint was double-clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>contextmenu</strong> - a right-click on some given component.  jsPlumb will report right clicks on both Connections and Endpoints; the callback is passed the component that received the right-click.				
    -				<li class="bullet"><strong>beforeDrop</strong>
    -					This event is fired when a new or existing connection has been dropped. Your callback is passed a JS object with these fields:
    -					<ul>
    -						<li><strong>sourceId</strong> - the id of the source element in the connection</li>
    -						<li><strong>targetId</strong> - the id of the target element in the connection</li>
    -						<li><strong>scope</strong> - the scope of the connection</li>
    -						<li><strong>connection</strong> - the actual Connection object.  You can access the 'endpoints' array in a Connection to get the Endpoints involved in the Connection, but be aware that when a Connection is being dragged, one of these Endpoints will always be a transient Endpoint that exists only for the life of the drag. To get the Endpoint on which the Connection is being dropped, use the 'dropEndpoint' member.</li>
    -						<li><strong>dropEndpoint</strong> - this is the actual Endpoint on which the Connection is being dropped.  This <strong>may be null</strong>, because it will not be set if the Connection is being dropped on an element on which makeTarget has been called. </li>
    -					</ul>
    -
    -					If you return false (or nothing) from this callback, the new Connection is aborted and removed from the UI.
    -				</li>					
    -				<!--li class="bullet"><strong>beforeDrag</strong>
    -					This event is fired when a new connection is about to be dragged.  Your callback function
    -					is passed the Connection that jsPlumb has just created.  If you return false (or nothing) 
    -					from this callback, the new Connection is aborted.
    -				</li-->
    -				<li class="bullet"><strong>beforeDetach</strong>
    -					This event is fired when a Connection is about to be detached, for whatever reason. Your callback function is passed the Connection that the user has just detached. Returning false from this interceptor aborts the Connection detach.
    -				</li>
    -							
    -			</ul>
    -			<h4><a id="connectionEvents">Connection Events</a></h4>		
    -			<p>To bind to an event on a Connection, you also use the <strong>bind</strong> method:</p>
    -			<div class="code">
    -<pre>
    -var connection = jsPlumb.connect({source:"d1", target:"d2"});
    -connection.bind("click", function(conn) {
    -	console.log("you clicked on ", conn);
    -});
    -</pre>
    -</div>
    -<p>These are the Connection events to which you can bind a listener:</p>
    -			<ul>
    -				<li class="bullet"><strong>click</strong> - notification a Connection was clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification a Connection was double-clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>contextmenu</strong> - a right-click on the Connection. The callback is passed the Connection and the original event.
    -				<li class="bullet"><strong>mouseenter</strong> - notification the mouse entered the Connection's path.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>mouseexit</strong> - notification the mouse exited the Connection's.  The callback is passed the Connection and the original mouse event.</li>
    -			</ul>
    -			<h4><a id="endpointEvents">Endpoint Events</a></h4>			
    -			<p>To bind to an event on a Endpoint, you again use the <strong>bind</strong> method:</p>
    -			<div class="code">
    -<pre>
    -var endpoint = jsPlumb.addEndpoint("d1", { someOptions } );
    -endpoint.bind("click", function(endpoint) {
    -	console.log("you clicked on ", endpoint);
    -});
    -</pre>
    -</div>
    -<p>These are the Endpoint events to which you can bind a listener:</p>
    -			<ul>
    -				<li class="bullet"><strong>click</strong> - notification an Endpoint was clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification an Endpoint was double-clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>contextmenu</strong> - a right-click on the Endpoint. The callback is passed the Endpoint and the original event.
    -				<li class="bullet"><strong>mouseenter</strong> - notification the mouse entered the Endpoint.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>mouseexit</strong> - notification the mouse exited the Endpoint.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>maxConnections</strong> - notification the user tried to drop a Connection on an Endpoint that already has the maximum number of Connections.  The callback is passed as first argument an object literal containing the Endpoint, the Connection the user tried to drop, and the value of maxConnections for the Endpoint, and as second argument the original mouse event.</li>
    -			</ul>
    -			<h4><a id="overlayEvents">Overlay Events</a></h4>
    -			Registering event listeners on an Overlay is a slightly different procedure - you provide them as arguments to the Overlay's 
    -			constructor.  This is because you never actually act on an 'Overlay' object.  Here's how to register a click listener on an Overlay:
    -			<div class="code">
    -<pre>jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  overlays:[
    -    [ "Label", {
    -      events:{
    -        click:function(labelOverlay, originalEvent) { 
    -          console.log("click on label overlay for :" + labelOverlay.component); 
    -        }
    -      }
    -    }],
    -    [ "Diamond", {
    -      events:{
    -        dblclick:function(diamondOverlay, originalEvent) { 
    -          console.log("double click on diamond overlay for : " + diamondOverlay.component); 
    -        }
    -      }
    -    }] 	
    -  ]
    -});
    -</pre>			
    -			</div>		
    -		Note that events registered on Diamond, Arrow or PlainArrow overlays will not fire with the Canvas renderer - they work only with the SVG and VML renderers. 
    -
    -
    -		<h4><a id="jsPlumbEvents">Unbinding Events</a></h4>
    -		<p>On the jsPlumb object and on Connections and Endpoints, you can use the <strong>unbind</strong> method to remove a listener.  This method either takes the name of the event to unbind:
    -		<div class="code">
    -<pre>
    -jsPlumb.unbind("click");
    -</pre>
    -</div>		
    -...or no argument, meaning unbind all events:
    -	<div class="code">
    -<pre>
    -var e = jsPlumb.addEndpoint("someDiv");
    -e.bind("click", function() { ... });
    -e.bind("dblclick", function() { ... });
    -
    -...
    -
    -e.unbind("click");
    -</pre>
    -</div>
    -
    -		</div>
    -
    -				<div class="section">
    -			<a id="examples"><h3>jsPlumb.connect Examples</h3></a>
    -This section provides examples of how to use the programmatic API to establish Connections. 
    -			<p>
    -			The basic syntax of a call is that you execute 'connect', providing a source and a target, and optionally a paintStyle and preferences for where you
    -			want the plumbing to be anchored on each element, as well as the type of connector to use. 
    -			</p>
    -			<ul>
    -				<li>Connect window1 to window2 with the default settings:
    -					<div class="code">
    -						<pre>jsPlumb.connect({source:"window1", target:"window2"});</pre>
    -					</div>
    -This connects 'window1' to 'window2' using the jsPlumb defaults - a red Bezier curve Connector and black Dot Endpoints.
    -				</li>
    -				<li>Connect window1 to window2 with a 15 pixel wide yellow Connector, and a slightly brighter endpoint (remember the default Endpoint is a Dot):
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -					</div>
    -				</li>
    -				
    -				<li>Connect window1 to window2 with a 15 pixel wide yellow Connector, and a slightly brighter endpoint:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -					</div>
    -				</li>				
    -				<li>Connect window3 to 'window4' with a 10 pixel wide, semi opaque blue Connector, anchored to the left middle of window3, and the right middle of window4, with a Rectangle endpoint of width 10 and height 8:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window3',
    -	target:'window4',
    -	paintStyle:{ lineWidth:10, strokeStyle:'rgba(0, 0, 200, 0.5)' },
    -	anchors:["RightMiddle", "LeftMiddle"],
    -	endpoint:[ "Rectangle", { width:10, height:8 } ]
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window2 to window3 with a default Connector from the top center of window2 to the bottom center of window3, and rectangular endpoints:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window2',
    -	target:'window3',
    -	paintStyle:{lineWidth:8, strokeStyle:'rgb(189,11,11)'},
    -	anchors:["BottomCenter", "TopCenter"],
    -	endpoint:"Rectangle"
    -});</pre>
    -					</div>
    -				</li>
    -
    -
    -				<li>Connect window1 to window2 with a 15 px wide yellow Bezier. endpoints are a slightly lighter shade of yellow.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	anchors:["BottomCenter", [0.75,0,0,-1]],
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -</div></li>
    -<li>Connect window3 to window4 with a 10px wide blue-ish half transparent Bezier. put endpoints underneath the element they attach to.
    -the endpoints have a radial gradient. both ways of specifying gradient positioning are shown here.
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect( {
    -	source:'window3',
    -	target:'window4',
    -		 paintStyle:{lineWidth:10, strokeStyle:w34Stroke},
    -		 anchors:["RightMiddle", "LeftMiddle"],
    -		 endpointStyle:{ gradient : {stops:[[0, w34Stroke], [1, w34HlStroke]], offset:17.5, innerRadius:15 }, radius:35},
    -		 //endpointStyle:{ gradient : {stops:[[0, w34Stroke], [1, w34HlStroke]], offset:'78%', innerRadius:'73%'}, radius:35 },
    -		 endpointsOnTop:false
    -	}
    -);</pre>
    -</div></li>
    -<li>Connect window2 to window3 with an 8px red Bezier and default rectangular endpoints.  see also how the first anchor is
    -specified here - this is how you create anchors in locations jsPlumb does not offer shortcuts for.
    -the endpoints in this example have linear gradients applied.
    -<div class="code">
    -<pre>var w23Stroke = 'rgb(189,11,11)';
    -jsPlumb.connect({
    -	source:'window2',
    -	target:'window3',
    -	paintStyle:{lineWidth:8,strokeStyle:w23Stroke},
    -	anchors:[[0.3,1,0,1], "TopCenter"],
    -	endpoint:"Rectangle",
    -	endpointStyles:[{ gradient : {stops:[[0, w23Stroke], [1, '#558822']] }},
    -       				{ gradient : {stops:[[0, w23Stroke], [1, '#882255']] }}]
    -});</pre>
    -</div></li>
    -
    -<li>Connect window5 to window6 from center to center, 5px wide line that is green and half transparent. the endpoints are
    -125px in radius and spill out from underneath their elements.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window5',
    -	target:'window6',
    -	anchors:["Center", "Center"],
    -	paintStyle:{lineWidth:5,strokeStyle:'rgba(0,255,0,0.5)'},
    -	endpointStyle:{radius:125}
    -});</pre>
    -</div></li>
    -
    -<li>Connect window4 to window5 from bottom right to top left, with a 7px straight line purple connector, and an image as the endpoint,
    -placed on top of the element it is connected to.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window4",
    -	target:"window5",
    -	anchors:[ "BottomRight","TopLeft" ],
    -	paintStyle:{ lineWidth:7, strokeStyle:"rgb(131,8,135)" },
    -	endpoint:[ "Image", { src:"http://morrisonpitt.com/jsPlumb/img/endpointTest1.png" } ],
    -	connector:"Straight"
    -});</pre>
    -</div></li>
    -
    -
    -				<li>Connect window5 to window6 between their center points with a semi-opaque connector, and 125px endpoints:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window5",
    -	target:"window6",
    -	anchors:[ "Center", "Center" ],
    -	paintStyle:{ lineWidth:5, strokeStyle:"rgba(0,255,0,0.5)" },
    -	endpointStyle:{ radius:125 }
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window7 to window8 with a 10 pixel wide blue Connector, anchored on the top left of window7 and the bottom right of window8:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window7",
    -	target:"window8",
    -	paintStyle:{ lineWidth:10, strokeStyle:"blue" },
    -	anchors:[ "TopLeft", "BottomRight" ]
    -});
    -</pre>
    -					</div>
    -				</li>
    -				<li>Connect the bottom right corner of window4 to the top left corner of window5, with rectangular endpoints of size 40x40 and a hover color of light blue:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window4",
    -	target:"window5",
    -	anchors:["BottomRight","TopLeft"],
    -	paintStyle:{lineWidth:7,strokeStyle:'rgb(131,8,135)'},
    -	hoverPaintStyle:{ strokeStyle:"rgb(0, 0, 135)" },
    -	endpointStyle:{ width:40, height:40 },
    -	endpoint:"Rectangle",
    -	connector:"Straight"
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window1 to window2 with the default paint settings but provide some drag options (which are passed through to the underlying library's draggable call):
    -					<div class="code">
    -						<pre>jsPlumb.connect({source:'window1', target:'window2', dragOptions:{cursor:'crosshair'}});</pre>
    -					</div>
    -				</li>
    -</div>
    -		
    -		<div class="section">
    -			<a id="dragAndDropExamples"><h3>Draggable Connections Examples</h3></a>
    -			This is a list of examples of how to use jsPlumb to create Connections using drag and drop.
    -			<p>
    -			The basic procedure is:
    -			<ol>
    -				<li>1a. Create Endpoints and register them on elements in your UI</li>
    -				<li>1b. alternatively, create a source endpoint and then make some element a drop target </li>
    -				<li>2. Drag and Drop</li>
    -			</ol>
    -			That's all there is to it.  Of course there are plenty of options you can set when doing this...it will be easier
    -			to show you some examples:
    -			</p>
    -			<ul>
    -				<li>Define an Endpoint with default appearance, that is both a source and target of new Connections:
    -<div class="code">
    -<pre>var endpointOptions = { isSource:true, isTarget:true }; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Register that Endpoint on window3, specifying that it should be located in the top center of the element:
    -<div class="code">
    -<pre>var window3Endpoint = jsPlumb.addEndpoint('window3', { anchor:"TopCenter" }, endpointOptions );  
    -</pre>
    -</div>
    -Notice here the usage of the three-argument addEndpoint  - we can reuse 'endpointOptions' with a different Anchor for another element.  This is a useful
    -practice to get into.<br/><br/>
    -				</li>
    -				
    -				<li>Now register that Endpoint on window4, specifying that it should be located in the bottom center of the element:
    -<div class="code">
    -<pre>var window4Endpoint = jsPlumb.addEndpoint('window4', { anchor:"BottomCenter" }, endpointOptions );  
    -</pre>
    -</div>
    -Now we have two Endpoints, both of which support drag and drop of new Connections.  We can use these to make a programmatic Connection, too, though:<br/><br/>
    -				</li>				
    -				
    -				<li>Connect window3 to window4 with a 25px wide yellow Bezier that has a 'curviness' of 175:
    -<div class="code">
    -<pre>jsPlumb.connect({ 
    -	source:window3Endpoint,
    -	target:window4Endpoint,
    -	connector: [ "Bezier", 175 ],
    -	paintStyle:{ lineWidth:25, strokeStyle:'yellow' }
    -});  
    -</pre>
    -</div>
    -				</li>
    -				
    -				<li>Define an Endpoint that creates Connections that are 20px wide straight lines, that is both a source and target of new Connections,
    -				and that has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Define an Endpoint that will be anchored to "TopCenter".  It creates Connections that are 20px wide straight lines, that is both a source and target of new Connections,
    -				and that has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchor:"TopCenter", 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Define an Endpoint that will create a dynamic anchor which can be positioned at "TopCenter" or "BottomCenter".  It creates Connections that are 20px wide straight lines, it is both a source and target of new Connections,
    -				and it has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchor:[ "TopCenter", "BottomCenter" ], 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Exactly the same as before, but shows how you can use "anchors" instead of "anchor", if that makes you feel happier:
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchors:[ "TopCenter", "BottomCenter" ], 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>								
    -				
    -<li>Define an Endpoint that is a 30px blue dot, creates Connections that are 20px wide straight lines, is both a source and target of new Connections,
    -				has a 'scope' of 'blueline', and has an event handler that pops up an alert (note: the event handler name means this example is jQuery - MooTools
    -				and YUI3 use different event handler names):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{fillStyle:'blue'},
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ drop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -			<li>Same example as before, but this is for MooTools, and the Endpoint can support up to 5 connections (the default is 1):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{ fillStyle:'blue' },
    -	maxConnections:5,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ onDrop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Same example again, but maxConnections being set to -1 means that the Endpoint has no maximum limit of Connections:
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", {radius:30} ],
    -	style:{ fillStyle:'blue' },
    -	maxConnections:-1,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ onDrop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Same example again, but for YUI3.  Note the drop callback is "drop:hit":
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{fillStyle:'blue'},
    -	maxConnections:-1,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ "drop:hit":function(e, ui) { alert('drop!'); } }	 	
    -}; 
    -</pre>
    -</div>				 				
    -				</li>				
    -				
    -	<li>Assign a UUID to the endpoint options created above, and add as Endpoints to "window1" and "window2":
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("window1", { uuid:"abcdefg" }, endpointOptions );
    -jsPlumb.addEndpoint("window2", { uuid:"hijklmn" }, endpointOptions );
    -</pre>
    -</div>	
    -	</li>
    -	
    -	<li>Connect the two Endpoints we just registered on "window1" and "window2":
    -<div class="code">
    -<pre>
    -jsPlumb.connect({uuids:["abcdefg", "hijklmn"]});
    -</pre>
    -</div>	
    -	</li>
    -	
    -	<li>Create a source Endpoint, register it on some element, then make some other element a Connection target:
    -<div class="code">
    -<pre>
    -var sourceEndpoint = { isSource:true, endpoint:[ "Dot", { radius:50 } ] };
    -var targetEndpoint = { endpoint:[ "Rectangle", { width:10, height:10 } ] };
    -jsPlumb.addEndpoint( "window1", sourceEndpoint );
    -jsPlumb.makeTarget( "window2", targetEndpoint );
    -</pre>
    -</div>	
    -Notice that the endpoint definition we use on the target window does not include the "isTarget:true" directive.  jsPlumb ignores that flag when
    -creating a connection using an element as the target; but if you then tried to drag another connection to the Endpoint that was created, you would
    -not be able to.  To permit that, you would set 'isTarget:true' on the targetEndpoint options defined above.
    -	
    -	</li>
    -				
    -			</ul>
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="utilityFunctions">Utility Functions</a></h3>
    -			<ul>
    -				<li>Detach window5 from all connections
    -					<div class="code">
    -						<pre>jsPlumb.detachAll("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Hide all window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.hide("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Hide all window5's connections endpoints
    -					<div class="code">
    -						<pre>jsPlumb.hide("window5", true);</pre>
    -					</div>
    -				</li>
    -				<li>Show all window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.show("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Show all window5's connections and endpoints.  Note that in the case that you call jsPlumb.show with two arguments, jsPlumb
    -				will also not make a connection visible if it determines that the other endpoint in the connection is not visible.
    -					<div class="code">
    -						<pre>jsPlumb.show("window5", hide);</pre>
    -					</div>
    -				</li>				
    -				<li>Toggle the visibility of window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.toggle("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of all of window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.repaint("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of all of window5, window6 and window11's connections
    -					<div class="code">
    -						<pre>jsPlumb.repaint( [ "window5", "window6", "window11" ] );</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of every connection
    -					<div class="code">
    -						<pre>jsPlumb.repaintEverything();</pre>
    -					</div>
    -				</li>
    -				<li>Detach every connection that the given instance of jsPlumb is managing
    -					<div class="code">
    -						<pre>jsPlumb.detachEveryConnection();</pre>
    -					</div>
    -				</li>
    -				<li>Detach all connections from "window1"
    -					<div class="code">
    -						<pre>jsPlumb.detachAllConnections("window1");</pre>
    -					</div>
    -				</li>
    -				<li>Remove the given Endpoint from element "window1", deleting its Connections. 
    -					<div class="code">
    -						<pre>jsPlumb.removeEndpoint("window1", someEndpoint);</pre>
    -					</div>
    -				</li>
    -				<li>Remove all Endpoints for the element 'window1', deleting their Connections.
    -					<div class="code">
    -						<pre>jsPlumb.removeAllEndpoints("window1");</pre>
    -					</div>
    -				</li>	
    -				<li>Removes every Endpoint managed by this instance of jsPlumb, deleting all Connections.
    -				This is the same as jsPlumb.reset(), effectively, but it does not clear out the event listeners list. 
    -					<div class="code">
    -						<pre>jsPlumb.removeEveryEndpoint();</pre>
    -					</div>
    -				</li>
    -				<li>Deletes the given Endpoint and all its Connections. 
    -					<div class="code">
    -						<pre>jsPlumb.deleteEndpoint(endpoint);</pre>
    -					</div>
    -				</li>
    -				<li>Removes every endpoint, detaches every connection, and clears the event listeners list.  Returns jsPlumb instance to its initial state.  
    -					<div class="code">
    -						<pre>jsPlumb.reset();</pre>
    -					</div>
    -				</li>	
    -				<li>Library-agnostic method to get a selector from the given selector specification.  This function is used mostly by the jsPlumb demos, to cut down on repeating code across the different libraries.  But perhaps you are writing something with jsPlumb that you also wisht to be library agnostic - this function can help you. The returned object, in each of jQuery, MooTools and YUI, has a ".each" iterator.
    -					<div class="code">
    -						<pre>var selector = jsPlumb.getSelector(".someSelector .spec");</pre>
    -					</div>					
    -				</li>
    -				<li>Set window1 to be not draggable, no matter what some jsPlumb command may request.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggable("window1", false);</pre>
    -					</div>
    -				</li>
    -				<li>Set window1 and window2 to be not draggable, no matter what some jsPlumb command may request.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggable(["window1","window2"], false);</pre>
    -					</div>
    -				</li>				
    -				<li>Initialises window1 as a draggable element (all libraries). Passes in an on drag callback
    -				<div class="code">
    -					<pre>jsPlumb.draggable("window1");</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 and window2 as draggable elements (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(["window1","window2"]);</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable("window1");</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (jQuery)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (MooTools)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($$(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (YIU3)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(Y.all(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (jQuery)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($("#window1"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (MooTools)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($("window1"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (YUI3)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(Y.one("window1"));</pre>
    -				</div>
    -				</li>
    -			</ul>
    -		</div>					
    -		
    -		<div class="section">
    -			<h3><a id="developingJsPlumb">Advanced Topics</a></h3>
    -			<h4><a id="which">Which files are which?</a></h4>
    -			In development, jsPlumb is broken up into nine scripts:
    -				<ul>
    -					<li>- jsPlumb-util-x.x.x.js
    -					<p>There are the jsPlumb utility functions (and some base classes).</p>					
    -					</li>
    -					<li>- jsPlumb-x.x.x.js
    -					<p>This is the main jsPlumb engine.</p>					
    -					</li>
    -					<li>- jsPlumb-defaults-x.x.x.js
    -					<p>This contains the default Anchor, Endpoint, Connector and Overlay implementations.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-canvas-x.x.x.js
    -					<p>This contains the HTML5 canvas render code.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-svg-x.x.x.js
    -					<p>This contains the SVG render code.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-vml-x.x.x.js
    -					<p>This contains the VML render code.</p>
    -					</li>
    -					<li>- jsPlumb-connectors-statemachine-x.x.x.js
    -					<p>This State Machine connector.</p>
    -					</li>
    -					<li>- &lt;LIBRARY_PREFIX&gt;.jsPlumb-x.x.x.js
    -					<p>This contains library-specific helper methods.  jsPlumb ships with three of these - one each for jQuery, MooTools and YUI3. See below for information on how to create a new library implementation.</p>
    -					</li>
    -					<li>- jsBezier-0.4-min.js
    -					<p>These are the Bezier curve functions; they are maintained in a separate project called <a href="http://code.google.com/p/jsbezier/">jsBezier</a></p>
    -					</li>
    -				</ul>
    -			These seven files are packaged together to form the scripts that people use, for example:
    -			<ul>
    -				<li>jquery.jsPlumb-1.3.13-all.js
    -				<p>Contains jsPlumb-1.3.13.js, jsPlumb-defaults-1.3.13.js, jsPlumb-renderers-canvas-1.3.13.js, jsPlumb-renderers-svg-1.3.13.js, jsPlumb-renderers-vml-1.3.13.js, jsPlumb-connectors-statemachine-1.3.13.js, jquery.jsPlumb-1.3.13.js and jsBezier-0.4-min.js</p>
    -				</li>
    -				<li>jquery.jsPlumb-1.3.13-all-min.js
    -				<p>A minified version of the script above (minified using the YUI Compressor)</p>
    -				</li>
    -			</ul>
    -			<h4><a id="pluggableLibrarySupport">Pluggable Library Support</a></h4>
    -		Out of the box, jsPlumb can be run on top of jQuery, MooTools or YUI3.  This is achieved by
    -		delegating several core methods - tasks such as finding an element by id, finding an element's
    -		position or dimensions, initialising a draggable, etc - to the library in question.
    -		<p>To develop one of these, your test page should include the first two scripts discussed above, and
    -		then your own script containing your library specific functionality.  The existing implementations
    -		may be documented well enough for you to create your own, but contact me if you need assistance.  If you do this, it would be
    -		great to share it with everyone...</p>   
    -		<p>There is a group of people working on an ExtJS adapter currently.  There is no schedule for when this will be completed though.</p>
    -
    -			<!-- h4><a id="customConnectors">Custom Connectors</a></h4>			
    -					
    -			You can provide your own connectors if you need to. A Connector consists of two functions, which work as a pair.  First a call is made to the <em>compute</em> function:
    -
    -			<div class="code">
    -<pre>
    -this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) {
    -	...
    -	return dimensions;
    -}
    -</pre>
    -			</div>
    -			which is expected to return a list that the <em>paint</em> function can make sense of.  The first four entries in the
    -			list <strong>must be</strong> the [x,y,width,height] values for the canvas that the connector will be drawn on; jsPlumb will
    -			use this information to size the canvas prior to calling the Connector's <em>paint</em> function.  Therefore it
    -			is the Connector's responsibility to ensure that the returned dimensions describe a large enough space for
    -			the line that will be drawn on it.</p><p>The next four elements <strong>must be</strong> the coordinates of the two endpoints of the line
    -			you are going to draw.</p><p>The remainder of the items in the returned list are arbitrary, and will
    -			vary between Connector implementations; this list is passed in to a Connector's <em>paint</em> function, so each
    -			implementation will put into the list whatever it needs to paint itself.  For instance, the straight line
    -			connector only needs the [x,y] location of each end of the line it will paint, and that is one of the required entries, so
    -			it does not have to do anything extra,  whereas the Bezier connector adds the location of the two control points.  Other types of
    -			Connectors will do whatever is appropriate for their particular situation.
    -			</p>
    -			This is the method signature for the <em>paint</em> function:
    -			<div class="code">
    -				<pre>this.paint = function(dimensions, ctx) { .. }</pre>
    -			</div>
    -
    -			here, the 'dimensions' argument to the 'paint' function is the return value of the 'compute' function.  The 'ctx' argument is the Canvas context; you
    -			will do all your drawing on this.
    -			</p>
    -			To change the connector from the default, specify it in your connect call:
    -			<div class="code">
    -				<pre>jsPlumb.connect({source:'someWindow', target:'otherWindow', connector:new jsPlumb.Connectors.Straight()});</pre>
    -			</div>
    -			note that you can use shorthand for connectors if you don't need to specify parameters to it:
    -			<div class="code">
    -				<pre>jsPlumb.connect({source:'someWindow', target:'otherWindow', connector:"Straight"});</pre>
    -			</div>
    -			This works in the same way as specifying Anchors as string does.
    -<p>
    -The section below this discusses Overlays, which allow you to decorate Connectors with
    -			things such as labels or arrows or whatever else you like. Overlays can only work with Connectors that implement a few helper functions.
    -			<h4>The concept of <strong>location</strong></h4>
    -			Before discussing the helper functions you first must be across the concept of 'location' as used by this mechanism.  The <em>location</em>
    -			on a connector can be a decimal value between 0 and 1 inclusive, which marks a proportionate amount of travel <em>along the path
    -			inscribed by the Connector</em>.  For a straight line connector this is a simple piece of maths, but for Bezier connectors it's a little
    -			bit more involved.
    -			<h4>Required Helper Methods</h4>
    -			<ul>
    -				<li><strong>pointOnPath(location)</strong> - returns an [x,y] point corresponding to the given location</li>
    -				<li><strong>pointAlongPathFrom(location, distance)</strong> - returns an [x,y] point corresponding to travelling 'distance' pixels along the connector from 'location'.</li>
    -				<li><strong>gradientAtPoint(location)</strong> - returns the connector's gradient at the given location.  For linear connectors such as Straight this is constant, but for Bezier connectors the gradient changes continually.</li>
    -				<li><strong>perpendicularToPathAt(location, distance, length)</strong> - returns a line that is perpendicular to (and centered on) the connector at 'distance' from the given location, with length 'length'.</li>
    -			</ul>
    -</p>
    -<h4><a id="customEndpoints">Custom Endpoints</a></h4>
    -<p>To create your own Endpoint implementation, you need to implement a single method:
    -				<div class="code"><pre>paint : function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { ... }</pre></div>
    -				The arguments to this method are as follows:
    -				<ul>
    -					<li>anchorPoint - [x,y] location of the anchor point on screen</li>
    -					<li>orientation - [x,y] hints for the general direction the anchor points to</li>
    -					<li>canvas - the canvas to draw into</li>
    -					<li>endpointStyle - Javascript object containing style directives as discussed above.  The contents of this are arbitrary, so if you write a new Endpoint that needs some extra settings, you can add them no hassle.</li>
    -					<li>connectorPaintStyle - the style being used to paint the associated Connector.</li>
    -				</ul>
    -			</p>
    -			<p>
    -				It is your responsibility to size and locate the canvas to suit your needs. jsPlumb provides the following
    -				helper method to assist you:
    -				<div class="code"><pre>jsPlumb.sizeCanvas(canvas, x, y, width, height);</pre></div>
    -				Allows you to locate the canvas on screen and to size it.
    -			</p>
    -<h4><a id="customOverlays">Custom Overlays</a></h4>
    -			Overlays can only work with Connectors that implement the methods <strong>pointOnPath</strong>, <strong>pointAlongPathFrom</strong>,
    -			<strong>gradientAtPoint</strong>, and <strong>perpendicularToPathAt</strong>.  These are discussed in more detail in the Custom Connectors section above.  Both of the Connectors
    -			that come with jsPlumb - Straight and Bezier -implement these methods; if you write a custom connector, or have written a custom connector, you will need to supply
    -			them.   
    -			<h4>Overlay Interface</h4>
    -			An Overlay is required to implement two methods in order to be usable by jsPlumb:
    -			<ul>
    -				<li><strong>computeMaxSize(connector, context)</strong> - returns an integer value indicating the larger of this overlay's width and height.  It is used by jsPlumb to ensure that the canvas is large enough to accomodate the overlay.  The examples below should help to clarify this.</li>
    -				<li><strong>draw(connector, ctx)</strong> - draws the overlay.  What happens in this method is up to the given implementation.</li>
    -			</ul>
    -			<h4>computeMaxSize methods</h4>
    -			This is the Arrow overlay's computeMaxSize method:
    -			<div class="code">
    -<pre>
    -this.computeMaxSize = function() { return width; }
    -</pre>			
    -			</div>
    -Here, <em>width</em> is a private member of Arrow that indicates the width of the arrow's tail.  So the Arrow overlay reports that figure as the width it needs.					
    -Contrast this with the Label Overlay's computeMaxSize method:
    -		<div class="code">
    -<pre>
    -this.computeMaxSize = function(connector, ctx) {
    -  	if (labelText) {
    -  		ctx.save();
    -        if (self.labelStyle.font) ctx.font = self.labelStyle.font;
    -         var t = ctx.measureText(labelText).width;			            
    -		// a fake text height measurement: use the width of upper case M
    -		var h = ctx.measureText("M").width;					
    -		labelPadding = self.labelStyle.padding || 0.25;
    -		labelWidth = t + (2 * t * labelPadding);
    -		labelHeight = h + (2 * h * labelPadding);
    -		ctx.restore();
    -		return Math.max(labelWidth, labelHeight);
    -  	}
    - 	return 0;
    - };
    -</pre>			
    -			</div>
    -The Label overlay has to use the context to determine how big it will be on screen.
    -
    -<h4>draw methods</h4>
    -To give you a taste for how you can interact with a connector, consider the first few lines of the Arrow overlay's draw method:
    -<div class="code">
    -<pre>
    -this.draw = function(connector, ctx) {
    -	// this is the arrow head position
    -	var hxy = connector.pointAlongPathFrom(self.loc, length / 2);		
    -	// this is the center of the tail
    -	var txy = connector.pointAlongPathFrom(self.loc, -length / 2), tx = txy[0], ty = txy[1];
    -	// this is the tail vector
    -	var tail = connector.perpendicularToPathAt(self.loc, -length / 2, width);
    -	
    -	...
    -</pre>
    -</div>
    -	<p>The first two calls to the connector use the <strong>pointAlongPathFrom</strong> method, which passes <em>self.loc</em> as the location of the
    -	point on the path, and <em>length / 2</em> as the number of pixels along the path to traverse.</p>
    -	<p><em>self.loc</em> is Arrow's internal member describing the location of the overlay, and it is a decimal (between 0 and 1 inclusive) that points to
    -	a location at some distance along the path inscribed by the connector.  So these first two calls get us [x,y] locations of points on 
    -	the connector that mark the head and tail points for the arrow.  
    -	</p>		
    -	<p>
    -	The <strong>connector.perpendicularToPathAt(self.loc, -length / 2, width)</strong> call returns a line description of a line that is perpendicular to, and centered on, the connector
    -	at "-length / 2" pixels from the given point, with the given width (width is an internal member of Arrow).  At this point, the Arrow object has the three main points it
    -	needs in order to draw itself.
    -	</p>
    -		</div-->
    -</div>
    -
    -</body>
    -
    -</html>
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.13/index.html b/demo/doc/archive/1.3.13/index.html
    deleted file mode 100644
    index efed9c600..000000000
    --- a/demo/doc/archive/1.3.13/index.html
    +++ /dev/null
    @@ -1,92 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.13 documentation</title>
    -		<link rel="stylesheet" target="contentFrame" href="/mp.css"></link>
    -		<link rel="stylesheet" target="contentFrame" href="jsPlumbDoc.css"></link>
    -	</head>
    -	<body>
    -		<div class="index section">
    -			<h3>jsPlumb 1.3.13</h3>
    -			<ul>
    -				<li><a target="contentFrame" href="content.html#summary">Summary</a></li>
    -				<li><a target="contentFrame" href="content.html#changes">Changes since version 1.3.12</a></li>
    -				
    -				<li><h4>Setup</h4></li>
    -				<li><a target="contentFrame" href="content.html#imports">Required Imports</a></li>
    -				<li><a target="contentFrame" href="content.html#renderMode">Render Mode</a></li>
    -				<li><a target="contentFrame" href="content.html#initializing">Initializing jsPlumb</a></li>				
    -				<li><a target="contentFrame" href="content.html#multipleInstances">Multiple jsPlumb Instances</a></li>
    -				<li><a target="contentFrame" href="content.html#zIndex">Z-index Considerations</a></li>
    -				<li><a target="contentFrame" href="content.html#containerDefault">Where does jsPlumb add elements?</a></li>
    -				<li><a target="contentFrame" href="content.html#elementDragging">Element dragging</a></li>
    -				
    -				<li><h4>Introduction</h4></li>
    -				<li><a target="contentFrame" href="content.html#jsPlumbBasics">Basic Concepts</a></li>
    -				<li><a target="contentFrame" href="content.html#anchors">Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#staticAnchors">Static Anchors</a></li>				
    -				<li><a target="contentFrame" href="content.html#dynamicAnchors">Dynamic Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#perimeterAnchors">Perimeter Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#continuousAnchors">Continuous Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#definitions">Connector, Endpoint &amp; Overlay Definitions</a></li>
    -				<li><a target="contentFrame" href="content.html#connectors">Connectors</a></li>
    -				<li><a target="contentFrame" href="content.html#endpoints">Endpoints</a></li>
    -				<li><a target="contentFrame" href="content.html#overlays">Overlays</a></li>
    -				<li><a target="contentFrame" href="content.html#defaults">Defaults</a></li>								
    -				
    -				<li><h4>Connections</h4></li>
    -				<li><a target="contentFrame" href="content.html#simpleConnections">Programmatic Connections</a></li>
    -				<li><a target="contentFrame" href="content.html#draggableConnections">Connections using Drag and Drop</a></li>
    -				<li><a target="contentFrame" href="content.html#sourcesAndTargets">Elements as Sources &amp; Targets</a></li>
    -				<li class="sub"><a target="contentFrame" href="content.html#makeTarget">makeTarget</a></li>
    -				<li class="sub"><a target="contentFrame" href="content.html#makeSource">makeSource</a></li>
    -				<li><a target="contentFrame" href="content.html#dragOptions">Drag Options</a></li>				
    -				<li><a target="contentFrame" href="content.html#dropOptions">Drop Options</a></li>
    -				<li><a target="contentFrame" href="content.html#dragAndDropScope">Drag and Drop Scope</a></li>
    -				
    -				<li><h4>Parameters</h4></li>		
    -				<li><a target="contentFrame" href="content.html#connectParameters">jsPlumb.connect</a></li>
    -				<li><a target="contentFrame" href="content.html#addEndpointParameters">jsPlumb.addEndpoint</a></li>
    -				
    -				<li><h4>Connection and Endpoint Types</h4></li>		
    -				<li><a target="contentFrame" href="content.html#connectionTypes">Connection Type</a></li>
    -				<li><a target="contentFrame" href="content.html#endpointTypes">Endpoint Type</a></li>
    -
    -				<li><h4>Interceptors</h4></li>				
    -				<li><a target="contentFrame" href="content.html#beforeDrop">Before Drop</a></li>
    -				<li><a target="contentFrame" href="content.html#beforeDetach">Before Detach</a></li>
    -				
    -				<li><h4>Appearance</h4></li>
    -				<li><a target="contentFrame" href="content.html#paintstyles">Paint Styles</a></li>
    -				<li><a target="contentFrame" href="content.html#hoverpaintstyles">Hover Paint Styles</a></li>
    -				<li><a target="contentFrame" href="content.html#gradients">Gradients</a></li>				
    -				
    -				<li><h4>Miscellaneous</h4></li>
    -				<li><a target="contentFrame" href="content.html#cssclasses">CSS</a></li>
    -				<li><a target="contentFrame" href="content.html#animation">Animation</a></li>
    -				<li><a target="contentFrame" href="content.html#connectionInfo">Retrieving Connection Information</a></li>
    -				<li><a target="contentFrame" href="content.html#selectConnections">Selecting Connections</a></li>
    -				<li><a target="contentFrame" href="content.html#selectEndpoints">Selecting Endpoints</a></li>
    -				
    -				<li><h4>Events</h4></li>
    -				<li><a target="contentFrame" href="content.html#events">jsPlumb Events</a></li>
    -				<li><a target="contentFrame" href="content.html#connectionEvents">Connection Events</a></li>
    -				<li><a target="contentFrame" href="content.html#endpointEvents">Endpoint Events</a></li>
    -				<li><a target="contentFrame" href="content.html#overlayEvents">Overlay Events</a></li>
    -				
    -				<li><h4>Examples</h4></li>				
    -				<li><a target="contentFrame" href="content.html#examples">Programmatic API Examples</a></li>
    -				<li><a target="contentFrame" href="content.html#dragAndDropExamples">Draggable Connections Examples</a></li>
    -				<li><a target="contentFrame" href="content.html#utilityFunctions">Utility Functions</a></li>				
    -				
    -				<li><h4>Develop</h4></li>
    -				<li><a target="contentFrame" href="content.html#which">Which files are which?</a></li>
    -				<li><a target="contentFrame" href="content.html#pluggableLibrarySupport">Pluggable Library Support</a></li>
    -				<!-- li><a target="contentFrame" href="content.html#customConnectors">Custom Connectors</a></li>
    -				<li><a target="contentFrame" href="content.html#customEndpoints">Custom Endpoints</a></li>
    -				<li><a target="contentFrame" href="content.html#customOverlays">Custom Overlays</a></li-->
    -			</ul>
    -			<p><strong>This document refers to release 1.3.13 of jsPlumb.</strong></p>
    -			<strong>28 Aug 2012</strong>
    -		</div>		
    -	</body>
    -</html>
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.13/jsPlumbDoc.css b/demo/doc/archive/1.3.13/jsPlumbDoc.css
    deleted file mode 100644
    index f2949bd91..000000000
    --- a/demo/doc/archive/1.3.13/jsPlumbDoc.css
    +++ /dev/null
    @@ -1,127 +0,0 @@
    -body { 
    -	background-color:white; font-size:90%;
    -	background-image:url(../img/dynamicAnchorBg.jpg); 
    -}
    -/* docs */
    -.code { background-color:#e4eeee;
    -border:1px solid black; margin:1em; padding:0.8em; 
    -overflow:auto;
    -font-family:courier; 
    -font-size:8pt;
    -border-radius:0.3em;
    -}
    -.section { border-bottom: 1px dotted black; padding:1em;font-family:helvetica;background-color:white;color:#333;}
    -.menu { 
    -	position:fixed; 
    -	right:10em; top:1em;
    -	background-color:white;
    -	padding:0.3em;
    -	opacity:0.8;
    -	z-index:5000;
    -	filter:alpha(opacity=80);
    -		box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -a {text-decoration:none;color:#05b;font-weight:bold;font-family:helvetica;}
    -a:hover {text-decoration:underline;}
    -.window, .label { 
    -	cursor:pointer;
    -	box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -#explanation { 
    -	background-color:white;
    -	color:black;
    -	border:0.25em solid #ddd; 
    -	position:absolute;
    -	right:3.5em;
    -	top:5em;
    -	width:22em;
    -	font-size:100%;
    -	-moz-border-radius:1em;
    -	border-radius:1em;
    -	padding:1em;
    -	z-index:1000;
    -	opacity:0.9;
    -	filter:alpha(opacity=90); 
    -	font-family:helvetica;
    -		box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -.selected {
    -	color:rgb(189,11,11);
    -}
    -
    -
    -/*  these styles are what jsPlumb attaches to the various elements it creates.
    -._jsPlumb_connector {
    -border:1px solid blue;
    -}
    -._jsPlumb_endpoint {
    -border:1px solid green;
    -}
    -._jsPlumb_overlay {
    -border:1px solid yellow;
    -}
    -*/
    -
    -
    -.section li {
    -list-style-type:none;
    -}
    -
    -li.bullet {
    -	list-style-type:circle;
    -	}
    -
    -
    -.section {
    -padding-left:2em;
    -font-size:10pt;
    -}
    -.index.section {
    -padding-left:0;
    -}
    -
    -.index {
    -width:20em;
    -font-size:9pt;
    -float:left;
    -background-color:#e4eeee;
    -overflow:auto;
    -padding-left:0.5em;
    -}
    -
    -.index li {
    -list-style-type:none;
    -}
    -
    -.index ul, .index h3 {
    -padding-left:1.5em;
    -
    -}
    -
    -#demo {
    -width:80%;
    -height:50em;
    -margin-left:3em;
    -margin-top:3em;
    -position:relative;
    -}
    -
    -.sub {
    -margin-left:1em;
    -}
    -
    -.table td {
    -	text-align:center;
    -}
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.13/usage.html b/demo/doc/archive/1.3.13/usage.html
    deleted file mode 100644
    index 5a7dca233..000000000
    --- a/demo/doc/archive/1.3.13/usage.html
    +++ /dev/null
    @@ -1,10 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.13 - documentation</title>		
    -	</head>
    -	
    -	<frameset cols="250,*">
    -  		<frame src="index.html" />
    -  		<frame src="content.html" name="contentFrame" />
    -	</frameset>	
    -</html>
    diff --git a/demo/doc/archive/1.3.14/content.html b/demo/doc/archive/1.3.14/content.html
    deleted file mode 100644
    index 132e7ef54..000000000
    --- a/demo/doc/archive/1.3.14/content.html
    +++ /dev/null
    @@ -1,3724 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.14 - documentation</title>
    -		<link rel="stylesheet" href="/mp.css">
    -		<link rel="stylesheet" href="jsPlumbDoc.css">
    -	</head>
    -	<body>
    -
    -<div class="menu">
    -	<a href="http://jsplumb.org" class="mplink" target="_top">view demo</a>
    -	&nbsp;|&nbsp;<a href="mailto:simon.porritt@gmail.com" class="mplink">contact me</a>
    -	&nbsp;|&nbsp;<a href="http://jsplumb.tumblr.com" class="mplink">jsPlumb on Tumblr</a>	
    -</div>
    -
    -<div class="content">
    -		<div class="section">
    -			<h3><a id="summary">Summary</a></h3>
    -			jsPlumb allows you to connect elements on the screen using SVG, Canvas or VML, depending on the capabilities of the browser.
    -			<p>
    -				It can be used with jQuery, MooTools or YUI3 (or another library of your choice if you feel like implementing an adapter for it).
    -			</p>  
    -
    -			<h5>Browser Compatibility</h5>
    -			<p>jsPlumb 1.3.14 runs on everything from IE6 up.  There are some caveats, though, because of various browser/library bugs:
    -				<ul>
    -					<li class="bullet">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.
    -					<li class="bullet">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)</li>
    -					<li class="bullet">MooTools has a bug when using SVG in Firefox 11</li>
    -				<ul>
    -				
    -			</p>
    -		</div>
    -		
    -		<div class="section">		
    -			<h3><a id="changes">Changes between 1.3.14 and 1.3.13</a></h3>
    -			
    -			The main reason for the 1.3.14 release was to back out some over-zealous performance enhancements that were added to
    -			1.3.13, affecting certain usages of the repaintEverything function.
    -			
    -			<h5>Backwards Compatibility</h5>
    -			<ul>
    -				<li class="bullet">setRepaintFunction method removed from jsPlumb</li>
    -				<li class="bullet">unload method removed from jsPlumb</li>
    -				<li class="bullet">when providing a type to a constructor, what used to be an implicit setType call is now an addType call. it used to be that
    -		the type would remove any additional overlays you supplied with the constructor, because that's what setType does. but
    -		it was pointed out to me that addType was perhaps a little more accomodating.</li>
    -			</ul>
    -			
    -			<h5>New Features</h5>
    -			<ul>
    -			  <li class="bullet">Perimeter anchors now support optional 'rotation' parameter</li>	
    -			</ul>
    -
    -			<h5>Miscellaneous</h5>
    -			<ul>
    -				<li class="bullet">This release sees the removal of some overzealous performance enhancements added to 1.3.13, which affected particular
    -		usages of the repaintEverything function. Their removal does not seem to have unduly affected performance though.</li>
    -		
    -			  <li class="bullet">fix for nested draggables causing extra ids to be added throughout the DOM</li>
    -			</ul>
    -			
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="init">Setup</a></h3>
    -
    -            <h4><a id="imports">Required Imports</a></h4>
    -			<h4>jQuery</h4>
    -			    <ul>
    -				    <li class="bullet">jQuery 1.3.x or higher.</li>
    -				    <li class="bullet">jQuery UI 1.7.x or 1.8.x (if you wish to support drag and drop).</li>
    -			    </ul>
    -			    <div class="code">
    -<pre>&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/jquery-ui.min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/jquery.jsPlumb-1.3.14-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			<h4>MooTools</h4>
    -				<ul>
    -					<li class="bullet">MooTools Core 1.2.4 or higher. jsPlumb has been tested with 1.2.4 and 1.3.3.</li>
    -					<li class="bullet">Drag.Move from MooTools More 1.2.4.4 or higher (if you wish to support drag and drop).</li>
    -				</ul>
    -			<div class="code">
    -			<pre>&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/mootools/1.3.3/mootools-yui-compressed.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO_MOO_TOOLS_MORE_1_3_2_1"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/mootools.jsPlumb-1.3.14-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			<h4>YUI3</h4>
    -			<ul>
    -				<li class="bullet">YUI 3.3.x.  jsPlumb has been tested on 3.3.0 only; it is possible other 3.x.x versions will work.</li>
    -			</ul>
    -			<div class="code">
    -			<pre>&lt;script type="text/javascript" src="http://yui.yahooapis.com/3.3.0/build/simpleyui/simpleyui-min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/yui.jsPlumb-1.3.14-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			
    -			<h4><a id="renderMode">Render Mode</a></h4>
    -			jsPlumb can use SVG, HTML5 Canvas elements or VML to render the various objects it adds to the display.  Most modern browsers support Canvas and SVG; IE&lt;9 browsers support neither.
    -			<p>
    -				By default, jsPlumb attempts to use <strong>jsPlumb.SVG</strong> as the render mode (prior to 1.3.4, the default renderer was Canvas), falling back to <strong>jsPlumb.VML</strong> if the 
    -				browser is IE&lt;9.  You can change the render mode by making this call:  
    -			</p>
    -			<div class="code">
    -<pre>jsPlumb.setRenderMode(jsPlumb.CANVAS);</pre>			
    -			</div>
    -			if, for some reason, you set the render mode to be <strong>jsPlumb.VML</strong> but you're in any browser other than IE, jsPlumb will use SVG.
    -			<p>
    -			The <strong>jsPlumb.setRenderMode</strong> method returns you the render mode that jsPlumb actually ended up setting.  Valid values for setRenderMode are:
    -			</p>
    -			<ul>
    -				<li class="bullet">jsPlumb.CANVAS</li>
    -				<li class="bullet">jsPlumb.SVG</li>
    -				<li class="bullet">jsPlumb.VML</li>
    -			</ul>			
    -			<h5>Performance Considerations</h5>
    -			<p>
    -			SVG uses less memory than Canvas and has support for mouse events built in, so I recommend using
    -			SVG unless you have a really good reason for wanting Canvas. Of course, you might consider that horrendous Safari bug a really good reason for wanting Canvas.
    -			</p>
    -			
    -			<h4><a id="initializing">Initializing jsPlumb</a></h4>
    -			You should not start making calls to jsPlumb until the DOM has been initialized - perhaps no surprises there.  With YUI, though, the asynchronous
    -			nature of the script loading process means that you are not guaranteed that jsPlumb is ready as soon as the DOM is.  To handle this,
    -			you should bind to the "ready" event on jsPlumb (or the instance of jsPlumb you are working with):
    -			<div class="code">
    -			<pre>jsPlumb.bind("ready", function() {
    -...	
    -		
    -// your jsPlumb related init code goes here
    -
    -...
    -
    -});						
    -			</pre>
    -			</div>
    -			There's a helper method that can save you approximately 9 (count them!) characters:
    -			<div class="code">
    -			<pre>jsPlumb.ready(function() {
    -...	
    -		
    -// your jsPlumb related init code goes here
    -
    -...
    -
    -});						
    -			</pre>
    -			</div>
    -			<p>
    -			For jQuery and MooTools you do not actually need to do this; by the time the DOM ready event is fired in each of those libraries you can be sure 
    -			all the JS we need has been loaded.  But in terms of explicitly calling out that you are waiting for jsPlumb, it seems like a good practice to bind to the "ready" event.
    -			</p>
    -			<p>
    -			If you bind to the "ready" event after jsPlumb has already been initialized, your callback will be executed immediately.
    -			</p>			
    -
    -				
    -<h4><a id="multipleInstances">Multiple jsPlumb instances</a></h4>
    -			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 <strong>getInstance</strong> method, for example:
    -<div class="code">
    -<pre>
    -var firstInstance = jsPlumb.getInstance();
    -</pre>
    -</div>
    -The variable 'firstInstance' can now be treated exactly as you would treat the 'jsPlumb' variable - you can set
    -defaults, call the connect method, whatever:
    -<div class="code">
    -<pre>
    -firstInstance.importDefaults({
    -  Connector : [ "Bezier", { curviness: 150 } ],
    -  Anchors : [ "TopCenter", "BottomCenter" ]
    -});
    -
    -firstInstance.connect({source:"element1", target:"element2", scope:"someScope" });
    -</pre>
    -</div>
    -
    -<strong>getInstance</strong> optionally takes an object that provides the defaults:
    -<div class="code">
    -<pre>
    -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" });
    -</pre>
    -</div>
    -
    -		<h4><a id="zIndex">Z-index Considerations</a></h4>
    -		<p>
    -			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. 
    -		</p>  
    -		<p>
    -			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).  			
    -		</p>
    -		<p>
    -			To help you organise z-indices correctly, jsPlumb adds a CSS class to each type of
    -			element it adds. They are as follows:			
    -		</p>
    -		<table style="color:black;width:70%;font-size:90%;">
    -			<tr><td><strong>type of element</strong></td><td><strong>class added</strong></td></tr>
    -			<tr><td>Endpoint</td><td>_jsPlumb_endpoint</td></tr>
    -			<tr><td>Connector</td><td>_jsPlumb_connector</td></tr>
    -			<tr><td>Overlay</td><td>_jsPlumb_overlay</td></tr>
    -		</table>
    -		
    -		<h5>Automatic z-index for Connections</h5>
    -		A common use case is the need to bring hovered Connections up on top of the other connections in your UI. From
    -		1.3.14 onwards, jsPlumb offers a way to do this:
    -		<div class="code">
    -<pre>
    -  jsPlumb.importDefaults({
    -    ...
    -    ConnectorZIndex:5
    -	...
    -  });
    -</pre>			
    -		</div>
    -		This tells jsPlumb to set the z-index of all Connections to be 5, and the z-index of any hovered Connections will
    -		be, in this case, 6.  You could argue that jsPlumb could figure out the z-index automatically, but z-index selection
    -		is typically a conscious decision in a webapp, and this approach formalises things a little better than trying
    -		to infer what the value is supposed to be.
    -		<p>To achieve this, there is actually a <strong>setZIndex</strong> method on Connections now, which is perfectly fine
    -		to call, but remember that if you change the z-index of some Connection, hover over it, and then mouse exit, resetting
    -		the hover state will mean that jsPlumb resets the z-index to the default value discussed above.</p>
    -
    -		<h4><a id="containerDefault">Where does jsPlumb add elements?</a></h4>
    -		<p>
    -			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.
    -		</p>
    -		<p>
    -			Prior to 1.3.0, jsPlumb added everything to the end of the <em>body</em> element. This
    -			has the advantage of being the most flexible arrangement in terms of supporting what
    -			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!   
    -		</p>
    -		<p>
    -			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 (<strong>not</strong> 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.
    -		</p>
    -		
    -		<p>
    -			In general, the default behaviour means that you cannot do things like this:
    -		</p>
    -		<div class="code">
    -<pre>
    -&lt;body&gt;
    -	&lt;div id="container0"&gt;
    -		&lt;div id="node0"&gt;&lt;/div&gt;
    -	&lt;/div&gt;
    -	&lt;div id="container1"&gt;
    -		&lt;div id="node1"&gt;&lt;/div&gt;
    -	&lt;/div&gt;
    -&lt;/body&gt;
    -
    -&lt;script type="text/javascript"&gt;
    -
    -	var e0 = jsPlumb.addEndpoint("node0"),
    -	e1 = jsPlumb.addEndpoint("node1");
    -
    -	jsPlumb.connect({ source:e0, target:e1 });
    -&lt;/script&gt;
    -</pre>		
    -		</div>
    -<p>
    -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):  
    -</p>
    -		<div class="code">
    -<pre>
    -&lt;body&gt;
    -	&lt;div id="container0"&gt;
    -	&lt;/div&gt;
    -	&lt;div id="container1"&gt;
    -	&lt;/div&gt;
    -&lt;/body&gt;
    -
    -&lt;script type="text/javascript"&gt;
    -
    -	var e0 = jsPlumb.addEndpoint("container0"),
    -	e1 = jsPlumb.addEndpoint("container1");
    -
    -	jsPlumb.connect({ source:e0, target:e1 });
    -&lt;/script&gt;
    -</pre>		
    -		</div>
    -	now 'e0' and 'e1' have the same parent - the <em>body</em> element. 
    -	
    -	<p>Remember that you can use the <strong>anchor</strong> parameter to an <strong>addEndpoint</strong>
    -	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. 
    -	</p>									
    -	<p><strong>Note regarding the drawEndpoints option on jsPlumb.connect</strong>: 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). 
    -	</p>
    -	
    -	<h5>Overriding the default behaviour</h5>
    -	<p>
    -From version 1.3.3, the <strong>container</strong> 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 <strong>jsPlumb.Defaults.Container</strong> property, or you can set a <strong>container</strong> parameter on calls to <strong>addEndpoint</strong> or <strong>connect</strong> (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:
    -	</p>
    -		
    -Set a container to use as the default container, using a jQuery selector (you can supply MooTools/YUI selectors if that's the library you're using), and then add an Endpoint.  The canvas (or SVG/VML element) created will be a child of the document body:
    -		<div class="code">
    -<pre>
    -jsPlumb.Defaults.Container = $("body");
    -...
    -jsPlumb.addEndpoint(someDiv, { endpoint options });
    -</pre>		
    -		</div>
    -		
    -			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":
    -		<div class="code">
    -<pre>
    -jsPlumb.Defaults.Container = "containerId";
    -...
    -jsPlumb.connect({ source:someDiv, target:someOtherDiv });
    -</pre>		
    -		</div>
    -		
    -		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":
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({ source:someDiv, target:someOtherDiv, container:$("#containerId") });
    -</pre>		
    -		</div>
    -				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":
    -		<div class="code">
    -<pre>
    -jsPlumb.addEndpoint(someDiv, { ..., container:"containerId" });
    -</pre>		
    -		</div>
    -
    -		<h4><a id="elementDragging">Element Dragging</a></h4>
    -		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 <strong>jsPlumb.draggable(...)</strong>:
    -<div class="code">
    -<pre>jsPlumb.draggable("elementId");</pre>
    -</div>
    -You can also pass a selector from your library in to this method:
    -<div class="code">
    -<pre>jsPlumb.draggable($(".someClass");</pre>
    -</div>
    -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.  A common requirement when using jQuery, for instance, is to set the containment of elements that you are making draggable.  Here's how you would tell jsPlumb to initialise some set of elements to be draggable but only inside their parent:
    -<div class="code">
    -<pre>jsPlumb.draggable($(".someClass", {
    -	containment:"parent"
    -});</pre>
    -</div>
    -		<h5>Dragging nested elements</h5>
    -jsPlumb takes nesting into account when handling draggable elements. For example, say you have this markup:
    -<div class="code">
    -<pre>
    -&lt;div id="container"&gt;
    -  &lt;div class="childType1"&gt;&lt;/div&gt;
    -  &lt;div class="childType2"&gt;&lt;/div&gt;
    -&lt;/div&gt;
    -</pre>
    -</div>
    -..and then you connect one of those child divs to something, and make "container" draggable:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:$("#container .childType1"),
    -	target:"somewhere else"
    -});
    -
    -jsPlumb.draggable("container");
    -</pre>
    -</div>
    -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.
    -
    -
    -		</div>
    -				
    -		<div class="section">
    -			<h3><a id="jsPlumbBasics">Basic Concepts</a></h3>			
    -	
    -			jsPlumb is all about connecting things together, so the core abstraction in jsPlumb is the <strong>Connection</strong> object, which is itself
    -			broken down into these four concepts:
    -
    -			<ul>
    -				<li><strong>Anchor</strong> - a location, relative to an element's origin, at which an Endpoint can exist. You do not create these yourself; you 
    -				supply hints to the various jsPlumb functions, which create them as needed.  They have no visual representation; they are a logical position only. Anchors can be referenced by name, for the Anchors that jsPlumb ships with, or a four element array describing [ x, y, x orientation, y orientation ]. 
    -				See the <a href="#anchors">anchors</a> section for more detail.<br/><br/></li>
    -				<li><strong>Endpoint</strong> - the visual representation of one end of a Connection.  You can create and attach these to elements yourself, which you are required to do to support drag and drop, or have jsPlumb create them when creating a Connection programmatically using jsPlumb.connect(...).  You can also join two Endpoints programmatically, by passing them as arguments to jsPlumb.connect(...).<br/><br/></li>				
    -				<li><strong>Connector</strong> - the visual representation of the line connecting two elements in the page.  jsPlumb has four types of these 
    -				available as defaults - a Bezier curve, a straight line, 'flowchart' connectors and 'state machine connectors. You do not interact with Connectors; 
    -				you just specify definitions of them when you need to - see <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions</a> <br/><br/></li>
    -				<li><strong>Overlay</strong> - a UI component that is used to decorate a Connector, such as a Label, Arrow, etc.<br/><br/></li>
    -			</ul>
    -						
    -			<p>One Connection is made up of two Endpoints, a Connector, and zero or more Overlays working together to join two elements.</p>
    -			<p>jsPlumb's public API exposes only Connection and Endpoint, handling the creation and configuration of everything else internally. But you still need to be across the concepts encapsulated by Anchor, Connector and Overlay.
    -			</p>
    -			</div>
    -			
    -			<div class="section">
    -			<h3><a id="anchors">Anchors</a></h3>			
    -	
    -			An Anchor models the notion of where on an element a Connector should connect.  There are three main types of Anchors:
    -			<ul>
    -			<li/>
    -				<li class="bullet">'Static' anchors - these are fixed to some point on an element and do not move.  They can be specified using a string to
    -				identify one of the defaults that jsPlumb ships with, or an array describing the location (see below)<br/><br/></li>				
    -				<li class="bullet">'Dynamic' anchors - these are lists of Static anchors from which jsPlumb picks the most appropriate one each time a Connection
    -				is painted.  The algorithm used to determine the most appropriate anchor picks the one that is closest to the center of the other element in
    -				the Connection. A future version of jsPlumb might support a pluggable algorithm to make this decision.<br/><br/></li>
    -				<li class="bullet">'Perimeter' anchors - these are anchors that follow the perimeter of some given shape. They are, in essence, Dynamic
    -				anchors whose locations are chosen from the perimeter of the underlying shape.<br/><br/></li>
    -				<li class="bullet">'Continuous' anchors - These anchors, introduced in 1.3.4, are not fixed to any specific loction; they are assigned to one of
    -				the four faces of an element depending on that element's orientation to the other element in the associated Connection.  Continuous anchors are slightly more computationally intensive than Static or Dynamic anchors because jsPlumb is required to calculate the position of every Connection during a paint cycle, rather than just Connections belonging to the element in motion.  These anchors were designed for use with the StateMachine connector that was also introduced in 1.3.4, but will work with any combination of Connector and Endpoint.<br/><br/></li> 
    -			</ul>  
    -					
    -			<h4><a id="staticAnchors">Static Anchors</a></h4>		
    -			jsPlumb has nine default anchor locations you can use to specify where the Connectors connect to elements: these are the four corners of an element,
    -			the center of the element, and the midpoint of each edge of the element:  
    -			<ul>
    -				<li class="bullet">TopCenter</li>
    -				<li class="bullet">TopRight</li>
    -				<li class="bullet">RightMiddle</li>
    -				<li class="bullet">BottomRight</li>
    -				<li class="bullet">BottomCenter</li>
    -				<li class="bullet">BottomLeft</li>
    -				<li class="bullet">LeftMiddle</li>
    -				<li class="bullet">TopLeft</li>
    -			</ul>
    -			
    -Each of these string representations is just a wrapper around the underlying array-based syntax  [x, y, dx, dy], where x and y are coordinates in the interval [0,1] specifying the position of the
    -anchor, and dx and dy are coordinates in the interval [-1, 1] specifying the orientation of the curve incident to the anchor. For example, [0, 0.5, -1, 0] defines a "LeftCenter" anchor with a connector curve that emanates leftward from the anchor. Similarly, [0.5, 0, 0, -1] defines a "CenterTop" anchor with a connector curve emanating upwards. 	
    -
    -
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:"BottomCenter", ... }); 
    -</pre>
    -</div>
    -is identical to:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:[ 0.5, 1, 0, 1 ], ... }); 
    -</pre>
    -</div>		
    -In addition to supplying the location and orientation of an anchor, you can optionally supply two more parameters that define an offset in pixels from the given location.  Here's the anchor specified above, but with a 50 pixel offset below the element in the y axis:	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:[ 0.5, 1, 0, 1, 0, 50 ], ... }); 
    -</pre>
    -</div>		
    -
    -		<h4><a id="dynamicAnchors">Dynamic Anchors</a></h4>
    -	These are Anchors that can be positioned in one of a number of locations, choosing the one that is most appropriate each time something moves or is painted in the UI.
    -	<p>
    -	There is no special syntax to creating a DynamicAnchor; you just provide an array of individual Static Anchor specifications, eg:
    -	</p>
    -<div class="code">
    -<pre>
    -var dynamicAnchors = [ [ 0.2, 0, 0, -1 ],  [ 1, 0.2, 1, 0 ], 
    -			   [ 0.8, 1, 0, 1 ], [ 0, 0.8, -1, 0 ] ];
    -
    -jsPlumb.connect({...., anchor:dynamicAnchors, ... }); 			   
    -</pre>
    -</div>
    -<h4>	Default Dynamic Anchor</h4>
    -jsPlumb provides a dynamic anchor called "AutoDefault" that chooses from TopCenter, RightMiddle, BottomCenter and LeftMiddle:
    -<div class="code">
    -<pre>jsPlumb.connect({...., anchor:"AutoDefault", ... });</pre>
    -</div>
    -
    -<h4>	Location Selection</h4>
    -The initial implementation of the algorithm that decides which location to choose just calculates which location is closest to the center of the 
    -other element in the connection.  It is possible that future versions of jsPlumb could support more sophisticated choice algorithms, if the need arose.
    -<h4>Draggable Connections</h4>
    -Dynamic Anchors and Draggable Connections can interoperate: jsPlumb locks the position of a dynamic anchor when you start to drag a connection from it,
    -and unlocks it once the connection is either established or abandoned. At that point you may see the position of the dynamic anchor change, as jsPlumb
    -optimises the connection.  <p>You can see this behaviour in the <a href="../html/jquery/draggableConnectorsDemo.html" target="_blank">draggable connections</a> demonstration, when
    -you drag a connection from the blue endpoint on window 1 to the blue endpoint on window 3 - the connection is established and then window 1's blue
    -endpoint jumps down to a location that is closer to window 3.</p>
    -
    -	<h4><a id="perimeterAnchors">Perimeter Anchors</a></h4>	
    -These are a form of Dynamic anchor in which the anchor locations are chosen from the perimeter of some given shape. jsPlumb
    -supports six shapes:
    -
    -<ul>
    -	<li class="bullet">circle</li>
    -	<li class="bullet">ellipse</li>
    -	<li class="bullet">triangle</li>
    -	<li class="bullet">diamond</li>
    -	<li class="bullet">rectangle</li>
    -	<li class="bullet">square</li>	
    -</ul>
    -
    -'Rectangle' and 'Square', are not, strictly speaking, necessary, since rectangular shapes are the norm in a web page. But they
    -are included for completeness.
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("someElement", {
    -	endpoint:"Dot",
    -	anchor:[ "Perimeter", { shape:"circle" } ]
    -});
    -</pre>
    -</div>
    -
    -In this example our anchor will travel around the path inscribed by a circle whose diameter is the width and height of the
    -underlying element. Note that the 'circle' shape is therefore identical to 'ellipse', since it is assumed the underlying
    -element will have equal width and height, and if it does not, you will get an ellipse. 'rectangle' and 'square' have the
    -same relationship.
    -<p>By default, jsPlumb approximates the perimeter with 60 anchor locations.  You can change this, though:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("someDiv", {
    -	endpoint:"Dot",
    -	anchor:[ "Perimeter", { shape:"square", anchorCount:150 }]
    -});
    -</pre>		
    -</div>
    -Obviously, the more points the smoother the operation. But also the more work your browser has to do.
    -<p>'triangle' and 'diamond' both assume that there is a point at the top center of the shape. This may or may not turn
    -out to be acceptable for all applications, in which case it might be possible to add support for a 'rotation' argument to the
    -anchor constructor.</p>
    -Here's a triangle and diamond example, just for kicks:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:"Dot",
    -	anchors:[
    -		[ "Perimeter", { shape:"triangle" } ],
    -		[ "Perimeter", { shape:"diamond" } ]
    -	]
    -});
    -</pre>		
    -</div>
    -<h5>Perimeter Anchor Rotation</h5>
    -You can supply a 'rotation' value to a Perimeter anchor - an example can be seen in <a href="../jquery/perimeterAnchorsDemo.html" target="_blank">this demo</a>. Here's how you would use it:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:"Dot",
    -	anchors:[
    -		[ "Perimeter", { shape:"triangle", rotation:25 } ],
    -		[ "Perimeter", { shape:"triangle", rotation:-335 } ]
    -	]
    -});  
    -</pre>
    -</div>
    -
    -Note that the value must be supplied <strong>in degrees</strong>, not radians, and the number may be either positive or negative. In the example above, both triangles are of course rotated by the same amount.
    -
    -	<h4><a id="continuousAnchors">Continuous Anchors</a></h4>
    -	As discussed above, these are anchors whose positions are calculated by jsPlumb according to the orientation between elements in a Connection, and also how
    -	many other Continuous anchors happen to be sharing the element.  You specify that you want to use Continuous anchors using the string syntax you would use
    -	to specify one of the default Static Anchors, for example:
    -	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:someDiv,
    -	target:someOtherDiv,
    -	anchor:"Continuous"
    -});
    -</pre>
    -</div>
    -
    -Note in this example I specified only "anchor", rather than "anchors" - jsPlumb will use the same spec for both anchors.  But I could have said this:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:someDiv,
    -	target:someOtherDiv,
    -	anchors:["BottomCenter", "Continuous"]
    -});
    -</pre>
    -</div>	 	  
    -..which would have resulted in the source element having a Static Anchor at BottomCenter.  In practise, though, it seems the Continuous Anchors work best if both 
    -elements in a Connection are using them.
    -<p>Note also that Continuous Anchors can be specified on <strong>addEndpoint</strong> calls:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint(someDiv, {
    -	anchor:"Continuous",
    -	paintStyle:{ fillStyle:"red" }
    -});
    -</pre>
    -</div>	 	
    -...and in <strong>makeSource</strong>/<strong>makeTarget</strong>:
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource(someDiv, {
    -	anchor:"Continuous",
    -	paintStyle:{ fillStyle:"red" }
    -});
    -
    -jsPlumb.makeTarget(someDiv, {
    -	anchor:"Continuous",
    -	paintStyle:{ fillStyle:"red" }
    -});
    -</pre>
    -</div>	 
    -... and in the jsPlumb defaults:
    -<div class="code">
    -<pre>
    -jsPlumb.Defaults.Anchor = "Continuous";
    -</pre>
    -</div>  
    -					</div>		
    -												
    -<div class="section">
    -		<h4><a id="definitions">Connector, Endpoint &amp; Overlay Definitions</a></h4>
    -Before we discuss Connector, Endpoints and Overlays, a quick word on definitions:  whenever you need to define a Connector, Endpoint or Overlay, you must use a "definition" of it, rather than constructing one directly.  This definition can be either a string that nominates the artifact you want to create:
    -		<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:"Rectangle"
    -});
    -</pre>
    -		</div>
    -		or an array consisting of both the artifact's name and the arguments you want to pass to its constructor:
    -		<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Rectangle", { cssClass:"myEndpoint", width:30, height:10 } ]
    -});
    -</pre>
    -		</div>
    -There is also a three-argument method that allows you to specify two sets of parameters, which jsPlumb will merge together for you. The idea behind this is that you will often want to define common characteristics somewhere and reuse them across a bunch of different calls:
    -		<div class="code">
    -<pre>var common = {
    -	cssClass	:	"myCssClass",
    -	hoverClass	:	"myHoverClass"
    -};
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Rectangle", { width:30, height:10 }, common ]
    -});
    -</pre>
    -		</div>
    -	This syntax is supported for all Endpoint, Connector and Overlay definitions.  Here's an example using definitions for all three:	
    -		<div class="code">
    -<pre>var common = {
    -	cssClass:"myCssClass"
    -};
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Dot", { radius:5, hoverClass:"myEndpointHover" }, common ],
    -	connector:[ "Bezier", { curviness:100 }, common ],
    -	overlays: [
    -		[ "Arrow", { foldback:0.2 }, common ],
    -		[ "Label", { cssClass:"labelClass" } ]	
    -	]
    -});
    -</pre>
    -		</div>		
    -The allowed constructor parameters are different for each artifact you create, but every artifact takes a single JS object as argument, with the parameters as [key,value] pairs in that object.  See the relevant sections on <a href="#connectors">Connectors</a>, <a href="#endpoints">Endpoints</a> and <a href="#overlays">Overlays</a> for information on supported parameters.</a>
    -				
    -	</div>
    -
    -		<div class="section">
    -			<h3><a id="connectors">Connectors</a></h3>
    -			<p>Connectors are the lines that actually join elements of the UI.  jsPlumb has four connector implementations - a straight line, a Bezier curve, "flowchart", and "state machine".  The default connector is the Bezier curve.</p>
    -			<p>You optionally specify a Connector by setting the 'connector' property on a call to jsPlumb.connect, jsPlumb.addEndpoint(s), jsPlumb.makeSource or jsPlumb.makeTarget. If you do not supply a value for 'connector', the default will be used (see <a href="#defaults">defaults</a>).</p>
    -			<p>You specify Connectors using the syntax described above in <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions.</a> Allowed constructor values for each Connector type are described below:</p>		
    -			<h4><a id="bezierConnector">Bezier Connector</a></h4>
    -			<p>The Bezier Connector provides a cubic Bezier path between the two Endpoints. It supports a single constructor argument:</p>
    -			<p>
    -			<strong>curviness</strong> - Optional; defaults to 150. This defines the distance in pixels that
    -			the Bezier's control points are situated from the anchor points.  This does not mean that your
    -			connector will pass through a point at this distance from your curve.  It is a hint to how you want the
    -			curve to travel. Rather than discuss Bezier curves at length here, because they are a complex topic,
    -			refer instead to <a href="http://en.wikipedia.org/wiki/B%C3%A9zier_curve" target="_blank">Wikipedia.</a>
    -			</p>
    -			<h4><a id="straightConnector">Straight Connector</a></h4>
    -			<p>The Straight Connector draws a straight line between the two endpoints. No constructor arguments are supported; use the <strong>endpointStyle</strong> and/or <strong>cssClass</strong>/<strong>connectorClass</strong> to a <strong>connect</strong>, <strong>addEndpoint</strong>, <strong>makeSource</strong> or <strong>makeTarget</strong> call to control the appearance of one of these Connectors.  
    -			</p>
    -			<h4><a id="flowchartConnector">Flowchart Connector</a></h4>
    -			<p>This type of Connector draws a connection that consists of a series of vertical or horizontal segments - the classic flowchart look. Two constructor arguments are supported:</p>
    -			<strong>stub</strong> - this is the minimum length, in pixels, of the initial stub that emanates from an Endpoint.  This is an optional parameter, and can be either an integer, which specifies the stub fo each end of the connector, or an array of two integers, specifying the stub for the [source, target] endpoints in the connection.  This parameter is optional, and defaults to an integer with value 30 pixels.<br/>
    -			<strong>gap</strong> - optional, defaults to 0 pixels. Lets you specify a gap between the end of the connector and the elements to which it is attached.
    -			<p>This Connector supports Connection that start and end on the same element.</p>
    -			<h4><a id="stateMachineConnector">State Machine Connector</a></h4>
    -			<p>This Connector draws slightly curved lines (they are actually quadratic Bezier curves), similar to the State Machine connectors you may have seen in software like GraphViz.  Connections in which some element is both the source and the target (so-called 'loopback') are supported by these Connectors (as they are with Flowchart Connectors); in this case you get a circle. Supported parameters are:
    -			</p>
    -			<p>
    -			<strong>margin</strong> - Optional; defaults to 5.  Defines the distance from the element that the connector begins/ends.
    -			</p>
    -			<p>
    -			<strong>curviness</strong> - Optional, defaults to 10.  This has a similar effect to the curviness parameter on Bezier curves.
    -			</p>
    -			<p>
    -			<strong>proximityLimit</strong> - Optional, defaults to 80.  The minimum distance between the two ends of the Connector before it paints itself as 
    -			a straight line rather than a quadratic Bezier curve.
    -			</p>    
    -			
    -		</div>
    -		
    -			<div class="section">
    -			<h3><a id="endpoints">Endpoint Types</a></h3>
    -			<p>An Endpoint is the UI component that marks the location of an Anchor, ie. the place where a Connector joins an
    -			element.  jsPlumb comes with four Endpoint implementations - <strong>Dot</strong>, <strong>Rectangle</strong>, <strong>Blank</strong> and <strong>Image</strong>. You optionally specify Endpoint properties using the
    -			<strong>endpoint</strong> parameter in a call to jsPlumb.connect, jsPlumb.addEndpoint, jsPlumb.makeSource or jsPlumb.makeTarget.
    -			</p>  
    -				<p>As with Connectors and Overlays, you specify Endpoints using the syntax described above in <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions.</a></p>
    -			
    -			The four available Endpoint types, and their constructor arguments, are as follows:
    -					<h4><a id="dotEndpoint">Dot Endpoint</a></h4>
    -					This Endpoint draw a dot on the screen. It supports three constructor parameters:
    -					<p>
    -						<strong>radius</strong> - Optional; defaults to 10 pixels. Defines the radius of the dot.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="rectangleEndpoint">Rectangle Endpoint</a></h4>
    -						Draws a rectangle. Supported constructor parameters are:
    -					<p>
    -						<strong>width</strong> - Optional; defaults to 20 pixels. Defines the width of the rectangle.
    -					</p>
    -					<p>
    -						<strong>height</strong> - Optional; defaults to 20 pixels. Defines the height of the rectangle.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="imageEndpoint">Image Endpoint</a></h4>
    -						Draws an image from a given URL.  This Endpoint supports three constructor parameters:  
    -					<p>
    -						<strong>src</strong> - Required.  Specifies the URL of the image to use.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="blankEndpoint">Blank Endpoint</a></h4>
    -						Does not draw anything visible to the user.  As discussed previously, this Endpoint is probably not what you want if you need your users to be able to drag existing Connections - for that, use a Rectangle or Dot Endpoint and assign to it a CSS class that causes it to be transparent.
    -																			
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="overlays">Overlay Types</a></h3>
    -			<p>
    -			Overlays are UI elements that are painted onto connections, such as labels or arrows.
    -			jsPlumb comes with four defaults:
    -			</p>
    -			<ul>
    -				<li><strong>Arrow</strong> - a configurable arrow that is painted at some point along the connector.  You can control the length and width of the Arrow, the 'foldback' point - a point the tail points fold back into, and the direction (allowed values are 1 and -1; 1 is the default and means point in the direction of the connection)</li>
    -				<li><strong>Label</strong> - a configurable label that is painted at some point along the connector.</li>
    -				<li><strong>PlainArrow</strong> - an Arrow shaped as a triangle, with no foldback.</li>
    -				<li><strong>Diamond</strong> - as the name suggests, a diamond.</li>
    -			</ul> 
    -			The last two are actually just configured instances of the generic Arrow overlay (see examples). 
    -			
    -			<p>All Overlays support these two methods for getting/setting their location (location is either a number between 0 and 1 inclusive, indicating some point along the path inscribed by the associated Connector, or an absolute number of pixels, where negative values mean distance from the target of the connection):</p>
    -			<ul>
    -				<li class="bullet"><strong>getLocation</strong> - returns the current location</li>
    -				<li class="bullet"><strong>setLocation</strong> - sets the current location; valid values are decimals in the range 0-1 inclusive, or an integer less than 0 (to express distance from the target), or an integer greater than 1 (to express distance from the source)</li> 
    -			</ul>
    -			
    -			<p>You can specify one or more overlays when making a call to jsPlumb.connect, jsPlumb.addEndpoint or jsPlumb.makeSource (but not jsPlumb.makeTarget; overlays
    -			are always derived from what the source of a Connection defines)  The three cases are discussed below:</p>
    -			1. Specifying one or more overlays on a jsPlumb.connect call. In this example we'll create an Arrow with the default options for an Arrow, and
    -			a label with the text "foo":
    -			<div class="code">
    -			<pre>jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});			
    -</pre>
    -			</div>
    -			This connection will have an arrow located halfway along it, and the label "foo" one quarter of the way along.  Notice the <strong>id</strong>
    -			parameter; it can be used later if you wish to remove the Overlay or change its visibility (see below).<br/><br/> 
    -			Another example, this time with an absolute location of 50 pixels from the source:
    -			<div class="code">
    -			<pre>jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:50 }, id:"myLabel" ]
    -	],
    -	...
    -});			
    -</pre>
    -			</div>
    -			
    -			2. Specifying one or more overlays on a jsPlumb.addEndpoint call.  Note in this example that we use the parameter 'connectorOverlays' and not
    -			'overlays' as in the last example.  This is because 'overlays' would refer to Endpoint Overlays:
    -			<div class="code">
    -			<pre>jsPlumb.addEndpoint("someDiv", {
    -	...
    -	connectorOverlays:[ 
    -		[ "Arrow", { width:10, length:30, location:1, id:"arrow" } ], 
    -		[ "Label", { label:"foo", id:"label" } ]
    -	],
    -	...
    -});</pre>
    -			</div>	
    -			This connection will have a 10x30 Arrow located right at the head of the connection, and the label "foo" located at the halfway point. Again,
    -			note that <strong>id</strong> parameter passed in to each Overlay spec.<br/><br/>
    -			
    -			3. Specifying one or more overlays on a jsPlumb.makeSource call.  Note in this example that we again use the parameter 'connectorOverlays' and not
    -			'overlays'.  The 'endpoint' parameter to jsPlumb.makeSource supports everything you might pass to the second argument of a jsPlumb.addEndpoint call:
    -			<div class="code">
    -			<pre>jsPlumb.makeSource("someDiv", {
    -	...
    -	endpoint:{
    -		connectorOverlays:[ 
    -			[ "Arrow", { width:10, length:30, location:1, id:"arrow" } ], 
    -			[ "Label", { label:"foo", id:"label" } ]
    -		]
    -	}
    -	...
    -});</pre>
    -			</div>	
    -			This connection will have a 10x30 Arrow located right at the head of the connection, and the label "foo" located at the halfway point. Again,
    -			note that <strong>id</strong> parameter passed in to each Overlay spec.<br/><br/>
    -			
    -			 	
    -					<h4><a id="arrowOverlay">Arrow Overlay</a></h4>
    -					<p>This overlay draws an arrow, using four points: the head and two tail points, and a 'foldback' point, which permits the tail
    -					of the arrow to be indented. Available constructor arguments for this Overlay are </p>
    -					<ul>
    -					<li><strong>width</strong> - width of the tail of the arrow</li>
    -					<li><strong>length</strong> - distance from the tail of the arrow to the head</li>
    -					<li><strong>location</strong> - where, either as a proportional value from 0 to 1 inclusive, or as an absolute value (negative values mean distance from target; positive values greater than 1 mean distance from source) the Arrow should appear on the Connector</li>
    -					<li><strong>direction</strong> - which way to point. Allowed values are 1 (the default, meaning forwards) and -1, meaning backwards </li>
    -					<li><strong>foldback</strong> - how far along the axis of the arrow the tail points foldback in to. Default is 0.623.</li>
    -					<li><strong>paintStyle</strong> - a style object in the form used for paintStyle values for Endpoints and Connectors</li>
    -					</ul>
    -					<h4><a id="plainArrowOverlay">PlainArrow Overlay</a></h4>
    -					<p>This is just a specialized instance of Arrow in which jsPlumb hardcodes 'foldback' to 1, meaning the tail of the Arrow is a flat edge.  All of the constructor parameters from Arrow apply for PlainArrow.</p>
    -					<h4><a id="diamondOverlay">Diamond Overlay</a></h4>
    -					<p>This is a specialized instance of Arrow in which jsPlumb hardcodes 'foldback' to 2, meaning the Arrow turns into a Diamond.  All of the constructor parameters from Arrow apply for Diamond.</p>
    -					<h4><a id="labelOverlay">Label Overlay</a></h4>
    -					This provides a text label to decorate Connectors with.  The available constructor arguments are:
    -					<ul>
    -						<li><strong>label</strong> - The text to display.  You can provide a function here instead of plain text: it is passed the Connection as an argument, and it should return a String.</li>
    -						<li><strong>cssClass</strong> - Optional css class to use for the Label.  This is now preferred over using the 'labelStyle' parameter.</li>
    -						<li><strong>labelStyle</strong> - (deprecated, use cssClass instead) Optional arguments for the label's appearance.  Valid entries in this JS object are:
    -							<ul>
    -								<li><em>font</em> - a font string in a format suitable for the Canvas element</li>
    -								<li><em>fillStyle</em> - the color to fill the label's background with. Optional.</li>
    -								<li><em>color</em> - the color of the label's text. Optional.</li>
    -								<li><em>padding</em> - optional padding for the label. This is expressed as a proportion of the width of the label, not in pixels or ems.</li>
    -								<li><em>borderWidth</em> - optional width in pixels for the label's border. Defaults to 0.</li>
    -								<li><em>borderStyle</em> - optional. The color to paint the border, if there is one.</li>
    -								
    -							</ul>
    -						
    -						</li>
    -						<li><strong>location</strong> - As for Arrow Overlay.  Where, either proportionally from 0 to 1 inclusive, or as an absolute offset from either source or target, the label should appear.</li>
    -					</ul>
    -
    -					The Label overlay offers two methods - getLabel and setLabel - for accessing/manipulating its content dynamically:
    -<div class="code">
    -<pre>
    -var c = jsPlumb.connect({
    -	source:"d1", 
    -	target:"d2", 
    -	overlays:[
    -		[ "Label", {label:"FOO", id:"label"}]
    -	]	
    -});					
    -
    -...
    -
    -var label = c.getOverlay("label");
    -console.log("Label is currently", label.getLabel());
    -label.setLabel("BAR");
    -console.log("Label is now", label.getLabel());
    -</pre>
    -</div>
    -In this example you can see that the Label Overlay is assigned an id of "label" in the connect call, and then retrieved using that id in the call to Connection's getOverlay method.
    -<p>Both Connections and Endpoints support Label Overlays, and because changing labels is quite a common operation, <strong>setLabel</strong> and <strong>getLabel</strong> methods have been added to these objects:
    -<div class="code">
    -<pre>
    -var conn = jsPlumb.connect({
    -	source:"d1", 
    -	target:"d2",
    -	label:"FOO"
    -});					
    -
    -...
    -
    -console.log("Label is currently", conn.getLabel());
    -conn.setLabel("BAR");
    -console.log("Label is now", conn.getLabel());
    -</pre>
    -</div>
    -These methods support passing in a Function instead of a String, and jsPlumb will create a label overlay for you if one does not yet exist when you call setLabel:
    -<div class="code">
    -<pre>
    -var conn = jsPlumb.connect({
    -	source:"d1", 
    -	target:"d2"
    -});					
    -
    -...
    -
    -conn.setLabel(function(c) {
    -	var s = new Date();
    -	return s.getTime() + "milliseconds have elapsed since 01/01/1970";
    -});
    -console.log("Label is now", conn.getLabel());
    -</pre>
    -</div>
    -					
    -					<h4><a id="hideshowOverlay">Hiding/Showing Overlays</a></h4>
    -					<p>You can control the visibility of Overlays using the <strong>setVisible</strong> method of Overlays themselves, or with
    -					<strong>showOverlay(id)</strong> or <strong>hideOverlay(id)</strong> on a Connection.</p>
    -					<p>Remember the <strong>id</strong> parameter that we specified in the examples above?  This can be used to retrieve the Overlay
    -					from a Connection:</p>
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -var overlay = connection.getOverlay("myLabel");
    -// now you can hide this Overlay:
    -overlay.setVisible(false);
    -// there are also hide/show methods:
    -overlay.show();
    -overlay.hide();
    -
    -</pre>					
    -					</div>
    -					<p>However, Connection and Endpoint also have two convenience methods you could use instead:
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:-30 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -connection.hideOverlay("myLabel");
    -
    -// more time passes
    -
    -connection.showOverlay("myLabel");
    -
    -</pre>					
    -					</div>
    -					
    -		<h4><a id="hideshowOverlay">Removing Overlays</a></h4>
    -		<p>Connection and Endpoint also have a <strong>removeOverlay</strong> method, that does what you might expect:
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -connection.removeOverlay("myLabel");
    -
    -
    -</pre>					
    -					</div>							
    -	
    -		</div>
    -	
    -			
    -			<div class="section">
    -			<h3><a id="defaults">Defaults</a></h3>
    -			The easiest way to set a look and feel for your plumbing is to override the defaults that jsPlumb uses. If you
    -			do not do this you are forced to provide your overridden values on every call.  Every argument to the connect and addEndpoint methods has an
    -			associated default value in jsPlumb.<br/><br/>
    -
    -			The defaults that ship with jsPlumb are stored in <em>jsPlumb.Defaults</em>, which is a Javascript object.  Valid entries, and their initial values, are:
    -
    -			<div class="code">
    -<pre>
    -Anchor : "BottomCenter",
    -Anchors : [ null, null ],
    -Connector : "Bezier",
    -ConnectionOverlays : [ ],
    -DragOptions : { },
    -DropOptions : { },
    -Endpoint : "Dot",
    -EndpointOverlays : [ ],
    -Endpoints : [ null, null ],
    -EndpointStyle : { fillStyle : null },
    -EndpointStyles : [ null, null ],
    -EndpointHoverStyle : null,
    -EndpointHoverStyles : [ null, null ],
    -HoverPaintStyle : null,
    -LabelStyle : { color : "black" },
    -LogEnabled : false,
    -Overlays : [ ],
    -MaxConnections : 1,
    -PaintStyle : { lineWidth : 8, strokeStyle : "#456" },
    -RenderMode : "svg",
    -Scope : "_jsPlumb_DefaultScope"
    -</pre>
    -			</div>
    -			<p>
    -			Note that in EndpointStyle, the default fillStyle is 'null'.  This instructs jsPlumb to use the strokeStyle
    -from the attached connector to fill the endpoint.</p>
    -<p>Note also that you can specify either or both (or neither) of 'EndpointStyle' and 'EndpointStyles'.  This allows you to specify a different
    -end point for each end of a connection.  'Endpoint' and 'Endpoints' use the same concept.  jsPlumb will look first in the
    -individual endpoint/endpoint style arrays, and then fall back to the single default version.</p>
    -
    -			you can override these defaults by including this in a script somewhere:
    -			<div class="code">
    -<pre>
    -jsPlumb.Defaults.PaintStyle = {
    -	lineWidth:13,
    -	strokeStyle: 'rgba(200,0,0,0.5)'
    -}
    -
    -jsPlumb.Defaults.DragOptions = { cursor: "crosshair" };
    -
    -jsPlumb.Default.Endpoints = [ [ "Dot", 7 ], [ "Dot", 11 ] ];
    -
    -jsPlumb.Defaults.EndpointStyles = [{ fillStyle:"#225588" }, { fillStyle:"#558822" }];</pre>
    -			</div>
    -			after the jsPlumb script has been loaded of course!  Here we have specified the following default behaviour:
    -			<ul>
    -				<li>- connectors are 13 pixels wide and painted with a semi-transparent red line</li>
    -				<li>- when dragging an element the crosshair cursor is used</li>
    -				<li>- the source endpoint is a dot of radius 7; the target endpoint is a dot of radius 11</li>
    -				<li>- the source endpoint is blue; the target endpoint is green</li>
    -			</ul>
    -
    -			<h5>jsPlumb.importDefaults</h5>
    -There is a helper method called <strong>importDefaults</strong> that allows you to import a set of values with just one call to jsPlumb.  Here's the previous example using importDefaults:
    -			<div class="code">
    -<pre>
    -jsPlumb.importDefaults({
    -	PaintStyle : {
    -		lineWidth:13,
    -		strokeStyle: 'rgba(200,0,0,100)'
    -	},
    -	DragOptions : { cursor: "crosshair" },
    -	Endpoints : [ [ "Dot", 7 ], [ "Dot", 11 ] ],
    -	EndpointStyles : [{ fillStyle:"#225588" }, { fillStyle:"#558822" }]
    -});</pre>
    -			</div>
    -
    -		</div> 					
    -
    -<div class="section">
    -			<h3><a id="simpleConnections">Connections</a></h3>
    -			<h4><a id="simpleConnections">Programmatic Connections</a></h4>
    -The most simple connection you can make with jsPlumb looks like this:
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({source:"element1", target:"element2"});</pre>			
    -			</div>
    -
    -		In this example we have created a Connection from 'element1' to 'element2'.  Remember that a Connection in jsPlumb consists of two Endpoints,
    -		a Connector, and zero or more Overlays.  But this call to 'connect' supplied none of those things, so jsPlumb uses the default values wherever it needs to.
    -		In this case, default values have been used for the following: 
    -		<ul>
    -			<li>- The type and appearance of each Endpoint in the Connection. jsPlumb's default for this is the "Dot" endpoint, of radius 10, with fill color "#456".</li>
    -			<li>- The Anchors that define where the connection's Endpoints appear on each element. The jsPlumb default is "BottomCenter"</li>			
    -			<li>- Whether or not each Endpoint can be a source or target for new Connections. The default is false.</li>
    -			<li>- The type and appearance of the Connection's Connector. The default is a "Bezier" connector of line width 8, and color "#456".</li>
    -		</ul>
    -		<p>
    -		So this call will result in an 8px Bezier, colored "#456", from the bottom center of 'element1' to the bottom center of 'element2', and each
    -		Endpoint will be a 10px radius Dot Endpoint, colored "#456".
    -		</p>
    -		<p>
    -		Let's beef up this call a little and tell jsPlumb what sort of Endpoints we want, and where we want them:		
    -		</p>
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:"element1", 
    -	target:"element2",
    -	anchors:["RightMiddle", "LeftMiddle" ],
    -	endpoint:"Rectangle",
    -	endpointStyle:{ fillStyle: "yellow" }
    -});
    -</pre>			
    -			</div>
    -			
    -		<p>This is what we have told jsPlumb we want this time:</p>
    -		<ul>
    -			<li><strong>anchors</strong> - this array tells jsPlumb where the source and target Endpoints should be located on their parent elements. In this case,
    -			we use the shorthand syntax to name one of jsPlumb's default anchors; you can also specify custom locations (see <a href="#anchors">anchors</a>).
    -			Instead of <strong>anchors</strong> you can use <strong>anchor</strong>, if you want the source and target Endpoints to be located at the
    -			same place on their parent elements.			<br/>
    -			</li>
    -			<li>
    -				<strong>endpoint</strong>  - this tells jsPlumb to use the "Rectangle" Endpoint for both the source and target of the Connection.  As with anchors, 
    -				<strong>endpoint</strong> has a plural version that allows you to specify a different Endpoint for each end of the Connection.<br/>  
    -			</li>
    -			<li>
    -				<strong>endpointStyle</strong> - this is the definition of the appearance of the Endpoint you specified above.  Again, there is a
    -				plural equivalent of this that allows you to specify a different style for each end of the Connection. For more information about
    -				allowed values for this value, see <a href="#paintstyles">Connector, Endpoint & Overlay Styles</a>. 
    -			</li>
    -		</ul>		
    -		
    -		<h5>Reusing common settings between jsPlumb.connect calls</h5>
    -		<p>
    -			A fairly common situation you will find yourself in is wanting to create a bunch of Connections that have only minor differences
    -			between them.  To support that, <strong>jsPlumb.connect</strong> takes an optional second argument. For example:			 
    -		</p>
    -		<div class="code">
    -<pre>
    -var common = {
    -	anchors:[ "BottomCenter", "TopCenter" ],
    -	endpoints:["Dot", "Blank" ]
    -};
    -
    -jsPlumb.connect({ source:"someElement", target:"someOtherElement" }, common);
    -
    -jsPlumb.connect({ source:"aThirdElement", target:"yetAnotherElement" }, common);
    -
    -</pre>			
    -			</div>		
    -			
    -		<h5>Endpoints created by jsPlumb.connect</h5>
    -		<p>If you supply an element id or selector for either the source or target , jsPlumb.connect will automatically create an Endpoint on the 
    -		given element.  These automatically created Endpoints are not marked as drag source or targets, and cannot
    -		be interacted with.  For some situations this behaviour is perfectly fine, but for more interactive UIs you should set things up using the drag and drop
    -		method discussed below.</p>
    -		<p>Given that jsPlumb.connect creates its own Endpoints in some circumstances, in order to avoid leaving orphaned Endpoints around the place, if the Connection is subsequently
    -		deleted, these created Endpoints are deleted too.  Should you want to, you can override this behaviour by setting <strong>deleteEndpointsOnDetach</strong> to
    -		false in the connect call:</p>
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({ 
    -	source:"aThirdElement", 
    -	target:"yetAnotherElement",
    -	deleteEndpointsOnDetach:false 
    -});
    -</pre>
    -</div>	
    -
    -	<h5>Detaching Connections</h5>
    -	By default, connections made with jsPlumb.connect will be detachable via the mouse.  You can prevent this by either setting an appropriate default value:
    -
    -		<div class="code">
    -<pre>
    -jsPlumb.importDefaults({ 
    -	...
    -	ConnectionsDetachable:false
    -	...
    -});
    -</pre>
    -</div>	
    -
    -	Or by specifying it on the connect call like this:
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({ 
    -	source:"aThirdElement", 
    -	target:"yetAnotherElement",
    -	detachable:false
    -});
    -</pre>
    -</div>		
    -								
    -					
    -		<h4><a id="draggableConnections">Connections using Drag and Drop</a></h4>
    -		To support drag and drop connections, you first need to set a few things up.  Every drag and drop connection needs at least a source
    -		Endpoint that the user can drag a connection from. Here's a simple example of how to create an Endpoint:
    -		<div class="code">
    -<pre>
    -var endpointOptions = { isSource:true };
    -var endpoint = jsPlumb.addEndpoint('elementId', endpointOptions);
    -</pre>		
    -</div>  	
    -	This Endpoint will act as a source for new Connections, and will use the jsPlumb defaults for its own appearance and that of any Connections that are drawn from it.							
    -	
    -		<h5>Tip: use the three-argument addEndpoint method for common data </h5>
    -		One thing that happens quite often is that you have an Endpoint whose appearance and behaviour is largely the
    -		same between usages on different elements, with just a few differences. 
    -		<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -	endpoint:"Rectangle",
    -	paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -	isSource:true,
    -	connectorStyle : { strokeStyle:"#666" },
    -	isTarget:true
    -};
    -</pre>		
    -</div>  		
    -		Notice there is no 'anchor' set.  Here we apply it to two elements, at a different location in each:
    -		<div class="code">
    -<pre>
    -jsPlumb.addEndpoint('element1', { anchor:"BottomCenter" }, exampleGreyEndpointOptions)); 
    -
    -jsPlumb.addEndpoint('element2', { anchor:"TopCenter" }, exampleGreyEndpointOptions));
    -</pre>		
    -</div>
    -
    -<p>
    -Now that you have a source Endpoint, you need to either create a target Endpoint on some element, or notify jsPlumb that you wish to make an
    -entire element a drop target.  Let's look at how to attach a target Endpoint first:
    -</p>
    -
    -		<div class="code">
    -<pre>var endpointOptions = { isTarget:true, endpoint:"Rectangle", paintStyle:{ fillStyle:"gray" } };
    -var endpoint = jsPlumb.addEndpoint("otherElementId", endpointOptions);
    -</pre>		
    -		</div>
    -		<p>
    -		This Endpoint, a gray rectangle, has declared that it can act as a drop target for Connections. 
    -</p>
    -<p>
    -jsPlumb also supports marking entire elements as drag sources or drag targets, using the <strong>makeSource</strong> and <strong>makeTarget</strong> methods.  This is a useful function for certain applications.  Each of these methods takes an Endpoint declaration which defines the Endpoint that jsPlumb will create after a Connection has been attached.  Let's take a look at makeTarget first:
    -</p>
    -<h4><a id="sourcesAndTargets">Elements as sources &amp; targets</a></h4>
    -jsPlumb also supports turning entire elements into Connection sources and targets, using the methods <strong>makeSource</strong> and <strong>makeTarget</strong>.  With these methods you mark an element as a source or target, and provide an Endpoint specification for jsPlumb to use when a Connection is established.  makeSource also gives you the ability to mark some child element as the place from which you wish to drag Connections, but still have the Connection on the main element after it has been established.
    -<p>These methods honour the jsPlumb defaults - if, for example you set up the default Anchors to be this:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.Defaults.Anchors = [ "LeftMiddle", "BottomRight" ];
    -</pre>
    -</div>
    -... and then used makeSource and makeTarget without specifying Anchor locations, jsPlumb would use "LeftMiddle" for the makeSource element and "BottomRight" for the makeTarget element.
    -<p>
    -A further thing to note about makeSource and makeTarget is that any prior calls to one of these methods is honoured by subsequent calls to jsPlumb.connect. This helps when you're building a UI that uses this functionality at runtime but which loads some initial data, and you want the statically loaded data to have the same appearance and behaviour as dynamically created connections (obviously quite a common use case).  
    -<p>For example:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -	anchor:"Continuous",
    -	endpoint:["Rectangle", { width:40, height:20 }],
    -	maxConnections:3
    -});
    -
    -...
    -
    -jsPlumb.connect({source:"el1", target:"el2"});
    -</pre>
    -</div>
    -In this example, the source of the connection will be a Rectangle of size 40x20, having a Continuous Anchor.  The source is also configured to allow a maximum of three connections.
    -<p>You can override this behaviour by setting a <strong>newConnection</strong> parameter on the connect call:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -	anchor:"Continuous",
    -	endpoint:["Rectangle", { width:40, height:20 }],
    -	maxConnections:1,
    -	onMaxConnections:function(info, originalEvent) {
    -		console.log("element is ", info.element, "maxConnections is", info.maxConnections);	
    -	}
    -});
    -
    -...
    -
    -jsPlumb.connect({ source:"el1", target:"el2", newConnection:true });
    -</pre>
    -</div>
    -Note the <strong>onMaxConnections</strong> parameter to this call - it allows you to supply a function to call if the user tries to drag a new Connection when the source has already reached capacity.
    -<h5><a id="makeTarget">jsPlumb.makeTarget</a></h5>
    -<p>
    -This method takes two arguments, the first of which specifies some element (or list of elements); the second specifies the Endpoint you wish to create on that element whenever a Connection is established on it.  In this example we will use the exact same target Endpoint we used before - the gray rectangle - but we will tell jsPlumb that the element "aTargetDiv" will be the drop target:
    -</p>
    -		<div class="code">
    -<pre>var endpointOptions = { 
    -  isTarget:true, 
    -  maxConnections:5,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" } 
    -};
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -<p>
    -	The allowed values in 'endpointOptions' are identical for both the <strong>jsPlumb.addEndpoint</strong> and <strong>jsPlumb.makeTarget</strong> methods, but makeTarget supports an extended 
    -	anchor syntax that allows you more control over the location of the target endpoint.  This is discussed below.	
    -</p>		
    -<p>Notice in the 'endpointOptions' object above there is a 'isTarget' parameter set - this may seem incongruous, since you know you're going to make some element a target.  Remember that the endpointOptions object is the information jsPlumb will use to create an Endpoint on the given target element each time a Connection is established to it. It takes the exact same format as you would pass to addEndpoint; makeTarget is essentially a deferred addEndpoint call followed by a connect call.  So in this case, we're telling jsPlumb that any Endpoints it happens to create on some element that was configured by the makeTarget call are themselves Connection targets.
    -</p>
    -<p>makeTarget also supports the maxConnections and onMaxConnections parameters, as makeSource does, but note that onMaxConnections is passed one extra parameter than its corresponding callback from makeSource - the Connection the user tried to drop:</p>
    -		<div class="code">
    -<pre>
    -jsPlumb.makeTarget("aTargetDiv", { 
    -  isTarget:true, 
    -  maxConnections:5,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" },
    -  maxConnections:3,
    -  onMaxConnections:function(info, originalEvent) {
    -	console.log("user tried to drop connection", info.connection, "on element", info.element, "with max connections", info.maxConnections);
    -  }
    -};
    -</pre>		
    -		</div>
    -
    -<h5>Unique Endpoint per Target </h5>
    -<p>jsPlumb will create a new Endpoint using the supplied information every time a new Connection is established on the target element, by default, but you can override this behaviour and tell jsPlumb that it should create at most one Endpoint, which it should attempt to use for subsequent Connections:
    -</p>
    -		<div class="code">
    -<pre>var endpointOptions = { 
    -  isTarget:true, 
    -  uniqueEndpoint:true,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" } 
    -};
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -Here, the <strong>uniqueEndpoint</strong> parameter tells jsPlumb that there should be at most one Endpoint on this element.  Notice that 'maxConnections' is not set: the default is 1, so in this setup we have told jsPlumb that "aTargetDiv" can receive one Connection and no more.
    -<h5>Deleting Endpoints on Detach</h5>
    -By default, any Endpoints created using makeTarget have <strong>deleteEndpointsOnDetach</strong> set to true, which means that once all Connections to that Endpoint are removed, the Endpoint is deleted.  You can override this by setting the flag to true on the makeTarget call:
    -		<div class="code">
    -<pre>var endpointOptions = { 
    -  isTarget:true, 
    -  maxConnections:5,
    -  uniqueEndpoint:true,
    -  deleteEndpointsOnDetach:false,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" } 
    -};
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -In this setup we have told jsPlumb to create an Endpoint the first time "aTargetElement" is connected to, and to not delete it even if there are no longer any Connections to it.  The created Endpoint will be reused for subsequent Connections, and can support a maximum of 5.
    -
    -<h5>Detaching connections made with the mouse</h5>
    -As with jsPlumb.connect, connections made with the mouse after setting up Endpoints with one of the functions we've just covered will be, by default, detachable.  You can prevent this in the jsPlumb defaults, as previously mentioned:
    -		<div class="code">
    -<pre>jsPlumb.importDefaults({ 
    -	...
    -	ConnectionsDetachable:false,
    -	...
    -});
    -</pre>		
    -		</div>
    -
    -	And you can also set this on a per-endpoint (or source/target) level, like in these examples:
    -
    -		<div class="code">
    -<pre>jsPlumb.addEndpoint("someElementId", { 
    -	connectionsDetachable:false	
    -});
    -
    -jsPlumb.makeSource("someOtherElement", {
    -	...
    -	connectionsDetachable:false,
    -	...
    -});
    -
    -jsPlumb.makeTarget("yetAnotherElement", {
    -	...
    -	connectionsDetachable:false,
    -	...
    -});
    -</pre>		
    -		</div>
    -
    -Note that in the jsPlumb defaults, by convention each word in a parameter is capitalised ("ConnectionsDetachable"), whereas for a call to one of these methods, we use camel case ("connectionsDetachable").
    -
    -
    -<h5>Target Anchors positions with makeTarget</h5>
    -<p>When using the makeTarget method, jsPlumb allows you to provide a callback function to be used to determine
    -the appropriate location of a target anchor for every new Connection dropped on the given target.  It may be the
    -case that you want to take some special action rather than just relying on one of the standard anchor mechanisms.
    -</p>
    -<p>This is achieved through an extended anchor syntax (note that this syntax is <strong>not supported</strong> in
    -the <strong>jsPlumb.addEndpoint</strong> method) that supplies a "positionFinder" to the anchor specification.  jsPlumb 
    -provides two of these by default; you can register your own on jsPlumb and refer to them by name, or just supply a function.  Here's a few examples:</p>
    -Instruct jsPlumb to place the target anchor at the exact location at which the mouse button was released on the
    -target element. Note that you tell jsPlumb the anchor is of type "Assign", and you then provide a "position"
    -parameter, which can be the name of some position finder, or a position finder function itself:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {
    -  anchor:[ "Assign", { 
    -    position:"Fixed"
    -  }]
    -});
    -</pre>
    -</div>
    -<p><strong>Fixed</strong> is one of the two default position finders provided by jsPlumb. The other is <strong>Grid</strong>:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {
    -  anchor:[ "Assign", { 
    -    position:"Grid",
    -    grid:[3,3]
    -  }]
    -});
    -</pre>
    -</div>
    -The Grid position finder takes a "grid" parameter that defines the size of the grid required. [3,3] means
    -3 rows and 3 columns.
    -<h5>Supplying your own position finder</h5>
    -<p>To supply your own position finder to jsPlumb you first need to create the callback function. First let's take a look at what the source code for the Grid position finder looks like:</p>
    -<div class="code">
    -<pre>
    -function(eventOffset, elementOffset, elementSize, constructorParams) {
    -  var dx = eventOffset.left - elementOffset.left, dy = eventOffset.top - elementOffset.top,
    -      gx = elementSize[0] / (constructorParams.grid[0]), 
    -      gy = elementSize[1] / (constructorParams.grid[1]),
    -      mx = Math.floor(dx / gx), my = Math.floor(dy / gy);
    -
    -  return [ ((mx * gx) + (gx / 2)) / elementSize[0], ((my * gy) + (gy / 2)) / elementSize[1] ];
    -}
    -</pre>
    -</div>
    -The four arguments are:
    -<ul>
    -	<li class="bullet"><strong>eventOffset</strong> - Page left/top where the mouse button was released (a JS object containing left/top members like you get from a jQuery offset call)</li>
    -	<li class="bullet"><strong>elementOffset</strong> - JS offset object containing offsets for the element on which the Connection is to be created</li>
    -	<li class="bullet"><strong>elementSize</strong> - [width, height] array of the dimensions of the element on which the Connection is to be created</li>
    -	<li class="bullet"><strong>constructorParams</strong> - the parameters that were passed to the Anchor's constructor. In the example given above, those parameters are 'position' and 'grid'; you can pass arbitrary parameters.
    -</ul>
    -The return value of this function is an array of [x, y] - proportional values between 0 and 1 inclusive, such as you can pass to a static Anchor.
    -<p>To make your own position finder you need to create a function that takes those four arguments and returns an [x, y] position for the anchor, for example:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.AnchorPositionFinders.MyFinder = function(dp, ep, es, params) {
    -	...		
    -	return [ some maths ];	
    -};
    -</pre>
    -</div>
    -Then refer to it in a makeTarget call:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {  
    -  anchor:[ "Assign", { 
    -    position:"MyFinder",
    -    myCustomParameter:"foo",
    -    anInteger:5
    -  }]
    -});
    -</pre>
    -</div>
    -
    -<h5><a id="makeSource">jsPlumb.makeSource</a></h5>
    -There are two use cases supported by this method.  The first is the case that you want to drag a Connection from the element itself and have an Endpoint attached to the element when a Connection is established.  The second is a more specialised case: you want to drag a Connection from the element, but once the Connection is established you want jsPlumb to move it so that its source is on some other element.  For an example of this second case, check out the <a href="../demo/jquery/stateMachineDemo.html">State Machine Connectors</a> demonstration.
    -<p>Here's an example code snippet for the basic use case of makeSource:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource(someDiv, {
    -  paintStyle:{ fillStyle:"yellow" },
    -  endpoint:"Blank",
    -  anchor:"BottomCenter"
    -});
    -</pre>
    -</div>
    -<p>
    -Notice again that the second argument is the same as the second argument to an addEndpoint call.  makeSource is, essentially, a type of addEndpoint call.  In this example we have told jsPlumb that we will support dragging Connections directly from 'someDiv'.  Whenever a Connection is established between 'someDiv' and some other element, jsPlumb assigns an Endpoint at BottomCenter of 'someDiv', fills it yellow, and sets that Endpoint as the newly created Connection's source. 
    -</p>		
    -<p>Configuring an element to be an entire Connection source using makeSource means that the element cannnot itself be draggable.  There would be no way for jsPlumb to distinguish between the user attempting to drag the element and attempting to drag a Connection from the element.  If your UI works in such a way that that is acceptable then you'll be fine to use this method.  But if you want to drag Connections from some element but also have the element be draggable itself, you might want to consider the second use case.  First, a code snippet:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("someConnectionSourceDiv", {
    -  paintStyle:{ fillStyle:"yellow" },
    -  endpoint:"Blank",
    -  anchor:"BottomCenter",
    -  parent:"someDiv"
    -});
    -</pre>
    -</div>
    -<p>The only difference here is the inclusion of the 'parent' parameter.  It instructs jsPlumb to configure the source Endpoint on the element specified - in this case, "someDiv" (the value of this parameter may be a String id or a selector).</p>
    -<p>Again, I'd suggest taking a look at the <a href="../demo/jquery/stateMachineDemo.html">State Machine Connectors demonstration</a> for an example of this.</p>
    -
    -<h5>Filtering drag start based on element</h5>
    -You can supply a 'filter' parameter to the makeSource call in order to get a little more fine-grained control over which parts of the element will respond to a mousedown and start a drag. Consider this markup:
    -<div class="code">
    -<pre>
    -&lt;div id="foo"&gt;
    -  FOO
    -  &lt;button&gt;click me&lt;/button&gt;
    -&lt;/div&gt;
    -</pre>
    -</div>
    -Suppose we do not want to interfere with the operation of the 'click me' button. We can supply a filter to the makeSource call to do so:
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("foo", {
    -  filter:function(event, element) {
    -    return event.target.tagName !== "BUTTON";
    -  }
    -});
    -</pre>
    -</div>
    -If the filter returns anything other than a boolean false, the drag will begin. It's important to note that only boolean false will prevent a drag. False-y values will not.
    -
    -<h5>Endpoint options with makeSource</h5>
    -<p>
    -There are many things you can set in an Endpoint options object; for a thorough list see the API documentation for Endpoint.  
    -</p>
    -<p>Here's an example of specifying that you want an Arrow overlay halfway along any Connection dragged from this Endpoint:</p>
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -  isSource:true,
    -  connectorStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  connectorOverlays: [ [ "Arrow", { location:0.5 } ] ]
    -};
    -</pre>
    -</div>
    -This is an Endpoint that moves around the element it is attached to dependent on the location of other elements in the connections it is attached to (a 'dynamic' anchor):
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -  isSource:true,
    -  connectorStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  connectorOverlays: [ [ "Arrow", { location:0.5 } ] ]
    -  anchor:[ "TopCenter","RightMiddle","BottomCenter","LeftMiddle" ]
    -};
    -</pre>
    -</div>
    -<h5><a id="targetSourceTest">Testing if an element is a target or source</a></h5>
    -You can test if some element has been made a connection source or target using these methods:
    -<ul>
    -<li class="bullet">isTarget</li>
    -<li class="bullet">isSource</li>
    -</ul>
    -<p>The return value is a boolean.</p>
    -<h5><a id="targetSourceToggle">Toggling an element as connection target or source</a></h5>
    -You can toggle the state of some source or target using these methods:
    -<ul>
    -<li class="bullet">setTargetEnabled</li>
    -<li class="bullet">setSourceEnabled</li>
    -</ul>
    -<p>The return value from these methods is the current jsPlumb instance, allowing you to chain them:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.setTargetEnabled("aDivId").setSourceEnabled($(".aSelector"));
    -</pre>
    -</div>
    -<p>Note that if you call either of these methods on an element that was not originally configured as a target or source, nothing will happen.</p>
    -<p>You can check the enabled state of some target or source using these methods:
    -<ul>
    -<li class="bullet">isTargetEnabled</li>
    -<li class="bullet">isSourceEnabled</li>
    -</ul>
    -<h5><a id="targetSourceCancel">Canceling previous makeTarget and/or makeSource calls</a></h5>
    -jsPlumb offers four methods to let you cancel previous makeTarget or makeSource calls. Each of these methods returns the current jsPlumb instance, and so can be chained:
    -<ul>
    -<li class="bullet">unmakeTarget</li>
    -<li class="bullet">unmakeSource</li>
    -<li class="bullet">unmakeEveryTarget</li>
    -<li class="bullet">unmakeEverySource</li>
    -</ul>
    -<p>These last two are analogous to the <strong>removeEveryConnection</strong> and <strong>removeEveryEndpoint</strong> methods that have been in jsPlumb for a while now.</p>
    -<p>unmakeTarget and unmakeSource both take as argument the same sorts of values that makeTarget and makeSource accept - a string id, or a selector, or an array of either of these:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.unmakeTarget("aDivId").unmakeSource($(".aSelector"));
    -</pre>
    -</div>
    -
    -
    -<h5><a id="dragOptions">Drag Options</a></h5>
    -	These are options that will be passed through to the supporting library's drag API.  jsPlumb passes everything you supply here through, inserting
    -	wrapping functions if necessary for the various lifecycle events that jsPlumb needs to know about.  So if, for example, you pass a function to be
    -	called when dragging starts, jsPlumb will wrap that function with a function that does what jsPlumb needs to do, then call yours.
    -	<p>
    -	At the time of writing, jsPlumb supports jQuery, MooTools and YUI3, and each of those libraries uses different terminology.  In addition, jQuery's API is
    -	more fully featured, providing easy support for setting the zIndex and opacity of elements being dragged, as well as the 'scope' for a drag/drop (allowing
    -	you to specify more than one type of drag/drop pair), and hover classes for when a draggable is on the move or
    -	over a droppable. If you're using jQuery you can of course just supply these values on the dragOptions; to make it easier, jsPlumb's MooTools and YUI3 adapters recognize 
    -	these options and add appropriate callbacks for you.  
    -	</p> 
    -	<p>
    -	Given that the options here are library-specific, and they are all well-documented, we're going to discuss just the three drag options
    -	that behave the same way in all (see below for hoverClass):
    -		<ul>
    -		<li><strong>opacity</strong> - the opacity of an element that is being dragged.  Should be a fraction between 0 and 1 inclusive.</li>
    -		<li><strong>zIndex</strong> - the zIndex of an element that is being dragged.</li>
    -		<li><strong>scope</strong> - the scope of the draggable.  can only be dropped on a droppable with the same scope.  this is discussed below.</li>
    -		</ul> 
    -	</p>
    -	
    -	<p>For more information about drag options, take a look at the <a href="http://docs.jquery.com/UI/Draggable" target="_blank">jQuery</a>, <a href="http://mootools.net/docs/more/Drag/Drag.Move" target="_blank">MooTools</a>, or <a href="http://developer.yahoo.com/yui/3/dd/" target="_blank">YUI3</a> docs.	
    -	</p>
    -	<p><strong>NOTE: there is an issue in Chrome that affects the 'cursor' argument to drag options in jQuery. See these links:</strong>
    -	</p><p>
    -<a href="http://forum.jquery.com/topic/draggable-cursor-option-does-not-work" target="_blank">http://forum.jquery.com/topic/draggable-cursor-option-does-not-work</a><br/>
    -<a href="http://forum.jquery.com/topic/chrome-text-select-cursor-on-drag" target="_blank">http://forum.jquery.com/topic/chrome-text-select-cursor-on-drag</a>
    -</p>
    -You should put something like this at the top of your JS to avoid it (perhaps not something so drastic as a document-wide override):	
    -<div class="code">
    -<pre>document.onselectstart = function () { return false; };</pre>
    -</div>		
    -	
    -	<h5><a id="dropOptions">Drop Options</a></h5>
    -	Drop options are treated by jsPlumb in the same way as drag options - they are passed through to the underlying library.  MooTools does not have drop options like jQuery and YUI3 do; droppable functionality in MooTools 
    -	is actually implemented by the Drag.Move class - the one used to initialise a draggable.  But when you setup an Endpoint in jsPlumb you should ignore
    -	that fact, and treat droppables like you would in jQuery or YUI3.  jsPlumb wires everything up for you under the hood.
    -	<p>There are three jQuery droppable options that jsPlumb treats as shortcuts in MooTools and YUI3, for the sake of consistency:</p>
    -		<ul>
    -			<li><strong>hoverClass</strong> - the CSS class to attach to the droppable when a draggable is hovering over it.</li>
    -			<li><strong>activeClass</strong> - the CSS class to attach to the droppable when a draggable is, um, being dragged.</li>
    -			<li><strong>scope</strong> - the scope of the draggable.  The draggable can only be dropped on a droppable with the same scope.  This is discussed below.</li>
    -		</ul>   
    -	<p>For more information about drop options when using jQuery, see <a href="http://docs.jquery.com/UI/Droppable" target="_blank">here</a>.</p>
    -		
    -	<h5><a id="dragAndDropScope">Drag and Drop Scope</a></h5>
    -		jsPlumb borrowed the concept of 'scope' from jQuery's drag/drop implementation: the notion of
    -		which draggables can be dropped on which droppables.  In jsPlumb you can provide a 'scope' entry when 
    -		creating an Endpoint.  Here's the example grey Endpoint example with 'scope' added:
    -		
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:"#666" },
    -  isSource:true,
    -  connectionStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  scope:"exampleGreyConnection"
    -};</pre>		
    -</div>  		
    -  										
    -	If you do not provide a 'scope' entry, jsPlumb uses a default scope.  Its value is accessible through this method:
    -	<div class="code">
    -<pre>
    -jsPlumb.getDefaultScope();
    -</pre>	
    -	</div>				
    -	If you want to change it for some reason you can do so with this method:
    -	<div class="code">
    -<pre>
    -jsPlumb.setDefaultScope("mySpecialDefaultScope");
    -</pre>	
    -	</div>			
    -	You can also, should you want to, provide the scope value separately on the drag/drop options, like this:
    -	<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -  isSource:true,
    -  connectionStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  dragOptions:{ scope:"dragScope" },
    -  dropOptions:{ scope:"dropScope" }
    -};</pre>		
    -</div>  
    -			
    -	</div>	
    -
    -	<div class="section">
    -		<h3><a id="parameters">Connection Parameters</a></h3>
    -		jsPlumb has a mechanism that allows you to set parameters on a per-connection basis.  This can be achieved in a few different ways:
    -
    -		<ul>
    -			<li class="bullet">Providing parameters to a jsPlumb.connect call</li>
    -			<li class="bullet">Providing parameters to an addEndpoint call to/from which a connection is subsequently established using the mouse</li>
    -			<li class="bullet">using the <strong>setParameter</strong> or <strong>setParameters</strong> method on a Connection</li>
    -		</ul>
    -		Getting parameters is achieved through either the <strong>getParameter(key)</strong> or <strong>getParameters</strong> method on a Connection.
    -		<h4><a id="connectParameters">jsPlumb.connect</a></h4>
    -		Parameters can be passed in via an object literal to a jsPlumb.connect call:
    -		<div class="code">
    -<pre>var myConnection = jsPlumb.connect({
    -	source:"foo",
    -	target:"bar",
    -	parameters:{
    -		"p1":34,
    -		"p2":new Date(),
    -		"p3":function() { console.log("i am p3"); }
    -	}
    -});
    -</pre>
    -</div>
    -Note that they can be any valid Javascript objects - it's just an object literal.  You then access them like this:
    -		<div class="code">
    -<pre>myConnection.getParameter("p3")();     // prints 'i am p3' to the console.
    -</pre>
    -</div>
    -
    -<h4><a id="addEndpointParameters">jsPlumb.addEndpoint</a></h4>
    -<p>The information in this section also applies to the <strong>makeSource</strong> and <strong>makeTarget</strong> functions</p>
    -<p>Using jsPlumb.addEndpoint, you can set parameters that will be copied in to any Connections that are established from or to the given Endpoint using the mouse.  (If you set parameters on both a source and target Endpoints and then connect them, the parameters set on the target Endpoint are copied in first, followed by those on the source. So the source Endpoint's parameters take precedence if they happen to have one or more with the same keys as those in the target).</p>
    -Consider this example:
    -		<div class="code">
    -<pre>var e1 = jsPlumb.addEndpoint("d1", {
    -	isSource:true,
    -	parameters:{
    -		"p1":34,
    -		"p2":new Date(),
    -		"p3":function() { console.log("i am p3"); }
    -	}
    -});
    -
    -var e2 = jsPlumb.addEndpoint("d2", {
    -	isTarget:true,
    -	parameters:{
    -		"p5":343,
    -		"p3":function() { console.log("FOO FOO FOO"); }
    -	}
    -});
    -
    -var conn = jsPlumb.connect({source:e1, target:e2});
    -
    -
    -</pre>
    -</div>
    -
    -'conn' will have four parameters set on it, with the value for "p3" coming from the source Endpoint:
    -		<div class="code">
    -<pre>var params = conn.getParameters();
    -console.log(params.p1);   	// 34
    -console.log(params.p2);   	// Mon May 14 2012 12:57:12 GMT+1000 (EST) (or however your console prints out a Date)
    -console.log((params.p3)()); // "i am p3"  (note: we executed the function after retrieving it)
    -console.log(params.p5);   	// 343
    -
    -</pre>
    -</div>
    -
    -
    -</div> <!-- /section -->
    -
    -<div class="section">
    -	<h3><a id="connectionTypes">Connection and Endpoint Type</a></h3>
    -	<p>
    -	jsPlumb supports the notion of "type" for both Connections and Endpoints. A type is a
    -	collection of attributes such as paint style, hover paint style, overlays etc - it is a subset, including most but not all, of the
    -	parameters you can set in an Endpoint or Connection definition. 
    -	</p>
    -	<p>
    -	An Endpoint or Connection can have zero or more types assigned; they are merged as granularly as possible, in the order
    -	in which they were assigned. There is a supporting API that works in the same way as the class stuff does in
    -	jQuery - hasType, addType, removeType, toggleType, setType, and each of these methods (except hasType) takes a space-separated
    -	string so you can add several at once.  Support for these methods has been added to the jsPlumb.select and
    -	jsPlumb.selectEndpoint methods, and you can also now specify a 'type' parameter to an Endpoint or Connection at create time.
    -	</p>
    -	<h4>Connection Type</h4>
    -	Probably the easiest way to explain types is with some code. In this snippet, we'll register a Connection type on jsPlumb,
    -	create a Connection, and then assign the type to it:
    -<div class="code">
    -<pre>
    -jsPlumb.registerConnectionType("example", {
    -	paintStyle:{ strokeStyle:"blue", lineWidth:5  },
    -	hoverPaintStyle:{ strokeStyle:"red", lineWidth:7 }
    -});
    -
    -var c = jsPlumb.connect({ source:"someDiv", target:"someOtherDiv" });
    -c.bind("click", function() {
    -	c.setType("example");
    -});	
    -</pre>		
    -</div>
    -<p>Another example - a better one, in fact.  Say you have a UI in which you can click to select or deselect Connections, and
    -you want a different appearance for each state.  Connection types to the rescue!</p>
    -<div class="code">
    -<pre>
    -jsPlumb.registerConnectionTypes({
    -	"basic": {
    -		paintStyle:{ strokeStyle:"blue", lineWidth:5  },
    -		hoverPaintStyle:{ strokeStyle:"red", lineWidth:7 }
    -	},
    -	"selected":{
    -		paintStyle:{ strokeStyle:"red", lineWidth:5 },
    -		hoverPaintStyle:{ lineWidth: 7 }
    -	}	
    -});
    -
    -var c = jsPlumb.connect({ source:"someDiv", target:"someOtherDiv", type:"basic" });
    -
    -c.bind("click", function() {
    -	c.toggleType("selected");
    -});	
    -</pre>		
    -</div>
    -<p>Notice here how we used a different method - <strong>registerConnectionTypes</strong> - to register a few types at once.
    -Notice also the hoverPaintStyle for the 'selected' type: it declares only a lineWidth.  As mentioned above, types are
    -merged with as much granularity as possible, so that means that in this case the lineWidth from 'selected' will be merged
    -into the hoverPaintStyle from 'basic', and voila, red, 7 pixels.</p>
    -<p>These examples, of course, use the <strong>jsPlumb.connect</strong> method, but in many UIs Connections are created
    -via drag and drop.  How would you assign that 'basic' type to a Connection created with drag and drop? Provide it as
    -the Endpoint's connectorType parameter, like so:</p>
    -<div class="code">
    -<pre>
    -	var e1 = jsPlumb.addEndpoint("someDiv", {
    -		connectorType:"basic",
    -		isSource:true
    -	});
    -	
    -	var e2 = jsPlumb.addEndpoint("someOtherDiv", {
    -		isTarget:true
    -	});
    -		
    -	//... user then perhaps drags a connection...or we do it programmatically:
    -	
    -	var c = jsPlumb.connect({ source:e1, target:e2 });
    -	
    -	// now c has type 'basic'
    -	console.log(c.hasType("basic));   // -> true
    -</pre>	
    -</div>
    -<p>Note that the second Endpoint we created did not have a 'connectorType' parameter - we didn't need it, as the source
    -Endpoint in the Connection had one.  But we could have supplied one, and jsPlumb will use it, but only if the source
    -Endpoint has not declared connectorType.  This is the same way jsPlumb treats other connector parameters such as
    -paintStyle etc - the source Endpoint wins.</p>
    -<p>Here's a few examples showing you the full type API:</p>
    -<div class="code">
    -<pre>
    -	jsPlumb.registerConnectionTypes({
    -		"foo":{ paintStyle:{ strokeStyle:"yellow", lineWidth:5 } },
    -		"bar":{ paintStyle:{ strokeStyle:"blue", lineWidth:10 } },
    -		"baz":{ paintStyle:{ strokeStyle:"green", lineWidth:1 } }
    -	});
    -	
    -	var c = jsPlumb.connect({ source:"someDiv", target:"someOtherDiv", type:"foo" });
    -	
    -	// see what types the connection has.  
    -	console.log(c.hasType("foo"));  // -> true
    -	console.log(c.hasType("bar"));  // -> false
    -	
    -	// add type 'bar'
    -	c.addType("bar");
    -	
    -	// toggle both types (they will be removed in this case)
    -	c.toggleType("foo bar");
    -	
    -	// toggle them back
    -	c.toggleType("foo bar");
    -	
    -	// getType returns a list of current types.
    -	console.log(c.getType()); // -> [ "foo", "bar" ]
    -	
    -	// set type to be 'baz' only
    -	c.setType("baz");
    -	
    -	// add foo and bar back in
    -	c.addType("foo bar");
    -	
    -	// remove baz and bar
    -	c.removeType("baz bar");
    -	
    -	// what are we left with? good old foo.
    -	console.log(c.getType()); // -> [ "foo" ]
    -	
    -</pre>	
    -</div>
    -<p>Things to note here are that every method <strong>except hasType</strong> can take a space-delimited list of types
    -to work with. So types work like CSS classes, basically.</p>
    -<h5>Supported Parameters in Connection Type objects</h5>
    -Not every parameter from a Connection's constructor is supported in a Connection Type - as mentioned above, types act pretty
    -much like CSS classes, so the things are supported are related to behaviour or appearance. For instance, 'source' is
    -not supported.  You cannot make a Connection Type that is fixed to a specific source element. Here's the full list of
    -supported properties in Connection Type objects:
    -<ul>
    -	<li class="bullet"><strong>detachable</strong> - whether or not the Connection is detachable using the mouse</li>
    -	<li class="bullet"><strong>paintStyle</strong></li>
    -	<li class="bullet"><strong>hoverPaintStyle</strong></li>
    -	<li class="bullet"><strong>scope</strong> - remember, Connections support a single scope. So if you have multiple types applied, you will get the scope from the last type that defines one.</li>
    -	<li class="bullet"><strong>parameters</strong> - when you add/set a type that has parameters, any existing parameters with the same keys will be overwritten. When you remove a type that has parameters, its parameters are NOT removed from the Connection.</li>
    -	<li class="bullet"><strong>overlays</strong> - when you have multiple types applied to a Connection, you get the union of
    -	all the Overlays defined across the various types. <strong>Note</strong> when you create a Connection using jsPlumb.connect and you
    -	provide a 'type', that is equivalent to calling 'addType': you will get the Overlays defined by the type(s) you set as
    -	well as any others you have provided to the constructor.</li>	
    -</ul>
    -
    -<h4><a id="endpointTypes">Endpoint Type</a></h4>
    -Endpoints can also be assigned one or more types, both at creation and programmatically using the API discussed above.
    -The only real differences between Endpoint and Connection types are the allowed parameters.  Here's the list for Endpoints:
    -<ul>
    -	<li class="bullet"><strong>paintStyle</strong></li>
    -	<li class="bullet"><strong>hoverPaintStyle</strong></li>
    -	<li class="bullet"><strong>maxConnections</strong></li>
    -	<li class="bullet"><strong>connectorStyle</strong> - paint style for any Connections that use this Endpoint.</li>
    -	<li class="bullet"><strong>connectorHoverStyle</strong> - hover paint style for Connections from this Endpoint.</li>
    -	<li class="bullet"><strong>connector</strong> - a Connector definition, like "StateMachine", or [ "Flowchart", { stub:50 } ] </li>
    -	<li class="bullet"><strong>connectionType</strong> - This allows you to specify the Connection Type for Connections made from this Endpoint.</li>
    -	<li class="bullet"><strong>scope</strong> - remember, Endpoints support a single scope. So if you have multiple types applied, you will get the scope from the last type that defines one.</li>
    -	<li class="bullet"><strong>parameters</strong> - when you add/set a type that has parameters, any existing parameters with the same keys will be overwritten. When you remove a type that has parameters, its parameters are NOT removed from the Connection.</li>
    -	<li class="bullet"><strong>overlays</strong> - when you have multiple types applied to an Endpoint, you get the union of all the Overlays defined across the various types.</li>
    -	
    -</ul>
    -<p>One thing to be aware of is that the parameters here that are passed to Connections are only passed from a source Endpoint,
    -not targets.</p>
    -Here's an example of using Endpoint types:
    -<div class="code">
    -<pre>
    -  jsPlumb.registerEndpointTypes({
    -    "basic":{			
    -      paintStyle:{fillStyle:"blue"}
    -    },
    -    "selected":{			
    -      paintStyle:{fillStyle:"red"}
    -    }
    -  });
    -
    -  var e = jsPlumb.addEndpoint("someElement", {
    -    anchor:"TopMiddle",
    -	type:"basic"
    -  });
    -  
    -  e.bind("click", function() {
    -    e.toggleType("selected");
    -  });
    -</pre>
    -</div>
    -So it works the same way as Connection Types.  There are several parameters allowed by an Endpoint Type that affect Connections
    -coming from that Endpoint. Note that this does not affect existing Connections.  It affects only Connections that are
    -created after you set the new type(s) on an Endpoint.
    -
    -<h4>Fluid Interface</h4>
    -As mentioned previously, all of the type operations are supported by the <strong>select</strong> and <strong>selectEndpoints</strong> methods.
    -So you can now do things like this:
    -<div class="code">
    -<pre>
    -	jsPlumb.selectEndpoints({scope:"terminal"}).toggleType("active");
    -	
    -	jsPlumb.select({source:"someElement"}).addType("highlighted available");
    -	
    -	etc
    -	
    -</pre>
    -</div>
    -Obviously, in these examples, 'available' and 'highlighted' would have previously been registered on jsPlumb using the appropriate
    -register methods.
    -
    -</div> <!-- / connection/endpoint types	-->
    -	
    -	<div class="section">
    -		<h3><a id="beforeDrop">Interceptors</a></h3>
    -		<p>
    -		Interceptors were new in jsPlumb 1.3.4.  They are basically event handlers from which you can return a value that tells jsPlumb to abort what it is that
    -		it was doing.  There are currently two interceptors supported - <strong>beforeDrop</strong>, which is called when the user has dropped a Connection onto some target, 
    -		and <strong>beforeDetach</strong>, which is called when the user is attempting to detach a Connection. 
    -		</p>
    -		<p>Interceptors can be registered via the <strong>bind</strong> method on jsPlumb just like any other event listeners, and they can also be passed in to
    -		the <strong>jsPlumb.addEndpoint</strong>, <strong>jsPlumb.makeSource</strong> and <strong>jsPlumb.makeTarget</strong>
    -		methods.  A future version of jsPlumb will support registering interceptors on Endpoints using the <strong>bind</strong> method as you can now with the jsPlumb object.
    -		Note that binding 'beforeDrop' (as an example) on jsPlumb itself is like a catch-all: it will be called every time a Connection is dropped on _any_ 
    -		Endpoint.  But passing a 'beforeDrop' callback into an Endpoint (and in the future, registering one using 'bind') constrains that callback to just the
    -		Endpoint in question.  		
    -		<h4><a id="beforeDrop">beforeDrop</a></h4>
    -		This event is fired when a new or existing connection has been dropped. Your callback is passed a JS object with these fields:
    -		<ul>
    -			<li><strong>sourceId</strong> - the id of the source element in the connection</li>
    -			<li><strong>targetId</strong> - the id of the target element in the connection</li>
    -			<li><strong>scope</strong> - the scope of the connection</li>
    -			<li><strong>connection</strong> - the actual Connection object.  You can access the 'endpoints' array in a Connection to get the Endpoints involved in the Connection, but be aware that when a Connection is being dragged, one of these Endpoints will always be a transient Endpoint that exists only for the life of the drag. To get the Endpoint on which the Connection is being dropped, use the 'dropEndpoint' member.</li>
    -			<li><strong>dropEndpoint</strong> - this is the actual Endpoint on which the Connection is being dropped.  This <strong>may be null</strong>, because it will not be set if the Connection is being dropped on an element on which makeTarget has been called. </li>
    -		</ul>
    -
    -		If you return false (or nothing) from this callback, the new Connection is aborted and removed from the UI.
    -		<h4><a id="beforeDetach">beforeDetach</a></h4>
    -		This is called when the user has detached a Connection, which can happen for a number of reasons: by default, jsPlumb allows users to drag Connections off of target Endpoints, but this can also result from a programmatic 'detach' call.  Every case is treated the same by jsPlumb, so in fact it is possible for you to write code that attempts to detach a Connection but then denies itself!  You might want to be careful with that. 
    -		<p>Note that this interceptor is passed the actual Connection object; this is different from the beforeDrop
    -		interceptor discussed above: in this case, we've already got a Connection, but with beforeDrop we are yet 
    -		to confirm that a Connection should be created.</p>
    -		<p>Returning false - or nothing - from this callback will cause the detach to be abandoned, and the Connection will be reinstated or left on its current target.</p>
    -			
    -	</div>
    -
    -		<div class="section">
    -			<h3><a id="paintstyles">Paint Styles</a></h3>
    -			Defining the appearance of Connectors, Endpoints (and Overlays, but this is deprecated in favour of using CSS classes) is achieved through a 'paintStyle' (or a quite similar name)
    -			object passed as a parameter to one of jsPlumb.connect, jsPlumb.addEndpoint, jsPlumb.makeSource or jsPlumb.makeTarget. Depending on the method you are calling, the parameter names vary.
    -			<h5>Connector Paint Styles</h5>
    -			These are specified in a <em>paintStyle</em> parameter on a call to <strong>jsPlumb.connect</strong>:
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			or in the <em>connectorPaintStyle</em> parameter on a call to <strong>jsPlumb.addEndpoint</strong> or <strong>jsPlumb.makeSource</strong>:
    -			<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div> 			
    -			 
    -
    -Notice the <em>paintStyle</em> parameter in those examples: it is the paint style for the Endpoint, which we'll discuss below.
    -
    -			<h5>Endpoint Paint Styles</h5>
    -			These are specified in a <em>paintStyle</em> parameter on a call to <strong>addEndpoint</strong>. This is the example from just above:
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -</div> 
    -			...or as the <em>endpointStyle</em> parameter to a <strong>connect</strong> call:		
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			... or as an entry in the <em>endpointStyles</em> array passed to a <strong>jsPlumb.connect</strong> call:
    -					<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  endpointStyles:[ 
    -    { fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -    { fillStyle:"green" }
    -  ],
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			or as the <em>paintStyle</em> parameter passed to a <strong>makeTarget</strong> or <strong>makeSource</strong> call:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("el1", {
    -  ...
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  ...
    -});
    -</pre>			
    -</div>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 }
    -  parent:"someOtherDivIJustPutThisHereToRemindYouYouCanDoThis"
    -});
    -</pre>			
    -			</div>						
    -	In the first example we  made "el1" into a drop target, and defined a paint style for the Endpoint jsPlumb will create when a Connection is
    -	established.  In the second we made "el1" a connection source and assigned the same values for the Endpoint jsPlumb will create when a Connection
    -	is dragged from that element.
    -	
    -	<h5>Overlay Paint Styles</h5>
    -	The preferred way to set paint styles for Overlays is to use the <em>cssClass</em> parameter in the constructor arguments of an Overlay definition.
    -	
    -	<h4>Paint Style Parameters</h4>
    -	This is the full list of parameters you can set in a paintStyle object, but note that <strong>fillStyle</strong> is ignored by Connectors,
    -	and <strong>strokeStyle</strong> is ignored by Endpoints.  Also, if you create a Connection using jsPlumb.connect and do not specify any
    -	Endpoint styles, the Endpoints will derive their fillStyle from the Connector's strokeStyle.
    -	<p>
    -		fillStyle, strokeStyle and outlineColor can be specified using any valid CSS3 syntax.
    -	</p>
    -	<ul>
    -		<li><strong>fillStyle</strong> - color for an Endpoint, eg. rgba(100,100,100,50), "blue", "#456", "#993355", rgb(34, 56, 78).</li>
    -		<li><strong>strokeStyle</strong> - color for a Connector. see fillStyle examples.</li>
    -		<li><strong>lineWidth</strong> - width of a Connector's line. An integer.</li>
    -		<li><strong>outlineWidth</strong> - width of the outline for an Endpoint or Connector. An integer.</li>
    -		<li><strong>outlineColor</strong> - color of the outline for an Endpoint or Connector. see fillStyle examples.</li>
    -		<li><strong>dashstyle</strong> - VML and SVG only. This comes from VML, and allows you to create dashed or dotted lines.  It has a 
    -			better syntax than the equivalent attribute in SVG (<em>stroke-dasharray</em>, discussed below), so jsPlumb supports this for both renderers. 
    -			The <em>dashstyle</em> attribute is specified as an array of strokes and spaces, where each value is some multiple of <em>the width of 
    -			the Connector</em>, and that's where it's better than SVG, which uses pixels.
    -			<p>
    -			<a href="http://www.w3.org/TR/NOTE-VML" target="_blank">The VML spec</a> is a good place to find valid values for dashstyle. Note that
    -			jsPlumb does not support the string values for this attribute ("solid", "dashdot", etc).
    -			</p>
    -			<p>
    -			In SVG render mode, jsPlumb uses the <em>lineWidth</em> parameter in conjunction with the values in a <em>dashstyle</em> attribute
    -			to create an appropriate value for <em>stroke-dasharray</em>.
    -			</p>
    -		</li>
    -		<li><strong>stroke-dasharray</strong> - SVG only. This is the SVG equivalent of <em>dashstyle</em>.  
    -		<a href="http://www.w3.org/TR/SVG/painting.html" target="_blank">The SVG spec</a> discusses valid values for this parameter.  But be aware
    -		that jsPlumb does not convert this into a valid <em>dashstyle</em> attribute when using the VML renderer. Better to use <em>dashstyle</em>.
    -		</li>
    -		<li><strong>stroke-dashoffset</strong> - SVG only.  This is used in SVG to specify how far into the dash pattern to start painting.  For more information,
    -		see <a href="http://www.w3.org/TR/SVG/painting.html" target="_blank">the SVG spec.</a></li>
    -		<li><strong>joinstyle</strong> - VML and SVG only.  As with <em>dashstyle</em>, this is a VML attribute that jsPlumb supports for both VML
    -		and SVG - jsPlumb turns this into a <em>stroke-linejoin</em> attribute when rendering with SVG.  This attribute specifies how you want individual
    -		segments of connectors to be joined; the VML and SVG specs both have examples of this, of which many are the same between the two, which is
    -		why jsPlumb will automatically convert this attribute into the SVG equivalent.
    -		</li>
    -		<li><strong>stroke-linejoin</strong> - SVG only.  This is the equivalent of VML's <em>joinstyle</em> attribute, but as with <em>stroke-dasharray</em>,
    -		jsPlumb does not convert this into something approriate for VML.  So, using <em>joinstyle</em> will enable you to support more browsers with
    -		less effort.		
    -		</li>
    -	</ul>
    -	
    -	<h3><a id="hoverpaintstyles">Hover Paint Styles</a></h3>
    -	 		
    -	 		Connectors and Endpoints both support the concept of a "hover" paint style - a paint style to use when the mouse is hovering over
    -	 		the component.  These are specified in the exact same format as paint styles discussed above, but hover paint styles also inherit
    -	 		any values from the main paint style.  This is because you will typically want to just change the color, or perhaps outline color, of
    -	 		a Connector or Endpoint when the mouse is hovering, but leave everything else the same.  So having hover paint styles inherit their
    -	 		values precludes you from having to define things in more than one place.
    -	 		<p>
    -	 			The naming convention adopted for hover paint styles is pretty much to insert the word 'hover' into the corresponding main paint
    -	 			style parameters.  Here are a couple of examples:
    -	 		</p> 
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -  hoverPaintStyle:{ strokeStyle:"red" },
    -  endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  endpointHoverStyle:{ fillStyle:"red" }
    -});
    -</pre>			
    -</div>
    -			In this example we specified a hover style for both the Connector, and each of its Endpoints.  Here's the same thing, but using the plural version, to specify a different hover style for each Endpoint:	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -  hoverPaintStyle:{ strokeStyle:"red" },
    -  endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  endpointHoverStyles:[ { fillStyle:"red" }, { fillStyle:"yellow" } ]
    -});
    -</pre>			
    -</div>
    -	 		Calls to <strong>addEndpoint</strong>, <strong>makeSource</strong> and <strong>makeTarget</strong> can also specify various hover paint styles:
    -	 								<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  hoverPaintStyle:{ fillStyle:"red" },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -  connectorHoverPaintStyle:{ strokeStyle:"red", outlineColor:"yellow", outlineWidth:1 }
    -});
    -
    -jsPlumb.makeSource("el2", {
    -  paintStyle:{ fillStyle:"transparent", outlineColor:"yellow", outlineWidth:1 },
    -  hoverPaintStyle:{ fillStyle:"red" },
    -  connectorPaintStyle:{ strokeStyle:"green", lineWidth:3 },
    -  connectorHoverPaintStyle:{ strokeStyle:"#678", outlineColor:"yellow", outlineWidth:1 }
    -});
    -
    -jsPlumb.makeTarget("el3", {
    -  paintStyle:{ fillStyle:"transparent", outlineColor:"yellow", outlineWidth:1 },
    -  hoverPaintStyle:{ fillStyle:"red" }
    -});
    -</pre>			
    -			</div> 
    -	 		
    -	 		In these examples we specified a hover paint style for both the Endpoint we are adding, and any Connections to/from the Endpoint.
    -	 		<p>	
    -		Note that <strong>makeTarget</strong> does not support Connector parameters. It is for creating targets only; Connector parameters will be set by the source Endpoint in any Connections that are made to the element that you turned into a target by using this method.</p>
    -	 			 		
    -			<h4><a id="gradients">Gradients</a></h4>
    -			The Canvas and SVG renderers both support gradients. The VML renderer does not.  jsPlumb uses its own syntax to define gradients, to abstract out the differences between the syntax required by canvas and that required by SVG.  
    -			<p>There are two types of gradients available - a 'linear' gradient, which consists of colored lines all
    -			going in one direction, and a 'radial' gradient, which consists of colored circles emanating from one circle to another.
    -			Because of their basic shape, jsPlumb supports only <em>linear</em> gradients for
    -			Connectors.  But for Endpoints, jsPlumb supports both <em>linear</em> and <em>radial</em> gradients.</p>
    -			
    -				<h5>Connector gradients</h5>
    -				To specify a linear gradient to use in a Connector, you must add a <em>gradient</em> object to your
    -				Connector's <em>paintStyle</em>, for instance:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : "window2",
    -	target : "window3",
    -	paintStyle:{
    -		gradient:{
    -			stops:[[0,"green"], [1,"red"]]
    -		},
    -		lineWidth:15
    -	}
    -});
    -</pre>
    -</div>
    -Here we have connected window2 to window3 with a 15 pixel wide connector that has a gradient from green to red.</p><p> Notice the <strong>gradient</strong> object and the
    -<strong>stops</strong> list inside it - the gradient consists of an arbitrary number of these "color stops".  Each color stop is comprised
    -of two values - [position, color].  Position must be a decimal value between 0 and 1 (inclusive), and indicates where the color
    -stop is situated as a fraction of the length of the entire gradient.  Valid values for
    -the colors in the <strong>stops</strong> list are the same as those that are valid for <strong>strokeStyle</strong> when describing a color.
    -			</p>
    -			As mentioned, the <strong>stops</strong> list can hold an arbitrary number of entries.  Here's an example of a gradient that goes from red to blue to green, and back again through blue to red:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle : {
    -		gradient:{
    -			stops:[[0,'red'], [0.33,'blue'], [0.66,'green'], [0.33,'blue'], [1,'red']]
    -		},
    -		lineWidth : 15
    -	}
    -});
    -</pre>
    -</div>
    -<strong>Note:</strong> when using the VML renderer, jsPlumb will simply ignore the gradient directive
    -so it is best to ensure you also supply a <em>strokeStyle</em> in your paintStyle object, to give jsPlumb something to fall back on.  If you do not supply
    -a <em>strokeStyle</em> your Connectors will be painted black.  The previous example might look like this, for instance:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle:{
    -		strokeStyle:'red',
    -		gradient:{
    -			stops:[[0,'red'], [0.33,'blue'], [0.66,'green'], [0.33,'blue'], [1,'red']]
    -		},
    -		lineWidth:15
    -	}
    -});
    -</pre>
    -</div>
    -Notice the <strong>strokeStyle:'red'</strong> directive at the beginning of the parameter list in <strong>paintStyle</strong>.
    -			<h5>Endpoint gradients</h5>
    -			Endpoint gradients are specified using the same syntax as Connector gradients.  You put the gradient specifier either in the
    -			<strong>endpoint</strong> member, or if you are specifying different Endpoints for each end of the Connector, in one or both of
    -			the values in the <strong>endpoints</strong> array.  Also, this information applies to the case that you are creating standalone
    -			Endpoints that you will be configuring for drag and drop creation of new Connections. 
    -			<p>
    -			This is an example of an Endpoint gradient that is different for each Endpoint in the Connector.  This comes from the main demo; it is
    -			the Connector joining Window 2 to Window 3:
    -			</p>
    -<div class="code">
    -<pre>var w23Stroke = 'rgb(189,11,11)';
    -jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle:{
    -		lineWidth:8,
    -		strokeStyle:w23Stroke
    -	},
    - 	anchors:[ [0.3,1,0,1], "TopCenter" ],
    - 	endpoint:"Rectangle",
    - 	endpointStyles:[
    - 		{ gradient : {stops:[[0, w23Stroke], [1, '#558822']] } },
    -    	{ gradient : {stops:[[0, w23Stroke], [1, '#882255']] } }
    -    ]
    -});
    -</pre>
    -</div>
    -The first entry in the gradient will be the one that is on the Connector end of the Endpoint.  You can of course have as many color stops as
    -you want in this gradient, just like with Connector gradients.
    -	<h5>Applying the gradient in Endpoints</h5>
    -	Only the Dot and Rectangle endpoints honour the presence of a gradient (and, remember, not in VML). The Image endpoint of course ignores a gradient
    -	as it does no painting of its own.
    -	<p>The type of gradient you will see depends on the Endpoint type:</p>
    -		<ul>
    -			<li><strong>Dot</strong> - renders a radial endpoint, with color stop 0 on the outside, progressing inwards as we move through color stops.
    -			<p>Radial gradients actually require more data than linear gradients - in a linear gradient we just move from one point to another, whereas
    -			in a radial gradient we move from one <em>circle</em> to another.  By default, jsPlumb will render a radial gradient using a source
    -			circle of the same radius as the Endpoint itself, and a target circle of 1/3 of the radius of the Endpoint (both circles share the
    -			same center as the Endpoint itself). This circle will be offset by radius/2 in each direction.</p>
    -			<p>You can supply your own values for these inside the gradient descriptor:</p>
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect({
    -	source : 'window3',
    -	target : 'window4',
    -    paintStyle:{
    -    	lineWidth:10,
    -    	strokeStyle:w34Stroke
    -    },
    -    anchors:[ "RightMiddle", "LeftMiddle" ],
    -    endpointStyle:{
    -    	gradient : {
    -    		stops:[ [0, w34Stroke], [1, w34HlStroke] ],
    -    		offset:37.5,
    -    		innerRadius:40
    -    	},
    -    	radius:55
    -    }
    - });
    -</pre>
    -</div>
    -Here we have instructed jsPlumb to make the gradient's inner radius 10px instead of the default 25/3 = 8 ish pixels, and the offset in each direction
    -will be 5px, instead of the default radius / 2 = 12.5 pixels.
    -<p>It is also possible to specify the offset and inner radius as percentages - enter the values as strings with a '%' symbol on the end:</p>
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect({
    -	source : 'window3', 
    -	target : 'window4',
    -    paintStyle:{
    -    	lineWidth:10,
    -    	strokeStyle:w34Stroke
    -    },
    -	anchors:[ "RightMiddle", "LeftMiddle" ],
    -	endpointStyle:{
    -		gradient : {
    -			stops:[ [0, w34Stroke], [1, w34HlStroke] ],
    -			offset:'68%',
    -			innerRadius:'73%'
    -		},
    -		radius:25
    -	}
    -});
    -</pre>
    -</div>
    -This will give roughly the same output as the example above (the percentages are not entirely exact).<br/><br/>
    -			</li>
    -			<li><strong>Rectangle</strong> - renders a linear endpoint, with color stop 0 closest to the end of the Connector</li>
    -		</ul>
    -		
    -<h4><a id="svgvmlcss">Styling SVG/VML using CSS</a></h4>
    -	A nice way of controlling your UI's appearance is to make use of the fact that both SVG and VML can be styled using CSS.
    -	<p>This section will be expanded in the next few weeks to give some decent examples of how to do so</p>
    -			
    -		
    -		
    -		</div>
    -
    -		<div class="section">
    -			<h3><a id="cssclasses">CSS</a></h3>
    -			In an ideal world, you'd be able to control the appearance of your UI using CSS only, but due to difference between
    -			renderers, this is not the case.  The Canvas renderer, for instance, is completely unaware of CSS, and VML can
    -			only be styled up to a point with CSS.  SVG behaves best, and perhaps at some stage in the future when all browsers
    -			in popular usage support SVG decently, we'll be able to switch over completely to CSS.
    -			<p>For now, though, you can - and should - still use CSS for one important consideration: z-indices.  Every Connection,
    -			Endpoint and Overlay in jsPlumb adds some element to the UI, and you should take care to establish appropriate z-indices for each
    -			of these, in conjunction with the nodes in your application. 
    -			</p>
    -			<p>By default, jsPlumb adds a specific class to each of the three types of elements it creates (These class names are
    -			exposed on the jsPlumb object and can be overridden if you need to do so - see the third column in the table) </p>
    -			<table width="90%" class="table" style="align:left;font-size:12px;">
    -				<tr><th>Component</th><th>CSS Class</th><th>jsPlumb Member</th></tr>
    -				<tr><td>Connector</td><td>_jsPlumb_connector</td><td>connectorClass</td></tr>
    -				<tr><td>Endpoint</td><td>_jsPlumb_endpoint</td><td>endpointClass</td></tr>
    -				<tr><td>Overlay</td><td>_jsPlumb_overlay</td><td>overlayClass</td></tr>
    -			</table>
    -			<p>In addition to these defaults, each of the main methods you use to configure Endpoints or make Connections
    -			in jsPlumb support the following two parameters:</p>
    -			<ul>
    -				<li class="bullet"><strong>cssClass</strong> - class(es) to set on the display elements</li>
    -				<li class="bullet"><strong>hoverClass</strong> - class(es) to set on the display elements when in hover mode</li>
    -			</ul>
    -			In addition, addEndpoint and makeSource allow you to specify what these classes will be for any Connections that are dragged from them:
    -			<ul>
    -				<li class="bullet"><strong>connectorClass</strong> - class(es) to set on the display elements of Connections</li>
    -				<li class="bullet"><strong>connectorHoverClass</strong> - class(es) to set on the display elements of Connections when in hover mode</li>
    -			</ul>
    -			<p>These parameters should be supplied as a String; they will be appended as-is to the class member, so feel free to
    -			include multiple classes.  jsPlumb won't even know.</p>
    -		</div>
    -				
    -		<div class="section">
    -			<h3><a id="animation">Animation</a></h3>
    -			jsPlumb offers an 'animate' function, which wraps the underlying animation engine for whichever library you happen to be using and
    -			inserts a callback for jsPlumb to repaint whatever it needs to at each step.  You could of course do this yourself; it's a convenience
    -			method really.
    -			<p>The method signature is:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.animate : function(el, properties, options) 
    -</pre>
    -</div>			
    -The arguments are as follows:
    -	<ul>
    -		<li><strong>el</strong> - element id, or element object from the library you're using.</li>
    -		<li><strong>properties</strong> - properties for the animation, such as duration etc.</li>
    -		<li><strong>options</strong> - options for the animation, such as callbacks etc.</li>
    -	</ul>
    -	
    -		<h4>jQuery and the 'revert' option</h4>
    -		jQuery offers a 'revert' option that you can use to instruct it to revert a drag under some condition. This is
    -		rendered in the UI as an animation that returns the drag object from its current location to wherever it started
    -		out.  It's a nice feature.  Unfortunately, the animation that runs the revert does not offer any lifecycle
    -		callback methods - no 'step', no 'complete' - so its not possible for jsPlumb to know that the revert 
    -		animation is taking place.
    -	
    -		</div>
    -		
    -			<div class="section">
    -			<h3><a id="connectionInfo">Retrieving Connection Information</a></h3>
    -			There are two ways of retrieving connection information from jsPlumb. <strong>getConnections</strong>is the original method; this method has since been supplanted by <a href="#selectConnections">jsPlumb.select</a>, a much more versatile variant.
    -			<h5>getConnections</h5>
    -			<p>Before you use this method you should understand jsPlumb's notion of 'scope' - documentation
    -			is <a href="#dragAndDropScope">here</a> </p>
    -			
    -			<h4>Retrieving connections for a single scope</h4>
    -			<p>To do this, you call getConnections with either no arguments, in which case jsPlumb uses the default scope, or with a string
    -			specifying one scope</p>
    -			<div class="code">
    -<pre>var connectionList = jsPlumb.getConnections();     // you get a list of Connection objects that are in the default scope.
    -</pre>			
    -			</div>
    -			Compare this with:
    -			<div class="code">
    -<pre>var connectionList = jsPlumb.getConnections("myScope");     // you get a list of Connection objects that are in "myScope".
    -</pre>			
    -			</div>
    -			<h4>More advanced filtering</h4>
    -			getConnections optionally takes a JS object specifying filter parameters, of which there are three:
    -			<ul>
    -				<li>scope - the scope(s) of the connection type(s) you wish to retrieve</li>
    -				<li>source - limits the returned connections to those that have this source id</li>
    -				<li>target - limits the returned connections to those that have this target id</li>
    -			</ul>			
    -			Each of these three parameters may be supplied as a string, which for source and target is an element id and for scope is the name of the scope, or a list of strings.  Als, you can pass "*" in as the value for any of these - a wildcard, meaning any value.  See the examples below.
    -			<p>
    -			The return value of a call to getConnection using a JS object as parameter varies on how many scopes you defined.  If you defined only a single scope then jsPlumb returns you a list of Connections in that scope.  Otherwise the return value is a dictionary whose keys are  scope names, and whose values are lists of Connections. For example, the following call:
    -			</p>
    -			<div class="code">
    -<pre>
    -	jsPlumb.getConnections({scope:["someScope", "someCustomScope"]});
    -</pre>
    -</div>
    -would result in this output:			
    -<div class="code">
    -<pre>
    -{
    -	"someScope" : [ 1..n Connections ],
    -	"someCustomScope": [ 1..m Connections ]
    -}
    -</pre>
    -</div>	
    -
    -There is an optional second parameter that tells getConnections to flatten its output and just return you an array.  The previous example with this parameter would look like this:
    -			<div class="code">
    -<pre>
    -	jsPlumb.getConnections({scope:["someScope", "someCustomScope"]}, true);
    -</pre>
    -</div>
    -would result in this output:			
    -<div class="code">
    -<pre>
    -[ 1..n Connections ]
    -</pre>
    -</div>	
    -		
    -		The following examples show the various ways you can get connection information:
    -	<ul>
    -		<li>Get all connections:
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getAllConnections();  
    -</pre>
    -</div>						
    -		</li>
    -<li>Get all connections for the default scope only (return value is a list):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections();  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given scope (return value is a list):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:"myTestScope"});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given scopes (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:["myTestScope", "yourTestScope"]});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given source (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:"mySourceElement"});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given sources (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:["mySourceElement", "yourSourceElement"]});  
    -</pre>
    -</div>						
    -		</li>		
    -<li>Get all connections for the given target (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({target:"myTargetElement"});  
    -</pre>
    -</div>						
    -		</li>				
    -<li>Get all connections for the given source and targets (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:"mySourceElement", target:["target1", "target2"]});  
    -</pre>
    -</div>						
    -		</li>				
    -		
    -<li>Get all connections for the given scope, with the given source and target (return value is a list of connections):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:'myScope", source:"mySourceElement", target:"myTargetElement"});  
    -</pre>
    -</div>						
    -		</li>				
    -		
    -	</ul>	
    -
    -		</div>
    -
    -		<div class="section">
    -			<h3><a id="selectConnections">Selecting Connections</a></h3>
    -			<strong>jsPlumb.select</strong> provides a fluid interface for working with lists of Connections.  The syntax used to specify which Connections you want is identical to that which you use for getConnections, but the return value is an object that supports most operations that you can perform on a Connection, and which is also chainable, for setter methods. Certain getter methods are also supported, but these are not chainable; they return an array consisting of all the Connections in the selection along with the return value for that Connection.
    -			<p>
    -			This is the full list of setter operations supported by jsPlumb.select:
    -			<ul>
    -				<li class="bullet">addOverlay</li>
    -				<li class="bullet">removeOverlay</li>
    -				<li class="bullet">removeOverlays</li>
    -				<li class="bullet">showOverlay</li>
    -				<li class="bullet">hideOverlay</li>
    -				<li class="bullet">showOverlays</li>
    -				<li class="bullet">hideOverlays</li>
    -				<li class="bullet">removeAllOverlays</li>
    -				<li class="bullet">setLabel</li>
    -				<li class="bullet">setPaintStyle</li>
    -				<li class="bullet">setHoverPaintStyle</li>
    -				<li class="bullet">setDetachable</li>
    -				<li class="bullet">setConnector</li>
    -				<li class="bullet">setParameter</li>
    -				<li class="bullet">setParameters</li>
    -				<li class="bullet">detach</li>
    -				<li class="bullet">repaint</li>
    -				<li class="bullet">setType</li>
    -				<li class="bullet">addType</li>
    -				<li class="bullet">removeType</li>
    -				<li class="bullet">toggleType</li>		
    -			</ul>
    -
    -			Each of these operations returns a selector that can be chained.
    -			</p>
    -			These is the full list of getter operations supported by jsPlumb.select:
    -			<p>
    -				<ul>
    -				<li class="bullet">getLabel</li>
    -				<li class="bullet">getOverlay</li>
    -				<li class="bullet">isHover</li>
    -				<li class="bullet">isDetachable</li>
    -				<li class="bullet">getParameter</li>
    -				<li class="bullet">getParameters</li>
    -				<li class="bullet">getType</li>
    -				<li class="bullet">hasType</li>
    -			</ul>
    -
    -			Each of these operations returns an array whose entries are [ value, Connection ] arrays, where 'value' is the return value from the given Connection.  Remember that the return values from a getter are not chainable, but a getter may be called at the end of a chain of setters.
    -			</p>
    -
    -			<p>Some examples:</p>
    -			Select all Connections and set their hover state to be false:
    -
    -<div class="code">
    -<pre>
    -jsPlumb.select().setHover(false);
    -</pre>
    -</div>
    -			Select all Connections from "d1" and remove all Overlays:
    -
    -<div class="code">
    -<pre>
    -jsPlumb.select({source:"d1"}).removeAllOverlays();
    -</pre>
    -</div>
    -
    -Select all connections in scope "foo" and set their paint style to be a thick blue line:
    -<div class="code">
    -<pre>
    -jsPlumb.select({scope:"foo"}).setPaintStyle({ strokeStyle:"blue", lineWidth:5 });
    -</pre>
    -</div>
    -Select all Connections from "d1" and detach them:
    -<div class="code">
    -<pre>
    -jsPlumb.select({source:"d1"}).detach();
    -</pre>
    -</div>
    -<h5>.each iterator</h5>
    -The return value of jsPlumb.select has a <strong>.each</strong> function that allows you to iterate through the list, performing an operation on each one:
    -<div class="code">
    -<pre>
    -jsPlumb.select({scope:"foo"}).each(function(connection) {
    -	
    -		// do something 
    -});
    -</pre>
    -</div>
    -.each is itself chainable:
    -<div class="code">
    -<pre>
    -jsPlumb.select({scope:"foo"}).each(function(connection) {
    -	
    -		// do something 
    -
    -}).setHover(true);
    -</pre>
    -</div>
    -
    -
    -<h5>Other properties/functions</h5>
    -<ul>
    -	<li class="bullet"><strong>length</strong> - this member reports the number of Connections in the selection</li>
    -	<li class="bullet"><strong>get(idx)</strong> - this function allows you to retrieve a Connection from the selection</li>
    -</ul>
    -
    -
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="selectEndpoints">Selecting Endpoints</a></h3>
    -			<strong>jsPlumb.selectEndpoints</strong> provides a fluid interface for working with lists of Endpoints.
    -			The syntax used to specify which Endpoints you want is identical to that which you use for jsPlumb.select,
    -			and the return value is an object that supports most operations that you can perform on an Endpoint (and
    -			which is also chainable, for setter methods). Certain getter methods are also supported, but these are not
    -			chainable; they return an array consisting of all the Endpoints in the selection along with the return
    -			value for that Endpoint.
    -			<p>
    -			<p>
    -				Four parameters are supported by selectEndpoints - each of these except 'scope' can be provided as either
    -				a string, a selector, a DOM element, or an array of a mixture of these types.  'scope' can be provided as either
    -				a string or an array of strings:
    -				<ul>
    -					<li class="bullet">element - element(s) to get both source and target endpoints from</li>
    -					<li class="bullet">source - element(s) to get source endpoints from</li>
    -					<li class="bullet">target - element(s) to get target endpoints from</li>
    -					<li class="bullet">scope - scope(s) for endpoints to retrieve.</li>					
    -				</ul>
    -			</p>
    -			This is the full list of setter operations supported by jsPlumb.selectEndpoints:
    -			<ul>
    -				<li class="bullet">addOverlay</li>
    -				<li class="bullet">removeOverlay</li>
    -				<li class="bullet">removeOverlays</li>
    -				<li class="bullet">showOverlay</li>
    -				<li class="bullet">hideOverlay</li>
    -				<li class="bullet">showOverlays</li>
    -				<li class="bullet">hideOverlays</li>
    -				<li class="bullet">removeAllOverlays</li>
    -				<li class="bullet">setLabel</li>
    -				<li class="bullet">setPaintStyle</li>
    -				<li class="bullet">setHoverPaintStyle</li>				
    -				<li class="bullet">setConnector</li>
    -				<li class="bullet">setParameter</li>
    -				<li class="bullet">setParameters</li>
    -				<li class="bullet">repaint</li>
    -				<li class="bullet">setType</li>
    -				<li class="bullet">addType</li>
    -				<li class="bullet">removeType</li>
    -				<li class="bullet">toggleType</li>
    -			</ul>
    -
    -			Each of these operations returns a selector that can be chained.
    -			</p>
    -			These is the full list of getter operations supported by jsPlumb.select:
    -			<p>
    -				<ul>
    -					<li class="bullet">getLabel</li>
    -					<li class="bullet">getOverlay</li>
    -					<li class="bullet">isHover</li>				
    -					<li class="bullet">getParameter</li>
    -					<li class="bullet">getParameters</li>
    -					<li class="bullet">getType</li>
    -					<li class="bullet">hasType</li>
    -				</ul>
    -
    -			Each of these operations returns an array whose entries are [ value, Endpoint ] arrays, where 'value'
    -			is the return value from the given Endpoint. Remember that the return values from a getter are not chainable,
    -			but a getter may be called at the end of a chain of setters.
    -			</p>
    -			<p>
    -				Other methods (not chainable):
    -				<ul>
    -					<li class="bullet">delete - deletes the Endpoints in the selection</li>
    -					<li class="bullet">detachAll - detaches all Connections from the Endpoints in the selection </li>
    -				</ul>
    -			</p>
    -
    -			<p>Some examples:</p>
    -			Select all Endpoints and set their hover state to be false:
    -
    -<div class="code">
    -<pre>
    -jsPlumb.selectEndpoints().setHover(false);
    -</pre>
    -</div>
    -			Select all source Endpoints on "d1" and remove all Overlays:
    -
    -<div class="code">
    -<pre>
    -jsPlumb.selectEndpoints({source:"d1"}).removeAllOverlays();
    -</pre>
    -</div>
    -
    -Select all Endpoints in scope "foo" and set their fill style to be blue:
    -<div class="code">
    -<pre>
    -jsPlumb.selectEndpoints({ scope:"foo" }).setPaintStyle({ fillStyle:"blue" });
    -</pre>
    -</div>
    -Select all Endpoints from "d1" and detach their Connections:
    -<div class="code">
    -<pre>
    -jsPlumb.selectEndpoints({source:"d1"}).detachAll();
    -</pre>
    -</div>
    -<h5>.each iterator</h5>
    -The return value of jsPlumb.selectEndpoints has a <strong>.each</strong> function that allows you to iterate through the list, performing an operation on each one:
    -<div class="code">
    -<pre>
    -jsPlumb.selectEndpoints({scope:"foo"}).each(function(endpoint) {
    -	
    -		// do something 
    -});
    -</pre>
    -</div>
    -.each is itself chainable:
    -<div class="code">
    -<pre>
    -jsPlumb.selectEndpoints({scope:"foo"}).each(function(endpoint) {
    -	
    -		// do something 
    -
    -}).setHover(true);
    -</pre>
    -</div>
    -
    -
    -<h5>Other properties/functions</h5>
    -<ul>
    -	<li class="bullet"><strong>length</strong> - this member reports the number of Endpoints in the selection</li>
    -	<li class="bullet"><strong>get(idx)</strong> - this function allows you to retrieve an Endpoint from the selection</li>
    -</ul>
    -
    -
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="events">Events</a></h3>
    -			jsPlumb supports binding to several different events on Connections, Endpoints and Overlays, and also on the jsPlumb object itself.  
    -			
    -			<h4><a id="jsPlumbEvents">jsPlumb Events</a></h4>
    -			<p>To bind an event to jsPlumb itself, use <strong>jsPlumb.bind(event, callback)</strong>:</p>
    -			<div class="code">
    -<pre>
    -jsPlumb.bind("jsPlumbConnection", function(info) {
    -   .. update your model in here, maybe.
    -});
    -</pre>
    -</div>
    -<p>These are the events you can bind to on the jsPlumb class:</p>
    -			<ul>
    -				<li class="bullet"><strong>jsPlumbConnection</strong> - notification a Connection was established.
    -					<p>
    -						The first argument to the callback is an object with the following properties:					
    -					</p>
    -					<div class="code">
    -<pre>
    -{
    -  connection 		: 	the new Connection.  you can register listeners on this etc.
    -  sourceId 		:	id of the source element in the Connection
    -  targetId		:	id of the target element in the Connection
    -  source		:	the source element in the Connection
    -  target		:	the target element in the Connection
    -  sourceEndpoint	:	the source Endpoint in the Connection
    -  targetEndpoint	:	the targetEndpoint in the Connection
    -}
    -</pre>
    -					</div>
    -The second argument is the original mouse event that caused the Connection, if any.
    -
    -					<p>
    -All of the source/target properties are actually available inside the Connection object, but - for one of those rubbish historical reasons - are provided separately because of a vagary of the <em>jsPlumbConnectionDetached</em> callback, which is discussed below.
    -</p>
    -				</li>
    -				<li class="bullet"><strong>jsPlumbConnectionDetached</strong> - notification a Connection was detached.  
    -					<p>
    -						As with <em>jsPlumbConnection</em>, the first argument to the callback is an object with the following properties:					
    -					</p>
    -					<div class="code">
    -<pre>
    -{
    -  connection		: 	the Connection that was detached.  
    -  sourceId		:	id of the source element in the Connection <em>before it was detached</em>
    -  targetId		:	id of the target element in the Connection before it was detached
    -  source		:	the source element in the Connection before it was detached
    -  target		:	the target element in the Connection before it was detached
    -  sourceEndpoint	:	the source Endpoint in the Connection before it was detached
    -  targetEndpoint	:	the targetEndpoint in the Connection before it was detached
    -}
    -</pre>
    -
    -					</div>	
    -The second argument is the original mouse event that caused the disconnection, if any. 					
    -								
    -					<p>
    -As mentioned above, the source/target properties are provided separately from the Connection, because this event is fired whenever
    -a Connection is either detached and abandoned, or detached from some Endpoint and attached to another.  In the latter case, the
    -Connection that is passed to this callback is in an indeterminate state (that is, the Endpoints are still in the state they are
    -in when dragging, and do not reflect static reality), and so the source/target properties give you the real story.
    -</p>
    -<p> Like I said above, this is really just for rubbish historical reasons.  I'm attempting to document my way out of it!</p>
    -				
    -				</li>
    -
    -				<li class="bullet"><strong>connectionDrag</strong> - notification an existing Connection is being dragged. The callback is passed the Connection.
    -				</li> 
    -
    -				<li class="bullet"><strong>connectionDragStop</strong> - notification a Connection drag has stopped. This is only fired for existing Connections. The callback is passed the Connection that was just dragged.
    -				</li> 
    -
    -				<li class="bullet"><strong>click</strong> - notification a Connection was clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification a Connection was double-clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>endpointClick</strong> - notification an Endpoint was clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>endpointDblClick</strong> - notification an Endpoint was double-clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>contextmenu</strong> - a right-click on some given component.  jsPlumb will report right clicks on both Connections and Endpoints; the callback is passed the component that received the right-click.				
    -				<li class="bullet"><strong>beforeDrop</strong>
    -					This event is fired when a new or existing connection has been dropped. Your callback is passed a JS object with these fields:
    -					<ul>
    -						<li><strong>sourceId</strong> - the id of the source element in the connection</li>
    -						<li><strong>targetId</strong> - the id of the target element in the connection</li>
    -						<li><strong>scope</strong> - the scope of the connection</li>
    -						<li><strong>connection</strong> - the actual Connection object.  You can access the 'endpoints' array in a Connection to get the Endpoints involved in the Connection, but be aware that when a Connection is being dragged, one of these Endpoints will always be a transient Endpoint that exists only for the life of the drag. To get the Endpoint on which the Connection is being dropped, use the 'dropEndpoint' member.</li>
    -						<li><strong>dropEndpoint</strong> - this is the actual Endpoint on which the Connection is being dropped.  This <strong>may be null</strong>, because it will not be set if the Connection is being dropped on an element on which makeTarget has been called. </li>
    -					</ul>
    -
    -					If you return false (or nothing) from this callback, the new Connection is aborted and removed from the UI.
    -				</li>					
    -				<!--li class="bullet"><strong>beforeDrag</strong>
    -					This event is fired when a new connection is about to be dragged.  Your callback function
    -					is passed the Connection that jsPlumb has just created.  If you return false (or nothing) 
    -					from this callback, the new Connection is aborted.
    -				</li-->
    -				<li class="bullet"><strong>beforeDetach</strong>
    -					This event is fired when a Connection is about to be detached, for whatever reason. Your callback function is passed the Connection that the user has just detached. Returning false from this interceptor aborts the Connection detach.
    -				</li>
    -							
    -			</ul>
    -			<h4><a id="connectionEvents">Connection Events</a></h4>		
    -			<p>To bind to an event on a Connection, you also use the <strong>bind</strong> method:</p>
    -			<div class="code">
    -<pre>
    -var connection = jsPlumb.connect({source:"d1", target:"d2"});
    -connection.bind("click", function(conn) {
    -	console.log("you clicked on ", conn);
    -});
    -</pre>
    -</div>
    -<p>These are the Connection events to which you can bind a listener:</p>
    -			<ul>
    -				<li class="bullet"><strong>click</strong> - notification a Connection was clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification a Connection was double-clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>contextmenu</strong> - a right-click on the Connection. The callback is passed the Connection and the original event.
    -				<li class="bullet"><strong>mouseenter</strong> - notification the mouse entered the Connection's path.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>mouseexit</strong> - notification the mouse exited the Connection's.  The callback is passed the Connection and the original mouse event.</li>
    -			</ul>
    -			<h4><a id="endpointEvents">Endpoint Events</a></h4>			
    -			<p>To bind to an event on a Endpoint, you again use the <strong>bind</strong> method:</p>
    -			<div class="code">
    -<pre>
    -var endpoint = jsPlumb.addEndpoint("d1", { someOptions } );
    -endpoint.bind("click", function(endpoint) {
    -	console.log("you clicked on ", endpoint);
    -});
    -</pre>
    -</div>
    -<p>These are the Endpoint events to which you can bind a listener:</p>
    -			<ul>
    -				<li class="bullet"><strong>click</strong> - notification an Endpoint was clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification an Endpoint was double-clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>contextmenu</strong> - a right-click on the Endpoint. The callback is passed the Endpoint and the original event.
    -				<li class="bullet"><strong>mouseenter</strong> - notification the mouse entered the Endpoint.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>mouseexit</strong> - notification the mouse exited the Endpoint.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>maxConnections</strong> - notification the user tried to drop a Connection on an Endpoint that already has the maximum number of Connections.  The callback is passed as first argument an object literal containing the Endpoint, the Connection the user tried to drop, and the value of maxConnections for the Endpoint, and as second argument the original mouse event.</li>
    -			</ul>
    -			<h4><a id="overlayEvents">Overlay Events</a></h4>
    -			Registering event listeners on an Overlay is a slightly different procedure - you provide them as arguments to the Overlay's 
    -			constructor.  This is because you never actually act on an 'Overlay' object.  Here's how to register a click listener on an Overlay:
    -			<div class="code">
    -<pre>jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  overlays:[
    -    [ "Label", {
    -      events:{
    -        click:function(labelOverlay, originalEvent) { 
    -          console.log("click on label overlay for :" + labelOverlay.component); 
    -        }
    -      }
    -    }],
    -    [ "Diamond", {
    -      events:{
    -        dblclick:function(diamondOverlay, originalEvent) { 
    -          console.log("double click on diamond overlay for : " + diamondOverlay.component); 
    -        }
    -      }
    -    }] 	
    -  ]
    -});
    -</pre>			
    -			</div>		
    -		Note that events registered on Diamond, Arrow or PlainArrow overlays will not fire with the Canvas renderer - they work only with the SVG and VML renderers. 
    -
    -
    -		<h4><a id="jsPlumbEvents">Unbinding Events</a></h4>
    -		<p>On the jsPlumb object and on Connections and Endpoints, you can use the <strong>unbind</strong> method to remove a listener.  This method either takes the name of the event to unbind:
    -		<div class="code">
    -<pre>
    -jsPlumb.unbind("click");
    -</pre>
    -</div>		
    -...or no argument, meaning unbind all events:
    -	<div class="code">
    -<pre>
    -var e = jsPlumb.addEndpoint("someDiv");
    -e.bind("click", function() { ... });
    -e.bind("dblclick", function() { ... });
    -
    -...
    -
    -e.unbind("click");
    -</pre>
    -</div>
    -
    -		</div>
    -
    -				<div class="section">
    -			<a id="examples"><h3>jsPlumb.connect Examples</h3></a>
    -This section provides examples of how to use the programmatic API to establish Connections. 
    -			<p>
    -			The basic syntax of a call is that you execute 'connect', providing a source and a target, and optionally a paintStyle and preferences for where you
    -			want the plumbing to be anchored on each element, as well as the type of connector to use. 
    -			</p>
    -			<ul>
    -				<li>Connect window1 to window2 with the default settings:
    -					<div class="code">
    -						<pre>jsPlumb.connect({source:"window1", target:"window2"});</pre>
    -					</div>
    -This connects 'window1' to 'window2' using the jsPlumb defaults - a red Bezier curve Connector and black Dot Endpoints.
    -				</li>
    -				<li>Connect window1 to window2 with a 15 pixel wide yellow Connector, and a slightly brighter endpoint (remember the default Endpoint is a Dot):
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -					</div>
    -				</li>
    -				
    -				<li>Connect window1 to window2 with a 15 pixel wide yellow Connector, and a slightly brighter endpoint:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -					</div>
    -				</li>				
    -				<li>Connect window3 to 'window4' with a 10 pixel wide, semi opaque blue Connector, anchored to the left middle of window3, and the right middle of window4, with a Rectangle endpoint of width 10 and height 8:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window3',
    -	target:'window4',
    -	paintStyle:{ lineWidth:10, strokeStyle:'rgba(0, 0, 200, 0.5)' },
    -	anchors:["RightMiddle", "LeftMiddle"],
    -	endpoint:[ "Rectangle", { width:10, height:8 } ]
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window2 to window3 with a default Connector from the top center of window2 to the bottom center of window3, and rectangular endpoints:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window2',
    -	target:'window3',
    -	paintStyle:{lineWidth:8, strokeStyle:'rgb(189,11,11)'},
    -	anchors:["BottomCenter", "TopCenter"],
    -	endpoint:"Rectangle"
    -});</pre>
    -					</div>
    -				</li>
    -
    -
    -				<li>Connect window1 to window2 with a 15 px wide yellow Bezier. endpoints are a slightly lighter shade of yellow.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	anchors:["BottomCenter", [0.75,0,0,-1]],
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -</div></li>
    -<li>Connect window3 to window4 with a 10px wide blue-ish half transparent Bezier. put endpoints underneath the element they attach to.
    -the endpoints have a radial gradient. both ways of specifying gradient positioning are shown here.
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect( {
    -	source:'window3',
    -	target:'window4',
    -		 paintStyle:{lineWidth:10, strokeStyle:w34Stroke},
    -		 anchors:["RightMiddle", "LeftMiddle"],
    -		 endpointStyle:{ gradient : {stops:[[0, w34Stroke], [1, w34HlStroke]], offset:17.5, innerRadius:15 }, radius:35},
    -		 //endpointStyle:{ gradient : {stops:[[0, w34Stroke], [1, w34HlStroke]], offset:'78%', innerRadius:'73%'}, radius:35 },
    -		 endpointsOnTop:false
    -	}
    -);</pre>
    -</div></li>
    -<li>Connect window2 to window3 with an 8px red Bezier and default rectangular endpoints.  see also how the first anchor is
    -specified here - this is how you create anchors in locations jsPlumb does not offer shortcuts for.
    -the endpoints in this example have linear gradients applied.
    -<div class="code">
    -<pre>var w23Stroke = 'rgb(189,11,11)';
    -jsPlumb.connect({
    -	source:'window2',
    -	target:'window3',
    -	paintStyle:{lineWidth:8,strokeStyle:w23Stroke},
    -	anchors:[[0.3,1,0,1], "TopCenter"],
    -	endpoint:"Rectangle",
    -	endpointStyles:[{ gradient : {stops:[[0, w23Stroke], [1, '#558822']] }},
    -       				{ gradient : {stops:[[0, w23Stroke], [1, '#882255']] }}]
    -});</pre>
    -</div></li>
    -
    -<li>Connect window5 to window6 from center to center, 5px wide line that is green and half transparent. the endpoints are
    -125px in radius and spill out from underneath their elements.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window5',
    -	target:'window6',
    -	anchors:["Center", "Center"],
    -	paintStyle:{lineWidth:5,strokeStyle:'rgba(0,255,0,0.5)'},
    -	endpointStyle:{radius:125}
    -});</pre>
    -</div></li>
    -
    -<li>Connect window4 to window5 from bottom right to top left, with a 7px straight line purple connector, and an image as the endpoint,
    -placed on top of the element it is connected to.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window4",
    -	target:"window5",
    -	anchors:[ "BottomRight","TopLeft" ],
    -	paintStyle:{ lineWidth:7, strokeStyle:"rgb(131,8,135)" },
    -	endpoint:[ "Image", { src:"http://morrisonpitt.com/jsPlumb/img/endpointTest1.png" } ],
    -	connector:"Straight"
    -});</pre>
    -</div></li>
    -
    -
    -				<li>Connect window5 to window6 between their center points with a semi-opaque connector, and 125px endpoints:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window5",
    -	target:"window6",
    -	anchors:[ "Center", "Center" ],
    -	paintStyle:{ lineWidth:5, strokeStyle:"rgba(0,255,0,0.5)" },
    -	endpointStyle:{ radius:125 }
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window7 to window8 with a 10 pixel wide blue Connector, anchored on the top left of window7 and the bottom right of window8:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window7",
    -	target:"window8",
    -	paintStyle:{ lineWidth:10, strokeStyle:"blue" },
    -	anchors:[ "TopLeft", "BottomRight" ]
    -});
    -</pre>
    -					</div>
    -				</li>
    -				<li>Connect the bottom right corner of window4 to the top left corner of window5, with rectangular endpoints of size 40x40 and a hover color of light blue:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window4",
    -	target:"window5",
    -	anchors:["BottomRight","TopLeft"],
    -	paintStyle:{lineWidth:7,strokeStyle:'rgb(131,8,135)'},
    -	hoverPaintStyle:{ strokeStyle:"rgb(0, 0, 135)" },
    -	endpointStyle:{ width:40, height:40 },
    -	endpoint:"Rectangle",
    -	connector:"Straight"
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window1 to window2 with the default paint settings but provide some drag options (which are passed through to the underlying library's draggable call):
    -					<div class="code">
    -						<pre>jsPlumb.connect({source:'window1', target:'window2', dragOptions:{cursor:'crosshair'}});</pre>
    -					</div>
    -				</li>
    -</div>
    -		
    -		<div class="section">
    -			<a id="dragAndDropExamples"><h3>Draggable Connections Examples</h3></a>
    -			This is a list of examples of how to use jsPlumb to create Connections using drag and drop.
    -			<p>
    -			The basic procedure is:
    -			<ol>
    -				<li>1a. Create Endpoints and register them on elements in your UI</li>
    -				<li>1b. alternatively, create a source endpoint and then make some element a drop target </li>
    -				<li>2. Drag and Drop</li>
    -			</ol>
    -			That's all there is to it.  Of course there are plenty of options you can set when doing this...it will be easier
    -			to show you some examples:
    -			</p>
    -			<ul>
    -				<li>Define an Endpoint with default appearance, that is both a source and target of new Connections:
    -<div class="code">
    -<pre>var endpointOptions = { isSource:true, isTarget:true }; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Register that Endpoint on window3, specifying that it should be located in the top center of the element:
    -<div class="code">
    -<pre>var window3Endpoint = jsPlumb.addEndpoint('window3', { anchor:"TopCenter" }, endpointOptions );  
    -</pre>
    -</div>
    -Notice here the usage of the three-argument addEndpoint  - we can reuse 'endpointOptions' with a different Anchor for another element.  This is a useful
    -practice to get into.<br/><br/>
    -				</li>
    -				
    -				<li>Now register that Endpoint on window4, specifying that it should be located in the bottom center of the element:
    -<div class="code">
    -<pre>var window4Endpoint = jsPlumb.addEndpoint('window4', { anchor:"BottomCenter" }, endpointOptions );  
    -</pre>
    -</div>
    -Now we have two Endpoints, both of which support drag and drop of new Connections.  We can use these to make a programmatic Connection, too, though:<br/><br/>
    -				</li>				
    -				
    -				<li>Connect window3 to window4 with a 25px wide yellow Bezier that has a 'curviness' of 175:
    -<div class="code">
    -<pre>jsPlumb.connect({ 
    -	source:window3Endpoint,
    -	target:window4Endpoint,
    -	connector: [ "Bezier", 175 ],
    -	paintStyle:{ lineWidth:25, strokeStyle:'yellow' }
    -});  
    -</pre>
    -</div>
    -				</li>
    -				
    -				<li>Define an Endpoint that creates Connections that are 20px wide straight lines, that is both a source and target of new Connections,
    -				and that has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Define an Endpoint that will be anchored to "TopCenter".  It creates Connections that are 20px wide straight lines, that is both a source and target of new Connections,
    -				and that has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchor:"TopCenter", 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Define an Endpoint that will create a dynamic anchor which can be positioned at "TopCenter" or "BottomCenter".  It creates Connections that are 20px wide straight lines, it is both a source and target of new Connections,
    -				and it has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchor:[ "TopCenter", "BottomCenter" ], 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Exactly the same as before, but shows how you can use "anchors" instead of "anchor", if that makes you feel happier:
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchors:[ "TopCenter", "BottomCenter" ], 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>								
    -				
    -<li>Define an Endpoint that is a 30px blue dot, creates Connections that are 20px wide straight lines, is both a source and target of new Connections,
    -				has a 'scope' of 'blueline', and has an event handler that pops up an alert (note: the event handler name means this example is jQuery - MooTools
    -				and YUI3 use different event handler names):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{fillStyle:'blue'},
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ drop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -			<li>Same example as before, but this is for MooTools, and the Endpoint can support up to 5 connections (the default is 1):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{ fillStyle:'blue' },
    -	maxConnections:5,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ onDrop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Same example again, but maxConnections being set to -1 means that the Endpoint has no maximum limit of Connections:
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", {radius:30} ],
    -	style:{ fillStyle:'blue' },
    -	maxConnections:-1,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ onDrop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Same example again, but for YUI3.  Note the drop callback is "drop:hit":
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{fillStyle:'blue'},
    -	maxConnections:-1,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ "drop:hit":function(e, ui) { alert('drop!'); } }	 	
    -}; 
    -</pre>
    -</div>				 				
    -				</li>				
    -				
    -	<li>Assign a UUID to the endpoint options created above, and add as Endpoints to "window1" and "window2":
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("window1", { uuid:"abcdefg" }, endpointOptions );
    -jsPlumb.addEndpoint("window2", { uuid:"hijklmn" }, endpointOptions );
    -</pre>
    -</div>	
    -	</li>
    -	
    -	<li>Connect the two Endpoints we just registered on "window1" and "window2":
    -<div class="code">
    -<pre>
    -jsPlumb.connect({uuids:["abcdefg", "hijklmn"]});
    -</pre>
    -</div>	
    -	</li>
    -	
    -	<li>Create a source Endpoint, register it on some element, then make some other element a Connection target:
    -<div class="code">
    -<pre>
    -var sourceEndpoint = { isSource:true, endpoint:[ "Dot", { radius:50 } ] };
    -var targetEndpoint = { endpoint:[ "Rectangle", { width:10, height:10 } ] };
    -jsPlumb.addEndpoint( "window1", sourceEndpoint );
    -jsPlumb.makeTarget( "window2", targetEndpoint );
    -</pre>
    -</div>	
    -Notice that the endpoint definition we use on the target window does not include the "isTarget:true" directive.  jsPlumb ignores that flag when
    -creating a connection using an element as the target; but if you then tried to drag another connection to the Endpoint that was created, you would
    -not be able to.  To permit that, you would set 'isTarget:true' on the targetEndpoint options defined above.
    -	
    -	</li>
    -				
    -			</ul>
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="utilityFunctions">Utility Functions</a></h3>
    -			<ul>
    -				<li>Detach window5 from all connections
    -					<div class="code">
    -						<pre>jsPlumb.detachAll("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Hide all window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.hide("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Hide all window5's connections endpoints
    -					<div class="code">
    -						<pre>jsPlumb.hide("window5", true);</pre>
    -					</div>
    -				</li>
    -				<li>Show all window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.show("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Show all window5's connections and endpoints.  Note that in the case that you call jsPlumb.show with two arguments, jsPlumb
    -				will also not make a connection visible if it determines that the other endpoint in the connection is not visible.
    -					<div class="code">
    -						<pre>jsPlumb.show("window5", hide);</pre>
    -					</div>
    -				</li>				
    -				<li>Toggle the visibility of window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.toggle("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of all of window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.repaint("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of all of window5, window6 and window11's connections
    -					<div class="code">
    -						<pre>jsPlumb.repaint( [ "window5", "window6", "window11" ] );</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of every connection
    -					<div class="code">
    -						<pre>jsPlumb.repaintEverything();</pre>
    -					</div>
    -				</li>
    -				<li>Detach every connection that the given instance of jsPlumb is managing
    -					<div class="code">
    -						<pre>jsPlumb.detachEveryConnection();</pre>
    -					</div>
    -				</li>
    -				<li>Detach all connections from "window1"
    -					<div class="code">
    -						<pre>jsPlumb.detachAllConnections("window1");</pre>
    -					</div>
    -				</li>
    -				<li>Remove the given Endpoint from element "window1", deleting its Connections. 
    -					<div class="code">
    -						<pre>jsPlumb.removeEndpoint("window1", someEndpoint);</pre>
    -					</div>
    -				</li>
    -				<li>Remove all Endpoints for the element 'window1', deleting their Connections.
    -					<div class="code">
    -						<pre>jsPlumb.removeAllEndpoints("window1");</pre>
    -					</div>
    -				</li>	
    -				<li>Removes every Endpoint managed by this instance of jsPlumb, deleting all Connections.
    -				This is the same as jsPlumb.reset(), effectively, but it does not clear out the event listeners list. 
    -					<div class="code">
    -						<pre>jsPlumb.removeEveryEndpoint();</pre>
    -					</div>
    -				</li>
    -				<li>Deletes the given Endpoint and all its Connections. 
    -					<div class="code">
    -						<pre>jsPlumb.deleteEndpoint(endpoint);</pre>
    -					</div>
    -				</li>
    -				<li>Removes every endpoint, detaches every connection, and clears the event listeners list.  Returns jsPlumb instance to its initial state.  
    -					<div class="code">
    -						<pre>jsPlumb.reset();</pre>
    -					</div>
    -				</li>	
    -				<li>Library-agnostic method to get a selector from the given selector specification.  This function is used mostly by the jsPlumb demos, to cut down on repeating code across the different libraries.  But perhaps you are writing something with jsPlumb that you also wisht to be library agnostic - this function can help you. The returned object, in each of jQuery, MooTools and YUI, has a ".each" iterator.
    -					<div class="code">
    -						<pre>var selector = jsPlumb.getSelector(".someSelector .spec");</pre>
    -					</div>					
    -				</li>
    -				<li>Set window1 to be not draggable, no matter what some jsPlumb command may request.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggable("window1", false);</pre>
    -					</div>
    -				</li>
    -				<li>Set window1 and window2 to be not draggable, no matter what some jsPlumb command may request.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggable(["window1","window2"], false);</pre>
    -					</div>
    -				</li>				
    -				<li>Initialises window1 as a draggable element (all libraries). Passes in an on drag callback
    -				<div class="code">
    -					<pre>jsPlumb.draggable("window1");</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 and window2 as draggable elements (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(["window1","window2"]);</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable("window1");</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (jQuery)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (MooTools)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($$(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (YIU3)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(Y.all(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (jQuery)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($("#window1"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (MooTools)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($("window1"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (YUI3)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(Y.one("window1"));</pre>
    -				</div>
    -				</li>
    -			</ul>
    -		</div>					
    -		
    -		<div class="section">
    -			<h3><a id="developingJsPlumb">Advanced Topics</a></h3>
    -			<h4><a id="which">Which files are which?</a></h4>
    -			In development, jsPlumb is broken up into nine scripts:
    -				<ul>
    -					<li>- jsPlumb-util-x.x.x.js
    -					<p>There are the jsPlumb utility functions (and some base classes).</p>					
    -					</li>
    -					<li>- jsPlumb-x.x.x.js
    -					<p>This is the main jsPlumb engine.</p>					
    -					</li>
    -					<li>- jsPlumb-defaults-x.x.x.js
    -					<p>This contains the default Anchor, Endpoint, Connector and Overlay implementations.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-canvas-x.x.x.js
    -					<p>This contains the HTML5 canvas render code.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-svg-x.x.x.js
    -					<p>This contains the SVG render code.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-vml-x.x.x.js
    -					<p>This contains the VML render code.</p>
    -					</li>
    -					<li>- jsPlumb-connectors-statemachine-x.x.x.js
    -					<p>This State Machine connector.</p>
    -					</li>
    -					<li>- &lt;LIBRARY_PREFIX&gt;.jsPlumb-x.x.x.js
    -					<p>This contains library-specific helper methods.  jsPlumb ships with three of these - one each for jQuery, MooTools and YUI3. See below for information on how to create a new library implementation.</p>
    -					</li>
    -					<li>- jsBezier-0.4-min.js
    -					<p>These are the Bezier curve functions; they are maintained in a separate project called <a href="http://code.google.com/p/jsbezier/">jsBezier</a></p>
    -					</li>
    -				</ul>
    -			These seven files are packaged together to form the scripts that people use, for example:
    -			<ul>
    -				<li>jquery.jsPlumb-1.3.14-all.js
    -				<p>Contains jsPlumb-1.3.14.js, jsPlumb-defaults-1.3.14.js, jsPlumb-renderers-canvas-1.3.14.js, jsPlumb-renderers-svg-1.3.14.js, jsPlumb-renderers-vml-1.3.14.js, jsPlumb-connectors-statemachine-1.3.14.js, jquery.jsPlumb-1.3.14.js and jsBezier-0.4-min.js</p>
    -				</li>
    -				<li>jquery.jsPlumb-1.3.14-all-min.js
    -				<p>A minified version of the script above (minified using the YUI Compressor)</p>
    -				</li>
    -			</ul>
    -			<h4><a id="pluggableLibrarySupport">Pluggable Library Support</a></h4>
    -		Out of the box, jsPlumb can be run on top of jQuery, MooTools or YUI3.  This is achieved by
    -		delegating several core methods - tasks such as finding an element by id, finding an element's
    -		position or dimensions, initialising a draggable, etc - to the library in question.
    -		<p>To develop one of these, your test page should include the first two scripts discussed above, and
    -		then your own script containing your library specific functionality.  The existing implementations
    -		may be documented well enough for you to create your own, but contact me if you need assistance.  If you do this, it would be
    -		great to share it with everyone...</p>   
    -		<p>There is a group of people working on an ExtJS adapter currently.  There is no schedule for when this will be completed though.</p>
    -
    -			<!-- h4><a id="customConnectors">Custom Connectors</a></h4>			
    -					
    -			You can provide your own connectors if you need to. A Connector consists of two functions, which work as a pair.  First a call is made to the <em>compute</em> function:
    -
    -			<div class="code">
    -<pre>
    -this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) {
    -	...
    -	return dimensions;
    -}
    -</pre>
    -			</div>
    -			which is expected to return a list that the <em>paint</em> function can make sense of.  The first four entries in the
    -			list <strong>must be</strong> the [x,y,width,height] values for the canvas that the connector will be drawn on; jsPlumb will
    -			use this information to size the canvas prior to calling the Connector's <em>paint</em> function.  Therefore it
    -			is the Connector's responsibility to ensure that the returned dimensions describe a large enough space for
    -			the line that will be drawn on it.</p><p>The next four elements <strong>must be</strong> the coordinates of the two endpoints of the line
    -			you are going to draw.</p><p>The remainder of the items in the returned list are arbitrary, and will
    -			vary between Connector implementations; this list is passed in to a Connector's <em>paint</em> function, so each
    -			implementation will put into the list whatever it needs to paint itself.  For instance, the straight line
    -			connector only needs the [x,y] location of each end of the line it will paint, and that is one of the required entries, so
    -			it does not have to do anything extra,  whereas the Bezier connector adds the location of the two control points.  Other types of
    -			Connectors will do whatever is appropriate for their particular situation.
    -			</p>
    -			This is the method signature for the <em>paint</em> function:
    -			<div class="code">
    -				<pre>this.paint = function(dimensions, ctx) { .. }</pre>
    -			</div>
    -
    -			here, the 'dimensions' argument to the 'paint' function is the return value of the 'compute' function.  The 'ctx' argument is the Canvas context; you
    -			will do all your drawing on this.
    -			</p>
    -			To change the connector from the default, specify it in your connect call:
    -			<div class="code">
    -				<pre>jsPlumb.connect({source:'someWindow', target:'otherWindow', connector:new jsPlumb.Connectors.Straight()});</pre>
    -			</div>
    -			note that you can use shorthand for connectors if you don't need to specify parameters to it:
    -			<div class="code">
    -				<pre>jsPlumb.connect({source:'someWindow', target:'otherWindow', connector:"Straight"});</pre>
    -			</div>
    -			This works in the same way as specifying Anchors as string does.
    -<p>
    -The section below this discusses Overlays, which allow you to decorate Connectors with
    -			things such as labels or arrows or whatever else you like. Overlays can only work with Connectors that implement a few helper functions.
    -			<h4>The concept of <strong>location</strong></h4>
    -			Before discussing the helper functions you first must be across the concept of 'location' as used by this mechanism.  The <em>location</em>
    -			on a connector can be a decimal value between 0 and 1 inclusive, which marks a proportionate amount of travel <em>along the path
    -			inscribed by the Connector</em>.  For a straight line connector this is a simple piece of maths, but for Bezier connectors it's a little
    -			bit more involved.
    -			<h4>Required Helper Methods</h4>
    -			<ul>
    -				<li><strong>pointOnPath(location)</strong> - returns an [x,y] point corresponding to the given location</li>
    -				<li><strong>pointAlongPathFrom(location, distance)</strong> - returns an [x,y] point corresponding to travelling 'distance' pixels along the connector from 'location'.</li>
    -				<li><strong>gradientAtPoint(location)</strong> - returns the connector's gradient at the given location.  For linear connectors such as Straight this is constant, but for Bezier connectors the gradient changes continually.</li>
    -				<li><strong>perpendicularToPathAt(location, distance, length)</strong> - returns a line that is perpendicular to (and centered on) the connector at 'distance' from the given location, with length 'length'.</li>
    -			</ul>
    -</p>
    -<h4><a id="customEndpoints">Custom Endpoints</a></h4>
    -<p>To create your own Endpoint implementation, you need to implement a single method:
    -				<div class="code"><pre>paint : function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { ... }</pre></div>
    -				The arguments to this method are as follows:
    -				<ul>
    -					<li>anchorPoint - [x,y] location of the anchor point on screen</li>
    -					<li>orientation - [x,y] hints for the general direction the anchor points to</li>
    -					<li>canvas - the canvas to draw into</li>
    -					<li>endpointStyle - Javascript object containing style directives as discussed above.  The contents of this are arbitrary, so if you write a new Endpoint that needs some extra settings, you can add them no hassle.</li>
    -					<li>connectorPaintStyle - the style being used to paint the associated Connector.</li>
    -				</ul>
    -			</p>
    -			<p>
    -				It is your responsibility to size and locate the canvas to suit your needs. jsPlumb provides the following
    -				helper method to assist you:
    -				<div class="code"><pre>jsPlumb.sizeCanvas(canvas, x, y, width, height);</pre></div>
    -				Allows you to locate the canvas on screen and to size it.
    -			</p>
    -<h4><a id="customOverlays">Custom Overlays</a></h4>
    -			Overlays can only work with Connectors that implement the methods <strong>pointOnPath</strong>, <strong>pointAlongPathFrom</strong>,
    -			<strong>gradientAtPoint</strong>, and <strong>perpendicularToPathAt</strong>.  These are discussed in more detail in the Custom Connectors section above.  Both of the Connectors
    -			that come with jsPlumb - Straight and Bezier -implement these methods; if you write a custom connector, or have written a custom connector, you will need to supply
    -			them.   
    -			<h4>Overlay Interface</h4>
    -			An Overlay is required to implement two methods in order to be usable by jsPlumb:
    -			<ul>
    -				<li><strong>computeMaxSize(connector, context)</strong> - returns an integer value indicating the larger of this overlay's width and height.  It is used by jsPlumb to ensure that the canvas is large enough to accomodate the overlay.  The examples below should help to clarify this.</li>
    -				<li><strong>draw(connector, ctx)</strong> - draws the overlay.  What happens in this method is up to the given implementation.</li>
    -			</ul>
    -			<h4>computeMaxSize methods</h4>
    -			This is the Arrow overlay's computeMaxSize method:
    -			<div class="code">
    -<pre>
    -this.computeMaxSize = function() { return width; }
    -</pre>			
    -			</div>
    -Here, <em>width</em> is a private member of Arrow that indicates the width of the arrow's tail.  So the Arrow overlay reports that figure as the width it needs.					
    -Contrast this with the Label Overlay's computeMaxSize method:
    -		<div class="code">
    -<pre>
    -this.computeMaxSize = function(connector, ctx) {
    -  	if (labelText) {
    -  		ctx.save();
    -        if (self.labelStyle.font) ctx.font = self.labelStyle.font;
    -         var t = ctx.measureText(labelText).width;			            
    -		// a fake text height measurement: use the width of upper case M
    -		var h = ctx.measureText("M").width;					
    -		labelPadding = self.labelStyle.padding || 0.25;
    -		labelWidth = t + (2 * t * labelPadding);
    -		labelHeight = h + (2 * h * labelPadding);
    -		ctx.restore();
    -		return Math.max(labelWidth, labelHeight);
    -  	}
    - 	return 0;
    - };
    -</pre>			
    -			</div>
    -The Label overlay has to use the context to determine how big it will be on screen.
    -
    -<h4>draw methods</h4>
    -To give you a taste for how you can interact with a connector, consider the first few lines of the Arrow overlay's draw method:
    -<div class="code">
    -<pre>
    -this.draw = function(connector, ctx) {
    -	// this is the arrow head position
    -	var hxy = connector.pointAlongPathFrom(self.loc, length / 2);		
    -	// this is the center of the tail
    -	var txy = connector.pointAlongPathFrom(self.loc, -length / 2), tx = txy[0], ty = txy[1];
    -	// this is the tail vector
    -	var tail = connector.perpendicularToPathAt(self.loc, -length / 2, width);
    -	
    -	...
    -</pre>
    -</div>
    -	<p>The first two calls to the connector use the <strong>pointAlongPathFrom</strong> method, which passes <em>self.loc</em> as the location of the
    -	point on the path, and <em>length / 2</em> as the number of pixels along the path to traverse.</p>
    -	<p><em>self.loc</em> is Arrow's internal member describing the location of the overlay, and it is a decimal (between 0 and 1 inclusive) that points to
    -	a location at some distance along the path inscribed by the connector.  So these first two calls get us [x,y] locations of points on 
    -	the connector that mark the head and tail points for the arrow.  
    -	</p>		
    -	<p>
    -	The <strong>connector.perpendicularToPathAt(self.loc, -length / 2, width)</strong> call returns a line description of a line that is perpendicular to, and centered on, the connector
    -	at "-length / 2" pixels from the given point, with the given width (width is an internal member of Arrow).  At this point, the Arrow object has the three main points it
    -	needs in order to draw itself.
    -	</p>
    -		</div-->
    -</div>
    -
    -</body>
    -
    -</html>
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.14/index.html b/demo/doc/archive/1.3.14/index.html
    deleted file mode 100644
    index aefc7262b..000000000
    --- a/demo/doc/archive/1.3.14/index.html
    +++ /dev/null
    @@ -1,92 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.14 documentation</title>
    -		<link rel="stylesheet" target="contentFrame" href="/mp.css"></link>
    -		<link rel="stylesheet" target="contentFrame" href="jsPlumbDoc.css"></link>
    -	</head>
    -	<body>
    -		<div class="index section">
    -			<h3>jsPlumb 1.3.14</h3>
    -			<ul>
    -				<li><a target="contentFrame" href="content.html#summary">Summary</a></li>
    -				<li><a target="contentFrame" href="content.html#changes">Changes since version 1.3.13</a></li>
    -				
    -				<li><h4>Setup</h4></li>
    -				<li><a target="contentFrame" href="content.html#imports">Required Imports</a></li>
    -				<li><a target="contentFrame" href="content.html#renderMode">Render Mode</a></li>
    -				<li><a target="contentFrame" href="content.html#initializing">Initializing jsPlumb</a></li>				
    -				<li><a target="contentFrame" href="content.html#multipleInstances">Multiple jsPlumb Instances</a></li>
    -				<li><a target="contentFrame" href="content.html#zIndex">Z-index Considerations</a></li>
    -				<li><a target="contentFrame" href="content.html#containerDefault">Where does jsPlumb add elements?</a></li>
    -				<li><a target="contentFrame" href="content.html#elementDragging">Element dragging</a></li>
    -				
    -				<li><h4>Introduction</h4></li>
    -				<li><a target="contentFrame" href="content.html#jsPlumbBasics">Basic Concepts</a></li>
    -				<li><a target="contentFrame" href="content.html#anchors">Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#staticAnchors">Static Anchors</a></li>				
    -				<li><a target="contentFrame" href="content.html#dynamicAnchors">Dynamic Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#perimeterAnchors">Perimeter Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#continuousAnchors">Continuous Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#definitions">Connector, Endpoint &amp; Overlay Definitions</a></li>
    -				<li><a target="contentFrame" href="content.html#connectors">Connectors</a></li>
    -				<li><a target="contentFrame" href="content.html#endpoints">Endpoints</a></li>
    -				<li><a target="contentFrame" href="content.html#overlays">Overlays</a></li>
    -				<li><a target="contentFrame" href="content.html#defaults">Defaults</a></li>								
    -				
    -				<li><h4>Connections</h4></li>
    -				<li><a target="contentFrame" href="content.html#simpleConnections">Programmatic Connections</a></li>
    -				<li><a target="contentFrame" href="content.html#draggableConnections">Connections using Drag and Drop</a></li>
    -				<li><a target="contentFrame" href="content.html#sourcesAndTargets">Elements as Sources &amp; Targets</a></li>
    -				<li class="sub"><a target="contentFrame" href="content.html#makeTarget">makeTarget</a></li>
    -				<li class="sub"><a target="contentFrame" href="content.html#makeSource">makeSource</a></li>
    -				<li><a target="contentFrame" href="content.html#dragOptions">Drag Options</a></li>				
    -				<li><a target="contentFrame" href="content.html#dropOptions">Drop Options</a></li>
    -				<li><a target="contentFrame" href="content.html#dragAndDropScope">Drag and Drop Scope</a></li>
    -				
    -				<li><h4>Parameters</h4></li>		
    -				<li><a target="contentFrame" href="content.html#connectParameters">jsPlumb.connect</a></li>
    -				<li><a target="contentFrame" href="content.html#addEndpointParameters">jsPlumb.addEndpoint</a></li>
    -				
    -				<li><h4>Connection and Endpoint Types</h4></li>		
    -				<li><a target="contentFrame" href="content.html#connectionTypes">Connection Type</a></li>
    -				<li><a target="contentFrame" href="content.html#endpointTypes">Endpoint Type</a></li>
    -
    -				<li><h4>Interceptors</h4></li>				
    -				<li><a target="contentFrame" href="content.html#beforeDrop">Before Drop</a></li>
    -				<li><a target="contentFrame" href="content.html#beforeDetach">Before Detach</a></li>
    -				
    -				<li><h4>Appearance</h4></li>
    -				<li><a target="contentFrame" href="content.html#paintstyles">Paint Styles</a></li>
    -				<li><a target="contentFrame" href="content.html#hoverpaintstyles">Hover Paint Styles</a></li>
    -				<li><a target="contentFrame" href="content.html#gradients">Gradients</a></li>				
    -				
    -				<li><h4>Miscellaneous</h4></li>
    -				<li><a target="contentFrame" href="content.html#cssclasses">CSS</a></li>
    -				<li><a target="contentFrame" href="content.html#animation">Animation</a></li>
    -				<li><a target="contentFrame" href="content.html#connectionInfo">Retrieving Connection Information</a></li>
    -				<li><a target="contentFrame" href="content.html#selectConnections">Selecting Connections</a></li>
    -				<li><a target="contentFrame" href="content.html#selectEndpoints">Selecting Endpoints</a></li>
    -				
    -				<li><h4>Events</h4></li>
    -				<li><a target="contentFrame" href="content.html#events">jsPlumb Events</a></li>
    -				<li><a target="contentFrame" href="content.html#connectionEvents">Connection Events</a></li>
    -				<li><a target="contentFrame" href="content.html#endpointEvents">Endpoint Events</a></li>
    -				<li><a target="contentFrame" href="content.html#overlayEvents">Overlay Events</a></li>
    -				
    -				<li><h4>Examples</h4></li>				
    -				<li><a target="contentFrame" href="content.html#examples">Programmatic API Examples</a></li>
    -				<li><a target="contentFrame" href="content.html#dragAndDropExamples">Draggable Connections Examples</a></li>
    -				<li><a target="contentFrame" href="content.html#utilityFunctions">Utility Functions</a></li>				
    -				
    -				<li><h4>Develop</h4></li>
    -				<li><a target="contentFrame" href="content.html#which">Which files are which?</a></li>
    -				<li><a target="contentFrame" href="content.html#pluggableLibrarySupport">Pluggable Library Support</a></li>
    -				<!-- li><a target="contentFrame" href="content.html#customConnectors">Custom Connectors</a></li>
    -				<li><a target="contentFrame" href="content.html#customEndpoints">Custom Endpoints</a></li>
    -				<li><a target="contentFrame" href="content.html#customOverlays">Custom Overlays</a></li-->
    -			</ul>
    -			<p><strong>This document refers to release 1.3.14 of jsPlumb.</strong></p>
    -			<strong>11 Sep 2012</strong>
    -		</div>		
    -	</body>
    -</html>
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.14/jsPlumbDoc.css b/demo/doc/archive/1.3.14/jsPlumbDoc.css
    deleted file mode 100644
    index f2949bd91..000000000
    --- a/demo/doc/archive/1.3.14/jsPlumbDoc.css
    +++ /dev/null
    @@ -1,127 +0,0 @@
    -body { 
    -	background-color:white; font-size:90%;
    -	background-image:url(../img/dynamicAnchorBg.jpg); 
    -}
    -/* docs */
    -.code { background-color:#e4eeee;
    -border:1px solid black; margin:1em; padding:0.8em; 
    -overflow:auto;
    -font-family:courier; 
    -font-size:8pt;
    -border-radius:0.3em;
    -}
    -.section { border-bottom: 1px dotted black; padding:1em;font-family:helvetica;background-color:white;color:#333;}
    -.menu { 
    -	position:fixed; 
    -	right:10em; top:1em;
    -	background-color:white;
    -	padding:0.3em;
    -	opacity:0.8;
    -	z-index:5000;
    -	filter:alpha(opacity=80);
    -		box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -a {text-decoration:none;color:#05b;font-weight:bold;font-family:helvetica;}
    -a:hover {text-decoration:underline;}
    -.window, .label { 
    -	cursor:pointer;
    -	box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -#explanation { 
    -	background-color:white;
    -	color:black;
    -	border:0.25em solid #ddd; 
    -	position:absolute;
    -	right:3.5em;
    -	top:5em;
    -	width:22em;
    -	font-size:100%;
    -	-moz-border-radius:1em;
    -	border-radius:1em;
    -	padding:1em;
    -	z-index:1000;
    -	opacity:0.9;
    -	filter:alpha(opacity=90); 
    -	font-family:helvetica;
    -		box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -.selected {
    -	color:rgb(189,11,11);
    -}
    -
    -
    -/*  these styles are what jsPlumb attaches to the various elements it creates.
    -._jsPlumb_connector {
    -border:1px solid blue;
    -}
    -._jsPlumb_endpoint {
    -border:1px solid green;
    -}
    -._jsPlumb_overlay {
    -border:1px solid yellow;
    -}
    -*/
    -
    -
    -.section li {
    -list-style-type:none;
    -}
    -
    -li.bullet {
    -	list-style-type:circle;
    -	}
    -
    -
    -.section {
    -padding-left:2em;
    -font-size:10pt;
    -}
    -.index.section {
    -padding-left:0;
    -}
    -
    -.index {
    -width:20em;
    -font-size:9pt;
    -float:left;
    -background-color:#e4eeee;
    -overflow:auto;
    -padding-left:0.5em;
    -}
    -
    -.index li {
    -list-style-type:none;
    -}
    -
    -.index ul, .index h3 {
    -padding-left:1.5em;
    -
    -}
    -
    -#demo {
    -width:80%;
    -height:50em;
    -margin-left:3em;
    -margin-top:3em;
    -position:relative;
    -}
    -
    -.sub {
    -margin-left:1em;
    -}
    -
    -.table td {
    -	text-align:center;
    -}
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.14/usage.html b/demo/doc/archive/1.3.14/usage.html
    deleted file mode 100644
    index f4a299ff6..000000000
    --- a/demo/doc/archive/1.3.14/usage.html
    +++ /dev/null
    @@ -1,10 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.14 - documentation</title>		
    -	</head>
    -	
    -	<frameset cols="250,*">
    -  		<frame src="index.html" />
    -  		<frame src="content.html" name="contentFrame" />
    -	</frameset>	
    -</html>
    diff --git a/demo/doc/archive/1.3.15/content.html b/demo/doc/archive/1.3.15/content.html
    deleted file mode 100644
    index 5044d15ec..000000000
    --- a/demo/doc/archive/1.3.15/content.html
    +++ /dev/null
    @@ -1,3785 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.15 - documentation</title>
    -		<link rel="stylesheet" href="/mp.css">
    -		<link rel="stylesheet" href="jsPlumbDoc.css">
    -	</head>
    -	<body>
    -
    -<div class="menu">
    -	<a href="http://jsplumb.org" class="mplink" target="_top">view demo</a>
    -	&nbsp;|&nbsp;<a href="mailto:simon.porritt@gmail.com" class="mplink">contact me</a>
    -	&nbsp;|&nbsp;<a href="http://jsplumb.tumblr.com" class="mplink">jsPlumb on Tumblr</a>	
    -</div>
    -
    -<div class="content">
    -		<div class="section">
    -			<h3><a id="summary">Summary</a></h3>
    -			jsPlumb allows you to connect elements on the screen using SVG, Canvas or VML, depending on the capabilities of the browser.
    -			<p>
    -				It can be used with jQuery, MooTools or YUI3 (or another library of your choice if you feel like implementing an adapter for it).
    -			</p>  
    -
    -			<h5>Browser Compatibility</h5>
    -			<p>jsPlumb 1.3.15 runs on everything from IE6 up.  There are some caveats, though, because of various browser/library bugs:
    -				<ul>
    -					<li class="bullet">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.
    -					<li class="bullet">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)</li>
    -					<li class="bullet">MooTools has a bug when using SVG in Firefox 11</li>
    -				<ul>
    -				
    -			</p>
    -		</div>
    -		
    -		<div class="section">		
    -			<h3><a id="changes">Changes between 1.3.15 and 1.3.14</a></h3>	
    -			<h5>Backwards Compatibility</h5>
    -			<ul>
    -			  <li class="bullet">'removeEveryEndpoint' method removed from jsPlumb. use 'deleteEveryEndpoint'.</li>
    -			  <li class="bullet">'removeEndpoint' method removed from jsPlumb. use 'deleteEndpoint'.</li>
    -			</ul>
    -			
    -			<h5>New Features</h5>
    -			<ul>
    -			  <li class="bullet">added 'ReattachConnections' default. instructs jsPlumb whether or not to automatically reattach connections dragged off and dropped via the mouse.</li>
    -			  <li class="bullet">added setReattach/isReattach methods to Connection</li>
    -			  <li class="bullet">added setReattach/isReattach methods to jsPlumb.select handler</li>
    -			</ul>
    -			
    -			<h5>Issues Resolved</h5>
    -			<ul>
    -			  <li class="bullet"><strong>279</strong> - deleteEndpointsOnDetach flag ignored when Endpoints assigned to an element previously configured with makeSource/makeTarget</li>
    -			  <li class="bullet"><strong>280</strong> - cannot drop connection dragged by source back onto its original source</li>
    -			</ul>
    -
    -			<h5>Miscellaneous</h5>
    -			<ul>
    -			  <li class="bullet">fixed potential infinite loop issue related to makeSource function</li>
    -			</ul>
    -			
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="init">Setup</a></h3>
    -
    -            <h4><a id="imports">Required Imports</a></h4>
    -			<h4>jQuery</h4>
    -			    <ul>
    -				    <li class="bullet">jQuery 1.3.x or higher.</li>
    -				    <li class="bullet">jQuery UI 1.7.x or 1.8.x (if you wish to support drag and drop). Note that if you use
    -					jQuery 1.8.x you must use jQuery UI 1.8.22 or higher.</li>
    -			    </ul>
    -			    <div class="code">
    -<pre>&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.23/jquery-ui.min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/jquery.jsPlumb-1.3.15-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			<h4>MooTools</h4>
    -				<ul>
    -					<li class="bullet">MooTools Core 1.2.4 or higher. jsPlumb has been tested with 1.2.4 and 1.3.3.</li>
    -					<li class="bullet">Drag.Move from MooTools More 1.2.4.4 or higher (if you wish to support drag and drop).</li>
    -				</ul>
    -			<div class="code">
    -			<pre>&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/mootools/1.3.3/mootools-yui-compressed.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO_MOO_TOOLS_MORE_1_3_2_1"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/mootools.jsPlumb-1.3.15-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			<h4>YUI3</h4>
    -			<ul>
    -				<li class="bullet">YUI 3.3.x.  jsPlumb has been tested on 3.3.0 only; it is possible other 3.x.x versions will work.</li>
    -			</ul>
    -			<div class="code">
    -			<pre>&lt;script type="text/javascript" src="http://yui.yahooapis.com/3.3.0/build/simpleyui/simpleyui-min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/yui.jsPlumb-1.3.15-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			
    -			<h4><a id="renderMode">Render Mode</a></h4>
    -			jsPlumb can use SVG, HTML5 Canvas elements or VML to render the various objects it adds to the display.  Most modern browsers support Canvas and SVG; IE&lt;9 browsers support neither.
    -			<p>
    -				By default, jsPlumb attempts to use <strong>jsPlumb.SVG</strong> as the render mode (prior to 1.3.4, the default renderer was Canvas), falling back to <strong>jsPlumb.VML</strong> if the 
    -				browser is IE&lt;9.  You can change the render mode by making this call:  
    -			</p>
    -			<div class="code">
    -<pre>jsPlumb.setRenderMode(jsPlumb.CANVAS);</pre>			
    -			</div>
    -			if, for some reason, you set the render mode to be <strong>jsPlumb.VML</strong> but you're in any browser other than IE, jsPlumb will use SVG.
    -			<p>
    -			The <strong>jsPlumb.setRenderMode</strong> method returns you the render mode that jsPlumb actually ended up setting.  Valid values for setRenderMode are:
    -			</p>
    -			<ul>
    -				<li class="bullet">jsPlumb.CANVAS</li>
    -				<li class="bullet">jsPlumb.SVG</li>
    -				<li class="bullet">jsPlumb.VML</li>
    -			</ul>			
    -			<h5>Performance Considerations</h5>
    -			<p>
    -			SVG uses less memory than Canvas and has support for mouse events built in, so I recommend using
    -			SVG unless you have a really good reason for wanting Canvas. Of course, you might consider that horrendous Safari bug a really good reason for wanting Canvas.
    -			</p>
    -			
    -			<h4><a id="initializing">Initializing jsPlumb</a></h4>
    -			You should not start making calls to jsPlumb until the DOM has been initialized - perhaps no surprises there.  With YUI, though, the asynchronous
    -			nature of the script loading process means that you are not guaranteed that jsPlumb is ready as soon as the DOM is.  To handle this,
    -			you should bind to the "ready" event on jsPlumb (or the instance of jsPlumb you are working with):
    -			<div class="code">
    -			<pre>jsPlumb.bind("ready", function() {
    -...	
    -		
    -// your jsPlumb related init code goes here
    -
    -...
    -
    -});						
    -			</pre>
    -			</div>
    -			There's a helper method that can save you approximately 9 (count them!) characters:
    -			<div class="code">
    -			<pre>jsPlumb.ready(function() {
    -...	
    -		
    -// your jsPlumb related init code goes here
    -
    -...
    -
    -});						
    -			</pre>
    -			</div>
    -			<p>
    -			For jQuery and MooTools you do not actually need to do this; by the time the DOM ready event is fired in each of those libraries you can be sure 
    -			all the JS we need has been loaded.  But in terms of explicitly calling out that you are waiting for jsPlumb, it seems like a good practice to bind to the "ready" event.
    -			</p>
    -			<p>
    -			If you bind to the "ready" event after jsPlumb has already been initialized, your callback will be executed immediately.
    -			</p>			
    -
    -				
    -<h4><a id="multipleInstances">Multiple jsPlumb instances</a></h4>
    -			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 <strong>getInstance</strong> method, for example:
    -<div class="code">
    -<pre>
    -var firstInstance = jsPlumb.getInstance();
    -</pre>
    -</div>
    -The variable 'firstInstance' can now be treated exactly as you would treat the 'jsPlumb' variable - you can set
    -defaults, call the connect method, whatever:
    -<div class="code">
    -<pre>
    -firstInstance.importDefaults({
    -  Connector : [ "Bezier", { curviness: 150 } ],
    -  Anchors : [ "TopCenter", "BottomCenter" ]
    -});
    -
    -firstInstance.connect({source:"element1", target:"element2", scope:"someScope" });
    -</pre>
    -</div>
    -
    -<strong>getInstance</strong> optionally takes an object that provides the defaults:
    -<div class="code">
    -<pre>
    -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" });
    -</pre>
    -</div>
    -
    -		<h4><a id="zIndex">Z-index Considerations</a></h4>
    -		<p>
    -			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. 
    -		</p>  
    -		<p>
    -			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).  			
    -		</p>
    -		<p>
    -			To help you organise z-indices correctly, jsPlumb adds a CSS class to each type of
    -			element it adds. They are as follows:			
    -		</p>
    -		<table style="color:black;width:70%;font-size:90%;">
    -			<tr><td><strong>type of element</strong></td><td><strong>class added</strong></td></tr>
    -			<tr><td>Endpoint</td><td>_jsPlumb_endpoint</td></tr>
    -			<tr><td>Connector</td><td>_jsPlumb_connector</td></tr>
    -			<tr><td>Overlay</td><td>_jsPlumb_overlay</td></tr>
    -		</table>
    -		
    -		<h5>Automatic z-index for Connections</h5>
    -		A common use case is the need to bring hovered Connections up on top of the other connections in your UI. From
    -		1.3.15 onwards, jsPlumb offers a way to do this:
    -		<div class="code">
    -<pre>
    -  jsPlumb.importDefaults({
    -    ...
    -    ConnectorZIndex:5
    -	...
    -  });
    -</pre>			
    -		</div>
    -		This tells jsPlumb to set the z-index of all Connections to be 5, and the z-index of any hovered Connections will
    -		be, in this case, 6.  You could argue that jsPlumb could figure out the z-index automatically, but z-index selection
    -		is typically a conscious decision in a webapp, and this approach formalises things a little better than trying
    -		to infer what the value is supposed to be.
    -		<p>To achieve this, there is actually a <strong>setZIndex</strong> method on Connections now, which is perfectly fine
    -		to call, but remember that if you change the z-index of some Connection, hover over it, and then mouse exit, resetting
    -		the hover state will mean that jsPlumb resets the z-index to the default value discussed above.</p>
    -
    -		<h4><a id="containerDefault">Where does jsPlumb add elements?</a></h4>
    -		<p>
    -			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.
    -		</p>
    -		<p>
    -			Prior to 1.3.0, jsPlumb added everything to the end of the <em>body</em> element. This
    -			has the advantage of being the most flexible arrangement in terms of supporting what
    -			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!   
    -		</p>
    -		<p>
    -			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 (<strong>not</strong> 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.
    -		</p>
    -		
    -		<p>
    -			In general, the default behaviour means that you cannot do things like this:
    -		</p>
    -		<div class="code">
    -<pre>
    -&lt;body&gt;
    -	&lt;div id="container0"&gt;
    -		&lt;div id="node0"&gt;&lt;/div&gt;
    -	&lt;/div&gt;
    -	&lt;div id="container1"&gt;
    -		&lt;div id="node1"&gt;&lt;/div&gt;
    -	&lt;/div&gt;
    -&lt;/body&gt;
    -
    -&lt;script type="text/javascript"&gt;
    -
    -	var e0 = jsPlumb.addEndpoint("node0"),
    -	e1 = jsPlumb.addEndpoint("node1");
    -
    -	jsPlumb.connect({ source:e0, target:e1 });
    -&lt;/script&gt;
    -</pre>		
    -		</div>
    -<p>
    -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):  
    -</p>
    -		<div class="code">
    -<pre>
    -&lt;body&gt;
    -	&lt;div id="container0"&gt;
    -	&lt;/div&gt;
    -	&lt;div id="container1"&gt;
    -	&lt;/div&gt;
    -&lt;/body&gt;
    -
    -&lt;script type="text/javascript"&gt;
    -
    -	var e0 = jsPlumb.addEndpoint("container0"),
    -	e1 = jsPlumb.addEndpoint("container1");
    -
    -	jsPlumb.connect({ source:e0, target:e1 });
    -&lt;/script&gt;
    -</pre>		
    -		</div>
    -	now 'e0' and 'e1' have the same parent - the <em>body</em> element. 
    -	
    -	<p>Remember that you can use the <strong>anchor</strong> parameter to an <strong>addEndpoint</strong>
    -	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. 
    -	</p>									
    -	<p><strong>Note regarding the drawEndpoints option on jsPlumb.connect</strong>: 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). 
    -	</p>
    -	
    -	<h5>Overriding the default behaviour</h5>
    -	<p>
    -From version 1.3.3, the <strong>container</strong> 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 <strong>jsPlumb.Defaults.Container</strong> property, or you can set a <strong>container</strong> parameter on calls to <strong>addEndpoint</strong> or <strong>connect</strong> (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:
    -	</p>
    -		
    -Set a container to use as the default container, using a jQuery selector (you can supply MooTools/YUI selectors if that's the library you're using), and then add an Endpoint.  The canvas (or SVG/VML element) created will be a child of the document body:
    -		<div class="code">
    -<pre>
    -jsPlumb.Defaults.Container = $("body");
    -...
    -jsPlumb.addEndpoint(someDiv, { endpoint options });
    -</pre>		
    -		</div>
    -		
    -			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":
    -		<div class="code">
    -<pre>
    -jsPlumb.Defaults.Container = "containerId";
    -...
    -jsPlumb.connect({ source:someDiv, target:someOtherDiv });
    -</pre>		
    -		</div>
    -		
    -		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":
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({ source:someDiv, target:someOtherDiv, container:$("#containerId") });
    -</pre>		
    -		</div>
    -				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":
    -		<div class="code">
    -<pre>
    -jsPlumb.addEndpoint(someDiv, { ..., container:"containerId" });
    -</pre>		
    -		</div>
    -
    -		<h4><a id="elementDragging">Element Dragging</a></h4>
    -		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 <strong>jsPlumb.draggable(...)</strong>:
    -<div class="code">
    -<pre>jsPlumb.draggable("elementId");</pre>
    -</div>
    -You can also pass a selector from your library in to this method:
    -<div class="code">
    -<pre>jsPlumb.draggable($(".someClass"));</pre>
    -</div>
    -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.  A common requirement when using jQuery, for instance, is to set the containment of elements that you are making draggable.  Here's how you would tell jsPlumb to initialise some set of elements to be draggable but only inside their parent:
    -<div class="code">
    -<pre>jsPlumb.draggable($(".someClass"), {
    -	containment:"parent"
    -});</pre>
    -</div>
    -		<h5>Dragging nested elements</h5>
    -jsPlumb takes nesting into account when handling draggable elements. For example, say you have this markup:
    -<div class="code">
    -<pre>
    -&lt;div id="container"&gt;
    -  &lt;div class="childType1"&gt;&lt;/div&gt;
    -  &lt;div class="childType2"&gt;&lt;/div&gt;
    -&lt;/div&gt;
    -</pre>
    -</div>
    -..and then you connect one of those child divs to something, and make "container" draggable:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:$("#container .childType1"),
    -	target:"somewhere else"
    -});
    -
    -jsPlumb.draggable("container");
    -</pre>
    -</div>
    -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.
    -
    -
    -		</div>
    -				
    -		<div class="section">
    -			<h3><a id="jsPlumbBasics">Basic Concepts</a></h3>			
    -	
    -			jsPlumb is all about connecting things together, so the core abstraction in jsPlumb is the <strong>Connection</strong> object, which is itself
    -			broken down into these four concepts:
    -
    -			<ul>
    -				<li><strong>Anchor</strong> - a location, relative to an element's origin, at which an Endpoint can exist. You do not create these yourself; you 
    -				supply hints to the various jsPlumb functions, which create them as needed.  They have no visual representation; they are a logical position only. Anchors can be referenced by name, for the Anchors that jsPlumb ships with, or a four element array describing [ x, y, x orientation, y orientation ]. 
    -				See the <a href="#anchors">anchors</a> section for more detail.<br/><br/></li>
    -				<li><strong>Endpoint</strong> - the visual representation of one end of a Connection.  You can create and attach these to elements yourself, which you are required to do to support drag and drop, or have jsPlumb create them when creating a Connection programmatically using jsPlumb.connect(...).  You can also join two Endpoints programmatically, by passing them as arguments to jsPlumb.connect(...).<br/><br/></li>				
    -				<li><strong>Connector</strong> - the visual representation of the line connecting two elements in the page.  jsPlumb has four types of these 
    -				available as defaults - a Bezier curve, a straight line, 'flowchart' connectors and 'state machine connectors. You do not interact with Connectors; 
    -				you just specify definitions of them when you need to - see <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions</a> <br/><br/></li>
    -				<li><strong>Overlay</strong> - a UI component that is used to decorate a Connector, such as a Label, Arrow, etc.<br/><br/></li>
    -			</ul>
    -						
    -			<p>One Connection is made up of two Endpoints, a Connector, and zero or more Overlays working together to join two elements.</p>
    -			<p>jsPlumb's public API exposes only Connection and Endpoint, handling the creation and configuration of everything else internally. But you still need to be across the concepts encapsulated by Anchor, Connector and Overlay.
    -			</p>
    -			</div>
    -			
    -			<div class="section">
    -			<h3><a id="anchors">Anchors</a></h3>			
    -	
    -			An Anchor models the notion of where on an element a Connector should connect.  There are three main types of Anchors:
    -			<ul>
    -			<li/>
    -				<li class="bullet">'Static' anchors - these are fixed to some point on an element and do not move.  They can be specified using a string to
    -				identify one of the defaults that jsPlumb ships with, or an array describing the location (see below)<br/><br/></li>				
    -				<li class="bullet">'Dynamic' anchors - these are lists of Static anchors from which jsPlumb picks the most appropriate one each time a Connection
    -				is painted.  The algorithm used to determine the most appropriate anchor picks the one that is closest to the center of the other element in
    -				the Connection. A future version of jsPlumb might support a pluggable algorithm to make this decision.<br/><br/></li>
    -				<li class="bullet">'Perimeter' anchors - these are anchors that follow the perimeter of some given shape. They are, in essence, Dynamic
    -				anchors whose locations are chosen from the perimeter of the underlying shape.<br/><br/></li>
    -				<li class="bullet">'Continuous' anchors - These anchors, introduced in 1.3.4, are not fixed to any specific loction; they are assigned to one of
    -				the four faces of an element depending on that element's orientation to the other element in the associated Connection.  Continuous anchors are slightly more computationally intensive than Static or Dynamic anchors because jsPlumb is required to calculate the position of every Connection during a paint cycle, rather than just Connections belonging to the element in motion.  These anchors were designed for use with the StateMachine connector that was also introduced in 1.3.4, but will work with any combination of Connector and Endpoint.<br/><br/></li> 
    -			</ul>  
    -					
    -			<h4><a id="staticAnchors">Static Anchors</a></h4>		
    -			jsPlumb has nine default anchor locations you can use to specify where the Connectors connect to elements: these are the four corners of an element,
    -			the center of the element, and the midpoint of each edge of the element:  
    -			<ul>
    -				<li class="bullet">TopCenter</li>
    -				<li class="bullet">TopRight</li>
    -				<li class="bullet">RightMiddle</li>
    -				<li class="bullet">BottomRight</li>
    -				<li class="bullet">BottomCenter</li>
    -				<li class="bullet">BottomLeft</li>
    -				<li class="bullet">LeftMiddle</li>
    -				<li class="bullet">TopLeft</li>
    -			</ul>
    -			
    -Each of these string representations is just a wrapper around the underlying array-based syntax  [x, y, dx, dy], where x and y are coordinates in the interval [0,1] specifying the position of the
    -anchor, and dx and dy are coordinates in the interval [-1, 1] specifying the orientation of the curve incident to the anchor. For example, [0, 0.5, -1, 0] defines a "LeftCenter" anchor with a connector curve that emanates leftward from the anchor. Similarly, [0.5, 0, 0, -1] defines a "CenterTop" anchor with a connector curve emanating upwards. 	
    -
    -
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:"BottomCenter", ... }); 
    -</pre>
    -</div>
    -is identical to:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:[ 0.5, 1, 0, 1 ], ... }); 
    -</pre>
    -</div>		
    -In addition to supplying the location and orientation of an anchor, you can optionally supply two more parameters that define an offset in pixels from the given location.  Here's the anchor specified above, but with a 50 pixel offset below the element in the y axis:	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:[ 0.5, 1, 0, 1, 0, 50 ], ... }); 
    -</pre>
    -</div>		
    -
    -		<h4><a id="dynamicAnchors">Dynamic Anchors</a></h4>
    -	These are Anchors that can be positioned in one of a number of locations, choosing the one that is most appropriate each time something moves or is painted in the UI.
    -	<p>
    -	There is no special syntax to creating a DynamicAnchor; you just provide an array of individual Static Anchor specifications, eg:
    -	</p>
    -<div class="code">
    -<pre>
    -var dynamicAnchors = [ [ 0.2, 0, 0, -1 ],  [ 1, 0.2, 1, 0 ], 
    -			   [ 0.8, 1, 0, 1 ], [ 0, 0.8, -1, 0 ] ];
    -
    -jsPlumb.connect({...., anchor:dynamicAnchors, ... }); 			   
    -</pre>
    -</div>
    -<h4>	Default Dynamic Anchor</h4>
    -jsPlumb provides a dynamic anchor called "AutoDefault" that chooses from TopCenter, RightMiddle, BottomCenter and LeftMiddle:
    -<div class="code">
    -<pre>jsPlumb.connect({...., anchor:"AutoDefault", ... });</pre>
    -</div>
    -
    -<h4>	Location Selection</h4>
    -The initial implementation of the algorithm that decides which location to choose just calculates which location is closest to the center of the 
    -other element in the connection.  It is possible that future versions of jsPlumb could support more sophisticated choice algorithms, if the need arose.
    -<h4>Draggable Connections</h4>
    -Dynamic Anchors and Draggable Connections can interoperate: jsPlumb locks the position of a dynamic anchor when you start to drag a connection from it,
    -and unlocks it once the connection is either established or abandoned. At that point you may see the position of the dynamic anchor change, as jsPlumb
    -optimises the connection.  <p>You can see this behaviour in the <a href="../html/jquery/draggableConnectorsDemo.html" target="_blank">draggable connections</a> demonstration, when
    -you drag a connection from the blue endpoint on window 1 to the blue endpoint on window 3 - the connection is established and then window 1's blue
    -endpoint jumps down to a location that is closer to window 3.</p>
    -
    -	<h4><a id="perimeterAnchors">Perimeter Anchors</a></h4>	
    -These are a form of Dynamic anchor in which the anchor locations are chosen from the perimeter of some given shape. jsPlumb
    -supports six shapes:
    -
    -<ul>
    -	<li class="bullet">circle</li>
    -	<li class="bullet">ellipse</li>
    -	<li class="bullet">triangle</li>
    -	<li class="bullet">diamond</li>
    -	<li class="bullet">rectangle</li>
    -	<li class="bullet">square</li>	
    -</ul>
    -
    -'Rectangle' and 'Square', are not, strictly speaking, necessary, since rectangular shapes are the norm in a web page. But they
    -are included for completeness.
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("someElement", {
    -	endpoint:"Dot",
    -	anchor:[ "Perimeter", { shape:"circle" } ]
    -});
    -</pre>
    -</div>
    -
    -In this example our anchor will travel around the path inscribed by a circle whose diameter is the width and height of the
    -underlying element. Note that the 'circle' shape is therefore identical to 'ellipse', since it is assumed the underlying
    -element will have equal width and height, and if it does not, you will get an ellipse. 'rectangle' and 'square' have the
    -same relationship.
    -<p>By default, jsPlumb approximates the perimeter with 60 anchor locations.  You can change this, though:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("someDiv", {
    -	endpoint:"Dot",
    -	anchor:[ "Perimeter", { shape:"square", anchorCount:150 }]
    -});
    -</pre>		
    -</div>
    -Obviously, the more points the smoother the operation. But also the more work your browser has to do.
    -<p>'triangle' and 'diamond' both assume that there is a point at the top center of the shape. This may or may not turn
    -out to be acceptable for all applications, in which case it might be possible to add support for a 'rotation' argument to the
    -anchor constructor.</p>
    -Here's a triangle and diamond example, just for kicks:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:"Dot",
    -	anchors:[
    -		[ "Perimeter", { shape:"triangle" } ],
    -		[ "Perimeter", { shape:"diamond" } ]
    -	]
    -});
    -</pre>		
    -</div>
    -<h5>Perimeter Anchor Rotation</h5>
    -You can supply a 'rotation' value to a Perimeter anchor - an example can be seen in <a href="../jquery/perimeterAnchorsDemo.html" target="_blank">this demo</a>. Here's how you would use it:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:"Dot",
    -	anchors:[
    -		[ "Perimeter", { shape:"triangle", rotation:25 } ],
    -		[ "Perimeter", { shape:"triangle", rotation:-335 } ]
    -	]
    -});  
    -</pre>
    -</div>
    -
    -Note that the value must be supplied <strong>in degrees</strong>, not radians, and the number may be either positive or negative. In the example above, both triangles are of course rotated by the same amount.
    -
    -	<h4><a id="continuousAnchors">Continuous Anchors</a></h4>
    -	As discussed above, these are anchors whose positions are calculated by jsPlumb according to the orientation between elements in a Connection, and also how
    -	many other Continuous anchors happen to be sharing the element.  You specify that you want to use Continuous anchors using the string syntax you would use
    -	to specify one of the default Static Anchors, for example:
    -	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:someDiv,
    -	target:someOtherDiv,
    -	anchor:"Continuous"
    -});
    -</pre>
    -</div>
    -
    -Note in this example I specified only "anchor", rather than "anchors" - jsPlumb will use the same spec for both anchors.  But I could have said this:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:someDiv,
    -	target:someOtherDiv,
    -	anchors:["BottomCenter", "Continuous"]
    -});
    -</pre>
    -</div>	 	  
    -..which would have resulted in the source element having a Static Anchor at BottomCenter.  In practise, though, it seems the Continuous Anchors work best if both 
    -elements in a Connection are using them.
    -<p>Note also that Continuous Anchors can be specified on <strong>addEndpoint</strong> calls:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint(someDiv, {
    -	anchor:"Continuous",
    -	paintStyle:{ fillStyle:"red" }
    -});
    -</pre>
    -</div>	 	
    -...and in <strong>makeSource</strong>/<strong>makeTarget</strong>:
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource(someDiv, {
    -	anchor:"Continuous",
    -	paintStyle:{ fillStyle:"red" }
    -});
    -
    -jsPlumb.makeTarget(someDiv, {
    -	anchor:"Continuous",
    -	paintStyle:{ fillStyle:"red" }
    -});
    -</pre>
    -</div>	 
    -... and in the jsPlumb defaults:
    -<div class="code">
    -<pre>
    -jsPlumb.Defaults.Anchor = "Continuous";
    -</pre>
    -</div>  
    -					</div>		
    -												
    -<div class="section">
    -		<h4><a id="definitions">Connector, Endpoint &amp; Overlay Definitions</a></h4>
    -Before we discuss Connector, Endpoints and Overlays, a quick word on definitions:  whenever you need to define a Connector, Endpoint or Overlay, you must use a "definition" of it, rather than constructing one directly.  This definition can be either a string that nominates the artifact you want to create:
    -		<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:"Rectangle"
    -});
    -</pre>
    -		</div>
    -		or an array consisting of both the artifact's name and the arguments you want to pass to its constructor:
    -		<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Rectangle", { cssClass:"myEndpoint", width:30, height:10 } ]
    -});
    -</pre>
    -		</div>
    -There is also a three-argument method that allows you to specify two sets of parameters, which jsPlumb will merge together for you. The idea behind this is that you will often want to define common characteristics somewhere and reuse them across a bunch of different calls:
    -		<div class="code">
    -<pre>var common = {
    -	cssClass	:	"myCssClass",
    -	hoverClass	:	"myHoverClass"
    -};
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Rectangle", { width:30, height:10 }, common ]
    -});
    -</pre>
    -		</div>
    -	This syntax is supported for all Endpoint, Connector and Overlay definitions.  Here's an example using definitions for all three:	
    -		<div class="code">
    -<pre>var common = {
    -	cssClass:"myCssClass"
    -};
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Dot", { radius:5, hoverClass:"myEndpointHover" }, common ],
    -	connector:[ "Bezier", { curviness:100 }, common ],
    -	overlays: [
    -		[ "Arrow", { foldback:0.2 }, common ],
    -		[ "Label", { cssClass:"labelClass" } ]	
    -	]
    -});
    -</pre>
    -		</div>		
    -The allowed constructor parameters are different for each artifact you create, but every artifact takes a single JS object as argument, with the parameters as [key,value] pairs in that object.  See the relevant sections on <a href="#connectors">Connectors</a>, <a href="#endpoints">Endpoints</a> and <a href="#overlays">Overlays</a> for information on supported parameters.</a>
    -				
    -	</div>
    -
    -		<div class="section">
    -			<h3><a id="connectors">Connectors</a></h3>
    -			<p>Connectors are the lines that actually join elements of the UI.  jsPlumb has four connector implementations - a straight line, a Bezier curve, "flowchart", and "state machine".  The default connector is the Bezier curve.</p>
    -			<p>You optionally specify a Connector by setting the 'connector' property on a call to jsPlumb.connect, jsPlumb.addEndpoint(s), jsPlumb.makeSource or jsPlumb.makeTarget. If you do not supply a value for 'connector', the default will be used (see <a href="#defaults">defaults</a>).</p>
    -			<p>You specify Connectors using the syntax described above in <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions.</a> Allowed constructor values for each Connector type are described below:</p>		
    -			<h4><a id="bezierConnector">Bezier Connector</a></h4>
    -			<p>The Bezier Connector provides a cubic Bezier path between the two Endpoints. It supports a single constructor argument:</p>
    -			<p>
    -			<strong>curviness</strong> - Optional; defaults to 150. This defines the distance in pixels that
    -			the Bezier's control points are situated from the anchor points.  This does not mean that your
    -			connector will pass through a point at this distance from your curve.  It is a hint to how you want the
    -			curve to travel. Rather than discuss Bezier curves at length here, because they are a complex topic,
    -			refer instead to <a href="http://en.wikipedia.org/wiki/B%C3%A9zier_curve" target="_blank">Wikipedia.</a>
    -			</p>
    -			<h4><a id="straightConnector">Straight Connector</a></h4>
    -			<p>The Straight Connector draws a straight line between the two endpoints. No constructor arguments are supported; use the <strong>endpointStyle</strong> and/or <strong>cssClass</strong>/<strong>connectorClass</strong> to a <strong>connect</strong>, <strong>addEndpoint</strong>, <strong>makeSource</strong> or <strong>makeTarget</strong> call to control the appearance of one of these Connectors.  
    -			</p>
    -			<h4><a id="flowchartConnector">Flowchart Connector</a></h4>
    -			<p>This type of Connector draws a connection that consists of a series of vertical or horizontal segments - the classic flowchart look. Two constructor arguments are supported:</p>
    -			<strong>stub</strong> - this is the minimum length, in pixels, of the initial stub that emanates from an Endpoint.  This is an optional parameter, and can be either an integer, which specifies the stub fo each end of the connector, or an array of two integers, specifying the stub for the [source, target] endpoints in the connection.  This parameter is optional, and defaults to an integer with value 30 pixels.<br/>
    -			<strong>gap</strong> - optional, defaults to 0 pixels. Lets you specify a gap between the end of the connector and the elements to which it is attached.
    -			<p>This Connector supports Connection that start and end on the same element.</p>
    -			<h4><a id="stateMachineConnector">State Machine Connector</a></h4>
    -			<p>This Connector draws slightly curved lines (they are actually quadratic Bezier curves), similar to the State Machine connectors you may have seen in software like GraphViz.  Connections in which some element is both the source and the target (so-called 'loopback') are supported by these Connectors (as they are with Flowchart Connectors); in this case you get a circle. Supported parameters are:
    -			</p>
    -			<p>
    -			<strong>margin</strong> - Optional; defaults to 5.  Defines the distance from the element that the connector begins/ends.
    -			</p>
    -			<p>
    -			<strong>curviness</strong> - Optional, defaults to 10.  This has a similar effect to the curviness parameter on Bezier curves.
    -			</p>
    -			<p>
    -			<strong>proximityLimit</strong> - Optional, defaults to 80.  The minimum distance between the two ends of the Connector before it paints itself as 
    -			a straight line rather than a quadratic Bezier curve.
    -			</p>    
    -			
    -		</div>
    -		
    -			<div class="section">
    -			<h3><a id="endpoints">Endpoint Types</a></h3>
    -			<p>An Endpoint is the UI component that marks the location of an Anchor, ie. the place where a Connector joins an
    -			element.  jsPlumb comes with four Endpoint implementations - <strong>Dot</strong>, <strong>Rectangle</strong>, <strong>Blank</strong> and <strong>Image</strong>. You optionally specify Endpoint properties using the
    -			<strong>endpoint</strong> parameter in a call to jsPlumb.connect, jsPlumb.addEndpoint, jsPlumb.makeSource or jsPlumb.makeTarget.
    -			</p>  
    -				<p>As with Connectors and Overlays, you specify Endpoints using the syntax described above in <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions.</a></p>
    -			
    -			The four available Endpoint types, and their constructor arguments, are as follows:
    -					<h4><a id="dotEndpoint">Dot Endpoint</a></h4>
    -					This Endpoint draw a dot on the screen. It supports three constructor parameters:
    -					<p>
    -						<strong>radius</strong> - Optional; defaults to 10 pixels. Defines the radius of the dot.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="rectangleEndpoint">Rectangle Endpoint</a></h4>
    -						Draws a rectangle. Supported constructor parameters are:
    -					<p>
    -						<strong>width</strong> - Optional; defaults to 20 pixels. Defines the width of the rectangle.
    -					</p>
    -					<p>
    -						<strong>height</strong> - Optional; defaults to 20 pixels. Defines the height of the rectangle.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="imageEndpoint">Image Endpoint</a></h4>
    -						Draws an image from a given URL.  This Endpoint supports three constructor parameters:  
    -					<p>
    -						<strong>src</strong> - Required.  Specifies the URL of the image to use.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="blankEndpoint">Blank Endpoint</a></h4>
    -						Does not draw anything visible to the user.  As discussed previously, this Endpoint is probably not what you want if you need your users to be able to drag existing Connections - for that, use a Rectangle or Dot Endpoint and assign to it a CSS class that causes it to be transparent.
    -																			
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="overlays">Overlay Types</a></h3>
    -			<p>
    -			Overlays are UI elements that are painted onto connections, such as labels or arrows.
    -			jsPlumb comes with four defaults:
    -			</p>
    -			<ul>
    -				<li><strong>Arrow</strong> - a configurable arrow that is painted at some point along the connector.  You can control the length and width of the Arrow, the 'foldback' point - a point the tail points fold back into, and the direction (allowed values are 1 and -1; 1 is the default and means point in the direction of the connection)</li>
    -				<li><strong>Label</strong> - a configurable label that is painted at some point along the connector.</li>
    -				<li><strong>PlainArrow</strong> - an Arrow shaped as a triangle, with no foldback.</li>
    -				<li><strong>Diamond</strong> - as the name suggests, a diamond.</li>
    -			</ul> 
    -			The last two are actually just configured instances of the generic Arrow overlay (see examples). 
    -			
    -			<p>All Overlays support these two methods for getting/setting their location (location is either a number between 0 and 1 inclusive, indicating some point along the path inscribed by the associated Connector, or an absolute number of pixels, where negative values mean distance from the target of the connection):</p>
    -			<ul>
    -				<li class="bullet"><strong>getLocation</strong> - returns the current location</li>
    -				<li class="bullet"><strong>setLocation</strong> - sets the current location; valid values are decimals in the range 0-1 inclusive, or an integer less than 0 (to express distance from the target), or an integer greater than 1 (to express distance from the source)</li> 
    -			</ul>
    -			
    -			<p>You can specify one or more overlays when making a call to jsPlumb.connect, jsPlumb.addEndpoint or jsPlumb.makeSource (but not jsPlumb.makeTarget; overlays
    -			are always derived from what the source of a Connection defines)  The three cases are discussed below:</p>
    -			1. Specifying one or more overlays on a jsPlumb.connect call. In this example we'll create an Arrow with the default options for an Arrow, and
    -			a label with the text "foo":
    -			<div class="code">
    -			<pre>jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});			
    -</pre>
    -			</div>
    -			This connection will have an arrow located halfway along it, and the label "foo" one quarter of the way along.  Notice the <strong>id</strong>
    -			parameter; it can be used later if you wish to remove the Overlay or change its visibility (see below).<br/><br/> 
    -			Another example, this time with an absolute location of 50 pixels from the source:
    -			<div class="code">
    -			<pre>jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:50 }, id:"myLabel" ]
    -	],
    -	...
    -});			
    -</pre>
    -			</div>
    -			
    -			2. Specifying one or more overlays on a jsPlumb.addEndpoint call.  Note in this example that we use the parameter 'connectorOverlays' and not
    -			'overlays' as in the last example.  This is because 'overlays' would refer to Endpoint Overlays:
    -			<div class="code">
    -			<pre>jsPlumb.addEndpoint("someDiv", {
    -	...
    -	connectorOverlays:[ 
    -		[ "Arrow", { width:10, length:30, location:1, id:"arrow" } ], 
    -		[ "Label", { label:"foo", id:"label" } ]
    -	],
    -	...
    -});</pre>
    -			</div>	
    -			This connection will have a 10x30 Arrow located right at the head of the connection, and the label "foo" located at the halfway point. Again,
    -			note that <strong>id</strong> parameter passed in to each Overlay spec.<br/><br/>
    -			
    -			3. Specifying one or more overlays on a jsPlumb.makeSource call.  Note in this example that we again use the parameter 'connectorOverlays' and not
    -			'overlays'.  The 'endpoint' parameter to jsPlumb.makeSource supports everything you might pass to the second argument of a jsPlumb.addEndpoint call:
    -			<div class="code">
    -			<pre>jsPlumb.makeSource("someDiv", {
    -	...
    -	endpoint:{
    -		connectorOverlays:[ 
    -			[ "Arrow", { width:10, length:30, location:1, id:"arrow" } ], 
    -			[ "Label", { label:"foo", id:"label" } ]
    -		]
    -	}
    -	...
    -});</pre>
    -			</div>	
    -			This connection will have a 10x30 Arrow located right at the head of the connection, and the label "foo" located at the halfway point. Again,
    -			note that <strong>id</strong> parameter passed in to each Overlay spec.<br/><br/>
    -			
    -			 	
    -					<h4><a id="arrowOverlay">Arrow Overlay</a></h4>
    -					<p>This overlay draws an arrow, using four points: the head and two tail points, and a 'foldback' point, which permits the tail
    -					of the arrow to be indented. Available constructor arguments for this Overlay are </p>
    -					<ul>
    -					<li><strong>width</strong> - width of the tail of the arrow</li>
    -					<li><strong>length</strong> - distance from the tail of the arrow to the head</li>
    -					<li><strong>location</strong> - where, either as a proportional value from 0 to 1 inclusive, or as an absolute value (negative values mean distance from target; positive values greater than 1 mean distance from source) the Arrow should appear on the Connector</li>
    -					<li><strong>direction</strong> - which way to point. Allowed values are 1 (the default, meaning forwards) and -1, meaning backwards </li>
    -					<li><strong>foldback</strong> - how far along the axis of the arrow the tail points foldback in to. Default is 0.623.</li>
    -					<li><strong>paintStyle</strong> - a style object in the form used for paintStyle values for Endpoints and Connectors</li>
    -					</ul>
    -					<h4><a id="plainArrowOverlay">PlainArrow Overlay</a></h4>
    -					<p>This is just a specialized instance of Arrow in which jsPlumb hardcodes 'foldback' to 1, meaning the tail of the Arrow is a flat edge.  All of the constructor parameters from Arrow apply for PlainArrow.</p>
    -					<h4><a id="diamondOverlay">Diamond Overlay</a></h4>
    -					<p>This is a specialized instance of Arrow in which jsPlumb hardcodes 'foldback' to 2, meaning the Arrow turns into a Diamond.  All of the constructor parameters from Arrow apply for Diamond.</p>
    -					<h4><a id="labelOverlay">Label Overlay</a></h4>
    -					This provides a text label to decorate Connectors with.  The available constructor arguments are:
    -					<ul>
    -						<li><strong>label</strong> - The text to display.  You can provide a function here instead of plain text: it is passed the Connection as an argument, and it should return a String.</li>
    -						<li><strong>cssClass</strong> - Optional css class to use for the Label.  This is now preferred over using the 'labelStyle' parameter.</li>
    -						<li><strong>labelStyle</strong> - (deprecated, use cssClass instead) Optional arguments for the label's appearance.  Valid entries in this JS object are:
    -							<ul>
    -								<li><em>font</em> - a font string in a format suitable for the Canvas element</li>
    -								<li><em>fillStyle</em> - the color to fill the label's background with. Optional.</li>
    -								<li><em>color</em> - the color of the label's text. Optional.</li>
    -								<li><em>padding</em> - optional padding for the label. This is expressed as a proportion of the width of the label, not in pixels or ems.</li>
    -								<li><em>borderWidth</em> - optional width in pixels for the label's border. Defaults to 0.</li>
    -								<li><em>borderStyle</em> - optional. The color to paint the border, if there is one.</li>
    -								
    -							</ul>
    -						
    -						</li>
    -						<li><strong>location</strong> - As for Arrow Overlay.  Where, either proportionally from 0 to 1 inclusive, or as an absolute offset from either source or target, the label should appear.</li>
    -					</ul>
    -
    -					The Label overlay offers two methods - getLabel and setLabel - for accessing/manipulating its content dynamically:
    -<div class="code">
    -<pre>
    -var c = jsPlumb.connect({
    -	source:"d1", 
    -	target:"d2", 
    -	overlays:[
    -		[ "Label", {label:"FOO", id:"label"}]
    -	]	
    -});					
    -
    -...
    -
    -var label = c.getOverlay("label");
    -console.log("Label is currently", label.getLabel());
    -label.setLabel("BAR");
    -console.log("Label is now", label.getLabel());
    -</pre>
    -</div>
    -In this example you can see that the Label Overlay is assigned an id of "label" in the connect call, and then retrieved using that id in the call to Connection's getOverlay method.
    -<p>Both Connections and Endpoints support Label Overlays, and because changing labels is quite a common operation, <strong>setLabel</strong> and <strong>getLabel</strong> methods have been added to these objects:
    -<div class="code">
    -<pre>
    -var conn = jsPlumb.connect({
    -	source:"d1", 
    -	target:"d2",
    -	label:"FOO"
    -});					
    -
    -...
    -
    -console.log("Label is currently", conn.getLabel());
    -conn.setLabel("BAR");
    -console.log("Label is now", conn.getLabel());
    -</pre>
    -</div>
    -These methods support passing in a Function instead of a String, and jsPlumb will create a label overlay for you if one does not yet exist when you call setLabel:
    -<div class="code">
    -<pre>
    -var conn = jsPlumb.connect({
    -	source:"d1", 
    -	target:"d2"
    -});					
    -
    -...
    -
    -conn.setLabel(function(c) {
    -	var s = new Date();
    -	return s.getTime() + "milliseconds have elapsed since 01/01/1970";
    -});
    -console.log("Label is now", conn.getLabel());
    -</pre>
    -</div>
    -					
    -					<h4><a id="hideshowOverlay">Hiding/Showing Overlays</a></h4>
    -					<p>You can control the visibility of Overlays using the <strong>setVisible</strong> method of Overlays themselves, or with
    -					<strong>showOverlay(id)</strong> or <strong>hideOverlay(id)</strong> on a Connection.</p>
    -					<p>Remember the <strong>id</strong> parameter that we specified in the examples above?  This can be used to retrieve the Overlay
    -					from a Connection:</p>
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -var overlay = connection.getOverlay("myLabel");
    -// now you can hide this Overlay:
    -overlay.setVisible(false);
    -// there are also hide/show methods:
    -overlay.show();
    -overlay.hide();
    -
    -</pre>					
    -					</div>
    -					<p>However, Connection and Endpoint also have two convenience methods you could use instead:
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:-30 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -connection.hideOverlay("myLabel");
    -
    -// more time passes
    -
    -connection.showOverlay("myLabel");
    -
    -</pre>					
    -					</div>
    -					
    -		<h4><a id="hideshowOverlay">Removing Overlays</a></h4>
    -		<p>Connection and Endpoint also have a <strong>removeOverlay</strong> method, that does what you might expect:
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -connection.removeOverlay("myLabel");
    -
    -
    -</pre>					
    -					</div>							
    -	
    -		</div>
    -	
    -			
    -			<div class="section">
    -			<h3><a id="defaults">Defaults</a></h3>
    -			The easiest way to set a look and feel for your plumbing is to override the defaults that jsPlumb uses. If you
    -			do not do this you are forced to provide your overridden values on every call.  Every argument to the connect and addEndpoint methods has an
    -			associated default value in jsPlumb.<br/><br/>
    -
    -			The defaults that ship with jsPlumb are stored in <em>jsPlumb.Defaults</em>, which is a Javascript object.  Valid entries, and their initial values, are:
    -
    -			<div class="code">
    -<pre>
    -Anchor : "BottomCenter",
    -Anchors : [ null, null ],
    -Connector : "Bezier",
    -ConnectionOverlays : [ ],
    -DragOptions : { },
    -DropOptions : { },
    -Endpoint : "Dot",
    -EndpointOverlays : [ ],
    -Endpoints : [ null, null ],
    -EndpointStyle : { fillStyle : null },
    -EndpointStyles : [ null, null ],
    -EndpointHoverStyle : null,
    -EndpointHoverStyles : [ null, null ],
    -HoverPaintStyle : null,
    -LabelStyle : { color : "black" },
    -LogEnabled : false,
    -Overlays : [ ],
    -MaxConnections : 1,
    -PaintStyle : { lineWidth : 8, strokeStyle : "#456" },
    -RenderMode : "svg",
    -Scope : "_jsPlumb_DefaultScope"
    -</pre>
    -			</div>
    -			<p>
    -			Note that in EndpointStyle, the default fillStyle is 'null'.  This instructs jsPlumb to use the strokeStyle
    -from the attached connector to fill the endpoint.</p>
    -<p>Note also that you can specify either or both (or neither) of 'EndpointStyle' and 'EndpointStyles'.  This allows you to specify a different
    -end point for each end of a connection.  'Endpoint' and 'Endpoints' use the same concept.  jsPlumb will look first in the
    -individual endpoint/endpoint style arrays, and then fall back to the single default version.</p>
    -
    -			you can override these defaults by including this in a script somewhere:
    -			<div class="code">
    -<pre>
    -jsPlumb.Defaults.PaintStyle = {
    -	lineWidth:13,
    -	strokeStyle: 'rgba(200,0,0,0.5)'
    -}
    -
    -jsPlumb.Defaults.DragOptions = { cursor: "crosshair" };
    -
    -jsPlumb.Default.Endpoints = [ [ "Dot", 7 ], [ "Dot", 11 ] ];
    -
    -jsPlumb.Defaults.EndpointStyles = [{ fillStyle:"#225588" }, { fillStyle:"#558822" }];</pre>
    -			</div>
    -			after the jsPlumb script has been loaded of course!  Here we have specified the following default behaviour:
    -			<ul>
    -				<li>- connectors are 13 pixels wide and painted with a semi-transparent red line</li>
    -				<li>- when dragging an element the crosshair cursor is used</li>
    -				<li>- the source endpoint is a dot of radius 7; the target endpoint is a dot of radius 11</li>
    -				<li>- the source endpoint is blue; the target endpoint is green</li>
    -			</ul>
    -
    -			<h5>jsPlumb.importDefaults</h5>
    -There is a helper method called <strong>importDefaults</strong> that allows you to import a set of values with just one call to jsPlumb.  Here's the previous example using importDefaults:
    -			<div class="code">
    -<pre>
    -jsPlumb.importDefaults({
    -	PaintStyle : {
    -		lineWidth:13,
    -		strokeStyle: 'rgba(200,0,0,100)'
    -	},
    -	DragOptions : { cursor: "crosshair" },
    -	Endpoints : [ [ "Dot", 7 ], [ "Dot", 11 ] ],
    -	EndpointStyles : [{ fillStyle:"#225588" }, { fillStyle:"#558822" }]
    -});</pre>
    -			</div>
    -
    -		</div> 					
    -
    -<div class="section">
    -			<h3><a id="simpleConnections">Connections</a></h3>
    -			<h4><a id="simpleConnections">Programmatic Connections</a></h4>
    -The most simple connection you can make with jsPlumb looks like this:
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({source:"element1", target:"element2"});</pre>			
    -			</div>
    -
    -		In this example we have created a Connection from 'element1' to 'element2'.  Remember that a Connection in jsPlumb consists of two Endpoints,
    -		a Connector, and zero or more Overlays.  But this call to 'connect' supplied none of those things, so jsPlumb uses the default values wherever it needs to.
    -		In this case, default values have been used for the following: 
    -		<ul>
    -			<li>- The type and appearance of each Endpoint in the Connection. jsPlumb's default for this is the "Dot" endpoint, of radius 10, with fill color "#456".</li>
    -			<li>- The Anchors that define where the connection's Endpoints appear on each element. The jsPlumb default is "BottomCenter"</li>			
    -			<li>- Whether or not each Endpoint can be a source or target for new Connections. The default is false.</li>
    -			<li>- The type and appearance of the Connection's Connector. The default is a "Bezier" connector of line width 8, and color "#456".</li>
    -		</ul>
    -		<p>
    -		So this call will result in an 8px Bezier, colored "#456", from the bottom center of 'element1' to the bottom center of 'element2', and each
    -		Endpoint will be a 10px radius Dot Endpoint, colored "#456".
    -		</p>
    -		<p>
    -		Let's beef up this call a little and tell jsPlumb what sort of Endpoints we want, and where we want them:		
    -		</p>
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:"element1", 
    -	target:"element2",
    -	anchors:["RightMiddle", "LeftMiddle" ],
    -	endpoint:"Rectangle",
    -	endpointStyle:{ fillStyle: "yellow" }
    -});
    -</pre>			
    -			</div>
    -			
    -		<p>This is what we have told jsPlumb we want this time:</p>
    -		<ul>
    -			<li><strong>anchors</strong> - this array tells jsPlumb where the source and target Endpoints should be located on their parent elements. In this case,
    -			we use the shorthand syntax to name one of jsPlumb's default anchors; you can also specify custom locations (see <a href="#anchors">anchors</a>).
    -			Instead of <strong>anchors</strong> you can use <strong>anchor</strong>, if you want the source and target Endpoints to be located at the
    -			same place on their parent elements.			<br/>
    -			</li>
    -			<li>
    -				<strong>endpoint</strong>  - this tells jsPlumb to use the "Rectangle" Endpoint for both the source and target of the Connection.  As with anchors, 
    -				<strong>endpoint</strong> has a plural version that allows you to specify a different Endpoint for each end of the Connection.<br/>  
    -			</li>
    -			<li>
    -				<strong>endpointStyle</strong> - this is the definition of the appearance of the Endpoint you specified above.  Again, there is a
    -				plural equivalent of this that allows you to specify a different style for each end of the Connection. For more information about
    -				allowed values for this value, see <a href="#paintstyles">Connector, Endpoint & Overlay Styles</a>. 
    -			</li>
    -		</ul>		
    -		
    -		<h5>Reusing common settings between jsPlumb.connect calls</h5>
    -		<p>
    -			A fairly common situation you will find yourself in is wanting to create a bunch of Connections that have only minor differences
    -			between them.  To support that, <strong>jsPlumb.connect</strong> takes an optional second argument. For example:			 
    -		</p>
    -		<div class="code">
    -<pre>
    -var common = {
    -	anchors:[ "BottomCenter", "TopCenter" ],
    -	endpoints:["Dot", "Blank" ]
    -};
    -
    -jsPlumb.connect({ source:"someElement", target:"someOtherElement" }, common);
    -
    -jsPlumb.connect({ source:"aThirdElement", target:"yetAnotherElement" }, common);
    -
    -</pre>			
    -			</div>		
    -			
    -		<h5>Endpoints created by jsPlumb.connect</h5>
    -		<p>If you supply an element id or selector for either the source or target , jsPlumb.connect will automatically create an Endpoint on the 
    -		given element.  These automatically created Endpoints are not marked as drag source or targets, and cannot
    -		be interacted with.  For some situations this behaviour is perfectly fine, but for more interactive UIs you should set things up using the drag and drop
    -		method discussed below.</p>
    -		<p>Given that jsPlumb.connect creates its own Endpoints in some circumstances, in order to avoid leaving orphaned Endpoints around the place, if the Connection is subsequently
    -		deleted, these created Endpoints are deleted too.  Should you want to, you can override this behaviour by setting <strong>deleteEndpointsOnDetach</strong> to
    -		false in the connect call:</p>
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({ 
    -	source:"aThirdElement", 
    -	target:"yetAnotherElement",
    -	deleteEndpointsOnDetach:false 
    -});
    -</pre>
    -</div>	
    -
    -	<h5>Detaching Connections</h5>
    -	By default, connections made with jsPlumb.connect will be detachable via the mouse.  You can prevent this by either setting an appropriate default value:
    -
    -		<div class="code">
    -<pre>
    -jsPlumb.importDefaults({ 
    -	...
    -	ConnectionsDetachable:false
    -	...
    -});
    -</pre>
    -</div>	
    -
    -	Or by specifying it on the connect call like this:
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({ 
    -	source:"aThirdElement", 
    -	target:"yetAnotherElement",
    -	detachable:false
    -});
    -</pre>
    -</div>		
    -								
    -					
    -		<h4><a id="draggableConnections">Connections using Drag and Drop</a></h4>
    -		To support drag and drop connections, you first need to set a few things up.  Every drag and drop connection needs at least a source
    -		Endpoint that the user can drag a connection from. Here's a simple example of how to create an Endpoint:
    -		<div class="code">
    -<pre>
    -var endpointOptions = { isSource:true };
    -var endpoint = jsPlumb.addEndpoint('elementId', endpointOptions);
    -</pre>		
    -</div>  	
    -	This Endpoint will act as a source for new Connections, and will use the jsPlumb defaults for its own appearance and that of any Connections that are drawn from it.							
    -	
    -		<h5>Tip: use the three-argument addEndpoint method for common data </h5>
    -		One thing that happens quite often is that you have an Endpoint whose appearance and behaviour is largely the
    -		same between usages on different elements, with just a few differences. 
    -		<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -	endpoint:"Rectangle",
    -	paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -	isSource:true,
    -	connectorStyle : { strokeStyle:"#666" },
    -	isTarget:true
    -};
    -</pre>		
    -</div>  		
    -		Notice there is no 'anchor' set.  Here we apply it to two elements, at a different location in each:
    -		<div class="code">
    -<pre>
    -jsPlumb.addEndpoint('element1', { anchor:"BottomCenter" }, exampleGreyEndpointOptions)); 
    -
    -jsPlumb.addEndpoint('element2', { anchor:"TopCenter" }, exampleGreyEndpointOptions));
    -</pre>		
    -</div>
    -
    -<p>
    -Now that you have a source Endpoint, you need to either create a target Endpoint on some element, or notify jsPlumb that you wish to make an
    -entire element a drop target.  Let's look at how to attach a target Endpoint first:
    -</p>
    -
    -		<div class="code">
    -<pre>var endpointOptions = { isTarget:true, endpoint:"Rectangle", paintStyle:{ fillStyle:"gray" } };
    -var endpoint = jsPlumb.addEndpoint("otherElementId", endpointOptions);
    -</pre>		
    -		</div>
    -		<p>
    -		This Endpoint, a gray rectangle, has declared that it can act as a drop target for Connections. 
    -</p>
    -<p>
    -jsPlumb also supports marking entire elements as drag sources or drag targets, using the <strong>makeSource</strong> and <strong>makeTarget</strong> methods.  This is a useful function for certain applications.  Each of these methods takes an Endpoint declaration which defines the Endpoint that jsPlumb will create after a Connection has been attached.  Let's take a look at makeTarget first:
    -</p>
    -<h4><a id="sourcesAndTargets">Elements as sources &amp; targets</a></h4>
    -jsPlumb also supports turning entire elements into Connection sources and targets, using the methods <strong>makeSource</strong> and <strong>makeTarget</strong>.  With these methods you mark an element as a source or target, and provide an Endpoint specification for jsPlumb to use when a Connection is established.  makeSource also gives you the ability to mark some child element as the place from which you wish to drag Connections, but still have the Connection on the main element after it has been established.
    -<p>These methods honour the jsPlumb defaults - if, for example you set up the default Anchors to be this:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.Defaults.Anchors = [ "LeftMiddle", "BottomRight" ];
    -</pre>
    -</div>
    -... and then used makeSource and makeTarget without specifying Anchor locations, jsPlumb would use "LeftMiddle" for the makeSource element and "BottomRight" for the makeTarget element.
    -<p>
    -A further thing to note about makeSource and makeTarget is that any prior calls to one of these methods is honoured by subsequent calls to jsPlumb.connect. This helps when you're building a UI that uses this functionality at runtime but which loads some initial data, and you want the statically loaded data to have the same appearance and behaviour as dynamically created connections (obviously quite a common use case).  
    -<p>For example:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -	anchor:"Continuous",
    -	endpoint:["Rectangle", { width:40, height:20 }],
    -	maxConnections:3
    -});
    -
    -...
    -
    -jsPlumb.connect({source:"el1", target:"el2"});
    -</pre>
    -</div>
    -In this example, the source of the connection will be a Rectangle of size 40x20, having a Continuous Anchor.  The source is also configured to allow a maximum of three connections.
    -<p>You can override this behaviour by setting a <strong>newConnection</strong> parameter on the connect call:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -	anchor:"Continuous",
    -	endpoint:["Rectangle", { width:40, height:20 }],
    -	maxConnections:1,
    -	onMaxConnections:function(info, originalEvent) {
    -		console.log("element is ", info.element, "maxConnections is", info.maxConnections);	
    -	}
    -});
    -
    -...
    -
    -jsPlumb.connect({ source:"el1", target:"el2", newConnection:true });
    -</pre>
    -</div>
    -Note the <strong>onMaxConnections</strong> parameter to this call - it allows you to supply a function to call if the user tries to drag a new Connection when the source has already reached capacity.
    -<h5><a id="makeTarget">jsPlumb.makeTarget</a></h5>
    -<p>
    -This method takes two arguments, the first of which specifies some element (or list of elements); the second specifies the Endpoint you wish to create on that element whenever a Connection is established on it.  In this example we will use the exact same target Endpoint we used before - the gray rectangle - but we will tell jsPlumb that the element "aTargetDiv" will be the drop target:
    -</p>
    -		<div class="code">
    -<pre>var endpointOptions = { 
    -  isTarget:true, 
    -  maxConnections:5,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" } 
    -};
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -<p>
    -	The allowed values in 'endpointOptions' are identical for both the <strong>jsPlumb.addEndpoint</strong> and <strong>jsPlumb.makeTarget</strong> methods, but makeTarget supports an extended 
    -	anchor syntax that allows you more control over the location of the target endpoint.  This is discussed below.	
    -</p>		
    -<p>Notice in the 'endpointOptions' object above there is a 'isTarget' parameter set - this may seem incongruous, since you know you're going to make some element a target.  Remember that the endpointOptions object is the information jsPlumb will use to create an Endpoint on the given target element each time a Connection is established to it. It takes the exact same format as you would pass to addEndpoint; makeTarget is essentially a deferred addEndpoint call followed by a connect call.  So in this case, we're telling jsPlumb that any Endpoints it happens to create on some element that was configured by the makeTarget call are themselves Connection targets.
    -</p>
    -<p>makeTarget also supports the maxConnections and onMaxConnections parameters, as makeSource does, but note that onMaxConnections is passed one extra parameter than its corresponding callback from makeSource - the Connection the user tried to drop:</p>
    -		<div class="code">
    -<pre>
    -jsPlumb.makeTarget("aTargetDiv", { 
    -  isTarget:true, 
    -  maxConnections:5,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" },
    -  maxConnections:3,
    -  onMaxConnections:function(info, originalEvent) {
    -	console.log("user tried to drop connection", info.connection, "on element", info.element, "with max connections", info.maxConnections);
    -  }
    -};
    -</pre>		
    -		</div>
    -
    -<h5>Unique Endpoint per Target </h5>
    -<p>jsPlumb will create a new Endpoint using the supplied information every time a new Connection is established on the target element, by default, but you can override this behaviour and tell jsPlumb that it should create at most one Endpoint, which it should attempt to use for subsequent Connections:
    -</p>
    -		<div class="code">
    -<pre>var endpointOptions = { 
    -  isTarget:true, 
    -  uniqueEndpoint:true,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" } 
    -};
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -Here, the <strong>uniqueEndpoint</strong> parameter tells jsPlumb that there should be at most one Endpoint on this element.  Notice that 'maxConnections' is not set: the default is 1, so in this setup we have told jsPlumb that "aTargetDiv" can receive one Connection and no more.
    -<h5>Deleting Endpoints on Detach</h5>
    -By default, any Endpoints created using makeTarget have <strong>deleteEndpointsOnDetach</strong> set to true, which means that once all Connections to that Endpoint are removed, the Endpoint is deleted.  You can override this by setting the flag to true on the makeTarget call:
    -		<div class="code">
    -<pre>var endpointOptions = { 
    -  isTarget:true, 
    -  maxConnections:5,
    -  uniqueEndpoint:true,
    -  deleteEndpointsOnDetach:false,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" } 
    -};
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -In this setup we have told jsPlumb to create an Endpoint the first time "aTargetElement" is connected to, and to not delete it even if there are no longer any Connections to it.  The created Endpoint will be reused for subsequent Connections, and can support a maximum of 5.
    -
    -<h5>Detaching connections made with the mouse</h5>
    -As with jsPlumb.connect, connections made with the mouse after setting up Endpoints with one of the functions we've just covered will be, by default, detachable.  You can prevent this in the jsPlumb defaults, as previously mentioned:
    -		<div class="code">
    -<pre>jsPlumb.importDefaults({ 
    -	...
    -	ConnectionsDetachable:false,
    -	...
    -});
    -</pre>		
    -		</div>
    -
    -	And you can also set this on a per-endpoint (or source/target) level, like in these examples:
    -
    -		<div class="code">
    -<pre>jsPlumb.addEndpoint("someElementId", { 
    -	connectionsDetachable:false	
    -});
    -
    -jsPlumb.makeSource("someOtherElement", {
    -	...
    -	connectionsDetachable:false,
    -	...
    -});
    -
    -jsPlumb.makeTarget("yetAnotherElement", {
    -	...
    -	connectionsDetachable:false,
    -	...
    -});
    -</pre>		
    -		</div>
    -
    -Note that in the jsPlumb defaults, by convention each word in a parameter is capitalised ("ConnectionsDetachable"), whereas for a call to one of these methods, we use camel case ("connectionsDetachable").
    -
    -
    -<h5>Target Anchors positions with makeTarget</h5>
    -<p>When using the makeTarget method, jsPlumb allows you to provide a callback function to be used to determine
    -the appropriate location of a target anchor for every new Connection dropped on the given target.  It may be the
    -case that you want to take some special action rather than just relying on one of the standard anchor mechanisms.
    -</p>
    -<p>This is achieved through an extended anchor syntax (note that this syntax is <strong>not supported</strong> in
    -the <strong>jsPlumb.addEndpoint</strong> method) that supplies a "positionFinder" to the anchor specification.  jsPlumb 
    -provides two of these by default; you can register your own on jsPlumb and refer to them by name, or just supply a function.  Here's a few examples:</p>
    -Instruct jsPlumb to place the target anchor at the exact location at which the mouse button was released on the
    -target element. Note that you tell jsPlumb the anchor is of type "Assign", and you then provide a "position"
    -parameter, which can be the name of some position finder, or a position finder function itself:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {
    -  anchor:[ "Assign", { 
    -    position:"Fixed"
    -  }]
    -});
    -</pre>
    -</div>
    -<p><strong>Fixed</strong> is one of the two default position finders provided by jsPlumb. The other is <strong>Grid</strong>:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {
    -  anchor:[ "Assign", { 
    -    position:"Grid",
    -    grid:[3,3]
    -  }]
    -});
    -</pre>
    -</div>
    -The Grid position finder takes a "grid" parameter that defines the size of the grid required. [3,3] means
    -3 rows and 3 columns.
    -<h5>Supplying your own position finder</h5>
    -<p>To supply your own position finder to jsPlumb you first need to create the callback function. First let's take a look at what the source code for the Grid position finder looks like:</p>
    -<div class="code">
    -<pre>
    -function(eventOffset, elementOffset, elementSize, constructorParams) {
    -  var dx = eventOffset.left - elementOffset.left, dy = eventOffset.top - elementOffset.top,
    -      gx = elementSize[0] / (constructorParams.grid[0]), 
    -      gy = elementSize[1] / (constructorParams.grid[1]),
    -      mx = Math.floor(dx / gx), my = Math.floor(dy / gy);
    -
    -  return [ ((mx * gx) + (gx / 2)) / elementSize[0], ((my * gy) + (gy / 2)) / elementSize[1] ];
    -}
    -</pre>
    -</div>
    -The four arguments are:
    -<ul>
    -	<li class="bullet"><strong>eventOffset</strong> - Page left/top where the mouse button was released (a JS object containing left/top members like you get from a jQuery offset call)</li>
    -	<li class="bullet"><strong>elementOffset</strong> - JS offset object containing offsets for the element on which the Connection is to be created</li>
    -	<li class="bullet"><strong>elementSize</strong> - [width, height] array of the dimensions of the element on which the Connection is to be created</li>
    -	<li class="bullet"><strong>constructorParams</strong> - the parameters that were passed to the Anchor's constructor. In the example given above, those parameters are 'position' and 'grid'; you can pass arbitrary parameters.
    -</ul>
    -The return value of this function is an array of [x, y] - proportional values between 0 and 1 inclusive, such as you can pass to a static Anchor.
    -<p>To make your own position finder you need to create a function that takes those four arguments and returns an [x, y] position for the anchor, for example:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.AnchorPositionFinders.MyFinder = function(dp, ep, es, params) {
    -	...		
    -	return [ some maths ];	
    -};
    -</pre>
    -</div>
    -Then refer to it in a makeTarget call:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {  
    -  anchor:[ "Assign", { 
    -    position:"MyFinder",
    -    myCustomParameter:"foo",
    -    anInteger:5
    -  }]
    -});
    -</pre>
    -</div>
    -
    -<h5><a id="makeSource">jsPlumb.makeSource</a></h5>
    -There are two use cases supported by this method.  The first is the case that you want to drag a Connection from the element itself and have an Endpoint attached to the element when a Connection is established.  The second is a more specialised case: you want to drag a Connection from the element, but once the Connection is established you want jsPlumb to move it so that its source is on some other element.  For an example of this second case, check out the <a href="../demo/jquery/stateMachineDemo.html">State Machine Connectors</a> demonstration.
    -<p>Here's an example code snippet for the basic use case of makeSource:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource(someDiv, {
    -  paintStyle:{ fillStyle:"yellow" },
    -  endpoint:"Blank",
    -  anchor:"BottomCenter"
    -});
    -</pre>
    -</div>
    -<p>
    -Notice again that the second argument is the same as the second argument to an addEndpoint call.  makeSource is, essentially, a type of addEndpoint call.  In this example we have told jsPlumb that we will support dragging Connections directly from 'someDiv'.  Whenever a Connection is established between 'someDiv' and some other element, jsPlumb assigns an Endpoint at BottomCenter of 'someDiv', fills it yellow, and sets that Endpoint as the newly created Connection's source. 
    -</p>		
    -<p>Configuring an element to be an entire Connection source using makeSource means that the element cannnot itself be draggable.  There would be no way for jsPlumb to distinguish between the user attempting to drag the element and attempting to drag a Connection from the element.  If your UI works in such a way that that is acceptable then you'll be fine to use this method.  But if you want to drag Connections from some element but also have the element be draggable itself, you might want to consider the second use case.  First, a code snippet:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("someConnectionSourceDiv", {
    -  paintStyle:{ fillStyle:"yellow" },
    -  endpoint:"Blank",
    -  anchor:"BottomCenter",
    -  parent:"someDiv"
    -});
    -</pre>
    -</div>
    -<p>The only difference here is the inclusion of the 'parent' parameter.  It instructs jsPlumb to configure the source Endpoint on the element specified - in this case, "someDiv" (the value of this parameter may be a String id or a selector).</p>
    -<p>Again, I'd suggest taking a look at the <a href="../demo/jquery/stateMachineDemo.html">State Machine Connectors demonstration</a> for an example of this.</p>
    -
    -<h5>Filtering drag start based on element</h5>
    -You can supply a 'filter' parameter to the makeSource call in order to get a little more fine-grained control over which parts of the element will respond to a mousedown and start a drag. Consider this markup:
    -<div class="code">
    -<pre>
    -&lt;div id="foo"&gt;
    -  FOO
    -  &lt;button&gt;click me&lt;/button&gt;
    -&lt;/div&gt;
    -</pre>
    -</div>
    -Suppose we do not want to interfere with the operation of the 'click me' button. We can supply a filter to the makeSource call to do so:
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("foo", {
    -  filter:function(event, element) {
    -    return event.target.tagName !== "BUTTON";
    -  }
    -});
    -</pre>
    -</div>
    -If the filter returns anything other than a boolean false, the drag will begin. It's important to note that only boolean false will prevent a drag. False-y values will not.
    -
    -<h5>Endpoint options with makeSource</h5>
    -<p>
    -There are many things you can set in an Endpoint options object; for a thorough list see the API documentation for Endpoint.  
    -</p>
    -<p>Here's an example of specifying that you want an Arrow overlay halfway along any Connection dragged from this Endpoint:</p>
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -  isSource:true,
    -  connectorStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  connectorOverlays: [ [ "Arrow", { location:0.5 } ] ]
    -};
    -</pre>
    -</div>
    -This is an Endpoint that moves around the element it is attached to dependent on the location of other elements in the connections it is attached to (a 'dynamic' anchor):
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -  isSource:true,
    -  connectorStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  connectorOverlays: [ [ "Arrow", { location:0.5 } ] ]
    -  anchor:[ "TopCenter","RightMiddle","BottomCenter","LeftMiddle" ]
    -};
    -</pre>
    -</div>
    -<h5><a id="targetSourceTest">Testing if an element is a target or source</a></h5>
    -You can test if some element has been made a connection source or target using these methods:
    -<ul>
    -<li class="bullet">isTarget</li>
    -<li class="bullet">isSource</li>
    -</ul>
    -<p>The return value is a boolean.</p>
    -<h5><a id="targetSourceToggle">Toggling an element as connection target or source</a></h5>
    -You can toggle the state of some source or target using these methods:
    -<ul>
    -<li class="bullet">setTargetEnabled</li>
    -<li class="bullet">setSourceEnabled</li>
    -</ul>
    -<p>The return value from these methods is the current jsPlumb instance, allowing you to chain them:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.setTargetEnabled("aDivId").setSourceEnabled($(".aSelector"));
    -</pre>
    -</div>
    -<p>Note that if you call either of these methods on an element that was not originally configured as a target or source, nothing will happen.</p>
    -<p>You can check the enabled state of some target or source using these methods:
    -<ul>
    -<li class="bullet">isTargetEnabled</li>
    -<li class="bullet">isSourceEnabled</li>
    -</ul>
    -<h5><a id="targetSourceCancel">Canceling previous makeTarget and/or makeSource calls</a></h5>
    -jsPlumb offers four methods to let you cancel previous makeTarget or makeSource calls. Each of these methods returns the current jsPlumb instance, and so can be chained:
    -<ul>
    -<li class="bullet">unmakeTarget</li>
    -<li class="bullet">unmakeSource</li>
    -<li class="bullet">unmakeEveryTarget</li>
    -<li class="bullet">unmakeEverySource</li>
    -</ul>
    -<p>These last two are analogous to the <strong>removeEveryConnection</strong> and <strong>removeEveryEndpoint</strong> methods that have been in jsPlumb for a while now.</p>
    -<p>unmakeTarget and unmakeSource both take as argument the same sorts of values that makeTarget and makeSource accept - a string id, or a selector, or an array of either of these:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.unmakeTarget("aDivId").unmakeSource($(".aSelector"));
    -</pre>
    -</div>
    -
    -
    -<h5><a id="dragOptions">Drag Options</a></h5>
    -	These are options that will be passed through to the supporting library's drag API.  jsPlumb passes everything you supply here through, inserting
    -	wrapping functions if necessary for the various lifecycle events that jsPlumb needs to know about.  So if, for example, you pass a function to be
    -	called when dragging starts, jsPlumb will wrap that function with a function that does what jsPlumb needs to do, then call yours.
    -	<p>
    -	At the time of writing, jsPlumb supports jQuery, MooTools and YUI3, and each of those libraries uses different terminology.  In addition, jQuery's API is
    -	more fully featured, providing easy support for setting the zIndex and opacity of elements being dragged, as well as the 'scope' for a drag/drop (allowing
    -	you to specify more than one type of drag/drop pair), and hover classes for when a draggable is on the move or
    -	over a droppable. If you're using jQuery you can of course just supply these values on the dragOptions; to make it easier, jsPlumb's MooTools and YUI3 adapters recognize 
    -	these options and add appropriate callbacks for you.  
    -	</p> 
    -	<p>
    -	Given that the options here are library-specific, and they are all well-documented, we're going to discuss just the three drag options
    -	that behave the same way in all (see below for hoverClass):
    -		<ul>
    -		<li><strong>opacity</strong> - the opacity of an element that is being dragged.  Should be a fraction between 0 and 1 inclusive.</li>
    -		<li><strong>zIndex</strong> - the zIndex of an element that is being dragged.</li>
    -		<li><strong>scope</strong> - the scope of the draggable.  can only be dropped on a droppable with the same scope.  this is discussed below.</li>
    -		</ul> 
    -	</p>
    -	
    -	<p>For more information about drag options, take a look at the <a href="http://docs.jquery.com/UI/Draggable" target="_blank">jQuery</a>, <a href="http://mootools.net/docs/more/Drag/Drag.Move" target="_blank">MooTools</a>, or <a href="http://developer.yahoo.com/yui/3/dd/" target="_blank">YUI3</a> docs.	
    -	</p>
    -	<p><strong>NOTE: there is an issue in Chrome that affects the 'cursor' argument to drag options in jQuery. See these links:</strong>
    -	</p><p>
    -<a href="http://forum.jquery.com/topic/draggable-cursor-option-does-not-work" target="_blank">http://forum.jquery.com/topic/draggable-cursor-option-does-not-work</a><br/>
    -<a href="http://forum.jquery.com/topic/chrome-text-select-cursor-on-drag" target="_blank">http://forum.jquery.com/topic/chrome-text-select-cursor-on-drag</a>
    -</p>
    -You should put something like this at the top of your JS to avoid it (perhaps not something so drastic as a document-wide override):	
    -<div class="code">
    -<pre>document.onselectstart = function () { return false; };</pre>
    -</div>		
    -	
    -	<h5><a id="dropOptions">Drop Options</a></h5>
    -	Drop options are treated by jsPlumb in the same way as drag options - they are passed through to the underlying library.  MooTools does not have drop options like jQuery and YUI3 do; droppable functionality in MooTools 
    -	is actually implemented by the Drag.Move class - the one used to initialise a draggable.  But when you setup an Endpoint in jsPlumb you should ignore
    -	that fact, and treat droppables like you would in jQuery or YUI3.  jsPlumb wires everything up for you under the hood.
    -	<p>There are three jQuery droppable options that jsPlumb treats as shortcuts in MooTools and YUI3, for the sake of consistency:</p>
    -		<ul>
    -			<li><strong>hoverClass</strong> - the CSS class to attach to the droppable when a draggable is hovering over it.</li>
    -			<li><strong>activeClass</strong> - the CSS class to attach to the droppable when a draggable is, um, being dragged.</li>
    -			<li><strong>scope</strong> - the scope of the draggable.  The draggable can only be dropped on a droppable with the same scope.  This is discussed below.</li>
    -		</ul>   
    -	<p>For more information about drop options when using jQuery, see <a href="http://docs.jquery.com/UI/Droppable" target="_blank">here</a>.</p>
    -		
    -	<h5><a id="dragAndDropScope">Drag and Drop Scope</a></h5>
    -		jsPlumb borrowed the concept of 'scope' from jQuery's drag/drop implementation: the notion of
    -		which draggables can be dropped on which droppables.  In jsPlumb you can provide a 'scope' entry when 
    -		creating an Endpoint.  Here's the example grey Endpoint example with 'scope' added:
    -		
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:"#666" },
    -  isSource:true,
    -  connectionStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  scope:"exampleGreyConnection"
    -};</pre>		
    -</div>  		
    -  										
    -	If you do not provide a 'scope' entry, jsPlumb uses a default scope.  Its value is accessible through this method:
    -	<div class="code">
    -<pre>
    -jsPlumb.getDefaultScope();
    -</pre>	
    -	</div>				
    -	If you want to change it for some reason you can do so with this method:
    -	<div class="code">
    -<pre>
    -jsPlumb.setDefaultScope("mySpecialDefaultScope");
    -</pre>	
    -	</div>			
    -	You can also, should you want to, provide the scope value separately on the drag/drop options, like this:
    -	<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -  isSource:true,
    -  connectionStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  dragOptions:{ scope:"dragScope" },
    -  dropOptions:{ scope:"dropScope" }
    -};</pre>		
    -</div>
    -	
    -	<h5><a id="disconnectReconnect">Disconnecting and Reconnecting</a></h5>
    -	<p>
    -	  By default, jsPlumb will allow you to detach connections from either Endpoint by dragging (assuming the Endpoint allows it;
    -	  Blank Endpoints, for example, have nothing you can grab with the mouse). If you then drop a Connection you have dragged
    -	  off an Endpoint, the Connection will be detached. This behaviour can be controlled using the
    -	  <strong>detachable</strong> and <strong>reattach</strong>parameters, or their equivalents in the jsPlumb Defaults.
    -	  Some examples should help explain:	  
    -	</p>
    -Create a Connection that is detachable using the mouse, from either Endpoint, and which does not reattach:
    -	<div class="code">
    -<pre>
    -jsPlumb.connect({ source:"someElement", target:"someOtherElement" });
    -</pre>		
    -</div>
    -	Create a Connection that is NOT detachable using the mouse:
    -	<div class="code">
    -<pre>
    -jsPlumb.connect({ source:"someElement", target:"someOtherElement", detachable:false });
    -</pre>		
    -</div>
    -Create a Connection that is detachable using the mouse and which reattaches on drop:
    -	<div class="code">
    -<pre>
    -jsPlumb.connect({ source:"someElement", target:"someOtherElement", reattach:true });
    -</pre>		
    -</div>
    -	
    -This behaviour can also be controlled using jsPlumb defaults:
    -<div class="code">
    -<pre>
    -jsPlumb.importDefaults({
    -  ConnectionsDetachable:true,
    -  ReattachConnections:true
    -});
    -</pre>		
    -</div>
    -The default value of ConnectionsDetachable is <strong>true</strong>, and the default value of ReattachConnections is <strong>false</strong>,
    -so in actual fact those defaults are kind of pointless. But you probably get the point.
    -
    -<h5>Setting detachable/reattach on Endpoints</h5>
    -Endpoints support the 'detachable' and 'reattach' parameters too. If you create an Endpoint and mark 'detachable:false', then
    -all Connections made from that Endpoint will not be detachable.  However, since there are two Endpoints involved in any
    -Connection, jsPlumb takes into account the 'detachable' and 'reattach' parameters from both Endpoints when establishing
    -a Connection. If either Endpoint declares either of these values as true, jsPlumb assumes the value to be true.  It is possible
    -that in a future version of jsPlumb the concepts of detachable and reattach could be made more granular, through the
    -introduction of parameters like <strong>sourceDetachable</strong>/<strong>targetReattach</strong> etc.
    -
    -<h5>Dropping a dragged Connection on another Endpoint</h5>
    -If you drag a Connection from its target Endpoint you can then drop it on another suitable target Endpoint - suitable meaning
    -that it is of the correct scope and it is not full.  If you try to drop a Connection on another target that is full, the
    -drop will be aborted and then the same rules will apply as if you had dropped the Connection in whitespace: if 'reattach' is
    -set, the Connection will reattach, otherwise it will be removed.
    -<p>
    -  You can drag a Connection from its source Endpoint, but you can only drop it back on its source Endpoint - if you try to drop
    -  it on any other source or target Endpoint jsPlumb will treat the drop as if it happened in whitespace. Note that there is
    -  an issue with the hoverClass parameter when dragging a source Endpoint: target Endpoints are assigned the hover class, as if
    -  you could drop there. But you cannot; this is caused by how jsPlumb uses the underlying library's drag and drop, and is
    -  something that will be addressed in a future release.
    -</p>
    -	
    -	</div>	
    -
    -	<div class="section">
    -		<h3><a id="parameters">Connection Parameters</a></h3>
    -		jsPlumb has a mechanism that allows you to set parameters on a per-connection basis.  This can be achieved in a few different ways:
    -
    -		<ul>
    -			<li class="bullet">Providing parameters to a jsPlumb.connect call</li>
    -			<li class="bullet">Providing parameters to an addEndpoint call to/from which a connection is subsequently established using the mouse</li>
    -			<li class="bullet">using the <strong>setParameter</strong> or <strong>setParameters</strong> method on a Connection</li>
    -		</ul>
    -		Getting parameters is achieved through either the <strong>getParameter(key)</strong> or <strong>getParameters</strong> method on a Connection.
    -		<h4><a id="connectParameters">jsPlumb.connect</a></h4>
    -		Parameters can be passed in via an object literal to a jsPlumb.connect call:
    -		<div class="code">
    -<pre>var myConnection = jsPlumb.connect({
    -	source:"foo",
    -	target:"bar",
    -	parameters:{
    -		"p1":34,
    -		"p2":new Date(),
    -		"p3":function() { console.log("i am p3"); }
    -	}
    -});
    -</pre>
    -</div>
    -Note that they can be any valid Javascript objects - it's just an object literal.  You then access them like this:
    -		<div class="code">
    -<pre>myConnection.getParameter("p3")();     // prints 'i am p3' to the console.
    -</pre>
    -</div>
    -
    -<h4><a id="addEndpointParameters">jsPlumb.addEndpoint</a></h4>
    -<p>The information in this section also applies to the <strong>makeSource</strong> and <strong>makeTarget</strong> functions</p>
    -<p>Using jsPlumb.addEndpoint, you can set parameters that will be copied in to any Connections that are established from or to the given Endpoint using the mouse.  (If you set parameters on both a source and target Endpoints and then connect them, the parameters set on the target Endpoint are copied in first, followed by those on the source. So the source Endpoint's parameters take precedence if they happen to have one or more with the same keys as those in the target).</p>
    -Consider this example:
    -		<div class="code">
    -<pre>var e1 = jsPlumb.addEndpoint("d1", {
    -	isSource:true,
    -	parameters:{
    -		"p1":34,
    -		"p2":new Date(),
    -		"p3":function() { console.log("i am p3"); }
    -	}
    -});
    -
    -var e2 = jsPlumb.addEndpoint("d2", {
    -	isTarget:true,
    -	parameters:{
    -		"p5":343,
    -		"p3":function() { console.log("FOO FOO FOO"); }
    -	}
    -});
    -
    -var conn = jsPlumb.connect({source:e1, target:e2});
    -
    -
    -</pre>
    -</div>
    -
    -'conn' will have four parameters set on it, with the value for "p3" coming from the source Endpoint:
    -		<div class="code">
    -<pre>var params = conn.getParameters();
    -console.log(params.p1);   	// 34
    -console.log(params.p2);   	// Mon May 14 2012 12:57:12 GMT+1000 (EST) (or however your console prints out a Date)
    -console.log((params.p3)()); // "i am p3"  (note: we executed the function after retrieving it)
    -console.log(params.p5);   	// 343
    -
    -</pre>
    -</div>
    -
    -
    -</div> <!-- /section -->
    -
    -<div class="section">
    -	<h3><a id="connectionTypes">Connection and Endpoint Type</a></h3>
    -	<p>
    -	jsPlumb supports the notion of "type" for both Connections and Endpoints. A type is a
    -	collection of attributes such as paint style, hover paint style, overlays etc - it is a subset, including most but not all, of the
    -	parameters you can set in an Endpoint or Connection definition. 
    -	</p>
    -	<p>
    -	An Endpoint or Connection can have zero or more types assigned; they are merged as granularly as possible, in the order
    -	in which they were assigned. There is a supporting API that works in the same way as the class stuff does in
    -	jQuery - hasType, addType, removeType, toggleType, setType, and each of these methods (except hasType) takes a space-separated
    -	string so you can add several at once.  Support for these methods has been added to the jsPlumb.select and
    -	jsPlumb.selectEndpoint methods, and you can also now specify a 'type' parameter to an Endpoint or Connection at create time.
    -	</p>
    -	<h4>Connection Type</h4>
    -	Probably the easiest way to explain types is with some code. In this snippet, we'll register a Connection type on jsPlumb,
    -	create a Connection, and then assign the type to it:
    -<div class="code">
    -<pre>
    -jsPlumb.registerConnectionType("example", {
    -	paintStyle:{ strokeStyle:"blue", lineWidth:5  },
    -	hoverPaintStyle:{ strokeStyle:"red", lineWidth:7 }
    -});
    -
    -var c = jsPlumb.connect({ source:"someDiv", target:"someOtherDiv" });
    -c.bind("click", function() {
    -	c.setType("example");
    -});	
    -</pre>		
    -</div>
    -<p>Another example - a better one, in fact.  Say you have a UI in which you can click to select or deselect Connections, and
    -you want a different appearance for each state.  Connection types to the rescue!</p>
    -<div class="code">
    -<pre>
    -jsPlumb.registerConnectionTypes({
    -	"basic": {
    -		paintStyle:{ strokeStyle:"blue", lineWidth:5  },
    -		hoverPaintStyle:{ strokeStyle:"red", lineWidth:7 }
    -	},
    -	"selected":{
    -		paintStyle:{ strokeStyle:"red", lineWidth:5 },
    -		hoverPaintStyle:{ lineWidth: 7 }
    -	}	
    -});
    -
    -var c = jsPlumb.connect({ source:"someDiv", target:"someOtherDiv", type:"basic" });
    -
    -c.bind("click", function() {
    -	c.toggleType("selected");
    -});	
    -</pre>		
    -</div>
    -<p>Notice here how we used a different method - <strong>registerConnectionTypes</strong> - to register a few types at once.
    -Notice also the hoverPaintStyle for the 'selected' type: it declares only a lineWidth.  As mentioned above, types are
    -merged with as much granularity as possible, so that means that in this case the lineWidth from 'selected' will be merged
    -into the hoverPaintStyle from 'basic', and voila, red, 7 pixels.</p>
    -<p>These examples, of course, use the <strong>jsPlumb.connect</strong> method, but in many UIs Connections are created
    -via drag and drop.  How would you assign that 'basic' type to a Connection created with drag and drop? Provide it as
    -the Endpoint's connectorType parameter, like so:</p>
    -<div class="code">
    -<pre>
    -	var e1 = jsPlumb.addEndpoint("someDiv", {
    -		connectorType:"basic",
    -		isSource:true
    -	});
    -	
    -	var e2 = jsPlumb.addEndpoint("someOtherDiv", {
    -		isTarget:true
    -	});
    -		
    -	//... user then perhaps drags a connection...or we do it programmatically:
    -	
    -	var c = jsPlumb.connect({ source:e1, target:e2 });
    -	
    -	// now c has type 'basic'
    -	console.log(c.hasType("basic));   // -> true
    -</pre>	
    -</div>
    -<p>Note that the second Endpoint we created did not have a 'connectorType' parameter - we didn't need it, as the source
    -Endpoint in the Connection had one.  But we could have supplied one, and jsPlumb will use it, but only if the source
    -Endpoint has not declared connectorType.  This is the same way jsPlumb treats other connector parameters such as
    -paintStyle etc - the source Endpoint wins.</p>
    -<p>Here's a few examples showing you the full type API:</p>
    -<div class="code">
    -<pre>
    -	jsPlumb.registerConnectionTypes({
    -		"foo":{ paintStyle:{ strokeStyle:"yellow", lineWidth:5 } },
    -		"bar":{ paintStyle:{ strokeStyle:"blue", lineWidth:10 } },
    -		"baz":{ paintStyle:{ strokeStyle:"green", lineWidth:1 } }
    -	});
    -	
    -	var c = jsPlumb.connect({ source:"someDiv", target:"someOtherDiv", type:"foo" });
    -	
    -	// see what types the connection has.  
    -	console.log(c.hasType("foo"));  // -> true
    -	console.log(c.hasType("bar"));  // -> false
    -	
    -	// add type 'bar'
    -	c.addType("bar");
    -	
    -	// toggle both types (they will be removed in this case)
    -	c.toggleType("foo bar");
    -	
    -	// toggle them back
    -	c.toggleType("foo bar");
    -	
    -	// getType returns a list of current types.
    -	console.log(c.getType()); // -> [ "foo", "bar" ]
    -	
    -	// set type to be 'baz' only
    -	c.setType("baz");
    -	
    -	// add foo and bar back in
    -	c.addType("foo bar");
    -	
    -	// remove baz and bar
    -	c.removeType("baz bar");
    -	
    -	// what are we left with? good old foo.
    -	console.log(c.getType()); // -> [ "foo" ]
    -	
    -</pre>	
    -</div>
    -<p>Things to note here are that every method <strong>except hasType</strong> can take a space-delimited list of types
    -to work with. So types work like CSS classes, basically.</p>
    -<h5>Supported Parameters in Connection Type objects</h5>
    -Not every parameter from a Connection's constructor is supported in a Connection Type - as mentioned above, types act pretty
    -much like CSS classes, so the things are supported are related to behaviour or appearance. For instance, 'source' is
    -not supported.  You cannot make a Connection Type that is fixed to a specific source element. Here's the full list of
    -supported properties in Connection Type objects:
    -<ul>
    -	<li class="bullet"><strong>detachable</strong> - whether or not the Connection is detachable using the mouse</li>
    -	<li class="bullet"><strong>paintStyle</strong></li>
    -	<li class="bullet"><strong>hoverPaintStyle</strong></li>
    -	<li class="bullet"><strong>scope</strong> - remember, Connections support a single scope. So if you have multiple types applied, you will get the scope from the last type that defines one.</li>
    -	<li class="bullet"><strong>parameters</strong> - when you add/set a type that has parameters, any existing parameters with the same keys will be overwritten. When you remove a type that has parameters, its parameters are NOT removed from the Connection.</li>
    -	<li class="bullet"><strong>overlays</strong> - when you have multiple types applied to a Connection, you get the union of
    -	all the Overlays defined across the various types. <strong>Note</strong> when you create a Connection using jsPlumb.connect and you
    -	provide a 'type', that is equivalent to calling 'addType': you will get the Overlays defined by the type(s) you set as
    -	well as any others you have provided to the constructor.</li>	
    -</ul>
    -
    -<h4><a id="endpointTypes">Endpoint Type</a></h4>
    -Endpoints can also be assigned one or more types, both at creation and programmatically using the API discussed above.
    -The only real differences between Endpoint and Connection types are the allowed parameters.  Here's the list for Endpoints:
    -<ul>
    -	<li class="bullet"><strong>paintStyle</strong></li>
    -	<li class="bullet"><strong>hoverPaintStyle</strong></li>
    -	<li class="bullet"><strong>maxConnections</strong></li>
    -	<li class="bullet"><strong>connectorStyle</strong> - paint style for any Connections that use this Endpoint.</li>
    -	<li class="bullet"><strong>connectorHoverStyle</strong> - hover paint style for Connections from this Endpoint.</li>
    -	<li class="bullet"><strong>connector</strong> - a Connector definition, like "StateMachine", or [ "Flowchart", { stub:50 } ] </li>
    -	<li class="bullet"><strong>connectionType</strong> - This allows you to specify the Connection Type for Connections made from this Endpoint.</li>
    -	<li class="bullet"><strong>scope</strong> - remember, Endpoints support a single scope. So if you have multiple types applied, you will get the scope from the last type that defines one.</li>
    -	<li class="bullet"><strong>parameters</strong> - when you add/set a type that has parameters, any existing parameters with the same keys will be overwritten. When you remove a type that has parameters, its parameters are NOT removed from the Connection.</li>
    -	<li class="bullet"><strong>overlays</strong> - when you have multiple types applied to an Endpoint, you get the union of all the Overlays defined across the various types.</li>
    -	
    -</ul>
    -<p>One thing to be aware of is that the parameters here that are passed to Connections are only passed from a source Endpoint,
    -not targets.</p>
    -Here's an example of using Endpoint types:
    -<div class="code">
    -<pre>
    -  jsPlumb.registerEndpointTypes({
    -    "basic":{			
    -      paintStyle:{fillStyle:"blue"}
    -    },
    -    "selected":{			
    -      paintStyle:{fillStyle:"red"}
    -    }
    -  });
    -
    -  var e = jsPlumb.addEndpoint("someElement", {
    -    anchor:"TopMiddle",
    -	type:"basic"
    -  });
    -  
    -  e.bind("click", function() {
    -    e.toggleType("selected");
    -  });
    -</pre>
    -</div>
    -So it works the same way as Connection Types.  There are several parameters allowed by an Endpoint Type that affect Connections
    -coming from that Endpoint. Note that this does not affect existing Connections.  It affects only Connections that are
    -created after you set the new type(s) on an Endpoint.
    -
    -<h4>Fluid Interface</h4>
    -As mentioned previously, all of the type operations are supported by the <strong>select</strong> and <strong>selectEndpoints</strong> methods.
    -So you can now do things like this:
    -<div class="code">
    -<pre>
    -	jsPlumb.selectEndpoints({scope:"terminal"}).toggleType("active");
    -	
    -	jsPlumb.select({source:"someElement"}).addType("highlighted available");
    -	
    -	etc
    -	
    -</pre>
    -</div>
    -Obviously, in these examples, 'available' and 'highlighted' would have previously been registered on jsPlumb using the appropriate
    -register methods.
    -
    -</div> <!-- / connection/endpoint types	-->
    -	
    -	<div class="section">
    -		<h3><a id="beforeDrop">Interceptors</a></h3>
    -		<p>
    -		Interceptors were new in jsPlumb 1.3.4.  They are basically event handlers from which you can return a value that tells jsPlumb to abort what it is that
    -		it was doing.  There are currently two interceptors supported - <strong>beforeDrop</strong>, which is called when the user has dropped a Connection onto some target, 
    -		and <strong>beforeDetach</strong>, which is called when the user is attempting to detach a Connection. 
    -		</p>
    -		<p>Interceptors can be registered via the <strong>bind</strong> method on jsPlumb just like any other event listeners, and they can also be passed in to
    -		the <strong>jsPlumb.addEndpoint</strong>, <strong>jsPlumb.makeSource</strong> and <strong>jsPlumb.makeTarget</strong>
    -		methods.  A future version of jsPlumb will support registering interceptors on Endpoints using the <strong>bind</strong> method as you can now with the jsPlumb object.
    -		Note that binding 'beforeDrop' (as an example) on jsPlumb itself is like a catch-all: it will be called every time a Connection is dropped on _any_ 
    -		Endpoint.  But passing a 'beforeDrop' callback into an Endpoint (and in the future, registering one using 'bind') constrains that callback to just the
    -		Endpoint in question.  		
    -		<h4><a id="beforeDrop">beforeDrop</a></h4>
    -		This event is fired when a new or existing connection has been dropped. Your callback is passed a JS object with these fields:
    -		<ul>
    -			<li><strong>sourceId</strong> - the id of the source element in the connection</li>
    -			<li><strong>targetId</strong> - the id of the target element in the connection</li>
    -			<li><strong>scope</strong> - the scope of the connection</li>
    -			<li><strong>connection</strong> - the actual Connection object.  You can access the 'endpoints' array in a Connection to get the Endpoints involved in the Connection, but be aware that when a Connection is being dragged, one of these Endpoints will always be a transient Endpoint that exists only for the life of the drag. To get the Endpoint on which the Connection is being dropped, use the 'dropEndpoint' member.</li>
    -			<li><strong>dropEndpoint</strong> - this is the actual Endpoint on which the Connection is being dropped.  This <strong>may be null</strong>, because it will not be set if the Connection is being dropped on an element on which makeTarget has been called. </li>
    -		</ul>
    -
    -		If you return false (or nothing) from this callback, the new Connection is aborted and removed from the UI.
    -		<h4><a id="beforeDetach">beforeDetach</a></h4>
    -		This is called when the user has detached a Connection, which can happen for a number of reasons: by default, jsPlumb allows users to drag Connections off of target Endpoints, but this can also result from a programmatic 'detach' call.  Every case is treated the same by jsPlumb, so in fact it is possible for you to write code that attempts to detach a Connection but then denies itself!  You might want to be careful with that. 
    -		<p>Note that this interceptor is passed the actual Connection object; this is different from the beforeDrop
    -		interceptor discussed above: in this case, we've already got a Connection, but with beforeDrop we are yet 
    -		to confirm that a Connection should be created.</p>
    -		<p>Returning false - or nothing - from this callback will cause the detach to be abandoned, and the Connection will be reinstated or left on its current target.</p>
    -			
    -	</div>
    -
    -		<div class="section">
    -			<h3><a id="paintstyles">Paint Styles</a></h3>
    -			Defining the appearance of Connectors, Endpoints (and Overlays, but this is deprecated in favour of using CSS classes) is achieved through a 'paintStyle' (or a quite similar name)
    -			object passed as a parameter to one of jsPlumb.connect, jsPlumb.addEndpoint, jsPlumb.makeSource or jsPlumb.makeTarget. Depending on the method you are calling, the parameter names vary.
    -			<h5>Connector Paint Styles</h5>
    -			These are specified in a <em>paintStyle</em> parameter on a call to <strong>jsPlumb.connect</strong>:
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			or in the <em>connectorPaintStyle</em> parameter on a call to <strong>jsPlumb.addEndpoint</strong> or <strong>jsPlumb.makeSource</strong>:
    -			<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div> 			
    -			 
    -
    -Notice the <em>paintStyle</em> parameter in those examples: it is the paint style for the Endpoint, which we'll discuss below.
    -
    -			<h5>Endpoint Paint Styles</h5>
    -			These are specified in a <em>paintStyle</em> parameter on a call to <strong>addEndpoint</strong>. This is the example from just above:
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -</div> 
    -			...or as the <em>endpointStyle</em> parameter to a <strong>connect</strong> call:		
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			... or as an entry in the <em>endpointStyles</em> array passed to a <strong>jsPlumb.connect</strong> call:
    -					<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  endpointStyles:[ 
    -    { fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -    { fillStyle:"green" }
    -  ],
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			or as the <em>paintStyle</em> parameter passed to a <strong>makeTarget</strong> or <strong>makeSource</strong> call:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("el1", {
    -  ...
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  ...
    -});
    -</pre>			
    -</div>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 }
    -  parent:"someOtherDivIJustPutThisHereToRemindYouYouCanDoThis"
    -});
    -</pre>			
    -			</div>						
    -	In the first example we  made "el1" into a drop target, and defined a paint style for the Endpoint jsPlumb will create when a Connection is
    -	established.  In the second we made "el1" a connection source and assigned the same values for the Endpoint jsPlumb will create when a Connection
    -	is dragged from that element.
    -	
    -	<h5>Overlay Paint Styles</h5>
    -	The preferred way to set paint styles for Overlays is to use the <em>cssClass</em> parameter in the constructor arguments of an Overlay definition.
    -	
    -	<h4>Paint Style Parameters</h4>
    -	This is the full list of parameters you can set in a paintStyle object, but note that <strong>fillStyle</strong> is ignored by Connectors,
    -	and <strong>strokeStyle</strong> is ignored by Endpoints.  Also, if you create a Connection using jsPlumb.connect and do not specify any
    -	Endpoint styles, the Endpoints will derive their fillStyle from the Connector's strokeStyle.
    -	<p>
    -		fillStyle, strokeStyle and outlineColor can be specified using any valid CSS3 syntax.
    -	</p>
    -	<ul>
    -		<li><strong>fillStyle</strong> - color for an Endpoint, eg. rgba(100,100,100,50), "blue", "#456", "#993355", rgb(34, 56, 78).</li>
    -		<li><strong>strokeStyle</strong> - color for a Connector. see fillStyle examples.</li>
    -		<li><strong>lineWidth</strong> - width of a Connector's line. An integer.</li>
    -		<li><strong>outlineWidth</strong> - width of the outline for an Endpoint or Connector. An integer.</li>
    -		<li><strong>outlineColor</strong> - color of the outline for an Endpoint or Connector. see fillStyle examples.</li>
    -		<li><strong>dashstyle</strong> - VML and SVG only. This comes from VML, and allows you to create dashed or dotted lines.  It has a 
    -			better syntax than the equivalent attribute in SVG (<em>stroke-dasharray</em>, discussed below), so jsPlumb supports this for both renderers. 
    -			The <em>dashstyle</em> attribute is specified as an array of strokes and spaces, where each value is some multiple of <em>the width of 
    -			the Connector</em>, and that's where it's better than SVG, which uses pixels.
    -			<p>
    -			<a href="http://www.w3.org/TR/NOTE-VML" target="_blank">The VML spec</a> is a good place to find valid values for dashstyle. Note that
    -			jsPlumb does not support the string values for this attribute ("solid", "dashdot", etc).
    -			</p>
    -			<p>
    -			In SVG render mode, jsPlumb uses the <em>lineWidth</em> parameter in conjunction with the values in a <em>dashstyle</em> attribute
    -			to create an appropriate value for <em>stroke-dasharray</em>.
    -			</p>
    -		</li>
    -		<li><strong>stroke-dasharray</strong> - SVG only. This is the SVG equivalent of <em>dashstyle</em>.  
    -		<a href="http://www.w3.org/TR/SVG/painting.html" target="_blank">The SVG spec</a> discusses valid values for this parameter.  But be aware
    -		that jsPlumb does not convert this into a valid <em>dashstyle</em> attribute when using the VML renderer. Better to use <em>dashstyle</em>.
    -		</li>
    -		<li><strong>stroke-dashoffset</strong> - SVG only.  This is used in SVG to specify how far into the dash pattern to start painting.  For more information,
    -		see <a href="http://www.w3.org/TR/SVG/painting.html" target="_blank">the SVG spec.</a></li>
    -		<li><strong>joinstyle</strong> - VML and SVG only.  As with <em>dashstyle</em>, this is a VML attribute that jsPlumb supports for both VML
    -		and SVG - jsPlumb turns this into a <em>stroke-linejoin</em> attribute when rendering with SVG.  This attribute specifies how you want individual
    -		segments of connectors to be joined; the VML and SVG specs both have examples of this, of which many are the same between the two, which is
    -		why jsPlumb will automatically convert this attribute into the SVG equivalent.
    -		</li>
    -		<li><strong>stroke-linejoin</strong> - SVG only.  This is the equivalent of VML's <em>joinstyle</em> attribute, but as with <em>stroke-dasharray</em>,
    -		jsPlumb does not convert this into something approriate for VML.  So, using <em>joinstyle</em> will enable you to support more browsers with
    -		less effort.		
    -		</li>
    -	</ul>
    -	
    -	<h3><a id="hoverpaintstyles">Hover Paint Styles</a></h3>
    -	 		
    -	 		Connectors and Endpoints both support the concept of a "hover" paint style - a paint style to use when the mouse is hovering over
    -	 		the component.  These are specified in the exact same format as paint styles discussed above, but hover paint styles also inherit
    -	 		any values from the main paint style.  This is because you will typically want to just change the color, or perhaps outline color, of
    -	 		a Connector or Endpoint when the mouse is hovering, but leave everything else the same.  So having hover paint styles inherit their
    -	 		values precludes you from having to define things in more than one place.
    -	 		<p>
    -	 			The naming convention adopted for hover paint styles is pretty much to insert the word 'hover' into the corresponding main paint
    -	 			style parameters.  Here are a couple of examples:
    -	 		</p> 
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -  hoverPaintStyle:{ strokeStyle:"red" },
    -  endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  endpointHoverStyle:{ fillStyle:"red" }
    -});
    -</pre>			
    -</div>
    -			In this example we specified a hover style for both the Connector, and each of its Endpoints.  Here's the same thing, but using the plural version, to specify a different hover style for each Endpoint:	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -  hoverPaintStyle:{ strokeStyle:"red" },
    -  endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  endpointHoverStyles:[ { fillStyle:"red" }, { fillStyle:"yellow" } ]
    -});
    -</pre>			
    -</div>
    -	 		Calls to <strong>addEndpoint</strong>, <strong>makeSource</strong> and <strong>makeTarget</strong> can also specify various hover paint styles:
    -	 								<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  hoverPaintStyle:{ fillStyle:"red" },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -  connectorHoverPaintStyle:{ strokeStyle:"red", outlineColor:"yellow", outlineWidth:1 }
    -});
    -
    -jsPlumb.makeSource("el2", {
    -  paintStyle:{ fillStyle:"transparent", outlineColor:"yellow", outlineWidth:1 },
    -  hoverPaintStyle:{ fillStyle:"red" },
    -  connectorPaintStyle:{ strokeStyle:"green", lineWidth:3 },
    -  connectorHoverPaintStyle:{ strokeStyle:"#678", outlineColor:"yellow", outlineWidth:1 }
    -});
    -
    -jsPlumb.makeTarget("el3", {
    -  paintStyle:{ fillStyle:"transparent", outlineColor:"yellow", outlineWidth:1 },
    -  hoverPaintStyle:{ fillStyle:"red" }
    -});
    -</pre>			
    -			</div> 
    -	 		
    -	 		In these examples we specified a hover paint style for both the Endpoint we are adding, and any Connections to/from the Endpoint.
    -	 		<p>	
    -		Note that <strong>makeTarget</strong> does not support Connector parameters. It is for creating targets only; Connector parameters will be set by the source Endpoint in any Connections that are made to the element that you turned into a target by using this method.</p>
    -	 			 		
    -			<h4><a id="gradients">Gradients</a></h4>
    -			The Canvas and SVG renderers both support gradients. The VML renderer does not.  jsPlumb uses its own syntax to define gradients, to abstract out the differences between the syntax required by canvas and that required by SVG.  
    -			<p>There are two types of gradients available - a 'linear' gradient, which consists of colored lines all
    -			going in one direction, and a 'radial' gradient, which consists of colored circles emanating from one circle to another.
    -			Because of their basic shape, jsPlumb supports only <em>linear</em> gradients for
    -			Connectors.  But for Endpoints, jsPlumb supports both <em>linear</em> and <em>radial</em> gradients.</p>
    -			
    -				<h5>Connector gradients</h5>
    -				To specify a linear gradient to use in a Connector, you must add a <em>gradient</em> object to your
    -				Connector's <em>paintStyle</em>, for instance:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : "window2",
    -	target : "window3",
    -	paintStyle:{
    -		gradient:{
    -			stops:[[0,"green"], [1,"red"]]
    -		},
    -		lineWidth:15
    -	}
    -});
    -</pre>
    -</div>
    -Here we have connected window2 to window3 with a 15 pixel wide connector that has a gradient from green to red.</p><p> Notice the <strong>gradient</strong> object and the
    -<strong>stops</strong> list inside it - the gradient consists of an arbitrary number of these "color stops".  Each color stop is comprised
    -of two values - [position, color].  Position must be a decimal value between 0 and 1 (inclusive), and indicates where the color
    -stop is situated as a fraction of the length of the entire gradient.  Valid values for
    -the colors in the <strong>stops</strong> list are the same as those that are valid for <strong>strokeStyle</strong> when describing a color.
    -			</p>
    -			As mentioned, the <strong>stops</strong> list can hold an arbitrary number of entries.  Here's an example of a gradient that goes from red to blue to green, and back again through blue to red:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle : {
    -		gradient:{
    -			stops:[[0,'red'], [0.33,'blue'], [0.66,'green'], [0.33,'blue'], [1,'red']]
    -		},
    -		lineWidth : 15
    -	}
    -});
    -</pre>
    -</div>
    -<strong>Note:</strong> when using the VML renderer, jsPlumb will simply ignore the gradient directive
    -so it is best to ensure you also supply a <em>strokeStyle</em> in your paintStyle object, to give jsPlumb something to fall back on.  If you do not supply
    -a <em>strokeStyle</em> your Connectors will be painted black.  The previous example might look like this, for instance:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle:{
    -		strokeStyle:'red',
    -		gradient:{
    -			stops:[[0,'red'], [0.33,'blue'], [0.66,'green'], [0.33,'blue'], [1,'red']]
    -		},
    -		lineWidth:15
    -	}
    -});
    -</pre>
    -</div>
    -Notice the <strong>strokeStyle:'red'</strong> directive at the beginning of the parameter list in <strong>paintStyle</strong>.
    -			<h5>Endpoint gradients</h5>
    -			Endpoint gradients are specified using the same syntax as Connector gradients.  You put the gradient specifier either in the
    -			<strong>endpoint</strong> member, or if you are specifying different Endpoints for each end of the Connector, in one or both of
    -			the values in the <strong>endpoints</strong> array.  Also, this information applies to the case that you are creating standalone
    -			Endpoints that you will be configuring for drag and drop creation of new Connections. 
    -			<p>
    -			This is an example of an Endpoint gradient that is different for each Endpoint in the Connector.  This comes from the main demo; it is
    -			the Connector joining Window 2 to Window 3:
    -			</p>
    -<div class="code">
    -<pre>var w23Stroke = 'rgb(189,11,11)';
    -jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle:{
    -		lineWidth:8,
    -		strokeStyle:w23Stroke
    -	},
    - 	anchors:[ [0.3,1,0,1], "TopCenter" ],
    - 	endpoint:"Rectangle",
    - 	endpointStyles:[
    - 		{ gradient : {stops:[[0, w23Stroke], [1, '#558822']] } },
    -    	{ gradient : {stops:[[0, w23Stroke], [1, '#882255']] } }
    -    ]
    -});
    -</pre>
    -</div>
    -The first entry in the gradient will be the one that is on the Connector end of the Endpoint.  You can of course have as many color stops as
    -you want in this gradient, just like with Connector gradients.
    -	<h5>Applying the gradient in Endpoints</h5>
    -	Only the Dot and Rectangle endpoints honour the presence of a gradient (and, remember, not in VML). The Image endpoint of course ignores a gradient
    -	as it does no painting of its own.
    -	<p>The type of gradient you will see depends on the Endpoint type:</p>
    -		<ul>
    -			<li><strong>Dot</strong> - renders a radial endpoint, with color stop 0 on the outside, progressing inwards as we move through color stops.
    -			<p>Radial gradients actually require more data than linear gradients - in a linear gradient we just move from one point to another, whereas
    -			in a radial gradient we move from one <em>circle</em> to another.  By default, jsPlumb will render a radial gradient using a source
    -			circle of the same radius as the Endpoint itself, and a target circle of 1/3 of the radius of the Endpoint (both circles share the
    -			same center as the Endpoint itself). This circle will be offset by radius/2 in each direction.</p>
    -			<p>You can supply your own values for these inside the gradient descriptor:</p>
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect({
    -	source : 'window3',
    -	target : 'window4',
    -    paintStyle:{
    -    	lineWidth:10,
    -    	strokeStyle:w34Stroke
    -    },
    -    anchors:[ "RightMiddle", "LeftMiddle" ],
    -    endpointStyle:{
    -    	gradient : {
    -    		stops:[ [0, w34Stroke], [1, w34HlStroke] ],
    -    		offset:37.5,
    -    		innerRadius:40
    -    	},
    -    	radius:55
    -    }
    - });
    -</pre>
    -</div>
    -Here we have instructed jsPlumb to make the gradient's inner radius 10px instead of the default 25/3 = 8 ish pixels, and the offset in each direction
    -will be 5px, instead of the default radius / 2 = 12.5 pixels.
    -<p>It is also possible to specify the offset and inner radius as percentages - enter the values as strings with a '%' symbol on the end:</p>
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect({
    -	source : 'window3', 
    -	target : 'window4',
    -    paintStyle:{
    -    	lineWidth:10,
    -    	strokeStyle:w34Stroke
    -    },
    -	anchors:[ "RightMiddle", "LeftMiddle" ],
    -	endpointStyle:{
    -		gradient : {
    -			stops:[ [0, w34Stroke], [1, w34HlStroke] ],
    -			offset:'68%',
    -			innerRadius:'73%'
    -		},
    -		radius:25
    -	}
    -});
    -</pre>
    -</div>
    -This will give roughly the same output as the example above (the percentages are not entirely exact).<br/><br/>
    -			</li>
    -			<li><strong>Rectangle</strong> - renders a linear endpoint, with color stop 0 closest to the end of the Connector</li>
    -		</ul>
    -		
    -<h4><a id="svgvmlcss">Styling SVG/VML using CSS</a></h4>
    -	A nice way of controlling your UI's appearance is to make use of the fact that both SVG and VML can be styled using CSS.
    -	<p>This section will be expanded in the next few weeks to give some decent examples of how to do so</p>
    -			
    -		
    -		
    -		</div>
    -
    -		<div class="section">
    -			<h3><a id="cssclasses">CSS</a></h3>
    -			In an ideal world, you'd be able to control the appearance of your UI using CSS only, but due to difference between
    -			renderers, this is not the case.  The Canvas renderer, for instance, is completely unaware of CSS, and VML can
    -			only be styled up to a point with CSS.  SVG behaves best, and perhaps at some stage in the future when all browsers
    -			in popular usage support SVG decently, we'll be able to switch over completely to CSS.
    -			<p>For now, though, you can - and should - still use CSS for one important consideration: z-indices.  Every Connection,
    -			Endpoint and Overlay in jsPlumb adds some element to the UI, and you should take care to establish appropriate z-indices for each
    -			of these, in conjunction with the nodes in your application. 
    -			</p>
    -			<p>By default, jsPlumb adds a specific class to each of the three types of elements it creates (These class names are
    -			exposed on the jsPlumb object and can be overridden if you need to do so - see the third column in the table) </p>
    -			<table width="90%" class="table" style="align:left;font-size:12px;">
    -				<tr><th>Component</th><th>CSS Class</th><th>jsPlumb Member</th></tr>
    -				<tr><td>Connector</td><td>_jsPlumb_connector</td><td>connectorClass</td></tr>
    -				<tr><td>Endpoint</td><td>_jsPlumb_endpoint</td><td>endpointClass</td></tr>
    -				<tr><td>Overlay</td><td>_jsPlumb_overlay</td><td>overlayClass</td></tr>
    -			</table>
    -			<p>In addition to these defaults, each of the main methods you use to configure Endpoints or make Connections
    -			in jsPlumb support the following two parameters:</p>
    -			<ul>
    -				<li class="bullet"><strong>cssClass</strong> - class(es) to set on the display elements</li>
    -				<li class="bullet"><strong>hoverClass</strong> - class(es) to set on the display elements when in hover mode</li>
    -			</ul>
    -			In addition, addEndpoint and makeSource allow you to specify what these classes will be for any Connections that are dragged from them:
    -			<ul>
    -				<li class="bullet"><strong>connectorClass</strong> - class(es) to set on the display elements of Connections</li>
    -				<li class="bullet"><strong>connectorHoverClass</strong> - class(es) to set on the display elements of Connections when in hover mode</li>
    -			</ul>
    -			<p>These parameters should be supplied as a String; they will be appended as-is to the class member, so feel free to
    -			include multiple classes.  jsPlumb won't even know.</p>
    -		</div>
    -				
    -		<div class="section">
    -			<h3><a id="animation">Animation</a></h3>
    -			jsPlumb offers an 'animate' function, which wraps the underlying animation engine for whichever library you happen to be using and
    -			inserts a callback for jsPlumb to repaint whatever it needs to at each step.  You could of course do this yourself; it's a convenience
    -			method really.
    -			<p>The method signature is:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.animate : function(el, properties, options) 
    -</pre>
    -</div>			
    -The arguments are as follows:
    -	<ul>
    -		<li><strong>el</strong> - element id, or element object from the library you're using.</li>
    -		<li><strong>properties</strong> - properties for the animation, such as duration etc.</li>
    -		<li><strong>options</strong> - options for the animation, such as callbacks etc.</li>
    -	</ul>
    -	
    -		<h4>jQuery and the 'revert' option</h4>
    -		jQuery offers a 'revert' option that you can use to instruct it to revert a drag under some condition. This is
    -		rendered in the UI as an animation that returns the drag object from its current location to wherever it started
    -		out.  It's a nice feature.  Unfortunately, the animation that runs the revert does not offer any lifecycle
    -		callback methods - no 'step', no 'complete' - so its not possible for jsPlumb to know that the revert 
    -		animation is taking place.
    -	
    -		</div>
    -		
    -			<div class="section">
    -			<h3><a id="connectionInfo">Retrieving Connection Information</a></h3>
    -			There are two ways of retrieving connection information from jsPlumb. <strong>getConnections</strong>is the original method; this method has since been supplanted by <a href="#selectConnections">jsPlumb.select</a>, a much more versatile variant.
    -			<h5>getConnections</h5>
    -			<p>Before you use this method you should understand jsPlumb's notion of 'scope' - documentation
    -			is <a href="#dragAndDropScope">here</a> </p>
    -			
    -			<h4>Retrieving connections for a single scope</h4>
    -			<p>To do this, you call getConnections with either no arguments, in which case jsPlumb uses the default scope, or with a string
    -			specifying one scope</p>
    -			<div class="code">
    -<pre>var connectionList = jsPlumb.getConnections();     // you get a list of Connection objects that are in the default scope.
    -</pre>			
    -			</div>
    -			Compare this with:
    -			<div class="code">
    -<pre>var connectionList = jsPlumb.getConnections("myScope");     // you get a list of Connection objects that are in "myScope".
    -</pre>			
    -			</div>
    -			<h4>More advanced filtering</h4>
    -			getConnections optionally takes a JS object specifying filter parameters, of which there are three:
    -			<ul>
    -				<li>scope - the scope(s) of the connection type(s) you wish to retrieve</li>
    -				<li>source - limits the returned connections to those that have this source id</li>
    -				<li>target - limits the returned connections to those that have this target id</li>
    -			</ul>			
    -			Each of these three parameters may be supplied as a string, which for source and target is an element id and for scope is the name of the scope, or a list of strings.  Als, you can pass "*" in as the value for any of these - a wildcard, meaning any value.  See the examples below.
    -			<p>
    -			The return value of a call to getConnection using a JS object as parameter varies on how many scopes you defined.  If you defined only a single scope then jsPlumb returns you a list of Connections in that scope.  Otherwise the return value is a dictionary whose keys are  scope names, and whose values are lists of Connections. For example, the following call:
    -			</p>
    -			<div class="code">
    -<pre>
    -	jsPlumb.getConnections({scope:["someScope", "someCustomScope"]});
    -</pre>
    -</div>
    -would result in this output:			
    -<div class="code">
    -<pre>
    -{
    -	"someScope" : [ 1..n Connections ],
    -	"someCustomScope": [ 1..m Connections ]
    -}
    -</pre>
    -</div>	
    -
    -There is an optional second parameter that tells getConnections to flatten its output and just return you an array.  The previous example with this parameter would look like this:
    -			<div class="code">
    -<pre>
    -	jsPlumb.getConnections({scope:["someScope", "someCustomScope"]}, true);
    -</pre>
    -</div>
    -would result in this output:			
    -<div class="code">
    -<pre>
    -[ 1..n Connections ]
    -</pre>
    -</div>	
    -		
    -		The following examples show the various ways you can get connection information:
    -	<ul>
    -		<li>Get all connections:
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getAllConnections();  
    -</pre>
    -</div>						
    -		</li>
    -<li>Get all connections for the default scope only (return value is a list):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections();  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given scope (return value is a list):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:"myTestScope"});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given scopes (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:["myTestScope", "yourTestScope"]});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given source (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:"mySourceElement"});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given sources (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:["mySourceElement", "yourSourceElement"]});  
    -</pre>
    -</div>						
    -		</li>		
    -<li>Get all connections for the given target (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({target:"myTargetElement"});  
    -</pre>
    -</div>						
    -		</li>				
    -<li>Get all connections for the given source and targets (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:"mySourceElement", target:["target1", "target2"]});  
    -</pre>
    -</div>						
    -		</li>				
    -		
    -<li>Get all connections for the given scope, with the given source and target (return value is a list of connections):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:'myScope", source:"mySourceElement", target:"myTargetElement"});  
    -</pre>
    -</div>						
    -		</li>				
    -		
    -	</ul>	
    -
    -		</div>
    -
    -		<div class="section">
    -			<h3><a id="selectConnections">Selecting Connections</a></h3>
    -			<strong>jsPlumb.select</strong> provides a fluid interface for working with lists of Connections.  The syntax used to specify which Connections you want is identical to that which you use for getConnections, but the return value is an object that supports most operations that you can perform on a Connection, and which is also chainable, for setter methods. Certain getter methods are also supported, but these are not chainable; they return an array consisting of all the Connections in the selection along with the return value for that Connection.
    -			<p>
    -			This is the full list of setter operations supported by jsPlumb.select:
    -			<ul>
    -				<li class="bullet">addOverlay</li>
    -				<li class="bullet">removeOverlay</li>
    -				<li class="bullet">removeOverlays</li>
    -				<li class="bullet">showOverlay</li>
    -				<li class="bullet">hideOverlay</li>
    -				<li class="bullet">showOverlays</li>
    -				<li class="bullet">hideOverlays</li>
    -				<li class="bullet">removeAllOverlays</li>
    -				<li class="bullet">setLabel</li>
    -				<li class="bullet">setPaintStyle</li>
    -				<li class="bullet">setHoverPaintStyle</li>
    -				<li class="bullet">setDetachable</li>
    -				<li class="bullet">setReattach</li>
    -				<li class="bullet">setConnector</li>
    -				<li class="bullet">setParameter</li>
    -				<li class="bullet">setParameters</li>
    -				<li class="bullet">detach</li>
    -				<li class="bullet">repaint</li>
    -				<li class="bullet">setType</li>
    -				<li class="bullet">addType</li>
    -				<li class="bullet">removeType</li>
    -				<li class="bullet">toggleType</li>		
    -			</ul>
    -
    -			Each of these operations returns a selector that can be chained.
    -			</p>
    -			These is the full list of getter operations supported by jsPlumb.select:
    -			<p>
    -				<ul>
    -				<li class="bullet">getLabel</li>
    -				<li class="bullet">getOverlay</li>
    -				<li class="bullet">isHover</li>
    -				<li class="bullet">isDetachable</li>
    -				<li class="bullet">isReattach</li>
    -				<li class="bullet">getParameter</li>
    -				<li class="bullet">getParameters</li>
    -				<li class="bullet">getType</li>
    -				<li class="bullet">hasType</li>
    -			</ul>
    -
    -			Each of these operations returns an array whose entries are [ value, Connection ] arrays, where 'value' is the return value from the given Connection.  Remember that the return values from a getter are not chainable, but a getter may be called at the end of a chain of setters.
    -			</p>
    -
    -			<p>Some examples:</p>
    -			Select all Connections and set their hover state to be false:
    -
    -<div class="code">
    -<pre>
    -jsPlumb.select().setHover(false);
    -</pre>
    -</div>
    -			Select all Connections from "d1" and remove all Overlays:
    -
    -<div class="code">
    -<pre>
    -jsPlumb.select({source:"d1"}).removeAllOverlays();
    -</pre>
    -</div>
    -
    -Select all connections in scope "foo" and set their paint style to be a thick blue line:
    -<div class="code">
    -<pre>
    -jsPlumb.select({scope:"foo"}).setPaintStyle({ strokeStyle:"blue", lineWidth:5 });
    -</pre>
    -</div>
    -Select all Connections from "d1" and detach them:
    -<div class="code">
    -<pre>
    -jsPlumb.select({source:"d1"}).detach();
    -</pre>
    -</div>
    -<h5>.each iterator</h5>
    -The return value of jsPlumb.select has a <strong>.each</strong> function that allows you to iterate through the list, performing an operation on each one:
    -<div class="code">
    -<pre>
    -jsPlumb.select({scope:"foo"}).each(function(connection) {
    -	
    -		// do something 
    -});
    -</pre>
    -</div>
    -.each is itself chainable:
    -<div class="code">
    -<pre>
    -jsPlumb.select({scope:"foo"}).each(function(connection) {
    -	
    -		// do something 
    -
    -}).setHover(true);
    -</pre>
    -</div>
    -
    -
    -<h5>Other properties/functions</h5>
    -<ul>
    -	<li class="bullet"><strong>length</strong> - this member reports the number of Connections in the selection</li>
    -	<li class="bullet"><strong>get(idx)</strong> - this function allows you to retrieve a Connection from the selection</li>
    -</ul>
    -
    -
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="selectEndpoints">Selecting Endpoints</a></h3>
    -			<strong>jsPlumb.selectEndpoints</strong> provides a fluid interface for working with lists of Endpoints.
    -			The syntax used to specify which Endpoints you want is identical to that which you use for jsPlumb.select,
    -			and the return value is an object that supports most operations that you can perform on an Endpoint (and
    -			which is also chainable, for setter methods). Certain getter methods are also supported, but these are not
    -			chainable; they return an array consisting of all the Endpoints in the selection along with the return
    -			value for that Endpoint.
    -			<p>
    -			<p>
    -				Four parameters are supported by selectEndpoints - each of these except 'scope' can be provided as either
    -				a string, a selector, a DOM element, or an array of a mixture of these types.  'scope' can be provided as either
    -				a string or an array of strings:
    -				<ul>
    -					<li class="bullet">element - element(s) to get both source and target endpoints from</li>
    -					<li class="bullet">source - element(s) to get source endpoints from</li>
    -					<li class="bullet">target - element(s) to get target endpoints from</li>
    -					<li class="bullet">scope - scope(s) for endpoints to retrieve.</li>					
    -				</ul>
    -			</p>
    -			This is the full list of setter operations supported by jsPlumb.selectEndpoints:
    -			<ul>
    -				<li class="bullet">addOverlay</li>
    -				<li class="bullet">removeOverlay</li>
    -				<li class="bullet">removeOverlays</li>
    -				<li class="bullet">showOverlay</li>
    -				<li class="bullet">hideOverlay</li>
    -				<li class="bullet">showOverlays</li>
    -				<li class="bullet">hideOverlays</li>
    -				<li class="bullet">removeAllOverlays</li>
    -				<li class="bullet">setLabel</li>
    -				<li class="bullet">setPaintStyle</li>
    -				<li class="bullet">setHoverPaintStyle</li>				
    -				<li class="bullet">setConnector</li>
    -				<li class="bullet">setParameter</li>
    -				<li class="bullet">setParameters</li>
    -				<li class="bullet">repaint</li>
    -				<li class="bullet">setType</li>
    -				<li class="bullet">addType</li>
    -				<li class="bullet">removeType</li>
    -				<li class="bullet">toggleType</li>
    -			</ul>
    -
    -			Each of these operations returns a selector that can be chained.
    -			</p>
    -			These is the full list of getter operations supported by jsPlumb.select:
    -			<p>
    -				<ul>
    -					<li class="bullet">getLabel</li>
    -					<li class="bullet">getOverlay</li>
    -					<li class="bullet">isHover</li>				
    -					<li class="bullet">getParameter</li>
    -					<li class="bullet">getParameters</li>
    -					<li class="bullet">getType</li>
    -					<li class="bullet">hasType</li>
    -				</ul>
    -
    -			Each of these operations returns an array whose entries are [ value, Endpoint ] arrays, where 'value'
    -			is the return value from the given Endpoint. Remember that the return values from a getter are not chainable,
    -			but a getter may be called at the end of a chain of setters.
    -			</p>
    -			<p>
    -				Other methods (not chainable):
    -				<ul>
    -					<li class="bullet">delete - deletes the Endpoints in the selection</li>
    -					<li class="bullet">detachAll - detaches all Connections from the Endpoints in the selection </li>
    -				</ul>
    -			</p>
    -
    -			<p>Some examples:</p>
    -			Select all Endpoints and set their hover state to be false:
    -
    -<div class="code">
    -<pre>
    -jsPlumb.selectEndpoints().setHover(false);
    -</pre>
    -</div>
    -			Select all source Endpoints on "d1" and remove all Overlays:
    -
    -<div class="code">
    -<pre>
    -jsPlumb.selectEndpoints({source:"d1"}).removeAllOverlays();
    -</pre>
    -</div>
    -
    -Select all Endpoints in scope "foo" and set their fill style to be blue:
    -<div class="code">
    -<pre>
    -jsPlumb.selectEndpoints({ scope:"foo" }).setPaintStyle({ fillStyle:"blue" });
    -</pre>
    -</div>
    -Select all Endpoints from "d1" and detach their Connections:
    -<div class="code">
    -<pre>
    -jsPlumb.selectEndpoints({source:"d1"}).detachAll();
    -</pre>
    -</div>
    -<h5>.each iterator</h5>
    -The return value of jsPlumb.selectEndpoints has a <strong>.each</strong> function that allows you to iterate through the list, performing an operation on each one:
    -<div class="code">
    -<pre>
    -jsPlumb.selectEndpoints({scope:"foo"}).each(function(endpoint) {
    -	
    -		// do something 
    -});
    -</pre>
    -</div>
    -.each is itself chainable:
    -<div class="code">
    -<pre>
    -jsPlumb.selectEndpoints({scope:"foo"}).each(function(endpoint) {
    -	
    -		// do something 
    -
    -}).setHover(true);
    -</pre>
    -</div>
    -
    -
    -<h5>Other properties/functions</h5>
    -<ul>
    -	<li class="bullet"><strong>length</strong> - this member reports the number of Endpoints in the selection</li>
    -	<li class="bullet"><strong>get(idx)</strong> - this function allows you to retrieve an Endpoint from the selection</li>
    -</ul>
    -
    -
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="events">Events</a></h3>
    -			jsPlumb supports binding to several different events on Connections, Endpoints and Overlays, and also on the jsPlumb object itself.  
    -			
    -			<h4><a id="jsPlumbEvents">jsPlumb Events</a></h4>
    -			<p>To bind an event to jsPlumb itself, use <strong>jsPlumb.bind(event, callback)</strong>:</p>
    -			<div class="code">
    -<pre>
    -jsPlumb.bind("jsPlumbConnection", function(info) {
    -   .. update your model in here, maybe.
    -});
    -</pre>
    -</div>
    -<p>These are the events you can bind to on the jsPlumb class:</p>
    -			<ul>
    -				<li class="bullet"><strong>jsPlumbConnection</strong> - notification a Connection was established.
    -					<p>
    -						The first argument to the callback is an object with the following properties:					
    -					</p>
    -					<div class="code">
    -<pre>
    -{
    -  connection 		: 	the new Connection.  you can register listeners on this etc.
    -  sourceId 		:	id of the source element in the Connection
    -  targetId		:	id of the target element in the Connection
    -  source		:	the source element in the Connection
    -  target		:	the target element in the Connection
    -  sourceEndpoint	:	the source Endpoint in the Connection
    -  targetEndpoint	:	the targetEndpoint in the Connection
    -}
    -</pre>
    -					</div>
    -The second argument is the original mouse event that caused the Connection, if any.
    -
    -					<p>
    -All of the source/target properties are actually available inside the Connection object, but - for one of those rubbish historical reasons - are provided separately because of a vagary of the <em>jsPlumbConnectionDetached</em> callback, which is discussed below.
    -</p>
    -				</li>
    -				<li class="bullet"><strong>jsPlumbConnectionDetached</strong> - notification a Connection was detached.  
    -					<p>
    -						As with <em>jsPlumbConnection</em>, the first argument to the callback is an object with the following properties:					
    -					</p>
    -					<div class="code">
    -<pre>
    -{
    -  connection		: 	the Connection that was detached.  
    -  sourceId		:	id of the source element in the Connection <em>before it was detached</em>
    -  targetId		:	id of the target element in the Connection before it was detached
    -  source		:	the source element in the Connection before it was detached
    -  target		:	the target element in the Connection before it was detached
    -  sourceEndpoint	:	the source Endpoint in the Connection before it was detached
    -  targetEndpoint	:	the targetEndpoint in the Connection before it was detached
    -}
    -</pre>
    -
    -					</div>	
    -The second argument is the original mouse event that caused the disconnection, if any. 					
    -								
    -					<p>
    -As mentioned above, the source/target properties are provided separately from the Connection, because this event is fired whenever
    -a Connection is either detached and abandoned, or detached from some Endpoint and attached to another.  In the latter case, the
    -Connection that is passed to this callback is in an indeterminate state (that is, the Endpoints are still in the state they are
    -in when dragging, and do not reflect static reality), and so the source/target properties give you the real story.
    -</p>
    -<p> Like I said above, this is really just for rubbish historical reasons.  I'm attempting to document my way out of it!</p>
    -				
    -				</li>
    -
    -				<li class="bullet"><strong>connectionDrag</strong> - notification an existing Connection is being dragged. The callback is passed the Connection.
    -				</li> 
    -
    -				<li class="bullet"><strong>connectionDragStop</strong> - notification a Connection drag has stopped. This is only fired for existing Connections. The callback is passed the Connection that was just dragged.
    -				</li> 
    -
    -				<li class="bullet"><strong>click</strong> - notification a Connection was clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification a Connection was double-clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>endpointClick</strong> - notification an Endpoint was clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>endpointDblClick</strong> - notification an Endpoint was double-clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>contextmenu</strong> - a right-click on some given component.  jsPlumb will report right clicks on both Connections and Endpoints; the callback is passed the component that received the right-click.				
    -				<li class="bullet"><strong>beforeDrop</strong>
    -					This event is fired when a new or existing connection has been dropped. Your callback is passed a JS object with these fields:
    -					<ul>
    -						<li><strong>sourceId</strong> - the id of the source element in the connection</li>
    -						<li><strong>targetId</strong> - the id of the target element in the connection</li>
    -						<li><strong>scope</strong> - the scope of the connection</li>
    -						<li><strong>connection</strong> - the actual Connection object.  You can access the 'endpoints' array in a Connection to get the Endpoints involved in the Connection, but be aware that when a Connection is being dragged, one of these Endpoints will always be a transient Endpoint that exists only for the life of the drag. To get the Endpoint on which the Connection is being dropped, use the 'dropEndpoint' member.</li>
    -						<li><strong>dropEndpoint</strong> - this is the actual Endpoint on which the Connection is being dropped.  This <strong>may be null</strong>, because it will not be set if the Connection is being dropped on an element on which makeTarget has been called. </li>
    -					</ul>
    -
    -					If you return false (or nothing) from this callback, the new Connection is aborted and removed from the UI.
    -				</li>					
    -				<!--li class="bullet"><strong>beforeDrag</strong>
    -					This event is fired when a new connection is about to be dragged.  Your callback function
    -					is passed the Connection that jsPlumb has just created.  If you return false (or nothing) 
    -					from this callback, the new Connection is aborted.
    -				</li-->
    -				<li class="bullet"><strong>beforeDetach</strong>
    -					This event is fired when a Connection is about to be detached, for whatever reason. Your callback function is passed the Connection that the user has just detached. Returning false from this interceptor aborts the Connection detach.
    -				</li>
    -							
    -			</ul>
    -			<h4><a id="connectionEvents">Connection Events</a></h4>		
    -			<p>To bind to an event on a Connection, you also use the <strong>bind</strong> method:</p>
    -			<div class="code">
    -<pre>
    -var connection = jsPlumb.connect({source:"d1", target:"d2"});
    -connection.bind("click", function(conn) {
    -	console.log("you clicked on ", conn);
    -});
    -</pre>
    -</div>
    -<p>These are the Connection events to which you can bind a listener:</p>
    -			<ul>
    -				<li class="bullet"><strong>click</strong> - notification a Connection was clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification a Connection was double-clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>contextmenu</strong> - a right-click on the Connection. The callback is passed the Connection and the original event.
    -				<li class="bullet"><strong>mouseenter</strong> - notification the mouse entered the Connection's path.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>mouseexit</strong> - notification the mouse exited the Connection's.  The callback is passed the Connection and the original mouse event.</li>
    -			</ul>
    -			<h4><a id="endpointEvents">Endpoint Events</a></h4>			
    -			<p>To bind to an event on a Endpoint, you again use the <strong>bind</strong> method:</p>
    -			<div class="code">
    -<pre>
    -var endpoint = jsPlumb.addEndpoint("d1", { someOptions } );
    -endpoint.bind("click", function(endpoint) {
    -	console.log("you clicked on ", endpoint);
    -});
    -</pre>
    -</div>
    -<p>These are the Endpoint events to which you can bind a listener:</p>
    -			<ul>
    -				<li class="bullet"><strong>click</strong> - notification an Endpoint was clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification an Endpoint was double-clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>contextmenu</strong> - a right-click on the Endpoint. The callback is passed the Endpoint and the original event.
    -				<li class="bullet"><strong>mouseenter</strong> - notification the mouse entered the Endpoint.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>mouseexit</strong> - notification the mouse exited the Endpoint.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>maxConnections</strong> - notification the user tried to drop a Connection on an Endpoint that already has the maximum number of Connections.  The callback is passed as first argument an object literal containing the Endpoint, the Connection the user tried to drop, and the value of maxConnections for the Endpoint, and as second argument the original mouse event.</li>
    -			</ul>
    -			<h4><a id="overlayEvents">Overlay Events</a></h4>
    -			Registering event listeners on an Overlay is a slightly different procedure - you provide them as arguments to the Overlay's 
    -			constructor.  This is because you never actually act on an 'Overlay' object.  Here's how to register a click listener on an Overlay:
    -			<div class="code">
    -<pre>jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  overlays:[
    -    [ "Label", {
    -      events:{
    -        click:function(labelOverlay, originalEvent) { 
    -          console.log("click on label overlay for :" + labelOverlay.component); 
    -        }
    -      }
    -    }],
    -    [ "Diamond", {
    -      events:{
    -        dblclick:function(diamondOverlay, originalEvent) { 
    -          console.log("double click on diamond overlay for : " + diamondOverlay.component); 
    -        }
    -      }
    -    }] 	
    -  ]
    -});
    -</pre>			
    -			</div>		
    -		Note that events registered on Diamond, Arrow or PlainArrow overlays will not fire with the Canvas renderer - they work only with the SVG and VML renderers. 
    -
    -
    -		<h4><a id="jsPlumbEvents">Unbinding Events</a></h4>
    -		<p>On the jsPlumb object and on Connections and Endpoints, you can use the <strong>unbind</strong> method to remove a listener.  This method either takes the name of the event to unbind:
    -		<div class="code">
    -<pre>
    -jsPlumb.unbind("click");
    -</pre>
    -</div>		
    -...or no argument, meaning unbind all events:
    -	<div class="code">
    -<pre>
    -var e = jsPlumb.addEndpoint("someDiv");
    -e.bind("click", function() { ... });
    -e.bind("dblclick", function() { ... });
    -
    -...
    -
    -e.unbind("click");
    -</pre>
    -</div>
    -
    -		</div>
    -
    -				<div class="section">
    -			<a id="examples"><h3>jsPlumb.connect Examples</h3></a>
    -This section provides examples of how to use the programmatic API to establish Connections. 
    -			<p>
    -			The basic syntax of a call is that you execute 'connect', providing a source and a target, and optionally a paintStyle and preferences for where you
    -			want the plumbing to be anchored on each element, as well as the type of connector to use. 
    -			</p>
    -			<ul>
    -				<li>Connect window1 to window2 with the default settings:
    -					<div class="code">
    -						<pre>jsPlumb.connect({source:"window1", target:"window2"});</pre>
    -					</div>
    -This connects 'window1' to 'window2' using the jsPlumb defaults - a red Bezier curve Connector and black Dot Endpoints.
    -				</li>
    -				<li>Connect window1 to window2 with a 15 pixel wide yellow Connector, and a slightly brighter endpoint (remember the default Endpoint is a Dot):
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -					</div>
    -				</li>
    -				
    -				<li>Connect window1 to window2 with a 15 pixel wide yellow Connector, and a slightly brighter endpoint:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -					</div>
    -				</li>				
    -				<li>Connect window3 to 'window4' with a 10 pixel wide, semi opaque blue Connector, anchored to the left middle of window3, and the right middle of window4, with a Rectangle endpoint of width 10 and height 8:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window3',
    -	target:'window4',
    -	paintStyle:{ lineWidth:10, strokeStyle:'rgba(0, 0, 200, 0.5)' },
    -	anchors:["RightMiddle", "LeftMiddle"],
    -	endpoint:[ "Rectangle", { width:10, height:8 } ]
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window2 to window3 with a default Connector from the top center of window2 to the bottom center of window3, and rectangular endpoints:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window2',
    -	target:'window3',
    -	paintStyle:{lineWidth:8, strokeStyle:'rgb(189,11,11)'},
    -	anchors:["BottomCenter", "TopCenter"],
    -	endpoint:"Rectangle"
    -});</pre>
    -					</div>
    -				</li>
    -
    -
    -				<li>Connect window1 to window2 with a 15 px wide yellow Bezier. endpoints are a slightly lighter shade of yellow.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	anchors:["BottomCenter", [0.75,0,0,-1]],
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -</div></li>
    -<li>Connect window3 to window4 with a 10px wide blue-ish half transparent Bezier. put endpoints underneath the element they attach to.
    -the endpoints have a radial gradient. both ways of specifying gradient positioning are shown here.
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect( {
    -	source:'window3',
    -	target:'window4',
    -		 paintStyle:{lineWidth:10, strokeStyle:w34Stroke},
    -		 anchors:["RightMiddle", "LeftMiddle"],
    -		 endpointStyle:{ gradient : {stops:[[0, w34Stroke], [1, w34HlStroke]], offset:17.5, innerRadius:15 }, radius:35},
    -		 //endpointStyle:{ gradient : {stops:[[0, w34Stroke], [1, w34HlStroke]], offset:'78%', innerRadius:'73%'}, radius:35 },
    -		 endpointsOnTop:false
    -	}
    -);</pre>
    -</div></li>
    -<li>Connect window2 to window3 with an 8px red Bezier and default rectangular endpoints.  see also how the first anchor is
    -specified here - this is how you create anchors in locations jsPlumb does not offer shortcuts for.
    -the endpoints in this example have linear gradients applied.
    -<div class="code">
    -<pre>var w23Stroke = 'rgb(189,11,11)';
    -jsPlumb.connect({
    -	source:'window2',
    -	target:'window3',
    -	paintStyle:{lineWidth:8,strokeStyle:w23Stroke},
    -	anchors:[[0.3,1,0,1], "TopCenter"],
    -	endpoint:"Rectangle",
    -	endpointStyles:[{ gradient : {stops:[[0, w23Stroke], [1, '#558822']] }},
    -       				{ gradient : {stops:[[0, w23Stroke], [1, '#882255']] }}]
    -});</pre>
    -</div></li>
    -
    -<li>Connect window5 to window6 from center to center, 5px wide line that is green and half transparent. the endpoints are
    -125px in radius and spill out from underneath their elements.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window5',
    -	target:'window6',
    -	anchors:["Center", "Center"],
    -	paintStyle:{lineWidth:5,strokeStyle:'rgba(0,255,0,0.5)'},
    -	endpointStyle:{radius:125}
    -});</pre>
    -</div></li>
    -
    -<li>Connect window4 to window5 from bottom right to top left, with a 7px straight line purple connector, and an image as the endpoint,
    -placed on top of the element it is connected to.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window4",
    -	target:"window5",
    -	anchors:[ "BottomRight","TopLeft" ],
    -	paintStyle:{ lineWidth:7, strokeStyle:"rgb(131,8,135)" },
    -	endpoint:[ "Image", { src:"http://morrisonpitt.com/jsPlumb/img/endpointTest1.png" } ],
    -	connector:"Straight"
    -});</pre>
    -</div></li>
    -
    -
    -				<li>Connect window5 to window6 between their center points with a semi-opaque connector, and 125px endpoints:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window5",
    -	target:"window6",
    -	anchors:[ "Center", "Center" ],
    -	paintStyle:{ lineWidth:5, strokeStyle:"rgba(0,255,0,0.5)" },
    -	endpointStyle:{ radius:125 }
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window7 to window8 with a 10 pixel wide blue Connector, anchored on the top left of window7 and the bottom right of window8:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window7",
    -	target:"window8",
    -	paintStyle:{ lineWidth:10, strokeStyle:"blue" },
    -	anchors:[ "TopLeft", "BottomRight" ]
    -});
    -</pre>
    -					</div>
    -				</li>
    -				<li>Connect the bottom right corner of window4 to the top left corner of window5, with rectangular endpoints of size 40x40 and a hover color of light blue:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window4",
    -	target:"window5",
    -	anchors:["BottomRight","TopLeft"],
    -	paintStyle:{lineWidth:7,strokeStyle:'rgb(131,8,135)'},
    -	hoverPaintStyle:{ strokeStyle:"rgb(0, 0, 135)" },
    -	endpointStyle:{ width:40, height:40 },
    -	endpoint:"Rectangle",
    -	connector:"Straight"
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window1 to window2 with the default paint settings but provide some drag options (which are passed through to the underlying library's draggable call):
    -					<div class="code">
    -						<pre>jsPlumb.connect({source:'window1', target:'window2', dragOptions:{cursor:'crosshair'}});</pre>
    -					</div>
    -				</li>
    -</div>
    -		
    -		<div class="section">
    -			<a id="dragAndDropExamples"><h3>Draggable Connections Examples</h3></a>
    -			This is a list of examples of how to use jsPlumb to create Connections using drag and drop.
    -			<p>
    -			The basic procedure is:
    -			<ol>
    -				<li>1a. Create Endpoints and register them on elements in your UI</li>
    -				<li>1b. alternatively, create a source endpoint and then make some element a drop target </li>
    -				<li>2. Drag and Drop</li>
    -			</ol>
    -			That's all there is to it.  Of course there are plenty of options you can set when doing this...it will be easier
    -			to show you some examples:
    -			</p>
    -			<ul>
    -				<li>Define an Endpoint with default appearance, that is both a source and target of new Connections:
    -<div class="code">
    -<pre>var endpointOptions = { isSource:true, isTarget:true }; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Register that Endpoint on window3, specifying that it should be located in the top center of the element:
    -<div class="code">
    -<pre>var window3Endpoint = jsPlumb.addEndpoint('window3', { anchor:"TopCenter" }, endpointOptions );  
    -</pre>
    -</div>
    -Notice here the usage of the three-argument addEndpoint  - we can reuse 'endpointOptions' with a different Anchor for another element.  This is a useful
    -practice to get into.<br/><br/>
    -				</li>
    -				
    -				<li>Now register that Endpoint on window4, specifying that it should be located in the bottom center of the element:
    -<div class="code">
    -<pre>var window4Endpoint = jsPlumb.addEndpoint('window4', { anchor:"BottomCenter" }, endpointOptions );  
    -</pre>
    -</div>
    -Now we have two Endpoints, both of which support drag and drop of new Connections.  We can use these to make a programmatic Connection, too, though:<br/><br/>
    -				</li>				
    -				
    -				<li>Connect window3 to window4 with a 25px wide yellow Bezier that has a 'curviness' of 175:
    -<div class="code">
    -<pre>jsPlumb.connect({ 
    -	source:window3Endpoint,
    -	target:window4Endpoint,
    -	connector: [ "Bezier", 175 ],
    -	paintStyle:{ lineWidth:25, strokeStyle:'yellow' }
    -});  
    -</pre>
    -</div>
    -				</li>
    -				
    -				<li>Define an Endpoint that creates Connections that are 20px wide straight lines, that is both a source and target of new Connections,
    -				and that has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Define an Endpoint that will be anchored to "TopCenter".  It creates Connections that are 20px wide straight lines, that is both a source and target of new Connections,
    -				and that has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchor:"TopCenter", 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Define an Endpoint that will create a dynamic anchor which can be positioned at "TopCenter" or "BottomCenter".  It creates Connections that are 20px wide straight lines, it is both a source and target of new Connections,
    -				and it has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchor:[ "TopCenter", "BottomCenter" ], 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Exactly the same as before, but shows how you can use "anchors" instead of "anchor", if that makes you feel happier:
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchors:[ "TopCenter", "BottomCenter" ], 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>								
    -				
    -<li>Define an Endpoint that is a 30px blue dot, creates Connections that are 20px wide straight lines, is both a source and target of new Connections,
    -				has a 'scope' of 'blueline', and has an event handler that pops up an alert (note: the event handler name means this example is jQuery - MooTools
    -				and YUI3 use different event handler names):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{fillStyle:'blue'},
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ drop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -			<li>Same example as before, but this is for MooTools, and the Endpoint can support up to 5 connections (the default is 1):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{ fillStyle:'blue' },
    -	maxConnections:5,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ onDrop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Same example again, but maxConnections being set to -1 means that the Endpoint has no maximum limit of Connections:
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", {radius:30} ],
    -	style:{ fillStyle:'blue' },
    -	maxConnections:-1,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ onDrop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Same example again, but for YUI3.  Note the drop callback is "drop:hit":
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{fillStyle:'blue'},
    -	maxConnections:-1,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ "drop:hit":function(e, ui) { alert('drop!'); } }	 	
    -}; 
    -</pre>
    -</div>				 				
    -				</li>				
    -				
    -	<li>Assign a UUID to the endpoint options created above, and add as Endpoints to "window1" and "window2":
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("window1", { uuid:"abcdefg" }, endpointOptions );
    -jsPlumb.addEndpoint("window2", { uuid:"hijklmn" }, endpointOptions );
    -</pre>
    -</div>	
    -	</li>
    -	
    -	<li>Connect the two Endpoints we just registered on "window1" and "window2":
    -<div class="code">
    -<pre>
    -jsPlumb.connect({uuids:["abcdefg", "hijklmn"]});
    -</pre>
    -</div>	
    -	</li>
    -	
    -	<li>Create a source Endpoint, register it on some element, then make some other element a Connection target:
    -<div class="code">
    -<pre>
    -var sourceEndpoint = { isSource:true, endpoint:[ "Dot", { radius:50 } ] };
    -var targetEndpoint = { endpoint:[ "Rectangle", { width:10, height:10 } ] };
    -jsPlumb.addEndpoint( "window1", sourceEndpoint );
    -jsPlumb.makeTarget( "window2", targetEndpoint );
    -</pre>
    -</div>	
    -Notice that the endpoint definition we use on the target window does not include the "isTarget:true" directive.  jsPlumb ignores that flag when
    -creating a connection using an element as the target; but if you then tried to drag another connection to the Endpoint that was created, you would
    -not be able to.  To permit that, you would set 'isTarget:true' on the targetEndpoint options defined above.
    -	
    -	</li>
    -				
    -			</ul>
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="utilityFunctions">Utility Functions</a></h3>
    -			<ul>
    -				<li>Detach window5 from all connections
    -					<div class="code">
    -						<pre>jsPlumb.detachAll("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Hide all window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.hide("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Hide all window5's connections endpoints
    -					<div class="code">
    -						<pre>jsPlumb.hide("window5", true);</pre>
    -					</div>
    -				</li>
    -				<li>Show all window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.show("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Show all window5's connections and endpoints.  Note that in the case that you call jsPlumb.show with two arguments, jsPlumb
    -				will also not make a connection visible if it determines that the other endpoint in the connection is not visible.
    -					<div class="code">
    -						<pre>jsPlumb.show("window5", hide);</pre>
    -					</div>
    -				</li>				
    -				<li>Toggle the visibility of window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.toggle("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of all of window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.repaint("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of all of window5, window6 and window11's connections
    -					<div class="code">
    -						<pre>jsPlumb.repaint( [ "window5", "window6", "window11" ] );</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of every connection
    -					<div class="code">
    -						<pre>jsPlumb.repaintEverything();</pre>
    -					</div>
    -				</li>
    -				<li>Detach every connection that the given instance of jsPlumb is managing
    -					<div class="code">
    -						<pre>jsPlumb.detachEveryConnection();</pre>
    -					</div>
    -				</li>
    -				<li>Detach all connections from "window1"
    -					<div class="code">
    -						<pre>jsPlumb.detachAllConnections("window1");</pre>
    -					</div>
    -				</li>
    -				<li>Remove the given Endpoint from element "window1", deleting its Connections. 
    -					<div class="code">
    -						<pre>jsPlumb.removeEndpoint("window1", someEndpoint);</pre>
    -					</div>
    -				</li>
    -				<li>Remove all Endpoints for the element 'window1', deleting their Connections.
    -					<div class="code">
    -						<pre>jsPlumb.removeAllEndpoints("window1");</pre>
    -					</div>
    -				</li>	
    -				<li>Removes every Endpoint managed by this instance of jsPlumb, deleting all Connections.
    -				This is the same as jsPlumb.reset(), effectively, but it does not clear out the event listeners list. 
    -					<div class="code">
    -						<pre>jsPlumb.removeEveryEndpoint();</pre>
    -					</div>
    -				</li>
    -				<li>Deletes the given Endpoint and all its Connections. 
    -					<div class="code">
    -						<pre>jsPlumb.deleteEndpoint(endpoint);</pre>
    -					</div>
    -				</li>
    -				<li>Removes every endpoint, detaches every connection, and clears the event listeners list.  Returns jsPlumb instance to its initial state.  
    -					<div class="code">
    -						<pre>jsPlumb.reset();</pre>
    -					</div>
    -				</li>	
    -				<li>Library-agnostic method to get a selector from the given selector specification.  This function is used mostly by the jsPlumb demos, to cut down on repeating code across the different libraries.  But perhaps you are writing something with jsPlumb that you also wisht to be library agnostic - this function can help you. The returned object, in each of jQuery, MooTools and YUI, has a ".each" iterator.
    -					<div class="code">
    -						<pre>var selector = jsPlumb.getSelector(".someSelector .spec");</pre>
    -					</div>					
    -				</li>
    -				<li>Set window1 to be not draggable, no matter what some jsPlumb command may request.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggable("window1", false);</pre>
    -					</div>
    -				</li>
    -				<li>Set window1 and window2 to be not draggable, no matter what some jsPlumb command may request.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggable(["window1","window2"], false);</pre>
    -					</div>
    -				</li>				
    -				<li>Initialises window1 as a draggable element (all libraries). Passes in an on drag callback
    -				<div class="code">
    -					<pre>jsPlumb.draggable("window1");</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 and window2 as draggable elements (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(["window1","window2"]);</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable("window1");</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (jQuery)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (MooTools)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($$(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (YIU3)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(Y.all(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (jQuery)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($("#window1"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (MooTools)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($("window1"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (YUI3)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(Y.one("window1"));</pre>
    -				</div>
    -				</li>
    -			</ul>
    -		</div>					
    -		
    -		<div class="section">
    -			<h3><a id="developingJsPlumb">Advanced Topics</a></h3>
    -			<h4><a id="which">Which files are which?</a></h4>
    -			In development, jsPlumb is broken up into nine scripts:
    -				<ul>
    -					<li>- jsPlumb-util-x.x.x.js
    -					<p>There are the jsPlumb utility functions (and some base classes).</p>					
    -					</li>
    -					<li>- jsPlumb-x.x.x.js
    -					<p>This is the main jsPlumb engine.</p>					
    -					</li>
    -					<li>- jsPlumb-defaults-x.x.x.js
    -					<p>This contains the default Anchor, Endpoint, Connector and Overlay implementations.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-canvas-x.x.x.js
    -					<p>This contains the HTML5 canvas render code.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-svg-x.x.x.js
    -					<p>This contains the SVG render code.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-vml-x.x.x.js
    -					<p>This contains the VML render code.</p>
    -					</li>
    -					<li>- jsPlumb-connectors-statemachine-x.x.x.js
    -					<p>This State Machine connector.</p>
    -					</li>
    -					<li>- &lt;LIBRARY_PREFIX&gt;.jsPlumb-x.x.x.js
    -					<p>This contains library-specific helper methods.  jsPlumb ships with three of these - one each for jQuery, MooTools and YUI3. See below for information on how to create a new library implementation.</p>
    -					</li>
    -					<li>- jsBezier-0.4-min.js
    -					<p>These are the Bezier curve functions; they are maintained in a separate project called <a href="http://code.google.com/p/jsbezier/">jsBezier</a></p>
    -					</li>
    -				</ul>
    -			These seven files are packaged together to form the scripts that people use, for example:
    -			<ul>
    -				<li>jquery.jsPlumb-1.3.15-all.js
    -				<p>Contains jsPlumb-1.3.15.js, jsPlumb-defaults-1.3.15.js, jsPlumb-renderers-canvas-1.3.15.js, jsPlumb-renderers-svg-1.3.15.js, jsPlumb-renderers-vml-1.3.15.js, jsPlumb-connectors-statemachine-1.3.15.js, jquery.jsPlumb-1.3.15.js and jsBezier-0.4-min.js</p>
    -				</li>
    -				<li>jquery.jsPlumb-1.3.15-all-min.js
    -				<p>A minified version of the script above (minified using the YUI Compressor)</p>
    -				</li>
    -			</ul>
    -			<h4><a id="pluggableLibrarySupport">Pluggable Library Support</a></h4>
    -		Out of the box, jsPlumb can be run on top of jQuery, MooTools or YUI3.  This is achieved by
    -		delegating several core methods - tasks such as finding an element by id, finding an element's
    -		position or dimensions, initialising a draggable, etc - to the library in question.
    -		<p>To develop one of these, your test page should include the first two scripts discussed above, and
    -		then your own script containing your library specific functionality.  The existing implementations
    -		may be documented well enough for you to create your own, but contact me if you need assistance.  If you do this, it would be
    -		great to share it with everyone...</p>   
    -		<p>There is a group of people working on an ExtJS adapter currently.  There is no schedule for when this will be completed though.</p>
    -
    -			<!-- h4><a id="customConnectors">Custom Connectors</a></h4>			
    -					
    -			You can provide your own connectors if you need to. A Connector consists of two functions, which work as a pair.  First a call is made to the <em>compute</em> function:
    -
    -			<div class="code">
    -<pre>
    -this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) {
    -	...
    -	return dimensions;
    -}
    -</pre>
    -			</div>
    -			which is expected to return a list that the <em>paint</em> function can make sense of.  The first four entries in the
    -			list <strong>must be</strong> the [x,y,width,height] values for the canvas that the connector will be drawn on; jsPlumb will
    -			use this information to size the canvas prior to calling the Connector's <em>paint</em> function.  Therefore it
    -			is the Connector's responsibility to ensure that the returned dimensions describe a large enough space for
    -			the line that will be drawn on it.</p><p>The next four elements <strong>must be</strong> the coordinates of the two endpoints of the line
    -			you are going to draw.</p><p>The remainder of the items in the returned list are arbitrary, and will
    -			vary between Connector implementations; this list is passed in to a Connector's <em>paint</em> function, so each
    -			implementation will put into the list whatever it needs to paint itself.  For instance, the straight line
    -			connector only needs the [x,y] location of each end of the line it will paint, and that is one of the required entries, so
    -			it does not have to do anything extra,  whereas the Bezier connector adds the location of the two control points.  Other types of
    -			Connectors will do whatever is appropriate for their particular situation.
    -			</p>
    -			This is the method signature for the <em>paint</em> function:
    -			<div class="code">
    -				<pre>this.paint = function(dimensions, ctx) { .. }</pre>
    -			</div>
    -
    -			here, the 'dimensions' argument to the 'paint' function is the return value of the 'compute' function.  The 'ctx' argument is the Canvas context; you
    -			will do all your drawing on this.
    -			</p>
    -			To change the connector from the default, specify it in your connect call:
    -			<div class="code">
    -				<pre>jsPlumb.connect({source:'someWindow', target:'otherWindow', connector:new jsPlumb.Connectors.Straight()});</pre>
    -			</div>
    -			note that you can use shorthand for connectors if you don't need to specify parameters to it:
    -			<div class="code">
    -				<pre>jsPlumb.connect({source:'someWindow', target:'otherWindow', connector:"Straight"});</pre>
    -			</div>
    -			This works in the same way as specifying Anchors as string does.
    -<p>
    -The section below this discusses Overlays, which allow you to decorate Connectors with
    -			things such as labels or arrows or whatever else you like. Overlays can only work with Connectors that implement a few helper functions.
    -			<h4>The concept of <strong>location</strong></h4>
    -			Before discussing the helper functions you first must be across the concept of 'location' as used by this mechanism.  The <em>location</em>
    -			on a connector can be a decimal value between 0 and 1 inclusive, which marks a proportionate amount of travel <em>along the path
    -			inscribed by the Connector</em>.  For a straight line connector this is a simple piece of maths, but for Bezier connectors it's a little
    -			bit more involved.
    -			<h4>Required Helper Methods</h4>
    -			<ul>
    -				<li><strong>pointOnPath(location)</strong> - returns an [x,y] point corresponding to the given location</li>
    -				<li><strong>pointAlongPathFrom(location, distance)</strong> - returns an [x,y] point corresponding to travelling 'distance' pixels along the connector from 'location'.</li>
    -				<li><strong>gradientAtPoint(location)</strong> - returns the connector's gradient at the given location.  For linear connectors such as Straight this is constant, but for Bezier connectors the gradient changes continually.</li>
    -				<li><strong>perpendicularToPathAt(location, distance, length)</strong> - returns a line that is perpendicular to (and centered on) the connector at 'distance' from the given location, with length 'length'.</li>
    -			</ul>
    -</p>
    -<h4><a id="customEndpoints">Custom Endpoints</a></h4>
    -<p>To create your own Endpoint implementation, you need to implement a single method:
    -				<div class="code"><pre>paint : function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { ... }</pre></div>
    -				The arguments to this method are as follows:
    -				<ul>
    -					<li>anchorPoint - [x,y] location of the anchor point on screen</li>
    -					<li>orientation - [x,y] hints for the general direction the anchor points to</li>
    -					<li>canvas - the canvas to draw into</li>
    -					<li>endpointStyle - Javascript object containing style directives as discussed above.  The contents of this are arbitrary, so if you write a new Endpoint that needs some extra settings, you can add them no hassle.</li>
    -					<li>connectorPaintStyle - the style being used to paint the associated Connector.</li>
    -				</ul>
    -			</p>
    -			<p>
    -				It is your responsibility to size and locate the canvas to suit your needs. jsPlumb provides the following
    -				helper method to assist you:
    -				<div class="code"><pre>jsPlumb.sizeCanvas(canvas, x, y, width, height);</pre></div>
    -				Allows you to locate the canvas on screen and to size it.
    -			</p>
    -<h4><a id="customOverlays">Custom Overlays</a></h4>
    -			Overlays can only work with Connectors that implement the methods <strong>pointOnPath</strong>, <strong>pointAlongPathFrom</strong>,
    -			<strong>gradientAtPoint</strong>, and <strong>perpendicularToPathAt</strong>.  These are discussed in more detail in the Custom Connectors section above.  Both of the Connectors
    -			that come with jsPlumb - Straight and Bezier -implement these methods; if you write a custom connector, or have written a custom connector, you will need to supply
    -			them.   
    -			<h4>Overlay Interface</h4>
    -			An Overlay is required to implement two methods in order to be usable by jsPlumb:
    -			<ul>
    -				<li><strong>computeMaxSize(connector, context)</strong> - returns an integer value indicating the larger of this overlay's width and height.  It is used by jsPlumb to ensure that the canvas is large enough to accomodate the overlay.  The examples below should help to clarify this.</li>
    -				<li><strong>draw(connector, ctx)</strong> - draws the overlay.  What happens in this method is up to the given implementation.</li>
    -			</ul>
    -			<h4>computeMaxSize methods</h4>
    -			This is the Arrow overlay's computeMaxSize method:
    -			<div class="code">
    -<pre>
    -this.computeMaxSize = function() { return width; }
    -</pre>			
    -			</div>
    -Here, <em>width</em> is a private member of Arrow that indicates the width of the arrow's tail.  So the Arrow overlay reports that figure as the width it needs.					
    -Contrast this with the Label Overlay's computeMaxSize method:
    -		<div class="code">
    -<pre>
    -this.computeMaxSize = function(connector, ctx) {
    -  	if (labelText) {
    -  		ctx.save();
    -        if (self.labelStyle.font) ctx.font = self.labelStyle.font;
    -         var t = ctx.measureText(labelText).width;			            
    -		// a fake text height measurement: use the width of upper case M
    -		var h = ctx.measureText("M").width;					
    -		labelPadding = self.labelStyle.padding || 0.25;
    -		labelWidth = t + (2 * t * labelPadding);
    -		labelHeight = h + (2 * h * labelPadding);
    -		ctx.restore();
    -		return Math.max(labelWidth, labelHeight);
    -  	}
    - 	return 0;
    - };
    -</pre>			
    -			</div>
    -The Label overlay has to use the context to determine how big it will be on screen.
    -
    -<h4>draw methods</h4>
    -To give you a taste for how you can interact with a connector, consider the first few lines of the Arrow overlay's draw method:
    -<div class="code">
    -<pre>
    -this.draw = function(connector, ctx) {
    -	// this is the arrow head position
    -	var hxy = connector.pointAlongPathFrom(self.loc, length / 2);		
    -	// this is the center of the tail
    -	var txy = connector.pointAlongPathFrom(self.loc, -length / 2), tx = txy[0], ty = txy[1];
    -	// this is the tail vector
    -	var tail = connector.perpendicularToPathAt(self.loc, -length / 2, width);
    -	
    -	...
    -</pre>
    -</div>
    -	<p>The first two calls to the connector use the <strong>pointAlongPathFrom</strong> method, which passes <em>self.loc</em> as the location of the
    -	point on the path, and <em>length / 2</em> as the number of pixels along the path to traverse.</p>
    -	<p><em>self.loc</em> is Arrow's internal member describing the location of the overlay, and it is a decimal (between 0 and 1 inclusive) that points to
    -	a location at some distance along the path inscribed by the connector.  So these first two calls get us [x,y] locations of points on 
    -	the connector that mark the head and tail points for the arrow.  
    -	</p>		
    -	<p>
    -	The <strong>connector.perpendicularToPathAt(self.loc, -length / 2, width)</strong> call returns a line description of a line that is perpendicular to, and centered on, the connector
    -	at "-length / 2" pixels from the given point, with the given width (width is an internal member of Arrow).  At this point, the Arrow object has the three main points it
    -	needs in order to draw itself.
    -	</p>
    -		</div-->
    -</div>
    -
    -</body>
    -
    -</html>
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.15/index.html b/demo/doc/archive/1.3.15/index.html
    deleted file mode 100644
    index 69b5563f1..000000000
    --- a/demo/doc/archive/1.3.15/index.html
    +++ /dev/null
    @@ -1,93 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.15 documentation</title>
    -		<link rel="stylesheet" target="contentFrame" href="/mp.css"></link>
    -		<link rel="stylesheet" target="contentFrame" href="jsPlumbDoc.css"></link>
    -	</head>
    -	<body>
    -		<div class="index section">
    -			<h3>jsPlumb 1.3.15</h3>
    -			<ul>
    -				<li><a target="contentFrame" href="content.html#summary">Summary</a></li>
    -				<li><a target="contentFrame" href="content.html#changes">Changes since version 1.3.14</a></li>
    -				
    -				<li><h4>Setup</h4></li>
    -				<li><a target="contentFrame" href="content.html#imports">Required Imports</a></li>
    -				<li><a target="contentFrame" href="content.html#renderMode">Render Mode</a></li>
    -				<li><a target="contentFrame" href="content.html#initializing">Initializing jsPlumb</a></li>				
    -				<li><a target="contentFrame" href="content.html#multipleInstances">Multiple jsPlumb Instances</a></li>
    -				<li><a target="contentFrame" href="content.html#zIndex">Z-index Considerations</a></li>
    -				<li><a target="contentFrame" href="content.html#containerDefault">Where does jsPlumb add elements?</a></li>
    -				<li><a target="contentFrame" href="content.html#elementDragging">Element dragging</a></li>
    -				
    -				<li><h4>Introduction</h4></li>
    -				<li><a target="contentFrame" href="content.html#jsPlumbBasics">Basic Concepts</a></li>
    -				<li><a target="contentFrame" href="content.html#anchors">Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#staticAnchors">Static Anchors</a></li>				
    -				<li><a target="contentFrame" href="content.html#dynamicAnchors">Dynamic Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#perimeterAnchors">Perimeter Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#continuousAnchors">Continuous Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#definitions">Connector, Endpoint &amp; Overlay Definitions</a></li>
    -				<li><a target="contentFrame" href="content.html#connectors">Connectors</a></li>
    -				<li><a target="contentFrame" href="content.html#endpoints">Endpoints</a></li>
    -				<li><a target="contentFrame" href="content.html#overlays">Overlays</a></li>
    -				<li><a target="contentFrame" href="content.html#defaults">Defaults</a></li>								
    -				
    -				<li><h4>Connections</h4></li>
    -				<li><a target="contentFrame" href="content.html#simpleConnections">Programmatic Connections</a></li>
    -				<li><a target="contentFrame" href="content.html#draggableConnections">Connections using Drag and Drop</a></li>
    -				<li><a target="contentFrame" href="content.html#sourcesAndTargets">Elements as Sources &amp; Targets</a></li>
    -				<li class="sub"><a target="contentFrame" href="content.html#makeTarget">makeTarget</a></li>
    -				<li class="sub"><a target="contentFrame" href="content.html#makeSource">makeSource</a></li>
    -				<li><a target="contentFrame" href="content.html#dragOptions">Drag Options</a></li>				
    -				<li><a target="contentFrame" href="content.html#dropOptions">Drop Options</a></li>
    -				<li><a target="contentFrame" href="content.html#dragAndDropScope">Drag and Drop Scope</a></li>
    -				<li><a target="contentFrame" href="content.html#disconnectReconnect">Disconnecting and Reconnecting</a></li>
    -				
    -				<li><h4>Parameters</h4></li>		
    -				<li><a target="contentFrame" href="content.html#connectParameters">jsPlumb.connect</a></li>
    -				<li><a target="contentFrame" href="content.html#addEndpointParameters">jsPlumb.addEndpoint</a></li>
    -				
    -				<li><h4>Connection and Endpoint Types</h4></li>		
    -				<li><a target="contentFrame" href="content.html#connectionTypes">Connection Type</a></li>
    -				<li><a target="contentFrame" href="content.html#endpointTypes">Endpoint Type</a></li>
    -
    -				<li><h4>Interceptors</h4></li>				
    -				<li><a target="contentFrame" href="content.html#beforeDrop">Before Drop</a></li>
    -				<li><a target="contentFrame" href="content.html#beforeDetach">Before Detach</a></li>
    -				
    -				<li><h4>Appearance</h4></li>
    -				<li><a target="contentFrame" href="content.html#paintstyles">Paint Styles</a></li>
    -				<li><a target="contentFrame" href="content.html#hoverpaintstyles">Hover Paint Styles</a></li>
    -				<li><a target="contentFrame" href="content.html#gradients">Gradients</a></li>				
    -				
    -				<li><h4>Miscellaneous</h4></li>
    -				<li><a target="contentFrame" href="content.html#cssclasses">CSS</a></li>
    -				<li><a target="contentFrame" href="content.html#animation">Animation</a></li>
    -				<li><a target="contentFrame" href="content.html#connectionInfo">Retrieving Connection Information</a></li>
    -				<li><a target="contentFrame" href="content.html#selectConnections">Selecting Connections</a></li>
    -				<li><a target="contentFrame" href="content.html#selectEndpoints">Selecting Endpoints</a></li>
    -				
    -				<li><h4>Events</h4></li>
    -				<li><a target="contentFrame" href="content.html#events">jsPlumb Events</a></li>
    -				<li><a target="contentFrame" href="content.html#connectionEvents">Connection Events</a></li>
    -				<li><a target="contentFrame" href="content.html#endpointEvents">Endpoint Events</a></li>
    -				<li><a target="contentFrame" href="content.html#overlayEvents">Overlay Events</a></li>
    -				
    -				<li><h4>Examples</h4></li>				
    -				<li><a target="contentFrame" href="content.html#examples">Programmatic API Examples</a></li>
    -				<li><a target="contentFrame" href="content.html#dragAndDropExamples">Draggable Connections Examples</a></li>
    -				<li><a target="contentFrame" href="content.html#utilityFunctions">Utility Functions</a></li>				
    -				
    -				<li><h4>Develop</h4></li>
    -				<li><a target="contentFrame" href="content.html#which">Which files are which?</a></li>
    -				<li><a target="contentFrame" href="content.html#pluggableLibrarySupport">Pluggable Library Support</a></li>
    -				<!-- li><a target="contentFrame" href="content.html#customConnectors">Custom Connectors</a></li>
    -				<li><a target="contentFrame" href="content.html#customEndpoints">Custom Endpoints</a></li>
    -				<li><a target="contentFrame" href="content.html#customOverlays">Custom Overlays</a></li-->
    -			</ul>
    -			<p><strong>This document refers to release 1.3.15 of jsPlumb.</strong></p>
    -			<strong>24 Sep 2012</strong>
    -		</div>		
    -	</body>
    -</html>
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.15/jsPlumbDoc.css b/demo/doc/archive/1.3.15/jsPlumbDoc.css
    deleted file mode 100644
    index f2949bd91..000000000
    --- a/demo/doc/archive/1.3.15/jsPlumbDoc.css
    +++ /dev/null
    @@ -1,127 +0,0 @@
    -body { 
    -	background-color:white; font-size:90%;
    -	background-image:url(../img/dynamicAnchorBg.jpg); 
    -}
    -/* docs */
    -.code { background-color:#e4eeee;
    -border:1px solid black; margin:1em; padding:0.8em; 
    -overflow:auto;
    -font-family:courier; 
    -font-size:8pt;
    -border-radius:0.3em;
    -}
    -.section { border-bottom: 1px dotted black; padding:1em;font-family:helvetica;background-color:white;color:#333;}
    -.menu { 
    -	position:fixed; 
    -	right:10em; top:1em;
    -	background-color:white;
    -	padding:0.3em;
    -	opacity:0.8;
    -	z-index:5000;
    -	filter:alpha(opacity=80);
    -		box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -a {text-decoration:none;color:#05b;font-weight:bold;font-family:helvetica;}
    -a:hover {text-decoration:underline;}
    -.window, .label { 
    -	cursor:pointer;
    -	box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -#explanation { 
    -	background-color:white;
    -	color:black;
    -	border:0.25em solid #ddd; 
    -	position:absolute;
    -	right:3.5em;
    -	top:5em;
    -	width:22em;
    -	font-size:100%;
    -	-moz-border-radius:1em;
    -	border-radius:1em;
    -	padding:1em;
    -	z-index:1000;
    -	opacity:0.9;
    -	filter:alpha(opacity=90); 
    -	font-family:helvetica;
    -		box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -.selected {
    -	color:rgb(189,11,11);
    -}
    -
    -
    -/*  these styles are what jsPlumb attaches to the various elements it creates.
    -._jsPlumb_connector {
    -border:1px solid blue;
    -}
    -._jsPlumb_endpoint {
    -border:1px solid green;
    -}
    -._jsPlumb_overlay {
    -border:1px solid yellow;
    -}
    -*/
    -
    -
    -.section li {
    -list-style-type:none;
    -}
    -
    -li.bullet {
    -	list-style-type:circle;
    -	}
    -
    -
    -.section {
    -padding-left:2em;
    -font-size:10pt;
    -}
    -.index.section {
    -padding-left:0;
    -}
    -
    -.index {
    -width:20em;
    -font-size:9pt;
    -float:left;
    -background-color:#e4eeee;
    -overflow:auto;
    -padding-left:0.5em;
    -}
    -
    -.index li {
    -list-style-type:none;
    -}
    -
    -.index ul, .index h3 {
    -padding-left:1.5em;
    -
    -}
    -
    -#demo {
    -width:80%;
    -height:50em;
    -margin-left:3em;
    -margin-top:3em;
    -position:relative;
    -}
    -
    -.sub {
    -margin-left:1em;
    -}
    -
    -.table td {
    -	text-align:center;
    -}
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.15/usage.html b/demo/doc/archive/1.3.15/usage.html
    deleted file mode 100644
    index 38ff665ef..000000000
    --- a/demo/doc/archive/1.3.15/usage.html
    +++ /dev/null
    @@ -1,10 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.15 - documentation</title>		
    -	</head>
    -	
    -	<frameset cols="250,*">
    -  		<frame src="index.html" />
    -  		<frame src="content.html" name="contentFrame" />
    -	</frameset>	
    -</html>
    diff --git a/demo/doc/archive/1.3.16/content.html b/demo/doc/archive/1.3.16/content.html
    deleted file mode 100644
    index 3b5e368d8..000000000
    --- a/demo/doc/archive/1.3.16/content.html
    +++ /dev/null
    @@ -1,3818 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.16 - documentation</title>
    -		<link rel="stylesheet" href="/mp.css">
    -		<link rel="stylesheet" href="jsPlumbDoc.css">
    -	</head>
    -	<body>
    -
    -<div class="menu">
    -	<a href="http://jsplumb.org" class="mplink" target="_top">view demo</a>
    -	&nbsp;|&nbsp;<a href="mailto:simon.porritt@gmail.com" class="mplink">contact me</a>
    -	&nbsp;|&nbsp;<a href="http://jsplumb.tumblr.com" class="mplink">jsPlumb on Tumblr</a>	
    -</div>
    -
    -<div class="content">
    -		<div class="section">
    -			<h3><a id="summary">Summary</a></h3>
    -			jsPlumb allows you to connect elements on the screen using SVG, Canvas or VML, depending on the capabilities of the browser.
    -			<p>
    -				It can be used with jQuery, MooTools or YUI3 (or another library of your choice if you feel like implementing an adapter for it).
    -			</p>  
    -
    -			<h5>Browser Compatibility</h5>
    -			<p>jsPlumb 1.3.16 runs on everything from IE6 up.  There are some caveats, though, because of various browser/library bugs:
    -				<ul>
    -					<li class="bullet">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.
    -					<li class="bullet">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)</li>
    -					<li class="bullet">MooTools has a bug when using SVG in Firefox 11</li>
    -				<ul>
    -				
    -			</p>
    -		</div>
    -		
    -		<div class="section">		
    -			<h3><a id="changes">Changes between 1.3.16 and 1.3.15</a></h3>	
    -			<h5>Backwards Compatibility</h5>
    -			<ul>
    -				<li>No Issues</li> 
    -			</ul>
    -			
    -			<h5>New Features</h5>
    -			<ul>
    -			  <li class="bullet">Flowchart connectors now support configurable 'midpoint'</li>
    -			  <li class="bullet">recalculateOffsets method added. This lets you tell jsPlumb that the internal layout of some element that
    -		has nested endpoints has changed, and that it should recalculate the offset of each endpoint with respect to
    -		the element's origin.</li>
    -			</ul>
    -			
    -			<h5>Issues Resolved</h5>
    -			<ul>
    -				<li><strong>284</strong> - JS errors when dragging connections</li>
    -				<li><strong>285</strong> - occasional error thrown by getOffset (dupe of 284)</li>
    -				<li><strong>286</strong> - Bounding box of connections does not extend to include arrow</li> 
    -			</ul>
    -
    -			<h5>Miscellaneous</h5>
    -			<ul>
    -				<li class="bullet">fixed paint issue with arrows in reverse direction on Straight connector.</li> 
    -			</ul>
    -			
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="init">Setup</a></h3>
    -
    -            <h4><a id="imports">Required Imports</a></h4>
    -			<h4>jQuery</h4>
    -			    <ul>
    -				    <li class="bullet">jQuery 1.3.x or higher.</li>
    -				    <li class="bullet">jQuery UI 1.7.x or 1.8.x (if you wish to support drag and drop). Note that if you use
    -					jQuery 1.8.x you must use jQuery UI 1.8.22 or higher.</li>
    -			    </ul>
    -			    <div class="code">
    -<pre>&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.23/jquery-ui.min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/jquery.jsPlumb-1.3.16-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			<h4>MooTools</h4>
    -				<ul>
    -					<li class="bullet">MooTools Core 1.2.4 or higher. jsPlumb has been tested with 1.2.4 and 1.3.3.</li>
    -					<li class="bullet">Drag.Move from MooTools More 1.2.4.4 or higher (if you wish to support drag and drop).</li>
    -				</ul>
    -			<div class="code">
    -			<pre>&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/mootools/1.3.3/mootools-yui-compressed.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO_MOO_TOOLS_MORE_1_3_2_1"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/mootools.jsPlumb-1.3.16-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			<h4>YUI3</h4>
    -			<ul>
    -				<li class="bullet">YUI 3.3.x.  jsPlumb has been tested on 3.3.0 only; it is possible other 3.x.x versions will work.</li>
    -			</ul>
    -			<div class="code">
    -			<pre>&lt;script type="text/javascript" src="http://yui.yahooapis.com/3.3.0/build/simpleyui/simpleyui-min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/yui.jsPlumb-1.3.16-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			
    -			<h4><a id="renderMode">Render Mode</a></h4>
    -			jsPlumb can use SVG, HTML5 Canvas elements or VML to render the various objects it adds to the display.  Most modern browsers support Canvas and SVG; IE&lt;9 browsers support neither.
    -			<p>
    -				By default, jsPlumb attempts to use <strong>jsPlumb.SVG</strong> as the render mode (prior to 1.3.4, the default renderer was Canvas), falling back to <strong>jsPlumb.VML</strong> if the 
    -				browser is IE&lt;9.  You can change the render mode by making this call:  
    -			</p>
    -			<div class="code">
    -<pre>jsPlumb.setRenderMode(jsPlumb.CANVAS);</pre>			
    -			</div>
    -			if, for some reason, you set the render mode to be <strong>jsPlumb.VML</strong> but you're in any browser other than IE, jsPlumb will use SVG.
    -			<p>
    -			The <strong>jsPlumb.setRenderMode</strong> method returns you the render mode that jsPlumb actually ended up setting.  Valid values for setRenderMode are:
    -			</p>
    -			<ul>
    -				<li class="bullet">jsPlumb.CANVAS</li>
    -				<li class="bullet">jsPlumb.SVG</li>
    -				<li class="bullet">jsPlumb.VML</li>
    -			</ul>			
    -			<h5>Performance Considerations</h5>
    -			<p>
    -			SVG uses less memory than Canvas and has support for mouse events built in, so I recommend using
    -			SVG unless you have a really good reason for wanting Canvas. Of course, you might consider that horrendous Safari bug a really good reason for wanting Canvas.
    -			</p>
    -			
    -			<h4><a id="initializing">Initializing jsPlumb</a></h4>
    -			You should not start making calls to jsPlumb until the DOM has been initialized - perhaps no surprises there.  With YUI, though, the asynchronous
    -			nature of the script loading process means that you are not guaranteed that jsPlumb is ready as soon as the DOM is.  To handle this,
    -			you should bind to the "ready" event on jsPlumb (or the instance of jsPlumb you are working with):
    -			<div class="code">
    -			<pre>jsPlumb.bind("ready", function() {
    -...	
    -		
    -// your jsPlumb related init code goes here
    -
    -...
    -
    -});						
    -			</pre>
    -			</div>
    -			There's a helper method that can save you approximately 9 (count them!) characters:
    -			<div class="code">
    -			<pre>jsPlumb.ready(function() {
    -...	
    -		
    -// your jsPlumb related init code goes here
    -
    -...
    -
    -});						
    -			</pre>
    -			</div>
    -			<p>
    -			For jQuery and MooTools you do not actually need to do this; by the time the DOM ready event is fired in each of those libraries you can be sure 
    -			all the JS we need has been loaded.  But in terms of explicitly calling out that you are waiting for jsPlumb, it seems like a good practice to bind to the "ready" event.
    -			</p>
    -			<p>
    -			If you bind to the "ready" event after jsPlumb has already been initialized, your callback will be executed immediately.
    -			</p>			
    -
    -				
    -<h4><a id="multipleInstances">Multiple jsPlumb instances</a></h4>
    -			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 <strong>getInstance</strong> method, for example:
    -<div class="code">
    -<pre>
    -var firstInstance = jsPlumb.getInstance();
    -</pre>
    -</div>
    -The variable 'firstInstance' can now be treated exactly as you would treat the 'jsPlumb' variable - you can set
    -defaults, call the connect method, whatever:
    -<div class="code">
    -<pre>
    -firstInstance.importDefaults({
    -  Connector : [ "Bezier", { curviness: 150 } ],
    -  Anchors : [ "TopCenter", "BottomCenter" ]
    -});
    -
    -firstInstance.connect({source:"element1", target:"element2", scope:"someScope" });
    -</pre>
    -</div>
    -
    -<strong>getInstance</strong> optionally takes an object that provides the defaults:
    -<div class="code">
    -<pre>
    -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" });
    -</pre>
    -</div>
    -
    -		<h4><a id="zIndex">Z-index Considerations</a></h4>
    -		<p>
    -			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. 
    -		</p>  
    -		<p>
    -			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).  			
    -		</p>
    -		<p>
    -			To help you organise z-indices correctly, jsPlumb adds a CSS class to each type of
    -			element it adds. They are as follows:			
    -		</p>
    -		<table style="color:black;width:70%;font-size:90%;">
    -			<tr><td><strong>type of element</strong></td><td><strong>class added</strong></td></tr>
    -			<tr><td>Endpoint</td><td>_jsPlumb_endpoint</td></tr>
    -			<tr><td>Connector</td><td>_jsPlumb_connector</td></tr>
    -			<tr><td>Overlay</td><td>_jsPlumb_overlay</td></tr>
    -		</table>
    -		
    -		<h5>Automatic z-index for Connections</h5>
    -		A common use case is the need to bring hovered Connections up on top of the other connections in your UI. From
    -		1.3.16 onwards, jsPlumb offers a way to do this:
    -		<div class="code">
    -<pre>
    -  jsPlumb.importDefaults({
    -    ...
    -    ConnectorZIndex:5
    -	...
    -  });
    -</pre>			
    -		</div>
    -		This tells jsPlumb to set the z-index of all Connections to be 5, and the z-index of any hovered Connections will
    -		be, in this case, 6.  You could argue that jsPlumb could figure out the z-index automatically, but z-index selection
    -		is typically a conscious decision in a webapp, and this approach formalises things a little better than trying
    -		to infer what the value is supposed to be.
    -		<p>To achieve this, there is actually a <strong>setZIndex</strong> method on Connections now, which is perfectly fine
    -		to call, but remember that if you change the z-index of some Connection, hover over it, and then mouse exit, resetting
    -		the hover state will mean that jsPlumb resets the z-index to the default value discussed above.</p>
    -
    -		<h4><a id="containerDefault">Where does jsPlumb add elements?</a></h4>
    -		<p>
    -			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.
    -		</p>
    -		<p>
    -			Prior to 1.3.0, jsPlumb added everything to the end of the <em>body</em> element. This
    -			has the advantage of being the most flexible arrangement in terms of supporting what
    -			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!   
    -		</p>
    -		<p>
    -			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 (<strong>not</strong> 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.
    -		</p>
    -		
    -		<p>
    -			In general, the default behaviour means that you cannot do things like this:
    -		</p>
    -		<div class="code">
    -<pre>
    -&lt;body&gt;
    -	&lt;div id="container0"&gt;
    -		&lt;div id="node0"&gt;&lt;/div&gt;
    -	&lt;/div&gt;
    -	&lt;div id="container1"&gt;
    -		&lt;div id="node1"&gt;&lt;/div&gt;
    -	&lt;/div&gt;
    -&lt;/body&gt;
    -
    -&lt;script type="text/javascript"&gt;
    -
    -	var e0 = jsPlumb.addEndpoint("node0"),
    -	e1 = jsPlumb.addEndpoint("node1");
    -
    -	jsPlumb.connect({ source:e0, target:e1 });
    -&lt;/script&gt;
    -</pre>		
    -		</div>
    -<p>
    -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):  
    -</p>
    -		<div class="code">
    -<pre>
    -&lt;body&gt;
    -	&lt;div id="container0"&gt;
    -	&lt;/div&gt;
    -	&lt;div id="container1"&gt;
    -	&lt;/div&gt;
    -&lt;/body&gt;
    -
    -&lt;script type="text/javascript"&gt;
    -
    -	var e0 = jsPlumb.addEndpoint("container0"),
    -	e1 = jsPlumb.addEndpoint("container1");
    -
    -	jsPlumb.connect({ source:e0, target:e1 });
    -&lt;/script&gt;
    -</pre>		
    -		</div>
    -	now 'e0' and 'e1' have the same parent - the <em>body</em> element. 
    -	
    -	<p>Remember that you can use the <strong>anchor</strong> parameter to an <strong>addEndpoint</strong>
    -	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. 
    -	</p>									
    -	<p><strong>Note regarding the drawEndpoints option on jsPlumb.connect</strong>: 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). 
    -	</p>
    -	
    -	<h5>Overriding the default behaviour</h5>
    -	<p>
    -From version 1.3.3, the <strong>container</strong> 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 <strong>jsPlumb.Defaults.Container</strong> property, or you can set a <strong>container</strong> parameter on calls to <strong>addEndpoint</strong> or <strong>connect</strong> (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:
    -	</p>
    -		
    -Set a container to use as the default container, using a jQuery selector (you can supply MooTools/YUI selectors if that's the library you're using), and then add an Endpoint.  The canvas (or SVG/VML element) created will be a child of the document body:
    -		<div class="code">
    -<pre>
    -jsPlumb.Defaults.Container = $("body");
    -...
    -jsPlumb.addEndpoint(someDiv, { endpoint options });
    -</pre>		
    -		</div>
    -		
    -			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":
    -		<div class="code">
    -<pre>
    -jsPlumb.Defaults.Container = "containerId";
    -...
    -jsPlumb.connect({ source:someDiv, target:someOtherDiv });
    -</pre>		
    -		</div>
    -		
    -		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":
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({ source:someDiv, target:someOtherDiv, container:$("#containerId") });
    -</pre>		
    -		</div>
    -				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":
    -		<div class="code">
    -<pre>
    -jsPlumb.addEndpoint(someDiv, { ..., container:"containerId" });
    -</pre>		
    -		</div>
    -
    -		<h4><a id="elementDragging">Element Dragging</a></h4>
    -		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 <strong>jsPlumb.draggable(...)</strong>:
    -<div class="code">
    -<pre>jsPlumb.draggable("elementId");</pre>
    -</div>
    -You can also pass a selector from your library in to this method:
    -<div class="code">
    -<pre>jsPlumb.draggable($(".someClass"));</pre>
    -</div>
    -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.  A common requirement when using jQuery, for instance, is to set the containment of elements that you are making draggable.  Here's how you would tell jsPlumb to initialise some set of elements to be draggable but only inside their parent:
    -<div class="code">
    -<pre>jsPlumb.draggable($(".someClass"), {
    -	containment:"parent"
    -});</pre>
    -</div>
    -		<h5>Dragging nested elements</h5>
    -jsPlumb takes nesting into account when handling draggable elements. For example, say you have this markup:
    -<div class="code">
    -<pre>
    -&lt;div id="container"&gt;
    -  &lt;div class="childType1"&gt;&lt;/div&gt;
    -  &lt;div class="childType2"&gt;&lt;/div&gt;
    -&lt;/div&gt;
    -</pre>
    -</div>
    -..and then you connect one of those child divs to something, and make "container" draggable:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:$("#container .childType1"),
    -	target:"somewhere else"
    -});
    -
    -jsPlumb.draggable("container");
    -</pre>
    -</div>
    -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.
    -
    -		<h5>Nested Element offsets</h5>
    -		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 <strong>recalculateOffsets</strong> function. Consider the example from
    -		before, but with a change to the markup after initializing everything:
    -<div class="code">
    -<pre>
    -&lt;div id="container"&gt;
    -  &lt;div class="header" style="height:20px;background-color:blue;"&gt;header&lt;/div&gt;
    -  &lt;div class="childType1"&gt;&lt;/div&gt;
    -  &lt;div class="childType2"&gt;&lt;/div&gt;
    -&lt;/div&gt;
    -</pre>
    -</div>
    -..and then you connect one of those child divs to something, and make "container" draggable:
    -<div class="code">
    -<pre>
    -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")
    -</pre>
    -</div>
    -
    -		</div>
    -				
    -		<div class="section">
    -			<h3><a id="jsPlumbBasics">Basic Concepts</a></h3>			
    -	
    -			jsPlumb is all about connecting things together, so the core abstraction in jsPlumb is the <strong>Connection</strong> object, which is itself
    -			broken down into these four concepts:
    -
    -			<ul>
    -				<li><strong>Anchor</strong> - a location, relative to an element's origin, at which an Endpoint can exist. You do not create these yourself; you 
    -				supply hints to the various jsPlumb functions, which create them as needed.  They have no visual representation; they are a logical position only. Anchors can be referenced by name, for the Anchors that jsPlumb ships with, or a four element array describing [ x, y, x orientation, y orientation ]. 
    -				See the <a href="#anchors">anchors</a> section for more detail.<br/><br/></li>
    -				<li><strong>Endpoint</strong> - the visual representation of one end of a Connection.  You can create and attach these to elements yourself, which you are required to do to support drag and drop, or have jsPlumb create them when creating a Connection programmatically using jsPlumb.connect(...).  You can also join two Endpoints programmatically, by passing them as arguments to jsPlumb.connect(...).<br/><br/></li>				
    -				<li><strong>Connector</strong> - the visual representation of the line connecting two elements in the page.  jsPlumb has four types of these 
    -				available as defaults - a Bezier curve, a straight line, 'flowchart' connectors and 'state machine connectors. You do not interact with Connectors; 
    -				you just specify definitions of them when you need to - see <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions</a> <br/><br/></li>
    -				<li><strong>Overlay</strong> - a UI component that is used to decorate a Connector, such as a Label, Arrow, etc.<br/><br/></li>
    -			</ul>
    -						
    -			<p>One Connection is made up of two Endpoints, a Connector, and zero or more Overlays working together to join two elements.</p>
    -			<p>jsPlumb's public API exposes only Connection and Endpoint, handling the creation and configuration of everything else internally. But you still need to be across the concepts encapsulated by Anchor, Connector and Overlay.
    -			</p>
    -			</div>
    -			
    -			<div class="section">
    -			<h3><a id="anchors">Anchors</a></h3>			
    -	
    -			An Anchor models the notion of where on an element a Connector should connect.  There are three main types of Anchors:
    -			<ul>
    -			<li/>
    -				<li class="bullet">'Static' anchors - these are fixed to some point on an element and do not move.  They can be specified using a string to
    -				identify one of the defaults that jsPlumb ships with, or an array describing the location (see below)<br/><br/></li>				
    -				<li class="bullet">'Dynamic' anchors - these are lists of Static anchors from which jsPlumb picks the most appropriate one each time a Connection
    -				is painted.  The algorithm used to determine the most appropriate anchor picks the one that is closest to the center of the other element in
    -				the Connection. A future version of jsPlumb might support a pluggable algorithm to make this decision.<br/><br/></li>
    -				<li class="bullet">'Perimeter' anchors - these are anchors that follow the perimeter of some given shape. They are, in essence, Dynamic
    -				anchors whose locations are chosen from the perimeter of the underlying shape.<br/><br/></li>
    -				<li class="bullet">'Continuous' anchors - These anchors, introduced in 1.3.4, are not fixed to any specific loction; they are assigned to one of
    -				the four faces of an element depending on that element's orientation to the other element in the associated Connection.  Continuous anchors are slightly more computationally intensive than Static or Dynamic anchors because jsPlumb is required to calculate the position of every Connection during a paint cycle, rather than just Connections belonging to the element in motion.  These anchors were designed for use with the StateMachine connector that was also introduced in 1.3.4, but will work with any combination of Connector and Endpoint.<br/><br/></li> 
    -			</ul>  
    -					
    -			<h4><a id="staticAnchors">Static Anchors</a></h4>		
    -			jsPlumb has nine default anchor locations you can use to specify where the Connectors connect to elements: these are the four corners of an element,
    -			the center of the element, and the midpoint of each edge of the element:  
    -			<ul>
    -				<li class="bullet">TopCenter</li>
    -				<li class="bullet">TopRight</li>
    -				<li class="bullet">RightMiddle</li>
    -				<li class="bullet">BottomRight</li>
    -				<li class="bullet">BottomCenter</li>
    -				<li class="bullet">BottomLeft</li>
    -				<li class="bullet">LeftMiddle</li>
    -				<li class="bullet">TopLeft</li>
    -			</ul>
    -			
    -Each of these string representations is just a wrapper around the underlying array-based syntax  [x, y, dx, dy], where x and y are coordinates in the interval [0,1] specifying the position of the
    -anchor, and dx and dy are coordinates in the interval [-1, 1] specifying the orientation of the curve incident to the anchor. For example, [0, 0.5, -1, 0] defines a "LeftCenter" anchor with a connector curve that emanates leftward from the anchor. Similarly, [0.5, 0, 0, -1] defines a "CenterTop" anchor with a connector curve emanating upwards. 	
    -
    -
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:"BottomCenter", ... }); 
    -</pre>
    -</div>
    -is identical to:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:[ 0.5, 1, 0, 1 ], ... }); 
    -</pre>
    -</div>		
    -In addition to supplying the location and orientation of an anchor, you can optionally supply two more parameters that define an offset in pixels from the given location.  Here's the anchor specified above, but with a 50 pixel offset below the element in the y axis:	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:[ 0.5, 1, 0, 1, 0, 50 ], ... }); 
    -</pre>
    -</div>		
    -
    -		<h4><a id="dynamicAnchors">Dynamic Anchors</a></h4>
    -	These are Anchors that can be positioned in one of a number of locations, choosing the one that is most appropriate each time something moves or is painted in the UI.
    -	<p>
    -	There is no special syntax to creating a DynamicAnchor; you just provide an array of individual Static Anchor specifications, eg:
    -	</p>
    -<div class="code">
    -<pre>
    -var dynamicAnchors = [ [ 0.2, 0, 0, -1 ],  [ 1, 0.2, 1, 0 ], 
    -			   [ 0.8, 1, 0, 1 ], [ 0, 0.8, -1, 0 ] ];
    -
    -jsPlumb.connect({...., anchor:dynamicAnchors, ... }); 			   
    -</pre>
    -</div>
    -<h4>	Default Dynamic Anchor</h4>
    -jsPlumb provides a dynamic anchor called "AutoDefault" that chooses from TopCenter, RightMiddle, BottomCenter and LeftMiddle:
    -<div class="code">
    -<pre>jsPlumb.connect({...., anchor:"AutoDefault", ... });</pre>
    -</div>
    -
    -<h4>	Location Selection</h4>
    -The initial implementation of the algorithm that decides which location to choose just calculates which location is closest to the center of the 
    -other element in the connection.  It is possible that future versions of jsPlumb could support more sophisticated choice algorithms, if the need arose.
    -<h4>Draggable Connections</h4>
    -Dynamic Anchors and Draggable Connections can interoperate: jsPlumb locks the position of a dynamic anchor when you start to drag a connection from it,
    -and unlocks it once the connection is either established or abandoned. At that point you may see the position of the dynamic anchor change, as jsPlumb
    -optimises the connection.  <p>You can see this behaviour in the <a href="../html/jquery/draggableConnectorsDemo.html" target="_blank">draggable connections</a> demonstration, when
    -you drag a connection from the blue endpoint on window 1 to the blue endpoint on window 3 - the connection is established and then window 1's blue
    -endpoint jumps down to a location that is closer to window 3.</p>
    -
    -	<h4><a id="perimeterAnchors">Perimeter Anchors</a></h4>	
    -These are a form of Dynamic anchor in which the anchor locations are chosen from the perimeter of some given shape. jsPlumb
    -supports six shapes:
    -
    -<ul>
    -	<li class="bullet">circle</li>
    -	<li class="bullet">ellipse</li>
    -	<li class="bullet">triangle</li>
    -	<li class="bullet">diamond</li>
    -	<li class="bullet">rectangle</li>
    -	<li class="bullet">square</li>	
    -</ul>
    -
    -'Rectangle' and 'Square', are not, strictly speaking, necessary, since rectangular shapes are the norm in a web page. But they
    -are included for completeness.
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("someElement", {
    -	endpoint:"Dot",
    -	anchor:[ "Perimeter", { shape:"circle" } ]
    -});
    -</pre>
    -</div>
    -
    -In this example our anchor will travel around the path inscribed by a circle whose diameter is the width and height of the
    -underlying element. Note that the 'circle' shape is therefore identical to 'ellipse', since it is assumed the underlying
    -element will have equal width and height, and if it does not, you will get an ellipse. 'rectangle' and 'square' have the
    -same relationship.
    -<p>By default, jsPlumb approximates the perimeter with 60 anchor locations.  You can change this, though:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("someDiv", {
    -	endpoint:"Dot",
    -	anchor:[ "Perimeter", { shape:"square", anchorCount:150 }]
    -});
    -</pre>		
    -</div>
    -Obviously, the more points the smoother the operation. But also the more work your browser has to do.
    -<p>'triangle' and 'diamond' both assume that there is a point at the top center of the shape. This may or may not turn
    -out to be acceptable for all applications, in which case it might be possible to add support for a 'rotation' argument to the
    -anchor constructor.</p>
    -Here's a triangle and diamond example, just for kicks:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:"Dot",
    -	anchors:[
    -		[ "Perimeter", { shape:"triangle" } ],
    -		[ "Perimeter", { shape:"diamond" } ]
    -	]
    -});
    -</pre>		
    -</div>
    -<h5>Perimeter Anchor Rotation</h5>
    -You can supply a 'rotation' value to a Perimeter anchor - an example can be seen in <a href="../jquery/perimeterAnchorsDemo.html" target="_blank">this demo</a>. Here's how you would use it:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:"Dot",
    -	anchors:[
    -		[ "Perimeter", { shape:"triangle", rotation:25 } ],
    -		[ "Perimeter", { shape:"triangle", rotation:-335 } ]
    -	]
    -});  
    -</pre>
    -</div>
    -
    -Note that the value must be supplied <strong>in degrees</strong>, not radians, and the number may be either positive or negative. In the example above, both triangles are of course rotated by the same amount.
    -
    -	<h4><a id="continuousAnchors">Continuous Anchors</a></h4>
    -	As discussed above, these are anchors whose positions are calculated by jsPlumb according to the orientation between elements in a Connection, and also how
    -	many other Continuous anchors happen to be sharing the element.  You specify that you want to use Continuous anchors using the string syntax you would use
    -	to specify one of the default Static Anchors, for example:
    -	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:someDiv,
    -	target:someOtherDiv,
    -	anchor:"Continuous"
    -});
    -</pre>
    -</div>
    -
    -Note in this example I specified only "anchor", rather than "anchors" - jsPlumb will use the same spec for both anchors.  But I could have said this:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:someDiv,
    -	target:someOtherDiv,
    -	anchors:["BottomCenter", "Continuous"]
    -});
    -</pre>
    -</div>	 	  
    -..which would have resulted in the source element having a Static Anchor at BottomCenter.  In practise, though, it seems the Continuous Anchors work best if both 
    -elements in a Connection are using them.
    -<p>Note also that Continuous Anchors can be specified on <strong>addEndpoint</strong> calls:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint(someDiv, {
    -	anchor:"Continuous",
    -	paintStyle:{ fillStyle:"red" }
    -});
    -</pre>
    -</div>	 	
    -...and in <strong>makeSource</strong>/<strong>makeTarget</strong>:
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource(someDiv, {
    -	anchor:"Continuous",
    -	paintStyle:{ fillStyle:"red" }
    -});
    -
    -jsPlumb.makeTarget(someDiv, {
    -	anchor:"Continuous",
    -	paintStyle:{ fillStyle:"red" }
    -});
    -</pre>
    -</div>	 
    -... and in the jsPlumb defaults:
    -<div class="code">
    -<pre>
    -jsPlumb.Defaults.Anchor = "Continuous";
    -</pre>
    -</div>  
    -					</div>		
    -												
    -<div class="section">
    -		<h4><a id="definitions">Connector, Endpoint &amp; Overlay Definitions</a></h4>
    -Before we discuss Connector, Endpoints and Overlays, a quick word on definitions:  whenever you need to define a Connector, Endpoint or Overlay, you must use a "definition" of it, rather than constructing one directly.  This definition can be either a string that nominates the artifact you want to create:
    -		<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:"Rectangle"
    -});
    -</pre>
    -		</div>
    -		or an array consisting of both the artifact's name and the arguments you want to pass to its constructor:
    -		<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Rectangle", { cssClass:"myEndpoint", width:30, height:10 } ]
    -});
    -</pre>
    -		</div>
    -There is also a three-argument method that allows you to specify two sets of parameters, which jsPlumb will merge together for you. The idea behind this is that you will often want to define common characteristics somewhere and reuse them across a bunch of different calls:
    -		<div class="code">
    -<pre>var common = {
    -	cssClass	:	"myCssClass",
    -	hoverClass	:	"myHoverClass"
    -};
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Rectangle", { width:30, height:10 }, common ]
    -});
    -</pre>
    -		</div>
    -	This syntax is supported for all Endpoint, Connector and Overlay definitions.  Here's an example using definitions for all three:	
    -		<div class="code">
    -<pre>var common = {
    -	cssClass:"myCssClass"
    -};
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Dot", { radius:5, hoverClass:"myEndpointHover" }, common ],
    -	connector:[ "Bezier", { curviness:100 }, common ],
    -	overlays: [
    -		[ "Arrow", { foldback:0.2 }, common ],
    -		[ "Label", { cssClass:"labelClass" } ]	
    -	]
    -});
    -</pre>
    -		</div>		
    -The allowed constructor parameters are different for each artifact you create, but every artifact takes a single JS object as argument, with the parameters as [key,value] pairs in that object.  See the relevant sections on <a href="#connectors">Connectors</a>, <a href="#endpoints">Endpoints</a> and <a href="#overlays">Overlays</a> for information on supported parameters.</a>
    -				
    -	</div>
    -
    -		<div class="section">
    -			<h3><a id="connectors">Connectors</a></h3>
    -			<p>Connectors are the lines that actually join elements of the UI.  jsPlumb has four connector implementations - a straight line, a Bezier curve, "flowchart", and "state machine".  The default connector is the Bezier curve.</p>
    -			<p>You optionally specify a Connector by setting the 'connector' property on a call to jsPlumb.connect, jsPlumb.addEndpoint(s), jsPlumb.makeSource or jsPlumb.makeTarget. If you do not supply a value for 'connector', the default will be used (see <a href="#defaults">defaults</a>).</p>
    -			<p>You specify Connectors using the syntax described above in <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions.</a> Allowed constructor values for each Connector type are described below:</p>		
    -			<h4><a id="bezierConnector">Bezier Connector</a></h4>
    -			<p>The Bezier Connector provides a cubic Bezier path between the two Endpoints. It supports a single constructor argument:</p>
    -			<p>
    -			<strong>curviness</strong> - Optional; defaults to 150. This defines the distance in pixels that
    -			the Bezier's control points are situated from the anchor points.  This does not mean that your
    -			connector will pass through a point at this distance from your curve.  It is a hint to how you want the
    -			curve to travel. Rather than discuss Bezier curves at length here, we 
    -			refer you to <a href="http://en.wikipedia.org/wiki/B%C3%A9zier_curve" target="_blank">Wikipedia.</a>
    -			</p>
    -			<h4><a id="straightConnector">Straight Connector</a></h4>
    -			<p>The Straight Connector draws a straight line between the two endpoints. No constructor arguments are supported; use the <strong>endpointStyle</strong> and/or <strong>cssClass</strong>/<strong>connectorClass</strong> to a <strong>connect</strong>, <strong>addEndpoint</strong>, <strong>makeSource</strong> or <strong>makeTarget</strong> call to control the appearance of one of these Connectors.  
    -			</p>
    -			<h4><a id="flowchartConnector">Flowchart Connector</a></h4>
    -			<p>This type of Connector draws a connection that consists of a series of vertical or horizontal segments - the classic flowchart look. Two constructor arguments are supported:</p>
    -			<strong>stub</strong> - this is the minimum length, in pixels, of the initial stub that emanates from an Endpoint.  This is an optional parameter, and can be either an integer, which specifies the stub fo each end of the connector, or an array of two integers, specifying the stub for the [source, target] endpoints in the connection.  This parameter is optional, and defaults to an integer with value 30 pixels.<br/>
    -			<strong>gap</strong> - optional, defaults to 0 pixels. Lets you specify a gap between the end of the connector and the elements to which it is attached.
    -			<strong>midpoint</strong> - optional, defaults to 0.5. This is the distance between the two elements that the longest section of the flowchart connector will be drawn at.  This parameter is useful for those cases where you have programmatic control of the drawing and perhaps want to avoid some other element on the page.
    -			<p>This Connector supports Connections that start and end on the same element.</p>
    -			<h4><a id="stateMachineConnector">State Machine Connector</a></h4>
    -			<p>This Connector draws slightly curved lines (they are actually quadratic Bezier curves), similar to the State Machine connectors you may have seen in software like GraphViz.  Connections in which some element is both the source and the target (so-called 'loopback') are supported by these Connectors (as they are with Flowchart Connectors); in this case you get a circle. Supported parameters are:
    -			</p>
    -			<p>
    -			<strong>margin</strong> - Optional; defaults to 5.  Defines the distance from the element that the connector begins/ends.
    -			</p>
    -			<p>
    -			<strong>curviness</strong> - Optional, defaults to 10.  This has a similar effect to the curviness parameter on Bezier curves.
    -			</p>
    -			<p>
    -			<strong>proximityLimit</strong> - Optional, defaults to 80.  The minimum distance between the two ends of the Connector before it paints itself as 
    -			a straight line rather than a quadratic Bezier curve.
    -			</p>    
    -			
    -		</div>
    -		
    -			<div class="section">
    -			<h3><a id="endpoints">Endpoint Types</a></h3>
    -			<p>An Endpoint is the UI component that marks the location of an Anchor, ie. the place where a Connector joins an
    -			element.  jsPlumb comes with four Endpoint implementations - <strong>Dot</strong>, <strong>Rectangle</strong>, <strong>Blank</strong> and <strong>Image</strong>. You optionally specify Endpoint properties using the
    -			<strong>endpoint</strong> parameter in a call to jsPlumb.connect, jsPlumb.addEndpoint, jsPlumb.makeSource or jsPlumb.makeTarget.
    -			</p>  
    -				<p>As with Connectors and Overlays, you specify Endpoints using the syntax described above in <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions.</a></p>
    -			
    -			The four available Endpoint types, and their constructor arguments, are as follows:
    -					<h4><a id="dotEndpoint">Dot Endpoint</a></h4>
    -					This Endpoint draw a dot on the screen. It supports three constructor parameters:
    -					<p>
    -						<strong>radius</strong> - Optional; defaults to 10 pixels. Defines the radius of the dot.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="rectangleEndpoint">Rectangle Endpoint</a></h4>
    -						Draws a rectangle. Supported constructor parameters are:
    -					<p>
    -						<strong>width</strong> - Optional; defaults to 20 pixels. Defines the width of the rectangle.
    -					</p>
    -					<p>
    -						<strong>height</strong> - Optional; defaults to 20 pixels. Defines the height of the rectangle.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="imageEndpoint">Image Endpoint</a></h4>
    -						Draws an image from a given URL.  This Endpoint supports three constructor parameters:  
    -					<p>
    -						<strong>src</strong> - Required.  Specifies the URL of the image to use.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="blankEndpoint">Blank Endpoint</a></h4>
    -						Does not draw anything visible to the user.  As discussed previously, this Endpoint is probably not what you want if you need your users to be able to drag existing Connections - for that, use a Rectangle or Dot Endpoint and assign to it a CSS class that causes it to be transparent.
    -																			
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="overlays">Overlay Types</a></h3>
    -			<p>
    -			Overlays are UI elements that are painted onto connections, such as labels or arrows.
    -			jsPlumb comes with four defaults:
    -			</p>
    -			<ul>
    -				<li><strong>Arrow</strong> - a configurable arrow that is painted at some point along the connector.  You can control the length and width of the Arrow, the 'foldback' point - a point the tail points fold back into, and the direction (allowed values are 1 and -1; 1 is the default and means point in the direction of the connection)</li>
    -				<li><strong>Label</strong> - a configurable label that is painted at some point along the connector.</li>
    -				<li><strong>PlainArrow</strong> - an Arrow shaped as a triangle, with no foldback.</li>
    -				<li><strong>Diamond</strong> - as the name suggests, a diamond.</li>
    -			</ul> 
    -			The last two are actually just configured instances of the generic Arrow overlay (see examples). 
    -			
    -			<p>All Overlays support these two methods for getting/setting their location (location is either a number between 0 and 1 inclusive, indicating some point along the path inscribed by the associated Connector, or an absolute number of pixels, where negative values mean distance from the target of the connection):</p>
    -			<ul>
    -				<li class="bullet"><strong>getLocation</strong> - returns the current location</li>
    -				<li class="bullet"><strong>setLocation</strong> - sets the current location; valid values are decimals in the range 0-1 inclusive, or an integer less than 0 (to express distance from the target), or an integer greater than 1 (to express distance from the source)</li> 
    -			</ul>
    -			
    -			<p>You can specify one or more overlays when making a call to jsPlumb.connect, jsPlumb.addEndpoint or jsPlumb.makeSource (but not jsPlumb.makeTarget; overlays
    -			are always derived from what the source of a Connection defines)  The three cases are discussed below:</p>
    -			1. Specifying one or more overlays on a jsPlumb.connect call. In this example we'll create an Arrow with the default options for an Arrow, and
    -			a label with the text "foo":
    -			<div class="code">
    -			<pre>jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});			
    -</pre>
    -			</div>
    -			This connection will have an arrow located halfway along it, and the label "foo" one quarter of the way along.  Notice the <strong>id</strong>
    -			parameter; it can be used later if you wish to remove the Overlay or change its visibility (see below).<br/><br/> 
    -			Another example, this time with an absolute location of 50 pixels from the source:
    -			<div class="code">
    -			<pre>jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:50 }, id:"myLabel" ]
    -	],
    -	...
    -});			
    -</pre>
    -			</div>
    -			
    -			2. Specifying one or more overlays on a jsPlumb.addEndpoint call.  Note in this example that we use the parameter 'connectorOverlays' and not
    -			'overlays' as in the last example.  This is because 'overlays' would refer to Endpoint Overlays:
    -			<div class="code">
    -			<pre>jsPlumb.addEndpoint("someDiv", {
    -	...
    -	connectorOverlays:[ 
    -		[ "Arrow", { width:10, length:30, location:1, id:"arrow" } ], 
    -		[ "Label", { label:"foo", id:"label" } ]
    -	],
    -	...
    -});</pre>
    -			</div>	
    -			This connection will have a 10x30 Arrow located right at the head of the connection, and the label "foo" located at the halfway point. Again,
    -			note that <strong>id</strong> parameter passed in to each Overlay spec.<br/><br/>
    -			
    -			3. Specifying one or more overlays on a jsPlumb.makeSource call.  Note in this example that we again use the parameter 'connectorOverlays' and not
    -			'overlays'.  The 'endpoint' parameter to jsPlumb.makeSource supports everything you might pass to the second argument of a jsPlumb.addEndpoint call:
    -			<div class="code">
    -			<pre>jsPlumb.makeSource("someDiv", {
    -	...
    -	endpoint:{
    -		connectorOverlays:[ 
    -			[ "Arrow", { width:10, length:30, location:1, id:"arrow" } ], 
    -			[ "Label", { label:"foo", id:"label" } ]
    -		]
    -	}
    -	...
    -});</pre>
    -			</div>	
    -			This connection will have a 10x30 Arrow located right at the head of the connection, and the label "foo" located at the halfway point. Again,
    -			note that <strong>id</strong> parameter passed in to each Overlay spec.<br/><br/>
    -			
    -			 	
    -					<h4><a id="arrowOverlay">Arrow Overlay</a></h4>
    -					<p>This overlay draws an arrow, using four points: the head and two tail points, and a 'foldback' point, which permits the tail
    -					of the arrow to be indented. Available constructor arguments for this Overlay are </p>
    -					<ul>
    -					<li><strong>width</strong> - width of the tail of the arrow</li>
    -					<li><strong>length</strong> - distance from the tail of the arrow to the head</li>
    -					<li><strong>location</strong> - where, either as a proportional value from 0 to 1 inclusive, or as an absolute value (negative values mean distance from target; positive values greater than 1 mean distance from source) the Arrow should appear on the Connector</li>
    -					<li><strong>direction</strong> - which way to point. Allowed values are 1 (the default, meaning forwards) and -1, meaning backwards </li>
    -					<li><strong>foldback</strong> - how far along the axis of the arrow the tail points foldback in to. Default is 0.623.</li>
    -					<li><strong>paintStyle</strong> - a style object in the form used for paintStyle values for Endpoints and Connectors</li>
    -					</ul>
    -					<h4><a id="plainArrowOverlay">PlainArrow Overlay</a></h4>
    -					<p>This is just a specialized instance of Arrow in which jsPlumb hardcodes 'foldback' to 1, meaning the tail of the Arrow is a flat edge.  All of the constructor parameters from Arrow apply for PlainArrow.</p>
    -					<h4><a id="diamondOverlay">Diamond Overlay</a></h4>
    -					<p>This is a specialized instance of Arrow in which jsPlumb hardcodes 'foldback' to 2, meaning the Arrow turns into a Diamond.  All of the constructor parameters from Arrow apply for Diamond.</p>
    -					<h4><a id="labelOverlay">Label Overlay</a></h4>
    -					This provides a text label to decorate Connectors with.  The available constructor arguments are:
    -					<ul>
    -						<li><strong>label</strong> - The text to display.  You can provide a function here instead of plain text: it is passed the Connection as an argument, and it should return a String.</li>
    -						<li><strong>cssClass</strong> - Optional css class to use for the Label.  This is now preferred over using the 'labelStyle' parameter.</li>
    -						<li><strong>labelStyle</strong> - (deprecated, use cssClass instead) Optional arguments for the label's appearance.  Valid entries in this JS object are:
    -							<ul>
    -								<li><em>font</em> - a font string in a format suitable for the Canvas element</li>
    -								<li><em>fillStyle</em> - the color to fill the label's background with. Optional.</li>
    -								<li><em>color</em> - the color of the label's text. Optional.</li>
    -								<li><em>padding</em> - optional padding for the label. This is expressed as a proportion of the width of the label, not in pixels or ems.</li>
    -								<li><em>borderWidth</em> - optional width in pixels for the label's border. Defaults to 0.</li>
    -								<li><em>borderStyle</em> - optional. The color to paint the border, if there is one.</li>
    -								
    -							</ul>
    -						
    -						</li>
    -						<li><strong>location</strong> - As for Arrow Overlay.  Where, either proportionally from 0 to 1 inclusive, or as an absolute offset from either source or target, the label should appear.</li>
    -					</ul>
    -
    -					The Label overlay offers two methods - getLabel and setLabel - for accessing/manipulating its content dynamically:
    -<div class="code">
    -<pre>
    -var c = jsPlumb.connect({
    -	source:"d1", 
    -	target:"d2", 
    -	overlays:[
    -		[ "Label", {label:"FOO", id:"label"}]
    -	]	
    -});					
    -
    -...
    -
    -var label = c.getOverlay("label");
    -console.log("Label is currently", label.getLabel());
    -label.setLabel("BAR");
    -console.log("Label is now", label.getLabel());
    -</pre>
    -</div>
    -In this example you can see that the Label Overlay is assigned an id of "label" in the connect call, and then retrieved using that id in the call to Connection's getOverlay method.
    -<p>Both Connections and Endpoints support Label Overlays, and because changing labels is quite a common operation, <strong>setLabel</strong> and <strong>getLabel</strong> methods have been added to these objects:
    -<div class="code">
    -<pre>
    -var conn = jsPlumb.connect({
    -	source:"d1", 
    -	target:"d2",
    -	label:"FOO"
    -});					
    -
    -...
    -
    -console.log("Label is currently", conn.getLabel());
    -conn.setLabel("BAR");
    -console.log("Label is now", conn.getLabel());
    -</pre>
    -</div>
    -These methods support passing in a Function instead of a String, and jsPlumb will create a label overlay for you if one does not yet exist when you call setLabel:
    -<div class="code">
    -<pre>
    -var conn = jsPlumb.connect({
    -	source:"d1", 
    -	target:"d2"
    -});					
    -
    -...
    -
    -conn.setLabel(function(c) {
    -	var s = new Date();
    -	return s.getTime() + "milliseconds have elapsed since 01/01/1970";
    -});
    -console.log("Label is now", conn.getLabel());
    -</pre>
    -</div>
    -					
    -					<h4><a id="hideshowOverlay">Hiding/Showing Overlays</a></h4>
    -					<p>You can control the visibility of Overlays using the <strong>setVisible</strong> method of Overlays themselves, or with
    -					<strong>showOverlay(id)</strong> or <strong>hideOverlay(id)</strong> on a Connection.</p>
    -					<p>Remember the <strong>id</strong> parameter that we specified in the examples above?  This can be used to retrieve the Overlay
    -					from a Connection:</p>
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -var overlay = connection.getOverlay("myLabel");
    -// now you can hide this Overlay:
    -overlay.setVisible(false);
    -// there are also hide/show methods:
    -overlay.show();
    -overlay.hide();
    -
    -</pre>					
    -					</div>
    -					<p>However, Connection and Endpoint also have two convenience methods you could use instead:
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:-30 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -connection.hideOverlay("myLabel");
    -
    -// more time passes
    -
    -connection.showOverlay("myLabel");
    -
    -</pre>					
    -					</div>
    -					
    -		<h4><a id="hideshowOverlay">Removing Overlays</a></h4>
    -		<p>Connection and Endpoint also have a <strong>removeOverlay</strong> method, that does what you might expect:
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -connection.removeOverlay("myLabel");
    -
    -
    -</pre>					
    -					</div>							
    -	
    -		</div>
    -	
    -			
    -			<div class="section">
    -			<h3><a id="defaults">Defaults</a></h3>
    -			The easiest way to set a look and feel for your plumbing is to override the defaults that jsPlumb uses. If you
    -			do not do this you are forced to provide your overridden values on every call.  Every argument to the connect and addEndpoint methods has an
    -			associated default value in jsPlumb.<br/><br/>
    -
    -			The defaults that ship with jsPlumb are stored in <em>jsPlumb.Defaults</em>, which is a Javascript object.  Valid entries, and their initial values, are:
    -
    -			<div class="code">
    -<pre>
    -Anchor : "BottomCenter",
    -Anchors : [ null, null ],
    -Connector : "Bezier",
    -ConnectionOverlays : [ ],
    -DragOptions : { },
    -DropOptions : { },
    -Endpoint : "Dot",
    -EndpointOverlays : [ ],
    -Endpoints : [ null, null ],
    -EndpointStyle : { fillStyle : null },
    -EndpointStyles : [ null, null ],
    -EndpointHoverStyle : null,
    -EndpointHoverStyles : [ null, null ],
    -HoverPaintStyle : null,
    -LabelStyle : { color : "black" },
    -LogEnabled : false,
    -Overlays : [ ],
    -MaxConnections : 1,
    -PaintStyle : { lineWidth : 8, strokeStyle : "#456" },
    -RenderMode : "svg",
    -Scope : "_jsPlumb_DefaultScope"
    -</pre>
    -			</div>
    -			<p>
    -			Note that in EndpointStyle, the default fillStyle is 'null'.  This instructs jsPlumb to use the strokeStyle
    -from the attached connector to fill the endpoint.</p>
    -<p>Note also that you can specify either or both (or neither) of 'EndpointStyle' and 'EndpointStyles'.  This allows you to specify a different
    -end point for each end of a connection.  'Endpoint' and 'Endpoints' use the same concept.  jsPlumb will look first in the
    -individual endpoint/endpoint style arrays, and then fall back to the single default version.</p>
    -
    -			you can override these defaults by including this in a script somewhere:
    -			<div class="code">
    -<pre>
    -jsPlumb.Defaults.PaintStyle = {
    -	lineWidth:13,
    -	strokeStyle: 'rgba(200,0,0,0.5)'
    -}
    -
    -jsPlumb.Defaults.DragOptions = { cursor: "crosshair" };
    -
    -jsPlumb.Default.Endpoints = [ [ "Dot", 7 ], [ "Dot", 11 ] ];
    -
    -jsPlumb.Defaults.EndpointStyles = [{ fillStyle:"#225588" }, { fillStyle:"#558822" }];</pre>
    -			</div>
    -			after the jsPlumb script has been loaded of course!  Here we have specified the following default behaviour:
    -			<ul>
    -				<li>- connectors are 13 pixels wide and painted with a semi-transparent red line</li>
    -				<li>- when dragging an element the crosshair cursor is used</li>
    -				<li>- the source endpoint is a dot of radius 7; the target endpoint is a dot of radius 11</li>
    -				<li>- the source endpoint is blue; the target endpoint is green</li>
    -			</ul>
    -
    -			<h5>jsPlumb.importDefaults</h5>
    -There is a helper method called <strong>importDefaults</strong> that allows you to import a set of values with just one call to jsPlumb.  Here's the previous example using importDefaults:
    -			<div class="code">
    -<pre>
    -jsPlumb.importDefaults({
    -	PaintStyle : {
    -		lineWidth:13,
    -		strokeStyle: 'rgba(200,0,0,100)'
    -	},
    -	DragOptions : { cursor: "crosshair" },
    -	Endpoints : [ [ "Dot", 7 ], [ "Dot", 11 ] ],
    -	EndpointStyles : [{ fillStyle:"#225588" }, { fillStyle:"#558822" }]
    -});</pre>
    -			</div>
    -
    -		</div> 					
    -
    -<div class="section">
    -			<h3><a id="simpleConnections">Connections</a></h3>
    -			<h4><a id="simpleConnections">Programmatic Connections</a></h4>
    -The most simple connection you can make with jsPlumb looks like this:
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({source:"element1", target:"element2"});</pre>			
    -			</div>
    -
    -		In this example we have created a Connection from 'element1' to 'element2'.  Remember that a Connection in jsPlumb consists of two Endpoints,
    -		a Connector, and zero or more Overlays.  But this call to 'connect' supplied none of those things, so jsPlumb uses the default values wherever it needs to.
    -		In this case, default values have been used for the following: 
    -		<ul>
    -			<li>- The type and appearance of each Endpoint in the Connection. jsPlumb's default for this is the "Dot" endpoint, of radius 10, with fill color "#456".</li>
    -			<li>- The Anchors that define where the connection's Endpoints appear on each element. The jsPlumb default is "BottomCenter"</li>			
    -			<li>- Whether or not each Endpoint can be a source or target for new Connections. The default is false.</li>
    -			<li>- The type and appearance of the Connection's Connector. The default is a "Bezier" connector of line width 8, and color "#456".</li>
    -		</ul>
    -		<p>
    -		So this call will result in an 8px Bezier, colored "#456", from the bottom center of 'element1' to the bottom center of 'element2', and each
    -		Endpoint will be a 10px radius Dot Endpoint, colored "#456".
    -		</p>
    -		<p>
    -		Let's beef up this call a little and tell jsPlumb what sort of Endpoints we want, and where we want them:		
    -		</p>
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:"element1", 
    -	target:"element2",
    -	anchors:["RightMiddle", "LeftMiddle" ],
    -	endpoint:"Rectangle",
    -	endpointStyle:{ fillStyle: "yellow" }
    -});
    -</pre>			
    -			</div>
    -			
    -		<p>This is what we have told jsPlumb we want this time:</p>
    -		<ul>
    -			<li><strong>anchors</strong> - this array tells jsPlumb where the source and target Endpoints should be located on their parent elements. In this case,
    -			we use the shorthand syntax to name one of jsPlumb's default anchors; you can also specify custom locations (see <a href="#anchors">anchors</a>).
    -			Instead of <strong>anchors</strong> you can use <strong>anchor</strong>, if you want the source and target Endpoints to be located at the
    -			same place on their parent elements.			<br/>
    -			</li>
    -			<li>
    -				<strong>endpoint</strong>  - this tells jsPlumb to use the "Rectangle" Endpoint for both the source and target of the Connection.  As with anchors, 
    -				<strong>endpoint</strong> has a plural version that allows you to specify a different Endpoint for each end of the Connection.<br/>  
    -			</li>
    -			<li>
    -				<strong>endpointStyle</strong> - this is the definition of the appearance of the Endpoint you specified above.  Again, there is a
    -				plural equivalent of this that allows you to specify a different style for each end of the Connection. For more information about
    -				allowed values for this value, see <a href="#paintstyles">Connector, Endpoint & Overlay Styles</a>. 
    -			</li>
    -		</ul>		
    -		
    -		<h5>Reusing common settings between jsPlumb.connect calls</h5>
    -		<p>
    -			A fairly common situation you will find yourself in is wanting to create a bunch of Connections that have only minor differences
    -			between them.  To support that, <strong>jsPlumb.connect</strong> takes an optional second argument. For example:			 
    -		</p>
    -		<div class="code">
    -<pre>
    -var common = {
    -	anchors:[ "BottomCenter", "TopCenter" ],
    -	endpoints:["Dot", "Blank" ]
    -};
    -
    -jsPlumb.connect({ source:"someElement", target:"someOtherElement" }, common);
    -
    -jsPlumb.connect({ source:"aThirdElement", target:"yetAnotherElement" }, common);
    -
    -</pre>			
    -			</div>		
    -			
    -		<h5>Endpoints created by jsPlumb.connect</h5>
    -		<p>If you supply an element id or selector for either the source or target , jsPlumb.connect will automatically create an Endpoint on the 
    -		given element.  These automatically created Endpoints are not marked as drag source or targets, and cannot
    -		be interacted with.  For some situations this behaviour is perfectly fine, but for more interactive UIs you should set things up using the drag and drop
    -		method discussed below.</p>
    -		<p>Given that jsPlumb.connect creates its own Endpoints in some circumstances, in order to avoid leaving orphaned Endpoints around the place, if the Connection is subsequently
    -		deleted, these created Endpoints are deleted too.  Should you want to, you can override this behaviour by setting <strong>deleteEndpointsOnDetach</strong> to
    -		false in the connect call:</p>
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({ 
    -	source:"aThirdElement", 
    -	target:"yetAnotherElement",
    -	deleteEndpointsOnDetach:false 
    -});
    -</pre>
    -</div>	
    -
    -	<h5>Detaching Connections</h5>
    -	By default, connections made with jsPlumb.connect will be detachable via the mouse.  You can prevent this by either setting an appropriate default value:
    -
    -		<div class="code">
    -<pre>
    -jsPlumb.importDefaults({ 
    -	...
    -	ConnectionsDetachable:false
    -	...
    -});
    -</pre>
    -</div>	
    -
    -	Or by specifying it on the connect call like this:
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({ 
    -	source:"aThirdElement", 
    -	target:"yetAnotherElement",
    -	detachable:false
    -});
    -</pre>
    -</div>		
    -								
    -					
    -		<h4><a id="draggableConnections">Connections using Drag and Drop</a></h4>
    -		To support drag and drop connections, you first need to set a few things up.  Every drag and drop connection needs at least a source
    -		Endpoint that the user can drag a connection from. Here's a simple example of how to create an Endpoint:
    -		<div class="code">
    -<pre>
    -var endpointOptions = { isSource:true };
    -var endpoint = jsPlumb.addEndpoint('elementId', endpointOptions);
    -</pre>		
    -</div>  	
    -	This Endpoint will act as a source for new Connections, and will use the jsPlumb defaults for its own appearance and that of any Connections that are drawn from it.							
    -	
    -		<h5>Tip: use the three-argument addEndpoint method for common data </h5>
    -		One thing that happens quite often is that you have an Endpoint whose appearance and behaviour is largely the
    -		same between usages on different elements, with just a few differences. 
    -		<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -	endpoint:"Rectangle",
    -	paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -	isSource:true,
    -	connectorStyle : { strokeStyle:"#666" },
    -	isTarget:true
    -};
    -</pre>		
    -</div>  		
    -		Notice there is no 'anchor' set.  Here we apply it to two elements, at a different location in each:
    -		<div class="code">
    -<pre>
    -jsPlumb.addEndpoint('element1', { anchor:"BottomCenter" }, exampleGreyEndpointOptions)); 
    -
    -jsPlumb.addEndpoint('element2', { anchor:"TopCenter" }, exampleGreyEndpointOptions));
    -</pre>		
    -</div>
    -
    -<p>
    -Now that you have a source Endpoint, you need to either create a target Endpoint on some element, or notify jsPlumb that you wish to make an
    -entire element a drop target.  Let's look at how to attach a target Endpoint first:
    -</p>
    -
    -		<div class="code">
    -<pre>var endpointOptions = { isTarget:true, endpoint:"Rectangle", paintStyle:{ fillStyle:"gray" } };
    -var endpoint = jsPlumb.addEndpoint("otherElementId", endpointOptions);
    -</pre>		
    -		</div>
    -		<p>
    -		This Endpoint, a gray rectangle, has declared that it can act as a drop target for Connections. 
    -</p>
    -<p>
    -jsPlumb also supports marking entire elements as drag sources or drag targets, using the <strong>makeSource</strong> and <strong>makeTarget</strong> methods.  This is a useful function for certain applications.  Each of these methods takes an Endpoint declaration which defines the Endpoint that jsPlumb will create after a Connection has been attached.  Let's take a look at makeTarget first:
    -</p>
    -<h4><a id="sourcesAndTargets">Elements as sources &amp; targets</a></h4>
    -jsPlumb also supports turning entire elements into Connection sources and targets, using the methods <strong>makeSource</strong> and <strong>makeTarget</strong>.  With these methods you mark an element as a source or target, and provide an Endpoint specification for jsPlumb to use when a Connection is established.  makeSource also gives you the ability to mark some child element as the place from which you wish to drag Connections, but still have the Connection on the main element after it has been established.
    -<p>These methods honour the jsPlumb defaults - if, for example you set up the default Anchors to be this:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.Defaults.Anchors = [ "LeftMiddle", "BottomRight" ];
    -</pre>
    -</div>
    -... and then used makeSource and makeTarget without specifying Anchor locations, jsPlumb would use "LeftMiddle" for the makeSource element and "BottomRight" for the makeTarget element.
    -<p>
    -A further thing to note about makeSource and makeTarget is that any prior calls to one of these methods is honoured by subsequent calls to jsPlumb.connect. This helps when you're building a UI that uses this functionality at runtime but which loads some initial data, and you want the statically loaded data to have the same appearance and behaviour as dynamically created connections (obviously quite a common use case).  
    -<p>For example:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -	anchor:"Continuous",
    -	endpoint:["Rectangle", { width:40, height:20 }],
    -	maxConnections:3
    -});
    -
    -...
    -
    -jsPlumb.connect({source:"el1", target:"el2"});
    -</pre>
    -</div>
    -In this example, the source of the connection will be a Rectangle of size 40x20, having a Continuous Anchor.  The source is also configured to allow a maximum of three connections.
    -<p>You can override this behaviour by setting a <strong>newConnection</strong> parameter on the connect call:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -	anchor:"Continuous",
    -	endpoint:["Rectangle", { width:40, height:20 }],
    -	maxConnections:1,
    -	onMaxConnections:function(info, originalEvent) {
    -		console.log("element is ", info.element, "maxConnections is", info.maxConnections);	
    -	}
    -});
    -
    -...
    -
    -jsPlumb.connect({ source:"el1", target:"el2", newConnection:true });
    -</pre>
    -</div>
    -Note the <strong>onMaxConnections</strong> parameter to this call - it allows you to supply a function to call if the user tries to drag a new Connection when the source has already reached capacity.
    -<h5><a id="makeTarget">jsPlumb.makeTarget</a></h5>
    -<p>
    -This method takes two arguments, the first of which specifies some element (or list of elements); the second specifies the Endpoint you wish to create on that element whenever a Connection is established on it.  In this example we will use the exact same target Endpoint we used before - the gray rectangle - but we will tell jsPlumb that the element "aTargetDiv" will be the drop target:
    -</p>
    -		<div class="code">
    -<pre>var endpointOptions = { 
    -  isTarget:true, 
    -  maxConnections:5,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" } 
    -};
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -<p>
    -	The allowed values in 'endpointOptions' are identical for both the <strong>jsPlumb.addEndpoint</strong> and <strong>jsPlumb.makeTarget</strong> methods, but makeTarget supports an extended 
    -	anchor syntax that allows you more control over the location of the target endpoint.  This is discussed below.	
    -</p>		
    -<p>Notice in the 'endpointOptions' object above there is a 'isTarget' parameter set - this may seem incongruous, since you know you're going to make some element a target.  Remember that the endpointOptions object is the information jsPlumb will use to create an Endpoint on the given target element each time a Connection is established to it. It takes the exact same format as you would pass to addEndpoint; makeTarget is essentially a deferred addEndpoint call followed by a connect call.  So in this case, we're telling jsPlumb that any Endpoints it happens to create on some element that was configured by the makeTarget call are themselves Connection targets.
    -</p>
    -<p>makeTarget also supports the maxConnections and onMaxConnections parameters, as makeSource does, but note that onMaxConnections is passed one extra parameter than its corresponding callback from makeSource - the Connection the user tried to drop:</p>
    -		<div class="code">
    -<pre>
    -jsPlumb.makeTarget("aTargetDiv", { 
    -  isTarget:true, 
    -  maxConnections:5,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" },
    -  maxConnections:3,
    -  onMaxConnections:function(info, originalEvent) {
    -	console.log("user tried to drop connection", info.connection, "on element", info.element, "with max connections", info.maxConnections);
    -  }
    -};
    -</pre>		
    -		</div>
    -
    -<h5>Unique Endpoint per Target </h5>
    -<p>jsPlumb will create a new Endpoint using the supplied information every time a new Connection is established on the target element, by default, but you can override this behaviour and tell jsPlumb that it should create at most one Endpoint, which it should attempt to use for subsequent Connections:
    -</p>
    -		<div class="code">
    -<pre>var endpointOptions = { 
    -  isTarget:true, 
    -  uniqueEndpoint:true,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" } 
    -};
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -Here, the <strong>uniqueEndpoint</strong> parameter tells jsPlumb that there should be at most one Endpoint on this element.  Notice that 'maxConnections' is not set: the default is 1, so in this setup we have told jsPlumb that "aTargetDiv" can receive one Connection and no more.
    -<h5>Deleting Endpoints on Detach</h5>
    -By default, any Endpoints created using makeTarget have <strong>deleteEndpointsOnDetach</strong> set to true, which means that once all Connections to that Endpoint are removed, the Endpoint is deleted.  You can override this by setting the flag to true on the makeTarget call:
    -		<div class="code">
    -<pre>var endpointOptions = { 
    -  isTarget:true, 
    -  maxConnections:5,
    -  uniqueEndpoint:true,
    -  deleteEndpointsOnDetach:false,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" } 
    -};
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -In this setup we have told jsPlumb to create an Endpoint the first time "aTargetElement" is connected to, and to not delete it even if there are no longer any Connections to it.  The created Endpoint will be reused for subsequent Connections, and can support a maximum of 5.
    -
    -<h5>Detaching connections made with the mouse</h5>
    -As with jsPlumb.connect, connections made with the mouse after setting up Endpoints with one of the functions we've just covered will be, by default, detachable.  You can prevent this in the jsPlumb defaults, as previously mentioned:
    -		<div class="code">
    -<pre>jsPlumb.importDefaults({ 
    -	...
    -	ConnectionsDetachable:false,
    -	...
    -});
    -</pre>		
    -		</div>
    -
    -	And you can also set this on a per-endpoint (or source/target) level, like in these examples:
    -
    -		<div class="code">
    -<pre>jsPlumb.addEndpoint("someElementId", { 
    -	connectionsDetachable:false	
    -});
    -
    -jsPlumb.makeSource("someOtherElement", {
    -	...
    -	connectionsDetachable:false,
    -	...
    -});
    -
    -jsPlumb.makeTarget("yetAnotherElement", {
    -	...
    -	connectionsDetachable:false,
    -	...
    -});
    -</pre>		
    -		</div>
    -
    -Note that in the jsPlumb defaults, by convention each word in a parameter is capitalised ("ConnectionsDetachable"), whereas for a call to one of these methods, we use camel case ("connectionsDetachable").
    -
    -
    -<h5>Target Anchors positions with makeTarget</h5>
    -<p>When using the makeTarget method, jsPlumb allows you to provide a callback function to be used to determine
    -the appropriate location of a target anchor for every new Connection dropped on the given target.  It may be the
    -case that you want to take some special action rather than just relying on one of the standard anchor mechanisms.
    -</p>
    -<p>This is achieved through an extended anchor syntax (note that this syntax is <strong>not supported</strong> in
    -the <strong>jsPlumb.addEndpoint</strong> method) that supplies a "positionFinder" to the anchor specification.  jsPlumb 
    -provides two of these by default; you can register your own on jsPlumb and refer to them by name, or just supply a function.  Here's a few examples:</p>
    -Instruct jsPlumb to place the target anchor at the exact location at which the mouse button was released on the
    -target element. Note that you tell jsPlumb the anchor is of type "Assign", and you then provide a "position"
    -parameter, which can be the name of some position finder, or a position finder function itself:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {
    -  anchor:[ "Assign", { 
    -    position:"Fixed"
    -  }]
    -});
    -</pre>
    -</div>
    -<p><strong>Fixed</strong> is one of the two default position finders provided by jsPlumb. The other is <strong>Grid</strong>:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {
    -  anchor:[ "Assign", { 
    -    position:"Grid",
    -    grid:[3,3]
    -  }]
    -});
    -</pre>
    -</div>
    -The Grid position finder takes a "grid" parameter that defines the size of the grid required. [3,3] means
    -3 rows and 3 columns.
    -<h5>Supplying your own position finder</h5>
    -<p>To supply your own position finder to jsPlumb you first need to create the callback function. First let's take a look at what the source code for the Grid position finder looks like:</p>
    -<div class="code">
    -<pre>
    -function(eventOffset, elementOffset, elementSize, constructorParams) {
    -  var dx = eventOffset.left - elementOffset.left, dy = eventOffset.top - elementOffset.top,
    -      gx = elementSize[0] / (constructorParams.grid[0]), 
    -      gy = elementSize[1] / (constructorParams.grid[1]),
    -      mx = Math.floor(dx / gx), my = Math.floor(dy / gy);
    -
    -  return [ ((mx * gx) + (gx / 2)) / elementSize[0], ((my * gy) + (gy / 2)) / elementSize[1] ];
    -}
    -</pre>
    -</div>
    -The four arguments are:
    -<ul>
    -	<li class="bullet"><strong>eventOffset</strong> - Page left/top where the mouse button was released (a JS object containing left/top members like you get from a jQuery offset call)</li>
    -	<li class="bullet"><strong>elementOffset</strong> - JS offset object containing offsets for the element on which the Connection is to be created</li>
    -	<li class="bullet"><strong>elementSize</strong> - [width, height] array of the dimensions of the element on which the Connection is to be created</li>
    -	<li class="bullet"><strong>constructorParams</strong> - the parameters that were passed to the Anchor's constructor. In the example given above, those parameters are 'position' and 'grid'; you can pass arbitrary parameters.
    -</ul>
    -The return value of this function is an array of [x, y] - proportional values between 0 and 1 inclusive, such as you can pass to a static Anchor.
    -<p>To make your own position finder you need to create a function that takes those four arguments and returns an [x, y] position for the anchor, for example:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.AnchorPositionFinders.MyFinder = function(dp, ep, es, params) {
    -	...		
    -	return [ some maths ];	
    -};
    -</pre>
    -</div>
    -Then refer to it in a makeTarget call:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {  
    -  anchor:[ "Assign", { 
    -    position:"MyFinder",
    -    myCustomParameter:"foo",
    -    anInteger:5
    -  }]
    -});
    -</pre>
    -</div>
    -
    -<h5><a id="makeSource">jsPlumb.makeSource</a></h5>
    -There are two use cases supported by this method.  The first is the case that you want to drag a Connection from the element itself and have an Endpoint attached to the element when a Connection is established.  The second is a more specialised case: you want to drag a Connection from the element, but once the Connection is established you want jsPlumb to move it so that its source is on some other element.  For an example of this second case, check out the <a href="../demo/jquery/stateMachineDemo.html">State Machine Connectors</a> demonstration.
    -<p>Here's an example code snippet for the basic use case of makeSource:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource(someDiv, {
    -  paintStyle:{ fillStyle:"yellow" },
    -  endpoint:"Blank",
    -  anchor:"BottomCenter"
    -});
    -</pre>
    -</div>
    -<p>
    -Notice again that the second argument is the same as the second argument to an addEndpoint call.  makeSource is, essentially, a type of addEndpoint call.  In this example we have told jsPlumb that we will support dragging Connections directly from 'someDiv'.  Whenever a Connection is established between 'someDiv' and some other element, jsPlumb assigns an Endpoint at BottomCenter of 'someDiv', fills it yellow, and sets that Endpoint as the newly created Connection's source. 
    -</p>		
    -<p>Configuring an element to be an entire Connection source using makeSource means that the element cannnot itself be draggable.  There would be no way for jsPlumb to distinguish between the user attempting to drag the element and attempting to drag a Connection from the element.  If your UI works in such a way that that is acceptable then you'll be fine to use this method.  But if you want to drag Connections from some element but also have the element be draggable itself, you might want to consider the second use case.  First, a code snippet:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("someConnectionSourceDiv", {
    -  paintStyle:{ fillStyle:"yellow" },
    -  endpoint:"Blank",
    -  anchor:"BottomCenter",
    -  parent:"someDiv"
    -});
    -</pre>
    -</div>
    -<p>The only difference here is the inclusion of the 'parent' parameter.  It instructs jsPlumb to configure the source Endpoint on the element specified - in this case, "someDiv" (the value of this parameter may be a String id or a selector).</p>
    -<p>Again, I'd suggest taking a look at the <a href="../demo/jquery/stateMachineDemo.html">State Machine Connectors demonstration</a> for an example of this.</p>
    -
    -<h5>Filtering drag start based on element</h5>
    -You can supply a 'filter' parameter to the makeSource call in order to get a little more fine-grained control over which parts of the element will respond to a mousedown and start a drag. Consider this markup:
    -<div class="code">
    -<pre>
    -&lt;div id="foo"&gt;
    -  FOO
    -  &lt;button&gt;click me&lt;/button&gt;
    -&lt;/div&gt;
    -</pre>
    -</div>
    -Suppose we do not want to interfere with the operation of the 'click me' button. We can supply a filter to the makeSource call to do so:
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("foo", {
    -  filter:function(event, element) {
    -    return event.target.tagName !== "BUTTON";
    -  }
    -});
    -</pre>
    -</div>
    -If the filter returns anything other than a boolean false, the drag will begin. It's important to note that only boolean false will prevent a drag. False-y values will not.
    -
    -<h5>Endpoint options with makeSource</h5>
    -<p>
    -There are many things you can set in an Endpoint options object; for a thorough list see the API documentation for Endpoint.  
    -</p>
    -<p>Here's an example of specifying that you want an Arrow overlay halfway along any Connection dragged from this Endpoint:</p>
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -  isSource:true,
    -  connectorStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  connectorOverlays: [ [ "Arrow", { location:0.5 } ] ]
    -};
    -</pre>
    -</div>
    -This is an Endpoint that moves around the element it is attached to dependent on the location of other elements in the connections it is attached to (a 'dynamic' anchor):
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -  isSource:true,
    -  connectorStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  connectorOverlays: [ [ "Arrow", { location:0.5 } ] ]
    -  anchor:[ "TopCenter","RightMiddle","BottomCenter","LeftMiddle" ]
    -};
    -</pre>
    -</div>
    -<h5><a id="targetSourceTest">Testing if an element is a target or source</a></h5>
    -You can test if some element has been made a connection source or target using these methods:
    -<ul>
    -<li class="bullet">isTarget</li>
    -<li class="bullet">isSource</li>
    -</ul>
    -<p>The return value is a boolean.</p>
    -<h5><a id="targetSourceToggle">Toggling an element as connection target or source</a></h5>
    -You can toggle the state of some source or target using these methods:
    -<ul>
    -<li class="bullet">setTargetEnabled</li>
    -<li class="bullet">setSourceEnabled</li>
    -</ul>
    -<p>The return value from these methods is the current jsPlumb instance, allowing you to chain them:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.setTargetEnabled("aDivId").setSourceEnabled($(".aSelector"));
    -</pre>
    -</div>
    -<p>Note that if you call either of these methods on an element that was not originally configured as a target or source, nothing will happen.</p>
    -<p>You can check the enabled state of some target or source using these methods:
    -<ul>
    -<li class="bullet">isTargetEnabled</li>
    -<li class="bullet">isSourceEnabled</li>
    -</ul>
    -<h5><a id="targetSourceCancel">Canceling previous makeTarget and/or makeSource calls</a></h5>
    -jsPlumb offers four methods to let you cancel previous makeTarget or makeSource calls. Each of these methods returns the current jsPlumb instance, and so can be chained:
    -<ul>
    -<li class="bullet">unmakeTarget</li>
    -<li class="bullet">unmakeSource</li>
    -<li class="bullet">unmakeEveryTarget</li>
    -<li class="bullet">unmakeEverySource</li>
    -</ul>
    -<p>These last two are analogous to the <strong>removeEveryConnection</strong> and <strong>removeEveryEndpoint</strong> methods that have been in jsPlumb for a while now.</p>
    -<p>unmakeTarget and unmakeSource both take as argument the same sorts of values that makeTarget and makeSource accept - a string id, or a selector, or an array of either of these:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.unmakeTarget("aDivId").unmakeSource($(".aSelector"));
    -</pre>
    -</div>
    -
    -
    -<h5><a id="dragOptions">Drag Options</a></h5>
    -	These are options that will be passed through to the supporting library's drag API.  jsPlumb passes everything you supply here through, inserting
    -	wrapping functions if necessary for the various lifecycle events that jsPlumb needs to know about.  So if, for example, you pass a function to be
    -	called when dragging starts, jsPlumb will wrap that function with a function that does what jsPlumb needs to do, then call yours.
    -	<p>
    -	At the time of writing, jsPlumb supports jQuery, MooTools and YUI3, and each of those libraries uses different terminology.  In addition, jQuery's API is
    -	more fully featured, providing easy support for setting the zIndex and opacity of elements being dragged, as well as the 'scope' for a drag/drop (allowing
    -	you to specify more than one type of drag/drop pair), and hover classes for when a draggable is on the move or
    -	over a droppable. If you're using jQuery you can of course just supply these values on the dragOptions; to make it easier, jsPlumb's MooTools and YUI3 adapters recognize 
    -	these options and add appropriate callbacks for you.  
    -	</p> 
    -	<p>
    -	Given that the options here are library-specific, and they are all well-documented, we're going to discuss just the three drag options
    -	that behave the same way in all (see below for hoverClass):
    -		<ul>
    -		<li><strong>opacity</strong> - the opacity of an element that is being dragged.  Should be a fraction between 0 and 1 inclusive.</li>
    -		<li><strong>zIndex</strong> - the zIndex of an element that is being dragged.</li>
    -		<li><strong>scope</strong> - the scope of the draggable.  can only be dropped on a droppable with the same scope.  this is discussed below.</li>
    -		</ul> 
    -	</p>
    -	
    -	<p>For more information about drag options, take a look at the <a href="http://docs.jquery.com/UI/Draggable" target="_blank">jQuery</a>, <a href="http://mootools.net/docs/more/Drag/Drag.Move" target="_blank">MooTools</a>, or <a href="http://developer.yahoo.com/yui/3/dd/" target="_blank">YUI3</a> docs.	
    -	</p>
    -	<p><strong>NOTE: there is an issue in Chrome that affects the 'cursor' argument to drag options in jQuery. See these links:</strong>
    -	</p><p>
    -<a href="http://forum.jquery.com/topic/draggable-cursor-option-does-not-work" target="_blank">http://forum.jquery.com/topic/draggable-cursor-option-does-not-work</a><br/>
    -<a href="http://forum.jquery.com/topic/chrome-text-select-cursor-on-drag" target="_blank">http://forum.jquery.com/topic/chrome-text-select-cursor-on-drag</a>
    -</p>
    -You should put something like this at the top of your JS to avoid it (perhaps not something so drastic as a document-wide override):	
    -<div class="code">
    -<pre>document.onselectstart = function () { return false; };</pre>
    -</div>		
    -	
    -	<h5><a id="dropOptions">Drop Options</a></h5>
    -	Drop options are treated by jsPlumb in the same way as drag options - they are passed through to the underlying library.  MooTools does not have drop options like jQuery and YUI3 do; droppable functionality in MooTools 
    -	is actually implemented by the Drag.Move class - the one used to initialise a draggable.  But when you setup an Endpoint in jsPlumb you should ignore
    -	that fact, and treat droppables like you would in jQuery or YUI3.  jsPlumb wires everything up for you under the hood.
    -	<p>There are three jQuery droppable options that jsPlumb treats as shortcuts in MooTools and YUI3, for the sake of consistency:</p>
    -		<ul>
    -			<li><strong>hoverClass</strong> - the CSS class to attach to the droppable when a draggable is hovering over it.</li>
    -			<li><strong>activeClass</strong> - the CSS class to attach to the droppable when a draggable is, um, being dragged.</li>
    -			<li><strong>scope</strong> - the scope of the draggable.  The draggable can only be dropped on a droppable with the same scope.  This is discussed below.</li>
    -		</ul>   
    -	<p>For more information about drop options when using jQuery, see <a href="http://docs.jquery.com/UI/Droppable" target="_blank">here</a>.</p>
    -		
    -	<h5><a id="dragAndDropScope">Drag and Drop Scope</a></h5>
    -		jsPlumb borrowed the concept of 'scope' from jQuery's drag/drop implementation: the notion of
    -		which draggables can be dropped on which droppables.  In jsPlumb you can provide a 'scope' entry when 
    -		creating an Endpoint.  Here's the example grey Endpoint example with 'scope' added:
    -		
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:"#666" },
    -  isSource:true,
    -  connectionStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  scope:"exampleGreyConnection"
    -};</pre>		
    -</div>  		
    -  										
    -	If you do not provide a 'scope' entry, jsPlumb uses a default scope.  Its value is accessible through this method:
    -	<div class="code">
    -<pre>
    -jsPlumb.getDefaultScope();
    -</pre>	
    -	</div>				
    -	If you want to change it for some reason you can do so with this method:
    -	<div class="code">
    -<pre>
    -jsPlumb.setDefaultScope("mySpecialDefaultScope");
    -</pre>	
    -	</div>			
    -	You can also, should you want to, provide the scope value separately on the drag/drop options, like this:
    -	<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -  isSource:true,
    -  connectionStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  dragOptions:{ scope:"dragScope" },
    -  dropOptions:{ scope:"dropScope" }
    -};</pre>		
    -</div>
    -	
    -	<h5><a id="disconnectReconnect">Disconnecting and Reconnecting</a></h5>
    -	<p>
    -	  By default, jsPlumb will allow you to detach connections from either Endpoint by dragging (assuming the Endpoint allows it;
    -	  Blank Endpoints, for example, have nothing you can grab with the mouse). If you then drop a Connection you have dragged
    -	  off an Endpoint, the Connection will be detached. This behaviour can be controlled using the
    -	  <strong>detachable</strong> and <strong>reattach</strong>parameters, or their equivalents in the jsPlumb Defaults.
    -	  Some examples should help explain:	  
    -	</p>
    -Create a Connection that is detachable using the mouse, from either Endpoint, and which does not reattach:
    -	<div class="code">
    -<pre>
    -jsPlumb.connect({ source:"someElement", target:"someOtherElement" });
    -</pre>		
    -</div>
    -	Create a Connection that is NOT detachable using the mouse:
    -	<div class="code">
    -<pre>
    -jsPlumb.connect({ source:"someElement", target:"someOtherElement", detachable:false });
    -</pre>		
    -</div>
    -Create a Connection that is detachable using the mouse and which reattaches on drop:
    -	<div class="code">
    -<pre>
    -jsPlumb.connect({ source:"someElement", target:"someOtherElement", reattach:true });
    -</pre>		
    -</div>
    -	
    -This behaviour can also be controlled using jsPlumb defaults:
    -<div class="code">
    -<pre>
    -jsPlumb.importDefaults({
    -  ConnectionsDetachable:true,
    -  ReattachConnections:true
    -});
    -</pre>		
    -</div>
    -The default value of ConnectionsDetachable is <strong>true</strong>, and the default value of ReattachConnections is <strong>false</strong>,
    -so in actual fact those defaults are kind of pointless. But you probably get the point.
    -
    -<h5>Setting detachable/reattach on Endpoints</h5>
    -Endpoints support the 'detachable' and 'reattach' parameters too. If you create an Endpoint and mark 'detachable:false', then
    -all Connections made from that Endpoint will not be detachable.  However, since there are two Endpoints involved in any
    -Connection, jsPlumb takes into account the 'detachable' and 'reattach' parameters from both Endpoints when establishing
    -a Connection. If either Endpoint declares either of these values as true, jsPlumb assumes the value to be true.  It is possible
    -that in a future version of jsPlumb the concepts of detachable and reattach could be made more granular, through the
    -introduction of parameters like <strong>sourceDetachable</strong>/<strong>targetReattach</strong> etc.
    -
    -<h5>Dropping a dragged Connection on another Endpoint</h5>
    -If you drag a Connection from its target Endpoint you can then drop it on another suitable target Endpoint - suitable meaning
    -that it is of the correct scope and it is not full.  If you try to drop a Connection on another target that is full, the
    -drop will be aborted and then the same rules will apply as if you had dropped the Connection in whitespace: if 'reattach' is
    -set, the Connection will reattach, otherwise it will be removed.
    -<p>
    -  You can drag a Connection from its source Endpoint, but you can only drop it back on its source Endpoint - if you try to drop
    -  it on any other source or target Endpoint jsPlumb will treat the drop as if it happened in whitespace. Note that there is
    -  an issue with the hoverClass parameter when dragging a source Endpoint: target Endpoints are assigned the hover class, as if
    -  you could drop there. But you cannot; this is caused by how jsPlumb uses the underlying library's drag and drop, and is
    -  something that will be addressed in a future release.
    -</p>
    -	
    -	</div>	
    -
    -	<div class="section">
    -		<h3><a id="parameters">Connection Parameters</a></h3>
    -		jsPlumb has a mechanism that allows you to set parameters on a per-connection basis.  This can be achieved in a few different ways:
    -
    -		<ul>
    -			<li class="bullet">Providing parameters to a jsPlumb.connect call</li>
    -			<li class="bullet">Providing parameters to an addEndpoint call to/from which a connection is subsequently established using the mouse</li>
    -			<li class="bullet">using the <strong>setParameter</strong> or <strong>setParameters</strong> method on a Connection</li>
    -		</ul>
    -		Getting parameters is achieved through either the <strong>getParameter(key)</strong> or <strong>getParameters</strong> method on a Connection.
    -		<h4><a id="connectParameters">jsPlumb.connect</a></h4>
    -		Parameters can be passed in via an object literal to a jsPlumb.connect call:
    -		<div class="code">
    -<pre>var myConnection = jsPlumb.connect({
    -	source:"foo",
    -	target:"bar",
    -	parameters:{
    -		"p1":34,
    -		"p2":new Date(),
    -		"p3":function() { console.log("i am p3"); }
    -	}
    -});
    -</pre>
    -</div>
    -Note that they can be any valid Javascript objects - it's just an object literal.  You then access them like this:
    -		<div class="code">
    -<pre>myConnection.getParameter("p3")();     // prints 'i am p3' to the console.
    -</pre>
    -</div>
    -
    -<h4><a id="addEndpointParameters">jsPlumb.addEndpoint</a></h4>
    -<p>The information in this section also applies to the <strong>makeSource</strong> and <strong>makeTarget</strong> functions</p>
    -<p>Using jsPlumb.addEndpoint, you can set parameters that will be copied in to any Connections that are established from or to the given Endpoint using the mouse.  (If you set parameters on both a source and target Endpoints and then connect them, the parameters set on the target Endpoint are copied in first, followed by those on the source. So the source Endpoint's parameters take precedence if they happen to have one or more with the same keys as those in the target).</p>
    -Consider this example:
    -		<div class="code">
    -<pre>var e1 = jsPlumb.addEndpoint("d1", {
    -	isSource:true,
    -	parameters:{
    -		"p1":34,
    -		"p2":new Date(),
    -		"p3":function() { console.log("i am p3"); }
    -	}
    -});
    -
    -var e2 = jsPlumb.addEndpoint("d2", {
    -	isTarget:true,
    -	parameters:{
    -		"p5":343,
    -		"p3":function() { console.log("FOO FOO FOO"); }
    -	}
    -});
    -
    -var conn = jsPlumb.connect({source:e1, target:e2});
    -
    -
    -</pre>
    -</div>
    -
    -'conn' will have four parameters set on it, with the value for "p3" coming from the source Endpoint:
    -		<div class="code">
    -<pre>var params = conn.getParameters();
    -console.log(params.p1);   	// 34
    -console.log(params.p2);   	// Mon May 14 2012 12:57:12 GMT+1000 (EST) (or however your console prints out a Date)
    -console.log((params.p3)()); // "i am p3"  (note: we executed the function after retrieving it)
    -console.log(params.p5);   	// 343
    -
    -</pre>
    -</div>
    -
    -
    -</div> <!-- /section -->
    -
    -<div class="section">
    -	<h3><a id="connectionTypes">Connection and Endpoint Type</a></h3>
    -	<p>
    -	jsPlumb supports the notion of "type" for both Connections and Endpoints. A type is a
    -	collection of attributes such as paint style, hover paint style, overlays etc - it is a subset, including most but not all, of the
    -	parameters you can set in an Endpoint or Connection definition. 
    -	</p>
    -	<p>
    -	An Endpoint or Connection can have zero or more types assigned; they are merged as granularly as possible, in the order
    -	in which they were assigned. There is a supporting API that works in the same way as the class stuff does in
    -	jQuery - hasType, addType, removeType, toggleType, setType, and each of these methods (except hasType) takes a space-separated
    -	string so you can add several at once.  Support for these methods has been added to the jsPlumb.select and
    -	jsPlumb.selectEndpoint methods, and you can also now specify a 'type' parameter to an Endpoint or Connection at create time.
    -	</p>
    -	<h4>Connection Type</h4>
    -	Probably the easiest way to explain types is with some code. In this snippet, we'll register a Connection type on jsPlumb,
    -	create a Connection, and then assign the type to it:
    -<div class="code">
    -<pre>
    -jsPlumb.registerConnectionType("example", {
    -	paintStyle:{ strokeStyle:"blue", lineWidth:5  },
    -	hoverPaintStyle:{ strokeStyle:"red", lineWidth:7 }
    -});
    -
    -var c = jsPlumb.connect({ source:"someDiv", target:"someOtherDiv" });
    -c.bind("click", function() {
    -	c.setType("example");
    -});	
    -</pre>		
    -</div>
    -<p>Another example - a better one, in fact.  Say you have a UI in which you can click to select or deselect Connections, and
    -you want a different appearance for each state.  Connection types to the rescue!</p>
    -<div class="code">
    -<pre>
    -jsPlumb.registerConnectionTypes({
    -	"basic": {
    -		paintStyle:{ strokeStyle:"blue", lineWidth:5  },
    -		hoverPaintStyle:{ strokeStyle:"red", lineWidth:7 }
    -	},
    -	"selected":{
    -		paintStyle:{ strokeStyle:"red", lineWidth:5 },
    -		hoverPaintStyle:{ lineWidth: 7 }
    -	}	
    -});
    -
    -var c = jsPlumb.connect({ source:"someDiv", target:"someOtherDiv", type:"basic" });
    -
    -c.bind("click", function() {
    -	c.toggleType("selected");
    -});	
    -</pre>		
    -</div>
    -<p>Notice here how we used a different method - <strong>registerConnectionTypes</strong> - to register a few types at once.
    -Notice also the hoverPaintStyle for the 'selected' type: it declares only a lineWidth.  As mentioned above, types are
    -merged with as much granularity as possible, so that means that in this case the lineWidth from 'selected' will be merged
    -into the hoverPaintStyle from 'basic', and voila, red, 7 pixels.</p>
    -<p>These examples, of course, use the <strong>jsPlumb.connect</strong> method, but in many UIs Connections are created
    -via drag and drop.  How would you assign that 'basic' type to a Connection created with drag and drop? Provide it as
    -the Endpoint's connectorType parameter, like so:</p>
    -<div class="code">
    -<pre>
    -	var e1 = jsPlumb.addEndpoint("someDiv", {
    -		connectorType:"basic",
    -		isSource:true
    -	});
    -	
    -	var e2 = jsPlumb.addEndpoint("someOtherDiv", {
    -		isTarget:true
    -	});
    -		
    -	//... user then perhaps drags a connection...or we do it programmatically:
    -	
    -	var c = jsPlumb.connect({ source:e1, target:e2 });
    -	
    -	// now c has type 'basic'
    -	console.log(c.hasType("basic));   // -> true
    -</pre>	
    -</div>
    -<p>Note that the second Endpoint we created did not have a 'connectorType' parameter - we didn't need it, as the source
    -Endpoint in the Connection had one.  But we could have supplied one, and jsPlumb will use it, but only if the source
    -Endpoint has not declared connectorType.  This is the same way jsPlumb treats other connector parameters such as
    -paintStyle etc - the source Endpoint wins.</p>
    -<p>Here's a few examples showing you the full type API:</p>
    -<div class="code">
    -<pre>
    -	jsPlumb.registerConnectionTypes({
    -		"foo":{ paintStyle:{ strokeStyle:"yellow", lineWidth:5 } },
    -		"bar":{ paintStyle:{ strokeStyle:"blue", lineWidth:10 } },
    -		"baz":{ paintStyle:{ strokeStyle:"green", lineWidth:1 } }
    -	});
    -	
    -	var c = jsPlumb.connect({ source:"someDiv", target:"someOtherDiv", type:"foo" });
    -	
    -	// see what types the connection has.  
    -	console.log(c.hasType("foo"));  // -> true
    -	console.log(c.hasType("bar"));  // -> false
    -	
    -	// add type 'bar'
    -	c.addType("bar");
    -	
    -	// toggle both types (they will be removed in this case)
    -	c.toggleType("foo bar");
    -	
    -	// toggle them back
    -	c.toggleType("foo bar");
    -	
    -	// getType returns a list of current types.
    -	console.log(c.getType()); // -> [ "foo", "bar" ]
    -	
    -	// set type to be 'baz' only
    -	c.setType("baz");
    -	
    -	// add foo and bar back in
    -	c.addType("foo bar");
    -	
    -	// remove baz and bar
    -	c.removeType("baz bar");
    -	
    -	// what are we left with? good old foo.
    -	console.log(c.getType()); // -> [ "foo" ]
    -	
    -</pre>	
    -</div>
    -<p>Things to note here are that every method <strong>except hasType</strong> can take a space-delimited list of types
    -to work with. So types work like CSS classes, basically.</p>
    -<h5>Supported Parameters in Connection Type objects</h5>
    -Not every parameter from a Connection's constructor is supported in a Connection Type - as mentioned above, types act pretty
    -much like CSS classes, so the things are supported are related to behaviour or appearance. For instance, 'source' is
    -not supported.  You cannot make a Connection Type that is fixed to a specific source element. Here's the full list of
    -supported properties in Connection Type objects:
    -<ul>
    -	<li class="bullet"><strong>detachable</strong> - whether or not the Connection is detachable using the mouse</li>
    -	<li class="bullet"><strong>paintStyle</strong></li>
    -	<li class="bullet"><strong>hoverPaintStyle</strong></li>
    -	<li class="bullet"><strong>scope</strong> - remember, Connections support a single scope. So if you have multiple types applied, you will get the scope from the last type that defines one.</li>
    -	<li class="bullet"><strong>parameters</strong> - when you add/set a type that has parameters, any existing parameters with the same keys will be overwritten. When you remove a type that has parameters, its parameters are NOT removed from the Connection.</li>
    -	<li class="bullet"><strong>overlays</strong> - when you have multiple types applied to a Connection, you get the union of
    -	all the Overlays defined across the various types. <strong>Note</strong> when you create a Connection using jsPlumb.connect and you
    -	provide a 'type', that is equivalent to calling 'addType': you will get the Overlays defined by the type(s) you set as
    -	well as any others you have provided to the constructor.</li>	
    -</ul>
    -
    -<h4><a id="endpointTypes">Endpoint Type</a></h4>
    -Endpoints can also be assigned one or more types, both at creation and programmatically using the API discussed above.
    -The only real differences between Endpoint and Connection types are the allowed parameters.  Here's the list for Endpoints:
    -<ul>
    -	<li class="bullet"><strong>paintStyle</strong></li>
    -	<li class="bullet"><strong>hoverPaintStyle</strong></li>
    -	<li class="bullet"><strong>maxConnections</strong></li>
    -	<li class="bullet"><strong>connectorStyle</strong> - paint style for any Connections that use this Endpoint.</li>
    -	<li class="bullet"><strong>connectorHoverStyle</strong> - hover paint style for Connections from this Endpoint.</li>
    -	<li class="bullet"><strong>connector</strong> - a Connector definition, like "StateMachine", or [ "Flowchart", { stub:50 } ] </li>
    -	<li class="bullet"><strong>connectionType</strong> - This allows you to specify the Connection Type for Connections made from this Endpoint.</li>
    -	<li class="bullet"><strong>scope</strong> - remember, Endpoints support a single scope. So if you have multiple types applied, you will get the scope from the last type that defines one.</li>
    -	<li class="bullet"><strong>parameters</strong> - when you add/set a type that has parameters, any existing parameters with the same keys will be overwritten. When you remove a type that has parameters, its parameters are NOT removed from the Connection.</li>
    -	<li class="bullet"><strong>overlays</strong> - when you have multiple types applied to an Endpoint, you get the union of all the Overlays defined across the various types.</li>
    -	
    -</ul>
    -<p>One thing to be aware of is that the parameters here that are passed to Connections are only passed from a source Endpoint,
    -not targets.</p>
    -Here's an example of using Endpoint types:
    -<div class="code">
    -<pre>
    -  jsPlumb.registerEndpointTypes({
    -    "basic":{			
    -      paintStyle:{fillStyle:"blue"}
    -    },
    -    "selected":{			
    -      paintStyle:{fillStyle:"red"}
    -    }
    -  });
    -
    -  var e = jsPlumb.addEndpoint("someElement", {
    -    anchor:"TopMiddle",
    -	type:"basic"
    -  });
    -  
    -  e.bind("click", function() {
    -    e.toggleType("selected");
    -  });
    -</pre>
    -</div>
    -So it works the same way as Connection Types.  There are several parameters allowed by an Endpoint Type that affect Connections
    -coming from that Endpoint. Note that this does not affect existing Connections.  It affects only Connections that are
    -created after you set the new type(s) on an Endpoint.
    -
    -<h4>Fluid Interface</h4>
    -As mentioned previously, all of the type operations are supported by the <strong>select</strong> and <strong>selectEndpoints</strong> methods.
    -So you can now do things like this:
    -<div class="code">
    -<pre>
    -	jsPlumb.selectEndpoints({scope:"terminal"}).toggleType("active");
    -	
    -	jsPlumb.select({source:"someElement"}).addType("highlighted available");
    -	
    -	etc
    -	
    -</pre>
    -</div>
    -Obviously, in these examples, 'available' and 'highlighted' would have previously been registered on jsPlumb using the appropriate
    -register methods.
    -
    -</div> <!-- / connection/endpoint types	-->
    -	
    -	<div class="section">
    -		<h3><a id="beforeDrop">Interceptors</a></h3>
    -		<p>
    -		Interceptors were new in jsPlumb 1.3.4.  They are basically event handlers from which you can return a value that tells jsPlumb to abort what it is that
    -		it was doing.  There are currently two interceptors supported - <strong>beforeDrop</strong>, which is called when the user has dropped a Connection onto some target, 
    -		and <strong>beforeDetach</strong>, which is called when the user is attempting to detach a Connection. 
    -		</p>
    -		<p>Interceptors can be registered via the <strong>bind</strong> method on jsPlumb just like any other event listeners, and they can also be passed in to
    -		the <strong>jsPlumb.addEndpoint</strong>, <strong>jsPlumb.makeSource</strong> and <strong>jsPlumb.makeTarget</strong>
    -		methods.  A future version of jsPlumb will support registering interceptors on Endpoints using the <strong>bind</strong> method as you can now with the jsPlumb object.
    -		Note that binding 'beforeDrop' (as an example) on jsPlumb itself is like a catch-all: it will be called every time a Connection is dropped on _any_ 
    -		Endpoint.  But passing a 'beforeDrop' callback into an Endpoint (and in the future, registering one using 'bind') constrains that callback to just the
    -		Endpoint in question.  		
    -		<h4><a id="beforeDrop">beforeDrop</a></h4>
    -		This event is fired when a new or existing connection has been dropped. Your callback is passed a JS object with these fields:
    -		<ul>
    -			<li><strong>sourceId</strong> - the id of the source element in the connection</li>
    -			<li><strong>targetId</strong> - the id of the target element in the connection</li>
    -			<li><strong>scope</strong> - the scope of the connection</li>
    -			<li><strong>connection</strong> - the actual Connection object.  You can access the 'endpoints' array in a Connection to get the Endpoints involved in the Connection, but be aware that when a Connection is being dragged, one of these Endpoints will always be a transient Endpoint that exists only for the life of the drag. To get the Endpoint on which the Connection is being dropped, use the 'dropEndpoint' member.</li>
    -			<li><strong>dropEndpoint</strong> - this is the actual Endpoint on which the Connection is being dropped.  This <strong>may be null</strong>, because it will not be set if the Connection is being dropped on an element on which makeTarget has been called. </li>
    -		</ul>
    -
    -		If you return false (or nothing) from this callback, the new Connection is aborted and removed from the UI.
    -		<h4><a id="beforeDetach">beforeDetach</a></h4>
    -		This is called when the user has detached a Connection, which can happen for a number of reasons: by default, jsPlumb allows users to drag Connections off of target Endpoints, but this can also result from a programmatic 'detach' call.  Every case is treated the same by jsPlumb, so in fact it is possible for you to write code that attempts to detach a Connection but then denies itself!  You might want to be careful with that. 
    -		<p>Note that this interceptor is passed the actual Connection object; this is different from the beforeDrop
    -		interceptor discussed above: in this case, we've already got a Connection, but with beforeDrop we are yet 
    -		to confirm that a Connection should be created.</p>
    -		<p>Returning false - or nothing - from this callback will cause the detach to be abandoned, and the Connection will be reinstated or left on its current target.</p>
    -			
    -	</div>
    -
    -		<div class="section">
    -			<h3><a id="paintstyles">Paint Styles</a></h3>
    -			Defining the appearance of Connectors, Endpoints (and Overlays, but this is deprecated in favour of using CSS classes) is achieved through a 'paintStyle' (or a quite similar name)
    -			object passed as a parameter to one of jsPlumb.connect, jsPlumb.addEndpoint, jsPlumb.makeSource or jsPlumb.makeTarget. Depending on the method you are calling, the parameter names vary.
    -			<h5>Connector Paint Styles</h5>
    -			These are specified in a <em>paintStyle</em> parameter on a call to <strong>jsPlumb.connect</strong>:
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			or in the <em>connectorPaintStyle</em> parameter on a call to <strong>jsPlumb.addEndpoint</strong> or <strong>jsPlumb.makeSource</strong>:
    -			<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div> 			
    -			 
    -
    -Notice the <em>paintStyle</em> parameter in those examples: it is the paint style for the Endpoint, which we'll discuss below.
    -
    -			<h5>Endpoint Paint Styles</h5>
    -			These are specified in a <em>paintStyle</em> parameter on a call to <strong>addEndpoint</strong>. This is the example from just above:
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -</div> 
    -			...or as the <em>endpointStyle</em> parameter to a <strong>connect</strong> call:		
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			... or as an entry in the <em>endpointStyles</em> array passed to a <strong>jsPlumb.connect</strong> call:
    -					<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  endpointStyles:[ 
    -    { fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -    { fillStyle:"green" }
    -  ],
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			or as the <em>paintStyle</em> parameter passed to a <strong>makeTarget</strong> or <strong>makeSource</strong> call:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("el1", {
    -  ...
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  ...
    -});
    -</pre>			
    -</div>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 }
    -  parent:"someOtherDivIJustPutThisHereToRemindYouYouCanDoThis"
    -});
    -</pre>			
    -			</div>						
    -	In the first example we  made "el1" into a drop target, and defined a paint style for the Endpoint jsPlumb will create when a Connection is
    -	established.  In the second we made "el1" a connection source and assigned the same values for the Endpoint jsPlumb will create when a Connection
    -	is dragged from that element.
    -	
    -	<h5>Overlay Paint Styles</h5>
    -	The preferred way to set paint styles for Overlays is to use the <em>cssClass</em> parameter in the constructor arguments of an Overlay definition.
    -	
    -	<h4>Paint Style Parameters</h4>
    -	This is the full list of parameters you can set in a paintStyle object, but note that <strong>fillStyle</strong> is ignored by Connectors,
    -	and <strong>strokeStyle</strong> is ignored by Endpoints.  Also, if you create a Connection using jsPlumb.connect and do not specify any
    -	Endpoint styles, the Endpoints will derive their fillStyle from the Connector's strokeStyle.
    -	<p>
    -		fillStyle, strokeStyle and outlineColor can be specified using any valid CSS3 syntax.
    -	</p>
    -	<ul>
    -		<li><strong>fillStyle</strong> - color for an Endpoint, eg. rgba(100,100,100,50), "blue", "#456", "#993355", rgb(34, 56, 78).</li>
    -		<li><strong>strokeStyle</strong> - color for a Connector. see fillStyle examples.</li>
    -		<li><strong>lineWidth</strong> - width of a Connector's line. An integer.</li>
    -		<li><strong>outlineWidth</strong> - width of the outline for an Endpoint or Connector. An integer.</li>
    -		<li><strong>outlineColor</strong> - color of the outline for an Endpoint or Connector. see fillStyle examples.</li>
    -		<li><strong>dashstyle</strong> - VML and SVG only. This comes from VML, and allows you to create dashed or dotted lines.  It has a 
    -			better syntax than the equivalent attribute in SVG (<em>stroke-dasharray</em>, discussed below), so jsPlumb supports this for both renderers. 
    -			The <em>dashstyle</em> attribute is specified as an array of strokes and spaces, where each value is some multiple of <em>the width of 
    -			the Connector</em>, and that's where it's better than SVG, which uses pixels.
    -			<p>
    -			<a href="http://www.w3.org/TR/NOTE-VML" target="_blank">The VML spec</a> is a good place to find valid values for dashstyle. Note that
    -			jsPlumb does not support the string values for this attribute ("solid", "dashdot", etc).
    -			</p>
    -			<p>
    -			In SVG render mode, jsPlumb uses the <em>lineWidth</em> parameter in conjunction with the values in a <em>dashstyle</em> attribute
    -			to create an appropriate value for <em>stroke-dasharray</em>.
    -			</p>
    -		</li>
    -		<li><strong>stroke-dasharray</strong> - SVG only. This is the SVG equivalent of <em>dashstyle</em>.  
    -		<a href="http://www.w3.org/TR/SVG/painting.html" target="_blank">The SVG spec</a> discusses valid values for this parameter.  But be aware
    -		that jsPlumb does not convert this into a valid <em>dashstyle</em> attribute when using the VML renderer. Better to use <em>dashstyle</em>.
    -		</li>
    -		<li><strong>stroke-dashoffset</strong> - SVG only.  This is used in SVG to specify how far into the dash pattern to start painting.  For more information,
    -		see <a href="http://www.w3.org/TR/SVG/painting.html" target="_blank">the SVG spec.</a></li>
    -		<li><strong>joinstyle</strong> - VML and SVG only.  As with <em>dashstyle</em>, this is a VML attribute that jsPlumb supports for both VML
    -		and SVG - jsPlumb turns this into a <em>stroke-linejoin</em> attribute when rendering with SVG.  This attribute specifies how you want individual
    -		segments of connectors to be joined; the VML and SVG specs both have examples of this, of which many are the same between the two, which is
    -		why jsPlumb will automatically convert this attribute into the SVG equivalent.
    -		</li>
    -		<li><strong>stroke-linejoin</strong> - SVG only.  This is the equivalent of VML's <em>joinstyle</em> attribute, but as with <em>stroke-dasharray</em>,
    -		jsPlumb does not convert this into something approriate for VML.  So, using <em>joinstyle</em> will enable you to support more browsers with
    -		less effort.		
    -		</li>
    -	</ul>
    -	
    -	<h3><a id="hoverpaintstyles">Hover Paint Styles</a></h3>
    -	 		
    -	 		Connectors and Endpoints both support the concept of a "hover" paint style - a paint style to use when the mouse is hovering over
    -	 		the component.  These are specified in the exact same format as paint styles discussed above, but hover paint styles also inherit
    -	 		any values from the main paint style.  This is because you will typically want to just change the color, or perhaps outline color, of
    -	 		a Connector or Endpoint when the mouse is hovering, but leave everything else the same.  So having hover paint styles inherit their
    -	 		values precludes you from having to define things in more than one place.
    -	 		<p>
    -	 			The naming convention adopted for hover paint styles is pretty much to insert the word 'hover' into the corresponding main paint
    -	 			style parameters.  Here are a couple of examples:
    -	 		</p> 
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -  hoverPaintStyle:{ strokeStyle:"red" },
    -  endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  endpointHoverStyle:{ fillStyle:"red" }
    -});
    -</pre>			
    -</div>
    -			In this example we specified a hover style for both the Connector, and each of its Endpoints.  Here's the same thing, but using the plural version, to specify a different hover style for each Endpoint:	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -  hoverPaintStyle:{ strokeStyle:"red" },
    -  endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  endpointHoverStyles:[ { fillStyle:"red" }, { fillStyle:"yellow" } ]
    -});
    -</pre>			
    -</div>
    -	 		Calls to <strong>addEndpoint</strong>, <strong>makeSource</strong> and <strong>makeTarget</strong> can also specify various hover paint styles:
    -	 								<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  hoverPaintStyle:{ fillStyle:"red" },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -  connectorHoverPaintStyle:{ strokeStyle:"red", outlineColor:"yellow", outlineWidth:1 }
    -});
    -
    -jsPlumb.makeSource("el2", {
    -  paintStyle:{ fillStyle:"transparent", outlineColor:"yellow", outlineWidth:1 },
    -  hoverPaintStyle:{ fillStyle:"red" },
    -  connectorPaintStyle:{ strokeStyle:"green", lineWidth:3 },
    -  connectorHoverPaintStyle:{ strokeStyle:"#678", outlineColor:"yellow", outlineWidth:1 }
    -});
    -
    -jsPlumb.makeTarget("el3", {
    -  paintStyle:{ fillStyle:"transparent", outlineColor:"yellow", outlineWidth:1 },
    -  hoverPaintStyle:{ fillStyle:"red" }
    -});
    -</pre>			
    -			</div> 
    -	 		
    -	 		In these examples we specified a hover paint style for both the Endpoint we are adding, and any Connections to/from the Endpoint.
    -	 		<p>	
    -		Note that <strong>makeTarget</strong> does not support Connector parameters. It is for creating targets only; Connector parameters will be set by the source Endpoint in any Connections that are made to the element that you turned into a target by using this method.</p>
    -	 			 		
    -			<h4><a id="gradients">Gradients</a></h4>
    -			The Canvas and SVG renderers both support gradients. The VML renderer does not.  jsPlumb uses its own syntax to define gradients, to abstract out the differences between the syntax required by canvas and that required by SVG.  
    -			<p>There are two types of gradients available - a 'linear' gradient, which consists of colored lines all
    -			going in one direction, and a 'radial' gradient, which consists of colored circles emanating from one circle to another.
    -			Because of their basic shape, jsPlumb supports only <em>linear</em> gradients for
    -			Connectors.  But for Endpoints, jsPlumb supports both <em>linear</em> and <em>radial</em> gradients.</p>
    -			
    -				<h5>Connector gradients</h5>
    -				To specify a linear gradient to use in a Connector, you must add a <em>gradient</em> object to your
    -				Connector's <em>paintStyle</em>, for instance:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : "window2",
    -	target : "window3",
    -	paintStyle:{
    -		gradient:{
    -			stops:[[0,"green"], [1,"red"]]
    -		},
    -		lineWidth:15
    -	}
    -});
    -</pre>
    -</div>
    -Here we have connected window2 to window3 with a 15 pixel wide connector that has a gradient from green to red.</p><p> Notice the <strong>gradient</strong> object and the
    -<strong>stops</strong> list inside it - the gradient consists of an arbitrary number of these "color stops".  Each color stop is comprised
    -of two values - [position, color].  Position must be a decimal value between 0 and 1 (inclusive), and indicates where the color
    -stop is situated as a fraction of the length of the entire gradient.  Valid values for
    -the colors in the <strong>stops</strong> list are the same as those that are valid for <strong>strokeStyle</strong> when describing a color.
    -			</p>
    -			As mentioned, the <strong>stops</strong> list can hold an arbitrary number of entries.  Here's an example of a gradient that goes from red to blue to green, and back again through blue to red:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle : {
    -		gradient:{
    -			stops:[[0,'red'], [0.33,'blue'], [0.66,'green'], [0.33,'blue'], [1,'red']]
    -		},
    -		lineWidth : 15
    -	}
    -});
    -</pre>
    -</div>
    -<strong>Note:</strong> when using the VML renderer, jsPlumb will simply ignore the gradient directive
    -so it is best to ensure you also supply a <em>strokeStyle</em> in your paintStyle object, to give jsPlumb something to fall back on.  If you do not supply
    -a <em>strokeStyle</em> your Connectors will be painted black.  The previous example might look like this, for instance:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle:{
    -		strokeStyle:'red',
    -		gradient:{
    -			stops:[[0,'red'], [0.33,'blue'], [0.66,'green'], [0.33,'blue'], [1,'red']]
    -		},
    -		lineWidth:15
    -	}
    -});
    -</pre>
    -</div>
    -Notice the <strong>strokeStyle:'red'</strong> directive at the beginning of the parameter list in <strong>paintStyle</strong>.
    -			<h5>Endpoint gradients</h5>
    -			Endpoint gradients are specified using the same syntax as Connector gradients.  You put the gradient specifier either in the
    -			<strong>endpoint</strong> member, or if you are specifying different Endpoints for each end of the Connector, in one or both of
    -			the values in the <strong>endpoints</strong> array.  Also, this information applies to the case that you are creating standalone
    -			Endpoints that you will be configuring for drag and drop creation of new Connections. 
    -			<p>
    -			This is an example of an Endpoint gradient that is different for each Endpoint in the Connector.  This comes from the main demo; it is
    -			the Connector joining Window 2 to Window 3:
    -			</p>
    -<div class="code">
    -<pre>var w23Stroke = 'rgb(189,11,11)';
    -jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle:{
    -		lineWidth:8,
    -		strokeStyle:w23Stroke
    -	},
    - 	anchors:[ [0.3,1,0,1], "TopCenter" ],
    - 	endpoint:"Rectangle",
    - 	endpointStyles:[
    - 		{ gradient : {stops:[[0, w23Stroke], [1, '#558822']] } },
    -    	{ gradient : {stops:[[0, w23Stroke], [1, '#882255']] } }
    -    ]
    -});
    -</pre>
    -</div>
    -The first entry in the gradient will be the one that is on the Connector end of the Endpoint.  You can of course have as many color stops as
    -you want in this gradient, just like with Connector gradients.
    -	<h5>Applying the gradient in Endpoints</h5>
    -	Only the Dot and Rectangle endpoints honour the presence of a gradient (and, remember, not in VML). The Image endpoint of course ignores a gradient
    -	as it does no painting of its own.
    -	<p>The type of gradient you will see depends on the Endpoint type:</p>
    -		<ul>
    -			<li><strong>Dot</strong> - renders a radial endpoint, with color stop 0 on the outside, progressing inwards as we move through color stops.
    -			<p>Radial gradients actually require more data than linear gradients - in a linear gradient we just move from one point to another, whereas
    -			in a radial gradient we move from one <em>circle</em> to another.  By default, jsPlumb will render a radial gradient using a source
    -			circle of the same radius as the Endpoint itself, and a target circle of 1/3 of the radius of the Endpoint (both circles share the
    -			same center as the Endpoint itself). This circle will be offset by radius/2 in each direction.</p>
    -			<p>You can supply your own values for these inside the gradient descriptor:</p>
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect({
    -	source : 'window3',
    -	target : 'window4',
    -    paintStyle:{
    -    	lineWidth:10,
    -    	strokeStyle:w34Stroke
    -    },
    -    anchors:[ "RightMiddle", "LeftMiddle" ],
    -    endpointStyle:{
    -    	gradient : {
    -    		stops:[ [0, w34Stroke], [1, w34HlStroke] ],
    -    		offset:37.5,
    -    		innerRadius:40
    -    	},
    -    	radius:55
    -    }
    - });
    -</pre>
    -</div>
    -Here we have instructed jsPlumb to make the gradient's inner radius 10px instead of the default 25/3 = 8 ish pixels, and the offset in each direction
    -will be 5px, instead of the default radius / 2 = 12.5 pixels.
    -<p>It is also possible to specify the offset and inner radius as percentages - enter the values as strings with a '%' symbol on the end:</p>
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect({
    -	source : 'window3', 
    -	target : 'window4',
    -    paintStyle:{
    -    	lineWidth:10,
    -    	strokeStyle:w34Stroke
    -    },
    -	anchors:[ "RightMiddle", "LeftMiddle" ],
    -	endpointStyle:{
    -		gradient : {
    -			stops:[ [0, w34Stroke], [1, w34HlStroke] ],
    -			offset:'68%',
    -			innerRadius:'73%'
    -		},
    -		radius:25
    -	}
    -});
    -</pre>
    -</div>
    -This will give roughly the same output as the example above (the percentages are not entirely exact).<br/><br/>
    -			</li>
    -			<li><strong>Rectangle</strong> - renders a linear endpoint, with color stop 0 closest to the end of the Connector</li>
    -		</ul>
    -		
    -<h4><a id="svgvmlcss">Styling SVG/VML using CSS</a></h4>
    -	A nice way of controlling your UI's appearance is to make use of the fact that both SVG and VML can be styled using CSS.
    -	<p>This section will be expanded in the next few weeks to give some decent examples of how to do so</p>
    -			
    -		
    -		
    -		</div>
    -
    -		<div class="section">
    -			<h3><a id="cssclasses">CSS</a></h3>
    -			In an ideal world, you'd be able to control the appearance of your UI using CSS only, but due to difference between
    -			renderers, this is not the case.  The Canvas renderer, for instance, is completely unaware of CSS, and VML can
    -			only be styled up to a point with CSS.  SVG behaves best, and perhaps at some stage in the future when all browsers
    -			in popular usage support SVG decently, we'll be able to switch over completely to CSS.
    -			<p>For now, though, you can - and should - still use CSS for one important consideration: z-indices.  Every Connection,
    -			Endpoint and Overlay in jsPlumb adds some element to the UI, and you should take care to establish appropriate z-indices for each
    -			of these, in conjunction with the nodes in your application. 
    -			</p>
    -			<p>By default, jsPlumb adds a specific class to each of the three types of elements it creates (These class names are
    -			exposed on the jsPlumb object and can be overridden if you need to do so - see the third column in the table) </p>
    -			<table width="90%" class="table" style="align:left;font-size:12px;">
    -				<tr><th>Component</th><th>CSS Class</th><th>jsPlumb Member</th></tr>
    -				<tr><td>Connector</td><td>_jsPlumb_connector</td><td>connectorClass</td></tr>
    -				<tr><td>Endpoint</td><td>_jsPlumb_endpoint</td><td>endpointClass</td></tr>
    -				<tr><td>Overlay</td><td>_jsPlumb_overlay</td><td>overlayClass</td></tr>
    -			</table>
    -			<p>In addition to these defaults, each of the main methods you use to configure Endpoints or make Connections
    -			in jsPlumb support the following two parameters:</p>
    -			<ul>
    -				<li class="bullet"><strong>cssClass</strong> - class(es) to set on the display elements</li>
    -				<li class="bullet"><strong>hoverClass</strong> - class(es) to set on the display elements when in hover mode</li>
    -			</ul>
    -			In addition, addEndpoint and makeSource allow you to specify what these classes will be for any Connections that are dragged from them:
    -			<ul>
    -				<li class="bullet"><strong>connectorClass</strong> - class(es) to set on the display elements of Connections</li>
    -				<li class="bullet"><strong>connectorHoverClass</strong> - class(es) to set on the display elements of Connections when in hover mode</li>
    -			</ul>
    -			<p>These parameters should be supplied as a String; they will be appended as-is to the class member, so feel free to
    -			include multiple classes.  jsPlumb won't even know.</p>
    -		</div>
    -				
    -		<div class="section">
    -			<h3><a id="animation">Animation</a></h3>
    -			jsPlumb offers an 'animate' function, which wraps the underlying animation engine for whichever library you happen to be using and
    -			inserts a callback for jsPlumb to repaint whatever it needs to at each step.  You could of course do this yourself; it's a convenience
    -			method really.
    -			<p>The method signature is:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.animate : function(el, properties, options) 
    -</pre>
    -</div>			
    -The arguments are as follows:
    -	<ul>
    -		<li><strong>el</strong> - element id, or element object from the library you're using.</li>
    -		<li><strong>properties</strong> - properties for the animation, such as duration etc.</li>
    -		<li><strong>options</strong> - options for the animation, such as callbacks etc.</li>
    -	</ul>
    -	
    -		<h4>jQuery and the 'revert' option</h4>
    -		jQuery offers a 'revert' option that you can use to instruct it to revert a drag under some condition. This is
    -		rendered in the UI as an animation that returns the drag object from its current location to wherever it started
    -		out.  It's a nice feature.  Unfortunately, the animation that runs the revert does not offer any lifecycle
    -		callback methods - no 'step', no 'complete' - so its not possible for jsPlumb to know that the revert 
    -		animation is taking place.
    -	
    -		</div>
    -		
    -			<div class="section">
    -			<h3><a id="connectionInfo">Retrieving Connection Information</a></h3>
    -			There are two ways of retrieving connection information from jsPlumb. <strong>getConnections</strong>is the original method; this method has since been supplanted by <a href="#selectConnections">jsPlumb.select</a>, a much more versatile variant.
    -			<h5>getConnections</h5>
    -			<p>Before you use this method you should understand jsPlumb's notion of 'scope' - documentation
    -			is <a href="#dragAndDropScope">here</a> </p>
    -			
    -			<h4>Retrieving connections for a single scope</h4>
    -			<p>To do this, you call getConnections with either no arguments, in which case jsPlumb uses the default scope, or with a string
    -			specifying one scope</p>
    -			<div class="code">
    -<pre>var connectionList = jsPlumb.getConnections();     // you get a list of Connection objects that are in the default scope.
    -</pre>			
    -			</div>
    -			Compare this with:
    -			<div class="code">
    -<pre>var connectionList = jsPlumb.getConnections("myScope");     // you get a list of Connection objects that are in "myScope".
    -</pre>			
    -			</div>
    -			<h4>More advanced filtering</h4>
    -			getConnections optionally takes a JS object specifying filter parameters, of which there are three:
    -			<ul>
    -				<li>scope - the scope(s) of the connection type(s) you wish to retrieve</li>
    -				<li>source - limits the returned connections to those that have this source id</li>
    -				<li>target - limits the returned connections to those that have this target id</li>
    -			</ul>			
    -			Each of these three parameters may be supplied as a string, which for source and target is an element id and for scope is the name of the scope, or a list of strings.  Als, you can pass "*" in as the value for any of these - a wildcard, meaning any value.  See the examples below.
    -			<p>
    -			The return value of a call to getConnection using a JS object as parameter varies on how many scopes you defined.  If you defined only a single scope then jsPlumb returns you a list of Connections in that scope.  Otherwise the return value is a dictionary whose keys are  scope names, and whose values are lists of Connections. For example, the following call:
    -			</p>
    -			<div class="code">
    -<pre>
    -	jsPlumb.getConnections({scope:["someScope", "someCustomScope"]});
    -</pre>
    -</div>
    -would result in this output:			
    -<div class="code">
    -<pre>
    -{
    -	"someScope" : [ 1..n Connections ],
    -	"someCustomScope": [ 1..m Connections ]
    -}
    -</pre>
    -</div>	
    -
    -There is an optional second parameter that tells getConnections to flatten its output and just return you an array.  The previous example with this parameter would look like this:
    -			<div class="code">
    -<pre>
    -	jsPlumb.getConnections({scope:["someScope", "someCustomScope"]}, true);
    -</pre>
    -</div>
    -would result in this output:			
    -<div class="code">
    -<pre>
    -[ 1..n Connections ]
    -</pre>
    -</div>	
    -		
    -		The following examples show the various ways you can get connection information:
    -	<ul>
    -		<li>Get all connections:
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getAllConnections();  
    -</pre>
    -</div>						
    -		</li>
    -<li>Get all connections for the default scope only (return value is a list):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections();  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given scope (return value is a list):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:"myTestScope"});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given scopes (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:["myTestScope", "yourTestScope"]});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given source (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:"mySourceElement"});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given sources (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:["mySourceElement", "yourSourceElement"]});  
    -</pre>
    -</div>						
    -		</li>		
    -<li>Get all connections for the given target (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({target:"myTargetElement"});  
    -</pre>
    -</div>						
    -		</li>				
    -<li>Get all connections for the given source and targets (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:"mySourceElement", target:["target1", "target2"]});  
    -</pre>
    -</div>						
    -		</li>				
    -		
    -<li>Get all connections for the given scope, with the given source and target (return value is a list of connections):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:'myScope", source:"mySourceElement", target:"myTargetElement"});  
    -</pre>
    -</div>						
    -		</li>				
    -		
    -	</ul>	
    -
    -		</div>
    -
    -		<div class="section">
    -			<h3><a id="selectConnections">Selecting Connections</a></h3>
    -			<strong>jsPlumb.select</strong> provides a fluid interface for working with lists of Connections.  The syntax used to specify which Connections you want is identical to that which you use for getConnections, but the return value is an object that supports most operations that you can perform on a Connection, and which is also chainable, for setter methods. Certain getter methods are also supported, but these are not chainable; they return an array consisting of all the Connections in the selection along with the return value for that Connection.
    -			<p>
    -			This is the full list of setter operations supported by jsPlumb.select:
    -			<ul>
    -				<li class="bullet">addOverlay</li>
    -				<li class="bullet">removeOverlay</li>
    -				<li class="bullet">removeOverlays</li>
    -				<li class="bullet">showOverlay</li>
    -				<li class="bullet">hideOverlay</li>
    -				<li class="bullet">showOverlays</li>
    -				<li class="bullet">hideOverlays</li>
    -				<li class="bullet">removeAllOverlays</li>
    -				<li class="bullet">setLabel</li>
    -				<li class="bullet">setPaintStyle</li>
    -				<li class="bullet">setHoverPaintStyle</li>
    -				<li class="bullet">setDetachable</li>
    -				<li class="bullet">setReattach</li>
    -				<li class="bullet">setConnector</li>
    -				<li class="bullet">setParameter</li>
    -				<li class="bullet">setParameters</li>
    -				<li class="bullet">detach</li>
    -				<li class="bullet">repaint</li>
    -				<li class="bullet">setType</li>
    -				<li class="bullet">addType</li>
    -				<li class="bullet">removeType</li>
    -				<li class="bullet">toggleType</li>		
    -			</ul>
    -
    -			Each of these operations returns a selector that can be chained.
    -			</p>
    -			These is the full list of getter operations supported by jsPlumb.select:
    -			<p>
    -				<ul>
    -				<li class="bullet">getLabel</li>
    -				<li class="bullet">getOverlay</li>
    -				<li class="bullet">isHover</li>
    -				<li class="bullet">isDetachable</li>
    -				<li class="bullet">isReattach</li>
    -				<li class="bullet">getParameter</li>
    -				<li class="bullet">getParameters</li>
    -				<li class="bullet">getType</li>
    -				<li class="bullet">hasType</li>
    -			</ul>
    -
    -			Each of these operations returns an array whose entries are [ value, Connection ] arrays, where 'value' is the return value from the given Connection.  Remember that the return values from a getter are not chainable, but a getter may be called at the end of a chain of setters.
    -			</p>
    -
    -			<p>Some examples:</p>
    -			Select all Connections and set their hover state to be false:
    -
    -<div class="code">
    -<pre>
    -jsPlumb.select().setHover(false);
    -</pre>
    -</div>
    -			Select all Connections from "d1" and remove all Overlays:
    -
    -<div class="code">
    -<pre>
    -jsPlumb.select({source:"d1"}).removeAllOverlays();
    -</pre>
    -</div>
    -
    -Select all connections in scope "foo" and set their paint style to be a thick blue line:
    -<div class="code">
    -<pre>
    -jsPlumb.select({scope:"foo"}).setPaintStyle({ strokeStyle:"blue", lineWidth:5 });
    -</pre>
    -</div>
    -Select all Connections from "d1" and detach them:
    -<div class="code">
    -<pre>
    -jsPlumb.select({source:"d1"}).detach();
    -</pre>
    -</div>
    -<h5>.each iterator</h5>
    -The return value of jsPlumb.select has a <strong>.each</strong> function that allows you to iterate through the list, performing an operation on each one:
    -<div class="code">
    -<pre>
    -jsPlumb.select({scope:"foo"}).each(function(connection) {
    -	
    -		// do something 
    -});
    -</pre>
    -</div>
    -.each is itself chainable:
    -<div class="code">
    -<pre>
    -jsPlumb.select({scope:"foo"}).each(function(connection) {
    -	
    -		// do something 
    -
    -}).setHover(true);
    -</pre>
    -</div>
    -
    -
    -<h5>Other properties/functions</h5>
    -<ul>
    -	<li class="bullet"><strong>length</strong> - this member reports the number of Connections in the selection</li>
    -	<li class="bullet"><strong>get(idx)</strong> - this function allows you to retrieve a Connection from the selection</li>
    -</ul>
    -
    -
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="selectEndpoints">Selecting Endpoints</a></h3>
    -			<strong>jsPlumb.selectEndpoints</strong> provides a fluid interface for working with lists of Endpoints.
    -			The syntax used to specify which Endpoints you want is identical to that which you use for jsPlumb.select,
    -			and the return value is an object that supports most operations that you can perform on an Endpoint (and
    -			which is also chainable, for setter methods). Certain getter methods are also supported, but these are not
    -			chainable; they return an array consisting of all the Endpoints in the selection along with the return
    -			value for that Endpoint.
    -			<p>
    -			<p>
    -				Four parameters are supported by selectEndpoints - each of these except 'scope' can be provided as either
    -				a string, a selector, a DOM element, or an array of a mixture of these types.  'scope' can be provided as either
    -				a string or an array of strings:
    -				<ul>
    -					<li class="bullet">element - element(s) to get both source and target endpoints from</li>
    -					<li class="bullet">source - element(s) to get source endpoints from</li>
    -					<li class="bullet">target - element(s) to get target endpoints from</li>
    -					<li class="bullet">scope - scope(s) for endpoints to retrieve.</li>					
    -				</ul>
    -			</p>
    -			This is the full list of setter operations supported by jsPlumb.selectEndpoints:
    -			<ul>
    -				<li class="bullet">addOverlay</li>
    -				<li class="bullet">removeOverlay</li>
    -				<li class="bullet">removeOverlays</li>
    -				<li class="bullet">showOverlay</li>
    -				<li class="bullet">hideOverlay</li>
    -				<li class="bullet">showOverlays</li>
    -				<li class="bullet">hideOverlays</li>
    -				<li class="bullet">removeAllOverlays</li>
    -				<li class="bullet">setLabel</li>
    -				<li class="bullet">setPaintStyle</li>
    -				<li class="bullet">setHoverPaintStyle</li>				
    -				<li class="bullet">setConnector</li>
    -				<li class="bullet">setParameter</li>
    -				<li class="bullet">setParameters</li>
    -				<li class="bullet">repaint</li>
    -				<li class="bullet">setType</li>
    -				<li class="bullet">addType</li>
    -				<li class="bullet">removeType</li>
    -				<li class="bullet">toggleType</li>
    -			</ul>
    -
    -			Each of these operations returns a selector that can be chained.
    -			</p>
    -			These is the full list of getter operations supported by jsPlumb.select:
    -			<p>
    -				<ul>
    -					<li class="bullet">getLabel</li>
    -					<li class="bullet">getOverlay</li>
    -					<li class="bullet">isHover</li>				
    -					<li class="bullet">getParameter</li>
    -					<li class="bullet">getParameters</li>
    -					<li class="bullet">getType</li>
    -					<li class="bullet">hasType</li>
    -				</ul>
    -
    -			Each of these operations returns an array whose entries are [ value, Endpoint ] arrays, where 'value'
    -			is the return value from the given Endpoint. Remember that the return values from a getter are not chainable,
    -			but a getter may be called at the end of a chain of setters.
    -			</p>
    -			<p>
    -				Other methods (not chainable):
    -				<ul>
    -					<li class="bullet">delete - deletes the Endpoints in the selection</li>
    -					<li class="bullet">detachAll - detaches all Connections from the Endpoints in the selection </li>
    -				</ul>
    -			</p>
    -
    -			<p>Some examples:</p>
    -			Select all Endpoints and set their hover state to be false:
    -
    -<div class="code">
    -<pre>
    -jsPlumb.selectEndpoints().setHover(false);
    -</pre>
    -</div>
    -			Select all source Endpoints on "d1" and remove all Overlays:
    -
    -<div class="code">
    -<pre>
    -jsPlumb.selectEndpoints({source:"d1"}).removeAllOverlays();
    -</pre>
    -</div>
    -
    -Select all Endpoints in scope "foo" and set their fill style to be blue:
    -<div class="code">
    -<pre>
    -jsPlumb.selectEndpoints({ scope:"foo" }).setPaintStyle({ fillStyle:"blue" });
    -</pre>
    -</div>
    -Select all Endpoints from "d1" and detach their Connections:
    -<div class="code">
    -<pre>
    -jsPlumb.selectEndpoints({source:"d1"}).detachAll();
    -</pre>
    -</div>
    -<h5>.each iterator</h5>
    -The return value of jsPlumb.selectEndpoints has a <strong>.each</strong> function that allows you to iterate through the list, performing an operation on each one:
    -<div class="code">
    -<pre>
    -jsPlumb.selectEndpoints({scope:"foo"}).each(function(endpoint) {
    -	
    -		// do something 
    -});
    -</pre>
    -</div>
    -.each is itself chainable:
    -<div class="code">
    -<pre>
    -jsPlumb.selectEndpoints({scope:"foo"}).each(function(endpoint) {
    -	
    -		// do something 
    -
    -}).setHover(true);
    -</pre>
    -</div>
    -
    -
    -<h5>Other properties/functions</h5>
    -<ul>
    -	<li class="bullet"><strong>length</strong> - this member reports the number of Endpoints in the selection</li>
    -	<li class="bullet"><strong>get(idx)</strong> - this function allows you to retrieve an Endpoint from the selection</li>
    -</ul>
    -
    -
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="events">Events</a></h3>
    -			jsPlumb supports binding to several different events on Connections, Endpoints and Overlays, and also on the jsPlumb object itself.  
    -			
    -			<h4><a id="jsPlumbEvents">jsPlumb Events</a></h4>
    -			<p>To bind an event to jsPlumb itself, use <strong>jsPlumb.bind(event, callback)</strong>:</p>
    -			<div class="code">
    -<pre>
    -jsPlumb.bind("jsPlumbConnection", function(info) {
    -   .. update your model in here, maybe.
    -});
    -</pre>
    -</div>
    -<p>These are the events you can bind to on the jsPlumb class:</p>
    -			<ul>
    -				<li class="bullet"><strong>jsPlumbConnection</strong> - notification a Connection was established.
    -					<p>
    -						The first argument to the callback is an object with the following properties:					
    -					</p>
    -					<div class="code">
    -<pre>
    -{
    -  connection 		: 	the new Connection.  you can register listeners on this etc.
    -  sourceId 		:	id of the source element in the Connection
    -  targetId		:	id of the target element in the Connection
    -  source		:	the source element in the Connection
    -  target		:	the target element in the Connection
    -  sourceEndpoint	:	the source Endpoint in the Connection
    -  targetEndpoint	:	the targetEndpoint in the Connection
    -}
    -</pre>
    -					</div>
    -The second argument is the original mouse event that caused the Connection, if any.
    -
    -					<p>
    -All of the source/target properties are actually available inside the Connection object, but - for one of those rubbish historical reasons - are provided separately because of a vagary of the <em>jsPlumbConnectionDetached</em> callback, which is discussed below.
    -</p>
    -				</li>
    -				<li class="bullet"><strong>jsPlumbConnectionDetached</strong> - notification a Connection was detached.  
    -					<p>
    -						As with <em>jsPlumbConnection</em>, the first argument to the callback is an object with the following properties:					
    -					</p>
    -					<div class="code">
    -<pre>
    -{
    -  connection		: 	the Connection that was detached.  
    -  sourceId		:	id of the source element in the Connection <em>before it was detached</em>
    -  targetId		:	id of the target element in the Connection before it was detached
    -  source		:	the source element in the Connection before it was detached
    -  target		:	the target element in the Connection before it was detached
    -  sourceEndpoint	:	the source Endpoint in the Connection before it was detached
    -  targetEndpoint	:	the targetEndpoint in the Connection before it was detached
    -}
    -</pre>
    -
    -					</div>	
    -The second argument is the original mouse event that caused the disconnection, if any. 					
    -								
    -					<p>
    -As mentioned above, the source/target properties are provided separately from the Connection, because this event is fired whenever
    -a Connection is either detached and abandoned, or detached from some Endpoint and attached to another.  In the latter case, the
    -Connection that is passed to this callback is in an indeterminate state (that is, the Endpoints are still in the state they are
    -in when dragging, and do not reflect static reality), and so the source/target properties give you the real story.
    -</p>
    -<p> Like I said above, this is really just for rubbish historical reasons.  I'm attempting to document my way out of it!</p>
    -				
    -				</li>
    -
    -				<li class="bullet"><strong>connectionDrag</strong> - notification an existing Connection is being dragged. The callback is passed the Connection.
    -				</li> 
    -
    -				<li class="bullet"><strong>connectionDragStop</strong> - notification a Connection drag has stopped. This is only fired for existing Connections. The callback is passed the Connection that was just dragged.
    -				</li> 
    -
    -				<li class="bullet"><strong>click</strong> - notification a Connection was clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification a Connection was double-clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>endpointClick</strong> - notification an Endpoint was clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>endpointDblClick</strong> - notification an Endpoint was double-clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>contextmenu</strong> - a right-click on some given component.  jsPlumb will report right clicks on both Connections and Endpoints; the callback is passed the component that received the right-click.				
    -				<li class="bullet"><strong>beforeDrop</strong>
    -					This event is fired when a new or existing connection has been dropped. Your callback is passed a JS object with these fields:
    -					<ul>
    -						<li><strong>sourceId</strong> - the id of the source element in the connection</li>
    -						<li><strong>targetId</strong> - the id of the target element in the connection</li>
    -						<li><strong>scope</strong> - the scope of the connection</li>
    -						<li><strong>connection</strong> - the actual Connection object.  You can access the 'endpoints' array in a Connection to get the Endpoints involved in the Connection, but be aware that when a Connection is being dragged, one of these Endpoints will always be a transient Endpoint that exists only for the life of the drag. To get the Endpoint on which the Connection is being dropped, use the 'dropEndpoint' member.</li>
    -						<li><strong>dropEndpoint</strong> - this is the actual Endpoint on which the Connection is being dropped.  This <strong>may be null</strong>, because it will not be set if the Connection is being dropped on an element on which makeTarget has been called. </li>
    -					</ul>
    -
    -					If you return false (or nothing) from this callback, the new Connection is aborted and removed from the UI.
    -				</li>					
    -				<!--li class="bullet"><strong>beforeDrag</strong>
    -					This event is fired when a new connection is about to be dragged.  Your callback function
    -					is passed the Connection that jsPlumb has just created.  If you return false (or nothing) 
    -					from this callback, the new Connection is aborted.
    -				</li-->
    -				<li class="bullet"><strong>beforeDetach</strong>
    -					This event is fired when a Connection is about to be detached, for whatever reason. Your callback function is passed the Connection that the user has just detached. Returning false from this interceptor aborts the Connection detach.
    -				</li>
    -							
    -			</ul>
    -			<h4><a id="connectionEvents">Connection Events</a></h4>		
    -			<p>To bind to an event on a Connection, you also use the <strong>bind</strong> method:</p>
    -			<div class="code">
    -<pre>
    -var connection = jsPlumb.connect({source:"d1", target:"d2"});
    -connection.bind("click", function(conn) {
    -	console.log("you clicked on ", conn);
    -});
    -</pre>
    -</div>
    -<p>These are the Connection events to which you can bind a listener:</p>
    -			<ul>
    -				<li class="bullet"><strong>click</strong> - notification a Connection was clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification a Connection was double-clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>contextmenu</strong> - a right-click on the Connection. The callback is passed the Connection and the original event.
    -				<li class="bullet"><strong>mouseenter</strong> - notification the mouse entered the Connection's path.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>mouseexit</strong> - notification the mouse exited the Connection's.  The callback is passed the Connection and the original mouse event.</li>
    -			</ul>
    -			<h4><a id="endpointEvents">Endpoint Events</a></h4>			
    -			<p>To bind to an event on a Endpoint, you again use the <strong>bind</strong> method:</p>
    -			<div class="code">
    -<pre>
    -var endpoint = jsPlumb.addEndpoint("d1", { someOptions } );
    -endpoint.bind("click", function(endpoint) {
    -	console.log("you clicked on ", endpoint);
    -});
    -</pre>
    -</div>
    -<p>These are the Endpoint events to which you can bind a listener:</p>
    -			<ul>
    -				<li class="bullet"><strong>click</strong> - notification an Endpoint was clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification an Endpoint was double-clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>contextmenu</strong> - a right-click on the Endpoint. The callback is passed the Endpoint and the original event.
    -				<li class="bullet"><strong>mouseenter</strong> - notification the mouse entered the Endpoint.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>mouseexit</strong> - notification the mouse exited the Endpoint.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>maxConnections</strong> - notification the user tried to drop a Connection on an Endpoint that already has the maximum number of Connections.  The callback is passed as first argument an object literal containing the Endpoint, the Connection the user tried to drop, and the value of maxConnections for the Endpoint, and as second argument the original mouse event.</li>
    -			</ul>
    -			<h4><a id="overlayEvents">Overlay Events</a></h4>
    -			Registering event listeners on an Overlay is a slightly different procedure - you provide them as arguments to the Overlay's 
    -			constructor.  This is because you never actually act on an 'Overlay' object.  Here's how to register a click listener on an Overlay:
    -			<div class="code">
    -<pre>jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  overlays:[
    -    [ "Label", {
    -      events:{
    -        click:function(labelOverlay, originalEvent) { 
    -          console.log("click on label overlay for :" + labelOverlay.component); 
    -        }
    -      }
    -    }],
    -    [ "Diamond", {
    -      events:{
    -        dblclick:function(diamondOverlay, originalEvent) { 
    -          console.log("double click on diamond overlay for : " + diamondOverlay.component); 
    -        }
    -      }
    -    }] 	
    -  ]
    -});
    -</pre>			
    -			</div>		
    -		Note that events registered on Diamond, Arrow or PlainArrow overlays will not fire with the Canvas renderer - they work only with the SVG and VML renderers. 
    -
    -
    -		<h4><a id="jsPlumbEvents">Unbinding Events</a></h4>
    -		<p>On the jsPlumb object and on Connections and Endpoints, you can use the <strong>unbind</strong> method to remove a listener.  This method either takes the name of the event to unbind:
    -		<div class="code">
    -<pre>
    -jsPlumb.unbind("click");
    -</pre>
    -</div>		
    -...or no argument, meaning unbind all events:
    -	<div class="code">
    -<pre>
    -var e = jsPlumb.addEndpoint("someDiv");
    -e.bind("click", function() { ... });
    -e.bind("dblclick", function() { ... });
    -
    -...
    -
    -e.unbind("click");
    -</pre>
    -</div>
    -
    -		</div>
    -
    -				<div class="section">
    -			<a id="examples"><h3>jsPlumb.connect Examples</h3></a>
    -This section provides examples of how to use the programmatic API to establish Connections. 
    -			<p>
    -			The basic syntax of a call is that you execute 'connect', providing a source and a target, and optionally a paintStyle and preferences for where you
    -			want the plumbing to be anchored on each element, as well as the type of connector to use. 
    -			</p>
    -			<ul>
    -				<li>Connect window1 to window2 with the default settings:
    -					<div class="code">
    -						<pre>jsPlumb.connect({source:"window1", target:"window2"});</pre>
    -					</div>
    -This connects 'window1' to 'window2' using the jsPlumb defaults - a red Bezier curve Connector and black Dot Endpoints.
    -				</li>
    -				<li>Connect window1 to window2 with a 15 pixel wide yellow Connector, and a slightly brighter endpoint (remember the default Endpoint is a Dot):
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -					</div>
    -				</li>
    -				
    -				<li>Connect window1 to window2 with a 15 pixel wide yellow Connector, and a slightly brighter endpoint:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -					</div>
    -				</li>				
    -				<li>Connect window3 to 'window4' with a 10 pixel wide, semi opaque blue Connector, anchored to the left middle of window3, and the right middle of window4, with a Rectangle endpoint of width 10 and height 8:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window3',
    -	target:'window4',
    -	paintStyle:{ lineWidth:10, strokeStyle:'rgba(0, 0, 200, 0.5)' },
    -	anchors:["RightMiddle", "LeftMiddle"],
    -	endpoint:[ "Rectangle", { width:10, height:8 } ]
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window2 to window3 with a default Connector from the top center of window2 to the bottom center of window3, and rectangular endpoints:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window2',
    -	target:'window3',
    -	paintStyle:{lineWidth:8, strokeStyle:'rgb(189,11,11)'},
    -	anchors:["BottomCenter", "TopCenter"],
    -	endpoint:"Rectangle"
    -});</pre>
    -					</div>
    -				</li>
    -
    -
    -				<li>Connect window1 to window2 with a 15 px wide yellow Bezier. endpoints are a slightly lighter shade of yellow.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	anchors:["BottomCenter", [0.75,0,0,-1]],
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -</div></li>
    -<li>Connect window3 to window4 with a 10px wide blue-ish half transparent Bezier. put endpoints underneath the element they attach to.
    -the endpoints have a radial gradient. both ways of specifying gradient positioning are shown here.
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect( {
    -	source:'window3',
    -	target:'window4',
    -		 paintStyle:{lineWidth:10, strokeStyle:w34Stroke},
    -		 anchors:["RightMiddle", "LeftMiddle"],
    -		 endpointStyle:{ gradient : {stops:[[0, w34Stroke], [1, w34HlStroke]], offset:17.5, innerRadius:15 }, radius:35},
    -		 //endpointStyle:{ gradient : {stops:[[0, w34Stroke], [1, w34HlStroke]], offset:'78%', innerRadius:'73%'}, radius:35 },
    -		 endpointsOnTop:false
    -	}
    -);</pre>
    -</div></li>
    -<li>Connect window2 to window3 with an 8px red Bezier and default rectangular endpoints.  see also how the first anchor is
    -specified here - this is how you create anchors in locations jsPlumb does not offer shortcuts for.
    -the endpoints in this example have linear gradients applied.
    -<div class="code">
    -<pre>var w23Stroke = 'rgb(189,11,11)';
    -jsPlumb.connect({
    -	source:'window2',
    -	target:'window3',
    -	paintStyle:{lineWidth:8,strokeStyle:w23Stroke},
    -	anchors:[[0.3,1,0,1], "TopCenter"],
    -	endpoint:"Rectangle",
    -	endpointStyles:[{ gradient : {stops:[[0, w23Stroke], [1, '#558822']] }},
    -       				{ gradient : {stops:[[0, w23Stroke], [1, '#882255']] }}]
    -});</pre>
    -</div></li>
    -
    -<li>Connect window5 to window6 from center to center, 5px wide line that is green and half transparent. the endpoints are
    -125px in radius and spill out from underneath their elements.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window5',
    -	target:'window6',
    -	anchors:["Center", "Center"],
    -	paintStyle:{lineWidth:5,strokeStyle:'rgba(0,255,0,0.5)'},
    -	endpointStyle:{radius:125}
    -});</pre>
    -</div></li>
    -
    -<li>Connect window4 to window5 from bottom right to top left, with a 7px straight line purple connector, and an image as the endpoint,
    -placed on top of the element it is connected to.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window4",
    -	target:"window5",
    -	anchors:[ "BottomRight","TopLeft" ],
    -	paintStyle:{ lineWidth:7, strokeStyle:"rgb(131,8,135)" },
    -	endpoint:[ "Image", { src:"http://morrisonpitt.com/jsPlumb/img/endpointTest1.png" } ],
    -	connector:"Straight"
    -});</pre>
    -</div></li>
    -
    -
    -				<li>Connect window5 to window6 between their center points with a semi-opaque connector, and 125px endpoints:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window5",
    -	target:"window6",
    -	anchors:[ "Center", "Center" ],
    -	paintStyle:{ lineWidth:5, strokeStyle:"rgba(0,255,0,0.5)" },
    -	endpointStyle:{ radius:125 }
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window7 to window8 with a 10 pixel wide blue Connector, anchored on the top left of window7 and the bottom right of window8:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window7",
    -	target:"window8",
    -	paintStyle:{ lineWidth:10, strokeStyle:"blue" },
    -	anchors:[ "TopLeft", "BottomRight" ]
    -});
    -</pre>
    -					</div>
    -				</li>
    -				<li>Connect the bottom right corner of window4 to the top left corner of window5, with rectangular endpoints of size 40x40 and a hover color of light blue:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window4",
    -	target:"window5",
    -	anchors:["BottomRight","TopLeft"],
    -	paintStyle:{lineWidth:7,strokeStyle:'rgb(131,8,135)'},
    -	hoverPaintStyle:{ strokeStyle:"rgb(0, 0, 135)" },
    -	endpointStyle:{ width:40, height:40 },
    -	endpoint:"Rectangle",
    -	connector:"Straight"
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window1 to window2 with the default paint settings but provide some drag options (which are passed through to the underlying library's draggable call):
    -					<div class="code">
    -						<pre>jsPlumb.connect({source:'window1', target:'window2', dragOptions:{cursor:'crosshair'}});</pre>
    -					</div>
    -				</li>
    -</div>
    -		
    -		<div class="section">
    -			<a id="dragAndDropExamples"><h3>Draggable Connections Examples</h3></a>
    -			This is a list of examples of how to use jsPlumb to create Connections using drag and drop.
    -			<p>
    -			The basic procedure is:
    -			<ol>
    -				<li>1a. Create Endpoints and register them on elements in your UI</li>
    -				<li>1b. alternatively, create a source endpoint and then make some element a drop target </li>
    -				<li>2. Drag and Drop</li>
    -			</ol>
    -			That's all there is to it.  Of course there are plenty of options you can set when doing this...it will be easier
    -			to show you some examples:
    -			</p>
    -			<ul>
    -				<li>Define an Endpoint with default appearance, that is both a source and target of new Connections:
    -<div class="code">
    -<pre>var endpointOptions = { isSource:true, isTarget:true }; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Register that Endpoint on window3, specifying that it should be located in the top center of the element:
    -<div class="code">
    -<pre>var window3Endpoint = jsPlumb.addEndpoint('window3', { anchor:"TopCenter" }, endpointOptions );  
    -</pre>
    -</div>
    -Notice here the usage of the three-argument addEndpoint  - we can reuse 'endpointOptions' with a different Anchor for another element.  This is a useful
    -practice to get into.<br/><br/>
    -				</li>
    -				
    -				<li>Now register that Endpoint on window4, specifying that it should be located in the bottom center of the element:
    -<div class="code">
    -<pre>var window4Endpoint = jsPlumb.addEndpoint('window4', { anchor:"BottomCenter" }, endpointOptions );  
    -</pre>
    -</div>
    -Now we have two Endpoints, both of which support drag and drop of new Connections.  We can use these to make a programmatic Connection, too, though:<br/><br/>
    -				</li>				
    -				
    -				<li>Connect window3 to window4 with a 25px wide yellow Bezier that has a 'curviness' of 175:
    -<div class="code">
    -<pre>jsPlumb.connect({ 
    -	source:window3Endpoint,
    -	target:window4Endpoint,
    -	connector: [ "Bezier", 175 ],
    -	paintStyle:{ lineWidth:25, strokeStyle:'yellow' }
    -});  
    -</pre>
    -</div>
    -				</li>
    -				
    -				<li>Define an Endpoint that creates Connections that are 20px wide straight lines, that is both a source and target of new Connections,
    -				and that has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Define an Endpoint that will be anchored to "TopCenter".  It creates Connections that are 20px wide straight lines, that is both a source and target of new Connections,
    -				and that has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchor:"TopCenter", 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Define an Endpoint that will create a dynamic anchor which can be positioned at "TopCenter" or "BottomCenter".  It creates Connections that are 20px wide straight lines, it is both a source and target of new Connections,
    -				and it has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchor:[ "TopCenter", "BottomCenter" ], 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Exactly the same as before, but shows how you can use "anchors" instead of "anchor", if that makes you feel happier:
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchors:[ "TopCenter", "BottomCenter" ], 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>								
    -				
    -<li>Define an Endpoint that is a 30px blue dot, creates Connections that are 20px wide straight lines, is both a source and target of new Connections,
    -				has a 'scope' of 'blueline', and has an event handler that pops up an alert (note: the event handler name means this example is jQuery - MooTools
    -				and YUI3 use different event handler names):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{fillStyle:'blue'},
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ drop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -			<li>Same example as before, but this is for MooTools, and the Endpoint can support up to 5 connections (the default is 1):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{ fillStyle:'blue' },
    -	maxConnections:5,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ onDrop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Same example again, but maxConnections being set to -1 means that the Endpoint has no maximum limit of Connections:
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", {radius:30} ],
    -	style:{ fillStyle:'blue' },
    -	maxConnections:-1,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ onDrop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Same example again, but for YUI3.  Note the drop callback is "drop:hit":
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{fillStyle:'blue'},
    -	maxConnections:-1,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ "drop:hit":function(e, ui) { alert('drop!'); } }	 	
    -}; 
    -</pre>
    -</div>				 				
    -				</li>				
    -				
    -	<li>Assign a UUID to the endpoint options created above, and add as Endpoints to "window1" and "window2":
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("window1", { uuid:"abcdefg" }, endpointOptions );
    -jsPlumb.addEndpoint("window2", { uuid:"hijklmn" }, endpointOptions );
    -</pre>
    -</div>	
    -	</li>
    -	
    -	<li>Connect the two Endpoints we just registered on "window1" and "window2":
    -<div class="code">
    -<pre>
    -jsPlumb.connect({uuids:["abcdefg", "hijklmn"]});
    -</pre>
    -</div>	
    -	</li>
    -	
    -	<li>Create a source Endpoint, register it on some element, then make some other element a Connection target:
    -<div class="code">
    -<pre>
    -var sourceEndpoint = { isSource:true, endpoint:[ "Dot", { radius:50 } ] };
    -var targetEndpoint = { endpoint:[ "Rectangle", { width:10, height:10 } ] };
    -jsPlumb.addEndpoint( "window1", sourceEndpoint );
    -jsPlumb.makeTarget( "window2", targetEndpoint );
    -</pre>
    -</div>	
    -Notice that the endpoint definition we use on the target window does not include the "isTarget:true" directive.  jsPlumb ignores that flag when
    -creating a connection using an element as the target; but if you then tried to drag another connection to the Endpoint that was created, you would
    -not be able to.  To permit that, you would set 'isTarget:true' on the targetEndpoint options defined above.
    -	
    -	</li>
    -				
    -			</ul>
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="utilityFunctions">Utility Functions</a></h3>
    -			<ul>
    -				<li>Detach window5 from all connections
    -					<div class="code">
    -						<pre>jsPlumb.detachAll("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Hide all window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.hide("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Hide all window5's connections endpoints
    -					<div class="code">
    -						<pre>jsPlumb.hide("window5", true);</pre>
    -					</div>
    -				</li>
    -				<li>Show all window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.show("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Show all window5's connections and endpoints.  Note that in the case that you call jsPlumb.show with two arguments, jsPlumb
    -				will also not make a connection visible if it determines that the other endpoint in the connection is not visible.
    -					<div class="code">
    -						<pre>jsPlumb.show("window5", hide);</pre>
    -					</div>
    -				</li>				
    -				<li>Toggle the visibility of window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.toggle("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of all of window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.repaint("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of all of window5, window6 and window11's connections
    -					<div class="code">
    -						<pre>jsPlumb.repaint( [ "window5", "window6", "window11" ] );</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of every connection
    -					<div class="code">
    -						<pre>jsPlumb.repaintEverything();</pre>
    -					</div>
    -				</li>
    -				<li>Detach every connection that the given instance of jsPlumb is managing
    -					<div class="code">
    -						<pre>jsPlumb.detachEveryConnection();</pre>
    -					</div>
    -				</li>
    -				<li>Detach all connections from "window1"
    -					<div class="code">
    -						<pre>jsPlumb.detachAllConnections("window1");</pre>
    -					</div>
    -				</li>
    -				<li>Remove the given Endpoint from element "window1", deleting its Connections. 
    -					<div class="code">
    -						<pre>jsPlumb.removeEndpoint("window1", someEndpoint);</pre>
    -					</div>
    -				</li>
    -				<li>Remove all Endpoints for the element 'window1', deleting their Connections.
    -					<div class="code">
    -						<pre>jsPlumb.removeAllEndpoints("window1");</pre>
    -					</div>
    -				</li>	
    -				<li>Removes every Endpoint managed by this instance of jsPlumb, deleting all Connections.
    -				This is the same as jsPlumb.reset(), effectively, but it does not clear out the event listeners list. 
    -					<div class="code">
    -						<pre>jsPlumb.removeEveryEndpoint();</pre>
    -					</div>
    -				</li>
    -				<li>Deletes the given Endpoint and all its Connections. 
    -					<div class="code">
    -						<pre>jsPlumb.deleteEndpoint(endpoint);</pre>
    -					</div>
    -				</li>
    -				<li>Removes every endpoint, detaches every connection, and clears the event listeners list.  Returns jsPlumb instance to its initial state.  
    -					<div class="code">
    -						<pre>jsPlumb.reset();</pre>
    -					</div>
    -				</li>	
    -				<li>Library-agnostic method to get a selector from the given selector specification.  This function is used mostly by the jsPlumb demos, to cut down on repeating code across the different libraries.  But perhaps you are writing something with jsPlumb that you also wisht to be library agnostic - this function can help you. The returned object, in each of jQuery, MooTools and YUI, has a ".each" iterator.
    -					<div class="code">
    -						<pre>var selector = jsPlumb.getSelector(".someSelector .spec");</pre>
    -					</div>					
    -				</li>
    -				<li>Set window1 to be not draggable, no matter what some jsPlumb command may request.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggable("window1", false);</pre>
    -					</div>
    -				</li>
    -				<li>Set window1 and window2 to be not draggable, no matter what some jsPlumb command may request.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggable(["window1","window2"], false);</pre>
    -					</div>
    -				</li>				
    -				<li>Initialises window1 as a draggable element (all libraries). Passes in an on drag callback
    -				<div class="code">
    -					<pre>jsPlumb.draggable("window1");</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 and window2 as draggable elements (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(["window1","window2"]);</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable("window1");</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (jQuery)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (MooTools)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($$(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (YIU3)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(Y.all(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (jQuery)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($("#window1"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (MooTools)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($("window1"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (YUI3)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(Y.one("window1"));</pre>
    -				</div>
    -				</li>
    -			</ul>
    -		</div>					
    -		
    -		<div class="section">
    -			<h3><a id="developingJsPlumb">Advanced Topics</a></h3>
    -			<h4><a id="which">Which files are which?</a></h4>
    -			In development, jsPlumb is broken up into nine scripts:
    -				<ul>
    -					<li>- jsPlumb-util-x.x.x.js
    -					<p>There are the jsPlumb utility functions (and some base classes).</p>					
    -					</li>
    -					<li>- jsPlumb-x.x.x.js
    -					<p>This is the main jsPlumb engine.</p>					
    -					</li>
    -					<li>- jsPlumb-defaults-x.x.x.js
    -					<p>This contains the default Anchor, Endpoint, Connector and Overlay implementations.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-canvas-x.x.x.js
    -					<p>This contains the HTML5 canvas render code.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-svg-x.x.x.js
    -					<p>This contains the SVG render code.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-vml-x.x.x.js
    -					<p>This contains the VML render code.</p>
    -					</li>
    -					<li>- jsPlumb-connectors-statemachine-x.x.x.js
    -					<p>This State Machine connector.</p>
    -					</li>
    -					<li>- &lt;LIBRARY_PREFIX&gt;.jsPlumb-x.x.x.js
    -					<p>This contains library-specific helper methods.  jsPlumb ships with three of these - one each for jQuery, MooTools and YUI3. See below for information on how to create a new library implementation.</p>
    -					</li>
    -					<li>- jsBezier-0.4-min.js
    -					<p>These are the Bezier curve functions; they are maintained in a separate project called <a href="http://code.google.com/p/jsbezier/">jsBezier</a></p>
    -					</li>
    -				</ul>
    -			These seven files are packaged together to form the scripts that people use, for example:
    -			<ul>
    -				<li>jquery.jsPlumb-1.3.16-all.js
    -				<p>Contains jsPlumb-1.3.16.js, jsPlumb-defaults-1.3.16.js, jsPlumb-renderers-canvas-1.3.16.js, jsPlumb-renderers-svg-1.3.16.js, jsPlumb-renderers-vml-1.3.16.js, jsPlumb-connectors-statemachine-1.3.16.js, jquery.jsPlumb-1.3.16.js and jsBezier-0.4-min.js</p>
    -				</li>
    -				<li>jquery.jsPlumb-1.3.16-all-min.js
    -				<p>A minified version of the script above (minified using the YUI Compressor)</p>
    -				</li>
    -			</ul>
    -			<h4><a id="pluggableLibrarySupport">Pluggable Library Support</a></h4>
    -		Out of the box, jsPlumb can be run on top of jQuery, MooTools or YUI3.  This is achieved by
    -		delegating several core methods - tasks such as finding an element by id, finding an element's
    -		position or dimensions, initialising a draggable, etc - to the library in question.
    -		<p>To develop one of these, your test page should include the first two scripts discussed above, and
    -		then your own script containing your library specific functionality.  The existing implementations
    -		may be documented well enough for you to create your own, but contact me if you need assistance.  If you do this, it would be
    -		great to share it with everyone...</p>   
    -		<p>There is a group of people working on an ExtJS adapter currently.  There is no schedule for when this will be completed though.</p>
    -
    -			<!-- h4><a id="customConnectors">Custom Connectors</a></h4>			
    -					
    -			You can provide your own connectors if you need to. A Connector consists of two functions, which work as a pair.  First a call is made to the <em>compute</em> function:
    -
    -			<div class="code">
    -<pre>
    -this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) {
    -	...
    -	return dimensions;
    -}
    -</pre>
    -			</div>
    -			which is expected to return a list that the <em>paint</em> function can make sense of.  The first four entries in the
    -			list <strong>must be</strong> the [x,y,width,height] values for the canvas that the connector will be drawn on; jsPlumb will
    -			use this information to size the canvas prior to calling the Connector's <em>paint</em> function.  Therefore it
    -			is the Connector's responsibility to ensure that the returned dimensions describe a large enough space for
    -			the line that will be drawn on it.</p><p>The next four elements <strong>must be</strong> the coordinates of the two endpoints of the line
    -			you are going to draw.</p><p>The remainder of the items in the returned list are arbitrary, and will
    -			vary between Connector implementations; this list is passed in to a Connector's <em>paint</em> function, so each
    -			implementation will put into the list whatever it needs to paint itself.  For instance, the straight line
    -			connector only needs the [x,y] location of each end of the line it will paint, and that is one of the required entries, so
    -			it does not have to do anything extra,  whereas the Bezier connector adds the location of the two control points.  Other types of
    -			Connectors will do whatever is appropriate for their particular situation.
    -			</p>
    -			This is the method signature for the <em>paint</em> function:
    -			<div class="code">
    -				<pre>this.paint = function(dimensions, ctx) { .. }</pre>
    -			</div>
    -
    -			here, the 'dimensions' argument to the 'paint' function is the return value of the 'compute' function.  The 'ctx' argument is the Canvas context; you
    -			will do all your drawing on this.
    -			</p>
    -			To change the connector from the default, specify it in your connect call:
    -			<div class="code">
    -				<pre>jsPlumb.connect({source:'someWindow', target:'otherWindow', connector:new jsPlumb.Connectors.Straight()});</pre>
    -			</div>
    -			note that you can use shorthand for connectors if you don't need to specify parameters to it:
    -			<div class="code">
    -				<pre>jsPlumb.connect({source:'someWindow', target:'otherWindow', connector:"Straight"});</pre>
    -			</div>
    -			This works in the same way as specifying Anchors as string does.
    -<p>
    -The section below this discusses Overlays, which allow you to decorate Connectors with
    -			things such as labels or arrows or whatever else you like. Overlays can only work with Connectors that implement a few helper functions.
    -			<h4>The concept of <strong>location</strong></h4>
    -			Before discussing the helper functions you first must be across the concept of 'location' as used by this mechanism.  The <em>location</em>
    -			on a connector can be a decimal value between 0 and 1 inclusive, which marks a proportionate amount of travel <em>along the path
    -			inscribed by the Connector</em>.  For a straight line connector this is a simple piece of maths, but for Bezier connectors it's a little
    -			bit more involved.
    -			<h4>Required Helper Methods</h4>
    -			<ul>
    -				<li><strong>pointOnPath(location)</strong> - returns an [x,y] point corresponding to the given location</li>
    -				<li><strong>pointAlongPathFrom(location, distance)</strong> - returns an [x,y] point corresponding to travelling 'distance' pixels along the connector from 'location'.</li>
    -				<li><strong>gradientAtPoint(location)</strong> - returns the connector's gradient at the given location.  For linear connectors such as Straight this is constant, but for Bezier connectors the gradient changes continually.</li>
    -				<li><strong>perpendicularToPathAt(location, distance, length)</strong> - returns a line that is perpendicular to (and centered on) the connector at 'distance' from the given location, with length 'length'.</li>
    -			</ul>
    -</p>
    -<h4><a id="customEndpoints">Custom Endpoints</a></h4>
    -<p>To create your own Endpoint implementation, you need to implement a single method:
    -				<div class="code"><pre>paint : function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { ... }</pre></div>
    -				The arguments to this method are as follows:
    -				<ul>
    -					<li>anchorPoint - [x,y] location of the anchor point on screen</li>
    -					<li>orientation - [x,y] hints for the general direction the anchor points to</li>
    -					<li>canvas - the canvas to draw into</li>
    -					<li>endpointStyle - Javascript object containing style directives as discussed above.  The contents of this are arbitrary, so if you write a new Endpoint that needs some extra settings, you can add them no hassle.</li>
    -					<li>connectorPaintStyle - the style being used to paint the associated Connector.</li>
    -				</ul>
    -			</p>
    -			<p>
    -				It is your responsibility to size and locate the canvas to suit your needs. jsPlumb provides the following
    -				helper method to assist you:
    -				<div class="code"><pre>jsPlumb.sizeCanvas(canvas, x, y, width, height);</pre></div>
    -				Allows you to locate the canvas on screen and to size it.
    -			</p>
    -<h4><a id="customOverlays">Custom Overlays</a></h4>
    -			Overlays can only work with Connectors that implement the methods <strong>pointOnPath</strong>, <strong>pointAlongPathFrom</strong>,
    -			<strong>gradientAtPoint</strong>, and <strong>perpendicularToPathAt</strong>.  These are discussed in more detail in the Custom Connectors section above.  Both of the Connectors
    -			that come with jsPlumb - Straight and Bezier -implement these methods; if you write a custom connector, or have written a custom connector, you will need to supply
    -			them.   
    -			<h4>Overlay Interface</h4>
    -			An Overlay is required to implement two methods in order to be usable by jsPlumb:
    -			<ul>
    -				<li><strong>computeMaxSize(connector, context)</strong> - returns an integer value indicating the larger of this overlay's width and height.  It is used by jsPlumb to ensure that the canvas is large enough to accomodate the overlay.  The examples below should help to clarify this.</li>
    -				<li><strong>draw(connector, ctx)</strong> - draws the overlay.  What happens in this method is up to the given implementation.</li>
    -			</ul>
    -			<h4>computeMaxSize methods</h4>
    -			This is the Arrow overlay's computeMaxSize method:
    -			<div class="code">
    -<pre>
    -this.computeMaxSize = function() { return width; }
    -</pre>			
    -			</div>
    -Here, <em>width</em> is a private member of Arrow that indicates the width of the arrow's tail.  So the Arrow overlay reports that figure as the width it needs.					
    -Contrast this with the Label Overlay's computeMaxSize method:
    -		<div class="code">
    -<pre>
    -this.computeMaxSize = function(connector, ctx) {
    -  	if (labelText) {
    -  		ctx.save();
    -        if (self.labelStyle.font) ctx.font = self.labelStyle.font;
    -         var t = ctx.measureText(labelText).width;			            
    -		// a fake text height measurement: use the width of upper case M
    -		var h = ctx.measureText("M").width;					
    -		labelPadding = self.labelStyle.padding || 0.25;
    -		labelWidth = t + (2 * t * labelPadding);
    -		labelHeight = h + (2 * h * labelPadding);
    -		ctx.restore();
    -		return Math.max(labelWidth, labelHeight);
    -  	}
    - 	return 0;
    - };
    -</pre>			
    -			</div>
    -The Label overlay has to use the context to determine how big it will be on screen.
    -
    -<h4>draw methods</h4>
    -To give you a taste for how you can interact with a connector, consider the first few lines of the Arrow overlay's draw method:
    -<div class="code">
    -<pre>
    -this.draw = function(connector, ctx) {
    -	// this is the arrow head position
    -	var hxy = connector.pointAlongPathFrom(self.loc, length / 2);		
    -	// this is the center of the tail
    -	var txy = connector.pointAlongPathFrom(self.loc, -length / 2), tx = txy[0], ty = txy[1];
    -	// this is the tail vector
    -	var tail = connector.perpendicularToPathAt(self.loc, -length / 2, width);
    -	
    -	...
    -</pre>
    -</div>
    -	<p>The first two calls to the connector use the <strong>pointAlongPathFrom</strong> method, which passes <em>self.loc</em> as the location of the
    -	point on the path, and <em>length / 2</em> as the number of pixels along the path to traverse.</p>
    -	<p><em>self.loc</em> is Arrow's internal member describing the location of the overlay, and it is a decimal (between 0 and 1 inclusive) that points to
    -	a location at some distance along the path inscribed by the connector.  So these first two calls get us [x,y] locations of points on 
    -	the connector that mark the head and tail points for the arrow.  
    -	</p>		
    -	<p>
    -	The <strong>connector.perpendicularToPathAt(self.loc, -length / 2, width)</strong> call returns a line description of a line that is perpendicular to, and centered on, the connector
    -	at "-length / 2" pixels from the given point, with the given width (width is an internal member of Arrow).  At this point, the Arrow object has the three main points it
    -	needs in order to draw itself.
    -	</p>
    -		</div-->
    -</div>
    -
    -</body>
    -
    -</html>
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.16/index.html b/demo/doc/archive/1.3.16/index.html
    deleted file mode 100644
    index eab9ae94d..000000000
    --- a/demo/doc/archive/1.3.16/index.html
    +++ /dev/null
    @@ -1,93 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.16 documentation</title>
    -		<link rel="stylesheet" target="contentFrame" href="/mp.css"></link>
    -		<link rel="stylesheet" target="contentFrame" href="jsPlumbDoc.css"></link>
    -	</head>
    -	<body>
    -		<div class="index section">
    -			<h3>jsPlumb 1.3.16</h3>
    -			<ul>
    -				<li><a target="contentFrame" href="content.html#summary">Summary</a></li>
    -				<li><a target="contentFrame" href="content.html#changes">Changes since version 1.3.15</a></li>
    -				
    -				<li><h4>Setup</h4></li>
    -				<li><a target="contentFrame" href="content.html#imports">Required Imports</a></li>
    -				<li><a target="contentFrame" href="content.html#renderMode">Render Mode</a></li>
    -				<li><a target="contentFrame" href="content.html#initializing">Initializing jsPlumb</a></li>				
    -				<li><a target="contentFrame" href="content.html#multipleInstances">Multiple jsPlumb Instances</a></li>
    -				<li><a target="contentFrame" href="content.html#zIndex">Z-index Considerations</a></li>
    -				<li><a target="contentFrame" href="content.html#containerDefault">Where does jsPlumb add elements?</a></li>
    -				<li><a target="contentFrame" href="content.html#elementDragging">Element dragging</a></li>
    -				
    -				<li><h4>Introduction</h4></li>
    -				<li><a target="contentFrame" href="content.html#jsPlumbBasics">Basic Concepts</a></li>
    -				<li><a target="contentFrame" href="content.html#anchors">Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#staticAnchors">Static Anchors</a></li>				
    -				<li><a target="contentFrame" href="content.html#dynamicAnchors">Dynamic Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#perimeterAnchors">Perimeter Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#continuousAnchors">Continuous Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#definitions">Connector, Endpoint &amp; Overlay Definitions</a></li>
    -				<li><a target="contentFrame" href="content.html#connectors">Connectors</a></li>
    -				<li><a target="contentFrame" href="content.html#endpoints">Endpoints</a></li>
    -				<li><a target="contentFrame" href="content.html#overlays">Overlays</a></li>
    -				<li><a target="contentFrame" href="content.html#defaults">Defaults</a></li>								
    -				
    -				<li><h4>Connections</h4></li>
    -				<li><a target="contentFrame" href="content.html#simpleConnections">Programmatic Connections</a></li>
    -				<li><a target="contentFrame" href="content.html#draggableConnections">Connections using Drag and Drop</a></li>
    -				<li><a target="contentFrame" href="content.html#sourcesAndTargets">Elements as Sources &amp; Targets</a></li>
    -				<li class="sub"><a target="contentFrame" href="content.html#makeTarget">makeTarget</a></li>
    -				<li class="sub"><a target="contentFrame" href="content.html#makeSource">makeSource</a></li>
    -				<li><a target="contentFrame" href="content.html#dragOptions">Drag Options</a></li>				
    -				<li><a target="contentFrame" href="content.html#dropOptions">Drop Options</a></li>
    -				<li><a target="contentFrame" href="content.html#dragAndDropScope">Drag and Drop Scope</a></li>
    -				<li><a target="contentFrame" href="content.html#disconnectReconnect">Disconnecting and Reconnecting</a></li>
    -				
    -				<li><h4>Parameters</h4></li>		
    -				<li><a target="contentFrame" href="content.html#connectParameters">jsPlumb.connect</a></li>
    -				<li><a target="contentFrame" href="content.html#addEndpointParameters">jsPlumb.addEndpoint</a></li>
    -				
    -				<li><h4>Connection and Endpoint Types</h4></li>		
    -				<li><a target="contentFrame" href="content.html#connectionTypes">Connection Type</a></li>
    -				<li><a target="contentFrame" href="content.html#endpointTypes">Endpoint Type</a></li>
    -
    -				<li><h4>Interceptors</h4></li>				
    -				<li><a target="contentFrame" href="content.html#beforeDrop">Before Drop</a></li>
    -				<li><a target="contentFrame" href="content.html#beforeDetach">Before Detach</a></li>
    -				
    -				<li><h4>Appearance</h4></li>
    -				<li><a target="contentFrame" href="content.html#paintstyles">Paint Styles</a></li>
    -				<li><a target="contentFrame" href="content.html#hoverpaintstyles">Hover Paint Styles</a></li>
    -				<li><a target="contentFrame" href="content.html#gradients">Gradients</a></li>				
    -				
    -				<li><h4>Miscellaneous</h4></li>
    -				<li><a target="contentFrame" href="content.html#cssclasses">CSS</a></li>
    -				<li><a target="contentFrame" href="content.html#animation">Animation</a></li>
    -				<li><a target="contentFrame" href="content.html#connectionInfo">Retrieving Connection Information</a></li>
    -				<li><a target="contentFrame" href="content.html#selectConnections">Selecting Connections</a></li>
    -				<li><a target="contentFrame" href="content.html#selectEndpoints">Selecting Endpoints</a></li>
    -				
    -				<li><h4>Events</h4></li>
    -				<li><a target="contentFrame" href="content.html#events">jsPlumb Events</a></li>
    -				<li><a target="contentFrame" href="content.html#connectionEvents">Connection Events</a></li>
    -				<li><a target="contentFrame" href="content.html#endpointEvents">Endpoint Events</a></li>
    -				<li><a target="contentFrame" href="content.html#overlayEvents">Overlay Events</a></li>
    -				
    -				<li><h4>Examples</h4></li>				
    -				<li><a target="contentFrame" href="content.html#examples">Programmatic API Examples</a></li>
    -				<li><a target="contentFrame" href="content.html#dragAndDropExamples">Draggable Connections Examples</a></li>
    -				<li><a target="contentFrame" href="content.html#utilityFunctions">Utility Functions</a></li>				
    -				
    -				<li><h4>Develop</h4></li>
    -				<li><a target="contentFrame" href="content.html#which">Which files are which?</a></li>
    -				<li><a target="contentFrame" href="content.html#pluggableLibrarySupport">Pluggable Library Support</a></li>
    -				<!-- li><a target="contentFrame" href="content.html#customConnectors">Custom Connectors</a></li>
    -				<li><a target="contentFrame" href="content.html#customEndpoints">Custom Endpoints</a></li>
    -				<li><a target="contentFrame" href="content.html#customOverlays">Custom Overlays</a></li-->
    -			</ul>
    -			<p><strong>This document refers to release 1.3.16 of jsPlumb.</strong></p>
    -			<strong>26 Oct 2012</strong>
    -		</div>		
    -	</body>
    -</html>
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.16/jsPlumbDoc.css b/demo/doc/archive/1.3.16/jsPlumbDoc.css
    deleted file mode 100644
    index f2949bd91..000000000
    --- a/demo/doc/archive/1.3.16/jsPlumbDoc.css
    +++ /dev/null
    @@ -1,127 +0,0 @@
    -body { 
    -	background-color:white; font-size:90%;
    -	background-image:url(../img/dynamicAnchorBg.jpg); 
    -}
    -/* docs */
    -.code { background-color:#e4eeee;
    -border:1px solid black; margin:1em; padding:0.8em; 
    -overflow:auto;
    -font-family:courier; 
    -font-size:8pt;
    -border-radius:0.3em;
    -}
    -.section { border-bottom: 1px dotted black; padding:1em;font-family:helvetica;background-color:white;color:#333;}
    -.menu { 
    -	position:fixed; 
    -	right:10em; top:1em;
    -	background-color:white;
    -	padding:0.3em;
    -	opacity:0.8;
    -	z-index:5000;
    -	filter:alpha(opacity=80);
    -		box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -a {text-decoration:none;color:#05b;font-weight:bold;font-family:helvetica;}
    -a:hover {text-decoration:underline;}
    -.window, .label { 
    -	cursor:pointer;
    -	box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -#explanation { 
    -	background-color:white;
    -	color:black;
    -	border:0.25em solid #ddd; 
    -	position:absolute;
    -	right:3.5em;
    -	top:5em;
    -	width:22em;
    -	font-size:100%;
    -	-moz-border-radius:1em;
    -	border-radius:1em;
    -	padding:1em;
    -	z-index:1000;
    -	opacity:0.9;
    -	filter:alpha(opacity=90); 
    -	font-family:helvetica;
    -		box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -.selected {
    -	color:rgb(189,11,11);
    -}
    -
    -
    -/*  these styles are what jsPlumb attaches to the various elements it creates.
    -._jsPlumb_connector {
    -border:1px solid blue;
    -}
    -._jsPlumb_endpoint {
    -border:1px solid green;
    -}
    -._jsPlumb_overlay {
    -border:1px solid yellow;
    -}
    -*/
    -
    -
    -.section li {
    -list-style-type:none;
    -}
    -
    -li.bullet {
    -	list-style-type:circle;
    -	}
    -
    -
    -.section {
    -padding-left:2em;
    -font-size:10pt;
    -}
    -.index.section {
    -padding-left:0;
    -}
    -
    -.index {
    -width:20em;
    -font-size:9pt;
    -float:left;
    -background-color:#e4eeee;
    -overflow:auto;
    -padding-left:0.5em;
    -}
    -
    -.index li {
    -list-style-type:none;
    -}
    -
    -.index ul, .index h3 {
    -padding-left:1.5em;
    -
    -}
    -
    -#demo {
    -width:80%;
    -height:50em;
    -margin-left:3em;
    -margin-top:3em;
    -position:relative;
    -}
    -
    -.sub {
    -margin-left:1em;
    -}
    -
    -.table td {
    -	text-align:center;
    -}
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.16/usage.html b/demo/doc/archive/1.3.16/usage.html
    deleted file mode 100644
    index a86a24f07..000000000
    --- a/demo/doc/archive/1.3.16/usage.html
    +++ /dev/null
    @@ -1,10 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.16 - documentation</title>		
    -	</head>
    -	
    -	<frameset cols="250,*">
    -  		<frame src="index.html" />
    -  		<frame src="content.html" name="contentFrame" />
    -	</frameset>	
    -</html>
    diff --git a/demo/doc/archive/1.3.2/content.html b/demo/doc/archive/1.3.2/content.html
    deleted file mode 100644
    index 19220be16..000000000
    --- a/demo/doc/archive/1.3.2/content.html
    +++ /dev/null
    @@ -1,2293 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.2 - documentation</title>
    -		<link rel="stylesheet" href="/mp.css"></link>
    -		<link rel="stylesheet" href="../css/jsPlumbDemo.css"></link>	
    -	</head>
    -	<body>
    -
    -<div class="menu"><a href="http://jsplumb.org" class="mplink" target="_top">view demo</a>&nbsp;|&nbsp;<a href="mailto:simon.porritt@gmail.com" class="mplink">contact me</a></div>
    -
    -<div class="content">
    -		<div class="section">
    -			<h3><a id="summary">Summary</a></h3>
    -			jsPlumb allows you to connect elements on the screen using SVG, Canvas or VML, depending on the capabilities of the browser.
    -			<p>
    -				It can be used with jQuery, MooTools or YUI3 (or another library of your choice if you
    -				feel like implementing an adapter for it).
    -			</p>  
    -
    -			<h5>Browser Compatibility</h5>
    -			<p>jsPlumb 1.3.2 has been tested on the following browsers:
    -				<ul>
    -					<li class="bullet">IE 6 on Windows XP</li>
    -					<li class="bullet">IE 7 on Windows XP</li>
    -					<li class="bullet">IE 8 on Windows XP</li>
    -					<li class="bullet">Firefox 3.5.8 on Windows XP</li>
    -					
    -					<li class="bullet">IE 9 on Windows 7</li>
    -					<li class="bullet">Chrome 12 on Windows 7</li>
    -					<li class="bullet">Firefox 3.5.8 on Windows 7</li>
    -					
    -					<li class="bullet">Firefox 3.6.3 on Ubuntu 10.04</li>
    -					<li class="bullet">Chrome on Ubuntu 10.04</li>
    -					
    -					<li class="bullet">Safari 4 on Mac Tiger</li>
    -					<li class="bullet">Safari 4 on Windows Vista</li>
    -					<li class="bullet">Safari 5.0.5 on Windows 7</li>
    -					<li class="bullet">Opera 10.54 on Windows XP</li>
    -				</ul>
    -			</p>
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="changes">Changes since version 1.3.1</a></h3>
    -			<p>
    -			Version 1.3.2 contains several bugfixes and sees the reintroduction of the 'container' concept, allowing you to control where in the
    -			DOM jsPlumb adds elements (see <a href="#containerDefault">here</a>).  Since a few 1.3.x versions have been released in a short amount
    -			of time, I've left in the notes from 1.3.1, as there are a few backwards compatibility issues you may need to know about.		
    -			</p>
    -			<h5>Issues Resolved</h5>
    -			<ul>
    -				<li><strong>102</strong> - FF5 requires a default paint style for endpoints</li>
    -				<li><strong>103</strong> - add 'type' member to connectors, endpoints and overlays</li>
    -				<li><strong>104</strong> - do not set label overlay cursor style by default</li>
    -				<li><strong>105</strong> - hoverPaintStyle for endpoints stopped working</li>
    -				<li><strong>107</strong> - binding 'mouseenter' and 'mouseexit' on endpoints doesn't work</li>
    -				<li><strong>109</strong> - maxConnections:-1 does not allow using endpoint as source</li>
    -				<li><strong>112</strong> - add class to SVG container div</li>
    -				<li><strong>114</strong> - 'drawEndpoints:false' can cause connection paint errors.</li>
    -				<li><strong>113</strong> - render mode not set for extra instances of jsPlumb</li>
    -				<li><strong>115</strong> - reinstate 'container' concept</li>
    -				<li><strong>116</strong> - endpoint clone problem</li>
    -				<li><strong>117</strong> - jsPlumb.hide() issue in IE7 and IE8</li>
    -				<li><strong>119</strong> - inconsistent behaviour of dynamic anchors when multiple connections share an anchor.</li>
    -			</ul>
    -			<p>
    -			Version 1.3.1 was a major new release of jsPlumb, containing several important new features, and a few changes that break backwards compatibility.
    -			</p>
    -			<p>
    -			Major new features include:
    -			</p>			
    -			<ul>
    -				<li>- introduced support for using SVG</li>
    -				<li>- removed dependency on Excanvas script for IE&lt;9. Jsplumb draws VML directly now.</li>
    -				<li>- introduced support for mouse events (click/doubleclick/enter/exit) for all browsers</li>
    -				<li>- introduced support for turning an entire element into a drag target for new connections</li>
    -			</ul>
    -			<p>
    -			Backwards Compatibility Issues:			
    -			</p>
    -			<ul>
    -				<li>
    -					<p>- you cannot create Endpoints/Connectors/Overlays directly anymore.  You must supply jsPlumb with a definition of what you
    -					 want, and it creates the actual object.  For example:</p>
    -	  				<pre>
    -OLD: 	jsPlumb.Defaults.Endpoint = new jsPlumb.Endpoints.Dot(45);
    -NEW:	jsPlumb.Defaults.Endpoint = [ "Dot", { radius:45 } ];
    -	  				</pre>	
    -				</li>					  	  	  	
    -			
    -				<li>
    -					<p>- Bezier connection requires a js object as argument now, instead of a single int</p>
    -					<pre>
    -OLD:  	connector: [ "Bezier", 150 ]
    -NEW: 	connector: [ "Bezier", { curviness:150 } ]
    -					</pre>
    -				</li>
    -				<li>
    -					<p>- Dot Endpoint requires a js object as argument now, instead of a single int</p>
    -					<pre>
    -OLD:	endpoint: [ "Dot", 10 ]
    -NEW:	endpoint: [ "Dot", { radius:10 } ]
    -					</pre>		
    -				</li>
    -				<li>
    -					<p>- getConnections has been reworked a little to make it easier to use.  See the API documentation.</p>
    -				</li>
    -				<li>
    -				<p>- The 'container' concept, as an argument to addEndpoint or connect, and in the jsPlumb defaults, has been 
    -				removed.  Created elements are now appended to the parent of the source element of a connection, or the parent of an element
    -				to which you are adding an Endpoint.
    -				</p>  
    -				</li>
    -				<li>
    -					<p>- The 'backgroundPaintStyle' concept has been replaced with 'outlineColor' and 'outlineWidth' members in paint style definitions.
    -					To use these, you must supply at least 'outlineWidth'; if 'outlineColor' is not supplied, jsPlumb will use black by default.
    -					</p>
    -				</li>
    -				<li>
    -					<p>- Functionality to drag an entire connection, by clicking and dragging the connector line, which was introduced for some browsers 
    -					in 1.2.6, has been removed.  I had several requests for a way to disable this, and got the distinct impression it was not
    -					very popular.
    -					</p>		
    -				</li>
    -				<li>
    -					<p>- The 'setDefaultNewCanvasSize' method on jsPlumb has been removed.  jsPlumb no longer uses excanvas to render
    -	  				VML; this method was there to support edge cases of VML usage. You probably were not even aware of this method's existence.
    -	  				</p>		
    -				</li>		
    -			</ul>
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="init">Setup</a></h3>
    -
    -<h4><a id="imports">Required Imports</a></h4>	
    -							<h4>jQuery</h4>
    -			<ul>
    -				<li class="bullet">jQuery 1.3.x or higher.</li>
    -				<li class="bullet">jQuery UI 1.7.x or 1.8.x (if you wish to support drag and drop).</li>
    -			</ul>
    -			<div class="code">
    -				<pre>&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.0/jquery.min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/jquery-ui.min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/jquery.jsPlumb-1.3.2-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			<h4>MooTools</h4>
    -				<ul>
    -					<li class="bullet">MooTools Core 1.2.4 or higher. jsPlumb has been tested with 1.2.4 and 1.3.2.</li>
    -					<li class="bullet">Drag.Move from MooTools More 1.2.4.4 or higher (if you wish to support drag and drop).</li>
    -				</ul>
    -			<div class="code">
    -			<pre>&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/mootools/1.3.2/mootools-yui-compressed.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO_MOO_TOOLS_MORE_1_3_2_1"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/mootools.jsPlumb-1.3.2-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			<h4>YUI3</h4>
    -			<ul>
    -				<li class="bullet">YUI 3.3.x.  jsPlumb has been tested on 3.3.0 only; it is possible other 3.x.x versions will work.</li>
    -			</ul>
    -			<div class="code">
    -			<pre>&lt;script type="text/javascript" src="http://yui.yahooapis.com/3.3.0/build/simpleyui/simpleyui-min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/yui.jsPlumb-1.3.2-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			
    -			<h4><a id="renderMode">Render Mode</a></h4>
    -			jsPlumb can use SVG, HTML5 Canvas elements or VML to render the various objects it adds to the display.  Most modern browsers support
    -			Canvas and SVG; IE&lt;9 browsers support neither.
    -			<p>
    -				By default, jsPlumb attempts to use <strong>jsPlumb.CANVAS</strong> as the render mode, falling back to <strong>jsPlumb.VML</strong> if the 
    -				browser is IE&lt;9.  You can change the render mode by making this call:  
    -			</p>
    -			<div class="code">
    -<pre>jsPlumb.setRenderMode(jsPlumb.SVG);</pre>			
    -			</div>
    -			if, for some reason, you set the render mode to be <strong>jsPlumb.VML</strong> but you're in any browser other than IE&lt;9, jsPlumb
    -			will use Canvas.
    -			<p>
    -			The <strong>jsPlumb.setRenderMode</strong> method returns you the render mode that jsPlumb actually ended up setting.  Valid values for setRenderMode are:
    -			</p>
    -			<ul>
    -				<li class="bullet">jsPlumb.CANVAS</li>
    -				<li class="bullet">jsPlumb.SVG</li>
    -				<li class="bullet">jsPlumb.VML</li>
    -			</ul>			
    -			
    -			<h4><a id="initializing">Initializing jsPlumb</a></h4>
    -			You should not start making calls to jsPlumb until the DOM has been initialized - perhaps no surprises there.  With YUI, though, the asynchronous
    -			nature of the script loading process means that you are not guaranteed that jsPlumb is ready as soon as the DOM is.  To handle this,
    -			you should bind to the "ready" event on jsPlumb (or the instance of jsPlumb you are working with):
    -			<div class="code">
    -			<pre>jsPlumb.bind("ready", function() {
    -...	
    -		
    -// your jsPlumb related init code goes here
    -
    -...
    -
    -});						
    -			</pre>
    -			</div>
    -			In 1.3.2 a helper method was added that can save you approximately 9 (count them!) characters:
    -			<div class="code">
    -			<pre>jsPlumb.ready(function() {
    -...	
    -		
    -// your jsPlumb related init code goes here
    -
    -...
    -
    -});						
    -			</pre>
    -			</div>
    -			<p>
    -			For jQuery and MooTools you do not actually need to do this; by the time the DOM ready event is fired in each of those libraries you can be sure 
    -			all the JS we need has been loaded.  But in terms of explicitly calling out that you are waiting for jsPlumb, it seems like a good practice to bind to the "ready" event.
    -			</p>
    -			<p>
    -			If you bind to the "ready" event after jsPlumb has already been initialized, your callback will be executed immediately.
    -			</p>			
    -			<h4><a id="unload">Unloading jsPlumb</a></h4>
    -			jsPlumb offers a method you can call when your page is unloading.  You should do this to insure
    -			against memory leaks.  You configure it like this:
    -				<div class="code">
    -<pre>
    -&lt;body onunload="jsPlumb.unload();"&gt;
    -
    -...
    -
    -&lt;/body&gt;
    -</pre>
    -				</div>
    -<h4><a id="multipleInstances">Multiple jsPlumb instances</a></h4>
    -			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 <strong>getInstance</strong> method, for example:
    -<div class="code">
    -<pre>
    -var firstInstance = jsPlumb.getInstance();
    -</pre>
    -</div>
    -The variable 'firstInstance' can now be treated exactly as you would treat the 'jsPlumb' variable - you can set
    -defaults, call the connect method, whatever:
    -<div class="code">
    -<pre>
    -firstInstance.Defaults.Connector = [ "Bezier", { curviness: 150 } ];
    -firstInstance.Defaults.Anchors = [ "TopCenter", "BottomCenter" ];
    -
    -firstInstance.connect({source:"element1", target:"element2", scope:"someScope" });
    -</pre>
    -</div>
    -
    -<strong>getInstance</strong> optionally takes an object that provides the defaults:
    -<div class="code">
    -<pre>
    -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" });
    -</pre>
    -</div>
    -
    -		<h4><a id="zIndex">Z-index Considerations</a></h4>
    -		<p>
    -			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. 
    -		</p>  
    -		<p>
    -			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).  			
    -		</p>
    -		<p>
    -			To help you organise z-indices correctly, jsPlumb adds a CSS class to each type of
    -			element it adds. They are as follows:			
    -		</p>
    -		<table style="color:black;width:70%;font-size:90%;">
    -			<tr><td><strong>type of element</strong></td><td><strong>class added</strong></td></tr>
    -			<tr><td>Endpoint</td><td>_jsPlumb_endpoint</td></tr>
    -			<tr><td>Connector</td><td>_jsPlumb_connector</td></tr>
    -			<tr><td>Overlay</td><td>_jsPlumb_overlay</td></tr>
    -		</table>
    -
    -		<h4><a id="containerDefault">Where does jsPlumb add elements?</a></h4>
    -		<p>
    -			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.
    -		</p>
    -		<p>
    -			Prior to 1.3.0, jsPlumb added everything to the end of the <em>body</em> element. This
    -			has the advantage of being the most flexible arrangement in terms of supporting what
    -			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!   
    -		</p>
    -		<p>
    -			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 (<strong>not</strong> 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.
    -		</p>
    -		
    -		<p>
    -			In general, the default behaviour means that you cannot do things like this:
    -		</p>
    -		<div class="code">
    -<pre>
    -&lt;body&gt;
    -	&lt;div id="container0"&gt;
    -		&lt;div id="node0"&gt;&lt;/div&gt;
    -	&lt;/div&gt;
    -	&lt;div id="container1"&gt;
    -		&lt;div id="node1"&gt;&lt;/div&gt;
    -	&lt;/div&gt;
    -&lt;/body&gt;
    -
    -&lt;script type="text/javascript"&gt;
    -
    -	var e0 = jsPlumb.addEndpoint("node0"),
    -	e1 = jsPlumb.addEndpoint("node1");
    -
    -	jsPlumb.connect({ source:e0, target:e1 });
    -&lt;/script&gt;
    -</pre>		
    -		</div>
    -<p>
    -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):  
    -</p>
    -		<div class="code">
    -<pre>
    -&lt;body&gt;
    -	&lt;div id="container0"&gt;
    -	&lt;/div&gt;
    -	&lt;div id="container1"&gt;
    -	&lt;/div&gt;
    -&lt;/body&gt;
    -
    -&lt;script type="text/javascript"&gt;
    -
    -	var e0 = jsPlumb.addEndpoint("container0"),
    -	e1 = jsPlumb.addEndpoint("container1");
    -
    -	jsPlumb.connect({ source:e0, target:e1 });
    -&lt;/script&gt;
    -</pre>		
    -		</div>
    -	now 'e0' and 'e1' have the same parent - the <em>body</em> element. 
    -	
    -	<p>Remember that you can use the <strong>anchor</strong> parameter to an <strong>addEndpoint</strong>
    -	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. 
    -	</p>									
    -	<p><strong>Note regarding the drawEndpoints option on jsPlumb.connect</strong>: 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). 
    -	</p>
    -	
    -	<h5>Overriding the default behaviour</h5>
    -	<p>
    -		From version 1.3.2, the <strong>container</strong> 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 <strong>jsPlumb.Defaults.Container</strong> property,
    -		or you can set a <strong>container</strong> parameter on calls to <strong>addEndpoint</strong> or <strong>connect</strong> (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:
    -	</p>
    -		
    -		Set a container to use as the default container, using a jQuery selector (you can supply MooTools/YUI selectors if that's the library you're using),
    -		and then add an Endpoint.  The canvas (or SVG/VML element) created will be a child of the document body:
    -		<div class="code">
    -<pre>
    -jsPlumb.Defaults.Container = $("body");
    -...
    -jsPlumb.addEndpoint(someDiv, { endpoint options });
    -</pre>		
    -		</div>
    -		
    -			Set a container to use as the default container, using an element id,
    -		and then connect two elements.  The canvases (or SVG/VML elements) created in this example will be children of the element with id "containerId":
    -		<div class="code">
    -<pre>
    -jsPlumb.Defaults.Container = "containerId";
    -...
    -jsPlumb.connect({ source:someDiv, target:someOtherDiv });
    -</pre>		
    -		</div>
    -		
    -		Pass a container to use into a jsPlumb.connect call, using a selector.  The canvases (or SVG/VML elements) created in this example will be children of the element with id "containerId":
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({ source:someDiv, target:someOtherDiv, container:$("#containerId") });
    -</pre>		
    -		</div>
    -				Pass a container to use into a jsPlumb.addEndpoint call, using an element id. The canvas (or SVG/VML element) created in this example will be a child of the element with id "containerId":
    -		<div class="code">
    -<pre>
    -jsPlumb.addEndpoint(someDiv, { ..., container:"containerId" });
    -</pre>		
    -		</div>
    -		
    -		</div>
    -				
    -		<div class="section">
    -			<h3><a id="jsPlumbBasics">Basic Concepts</a></h3>
    -	
    -			jsPlumb is all about connecting things together, so the core abstraction in jsPlumb is the <strong>Connection</strong> object, which is itself
    -			broken down into these four concepts:
    -
    -			<ul>
    -				<li><strong>Anchor</strong> - a location, relative to an element's origin, at which an Endpoint can exist. You do not create these yourself; you supply hints to the various jsPlumb functions, which create them as needed.  They have no visual representation; they are a logical position only. Anchors can be referenced by name, for the Anchors that jsPlumb ships with, or a four element array describing [ x, y, x orientation, y orientation ]. See the <a href="#anchors">anchors</a> section for more detail.<br/><br/></li>
    -				<li><strong>Endpoint</strong> - the visual representation of one end of a Connection.  You can create and attach these to elements 
    -				yourself, which you are required to do to support drag and drop, or have jsPlumb create them when creating a Connection 
    -				programmatically using jsPlumb.connect(...).  You can also join two Endpoints programmatically, by passing them as arguments to 
    -				jsPlumb.connect(...).<br/><br/></li>				
    -				<li><strong>Connector</strong> - the visual representation of the line connecting two elements in the page.  jsPlumb has three types of these available as defaults - a Bezier curve, a straight line, and 'flowchart' connectors. You do not interact with Connectors; you just specify definitions of them when you need to - see <a href="#definitions">Connector, Endpoint & Overlay Definitions</a> <br/><br/></li>
    -				<li><strong>Overlay</strong> - a UI component that is used to decorate a Connector, such as a label, arrow, etc.<br/><br/></li>
    -			</ul>
    -						
    -			<p>One Connection is made up of two Endpoints, a Connector, and zero or more Overlays working together to join two elements.</p>
    -			<p>jsPlumb's public API exposes only Connection and Endpoint, handling the creation and configuration of everything else internally.
    -			But you still need to be across the concepts encapsulated by Anchor, Connector and Overlay.
    -			</p>
    -			</div>
    -			
    -			<div class="section">
    -			<h3><a id="anchors">Anchors</a></h3>			
    -			<p>An Anchor models the notion of where on an element a Connector should connect.  jsPlumb has nine default anchor locations you
    -			can use to specify where the Connectors connect to elements: these are the four corners of an element,
    -			the center of the element, and the midpoint of each edge of the element:  
    -			<ul>
    -				<li class="bullet">TopCenter</li>
    -				<li class="bullet">TopRight</li>
    -				<li class="bullet">RightMiddle</li>
    -				<li class="bullet">BottomRight</li>
    -				<li class="bullet">BottomCenter</li>
    -				<li class="bullet">BottomLeft</li>
    -				<li class="bullet">LeftMiddle</li>
    -				<li class="bullet">TopLeft</li>
    -			</ul>
    -			
    -			Each of these string representations is just a wrapper around
    -			the underlying array-based syntax, for example: 	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:"BottomCenter", ... }); 
    -</pre>
    -</div>
    -is identical to:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:[ 0.5, 1, 0, 1 ], ... }); 
    -</pre>
    -</div>			
    -
    -		<h3><a id="dynamicAnchors">Dynamic Anchors</a></h3>
    -	These are Anchors that can be positioned in one of a number of locations, choosing the one that is most appropriate each time something moves or is painted in the UI.
    -	<p>
    -	There is no special syntax to creating a DynamicAnchor; you just provide an array of individual Anchor specifications, eg:
    -	</p>
    -<div class="code">
    -<pre>
    -var dynamicAnchors = [ [ 0.2, 0, 0, -1 ],  [ 1, 0.2, 1, 0 ], 
    -			   [ 0.8, 1, 0, 1 ], [ 0, 0.8, -1, 0 ] ];
    -</pre>
    -</div>
    -<h4>	Default Dynamic Anchor</h4>
    -jsPlumb provides a dynamic anchor called "AutoDefault" that chooses from TopCenter, RightMiddle, BottomCenter and LeftMiddle:
    -<div class="code">
    -<pre>jsPlumb.connect({...., anchor:"AutoDefault", ... });</pre>
    -</div>
    -
    -<h4>	Location Selection</h4>
    -The initial implementation of the algorithm that decides which location to choose just calculates which location is closest to the center of the 
    -other element in the connection.  It is possible that future versions of jsPlumb could support more sophisticated choice algorithms, if the need arose.
    -<h4>Draggable Connections</h4>
    -Dynamic Anchors and Draggable Connections can interoperate: jsPlumb locks the position of a dynamic anchor when you start to drag a connection from it,
    -and unlocks it once the connection is either established or abandoned. At that point you may see the position of the dynamic anchor change, as jsPlumb
    -optimises the connection.  <p>You can see this behaviour in the <a href="../html/jquery/draggableConnectorsDemo.html" target="_blank">draggable connections</a> demonstration, when
    -you drag a connection from the blue endpoint on window 1 to the blue endpoint on window 3 - the connection is established and then window 1's blue
    -endpoint jumps down to a location that is closer to window 3.</p> 	  
    -					</div>		
    -												
    -<div class="section">
    -		<h4><a id="definitions">Connector, Endpoint & Overlay Definitions</a></h4>
    -		Before we discuss Connector, Endpoints and Overlays, a quick word on definitions:  whenever you need to define a Connector, Endpoint or Overlay, you must use a "definition" of it, rather than constructing one
    -		directly.  This definition can be either a string that nominates the artifact you want to create:
    -		<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:"Rectangle"
    -});
    -</pre>
    -		</div>
    -		or an array consisting of both the artifact's name and the arguments you want to pass to its constructor:
    -		<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Rectangle", { cssClass:"myEndpoint", width:30, height:10 } ]
    -});
    -</pre>
    -		</div>
    -		there is also a three-argument method that allows you to specify two sets of parameters, which jsPlumb will merge together for you. The idea
    -		behind this is that you will often want to define common characteristics somewhere and reuse them across a bunch of different calls:
    -		<div class="code">
    -<pre>var common = {
    -	cssClass:"myCssClass"
    -};
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Rectangle", { width:30, height:10 }, common ]
    -});
    -</pre>
    -		</div>
    -	This syntax is supported for all Endpoint, Connector and Overlay definitions.  Here's an example using definitions for all three:	
    -		<div class="code">
    -<pre>var common = {
    -	cssClass:"myCssClass"
    -};
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Dot", { radius:5 }, common ],
    -	connector:[ "Bezier", { curviness:100 }, common ],
    -	overlays: [
    -		[ "Arrow", { foldback:0.2 }, common ],
    -		[ "Label", { cssClass:"labelClass" } ]	
    -	]
    -});
    -</pre>
    -		</div>		
    -		
    -		The allowed constructor parameters are different for each artifact you create, but every artifact takes a single JS object as argument,
    -		with the parameters as [key,value] pairs in that object.  See the relevant sections on <a href="#connectors">Connectors</a>,
    -		<a href="#endpoints">Endpoints</a> and <a href="#overlays">Overlays for information on supported parameters.</a>
    -				
    -	</div>
    -
    -
    -		<div class="section">
    -			<h3><a id="connectors">Connectors</a></h3>
    -			<p>Connectors are the lines that actually join elements of the UI.  jsPlumb has three connector implementations - a straight line, a Bezier curve, and
    -			"flowchart" connectors.  The default connector is the Bezier curve.  </p>
    -			<p>You optionally specify a Connector by setting the 'connector' property on a call to jsPlumb.connect or jsPlumb.addEndpoint(s). If you do not
    -			supply a value for 'connector', the default will be used (see <a href="#defaults">defaults</a>).</p>
    -			<p>You specify Connectors using the syntax described above in <a href="#definitions">Connector, Endpoint & Overlay Definitions.</a>Allowed 
    -			constructor values for each Connector type are described below:</p>
    -			
    -			<h4><a id="bezierConnector">Bezier Connector</a></h4>
    -			<p>The Bezier Connector provides a cubic Bezier path between the two Endpoints. It supports a single constructor argument:</p>
    -			<p>
    -			<strong>curviness</strong> - Optional; defaults to 150. This defines the distance in pixels that
    -			the Bezier's control points are situated from the anchor points.  This does not mean that your
    -			connector will pass through a point at this distance from your curve.  It is a hint to how you want the
    -			curve to travel. Rather than discuss Bezier curves at length here, because they are a complex topic,
    -			we refer you to <a href="http://en.wikipedia.org/wiki/B%C3%A9zier_curve" target="_blank">Wikipedia.</a>
    -			</p>
    -			<h4><a id="straightConnector">Straight Connector</a></h4>
    -			<p>The Straight Connector draws a straight line between the two endpoints. No constructor arguments are supported; use the <strong>endpointStyle</strong>
    -			argument to a connect or addEndpoint call to control the appearance of one of these Connectors.  
    -			</p>
    -			<h4><a id="flowchartConnector">Flowchart Connector</a></h4>
    -			<p>This type of Connector draws a connection that consists of a series of vertical or horizontal segments - the classic flowchart look. A single constructor argument is supported:</p>
    -			<strong>stub</strong> - this is the minimum length, in pixels, of the initial stub that emanates from an Endpoint.  This parameter is optional, and defaults to 30 pixels.
    -			
    -		</div>
    -		
    -			<div class="section">
    -			<h3><a id="endpoints">Endpoint Types</a></h3>
    -			<p>An Endpoint is the UI component that marks the location of an Anchor, ie. the place where a Connector joins an
    -			element.  jsPlumb comes with three Endpoint implementations - Dot, Rectangle and Image. You optionally specify Endpoint properties using the
    -			<strong>endpoint</strong> parameter in a call to jsPlumb.connect, jsPlumb.addEndpoint or jsPlumb.makeTarget.
    -			</p>  
    -				<p>As with Connectors and Overlays, you specify Endpoints using the syntax described above in <a href="#definitions">Connector, Endpoint & Overlay Definitions.</a></p>
    -			
    -			The three available Endpoint types, and their constructor arguments, are as follows:
    -					<h4><a id="dotEndpoint">Dot Endpoint</a></h4>
    -					This Endpoint draw a dot on the screen. It supports a single constructor parameter:
    -					<p>
    -						<strong>radius</strong> - Optional; defaults to 10 pixels. Defines the radius of the dot.
    -					</p>
    -					<h4><a id="rectangleEndpoint">Rectangle Endpoint</a></h4>
    -						Draws a rectangle. Supported constructor parameters are:
    -					<p>
    -						<strong>width</strong> - Optional; defaults to 20 pixels. Defines the width of the rectangle.
    -					</p>
    -					<p>
    -						<strong>height</strong> - Optional; defaults to 20 pixels. Defines the height of the rectangle.
    -					</p>
    -					
    -					<h4><a id="imageEndpoint">Image Endpoint</a></h4>
    -						Draws an image from a given URL.  This Endpoint supports one constructor parameter:  
    -					<p>
    -						<strong>src</strong> - Required.  Specifies the URL of the image to use.
    -					</p>
    -					
    -									
    -			
    -			
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="overlays">Overlay Types</a></h3>
    -			<p>
    -			Overlays are UI elements that are painted onto connections, such as labels or arrows.
    -			jsPlumb comes with four defaults:
    -			</p>
    -			<ul>
    -				<li><strong>Arrow</strong> - a configurable arrow that is painted at some point along the connector.  You can control the length and width of the Arrow, the 'foldback' point - a point the tail points fold back into, and the direction (allowed values are 1 and -1; 1 is the default and means point in the direction of the connection)</li>
    -				<li><strong>Label</strong> - a configurable label that is painted at some point along the connector.</li>
    -				<li><strong>PlainArrow</strong> - an Arrow shaped as a triangle, with no foldback.</li>
    -				<li><strong>Diamond</strong> - as the name suggests, a diamond.</li>
    -			</ul> 
    -			The last two are actually just configured instances of the generic Arrow overlay (see examples). 
    -			
    -			<p>You can specify one or more overlays when making a call to jsPlumb.connect or jsPlumb.addEndpoint (but not jsPlumb.makeTarget; overlays
    -			are always derived from what the source of a Connection defines)  The two cases are discussed below:</p>
    -			1. Specifying one or more overlays on a jsPlumb.connect call. In this example we'll create an Arrow with the default options for an Arrow, and
    -			a label with the text "foo":
    -			<div class="code">
    -			<pre>jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 } ]
    -	],
    -	...
    -});			
    -</pre>
    -			</div>
    -			This connection will have an arrow located halfway along it, and the label "foo" one quarter of the way along.<br/><br/> 
    -			
    -			2. Specifying one or more overlays on a jsPlumb.addEndpoint call.  Note in this example that we use the parameter 'connectorOverlays' and not
    -			'overlays' as in the last example.  This is because 'overlays' will one day be used to support Endpoint Overlays:
    -			<div class="code">
    -			<pre>jsPlumb.addEndpoint("someDiv", {
    -	...
    -	connectorOverlays:[ 
    -		[ "Arrow", { width:10, length:30, location:1 } ], 
    -		[ "Label", { label:"foo" } ]
    -	],
    -	...
    -});</pre>
    -			</div>	
    -			This connection will have a 10x30 Arrow located right at the head of the connection, and the label "foo" located at the halfway point.<br/><br/>
    -			
    -			 
    -	
    -					<h4><a id="arrowOverlay">Arrow Overlay</a></h4>
    -					<p>This overlay draws an arrow, using four points: the head and two tail points, and a 'foldback' point, which permits the tail
    -					of the arrow to be indented. Available constructor arguments for this Overlay are </p>
    -					<ul>
    -					<li><strong>width</strong> - width of the tail of the arrow</li>
    -					<li><strong>length</strong> - distance from the tail of the arrow to the head</li>
    -					<li><strong>location</strong> - where, as a proportional value from 0 to 1 inclusive, the Arrow should appear on the Connector</li>
    -					<li><strong>direction</strong> - which way to point. Allowed values are 1 (the default, meaning forwards) and -1, meaning backwards </li>
    -					<li><strong>foldback</strong> - how far along the axis of the arrow the tail points foldback in to. Default is 0.623.</li>
    -					<li><strong>paintStyle</strong> - a style object in the form used for paintStyle values for Endpoints and Connectors</li>
    -					</ul>
    -					<h4><a id="plainArrowOverlay">PlainArrow Overlay</a></h4>
    -					<p>This is just a specialized instance of Arrow in which jsPlumb hardcodes 'foldback' to 1, meaning the tail of the Arrow is a flat edge.  All of the constructor parameters from Arrow apply for PlainArrow.</p>
    -					<h4><a id="diamondOverlay">Diamond Overlay</a></h4>
    -					<p>This is a specialized instance of Arrow in which jsPlumb hardcodes 'foldback' to 2, meaning the Arrow turns into a Diamond.  All of the constructor parameters from Arrow apply for PlainArrow.</p>
    -					<h4><a id="labelOverlay">Label Overlay</a></h4>
    -					This provides a text label to decorate Connectors with.  The available constructor arguments are:
    -					<ul>
    -						<li><strong>label</strong> - The text to display.  You can provide a function here instead of plain text: it is passed the Connection as an argument, and it should return a String.</li>
    -						<li><strong>cssClass</strong> - Optional css class to use for the Label.  This is now preferred over using the 'labelStyle' parameter.</li>
    -						<li><strong>labelStyle</strong> - (deprecated, use cssClass instead) Optional arguments for the label's appearance.  Valid entries in this JS object are:
    -							<ul>
    -								<li><em>font</em> - a font string in a format suitable for the Canvas element</li>
    -								<li><em>fillStyle</em> - the color to fill the label's background with. Optional.</li>
    -								<li><em>color</em> - the color of the label's text. Optional.</li>
    -								<li><em>padding</em> - optional padding for the label. This is expressed as a proportion of the width of the label, not in pixels or ems.</li>
    -								<li><em>borderWidth</em> - optional width in pixels for the label's border. Defaults to 0.</li>
    -								<li><em>borderStyle</em> - optional. The color to paint the border, if there is one.</li>
    -								
    -							</ul>
    -						
    -						</li>
    -						<li><strong>location</strong> - As for Arrow Overlay.  Where, proportionally from 0 to 1 inclusive, the label should appear.</li>
    -					</ul>
    -	
    -		</div>
    -	
    -			
    -			<div class="section">
    -			<h3><a id="defaults">Defaults</a></h3>
    -			The easiest way to set a look and feel for your plumbing is to override the defaults that jsPlumb uses. If you
    -			do not do this you are forced to provide your overridden values on every call.  Every argument to the connect and addEndpoint methods has an
    -			associated default value in jsPlumb.<br/><br/>
    -
    -			The defaults that ship with jsPlumb are stored in <em>jsPlumb.Defaults</em>, which is a Javascript object.  Valid entries, and their initial values, are:
    -
    -			<div class="code">
    -<pre>
    -Anchor : "BottomCenter",
    -Anchors : [ null, null ],
    -AppendElementsToBody : false,
    -Connector : "Bezier",
    -DragOptions : { },
    -DropOptions : { },
    -Endpoint : "Dot",
    -Endpoints : [ null, null ],
    -EndpointStyle : { fillStyle : null },
    -EndpointStyles : [ null, null ],
    -EndpointHoverStyle : null,
    -EndpointHoverStyles : [ null, null ],
    -HoverPaintStyle : null,
    -LabelStyle : { color : "black" },
    -LogEnabled : false,
    -Overlays : [ ],
    -MaxConnections : 1,
    -MouseEventsEnabled : true,
    -PaintStyle : { lineWidth : 8, strokeStyle : "#456" },
    -RenderMode : "canvas",
    -Scope : "_jsPlumb_DefaultScope"
    -</pre>
    -			</div>
    -			<p>
    -			Note that in EndpointStyle, the default fillStyle is 'null'.  This instructs jsPlumb to use the strokeStyle
    -from the attached connector to fill the endpoint.</p>
    -<p>Note also that you can specify either or both (or neither) of 'EndpointStyle' and 'EndpointStyles'.  This allows you to specify a different
    -end point for each end of a connection.  'Endpoint' and 'Endpoints' use the same concept.  jsPlumb will look first in the
    -individual endpoint/endpoint style arrays, and then fall back to the single default version.</p>
    -
    -			you can override these defaults by including this in a script somewhere:
    -			<div class="code">
    -<pre>
    -jsPlumb.Defaults.PaintStyle = {
    -	lineWidth:13,
    -	strokeStyle: 'rgba(200,0,0,100)'
    -}
    -
    -jsPlumb.Defaults.DragOptions = { cursor: "crosshair" };
    -
    -jsPlumb.Default.Endpoints = [ [ "Dot", 7 ], [ "Dot", 11 ] ];
    -
    -jsPlumb.Defaults.EndpointStyles = [{ fillStyle:"#225588" }, { fillStyle:"#558822" }];</pre>
    -			</div>
    -			after the jsPlumb script has been loaded of course!  Here we have specified the following default behaviour:
    -			<ul>
    -				<li>- connectors are 13 pixels wide and painted with a semi-transparent red line</li>
    -				<li>- when dragging an element the crosshair cursor is used</li>
    -				<li>- the source endpoint is a dot of radius 7; the target endpoint is a dot of radius 11</li>
    -				<li>- the source endpoint is blue; the target endpoint is green</li>
    -			</ul>
    -
    -		</div> 					
    -
    -<div class="section">
    -			<h3><a id="simpleConnections">Connections</a></h3>
    -			<h4><a id="simpleConnections">Programmatic Connections</a></h4>
    -The most simple connection you can make with jsPlumb looks like this:
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({source:"element1", target:"element2"});</pre>			
    -			</div>
    -
    -		In this example we have created a Connection from 'element1' to 'element2'.  Remember that a Connection in jsPlumb consists of two Endpoints,
    -		a Connector, and zero or more Overlays.  But this call to 'connect' supplied none of those things, so jsPlumb uses the default values wherever it needs to.
    -		In this case, default values have been used for the following: 
    -		<ul>
    -			<li>- The type and appearance of each Endpoint in the Connection. jsPlumb's default for this is the "Dot" endpoint, of radius 10, with fill color "#456".</li>
    -			<li>- The Anchors that define where the connection's Endpoints appear on each element. The jsPlumb default is "BottomCenter"</li>			
    -			<li>- Whether or not each Endpoint can be a source or target for new Connections. The default is false.</li>
    -			<li>- The type and appearance of the Connection's Connector. The default is a "Bezier" connector of line width 8, and color "#456".</li>
    -		</ul>
    -		<p>
    -		So this call will result in an 8px Bezier, colored "#456", from the bottom center of 'element1' to the bottom center of 'element2', and each
    -		Endpoint will be a 10px radius Dot Endpoint, colored "#456".
    -		</p>
    -		<p>
    -		Let's beef up this call a little and tell jsPlumb what sort of Endpoints we want, and where we want them:		
    -		</p>
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:"element1", 
    -	target:"element2",
    -	anchors:["RightMiddle", "LeftMiddle" ],
    -	endpoint:"Rectangle",
    -	endpointStyle:{ fillStyle: "yellow" }
    -});
    -</pre>			
    -			</div>
    -			
    -		<p>This is what we have told jsPlumb we want this time:</p>
    -		<ul>
    -			<li><strong>anchors</strong> - this array tells jsPlumb where the source and target Endpoints should be located on their parent elements. In this case,
    -			we use the shorthand syntax to name one of jsPlumb's default anchors; you can also specify custom locations (see <a href="#anchors">anchors</a>).
    -			Instead of <strong>anchors</strong> you can use <strong>anchor</strong>, if you want the source and target Endpoints to be located at the
    -			same place on their parent elements.			<br/>
    -			</li>
    -			<li>
    -				<strong>endpoint</strong>  - this tells jsPlumb to use the "Rectangle" Endpoint for both the source and target of the Connection.  As with anchors, 
    -				<strong>endpoint</strong> has a plural version that allows you to specify a different Endpoint for each end of the Connection.<br/>  
    -			</li>
    -			<li>
    -				<strong>endpointStyle</strong> - this is the definition of the appearance of the Endpoint you specified above.  Again, there is a
    -				plural equivalent of this that allows you to specify a different style for each end of the Connection. For more information about
    -				allowed values for this value, see <a href="#paintstyles">Connector, Endpoint & Overlay Styles</a>. 
    -			</li>
    -		</ul>		
    -		
    -		<h5>Reusing common settings between jsPlumb.connect calls</h5>
    -		<p>
    -			A fairly common situation you will find yourself in is wanting to create a bunch of Connections that have only minor differences
    -			between them.  To support that, <strong>jsPlumb.connect</strong> takes an optional second argument. For example:			 
    -		</p>
    -		<div class="code">
    -<pre>
    -var common = {
    -	anchors:[ "BottomCenter", "TopCenter" ],
    -	endpoints:["Dot", "Blank" ]
    -};
    -
    -jsPlumb.connect({ source:"someElement", target:"someOtherElement" }, common);
    -
    -jsPlumb.connect({ source:"aThirdElement", target:"yetAnotherElement" }, common);
    -
    -</pre>			
    -			</div>							
    -					
    -		<h4><a id="draggableConnections">Connections using Drag and Drop</a></h4>
    -		To support drag and drop connections, you first need to set a few things up.  Every drag and drop connection needs at least a source
    -		Endpoint that the user can drag a connection from. Here's a simple example of how to create an Endpoint:
    -		<div class="code">
    -<pre>
    -var endpointOptions = { isSource:true };
    -var endpoint = jsPlumb.addEndpoint('elementId', endpointOptions);
    -</pre>		
    -</div>  	
    -	This Endpoint will act as a source for new Connections, and will use the jsPlumb defaults for its own appearance and that of any Connections that are drawn from it.							
    -	
    -		<h5>Tip: use the three-argument addEndpoint method for common data </h5>
    -		One thing that happens quite often is that you have an Endpoint whose appearance and behaviour is largely the
    -		same between usages on different elements, with just a few differences. 
    -		<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -	endpoint:"Rectangle",
    -	paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -	isSource:true,
    -	connectorStyle : { strokeStyle:"#666" },
    -	isTarget:true
    -};
    -</pre>		
    -</div>  		
    -		Notice there is no 'anchor' set.  Here we apply it to two elements, at a different location in each:
    -		<div class="code">
    -<pre>
    -jsPlumb.addEndpoint('element1', { anchor:"BottomCenter" }, exampleGreyEndpointOptions)); 
    -
    -jsPlumb.addEndpoint('element2', { anchor:"TopCenter" }, exampleGreyEndpointOptions));
    -</pre>		
    -</div>
    -
    -<p>
    -Now that you have a source Endpoint, you need to either create a target Endpoint on some element, or notify jsPlumb that you wish to make an
    -entire element a drop target.  Let's look at how to attach a target Endpoint first:
    -</p>
    -
    -		<div class="code">
    -<pre>var endpointOptions = { isTarget:true, endpoint:"Rectangle", paintStyle:{ fillStyle:"gray" } };
    -var endpoint = jsPlumb.addEndpoint("otherElementId", endpointOptions);
    -</pre>		
    -		</div>
    -		<p>
    -		This Endpoint, a gray rectangle, has declared that it can act as a drop target for Connections. 
    -</p>
    -<p>
    -Your other option for creating a drag and drop target is to make an entire element a drop target, using the <strong>jsPlumb.makeTarget</strong> method.
    -This method takes two arguments, the first of which specifies some element (or list of elements); the second specifies the Endpoint you wish to create on 
    -that element whenever a Connection is established on it.  In this example we will use the exact same target Endpoint we used before - the gray
    -rectangle - but we will tell jsPlumb that the element "aTargetDiv" will be the drop target:
    -</p>
    -		<div class="code">
    -<pre>var endpointOptions = { isTarget:true, endpoint:"Rectangle", paintStyle:{ fillStyle:"gray" } };
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -<p>
    -	The allowed values in 'endpointOptions' are identical for both the <strong>jsPlumb.addEndpoint</strong> and <strong>jsPlumb.makeTarget</strong> methods.
    -</p>		
    -		
    -		
    -<p>
    -There are many things you can set in an Endpoint options object; for a thorough list see the API documentation for Endpoint.  
    -</p>
    -<p>Here's an example of specifying that you want an Arrow overlay halfway along any Connection dragged from this Endpoint:</p>
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -	endpoint:"Rectangle",
    -	paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -	isSource:true,
    -	connectorStyle : { strokeStyle:"#666" },
    -	isTarget:true,
    -	connectorOverlays: [ [ "Arrow", { location:0.5 } ] ]
    -};
    -</pre>
    -</div>
    -This is an Endpoint that moves around the element it is attached to dependent on the location of other elements in the connections it is attached to
    -(a 'dynamic' anchor):
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -	endpoint:"Rectangle",
    -	paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -	isSource:true,
    -	connectorStyle : { strokeStyle:"#666" },
    -	isTarget:true,
    -	connectorOverlays: [ [ "Arrow", { location:0.5 } ] ]
    -	anchor:[ "TopCenter","RightMiddle","BottomCenter","LeftMiddle" ]
    -};
    -</pre>
    -</div>
    -
    -<h5><a id="dragOptions">Drag Options</a></h5>
    -	These are options that will be passed through to the supporting library's drag API.  jsPlumb passes everything you supply here through, inserting
    -	wrapping functions if necessary for the various lifecycle events that jsPlumb needs to know about.  So if, for example, you pass a function to be
    -	called when dragging starts, jsPlumb will wrap that function with a function that does what jsPlumb needs to do, then call yours.
    -	<p>
    -	At the time of writing, jsPlumb supports jQuery, MooTools and YUI3, and each of those libraries uses different terminology.  In addition, jQuery's API is
    -	more fully featured, providing easy support for setting the zIndex and opacity of elements being dragged, as well as the 'scope' for a drag/drop (allowing
    -	you to specify more than one type of drag/drop pair), and hover classes for when a draggable is on the move or
    -	over a droppable. If you're using jQuery you can of course just supply these values on the dragOptions; to make it easier, jsPlumb's MooTools and YUI3 adapters recognize 
    -	these options and add appropriate callbacks for you.  
    -	</p> 
    -	<p>
    -	Given that the options here are library-specific, and they are all well-documented, we're going to discuss just the three drag options
    -	that behave the same way in all (see below for hoverClass):
    -		<ul>
    -		<li>opacity - the opacity of an element that is being dragged.  Should be a fraction between 0 and 1 inclusive.</li>
    -		<li>zIndex - the zIndex of an element that is being dragged.</li>
    -		<li>scope - the scope of the draggable.  can only be dropped on a droppable with the same scope.  this is discussed below.</li>
    -		</ul> 
    -	</p>
    -	
    -	<p>For more information about drag options, take a look at the <a href="http://docs.jquery.com/UI/Draggable" target="_blank">jQuery</a>, <a href="http://mootools.net/docs/more/Drag/Drag.Move" target="_blank">MooTools</a>, or <a href="http://developer.yahoo.com/yui/3/dd/" target="_blank">YUI3</a> docs.	
    -	</p>
    -	<p><strong>NOTE: there is an issue in Chrome that affects the 'cursor' argument to drag options in jQuery. See these links:</strong>
    -	</p><p>
    -<a href="http://forum.jquery.com/topic/draggable-cursor-option-does-not-work" target="_blank">http://forum.jquery.com/topic/draggable-cursor-option-does-not-work</a><br/>
    -<a href="http://forum.jquery.com/topic/chrome-text-select-cursor-on-drag" target="_blank">http://forum.jquery.com/topic/chrome-text-select-cursor-on-drag</a>
    -</p>
    -You should put something like this at the top of your JS to avoid it (perhaps not something so drastic as a document-wide override):	
    -<div class="code">
    -<pre>document.onselectstart = function () { return false; };</pre>
    -</div>		
    -	
    -	<h5><a id="dropOptions">Drop Options</a></h5>
    -	Drop options are treated by jsPlumb in the same way as drag options - they are passed through to the underlying library.  MooTools does not have drop options like jQuery and YUI3 do; droppable functionality in MooTools 
    -	is actually implemented by the Drag.Move class - the one used to initialise a draggable.  But when you setup an Endpoint in jsPlumb you should ignore
    -	that fact, and treat droppables like you would in jQuery or YUI3.  jsPlumb wires everything up for you under the hood.
    -	<p>There are three jQuery droppable options that jsPlumb treats as shortcuts in MooTools and YUI3, for the sake of consistency:</p>
    -		<ul>
    -			<li>hoverClass - the CSS class to attach to the droppable when a draggable is hovering over it.</li>
    -			<li>activeClass - the CSS class to attach to the droppable when a draggable is, um, being dragged.</li>
    -			<li>scope - the scope of the draggable.  The draggable can only be dropped on a droppable with the same scope.  This is discussed below.</li>
    -		</ul>   
    -	<p>For more information about drop options when using jQuery, see <a href="http://docs.jquery.com/UI/Droppable" target="_blank">here</a>.</p>
    -		
    -	<h5><a id="dragAndDropScope">Drag and Drop Scope</a></h5>
    -		jsPlumb borrowed the concept of 'scope' from jQuery's drag/drop implementation: the notion of
    -		which draggables can be dropped on which droppables.  In jsPlumb you can provide a 'scope' entry when 
    -		creating an Endpoint.  Here's the example grey Endpoint example with 'scope' added:
    -		
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -	endpoint:"Rectangle",
    -	paintStyle:{ width:25, height:21, fillStyle:"#666" },
    -	isSource:true,
    -	connectionStyle : { strokeStyle:"#666" },
    -	isTarget:true,
    -	scope:"exampleGreyConnection"
    -};</pre>		
    -</div>  		
    -  										
    -	If you do not provide a 'scope' entry, jsPlumb uses a default scope.  Its value is accessible through this method:
    -	<div class="code">
    -<pre>
    -jsPlumb.getDefaultScope();
    -</pre>	
    -	</div>				
    -	If you want to change it for some reason you can do so with this method:
    -	<div class="code">
    -<pre>
    -jsPlumb.setDefaultScope("mySpecialDefaultScope");
    -</pre>	
    -	</div>			
    -	You can also, should you want to, provide the scope value separately on the drag/drop options, like this:
    -	<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -	endpoint:"Rectangle",
    -	painStyle:{ width:25, height:21, fillStyle:'#666' },
    -	isSource:true,
    -	connectionStyle : { strokeStyle:"#666" },
    -	isTarget:true,
    -	dragOptions:{ scope:"dragScope" },
    -	dropOptions:{ scope:"dropScope" }
    -};</pre>		
    -</div>  
    -			
    -	</div>	
    -
    -		<div class="section">
    -			<h3><a id="paintstyles">Paint Styles</a></h3>
    -			Defining the appearance of Connectors, Endpoints (and Overlays, but this is deprecated in favour of using CSS classes) is achieved through a 'paintStyle' (or a quite similar name...keep reading)
    -			object passed as a parameter to one of jsPlumb.connect, jsPlumb.addEndpoint or jsPlumb.makeTarget. Depending on the method you are
    -			calling, the parameter names vary.
    -			<h5>Connector Paint Styles</h5>
    -			These are specified in a <em>paintStyle</em> parameter on a call to <strong>jsPlumb.connect</strong>:
    -			<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"el1",
    -	target:"el2",
    -	paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			or in the <em>connectorPaintStyle</em> parameter on a call to <strong>jsPlumb.addEndpoint</strong>:
    -			<div class="code">
    -<pre>jsPlumb.addEndpoint({
    -	source:"el1",
    -	target:"el2",
    -	paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -	connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div> 
    -
    -Notice the <em>paintStyle</em> parameter in that example: it is the paint style for the Endpoint, which we'll discuss below.
    -
    -			<h5>Endpoint Paint Styles</h5>
    -			These are specified in a <em>paintStyle</em> parameter on a call to <strong>jsPlumb.addEndpoint</strong>. This is the example from
    -			just above:
    -			<div class="code">
    -<pre>jsPlumb.addEndpoint("el1", {
    -	paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -	connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div> 
    -			...or as the <em>endpointStyle</em> parameter to a jsPlumb.connect call:		
    -			<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"el1",
    -	target:"el2",
    -	endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -	paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			... or as an entry in the <em>endpointStyles</em> array passed to a jsPlumb.connect call:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"el1",
    -	target:"el2",
    -	endpointStyles:[ 
    -		{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -		{ fillStyle:"green" }
    -	],
    -	paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			or as the <em>paintStyle</em> entry in an <em>endpoint</em> parameter passed to a jsPlumb.makeTarget call:
    -					<div class="code">
    -<pre>jsPlumb.makeTarget("el1", {
    -	endpoint: {
    -		paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 }
    -	}
    -});
    -</pre>			
    -			</div>			
    -	In this example we've made "el1" into a drop target, and defined a paint style for the Endpoint jsPlumb will create when a Connection is
    -	established.
    -	
    -	<h5>Overlay Paint Styles</h5>
    -	The preferred way to set paint styles for Overlays is to use the <em>cssClass</em> parameter in the constructor arguments of an Overlay definition.
    -	
    -	<h4>Paint Style Parameters</h4>
    -	This is the full list of parameters you can set in a paintStyle object, but note that <strong>fillStyle</strong> is ignored by Connectors,
    -	and <strong>strokeStyle</strong> is ignored by Endpoints.  Also, if you create a Connection using jsPlumb.connect and do not specify any
    -	Endpoint styles, the Endpoints will derive their fillStyle from the Connector's strokeStyle.
    -	<p>
    -		fillStyle, strokeStyle and outlineColor can be specified using any valid CSS3 syntax.
    -	</p>
    -	<ul>
    -		<li><strong>fillStyle</strong> - color for an Endpoint, eg. rgba(100,100,100,50), "blue", "#456", "#993355", rgb(34, 56, 78).</li>
    -		<li><strong>strokeStyle</strong> - color for a Connector. see fillStyle examples.</li>
    -		<li><strong>lineWidth</strong> - width of a Connector's line. An integer.</li>
    -		<li><strong>outlineWidth</strong> - width of the outline for an Endpoint or Connector. An integer.</li>
    -		<li><strong>outlineColor</strong> - color of the outline for an Endpoint or Connector. see fillStyle examples.</li>
    -		<li><strong>dashstyle</strong> - VML and SVG only. This comes from VML, and allows you to create dashed or dotted lines.  It has a 
    -			better syntax than the equivalent attribute in SVG (<em>stroke-dasharray</em>, discussed below), so jsPlumb supports this for both renderers. 
    -			The <em>dashstyle</em> attribute is specified as an array of strokes and spaces, where each value is some multiple of <em>the width of 
    -			the Connector</em>, and that's where it's better than SVG, which uses pixels.
    -			<p>
    -			<a href="http://www.w3.org/TR/NOTE-VML" target="_blank">The VML spec</a> is a good place to find valid values for dashstyle. Note that
    -			jsPlumb does not support the string values for this attribute ("solid", "dashdot", etc).
    -			</p>
    -			<p>
    -			In SVG render mode, jsPlumb uses the <em>lineWidth</em> parameter in conjunction with the values in a <em>dashstyle</em> attribute
    -			to create an appropriate value for <em>stroke-dasharray</em>.
    -			</p>
    -		</li>
    -		<li><strong>stroke-dasharray</strong> - SVG only. This is the SVG equivalent of <em>dashstyle</em>.  
    -		<a href="http://www.w3.org/TR/SVG/painting.html" target="_blank">The SVG spec</a> discusses valid values for this parameter.  But be aware
    -		that jsPlumb does not convert this into a valid <em>dashstyle</em> attribute when using the VML renderer. Better to use <em>dashstyle</em>.
    -		</li>
    -		<li><strong>stroke-dashoffset</strong> - SVG only.  This is used in SVG to specify how far into the dash pattern to start painting.  For more information,
    -		see <a href="http://www.w3.org/TR/SVG/painting.html" target="_blank">the SVG spec.</a></li>
    -		<li><strong>joinstyle</strong> - VML and SVG only.  As with <em>dashstyle</em>, this is a VML attribute that jsPlumb supports for both VML
    -		and SVG - jsPlumb turns this into a <em>stroke-linejoin</em> attribute when rendering with SVG.  This attribute specifies how you want individual
    -		segments of connectors to be joined; the VML and SVG specs both have examples of this, of which many are the same between the two, which is
    -		why jsPlumb will automatically convert this attribute into the SVG equivalent.
    -		</li>
    -		<li><strong>stroke-linejoin</strong> - SVG only.  This is the equivalent of VML's <em>joinstyle</em> attribute, but as with <em>stroke-dasharray</em>,
    -		jsPlumb does not convert this into something approriate for VML.  So, using <em>joinstyle</em> will enable you to support more browsers with
    -		less effort.		
    -		</li>
    -	</ul>
    -	
    -	<h3><a id="hoverpaintstyles">Hover Paint Styles</a></h3>
    -	 		
    -	 		Connectors and Endpoints both support the concept of a "hover" paint style - a paint style to use when the mouse is hovering over
    -	 		the component.  These are specified in the exact same format as paint styles discussed above, but hover paint styles also inherit
    -	 		any values from the main paint style.  This is because you will typically want to just change the color, or perhaps outline color, of
    -	 		a Connector or Endpoint when the mouse is hovering, but leave everything else the same.  So having hover paint styles inherit their
    -	 		values precludes you from having to define things in more than one place.
    -	 		<p>
    -	 			The naming convention adopted for hover paint styles is pretty much to insert the word 'hover' into the corresponding main paint
    -	 			style parameters.  Here are a couple of examples:
    -	 		</p> 
    -	 					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"el1",
    -	target:"el2",
    -	paintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -	hoverPaintStyle:{ strokeStyle:"red" },
    -	endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -	endpointHoverStyle:{ fillStyle:"red" }
    -});
    -</pre>			
    -			</div>
    -			In this example we specified a hover style for both the Connector, and each of its Endpoints.  Here's the same thing, but using the plural
    -			version, to specify a different hover style for each Endpoint:
    -			
    -			
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"el1",
    -	target:"el2",
    -	paintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -	hoverPaintStyle:{ strokeStyle:"red" },
    -	endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -	endpointHoverStyles:[ { fillStyle:"red" }, { fillStyle:"yellow" } ]
    -});
    -</pre>			
    -			</div>
    -	 		Calls to <strong>jsPlumb.addEndpoint</strong> and <strong>jsPlumb.makeTarget</strong> can also specify various hover paint styles:
    -	 								<div class="code">
    -<pre>jsPlumb.addEndpoint("el1", {
    -	paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -	hoverPaintStyle:{ fillStyle:"red" },
    -	connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -	connectorHoverPaintStyle:{ strokeStyle:"red", outlineColor:"yellow", outlineWidth:1 }
    -});
    -</pre>			
    -			</div> 
    -	 		
    -	 		Here we specified a hover paint style for both the Endpoint we are adding, and any Connections that are made from the Endpoint.
    -	 		<p><strong>jsPlumb.makeTarget</strong> also supports hover paint styles - here's the example from before, with one applied:</p>
    -	 		
    -	 							<div class="code">
    -<pre>jsPlumb.makeTarget("el1", {
    -	endpoint: {
    -		paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -		hoverPaintStyle:{ fillStyle:"red" }
    -	}
    -});
    -</pre>			
    -			</div>	
    -	
    -		Note that <strong>jsPlumb.makeTarget</strong> does not support Connector parameters. It is for creating targets only; Connector parameters 
    -		will be set by the source Endpoint in any Connections that are made to the element that you turned into a target by using this method.
    -	 		
    -	 		
    -			<h4><a id="gradients">Gradients</a></h4>
    -			The Canvas and SVG renderers both support gradients. The VML renderer does not.  jsPlumb uses its own syntax to define
    -			gradients, to abstract out the differences between the syntax required by canvas and that required by SVG.  
    -			<p>There are two types of gradients available - a 'linear' gradient, which consists of colored lines all
    -			going in one direction, and a 'radial' gradient, which consists of colored circles emanating from one circle to another.
    -			Because of their basic shape, jsPlumb supports only <em>linear</em> gradients for
    -			Connectors.  But for Endpoints, jsPlumb supports both <em>linear</em> and <em>radial</em> gradients.</p>
    -			
    -				<h5>Connector gradients</h5>
    -				To specify a linear gradient to use in a Connector, you must add a <em>gradient</em> object to your
    -				Connector's <em>paintStyle</em>, for instance:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : "window2",
    -	target : "window3",
    -	paintStyle:{
    -		gradient:{
    -			stops:[[0,"green"], [1,"red"]]
    -		},
    -		lineWidth:15
    -	}
    -});
    -</pre>
    -</div>
    -Here we have connected window2 to window3 with a 15 pixel wide connector that has a gradient from green to red.</p><p> Notice the <strong>gradient</strong> object and the
    -<strong>stops</strong> list inside it - the gradient consists of an arbitrary number of these "color stops".  Each color stop is comprised
    -of two values - [position, color].  Position must be a decimal value between 0 and 1 (inclusive), and indicates where the color
    -stop is situated as a fraction of the length of the entire gradient.  Valid values for
    -the colors in the <strong>stops</strong> list are the same as those that are valid for <strong>strokeStyle</strong> when describing a color.
    -			</p>
    -			As mentioned, the <strong>stops</strong> list can hold an arbitrary number of entries.  Here's an example of a gradient that goes from red to blue to green, and back again through blue to red:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle : {
    -		gradient:{
    -			stops:[[0,'red'], [0.33,'blue'], [0.66,'green'], [0.33,'blue'], [1,'red']]
    -		},
    -		lineWidth : 15
    -	}
    -});
    -</pre>
    -</div>
    -<strong>Note:</strong> when using the VML renderer, jsPlumb will simply ignore the gradient directive
    -so it is best to ensure you also supply a <em>strokeStyle</em> in your paintStyle object, to give jsPlumb something to fall back on.  If you do not supply
    -a <em>strokeStyle</em> your Connectors will be painted black.  The previous example might look like this, for instance:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle:{
    -		strokeStyle:'red',
    -		gradient:{
    -			stops:[[0,'red'], [0.33,'blue'], [0.66,'green'], [0.33,'blue'], [1,'red']]
    -		},
    -		lineWidth:15
    -	}
    -});
    -</pre>
    -</div>
    -Notice the <strong>strokeStyle:'red'</strong> directive at the beginning of the parameter list in <strong>paintStyle</strong>.
    -			<h5>Endpoint gradients</h5>
    -			Endpoint gradients are specified using the same syntax as Connector gradients.  You put the gradient specifier either in the
    -			<strong>endpoint</strong> member, or if you are specifying different Endpoints for each end of the Connector, in one or both of
    -			the values in the <strong>endpoints</strong> array.  Also, this information applies to the case that you are creating standalone
    -			Endpoints that you will be configuring for drag and drop creation of new Connections. 
    -			<p>
    -			This is an example of an Endpoint gradient that is different for each Endpoint in the Connector.  This comes from the main demo; it is
    -			the Connector joining Window 2 to Window 3:
    -			</p>
    -<div class="code">
    -<pre>var w23Stroke = 'rgb(189,11,11)';
    -jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle:{
    -		lineWidth:8,
    -		strokeStyle:w23Stroke
    -	},
    - 	anchors:[ [0.3,1,0,1], "TopCenter" ],
    - 	endpoint:"Rectangle",
    - 	endpointStyles:[
    - 		{ gradient : {stops:[[0, w23Stroke], [1, '#558822']] } },
    -    	{ gradient : {stops:[[0, w23Stroke], [1, '#882255']] } }
    -    ]
    -});
    -</pre>
    -</div>
    -The first entry in the gradient will be the one that is on the Connector end of the Endpoint.  You can of course have as many color stops as
    -you want in this gradient, just like with Connector gradients.
    -	<h5>Applying the gradient in Endpoints</h5>
    -	Only the Dot and Rectangle endpoints honour the presence of a gradient (and, remember, not in VML). The Image endpoint of course ignores a gradient
    -	as it does no painting of its own.
    -	<p>The type of gradient you will see depends on the Endpoint type:</p>
    -		<ul>
    -			<li><strong>Dot</strong> - renders a radial endpoint, with color stop 0 on the outside, progressing inwards as we move through color stops.
    -			<p>Radial gradients actually require more data than linear gradients - in a linear gradient we just move from one point to another, whereas
    -			in a radial gradient we move from one <em>circle</em> to another.  By default, jsPlumb will render a radial gradient using a source
    -			circle of the same radius as the Endpoint itself, and a target circle of 1/3 of the radius of the Endpoint (both circles share the
    -			same center as the Endpoint itself). This circle will be offset by radius/2 in each direction.</p>
    -			<p>You can supply your own values for these inside the gradient descriptor:</p>
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect({
    -	source : 'window3',
    -	target : 'window4',
    -    paintStyle:{
    -    	lineWidth:10,
    -    	strokeStyle:w34Stroke
    -    },
    -    anchors:[ "RightMiddle", "LeftMiddle" ],
    -    endpointStyle:{
    -    	gradient : {
    -    		stops:[ [0, w34Stroke], [1, w34HlStroke] ],
    -    		offset:37.5,
    -    		innerRadius:40
    -    	},
    -    	radius:55
    -    }
    - });
    -</pre>
    -</div>
    -Here we have instructed jsPlumb to make the gradient's inner radius 10px instead of the default 25/3 = 8 ish pixels, and the offset in each direction
    -will be 5px, instead of the default radius / 2 = 12.5 pixels.
    -<p>It is also possible to specify the offset and inner radius as percentages - enter the values as strings with a '%' symbol on the end:</p>
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect({
    -	source : 'window3', 
    -	target : 'window4',
    -    paintStyle:{
    -    	lineWidth:10,
    -    	strokeStyle:w34Stroke
    -    },
    -	anchors:[ "RightMiddle", "LeftMiddle" ],
    -	endpointStyle:{
    -		gradient : {
    -			stops:[ [0, w34Stroke], [1, w34HlStroke] ],
    -			offset:'68%',
    -			innerRadius:'73%'
    -		},
    -		radius:25
    -	}
    -});
    -</pre>
    -</div>
    -This will give roughly the same output as the example above (the percentages are not entirely exact).<br/><br/>
    -			</li>
    -			<li><strong>Rectangle</strong> - renders a linear endpoint, with color stop 0 closest to the end of the Connector</li>
    -		</ul>
    -		</div>
    -
    -		<div class="section">
    -			<h3><a id="cssclasses">CSS Class Reference</a></h3>
    -			jsPlumb attaches classes to each of the UI components it creates.
    -			<p>These class names are exposed on the jsPlumb object and can be overridden if you need to do so (see the third column in the table) </p>
    -			<table width="70%" class="table">
    -				<tr><th>Component</th><th>CSS Class</th><th>jsPlumb Member</th></tr>
    -				<tr><td>Connector</td><td>_jsPlumb_connector</td><td>connectorClass</td></tr>
    -				<tr><td>Endpoint</td><td>_jsPlumb_endpoint</td><td>endpointClass</td></tr>
    -				<tr><td>Overlay</td><td>_jsPlumb_overlay</td><td>overlayClass</td></tr>
    -			</table>
    -			<p>You would typically use these to establish appropriate z-indices for your UI.</p>			
    -		</div>
    -				
    -		<div class="section">
    -			<h3><a id="animation">Animation</a></h3>
    -			jsPlumb offers an 'animate' function, which wraps the underlying animation engine for whichever library you happen to be using and
    -			inserts a callback for jsPlumb to repaint whatever it needs to at each step.  You could of course do this yourself; it's a convenience
    -			method really.
    -			<p>The method signature is:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.animate : function(el, properties, options) 
    -</pre>
    -</div>			
    -The arguments are as follows:
    -	<ul>
    -		<li><strong>el</strong> - element id, or element object from the library you're using.</li>
    -		<li><strong>properties</strong> - properties for the animation, such as duration etc.</li>
    -		<li><strong>options</strong> - options for the animation, such as callbacks etc.</li>
    -	</ul>
    -	
    -		</div>
    -		
    -			<div class="section">
    -			<h3><a id="connectionInfo">Retrieving Connection Information</a></h3>
    -			jsPlumb offers one fairly versatile method - <strong>getConnections</strong> - to retrieve information about the
    -			currently managed connections.  
    -			<p>Before you use this method you should understand jsPlumb's notion of 'scope' - documentation
    -			is <a href="#dragAndDropScope">here</a> </p>
    -			
    -			<h4>Retrieving connections for a single scope</h4>
    -			<p>To do this, you call getConnections with either no arguments, in which case jsPlumb uses the default scope, or with a string
    -			specifying one scope</p>
    -			<div class="code">
    -<pre>var connectionList = jsPlumb.getConnections();     // you get a list of Connection objects that are in the default scope.
    -</pre>			
    -			</div>
    -			Compare this with:
    -			<div class="code">
    -<pre>var connectionList = jsPlumb.getConnections("myScope");     // you get a list of Connection objects that are in "myScope".
    -</pre>			
    -			</div>
    -			<h4>More advanced filtering</h4>
    -			getConnections optionally takes a JS object specifying filter parameters, of which there are three:
    -			<ul>
    -				<li>scope - the scope(s) of the connection type(s) you wish to retrieve</li>
    -				<li>source - limits the returned connections to those that have this source id</li>
    -				<li>target - limits the returned connections to those that have this target id</li>
    -			</ul>			
    -			Each of these three parameters may be supplied as a string, or a list of strings - see the examples below.
    -			<p>
    -			The return value of a call to getConnection using a JS object as parameter varies on how many scopes you defined.  If you defined only
    -			a single scope then jsPlumb returns you a list of Connections in that scope.  Otherwise the return value is a dictionary whose keys are 
    -			scope names, and whose values are lists of Connections. For example, the following call:
    -			</p>
    -			<div class="code">
    -<pre>
    -	jsPlumb.getConnections({scope:["someScope", "someCustomScope"]});
    -</pre>
    -</div>
    -would result in this output:			
    -<div class="code">
    -<pre>
    -{
    -	"someScope" : [ 1..n Connections ],
    -	"someCustomScope": [ 1..m Connections ]
    -}
    -</pre>
    -</div>			
    -		The following examples show the various ways you can get connection information:
    -	<ul>
    -		<li>Get all connections:
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getAllConnections();  
    -</pre>
    -</div>						
    -		</li>
    -<li>Get all connections for the default scope only (return value is a list):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections();  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given scope (return value is a list):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:"myTestScope"});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given scopes (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:["myTestScope", "yourTestScope"]});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given source (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:"mySourceElement"});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given sources (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:["mySourceElement", "yourSourceElement"]});  
    -</pre>
    -</div>						
    -		</li>		
    -<li>Get all connections for the given target (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({target:"myTargetElement"});  
    -</pre>
    -</div>						
    -		</li>				
    -<li>Get all connections for the given source and targets (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:"mySourceElement", target:["target1", "target2"]});  
    -</pre>
    -</div>						
    -		</li>				
    -		
    -<li>Get all connections for the given scope, with the given source and target (return value is a list of connections):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:'myScope", source:"mySourceElement", target:"myTargetElement"});  
    -</pre>
    -</div>						
    -		</li>				
    -		
    -	</ul>	
    -
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="events">Events</a></h3>
    -			jsPlumb supports binding to several different events, both on Connections, Endpoints and Overlays, and also on the jsPlumb
    -			object itself.  To bind events on any of these objects you use the <strong>jsPlumb.bind(object, event, callback)</strong> method, 
    -			<strong>except</strong> in the case of Overlays, in which you provide the event listeners in the Overlay's constructor arguments (example below). 
    -			
    -			<h4><a id="jsPlumbEvents">jsPlumb Events</a></h4>
    -			<ul>
    -				<li class="bullet"><strong>jsPlumbConnection</strong> - notification a Connection was established.  The callback is passed the new Connection as argument.</li>
    -				<li class="bullet"><strong>jsPlumbConnectionDetached</strong> - notification a Connection was detached.  The callback is passed the detached Connection as argument.</li>
    -				<li class="bullet"><strong>click</strong> - notification a Connection was clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification a Connection was double-clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>endpointClick</strong> - notification an Endpoint was clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>endpointDblClick</strong> - notification an Endpoint was double-clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -			</ul>
    -			<h4><a id="connectionEvents">Connection Events</a></h4>			
    -			<ul>
    -				<li class="bullet"><strong>click</strong> - notification a Connection was clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification a Connection was double-clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>mouseenter</strong> - notification the mouse entered the Connection's path.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>mouseexit</strong> - notification the mouse exited the Connection's.  The callback is passed the Connection and the original mouse event.</li>
    -			</ul>
    -			<h4><a id="endpointEvents">Endpoint Events</a></h4>			
    -			<ul>
    -				<li class="bullet"><strong>click</strong> - notification an Endpoint was clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification an Endpoint was double-clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>mouseenter</strong> - notification the mouse entered the Endpoint.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>mouseexit</strong> - notification the mouse exited the Endpoint.  The callback is passed the Endpoint and the original mouse event.</li>
    -			</ul>
    -			<h4><a id="overlayEvents">Overlay Events</a></h4>
    -			Registering event listeners on an Overlay is a slightly different procedure - you provide them as arguments to the Overlay's 
    -			constructor.  This is because you never actually act on an 'Overlay' object.  Here's how to register a click listener on an Overlay:
    -			<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"el1",
    -	target:"el2",
    -	overlays:[
    -		[ "Label", {
    -			events:{
    -				click:function(labelOverlay, originalEvent) { alert("you clicked on the label overlay for this connection :" + labelOverlay.connection) }
    -			}
    -		}] 	
    -	]
    -});
    -</pre>			
    -			</div>		
    -		</div>
    -
    -				<div class="section">
    -			<a id="examples"><h3>jsPlumb.connect Examples</h3></a>
    -This section provides examples of how to use the programmatic API to establish Connections. 
    -			<p>
    -			The basic syntax of a call is that you execute 'connect', providing a source and a target, and optionally a paintStyle and preferences for where you
    -			want the plumbing to be anchored on each element, as well as the type of connector to use. 
    -			</p>
    -			<ul>
    -				<li>Connect window1 to window2 with the default settings:
    -					<div class="code">
    -						<pre>jsPlumb.connect({source:"window1", target:"window2"});</pre>
    -					</div>
    -This connects 'window1' to 'window2' using the jsPlumb defaults - a red Bezier curve Connector and black Dot Endpoints.
    -				</li>
    -				<li>Connect window1 to window2 with a 15 pixel wide yellow Connector, and a slightly brighter endpoint (remember the default Endpoint is a Dot):
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -					</div>
    -				</li>
    -				
    -				<li>Connect window1 to window2 with a 15 pixel wide yellow Connector, and a slightly brighter endpoint:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -					</div>
    -				</li>				
    -				<li>Connect window3 to 'window4' with a 10 pixel wide, semi opaque blue Connector, anchored to the left middle of window3, and the right middle of window4, with a Rectangle endpoint of width 10 and height 8:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window3',
    -	target:'window4',
    -	paintStyle:{ lineWidth:10, strokeStyle:'rgba(0, 0, 200, 0.5)' },
    -	anchors:["RightMiddle", "LeftMiddle"],
    -	endpoint:[ "Rectangle", { width:10, height:8 } ]
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window2 to window3 with a default Connector from the top center of window2 to the bottom center of window3, and rectangular endpoints:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window2',
    -	target:'window3',
    -	paintStyle:{lineWidth:8, strokeStyle:'rgb(189,11,11)'},
    -	anchors:["BottomCenter", "TopCenter"],
    -	endpoint:"Rectangle"
    -});</pre>
    -					</div>
    -				</li>
    -
    -
    -				<li>Connect window1 to window2 with a 15 px wide yellow Bezier. endpoints are a slightly lighter shade of yellow.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	anchors:["BottomCenter", [0.75,0,0,-1]],
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -</div></li>
    -<li>Connect window3 to window4 with a 10px wide blue-ish half transparent Bezier. put endpoints underneath the element they attach to.
    -the endpoints have a radial gradient. both ways of specifying gradient positioning are shown here.
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect( {
    -	source:'window3',
    -	target:'window4',
    -		 paintStyle:{lineWidth:10, strokeStyle:w34Stroke},
    -		 anchors:["RightMiddle", "LeftMiddle"],
    -		 endpointStyle:{ gradient : {stops:[[0, w34Stroke], [1, w34HlStroke]], offset:17.5, innerRadius:15 }, radius:35},
    -		 //endpointStyle:{ gradient : {stops:[[0, w34Stroke], [1, w34HlStroke]], offset:'78%', innerRadius:'73%'}, radius:35 },
    -		 endpointsOnTop:false
    -	}
    -);</pre>
    -</div></li>
    -<li>Connect window2 to window3 with an 8px red Bezier and default rectangular endpoints.  see also how the first anchor is
    -specified here - this is how you create anchors in locations jsPlumb does not offer shortcuts for.
    -the endpoints in this example have linear gradients applied.
    -<div class="code">
    -<pre>var w23Stroke = 'rgb(189,11,11)';
    -jsPlumb.connect({
    -	source:'window2',
    -	target:'window3',
    -	paintStyle:{lineWidth:8,strokeStyle:w23Stroke},
    -	anchors:[[0.3,1,0,1], "TopCenter"],
    -	endpoint:"Rectangle",
    -	endpointStyles:[{ gradient : {stops:[[0, w23Stroke], [1, '#558822']] }},
    -       				{ gradient : {stops:[[0, w23Stroke], [1, '#882255']] }}]
    -});</pre>
    -</div></li>
    -
    -<li>Connect window5 to window6 from center to center, 5px wide line that is green and half transparent. the endpoints are
    -125px in radius and spill out from underneath their elements.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window5',
    -	target:'window6',
    -	anchors:["Center", "Center"],
    -	paintStyle:{lineWidth:5,strokeStyle:'rgba(0,255,0,0.5)'},
    -	endpointStyle:{radius:125}
    -});</pre>
    -</div></li>
    -
    -<li>Connect window4 to window5 from bottom right to top left, with a 7px straight line purple connector, and an image as the endpoint,
    -placed on top of the element it is connected to.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window4",
    -	target:"window5",
    -	anchors:[ "BottomRight","TopLeft" ],
    -	paintStyle:{ lineWidth:7, strokeStyle:"rgb(131,8,135)" },
    -	endpoint:[ "Image", { src:"http://morrisonpitt.com/jsPlumb/img/endpointTest1.png" } ],
    -	connector:"Straight"
    -});</pre>
    -</div></li>
    -
    -
    -				<li>Connect window5 to window6 between their center points with a semi-opaque connector, and 125px endpoints:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window5",
    -	target:"window6",
    -	anchors:[ "Center", "Center" ],
    -	paintStyle:{ lineWidth:5, strokeStyle:"rgba(0,255,0,0.5)" },
    -	endpointStyle:{ radius:125 }
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window7 to window8 with a 10 pixel wide blue Connector, anchored on the top left of window7 and the bottom right of window8:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window7",
    -	target:"window8",
    -	paintStyle:{ lineWidth:10, strokeStyle:"blue" },
    -	anchors:[ "TopLeft", "BottomRight" ]
    -});
    -</pre>
    -					</div>
    -				</li>
    -				<li>Connect the bottom right corner of window4 to the top left corner of window5, with rectangular endpoints of size 40x40 and a hover color of light blue:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window4",
    -	target:"window5",
    -	anchors:["BottomRight","TopLeft"],
    -	paintStyle:{lineWidth:7,strokeStyle:'rgb(131,8,135)'},
    -	hoverPaintStyle:{ strokeStyle:"rgb(0, 0, 135)" },
    -	endpointStyle:{ width:40, height:40 },
    -	endpoint:"Rectangle",
    -	connector:"Straight"
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window1 to window2 with the default paint settings but provide some drag options (which are passed through to the underlying library's draggable call):
    -					<div class="code">
    -						<pre>jsPlumb.connect({source:'window1', target:'window2', dragOptions:{cursor:'crosshair'}});</pre>
    -					</div>
    -				</li>
    -</div>
    -		
    -		<div class="section">
    -			<a id="dragAndDropExamples"><h3>Draggable Connections Examples</h3></a>
    -			This is a list of examples of how to use jsPlumb to create Connections using drag and drop.
    -			<p>
    -			The basic procedure is:
    -			<ol>
    -				<li>1a. Create Endpoints and register them on elements in your UI</li>
    -				<li>1b. alternatively, create a source endpoint and then make some element a drop target </li>
    -				<li>2. Drag and Drop</li>
    -			</ol>
    -			That's all there is to it.  Of course there are plenty of options you can set when doing this...it will be easier
    -			to show you some examples:
    -			</p>
    -			<ul>
    -				<li>Define an Endpoint with default appearance, that is both a source and target of new Connections:
    -<div class="code">
    -<pre>var endpointOptions = { isSource:true, isTarget:true }; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Register that Endpoint on window3, specifying that it should be located in the top center of the element:
    -<div class="code">
    -<pre>var window3Endpoint = jsPlumb.addEndpoint('window3', { anchor:"TopCenter" }, endpointOptions );  
    -</pre>
    -</div>
    -Notice here the usage of the three-argument addEndpoint  - we can reuse 'endpointOptions' with a different Anchor for another element.  This is a useful
    -practice to get into.<br/><br/>
    -				</li>
    -				
    -				<li>Now register that Endpoint on window4, specifying that it should be located in the bottom center of the element:
    -<div class="code">
    -<pre>var window4Endpoint = jsPlumb.addEndpoint('window4', { anchor:"BottomCenter" }, endpointOptions );  
    -</pre>
    -</div>
    -Now we have two Endpoints, both of which support drag and drop of new Connections.  We can use these to make a programmatic Connection, too, though:<br/><br/>
    -				</li>				
    -				
    -				<li>Connect window3 to window4 with a 25px wide yellow Bezier that has a 'curviness' of 175:
    -<div class="code">
    -<pre>jsPlumb.connect({ 
    -	source:window3Endpoint,
    -	target:window4Endpoint,
    -	connector: [ "Bezier", 175 ],
    -	paintStyle:{ lineWidth:25, strokeStyle:'yellow' }
    -});  
    -</pre>
    -</div>
    -				</li>
    -				
    -				<li>Define an Endpoint that creates Connections that are 20px wide straight lines, that is both a source and target of new Connections,
    -				and that has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Define an Endpoint that will be anchored to "TopCenter".  It creates Connections that are 20px wide straight lines, that is both a source and target of new Connections,
    -				and that has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchor:"TopCenter", 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Define an Endpoint that will create a dynamic anchor which can be positioned at "TopCenter" or "BottomCenter".  It creates Connections that are 20px wide straight lines, it is both a source and target of new Connections,
    -				and it has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchor:[ "TopCenter", "BottomCenter" ], 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Exactly the same as before, but shows how you can use "anchors" instead of "anchor", if that makes you feel happier:
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchors:[ "TopCenter", "BottomCenter" ], 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>								
    -				
    -<li>Define an Endpoint that is a 30px blue dot, creates Connections that are 20px wide straight lines, is both a source and target of new Connections,
    -				has a 'scope' of 'blueline', and has an event handler that pops up an alert (note: the event handler name means this example is jQuery - MooTools
    -				and YUI3 use different event handler names):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{fillStyle:'blue'},
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ drop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -			<li>Same example as before, but this is for MooTools, and the Endpoint can support up to 5 connections (the default is 1):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{ fillStyle:'blue' },
    -	maxConnections:5,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ onDrop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Same example again, but maxConnections being set to -1 means that the Endpoint has no maximum limit of Connections:
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", {radius:30} ],
    -	style:{ fillStyle:'blue' },
    -	maxConnections:-1,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ onDrop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Same example again, but for YUI3.  Note the drop callback is "drop:hit":
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{fillStyle:'blue'},
    -	maxConnections:-1,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ "drop:hit":function(e, ui) { alert('drop!'); } }	 	
    -}; 
    -</pre>
    -</div>				 				
    -				</li>				
    -				
    -	<li>Assign a UUID to the endpoint options created above, and add as Endpoints to "window1" and "window2":
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("window1", { uuid:"abcdefg" }, endpointOptions );
    -jsPlumb.addEndpoint("window2", { uuid:"hijklmn" }, endpointOptions );
    -</pre>
    -</div>	
    -	</li>
    -	
    -	<li>Connect the two Endpoints we just registered on "window1" and "window2":
    -<div class="code">
    -<pre>
    -jsPlumb.connect({uuids:["abcdefg", "hijklmn"]});
    -</pre>
    -</div>	
    -	</li>
    -	
    -	<li>Create a source Endpoint, register it on some element, then make some other element a Connection target:
    -<div class="code">
    -<pre>
    -var sourceEndpoint = { isSource:true, endpoint:[ "Dot", { radius:50 } ] };
    -var targetEndpoint = { endpoint:[ "Rectangle", { width:10, height:10 } ] };
    -jsPlumb.addEndpoint( "window1", sourceEndpoint );
    -jsPlumb.makeTarget( "window2", targetEndpoint );
    -</pre>
    -</div>	
    -Notice that the endpoint definition we use on the target window does not include the "isTarget:true" directive.  jsPlumb ignores that flag when
    -creating a connection using an element as the target; but if you then tried to drag another connection to the Endpoint that was created, you would
    -not be able to.  To permit that, you would set 'isTarget:true' on the targetEndpoint options defined above.
    -	
    -	</li>
    -				
    -			</ul>
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="utilityFunctions">Utility Functions</a></h3>
    -			<ul>
    -				<li>Detach window5 from all connections
    -					<div class="code">
    -						<pre>jsPlumb.detachAll("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Hide all window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.hide("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Hide all window5's connections endpoints
    -					<div class="code">
    -						<pre>jsPlumb.hide("window5", true);</pre>
    -					</div>
    -				</li>
    -				<li>Show all window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.show("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Show all window5's connections and endpoints.  Note that in the case that you call jsPlumb.show with two arguments, jsPlumb
    -				will also not make a connection visible if it determines that the other endpoint in the connection is not visible.
    -					<div class="code">
    -						<pre>jsPlumb.show("window5", hide);</pre>
    -					</div>
    -				</li>				
    -				<li>Toggle the visibility of window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.toggle("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of all of window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.repaint("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of all of window5, window6 and window11's connections
    -					<div class="code">
    -						<pre>jsPlumb.repaint( [ "window5", "window6", "window11" ] );</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of every connection
    -					<div class="code">
    -						<pre>jsPlumb.repaintEverything();</pre>
    -					</div>
    -				</li>
    -				<li>Detach every connection
    -					<div class="code">
    -						<pre>jsPlumb.detachEverything();</pre>
    -					</div>
    -				</li>
    -				<li>Remove the given Endpoint from element "window1", deleting its Connections. 
    -					<div class="code">
    -						<pre>jsPlumb.removeEndpoint("window1", someEndpoint);</pre>
    -					</div>
    -				</li>
    -				<li>Remove all Endpoints for the element 'window1', deleting their Connections.
    -					<div class="code">
    -						<pre>jsPlumb.removeAllEndpoints("window1");</pre>
    -					</div>
    -				</li>	
    -				<li>Removes every Endpoint managed by this instance of jsPlumb, deleting all Connections.
    -				This is the same as jsPlumb.reset(), effectively, but it does not clear out the event listeners list. 
    -					<div class="code">
    -						<pre>jsPlumb.removeEveryEndpoint();</pre>
    -					</div>
    -				</li>
    -				<li>Deletes the given Endpoint and all its Connections. 
    -					<div class="code">
    -						<pre>jsPlumb.deleteEndpoint(endpoint);</pre>
    -					</div>
    -				</li>
    -				<li>Removes every endpoint, detaches every connection, and clears the event listeners list.  Returns jsPlumb instance to its initial state.  
    -					<div class="code">
    -						<pre>jsPlumb.reset();</pre>
    -					</div>
    -				</li>				
    -				<li>Set window1 to be not draggable, no matter what some jsPlumb command may request.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggable("window1", false);</pre>
    -					</div>
    -				</li>
    -				<li>Set window1 and window2 to be not draggable, no matter what some jsPlumb command may request.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggable(["window1","window2"], false);</pre>
    -					</div>
    -				</li>
    -				<li>Sets whether or not elements that are connected are draggable by default.
    -				The default for this is true.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggableByDefault(false);</pre>
    -					</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable("window1");</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 and window2 as draggable elements (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(["window1","window2"]);</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable("window1");</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (jQuery)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (MooTools)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($$(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (YIU3)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(Y.all(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (jQuery)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($("#window1"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (MooTools)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($("window1"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (YUI3)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(Y.one("window1"));</pre>
    -				</div>
    -				</li>
    -			</ul>
    -		</div>
    -		
    -	
    -		
    -		
    -		<div class="section">
    -			<h3><a id="developingJsPlumb">Advanced Topics</a></h3>
    -			<h4><a id="which">Which files are which?</a></h4>
    -			In development, jsPlumb is broken up into seven scripts:
    -				<ul>
    -					<li>- jsPlumb-x.x.x.js
    -					<p>This is the main jsPlumb engine.</p>					
    -					</li>
    -					<li>- jsPlumb-defaults-x.x.x.js
    -					<p>This contains the default Anchor, Endpoint, Connector and Overlay implementations.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-canvas-x.x.x.js
    -					<p>This contains the HTML5 canvas render code.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-svg-x.x.x.js
    -					<p>This contains the SVG render code.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-vml-x.x.x.js
    -					<p>This contains the VML render code.</p>
    -					</li>
    -					<li>- &lt;LIBRARY_PREFIX&gt;.jsPlumb-x.x.x.js
    -					<p>This contains library-specific helper methods.  jsPlumb ships with three of these - one each for jQuery, MooTools and YUI3. See below for information on how to create a new library implementation.</p>
    -					</li>
    -					<li>- jsBezier-0.2-min.js
    -					<p>These are the Bezier curve functions; they are maintained in a separate project called <a href="http://code.google.com/p/jsbezier/">jsBezier</a></p>
    -					</li>
    -				</ul>
    -			These seven files are packaged together to form the scripts that people use, for example:
    -			<ul>
    -				<li>jquery.jsPlumb-1.3.1-all.js
    -				<p>Contains jsPlumb-1.3.1.js, jsPlumb-defaults-1.3.1.js, jsPlumb-renderers-canvas-1.3.1.js, jsPlumb-renderers-svg-1.3.1.js, jsPlumb-renderers-vml-1.3.1.js, jquery.jsPlumb-1.3.1.js and jsBezier-0.2-min.js</p>
    -				</li>
    -				<li>jquery.jsPlumb-1.3.1-all-min.js
    -				<p>A minified version of the script above (minified using the YUI Compressor)</p>
    -				</li>
    -			</ul>
    -			<h4><a id="pluggableLibrarySupport">Pluggable Library Support</a></h4>
    -		Out of the box, jsPlumb can be run on top of jQuery, MooTools or YUI3.  This is achieved by
    -		delegating several core methods - tasks such as finding an element by id, finding an element's
    -		position or dimensions, initialising a draggable, etc - to the library in question.
    -		<p>To develop one of these, your test page should include the first two scripts discussed above, and
    -		then your own script containing your library specific functionality.  The existing implementations
    -		may be documented well enough for you to create your own, but contact me if you need assistance.  If you do this, it would be
    -		great to share it with everyone...</p>   
    -
    -			<!-- h4><a id="customConnectors">Custom Connectors</a></h4>			
    -					
    -			You can provide your own connectors if you need to. A Connector consists of two functions, which work as a pair.  First a call is made to the <em>compute</em> function:
    -
    -			<div class="code">
    -<pre>
    -this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) {
    -	...
    -	return dimensions;
    -}
    -</pre>
    -			</div>
    -			which is expected to return a list that the <em>paint</em> function can make sense of.  The first four entries in the
    -			list <strong>must be</strong> the [x,y,width,height] values for the canvas that the connector will be drawn on; jsPlumb will
    -			use this information to size the canvas prior to calling the Connector's <em>paint</em> function.  Therefore it
    -			is the Connector's responsibility to ensure that the returned dimensions describe a large enough space for
    -			the line that will be drawn on it.</p><p>The next four elements <strong>must be</strong> the coordinates of the two endpoints of the line
    -			you are going to draw.</p><p>The remainder of the items in the returned list are arbitrary, and will
    -			vary between Connector implementations; this list is passed in to a Connector's <em>paint</em> function, so each
    -			implementation will put into the list whatever it needs to paint itself.  For instance, the straight line
    -			connector only needs the [x,y] location of each end of the line it will paint, and that is one of the required entries, so
    -			it does not have to do anything extra,  whereas the Bezier connector adds the location of the two control points.  Other types of
    -			Connectors will do whatever is appropriate for their particular situation.
    -			</p>
    -			This is the method signature for the <em>paint</em> function:
    -			<div class="code">
    -				<pre>this.paint = function(dimensions, ctx) { .. }</pre>
    -			</div>
    -
    -			here, the 'dimensions' argument to the 'paint' function is the return value of the 'compute' function.  The 'ctx' argument is the Canvas context; you
    -			will do all your drawing on this.
    -			</p>
    -			To change the connector from the default, specify it in your connect call:
    -			<div class="code">
    -				<pre>jsPlumb.connect({source:'someWindow', target:'otherWindow', connector:new jsPlumb.Connectors.Straight()});</pre>
    -			</div>
    -			note that you can use shorthand for connectors if you don't need to specify parameters to it:
    -			<div class="code">
    -				<pre>jsPlumb.connect({source:'someWindow', target:'otherWindow', connector:"Straight"});</pre>
    -			</div>
    -			This works in the same way as specifying Anchors as string does.
    -<p>
    -The section below this discusses Overlays, which allow you to decorate Connectors with
    -			things such as labels or arrows or whatever else you like. Overlays can only work with Connectors that implement a few helper functions.
    -			<h4>The concept of <strong>location</strong></h4>
    -			Before discussing the helper functions you first must be across the concept of 'location' as used by this mechanism.  The <em>location</em>
    -			on a connector can be a decimal value between 0 and 1 inclusive, which marks a proportionate amount of travel <em>along the path
    -			inscribed by the Connector</em>.  For a straight line connector this is a simple piece of maths, but for Bezier connectors it's a little
    -			bit more involved.
    -			<h4>Required Helper Methods</h4>
    -			<ul>
    -				<li><strong>pointOnPath(location)</strong> - returns an [x,y] point corresponding to the given location</li>
    -				<li><strong>pointAlongPathFrom(location, distance)</strong> - returns an [x,y] point corresponding to travelling 'distance' pixels along the connector from 'location'.</li>
    -				<li><strong>gradientAtPoint(location)</strong> - returns the connector's gradient at the given location.  For linear connectors such as Straight this is constant, but for Bezier connectors the gradient changes continually.</li>
    -				<li><strong>perpendicularToPathAt(location, distance, length)</strong> - returns a line that is perpendicular to (and centered on) the connector at 'distance' from the given location, with length 'length'.</li>
    -			</ul>
    -</p>
    -<h4><a id="customEndpoints">Custom Endpoints</a></h4>
    -<p>To create your own Endpoint implementation, you need to implement a single method:
    -				<div class="code"><pre>paint : function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { ... }</pre></div>
    -				The arguments to this method are as follows:
    -				<ul>
    -					<li>anchorPoint - [x,y] location of the anchor point on screen</li>
    -					<li>orientation - [x,y] hints for the general direction the anchor points to</li>
    -					<li>canvas - the canvas to draw into</li>
    -					<li>endpointStyle - Javascript object containing style directives as discussed above.  The contents of this are arbitrary, so if you write a new Endpoint that needs some extra settings, you can add them no hassle.</li>
    -					<li>connectorPaintStyle - the style being used to paint the associated Connector.</li>
    -				</ul>
    -			</p>
    -			<p>
    -				It is your responsibility to size and locate the canvas to suit your needs. jsPlumb provides the following
    -				helper method to assist you:
    -				<div class="code"><pre>jsPlumb.sizeCanvas(canvas, x, y, width, height);</pre></div>
    -				Allows you to locate the canvas on screen and to size it.
    -			</p>
    -<h4><a id="customOverlays">Custom Overlays</a></h4>
    -			Overlays can only work with Connectors that implement the methods <strong>pointOnPath</strong>, <strong>pointAlongPathFrom</strong>,
    -			<strong>gradientAtPoint</strong>, and <strong>perpendicularToPathAt</strong>.  These are discussed in more detail in the Custom Connectors section above.  Both of the Connectors
    -			that come with jsPlumb - Straight and Bezier -implement these methods; if you write a custom connector, or have written a custom connector, you will need to supply
    -			them.   
    -			<h4>Overlay Interface</h4>
    -			An Overlay is required to implement two methods in order to be usable by jsPlumb:
    -			<ul>
    -				<li><strong>computeMaxSize(connector, context)</strong> - returns an integer value indicating the larger of this overlay's width and height.  It is used by jsPlumb to ensure that the canvas is large enough to accomodate the overlay.  The examples below should help to clarify this.</li>
    -				<li><strong>draw(connector, ctx)</strong> - draws the overlay.  What happens in this method is up to the given implementation.</li>
    -			</ul>
    -			<h4>computeMaxSize methods</h4>
    -			This is the Arrow overlay's computeMaxSize method:
    -			<div class="code">
    -<pre>
    -this.computeMaxSize = function() { return width; }
    -</pre>			
    -			</div>
    -Here, <em>width</em> is a private member of Arrow that indicates the width of the arrow's tail.  So the Arrow overlay reports that figure as the width it needs.					
    -Contrast this with the Label Overlay's computeMaxSize method:
    -		<div class="code">
    -<pre>
    -this.computeMaxSize = function(connector, ctx) {
    -  	if (labelText) {
    -  		ctx.save();
    -        if (self.labelStyle.font) ctx.font = self.labelStyle.font;
    -         var t = ctx.measureText(labelText).width;			            
    -		// a fake text height measurement: use the width of upper case M
    -		var h = ctx.measureText("M").width;					
    -		labelPadding = self.labelStyle.padding || 0.25;
    -		labelWidth = t + (2 * t * labelPadding);
    -		labelHeight = h + (2 * h * labelPadding);
    -		ctx.restore();
    -		return Math.max(labelWidth, labelHeight);
    -  	}
    - 	return 0;
    - };
    -</pre>			
    -			</div>
    -The Label overlay has to use the context to determine how big it will be on screen.
    -
    -<h4>draw methods</h4>
    -To give you a taste for how you can interact with a connector, consider the first few lines of the Arrow overlay's draw method:
    -<div class="code">
    -<pre>
    -this.draw = function(connector, ctx) {
    -	// this is the arrow head position
    -	var hxy = connector.pointAlongPathFrom(self.loc, length / 2);		
    -	// this is the center of the tail
    -	var txy = connector.pointAlongPathFrom(self.loc, -length / 2), tx = txy[0], ty = txy[1];
    -	// this is the tail vector
    -	var tail = connector.perpendicularToPathAt(self.loc, -length / 2, width);
    -	
    -	...
    -</pre>
    -</div>
    -	<p>The first two calls to the connector use the <strong>pointAlongPathFrom</strong> method, which passes <em>self.loc</em> as the location of the
    -	point on the path, and <em>length / 2</em> as the number of pixels along the path to traverse.</p>
    -	<p><em>self.loc</em> is Arrow's internal member describing the location of the overlay, and it is a decimal (between 0 and 1 inclusive) that points to
    -	a location at some distance along the path inscribed by the connector.  So these first two calls get us [x,y] locations of points on 
    -	the connector that mark the head and tail points for the arrow.  
    -	</p>		
    -	<p>
    -	The <strong>connector.perpendicularToPathAt(self.loc, -length / 2, width)</strong> call returns a line description of a line that is perpendicular to, and centered on, the connector
    -	at "-length / 2" pixels from the given point, with the given width (width is an internal member of Arrow).  At this point, the Arrow object has the three main points it
    -	needs in order to draw itself.
    -	</p>
    -		</div-->
    -</div>
    -
    -</body>
    -
    -</html>
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.2/index.html b/demo/doc/archive/1.3.2/index.html
    deleted file mode 100644
    index 0bb45af38..000000000
    --- a/demo/doc/archive/1.3.2/index.html
    +++ /dev/null
    @@ -1,72 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.2 documentation</title>
    -		<link rel="stylesheet" target="contentFrame" href="/mp.css"></link>
    -		<link rel="stylesheet" target="contentFrame" href="../css/jsPlumbDemo.css"></link>
    -	</head>
    -	<body>
    -		<div class="index section">
    -			<h3>jsPlumb 1.3.2</h3>
    -			<ul>
    -				<li><a target="contentFrame" href="content.html#summary">Summary</a></li>
    -				<li><a target="contentFrame" href="content.html#changes">Changes since version 1.3.1</a></li>
    -				
    -				<li><h4>Setup</h4></li>
    -				<li><a target="contentFrame" href="content.html#imports">Required Imports</a></li>
    -				<li><a target="contentFrame" href="content.html#renderMode">Render Mode</a></li>
    -				<li><a target="contentFrame" href="content.html#initializing">Initializing jsPlumb</a></li>
    -				<li><a target="contentFrame" href="content.html#unload">Unloading jsPlumb</a></li>
    -				<li><a target="contentFrame" href="content.html#multipleInstances">Multiple jsPlumb Instances</a></li>
    -				<li><a target="contentFrame" href="content.html#zIndex">Z-index Considerations</a></li>
    -				<li><a target="contentFrame" href="content.html#containerDefault">Where does jsPlumb add elements?</a></li>
    -				
    -				<li><h4>Introduction</h4></li>
    -				<li><a target="contentFrame" href="content.html#jsPlumbBasics">Basic Concepts</a></li>
    -				<li><a target="contentFrame" href="content.html#anchors">Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#dynamicAnchors">Dynamic Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#definitions">Connector, Endpoint & Overlay Definitions</a></li>
    -				<li><a target="contentFrame" href="content.html#connectors">Connectors</a></li>
    -				<li><a target="contentFrame" href="content.html#endpoints">Endpoints</a></li>
    -				<li><a target="contentFrame" href="content.html#overlays">Overlays</a></li>
    -				<li><a target="contentFrame" href="content.html#defaults">Defaults</a></li>								
    -				
    -				<li><h4>Connections</h4></li>
    -				<li><a target="contentFrame" href="content.html#simpleConnections">Programmatic Connections</a></li>
    -				<li><a target="contentFrame" href="content.html#draggableConnections">Connections using Drag and Drop</a></li>
    -				<li><a target="contentFrame" href="content.html#dragOptions">Drag Options</a></li>
    -				<li><a target="contentFrame" href="content.html#dropOptions">Drop Options</a></li>
    -				<li><a target="contentFrame" href="content.html#dragAndDropScope">Drag and Drop Scope</a></li>
    -				
    -				<li><h4>Appearance</h4></li>
    -				<li><a target="contentFrame" href="content.html#paintstyles">Paint Styles</a></li>
    -				<li><a target="contentFrame" href="content.html#hoverpaintstyles">Hover Paint Styles</a></li>
    -				<li><a target="contentFrame" href="content.html#gradients">Gradients</a></li>				
    -				
    -				<li><h4>Miscellaneous</h4></li>
    -				<li><a target="contentFrame" href="content.html#cssclasses">CSS Class Reference</a></li>
    -				<li><a target="contentFrame" href="content.html#animation">Animation</a></li>
    -				<li><a target="contentFrame" href="content.html#connectionInfo">Retrieving Connection Information</a></li>
    -				
    -				<li><h4>Events</h4></li>
    -				<li><a target="contentFrame" href="content.html#events">jsPlumb Events</a></li>
    -				<li><a target="contentFrame" href="content.html#connectionEvents">Connection Events</a></li>
    -				<li><a target="contentFrame" href="content.html#endpointEvents">Endpoint Events</a></li>
    -				<li><a target="contentFrame" href="content.html#overlayEvents">Overlay Events</a></li>
    -				
    -				<li><h4>Examples</h4></li>				
    -				<li><a target="contentFrame" href="content.html#examples">Programmatic API Examples</a></li>
    -				<li><a target="contentFrame" href="content.html#dragAndDropExamples">Draggable Connections Examples</a></li>
    -				<li><a target="contentFrame" href="content.html#utilityFunctions">Utility Functions</a></li>				
    -				
    -				<li><h4>Develop</h4></li>
    -				<li><a target="contentFrame" href="content.html#which">Which files are which?</a></li>
    -				<li><a target="contentFrame" href="content.html#pluggableLibrarySupport">Pluggable Library Support</a></li>
    -				<!-- li><a target="contentFrame" href="content.html#customConnectors">Custom Connectors</a></li>
    -				<li><a target="contentFrame" href="content.html#customEndpoints">Custom Endpoints</a></li>
    -				<li><a target="contentFrame" href="content.html#customOverlays">Custom Overlays</a></li-->
    -			</ul>
    -			<strong>This document refers to release 1.3.2 of jsPlumb.</strong>  
    -			<strong>6 Aug 2011</strong>
    -		</div>		
    -	</body>
    -</html>
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.2/usage.html b/demo/doc/archive/1.3.2/usage.html
    deleted file mode 100644
    index e3e45f8fe..000000000
    --- a/demo/doc/archive/1.3.2/usage.html
    +++ /dev/null
    @@ -1,10 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.2 - documentation</title>		
    -	</head>
    -	
    -	<frameset cols="250,*">
    -  		<frame src="index.html" />
    -  		<frame src="content.html" name="contentFrame" />
    -	</frameset>	
    -</html>
    diff --git a/demo/doc/archive/1.3.3/content.html b/demo/doc/archive/1.3.3/content.html
    deleted file mode 100644
    index 4f6e9ec78..000000000
    --- a/demo/doc/archive/1.3.3/content.html
    +++ /dev/null
    @@ -1,2366 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.3 - documentation</title>
    -		<link rel="stylesheet" href="/mp.css"></link>
    -		<link rel="stylesheet" href="jsPlumbDoc.css"></link>	
    -	</head>
    -	<body>
    -
    -<div class="menu"><a href="http://jsplumb.org" class="mplink" target="_top">view demo</a>&nbsp;|&nbsp;<a href="mailto:simon.porritt@gmail.com" class="mplink">contact me</a></div>
    -
    -<div class="content">
    -		<div class="section">
    -			<h3><a id="summary">Summary</a></h3>
    -			jsPlumb allows you to connect elements on the screen using SVG, Canvas or VML, depending on the capabilities of the browser.
    -			<p>
    -				It can be used with jQuery, MooTools or YUI3 (or another library of your choice if you
    -				feel like implementing an adapter for it).
    -			</p>  
    -
    -			<h5>Browser Compatibility</h5>
    -			<p>jsPlumb 1.3.3 has been tested on the following browsers:
    -				<ul>
    -					<li class="bullet">IE 6 on Windows XP</li>
    -					<li class="bullet">IE 7 on Windows XP</li>
    -					<li class="bullet">IE 8 on Windows XP</li>
    -					<li class="bullet">Firefox 3.5.8 on Windows XP</li>
    -					
    -					<li class="bullet">IE 9 on Windows 7</li>
    -					<li class="bullet">Chrome 12 on Windows 7</li>
    -					<li class="bullet">Firefox 3.5.8 on Windows 7</li>
    -					
    -					<li class="bullet">Firefox 3.6.3 on Ubuntu 10.04</li>
    -					<li class="bullet">Chrome on Ubuntu 10.04</li>
    -					
    -					<li class="bullet">Safari 4 on Mac Tiger</li>
    -					<li class="bullet">Safari 4 on Windows Vista</li>
    -					<li class="bullet">Safari 5.0.5 on Windows 7</li>
    -					<li class="bullet">Opera 10.54 on Windows XP</li>
    -				</ul>
    -			</p>
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="changes">Changes since version 1.3.1</a></h3>
    -			<p>
    -			Version 1.3.3 contains a few new methods for working with Overlays, and a couple of bug fixes.  Since a few 1.3.x versions have been released in a short amount
    -			of time, I've left in the notes from 1.3.1, as there are a few backwards compatibility issues you may need to know about.		
    -			</p>
    -			<h5>New Features</h5>
    -				<ul>
    -					<li>added getOverlay(id) to Connection</li>
    -					<li>added hideOverlay(id) to Connection</li>
    -					<li>added showOverlay(id) to Connection</li>
    -					<li>added hide() to Overlay</li>
    -					<li>added show() to Overlay</li>
    -					<li>added setVisible(state) to Overlay</li>
    -				</ul>
    -			
    -			<h5>Issues Resolved</h5>
    -			<ul>
    -				<li><strong>120</strong> - dynamic anchors get confused by scrolled div</li>
    -				<li><strong>127</strong> - original event was lost in the jsPlumb shortcut bind methods</li>
    -			</ul>
    -			<p>
    -			Version 1.3.1 was a major new release of jsPlumb, containing several important new features, and a few changes that break backwards compatibility.
    -			</p>
    -			<p>
    -			Major new features include:
    -			</p>			
    -			<ul>
    -				<li>- introduced support for using SVG</li>
    -				<li>- removed dependency on Excanvas script for IE&lt;9. Jsplumb draws VML directly now.</li>
    -				<li>- introduced support for mouse events (click/doubleclick/enter/exit) for all browsers</li>
    -				<li>- introduced support for turning an entire element into a drag target for new connections</li>
    -			</ul>
    -			<p>
    -			Backwards Compatibility Issues:			
    -			</p>
    -			<ul>
    -				<li>
    -					<p>- you cannot create Endpoints/Connectors/Overlays directly anymore.  You must supply jsPlumb with a definition of what you
    -					 want, and it creates the actual object.  For example:</p>
    -	  				<pre>
    -OLD: 	jsPlumb.Defaults.Endpoint = new jsPlumb.Endpoints.Dot(45);
    -NEW:	jsPlumb.Defaults.Endpoint = [ "Dot", { radius:45 } ];
    -	  				</pre>	
    -				</li>					  	  	  	
    -			
    -				<li>
    -					<p>- Bezier connection requires a js object as argument now, instead of a single int</p>
    -					<pre>
    -OLD:  	connector: [ "Bezier", 150 ]
    -NEW: 	connector: [ "Bezier", { curviness:150 } ]
    -					</pre>
    -				</li>
    -				<li>
    -					<p>- Dot Endpoint requires a js object as argument now, instead of a single int</p>
    -					<pre>
    -OLD:	endpoint: [ "Dot", 10 ]
    -NEW:	endpoint: [ "Dot", { radius:10 } ]
    -					</pre>		
    -				</li>
    -				<li>
    -					<p>- getConnections has been reworked a little to make it easier to use.  See the API documentation.</p>
    -				</li>
    -				<li>
    -				<p>- The 'container' concept, as an argument to addEndpoint or connect, and in the jsPlumb defaults, has been 
    -				removed.  Created elements are now appended to the parent of the source element of a connection, or the parent of an element
    -				to which you are adding an Endpoint.
    -				</p>  
    -				</li>
    -				<li>
    -					<p>- The 'backgroundPaintStyle' concept has been replaced with 'outlineColor' and 'outlineWidth' members in paint style definitions.
    -					To use these, you must supply at least 'outlineWidth'; if 'outlineColor' is not supplied, jsPlumb will use black by default.
    -					</p>
    -				</li>
    -				<li>
    -					<p>- Functionality to drag an entire connection, by clicking and dragging the connector line, which was introduced for some browsers 
    -					in 1.2.6, has been removed.  I had several requests for a way to disable this, and got the distinct impression it was not
    -					very popular.
    -					</p>		
    -				</li>
    -				<li>
    -					<p>- The 'setDefaultNewCanvasSize' method on jsPlumb has been removed.  jsPlumb no longer uses excanvas to render
    -	  				VML; this method was there to support edge cases of VML usage. You probably were not even aware of this method's existence.
    -	  				</p>		
    -				</li>		
    -			</ul>
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="init">Setup</a></h3>
    -
    -<h4><a id="imports">Required Imports</a></h4>	
    -							<h4>jQuery</h4>
    -			<ul>
    -				<li class="bullet">jQuery 1.3.x or higher.</li>
    -				<li class="bullet">jQuery UI 1.7.x or 1.8.x (if you wish to support drag and drop).</li>
    -			</ul>
    -			<div class="code">
    -				<pre>&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.0/jquery.min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/jquery-ui.min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/jquery.jsPlumb-1.3.3-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			<h4>MooTools</h4>
    -				<ul>
    -					<li class="bullet">MooTools Core 1.2.4 or higher. jsPlumb has been tested with 1.2.4 and 1.3.3.</li>
    -					<li class="bullet">Drag.Move from MooTools More 1.2.4.4 or higher (if you wish to support drag and drop).</li>
    -				</ul>
    -			<div class="code">
    -			<pre>&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/mootools/1.3.3/mootools-yui-compressed.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO_MOO_TOOLS_MORE_1_3_2_1"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/mootools.jsPlumb-1.3.3-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			<h4>YUI3</h4>
    -			<ul>
    -				<li class="bullet">YUI 3.3.x.  jsPlumb has been tested on 3.3.0 only; it is possible other 3.x.x versions will work.</li>
    -			</ul>
    -			<div class="code">
    -			<pre>&lt;script type="text/javascript" src="http://yui.yahooapis.com/3.3.0/build/simpleyui/simpleyui-min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/yui.jsPlumb-1.3.3-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			
    -			<h4><a id="renderMode">Render Mode</a></h4>
    -			jsPlumb can use SVG, HTML5 Canvas elements or VML to render the various objects it adds to the display.  Most modern browsers support
    -			Canvas and SVG; IE&lt;9 browsers support neither.
    -			<p>
    -				By default, jsPlumb attempts to use <strong>jsPlumb.CANVAS</strong> as the render mode, falling back to <strong>jsPlumb.VML</strong> if the 
    -				browser is IE&lt;9.  You can change the render mode by making this call:  
    -			</p>
    -			<div class="code">
    -<pre>jsPlumb.setRenderMode(jsPlumb.SVG);</pre>			
    -			</div>
    -			if, for some reason, you set the render mode to be <strong>jsPlumb.VML</strong> but you're in any browser other than IE&lt;9, jsPlumb
    -			will use Canvas.
    -			<p>
    -			The <strong>jsPlumb.setRenderMode</strong> method returns you the render mode that jsPlumb actually ended up setting.  Valid values for setRenderMode are:
    -			</p>
    -			<ul>
    -				<li class="bullet">jsPlumb.CANVAS</li>
    -				<li class="bullet">jsPlumb.SVG</li>
    -				<li class="bullet">jsPlumb.VML</li>
    -			</ul>			
    -			
    -			<h4><a id="initializing">Initializing jsPlumb</a></h4>
    -			You should not start making calls to jsPlumb until the DOM has been initialized - perhaps no surprises there.  With YUI, though, the asynchronous
    -			nature of the script loading process means that you are not guaranteed that jsPlumb is ready as soon as the DOM is.  To handle this,
    -			you should bind to the "ready" event on jsPlumb (or the instance of jsPlumb you are working with):
    -			<div class="code">
    -			<pre>jsPlumb.bind("ready", function() {
    -...	
    -		
    -// your jsPlumb related init code goes here
    -
    -...
    -
    -});						
    -			</pre>
    -			</div>
    -			In 1.3.3 a helper method was added that can save you approximately 9 (count them!) characters:
    -			<div class="code">
    -			<pre>jsPlumb.ready(function() {
    -...	
    -		
    -// your jsPlumb related init code goes here
    -
    -...
    -
    -});						
    -			</pre>
    -			</div>
    -			<p>
    -			For jQuery and MooTools you do not actually need to do this; by the time the DOM ready event is fired in each of those libraries you can be sure 
    -			all the JS we need has been loaded.  But in terms of explicitly calling out that you are waiting for jsPlumb, it seems like a good practice to bind to the "ready" event.
    -			</p>
    -			<p>
    -			If you bind to the "ready" event after jsPlumb has already been initialized, your callback will be executed immediately.
    -			</p>			
    -			<h4><a id="unload">Unloading jsPlumb</a></h4>
    -			jsPlumb offers a method you can call when your page is unloading.  You should do this to insure
    -			against memory leaks.  You configure it like this:
    -				<div class="code">
    -<pre>
    -&lt;body onunload="jsPlumb.unload();"&gt;
    -
    -...
    -
    -&lt;/body&gt;
    -</pre>
    -				</div>
    -<h4><a id="multipleInstances">Multiple jsPlumb instances</a></h4>
    -			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 <strong>getInstance</strong> method, for example:
    -<div class="code">
    -<pre>
    -var firstInstance = jsPlumb.getInstance();
    -</pre>
    -</div>
    -The variable 'firstInstance' can now be treated exactly as you would treat the 'jsPlumb' variable - you can set
    -defaults, call the connect method, whatever:
    -<div class="code">
    -<pre>
    -firstInstance.Defaults.Connector = [ "Bezier", { curviness: 150 } ];
    -firstInstance.Defaults.Anchors = [ "TopCenter", "BottomCenter" ];
    -
    -firstInstance.connect({source:"element1", target:"element2", scope:"someScope" });
    -</pre>
    -</div>
    -
    -<strong>getInstance</strong> optionally takes an object that provides the defaults:
    -<div class="code">
    -<pre>
    -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" });
    -</pre>
    -</div>
    -
    -		<h4><a id="zIndex">Z-index Considerations</a></h4>
    -		<p>
    -			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. 
    -		</p>  
    -		<p>
    -			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).  			
    -		</p>
    -		<p>
    -			To help you organise z-indices correctly, jsPlumb adds a CSS class to each type of
    -			element it adds. They are as follows:			
    -		</p>
    -		<table style="color:black;width:70%;font-size:90%;">
    -			<tr><td><strong>type of element</strong></td><td><strong>class added</strong></td></tr>
    -			<tr><td>Endpoint</td><td>_jsPlumb_endpoint</td></tr>
    -			<tr><td>Connector</td><td>_jsPlumb_connector</td></tr>
    -			<tr><td>Overlay</td><td>_jsPlumb_overlay</td></tr>
    -		</table>
    -
    -		<h4><a id="containerDefault">Where does jsPlumb add elements?</a></h4>
    -		<p>
    -			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.
    -		</p>
    -		<p>
    -			Prior to 1.3.0, jsPlumb added everything to the end of the <em>body</em> element. This
    -			has the advantage of being the most flexible arrangement in terms of supporting what
    -			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!   
    -		</p>
    -		<p>
    -			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 (<strong>not</strong> 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.
    -		</p>
    -		
    -		<p>
    -			In general, the default behaviour means that you cannot do things like this:
    -		</p>
    -		<div class="code">
    -<pre>
    -&lt;body&gt;
    -	&lt;div id="container0"&gt;
    -		&lt;div id="node0"&gt;&lt;/div&gt;
    -	&lt;/div&gt;
    -	&lt;div id="container1"&gt;
    -		&lt;div id="node1"&gt;&lt;/div&gt;
    -	&lt;/div&gt;
    -&lt;/body&gt;
    -
    -&lt;script type="text/javascript"&gt;
    -
    -	var e0 = jsPlumb.addEndpoint("node0"),
    -	e1 = jsPlumb.addEndpoint("node1");
    -
    -	jsPlumb.connect({ source:e0, target:e1 });
    -&lt;/script&gt;
    -</pre>		
    -		</div>
    -<p>
    -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):  
    -</p>
    -		<div class="code">
    -<pre>
    -&lt;body&gt;
    -	&lt;div id="container0"&gt;
    -	&lt;/div&gt;
    -	&lt;div id="container1"&gt;
    -	&lt;/div&gt;
    -&lt;/body&gt;
    -
    -&lt;script type="text/javascript"&gt;
    -
    -	var e0 = jsPlumb.addEndpoint("container0"),
    -	e1 = jsPlumb.addEndpoint("container1");
    -
    -	jsPlumb.connect({ source:e0, target:e1 });
    -&lt;/script&gt;
    -</pre>		
    -		</div>
    -	now 'e0' and 'e1' have the same parent - the <em>body</em> element. 
    -	
    -	<p>Remember that you can use the <strong>anchor</strong> parameter to an <strong>addEndpoint</strong>
    -	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. 
    -	</p>									
    -	<p><strong>Note regarding the drawEndpoints option on jsPlumb.connect</strong>: 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). 
    -	</p>
    -	
    -	<h5>Overriding the default behaviour</h5>
    -	<p>
    -		From version 1.3.3, the <strong>container</strong> 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 <strong>jsPlumb.Defaults.Container</strong> property,
    -		or you can set a <strong>container</strong> parameter on calls to <strong>addEndpoint</strong> or <strong>connect</strong> (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:
    -	</p>
    -		
    -		Set a container to use as the default container, using a jQuery selector (you can supply MooTools/YUI selectors if that's the library you're using),
    -		and then add an Endpoint.  The canvas (or SVG/VML element) created will be a child of the document body:
    -		<div class="code">
    -<pre>
    -jsPlumb.Defaults.Container = $("body");
    -...
    -jsPlumb.addEndpoint(someDiv, { endpoint options });
    -</pre>		
    -		</div>
    -		
    -			Set a container to use as the default container, using an element id,
    -		and then connect two elements.  The canvases (or SVG/VML elements) created in this example will be children of the element with id "containerId":
    -		<div class="code">
    -<pre>
    -jsPlumb.Defaults.Container = "containerId";
    -...
    -jsPlumb.connect({ source:someDiv, target:someOtherDiv });
    -</pre>		
    -		</div>
    -		
    -		Pass a container to use into a jsPlumb.connect call, using a selector.  The canvases (or SVG/VML elements) created in this example will be children of the element with id "containerId":
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({ source:someDiv, target:someOtherDiv, container:$("#containerId") });
    -</pre>		
    -		</div>
    -				Pass a container to use into a jsPlumb.addEndpoint call, using an element id. The canvas (or SVG/VML element) created in this example will be a child of the element with id "containerId":
    -		<div class="code">
    -<pre>
    -jsPlumb.addEndpoint(someDiv, { ..., container:"containerId" });
    -</pre>		
    -		</div>
    -		
    -		</div>
    -				
    -		<div class="section">
    -			<h3><a id="jsPlumbBasics">Basic Concepts</a></h3>
    -	
    -			jsPlumb is all about connecting things together, so the core abstraction in jsPlumb is the <strong>Connection</strong> object, which is itself
    -			broken down into these four concepts:
    -
    -			<ul>
    -				<li><strong>Anchor</strong> - a location, relative to an element's origin, at which an Endpoint can exist. You do not create these yourself; you supply hints to the various jsPlumb functions, which create them as needed.  They have no visual representation; they are a logical position only. Anchors can be referenced by name, for the Anchors that jsPlumb ships with, or a four element array describing [ x, y, x orientation, y orientation ]. See the <a href="#anchors">anchors</a> section for more detail.<br/><br/></li>
    -				<li><strong>Endpoint</strong> - the visual representation of one end of a Connection.  You can create and attach these to elements 
    -				yourself, which you are required to do to support drag and drop, or have jsPlumb create them when creating a Connection 
    -				programmatically using jsPlumb.connect(...).  You can also join two Endpoints programmatically, by passing them as arguments to 
    -				jsPlumb.connect(...).<br/><br/></li>				
    -				<li><strong>Connector</strong> - the visual representation of the line connecting two elements in the page.  jsPlumb has three types of these available as defaults - a Bezier curve, a straight line, and 'flowchart' connectors. You do not interact with Connectors; you just specify definitions of them when you need to - see <a href="#definitions">Connector, Endpoint & Overlay Definitions</a> <br/><br/></li>
    -				<li><strong>Overlay</strong> - a UI component that is used to decorate a Connector, such as a label, arrow, etc.<br/><br/></li>
    -			</ul>
    -						
    -			<p>One Connection is made up of two Endpoints, a Connector, and zero or more Overlays working together to join two elements.</p>
    -			<p>jsPlumb's public API exposes only Connection and Endpoint, handling the creation and configuration of everything else internally.
    -			But you still need to be across the concepts encapsulated by Anchor, Connector and Overlay.
    -			</p>
    -			</div>
    -			
    -			<div class="section">
    -			<h3><a id="anchors">Anchors</a></h3>			
    -			<p>An Anchor models the notion of where on an element a Connector should connect.  jsPlumb has nine default anchor locations you
    -			can use to specify where the Connectors connect to elements: these are the four corners of an element,
    -			the center of the element, and the midpoint of each edge of the element:  
    -			<ul>
    -				<li class="bullet">TopCenter</li>
    -				<li class="bullet">TopRight</li>
    -				<li class="bullet">RightMiddle</li>
    -				<li class="bullet">BottomRight</li>
    -				<li class="bullet">BottomCenter</li>
    -				<li class="bullet">BottomLeft</li>
    -				<li class="bullet">LeftMiddle</li>
    -				<li class="bullet">TopLeft</li>
    -			</ul>
    -			
    -			Each of these string representations is just a wrapper around the underlying array-based syntax  [x, y, dx, dy], where x and y are
    -coordinates in the interval [0,1] specifying the position of the
    -anchor, and dx and dy are coordinates in the interval [-1, 1]
    -specifying the orientation of the curve incident to the anchor. For
    -example, [0, 0.5, -1, 0] defines a "LeftCenter" anchor with a
    -connector curve that emanates leftward from the anchor. Similarly,
    -[0.5, 0, 0, -1] defines a "CenterTop" anchor with a connector curve
    -emanating upwards. 	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:"BottomCenter", ... }); 
    -</pre>
    -</div>
    -is identical to:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:[ 0.5, 1, 0, 1 ], ... }); 
    -</pre>
    -</div>			
    -
    -		<h3><a id="dynamicAnchors">Dynamic Anchors</a></h3>
    -	These are Anchors that can be positioned in one of a number of locations, choosing the one that is most appropriate each time something moves or is painted in the UI.
    -	<p>
    -	There is no special syntax to creating a DynamicAnchor; you just provide an array of individual Anchor specifications, eg:
    -	</p>
    -<div class="code">
    -<pre>
    -var dynamicAnchors = [ [ 0.2, 0, 0, -1 ],  [ 1, 0.2, 1, 0 ], 
    -			   [ 0.8, 1, 0, 1 ], [ 0, 0.8, -1, 0 ] ];
    -</pre>
    -</div>
    -<h4>	Default Dynamic Anchor</h4>
    -jsPlumb provides a dynamic anchor called "AutoDefault" that chooses from TopCenter, RightMiddle, BottomCenter and LeftMiddle:
    -<div class="code">
    -<pre>jsPlumb.connect({...., anchor:"AutoDefault", ... });</pre>
    -</div>
    -
    -<h4>	Location Selection</h4>
    -The initial implementation of the algorithm that decides which location to choose just calculates which location is closest to the center of the 
    -other element in the connection.  It is possible that future versions of jsPlumb could support more sophisticated choice algorithms, if the need arose.
    -<h4>Draggable Connections</h4>
    -Dynamic Anchors and Draggable Connections can interoperate: jsPlumb locks the position of a dynamic anchor when you start to drag a connection from it,
    -and unlocks it once the connection is either established or abandoned. At that point you may see the position of the dynamic anchor change, as jsPlumb
    -optimises the connection.  <p>You can see this behaviour in the <a href="../html/jquery/draggableConnectorsDemo.html" target="_blank">draggable connections</a> demonstration, when
    -you drag a connection from the blue endpoint on window 1 to the blue endpoint on window 3 - the connection is established and then window 1's blue
    -endpoint jumps down to a location that is closer to window 3.</p> 	  
    -					</div>		
    -												
    -<div class="section">
    -		<h4><a id="definitions">Connector, Endpoint & Overlay Definitions</a></h4>
    -		Before we discuss Connector, Endpoints and Overlays, a quick word on definitions:  whenever you need to define a Connector, Endpoint or Overlay, you must use a "definition" of it, rather than constructing one
    -		directly.  This definition can be either a string that nominates the artifact you want to create:
    -		<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:"Rectangle"
    -});
    -</pre>
    -		</div>
    -		or an array consisting of both the artifact's name and the arguments you want to pass to its constructor:
    -		<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Rectangle", { cssClass:"myEndpoint", width:30, height:10 } ]
    -});
    -</pre>
    -		</div>
    -		there is also a three-argument method that allows you to specify two sets of parameters, which jsPlumb will merge together for you. The idea
    -		behind this is that you will often want to define common characteristics somewhere and reuse them across a bunch of different calls:
    -		<div class="code">
    -<pre>var common = {
    -	cssClass:"myCssClass"
    -};
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Rectangle", { width:30, height:10 }, common ]
    -});
    -</pre>
    -		</div>
    -	This syntax is supported for all Endpoint, Connector and Overlay definitions.  Here's an example using definitions for all three:	
    -		<div class="code">
    -<pre>var common = {
    -	cssClass:"myCssClass"
    -};
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Dot", { radius:5 }, common ],
    -	connector:[ "Bezier", { curviness:100 }, common ],
    -	overlays: [
    -		[ "Arrow", { foldback:0.2 }, common ],
    -		[ "Label", { cssClass:"labelClass" } ]	
    -	]
    -});
    -</pre>
    -		</div>		
    -		
    -		The allowed constructor parameters are different for each artifact you create, but every artifact takes a single JS object as argument,
    -		with the parameters as [key,value] pairs in that object.  See the relevant sections on <a href="#connectors">Connectors</a>,
    -		<a href="#endpoints">Endpoints</a> and <a href="#overlays">Overlays for information on supported parameters.</a>
    -				
    -	</div>
    -
    -
    -		<div class="section">
    -			<h3><a id="connectors">Connectors</a></h3>
    -			<p>Connectors are the lines that actually join elements of the UI.  jsPlumb has three connector implementations - a straight line, a Bezier curve, and
    -			"flowchart" connectors.  The default connector is the Bezier curve.  </p>
    -			<p>You optionally specify a Connector by setting the 'connector' property on a call to jsPlumb.connect or jsPlumb.addEndpoint(s). If you do not
    -			supply a value for 'connector', the default will be used (see <a href="#defaults">defaults</a>).</p>
    -			<p>You specify Connectors using the syntax described above in <a href="#definitions">Connector, Endpoint & Overlay Definitions.</a>Allowed 
    -			constructor values for each Connector type are described below:</p>
    -			
    -			<h4><a id="bezierConnector">Bezier Connector</a></h4>
    -			<p>The Bezier Connector provides a cubic Bezier path between the two Endpoints. It supports a single constructor argument:</p>
    -			<p>
    -			<strong>curviness</strong> - Optional; defaults to 150. This defines the distance in pixels that
    -			the Bezier's control points are situated from the anchor points.  This does not mean that your
    -			connector will pass through a point at this distance from your curve.  It is a hint to how you want the
    -			curve to travel. Rather than discuss Bezier curves at length here, because they are a complex topic,
    -			we refer you to <a href="http://en.wikipedia.org/wiki/B%C3%A9zier_curve" target="_blank">Wikipedia.</a>
    -			</p>
    -			<h4><a id="straightConnector">Straight Connector</a></h4>
    -			<p>The Straight Connector draws a straight line between the two endpoints. No constructor arguments are supported; use the <strong>endpointStyle</strong>
    -			argument to a connect or addEndpoint call to control the appearance of one of these Connectors.  
    -			</p>
    -			<h4><a id="flowchartConnector">Flowchart Connector</a></h4>
    -			<p>This type of Connector draws a connection that consists of a series of vertical or horizontal segments - the classic flowchart look. A single constructor argument is supported:</p>
    -			<strong>stub</strong> - this is the minimum length, in pixels, of the initial stub that emanates from an Endpoint.  This parameter is optional, and defaults to 30 pixels.
    -			
    -		</div>
    -		
    -			<div class="section">
    -			<h3><a id="endpoints">Endpoint Types</a></h3>
    -			<p>An Endpoint is the UI component that marks the location of an Anchor, ie. the place where a Connector joins an
    -			element.  jsPlumb comes with three Endpoint implementations - Dot, Rectangle and Image. You optionally specify Endpoint properties using the
    -			<strong>endpoint</strong> parameter in a call to jsPlumb.connect, jsPlumb.addEndpoint or jsPlumb.makeTarget.
    -			</p>  
    -				<p>As with Connectors and Overlays, you specify Endpoints using the syntax described above in <a href="#definitions">Connector, Endpoint & Overlay Definitions.</a></p>
    -			
    -			The three available Endpoint types, and their constructor arguments, are as follows:
    -					<h4><a id="dotEndpoint">Dot Endpoint</a></h4>
    -					This Endpoint draw a dot on the screen. It supports a single constructor parameter:
    -					<p>
    -						<strong>radius</strong> - Optional; defaults to 10 pixels. Defines the radius of the dot.
    -					</p>
    -					<h4><a id="rectangleEndpoint">Rectangle Endpoint</a></h4>
    -						Draws a rectangle. Supported constructor parameters are:
    -					<p>
    -						<strong>width</strong> - Optional; defaults to 20 pixels. Defines the width of the rectangle.
    -					</p>
    -					<p>
    -						<strong>height</strong> - Optional; defaults to 20 pixels. Defines the height of the rectangle.
    -					</p>
    -					
    -					<h4><a id="imageEndpoint">Image Endpoint</a></h4>
    -						Draws an image from a given URL.  This Endpoint supports one constructor parameter:  
    -					<p>
    -						<strong>src</strong> - Required.  Specifies the URL of the image to use.
    -					</p>
    -					
    -									
    -			
    -			
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="overlays">Overlay Types</a></h3>
    -			<p>
    -			Overlays are UI elements that are painted onto connections, such as labels or arrows.
    -			jsPlumb comes with four defaults:
    -			</p>
    -			<ul>
    -				<li><strong>Arrow</strong> - a configurable arrow that is painted at some point along the connector.  You can control the length and width of the Arrow, the 'foldback' point - a point the tail points fold back into, and the direction (allowed values are 1 and -1; 1 is the default and means point in the direction of the connection)</li>
    -				<li><strong>Label</strong> - a configurable label that is painted at some point along the connector.</li>
    -				<li><strong>PlainArrow</strong> - an Arrow shaped as a triangle, with no foldback.</li>
    -				<li><strong>Diamond</strong> - as the name suggests, a diamond.</li>
    -			</ul> 
    -			The last two are actually just configured instances of the generic Arrow overlay (see examples). 
    -			
    -			<p>You can specify one or more overlays when making a call to jsPlumb.connect or jsPlumb.addEndpoint (but not jsPlumb.makeTarget; overlays
    -			are always derived from what the source of a Connection defines)  The two cases are discussed below:</p>
    -			1. Specifying one or more overlays on a jsPlumb.connect call. In this example we'll create an Arrow with the default options for an Arrow, and
    -			a label with the text "foo":
    -			<div class="code">
    -			<pre>jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});			
    -</pre>
    -			</div>
    -			This connection will have an arrow located halfway along it, and the label "foo" one quarter of the way along.  Notice the <strong>id</strong>
    -			parameter; it can be used later if you wish to remove the Overlay or change its visibility (see below).<br/><br/> 
    -			
    -			2. Specifying one or more overlays on a jsPlumb.addEndpoint call.  Note in this example that we use the parameter 'connectorOverlays' and not
    -			'overlays' as in the last example.  This is because 'overlays' will one day be used to support Endpoint Overlays:
    -			<div class="code">
    -			<pre>jsPlumb.addEndpoint("someDiv", {
    -	...
    -	connectorOverlays:[ 
    -		[ "Arrow", { width:10, length:30, location:1, id:"arrow" } ], 
    -		[ "Label", { label:"foo", id:"label" } ]
    -	],
    -	...
    -});</pre>
    -			</div>	
    -			This connection will have a 10x30 Arrow located right at the head of the connection, and the label "foo" located at the halfway point. Again,
    -			note that <strong>id</strong> parameter passed in to each Overlay spec.<br/><br/>
    -			
    -			 	
    -					<h4><a id="arrowOverlay">Arrow Overlay</a></h4>
    -					<p>This overlay draws an arrow, using four points: the head and two tail points, and a 'foldback' point, which permits the tail
    -					of the arrow to be indented. Available constructor arguments for this Overlay are </p>
    -					<ul>
    -					<li><strong>width</strong> - width of the tail of the arrow</li>
    -					<li><strong>length</strong> - distance from the tail of the arrow to the head</li>
    -					<li><strong>location</strong> - where, as a proportional value from 0 to 1 inclusive, the Arrow should appear on the Connector</li>
    -					<li><strong>direction</strong> - which way to point. Allowed values are 1 (the default, meaning forwards) and -1, meaning backwards </li>
    -					<li><strong>foldback</strong> - how far along the axis of the arrow the tail points foldback in to. Default is 0.623.</li>
    -					<li><strong>paintStyle</strong> - a style object in the form used for paintStyle values for Endpoints and Connectors</li>
    -					</ul>
    -					<h4><a id="plainArrowOverlay">PlainArrow Overlay</a></h4>
    -					<p>This is just a specialized instance of Arrow in which jsPlumb hardcodes 'foldback' to 1, meaning the tail of the Arrow is a flat edge.  All of the constructor parameters from Arrow apply for PlainArrow.</p>
    -					<h4><a id="diamondOverlay">Diamond Overlay</a></h4>
    -					<p>This is a specialized instance of Arrow in which jsPlumb hardcodes 'foldback' to 2, meaning the Arrow turns into a Diamond.  All of the constructor parameters from Arrow apply for PlainArrow.</p>
    -					<h4><a id="labelOverlay">Label Overlay</a></h4>
    -					This provides a text label to decorate Connectors with.  The available constructor arguments are:
    -					<ul>
    -						<li><strong>label</strong> - The text to display.  You can provide a function here instead of plain text: it is passed the Connection as an argument, and it should return a String.</li>
    -						<li><strong>cssClass</strong> - Optional css class to use for the Label.  This is now preferred over using the 'labelStyle' parameter.</li>
    -						<li><strong>labelStyle</strong> - (deprecated, use cssClass instead) Optional arguments for the label's appearance.  Valid entries in this JS object are:
    -							<ul>
    -								<li><em>font</em> - a font string in a format suitable for the Canvas element</li>
    -								<li><em>fillStyle</em> - the color to fill the label's background with. Optional.</li>
    -								<li><em>color</em> - the color of the label's text. Optional.</li>
    -								<li><em>padding</em> - optional padding for the label. This is expressed as a proportion of the width of the label, not in pixels or ems.</li>
    -								<li><em>borderWidth</em> - optional width in pixels for the label's border. Defaults to 0.</li>
    -								<li><em>borderStyle</em> - optional. The color to paint the border, if there is one.</li>
    -								
    -							</ul>
    -						
    -						</li>
    -						<li><strong>location</strong> - As for Arrow Overlay.  Where, proportionally from 0 to 1 inclusive, the label should appear.</li>
    -					</ul>
    -					
    -					<h4><a id="hideshowOverlay">Hiding/Showing Overlays</a></h4>
    -					<p>You can control the visibility of Overlays using the <strong>setVisible</strong> method of Overlays themselves, or with
    -					<strong>showOverlay(id)</strong> or <strong>hideOverlay(id)</strong> on a Connection.</p>
    -					<p>Remember the <strong>id</strong> parameter that we specified in the examples above?  This can be used to retrieve the Overlay
    -					from a Connection:</p>
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -var overlay = connection.getOverlay("myLabel");
    -// now you can hide this Overlay:
    -overlay.setVisible(false);
    -// there are also hide/show methods:
    -overlay.show();
    -overlay.hide();
    -
    -</pre>					
    -					</div>
    -					<p>However, Connection also has two convenience methods you could use instead:
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -connection.hideOverlay("myLabel");
    -
    -// more time passes
    -
    -connection.showOverlay("myLabel");
    -
    -</pre>					
    -					</div>
    -					
    -		<h4><a id="hideshowOverlay">Removing Overlays</a></h4>
    -		<p>Connection also has a <strong>removeOverlay</strong> method, that does what you might expect:
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -connection.removeOverlay("myLabel");
    -
    -
    -</pre>					
    -					</div>							
    -	
    -		</div>
    -	
    -			
    -			<div class="section">
    -			<h3><a id="defaults">Defaults</a></h3>
    -			The easiest way to set a look and feel for your plumbing is to override the defaults that jsPlumb uses. If you
    -			do not do this you are forced to provide your overridden values on every call.  Every argument to the connect and addEndpoint methods has an
    -			associated default value in jsPlumb.<br/><br/>
    -
    -			The defaults that ship with jsPlumb are stored in <em>jsPlumb.Defaults</em>, which is a Javascript object.  Valid entries, and their initial values, are:
    -
    -			<div class="code">
    -<pre>
    -Anchor : "BottomCenter",
    -Anchors : [ null, null ],
    -AppendElementsToBody : false,
    -Connector : "Bezier",
    -DragOptions : { },
    -DropOptions : { },
    -Endpoint : "Dot",
    -Endpoints : [ null, null ],
    -EndpointStyle : { fillStyle : null },
    -EndpointStyles : [ null, null ],
    -EndpointHoverStyle : null,
    -EndpointHoverStyles : [ null, null ],
    -HoverPaintStyle : null,
    -LabelStyle : { color : "black" },
    -LogEnabled : false,
    -Overlays : [ ],
    -MaxConnections : 1,
    -MouseEventsEnabled : true,
    -PaintStyle : { lineWidth : 8, strokeStyle : "#456" },
    -RenderMode : "canvas",
    -Scope : "_jsPlumb_DefaultScope"
    -</pre>
    -			</div>
    -			<p>
    -			Note that in EndpointStyle, the default fillStyle is 'null'.  This instructs jsPlumb to use the strokeStyle
    -from the attached connector to fill the endpoint.</p>
    -<p>Note also that you can specify either or both (or neither) of 'EndpointStyle' and 'EndpointStyles'.  This allows you to specify a different
    -end point for each end of a connection.  'Endpoint' and 'Endpoints' use the same concept.  jsPlumb will look first in the
    -individual endpoint/endpoint style arrays, and then fall back to the single default version.</p>
    -
    -			you can override these defaults by including this in a script somewhere:
    -			<div class="code">
    -<pre>
    -jsPlumb.Defaults.PaintStyle = {
    -	lineWidth:13,
    -	strokeStyle: 'rgba(200,0,0,100)'
    -}
    -
    -jsPlumb.Defaults.DragOptions = { cursor: "crosshair" };
    -
    -jsPlumb.Default.Endpoints = [ [ "Dot", 7 ], [ "Dot", 11 ] ];
    -
    -jsPlumb.Defaults.EndpointStyles = [{ fillStyle:"#225588" }, { fillStyle:"#558822" }];</pre>
    -			</div>
    -			after the jsPlumb script has been loaded of course!  Here we have specified the following default behaviour:
    -			<ul>
    -				<li>- connectors are 13 pixels wide and painted with a semi-transparent red line</li>
    -				<li>- when dragging an element the crosshair cursor is used</li>
    -				<li>- the source endpoint is a dot of radius 7; the target endpoint is a dot of radius 11</li>
    -				<li>- the source endpoint is blue; the target endpoint is green</li>
    -			</ul>
    -
    -		</div> 					
    -
    -<div class="section">
    -			<h3><a id="simpleConnections">Connections</a></h3>
    -			<h4><a id="simpleConnections">Programmatic Connections</a></h4>
    -The most simple connection you can make with jsPlumb looks like this:
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({source:"element1", target:"element2"});</pre>			
    -			</div>
    -
    -		In this example we have created a Connection from 'element1' to 'element2'.  Remember that a Connection in jsPlumb consists of two Endpoints,
    -		a Connector, and zero or more Overlays.  But this call to 'connect' supplied none of those things, so jsPlumb uses the default values wherever it needs to.
    -		In this case, default values have been used for the following: 
    -		<ul>
    -			<li>- The type and appearance of each Endpoint in the Connection. jsPlumb's default for this is the "Dot" endpoint, of radius 10, with fill color "#456".</li>
    -			<li>- The Anchors that define where the connection's Endpoints appear on each element. The jsPlumb default is "BottomCenter"</li>			
    -			<li>- Whether or not each Endpoint can be a source or target for new Connections. The default is false.</li>
    -			<li>- The type and appearance of the Connection's Connector. The default is a "Bezier" connector of line width 8, and color "#456".</li>
    -		</ul>
    -		<p>
    -		So this call will result in an 8px Bezier, colored "#456", from the bottom center of 'element1' to the bottom center of 'element2', and each
    -		Endpoint will be a 10px radius Dot Endpoint, colored "#456".
    -		</p>
    -		<p>
    -		Let's beef up this call a little and tell jsPlumb what sort of Endpoints we want, and where we want them:		
    -		</p>
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:"element1", 
    -	target:"element2",
    -	anchors:["RightMiddle", "LeftMiddle" ],
    -	endpoint:"Rectangle",
    -	endpointStyle:{ fillStyle: "yellow" }
    -});
    -</pre>			
    -			</div>
    -			
    -		<p>This is what we have told jsPlumb we want this time:</p>
    -		<ul>
    -			<li><strong>anchors</strong> - this array tells jsPlumb where the source and target Endpoints should be located on their parent elements. In this case,
    -			we use the shorthand syntax to name one of jsPlumb's default anchors; you can also specify custom locations (see <a href="#anchors">anchors</a>).
    -			Instead of <strong>anchors</strong> you can use <strong>anchor</strong>, if you want the source and target Endpoints to be located at the
    -			same place on their parent elements.			<br/>
    -			</li>
    -			<li>
    -				<strong>endpoint</strong>  - this tells jsPlumb to use the "Rectangle" Endpoint for both the source and target of the Connection.  As with anchors, 
    -				<strong>endpoint</strong> has a plural version that allows you to specify a different Endpoint for each end of the Connection.<br/>  
    -			</li>
    -			<li>
    -				<strong>endpointStyle</strong> - this is the definition of the appearance of the Endpoint you specified above.  Again, there is a
    -				plural equivalent of this that allows you to specify a different style for each end of the Connection. For more information about
    -				allowed values for this value, see <a href="#paintstyles">Connector, Endpoint & Overlay Styles</a>. 
    -			</li>
    -		</ul>		
    -		
    -		<h5>Reusing common settings between jsPlumb.connect calls</h5>
    -		<p>
    -			A fairly common situation you will find yourself in is wanting to create a bunch of Connections that have only minor differences
    -			between them.  To support that, <strong>jsPlumb.connect</strong> takes an optional second argument. For example:			 
    -		</p>
    -		<div class="code">
    -<pre>
    -var common = {
    -	anchors:[ "BottomCenter", "TopCenter" ],
    -	endpoints:["Dot", "Blank" ]
    -};
    -
    -jsPlumb.connect({ source:"someElement", target:"someOtherElement" }, common);
    -
    -jsPlumb.connect({ source:"aThirdElement", target:"yetAnotherElement" }, common);
    -
    -</pre>			
    -			</div>							
    -					
    -		<h4><a id="draggableConnections">Connections using Drag and Drop</a></h4>
    -		To support drag and drop connections, you first need to set a few things up.  Every drag and drop connection needs at least a source
    -		Endpoint that the user can drag a connection from. Here's a simple example of how to create an Endpoint:
    -		<div class="code">
    -<pre>
    -var endpointOptions = { isSource:true };
    -var endpoint = jsPlumb.addEndpoint('elementId', endpointOptions);
    -</pre>		
    -</div>  	
    -	This Endpoint will act as a source for new Connections, and will use the jsPlumb defaults for its own appearance and that of any Connections that are drawn from it.							
    -	
    -		<h5>Tip: use the three-argument addEndpoint method for common data </h5>
    -		One thing that happens quite often is that you have an Endpoint whose appearance and behaviour is largely the
    -		same between usages on different elements, with just a few differences. 
    -		<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -	endpoint:"Rectangle",
    -	paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -	isSource:true,
    -	connectorStyle : { strokeStyle:"#666" },
    -	isTarget:true
    -};
    -</pre>		
    -</div>  		
    -		Notice there is no 'anchor' set.  Here we apply it to two elements, at a different location in each:
    -		<div class="code">
    -<pre>
    -jsPlumb.addEndpoint('element1', { anchor:"BottomCenter" }, exampleGreyEndpointOptions)); 
    -
    -jsPlumb.addEndpoint('element2', { anchor:"TopCenter" }, exampleGreyEndpointOptions));
    -</pre>		
    -</div>
    -
    -<p>
    -Now that you have a source Endpoint, you need to either create a target Endpoint on some element, or notify jsPlumb that you wish to make an
    -entire element a drop target.  Let's look at how to attach a target Endpoint first:
    -</p>
    -
    -		<div class="code">
    -<pre>var endpointOptions = { isTarget:true, endpoint:"Rectangle", paintStyle:{ fillStyle:"gray" } };
    -var endpoint = jsPlumb.addEndpoint("otherElementId", endpointOptions);
    -</pre>		
    -		</div>
    -		<p>
    -		This Endpoint, a gray rectangle, has declared that it can act as a drop target for Connections. 
    -</p>
    -<p>
    -Your other option for creating a drag and drop target is to make an entire element a drop target, using the <strong>jsPlumb.makeTarget</strong> method.
    -This method takes two arguments, the first of which specifies some element (or list of elements); the second specifies the Endpoint you wish to create on 
    -that element whenever a Connection is established on it.  In this example we will use the exact same target Endpoint we used before - the gray
    -rectangle - but we will tell jsPlumb that the element "aTargetDiv" will be the drop target:
    -</p>
    -		<div class="code">
    -<pre>var endpointOptions = { isTarget:true, endpoint:"Rectangle", paintStyle:{ fillStyle:"gray" } };
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -<p>
    -	The allowed values in 'endpointOptions' are identical for both the <strong>jsPlumb.addEndpoint</strong> and <strong>jsPlumb.makeTarget</strong> methods.
    -</p>		
    -		
    -		
    -<p>
    -There are many things you can set in an Endpoint options object; for a thorough list see the API documentation for Endpoint.  
    -</p>
    -<p>Here's an example of specifying that you want an Arrow overlay halfway along any Connection dragged from this Endpoint:</p>
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -	endpoint:"Rectangle",
    -	paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -	isSource:true,
    -	connectorStyle : { strokeStyle:"#666" },
    -	isTarget:true,
    -	connectorOverlays: [ [ "Arrow", { location:0.5 } ] ]
    -};
    -</pre>
    -</div>
    -This is an Endpoint that moves around the element it is attached to dependent on the location of other elements in the connections it is attached to
    -(a 'dynamic' anchor):
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -	endpoint:"Rectangle",
    -	paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -	isSource:true,
    -	connectorStyle : { strokeStyle:"#666" },
    -	isTarget:true,
    -	connectorOverlays: [ [ "Arrow", { location:0.5 } ] ]
    -	anchor:[ "TopCenter","RightMiddle","BottomCenter","LeftMiddle" ]
    -};
    -</pre>
    -</div>
    -
    -<h5><a id="dragOptions">Drag Options</a></h5>
    -	These are options that will be passed through to the supporting library's drag API.  jsPlumb passes everything you supply here through, inserting
    -	wrapping functions if necessary for the various lifecycle events that jsPlumb needs to know about.  So if, for example, you pass a function to be
    -	called when dragging starts, jsPlumb will wrap that function with a function that does what jsPlumb needs to do, then call yours.
    -	<p>
    -	At the time of writing, jsPlumb supports jQuery, MooTools and YUI3, and each of those libraries uses different terminology.  In addition, jQuery's API is
    -	more fully featured, providing easy support for setting the zIndex and opacity of elements being dragged, as well as the 'scope' for a drag/drop (allowing
    -	you to specify more than one type of drag/drop pair), and hover classes for when a draggable is on the move or
    -	over a droppable. If you're using jQuery you can of course just supply these values on the dragOptions; to make it easier, jsPlumb's MooTools and YUI3 adapters recognize 
    -	these options and add appropriate callbacks for you.  
    -	</p> 
    -	<p>
    -	Given that the options here are library-specific, and they are all well-documented, we're going to discuss just the three drag options
    -	that behave the same way in all (see below for hoverClass):
    -		<ul>
    -		<li>opacity - the opacity of an element that is being dragged.  Should be a fraction between 0 and 1 inclusive.</li>
    -		<li>zIndex - the zIndex of an element that is being dragged.</li>
    -		<li>scope - the scope of the draggable.  can only be dropped on a droppable with the same scope.  this is discussed below.</li>
    -		</ul> 
    -	</p>
    -	
    -	<p>For more information about drag options, take a look at the <a href="http://docs.jquery.com/UI/Draggable" target="_blank">jQuery</a>, <a href="http://mootools.net/docs/more/Drag/Drag.Move" target="_blank">MooTools</a>, or <a href="http://developer.yahoo.com/yui/3/dd/" target="_blank">YUI3</a> docs.	
    -	</p>
    -	<p><strong>NOTE: there is an issue in Chrome that affects the 'cursor' argument to drag options in jQuery. See these links:</strong>
    -	</p><p>
    -<a href="http://forum.jquery.com/topic/draggable-cursor-option-does-not-work" target="_blank">http://forum.jquery.com/topic/draggable-cursor-option-does-not-work</a><br/>
    -<a href="http://forum.jquery.com/topic/chrome-text-select-cursor-on-drag" target="_blank">http://forum.jquery.com/topic/chrome-text-select-cursor-on-drag</a>
    -</p>
    -You should put something like this at the top of your JS to avoid it (perhaps not something so drastic as a document-wide override):	
    -<div class="code">
    -<pre>document.onselectstart = function () { return false; };</pre>
    -</div>		
    -	
    -	<h5><a id="dropOptions">Drop Options</a></h5>
    -	Drop options are treated by jsPlumb in the same way as drag options - they are passed through to the underlying library.  MooTools does not have drop options like jQuery and YUI3 do; droppable functionality in MooTools 
    -	is actually implemented by the Drag.Move class - the one used to initialise a draggable.  But when you setup an Endpoint in jsPlumb you should ignore
    -	that fact, and treat droppables like you would in jQuery or YUI3.  jsPlumb wires everything up for you under the hood.
    -	<p>There are three jQuery droppable options that jsPlumb treats as shortcuts in MooTools and YUI3, for the sake of consistency:</p>
    -		<ul>
    -			<li>hoverClass - the CSS class to attach to the droppable when a draggable is hovering over it.</li>
    -			<li>activeClass - the CSS class to attach to the droppable when a draggable is, um, being dragged.</li>
    -			<li>scope - the scope of the draggable.  The draggable can only be dropped on a droppable with the same scope.  This is discussed below.</li>
    -		</ul>   
    -	<p>For more information about drop options when using jQuery, see <a href="http://docs.jquery.com/UI/Droppable" target="_blank">here</a>.</p>
    -		
    -	<h5><a id="dragAndDropScope">Drag and Drop Scope</a></h5>
    -		jsPlumb borrowed the concept of 'scope' from jQuery's drag/drop implementation: the notion of
    -		which draggables can be dropped on which droppables.  In jsPlumb you can provide a 'scope' entry when 
    -		creating an Endpoint.  Here's the example grey Endpoint example with 'scope' added:
    -		
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -	endpoint:"Rectangle",
    -	paintStyle:{ width:25, height:21, fillStyle:"#666" },
    -	isSource:true,
    -	connectionStyle : { strokeStyle:"#666" },
    -	isTarget:true,
    -	scope:"exampleGreyConnection"
    -};</pre>		
    -</div>  		
    -  										
    -	If you do not provide a 'scope' entry, jsPlumb uses a default scope.  Its value is accessible through this method:
    -	<div class="code">
    -<pre>
    -jsPlumb.getDefaultScope();
    -</pre>	
    -	</div>				
    -	If you want to change it for some reason you can do so with this method:
    -	<div class="code">
    -<pre>
    -jsPlumb.setDefaultScope("mySpecialDefaultScope");
    -</pre>	
    -	</div>			
    -	You can also, should you want to, provide the scope value separately on the drag/drop options, like this:
    -	<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -	endpoint:"Rectangle",
    -	painStyle:{ width:25, height:21, fillStyle:'#666' },
    -	isSource:true,
    -	connectionStyle : { strokeStyle:"#666" },
    -	isTarget:true,
    -	dragOptions:{ scope:"dragScope" },
    -	dropOptions:{ scope:"dropScope" }
    -};</pre>		
    -</div>  
    -			
    -	</div>	
    -
    -		<div class="section">
    -			<h3><a id="paintstyles">Paint Styles</a></h3>
    -			Defining the appearance of Connectors, Endpoints (and Overlays, but this is deprecated in favour of using CSS classes) is achieved through a 'paintStyle' (or a quite similar name...keep reading)
    -			object passed as a parameter to one of jsPlumb.connect, jsPlumb.addEndpoint or jsPlumb.makeTarget. Depending on the method you are
    -			calling, the parameter names vary.
    -			<h5>Connector Paint Styles</h5>
    -			These are specified in a <em>paintStyle</em> parameter on a call to <strong>jsPlumb.connect</strong>:
    -			<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"el1",
    -	target:"el2",
    -	paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			or in the <em>connectorPaintStyle</em> parameter on a call to <strong>jsPlumb.addEndpoint</strong>:
    -			<div class="code">
    -<pre>jsPlumb.addEndpoint({
    -	source:"el1",
    -	target:"el2",
    -	paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -	connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div> 
    -
    -Notice the <em>paintStyle</em> parameter in that example: it is the paint style for the Endpoint, which we'll discuss below.
    -
    -			<h5>Endpoint Paint Styles</h5>
    -			These are specified in a <em>paintStyle</em> parameter on a call to <strong>jsPlumb.addEndpoint</strong>. This is the example from
    -			just above:
    -			<div class="code">
    -<pre>jsPlumb.addEndpoint("el1", {
    -	paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -	connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div> 
    -			...or as the <em>endpointStyle</em> parameter to a jsPlumb.connect call:		
    -			<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"el1",
    -	target:"el2",
    -	endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -	paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			... or as an entry in the <em>endpointStyles</em> array passed to a jsPlumb.connect call:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"el1",
    -	target:"el2",
    -	endpointStyles:[ 
    -		{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -		{ fillStyle:"green" }
    -	],
    -	paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			or as the <em>paintStyle</em> entry in an <em>endpoint</em> parameter passed to a jsPlumb.makeTarget call:
    -					<div class="code">
    -<pre>jsPlumb.makeTarget("el1", {
    -	endpoint: {
    -		paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 }
    -	}
    -});
    -</pre>			
    -			</div>			
    -	In this example we've made "el1" into a drop target, and defined a paint style for the Endpoint jsPlumb will create when a Connection is
    -	established.
    -	
    -	<h5>Overlay Paint Styles</h5>
    -	The preferred way to set paint styles for Overlays is to use the <em>cssClass</em> parameter in the constructor arguments of an Overlay definition.
    -	
    -	<h4>Paint Style Parameters</h4>
    -	This is the full list of parameters you can set in a paintStyle object, but note that <strong>fillStyle</strong> is ignored by Connectors,
    -	and <strong>strokeStyle</strong> is ignored by Endpoints.  Also, if you create a Connection using jsPlumb.connect and do not specify any
    -	Endpoint styles, the Endpoints will derive their fillStyle from the Connector's strokeStyle.
    -	<p>
    -		fillStyle, strokeStyle and outlineColor can be specified using any valid CSS3 syntax.
    -	</p>
    -	<ul>
    -		<li><strong>fillStyle</strong> - color for an Endpoint, eg. rgba(100,100,100,50), "blue", "#456", "#993355", rgb(34, 56, 78).</li>
    -		<li><strong>strokeStyle</strong> - color for a Connector. see fillStyle examples.</li>
    -		<li><strong>lineWidth</strong> - width of a Connector's line. An integer.</li>
    -		<li><strong>outlineWidth</strong> - width of the outline for an Endpoint or Connector. An integer.</li>
    -		<li><strong>outlineColor</strong> - color of the outline for an Endpoint or Connector. see fillStyle examples.</li>
    -		<li><strong>dashstyle</strong> - VML and SVG only. This comes from VML, and allows you to create dashed or dotted lines.  It has a 
    -			better syntax than the equivalent attribute in SVG (<em>stroke-dasharray</em>, discussed below), so jsPlumb supports this for both renderers. 
    -			The <em>dashstyle</em> attribute is specified as an array of strokes and spaces, where each value is some multiple of <em>the width of 
    -			the Connector</em>, and that's where it's better than SVG, which uses pixels.
    -			<p>
    -			<a href="http://www.w3.org/TR/NOTE-VML" target="_blank">The VML spec</a> is a good place to find valid values for dashstyle. Note that
    -			jsPlumb does not support the string values for this attribute ("solid", "dashdot", etc).
    -			</p>
    -			<p>
    -			In SVG render mode, jsPlumb uses the <em>lineWidth</em> parameter in conjunction with the values in a <em>dashstyle</em> attribute
    -			to create an appropriate value for <em>stroke-dasharray</em>.
    -			</p>
    -		</li>
    -		<li><strong>stroke-dasharray</strong> - SVG only. This is the SVG equivalent of <em>dashstyle</em>.  
    -		<a href="http://www.w3.org/TR/SVG/painting.html" target="_blank">The SVG spec</a> discusses valid values for this parameter.  But be aware
    -		that jsPlumb does not convert this into a valid <em>dashstyle</em> attribute when using the VML renderer. Better to use <em>dashstyle</em>.
    -		</li>
    -		<li><strong>stroke-dashoffset</strong> - SVG only.  This is used in SVG to specify how far into the dash pattern to start painting.  For more information,
    -		see <a href="http://www.w3.org/TR/SVG/painting.html" target="_blank">the SVG spec.</a></li>
    -		<li><strong>joinstyle</strong> - VML and SVG only.  As with <em>dashstyle</em>, this is a VML attribute that jsPlumb supports for both VML
    -		and SVG - jsPlumb turns this into a <em>stroke-linejoin</em> attribute when rendering with SVG.  This attribute specifies how you want individual
    -		segments of connectors to be joined; the VML and SVG specs both have examples of this, of which many are the same between the two, which is
    -		why jsPlumb will automatically convert this attribute into the SVG equivalent.
    -		</li>
    -		<li><strong>stroke-linejoin</strong> - SVG only.  This is the equivalent of VML's <em>joinstyle</em> attribute, but as with <em>stroke-dasharray</em>,
    -		jsPlumb does not convert this into something approriate for VML.  So, using <em>joinstyle</em> will enable you to support more browsers with
    -		less effort.		
    -		</li>
    -	</ul>
    -	
    -	<h3><a id="hoverpaintstyles">Hover Paint Styles</a></h3>
    -	 		
    -	 		Connectors and Endpoints both support the concept of a "hover" paint style - a paint style to use when the mouse is hovering over
    -	 		the component.  These are specified in the exact same format as paint styles discussed above, but hover paint styles also inherit
    -	 		any values from the main paint style.  This is because you will typically want to just change the color, or perhaps outline color, of
    -	 		a Connector or Endpoint when the mouse is hovering, but leave everything else the same.  So having hover paint styles inherit their
    -	 		values precludes you from having to define things in more than one place.
    -	 		<p>
    -	 			The naming convention adopted for hover paint styles is pretty much to insert the word 'hover' into the corresponding main paint
    -	 			style parameters.  Here are a couple of examples:
    -	 		</p> 
    -	 					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"el1",
    -	target:"el2",
    -	paintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -	hoverPaintStyle:{ strokeStyle:"red" },
    -	endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -	endpointHoverStyle:{ fillStyle:"red" }
    -});
    -</pre>			
    -			</div>
    -			In this example we specified a hover style for both the Connector, and each of its Endpoints.  Here's the same thing, but using the plural
    -			version, to specify a different hover style for each Endpoint:
    -			
    -			
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"el1",
    -	target:"el2",
    -	paintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -	hoverPaintStyle:{ strokeStyle:"red" },
    -	endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -	endpointHoverStyles:[ { fillStyle:"red" }, { fillStyle:"yellow" } ]
    -});
    -</pre>			
    -			</div>
    -	 		Calls to <strong>jsPlumb.addEndpoint</strong> and <strong>jsPlumb.makeTarget</strong> can also specify various hover paint styles:
    -	 								<div class="code">
    -<pre>jsPlumb.addEndpoint("el1", {
    -	paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -	hoverPaintStyle:{ fillStyle:"red" },
    -	connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -	connectorHoverPaintStyle:{ strokeStyle:"red", outlineColor:"yellow", outlineWidth:1 }
    -});
    -</pre>			
    -			</div> 
    -	 		
    -	 		Here we specified a hover paint style for both the Endpoint we are adding, and any Connections that are made from the Endpoint.
    -	 		<p><strong>jsPlumb.makeTarget</strong> also supports hover paint styles - here's the example from before, with one applied:</p>
    -	 		
    -	 							<div class="code">
    -<pre>jsPlumb.makeTarget("el1", {
    -	endpoint: {
    -		paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -		hoverPaintStyle:{ fillStyle:"red" }
    -	}
    -});
    -</pre>			
    -			</div>	
    -	
    -		Note that <strong>jsPlumb.makeTarget</strong> does not support Connector parameters. It is for creating targets only; Connector parameters 
    -		will be set by the source Endpoint in any Connections that are made to the element that you turned into a target by using this method.
    -	 		
    -	 		
    -			<h4><a id="gradients">Gradients</a></h4>
    -			The Canvas and SVG renderers both support gradients. The VML renderer does not.  jsPlumb uses its own syntax to define
    -			gradients, to abstract out the differences between the syntax required by canvas and that required by SVG.  
    -			<p>There are two types of gradients available - a 'linear' gradient, which consists of colored lines all
    -			going in one direction, and a 'radial' gradient, which consists of colored circles emanating from one circle to another.
    -			Because of their basic shape, jsPlumb supports only <em>linear</em> gradients for
    -			Connectors.  But for Endpoints, jsPlumb supports both <em>linear</em> and <em>radial</em> gradients.</p>
    -			
    -				<h5>Connector gradients</h5>
    -				To specify a linear gradient to use in a Connector, you must add a <em>gradient</em> object to your
    -				Connector's <em>paintStyle</em>, for instance:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : "window2",
    -	target : "window3",
    -	paintStyle:{
    -		gradient:{
    -			stops:[[0,"green"], [1,"red"]]
    -		},
    -		lineWidth:15
    -	}
    -});
    -</pre>
    -</div>
    -Here we have connected window2 to window3 with a 15 pixel wide connector that has a gradient from green to red.</p><p> Notice the <strong>gradient</strong> object and the
    -<strong>stops</strong> list inside it - the gradient consists of an arbitrary number of these "color stops".  Each color stop is comprised
    -of two values - [position, color].  Position must be a decimal value between 0 and 1 (inclusive), and indicates where the color
    -stop is situated as a fraction of the length of the entire gradient.  Valid values for
    -the colors in the <strong>stops</strong> list are the same as those that are valid for <strong>strokeStyle</strong> when describing a color.
    -			</p>
    -			As mentioned, the <strong>stops</strong> list can hold an arbitrary number of entries.  Here's an example of a gradient that goes from red to blue to green, and back again through blue to red:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle : {
    -		gradient:{
    -			stops:[[0,'red'], [0.33,'blue'], [0.66,'green'], [0.33,'blue'], [1,'red']]
    -		},
    -		lineWidth : 15
    -	}
    -});
    -</pre>
    -</div>
    -<strong>Note:</strong> when using the VML renderer, jsPlumb will simply ignore the gradient directive
    -so it is best to ensure you also supply a <em>strokeStyle</em> in your paintStyle object, to give jsPlumb something to fall back on.  If you do not supply
    -a <em>strokeStyle</em> your Connectors will be painted black.  The previous example might look like this, for instance:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle:{
    -		strokeStyle:'red',
    -		gradient:{
    -			stops:[[0,'red'], [0.33,'blue'], [0.66,'green'], [0.33,'blue'], [1,'red']]
    -		},
    -		lineWidth:15
    -	}
    -});
    -</pre>
    -</div>
    -Notice the <strong>strokeStyle:'red'</strong> directive at the beginning of the parameter list in <strong>paintStyle</strong>.
    -			<h5>Endpoint gradients</h5>
    -			Endpoint gradients are specified using the same syntax as Connector gradients.  You put the gradient specifier either in the
    -			<strong>endpoint</strong> member, or if you are specifying different Endpoints for each end of the Connector, in one or both of
    -			the values in the <strong>endpoints</strong> array.  Also, this information applies to the case that you are creating standalone
    -			Endpoints that you will be configuring for drag and drop creation of new Connections. 
    -			<p>
    -			This is an example of an Endpoint gradient that is different for each Endpoint in the Connector.  This comes from the main demo; it is
    -			the Connector joining Window 2 to Window 3:
    -			</p>
    -<div class="code">
    -<pre>var w23Stroke = 'rgb(189,11,11)';
    -jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle:{
    -		lineWidth:8,
    -		strokeStyle:w23Stroke
    -	},
    - 	anchors:[ [0.3,1,0,1], "TopCenter" ],
    - 	endpoint:"Rectangle",
    - 	endpointStyles:[
    - 		{ gradient : {stops:[[0, w23Stroke], [1, '#558822']] } },
    -    	{ gradient : {stops:[[0, w23Stroke], [1, '#882255']] } }
    -    ]
    -});
    -</pre>
    -</div>
    -The first entry in the gradient will be the one that is on the Connector end of the Endpoint.  You can of course have as many color stops as
    -you want in this gradient, just like with Connector gradients.
    -	<h5>Applying the gradient in Endpoints</h5>
    -	Only the Dot and Rectangle endpoints honour the presence of a gradient (and, remember, not in VML). The Image endpoint of course ignores a gradient
    -	as it does no painting of its own.
    -	<p>The type of gradient you will see depends on the Endpoint type:</p>
    -		<ul>
    -			<li><strong>Dot</strong> - renders a radial endpoint, with color stop 0 on the outside, progressing inwards as we move through color stops.
    -			<p>Radial gradients actually require more data than linear gradients - in a linear gradient we just move from one point to another, whereas
    -			in a radial gradient we move from one <em>circle</em> to another.  By default, jsPlumb will render a radial gradient using a source
    -			circle of the same radius as the Endpoint itself, and a target circle of 1/3 of the radius of the Endpoint (both circles share the
    -			same center as the Endpoint itself). This circle will be offset by radius/2 in each direction.</p>
    -			<p>You can supply your own values for these inside the gradient descriptor:</p>
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect({
    -	source : 'window3',
    -	target : 'window4',
    -    paintStyle:{
    -    	lineWidth:10,
    -    	strokeStyle:w34Stroke
    -    },
    -    anchors:[ "RightMiddle", "LeftMiddle" ],
    -    endpointStyle:{
    -    	gradient : {
    -    		stops:[ [0, w34Stroke], [1, w34HlStroke] ],
    -    		offset:37.5,
    -    		innerRadius:40
    -    	},
    -    	radius:55
    -    }
    - });
    -</pre>
    -</div>
    -Here we have instructed jsPlumb to make the gradient's inner radius 10px instead of the default 25/3 = 8 ish pixels, and the offset in each direction
    -will be 5px, instead of the default radius / 2 = 12.5 pixels.
    -<p>It is also possible to specify the offset and inner radius as percentages - enter the values as strings with a '%' symbol on the end:</p>
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect({
    -	source : 'window3', 
    -	target : 'window4',
    -    paintStyle:{
    -    	lineWidth:10,
    -    	strokeStyle:w34Stroke
    -    },
    -	anchors:[ "RightMiddle", "LeftMiddle" ],
    -	endpointStyle:{
    -		gradient : {
    -			stops:[ [0, w34Stroke], [1, w34HlStroke] ],
    -			offset:'68%',
    -			innerRadius:'73%'
    -		},
    -		radius:25
    -	}
    -});
    -</pre>
    -</div>
    -This will give roughly the same output as the example above (the percentages are not entirely exact).<br/><br/>
    -			</li>
    -			<li><strong>Rectangle</strong> - renders a linear endpoint, with color stop 0 closest to the end of the Connector</li>
    -		</ul>
    -		</div>
    -
    -		<div class="section">
    -			<h3><a id="cssclasses">CSS Class Reference</a></h3>
    -			jsPlumb attaches classes to each of the UI components it creates.
    -			<p>These class names are exposed on the jsPlumb object and can be overridden if you need to do so (see the third column in the table) </p>
    -			<table width="70%" class="table">
    -				<tr><th>Component</th><th>CSS Class</th><th>jsPlumb Member</th></tr>
    -				<tr><td>Connector</td><td>_jsPlumb_connector</td><td>connectorClass</td></tr>
    -				<tr><td>Endpoint</td><td>_jsPlumb_endpoint</td><td>endpointClass</td></tr>
    -				<tr><td>Overlay</td><td>_jsPlumb_overlay</td><td>overlayClass</td></tr>
    -			</table>
    -			<p>You would typically use these to establish appropriate z-indices for your UI.</p>			
    -		</div>
    -				
    -		<div class="section">
    -			<h3><a id="animation">Animation</a></h3>
    -			jsPlumb offers an 'animate' function, which wraps the underlying animation engine for whichever library you happen to be using and
    -			inserts a callback for jsPlumb to repaint whatever it needs to at each step.  You could of course do this yourself; it's a convenience
    -			method really.
    -			<p>The method signature is:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.animate : function(el, properties, options) 
    -</pre>
    -</div>			
    -The arguments are as follows:
    -	<ul>
    -		<li><strong>el</strong> - element id, or element object from the library you're using.</li>
    -		<li><strong>properties</strong> - properties for the animation, such as duration etc.</li>
    -		<li><strong>options</strong> - options for the animation, such as callbacks etc.</li>
    -	</ul>
    -	
    -		</div>
    -		
    -			<div class="section">
    -			<h3><a id="connectionInfo">Retrieving Connection Information</a></h3>
    -			jsPlumb offers one fairly versatile method - <strong>getConnections</strong> - to retrieve information about the
    -			currently managed connections.  
    -			<p>Before you use this method you should understand jsPlumb's notion of 'scope' - documentation
    -			is <a href="#dragAndDropScope">here</a> </p>
    -			
    -			<h4>Retrieving connections for a single scope</h4>
    -			<p>To do this, you call getConnections with either no arguments, in which case jsPlumb uses the default scope, or with a string
    -			specifying one scope</p>
    -			<div class="code">
    -<pre>var connectionList = jsPlumb.getConnections();     // you get a list of Connection objects that are in the default scope.
    -</pre>			
    -			</div>
    -			Compare this with:
    -			<div class="code">
    -<pre>var connectionList = jsPlumb.getConnections("myScope");     // you get a list of Connection objects that are in "myScope".
    -</pre>			
    -			</div>
    -			<h4>More advanced filtering</h4>
    -			getConnections optionally takes a JS object specifying filter parameters, of which there are three:
    -			<ul>
    -				<li>scope - the scope(s) of the connection type(s) you wish to retrieve</li>
    -				<li>source - limits the returned connections to those that have this source id</li>
    -				<li>target - limits the returned connections to those that have this target id</li>
    -			</ul>			
    -			Each of these three parameters may be supplied as a string, or a list of strings - see the examples below.
    -			<p>
    -			The return value of a call to getConnection using a JS object as parameter varies on how many scopes you defined.  If you defined only
    -			a single scope then jsPlumb returns you a list of Connections in that scope.  Otherwise the return value is a dictionary whose keys are 
    -			scope names, and whose values are lists of Connections. For example, the following call:
    -			</p>
    -			<div class="code">
    -<pre>
    -	jsPlumb.getConnections({scope:["someScope", "someCustomScope"]});
    -</pre>
    -</div>
    -would result in this output:			
    -<div class="code">
    -<pre>
    -{
    -	"someScope" : [ 1..n Connections ],
    -	"someCustomScope": [ 1..m Connections ]
    -}
    -</pre>
    -</div>			
    -		The following examples show the various ways you can get connection information:
    -	<ul>
    -		<li>Get all connections:
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getAllConnections();  
    -</pre>
    -</div>						
    -		</li>
    -<li>Get all connections for the default scope only (return value is a list):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections();  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given scope (return value is a list):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:"myTestScope"});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given scopes (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:["myTestScope", "yourTestScope"]});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given source (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:"mySourceElement"});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given sources (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:["mySourceElement", "yourSourceElement"]});  
    -</pre>
    -</div>						
    -		</li>		
    -<li>Get all connections for the given target (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({target:"myTargetElement"});  
    -</pre>
    -</div>						
    -		</li>				
    -<li>Get all connections for the given source and targets (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:"mySourceElement", target:["target1", "target2"]});  
    -</pre>
    -</div>						
    -		</li>				
    -		
    -<li>Get all connections for the given scope, with the given source and target (return value is a list of connections):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:'myScope", source:"mySourceElement", target:"myTargetElement"});  
    -</pre>
    -</div>						
    -		</li>				
    -		
    -	</ul>	
    -
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="events">Events</a></h3>
    -			jsPlumb supports binding to several different events, both on Connections, Endpoints and Overlays, and also on the jsPlumb
    -			object itself.  To bind events on any of these objects you use the <strong>jsPlumb.bind(object, event, callback)</strong> method, 
    -			<strong>except</strong> in the case of Overlays, in which you provide the event listeners in the Overlay's constructor arguments (example below). 
    -			
    -			<h4><a id="jsPlumbEvents">jsPlumb Events</a></h4>
    -			<ul>
    -				<li class="bullet"><strong>jsPlumbConnection</strong> - notification a Connection was established.  The callback is passed the new Connection as argument.</li>
    -				<li class="bullet"><strong>jsPlumbConnectionDetached</strong> - notification a Connection was detached.  The callback is passed the detached Connection as argument.</li>
    -				<li class="bullet"><strong>click</strong> - notification a Connection was clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification a Connection was double-clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>endpointClick</strong> - notification an Endpoint was clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>endpointDblClick</strong> - notification an Endpoint was double-clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -			</ul>
    -			<h4><a id="connectionEvents">Connection Events</a></h4>			
    -			<ul>
    -				<li class="bullet"><strong>click</strong> - notification a Connection was clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification a Connection was double-clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>mouseenter</strong> - notification the mouse entered the Connection's path.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>mouseexit</strong> - notification the mouse exited the Connection's.  The callback is passed the Connection and the original mouse event.</li>
    -			</ul>
    -			<h4><a id="endpointEvents">Endpoint Events</a></h4>			
    -			<ul>
    -				<li class="bullet"><strong>click</strong> - notification an Endpoint was clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification an Endpoint was double-clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>mouseenter</strong> - notification the mouse entered the Endpoint.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>mouseexit</strong> - notification the mouse exited the Endpoint.  The callback is passed the Endpoint and the original mouse event.</li>
    -			</ul>
    -			<h4><a id="overlayEvents">Overlay Events</a></h4>
    -			Registering event listeners on an Overlay is a slightly different procedure - you provide them as arguments to the Overlay's 
    -			constructor.  This is because you never actually act on an 'Overlay' object.  Here's how to register a click listener on an Overlay:
    -			<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"el1",
    -	target:"el2",
    -	overlays:[
    -		[ "Label", {
    -			events:{
    -				click:function(labelOverlay, originalEvent) { alert("you clicked on the label overlay for this connection :" + labelOverlay.connection) }
    -			}
    -		}] 	
    -	]
    -});
    -</pre>			
    -			</div>		
    -		</div>
    -
    -				<div class="section">
    -			<a id="examples"><h3>jsPlumb.connect Examples</h3></a>
    -This section provides examples of how to use the programmatic API to establish Connections. 
    -			<p>
    -			The basic syntax of a call is that you execute 'connect', providing a source and a target, and optionally a paintStyle and preferences for where you
    -			want the plumbing to be anchored on each element, as well as the type of connector to use. 
    -			</p>
    -			<ul>
    -				<li>Connect window1 to window2 with the default settings:
    -					<div class="code">
    -						<pre>jsPlumb.connect({source:"window1", target:"window2"});</pre>
    -					</div>
    -This connects 'window1' to 'window2' using the jsPlumb defaults - a red Bezier curve Connector and black Dot Endpoints.
    -				</li>
    -				<li>Connect window1 to window2 with a 15 pixel wide yellow Connector, and a slightly brighter endpoint (remember the default Endpoint is a Dot):
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -					</div>
    -				</li>
    -				
    -				<li>Connect window1 to window2 with a 15 pixel wide yellow Connector, and a slightly brighter endpoint:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -					</div>
    -				</li>				
    -				<li>Connect window3 to 'window4' with a 10 pixel wide, semi opaque blue Connector, anchored to the left middle of window3, and the right middle of window4, with a Rectangle endpoint of width 10 and height 8:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window3',
    -	target:'window4',
    -	paintStyle:{ lineWidth:10, strokeStyle:'rgba(0, 0, 200, 0.5)' },
    -	anchors:["RightMiddle", "LeftMiddle"],
    -	endpoint:[ "Rectangle", { width:10, height:8 } ]
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window2 to window3 with a default Connector from the top center of window2 to the bottom center of window3, and rectangular endpoints:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window2',
    -	target:'window3',
    -	paintStyle:{lineWidth:8, strokeStyle:'rgb(189,11,11)'},
    -	anchors:["BottomCenter", "TopCenter"],
    -	endpoint:"Rectangle"
    -});</pre>
    -					</div>
    -				</li>
    -
    -
    -				<li>Connect window1 to window2 with a 15 px wide yellow Bezier. endpoints are a slightly lighter shade of yellow.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	anchors:["BottomCenter", [0.75,0,0,-1]],
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -</div></li>
    -<li>Connect window3 to window4 with a 10px wide blue-ish half transparent Bezier. put endpoints underneath the element they attach to.
    -the endpoints have a radial gradient. both ways of specifying gradient positioning are shown here.
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect( {
    -	source:'window3',
    -	target:'window4',
    -		 paintStyle:{lineWidth:10, strokeStyle:w34Stroke},
    -		 anchors:["RightMiddle", "LeftMiddle"],
    -		 endpointStyle:{ gradient : {stops:[[0, w34Stroke], [1, w34HlStroke]], offset:17.5, innerRadius:15 }, radius:35},
    -		 //endpointStyle:{ gradient : {stops:[[0, w34Stroke], [1, w34HlStroke]], offset:'78%', innerRadius:'73%'}, radius:35 },
    -		 endpointsOnTop:false
    -	}
    -);</pre>
    -</div></li>
    -<li>Connect window2 to window3 with an 8px red Bezier and default rectangular endpoints.  see also how the first anchor is
    -specified here - this is how you create anchors in locations jsPlumb does not offer shortcuts for.
    -the endpoints in this example have linear gradients applied.
    -<div class="code">
    -<pre>var w23Stroke = 'rgb(189,11,11)';
    -jsPlumb.connect({
    -	source:'window2',
    -	target:'window3',
    -	paintStyle:{lineWidth:8,strokeStyle:w23Stroke},
    -	anchors:[[0.3,1,0,1], "TopCenter"],
    -	endpoint:"Rectangle",
    -	endpointStyles:[{ gradient : {stops:[[0, w23Stroke], [1, '#558822']] }},
    -       				{ gradient : {stops:[[0, w23Stroke], [1, '#882255']] }}]
    -});</pre>
    -</div></li>
    -
    -<li>Connect window5 to window6 from center to center, 5px wide line that is green and half transparent. the endpoints are
    -125px in radius and spill out from underneath their elements.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window5',
    -	target:'window6',
    -	anchors:["Center", "Center"],
    -	paintStyle:{lineWidth:5,strokeStyle:'rgba(0,255,0,0.5)'},
    -	endpointStyle:{radius:125}
    -});</pre>
    -</div></li>
    -
    -<li>Connect window4 to window5 from bottom right to top left, with a 7px straight line purple connector, and an image as the endpoint,
    -placed on top of the element it is connected to.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window4",
    -	target:"window5",
    -	anchors:[ "BottomRight","TopLeft" ],
    -	paintStyle:{ lineWidth:7, strokeStyle:"rgb(131,8,135)" },
    -	endpoint:[ "Image", { src:"http://morrisonpitt.com/jsPlumb/img/endpointTest1.png" } ],
    -	connector:"Straight"
    -});</pre>
    -</div></li>
    -
    -
    -				<li>Connect window5 to window6 between their center points with a semi-opaque connector, and 125px endpoints:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window5",
    -	target:"window6",
    -	anchors:[ "Center", "Center" ],
    -	paintStyle:{ lineWidth:5, strokeStyle:"rgba(0,255,0,0.5)" },
    -	endpointStyle:{ radius:125 }
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window7 to window8 with a 10 pixel wide blue Connector, anchored on the top left of window7 and the bottom right of window8:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window7",
    -	target:"window8",
    -	paintStyle:{ lineWidth:10, strokeStyle:"blue" },
    -	anchors:[ "TopLeft", "BottomRight" ]
    -});
    -</pre>
    -					</div>
    -				</li>
    -				<li>Connect the bottom right corner of window4 to the top left corner of window5, with rectangular endpoints of size 40x40 and a hover color of light blue:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window4",
    -	target:"window5",
    -	anchors:["BottomRight","TopLeft"],
    -	paintStyle:{lineWidth:7,strokeStyle:'rgb(131,8,135)'},
    -	hoverPaintStyle:{ strokeStyle:"rgb(0, 0, 135)" },
    -	endpointStyle:{ width:40, height:40 },
    -	endpoint:"Rectangle",
    -	connector:"Straight"
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window1 to window2 with the default paint settings but provide some drag options (which are passed through to the underlying library's draggable call):
    -					<div class="code">
    -						<pre>jsPlumb.connect({source:'window1', target:'window2', dragOptions:{cursor:'crosshair'}});</pre>
    -					</div>
    -				</li>
    -</div>
    -		
    -		<div class="section">
    -			<a id="dragAndDropExamples"><h3>Draggable Connections Examples</h3></a>
    -			This is a list of examples of how to use jsPlumb to create Connections using drag and drop.
    -			<p>
    -			The basic procedure is:
    -			<ol>
    -				<li>1a. Create Endpoints and register them on elements in your UI</li>
    -				<li>1b. alternatively, create a source endpoint and then make some element a drop target </li>
    -				<li>2. Drag and Drop</li>
    -			</ol>
    -			That's all there is to it.  Of course there are plenty of options you can set when doing this...it will be easier
    -			to show you some examples:
    -			</p>
    -			<ul>
    -				<li>Define an Endpoint with default appearance, that is both a source and target of new Connections:
    -<div class="code">
    -<pre>var endpointOptions = { isSource:true, isTarget:true }; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Register that Endpoint on window3, specifying that it should be located in the top center of the element:
    -<div class="code">
    -<pre>var window3Endpoint = jsPlumb.addEndpoint('window3', { anchor:"TopCenter" }, endpointOptions );  
    -</pre>
    -</div>
    -Notice here the usage of the three-argument addEndpoint  - we can reuse 'endpointOptions' with a different Anchor for another element.  This is a useful
    -practice to get into.<br/><br/>
    -				</li>
    -				
    -				<li>Now register that Endpoint on window4, specifying that it should be located in the bottom center of the element:
    -<div class="code">
    -<pre>var window4Endpoint = jsPlumb.addEndpoint('window4', { anchor:"BottomCenter" }, endpointOptions );  
    -</pre>
    -</div>
    -Now we have two Endpoints, both of which support drag and drop of new Connections.  We can use these to make a programmatic Connection, too, though:<br/><br/>
    -				</li>				
    -				
    -				<li>Connect window3 to window4 with a 25px wide yellow Bezier that has a 'curviness' of 175:
    -<div class="code">
    -<pre>jsPlumb.connect({ 
    -	source:window3Endpoint,
    -	target:window4Endpoint,
    -	connector: [ "Bezier", 175 ],
    -	paintStyle:{ lineWidth:25, strokeStyle:'yellow' }
    -});  
    -</pre>
    -</div>
    -				</li>
    -				
    -				<li>Define an Endpoint that creates Connections that are 20px wide straight lines, that is both a source and target of new Connections,
    -				and that has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Define an Endpoint that will be anchored to "TopCenter".  It creates Connections that are 20px wide straight lines, that is both a source and target of new Connections,
    -				and that has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchor:"TopCenter", 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Define an Endpoint that will create a dynamic anchor which can be positioned at "TopCenter" or "BottomCenter".  It creates Connections that are 20px wide straight lines, it is both a source and target of new Connections,
    -				and it has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchor:[ "TopCenter", "BottomCenter" ], 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Exactly the same as before, but shows how you can use "anchors" instead of "anchor", if that makes you feel happier:
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchors:[ "TopCenter", "BottomCenter" ], 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>								
    -				
    -<li>Define an Endpoint that is a 30px blue dot, creates Connections that are 20px wide straight lines, is both a source and target of new Connections,
    -				has a 'scope' of 'blueline', and has an event handler that pops up an alert (note: the event handler name means this example is jQuery - MooTools
    -				and YUI3 use different event handler names):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{fillStyle:'blue'},
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ drop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -			<li>Same example as before, but this is for MooTools, and the Endpoint can support up to 5 connections (the default is 1):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{ fillStyle:'blue' },
    -	maxConnections:5,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ onDrop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Same example again, but maxConnections being set to -1 means that the Endpoint has no maximum limit of Connections:
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", {radius:30} ],
    -	style:{ fillStyle:'blue' },
    -	maxConnections:-1,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ onDrop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Same example again, but for YUI3.  Note the drop callback is "drop:hit":
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{fillStyle:'blue'},
    -	maxConnections:-1,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ "drop:hit":function(e, ui) { alert('drop!'); } }	 	
    -}; 
    -</pre>
    -</div>				 				
    -				</li>				
    -				
    -	<li>Assign a UUID to the endpoint options created above, and add as Endpoints to "window1" and "window2":
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("window1", { uuid:"abcdefg" }, endpointOptions );
    -jsPlumb.addEndpoint("window2", { uuid:"hijklmn" }, endpointOptions );
    -</pre>
    -</div>	
    -	</li>
    -	
    -	<li>Connect the two Endpoints we just registered on "window1" and "window2":
    -<div class="code">
    -<pre>
    -jsPlumb.connect({uuids:["abcdefg", "hijklmn"]});
    -</pre>
    -</div>	
    -	</li>
    -	
    -	<li>Create a source Endpoint, register it on some element, then make some other element a Connection target:
    -<div class="code">
    -<pre>
    -var sourceEndpoint = { isSource:true, endpoint:[ "Dot", { radius:50 } ] };
    -var targetEndpoint = { endpoint:[ "Rectangle", { width:10, height:10 } ] };
    -jsPlumb.addEndpoint( "window1", sourceEndpoint );
    -jsPlumb.makeTarget( "window2", targetEndpoint );
    -</pre>
    -</div>	
    -Notice that the endpoint definition we use on the target window does not include the "isTarget:true" directive.  jsPlumb ignores that flag when
    -creating a connection using an element as the target; but if you then tried to drag another connection to the Endpoint that was created, you would
    -not be able to.  To permit that, you would set 'isTarget:true' on the targetEndpoint options defined above.
    -	
    -	</li>
    -				
    -			</ul>
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="utilityFunctions">Utility Functions</a></h3>
    -			<ul>
    -				<li>Detach window5 from all connections
    -					<div class="code">
    -						<pre>jsPlumb.detachAll("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Hide all window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.hide("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Hide all window5's connections endpoints
    -					<div class="code">
    -						<pre>jsPlumb.hide("window5", true);</pre>
    -					</div>
    -				</li>
    -				<li>Show all window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.show("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Show all window5's connections and endpoints.  Note that in the case that you call jsPlumb.show with two arguments, jsPlumb
    -				will also not make a connection visible if it determines that the other endpoint in the connection is not visible.
    -					<div class="code">
    -						<pre>jsPlumb.show("window5", hide);</pre>
    -					</div>
    -				</li>				
    -				<li>Toggle the visibility of window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.toggle("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of all of window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.repaint("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of all of window5, window6 and window11's connections
    -					<div class="code">
    -						<pre>jsPlumb.repaint( [ "window5", "window6", "window11" ] );</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of every connection
    -					<div class="code">
    -						<pre>jsPlumb.repaintEverything();</pre>
    -					</div>
    -				</li>
    -				<li>Detach every connection
    -					<div class="code">
    -						<pre>jsPlumb.detachEverything();</pre>
    -					</div>
    -				</li>
    -				<li>Remove the given Endpoint from element "window1", deleting its Connections. 
    -					<div class="code">
    -						<pre>jsPlumb.removeEndpoint("window1", someEndpoint);</pre>
    -					</div>
    -				</li>
    -				<li>Remove all Endpoints for the element 'window1', deleting their Connections.
    -					<div class="code">
    -						<pre>jsPlumb.removeAllEndpoints("window1");</pre>
    -					</div>
    -				</li>	
    -				<li>Removes every Endpoint managed by this instance of jsPlumb, deleting all Connections.
    -				This is the same as jsPlumb.reset(), effectively, but it does not clear out the event listeners list. 
    -					<div class="code">
    -						<pre>jsPlumb.removeEveryEndpoint();</pre>
    -					</div>
    -				</li>
    -				<li>Deletes the given Endpoint and all its Connections. 
    -					<div class="code">
    -						<pre>jsPlumb.deleteEndpoint(endpoint);</pre>
    -					</div>
    -				</li>
    -				<li>Removes every endpoint, detaches every connection, and clears the event listeners list.  Returns jsPlumb instance to its initial state.  
    -					<div class="code">
    -						<pre>jsPlumb.reset();</pre>
    -					</div>
    -				</li>				
    -				<li>Set window1 to be not draggable, no matter what some jsPlumb command may request.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggable("window1", false);</pre>
    -					</div>
    -				</li>
    -				<li>Set window1 and window2 to be not draggable, no matter what some jsPlumb command may request.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggable(["window1","window2"], false);</pre>
    -					</div>
    -				</li>
    -				<li>Sets whether or not elements that are connected are draggable by default.
    -				The default for this is true.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggableByDefault(false);</pre>
    -					</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable("window1");</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 and window2 as draggable elements (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(["window1","window2"]);</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable("window1");</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (jQuery)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (MooTools)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($$(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (YIU3)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(Y.all(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (jQuery)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($("#window1"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (MooTools)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($("window1"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (YUI3)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(Y.one("window1"));</pre>
    -				</div>
    -				</li>
    -			</ul>
    -		</div>
    -		
    -	
    -		
    -		
    -		<div class="section">
    -			<h3><a id="developingJsPlumb">Advanced Topics</a></h3>
    -			<h4><a id="which">Which files are which?</a></h4>
    -			In development, jsPlumb is broken up into seven scripts:
    -				<ul>
    -					<li>- jsPlumb-x.x.x.js
    -					<p>This is the main jsPlumb engine.</p>					
    -					</li>
    -					<li>- jsPlumb-defaults-x.x.x.js
    -					<p>This contains the default Anchor, Endpoint, Connector and Overlay implementations.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-canvas-x.x.x.js
    -					<p>This contains the HTML5 canvas render code.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-svg-x.x.x.js
    -					<p>This contains the SVG render code.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-vml-x.x.x.js
    -					<p>This contains the VML render code.</p>
    -					</li>
    -					<li>- &lt;LIBRARY_PREFIX&gt;.jsPlumb-x.x.x.js
    -					<p>This contains library-specific helper methods.  jsPlumb ships with three of these - one each for jQuery, MooTools and YUI3. See below for information on how to create a new library implementation.</p>
    -					</li>
    -					<li>- jsBezier-0.2-min.js
    -					<p>These are the Bezier curve functions; they are maintained in a separate project called <a href="http://code.google.com/p/jsbezier/">jsBezier</a></p>
    -					</li>
    -				</ul>
    -			These seven files are packaged together to form the scripts that people use, for example:
    -			<ul>
    -				<li>jquery.jsPlumb-1.3.3-all.js
    -				<p>Contains jsPlumb-1.3.3.js, jsPlumb-defaults-1.3.3.js, jsPlumb-renderers-canvas-1.3.3.js, jsPlumb-renderers-svg-1.3.3.js, jsPlumb-renderers-vml-1.3.3.js, jquery.jsPlumb-1.3.3.js and jsBezier-0.2-min.js</p>
    -				</li>
    -				<li>jquery.jsPlumb-1.3.3-all-min.js
    -				<p>A minified version of the script above (minified using the YUI Compressor)</p>
    -				</li>
    -			</ul>
    -			<h4><a id="pluggableLibrarySupport">Pluggable Library Support</a></h4>
    -		Out of the box, jsPlumb can be run on top of jQuery, MooTools or YUI3.  This is achieved by
    -		delegating several core methods - tasks such as finding an element by id, finding an element's
    -		position or dimensions, initialising a draggable, etc - to the library in question.
    -		<p>To develop one of these, your test page should include the first two scripts discussed above, and
    -		then your own script containing your library specific functionality.  The existing implementations
    -		may be documented well enough for you to create your own, but contact me if you need assistance.  If you do this, it would be
    -		great to share it with everyone...</p>   
    -
    -			<!-- h4><a id="customConnectors">Custom Connectors</a></h4>			
    -					
    -			You can provide your own connectors if you need to. A Connector consists of two functions, which work as a pair.  First a call is made to the <em>compute</em> function:
    -
    -			<div class="code">
    -<pre>
    -this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) {
    -	...
    -	return dimensions;
    -}
    -</pre>
    -			</div>
    -			which is expected to return a list that the <em>paint</em> function can make sense of.  The first four entries in the
    -			list <strong>must be</strong> the [x,y,width,height] values for the canvas that the connector will be drawn on; jsPlumb will
    -			use this information to size the canvas prior to calling the Connector's <em>paint</em> function.  Therefore it
    -			is the Connector's responsibility to ensure that the returned dimensions describe a large enough space for
    -			the line that will be drawn on it.</p><p>The next four elements <strong>must be</strong> the coordinates of the two endpoints of the line
    -			you are going to draw.</p><p>The remainder of the items in the returned list are arbitrary, and will
    -			vary between Connector implementations; this list is passed in to a Connector's <em>paint</em> function, so each
    -			implementation will put into the list whatever it needs to paint itself.  For instance, the straight line
    -			connector only needs the [x,y] location of each end of the line it will paint, and that is one of the required entries, so
    -			it does not have to do anything extra,  whereas the Bezier connector adds the location of the two control points.  Other types of
    -			Connectors will do whatever is appropriate for their particular situation.
    -			</p>
    -			This is the method signature for the <em>paint</em> function:
    -			<div class="code">
    -				<pre>this.paint = function(dimensions, ctx) { .. }</pre>
    -			</div>
    -
    -			here, the 'dimensions' argument to the 'paint' function is the return value of the 'compute' function.  The 'ctx' argument is the Canvas context; you
    -			will do all your drawing on this.
    -			</p>
    -			To change the connector from the default, specify it in your connect call:
    -			<div class="code">
    -				<pre>jsPlumb.connect({source:'someWindow', target:'otherWindow', connector:new jsPlumb.Connectors.Straight()});</pre>
    -			</div>
    -			note that you can use shorthand for connectors if you don't need to specify parameters to it:
    -			<div class="code">
    -				<pre>jsPlumb.connect({source:'someWindow', target:'otherWindow', connector:"Straight"});</pre>
    -			</div>
    -			This works in the same way as specifying Anchors as string does.
    -<p>
    -The section below this discusses Overlays, which allow you to decorate Connectors with
    -			things such as labels or arrows or whatever else you like. Overlays can only work with Connectors that implement a few helper functions.
    -			<h4>The concept of <strong>location</strong></h4>
    -			Before discussing the helper functions you first must be across the concept of 'location' as used by this mechanism.  The <em>location</em>
    -			on a connector can be a decimal value between 0 and 1 inclusive, which marks a proportionate amount of travel <em>along the path
    -			inscribed by the Connector</em>.  For a straight line connector this is a simple piece of maths, but for Bezier connectors it's a little
    -			bit more involved.
    -			<h4>Required Helper Methods</h4>
    -			<ul>
    -				<li><strong>pointOnPath(location)</strong> - returns an [x,y] point corresponding to the given location</li>
    -				<li><strong>pointAlongPathFrom(location, distance)</strong> - returns an [x,y] point corresponding to travelling 'distance' pixels along the connector from 'location'.</li>
    -				<li><strong>gradientAtPoint(location)</strong> - returns the connector's gradient at the given location.  For linear connectors such as Straight this is constant, but for Bezier connectors the gradient changes continually.</li>
    -				<li><strong>perpendicularToPathAt(location, distance, length)</strong> - returns a line that is perpendicular to (and centered on) the connector at 'distance' from the given location, with length 'length'.</li>
    -			</ul>
    -</p>
    -<h4><a id="customEndpoints">Custom Endpoints</a></h4>
    -<p>To create your own Endpoint implementation, you need to implement a single method:
    -				<div class="code"><pre>paint : function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { ... }</pre></div>
    -				The arguments to this method are as follows:
    -				<ul>
    -					<li>anchorPoint - [x,y] location of the anchor point on screen</li>
    -					<li>orientation - [x,y] hints for the general direction the anchor points to</li>
    -					<li>canvas - the canvas to draw into</li>
    -					<li>endpointStyle - Javascript object containing style directives as discussed above.  The contents of this are arbitrary, so if you write a new Endpoint that needs some extra settings, you can add them no hassle.</li>
    -					<li>connectorPaintStyle - the style being used to paint the associated Connector.</li>
    -				</ul>
    -			</p>
    -			<p>
    -				It is your responsibility to size and locate the canvas to suit your needs. jsPlumb provides the following
    -				helper method to assist you:
    -				<div class="code"><pre>jsPlumb.sizeCanvas(canvas, x, y, width, height);</pre></div>
    -				Allows you to locate the canvas on screen and to size it.
    -			</p>
    -<h4><a id="customOverlays">Custom Overlays</a></h4>
    -			Overlays can only work with Connectors that implement the methods <strong>pointOnPath</strong>, <strong>pointAlongPathFrom</strong>,
    -			<strong>gradientAtPoint</strong>, and <strong>perpendicularToPathAt</strong>.  These are discussed in more detail in the Custom Connectors section above.  Both of the Connectors
    -			that come with jsPlumb - Straight and Bezier -implement these methods; if you write a custom connector, or have written a custom connector, you will need to supply
    -			them.   
    -			<h4>Overlay Interface</h4>
    -			An Overlay is required to implement two methods in order to be usable by jsPlumb:
    -			<ul>
    -				<li><strong>computeMaxSize(connector, context)</strong> - returns an integer value indicating the larger of this overlay's width and height.  It is used by jsPlumb to ensure that the canvas is large enough to accomodate the overlay.  The examples below should help to clarify this.</li>
    -				<li><strong>draw(connector, ctx)</strong> - draws the overlay.  What happens in this method is up to the given implementation.</li>
    -			</ul>
    -			<h4>computeMaxSize methods</h4>
    -			This is the Arrow overlay's computeMaxSize method:
    -			<div class="code">
    -<pre>
    -this.computeMaxSize = function() { return width; }
    -</pre>			
    -			</div>
    -Here, <em>width</em> is a private member of Arrow that indicates the width of the arrow's tail.  So the Arrow overlay reports that figure as the width it needs.					
    -Contrast this with the Label Overlay's computeMaxSize method:
    -		<div class="code">
    -<pre>
    -this.computeMaxSize = function(connector, ctx) {
    -  	if (labelText) {
    -  		ctx.save();
    -        if (self.labelStyle.font) ctx.font = self.labelStyle.font;
    -         var t = ctx.measureText(labelText).width;			            
    -		// a fake text height measurement: use the width of upper case M
    -		var h = ctx.measureText("M").width;					
    -		labelPadding = self.labelStyle.padding || 0.25;
    -		labelWidth = t + (2 * t * labelPadding);
    -		labelHeight = h + (2 * h * labelPadding);
    -		ctx.restore();
    -		return Math.max(labelWidth, labelHeight);
    -  	}
    - 	return 0;
    - };
    -</pre>			
    -			</div>
    -The Label overlay has to use the context to determine how big it will be on screen.
    -
    -<h4>draw methods</h4>
    -To give you a taste for how you can interact with a connector, consider the first few lines of the Arrow overlay's draw method:
    -<div class="code">
    -<pre>
    -this.draw = function(connector, ctx) {
    -	// this is the arrow head position
    -	var hxy = connector.pointAlongPathFrom(self.loc, length / 2);		
    -	// this is the center of the tail
    -	var txy = connector.pointAlongPathFrom(self.loc, -length / 2), tx = txy[0], ty = txy[1];
    -	// this is the tail vector
    -	var tail = connector.perpendicularToPathAt(self.loc, -length / 2, width);
    -	
    -	...
    -</pre>
    -</div>
    -	<p>The first two calls to the connector use the <strong>pointAlongPathFrom</strong> method, which passes <em>self.loc</em> as the location of the
    -	point on the path, and <em>length / 2</em> as the number of pixels along the path to traverse.</p>
    -	<p><em>self.loc</em> is Arrow's internal member describing the location of the overlay, and it is a decimal (between 0 and 1 inclusive) that points to
    -	a location at some distance along the path inscribed by the connector.  So these first two calls get us [x,y] locations of points on 
    -	the connector that mark the head and tail points for the arrow.  
    -	</p>		
    -	<p>
    -	The <strong>connector.perpendicularToPathAt(self.loc, -length / 2, width)</strong> call returns a line description of a line that is perpendicular to, and centered on, the connector
    -	at "-length / 2" pixels from the given point, with the given width (width is an internal member of Arrow).  At this point, the Arrow object has the three main points it
    -	needs in order to draw itself.
    -	</p>
    -		</div-->
    -</div>
    -
    -</body>
    -
    -</html>
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.3/index.html b/demo/doc/archive/1.3.3/index.html
    deleted file mode 100644
    index f684d136a..000000000
    --- a/demo/doc/archive/1.3.3/index.html
    +++ /dev/null
    @@ -1,72 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.3 documentation</title>
    -		<link rel="stylesheet" target="contentFrame" href="/mp.css"></link>
    -		<link rel="stylesheet" target="contentFrame" href="jsPlumbDoc.css"></link>
    -	</head>
    -	<body>
    -		<div class="index section">
    -			<h3>jsPlumb 1.3.3</h3>
    -			<ul>
    -				<li><a target="contentFrame" href="content.html#summary">Summary</a></li>
    -				<li><a target="contentFrame" href="content.html#changes">Changes since version 1.3.1</a></li>
    -				
    -				<li><h4>Setup</h4></li>
    -				<li><a target="contentFrame" href="content.html#imports">Required Imports</a></li>
    -				<li><a target="contentFrame" href="content.html#renderMode">Render Mode</a></li>
    -				<li><a target="contentFrame" href="content.html#initializing">Initializing jsPlumb</a></li>
    -				<li><a target="contentFrame" href="content.html#unload">Unloading jsPlumb</a></li>
    -				<li><a target="contentFrame" href="content.html#multipleInstances">Multiple jsPlumb Instances</a></li>
    -				<li><a target="contentFrame" href="content.html#zIndex">Z-index Considerations</a></li>
    -				<li><a target="contentFrame" href="content.html#containerDefault">Where does jsPlumb add elements?</a></li>
    -				
    -				<li><h4>Introduction</h4></li>
    -				<li><a target="contentFrame" href="content.html#jsPlumbBasics">Basic Concepts</a></li>
    -				<li><a target="contentFrame" href="content.html#anchors">Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#dynamicAnchors">Dynamic Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#definitions">Connector, Endpoint & Overlay Definitions</a></li>
    -				<li><a target="contentFrame" href="content.html#connectors">Connectors</a></li>
    -				<li><a target="contentFrame" href="content.html#endpoints">Endpoints</a></li>
    -				<li><a target="contentFrame" href="content.html#overlays">Overlays</a></li>
    -				<li><a target="contentFrame" href="content.html#defaults">Defaults</a></li>								
    -				
    -				<li><h4>Connections</h4></li>
    -				<li><a target="contentFrame" href="content.html#simpleConnections">Programmatic Connections</a></li>
    -				<li><a target="contentFrame" href="content.html#draggableConnections">Connections using Drag and Drop</a></li>
    -				<li><a target="contentFrame" href="content.html#dragOptions">Drag Options</a></li>
    -				<li><a target="contentFrame" href="content.html#dropOptions">Drop Options</a></li>
    -				<li><a target="contentFrame" href="content.html#dragAndDropScope">Drag and Drop Scope</a></li>
    -				
    -				<li><h4>Appearance</h4></li>
    -				<li><a target="contentFrame" href="content.html#paintstyles">Paint Styles</a></li>
    -				<li><a target="contentFrame" href="content.html#hoverpaintstyles">Hover Paint Styles</a></li>
    -				<li><a target="contentFrame" href="content.html#gradients">Gradients</a></li>				
    -				
    -				<li><h4>Miscellaneous</h4></li>
    -				<li><a target="contentFrame" href="content.html#cssclasses">CSS Class Reference</a></li>
    -				<li><a target="contentFrame" href="content.html#animation">Animation</a></li>
    -				<li><a target="contentFrame" href="content.html#connectionInfo">Retrieving Connection Information</a></li>
    -				
    -				<li><h4>Events</h4></li>
    -				<li><a target="contentFrame" href="content.html#events">jsPlumb Events</a></li>
    -				<li><a target="contentFrame" href="content.html#connectionEvents">Connection Events</a></li>
    -				<li><a target="contentFrame" href="content.html#endpointEvents">Endpoint Events</a></li>
    -				<li><a target="contentFrame" href="content.html#overlayEvents">Overlay Events</a></li>
    -				
    -				<li><h4>Examples</h4></li>				
    -				<li><a target="contentFrame" href="content.html#examples">Programmatic API Examples</a></li>
    -				<li><a target="contentFrame" href="content.html#dragAndDropExamples">Draggable Connections Examples</a></li>
    -				<li><a target="contentFrame" href="content.html#utilityFunctions">Utility Functions</a></li>				
    -				
    -				<li><h4>Develop</h4></li>
    -				<li><a target="contentFrame" href="content.html#which">Which files are which?</a></li>
    -				<li><a target="contentFrame" href="content.html#pluggableLibrarySupport">Pluggable Library Support</a></li>
    -				<!-- li><a target="contentFrame" href="content.html#customConnectors">Custom Connectors</a></li>
    -				<li><a target="contentFrame" href="content.html#customEndpoints">Custom Endpoints</a></li>
    -				<li><a target="contentFrame" href="content.html#customOverlays">Custom Overlays</a></li-->
    -			</ul>
    -			<strong>This document refers to release 1.3.3 of jsPlumb.</strong>  
    -			<strong>1 Sep 2011</strong>
    -		</div>		
    -	</body>
    -</html>
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.3/jsPlumbDoc.css b/demo/doc/archive/1.3.3/jsPlumbDoc.css
    deleted file mode 100644
    index bbe50c8ee..000000000
    --- a/demo/doc/archive/1.3.3/jsPlumbDoc.css
    +++ /dev/null
    @@ -1,126 +0,0 @@
    -body { 
    -	background-color:white; font-size:90%;
    -	background-image:url(../img/dynamicAnchorBg.jpg); 
    -}
    -/* docs */
    -.code { background-color:#e4e5ef; 
    -border:1px solid black; margin:1em; padding:0.8em; 
    -overflow:auto;
    -font-family:courier; 
    -font-size:8pt;
    -}
    -.section { border-bottom: 1px dotted black; padding:1em;font-family:helvetica;background-color:white;color:#333;}
    -.menu { 
    -	position:fixed; 
    -	right:10em; top:1em;
    -	background-color:white;
    -	padding:0.3em;
    -	opacity:0.8;
    -	z-index:5000;
    -	filter:alpha(opacity=80);
    -		box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -a {text-decoration:none;color:#05f;font-weight:bold;font-family:helvetica;}
    -a:hover {text-decoration:underline;}
    -.window, .label { 
    -	cursor:pointer;
    -	box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -#explanation { 
    -	background-color:white;
    -	color:black;
    -	border:0.25em solid #ddd; 
    -	position:absolute;
    -	right:3.5em;
    -	top:5em;
    -	width:22em;
    -	font-size:100%;
    -	-moz-border-radius:1em;
    -	border-radius:1em;
    -	padding:1em;
    -	z-index:1000;
    -	opacity:0.9;
    -	filter:alpha(opacity=90); 
    -	font-family:helvetica;
    -		box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -.selected {
    -	color:rgb(189,11,11);
    -}
    -
    -
    -/*  these styles are what jsPlumb attaches to the various elements it creates.
    -._jsPlumb_connector {
    -border:1px solid blue;
    -}
    -._jsPlumb_endpoint {
    -border:1px solid green;
    -}
    -._jsPlumb_overlay {
    -border:1px solid yellow;
    -}
    -*/
    -
    -
    -.section li {
    -list-style-type:none;
    -}
    -
    -li.bullet {
    -	list-style-type:circle;
    -	}
    -
    -
    -.section {
    -padding-left:2em;
    -font-size:10pt;
    -}
    -.index.section {
    -padding-left:0;
    -}
    -
    -.index {
    -width:20em;
    -font-size:9pt;
    -float:left;
    -background-color:#e4eeee;
    -overflow:auto;
    -padding-left:0.5em;
    -}
    -
    -.index li {
    -list-style-type:none;
    -}
    -
    -.index ul, .index h3 {
    -padding-left:1.5em;
    -
    -}
    -
    -#demo {
    -width:80%;
    -height:50em;
    -margin-left:3em;
    -margin-top:3em;
    -position:relative;
    -}
    -
    -.sub {
    -margin-left:1em;
    -}
    -
    -.table td {
    -	text-align:center;
    -}
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.3/usage.html b/demo/doc/archive/1.3.3/usage.html
    deleted file mode 100644
    index 7b21f1f5f..000000000
    --- a/demo/doc/archive/1.3.3/usage.html
    +++ /dev/null
    @@ -1,10 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.3 - documentation</title>		
    -	</head>
    -	
    -	<frameset cols="250,*">
    -  		<frame src="index.html" />
    -  		<frame src="content.html" name="contentFrame" />
    -	</frameset>	
    -</html>
    diff --git a/demo/doc/archive/1.3.4/content.html b/demo/doc/archive/1.3.4/content.html
    deleted file mode 100644
    index 9526dd15e..000000000
    --- a/demo/doc/archive/1.3.4/content.html
    +++ /dev/null
    @@ -1,2776 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.4 - documentation</title>
    -		<link rel="stylesheet" href="/mp.css">
    -		<link rel="stylesheet" href="jsPlumbDoc.css">
    -	</head>
    -	<body>
    -
    -<div class="menu">
    -	<a href="http://jsplumb.org" class="mplink" target="_top">view demo</a>
    -	&nbsp;|&nbsp;<a href="mailto:simon.porritt@gmail.com" class="mplink">contact me</a>
    -	&nbsp;|&nbsp;<a href="http://jsplumb.tumblr.com" class="mplink">jsPlumb on Tumblr</a>	
    -</div>
    -
    -<!--
    -GWT
    -
    -http://fishfilosophy.blogspot.com/2011/12/how-to-connect-gwt-widgets-using.html?spref=tw
    -
    --->
    -
    -<div class="content">
    -		<div class="section">
    -			<h3><a id="summary">Summary</a></h3>
    -			jsPlumb allows you to connect elements on the screen using SVG, Canvas or VML, depending on the capabilities of the browser.
    -			<p>
    -				It can be used with jQuery, MooTools or YUI3 (or another library of your choice if you feel like implementing an adapter for it).
    -			</p>  
    -
    -			<h5>Browser Compatibility</h5>
    -			<p>jsPlumb 1.3.4 has been tested on the following browsers:
    -				<ul>
    -					<li class="bullet">IE 6 on Windows XP</li>
    -					<li class="bullet">IE 7 on Windows XP</li>
    -					<li class="bullet">IE 8 on Windows XP</li>
    -					<li class="bullet">Firefox 3.5.8 on Windows XP</li>
    -					
    -					<li class="bullet">IE 9 on Windows 7</li>
    -					<li class="bullet">Chrome 12 on Windows 7</li>
    -					<li class="bullet">Firefox 3.5.8 on Windows 7</li>
    -					
    -					<li class="bullet">Firefox 3.6.3 on Ubuntu 10.04</li>
    -					<li class="bullet">Chrome on Ubuntu 11.04</li>
    -					
    -					<li class="bullet">Safari 4 on Mac Leopard</li>
    -					<li class="bullet">Chrome on Mac Leopard</li>					
    -					<li class="bullet">Safari 4 on Windows Vista</li>
    -					<li class="bullet">Safari 5.0.5 on Windows 7</li>
    -					
    -					<li class="bullet">Opera 10.54 on Windows XP</li>
    -				</ul>
    -			</p>
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="changes">Changes since version 1.3.3</a></h3>
    -			<p>
    -			Version 1.3.4 is a fairly significant release, containing several bugfixes relating to positioning
    -			when elements with scrollbars or 'overflow:auto' are involved, a new type of connector, "Continuous"
    -			anchors, which are like Dynamic anchors on steroids, beforeDrag/beforeDrop interceptors, and a bunch
    -			of other bits and pieces such as the ability to make an entire div a connection source or target.
    -			</p>
    -			<h5>Backwards Compatibility</h5>
    -			<ul>
    -			<li class="bullet">Starting with this release, jsPlumb <strong>no longer automatically initialises any elements to be
    -			draggable</strong>.
    -			<p>I thought about this long and hard and decided that it was too invasive for jsPlumb to assume that
    -			you wanted every connected element to be draggable.  There were other complications, too, such as the
    -			fact that dragging was not initialised until you had at least one connection to an element - you could
    -			start out with a UI in which some elements were draggable, and others were not, and there did not seem,
    -			to the user, to be a decent reason for that.</p>
    -			<p>You should use the <strong>jsPlumb.draggable(some selector)</strong> method to initialise elements
    -			as draggable now.  It is still imperative that you use this method and not your underlying library's
    -			drag method, because otherwise jsPlumb will not know what's going on.  Valid values for that
    -			selector are whatever your underlying library can support: $("someSelector") for jQuery, $$("someSelector") for MooTools, and
    -			Y.all("someSelector") for YUI3.</p>
    -			</li>
    -			<li class="bullet"><strong>The default renderer is now SVG</strong>, where supported, and VML elsewhere.
    -			<p>I'm of the opinion that SVG is a better option for almost every application using jsPlumb: it
    -			uses far less memory, has native mouse event support so is faster, and can be styled using CSS.</p>
    -			<p>If you really want to use Canvas, call <strong>jsPlumb.setRenderMode(jsPlumb.CANVAS);</strong>.</p>
    -                <p>Be aware that there is a jQuery bug with SVG in IE9 for all jQuery version from 1.6.x to 1.7.2: the hover events
    -                are not properly reported.
    -            </p>
    -			</li>
    -            <li class="bullet">The <strong>detachEverything</strong> method, which was deprecated in 1.3.2, has been removed from jsPlumb. Use detachEveryConnection instead.
    -                It does the same thing.</li>
    -            <li class="bullet">The <strong>detachAll</strong> method, which was deprecated in 1.3.2, has been removed. Use detachAllConnections instead. It does the same thing.
    -            </li>
    -                <li class="bullet">The <strong>labelStyle</strong> parameter has been removed from the Label overlay.  You should use the <strong>cssClass</strong> parameter to set a class, and then control the appearance in your stylesheets.</li>
    -			</ul>			
    -			<h5>New Features</h5>
    -				<ul>
    -					<li class="bullet">
    -						<p>StateMachine Connectors<p>
    -						These connectors look like the state machine connectors you might have seen in 
    -						software like GraphViz.  Support for "loopback" connections is included.
    -					</li>
    -					<li class="bullet">
    -						<p>Continuous Anchors<p>
    -						Continuous anchors are anchors whose position is dynamically dependent on every connection
    -						that is attached to some element.  If you're familiar with the existing Dynamic 
    -						anchors in jsPlumb, these operate in pretty much the same way, with the exception that
    -						you do not need to nominate any specific locations yourself.  They choose their position
    -						based on an element's orientation to the other element in a connection, and everything
    -						is ordered to keep the number of crossed connections to a minimum.  
    -						<p>For more information, see the <a href="#anchors">anchors</a> section.</p>
    -					</li>
    -					<li class="bullet">
    -						<p>beforeDrag/beforeDrop interceptors<p>
    -						These interceptors allow you to decide whether or not to allow a new connection to be
    -						dragged or dropped before jsPlumb goes ahead with the action. They are documented in 
    -						full in the <a href="#interceptors">interceptors</a> section of the documentation.
    -					</li>
    -					<li class="bullet">
    -						<p>makeSource/makeTarget methods</p>
    -						makeSource and makeTarget provide a means for you to nominate some entire element as
    -						a connection source or target.  There are many applications in which this functionality
    -						makes more sense than having to register a dedicated Endpoint.  Read more about these
    -						in the <a href="#connections">connections</a> section below.
    -					</li>
    -					<li class="bullet"><p>'transparent' is now supported as a fillStyle or strokeStyle.</p></li>
    -					<li class="bullet"><p>Endpoint and Connector definitions now support a <strong>hoverClass</strong> parameter.
    -					This is the hover equivalent of the existing <strong>cssClass</strong> parameter.</p>
    -					</li>
    -                    <li class="bullet"><p>The Image Endpoint now has an <strong>onload</strong> parameter, allowing you to supply a callback to run once the image has loaded</p></li>
    -                    <li class="bullet"><p>The Image Endpoint now has a <strong>setImage</strong> method, which also takes an optional onload parameter.  See the API docs.</p></li>
    -                    <li class="bullet"><p>The Image Endpoint now has optional <strong>width</strong>  and <strong>height</strong> parameters, allowing you to override the displayed size of the endpoint.</p></li>
    -                    <li class="bullet"><p>You can supply a <strong>parameters</strong> object to connect, addEndpoint, makeSource and makeTarget calls.  This specifies an object literal of parameter values which will be present
    -                    on resulting connections, available via the <strong>getParameter(String)</strong>method</p></li>
    -				</ul>			
    -			
    -			<h5>Bugfixes for which there were not issues raised</h5>
    -				<ul>
    -					<li class="bullet">A mouse event bug with the SVG renderer was fixed; it was preventing mouse events from
    -					reaching SVG elements that were underneath other SVG elements</li>
    -				</ul>
    -			
    -			<h5>Issues Resolved</h5>
    -			<ul>
    -				<!--li><strong>154</strong> - reinstate the 'tooltip' and 'connectorTooltip' parameters</li-->
    -                <li><strong>159</strong> - do not add methods to the Array prototype</li>
    -                <li><strong>158</strong> - use enclosing table element if an Endpoint is added to a table cell.</li>
    -                <li><strong>150</strong> - hover over one Endpoint does not send the event to the other Endpoint in a Connection</li>
    -				<li><strong>145</strong> - add getLabel/setLabel methods to Overlays</li>
    -				<li><strong>142</strong> - document jsPlumb.connectionDetached event properly</li>				
    -				<li><strong>139</strong> - duplicate ids are sometimes given to Endpoints and Connections</li>
    -				<li><strong>132</strong> - moving a Label Overlay does not remove its associated element</li>
    -				<li><strong>130</strong> - positioning issues when in div with overflow:scroll</li>
    -			</ul>						
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="init">Setup</a></h3>
    -
    -            <h4><a id="imports">Required Imports</a></h4>
    -			<h4>jQuery</h4>
    -			    <ul>
    -				    <li class="bullet">jQuery 1.3.x or higher.</li>
    -				    <li class="bullet">jQuery UI 1.7.x or 1.8.x (if you wish to support drag and drop).</li>
    -			    </ul>
    -			    <div class="code">
    -<pre>&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.0/jquery.min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/jquery-ui.min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/jquery.jsPlumb-1.3.4-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			<h4>MooTools</h4>
    -				<ul>
    -					<li class="bullet">MooTools Core 1.2.4 or higher. jsPlumb has been tested with 1.2.4 and 1.3.3.</li>
    -					<li class="bullet">Drag.Move from MooTools More 1.2.4.4 or higher (if you wish to support drag and drop).</li>
    -				</ul>
    -			<div class="code">
    -			<pre>&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/mootools/1.3.3/mootools-yui-compressed.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO_MOO_TOOLS_MORE_1_3_2_1"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/mootools.jsPlumb-1.3.4-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			<h4>YUI3</h4>
    -			<ul>
    -				<li class="bullet">YUI 3.3.x.  jsPlumb has been tested on 3.3.0 only; it is possible other 3.x.x versions will work.</li>
    -			</ul>
    -			<div class="code">
    -			<pre>&lt;script type="text/javascript" src="http://yui.yahooapis.com/3.3.0/build/simpleyui/simpleyui-min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/yui.jsPlumb-1.3.4-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			
    -			<h4><a id="renderMode">Render Mode</a></h4>
    -			jsPlumb can use SVG, HTML5 Canvas elements or VML to render the various objects it adds to the display.  Most modern browsers support
    -			Canvas and SVG; IE&lt;9 browsers support neither.
    -			<p>
    -				By default, jsPlumb attempts to use <strong>jsPlumb.SVG</strong> as the render mode (prior to 1.3.4, the default renderer was Canvas), falling back to <strong>jsPlumb.VML</strong> if the 
    -				browser is IE&lt;9.  You can change the render mode by making this call:  
    -			</p>
    -			<div class="code">
    -<pre>jsPlumb.setRenderMode(jsPlumb.CANVAS);</pre>			
    -			</div>
    -			if, for some reason, you set the render mode to be <strong>jsPlumb.VML</strong> but you're in any browser other than IE&lt;9, jsPlumb
    -			will use SVG.
    -			<p>
    -			The <strong>jsPlumb.setRenderMode</strong> method returns you the render mode that jsPlumb actually ended up setting.  Valid values for setRenderMode are:
    -			</p>
    -			<ul>
    -				<li class="bullet">jsPlumb.CANVAS</li>
    -				<li class="bullet">jsPlumb.SVG</li>
    -				<li class="bullet">jsPlumb.VML</li>
    -			</ul>			
    -			<h5>Performance Considerations</h5>
    -			<p>
    -			SVG uses less memory than Canvas and has support for mouse events built in, so I recommend using
    -			SVG unless you have a really good reason for wanting Canvas.
    -			</p>
    -			
    -			<h4><a id="initializing">Initializing jsPlumb</a></h4>
    -			You should not start making calls to jsPlumb until the DOM has been initialized - perhaps no surprises there.  With YUI, though, the asynchronous
    -			nature of the script loading process means that you are not guaranteed that jsPlumb is ready as soon as the DOM is.  To handle this,
    -			you should bind to the "ready" event on jsPlumb (or the instance of jsPlumb you are working with):
    -			<div class="code">
    -			<pre>jsPlumb.bind("ready", function() {
    -...	
    -		
    -// your jsPlumb related init code goes here
    -
    -...
    -
    -});						
    -			</pre>
    -			</div>
    -			There's a helper method that can save you approximately 9 (count them!) characters:
    -			<div class="code">
    -			<pre>jsPlumb.ready(function() {
    -...	
    -		
    -// your jsPlumb related init code goes here
    -
    -...
    -
    -});						
    -			</pre>
    -			</div>
    -			<p>
    -			For jQuery and MooTools you do not actually need to do this; by the time the DOM ready event is fired in each of those libraries you can be sure 
    -			all the JS we need has been loaded.  But in terms of explicitly calling out that you are waiting for jsPlumb, it seems like a good practice to bind to the "ready" event.
    -			</p>
    -			<p>
    -			If you bind to the "ready" event after jsPlumb has already been initialized, your callback will be executed immediately.
    -			</p>			
    -			<h4><a id="unload">Unloading jsPlumb</a></h4>
    -			jsPlumb offers a method you can call when your page is unloading.  You should do this to insure
    -			against memory leaks.  You configure it like this:
    -				<div class="code">
    -<pre>
    -&lt;body onunload="jsPlumb.unload();"&gt;
    -
    -...
    -
    -&lt;/body&gt;
    -</pre>
    -				</div>
    -<h4><a id="multipleInstances">Multiple jsPlumb instances</a></h4>
    -			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 <strong>getInstance</strong> method, for example:
    -<div class="code">
    -<pre>
    -var firstInstance = jsPlumb.getInstance();
    -</pre>
    -</div>
    -The variable 'firstInstance' can now be treated exactly as you would treat the 'jsPlumb' variable - you can set
    -defaults, call the connect method, whatever:
    -<div class="code">
    -<pre>
    -firstInstance.Defaults.Connector = [ "Bezier", { curviness: 150 } ];
    -firstInstance.Defaults.Anchors = [ "TopCenter", "BottomCenter" ];
    -
    -firstInstance.connect({source:"element1", target:"element2", scope:"someScope" });
    -</pre>
    -</div>
    -
    -<strong>getInstance</strong> optionally takes an object that provides the defaults:
    -<div class="code">
    -<pre>
    -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" });
    -</pre>
    -</div>
    -
    -		<h4><a id="zIndex">Z-index Considerations</a></h4>
    -		<p>
    -			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. 
    -		</p>  
    -		<p>
    -			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).  			
    -		</p>
    -		<p>
    -			To help you organise z-indices correctly, jsPlumb adds a CSS class to each type of
    -			element it adds. They are as follows:			
    -		</p>
    -		<table style="color:black;width:70%;font-size:90%;">
    -			<tr><td><strong>type of element</strong></td><td><strong>class added</strong></td></tr>
    -			<tr><td>Endpoint</td><td>_jsPlumb_endpoint</td></tr>
    -			<tr><td>Connector</td><td>_jsPlumb_connector</td></tr>
    -			<tr><td>Overlay</td><td>_jsPlumb_overlay</td></tr>
    -		</table>
    -
    -		<h4><a id="containerDefault">Where does jsPlumb add elements?</a></h4>
    -		<p>
    -			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.
    -		</p>
    -		<p>
    -			Prior to 1.3.0, jsPlumb added everything to the end of the <em>body</em> element. This
    -			has the advantage of being the most flexible arrangement in terms of supporting what
    -			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!   
    -		</p>
    -		<p>
    -			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 (<strong>not</strong> 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.
    -		</p>
    -		
    -		<p>
    -			In general, the default behaviour means that you cannot do things like this:
    -		</p>
    -		<div class="code">
    -<pre>
    -&lt;body&gt;
    -	&lt;div id="container0"&gt;
    -		&lt;div id="node0"&gt;&lt;/div&gt;
    -	&lt;/div&gt;
    -	&lt;div id="container1"&gt;
    -		&lt;div id="node1"&gt;&lt;/div&gt;
    -	&lt;/div&gt;
    -&lt;/body&gt;
    -
    -&lt;script type="text/javascript"&gt;
    -
    -	var e0 = jsPlumb.addEndpoint("node0"),
    -	e1 = jsPlumb.addEndpoint("node1");
    -
    -	jsPlumb.connect({ source:e0, target:e1 });
    -&lt;/script&gt;
    -</pre>		
    -		</div>
    -<p>
    -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):  
    -</p>
    -		<div class="code">
    -<pre>
    -&lt;body&gt;
    -	&lt;div id="container0"&gt;
    -	&lt;/div&gt;
    -	&lt;div id="container1"&gt;
    -	&lt;/div&gt;
    -&lt;/body&gt;
    -
    -&lt;script type="text/javascript"&gt;
    -
    -	var e0 = jsPlumb.addEndpoint("container0"),
    -	e1 = jsPlumb.addEndpoint("container1");
    -
    -	jsPlumb.connect({ source:e0, target:e1 });
    -&lt;/script&gt;
    -</pre>		
    -		</div>
    -	now 'e0' and 'e1' have the same parent - the <em>body</em> element. 
    -	
    -	<p>Remember that you can use the <strong>anchor</strong> parameter to an <strong>addEndpoint</strong>
    -	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. 
    -	</p>									
    -	<p><strong>Note regarding the drawEndpoints option on jsPlumb.connect</strong>: 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). 
    -	</p>
    -	
    -	<h5>Overriding the default behaviour</h5>
    -	<p>
    -		From version 1.3.3, the <strong>container</strong> 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 <strong>jsPlumb.Defaults.Container</strong> property,
    -		or you can set a <strong>container</strong> parameter on calls to <strong>addEndpoint</strong> or <strong>connect</strong> (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:
    -	</p>
    -		
    -		Set a container to use as the default container, using a jQuery selector (you can supply MooTools/YUI selectors if that's the library you're using),
    -		and then add an Endpoint.  The canvas (or SVG/VML element) created will be a child of the document body:
    -		<div class="code">
    -<pre>
    -jsPlumb.Defaults.Container = $("body");
    -...
    -jsPlumb.addEndpoint(someDiv, { endpoint options });
    -</pre>		
    -		</div>
    -		
    -			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":
    -		<div class="code">
    -<pre>
    -jsPlumb.Defaults.Container = "containerId";
    -...
    -jsPlumb.connect({ source:someDiv, target:someOtherDiv });
    -</pre>		
    -		</div>
    -		
    -		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":
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({ source:someDiv, target:someOtherDiv, container:$("#containerId") });
    -</pre>		
    -		</div>
    -				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":
    -		<div class="code">
    -<pre>
    -jsPlumb.addEndpoint(someDiv, { ..., container:"containerId" });
    -</pre>		
    -		</div>
    -		
    -		</div>
    -				
    -		<div class="section">
    -			<h3><a id="jsPlumbBasics">Basic Concepts</a></h3>
    -	
    -			jsPlumb is all about connecting things together, so the core abstraction in jsPlumb is the <strong>Connection</strong> object, which is itself
    -			broken down into these four concepts:
    -
    -			<ul>
    -				<li><strong>Anchor</strong> - a location, relative to an element's origin, at which an Endpoint can exist. You do not create these yourself; you 
    -				supply hints to the various jsPlumb functions, which create them as needed.  They have no visual representation; they are a logical position only. 
    -				Anchors can be referenced by name, for the Anchors that jsPlumb ships with, or a four element array describing [ x, y, x orientation, y orientation ]. 
    -				See the <a href="#anchors">anchors</a> section for more detail.<br/><br/></li>
    -				<li><strong>Endpoint</strong> - the visual representation of one end of a Connection.  You can create and attach these to elements 
    -				yourself, which you are required to do to support drag and drop, or have jsPlumb create them when creating a Connection 
    -				programmatically using jsPlumb.connect(...).  You can also join two Endpoints programmatically, by passing them as arguments to 
    -				jsPlumb.connect(...).<br/><br/></li>				
    -				<li><strong>Connector</strong> - the visual representation of the line connecting two elements in the page.  jsPlumb has four types of these 
    -				available as defaults - a Bezier curve, a straight line, 'flowchart' connectors and 'state machine connectors. You do not interact with Connectors; 
    -				you just specify definitions of them when you need to - see <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions</a> <br/><br/></li>
    -				<li><strong>Overlay</strong> - a UI component that is used to decorate a Connector, such as a Label, Arrow, etc.<br/><br/></li>
    -			</ul>
    -						
    -			<p>One Connection is made up of two Endpoints, a Connector, and zero or more Overlays working together to join two elements.</p>
    -			<p>jsPlumb's public API exposes only Connection and Endpoint, handling the creation and configuration of everything else internally.
    -			But you still need to be across the concepts encapsulated by Anchor, Connector and Overlay.
    -			</p>
    -			</div>
    -			
    -			<div class="section">
    -			<h3><a id="anchors">Anchors</a></h3>			
    -	
    -					An Anchor models the notion of where on an element a Connector should connect.  There are three main types of Anchors:
    -					<ul>
    -						<li class="bullet">'Static' anchors - these are fixed to some point on an element and do not move.  They can be specified using a string to
    -						identify one of the defaults that jsPlumb ships with, or an array describing the location (see below)</li>
    -						<li class="bullet">'Dynamic' anchors - these are lists of Static anchors from which jsPlumb picks the most appropriate one each time a Connection
    -						is painted.  The algorithm used to determine the most appropriate anchor picks the one that is closest to the center of the other element in
    -						the Connection. A future version of jsPlumb might support a pluggable algorithm to make this decision.</li>
    -						<li class="bullet">'Continuous' anchors - These anchors, introduced in 1.3.4, are not fixed to any specific loction; they are assigned to one of
    -						the four faces of an element depending on that element's orientation to the other element in the associated Connection.  Continuous anchors are
    -						slightly more computationally intensive than Static or Dynamic anchors because jsPlumb is required to calculate the position of every Connection
    -						during a paint cycle, rather than just Connections belonging to the element in motion.  These anchors were designed for use with the StateMachine
    -						connector that was also introduced in 1.3.4, but will work with any combination of Connector and Endpoint.</li> 
    -					</ul>  
    -					
    -			<h4><a id="staticAnchors">Static Anchors</a></h4>		
    -			jsPlumb has nine default anchor locations you can use to specify where the Connectors connect to elements: these are the four corners of an element,
    -			the center of the element, and the midpoint of each edge of the element:  
    -			<ul>
    -				<li class="bullet">TopCenter</li>
    -				<li class="bullet">TopRight</li>
    -				<li class="bullet">RightMiddle</li>
    -				<li class="bullet">BottomRight</li>
    -				<li class="bullet">BottomCenter</li>
    -				<li class="bullet">BottomLeft</li>
    -				<li class="bullet">LeftMiddle</li>
    -				<li class="bullet">TopLeft</li>
    -			</ul>
    -			
    -			Each of these string representations is just a wrapper around the underlying array-based syntax  [x, y, dx, dy], where x and y are
    -coordinates in the interval [0,1] specifying the position of the
    -anchor, and dx and dy are coordinates in the interval [-1, 1]
    -specifying the orientation of the curve incident to the anchor. For
    -example, [0, 0.5, -1, 0] defines a "LeftCenter" anchor with a
    -connector curve that emanates leftward from the anchor. Similarly,
    -[0.5, 0, 0, -1] defines a "CenterTop" anchor with a connector curve
    -emanating upwards. 	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:"BottomCenter", ... }); 
    -</pre>
    -</div>
    -is identical to:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:[ 0.5, 1, 0, 1 ], ... }); 
    -</pre>
    -</div>			
    -
    -		<h4><a id="dynamicAnchors">Dynamic Anchors</a></h4>
    -	These are Anchors that can be positioned in one of a number of locations, choosing the one that is most appropriate each time something moves or is painted in the UI.
    -	<p>
    -	There is no special syntax to creating a DynamicAnchor; you just provide an array of individual Static Anchor specifications, eg:
    -	</p>
    -<div class="code">
    -<pre>
    -var dynamicAnchors = [ [ 0.2, 0, 0, -1 ],  [ 1, 0.2, 1, 0 ], 
    -			   [ 0.8, 1, 0, 1 ], [ 0, 0.8, -1, 0 ] ];
    -</pre>
    -</div>
    -<h4>	Default Dynamic Anchor</h4>
    -jsPlumb provides a dynamic anchor called "AutoDefault" that chooses from TopCenter, RightMiddle, BottomCenter and LeftMiddle:
    -<div class="code">
    -<pre>jsPlumb.connect({...., anchor:"AutoDefault", ... });</pre>
    -</div>
    -
    -<h4>	Location Selection</h4>
    -The initial implementation of the algorithm that decides which location to choose just calculates which location is closest to the center of the 
    -other element in the connection.  It is possible that future versions of jsPlumb could support more sophisticated choice algorithms, if the need arose.
    -<h4>Draggable Connections</h4>
    -Dynamic Anchors and Draggable Connections can interoperate: jsPlumb locks the position of a dynamic anchor when you start to drag a connection from it,
    -and unlocks it once the connection is either established or abandoned. At that point you may see the position of the dynamic anchor change, as jsPlumb
    -optimises the connection.  <p>You can see this behaviour in the <a href="../html/jquery/draggableConnectorsDemo.html" target="_blank">draggable connections</a> demonstration, when
    -you drag a connection from the blue endpoint on window 1 to the blue endpoint on window 3 - the connection is established and then window 1's blue
    -endpoint jumps down to a location that is closer to window 3.</p>
    -
    -
    -	<h4><a id="continuousAnchors">Continuous Anchors</a></h4>
    -	As discussed above, these are anchors whose positions are calculated by jsPlumb according to the orientation between elements in a Connection, and also how
    -	many other Continuous anchors happen to be sharing the element.  You specify that you want to use Continuous anchors using the string syntax you would use
    -	to specify one of the default Static Anchors, for example:
    -	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:someDiv,
    -	target:someOtherDiv,
    -	anchor:"Continuous"
    -});
    -</pre>
    -</div>
    -
    -Note in this example I specified only "anchor", rather than "anchors" - jsPlumb will use the same spec for both anchors.  But I could have said this:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:someDiv,
    -	target:someOtherDiv,
    -	anchors:["BottomCenter", "Continuous"]
    -});
    -</pre>
    -</div>	 	  
    -..which would have resulted in the source element having a Static Anchor at BottomCenter.  In practise, though, it seems the Continuous Anchors work best if both 
    -elements in a Connection are using them.
    -<p>Note also that Continuous Anchors can be specified on <strong>addEndpoint</strong> calls:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint(someDiv, {
    -	anchor:"Continuous",
    -	paintStyle:{ fillStyle:"red" }
    -});
    -</pre>
    -</div>	 	
    -... and in the jsPlumb defaults:
    -<div class="code">
    -<pre>
    -jsPlumb.Defaults.Anchor = "Continuous";
    -</pre>
    -</div>  
    -					</div>		
    -												
    -<div class="section">
    -		<h4><a id="definitions">Connector, Endpoint &amp; Overlay Definitions</a></h4>
    -		Before we discuss Connector, Endpoints and Overlays, a quick word on definitions:  whenever you need to define a Connector, Endpoint or Overlay, you must use a "definition" of it, rather than constructing one
    -		directly.  This definition can be either a string that nominates the artifact you want to create:
    -		<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:"Rectangle"
    -});
    -</pre>
    -		</div>
    -		or an array consisting of both the artifact's name and the arguments you want to pass to its constructor:
    -		<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Rectangle", { cssClass:"myEndpoint", width:30, height:10 } ]
    -});
    -</pre>
    -		</div>
    -		there is also a three-argument method that allows you to specify two sets of parameters, which jsPlumb will merge together for you. The idea
    -		behind this is that you will often want to define common characteristics somewhere and reuse them across a bunch of different calls:
    -		<div class="code">
    -<pre>var common = {
    -	cssClass	:	"myCssClass",
    -	hoverClass	:	"myHoverClass"
    -};
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Rectangle", { width:30, height:10 }, common ]
    -});
    -</pre>
    -		</div>
    -	This syntax is supported for all Endpoint, Connector and Overlay definitions.  Here's an example using definitions for all three:	
    -		<div class="code">
    -<pre>var common = {
    -	cssClass:"myCssClass"
    -};
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Dot", { radius:5, hoverClass:"myEndpointHover" }, common ],
    -	connector:[ "Bezier", { curviness:100 }, common ],
    -	overlays: [
    -		[ "Arrow", { foldback:0.2 }, common ],
    -		[ "Label", { cssClass:"labelClass" } ]	
    -	]
    -});
    -</pre>
    -		</div>		
    -		
    -		The allowed constructor parameters are different for each artifact you create, but every artifact takes a single JS object as argument,
    -		with the parameters as [key,value] pairs in that object.  See the relevant sections on <a href="#connectors">Connectors</a>,
    -		<a href="#endpoints">Endpoints</a> and <a href="#overlays">Overlays for information on supported parameters.</a>
    -				
    -	</div>
    -
    -
    -		<div class="section">
    -			<h3><a id="connectors">Connectors</a></h3>
    -			<p>Connectors are the lines that actually join elements of the UI.  jsPlumb has four connector implementations - a straight line, a Bezier curve, 
    -			"flowchart", and "state machine".  The default connector is the Bezier curve.  </p>
    -			<p>You optionally specify a Connector by setting the 'connector' property on a call to jsPlumb.connect, jsPlumb.addEndpoint(s), jsPlumb.makeSource or
    -			jsPlumb.makeTarget. If you do not supply a value for 'connector', the default will be used (see <a href="#defaults">defaults</a>).</p>
    -			<p>You specify Connectors using the syntax described above in <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions.</a>Allowed 
    -			constructor values for each Connector type are described below:</p>
    -			
    -			<h4><a id="bezierConnector">Bezier Connector</a></h4>
    -			<p>The Bezier Connector provides a cubic Bezier path between the two Endpoints. It supports a single constructor argument:</p>
    -			<p>
    -			<strong>curviness</strong> - Optional; defaults to 150. This defines the distance in pixels that
    -			the Bezier's control points are situated from the anchor points.  This does not mean that your
    -			connector will pass through a point at this distance from your curve.  It is a hint to how you want the
    -			curve to travel. Rather than discuss Bezier curves at length here, because they are a complex topic,
    -			we refer you to <a href="http://en.wikipedia.org/wiki/B%C3%A9zier_curve" target="_blank">Wikipedia.</a>
    -			</p>
    -			<h4><a id="straightConnector">Straight Connector</a></h4>
    -			<p>The Straight Connector draws a straight line between the two endpoints. No constructor arguments are supported; use the <strong>endpointStyle</strong>
    -			argument to a connect or addEndpoint call to control the appearance of one of these Connectors.  
    -			</p>
    -			<h4><a id="flowchartConnector">Flowchart Connector</a></h4>
    -			<p>This type of Connector draws a connection that consists of a series of vertical or horizontal segments - the classic flowchart look. A single constructor argument is supported:</p>
    -			<strong>stub</strong> - this is the minimum length, in pixels, of the initial stub that emanates from an Endpoint.  This parameter is optional, and defaults to 30 pixels.
    -			<h4><a id="stateMachineConnector">State Machine Connector</a></h4>
    -			<p>This Connector draws slightly curved lines (they are actually quadratic Bezier curves), similar to the State Machine connectors you may have 
    -			seen in software like GraphViz.  Connections in which some element is both the source and the target (so-called 'loopback') are supported by these 
    -			Connectors, in which case you get a circle.  These are the only Connectors for which the loopback case is supported. Supported parameters are:
    -			</p>
    -			<p>
    -			<strong>margin</strong> - Optional; defaults to 5.  Defines the distance from the element that the connector begins/ends.
    -			</p>
    -			<p>
    -			<strong>curviness</strong> - Optional, defaults to 10.  This has a similar effect to the curviness parameter on Bezier curves.
    -			</p>
    -			<p>
    -			<strong>proximityLimit</strong> - Optional, defaults to 80.  The minimum distance between the two ends of the Connector before it paints itself as 
    -			a straight line rather than a quadratic Bezier curve.
    -			</p>    
    -			
    -		</div>
    -		
    -			<div class="section">
    -			<h3><a id="endpoints">Endpoint Types</a></h3>
    -			<p>An Endpoint is the UI component that marks the location of an Anchor, ie. the place where a Connector joins an
    -			element.  jsPlumb comes with four Endpoint implementations - Dot, Rectangle, Blank and Image. You optionally specify Endpoint properties using the
    -			<strong>endpoint</strong> parameter in a call to jsPlumb.connect, jsPlumb.addEndpoint, jsPlumb.makeSource or jsPlumb.makeTarget.
    -			</p>  
    -				<p>As with Connectors and Overlays, you specify Endpoints using the syntax described above in <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions.</a></p>
    -			
    -			The four available Endpoint types, and their constructor arguments, are as follows:
    -					<h4><a id="dotEndpoint">Dot Endpoint</a></h4>
    -					This Endpoint draw a dot on the screen. It supports three constructor parameters:
    -					<p>
    -						<strong>radius</strong> - Optional; defaults to 10 pixels. Defines the radius of the dot.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="rectangleEndpoint">Rectangle Endpoint</a></h4>
    -						Draws a rectangle. Supported constructor parameters are:
    -					<p>
    -						<strong>width</strong> - Optional; defaults to 20 pixels. Defines the width of the rectangle.
    -					</p>
    -					<p>
    -						<strong>height</strong> - Optional; defaults to 20 pixels. Defines the height of the rectangle.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="imageEndpoint">Image Endpoint</a></h4>
    -						Draws an image from a given URL.  This Endpoint supports three constructor parameters:  
    -					<p>
    -						<strong>src</strong> - Required.  Specifies the URL of the image to use.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="blankEndpoint">Blank Endpoint</a></h4>
    -						Does not draw anything visible to the user.  However, a div element is added to the UI and so this Endpoint also support the css parameters:
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -																			
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="overlays">Overlay Types</a></h3>
    -			<p>
    -			Overlays are UI elements that are painted onto connections, such as labels or arrows.
    -			jsPlumb comes with four defaults:
    -			</p>
    -			<ul>
    -				<li><strong>Arrow</strong> - a configurable arrow that is painted at some point along the connector.  You can control the length and width of the Arrow, the 'foldback' point - a point the tail points fold back into, and the direction (allowed values are 1 and -1; 1 is the default and means point in the direction of the connection)</li>
    -				<li><strong>Label</strong> - a configurable label that is painted at some point along the connector.</li>
    -				<li><strong>PlainArrow</strong> - an Arrow shaped as a triangle, with no foldback.</li>
    -				<li><strong>Diamond</strong> - as the name suggests, a diamond.</li>
    -			</ul> 
    -			The last two are actually just configured instances of the generic Arrow overlay (see examples). 
    -			
    -			<p>All Overlays support these two methods for getting/setting their location (location is a number between 0 and 1 inclusive, indicating some point along the path
    -			inscribed by the associated Connector):</p>
    -			<ul>
    -				<li class="bullet"><strong>getLocation</strong> - returns the current location</li>
    -				<li class="bullet"><strong>setLocation</strong> - sets the current location; valid values are decimals in the range 0-1 inclusive</li> 
    -			</ul>
    -			
    -			<p>You can specify one or more overlays when making a call to jsPlumb.connect, jsPlumb.addEndpoint or jsPlumb.makeSource (but not jsPlumb.makeTarget; overlays
    -			are always derived from what the source of a Connection defines)  The three cases are discussed below:</p>
    -			1. Specifying one or more overlays on a jsPlumb.connect call. In this example we'll create an Arrow with the default options for an Arrow, and
    -			a label with the text "foo":
    -			<div class="code">
    -			<pre>jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});			
    -</pre>
    -			</div>
    -			This connection will have an arrow located halfway along it, and the label "foo" one quarter of the way along.  Notice the <strong>id</strong>
    -			parameter; it can be used later if you wish to remove the Overlay or change its visibility (see below).<br/><br/> 
    -			
    -			2. Specifying one or more overlays on a jsPlumb.addEndpoint call.  Note in this example that we use the parameter 'connectorOverlays' and not
    -			'overlays' as in the last example.  This is because 'overlays' will one day be used to support Endpoint Overlays:
    -			<div class="code">
    -			<pre>jsPlumb.addEndpoint("someDiv", {
    -	...
    -	connectorOverlays:[ 
    -		[ "Arrow", { width:10, length:30, location:1, id:"arrow" } ], 
    -		[ "Label", { label:"foo", id:"label" } ]
    -	],
    -	...
    -});</pre>
    -			</div>	
    -			This connection will have a 10x30 Arrow located right at the head of the connection, and the label "foo" located at the halfway point. Again,
    -			note that <strong>id</strong> parameter passed in to each Overlay spec.<br/><br/>
    -			
    -			3. Specifying one or more overlays on a jsPlumb.makeSource call.  Note in this example that we again use the parameter 'connectorOverlays' and not
    -			'overlays'.  The 'endpoint' parameter to jsPlumb.makeSource supports everything you might pass to the second argument of a jsPlumb.addEndpoint call:
    -			<div class="code">
    -			<pre>jsPlumb.makeSource("someDiv", {
    -	...
    -	endpoint:{
    -		connectorOverlays:[ 
    -			[ "Arrow", { width:10, length:30, location:1, id:"arrow" } ], 
    -			[ "Label", { label:"foo", id:"label" } ]
    -		]
    -	}
    -	...
    -});</pre>
    -			</div>	
    -			This connection will have a 10x30 Arrow located right at the head of the connection, and the label "foo" located at the halfway point. Again,
    -			note that <strong>id</strong> parameter passed in to each Overlay spec.<br/><br/>
    -			
    -			 	
    -					<h4><a id="arrowOverlay">Arrow Overlay</a></h4>
    -					<p>This overlay draws an arrow, using four points: the head and two tail points, and a 'foldback' point, which permits the tail
    -					of the arrow to be indented. Available constructor arguments for this Overlay are </p>
    -					<ul>
    -					<li><strong>width</strong> - width of the tail of the arrow</li>
    -					<li><strong>length</strong> - distance from the tail of the arrow to the head</li>
    -					<li><strong>location</strong> - where, as a proportional value from 0 to 1 inclusive, the Arrow should appear on the Connector</li>
    -					<li><strong>direction</strong> - which way to point. Allowed values are 1 (the default, meaning forwards) and -1, meaning backwards </li>
    -					<li><strong>foldback</strong> - how far along the axis of the arrow the tail points foldback in to. Default is 0.623.</li>
    -					<li><strong>paintStyle</strong> - a style object in the form used for paintStyle values for Endpoints and Connectors</li>
    -					</ul>
    -					<h4><a id="plainArrowOverlay">PlainArrow Overlay</a></h4>
    -					<p>This is just a specialized instance of Arrow in which jsPlumb hardcodes 'foldback' to 1, meaning the tail of the Arrow is a flat edge.  All of the constructor parameters from Arrow apply for PlainArrow.</p>
    -					<h4><a id="diamondOverlay">Diamond Overlay</a></h4>
    -					<p>This is a specialized instance of Arrow in which jsPlumb hardcodes 'foldback' to 2, meaning the Arrow turns into a Diamond.  All of the constructor parameters from Arrow apply for PlainArrow.</p>
    -					<h4><a id="labelOverlay">Label Overlay</a></h4>
    -					This provides a text label to decorate Connectors with.  The available constructor arguments are:
    -					<ul>
    -						<li><strong>label</strong> - The text to display.  You can provide a function here instead of plain text: it is passed the Connection as an argument, and it should return a String.</li>
    -						<li><strong>cssClass</strong> - Optional css class to use for the Label.  This is now preferred over using the 'labelStyle' parameter.</li>
    -						<li><strong>labelStyle</strong> - (deprecated, use cssClass instead) Optional arguments for the label's appearance.  Valid entries in this JS object are:
    -							<ul>
    -								<li><em>font</em> - a font string in a format suitable for the Canvas element</li>
    -								<li><em>fillStyle</em> - the color to fill the label's background with. Optional.</li>
    -								<li><em>color</em> - the color of the label's text. Optional.</li>
    -								<li><em>padding</em> - optional padding for the label. This is expressed as a proportion of the width of the label, not in pixels or ems.</li>
    -								<li><em>borderWidth</em> - optional width in pixels for the label's border. Defaults to 0.</li>
    -								<li><em>borderStyle</em> - optional. The color to paint the border, if there is one.</li>
    -								
    -							</ul>
    -						
    -						</li>
    -						<li><strong>location</strong> - As for Arrow Overlay.  Where, proportionally from 0 to 1 inclusive, the label should appear.</li>
    -					</ul>
    -					
    -					<h4><a id="hideshowOverlay">Hiding/Showing Overlays</a></h4>
    -					<p>You can control the visibility of Overlays using the <strong>setVisible</strong> method of Overlays themselves, or with
    -					<strong>showOverlay(id)</strong> or <strong>hideOverlay(id)</strong> on a Connection.</p>
    -					<p>Remember the <strong>id</strong> parameter that we specified in the examples above?  This can be used to retrieve the Overlay
    -					from a Connection:</p>
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -var overlay = connection.getOverlay("myLabel");
    -// now you can hide this Overlay:
    -overlay.setVisible(false);
    -// there are also hide/show methods:
    -overlay.show();
    -overlay.hide();
    -
    -</pre>					
    -					</div>
    -					<p>However, Connection also has two convenience methods you could use instead:
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -connection.hideOverlay("myLabel");
    -
    -// more time passes
    -
    -connection.showOverlay("myLabel");
    -
    -</pre>					
    -					</div>
    -					
    -		<h4><a id="hideshowOverlay">Removing Overlays</a></h4>
    -		<p>Connection also has a <strong>removeOverlay</strong> method, that does what you might expect:
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -connection.removeOverlay("myLabel");
    -
    -
    -</pre>					
    -					</div>							
    -	
    -		</div>
    -	
    -			
    -			<div class="section">
    -			<h3><a id="defaults">Defaults</a></h3>
    -			The easiest way to set a look and feel for your plumbing is to override the defaults that jsPlumb uses. If you
    -			do not do this you are forced to provide your overridden values on every call.  Every argument to the connect and addEndpoint methods has an
    -			associated default value in jsPlumb.<br/><br/>
    -
    -			The defaults that ship with jsPlumb are stored in <em>jsPlumb.Defaults</em>, which is a Javascript object.  Valid entries, and their initial values, are:
    -
    -			<div class="code">
    -<pre>
    -Anchor : "BottomCenter",
    -Anchors : [ null, null ],
    -Connector : "Bezier",
    -DragOptions : { },
    -DropOptions : { },
    -Endpoint : "Dot",
    -Endpoints : [ null, null ],
    -EndpointStyle : { fillStyle : null },
    -EndpointStyles : [ null, null ],
    -EndpointHoverStyle : null,
    -EndpointHoverStyles : [ null, null ],
    -HoverPaintStyle : null,
    -LabelStyle : { color : "black" },
    -LogEnabled : false,
    -Overlays : [ ],
    -MaxConnections : 1,
    -MouseEventsEnabled : true,
    -PaintStyle : { lineWidth : 8, strokeStyle : "#456" },
    -RenderMode : "svg",
    -Scope : "_jsPlumb_DefaultScope"
    -</pre>
    -			</div>
    -			<p>
    -			Note that in EndpointStyle, the default fillStyle is 'null'.  This instructs jsPlumb to use the strokeStyle
    -from the attached connector to fill the endpoint.</p>
    -<p>Note also that you can specify either or both (or neither) of 'EndpointStyle' and 'EndpointStyles'.  This allows you to specify a different
    -end point for each end of a connection.  'Endpoint' and 'Endpoints' use the same concept.  jsPlumb will look first in the
    -individual endpoint/endpoint style arrays, and then fall back to the single default version.</p>
    -
    -			you can override these defaults by including this in a script somewhere:
    -			<div class="code">
    -<pre>
    -jsPlumb.Defaults.PaintStyle = {
    -	lineWidth:13,
    -	strokeStyle: 'rgba(200,0,0,100)'
    -}
    -
    -jsPlumb.Defaults.DragOptions = { cursor: "crosshair" };
    -
    -jsPlumb.Default.Endpoints = [ [ "Dot", 7 ], [ "Dot", 11 ] ];
    -
    -jsPlumb.Defaults.EndpointStyles = [{ fillStyle:"#225588" }, { fillStyle:"#558822" }];</pre>
    -			</div>
    -			after the jsPlumb script has been loaded of course!  Here we have specified the following default behaviour:
    -			<ul>
    -				<li>- connectors are 13 pixels wide and painted with a semi-transparent red line</li>
    -				<li>- when dragging an element the crosshair cursor is used</li>
    -				<li>- the source endpoint is a dot of radius 7; the target endpoint is a dot of radius 11</li>
    -				<li>- the source endpoint is blue; the target endpoint is green</li>
    -			</ul>
    -
    -		</div> 					
    -
    -<div class="section">
    -			<h3><a id="simpleConnections">Connections</a></h3>
    -			<h4><a id="simpleConnections">Programmatic Connections</a></h4>
    -The most simple connection you can make with jsPlumb looks like this:
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({source:"element1", target:"element2"});</pre>			
    -			</div>
    -
    -		In this example we have created a Connection from 'element1' to 'element2'.  Remember that a Connection in jsPlumb consists of two Endpoints,
    -		a Connector, and zero or more Overlays.  But this call to 'connect' supplied none of those things, so jsPlumb uses the default values wherever it needs to.
    -		In this case, default values have been used for the following: 
    -		<ul>
    -			<li>- The type and appearance of each Endpoint in the Connection. jsPlumb's default for this is the "Dot" endpoint, of radius 10, with fill color "#456".</li>
    -			<li>- The Anchors that define where the connection's Endpoints appear on each element. The jsPlumb default is "BottomCenter"</li>			
    -			<li>- Whether or not each Endpoint can be a source or target for new Connections. The default is false.</li>
    -			<li>- The type and appearance of the Connection's Connector. The default is a "Bezier" connector of line width 8, and color "#456".</li>
    -		</ul>
    -		<p>
    -		So this call will result in an 8px Bezier, colored "#456", from the bottom center of 'element1' to the bottom center of 'element2', and each
    -		Endpoint will be a 10px radius Dot Endpoint, colored "#456".
    -		</p>
    -		<p>
    -		Let's beef up this call a little and tell jsPlumb what sort of Endpoints we want, and where we want them:		
    -		</p>
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:"element1", 
    -	target:"element2",
    -	anchors:["RightMiddle", "LeftMiddle" ],
    -	endpoint:"Rectangle",
    -	endpointStyle:{ fillStyle: "yellow" }
    -});
    -</pre>			
    -			</div>
    -			
    -		<p>This is what we have told jsPlumb we want this time:</p>
    -		<ul>
    -			<li><strong>anchors</strong> - this array tells jsPlumb where the source and target Endpoints should be located on their parent elements. In this case,
    -			we use the shorthand syntax to name one of jsPlumb's default anchors; you can also specify custom locations (see <a href="#anchors">anchors</a>).
    -			Instead of <strong>anchors</strong> you can use <strong>anchor</strong>, if you want the source and target Endpoints to be located at the
    -			same place on their parent elements.			<br/>
    -			</li>
    -			<li>
    -				<strong>endpoint</strong>  - this tells jsPlumb to use the "Rectangle" Endpoint for both the source and target of the Connection.  As with anchors, 
    -				<strong>endpoint</strong> has a plural version that allows you to specify a different Endpoint for each end of the Connection.<br/>  
    -			</li>
    -			<li>
    -				<strong>endpointStyle</strong> - this is the definition of the appearance of the Endpoint you specified above.  Again, there is a
    -				plural equivalent of this that allows you to specify a different style for each end of the Connection. For more information about
    -				allowed values for this value, see <a href="#paintstyles">Connector, Endpoint & Overlay Styles</a>. 
    -			</li>
    -		</ul>		
    -		
    -		<h5>Reusing common settings between jsPlumb.connect calls</h5>
    -		<p>
    -			A fairly common situation you will find yourself in is wanting to create a bunch of Connections that have only minor differences
    -			between them.  To support that, <strong>jsPlumb.connect</strong> takes an optional second argument. For example:			 
    -		</p>
    -		<div class="code">
    -<pre>
    -var common = {
    -	anchors:[ "BottomCenter", "TopCenter" ],
    -	endpoints:["Dot", "Blank" ]
    -};
    -
    -jsPlumb.connect({ source:"someElement", target:"someOtherElement" }, common);
    -
    -jsPlumb.connect({ source:"aThirdElement", target:"yetAnotherElement" }, common);
    -
    -</pre>			
    -			</div>		
    -			
    -		<h5>Endpoints created by jsPlumb.connect</h5>
    -		<p>If you supply an element id or selector for either the source or target , jsPlumb.connect will automatically create an Endpoint on the 
    -		given element.  These automatically created Endpoints are not marked as drag source or targets, and cannot
    -		be interacted with.  For some situations this behaviour is perfectly fine, but for more interactive UIs you should set things up using the drag and drop
    -		method discussed below.</p>
    -		<p>Given that jsPlumb.connect creates its own Endpoints in some circumstances, in order to avoid leaving orphaned Endpoints around the place, if the Connection is subsequently
    -		deleted, these created Endpoints are deleted too.  Should you want to, you can override this behaviour by setting <strong>deleteEndpointsOnDetach</strong> to
    -		false in the connect call:</p>
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({ 
    -	source:"aThirdElement", 
    -	target:"yetAnotherElement",
    -	deleteEndpointsOnDetach:false 
    -});
    -</pre>
    -</div>								
    -					
    -		<h4><a id="draggableConnections">Connections using Drag and Drop</a></h4>
    -		To support drag and drop connections, you first need to set a few things up.  Every drag and drop connection needs at least a source
    -		Endpoint that the user can drag a connection from. Here's a simple example of how to create an Endpoint:
    -		<div class="code">
    -<pre>
    -var endpointOptions = { isSource:true };
    -var endpoint = jsPlumb.addEndpoint('elementId', endpointOptions);
    -</pre>		
    -</div>  	
    -	This Endpoint will act as a source for new Connections, and will use the jsPlumb defaults for its own appearance and that of any Connections that are drawn from it.							
    -	
    -		<h5>Tip: use the three-argument addEndpoint method for common data </h5>
    -		One thing that happens quite often is that you have an Endpoint whose appearance and behaviour is largely the
    -		same between usages on different elements, with just a few differences. 
    -		<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -	endpoint:"Rectangle",
    -	paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -	isSource:true,
    -	connectorStyle : { strokeStyle:"#666" },
    -	isTarget:true
    -};
    -</pre>		
    -</div>  		
    -		Notice there is no 'anchor' set.  Here we apply it to two elements, at a different location in each:
    -		<div class="code">
    -<pre>
    -jsPlumb.addEndpoint('element1', { anchor:"BottomCenter" }, exampleGreyEndpointOptions)); 
    -
    -jsPlumb.addEndpoint('element2', { anchor:"TopCenter" }, exampleGreyEndpointOptions));
    -</pre>		
    -</div>
    -
    -<p>
    -Now that you have a source Endpoint, you need to either create a target Endpoint on some element, or notify jsPlumb that you wish to make an
    -entire element a drop target.  Let's look at how to attach a target Endpoint first:
    -</p>
    -
    -		<div class="code">
    -<pre>var endpointOptions = { isTarget:true, endpoint:"Rectangle", paintStyle:{ fillStyle:"gray" } };
    -var endpoint = jsPlumb.addEndpoint("otherElementId", endpointOptions);
    -</pre>		
    -		</div>
    -		<p>
    -		This Endpoint, a gray rectangle, has declared that it can act as a drop target for Connections. 
    -</p>
    -<p>
    -jsPlumb also supports marking entire elements as drag sources or drag targets, using the <strong>makeSource</strong> and <strong>makeTarget</strong> methods.  This
    -is a useful function for certain applications.  Each of these methods takes an Endpoint declaration which defines the Endpoint that jsPlumb will create after a Connection
    -has been attached.  Let's take a look at makeTarget first:
    -</p>
    -<h5>jsPlumb.makeTarget</h5>
    -<p>
    -This method takes two arguments, the first of which specifies some element (or list of elements); the second specifies the Endpoint you wish to create on 
    -that element whenever a Connection is established on it.  In this example we will use the exact same target Endpoint we used before - the gray
    -rectangle - but we will tell jsPlumb that the element "aTargetDiv" will be the drop target:
    -</p>
    -		<div class="code">
    -<pre>var endpointOptions = { 
    -  isTarget:true, 
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" } 
    -};
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -<p>
    -	The allowed values in 'endpointOptions' are identical for both the <strong>jsPlumb.addEndpoint</strong> and <strong>jsPlumb.makeTarget</strong> methods, but makeTarget supports an extended 
    -	anchor syntax that allows you more control over the location of the target endpoint.  This is discussed below.	
    -</p>		
    -
    -<h5>Target Anchors positions with makeTarget</h5>
    -<p>When using the makeTarget method, jsPlumb allows you to provide a callback function to be used to determine
    -the appropriate location of a target anchor for every new Connection dropped on the given target.  It may be the
    -case that you want to take some special action rather than just relying on one of the standard anchor mechanisms.
    -</p>
    -<p>This is achieved through an extended anchor syntax (note that this syntax is <strong>not supported</strong> in
    -the <strong>jsPlumb.addEndpoint</strong> method) that supplies a "positionFinder" to the anchor specification.  jsPlumb 
    -provides two of these by default; you can register your own on jsPlumb and refer to them by name, or just 
    -supply a function.  Here's a few examples:</p>
    -Instruct jsPlumb to place the target anchor at the exact location at which the mouse button was released on the
    -target element. Note that you tell jsPlumb the anchor is of type "Assign", and you then provide a "position"
    -parameter, which can be the name of some position finder, or a position finder function itself:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {
    -  endpoint:{
    -    anchor:[ "Assign", { 
    -	  position:"Fixed"
    -	}]
    -  }
    -});
    -</pre>
    -</div>
    -<p><strong>Fixed</strong> is one of the two default position finders provided by jsPlumb. The other is <strong>Grid</strong>:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {
    -  endpoint:{
    -    anchor:[ "Assign", { 
    -	  position:"Grid",
    -	  grid:[3,3]
    -	}]
    -  }
    -});
    -</pre>
    -</div>
    -The Grid position finder takes a "grid" parameter that defines the size of the grid required. [3,3] means
    -3 rows and 3 columns.
    -<p>To supply your own position finder to jsPlumb you first need to create the callback function. It takes
    -N arguments and does stuff. This paragraph obviously needs to be finished.</p>
    -
    -<h5>jsPlumb.makeSource</h5>
    -There are two use cases supported by this method.  The first is the case that you want to drag a Connection from the element itself and have an Endpoint attached to
    -the element when a Connection is established.  The second is a more specialised case: you want to drag a Connection from the element, but once the Connection is
    -established you want jsPlumb to move it so that its source is on some other element.  For an example of this second case, check out the <a href="">WHERE IS THIS State Machine Connectors Demo</a>
    -<p>Here's an example code snippet for the basic use case of makeSource:</p>
    -<div class="code">
    -<pre>
    -	jsPlumb.makeSource(someDiv, {
    -		paintStyle:{ fillStyle:"yellow" },
    -		endpoint:"Blank",
    -		anchor:"BottomCenter"
    -	});
    -</pre>
    -</div>
    -<p>
    -Notice again that the second argument is the same as the second argument to an addEndpoint call.  makeSource is, essentially, a type of addEndpoint call.  In this example 
    -we have told jsPlumb that we will support dragging Connections directly from 'someDiv'.  Whenever a Connection is established between 'someDiv' and some other element,
    -jsPlumb assigns an Endpoint at BottomCenter of 'someDiv', fills it yellow, and sets that Endpoint as the newly created Connection's source. 
    -</p>		
    -<p>Configuring an element to be an entire Connection source using makeSource means that the element cannnot itself be draggable.  There would be no way for jsPlumb to distinguish
    -between the user attempting to drag the element and attempting to drag a Connection from the element.  If your UI works in such a way that that is acceptable then you'll
    -be fine to use this method.  But if you want to drag Connections from some element but also have the element be draggable itself, you might want to consider the second
    -use case.  First, a code snippet:</p>
    -<div class="code">
    -<pre>
    -	jsPlumb.makeSource("someConnectionSourceDiv", {
    -		paintStyle:{ fillStyle:"yellow" },
    -		endpoint:"Blank",
    -		anchor:"BottomCenter",
    -		parent:"someDiv"
    -	});
    -</pre>
    -</div>
    -<p>The only difference here is the inclusion of the 'parent' parameter.  It instructs jsPlumb to configure the source Endpoint on the element specified - in this case,
    -"someDiv" (the value of this parameter may be a String id or a selector).</p>
    -<p>Again, I'd suggest taking a look at the <a href="../demo/jquery/stateMachineDemo.html">State Machine Connectors demonstration</a> for an example of this.</p>
    -
    -<p>
    -There are many things you can set in an Endpoint options object; for a thorough list see the API documentation for Endpoint.  
    -</p>
    -<p>Here's an example of specifying that you want an Arrow overlay halfway along any Connection dragged from this Endpoint:</p>
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -	endpoint:"Rectangle",
    -	paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -	isSource:true,
    -	connectorStyle : { strokeStyle:"#666" },
    -	isTarget:true,
    -	connectorOverlays: [ [ "Arrow", { location:0.5 } ] ]
    -};
    -</pre>
    -</div>
    -This is an Endpoint that moves around the element it is attached to dependent on the location of other elements in the connections it is attached to
    -(a 'dynamic' anchor):
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -	endpoint:"Rectangle",
    -	paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -	isSource:true,
    -	connectorStyle : { strokeStyle:"#666" },
    -	isTarget:true,
    -	connectorOverlays: [ [ "Arrow", { location:0.5 } ] ]
    -	anchor:[ "TopCenter","RightMiddle","BottomCenter","LeftMiddle" ]
    -};
    -</pre>
    -</div>
    -
    -<h5><a id="dragOptions">Drag Options</a></h5>
    -	These are options that will be passed through to the supporting library's drag API.  jsPlumb passes everything you supply here through, inserting
    -	wrapping functions if necessary for the various lifecycle events that jsPlumb needs to know about.  So if, for example, you pass a function to be
    -	called when dragging starts, jsPlumb will wrap that function with a function that does what jsPlumb needs to do, then call yours.
    -	<p>
    -	At the time of writing, jsPlumb supports jQuery, MooTools and YUI3, and each of those libraries uses different terminology.  In addition, jQuery's API is
    -	more fully featured, providing easy support for setting the zIndex and opacity of elements being dragged, as well as the 'scope' for a drag/drop (allowing
    -	you to specify more than one type of drag/drop pair), and hover classes for when a draggable is on the move or
    -	over a droppable. If you're using jQuery you can of course just supply these values on the dragOptions; to make it easier, jsPlumb's MooTools and YUI3 adapters recognize 
    -	these options and add appropriate callbacks for you.  
    -	</p> 
    -	<p>
    -	Given that the options here are library-specific, and they are all well-documented, we're going to discuss just the three drag options
    -	that behave the same way in all (see below for hoverClass):
    -		<ul>
    -		<li><strong>opacity</strong> - the opacity of an element that is being dragged.  Should be a fraction between 0 and 1 inclusive.</li>
    -		<li><strong>zIndex</strong> - the zIndex of an element that is being dragged.</li>
    -		<li><strong>scope</strong> - the scope of the draggable.  can only be dropped on a droppable with the same scope.  this is discussed below.</li>
    -		</ul> 
    -	</p>
    -	
    -	<p>For more information about drag options, take a look at the <a href="http://docs.jquery.com/UI/Draggable" target="_blank">jQuery</a>, <a href="http://mootools.net/docs/more/Drag/Drag.Move" target="_blank">MooTools</a>, or <a href="http://developer.yahoo.com/yui/3/dd/" target="_blank">YUI3</a> docs.	
    -	</p>
    -	<p><strong>NOTE: there is an issue in Chrome that affects the 'cursor' argument to drag options in jQuery. See these links:</strong>
    -	</p><p>
    -<a href="http://forum.jquery.com/topic/draggable-cursor-option-does-not-work" target="_blank">http://forum.jquery.com/topic/draggable-cursor-option-does-not-work</a><br/>
    -<a href="http://forum.jquery.com/topic/chrome-text-select-cursor-on-drag" target="_blank">http://forum.jquery.com/topic/chrome-text-select-cursor-on-drag</a>
    -</p>
    -You should put something like this at the top of your JS to avoid it (perhaps not something so drastic as a document-wide override):	
    -<div class="code">
    -<pre>document.onselectstart = function () { return false; };</pre>
    -</div>		
    -	
    -	<h5><a id="dropOptions">Drop Options</a></h5>
    -	Drop options are treated by jsPlumb in the same way as drag options - they are passed through to the underlying library.  MooTools does not have drop options like jQuery and YUI3 do; droppable functionality in MooTools 
    -	is actually implemented by the Drag.Move class - the one used to initialise a draggable.  But when you setup an Endpoint in jsPlumb you should ignore
    -	that fact, and treat droppables like you would in jQuery or YUI3.  jsPlumb wires everything up for you under the hood.
    -	<p>There are three jQuery droppable options that jsPlumb treats as shortcuts in MooTools and YUI3, for the sake of consistency:</p>
    -		<ul>
    -			<li><strong>hoverClass</strong> - the CSS class to attach to the droppable when a draggable is hovering over it.</li>
    -			<li><strong>activeClass</strong> - the CSS class to attach to the droppable when a draggable is, um, being dragged.</li>
    -			<li><strong>scope</strong> - the scope of the draggable.  The draggable can only be dropped on a droppable with the same scope.  This is discussed below.</li>
    -		</ul>   
    -	<p>For more information about drop options when using jQuery, see <a href="http://docs.jquery.com/UI/Droppable" target="_blank">here</a>.</p>
    -		
    -	<h5><a id="dragAndDropScope">Drag and Drop Scope</a></h5>
    -		jsPlumb borrowed the concept of 'scope' from jQuery's drag/drop implementation: the notion of
    -		which draggables can be dropped on which droppables.  In jsPlumb you can provide a 'scope' entry when 
    -		creating an Endpoint.  Here's the example grey Endpoint example with 'scope' added:
    -		
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -	endpoint:"Rectangle",
    -	paintStyle:{ width:25, height:21, fillStyle:"#666" },
    -	isSource:true,
    -	connectionStyle : { strokeStyle:"#666" },
    -	isTarget:true,
    -	scope:"exampleGreyConnection"
    -};</pre>		
    -</div>  		
    -  										
    -	If you do not provide a 'scope' entry, jsPlumb uses a default scope.  Its value is accessible through this method:
    -	<div class="code">
    -<pre>
    -jsPlumb.getDefaultScope();
    -</pre>	
    -	</div>				
    -	If you want to change it for some reason you can do so with this method:
    -	<div class="code">
    -<pre>
    -jsPlumb.setDefaultScope("mySpecialDefaultScope");
    -</pre>	
    -	</div>			
    -	You can also, should you want to, provide the scope value separately on the drag/drop options, like this:
    -	<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -	endpoint:"Rectangle",
    -	painStyle:{ width:25, height:21, fillStyle:'#666' },
    -	isSource:true,
    -	connectionStyle : { strokeStyle:"#666" },
    -	isTarget:true,
    -	dragOptions:{ scope:"dragScope" },
    -	dropOptions:{ scope:"dropScope" }
    -};</pre>		
    -</div>  
    -			
    -	</div>	
    -	
    -	<div class="section">
    -		<h3><a id="beforeDrop">Interceptors</a></h3>
    -		<p>
    -		Interceptors are new to jsPlumb 1.3.4.  They are basically event handlers from which you can return a value that tells jsPlumb to abort what it is that
    -		it was doing.  There are currently two interceptors supported - <strong>beforeDrop</strong>, which is called when the user has dropped a Connection onto some target, 
    -		and <strong>beforeDetach</strong>, which is called when the user is attempting to detach a Connection. 
    -		</p>
    -		<p>Interceptors can be registered via the <strong>bind</strong> method on jsPlumb just like any other event listeners, and they can also be passed in to
    -		the <strong>jsPlumb.addEndpoint</strong>, <strong>jsPlumb.makeSource</strong> and <strong>jsPlumb.makeTarget</strong>
    -		methods.  A future version of jsPlumb will support registering interceptors on Endpoints using the <strong>bind</strong> method as you can now with the jsPlumb object.
    -		Note that binding 'beforeDrop' (as an example) on jsPlumb itself is like a catch-all: it will be called every time a Connection is dropped on _any_ 
    -		Endpoint.  But passing a 'beforeDrop' callback into an Endpoint (and in the future, registering one using 'bind') constrains that callback to just the
    -		Endpoint in question.  		
    -		<h4><a id="beforeDrop">Before Drop</a></h4>
    -		This is called when the user has released the mouse button over a Connection target.  Your callback is passed the
    -		a JS object containing these fields:
    -		<ul>
    -			<li>sourceId</li>
    -			<li>targetId</li>
    -			<li>scope</li>
    -		</ul>
    -		<p>Returning false - or nothing - from this callback will cause the drag to be abandoned, and the new Connection removed from the UI.</p>
    -		<h4><a id="beforeDetach">Before Detach</a></h4>
    -		This is called when the user has detached a Connection, which can happen for a number of reasons: by default, jsPlumb allows users to drag Connections
    -		off of target Endpoints, but this can also result from a programmatic 'detach' call.  Every case is treated the same by jsPlumb, so in fact it is possible
    -		for you to write code that attempts to detach a Connection but then denies itself!  You might want to be careful with that. 
    -		<p>Note that this interceptor is passed the actual Connection object; this is different from the beforeDrop
    -		interceptor discussed above: in this case, we've already got a Connection, but with beforeDrop we are yet 
    -		to confirm that a Connection should be created.</p>
    -		<p>Returning false - or nothing - from this callback will cause the detach to be abandoned, and the Connection will be reinstated/left on its current target.</p>
    -			
    -	</div>
    -
    -		<div class="section">
    -			<h3><a id="paintstyles">Paint Styles</a></h3>
    -			Defining the appearance of Connectors, Endpoints (and Overlays, but this is deprecated in favour of using CSS classes) is achieved through a 'paintStyle' (or a quite similar name...keep reading)
    -			object passed as a parameter to one of jsPlumb.connect, jsPlumb.addEndpoint, jsPlumb.makeSource or jsPlumb.makeTarget. Depending on the method you are
    -			calling, the parameter names vary.
    -			<h5>Connector Paint Styles</h5>
    -			These are specified in a <em>paintStyle</em> parameter on a call to <strong>jsPlumb.connect</strong>:
    -			<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"el1",
    -	target:"el2",
    -	paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			or in the <em>connectorPaintStyle</em> parameter on a call to <strong>jsPlumb.addEndpoint</strong> or <strong>jsPlumb.makeSource</strong>:
    -			<div class="code">
    -<pre>jsPlumb.addEndpoint("el1", {
    -	paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -	connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			<div class="code">
    -<pre>jsPlumb.makeSource("el1", {
    -	paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -	connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div> 			
    -			 
    -
    -Notice the <em>paintStyle</em> parameter in those examples: it is the paint style for the Endpoint, which we'll discuss below.
    -
    -			<h5>Endpoint Paint Styles</h5>
    -			These are specified in a <em>paintStyle</em> parameter on a call to <strong>jsPlumb.addEndpoint</strong>. This is the example from
    -			just above:
    -			<div class="code">
    -<pre>jsPlumb.addEndpoint("el1", {
    -	paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -	connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div> 
    -			...or as the <em>endpointStyle</em> parameter to a <strong>jsPlumb.connect</strong> call:		
    -			<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"el1",
    -	target:"el2",
    -	endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -	paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			... or as an entry in the <em>endpointStyles</em> array passed to a <strong>jsPlumb.connect</strong> call:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"el1",
    -	target:"el2",
    -	endpointStyles:[ 
    -		{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -		{ fillStyle:"green" }
    -	],
    -	paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			or as the <em>paintStyle</em> entry in an <em>endpoint</em> parameter passed to a <strong>jsPlumb.makeTarget</strong> or <strong>jsPlumb.makeSource</strong> call:
    -					<div class="code">
    -<pre>jsPlumb.makeTarget("el1", {
    -	endpoint: {
    -		paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 }
    -	}
    -});
    -</pre>			
    -			</div>
    -							<div class="code">
    -<pre>jsPlumb.makeSource("el1", {
    -	endpoint: {
    -		paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 }
    -	},
    -	parent:"someOtherDivIJustPutThisHereToRemindYouYouCanDoThis"
    -});
    -</pre>			
    -			</div>						
    -	In the first example we  made "el1" into a drop target, and defined a paint style for the Endpoint jsPlumb will create when a Connection is
    -	established.  In the second we made "el1" a connection source and assigned the same values for the Endpoint jsPlumb will create when a Connection
    -	is dragged from that element.
    -	
    -	<h5>Overlay Paint Styles</h5>
    -	The preferred way to set paint styles for Overlays is to use the <em>cssClass</em> parameter in the constructor arguments of an Overlay definition.
    -	
    -	<h4>Paint Style Parameters</h4>
    -	This is the full list of parameters you can set in a paintStyle object, but note that <strong>fillStyle</strong> is ignored by Connectors,
    -	and <strong>strokeStyle</strong> is ignored by Endpoints.  Also, if you create a Connection using jsPlumb.connect and do not specify any
    -	Endpoint styles, the Endpoints will derive their fillStyle from the Connector's strokeStyle.
    -	<p>
    -		fillStyle, strokeStyle and outlineColor can be specified using any valid CSS3 syntax.
    -	</p>
    -	<ul>
    -		<li><strong>fillStyle</strong> - color for an Endpoint, eg. rgba(100,100,100,50), "blue", "#456", "#993355", rgb(34, 56, 78).</li>
    -		<li><strong>strokeStyle</strong> - color for a Connector. see fillStyle examples.</li>
    -		<li><strong>lineWidth</strong> - width of a Connector's line. An integer.</li>
    -		<li><strong>outlineWidth</strong> - width of the outline for an Endpoint or Connector. An integer.</li>
    -		<li><strong>outlineColor</strong> - color of the outline for an Endpoint or Connector. see fillStyle examples.</li>
    -		<li><strong>dashstyle</strong> - VML and SVG only. This comes from VML, and allows you to create dashed or dotted lines.  It has a 
    -			better syntax than the equivalent attribute in SVG (<em>stroke-dasharray</em>, discussed below), so jsPlumb supports this for both renderers. 
    -			The <em>dashstyle</em> attribute is specified as an array of strokes and spaces, where each value is some multiple of <em>the width of 
    -			the Connector</em>, and that's where it's better than SVG, which uses pixels.
    -			<p>
    -			<a href="http://www.w3.org/TR/NOTE-VML" target="_blank">The VML spec</a> is a good place to find valid values for dashstyle. Note that
    -			jsPlumb does not support the string values for this attribute ("solid", "dashdot", etc).
    -			</p>
    -			<p>
    -			In SVG render mode, jsPlumb uses the <em>lineWidth</em> parameter in conjunction with the values in a <em>dashstyle</em> attribute
    -			to create an appropriate value for <em>stroke-dasharray</em>.
    -			</p>
    -		</li>
    -		<li><strong>stroke-dasharray</strong> - SVG only. This is the SVG equivalent of <em>dashstyle</em>.  
    -		<a href="http://www.w3.org/TR/SVG/painting.html" target="_blank">The SVG spec</a> discusses valid values for this parameter.  But be aware
    -		that jsPlumb does not convert this into a valid <em>dashstyle</em> attribute when using the VML renderer. Better to use <em>dashstyle</em>.
    -		</li>
    -		<li><strong>stroke-dashoffset</strong> - SVG only.  This is used in SVG to specify how far into the dash pattern to start painting.  For more information,
    -		see <a href="http://www.w3.org/TR/SVG/painting.html" target="_blank">the SVG spec.</a></li>
    -		<li><strong>joinstyle</strong> - VML and SVG only.  As with <em>dashstyle</em>, this is a VML attribute that jsPlumb supports for both VML
    -		and SVG - jsPlumb turns this into a <em>stroke-linejoin</em> attribute when rendering with SVG.  This attribute specifies how you want individual
    -		segments of connectors to be joined; the VML and SVG specs both have examples of this, of which many are the same between the two, which is
    -		why jsPlumb will automatically convert this attribute into the SVG equivalent.
    -		</li>
    -		<li><strong>stroke-linejoin</strong> - SVG only.  This is the equivalent of VML's <em>joinstyle</em> attribute, but as with <em>stroke-dasharray</em>,
    -		jsPlumb does not convert this into something approriate for VML.  So, using <em>joinstyle</em> will enable you to support more browsers with
    -		less effort.		
    -		</li>
    -	</ul>
    -	
    -	<h3><a id="hoverpaintstyles">Hover Paint Styles</a></h3>
    -	 		
    -	 		Connectors and Endpoints both support the concept of a "hover" paint style - a paint style to use when the mouse is hovering over
    -	 		the component.  These are specified in the exact same format as paint styles discussed above, but hover paint styles also inherit
    -	 		any values from the main paint style.  This is because you will typically want to just change the color, or perhaps outline color, of
    -	 		a Connector or Endpoint when the mouse is hovering, but leave everything else the same.  So having hover paint styles inherit their
    -	 		values precludes you from having to define things in more than one place.
    -	 		<p>
    -	 			The naming convention adopted for hover paint styles is pretty much to insert the word 'hover' into the corresponding main paint
    -	 			style parameters.  Here are a couple of examples:
    -	 		</p> 
    -	 					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"el1",
    -	target:"el2",
    -	paintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -	hoverPaintStyle:{ strokeStyle:"red" },
    -	endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -	endpointHoverStyle:{ fillStyle:"red" }
    -});
    -</pre>			
    -			</div>
    -			In this example we specified a hover style for both the Connector, and each of its Endpoints.  Here's the same thing, but using the plural
    -			version, to specify a different hover style for each Endpoint:
    -			
    -			
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"el1",
    -	target:"el2",
    -	paintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -	hoverPaintStyle:{ strokeStyle:"red" },
    -	endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -	endpointHoverStyles:[ { fillStyle:"red" }, { fillStyle:"yellow" } ]
    -});
    -</pre>			
    -			</div>
    -	 		Calls to <strong>jsPlumb.addEndpoint</strong> and <strong>jsPlumb.makeTarget</strong> can also specify various hover paint styles:
    -	 								<div class="code">
    -<pre>jsPlumb.addEndpoint("el1", {
    -	paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -	hoverPaintStyle:{ fillStyle:"red" },
    -	connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -	connectorHoverPaintStyle:{ strokeStyle:"red", outlineColor:"yellow", outlineWidth:1 }
    -});
    -</pre>			
    -			</div> 
    -	 		
    -	 		Here we specified a hover paint style for both the Endpoint we are adding, and any Connections that are made from the Endpoint.
    -	 		<p><strong>jsPlumb.makeTarget</strong> also supports hover paint styles - here's the example from before, with one applied:</p>
    -	 		
    -	 							<div class="code">
    -<pre>jsPlumb.makeTarget("el1", {
    -	endpoint: {
    -		paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -		hoverPaintStyle:{ fillStyle:"red" }
    -	}
    -});
    -</pre>			
    -			</div>	
    -	
    -		Note that <strong>jsPlumb.makeTarget</strong> does not support Connector parameters. It is for creating targets only; Connector parameters 
    -		will be set by the source Endpoint in any Connections that are made to the element that you turned into a target by using this method.
    -	 		
    -	 		
    -			<h4><a id="gradients">Gradients</a></h4>
    -			The Canvas and SVG renderers both support gradients. The VML renderer does not.  jsPlumb uses its own syntax to define
    -			gradients, to abstract out the differences between the syntax required by canvas and that required by SVG.  
    -			<p>There are two types of gradients available - a 'linear' gradient, which consists of colored lines all
    -			going in one direction, and a 'radial' gradient, which consists of colored circles emanating from one circle to another.
    -			Because of their basic shape, jsPlumb supports only <em>linear</em> gradients for
    -			Connectors.  But for Endpoints, jsPlumb supports both <em>linear</em> and <em>radial</em> gradients.</p>
    -			
    -				<h5>Connector gradients</h5>
    -				To specify a linear gradient to use in a Connector, you must add a <em>gradient</em> object to your
    -				Connector's <em>paintStyle</em>, for instance:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : "window2",
    -	target : "window3",
    -	paintStyle:{
    -		gradient:{
    -			stops:[[0,"green"], [1,"red"]]
    -		},
    -		lineWidth:15
    -	}
    -});
    -</pre>
    -</div>
    -Here we have connected window2 to window3 with a 15 pixel wide connector that has a gradient from green to red.</p><p> Notice the <strong>gradient</strong> object and the
    -<strong>stops</strong> list inside it - the gradient consists of an arbitrary number of these "color stops".  Each color stop is comprised
    -of two values - [position, color].  Position must be a decimal value between 0 and 1 (inclusive), and indicates where the color
    -stop is situated as a fraction of the length of the entire gradient.  Valid values for
    -the colors in the <strong>stops</strong> list are the same as those that are valid for <strong>strokeStyle</strong> when describing a color.
    -			</p>
    -			As mentioned, the <strong>stops</strong> list can hold an arbitrary number of entries.  Here's an example of a gradient that goes from red to blue to green, and back again through blue to red:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle : {
    -		gradient:{
    -			stops:[[0,'red'], [0.33,'blue'], [0.66,'green'], [0.33,'blue'], [1,'red']]
    -		},
    -		lineWidth : 15
    -	}
    -});
    -</pre>
    -</div>
    -<strong>Note:</strong> when using the VML renderer, jsPlumb will simply ignore the gradient directive
    -so it is best to ensure you also supply a <em>strokeStyle</em> in your paintStyle object, to give jsPlumb something to fall back on.  If you do not supply
    -a <em>strokeStyle</em> your Connectors will be painted black.  The previous example might look like this, for instance:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle:{
    -		strokeStyle:'red',
    -		gradient:{
    -			stops:[[0,'red'], [0.33,'blue'], [0.66,'green'], [0.33,'blue'], [1,'red']]
    -		},
    -		lineWidth:15
    -	}
    -});
    -</pre>
    -</div>
    -Notice the <strong>strokeStyle:'red'</strong> directive at the beginning of the parameter list in <strong>paintStyle</strong>.
    -			<h5>Endpoint gradients</h5>
    -			Endpoint gradients are specified using the same syntax as Connector gradients.  You put the gradient specifier either in the
    -			<strong>endpoint</strong> member, or if you are specifying different Endpoints for each end of the Connector, in one or both of
    -			the values in the <strong>endpoints</strong> array.  Also, this information applies to the case that you are creating standalone
    -			Endpoints that you will be configuring for drag and drop creation of new Connections. 
    -			<p>
    -			This is an example of an Endpoint gradient that is different for each Endpoint in the Connector.  This comes from the main demo; it is
    -			the Connector joining Window 2 to Window 3:
    -			</p>
    -<div class="code">
    -<pre>var w23Stroke = 'rgb(189,11,11)';
    -jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle:{
    -		lineWidth:8,
    -		strokeStyle:w23Stroke
    -	},
    - 	anchors:[ [0.3,1,0,1], "TopCenter" ],
    - 	endpoint:"Rectangle",
    - 	endpointStyles:[
    - 		{ gradient : {stops:[[0, w23Stroke], [1, '#558822']] } },
    -    	{ gradient : {stops:[[0, w23Stroke], [1, '#882255']] } }
    -    ]
    -});
    -</pre>
    -</div>
    -The first entry in the gradient will be the one that is on the Connector end of the Endpoint.  You can of course have as many color stops as
    -you want in this gradient, just like with Connector gradients.
    -	<h5>Applying the gradient in Endpoints</h5>
    -	Only the Dot and Rectangle endpoints honour the presence of a gradient (and, remember, not in VML). The Image endpoint of course ignores a gradient
    -	as it does no painting of its own.
    -	<p>The type of gradient you will see depends on the Endpoint type:</p>
    -		<ul>
    -			<li><strong>Dot</strong> - renders a radial endpoint, with color stop 0 on the outside, progressing inwards as we move through color stops.
    -			<p>Radial gradients actually require more data than linear gradients - in a linear gradient we just move from one point to another, whereas
    -			in a radial gradient we move from one <em>circle</em> to another.  By default, jsPlumb will render a radial gradient using a source
    -			circle of the same radius as the Endpoint itself, and a target circle of 1/3 of the radius of the Endpoint (both circles share the
    -			same center as the Endpoint itself). This circle will be offset by radius/2 in each direction.</p>
    -			<p>You can supply your own values for these inside the gradient descriptor:</p>
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect({
    -	source : 'window3',
    -	target : 'window4',
    -    paintStyle:{
    -    	lineWidth:10,
    -    	strokeStyle:w34Stroke
    -    },
    -    anchors:[ "RightMiddle", "LeftMiddle" ],
    -    endpointStyle:{
    -    	gradient : {
    -    		stops:[ [0, w34Stroke], [1, w34HlStroke] ],
    -    		offset:37.5,
    -    		innerRadius:40
    -    	},
    -    	radius:55
    -    }
    - });
    -</pre>
    -</div>
    -Here we have instructed jsPlumb to make the gradient's inner radius 10px instead of the default 25/3 = 8 ish pixels, and the offset in each direction
    -will be 5px, instead of the default radius / 2 = 12.5 pixels.
    -<p>It is also possible to specify the offset and inner radius as percentages - enter the values as strings with a '%' symbol on the end:</p>
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect({
    -	source : 'window3', 
    -	target : 'window4',
    -    paintStyle:{
    -    	lineWidth:10,
    -    	strokeStyle:w34Stroke
    -    },
    -	anchors:[ "RightMiddle", "LeftMiddle" ],
    -	endpointStyle:{
    -		gradient : {
    -			stops:[ [0, w34Stroke], [1, w34HlStroke] ],
    -			offset:'68%',
    -			innerRadius:'73%'
    -		},
    -		radius:25
    -	}
    -});
    -</pre>
    -</div>
    -This will give roughly the same output as the example above (the percentages are not entirely exact).<br/><br/>
    -			</li>
    -			<li><strong>Rectangle</strong> - renders a linear endpoint, with color stop 0 closest to the end of the Connector</li>
    -		</ul>
    -		
    -<h4><a id="svgvmlcss">Styling SVG/VML using CSS</a></h4>
    -	A nice way of controlling your UI's appearance is to make use of the fact that both SVG and VML can be styled using CSS.
    -	<p>This section will be expanded in the next few weeks to give some decent examples of how to do so</p>
    -			
    -		
    -		
    -		</div>
    -
    -		<div class="section">
    -			<h3><a id="cssclasses">CSS Class Reference</a></h3>
    -			jsPlumb attaches classes to each of the UI components it creates.
    -			<p>These class names are exposed on the jsPlumb object and can be overridden if you need to do so (see the third column in the table) </p>
    -			<table width="70%" class="table">
    -				<tr><th>Component</th><th>CSS Class</th><th>jsPlumb Member</th></tr>
    -				<tr><td>Connector</td><td>_jsPlumb_connector</td><td>connectorClass</td></tr>
    -				<tr><td>Endpoint</td><td>_jsPlumb_endpoint</td><td>endpointClass</td></tr>
    -				<tr><td>Overlay</td><td>_jsPlumb_overlay</td><td>overlayClass</td></tr>
    -			</table>
    -			<p>You would typically use these to establish appropriate z-indices for your UI.</p>			
    -		</div>
    -				
    -		<div class="section">
    -			<h3><a id="animation">Animation</a></h3>
    -			jsPlumb offers an 'animate' function, which wraps the underlying animation engine for whichever library you happen to be using and
    -			inserts a callback for jsPlumb to repaint whatever it needs to at each step.  You could of course do this yourself; it's a convenience
    -			method really.
    -			<p>The method signature is:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.animate : function(el, properties, options) 
    -</pre>
    -</div>			
    -The arguments are as follows:
    -	<ul>
    -		<li><strong>el</strong> - element id, or element object from the library you're using.</li>
    -		<li><strong>properties</strong> - properties for the animation, such as duration etc.</li>
    -		<li><strong>options</strong> - options for the animation, such as callbacks etc.</li>
    -	</ul>
    -	
    -		<h4>jQuery and the 'revert' option</h4>
    -		jQuery offers a 'revert' option that you can use to instruct it to revert a drag under some condition. This is
    -		rendered in the UI as an animation that returns the drag object from its current location to wherever it started
    -		out.  It's a nice feature.  Unfortunately, the animation that runs the revert does not offer any lifecycle
    -		callback methods - no 'step', no 'complete' - so its not possible for jsPlumb to know that the revert 
    -		animation is taking place.
    -	
    -		</div>
    -		
    -			<div class="section">
    -			<h3><a id="connectionInfo">Retrieving Connection Information</a></h3>
    -			jsPlumb offers one fairly versatile method - <strong>getConnections</strong> - to retrieve information about the
    -			currently managed connections.  
    -			<p>Before you use this method you should understand jsPlumb's notion of 'scope' - documentation
    -			is <a href="#dragAndDropScope">here</a> </p>
    -			
    -			<h4>Retrieving connections for a single scope</h4>
    -			<p>To do this, you call getConnections with either no arguments, in which case jsPlumb uses the default scope, or with a string
    -			specifying one scope</p>
    -			<div class="code">
    -<pre>var connectionList = jsPlumb.getConnections();     // you get a list of Connection objects that are in the default scope.
    -</pre>			
    -			</div>
    -			Compare this with:
    -			<div class="code">
    -<pre>var connectionList = jsPlumb.getConnections("myScope");     // you get a list of Connection objects that are in "myScope".
    -</pre>			
    -			</div>
    -			<h4>More advanced filtering</h4>
    -			getConnections optionally takes a JS object specifying filter parameters, of which there are three:
    -			<ul>
    -				<li>scope - the scope(s) of the connection type(s) you wish to retrieve</li>
    -				<li>source - limits the returned connections to those that have this source id</li>
    -				<li>target - limits the returned connections to those that have this target id</li>
    -			</ul>			
    -			Each of these three parameters may be supplied as a string, or a list of strings - see the examples below.
    -			<p>
    -			The return value of a call to getConnection using a JS object as parameter varies on how many scopes you defined.  If you defined only
    -			a single scope then jsPlumb returns you a list of Connections in that scope.  Otherwise the return value is a dictionary whose keys are 
    -			scope names, and whose values are lists of Connections. For example, the following call:
    -			</p>
    -			<div class="code">
    -<pre>
    -	jsPlumb.getConnections({scope:["someScope", "someCustomScope"]});
    -</pre>
    -</div>
    -would result in this output:			
    -<div class="code">
    -<pre>
    -{
    -	"someScope" : [ 1..n Connections ],
    -	"someCustomScope": [ 1..m Connections ]
    -}
    -</pre>
    -</div>			
    -		The following examples show the various ways you can get connection information:
    -	<ul>
    -		<li>Get all connections:
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getAllConnections();  
    -</pre>
    -</div>						
    -		</li>
    -<li>Get all connections for the default scope only (return value is a list):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections();  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given scope (return value is a list):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:"myTestScope"});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given scopes (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:["myTestScope", "yourTestScope"]});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given source (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:"mySourceElement"});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given sources (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:["mySourceElement", "yourSourceElement"]});  
    -</pre>
    -</div>						
    -		</li>		
    -<li>Get all connections for the given target (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({target:"myTargetElement"});  
    -</pre>
    -</div>						
    -		</li>				
    -<li>Get all connections for the given source and targets (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:"mySourceElement", target:["target1", "target2"]});  
    -</pre>
    -</div>						
    -		</li>				
    -		
    -<li>Get all connections for the given scope, with the given source and target (return value is a list of connections):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:'myScope", source:"mySourceElement", target:"myTargetElement"});  
    -</pre>
    -</div>						
    -		</li>				
    -		
    -	</ul>	
    -
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="events">Events</a></h3>
    -			jsPlumb supports binding to several different events on Connections, Endpoints and Overlays, and also on the jsPlumb
    -			object itself.  
    -			
    -			<h4><a id="jsPlumbEvents">jsPlumb Events</a></h4>
    -			<p>To bind an event to jsPlumb itself, use <strong>jsPlumb.bind(event, callback)</strong>:</p>
    -			<div class="code">
    -<pre>
    -jsPlumb.bind("jsPlumbConnection", function(info) {
    -   .. update your model in here, maybe.
    -});
    -</pre>
    -</div>
    -<p>These are the events you can bind to on the jsPlumb class:</p>
    -			<ul>
    -				<li class="bullet"><strong>jsPlumbConnection</strong> - notification a Connection was established.
    -					<p>
    -						The callback is passed an object with the following properties:					
    -					</p>
    -					<div class="code">
    -<pre>
    -{
    -  connection		: 	the new Connection.  you can register listeners on this etc.
    -  sourceId			:	id of the source element in the Connection
    -  targetId			:	id of the target element in the Connection
    -  source			:	the source element in the Connection
    -  target			:	the target element in the Connection
    -  sourceEndpoint	:	the source Endpoint in the Connection
    -  targetEndpoint	:	the targetEndpoint in the Connection
    -}
    -</pre>
    -					</div>
    -					<p>
    -All of the source/target properties are actually available inside the Connection object, but - for one of those rubbish historical
    -reasons - are provided separately because of a vagary of the <em>jsPlumbConnectionDetached</em> callback, which is discussed below.
    -</p>
    -				</li>
    -				<li class="bullet"><strong>jsPlumbConnectionDetached</strong> - notification a Connection was detached.  
    -					<p>
    -						As with <em>jsPlumbConnection</em>, the callback is passed an object with the following properties:					
    -					</p>
    -					<div class="code">
    -<pre>
    -{
    -  connection		: 	the Connection that was detached.  
    -  sourceId			:	id of the source element in the Connection <em>before it was detached</em>
    -  targetId			:	id of the target element in the Connection before it was detached
    -  source			:	the source element in the Connection before it was detached
    -  target			:	the target element in the Connection before it was detached
    -  sourceEndpoint	:	the source Endpoint in the Connection before it was detached
    -  targetEndpoint	:	the targetEndpoint in the Connection before it was detached
    -}
    -</pre>
    -
    -					</div>				
    -					<p>
    -As mentioned above, the source/target properties are provided separately from the Connection, because this event is fired whenever
    -a Connection is either detached and abandoned, or detached from some Endpoint and attached to another.  In the latter case, the
    -Connection that is passed to this callback is in an indeterminate state (that is, the Endpoints are still in the state they are
    -in when dragging, and do not reflect static reality), and so the source/target properties give you the real story.
    -</p>
    -<p> Like I said above, this is really just for rubbish historical reasons.  I'm attempting to document my way out of it!</p>
    -				
    -				</li>
    -				<li class="bullet"><strong>click</strong> - notification a Connection was clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification a Connection was double-clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>endpointClick</strong> - notification an Endpoint was clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>endpointDblClick</strong> - notification an Endpoint was double-clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>beforeDrag</strong>
    -					This event is fired when a new connection is about to be dragged.  Your callback function
    -					is passed the Connection that jsPlumb has just created.  If you return false (or nothing) 
    -					from this callback, the new Connection is aborted.
    -				</li>
    -				<li class="bullet"><strong>beforeDrop</strong>
    -					This event is fired when a new connection has been dropped.  Your callback function
    -					is passed the Connection that jsPlumb has just created.  If you return false (or nothing) 
    -					from this callback, the new Connection is aborted and removed from the UI.
    -				</li>				
    -			</ul>
    -			<h4><a id="connectionEvents">Connection Events</a></h4>		
    -			<p>To bind to an event on a Connection, you also use the <strong>bind</strong> method:</p>
    -			<div class="code">
    -<pre>
    -var connection = jsPlumb.connect({source:"d1", target:"d2"});
    -connection.bind("click", function(conn) {
    -	console.log("you clicked on ", conn);
    -});
    -</pre>
    -</div>
    -<p>These are the Connection events to which you can bind a listener:</p>
    -			<ul>
    -				<li class="bullet"><strong>click</strong> - notification a Connection was clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification a Connection was double-clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>mouseenter</strong> - notification the mouse entered the Connection's path.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>mouseexit</strong> - notification the mouse exited the Connection's.  The callback is passed the Connection and the original mouse event.</li>
    -			</ul>
    -			<h4><a id="endpointEvents">Endpoint Events</a></h4>			
    -			<p>To bind to an event on a Endpoint, you again use the <strong>bind</strong> method:</p>
    -			<div class="code">
    -<pre>
    -var endpoint = jsPlumb.addEndpoint("d1", { someOptions } );
    -endpoint.bind("click", function(endpoint) {
    -	console.log("you clicked on ", endpoint);
    -});
    -</pre>
    -</div>
    -<p>These are the Endpoint events to which you can bind a listener:</p>
    -			<ul>
    -				<li class="bullet"><strong>click</strong> - notification an Endpoint was clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification an Endpoint was double-clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>mouseenter</strong> - notification the mouse entered the Endpoint.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>mouseexit</strong> - notification the mouse exited the Endpoint.  The callback is passed the Endpoint and the original mouse event.</li>
    -			</ul>
    -			<h4><a id="overlayEvents">Overlay Events</a></h4>
    -			Registering event listeners on an Overlay is a slightly different procedure - you provide them as arguments to the Overlay's 
    -			constructor.  This is because you never actually act on an 'Overlay' object.  Here's how to register a click listener on an Overlay:
    -			<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"el1",
    -	target:"el2",
    -	overlays:[
    -		[ "Label", {
    -			events:{
    -				click:function(labelOverlay, originalEvent) { alert("you clicked on the label overlay for this connection :" + labelOverlay.connection) }
    -			}
    -		}] 	
    -	]
    -});
    -</pre>			
    -			</div>		
    -		</div>
    -
    -				<div class="section">
    -			<a id="examples"><h3>jsPlumb.connect Examples</h3></a>
    -This section provides examples of how to use the programmatic API to establish Connections. 
    -			<p>
    -			The basic syntax of a call is that you execute 'connect', providing a source and a target, and optionally a paintStyle and preferences for where you
    -			want the plumbing to be anchored on each element, as well as the type of connector to use. 
    -			</p>
    -			<ul>
    -				<li>Connect window1 to window2 with the default settings:
    -					<div class="code">
    -						<pre>jsPlumb.connect({source:"window1", target:"window2"});</pre>
    -					</div>
    -This connects 'window1' to 'window2' using the jsPlumb defaults - a red Bezier curve Connector and black Dot Endpoints.
    -				</li>
    -				<li>Connect window1 to window2 with a 15 pixel wide yellow Connector, and a slightly brighter endpoint (remember the default Endpoint is a Dot):
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -					</div>
    -				</li>
    -				
    -				<li>Connect window1 to window2 with a 15 pixel wide yellow Connector, and a slightly brighter endpoint:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -					</div>
    -				</li>				
    -				<li>Connect window3 to 'window4' with a 10 pixel wide, semi opaque blue Connector, anchored to the left middle of window3, and the right middle of window4, with a Rectangle endpoint of width 10 and height 8:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window3',
    -	target:'window4',
    -	paintStyle:{ lineWidth:10, strokeStyle:'rgba(0, 0, 200, 0.5)' },
    -	anchors:["RightMiddle", "LeftMiddle"],
    -	endpoint:[ "Rectangle", { width:10, height:8 } ]
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window2 to window3 with a default Connector from the top center of window2 to the bottom center of window3, and rectangular endpoints:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window2',
    -	target:'window3',
    -	paintStyle:{lineWidth:8, strokeStyle:'rgb(189,11,11)'},
    -	anchors:["BottomCenter", "TopCenter"],
    -	endpoint:"Rectangle"
    -});</pre>
    -					</div>
    -				</li>
    -
    -
    -				<li>Connect window1 to window2 with a 15 px wide yellow Bezier. endpoints are a slightly lighter shade of yellow.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	anchors:["BottomCenter", [0.75,0,0,-1]],
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -</div></li>
    -<li>Connect window3 to window4 with a 10px wide blue-ish half transparent Bezier. put endpoints underneath the element they attach to.
    -the endpoints have a radial gradient. both ways of specifying gradient positioning are shown here.
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect( {
    -	source:'window3',
    -	target:'window4',
    -		 paintStyle:{lineWidth:10, strokeStyle:w34Stroke},
    -		 anchors:["RightMiddle", "LeftMiddle"],
    -		 endpointStyle:{ gradient : {stops:[[0, w34Stroke], [1, w34HlStroke]], offset:17.5, innerRadius:15 }, radius:35},
    -		 //endpointStyle:{ gradient : {stops:[[0, w34Stroke], [1, w34HlStroke]], offset:'78%', innerRadius:'73%'}, radius:35 },
    -		 endpointsOnTop:false
    -	}
    -);</pre>
    -</div></li>
    -<li>Connect window2 to window3 with an 8px red Bezier and default rectangular endpoints.  see also how the first anchor is
    -specified here - this is how you create anchors in locations jsPlumb does not offer shortcuts for.
    -the endpoints in this example have linear gradients applied.
    -<div class="code">
    -<pre>var w23Stroke = 'rgb(189,11,11)';
    -jsPlumb.connect({
    -	source:'window2',
    -	target:'window3',
    -	paintStyle:{lineWidth:8,strokeStyle:w23Stroke},
    -	anchors:[[0.3,1,0,1], "TopCenter"],
    -	endpoint:"Rectangle",
    -	endpointStyles:[{ gradient : {stops:[[0, w23Stroke], [1, '#558822']] }},
    -       				{ gradient : {stops:[[0, w23Stroke], [1, '#882255']] }}]
    -});</pre>
    -</div></li>
    -
    -<li>Connect window5 to window6 from center to center, 5px wide line that is green and half transparent. the endpoints are
    -125px in radius and spill out from underneath their elements.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window5',
    -	target:'window6',
    -	anchors:["Center", "Center"],
    -	paintStyle:{lineWidth:5,strokeStyle:'rgba(0,255,0,0.5)'},
    -	endpointStyle:{radius:125}
    -});</pre>
    -</div></li>
    -
    -<li>Connect window4 to window5 from bottom right to top left, with a 7px straight line purple connector, and an image as the endpoint,
    -placed on top of the element it is connected to.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window4",
    -	target:"window5",
    -	anchors:[ "BottomRight","TopLeft" ],
    -	paintStyle:{ lineWidth:7, strokeStyle:"rgb(131,8,135)" },
    -	endpoint:[ "Image", { src:"http://morrisonpitt.com/jsPlumb/img/endpointTest1.png" } ],
    -	connector:"Straight"
    -});</pre>
    -</div></li>
    -
    -
    -				<li>Connect window5 to window6 between their center points with a semi-opaque connector, and 125px endpoints:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window5",
    -	target:"window6",
    -	anchors:[ "Center", "Center" ],
    -	paintStyle:{ lineWidth:5, strokeStyle:"rgba(0,255,0,0.5)" },
    -	endpointStyle:{ radius:125 }
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window7 to window8 with a 10 pixel wide blue Connector, anchored on the top left of window7 and the bottom right of window8:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window7",
    -	target:"window8",
    -	paintStyle:{ lineWidth:10, strokeStyle:"blue" },
    -	anchors:[ "TopLeft", "BottomRight" ]
    -});
    -</pre>
    -					</div>
    -				</li>
    -				<li>Connect the bottom right corner of window4 to the top left corner of window5, with rectangular endpoints of size 40x40 and a hover color of light blue:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window4",
    -	target:"window5",
    -	anchors:["BottomRight","TopLeft"],
    -	paintStyle:{lineWidth:7,strokeStyle:'rgb(131,8,135)'},
    -	hoverPaintStyle:{ strokeStyle:"rgb(0, 0, 135)" },
    -	endpointStyle:{ width:40, height:40 },
    -	endpoint:"Rectangle",
    -	connector:"Straight"
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window1 to window2 with the default paint settings but provide some drag options (which are passed through to the underlying library's draggable call):
    -					<div class="code">
    -						<pre>jsPlumb.connect({source:'window1', target:'window2', dragOptions:{cursor:'crosshair'}});</pre>
    -					</div>
    -				</li>
    -</div>
    -		
    -		<div class="section">
    -			<a id="dragAndDropExamples"><h3>Draggable Connections Examples</h3></a>
    -			This is a list of examples of how to use jsPlumb to create Connections using drag and drop.
    -			<p>
    -			The basic procedure is:
    -			<ol>
    -				<li>1a. Create Endpoints and register them on elements in your UI</li>
    -				<li>1b. alternatively, create a source endpoint and then make some element a drop target </li>
    -				<li>2. Drag and Drop</li>
    -			</ol>
    -			That's all there is to it.  Of course there are plenty of options you can set when doing this...it will be easier
    -			to show you some examples:
    -			</p>
    -			<ul>
    -				<li>Define an Endpoint with default appearance, that is both a source and target of new Connections:
    -<div class="code">
    -<pre>var endpointOptions = { isSource:true, isTarget:true }; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Register that Endpoint on window3, specifying that it should be located in the top center of the element:
    -<div class="code">
    -<pre>var window3Endpoint = jsPlumb.addEndpoint('window3', { anchor:"TopCenter" }, endpointOptions );  
    -</pre>
    -</div>
    -Notice here the usage of the three-argument addEndpoint  - we can reuse 'endpointOptions' with a different Anchor for another element.  This is a useful
    -practice to get into.<br/><br/>
    -				</li>
    -				
    -				<li>Now register that Endpoint on window4, specifying that it should be located in the bottom center of the element:
    -<div class="code">
    -<pre>var window4Endpoint = jsPlumb.addEndpoint('window4', { anchor:"BottomCenter" }, endpointOptions );  
    -</pre>
    -</div>
    -Now we have two Endpoints, both of which support drag and drop of new Connections.  We can use these to make a programmatic Connection, too, though:<br/><br/>
    -				</li>				
    -				
    -				<li>Connect window3 to window4 with a 25px wide yellow Bezier that has a 'curviness' of 175:
    -<div class="code">
    -<pre>jsPlumb.connect({ 
    -	source:window3Endpoint,
    -	target:window4Endpoint,
    -	connector: [ "Bezier", 175 ],
    -	paintStyle:{ lineWidth:25, strokeStyle:'yellow' }
    -});  
    -</pre>
    -</div>
    -				</li>
    -				
    -				<li>Define an Endpoint that creates Connections that are 20px wide straight lines, that is both a source and target of new Connections,
    -				and that has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Define an Endpoint that will be anchored to "TopCenter".  It creates Connections that are 20px wide straight lines, that is both a source and target of new Connections,
    -				and that has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchor:"TopCenter", 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Define an Endpoint that will create a dynamic anchor which can be positioned at "TopCenter" or "BottomCenter".  It creates Connections that are 20px wide straight lines, it is both a source and target of new Connections,
    -				and it has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchor:[ "TopCenter", "BottomCenter" ], 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Exactly the same as before, but shows how you can use "anchors" instead of "anchor", if that makes you feel happier:
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchors:[ "TopCenter", "BottomCenter" ], 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>								
    -				
    -<li>Define an Endpoint that is a 30px blue dot, creates Connections that are 20px wide straight lines, is both a source and target of new Connections,
    -				has a 'scope' of 'blueline', and has an event handler that pops up an alert (note: the event handler name means this example is jQuery - MooTools
    -				and YUI3 use different event handler names):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{fillStyle:'blue'},
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ drop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -			<li>Same example as before, but this is for MooTools, and the Endpoint can support up to 5 connections (the default is 1):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{ fillStyle:'blue' },
    -	maxConnections:5,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ onDrop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Same example again, but maxConnections being set to -1 means that the Endpoint has no maximum limit of Connections:
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", {radius:30} ],
    -	style:{ fillStyle:'blue' },
    -	maxConnections:-1,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ onDrop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Same example again, but for YUI3.  Note the drop callback is "drop:hit":
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{fillStyle:'blue'},
    -	maxConnections:-1,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ "drop:hit":function(e, ui) { alert('drop!'); } }	 	
    -}; 
    -</pre>
    -</div>				 				
    -				</li>				
    -				
    -	<li>Assign a UUID to the endpoint options created above, and add as Endpoints to "window1" and "window2":
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("window1", { uuid:"abcdefg" }, endpointOptions );
    -jsPlumb.addEndpoint("window2", { uuid:"hijklmn" }, endpointOptions );
    -</pre>
    -</div>	
    -	</li>
    -	
    -	<li>Connect the two Endpoints we just registered on "window1" and "window2":
    -<div class="code">
    -<pre>
    -jsPlumb.connect({uuids:["abcdefg", "hijklmn"]});
    -</pre>
    -</div>	
    -	</li>
    -	
    -	<li>Create a source Endpoint, register it on some element, then make some other element a Connection target:
    -<div class="code">
    -<pre>
    -var sourceEndpoint = { isSource:true, endpoint:[ "Dot", { radius:50 } ] };
    -var targetEndpoint = { endpoint:[ "Rectangle", { width:10, height:10 } ] };
    -jsPlumb.addEndpoint( "window1", sourceEndpoint );
    -jsPlumb.makeTarget( "window2", targetEndpoint );
    -</pre>
    -</div>	
    -Notice that the endpoint definition we use on the target window does not include the "isTarget:true" directive.  jsPlumb ignores that flag when
    -creating a connection using an element as the target; but if you then tried to drag another connection to the Endpoint that was created, you would
    -not be able to.  To permit that, you would set 'isTarget:true' on the targetEndpoint options defined above.
    -	
    -	</li>
    -				
    -			</ul>
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="utilityFunctions">Utility Functions</a></h3>
    -			<ul>
    -				<li>Detach window5 from all connections
    -					<div class="code">
    -						<pre>jsPlumb.detachAll("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Hide all window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.hide("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Hide all window5's connections endpoints
    -					<div class="code">
    -						<pre>jsPlumb.hide("window5", true);</pre>
    -					</div>
    -				</li>
    -				<li>Show all window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.show("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Show all window5's connections and endpoints.  Note that in the case that you call jsPlumb.show with two arguments, jsPlumb
    -				will also not make a connection visible if it determines that the other endpoint in the connection is not visible.
    -					<div class="code">
    -						<pre>jsPlumb.show("window5", hide);</pre>
    -					</div>
    -				</li>				
    -				<li>Toggle the visibility of window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.toggle("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of all of window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.repaint("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of all of window5, window6 and window11's connections
    -					<div class="code">
    -						<pre>jsPlumb.repaint( [ "window5", "window6", "window11" ] );</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of every connection
    -					<div class="code">
    -						<pre>jsPlumb.repaintEverything();</pre>
    -					</div>
    -				</li>
    -				<li>Detach every connection
    -					<div class="code">
    -						<pre>jsPlumb.detachEverything();</pre>
    -					</div>
    -				</li>
    -				<li>Remove the given Endpoint from element "window1", deleting its Connections. 
    -					<div class="code">
    -						<pre>jsPlumb.removeEndpoint("window1", someEndpoint);</pre>
    -					</div>
    -				</li>
    -				<li>Remove all Endpoints for the element 'window1', deleting their Connections.
    -					<div class="code">
    -						<pre>jsPlumb.removeAllEndpoints("window1");</pre>
    -					</div>
    -				</li>	
    -				<li>Removes every Endpoint managed by this instance of jsPlumb, deleting all Connections.
    -				This is the same as jsPlumb.reset(), effectively, but it does not clear out the event listeners list. 
    -					<div class="code">
    -						<pre>jsPlumb.removeEveryEndpoint();</pre>
    -					</div>
    -				</li>
    -				<li>Deletes the given Endpoint and all its Connections. 
    -					<div class="code">
    -						<pre>jsPlumb.deleteEndpoint(endpoint);</pre>
    -					</div>
    -				</li>
    -				<li>Removes every endpoint, detaches every connection, and clears the event listeners list.  Returns jsPlumb instance to its initial state.  
    -					<div class="code">
    -						<pre>jsPlumb.reset();</pre>
    -					</div>
    -				</li>				
    -				<li>Set window1 to be not draggable, no matter what some jsPlumb command may request.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggable("window1", false);</pre>
    -					</div>
    -				</li>
    -				<li>Set window1 and window2 to be not draggable, no matter what some jsPlumb command may request.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggable(["window1","window2"], false);</pre>
    -					</div>
    -				</li>
    -				<li>Sets whether or not elements that are connected are draggable by default.
    -				The default for this is true.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggableByDefault(false);</pre>
    -					</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable("window1");</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 and window2 as draggable elements (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(["window1","window2"]);</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable("window1");</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (jQuery)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (MooTools)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($$(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (YIU3)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(Y.all(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (jQuery)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($("#window1"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (MooTools)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($("window1"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (YUI3)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(Y.one("window1"));</pre>
    -				</div>
    -				</li>
    -			</ul>
    -		</div>					
    -		
    -		<div class="section">
    -			<h3><a id="developingJsPlumb">Advanced Topics</a></h3>
    -			<h4><a id="which">Which files are which?</a></h4>
    -			In development, jsPlumb is broken up into seven scripts:
    -				<ul>
    -					<li>- jsPlumb-x.x.x.js
    -					<p>This is the main jsPlumb engine.</p>					
    -					</li>
    -					<li>- jsPlumb-defaults-x.x.x.js
    -					<p>This contains the default Anchor, Endpoint, Connector and Overlay implementations.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-canvas-x.x.x.js
    -					<p>This contains the HTML5 canvas render code.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-svg-x.x.x.js
    -					<p>This contains the SVG render code.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-vml-x.x.x.js
    -					<p>This contains the VML render code.</p>
    -					</li>
    -					<li>- jsPlumb-connectors-statemachine-x.x.x.js
    -					<p>This State Machine connector.</p>
    -					</li>
    -					<li>- &lt;LIBRARY_PREFIX&gt;.jsPlumb-x.x.x.js
    -					<p>This contains library-specific helper methods.  jsPlumb ships with three of these - one each for jQuery, MooTools and YUI3. See below for information on how to create a new library implementation.</p>
    -					</li>
    -					<li>- jsBezier-0.3-min.js
    -					<p>These are the Bezier curve functions; they are maintained in a separate project called <a href="http://code.google.com/p/jsbezier/">jsBezier</a></p>
    -					</li>
    -				</ul>
    -			These seven files are packaged together to form the scripts that people use, for example:
    -			<ul>
    -				<li>jquery.jsPlumb-1.3.4-all.js
    -				<p>Contains jsPlumb-1.3.4.js, jsPlumb-defaults-1.3.4.js, jsPlumb-renderers-canvas-1.3.4.js, jsPlumb-renderers-svg-1.3.4.js, jsPlumb-renderers-vml-1.3.4.js, jsPlumb-connectors-statemachine-1.3.4.js, jquery.jsPlumb-1.3.4.js and jsBezier-0.3-min.js</p>
    -				</li>
    -				<li>jquery.jsPlumb-1.3.4-all-min.js
    -				<p>A minified version of the script above (minified using the YUI Compressor)</p>
    -				</li>
    -			</ul>
    -			<h4><a id="pluggableLibrarySupport">Pluggable Library Support</a></h4>
    -		Out of the box, jsPlumb can be run on top of jQuery, MooTools or YUI3.  This is achieved by
    -		delegating several core methods - tasks such as finding an element by id, finding an element's
    -		position or dimensions, initialising a draggable, etc - to the library in question.
    -		<p>To develop one of these, your test page should include the first two scripts discussed above, and
    -		then your own script containing your library specific functionality.  The existing implementations
    -		may be documented well enough for you to create your own, but contact me if you need assistance.  If you do this, it would be
    -		great to share it with everyone...</p>   
    -		<p>There is a group of people working on an ExtJS adapter currently.  There is no schedule for when this will be completed though.</p>
    -
    -			<!-- h4><a id="customConnectors">Custom Connectors</a></h4>			
    -					
    -			You can provide your own connectors if you need to. A Connector consists of two functions, which work as a pair.  First a call is made to the <em>compute</em> function:
    -
    -			<div class="code">
    -<pre>
    -this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) {
    -	...
    -	return dimensions;
    -}
    -</pre>
    -			</div>
    -			which is expected to return a list that the <em>paint</em> function can make sense of.  The first four entries in the
    -			list <strong>must be</strong> the [x,y,width,height] values for the canvas that the connector will be drawn on; jsPlumb will
    -			use this information to size the canvas prior to calling the Connector's <em>paint</em> function.  Therefore it
    -			is the Connector's responsibility to ensure that the returned dimensions describe a large enough space for
    -			the line that will be drawn on it.</p><p>The next four elements <strong>must be</strong> the coordinates of the two endpoints of the line
    -			you are going to draw.</p><p>The remainder of the items in the returned list are arbitrary, and will
    -			vary between Connector implementations; this list is passed in to a Connector's <em>paint</em> function, so each
    -			implementation will put into the list whatever it needs to paint itself.  For instance, the straight line
    -			connector only needs the [x,y] location of each end of the line it will paint, and that is one of the required entries, so
    -			it does not have to do anything extra,  whereas the Bezier connector adds the location of the two control points.  Other types of
    -			Connectors will do whatever is appropriate for their particular situation.
    -			</p>
    -			This is the method signature for the <em>paint</em> function:
    -			<div class="code">
    -				<pre>this.paint = function(dimensions, ctx) { .. }</pre>
    -			</div>
    -
    -			here, the 'dimensions' argument to the 'paint' function is the return value of the 'compute' function.  The 'ctx' argument is the Canvas context; you
    -			will do all your drawing on this.
    -			</p>
    -			To change the connector from the default, specify it in your connect call:
    -			<div class="code">
    -				<pre>jsPlumb.connect({source:'someWindow', target:'otherWindow', connector:new jsPlumb.Connectors.Straight()});</pre>
    -			</div>
    -			note that you can use shorthand for connectors if you don't need to specify parameters to it:
    -			<div class="code">
    -				<pre>jsPlumb.connect({source:'someWindow', target:'otherWindow', connector:"Straight"});</pre>
    -			</div>
    -			This works in the same way as specifying Anchors as string does.
    -<p>
    -The section below this discusses Overlays, which allow you to decorate Connectors with
    -			things such as labels or arrows or whatever else you like. Overlays can only work with Connectors that implement a few helper functions.
    -			<h4>The concept of <strong>location</strong></h4>
    -			Before discussing the helper functions you first must be across the concept of 'location' as used by this mechanism.  The <em>location</em>
    -			on a connector can be a decimal value between 0 and 1 inclusive, which marks a proportionate amount of travel <em>along the path
    -			inscribed by the Connector</em>.  For a straight line connector this is a simple piece of maths, but for Bezier connectors it's a little
    -			bit more involved.
    -			<h4>Required Helper Methods</h4>
    -			<ul>
    -				<li><strong>pointOnPath(location)</strong> - returns an [x,y] point corresponding to the given location</li>
    -				<li><strong>pointAlongPathFrom(location, distance)</strong> - returns an [x,y] point corresponding to travelling 'distance' pixels along the connector from 'location'.</li>
    -				<li><strong>gradientAtPoint(location)</strong> - returns the connector's gradient at the given location.  For linear connectors such as Straight this is constant, but for Bezier connectors the gradient changes continually.</li>
    -				<li><strong>perpendicularToPathAt(location, distance, length)</strong> - returns a line that is perpendicular to (and centered on) the connector at 'distance' from the given location, with length 'length'.</li>
    -			</ul>
    -</p>
    -<h4><a id="customEndpoints">Custom Endpoints</a></h4>
    -<p>To create your own Endpoint implementation, you need to implement a single method:
    -				<div class="code"><pre>paint : function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { ... }</pre></div>
    -				The arguments to this method are as follows:
    -				<ul>
    -					<li>anchorPoint - [x,y] location of the anchor point on screen</li>
    -					<li>orientation - [x,y] hints for the general direction the anchor points to</li>
    -					<li>canvas - the canvas to draw into</li>
    -					<li>endpointStyle - Javascript object containing style directives as discussed above.  The contents of this are arbitrary, so if you write a new Endpoint that needs some extra settings, you can add them no hassle.</li>
    -					<li>connectorPaintStyle - the style being used to paint the associated Connector.</li>
    -				</ul>
    -			</p>
    -			<p>
    -				It is your responsibility to size and locate the canvas to suit your needs. jsPlumb provides the following
    -				helper method to assist you:
    -				<div class="code"><pre>jsPlumb.sizeCanvas(canvas, x, y, width, height);</pre></div>
    -				Allows you to locate the canvas on screen and to size it.
    -			</p>
    -<h4><a id="customOverlays">Custom Overlays</a></h4>
    -			Overlays can only work with Connectors that implement the methods <strong>pointOnPath</strong>, <strong>pointAlongPathFrom</strong>,
    -			<strong>gradientAtPoint</strong>, and <strong>perpendicularToPathAt</strong>.  These are discussed in more detail in the Custom Connectors section above.  Both of the Connectors
    -			that come with jsPlumb - Straight and Bezier -implement these methods; if you write a custom connector, or have written a custom connector, you will need to supply
    -			them.   
    -			<h4>Overlay Interface</h4>
    -			An Overlay is required to implement two methods in order to be usable by jsPlumb:
    -			<ul>
    -				<li><strong>computeMaxSize(connector, context)</strong> - returns an integer value indicating the larger of this overlay's width and height.  It is used by jsPlumb to ensure that the canvas is large enough to accomodate the overlay.  The examples below should help to clarify this.</li>
    -				<li><strong>draw(connector, ctx)</strong> - draws the overlay.  What happens in this method is up to the given implementation.</li>
    -			</ul>
    -			<h4>computeMaxSize methods</h4>
    -			This is the Arrow overlay's computeMaxSize method:
    -			<div class="code">
    -<pre>
    -this.computeMaxSize = function() { return width; }
    -</pre>			
    -			</div>
    -Here, <em>width</em> is a private member of Arrow that indicates the width of the arrow's tail.  So the Arrow overlay reports that figure as the width it needs.					
    -Contrast this with the Label Overlay's computeMaxSize method:
    -		<div class="code">
    -<pre>
    -this.computeMaxSize = function(connector, ctx) {
    -  	if (labelText) {
    -  		ctx.save();
    -        if (self.labelStyle.font) ctx.font = self.labelStyle.font;
    -         var t = ctx.measureText(labelText).width;			            
    -		// a fake text height measurement: use the width of upper case M
    -		var h = ctx.measureText("M").width;					
    -		labelPadding = self.labelStyle.padding || 0.25;
    -		labelWidth = t + (2 * t * labelPadding);
    -		labelHeight = h + (2 * h * labelPadding);
    -		ctx.restore();
    -		return Math.max(labelWidth, labelHeight);
    -  	}
    - 	return 0;
    - };
    -</pre>			
    -			</div>
    -The Label overlay has to use the context to determine how big it will be on screen.
    -
    -<h4>draw methods</h4>
    -To give you a taste for how you can interact with a connector, consider the first few lines of the Arrow overlay's draw method:
    -<div class="code">
    -<pre>
    -this.draw = function(connector, ctx) {
    -	// this is the arrow head position
    -	var hxy = connector.pointAlongPathFrom(self.loc, length / 2);		
    -	// this is the center of the tail
    -	var txy = connector.pointAlongPathFrom(self.loc, -length / 2), tx = txy[0], ty = txy[1];
    -	// this is the tail vector
    -	var tail = connector.perpendicularToPathAt(self.loc, -length / 2, width);
    -	
    -	...
    -</pre>
    -</div>
    -	<p>The first two calls to the connector use the <strong>pointAlongPathFrom</strong> method, which passes <em>self.loc</em> as the location of the
    -	point on the path, and <em>length / 2</em> as the number of pixels along the path to traverse.</p>
    -	<p><em>self.loc</em> is Arrow's internal member describing the location of the overlay, and it is a decimal (between 0 and 1 inclusive) that points to
    -	a location at some distance along the path inscribed by the connector.  So these first two calls get us [x,y] locations of points on 
    -	the connector that mark the head and tail points for the arrow.  
    -	</p>		
    -	<p>
    -	The <strong>connector.perpendicularToPathAt(self.loc, -length / 2, width)</strong> call returns a line description of a line that is perpendicular to, and centered on, the connector
    -	at "-length / 2" pixels from the given point, with the given width (width is an internal member of Arrow).  At this point, the Arrow object has the three main points it
    -	needs in order to draw itself.
    -	</p>
    -		</div-->
    -</div>
    -
    -</body>
    -
    -</html>
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.4/index.html b/demo/doc/archive/1.3.4/index.html
    deleted file mode 100644
    index 23e55d152..000000000
    --- a/demo/doc/archive/1.3.4/index.html
    +++ /dev/null
    @@ -1,78 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.4 documentation</title>
    -		<link rel="stylesheet" target="contentFrame" href="/mp.css"></link>
    -		<link rel="stylesheet" target="contentFrame" href="jsPlumbDoc.css"></link>
    -	</head>
    -	<body>
    -		<div class="index section">
    -			<h3>jsPlumb 1.3.4</h3>
    -			<ul>
    -				<li><a target="contentFrame" href="content.html#summary">Summary</a></li>
    -				<li><a target="contentFrame" href="content.html#changes">Changes since version 1.3.3</a></li>
    -				
    -				<li><h4>Setup</h4></li>
    -				<li><a target="contentFrame" href="content.html#imports">Required Imports</a></li>
    -				<li><a target="contentFrame" href="content.html#renderMode">Render Mode</a></li>
    -				<li><a target="contentFrame" href="content.html#initializing">Initializing jsPlumb</a></li>
    -				<li><a target="contentFrame" href="content.html#unload">Unloading jsPlumb</a></li>
    -				<li><a target="contentFrame" href="content.html#multipleInstances">Multiple jsPlumb Instances</a></li>
    -				<li><a target="contentFrame" href="content.html#zIndex">Z-index Considerations</a></li>
    -				<li><a target="contentFrame" href="content.html#containerDefault">Where does jsPlumb add elements?</a></li>
    -				
    -				<li><h4>Introduction</h4></li>
    -				<li><a target="contentFrame" href="content.html#jsPlumbBasics">Basic Concepts</a></li>
    -				<li><a target="contentFrame" href="content.html#anchors">Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#staticAnchors">Static Anchors</a></li>				
    -				<li><a target="contentFrame" href="content.html#dynamicAnchors">Dynamic Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#continuousAnchors">Continuous Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#definitions">Connector, Endpoint & Overlay Definitions</a></li>
    -				<li><a target="contentFrame" href="content.html#connectors">Connectors</a></li>
    -				<li><a target="contentFrame" href="content.html#endpoints">Endpoints</a></li>
    -				<li><a target="contentFrame" href="content.html#overlays">Overlays</a></li>
    -				<li><a target="contentFrame" href="content.html#defaults">Defaults</a></li>								
    -				
    -				<li><h4>Connections</h4></li>
    -				<li><a target="contentFrame" href="content.html#simpleConnections">Programmatic Connections</a></li>
    -				<li><a target="contentFrame" href="content.html#draggableConnections">Connections using Drag and Drop</a></li>
    -				<li><a target="contentFrame" href="content.html#dragOptions">Drag Options</a></li>
    -				<li><a target="contentFrame" href="content.html#dropOptions">Drop Options</a></li>
    -				<li><a target="contentFrame" href="content.html#dragAndDropScope">Drag and Drop Scope</a></li>
    -				
    -				<li><h4>Interceptors</h4></li>
    -				<li><a target="contentFrame" href="content.html#beforeDrag">Before Drag</a></li>
    -				<li><a target="contentFrame" href="content.html#beforeDrop">Before Drop</a></li>
    -				
    -				<li><h4>Appearance</h4></li>
    -				<li><a target="contentFrame" href="content.html#paintstyles">Paint Styles</a></li>
    -				<li><a target="contentFrame" href="content.html#hoverpaintstyles">Hover Paint Styles</a></li>
    -				<li><a target="contentFrame" href="content.html#gradients">Gradients</a></li>				
    -				
    -				<li><h4>Miscellaneous</h4></li>
    -				<li><a target="contentFrame" href="content.html#cssclasses">CSS Class Reference</a></li>
    -				<li><a target="contentFrame" href="content.html#animation">Animation</a></li>
    -				<li><a target="contentFrame" href="content.html#connectionInfo">Retrieving Connection Information</a></li>
    -				
    -				<li><h4>Events</h4></li>
    -				<li><a target="contentFrame" href="content.html#events">jsPlumb Events</a></li>
    -				<li><a target="contentFrame" href="content.html#connectionEvents">Connection Events</a></li>
    -				<li><a target="contentFrame" href="content.html#endpointEvents">Endpoint Events</a></li>
    -				<li><a target="contentFrame" href="content.html#overlayEvents">Overlay Events</a></li>
    -				
    -				<li><h4>Examples</h4></li>				
    -				<li><a target="contentFrame" href="content.html#examples">Programmatic API Examples</a></li>
    -				<li><a target="contentFrame" href="content.html#dragAndDropExamples">Draggable Connections Examples</a></li>
    -				<li><a target="contentFrame" href="content.html#utilityFunctions">Utility Functions</a></li>				
    -				
    -				<li><h4>Develop</h4></li>
    -				<li><a target="contentFrame" href="content.html#which">Which files are which?</a></li>
    -				<li><a target="contentFrame" href="content.html#pluggableLibrarySupport">Pluggable Library Support</a></li>
    -				<!-- li><a target="contentFrame" href="content.html#customConnectors">Custom Connectors</a></li>
    -				<li><a target="contentFrame" href="content.html#customEndpoints">Custom Endpoints</a></li>
    -				<li><a target="contentFrame" href="content.html#customOverlays">Custom Overlays</a></li-->
    -			</ul>
    -			<strong>This document refers to release 1.3.4 of jsPlumb.</strong>  
    -			<strong>8 Dec 2011</strong>
    -		</div>		
    -	</body>
    -</html>
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.4/jsPlumbDoc.css b/demo/doc/archive/1.3.4/jsPlumbDoc.css
    deleted file mode 100644
    index bbe50c8ee..000000000
    --- a/demo/doc/archive/1.3.4/jsPlumbDoc.css
    +++ /dev/null
    @@ -1,126 +0,0 @@
    -body { 
    -	background-color:white; font-size:90%;
    -	background-image:url(../img/dynamicAnchorBg.jpg); 
    -}
    -/* docs */
    -.code { background-color:#e4e5ef; 
    -border:1px solid black; margin:1em; padding:0.8em; 
    -overflow:auto;
    -font-family:courier; 
    -font-size:8pt;
    -}
    -.section { border-bottom: 1px dotted black; padding:1em;font-family:helvetica;background-color:white;color:#333;}
    -.menu { 
    -	position:fixed; 
    -	right:10em; top:1em;
    -	background-color:white;
    -	padding:0.3em;
    -	opacity:0.8;
    -	z-index:5000;
    -	filter:alpha(opacity=80);
    -		box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -a {text-decoration:none;color:#05f;font-weight:bold;font-family:helvetica;}
    -a:hover {text-decoration:underline;}
    -.window, .label { 
    -	cursor:pointer;
    -	box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -#explanation { 
    -	background-color:white;
    -	color:black;
    -	border:0.25em solid #ddd; 
    -	position:absolute;
    -	right:3.5em;
    -	top:5em;
    -	width:22em;
    -	font-size:100%;
    -	-moz-border-radius:1em;
    -	border-radius:1em;
    -	padding:1em;
    -	z-index:1000;
    -	opacity:0.9;
    -	filter:alpha(opacity=90); 
    -	font-family:helvetica;
    -		box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -.selected {
    -	color:rgb(189,11,11);
    -}
    -
    -
    -/*  these styles are what jsPlumb attaches to the various elements it creates.
    -._jsPlumb_connector {
    -border:1px solid blue;
    -}
    -._jsPlumb_endpoint {
    -border:1px solid green;
    -}
    -._jsPlumb_overlay {
    -border:1px solid yellow;
    -}
    -*/
    -
    -
    -.section li {
    -list-style-type:none;
    -}
    -
    -li.bullet {
    -	list-style-type:circle;
    -	}
    -
    -
    -.section {
    -padding-left:2em;
    -font-size:10pt;
    -}
    -.index.section {
    -padding-left:0;
    -}
    -
    -.index {
    -width:20em;
    -font-size:9pt;
    -float:left;
    -background-color:#e4eeee;
    -overflow:auto;
    -padding-left:0.5em;
    -}
    -
    -.index li {
    -list-style-type:none;
    -}
    -
    -.index ul, .index h3 {
    -padding-left:1.5em;
    -
    -}
    -
    -#demo {
    -width:80%;
    -height:50em;
    -margin-left:3em;
    -margin-top:3em;
    -position:relative;
    -}
    -
    -.sub {
    -margin-left:1em;
    -}
    -
    -.table td {
    -	text-align:center;
    -}
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.4/usage.html b/demo/doc/archive/1.3.4/usage.html
    deleted file mode 100644
    index 4241d6432..000000000
    --- a/demo/doc/archive/1.3.4/usage.html
    +++ /dev/null
    @@ -1,10 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.4 - documentation</title>		
    -	</head>
    -	
    -	<frameset cols="250,*">
    -  		<frame src="index.html" />
    -  		<frame src="content.html" name="contentFrame" />
    -	</frameset>	
    -</html>
    diff --git a/demo/doc/archive/1.3.5/content.html b/demo/doc/archive/1.3.5/content.html
    deleted file mode 100644
    index 85a55e547..000000000
    --- a/demo/doc/archive/1.3.5/content.html
    +++ /dev/null
    @@ -1,2833 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.5 - documentation</title>
    -		<link rel="stylesheet" href="/mp.css">
    -		<link rel="stylesheet" href="jsPlumbDoc.css">
    -	</head>
    -	<body>
    -
    -<div class="menu">
    -	<a href="http://jsplumb.org" class="mplink" target="_top">view demo</a>
    -	&nbsp;|&nbsp;<a href="mailto:simon.porritt@gmail.com" class="mplink">contact me</a>
    -	&nbsp;|&nbsp;<a href="http://jsplumb.tumblr.com" class="mplink">jsPlumb on Tumblr</a>	
    -</div>
    -
    -<!--
    -GWT
    -
    -http://fishfilosophy.blogspot.com/2011/12/how-to-connect-gwt-widgets-using.html?spref=tw
    -
    --->
    -
    -<div class="content">
    -		<div class="section">
    -			<h3><a id="summary">Summary</a></h3>
    -			jsPlumb allows you to connect elements on the screen using SVG, Canvas or VML, depending on the capabilities of the browser.
    -			<p>
    -				It can be used with jQuery, MooTools or YUI3 (or another library of your choice if you feel like implementing an adapter for it).
    -			</p>  
    -
    -			<h5>Browser Compatibility</h5>
    -			<p>jsPlumb 1.3.5 has been tested on the following browsers:
    -				<ul>
    -					<li class="bullet">IE 6 on Windows XP</li>
    -					<li class="bullet">IE 7 on Windows XP</li>
    -					<li class="bullet">IE 8 on Windows XP</li>
    -					<li class="bullet">Firefox 3.5.8 on Windows XP</li>
    -					
    -					<li class="bullet">IE 9 on Windows 7</li>
    -					<li class="bullet">Chrome 12 on Windows 7</li>
    -					<li class="bullet">Firefox 3.5.8 on Windows 7</li>
    -					
    -					<li class="bullet">Firefox 3.6.3 on Ubuntu 10.04</li>
    -					<li class="bullet">Chrome on Ubuntu 11.04</li>
    -					
    -					<li class="bullet">Safari 4 on Mac Leopard</li>
    -					<li class="bullet">Chrome on Mac Leopard</li>					
    -					<li class="bullet">Safari 4 on Windows Vista</li>
    -					<li class="bullet">Safari 5.0.5 on Windows 7</li>
    -					
    -					<li class="bullet">Opera 10.54 on Windows XP</li>
    -				</ul>
    -			</p>
    -		</div>
    -		
    -		<div class="section">		
    -			<h3><a id="changes">Changes between 1.3.5 and 1.3.4</a></h3>
    -			<p>
    -			1.3.5 is mostly a performance and bugfix release, with a couple of new features.  For that reason, I've left the list of changes between 1.3.4 and 1.3.3 in this file.
    -			</p>
    -			<h5>Backwards Compatibility</h5>
    -			<ul>
    -				<li class="bullet">The <strong>dynamicAnchors</strong> parameter as an argument to addEndpoint or connect calls has been removed.  Just use <strong>anchor</strong> or <strong>anchors</strong> instead; jsPlumb will figure out you need Dynamic Anchors if you supply an array of possible
    -				locations</li>
    -				<li class="bullet"><strong>jsPlumb.Defaults.Overlays</strong> is now applied to both Connections and Endpoints.  This is because Endpoints now support Label overlays (see below).  You should update your code to set <strong>jsPlumb.Defaults.ConnectionOverlays</strong> - unless you want your overlays to appear on Connections and Endpoints.</li>
    -			</ul>
    -			<h5>New Features</h5>
    -			The following new features have been added:
    -			<ul>
    -				<li class="bullet">support for the <strong>contextmenu</strong> event has been added to the jsPlumb object, Connections and Endpoints. See <a href="#events">Events</a> for more information.</li>
    -				<li class="bullet">A new method, <strong>setSuspendDrawing</strong>, was added to jsPlumb.  This allows you to instruct jsPlumb to not paint anything until you call this method again; it is useful for situations where you are adding a lot of connections at once but don't need to see them painted until everything has been added</li>
    -				<li class="bullet">A new method, <strong>setHoverSuspended</strong>, was added to jsPlumb.  This method allows you to instruct jsPlumb to not post hover events until you tell it to again.  It can be useful when you're working with a lot of connections, because the occasional stray mouse event can creep through and cause your UI to flash like a 1970s disco.</li>
    -				<li class="bullet"><strong>isHoverSuspended</strong> was also added.</li>
    -				<li class="bullet"><strong>setLabel</strong> was re-implemented, properly this time.  This is available on both Connections and Endpoints.</li>
    -				<li class="bullet"><strong>getLabel</strong> was also re-implemented, correctly.</li>
    -				<li class="bullet"><strong>labelLocation</strong> was added as a parameter on the setLabel function, for both Connections and Endpoints.  For Connections the allowed values are a decimal between 0 and 1 inclusive, for Endpoints the allowed value is an array of two of these decimals, one for each axis</li>
    -				<li class="bullet">Support for label overlays on Endpoints was added</li>
    -				<li class="bullet"><strong>jsPlumb.Defaults.ConnectionOverlays</strong> default setting added</li>
    -				<li class="bullet"><strong>jsPlumb.Defaults.EndpointOverlays</strong> default setting added</li>
    -				<li class="bullet">Flowchart connectors now support the loopback case (in fact they received an overhaul in 1.3.5 and work better in all situations now)</li>
    -			</ul>
    -			<h5>Issues Resolved</h5>
    -			<ul>
    -				<li class="bullet">
    -					<strong>169</strong> - continuous anchors not tracking when existing connection dragged
    -				</li>
    -				<li class="bullet">
    -					<strong>173</strong> - getCachedData method was effectively being bypassed				
    -				</li>
    -				<li class="bullet">
    -					<strong>187</strong> - flowchart connectors not symmetric
    -				</li>
    -				<li class="bullet">
    -					<strong>187</strong> - dynamic anchors broken for Endpoints
    -				</li>
    -				<li class="bullet">
    -					<strong>189</strong> - Endpoints draggable when they should not be
    -				</li>
    -				<li class="bullet">
    -					<strong>190</strong> - getInstance regressions
    -				</li>
    -				<li class="bullet">
    -					<strong>191</strong> - offsets calculated wrongly in certain situations
    -				</li>
    -
    -			</ul>
    -
    -
    -			<h3>Changes between version 1.3.4 and 1.3.3</h3>
    -			<p>
    -			Version 1.3.4 was a fairly significant release, containing several bugfixes relating to positioning
    -			when elements with scrollbars or 'overflow:auto' are involved, a new type of connector, "Continuous"
    -			anchors, which are like Dynamic anchors on steroids, beforeDrag/beforeDrop interceptors, and a bunch
    -			of other bits and pieces such as the ability to make an entire div a connection source or target.
    -			</p>
    -			<h5>Backwards Compatibility</h5>
    -			<ul>
    -			<li class="bullet">Starting with this release, jsPlumb <strong>no longer automatically initialises any elements to be
    -			draggable</strong>.
    -			<p>I thought about this long and hard and decided that it was too invasive for jsPlumb to assume that
    -			you wanted every connected element to be draggable.  There were other complications, too, such as the
    -			fact that dragging was not initialised until you had at least one connection to an element - you could
    -			start out with a UI in which some elements were draggable, and others were not, and there did not seem,
    -			to the user, to be a decent reason for that.</p>
    -			<p>You should use the <strong>jsPlumb.draggable(some selector)</strong> method to initialise elements
    -			as draggable now.  It is still imperative that you use this method and not your underlying library's
    -			drag method, because otherwise jsPlumb will not know what's going on.  Valid values for that
    -			selector are whatever your underlying library can support: $("someSelector") for jQuery, $$("someSelector") for MooTools, and
    -			Y.all("someSelector") for YUI3.</p>
    -			</li>
    -			<li class="bullet"><strong>The default renderer is now SVG</strong>, where supported, and VML elsewhere.
    -			<p>I'm of the opinion that SVG is a better option for almost every application using jsPlumb: it
    -			uses far less memory, has native mouse event support so is faster, and can be styled using CSS.</p>
    -			<p>If you really want to use Canvas, call <strong>jsPlumb.setRenderMode(jsPlumb.CANVAS);</strong>.</p>
    -                <p>Be aware that there is a jQuery bug with SVG in IE9 for all jQuery version from 1.6.x to 1.7.2: the hover events
    -                are not properly reported.
    -            </p>
    -			</li>
    -            <li class="bullet">The <strong>detachEverything</strong> method, which was deprecated in 1.3.2, has been removed from jsPlumb. Use detachEveryConnection instead.
    -                It does the same thing.</li>
    -            <li class="bullet">The <strong>detachAll</strong> method, which was deprecated in 1.3.2, has been removed. Use detachAllConnections instead. It does the same thing.
    -            </li>
    -                <li class="bullet">The <strong>labelStyle</strong> parameter has been removed from the Label overlay.  You should use the <strong>cssClass</strong> parameter to set a class, and then control the appearance in your stylesheets.</li>
    -			</ul>			
    -			<h5>New Features</h5>
    -				<ul>
    -					<li class="bullet">
    -						<p>StateMachine Connectors<p>
    -						These connectors look like the state machine connectors you might have seen in 
    -						software like GraphViz.  Support for "loopback" connections is included.
    -					</li>
    -					<li class="bullet">
    -						<p>Continuous Anchors<p>
    -						Continuous anchors are anchors whose position is dynamically dependent on every connection
    -						that is attached to some element.  If you're familiar with the existing Dynamic 
    -						anchors in jsPlumb, these operate in pretty much the same way, with the exception that
    -						you do not need to nominate any specific locations yourself.  They choose their position
    -						based on an element's orientation to the other element in a connection, and everything
    -						is ordered to keep the number of crossed connections to a minimum.  
    -						<p>For more information, see the <a href="#anchors">anchors</a> section.</p>
    -					</li>
    -					<li class="bullet">
    -						<p>beforeDrag/beforeDrop interceptors<p>
    -						These interceptors allow you to decide whether or not to allow a new connection to be
    -						dragged or dropped before jsPlumb goes ahead with the action. They are documented in 
    -						full in the <a href="#interceptors">interceptors</a> section of the documentation.
    -					</li>
    -					<li class="bullet">
    -						<p>makeSource/makeTarget methods</p>
    -						makeSource and makeTarget provide a means for you to nominate some entire element as
    -						a connection source or target.  There are many applications in which this functionality
    -						makes more sense than having to register a dedicated Endpoint.  Read more about these
    -						in the <a href="#connections">connections</a> section below.
    -					</li>
    -					<li class="bullet"><p>'transparent' is now supported as a fillStyle or strokeStyle.</p></li>
    -					<li class="bullet"><p>Endpoint and Connector definitions now support a <strong>hoverClass</strong> parameter.
    -					This is the hover equivalent of the existing <strong>cssClass</strong> parameter.</p>
    -					</li>
    -                    <li class="bullet"><p>The Image Endpoint now has an <strong>onload</strong> parameter, allowing you to supply a callback to run once the image has loaded</p></li>
    -                    <li class="bullet"><p>The Image Endpoint now has a <strong>setImage</strong> method, which also takes an optional onload parameter.  See the API docs.</p></li>
    -                    <li class="bullet"><p>The Image Endpoint now has optional <strong>width</strong>  and <strong>height</strong> parameters, allowing you to override the displayed size of the endpoint.</p></li>
    -                    <li class="bullet"><p>You can supply a <strong>parameters</strong> object to connect, addEndpoint, makeSource and makeTarget calls.  This specifies an object literal of parameter values which will be present
    -                    on resulting connections, available via the <strong>getParameter(String)</strong>method</p></li>
    -				</ul>			
    -			
    -			<h5>Bugfixes for which there were not issues raised</h5>
    -				<ul>
    -					<li class="bullet">A mouse event bug with the SVG renderer was fixed; it was preventing mouse events from
    -					reaching SVG elements that were underneath other SVG elements</li>
    -				</ul>
    -			
    -			<h5>Issues Resolved</h5>
    -			<ul>
    -				<!--li><strong>154</strong> - reinstate the 'tooltip' and 'connectorTooltip' parameters</li-->
    -                <li><strong>159</strong> - do not add methods to the Array prototype</li>
    -                <li><strong>158</strong> - use enclosing table element if an Endpoint is added to a table cell.</li>
    -                <li><strong>150</strong> - hover over one Endpoint does not send the event to the other Endpoint in a Connection</li>
    -				<li><strong>145</strong> - add getLabel/setLabel methods to Overlays</li>
    -				<li><strong>142</strong> - document jsPlumb.connectionDetached event properly</li>				
    -				<li><strong>139</strong> - duplicate ids are sometimes given to Endpoints and Connections</li>
    -				<li><strong>132</strong> - moving a Label Overlay does not remove its associated element</li>
    -				<li><strong>130</strong> - positioning issues when in div with overflow:scroll</li>
    -			</ul>						
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="init">Setup</a></h3>
    -
    -            <h4><a id="imports">Required Imports</a></h4>
    -			<h4>jQuery</h4>
    -			    <ul>
    -				    <li class="bullet">jQuery 1.3.x or higher.</li>
    -				    <li class="bullet">jQuery UI 1.7.x or 1.8.x (if you wish to support drag and drop).</li>
    -			    </ul>
    -			    <div class="code">
    -<pre>&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/jquery-ui.min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/jquery.jsPlumb-1.3.5-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			<h4>MooTools</h4>
    -				<ul>
    -					<li class="bullet">MooTools Core 1.2.4 or higher. jsPlumb has been tested with 1.2.4 and 1.3.3.</li>
    -					<li class="bullet">Drag.Move from MooTools More 1.2.4.4 or higher (if you wish to support drag and drop).</li>
    -				</ul>
    -			<div class="code">
    -			<pre>&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/mootools/1.3.3/mootools-yui-compressed.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO_MOO_TOOLS_MORE_1_3_2_1"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/mootools.jsPlumb-1.3.5-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			<h4>YUI3</h4>
    -			<ul>
    -				<li class="bullet">YUI 3.3.x.  jsPlumb has been tested on 3.3.0 only; it is possible other 3.x.x versions will work.</li>
    -			</ul>
    -			<div class="code">
    -			<pre>&lt;script type="text/javascript" src="http://yui.yahooapis.com/3.3.0/build/simpleyui/simpleyui-min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/yui.jsPlumb-1.3.5-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			
    -			<h4><a id="renderMode">Render Mode</a></h4>
    -			jsPlumb can use SVG, HTML5 Canvas elements or VML to render the various objects it adds to the display.  Most modern browsers support
    -			Canvas and SVG; IE&lt;9 browsers support neither.
    -			<p>
    -				By default, jsPlumb attempts to use <strong>jsPlumb.SVG</strong> as the render mode (prior to 1.3.4, the default renderer was Canvas), falling back to <strong>jsPlumb.VML</strong> if the 
    -				browser is IE&lt;9.  You can change the render mode by making this call:  
    -			</p>
    -			<div class="code">
    -<pre>jsPlumb.setRenderMode(jsPlumb.CANVAS);</pre>			
    -			</div>
    -			if, for some reason, you set the render mode to be <strong>jsPlumb.VML</strong> but you're in any browser other than IE&lt;9, jsPlumb
    -			will use SVG.
    -			<p>
    -			The <strong>jsPlumb.setRenderMode</strong> method returns you the render mode that jsPlumb actually ended up setting.  Valid values for setRenderMode are:
    -			</p>
    -			<ul>
    -				<li class="bullet">jsPlumb.CANVAS</li>
    -				<li class="bullet">jsPlumb.SVG</li>
    -				<li class="bullet">jsPlumb.VML</li>
    -			</ul>			
    -			<h5>Performance Considerations</h5>
    -			<p>
    -			SVG uses less memory than Canvas and has support for mouse events built in, so I recommend using
    -			SVG unless you have a really good reason for wanting Canvas.
    -			</p>
    -			
    -			<h4><a id="initializing">Initializing jsPlumb</a></h4>
    -			You should not start making calls to jsPlumb until the DOM has been initialized - perhaps no surprises there.  With YUI, though, the asynchronous
    -			nature of the script loading process means that you are not guaranteed that jsPlumb is ready as soon as the DOM is.  To handle this,
    -			you should bind to the "ready" event on jsPlumb (or the instance of jsPlumb you are working with):
    -			<div class="code">
    -			<pre>jsPlumb.bind("ready", function() {
    -...	
    -		
    -// your jsPlumb related init code goes here
    -
    -...
    -
    -});						
    -			</pre>
    -			</div>
    -			There's a helper method that can save you approximately 9 (count them!) characters:
    -			<div class="code">
    -			<pre>jsPlumb.ready(function() {
    -...	
    -		
    -// your jsPlumb related init code goes here
    -
    -...
    -
    -});						
    -			</pre>
    -			</div>
    -			<p>
    -			For jQuery and MooTools you do not actually need to do this; by the time the DOM ready event is fired in each of those libraries you can be sure 
    -			all the JS we need has been loaded.  But in terms of explicitly calling out that you are waiting for jsPlumb, it seems like a good practice to bind to the "ready" event.
    -			</p>
    -			<p>
    -			If you bind to the "ready" event after jsPlumb has already been initialized, your callback will be executed immediately.
    -			</p>			
    -			<h4><a id="unload">Unloading jsPlumb</a></h4>
    -			jsPlumb offers a method you can call when your page is unloading.  You should do this to insure
    -			against memory leaks.  You configure it like this:
    -				<div class="code">
    -<pre>
    -&lt;body onunload="jsPlumb.unload();"&gt;
    -
    -...
    -
    -&lt;/body&gt;
    -</pre>
    -				</div>
    -<h4><a id="multipleInstances">Multiple jsPlumb instances</a></h4>
    -			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 <strong>getInstance</strong> method, for example:
    -<div class="code">
    -<pre>
    -var firstInstance = jsPlumb.getInstance();
    -</pre>
    -</div>
    -The variable 'firstInstance' can now be treated exactly as you would treat the 'jsPlumb' variable - you can set
    -defaults, call the connect method, whatever:
    -<div class="code">
    -<pre>
    -firstInstance.Defaults.Connector = [ "Bezier", { curviness: 150 } ];
    -firstInstance.Defaults.Anchors = [ "TopCenter", "BottomCenter" ];
    -
    -firstInstance.connect({source:"element1", target:"element2", scope:"someScope" });
    -</pre>
    -</div>
    -
    -<strong>getInstance</strong> optionally takes an object that provides the defaults:
    -<div class="code">
    -<pre>
    -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" });
    -</pre>
    -</div>
    -
    -		<h4><a id="zIndex">Z-index Considerations</a></h4>
    -		<p>
    -			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. 
    -		</p>  
    -		<p>
    -			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).  			
    -		</p>
    -		<p>
    -			To help you organise z-indices correctly, jsPlumb adds a CSS class to each type of
    -			element it adds. They are as follows:			
    -		</p>
    -		<table style="color:black;width:70%;font-size:90%;">
    -			<tr><td><strong>type of element</strong></td><td><strong>class added</strong></td></tr>
    -			<tr><td>Endpoint</td><td>_jsPlumb_endpoint</td></tr>
    -			<tr><td>Connector</td><td>_jsPlumb_connector</td></tr>
    -			<tr><td>Overlay</td><td>_jsPlumb_overlay</td></tr>
    -		</table>
    -
    -		<h4><a id="containerDefault">Where does jsPlumb add elements?</a></h4>
    -		<p>
    -			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.
    -		</p>
    -		<p>
    -			Prior to 1.3.0, jsPlumb added everything to the end of the <em>body</em> element. This
    -			has the advantage of being the most flexible arrangement in terms of supporting what
    -			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!   
    -		</p>
    -		<p>
    -			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 (<strong>not</strong> 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.
    -		</p>
    -		
    -		<p>
    -			In general, the default behaviour means that you cannot do things like this:
    -		</p>
    -		<div class="code">
    -<pre>
    -&lt;body&gt;
    -	&lt;div id="container0"&gt;
    -		&lt;div id="node0"&gt;&lt;/div&gt;
    -	&lt;/div&gt;
    -	&lt;div id="container1"&gt;
    -		&lt;div id="node1"&gt;&lt;/div&gt;
    -	&lt;/div&gt;
    -&lt;/body&gt;
    -
    -&lt;script type="text/javascript"&gt;
    -
    -	var e0 = jsPlumb.addEndpoint("node0"),
    -	e1 = jsPlumb.addEndpoint("node1");
    -
    -	jsPlumb.connect({ source:e0, target:e1 });
    -&lt;/script&gt;
    -</pre>		
    -		</div>
    -<p>
    -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):  
    -</p>
    -		<div class="code">
    -<pre>
    -&lt;body&gt;
    -	&lt;div id="container0"&gt;
    -	&lt;/div&gt;
    -	&lt;div id="container1"&gt;
    -	&lt;/div&gt;
    -&lt;/body&gt;
    -
    -&lt;script type="text/javascript"&gt;
    -
    -	var e0 = jsPlumb.addEndpoint("container0"),
    -	e1 = jsPlumb.addEndpoint("container1");
    -
    -	jsPlumb.connect({ source:e0, target:e1 });
    -&lt;/script&gt;
    -</pre>		
    -		</div>
    -	now 'e0' and 'e1' have the same parent - the <em>body</em> element. 
    -	
    -	<p>Remember that you can use the <strong>anchor</strong> parameter to an <strong>addEndpoint</strong>
    -	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. 
    -	</p>									
    -	<p><strong>Note regarding the drawEndpoints option on jsPlumb.connect</strong>: 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). 
    -	</p>
    -	
    -	<h5>Overriding the default behaviour</h5>
    -	<p>
    -		From version 1.3.3, the <strong>container</strong> 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 <strong>jsPlumb.Defaults.Container</strong> property,
    -		or you can set a <strong>container</strong> parameter on calls to <strong>addEndpoint</strong> or <strong>connect</strong> (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:
    -	</p>
    -		
    -		Set a container to use as the default container, using a jQuery selector (you can supply MooTools/YUI selectors if that's the library you're using),
    -		and then add an Endpoint.  The canvas (or SVG/VML element) created will be a child of the document body:
    -		<div class="code">
    -<pre>
    -jsPlumb.Defaults.Container = $("body");
    -...
    -jsPlumb.addEndpoint(someDiv, { endpoint options });
    -</pre>		
    -		</div>
    -		
    -			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":
    -		<div class="code">
    -<pre>
    -jsPlumb.Defaults.Container = "containerId";
    -...
    -jsPlumb.connect({ source:someDiv, target:someOtherDiv });
    -</pre>		
    -		</div>
    -		
    -		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":
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({ source:someDiv, target:someOtherDiv, container:$("#containerId") });
    -</pre>		
    -		</div>
    -				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":
    -		<div class="code">
    -<pre>
    -jsPlumb.addEndpoint(someDiv, { ..., container:"containerId" });
    -</pre>		
    -		</div>
    -		
    -		</div>
    -				
    -		<div class="section">
    -			<h3><a id="jsPlumbBasics">Basic Concepts</a></h3>
    -	
    -			jsPlumb is all about connecting things together, so the core abstraction in jsPlumb is the <strong>Connection</strong> object, which is itself
    -			broken down into these four concepts:
    -
    -			<ul>
    -				<li><strong>Anchor</strong> - a location, relative to an element's origin, at which an Endpoint can exist. You do not create these yourself; you 
    -				supply hints to the various jsPlumb functions, which create them as needed.  They have no visual representation; they are a logical position only. 
    -				Anchors can be referenced by name, for the Anchors that jsPlumb ships with, or a four element array describing [ x, y, x orientation, y orientation ]. 
    -				See the <a href="#anchors">anchors</a> section for more detail.<br/><br/></li>
    -				<li><strong>Endpoint</strong> - the visual representation of one end of a Connection.  You can create and attach these to elements 
    -				yourself, which you are required to do to support drag and drop, or have jsPlumb create them when creating a Connection 
    -				programmatically using jsPlumb.connect(...).  You can also join two Endpoints programmatically, by passing them as arguments to 
    -				jsPlumb.connect(...).<br/><br/></li>				
    -				<li><strong>Connector</strong> - the visual representation of the line connecting two elements in the page.  jsPlumb has four types of these 
    -				available as defaults - a Bezier curve, a straight line, 'flowchart' connectors and 'state machine connectors. You do not interact with Connectors; 
    -				you just specify definitions of them when you need to - see <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions</a> <br/><br/></li>
    -				<li><strong>Overlay</strong> - a UI component that is used to decorate a Connector, such as a Label, Arrow, etc.<br/><br/></li>
    -			</ul>
    -						
    -			<p>One Connection is made up of two Endpoints, a Connector, and zero or more Overlays working together to join two elements.</p>
    -			<p>jsPlumb's public API exposes only Connection and Endpoint, handling the creation and configuration of everything else internally.
    -			But you still need to be across the concepts encapsulated by Anchor, Connector and Overlay.
    -			</p>
    -			</div>
    -			
    -			<div class="section">
    -			<h3><a id="anchors">Anchors</a></h3>			
    -	
    -					An Anchor models the notion of where on an element a Connector should connect.  There are three main types of Anchors:
    -					<ul>
    -						<li class="bullet">'Static' anchors - these are fixed to some point on an element and do not move.  They can be specified using a string to
    -						identify one of the defaults that jsPlumb ships with, or an array describing the location (see below)</li>
    -						<li class="bullet">'Dynamic' anchors - these are lists of Static anchors from which jsPlumb picks the most appropriate one each time a Connection
    -						is painted.  The algorithm used to determine the most appropriate anchor picks the one that is closest to the center of the other element in
    -						the Connection. A future version of jsPlumb might support a pluggable algorithm to make this decision.</li>
    -						<li class="bullet">'Continuous' anchors - These anchors, introduced in 1.3.4, are not fixed to any specific loction; they are assigned to one of
    -						the four faces of an element depending on that element's orientation to the other element in the associated Connection.  Continuous anchors are
    -						slightly more computationally intensive than Static or Dynamic anchors because jsPlumb is required to calculate the position of every Connection
    -						during a paint cycle, rather than just Connections belonging to the element in motion.  These anchors were designed for use with the StateMachine
    -						connector that was also introduced in 1.3.4, but will work with any combination of Connector and Endpoint.</li> 
    -					</ul>  
    -					
    -			<h4><a id="staticAnchors">Static Anchors</a></h4>		
    -			jsPlumb has nine default anchor locations you can use to specify where the Connectors connect to elements: these are the four corners of an element,
    -			the center of the element, and the midpoint of each edge of the element:  
    -			<ul>
    -				<li class="bullet">TopCenter</li>
    -				<li class="bullet">TopRight</li>
    -				<li class="bullet">RightMiddle</li>
    -				<li class="bullet">BottomRight</li>
    -				<li class="bullet">BottomCenter</li>
    -				<li class="bullet">BottomLeft</li>
    -				<li class="bullet">LeftMiddle</li>
    -				<li class="bullet">TopLeft</li>
    -			</ul>
    -			
    -			Each of these string representations is just a wrapper around the underlying array-based syntax  [x, y, dx, dy], where x and y are
    -coordinates in the interval [0,1] specifying the position of the
    -anchor, and dx and dy are coordinates in the interval [-1, 1]
    -specifying the orientation of the curve incident to the anchor. For
    -example, [0, 0.5, -1, 0] defines a "LeftCenter" anchor with a
    -connector curve that emanates leftward from the anchor. Similarly,
    -[0.5, 0, 0, -1] defines a "CenterTop" anchor with a connector curve
    -emanating upwards. 	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:"BottomCenter", ... }); 
    -</pre>
    -</div>
    -is identical to:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:[ 0.5, 1, 0, 1 ], ... }); 
    -</pre>
    -</div>			
    -
    -		<h4><a id="dynamicAnchors">Dynamic Anchors</a></h4>
    -	These are Anchors that can be positioned in one of a number of locations, choosing the one that is most appropriate each time something moves or is painted in the UI.
    -	<p>
    -	There is no special syntax to creating a DynamicAnchor; you just provide an array of individual Static Anchor specifications, eg:
    -	</p>
    -<div class="code">
    -<pre>
    -var dynamicAnchors = [ [ 0.2, 0, 0, -1 ],  [ 1, 0.2, 1, 0 ], 
    -			   [ 0.8, 1, 0, 1 ], [ 0, 0.8, -1, 0 ] ];
    -</pre>
    -</div>
    -<h4>	Default Dynamic Anchor</h4>
    -jsPlumb provides a dynamic anchor called "AutoDefault" that chooses from TopCenter, RightMiddle, BottomCenter and LeftMiddle:
    -<div class="code">
    -<pre>jsPlumb.connect({...., anchor:"AutoDefault", ... });</pre>
    -</div>
    -
    -<h4>	Location Selection</h4>
    -The initial implementation of the algorithm that decides which location to choose just calculates which location is closest to the center of the 
    -other element in the connection.  It is possible that future versions of jsPlumb could support more sophisticated choice algorithms, if the need arose.
    -<h4>Draggable Connections</h4>
    -Dynamic Anchors and Draggable Connections can interoperate: jsPlumb locks the position of a dynamic anchor when you start to drag a connection from it,
    -and unlocks it once the connection is either established or abandoned. At that point you may see the position of the dynamic anchor change, as jsPlumb
    -optimises the connection.  <p>You can see this behaviour in the <a href="../html/jquery/draggableConnectorsDemo.html" target="_blank">draggable connections</a> demonstration, when
    -you drag a connection from the blue endpoint on window 1 to the blue endpoint on window 3 - the connection is established and then window 1's blue
    -endpoint jumps down to a location that is closer to window 3.</p>
    -
    -
    -	<h4><a id="continuousAnchors">Continuous Anchors</a></h4>
    -	As discussed above, these are anchors whose positions are calculated by jsPlumb according to the orientation between elements in a Connection, and also how
    -	many other Continuous anchors happen to be sharing the element.  You specify that you want to use Continuous anchors using the string syntax you would use
    -	to specify one of the default Static Anchors, for example:
    -	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:someDiv,
    -	target:someOtherDiv,
    -	anchor:"Continuous"
    -});
    -</pre>
    -</div>
    -
    -Note in this example I specified only "anchor", rather than "anchors" - jsPlumb will use the same spec for both anchors.  But I could have said this:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:someDiv,
    -	target:someOtherDiv,
    -	anchors:["BottomCenter", "Continuous"]
    -});
    -</pre>
    -</div>	 	  
    -..which would have resulted in the source element having a Static Anchor at BottomCenter.  In practise, though, it seems the Continuous Anchors work best if both 
    -elements in a Connection are using them.
    -<p>Note also that Continuous Anchors can be specified on <strong>addEndpoint</strong> calls:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint(someDiv, {
    -	anchor:"Continuous",
    -	paintStyle:{ fillStyle:"red" }
    -});
    -</pre>
    -</div>	 	
    -... and in the jsPlumb defaults:
    -<div class="code">
    -<pre>
    -jsPlumb.Defaults.Anchor = "Continuous";
    -</pre>
    -</div>  
    -					</div>		
    -												
    -<div class="section">
    -		<h4><a id="definitions">Connector, Endpoint &amp; Overlay Definitions</a></h4>
    -		Before we discuss Connector, Endpoints and Overlays, a quick word on definitions:  whenever you need to define a Connector, Endpoint or Overlay, you must use a "definition" of it, rather than constructing one
    -		directly.  This definition can be either a string that nominates the artifact you want to create:
    -		<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:"Rectangle"
    -});
    -</pre>
    -		</div>
    -		or an array consisting of both the artifact's name and the arguments you want to pass to its constructor:
    -		<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Rectangle", { cssClass:"myEndpoint", width:30, height:10 } ]
    -});
    -</pre>
    -		</div>
    -		there is also a three-argument method that allows you to specify two sets of parameters, which jsPlumb will merge together for you. The idea
    -		behind this is that you will often want to define common characteristics somewhere and reuse them across a bunch of different calls:
    -		<div class="code">
    -<pre>var common = {
    -	cssClass	:	"myCssClass",
    -	hoverClass	:	"myHoverClass"
    -};
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Rectangle", { width:30, height:10 }, common ]
    -});
    -</pre>
    -		</div>
    -	This syntax is supported for all Endpoint, Connector and Overlay definitions.  Here's an example using definitions for all three:	
    -		<div class="code">
    -<pre>var common = {
    -	cssClass:"myCssClass"
    -};
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Dot", { radius:5, hoverClass:"myEndpointHover" }, common ],
    -	connector:[ "Bezier", { curviness:100 }, common ],
    -	overlays: [
    -		[ "Arrow", { foldback:0.2 }, common ],
    -		[ "Label", { cssClass:"labelClass" } ]	
    -	]
    -});
    -</pre>
    -		</div>		
    -		
    -		The allowed constructor parameters are different for each artifact you create, but every artifact takes a single JS object as argument,
    -		with the parameters as [key,value] pairs in that object.  See the relevant sections on <a href="#connectors">Connectors</a>,
    -		<a href="#endpoints">Endpoints</a> and <a href="#overlays">Overlays for information on supported parameters.</a>
    -				
    -	</div>
    -
    -
    -		<div class="section">
    -			<h3><a id="connectors">Connectors</a></h3>
    -			<p>Connectors are the lines that actually join elements of the UI.  jsPlumb has four connector implementations - a straight line, a Bezier curve, 
    -			"flowchart", and "state machine".  The default connector is the Bezier curve.  </p>
    -			<p>You optionally specify a Connector by setting the 'connector' property on a call to jsPlumb.connect, jsPlumb.addEndpoint(s), jsPlumb.makeSource or
    -			jsPlumb.makeTarget. If you do not supply a value for 'connector', the default will be used (see <a href="#defaults">defaults</a>).</p>
    -			<p>You specify Connectors using the syntax described above in <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions.</a>Allowed 
    -			constructor values for each Connector type are described below:</p>
    -			
    -			<h4><a id="bezierConnector">Bezier Connector</a></h4>
    -			<p>The Bezier Connector provides a cubic Bezier path between the two Endpoints. It supports a single constructor argument:</p>
    -			<p>
    -			<strong>curviness</strong> - Optional; defaults to 150. This defines the distance in pixels that
    -			the Bezier's control points are situated from the anchor points.  This does not mean that your
    -			connector will pass through a point at this distance from your curve.  It is a hint to how you want the
    -			curve to travel. Rather than discuss Bezier curves at length here, because they are a complex topic,
    -			we refer you to <a href="http://en.wikipedia.org/wiki/B%C3%A9zier_curve" target="_blank">Wikipedia.</a>
    -			</p>
    -			<h4><a id="straightConnector">Straight Connector</a></h4>
    -			<p>The Straight Connector draws a straight line between the two endpoints. No constructor arguments are supported; use the <strong>endpointStyle</strong>
    -			argument to a connect or addEndpoint call to control the appearance of one of these Connectors.  
    -			</p>
    -			<h4><a id="flowchartConnector">Flowchart Connector</a></h4>
    -			<p>This type of Connector draws a connection that consists of a series of vertical or horizontal segments - the classic flowchart look. A single constructor argument is supported:</p>
    -			<strong>stub</strong> - this is the minimum length, in pixels, of the initial stub that emanates from an Endpoint.  This parameter is optional, and defaults to 30 pixels.
    -			<h4><a id="stateMachineConnector">State Machine Connector</a></h4>
    -			<p>This Connector draws slightly curved lines (they are actually quadratic Bezier curves), similar to the State Machine connectors you may have 
    -			seen in software like GraphViz.  Connections in which some element is both the source and the target (so-called 'loopback') are supported by these 
    -			Connectors, in which case you get a circle.  These are the only Connectors for which the loopback case is supported. Supported parameters are:
    -			</p>
    -			<p>
    -			<strong>margin</strong> - Optional; defaults to 5.  Defines the distance from the element that the connector begins/ends.
    -			</p>
    -			<p>
    -			<strong>curviness</strong> - Optional, defaults to 10.  This has a similar effect to the curviness parameter on Bezier curves.
    -			</p>
    -			<p>
    -			<strong>proximityLimit</strong> - Optional, defaults to 80.  The minimum distance between the two ends of the Connector before it paints itself as 
    -			a straight line rather than a quadratic Bezier curve.
    -			</p>    
    -			
    -		</div>
    -		
    -			<div class="section">
    -			<h3><a id="endpoints">Endpoint Types</a></h3>
    -			<p>An Endpoint is the UI component that marks the location of an Anchor, ie. the place where a Connector joins an
    -			element.  jsPlumb comes with four Endpoint implementations - Dot, Rectangle, Blank and Image. You optionally specify Endpoint properties using the
    -			<strong>endpoint</strong> parameter in a call to jsPlumb.connect, jsPlumb.addEndpoint, jsPlumb.makeSource or jsPlumb.makeTarget.
    -			</p>  
    -				<p>As with Connectors and Overlays, you specify Endpoints using the syntax described above in <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions.</a></p>
    -			
    -			The four available Endpoint types, and their constructor arguments, are as follows:
    -					<h4><a id="dotEndpoint">Dot Endpoint</a></h4>
    -					This Endpoint draw a dot on the screen. It supports three constructor parameters:
    -					<p>
    -						<strong>radius</strong> - Optional; defaults to 10 pixels. Defines the radius of the dot.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="rectangleEndpoint">Rectangle Endpoint</a></h4>
    -						Draws a rectangle. Supported constructor parameters are:
    -					<p>
    -						<strong>width</strong> - Optional; defaults to 20 pixels. Defines the width of the rectangle.
    -					</p>
    -					<p>
    -						<strong>height</strong> - Optional; defaults to 20 pixels. Defines the height of the rectangle.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="imageEndpoint">Image Endpoint</a></h4>
    -						Draws an image from a given URL.  This Endpoint supports three constructor parameters:  
    -					<p>
    -						<strong>src</strong> - Required.  Specifies the URL of the image to use.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="blankEndpoint">Blank Endpoint</a></h4>
    -						Does not draw anything visible to the user.  However, a div element is added to the UI and so this Endpoint also support the css parameters:
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -																			
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="overlays">Overlay Types</a></h3>
    -			<p>
    -			Overlays are UI elements that are painted onto connections, such as labels or arrows.
    -			jsPlumb comes with four defaults:
    -			</p>
    -			<ul>
    -				<li><strong>Arrow</strong> - a configurable arrow that is painted at some point along the connector.  You can control the length and width of the Arrow, the 'foldback' point - a point the tail points fold back into, and the direction (allowed values are 1 and -1; 1 is the default and means point in the direction of the connection)</li>
    -				<li><strong>Label</strong> - a configurable label that is painted at some point along the connector.</li>
    -				<li><strong>PlainArrow</strong> - an Arrow shaped as a triangle, with no foldback.</li>
    -				<li><strong>Diamond</strong> - as the name suggests, a diamond.</li>
    -			</ul> 
    -			The last two are actually just configured instances of the generic Arrow overlay (see examples). 
    -			
    -			<p>All Overlays support these two methods for getting/setting their location (location is a number between 0 and 1 inclusive, indicating some point along the path
    -			inscribed by the associated Connector):</p>
    -			<ul>
    -				<li class="bullet"><strong>getLocation</strong> - returns the current location</li>
    -				<li class="bullet"><strong>setLocation</strong> - sets the current location; valid values are decimals in the range 0-1 inclusive</li> 
    -			</ul>
    -			
    -			<p>You can specify one or more overlays when making a call to jsPlumb.connect, jsPlumb.addEndpoint or jsPlumb.makeSource (but not jsPlumb.makeTarget; overlays
    -			are always derived from what the source of a Connection defines)  The three cases are discussed below:</p>
    -			1. Specifying one or more overlays on a jsPlumb.connect call. In this example we'll create an Arrow with the default options for an Arrow, and
    -			a label with the text "foo":
    -			<div class="code">
    -			<pre>jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});			
    -</pre>
    -			</div>
    -			This connection will have an arrow located halfway along it, and the label "foo" one quarter of the way along.  Notice the <strong>id</strong>
    -			parameter; it can be used later if you wish to remove the Overlay or change its visibility (see below).<br/><br/> 
    -			
    -			2. Specifying one or more overlays on a jsPlumb.addEndpoint call.  Note in this example that we use the parameter 'connectorOverlays' and not
    -			'overlays' as in the last example.  This is because 'overlays' would refer to Endpoint Overlays:
    -			<div class="code">
    -			<pre>jsPlumb.addEndpoint("someDiv", {
    -	...
    -	connectorOverlays:[ 
    -		[ "Arrow", { width:10, length:30, location:1, id:"arrow" } ], 
    -		[ "Label", { label:"foo", id:"label" } ]
    -	],
    -	...
    -});</pre>
    -			</div>	
    -			This connection will have a 10x30 Arrow located right at the head of the connection, and the label "foo" located at the halfway point. Again,
    -			note that <strong>id</strong> parameter passed in to each Overlay spec.<br/><br/>
    -			
    -			3. Specifying one or more overlays on a jsPlumb.makeSource call.  Note in this example that we again use the parameter 'connectorOverlays' and not
    -			'overlays'.  The 'endpoint' parameter to jsPlumb.makeSource supports everything you might pass to the second argument of a jsPlumb.addEndpoint call:
    -			<div class="code">
    -			<pre>jsPlumb.makeSource("someDiv", {
    -	...
    -	endpoint:{
    -		connectorOverlays:[ 
    -			[ "Arrow", { width:10, length:30, location:1, id:"arrow" } ], 
    -			[ "Label", { label:"foo", id:"label" } ]
    -		]
    -	}
    -	...
    -});</pre>
    -			</div>	
    -			This connection will have a 10x30 Arrow located right at the head of the connection, and the label "foo" located at the halfway point. Again,
    -			note that <strong>id</strong> parameter passed in to each Overlay spec.<br/><br/>
    -			
    -			 	
    -					<h4><a id="arrowOverlay">Arrow Overlay</a></h4>
    -					<p>This overlay draws an arrow, using four points: the head and two tail points, and a 'foldback' point, which permits the tail
    -					of the arrow to be indented. Available constructor arguments for this Overlay are </p>
    -					<ul>
    -					<li><strong>width</strong> - width of the tail of the arrow</li>
    -					<li><strong>length</strong> - distance from the tail of the arrow to the head</li>
    -					<li><strong>location</strong> - where, as a proportional value from 0 to 1 inclusive, the Arrow should appear on the Connector</li>
    -					<li><strong>direction</strong> - which way to point. Allowed values are 1 (the default, meaning forwards) and -1, meaning backwards </li>
    -					<li><strong>foldback</strong> - how far along the axis of the arrow the tail points foldback in to. Default is 0.623.</li>
    -					<li><strong>paintStyle</strong> - a style object in the form used for paintStyle values for Endpoints and Connectors</li>
    -					</ul>
    -					<h4><a id="plainArrowOverlay">PlainArrow Overlay</a></h4>
    -					<p>This is just a specialized instance of Arrow in which jsPlumb hardcodes 'foldback' to 1, meaning the tail of the Arrow is a flat edge.  All of the constructor parameters from Arrow apply for PlainArrow.</p>
    -					<h4><a id="diamondOverlay">Diamond Overlay</a></h4>
    -					<p>This is a specialized instance of Arrow in which jsPlumb hardcodes 'foldback' to 2, meaning the Arrow turns into a Diamond.  All of the constructor parameters from Arrow apply for PlainArrow.</p>
    -					<h4><a id="labelOverlay">Label Overlay</a></h4>
    -					This provides a text label to decorate Connectors with.  The available constructor arguments are:
    -					<ul>
    -						<li><strong>label</strong> - The text to display.  You can provide a function here instead of plain text: it is passed the Connection as an argument, and it should return a String.</li>
    -						<li><strong>cssClass</strong> - Optional css class to use for the Label.  This is now preferred over using the 'labelStyle' parameter.</li>
    -						<li><strong>labelStyle</strong> - (deprecated, use cssClass instead) Optional arguments for the label's appearance.  Valid entries in this JS object are:
    -							<ul>
    -								<li><em>font</em> - a font string in a format suitable for the Canvas element</li>
    -								<li><em>fillStyle</em> - the color to fill the label's background with. Optional.</li>
    -								<li><em>color</em> - the color of the label's text. Optional.</li>
    -								<li><em>padding</em> - optional padding for the label. This is expressed as a proportion of the width of the label, not in pixels or ems.</li>
    -								<li><em>borderWidth</em> - optional width in pixels for the label's border. Defaults to 0.</li>
    -								<li><em>borderStyle</em> - optional. The color to paint the border, if there is one.</li>
    -								
    -							</ul>
    -						
    -						</li>
    -						<li><strong>location</strong> - As for Arrow Overlay.  Where, proportionally from 0 to 1 inclusive, the label should appear.</li>
    -					</ul>
    -					
    -					<h4><a id="hideshowOverlay">Hiding/Showing Overlays</a></h4>
    -					<p>You can control the visibility of Overlays using the <strong>setVisible</strong> method of Overlays themselves, or with
    -					<strong>showOverlay(id)</strong> or <strong>hideOverlay(id)</strong> on a Connection.</p>
    -					<p>Remember the <strong>id</strong> parameter that we specified in the examples above?  This can be used to retrieve the Overlay
    -					from a Connection:</p>
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -var overlay = connection.getOverlay("myLabel");
    -// now you can hide this Overlay:
    -overlay.setVisible(false);
    -// there are also hide/show methods:
    -overlay.show();
    -overlay.hide();
    -
    -</pre>					
    -					</div>
    -					<p>However, Connection also has two convenience methods you could use instead:
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -connection.hideOverlay("myLabel");
    -
    -// more time passes
    -
    -connection.showOverlay("myLabel");
    -
    -</pre>					
    -					</div>
    -					
    -		<h4><a id="hideshowOverlay">Removing Overlays</a></h4>
    -		<p>Connection also has a <strong>removeOverlay</strong> method, that does what you might expect:
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -connection.removeOverlay("myLabel");
    -
    -
    -</pre>					
    -					</div>							
    -	
    -		</div>
    -	
    -			
    -			<div class="section">
    -			<h3><a id="defaults">Defaults</a></h3>
    -			The easiest way to set a look and feel for your plumbing is to override the defaults that jsPlumb uses. If you
    -			do not do this you are forced to provide your overridden values on every call.  Every argument to the connect and addEndpoint methods has an
    -			associated default value in jsPlumb.<br/><br/>
    -
    -			The defaults that ship with jsPlumb are stored in <em>jsPlumb.Defaults</em>, which is a Javascript object.  Valid entries, and their initial values, are:
    -
    -			<div class="code">
    -<pre>
    -Anchor : "BottomCenter",
    -Anchors : [ null, null ],
    -Connector : "Bezier",
    -ConnectionOverlays : [ ],
    -DragOptions : { },
    -DropOptions : { },
    -Endpoint : "Dot",
    -EndpointOverlays : [ ],
    -Endpoints : [ null, null ],
    -EndpointStyle : { fillStyle : null },
    -EndpointStyles : [ null, null ],
    -EndpointHoverStyle : null,
    -EndpointHoverStyles : [ null, null ],
    -HoverPaintStyle : null,
    -LabelStyle : { color : "black" },
    -LogEnabled : false,
    -Overlays : [ ],
    -MaxConnections : 1,
    -MouseEventsEnabled : true,
    -PaintStyle : { lineWidth : 8, strokeStyle : "#456" },
    -RenderMode : "svg",
    -Scope : "_jsPlumb_DefaultScope"
    -</pre>
    -			</div>
    -			<p>
    -			Note that in EndpointStyle, the default fillStyle is 'null'.  This instructs jsPlumb to use the strokeStyle
    -from the attached connector to fill the endpoint.</p>
    -<p>Note also that you can specify either or both (or neither) of 'EndpointStyle' and 'EndpointStyles'.  This allows you to specify a different
    -end point for each end of a connection.  'Endpoint' and 'Endpoints' use the same concept.  jsPlumb will look first in the
    -individual endpoint/endpoint style arrays, and then fall back to the single default version.</p>
    -
    -			you can override these defaults by including this in a script somewhere:
    -			<div class="code">
    -<pre>
    -jsPlumb.Defaults.PaintStyle = {
    -	lineWidth:13,
    -	strokeStyle: 'rgba(200,0,0,100)'
    -}
    -
    -jsPlumb.Defaults.DragOptions = { cursor: "crosshair" };
    -
    -jsPlumb.Default.Endpoints = [ [ "Dot", 7 ], [ "Dot", 11 ] ];
    -
    -jsPlumb.Defaults.EndpointStyles = [{ fillStyle:"#225588" }, { fillStyle:"#558822" }];</pre>
    -			</div>
    -			after the jsPlumb script has been loaded of course!  Here we have specified the following default behaviour:
    -			<ul>
    -				<li>- connectors are 13 pixels wide and painted with a semi-transparent red line</li>
    -				<li>- when dragging an element the crosshair cursor is used</li>
    -				<li>- the source endpoint is a dot of radius 7; the target endpoint is a dot of radius 11</li>
    -				<li>- the source endpoint is blue; the target endpoint is green</li>
    -			</ul>
    -
    -		</div> 					
    -
    -<div class="section">
    -			<h3><a id="simpleConnections">Connections</a></h3>
    -			<h4><a id="simpleConnections">Programmatic Connections</a></h4>
    -The most simple connection you can make with jsPlumb looks like this:
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({source:"element1", target:"element2"});</pre>			
    -			</div>
    -
    -		In this example we have created a Connection from 'element1' to 'element2'.  Remember that a Connection in jsPlumb consists of two Endpoints,
    -		a Connector, and zero or more Overlays.  But this call to 'connect' supplied none of those things, so jsPlumb uses the default values wherever it needs to.
    -		In this case, default values have been used for the following: 
    -		<ul>
    -			<li>- The type and appearance of each Endpoint in the Connection. jsPlumb's default for this is the "Dot" endpoint, of radius 10, with fill color "#456".</li>
    -			<li>- The Anchors that define where the connection's Endpoints appear on each element. The jsPlumb default is "BottomCenter"</li>			
    -			<li>- Whether or not each Endpoint can be a source or target for new Connections. The default is false.</li>
    -			<li>- The type and appearance of the Connection's Connector. The default is a "Bezier" connector of line width 8, and color "#456".</li>
    -		</ul>
    -		<p>
    -		So this call will result in an 8px Bezier, colored "#456", from the bottom center of 'element1' to the bottom center of 'element2', and each
    -		Endpoint will be a 10px radius Dot Endpoint, colored "#456".
    -		</p>
    -		<p>
    -		Let's beef up this call a little and tell jsPlumb what sort of Endpoints we want, and where we want them:		
    -		</p>
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:"element1", 
    -	target:"element2",
    -	anchors:["RightMiddle", "LeftMiddle" ],
    -	endpoint:"Rectangle",
    -	endpointStyle:{ fillStyle: "yellow" }
    -});
    -</pre>			
    -			</div>
    -			
    -		<p>This is what we have told jsPlumb we want this time:</p>
    -		<ul>
    -			<li><strong>anchors</strong> - this array tells jsPlumb where the source and target Endpoints should be located on their parent elements. In this case,
    -			we use the shorthand syntax to name one of jsPlumb's default anchors; you can also specify custom locations (see <a href="#anchors">anchors</a>).
    -			Instead of <strong>anchors</strong> you can use <strong>anchor</strong>, if you want the source and target Endpoints to be located at the
    -			same place on their parent elements.			<br/>
    -			</li>
    -			<li>
    -				<strong>endpoint</strong>  - this tells jsPlumb to use the "Rectangle" Endpoint for both the source and target of the Connection.  As with anchors, 
    -				<strong>endpoint</strong> has a plural version that allows you to specify a different Endpoint for each end of the Connection.<br/>  
    -			</li>
    -			<li>
    -				<strong>endpointStyle</strong> - this is the definition of the appearance of the Endpoint you specified above.  Again, there is a
    -				plural equivalent of this that allows you to specify a different style for each end of the Connection. For more information about
    -				allowed values for this value, see <a href="#paintstyles">Connector, Endpoint & Overlay Styles</a>. 
    -			</li>
    -		</ul>		
    -		
    -		<h5>Reusing common settings between jsPlumb.connect calls</h5>
    -		<p>
    -			A fairly common situation you will find yourself in is wanting to create a bunch of Connections that have only minor differences
    -			between them.  To support that, <strong>jsPlumb.connect</strong> takes an optional second argument. For example:			 
    -		</p>
    -		<div class="code">
    -<pre>
    -var common = {
    -	anchors:[ "BottomCenter", "TopCenter" ],
    -	endpoints:["Dot", "Blank" ]
    -};
    -
    -jsPlumb.connect({ source:"someElement", target:"someOtherElement" }, common);
    -
    -jsPlumb.connect({ source:"aThirdElement", target:"yetAnotherElement" }, common);
    -
    -</pre>			
    -			</div>		
    -			
    -		<h5>Endpoints created by jsPlumb.connect</h5>
    -		<p>If you supply an element id or selector for either the source or target , jsPlumb.connect will automatically create an Endpoint on the 
    -		given element.  These automatically created Endpoints are not marked as drag source or targets, and cannot
    -		be interacted with.  For some situations this behaviour is perfectly fine, but for more interactive UIs you should set things up using the drag and drop
    -		method discussed below.</p>
    -		<p>Given that jsPlumb.connect creates its own Endpoints in some circumstances, in order to avoid leaving orphaned Endpoints around the place, if the Connection is subsequently
    -		deleted, these created Endpoints are deleted too.  Should you want to, you can override this behaviour by setting <strong>deleteEndpointsOnDetach</strong> to
    -		false in the connect call:</p>
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({ 
    -	source:"aThirdElement", 
    -	target:"yetAnotherElement",
    -	deleteEndpointsOnDetach:false 
    -});
    -</pre>
    -</div>								
    -					
    -		<h4><a id="draggableConnections">Connections using Drag and Drop</a></h4>
    -		To support drag and drop connections, you first need to set a few things up.  Every drag and drop connection needs at least a source
    -		Endpoint that the user can drag a connection from. Here's a simple example of how to create an Endpoint:
    -		<div class="code">
    -<pre>
    -var endpointOptions = { isSource:true };
    -var endpoint = jsPlumb.addEndpoint('elementId', endpointOptions);
    -</pre>		
    -</div>  	
    -	This Endpoint will act as a source for new Connections, and will use the jsPlumb defaults for its own appearance and that of any Connections that are drawn from it.							
    -	
    -		<h5>Tip: use the three-argument addEndpoint method for common data </h5>
    -		One thing that happens quite often is that you have an Endpoint whose appearance and behaviour is largely the
    -		same between usages on different elements, with just a few differences. 
    -		<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -	endpoint:"Rectangle",
    -	paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -	isSource:true,
    -	connectorStyle : { strokeStyle:"#666" },
    -	isTarget:true
    -};
    -</pre>		
    -</div>  		
    -		Notice there is no 'anchor' set.  Here we apply it to two elements, at a different location in each:
    -		<div class="code">
    -<pre>
    -jsPlumb.addEndpoint('element1', { anchor:"BottomCenter" }, exampleGreyEndpointOptions)); 
    -
    -jsPlumb.addEndpoint('element2', { anchor:"TopCenter" }, exampleGreyEndpointOptions));
    -</pre>		
    -</div>
    -
    -<p>
    -Now that you have a source Endpoint, you need to either create a target Endpoint on some element, or notify jsPlumb that you wish to make an
    -entire element a drop target.  Let's look at how to attach a target Endpoint first:
    -</p>
    -
    -		<div class="code">
    -<pre>var endpointOptions = { isTarget:true, endpoint:"Rectangle", paintStyle:{ fillStyle:"gray" } };
    -var endpoint = jsPlumb.addEndpoint("otherElementId", endpointOptions);
    -</pre>		
    -		</div>
    -		<p>
    -		This Endpoint, a gray rectangle, has declared that it can act as a drop target for Connections. 
    -</p>
    -<p>
    -jsPlumb also supports marking entire elements as drag sources or drag targets, using the <strong>makeSource</strong> and <strong>makeTarget</strong> methods.  This
    -is a useful function for certain applications.  Each of these methods takes an Endpoint declaration which defines the Endpoint that jsPlumb will create after a Connection
    -has been attached.  Let's take a look at makeTarget first:
    -</p>
    -<h5>jsPlumb.makeTarget</h5>
    -<p>
    -This method takes two arguments, the first of which specifies some element (or list of elements); the second specifies the Endpoint you wish to create on 
    -that element whenever a Connection is established on it.  In this example we will use the exact same target Endpoint we used before - the gray
    -rectangle - but we will tell jsPlumb that the element "aTargetDiv" will be the drop target:
    -</p>
    -		<div class="code">
    -<pre>var endpointOptions = { 
    -  isTarget:true, 
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" } 
    -};
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -<p>
    -	The allowed values in 'endpointOptions' are identical for both the <strong>jsPlumb.addEndpoint</strong> and <strong>jsPlumb.makeTarget</strong> methods, but makeTarget supports an extended 
    -	anchor syntax that allows you more control over the location of the target endpoint.  This is discussed below.	
    -</p>		
    -
    -<h5>Target Anchors positions with makeTarget</h5>
    -<p>When using the makeTarget method, jsPlumb allows you to provide a callback function to be used to determine
    -the appropriate location of a target anchor for every new Connection dropped on the given target.  It may be the
    -case that you want to take some special action rather than just relying on one of the standard anchor mechanisms.
    -</p>
    -<p>This is achieved through an extended anchor syntax (note that this syntax is <strong>not supported</strong> in
    -the <strong>jsPlumb.addEndpoint</strong> method) that supplies a "positionFinder" to the anchor specification.  jsPlumb 
    -provides two of these by default; you can register your own on jsPlumb and refer to them by name, or just 
    -supply a function.  Here's a few examples:</p>
    -Instruct jsPlumb to place the target anchor at the exact location at which the mouse button was released on the
    -target element. Note that you tell jsPlumb the anchor is of type "Assign", and you then provide a "position"
    -parameter, which can be the name of some position finder, or a position finder function itself:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {
    -  endpoint:{
    -    anchor:[ "Assign", { 
    -	  position:"Fixed"
    -	}]
    -  }
    -});
    -</pre>
    -</div>
    -<p><strong>Fixed</strong> is one of the two default position finders provided by jsPlumb. The other is <strong>Grid</strong>:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {
    -  endpoint:{
    -    anchor:[ "Assign", { 
    -	  position:"Grid",
    -	  grid:[3,3]
    -	}]
    -  }
    -});
    -</pre>
    -</div>
    -The Grid position finder takes a "grid" parameter that defines the size of the grid required. [3,3] means
    -3 rows and 3 columns.
    -<p>To supply your own position finder to jsPlumb you first need to create the callback function. It takes
    -N arguments and does stuff. This paragraph obviously needs to be finished.</p>
    -
    -<h5>jsPlumb.makeSource</h5>
    -There are two use cases supported by this method.  The first is the case that you want to drag a Connection from the element itself and have an Endpoint attached to
    -the element when a Connection is established.  The second is a more specialised case: you want to drag a Connection from the element, but once the Connection is
    -established you want jsPlumb to move it so that its source is on some other element.  For an example of this second case, check out the <a href="">WHERE IS THIS State Machine Connectors Demo</a>
    -<p>Here's an example code snippet for the basic use case of makeSource:</p>
    -<div class="code">
    -<pre>
    -	jsPlumb.makeSource(someDiv, {
    -		paintStyle:{ fillStyle:"yellow" },
    -		endpoint:"Blank",
    -		anchor:"BottomCenter"
    -	});
    -</pre>
    -</div>
    -<p>
    -Notice again that the second argument is the same as the second argument to an addEndpoint call.  makeSource is, essentially, a type of addEndpoint call.  In this example 
    -we have told jsPlumb that we will support dragging Connections directly from 'someDiv'.  Whenever a Connection is established between 'someDiv' and some other element,
    -jsPlumb assigns an Endpoint at BottomCenter of 'someDiv', fills it yellow, and sets that Endpoint as the newly created Connection's source. 
    -</p>		
    -<p>Configuring an element to be an entire Connection source using makeSource means that the element cannnot itself be draggable.  There would be no way for jsPlumb to distinguish
    -between the user attempting to drag the element and attempting to drag a Connection from the element.  If your UI works in such a way that that is acceptable then you'll
    -be fine to use this method.  But if you want to drag Connections from some element but also have the element be draggable itself, you might want to consider the second
    -use case.  First, a code snippet:</p>
    -<div class="code">
    -<pre>
    -	jsPlumb.makeSource("someConnectionSourceDiv", {
    -		paintStyle:{ fillStyle:"yellow" },
    -		endpoint:"Blank",
    -		anchor:"BottomCenter",
    -		parent:"someDiv"
    -	});
    -</pre>
    -</div>
    -<p>The only difference here is the inclusion of the 'parent' parameter.  It instructs jsPlumb to configure the source Endpoint on the element specified - in this case,
    -"someDiv" (the value of this parameter may be a String id or a selector).</p>
    -<p>Again, I'd suggest taking a look at the <a href="../demo/jquery/stateMachineDemo.html">State Machine Connectors demonstration</a> for an example of this.</p>
    -
    -<p>
    -There are many things you can set in an Endpoint options object; for a thorough list see the API documentation for Endpoint.  
    -</p>
    -<p>Here's an example of specifying that you want an Arrow overlay halfway along any Connection dragged from this Endpoint:</p>
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -	endpoint:"Rectangle",
    -	paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -	isSource:true,
    -	connectorStyle : { strokeStyle:"#666" },
    -	isTarget:true,
    -	connectorOverlays: [ [ "Arrow", { location:0.5 } ] ]
    -};
    -</pre>
    -</div>
    -This is an Endpoint that moves around the element it is attached to dependent on the location of other elements in the connections it is attached to
    -(a 'dynamic' anchor):
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -	endpoint:"Rectangle",
    -	paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -	isSource:true,
    -	connectorStyle : { strokeStyle:"#666" },
    -	isTarget:true,
    -	connectorOverlays: [ [ "Arrow", { location:0.5 } ] ]
    -	anchor:[ "TopCenter","RightMiddle","BottomCenter","LeftMiddle" ]
    -};
    -</pre>
    -</div>
    -
    -<h5><a id="dragOptions">Drag Options</a></h5>
    -	These are options that will be passed through to the supporting library's drag API.  jsPlumb passes everything you supply here through, inserting
    -	wrapping functions if necessary for the various lifecycle events that jsPlumb needs to know about.  So if, for example, you pass a function to be
    -	called when dragging starts, jsPlumb will wrap that function with a function that does what jsPlumb needs to do, then call yours.
    -	<p>
    -	At the time of writing, jsPlumb supports jQuery, MooTools and YUI3, and each of those libraries uses different terminology.  In addition, jQuery's API is
    -	more fully featured, providing easy support for setting the zIndex and opacity of elements being dragged, as well as the 'scope' for a drag/drop (allowing
    -	you to specify more than one type of drag/drop pair), and hover classes for when a draggable is on the move or
    -	over a droppable. If you're using jQuery you can of course just supply these values on the dragOptions; to make it easier, jsPlumb's MooTools and YUI3 adapters recognize 
    -	these options and add appropriate callbacks for you.  
    -	</p> 
    -	<p>
    -	Given that the options here are library-specific, and they are all well-documented, we're going to discuss just the three drag options
    -	that behave the same way in all (see below for hoverClass):
    -		<ul>
    -		<li><strong>opacity</strong> - the opacity of an element that is being dragged.  Should be a fraction between 0 and 1 inclusive.</li>
    -		<li><strong>zIndex</strong> - the zIndex of an element that is being dragged.</li>
    -		<li><strong>scope</strong> - the scope of the draggable.  can only be dropped on a droppable with the same scope.  this is discussed below.</li>
    -		</ul> 
    -	</p>
    -	
    -	<p>For more information about drag options, take a look at the <a href="http://docs.jquery.com/UI/Draggable" target="_blank">jQuery</a>, <a href="http://mootools.net/docs/more/Drag/Drag.Move" target="_blank">MooTools</a>, or <a href="http://developer.yahoo.com/yui/3/dd/" target="_blank">YUI3</a> docs.	
    -	</p>
    -	<p><strong>NOTE: there is an issue in Chrome that affects the 'cursor' argument to drag options in jQuery. See these links:</strong>
    -	</p><p>
    -<a href="http://forum.jquery.com/topic/draggable-cursor-option-does-not-work" target="_blank">http://forum.jquery.com/topic/draggable-cursor-option-does-not-work</a><br/>
    -<a href="http://forum.jquery.com/topic/chrome-text-select-cursor-on-drag" target="_blank">http://forum.jquery.com/topic/chrome-text-select-cursor-on-drag</a>
    -</p>
    -You should put something like this at the top of your JS to avoid it (perhaps not something so drastic as a document-wide override):	
    -<div class="code">
    -<pre>document.onselectstart = function () { return false; };</pre>
    -</div>		
    -	
    -	<h5><a id="dropOptions">Drop Options</a></h5>
    -	Drop options are treated by jsPlumb in the same way as drag options - they are passed through to the underlying library.  MooTools does not have drop options like jQuery and YUI3 do; droppable functionality in MooTools 
    -	is actually implemented by the Drag.Move class - the one used to initialise a draggable.  But when you setup an Endpoint in jsPlumb you should ignore
    -	that fact, and treat droppables like you would in jQuery or YUI3.  jsPlumb wires everything up for you under the hood.
    -	<p>There are three jQuery droppable options that jsPlumb treats as shortcuts in MooTools and YUI3, for the sake of consistency:</p>
    -		<ul>
    -			<li><strong>hoverClass</strong> - the CSS class to attach to the droppable when a draggable is hovering over it.</li>
    -			<li><strong>activeClass</strong> - the CSS class to attach to the droppable when a draggable is, um, being dragged.</li>
    -			<li><strong>scope</strong> - the scope of the draggable.  The draggable can only be dropped on a droppable with the same scope.  This is discussed below.</li>
    -		</ul>   
    -	<p>For more information about drop options when using jQuery, see <a href="http://docs.jquery.com/UI/Droppable" target="_blank">here</a>.</p>
    -		
    -	<h5><a id="dragAndDropScope">Drag and Drop Scope</a></h5>
    -		jsPlumb borrowed the concept of 'scope' from jQuery's drag/drop implementation: the notion of
    -		which draggables can be dropped on which droppables.  In jsPlumb you can provide a 'scope' entry when 
    -		creating an Endpoint.  Here's the example grey Endpoint example with 'scope' added:
    -		
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -	endpoint:"Rectangle",
    -	paintStyle:{ width:25, height:21, fillStyle:"#666" },
    -	isSource:true,
    -	connectionStyle : { strokeStyle:"#666" },
    -	isTarget:true,
    -	scope:"exampleGreyConnection"
    -};</pre>		
    -</div>  		
    -  										
    -	If you do not provide a 'scope' entry, jsPlumb uses a default scope.  Its value is accessible through this method:
    -	<div class="code">
    -<pre>
    -jsPlumb.getDefaultScope();
    -</pre>	
    -	</div>				
    -	If you want to change it for some reason you can do so with this method:
    -	<div class="code">
    -<pre>
    -jsPlumb.setDefaultScope("mySpecialDefaultScope");
    -</pre>	
    -	</div>			
    -	You can also, should you want to, provide the scope value separately on the drag/drop options, like this:
    -	<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -	endpoint:"Rectangle",
    -	painStyle:{ width:25, height:21, fillStyle:'#666' },
    -	isSource:true,
    -	connectionStyle : { strokeStyle:"#666" },
    -	isTarget:true,
    -	dragOptions:{ scope:"dragScope" },
    -	dropOptions:{ scope:"dropScope" }
    -};</pre>		
    -</div>  
    -			
    -	</div>	
    -	
    -	<div class="section">
    -		<h3><a id="beforeDrop">Interceptors</a></h3>
    -		<p>
    -		Interceptors were new in jsPlumb 1.3.4.  They are basically event handlers from which you can return a value that tells jsPlumb to abort what it is that
    -		it was doing.  There are currently two interceptors supported - <strong>beforeDrop</strong>, which is called when the user has dropped a Connection onto some target, 
    -		and <strong>beforeDetach</strong>, which is called when the user is attempting to detach a Connection. 
    -		</p>
    -		<p>Interceptors can be registered via the <strong>bind</strong> method on jsPlumb just like any other event listeners, and they can also be passed in to
    -		the <strong>jsPlumb.addEndpoint</strong>, <strong>jsPlumb.makeSource</strong> and <strong>jsPlumb.makeTarget</strong>
    -		methods.  A future version of jsPlumb will support registering interceptors on Endpoints using the <strong>bind</strong> method as you can now with the jsPlumb object.
    -		Note that binding 'beforeDrop' (as an example) on jsPlumb itself is like a catch-all: it will be called every time a Connection is dropped on _any_ 
    -		Endpoint.  But passing a 'beforeDrop' callback into an Endpoint (and in the future, registering one using 'bind') constrains that callback to just the
    -		Endpoint in question.  		
    -		<h4><a id="beforeDrop">Before Drop</a></h4>
    -		This is called when the user has released the mouse button over a Connection target.  Your callback is passed the
    -		a JS object containing these fields:
    -		<ul>
    -			<li>sourceId</li>
    -			<li>targetId</li>
    -			<li>scope</li>
    -		</ul>
    -		<p>Returning false - or nothing - from this callback will cause the drag to be abandoned, and the new Connection removed from the UI.</p>
    -		<h4><a id="beforeDetach">Before Detach</a></h4>
    -		This is called when the user has detached a Connection, which can happen for a number of reasons: by default, jsPlumb allows users to drag Connections
    -		off of target Endpoints, but this can also result from a programmatic 'detach' call.  Every case is treated the same by jsPlumb, so in fact it is possible
    -		for you to write code that attempts to detach a Connection but then denies itself!  You might want to be careful with that. 
    -		<p>Note that this interceptor is passed the actual Connection object; this is different from the beforeDrop
    -		interceptor discussed above: in this case, we've already got a Connection, but with beforeDrop we are yet 
    -		to confirm that a Connection should be created.</p>
    -		<p>Returning false - or nothing - from this callback will cause the detach to be abandoned, and the Connection will be reinstated/left on its current target.</p>
    -			
    -	</div>
    -
    -		<div class="section">
    -			<h3><a id="paintstyles">Paint Styles</a></h3>
    -			Defining the appearance of Connectors, Endpoints (and Overlays, but this is deprecated in favour of using CSS classes) is achieved through a 'paintStyle' (or a quite similar name...keep reading)
    -			object passed as a parameter to one of jsPlumb.connect, jsPlumb.addEndpoint, jsPlumb.makeSource or jsPlumb.makeTarget. Depending on the method you are
    -			calling, the parameter names vary.
    -			<h5>Connector Paint Styles</h5>
    -			These are specified in a <em>paintStyle</em> parameter on a call to <strong>jsPlumb.connect</strong>:
    -			<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"el1",
    -	target:"el2",
    -	paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			or in the <em>connectorPaintStyle</em> parameter on a call to <strong>jsPlumb.addEndpoint</strong> or <strong>jsPlumb.makeSource</strong>:
    -			<div class="code">
    -<pre>jsPlumb.addEndpoint("el1", {
    -	paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -	connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			<div class="code">
    -<pre>jsPlumb.makeSource("el1", {
    -	paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -	connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div> 			
    -			 
    -
    -Notice the <em>paintStyle</em> parameter in those examples: it is the paint style for the Endpoint, which we'll discuss below.
    -
    -			<h5>Endpoint Paint Styles</h5>
    -			These are specified in a <em>paintStyle</em> parameter on a call to <strong>jsPlumb.addEndpoint</strong>. This is the example from
    -			just above:
    -			<div class="code">
    -<pre>jsPlumb.addEndpoint("el1", {
    -	paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -	connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div> 
    -			...or as the <em>endpointStyle</em> parameter to a <strong>jsPlumb.connect</strong> call:		
    -			<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"el1",
    -	target:"el2",
    -	endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -	paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			... or as an entry in the <em>endpointStyles</em> array passed to a <strong>jsPlumb.connect</strong> call:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"el1",
    -	target:"el2",
    -	endpointStyles:[ 
    -		{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -		{ fillStyle:"green" }
    -	],
    -	paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			or as the <em>paintStyle</em> entry in an <em>endpoint</em> parameter passed to a <strong>jsPlumb.makeTarget</strong> or <strong>jsPlumb.makeSource</strong> call:
    -					<div class="code">
    -<pre>jsPlumb.makeTarget("el1", {
    -	endpoint: {
    -		paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 }
    -	}
    -});
    -</pre>			
    -			</div>
    -							<div class="code">
    -<pre>jsPlumb.makeSource("el1", {
    -	endpoint: {
    -		paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 }
    -	},
    -	parent:"someOtherDivIJustPutThisHereToRemindYouYouCanDoThis"
    -});
    -</pre>			
    -			</div>						
    -	In the first example we  made "el1" into a drop target, and defined a paint style for the Endpoint jsPlumb will create when a Connection is
    -	established.  In the second we made "el1" a connection source and assigned the same values for the Endpoint jsPlumb will create when a Connection
    -	is dragged from that element.
    -	
    -	<h5>Overlay Paint Styles</h5>
    -	The preferred way to set paint styles for Overlays is to use the <em>cssClass</em> parameter in the constructor arguments of an Overlay definition.
    -	
    -	<h4>Paint Style Parameters</h4>
    -	This is the full list of parameters you can set in a paintStyle object, but note that <strong>fillStyle</strong> is ignored by Connectors,
    -	and <strong>strokeStyle</strong> is ignored by Endpoints.  Also, if you create a Connection using jsPlumb.connect and do not specify any
    -	Endpoint styles, the Endpoints will derive their fillStyle from the Connector's strokeStyle.
    -	<p>
    -		fillStyle, strokeStyle and outlineColor can be specified using any valid CSS3 syntax.
    -	</p>
    -	<ul>
    -		<li><strong>fillStyle</strong> - color for an Endpoint, eg. rgba(100,100,100,50), "blue", "#456", "#993355", rgb(34, 56, 78).</li>
    -		<li><strong>strokeStyle</strong> - color for a Connector. see fillStyle examples.</li>
    -		<li><strong>lineWidth</strong> - width of a Connector's line. An integer.</li>
    -		<li><strong>outlineWidth</strong> - width of the outline for an Endpoint or Connector. An integer.</li>
    -		<li><strong>outlineColor</strong> - color of the outline for an Endpoint or Connector. see fillStyle examples.</li>
    -		<li><strong>dashstyle</strong> - VML and SVG only. This comes from VML, and allows you to create dashed or dotted lines.  It has a 
    -			better syntax than the equivalent attribute in SVG (<em>stroke-dasharray</em>, discussed below), so jsPlumb supports this for both renderers. 
    -			The <em>dashstyle</em> attribute is specified as an array of strokes and spaces, where each value is some multiple of <em>the width of 
    -			the Connector</em>, and that's where it's better than SVG, which uses pixels.
    -			<p>
    -			<a href="http://www.w3.org/TR/NOTE-VML" target="_blank">The VML spec</a> is a good place to find valid values for dashstyle. Note that
    -			jsPlumb does not support the string values for this attribute ("solid", "dashdot", etc).
    -			</p>
    -			<p>
    -			In SVG render mode, jsPlumb uses the <em>lineWidth</em> parameter in conjunction with the values in a <em>dashstyle</em> attribute
    -			to create an appropriate value for <em>stroke-dasharray</em>.
    -			</p>
    -		</li>
    -		<li><strong>stroke-dasharray</strong> - SVG only. This is the SVG equivalent of <em>dashstyle</em>.  
    -		<a href="http://www.w3.org/TR/SVG/painting.html" target="_blank">The SVG spec</a> discusses valid values for this parameter.  But be aware
    -		that jsPlumb does not convert this into a valid <em>dashstyle</em> attribute when using the VML renderer. Better to use <em>dashstyle</em>.
    -		</li>
    -		<li><strong>stroke-dashoffset</strong> - SVG only.  This is used in SVG to specify how far into the dash pattern to start painting.  For more information,
    -		see <a href="http://www.w3.org/TR/SVG/painting.html" target="_blank">the SVG spec.</a></li>
    -		<li><strong>joinstyle</strong> - VML and SVG only.  As with <em>dashstyle</em>, this is a VML attribute that jsPlumb supports for both VML
    -		and SVG - jsPlumb turns this into a <em>stroke-linejoin</em> attribute when rendering with SVG.  This attribute specifies how you want individual
    -		segments of connectors to be joined; the VML and SVG specs both have examples of this, of which many are the same between the two, which is
    -		why jsPlumb will automatically convert this attribute into the SVG equivalent.
    -		</li>
    -		<li><strong>stroke-linejoin</strong> - SVG only.  This is the equivalent of VML's <em>joinstyle</em> attribute, but as with <em>stroke-dasharray</em>,
    -		jsPlumb does not convert this into something approriate for VML.  So, using <em>joinstyle</em> will enable you to support more browsers with
    -		less effort.		
    -		</li>
    -	</ul>
    -	
    -	<h3><a id="hoverpaintstyles">Hover Paint Styles</a></h3>
    -	 		
    -	 		Connectors and Endpoints both support the concept of a "hover" paint style - a paint style to use when the mouse is hovering over
    -	 		the component.  These are specified in the exact same format as paint styles discussed above, but hover paint styles also inherit
    -	 		any values from the main paint style.  This is because you will typically want to just change the color, or perhaps outline color, of
    -	 		a Connector or Endpoint when the mouse is hovering, but leave everything else the same.  So having hover paint styles inherit their
    -	 		values precludes you from having to define things in more than one place.
    -	 		<p>
    -	 			The naming convention adopted for hover paint styles is pretty much to insert the word 'hover' into the corresponding main paint
    -	 			style parameters.  Here are a couple of examples:
    -	 		</p> 
    -	 					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"el1",
    -	target:"el2",
    -	paintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -	hoverPaintStyle:{ strokeStyle:"red" },
    -	endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -	endpointHoverStyle:{ fillStyle:"red" }
    -});
    -</pre>			
    -			</div>
    -			In this example we specified a hover style for both the Connector, and each of its Endpoints.  Here's the same thing, but using the plural
    -			version, to specify a different hover style for each Endpoint:
    -			
    -			
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"el1",
    -	target:"el2",
    -	paintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -	hoverPaintStyle:{ strokeStyle:"red" },
    -	endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -	endpointHoverStyles:[ { fillStyle:"red" }, { fillStyle:"yellow" } ]
    -});
    -</pre>			
    -			</div>
    -	 		Calls to <strong>jsPlumb.addEndpoint</strong> and <strong>jsPlumb.makeTarget</strong> can also specify various hover paint styles:
    -	 								<div class="code">
    -<pre>jsPlumb.addEndpoint("el1", {
    -	paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -	hoverPaintStyle:{ fillStyle:"red" },
    -	connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -	connectorHoverPaintStyle:{ strokeStyle:"red", outlineColor:"yellow", outlineWidth:1 }
    -});
    -</pre>			
    -			</div> 
    -	 		
    -	 		Here we specified a hover paint style for both the Endpoint we are adding, and any Connections that are made from the Endpoint.
    -	 		<p><strong>jsPlumb.makeTarget</strong> also supports hover paint styles - here's the example from before, with one applied:</p>
    -	 		
    -	 							<div class="code">
    -<pre>jsPlumb.makeTarget("el1", {
    -	endpoint: {
    -		paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -		hoverPaintStyle:{ fillStyle:"red" }
    -	}
    -});
    -</pre>			
    -			</div>	
    -	
    -		Note that <strong>jsPlumb.makeTarget</strong> does not support Connector parameters. It is for creating targets only; Connector parameters 
    -		will be set by the source Endpoint in any Connections that are made to the element that you turned into a target by using this method.
    -	 		
    -	 		
    -			<h4><a id="gradients">Gradients</a></h4>
    -			The Canvas and SVG renderers both support gradients. The VML renderer does not.  jsPlumb uses its own syntax to define
    -			gradients, to abstract out the differences between the syntax required by canvas and that required by SVG.  
    -			<p>There are two types of gradients available - a 'linear' gradient, which consists of colored lines all
    -			going in one direction, and a 'radial' gradient, which consists of colored circles emanating from one circle to another.
    -			Because of their basic shape, jsPlumb supports only <em>linear</em> gradients for
    -			Connectors.  But for Endpoints, jsPlumb supports both <em>linear</em> and <em>radial</em> gradients.</p>
    -			
    -				<h5>Connector gradients</h5>
    -				To specify a linear gradient to use in a Connector, you must add a <em>gradient</em> object to your
    -				Connector's <em>paintStyle</em>, for instance:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : "window2",
    -	target : "window3",
    -	paintStyle:{
    -		gradient:{
    -			stops:[[0,"green"], [1,"red"]]
    -		},
    -		lineWidth:15
    -	}
    -});
    -</pre>
    -</div>
    -Here we have connected window2 to window3 with a 15 pixel wide connector that has a gradient from green to red.</p><p> Notice the <strong>gradient</strong> object and the
    -<strong>stops</strong> list inside it - the gradient consists of an arbitrary number of these "color stops".  Each color stop is comprised
    -of two values - [position, color].  Position must be a decimal value between 0 and 1 (inclusive), and indicates where the color
    -stop is situated as a fraction of the length of the entire gradient.  Valid values for
    -the colors in the <strong>stops</strong> list are the same as those that are valid for <strong>strokeStyle</strong> when describing a color.
    -			</p>
    -			As mentioned, the <strong>stops</strong> list can hold an arbitrary number of entries.  Here's an example of a gradient that goes from red to blue to green, and back again through blue to red:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle : {
    -		gradient:{
    -			stops:[[0,'red'], [0.33,'blue'], [0.66,'green'], [0.33,'blue'], [1,'red']]
    -		},
    -		lineWidth : 15
    -	}
    -});
    -</pre>
    -</div>
    -<strong>Note:</strong> when using the VML renderer, jsPlumb will simply ignore the gradient directive
    -so it is best to ensure you also supply a <em>strokeStyle</em> in your paintStyle object, to give jsPlumb something to fall back on.  If you do not supply
    -a <em>strokeStyle</em> your Connectors will be painted black.  The previous example might look like this, for instance:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle:{
    -		strokeStyle:'red',
    -		gradient:{
    -			stops:[[0,'red'], [0.33,'blue'], [0.66,'green'], [0.33,'blue'], [1,'red']]
    -		},
    -		lineWidth:15
    -	}
    -});
    -</pre>
    -</div>
    -Notice the <strong>strokeStyle:'red'</strong> directive at the beginning of the parameter list in <strong>paintStyle</strong>.
    -			<h5>Endpoint gradients</h5>
    -			Endpoint gradients are specified using the same syntax as Connector gradients.  You put the gradient specifier either in the
    -			<strong>endpoint</strong> member, or if you are specifying different Endpoints for each end of the Connector, in one or both of
    -			the values in the <strong>endpoints</strong> array.  Also, this information applies to the case that you are creating standalone
    -			Endpoints that you will be configuring for drag and drop creation of new Connections. 
    -			<p>
    -			This is an example of an Endpoint gradient that is different for each Endpoint in the Connector.  This comes from the main demo; it is
    -			the Connector joining Window 2 to Window 3:
    -			</p>
    -<div class="code">
    -<pre>var w23Stroke = 'rgb(189,11,11)';
    -jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle:{
    -		lineWidth:8,
    -		strokeStyle:w23Stroke
    -	},
    - 	anchors:[ [0.3,1,0,1], "TopCenter" ],
    - 	endpoint:"Rectangle",
    - 	endpointStyles:[
    - 		{ gradient : {stops:[[0, w23Stroke], [1, '#558822']] } },
    -    	{ gradient : {stops:[[0, w23Stroke], [1, '#882255']] } }
    -    ]
    -});
    -</pre>
    -</div>
    -The first entry in the gradient will be the one that is on the Connector end of the Endpoint.  You can of course have as many color stops as
    -you want in this gradient, just like with Connector gradients.
    -	<h5>Applying the gradient in Endpoints</h5>
    -	Only the Dot and Rectangle endpoints honour the presence of a gradient (and, remember, not in VML). The Image endpoint of course ignores a gradient
    -	as it does no painting of its own.
    -	<p>The type of gradient you will see depends on the Endpoint type:</p>
    -		<ul>
    -			<li><strong>Dot</strong> - renders a radial endpoint, with color stop 0 on the outside, progressing inwards as we move through color stops.
    -			<p>Radial gradients actually require more data than linear gradients - in a linear gradient we just move from one point to another, whereas
    -			in a radial gradient we move from one <em>circle</em> to another.  By default, jsPlumb will render a radial gradient using a source
    -			circle of the same radius as the Endpoint itself, and a target circle of 1/3 of the radius of the Endpoint (both circles share the
    -			same center as the Endpoint itself). This circle will be offset by radius/2 in each direction.</p>
    -			<p>You can supply your own values for these inside the gradient descriptor:</p>
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect({
    -	source : 'window3',
    -	target : 'window4',
    -    paintStyle:{
    -    	lineWidth:10,
    -    	strokeStyle:w34Stroke
    -    },
    -    anchors:[ "RightMiddle", "LeftMiddle" ],
    -    endpointStyle:{
    -    	gradient : {
    -    		stops:[ [0, w34Stroke], [1, w34HlStroke] ],
    -    		offset:37.5,
    -    		innerRadius:40
    -    	},
    -    	radius:55
    -    }
    - });
    -</pre>
    -</div>
    -Here we have instructed jsPlumb to make the gradient's inner radius 10px instead of the default 25/3 = 8 ish pixels, and the offset in each direction
    -will be 5px, instead of the default radius / 2 = 12.5 pixels.
    -<p>It is also possible to specify the offset and inner radius as percentages - enter the values as strings with a '%' symbol on the end:</p>
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect({
    -	source : 'window3', 
    -	target : 'window4',
    -    paintStyle:{
    -    	lineWidth:10,
    -    	strokeStyle:w34Stroke
    -    },
    -	anchors:[ "RightMiddle", "LeftMiddle" ],
    -	endpointStyle:{
    -		gradient : {
    -			stops:[ [0, w34Stroke], [1, w34HlStroke] ],
    -			offset:'68%',
    -			innerRadius:'73%'
    -		},
    -		radius:25
    -	}
    -});
    -</pre>
    -</div>
    -This will give roughly the same output as the example above (the percentages are not entirely exact).<br/><br/>
    -			</li>
    -			<li><strong>Rectangle</strong> - renders a linear endpoint, with color stop 0 closest to the end of the Connector</li>
    -		</ul>
    -		
    -<h4><a id="svgvmlcss">Styling SVG/VML using CSS</a></h4>
    -	A nice way of controlling your UI's appearance is to make use of the fact that both SVG and VML can be styled using CSS.
    -	<p>This section will be expanded in the next few weeks to give some decent examples of how to do so</p>
    -			
    -		
    -		
    -		</div>
    -
    -		<div class="section">
    -			<h3><a id="cssclasses">CSS Class Reference</a></h3>
    -			jsPlumb attaches classes to each of the UI components it creates.
    -			<p>These class names are exposed on the jsPlumb object and can be overridden if you need to do so (see the third column in the table) </p>
    -			<table width="70%" class="table">
    -				<tr><th>Component</th><th>CSS Class</th><th>jsPlumb Member</th></tr>
    -				<tr><td>Connector</td><td>_jsPlumb_connector</td><td>connectorClass</td></tr>
    -				<tr><td>Endpoint</td><td>_jsPlumb_endpoint</td><td>endpointClass</td></tr>
    -				<tr><td>Overlay</td><td>_jsPlumb_overlay</td><td>overlayClass</td></tr>
    -			</table>
    -			<p>You would typically use these to establish appropriate z-indices for your UI.</p>			
    -		</div>
    -				
    -		<div class="section">
    -			<h3><a id="animation">Animation</a></h3>
    -			jsPlumb offers an 'animate' function, which wraps the underlying animation engine for whichever library you happen to be using and
    -			inserts a callback for jsPlumb to repaint whatever it needs to at each step.  You could of course do this yourself; it's a convenience
    -			method really.
    -			<p>The method signature is:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.animate : function(el, properties, options) 
    -</pre>
    -</div>			
    -The arguments are as follows:
    -	<ul>
    -		<li><strong>el</strong> - element id, or element object from the library you're using.</li>
    -		<li><strong>properties</strong> - properties for the animation, such as duration etc.</li>
    -		<li><strong>options</strong> - options for the animation, such as callbacks etc.</li>
    -	</ul>
    -	
    -		<h4>jQuery and the 'revert' option</h4>
    -		jQuery offers a 'revert' option that you can use to instruct it to revert a drag under some condition. This is
    -		rendered in the UI as an animation that returns the drag object from its current location to wherever it started
    -		out.  It's a nice feature.  Unfortunately, the animation that runs the revert does not offer any lifecycle
    -		callback methods - no 'step', no 'complete' - so its not possible for jsPlumb to know that the revert 
    -		animation is taking place.
    -	
    -		</div>
    -		
    -			<div class="section">
    -			<h3><a id="connectionInfo">Retrieving Connection Information</a></h3>
    -			jsPlumb offers one fairly versatile method - <strong>getConnections</strong> - to retrieve information about the
    -			currently managed connections.  
    -			<p>Before you use this method you should understand jsPlumb's notion of 'scope' - documentation
    -			is <a href="#dragAndDropScope">here</a> </p>
    -			
    -			<h4>Retrieving connections for a single scope</h4>
    -			<p>To do this, you call getConnections with either no arguments, in which case jsPlumb uses the default scope, or with a string
    -			specifying one scope</p>
    -			<div class="code">
    -<pre>var connectionList = jsPlumb.getConnections();     // you get a list of Connection objects that are in the default scope.
    -</pre>			
    -			</div>
    -			Compare this with:
    -			<div class="code">
    -<pre>var connectionList = jsPlumb.getConnections("myScope");     // you get a list of Connection objects that are in "myScope".
    -</pre>			
    -			</div>
    -			<h4>More advanced filtering</h4>
    -			getConnections optionally takes a JS object specifying filter parameters, of which there are three:
    -			<ul>
    -				<li>scope - the scope(s) of the connection type(s) you wish to retrieve</li>
    -				<li>source - limits the returned connections to those that have this source id</li>
    -				<li>target - limits the returned connections to those that have this target id</li>
    -			</ul>			
    -			Each of these three parameters may be supplied as a string, or a list of strings - see the examples below.
    -			<p>
    -			The return value of a call to getConnection using a JS object as parameter varies on how many scopes you defined.  If you defined only
    -			a single scope then jsPlumb returns you a list of Connections in that scope.  Otherwise the return value is a dictionary whose keys are 
    -			scope names, and whose values are lists of Connections. For example, the following call:
    -			</p>
    -			<div class="code">
    -<pre>
    -	jsPlumb.getConnections({scope:["someScope", "someCustomScope"]});
    -</pre>
    -</div>
    -would result in this output:			
    -<div class="code">
    -<pre>
    -{
    -	"someScope" : [ 1..n Connections ],
    -	"someCustomScope": [ 1..m Connections ]
    -}
    -</pre>
    -</div>			
    -		The following examples show the various ways you can get connection information:
    -	<ul>
    -		<li>Get all connections:
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getAllConnections();  
    -</pre>
    -</div>						
    -		</li>
    -<li>Get all connections for the default scope only (return value is a list):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections();  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given scope (return value is a list):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:"myTestScope"});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given scopes (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:["myTestScope", "yourTestScope"]});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given source (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:"mySourceElement"});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given sources (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:["mySourceElement", "yourSourceElement"]});  
    -</pre>
    -</div>						
    -		</li>		
    -<li>Get all connections for the given target (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({target:"myTargetElement"});  
    -</pre>
    -</div>						
    -		</li>				
    -<li>Get all connections for the given source and targets (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:"mySourceElement", target:["target1", "target2"]});  
    -</pre>
    -</div>						
    -		</li>				
    -		
    -<li>Get all connections for the given scope, with the given source and target (return value is a list of connections):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:'myScope", source:"mySourceElement", target:"myTargetElement"});  
    -</pre>
    -</div>						
    -		</li>				
    -		
    -	</ul>	
    -
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="events">Events</a></h3>
    -			jsPlumb supports binding to several different events on Connections, Endpoints and Overlays, and also on the jsPlumb
    -			object itself.  
    -			
    -			<h4><a id="jsPlumbEvents">jsPlumb Events</a></h4>
    -			<p>To bind an event to jsPlumb itself, use <strong>jsPlumb.bind(event, callback)</strong>:</p>
    -			<div class="code">
    -<pre>
    -jsPlumb.bind("jsPlumbConnection", function(info) {
    -   .. update your model in here, maybe.
    -});
    -</pre>
    -</div>
    -<p>These are the events you can bind to on the jsPlumb class:</p>
    -			<ul>
    -				<li class="bullet"><strong>jsPlumbConnection</strong> - notification a Connection was established.
    -					<p>
    -						The callback is passed an object with the following properties:					
    -					</p>
    -					<div class="code">
    -<pre>
    -{
    -  connection		: 	the new Connection.  you can register listeners on this etc.
    -  sourceId			:	id of the source element in the Connection
    -  targetId			:	id of the target element in the Connection
    -  source			:	the source element in the Connection
    -  target			:	the target element in the Connection
    -  sourceEndpoint	:	the source Endpoint in the Connection
    -  targetEndpoint	:	the targetEndpoint in the Connection
    -}
    -</pre>
    -					</div>
    -					<p>
    -All of the source/target properties are actually available inside the Connection object, but - for one of those rubbish historical
    -reasons - are provided separately because of a vagary of the <em>jsPlumbConnectionDetached</em> callback, which is discussed below.
    -</p>
    -				</li>
    -				<li class="bullet"><strong>jsPlumbConnectionDetached</strong> - notification a Connection was detached.  
    -					<p>
    -						As with <em>jsPlumbConnection</em>, the callback is passed an object with the following properties:					
    -					</p>
    -					<div class="code">
    -<pre>
    -{
    -  connection		: 	the Connection that was detached.  
    -  sourceId			:	id of the source element in the Connection <em>before it was detached</em>
    -  targetId			:	id of the target element in the Connection before it was detached
    -  source			:	the source element in the Connection before it was detached
    -  target			:	the target element in the Connection before it was detached
    -  sourceEndpoint	:	the source Endpoint in the Connection before it was detached
    -  targetEndpoint	:	the targetEndpoint in the Connection before it was detached
    -}
    -</pre>
    -
    -					</div>				
    -					<p>
    -As mentioned above, the source/target properties are provided separately from the Connection, because this event is fired whenever
    -a Connection is either detached and abandoned, or detached from some Endpoint and attached to another.  In the latter case, the
    -Connection that is passed to this callback is in an indeterminate state (that is, the Endpoints are still in the state they are
    -in when dragging, and do not reflect static reality), and so the source/target properties give you the real story.
    -</p>
    -<p> Like I said above, this is really just for rubbish historical reasons.  I'm attempting to document my way out of it!</p>
    -				
    -				</li>
    -				<li class="bullet"><strong>click</strong> - notification a Connection was clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification a Connection was double-clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>endpointClick</strong> - notification an Endpoint was clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>endpointDblClick</strong> - notification an Endpoint was double-clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>contextmenu</strong> - a right-click on some given component.  jsPlumb will report right clicks on both Connections and Endpoints; the callback is passed the component that received the right-click.
    -				<li class="bullet"><strong>beforeDrag</strong>
    -					This event is fired when a new connection is about to be dragged.  Your callback function
    -					is passed the Connection that jsPlumb has just created.  If you return false (or nothing) 
    -					from this callback, the new Connection is aborted.
    -				</li>
    -				<li class="bullet"><strong>beforeDrop</strong>
    -					This event is fired when a new connection has been dropped.  Your callback function
    -					is passed the Connection that jsPlumb has just created.  If you return false (or nothing) 
    -					from this callback, the new Connection is aborted and removed from the UI.
    -				</li>								
    -			</ul>
    -			<h4><a id="connectionEvents">Connection Events</a></h4>		
    -			<p>To bind to an event on a Connection, you also use the <strong>bind</strong> method:</p>
    -			<div class="code">
    -<pre>
    -var connection = jsPlumb.connect({source:"d1", target:"d2"});
    -connection.bind("click", function(conn) {
    -	console.log("you clicked on ", conn);
    -});
    -</pre>
    -</div>
    -<p>These are the Connection events to which you can bind a listener:</p>
    -			<ul>
    -				<li class="bullet"><strong>click</strong> - notification a Connection was clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification a Connection was double-clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>contextmenu</strong> - a right-click on the Connection. The callback is passed the Connection and the original event.
    -				<li class="bullet"><strong>mouseenter</strong> - notification the mouse entered the Connection's path.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>mouseexit</strong> - notification the mouse exited the Connection's.  The callback is passed the Connection and the original mouse event.</li>
    -			</ul>
    -			<h4><a id="endpointEvents">Endpoint Events</a></h4>			
    -			<p>To bind to an event on a Endpoint, you again use the <strong>bind</strong> method:</p>
    -			<div class="code">
    -<pre>
    -var endpoint = jsPlumb.addEndpoint("d1", { someOptions } );
    -endpoint.bind("click", function(endpoint) {
    -	console.log("you clicked on ", endpoint);
    -});
    -</pre>
    -</div>
    -<p>These are the Endpoint events to which you can bind a listener:</p>
    -			<ul>
    -				<li class="bullet"><strong>click</strong> - notification an Endpoint was clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification an Endpoint was double-clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>contextmenu</strong> - a right-click on the Endpoint. The callback is passed the Endpoint and the original event.
    -				<li class="bullet"><strong>mouseenter</strong> - notification the mouse entered the Endpoint.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>mouseexit</strong> - notification the mouse exited the Endpoint.  The callback is passed the Endpoint and the original mouse event.</li>
    -			</ul>
    -			<h4><a id="overlayEvents">Overlay Events</a></h4>
    -			Registering event listeners on an Overlay is a slightly different procedure - you provide them as arguments to the Overlay's 
    -			constructor.  This is because you never actually act on an 'Overlay' object.  Here's how to register a click listener on an Overlay:
    -			<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"el1",
    -	target:"el2",
    -	overlays:[
    -		[ "Label", {
    -			events:{
    -				click:function(labelOverlay, originalEvent) { alert("you clicked on the label overlay for this connection :" + labelOverlay.connection) }
    -			}
    -		}] 	
    -	]
    -});
    -</pre>			
    -			</div>		
    -		</div>
    -
    -				<div class="section">
    -			<a id="examples"><h3>jsPlumb.connect Examples</h3></a>
    -This section provides examples of how to use the programmatic API to establish Connections. 
    -			<p>
    -			The basic syntax of a call is that you execute 'connect', providing a source and a target, and optionally a paintStyle and preferences for where you
    -			want the plumbing to be anchored on each element, as well as the type of connector to use. 
    -			</p>
    -			<ul>
    -				<li>Connect window1 to window2 with the default settings:
    -					<div class="code">
    -						<pre>jsPlumb.connect({source:"window1", target:"window2"});</pre>
    -					</div>
    -This connects 'window1' to 'window2' using the jsPlumb defaults - a red Bezier curve Connector and black Dot Endpoints.
    -				</li>
    -				<li>Connect window1 to window2 with a 15 pixel wide yellow Connector, and a slightly brighter endpoint (remember the default Endpoint is a Dot):
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -					</div>
    -				</li>
    -				
    -				<li>Connect window1 to window2 with a 15 pixel wide yellow Connector, and a slightly brighter endpoint:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -					</div>
    -				</li>				
    -				<li>Connect window3 to 'window4' with a 10 pixel wide, semi opaque blue Connector, anchored to the left middle of window3, and the right middle of window4, with a Rectangle endpoint of width 10 and height 8:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window3',
    -	target:'window4',
    -	paintStyle:{ lineWidth:10, strokeStyle:'rgba(0, 0, 200, 0.5)' },
    -	anchors:["RightMiddle", "LeftMiddle"],
    -	endpoint:[ "Rectangle", { width:10, height:8 } ]
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window2 to window3 with a default Connector from the top center of window2 to the bottom center of window3, and rectangular endpoints:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window2',
    -	target:'window3',
    -	paintStyle:{lineWidth:8, strokeStyle:'rgb(189,11,11)'},
    -	anchors:["BottomCenter", "TopCenter"],
    -	endpoint:"Rectangle"
    -});</pre>
    -					</div>
    -				</li>
    -
    -
    -				<li>Connect window1 to window2 with a 15 px wide yellow Bezier. endpoints are a slightly lighter shade of yellow.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	anchors:["BottomCenter", [0.75,0,0,-1]],
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -</div></li>
    -<li>Connect window3 to window4 with a 10px wide blue-ish half transparent Bezier. put endpoints underneath the element they attach to.
    -the endpoints have a radial gradient. both ways of specifying gradient positioning are shown here.
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect( {
    -	source:'window3',
    -	target:'window4',
    -		 paintStyle:{lineWidth:10, strokeStyle:w34Stroke},
    -		 anchors:["RightMiddle", "LeftMiddle"],
    -		 endpointStyle:{ gradient : {stops:[[0, w34Stroke], [1, w34HlStroke]], offset:17.5, innerRadius:15 }, radius:35},
    -		 //endpointStyle:{ gradient : {stops:[[0, w34Stroke], [1, w34HlStroke]], offset:'78%', innerRadius:'73%'}, radius:35 },
    -		 endpointsOnTop:false
    -	}
    -);</pre>
    -</div></li>
    -<li>Connect window2 to window3 with an 8px red Bezier and default rectangular endpoints.  see also how the first anchor is
    -specified here - this is how you create anchors in locations jsPlumb does not offer shortcuts for.
    -the endpoints in this example have linear gradients applied.
    -<div class="code">
    -<pre>var w23Stroke = 'rgb(189,11,11)';
    -jsPlumb.connect({
    -	source:'window2',
    -	target:'window3',
    -	paintStyle:{lineWidth:8,strokeStyle:w23Stroke},
    -	anchors:[[0.3,1,0,1], "TopCenter"],
    -	endpoint:"Rectangle",
    -	endpointStyles:[{ gradient : {stops:[[0, w23Stroke], [1, '#558822']] }},
    -       				{ gradient : {stops:[[0, w23Stroke], [1, '#882255']] }}]
    -});</pre>
    -</div></li>
    -
    -<li>Connect window5 to window6 from center to center, 5px wide line that is green and half transparent. the endpoints are
    -125px in radius and spill out from underneath their elements.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window5',
    -	target:'window6',
    -	anchors:["Center", "Center"],
    -	paintStyle:{lineWidth:5,strokeStyle:'rgba(0,255,0,0.5)'},
    -	endpointStyle:{radius:125}
    -});</pre>
    -</div></li>
    -
    -<li>Connect window4 to window5 from bottom right to top left, with a 7px straight line purple connector, and an image as the endpoint,
    -placed on top of the element it is connected to.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window4",
    -	target:"window5",
    -	anchors:[ "BottomRight","TopLeft" ],
    -	paintStyle:{ lineWidth:7, strokeStyle:"rgb(131,8,135)" },
    -	endpoint:[ "Image", { src:"http://morrisonpitt.com/jsPlumb/img/endpointTest1.png" } ],
    -	connector:"Straight"
    -});</pre>
    -</div></li>
    -
    -
    -				<li>Connect window5 to window6 between their center points with a semi-opaque connector, and 125px endpoints:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window5",
    -	target:"window6",
    -	anchors:[ "Center", "Center" ],
    -	paintStyle:{ lineWidth:5, strokeStyle:"rgba(0,255,0,0.5)" },
    -	endpointStyle:{ radius:125 }
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window7 to window8 with a 10 pixel wide blue Connector, anchored on the top left of window7 and the bottom right of window8:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window7",
    -	target:"window8",
    -	paintStyle:{ lineWidth:10, strokeStyle:"blue" },
    -	anchors:[ "TopLeft", "BottomRight" ]
    -});
    -</pre>
    -					</div>
    -				</li>
    -				<li>Connect the bottom right corner of window4 to the top left corner of window5, with rectangular endpoints of size 40x40 and a hover color of light blue:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window4",
    -	target:"window5",
    -	anchors:["BottomRight","TopLeft"],
    -	paintStyle:{lineWidth:7,strokeStyle:'rgb(131,8,135)'},
    -	hoverPaintStyle:{ strokeStyle:"rgb(0, 0, 135)" },
    -	endpointStyle:{ width:40, height:40 },
    -	endpoint:"Rectangle",
    -	connector:"Straight"
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window1 to window2 with the default paint settings but provide some drag options (which are passed through to the underlying library's draggable call):
    -					<div class="code">
    -						<pre>jsPlumb.connect({source:'window1', target:'window2', dragOptions:{cursor:'crosshair'}});</pre>
    -					</div>
    -				</li>
    -</div>
    -		
    -		<div class="section">
    -			<a id="dragAndDropExamples"><h3>Draggable Connections Examples</h3></a>
    -			This is a list of examples of how to use jsPlumb to create Connections using drag and drop.
    -			<p>
    -			The basic procedure is:
    -			<ol>
    -				<li>1a. Create Endpoints and register them on elements in your UI</li>
    -				<li>1b. alternatively, create a source endpoint and then make some element a drop target </li>
    -				<li>2. Drag and Drop</li>
    -			</ol>
    -			That's all there is to it.  Of course there are plenty of options you can set when doing this...it will be easier
    -			to show you some examples:
    -			</p>
    -			<ul>
    -				<li>Define an Endpoint with default appearance, that is both a source and target of new Connections:
    -<div class="code">
    -<pre>var endpointOptions = { isSource:true, isTarget:true }; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Register that Endpoint on window3, specifying that it should be located in the top center of the element:
    -<div class="code">
    -<pre>var window3Endpoint = jsPlumb.addEndpoint('window3', { anchor:"TopCenter" }, endpointOptions );  
    -</pre>
    -</div>
    -Notice here the usage of the three-argument addEndpoint  - we can reuse 'endpointOptions' with a different Anchor for another element.  This is a useful
    -practice to get into.<br/><br/>
    -				</li>
    -				
    -				<li>Now register that Endpoint on window4, specifying that it should be located in the bottom center of the element:
    -<div class="code">
    -<pre>var window4Endpoint = jsPlumb.addEndpoint('window4', { anchor:"BottomCenter" }, endpointOptions );  
    -</pre>
    -</div>
    -Now we have two Endpoints, both of which support drag and drop of new Connections.  We can use these to make a programmatic Connection, too, though:<br/><br/>
    -				</li>				
    -				
    -				<li>Connect window3 to window4 with a 25px wide yellow Bezier that has a 'curviness' of 175:
    -<div class="code">
    -<pre>jsPlumb.connect({ 
    -	source:window3Endpoint,
    -	target:window4Endpoint,
    -	connector: [ "Bezier", 175 ],
    -	paintStyle:{ lineWidth:25, strokeStyle:'yellow' }
    -});  
    -</pre>
    -</div>
    -				</li>
    -				
    -				<li>Define an Endpoint that creates Connections that are 20px wide straight lines, that is both a source and target of new Connections,
    -				and that has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Define an Endpoint that will be anchored to "TopCenter".  It creates Connections that are 20px wide straight lines, that is both a source and target of new Connections,
    -				and that has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchor:"TopCenter", 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Define an Endpoint that will create a dynamic anchor which can be positioned at "TopCenter" or "BottomCenter".  It creates Connections that are 20px wide straight lines, it is both a source and target of new Connections,
    -				and it has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchor:[ "TopCenter", "BottomCenter" ], 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Exactly the same as before, but shows how you can use "anchors" instead of "anchor", if that makes you feel happier:
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchors:[ "TopCenter", "BottomCenter" ], 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>								
    -				
    -<li>Define an Endpoint that is a 30px blue dot, creates Connections that are 20px wide straight lines, is both a source and target of new Connections,
    -				has a 'scope' of 'blueline', and has an event handler that pops up an alert (note: the event handler name means this example is jQuery - MooTools
    -				and YUI3 use different event handler names):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{fillStyle:'blue'},
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ drop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -			<li>Same example as before, but this is for MooTools, and the Endpoint can support up to 5 connections (the default is 1):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{ fillStyle:'blue' },
    -	maxConnections:5,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ onDrop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Same example again, but maxConnections being set to -1 means that the Endpoint has no maximum limit of Connections:
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", {radius:30} ],
    -	style:{ fillStyle:'blue' },
    -	maxConnections:-1,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ onDrop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Same example again, but for YUI3.  Note the drop callback is "drop:hit":
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{fillStyle:'blue'},
    -	maxConnections:-1,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ "drop:hit":function(e, ui) { alert('drop!'); } }	 	
    -}; 
    -</pre>
    -</div>				 				
    -				</li>				
    -				
    -	<li>Assign a UUID to the endpoint options created above, and add as Endpoints to "window1" and "window2":
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("window1", { uuid:"abcdefg" }, endpointOptions );
    -jsPlumb.addEndpoint("window2", { uuid:"hijklmn" }, endpointOptions );
    -</pre>
    -</div>	
    -	</li>
    -	
    -	<li>Connect the two Endpoints we just registered on "window1" and "window2":
    -<div class="code">
    -<pre>
    -jsPlumb.connect({uuids:["abcdefg", "hijklmn"]});
    -</pre>
    -</div>	
    -	</li>
    -	
    -	<li>Create a source Endpoint, register it on some element, then make some other element a Connection target:
    -<div class="code">
    -<pre>
    -var sourceEndpoint = { isSource:true, endpoint:[ "Dot", { radius:50 } ] };
    -var targetEndpoint = { endpoint:[ "Rectangle", { width:10, height:10 } ] };
    -jsPlumb.addEndpoint( "window1", sourceEndpoint );
    -jsPlumb.makeTarget( "window2", targetEndpoint );
    -</pre>
    -</div>	
    -Notice that the endpoint definition we use on the target window does not include the "isTarget:true" directive.  jsPlumb ignores that flag when
    -creating a connection using an element as the target; but if you then tried to drag another connection to the Endpoint that was created, you would
    -not be able to.  To permit that, you would set 'isTarget:true' on the targetEndpoint options defined above.
    -	
    -	</li>
    -				
    -			</ul>
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="utilityFunctions">Utility Functions</a></h3>
    -			<ul>
    -				<li>Detach window5 from all connections
    -					<div class="code">
    -						<pre>jsPlumb.detachAll("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Hide all window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.hide("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Hide all window5's connections endpoints
    -					<div class="code">
    -						<pre>jsPlumb.hide("window5", true);</pre>
    -					</div>
    -				</li>
    -				<li>Show all window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.show("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Show all window5's connections and endpoints.  Note that in the case that you call jsPlumb.show with two arguments, jsPlumb
    -				will also not make a connection visible if it determines that the other endpoint in the connection is not visible.
    -					<div class="code">
    -						<pre>jsPlumb.show("window5", hide);</pre>
    -					</div>
    -				</li>				
    -				<li>Toggle the visibility of window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.toggle("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of all of window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.repaint("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of all of window5, window6 and window11's connections
    -					<div class="code">
    -						<pre>jsPlumb.repaint( [ "window5", "window6", "window11" ] );</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of every connection
    -					<div class="code">
    -						<pre>jsPlumb.repaintEverything();</pre>
    -					</div>
    -				</li>
    -				<li>Detach every connection
    -					<div class="code">
    -						<pre>jsPlumb.detachEverything();</pre>
    -					</div>
    -				</li>
    -				<li>Remove the given Endpoint from element "window1", deleting its Connections. 
    -					<div class="code">
    -						<pre>jsPlumb.removeEndpoint("window1", someEndpoint);</pre>
    -					</div>
    -				</li>
    -				<li>Remove all Endpoints for the element 'window1', deleting their Connections.
    -					<div class="code">
    -						<pre>jsPlumb.removeAllEndpoints("window1");</pre>
    -					</div>
    -				</li>	
    -				<li>Removes every Endpoint managed by this instance of jsPlumb, deleting all Connections.
    -				This is the same as jsPlumb.reset(), effectively, but it does not clear out the event listeners list. 
    -					<div class="code">
    -						<pre>jsPlumb.removeEveryEndpoint();</pre>
    -					</div>
    -				</li>
    -				<li>Deletes the given Endpoint and all its Connections. 
    -					<div class="code">
    -						<pre>jsPlumb.deleteEndpoint(endpoint);</pre>
    -					</div>
    -				</li>
    -				<li>Removes every endpoint, detaches every connection, and clears the event listeners list.  Returns jsPlumb instance to its initial state.  
    -					<div class="code">
    -						<pre>jsPlumb.reset();</pre>
    -					</div>
    -				</li>				
    -				<li>Set window1 to be not draggable, no matter what some jsPlumb command may request.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggable("window1", false);</pre>
    -					</div>
    -				</li>
    -				<li>Set window1 and window2 to be not draggable, no matter what some jsPlumb command may request.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggable(["window1","window2"], false);</pre>
    -					</div>
    -				</li>
    -				<li>Sets whether or not elements that are connected are draggable by default.
    -				The default for this is true.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggableByDefault(false);</pre>
    -					</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable("window1");</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 and window2 as draggable elements (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(["window1","window2"]);</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable("window1");</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (jQuery)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (MooTools)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($$(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (YIU3)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(Y.all(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (jQuery)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($("#window1"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (MooTools)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($("window1"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (YUI3)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(Y.one("window1"));</pre>
    -				</div>
    -				</li>
    -			</ul>
    -		</div>					
    -		
    -		<div class="section">
    -			<h3><a id="developingJsPlumb">Advanced Topics</a></h3>
    -			<h4><a id="which">Which files are which?</a></h4>
    -			In development, jsPlumb is broken up into seven scripts:
    -				<ul>
    -					<li>- jsPlumb-x.x.x.js
    -					<p>This is the main jsPlumb engine.</p>					
    -					</li>
    -					<li>- jsPlumb-defaults-x.x.x.js
    -					<p>This contains the default Anchor, Endpoint, Connector and Overlay implementations.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-canvas-x.x.x.js
    -					<p>This contains the HTML5 canvas render code.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-svg-x.x.x.js
    -					<p>This contains the SVG render code.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-vml-x.x.x.js
    -					<p>This contains the VML render code.</p>
    -					</li>
    -					<li>- jsPlumb-connectors-statemachine-x.x.x.js
    -					<p>This State Machine connector.</p>
    -					</li>
    -					<li>- &lt;LIBRARY_PREFIX&gt;.jsPlumb-x.x.x.js
    -					<p>This contains library-specific helper methods.  jsPlumb ships with three of these - one each for jQuery, MooTools and YUI3. See below for information on how to create a new library implementation.</p>
    -					</li>
    -					<li>- jsBezier-0.3-min.js
    -					<p>These are the Bezier curve functions; they are maintained in a separate project called <a href="http://code.google.com/p/jsbezier/">jsBezier</a></p>
    -					</li>
    -				</ul>
    -			These seven files are packaged together to form the scripts that people use, for example:
    -			<ul>
    -				<li>jquery.jsPlumb-1.3.5-all.js
    -				<p>Contains jsPlumb-1.3.5.js, jsPlumb-defaults-1.3.5.js, jsPlumb-renderers-canvas-1.3.5.js, jsPlumb-renderers-svg-1.3.5.js, jsPlumb-renderers-vml-1.3.5.js, jsPlumb-connectors-statemachine-1.3.5.js, jquery.jsPlumb-1.3.5.js and jsBezier-0.3-min.js</p>
    -				</li>
    -				<li>jquery.jsPlumb-1.3.5-all-min.js
    -				<p>A minified version of the script above (minified using the YUI Compressor)</p>
    -				</li>
    -			</ul>
    -			<h4><a id="pluggableLibrarySupport">Pluggable Library Support</a></h4>
    -		Out of the box, jsPlumb can be run on top of jQuery, MooTools or YUI3.  This is achieved by
    -		delegating several core methods - tasks such as finding an element by id, finding an element's
    -		position or dimensions, initialising a draggable, etc - to the library in question.
    -		<p>To develop one of these, your test page should include the first two scripts discussed above, and
    -		then your own script containing your library specific functionality.  The existing implementations
    -		may be documented well enough for you to create your own, but contact me if you need assistance.  If you do this, it would be
    -		great to share it with everyone...</p>   
    -		<p>There is a group of people working on an ExtJS adapter currently.  There is no schedule for when this will be completed though.</p>
    -
    -			<!-- h4><a id="customConnectors">Custom Connectors</a></h4>			
    -					
    -			You can provide your own connectors if you need to. A Connector consists of two functions, which work as a pair.  First a call is made to the <em>compute</em> function:
    -
    -			<div class="code">
    -<pre>
    -this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) {
    -	...
    -	return dimensions;
    -}
    -</pre>
    -			</div>
    -			which is expected to return a list that the <em>paint</em> function can make sense of.  The first four entries in the
    -			list <strong>must be</strong> the [x,y,width,height] values for the canvas that the connector will be drawn on; jsPlumb will
    -			use this information to size the canvas prior to calling the Connector's <em>paint</em> function.  Therefore it
    -			is the Connector's responsibility to ensure that the returned dimensions describe a large enough space for
    -			the line that will be drawn on it.</p><p>The next four elements <strong>must be</strong> the coordinates of the two endpoints of the line
    -			you are going to draw.</p><p>The remainder of the items in the returned list are arbitrary, and will
    -			vary between Connector implementations; this list is passed in to a Connector's <em>paint</em> function, so each
    -			implementation will put into the list whatever it needs to paint itself.  For instance, the straight line
    -			connector only needs the [x,y] location of each end of the line it will paint, and that is one of the required entries, so
    -			it does not have to do anything extra,  whereas the Bezier connector adds the location of the two control points.  Other types of
    -			Connectors will do whatever is appropriate for their particular situation.
    -			</p>
    -			This is the method signature for the <em>paint</em> function:
    -			<div class="code">
    -				<pre>this.paint = function(dimensions, ctx) { .. }</pre>
    -			</div>
    -
    -			here, the 'dimensions' argument to the 'paint' function is the return value of the 'compute' function.  The 'ctx' argument is the Canvas context; you
    -			will do all your drawing on this.
    -			</p>
    -			To change the connector from the default, specify it in your connect call:
    -			<div class="code">
    -				<pre>jsPlumb.connect({source:'someWindow', target:'otherWindow', connector:new jsPlumb.Connectors.Straight()});</pre>
    -			</div>
    -			note that you can use shorthand for connectors if you don't need to specify parameters to it:
    -			<div class="code">
    -				<pre>jsPlumb.connect({source:'someWindow', target:'otherWindow', connector:"Straight"});</pre>
    -			</div>
    -			This works in the same way as specifying Anchors as string does.
    -<p>
    -The section below this discusses Overlays, which allow you to decorate Connectors with
    -			things such as labels or arrows or whatever else you like. Overlays can only work with Connectors that implement a few helper functions.
    -			<h4>The concept of <strong>location</strong></h4>
    -			Before discussing the helper functions you first must be across the concept of 'location' as used by this mechanism.  The <em>location</em>
    -			on a connector can be a decimal value between 0 and 1 inclusive, which marks a proportionate amount of travel <em>along the path
    -			inscribed by the Connector</em>.  For a straight line connector this is a simple piece of maths, but for Bezier connectors it's a little
    -			bit more involved.
    -			<h4>Required Helper Methods</h4>
    -			<ul>
    -				<li><strong>pointOnPath(location)</strong> - returns an [x,y] point corresponding to the given location</li>
    -				<li><strong>pointAlongPathFrom(location, distance)</strong> - returns an [x,y] point corresponding to travelling 'distance' pixels along the connector from 'location'.</li>
    -				<li><strong>gradientAtPoint(location)</strong> - returns the connector's gradient at the given location.  For linear connectors such as Straight this is constant, but for Bezier connectors the gradient changes continually.</li>
    -				<li><strong>perpendicularToPathAt(location, distance, length)</strong> - returns a line that is perpendicular to (and centered on) the connector at 'distance' from the given location, with length 'length'.</li>
    -			</ul>
    -</p>
    -<h4><a id="customEndpoints">Custom Endpoints</a></h4>
    -<p>To create your own Endpoint implementation, you need to implement a single method:
    -				<div class="code"><pre>paint : function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { ... }</pre></div>
    -				The arguments to this method are as follows:
    -				<ul>
    -					<li>anchorPoint - [x,y] location of the anchor point on screen</li>
    -					<li>orientation - [x,y] hints for the general direction the anchor points to</li>
    -					<li>canvas - the canvas to draw into</li>
    -					<li>endpointStyle - Javascript object containing style directives as discussed above.  The contents of this are arbitrary, so if you write a new Endpoint that needs some extra settings, you can add them no hassle.</li>
    -					<li>connectorPaintStyle - the style being used to paint the associated Connector.</li>
    -				</ul>
    -			</p>
    -			<p>
    -				It is your responsibility to size and locate the canvas to suit your needs. jsPlumb provides the following
    -				helper method to assist you:
    -				<div class="code"><pre>jsPlumb.sizeCanvas(canvas, x, y, width, height);</pre></div>
    -				Allows you to locate the canvas on screen and to size it.
    -			</p>
    -<h4><a id="customOverlays">Custom Overlays</a></h4>
    -			Overlays can only work with Connectors that implement the methods <strong>pointOnPath</strong>, <strong>pointAlongPathFrom</strong>,
    -			<strong>gradientAtPoint</strong>, and <strong>perpendicularToPathAt</strong>.  These are discussed in more detail in the Custom Connectors section above.  Both of the Connectors
    -			that come with jsPlumb - Straight and Bezier -implement these methods; if you write a custom connector, or have written a custom connector, you will need to supply
    -			them.   
    -			<h4>Overlay Interface</h4>
    -			An Overlay is required to implement two methods in order to be usable by jsPlumb:
    -			<ul>
    -				<li><strong>computeMaxSize(connector, context)</strong> - returns an integer value indicating the larger of this overlay's width and height.  It is used by jsPlumb to ensure that the canvas is large enough to accomodate the overlay.  The examples below should help to clarify this.</li>
    -				<li><strong>draw(connector, ctx)</strong> - draws the overlay.  What happens in this method is up to the given implementation.</li>
    -			</ul>
    -			<h4>computeMaxSize methods</h4>
    -			This is the Arrow overlay's computeMaxSize method:
    -			<div class="code">
    -<pre>
    -this.computeMaxSize = function() { return width; }
    -</pre>			
    -			</div>
    -Here, <em>width</em> is a private member of Arrow that indicates the width of the arrow's tail.  So the Arrow overlay reports that figure as the width it needs.					
    -Contrast this with the Label Overlay's computeMaxSize method:
    -		<div class="code">
    -<pre>
    -this.computeMaxSize = function(connector, ctx) {
    -  	if (labelText) {
    -  		ctx.save();
    -        if (self.labelStyle.font) ctx.font = self.labelStyle.font;
    -         var t = ctx.measureText(labelText).width;			            
    -		// a fake text height measurement: use the width of upper case M
    -		var h = ctx.measureText("M").width;					
    -		labelPadding = self.labelStyle.padding || 0.25;
    -		labelWidth = t + (2 * t * labelPadding);
    -		labelHeight = h + (2 * h * labelPadding);
    -		ctx.restore();
    -		return Math.max(labelWidth, labelHeight);
    -  	}
    - 	return 0;
    - };
    -</pre>			
    -			</div>
    -The Label overlay has to use the context to determine how big it will be on screen.
    -
    -<h4>draw methods</h4>
    -To give you a taste for how you can interact with a connector, consider the first few lines of the Arrow overlay's draw method:
    -<div class="code">
    -<pre>
    -this.draw = function(connector, ctx) {
    -	// this is the arrow head position
    -	var hxy = connector.pointAlongPathFrom(self.loc, length / 2);		
    -	// this is the center of the tail
    -	var txy = connector.pointAlongPathFrom(self.loc, -length / 2), tx = txy[0], ty = txy[1];
    -	// this is the tail vector
    -	var tail = connector.perpendicularToPathAt(self.loc, -length / 2, width);
    -	
    -	...
    -</pre>
    -</div>
    -	<p>The first two calls to the connector use the <strong>pointAlongPathFrom</strong> method, which passes <em>self.loc</em> as the location of the
    -	point on the path, and <em>length / 2</em> as the number of pixels along the path to traverse.</p>
    -	<p><em>self.loc</em> is Arrow's internal member describing the location of the overlay, and it is a decimal (between 0 and 1 inclusive) that points to
    -	a location at some distance along the path inscribed by the connector.  So these first two calls get us [x,y] locations of points on 
    -	the connector that mark the head and tail points for the arrow.  
    -	</p>		
    -	<p>
    -	The <strong>connector.perpendicularToPathAt(self.loc, -length / 2, width)</strong> call returns a line description of a line that is perpendicular to, and centered on, the connector
    -	at "-length / 2" pixels from the given point, with the given width (width is an internal member of Arrow).  At this point, the Arrow object has the three main points it
    -	needs in order to draw itself.
    -	</p>
    -		</div-->
    -</div>
    -
    -</body>
    -
    -</html>
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.5/index.html b/demo/doc/archive/1.3.5/index.html
    deleted file mode 100644
    index 2743b0a88..000000000
    --- a/demo/doc/archive/1.3.5/index.html
    +++ /dev/null
    @@ -1,78 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.5 documentation</title>
    -		<link rel="stylesheet" target="contentFrame" href="/mp.css"></link>
    -		<link rel="stylesheet" target="contentFrame" href="jsPlumbDoc.css"></link>
    -	</head>
    -	<body>
    -		<div class="index section">
    -			<h3>jsPlumb 1.3.5</h3>
    -			<ul>
    -				<li><a target="contentFrame" href="content.html#summary">Summary</a></li>
    -				<li><a target="contentFrame" href="content.html#changes">Changes since version 1.3.4</a></li>
    -				
    -				<li><h4>Setup</h4></li>
    -				<li><a target="contentFrame" href="content.html#imports">Required Imports</a></li>
    -				<li><a target="contentFrame" href="content.html#renderMode">Render Mode</a></li>
    -				<li><a target="contentFrame" href="content.html#initializing">Initializing jsPlumb</a></li>
    -				<li><a target="contentFrame" href="content.html#unload">Unloading jsPlumb</a></li>
    -				<li><a target="contentFrame" href="content.html#multipleInstances">Multiple jsPlumb Instances</a></li>
    -				<li><a target="contentFrame" href="content.html#zIndex">Z-index Considerations</a></li>
    -				<li><a target="contentFrame" href="content.html#containerDefault">Where does jsPlumb add elements?</a></li>
    -				
    -				<li><h4>Introduction</h4></li>
    -				<li><a target="contentFrame" href="content.html#jsPlumbBasics">Basic Concepts</a></li>
    -				<li><a target="contentFrame" href="content.html#anchors">Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#staticAnchors">Static Anchors</a></li>				
    -				<li><a target="contentFrame" href="content.html#dynamicAnchors">Dynamic Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#continuousAnchors">Continuous Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#definitions">Connector, Endpoint & Overlay Definitions</a></li>
    -				<li><a target="contentFrame" href="content.html#connectors">Connectors</a></li>
    -				<li><a target="contentFrame" href="content.html#endpoints">Endpoints</a></li>
    -				<li><a target="contentFrame" href="content.html#overlays">Overlays</a></li>
    -				<li><a target="contentFrame" href="content.html#defaults">Defaults</a></li>								
    -				
    -				<li><h4>Connections</h4></li>
    -				<li><a target="contentFrame" href="content.html#simpleConnections">Programmatic Connections</a></li>
    -				<li><a target="contentFrame" href="content.html#draggableConnections">Connections using Drag and Drop</a></li>
    -				<li><a target="contentFrame" href="content.html#dragOptions">Drag Options</a></li>
    -				<li><a target="contentFrame" href="content.html#dropOptions">Drop Options</a></li>
    -				<li><a target="contentFrame" href="content.html#dragAndDropScope">Drag and Drop Scope</a></li>
    -				
    -				<li><h4>Interceptors</h4></li>
    -				<li><a target="contentFrame" href="content.html#beforeDrag">Before Drag</a></li>
    -				<li><a target="contentFrame" href="content.html#beforeDrop">Before Drop</a></li>
    -				
    -				<li><h4>Appearance</h4></li>
    -				<li><a target="contentFrame" href="content.html#paintstyles">Paint Styles</a></li>
    -				<li><a target="contentFrame" href="content.html#hoverpaintstyles">Hover Paint Styles</a></li>
    -				<li><a target="contentFrame" href="content.html#gradients">Gradients</a></li>				
    -				
    -				<li><h4>Miscellaneous</h4></li>
    -				<li><a target="contentFrame" href="content.html#cssclasses">CSS Class Reference</a></li>
    -				<li><a target="contentFrame" href="content.html#animation">Animation</a></li>
    -				<li><a target="contentFrame" href="content.html#connectionInfo">Retrieving Connection Information</a></li>
    -				
    -				<li><h4>Events</h4></li>
    -				<li><a target="contentFrame" href="content.html#events">jsPlumb Events</a></li>
    -				<li><a target="contentFrame" href="content.html#connectionEvents">Connection Events</a></li>
    -				<li><a target="contentFrame" href="content.html#endpointEvents">Endpoint Events</a></li>
    -				<li><a target="contentFrame" href="content.html#overlayEvents">Overlay Events</a></li>
    -				
    -				<li><h4>Examples</h4></li>				
    -				<li><a target="contentFrame" href="content.html#examples">Programmatic API Examples</a></li>
    -				<li><a target="contentFrame" href="content.html#dragAndDropExamples">Draggable Connections Examples</a></li>
    -				<li><a target="contentFrame" href="content.html#utilityFunctions">Utility Functions</a></li>				
    -				
    -				<li><h4>Develop</h4></li>
    -				<li><a target="contentFrame" href="content.html#which">Which files are which?</a></li>
    -				<li><a target="contentFrame" href="content.html#pluggableLibrarySupport">Pluggable Library Support</a></li>
    -				<!-- li><a target="contentFrame" href="content.html#customConnectors">Custom Connectors</a></li>
    -				<li><a target="contentFrame" href="content.html#customEndpoints">Custom Endpoints</a></li>
    -				<li><a target="contentFrame" href="content.html#customOverlays">Custom Overlays</a></li-->
    -			</ul>
    -			<strong>This document refers to release 1.3.5 of jsPlumb.</strong>  
    -			<strong>1 Feb 2012</strong>
    -		</div>		
    -	</body>
    -</html>
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.5/jsPlumbDoc.css b/demo/doc/archive/1.3.5/jsPlumbDoc.css
    deleted file mode 100644
    index bbe50c8ee..000000000
    --- a/demo/doc/archive/1.3.5/jsPlumbDoc.css
    +++ /dev/null
    @@ -1,126 +0,0 @@
    -body { 
    -	background-color:white; font-size:90%;
    -	background-image:url(../img/dynamicAnchorBg.jpg); 
    -}
    -/* docs */
    -.code { background-color:#e4e5ef; 
    -border:1px solid black; margin:1em; padding:0.8em; 
    -overflow:auto;
    -font-family:courier; 
    -font-size:8pt;
    -}
    -.section { border-bottom: 1px dotted black; padding:1em;font-family:helvetica;background-color:white;color:#333;}
    -.menu { 
    -	position:fixed; 
    -	right:10em; top:1em;
    -	background-color:white;
    -	padding:0.3em;
    -	opacity:0.8;
    -	z-index:5000;
    -	filter:alpha(opacity=80);
    -		box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -a {text-decoration:none;color:#05f;font-weight:bold;font-family:helvetica;}
    -a:hover {text-decoration:underline;}
    -.window, .label { 
    -	cursor:pointer;
    -	box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -#explanation { 
    -	background-color:white;
    -	color:black;
    -	border:0.25em solid #ddd; 
    -	position:absolute;
    -	right:3.5em;
    -	top:5em;
    -	width:22em;
    -	font-size:100%;
    -	-moz-border-radius:1em;
    -	border-radius:1em;
    -	padding:1em;
    -	z-index:1000;
    -	opacity:0.9;
    -	filter:alpha(opacity=90); 
    -	font-family:helvetica;
    -		box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -.selected {
    -	color:rgb(189,11,11);
    -}
    -
    -
    -/*  these styles are what jsPlumb attaches to the various elements it creates.
    -._jsPlumb_connector {
    -border:1px solid blue;
    -}
    -._jsPlumb_endpoint {
    -border:1px solid green;
    -}
    -._jsPlumb_overlay {
    -border:1px solid yellow;
    -}
    -*/
    -
    -
    -.section li {
    -list-style-type:none;
    -}
    -
    -li.bullet {
    -	list-style-type:circle;
    -	}
    -
    -
    -.section {
    -padding-left:2em;
    -font-size:10pt;
    -}
    -.index.section {
    -padding-left:0;
    -}
    -
    -.index {
    -width:20em;
    -font-size:9pt;
    -float:left;
    -background-color:#e4eeee;
    -overflow:auto;
    -padding-left:0.5em;
    -}
    -
    -.index li {
    -list-style-type:none;
    -}
    -
    -.index ul, .index h3 {
    -padding-left:1.5em;
    -
    -}
    -
    -#demo {
    -width:80%;
    -height:50em;
    -margin-left:3em;
    -margin-top:3em;
    -position:relative;
    -}
    -
    -.sub {
    -margin-left:1em;
    -}
    -
    -.table td {
    -	text-align:center;
    -}
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.5/usage.html b/demo/doc/archive/1.3.5/usage.html
    deleted file mode 100644
    index 361d3610a..000000000
    --- a/demo/doc/archive/1.3.5/usage.html
    +++ /dev/null
    @@ -1,10 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.5 - documentation</title>		
    -	</head>
    -	
    -	<frameset cols="250,*">
    -  		<frame src="index.html" />
    -  		<frame src="content.html" name="contentFrame" />
    -	</frameset>	
    -</html>
    diff --git a/demo/doc/archive/1.3.6/content.html b/demo/doc/archive/1.3.6/content.html
    deleted file mode 100644
    index a2da5b9a1..000000000
    --- a/demo/doc/archive/1.3.6/content.html
    +++ /dev/null
    @@ -1,2894 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.6 - documentation</title>
    -		<link rel="stylesheet" href="/mp.css">
    -		<link rel="stylesheet" href="jsPlumbDoc.css">
    -	</head>
    -	<body>
    -
    -<div class="menu">
    -	<a href="http://jsplumb.org" class="mplink" target="_top">view demo</a>
    -	&nbsp;|&nbsp;<a href="mailto:simon.porritt@gmail.com" class="mplink">contact me</a>
    -	&nbsp;|&nbsp;<a href="http://jsplumb.tumblr.com" class="mplink">jsPlumb on Tumblr</a>	
    -</div>
    -
    -<!--
    -GWT
    -
    -http://fishfilosophy.blogspot.com/2011/12/how-to-connect-gwt-widgets-using.html?spref=tw
    -
    --->
    -
    -<div class="content">
    -		<div class="section">
    -			<h3><a id="summary">Summary</a></h3>
    -			jsPlumb allows you to connect elements on the screen using SVG, Canvas or VML, depending on the capabilities of the browser.
    -			<p>
    -				It can be used with jQuery, MooTools or YUI3 (or another library of your choice if you feel like implementing an adapter for it).
    -			</p>  
    -
    -			<h5>Browser Compatibility</h5>
    -			<p>jsPlumb 1.3.6 has been tested on the following browsers:
    -				<ul>
    -					<li class="bullet">IE 6 on Windows XP</li>
    -					<li class="bullet">IE 7 on Windows XP</li>
    -					<li class="bullet">IE 8 on Windows XP</li>
    -					<li class="bullet">Firefox 3.5.8 on Windows XP</li>
    -					
    -					<li class="bullet">IE 9 on Windows 7</li>
    -					<li class="bullet">Chrome 12 on Windows 7</li>
    -					<li class="bullet">Firefox 3.5.8 on Windows 7</li>
    -					
    -					<li class="bullet">Firefox 3.6.3 on Ubuntu 10.04</li>
    -					<li class="bullet">Chrome on Ubuntu 11.04</li>
    -					
    -					<li class="bullet">Safari 4 on Mac Leopard</li>
    -					<li class="bullet">Chrome on Mac Leopard</li>					
    -					<li class="bullet">Safari 4 on Windows Vista</li>
    -					<li class="bullet">Safari 5.0.5 on Windows 7</li>
    -					
    -					<li class="bullet">Opera 10.54 on Windows XP</li>
    -				</ul>
    -			</p>
    -		</div>
    -		
    -		<div class="section">		
    -			<h3><a id="changes">Changes between 1.3.6 and 1.3.5</a></h3>
    -			<h5>Backwards Compatibility</h5>
    -			<ul>
    -				<li class="bullet"><strong>makeSource</strong> and <strong>makeTarget</strong> calls are now honoured by subsquent calls to jsPlumb.connect.  So if you call makeTarget on some element, providing an endpoint spec, and then later on call jsPlumb.connect with that element as the target, jsPlumb will use the endpoint spec you provided to makeTarget.  The thinking behind this is that typically you will want to setup some sources and/or targets for interactive connections, but then possibly load some initial state for the UI. Prior to this change you would have to provide the same information twice.<br/></li>
    -				<li class="bullet">The second argument to <strong>makeSource</strong> and <strong>makeTarget</strong> no longer requires the nested 'endpoint' parameter - they take the exact same object that you would pass to the second argument to addEndpoint.  Of course that object can itself have an 'endpoint' parameter, which is used to specify the type of endpoint (Dot, Rectangle etc) - kind of confusing, and yet another reason to unify the API a little.<br/></li>
    -				<li class="bullet">- Mouse events are always enabled from 1.3.6 onwards.  The default setting <strong>MouseEventsEnabled</strong> and the method <strong>setMouseEventsEnabled</strong> have both been removed.
    -			</ul>
    -			<h5>New Features</h5>
    -			The following new features have been added:
    -			<ul>
    -				
    -			</ul>
    -			<h5>Issues Resolved</h5>
    -			<ul>
    -				<li class="bullet">
    -					<strong>98</strong> - Endpoints nested inside draggable element are not refreshed on drag
    -				</li>
    -				<li class="bullet">
    -					<strong>195</strong> - deleteEndpointsOnDetach was being ignored by makeTarget method
    -				</li>
    -				
    -				<li class="bullet">
    -					<strong>204</strong> - add importDefaults method
    -				</li>
    -
    -				<!--li class="bullet">
    -					<strong>187</strong> - dynamic anchors broken for Endpoints
    -				</li>
    -				<li class="bullet">
    -					<strong>189</strong> - Endpoints draggable when they should not be
    -				</li>
    -				<li class="bullet">
    -					<strong>190</strong> - getInstance regressions
    -				</li>
    -				<li class="bullet">
    -					<strong>191</strong> - offsets calculated wrongly in certain situations
    -				</li-->
    -
    -			</ul>
    -
    -
    -			
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="init">Setup</a></h3>
    -
    -            <h4><a id="imports">Required Imports</a></h4>
    -			<h4>jQuery</h4>
    -			    <ul>
    -				    <li class="bullet">jQuery 1.3.x or higher.</li>
    -				    <li class="bullet">jQuery UI 1.7.x or 1.8.x (if you wish to support drag and drop).</li>
    -			    </ul>
    -			    <div class="code">
    -<pre>&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/jquery-ui.min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/jquery.jsPlumb-1.3.6-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			<h4>MooTools</h4>
    -				<ul>
    -					<li class="bullet">MooTools Core 1.2.4 or higher. jsPlumb has been tested with 1.2.4 and 1.3.3.</li>
    -					<li class="bullet">Drag.Move from MooTools More 1.2.4.4 or higher (if you wish to support drag and drop).</li>
    -				</ul>
    -			<div class="code">
    -			<pre>&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/mootools/1.3.3/mootools-yui-compressed.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO_MOO_TOOLS_MORE_1_3_2_1"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/mootools.jsPlumb-1.3.6-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			<h4>YUI3</h4>
    -			<ul>
    -				<li class="bullet">YUI 3.3.x.  jsPlumb has been tested on 3.3.0 only; it is possible other 3.x.x versions will work.</li>
    -			</ul>
    -			<div class="code">
    -			<pre>&lt;script type="text/javascript" src="http://yui.yahooapis.com/3.3.0/build/simpleyui/simpleyui-min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/yui.jsPlumb-1.3.6-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			
    -			<h4><a id="renderMode">Render Mode</a></h4>
    -			jsPlumb can use SVG, HTML5 Canvas elements or VML to render the various objects it adds to the display.  Most modern browsers support
    -			Canvas and SVG; IE&lt;9 browsers support neither.
    -			<p>
    -				By default, jsPlumb attempts to use <strong>jsPlumb.SVG</strong> as the render mode (prior to 1.3.4, the default renderer was Canvas), falling back to <strong>jsPlumb.VML</strong> if the 
    -				browser is IE&lt;9.  You can change the render mode by making this call:  
    -			</p>
    -			<div class="code">
    -<pre>jsPlumb.setRenderMode(jsPlumb.CANVAS);</pre>			
    -			</div>
    -			if, for some reason, you set the render mode to be <strong>jsPlumb.VML</strong> but you're in any browser other than IE&lt;9, jsPlumb
    -			will use SVG.
    -			<p>
    -			The <strong>jsPlumb.setRenderMode</strong> method returns you the render mode that jsPlumb actually ended up setting.  Valid values for setRenderMode are:
    -			</p>
    -			<ul>
    -				<li class="bullet">jsPlumb.CANVAS</li>
    -				<li class="bullet">jsPlumb.SVG</li>
    -				<li class="bullet">jsPlumb.VML</li>
    -			</ul>			
    -			<h5>Performance Considerations</h5>
    -			<p>
    -			SVG uses less memory than Canvas and has support for mouse events built in, so I recommend using
    -			SVG unless you have a really good reason for wanting Canvas.
    -			</p>
    -			
    -			<h4><a id="initializing">Initializing jsPlumb</a></h4>
    -			You should not start making calls to jsPlumb until the DOM has been initialized - perhaps no surprises there.  With YUI, though, the asynchronous
    -			nature of the script loading process means that you are not guaranteed that jsPlumb is ready as soon as the DOM is.  To handle this,
    -			you should bind to the "ready" event on jsPlumb (or the instance of jsPlumb you are working with):
    -			<div class="code">
    -			<pre>jsPlumb.bind("ready", function() {
    -...	
    -		
    -// your jsPlumb related init code goes here
    -
    -...
    -
    -});						
    -			</pre>
    -			</div>
    -			There's a helper method that can save you approximately 9 (count them!) characters:
    -			<div class="code">
    -			<pre>jsPlumb.ready(function() {
    -...	
    -		
    -// your jsPlumb related init code goes here
    -
    -...
    -
    -});						
    -			</pre>
    -			</div>
    -			<p>
    -			For jQuery and MooTools you do not actually need to do this; by the time the DOM ready event is fired in each of those libraries you can be sure 
    -			all the JS we need has been loaded.  But in terms of explicitly calling out that you are waiting for jsPlumb, it seems like a good practice to bind to the "ready" event.
    -			</p>
    -			<p>
    -			If you bind to the "ready" event after jsPlumb has already been initialized, your callback will be executed immediately.
    -			</p>			
    -			<h4><a id="unload">Unloading jsPlumb</a></h4>
    -			jsPlumb offers a method you can call when your page is unloading.  You should do this to insure
    -			against memory leaks.  You configure it like this:
    -				<div class="code">
    -<pre>
    -&lt;body onunload="jsPlumb.unload();"&gt;
    -
    -...
    -
    -&lt;/body&gt;
    -</pre>
    -				</div>
    -<h4><a id="multipleInstances">Multiple jsPlumb instances</a></h4>
    -			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 <strong>getInstance</strong> method, for example:
    -<div class="code">
    -<pre>
    -var firstInstance = jsPlumb.getInstance();
    -</pre>
    -</div>
    -The variable 'firstInstance' can now be treated exactly as you would treat the 'jsPlumb' variable - you can set
    -defaults, call the connect method, whatever:
    -<div class="code">
    -<pre>
    -firstInstance.Defaults.Connector = [ "Bezier", { curviness: 150 } ];
    -firstInstance.Defaults.Anchors = [ "TopCenter", "BottomCenter" ];
    -
    -firstInstance.connect({source:"element1", target:"element2", scope:"someScope" });
    -</pre>
    -</div>
    -
    -<strong>getInstance</strong> optionally takes an object that provides the defaults:
    -<div class="code">
    -<pre>
    -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" });
    -</pre>
    -</div>
    -
    -		<h4><a id="zIndex">Z-index Considerations</a></h4>
    -		<p>
    -			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. 
    -		</p>  
    -		<p>
    -			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).  			
    -		</p>
    -		<p>
    -			To help you organise z-indices correctly, jsPlumb adds a CSS class to each type of
    -			element it adds. They are as follows:			
    -		</p>
    -		<table style="color:black;width:70%;font-size:90%;">
    -			<tr><td><strong>type of element</strong></td><td><strong>class added</strong></td></tr>
    -			<tr><td>Endpoint</td><td>_jsPlumb_endpoint</td></tr>
    -			<tr><td>Connector</td><td>_jsPlumb_connector</td></tr>
    -			<tr><td>Overlay</td><td>_jsPlumb_overlay</td></tr>
    -		</table>
    -
    -		<h4><a id="containerDefault">Where does jsPlumb add elements?</a></h4>
    -		<p>
    -			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.
    -		</p>
    -		<p>
    -			Prior to 1.3.0, jsPlumb added everything to the end of the <em>body</em> element. This
    -			has the advantage of being the most flexible arrangement in terms of supporting what
    -			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!   
    -		</p>
    -		<p>
    -			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 (<strong>not</strong> 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.
    -		</p>
    -		
    -		<p>
    -			In general, the default behaviour means that you cannot do things like this:
    -		</p>
    -		<div class="code">
    -<pre>
    -&lt;body&gt;
    -	&lt;div id="container0"&gt;
    -		&lt;div id="node0"&gt;&lt;/div&gt;
    -	&lt;/div&gt;
    -	&lt;div id="container1"&gt;
    -		&lt;div id="node1"&gt;&lt;/div&gt;
    -	&lt;/div&gt;
    -&lt;/body&gt;
    -
    -&lt;script type="text/javascript"&gt;
    -
    -	var e0 = jsPlumb.addEndpoint("node0"),
    -	e1 = jsPlumb.addEndpoint("node1");
    -
    -	jsPlumb.connect({ source:e0, target:e1 });
    -&lt;/script&gt;
    -</pre>		
    -		</div>
    -<p>
    -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):  
    -</p>
    -		<div class="code">
    -<pre>
    -&lt;body&gt;
    -	&lt;div id="container0"&gt;
    -	&lt;/div&gt;
    -	&lt;div id="container1"&gt;
    -	&lt;/div&gt;
    -&lt;/body&gt;
    -
    -&lt;script type="text/javascript"&gt;
    -
    -	var e0 = jsPlumb.addEndpoint("container0"),
    -	e1 = jsPlumb.addEndpoint("container1");
    -
    -	jsPlumb.connect({ source:e0, target:e1 });
    -&lt;/script&gt;
    -</pre>		
    -		</div>
    -	now 'e0' and 'e1' have the same parent - the <em>body</em> element. 
    -	
    -	<p>Remember that you can use the <strong>anchor</strong> parameter to an <strong>addEndpoint</strong>
    -	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. 
    -	</p>									
    -	<p><strong>Note regarding the drawEndpoints option on jsPlumb.connect</strong>: 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). 
    -	</p>
    -	
    -	<h5>Overriding the default behaviour</h5>
    -	<p>
    -From version 1.3.3, the <strong>container</strong> 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 <strong>jsPlumb.Defaults.Container</strong> property, or you can set a <strong>container</strong> parameter on calls to <strong>addEndpoint</strong> or <strong>connect</strong> (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:
    -	</p>
    -		
    -Set a container to use as the default container, using a jQuery selector (you can supply MooTools/YUI selectors if that's the library you're using), and then add an Endpoint.  The canvas (or SVG/VML element) created will be a child of the document body:
    -		<div class="code">
    -<pre>
    -jsPlumb.Defaults.Container = $("body");
    -...
    -jsPlumb.addEndpoint(someDiv, { endpoint options });
    -</pre>		
    -		</div>
    -		
    -			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":
    -		<div class="code">
    -<pre>
    -jsPlumb.Defaults.Container = "containerId";
    -...
    -jsPlumb.connect({ source:someDiv, target:someOtherDiv });
    -</pre>		
    -		</div>
    -		
    -		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":
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({ source:someDiv, target:someOtherDiv, container:$("#containerId") });
    -</pre>		
    -		</div>
    -				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":
    -		<div class="code">
    -<pre>
    -jsPlumb.addEndpoint(someDiv, { ..., container:"containerId" });
    -</pre>		
    -		</div>
    -		
    -		</div>
    -				
    -		<div class="section">
    -			<h3><a id="jsPlumbBasics">Basic Concepts</a></h3>
    -	
    -			jsPlumb is all about connecting things together, so the core abstraction in jsPlumb is the <strong>Connection</strong> object, which is itself
    -			broken down into these four concepts:
    -
    -			<ul>
    -				<li><strong>Anchor</strong> - a location, relative to an element's origin, at which an Endpoint can exist. You do not create these yourself; you 
    -				supply hints to the various jsPlumb functions, which create them as needed.  They have no visual representation; they are a logical position only. 
    -				Anchors can be referenced by name, for the Anchors that jsPlumb ships with, or a four element array describing [ x, y, x orientation, y orientation ]. 
    -				See the <a href="#anchors">anchors</a> section for more detail.<br/><br/></li>
    -				<li><strong>Endpoint</strong> - the visual representation of one end of a Connection.  You can create and attach these to elements yourself, which you are required to do to support drag and drop, or have jsPlumb create them when creating a Connection programmatically using jsPlumb.connect(...).  You can also join two Endpoints programmatically, by passing them as arguments to jsPlumb.connect(...).<br/><br/></li>				
    -				<li><strong>Connector</strong> - the visual representation of the line connecting two elements in the page.  jsPlumb has four types of these 
    -				available as defaults - a Bezier curve, a straight line, 'flowchart' connectors and 'state machine connectors. You do not interact with Connectors; 
    -				you just specify definitions of them when you need to - see <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions</a> <br/><br/></li>
    -				<li><strong>Overlay</strong> - a UI component that is used to decorate a Connector, such as a Label, Arrow, etc.<br/><br/></li>
    -			</ul>
    -						
    -			<p>One Connection is made up of two Endpoints, a Connector, and zero or more Overlays working together to join two elements.</p>
    -			<p>jsPlumb's public API exposes only Connection and Endpoint, handling the creation and configuration of everything else internally.
    -			But you still need to be across the concepts encapsulated by Anchor, Connector and Overlay.
    -			</p>
    -			</div>
    -			
    -			<div class="section">
    -			<h3><a id="anchors">Anchors</a></h3>			
    -	
    -					An Anchor models the notion of where on an element a Connector should connect.  There are three main types of Anchors:
    -					<ul>
    -						<li class="bullet">'Static' anchors - these are fixed to some point on an element and do not move.  They can be specified using a string to
    -						identify one of the defaults that jsPlumb ships with, or an array describing the location (see below)</li>
    -						<li class="bullet">'Dynamic' anchors - these are lists of Static anchors from which jsPlumb picks the most appropriate one each time a Connection
    -						is painted.  The algorithm used to determine the most appropriate anchor picks the one that is closest to the center of the other element in
    -						the Connection. A future version of jsPlumb might support a pluggable algorithm to make this decision.</li>
    -						<li class="bullet">'Continuous' anchors - These anchors, introduced in 1.3.4, are not fixed to any specific loction; they are assigned to one of
    -						the four faces of an element depending on that element's orientation to the other element in the associated Connection.  Continuous anchors are
    -						slightly more computationally intensive than Static or Dynamic anchors because jsPlumb is required to calculate the position of every Connection
    -						during a paint cycle, rather than just Connections belonging to the element in motion.  These anchors were designed for use with the StateMachine
    -						connector that was also introduced in 1.3.4, but will work with any combination of Connector and Endpoint.</li> 
    -					</ul>  
    -					
    -			<h4><a id="staticAnchors">Static Anchors</a></h4>		
    -			jsPlumb has nine default anchor locations you can use to specify where the Connectors connect to elements: these are the four corners of an element,
    -			the center of the element, and the midpoint of each edge of the element:  
    -			<ul>
    -				<li class="bullet">TopCenter</li>
    -				<li class="bullet">TopRight</li>
    -				<li class="bullet">RightMiddle</li>
    -				<li class="bullet">BottomRight</li>
    -				<li class="bullet">BottomCenter</li>
    -				<li class="bullet">BottomLeft</li>
    -				<li class="bullet">LeftMiddle</li>
    -				<li class="bullet">TopLeft</li>
    -			</ul>
    -			
    -Each of these string representations is just a wrapper around the underlying array-based syntax  [x, y, dx, dy], where x and y are coordinates in the interval [0,1] specifying the position of the
    -anchor, and dx and dy are coordinates in the interval [-1, 1] specifying the orientation of the curve incident to the anchor. For example, [0, 0.5, -1, 0] defines a "LeftCenter" anchor with a connector curve that emanates leftward from the anchor. Similarly, [0.5, 0, 0, -1] defines a "CenterTop" anchor with a connector curve emanating upwards. 	
    -
    -
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:"BottomCenter", ... }); 
    -</pre>
    -</div>
    -is identical to:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:[ 0.5, 1, 0, 1 ], ... }); 
    -</pre>
    -</div>		
    -In addition to supplying the location and orientation of an anchor, you can optionally supply two more parameters that define an offset in pixels from the given location.  Here's the anchor specified above, but with a 50 pixel offset below the element in the y axis:	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:[ 0.5, 1, 0, 1, 0, 50 ], ... }); 
    -</pre>
    -</div>		
    -
    -		<h4><a id="dynamicAnchors">Dynamic Anchors</a></h4>
    -	These are Anchors that can be positioned in one of a number of locations, choosing the one that is most appropriate each time something moves or is painted in the UI.
    -	<p>
    -	There is no special syntax to creating a DynamicAnchor; you just provide an array of individual Static Anchor specifications, eg:
    -	</p>
    -<div class="code">
    -<pre>
    -var dynamicAnchors = [ [ 0.2, 0, 0, -1 ],  [ 1, 0.2, 1, 0 ], 
    -			   [ 0.8, 1, 0, 1 ], [ 0, 0.8, -1, 0 ] ];
    -
    -jsPlumb.connect({...., anchor:dynamicAnchors, ... }); 			   
    -</pre>
    -</div>
    -<h4>	Default Dynamic Anchor</h4>
    -jsPlumb provides a dynamic anchor called "AutoDefault" that chooses from TopCenter, RightMiddle, BottomCenter and LeftMiddle:
    -<div class="code">
    -<pre>jsPlumb.connect({...., anchor:"AutoDefault", ... });</pre>
    -</div>
    -
    -<h4>	Location Selection</h4>
    -The initial implementation of the algorithm that decides which location to choose just calculates which location is closest to the center of the 
    -other element in the connection.  It is possible that future versions of jsPlumb could support more sophisticated choice algorithms, if the need arose.
    -<h4>Draggable Connections</h4>
    -Dynamic Anchors and Draggable Connections can interoperate: jsPlumb locks the position of a dynamic anchor when you start to drag a connection from it,
    -and unlocks it once the connection is either established or abandoned. At that point you may see the position of the dynamic anchor change, as jsPlumb
    -optimises the connection.  <p>You can see this behaviour in the <a href="../html/jquery/draggableConnectorsDemo.html" target="_blank">draggable connections</a> demonstration, when
    -you drag a connection from the blue endpoint on window 1 to the blue endpoint on window 3 - the connection is established and then window 1's blue
    -endpoint jumps down to a location that is closer to window 3.</p>
    -
    -
    -	<h4><a id="continuousAnchors">Continuous Anchors</a></h4>
    -	As discussed above, these are anchors whose positions are calculated by jsPlumb according to the orientation between elements in a Connection, and also how
    -	many other Continuous anchors happen to be sharing the element.  You specify that you want to use Continuous anchors using the string syntax you would use
    -	to specify one of the default Static Anchors, for example:
    -	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:someDiv,
    -	target:someOtherDiv,
    -	anchor:"Continuous"
    -});
    -</pre>
    -</div>
    -
    -Note in this example I specified only "anchor", rather than "anchors" - jsPlumb will use the same spec for both anchors.  But I could have said this:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:someDiv,
    -	target:someOtherDiv,
    -	anchors:["BottomCenter", "Continuous"]
    -});
    -</pre>
    -</div>	 	  
    -..which would have resulted in the source element having a Static Anchor at BottomCenter.  In practise, though, it seems the Continuous Anchors work best if both 
    -elements in a Connection are using them.
    -<p>Note also that Continuous Anchors can be specified on <strong>addEndpoint</strong> calls:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint(someDiv, {
    -	anchor:"Continuous",
    -	paintStyle:{ fillStyle:"red" }
    -});
    -</pre>
    -</div>	 	
    -...and in <strong>makeSource</strong>/<strong>makeTarget</strong>:
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource(someDiv, {
    -	anchor:"Continuous",
    -	paintStyle:{ fillStyle:"red" }
    -});
    -
    -jsPlumb.makeTarget(someDiv, {
    -	anchor:"Continuous",
    -	paintStyle:{ fillStyle:"red" }
    -});
    -</pre>
    -</div>	 
    -... and in the jsPlumb defaults:
    -<div class="code">
    -<pre>
    -jsPlumb.Defaults.Anchor = "Continuous";
    -</pre>
    -</div>  
    -					</div>		
    -												
    -<div class="section">
    -		<h4><a id="definitions">Connector, Endpoint &amp; Overlay Definitions</a></h4>
    -Before we discuss Connector, Endpoints and Overlays, a quick word on definitions:  whenever you need to define a Connector, Endpoint or Overlay, you must use a "definition" of it, rather than constructing one directly.  This definition can be either a string that nominates the artifact you want to create:
    -		<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:"Rectangle"
    -});
    -</pre>
    -		</div>
    -		or an array consisting of both the artifact's name and the arguments you want to pass to its constructor:
    -		<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Rectangle", { cssClass:"myEndpoint", width:30, height:10 } ]
    -});
    -</pre>
    -		</div>
    -There is also a three-argument method that allows you to specify two sets of parameters, which jsPlumb will merge together for you. The idea behind this is that you will often want to define common characteristics somewhere and reuse them across a bunch of different calls:
    -		<div class="code">
    -<pre>var common = {
    -	cssClass	:	"myCssClass",
    -	hoverClass	:	"myHoverClass"
    -};
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Rectangle", { width:30, height:10 }, common ]
    -});
    -</pre>
    -		</div>
    -	This syntax is supported for all Endpoint, Connector and Overlay definitions.  Here's an example using definitions for all three:	
    -		<div class="code">
    -<pre>var common = {
    -	cssClass:"myCssClass"
    -};
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Dot", { radius:5, hoverClass:"myEndpointHover" }, common ],
    -	connector:[ "Bezier", { curviness:100 }, common ],
    -	overlays: [
    -		[ "Arrow", { foldback:0.2 }, common ],
    -		[ "Label", { cssClass:"labelClass" } ]	
    -	]
    -});
    -</pre>
    -		</div>		
    -The allowed constructor parameters are different for each artifact you create, but every artifact takes a single JS object as argument, with the parameters as [key,value] pairs in that object.  See the relevant sections on <a href="#connectors">Connectors</a>, <a href="#endpoints">Endpoints</a> and <a href="#overlays">Overlays</a> for information on supported parameters.</a>
    -				
    -	</div>
    -
    -		<div class="section">
    -			<h3><a id="connectors">Connectors</a></h3>
    -			<p>Connectors are the lines that actually join elements of the UI.  jsPlumb has four connector implementations - a straight line, a Bezier curve, "flowchart", and "state machine".  The default connector is the Bezier curve.</p>
    -			<p>You optionally specify a Connector by setting the 'connector' property on a call to jsPlumb.connect, jsPlumb.addEndpoint(s), jsPlumb.makeSource or jsPlumb.makeTarget. If you do not supply a value for 'connector', the default will be used (see <a href="#defaults">defaults</a>).</p>
    -			<p>You specify Connectors using the syntax described above in <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions.</a> Allowed constructor values for each Connector type are described below:</p>		
    -			<h4><a id="bezierConnector">Bezier Connector</a></h4>
    -			<p>The Bezier Connector provides a cubic Bezier path between the two Endpoints. It supports a single constructor argument:</p>
    -			<p>
    -			<strong>curviness</strong> - Optional; defaults to 150. This defines the distance in pixels that
    -			the Bezier's control points are situated from the anchor points.  This does not mean that your
    -			connector will pass through a point at this distance from your curve.  It is a hint to how you want the
    -			curve to travel. Rather than discuss Bezier curves at length here, because they are a complex topic,
    -			refer instead to <a href="http://en.wikipedia.org/wiki/B%C3%A9zier_curve" target="_blank">Wikipedia.</a>
    -			</p>
    -			<h4><a id="straightConnector">Straight Connector</a></h4>
    -			<p>The Straight Connector draws a straight line between the two endpoints. No constructor arguments are supported; use the <strong>endpointStyle</strong> and/or <strong>cssClass</strong>/<strong>connectorClass</strong> to a <strong>connect</strong>, <strong>addEndpoint</strong>, <strong>makeSource</strong> or <strong>makeTarget</strong> call to control the appearance of one of these Connectors.  
    -			</p>
    -			<h4><a id="flowchartConnector">Flowchart Connector</a></h4>
    -			<p>This type of Connector draws a connection that consists of a series of vertical or horizontal segments - the classic flowchart look. A single constructor argument is supported:</p>
    -			<strong>stub</strong> - this is the minimum length, in pixels, of the initial stub that emanates from an Endpoint.  This parameter is optional, and defaults to 30 pixels.
    -			<p>This Connector supports Connection that start and end on the same element.</p>
    -			<h4><a id="stateMachineConnector">State Machine Connector</a></h4>
    -			<p>This Connector draws slightly curved lines (they are actually quadratic Bezier curves), similar to the State Machine connectors you may have seen in software like GraphViz.  Connections in which some element is both the source and the target (so-called 'loopback') are supported by these Connectors (as they are with Flowchart Connectors); in this case you get a circle. Supported parameters are:
    -			</p>
    -			<p>
    -			<strong>margin</strong> - Optional; defaults to 5.  Defines the distance from the element that the connector begins/ends.
    -			</p>
    -			<p>
    -			<strong>curviness</strong> - Optional, defaults to 10.  This has a similar effect to the curviness parameter on Bezier curves.
    -			</p>
    -			<p>
    -			<strong>proximityLimit</strong> - Optional, defaults to 80.  The minimum distance between the two ends of the Connector before it paints itself as 
    -			a straight line rather than a quadratic Bezier curve.
    -			</p>    
    -			
    -		</div>
    -		
    -			<div class="section">
    -			<h3><a id="endpoints">Endpoint Types</a></h3>
    -			<p>An Endpoint is the UI component that marks the location of an Anchor, ie. the place where a Connector joins an
    -			element.  jsPlumb comes with four Endpoint implementations - <strong>Dot</strong>, <strong>Rectangle</strong>, <strong>Blank</strong> and <strong>Image</strong>. You optionally specify Endpoint properties using the
    -			<strong>endpoint</strong> parameter in a call to jsPlumb.connect, jsPlumb.addEndpoint, jsPlumb.makeSource or jsPlumb.makeTarget.
    -			</p>  
    -				<p>As with Connectors and Overlays, you specify Endpoints using the syntax described above in <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions.</a></p>
    -			
    -			The four available Endpoint types, and their constructor arguments, are as follows:
    -					<h4><a id="dotEndpoint">Dot Endpoint</a></h4>
    -					This Endpoint draw a dot on the screen. It supports three constructor parameters:
    -					<p>
    -						<strong>radius</strong> - Optional; defaults to 10 pixels. Defines the radius of the dot.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="rectangleEndpoint">Rectangle Endpoint</a></h4>
    -						Draws a rectangle. Supported constructor parameters are:
    -					<p>
    -						<strong>width</strong> - Optional; defaults to 20 pixels. Defines the width of the rectangle.
    -					</p>
    -					<p>
    -						<strong>height</strong> - Optional; defaults to 20 pixels. Defines the height of the rectangle.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="imageEndpoint">Image Endpoint</a></h4>
    -						Draws an image from a given URL.  This Endpoint supports three constructor parameters:  
    -					<p>
    -						<strong>src</strong> - Required.  Specifies the URL of the image to use.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="blankEndpoint">Blank Endpoint</a></h4>
    -						Does not draw anything visible to the user.  As discussed previously, this Endpoint is probably not what you want if you need your users to be able to drag existing Connections - for that, use a Rectangle or Dot Endpoint ans assign to it a CSS class that causes it to be transparent.
    -																			
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="overlays">Overlay Types</a></h3>
    -			<p>
    -			Overlays are UI elements that are painted onto connections, such as labels or arrows.
    -			jsPlumb comes with four defaults:
    -			</p>
    -			<ul>
    -				<li><strong>Arrow</strong> - a configurable arrow that is painted at some point along the connector.  You can control the length and width of the Arrow, the 'foldback' point - a point the tail points fold back into, and the direction (allowed values are 1 and -1; 1 is the default and means point in the direction of the connection)</li>
    -				<li><strong>Label</strong> - a configurable label that is painted at some point along the connector.</li>
    -				<li><strong>PlainArrow</strong> - an Arrow shaped as a triangle, with no foldback.</li>
    -				<li><strong>Diamond</strong> - as the name suggests, a diamond.</li>
    -			</ul> 
    -			The last two are actually just configured instances of the generic Arrow overlay (see examples). 
    -			
    -			<p>All Overlays support these two methods for getting/setting their location (location is a number between 0 and 1 inclusive, indicating some point along the path
    -			inscribed by the associated Connector):</p>
    -			<ul>
    -				<li class="bullet"><strong>getLocation</strong> - returns the current location</li>
    -				<li class="bullet"><strong>setLocation</strong> - sets the current location; valid values are decimals in the range 0-1 inclusive</li> 
    -			</ul>
    -			
    -			<p>You can specify one or more overlays when making a call to jsPlumb.connect, jsPlumb.addEndpoint or jsPlumb.makeSource (but not jsPlumb.makeTarget; overlays
    -			are always derived from what the source of a Connection defines)  The three cases are discussed below:</p>
    -			1. Specifying one or more overlays on a jsPlumb.connect call. In this example we'll create an Arrow with the default options for an Arrow, and
    -			a label with the text "foo":
    -			<div class="code">
    -			<pre>jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});			
    -</pre>
    -			</div>
    -			This connection will have an arrow located halfway along it, and the label "foo" one quarter of the way along.  Notice the <strong>id</strong>
    -			parameter; it can be used later if you wish to remove the Overlay or change its visibility (see below).<br/><br/> 
    -			
    -			2. Specifying one or more overlays on a jsPlumb.addEndpoint call.  Note in this example that we use the parameter 'connectorOverlays' and not
    -			'overlays' as in the last example.  This is because 'overlays' would refer to Endpoint Overlays:
    -			<div class="code">
    -			<pre>jsPlumb.addEndpoint("someDiv", {
    -	...
    -	connectorOverlays:[ 
    -		[ "Arrow", { width:10, length:30, location:1, id:"arrow" } ], 
    -		[ "Label", { label:"foo", id:"label" } ]
    -	],
    -	...
    -});</pre>
    -			</div>	
    -			This connection will have a 10x30 Arrow located right at the head of the connection, and the label "foo" located at the halfway point. Again,
    -			note that <strong>id</strong> parameter passed in to each Overlay spec.<br/><br/>
    -			
    -			3. Specifying one or more overlays on a jsPlumb.makeSource call.  Note in this example that we again use the parameter 'connectorOverlays' and not
    -			'overlays'.  The 'endpoint' parameter to jsPlumb.makeSource supports everything you might pass to the second argument of a jsPlumb.addEndpoint call:
    -			<div class="code">
    -			<pre>jsPlumb.makeSource("someDiv", {
    -	...
    -	endpoint:{
    -		connectorOverlays:[ 
    -			[ "Arrow", { width:10, length:30, location:1, id:"arrow" } ], 
    -			[ "Label", { label:"foo", id:"label" } ]
    -		]
    -	}
    -	...
    -});</pre>
    -			</div>	
    -			This connection will have a 10x30 Arrow located right at the head of the connection, and the label "foo" located at the halfway point. Again,
    -			note that <strong>id</strong> parameter passed in to each Overlay spec.<br/><br/>
    -			
    -			 	
    -					<h4><a id="arrowOverlay">Arrow Overlay</a></h4>
    -					<p>This overlay draws an arrow, using four points: the head and two tail points, and a 'foldback' point, which permits the tail
    -					of the arrow to be indented. Available constructor arguments for this Overlay are </p>
    -					<ul>
    -					<li><strong>width</strong> - width of the tail of the arrow</li>
    -					<li><strong>length</strong> - distance from the tail of the arrow to the head</li>
    -					<li><strong>location</strong> - where, as a proportional value from 0 to 1 inclusive, the Arrow should appear on the Connector</li>
    -					<li><strong>direction</strong> - which way to point. Allowed values are 1 (the default, meaning forwards) and -1, meaning backwards </li>
    -					<li><strong>foldback</strong> - how far along the axis of the arrow the tail points foldback in to. Default is 0.623.</li>
    -					<li><strong>paintStyle</strong> - a style object in the form used for paintStyle values for Endpoints and Connectors</li>
    -					</ul>
    -					<h4><a id="plainArrowOverlay">PlainArrow Overlay</a></h4>
    -					<p>This is just a specialized instance of Arrow in which jsPlumb hardcodes 'foldback' to 1, meaning the tail of the Arrow is a flat edge.  All of the constructor parameters from Arrow apply for PlainArrow.</p>
    -					<h4><a id="diamondOverlay">Diamond Overlay</a></h4>
    -					<p>This is a specialized instance of Arrow in which jsPlumb hardcodes 'foldback' to 2, meaning the Arrow turns into a Diamond.  All of the constructor parameters from Arrow apply for PlainArrow.</p>
    -					<h4><a id="labelOverlay">Label Overlay</a></h4>
    -					This provides a text label to decorate Connectors with.  The available constructor arguments are:
    -					<ul>
    -						<li><strong>label</strong> - The text to display.  You can provide a function here instead of plain text: it is passed the Connection as an argument, and it should return a String.</li>
    -						<li><strong>cssClass</strong> - Optional css class to use for the Label.  This is now preferred over using the 'labelStyle' parameter.</li>
    -						<li><strong>labelStyle</strong> - (deprecated, use cssClass instead) Optional arguments for the label's appearance.  Valid entries in this JS object are:
    -							<ul>
    -								<li><em>font</em> - a font string in a format suitable for the Canvas element</li>
    -								<li><em>fillStyle</em> - the color to fill the label's background with. Optional.</li>
    -								<li><em>color</em> - the color of the label's text. Optional.</li>
    -								<li><em>padding</em> - optional padding for the label. This is expressed as a proportion of the width of the label, not in pixels or ems.</li>
    -								<li><em>borderWidth</em> - optional width in pixels for the label's border. Defaults to 0.</li>
    -								<li><em>borderStyle</em> - optional. The color to paint the border, if there is one.</li>
    -								
    -							</ul>
    -						
    -						</li>
    -						<li><strong>location</strong> - As for Arrow Overlay.  Where, proportionally from 0 to 1 inclusive, the label should appear.</li>
    -					</ul>
    -
    -					The Label overlay offers two methods - getLabel and setLabel - for accessing/manipulating its content dynamically:
    -<div class="code">
    -<pre>
    -var c = jsPlumb.connect({
    -	source:"d1", 
    -	target:"d2", 
    -	overlays:[
    -		[ "Label", {label:"FOO", id:"label"}]
    -	]	
    -});					
    -
    -...
    -
    -var label = c.getOverlay("label");
    -console.log("Label is currently", label.getLabel());
    -label.setLabel("BAR");
    -console.log("Label is now", label.getLabel());
    -</pre>
    -</div>
    -In this example you can see that the Label Overlay is assigned an id of "label" in the connect call, and then retrieved using that id in the call to Connection's getOverlay method.
    -<p>Both Connections and Endpoints support Label Overlays, and because changing labels is quite a common operation, <strong>setLabel</strong> and <strong>getLabel</strong> methods have been added to these objects:
    -<div class="code">
    -<pre>
    -var conn = jsPlumb.connect({
    -	source:"d1", 
    -	target:"d2",
    -	label:"FOO"
    -});					
    -
    -...
    -
    -console.log("Label is currently", conn.getLabel());
    -conn.setLabel("BAR");
    -console.log("Label is now", conn.getLabel());
    -</pre>
    -</div>
    -These methods support passing in a Function instead of a String, and jsPlumb will create a label overlay for you if one does not yet exist when you call getLabel:
    -<div class="code">
    -<pre>
    -var conn = jsPlumb.connect({
    -	source:"d1", 
    -	target:"d2"
    -});					
    -
    -...
    -
    -conn.setLabel(function(c) {
    -	var s = new Date();
    -	return s.getTime() + "milliseconds have elapsed since 01/01/1970";
    -});
    -console.log("Label is now", conn.getLabel());
    -</pre>
    -</div>
    -					
    -					<h4><a id="hideshowOverlay">Hiding/Showing Overlays</a></h4>
    -					<p>You can control the visibility of Overlays using the <strong>setVisible</strong> method of Overlays themselves, or with
    -					<strong>showOverlay(id)</strong> or <strong>hideOverlay(id)</strong> on a Connection.</p>
    -					<p>Remember the <strong>id</strong> parameter that we specified in the examples above?  This can be used to retrieve the Overlay
    -					from a Connection:</p>
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -var overlay = connection.getOverlay("myLabel");
    -// now you can hide this Overlay:
    -overlay.setVisible(false);
    -// there are also hide/show methods:
    -overlay.show();
    -overlay.hide();
    -
    -</pre>					
    -					</div>
    -					<p>However, Connection and Endpoint also have two convenience methods you could use instead:
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -connection.hideOverlay("myLabel");
    -
    -// more time passes
    -
    -connection.showOverlay("myLabel");
    -
    -</pre>					
    -					</div>
    -					
    -		<h4><a id="hideshowOverlay">Removing Overlays</a></h4>
    -		<p>Connection and Endpoint also have a <strong>removeOverlay</strong> method, that does what you might expect:
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -connection.removeOverlay("myLabel");
    -
    -
    -</pre>					
    -					</div>							
    -	
    -		</div>
    -	
    -			
    -			<div class="section">
    -			<h3><a id="defaults">Defaults</a></h3>
    -			The easiest way to set a look and feel for your plumbing is to override the defaults that jsPlumb uses. If you
    -			do not do this you are forced to provide your overridden values on every call.  Every argument to the connect and addEndpoint methods has an
    -			associated default value in jsPlumb.<br/><br/>
    -
    -			The defaults that ship with jsPlumb are stored in <em>jsPlumb.Defaults</em>, which is a Javascript object.  Valid entries, and their initial values, are:
    -
    -			<div class="code">
    -<pre>
    -Anchor : "BottomCenter",
    -Anchors : [ null, null ],
    -Connector : "Bezier",
    -ConnectionOverlays : [ ],
    -DragOptions : { },
    -DropOptions : { },
    -Endpoint : "Dot",
    -EndpointOverlays : [ ],
    -Endpoints : [ null, null ],
    -EndpointStyle : { fillStyle : null },
    -EndpointStyles : [ null, null ],
    -EndpointHoverStyle : null,
    -EndpointHoverStyles : [ null, null ],
    -HoverPaintStyle : null,
    -LabelStyle : { color : "black" },
    -LogEnabled : false,
    -Overlays : [ ],
    -MaxConnections : 1,
    -MouseEventsEnabled : true,
    -PaintStyle : { lineWidth : 8, strokeStyle : "#456" },
    -RenderMode : "svg",
    -Scope : "_jsPlumb_DefaultScope"
    -</pre>
    -			</div>
    -			<p>
    -			Note that in EndpointStyle, the default fillStyle is 'null'.  This instructs jsPlumb to use the strokeStyle
    -from the attached connector to fill the endpoint.</p>
    -<p>Note also that you can specify either or both (or neither) of 'EndpointStyle' and 'EndpointStyles'.  This allows you to specify a different
    -end point for each end of a connection.  'Endpoint' and 'Endpoints' use the same concept.  jsPlumb will look first in the
    -individual endpoint/endpoint style arrays, and then fall back to the single default version.</p>
    -
    -			you can override these defaults by including this in a script somewhere:
    -			<div class="code">
    -<pre>
    -jsPlumb.Defaults.PaintStyle = {
    -	lineWidth:13,
    -	strokeStyle: 'rgba(200,0,0,100)'
    -}
    -
    -jsPlumb.Defaults.DragOptions = { cursor: "crosshair" };
    -
    -jsPlumb.Default.Endpoints = [ [ "Dot", 7 ], [ "Dot", 11 ] ];
    -
    -jsPlumb.Defaults.EndpointStyles = [{ fillStyle:"#225588" }, { fillStyle:"#558822" }];</pre>
    -			</div>
    -			after the jsPlumb script has been loaded of course!  Here we have specified the following default behaviour:
    -			<ul>
    -				<li>- connectors are 13 pixels wide and painted with a semi-transparent red line</li>
    -				<li>- when dragging an element the crosshair cursor is used</li>
    -				<li>- the source endpoint is a dot of radius 7; the target endpoint is a dot of radius 11</li>
    -				<li>- the source endpoint is blue; the target endpoint is green</li>
    -			</ul>
    -
    -			<h5>jsPlumb.importDefaults</h5>
    -From version 1.3.6 onwards there is a helper method called <strong>importDefaults</strong> that allows you to import a set of values with just one call to jsPlumb.  Here's the previous example using importDefaults:
    -			<div class="code">
    -<pre>
    -jsPlumb.importDefaults({
    -	PaintStyle : {
    -		lineWidth:13,
    -		strokeStyle: 'rgba(200,0,0,100)'
    -	},
    -	DragOptions : { cursor: "crosshair" },
    -	Endpoints : [ [ "Dot", 7 ], [ "Dot", 11 ] ],
    -	EndpointStyles : [{ fillStyle:"#225588" }, { fillStyle:"#558822" }]
    -});</pre>
    -			</div>
    -
    -		</div> 					
    -
    -<div class="section">
    -			<h3><a id="simpleConnections">Connections</a></h3>
    -			<h4><a id="simpleConnections">Programmatic Connections</a></h4>
    -The most simple connection you can make with jsPlumb looks like this:
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({source:"element1", target:"element2"});</pre>			
    -			</div>
    -
    -		In this example we have created a Connection from 'element1' to 'element2'.  Remember that a Connection in jsPlumb consists of two Endpoints,
    -		a Connector, and zero or more Overlays.  But this call to 'connect' supplied none of those things, so jsPlumb uses the default values wherever it needs to.
    -		In this case, default values have been used for the following: 
    -		<ul>
    -			<li>- The type and appearance of each Endpoint in the Connection. jsPlumb's default for this is the "Dot" endpoint, of radius 10, with fill color "#456".</li>
    -			<li>- The Anchors that define where the connection's Endpoints appear on each element. The jsPlumb default is "BottomCenter"</li>			
    -			<li>- Whether or not each Endpoint can be a source or target for new Connections. The default is false.</li>
    -			<li>- The type and appearance of the Connection's Connector. The default is a "Bezier" connector of line width 8, and color "#456".</li>
    -		</ul>
    -		<p>
    -		So this call will result in an 8px Bezier, colored "#456", from the bottom center of 'element1' to the bottom center of 'element2', and each
    -		Endpoint will be a 10px radius Dot Endpoint, colored "#456".
    -		</p>
    -		<p>
    -		Let's beef up this call a little and tell jsPlumb what sort of Endpoints we want, and where we want them:		
    -		</p>
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:"element1", 
    -	target:"element2",
    -	anchors:["RightMiddle", "LeftMiddle" ],
    -	endpoint:"Rectangle",
    -	endpointStyle:{ fillStyle: "yellow" }
    -});
    -</pre>			
    -			</div>
    -			
    -		<p>This is what we have told jsPlumb we want this time:</p>
    -		<ul>
    -			<li><strong>anchors</strong> - this array tells jsPlumb where the source and target Endpoints should be located on their parent elements. In this case,
    -			we use the shorthand syntax to name one of jsPlumb's default anchors; you can also specify custom locations (see <a href="#anchors">anchors</a>).
    -			Instead of <strong>anchors</strong> you can use <strong>anchor</strong>, if you want the source and target Endpoints to be located at the
    -			same place on their parent elements.			<br/>
    -			</li>
    -			<li>
    -				<strong>endpoint</strong>  - this tells jsPlumb to use the "Rectangle" Endpoint for both the source and target of the Connection.  As with anchors, 
    -				<strong>endpoint</strong> has a plural version that allows you to specify a different Endpoint for each end of the Connection.<br/>  
    -			</li>
    -			<li>
    -				<strong>endpointStyle</strong> - this is the definition of the appearance of the Endpoint you specified above.  Again, there is a
    -				plural equivalent of this that allows you to specify a different style for each end of the Connection. For more information about
    -				allowed values for this value, see <a href="#paintstyles">Connector, Endpoint & Overlay Styles</a>. 
    -			</li>
    -		</ul>		
    -		
    -		<h5>Reusing common settings between jsPlumb.connect calls</h5>
    -		<p>
    -			A fairly common situation you will find yourself in is wanting to create a bunch of Connections that have only minor differences
    -			between them.  To support that, <strong>jsPlumb.connect</strong> takes an optional second argument. For example:			 
    -		</p>
    -		<div class="code">
    -<pre>
    -var common = {
    -	anchors:[ "BottomCenter", "TopCenter" ],
    -	endpoints:["Dot", "Blank" ]
    -};
    -
    -jsPlumb.connect({ source:"someElement", target:"someOtherElement" }, common);
    -
    -jsPlumb.connect({ source:"aThirdElement", target:"yetAnotherElement" }, common);
    -
    -</pre>			
    -			</div>		
    -			
    -		<h5>Endpoints created by jsPlumb.connect</h5>
    -		<p>If you supply an element id or selector for either the source or target , jsPlumb.connect will automatically create an Endpoint on the 
    -		given element.  These automatically created Endpoints are not marked as drag source or targets, and cannot
    -		be interacted with.  For some situations this behaviour is perfectly fine, but for more interactive UIs you should set things up using the drag and drop
    -		method discussed below.</p>
    -		<p>Given that jsPlumb.connect creates its own Endpoints in some circumstances, in order to avoid leaving orphaned Endpoints around the place, if the Connection is subsequently
    -		deleted, these created Endpoints are deleted too.  Should you want to, you can override this behaviour by setting <strong>deleteEndpointsOnDetach</strong> to
    -		false in the connect call:</p>
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({ 
    -	source:"aThirdElement", 
    -	target:"yetAnotherElement",
    -	deleteEndpointsOnDetach:false 
    -});
    -</pre>
    -</div>								
    -					
    -		<h4><a id="draggableConnections">Connections using Drag and Drop</a></h4>
    -		To support drag and drop connections, you first need to set a few things up.  Every drag and drop connection needs at least a source
    -		Endpoint that the user can drag a connection from. Here's a simple example of how to create an Endpoint:
    -		<div class="code">
    -<pre>
    -var endpointOptions = { isSource:true };
    -var endpoint = jsPlumb.addEndpoint('elementId', endpointOptions);
    -</pre>		
    -</div>  	
    -	This Endpoint will act as a source for new Connections, and will use the jsPlumb defaults for its own appearance and that of any Connections that are drawn from it.							
    -	
    -		<h5>Tip: use the three-argument addEndpoint method for common data </h5>
    -		One thing that happens quite often is that you have an Endpoint whose appearance and behaviour is largely the
    -		same between usages on different elements, with just a few differences. 
    -		<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -	endpoint:"Rectangle",
    -	paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -	isSource:true,
    -	connectorStyle : { strokeStyle:"#666" },
    -	isTarget:true
    -};
    -</pre>		
    -</div>  		
    -		Notice there is no 'anchor' set.  Here we apply it to two elements, at a different location in each:
    -		<div class="code">
    -<pre>
    -jsPlumb.addEndpoint('element1', { anchor:"BottomCenter" }, exampleGreyEndpointOptions)); 
    -
    -jsPlumb.addEndpoint('element2', { anchor:"TopCenter" }, exampleGreyEndpointOptions));
    -</pre>		
    -</div>
    -
    -<p>
    -Now that you have a source Endpoint, you need to either create a target Endpoint on some element, or notify jsPlumb that you wish to make an
    -entire element a drop target.  Let's look at how to attach a target Endpoint first:
    -</p>
    -
    -		<div class="code">
    -<pre>var endpointOptions = { isTarget:true, endpoint:"Rectangle", paintStyle:{ fillStyle:"gray" } };
    -var endpoint = jsPlumb.addEndpoint("otherElementId", endpointOptions);
    -</pre>		
    -		</div>
    -		<p>
    -		This Endpoint, a gray rectangle, has declared that it can act as a drop target for Connections. 
    -</p>
    -<p>
    -jsPlumb also supports marking entire elements as drag sources or drag targets, using the <strong>makeSource</strong> and <strong>makeTarget</strong> methods.  This is a useful function for certain applications.  Each of these methods takes an Endpoint declaration which defines the Endpoint that jsPlumb will create after a Connection has been attached.  Let's take a look at makeTarget first:
    -</p>
    -<h4><a id="sourcesAndTargets">Elements as sources &amp; targets</a></h4>
    -jsPlumb also supports turning entire elements into Connection sources and targets, using the methods <strong>makeSource</strong> and <strong>makeTarget</strong>.  With these methods you mark an element as a source or target, and provide an Endpoint specification for jsPlumb to use when a Connection is established.  makeSource also gives you the ability to mark some child element as the place from which you wish to drag Connections, but still have the Connection on the main element after it has been established.
    -<p>These methods honour the jsPlumb defaults - if, for example you set up the default Anchors to be this:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.Defaults.Anchors = [ "LeftMiddle", "BottomRight" ];
    -</pre>
    -</div>
    -... and then used makeSource and makeTarget without specifying Anchor locations, jsPlumb would use "LeftMiddle" for the makeSource element and "BottomRight" for the makeTarget element.
    -<p>
    -A further thing to note about makeSource and makeTarget is that from 1.3.6 onwards any prior calls to one of these methods is honoured by subsequent calls to jsPlumb.connect. This helps when you're building a UI that uses this functionality at runtime but which loads some initial data, and you want the statically loaded data to have the same appearance and behaviour as dynamically created connections (obviously quite a common use case).  
    -<p>For example:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -	anchor:"Continuous",
    -	endpoint:["Rectangle", { width:40, height:20 }]
    -});
    -
    -...
    -
    -jsPlumb.connect({source:"el1", target:"el2"});
    -</pre>
    -</div>
    -In this example, the source of the connection will be a Rectangle of size 40x20, having a Continuous Anchor.
    -<p>You can override this behaviour by setting a <strong>newConnection</strong> parameter on the connect call:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -	anchor:"Continuous",
    -	endpoint:["Rectangle", { width:40, height:20 }]
    -});
    -
    -...
    -
    -jsPlumb.connect({ source:"el1", target:"el2", newConnection:true });
    -</pre>
    -</div>
    -<h5><a id="makeTarget">jsPlumb.makeTarget</a></h5>
    -<p>
    -This method takes two arguments, the first of which specifies some element (or list of elements); the second specifies the Endpoint you wish to create on that element whenever a Connection is established on it.  In this example we will use the exact same target Endpoint we used before - the gray rectangle - but we will tell jsPlumb that the element "aTargetDiv" will be the drop target:
    -</p>
    -		<div class="code">
    -<pre>var endpointOptions = { 
    -  isTarget:true, 
    -  maxConnections:5,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" } 
    -};
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -<p>
    -	The allowed values in 'endpointOptions' are identical for both the <strong>jsPlumb.addEndpoint</strong> and <strong>jsPlumb.makeTarget</strong> methods, but makeTarget supports an extended 
    -	anchor syntax that allows you more control over the location of the target endpoint.  This is discussed below.	
    -</p>		
    -<p>Notice in the 'endpointOptions' object above there is a 'isTarget' parameter set - this may seem incongruous, since you know you're going to make some element a target.  Remember that the endpointOptions object is the information jsPlumb will use to create an Endpoint on the given target element each time a Connection is established to it. It takes the exact same format as you would pass to addEndpoint; makeTarget is essentially a deferred addEndpoint call followed by a connect call.  So in this case, we're telling jsPlumb that any Endpoints it happens to create on some element that was configured by the makeTarget call are themselves Connection targets.
    -</p>
    -<h5>Unique Endpoint per Target </h5>
    -<p>jsPlumb will create a new Endpoint using the supplied information every time a new Connection is established on the target element, by default, but you can override this behaviour and tell jsPlumb that it should create at most one Endpoint, which it should attempt to use for subsequent Connections:
    -</p>
    -		<div class="code">
    -<pre>var endpointOptions = { 
    -  isTarget:true, 
    -  uniqueEndpoint:true,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" } 
    -};
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -Here, the <strong>uniqueEndpoint</strong> parameter tells jsPlumb that there should be at most one Endpoint on this element.  Notice that 'maxConnections' is not set: the default is 1, so in this setup we have told jsPlumb that "aTargetDiv" can receive one Connection and no more.
    -<h5>Deleting Endpoints on Detach</h5>
    -By default, any Endpoints created using makeTarget have <strong>deleteEndpointsOnDetach</strong> set to true, which means that once all Connections to that Endpoint are removed, the Endpoint is deleted.  You can override this by setting the flag to true on the makeTarget call:
    -		<div class="code">
    -<pre>var endpointOptions = { 
    -  isTarget:true, 
    -  maxConnections:5,
    -  uniqueEndpoint:true,
    -  deleteEndpointsOnDetach:false,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" } 
    -};
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -In this setup we have told jsPlumb to create an Endpoint the first time "aTargetElement" is connected to, and to not delete it even if there are no longer any Connections to it.  The created Endpoint will be reused for subsequent Connections, and can support a maximum of 5.
    -
    -<h5>Target Anchors positions with makeTarget</h5>
    -<p>When using the makeTarget method, jsPlumb allows you to provide a callback function to be used to determine
    -the appropriate location of a target anchor for every new Connection dropped on the given target.  It may be the
    -case that you want to take some special action rather than just relying on one of the standard anchor mechanisms.
    -</p>
    -<p>This is achieved through an extended anchor syntax (note that this syntax is <strong>not supported</strong> in
    -the <strong>jsPlumb.addEndpoint</strong> method) that supplies a "positionFinder" to the anchor specification.  jsPlumb 
    -provides two of these by default; you can register your own on jsPlumb and refer to them by name, or just 
    -supply a function.  Here's a few examples:</p>
    -Instruct jsPlumb to place the target anchor at the exact location at which the mouse button was released on the
    -target element. Note that you tell jsPlumb the anchor is of type "Assign", and you then provide a "position"
    -parameter, which can be the name of some position finder, or a position finder function itself:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {
    -  anchor:[ "Assign", { 
    -    position:"Fixed"
    -  }]
    -});
    -</pre>
    -</div>
    -<p><strong>Fixed</strong> is one of the two default position finders provided by jsPlumb. The other is <strong>Grid</strong>:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {
    -  anchor:[ "Assign", { 
    -    position:"Grid",
    -    grid:[3,3]
    -  }]
    -});
    -</pre>
    -</div>
    -The Grid position finder takes a "grid" parameter that defines the size of the grid required. [3,3] means
    -3 rows and 3 columns.
    -<h5>Supplying your own position finder</h5>
    -<p>To supply your own position finder to jsPlumb you first need to create the callback function. First let's take a look at what the source code for the Grid position finder looks like:</p>
    -<div class="code">
    -<pre>
    -function(eventOffset, elementOffset, elementSize, constructorParams) {
    -  var dx = eventOffset.left - elementOffset.left, dy = eventOffset.top - elementOffset.top,
    -      gx = elementSize[0] / (constructorParams.grid[0]), 
    -      gy = elementSize[1] / (constructorParams.grid[1]),
    -      mx = Math.floor(dx / gx), my = Math.floor(dy / gy);
    -
    -  return [ ((mx * gx) + (gx / 2)) / elementSize[0], ((my * gy) + (gy / 2)) / elementSize[1] ];
    -}
    -</pre>
    -</div>
    -The four arguments are:
    -<ul>
    -	<li class="bullet"><strong>eventOffset</strong> - Page left/top where the mouse button was released (a JS object containing left/top members like you get from a jQuery offset call)</li>
    -	<li class="bullet"><strong>elementOffset</strong> - JS offset object containing offsets for the element on which the Connection is to be created</li>
    -	<li class="bullet"><strong>elementSize</strong> - [width, height] array of the dimensions of the element on which the Connection is to be created</li>
    -	<li class="bullet"><strong>constructorParams</strong> - the parameters that were passed to the Anchor's constructor. In the example given above, those parameters are 'position' and 'grid'; you can pass arbitrary parameters.
    -</ul>
    -The return value of this function is an array of [x, y] - proportional values between 0 and 1 inclusive, such as you can pass to a static Anchor.
    -<p>To make your own position finder you need to create a function that takes those four arguments and returns an [x, y] position for the anchor, for example:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.AnchorPositionFinders.MyFinder = function(dp, ep, es, params) {
    -	...		
    -	return [ some maths ];	
    -};
    -</pre>
    -</div>
    -Then refer to it in a makeTarget call:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {  
    -  anchor:[ "Assign", { 
    -    position:"MyFinder",
    -    myCustomParameter:"foo",
    -    anInteger:5
    -  }]
    -});
    -</pre>
    -</div>
    -
    -<h5><a id="makeSource">jsPlumb.makeSource</a></h5>
    -There are two use cases supported by this method.  The first is the case that you want to drag a Connection from the element itself and have an Endpoint attached to the element when a Connection is established.  The second is a more specialised case: you want to drag a Connection from the element, but once the Connection is established you want jsPlumb to move it so that its source is on some other element.  For an example of this second case, check out the <a href="../../demo/jquery/stateMachineDemo.html">State Machine Connectors</a> demonstration.
    -<p>Here's an example code snippet for the basic use case of makeSource:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource(someDiv, {
    -  paintStyle:{ fillStyle:"yellow" },
    -  endpoint:"Blank",
    -  anchor:"BottomCenter"
    -});
    -</pre>
    -</div>
    -<p>
    -Notice again that the second argument is the same as the second argument to an addEndpoint call.  makeSource is, essentially, a type of addEndpoint call.  In this example we have told jsPlumb that we will support dragging Connections directly from 'someDiv'.  Whenever a Connection is established between 'someDiv' and some other element, jsPlumb assigns an Endpoint at BottomCenter of 'someDiv', fills it yellow, and sets that Endpoint as the newly created Connection's source. 
    -</p>		
    -<p>Configuring an element to be an entire Connection source using makeSource means that the element cannnot itself be draggable.  There would be no way for jsPlumb to distinguish between the user attempting to drag the element and attempting to drag a Connection from the element.  If your UI works in such a way that that is acceptable then you'll be fine to use this method.  But if you want to drag Connections from some element but also have the element be draggable itself, you might want to consider the second use case.  First, a code snippet:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("someConnectionSourceDiv", {
    -  paintStyle:{ fillStyle:"yellow" },
    -  endpoint:"Blank",
    -  anchor:"BottomCenter",
    -  parent:"someDiv"
    -});
    -</pre>
    -</div>
    -<p>The only difference here is the inclusion of the 'parent' parameter.  It instructs jsPlumb to configure the source Endpoint on the element specified - in this case, "someDiv" (the value of this parameter may be a String id or a selector).</p>
    -<p>Again, I'd suggest taking a look at the <a href="../../demo/jquery/stateMachineDemo.html">State Machine Connectors demonstration</a> for an example of this.</p>
    -
    -<p>
    -There are many things you can set in an Endpoint options object; for a thorough list see the API documentation for Endpoint.  
    -</p>
    -<p>Here's an example of specifying that you want an Arrow overlay halfway along any Connection dragged from this Endpoint:</p>
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -  isSource:true,
    -  connectorStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  connectorOverlays: [ [ "Arrow", { location:0.5 } ] ]
    -};
    -</pre>
    -</div>
    -This is an Endpoint that moves around the element it is attached to dependent on the location of other elements in the connections it is attached to (a 'dynamic' anchor):
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -  isSource:true,
    -  connectorStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  connectorOverlays: [ [ "Arrow", { location:0.5 } ] ]
    -  anchor:[ "TopCenter","RightMiddle","BottomCenter","LeftMiddle" ]
    -};
    -</pre>
    -</div>
    -
    -<h5><a id="dragOptions">Drag Options</a></h5>
    -	These are options that will be passed through to the supporting library's drag API.  jsPlumb passes everything you supply here through, inserting
    -	wrapping functions if necessary for the various lifecycle events that jsPlumb needs to know about.  So if, for example, you pass a function to be
    -	called when dragging starts, jsPlumb will wrap that function with a function that does what jsPlumb needs to do, then call yours.
    -	<p>
    -	At the time of writing, jsPlumb supports jQuery, MooTools and YUI3, and each of those libraries uses different terminology.  In addition, jQuery's API is
    -	more fully featured, providing easy support for setting the zIndex and opacity of elements being dragged, as well as the 'scope' for a drag/drop (allowing
    -	you to specify more than one type of drag/drop pair), and hover classes for when a draggable is on the move or
    -	over a droppable. If you're using jQuery you can of course just supply these values on the dragOptions; to make it easier, jsPlumb's MooTools and YUI3 adapters recognize 
    -	these options and add appropriate callbacks for you.  
    -	</p> 
    -	<p>
    -	Given that the options here are library-specific, and they are all well-documented, we're going to discuss just the three drag options
    -	that behave the same way in all (see below for hoverClass):
    -		<ul>
    -		<li><strong>opacity</strong> - the opacity of an element that is being dragged.  Should be a fraction between 0 and 1 inclusive.</li>
    -		<li><strong>zIndex</strong> - the zIndex of an element that is being dragged.</li>
    -		<li><strong>scope</strong> - the scope of the draggable.  can only be dropped on a droppable with the same scope.  this is discussed below.</li>
    -		</ul> 
    -	</p>
    -	
    -	<p>For more information about drag options, take a look at the <a href="http://docs.jquery.com/UI/Draggable" target="_blank">jQuery</a>, <a href="http://mootools.net/docs/more/Drag/Drag.Move" target="_blank">MooTools</a>, or <a href="http://developer.yahoo.com/yui/3/dd/" target="_blank">YUI3</a> docs.	
    -	</p>
    -	<p><strong>NOTE: there is an issue in Chrome that affects the 'cursor' argument to drag options in jQuery. See these links:</strong>
    -	</p><p>
    -<a href="http://forum.jquery.com/topic/draggable-cursor-option-does-not-work" target="_blank">http://forum.jquery.com/topic/draggable-cursor-option-does-not-work</a><br/>
    -<a href="http://forum.jquery.com/topic/chrome-text-select-cursor-on-drag" target="_blank">http://forum.jquery.com/topic/chrome-text-select-cursor-on-drag</a>
    -</p>
    -You should put something like this at the top of your JS to avoid it (perhaps not something so drastic as a document-wide override):	
    -<div class="code">
    -<pre>document.onselectstart = function () { return false; };</pre>
    -</div>		
    -	
    -	<h5><a id="dropOptions">Drop Options</a></h5>
    -	Drop options are treated by jsPlumb in the same way as drag options - they are passed through to the underlying library.  MooTools does not have drop options like jQuery and YUI3 do; droppable functionality in MooTools 
    -	is actually implemented by the Drag.Move class - the one used to initialise a draggable.  But when you setup an Endpoint in jsPlumb you should ignore
    -	that fact, and treat droppables like you would in jQuery or YUI3.  jsPlumb wires everything up for you under the hood.
    -	<p>There are three jQuery droppable options that jsPlumb treats as shortcuts in MooTools and YUI3, for the sake of consistency:</p>
    -		<ul>
    -			<li><strong>hoverClass</strong> - the CSS class to attach to the droppable when a draggable is hovering over it.</li>
    -			<li><strong>activeClass</strong> - the CSS class to attach to the droppable when a draggable is, um, being dragged.</li>
    -			<li><strong>scope</strong> - the scope of the draggable.  The draggable can only be dropped on a droppable with the same scope.  This is discussed below.</li>
    -		</ul>   
    -	<p>For more information about drop options when using jQuery, see <a href="http://docs.jquery.com/UI/Droppable" target="_blank">here</a>.</p>
    -		
    -	<h5><a id="dragAndDropScope">Drag and Drop Scope</a></h5>
    -		jsPlumb borrowed the concept of 'scope' from jQuery's drag/drop implementation: the notion of
    -		which draggables can be dropped on which droppables.  In jsPlumb you can provide a 'scope' entry when 
    -		creating an Endpoint.  Here's the example grey Endpoint example with 'scope' added:
    -		
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:"#666" },
    -  isSource:true,
    -  connectionStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  scope:"exampleGreyConnection"
    -};</pre>		
    -</div>  		
    -  										
    -	If you do not provide a 'scope' entry, jsPlumb uses a default scope.  Its value is accessible through this method:
    -	<div class="code">
    -<pre>
    -jsPlumb.getDefaultScope();
    -</pre>	
    -	</div>				
    -	If you want to change it for some reason you can do so with this method:
    -	<div class="code">
    -<pre>
    -jsPlumb.setDefaultScope("mySpecialDefaultScope");
    -</pre>	
    -	</div>			
    -	You can also, should you want to, provide the scope value separately on the drag/drop options, like this:
    -	<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -  isSource:true,
    -  connectionStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  dragOptions:{ scope:"dragScope" },
    -  dropOptions:{ scope:"dropScope" }
    -};</pre>		
    -</div>  
    -			
    -	</div>	
    -	
    -	<div class="section">
    -		<h3><a id="beforeDrop">Interceptors</a></h3>
    -		<p>
    -		Interceptors were new in jsPlumb 1.3.4.  They are basically event handlers from which you can return a value that tells jsPlumb to abort what it is that
    -		it was doing.  There are currently two interceptors supported - <strong>beforeDrop</strong>, which is called when the user has dropped a Connection onto some target, 
    -		and <strong>beforeDetach</strong>, which is called when the user is attempting to detach a Connection. 
    -		</p>
    -		<p>Interceptors can be registered via the <strong>bind</strong> method on jsPlumb just like any other event listeners, and they can also be passed in to
    -		the <strong>jsPlumb.addEndpoint</strong>, <strong>jsPlumb.makeSource</strong> and <strong>jsPlumb.makeTarget</strong>
    -		methods.  A future version of jsPlumb will support registering interceptors on Endpoints using the <strong>bind</strong> method as you can now with the jsPlumb object.
    -		Note that binding 'beforeDrop' (as an example) on jsPlumb itself is like a catch-all: it will be called every time a Connection is dropped on _any_ 
    -		Endpoint.  But passing a 'beforeDrop' callback into an Endpoint (and in the future, registering one using 'bind') constrains that callback to just the
    -		Endpoint in question.  		
    -		<h4><a id="beforeDrop">Before Drop</a></h4>
    -		This is called when the user has released the mouse button over a Connection target.  Your callback is passed the
    -		a JS object containing these fields:
    -		<ul>
    -			<li>sourceId</li>
    -			<li>targetId</li>
    -			<li>scope</li>
    -		</ul>
    -		<p>Returning false - or nothing - from this callback will cause the drag to be abandoned, and the new Connection removed from the UI.</p>
    -		<h4><a id="beforeDetach">Before Detach</a></h4>
    -		This is called when the user has detached a Connection, which can happen for a number of reasons: by default, jsPlumb allows users to drag Connections
    -		off of target Endpoints, but this can also result from a programmatic 'detach' call.  Every case is treated the same by jsPlumb, so in fact it is possible
    -		for you to write code that attempts to detach a Connection but then denies itself!  You might want to be careful with that. 
    -		<p>Note that this interceptor is passed the actual Connection object; this is different from the beforeDrop
    -		interceptor discussed above: in this case, we've already got a Connection, but with beforeDrop we are yet 
    -		to confirm that a Connection should be created.</p>
    -		<p>Returning false - or nothing - from this callback will cause the detach to be abandoned, and the Connection will be reinstated/left on its current target.</p>
    -			
    -	</div>
    -
    -		<div class="section">
    -			<h3><a id="paintstyles">Paint Styles</a></h3>
    -			Defining the appearance of Connectors, Endpoints (and Overlays, but this is deprecated in favour of using CSS classes) is achieved through a 'paintStyle' (or a quite similar name...keep reading)
    -			object passed as a parameter to one of jsPlumb.connect, jsPlumb.addEndpoint, jsPlumb.makeSource or jsPlumb.makeTarget. Depending on the method you are
    -			calling, the parameter names vary.
    -			<h5>Connector Paint Styles</h5>
    -			These are specified in a <em>paintStyle</em> parameter on a call to <strong>jsPlumb.connect</strong>:
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			or in the <em>connectorPaintStyle</em> parameter on a call to <strong>jsPlumb.addEndpoint</strong> or <strong>jsPlumb.makeSource</strong>:
    -			<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div> 			
    -			 
    -
    -Notice the <em>paintStyle</em> parameter in those examples: it is the paint style for the Endpoint, which we'll discuss below.
    -
    -			<h5>Endpoint Paint Styles</h5>
    -			These are specified in a <em>paintStyle</em> parameter on a call to <strong>addEndpoint</strong>. This is the example from just above:
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -</div> 
    -			...or as the <em>endpointStyle</em> parameter to a <strong>connect</strong> call:		
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			... or as an entry in the <em>endpointStyles</em> array passed to a <strong>jsPlumb.connect</strong> call:
    -					<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  endpointStyles:[ 
    -    { fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -    { fillStyle:"green" }
    -  ],
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			or as the <em>paintStyle</em> parameter passed to a <strong>makeTarget</strong> or <strong>makeSource</strong> call:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("el1", {
    -  ...
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  ...
    -});
    -</pre>			
    -</div>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 }
    -  parent:"someOtherDivIJustPutThisHereToRemindYouYouCanDoThis"
    -});
    -</pre>			
    -			</div>						
    -	In the first example we  made "el1" into a drop target, and defined a paint style for the Endpoint jsPlumb will create when a Connection is
    -	established.  In the second we made "el1" a connection source and assigned the same values for the Endpoint jsPlumb will create when a Connection
    -	is dragged from that element.
    -	
    -	<h5>Overlay Paint Styles</h5>
    -	The preferred way to set paint styles for Overlays is to use the <em>cssClass</em> parameter in the constructor arguments of an Overlay definition.
    -	
    -	<h4>Paint Style Parameters</h4>
    -	This is the full list of parameters you can set in a paintStyle object, but note that <strong>fillStyle</strong> is ignored by Connectors,
    -	and <strong>strokeStyle</strong> is ignored by Endpoints.  Also, if you create a Connection using jsPlumb.connect and do not specify any
    -	Endpoint styles, the Endpoints will derive their fillStyle from the Connector's strokeStyle.
    -	<p>
    -		fillStyle, strokeStyle and outlineColor can be specified using any valid CSS3 syntax.
    -	</p>
    -	<ul>
    -		<li><strong>fillStyle</strong> - color for an Endpoint, eg. rgba(100,100,100,50), "blue", "#456", "#993355", rgb(34, 56, 78).</li>
    -		<li><strong>strokeStyle</strong> - color for a Connector. see fillStyle examples.</li>
    -		<li><strong>lineWidth</strong> - width of a Connector's line. An integer.</li>
    -		<li><strong>outlineWidth</strong> - width of the outline for an Endpoint or Connector. An integer.</li>
    -		<li><strong>outlineColor</strong> - color of the outline for an Endpoint or Connector. see fillStyle examples.</li>
    -		<li><strong>dashstyle</strong> - VML and SVG only. This comes from VML, and allows you to create dashed or dotted lines.  It has a 
    -			better syntax than the equivalent attribute in SVG (<em>stroke-dasharray</em>, discussed below), so jsPlumb supports this for both renderers. 
    -			The <em>dashstyle</em> attribute is specified as an array of strokes and spaces, where each value is some multiple of <em>the width of 
    -			the Connector</em>, and that's where it's better than SVG, which uses pixels.
    -			<p>
    -			<a href="http://www.w3.org/TR/NOTE-VML" target="_blank">The VML spec</a> is a good place to find valid values for dashstyle. Note that
    -			jsPlumb does not support the string values for this attribute ("solid", "dashdot", etc).
    -			</p>
    -			<p>
    -			In SVG render mode, jsPlumb uses the <em>lineWidth</em> parameter in conjunction with the values in a <em>dashstyle</em> attribute
    -			to create an appropriate value for <em>stroke-dasharray</em>.
    -			</p>
    -		</li>
    -		<li><strong>stroke-dasharray</strong> - SVG only. This is the SVG equivalent of <em>dashstyle</em>.  
    -		<a href="http://www.w3.org/TR/SVG/painting.html" target="_blank">The SVG spec</a> discusses valid values for this parameter.  But be aware
    -		that jsPlumb does not convert this into a valid <em>dashstyle</em> attribute when using the VML renderer. Better to use <em>dashstyle</em>.
    -		</li>
    -		<li><strong>stroke-dashoffset</strong> - SVG only.  This is used in SVG to specify how far into the dash pattern to start painting.  For more information,
    -		see <a href="http://www.w3.org/TR/SVG/painting.html" target="_blank">the SVG spec.</a></li>
    -		<li><strong>joinstyle</strong> - VML and SVG only.  As with <em>dashstyle</em>, this is a VML attribute that jsPlumb supports for both VML
    -		and SVG - jsPlumb turns this into a <em>stroke-linejoin</em> attribute when rendering with SVG.  This attribute specifies how you want individual
    -		segments of connectors to be joined; the VML and SVG specs both have examples of this, of which many are the same between the two, which is
    -		why jsPlumb will automatically convert this attribute into the SVG equivalent.
    -		</li>
    -		<li><strong>stroke-linejoin</strong> - SVG only.  This is the equivalent of VML's <em>joinstyle</em> attribute, but as with <em>stroke-dasharray</em>,
    -		jsPlumb does not convert this into something approriate for VML.  So, using <em>joinstyle</em> will enable you to support more browsers with
    -		less effort.		
    -		</li>
    -	</ul>
    -	
    -	<h3><a id="hoverpaintstyles">Hover Paint Styles</a></h3>
    -	 		
    -	 		Connectors and Endpoints both support the concept of a "hover" paint style - a paint style to use when the mouse is hovering over
    -	 		the component.  These are specified in the exact same format as paint styles discussed above, but hover paint styles also inherit
    -	 		any values from the main paint style.  This is because you will typically want to just change the color, or perhaps outline color, of
    -	 		a Connector or Endpoint when the mouse is hovering, but leave everything else the same.  So having hover paint styles inherit their
    -	 		values precludes you from having to define things in more than one place.
    -	 		<p>
    -	 			The naming convention adopted for hover paint styles is pretty much to insert the word 'hover' into the corresponding main paint
    -	 			style parameters.  Here are a couple of examples:
    -	 		</p> 
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -  hoverPaintStyle:{ strokeStyle:"red" },
    -  endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  endpointHoverStyle:{ fillStyle:"red" }
    -});
    -</pre>			
    -</div>
    -			In this example we specified a hover style for both the Connector, and each of its Endpoints.  Here's the same thing, but using the plural version, to specify a different hover style for each Endpoint:	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -  hoverPaintStyle:{ strokeStyle:"red" },
    -  endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  endpointHoverStyles:[ { fillStyle:"red" }, { fillStyle:"yellow" } ]
    -});
    -</pre>			
    -</div>
    -	 		Calls to <strong>addEndpoint</strong>, <strong>makeSource</strong> and <strong>makeTarget</strong> can also specify various hover paint styles:
    -	 								<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  hoverPaintStyle:{ fillStyle:"red" },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -  connectorHoverPaintStyle:{ strokeStyle:"red", outlineColor:"yellow", outlineWidth:1 }
    -});
    -
    -jsPlumb.makeSource("el2", {
    -  paintStyle:{ fillStyle:"transparent", outlineColor:"yellow", outlineWidth:1 },
    -  hoverPaintStyle:{ fillStyle:"red" },
    -  connectorPaintStyle:{ strokeStyle:"green", lineWidth:3 },
    -  connectorHoverPaintStyle:{ strokeStyle:"#678", outlineColor:"yellow", outlineWidth:1 }
    -});
    -
    -jsPlumb.makeTarget("el3", {
    -  paintStyle:{ fillStyle:"transparent", outlineColor:"yellow", outlineWidth:1 },
    -  hoverPaintStyle:{ fillStyle:"red" }
    -});
    -</pre>			
    -			</div> 
    -	 		
    -	 		In these examples we specified a hover paint style for both the Endpoint we are adding, and any Connections to/from the Endpoint.
    -	 		<p>	
    -		Note that <strong>makeTarget</strong> does not support Connector parameters. It is for creating targets only; Connector parameters will be set by the source Endpoint in any Connections that are made to the element that you turned into a target by using this method.</p>
    -	 			 		
    -			<h4><a id="gradients">Gradients</a></h4>
    -			The Canvas and SVG renderers both support gradients. The VML renderer does not.  jsPlumb uses its own syntax to define gradients, to abstract out the differences between the syntax required by canvas and that required by SVG.  
    -			<p>There are two types of gradients available - a 'linear' gradient, which consists of colored lines all
    -			going in one direction, and a 'radial' gradient, which consists of colored circles emanating from one circle to another.
    -			Because of their basic shape, jsPlumb supports only <em>linear</em> gradients for
    -			Connectors.  But for Endpoints, jsPlumb supports both <em>linear</em> and <em>radial</em> gradients.</p>
    -			
    -				<h5>Connector gradients</h5>
    -				To specify a linear gradient to use in a Connector, you must add a <em>gradient</em> object to your
    -				Connector's <em>paintStyle</em>, for instance:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : "window2",
    -	target : "window3",
    -	paintStyle:{
    -		gradient:{
    -			stops:[[0,"green"], [1,"red"]]
    -		},
    -		lineWidth:15
    -	}
    -});
    -</pre>
    -</div>
    -Here we have connected window2 to window3 with a 15 pixel wide connector that has a gradient from green to red.</p><p> Notice the <strong>gradient</strong> object and the
    -<strong>stops</strong> list inside it - the gradient consists of an arbitrary number of these "color stops".  Each color stop is comprised
    -of two values - [position, color].  Position must be a decimal value between 0 and 1 (inclusive), and indicates where the color
    -stop is situated as a fraction of the length of the entire gradient.  Valid values for
    -the colors in the <strong>stops</strong> list are the same as those that are valid for <strong>strokeStyle</strong> when describing a color.
    -			</p>
    -			As mentioned, the <strong>stops</strong> list can hold an arbitrary number of entries.  Here's an example of a gradient that goes from red to blue to green, and back again through blue to red:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle : {
    -		gradient:{
    -			stops:[[0,'red'], [0.33,'blue'], [0.66,'green'], [0.33,'blue'], [1,'red']]
    -		},
    -		lineWidth : 15
    -	}
    -});
    -</pre>
    -</div>
    -<strong>Note:</strong> when using the VML renderer, jsPlumb will simply ignore the gradient directive
    -so it is best to ensure you also supply a <em>strokeStyle</em> in your paintStyle object, to give jsPlumb something to fall back on.  If you do not supply
    -a <em>strokeStyle</em> your Connectors will be painted black.  The previous example might look like this, for instance:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle:{
    -		strokeStyle:'red',
    -		gradient:{
    -			stops:[[0,'red'], [0.33,'blue'], [0.66,'green'], [0.33,'blue'], [1,'red']]
    -		},
    -		lineWidth:15
    -	}
    -});
    -</pre>
    -</div>
    -Notice the <strong>strokeStyle:'red'</strong> directive at the beginning of the parameter list in <strong>paintStyle</strong>.
    -			<h5>Endpoint gradients</h5>
    -			Endpoint gradients are specified using the same syntax as Connector gradients.  You put the gradient specifier either in the
    -			<strong>endpoint</strong> member, or if you are specifying different Endpoints for each end of the Connector, in one or both of
    -			the values in the <strong>endpoints</strong> array.  Also, this information applies to the case that you are creating standalone
    -			Endpoints that you will be configuring for drag and drop creation of new Connections. 
    -			<p>
    -			This is an example of an Endpoint gradient that is different for each Endpoint in the Connector.  This comes from the main demo; it is
    -			the Connector joining Window 2 to Window 3:
    -			</p>
    -<div class="code">
    -<pre>var w23Stroke = 'rgb(189,11,11)';
    -jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle:{
    -		lineWidth:8,
    -		strokeStyle:w23Stroke
    -	},
    - 	anchors:[ [0.3,1,0,1], "TopCenter" ],
    - 	endpoint:"Rectangle",
    - 	endpointStyles:[
    - 		{ gradient : {stops:[[0, w23Stroke], [1, '#558822']] } },
    -    	{ gradient : {stops:[[0, w23Stroke], [1, '#882255']] } }
    -    ]
    -});
    -</pre>
    -</div>
    -The first entry in the gradient will be the one that is on the Connector end of the Endpoint.  You can of course have as many color stops as
    -you want in this gradient, just like with Connector gradients.
    -	<h5>Applying the gradient in Endpoints</h5>
    -	Only the Dot and Rectangle endpoints honour the presence of a gradient (and, remember, not in VML). The Image endpoint of course ignores a gradient
    -	as it does no painting of its own.
    -	<p>The type of gradient you will see depends on the Endpoint type:</p>
    -		<ul>
    -			<li><strong>Dot</strong> - renders a radial endpoint, with color stop 0 on the outside, progressing inwards as we move through color stops.
    -			<p>Radial gradients actually require more data than linear gradients - in a linear gradient we just move from one point to another, whereas
    -			in a radial gradient we move from one <em>circle</em> to another.  By default, jsPlumb will render a radial gradient using a source
    -			circle of the same radius as the Endpoint itself, and a target circle of 1/3 of the radius of the Endpoint (both circles share the
    -			same center as the Endpoint itself). This circle will be offset by radius/2 in each direction.</p>
    -			<p>You can supply your own values for these inside the gradient descriptor:</p>
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect({
    -	source : 'window3',
    -	target : 'window4',
    -    paintStyle:{
    -    	lineWidth:10,
    -    	strokeStyle:w34Stroke
    -    },
    -    anchors:[ "RightMiddle", "LeftMiddle" ],
    -    endpointStyle:{
    -    	gradient : {
    -    		stops:[ [0, w34Stroke], [1, w34HlStroke] ],
    -    		offset:37.5,
    -    		innerRadius:40
    -    	},
    -    	radius:55
    -    }
    - });
    -</pre>
    -</div>
    -Here we have instructed jsPlumb to make the gradient's inner radius 10px instead of the default 25/3 = 8 ish pixels, and the offset in each direction
    -will be 5px, instead of the default radius / 2 = 12.5 pixels.
    -<p>It is also possible to specify the offset and inner radius as percentages - enter the values as strings with a '%' symbol on the end:</p>
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect({
    -	source : 'window3', 
    -	target : 'window4',
    -    paintStyle:{
    -    	lineWidth:10,
    -    	strokeStyle:w34Stroke
    -    },
    -	anchors:[ "RightMiddle", "LeftMiddle" ],
    -	endpointStyle:{
    -		gradient : {
    -			stops:[ [0, w34Stroke], [1, w34HlStroke] ],
    -			offset:'68%',
    -			innerRadius:'73%'
    -		},
    -		radius:25
    -	}
    -});
    -</pre>
    -</div>
    -This will give roughly the same output as the example above (the percentages are not entirely exact).<br/><br/>
    -			</li>
    -			<li><strong>Rectangle</strong> - renders a linear endpoint, with color stop 0 closest to the end of the Connector</li>
    -		</ul>
    -		
    -<h4><a id="svgvmlcss">Styling SVG/VML using CSS</a></h4>
    -	A nice way of controlling your UI's appearance is to make use of the fact that both SVG and VML can be styled using CSS.
    -	<p>This section will be expanded in the next few weeks to give some decent examples of how to do so</p>
    -			
    -		
    -		
    -		</div>
    -
    -		<div class="section">
    -			<h3><a id="cssclasses">CSS Class Reference</a></h3>
    -			jsPlumb attaches classes to each of the UI components it creates.
    -			<p>These class names are exposed on the jsPlumb object and can be overridden if you need to do so (see the third column in the table) </p>
    -			<table width="70%" class="table">
    -				<tr><th>Component</th><th>CSS Class</th><th>jsPlumb Member</th></tr>
    -				<tr><td>Connector</td><td>_jsPlumb_connector</td><td>connectorClass</td></tr>
    -				<tr><td>Endpoint</td><td>_jsPlumb_endpoint</td><td>endpointClass</td></tr>
    -				<tr><td>Overlay</td><td>_jsPlumb_overlay</td><td>overlayClass</td></tr>
    -			</table>
    -			<p>You would typically use these to establish appropriate z-indices for your UI.</p>			
    -		</div>
    -				
    -		<div class="section">
    -			<h3><a id="animation">Animation</a></h3>
    -			jsPlumb offers an 'animate' function, which wraps the underlying animation engine for whichever library you happen to be using and
    -			inserts a callback for jsPlumb to repaint whatever it needs to at each step.  You could of course do this yourself; it's a convenience
    -			method really.
    -			<p>The method signature is:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.animate : function(el, properties, options) 
    -</pre>
    -</div>			
    -The arguments are as follows:
    -	<ul>
    -		<li><strong>el</strong> - element id, or element object from the library you're using.</li>
    -		<li><strong>properties</strong> - properties for the animation, such as duration etc.</li>
    -		<li><strong>options</strong> - options for the animation, such as callbacks etc.</li>
    -	</ul>
    -	
    -		<h4>jQuery and the 'revert' option</h4>
    -		jQuery offers a 'revert' option that you can use to instruct it to revert a drag under some condition. This is
    -		rendered in the UI as an animation that returns the drag object from its current location to wherever it started
    -		out.  It's a nice feature.  Unfortunately, the animation that runs the revert does not offer any lifecycle
    -		callback methods - no 'step', no 'complete' - so its not possible for jsPlumb to know that the revert 
    -		animation is taking place.
    -	
    -		</div>
    -		
    -			<div class="section">
    -			<h3><a id="connectionInfo">Retrieving Connection Information</a></h3>
    -			jsPlumb offers one fairly versatile method - <strong>getConnections</strong> - to retrieve information about the
    -			currently managed connections.  
    -			<p>Before you use this method you should understand jsPlumb's notion of 'scope' - documentation
    -			is <a href="#dragAndDropScope">here</a> </p>
    -			
    -			<h4>Retrieving connections for a single scope</h4>
    -			<p>To do this, you call getConnections with either no arguments, in which case jsPlumb uses the default scope, or with a string
    -			specifying one scope</p>
    -			<div class="code">
    -<pre>var connectionList = jsPlumb.getConnections();     // you get a list of Connection objects that are in the default scope.
    -</pre>			
    -			</div>
    -			Compare this with:
    -			<div class="code">
    -<pre>var connectionList = jsPlumb.getConnections("myScope");     // you get a list of Connection objects that are in "myScope".
    -</pre>			
    -			</div>
    -			<h4>More advanced filtering</h4>
    -			getConnections optionally takes a JS object specifying filter parameters, of which there are three:
    -			<ul>
    -				<li>scope - the scope(s) of the connection type(s) you wish to retrieve</li>
    -				<li>source - limits the returned connections to those that have this source id</li>
    -				<li>target - limits the returned connections to those that have this target id</li>
    -			</ul>			
    -			Each of these three parameters may be supplied as a string, or a list of strings - see the examples below.
    -			<p>
    -			The return value of a call to getConnection using a JS object as parameter varies on how many scopes you defined.  If you defined only
    -			a single scope then jsPlumb returns you a list of Connections in that scope.  Otherwise the return value is a dictionary whose keys are 
    -			scope names, and whose values are lists of Connections. For example, the following call:
    -			</p>
    -			<div class="code">
    -<pre>
    -	jsPlumb.getConnections({scope:["someScope", "someCustomScope"]});
    -</pre>
    -</div>
    -would result in this output:			
    -<div class="code">
    -<pre>
    -{
    -	"someScope" : [ 1..n Connections ],
    -	"someCustomScope": [ 1..m Connections ]
    -}
    -</pre>
    -</div>			
    -		The following examples show the various ways you can get connection information:
    -	<ul>
    -		<li>Get all connections:
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getAllConnections();  
    -</pre>
    -</div>						
    -		</li>
    -<li>Get all connections for the default scope only (return value is a list):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections();  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given scope (return value is a list):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:"myTestScope"});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given scopes (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:["myTestScope", "yourTestScope"]});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given source (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:"mySourceElement"});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given sources (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:["mySourceElement", "yourSourceElement"]});  
    -</pre>
    -</div>						
    -		</li>		
    -<li>Get all connections for the given target (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({target:"myTargetElement"});  
    -</pre>
    -</div>						
    -		</li>				
    -<li>Get all connections for the given source and targets (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:"mySourceElement", target:["target1", "target2"]});  
    -</pre>
    -</div>						
    -		</li>				
    -		
    -<li>Get all connections for the given scope, with the given source and target (return value is a list of connections):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:'myScope", source:"mySourceElement", target:"myTargetElement"});  
    -</pre>
    -</div>						
    -		</li>				
    -		
    -	</ul>	
    -
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="events">Events</a></h3>
    -			jsPlumb supports binding to several different events on Connections, Endpoints and Overlays, and also on the jsPlumb
    -			object itself.  
    -			
    -			<h4><a id="jsPlumbEvents">jsPlumb Events</a></h4>
    -			<p>To bind an event to jsPlumb itself, use <strong>jsPlumb.bind(event, callback)</strong>:</p>
    -			<div class="code">
    -<pre>
    -jsPlumb.bind("jsPlumbConnection", function(info) {
    -   .. update your model in here, maybe.
    -});
    -</pre>
    -</div>
    -<p>These are the events you can bind to on the jsPlumb class:</p>
    -			<ul>
    -				<li class="bullet"><strong>jsPlumbConnection</strong> - notification a Connection was established.
    -					<p>
    -						The callback is passed an object with the following properties:					
    -					</p>
    -					<div class="code">
    -<pre>
    -{
    -  connection		: 	the new Connection.  you can register listeners on this etc.
    -  sourceId			:	id of the source element in the Connection
    -  targetId			:	id of the target element in the Connection
    -  source			:	the source element in the Connection
    -  target			:	the target element in the Connection
    -  sourceEndpoint	:	the source Endpoint in the Connection
    -  targetEndpoint	:	the targetEndpoint in the Connection
    -}
    -</pre>
    -					</div>
    -					<p>
    -All of the source/target properties are actually available inside the Connection object, but - for one of those rubbish historical
    -reasons - are provided separately because of a vagary of the <em>jsPlumbConnectionDetached</em> callback, which is discussed below.
    -</p>
    -				</li>
    -				<li class="bullet"><strong>jsPlumbConnectionDetached</strong> - notification a Connection was detached.  
    -					<p>
    -						As with <em>jsPlumbConnection</em>, the callback is passed an object with the following properties:					
    -					</p>
    -					<div class="code">
    -<pre>
    -{
    -  connection		: 	the Connection that was detached.  
    -  sourceId			:	id of the source element in the Connection <em>before it was detached</em>
    -  targetId			:	id of the target element in the Connection before it was detached
    -  source			:	the source element in the Connection before it was detached
    -  target			:	the target element in the Connection before it was detached
    -  sourceEndpoint	:	the source Endpoint in the Connection before it was detached
    -  targetEndpoint	:	the targetEndpoint in the Connection before it was detached
    -}
    -</pre>
    -
    -					</div>				
    -					<p>
    -As mentioned above, the source/target properties are provided separately from the Connection, because this event is fired whenever
    -a Connection is either detached and abandoned, or detached from some Endpoint and attached to another.  In the latter case, the
    -Connection that is passed to this callback is in an indeterminate state (that is, the Endpoints are still in the state they are
    -in when dragging, and do not reflect static reality), and so the source/target properties give you the real story.
    -</p>
    -<p> Like I said above, this is really just for rubbish historical reasons.  I'm attempting to document my way out of it!</p>
    -				
    -				</li>
    -				<li class="bullet"><strong>click</strong> - notification a Connection was clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification a Connection was double-clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>endpointClick</strong> - notification an Endpoint was clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>endpointDblClick</strong> - notification an Endpoint was double-clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>contextmenu</strong> - a right-click on some given component.  jsPlumb will report right clicks on both Connections and Endpoints; the callback is passed the component that received the right-click.
    -				<li class="bullet"><strong>beforeDrag</strong>
    -					This event is fired when a new connection is about to be dragged.  Your callback function
    -					is passed the Connection that jsPlumb has just created.  If you return false (or nothing) 
    -					from this callback, the new Connection is aborted.
    -				</li>
    -				<li class="bullet"><strong>beforeDrop</strong>
    -					This event is fired when a new connection has been dropped.  Your callback function
    -					is passed the Connection that jsPlumb has just created.  If you return false (or nothing) 
    -					from this callback, the new Connection is aborted and removed from the UI.
    -				</li>								
    -			</ul>
    -			<h4><a id="connectionEvents">Connection Events</a></h4>		
    -			<p>To bind to an event on a Connection, you also use the <strong>bind</strong> method:</p>
    -			<div class="code">
    -<pre>
    -var connection = jsPlumb.connect({source:"d1", target:"d2"});
    -connection.bind("click", function(conn) {
    -	console.log("you clicked on ", conn);
    -});
    -</pre>
    -</div>
    -<p>These are the Connection events to which you can bind a listener:</p>
    -			<ul>
    -				<li class="bullet"><strong>click</strong> - notification a Connection was clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification a Connection was double-clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>contextmenu</strong> - a right-click on the Connection. The callback is passed the Connection and the original event.
    -				<li class="bullet"><strong>mouseenter</strong> - notification the mouse entered the Connection's path.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>mouseexit</strong> - notification the mouse exited the Connection's.  The callback is passed the Connection and the original mouse event.</li>
    -			</ul>
    -			<h4><a id="endpointEvents">Endpoint Events</a></h4>			
    -			<p>To bind to an event on a Endpoint, you again use the <strong>bind</strong> method:</p>
    -			<div class="code">
    -<pre>
    -var endpoint = jsPlumb.addEndpoint("d1", { someOptions } );
    -endpoint.bind("click", function(endpoint) {
    -	console.log("you clicked on ", endpoint);
    -});
    -</pre>
    -</div>
    -<p>These are the Endpoint events to which you can bind a listener:</p>
    -			<ul>
    -				<li class="bullet"><strong>click</strong> - notification an Endpoint was clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification an Endpoint was double-clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>contextmenu</strong> - a right-click on the Endpoint. The callback is passed the Endpoint and the original event.
    -				<li class="bullet"><strong>mouseenter</strong> - notification the mouse entered the Endpoint.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>mouseexit</strong> - notification the mouse exited the Endpoint.  The callback is passed the Endpoint and the original mouse event.</li>
    -			</ul>
    -			<h4><a id="overlayEvents">Overlay Events</a></h4>
    -			Registering event listeners on an Overlay is a slightly different procedure - you provide them as arguments to the Overlay's 
    -			constructor.  This is because you never actually act on an 'Overlay' object.  Here's how to register a click listener on an Overlay:
    -			<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"el1",
    -	target:"el2",
    -	overlays:[
    -		[ "Label", {
    -			events:{
    -				click:function(labelOverlay, originalEvent) { alert("you clicked on the label overlay for this connection :" + labelOverlay.connection) }
    -			}
    -		}] 	
    -	]
    -});
    -</pre>			
    -			</div>		
    -		</div>
    -
    -				<div class="section">
    -			<a id="examples"><h3>jsPlumb.connect Examples</h3></a>
    -This section provides examples of how to use the programmatic API to establish Connections. 
    -			<p>
    -			The basic syntax of a call is that you execute 'connect', providing a source and a target, and optionally a paintStyle and preferences for where you
    -			want the plumbing to be anchored on each element, as well as the type of connector to use. 
    -			</p>
    -			<ul>
    -				<li>Connect window1 to window2 with the default settings:
    -					<div class="code">
    -						<pre>jsPlumb.connect({source:"window1", target:"window2"});</pre>
    -					</div>
    -This connects 'window1' to 'window2' using the jsPlumb defaults - a red Bezier curve Connector and black Dot Endpoints.
    -				</li>
    -				<li>Connect window1 to window2 with a 15 pixel wide yellow Connector, and a slightly brighter endpoint (remember the default Endpoint is a Dot):
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -					</div>
    -				</li>
    -				
    -				<li>Connect window1 to window2 with a 15 pixel wide yellow Connector, and a slightly brighter endpoint:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -					</div>
    -				</li>				
    -				<li>Connect window3 to 'window4' with a 10 pixel wide, semi opaque blue Connector, anchored to the left middle of window3, and the right middle of window4, with a Rectangle endpoint of width 10 and height 8:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window3',
    -	target:'window4',
    -	paintStyle:{ lineWidth:10, strokeStyle:'rgba(0, 0, 200, 0.5)' },
    -	anchors:["RightMiddle", "LeftMiddle"],
    -	endpoint:[ "Rectangle", { width:10, height:8 } ]
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window2 to window3 with a default Connector from the top center of window2 to the bottom center of window3, and rectangular endpoints:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window2',
    -	target:'window3',
    -	paintStyle:{lineWidth:8, strokeStyle:'rgb(189,11,11)'},
    -	anchors:["BottomCenter", "TopCenter"],
    -	endpoint:"Rectangle"
    -});</pre>
    -					</div>
    -				</li>
    -
    -
    -				<li>Connect window1 to window2 with a 15 px wide yellow Bezier. endpoints are a slightly lighter shade of yellow.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	anchors:["BottomCenter", [0.75,0,0,-1]],
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -</div></li>
    -<li>Connect window3 to window4 with a 10px wide blue-ish half transparent Bezier. put endpoints underneath the element they attach to.
    -the endpoints have a radial gradient. both ways of specifying gradient positioning are shown here.
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect( {
    -	source:'window3',
    -	target:'window4',
    -		 paintStyle:{lineWidth:10, strokeStyle:w34Stroke},
    -		 anchors:["RightMiddle", "LeftMiddle"],
    -		 endpointStyle:{ gradient : {stops:[[0, w34Stroke], [1, w34HlStroke]], offset:17.5, innerRadius:15 }, radius:35},
    -		 //endpointStyle:{ gradient : {stops:[[0, w34Stroke], [1, w34HlStroke]], offset:'78%', innerRadius:'73%'}, radius:35 },
    -		 endpointsOnTop:false
    -	}
    -);</pre>
    -</div></li>
    -<li>Connect window2 to window3 with an 8px red Bezier and default rectangular endpoints.  see also how the first anchor is
    -specified here - this is how you create anchors in locations jsPlumb does not offer shortcuts for.
    -the endpoints in this example have linear gradients applied.
    -<div class="code">
    -<pre>var w23Stroke = 'rgb(189,11,11)';
    -jsPlumb.connect({
    -	source:'window2',
    -	target:'window3',
    -	paintStyle:{lineWidth:8,strokeStyle:w23Stroke},
    -	anchors:[[0.3,1,0,1], "TopCenter"],
    -	endpoint:"Rectangle",
    -	endpointStyles:[{ gradient : {stops:[[0, w23Stroke], [1, '#558822']] }},
    -       				{ gradient : {stops:[[0, w23Stroke], [1, '#882255']] }}]
    -});</pre>
    -</div></li>
    -
    -<li>Connect window5 to window6 from center to center, 5px wide line that is green and half transparent. the endpoints are
    -125px in radius and spill out from underneath their elements.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window5',
    -	target:'window6',
    -	anchors:["Center", "Center"],
    -	paintStyle:{lineWidth:5,strokeStyle:'rgba(0,255,0,0.5)'},
    -	endpointStyle:{radius:125}
    -});</pre>
    -</div></li>
    -
    -<li>Connect window4 to window5 from bottom right to top left, with a 7px straight line purple connector, and an image as the endpoint,
    -placed on top of the element it is connected to.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window4",
    -	target:"window5",
    -	anchors:[ "BottomRight","TopLeft" ],
    -	paintStyle:{ lineWidth:7, strokeStyle:"rgb(131,8,135)" },
    -	endpoint:[ "Image", { src:"http://morrisonpitt.com/jsPlumb/img/endpointTest1.png" } ],
    -	connector:"Straight"
    -});</pre>
    -</div></li>
    -
    -
    -				<li>Connect window5 to window6 between their center points with a semi-opaque connector, and 125px endpoints:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window5",
    -	target:"window6",
    -	anchors:[ "Center", "Center" ],
    -	paintStyle:{ lineWidth:5, strokeStyle:"rgba(0,255,0,0.5)" },
    -	endpointStyle:{ radius:125 }
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window7 to window8 with a 10 pixel wide blue Connector, anchored on the top left of window7 and the bottom right of window8:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window7",
    -	target:"window8",
    -	paintStyle:{ lineWidth:10, strokeStyle:"blue" },
    -	anchors:[ "TopLeft", "BottomRight" ]
    -});
    -</pre>
    -					</div>
    -				</li>
    -				<li>Connect the bottom right corner of window4 to the top left corner of window5, with rectangular endpoints of size 40x40 and a hover color of light blue:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window4",
    -	target:"window5",
    -	anchors:["BottomRight","TopLeft"],
    -	paintStyle:{lineWidth:7,strokeStyle:'rgb(131,8,135)'},
    -	hoverPaintStyle:{ strokeStyle:"rgb(0, 0, 135)" },
    -	endpointStyle:{ width:40, height:40 },
    -	endpoint:"Rectangle",
    -	connector:"Straight"
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window1 to window2 with the default paint settings but provide some drag options (which are passed through to the underlying library's draggable call):
    -					<div class="code">
    -						<pre>jsPlumb.connect({source:'window1', target:'window2', dragOptions:{cursor:'crosshair'}});</pre>
    -					</div>
    -				</li>
    -</div>
    -		
    -		<div class="section">
    -			<a id="dragAndDropExamples"><h3>Draggable Connections Examples</h3></a>
    -			This is a list of examples of how to use jsPlumb to create Connections using drag and drop.
    -			<p>
    -			The basic procedure is:
    -			<ol>
    -				<li>1a. Create Endpoints and register them on elements in your UI</li>
    -				<li>1b. alternatively, create a source endpoint and then make some element a drop target </li>
    -				<li>2. Drag and Drop</li>
    -			</ol>
    -			That's all there is to it.  Of course there are plenty of options you can set when doing this...it will be easier
    -			to show you some examples:
    -			</p>
    -			<ul>
    -				<li>Define an Endpoint with default appearance, that is both a source and target of new Connections:
    -<div class="code">
    -<pre>var endpointOptions = { isSource:true, isTarget:true }; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Register that Endpoint on window3, specifying that it should be located in the top center of the element:
    -<div class="code">
    -<pre>var window3Endpoint = jsPlumb.addEndpoint('window3', { anchor:"TopCenter" }, endpointOptions );  
    -</pre>
    -</div>
    -Notice here the usage of the three-argument addEndpoint  - we can reuse 'endpointOptions' with a different Anchor for another element.  This is a useful
    -practice to get into.<br/><br/>
    -				</li>
    -				
    -				<li>Now register that Endpoint on window4, specifying that it should be located in the bottom center of the element:
    -<div class="code">
    -<pre>var window4Endpoint = jsPlumb.addEndpoint('window4', { anchor:"BottomCenter" }, endpointOptions );  
    -</pre>
    -</div>
    -Now we have two Endpoints, both of which support drag and drop of new Connections.  We can use these to make a programmatic Connection, too, though:<br/><br/>
    -				</li>				
    -				
    -				<li>Connect window3 to window4 with a 25px wide yellow Bezier that has a 'curviness' of 175:
    -<div class="code">
    -<pre>jsPlumb.connect({ 
    -	source:window3Endpoint,
    -	target:window4Endpoint,
    -	connector: [ "Bezier", 175 ],
    -	paintStyle:{ lineWidth:25, strokeStyle:'yellow' }
    -});  
    -</pre>
    -</div>
    -				</li>
    -				
    -				<li>Define an Endpoint that creates Connections that are 20px wide straight lines, that is both a source and target of new Connections,
    -				and that has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Define an Endpoint that will be anchored to "TopCenter".  It creates Connections that are 20px wide straight lines, that is both a source and target of new Connections,
    -				and that has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchor:"TopCenter", 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Define an Endpoint that will create a dynamic anchor which can be positioned at "TopCenter" or "BottomCenter".  It creates Connections that are 20px wide straight lines, it is both a source and target of new Connections,
    -				and it has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchor:[ "TopCenter", "BottomCenter" ], 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Exactly the same as before, but shows how you can use "anchors" instead of "anchor", if that makes you feel happier:
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchors:[ "TopCenter", "BottomCenter" ], 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>								
    -				
    -<li>Define an Endpoint that is a 30px blue dot, creates Connections that are 20px wide straight lines, is both a source and target of new Connections,
    -				has a 'scope' of 'blueline', and has an event handler that pops up an alert (note: the event handler name means this example is jQuery - MooTools
    -				and YUI3 use different event handler names):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{fillStyle:'blue'},
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ drop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -			<li>Same example as before, but this is for MooTools, and the Endpoint can support up to 5 connections (the default is 1):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{ fillStyle:'blue' },
    -	maxConnections:5,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ onDrop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Same example again, but maxConnections being set to -1 means that the Endpoint has no maximum limit of Connections:
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", {radius:30} ],
    -	style:{ fillStyle:'blue' },
    -	maxConnections:-1,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ onDrop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Same example again, but for YUI3.  Note the drop callback is "drop:hit":
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{fillStyle:'blue'},
    -	maxConnections:-1,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ "drop:hit":function(e, ui) { alert('drop!'); } }	 	
    -}; 
    -</pre>
    -</div>				 				
    -				</li>				
    -				
    -	<li>Assign a UUID to the endpoint options created above, and add as Endpoints to "window1" and "window2":
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("window1", { uuid:"abcdefg" }, endpointOptions );
    -jsPlumb.addEndpoint("window2", { uuid:"hijklmn" }, endpointOptions );
    -</pre>
    -</div>	
    -	</li>
    -	
    -	<li>Connect the two Endpoints we just registered on "window1" and "window2":
    -<div class="code">
    -<pre>
    -jsPlumb.connect({uuids:["abcdefg", "hijklmn"]});
    -</pre>
    -</div>	
    -	</li>
    -	
    -	<li>Create a source Endpoint, register it on some element, then make some other element a Connection target:
    -<div class="code">
    -<pre>
    -var sourceEndpoint = { isSource:true, endpoint:[ "Dot", { radius:50 } ] };
    -var targetEndpoint = { endpoint:[ "Rectangle", { width:10, height:10 } ] };
    -jsPlumb.addEndpoint( "window1", sourceEndpoint );
    -jsPlumb.makeTarget( "window2", targetEndpoint );
    -</pre>
    -</div>	
    -Notice that the endpoint definition we use on the target window does not include the "isTarget:true" directive.  jsPlumb ignores that flag when
    -creating a connection using an element as the target; but if you then tried to drag another connection to the Endpoint that was created, you would
    -not be able to.  To permit that, you would set 'isTarget:true' on the targetEndpoint options defined above.
    -	
    -	</li>
    -				
    -			</ul>
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="utilityFunctions">Utility Functions</a></h3>
    -			<ul>
    -				<li>Detach window5 from all connections
    -					<div class="code">
    -						<pre>jsPlumb.detachAll("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Hide all window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.hide("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Hide all window5's connections endpoints
    -					<div class="code">
    -						<pre>jsPlumb.hide("window5", true);</pre>
    -					</div>
    -				</li>
    -				<li>Show all window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.show("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Show all window5's connections and endpoints.  Note that in the case that you call jsPlumb.show with two arguments, jsPlumb
    -				will also not make a connection visible if it determines that the other endpoint in the connection is not visible.
    -					<div class="code">
    -						<pre>jsPlumb.show("window5", hide);</pre>
    -					</div>
    -				</li>				
    -				<li>Toggle the visibility of window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.toggle("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of all of window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.repaint("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of all of window5, window6 and window11's connections
    -					<div class="code">
    -						<pre>jsPlumb.repaint( [ "window5", "window6", "window11" ] );</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of every connection
    -					<div class="code">
    -						<pre>jsPlumb.repaintEverything();</pre>
    -					</div>
    -				</li>
    -				<li>Detach every connection that the given instance of jsPlumb is managing
    -					<div class="code">
    -						<pre>jsPlumb.detachEveryConnection();</pre>
    -					</div>
    -				</li>
    -				<li>Detach all connections from "window1"
    -					<div class="code">
    -						<pre>jsPlumb.detachAllConnections("window1");</pre>
    -					</div>
    -				</li>
    -				<li>Remove the given Endpoint from element "window1", deleting its Connections. 
    -					<div class="code">
    -						<pre>jsPlumb.removeEndpoint("window1", someEndpoint);</pre>
    -					</div>
    -				</li>
    -				<li>Remove all Endpoints for the element 'window1', deleting their Connections.
    -					<div class="code">
    -						<pre>jsPlumb.removeAllEndpoints("window1");</pre>
    -					</div>
    -				</li>	
    -				<li>Removes every Endpoint managed by this instance of jsPlumb, deleting all Connections.
    -				This is the same as jsPlumb.reset(), effectively, but it does not clear out the event listeners list. 
    -					<div class="code">
    -						<pre>jsPlumb.removeEveryEndpoint();</pre>
    -					</div>
    -				</li>
    -				<li>Deletes the given Endpoint and all its Connections. 
    -					<div class="code">
    -						<pre>jsPlumb.deleteEndpoint(endpoint);</pre>
    -					</div>
    -				</li>
    -				<li>Removes every endpoint, detaches every connection, and clears the event listeners list.  Returns jsPlumb instance to its initial state.  
    -					<div class="code">
    -						<pre>jsPlumb.reset();</pre>
    -					</div>
    -				</li>	
    -				<li>Library-agnostic method to get a selector from the given selector specification.  This function is used mostly by the jsPlumb demos, to cut down on repeating code across the different libraries.  But perhaps you are writing something with jsPlumb that you also wisht to be library agnostic - this function can help you. The returned object, in each of jQuery, MooTools and YUI, has a ".each" iterator.
    -					<div class="code">
    -						<pre>var selector = jsPlumb.getSelector(".someSelector .spec");</pre>
    -					</div>					
    -				</li>
    -				<li>Set window1 to be not draggable, no matter what some jsPlumb command may request.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggable("window1", false);</pre>
    -					</div>
    -				</li>
    -				<li>Set window1 and window2 to be not draggable, no matter what some jsPlumb command may request.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggable(["window1","window2"], false);</pre>
    -					</div>
    -				</li>
    -				<li>Sets whether or not elements that are connected are draggable by default.
    -				The default for this is true.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggableByDefault(false);</pre>
    -					</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (all libraries). Passes in an on drag callback
    -				<div class="code">
    -					<pre>jsPlumb.draggable("window1");</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 and window2 as draggable elements (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(["window1","window2"]);</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable("window1");</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (jQuery)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (MooTools)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($$(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (YIU3)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(Y.all(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (jQuery)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($("#window1"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (MooTools)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($("window1"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (YUI3)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(Y.one("window1"));</pre>
    -				</div>
    -				</li>
    -			</ul>
    -		</div>					
    -		
    -		<div class="section">
    -			<h3><a id="developingJsPlumb">Advanced Topics</a></h3>
    -			<h4><a id="which">Which files are which?</a></h4>
    -			In development, jsPlumb is broken up into seven scripts:
    -				<ul>
    -					<li>- jsPlumb-x.x.x.js
    -					<p>This is the main jsPlumb engine.</p>					
    -					</li>
    -					<li>- jsPlumb-defaults-x.x.x.js
    -					<p>This contains the default Anchor, Endpoint, Connector and Overlay implementations.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-canvas-x.x.x.js
    -					<p>This contains the HTML5 canvas render code.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-svg-x.x.x.js
    -					<p>This contains the SVG render code.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-vml-x.x.x.js
    -					<p>This contains the VML render code.</p>
    -					</li>
    -					<li>- jsPlumb-connectors-statemachine-x.x.x.js
    -					<p>This State Machine connector.</p>
    -					</li>
    -					<li>- &lt;LIBRARY_PREFIX&gt;.jsPlumb-x.x.x.js
    -					<p>This contains library-specific helper methods.  jsPlumb ships with three of these - one each for jQuery, MooTools and YUI3. See below for information on how to create a new library implementation.</p>
    -					</li>
    -					<li>- jsBezier-0.3-min.js
    -					<p>These are the Bezier curve functions; they are maintained in a separate project called <a href="http://code.google.com/p/jsbezier/">jsBezier</a></p>
    -					</li>
    -				</ul>
    -			These seven files are packaged together to form the scripts that people use, for example:
    -			<ul>
    -				<li>jquery.jsPlumb-1.3.6-all.js
    -				<p>Contains jsPlumb-1.3.6.js, jsPlumb-defaults-1.3.6.js, jsPlumb-renderers-canvas-1.3.6.js, jsPlumb-renderers-svg-1.3.6.js, jsPlumb-renderers-vml-1.3.6.js, jsPlumb-connectors-statemachine-1.3.6.js, jquery.jsPlumb-1.3.6.js and jsBezier-0.3-min.js</p>
    -				</li>
    -				<li>jquery.jsPlumb-1.3.6-all-min.js
    -				<p>A minified version of the script above (minified using the YUI Compressor)</p>
    -				</li>
    -			</ul>
    -			<h4><a id="pluggableLibrarySupport">Pluggable Library Support</a></h4>
    -		Out of the box, jsPlumb can be run on top of jQuery, MooTools or YUI3.  This is achieved by
    -		delegating several core methods - tasks such as finding an element by id, finding an element's
    -		position or dimensions, initialising a draggable, etc - to the library in question.
    -		<p>To develop one of these, your test page should include the first two scripts discussed above, and
    -		then your own script containing your library specific functionality.  The existing implementations
    -		may be documented well enough for you to create your own, but contact me if you need assistance.  If you do this, it would be
    -		great to share it with everyone...</p>   
    -		<p>There is a group of people working on an ExtJS adapter currently.  There is no schedule for when this will be completed though.</p>
    -
    -			<!-- h4><a id="customConnectors">Custom Connectors</a></h4>			
    -					
    -			You can provide your own connectors if you need to. A Connector consists of two functions, which work as a pair.  First a call is made to the <em>compute</em> function:
    -
    -			<div class="code">
    -<pre>
    -this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) {
    -	...
    -	return dimensions;
    -}
    -</pre>
    -			</div>
    -			which is expected to return a list that the <em>paint</em> function can make sense of.  The first four entries in the
    -			list <strong>must be</strong> the [x,y,width,height] values for the canvas that the connector will be drawn on; jsPlumb will
    -			use this information to size the canvas prior to calling the Connector's <em>paint</em> function.  Therefore it
    -			is the Connector's responsibility to ensure that the returned dimensions describe a large enough space for
    -			the line that will be drawn on it.</p><p>The next four elements <strong>must be</strong> the coordinates of the two endpoints of the line
    -			you are going to draw.</p><p>The remainder of the items in the returned list are arbitrary, and will
    -			vary between Connector implementations; this list is passed in to a Connector's <em>paint</em> function, so each
    -			implementation will put into the list whatever it needs to paint itself.  For instance, the straight line
    -			connector only needs the [x,y] location of each end of the line it will paint, and that is one of the required entries, so
    -			it does not have to do anything extra,  whereas the Bezier connector adds the location of the two control points.  Other types of
    -			Connectors will do whatever is appropriate for their particular situation.
    -			</p>
    -			This is the method signature for the <em>paint</em> function:
    -			<div class="code">
    -				<pre>this.paint = function(dimensions, ctx) { .. }</pre>
    -			</div>
    -
    -			here, the 'dimensions' argument to the 'paint' function is the return value of the 'compute' function.  The 'ctx' argument is the Canvas context; you
    -			will do all your drawing on this.
    -			</p>
    -			To change the connector from the default, specify it in your connect call:
    -			<div class="code">
    -				<pre>jsPlumb.connect({source:'someWindow', target:'otherWindow', connector:new jsPlumb.Connectors.Straight()});</pre>
    -			</div>
    -			note that you can use shorthand for connectors if you don't need to specify parameters to it:
    -			<div class="code">
    -				<pre>jsPlumb.connect({source:'someWindow', target:'otherWindow', connector:"Straight"});</pre>
    -			</div>
    -			This works in the same way as specifying Anchors as string does.
    -<p>
    -The section below this discusses Overlays, which allow you to decorate Connectors with
    -			things such as labels or arrows or whatever else you like. Overlays can only work with Connectors that implement a few helper functions.
    -			<h4>The concept of <strong>location</strong></h4>
    -			Before discussing the helper functions you first must be across the concept of 'location' as used by this mechanism.  The <em>location</em>
    -			on a connector can be a decimal value between 0 and 1 inclusive, which marks a proportionate amount of travel <em>along the path
    -			inscribed by the Connector</em>.  For a straight line connector this is a simple piece of maths, but for Bezier connectors it's a little
    -			bit more involved.
    -			<h4>Required Helper Methods</h4>
    -			<ul>
    -				<li><strong>pointOnPath(location)</strong> - returns an [x,y] point corresponding to the given location</li>
    -				<li><strong>pointAlongPathFrom(location, distance)</strong> - returns an [x,y] point corresponding to travelling 'distance' pixels along the connector from 'location'.</li>
    -				<li><strong>gradientAtPoint(location)</strong> - returns the connector's gradient at the given location.  For linear connectors such as Straight this is constant, but for Bezier connectors the gradient changes continually.</li>
    -				<li><strong>perpendicularToPathAt(location, distance, length)</strong> - returns a line that is perpendicular to (and centered on) the connector at 'distance' from the given location, with length 'length'.</li>
    -			</ul>
    -</p>
    -<h4><a id="customEndpoints">Custom Endpoints</a></h4>
    -<p>To create your own Endpoint implementation, you need to implement a single method:
    -				<div class="code"><pre>paint : function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { ... }</pre></div>
    -				The arguments to this method are as follows:
    -				<ul>
    -					<li>anchorPoint - [x,y] location of the anchor point on screen</li>
    -					<li>orientation - [x,y] hints for the general direction the anchor points to</li>
    -					<li>canvas - the canvas to draw into</li>
    -					<li>endpointStyle - Javascript object containing style directives as discussed above.  The contents of this are arbitrary, so if you write a new Endpoint that needs some extra settings, you can add them no hassle.</li>
    -					<li>connectorPaintStyle - the style being used to paint the associated Connector.</li>
    -				</ul>
    -			</p>
    -			<p>
    -				It is your responsibility to size and locate the canvas to suit your needs. jsPlumb provides the following
    -				helper method to assist you:
    -				<div class="code"><pre>jsPlumb.sizeCanvas(canvas, x, y, width, height);</pre></div>
    -				Allows you to locate the canvas on screen and to size it.
    -			</p>
    -<h4><a id="customOverlays">Custom Overlays</a></h4>
    -			Overlays can only work with Connectors that implement the methods <strong>pointOnPath</strong>, <strong>pointAlongPathFrom</strong>,
    -			<strong>gradientAtPoint</strong>, and <strong>perpendicularToPathAt</strong>.  These are discussed in more detail in the Custom Connectors section above.  Both of the Connectors
    -			that come with jsPlumb - Straight and Bezier -implement these methods; if you write a custom connector, or have written a custom connector, you will need to supply
    -			them.   
    -			<h4>Overlay Interface</h4>
    -			An Overlay is required to implement two methods in order to be usable by jsPlumb:
    -			<ul>
    -				<li><strong>computeMaxSize(connector, context)</strong> - returns an integer value indicating the larger of this overlay's width and height.  It is used by jsPlumb to ensure that the canvas is large enough to accomodate the overlay.  The examples below should help to clarify this.</li>
    -				<li><strong>draw(connector, ctx)</strong> - draws the overlay.  What happens in this method is up to the given implementation.</li>
    -			</ul>
    -			<h4>computeMaxSize methods</h4>
    -			This is the Arrow overlay's computeMaxSize method:
    -			<div class="code">
    -<pre>
    -this.computeMaxSize = function() { return width; }
    -</pre>			
    -			</div>
    -Here, <em>width</em> is a private member of Arrow that indicates the width of the arrow's tail.  So the Arrow overlay reports that figure as the width it needs.					
    -Contrast this with the Label Overlay's computeMaxSize method:
    -		<div class="code">
    -<pre>
    -this.computeMaxSize = function(connector, ctx) {
    -  	if (labelText) {
    -  		ctx.save();
    -        if (self.labelStyle.font) ctx.font = self.labelStyle.font;
    -         var t = ctx.measureText(labelText).width;			            
    -		// a fake text height measurement: use the width of upper case M
    -		var h = ctx.measureText("M").width;					
    -		labelPadding = self.labelStyle.padding || 0.25;
    -		labelWidth = t + (2 * t * labelPadding);
    -		labelHeight = h + (2 * h * labelPadding);
    -		ctx.restore();
    -		return Math.max(labelWidth, labelHeight);
    -  	}
    - 	return 0;
    - };
    -</pre>			
    -			</div>
    -The Label overlay has to use the context to determine how big it will be on screen.
    -
    -<h4>draw methods</h4>
    -To give you a taste for how you can interact with a connector, consider the first few lines of the Arrow overlay's draw method:
    -<div class="code">
    -<pre>
    -this.draw = function(connector, ctx) {
    -	// this is the arrow head position
    -	var hxy = connector.pointAlongPathFrom(self.loc, length / 2);		
    -	// this is the center of the tail
    -	var txy = connector.pointAlongPathFrom(self.loc, -length / 2), tx = txy[0], ty = txy[1];
    -	// this is the tail vector
    -	var tail = connector.perpendicularToPathAt(self.loc, -length / 2, width);
    -	
    -	...
    -</pre>
    -</div>
    -	<p>The first two calls to the connector use the <strong>pointAlongPathFrom</strong> method, which passes <em>self.loc</em> as the location of the
    -	point on the path, and <em>length / 2</em> as the number of pixels along the path to traverse.</p>
    -	<p><em>self.loc</em> is Arrow's internal member describing the location of the overlay, and it is a decimal (between 0 and 1 inclusive) that points to
    -	a location at some distance along the path inscribed by the connector.  So these first two calls get us [x,y] locations of points on 
    -	the connector that mark the head and tail points for the arrow.  
    -	</p>		
    -	<p>
    -	The <strong>connector.perpendicularToPathAt(self.loc, -length / 2, width)</strong> call returns a line description of a line that is perpendicular to, and centered on, the connector
    -	at "-length / 2" pixels from the given point, with the given width (width is an internal member of Arrow).  At this point, the Arrow object has the three main points it
    -	needs in order to draw itself.
    -	</p>
    -		</div-->
    -</div>
    -
    -</body>
    -
    -</html>
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.6/index.html b/demo/doc/archive/1.3.6/index.html
    deleted file mode 100644
    index 419c1c500..000000000
    --- a/demo/doc/archive/1.3.6/index.html
    +++ /dev/null
    @@ -1,81 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.6 documentation</title>
    -		<link rel="stylesheet" target="contentFrame" href="/mp.css"></link>
    -		<link rel="stylesheet" target="contentFrame" href="jsPlumbDoc.css"></link>
    -	</head>
    -	<body>
    -		<div class="index section">
    -			<h3>jsPlumb 1.3.6</h3>
    -			<ul>
    -				<li><a target="contentFrame" href="content.html#summary">Summary</a></li>
    -				<li><a target="contentFrame" href="content.html#changes">Changes since version 1.3.5</a></li>
    -				
    -				<li><h4>Setup</h4></li>
    -				<li><a target="contentFrame" href="content.html#imports">Required Imports</a></li>
    -				<li><a target="contentFrame" href="content.html#renderMode">Render Mode</a></li>
    -				<li><a target="contentFrame" href="content.html#initializing">Initializing jsPlumb</a></li>
    -				<li><a target="contentFrame" href="content.html#unload">Unloading jsPlumb</a></li>
    -				<li><a target="contentFrame" href="content.html#multipleInstances">Multiple jsPlumb Instances</a></li>
    -				<li><a target="contentFrame" href="content.html#zIndex">Z-index Considerations</a></li>
    -				<li><a target="contentFrame" href="content.html#containerDefault">Where does jsPlumb add elements?</a></li>
    -				
    -				<li><h4>Introduction</h4></li>
    -				<li><a target="contentFrame" href="content.html#jsPlumbBasics">Basic Concepts</a></li>
    -				<li><a target="contentFrame" href="content.html#anchors">Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#staticAnchors">Static Anchors</a></li>				
    -				<li><a target="contentFrame" href="content.html#dynamicAnchors">Dynamic Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#continuousAnchors">Continuous Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#definitions">Connector, Endpoint & Overlay Definitions</a></li>
    -				<li><a target="contentFrame" href="content.html#connectors">Connectors</a></li>
    -				<li><a target="contentFrame" href="content.html#endpoints">Endpoints</a></li>
    -				<li><a target="contentFrame" href="content.html#overlays">Overlays</a></li>
    -				<li><a target="contentFrame" href="content.html#defaults">Defaults</a></li>								
    -				
    -				<li><h4>Connections</h4></li>
    -				<li><a target="contentFrame" href="content.html#simpleConnections">Programmatic Connections</a></li>
    -				<li><a target="contentFrame" href="content.html#draggableConnections">Connections using Drag and Drop</a></li>
    -				<li><a target="contentFrame" href="content.html#sourcesAndTargets">Elements as Sources &amp; Targets</a></li>
    -				<li class="sub"><a target="contentFrame" href="content.html#makeTarget">makeTarget</a></li>
    -				<li class="sub"><a target="contentFrame" href="content.html#makeSource">makeSource</a></li>
    -				<li><a target="contentFrame" href="content.html#dragOptions">Drag Options</a></li>				
    -				<li><a target="contentFrame" href="content.html#dropOptions">Drop Options</a></li>
    -				<li><a target="contentFrame" href="content.html#dragAndDropScope">Drag and Drop Scope</a></li>
    -				
    -				<li><h4>Interceptors</h4></li>
    -				<li><a target="contentFrame" href="content.html#beforeDrag">Before Drag</a></li>
    -				<li><a target="contentFrame" href="content.html#beforeDrop">Before Drop</a></li>
    -				
    -				<li><h4>Appearance</h4></li>
    -				<li><a target="contentFrame" href="content.html#paintstyles">Paint Styles</a></li>
    -				<li><a target="contentFrame" href="content.html#hoverpaintstyles">Hover Paint Styles</a></li>
    -				<li><a target="contentFrame" href="content.html#gradients">Gradients</a></li>				
    -				
    -				<li><h4>Miscellaneous</h4></li>
    -				<li><a target="contentFrame" href="content.html#cssclasses">CSS Class Reference</a></li>
    -				<li><a target="contentFrame" href="content.html#animation">Animation</a></li>
    -				<li><a target="contentFrame" href="content.html#connectionInfo">Retrieving Connection Information</a></li>
    -				
    -				<li><h4>Events</h4></li>
    -				<li><a target="contentFrame" href="content.html#events">jsPlumb Events</a></li>
    -				<li><a target="contentFrame" href="content.html#connectionEvents">Connection Events</a></li>
    -				<li><a target="contentFrame" href="content.html#endpointEvents">Endpoint Events</a></li>
    -				<li><a target="contentFrame" href="content.html#overlayEvents">Overlay Events</a></li>
    -				
    -				<li><h4>Examples</h4></li>				
    -				<li><a target="contentFrame" href="content.html#examples">Programmatic API Examples</a></li>
    -				<li><a target="contentFrame" href="content.html#dragAndDropExamples">Draggable Connections Examples</a></li>
    -				<li><a target="contentFrame" href="content.html#utilityFunctions">Utility Functions</a></li>				
    -				
    -				<li><h4>Develop</h4></li>
    -				<li><a target="contentFrame" href="content.html#which">Which files are which?</a></li>
    -				<li><a target="contentFrame" href="content.html#pluggableLibrarySupport">Pluggable Library Support</a></li>
    -				<!-- li><a target="contentFrame" href="content.html#customConnectors">Custom Connectors</a></li>
    -				<li><a target="contentFrame" href="content.html#customEndpoints">Custom Endpoints</a></li>
    -				<li><a target="contentFrame" href="content.html#customOverlays">Custom Overlays</a></li-->
    -			</ul>
    -			<strong>This document refers to release 1.3.6 of jsPlumb.</strong>  
    -			<strong>23 Feb 2012</strong>
    -		</div>		
    -	</body>
    -</html>
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.6/jsPlumbDoc.css b/demo/doc/archive/1.3.6/jsPlumbDoc.css
    deleted file mode 100644
    index bbe50c8ee..000000000
    --- a/demo/doc/archive/1.3.6/jsPlumbDoc.css
    +++ /dev/null
    @@ -1,126 +0,0 @@
    -body { 
    -	background-color:white; font-size:90%;
    -	background-image:url(../img/dynamicAnchorBg.jpg); 
    -}
    -/* docs */
    -.code { background-color:#e4e5ef; 
    -border:1px solid black; margin:1em; padding:0.8em; 
    -overflow:auto;
    -font-family:courier; 
    -font-size:8pt;
    -}
    -.section { border-bottom: 1px dotted black; padding:1em;font-family:helvetica;background-color:white;color:#333;}
    -.menu { 
    -	position:fixed; 
    -	right:10em; top:1em;
    -	background-color:white;
    -	padding:0.3em;
    -	opacity:0.8;
    -	z-index:5000;
    -	filter:alpha(opacity=80);
    -		box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -a {text-decoration:none;color:#05f;font-weight:bold;font-family:helvetica;}
    -a:hover {text-decoration:underline;}
    -.window, .label { 
    -	cursor:pointer;
    -	box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -#explanation { 
    -	background-color:white;
    -	color:black;
    -	border:0.25em solid #ddd; 
    -	position:absolute;
    -	right:3.5em;
    -	top:5em;
    -	width:22em;
    -	font-size:100%;
    -	-moz-border-radius:1em;
    -	border-radius:1em;
    -	padding:1em;
    -	z-index:1000;
    -	opacity:0.9;
    -	filter:alpha(opacity=90); 
    -	font-family:helvetica;
    -		box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -.selected {
    -	color:rgb(189,11,11);
    -}
    -
    -
    -/*  these styles are what jsPlumb attaches to the various elements it creates.
    -._jsPlumb_connector {
    -border:1px solid blue;
    -}
    -._jsPlumb_endpoint {
    -border:1px solid green;
    -}
    -._jsPlumb_overlay {
    -border:1px solid yellow;
    -}
    -*/
    -
    -
    -.section li {
    -list-style-type:none;
    -}
    -
    -li.bullet {
    -	list-style-type:circle;
    -	}
    -
    -
    -.section {
    -padding-left:2em;
    -font-size:10pt;
    -}
    -.index.section {
    -padding-left:0;
    -}
    -
    -.index {
    -width:20em;
    -font-size:9pt;
    -float:left;
    -background-color:#e4eeee;
    -overflow:auto;
    -padding-left:0.5em;
    -}
    -
    -.index li {
    -list-style-type:none;
    -}
    -
    -.index ul, .index h3 {
    -padding-left:1.5em;
    -
    -}
    -
    -#demo {
    -width:80%;
    -height:50em;
    -margin-left:3em;
    -margin-top:3em;
    -position:relative;
    -}
    -
    -.sub {
    -margin-left:1em;
    -}
    -
    -.table td {
    -	text-align:center;
    -}
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.6/usage.html b/demo/doc/archive/1.3.6/usage.html
    deleted file mode 100644
    index 23b517b16..000000000
    --- a/demo/doc/archive/1.3.6/usage.html
    +++ /dev/null
    @@ -1,10 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.6 - documentation</title>		
    -	</head>
    -	
    -	<frameset cols="250,*">
    -  		<frame src="index.html" />
    -  		<frame src="content.html" name="contentFrame" />
    -	</frameset>	
    -</html>
    diff --git a/demo/doc/archive/1.3.7/content.html b/demo/doc/archive/1.3.7/content.html
    deleted file mode 100644
    index 8737f64f6..000000000
    --- a/demo/doc/archive/1.3.7/content.html
    +++ /dev/null
    @@ -1,3014 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.8 - documentation</title>
    -		<link rel="stylesheet" href="/mp.css">
    -		<link rel="stylesheet" href="jsPlumbDoc.css">
    -	</head>
    -	<body>
    -
    -<div class="menu">
    -	<a href="http://jsplumb.org" class="mplink" target="_top">view demo</a>
    -	&nbsp;|&nbsp;<a href="mailto:simon.porritt@gmail.com" class="mplink">contact me</a>
    -	&nbsp;|&nbsp;<a href="http://jsplumb.tumblr.com" class="mplink">jsPlumb on Tumblr</a>	
    -</div>
    -
    -<!--
    -GWT
    -
    -http://fishfilosophy.blogspot.com/2011/12/how-to-connect-gwt-widgets-using.html?spref=tw
    -
    --->
    -
    -<div class="content">
    -		<div class="section">
    -			<h3><a id="summary">Summary</a></h3>
    -			jsPlumb allows you to connect elements on the screen using SVG, Canvas or VML, depending on the capabilities of the browser.
    -			<p>
    -				It can be used with jQuery, MooTools or YUI3 (or another library of your choice if you feel like implementing an adapter for it).
    -			</p>  
    -
    -			<h5>Browser Compatibility</h5>
    -			<p>jsPlumb 1.3.8 has been tested on the following browsers:
    -				<ul>
    -					<li class="bullet">IE 6 on Windows XP</li>
    -					<li class="bullet">IE 7 on Windows XP</li>
    -					<li class="bullet">IE 8 on Windows XP</li>
    -					<li class="bullet">Firefox 3.5.8 on Windows XP</li>
    -					
    -					<li class="bullet">IE 9 on Windows 7</li>
    -					<li class="bullet">Chrome 12 on Windows 7</li>
    -					<li class="bullet">Firefox 3.5.8 on Windows 7</li>
    -					
    -					<li class="bullet">Firefox 3.6.3 on Ubuntu 10.04</li>
    -					<li class="bullet">Chrome on Ubuntu 11.04</li>
    -					
    -					<li class="bullet">Safari 4 on Mac Leopard</li>
    -					<li class="bullet">Chrome on Mac Leopard</li>					
    -					<li class="bullet">Safari 4 on Windows Vista</li>
    -					<li class="bullet">Safari 5.0.5 on Windows 7</li>
    -					
    -					<li class="bullet">Opera 10.54 on Windows XP</li>
    -				</ul>
    -			</p>
    -		</div>
    -		
    -		<div class="section">		
    -			<h3><a id="changes">Changes between 1.3.8 and 1.3.6</a></h3>
    -			<h5>Backwards Compatibility</h5>
    -			There are no backwards compatibility issues between 1.3.8 and 1.3.6.
    -			<h5>New Features</h5>
    -			The following new features have been added:
    -			<ul>				
    -				<li class="bullet">new method <strong>jsPlumb.select</strong> provides a fluid interface to work with lists of Connections.  This is similar to the existing getConnections method, but the return value of jsPlumb.select supports all of the setter/getter operations that are available on Connection, executing that method on each Connection in the returned list. The method is also chainable. So you can now do things like this:
    -					<div class="code">
    -<pre>
    -jsPlumb.select({source:"d1"}).setHover(true).showOverlays();
    -</pre>
    -</div>
    -				Read more about this method <a href="#selectConnections">here</a>	
    -				<br/><br/>				
    -				</li>
    -				<li class="bullet"><strong>makeTarget</strong> now supports a beforeDrop interceptor</li>
    -				<li class="bullet"><strong>getOverlays</strong> method added to Connection and Endpoint</li>
    -				<li class="bullet"><strong>showOverlays</strong>/<strong>hideOverlays</strong> methods added to Connection and Endpoint</li>
    -				<li class="bullet"><strong>getConnections</strong> now support an optional second argument that returns results as a flat array instead of an object with scopes as keys.  But if you're going to use this you might want to just use <strong>select</strong> instead...</li>
    -				<li class="bullet">getConnections now supports a value of '*' for any of source, target or scope - a wildcard, meaning "any value".</li>
    -				<li class="bullet">new methods <strong>setId</strong> and <strong>setIdChanged</strong> let you either tell jsPlumb to change an element's id, or that an element's id has changed.  You should use these methods if you need to change element ids as jsPlumb uses element id internally.</li>
    -			</ul>
    -			<h5>Issues Resolved</h5>
    -			<ul>
    -				<li class="bullet">
    -					<strong>207</strong> - pass original event back in connection detached callback
    -				</li>
    -				<li class="bullet">
    -					<strong>209</strong> - endpoint drag not creating connection after setEnabled(false) and then setEnabled(true)
    -				</li>
    -				
    -				<li class="bullet">
    -					<strong>210</strong> - overlays not being hidden when setVisible(false) called
    -				</li>
    -				
    -				<li class="bullet">
    -					<strong>211</strong> - IE8 JS error with overlays and continuous anchors
    -				</li>
    -
    -				<li class="bullet">
    -					<strong>212</strong> - change the id of an element
    -				</li>
    -				<li class="bullet">
    -					<strong>214</strong> - beforeDetach gives wrong targetId when being reattached
    -				</li>
    -			</ul>
    -
    -
    -			
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="init">Setup</a></h3>
    -
    -            <h4><a id="imports">Required Imports</a></h4>
    -			<h4>jQuery</h4>
    -			    <ul>
    -				    <li class="bullet">jQuery 1.3.x or higher.</li>
    -				    <li class="bullet">jQuery UI 1.7.x or 1.8.x (if you wish to support drag and drop).</li>
    -			    </ul>
    -			    <div class="code">
    -<pre>&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/jquery-ui.min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/jquery.jsPlumb-1.3.8-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			<h4>MooTools</h4>
    -				<ul>
    -					<li class="bullet">MooTools Core 1.2.4 or higher. jsPlumb has been tested with 1.2.4 and 1.3.3.</li>
    -					<li class="bullet">Drag.Move from MooTools More 1.2.4.4 or higher (if you wish to support drag and drop).</li>
    -				</ul>
    -			<div class="code">
    -			<pre>&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/mootools/1.3.3/mootools-yui-compressed.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO_MOO_TOOLS_MORE_1_3_2_1"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/mootools.jsPlumb-1.3.8-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			<h4>YUI3</h4>
    -			<ul>
    -				<li class="bullet">YUI 3.3.x.  jsPlumb has been tested on 3.3.0 only; it is possible other 3.x.x versions will work.</li>
    -			</ul>
    -			<div class="code">
    -			<pre>&lt;script type="text/javascript" src="http://yui.yahooapis.com/3.3.0/build/simpleyui/simpleyui-min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/yui.jsPlumb-1.3.8-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			
    -			<h4><a id="renderMode">Render Mode</a></h4>
    -			jsPlumb can use SVG, HTML5 Canvas elements or VML to render the various objects it adds to the display.  Most modern browsers support
    -			Canvas and SVG; IE&lt;9 browsers support neither.
    -			<p>
    -				By default, jsPlumb attempts to use <strong>jsPlumb.SVG</strong> as the render mode (prior to 1.3.4, the default renderer was Canvas), falling back to <strong>jsPlumb.VML</strong> if the 
    -				browser is IE&lt;9.  You can change the render mode by making this call:  
    -			</p>
    -			<div class="code">
    -<pre>jsPlumb.setRenderMode(jsPlumb.CANVAS);</pre>			
    -			</div>
    -			if, for some reason, you set the render mode to be <strong>jsPlumb.VML</strong> but you're in any browser other than IE&lt;9, jsPlumb
    -			will use SVG.
    -			<p>
    -			The <strong>jsPlumb.setRenderMode</strong> method returns you the render mode that jsPlumb actually ended up setting.  Valid values for setRenderMode are:
    -			</p>
    -			<ul>
    -				<li class="bullet">jsPlumb.CANVAS</li>
    -				<li class="bullet">jsPlumb.SVG</li>
    -				<li class="bullet">jsPlumb.VML</li>
    -			</ul>			
    -			<h5>Performance Considerations</h5>
    -			<p>
    -			SVG uses less memory than Canvas and has support for mouse events built in, so I recommend using
    -			SVG unless you have a really good reason for wanting Canvas.
    -			</p>
    -			
    -			<h4><a id="initializing">Initializing jsPlumb</a></h4>
    -			You should not start making calls to jsPlumb until the DOM has been initialized - perhaps no surprises there.  With YUI, though, the asynchronous
    -			nature of the script loading process means that you are not guaranteed that jsPlumb is ready as soon as the DOM is.  To handle this,
    -			you should bind to the "ready" event on jsPlumb (or the instance of jsPlumb you are working with):
    -			<div class="code">
    -			<pre>jsPlumb.bind("ready", function() {
    -...	
    -		
    -// your jsPlumb related init code goes here
    -
    -...
    -
    -});						
    -			</pre>
    -			</div>
    -			There's a helper method that can save you approximately 9 (count them!) characters:
    -			<div class="code">
    -			<pre>jsPlumb.ready(function() {
    -...	
    -		
    -// your jsPlumb related init code goes here
    -
    -...
    -
    -});						
    -			</pre>
    -			</div>
    -			<p>
    -			For jQuery and MooTools you do not actually need to do this; by the time the DOM ready event is fired in each of those libraries you can be sure 
    -			all the JS we need has been loaded.  But in terms of explicitly calling out that you are waiting for jsPlumb, it seems like a good practice to bind to the "ready" event.
    -			</p>
    -			<p>
    -			If you bind to the "ready" event after jsPlumb has already been initialized, your callback will be executed immediately.
    -			</p>			
    -			<h4><a id="unload">Unloading jsPlumb</a></h4>
    -			jsPlumb offers a method you can call when your page is unloading.  You should do this to insure
    -			against memory leaks.  You configure it like this:
    -				<div class="code">
    -<pre>
    -&lt;body onunload="jsPlumb.unload();"&gt;
    -
    -...
    -
    -&lt;/body&gt;
    -</pre>
    -				</div>
    -<h4><a id="multipleInstances">Multiple jsPlumb instances</a></h4>
    -			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 <strong>getInstance</strong> method, for example:
    -<div class="code">
    -<pre>
    -var firstInstance = jsPlumb.getInstance();
    -</pre>
    -</div>
    -The variable 'firstInstance' can now be treated exactly as you would treat the 'jsPlumb' variable - you can set
    -defaults, call the connect method, whatever:
    -<div class="code">
    -<pre>
    -firstInstance.Defaults.Connector = [ "Bezier", { curviness: 150 } ];
    -firstInstance.Defaults.Anchors = [ "TopCenter", "BottomCenter" ];
    -
    -firstInstance.connect({source:"element1", target:"element2", scope:"someScope" });
    -</pre>
    -</div>
    -
    -<strong>getInstance</strong> optionally takes an object that provides the defaults:
    -<div class="code">
    -<pre>
    -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" });
    -</pre>
    -</div>
    -
    -		<h4><a id="zIndex">Z-index Considerations</a></h4>
    -		<p>
    -			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. 
    -		</p>  
    -		<p>
    -			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).  			
    -		</p>
    -		<p>
    -			To help you organise z-indices correctly, jsPlumb adds a CSS class to each type of
    -			element it adds. They are as follows:			
    -		</p>
    -		<table style="color:black;width:70%;font-size:90%;">
    -			<tr><td><strong>type of element</strong></td><td><strong>class added</strong></td></tr>
    -			<tr><td>Endpoint</td><td>_jsPlumb_endpoint</td></tr>
    -			<tr><td>Connector</td><td>_jsPlumb_connector</td></tr>
    -			<tr><td>Overlay</td><td>_jsPlumb_overlay</td></tr>
    -		</table>
    -
    -		<h4><a id="containerDefault">Where does jsPlumb add elements?</a></h4>
    -		<p>
    -			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.
    -		</p>
    -		<p>
    -			Prior to 1.3.0, jsPlumb added everything to the end of the <em>body</em> element. This
    -			has the advantage of being the most flexible arrangement in terms of supporting what
    -			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!   
    -		</p>
    -		<p>
    -			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 (<strong>not</strong> 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.
    -		</p>
    -		
    -		<p>
    -			In general, the default behaviour means that you cannot do things like this:
    -		</p>
    -		<div class="code">
    -<pre>
    -&lt;body&gt;
    -	&lt;div id="container0"&gt;
    -		&lt;div id="node0"&gt;&lt;/div&gt;
    -	&lt;/div&gt;
    -	&lt;div id="container1"&gt;
    -		&lt;div id="node1"&gt;&lt;/div&gt;
    -	&lt;/div&gt;
    -&lt;/body&gt;
    -
    -&lt;script type="text/javascript"&gt;
    -
    -	var e0 = jsPlumb.addEndpoint("node0"),
    -	e1 = jsPlumb.addEndpoint("node1");
    -
    -	jsPlumb.connect({ source:e0, target:e1 });
    -&lt;/script&gt;
    -</pre>		
    -		</div>
    -<p>
    -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):  
    -</p>
    -		<div class="code">
    -<pre>
    -&lt;body&gt;
    -	&lt;div id="container0"&gt;
    -	&lt;/div&gt;
    -	&lt;div id="container1"&gt;
    -	&lt;/div&gt;
    -&lt;/body&gt;
    -
    -&lt;script type="text/javascript"&gt;
    -
    -	var e0 = jsPlumb.addEndpoint("container0"),
    -	e1 = jsPlumb.addEndpoint("container1");
    -
    -	jsPlumb.connect({ source:e0, target:e1 });
    -&lt;/script&gt;
    -</pre>		
    -		</div>
    -	now 'e0' and 'e1' have the same parent - the <em>body</em> element. 
    -	
    -	<p>Remember that you can use the <strong>anchor</strong> parameter to an <strong>addEndpoint</strong>
    -	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. 
    -	</p>									
    -	<p><strong>Note regarding the drawEndpoints option on jsPlumb.connect</strong>: 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). 
    -	</p>
    -	
    -	<h5>Overriding the default behaviour</h5>
    -	<p>
    -From version 1.3.3, the <strong>container</strong> 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 <strong>jsPlumb.Defaults.Container</strong> property, or you can set a <strong>container</strong> parameter on calls to <strong>addEndpoint</strong> or <strong>connect</strong> (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:
    -	</p>
    -		
    -Set a container to use as the default container, using a jQuery selector (you can supply MooTools/YUI selectors if that's the library you're using), and then add an Endpoint.  The canvas (or SVG/VML element) created will be a child of the document body:
    -		<div class="code">
    -<pre>
    -jsPlumb.Defaults.Container = $("body");
    -...
    -jsPlumb.addEndpoint(someDiv, { endpoint options });
    -</pre>		
    -		</div>
    -		
    -			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":
    -		<div class="code">
    -<pre>
    -jsPlumb.Defaults.Container = "containerId";
    -...
    -jsPlumb.connect({ source:someDiv, target:someOtherDiv });
    -</pre>		
    -		</div>
    -		
    -		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":
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({ source:someDiv, target:someOtherDiv, container:$("#containerId") });
    -</pre>		
    -		</div>
    -				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":
    -		<div class="code">
    -<pre>
    -jsPlumb.addEndpoint(someDiv, { ..., container:"containerId" });
    -</pre>		
    -		</div>
    -		
    -		</div>
    -				
    -		<div class="section">
    -			<h3><a id="jsPlumbBasics">Basic Concepts</a></h3>
    -	
    -			jsPlumb is all about connecting things together, so the core abstraction in jsPlumb is the <strong>Connection</strong> object, which is itself
    -			broken down into these four concepts:
    -
    -			<ul>
    -				<li><strong>Anchor</strong> - a location, relative to an element's origin, at which an Endpoint can exist. You do not create these yourself; you 
    -				supply hints to the various jsPlumb functions, which create them as needed.  They have no visual representation; they are a logical position only. 
    -				Anchors can be referenced by name, for the Anchors that jsPlumb ships with, or a four element array describing [ x, y, x orientation, y orientation ]. 
    -				See the <a href="#anchors">anchors</a> section for more detail.<br/><br/></li>
    -				<li><strong>Endpoint</strong> - the visual representation of one end of a Connection.  You can create and attach these to elements yourself, which you are required to do to support drag and drop, or have jsPlumb create them when creating a Connection programmatically using jsPlumb.connect(...).  You can also join two Endpoints programmatically, by passing them as arguments to jsPlumb.connect(...).<br/><br/></li>				
    -				<li><strong>Connector</strong> - the visual representation of the line connecting two elements in the page.  jsPlumb has four types of these 
    -				available as defaults - a Bezier curve, a straight line, 'flowchart' connectors and 'state machine connectors. You do not interact with Connectors; 
    -				you just specify definitions of them when you need to - see <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions</a> <br/><br/></li>
    -				<li><strong>Overlay</strong> - a UI component that is used to decorate a Connector, such as a Label, Arrow, etc.<br/><br/></li>
    -			</ul>
    -						
    -			<p>One Connection is made up of two Endpoints, a Connector, and zero or more Overlays working together to join two elements.</p>
    -			<p>jsPlumb's public API exposes only Connection and Endpoint, handling the creation and configuration of everything else internally.
    -			But you still need to be across the concepts encapsulated by Anchor, Connector and Overlay.
    -			</p>
    -			</div>
    -			
    -			<div class="section">
    -			<h3><a id="anchors">Anchors</a></h3>			
    -	
    -					An Anchor models the notion of where on an element a Connector should connect.  There are three main types of Anchors:
    -					<ul>
    -						<li class="bullet">'Static' anchors - these are fixed to some point on an element and do not move.  They can be specified using a string to
    -						identify one of the defaults that jsPlumb ships with, or an array describing the location (see below)</li>
    -						<li class="bullet">'Dynamic' anchors - these are lists of Static anchors from which jsPlumb picks the most appropriate one each time a Connection
    -						is painted.  The algorithm used to determine the most appropriate anchor picks the one that is closest to the center of the other element in
    -						the Connection. A future version of jsPlumb might support a pluggable algorithm to make this decision.</li>
    -						<li class="bullet">'Continuous' anchors - These anchors, introduced in 1.3.4, are not fixed to any specific loction; they are assigned to one of
    -						the four faces of an element depending on that element's orientation to the other element in the associated Connection.  Continuous anchors are
    -						slightly more computationally intensive than Static or Dynamic anchors because jsPlumb is required to calculate the position of every Connection
    -						during a paint cycle, rather than just Connections belonging to the element in motion.  These anchors were designed for use with the StateMachine
    -						connector that was also introduced in 1.3.4, but will work with any combination of Connector and Endpoint.</li> 
    -					</ul>  
    -					
    -			<h4><a id="staticAnchors">Static Anchors</a></h4>		
    -			jsPlumb has nine default anchor locations you can use to specify where the Connectors connect to elements: these are the four corners of an element,
    -			the center of the element, and the midpoint of each edge of the element:  
    -			<ul>
    -				<li class="bullet">TopCenter</li>
    -				<li class="bullet">TopRight</li>
    -				<li class="bullet">RightMiddle</li>
    -				<li class="bullet">BottomRight</li>
    -				<li class="bullet">BottomCenter</li>
    -				<li class="bullet">BottomLeft</li>
    -				<li class="bullet">LeftMiddle</li>
    -				<li class="bullet">TopLeft</li>
    -			</ul>
    -			
    -Each of these string representations is just a wrapper around the underlying array-based syntax  [x, y, dx, dy], where x and y are coordinates in the interval [0,1] specifying the position of the
    -anchor, and dx and dy are coordinates in the interval [-1, 1] specifying the orientation of the curve incident to the anchor. For example, [0, 0.5, -1, 0] defines a "LeftCenter" anchor with a connector curve that emanates leftward from the anchor. Similarly, [0.5, 0, 0, -1] defines a "CenterTop" anchor with a connector curve emanating upwards. 	
    -
    -
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:"BottomCenter", ... }); 
    -</pre>
    -</div>
    -is identical to:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:[ 0.5, 1, 0, 1 ], ... }); 
    -</pre>
    -</div>		
    -In addition to supplying the location and orientation of an anchor, you can optionally supply two more parameters that define an offset in pixels from the given location.  Here's the anchor specified above, but with a 50 pixel offset below the element in the y axis:	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:[ 0.5, 1, 0, 1, 0, 50 ], ... }); 
    -</pre>
    -</div>		
    -
    -		<h4><a id="dynamicAnchors">Dynamic Anchors</a></h4>
    -	These are Anchors that can be positioned in one of a number of locations, choosing the one that is most appropriate each time something moves or is painted in the UI.
    -	<p>
    -	There is no special syntax to creating a DynamicAnchor; you just provide an array of individual Static Anchor specifications, eg:
    -	</p>
    -<div class="code">
    -<pre>
    -var dynamicAnchors = [ [ 0.2, 0, 0, -1 ],  [ 1, 0.2, 1, 0 ], 
    -			   [ 0.8, 1, 0, 1 ], [ 0, 0.8, -1, 0 ] ];
    -
    -jsPlumb.connect({...., anchor:dynamicAnchors, ... }); 			   
    -</pre>
    -</div>
    -<h4>	Default Dynamic Anchor</h4>
    -jsPlumb provides a dynamic anchor called "AutoDefault" that chooses from TopCenter, RightMiddle, BottomCenter and LeftMiddle:
    -<div class="code">
    -<pre>jsPlumb.connect({...., anchor:"AutoDefault", ... });</pre>
    -</div>
    -
    -<h4>	Location Selection</h4>
    -The initial implementation of the algorithm that decides which location to choose just calculates which location is closest to the center of the 
    -other element in the connection.  It is possible that future versions of jsPlumb could support more sophisticated choice algorithms, if the need arose.
    -<h4>Draggable Connections</h4>
    -Dynamic Anchors and Draggable Connections can interoperate: jsPlumb locks the position of a dynamic anchor when you start to drag a connection from it,
    -and unlocks it once the connection is either established or abandoned. At that point you may see the position of the dynamic anchor change, as jsPlumb
    -optimises the connection.  <p>You can see this behaviour in the <a href="../html/jquery/draggableConnectorsDemo.html" target="_blank">draggable connections</a> demonstration, when
    -you drag a connection from the blue endpoint on window 1 to the blue endpoint on window 3 - the connection is established and then window 1's blue
    -endpoint jumps down to a location that is closer to window 3.</p>
    -
    -
    -	<h4><a id="continuousAnchors">Continuous Anchors</a></h4>
    -	As discussed above, these are anchors whose positions are calculated by jsPlumb according to the orientation between elements in a Connection, and also how
    -	many other Continuous anchors happen to be sharing the element.  You specify that you want to use Continuous anchors using the string syntax you would use
    -	to specify one of the default Static Anchors, for example:
    -	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:someDiv,
    -	target:someOtherDiv,
    -	anchor:"Continuous"
    -});
    -</pre>
    -</div>
    -
    -Note in this example I specified only "anchor", rather than "anchors" - jsPlumb will use the same spec for both anchors.  But I could have said this:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:someDiv,
    -	target:someOtherDiv,
    -	anchors:["BottomCenter", "Continuous"]
    -});
    -</pre>
    -</div>	 	  
    -..which would have resulted in the source element having a Static Anchor at BottomCenter.  In practise, though, it seems the Continuous Anchors work best if both 
    -elements in a Connection are using them.
    -<p>Note also that Continuous Anchors can be specified on <strong>addEndpoint</strong> calls:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint(someDiv, {
    -	anchor:"Continuous",
    -	paintStyle:{ fillStyle:"red" }
    -});
    -</pre>
    -</div>	 	
    -...and in <strong>makeSource</strong>/<strong>makeTarget</strong>:
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource(someDiv, {
    -	anchor:"Continuous",
    -	paintStyle:{ fillStyle:"red" }
    -});
    -
    -jsPlumb.makeTarget(someDiv, {
    -	anchor:"Continuous",
    -	paintStyle:{ fillStyle:"red" }
    -});
    -</pre>
    -</div>	 
    -... and in the jsPlumb defaults:
    -<div class="code">
    -<pre>
    -jsPlumb.Defaults.Anchor = "Continuous";
    -</pre>
    -</div>  
    -					</div>		
    -												
    -<div class="section">
    -		<h4><a id="definitions">Connector, Endpoint &amp; Overlay Definitions</a></h4>
    -Before we discuss Connector, Endpoints and Overlays, a quick word on definitions:  whenever you need to define a Connector, Endpoint or Overlay, you must use a "definition" of it, rather than constructing one directly.  This definition can be either a string that nominates the artifact you want to create:
    -		<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:"Rectangle"
    -});
    -</pre>
    -		</div>
    -		or an array consisting of both the artifact's name and the arguments you want to pass to its constructor:
    -		<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Rectangle", { cssClass:"myEndpoint", width:30, height:10 } ]
    -});
    -</pre>
    -		</div>
    -There is also a three-argument method that allows you to specify two sets of parameters, which jsPlumb will merge together for you. The idea behind this is that you will often want to define common characteristics somewhere and reuse them across a bunch of different calls:
    -		<div class="code">
    -<pre>var common = {
    -	cssClass	:	"myCssClass",
    -	hoverClass	:	"myHoverClass"
    -};
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Rectangle", { width:30, height:10 }, common ]
    -});
    -</pre>
    -		</div>
    -	This syntax is supported for all Endpoint, Connector and Overlay definitions.  Here's an example using definitions for all three:	
    -		<div class="code">
    -<pre>var common = {
    -	cssClass:"myCssClass"
    -};
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Dot", { radius:5, hoverClass:"myEndpointHover" }, common ],
    -	connector:[ "Bezier", { curviness:100 }, common ],
    -	overlays: [
    -		[ "Arrow", { foldback:0.2 }, common ],
    -		[ "Label", { cssClass:"labelClass" } ]	
    -	]
    -});
    -</pre>
    -		</div>		
    -The allowed constructor parameters are different for each artifact you create, but every artifact takes a single JS object as argument, with the parameters as [key,value] pairs in that object.  See the relevant sections on <a href="#connectors">Connectors</a>, <a href="#endpoints">Endpoints</a> and <a href="#overlays">Overlays</a> for information on supported parameters.</a>
    -				
    -	</div>
    -
    -		<div class="section">
    -			<h3><a id="connectors">Connectors</a></h3>
    -			<p>Connectors are the lines that actually join elements of the UI.  jsPlumb has four connector implementations - a straight line, a Bezier curve, "flowchart", and "state machine".  The default connector is the Bezier curve.</p>
    -			<p>You optionally specify a Connector by setting the 'connector' property on a call to jsPlumb.connect, jsPlumb.addEndpoint(s), jsPlumb.makeSource or jsPlumb.makeTarget. If you do not supply a value for 'connector', the default will be used (see <a href="#defaults">defaults</a>).</p>
    -			<p>You specify Connectors using the syntax described above in <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions.</a> Allowed constructor values for each Connector type are described below:</p>		
    -			<h4><a id="bezierConnector">Bezier Connector</a></h4>
    -			<p>The Bezier Connector provides a cubic Bezier path between the two Endpoints. It supports a single constructor argument:</p>
    -			<p>
    -			<strong>curviness</strong> - Optional; defaults to 150. This defines the distance in pixels that
    -			the Bezier's control points are situated from the anchor points.  This does not mean that your
    -			connector will pass through a point at this distance from your curve.  It is a hint to how you want the
    -			curve to travel. Rather than discuss Bezier curves at length here, because they are a complex topic,
    -			refer instead to <a href="http://en.wikipedia.org/wiki/B%C3%A9zier_curve" target="_blank">Wikipedia.</a>
    -			</p>
    -			<h4><a id="straightConnector">Straight Connector</a></h4>
    -			<p>The Straight Connector draws a straight line between the two endpoints. No constructor arguments are supported; use the <strong>endpointStyle</strong> and/or <strong>cssClass</strong>/<strong>connectorClass</strong> to a <strong>connect</strong>, <strong>addEndpoint</strong>, <strong>makeSource</strong> or <strong>makeTarget</strong> call to control the appearance of one of these Connectors.  
    -			</p>
    -			<h4><a id="flowchartConnector">Flowchart Connector</a></h4>
    -			<p>This type of Connector draws a connection that consists of a series of vertical or horizontal segments - the classic flowchart look. A single constructor argument is supported:</p>
    -			<strong>stub</strong> - this is the minimum length, in pixels, of the initial stub that emanates from an Endpoint.  This parameter is optional, and defaults to 30 pixels.
    -			<p>This Connector supports Connection that start and end on the same element.</p>
    -			<h4><a id="stateMachineConnector">State Machine Connector</a></h4>
    -			<p>This Connector draws slightly curved lines (they are actually quadratic Bezier curves), similar to the State Machine connectors you may have seen in software like GraphViz.  Connections in which some element is both the source and the target (so-called 'loopback') are supported by these Connectors (as they are with Flowchart Connectors); in this case you get a circle. Supported parameters are:
    -			</p>
    -			<p>
    -			<strong>margin</strong> - Optional; defaults to 5.  Defines the distance from the element that the connector begins/ends.
    -			</p>
    -			<p>
    -			<strong>curviness</strong> - Optional, defaults to 10.  This has a similar effect to the curviness parameter on Bezier curves.
    -			</p>
    -			<p>
    -			<strong>proximityLimit</strong> - Optional, defaults to 80.  The minimum distance between the two ends of the Connector before it paints itself as 
    -			a straight line rather than a quadratic Bezier curve.
    -			</p>    
    -			
    -		</div>
    -		
    -			<div class="section">
    -			<h3><a id="endpoints">Endpoint Types</a></h3>
    -			<p>An Endpoint is the UI component that marks the location of an Anchor, ie. the place where a Connector joins an
    -			element.  jsPlumb comes with four Endpoint implementations - <strong>Dot</strong>, <strong>Rectangle</strong>, <strong>Blank</strong> and <strong>Image</strong>. You optionally specify Endpoint properties using the
    -			<strong>endpoint</strong> parameter in a call to jsPlumb.connect, jsPlumb.addEndpoint, jsPlumb.makeSource or jsPlumb.makeTarget.
    -			</p>  
    -				<p>As with Connectors and Overlays, you specify Endpoints using the syntax described above in <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions.</a></p>
    -			
    -			The four available Endpoint types, and their constructor arguments, are as follows:
    -					<h4><a id="dotEndpoint">Dot Endpoint</a></h4>
    -					This Endpoint draw a dot on the screen. It supports three constructor parameters:
    -					<p>
    -						<strong>radius</strong> - Optional; defaults to 10 pixels. Defines the radius of the dot.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="rectangleEndpoint">Rectangle Endpoint</a></h4>
    -						Draws a rectangle. Supported constructor parameters are:
    -					<p>
    -						<strong>width</strong> - Optional; defaults to 20 pixels. Defines the width of the rectangle.
    -					</p>
    -					<p>
    -						<strong>height</strong> - Optional; defaults to 20 pixels. Defines the height of the rectangle.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="imageEndpoint">Image Endpoint</a></h4>
    -						Draws an image from a given URL.  This Endpoint supports three constructor parameters:  
    -					<p>
    -						<strong>src</strong> - Required.  Specifies the URL of the image to use.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="blankEndpoint">Blank Endpoint</a></h4>
    -						Does not draw anything visible to the user.  As discussed previously, this Endpoint is probably not what you want if you need your users to be able to drag existing Connections - for that, use a Rectangle or Dot Endpoint ans assign to it a CSS class that causes it to be transparent.
    -																			
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="overlays">Overlay Types</a></h3>
    -			<p>
    -			Overlays are UI elements that are painted onto connections, such as labels or arrows.
    -			jsPlumb comes with four defaults:
    -			</p>
    -			<ul>
    -				<li><strong>Arrow</strong> - a configurable arrow that is painted at some point along the connector.  You can control the length and width of the Arrow, the 'foldback' point - a point the tail points fold back into, and the direction (allowed values are 1 and -1; 1 is the default and means point in the direction of the connection)</li>
    -				<li><strong>Label</strong> - a configurable label that is painted at some point along the connector.</li>
    -				<li><strong>PlainArrow</strong> - an Arrow shaped as a triangle, with no foldback.</li>
    -				<li><strong>Diamond</strong> - as the name suggests, a diamond.</li>
    -			</ul> 
    -			The last two are actually just configured instances of the generic Arrow overlay (see examples). 
    -			
    -			<p>All Overlays support these two methods for getting/setting their location (location is a number between 0 and 1 inclusive, indicating some point along the path
    -			inscribed by the associated Connector):</p>
    -			<ul>
    -				<li class="bullet"><strong>getLocation</strong> - returns the current location</li>
    -				<li class="bullet"><strong>setLocation</strong> - sets the current location; valid values are decimals in the range 0-1 inclusive</li> 
    -			</ul>
    -			
    -			<p>You can specify one or more overlays when making a call to jsPlumb.connect, jsPlumb.addEndpoint or jsPlumb.makeSource (but not jsPlumb.makeTarget; overlays
    -			are always derived from what the source of a Connection defines)  The three cases are discussed below:</p>
    -			1. Specifying one or more overlays on a jsPlumb.connect call. In this example we'll create an Arrow with the default options for an Arrow, and
    -			a label with the text "foo":
    -			<div class="code">
    -			<pre>jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});			
    -</pre>
    -			</div>
    -			This connection will have an arrow located halfway along it, and the label "foo" one quarter of the way along.  Notice the <strong>id</strong>
    -			parameter; it can be used later if you wish to remove the Overlay or change its visibility (see below).<br/><br/> 
    -			
    -			2. Specifying one or more overlays on a jsPlumb.addEndpoint call.  Note in this example that we use the parameter 'connectorOverlays' and not
    -			'overlays' as in the last example.  This is because 'overlays' would refer to Endpoint Overlays:
    -			<div class="code">
    -			<pre>jsPlumb.addEndpoint("someDiv", {
    -	...
    -	connectorOverlays:[ 
    -		[ "Arrow", { width:10, length:30, location:1, id:"arrow" } ], 
    -		[ "Label", { label:"foo", id:"label" } ]
    -	],
    -	...
    -});</pre>
    -			</div>	
    -			This connection will have a 10x30 Arrow located right at the head of the connection, and the label "foo" located at the halfway point. Again,
    -			note that <strong>id</strong> parameter passed in to each Overlay spec.<br/><br/>
    -			
    -			3. Specifying one or more overlays on a jsPlumb.makeSource call.  Note in this example that we again use the parameter 'connectorOverlays' and not
    -			'overlays'.  The 'endpoint' parameter to jsPlumb.makeSource supports everything you might pass to the second argument of a jsPlumb.addEndpoint call:
    -			<div class="code">
    -			<pre>jsPlumb.makeSource("someDiv", {
    -	...
    -	endpoint:{
    -		connectorOverlays:[ 
    -			[ "Arrow", { width:10, length:30, location:1, id:"arrow" } ], 
    -			[ "Label", { label:"foo", id:"label" } ]
    -		]
    -	}
    -	...
    -});</pre>
    -			</div>	
    -			This connection will have a 10x30 Arrow located right at the head of the connection, and the label "foo" located at the halfway point. Again,
    -			note that <strong>id</strong> parameter passed in to each Overlay spec.<br/><br/>
    -			
    -			 	
    -					<h4><a id="arrowOverlay">Arrow Overlay</a></h4>
    -					<p>This overlay draws an arrow, using four points: the head and two tail points, and a 'foldback' point, which permits the tail
    -					of the arrow to be indented. Available constructor arguments for this Overlay are </p>
    -					<ul>
    -					<li><strong>width</strong> - width of the tail of the arrow</li>
    -					<li><strong>length</strong> - distance from the tail of the arrow to the head</li>
    -					<li><strong>location</strong> - where, as a proportional value from 0 to 1 inclusive, the Arrow should appear on the Connector</li>
    -					<li><strong>direction</strong> - which way to point. Allowed values are 1 (the default, meaning forwards) and -1, meaning backwards </li>
    -					<li><strong>foldback</strong> - how far along the axis of the arrow the tail points foldback in to. Default is 0.623.</li>
    -					<li><strong>paintStyle</strong> - a style object in the form used for paintStyle values for Endpoints and Connectors</li>
    -					</ul>
    -					<h4><a id="plainArrowOverlay">PlainArrow Overlay</a></h4>
    -					<p>This is just a specialized instance of Arrow in which jsPlumb hardcodes 'foldback' to 1, meaning the tail of the Arrow is a flat edge.  All of the constructor parameters from Arrow apply for PlainArrow.</p>
    -					<h4><a id="diamondOverlay">Diamond Overlay</a></h4>
    -					<p>This is a specialized instance of Arrow in which jsPlumb hardcodes 'foldback' to 2, meaning the Arrow turns into a Diamond.  All of the constructor parameters from Arrow apply for PlainArrow.</p>
    -					<h4><a id="labelOverlay">Label Overlay</a></h4>
    -					This provides a text label to decorate Connectors with.  The available constructor arguments are:
    -					<ul>
    -						<li><strong>label</strong> - The text to display.  You can provide a function here instead of plain text: it is passed the Connection as an argument, and it should return a String.</li>
    -						<li><strong>cssClass</strong> - Optional css class to use for the Label.  This is now preferred over using the 'labelStyle' parameter.</li>
    -						<li><strong>labelStyle</strong> - (deprecated, use cssClass instead) Optional arguments for the label's appearance.  Valid entries in this JS object are:
    -							<ul>
    -								<li><em>font</em> - a font string in a format suitable for the Canvas element</li>
    -								<li><em>fillStyle</em> - the color to fill the label's background with. Optional.</li>
    -								<li><em>color</em> - the color of the label's text. Optional.</li>
    -								<li><em>padding</em> - optional padding for the label. This is expressed as a proportion of the width of the label, not in pixels or ems.</li>
    -								<li><em>borderWidth</em> - optional width in pixels for the label's border. Defaults to 0.</li>
    -								<li><em>borderStyle</em> - optional. The color to paint the border, if there is one.</li>
    -								
    -							</ul>
    -						
    -						</li>
    -						<li><strong>location</strong> - As for Arrow Overlay.  Where, proportionally from 0 to 1 inclusive, the label should appear.</li>
    -					</ul>
    -
    -					The Label overlay offers two methods - getLabel and setLabel - for accessing/manipulating its content dynamically:
    -<div class="code">
    -<pre>
    -var c = jsPlumb.connect({
    -	source:"d1", 
    -	target:"d2", 
    -	overlays:[
    -		[ "Label", {label:"FOO", id:"label"}]
    -	]	
    -});					
    -
    -...
    -
    -var label = c.getOverlay("label");
    -console.log("Label is currently", label.getLabel());
    -label.setLabel("BAR");
    -console.log("Label is now", label.getLabel());
    -</pre>
    -</div>
    -In this example you can see that the Label Overlay is assigned an id of "label" in the connect call, and then retrieved using that id in the call to Connection's getOverlay method.
    -<p>Both Connections and Endpoints support Label Overlays, and because changing labels is quite a common operation, <strong>setLabel</strong> and <strong>getLabel</strong> methods have been added to these objects:
    -<div class="code">
    -<pre>
    -var conn = jsPlumb.connect({
    -	source:"d1", 
    -	target:"d2",
    -	label:"FOO"
    -});					
    -
    -...
    -
    -console.log("Label is currently", conn.getLabel());
    -conn.setLabel("BAR");
    -console.log("Label is now", conn.getLabel());
    -</pre>
    -</div>
    -These methods support passing in a Function instead of a String, and jsPlumb will create a label overlay for you if one does not yet exist when you call getLabel:
    -<div class="code">
    -<pre>
    -var conn = jsPlumb.connect({
    -	source:"d1", 
    -	target:"d2"
    -});					
    -
    -...
    -
    -conn.setLabel(function(c) {
    -	var s = new Date();
    -	return s.getTime() + "milliseconds have elapsed since 01/01/1970";
    -});
    -console.log("Label is now", conn.getLabel());
    -</pre>
    -</div>
    -					
    -					<h4><a id="hideshowOverlay">Hiding/Showing Overlays</a></h4>
    -					<p>You can control the visibility of Overlays using the <strong>setVisible</strong> method of Overlays themselves, or with
    -					<strong>showOverlay(id)</strong> or <strong>hideOverlay(id)</strong> on a Connection.</p>
    -					<p>Remember the <strong>id</strong> parameter that we specified in the examples above?  This can be used to retrieve the Overlay
    -					from a Connection:</p>
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -var overlay = connection.getOverlay("myLabel");
    -// now you can hide this Overlay:
    -overlay.setVisible(false);
    -// there are also hide/show methods:
    -overlay.show();
    -overlay.hide();
    -
    -</pre>					
    -					</div>
    -					<p>However, Connection and Endpoint also have two convenience methods you could use instead:
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -connection.hideOverlay("myLabel");
    -
    -// more time passes
    -
    -connection.showOverlay("myLabel");
    -
    -</pre>					
    -					</div>
    -					
    -		<h4><a id="hideshowOverlay">Removing Overlays</a></h4>
    -		<p>Connection and Endpoint also have a <strong>removeOverlay</strong> method, that does what you might expect:
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -connection.removeOverlay("myLabel");
    -
    -
    -</pre>					
    -					</div>							
    -	
    -		</div>
    -	
    -			
    -			<div class="section">
    -			<h3><a id="defaults">Defaults</a></h3>
    -			The easiest way to set a look and feel for your plumbing is to override the defaults that jsPlumb uses. If you
    -			do not do this you are forced to provide your overridden values on every call.  Every argument to the connect and addEndpoint methods has an
    -			associated default value in jsPlumb.<br/><br/>
    -
    -			The defaults that ship with jsPlumb are stored in <em>jsPlumb.Defaults</em>, which is a Javascript object.  Valid entries, and their initial values, are:
    -
    -			<div class="code">
    -<pre>
    -Anchor : "BottomCenter",
    -Anchors : [ null, null ],
    -Connector : "Bezier",
    -ConnectionOverlays : [ ],
    -DragOptions : { },
    -DropOptions : { },
    -Endpoint : "Dot",
    -EndpointOverlays : [ ],
    -Endpoints : [ null, null ],
    -EndpointStyle : { fillStyle : null },
    -EndpointStyles : [ null, null ],
    -EndpointHoverStyle : null,
    -EndpointHoverStyles : [ null, null ],
    -HoverPaintStyle : null,
    -LabelStyle : { color : "black" },
    -LogEnabled : false,
    -Overlays : [ ],
    -MaxConnections : 1,
    -MouseEventsEnabled : true,
    -PaintStyle : { lineWidth : 8, strokeStyle : "#456" },
    -RenderMode : "svg",
    -Scope : "_jsPlumb_DefaultScope"
    -</pre>
    -			</div>
    -			<p>
    -			Note that in EndpointStyle, the default fillStyle is 'null'.  This instructs jsPlumb to use the strokeStyle
    -from the attached connector to fill the endpoint.</p>
    -<p>Note also that you can specify either or both (or neither) of 'EndpointStyle' and 'EndpointStyles'.  This allows you to specify a different
    -end point for each end of a connection.  'Endpoint' and 'Endpoints' use the same concept.  jsPlumb will look first in the
    -individual endpoint/endpoint style arrays, and then fall back to the single default version.</p>
    -
    -			you can override these defaults by including this in a script somewhere:
    -			<div class="code">
    -<pre>
    -jsPlumb.Defaults.PaintStyle = {
    -	lineWidth:13,
    -	strokeStyle: 'rgba(200,0,0,100)'
    -}
    -
    -jsPlumb.Defaults.DragOptions = { cursor: "crosshair" };
    -
    -jsPlumb.Default.Endpoints = [ [ "Dot", 7 ], [ "Dot", 11 ] ];
    -
    -jsPlumb.Defaults.EndpointStyles = [{ fillStyle:"#225588" }, { fillStyle:"#558822" }];</pre>
    -			</div>
    -			after the jsPlumb script has been loaded of course!  Here we have specified the following default behaviour:
    -			<ul>
    -				<li>- connectors are 13 pixels wide and painted with a semi-transparent red line</li>
    -				<li>- when dragging an element the crosshair cursor is used</li>
    -				<li>- the source endpoint is a dot of radius 7; the target endpoint is a dot of radius 11</li>
    -				<li>- the source endpoint is blue; the target endpoint is green</li>
    -			</ul>
    -
    -			<h5>jsPlumb.importDefaults</h5>
    -From version 1.3.8 onwards there is a helper method called <strong>importDefaults</strong> that allows you to import a set of values with just one call to jsPlumb.  Here's the previous example using importDefaults:
    -			<div class="code">
    -<pre>
    -jsPlumb.importDefaults({
    -	PaintStyle : {
    -		lineWidth:13,
    -		strokeStyle: 'rgba(200,0,0,100)'
    -	},
    -	DragOptions : { cursor: "crosshair" },
    -	Endpoints : [ [ "Dot", 7 ], [ "Dot", 11 ] ],
    -	EndpointStyles : [{ fillStyle:"#225588" }, { fillStyle:"#558822" }]
    -});</pre>
    -			</div>
    -
    -		</div> 					
    -
    -<div class="section">
    -			<h3><a id="simpleConnections">Connections</a></h3>
    -			<h4><a id="simpleConnections">Programmatic Connections</a></h4>
    -The most simple connection you can make with jsPlumb looks like this:
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({source:"element1", target:"element2"});</pre>			
    -			</div>
    -
    -		In this example we have created a Connection from 'element1' to 'element2'.  Remember that a Connection in jsPlumb consists of two Endpoints,
    -		a Connector, and zero or more Overlays.  But this call to 'connect' supplied none of those things, so jsPlumb uses the default values wherever it needs to.
    -		In this case, default values have been used for the following: 
    -		<ul>
    -			<li>- The type and appearance of each Endpoint in the Connection. jsPlumb's default for this is the "Dot" endpoint, of radius 10, with fill color "#456".</li>
    -			<li>- The Anchors that define where the connection's Endpoints appear on each element. The jsPlumb default is "BottomCenter"</li>			
    -			<li>- Whether or not each Endpoint can be a source or target for new Connections. The default is false.</li>
    -			<li>- The type and appearance of the Connection's Connector. The default is a "Bezier" connector of line width 8, and color "#456".</li>
    -		</ul>
    -		<p>
    -		So this call will result in an 8px Bezier, colored "#456", from the bottom center of 'element1' to the bottom center of 'element2', and each
    -		Endpoint will be a 10px radius Dot Endpoint, colored "#456".
    -		</p>
    -		<p>
    -		Let's beef up this call a little and tell jsPlumb what sort of Endpoints we want, and where we want them:		
    -		</p>
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:"element1", 
    -	target:"element2",
    -	anchors:["RightMiddle", "LeftMiddle" ],
    -	endpoint:"Rectangle",
    -	endpointStyle:{ fillStyle: "yellow" }
    -});
    -</pre>			
    -			</div>
    -			
    -		<p>This is what we have told jsPlumb we want this time:</p>
    -		<ul>
    -			<li><strong>anchors</strong> - this array tells jsPlumb where the source and target Endpoints should be located on their parent elements. In this case,
    -			we use the shorthand syntax to name one of jsPlumb's default anchors; you can also specify custom locations (see <a href="#anchors">anchors</a>).
    -			Instead of <strong>anchors</strong> you can use <strong>anchor</strong>, if you want the source and target Endpoints to be located at the
    -			same place on their parent elements.			<br/>
    -			</li>
    -			<li>
    -				<strong>endpoint</strong>  - this tells jsPlumb to use the "Rectangle" Endpoint for both the source and target of the Connection.  As with anchors, 
    -				<strong>endpoint</strong> has a plural version that allows you to specify a different Endpoint for each end of the Connection.<br/>  
    -			</li>
    -			<li>
    -				<strong>endpointStyle</strong> - this is the definition of the appearance of the Endpoint you specified above.  Again, there is a
    -				plural equivalent of this that allows you to specify a different style for each end of the Connection. For more information about
    -				allowed values for this value, see <a href="#paintstyles">Connector, Endpoint & Overlay Styles</a>. 
    -			</li>
    -		</ul>		
    -		
    -		<h5>Reusing common settings between jsPlumb.connect calls</h5>
    -		<p>
    -			A fairly common situation you will find yourself in is wanting to create a bunch of Connections that have only minor differences
    -			between them.  To support that, <strong>jsPlumb.connect</strong> takes an optional second argument. For example:			 
    -		</p>
    -		<div class="code">
    -<pre>
    -var common = {
    -	anchors:[ "BottomCenter", "TopCenter" ],
    -	endpoints:["Dot", "Blank" ]
    -};
    -
    -jsPlumb.connect({ source:"someElement", target:"someOtherElement" }, common);
    -
    -jsPlumb.connect({ source:"aThirdElement", target:"yetAnotherElement" }, common);
    -
    -</pre>			
    -			</div>		
    -			
    -		<h5>Endpoints created by jsPlumb.connect</h5>
    -		<p>If you supply an element id or selector for either the source or target , jsPlumb.connect will automatically create an Endpoint on the 
    -		given element.  These automatically created Endpoints are not marked as drag source or targets, and cannot
    -		be interacted with.  For some situations this behaviour is perfectly fine, but for more interactive UIs you should set things up using the drag and drop
    -		method discussed below.</p>
    -		<p>Given that jsPlumb.connect creates its own Endpoints in some circumstances, in order to avoid leaving orphaned Endpoints around the place, if the Connection is subsequently
    -		deleted, these created Endpoints are deleted too.  Should you want to, you can override this behaviour by setting <strong>deleteEndpointsOnDetach</strong> to
    -		false in the connect call:</p>
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({ 
    -	source:"aThirdElement", 
    -	target:"yetAnotherElement",
    -	deleteEndpointsOnDetach:false 
    -});
    -</pre>
    -</div>								
    -					
    -		<h4><a id="draggableConnections">Connections using Drag and Drop</a></h4>
    -		To support drag and drop connections, you first need to set a few things up.  Every drag and drop connection needs at least a source
    -		Endpoint that the user can drag a connection from. Here's a simple example of how to create an Endpoint:
    -		<div class="code">
    -<pre>
    -var endpointOptions = { isSource:true };
    -var endpoint = jsPlumb.addEndpoint('elementId', endpointOptions);
    -</pre>		
    -</div>  	
    -	This Endpoint will act as a source for new Connections, and will use the jsPlumb defaults for its own appearance and that of any Connections that are drawn from it.							
    -	
    -		<h5>Tip: use the three-argument addEndpoint method for common data </h5>
    -		One thing that happens quite often is that you have an Endpoint whose appearance and behaviour is largely the
    -		same between usages on different elements, with just a few differences. 
    -		<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -	endpoint:"Rectangle",
    -	paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -	isSource:true,
    -	connectorStyle : { strokeStyle:"#666" },
    -	isTarget:true
    -};
    -</pre>		
    -</div>  		
    -		Notice there is no 'anchor' set.  Here we apply it to two elements, at a different location in each:
    -		<div class="code">
    -<pre>
    -jsPlumb.addEndpoint('element1', { anchor:"BottomCenter" }, exampleGreyEndpointOptions)); 
    -
    -jsPlumb.addEndpoint('element2', { anchor:"TopCenter" }, exampleGreyEndpointOptions));
    -</pre>		
    -</div>
    -
    -<p>
    -Now that you have a source Endpoint, you need to either create a target Endpoint on some element, or notify jsPlumb that you wish to make an
    -entire element a drop target.  Let's look at how to attach a target Endpoint first:
    -</p>
    -
    -		<div class="code">
    -<pre>var endpointOptions = { isTarget:true, endpoint:"Rectangle", paintStyle:{ fillStyle:"gray" } };
    -var endpoint = jsPlumb.addEndpoint("otherElementId", endpointOptions);
    -</pre>		
    -		</div>
    -		<p>
    -		This Endpoint, a gray rectangle, has declared that it can act as a drop target for Connections. 
    -</p>
    -<p>
    -jsPlumb also supports marking entire elements as drag sources or drag targets, using the <strong>makeSource</strong> and <strong>makeTarget</strong> methods.  This is a useful function for certain applications.  Each of these methods takes an Endpoint declaration which defines the Endpoint that jsPlumb will create after a Connection has been attached.  Let's take a look at makeTarget first:
    -</p>
    -<h4><a id="sourcesAndTargets">Elements as sources &amp; targets</a></h4>
    -jsPlumb also supports turning entire elements into Connection sources and targets, using the methods <strong>makeSource</strong> and <strong>makeTarget</strong>.  With these methods you mark an element as a source or target, and provide an Endpoint specification for jsPlumb to use when a Connection is established.  makeSource also gives you the ability to mark some child element as the place from which you wish to drag Connections, but still have the Connection on the main element after it has been established.
    -<p>These methods honour the jsPlumb defaults - if, for example you set up the default Anchors to be this:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.Defaults.Anchors = [ "LeftMiddle", "BottomRight" ];
    -</pre>
    -</div>
    -... and then used makeSource and makeTarget without specifying Anchor locations, jsPlumb would use "LeftMiddle" for the makeSource element and "BottomRight" for the makeTarget element.
    -<p>
    -A further thing to note about makeSource and makeTarget is that from 1.3.8 onwards any prior calls to one of these methods is honoured by subsequent calls to jsPlumb.connect. This helps when you're building a UI that uses this functionality at runtime but which loads some initial data, and you want the statically loaded data to have the same appearance and behaviour as dynamically created connections (obviously quite a common use case).  
    -<p>For example:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -	anchor:"Continuous",
    -	endpoint:["Rectangle", { width:40, height:20 }]
    -});
    -
    -...
    -
    -jsPlumb.connect({source:"el1", target:"el2"});
    -</pre>
    -</div>
    -In this example, the source of the connection will be a Rectangle of size 40x20, having a Continuous Anchor.
    -<p>You can override this behaviour by setting a <strong>newConnection</strong> parameter on the connect call:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -	anchor:"Continuous",
    -	endpoint:["Rectangle", { width:40, height:20 }]
    -});
    -
    -...
    -
    -jsPlumb.connect({ source:"el1", target:"el2", newConnection:true });
    -</pre>
    -</div>
    -<h5><a id="makeTarget">jsPlumb.makeTarget</a></h5>
    -<p>
    -This method takes two arguments, the first of which specifies some element (or list of elements); the second specifies the Endpoint you wish to create on that element whenever a Connection is established on it.  In this example we will use the exact same target Endpoint we used before - the gray rectangle - but we will tell jsPlumb that the element "aTargetDiv" will be the drop target:
    -</p>
    -		<div class="code">
    -<pre>var endpointOptions = { 
    -  isTarget:true, 
    -  maxConnections:5,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" } 
    -};
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -<p>
    -	The allowed values in 'endpointOptions' are identical for both the <strong>jsPlumb.addEndpoint</strong> and <strong>jsPlumb.makeTarget</strong> methods, but makeTarget supports an extended 
    -	anchor syntax that allows you more control over the location of the target endpoint.  This is discussed below.	
    -</p>		
    -<p>Notice in the 'endpointOptions' object above there is a 'isTarget' parameter set - this may seem incongruous, since you know you're going to make some element a target.  Remember that the endpointOptions object is the information jsPlumb will use to create an Endpoint on the given target element each time a Connection is established to it. It takes the exact same format as you would pass to addEndpoint; makeTarget is essentially a deferred addEndpoint call followed by a connect call.  So in this case, we're telling jsPlumb that any Endpoints it happens to create on some element that was configured by the makeTarget call are themselves Connection targets.
    -</p>
    -<h5>Unique Endpoint per Target </h5>
    -<p>jsPlumb will create a new Endpoint using the supplied information every time a new Connection is established on the target element, by default, but you can override this behaviour and tell jsPlumb that it should create at most one Endpoint, which it should attempt to use for subsequent Connections:
    -</p>
    -		<div class="code">
    -<pre>var endpointOptions = { 
    -  isTarget:true, 
    -  uniqueEndpoint:true,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" } 
    -};
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -Here, the <strong>uniqueEndpoint</strong> parameter tells jsPlumb that there should be at most one Endpoint on this element.  Notice that 'maxConnections' is not set: the default is 1, so in this setup we have told jsPlumb that "aTargetDiv" can receive one Connection and no more.
    -<h5>Deleting Endpoints on Detach</h5>
    -By default, any Endpoints created using makeTarget have <strong>deleteEndpointsOnDetach</strong> set to true, which means that once all Connections to that Endpoint are removed, the Endpoint is deleted.  You can override this by setting the flag to true on the makeTarget call:
    -		<div class="code">
    -<pre>var endpointOptions = { 
    -  isTarget:true, 
    -  maxConnections:5,
    -  uniqueEndpoint:true,
    -  deleteEndpointsOnDetach:false,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" } 
    -};
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -In this setup we have told jsPlumb to create an Endpoint the first time "aTargetElement" is connected to, and to not delete it even if there are no longer any Connections to it.  The created Endpoint will be reused for subsequent Connections, and can support a maximum of 5.
    -
    -<h5>Target Anchors positions with makeTarget</h5>
    -<p>When using the makeTarget method, jsPlumb allows you to provide a callback function to be used to determine
    -the appropriate location of a target anchor for every new Connection dropped on the given target.  It may be the
    -case that you want to take some special action rather than just relying on one of the standard anchor mechanisms.
    -</p>
    -<p>This is achieved through an extended anchor syntax (note that this syntax is <strong>not supported</strong> in
    -the <strong>jsPlumb.addEndpoint</strong> method) that supplies a "positionFinder" to the anchor specification.  jsPlumb 
    -provides two of these by default; you can register your own on jsPlumb and refer to them by name, or just 
    -supply a function.  Here's a few examples:</p>
    -Instruct jsPlumb to place the target anchor at the exact location at which the mouse button was released on the
    -target element. Note that you tell jsPlumb the anchor is of type "Assign", and you then provide a "position"
    -parameter, which can be the name of some position finder, or a position finder function itself:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {
    -  anchor:[ "Assign", { 
    -    position:"Fixed"
    -  }]
    -});
    -</pre>
    -</div>
    -<p><strong>Fixed</strong> is one of the two default position finders provided by jsPlumb. The other is <strong>Grid</strong>:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {
    -  anchor:[ "Assign", { 
    -    position:"Grid",
    -    grid:[3,3]
    -  }]
    -});
    -</pre>
    -</div>
    -The Grid position finder takes a "grid" parameter that defines the size of the grid required. [3,3] means
    -3 rows and 3 columns.
    -<h5>Supplying your own position finder</h5>
    -<p>To supply your own position finder to jsPlumb you first need to create the callback function. First let's take a look at what the source code for the Grid position finder looks like:</p>
    -<div class="code">
    -<pre>
    -function(eventOffset, elementOffset, elementSize, constructorParams) {
    -  var dx = eventOffset.left - elementOffset.left, dy = eventOffset.top - elementOffset.top,
    -      gx = elementSize[0] / (constructorParams.grid[0]), 
    -      gy = elementSize[1] / (constructorParams.grid[1]),
    -      mx = Math.floor(dx / gx), my = Math.floor(dy / gy);
    -
    -  return [ ((mx * gx) + (gx / 2)) / elementSize[0], ((my * gy) + (gy / 2)) / elementSize[1] ];
    -}
    -</pre>
    -</div>
    -The four arguments are:
    -<ul>
    -	<li class="bullet"><strong>eventOffset</strong> - Page left/top where the mouse button was released (a JS object containing left/top members like you get from a jQuery offset call)</li>
    -	<li class="bullet"><strong>elementOffset</strong> - JS offset object containing offsets for the element on which the Connection is to be created</li>
    -	<li class="bullet"><strong>elementSize</strong> - [width, height] array of the dimensions of the element on which the Connection is to be created</li>
    -	<li class="bullet"><strong>constructorParams</strong> - the parameters that were passed to the Anchor's constructor. In the example given above, those parameters are 'position' and 'grid'; you can pass arbitrary parameters.
    -</ul>
    -The return value of this function is an array of [x, y] - proportional values between 0 and 1 inclusive, such as you can pass to a static Anchor.
    -<p>To make your own position finder you need to create a function that takes those four arguments and returns an [x, y] position for the anchor, for example:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.AnchorPositionFinders.MyFinder = function(dp, ep, es, params) {
    -	...		
    -	return [ some maths ];	
    -};
    -</pre>
    -</div>
    -Then refer to it in a makeTarget call:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {  
    -  anchor:[ "Assign", { 
    -    position:"MyFinder",
    -    myCustomParameter:"foo",
    -    anInteger:5
    -  }]
    -});
    -</pre>
    -</div>
    -
    -<h5><a id="makeSource">jsPlumb.makeSource</a></h5>
    -There are two use cases supported by this method.  The first is the case that you want to drag a Connection from the element itself and have an Endpoint attached to the element when a Connection is established.  The second is a more specialised case: you want to drag a Connection from the element, but once the Connection is established you want jsPlumb to move it so that its source is on some other element.  For an example of this second case, check out the <a href="../demo/jquery/stateMachineDemo.html">State Machine Connectors</a> demonstration.
    -<p>Here's an example code snippet for the basic use case of makeSource:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource(someDiv, {
    -  paintStyle:{ fillStyle:"yellow" },
    -  endpoint:"Blank",
    -  anchor:"BottomCenter"
    -});
    -</pre>
    -</div>
    -<p>
    -Notice again that the second argument is the same as the second argument to an addEndpoint call.  makeSource is, essentially, a type of addEndpoint call.  In this example we have told jsPlumb that we will support dragging Connections directly from 'someDiv'.  Whenever a Connection is established between 'someDiv' and some other element, jsPlumb assigns an Endpoint at BottomCenter of 'someDiv', fills it yellow, and sets that Endpoint as the newly created Connection's source. 
    -</p>		
    -<p>Configuring an element to be an entire Connection source using makeSource means that the element cannnot itself be draggable.  There would be no way for jsPlumb to distinguish between the user attempting to drag the element and attempting to drag a Connection from the element.  If your UI works in such a way that that is acceptable then you'll be fine to use this method.  But if you want to drag Connections from some element but also have the element be draggable itself, you might want to consider the second use case.  First, a code snippet:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("someConnectionSourceDiv", {
    -  paintStyle:{ fillStyle:"yellow" },
    -  endpoint:"Blank",
    -  anchor:"BottomCenter",
    -  parent:"someDiv"
    -});
    -</pre>
    -</div>
    -<p>The only difference here is the inclusion of the 'parent' parameter.  It instructs jsPlumb to configure the source Endpoint on the element specified - in this case, "someDiv" (the value of this parameter may be a String id or a selector).</p>
    -<p>Again, I'd suggest taking a look at the <a href="../demo/jquery/stateMachineDemo.html">State Machine Connectors demonstration</a> for an example of this.</p>
    -
    -<p>
    -There are many things you can set in an Endpoint options object; for a thorough list see the API documentation for Endpoint.  
    -</p>
    -<p>Here's an example of specifying that you want an Arrow overlay halfway along any Connection dragged from this Endpoint:</p>
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -  isSource:true,
    -  connectorStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  connectorOverlays: [ [ "Arrow", { location:0.5 } ] ]
    -};
    -</pre>
    -</div>
    -This is an Endpoint that moves around the element it is attached to dependent on the location of other elements in the connections it is attached to (a 'dynamic' anchor):
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -  isSource:true,
    -  connectorStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  connectorOverlays: [ [ "Arrow", { location:0.5 } ] ]
    -  anchor:[ "TopCenter","RightMiddle","BottomCenter","LeftMiddle" ]
    -};
    -</pre>
    -</div>
    -
    -<h5><a id="dragOptions">Drag Options</a></h5>
    -	These are options that will be passed through to the supporting library's drag API.  jsPlumb passes everything you supply here through, inserting
    -	wrapping functions if necessary for the various lifecycle events that jsPlumb needs to know about.  So if, for example, you pass a function to be
    -	called when dragging starts, jsPlumb will wrap that function with a function that does what jsPlumb needs to do, then call yours.
    -	<p>
    -	At the time of writing, jsPlumb supports jQuery, MooTools and YUI3, and each of those libraries uses different terminology.  In addition, jQuery's API is
    -	more fully featured, providing easy support for setting the zIndex and opacity of elements being dragged, as well as the 'scope' for a drag/drop (allowing
    -	you to specify more than one type of drag/drop pair), and hover classes for when a draggable is on the move or
    -	over a droppable. If you're using jQuery you can of course just supply these values on the dragOptions; to make it easier, jsPlumb's MooTools and YUI3 adapters recognize 
    -	these options and add appropriate callbacks for you.  
    -	</p> 
    -	<p>
    -	Given that the options here are library-specific, and they are all well-documented, we're going to discuss just the three drag options
    -	that behave the same way in all (see below for hoverClass):
    -		<ul>
    -		<li><strong>opacity</strong> - the opacity of an element that is being dragged.  Should be a fraction between 0 and 1 inclusive.</li>
    -		<li><strong>zIndex</strong> - the zIndex of an element that is being dragged.</li>
    -		<li><strong>scope</strong> - the scope of the draggable.  can only be dropped on a droppable with the same scope.  this is discussed below.</li>
    -		</ul> 
    -	</p>
    -	
    -	<p>For more information about drag options, take a look at the <a href="http://docs.jquery.com/UI/Draggable" target="_blank">jQuery</a>, <a href="http://mootools.net/docs/more/Drag/Drag.Move" target="_blank">MooTools</a>, or <a href="http://developer.yahoo.com/yui/3/dd/" target="_blank">YUI3</a> docs.	
    -	</p>
    -	<p><strong>NOTE: there is an issue in Chrome that affects the 'cursor' argument to drag options in jQuery. See these links:</strong>
    -	</p><p>
    -<a href="http://forum.jquery.com/topic/draggable-cursor-option-does-not-work" target="_blank">http://forum.jquery.com/topic/draggable-cursor-option-does-not-work</a><br/>
    -<a href="http://forum.jquery.com/topic/chrome-text-select-cursor-on-drag" target="_blank">http://forum.jquery.com/topic/chrome-text-select-cursor-on-drag</a>
    -</p>
    -You should put something like this at the top of your JS to avoid it (perhaps not something so drastic as a document-wide override):	
    -<div class="code">
    -<pre>document.onselectstart = function () { return false; };</pre>
    -</div>		
    -	
    -	<h5><a id="dropOptions">Drop Options</a></h5>
    -	Drop options are treated by jsPlumb in the same way as drag options - they are passed through to the underlying library.  MooTools does not have drop options like jQuery and YUI3 do; droppable functionality in MooTools 
    -	is actually implemented by the Drag.Move class - the one used to initialise a draggable.  But when you setup an Endpoint in jsPlumb you should ignore
    -	that fact, and treat droppables like you would in jQuery or YUI3.  jsPlumb wires everything up for you under the hood.
    -	<p>There are three jQuery droppable options that jsPlumb treats as shortcuts in MooTools and YUI3, for the sake of consistency:</p>
    -		<ul>
    -			<li><strong>hoverClass</strong> - the CSS class to attach to the droppable when a draggable is hovering over it.</li>
    -			<li><strong>activeClass</strong> - the CSS class to attach to the droppable when a draggable is, um, being dragged.</li>
    -			<li><strong>scope</strong> - the scope of the draggable.  The draggable can only be dropped on a droppable with the same scope.  This is discussed below.</li>
    -		</ul>   
    -	<p>For more information about drop options when using jQuery, see <a href="http://docs.jquery.com/UI/Droppable" target="_blank">here</a>.</p>
    -		
    -	<h5><a id="dragAndDropScope">Drag and Drop Scope</a></h5>
    -		jsPlumb borrowed the concept of 'scope' from jQuery's drag/drop implementation: the notion of
    -		which draggables can be dropped on which droppables.  In jsPlumb you can provide a 'scope' entry when 
    -		creating an Endpoint.  Here's the example grey Endpoint example with 'scope' added:
    -		
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:"#666" },
    -  isSource:true,
    -  connectionStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  scope:"exampleGreyConnection"
    -};</pre>		
    -</div>  		
    -  										
    -	If you do not provide a 'scope' entry, jsPlumb uses a default scope.  Its value is accessible through this method:
    -	<div class="code">
    -<pre>
    -jsPlumb.getDefaultScope();
    -</pre>	
    -	</div>				
    -	If you want to change it for some reason you can do so with this method:
    -	<div class="code">
    -<pre>
    -jsPlumb.setDefaultScope("mySpecialDefaultScope");
    -</pre>	
    -	</div>			
    -	You can also, should you want to, provide the scope value separately on the drag/drop options, like this:
    -	<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -  isSource:true,
    -  connectionStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  dragOptions:{ scope:"dragScope" },
    -  dropOptions:{ scope:"dropScope" }
    -};</pre>		
    -</div>  
    -			
    -	</div>	
    -	
    -	<div class="section">
    -		<h3><a id="beforeDrop">Interceptors</a></h3>
    -		<p>
    -		Interceptors were new in jsPlumb 1.3.4.  They are basically event handlers from which you can return a value that tells jsPlumb to abort what it is that
    -		it was doing.  There are currently two interceptors supported - <strong>beforeDrop</strong>, which is called when the user has dropped a Connection onto some target, 
    -		and <strong>beforeDetach</strong>, which is called when the user is attempting to detach a Connection. 
    -		</p>
    -		<p>Interceptors can be registered via the <strong>bind</strong> method on jsPlumb just like any other event listeners, and they can also be passed in to
    -		the <strong>jsPlumb.addEndpoint</strong>, <strong>jsPlumb.makeSource</strong> and <strong>jsPlumb.makeTarget</strong>
    -		methods.  A future version of jsPlumb will support registering interceptors on Endpoints using the <strong>bind</strong> method as you can now with the jsPlumb object.
    -		Note that binding 'beforeDrop' (as an example) on jsPlumb itself is like a catch-all: it will be called every time a Connection is dropped on _any_ 
    -		Endpoint.  But passing a 'beforeDrop' callback into an Endpoint (and in the future, registering one using 'bind') constrains that callback to just the
    -		Endpoint in question.  		
    -		<h4><a id="beforeDrop">Before Drop</a></h4>
    -		This is called when the user has released the mouse button over a Connection target.  Your callback is passed the
    -		a JS object containing these fields:
    -		<ul>
    -			<li>sourceId</li>
    -			<li>targetId</li>
    -			<li>scope</li>
    -		</ul>
    -		<p>Returning false - or nothing - from this callback will cause the drag to be abandoned, and the new Connection removed from the UI.</p>
    -		<h4><a id="beforeDetach">Before Detach</a></h4>
    -		This is called when the user has detached a Connection, which can happen for a number of reasons: by default, jsPlumb allows users to drag Connections off of target Endpoints, but this can also result from a programmatic 'detach' call.  Every case is treated the same by jsPlumb, so in fact it is possible for you to write code that attempts to detach a Connection but then denies itself!  You might want to be careful with that. 
    -		<p>Note that this interceptor is passed the actual Connection object; this is different from the beforeDrop
    -		interceptor discussed above: in this case, we've already got a Connection, but with beforeDrop we are yet 
    -		to confirm that a Connection should be created.</p>
    -		<p>Returning false - or nothing - from this callback will cause the detach to be abandoned, and the Connection will be reinstated/left on its current target.</p>
    -			
    -	</div>
    -
    -		<div class="section">
    -			<h3><a id="paintstyles">Paint Styles</a></h3>
    -			Defining the appearance of Connectors, Endpoints (and Overlays, but this is deprecated in favour of using CSS classes) is achieved through a 'paintStyle' (or a quite similar name...keep reading)
    -			object passed as a parameter to one of jsPlumb.connect, jsPlumb.addEndpoint, jsPlumb.makeSource or jsPlumb.makeTarget. Depending on the method you are
    -			calling, the parameter names vary.
    -			<h5>Connector Paint Styles</h5>
    -			These are specified in a <em>paintStyle</em> parameter on a call to <strong>jsPlumb.connect</strong>:
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			or in the <em>connectorPaintStyle</em> parameter on a call to <strong>jsPlumb.addEndpoint</strong> or <strong>jsPlumb.makeSource</strong>:
    -			<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div> 			
    -			 
    -
    -Notice the <em>paintStyle</em> parameter in those examples: it is the paint style for the Endpoint, which we'll discuss below.
    -
    -			<h5>Endpoint Paint Styles</h5>
    -			These are specified in a <em>paintStyle</em> parameter on a call to <strong>addEndpoint</strong>. This is the example from just above:
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -</div> 
    -			...or as the <em>endpointStyle</em> parameter to a <strong>connect</strong> call:		
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			... or as an entry in the <em>endpointStyles</em> array passed to a <strong>jsPlumb.connect</strong> call:
    -					<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  endpointStyles:[ 
    -    { fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -    { fillStyle:"green" }
    -  ],
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			or as the <em>paintStyle</em> parameter passed to a <strong>makeTarget</strong> or <strong>makeSource</strong> call:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("el1", {
    -  ...
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  ...
    -});
    -</pre>			
    -</div>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 }
    -  parent:"someOtherDivIJustPutThisHereToRemindYouYouCanDoThis"
    -});
    -</pre>			
    -			</div>						
    -	In the first example we  made "el1" into a drop target, and defined a paint style for the Endpoint jsPlumb will create when a Connection is
    -	established.  In the second we made "el1" a connection source and assigned the same values for the Endpoint jsPlumb will create when a Connection
    -	is dragged from that element.
    -	
    -	<h5>Overlay Paint Styles</h5>
    -	The preferred way to set paint styles for Overlays is to use the <em>cssClass</em> parameter in the constructor arguments of an Overlay definition.
    -	
    -	<h4>Paint Style Parameters</h4>
    -	This is the full list of parameters you can set in a paintStyle object, but note that <strong>fillStyle</strong> is ignored by Connectors,
    -	and <strong>strokeStyle</strong> is ignored by Endpoints.  Also, if you create a Connection using jsPlumb.connect and do not specify any
    -	Endpoint styles, the Endpoints will derive their fillStyle from the Connector's strokeStyle.
    -	<p>
    -		fillStyle, strokeStyle and outlineColor can be specified using any valid CSS3 syntax.
    -	</p>
    -	<ul>
    -		<li><strong>fillStyle</strong> - color for an Endpoint, eg. rgba(100,100,100,50), "blue", "#456", "#993355", rgb(34, 56, 78).</li>
    -		<li><strong>strokeStyle</strong> - color for a Connector. see fillStyle examples.</li>
    -		<li><strong>lineWidth</strong> - width of a Connector's line. An integer.</li>
    -		<li><strong>outlineWidth</strong> - width of the outline for an Endpoint or Connector. An integer.</li>
    -		<li><strong>outlineColor</strong> - color of the outline for an Endpoint or Connector. see fillStyle examples.</li>
    -		<li><strong>dashstyle</strong> - VML and SVG only. This comes from VML, and allows you to create dashed or dotted lines.  It has a 
    -			better syntax than the equivalent attribute in SVG (<em>stroke-dasharray</em>, discussed below), so jsPlumb supports this for both renderers. 
    -			The <em>dashstyle</em> attribute is specified as an array of strokes and spaces, where each value is some multiple of <em>the width of 
    -			the Connector</em>, and that's where it's better than SVG, which uses pixels.
    -			<p>
    -			<a href="http://www.w3.org/TR/NOTE-VML" target="_blank">The VML spec</a> is a good place to find valid values for dashstyle. Note that
    -			jsPlumb does not support the string values for this attribute ("solid", "dashdot", etc).
    -			</p>
    -			<p>
    -			In SVG render mode, jsPlumb uses the <em>lineWidth</em> parameter in conjunction with the values in a <em>dashstyle</em> attribute
    -			to create an appropriate value for <em>stroke-dasharray</em>.
    -			</p>
    -		</li>
    -		<li><strong>stroke-dasharray</strong> - SVG only. This is the SVG equivalent of <em>dashstyle</em>.  
    -		<a href="http://www.w3.org/TR/SVG/painting.html" target="_blank">The SVG spec</a> discusses valid values for this parameter.  But be aware
    -		that jsPlumb does not convert this into a valid <em>dashstyle</em> attribute when using the VML renderer. Better to use <em>dashstyle</em>.
    -		</li>
    -		<li><strong>stroke-dashoffset</strong> - SVG only.  This is used in SVG to specify how far into the dash pattern to start painting.  For more information,
    -		see <a href="http://www.w3.org/TR/SVG/painting.html" target="_blank">the SVG spec.</a></li>
    -		<li><strong>joinstyle</strong> - VML and SVG only.  As with <em>dashstyle</em>, this is a VML attribute that jsPlumb supports for both VML
    -		and SVG - jsPlumb turns this into a <em>stroke-linejoin</em> attribute when rendering with SVG.  This attribute specifies how you want individual
    -		segments of connectors to be joined; the VML and SVG specs both have examples of this, of which many are the same between the two, which is
    -		why jsPlumb will automatically convert this attribute into the SVG equivalent.
    -		</li>
    -		<li><strong>stroke-linejoin</strong> - SVG only.  This is the equivalent of VML's <em>joinstyle</em> attribute, but as with <em>stroke-dasharray</em>,
    -		jsPlumb does not convert this into something approriate for VML.  So, using <em>joinstyle</em> will enable you to support more browsers with
    -		less effort.		
    -		</li>
    -	</ul>
    -	
    -	<h3><a id="hoverpaintstyles">Hover Paint Styles</a></h3>
    -	 		
    -	 		Connectors and Endpoints both support the concept of a "hover" paint style - a paint style to use when the mouse is hovering over
    -	 		the component.  These are specified in the exact same format as paint styles discussed above, but hover paint styles also inherit
    -	 		any values from the main paint style.  This is because you will typically want to just change the color, or perhaps outline color, of
    -	 		a Connector or Endpoint when the mouse is hovering, but leave everything else the same.  So having hover paint styles inherit their
    -	 		values precludes you from having to define things in more than one place.
    -	 		<p>
    -	 			The naming convention adopted for hover paint styles is pretty much to insert the word 'hover' into the corresponding main paint
    -	 			style parameters.  Here are a couple of examples:
    -	 		</p> 
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -  hoverPaintStyle:{ strokeStyle:"red" },
    -  endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  endpointHoverStyle:{ fillStyle:"red" }
    -});
    -</pre>			
    -</div>
    -			In this example we specified a hover style for both the Connector, and each of its Endpoints.  Here's the same thing, but using the plural version, to specify a different hover style for each Endpoint:	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -  hoverPaintStyle:{ strokeStyle:"red" },
    -  endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  endpointHoverStyles:[ { fillStyle:"red" }, { fillStyle:"yellow" } ]
    -});
    -</pre>			
    -</div>
    -	 		Calls to <strong>addEndpoint</strong>, <strong>makeSource</strong> and <strong>makeTarget</strong> can also specify various hover paint styles:
    -	 								<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  hoverPaintStyle:{ fillStyle:"red" },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -  connectorHoverPaintStyle:{ strokeStyle:"red", outlineColor:"yellow", outlineWidth:1 }
    -});
    -
    -jsPlumb.makeSource("el2", {
    -  paintStyle:{ fillStyle:"transparent", outlineColor:"yellow", outlineWidth:1 },
    -  hoverPaintStyle:{ fillStyle:"red" },
    -  connectorPaintStyle:{ strokeStyle:"green", lineWidth:3 },
    -  connectorHoverPaintStyle:{ strokeStyle:"#678", outlineColor:"yellow", outlineWidth:1 }
    -});
    -
    -jsPlumb.makeTarget("el3", {
    -  paintStyle:{ fillStyle:"transparent", outlineColor:"yellow", outlineWidth:1 },
    -  hoverPaintStyle:{ fillStyle:"red" }
    -});
    -</pre>			
    -			</div> 
    -	 		
    -	 		In these examples we specified a hover paint style for both the Endpoint we are adding, and any Connections to/from the Endpoint.
    -	 		<p>	
    -		Note that <strong>makeTarget</strong> does not support Connector parameters. It is for creating targets only; Connector parameters will be set by the source Endpoint in any Connections that are made to the element that you turned into a target by using this method.</p>
    -	 			 		
    -			<h4><a id="gradients">Gradients</a></h4>
    -			The Canvas and SVG renderers both support gradients. The VML renderer does not.  jsPlumb uses its own syntax to define gradients, to abstract out the differences between the syntax required by canvas and that required by SVG.  
    -			<p>There are two types of gradients available - a 'linear' gradient, which consists of colored lines all
    -			going in one direction, and a 'radial' gradient, which consists of colored circles emanating from one circle to another.
    -			Because of their basic shape, jsPlumb supports only <em>linear</em> gradients for
    -			Connectors.  But for Endpoints, jsPlumb supports both <em>linear</em> and <em>radial</em> gradients.</p>
    -			
    -				<h5>Connector gradients</h5>
    -				To specify a linear gradient to use in a Connector, you must add a <em>gradient</em> object to your
    -				Connector's <em>paintStyle</em>, for instance:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : "window2",
    -	target : "window3",
    -	paintStyle:{
    -		gradient:{
    -			stops:[[0,"green"], [1,"red"]]
    -		},
    -		lineWidth:15
    -	}
    -});
    -</pre>
    -</div>
    -Here we have connected window2 to window3 with a 15 pixel wide connector that has a gradient from green to red.</p><p> Notice the <strong>gradient</strong> object and the
    -<strong>stops</strong> list inside it - the gradient consists of an arbitrary number of these "color stops".  Each color stop is comprised
    -of two values - [position, color].  Position must be a decimal value between 0 and 1 (inclusive), and indicates where the color
    -stop is situated as a fraction of the length of the entire gradient.  Valid values for
    -the colors in the <strong>stops</strong> list are the same as those that are valid for <strong>strokeStyle</strong> when describing a color.
    -			</p>
    -			As mentioned, the <strong>stops</strong> list can hold an arbitrary number of entries.  Here's an example of a gradient that goes from red to blue to green, and back again through blue to red:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle : {
    -		gradient:{
    -			stops:[[0,'red'], [0.33,'blue'], [0.66,'green'], [0.33,'blue'], [1,'red']]
    -		},
    -		lineWidth : 15
    -	}
    -});
    -</pre>
    -</div>
    -<strong>Note:</strong> when using the VML renderer, jsPlumb will simply ignore the gradient directive
    -so it is best to ensure you also supply a <em>strokeStyle</em> in your paintStyle object, to give jsPlumb something to fall back on.  If you do not supply
    -a <em>strokeStyle</em> your Connectors will be painted black.  The previous example might look like this, for instance:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle:{
    -		strokeStyle:'red',
    -		gradient:{
    -			stops:[[0,'red'], [0.33,'blue'], [0.66,'green'], [0.33,'blue'], [1,'red']]
    -		},
    -		lineWidth:15
    -	}
    -});
    -</pre>
    -</div>
    -Notice the <strong>strokeStyle:'red'</strong> directive at the beginning of the parameter list in <strong>paintStyle</strong>.
    -			<h5>Endpoint gradients</h5>
    -			Endpoint gradients are specified using the same syntax as Connector gradients.  You put the gradient specifier either in the
    -			<strong>endpoint</strong> member, or if you are specifying different Endpoints for each end of the Connector, in one or both of
    -			the values in the <strong>endpoints</strong> array.  Also, this information applies to the case that you are creating standalone
    -			Endpoints that you will be configuring for drag and drop creation of new Connections. 
    -			<p>
    -			This is an example of an Endpoint gradient that is different for each Endpoint in the Connector.  This comes from the main demo; it is
    -			the Connector joining Window 2 to Window 3:
    -			</p>
    -<div class="code">
    -<pre>var w23Stroke = 'rgb(189,11,11)';
    -jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle:{
    -		lineWidth:8,
    -		strokeStyle:w23Stroke
    -	},
    - 	anchors:[ [0.3,1,0,1], "TopCenter" ],
    - 	endpoint:"Rectangle",
    - 	endpointStyles:[
    - 		{ gradient : {stops:[[0, w23Stroke], [1, '#558822']] } },
    -    	{ gradient : {stops:[[0, w23Stroke], [1, '#882255']] } }
    -    ]
    -});
    -</pre>
    -</div>
    -The first entry in the gradient will be the one that is on the Connector end of the Endpoint.  You can of course have as many color stops as
    -you want in this gradient, just like with Connector gradients.
    -	<h5>Applying the gradient in Endpoints</h5>
    -	Only the Dot and Rectangle endpoints honour the presence of a gradient (and, remember, not in VML). The Image endpoint of course ignores a gradient
    -	as it does no painting of its own.
    -	<p>The type of gradient you will see depends on the Endpoint type:</p>
    -		<ul>
    -			<li><strong>Dot</strong> - renders a radial endpoint, with color stop 0 on the outside, progressing inwards as we move through color stops.
    -			<p>Radial gradients actually require more data than linear gradients - in a linear gradient we just move from one point to another, whereas
    -			in a radial gradient we move from one <em>circle</em> to another.  By default, jsPlumb will render a radial gradient using a source
    -			circle of the same radius as the Endpoint itself, and a target circle of 1/3 of the radius of the Endpoint (both circles share the
    -			same center as the Endpoint itself). This circle will be offset by radius/2 in each direction.</p>
    -			<p>You can supply your own values for these inside the gradient descriptor:</p>
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect({
    -	source : 'window3',
    -	target : 'window4',
    -    paintStyle:{
    -    	lineWidth:10,
    -    	strokeStyle:w34Stroke
    -    },
    -    anchors:[ "RightMiddle", "LeftMiddle" ],
    -    endpointStyle:{
    -    	gradient : {
    -    		stops:[ [0, w34Stroke], [1, w34HlStroke] ],
    -    		offset:37.5,
    -    		innerRadius:40
    -    	},
    -    	radius:55
    -    }
    - });
    -</pre>
    -</div>
    -Here we have instructed jsPlumb to make the gradient's inner radius 10px instead of the default 25/3 = 8 ish pixels, and the offset in each direction
    -will be 5px, instead of the default radius / 2 = 12.5 pixels.
    -<p>It is also possible to specify the offset and inner radius as percentages - enter the values as strings with a '%' symbol on the end:</p>
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect({
    -	source : 'window3', 
    -	target : 'window4',
    -    paintStyle:{
    -    	lineWidth:10,
    -    	strokeStyle:w34Stroke
    -    },
    -	anchors:[ "RightMiddle", "LeftMiddle" ],
    -	endpointStyle:{
    -		gradient : {
    -			stops:[ [0, w34Stroke], [1, w34HlStroke] ],
    -			offset:'68%',
    -			innerRadius:'73%'
    -		},
    -		radius:25
    -	}
    -});
    -</pre>
    -</div>
    -This will give roughly the same output as the example above (the percentages are not entirely exact).<br/><br/>
    -			</li>
    -			<li><strong>Rectangle</strong> - renders a linear endpoint, with color stop 0 closest to the end of the Connector</li>
    -		</ul>
    -		
    -<h4><a id="svgvmlcss">Styling SVG/VML using CSS</a></h4>
    -	A nice way of controlling your UI's appearance is to make use of the fact that both SVG and VML can be styled using CSS.
    -	<p>This section will be expanded in the next few weeks to give some decent examples of how to do so</p>
    -			
    -		
    -		
    -		</div>
    -
    -		<div class="section">
    -			<h3><a id="cssclasses">CSS Class Reference</a></h3>
    -			jsPlumb attaches classes to each of the UI components it creates.
    -			<p>These class names are exposed on the jsPlumb object and can be overridden if you need to do so (see the third column in the table) </p>
    -			<table width="70%" class="table">
    -				<tr><th>Component</th><th>CSS Class</th><th>jsPlumb Member</th></tr>
    -				<tr><td>Connector</td><td>_jsPlumb_connector</td><td>connectorClass</td></tr>
    -				<tr><td>Endpoint</td><td>_jsPlumb_endpoint</td><td>endpointClass</td></tr>
    -				<tr><td>Overlay</td><td>_jsPlumb_overlay</td><td>overlayClass</td></tr>
    -			</table>
    -			<p>You would typically use these to establish appropriate z-indices for your UI.</p>			
    -		</div>
    -				
    -		<div class="section">
    -			<h3><a id="animation">Animation</a></h3>
    -			jsPlumb offers an 'animate' function, which wraps the underlying animation engine for whichever library you happen to be using and
    -			inserts a callback for jsPlumb to repaint whatever it needs to at each step.  You could of course do this yourself; it's a convenience
    -			method really.
    -			<p>The method signature is:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.animate : function(el, properties, options) 
    -</pre>
    -</div>			
    -The arguments are as follows:
    -	<ul>
    -		<li><strong>el</strong> - element id, or element object from the library you're using.</li>
    -		<li><strong>properties</strong> - properties for the animation, such as duration etc.</li>
    -		<li><strong>options</strong> - options for the animation, such as callbacks etc.</li>
    -	</ul>
    -	
    -		<h4>jQuery and the 'revert' option</h4>
    -		jQuery offers a 'revert' option that you can use to instruct it to revert a drag under some condition. This is
    -		rendered in the UI as an animation that returns the drag object from its current location to wherever it started
    -		out.  It's a nice feature.  Unfortunately, the animation that runs the revert does not offer any lifecycle
    -		callback methods - no 'step', no 'complete' - so its not possible for jsPlumb to know that the revert 
    -		animation is taking place.
    -	
    -		</div>
    -		
    -			<div class="section">
    -			<h3><a id="connectionInfo">Retrieving Connection Information</a></h3>
    -			There are two ways of retrieving connection information from jsPlumb. <strong>getConnections</strong>is the original method; from 1.3.8 onwards this method is supplemented by <a href="#selectConnections">jsPlumb.select</a>, a much more versatile variant.
    -			<h5>getConnections</h5>
    -			<p>Before you use this method you should understand jsPlumb's notion of 'scope' - documentation
    -			is <a href="#dragAndDropScope">here</a> </p>
    -			
    -			<h4>Retrieving connections for a single scope</h4>
    -			<p>To do this, you call getConnections with either no arguments, in which case jsPlumb uses the default scope, or with a string
    -			specifying one scope</p>
    -			<div class="code">
    -<pre>var connectionList = jsPlumb.getConnections();     // you get a list of Connection objects that are in the default scope.
    -</pre>			
    -			</div>
    -			Compare this with:
    -			<div class="code">
    -<pre>var connectionList = jsPlumb.getConnections("myScope");     // you get a list of Connection objects that are in "myScope".
    -</pre>			
    -			</div>
    -			<h4>More advanced filtering</h4>
    -			getConnections optionally takes a JS object specifying filter parameters, of which there are three:
    -			<ul>
    -				<li>scope - the scope(s) of the connection type(s) you wish to retrieve</li>
    -				<li>source - limits the returned connections to those that have this source id</li>
    -				<li>target - limits the returned connections to those that have this target id</li>
    -			</ul>			
    -			Each of these three parameters may be supplied as a string, which for source and target is an element id and for scope is the name of the scope, or a list of strings.  Also from 1.3.8 you can pass "*" in as the value for any of these - a wildcard, meaning any value.  See the examples below.
    -			<p>
    -			The return value of a call to getConnection using a JS object as parameter varies on how many scopes you defined.  If you defined only a single scope then jsPlumb returns you a list of Connections in that scope.  Otherwise the return value is a dictionary whose keys are  scope names, and whose values are lists of Connections. For example, the following call:
    -			</p>
    -			<div class="code">
    -<pre>
    -	jsPlumb.getConnections({scope:["someScope", "someCustomScope"]});
    -</pre>
    -</div>
    -would result in this output:			
    -<div class="code">
    -<pre>
    -{
    -	"someScope" : [ 1..n Connections ],
    -	"someCustomScope": [ 1..m Connections ]
    -}
    -</pre>
    -</div>	
    -
    -There is an optional second parameter that tells getConnections to flatten its output and just return you an array.  The previous example with this parameter would look like this:
    -			<div class="code">
    -<pre>
    -	jsPlumb.getConnections({scope:["someScope", "someCustomScope"]}, true);
    -</pre>
    -</div>
    -would result in this output:			
    -<div class="code">
    -<pre>
    -[ 1..n Connections ]
    -</pre>
    -</div>	
    -		
    -		The following examples show the various ways you can get connection information:
    -	<ul>
    -		<li>Get all connections:
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getAllConnections();  
    -</pre>
    -</div>						
    -		</li>
    -<li>Get all connections for the default scope only (return value is a list):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections();  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given scope (return value is a list):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:"myTestScope"});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given scopes (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:["myTestScope", "yourTestScope"]});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given source (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:"mySourceElement"});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given sources (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:["mySourceElement", "yourSourceElement"]});  
    -</pre>
    -</div>						
    -		</li>		
    -<li>Get all connections for the given target (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({target:"myTargetElement"});  
    -</pre>
    -</div>						
    -		</li>				
    -<li>Get all connections for the given source and targets (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:"mySourceElement", target:["target1", "target2"]});  
    -</pre>
    -</div>						
    -		</li>				
    -		
    -<li>Get all connections for the given scope, with the given source and target (return value is a list of connections):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:'myScope", source:"mySourceElement", target:"myTargetElement"});  
    -</pre>
    -</div>						
    -		</li>				
    -		
    -	</ul>	
    -
    -		</div>
    -
    -		<div class="section">
    -			<h3><a id="selectConnections">Selecting Connections</a></h3>
    -			Introduced in 1.3.8, <strong>jsPlumb.select</strong> provides a fluid interface for working with lists of Connections.  The syntax used to specify which Connections you want is identical to that which you use for getConnections, but the return value is an object that supports most operations that you can perform on a Connection, and which is also chainable, for setter methods. Certain getter methods are also supported, but these are not chainable; they return an array consisting of all the Connections in the selection along with the return value for that Connection.
    -			<p>
    -			This is the full list of setter operations supported by jsPlumb.select:
    -			<ul>
    -				<li class="bullet">addOverlay</li>
    -				<li class="bullet">removeOverlay</li>
    -				<li class="bullet">removeOverlays</li>
    -				<li class="bullet">showOverlay</li>
    -				<li class="bullet">hideOverlay</li>
    -				<li class="bullet">showOverlays</li>
    -				<li class="bullet">hideOverlays</li>
    -				<li class="bullet">removeAllOverlays</li>
    -				<li class="bullet">setLabel</li>
    -				<li class="bullet">setPaintStyle</li>
    -				<li class="bullet">setHoverPaintStyle</li>
    -				<li class="bullet">setDetachable</li>
    -				<li class="bullet">setConnector</li>
    -				<li class="bullet">setParameter</li>
    -				<li class="bullet">setParameters</li>
    -				<li class="bullet">detach</li>
    -			</ul>
    -
    -			Each of these operations returns a selector that can be chained.
    -			</p>
    -			These is the full list of getter operations supported by jsPlumb.select:
    -			<p>
    -				<ul>
    -				<li class="bullet">getLabel</li>
    -				<li class="bullet">getOverlay</li>
    -				<li class="bullet">isHover</li>
    -				<li class="bullet">isDetachable</li>
    -				<li class="bullet">getParameter</li>
    -				<li class="bullet">getParameters</li>
    -			</ul>
    -
    -			Each of these operations returns an array whose entries are [ value, Connection ] arrays, where 'value' is the return value from the given Connection.  Remember that the return values from a getter are not chainable, but a getter may be called at the end of a chain of setters.
    -			</p>
    -
    -			<p>Some examples:</p>
    -			Select all Connections and set their hover state to be false:
    -
    -<div class="code">
    -<pre>
    -jsPlumb.select().setHover(false);
    -</pre>
    -</div>
    -			Select all Connections from "d1" and remove all Overlays:
    -
    -<div class="code">
    -<pre>
    -jsPlumb.select({source:"d1"}).removeAllOverlays();
    -</pre>
    -</div>
    -
    -Select all connections in scope "foo" and set their paint style to be a thick blue line:
    -<div class="code">
    -<pre>
    -jsPlumb.select({scope:"foo"}).setPaintStyle({ strokeStyle:"blue", lineWidth:5 });
    -</pre>
    -</div>
    -Select all Connections from "d1" and detach them:
    -<div class="code">
    -<pre>
    -jsPlumb.select({source:"d1"}).detach();
    -</pre>
    -</div>
    -<h5>.each iterator</h5>
    -The return value of jsPlumb.select has a <strong>.each</strong> function that allows you to iterate through the list, performing an operation on each one:
    -<div class="code">
    -<pre>
    -jsPlumb.select({scope:"foo"}).each(function(connection) {
    -	
    -		// do something 
    -});
    -</pre>
    -</div>
    -.each is itself chainable:
    -<div class="code">
    -<pre>
    -jsPlumb.select({scope:"foo"}).each(function(connection) {
    -	
    -		// do something 
    -
    -}).setHover(true);
    -</pre>
    -</div>
    -
    -
    -<h5>Other properties/functions</h5>
    -<ul>
    -	<li class="bullet"><strong>length</strong> - this member reports the number of Connections in the selection</li>
    -	<li class="bullet"><strong>get(idx)</strong> - this function allows you to retrieve a Connection from the selection</li>
    -</ul>
    -
    -
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="events">Events</a></h3>
    -			jsPlumb supports binding to several different events on Connections, Endpoints and Overlays, and also on the jsPlumb object itself.  
    -			
    -			<h4><a id="jsPlumbEvents">jsPlumb Events</a></h4>
    -			<p>To bind an event to jsPlumb itself, use <strong>jsPlumb.bind(event, callback)</strong>:</p>
    -			<div class="code">
    -<pre>
    -jsPlumb.bind("jsPlumbConnection", function(info) {
    -   .. update your model in here, maybe.
    -});
    -</pre>
    -</div>
    -<p>These are the events you can bind to on the jsPlumb class:</p>
    -			<ul>
    -				<li class="bullet"><strong>jsPlumbConnection</strong> - notification a Connection was established.
    -					<p>
    -						The first argument to the callback is an object with the following properties:					
    -					</p>
    -					<div class="code">
    -<pre>
    -{
    -  connection 		: 	the new Connection.  you can register listeners on this etc.
    -  sourceId 		:	id of the source element in the Connection
    -  targetId		:	id of the target element in the Connection
    -  source		:	the source element in the Connection
    -  target		:	the target element in the Connection
    -  sourceEndpoint	:	the source Endpoint in the Connection
    -  targetEndpoint	:	the targetEndpoint in the Connection
    -}
    -</pre>
    -					</div>
    -The second argument is the original mouse event that caused the Connection, if any.
    -
    -					<p>
    -All of the source/target properties are actually available inside the Connection object, but - for one of those rubbish historical reasons - are provided separately because of a vagary of the <em>jsPlumbConnectionDetached</em> callback, which is discussed below.
    -</p>
    -				</li>
    -				<li class="bullet"><strong>jsPlumbConnectionDetached</strong> - notification a Connection was detached.  
    -					<p>
    -						As with <em>jsPlumbConnection</em>, the first argument to the callback is an object with the following properties:					
    -					</p>
    -					<div class="code">
    -<pre>
    -{
    -  connection		: 	the Connection that was detached.  
    -  sourceId		:	id of the source element in the Connection <em>before it was detached</em>
    -  targetId		:	id of the target element in the Connection before it was detached
    -  source		:	the source element in the Connection before it was detached
    -  target		:	the target element in the Connection before it was detached
    -  sourceEndpoint	:	the source Endpoint in the Connection before it was detached
    -  targetEndpoint	:	the targetEndpoint in the Connection before it was detached
    -}
    -</pre>
    -
    -					</div>	
    -The second argument is the original mouse event that caused the disconnection, if any. 					
    -								
    -					<p>
    -As mentioned above, the source/target properties are provided separately from the Connection, because this event is fired whenever
    -a Connection is either detached and abandoned, or detached from some Endpoint and attached to another.  In the latter case, the
    -Connection that is passed to this callback is in an indeterminate state (that is, the Endpoints are still in the state they are
    -in when dragging, and do not reflect static reality), and so the source/target properties give you the real story.
    -</p>
    -<p> Like I said above, this is really just for rubbish historical reasons.  I'm attempting to document my way out of it!</p>
    -				
    -				</li>
    -				<li class="bullet"><strong>click</strong> - notification a Connection was clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification a Connection was double-clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>endpointClick</strong> - notification an Endpoint was clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>endpointDblClick</strong> - notification an Endpoint was double-clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>contextmenu</strong> - a right-click on some given component.  jsPlumb will report right clicks on both Connections and Endpoints; the callback is passed the component that received the right-click.				
    -				<li class="bullet"><strong>beforeDrop</strong>
    -					This event is fired when a new connection has been dropped. Your callback is passed a JS object with sourceId, targetId and scope in it. If you return false (or nothing) from this callback, the new Connection is aborted and removed from the UI.
    -				</li>					
    -				<!--li class="bullet"><strong>beforeDrag</strong>
    -					This event is fired when a new connection is about to be dragged.  Your callback function
    -					is passed the Connection that jsPlumb has just created.  If you return false (or nothing) 
    -					from this callback, the new Connection is aborted.
    -				</li-->
    -				<li class="bullet"><strong>beforeDetach</strong>
    -					This event is fired when a Connection is about to be detached, for whatever reason. Your callback function is passed the Connection that jsPlumb has just created. Returning false from this interceptor aborts the Connection detach.
    -				</li>
    -							
    -			</ul>
    -			<h4><a id="connectionEvents">Connection Events</a></h4>		
    -			<p>To bind to an event on a Connection, you also use the <strong>bind</strong> method:</p>
    -			<div class="code">
    -<pre>
    -var connection = jsPlumb.connect({source:"d1", target:"d2"});
    -connection.bind("click", function(conn) {
    -	console.log("you clicked on ", conn);
    -});
    -</pre>
    -</div>
    -<p>These are the Connection events to which you can bind a listener:</p>
    -			<ul>
    -				<li class="bullet"><strong>click</strong> - notification a Connection was clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification a Connection was double-clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>contextmenu</strong> - a right-click on the Connection. The callback is passed the Connection and the original event.
    -				<li class="bullet"><strong>mouseenter</strong> - notification the mouse entered the Connection's path.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>mouseexit</strong> - notification the mouse exited the Connection's.  The callback is passed the Connection and the original mouse event.</li>
    -			</ul>
    -			<h4><a id="endpointEvents">Endpoint Events</a></h4>			
    -			<p>To bind to an event on a Endpoint, you again use the <strong>bind</strong> method:</p>
    -			<div class="code">
    -<pre>
    -var endpoint = jsPlumb.addEndpoint("d1", { someOptions } );
    -endpoint.bind("click", function(endpoint) {
    -	console.log("you clicked on ", endpoint);
    -});
    -</pre>
    -</div>
    -<p>These are the Endpoint events to which you can bind a listener:</p>
    -			<ul>
    -				<li class="bullet"><strong>click</strong> - notification an Endpoint was clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification an Endpoint was double-clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>contextmenu</strong> - a right-click on the Endpoint. The callback is passed the Endpoint and the original event.
    -				<li class="bullet"><strong>mouseenter</strong> - notification the mouse entered the Endpoint.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>mouseexit</strong> - notification the mouse exited the Endpoint.  The callback is passed the Endpoint and the original mouse event.</li>
    -			</ul>
    -			<h4><a id="overlayEvents">Overlay Events</a></h4>
    -			Registering event listeners on an Overlay is a slightly different procedure - you provide them as arguments to the Overlay's 
    -			constructor.  This is because you never actually act on an 'Overlay' object.  Here's how to register a click listener on an Overlay:
    -			<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"el1",
    -	target:"el2",
    -	overlays:[
    -		[ "Label", {
    -			events:{
    -				click:function(labelOverlay, originalEvent) { alert("you clicked on the label overlay for this connection :" + labelOverlay.connection) }
    -			}
    -		}] 	
    -	]
    -});
    -</pre>			
    -			</div>		
    -		</div>
    -
    -				<div class="section">
    -			<a id="examples"><h3>jsPlumb.connect Examples</h3></a>
    -This section provides examples of how to use the programmatic API to establish Connections. 
    -			<p>
    -			The basic syntax of a call is that you execute 'connect', providing a source and a target, and optionally a paintStyle and preferences for where you
    -			want the plumbing to be anchored on each element, as well as the type of connector to use. 
    -			</p>
    -			<ul>
    -				<li>Connect window1 to window2 with the default settings:
    -					<div class="code">
    -						<pre>jsPlumb.connect({source:"window1", target:"window2"});</pre>
    -					</div>
    -This connects 'window1' to 'window2' using the jsPlumb defaults - a red Bezier curve Connector and black Dot Endpoints.
    -				</li>
    -				<li>Connect window1 to window2 with a 15 pixel wide yellow Connector, and a slightly brighter endpoint (remember the default Endpoint is a Dot):
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -					</div>
    -				</li>
    -				
    -				<li>Connect window1 to window2 with a 15 pixel wide yellow Connector, and a slightly brighter endpoint:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -					</div>
    -				</li>				
    -				<li>Connect window3 to 'window4' with a 10 pixel wide, semi opaque blue Connector, anchored to the left middle of window3, and the right middle of window4, with a Rectangle endpoint of width 10 and height 8:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window3',
    -	target:'window4',
    -	paintStyle:{ lineWidth:10, strokeStyle:'rgba(0, 0, 200, 0.5)' },
    -	anchors:["RightMiddle", "LeftMiddle"],
    -	endpoint:[ "Rectangle", { width:10, height:8 } ]
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window2 to window3 with a default Connector from the top center of window2 to the bottom center of window3, and rectangular endpoints:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window2',
    -	target:'window3',
    -	paintStyle:{lineWidth:8, strokeStyle:'rgb(189,11,11)'},
    -	anchors:["BottomCenter", "TopCenter"],
    -	endpoint:"Rectangle"
    -});</pre>
    -					</div>
    -				</li>
    -
    -
    -				<li>Connect window1 to window2 with a 15 px wide yellow Bezier. endpoints are a slightly lighter shade of yellow.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	anchors:["BottomCenter", [0.75,0,0,-1]],
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -</div></li>
    -<li>Connect window3 to window4 with a 10px wide blue-ish half transparent Bezier. put endpoints underneath the element they attach to.
    -the endpoints have a radial gradient. both ways of specifying gradient positioning are shown here.
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect( {
    -	source:'window3',
    -	target:'window4',
    -		 paintStyle:{lineWidth:10, strokeStyle:w34Stroke},
    -		 anchors:["RightMiddle", "LeftMiddle"],
    -		 endpointStyle:{ gradient : {stops:[[0, w34Stroke], [1, w34HlStroke]], offset:17.5, innerRadius:15 }, radius:35},
    -		 //endpointStyle:{ gradient : {stops:[[0, w34Stroke], [1, w34HlStroke]], offset:'78%', innerRadius:'73%'}, radius:35 },
    -		 endpointsOnTop:false
    -	}
    -);</pre>
    -</div></li>
    -<li>Connect window2 to window3 with an 8px red Bezier and default rectangular endpoints.  see also how the first anchor is
    -specified here - this is how you create anchors in locations jsPlumb does not offer shortcuts for.
    -the endpoints in this example have linear gradients applied.
    -<div class="code">
    -<pre>var w23Stroke = 'rgb(189,11,11)';
    -jsPlumb.connect({
    -	source:'window2',
    -	target:'window3',
    -	paintStyle:{lineWidth:8,strokeStyle:w23Stroke},
    -	anchors:[[0.3,1,0,1], "TopCenter"],
    -	endpoint:"Rectangle",
    -	endpointStyles:[{ gradient : {stops:[[0, w23Stroke], [1, '#558822']] }},
    -       				{ gradient : {stops:[[0, w23Stroke], [1, '#882255']] }}]
    -});</pre>
    -</div></li>
    -
    -<li>Connect window5 to window6 from center to center, 5px wide line that is green and half transparent. the endpoints are
    -125px in radius and spill out from underneath their elements.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window5',
    -	target:'window6',
    -	anchors:["Center", "Center"],
    -	paintStyle:{lineWidth:5,strokeStyle:'rgba(0,255,0,0.5)'},
    -	endpointStyle:{radius:125}
    -});</pre>
    -</div></li>
    -
    -<li>Connect window4 to window5 from bottom right to top left, with a 7px straight line purple connector, and an image as the endpoint,
    -placed on top of the element it is connected to.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window4",
    -	target:"window5",
    -	anchors:[ "BottomRight","TopLeft" ],
    -	paintStyle:{ lineWidth:7, strokeStyle:"rgb(131,8,135)" },
    -	endpoint:[ "Image", { src:"http://morrisonpitt.com/jsPlumb/img/endpointTest1.png" } ],
    -	connector:"Straight"
    -});</pre>
    -</div></li>
    -
    -
    -				<li>Connect window5 to window6 between their center points with a semi-opaque connector, and 125px endpoints:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window5",
    -	target:"window6",
    -	anchors:[ "Center", "Center" ],
    -	paintStyle:{ lineWidth:5, strokeStyle:"rgba(0,255,0,0.5)" },
    -	endpointStyle:{ radius:125 }
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window7 to window8 with a 10 pixel wide blue Connector, anchored on the top left of window7 and the bottom right of window8:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window7",
    -	target:"window8",
    -	paintStyle:{ lineWidth:10, strokeStyle:"blue" },
    -	anchors:[ "TopLeft", "BottomRight" ]
    -});
    -</pre>
    -					</div>
    -				</li>
    -				<li>Connect the bottom right corner of window4 to the top left corner of window5, with rectangular endpoints of size 40x40 and a hover color of light blue:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window4",
    -	target:"window5",
    -	anchors:["BottomRight","TopLeft"],
    -	paintStyle:{lineWidth:7,strokeStyle:'rgb(131,8,135)'},
    -	hoverPaintStyle:{ strokeStyle:"rgb(0, 0, 135)" },
    -	endpointStyle:{ width:40, height:40 },
    -	endpoint:"Rectangle",
    -	connector:"Straight"
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window1 to window2 with the default paint settings but provide some drag options (which are passed through to the underlying library's draggable call):
    -					<div class="code">
    -						<pre>jsPlumb.connect({source:'window1', target:'window2', dragOptions:{cursor:'crosshair'}});</pre>
    -					</div>
    -				</li>
    -</div>
    -		
    -		<div class="section">
    -			<a id="dragAndDropExamples"><h3>Draggable Connections Examples</h3></a>
    -			This is a list of examples of how to use jsPlumb to create Connections using drag and drop.
    -			<p>
    -			The basic procedure is:
    -			<ol>
    -				<li>1a. Create Endpoints and register them on elements in your UI</li>
    -				<li>1b. alternatively, create a source endpoint and then make some element a drop target </li>
    -				<li>2. Drag and Drop</li>
    -			</ol>
    -			That's all there is to it.  Of course there are plenty of options you can set when doing this...it will be easier
    -			to show you some examples:
    -			</p>
    -			<ul>
    -				<li>Define an Endpoint with default appearance, that is both a source and target of new Connections:
    -<div class="code">
    -<pre>var endpointOptions = { isSource:true, isTarget:true }; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Register that Endpoint on window3, specifying that it should be located in the top center of the element:
    -<div class="code">
    -<pre>var window3Endpoint = jsPlumb.addEndpoint('window3', { anchor:"TopCenter" }, endpointOptions );  
    -</pre>
    -</div>
    -Notice here the usage of the three-argument addEndpoint  - we can reuse 'endpointOptions' with a different Anchor for another element.  This is a useful
    -practice to get into.<br/><br/>
    -				</li>
    -				
    -				<li>Now register that Endpoint on window4, specifying that it should be located in the bottom center of the element:
    -<div class="code">
    -<pre>var window4Endpoint = jsPlumb.addEndpoint('window4', { anchor:"BottomCenter" }, endpointOptions );  
    -</pre>
    -</div>
    -Now we have two Endpoints, both of which support drag and drop of new Connections.  We can use these to make a programmatic Connection, too, though:<br/><br/>
    -				</li>				
    -				
    -				<li>Connect window3 to window4 with a 25px wide yellow Bezier that has a 'curviness' of 175:
    -<div class="code">
    -<pre>jsPlumb.connect({ 
    -	source:window3Endpoint,
    -	target:window4Endpoint,
    -	connector: [ "Bezier", 175 ],
    -	paintStyle:{ lineWidth:25, strokeStyle:'yellow' }
    -});  
    -</pre>
    -</div>
    -				</li>
    -				
    -				<li>Define an Endpoint that creates Connections that are 20px wide straight lines, that is both a source and target of new Connections,
    -				and that has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Define an Endpoint that will be anchored to "TopCenter".  It creates Connections that are 20px wide straight lines, that is both a source and target of new Connections,
    -				and that has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchor:"TopCenter", 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Define an Endpoint that will create a dynamic anchor which can be positioned at "TopCenter" or "BottomCenter".  It creates Connections that are 20px wide straight lines, it is both a source and target of new Connections,
    -				and it has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchor:[ "TopCenter", "BottomCenter" ], 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Exactly the same as before, but shows how you can use "anchors" instead of "anchor", if that makes you feel happier:
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchors:[ "TopCenter", "BottomCenter" ], 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>								
    -				
    -<li>Define an Endpoint that is a 30px blue dot, creates Connections that are 20px wide straight lines, is both a source and target of new Connections,
    -				has a 'scope' of 'blueline', and has an event handler that pops up an alert (note: the event handler name means this example is jQuery - MooTools
    -				and YUI3 use different event handler names):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{fillStyle:'blue'},
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ drop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -			<li>Same example as before, but this is for MooTools, and the Endpoint can support up to 5 connections (the default is 1):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{ fillStyle:'blue' },
    -	maxConnections:5,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ onDrop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Same example again, but maxConnections being set to -1 means that the Endpoint has no maximum limit of Connections:
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", {radius:30} ],
    -	style:{ fillStyle:'blue' },
    -	maxConnections:-1,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ onDrop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Same example again, but for YUI3.  Note the drop callback is "drop:hit":
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{fillStyle:'blue'},
    -	maxConnections:-1,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ "drop:hit":function(e, ui) { alert('drop!'); } }	 	
    -}; 
    -</pre>
    -</div>				 				
    -				</li>				
    -				
    -	<li>Assign a UUID to the endpoint options created above, and add as Endpoints to "window1" and "window2":
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("window1", { uuid:"abcdefg" }, endpointOptions );
    -jsPlumb.addEndpoint("window2", { uuid:"hijklmn" }, endpointOptions );
    -</pre>
    -</div>	
    -	</li>
    -	
    -	<li>Connect the two Endpoints we just registered on "window1" and "window2":
    -<div class="code">
    -<pre>
    -jsPlumb.connect({uuids:["abcdefg", "hijklmn"]});
    -</pre>
    -</div>	
    -	</li>
    -	
    -	<li>Create a source Endpoint, register it on some element, then make some other element a Connection target:
    -<div class="code">
    -<pre>
    -var sourceEndpoint = { isSource:true, endpoint:[ "Dot", { radius:50 } ] };
    -var targetEndpoint = { endpoint:[ "Rectangle", { width:10, height:10 } ] };
    -jsPlumb.addEndpoint( "window1", sourceEndpoint );
    -jsPlumb.makeTarget( "window2", targetEndpoint );
    -</pre>
    -</div>	
    -Notice that the endpoint definition we use on the target window does not include the "isTarget:true" directive.  jsPlumb ignores that flag when
    -creating a connection using an element as the target; but if you then tried to drag another connection to the Endpoint that was created, you would
    -not be able to.  To permit that, you would set 'isTarget:true' on the targetEndpoint options defined above.
    -	
    -	</li>
    -				
    -			</ul>
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="utilityFunctions">Utility Functions</a></h3>
    -			<ul>
    -				<li>Detach window5 from all connections
    -					<div class="code">
    -						<pre>jsPlumb.detachAll("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Hide all window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.hide("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Hide all window5's connections endpoints
    -					<div class="code">
    -						<pre>jsPlumb.hide("window5", true);</pre>
    -					</div>
    -				</li>
    -				<li>Show all window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.show("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Show all window5's connections and endpoints.  Note that in the case that you call jsPlumb.show with two arguments, jsPlumb
    -				will also not make a connection visible if it determines that the other endpoint in the connection is not visible.
    -					<div class="code">
    -						<pre>jsPlumb.show("window5", hide);</pre>
    -					</div>
    -				</li>				
    -				<li>Toggle the visibility of window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.toggle("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of all of window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.repaint("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of all of window5, window6 and window11's connections
    -					<div class="code">
    -						<pre>jsPlumb.repaint( [ "window5", "window6", "window11" ] );</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of every connection
    -					<div class="code">
    -						<pre>jsPlumb.repaintEverything();</pre>
    -					</div>
    -				</li>
    -				<li>Detach every connection that the given instance of jsPlumb is managing
    -					<div class="code">
    -						<pre>jsPlumb.detachEveryConnection();</pre>
    -					</div>
    -				</li>
    -				<li>Detach all connections from "window1"
    -					<div class="code">
    -						<pre>jsPlumb.detachAllConnections("window1");</pre>
    -					</div>
    -				</li>
    -				<li>Remove the given Endpoint from element "window1", deleting its Connections. 
    -					<div class="code">
    -						<pre>jsPlumb.removeEndpoint("window1", someEndpoint);</pre>
    -					</div>
    -				</li>
    -				<li>Remove all Endpoints for the element 'window1', deleting their Connections.
    -					<div class="code">
    -						<pre>jsPlumb.removeAllEndpoints("window1");</pre>
    -					</div>
    -				</li>	
    -				<li>Removes every Endpoint managed by this instance of jsPlumb, deleting all Connections.
    -				This is the same as jsPlumb.reset(), effectively, but it does not clear out the event listeners list. 
    -					<div class="code">
    -						<pre>jsPlumb.removeEveryEndpoint();</pre>
    -					</div>
    -				</li>
    -				<li>Deletes the given Endpoint and all its Connections. 
    -					<div class="code">
    -						<pre>jsPlumb.deleteEndpoint(endpoint);</pre>
    -					</div>
    -				</li>
    -				<li>Removes every endpoint, detaches every connection, and clears the event listeners list.  Returns jsPlumb instance to its initial state.  
    -					<div class="code">
    -						<pre>jsPlumb.reset();</pre>
    -					</div>
    -				</li>	
    -				<li>Library-agnostic method to get a selector from the given selector specification.  This function is used mostly by the jsPlumb demos, to cut down on repeating code across the different libraries.  But perhaps you are writing something with jsPlumb that you also wisht to be library agnostic - this function can help you. The returned object, in each of jQuery, MooTools and YUI, has a ".each" iterator.
    -					<div class="code">
    -						<pre>var selector = jsPlumb.getSelector(".someSelector .spec");</pre>
    -					</div>					
    -				</li>
    -				<li>Set window1 to be not draggable, no matter what some jsPlumb command may request.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggable("window1", false);</pre>
    -					</div>
    -				</li>
    -				<li>Set window1 and window2 to be not draggable, no matter what some jsPlumb command may request.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggable(["window1","window2"], false);</pre>
    -					</div>
    -				</li>
    -				<li>Sets whether or not elements that are connected are draggable by default.
    -				The default for this is true.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggableByDefault(false);</pre>
    -					</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (all libraries). Passes in an on drag callback
    -				<div class="code">
    -					<pre>jsPlumb.draggable("window1");</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 and window2 as draggable elements (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(["window1","window2"]);</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable("window1");</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (jQuery)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (MooTools)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($$(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (YIU3)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(Y.all(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (jQuery)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($("#window1"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (MooTools)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($("window1"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (YUI3)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(Y.one("window1"));</pre>
    -				</div>
    -				</li>
    -			</ul>
    -		</div>					
    -		
    -		<div class="section">
    -			<h3><a id="developingJsPlumb">Advanced Topics</a></h3>
    -			<h4><a id="which">Which files are which?</a></h4>
    -			In development, jsPlumb is broken up into seven scripts:
    -				<ul>
    -					<li>- jsPlumb-x.x.x.js
    -					<p>This is the main jsPlumb engine.</p>					
    -					</li>
    -					<li>- jsPlumb-defaults-x.x.x.js
    -					<p>This contains the default Anchor, Endpoint, Connector and Overlay implementations.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-canvas-x.x.x.js
    -					<p>This contains the HTML5 canvas render code.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-svg-x.x.x.js
    -					<p>This contains the SVG render code.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-vml-x.x.x.js
    -					<p>This contains the VML render code.</p>
    -					</li>
    -					<li>- jsPlumb-connectors-statemachine-x.x.x.js
    -					<p>This State Machine connector.</p>
    -					</li>
    -					<li>- &lt;LIBRARY_PREFIX&gt;.jsPlumb-x.x.x.js
    -					<p>This contains library-specific helper methods.  jsPlumb ships with three of these - one each for jQuery, MooTools and YUI3. See below for information on how to create a new library implementation.</p>
    -					</li>
    -					<li>- jsBezier-0.3-min.js
    -					<p>These are the Bezier curve functions; they are maintained in a separate project called <a href="http://code.google.com/p/jsbezier/">jsBezier</a></p>
    -					</li>
    -				</ul>
    -			These seven files are packaged together to form the scripts that people use, for example:
    -			<ul>
    -				<li>jquery.jsPlumb-1.3.8-all.js
    -				<p>Contains jsPlumb-1.3.8.js, jsPlumb-defaults-1.3.8.js, jsPlumb-renderers-canvas-1.3.8.js, jsPlumb-renderers-svg-1.3.8.js, jsPlumb-renderers-vml-1.3.8.js, jsPlumb-connectors-statemachine-1.3.8.js, jquery.jsPlumb-1.3.8.js and jsBezier-0.3-min.js</p>
    -				</li>
    -				<li>jquery.jsPlumb-1.3.8-all-min.js
    -				<p>A minified version of the script above (minified using the YUI Compressor)</p>
    -				</li>
    -			</ul>
    -			<h4><a id="pluggableLibrarySupport">Pluggable Library Support</a></h4>
    -		Out of the box, jsPlumb can be run on top of jQuery, MooTools or YUI3.  This is achieved by
    -		delegating several core methods - tasks such as finding an element by id, finding an element's
    -		position or dimensions, initialising a draggable, etc - to the library in question.
    -		<p>To develop one of these, your test page should include the first two scripts discussed above, and
    -		then your own script containing your library specific functionality.  The existing implementations
    -		may be documented well enough for you to create your own, but contact me if you need assistance.  If you do this, it would be
    -		great to share it with everyone...</p>   
    -		<p>There is a group of people working on an ExtJS adapter currently.  There is no schedule for when this will be completed though.</p>
    -
    -			<!-- h4><a id="customConnectors">Custom Connectors</a></h4>			
    -					
    -			You can provide your own connectors if you need to. A Connector consists of two functions, which work as a pair.  First a call is made to the <em>compute</em> function:
    -
    -			<div class="code">
    -<pre>
    -this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) {
    -	...
    -	return dimensions;
    -}
    -</pre>
    -			</div>
    -			which is expected to return a list that the <em>paint</em> function can make sense of.  The first four entries in the
    -			list <strong>must be</strong> the [x,y,width,height] values for the canvas that the connector will be drawn on; jsPlumb will
    -			use this information to size the canvas prior to calling the Connector's <em>paint</em> function.  Therefore it
    -			is the Connector's responsibility to ensure that the returned dimensions describe a large enough space for
    -			the line that will be drawn on it.</p><p>The next four elements <strong>must be</strong> the coordinates of the two endpoints of the line
    -			you are going to draw.</p><p>The remainder of the items in the returned list are arbitrary, and will
    -			vary between Connector implementations; this list is passed in to a Connector's <em>paint</em> function, so each
    -			implementation will put into the list whatever it needs to paint itself.  For instance, the straight line
    -			connector only needs the [x,y] location of each end of the line it will paint, and that is one of the required entries, so
    -			it does not have to do anything extra,  whereas the Bezier connector adds the location of the two control points.  Other types of
    -			Connectors will do whatever is appropriate for their particular situation.
    -			</p>
    -			This is the method signature for the <em>paint</em> function:
    -			<div class="code">
    -				<pre>this.paint = function(dimensions, ctx) { .. }</pre>
    -			</div>
    -
    -			here, the 'dimensions' argument to the 'paint' function is the return value of the 'compute' function.  The 'ctx' argument is the Canvas context; you
    -			will do all your drawing on this.
    -			</p>
    -			To change the connector from the default, specify it in your connect call:
    -			<div class="code">
    -				<pre>jsPlumb.connect({source:'someWindow', target:'otherWindow', connector:new jsPlumb.Connectors.Straight()});</pre>
    -			</div>
    -			note that you can use shorthand for connectors if you don't need to specify parameters to it:
    -			<div class="code">
    -				<pre>jsPlumb.connect({source:'someWindow', target:'otherWindow', connector:"Straight"});</pre>
    -			</div>
    -			This works in the same way as specifying Anchors as string does.
    -<p>
    -The section below this discusses Overlays, which allow you to decorate Connectors with
    -			things such as labels or arrows or whatever else you like. Overlays can only work with Connectors that implement a few helper functions.
    -			<h4>The concept of <strong>location</strong></h4>
    -			Before discussing the helper functions you first must be across the concept of 'location' as used by this mechanism.  The <em>location</em>
    -			on a connector can be a decimal value between 0 and 1 inclusive, which marks a proportionate amount of travel <em>along the path
    -			inscribed by the Connector</em>.  For a straight line connector this is a simple piece of maths, but for Bezier connectors it's a little
    -			bit more involved.
    -			<h4>Required Helper Methods</h4>
    -			<ul>
    -				<li><strong>pointOnPath(location)</strong> - returns an [x,y] point corresponding to the given location</li>
    -				<li><strong>pointAlongPathFrom(location, distance)</strong> - returns an [x,y] point corresponding to travelling 'distance' pixels along the connector from 'location'.</li>
    -				<li><strong>gradientAtPoint(location)</strong> - returns the connector's gradient at the given location.  For linear connectors such as Straight this is constant, but for Bezier connectors the gradient changes continually.</li>
    -				<li><strong>perpendicularToPathAt(location, distance, length)</strong> - returns a line that is perpendicular to (and centered on) the connector at 'distance' from the given location, with length 'length'.</li>
    -			</ul>
    -</p>
    -<h4><a id="customEndpoints">Custom Endpoints</a></h4>
    -<p>To create your own Endpoint implementation, you need to implement a single method:
    -				<div class="code"><pre>paint : function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { ... }</pre></div>
    -				The arguments to this method are as follows:
    -				<ul>
    -					<li>anchorPoint - [x,y] location of the anchor point on screen</li>
    -					<li>orientation - [x,y] hints for the general direction the anchor points to</li>
    -					<li>canvas - the canvas to draw into</li>
    -					<li>endpointStyle - Javascript object containing style directives as discussed above.  The contents of this are arbitrary, so if you write a new Endpoint that needs some extra settings, you can add them no hassle.</li>
    -					<li>connectorPaintStyle - the style being used to paint the associated Connector.</li>
    -				</ul>
    -			</p>
    -			<p>
    -				It is your responsibility to size and locate the canvas to suit your needs. jsPlumb provides the following
    -				helper method to assist you:
    -				<div class="code"><pre>jsPlumb.sizeCanvas(canvas, x, y, width, height);</pre></div>
    -				Allows you to locate the canvas on screen and to size it.
    -			</p>
    -<h4><a id="customOverlays">Custom Overlays</a></h4>
    -			Overlays can only work with Connectors that implement the methods <strong>pointOnPath</strong>, <strong>pointAlongPathFrom</strong>,
    -			<strong>gradientAtPoint</strong>, and <strong>perpendicularToPathAt</strong>.  These are discussed in more detail in the Custom Connectors section above.  Both of the Connectors
    -			that come with jsPlumb - Straight and Bezier -implement these methods; if you write a custom connector, or have written a custom connector, you will need to supply
    -			them.   
    -			<h4>Overlay Interface</h4>
    -			An Overlay is required to implement two methods in order to be usable by jsPlumb:
    -			<ul>
    -				<li><strong>computeMaxSize(connector, context)</strong> - returns an integer value indicating the larger of this overlay's width and height.  It is used by jsPlumb to ensure that the canvas is large enough to accomodate the overlay.  The examples below should help to clarify this.</li>
    -				<li><strong>draw(connector, ctx)</strong> - draws the overlay.  What happens in this method is up to the given implementation.</li>
    -			</ul>
    -			<h4>computeMaxSize methods</h4>
    -			This is the Arrow overlay's computeMaxSize method:
    -			<div class="code">
    -<pre>
    -this.computeMaxSize = function() { return width; }
    -</pre>			
    -			</div>
    -Here, <em>width</em> is a private member of Arrow that indicates the width of the arrow's tail.  So the Arrow overlay reports that figure as the width it needs.					
    -Contrast this with the Label Overlay's computeMaxSize method:
    -		<div class="code">
    -<pre>
    -this.computeMaxSize = function(connector, ctx) {
    -  	if (labelText) {
    -  		ctx.save();
    -        if (self.labelStyle.font) ctx.font = self.labelStyle.font;
    -         var t = ctx.measureText(labelText).width;			            
    -		// a fake text height measurement: use the width of upper case M
    -		var h = ctx.measureText("M").width;					
    -		labelPadding = self.labelStyle.padding || 0.25;
    -		labelWidth = t + (2 * t * labelPadding);
    -		labelHeight = h + (2 * h * labelPadding);
    -		ctx.restore();
    -		return Math.max(labelWidth, labelHeight);
    -  	}
    - 	return 0;
    - };
    -</pre>			
    -			</div>
    -The Label overlay has to use the context to determine how big it will be on screen.
    -
    -<h4>draw methods</h4>
    -To give you a taste for how you can interact with a connector, consider the first few lines of the Arrow overlay's draw method:
    -<div class="code">
    -<pre>
    -this.draw = function(connector, ctx) {
    -	// this is the arrow head position
    -	var hxy = connector.pointAlongPathFrom(self.loc, length / 2);		
    -	// this is the center of the tail
    -	var txy = connector.pointAlongPathFrom(self.loc, -length / 2), tx = txy[0], ty = txy[1];
    -	// this is the tail vector
    -	var tail = connector.perpendicularToPathAt(self.loc, -length / 2, width);
    -	
    -	...
    -</pre>
    -</div>
    -	<p>The first two calls to the connector use the <strong>pointAlongPathFrom</strong> method, which passes <em>self.loc</em> as the location of the
    -	point on the path, and <em>length / 2</em> as the number of pixels along the path to traverse.</p>
    -	<p><em>self.loc</em> is Arrow's internal member describing the location of the overlay, and it is a decimal (between 0 and 1 inclusive) that points to
    -	a location at some distance along the path inscribed by the connector.  So these first two calls get us [x,y] locations of points on 
    -	the connector that mark the head and tail points for the arrow.  
    -	</p>		
    -	<p>
    -	The <strong>connector.perpendicularToPathAt(self.loc, -length / 2, width)</strong> call returns a line description of a line that is perpendicular to, and centered on, the connector
    -	at "-length / 2" pixels from the given point, with the given width (width is an internal member of Arrow).  At this point, the Arrow object has the three main points it
    -	needs in order to draw itself.
    -	</p>
    -		</div-->
    -</div>
    -
    -</body>
    -
    -</html>
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.7/index.html b/demo/doc/archive/1.3.7/index.html
    deleted file mode 100644
    index 0584a0977..000000000
    --- a/demo/doc/archive/1.3.7/index.html
    +++ /dev/null
    @@ -1,82 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.8 documentation</title>
    -		<link rel="stylesheet" target="contentFrame" href="/mp.css"></link>
    -		<link rel="stylesheet" target="contentFrame" href="jsPlumbDoc.css"></link>
    -	</head>
    -	<body>
    -		<div class="index section">
    -			<h3>jsPlumb 1.3.8</h3>
    -			<ul>
    -				<li><a target="contentFrame" href="content.html#summary">Summary</a></li>
    -				<li><a target="contentFrame" href="content.html#changes">Changes since version 1.3.6</a></li>
    -				
    -				<li><h4>Setup</h4></li>
    -				<li><a target="contentFrame" href="content.html#imports">Required Imports</a></li>
    -				<li><a target="contentFrame" href="content.html#renderMode">Render Mode</a></li>
    -				<li><a target="contentFrame" href="content.html#initializing">Initializing jsPlumb</a></li>
    -				<li><a target="contentFrame" href="content.html#unload">Unloading jsPlumb</a></li>
    -				<li><a target="contentFrame" href="content.html#multipleInstances">Multiple jsPlumb Instances</a></li>
    -				<li><a target="contentFrame" href="content.html#zIndex">Z-index Considerations</a></li>
    -				<li><a target="contentFrame" href="content.html#containerDefault">Where does jsPlumb add elements?</a></li>
    -				
    -				<li><h4>Introduction</h4></li>
    -				<li><a target="contentFrame" href="content.html#jsPlumbBasics">Basic Concepts</a></li>
    -				<li><a target="contentFrame" href="content.html#anchors">Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#staticAnchors">Static Anchors</a></li>				
    -				<li><a target="contentFrame" href="content.html#dynamicAnchors">Dynamic Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#continuousAnchors">Continuous Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#definitions">Connector, Endpoint &amp; Overlay Definitions</a></li>
    -				<li><a target="contentFrame" href="content.html#connectors">Connectors</a></li>
    -				<li><a target="contentFrame" href="content.html#endpoints">Endpoints</a></li>
    -				<li><a target="contentFrame" href="content.html#overlays">Overlays</a></li>
    -				<li><a target="contentFrame" href="content.html#defaults">Defaults</a></li>								
    -				
    -				<li><h4>Connections</h4></li>
    -				<li><a target="contentFrame" href="content.html#simpleConnections">Programmatic Connections</a></li>
    -				<li><a target="contentFrame" href="content.html#draggableConnections">Connections using Drag and Drop</a></li>
    -				<li><a target="contentFrame" href="content.html#sourcesAndTargets">Elements as Sources &amp; Targets</a></li>
    -				<li class="sub"><a target="contentFrame" href="content.html#makeTarget">makeTarget</a></li>
    -				<li class="sub"><a target="contentFrame" href="content.html#makeSource">makeSource</a></li>
    -				<li><a target="contentFrame" href="content.html#dragOptions">Drag Options</a></li>				
    -				<li><a target="contentFrame" href="content.html#dropOptions">Drop Options</a></li>
    -				<li><a target="contentFrame" href="content.html#dragAndDropScope">Drag and Drop Scope</a></li>
    -				
    -				<li><h4>Interceptors</h4></li>				
    -				<li><a target="contentFrame" href="content.html#beforeDrop">Before Drop</a></li>
    -				<li><a target="contentFrame" href="content.html#beforeDetach">Before Detach</a></li>
    -				
    -				<li><h4>Appearance</h4></li>
    -				<li><a target="contentFrame" href="content.html#paintstyles">Paint Styles</a></li>
    -				<li><a target="contentFrame" href="content.html#hoverpaintstyles">Hover Paint Styles</a></li>
    -				<li><a target="contentFrame" href="content.html#gradients">Gradients</a></li>				
    -				
    -				<li><h4>Miscellaneous</h4></li>
    -				<li><a target="contentFrame" href="content.html#cssclasses">CSS Class Reference</a></li>
    -				<li><a target="contentFrame" href="content.html#animation">Animation</a></li>
    -				<li><a target="contentFrame" href="content.html#connectionInfo">Retrieving Connection Information</a></li>
    -				<li><a target="contentFrame" href="content.html#selectConnections">Selecting Connections</a></li>
    -				
    -				<li><h4>Events</h4></li>
    -				<li><a target="contentFrame" href="content.html#events">jsPlumb Events</a></li>
    -				<li><a target="contentFrame" href="content.html#connectionEvents">Connection Events</a></li>
    -				<li><a target="contentFrame" href="content.html#endpointEvents">Endpoint Events</a></li>
    -				<li><a target="contentFrame" href="content.html#overlayEvents">Overlay Events</a></li>
    -				
    -				<li><h4>Examples</h4></li>				
    -				<li><a target="contentFrame" href="content.html#examples">Programmatic API Examples</a></li>
    -				<li><a target="contentFrame" href="content.html#dragAndDropExamples">Draggable Connections Examples</a></li>
    -				<li><a target="contentFrame" href="content.html#utilityFunctions">Utility Functions</a></li>				
    -				
    -				<li><h4>Develop</h4></li>
    -				<li><a target="contentFrame" href="content.html#which">Which files are which?</a></li>
    -				<li><a target="contentFrame" href="content.html#pluggableLibrarySupport">Pluggable Library Support</a></li>
    -				<!-- li><a target="contentFrame" href="content.html#customConnectors">Custom Connectors</a></li>
    -				<li><a target="contentFrame" href="content.html#customEndpoints">Custom Endpoints</a></li>
    -				<li><a target="contentFrame" href="content.html#customOverlays">Custom Overlays</a></li-->
    -			</ul>
    -			<strong>This document refers to release 1.3.8 of jsPlumb.</strong>  
    -			<strong>04 March 2012</strong>
    -		</div>		
    -	</body>
    -</html>
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.7/jsPlumbDoc.css b/demo/doc/archive/1.3.7/jsPlumbDoc.css
    deleted file mode 100644
    index bbe50c8ee..000000000
    --- a/demo/doc/archive/1.3.7/jsPlumbDoc.css
    +++ /dev/null
    @@ -1,126 +0,0 @@
    -body { 
    -	background-color:white; font-size:90%;
    -	background-image:url(../img/dynamicAnchorBg.jpg); 
    -}
    -/* docs */
    -.code { background-color:#e4e5ef; 
    -border:1px solid black; margin:1em; padding:0.8em; 
    -overflow:auto;
    -font-family:courier; 
    -font-size:8pt;
    -}
    -.section { border-bottom: 1px dotted black; padding:1em;font-family:helvetica;background-color:white;color:#333;}
    -.menu { 
    -	position:fixed; 
    -	right:10em; top:1em;
    -	background-color:white;
    -	padding:0.3em;
    -	opacity:0.8;
    -	z-index:5000;
    -	filter:alpha(opacity=80);
    -		box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -a {text-decoration:none;color:#05f;font-weight:bold;font-family:helvetica;}
    -a:hover {text-decoration:underline;}
    -.window, .label { 
    -	cursor:pointer;
    -	box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -#explanation { 
    -	background-color:white;
    -	color:black;
    -	border:0.25em solid #ddd; 
    -	position:absolute;
    -	right:3.5em;
    -	top:5em;
    -	width:22em;
    -	font-size:100%;
    -	-moz-border-radius:1em;
    -	border-radius:1em;
    -	padding:1em;
    -	z-index:1000;
    -	opacity:0.9;
    -	filter:alpha(opacity=90); 
    -	font-family:helvetica;
    -		box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -.selected {
    -	color:rgb(189,11,11);
    -}
    -
    -
    -/*  these styles are what jsPlumb attaches to the various elements it creates.
    -._jsPlumb_connector {
    -border:1px solid blue;
    -}
    -._jsPlumb_endpoint {
    -border:1px solid green;
    -}
    -._jsPlumb_overlay {
    -border:1px solid yellow;
    -}
    -*/
    -
    -
    -.section li {
    -list-style-type:none;
    -}
    -
    -li.bullet {
    -	list-style-type:circle;
    -	}
    -
    -
    -.section {
    -padding-left:2em;
    -font-size:10pt;
    -}
    -.index.section {
    -padding-left:0;
    -}
    -
    -.index {
    -width:20em;
    -font-size:9pt;
    -float:left;
    -background-color:#e4eeee;
    -overflow:auto;
    -padding-left:0.5em;
    -}
    -
    -.index li {
    -list-style-type:none;
    -}
    -
    -.index ul, .index h3 {
    -padding-left:1.5em;
    -
    -}
    -
    -#demo {
    -width:80%;
    -height:50em;
    -margin-left:3em;
    -margin-top:3em;
    -position:relative;
    -}
    -
    -.sub {
    -margin-left:1em;
    -}
    -
    -.table td {
    -	text-align:center;
    -}
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.7/usage.html b/demo/doc/archive/1.3.7/usage.html
    deleted file mode 100644
    index 0608fd36e..000000000
    --- a/demo/doc/archive/1.3.7/usage.html
    +++ /dev/null
    @@ -1,10 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.8 - documentation</title>		
    -	</head>
    -	
    -	<frameset cols="250,*">
    -  		<frame src="index.html" />
    -  		<frame src="content.html" name="contentFrame" />
    -	</frameset>	
    -</html>
    diff --git a/demo/doc/archive/1.3.8/content.html b/demo/doc/archive/1.3.8/content.html
    deleted file mode 100644
    index 4280574e1..000000000
    --- a/demo/doc/archive/1.3.8/content.html
    +++ /dev/null
    @@ -1,3031 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.8 - documentation</title>
    -		<link rel="stylesheet" href="/mp.css">
    -		<link rel="stylesheet" href="jsPlumbDoc.css">
    -	</head>
    -	<body>
    -
    -<div class="menu">
    -	<a href="http://jsplumb.org" class="mplink" target="_top">view demo</a>
    -	&nbsp;|&nbsp;<a href="mailto:simon.porritt@gmail.com" class="mplink">contact me</a>
    -	&nbsp;|&nbsp;<a href="http://jsplumb.tumblr.com" class="mplink">jsPlumb on Tumblr</a>	
    -</div>
    -
    -<!--
    -GWT
    -
    -http://fishfilosophy.blogspot.com/2011/12/how-to-connect-gwt-widgets-using.html?spref=tw
    -
    --->
    -
    -<div class="content">
    -		<div class="section">
    -			<h3><a id="summary">Summary</a></h3>
    -			jsPlumb allows you to connect elements on the screen using SVG, Canvas or VML, depending on the capabilities of the browser.
    -			<p>
    -				It can be used with jQuery, MooTools or YUI3 (or another library of your choice if you feel like implementing an adapter for it).
    -			</p>  
    -
    -			<h5>Browser Compatibility</h5>
    -			<p>jsPlumb 1.3.8 runs on everything from IE6 up.  There are some caveats, though, because of various browser/library bugs:
    -				<ul>
    -					<li class="bullet">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.
    -					<li class="bullet">Safari 5.1 has an SVG bug that prevents mouse events from being passed through the transparent area of an SVG element</li>
    -					<li class="bullet">MooTools has a bug when using SVG in Firefox 11</li>
    -				<ul>
    -				
    -			</p>
    -		</div>
    -		
    -		<div class="section">		
    -			<h3><a id="changes">Changes between 1.3.8 and 1.3.7</a></h3>
    -			1.3.8 is mostly a bugfix release, but it is notable for the fact that VML is now enabled for IE9.
    -			<h5>Backwards Compatibility</h5>
    -			There are no backwards compatibility issues between 1.3.8 and 1.3.7, but note that now that VML is supported in IE9, there is a different algorithm in the setRenderMode method. For each requested type, jsPlumb will attempt to use that type, trying others if it is not available, in the following order:
    -			<p>
    -				<ul>
    -					<li>CANVAS : Canvas, SVG, VML</li>
    -					<li>SVG : SVG, Canvas, VML   (remains the default)</li>
    -					<li>VML : VML, SVG, Canvas</li>
    -				</ul>
    -			</p>
    -			<h5>New Features</h5>
    -				<ul>
    -					<li class="bullet">VML renderer is now supported in IE9</li>
    -					<li class="bullet">Hover mode is suspended whenever an element is being dragged. This prevents a lot of random flashing in the UI when the user is dragging quickly.</li>
    -				</ul>
    -
    -			<h5>Issues Resolved</h5>
    -			<ul>
    -				<li class="bullet">
    -					<strong>207</strong> - pass original event back in connection detached callback
    -				</li>
    -				<li class="bullet">
    -					<strong>215</strong> - dragged connection did not get parameters from target endpoint
    -				</li>
    -				
    -				<li class="bullet">
    -					<strong>217</strong> - makeSource's onComplete callback does not get fired
    -				</li>
    -				
    -				<li class="bullet">
    -					<strong>218</strong> - IE max stylesheet limit
    -				</li>
    -
    -				<li class="bullet">
    -					<strong>219</strong> - connections made with makeSource and parent option were duplicated internally, skewing results from "select" and causing duplicate mouse events with the canvas renderer.
    -				</li>
    -				<li class="bullet">
    -					<strong>222</strong> - allow VML in IE9
    -				</li>
    -				<li class="bullet">
    -					<strong>223</strong> - extraneous IDs being created and assigned
    -				</li>
    -				<li class="bullet">
    -					<strong>226</strong> - image endpoint does not clean up image element from DOM on destroy.
    -				</li>
    -				<li class="bullet">
    -					<strong>227</strong> - maxConnections ignored by makeTarget method
    -				</li>
    -				<li class="bullet">
    -					<strong>230</strong> - array test fails when jsPlumb called from GWT
    -				</li>
    -			</ul>
    -
    -
    -			
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="init">Setup</a></h3>
    -
    -            <h4><a id="imports">Required Imports</a></h4>
    -			<h4>jQuery</h4>
    -			    <ul>
    -				    <li class="bullet">jQuery 1.3.x or higher.</li>
    -				    <li class="bullet">jQuery UI 1.7.x or 1.8.x (if you wish to support drag and drop).</li>
    -			    </ul>
    -			    <div class="code">
    -<pre>&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/jquery-ui.min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/jquery.jsPlumb-1.3.8-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			<h4>MooTools</h4>
    -				<ul>
    -					<li class="bullet">MooTools Core 1.2.4 or higher. jsPlumb has been tested with 1.2.4 and 1.3.3.</li>
    -					<li class="bullet">Drag.Move from MooTools More 1.2.4.4 or higher (if you wish to support drag and drop).</li>
    -				</ul>
    -			<div class="code">
    -			<pre>&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/mootools/1.3.3/mootools-yui-compressed.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO_MOO_TOOLS_MORE_1_3_2_1"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/mootools.jsPlumb-1.3.8-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			<h4>YUI3</h4>
    -			<ul>
    -				<li class="bullet">YUI 3.3.x.  jsPlumb has been tested on 3.3.0 only; it is possible other 3.x.x versions will work.</li>
    -			</ul>
    -			<div class="code">
    -			<pre>&lt;script type="text/javascript" src="http://yui.yahooapis.com/3.3.0/build/simpleyui/simpleyui-min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/yui.jsPlumb-1.3.8-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			
    -			<h4><a id="renderMode">Render Mode</a></h4>
    -			jsPlumb can use SVG, HTML5 Canvas elements or VML to render the various objects it adds to the display.  Most modern browsers support Canvas and SVG; IE&lt;9 browsers support neither.
    -			<p>
    -				By default, jsPlumb attempts to use <strong>jsPlumb.SVG</strong> as the render mode (prior to 1.3.4, the default renderer was Canvas), falling back to <strong>jsPlumb.VML</strong> if the 
    -				browser is IE&lt;9.  You can change the render mode by making this call:  
    -			</p>
    -			<div class="code">
    -<pre>jsPlumb.setRenderMode(jsPlumb.CANVAS);</pre>			
    -			</div>
    -			if, for some reason, you set the render mode to be <strong>jsPlumb.VML</strong> but you're in any browser other than IE, jsPlumb will use SVG.
    -			<p>
    -			The <strong>jsPlumb.setRenderMode</strong> method returns you the render mode that jsPlumb actually ended up setting.  Valid values for setRenderMode are:
    -			</p>
    -			<ul>
    -				<li class="bullet">jsPlumb.CANVAS</li>
    -				<li class="bullet">jsPlumb.SVG</li>
    -				<li class="bullet">jsPlumb.VML</li>
    -			</ul>			
    -			<h5>Performance Considerations</h5>
    -			<p>
    -			SVG uses less memory than Canvas and has support for mouse events built in, so I recommend using
    -			SVG unless you have a really good reason for wanting Canvas. Of course, you might consider that horrendous Safari bug a really good reason for wanting Canvas.
    -			</p>
    -			
    -			<h4><a id="initializing">Initializing jsPlumb</a></h4>
    -			You should not start making calls to jsPlumb until the DOM has been initialized - perhaps no surprises there.  With YUI, though, the asynchronous
    -			nature of the script loading process means that you are not guaranteed that jsPlumb is ready as soon as the DOM is.  To handle this,
    -			you should bind to the "ready" event on jsPlumb (or the instance of jsPlumb you are working with):
    -			<div class="code">
    -			<pre>jsPlumb.bind("ready", function() {
    -...	
    -		
    -// your jsPlumb related init code goes here
    -
    -...
    -
    -});						
    -			</pre>
    -			</div>
    -			There's a helper method that can save you approximately 9 (count them!) characters:
    -			<div class="code">
    -			<pre>jsPlumb.ready(function() {
    -...	
    -		
    -// your jsPlumb related init code goes here
    -
    -...
    -
    -});						
    -			</pre>
    -			</div>
    -			<p>
    -			For jQuery and MooTools you do not actually need to do this; by the time the DOM ready event is fired in each of those libraries you can be sure 
    -			all the JS we need has been loaded.  But in terms of explicitly calling out that you are waiting for jsPlumb, it seems like a good practice to bind to the "ready" event.
    -			</p>
    -			<p>
    -			If you bind to the "ready" event after jsPlumb has already been initialized, your callback will be executed immediately.
    -			</p>			
    -
    -				
    -<h4><a id="multipleInstances">Multiple jsPlumb instances</a></h4>
    -			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 <strong>getInstance</strong> method, for example:
    -<div class="code">
    -<pre>
    -var firstInstance = jsPlumb.getInstance();
    -</pre>
    -</div>
    -The variable 'firstInstance' can now be treated exactly as you would treat the 'jsPlumb' variable - you can set
    -defaults, call the connect method, whatever:
    -<div class="code">
    -<pre>
    -firstInstance.importDefaults({
    -  Connector : [ "Bezier", { curviness: 150 } ],
    -  Anchors : [ "TopCenter", "BottomCenter" ]
    -});
    -
    -firstInstance.connect({source:"element1", target:"element2", scope:"someScope" });
    -</pre>
    -</div>
    -
    -<strong>getInstance</strong> optionally takes an object that provides the defaults:
    -<div class="code">
    -<pre>
    -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" });
    -</pre>
    -</div>
    -
    -		<h4><a id="zIndex">Z-index Considerations</a></h4>
    -		<p>
    -			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. 
    -		</p>  
    -		<p>
    -			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).  			
    -		</p>
    -		<p>
    -			To help you organise z-indices correctly, jsPlumb adds a CSS class to each type of
    -			element it adds. They are as follows:			
    -		</p>
    -		<table style="color:black;width:70%;font-size:90%;">
    -			<tr><td><strong>type of element</strong></td><td><strong>class added</strong></td></tr>
    -			<tr><td>Endpoint</td><td>_jsPlumb_endpoint</td></tr>
    -			<tr><td>Connector</td><td>_jsPlumb_connector</td></tr>
    -			<tr><td>Overlay</td><td>_jsPlumb_overlay</td></tr>
    -		</table>
    -
    -		<h4><a id="containerDefault">Where does jsPlumb add elements?</a></h4>
    -		<p>
    -			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.
    -		</p>
    -		<p>
    -			Prior to 1.3.0, jsPlumb added everything to the end of the <em>body</em> element. This
    -			has the advantage of being the most flexible arrangement in terms of supporting what
    -			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!   
    -		</p>
    -		<p>
    -			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 (<strong>not</strong> 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.
    -		</p>
    -		
    -		<p>
    -			In general, the default behaviour means that you cannot do things like this:
    -		</p>
    -		<div class="code">
    -<pre>
    -&lt;body&gt;
    -	&lt;div id="container0"&gt;
    -		&lt;div id="node0"&gt;&lt;/div&gt;
    -	&lt;/div&gt;
    -	&lt;div id="container1"&gt;
    -		&lt;div id="node1"&gt;&lt;/div&gt;
    -	&lt;/div&gt;
    -&lt;/body&gt;
    -
    -&lt;script type="text/javascript"&gt;
    -
    -	var e0 = jsPlumb.addEndpoint("node0"),
    -	e1 = jsPlumb.addEndpoint("node1");
    -
    -	jsPlumb.connect({ source:e0, target:e1 });
    -&lt;/script&gt;
    -</pre>		
    -		</div>
    -<p>
    -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):  
    -</p>
    -		<div class="code">
    -<pre>
    -&lt;body&gt;
    -	&lt;div id="container0"&gt;
    -	&lt;/div&gt;
    -	&lt;div id="container1"&gt;
    -	&lt;/div&gt;
    -&lt;/body&gt;
    -
    -&lt;script type="text/javascript"&gt;
    -
    -	var e0 = jsPlumb.addEndpoint("container0"),
    -	e1 = jsPlumb.addEndpoint("container1");
    -
    -	jsPlumb.connect({ source:e0, target:e1 });
    -&lt;/script&gt;
    -</pre>		
    -		</div>
    -	now 'e0' and 'e1' have the same parent - the <em>body</em> element. 
    -	
    -	<p>Remember that you can use the <strong>anchor</strong> parameter to an <strong>addEndpoint</strong>
    -	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. 
    -	</p>									
    -	<p><strong>Note regarding the drawEndpoints option on jsPlumb.connect</strong>: 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). 
    -	</p>
    -	
    -	<h5>Overriding the default behaviour</h5>
    -	<p>
    -From version 1.3.3, the <strong>container</strong> 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 <strong>jsPlumb.Defaults.Container</strong> property, or you can set a <strong>container</strong> parameter on calls to <strong>addEndpoint</strong> or <strong>connect</strong> (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:
    -	</p>
    -		
    -Set a container to use as the default container, using a jQuery selector (you can supply MooTools/YUI selectors if that's the library you're using), and then add an Endpoint.  The canvas (or SVG/VML element) created will be a child of the document body:
    -		<div class="code">
    -<pre>
    -jsPlumb.Defaults.Container = $("body");
    -...
    -jsPlumb.addEndpoint(someDiv, { endpoint options });
    -</pre>		
    -		</div>
    -		
    -			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":
    -		<div class="code">
    -<pre>
    -jsPlumb.Defaults.Container = "containerId";
    -...
    -jsPlumb.connect({ source:someDiv, target:someOtherDiv });
    -</pre>		
    -		</div>
    -		
    -		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":
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({ source:someDiv, target:someOtherDiv, container:$("#containerId") });
    -</pre>		
    -		</div>
    -				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":
    -		<div class="code">
    -<pre>
    -jsPlumb.addEndpoint(someDiv, { ..., container:"containerId" });
    -</pre>		
    -		</div>
    -
    -		<h4><a id="elementDragging">Element Dragging</a></h4>
    -		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 <strong>jsPlumb.draggable(...)</strong>:
    -<div class="code">
    -<pre>jsPlumb.draggable("elementId");</pre>
    -</div>
    -You can also pass a selector from your library in to this method:
    -<div class="code">
    -<pre>jsPlumb.draggable($(".someClass");</pre>
    -</div>
    -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.  A common requirement when using jQuery, for instance, is to set the containment of elements that you are making draggable.  Here's how you would tell jsPlumb to initialise some set of elements to be draggable but only inside their parent:
    -<div class="code">
    -<pre>jsPlumb.draggable($(".someClass", {
    -	containment:"parent"
    -});</pre>
    -</div>
    -		<h5>Dragging nested elements</h5>
    -jsPlumb takes nesting into account when handling draggable elements. For example, say you have this markup:
    -<div class="code">
    -<pre>
    -&lt;div id="container"&gt;
    -  &lt;div class="childType1"&gt;&lt;/div&gt;
    -  &lt;div class="childType2"&gt;&lt;/div&gt;
    -&lt;/div&gt;
    -</pre>
    -</div>
    -..and then you connect one of those child divs to something, and make "container" draggable:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:$("#container .childType1"),
    -	target:"somehere else"
    -});
    -
    -jsPlumb.draggable("container");
    -</pre>
    -</div>
    -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.
    -
    -
    -		</div>
    -				
    -		<div class="section">
    -			<h3><a id="jsPlumbBasics">Basic Concepts</a></h3>			
    -	
    -			jsPlumb is all about connecting things together, so the core abstraction in jsPlumb is the <strong>Connection</strong> object, which is itself
    -			broken down into these four concepts:
    -
    -			<ul>
    -				<li><strong>Anchor</strong> - a location, relative to an element's origin, at which an Endpoint can exist. You do not create these yourself; you 
    -				supply hints to the various jsPlumb functions, which create them as needed.  They have no visual representation; they are a logical position only. Anchors can be referenced by name, for the Anchors that jsPlumb ships with, or a four element array describing [ x, y, x orientation, y orientation ]. 
    -				See the <a href="#anchors">anchors</a> section for more detail.<br/><br/></li>
    -				<li><strong>Endpoint</strong> - the visual representation of one end of a Connection.  You can create and attach these to elements yourself, which you are required to do to support drag and drop, or have jsPlumb create them when creating a Connection programmatically using jsPlumb.connect(...).  You can also join two Endpoints programmatically, by passing them as arguments to jsPlumb.connect(...).<br/><br/></li>				
    -				<li><strong>Connector</strong> - the visual representation of the line connecting two elements in the page.  jsPlumb has four types of these 
    -				available as defaults - a Bezier curve, a straight line, 'flowchart' connectors and 'state machine connectors. You do not interact with Connectors; 
    -				you just specify definitions of them when you need to - see <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions</a> <br/><br/></li>
    -				<li><strong>Overlay</strong> - a UI component that is used to decorate a Connector, such as a Label, Arrow, etc.<br/><br/></li>
    -			</ul>
    -						
    -			<p>One Connection is made up of two Endpoints, a Connector, and zero or more Overlays working together to join two elements.</p>
    -			<p>jsPlumb's public API exposes only Connection and Endpoint, handling the creation and configuration of everything else internally. But you still need to be across the concepts encapsulated by Anchor, Connector and Overlay.
    -			</p>
    -			</div>
    -			
    -			<div class="section">
    -			<h3><a id="anchors">Anchors</a></h3>			
    -	
    -			An Anchor models the notion of where on an element a Connector should connect.  There are three main types of Anchors:
    -			<ul>
    -				<li class="bullet">'Static' anchors - these are fixed to some point on an element and do not move.  They can be specified using a string to
    -				identify one of the defaults that jsPlumb ships with, or an array describing the location (see below)</li>
    -				<li class="bullet">'Dynamic' anchors - these are lists of Static anchors from which jsPlumb picks the most appropriate one each time a Connection
    -				is painted.  The algorithm used to determine the most appropriate anchor picks the one that is closest to the center of the other element in
    -				the Connection. A future version of jsPlumb might support a pluggable algorithm to make this decision.</li>
    -				<li class="bullet">'Continuous' anchors - These anchors, introduced in 1.3.4, are not fixed to any specific loction; they are assigned to one of
    -				the four faces of an element depending on that element's orientation to the other element in the associated Connection.  Continuous anchors are slightly more computationally intensive than Static or Dynamic anchors because jsPlumb is required to calculate the position of every Connection during a paint cycle, rather than just Connections belonging to the element in motion.  These anchors were designed for use with the StateMachine connector that was also introduced in 1.3.4, but will work with any combination of Connector and Endpoint.</li> 
    -			</ul>  
    -					
    -			<h4><a id="staticAnchors">Static Anchors</a></h4>		
    -			jsPlumb has nine default anchor locations you can use to specify where the Connectors connect to elements: these are the four corners of an element,
    -			the center of the element, and the midpoint of each edge of the element:  
    -			<ul>
    -				<li class="bullet">TopCenter</li>
    -				<li class="bullet">TopRight</li>
    -				<li class="bullet">RightMiddle</li>
    -				<li class="bullet">BottomRight</li>
    -				<li class="bullet">BottomCenter</li>
    -				<li class="bullet">BottomLeft</li>
    -				<li class="bullet">LeftMiddle</li>
    -				<li class="bullet">TopLeft</li>
    -			</ul>
    -			
    -Each of these string representations is just a wrapper around the underlying array-based syntax  [x, y, dx, dy], where x and y are coordinates in the interval [0,1] specifying the position of the
    -anchor, and dx and dy are coordinates in the interval [-1, 1] specifying the orientation of the curve incident to the anchor. For example, [0, 0.5, -1, 0] defines a "LeftCenter" anchor with a connector curve that emanates leftward from the anchor. Similarly, [0.5, 0, 0, -1] defines a "CenterTop" anchor with a connector curve emanating upwards. 	
    -
    -
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:"BottomCenter", ... }); 
    -</pre>
    -</div>
    -is identical to:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:[ 0.5, 1, 0, 1 ], ... }); 
    -</pre>
    -</div>		
    -In addition to supplying the location and orientation of an anchor, you can optionally supply two more parameters that define an offset in pixels from the given location.  Here's the anchor specified above, but with a 50 pixel offset below the element in the y axis:	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:[ 0.5, 1, 0, 1, 0, 50 ], ... }); 
    -</pre>
    -</div>		
    -
    -		<h4><a id="dynamicAnchors">Dynamic Anchors</a></h4>
    -	These are Anchors that can be positioned in one of a number of locations, choosing the one that is most appropriate each time something moves or is painted in the UI.
    -	<p>
    -	There is no special syntax to creating a DynamicAnchor; you just provide an array of individual Static Anchor specifications, eg:
    -	</p>
    -<div class="code">
    -<pre>
    -var dynamicAnchors = [ [ 0.2, 0, 0, -1 ],  [ 1, 0.2, 1, 0 ], 
    -			   [ 0.8, 1, 0, 1 ], [ 0, 0.8, -1, 0 ] ];
    -
    -jsPlumb.connect({...., anchor:dynamicAnchors, ... }); 			   
    -</pre>
    -</div>
    -<h4>	Default Dynamic Anchor</h4>
    -jsPlumb provides a dynamic anchor called "AutoDefault" that chooses from TopCenter, RightMiddle, BottomCenter and LeftMiddle:
    -<div class="code">
    -<pre>jsPlumb.connect({...., anchor:"AutoDefault", ... });</pre>
    -</div>
    -
    -<h4>	Location Selection</h4>
    -The initial implementation of the algorithm that decides which location to choose just calculates which location is closest to the center of the 
    -other element in the connection.  It is possible that future versions of jsPlumb could support more sophisticated choice algorithms, if the need arose.
    -<h4>Draggable Connections</h4>
    -Dynamic Anchors and Draggable Connections can interoperate: jsPlumb locks the position of a dynamic anchor when you start to drag a connection from it,
    -and unlocks it once the connection is either established or abandoned. At that point you may see the position of the dynamic anchor change, as jsPlumb
    -optimises the connection.  <p>You can see this behaviour in the <a href="../html/jquery/draggableConnectorsDemo.html" target="_blank">draggable connections</a> demonstration, when
    -you drag a connection from the blue endpoint on window 1 to the blue endpoint on window 3 - the connection is established and then window 1's blue
    -endpoint jumps down to a location that is closer to window 3.</p>
    -
    -
    -	<h4><a id="continuousAnchors">Continuous Anchors</a></h4>
    -	As discussed above, these are anchors whose positions are calculated by jsPlumb according to the orientation between elements in a Connection, and also how
    -	many other Continuous anchors happen to be sharing the element.  You specify that you want to use Continuous anchors using the string syntax you would use
    -	to specify one of the default Static Anchors, for example:
    -	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:someDiv,
    -	target:someOtherDiv,
    -	anchor:"Continuous"
    -});
    -</pre>
    -</div>
    -
    -Note in this example I specified only "anchor", rather than "anchors" - jsPlumb will use the same spec for both anchors.  But I could have said this:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:someDiv,
    -	target:someOtherDiv,
    -	anchors:["BottomCenter", "Continuous"]
    -});
    -</pre>
    -</div>	 	  
    -..which would have resulted in the source element having a Static Anchor at BottomCenter.  In practise, though, it seems the Continuous Anchors work best if both 
    -elements in a Connection are using them.
    -<p>Note also that Continuous Anchors can be specified on <strong>addEndpoint</strong> calls:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint(someDiv, {
    -	anchor:"Continuous",
    -	paintStyle:{ fillStyle:"red" }
    -});
    -</pre>
    -</div>	 	
    -...and in <strong>makeSource</strong>/<strong>makeTarget</strong>:
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource(someDiv, {
    -	anchor:"Continuous",
    -	paintStyle:{ fillStyle:"red" }
    -});
    -
    -jsPlumb.makeTarget(someDiv, {
    -	anchor:"Continuous",
    -	paintStyle:{ fillStyle:"red" }
    -});
    -</pre>
    -</div>	 
    -... and in the jsPlumb defaults:
    -<div class="code">
    -<pre>
    -jsPlumb.Defaults.Anchor = "Continuous";
    -</pre>
    -</div>  
    -					</div>		
    -												
    -<div class="section">
    -		<h4><a id="definitions">Connector, Endpoint &amp; Overlay Definitions</a></h4>
    -Before we discuss Connector, Endpoints and Overlays, a quick word on definitions:  whenever you need to define a Connector, Endpoint or Overlay, you must use a "definition" of it, rather than constructing one directly.  This definition can be either a string that nominates the artifact you want to create:
    -		<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:"Rectangle"
    -});
    -</pre>
    -		</div>
    -		or an array consisting of both the artifact's name and the arguments you want to pass to its constructor:
    -		<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Rectangle", { cssClass:"myEndpoint", width:30, height:10 } ]
    -});
    -</pre>
    -		</div>
    -There is also a three-argument method that allows you to specify two sets of parameters, which jsPlumb will merge together for you. The idea behind this is that you will often want to define common characteristics somewhere and reuse them across a bunch of different calls:
    -		<div class="code">
    -<pre>var common = {
    -	cssClass	:	"myCssClass",
    -	hoverClass	:	"myHoverClass"
    -};
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Rectangle", { width:30, height:10 }, common ]
    -});
    -</pre>
    -		</div>
    -	This syntax is supported for all Endpoint, Connector and Overlay definitions.  Here's an example using definitions for all three:	
    -		<div class="code">
    -<pre>var common = {
    -	cssClass:"myCssClass"
    -};
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Dot", { radius:5, hoverClass:"myEndpointHover" }, common ],
    -	connector:[ "Bezier", { curviness:100 }, common ],
    -	overlays: [
    -		[ "Arrow", { foldback:0.2 }, common ],
    -		[ "Label", { cssClass:"labelClass" } ]	
    -	]
    -});
    -</pre>
    -		</div>		
    -The allowed constructor parameters are different for each artifact you create, but every artifact takes a single JS object as argument, with the parameters as [key,value] pairs in that object.  See the relevant sections on <a href="#connectors">Connectors</a>, <a href="#endpoints">Endpoints</a> and <a href="#overlays">Overlays</a> for information on supported parameters.</a>
    -				
    -	</div>
    -
    -		<div class="section">
    -			<h3><a id="connectors">Connectors</a></h3>
    -			<p>Connectors are the lines that actually join elements of the UI.  jsPlumb has four connector implementations - a straight line, a Bezier curve, "flowchart", and "state machine".  The default connector is the Bezier curve.</p>
    -			<p>You optionally specify a Connector by setting the 'connector' property on a call to jsPlumb.connect, jsPlumb.addEndpoint(s), jsPlumb.makeSource or jsPlumb.makeTarget. If you do not supply a value for 'connector', the default will be used (see <a href="#defaults">defaults</a>).</p>
    -			<p>You specify Connectors using the syntax described above in <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions.</a> Allowed constructor values for each Connector type are described below:</p>		
    -			<h4><a id="bezierConnector">Bezier Connector</a></h4>
    -			<p>The Bezier Connector provides a cubic Bezier path between the two Endpoints. It supports a single constructor argument:</p>
    -			<p>
    -			<strong>curviness</strong> - Optional; defaults to 150. This defines the distance in pixels that
    -			the Bezier's control points are situated from the anchor points.  This does not mean that your
    -			connector will pass through a point at this distance from your curve.  It is a hint to how you want the
    -			curve to travel. Rather than discuss Bezier curves at length here, because they are a complex topic,
    -			refer instead to <a href="http://en.wikipedia.org/wiki/B%C3%A9zier_curve" target="_blank">Wikipedia.</a>
    -			</p>
    -			<h4><a id="straightConnector">Straight Connector</a></h4>
    -			<p>The Straight Connector draws a straight line between the two endpoints. No constructor arguments are supported; use the <strong>endpointStyle</strong> and/or <strong>cssClass</strong>/<strong>connectorClass</strong> to a <strong>connect</strong>, <strong>addEndpoint</strong>, <strong>makeSource</strong> or <strong>makeTarget</strong> call to control the appearance of one of these Connectors.  
    -			</p>
    -			<h4><a id="flowchartConnector">Flowchart Connector</a></h4>
    -			<p>This type of Connector draws a connection that consists of a series of vertical or horizontal segments - the classic flowchart look. A single constructor argument is supported:</p>
    -			<strong>stub</strong> - this is the minimum length, in pixels, of the initial stub that emanates from an Endpoint.  This parameter is optional, and defaults to 30 pixels.
    -			<p>This Connector supports Connection that start and end on the same element.</p>
    -			<h4><a id="stateMachineConnector">State Machine Connector</a></h4>
    -			<p>This Connector draws slightly curved lines (they are actually quadratic Bezier curves), similar to the State Machine connectors you may have seen in software like GraphViz.  Connections in which some element is both the source and the target (so-called 'loopback') are supported by these Connectors (as they are with Flowchart Connectors); in this case you get a circle. Supported parameters are:
    -			</p>
    -			<p>
    -			<strong>margin</strong> - Optional; defaults to 5.  Defines the distance from the element that the connector begins/ends.
    -			</p>
    -			<p>
    -			<strong>curviness</strong> - Optional, defaults to 10.  This has a similar effect to the curviness parameter on Bezier curves.
    -			</p>
    -			<p>
    -			<strong>proximityLimit</strong> - Optional, defaults to 80.  The minimum distance between the two ends of the Connector before it paints itself as 
    -			a straight line rather than a quadratic Bezier curve.
    -			</p>    
    -			
    -		</div>
    -		
    -			<div class="section">
    -			<h3><a id="endpoints">Endpoint Types</a></h3>
    -			<p>An Endpoint is the UI component that marks the location of an Anchor, ie. the place where a Connector joins an
    -			element.  jsPlumb comes with four Endpoint implementations - <strong>Dot</strong>, <strong>Rectangle</strong>, <strong>Blank</strong> and <strong>Image</strong>. You optionally specify Endpoint properties using the
    -			<strong>endpoint</strong> parameter in a call to jsPlumb.connect, jsPlumb.addEndpoint, jsPlumb.makeSource or jsPlumb.makeTarget.
    -			</p>  
    -				<p>As with Connectors and Overlays, you specify Endpoints using the syntax described above in <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions.</a></p>
    -			
    -			The four available Endpoint types, and their constructor arguments, are as follows:
    -					<h4><a id="dotEndpoint">Dot Endpoint</a></h4>
    -					This Endpoint draw a dot on the screen. It supports three constructor parameters:
    -					<p>
    -						<strong>radius</strong> - Optional; defaults to 10 pixels. Defines the radius of the dot.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="rectangleEndpoint">Rectangle Endpoint</a></h4>
    -						Draws a rectangle. Supported constructor parameters are:
    -					<p>
    -						<strong>width</strong> - Optional; defaults to 20 pixels. Defines the width of the rectangle.
    -					</p>
    -					<p>
    -						<strong>height</strong> - Optional; defaults to 20 pixels. Defines the height of the rectangle.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="imageEndpoint">Image Endpoint</a></h4>
    -						Draws an image from a given URL.  This Endpoint supports three constructor parameters:  
    -					<p>
    -						<strong>src</strong> - Required.  Specifies the URL of the image to use.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="blankEndpoint">Blank Endpoint</a></h4>
    -						Does not draw anything visible to the user.  As discussed previously, this Endpoint is probably not what you want if you need your users to be able to drag existing Connections - for that, use a Rectangle or Dot Endpoint ans assign to it a CSS class that causes it to be transparent.
    -																			
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="overlays">Overlay Types</a></h3>
    -			<p>
    -			Overlays are UI elements that are painted onto connections, such as labels or arrows.
    -			jsPlumb comes with four defaults:
    -			</p>
    -			<ul>
    -				<li><strong>Arrow</strong> - a configurable arrow that is painted at some point along the connector.  You can control the length and width of the Arrow, the 'foldback' point - a point the tail points fold back into, and the direction (allowed values are 1 and -1; 1 is the default and means point in the direction of the connection)</li>
    -				<li><strong>Label</strong> - a configurable label that is painted at some point along the connector.</li>
    -				<li><strong>PlainArrow</strong> - an Arrow shaped as a triangle, with no foldback.</li>
    -				<li><strong>Diamond</strong> - as the name suggests, a diamond.</li>
    -			</ul> 
    -			The last two are actually just configured instances of the generic Arrow overlay (see examples). 
    -			
    -			<p>All Overlays support these two methods for getting/setting their location (location is a number between 0 and 1 inclusive, indicating some point along the path
    -			inscribed by the associated Connector):</p>
    -			<ul>
    -				<li class="bullet"><strong>getLocation</strong> - returns the current location</li>
    -				<li class="bullet"><strong>setLocation</strong> - sets the current location; valid values are decimals in the range 0-1 inclusive</li> 
    -			</ul>
    -			
    -			<p>You can specify one or more overlays when making a call to jsPlumb.connect, jsPlumb.addEndpoint or jsPlumb.makeSource (but not jsPlumb.makeTarget; overlays
    -			are always derived from what the source of a Connection defines)  The three cases are discussed below:</p>
    -			1. Specifying one or more overlays on a jsPlumb.connect call. In this example we'll create an Arrow with the default options for an Arrow, and
    -			a label with the text "foo":
    -			<div class="code">
    -			<pre>jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});			
    -</pre>
    -			</div>
    -			This connection will have an arrow located halfway along it, and the label "foo" one quarter of the way along.  Notice the <strong>id</strong>
    -			parameter; it can be used later if you wish to remove the Overlay or change its visibility (see below).<br/><br/> 
    -			
    -			2. Specifying one or more overlays on a jsPlumb.addEndpoint call.  Note in this example that we use the parameter 'connectorOverlays' and not
    -			'overlays' as in the last example.  This is because 'overlays' would refer to Endpoint Overlays:
    -			<div class="code">
    -			<pre>jsPlumb.addEndpoint("someDiv", {
    -	...
    -	connectorOverlays:[ 
    -		[ "Arrow", { width:10, length:30, location:1, id:"arrow" } ], 
    -		[ "Label", { label:"foo", id:"label" } ]
    -	],
    -	...
    -});</pre>
    -			</div>	
    -			This connection will have a 10x30 Arrow located right at the head of the connection, and the label "foo" located at the halfway point. Again,
    -			note that <strong>id</strong> parameter passed in to each Overlay spec.<br/><br/>
    -			
    -			3. Specifying one or more overlays on a jsPlumb.makeSource call.  Note in this example that we again use the parameter 'connectorOverlays' and not
    -			'overlays'.  The 'endpoint' parameter to jsPlumb.makeSource supports everything you might pass to the second argument of a jsPlumb.addEndpoint call:
    -			<div class="code">
    -			<pre>jsPlumb.makeSource("someDiv", {
    -	...
    -	endpoint:{
    -		connectorOverlays:[ 
    -			[ "Arrow", { width:10, length:30, location:1, id:"arrow" } ], 
    -			[ "Label", { label:"foo", id:"label" } ]
    -		]
    -	}
    -	...
    -});</pre>
    -			</div>	
    -			This connection will have a 10x30 Arrow located right at the head of the connection, and the label "foo" located at the halfway point. Again,
    -			note that <strong>id</strong> parameter passed in to each Overlay spec.<br/><br/>
    -			
    -			 	
    -					<h4><a id="arrowOverlay">Arrow Overlay</a></h4>
    -					<p>This overlay draws an arrow, using four points: the head and two tail points, and a 'foldback' point, which permits the tail
    -					of the arrow to be indented. Available constructor arguments for this Overlay are </p>
    -					<ul>
    -					<li><strong>width</strong> - width of the tail of the arrow</li>
    -					<li><strong>length</strong> - distance from the tail of the arrow to the head</li>
    -					<li><strong>location</strong> - where, as a proportional value from 0 to 1 inclusive, the Arrow should appear on the Connector</li>
    -					<li><strong>direction</strong> - which way to point. Allowed values are 1 (the default, meaning forwards) and -1, meaning backwards </li>
    -					<li><strong>foldback</strong> - how far along the axis of the arrow the tail points foldback in to. Default is 0.623.</li>
    -					<li><strong>paintStyle</strong> - a style object in the form used for paintStyle values for Endpoints and Connectors</li>
    -					</ul>
    -					<h4><a id="plainArrowOverlay">PlainArrow Overlay</a></h4>
    -					<p>This is just a specialized instance of Arrow in which jsPlumb hardcodes 'foldback' to 1, meaning the tail of the Arrow is a flat edge.  All of the constructor parameters from Arrow apply for PlainArrow.</p>
    -					<h4><a id="diamondOverlay">Diamond Overlay</a></h4>
    -					<p>This is a specialized instance of Arrow in which jsPlumb hardcodes 'foldback' to 2, meaning the Arrow turns into a Diamond.  All of the constructor parameters from Arrow apply for PlainArrow.</p>
    -					<h4><a id="labelOverlay">Label Overlay</a></h4>
    -					This provides a text label to decorate Connectors with.  The available constructor arguments are:
    -					<ul>
    -						<li><strong>label</strong> - The text to display.  You can provide a function here instead of plain text: it is passed the Connection as an argument, and it should return a String.</li>
    -						<li><strong>cssClass</strong> - Optional css class to use for the Label.  This is now preferred over using the 'labelStyle' parameter.</li>
    -						<li><strong>labelStyle</strong> - (deprecated, use cssClass instead) Optional arguments for the label's appearance.  Valid entries in this JS object are:
    -							<ul>
    -								<li><em>font</em> - a font string in a format suitable for the Canvas element</li>
    -								<li><em>fillStyle</em> - the color to fill the label's background with. Optional.</li>
    -								<li><em>color</em> - the color of the label's text. Optional.</li>
    -								<li><em>padding</em> - optional padding for the label. This is expressed as a proportion of the width of the label, not in pixels or ems.</li>
    -								<li><em>borderWidth</em> - optional width in pixels for the label's border. Defaults to 0.</li>
    -								<li><em>borderStyle</em> - optional. The color to paint the border, if there is one.</li>
    -								
    -							</ul>
    -						
    -						</li>
    -						<li><strong>location</strong> - As for Arrow Overlay.  Where, proportionally from 0 to 1 inclusive, the label should appear.</li>
    -					</ul>
    -
    -					The Label overlay offers two methods - getLabel and setLabel - for accessing/manipulating its content dynamically:
    -<div class="code">
    -<pre>
    -var c = jsPlumb.connect({
    -	source:"d1", 
    -	target:"d2", 
    -	overlays:[
    -		[ "Label", {label:"FOO", id:"label"}]
    -	]	
    -});					
    -
    -...
    -
    -var label = c.getOverlay("label");
    -console.log("Label is currently", label.getLabel());
    -label.setLabel("BAR");
    -console.log("Label is now", label.getLabel());
    -</pre>
    -</div>
    -In this example you can see that the Label Overlay is assigned an id of "label" in the connect call, and then retrieved using that id in the call to Connection's getOverlay method.
    -<p>Both Connections and Endpoints support Label Overlays, and because changing labels is quite a common operation, <strong>setLabel</strong> and <strong>getLabel</strong> methods have been added to these objects:
    -<div class="code">
    -<pre>
    -var conn = jsPlumb.connect({
    -	source:"d1", 
    -	target:"d2",
    -	label:"FOO"
    -});					
    -
    -...
    -
    -console.log("Label is currently", conn.getLabel());
    -conn.setLabel("BAR");
    -console.log("Label is now", conn.getLabel());
    -</pre>
    -</div>
    -These methods support passing in a Function instead of a String, and jsPlumb will create a label overlay for you if one does not yet exist when you call getLabel:
    -<div class="code">
    -<pre>
    -var conn = jsPlumb.connect({
    -	source:"d1", 
    -	target:"d2"
    -});					
    -
    -...
    -
    -conn.setLabel(function(c) {
    -	var s = new Date();
    -	return s.getTime() + "milliseconds have elapsed since 01/01/1970";
    -});
    -console.log("Label is now", conn.getLabel());
    -</pre>
    -</div>
    -					
    -					<h4><a id="hideshowOverlay">Hiding/Showing Overlays</a></h4>
    -					<p>You can control the visibility of Overlays using the <strong>setVisible</strong> method of Overlays themselves, or with
    -					<strong>showOverlay(id)</strong> or <strong>hideOverlay(id)</strong> on a Connection.</p>
    -					<p>Remember the <strong>id</strong> parameter that we specified in the examples above?  This can be used to retrieve the Overlay
    -					from a Connection:</p>
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -var overlay = connection.getOverlay("myLabel");
    -// now you can hide this Overlay:
    -overlay.setVisible(false);
    -// there are also hide/show methods:
    -overlay.show();
    -overlay.hide();
    -
    -</pre>					
    -					</div>
    -					<p>However, Connection and Endpoint also have two convenience methods you could use instead:
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -connection.hideOverlay("myLabel");
    -
    -// more time passes
    -
    -connection.showOverlay("myLabel");
    -
    -</pre>					
    -					</div>
    -					
    -		<h4><a id="hideshowOverlay">Removing Overlays</a></h4>
    -		<p>Connection and Endpoint also have a <strong>removeOverlay</strong> method, that does what you might expect:
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -connection.removeOverlay("myLabel");
    -
    -
    -</pre>					
    -					</div>							
    -	
    -		</div>
    -	
    -			
    -			<div class="section">
    -			<h3><a id="defaults">Defaults</a></h3>
    -			The easiest way to set a look and feel for your plumbing is to override the defaults that jsPlumb uses. If you
    -			do not do this you are forced to provide your overridden values on every call.  Every argument to the connect and addEndpoint methods has an
    -			associated default value in jsPlumb.<br/><br/>
    -
    -			The defaults that ship with jsPlumb are stored in <em>jsPlumb.Defaults</em>, which is a Javascript object.  Valid entries, and their initial values, are:
    -
    -			<div class="code">
    -<pre>
    -Anchor : "BottomCenter",
    -Anchors : [ null, null ],
    -Connector : "Bezier",
    -ConnectionOverlays : [ ],
    -DragOptions : { },
    -DropOptions : { },
    -Endpoint : "Dot",
    -EndpointOverlays : [ ],
    -Endpoints : [ null, null ],
    -EndpointStyle : { fillStyle : null },
    -EndpointStyles : [ null, null ],
    -EndpointHoverStyle : null,
    -EndpointHoverStyles : [ null, null ],
    -HoverPaintStyle : null,
    -LabelStyle : { color : "black" },
    -LogEnabled : false,
    -Overlays : [ ],
    -MaxConnections : 1,
    -MouseEventsEnabled : true,
    -PaintStyle : { lineWidth : 8, strokeStyle : "#456" },
    -RenderMode : "svg",
    -Scope : "_jsPlumb_DefaultScope"
    -</pre>
    -			</div>
    -			<p>
    -			Note that in EndpointStyle, the default fillStyle is 'null'.  This instructs jsPlumb to use the strokeStyle
    -from the attached connector to fill the endpoint.</p>
    -<p>Note also that you can specify either or both (or neither) of 'EndpointStyle' and 'EndpointStyles'.  This allows you to specify a different
    -end point for each end of a connection.  'Endpoint' and 'Endpoints' use the same concept.  jsPlumb will look first in the
    -individual endpoint/endpoint style arrays, and then fall back to the single default version.</p>
    -
    -			you can override these defaults by including this in a script somewhere:
    -			<div class="code">
    -<pre>
    -jsPlumb.Defaults.PaintStyle = {
    -	lineWidth:13,
    -	strokeStyle: 'rgba(200,0,0,100)'
    -}
    -
    -jsPlumb.Defaults.DragOptions = { cursor: "crosshair" };
    -
    -jsPlumb.Default.Endpoints = [ [ "Dot", 7 ], [ "Dot", 11 ] ];
    -
    -jsPlumb.Defaults.EndpointStyles = [{ fillStyle:"#225588" }, { fillStyle:"#558822" }];</pre>
    -			</div>
    -			after the jsPlumb script has been loaded of course!  Here we have specified the following default behaviour:
    -			<ul>
    -				<li>- connectors are 13 pixels wide and painted with a semi-transparent red line</li>
    -				<li>- when dragging an element the crosshair cursor is used</li>
    -				<li>- the source endpoint is a dot of radius 7; the target endpoint is a dot of radius 11</li>
    -				<li>- the source endpoint is blue; the target endpoint is green</li>
    -			</ul>
    -
    -			<h5>jsPlumb.importDefaults</h5>
    -From version 1.3.8 onwards there is a helper method called <strong>importDefaults</strong> that allows you to import a set of values with just one call to jsPlumb.  Here's the previous example using importDefaults:
    -			<div class="code">
    -<pre>
    -jsPlumb.importDefaults({
    -	PaintStyle : {
    -		lineWidth:13,
    -		strokeStyle: 'rgba(200,0,0,100)'
    -	},
    -	DragOptions : { cursor: "crosshair" },
    -	Endpoints : [ [ "Dot", 7 ], [ "Dot", 11 ] ],
    -	EndpointStyles : [{ fillStyle:"#225588" }, { fillStyle:"#558822" }]
    -});</pre>
    -			</div>
    -
    -		</div> 					
    -
    -<div class="section">
    -			<h3><a id="simpleConnections">Connections</a></h3>
    -			<h4><a id="simpleConnections">Programmatic Connections</a></h4>
    -The most simple connection you can make with jsPlumb looks like this:
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({source:"element1", target:"element2"});</pre>			
    -			</div>
    -
    -		In this example we have created a Connection from 'element1' to 'element2'.  Remember that a Connection in jsPlumb consists of two Endpoints,
    -		a Connector, and zero or more Overlays.  But this call to 'connect' supplied none of those things, so jsPlumb uses the default values wherever it needs to.
    -		In this case, default values have been used for the following: 
    -		<ul>
    -			<li>- The type and appearance of each Endpoint in the Connection. jsPlumb's default for this is the "Dot" endpoint, of radius 10, with fill color "#456".</li>
    -			<li>- The Anchors that define where the connection's Endpoints appear on each element. The jsPlumb default is "BottomCenter"</li>			
    -			<li>- Whether or not each Endpoint can be a source or target for new Connections. The default is false.</li>
    -			<li>- The type and appearance of the Connection's Connector. The default is a "Bezier" connector of line width 8, and color "#456".</li>
    -		</ul>
    -		<p>
    -		So this call will result in an 8px Bezier, colored "#456", from the bottom center of 'element1' to the bottom center of 'element2', and each
    -		Endpoint will be a 10px radius Dot Endpoint, colored "#456".
    -		</p>
    -		<p>
    -		Let's beef up this call a little and tell jsPlumb what sort of Endpoints we want, and where we want them:		
    -		</p>
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:"element1", 
    -	target:"element2",
    -	anchors:["RightMiddle", "LeftMiddle" ],
    -	endpoint:"Rectangle",
    -	endpointStyle:{ fillStyle: "yellow" }
    -});
    -</pre>			
    -			</div>
    -			
    -		<p>This is what we have told jsPlumb we want this time:</p>
    -		<ul>
    -			<li><strong>anchors</strong> - this array tells jsPlumb where the source and target Endpoints should be located on their parent elements. In this case,
    -			we use the shorthand syntax to name one of jsPlumb's default anchors; you can also specify custom locations (see <a href="#anchors">anchors</a>).
    -			Instead of <strong>anchors</strong> you can use <strong>anchor</strong>, if you want the source and target Endpoints to be located at the
    -			same place on their parent elements.			<br/>
    -			</li>
    -			<li>
    -				<strong>endpoint</strong>  - this tells jsPlumb to use the "Rectangle" Endpoint for both the source and target of the Connection.  As with anchors, 
    -				<strong>endpoint</strong> has a plural version that allows you to specify a different Endpoint for each end of the Connection.<br/>  
    -			</li>
    -			<li>
    -				<strong>endpointStyle</strong> - this is the definition of the appearance of the Endpoint you specified above.  Again, there is a
    -				plural equivalent of this that allows you to specify a different style for each end of the Connection. For more information about
    -				allowed values for this value, see <a href="#paintstyles">Connector, Endpoint & Overlay Styles</a>. 
    -			</li>
    -		</ul>		
    -		
    -		<h5>Reusing common settings between jsPlumb.connect calls</h5>
    -		<p>
    -			A fairly common situation you will find yourself in is wanting to create a bunch of Connections that have only minor differences
    -			between them.  To support that, <strong>jsPlumb.connect</strong> takes an optional second argument. For example:			 
    -		</p>
    -		<div class="code">
    -<pre>
    -var common = {
    -	anchors:[ "BottomCenter", "TopCenter" ],
    -	endpoints:["Dot", "Blank" ]
    -};
    -
    -jsPlumb.connect({ source:"someElement", target:"someOtherElement" }, common);
    -
    -jsPlumb.connect({ source:"aThirdElement", target:"yetAnotherElement" }, common);
    -
    -</pre>			
    -			</div>		
    -			
    -		<h5>Endpoints created by jsPlumb.connect</h5>
    -		<p>If you supply an element id or selector for either the source or target , jsPlumb.connect will automatically create an Endpoint on the 
    -		given element.  These automatically created Endpoints are not marked as drag source or targets, and cannot
    -		be interacted with.  For some situations this behaviour is perfectly fine, but for more interactive UIs you should set things up using the drag and drop
    -		method discussed below.</p>
    -		<p>Given that jsPlumb.connect creates its own Endpoints in some circumstances, in order to avoid leaving orphaned Endpoints around the place, if the Connection is subsequently
    -		deleted, these created Endpoints are deleted too.  Should you want to, you can override this behaviour by setting <strong>deleteEndpointsOnDetach</strong> to
    -		false in the connect call:</p>
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({ 
    -	source:"aThirdElement", 
    -	target:"yetAnotherElement",
    -	deleteEndpointsOnDetach:false 
    -});
    -</pre>
    -</div>								
    -					
    -		<h4><a id="draggableConnections">Connections using Drag and Drop</a></h4>
    -		To support drag and drop connections, you first need to set a few things up.  Every drag and drop connection needs at least a source
    -		Endpoint that the user can drag a connection from. Here's a simple example of how to create an Endpoint:
    -		<div class="code">
    -<pre>
    -var endpointOptions = { isSource:true };
    -var endpoint = jsPlumb.addEndpoint('elementId', endpointOptions);
    -</pre>		
    -</div>  	
    -	This Endpoint will act as a source for new Connections, and will use the jsPlumb defaults for its own appearance and that of any Connections that are drawn from it.							
    -	
    -		<h5>Tip: use the three-argument addEndpoint method for common data </h5>
    -		One thing that happens quite often is that you have an Endpoint whose appearance and behaviour is largely the
    -		same between usages on different elements, with just a few differences. 
    -		<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -	endpoint:"Rectangle",
    -	paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -	isSource:true,
    -	connectorStyle : { strokeStyle:"#666" },
    -	isTarget:true
    -};
    -</pre>		
    -</div>  		
    -		Notice there is no 'anchor' set.  Here we apply it to two elements, at a different location in each:
    -		<div class="code">
    -<pre>
    -jsPlumb.addEndpoint('element1', { anchor:"BottomCenter" }, exampleGreyEndpointOptions)); 
    -
    -jsPlumb.addEndpoint('element2', { anchor:"TopCenter" }, exampleGreyEndpointOptions));
    -</pre>		
    -</div>
    -
    -<p>
    -Now that you have a source Endpoint, you need to either create a target Endpoint on some element, or notify jsPlumb that you wish to make an
    -entire element a drop target.  Let's look at how to attach a target Endpoint first:
    -</p>
    -
    -		<div class="code">
    -<pre>var endpointOptions = { isTarget:true, endpoint:"Rectangle", paintStyle:{ fillStyle:"gray" } };
    -var endpoint = jsPlumb.addEndpoint("otherElementId", endpointOptions);
    -</pre>		
    -		</div>
    -		<p>
    -		This Endpoint, a gray rectangle, has declared that it can act as a drop target for Connections. 
    -</p>
    -<p>
    -jsPlumb also supports marking entire elements as drag sources or drag targets, using the <strong>makeSource</strong> and <strong>makeTarget</strong> methods.  This is a useful function for certain applications.  Each of these methods takes an Endpoint declaration which defines the Endpoint that jsPlumb will create after a Connection has been attached.  Let's take a look at makeTarget first:
    -</p>
    -<h4><a id="sourcesAndTargets">Elements as sources &amp; targets</a></h4>
    -jsPlumb also supports turning entire elements into Connection sources and targets, using the methods <strong>makeSource</strong> and <strong>makeTarget</strong>.  With these methods you mark an element as a source or target, and provide an Endpoint specification for jsPlumb to use when a Connection is established.  makeSource also gives you the ability to mark some child element as the place from which you wish to drag Connections, but still have the Connection on the main element after it has been established.
    -<p>These methods honour the jsPlumb defaults - if, for example you set up the default Anchors to be this:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.Defaults.Anchors = [ "LeftMiddle", "BottomRight" ];
    -</pre>
    -</div>
    -... and then used makeSource and makeTarget without specifying Anchor locations, jsPlumb would use "LeftMiddle" for the makeSource element and "BottomRight" for the makeTarget element.
    -<p>
    -A further thing to note about makeSource and makeTarget is that from 1.3.8 onwards any prior calls to one of these methods is honoured by subsequent calls to jsPlumb.connect. This helps when you're building a UI that uses this functionality at runtime but which loads some initial data, and you want the statically loaded data to have the same appearance and behaviour as dynamically created connections (obviously quite a common use case).  
    -<p>For example:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -	anchor:"Continuous",
    -	endpoint:["Rectangle", { width:40, height:20 }]
    -});
    -
    -...
    -
    -jsPlumb.connect({source:"el1", target:"el2"});
    -</pre>
    -</div>
    -In this example, the source of the connection will be a Rectangle of size 40x20, having a Continuous Anchor.
    -<p>You can override this behaviour by setting a <strong>newConnection</strong> parameter on the connect call:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -	anchor:"Continuous",
    -	endpoint:["Rectangle", { width:40, height:20 }]
    -});
    -
    -...
    -
    -jsPlumb.connect({ source:"el1", target:"el2", newConnection:true });
    -</pre>
    -</div>
    -<h5><a id="makeTarget">jsPlumb.makeTarget</a></h5>
    -<p>
    -This method takes two arguments, the first of which specifies some element (or list of elements); the second specifies the Endpoint you wish to create on that element whenever a Connection is established on it.  In this example we will use the exact same target Endpoint we used before - the gray rectangle - but we will tell jsPlumb that the element "aTargetDiv" will be the drop target:
    -</p>
    -		<div class="code">
    -<pre>var endpointOptions = { 
    -  isTarget:true, 
    -  maxConnections:5,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" } 
    -};
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -<p>
    -	The allowed values in 'endpointOptions' are identical for both the <strong>jsPlumb.addEndpoint</strong> and <strong>jsPlumb.makeTarget</strong> methods, but makeTarget supports an extended 
    -	anchor syntax that allows you more control over the location of the target endpoint.  This is discussed below.	
    -</p>		
    -<p>Notice in the 'endpointOptions' object above there is a 'isTarget' parameter set - this may seem incongruous, since you know you're going to make some element a target.  Remember that the endpointOptions object is the information jsPlumb will use to create an Endpoint on the given target element each time a Connection is established to it. It takes the exact same format as you would pass to addEndpoint; makeTarget is essentially a deferred addEndpoint call followed by a connect call.  So in this case, we're telling jsPlumb that any Endpoints it happens to create on some element that was configured by the makeTarget call are themselves Connection targets.
    -</p>
    -<h5>Unique Endpoint per Target </h5>
    -<p>jsPlumb will create a new Endpoint using the supplied information every time a new Connection is established on the target element, by default, but you can override this behaviour and tell jsPlumb that it should create at most one Endpoint, which it should attempt to use for subsequent Connections:
    -</p>
    -		<div class="code">
    -<pre>var endpointOptions = { 
    -  isTarget:true, 
    -  uniqueEndpoint:true,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" } 
    -};
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -Here, the <strong>uniqueEndpoint</strong> parameter tells jsPlumb that there should be at most one Endpoint on this element.  Notice that 'maxConnections' is not set: the default is 1, so in this setup we have told jsPlumb that "aTargetDiv" can receive one Connection and no more.
    -<h5>Deleting Endpoints on Detach</h5>
    -By default, any Endpoints created using makeTarget have <strong>deleteEndpointsOnDetach</strong> set to true, which means that once all Connections to that Endpoint are removed, the Endpoint is deleted.  You can override this by setting the flag to true on the makeTarget call:
    -		<div class="code">
    -<pre>var endpointOptions = { 
    -  isTarget:true, 
    -  maxConnections:5,
    -  uniqueEndpoint:true,
    -  deleteEndpointsOnDetach:false,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" } 
    -};
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -In this setup we have told jsPlumb to create an Endpoint the first time "aTargetElement" is connected to, and to not delete it even if there are no longer any Connections to it.  The created Endpoint will be reused for subsequent Connections, and can support a maximum of 5.
    -
    -<h5>Target Anchors positions with makeTarget</h5>
    -<p>When using the makeTarget method, jsPlumb allows you to provide a callback function to be used to determine
    -the appropriate location of a target anchor for every new Connection dropped on the given target.  It may be the
    -case that you want to take some special action rather than just relying on one of the standard anchor mechanisms.
    -</p>
    -<p>This is achieved through an extended anchor syntax (note that this syntax is <strong>not supported</strong> in
    -the <strong>jsPlumb.addEndpoint</strong> method) that supplies a "positionFinder" to the anchor specification.  jsPlumb 
    -provides two of these by default; you can register your own on jsPlumb and refer to them by name, or just 
    -supply a function.  Here's a few examples:</p>
    -Instruct jsPlumb to place the target anchor at the exact location at which the mouse button was released on the
    -target element. Note that you tell jsPlumb the anchor is of type "Assign", and you then provide a "position"
    -parameter, which can be the name of some position finder, or a position finder function itself:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {
    -  anchor:[ "Assign", { 
    -    position:"Fixed"
    -  }]
    -});
    -</pre>
    -</div>
    -<p><strong>Fixed</strong> is one of the two default position finders provided by jsPlumb. The other is <strong>Grid</strong>:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {
    -  anchor:[ "Assign", { 
    -    position:"Grid",
    -    grid:[3,3]
    -  }]
    -});
    -</pre>
    -</div>
    -The Grid position finder takes a "grid" parameter that defines the size of the grid required. [3,3] means
    -3 rows and 3 columns.
    -<h5>Supplying your own position finder</h5>
    -<p>To supply your own position finder to jsPlumb you first need to create the callback function. First let's take a look at what the source code for the Grid position finder looks like:</p>
    -<div class="code">
    -<pre>
    -function(eventOffset, elementOffset, elementSize, constructorParams) {
    -  var dx = eventOffset.left - elementOffset.left, dy = eventOffset.top - elementOffset.top,
    -      gx = elementSize[0] / (constructorParams.grid[0]), 
    -      gy = elementSize[1] / (constructorParams.grid[1]),
    -      mx = Math.floor(dx / gx), my = Math.floor(dy / gy);
    -
    -  return [ ((mx * gx) + (gx / 2)) / elementSize[0], ((my * gy) + (gy / 2)) / elementSize[1] ];
    -}
    -</pre>
    -</div>
    -The four arguments are:
    -<ul>
    -	<li class="bullet"><strong>eventOffset</strong> - Page left/top where the mouse button was released (a JS object containing left/top members like you get from a jQuery offset call)</li>
    -	<li class="bullet"><strong>elementOffset</strong> - JS offset object containing offsets for the element on which the Connection is to be created</li>
    -	<li class="bullet"><strong>elementSize</strong> - [width, height] array of the dimensions of the element on which the Connection is to be created</li>
    -	<li class="bullet"><strong>constructorParams</strong> - the parameters that were passed to the Anchor's constructor. In the example given above, those parameters are 'position' and 'grid'; you can pass arbitrary parameters.
    -</ul>
    -The return value of this function is an array of [x, y] - proportional values between 0 and 1 inclusive, such as you can pass to a static Anchor.
    -<p>To make your own position finder you need to create a function that takes those four arguments and returns an [x, y] position for the anchor, for example:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.AnchorPositionFinders.MyFinder = function(dp, ep, es, params) {
    -	...		
    -	return [ some maths ];	
    -};
    -</pre>
    -</div>
    -Then refer to it in a makeTarget call:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {  
    -  anchor:[ "Assign", { 
    -    position:"MyFinder",
    -    myCustomParameter:"foo",
    -    anInteger:5
    -  }]
    -});
    -</pre>
    -</div>
    -
    -<h5><a id="makeSource">jsPlumb.makeSource</a></h5>
    -There are two use cases supported by this method.  The first is the case that you want to drag a Connection from the element itself and have an Endpoint attached to the element when a Connection is established.  The second is a more specialised case: you want to drag a Connection from the element, but once the Connection is established you want jsPlumb to move it so that its source is on some other element.  For an example of this second case, check out the <a href="../demo/jquery/stateMachineDemo.html">State Machine Connectors</a> demonstration.
    -<p>Here's an example code snippet for the basic use case of makeSource:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource(someDiv, {
    -  paintStyle:{ fillStyle:"yellow" },
    -  endpoint:"Blank",
    -  anchor:"BottomCenter"
    -});
    -</pre>
    -</div>
    -<p>
    -Notice again that the second argument is the same as the second argument to an addEndpoint call.  makeSource is, essentially, a type of addEndpoint call.  In this example we have told jsPlumb that we will support dragging Connections directly from 'someDiv'.  Whenever a Connection is established between 'someDiv' and some other element, jsPlumb assigns an Endpoint at BottomCenter of 'someDiv', fills it yellow, and sets that Endpoint as the newly created Connection's source. 
    -</p>		
    -<p>Configuring an element to be an entire Connection source using makeSource means that the element cannnot itself be draggable.  There would be no way for jsPlumb to distinguish between the user attempting to drag the element and attempting to drag a Connection from the element.  If your UI works in such a way that that is acceptable then you'll be fine to use this method.  But if you want to drag Connections from some element but also have the element be draggable itself, you might want to consider the second use case.  First, a code snippet:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("someConnectionSourceDiv", {
    -  paintStyle:{ fillStyle:"yellow" },
    -  endpoint:"Blank",
    -  anchor:"BottomCenter",
    -  parent:"someDiv"
    -});
    -</pre>
    -</div>
    -<p>The only difference here is the inclusion of the 'parent' parameter.  It instructs jsPlumb to configure the source Endpoint on the element specified - in this case, "someDiv" (the value of this parameter may be a String id or a selector).</p>
    -<p>Again, I'd suggest taking a look at the <a href="../demo/jquery/stateMachineDemo.html">State Machine Connectors demonstration</a> for an example of this.</p>
    -
    -<p>
    -There are many things you can set in an Endpoint options object; for a thorough list see the API documentation for Endpoint.  
    -</p>
    -<p>Here's an example of specifying that you want an Arrow overlay halfway along any Connection dragged from this Endpoint:</p>
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -  isSource:true,
    -  connectorStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  connectorOverlays: [ [ "Arrow", { location:0.5 } ] ]
    -};
    -</pre>
    -</div>
    -This is an Endpoint that moves around the element it is attached to dependent on the location of other elements in the connections it is attached to (a 'dynamic' anchor):
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -  isSource:true,
    -  connectorStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  connectorOverlays: [ [ "Arrow", { location:0.5 } ] ]
    -  anchor:[ "TopCenter","RightMiddle","BottomCenter","LeftMiddle" ]
    -};
    -</pre>
    -</div>
    -
    -<h5><a id="dragOptions">Drag Options</a></h5>
    -	These are options that will be passed through to the supporting library's drag API.  jsPlumb passes everything you supply here through, inserting
    -	wrapping functions if necessary for the various lifecycle events that jsPlumb needs to know about.  So if, for example, you pass a function to be
    -	called when dragging starts, jsPlumb will wrap that function with a function that does what jsPlumb needs to do, then call yours.
    -	<p>
    -	At the time of writing, jsPlumb supports jQuery, MooTools and YUI3, and each of those libraries uses different terminology.  In addition, jQuery's API is
    -	more fully featured, providing easy support for setting the zIndex and opacity of elements being dragged, as well as the 'scope' for a drag/drop (allowing
    -	you to specify more than one type of drag/drop pair), and hover classes for when a draggable is on the move or
    -	over a droppable. If you're using jQuery you can of course just supply these values on the dragOptions; to make it easier, jsPlumb's MooTools and YUI3 adapters recognize 
    -	these options and add appropriate callbacks for you.  
    -	</p> 
    -	<p>
    -	Given that the options here are library-specific, and they are all well-documented, we're going to discuss just the three drag options
    -	that behave the same way in all (see below for hoverClass):
    -		<ul>
    -		<li><strong>opacity</strong> - the opacity of an element that is being dragged.  Should be a fraction between 0 and 1 inclusive.</li>
    -		<li><strong>zIndex</strong> - the zIndex of an element that is being dragged.</li>
    -		<li><strong>scope</strong> - the scope of the draggable.  can only be dropped on a droppable with the same scope.  this is discussed below.</li>
    -		</ul> 
    -	</p>
    -	
    -	<p>For more information about drag options, take a look at the <a href="http://docs.jquery.com/UI/Draggable" target="_blank">jQuery</a>, <a href="http://mootools.net/docs/more/Drag/Drag.Move" target="_blank">MooTools</a>, or <a href="http://developer.yahoo.com/yui/3/dd/" target="_blank">YUI3</a> docs.	
    -	</p>
    -	<p><strong>NOTE: there is an issue in Chrome that affects the 'cursor' argument to drag options in jQuery. See these links:</strong>
    -	</p><p>
    -<a href="http://forum.jquery.com/topic/draggable-cursor-option-does-not-work" target="_blank">http://forum.jquery.com/topic/draggable-cursor-option-does-not-work</a><br/>
    -<a href="http://forum.jquery.com/topic/chrome-text-select-cursor-on-drag" target="_blank">http://forum.jquery.com/topic/chrome-text-select-cursor-on-drag</a>
    -</p>
    -You should put something like this at the top of your JS to avoid it (perhaps not something so drastic as a document-wide override):	
    -<div class="code">
    -<pre>document.onselectstart = function () { return false; };</pre>
    -</div>		
    -	
    -	<h5><a id="dropOptions">Drop Options</a></h5>
    -	Drop options are treated by jsPlumb in the same way as drag options - they are passed through to the underlying library.  MooTools does not have drop options like jQuery and YUI3 do; droppable functionality in MooTools 
    -	is actually implemented by the Drag.Move class - the one used to initialise a draggable.  But when you setup an Endpoint in jsPlumb you should ignore
    -	that fact, and treat droppables like you would in jQuery or YUI3.  jsPlumb wires everything up for you under the hood.
    -	<p>There are three jQuery droppable options that jsPlumb treats as shortcuts in MooTools and YUI3, for the sake of consistency:</p>
    -		<ul>
    -			<li><strong>hoverClass</strong> - the CSS class to attach to the droppable when a draggable is hovering over it.</li>
    -			<li><strong>activeClass</strong> - the CSS class to attach to the droppable when a draggable is, um, being dragged.</li>
    -			<li><strong>scope</strong> - the scope of the draggable.  The draggable can only be dropped on a droppable with the same scope.  This is discussed below.</li>
    -		</ul>   
    -	<p>For more information about drop options when using jQuery, see <a href="http://docs.jquery.com/UI/Droppable" target="_blank">here</a>.</p>
    -		
    -	<h5><a id="dragAndDropScope">Drag and Drop Scope</a></h5>
    -		jsPlumb borrowed the concept of 'scope' from jQuery's drag/drop implementation: the notion of
    -		which draggables can be dropped on which droppables.  In jsPlumb you can provide a 'scope' entry when 
    -		creating an Endpoint.  Here's the example grey Endpoint example with 'scope' added:
    -		
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:"#666" },
    -  isSource:true,
    -  connectionStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  scope:"exampleGreyConnection"
    -};</pre>		
    -</div>  		
    -  										
    -	If you do not provide a 'scope' entry, jsPlumb uses a default scope.  Its value is accessible through this method:
    -	<div class="code">
    -<pre>
    -jsPlumb.getDefaultScope();
    -</pre>	
    -	</div>				
    -	If you want to change it for some reason you can do so with this method:
    -	<div class="code">
    -<pre>
    -jsPlumb.setDefaultScope("mySpecialDefaultScope");
    -</pre>	
    -	</div>			
    -	You can also, should you want to, provide the scope value separately on the drag/drop options, like this:
    -	<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -  isSource:true,
    -  connectionStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  dragOptions:{ scope:"dragScope" },
    -  dropOptions:{ scope:"dropScope" }
    -};</pre>		
    -</div>  
    -			
    -	</div>	
    -	
    -	<div class="section">
    -		<h3><a id="beforeDrop">Interceptors</a></h3>
    -		<p>
    -		Interceptors were new in jsPlumb 1.3.4.  They are basically event handlers from which you can return a value that tells jsPlumb to abort what it is that
    -		it was doing.  There are currently two interceptors supported - <strong>beforeDrop</strong>, which is called when the user has dropped a Connection onto some target, 
    -		and <strong>beforeDetach</strong>, which is called when the user is attempting to detach a Connection. 
    -		</p>
    -		<p>Interceptors can be registered via the <strong>bind</strong> method on jsPlumb just like any other event listeners, and they can also be passed in to
    -		the <strong>jsPlumb.addEndpoint</strong>, <strong>jsPlumb.makeSource</strong> and <strong>jsPlumb.makeTarget</strong>
    -		methods.  A future version of jsPlumb will support registering interceptors on Endpoints using the <strong>bind</strong> method as you can now with the jsPlumb object.
    -		Note that binding 'beforeDrop' (as an example) on jsPlumb itself is like a catch-all: it will be called every time a Connection is dropped on _any_ 
    -		Endpoint.  But passing a 'beforeDrop' callback into an Endpoint (and in the future, registering one using 'bind') constrains that callback to just the
    -		Endpoint in question.  		
    -		<h4><a id="beforeDrop">Before Drop</a></h4>
    -		This is called when the user has released the mouse button over a Connection target.  Your callback is passed the
    -		a JS object containing these fields:
    -		<ul>
    -			<li>sourceId</li>
    -			<li>targetId</li>
    -			<li>scope</li>
    -		</ul>
    -		<p>Returning false - or nothing - from this callback will cause the drag to be abandoned, and the new Connection removed from the UI.</p>
    -		<h4><a id="beforeDetach">Before Detach</a></h4>
    -		This is called when the user has detached a Connection, which can happen for a number of reasons: by default, jsPlumb allows users to drag Connections off of target Endpoints, but this can also result from a programmatic 'detach' call.  Every case is treated the same by jsPlumb, so in fact it is possible for you to write code that attempts to detach a Connection but then denies itself!  You might want to be careful with that. 
    -		<p>Note that this interceptor is passed the actual Connection object; this is different from the beforeDrop
    -		interceptor discussed above: in this case, we've already got a Connection, but with beforeDrop we are yet 
    -		to confirm that a Connection should be created.</p>
    -		<p>Returning false - or nothing - from this callback will cause the detach to be abandoned, and the Connection will be reinstated/left on its current target.</p>
    -			
    -	</div>
    -
    -		<div class="section">
    -			<h3><a id="paintstyles">Paint Styles</a></h3>
    -			Defining the appearance of Connectors, Endpoints (and Overlays, but this is deprecated in favour of using CSS classes) is achieved through a 'paintStyle' (or a quite similar name...keep reading)
    -			object passed as a parameter to one of jsPlumb.connect, jsPlumb.addEndpoint, jsPlumb.makeSource or jsPlumb.makeTarget. Depending on the method you are
    -			calling, the parameter names vary.
    -			<h5>Connector Paint Styles</h5>
    -			These are specified in a <em>paintStyle</em> parameter on a call to <strong>jsPlumb.connect</strong>:
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			or in the <em>connectorPaintStyle</em> parameter on a call to <strong>jsPlumb.addEndpoint</strong> or <strong>jsPlumb.makeSource</strong>:
    -			<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div> 			
    -			 
    -
    -Notice the <em>paintStyle</em> parameter in those examples: it is the paint style for the Endpoint, which we'll discuss below.
    -
    -			<h5>Endpoint Paint Styles</h5>
    -			These are specified in a <em>paintStyle</em> parameter on a call to <strong>addEndpoint</strong>. This is the example from just above:
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -</div> 
    -			...or as the <em>endpointStyle</em> parameter to a <strong>connect</strong> call:		
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			... or as an entry in the <em>endpointStyles</em> array passed to a <strong>jsPlumb.connect</strong> call:
    -					<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  endpointStyles:[ 
    -    { fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -    { fillStyle:"green" }
    -  ],
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			or as the <em>paintStyle</em> parameter passed to a <strong>makeTarget</strong> or <strong>makeSource</strong> call:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("el1", {
    -  ...
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  ...
    -});
    -</pre>			
    -</div>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 }
    -  parent:"someOtherDivIJustPutThisHereToRemindYouYouCanDoThis"
    -});
    -</pre>			
    -			</div>						
    -	In the first example we  made "el1" into a drop target, and defined a paint style for the Endpoint jsPlumb will create when a Connection is
    -	established.  In the second we made "el1" a connection source and assigned the same values for the Endpoint jsPlumb will create when a Connection
    -	is dragged from that element.
    -	
    -	<h5>Overlay Paint Styles</h5>
    -	The preferred way to set paint styles for Overlays is to use the <em>cssClass</em> parameter in the constructor arguments of an Overlay definition.
    -	
    -	<h4>Paint Style Parameters</h4>
    -	This is the full list of parameters you can set in a paintStyle object, but note that <strong>fillStyle</strong> is ignored by Connectors,
    -	and <strong>strokeStyle</strong> is ignored by Endpoints.  Also, if you create a Connection using jsPlumb.connect and do not specify any
    -	Endpoint styles, the Endpoints will derive their fillStyle from the Connector's strokeStyle.
    -	<p>
    -		fillStyle, strokeStyle and outlineColor can be specified using any valid CSS3 syntax.
    -	</p>
    -	<ul>
    -		<li><strong>fillStyle</strong> - color for an Endpoint, eg. rgba(100,100,100,50), "blue", "#456", "#993355", rgb(34, 56, 78).</li>
    -		<li><strong>strokeStyle</strong> - color for a Connector. see fillStyle examples.</li>
    -		<li><strong>lineWidth</strong> - width of a Connector's line. An integer.</li>
    -		<li><strong>outlineWidth</strong> - width of the outline for an Endpoint or Connector. An integer.</li>
    -		<li><strong>outlineColor</strong> - color of the outline for an Endpoint or Connector. see fillStyle examples.</li>
    -		<li><strong>dashstyle</strong> - VML and SVG only. This comes from VML, and allows you to create dashed or dotted lines.  It has a 
    -			better syntax than the equivalent attribute in SVG (<em>stroke-dasharray</em>, discussed below), so jsPlumb supports this for both renderers. 
    -			The <em>dashstyle</em> attribute is specified as an array of strokes and spaces, where each value is some multiple of <em>the width of 
    -			the Connector</em>, and that's where it's better than SVG, which uses pixels.
    -			<p>
    -			<a href="http://www.w3.org/TR/NOTE-VML" target="_blank">The VML spec</a> is a good place to find valid values for dashstyle. Note that
    -			jsPlumb does not support the string values for this attribute ("solid", "dashdot", etc).
    -			</p>
    -			<p>
    -			In SVG render mode, jsPlumb uses the <em>lineWidth</em> parameter in conjunction with the values in a <em>dashstyle</em> attribute
    -			to create an appropriate value for <em>stroke-dasharray</em>.
    -			</p>
    -		</li>
    -		<li><strong>stroke-dasharray</strong> - SVG only. This is the SVG equivalent of <em>dashstyle</em>.  
    -		<a href="http://www.w3.org/TR/SVG/painting.html" target="_blank">The SVG spec</a> discusses valid values for this parameter.  But be aware
    -		that jsPlumb does not convert this into a valid <em>dashstyle</em> attribute when using the VML renderer. Better to use <em>dashstyle</em>.
    -		</li>
    -		<li><strong>stroke-dashoffset</strong> - SVG only.  This is used in SVG to specify how far into the dash pattern to start painting.  For more information,
    -		see <a href="http://www.w3.org/TR/SVG/painting.html" target="_blank">the SVG spec.</a></li>
    -		<li><strong>joinstyle</strong> - VML and SVG only.  As with <em>dashstyle</em>, this is a VML attribute that jsPlumb supports for both VML
    -		and SVG - jsPlumb turns this into a <em>stroke-linejoin</em> attribute when rendering with SVG.  This attribute specifies how you want individual
    -		segments of connectors to be joined; the VML and SVG specs both have examples of this, of which many are the same between the two, which is
    -		why jsPlumb will automatically convert this attribute into the SVG equivalent.
    -		</li>
    -		<li><strong>stroke-linejoin</strong> - SVG only.  This is the equivalent of VML's <em>joinstyle</em> attribute, but as with <em>stroke-dasharray</em>,
    -		jsPlumb does not convert this into something approriate for VML.  So, using <em>joinstyle</em> will enable you to support more browsers with
    -		less effort.		
    -		</li>
    -	</ul>
    -	
    -	<h3><a id="hoverpaintstyles">Hover Paint Styles</a></h3>
    -	 		
    -	 		Connectors and Endpoints both support the concept of a "hover" paint style - a paint style to use when the mouse is hovering over
    -	 		the component.  These are specified in the exact same format as paint styles discussed above, but hover paint styles also inherit
    -	 		any values from the main paint style.  This is because you will typically want to just change the color, or perhaps outline color, of
    -	 		a Connector or Endpoint when the mouse is hovering, but leave everything else the same.  So having hover paint styles inherit their
    -	 		values precludes you from having to define things in more than one place.
    -	 		<p>
    -	 			The naming convention adopted for hover paint styles is pretty much to insert the word 'hover' into the corresponding main paint
    -	 			style parameters.  Here are a couple of examples:
    -	 		</p> 
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -  hoverPaintStyle:{ strokeStyle:"red" },
    -  endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  endpointHoverStyle:{ fillStyle:"red" }
    -});
    -</pre>			
    -</div>
    -			In this example we specified a hover style for both the Connector, and each of its Endpoints.  Here's the same thing, but using the plural version, to specify a different hover style for each Endpoint:	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -  hoverPaintStyle:{ strokeStyle:"red" },
    -  endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  endpointHoverStyles:[ { fillStyle:"red" }, { fillStyle:"yellow" } ]
    -});
    -</pre>			
    -</div>
    -	 		Calls to <strong>addEndpoint</strong>, <strong>makeSource</strong> and <strong>makeTarget</strong> can also specify various hover paint styles:
    -	 								<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  hoverPaintStyle:{ fillStyle:"red" },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -  connectorHoverPaintStyle:{ strokeStyle:"red", outlineColor:"yellow", outlineWidth:1 }
    -});
    -
    -jsPlumb.makeSource("el2", {
    -  paintStyle:{ fillStyle:"transparent", outlineColor:"yellow", outlineWidth:1 },
    -  hoverPaintStyle:{ fillStyle:"red" },
    -  connectorPaintStyle:{ strokeStyle:"green", lineWidth:3 },
    -  connectorHoverPaintStyle:{ strokeStyle:"#678", outlineColor:"yellow", outlineWidth:1 }
    -});
    -
    -jsPlumb.makeTarget("el3", {
    -  paintStyle:{ fillStyle:"transparent", outlineColor:"yellow", outlineWidth:1 },
    -  hoverPaintStyle:{ fillStyle:"red" }
    -});
    -</pre>			
    -			</div> 
    -	 		
    -	 		In these examples we specified a hover paint style for both the Endpoint we are adding, and any Connections to/from the Endpoint.
    -	 		<p>	
    -		Note that <strong>makeTarget</strong> does not support Connector parameters. It is for creating targets only; Connector parameters will be set by the source Endpoint in any Connections that are made to the element that you turned into a target by using this method.</p>
    -	 			 		
    -			<h4><a id="gradients">Gradients</a></h4>
    -			The Canvas and SVG renderers both support gradients. The VML renderer does not.  jsPlumb uses its own syntax to define gradients, to abstract out the differences between the syntax required by canvas and that required by SVG.  
    -			<p>There are two types of gradients available - a 'linear' gradient, which consists of colored lines all
    -			going in one direction, and a 'radial' gradient, which consists of colored circles emanating from one circle to another.
    -			Because of their basic shape, jsPlumb supports only <em>linear</em> gradients for
    -			Connectors.  But for Endpoints, jsPlumb supports both <em>linear</em> and <em>radial</em> gradients.</p>
    -			
    -				<h5>Connector gradients</h5>
    -				To specify a linear gradient to use in a Connector, you must add a <em>gradient</em> object to your
    -				Connector's <em>paintStyle</em>, for instance:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : "window2",
    -	target : "window3",
    -	paintStyle:{
    -		gradient:{
    -			stops:[[0,"green"], [1,"red"]]
    -		},
    -		lineWidth:15
    -	}
    -});
    -</pre>
    -</div>
    -Here we have connected window2 to window3 with a 15 pixel wide connector that has a gradient from green to red.</p><p> Notice the <strong>gradient</strong> object and the
    -<strong>stops</strong> list inside it - the gradient consists of an arbitrary number of these "color stops".  Each color stop is comprised
    -of two values - [position, color].  Position must be a decimal value between 0 and 1 (inclusive), and indicates where the color
    -stop is situated as a fraction of the length of the entire gradient.  Valid values for
    -the colors in the <strong>stops</strong> list are the same as those that are valid for <strong>strokeStyle</strong> when describing a color.
    -			</p>
    -			As mentioned, the <strong>stops</strong> list can hold an arbitrary number of entries.  Here's an example of a gradient that goes from red to blue to green, and back again through blue to red:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle : {
    -		gradient:{
    -			stops:[[0,'red'], [0.33,'blue'], [0.66,'green'], [0.33,'blue'], [1,'red']]
    -		},
    -		lineWidth : 15
    -	}
    -});
    -</pre>
    -</div>
    -<strong>Note:</strong> when using the VML renderer, jsPlumb will simply ignore the gradient directive
    -so it is best to ensure you also supply a <em>strokeStyle</em> in your paintStyle object, to give jsPlumb something to fall back on.  If you do not supply
    -a <em>strokeStyle</em> your Connectors will be painted black.  The previous example might look like this, for instance:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle:{
    -		strokeStyle:'red',
    -		gradient:{
    -			stops:[[0,'red'], [0.33,'blue'], [0.66,'green'], [0.33,'blue'], [1,'red']]
    -		},
    -		lineWidth:15
    -	}
    -});
    -</pre>
    -</div>
    -Notice the <strong>strokeStyle:'red'</strong> directive at the beginning of the parameter list in <strong>paintStyle</strong>.
    -			<h5>Endpoint gradients</h5>
    -			Endpoint gradients are specified using the same syntax as Connector gradients.  You put the gradient specifier either in the
    -			<strong>endpoint</strong> member, or if you are specifying different Endpoints for each end of the Connector, in one or both of
    -			the values in the <strong>endpoints</strong> array.  Also, this information applies to the case that you are creating standalone
    -			Endpoints that you will be configuring for drag and drop creation of new Connections. 
    -			<p>
    -			This is an example of an Endpoint gradient that is different for each Endpoint in the Connector.  This comes from the main demo; it is
    -			the Connector joining Window 2 to Window 3:
    -			</p>
    -<div class="code">
    -<pre>var w23Stroke = 'rgb(189,11,11)';
    -jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle:{
    -		lineWidth:8,
    -		strokeStyle:w23Stroke
    -	},
    - 	anchors:[ [0.3,1,0,1], "TopCenter" ],
    - 	endpoint:"Rectangle",
    - 	endpointStyles:[
    - 		{ gradient : {stops:[[0, w23Stroke], [1, '#558822']] } },
    -    	{ gradient : {stops:[[0, w23Stroke], [1, '#882255']] } }
    -    ]
    -});
    -</pre>
    -</div>
    -The first entry in the gradient will be the one that is on the Connector end of the Endpoint.  You can of course have as many color stops as
    -you want in this gradient, just like with Connector gradients.
    -	<h5>Applying the gradient in Endpoints</h5>
    -	Only the Dot and Rectangle endpoints honour the presence of a gradient (and, remember, not in VML). The Image endpoint of course ignores a gradient
    -	as it does no painting of its own.
    -	<p>The type of gradient you will see depends on the Endpoint type:</p>
    -		<ul>
    -			<li><strong>Dot</strong> - renders a radial endpoint, with color stop 0 on the outside, progressing inwards as we move through color stops.
    -			<p>Radial gradients actually require more data than linear gradients - in a linear gradient we just move from one point to another, whereas
    -			in a radial gradient we move from one <em>circle</em> to another.  By default, jsPlumb will render a radial gradient using a source
    -			circle of the same radius as the Endpoint itself, and a target circle of 1/3 of the radius of the Endpoint (both circles share the
    -			same center as the Endpoint itself). This circle will be offset by radius/2 in each direction.</p>
    -			<p>You can supply your own values for these inside the gradient descriptor:</p>
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect({
    -	source : 'window3',
    -	target : 'window4',
    -    paintStyle:{
    -    	lineWidth:10,
    -    	strokeStyle:w34Stroke
    -    },
    -    anchors:[ "RightMiddle", "LeftMiddle" ],
    -    endpointStyle:{
    -    	gradient : {
    -    		stops:[ [0, w34Stroke], [1, w34HlStroke] ],
    -    		offset:37.5,
    -    		innerRadius:40
    -    	},
    -    	radius:55
    -    }
    - });
    -</pre>
    -</div>
    -Here we have instructed jsPlumb to make the gradient's inner radius 10px instead of the default 25/3 = 8 ish pixels, and the offset in each direction
    -will be 5px, instead of the default radius / 2 = 12.5 pixels.
    -<p>It is also possible to specify the offset and inner radius as percentages - enter the values as strings with a '%' symbol on the end:</p>
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect({
    -	source : 'window3', 
    -	target : 'window4',
    -    paintStyle:{
    -    	lineWidth:10,
    -    	strokeStyle:w34Stroke
    -    },
    -	anchors:[ "RightMiddle", "LeftMiddle" ],
    -	endpointStyle:{
    -		gradient : {
    -			stops:[ [0, w34Stroke], [1, w34HlStroke] ],
    -			offset:'68%',
    -			innerRadius:'73%'
    -		},
    -		radius:25
    -	}
    -});
    -</pre>
    -</div>
    -This will give roughly the same output as the example above (the percentages are not entirely exact).<br/><br/>
    -			</li>
    -			<li><strong>Rectangle</strong> - renders a linear endpoint, with color stop 0 closest to the end of the Connector</li>
    -		</ul>
    -		
    -<h4><a id="svgvmlcss">Styling SVG/VML using CSS</a></h4>
    -	A nice way of controlling your UI's appearance is to make use of the fact that both SVG and VML can be styled using CSS.
    -	<p>This section will be expanded in the next few weeks to give some decent examples of how to do so</p>
    -			
    -		
    -		
    -		</div>
    -
    -		<div class="section">
    -			<h3><a id="cssclasses">CSS Class Reference</a></h3>
    -			jsPlumb attaches classes to each of the UI components it creates.
    -			<p>These class names are exposed on the jsPlumb object and can be overridden if you need to do so (see the third column in the table) </p>
    -			<table width="70%" class="table">
    -				<tr><th>Component</th><th>CSS Class</th><th>jsPlumb Member</th></tr>
    -				<tr><td>Connector</td><td>_jsPlumb_connector</td><td>connectorClass</td></tr>
    -				<tr><td>Endpoint</td><td>_jsPlumb_endpoint</td><td>endpointClass</td></tr>
    -				<tr><td>Overlay</td><td>_jsPlumb_overlay</td><td>overlayClass</td></tr>
    -			</table>
    -			<p>You would typically use these to establish appropriate z-indices for your UI.</p>			
    -		</div>
    -				
    -		<div class="section">
    -			<h3><a id="animation">Animation</a></h3>
    -			jsPlumb offers an 'animate' function, which wraps the underlying animation engine for whichever library you happen to be using and
    -			inserts a callback for jsPlumb to repaint whatever it needs to at each step.  You could of course do this yourself; it's a convenience
    -			method really.
    -			<p>The method signature is:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.animate : function(el, properties, options) 
    -</pre>
    -</div>			
    -The arguments are as follows:
    -	<ul>
    -		<li><strong>el</strong> - element id, or element object from the library you're using.</li>
    -		<li><strong>properties</strong> - properties for the animation, such as duration etc.</li>
    -		<li><strong>options</strong> - options for the animation, such as callbacks etc.</li>
    -	</ul>
    -	
    -		<h4>jQuery and the 'revert' option</h4>
    -		jQuery offers a 'revert' option that you can use to instruct it to revert a drag under some condition. This is
    -		rendered in the UI as an animation that returns the drag object from its current location to wherever it started
    -		out.  It's a nice feature.  Unfortunately, the animation that runs the revert does not offer any lifecycle
    -		callback methods - no 'step', no 'complete' - so its not possible for jsPlumb to know that the revert 
    -		animation is taking place.
    -	
    -		</div>
    -		
    -			<div class="section">
    -			<h3><a id="connectionInfo">Retrieving Connection Information</a></h3>
    -			There are two ways of retrieving connection information from jsPlumb. <strong>getConnections</strong>is the original method; from 1.3.8 onwards this method is supplemented by <a href="#selectConnections">jsPlumb.select</a>, a much more versatile variant.
    -			<h5>getConnections</h5>
    -			<p>Before you use this method you should understand jsPlumb's notion of 'scope' - documentation
    -			is <a href="#dragAndDropScope">here</a> </p>
    -			
    -			<h4>Retrieving connections for a single scope</h4>
    -			<p>To do this, you call getConnections with either no arguments, in which case jsPlumb uses the default scope, or with a string
    -			specifying one scope</p>
    -			<div class="code">
    -<pre>var connectionList = jsPlumb.getConnections();     // you get a list of Connection objects that are in the default scope.
    -</pre>			
    -			</div>
    -			Compare this with:
    -			<div class="code">
    -<pre>var connectionList = jsPlumb.getConnections("myScope");     // you get a list of Connection objects that are in "myScope".
    -</pre>			
    -			</div>
    -			<h4>More advanced filtering</h4>
    -			getConnections optionally takes a JS object specifying filter parameters, of which there are three:
    -			<ul>
    -				<li>scope - the scope(s) of the connection type(s) you wish to retrieve</li>
    -				<li>source - limits the returned connections to those that have this source id</li>
    -				<li>target - limits the returned connections to those that have this target id</li>
    -			</ul>			
    -			Each of these three parameters may be supplied as a string, which for source and target is an element id and for scope is the name of the scope, or a list of strings.  Also from 1.3.8 you can pass "*" in as the value for any of these - a wildcard, meaning any value.  See the examples below.
    -			<p>
    -			The return value of a call to getConnection using a JS object as parameter varies on how many scopes you defined.  If you defined only a single scope then jsPlumb returns you a list of Connections in that scope.  Otherwise the return value is a dictionary whose keys are  scope names, and whose values are lists of Connections. For example, the following call:
    -			</p>
    -			<div class="code">
    -<pre>
    -	jsPlumb.getConnections({scope:["someScope", "someCustomScope"]});
    -</pre>
    -</div>
    -would result in this output:			
    -<div class="code">
    -<pre>
    -{
    -	"someScope" : [ 1..n Connections ],
    -	"someCustomScope": [ 1..m Connections ]
    -}
    -</pre>
    -</div>	
    -
    -There is an optional second parameter that tells getConnections to flatten its output and just return you an array.  The previous example with this parameter would look like this:
    -			<div class="code">
    -<pre>
    -	jsPlumb.getConnections({scope:["someScope", "someCustomScope"]}, true);
    -</pre>
    -</div>
    -would result in this output:			
    -<div class="code">
    -<pre>
    -[ 1..n Connections ]
    -</pre>
    -</div>	
    -		
    -		The following examples show the various ways you can get connection information:
    -	<ul>
    -		<li>Get all connections:
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getAllConnections();  
    -</pre>
    -</div>						
    -		</li>
    -<li>Get all connections for the default scope only (return value is a list):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections();  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given scope (return value is a list):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:"myTestScope"});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given scopes (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:["myTestScope", "yourTestScope"]});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given source (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:"mySourceElement"});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given sources (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:["mySourceElement", "yourSourceElement"]});  
    -</pre>
    -</div>						
    -		</li>		
    -<li>Get all connections for the given target (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({target:"myTargetElement"});  
    -</pre>
    -</div>						
    -		</li>				
    -<li>Get all connections for the given source and targets (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:"mySourceElement", target:["target1", "target2"]});  
    -</pre>
    -</div>						
    -		</li>				
    -		
    -<li>Get all connections for the given scope, with the given source and target (return value is a list of connections):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:'myScope", source:"mySourceElement", target:"myTargetElement"});  
    -</pre>
    -</div>						
    -		</li>				
    -		
    -	</ul>	
    -
    -		</div>
    -
    -		<div class="section">
    -			<h3><a id="selectConnections">Selecting Connections</a></h3>
    -			Introduced in 1.3.8, <strong>jsPlumb.select</strong> provides a fluid interface for working with lists of Connections.  The syntax used to specify which Connections you want is identical to that which you use for getConnections, but the return value is an object that supports most operations that you can perform on a Connection, and which is also chainable, for setter methods. Certain getter methods are also supported, but these are not chainable; they return an array consisting of all the Connections in the selection along with the return value for that Connection.
    -			<p>
    -			This is the full list of setter operations supported by jsPlumb.select:
    -			<ul>
    -				<li class="bullet">addOverlay</li>
    -				<li class="bullet">removeOverlay</li>
    -				<li class="bullet">removeOverlays</li>
    -				<li class="bullet">showOverlay</li>
    -				<li class="bullet">hideOverlay</li>
    -				<li class="bullet">showOverlays</li>
    -				<li class="bullet">hideOverlays</li>
    -				<li class="bullet">removeAllOverlays</li>
    -				<li class="bullet">setLabel</li>
    -				<li class="bullet">setPaintStyle</li>
    -				<li class="bullet">setHoverPaintStyle</li>
    -				<li class="bullet">setDetachable</li>
    -				<li class="bullet">setConnector</li>
    -				<li class="bullet">setParameter</li>
    -				<li class="bullet">setParameters</li>
    -				<li class="bullet">detach</li>
    -			</ul>
    -
    -			Each of these operations returns a selector that can be chained.
    -			</p>
    -			These is the full list of getter operations supported by jsPlumb.select:
    -			<p>
    -				<ul>
    -				<li class="bullet">getLabel</li>
    -				<li class="bullet">getOverlay</li>
    -				<li class="bullet">isHover</li>
    -				<li class="bullet">isDetachable</li>
    -				<li class="bullet">getParameter</li>
    -				<li class="bullet">getParameters</li>
    -			</ul>
    -
    -			Each of these operations returns an array whose entries are [ value, Connection ] arrays, where 'value' is the return value from the given Connection.  Remember that the return values from a getter are not chainable, but a getter may be called at the end of a chain of setters.
    -			</p>
    -
    -			<p>Some examples:</p>
    -			Select all Connections and set their hover state to be false:
    -
    -<div class="code">
    -<pre>
    -jsPlumb.select().setHover(false);
    -</pre>
    -</div>
    -			Select all Connections from "d1" and remove all Overlays:
    -
    -<div class="code">
    -<pre>
    -jsPlumb.select({source:"d1"}).removeAllOverlays();
    -</pre>
    -</div>
    -
    -Select all connections in scope "foo" and set their paint style to be a thick blue line:
    -<div class="code">
    -<pre>
    -jsPlumb.select({scope:"foo"}).setPaintStyle({ strokeStyle:"blue", lineWidth:5 });
    -</pre>
    -</div>
    -Select all Connections from "d1" and detach them:
    -<div class="code">
    -<pre>
    -jsPlumb.select({source:"d1"}).detach();
    -</pre>
    -</div>
    -<h5>.each iterator</h5>
    -The return value of jsPlumb.select has a <strong>.each</strong> function that allows you to iterate through the list, performing an operation on each one:
    -<div class="code">
    -<pre>
    -jsPlumb.select({scope:"foo"}).each(function(connection) {
    -	
    -		// do something 
    -});
    -</pre>
    -</div>
    -.each is itself chainable:
    -<div class="code">
    -<pre>
    -jsPlumb.select({scope:"foo"}).each(function(connection) {
    -	
    -		// do something 
    -
    -}).setHover(true);
    -</pre>
    -</div>
    -
    -
    -<h5>Other properties/functions</h5>
    -<ul>
    -	<li class="bullet"><strong>length</strong> - this member reports the number of Connections in the selection</li>
    -	<li class="bullet"><strong>get(idx)</strong> - this function allows you to retrieve a Connection from the selection</li>
    -</ul>
    -
    -
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="events">Events</a></h3>
    -			jsPlumb supports binding to several different events on Connections, Endpoints and Overlays, and also on the jsPlumb object itself.  
    -			
    -			<h4><a id="jsPlumbEvents">jsPlumb Events</a></h4>
    -			<p>To bind an event to jsPlumb itself, use <strong>jsPlumb.bind(event, callback)</strong>:</p>
    -			<div class="code">
    -<pre>
    -jsPlumb.bind("jsPlumbConnection", function(info) {
    -   .. update your model in here, maybe.
    -});
    -</pre>
    -</div>
    -<p>These are the events you can bind to on the jsPlumb class:</p>
    -			<ul>
    -				<li class="bullet"><strong>jsPlumbConnection</strong> - notification a Connection was established.
    -					<p>
    -						The first argument to the callback is an object with the following properties:					
    -					</p>
    -					<div class="code">
    -<pre>
    -{
    -  connection 		: 	the new Connection.  you can register listeners on this etc.
    -  sourceId 		:	id of the source element in the Connection
    -  targetId		:	id of the target element in the Connection
    -  source		:	the source element in the Connection
    -  target		:	the target element in the Connection
    -  sourceEndpoint	:	the source Endpoint in the Connection
    -  targetEndpoint	:	the targetEndpoint in the Connection
    -}
    -</pre>
    -					</div>
    -The second argument is the original mouse event that caused the Connection, if any.
    -
    -					<p>
    -All of the source/target properties are actually available inside the Connection object, but - for one of those rubbish historical reasons - are provided separately because of a vagary of the <em>jsPlumbConnectionDetached</em> callback, which is discussed below.
    -</p>
    -				</li>
    -				<li class="bullet"><strong>jsPlumbConnectionDetached</strong> - notification a Connection was detached.  
    -					<p>
    -						As with <em>jsPlumbConnection</em>, the first argument to the callback is an object with the following properties:					
    -					</p>
    -					<div class="code">
    -<pre>
    -{
    -  connection		: 	the Connection that was detached.  
    -  sourceId		:	id of the source element in the Connection <em>before it was detached</em>
    -  targetId		:	id of the target element in the Connection before it was detached
    -  source		:	the source element in the Connection before it was detached
    -  target		:	the target element in the Connection before it was detached
    -  sourceEndpoint	:	the source Endpoint in the Connection before it was detached
    -  targetEndpoint	:	the targetEndpoint in the Connection before it was detached
    -}
    -</pre>
    -
    -					</div>	
    -The second argument is the original mouse event that caused the disconnection, if any. 					
    -								
    -					<p>
    -As mentioned above, the source/target properties are provided separately from the Connection, because this event is fired whenever
    -a Connection is either detached and abandoned, or detached from some Endpoint and attached to another.  In the latter case, the
    -Connection that is passed to this callback is in an indeterminate state (that is, the Endpoints are still in the state they are
    -in when dragging, and do not reflect static reality), and so the source/target properties give you the real story.
    -</p>
    -<p> Like I said above, this is really just for rubbish historical reasons.  I'm attempting to document my way out of it!</p>
    -				
    -				</li>
    -				<li class="bullet"><strong>click</strong> - notification a Connection was clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification a Connection was double-clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>endpointClick</strong> - notification an Endpoint was clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>endpointDblClick</strong> - notification an Endpoint was double-clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>contextmenu</strong> - a right-click on some given component.  jsPlumb will report right clicks on both Connections and Endpoints; the callback is passed the component that received the right-click.				
    -				<li class="bullet"><strong>beforeDrop</strong>
    -					This event is fired when a new connection has been dropped. Your callback is passed a JS object with sourceId, targetId and scope in it. If you return false (or nothing) from this callback, the new Connection is aborted and removed from the UI.
    -				</li>					
    -				<!--li class="bullet"><strong>beforeDrag</strong>
    -					This event is fired when a new connection is about to be dragged.  Your callback function
    -					is passed the Connection that jsPlumb has just created.  If you return false (or nothing) 
    -					from this callback, the new Connection is aborted.
    -				</li-->
    -				<li class="bullet"><strong>beforeDetach</strong>
    -					This event is fired when a Connection is about to be detached, for whatever reason. Your callback function is passed the Connection that jsPlumb has just created. Returning false from this interceptor aborts the Connection detach.
    -				</li>
    -							
    -			</ul>
    -			<h4><a id="connectionEvents">Connection Events</a></h4>		
    -			<p>To bind to an event on a Connection, you also use the <strong>bind</strong> method:</p>
    -			<div class="code">
    -<pre>
    -var connection = jsPlumb.connect({source:"d1", target:"d2"});
    -connection.bind("click", function(conn) {
    -	console.log("you clicked on ", conn);
    -});
    -</pre>
    -</div>
    -<p>These are the Connection events to which you can bind a listener:</p>
    -			<ul>
    -				<li class="bullet"><strong>click</strong> - notification a Connection was clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification a Connection was double-clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>contextmenu</strong> - a right-click on the Connection. The callback is passed the Connection and the original event.
    -				<li class="bullet"><strong>mouseenter</strong> - notification the mouse entered the Connection's path.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>mouseexit</strong> - notification the mouse exited the Connection's.  The callback is passed the Connection and the original mouse event.</li>
    -			</ul>
    -			<h4><a id="endpointEvents">Endpoint Events</a></h4>			
    -			<p>To bind to an event on a Endpoint, you again use the <strong>bind</strong> method:</p>
    -			<div class="code">
    -<pre>
    -var endpoint = jsPlumb.addEndpoint("d1", { someOptions } );
    -endpoint.bind("click", function(endpoint) {
    -	console.log("you clicked on ", endpoint);
    -});
    -</pre>
    -</div>
    -<p>These are the Endpoint events to which you can bind a listener:</p>
    -			<ul>
    -				<li class="bullet"><strong>click</strong> - notification an Endpoint was clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification an Endpoint was double-clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>contextmenu</strong> - a right-click on the Endpoint. The callback is passed the Endpoint and the original event.
    -				<li class="bullet"><strong>mouseenter</strong> - notification the mouse entered the Endpoint.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>mouseexit</strong> - notification the mouse exited the Endpoint.  The callback is passed the Endpoint and the original mouse event.</li>
    -			</ul>
    -			<h4><a id="overlayEvents">Overlay Events</a></h4>
    -			Registering event listeners on an Overlay is a slightly different procedure - you provide them as arguments to the Overlay's 
    -			constructor.  This is because you never actually act on an 'Overlay' object.  Here's how to register a click listener on an Overlay:
    -			<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"el1",
    -	target:"el2",
    -	overlays:[
    -		[ "Label", {
    -			events:{
    -				click:function(labelOverlay, originalEvent) { alert("you clicked on the label overlay for this connection :" + labelOverlay.connection) }
    -			}
    -		}] 	
    -	]
    -});
    -</pre>			
    -			</div>		
    -		</div>
    -
    -				<div class="section">
    -			<a id="examples"><h3>jsPlumb.connect Examples</h3></a>
    -This section provides examples of how to use the programmatic API to establish Connections. 
    -			<p>
    -			The basic syntax of a call is that you execute 'connect', providing a source and a target, and optionally a paintStyle and preferences for where you
    -			want the plumbing to be anchored on each element, as well as the type of connector to use. 
    -			</p>
    -			<ul>
    -				<li>Connect window1 to window2 with the default settings:
    -					<div class="code">
    -						<pre>jsPlumb.connect({source:"window1", target:"window2"});</pre>
    -					</div>
    -This connects 'window1' to 'window2' using the jsPlumb defaults - a red Bezier curve Connector and black Dot Endpoints.
    -				</li>
    -				<li>Connect window1 to window2 with a 15 pixel wide yellow Connector, and a slightly brighter endpoint (remember the default Endpoint is a Dot):
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -					</div>
    -				</li>
    -				
    -				<li>Connect window1 to window2 with a 15 pixel wide yellow Connector, and a slightly brighter endpoint:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -					</div>
    -				</li>				
    -				<li>Connect window3 to 'window4' with a 10 pixel wide, semi opaque blue Connector, anchored to the left middle of window3, and the right middle of window4, with a Rectangle endpoint of width 10 and height 8:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window3',
    -	target:'window4',
    -	paintStyle:{ lineWidth:10, strokeStyle:'rgba(0, 0, 200, 0.5)' },
    -	anchors:["RightMiddle", "LeftMiddle"],
    -	endpoint:[ "Rectangle", { width:10, height:8 } ]
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window2 to window3 with a default Connector from the top center of window2 to the bottom center of window3, and rectangular endpoints:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window2',
    -	target:'window3',
    -	paintStyle:{lineWidth:8, strokeStyle:'rgb(189,11,11)'},
    -	anchors:["BottomCenter", "TopCenter"],
    -	endpoint:"Rectangle"
    -});</pre>
    -					</div>
    -				</li>
    -
    -
    -				<li>Connect window1 to window2 with a 15 px wide yellow Bezier. endpoints are a slightly lighter shade of yellow.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	anchors:["BottomCenter", [0.75,0,0,-1]],
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -</div></li>
    -<li>Connect window3 to window4 with a 10px wide blue-ish half transparent Bezier. put endpoints underneath the element they attach to.
    -the endpoints have a radial gradient. both ways of specifying gradient positioning are shown here.
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect( {
    -	source:'window3',
    -	target:'window4',
    -		 paintStyle:{lineWidth:10, strokeStyle:w34Stroke},
    -		 anchors:["RightMiddle", "LeftMiddle"],
    -		 endpointStyle:{ gradient : {stops:[[0, w34Stroke], [1, w34HlStroke]], offset:17.5, innerRadius:15 }, radius:35},
    -		 //endpointStyle:{ gradient : {stops:[[0, w34Stroke], [1, w34HlStroke]], offset:'78%', innerRadius:'73%'}, radius:35 },
    -		 endpointsOnTop:false
    -	}
    -);</pre>
    -</div></li>
    -<li>Connect window2 to window3 with an 8px red Bezier and default rectangular endpoints.  see also how the first anchor is
    -specified here - this is how you create anchors in locations jsPlumb does not offer shortcuts for.
    -the endpoints in this example have linear gradients applied.
    -<div class="code">
    -<pre>var w23Stroke = 'rgb(189,11,11)';
    -jsPlumb.connect({
    -	source:'window2',
    -	target:'window3',
    -	paintStyle:{lineWidth:8,strokeStyle:w23Stroke},
    -	anchors:[[0.3,1,0,1], "TopCenter"],
    -	endpoint:"Rectangle",
    -	endpointStyles:[{ gradient : {stops:[[0, w23Stroke], [1, '#558822']] }},
    -       				{ gradient : {stops:[[0, w23Stroke], [1, '#882255']] }}]
    -});</pre>
    -</div></li>
    -
    -<li>Connect window5 to window6 from center to center, 5px wide line that is green and half transparent. the endpoints are
    -125px in radius and spill out from underneath their elements.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window5',
    -	target:'window6',
    -	anchors:["Center", "Center"],
    -	paintStyle:{lineWidth:5,strokeStyle:'rgba(0,255,0,0.5)'},
    -	endpointStyle:{radius:125}
    -});</pre>
    -</div></li>
    -
    -<li>Connect window4 to window5 from bottom right to top left, with a 7px straight line purple connector, and an image as the endpoint,
    -placed on top of the element it is connected to.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window4",
    -	target:"window5",
    -	anchors:[ "BottomRight","TopLeft" ],
    -	paintStyle:{ lineWidth:7, strokeStyle:"rgb(131,8,135)" },
    -	endpoint:[ "Image", { src:"http://morrisonpitt.com/jsPlumb/img/endpointTest1.png" } ],
    -	connector:"Straight"
    -});</pre>
    -</div></li>
    -
    -
    -				<li>Connect window5 to window6 between their center points with a semi-opaque connector, and 125px endpoints:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window5",
    -	target:"window6",
    -	anchors:[ "Center", "Center" ],
    -	paintStyle:{ lineWidth:5, strokeStyle:"rgba(0,255,0,0.5)" },
    -	endpointStyle:{ radius:125 }
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window7 to window8 with a 10 pixel wide blue Connector, anchored on the top left of window7 and the bottom right of window8:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window7",
    -	target:"window8",
    -	paintStyle:{ lineWidth:10, strokeStyle:"blue" },
    -	anchors:[ "TopLeft", "BottomRight" ]
    -});
    -</pre>
    -					</div>
    -				</li>
    -				<li>Connect the bottom right corner of window4 to the top left corner of window5, with rectangular endpoints of size 40x40 and a hover color of light blue:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window4",
    -	target:"window5",
    -	anchors:["BottomRight","TopLeft"],
    -	paintStyle:{lineWidth:7,strokeStyle:'rgb(131,8,135)'},
    -	hoverPaintStyle:{ strokeStyle:"rgb(0, 0, 135)" },
    -	endpointStyle:{ width:40, height:40 },
    -	endpoint:"Rectangle",
    -	connector:"Straight"
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window1 to window2 with the default paint settings but provide some drag options (which are passed through to the underlying library's draggable call):
    -					<div class="code">
    -						<pre>jsPlumb.connect({source:'window1', target:'window2', dragOptions:{cursor:'crosshair'}});</pre>
    -					</div>
    -				</li>
    -</div>
    -		
    -		<div class="section">
    -			<a id="dragAndDropExamples"><h3>Draggable Connections Examples</h3></a>
    -			This is a list of examples of how to use jsPlumb to create Connections using drag and drop.
    -			<p>
    -			The basic procedure is:
    -			<ol>
    -				<li>1a. Create Endpoints and register them on elements in your UI</li>
    -				<li>1b. alternatively, create a source endpoint and then make some element a drop target </li>
    -				<li>2. Drag and Drop</li>
    -			</ol>
    -			That's all there is to it.  Of course there are plenty of options you can set when doing this...it will be easier
    -			to show you some examples:
    -			</p>
    -			<ul>
    -				<li>Define an Endpoint with default appearance, that is both a source and target of new Connections:
    -<div class="code">
    -<pre>var endpointOptions = { isSource:true, isTarget:true }; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Register that Endpoint on window3, specifying that it should be located in the top center of the element:
    -<div class="code">
    -<pre>var window3Endpoint = jsPlumb.addEndpoint('window3', { anchor:"TopCenter" }, endpointOptions );  
    -</pre>
    -</div>
    -Notice here the usage of the three-argument addEndpoint  - we can reuse 'endpointOptions' with a different Anchor for another element.  This is a useful
    -practice to get into.<br/><br/>
    -				</li>
    -				
    -				<li>Now register that Endpoint on window4, specifying that it should be located in the bottom center of the element:
    -<div class="code">
    -<pre>var window4Endpoint = jsPlumb.addEndpoint('window4', { anchor:"BottomCenter" }, endpointOptions );  
    -</pre>
    -</div>
    -Now we have two Endpoints, both of which support drag and drop of new Connections.  We can use these to make a programmatic Connection, too, though:<br/><br/>
    -				</li>				
    -				
    -				<li>Connect window3 to window4 with a 25px wide yellow Bezier that has a 'curviness' of 175:
    -<div class="code">
    -<pre>jsPlumb.connect({ 
    -	source:window3Endpoint,
    -	target:window4Endpoint,
    -	connector: [ "Bezier", 175 ],
    -	paintStyle:{ lineWidth:25, strokeStyle:'yellow' }
    -});  
    -</pre>
    -</div>
    -				</li>
    -				
    -				<li>Define an Endpoint that creates Connections that are 20px wide straight lines, that is both a source and target of new Connections,
    -				and that has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Define an Endpoint that will be anchored to "TopCenter".  It creates Connections that are 20px wide straight lines, that is both a source and target of new Connections,
    -				and that has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchor:"TopCenter", 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Define an Endpoint that will create a dynamic anchor which can be positioned at "TopCenter" or "BottomCenter".  It creates Connections that are 20px wide straight lines, it is both a source and target of new Connections,
    -				and it has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchor:[ "TopCenter", "BottomCenter" ], 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Exactly the same as before, but shows how you can use "anchors" instead of "anchor", if that makes you feel happier:
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchors:[ "TopCenter", "BottomCenter" ], 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>								
    -				
    -<li>Define an Endpoint that is a 30px blue dot, creates Connections that are 20px wide straight lines, is both a source and target of new Connections,
    -				has a 'scope' of 'blueline', and has an event handler that pops up an alert (note: the event handler name means this example is jQuery - MooTools
    -				and YUI3 use different event handler names):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{fillStyle:'blue'},
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ drop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -			<li>Same example as before, but this is for MooTools, and the Endpoint can support up to 5 connections (the default is 1):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{ fillStyle:'blue' },
    -	maxConnections:5,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ onDrop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Same example again, but maxConnections being set to -1 means that the Endpoint has no maximum limit of Connections:
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", {radius:30} ],
    -	style:{ fillStyle:'blue' },
    -	maxConnections:-1,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ onDrop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Same example again, but for YUI3.  Note the drop callback is "drop:hit":
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{fillStyle:'blue'},
    -	maxConnections:-1,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ "drop:hit":function(e, ui) { alert('drop!'); } }	 	
    -}; 
    -</pre>
    -</div>				 				
    -				</li>				
    -				
    -	<li>Assign a UUID to the endpoint options created above, and add as Endpoints to "window1" and "window2":
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("window1", { uuid:"abcdefg" }, endpointOptions );
    -jsPlumb.addEndpoint("window2", { uuid:"hijklmn" }, endpointOptions );
    -</pre>
    -</div>	
    -	</li>
    -	
    -	<li>Connect the two Endpoints we just registered on "window1" and "window2":
    -<div class="code">
    -<pre>
    -jsPlumb.connect({uuids:["abcdefg", "hijklmn"]});
    -</pre>
    -</div>	
    -	</li>
    -	
    -	<li>Create a source Endpoint, register it on some element, then make some other element a Connection target:
    -<div class="code">
    -<pre>
    -var sourceEndpoint = { isSource:true, endpoint:[ "Dot", { radius:50 } ] };
    -var targetEndpoint = { endpoint:[ "Rectangle", { width:10, height:10 } ] };
    -jsPlumb.addEndpoint( "window1", sourceEndpoint );
    -jsPlumb.makeTarget( "window2", targetEndpoint );
    -</pre>
    -</div>	
    -Notice that the endpoint definition we use on the target window does not include the "isTarget:true" directive.  jsPlumb ignores that flag when
    -creating a connection using an element as the target; but if you then tried to drag another connection to the Endpoint that was created, you would
    -not be able to.  To permit that, you would set 'isTarget:true' on the targetEndpoint options defined above.
    -	
    -	</li>
    -				
    -			</ul>
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="utilityFunctions">Utility Functions</a></h3>
    -			<ul>
    -				<li>Detach window5 from all connections
    -					<div class="code">
    -						<pre>jsPlumb.detachAll("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Hide all window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.hide("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Hide all window5's connections endpoints
    -					<div class="code">
    -						<pre>jsPlumb.hide("window5", true);</pre>
    -					</div>
    -				</li>
    -				<li>Show all window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.show("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Show all window5's connections and endpoints.  Note that in the case that you call jsPlumb.show with two arguments, jsPlumb
    -				will also not make a connection visible if it determines that the other endpoint in the connection is not visible.
    -					<div class="code">
    -						<pre>jsPlumb.show("window5", hide);</pre>
    -					</div>
    -				</li>				
    -				<li>Toggle the visibility of window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.toggle("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of all of window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.repaint("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of all of window5, window6 and window11's connections
    -					<div class="code">
    -						<pre>jsPlumb.repaint( [ "window5", "window6", "window11" ] );</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of every connection
    -					<div class="code">
    -						<pre>jsPlumb.repaintEverything();</pre>
    -					</div>
    -				</li>
    -				<li>Detach every connection that the given instance of jsPlumb is managing
    -					<div class="code">
    -						<pre>jsPlumb.detachEveryConnection();</pre>
    -					</div>
    -				</li>
    -				<li>Detach all connections from "window1"
    -					<div class="code">
    -						<pre>jsPlumb.detachAllConnections("window1");</pre>
    -					</div>
    -				</li>
    -				<li>Remove the given Endpoint from element "window1", deleting its Connections. 
    -					<div class="code">
    -						<pre>jsPlumb.removeEndpoint("window1", someEndpoint);</pre>
    -					</div>
    -				</li>
    -				<li>Remove all Endpoints for the element 'window1', deleting their Connections.
    -					<div class="code">
    -						<pre>jsPlumb.removeAllEndpoints("window1");</pre>
    -					</div>
    -				</li>	
    -				<li>Removes every Endpoint managed by this instance of jsPlumb, deleting all Connections.
    -				This is the same as jsPlumb.reset(), effectively, but it does not clear out the event listeners list. 
    -					<div class="code">
    -						<pre>jsPlumb.removeEveryEndpoint();</pre>
    -					</div>
    -				</li>
    -				<li>Deletes the given Endpoint and all its Connections. 
    -					<div class="code">
    -						<pre>jsPlumb.deleteEndpoint(endpoint);</pre>
    -					</div>
    -				</li>
    -				<li>Removes every endpoint, detaches every connection, and clears the event listeners list.  Returns jsPlumb instance to its initial state.  
    -					<div class="code">
    -						<pre>jsPlumb.reset();</pre>
    -					</div>
    -				</li>	
    -				<li>Library-agnostic method to get a selector from the given selector specification.  This function is used mostly by the jsPlumb demos, to cut down on repeating code across the different libraries.  But perhaps you are writing something with jsPlumb that you also wisht to be library agnostic - this function can help you. The returned object, in each of jQuery, MooTools and YUI, has a ".each" iterator.
    -					<div class="code">
    -						<pre>var selector = jsPlumb.getSelector(".someSelector .spec");</pre>
    -					</div>					
    -				</li>
    -				<li>Set window1 to be not draggable, no matter what some jsPlumb command may request.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggable("window1", false);</pre>
    -					</div>
    -				</li>
    -				<li>Set window1 and window2 to be not draggable, no matter what some jsPlumb command may request.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggable(["window1","window2"], false);</pre>
    -					</div>
    -				</li>
    -				<li>Sets whether or not elements that are connected are draggable by default.
    -				The default for this is true.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggableByDefault(false);</pre>
    -					</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (all libraries). Passes in an on drag callback
    -				<div class="code">
    -					<pre>jsPlumb.draggable("window1");</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 and window2 as draggable elements (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(["window1","window2"]);</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable("window1");</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (jQuery)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (MooTools)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($$(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (YIU3)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(Y.all(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (jQuery)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($("#window1"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (MooTools)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($("window1"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (YUI3)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(Y.one("window1"));</pre>
    -				</div>
    -				</li>
    -			</ul>
    -		</div>					
    -		
    -		<div class="section">
    -			<h3><a id="developingJsPlumb">Advanced Topics</a></h3>
    -			<h4><a id="which">Which files are which?</a></h4>
    -			In development, jsPlumb is broken up into seven scripts:
    -				<ul>
    -					<li>- jsPlumb-x.x.x.js
    -					<p>This is the main jsPlumb engine.</p>					
    -					</li>
    -					<li>- jsPlumb-defaults-x.x.x.js
    -					<p>This contains the default Anchor, Endpoint, Connector and Overlay implementations.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-canvas-x.x.x.js
    -					<p>This contains the HTML5 canvas render code.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-svg-x.x.x.js
    -					<p>This contains the SVG render code.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-vml-x.x.x.js
    -					<p>This contains the VML render code.</p>
    -					</li>
    -					<li>- jsPlumb-connectors-statemachine-x.x.x.js
    -					<p>This State Machine connector.</p>
    -					</li>
    -					<li>- &lt;LIBRARY_PREFIX&gt;.jsPlumb-x.x.x.js
    -					<p>This contains library-specific helper methods.  jsPlumb ships with three of these - one each for jQuery, MooTools and YUI3. See below for information on how to create a new library implementation.</p>
    -					</li>
    -					<li>- jsBezier-0.3-min.js
    -					<p>These are the Bezier curve functions; they are maintained in a separate project called <a href="http://code.google.com/p/jsbezier/">jsBezier</a></p>
    -					</li>
    -				</ul>
    -			These seven files are packaged together to form the scripts that people use, for example:
    -			<ul>
    -				<li>jquery.jsPlumb-1.3.8-all.js
    -				<p>Contains jsPlumb-1.3.8.js, jsPlumb-defaults-1.3.8.js, jsPlumb-renderers-canvas-1.3.8.js, jsPlumb-renderers-svg-1.3.8.js, jsPlumb-renderers-vml-1.3.8.js, jsPlumb-connectors-statemachine-1.3.8.js, jquery.jsPlumb-1.3.8.js and jsBezier-0.3-min.js</p>
    -				</li>
    -				<li>jquery.jsPlumb-1.3.8-all-min.js
    -				<p>A minified version of the script above (minified using the YUI Compressor)</p>
    -				</li>
    -			</ul>
    -			<h4><a id="pluggableLibrarySupport">Pluggable Library Support</a></h4>
    -		Out of the box, jsPlumb can be run on top of jQuery, MooTools or YUI3.  This is achieved by
    -		delegating several core methods - tasks such as finding an element by id, finding an element's
    -		position or dimensions, initialising a draggable, etc - to the library in question.
    -		<p>To develop one of these, your test page should include the first two scripts discussed above, and
    -		then your own script containing your library specific functionality.  The existing implementations
    -		may be documented well enough for you to create your own, but contact me if you need assistance.  If you do this, it would be
    -		great to share it with everyone...</p>   
    -		<p>There is a group of people working on an ExtJS adapter currently.  There is no schedule for when this will be completed though.</p>
    -
    -			<!-- h4><a id="customConnectors">Custom Connectors</a></h4>			
    -					
    -			You can provide your own connectors if you need to. A Connector consists of two functions, which work as a pair.  First a call is made to the <em>compute</em> function:
    -
    -			<div class="code">
    -<pre>
    -this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) {
    -	...
    -	return dimensions;
    -}
    -</pre>
    -			</div>
    -			which is expected to return a list that the <em>paint</em> function can make sense of.  The first four entries in the
    -			list <strong>must be</strong> the [x,y,width,height] values for the canvas that the connector will be drawn on; jsPlumb will
    -			use this information to size the canvas prior to calling the Connector's <em>paint</em> function.  Therefore it
    -			is the Connector's responsibility to ensure that the returned dimensions describe a large enough space for
    -			the line that will be drawn on it.</p><p>The next four elements <strong>must be</strong> the coordinates of the two endpoints of the line
    -			you are going to draw.</p><p>The remainder of the items in the returned list are arbitrary, and will
    -			vary between Connector implementations; this list is passed in to a Connector's <em>paint</em> function, so each
    -			implementation will put into the list whatever it needs to paint itself.  For instance, the straight line
    -			connector only needs the [x,y] location of each end of the line it will paint, and that is one of the required entries, so
    -			it does not have to do anything extra,  whereas the Bezier connector adds the location of the two control points.  Other types of
    -			Connectors will do whatever is appropriate for their particular situation.
    -			</p>
    -			This is the method signature for the <em>paint</em> function:
    -			<div class="code">
    -				<pre>this.paint = function(dimensions, ctx) { .. }</pre>
    -			</div>
    -
    -			here, the 'dimensions' argument to the 'paint' function is the return value of the 'compute' function.  The 'ctx' argument is the Canvas context; you
    -			will do all your drawing on this.
    -			</p>
    -			To change the connector from the default, specify it in your connect call:
    -			<div class="code">
    -				<pre>jsPlumb.connect({source:'someWindow', target:'otherWindow', connector:new jsPlumb.Connectors.Straight()});</pre>
    -			</div>
    -			note that you can use shorthand for connectors if you don't need to specify parameters to it:
    -			<div class="code">
    -				<pre>jsPlumb.connect({source:'someWindow', target:'otherWindow', connector:"Straight"});</pre>
    -			</div>
    -			This works in the same way as specifying Anchors as string does.
    -<p>
    -The section below this discusses Overlays, which allow you to decorate Connectors with
    -			things such as labels or arrows or whatever else you like. Overlays can only work with Connectors that implement a few helper functions.
    -			<h4>The concept of <strong>location</strong></h4>
    -			Before discussing the helper functions you first must be across the concept of 'location' as used by this mechanism.  The <em>location</em>
    -			on a connector can be a decimal value between 0 and 1 inclusive, which marks a proportionate amount of travel <em>along the path
    -			inscribed by the Connector</em>.  For a straight line connector this is a simple piece of maths, but for Bezier connectors it's a little
    -			bit more involved.
    -			<h4>Required Helper Methods</h4>
    -			<ul>
    -				<li><strong>pointOnPath(location)</strong> - returns an [x,y] point corresponding to the given location</li>
    -				<li><strong>pointAlongPathFrom(location, distance)</strong> - returns an [x,y] point corresponding to travelling 'distance' pixels along the connector from 'location'.</li>
    -				<li><strong>gradientAtPoint(location)</strong> - returns the connector's gradient at the given location.  For linear connectors such as Straight this is constant, but for Bezier connectors the gradient changes continually.</li>
    -				<li><strong>perpendicularToPathAt(location, distance, length)</strong> - returns a line that is perpendicular to (and centered on) the connector at 'distance' from the given location, with length 'length'.</li>
    -			</ul>
    -</p>
    -<h4><a id="customEndpoints">Custom Endpoints</a></h4>
    -<p>To create your own Endpoint implementation, you need to implement a single method:
    -				<div class="code"><pre>paint : function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { ... }</pre></div>
    -				The arguments to this method are as follows:
    -				<ul>
    -					<li>anchorPoint - [x,y] location of the anchor point on screen</li>
    -					<li>orientation - [x,y] hints for the general direction the anchor points to</li>
    -					<li>canvas - the canvas to draw into</li>
    -					<li>endpointStyle - Javascript object containing style directives as discussed above.  The contents of this are arbitrary, so if you write a new Endpoint that needs some extra settings, you can add them no hassle.</li>
    -					<li>connectorPaintStyle - the style being used to paint the associated Connector.</li>
    -				</ul>
    -			</p>
    -			<p>
    -				It is your responsibility to size and locate the canvas to suit your needs. jsPlumb provides the following
    -				helper method to assist you:
    -				<div class="code"><pre>jsPlumb.sizeCanvas(canvas, x, y, width, height);</pre></div>
    -				Allows you to locate the canvas on screen and to size it.
    -			</p>
    -<h4><a id="customOverlays">Custom Overlays</a></h4>
    -			Overlays can only work with Connectors that implement the methods <strong>pointOnPath</strong>, <strong>pointAlongPathFrom</strong>,
    -			<strong>gradientAtPoint</strong>, and <strong>perpendicularToPathAt</strong>.  These are discussed in more detail in the Custom Connectors section above.  Both of the Connectors
    -			that come with jsPlumb - Straight and Bezier -implement these methods; if you write a custom connector, or have written a custom connector, you will need to supply
    -			them.   
    -			<h4>Overlay Interface</h4>
    -			An Overlay is required to implement two methods in order to be usable by jsPlumb:
    -			<ul>
    -				<li><strong>computeMaxSize(connector, context)</strong> - returns an integer value indicating the larger of this overlay's width and height.  It is used by jsPlumb to ensure that the canvas is large enough to accomodate the overlay.  The examples below should help to clarify this.</li>
    -				<li><strong>draw(connector, ctx)</strong> - draws the overlay.  What happens in this method is up to the given implementation.</li>
    -			</ul>
    -			<h4>computeMaxSize methods</h4>
    -			This is the Arrow overlay's computeMaxSize method:
    -			<div class="code">
    -<pre>
    -this.computeMaxSize = function() { return width; }
    -</pre>			
    -			</div>
    -Here, <em>width</em> is a private member of Arrow that indicates the width of the arrow's tail.  So the Arrow overlay reports that figure as the width it needs.					
    -Contrast this with the Label Overlay's computeMaxSize method:
    -		<div class="code">
    -<pre>
    -this.computeMaxSize = function(connector, ctx) {
    -  	if (labelText) {
    -  		ctx.save();
    -        if (self.labelStyle.font) ctx.font = self.labelStyle.font;
    -         var t = ctx.measureText(labelText).width;			            
    -		// a fake text height measurement: use the width of upper case M
    -		var h = ctx.measureText("M").width;					
    -		labelPadding = self.labelStyle.padding || 0.25;
    -		labelWidth = t + (2 * t * labelPadding);
    -		labelHeight = h + (2 * h * labelPadding);
    -		ctx.restore();
    -		return Math.max(labelWidth, labelHeight);
    -  	}
    - 	return 0;
    - };
    -</pre>			
    -			</div>
    -The Label overlay has to use the context to determine how big it will be on screen.
    -
    -<h4>draw methods</h4>
    -To give you a taste for how you can interact with a connector, consider the first few lines of the Arrow overlay's draw method:
    -<div class="code">
    -<pre>
    -this.draw = function(connector, ctx) {
    -	// this is the arrow head position
    -	var hxy = connector.pointAlongPathFrom(self.loc, length / 2);		
    -	// this is the center of the tail
    -	var txy = connector.pointAlongPathFrom(self.loc, -length / 2), tx = txy[0], ty = txy[1];
    -	// this is the tail vector
    -	var tail = connector.perpendicularToPathAt(self.loc, -length / 2, width);
    -	
    -	...
    -</pre>
    -</div>
    -	<p>The first two calls to the connector use the <strong>pointAlongPathFrom</strong> method, which passes <em>self.loc</em> as the location of the
    -	point on the path, and <em>length / 2</em> as the number of pixels along the path to traverse.</p>
    -	<p><em>self.loc</em> is Arrow's internal member describing the location of the overlay, and it is a decimal (between 0 and 1 inclusive) that points to
    -	a location at some distance along the path inscribed by the connector.  So these first two calls get us [x,y] locations of points on 
    -	the connector that mark the head and tail points for the arrow.  
    -	</p>		
    -	<p>
    -	The <strong>connector.perpendicularToPathAt(self.loc, -length / 2, width)</strong> call returns a line description of a line that is perpendicular to, and centered on, the connector
    -	at "-length / 2" pixels from the given point, with the given width (width is an internal member of Arrow).  At this point, the Arrow object has the three main points it
    -	needs in order to draw itself.
    -	</p>
    -		</div-->
    -</div>
    -
    -</body>
    -
    -</html>
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.8/index.html b/demo/doc/archive/1.3.8/index.html
    deleted file mode 100644
    index 536be786d..000000000
    --- a/demo/doc/archive/1.3.8/index.html
    +++ /dev/null
    @@ -1,82 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.8 documentation</title>
    -		<link rel="stylesheet" target="contentFrame" href="/mp.css"></link>
    -		<link rel="stylesheet" target="contentFrame" href="jsPlumbDoc.css"></link>
    -	</head>
    -	<body>
    -		<div class="index section">
    -			<h3>jsPlumb 1.3.8</h3>
    -			<ul>
    -				<li><a target="contentFrame" href="content.html#summary">Summary</a></li>
    -				<li><a target="contentFrame" href="content.html#changes">Changes since version 1.3.7</a></li>
    -				
    -				<li><h4>Setup</h4></li>
    -				<li><a target="contentFrame" href="content.html#imports">Required Imports</a></li>
    -				<li><a target="contentFrame" href="content.html#renderMode">Render Mode</a></li>
    -				<li><a target="contentFrame" href="content.html#initializing">Initializing jsPlumb</a></li>				
    -				<li><a target="contentFrame" href="content.html#multipleInstances">Multiple jsPlumb Instances</a></li>
    -				<li><a target="contentFrame" href="content.html#zIndex">Z-index Considerations</a></li>
    -				<li><a target="contentFrame" href="content.html#containerDefault">Where does jsPlumb add elements?</a></li>
    -				<li><a target="contentFrame" href="content.html#elementDragging">Element dragging</a></li>
    -				
    -				<li><h4>Introduction</h4></li>
    -				<li><a target="contentFrame" href="content.html#jsPlumbBasics">Basic Concepts</a></li>
    -				<li><a target="contentFrame" href="content.html#anchors">Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#staticAnchors">Static Anchors</a></li>				
    -				<li><a target="contentFrame" href="content.html#dynamicAnchors">Dynamic Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#continuousAnchors">Continuous Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#definitions">Connector, Endpoint &amp; Overlay Definitions</a></li>
    -				<li><a target="contentFrame" href="content.html#connectors">Connectors</a></li>
    -				<li><a target="contentFrame" href="content.html#endpoints">Endpoints</a></li>
    -				<li><a target="contentFrame" href="content.html#overlays">Overlays</a></li>
    -				<li><a target="contentFrame" href="content.html#defaults">Defaults</a></li>								
    -				
    -				<li><h4>Connections</h4></li>
    -				<li><a target="contentFrame" href="content.html#simpleConnections">Programmatic Connections</a></li>
    -				<li><a target="contentFrame" href="content.html#draggableConnections">Connections using Drag and Drop</a></li>
    -				<li><a target="contentFrame" href="content.html#sourcesAndTargets">Elements as Sources &amp; Targets</a></li>
    -				<li class="sub"><a target="contentFrame" href="content.html#makeTarget">makeTarget</a></li>
    -				<li class="sub"><a target="contentFrame" href="content.html#makeSource">makeSource</a></li>
    -				<li><a target="contentFrame" href="content.html#dragOptions">Drag Options</a></li>				
    -				<li><a target="contentFrame" href="content.html#dropOptions">Drop Options</a></li>
    -				<li><a target="contentFrame" href="content.html#dragAndDropScope">Drag and Drop Scope</a></li>
    -				
    -				<li><h4>Interceptors</h4></li>				
    -				<li><a target="contentFrame" href="content.html#beforeDrop">Before Drop</a></li>
    -				<li><a target="contentFrame" href="content.html#beforeDetach">Before Detach</a></li>
    -				
    -				<li><h4>Appearance</h4></li>
    -				<li><a target="contentFrame" href="content.html#paintstyles">Paint Styles</a></li>
    -				<li><a target="contentFrame" href="content.html#hoverpaintstyles">Hover Paint Styles</a></li>
    -				<li><a target="contentFrame" href="content.html#gradients">Gradients</a></li>				
    -				
    -				<li><h4>Miscellaneous</h4></li>
    -				<li><a target="contentFrame" href="content.html#cssclasses">CSS Class Reference</a></li>
    -				<li><a target="contentFrame" href="content.html#animation">Animation</a></li>
    -				<li><a target="contentFrame" href="content.html#connectionInfo">Retrieving Connection Information</a></li>
    -				<li><a target="contentFrame" href="content.html#selectConnections">Selecting Connections</a></li>
    -				
    -				<li><h4>Events</h4></li>
    -				<li><a target="contentFrame" href="content.html#events">jsPlumb Events</a></li>
    -				<li><a target="contentFrame" href="content.html#connectionEvents">Connection Events</a></li>
    -				<li><a target="contentFrame" href="content.html#endpointEvents">Endpoint Events</a></li>
    -				<li><a target="contentFrame" href="content.html#overlayEvents">Overlay Events</a></li>
    -				
    -				<li><h4>Examples</h4></li>				
    -				<li><a target="contentFrame" href="content.html#examples">Programmatic API Examples</a></li>
    -				<li><a target="contentFrame" href="content.html#dragAndDropExamples">Draggable Connections Examples</a></li>
    -				<li><a target="contentFrame" href="content.html#utilityFunctions">Utility Functions</a></li>				
    -				
    -				<li><h4>Develop</h4></li>
    -				<li><a target="contentFrame" href="content.html#which">Which files are which?</a></li>
    -				<li><a target="contentFrame" href="content.html#pluggableLibrarySupport">Pluggable Library Support</a></li>
    -				<!-- li><a target="contentFrame" href="content.html#customConnectors">Custom Connectors</a></li>
    -				<li><a target="contentFrame" href="content.html#customEndpoints">Custom Endpoints</a></li>
    -				<li><a target="contentFrame" href="content.html#customOverlays">Custom Overlays</a></li-->
    -			</ul>
    -			<strong>This document refers to release 1.3.8 of jsPlumb.</strong> <br/> 
    -			<strong>01 April 2012</strong>
    -		</div>		
    -	</body>
    -</html>
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.8/jsPlumbDoc.css b/demo/doc/archive/1.3.8/jsPlumbDoc.css
    deleted file mode 100644
    index bbe50c8ee..000000000
    --- a/demo/doc/archive/1.3.8/jsPlumbDoc.css
    +++ /dev/null
    @@ -1,126 +0,0 @@
    -body { 
    -	background-color:white; font-size:90%;
    -	background-image:url(../img/dynamicAnchorBg.jpg); 
    -}
    -/* docs */
    -.code { background-color:#e4e5ef; 
    -border:1px solid black; margin:1em; padding:0.8em; 
    -overflow:auto;
    -font-family:courier; 
    -font-size:8pt;
    -}
    -.section { border-bottom: 1px dotted black; padding:1em;font-family:helvetica;background-color:white;color:#333;}
    -.menu { 
    -	position:fixed; 
    -	right:10em; top:1em;
    -	background-color:white;
    -	padding:0.3em;
    -	opacity:0.8;
    -	z-index:5000;
    -	filter:alpha(opacity=80);
    -		box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -a {text-decoration:none;color:#05f;font-weight:bold;font-family:helvetica;}
    -a:hover {text-decoration:underline;}
    -.window, .label { 
    -	cursor:pointer;
    -	box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -#explanation { 
    -	background-color:white;
    -	color:black;
    -	border:0.25em solid #ddd; 
    -	position:absolute;
    -	right:3.5em;
    -	top:5em;
    -	width:22em;
    -	font-size:100%;
    -	-moz-border-radius:1em;
    -	border-radius:1em;
    -	padding:1em;
    -	z-index:1000;
    -	opacity:0.9;
    -	filter:alpha(opacity=90); 
    -	font-family:helvetica;
    -		box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -.selected {
    -	color:rgb(189,11,11);
    -}
    -
    -
    -/*  these styles are what jsPlumb attaches to the various elements it creates.
    -._jsPlumb_connector {
    -border:1px solid blue;
    -}
    -._jsPlumb_endpoint {
    -border:1px solid green;
    -}
    -._jsPlumb_overlay {
    -border:1px solid yellow;
    -}
    -*/
    -
    -
    -.section li {
    -list-style-type:none;
    -}
    -
    -li.bullet {
    -	list-style-type:circle;
    -	}
    -
    -
    -.section {
    -padding-left:2em;
    -font-size:10pt;
    -}
    -.index.section {
    -padding-left:0;
    -}
    -
    -.index {
    -width:20em;
    -font-size:9pt;
    -float:left;
    -background-color:#e4eeee;
    -overflow:auto;
    -padding-left:0.5em;
    -}
    -
    -.index li {
    -list-style-type:none;
    -}
    -
    -.index ul, .index h3 {
    -padding-left:1.5em;
    -
    -}
    -
    -#demo {
    -width:80%;
    -height:50em;
    -margin-left:3em;
    -margin-top:3em;
    -position:relative;
    -}
    -
    -.sub {
    -margin-left:1em;
    -}
    -
    -.table td {
    -	text-align:center;
    -}
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.8/usage.html b/demo/doc/archive/1.3.8/usage.html
    deleted file mode 100644
    index 0608fd36e..000000000
    --- a/demo/doc/archive/1.3.8/usage.html
    +++ /dev/null
    @@ -1,10 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.3.8 - documentation</title>		
    -	</head>
    -	
    -	<frameset cols="250,*">
    -  		<frame src="index.html" />
    -  		<frame src="content.html" name="contentFrame" />
    -	</frameset>	
    -</html>
    diff --git a/demo/doc/archive/1.3.9/content.html b/demo/doc/archive/1.3.9/content.html
    deleted file mode 100644
    index 1be515eec..000000000
    --- a/demo/doc/archive/1.3.9/content.html
    +++ /dev/null
    @@ -1,3232 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.4.0 - documentation</title>
    -		<link rel="stylesheet" href="/mp.css">
    -		<link rel="stylesheet" href="jsPlumbDoc.css">
    -	</head>
    -	<body>
    -
    -<div class="menu">
    -	<a href="http://jsplumb.org" class="mplink" target="_top">view demo</a>
    -	&nbsp;|&nbsp;<a href="mailto:simon.porritt@gmail.com" class="mplink">contact me</a>
    -	&nbsp;|&nbsp;<a href="http://jsplumb.tumblr.com" class="mplink">jsPlumb on Tumblr</a>	
    -</div>
    -
    -<!--
    -GWT
    -
    -http://fishfilosophy.blogspot.com/2011/12/how-to-connect-gwt-widgets-using.html?spref=tw
    -
    --->
    -
    -<div class="content">
    -		<div class="section">
    -			<h3><a id="summary">Summary</a></h3>
    -			jsPlumb allows you to connect elements on the screen using SVG, Canvas or VML, depending on the capabilities of the browser.
    -			<p>
    -				It can be used with jQuery, MooTools or YUI3 (or another library of your choice if you feel like implementing an adapter for it).
    -			</p>  
    -
    -			<h5>Browser Compatibility</h5>
    -			<p>jsPlumb 1.4.0 runs on everything from IE6 up.  There are some caveats, though, because of various browser/library bugs:
    -				<ul>
    -					<li class="bullet">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.
    -					<li class="bullet">Safari 5.1 has an SVG bug that prevents mouse events from being passed through the transparent area of an SVG element</li>
    -					<li class="bullet">MooTools has a bug when using SVG in Firefox 11</li>
    -				<ul>
    -				
    -			</p>
    -		</div>
    -		
    -		<div class="section">		
    -			<h3><a id="changes">Changes between 1.4.0 and 1.3.8</a></h3>
    -			
    -			<h5>Backwards Compatibility</h5>
    -			Just a couple of minor points in this release:
    -			<ul>
    -				<li><strong>paintStyle</strong> and <strong>hoverPaintStyle</strong> are no longer exposed on Endpoint or Connection. Use <strong>setPaintStyle</strong>, <strong>getPaintStyle</strong>, <strong>setHoverPaintStyle</strong> and <strong>setHoverPaintStyle</strong> instead.</li>
    -			</ul>
    -			
    -			<h5>New Features</h5>
    -			<ul>
    -				<li class="bullet">Straight connector in Canvas renderer now supports simple dashstyle</li>	
    -				<li class="bullet">clearListeners, fire and bind methods in jsPlumb, Connection and Endpoint now return themselves, so you can use them in a 'fluent' style.</li>
    -				<li class="bullet">paintStyle and hoverPaintStyle are no longer exposed on Endpoint or Connection.</li>
    -				<li class="bullet">getPaintStyle and getHoverPaintStyle methods added to Endpoint and Connection.</li>
    -				<li class="bullet">HoverClass added to jsPlumb defaults.</li> 
    -				<li class="bullet">Several new methods added for working with targets and sources:
    -					<ul>
    -						<li>setTargetEnabled</li>
    -						<li>setSourceEnabled</li>
    -						<li>isTarget</li>
    -						<li>isSource</li>
    -						<li>toggleTargetEnabled</li>
    -						<li>toggleSourceEnabled</li>
    -						<li>isTargetEnabled</li>
    -						<li>isSourceEnabled</li>
    -						<li>unmakeSource</li>
    -						<li>unmakeTarget</li>
    -						<li>unmakeEverySource</li>
    -						<li>unmakeEveryTarget</li>
    -					</ul>
    -					<br/>
    -				</li>
    -				<li class="bullet">makeSource and makeTarget now return the current jsPlumb instance.</li>
    -				<li class="bullet">added optional 'gap' parameter to Flowchart connector params. Lets you leave a gap between the end of the connector and the element to which it is connected.</li>
    -				<li class="bullet">SvgEndpoint and VmlEndpoint classes are now exposed on the DOM window, which allows for subclassing (eg. when you want to make your own Endpoints)</li>
    -				<li class="bullet">removeAllOverlays was not cleaning up elements from the DOM properly. now fixed.</li>
    -				<li class="bullet">added connectionDrag and connectionDragStop events to the jsPlumb object. These events are fired when an existing connection is dragged, and when that drag stops.  The only argument passed to a listener on these events is the Connection that is being or has just been dragged.</li>
    -			</ul>
    -
    -			<h5>Issues Resolved</h5>
    -			<ul>
    -				<li class="bullet"><strong>136</strong> - diamond/arrow overlays do not fire mouse events (SVG/VML renderers only)</li>
    -				<li class="bullet"><strong>223</strong> - extraneous IDs being created throughout the DOM</li>
    -				<li class="bullet"><strong>235</strong> - add setTargetEnabled and related methods</li>
    -				<li class="bullet"><strong>233</strong> - add getPaintStyle and getHoverPaintStyle to Connection and Endpoint.</li>
    -				<li class="bullet"><strong>238</strong> - overlays in canvas renderer for state machine connectors not appearing in the correct position.</li>
    -				<li class="bullet"><strong>239</strong> - document detachable/connectionsDetachable properly</li>
    -				<li class="bullet"><strong>242</strong> - typo in docs about diamond overlay</li>
    -				<li class="bullet"><strong>244</strong> - straight gradients rendered black or transparent</li>
    -				<li class="bullet"><strong>245</strong> - should not set scope on nodes when making them draggable via jsPlumb.draggable.</li>		
    -			</ul>
    -			
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="init">Setup</a></h3>
    -
    -            <h4><a id="imports">Required Imports</a></h4>
    -			<h4>jQuery</h4>
    -			    <ul>
    -				    <li class="bullet">jQuery 1.3.x or higher.</li>
    -				    <li class="bullet">jQuery UI 1.7.x or 1.8.x (if you wish to support drag and drop).</li>
    -			    </ul>
    -			    <div class="code">
    -<pre>&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/jquery-ui.min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/jquery.jsPlumb-1.4.0-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			<h4>MooTools</h4>
    -				<ul>
    -					<li class="bullet">MooTools Core 1.2.4 or higher. jsPlumb has been tested with 1.2.4 and 1.3.3.</li>
    -					<li class="bullet">Drag.Move from MooTools More 1.2.4.4 or higher (if you wish to support drag and drop).</li>
    -				</ul>
    -			<div class="code">
    -			<pre>&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/mootools/1.3.3/mootools-yui-compressed.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO_MOO_TOOLS_MORE_1_3_2_1"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/mootools.jsPlumb-1.4.0-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			<h4>YUI3</h4>
    -			<ul>
    -				<li class="bullet">YUI 3.3.x.  jsPlumb has been tested on 3.3.0 only; it is possible other 3.x.x versions will work.</li>
    -			</ul>
    -			<div class="code">
    -			<pre>&lt;script type="text/javascript" src="http://yui.yahooapis.com/3.3.0/build/simpleyui/simpleyui-min.js"&gt;&lt;/script&gt;
    -&lt;script type="text/javascript" src="PATH_TO/yui.jsPlumb-1.4.0-all-min.js "&gt;&lt;/script&gt;</pre>
    -			</div>
    -			
    -			<h4><a id="renderMode">Render Mode</a></h4>
    -			jsPlumb can use SVG, HTML5 Canvas elements or VML to render the various objects it adds to the display.  Most modern browsers support Canvas and SVG; IE&lt;9 browsers support neither.
    -			<p>
    -				By default, jsPlumb attempts to use <strong>jsPlumb.SVG</strong> as the render mode (prior to 1.3.4, the default renderer was Canvas), falling back to <strong>jsPlumb.VML</strong> if the 
    -				browser is IE&lt;9.  You can change the render mode by making this call:  
    -			</p>
    -			<div class="code">
    -<pre>jsPlumb.setRenderMode(jsPlumb.CANVAS);</pre>			
    -			</div>
    -			if, for some reason, you set the render mode to be <strong>jsPlumb.VML</strong> but you're in any browser other than IE, jsPlumb will use SVG.
    -			<p>
    -			The <strong>jsPlumb.setRenderMode</strong> method returns you the render mode that jsPlumb actually ended up setting.  Valid values for setRenderMode are:
    -			</p>
    -			<ul>
    -				<li class="bullet">jsPlumb.CANVAS</li>
    -				<li class="bullet">jsPlumb.SVG</li>
    -				<li class="bullet">jsPlumb.VML</li>
    -			</ul>			
    -			<h5>Performance Considerations</h5>
    -			<p>
    -			SVG uses less memory than Canvas and has support for mouse events built in, so I recommend using
    -			SVG unless you have a really good reason for wanting Canvas. Of course, you might consider that horrendous Safari bug a really good reason for wanting Canvas.
    -			</p>
    -			
    -			<h4><a id="initializing">Initializing jsPlumb</a></h4>
    -			You should not start making calls to jsPlumb until the DOM has been initialized - perhaps no surprises there.  With YUI, though, the asynchronous
    -			nature of the script loading process means that you are not guaranteed that jsPlumb is ready as soon as the DOM is.  To handle this,
    -			you should bind to the "ready" event on jsPlumb (or the instance of jsPlumb you are working with):
    -			<div class="code">
    -			<pre>jsPlumb.bind("ready", function() {
    -...	
    -		
    -// your jsPlumb related init code goes here
    -
    -...
    -
    -});						
    -			</pre>
    -			</div>
    -			There's a helper method that can save you approximately 9 (count them!) characters:
    -			<div class="code">
    -			<pre>jsPlumb.ready(function() {
    -...	
    -		
    -// your jsPlumb related init code goes here
    -
    -...
    -
    -});						
    -			</pre>
    -			</div>
    -			<p>
    -			For jQuery and MooTools you do not actually need to do this; by the time the DOM ready event is fired in each of those libraries you can be sure 
    -			all the JS we need has been loaded.  But in terms of explicitly calling out that you are waiting for jsPlumb, it seems like a good practice to bind to the "ready" event.
    -			</p>
    -			<p>
    -			If you bind to the "ready" event after jsPlumb has already been initialized, your callback will be executed immediately.
    -			</p>			
    -
    -				
    -<h4><a id="multipleInstances">Multiple jsPlumb instances</a></h4>
    -			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 <strong>getInstance</strong> method, for example:
    -<div class="code">
    -<pre>
    -var firstInstance = jsPlumb.getInstance();
    -</pre>
    -</div>
    -The variable 'firstInstance' can now be treated exactly as you would treat the 'jsPlumb' variable - you can set
    -defaults, call the connect method, whatever:
    -<div class="code">
    -<pre>
    -firstInstance.importDefaults({
    -  Connector : [ "Bezier", { curviness: 150 } ],
    -  Anchors : [ "TopCenter", "BottomCenter" ]
    -});
    -
    -firstInstance.connect({source:"element1", target:"element2", scope:"someScope" });
    -</pre>
    -</div>
    -
    -<strong>getInstance</strong> optionally takes an object that provides the defaults:
    -<div class="code">
    -<pre>
    -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" });
    -</pre>
    -</div>
    -
    -		<h4><a id="zIndex">Z-index Considerations</a></h4>
    -		<p>
    -			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. 
    -		</p>  
    -		<p>
    -			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).  			
    -		</p>
    -		<p>
    -			To help you organise z-indices correctly, jsPlumb adds a CSS class to each type of
    -			element it adds. They are as follows:			
    -		</p>
    -		<table style="color:black;width:70%;font-size:90%;">
    -			<tr><td><strong>type of element</strong></td><td><strong>class added</strong></td></tr>
    -			<tr><td>Endpoint</td><td>_jsPlumb_endpoint</td></tr>
    -			<tr><td>Connector</td><td>_jsPlumb_connector</td></tr>
    -			<tr><td>Overlay</td><td>_jsPlumb_overlay</td></tr>
    -		</table>
    -
    -		<h4><a id="containerDefault">Where does jsPlumb add elements?</a></h4>
    -		<p>
    -			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.
    -		</p>
    -		<p>
    -			Prior to 1.3.0, jsPlumb added everything to the end of the <em>body</em> element. This
    -			has the advantage of being the most flexible arrangement in terms of supporting what
    -			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!   
    -		</p>
    -		<p>
    -			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 (<strong>not</strong> 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.
    -		</p>
    -		
    -		<p>
    -			In general, the default behaviour means that you cannot do things like this:
    -		</p>
    -		<div class="code">
    -<pre>
    -&lt;body&gt;
    -	&lt;div id="container0"&gt;
    -		&lt;div id="node0"&gt;&lt;/div&gt;
    -	&lt;/div&gt;
    -	&lt;div id="container1"&gt;
    -		&lt;div id="node1"&gt;&lt;/div&gt;
    -	&lt;/div&gt;
    -&lt;/body&gt;
    -
    -&lt;script type="text/javascript"&gt;
    -
    -	var e0 = jsPlumb.addEndpoint("node0"),
    -	e1 = jsPlumb.addEndpoint("node1");
    -
    -	jsPlumb.connect({ source:e0, target:e1 });
    -&lt;/script&gt;
    -</pre>		
    -		</div>
    -<p>
    -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):  
    -</p>
    -		<div class="code">
    -<pre>
    -&lt;body&gt;
    -	&lt;div id="container0"&gt;
    -	&lt;/div&gt;
    -	&lt;div id="container1"&gt;
    -	&lt;/div&gt;
    -&lt;/body&gt;
    -
    -&lt;script type="text/javascript"&gt;
    -
    -	var e0 = jsPlumb.addEndpoint("container0"),
    -	e1 = jsPlumb.addEndpoint("container1");
    -
    -	jsPlumb.connect({ source:e0, target:e1 });
    -&lt;/script&gt;
    -</pre>		
    -		</div>
    -	now 'e0' and 'e1' have the same parent - the <em>body</em> element. 
    -	
    -	<p>Remember that you can use the <strong>anchor</strong> parameter to an <strong>addEndpoint</strong>
    -	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. 
    -	</p>									
    -	<p><strong>Note regarding the drawEndpoints option on jsPlumb.connect</strong>: 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). 
    -	</p>
    -	
    -	<h5>Overriding the default behaviour</h5>
    -	<p>
    -From version 1.3.3, the <strong>container</strong> 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 <strong>jsPlumb.Defaults.Container</strong> property, or you can set a <strong>container</strong> parameter on calls to <strong>addEndpoint</strong> or <strong>connect</strong> (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:
    -	</p>
    -		
    -Set a container to use as the default container, using a jQuery selector (you can supply MooTools/YUI selectors if that's the library you're using), and then add an Endpoint.  The canvas (or SVG/VML element) created will be a child of the document body:
    -		<div class="code">
    -<pre>
    -jsPlumb.Defaults.Container = $("body");
    -...
    -jsPlumb.addEndpoint(someDiv, { endpoint options });
    -</pre>		
    -		</div>
    -		
    -			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":
    -		<div class="code">
    -<pre>
    -jsPlumb.Defaults.Container = "containerId";
    -...
    -jsPlumb.connect({ source:someDiv, target:someOtherDiv });
    -</pre>		
    -		</div>
    -		
    -		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":
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({ source:someDiv, target:someOtherDiv, container:$("#containerId") });
    -</pre>		
    -		</div>
    -				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":
    -		<div class="code">
    -<pre>
    -jsPlumb.addEndpoint(someDiv, { ..., container:"containerId" });
    -</pre>		
    -		</div>
    -
    -		<h4><a id="elementDragging">Element Dragging</a></h4>
    -		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 <strong>jsPlumb.draggable(...)</strong>:
    -<div class="code">
    -<pre>jsPlumb.draggable("elementId");</pre>
    -</div>
    -You can also pass a selector from your library in to this method:
    -<div class="code">
    -<pre>jsPlumb.draggable($(".someClass");</pre>
    -</div>
    -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.  A common requirement when using jQuery, for instance, is to set the containment of elements that you are making draggable.  Here's how you would tell jsPlumb to initialise some set of elements to be draggable but only inside their parent:
    -<div class="code">
    -<pre>jsPlumb.draggable($(".someClass", {
    -	containment:"parent"
    -});</pre>
    -</div>
    -		<h5>Dragging nested elements</h5>
    -jsPlumb takes nesting into account when handling draggable elements. For example, say you have this markup:
    -<div class="code">
    -<pre>
    -&lt;div id="container"&gt;
    -  &lt;div class="childType1"&gt;&lt;/div&gt;
    -  &lt;div class="childType2"&gt;&lt;/div&gt;
    -&lt;/div&gt;
    -</pre>
    -</div>
    -..and then you connect one of those child divs to something, and make "container" draggable:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:$("#container .childType1"),
    -	target:"somehere else"
    -});
    -
    -jsPlumb.draggable("container");
    -</pre>
    -</div>
    -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.
    -
    -
    -		</div>
    -				
    -		<div class="section">
    -			<h3><a id="jsPlumbBasics">Basic Concepts</a></h3>			
    -	
    -			jsPlumb is all about connecting things together, so the core abstraction in jsPlumb is the <strong>Connection</strong> object, which is itself
    -			broken down into these four concepts:
    -
    -			<ul>
    -				<li><strong>Anchor</strong> - a location, relative to an element's origin, at which an Endpoint can exist. You do not create these yourself; you 
    -				supply hints to the various jsPlumb functions, which create them as needed.  They have no visual representation; they are a logical position only. Anchors can be referenced by name, for the Anchors that jsPlumb ships with, or a four element array describing [ x, y, x orientation, y orientation ]. 
    -				See the <a href="#anchors">anchors</a> section for more detail.<br/><br/></li>
    -				<li><strong>Endpoint</strong> - the visual representation of one end of a Connection.  You can create and attach these to elements yourself, which you are required to do to support drag and drop, or have jsPlumb create them when creating a Connection programmatically using jsPlumb.connect(...).  You can also join two Endpoints programmatically, by passing them as arguments to jsPlumb.connect(...).<br/><br/></li>				
    -				<li><strong>Connector</strong> - the visual representation of the line connecting two elements in the page.  jsPlumb has four types of these 
    -				available as defaults - a Bezier curve, a straight line, 'flowchart' connectors and 'state machine connectors. You do not interact with Connectors; 
    -				you just specify definitions of them when you need to - see <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions</a> <br/><br/></li>
    -				<li><strong>Overlay</strong> - a UI component that is used to decorate a Connector, such as a Label, Arrow, etc.<br/><br/></li>
    -			</ul>
    -						
    -			<p>One Connection is made up of two Endpoints, a Connector, and zero or more Overlays working together to join two elements.</p>
    -			<p>jsPlumb's public API exposes only Connection and Endpoint, handling the creation and configuration of everything else internally. But you still need to be across the concepts encapsulated by Anchor, Connector and Overlay.
    -			</p>
    -			</div>
    -			
    -			<div class="section">
    -			<h3><a id="anchors">Anchors</a></h3>			
    -	
    -			An Anchor models the notion of where on an element a Connector should connect.  There are three main types of Anchors:
    -			<ul>
    -			<li/>
    -				<li class="bullet">'Static' anchors - these are fixed to some point on an element and do not move.  They can be specified using a string to
    -				identify one of the defaults that jsPlumb ships with, or an array describing the location (see below)<br/><br/></li>				
    -				<li class="bullet">'Dynamic' anchors - these are lists of Static anchors from which jsPlumb picks the most appropriate one each time a Connection
    -				is painted.  The algorithm used to determine the most appropriate anchor picks the one that is closest to the center of the other element in
    -				the Connection. A future version of jsPlumb might support a pluggable algorithm to make this decision.<br/><br/></li>
    -				<li class="bullet">'Continuous' anchors - These anchors, introduced in 1.3.4, are not fixed to any specific loction; they are assigned to one of
    -				the four faces of an element depending on that element's orientation to the other element in the associated Connection.  Continuous anchors are slightly more computationally intensive than Static or Dynamic anchors because jsPlumb is required to calculate the position of every Connection during a paint cycle, rather than just Connections belonging to the element in motion.  These anchors were designed for use with the StateMachine connector that was also introduced in 1.3.4, but will work with any combination of Connector and Endpoint.<br/><br/></li> 
    -			</ul>  
    -					
    -			<h4><a id="staticAnchors">Static Anchors</a></h4>		
    -			jsPlumb has nine default anchor locations you can use to specify where the Connectors connect to elements: these are the four corners of an element,
    -			the center of the element, and the midpoint of each edge of the element:  
    -			<ul>
    -				<li class="bullet">TopCenter</li>
    -				<li class="bullet">TopRight</li>
    -				<li class="bullet">RightMiddle</li>
    -				<li class="bullet">BottomRight</li>
    -				<li class="bullet">BottomCenter</li>
    -				<li class="bullet">BottomLeft</li>
    -				<li class="bullet">LeftMiddle</li>
    -				<li class="bullet">TopLeft</li>
    -			</ul>
    -			
    -Each of these string representations is just a wrapper around the underlying array-based syntax  [x, y, dx, dy], where x and y are coordinates in the interval [0,1] specifying the position of the
    -anchor, and dx and dy are coordinates in the interval [-1, 1] specifying the orientation of the curve incident to the anchor. For example, [0, 0.5, -1, 0] defines a "LeftCenter" anchor with a connector curve that emanates leftward from the anchor. Similarly, [0.5, 0, 0, -1] defines a "CenterTop" anchor with a connector curve emanating upwards. 	
    -
    -
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:"BottomCenter", ... }); 
    -</pre>
    -</div>
    -is identical to:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:[ 0.5, 1, 0, 1 ], ... }); 
    -</pre>
    -</div>		
    -In addition to supplying the location and orientation of an anchor, you can optionally supply two more parameters that define an offset in pixels from the given location.  Here's the anchor specified above, but with a 50 pixel offset below the element in the y axis:	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({...., anchor:[ 0.5, 1, 0, 1, 0, 50 ], ... }); 
    -</pre>
    -</div>		
    -
    -		<h4><a id="dynamicAnchors">Dynamic Anchors</a></h4>
    -	These are Anchors that can be positioned in one of a number of locations, choosing the one that is most appropriate each time something moves or is painted in the UI.
    -	<p>
    -	There is no special syntax to creating a DynamicAnchor; you just provide an array of individual Static Anchor specifications, eg:
    -	</p>
    -<div class="code">
    -<pre>
    -var dynamicAnchors = [ [ 0.2, 0, 0, -1 ],  [ 1, 0.2, 1, 0 ], 
    -			   [ 0.8, 1, 0, 1 ], [ 0, 0.8, -1, 0 ] ];
    -
    -jsPlumb.connect({...., anchor:dynamicAnchors, ... }); 			   
    -</pre>
    -</div>
    -<h4>	Default Dynamic Anchor</h4>
    -jsPlumb provides a dynamic anchor called "AutoDefault" that chooses from TopCenter, RightMiddle, BottomCenter and LeftMiddle:
    -<div class="code">
    -<pre>jsPlumb.connect({...., anchor:"AutoDefault", ... });</pre>
    -</div>
    -
    -<h4>	Location Selection</h4>
    -The initial implementation of the algorithm that decides which location to choose just calculates which location is closest to the center of the 
    -other element in the connection.  It is possible that future versions of jsPlumb could support more sophisticated choice algorithms, if the need arose.
    -<h4>Draggable Connections</h4>
    -Dynamic Anchors and Draggable Connections can interoperate: jsPlumb locks the position of a dynamic anchor when you start to drag a connection from it,
    -and unlocks it once the connection is either established or abandoned. At that point you may see the position of the dynamic anchor change, as jsPlumb
    -optimises the connection.  <p>You can see this behaviour in the <a href="../html/jquery/draggableConnectorsDemo.html" target="_blank">draggable connections</a> demonstration, when
    -you drag a connection from the blue endpoint on window 1 to the blue endpoint on window 3 - the connection is established and then window 1's blue
    -endpoint jumps down to a location that is closer to window 3.</p>
    -
    -
    -	<h4><a id="continuousAnchors">Continuous Anchors</a></h4>
    -	As discussed above, these are anchors whose positions are calculated by jsPlumb according to the orientation between elements in a Connection, and also how
    -	many other Continuous anchors happen to be sharing the element.  You specify that you want to use Continuous anchors using the string syntax you would use
    -	to specify one of the default Static Anchors, for example:
    -	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:someDiv,
    -	target:someOtherDiv,
    -	anchor:"Continuous"
    -});
    -</pre>
    -</div>
    -
    -Note in this example I specified only "anchor", rather than "anchors" - jsPlumb will use the same spec for both anchors.  But I could have said this:
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:someDiv,
    -	target:someOtherDiv,
    -	anchors:["BottomCenter", "Continuous"]
    -});
    -</pre>
    -</div>	 	  
    -..which would have resulted in the source element having a Static Anchor at BottomCenter.  In practise, though, it seems the Continuous Anchors work best if both 
    -elements in a Connection are using them.
    -<p>Note also that Continuous Anchors can be specified on <strong>addEndpoint</strong> calls:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint(someDiv, {
    -	anchor:"Continuous",
    -	paintStyle:{ fillStyle:"red" }
    -});
    -</pre>
    -</div>	 	
    -...and in <strong>makeSource</strong>/<strong>makeTarget</strong>:
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource(someDiv, {
    -	anchor:"Continuous",
    -	paintStyle:{ fillStyle:"red" }
    -});
    -
    -jsPlumb.makeTarget(someDiv, {
    -	anchor:"Continuous",
    -	paintStyle:{ fillStyle:"red" }
    -});
    -</pre>
    -</div>	 
    -... and in the jsPlumb defaults:
    -<div class="code">
    -<pre>
    -jsPlumb.Defaults.Anchor = "Continuous";
    -</pre>
    -</div>  
    -					</div>		
    -												
    -<div class="section">
    -		<h4><a id="definitions">Connector, Endpoint &amp; Overlay Definitions</a></h4>
    -Before we discuss Connector, Endpoints and Overlays, a quick word on definitions:  whenever you need to define a Connector, Endpoint or Overlay, you must use a "definition" of it, rather than constructing one directly.  This definition can be either a string that nominates the artifact you want to create:
    -		<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:"Rectangle"
    -});
    -</pre>
    -		</div>
    -		or an array consisting of both the artifact's name and the arguments you want to pass to its constructor:
    -		<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Rectangle", { cssClass:"myEndpoint", width:30, height:10 } ]
    -});
    -</pre>
    -		</div>
    -There is also a three-argument method that allows you to specify two sets of parameters, which jsPlumb will merge together for you. The idea behind this is that you will often want to define common characteristics somewhere and reuse them across a bunch of different calls:
    -		<div class="code">
    -<pre>var common = {
    -	cssClass	:	"myCssClass",
    -	hoverClass	:	"myHoverClass"
    -};
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Rectangle", { width:30, height:10 }, common ]
    -});
    -</pre>
    -		</div>
    -	This syntax is supported for all Endpoint, Connector and Overlay definitions.  Here's an example using definitions for all three:	
    -		<div class="code">
    -<pre>var common = {
    -	cssClass:"myCssClass"
    -};
    -jsPlumb.connect({
    -	source:"someDiv",
    -	target:"someOtherDiv",
    -	endpoint:[ "Dot", { radius:5, hoverClass:"myEndpointHover" }, common ],
    -	connector:[ "Bezier", { curviness:100 }, common ],
    -	overlays: [
    -		[ "Arrow", { foldback:0.2 }, common ],
    -		[ "Label", { cssClass:"labelClass" } ]	
    -	]
    -});
    -</pre>
    -		</div>		
    -The allowed constructor parameters are different for each artifact you create, but every artifact takes a single JS object as argument, with the parameters as [key,value] pairs in that object.  See the relevant sections on <a href="#connectors">Connectors</a>, <a href="#endpoints">Endpoints</a> and <a href="#overlays">Overlays</a> for information on supported parameters.</a>
    -				
    -	</div>
    -
    -		<div class="section">
    -			<h3><a id="connectors">Connectors</a></h3>
    -			<p>Connectors are the lines that actually join elements of the UI.  jsPlumb has four connector implementations - a straight line, a Bezier curve, "flowchart", and "state machine".  The default connector is the Bezier curve.</p>
    -			<p>You optionally specify a Connector by setting the 'connector' property on a call to jsPlumb.connect, jsPlumb.addEndpoint(s), jsPlumb.makeSource or jsPlumb.makeTarget. If you do not supply a value for 'connector', the default will be used (see <a href="#defaults">defaults</a>).</p>
    -			<p>You specify Connectors using the syntax described above in <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions.</a> Allowed constructor values for each Connector type are described below:</p>		
    -			<h4><a id="bezierConnector">Bezier Connector</a></h4>
    -			<p>The Bezier Connector provides a cubic Bezier path between the two Endpoints. It supports a single constructor argument:</p>
    -			<p>
    -			<strong>curviness</strong> - Optional; defaults to 150. This defines the distance in pixels that
    -			the Bezier's control points are situated from the anchor points.  This does not mean that your
    -			connector will pass through a point at this distance from your curve.  It is a hint to how you want the
    -			curve to travel. Rather than discuss Bezier curves at length here, because they are a complex topic,
    -			refer instead to <a href="http://en.wikipedia.org/wiki/B%C3%A9zier_curve" target="_blank">Wikipedia.</a>
    -			</p>
    -			<h4><a id="straightConnector">Straight Connector</a></h4>
    -			<p>The Straight Connector draws a straight line between the two endpoints. No constructor arguments are supported; use the <strong>endpointStyle</strong> and/or <strong>cssClass</strong>/<strong>connectorClass</strong> to a <strong>connect</strong>, <strong>addEndpoint</strong>, <strong>makeSource</strong> or <strong>makeTarget</strong> call to control the appearance of one of these Connectors.  
    -			</p>
    -			<h4><a id="flowchartConnector">Flowchart Connector</a></h4>
    -			<p>This type of Connector draws a connection that consists of a series of vertical or horizontal segments - the classic flowchart look. Two constructor arguments are supported:</p>
    -			<strong>stub</strong> - this is the minimum length, in pixels, of the initial stub that emanates from an Endpoint.  This parameter is optional, and defaults to 30 pixels.<br/>
    -			<strong>gap</strong> - optional, defaults to 0 pixels. Lets you specify a gap between the end of the connector and the elements to which it is attached.
    -			<p>This Connector supports Connection that start and end on the same element.</p>
    -			<h4><a id="stateMachineConnector">State Machine Connector</a></h4>
    -			<p>This Connector draws slightly curved lines (they are actually quadratic Bezier curves), similar to the State Machine connectors you may have seen in software like GraphViz.  Connections in which some element is both the source and the target (so-called 'loopback') are supported by these Connectors (as they are with Flowchart Connectors); in this case you get a circle. Supported parameters are:
    -			</p>
    -			<p>
    -			<strong>margin</strong> - Optional; defaults to 5.  Defines the distance from the element that the connector begins/ends.
    -			</p>
    -			<p>
    -			<strong>curviness</strong> - Optional, defaults to 10.  This has a similar effect to the curviness parameter on Bezier curves.
    -			</p>
    -			<p>
    -			<strong>proximityLimit</strong> - Optional, defaults to 80.  The minimum distance between the two ends of the Connector before it paints itself as 
    -			a straight line rather than a quadratic Bezier curve.
    -			</p>    
    -			
    -		</div>
    -		
    -			<div class="section">
    -			<h3><a id="endpoints">Endpoint Types</a></h3>
    -			<p>An Endpoint is the UI component that marks the location of an Anchor, ie. the place where a Connector joins an
    -			element.  jsPlumb comes with four Endpoint implementations - <strong>Dot</strong>, <strong>Rectangle</strong>, <strong>Blank</strong> and <strong>Image</strong>. You optionally specify Endpoint properties using the
    -			<strong>endpoint</strong> parameter in a call to jsPlumb.connect, jsPlumb.addEndpoint, jsPlumb.makeSource or jsPlumb.makeTarget.
    -			</p>  
    -				<p>As with Connectors and Overlays, you specify Endpoints using the syntax described above in <a href="#definitions">Connector, Endpoint &amp; Overlay Definitions.</a></p>
    -			
    -			The four available Endpoint types, and their constructor arguments, are as follows:
    -					<h4><a id="dotEndpoint">Dot Endpoint</a></h4>
    -					This Endpoint draw a dot on the screen. It supports three constructor parameters:
    -					<p>
    -						<strong>radius</strong> - Optional; defaults to 10 pixels. Defines the radius of the dot.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="rectangleEndpoint">Rectangle Endpoint</a></h4>
    -						Draws a rectangle. Supported constructor parameters are:
    -					<p>
    -						<strong>width</strong> - Optional; defaults to 20 pixels. Defines the width of the rectangle.
    -					</p>
    -					<p>
    -						<strong>height</strong> - Optional; defaults to 20 pixels. Defines the height of the rectangle.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="imageEndpoint">Image Endpoint</a></h4>
    -						Draws an image from a given URL.  This Endpoint supports three constructor parameters:  
    -					<p>
    -						<strong>src</strong> - Required.  Specifies the URL of the image to use.
    -					</p>
    -					<p>
    -						<strong>cssClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates.
    -					</p>
    -					<p>
    -						<strong>hoverClass</strong> - Optional.  A CSS class to attach to the element the Endpoint creates whenever the mouse is hovering over the 
    -						element or an attached Connection.
    -					</p>
    -					<h4><a id="blankEndpoint">Blank Endpoint</a></h4>
    -						Does not draw anything visible to the user.  As discussed previously, this Endpoint is probably not what you want if you need your users to be able to drag existing Connections - for that, use a Rectangle or Dot Endpoint ans assign to it a CSS class that causes it to be transparent.
    -																			
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="overlays">Overlay Types</a></h3>
    -			<p>
    -			Overlays are UI elements that are painted onto connections, such as labels or arrows.
    -			jsPlumb comes with four defaults:
    -			</p>
    -			<ul>
    -				<li><strong>Arrow</strong> - a configurable arrow that is painted at some point along the connector.  You can control the length and width of the Arrow, the 'foldback' point - a point the tail points fold back into, and the direction (allowed values are 1 and -1; 1 is the default and means point in the direction of the connection)</li>
    -				<li><strong>Label</strong> - a configurable label that is painted at some point along the connector.</li>
    -				<li><strong>PlainArrow</strong> - an Arrow shaped as a triangle, with no foldback.</li>
    -				<li><strong>Diamond</strong> - as the name suggests, a diamond.</li>
    -			</ul> 
    -			The last two are actually just configured instances of the generic Arrow overlay (see examples). 
    -			
    -			<p>All Overlays support these two methods for getting/setting their location (location is a number between 0 and 1 inclusive, indicating some point along the path
    -			inscribed by the associated Connector):</p>
    -			<ul>
    -				<li class="bullet"><strong>getLocation</strong> - returns the current location</li>
    -				<li class="bullet"><strong>setLocation</strong> - sets the current location; valid values are decimals in the range 0-1 inclusive</li> 
    -			</ul>
    -			
    -			<p>You can specify one or more overlays when making a call to jsPlumb.connect, jsPlumb.addEndpoint or jsPlumb.makeSource (but not jsPlumb.makeTarget; overlays
    -			are always derived from what the source of a Connection defines)  The three cases are discussed below:</p>
    -			1. Specifying one or more overlays on a jsPlumb.connect call. In this example we'll create an Arrow with the default options for an Arrow, and
    -			a label with the text "foo":
    -			<div class="code">
    -			<pre>jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});			
    -</pre>
    -			</div>
    -			This connection will have an arrow located halfway along it, and the label "foo" one quarter of the way along.  Notice the <strong>id</strong>
    -			parameter; it can be used later if you wish to remove the Overlay or change its visibility (see below).<br/><br/> 
    -			
    -			2. Specifying one or more overlays on a jsPlumb.addEndpoint call.  Note in this example that we use the parameter 'connectorOverlays' and not
    -			'overlays' as in the last example.  This is because 'overlays' would refer to Endpoint Overlays:
    -			<div class="code">
    -			<pre>jsPlumb.addEndpoint("someDiv", {
    -	...
    -	connectorOverlays:[ 
    -		[ "Arrow", { width:10, length:30, location:1, id:"arrow" } ], 
    -		[ "Label", { label:"foo", id:"label" } ]
    -	],
    -	...
    -});</pre>
    -			</div>	
    -			This connection will have a 10x30 Arrow located right at the head of the connection, and the label "foo" located at the halfway point. Again,
    -			note that <strong>id</strong> parameter passed in to each Overlay spec.<br/><br/>
    -			
    -			3. Specifying one or more overlays on a jsPlumb.makeSource call.  Note in this example that we again use the parameter 'connectorOverlays' and not
    -			'overlays'.  The 'endpoint' parameter to jsPlumb.makeSource supports everything you might pass to the second argument of a jsPlumb.addEndpoint call:
    -			<div class="code">
    -			<pre>jsPlumb.makeSource("someDiv", {
    -	...
    -	endpoint:{
    -		connectorOverlays:[ 
    -			[ "Arrow", { width:10, length:30, location:1, id:"arrow" } ], 
    -			[ "Label", { label:"foo", id:"label" } ]
    -		]
    -	}
    -	...
    -});</pre>
    -			</div>	
    -			This connection will have a 10x30 Arrow located right at the head of the connection, and the label "foo" located at the halfway point. Again,
    -			note that <strong>id</strong> parameter passed in to each Overlay spec.<br/><br/>
    -			
    -			 	
    -					<h4><a id="arrowOverlay">Arrow Overlay</a></h4>
    -					<p>This overlay draws an arrow, using four points: the head and two tail points, and a 'foldback' point, which permits the tail
    -					of the arrow to be indented. Available constructor arguments for this Overlay are </p>
    -					<ul>
    -					<li><strong>width</strong> - width of the tail of the arrow</li>
    -					<li><strong>length</strong> - distance from the tail of the arrow to the head</li>
    -					<li><strong>location</strong> - where, as a proportional value from 0 to 1 inclusive, the Arrow should appear on the Connector</li>
    -					<li><strong>direction</strong> - which way to point. Allowed values are 1 (the default, meaning forwards) and -1, meaning backwards </li>
    -					<li><strong>foldback</strong> - how far along the axis of the arrow the tail points foldback in to. Default is 0.623.</li>
    -					<li><strong>paintStyle</strong> - a style object in the form used for paintStyle values for Endpoints and Connectors</li>
    -					</ul>
    -					<h4><a id="plainArrowOverlay">PlainArrow Overlay</a></h4>
    -					<p>This is just a specialized instance of Arrow in which jsPlumb hardcodes 'foldback' to 1, meaning the tail of the Arrow is a flat edge.  All of the constructor parameters from Arrow apply for PlainArrow.</p>
    -					<h4><a id="diamondOverlay">Diamond Overlay</a></h4>
    -					<p>This is a specialized instance of Arrow in which jsPlumb hardcodes 'foldback' to 2, meaning the Arrow turns into a Diamond.  All of the constructor parameters from Arrow apply for Diamond.</p>
    -					<h4><a id="labelOverlay">Label Overlay</a></h4>
    -					This provides a text label to decorate Connectors with.  The available constructor arguments are:
    -					<ul>
    -						<li><strong>label</strong> - The text to display.  You can provide a function here instead of plain text: it is passed the Connection as an argument, and it should return a String.</li>
    -						<li><strong>cssClass</strong> - Optional css class to use for the Label.  This is now preferred over using the 'labelStyle' parameter.</li>
    -						<li><strong>labelStyle</strong> - (deprecated, use cssClass instead) Optional arguments for the label's appearance.  Valid entries in this JS object are:
    -							<ul>
    -								<li><em>font</em> - a font string in a format suitable for the Canvas element</li>
    -								<li><em>fillStyle</em> - the color to fill the label's background with. Optional.</li>
    -								<li><em>color</em> - the color of the label's text. Optional.</li>
    -								<li><em>padding</em> - optional padding for the label. This is expressed as a proportion of the width of the label, not in pixels or ems.</li>
    -								<li><em>borderWidth</em> - optional width in pixels for the label's border. Defaults to 0.</li>
    -								<li><em>borderStyle</em> - optional. The color to paint the border, if there is one.</li>
    -								
    -							</ul>
    -						
    -						</li>
    -						<li><strong>location</strong> - As for Arrow Overlay.  Where, proportionally from 0 to 1 inclusive, the label should appear.</li>
    -					</ul>
    -
    -					The Label overlay offers two methods - getLabel and setLabel - for accessing/manipulating its content dynamically:
    -<div class="code">
    -<pre>
    -var c = jsPlumb.connect({
    -	source:"d1", 
    -	target:"d2", 
    -	overlays:[
    -		[ "Label", {label:"FOO", id:"label"}]
    -	]	
    -});					
    -
    -...
    -
    -var label = c.getOverlay("label");
    -console.log("Label is currently", label.getLabel());
    -label.setLabel("BAR");
    -console.log("Label is now", label.getLabel());
    -</pre>
    -</div>
    -In this example you can see that the Label Overlay is assigned an id of "label" in the connect call, and then retrieved using that id in the call to Connection's getOverlay method.
    -<p>Both Connections and Endpoints support Label Overlays, and because changing labels is quite a common operation, <strong>setLabel</strong> and <strong>getLabel</strong> methods have been added to these objects:
    -<div class="code">
    -<pre>
    -var conn = jsPlumb.connect({
    -	source:"d1", 
    -	target:"d2",
    -	label:"FOO"
    -});					
    -
    -...
    -
    -console.log("Label is currently", conn.getLabel());
    -conn.setLabel("BAR");
    -console.log("Label is now", conn.getLabel());
    -</pre>
    -</div>
    -These methods support passing in a Function instead of a String, and jsPlumb will create a label overlay for you if one does not yet exist when you call getLabel:
    -<div class="code">
    -<pre>
    -var conn = jsPlumb.connect({
    -	source:"d1", 
    -	target:"d2"
    -});					
    -
    -...
    -
    -conn.setLabel(function(c) {
    -	var s = new Date();
    -	return s.getTime() + "milliseconds have elapsed since 01/01/1970";
    -});
    -console.log("Label is now", conn.getLabel());
    -</pre>
    -</div>
    -					
    -					<h4><a id="hideshowOverlay">Hiding/Showing Overlays</a></h4>
    -					<p>You can control the visibility of Overlays using the <strong>setVisible</strong> method of Overlays themselves, or with
    -					<strong>showOverlay(id)</strong> or <strong>hideOverlay(id)</strong> on a Connection.</p>
    -					<p>Remember the <strong>id</strong> parameter that we specified in the examples above?  This can be used to retrieve the Overlay
    -					from a Connection:</p>
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -var overlay = connection.getOverlay("myLabel");
    -// now you can hide this Overlay:
    -overlay.setVisible(false);
    -// there are also hide/show methods:
    -overlay.show();
    -overlay.hide();
    -
    -</pre>					
    -					</div>
    -					<p>However, Connection and Endpoint also have two convenience methods you could use instead:
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -connection.hideOverlay("myLabel");
    -
    -// more time passes
    -
    -connection.showOverlay("myLabel");
    -
    -</pre>					
    -					</div>
    -					
    -		<h4><a id="hideshowOverlay">Removing Overlays</a></h4>
    -		<p>Connection and Endpoint also have a <strong>removeOverlay</strong> method, that does what you might expect:
    -					<div class="code">
    -<pre>var connection = jsPlumb.connect({
    -	...
    -	overlays:[ 
    -		"Arrow", 
    -		[ "Label", { label:"foo", location:0.25 }, id:"myLabel" ]
    -	],
    -	...
    -});		
    -
    -// time passes
    -
    -connection.removeOverlay("myLabel");
    -
    -
    -</pre>					
    -					</div>							
    -	
    -		</div>
    -	
    -			
    -			<div class="section">
    -			<h3><a id="defaults">Defaults</a></h3>
    -			The easiest way to set a look and feel for your plumbing is to override the defaults that jsPlumb uses. If you
    -			do not do this you are forced to provide your overridden values on every call.  Every argument to the connect and addEndpoint methods has an
    -			associated default value in jsPlumb.<br/><br/>
    -
    -			The defaults that ship with jsPlumb are stored in <em>jsPlumb.Defaults</em>, which is a Javascript object.  Valid entries, and their initial values, are:
    -
    -			<div class="code">
    -<pre>
    -Anchor : "BottomCenter",
    -Anchors : [ null, null ],
    -Connector : "Bezier",
    -ConnectionOverlays : [ ],
    -DragOptions : { },
    -DropOptions : { },
    -Endpoint : "Dot",
    -EndpointOverlays : [ ],
    -Endpoints : [ null, null ],
    -EndpointStyle : { fillStyle : null },
    -EndpointStyles : [ null, null ],
    -EndpointHoverStyle : null,
    -EndpointHoverStyles : [ null, null ],
    -HoverPaintStyle : null,
    -LabelStyle : { color : "black" },
    -LogEnabled : false,
    -Overlays : [ ],
    -MaxConnections : 1,
    -MouseEventsEnabled : true,
    -PaintStyle : { lineWidth : 8, strokeStyle : "#456" },
    -RenderMode : "svg",
    -Scope : "_jsPlumb_DefaultScope"
    -</pre>
    -			</div>
    -			<p>
    -			Note that in EndpointStyle, the default fillStyle is 'null'.  This instructs jsPlumb to use the strokeStyle
    -from the attached connector to fill the endpoint.</p>
    -<p>Note also that you can specify either or both (or neither) of 'EndpointStyle' and 'EndpointStyles'.  This allows you to specify a different
    -end point for each end of a connection.  'Endpoint' and 'Endpoints' use the same concept.  jsPlumb will look first in the
    -individual endpoint/endpoint style arrays, and then fall back to the single default version.</p>
    -
    -			you can override these defaults by including this in a script somewhere:
    -			<div class="code">
    -<pre>
    -jsPlumb.Defaults.PaintStyle = {
    -	lineWidth:13,
    -	strokeStyle: 'rgba(200,0,0,100)'
    -}
    -
    -jsPlumb.Defaults.DragOptions = { cursor: "crosshair" };
    -
    -jsPlumb.Default.Endpoints = [ [ "Dot", 7 ], [ "Dot", 11 ] ];
    -
    -jsPlumb.Defaults.EndpointStyles = [{ fillStyle:"#225588" }, { fillStyle:"#558822" }];</pre>
    -			</div>
    -			after the jsPlumb script has been loaded of course!  Here we have specified the following default behaviour:
    -			<ul>
    -				<li>- connectors are 13 pixels wide and painted with a semi-transparent red line</li>
    -				<li>- when dragging an element the crosshair cursor is used</li>
    -				<li>- the source endpoint is a dot of radius 7; the target endpoint is a dot of radius 11</li>
    -				<li>- the source endpoint is blue; the target endpoint is green</li>
    -			</ul>
    -
    -			<h5>jsPlumb.importDefaults</h5>
    -From version 1.4.0 onwards there is a helper method called <strong>importDefaults</strong> that allows you to import a set of values with just one call to jsPlumb.  Here's the previous example using importDefaults:
    -			<div class="code">
    -<pre>
    -jsPlumb.importDefaults({
    -	PaintStyle : {
    -		lineWidth:13,
    -		strokeStyle: 'rgba(200,0,0,100)'
    -	},
    -	DragOptions : { cursor: "crosshair" },
    -	Endpoints : [ [ "Dot", 7 ], [ "Dot", 11 ] ],
    -	EndpointStyles : [{ fillStyle:"#225588" }, { fillStyle:"#558822" }]
    -});</pre>
    -			</div>
    -
    -		</div> 					
    -
    -<div class="section">
    -			<h3><a id="simpleConnections">Connections</a></h3>
    -			<h4><a id="simpleConnections">Programmatic Connections</a></h4>
    -The most simple connection you can make with jsPlumb looks like this:
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({source:"element1", target:"element2"});</pre>			
    -			</div>
    -
    -		In this example we have created a Connection from 'element1' to 'element2'.  Remember that a Connection in jsPlumb consists of two Endpoints,
    -		a Connector, and zero or more Overlays.  But this call to 'connect' supplied none of those things, so jsPlumb uses the default values wherever it needs to.
    -		In this case, default values have been used for the following: 
    -		<ul>
    -			<li>- The type and appearance of each Endpoint in the Connection. jsPlumb's default for this is the "Dot" endpoint, of radius 10, with fill color "#456".</li>
    -			<li>- The Anchors that define where the connection's Endpoints appear on each element. The jsPlumb default is "BottomCenter"</li>			
    -			<li>- Whether or not each Endpoint can be a source or target for new Connections. The default is false.</li>
    -			<li>- The type and appearance of the Connection's Connector. The default is a "Bezier" connector of line width 8, and color "#456".</li>
    -		</ul>
    -		<p>
    -		So this call will result in an 8px Bezier, colored "#456", from the bottom center of 'element1' to the bottom center of 'element2', and each
    -		Endpoint will be a 10px radius Dot Endpoint, colored "#456".
    -		</p>
    -		<p>
    -		Let's beef up this call a little and tell jsPlumb what sort of Endpoints we want, and where we want them:		
    -		</p>
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({
    -	source:"element1", 
    -	target:"element2",
    -	anchors:["RightMiddle", "LeftMiddle" ],
    -	endpoint:"Rectangle",
    -	endpointStyle:{ fillStyle: "yellow" }
    -});
    -</pre>			
    -			</div>
    -			
    -		<p>This is what we have told jsPlumb we want this time:</p>
    -		<ul>
    -			<li><strong>anchors</strong> - this array tells jsPlumb where the source and target Endpoints should be located on their parent elements. In this case,
    -			we use the shorthand syntax to name one of jsPlumb's default anchors; you can also specify custom locations (see <a href="#anchors">anchors</a>).
    -			Instead of <strong>anchors</strong> you can use <strong>anchor</strong>, if you want the source and target Endpoints to be located at the
    -			same place on their parent elements.			<br/>
    -			</li>
    -			<li>
    -				<strong>endpoint</strong>  - this tells jsPlumb to use the "Rectangle" Endpoint for both the source and target of the Connection.  As with anchors, 
    -				<strong>endpoint</strong> has a plural version that allows you to specify a different Endpoint for each end of the Connection.<br/>  
    -			</li>
    -			<li>
    -				<strong>endpointStyle</strong> - this is the definition of the appearance of the Endpoint you specified above.  Again, there is a
    -				plural equivalent of this that allows you to specify a different style for each end of the Connection. For more information about
    -				allowed values for this value, see <a href="#paintstyles">Connector, Endpoint & Overlay Styles</a>. 
    -			</li>
    -		</ul>		
    -		
    -		<h5>Reusing common settings between jsPlumb.connect calls</h5>
    -		<p>
    -			A fairly common situation you will find yourself in is wanting to create a bunch of Connections that have only minor differences
    -			between them.  To support that, <strong>jsPlumb.connect</strong> takes an optional second argument. For example:			 
    -		</p>
    -		<div class="code">
    -<pre>
    -var common = {
    -	anchors:[ "BottomCenter", "TopCenter" ],
    -	endpoints:["Dot", "Blank" ]
    -};
    -
    -jsPlumb.connect({ source:"someElement", target:"someOtherElement" }, common);
    -
    -jsPlumb.connect({ source:"aThirdElement", target:"yetAnotherElement" }, common);
    -
    -</pre>			
    -			</div>		
    -			
    -		<h5>Endpoints created by jsPlumb.connect</h5>
    -		<p>If you supply an element id or selector for either the source or target , jsPlumb.connect will automatically create an Endpoint on the 
    -		given element.  These automatically created Endpoints are not marked as drag source or targets, and cannot
    -		be interacted with.  For some situations this behaviour is perfectly fine, but for more interactive UIs you should set things up using the drag and drop
    -		method discussed below.</p>
    -		<p>Given that jsPlumb.connect creates its own Endpoints in some circumstances, in order to avoid leaving orphaned Endpoints around the place, if the Connection is subsequently
    -		deleted, these created Endpoints are deleted too.  Should you want to, you can override this behaviour by setting <strong>deleteEndpointsOnDetach</strong> to
    -		false in the connect call:</p>
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({ 
    -	source:"aThirdElement", 
    -	target:"yetAnotherElement",
    -	deleteEndpointsOnDetach:false 
    -});
    -</pre>
    -</div>	
    -
    -	<h5>Detaching Connections</h5>
    -	By default, connections made with jsPlumb.connect will be detachable via the mouse.  You can prevent this by either setting an appropriate default value:
    -
    -		<div class="code">
    -<pre>
    -jsPlumb.importDefaults({ 
    -	...
    -	ConnectionsDetachable:false
    -	...
    -});
    -</pre>
    -</div>	
    -
    -	Or by specifying it on the connect call like this:
    -		<div class="code">
    -<pre>
    -jsPlumb.connect({ 
    -	source:"aThirdElement", 
    -	target:"yetAnotherElement",
    -	detachable:false
    -});
    -</pre>
    -</div>		
    -								
    -					
    -		<h4><a id="draggableConnections">Connections using Drag and Drop</a></h4>
    -		To support drag and drop connections, you first need to set a few things up.  Every drag and drop connection needs at least a source
    -		Endpoint that the user can drag a connection from. Here's a simple example of how to create an Endpoint:
    -		<div class="code">
    -<pre>
    -var endpointOptions = { isSource:true };
    -var endpoint = jsPlumb.addEndpoint('elementId', endpointOptions);
    -</pre>		
    -</div>  	
    -	This Endpoint will act as a source for new Connections, and will use the jsPlumb defaults for its own appearance and that of any Connections that are drawn from it.							
    -	
    -		<h5>Tip: use the three-argument addEndpoint method for common data </h5>
    -		One thing that happens quite often is that you have an Endpoint whose appearance and behaviour is largely the
    -		same between usages on different elements, with just a few differences. 
    -		<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -	endpoint:"Rectangle",
    -	paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -	isSource:true,
    -	connectorStyle : { strokeStyle:"#666" },
    -	isTarget:true
    -};
    -</pre>		
    -</div>  		
    -		Notice there is no 'anchor' set.  Here we apply it to two elements, at a different location in each:
    -		<div class="code">
    -<pre>
    -jsPlumb.addEndpoint('element1', { anchor:"BottomCenter" }, exampleGreyEndpointOptions)); 
    -
    -jsPlumb.addEndpoint('element2', { anchor:"TopCenter" }, exampleGreyEndpointOptions));
    -</pre>		
    -</div>
    -
    -<p>
    -Now that you have a source Endpoint, you need to either create a target Endpoint on some element, or notify jsPlumb that you wish to make an
    -entire element a drop target.  Let's look at how to attach a target Endpoint first:
    -</p>
    -
    -		<div class="code">
    -<pre>var endpointOptions = { isTarget:true, endpoint:"Rectangle", paintStyle:{ fillStyle:"gray" } };
    -var endpoint = jsPlumb.addEndpoint("otherElementId", endpointOptions);
    -</pre>		
    -		</div>
    -		<p>
    -		This Endpoint, a gray rectangle, has declared that it can act as a drop target for Connections. 
    -</p>
    -<p>
    -jsPlumb also supports marking entire elements as drag sources or drag targets, using the <strong>makeSource</strong> and <strong>makeTarget</strong> methods.  This is a useful function for certain applications.  Each of these methods takes an Endpoint declaration which defines the Endpoint that jsPlumb will create after a Connection has been attached.  Let's take a look at makeTarget first:
    -</p>
    -<h4><a id="sourcesAndTargets">Elements as sources &amp; targets</a></h4>
    -jsPlumb also supports turning entire elements into Connection sources and targets, using the methods <strong>makeSource</strong> and <strong>makeTarget</strong>.  With these methods you mark an element as a source or target, and provide an Endpoint specification for jsPlumb to use when a Connection is established.  makeSource also gives you the ability to mark some child element as the place from which you wish to drag Connections, but still have the Connection on the main element after it has been established.
    -<p>These methods honour the jsPlumb defaults - if, for example you set up the default Anchors to be this:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.Defaults.Anchors = [ "LeftMiddle", "BottomRight" ];
    -</pre>
    -</div>
    -... and then used makeSource and makeTarget without specifying Anchor locations, jsPlumb would use "LeftMiddle" for the makeSource element and "BottomRight" for the makeTarget element.
    -<p>
    -A further thing to note about makeSource and makeTarget is that from 1.4.0 onwards any prior calls to one of these methods is honoured by subsequent calls to jsPlumb.connect. This helps when you're building a UI that uses this functionality at runtime but which loads some initial data, and you want the statically loaded data to have the same appearance and behaviour as dynamically created connections (obviously quite a common use case).  
    -<p>For example:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -	anchor:"Continuous",
    -	endpoint:["Rectangle", { width:40, height:20 }]
    -});
    -
    -...
    -
    -jsPlumb.connect({source:"el1", target:"el2"});
    -</pre>
    -</div>
    -In this example, the source of the connection will be a Rectangle of size 40x20, having a Continuous Anchor.
    -<p>You can override this behaviour by setting a <strong>newConnection</strong> parameter on the connect call:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -	anchor:"Continuous",
    -	endpoint:["Rectangle", { width:40, height:20 }]
    -});
    -
    -...
    -
    -jsPlumb.connect({ source:"el1", target:"el2", newConnection:true });
    -</pre>
    -</div>
    -<h5><a id="makeTarget">jsPlumb.makeTarget</a></h5>
    -<p>
    -This method takes two arguments, the first of which specifies some element (or list of elements); the second specifies the Endpoint you wish to create on that element whenever a Connection is established on it.  In this example we will use the exact same target Endpoint we used before - the gray rectangle - but we will tell jsPlumb that the element "aTargetDiv" will be the drop target:
    -</p>
    -		<div class="code">
    -<pre>var endpointOptions = { 
    -  isTarget:true, 
    -  maxConnections:5,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" } 
    -};
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -<p>
    -	The allowed values in 'endpointOptions' are identical for both the <strong>jsPlumb.addEndpoint</strong> and <strong>jsPlumb.makeTarget</strong> methods, but makeTarget supports an extended 
    -	anchor syntax that allows you more control over the location of the target endpoint.  This is discussed below.	
    -</p>		
    -<p>Notice in the 'endpointOptions' object above there is a 'isTarget' parameter set - this may seem incongruous, since you know you're going to make some element a target.  Remember that the endpointOptions object is the information jsPlumb will use to create an Endpoint on the given target element each time a Connection is established to it. It takes the exact same format as you would pass to addEndpoint; makeTarget is essentially a deferred addEndpoint call followed by a connect call.  So in this case, we're telling jsPlumb that any Endpoints it happens to create on some element that was configured by the makeTarget call are themselves Connection targets.
    -</p>
    -<h5>Unique Endpoint per Target </h5>
    -<p>jsPlumb will create a new Endpoint using the supplied information every time a new Connection is established on the target element, by default, but you can override this behaviour and tell jsPlumb that it should create at most one Endpoint, which it should attempt to use for subsequent Connections:
    -</p>
    -		<div class="code">
    -<pre>var endpointOptions = { 
    -  isTarget:true, 
    -  uniqueEndpoint:true,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" } 
    -};
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -Here, the <strong>uniqueEndpoint</strong> parameter tells jsPlumb that there should be at most one Endpoint on this element.  Notice that 'maxConnections' is not set: the default is 1, so in this setup we have told jsPlumb that "aTargetDiv" can receive one Connection and no more.
    -<h5>Deleting Endpoints on Detach</h5>
    -By default, any Endpoints created using makeTarget have <strong>deleteEndpointsOnDetach</strong> set to true, which means that once all Connections to that Endpoint are removed, the Endpoint is deleted.  You can override this by setting the flag to true on the makeTarget call:
    -		<div class="code">
    -<pre>var endpointOptions = { 
    -  isTarget:true, 
    -  maxConnections:5,
    -  uniqueEndpoint:true,
    -  deleteEndpointsOnDetach:false,
    -  endpoint:"Rectangle", 
    -  paintStyle:{ fillStyle:"gray" } 
    -};
    -jsPlumb.makeTarget("aTargetDiv", endpointOptions);
    -</pre>		
    -		</div>
    -In this setup we have told jsPlumb to create an Endpoint the first time "aTargetElement" is connected to, and to not delete it even if there are no longer any Connections to it.  The created Endpoint will be reused for subsequent Connections, and can support a maximum of 5.
    -
    -<h5>Detaching connections made with the mouse</h5>
    -As with jsPlumb.connect, connections made with the mouse after setting up Endpoints with one of the functions we've just covered will be, by default, detachable.  You can prevent this in the jsPlumb defaults, as previously mentioned:
    -		<div class="code">
    -<pre>jsPlumb.importDefaults({ 
    -	...
    -	ConnectionsDetachable:false,
    -	...
    -});
    -</pre>		
    -		</div>
    -
    -	And you can also set this on a per-endpoint (or source/target) level, like in these examples:
    -
    -		<div class="code">
    -<pre>jsPlumb.addEndpoint("someElementId", { 
    -	connectionsDetachable:false	
    -});
    -
    -jsPlumb.makeSource("someOtherElement", {
    -	...
    -	connectionsDetachable:false,
    -	...
    -});
    -
    -jsPlumb.makeTarget("yetAnotherElement", {
    -	...
    -	connectionsDetachable:false,
    -	...
    -});
    -</pre>		
    -		</div>
    -
    -Note that in the jsPlumb defaults, by convention each word in a parameter is capitalised ("ConnectionsDetachable"), whereas for a call to one of these methods, we use camel case ("connectionsDetachable").
    -
    -
    -<h5>Target Anchors positions with makeTarget</h5>
    -<p>When using the makeTarget method, jsPlumb allows you to provide a callback function to be used to determine
    -the appropriate location of a target anchor for every new Connection dropped on the given target.  It may be the
    -case that you want to take some special action rather than just relying on one of the standard anchor mechanisms.
    -</p>
    -<p>This is achieved through an extended anchor syntax (note that this syntax is <strong>not supported</strong> in
    -the <strong>jsPlumb.addEndpoint</strong> method) that supplies a "positionFinder" to the anchor specification.  jsPlumb 
    -provides two of these by default; you can register your own on jsPlumb and refer to them by name, or just 
    -supply a function.  Here's a few examples:</p>
    -Instruct jsPlumb to place the target anchor at the exact location at which the mouse button was released on the
    -target element. Note that you tell jsPlumb the anchor is of type "Assign", and you then provide a "position"
    -parameter, which can be the name of some position finder, or a position finder function itself:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {
    -  anchor:[ "Assign", { 
    -    position:"Fixed"
    -  }]
    -});
    -</pre>
    -</div>
    -<p><strong>Fixed</strong> is one of the two default position finders provided by jsPlumb. The other is <strong>Grid</strong>:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {
    -  anchor:[ "Assign", { 
    -    position:"Grid",
    -    grid:[3,3]
    -  }]
    -});
    -</pre>
    -</div>
    -The Grid position finder takes a "grid" parameter that defines the size of the grid required. [3,3] means
    -3 rows and 3 columns.
    -<h5>Supplying your own position finder</h5>
    -<p>To supply your own position finder to jsPlumb you first need to create the callback function. First let's take a look at what the source code for the Grid position finder looks like:</p>
    -<div class="code">
    -<pre>
    -function(eventOffset, elementOffset, elementSize, constructorParams) {
    -  var dx = eventOffset.left - elementOffset.left, dy = eventOffset.top - elementOffset.top,
    -      gx = elementSize[0] / (constructorParams.grid[0]), 
    -      gy = elementSize[1] / (constructorParams.grid[1]),
    -      mx = Math.floor(dx / gx), my = Math.floor(dy / gy);
    -
    -  return [ ((mx * gx) + (gx / 2)) / elementSize[0], ((my * gy) + (gy / 2)) / elementSize[1] ];
    -}
    -</pre>
    -</div>
    -The four arguments are:
    -<ul>
    -	<li class="bullet"><strong>eventOffset</strong> - Page left/top where the mouse button was released (a JS object containing left/top members like you get from a jQuery offset call)</li>
    -	<li class="bullet"><strong>elementOffset</strong> - JS offset object containing offsets for the element on which the Connection is to be created</li>
    -	<li class="bullet"><strong>elementSize</strong> - [width, height] array of the dimensions of the element on which the Connection is to be created</li>
    -	<li class="bullet"><strong>constructorParams</strong> - the parameters that were passed to the Anchor's constructor. In the example given above, those parameters are 'position' and 'grid'; you can pass arbitrary parameters.
    -</ul>
    -The return value of this function is an array of [x, y] - proportional values between 0 and 1 inclusive, such as you can pass to a static Anchor.
    -<p>To make your own position finder you need to create a function that takes those four arguments and returns an [x, y] position for the anchor, for example:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.AnchorPositionFinders.MyFinder = function(dp, ep, es, params) {
    -	...		
    -	return [ some maths ];	
    -};
    -</pre>
    -</div>
    -Then refer to it in a makeTarget call:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("someElement", {  
    -  anchor:[ "Assign", { 
    -    position:"MyFinder",
    -    myCustomParameter:"foo",
    -    anInteger:5
    -  }]
    -});
    -</pre>
    -</div>
    -
    -<h5><a id="makeSource">jsPlumb.makeSource</a></h5>
    -There are two use cases supported by this method.  The first is the case that you want to drag a Connection from the element itself and have an Endpoint attached to the element when a Connection is established.  The second is a more specialised case: you want to drag a Connection from the element, but once the Connection is established you want jsPlumb to move it so that its source is on some other element.  For an example of this second case, check out the <a href="../demo/jquery/stateMachineDemo.html">State Machine Connectors</a> demonstration.
    -<p>Here's an example code snippet for the basic use case of makeSource:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource(someDiv, {
    -  paintStyle:{ fillStyle:"yellow" },
    -  endpoint:"Blank",
    -  anchor:"BottomCenter"
    -});
    -</pre>
    -</div>
    -<p>
    -Notice again that the second argument is the same as the second argument to an addEndpoint call.  makeSource is, essentially, a type of addEndpoint call.  In this example we have told jsPlumb that we will support dragging Connections directly from 'someDiv'.  Whenever a Connection is established between 'someDiv' and some other element, jsPlumb assigns an Endpoint at BottomCenter of 'someDiv', fills it yellow, and sets that Endpoint as the newly created Connection's source. 
    -</p>		
    -<p>Configuring an element to be an entire Connection source using makeSource means that the element cannnot itself be draggable.  There would be no way for jsPlumb to distinguish between the user attempting to drag the element and attempting to drag a Connection from the element.  If your UI works in such a way that that is acceptable then you'll be fine to use this method.  But if you want to drag Connections from some element but also have the element be draggable itself, you might want to consider the second use case.  First, a code snippet:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("someConnectionSourceDiv", {
    -  paintStyle:{ fillStyle:"yellow" },
    -  endpoint:"Blank",
    -  anchor:"BottomCenter",
    -  parent:"someDiv"
    -});
    -</pre>
    -</div>
    -<p>The only difference here is the inclusion of the 'parent' parameter.  It instructs jsPlumb to configure the source Endpoint on the element specified - in this case, "someDiv" (the value of this parameter may be a String id or a selector).</p>
    -<p>Again, I'd suggest taking a look at the <a href="../demo/jquery/stateMachineDemo.html">State Machine Connectors demonstration</a> for an example of this.</p>
    -
    -<p>
    -There are many things you can set in an Endpoint options object; for a thorough list see the API documentation for Endpoint.  
    -</p>
    -<p>Here's an example of specifying that you want an Arrow overlay halfway along any Connection dragged from this Endpoint:</p>
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -  isSource:true,
    -  connectorStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  connectorOverlays: [ [ "Arrow", { location:0.5 } ] ]
    -};
    -</pre>
    -</div>
    -This is an Endpoint that moves around the element it is attached to dependent on the location of other elements in the connections it is attached to (a 'dynamic' anchor):
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -  isSource:true,
    -  connectorStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  connectorOverlays: [ [ "Arrow", { location:0.5 } ] ]
    -  anchor:[ "TopCenter","RightMiddle","BottomCenter","LeftMiddle" ]
    -};
    -</pre>
    -</div>
    -<h5><a id="targetSourceTest">Testing if an element is a target or source</a></h5>
    -You can test if some element has been made a connection source or target using these methods:
    -<ul>
    -<li class="bullet">isTarget</li>
    -<li class="bullet">isSource</li>
    -</ul>
    -<p>The return value is a boolean.</p>
    -<h5><a id="targetSourceToggle">Toggling an element as connection target or source</a></h5>
    -You can toggle the state of some source or target using these methods:
    -<ul>
    -<li class="bullet">setTargetEnabled</li>
    -<li class="bullet">setSourceEnabled</li>
    -</ul>
    -<p>The return value from these methods is the current jsPlumb instance, allowing you to chain them:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.setTargetEnabled("aDivId").setSourceEnabled($(".aSelector"));
    -</pre>
    -</div>
    -<p>Note that if you call either of these methods on an element that was not originally configured as a target or source, nothing will happen.</p>
    -<p>You can check the enabled state of some target or source using these methods:
    -<ul>
    -<li class="bullet">isTargetEnabled</li>
    -<li class="bullet">isSourceEnabled</li>
    -</ul>
    -<h5><a id="targetSourceCancel">Canceling previous makeTarget and/or makeSource calls</a></h5>
    -jsPlumb offers four methods to let you cancel previous makeTarget or makeSource calls. Each of these methods returns the current jsPlumb instance, and so can be chained:
    -<ul>
    -<li class="bullet">unmakeTarget</li>
    -<li class="bullet">unmakeSource</li>
    -<li class="bullet">unmakeEveryTarget</li>
    -<li class="bullet">unmakeEverySource</li>
    -</ul>
    -<p>These last two are analogous to the <strong>removeEveryConnection</strong> and <strong>removeEveryEndpoint</strong> methods that have been in jsPlumb for a while now.</p>
    -<p>unmakeTarget and unmakeSource both take as argument the same sorts of values that makeTarget and makeSource accept - a string id, or a selector, or an array of either of these:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.unmakeTarget("aDivId").unmakeSource($(".aSelector"));
    -</pre>
    -</div>
    -
    -
    -<h5><a id="dragOptions">Drag Options</a></h5>
    -	These are options that will be passed through to the supporting library's drag API.  jsPlumb passes everything you supply here through, inserting
    -	wrapping functions if necessary for the various lifecycle events that jsPlumb needs to know about.  So if, for example, you pass a function to be
    -	called when dragging starts, jsPlumb will wrap that function with a function that does what jsPlumb needs to do, then call yours.
    -	<p>
    -	At the time of writing, jsPlumb supports jQuery, MooTools and YUI3, and each of those libraries uses different terminology.  In addition, jQuery's API is
    -	more fully featured, providing easy support for setting the zIndex and opacity of elements being dragged, as well as the 'scope' for a drag/drop (allowing
    -	you to specify more than one type of drag/drop pair), and hover classes for when a draggable is on the move or
    -	over a droppable. If you're using jQuery you can of course just supply these values on the dragOptions; to make it easier, jsPlumb's MooTools and YUI3 adapters recognize 
    -	these options and add appropriate callbacks for you.  
    -	</p> 
    -	<p>
    -	Given that the options here are library-specific, and they are all well-documented, we're going to discuss just the three drag options
    -	that behave the same way in all (see below for hoverClass):
    -		<ul>
    -		<li><strong>opacity</strong> - the opacity of an element that is being dragged.  Should be a fraction between 0 and 1 inclusive.</li>
    -		<li><strong>zIndex</strong> - the zIndex of an element that is being dragged.</li>
    -		<li><strong>scope</strong> - the scope of the draggable.  can only be dropped on a droppable with the same scope.  this is discussed below.</li>
    -		</ul> 
    -	</p>
    -	
    -	<p>For more information about drag options, take a look at the <a href="http://docs.jquery.com/UI/Draggable" target="_blank">jQuery</a>, <a href="http://mootools.net/docs/more/Drag/Drag.Move" target="_blank">MooTools</a>, or <a href="http://developer.yahoo.com/yui/3/dd/" target="_blank">YUI3</a> docs.	
    -	</p>
    -	<p><strong>NOTE: there is an issue in Chrome that affects the 'cursor' argument to drag options in jQuery. See these links:</strong>
    -	</p><p>
    -<a href="http://forum.jquery.com/topic/draggable-cursor-option-does-not-work" target="_blank">http://forum.jquery.com/topic/draggable-cursor-option-does-not-work</a><br/>
    -<a href="http://forum.jquery.com/topic/chrome-text-select-cursor-on-drag" target="_blank">http://forum.jquery.com/topic/chrome-text-select-cursor-on-drag</a>
    -</p>
    -You should put something like this at the top of your JS to avoid it (perhaps not something so drastic as a document-wide override):	
    -<div class="code">
    -<pre>document.onselectstart = function () { return false; };</pre>
    -</div>		
    -	
    -	<h5><a id="dropOptions">Drop Options</a></h5>
    -	Drop options are treated by jsPlumb in the same way as drag options - they are passed through to the underlying library.  MooTools does not have drop options like jQuery and YUI3 do; droppable functionality in MooTools 
    -	is actually implemented by the Drag.Move class - the one used to initialise a draggable.  But when you setup an Endpoint in jsPlumb you should ignore
    -	that fact, and treat droppables like you would in jQuery or YUI3.  jsPlumb wires everything up for you under the hood.
    -	<p>There are three jQuery droppable options that jsPlumb treats as shortcuts in MooTools and YUI3, for the sake of consistency:</p>
    -		<ul>
    -			<li><strong>hoverClass</strong> - the CSS class to attach to the droppable when a draggable is hovering over it.</li>
    -			<li><strong>activeClass</strong> - the CSS class to attach to the droppable when a draggable is, um, being dragged.</li>
    -			<li><strong>scope</strong> - the scope of the draggable.  The draggable can only be dropped on a droppable with the same scope.  This is discussed below.</li>
    -		</ul>   
    -	<p>For more information about drop options when using jQuery, see <a href="http://docs.jquery.com/UI/Droppable" target="_blank">here</a>.</p>
    -		
    -	<h5><a id="dragAndDropScope">Drag and Drop Scope</a></h5>
    -		jsPlumb borrowed the concept of 'scope' from jQuery's drag/drop implementation: the notion of
    -		which draggables can be dropped on which droppables.  In jsPlumb you can provide a 'scope' entry when 
    -		creating an Endpoint.  Here's the example grey Endpoint example with 'scope' added:
    -		
    -<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:"#666" },
    -  isSource:true,
    -  connectionStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  scope:"exampleGreyConnection"
    -};</pre>		
    -</div>  		
    -  										
    -	If you do not provide a 'scope' entry, jsPlumb uses a default scope.  Its value is accessible through this method:
    -	<div class="code">
    -<pre>
    -jsPlumb.getDefaultScope();
    -</pre>	
    -	</div>				
    -	If you want to change it for some reason you can do so with this method:
    -	<div class="code">
    -<pre>
    -jsPlumb.setDefaultScope("mySpecialDefaultScope");
    -</pre>	
    -	</div>			
    -	You can also, should you want to, provide the scope value separately on the drag/drop options, like this:
    -	<div class="code">
    -<pre>
    -var exampleGreyEndpointOptions = {
    -  endpoint:"Rectangle",
    -  paintStyle:{ width:25, height:21, fillStyle:'#666' },
    -  isSource:true,
    -  connectionStyle : { strokeStyle:"#666" },
    -  isTarget:true,
    -  dragOptions:{ scope:"dragScope" },
    -  dropOptions:{ scope:"dropScope" }
    -};</pre>		
    -</div>  
    -			
    -	</div>	
    -
    -	<div class="section">
    -		<h3><a id="parameters">Connection Parameters</a></h3>
    -		jsPlumb has a mechanism that allows you to set parameters on a per-connection basis.  This can be achieved in a few different ways:
    -
    -		<ul>
    -			<li class="bullet">Providing parameters to a jsPlumb.connect call</li>
    -			<li class="bullet">Providing parameters to an addEndpoint call to/from which a connection is subsequently established using the mouse</li>
    -			<li class="bullet">using the <strong>setParameter</strong> or <strong>setParameters</strong> method on a Connection</li>
    -		</ul>
    -		Getting parameters is achieved through either the <strong>getParameter(key)</strong> or <strong>getParameters</strong> method on a Connection.
    -		<h4><a id="connectParameters">jsPlumb.connect</a></h4>
    -		Parameters can be passed in via an object literal to a jsPlumb.connect call:
    -		<div class="code">
    -<pre>var myConnection = jsPlumb.connect({
    -	source:"foo",
    -	target:"bar",
    -	parameters:{
    -		"p1":34,
    -		"p2":new Date(),
    -		"p3":function() { console.log("i am p3"); }
    -	}
    -});
    -</pre>
    -</div>
    -Note that they can be any valid Javascript objects - it's just an object literal.  You then access them like this:
    -		<div class="code">
    -<pre>myConnection.getParameter("p3")();     // prints 'i am p3' to the console.
    -</pre>
    -</div>
    -
    -<h4><a id="addEndpointParameters">jsPlumb.addEndpoint</a></h4>
    -<p>The information in this section also applies to the <strong>makeSource</strong> and <strong>makeTarget</strong> functions</p>
    -<p>Using jsPlumb.addEndpoint, you can set parameters that will be copied in to any Connections that are established from or to the given Endpoint using the mouse.  (If you set parameters on both a source and target Endpoints and then connect them, the parameters set on the target Endpoint are copied in first, followed by those on the source. So the source Endpoint's parameters take precedence if they happen to have one or more with the same keys as those in the target).</p>
    -Consider this example:
    -		<div class="code">
    -<pre>var e1 = jsPlumb.addEndpoint("d1", {
    -	isSource:true,
    -	parameters:{
    -		"p1":34,
    -		"p2":new Date(),
    -		"p3":function() { console.log("i am p3"); }
    -	}
    -});
    -
    -var e2 = jsPlumb.addEndpoint("d2", {
    -	isTarget:true,
    -	parameters:{
    -		"p5":343,
    -		"p3":function() { console.log("FOO FOO FOO"); }
    -	}
    -});
    -
    -var conn = jsPlumb.connect({source:e1, target:e2});
    -
    -
    -</pre>
    -</div>
    -
    -'conn' will have four parameters set on it, with the value for "p3" coming from the source Endpoint:
    -		<div class="code">
    -<pre>var params = conn.getParameters();
    -console.log(params.p1);   	// 34
    -console.log(params.p2);   	// Mon May 14 2012 12:57:12 GMT+1000 (EST) (or however your console prints out a Date)
    -console.log((params.p3)()); // "i am p3"  (note: we executed the function after retrieving it)
    -console.log(params.p5);   	// 343
    -
    -</pre>
    -</div>
    -
    -
    -</div> <!-- /section -->
    -	
    -	<div class="section">
    -		<h3><a id="beforeDrop">Interceptors</a></h3>
    -		<p>
    -		Interceptors were new in jsPlumb 1.3.4.  They are basically event handlers from which you can return a value that tells jsPlumb to abort what it is that
    -		it was doing.  There are currently two interceptors supported - <strong>beforeDrop</strong>, which is called when the user has dropped a Connection onto some target, 
    -		and <strong>beforeDetach</strong>, which is called when the user is attempting to detach a Connection. 
    -		</p>
    -		<p>Interceptors can be registered via the <strong>bind</strong> method on jsPlumb just like any other event listeners, and they can also be passed in to
    -		the <strong>jsPlumb.addEndpoint</strong>, <strong>jsPlumb.makeSource</strong> and <strong>jsPlumb.makeTarget</strong>
    -		methods.  A future version of jsPlumb will support registering interceptors on Endpoints using the <strong>bind</strong> method as you can now with the jsPlumb object.
    -		Note that binding 'beforeDrop' (as an example) on jsPlumb itself is like a catch-all: it will be called every time a Connection is dropped on _any_ 
    -		Endpoint.  But passing a 'beforeDrop' callback into an Endpoint (and in the future, registering one using 'bind') constrains that callback to just the
    -		Endpoint in question.  		
    -		<h4><a id="beforeDrop">beforeDrop</a></h4>
    -		This event is fired when a new or existing connection has been dropped. Your callback is passed a JS object with these fields:
    -		<ul>
    -			<li><strong>sourceId</strong> - the id of the source element in the connection</li>
    -			<li><strong>targetId</strong> - the id of the target element in the connection</li>
    -			<li><strong>scope</strong> - the scope of the connection</li>
    -			<li><strong>connection</strong> - the actual Connection object.  You can access the 'endpoints' array in a Connection to get the Endpoints involved in the Connection, but be aware that when a Connection is being dragged, one of these Endpoints will always be a transient Endpoint that exists only for the life of the drag. To get the Endpoint on which the Connection is being dropped, use the 'dropEndpoint' member.</li>
    -			<li><strong>dropEndpoint</strong> - this is the actual Endpoint on which the Connection is being dropped.  This <strong>may be null</strong>, because it will not be set if the Connection is being dropped on an element on which makeTarget has been called. </li>
    -		</ul>
    -
    -		If you return false (or nothing) from this callback, the new Connection is aborted and removed from the UI.
    -		<h4><a id="beforeDetach">beforeDetach</a></h4>
    -		This is called when the user has detached a Connection, which can happen for a number of reasons: by default, jsPlumb allows users to drag Connections off of target Endpoints, but this can also result from a programmatic 'detach' call.  Every case is treated the same by jsPlumb, so in fact it is possible for you to write code that attempts to detach a Connection but then denies itself!  You might want to be careful with that. 
    -		<p>Note that this interceptor is passed the actual Connection object; this is different from the beforeDrop
    -		interceptor discussed above: in this case, we've already got a Connection, but with beforeDrop we are yet 
    -		to confirm that a Connection should be created.</p>
    -		<p>Returning false - or nothing - from this callback will cause the detach to be abandoned, and the Connection will be reinstated or left on its current target.</p>
    -			
    -	</div>
    -
    -		<div class="section">
    -			<h3><a id="paintstyles">Paint Styles</a></h3>
    -			Defining the appearance of Connectors, Endpoints (and Overlays, but this is deprecated in favour of using CSS classes) is achieved through a 'paintStyle' (or a quite similar name)
    -			object passed as a parameter to one of jsPlumb.connect, jsPlumb.addEndpoint, jsPlumb.makeSource or jsPlumb.makeTarget. Depending on the method you are calling, the parameter names vary.
    -			<h5>Connector Paint Styles</h5>
    -			These are specified in a <em>paintStyle</em> parameter on a call to <strong>jsPlumb.connect</strong>:
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			or in the <em>connectorPaintStyle</em> parameter on a call to <strong>jsPlumb.addEndpoint</strong> or <strong>jsPlumb.makeSource</strong>:
    -			<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div> 			
    -			 
    -
    -Notice the <em>paintStyle</em> parameter in those examples: it is the paint style for the Endpoint, which we'll discuss below.
    -
    -			<h5>Endpoint Paint Styles</h5>
    -			These are specified in a <em>paintStyle</em> parameter on a call to <strong>addEndpoint</strong>. This is the example from just above:
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -</div> 
    -			...or as the <em>endpointStyle</em> parameter to a <strong>connect</strong> call:		
    -			<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			... or as an entry in the <em>endpointStyles</em> array passed to a <strong>jsPlumb.connect</strong> call:
    -					<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  endpointStyles:[ 
    -    { fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -    { fillStyle:"green" }
    -  ],
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 }
    -});
    -</pre>			
    -			</div>
    -			or as the <em>paintStyle</em> parameter passed to a <strong>makeTarget</strong> or <strong>makeSource</strong> call:
    -<div class="code">
    -<pre>
    -jsPlumb.makeTarget("el1", {
    -  ...
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  ...
    -});
    -</pre>			
    -</div>
    -<div class="code">
    -<pre>
    -jsPlumb.makeSource("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 }
    -  parent:"someOtherDivIJustPutThisHereToRemindYouYouCanDoThis"
    -});
    -</pre>			
    -			</div>						
    -	In the first example we  made "el1" into a drop target, and defined a paint style for the Endpoint jsPlumb will create when a Connection is
    -	established.  In the second we made "el1" a connection source and assigned the same values for the Endpoint jsPlumb will create when a Connection
    -	is dragged from that element.
    -	
    -	<h5>Overlay Paint Styles</h5>
    -	The preferred way to set paint styles for Overlays is to use the <em>cssClass</em> parameter in the constructor arguments of an Overlay definition.
    -	
    -	<h4>Paint Style Parameters</h4>
    -	This is the full list of parameters you can set in a paintStyle object, but note that <strong>fillStyle</strong> is ignored by Connectors,
    -	and <strong>strokeStyle</strong> is ignored by Endpoints.  Also, if you create a Connection using jsPlumb.connect and do not specify any
    -	Endpoint styles, the Endpoints will derive their fillStyle from the Connector's strokeStyle.
    -	<p>
    -		fillStyle, strokeStyle and outlineColor can be specified using any valid CSS3 syntax.
    -	</p>
    -	<ul>
    -		<li><strong>fillStyle</strong> - color for an Endpoint, eg. rgba(100,100,100,50), "blue", "#456", "#993355", rgb(34, 56, 78).</li>
    -		<li><strong>strokeStyle</strong> - color for a Connector. see fillStyle examples.</li>
    -		<li><strong>lineWidth</strong> - width of a Connector's line. An integer.</li>
    -		<li><strong>outlineWidth</strong> - width of the outline for an Endpoint or Connector. An integer.</li>
    -		<li><strong>outlineColor</strong> - color of the outline for an Endpoint or Connector. see fillStyle examples.</li>
    -		<li><strong>dashstyle</strong> - VML and SVG only. This comes from VML, and allows you to create dashed or dotted lines.  It has a 
    -			better syntax than the equivalent attribute in SVG (<em>stroke-dasharray</em>, discussed below), so jsPlumb supports this for both renderers. 
    -			The <em>dashstyle</em> attribute is specified as an array of strokes and spaces, where each value is some multiple of <em>the width of 
    -			the Connector</em>, and that's where it's better than SVG, which uses pixels.
    -			<p>
    -			<a href="http://www.w3.org/TR/NOTE-VML" target="_blank">The VML spec</a> is a good place to find valid values for dashstyle. Note that
    -			jsPlumb does not support the string values for this attribute ("solid", "dashdot", etc).
    -			</p>
    -			<p>
    -			In SVG render mode, jsPlumb uses the <em>lineWidth</em> parameter in conjunction with the values in a <em>dashstyle</em> attribute
    -			to create an appropriate value for <em>stroke-dasharray</em>.
    -			</p>
    -		</li>
    -		<li><strong>stroke-dasharray</strong> - SVG only. This is the SVG equivalent of <em>dashstyle</em>.  
    -		<a href="http://www.w3.org/TR/SVG/painting.html" target="_blank">The SVG spec</a> discusses valid values for this parameter.  But be aware
    -		that jsPlumb does not convert this into a valid <em>dashstyle</em> attribute when using the VML renderer. Better to use <em>dashstyle</em>.
    -		</li>
    -		<li><strong>stroke-dashoffset</strong> - SVG only.  This is used in SVG to specify how far into the dash pattern to start painting.  For more information,
    -		see <a href="http://www.w3.org/TR/SVG/painting.html" target="_blank">the SVG spec.</a></li>
    -		<li><strong>joinstyle</strong> - VML and SVG only.  As with <em>dashstyle</em>, this is a VML attribute that jsPlumb supports for both VML
    -		and SVG - jsPlumb turns this into a <em>stroke-linejoin</em> attribute when rendering with SVG.  This attribute specifies how you want individual
    -		segments of connectors to be joined; the VML and SVG specs both have examples of this, of which many are the same between the two, which is
    -		why jsPlumb will automatically convert this attribute into the SVG equivalent.
    -		</li>
    -		<li><strong>stroke-linejoin</strong> - SVG only.  This is the equivalent of VML's <em>joinstyle</em> attribute, but as with <em>stroke-dasharray</em>,
    -		jsPlumb does not convert this into something approriate for VML.  So, using <em>joinstyle</em> will enable you to support more browsers with
    -		less effort.		
    -		</li>
    -	</ul>
    -	
    -	<h3><a id="hoverpaintstyles">Hover Paint Styles</a></h3>
    -	 		
    -	 		Connectors and Endpoints both support the concept of a "hover" paint style - a paint style to use when the mouse is hovering over
    -	 		the component.  These are specified in the exact same format as paint styles discussed above, but hover paint styles also inherit
    -	 		any values from the main paint style.  This is because you will typically want to just change the color, or perhaps outline color, of
    -	 		a Connector or Endpoint when the mouse is hovering, but leave everything else the same.  So having hover paint styles inherit their
    -	 		values precludes you from having to define things in more than one place.
    -	 		<p>
    -	 			The naming convention adopted for hover paint styles is pretty much to insert the word 'hover' into the corresponding main paint
    -	 			style parameters.  Here are a couple of examples:
    -	 		</p> 
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -  hoverPaintStyle:{ strokeStyle:"red" },
    -  endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  endpointHoverStyle:{ fillStyle:"red" }
    -});
    -</pre>			
    -</div>
    -			In this example we specified a hover style for both the Connector, and each of its Endpoints.  Here's the same thing, but using the plural version, to specify a different hover style for each Endpoint:	
    -<div class="code">
    -<pre>
    -jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  paintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -  hoverPaintStyle:{ strokeStyle:"red" },
    -  endpointStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  endpointHoverStyles:[ { fillStyle:"red" }, { fillStyle:"yellow" } ]
    -});
    -</pre>			
    -</div>
    -	 		Calls to <strong>addEndpoint</strong>, <strong>makeSource</strong> and <strong>makeTarget</strong> can also specify various hover paint styles:
    -	 								<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("el1", {
    -  paintStyle:{ fillStyle:"blue", outlineColor:"black", outlineWidth:1 },
    -  hoverPaintStyle:{ fillStyle:"red" },
    -  connectorPaintStyle:{ strokeStyle:"blue", lineWidth:10 },
    -  connectorHoverPaintStyle:{ strokeStyle:"red", outlineColor:"yellow", outlineWidth:1 }
    -});
    -
    -jsPlumb.makeSource("el2", {
    -  paintStyle:{ fillStyle:"transparent", outlineColor:"yellow", outlineWidth:1 },
    -  hoverPaintStyle:{ fillStyle:"red" },
    -  connectorPaintStyle:{ strokeStyle:"green", lineWidth:3 },
    -  connectorHoverPaintStyle:{ strokeStyle:"#678", outlineColor:"yellow", outlineWidth:1 }
    -});
    -
    -jsPlumb.makeTarget("el3", {
    -  paintStyle:{ fillStyle:"transparent", outlineColor:"yellow", outlineWidth:1 },
    -  hoverPaintStyle:{ fillStyle:"red" }
    -});
    -</pre>			
    -			</div> 
    -	 		
    -	 		In these examples we specified a hover paint style for both the Endpoint we are adding, and any Connections to/from the Endpoint.
    -	 		<p>	
    -		Note that <strong>makeTarget</strong> does not support Connector parameters. It is for creating targets only; Connector parameters will be set by the source Endpoint in any Connections that are made to the element that you turned into a target by using this method.</p>
    -	 			 		
    -			<h4><a id="gradients">Gradients</a></h4>
    -			The Canvas and SVG renderers both support gradients. The VML renderer does not.  jsPlumb uses its own syntax to define gradients, to abstract out the differences between the syntax required by canvas and that required by SVG.  
    -			<p>There are two types of gradients available - a 'linear' gradient, which consists of colored lines all
    -			going in one direction, and a 'radial' gradient, which consists of colored circles emanating from one circle to another.
    -			Because of their basic shape, jsPlumb supports only <em>linear</em> gradients for
    -			Connectors.  But for Endpoints, jsPlumb supports both <em>linear</em> and <em>radial</em> gradients.</p>
    -			
    -				<h5>Connector gradients</h5>
    -				To specify a linear gradient to use in a Connector, you must add a <em>gradient</em> object to your
    -				Connector's <em>paintStyle</em>, for instance:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : "window2",
    -	target : "window3",
    -	paintStyle:{
    -		gradient:{
    -			stops:[[0,"green"], [1,"red"]]
    -		},
    -		lineWidth:15
    -	}
    -});
    -</pre>
    -</div>
    -Here we have connected window2 to window3 with a 15 pixel wide connector that has a gradient from green to red.</p><p> Notice the <strong>gradient</strong> object and the
    -<strong>stops</strong> list inside it - the gradient consists of an arbitrary number of these "color stops".  Each color stop is comprised
    -of two values - [position, color].  Position must be a decimal value between 0 and 1 (inclusive), and indicates where the color
    -stop is situated as a fraction of the length of the entire gradient.  Valid values for
    -the colors in the <strong>stops</strong> list are the same as those that are valid for <strong>strokeStyle</strong> when describing a color.
    -			</p>
    -			As mentioned, the <strong>stops</strong> list can hold an arbitrary number of entries.  Here's an example of a gradient that goes from red to blue to green, and back again through blue to red:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle : {
    -		gradient:{
    -			stops:[[0,'red'], [0.33,'blue'], [0.66,'green'], [0.33,'blue'], [1,'red']]
    -		},
    -		lineWidth : 15
    -	}
    -});
    -</pre>
    -</div>
    -<strong>Note:</strong> when using the VML renderer, jsPlumb will simply ignore the gradient directive
    -so it is best to ensure you also supply a <em>strokeStyle</em> in your paintStyle object, to give jsPlumb something to fall back on.  If you do not supply
    -a <em>strokeStyle</em> your Connectors will be painted black.  The previous example might look like this, for instance:
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle:{
    -		strokeStyle:'red',
    -		gradient:{
    -			stops:[[0,'red'], [0.33,'blue'], [0.66,'green'], [0.33,'blue'], [1,'red']]
    -		},
    -		lineWidth:15
    -	}
    -});
    -</pre>
    -</div>
    -Notice the <strong>strokeStyle:'red'</strong> directive at the beginning of the parameter list in <strong>paintStyle</strong>.
    -			<h5>Endpoint gradients</h5>
    -			Endpoint gradients are specified using the same syntax as Connector gradients.  You put the gradient specifier either in the
    -			<strong>endpoint</strong> member, or if you are specifying different Endpoints for each end of the Connector, in one or both of
    -			the values in the <strong>endpoints</strong> array.  Also, this information applies to the case that you are creating standalone
    -			Endpoints that you will be configuring for drag and drop creation of new Connections. 
    -			<p>
    -			This is an example of an Endpoint gradient that is different for each Endpoint in the Connector.  This comes from the main demo; it is
    -			the Connector joining Window 2 to Window 3:
    -			</p>
    -<div class="code">
    -<pre>var w23Stroke = 'rgb(189,11,11)';
    -jsPlumb.connect({
    -	source : 'window2',
    -	target : 'window3',
    -	paintStyle:{
    -		lineWidth:8,
    -		strokeStyle:w23Stroke
    -	},
    - 	anchors:[ [0.3,1,0,1], "TopCenter" ],
    - 	endpoint:"Rectangle",
    - 	endpointStyles:[
    - 		{ gradient : {stops:[[0, w23Stroke], [1, '#558822']] } },
    -    	{ gradient : {stops:[[0, w23Stroke], [1, '#882255']] } }
    -    ]
    -});
    -</pre>
    -</div>
    -The first entry in the gradient will be the one that is on the Connector end of the Endpoint.  You can of course have as many color stops as
    -you want in this gradient, just like with Connector gradients.
    -	<h5>Applying the gradient in Endpoints</h5>
    -	Only the Dot and Rectangle endpoints honour the presence of a gradient (and, remember, not in VML). The Image endpoint of course ignores a gradient
    -	as it does no painting of its own.
    -	<p>The type of gradient you will see depends on the Endpoint type:</p>
    -		<ul>
    -			<li><strong>Dot</strong> - renders a radial endpoint, with color stop 0 on the outside, progressing inwards as we move through color stops.
    -			<p>Radial gradients actually require more data than linear gradients - in a linear gradient we just move from one point to another, whereas
    -			in a radial gradient we move from one <em>circle</em> to another.  By default, jsPlumb will render a radial gradient using a source
    -			circle of the same radius as the Endpoint itself, and a target circle of 1/3 of the radius of the Endpoint (both circles share the
    -			same center as the Endpoint itself). This circle will be offset by radius/2 in each direction.</p>
    -			<p>You can supply your own values for these inside the gradient descriptor:</p>
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect({
    -	source : 'window3',
    -	target : 'window4',
    -    paintStyle:{
    -    	lineWidth:10,
    -    	strokeStyle:w34Stroke
    -    },
    -    anchors:[ "RightMiddle", "LeftMiddle" ],
    -    endpointStyle:{
    -    	gradient : {
    -    		stops:[ [0, w34Stroke], [1, w34HlStroke] ],
    -    		offset:37.5,
    -    		innerRadius:40
    -    	},
    -    	radius:55
    -    }
    - });
    -</pre>
    -</div>
    -Here we have instructed jsPlumb to make the gradient's inner radius 10px instead of the default 25/3 = 8 ish pixels, and the offset in each direction
    -will be 5px, instead of the default radius / 2 = 12.5 pixels.
    -<p>It is also possible to specify the offset and inner radius as percentages - enter the values as strings with a '%' symbol on the end:</p>
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect({
    -	source : 'window3', 
    -	target : 'window4',
    -    paintStyle:{
    -    	lineWidth:10,
    -    	strokeStyle:w34Stroke
    -    },
    -	anchors:[ "RightMiddle", "LeftMiddle" ],
    -	endpointStyle:{
    -		gradient : {
    -			stops:[ [0, w34Stroke], [1, w34HlStroke] ],
    -			offset:'68%',
    -			innerRadius:'73%'
    -		},
    -		radius:25
    -	}
    -});
    -</pre>
    -</div>
    -This will give roughly the same output as the example above (the percentages are not entirely exact).<br/><br/>
    -			</li>
    -			<li><strong>Rectangle</strong> - renders a linear endpoint, with color stop 0 closest to the end of the Connector</li>
    -		</ul>
    -		
    -<h4><a id="svgvmlcss">Styling SVG/VML using CSS</a></h4>
    -	A nice way of controlling your UI's appearance is to make use of the fact that both SVG and VML can be styled using CSS.
    -	<p>This section will be expanded in the next few weeks to give some decent examples of how to do so</p>
    -			
    -		
    -		
    -		</div>
    -
    -		<div class="section">
    -			<h3><a id="cssclasses">CSS Class Reference</a></h3>
    -			jsPlumb attaches classes to each of the UI components it creates.
    -			<p>These class names are exposed on the jsPlumb object and can be overridden if you need to do so (see the third column in the table) </p>
    -			<table width="70%" class="table">
    -				<tr><th>Component</th><th>CSS Class</th><th>jsPlumb Member</th></tr>
    -				<tr><td>Connector</td><td>_jsPlumb_connector</td><td>connectorClass</td></tr>
    -				<tr><td>Endpoint</td><td>_jsPlumb_endpoint</td><td>endpointClass</td></tr>
    -				<tr><td>Overlay</td><td>_jsPlumb_overlay</td><td>overlayClass</td></tr>
    -			</table>
    -			<p>You would typically use these to establish appropriate z-indices for your UI.</p>			
    -		</div>
    -				
    -		<div class="section">
    -			<h3><a id="animation">Animation</a></h3>
    -			jsPlumb offers an 'animate' function, which wraps the underlying animation engine for whichever library you happen to be using and
    -			inserts a callback for jsPlumb to repaint whatever it needs to at each step.  You could of course do this yourself; it's a convenience
    -			method really.
    -			<p>The method signature is:</p>
    -<div class="code">
    -<pre>
    -jsPlumb.animate : function(el, properties, options) 
    -</pre>
    -</div>			
    -The arguments are as follows:
    -	<ul>
    -		<li><strong>el</strong> - element id, or element object from the library you're using.</li>
    -		<li><strong>properties</strong> - properties for the animation, such as duration etc.</li>
    -		<li><strong>options</strong> - options for the animation, such as callbacks etc.</li>
    -	</ul>
    -	
    -		<h4>jQuery and the 'revert' option</h4>
    -		jQuery offers a 'revert' option that you can use to instruct it to revert a drag under some condition. This is
    -		rendered in the UI as an animation that returns the drag object from its current location to wherever it started
    -		out.  It's a nice feature.  Unfortunately, the animation that runs the revert does not offer any lifecycle
    -		callback methods - no 'step', no 'complete' - so its not possible for jsPlumb to know that the revert 
    -		animation is taking place.
    -	
    -		</div>
    -		
    -			<div class="section">
    -			<h3><a id="connectionInfo">Retrieving Connection Information</a></h3>
    -			There are two ways of retrieving connection information from jsPlumb. <strong>getConnections</strong>is the original method; from 1.4.0 onwards this method is supplemented by <a href="#selectConnections">jsPlumb.select</a>, a much more versatile variant.
    -			<h5>getConnections</h5>
    -			<p>Before you use this method you should understand jsPlumb's notion of 'scope' - documentation
    -			is <a href="#dragAndDropScope">here</a> </p>
    -			
    -			<h4>Retrieving connections for a single scope</h4>
    -			<p>To do this, you call getConnections with either no arguments, in which case jsPlumb uses the default scope, or with a string
    -			specifying one scope</p>
    -			<div class="code">
    -<pre>var connectionList = jsPlumb.getConnections();     // you get a list of Connection objects that are in the default scope.
    -</pre>			
    -			</div>
    -			Compare this with:
    -			<div class="code">
    -<pre>var connectionList = jsPlumb.getConnections("myScope");     // you get a list of Connection objects that are in "myScope".
    -</pre>			
    -			</div>
    -			<h4>More advanced filtering</h4>
    -			getConnections optionally takes a JS object specifying filter parameters, of which there are three:
    -			<ul>
    -				<li>scope - the scope(s) of the connection type(s) you wish to retrieve</li>
    -				<li>source - limits the returned connections to those that have this source id</li>
    -				<li>target - limits the returned connections to those that have this target id</li>
    -			</ul>			
    -			Each of these three parameters may be supplied as a string, which for source and target is an element id and for scope is the name of the scope, or a list of strings.  Also from 1.4.0 you can pass "*" in as the value for any of these - a wildcard, meaning any value.  See the examples below.
    -			<p>
    -			The return value of a call to getConnection using a JS object as parameter varies on how many scopes you defined.  If you defined only a single scope then jsPlumb returns you a list of Connections in that scope.  Otherwise the return value is a dictionary whose keys are  scope names, and whose values are lists of Connections. For example, the following call:
    -			</p>
    -			<div class="code">
    -<pre>
    -	jsPlumb.getConnections({scope:["someScope", "someCustomScope"]});
    -</pre>
    -</div>
    -would result in this output:			
    -<div class="code">
    -<pre>
    -{
    -	"someScope" : [ 1..n Connections ],
    -	"someCustomScope": [ 1..m Connections ]
    -}
    -</pre>
    -</div>	
    -
    -There is an optional second parameter that tells getConnections to flatten its output and just return you an array.  The previous example with this parameter would look like this:
    -			<div class="code">
    -<pre>
    -	jsPlumb.getConnections({scope:["someScope", "someCustomScope"]}, true);
    -</pre>
    -</div>
    -would result in this output:			
    -<div class="code">
    -<pre>
    -[ 1..n Connections ]
    -</pre>
    -</div>	
    -		
    -		The following examples show the various ways you can get connection information:
    -	<ul>
    -		<li>Get all connections:
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getAllConnections();  
    -</pre>
    -</div>						
    -		</li>
    -<li>Get all connections for the default scope only (return value is a list):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections();  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given scope (return value is a list):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:"myTestScope"});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given scopes (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:["myTestScope", "yourTestScope"]});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given source (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:"mySourceElement"});  
    -</pre>
    -</div>						
    -		</li>
    -		<li>Get all connections for the given sources (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:["mySourceElement", "yourSourceElement"]});  
    -</pre>
    -</div>						
    -		</li>		
    -<li>Get all connections for the given target (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({target:"myTargetElement"});  
    -</pre>
    -</div>						
    -		</li>				
    -<li>Get all connections for the given source and targets (return value is a map of scope names to connection lists):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({source:"mySourceElement", target:["target1", "target2"]});  
    -</pre>
    -</div>						
    -		</li>				
    -		
    -<li>Get all connections for the given scope, with the given source and target (return value is a list of connections):
    -			<div class="code">
    -<pre>
    -var c = jsPlumb.getConnections({scope:'myScope", source:"mySourceElement", target:"myTargetElement"});  
    -</pre>
    -</div>						
    -		</li>				
    -		
    -	</ul>	
    -
    -		</div>
    -
    -		<div class="section">
    -			<h3><a id="selectConnections">Selecting Connections</a></h3>
    -			Introduced in 1.4.0, <strong>jsPlumb.select</strong> provides a fluid interface for working with lists of Connections.  The syntax used to specify which Connections you want is identical to that which you use for getConnections, but the return value is an object that supports most operations that you can perform on a Connection, and which is also chainable, for setter methods. Certain getter methods are also supported, but these are not chainable; they return an array consisting of all the Connections in the selection along with the return value for that Connection.
    -			<p>
    -			This is the full list of setter operations supported by jsPlumb.select:
    -			<ul>
    -				<li class="bullet">addOverlay</li>
    -				<li class="bullet">removeOverlay</li>
    -				<li class="bullet">removeOverlays</li>
    -				<li class="bullet">showOverlay</li>
    -				<li class="bullet">hideOverlay</li>
    -				<li class="bullet">showOverlays</li>
    -				<li class="bullet">hideOverlays</li>
    -				<li class="bullet">removeAllOverlays</li>
    -				<li class="bullet">setLabel</li>
    -				<li class="bullet">setPaintStyle</li>
    -				<li class="bullet">setHoverPaintStyle</li>
    -				<li class="bullet">setDetachable</li>
    -				<li class="bullet">setConnector</li>
    -				<li class="bullet">setParameter</li>
    -				<li class="bullet">setParameters</li>
    -				<li class="bullet">detach</li>
    -			</ul>
    -
    -			Each of these operations returns a selector that can be chained.
    -			</p>
    -			These is the full list of getter operations supported by jsPlumb.select:
    -			<p>
    -				<ul>
    -				<li class="bullet">getLabel</li>
    -				<li class="bullet">getOverlay</li>
    -				<li class="bullet">isHover</li>
    -				<li class="bullet">isDetachable</li>
    -				<li class="bullet">getParameter</li>
    -				<li class="bullet">getParameters</li>
    -			</ul>
    -
    -			Each of these operations returns an array whose entries are [ value, Connection ] arrays, where 'value' is the return value from the given Connection.  Remember that the return values from a getter are not chainable, but a getter may be called at the end of a chain of setters.
    -			</p>
    -
    -			<p>Some examples:</p>
    -			Select all Connections and set their hover state to be false:
    -
    -<div class="code">
    -<pre>
    -jsPlumb.select().setHover(false);
    -</pre>
    -</div>
    -			Select all Connections from "d1" and remove all Overlays:
    -
    -<div class="code">
    -<pre>
    -jsPlumb.select({source:"d1"}).removeAllOverlays();
    -</pre>
    -</div>
    -
    -Select all connections in scope "foo" and set their paint style to be a thick blue line:
    -<div class="code">
    -<pre>
    -jsPlumb.select({scope:"foo"}).setPaintStyle({ strokeStyle:"blue", lineWidth:5 });
    -</pre>
    -</div>
    -Select all Connections from "d1" and detach them:
    -<div class="code">
    -<pre>
    -jsPlumb.select({source:"d1"}).detach();
    -</pre>
    -</div>
    -<h5>.each iterator</h5>
    -The return value of jsPlumb.select has a <strong>.each</strong> function that allows you to iterate through the list, performing an operation on each one:
    -<div class="code">
    -<pre>
    -jsPlumb.select({scope:"foo"}).each(function(connection) {
    -	
    -		// do something 
    -});
    -</pre>
    -</div>
    -.each is itself chainable:
    -<div class="code">
    -<pre>
    -jsPlumb.select({scope:"foo"}).each(function(connection) {
    -	
    -		// do something 
    -
    -}).setHover(true);
    -</pre>
    -</div>
    -
    -
    -<h5>Other properties/functions</h5>
    -<ul>
    -	<li class="bullet"><strong>length</strong> - this member reports the number of Connections in the selection</li>
    -	<li class="bullet"><strong>get(idx)</strong> - this function allows you to retrieve a Connection from the selection</li>
    -</ul>
    -
    -
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="events">Events</a></h3>
    -			jsPlumb supports binding to several different events on Connections, Endpoints and Overlays, and also on the jsPlumb object itself.  
    -			
    -			<h4><a id="jsPlumbEvents">jsPlumb Events</a></h4>
    -			<p>To bind an event to jsPlumb itself, use <strong>jsPlumb.bind(event, callback)</strong>:</p>
    -			<div class="code">
    -<pre>
    -jsPlumb.bind("jsPlumbConnection", function(info) {
    -   .. update your model in here, maybe.
    -});
    -</pre>
    -</div>
    -<p>These are the events you can bind to on the jsPlumb class:</p>
    -			<ul>
    -				<li class="bullet"><strong>jsPlumbConnection</strong> - notification a Connection was established.
    -					<p>
    -						The first argument to the callback is an object with the following properties:					
    -					</p>
    -					<div class="code">
    -<pre>
    -{
    -  connection 		: 	the new Connection.  you can register listeners on this etc.
    -  sourceId 		:	id of the source element in the Connection
    -  targetId		:	id of the target element in the Connection
    -  source		:	the source element in the Connection
    -  target		:	the target element in the Connection
    -  sourceEndpoint	:	the source Endpoint in the Connection
    -  targetEndpoint	:	the targetEndpoint in the Connection
    -}
    -</pre>
    -					</div>
    -The second argument is the original mouse event that caused the Connection, if any.
    -
    -					<p>
    -All of the source/target properties are actually available inside the Connection object, but - for one of those rubbish historical reasons - are provided separately because of a vagary of the <em>jsPlumbConnectionDetached</em> callback, which is discussed below.
    -</p>
    -				</li>
    -				<li class="bullet"><strong>jsPlumbConnectionDetached</strong> - notification a Connection was detached.  
    -					<p>
    -						As with <em>jsPlumbConnection</em>, the first argument to the callback is an object with the following properties:					
    -					</p>
    -					<div class="code">
    -<pre>
    -{
    -  connection		: 	the Connection that was detached.  
    -  sourceId		:	id of the source element in the Connection <em>before it was detached</em>
    -  targetId		:	id of the target element in the Connection before it was detached
    -  source		:	the source element in the Connection before it was detached
    -  target		:	the target element in the Connection before it was detached
    -  sourceEndpoint	:	the source Endpoint in the Connection before it was detached
    -  targetEndpoint	:	the targetEndpoint in the Connection before it was detached
    -}
    -</pre>
    -
    -					</div>	
    -The second argument is the original mouse event that caused the disconnection, if any. 					
    -								
    -					<p>
    -As mentioned above, the source/target properties are provided separately from the Connection, because this event is fired whenever
    -a Connection is either detached and abandoned, or detached from some Endpoint and attached to another.  In the latter case, the
    -Connection that is passed to this callback is in an indeterminate state (that is, the Endpoints are still in the state they are
    -in when dragging, and do not reflect static reality), and so the source/target properties give you the real story.
    -</p>
    -<p> Like I said above, this is really just for rubbish historical reasons.  I'm attempting to document my way out of it!</p>
    -				
    -				</li>
    -
    -				<li class="bullet"><strong>connectionDrag</strong> - notification an existing Connection is being dragged. The callback is passed the Connection.
    -				</li> 
    -
    -				<li class="bullet"><strong>connectionDragStop</strong> - notification a Connection drag has stopped. This is only fired for existing Connections. The callback is passed the Connection that was just dragged.
    -				</li> 
    -
    -				<li class="bullet"><strong>click</strong> - notification a Connection was clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification a Connection was double-clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>endpointClick</strong> - notification an Endpoint was clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>endpointDblClick</strong> - notification an Endpoint was double-clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>contextmenu</strong> - a right-click on some given component.  jsPlumb will report right clicks on both Connections and Endpoints; the callback is passed the component that received the right-click.				
    -				<li class="bullet"><strong>beforeDrop</strong>
    -					This event is fired when a new or existing connection has been dropped. Your callback is passed a JS object with these fields:
    -					<ul>
    -						<li><strong>sourceId</strong> - the id of the source element in the connection</li>
    -						<li><strong>targetId</strong> - the id of the target element in the connection</li>
    -						<li><strong>scope</strong> - the scope of the connection</li>
    -						<li><strong>connection</strong> - the actual Connection object.  You can access the 'endpoints' array in a Connection to get the Endpoints involved in the Connection, but be aware that when a Connection is being dragged, one of these Endpoints will always be a transient Endpoint that exists only for the life of the drag. To get the Endpoint on which the Connection is being dropped, use the 'dropEndpoint' member.</li>
    -						<li><strong>dropEndpoint</strong> - this is the actual Endpoint on which the Connection is being dropped.  This <strong>may be null</strong>, because it will not be set if the Connection is being dropped on an element on which makeTarget has been called. </li>
    -					</ul>
    -
    -					If you return false (or nothing) from this callback, the new Connection is aborted and removed from the UI.
    -				</li>					
    -				<!--li class="bullet"><strong>beforeDrag</strong>
    -					This event is fired when a new connection is about to be dragged.  Your callback function
    -					is passed the Connection that jsPlumb has just created.  If you return false (or nothing) 
    -					from this callback, the new Connection is aborted.
    -				</li-->
    -				<li class="bullet"><strong>beforeDetach</strong>
    -					This event is fired when a Connection is about to be detached, for whatever reason. Your callback function is passed the Connection that the user has just detached. Returning false from this interceptor aborts the Connection detach.
    -				</li>
    -							
    -			</ul>
    -			<h4><a id="connectionEvents">Connection Events</a></h4>		
    -			<p>To bind to an event on a Connection, you also use the <strong>bind</strong> method:</p>
    -			<div class="code">
    -<pre>
    -var connection = jsPlumb.connect({source:"d1", target:"d2"});
    -connection.bind("click", function(conn) {
    -	console.log("you clicked on ", conn);
    -});
    -</pre>
    -</div>
    -<p>These are the Connection events to which you can bind a listener:</p>
    -			<ul>
    -				<li class="bullet"><strong>click</strong> - notification a Connection was clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification a Connection was double-clicked.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>contextmenu</strong> - a right-click on the Connection. The callback is passed the Connection and the original event.
    -				<li class="bullet"><strong>mouseenter</strong> - notification the mouse entered the Connection's path.  The callback is passed the Connection and the original mouse event.</li>
    -				<li class="bullet"><strong>mouseexit</strong> - notification the mouse exited the Connection's.  The callback is passed the Connection and the original mouse event.</li>
    -			</ul>
    -			<h4><a id="endpointEvents">Endpoint Events</a></h4>			
    -			<p>To bind to an event on a Endpoint, you again use the <strong>bind</strong> method:</p>
    -			<div class="code">
    -<pre>
    -var endpoint = jsPlumb.addEndpoint("d1", { someOptions } );
    -endpoint.bind("click", function(endpoint) {
    -	console.log("you clicked on ", endpoint);
    -});
    -</pre>
    -</div>
    -<p>These are the Endpoint events to which you can bind a listener:</p>
    -			<ul>
    -				<li class="bullet"><strong>click</strong> - notification an Endpoint was clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>dblclick</strong> - notification an Endpoint was double-clicked.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>contextmenu</strong> - a right-click on the Endpoint. The callback is passed the Endpoint and the original event.
    -				<li class="bullet"><strong>mouseenter</strong> - notification the mouse entered the Endpoint.  The callback is passed the Endpoint and the original mouse event.</li>
    -				<li class="bullet"><strong>mouseexit</strong> - notification the mouse exited the Endpoint.  The callback is passed the Endpoint and the original mouse event.</li>
    -			</ul>
    -			<h4><a id="overlayEvents">Overlay Events</a></h4>
    -			Registering event listeners on an Overlay is a slightly different procedure - you provide them as arguments to the Overlay's 
    -			constructor.  This is because you never actually act on an 'Overlay' object.  Here's how to register a click listener on an Overlay:
    -			<div class="code">
    -<pre>jsPlumb.connect({
    -  source:"el1",
    -  target:"el2",
    -  overlays:[
    -    [ "Label", {
    -      events:{
    -        click:function(labelOverlay, originalEvent) { 
    -          console.log("click on label overlay for :" + labelOverlay.component); 
    -        }
    -      }
    -    }],
    -    [ "Diamond", {
    -      events:{
    -        dblclick:function(diamondOverlay, originalEvent) { 
    -          console.log("double click on diamond overlay for : " + diamondOverlay.component); 
    -        }
    -      }
    -    }] 	
    -  ]
    -});
    -</pre>			
    -			</div>		
    -		Note that events registered on Diamond, Arrow or PlainArrow overlays will not fire with the Canvas renderer - they work only with the SVG and VML renderers. 
    -		</div>
    -
    -				<div class="section">
    -			<a id="examples"><h3>jsPlumb.connect Examples</h3></a>
    -This section provides examples of how to use the programmatic API to establish Connections. 
    -			<p>
    -			The basic syntax of a call is that you execute 'connect', providing a source and a target, and optionally a paintStyle and preferences for where you
    -			want the plumbing to be anchored on each element, as well as the type of connector to use. 
    -			</p>
    -			<ul>
    -				<li>Connect window1 to window2 with the default settings:
    -					<div class="code">
    -						<pre>jsPlumb.connect({source:"window1", target:"window2"});</pre>
    -					</div>
    -This connects 'window1' to 'window2' using the jsPlumb defaults - a red Bezier curve Connector and black Dot Endpoints.
    -				</li>
    -				<li>Connect window1 to window2 with a 15 pixel wide yellow Connector, and a slightly brighter endpoint (remember the default Endpoint is a Dot):
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -					</div>
    -				</li>
    -				
    -				<li>Connect window1 to window2 with a 15 pixel wide yellow Connector, and a slightly brighter endpoint:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -					</div>
    -				</li>				
    -				<li>Connect window3 to 'window4' with a 10 pixel wide, semi opaque blue Connector, anchored to the left middle of window3, and the right middle of window4, with a Rectangle endpoint of width 10 and height 8:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window3',
    -	target:'window4',
    -	paintStyle:{ lineWidth:10, strokeStyle:'rgba(0, 0, 200, 0.5)' },
    -	anchors:["RightMiddle", "LeftMiddle"],
    -	endpoint:[ "Rectangle", { width:10, height:8 } ]
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window2 to window3 with a default Connector from the top center of window2 to the bottom center of window3, and rectangular endpoints:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window2',
    -	target:'window3',
    -	paintStyle:{lineWidth:8, strokeStyle:'rgb(189,11,11)'},
    -	anchors:["BottomCenter", "TopCenter"],
    -	endpoint:"Rectangle"
    -});</pre>
    -					</div>
    -				</li>
    -
    -
    -				<li>Connect window1 to window2 with a 15 px wide yellow Bezier. endpoints are a slightly lighter shade of yellow.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window1',
    -	target:'window2',
    -	anchors:["BottomCenter", [0.75,0,0,-1]],
    -	paintStyle:{lineWidth:15,strokeStyle:'rgb(243,230,18)'},
    -	endpointStyle:{fillStyle:'rgb(243,229,0)'}
    -});</pre>
    -</div></li>
    -<li>Connect window3 to window4 with a 10px wide blue-ish half transparent Bezier. put endpoints underneath the element they attach to.
    -the endpoints have a radial gradient. both ways of specifying gradient positioning are shown here.
    -<div class="code">
    -<pre>var w34Stroke = 'rgba(50, 50, 200, 1)';
    -var w34HlStroke = 'rgba(180, 180, 200, 1)';
    -jsPlumb.connect( {
    -	source:'window3',
    -	target:'window4',
    -		 paintStyle:{lineWidth:10, strokeStyle:w34Stroke},
    -		 anchors:["RightMiddle", "LeftMiddle"],
    -		 endpointStyle:{ gradient : {stops:[[0, w34Stroke], [1, w34HlStroke]], offset:17.5, innerRadius:15 }, radius:35},
    -		 //endpointStyle:{ gradient : {stops:[[0, w34Stroke], [1, w34HlStroke]], offset:'78%', innerRadius:'73%'}, radius:35 },
    -		 endpointsOnTop:false
    -	}
    -);</pre>
    -</div></li>
    -<li>Connect window2 to window3 with an 8px red Bezier and default rectangular endpoints.  see also how the first anchor is
    -specified here - this is how you create anchors in locations jsPlumb does not offer shortcuts for.
    -the endpoints in this example have linear gradients applied.
    -<div class="code">
    -<pre>var w23Stroke = 'rgb(189,11,11)';
    -jsPlumb.connect({
    -	source:'window2',
    -	target:'window3',
    -	paintStyle:{lineWidth:8,strokeStyle:w23Stroke},
    -	anchors:[[0.3,1,0,1], "TopCenter"],
    -	endpoint:"Rectangle",
    -	endpointStyles:[{ gradient : {stops:[[0, w23Stroke], [1, '#558822']] }},
    -       				{ gradient : {stops:[[0, w23Stroke], [1, '#882255']] }}]
    -});</pre>
    -</div></li>
    -
    -<li>Connect window5 to window6 from center to center, 5px wide line that is green and half transparent. the endpoints are
    -125px in radius and spill out from underneath their elements.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:'window5',
    -	target:'window6',
    -	anchors:["Center", "Center"],
    -	paintStyle:{lineWidth:5,strokeStyle:'rgba(0,255,0,0.5)'},
    -	endpointStyle:{radius:125}
    -});</pre>
    -</div></li>
    -
    -<li>Connect window4 to window5 from bottom right to top left, with a 7px straight line purple connector, and an image as the endpoint,
    -placed on top of the element it is connected to.
    -<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window4",
    -	target:"window5",
    -	anchors:[ "BottomRight","TopLeft" ],
    -	paintStyle:{ lineWidth:7, strokeStyle:"rgb(131,8,135)" },
    -	endpoint:[ "Image", { src:"http://morrisonpitt.com/jsPlumb/img/endpointTest1.png" } ],
    -	connector:"Straight"
    -});</pre>
    -</div></li>
    -
    -
    -				<li>Connect window5 to window6 between their center points with a semi-opaque connector, and 125px endpoints:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window5",
    -	target:"window6",
    -	anchors:[ "Center", "Center" ],
    -	paintStyle:{ lineWidth:5, strokeStyle:"rgba(0,255,0,0.5)" },
    -	endpointStyle:{ radius:125 }
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window7 to window8 with a 10 pixel wide blue Connector, anchored on the top left of window7 and the bottom right of window8:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window7",
    -	target:"window8",
    -	paintStyle:{ lineWidth:10, strokeStyle:"blue" },
    -	anchors:[ "TopLeft", "BottomRight" ]
    -});
    -</pre>
    -					</div>
    -				</li>
    -				<li>Connect the bottom right corner of window4 to the top left corner of window5, with rectangular endpoints of size 40x40 and a hover color of light blue:
    -					<div class="code">
    -<pre>jsPlumb.connect({
    -	source:"window4",
    -	target:"window5",
    -	anchors:["BottomRight","TopLeft"],
    -	paintStyle:{lineWidth:7,strokeStyle:'rgb(131,8,135)'},
    -	hoverPaintStyle:{ strokeStyle:"rgb(0, 0, 135)" },
    -	endpointStyle:{ width:40, height:40 },
    -	endpoint:"Rectangle",
    -	connector:"Straight"
    -});</pre>
    -					</div>
    -				</li>
    -				<li>Connect window1 to window2 with the default paint settings but provide some drag options (which are passed through to the underlying library's draggable call):
    -					<div class="code">
    -						<pre>jsPlumb.connect({source:'window1', target:'window2', dragOptions:{cursor:'crosshair'}});</pre>
    -					</div>
    -				</li>
    -</div>
    -		
    -		<div class="section">
    -			<a id="dragAndDropExamples"><h3>Draggable Connections Examples</h3></a>
    -			This is a list of examples of how to use jsPlumb to create Connections using drag and drop.
    -			<p>
    -			The basic procedure is:
    -			<ol>
    -				<li>1a. Create Endpoints and register them on elements in your UI</li>
    -				<li>1b. alternatively, create a source endpoint and then make some element a drop target </li>
    -				<li>2. Drag and Drop</li>
    -			</ol>
    -			That's all there is to it.  Of course there are plenty of options you can set when doing this...it will be easier
    -			to show you some examples:
    -			</p>
    -			<ul>
    -				<li>Define an Endpoint with default appearance, that is both a source and target of new Connections:
    -<div class="code">
    -<pre>var endpointOptions = { isSource:true, isTarget:true }; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Register that Endpoint on window3, specifying that it should be located in the top center of the element:
    -<div class="code">
    -<pre>var window3Endpoint = jsPlumb.addEndpoint('window3', { anchor:"TopCenter" }, endpointOptions );  
    -</pre>
    -</div>
    -Notice here the usage of the three-argument addEndpoint  - we can reuse 'endpointOptions' with a different Anchor for another element.  This is a useful
    -practice to get into.<br/><br/>
    -				</li>
    -				
    -				<li>Now register that Endpoint on window4, specifying that it should be located in the bottom center of the element:
    -<div class="code">
    -<pre>var window4Endpoint = jsPlumb.addEndpoint('window4', { anchor:"BottomCenter" }, endpointOptions );  
    -</pre>
    -</div>
    -Now we have two Endpoints, both of which support drag and drop of new Connections.  We can use these to make a programmatic Connection, too, though:<br/><br/>
    -				</li>				
    -				
    -				<li>Connect window3 to window4 with a 25px wide yellow Bezier that has a 'curviness' of 175:
    -<div class="code">
    -<pre>jsPlumb.connect({ 
    -	source:window3Endpoint,
    -	target:window4Endpoint,
    -	connector: [ "Bezier", 175 ],
    -	paintStyle:{ lineWidth:25, strokeStyle:'yellow' }
    -});  
    -</pre>
    -</div>
    -				</li>
    -				
    -				<li>Define an Endpoint that creates Connections that are 20px wide straight lines, that is both a source and target of new Connections,
    -				and that has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Define an Endpoint that will be anchored to "TopCenter".  It creates Connections that are 20px wide straight lines, that is both a source and target of new Connections,
    -				and that has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchor:"TopCenter", 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Define an Endpoint that will create a dynamic anchor which can be positioned at "TopCenter" or "BottomCenter".  It creates Connections that are 20px wide straight lines, it is both a source and target of new Connections,
    -				and it has a 'scope' of 'blueline'. Also, this Endpoint mandates that once it is full, Connections can
    -				no longer be dragged from it (even if 'reattach' is specified on a Connection):
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchor:[ "TopCenter", "BottomCenter" ], 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Exactly the same as before, but shows how you can use "anchors" instead of "anchor", if that makes you feel happier:
    -<div class="code">
    -<pre>var endpointOptions = {
    -	anchors:[ "TopCenter", "BottomCenter" ], 
    -	isSource:true, 
    -	isTarget:true,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dragAllowedWhenFull:false	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>								
    -				
    -<li>Define an Endpoint that is a 30px blue dot, creates Connections that are 20px wide straight lines, is both a source and target of new Connections,
    -				has a 'scope' of 'blueline', and has an event handler that pops up an alert (note: the event handler name means this example is jQuery - MooTools
    -				and YUI3 use different event handler names):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{fillStyle:'blue'},
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ drop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -			<li>Same example as before, but this is for MooTools, and the Endpoint can support up to 5 connections (the default is 1):
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{ fillStyle:'blue' },
    -	maxConnections:5,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ onDrop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Same example again, but maxConnections being set to -1 means that the Endpoint has no maximum limit of Connections:
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", {radius:30} ],
    -	style:{ fillStyle:'blue' },
    -	maxConnections:-1,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ onDrop:function(e, ui) { alert('drop!'); } }	 
    -}; 
    -</pre>
    -</div>				 				
    -				</li>
    -				
    -				<li>Same example again, but for YUI3.  Note the drop callback is "drop:hit":
    -<div class="code">
    -<pre>var endpointOptions = { 
    -	isSource:true, 
    -	isTarget:true,
    -	endpoint: [ "Dot", { radius:30 } ],
    -	style:{fillStyle:'blue'},
    -	maxConnections:-1,
    -	connector : "Straight",
    -	connectorStyle: { lineWidth:20, strokeStyle:'blue' },
    -	scope:"blueline",
    -	dropOptions:{ "drop:hit":function(e, ui) { alert('drop!'); } }	 	
    -}; 
    -</pre>
    -</div>				 				
    -				</li>				
    -				
    -	<li>Assign a UUID to the endpoint options created above, and add as Endpoints to "window1" and "window2":
    -<div class="code">
    -<pre>
    -jsPlumb.addEndpoint("window1", { uuid:"abcdefg" }, endpointOptions );
    -jsPlumb.addEndpoint("window2", { uuid:"hijklmn" }, endpointOptions );
    -</pre>
    -</div>	
    -	</li>
    -	
    -	<li>Connect the two Endpoints we just registered on "window1" and "window2":
    -<div class="code">
    -<pre>
    -jsPlumb.connect({uuids:["abcdefg", "hijklmn"]});
    -</pre>
    -</div>	
    -	</li>
    -	
    -	<li>Create a source Endpoint, register it on some element, then make some other element a Connection target:
    -<div class="code">
    -<pre>
    -var sourceEndpoint = { isSource:true, endpoint:[ "Dot", { radius:50 } ] };
    -var targetEndpoint = { endpoint:[ "Rectangle", { width:10, height:10 } ] };
    -jsPlumb.addEndpoint( "window1", sourceEndpoint );
    -jsPlumb.makeTarget( "window2", targetEndpoint );
    -</pre>
    -</div>	
    -Notice that the endpoint definition we use on the target window does not include the "isTarget:true" directive.  jsPlumb ignores that flag when
    -creating a connection using an element as the target; but if you then tried to drag another connection to the Endpoint that was created, you would
    -not be able to.  To permit that, you would set 'isTarget:true' on the targetEndpoint options defined above.
    -	
    -	</li>
    -				
    -			</ul>
    -		</div>
    -		
    -		<div class="section">
    -			<h3><a id="utilityFunctions">Utility Functions</a></h3>
    -			<ul>
    -				<li>Detach window5 from all connections
    -					<div class="code">
    -						<pre>jsPlumb.detachAll("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Hide all window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.hide("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Hide all window5's connections endpoints
    -					<div class="code">
    -						<pre>jsPlumb.hide("window5", true);</pre>
    -					</div>
    -				</li>
    -				<li>Show all window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.show("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Show all window5's connections and endpoints.  Note that in the case that you call jsPlumb.show with two arguments, jsPlumb
    -				will also not make a connection visible if it determines that the other endpoint in the connection is not visible.
    -					<div class="code">
    -						<pre>jsPlumb.show("window5", hide);</pre>
    -					</div>
    -				</li>				
    -				<li>Toggle the visibility of window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.toggle("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of all of window5's connections
    -					<div class="code">
    -						<pre>jsPlumb.repaint("window5");</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of all of window5, window6 and window11's connections
    -					<div class="code">
    -						<pre>jsPlumb.repaint( [ "window5", "window6", "window11" ] );</pre>
    -					</div>
    -				</li>
    -				<li>Force repaint of every connection
    -					<div class="code">
    -						<pre>jsPlumb.repaintEverything();</pre>
    -					</div>
    -				</li>
    -				<li>Detach every connection that the given instance of jsPlumb is managing
    -					<div class="code">
    -						<pre>jsPlumb.detachEveryConnection();</pre>
    -					</div>
    -				</li>
    -				<li>Detach all connections from "window1"
    -					<div class="code">
    -						<pre>jsPlumb.detachAllConnections("window1");</pre>
    -					</div>
    -				</li>
    -				<li>Remove the given Endpoint from element "window1", deleting its Connections. 
    -					<div class="code">
    -						<pre>jsPlumb.removeEndpoint("window1", someEndpoint);</pre>
    -					</div>
    -				</li>
    -				<li>Remove all Endpoints for the element 'window1', deleting their Connections.
    -					<div class="code">
    -						<pre>jsPlumb.removeAllEndpoints("window1");</pre>
    -					</div>
    -				</li>	
    -				<li>Removes every Endpoint managed by this instance of jsPlumb, deleting all Connections.
    -				This is the same as jsPlumb.reset(), effectively, but it does not clear out the event listeners list. 
    -					<div class="code">
    -						<pre>jsPlumb.removeEveryEndpoint();</pre>
    -					</div>
    -				</li>
    -				<li>Deletes the given Endpoint and all its Connections. 
    -					<div class="code">
    -						<pre>jsPlumb.deleteEndpoint(endpoint);</pre>
    -					</div>
    -				</li>
    -				<li>Removes every endpoint, detaches every connection, and clears the event listeners list.  Returns jsPlumb instance to its initial state.  
    -					<div class="code">
    -						<pre>jsPlumb.reset();</pre>
    -					</div>
    -				</li>	
    -				<li>Library-agnostic method to get a selector from the given selector specification.  This function is used mostly by the jsPlumb demos, to cut down on repeating code across the different libraries.  But perhaps you are writing something with jsPlumb that you also wisht to be library agnostic - this function can help you. The returned object, in each of jQuery, MooTools and YUI, has a ".each" iterator.
    -					<div class="code">
    -						<pre>var selector = jsPlumb.getSelector(".someSelector .spec");</pre>
    -					</div>					
    -				</li>
    -				<li>Set window1 to be not draggable, no matter what some jsPlumb command may request.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggable("window1", false);</pre>
    -					</div>
    -				</li>
    -				<li>Set window1 and window2 to be not draggable, no matter what some jsPlumb command may request.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggable(["window1","window2"], false);</pre>
    -					</div>
    -				</li>
    -				<li>Sets whether or not elements that are connected are draggable by default.
    -				The default for this is true.
    -					<div class="code">
    -						<pre>jsPlumb.setDraggableByDefault(false);</pre>
    -					</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (all libraries). Passes in an on drag callback
    -				<div class="code">
    -					<pre>jsPlumb.draggable("window1");</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 and window2 as draggable elements (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(["window1","window2"]);</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (all libraries)
    -				<div class="code">
    -					<pre>jsPlumb.draggable("window1");</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (jQuery)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (MooTools)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($$(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises all elements with class 'window' as draggable elements (YIU3)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(Y.all(".window"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (jQuery)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($("#window1"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (MooTools)
    -				<div class="code">
    -					<pre>jsPlumb.draggable($("window1"));</pre>
    -				</div>
    -				</li>
    -				<li>Initialises window1 as a draggable element (YUI3)
    -				<div class="code">
    -					<pre>jsPlumb.draggable(Y.one("window1"));</pre>
    -				</div>
    -				</li>
    -			</ul>
    -		</div>					
    -		
    -		<div class="section">
    -			<h3><a id="developingJsPlumb">Advanced Topics</a></h3>
    -			<h4><a id="which">Which files are which?</a></h4>
    -			In development, jsPlumb is broken up into nine scripts:
    -				<ul>
    -					<li>- jsPlumb-util-x.x.x.js
    -					<p>There are the jsPlumb utility functions (and some base classes).</p>					
    -					</li>
    -					<li>- jsPlumb-x.x.x.js
    -					<p>This is the main jsPlumb engine.</p>					
    -					</li>
    -					<li>- jsPlumb-defaults-x.x.x.js
    -					<p>This contains the default Anchor, Endpoint, Connector and Overlay implementations.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-canvas-x.x.x.js
    -					<p>This contains the HTML5 canvas render code.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-svg-x.x.x.js
    -					<p>This contains the SVG render code.</p>
    -					</li>
    -					<li>- jsPlumb-renderers-vml-x.x.x.js
    -					<p>This contains the VML render code.</p>
    -					</li>
    -					<li>- jsPlumb-connectors-statemachine-x.x.x.js
    -					<p>This State Machine connector.</p>
    -					</li>
    -					<li>- &lt;LIBRARY_PREFIX&gt;.jsPlumb-x.x.x.js
    -					<p>This contains library-specific helper methods.  jsPlumb ships with three of these - one each for jQuery, MooTools and YUI3. See below for information on how to create a new library implementation.</p>
    -					</li>
    -					<li>- jsBezier-0.3-min.js
    -					<p>These are the Bezier curve functions; they are maintained in a separate project called <a href="http://code.google.com/p/jsbezier/">jsBezier</a></p>
    -					</li>
    -				</ul>
    -			These seven files are packaged together to form the scripts that people use, for example:
    -			<ul>
    -				<li>jquery.jsPlumb-1.4.0-all.js
    -				<p>Contains jsPlumb-1.4.0.js, jsPlumb-defaults-1.4.0.js, jsPlumb-renderers-canvas-1.4.0.js, jsPlumb-renderers-svg-1.4.0.js, jsPlumb-renderers-vml-1.4.0.js, jsPlumb-connectors-statemachine-1.4.0.js, jquery.jsPlumb-1.4.0.js and jsBezier-0.3-min.js</p>
    -				</li>
    -				<li>jquery.jsPlumb-1.4.0-all-min.js
    -				<p>A minified version of the script above (minified using the YUI Compressor)</p>
    -				</li>
    -			</ul>
    -			<h4><a id="pluggableLibrarySupport">Pluggable Library Support</a></h4>
    -		Out of the box, jsPlumb can be run on top of jQuery, MooTools or YUI3.  This is achieved by
    -		delegating several core methods - tasks such as finding an element by id, finding an element's
    -		position or dimensions, initialising a draggable, etc - to the library in question.
    -		<p>To develop one of these, your test page should include the first two scripts discussed above, and
    -		then your own script containing your library specific functionality.  The existing implementations
    -		may be documented well enough for you to create your own, but contact me if you need assistance.  If you do this, it would be
    -		great to share it with everyone...</p>   
    -		<p>There is a group of people working on an ExtJS adapter currently.  There is no schedule for when this will be completed though.</p>
    -
    -			<!-- h4><a id="customConnectors">Custom Connectors</a></h4>			
    -					
    -			You can provide your own connectors if you need to. A Connector consists of two functions, which work as a pair.  First a call is made to the <em>compute</em> function:
    -
    -			<div class="code">
    -<pre>
    -this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) {
    -	...
    -	return dimensions;
    -}
    -</pre>
    -			</div>
    -			which is expected to return a list that the <em>paint</em> function can make sense of.  The first four entries in the
    -			list <strong>must be</strong> the [x,y,width,height] values for the canvas that the connector will be drawn on; jsPlumb will
    -			use this information to size the canvas prior to calling the Connector's <em>paint</em> function.  Therefore it
    -			is the Connector's responsibility to ensure that the returned dimensions describe a large enough space for
    -			the line that will be drawn on it.</p><p>The next four elements <strong>must be</strong> the coordinates of the two endpoints of the line
    -			you are going to draw.</p><p>The remainder of the items in the returned list are arbitrary, and will
    -			vary between Connector implementations; this list is passed in to a Connector's <em>paint</em> function, so each
    -			implementation will put into the list whatever it needs to paint itself.  For instance, the straight line
    -			connector only needs the [x,y] location of each end of the line it will paint, and that is one of the required entries, so
    -			it does not have to do anything extra,  whereas the Bezier connector adds the location of the two control points.  Other types of
    -			Connectors will do whatever is appropriate for their particular situation.
    -			</p>
    -			This is the method signature for the <em>paint</em> function:
    -			<div class="code">
    -				<pre>this.paint = function(dimensions, ctx) { .. }</pre>
    -			</div>
    -
    -			here, the 'dimensions' argument to the 'paint' function is the return value of the 'compute' function.  The 'ctx' argument is the Canvas context; you
    -			will do all your drawing on this.
    -			</p>
    -			To change the connector from the default, specify it in your connect call:
    -			<div class="code">
    -				<pre>jsPlumb.connect({source:'someWindow', target:'otherWindow', connector:new jsPlumb.Connectors.Straight()});</pre>
    -			</div>
    -			note that you can use shorthand for connectors if you don't need to specify parameters to it:
    -			<div class="code">
    -				<pre>jsPlumb.connect({source:'someWindow', target:'otherWindow', connector:"Straight"});</pre>
    -			</div>
    -			This works in the same way as specifying Anchors as string does.
    -<p>
    -The section below this discusses Overlays, which allow you to decorate Connectors with
    -			things such as labels or arrows or whatever else you like. Overlays can only work with Connectors that implement a few helper functions.
    -			<h4>The concept of <strong>location</strong></h4>
    -			Before discussing the helper functions you first must be across the concept of 'location' as used by this mechanism.  The <em>location</em>
    -			on a connector can be a decimal value between 0 and 1 inclusive, which marks a proportionate amount of travel <em>along the path
    -			inscribed by the Connector</em>.  For a straight line connector this is a simple piece of maths, but for Bezier connectors it's a little
    -			bit more involved.
    -			<h4>Required Helper Methods</h4>
    -			<ul>
    -				<li><strong>pointOnPath(location)</strong> - returns an [x,y] point corresponding to the given location</li>
    -				<li><strong>pointAlongPathFrom(location, distance)</strong> - returns an [x,y] point corresponding to travelling 'distance' pixels along the connector from 'location'.</li>
    -				<li><strong>gradientAtPoint(location)</strong> - returns the connector's gradient at the given location.  For linear connectors such as Straight this is constant, but for Bezier connectors the gradient changes continually.</li>
    -				<li><strong>perpendicularToPathAt(location, distance, length)</strong> - returns a line that is perpendicular to (and centered on) the connector at 'distance' from the given location, with length 'length'.</li>
    -			</ul>
    -</p>
    -<h4><a id="customEndpoints">Custom Endpoints</a></h4>
    -<p>To create your own Endpoint implementation, you need to implement a single method:
    -				<div class="code"><pre>paint : function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) { ... }</pre></div>
    -				The arguments to this method are as follows:
    -				<ul>
    -					<li>anchorPoint - [x,y] location of the anchor point on screen</li>
    -					<li>orientation - [x,y] hints for the general direction the anchor points to</li>
    -					<li>canvas - the canvas to draw into</li>
    -					<li>endpointStyle - Javascript object containing style directives as discussed above.  The contents of this are arbitrary, so if you write a new Endpoint that needs some extra settings, you can add them no hassle.</li>
    -					<li>connectorPaintStyle - the style being used to paint the associated Connector.</li>
    -				</ul>
    -			</p>
    -			<p>
    -				It is your responsibility to size and locate the canvas to suit your needs. jsPlumb provides the following
    -				helper method to assist you:
    -				<div class="code"><pre>jsPlumb.sizeCanvas(canvas, x, y, width, height);</pre></div>
    -				Allows you to locate the canvas on screen and to size it.
    -			</p>
    -<h4><a id="customOverlays">Custom Overlays</a></h4>
    -			Overlays can only work with Connectors that implement the methods <strong>pointOnPath</strong>, <strong>pointAlongPathFrom</strong>,
    -			<strong>gradientAtPoint</strong>, and <strong>perpendicularToPathAt</strong>.  These are discussed in more detail in the Custom Connectors section above.  Both of the Connectors
    -			that come with jsPlumb - Straight and Bezier -implement these methods; if you write a custom connector, or have written a custom connector, you will need to supply
    -			them.   
    -			<h4>Overlay Interface</h4>
    -			An Overlay is required to implement two methods in order to be usable by jsPlumb:
    -			<ul>
    -				<li><strong>computeMaxSize(connector, context)</strong> - returns an integer value indicating the larger of this overlay's width and height.  It is used by jsPlumb to ensure that the canvas is large enough to accomodate the overlay.  The examples below should help to clarify this.</li>
    -				<li><strong>draw(connector, ctx)</strong> - draws the overlay.  What happens in this method is up to the given implementation.</li>
    -			</ul>
    -			<h4>computeMaxSize methods</h4>
    -			This is the Arrow overlay's computeMaxSize method:
    -			<div class="code">
    -<pre>
    -this.computeMaxSize = function() { return width; }
    -</pre>			
    -			</div>
    -Here, <em>width</em> is a private member of Arrow that indicates the width of the arrow's tail.  So the Arrow overlay reports that figure as the width it needs.					
    -Contrast this with the Label Overlay's computeMaxSize method:
    -		<div class="code">
    -<pre>
    -this.computeMaxSize = function(connector, ctx) {
    -  	if (labelText) {
    -  		ctx.save();
    -        if (self.labelStyle.font) ctx.font = self.labelStyle.font;
    -         var t = ctx.measureText(labelText).width;			            
    -		// a fake text height measurement: use the width of upper case M
    -		var h = ctx.measureText("M").width;					
    -		labelPadding = self.labelStyle.padding || 0.25;
    -		labelWidth = t + (2 * t * labelPadding);
    -		labelHeight = h + (2 * h * labelPadding);
    -		ctx.restore();
    -		return Math.max(labelWidth, labelHeight);
    -  	}
    - 	return 0;
    - };
    -</pre>			
    -			</div>
    -The Label overlay has to use the context to determine how big it will be on screen.
    -
    -<h4>draw methods</h4>
    -To give you a taste for how you can interact with a connector, consider the first few lines of the Arrow overlay's draw method:
    -<div class="code">
    -<pre>
    -this.draw = function(connector, ctx) {
    -	// this is the arrow head position
    -	var hxy = connector.pointAlongPathFrom(self.loc, length / 2);		
    -	// this is the center of the tail
    -	var txy = connector.pointAlongPathFrom(self.loc, -length / 2), tx = txy[0], ty = txy[1];
    -	// this is the tail vector
    -	var tail = connector.perpendicularToPathAt(self.loc, -length / 2, width);
    -	
    -	...
    -</pre>
    -</div>
    -	<p>The first two calls to the connector use the <strong>pointAlongPathFrom</strong> method, which passes <em>self.loc</em> as the location of the
    -	point on the path, and <em>length / 2</em> as the number of pixels along the path to traverse.</p>
    -	<p><em>self.loc</em> is Arrow's internal member describing the location of the overlay, and it is a decimal (between 0 and 1 inclusive) that points to
    -	a location at some distance along the path inscribed by the connector.  So these first two calls get us [x,y] locations of points on 
    -	the connector that mark the head and tail points for the arrow.  
    -	</p>		
    -	<p>
    -	The <strong>connector.perpendicularToPathAt(self.loc, -length / 2, width)</strong> call returns a line description of a line that is perpendicular to, and centered on, the connector
    -	at "-length / 2" pixels from the given point, with the given width (width is an internal member of Arrow).  At this point, the Arrow object has the three main points it
    -	needs in order to draw itself.
    -	</p>
    -		</div-->
    -</div>
    -
    -</body>
    -
    -</html>
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.9/index.html b/demo/doc/archive/1.3.9/index.html
    deleted file mode 100644
    index 38ecea2e8..000000000
    --- a/demo/doc/archive/1.3.9/index.html
    +++ /dev/null
    @@ -1,86 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.4.0 documentation</title>
    -		<link rel="stylesheet" target="contentFrame" href="/mp.css"></link>
    -		<link rel="stylesheet" target="contentFrame" href="jsPlumbDoc.css"></link>
    -	</head>
    -	<body>
    -		<div class="index section">
    -			<h3>jsPlumb 1.4.0</h3>
    -			<ul>
    -				<li><a target="contentFrame" href="content.html#summary">Summary</a></li>
    -				<li><a target="contentFrame" href="content.html#changes">Changes since version 1.3.8</a></li>
    -				
    -				<li><h4>Setup</h4></li>
    -				<li><a target="contentFrame" href="content.html#imports">Required Imports</a></li>
    -				<li><a target="contentFrame" href="content.html#renderMode">Render Mode</a></li>
    -				<li><a target="contentFrame" href="content.html#initializing">Initializing jsPlumb</a></li>				
    -				<li><a target="contentFrame" href="content.html#multipleInstances">Multiple jsPlumb Instances</a></li>
    -				<li><a target="contentFrame" href="content.html#zIndex">Z-index Considerations</a></li>
    -				<li><a target="contentFrame" href="content.html#containerDefault">Where does jsPlumb add elements?</a></li>
    -				<li><a target="contentFrame" href="content.html#elementDragging">Element dragging</a></li>
    -				
    -				<li><h4>Introduction</h4></li>
    -				<li><a target="contentFrame" href="content.html#jsPlumbBasics">Basic Concepts</a></li>
    -				<li><a target="contentFrame" href="content.html#anchors">Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#staticAnchors">Static Anchors</a></li>				
    -				<li><a target="contentFrame" href="content.html#dynamicAnchors">Dynamic Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#continuousAnchors">Continuous Anchors</a></li>
    -				<li><a target="contentFrame" href="content.html#definitions">Connector, Endpoint &amp; Overlay Definitions</a></li>
    -				<li><a target="contentFrame" href="content.html#connectors">Connectors</a></li>
    -				<li><a target="contentFrame" href="content.html#endpoints">Endpoints</a></li>
    -				<li><a target="contentFrame" href="content.html#overlays">Overlays</a></li>
    -				<li><a target="contentFrame" href="content.html#defaults">Defaults</a></li>								
    -				
    -				<li><h4>Connections</h4></li>
    -				<li><a target="contentFrame" href="content.html#simpleConnections">Programmatic Connections</a></li>
    -				<li><a target="contentFrame" href="content.html#draggableConnections">Connections using Drag and Drop</a></li>
    -				<li><a target="contentFrame" href="content.html#sourcesAndTargets">Elements as Sources &amp; Targets</a></li>
    -				<li class="sub"><a target="contentFrame" href="content.html#makeTarget">makeTarget</a></li>
    -				<li class="sub"><a target="contentFrame" href="content.html#makeSource">makeSource</a></li>
    -				<li><a target="contentFrame" href="content.html#dragOptions">Drag Options</a></li>				
    -				<li><a target="contentFrame" href="content.html#dropOptions">Drop Options</a></li>
    -				<li><a target="contentFrame" href="content.html#dragAndDropScope">Drag and Drop Scope</a></li>
    -				
    -				<li><h4>Parameters</h4></li>		
    -				<li><a target="contentFrame" href="content.html#connectParameters">jsPlumb.connect</a></li>
    -				<li><a target="contentFrame" href="content.html#addEndpointParameters">jsPlumb.addEndpoint</a></li>
    -
    -				<li><h4>Interceptors</h4></li>				
    -				<li><a target="contentFrame" href="content.html#beforeDrop">Before Drop</a></li>
    -				<li><a target="contentFrame" href="content.html#beforeDetach">Before Detach</a></li>
    -				
    -				<li><h4>Appearance</h4></li>
    -				<li><a target="contentFrame" href="content.html#paintstyles">Paint Styles</a></li>
    -				<li><a target="contentFrame" href="content.html#hoverpaintstyles">Hover Paint Styles</a></li>
    -				<li><a target="contentFrame" href="content.html#gradients">Gradients</a></li>				
    -				
    -				<li><h4>Miscellaneous</h4></li>
    -				<li><a target="contentFrame" href="content.html#cssclasses">CSS Class Reference</a></li>
    -				<li><a target="contentFrame" href="content.html#animation">Animation</a></li>
    -				<li><a target="contentFrame" href="content.html#connectionInfo">Retrieving Connection Information</a></li>
    -				<li><a target="contentFrame" href="content.html#selectConnections">Selecting Connections</a></li>
    -				
    -				<li><h4>Events</h4></li>
    -				<li><a target="contentFrame" href="content.html#events">jsPlumb Events</a></li>
    -				<li><a target="contentFrame" href="content.html#connectionEvents">Connection Events</a></li>
    -				<li><a target="contentFrame" href="content.html#endpointEvents">Endpoint Events</a></li>
    -				<li><a target="contentFrame" href="content.html#overlayEvents">Overlay Events</a></li>
    -				
    -				<li><h4>Examples</h4></li>				
    -				<li><a target="contentFrame" href="content.html#examples">Programmatic API Examples</a></li>
    -				<li><a target="contentFrame" href="content.html#dragAndDropExamples">Draggable Connections Examples</a></li>
    -				<li><a target="contentFrame" href="content.html#utilityFunctions">Utility Functions</a></li>				
    -				
    -				<li><h4>Develop</h4></li>
    -				<li><a target="contentFrame" href="content.html#which">Which files are which?</a></li>
    -				<li><a target="contentFrame" href="content.html#pluggableLibrarySupport">Pluggable Library Support</a></li>
    -				<!-- li><a target="contentFrame" href="content.html#customConnectors">Custom Connectors</a></li>
    -				<li><a target="contentFrame" href="content.html#customEndpoints">Custom Endpoints</a></li>
    -				<li><a target="contentFrame" href="content.html#customOverlays">Custom Overlays</a></li-->
    -			</ul>
    -			<p><strong>This document refers to release 1.4.0 of jsPlumb.</strong></p>
    -			<strong>25 May 2012</strong>
    -		</div>		
    -	</body>
    -</html>
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.9/jsPlumbDoc.css b/demo/doc/archive/1.3.9/jsPlumbDoc.css
    deleted file mode 100644
    index bbe50c8ee..000000000
    --- a/demo/doc/archive/1.3.9/jsPlumbDoc.css
    +++ /dev/null
    @@ -1,126 +0,0 @@
    -body { 
    -	background-color:white; font-size:90%;
    -	background-image:url(../img/dynamicAnchorBg.jpg); 
    -}
    -/* docs */
    -.code { background-color:#e4e5ef; 
    -border:1px solid black; margin:1em; padding:0.8em; 
    -overflow:auto;
    -font-family:courier; 
    -font-size:8pt;
    -}
    -.section { border-bottom: 1px dotted black; padding:1em;font-family:helvetica;background-color:white;color:#333;}
    -.menu { 
    -	position:fixed; 
    -	right:10em; top:1em;
    -	background-color:white;
    -	padding:0.3em;
    -	opacity:0.8;
    -	z-index:5000;
    -	filter:alpha(opacity=80);
    -		box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -a {text-decoration:none;color:#05f;font-weight:bold;font-family:helvetica;}
    -a:hover {text-decoration:underline;}
    -.window, .label { 
    -	cursor:pointer;
    -	box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -#explanation { 
    -	background-color:white;
    -	color:black;
    -	border:0.25em solid #ddd; 
    -	position:absolute;
    -	right:3.5em;
    -	top:5em;
    -	width:22em;
    -	font-size:100%;
    -	-moz-border-radius:1em;
    -	border-radius:1em;
    -	padding:1em;
    -	z-index:1000;
    -	opacity:0.9;
    -	filter:alpha(opacity=90); 
    -	font-family:helvetica;
    -		box-shadow: 2px 2px 19px #aaa;
    -   -o-box-shadow: 2px 2px 19px #aaa;
    -   -webkit-box-shadow: 2px 2px 19px #aaa;
    -   -moz-box-shadow: 2px 2px 19px #aaa;
    -}
    -
    -.selected {
    -	color:rgb(189,11,11);
    -}
    -
    -
    -/*  these styles are what jsPlumb attaches to the various elements it creates.
    -._jsPlumb_connector {
    -border:1px solid blue;
    -}
    -._jsPlumb_endpoint {
    -border:1px solid green;
    -}
    -._jsPlumb_overlay {
    -border:1px solid yellow;
    -}
    -*/
    -
    -
    -.section li {
    -list-style-type:none;
    -}
    -
    -li.bullet {
    -	list-style-type:circle;
    -	}
    -
    -
    -.section {
    -padding-left:2em;
    -font-size:10pt;
    -}
    -.index.section {
    -padding-left:0;
    -}
    -
    -.index {
    -width:20em;
    -font-size:9pt;
    -float:left;
    -background-color:#e4eeee;
    -overflow:auto;
    -padding-left:0.5em;
    -}
    -
    -.index li {
    -list-style-type:none;
    -}
    -
    -.index ul, .index h3 {
    -padding-left:1.5em;
    -
    -}
    -
    -#demo {
    -width:80%;
    -height:50em;
    -margin-left:3em;
    -margin-top:3em;
    -position:relative;
    -}
    -
    -.sub {
    -margin-left:1em;
    -}
    -
    -.table td {
    -	text-align:center;
    -}
    \ No newline at end of file
    diff --git a/demo/doc/archive/1.3.9/usage.html b/demo/doc/archive/1.3.9/usage.html
    deleted file mode 100644
    index b5b154e16..000000000
    --- a/demo/doc/archive/1.3.9/usage.html
    +++ /dev/null
    @@ -1,10 +0,0 @@
    -<html>
    -	<head>
    -		<title>jsPlumb 1.4.0 - documentation</title>		
    -	</head>
    -	
    -	<frameset cols="250,*">
    -  		<frame src="index.html" />
    -  		<frame src="content.html" name="contentFrame" />
    -	</frameset>	
    -</html>
    diff --git a/demo/js/demo-list.js b/demo/js/demo-list.js
    index fbee27836..16636c219 100644
    --- a/demo/js/demo-list.js
    +++ b/demo/js/demo-list.js
    @@ -60,8 +60,8 @@
     			if (demoInfo) {
     				var prevString = '|&nbsp;<h5>prev:</h5><a href="' + demoInfo.prev.id + '.html" title="View previous demo">' + demoInfo.prev.name + '</a>',
     					nextString = '&nbsp;<h5>next:</h5><a href="' + demoInfo.next.id + '.html" title="View next demo">' + demoInfo.next.name + '</a>',
    -					menuString = '<a href="../doc/usage.html" class="mplink">Documentation</a>' +
    -							 '&nbsp;|&nbsp;<a href="../apidocs">API docs</a>' +
    +					menuString = '<a href="../../doc/usage.html" class="mplink">Documentation</a>' +
    +							 '&nbsp;|&nbsp;<a href="../../apidocs">API docs</a>' +
     							 '&nbsp;|&nbsp;<a href="../../tests/qunit-all.html">qUnit tests</a>' +
     							 '&nbsp;|&nbsp;<a href="mailto:simon.porritt@gmail.com" class="mplink">Contact</a>' +
                                  '&nbsp;|&nbsp;<a href="http://github.com/sporritt/jsplumb/" class="mplink">GitHub</a>' +
    diff --git a/demo/doc/content.html b/doc/content.html
    similarity index 100%
    rename from demo/doc/content.html
    rename to doc/content.html
    diff --git a/demo/doc/header.html b/doc/header.html
    similarity index 100%
    rename from demo/doc/header.html
    rename to doc/header.html
    diff --git a/demo/doc/index.html b/doc/index.html
    similarity index 100%
    rename from demo/doc/index.html
    rename to doc/index.html
    diff --git a/demo/doc/jsPlumbDoc.css b/doc/jsPlumbDoc.css
    similarity index 100%
    rename from demo/doc/jsPlumbDoc.css
    rename to doc/jsPlumbDoc.css
    diff --git a/demo/doc/usage.html b/doc/usage.html
    similarity index 100%
    rename from demo/doc/usage.html
    rename to doc/usage.html
    diff --git a/index.html b/index.html
    index 3e2b63cff..009ae46fa 100644
    --- a/index.html
    +++ b/index.html
    @@ -39,7 +39,8 @@ <h3>Tests</h3>
             </ul>
             <h3>Documentation</h3>
             <ul>
    -			<li><a href="demo/doc/usage.html">jsPlumb Documentation</a></li>
    +			<li><a href="doc/usage.html">jsPlumb Documentation</a></li>
    +            <li><a href="apidocs">API Documentation</a></li>
             </ul>        
         </body>
         
    diff --git a/lib/jquery-1.6.3-min.js b/lib/jquery-1.6.3-min.js
    new file mode 100644
    index 000000000..20d7f61e3
    --- /dev/null
    +++ b/lib/jquery-1.6.3-min.js
    @@ -0,0 +1,4 @@
    +/*! jQuery v1.6.3 http://jquery.com/ | http://jquery.org/license */
    +(function(a,b){function cu(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cr(a){if(!cg[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ch||(ch=c.createElement("iframe"),ch.frameBorder=ch.width=ch.height=0),b.appendChild(ch);if(!ci||!ch.createElement)ci=(ch.contentWindow||ch.contentDocument).document,ci.write((c.compatMode==="CSS1Compat"?"<!doctype html>":"")+"<html><body>"),ci.close();d=ci.createElement(a),ci.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ch)}cg[a]=e}return cg[a]}function cq(a,b){var c={};f.each(cm.concat.apply([],cm.slice(0,b)),function(){c[this]=a});return c}function cp(){cn=b}function co(){setTimeout(cp,0);return cn=f.now()}function cf(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ce(){try{return new a.XMLHttpRequest}catch(b){}}function b$(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function bZ(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function bY(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bA.test(a)?d(a,e):bY(a+"["+(typeof e=="object"||f.isArray(e)?b:"")+"]",e,c,d)});else if(!c&&b!=null&&typeof b=="object")for(var e in b)bY(a+"["+e+"]",b[e],c,d);else d(a,b)}function bX(a,c){var d,e,g=f.ajaxSettings.flatOptions||{};for(d in c)c[d]!==b&&((g[d]?a:e||(e={}))[d]=c[d]);e&&f.extend(!0,a,e)}function bW(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bP,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=bW(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=bW(a,c,d,e,"*",g));return l}function bV(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bL),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function by(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=b==="width"?bt:bu;if(d>0){c!=="border"&&f.each(e,function(){c||(d-=parseFloat(f.css(a,"padding"+this))||0),c==="margin"?d+=parseFloat(f.css(a,c+this))||0:d-=parseFloat(f.css(a,"border"+this+"Width"))||0});return d+"px"}d=bv(a,b,b);if(d<0||d==null)d=a.style[b]||0;d=parseFloat(d)||0,c&&f.each(e,function(){d+=parseFloat(f.css(a,"padding"+this))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+this+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+this))||0)});return d+"px"}function bl(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bd,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bk(a){f.nodeName(a,"input")?bj(a):"getElementsByTagName"in a&&f.grep(a.getElementsByTagName("input"),bj)}function bj(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bi(a){return"getElementsByTagName"in a?a.getElementsByTagName("*"):"querySelectorAll"in a?a.querySelectorAll("*"):[]}function bh(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bg(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c=f.expando,d=f.data(a),e=f.data(b,d);if(d=d[c]){var g=d.events;e=e[c]=f.extend({},d);if(g){delete e.handle,e.events={};for(var h in g)for(var i=0,j=g[h].length;i<j;i++)f.event.add(b,h+(g[h][i].namespace?".":"")+g[h][i].namespace,g[h][i],g[h][i].data)}}}}function bf(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function V(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(Q.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=0===c})}function U(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function M(a,b){return(a&&a!=="*"?a+".":"")+b.replace(y,"`").replace(z,"&")}function L(a){var b,c,d,e,g,h,i,j,k,l,m,n,o,p=[],q=[],r=f._data(this,"events");if(!(a.liveFired===this||!r||!r.live||a.target.disabled||a.button&&a.type==="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var s=r.live.slice(0);for(i=0;i<s.length;i++)g=s[i],g.origType.replace(w,"")===a.type?q.push(g.selector):s.splice(i--,1);e=f(a.target).closest(q,a.currentTarget);for(j=0,k=e.length;j<k;j++){m=e[j];for(i=0;i<s.length;i++){g=s[i];if(m.selector===g.selector&&(!n||n.test(g.namespace))&&!m.elem.disabled){h=m.elem,d=null;if(g.preType==="mouseenter"||g.preType==="mouseleave")a.type=g.preType,d=f(a.relatedTarget).closest(g.selector)[0],d&&f.contains(h,d)&&(d=h);(!d||d!==h)&&p.push({elem:h,handleObj:g,level:m.level})}}}for(j=0,k=p.length;j<k;j++){e=p[j];if(c&&e.level>c)break;a.currentTarget=e.elem,a.data=e.handleObj.data,a.handleObj=e.handleObj,o=e.handleObj.origHandler.apply(e.elem,arguments);if(o===!1||a.isPropagationStopped()){c=e.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function J(a,c,d){var e=f.extend({},d[0]);e.type=a,e.originalEvent={},e.liveFired=b,f.event.handle.call(c,e),e.isDefaultPrevented()&&d[0].preventDefault()}function D(){return!0}function C(){return!1}function m(a,c,d){var e=c+"defer",g=c+"queue",h=c+"mark",i=f.data(a,e,b,!0);i&&(d==="queue"||!f.data(a,g,b,!0))&&(d==="mark"||!f.data(a,h,b,!0))&&setTimeout(function(){!f.data(a,g,b,!0)&&!f.data(a,h,b,!0)&&(f.removeData(a,e,!0),i.resolve())},0)}function l(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function k(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(j,"$1-$2").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNaN(d)?i.test(d)?f.parseJSON(d):d:parseFloat(d)}catch(g){}f.data(a,c,d)}else d=b}return d}var c=a.document,d=a.navigator,e=a.location,f=function(){function K(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(K,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/\d/,n=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,o=/^[\],:{}\s]*$/,p=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,q=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,r=/(?:^|:|,)(?:\s*\[)+/g,s=/(webkit)[ \/]([\w.]+)/,t=/(opera)(?:.*version)?[ \/]([\w.]+)/,u=/(msie) ([\w.]+)/,v=/(mozilla)(?:.*? rv:([\w.]+))?/,w=/-([a-z]|[0-9])/ig,x=/^-ms-/,y=function(a,b){return(b+"").toUpperCase()},z=d.userAgent,A,B,C,D=Object.prototype.toString,E=Object.prototype.hasOwnProperty,F=Array.prototype.push,G=Array.prototype.slice,H=String.prototype.trim,I=Array.prototype.indexOf,J={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=n.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.6.3",length:0,size:function(){return this.length},toArray:function(){return G.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?F.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),B.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(G.apply(this,arguments),"slice",G.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:F,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j<k;j++)if((a=arguments[j])!=null)for(c in a){d=i[c],f=a[c];if(i===f)continue;l&&f&&(e.isPlainObject(f)||(g=e.isArray(f)))?(g?(g=!1,h=d&&e.isArray(d)?d:[]):h=d&&e.isPlainObject(d)?d:{},i[c]=e.extend(l,h,f)):f!==b&&(i[c]=f)}return i},e.extend({noConflict:function(b){a.$===e&&(a.$=g),b&&a.jQuery===e&&(a.jQuery=f);return e},isReady:!1,readyWait:1,holdReady:function(a){a?e.readyWait++:e.ready(!0)},ready:function(a){if(a===!0&&!--e.readyWait||a!==!0&&!e.isReady){if(!c.body)return setTimeout(e.ready,1);e.isReady=!0;if(a!==!0&&--e.readyWait>0)return;B.resolveWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!B){B=e._Deferred();if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",C,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",C),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&K()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNaN:function(a){return a==null||!m.test(a)||isNaN(a)},type:function(a){return a==null?String(a):J[D.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!E.call(a,"constructor")&&!E.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||E.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(o.test(b.replace(p,"@").replace(q,"]").replace(r,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(x,"ms-").replace(w,y)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a},trim:H?function(a){return a==null?"":H.call(a)}:function(a){return a==null?"":(a+"").replace(k,"").replace(l,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var d=e.type(a);a.length==null||d==="string"||d==="function"||d==="regexp"||e.isWindow(a)?F.call(c,a):e.merge(c,a)}return c},inArray:function(a,b){if(!b)return-1;if(I)return I.call(b,a);for(var c=0,d=b.length;c<d;c++)if(b[c]===a)return c;return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length=="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)},guid:1,proxy:function(a,c){if(typeof c=="string"){var d=a[c];c=a,a=d}if(!e.isFunction(a))return b;var f=G.call(arguments,2),g=function(){return a.apply(c,f.concat(G.call(arguments)))};g.guid=a.guid=a.guid||g.guid||e.guid++;return g},access:function(a,c,d,f,g,h){var i=a.length;if(typeof c=="object"){for(var j in c)e.access(a,j,c[j],f,g,d);return a}if(d!==b){f=!h&&f&&e.isFunction(d);for(var k=0;k<i;k++)g(a[k],c,f?d.call(a[k],k,g(a[k],c)):d,h);return a}return i?g(a[0],c):b},now:function(){return(new Date).getTime()},uaMatch:function(a){a=a.toLowerCase();var b=s.exec(a)||t.exec(a)||u.exec(a)||a.indexOf("compatible")<0&&v.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}e.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function(d,f){f&&f instanceof e&&!(f instanceof a)&&(f=a(f));return e.fn.init.call(this,d,f,b)},a.fn.init.prototype=a.fn;var b=a(c);return a},browser:{}}),e.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){J["[object "+b+"]"]=b.toLowerCase()}),A=e.uaMatch(z),A.browser&&(e.browser[A.browser]=!0,e.browser.version=A.version),e.browser.webkit&&(e.browser.safari=!0),j.test(" ")&&(k=/^[\s\xA0]+/,l=/[\s\xA0]+$/),h=e(c),c.addEventListener?C=function(){c.removeEventListener("DOMContentLoaded",C,!1),e.ready()}:c.attachEvent&&(C=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",C),e.ready())});return e}(),g="done fail isResolved isRejected promise then always pipe".split(" "),h=[].slice;f.extend({_Deferred:function(){var a=[],b,c,d,e={done:function(){if(!d){var c=arguments,g,h,i,j,k;b&&(k=b,b=0);for(g=0,h=c.length;g<h;g++)i=c[g],j=f.type(i),j==="array"?e.done.apply(e,i):j==="function"&&a.push(i);k&&e.resolveWith(k[0],k[1])}return this},resolveWith:function(e,f){if(!d&&!b&&!c){f=f||[],c=1;try{while(a[0])a.shift().apply(e,f)}finally{b=[e,f],c=0}}return this},resolve:function(){e.resolveWith(this,arguments);return this},isResolved:function(){return!!c||!!b},cancel:function(){d=1,a=[];return this}};return e},Deferred:function(a){var b=f._Deferred(),c=f._Deferred(),d;f.extend(b,{then:function(a,c){b.done(a).fail(c);return this},always:function(){return b.done.apply(b,arguments).fail.apply(this,arguments)},fail:c.done,rejectWith:c.resolveWith,reject:c.resolve,isRejected:c.isResolved,pipe:function(a,c){return f.Deferred(function(d){f.each({done:[a,"resolve"],fail:[c,"reject"]},function(a,c){var e=c[0],g=c[1],h;f.isFunction(e)?b[a](function(){h=e.apply(this,arguments),h&&f.isFunction(h.promise)?h.promise().then(d.resolve,d.reject):d[g+"With"](this===b?d:this,[h])}):b[a](d[g])})}).promise()},promise:function(a){if(a==null){if(d)return d;d=a={}}var c=g.length;while(c--)a[g[c]]=b[g[c]];return a}}),b.done(c.cancel).fail(b.cancel),delete b.cancel,a&&a.call(b,b);return b},when:function(a){function i(a){return function(c){b[a]=arguments.length>1?h.call(arguments,0):c,--e||g.resolveWith(g,h.call(b,0))}}var b=arguments,c=0,d=b.length,e=d,g=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred();if(d>1){for(;c<d;c++)b[c]&&f.isFunction(b[c].promise)?b[c].promise().then(i(c),g.reject):--e;e||g.resolveWith(g,b)}else g!==a&&g.resolveWith(g,d?[a]:[]);return g.promise()}}),f.support=function(){var a=c.createElement("div"),b=c.documentElement,d,e,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u;a.setAttribute("className","t"),a.innerHTML="   <link><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type=checkbox>",d=a.getElementsByTagName("*"),e=a.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=a.getElementsByTagName("input")[0],k={leadingWhitespace:a.firstChild.nodeType===3,tbody:!a.getElementsByTagName("tbody").length,htmlSerialize:!!a.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55$/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:a.className!=="t",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,k.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,k.optDisabled=!h.disabled;try{delete a.test}catch(v){k.deleteExpando=!1}!a.addEventListener&&a.attachEvent&&a.fireEvent&&(a.attachEvent("onclick",function(){k.noCloneEvent=!1}),a.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),k.radioValue=i.value==="t",i.setAttribute("checked","checked"),a.appendChild(i),l=c.createDocumentFragment(),l.appendChild(a.firstChild),k.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,a.innerHTML="",a.style.width=a.style.paddingLeft="1px",m=c.getElementsByTagName("body")[0],o=c.createElement(m?"div":"body"),p={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},m&&f.extend(p,{position:"absolute",left:"-1000px",top:"-1000px"});for(t in p)o.style[t]=p[t];o.appendChild(a),n=m||b,n.insertBefore(o,n.firstChild),k.appendChecked=i.checked,k.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,k.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="<div style='width:4px;'></div>",k.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>",q=a.getElementsByTagName("td"),u=q[0].offsetHeight===0,q[0].style.display="",q[1].style.display="none",k.reliableHiddenOffsets=u&&q[0].offsetHeight===0,a.innerHTML="",c.defaultView&&c.defaultView.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",a.appendChild(j),k.reliableMarginRight=(parseInt((c.defaultView.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0),o.innerHTML="",n.removeChild(o);if(a.attachEvent)for(t in{submit:1,change:1,focusin:1})s="on"+t,u=s in a,u||(a.setAttribute(s,"return;"),u=typeof a[s]=="function"),k[t+"Bubbles"]=u;o=l=g=h=m=j=a=i=null;return k}(),f.boxModel=f.support.boxModel;var i=/^(?:\{.*\}|\[.*\])$/,j=/([a-z])([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!l(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i=f.expando,j=typeof c=="string",k=a.nodeType,l=k?f.cache:a,m=k?a[f.expando]:a[f.expando]&&f.expando;if((!m||e&&m&&l[m]&&!l[m][i])&&j&&d===b)return;m||(k?a[f.expando]=m=++f.uuid:m=f.expando),l[m]||(l[m]={},k||(l[m].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?l[m][i]=f.extend(l[m][i],c):l[m]=f.extend(l[m],c);g=l[m],e&&(g[i]||(g[i]={}),g=g[i]),d!==b&&(g[f.camelCase(c)]=d);if(c==="events"&&!g[c])return g[i]&&g[i].events;j?(h=g[c],h==null&&(h=g[f.camelCase(c)])):h=g;return h}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e=f.expando,g=a.nodeType,h=g?f.cache:a,i=g?a[f.expando]:f.expando;if(!h[i])return;if(b){d=c?h[i][e]:h[i];if(d){d[b]||(b=f.camelCase(b)),delete d[b];if(!l(d))return}}if(c){delete h[i][e];if(!l(h[i]))return}var j=h[i][e];f.support.deleteExpando||!h.setInterval?delete h[i]:h[i]=null,j?(h[i]={},g||(h[i].toJSON=f.noop),h[i][e]=j):g&&(f.support.deleteExpando?delete a[f.expando]:a.removeAttribute?a.removeAttribute(f.expando):a[f.expando]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d=null;if(typeof a=="undefined"){if(this.length){d=f.data(this[0]);if(this[0].nodeType===1){var e=this[0].attributes,g;for(var h=0,i=e.length;h<i;h++)g=e[h].name,g.indexOf("data-")===0&&(g=f.camelCase(g.substring(5)),k(this[0],g,d[g]))}}return d}if(typeof a=="object")return this.each(function(){f.data(this,a)});var j=a.split(".");j[1]=j[1]?"."+j[1]:"";if(c===b){d=this.triggerHandler("getData"+j[1]+"!",[j[0]]),d===b&&this.length&&(d=f.data(this[0],a),d=k(this[0],a,d));return d===b&&j[1]?this.data(j[0]):d}return this.each(function(){var b=f(this),d=[j[0],c];b.triggerHandler("setData"+j[1]+"!",d),f.data(this,a,c),b.triggerHandler("changeData"+j[1]+"!",d)})},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,c){a&&(c=(c||"fx")+"mark",f.data(a,c,(f.data(a,c,b,!0)||0)+1,!0))},_unmark:function(a,c,d){a!==!0&&(d=c,c=a,a=!1);if(c){d=d||"fx";var e=d+"mark",g=a?0:(f.data(c,e,b,!0)||1)-1;g?f.data(c,e,g,!0):(f.removeData(c,e,!0),m(c,d,"mark"))}},queue:function(a,c,d){if(a){c=(c||"fx")+"queue";var e=f.data(a,c,b,!0);d&&(!e||f.isArray(d)?e=f.data(a,c,f.makeArray(d),!0):e.push(d));return e||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e;d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),d.call(a,function(){f.dequeue(a,b)})),c.length||(f.removeData(a,b+"queue",!0),m(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){typeof a!="string"&&(c=a,a="fx");if(c===b)return f.queue(this[0],a);return this.each(function(){var b=f.queue(this,a,c);a==="fx"&&b[0]!=="inprogress"&&f.dequeue(this,a)})},dequeue:function(a){return this.each(function(){f.dequeue(this,a)})},delay:function(a,b){a=f.fx?f.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(){var c=this;setTimeout(function(){f.dequeue(c,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){function m(){--h||d.resolveWith(e,[e])}typeof a!="string"&&(c=a,a=b),a=a||"fx";var d=f.Deferred(),e=this,g=e.length,h=1,i=a+"defer",j=a+"queue",k=a+"mark",l;while(g--)if(l=f.data(e[g],i,b,!0)||(f.data(e[g],j,b,!0)||f.data(e[g],k,b,!0))&&f.data(e[g],i,f._Deferred(),!0))h++,l.done(m);m();return d.promise()}});var n=/[\n\t\r]/g,o=/\s+/,p=/\r/g,q=/^(?:button|input)$/i,r=/^(?:button|input|object|select|textarea)$/i,s=/^a(?:rea)?$/i,t=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,u,v;f.fn.extend({attr:function(a,b){return f.access(this,a,b,!0,f.attr)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,a,b,!0,f.prop)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(o);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{g=" "+e.className+" ";for(h=0,i=b.length;h<i;h++)~g.indexOf(" "+b[h]+" ")||(g+=b[h]+" ");e.className=f.trim(g)}}}return this},removeClass:function(a){var c,d,e,g,h,i,j;if(f.isFunction(a))return this.each(function(b){f(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(o);for(d=0,e=this.length;d<e;d++){g=this[d];if(g.nodeType===1&&g.className)if(a){h=(" "+g.className+" ").replace(n," ");for(i=0,j=c.length;i<j;i++)h=h.replace(" "+c[i]+" "," ");g.className=f.trim(h)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";if(f.isFunction(a))return this.each(function(c){f(this).toggleClass(a.call(this,c,this.className,b),b)});return this.each(function(){if(c==="string"){var e,g=0,h=f(this),i=b,j=a.split(o);while(e=j[g++])i=d?i:!h.hasClass(e),h[i?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&f._data(this,"__className__",this.className),this.className=this.className||a===!1?"":f._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ";for(var c=0,d=this.length;c<d;c++)if(this[c].nodeType===1&&(" "+this[c].className+" ").replace(n," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e=this[0];if(!arguments.length){if(e){c=f.valHooks[e.nodeName.toLowerCase()]||f.valHooks[e.type];if(c&&"get"in c&&(d=c.get(e,"value"))!==b)return d;d=e.value;return typeof d=="string"?d.replace(p,""):d==null?"":d}return b}var g=f.isFunction(a);return this.each(function(d){var e=f(this),h;if(this.nodeType===1){g?h=a.call(this,d,e.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c=a.selectedIndex,d=[],e=a.options,g=a.type==="select-one";if(c<0)return null;for(var h=g?c:0,i=g?c+1:e.length;h<i;h++){var j=e[h];if(j.selected&&(f.support.optDisabled?!j.disabled:j.getAttribute("disabled")===null)&&(!j.parentNode.disabled||!f.nodeName(j.parentNode,"optgroup"))){b=f(j).val();if(g)return b;d.push(b)}}if(g&&!d.length&&e.length)return f(e[c]).val();return d},set:function(a,b){var c=f.makeArray(b);f(a).find("option").each(function(){this.selected=f.inArray(f(this).val(),c)>=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attrFix:{tabindex:"tabIndex"},attr:function(a,c,d,e){var g=a.nodeType;if(!a||g===3||g===8||g===2)return b;if(e&&c in f.attrFn)return f(a)[c](d);if(!("getAttribute"in a))return f.prop(a,c,d);var h,i,j=g!==1||!f.isXMLDoc(a);j&&(c=f.attrFix[c]||c,i=f.attrHooks[c],i||(t.test(c)?i=v:u&&(i=u)));if(d!==b){if(d===null){f.removeAttr(a,c);return b}if(i&&"set"in i&&j&&(h=i.set(a,d,c))!==b)return h;a.setAttribute(c,""+d);return d}if(i&&"get"in i&&j&&(h=i.get(a,c))!==null)return h;h=a.getAttribute(c);return h===null?b:h},removeAttr:function(a,b){var c;a.nodeType===1&&(b=f.attrFix[b]||b,f.attr(a,b,""),a.removeAttribute(b),t.test(b)&&(c=f.propFix[b]||b)in a&&(a[c]=!1))},attrHooks:{type:{set:function(a,b){if(q.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},value:{get:function(a,b){if(u&&f.nodeName(a,"button"))return u.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(u&&f.nodeName(a,"button"))return u.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e=a.nodeType;if(!a||e===3||e===8||e===2)return b;var g,h,i=e!==1||!f.isXMLDoc(a);i&&(c=f.propFix[c]||c,h=f.propHooks[c]);return d!==b?h&&"set"in h&&(g=h.set(a,d,c))!==b?g:a[c]=d:h&&"get"in h&&(g=h.get(a,c))!==null?g:a[c]},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):r.test(a.nodeName)||s.test(a.nodeName)&&a.href?0:b}}}}),f.attrHooks.tabIndex=f.propHooks.tabIndex,v={get:function(a,c){var d;return f.prop(a,c)===!0||(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},f.support.getSetAttribute||(u=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&d.nodeValue!==""?d.nodeValue:b},set:function(a,b,d){var e=a.getAttributeNode(d);e||(e=c.createAttribute(d),a.setAttributeNode(e));return e.nodeValue=b+""}},f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})})),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex);return null}})),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var w=/\.(.*)$/,x=/^(?:textarea|input|select)$/i,y=/\./g,z=/ /g,A=/[^\w\s.|`]/g,B=function(a){return a.replace(A,"\\$&")};f.event={add:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){if(d===!1)d=C;else if(!d)return;var g,h;d.handler&&(g=d,d=g.handler),d.guid||(d.guid=f.guid++);var i=f._data(a);if(!i)return;var j=i.events,k=i.handle;j||(i.events=j={}),k||(i.handle=k=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.handle.apply(k.elem,arguments):b}),k.elem=a,c=c.split(" ");var l,m=0,n;while(l=c[m++]){h=g?f.extend({},g):{handler:d,data:e},l.indexOf(".")>-1?(n=l.split("."),l=n.shift(),h.namespace=n.slice(0).sort().join(".")):(n=[],h.namespace=""),h.type=l,h.guid||(h.guid=d.guid);var o=j[l],p=f.event.special[l]||{};if(!o){o=j[l]=[];if(!p.setup||p.setup.call(a,e,n,k)===!1)a.addEventListener?a.addEventListener(l,k,!1):a.attachEvent&&a.attachEvent("on"+l,k)}p.add&&(p.add.call(a,h),h.handler.guid||(h.handler.guid=d.guid)),o.push(h),f.event.global[l]=!0}a=null}},global:{},remove:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){d===!1&&(d=C);var g,h,i,j,k=0,l,m,n,o,p,q,r,s=f.hasData(a)&&f._data(a),t=s&&s.events;if(!s||!t)return;c&&c.type&&(d=c.handler,c=c.type);if(!c||typeof c=="string"&&c.charAt(0)==="."){c=c||"";for(h in t)f.event.remove(a,h+c);return}c=c.split(" ");while(h=c[k++]){r=h,q=null,l=h.indexOf(".")<0,m=[],l||(m=h.split("."),h=m.shift(),n=new RegExp("(^|\\.)"+f.map(m.slice(0).sort(),B).join("\\.(?:.*\\.)?")+"(\\.|$)")),p=t[h];if(!p)continue;if(!d){for(j=0;j<p.length;j++){q=p[j];if(l||n.test(q.namespace))f.event.remove(a,r,q.handler,j),p.splice(j--,1)}continue}o=f.event.special[h]||{};for(j=e||0;j<p.length;j++){q=p[j];if(d.guid===q.guid){if(l||n.test(q.namespace))e==null&&p.splice(j--,1),o.remove&&o.remove.call(a,q);if(e!=null)break}}if(p.length===0||e!=null&&p.length===1)(!o.teardown||o.teardown.call(a,m)===!1)&&f.removeEvent(a,h,s.handle),g=null
    +,delete t[h]}if(f.isEmptyObject(t)){var u=s.handle;u&&(u.elem=null),delete s.events,delete s.handle,f.isEmptyObject(s)&&f.removeData(a,b,!0)}}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){var h=c.type||c,i=[],j;h.indexOf("!")>=0&&(h=h.slice(0,-1),j=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if(!!e&&!f.event.customEvent[h]||!!f.event.global[h]){c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.exclusive=j,c.namespace=i.join("."),c.namespace_re=new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)");if(g||!e)c.preventDefault(),c.stopPropagation();if(!e){f.each(f.cache,function(){var a=f.expando,b=this[a];b&&b.events&&b.events[h]&&f.event.trigger(c,d,b.handle.elem)});return}if(e.nodeType===3||e.nodeType===8)return;c.result=b,c.target=e,d=d!=null?f.makeArray(d):[],d.unshift(c);var k=e,l=h.indexOf(":")<0?"on"+h:"";do{var m=f._data(k,"handle");c.currentTarget=k,m&&m.apply(k,d),l&&f.acceptData(k)&&k[l]&&k[l].apply(k,d)===!1&&(c.result=!1,c.preventDefault()),k=k.parentNode||k.ownerDocument||k===c.target.ownerDocument&&a}while(k&&!c.isPropagationStopped());if(!c.isDefaultPrevented()){var n,o=f.event.special[h]||{};if((!o._default||o._default.call(e.ownerDocument,c)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)){try{l&&e[h]&&(n=e[l],n&&(e[l]=null),f.event.triggered=h,e[h]())}catch(p){}n&&(e[l]=n),f.event.triggered=b}}return c.result}},handle:function(c){c=f.event.fix(c||a.event);var d=((f._data(this,"events")||{})[c.type]||[]).slice(0),e=!c.exclusive&&!c.namespace,g=Array.prototype.slice.call(arguments,0);g[0]=c,c.currentTarget=this;for(var h=0,i=d.length;h<i;h++){var j=d[h];if(e||c.namespace_re.test(j.namespace)){c.handler=j.handler,c.data=j.data,c.handleObj=j;var k=j.handler.apply(this,g);k!==b&&(c.result=k,k===!1&&(c.preventDefault(),c.stopPropagation()));if(c.isImmediatePropagationStopped())break}}return c.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(a){if(a[f.expando])return a;var d=a;a=f.Event(d);for(var e=this.props.length,g;e;)g=this.props[--e],a[g]=d[g];a.target||(a.target=a.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),!a.relatedTarget&&a.fromElement&&(a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement);if(a.pageX==null&&a.clientX!=null){var h=a.target.ownerDocument||c,i=h.documentElement,j=h.body;a.pageX=a.clientX+(i&&i.scrollLeft||j&&j.scrollLeft||0)-(i&&i.clientLeft||j&&j.clientLeft||0),a.pageY=a.clientY+(i&&i.scrollTop||j&&j.scrollTop||0)-(i&&i.clientTop||j&&j.clientTop||0)}a.which==null&&(a.charCode!=null||a.keyCode!=null)&&(a.which=a.charCode!=null?a.charCode:a.keyCode),!a.metaKey&&a.ctrlKey&&(a.metaKey=a.ctrlKey),!a.which&&a.button!==b&&(a.which=a.button&1?1:a.button&2?3:a.button&4?2:0);return a},guid:1e8,proxy:f.proxy,special:{ready:{setup:f.bindReady,teardown:f.noop},live:{add:function(a){f.event.add(this,M(a.origType,a.selector),f.extend({},a,{handler:L,guid:a.handler.guid}))},remove:function(a){f.event.remove(this,M(a.origType,a.selector),a)}},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}}},f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(!this.preventDefault)return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?D:C):this.type=a,b&&f.extend(this,b),this.timeStamp=f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=D;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=D;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=D,this.stopPropagation()},isDefaultPrevented:C,isPropagationStopped:C,isImmediatePropagationStopped:C};var E=function(a){var b=a.relatedTarget,c=!1,d=a.type;a.type=a.data,b!==this&&(b&&(c=f.contains(this,b)),c||(f.event.handle.apply(this,arguments),a.type=d))},F=function(a){a.type=a.data,f.event.handle.apply(this,arguments)};f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]={setup:function(c){f.event.add(this,b,c&&c.selector?F:E,a)},teardown:function(a){f.event.remove(this,b,a&&a.selector?F:E)}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(a,b){if(!f.nodeName(this,"form"))f.event.add(this,"click.specialSubmit",function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(c==="submit"||c==="image")&&f(b).closest("form").length&&J("submit",this,arguments)}),f.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(c==="text"||c==="password")&&f(b).closest("form").length&&a.keyCode===13&&J("submit",this,arguments)});else return!1},teardown:function(a){f.event.remove(this,".specialSubmit")}});if(!f.support.changeBubbles){var G,H=function(a){var b=f.nodeName(a,"input")?a.type:"",c=a.value;b==="radio"||b==="checkbox"?c=a.checked:b==="select-multiple"?c=a.selectedIndex>-1?f.map(a.options,function(a){return a.selected}).join("-"):"":f.nodeName(a,"select")&&(c=a.selectedIndex);return c},I=function(c){var d=c.target,e,g;if(!!x.test(d.nodeName)&&!d.readOnly){e=f._data(d,"_change_data"),g=H(d),(c.type!=="focusout"||d.type!=="radio")&&f._data(d,"_change_data",g);if(e===b||g===e)return;if(e!=null||g)c.type="change",c.liveFired=b,f.event.trigger(c,arguments[1],d)}};f.event.special.change={filters:{focusout:I,beforedeactivate:I,click:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(c==="radio"||c==="checkbox"||f.nodeName(b,"select"))&&I.call(this,a)},keydown:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(a.keyCode===13&&!f.nodeName(b,"textarea")||a.keyCode===32&&(c==="checkbox"||c==="radio")||c==="select-multiple")&&I.call(this,a)},beforeactivate:function(a){var b=a.target;f._data(b,"_change_data",H(b))}},setup:function(a,b){if(this.type==="file")return!1;for(var c in G)f.event.add(this,c+".specialChange",G[c]);return x.test(this.nodeName)},teardown:function(a){f.event.remove(this,".specialChange");return x.test(this.nodeName)}},G=f.event.special.change.filters,G.focus=G.beforeactivate}f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){function e(a){var c=f.event.fix(a);c.type=b,c.originalEvent={},f.event.trigger(c,null,c.target),c.isDefaultPrevented()&&a.preventDefault()}var d=0;f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.each(["bind","one"],function(a,c){f.fn[c]=function(a,d,e){var g;if(typeof a=="object"){for(var h in a)this[c](h,d,a[h],e);return this}if(arguments.length===2||d===!1)e=d,d=b;c==="one"?(g=function(a){f(this).unbind(a,g);return e.apply(this,arguments)},g.guid=e.guid||f.guid++):g=e;if(a==="unload"&&c!=="one")this.one(a,d,e);else for(var i=0,j=this.length;i<j;i++)f.event.add(this[i],a,g,d);return this}}),f.fn.extend({unbind:function(a,b){if(typeof a=="object"&&!a.preventDefault)for(var c in a)this.unbind(c,a[c]);else for(var d=0,e=this.length;d<e;d++)f.event.remove(this[d],a,b);return this},delegate:function(a,b,c,d){return this.live(b,c,d,a)},undelegate:function(a,b,c){return arguments.length===0?this.unbind("live"):this.die(b,null,c,a)},trigger:function(a,b){return this.each(function(){f.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return f.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||f.guid++,d=0,e=function(c){var e=(f.data(this,"lastToggle"+a.guid)||0)%d;f.data(this,"lastToggle"+a.guid,e+1),c.preventDefault();return b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var K={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};f.each(["live","die"],function(a,c){f.fn[c]=function(a,d,e,g){var h,i=0,j,k,l,m=g||this.selector,n=g?this:f(this.context);if(typeof a=="object"&&!a.preventDefault){for(var o in a)n[c](o,d,a[o],m);return this}if(c==="die"&&!a&&g&&g.charAt(0)==="."){n.unbind(g);return this}if(d===!1||f.isFunction(d))e=d||C,d=b;a=(a||"").split(" ");while((h=a[i++])!=null){j=w.exec(h),k="",j&&(k=j[0],h=h.replace(w,""));if(h==="hover"){a.push("mouseenter"+k,"mouseleave"+k);continue}l=h,K[h]?(a.push(K[h]+k),h=h+k):h=(K[h]||h)+k;if(c==="live")for(var p=0,q=n.length;p<q;p++)f.event.add(n[p],"live."+M(h,m),{data:d,selector:m,handler:e,origType:h,origHandler:e,preType:l});else n.unbind("live."+M(h,m),e)}return this}}),f.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),function(a,b){f.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?this.bind(b,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0)}),function(){function u(a,b,c,d,e,f){for(var g=0,h=d.length;g<h;g++){var i=d[g];if(i){var j=!1;i=i[a];while(i){if(i.sizcache===c){j=d[i.sizset];break}if(i.nodeType===1){f||(i.sizcache=c,i.sizset=g);if(typeof b!="string"){if(i===b){j=!0;break}}else if(k.filter(b,[i]).length>0){j=i;break}}i=i[a]}d[g]=j}}}function t(a,b,c,d,e,f){for(var g=0,h=d.length;g<h;g++){var i=d[g];if(i){var j=!1;i=i[a];while(i){if(i.sizcache===c){j=d[i.sizset];break}i.nodeType===1&&!f&&(i.sizcache=c,i.sizset=g);if(i.nodeName.toLowerCase()===b){j=i;break}i=i[a]}d[g]=j}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d=0,e=Object.prototype.toString,g=!1,h=!0,i=/\\/g,j=/\W/;[0,0].sort(function(){h=!1;return 0});var k=function(b,d,f,g){f=f||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return f;var i,j,n,o,q,r,s,t,u=!0,w=k.isXML(d),x=[],y=b;do{a.exec(""),i=a.exec(y);if(i){y=i[3],x.push(i[1]);if(i[2]){o=i[3];break}}}while(i);if(x.length>1&&m.exec(b))if(x.length===2&&l.relative[x[0]])j=v(x[0]+x[1],d);else{j=l.relative[x[0]]?[d]:k(x.shift(),d);while(x.length)b=x.shift(),l.relative[b]&&(b+=x.shift()),j=v(b,j)}else{!g&&x.length>1&&d.nodeType===9&&!w&&l.match.ID.test(x[0])&&!l.match.ID.test(x[x.length-1])&&(q=k.find(x.shift(),d,w),d=q.expr?k.filter(q.expr,q.set)[0]:q.set[0]);if(d){q=g?{expr:x.pop(),set:p(g)}:k.find(x.pop(),x.length===1&&(x[0]==="~"||x[0]==="+")&&d.parentNode?d.parentNode:d,w),j=q.expr?k.filter(q.expr,q.set):q.set,x.length>0?n=p(j):u=!1;while(x.length)r=x.pop(),s=r,l.relative[r]?s=x.pop():r="",s==null&&(s=d),l.relative[r](n,s,w)}else n=x=[]}n||(n=j),n||k.error(r||b);if(e.call(n)==="[object Array]")if(!u)f.push.apply(f,n);else if(d&&d.nodeType===1)for(t=0;n[t]!=null;t++)n[t]&&(n[t]===!0||n[t].nodeType===1&&k.contains(d,n[t]))&&f.push(j[t]);else for(t=0;n[t]!=null;t++)n[t]&&n[t].nodeType===1&&f.push(j[t]);else p(n,f);o&&(k(o,h,f,g),k.uniqueSort(f));return f};k.uniqueSort=function(a){if(r){g=h,a.sort(r);if(g)for(var b=1;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},k.matches=function(a,b){return k(a,null,null,b)},k.matchesSelector=function(a,b){return k(b,null,null,[a]).length>0},k.find=function(a,b,c){var d;if(!a)return[];for(var e=0,f=l.order.length;e<f;e++){var g,h=l.order[e];if(g=l.leftMatch[h].exec(a)){var j=g[1];g.splice(1,1);if(j.substr(j.length-1)!=="\\"){g[1]=(g[1]||"").replace(i,""),d=l.find[h](g,b,c);if(d!=null){a=a.replace(l.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},k.filter=function(a,c,d,e){var f,g,h=a,i=[],j=c,m=c&&c[0]&&k.isXML(c[0]);while(a&&c.length){for(var n in l.filter)if((f=l.leftMatch[n].exec(a))!=null&&f[2]){var o,p,q=l.filter[n],r=f[1];g=!1,f.splice(1,1);if(r.substr(r.length-1)==="\\")continue;j===i&&(i=[]);if(l.preFilter[n]){f=l.preFilter[n](f,j,d,i,e,m);if(!f)g=o=!0;else if(f===!0)continue}if(f)for(var s=0;(p=j[s])!=null;s++)if(p){o=q(p,f,s,j);var t=e^!!o;d&&o!=null?t?g=!0:j[s]=!1:t&&(i.push(p),g=!0)}if(o!==b){d||(j=i),a=a.replace(l.match[n],"");if(!g)return[];break}}if(a===h)if(g==null)k.error(a);else break;h=a}return j},k.error=function(a){throw"Syntax error, unrecognized expression: "+a};var l=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b=="string",d=c&&!j.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1);a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&k.filter(b,a,!0)},">":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!j.test(b)){b=b.toLowerCase();for(;e<f;e++){c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&k.filter(b,a,!0)}},"":function(a,b,c){var e,f=d++,g=u;typeof b=="string"&&!j.test(b)&&(b=b.toLowerCase(),e=b,g=t),g("parentNode",b,f,a,e,c)},"~":function(a,b,c){var e,f=d++,g=u;typeof b=="string"&&!j.test(b)&&(b=b.toLowerCase(),e=b,g=t),g("previousSibling",b,f,a,e,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(i,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&(" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(i,"")},TAG:function(a,b){return a[1].replace(i,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||k.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&k.error(a[0]);a[0]=d++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(i,"");!f&&l.attrMap[g]&&(a[1]=l.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(i,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=k(b[3],null,null,c);else{var g=k.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(l.match.POS.test(b[0])||l.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!k(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return b<c[3]-0},gt:function(a,b,c){return b>c[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=l.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||k.getText([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h<i;h++)if(g[h]===a)return!1;return!0}k.error(e)},CHILD:function(a,b){var c=b[1],d=a;switch(c){case"only":case"first":while(d=d.previousSibling)if(d.nodeType===1)return!1;if(c==="first")return!0;d=a;case"last":while(d=d.nextSibling)if(d.nodeType===1)return!1;return!0;case"nth":var e=b[2],f=b[3];if(e===1&&f===0)return!0;var g=b[0],h=a.parentNode;if(h&&(h.sizcache!==g||!a.nodeIndex)){var i=0;for(d=h.firstChild;d;d=d.nextSibling)d.nodeType===1&&(d.nodeIndex=++i);h.sizcache=g}var j=a.nodeIndex-f;return e===0?j===0:j%e===0&&j/e>=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=l.attrHandle[c]?l.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=l.setFilters[e];if(f)return f(a,c,b,d)}}},m=l.match.POS,n=function(a,b){return"\\"+(b-0+1)};for(var o in l.match)l.match[o]=new RegExp(l.match[o].source+/(?![^\[]*\])(?![^\(]*\))/.source),l.leftMatch[o]=new RegExp(/(^(?:.|\r|\n)*?)/.source+l.match[o].source.replace(/\\(\d+)/g,n));var p=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(q){p=function(a,b){var c=0,d=b||[];if(e.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var f=a.length;c<f;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var r,s;c.documentElement.compareDocumentPosition?r=function(a,b){if(a===b){g=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compareDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(r=function(a,b){if(a===b){g=!0;return 0}if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],h=a.parentNode,i=b.parentNode,j=h;if(h===i)return s(a,b);if(!h)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return s(e[k],f[k]);return k===c?s(a,f[k],-1):s(e[k],b,1)},s=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),k.getText=function(a){var b="",c;for(var d=0;a[d];d++)c=a[d],c.nodeType===3||c.nodeType===4?b+=c.nodeValue:c.nodeType!==8&&(b+=k.getText(c.childNodes));return b},function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(l.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},l.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(l.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(l.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=k,b=c.createElement("div"),d="__sizzle__";b.innerHTML="<p class='TEST'></p>";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){k=function(b,e,f,g){e=e||c;if(!g&&!k.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return p(e.getElementsByTagName(b),f);if(h[2]&&l.find.CLASS&&e.getElementsByClassName)return p(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return p([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return p([],f);if(i.id===h[3])return p([i],f)}try{return p(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var m=e,n=e.getAttribute("id"),o=n||d,q=e.parentNode,r=/^\s*[+~]/.test(b);n?o=o.replace(/'/g,"\\$&"):e.setAttribute("id",o),r&&q&&(e=e.parentNode);try{if(!r||q)return p(e.querySelectorAll("[id='"+o+"'] "+b),f)}catch(s){}finally{n||m.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)k[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}k.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!k.isXML(a))try{if(e||!l.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return k(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="<div class='test e'></div><div class='test'></div>";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;l.order.splice(1,0,"CLASS"),l.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?k.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?k.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:k.contains=function(){return!1},k.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var v=function(a,b){var c,d=[],e="",f=b.nodeType?[b]:b;while(c=l.match.PSEUDO.exec(a))e+=c[0],a=a.replace(l.match.PSEUDO,"");a=l.relative[a]?a+"*":a;for(var g=0,h=f.length;g<h;g++)k(a,f[g],d);return k.filter(e,d)};f.find=k,f.expr=k.selectors,f.expr[":"]=f.expr.filters,f.unique=k.uniqueSort,f.text=k.getText,f.isXMLDoc=k.isXML,f.contains=k.contains}();var N=/Until$/,O=/^(?:parents|prevUntil|prevAll)/,P=/,/,Q=/^.[^:#\[\.,]*$/,R=Array.prototype.slice,S=f.expr.match.POS,T={children:!0,contents:!0,next:!0,prev:!0};f.fn.extend({find:function(a){var b=this,c,d;if(typeof a!="string")return f(a).filter(function(){for(c=0,d=b.length;c<d;c++)if(f.contains(b[c],this))return!0});var e=this.pushStack("","find",a),g,h,i;for(c=0,d=this.length;c<d;c++){g=e.length,f.find(a,this[c],e);if(c>0)for(h=g;h<e.length;h++)for(i=0;i<g;i++)if(e[i]===e[h]){e.splice(h--,1);break}}return e},has:function(a){var b=f(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(f.contains(this,b[a]))return!0})},not:function(a){return this.pushStack(V(this,a,!1),"not",a)},filter:function(a){return this.pushStack(V(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h,i,j={},k=1;if(g&&a.length){for(d=0,e=a.length;d<e;d++)i=a[d],j[i]||(j[i]=S.test(i)?f(i,b||this.context):i);while(g&&g.ownerDocument&&g!==b){for(i in j)h=j[i],(h.jquery?h.index(g)>-1:f(g).is(h))&&c.push({selector:i,elem:g,level:k});g=g.parentNode,k++}}return c}var l=S.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d<e;d++){g=this[d];while(g){if(l?l.index(g)>-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(U(c[0])||U(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c),g=R.call(arguments);N.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!T[a]?f.unique(e):e,(this.length>1||P.test(d))&&O.test(a)&&(e=e.reverse());return this.pushStack(e,a,g.join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/<tbody/i,_=/<|&#?\w+;/,ba=/<(?:script|object|embed|option|style)/i,bb=/checked\s*(?:[^=]|=\s*.checked.)/i,bc=/\/(java|ecma)script/i,bd=/^\s*<!(?:\[CDATA\[|\-\-)/,be={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};be.optgroup=be.option,be.tbody=be.tfoot=be.colgroup=be.caption=be.thead,be.th=be.td,f.support.htmlSerialize||(be._default=[1,"div<div>","</div>"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){f(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f(arguments[0]).toArray());return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!be[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1></$2>");try{for(var c=0,d=this.length;c<d;c++)this[c].nodeType===1&&(f.cleanData(this[c].getElementsByTagName("*")),this[c].innerHTML=a)}catch(e){this.empty().append(a)}}else f.isFunction(a)?this.each(function(b){var c=f(this);c.html(a.call(this,b,c.html()))}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(f.isFunction(a))return this.each(function(b){var c=f(this),d=c.html();c.replaceWith(a.call(this,b,d))});typeof a!="string"&&(a=f(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;f(this).remove(),b?f(b).before(a):f(c).append(a)})}return this.length?this.pushStack(f(f.isFunction(a)?a():a),"replaceWith",a):this},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){var e,g,h,i,j=a[0],k=[];if(!f.support.checkClone&&arguments.length===3&&typeof j=="string"&&bb.test(j))return this.each(function(){f(this).domManip(a,c,d,!0)});if(f.isFunction(j))return this.each(function(e){var g=f(this);a[0]=j.call(this,e,c?g.html():b),g.domManip(a,c,d)});if(this[0]){i=j&&j.parentNode,f.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?e={fragment:i}:e=f.buildFragment(a,this,k),h=e.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&f.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)d.call(c?bf(this[l],g):this[l],e.cacheable||m>1&&l<n?f.clone(h,!0,!0):h)}k.length&&f.each(k,bl)}return this}}),f.buildFragment=function(a,b,d){var e,g,h,i;b&&b[0]&&(i=b[0].ownerDocument||b[0]),i.createDocumentFragment||(i=c),a.length===1&&typeof a[0]=="string"&&a[0].length<512&&i===c&&a[0].charAt(0)==="<"&&!ba.test(a[0])&&(f.support.checkClone||!bb.test(a[0]))&&(g=!0,h=f.fragments[a[0]],h&&h!==1&&(e=h)),e||(e=i.createDocumentFragment(),f.clean(a,i,e,d)),g&&(f.fragments[a[0]]=h?e:1);
    +return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d=a.cloneNode(!0),e,g,h;if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bh(a,d),e=bi(a),g=bi(d);for(h=0;e[h];++h)g[h]&&bh(e[h],g[h])}if(b){bg(a,d);if(c){e=bi(a),g=bi(d);for(h=0;e[h];++h)bg(e[h],g[h])}}e=g=null;return d},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!_.test(k))k=b.createTextNode(k);else{k=k.replace(Y,"<$1></$2>");var l=(Z.exec(k)||["",""])[1].toLowerCase(),m=be[l]||be._default,n=m[0],o=b.createElement("div");o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=$.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]==="<table>"&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&X.test(k)&&o.insertBefore(b.createTextNode(X.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i<r;i++)bk(k[i]);else bk(k);k.nodeType?h.push(k):h=f.merge(h,k)}if(d){g=function(a){return!a.type||bc.test(a.type)};for(j=0;h[j];j++)if(e&&f.nodeName(h[j],"script")&&(!h[j].type||h[j].type.toLowerCase()==="text/javascript"))e.push(h[j].parentNode?h[j].parentNode.removeChild(h[j]):h[j]);else{if(h[j].nodeType===1){var s=f.grep(h[j].getElementsByTagName("script"),g);h.splice.apply(h,[j+1,0].concat(s))}d.appendChild(h[j])}}return h},cleanData:function(a){var b,c,d=f.cache,e=f.expando,g=f.event.special,h=f.support.deleteExpando;for(var i=0,j;(j=a[i])!=null;i++){if(j.nodeName&&f.noData[j.nodeName.toLowerCase()])continue;c=j[f.expando];if(c){b=d[c]&&d[c][e];if(b&&b.events){for(var k in b.events)g[k]?f.event.remove(j,k):f.removeEvent(j,k,b.handle);b.handle&&(b.handle.elem=null)}h?delete j[f.expando]:j.removeAttribute&&j.removeAttribute(f.expando),delete d[c]}}}});var bm=/alpha\([^)]*\)/i,bn=/opacity=([^)]*)/,bo=/([A-Z]|^ms)/g,bp=/^-?\d+(?:px)?$/i,bq=/^-?\d/,br=/^([\-+])=([\-+.\de]+)/,bs={position:"absolute",visibility:"hidden",display:"block"},bt=["Left","Right"],bu=["Top","Bottom"],bv,bw,bx;f.fn.css=function(a,c){if(arguments.length===2&&c===b)return this;return f.access(this,a,c,!0,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)})},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bv(a,"opacity","opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d,h==="string"&&(g=br.exec(d))&&(d=+(g[1]+1)*+g[2]+parseFloat(f.css(a,c)),h="number");if(d==null||h==="number"&&isNaN(d))return;h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(bv)return bv(a,c)},swap:function(a,b,c){var d={};for(var e in b)d[e]=a.style[e],a.style[e]=b[e];c.call(a);for(e in b)a.style[e]=d[e]}}),f.curCSS=f.css,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){var e;if(c){if(a.offsetWidth!==0)return by(a,b,d);f.swap(a,bs,function(){e=by(a,b,d)});return e}},set:function(a,b){if(!bp.test(b))return b;b=parseFloat(b);if(b>=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bn.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNaN(b)?"":"alpha(opacity="+b*100+")",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bm,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bm.test(g)?g.replace(bm,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bv(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bw=function(a,c){var d,e,g;c=c.replace(bo,"-$1").toLowerCase();if(!(e=a.ownerDocument.defaultView))return b;if(g=e.getComputedStyle(a,null))d=g.getPropertyValue(c),d===""&&!f.contains(a.ownerDocument.documentElement,a)&&(d=f.style(a,c));return d}),c.documentElement.currentStyle&&(bx=function(a,b){var c,d=a.currentStyle&&a.currentStyle[b],e=a.runtimeStyle&&a.runtimeStyle[b],f=a.style;!bp.test(d)&&bq.test(d)&&(c=f.left,e&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":d||0,d=f.pixelLeft+"px",f.left=c,e&&(a.runtimeStyle.left=e));return d===""?"auto":d}),bv=bw||bx,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bz=/%20/g,bA=/\[\]$/,bB=/\r?\n/g,bC=/#.*$/,bD=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bE=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bF=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bG=/^(?:GET|HEAD)$/,bH=/^\/\//,bI=/\?/,bJ=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bK=/^(?:select|textarea)/i,bL=/\s+/,bM=/([?&])_=[^&]*/,bN=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bO=f.fn.load,bP={},bQ={},bR,bS,bT=["*/"]+["*"];try{bR=e.href}catch(bU){bR=c.createElement("a"),bR.href="",bR=bR.href}bS=bN.exec(bR.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bO)return bO.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("<div>").append(c.replace(bJ,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bK.test(this.nodeName)||bE.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bB,"\r\n")}}):{name:b.name,value:c.replace(bB,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.bind(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?bX(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),bX(a,b);return a},ajaxSettings:{url:bR,isLocal:bF.test(bS[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bT},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bV(bP),ajaxTransport:bV(bQ),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?bZ(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=b$(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.resolveWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f._Deferred(),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bD.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.done,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bC,"").replace(bH,bS[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bL),d.crossDomain==null&&(r=bN.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bS[1]&&r[2]==bS[2]&&(r[3]||(r[1]==="http:"?80:443))==(bS[3]||(bS[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bW(bP,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bG.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bI.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bM,"$1_="+x);d.url=y+(y===d.url?(bI.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bT+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bW(bQ,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){s<2?w(-1,z):f.error(z)}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)bY(g,a[g],c,e);return d.join("&").replace(bz,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var b_=f.now(),ca=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+b_++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ca.test(b.url)||e&&ca.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ca,l),b.url===j&&(e&&(k=k.replace(ca,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cb=a.ActiveXObject?function(){for(var a in cd)cd[a](0,1)}:!1,cc=0,cd;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ce()||cf()}:ce,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cb&&delete cd[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cc,cb&&(cd||(cd={},f(a).unload(cb)),cd[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cg={},ch,ci,cj=/^(?:toggle|show|hide)$/,ck=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cl,cm=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cn;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cq("show",3),a,b,c);for(var g=0,h=this.length;g<h;g++)d=this[g],d.style&&(e=d.style.display,!f._data(d,"olddisplay")&&e==="none"&&(e=d.style.display=""),e===""&&f.css(d,"display")==="none"&&f._data(d,"olddisplay",cr(d.nodeName)));for(g=0;g<h;g++){d=this[g];if(d.style){e=d.style.display;if(e===""||e==="none")d.style.display=f._data(d,"olddisplay")||""}}return this},hide:function(a,b,c){if(a||a===0)return this.animate(cq("hide",3),a,b,c);for(var d=0,e=this.length;d<e;d++)if(this[d].style){var g=f.css(this[d],"display");g!=="none"&&!f._data(this[d],"olddisplay")&&f._data(this[d],"olddisplay",g)}for(d=0;d<e;d++)this[d].style&&(this[d].style.display="none");return this},_toggle:f.fn.toggle,toggle:function(a,b,c){var d=typeof a=="boolean";f.isFunction(a)&&f.isFunction(b)?this._toggle.apply(this,arguments):a==null||d?this.each(function(){var b=d?a:f(this).is(":hidden");f(this)[b?"show":"hide"]()}):this.animate(cq("toggle",3),a,b,c);return this},fadeTo:function(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=f.speed(b,c,d);if(f.isEmptyObject(a))return this.each(e.complete,[!1]);a=f.extend({},a);return this[e.queue===!1?"each":"queue"](function(){e.queue===!1&&f._mark(this);var b=f.extend({},e),c=this.nodeType===1,d=c&&f(this).is(":hidden"),g,h,i,j,k,l,m,n,o;b.animatedProperties={};for(i in a){g=f.camelCase(i),i!==g&&(a[g]=a[i],delete a[i]),h=a[g],f.isArray(h)?(b.animatedProperties[g]=h[1],h=a[g]=h[0]):b.animatedProperties[g]=b.specialEasing&&b.specialEasing[g]||b.easing||"swing";if(h==="hide"&&d||h==="show"&&!d)return b.complete.call(this);c&&(g==="height"||g==="width")&&(b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY],f.css(this,"display")==="inline"&&f.css(this,"float")==="none"&&(f.support.inlineBlockNeedsLayout?(j=cr(this.nodeName),j==="inline"?this.style.display="inline-block":(this.style.display="inline",this.style.zoom=1)):this.style.display="inline-block"))}b.overflow!=null&&(this.style.overflow="hidden");for(i in a)k=new f.fx(this,b,i),h=a[i],cj.test(h)?k[h==="toggle"?d?"show":"hide":h]():(l=ck.exec(h),m=k.cur(),l?(n=parseFloat(l[2]),o=l[3]||(f.cssNumber[i]?"":"px"),o!=="px"&&(f.style(this,i,(n||1)+o),m=(n||1)/k.cur()*m,f.style(this,i,m+o)),l[1]&&(n=(l[1]==="-="?-1:1)*n+m),k.custom(m,n,o)):k.custom(m,h,""));return!0})},stop:function(a,b){a&&this.queue([]),this.each(function(){var a=f.timers,c=a.length;b||f._unmark(!0,this);while(c--)a[c].elem===this&&(b&&a[c](!0),a.splice(c,1))}),b||this.dequeue();return this}}),f.each({slideDown:cq("show",1),slideUp:cq("hide",1),slideToggle:cq("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){f.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),f.extend({speed:function(a,b,c){var d=a&&typeof a=="object"?f.extend({},a):{complete:c||!c&&b||f.isFunction(a)&&a,duration:a,easing:c&&b||b&&!f.isFunction(b)&&b};d.duration=f.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in f.fx.speeds?f.fx.speeds[d.duration]:f.fx.speeds._default,d.old=d.complete,d.complete=function(a){f.isFunction(d.old)&&d.old.call(this),d.queue!==!1?f.dequeue(this):a!==!1&&f._unmark(this)};return d},easing:{linear:function(a,b,c,d){return c+d*a},swing:function(a,b,c,d){return(-Math.cos(a*Math.PI)/2+.5)*d+c}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig=b.orig||{}}}),f.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(f.fx.step[this.prop]||f.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=f.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,b,c){function g(a){return d.step(a)}var d=this,e=f.fx;this.startTime=cn||co(),this.start=a,this.end=b,this.unit=c||this.unit||(f.cssNumber[this.prop]?"":"px"),this.now=this.start,this.pos=this.state=0,g.elem=this.elem,g()&&f.timers.push(g)&&!cl&&(cl=setInterval(e.tick,e.interval))},show:function(){this.options.orig[this.prop]=f.style(this.elem,this.prop),this.options.show=!0,this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),f(this.elem).show()},hide:function(){this.options.orig[this.prop]=f.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b=cn||co(),c=!0,d=this.elem,e=this.options,g,h;if(a||b>=e.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),e.animatedProperties[this.prop]=!0;for(g in e.animatedProperties)e.animatedProperties[g]!==!0&&(c=!1);if(c){e.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){d.style["overflow"+b]=e.overflow[a]}),e.hide&&f(d).hide();if(e.hide||e.show)for(var i in e.animatedProperties)f.style(d,i,e.orig[i]);e.complete.call(d)}return!1}e.duration==Infinity?this.now=b:(h=b-this.startTime,this.state=h/e.duration,this.pos=f.easing[e.animatedProperties[this.prop]](this.state,h,0,1,e.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){for(var a=f.timers,b=0;b<a.length;++b)a[b]()||a.splice(b--,1);a.length||f.fx.stop()},interval:13,stop:function(){clearInterval(cl),cl=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){f.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit:a.elem[a.prop]=a.now}}}),f.expr&&f.expr.filters&&(f.expr.filters.animated=function(a){return f.grep(f.timers,function(b){return a===b.elem}).length});var cs=/^t(?:able|d|h)$/i,ct=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?f.fn.offset=function(a){var b=this[0],c;if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);try{c=b.getBoundingClientRect()}catch(d){}var e=b.ownerDocument,g=e.documentElement;if(!c||!f.contains(g,b))return c?{top:c.top,left:c.left}:{top:0,left:0};var h=e.body,i=cu(e),j=g.clientTop||h.clientTop||0,k=g.clientLeft||h.clientLeft||0,l=i.pageYOffset||f.support.boxModel&&g.scrollTop||h.scrollTop,m=i.pageXOffset||f.support.boxModel&&g.scrollLeft||h.scrollLeft,n=c.top+l-j,o=c.left+m-k;return{top:n,left:o}}:f.fn.offset=function(a){var b=this[0];if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);f.offset.initialize();var c,d=b.offsetParent,e=b,g=b.ownerDocument,h=g.documentElement,i=g.body,j=g.defaultView,k=j?j.getComputedStyle(b,null):b.currentStyle,l=b.offsetTop,m=b.offsetLeft;while((b=b.parentNode)&&b!==i&&b!==h){if(f.offset.supportsFixedPosition&&k.position==="fixed")break;c=j?j.getComputedStyle(b,null):b.currentStyle,l-=b.scrollTop,m-=b.scrollLeft,b===d&&(l+=b.offsetTop,m+=b.offsetLeft,f.offset.doesNotAddBorder&&(!f.offset.doesAddBorderForTableAndCells||!cs.test(b.nodeName))&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),e=d,d=b.offsetParent),f.offset.subtractsBorderForOverflowNotVisible&&c.overflow!=="visible"&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),k=c}if(k.position==="relative"||k.position==="static")l+=i.offsetTop,m+=i.offsetLeft;f.offset.supportsFixedPosition&&k.position==="fixed"&&(l+=Math.max(h.scrollTop,i.scrollTop),m+=Math.max(h.scrollLeft,i.scrollLeft));return{top:l,left:m}},f.offset={initialize:function(){var a=c.body,b=c.createElement("div"),d,e,g,h,i=parseFloat(f.css(a,"marginTop"))||0,j="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";f.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"}),b.innerHTML=j,a.insertBefore(b,a.firstChild),d=b.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,this.doesNotAddBorder=e.offsetTop!==5,this.doesAddBorderForTableAndCells=h.offsetTop===5,e.style.position="fixed",e.style.top="20px",this.supportsFixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",this.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i,a.removeChild(b),f.offset.initialize=f.noop},bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.offset.initialize(),f.offset.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=ct.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!ct.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cu(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cu(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a&&a.style?parseFloat(f.css(a,d,"padding")):null},f.fn["outer"+c]=function(a){var b=this[0];return b&&b.style?parseFloat(f.css(b,d,a?"margin":"border")):null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c],h=e.document.body;return e.document.compatMode==="CSS1Compat"&&g||h&&h["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var i=f.css(e,d),j=parseFloat(i);return f.isNaN(j)?i:j}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f})(window);
    \ No newline at end of file
    diff --git a/lib/jquery-1.7.1-min.js b/lib/jquery-1.7.1-min.js
    new file mode 100644
    index 000000000..198b3ff07
    --- /dev/null
    +++ b/lib/jquery-1.7.1-min.js
    @@ -0,0 +1,4 @@
    +/*! jQuery v1.7.1 jquery.com | jquery.org/license */
    +(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cv(a){if(!ck[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){cl||(cl=c.createElement("iframe"),cl.frameBorder=cl.width=cl.height=0),b.appendChild(cl);if(!cm||!cl.createElement)cm=(cl.contentWindow||cl.contentDocument).document,cm.write((c.compatMode==="CSS1Compat"?"<!doctype html>":"")+"<html><body>"),cm.close();d=cm.createElement(a),cm.body.appendChild(d),e=f.css(d,"display"),b.removeChild(cl)}ck[a]=e}return ck[a]}function cu(a,b){var c={};f.each(cq.concat.apply([],cq.slice(0,b)),function(){c[this]=a});return c}function ct(){cr=b}function cs(){setTimeout(ct,0);return cr=f.now()}function cj(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ci(){try{return new a.XMLHttpRequest}catch(b){}}function cc(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function cb(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function ca(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bE.test(a)?d(a,e):ca(a+"["+(typeof e=="object"||f.isArray(e)?b:"")+"]",e,c,d)});else if(!c&&b!=null&&typeof b=="object")for(var e in b)ca(a+"["+e+"]",b[e],c,d);else d(a,b)}function b_(a,c){var d,e,g=f.ajaxSettings.flatOptions||{};for(d in c)c[d]!==b&&((g[d]?a:e||(e={}))[d]=c[d]);e&&f.extend(!0,a,e)}function b$(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bT,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=b$(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=b$(a,c,d,e,"*",g));return l}function bZ(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bP),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bC(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=b==="width"?bx:by,g=0,h=e.length;if(d>0){if(c!=="border")for(;g<h;g++)c||(d-=parseFloat(f.css(a,"padding"+e[g]))||0),c==="margin"?d+=parseFloat(f.css(a,c+e[g]))||0:d-=parseFloat(f.css(a,"border"+e[g]+"Width"))||0;return d+"px"}d=bz(a,b,b);if(d<0||d==null)d=a.style[b]||0;d=parseFloat(d)||0;if(c)for(;g<h;g++)d+=parseFloat(f.css(a,"padding"+e[g]))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+e[g]+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+e[g]))||0);return d+"px"}function bp(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bf,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bo(a){var b=c.createElement("div");bh.appendChild(b),b.innerHTML=a.outerHTML;return b.firstChild}function bn(a){var b=(a.nodeName||"").toLowerCase();b==="input"?bm(a):b!=="script"&&typeof a.getElementsByTagName!="undefined"&&f.grep(a.getElementsByTagName("input"),bm)}function bm(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bl(a){return typeof a.getElementsByTagName!="undefined"?a.getElementsByTagName("*"):typeof a.querySelectorAll!="undefined"?a.querySelectorAll("*"):[]}function bk(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bj(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c,d,e,g=f._data(a),h=f._data(b,g),i=g.events;if(i){delete h.handle,h.events={};for(c in i)for(d=0,e=i[c].length;d<e;d++)f.event.add(b,c+(i[c][d].namespace?".":"")+i[c][d].namespace,i[c][d],i[c][d].data)}h.data&&(h.data=f.extend({},h.data))}}function bi(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function U(a){var b=V.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function T(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(O.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?parseFloat(d):j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c<d;c++)b[a[c]]=!0;return b}var c=a.document,d=a.navigator,e=a.location,f=function(){function J(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(J,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.1",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j<k;j++)if((a=arguments[j])!=null)for(c in a){d=i[c],f=a[c];if(i===f)continue;l&&f&&(e.isPlainObject(f)||(g=e.isArray(f)))?(g?(g=!1,h=d&&e.isArray(d)?d:[]):h=d&&e.isPlainObject(d)?d:{},i[c]=e.extend(l,h,f)):f!==b&&(i[c]=f)}return i},e.extend({noConflict:function(b){a.$===e&&(a.$=g),b&&a.jQuery===e&&(a.jQuery=f);return e},isReady:!1,readyWait:1,holdReady:function(a){a?e.readyWait++:e.ready(!0)},ready:function(a){if(a===!0&&!--e.readyWait||a!==!0&&!e.isReady){if(!c.body)return setTimeout(e.ready,1);e.isReady=!0;if(a!==!0&&--e.readyWait>0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a},trim:G?function(a){return a==null?"":G.call(a)}:function(a){return a==null?"":(a+"").replace(k,"").replace(l,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var d=e.type(a);a.length==null||d==="string"||d==="function"||d==="regexp"||e.isWindow(a)?E.call(c,a):e.merge(c,a)}return c},inArray:function(a,b,c){var d;if(b){if(H)return H.call(b,a,c);d=b.length,c=c?c<0?Math.max(0,d+c):c:0;for(;c<d;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length=="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)},guid:1,proxy:function(a,c){if(typeof c=="string"){var d=a[c];c=a,a=d}if(!e.isFunction(a))return b;var f=F.call(arguments,2),g=function(){return a.apply(c,f.concat(F.call(arguments)))};g.guid=a.guid=a.guid||g.guid||e.guid++;return g},access:function(a,c,d,f,g,h){var i=a.length;if(typeof c=="object"){for(var j in c)e.access(a,j,c[j],f,g,d);return a}if(d!==b){f=!h&&f&&e.isFunction(d);for(var k=0;k<i;k++)g(a[k],c,f?d.call(a[k],k,g(a[k],c)):d,h);return a}return i?g(a[0],c):b},now:function(){return(new Date).getTime()},uaMatch:function(a){a=a.toLowerCase();var b=r.exec(a)||s.exec(a)||t.exec(a)||a.indexOf("compatible")<0&&u.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}e.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function(d,f){f&&f instanceof e&&!(f instanceof a)&&(f=a(f));return e.fn.init.call(this,d,f,b)},a.fn.init.prototype=a.fn;var b=a(c);return a},browser:{}}),e.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){I["[object "+b+"]"]=b.toLowerCase()}),z=e.uaMatch(y),z.browser&&(e.browser[z.browser]=!0,e.browser.version=z.version),e.browser.webkit&&(e.browser.safari=!0),j.test(" ")&&(k=/^[\s\xA0]+/,l=/[\s\xA0]+$/),h=e(c),c.addEventListener?B=function(){c.removeEventListener("DOMContentLoaded",B,!1),e.ready()}:c.attachEvent&&(B=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",B),e.ready())});return e}(),g={};f.Callbacks=function(a){a=a?g[a]||h(a):{};var c=[],d=[],e,i,j,k,l,m=function(b){var d,e,g,h,i;for(d=0,e=b.length;d<e;d++)g=b[d],h=f.type(g),h==="array"?m(g):h==="function"&&(!a.unique||!o.has(g))&&c.push(g)},n=function(b,f){f=f||[],e=!a.memory||[b,f],i=!0,l=j||0,j=0,k=c.length;for(;c&&l<k;l++)if(c[l].apply(b,f)===!1&&a.stopOnFalse){e=!0;break}i=!1,c&&(a.once?e===!0?o.disable():c=[]:d&&d.length&&(e=d.shift(),o.fireWith(e[0],e[1])))},o={add:function(){if(c){var a=c.length;m(arguments),i?k=c.length:e&&e!==!0&&(j=a,n(e[0],e[1]))}return this},remove:function(){if(c){var b=arguments,d=0,e=b.length;for(;d<e;d++)for(var f=0;f<c.length;f++)if(b[d]===c[f]){i&&f<=k&&(k--,f<=l&&l--),c.splice(f--,1);if(a.unique)break}}return this},has:function(a){if(c){var b=0,d=c.length;for(;b<d;b++)if(a===c[b])return!0}return!1},empty:function(){c=[];return this},disable:function(){c=d=e=b;return this},disabled:function(){return!c},lock:function(){d=b,(!e||e===!0)&&o.disable();return this},locked:function(){return!d},fireWith:function(b,c){d&&(i?a.once||d.push([b,c]):(!a.once||!e)&&n(b,c));return this},fire:function(){o.fireWith(this,arguments);return this},fired:function(){return!!e}};return o};var i=[].slice;f.extend({Deferred:function(a){var b=f.Callbacks("once memory"),c=f.Callbacks("once memory"),d=f.Callbacks("memory"),e="pending",g={resolve:b,reject:c,notify:d},h={done:b.add,fail:c.add,progress:d.add,state:function(){return e},isResolved:b.fired,isRejected:c.fired,then:function(a,b,c){i.done(a).fail(b).progress(c);return this},always:function(){i.done.apply(i,arguments).fail.apply(i,arguments);return this},pipe:function(a,b,c){return f.Deferred(function(d){f.each({done:[a,"resolve"],fail:[b,"reject"],progress:[c,"notify"]},function(a,b){var c=b[0],e=b[1],g;f.isFunction(c)?i[a](function(){g=c.apply(this,arguments),g&&f.isFunction(g.promise)?g.promise().then(d.resolve,d.reject,d.notify):d[e+"With"](this===i?d:this,[g])}):i[a](d[e])})}).promise()},promise:function(a){if(a==null)a=h;else for(var b in h)a[b]=h[b];return a}},i=h.promise({}),j;for(j in g)i[j]=g[j].fire,i[j+"With"]=g[j].fireWith;i.done(function(){e="resolved"},c.disable,d.lock).fail(function(){e="rejected"},b.disable,d.lock),a&&a.call(i,i);return i},when:function(a){function m(a){return function(b){e[a]=arguments.length>1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c<d;c++)b[c]&&b[c].promise&&f.isFunction(b[c].promise)?b[c].promise().then(l(c),j.reject,m(c)):--g;g||j.resolveWith(j,b)}else j!==a&&j.resolveWith(j,d?[a]:[]);return k}}),f.support=function(){var b,d,e,g,h,i,j,k,l,m,n,o,p,q=c.createElement("div"),r=c.documentElement;q.setAttribute("className","t"),q.innerHTML="   <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>",d=q.getElementsByTagName("*"),e=q.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=q.getElementsByTagName("input")[0],b={leadingWhitespace:q.firstChild.nodeType===3,tbody:!q.getElementsByTagName("tbody").length,htmlSerialize:!!q.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:q.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav></:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete q.test}catch(s){b.deleteExpando=!1}!q.addEventListener&&q.attachEvent&&q.fireEvent&&(q.attachEvent("onclick",function(){b.noCloneEvent=!1}),q.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),q.appendChild(i),k=c.createDocumentFragment(),k.appendChild(q.lastChild),b.checkClone=k.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,k.removeChild(i),k.appendChild(q),q.innerHTML="",a.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",q.style.width="2px",q.appendChild(j),b.reliableMarginRight=(parseInt((a.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0);if(q.attachEvent)for(o in{submit:1,change:1,focusin:1})n="on"+o,p=n in q,p||(q.setAttribute(n,"return;"),p=typeof q[n]=="function"),b[o+"Bubbles"]=p;k.removeChild(q),k=g=h=j=q=i=null,f(function(){var a,d,e,g,h,i,j,k,m,n,o,r=c.getElementsByTagName("body")[0];!r||(j=1,k="position:absolute;top:0;left:0;width:1px;height:1px;margin:0;",m="visibility:hidden;border:0;",n="style='"+k+"border:5px solid #000;padding:0;'",o="<div "+n+"><div></div></div>"+"<table "+n+" cellpadding='0' cellspacing='0'>"+"<tr><td></td></tr></table>",a=c.createElement("div"),a.style.cssText=m+"width:0;height:0;position:static;top:0;margin-top:"+j+"px",r.insertBefore(a,r.firstChild),q=c.createElement("div"),a.appendChild(q),q.innerHTML="<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>",l=q.getElementsByTagName("td"),p=l[0].offsetHeight===0,l[0].style.display="",l[1].style.display="none",b.reliableHiddenOffsets=p&&l[0].offsetHeight===0,q.innerHTML="",q.style.width=q.style.paddingLeft="1px",f.boxModel=b.boxModel=q.offsetWidth===2,typeof q.style.zoom!="undefined"&&(q.style.display="inline",q.style.zoom=1,b.inlineBlockNeedsLayout=q.offsetWidth===2,q.style.display="",q.innerHTML="<div style='width:4px;'></div>",b.shrinkWrapBlocks=q.offsetWidth!==2),q.style.cssText=k+m,q.innerHTML=o,d=q.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,i={doesNotAddBorder:e.offsetTop!==5,doesAddBorderForTableAndCells:h.offsetTop===5},e.style.position="fixed",e.style.top="20px",i.fixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",i.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,i.doesNotIncludeMarginInBodyOffset=r.offsetTop!==j,r.removeChild(a),q=a=null,f.extend(b,i))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e<g;e++)delete d[b[e]];if(!(c?m:f.isEmptyObject)(d))return}}if(!c){delete j[k].data;if(!m(j[k]))return}f.support.deleteExpando||!j.setInterval?delete j[k]:j[k]=null,i&&(f.support.deleteExpando?delete a[h]:a.removeAttribute?a.removeAttribute(h):a[h]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d,e,g,h=null;if(typeof a=="undefined"){if(this.length){h=f.data(this[0]);if(this[0].nodeType===1&&!f._data(this[0],"parsedAttrs")){e=this[0].attributes;for(var i=0,j=e.length;i<j;i++)g=e[i].name,g.indexOf("data-")===0&&(g=f.camelCase(g.substring(5)),l(this[0],g,h[g]));f._data(this[0],"parsedAttrs",!0)}}return h}if(typeof a=="object")return this.each(function(){f.data(this,a)});d=a.split("."),d[1]=d[1]?"."+d[1]:"";if(c===b){h=this.triggerHandler("getData"+d[1]+"!",[d[0]]),h===b&&this.length&&(h=f.data(this[0],a),h=l(this[0],a,h));return h===b&&d[1]?this.data(d[0]):h}return this.each(function(){var b=f(this),e=[d[0],c];b.triggerHandler("setData"+d[1]+"!",e),f.data(this,a,c),b.triggerHandler("changeData"+d[1]+"!",e)})},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,b){a&&(b=(b||"fx")+"mark",f._data(a,b,(f._data(a,b)||0)+1))},_unmark:function(a,b,c){a!==!0&&(c=b,b=a,a=!1);if(b){c=c||"fx";var d=c+"mark",e=a?0:(f._data(b,d)||1)-1;e?f._data(b,d,e):(f.removeData(b,d,!0),n(b,c,"mark"))}},queue:function(a,b,c){var d;if(a){b=(b||"fx")+"queue",d=f._data(a,b),c&&(!d||f.isArray(c)?d=f._data(a,b,f.makeArray(c)):d.push(c));return d||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e={};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),f._data(a,b+".run",e),d.call(a,function(){f.dequeue(a,b)},e)),c.length||(f.removeData(a,b+"queue "+b+".run",!0),n(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){typeof a!="string"&&(c=a,a="fx");if(c===b)return f.queue(this[0],a);return this.each(function(){var b=f.queue(this,a,c);a==="fx"&&b[0]!=="inprogress"&&f.dequeue(this,a)})},dequeue:function(a){return this.each(function(){f.dequeue(this,a)})},delay:function(a,b){a=f.fx?f.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){function m(){--h||d.resolveWith(e,[e])}typeof a!="string"&&(c=a,a=b),a=a||"fx";var d=f.Deferred(),e=this,g=e.length,h=1,i=a+"defer",j=a+"queue",k=a+"mark",l;while(g--)if(l=f.data(e[g],i,b,!0)||(f.data(e[g],j,b,!0)||f.data(e[g],k,b,!0))&&f.data(e[g],i,f.Callbacks("once memory"),!0))h++,l.add(m);m();return d.promise()}});var o=/[\n\t\r]/g,p=/\s+/,q=/\r/g,r=/^(?:button|input)$/i,s=/^(?:button|input|object|select|textarea)$/i,t=/^a(?:rea)?$/i,u=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,v=f.support.getSetAttribute,w,x,y;f.fn.extend({attr:function(a,b){return f.access(this,a,b,!0,f.attr)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,a,b,!0,f.prop)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(p);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{g=" "+e.className+" ";for(h=0,i=b.length;h<i;h++)~g.indexOf(" "+b[h]+" ")||(g+=b[h]+" ");e.className=f.trim(g)}}}return this},removeClass:function(a){var c,d,e,g,h,i,j;if(f.isFunction(a))return this.each(function(b){f(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(p);for(d=0,e=this.length;d<e;d++){g=this[d];if(g.nodeType===1&&g.className)if(a){h=(" "+g.className+" ").replace(o," ");for(i=0,j=c.length;i<j;i++)h=h.replace(" "+c[i]+" "," ");g.className=f.trim(h)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";if(f.isFunction(a))return this.each(function(c){f(this).toggleClass(a.call(this,c,this.className,b),b)});return this.each(function(){if(c==="string"){var e,g=0,h=f(this),i=b,j=a.split(p);while(e=j[g++])i=d?i:!h.hasClass(e),h[i?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&f._data(this,"__className__",this.className),this.className=this.className||a===!1?"":f._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c<d;c++)if(this[c].nodeType===1&&(" "+this[c].className+" ").replace(o," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.nodeName.toLowerCase()]||f.valHooks[g.type];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c<d;c++){e=i[c];if(e.selected&&(f.support.optDisabled?!e.disabled:e.getAttribute("disabled")===null)&&(!e.parentNode.disabled||!f.nodeName(e.parentNode,"optgroup"))){b=f(e).val();if(j)return b;h.push(b)}}if(j&&!h.length&&i.length)return f(i[g]).val();return h},set:function(a,b){var c=f.makeArray(b);f(a).find("option").each(function(){this.selected=f.inArray(f(this).val(),c)>=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;h<g;h++)e=d[h],e&&(c=f.propFix[e]||e,f.attr(a,e,""),a.removeAttribute(v?e:c),u.test(e)&&c in a&&(a[c]=!1))}},attrHooks:{type:{set:function(a,b){if(r.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},value:{get:function(a,b){if(w&&f.nodeName(a,"button"))return w.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(w&&f.nodeName(a,"button"))return w.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e,g,h,i=a.nodeType;if(!!a&&i!==3&&i!==8&&i!==2){h=i!==1||!f.isXMLDoc(a),h&&(c=f.propFix[c]||c,g=f.propHooks[c]);return d!==b?g&&"set"in g&&(e=g.set(a,d,c))!==b?e:a[c]=d:g&&"get"in g&&(e=g.get(a,c))!==null?e:a[c]}},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):s.test(a.nodeName)||t.test(a.nodeName)&&a.href?0:b}}}}),f.attrHooks.tabindex=f.propHooks.tabIndex,x={get:function(a,c){var d,e=f.prop(a,c);return e===!0||typeof e!="boolean"&&(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},v||(y={name:!0,id:!0},w=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&(y[c]?d.nodeValue!=="":d.specified)?d.nodeValue:b},set:function(a,b,d){var e=a.getAttributeNode(d);e||(e=c.createAttribute(d),a.setAttributeNode(e));return e.nodeValue=b+""}},f.attrHooks.tabindex.set=w.set,f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})}),f.attrHooks.contenteditable={get:w.get,set:function(a,b,c){b===""&&(b="false"),w.set(a,b,c)}}),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex);return null}})),f.support.enctype||(f.propFix.enctype="encoding"),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/\bhover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function(a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")};
    +f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k<c.length;k++){l=A.exec(c[k])||[],m=l[1],n=(l[2]||"").split(".").sort(),s=f.event.special[m]||{},m=(g?s.delegateType:s.bindType)||m,s=f.event.special[m]||{},o=f.extend({type:m,origType:l[1],data:e,handler:d,guid:d.guid,selector:g,quick:G(g),namespace:n.join(".")},p),r=j[m];if(!r){r=j[m]=[],r.delegateCount=0;if(!s.setup||s.setup.call(a,e,n,i)===!1)a.addEventListener?a.addEventListener(m,i,!1):a.attachEvent&&a.attachEvent("on"+m,i)}s.add&&(s.add.call(a,o),o.handler.guid||(o.handler.guid=d.guid)),g?r.splice(r.delegateCount++,0,o):r.push(o),f.event.global[m]=!0}a=null}},global:{},remove:function(a,b,c,d,e){var g=f.hasData(a)&&f._data(a),h,i,j,k,l,m,n,o,p,q,r,s;if(!!g&&!!(o=g.events)){b=f.trim(I(b||"")).split(" ");for(h=0;h<b.length;h++){i=A.exec(b[h])||[],j=k=i[1],l=i[2];if(!j){for(j in o)f.event.remove(a,j+b[h],c,d,!0);continue}p=f.event.special[j]||{},j=(d?p.delegateType:p.bindType)||j,r=o[j]||[],m=r.length,l=l?new RegExp("(^|\\.)"+l.split(".").sort().join("\\.(?:.*\\.)?")+"(\\.|$)"):null;for(n=0;n<r.length;n++)s=r[n],(e||k===s.origType)&&(!c||c.guid===s.guid)&&(!l||l.test(s.namespace))&&(!d||d===s.selector||d==="**"&&s.selector)&&(r.splice(n--,1),s.selector&&r.delegateCount--,p.remove&&p.remove.call(a,s));r.length===0&&m!==r.length&&((!p.teardown||p.teardown.call(a,l)===!1)&&f.removeEvent(a,j,g.handle),delete o[j])}f.isEmptyObject(o)&&(q=g.handle,q&&(q.elem=null),f.removeData(a,["events","handle"],!0))}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){if(!e||e.nodeType!==3&&e.nodeType!==8){var h=c.type||c,i=[],j,k,l,m,n,o,p,q,r,s;if(E.test(h+f.event.triggered))return;h.indexOf("!")>=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;l<r.length&&!c.isPropagationStopped();l++)m=r[l][0],c.type=r[l][1],q=(f._data(m,"events")||{})[c.type]&&f._data(m,"handle"),q&&q.apply(m,d),q=o&&m[o],q&&f.acceptData(m)&&q.apply(m,d)===!1&&c.preventDefault();c.type=h,!g&&!c.isDefaultPrevented()&&(!p._default||p._default.apply(e.ownerDocument,d)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)&&o&&e[h]&&(h!=="focus"&&h!=="blur"||c.target.offsetWidth!==0)&&!f.isWindow(e)&&(n=e[o],n&&(e[o]=null),f.event.triggered=h,e[h](),f.event.triggered=b,n&&(e[o]=n));return c.result}},dispatch:function(c){c=f.event.fix(c||a.event);var d=(f._data(this,"events")||{})[c.type]||[],e=d.delegateCount,g=[].slice.call(arguments,0),h=!c.exclusive&&!c.namespace,i=[],j,k,l,m,n,o,p,q,r,s,t;g[0]=c,c.delegateTarget=this;if(e&&!c.target.disabled&&(!c.button||c.type!=="click")){m=f(this),m.context=this.ownerDocument||this;for(l=c.target;l!=this;l=l.parentNode||this){o={},q=[],m[0]=l;for(j=0;j<e;j++)r=d[j],s=r.selector,o[s]===b&&(o[s]=r.quick?H(l,r.quick):m.is(s)),o[s]&&q.push(r);q.length&&i.push({elem:l,matches:q})}}d.length>e&&i.push({elem:this,matches:d.slice(e)});for(j=0;j<i.length&&!c.isPropagationStopped();j++){p=i[j],c.currentTarget=p.elem;for(k=0;k<p.matches.length&&!c.isImmediatePropagationStopped();k++){r=p.matches[k];if(h||!c.namespace&&!r.namespace||c.namespace_re&&c.namespace_re.test(r.namespace))c.data=r.data,c.handleObj=r,n=((f.event.special[r.origType]||{}).handle||r.handler).apply(p.elem,g),n!==b&&(c.result=n,n===!1&&(c.preventDefault(),c.stopPropagation()))}}return c.result},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){a.which==null&&(a.which=b.charCode!=null?b.charCode:b.keyCode);return a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,d){var e,f,g,h=d.button,i=d.fromElement;a.pageX==null&&d.clientX!=null&&(e=a.target.ownerDocument||c,f=e.documentElement,g=e.body,a.pageX=d.clientX+(f&&f.scrollLeft||g&&g.scrollLeft||0)-(f&&f.clientLeft||g&&g.clientLeft||0),a.pageY=d.clientY+(f&&f.scrollTop||g&&g.scrollTop||0)-(f&&f.clientTop||g&&g.clientTop||0)),!a.relatedTarget&&i&&(a.relatedTarget=i===a.target?d.toElement:i),!a.which&&h!==b&&(a.which=h&1?1:h&2?3:h&4?2:0);return a}},fix:function(a){if(a[f.expando])return a;var d,e,g=a,h=f.event.fixHooks[a.type]||{},i=h.props?this.props.concat(h.props):this.props;a=f.Event(g);for(d=i.length;d;)e=i[--d],a[e]=g[e];a.target||(a.target=g.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),a.metaKey===b&&(a.metaKey=a.ctrlKey);return h.filter?h.filter(a,g):a},special:{ready:{setup:f.bindReady},load:{noBubble:!0},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}},simulate:function(a,b,c,d){var e=f.extend(new f.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?f.event.trigger(e,null,b):f.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},f.event.handle=f.event.dispatch,f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(!(this instanceof f.Event))return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?K:J):this.type=a,b&&f.extend(this,b),this.timeStamp=a&&a.timeStamp||f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=K;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=K;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=K,this.stopPropagation()},isDefaultPrevented:J,isPropagationStopped:J,isImmediatePropagationStopped:J},f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c=this,d=a.relatedTarget,e=a.handleObj,g=e.selector,h;if(!d||d!==c&&!f.contains(c,d))a.type=e.origType,h=e.handler.apply(this,arguments),a.type=b;return h}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(){if(f.nodeName(this,"form"))return!1;f.event.add(this,"click._submit keypress._submit",function(a){var c=a.target,d=f.nodeName(c,"input")||f.nodeName(c,"button")?c.form:b;d&&!d._submit_attached&&(f.event.add(d,"submit._submit",function(a){this.parentNode&&!a.isTrigger&&f.event.simulate("submit",this.parentNode,a,!0)}),d._submit_attached=!0)})},teardown:function(){if(f.nodeName(this,"form"))return!1;f.event.remove(this,"._submit")}}),f.support.changeBubbles||(f.event.special.change={setup:function(){if(z.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")f.event.add(this,"propertychange._change",function(a){a.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),f.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1,f.event.simulate("change",this,a,!0))});return!1}f.event.add(this,"beforeactivate._change",function(a){var b=a.target;z.test(b.nodeName)&&!b._change_attached&&(f.event.add(b,"change._change",function(a){this.parentNode&&!a.isSimulated&&!a.isTrigger&&f.event.simulate("change",this.parentNode,a,!0)}),b._change_attached=!0)})},handle:function(a){var b=a.target;if(this!==b||a.isSimulated||a.isTrigger||b.type!=="radio"&&b.type!=="checkbox")return a.handleObj.handler.apply(this,arguments)},teardown:function(){f.event.remove(this,"._change");return z.test(this.nodeName)}}),f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){var d=0,e=function(a){f.event.simulate(b,a.target,f.event.fix(a),!0)};f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.fn.extend({on:function(a,c,d,e,g){var h,i;if(typeof a=="object"){typeof c!="string"&&(d=c,c=b);for(i in a)this.on(i,c,d,a[i],g);return this}d==null&&e==null?(e=c,d=c=b):e==null&&(typeof c=="string"?(e=d,d=b):(e=d,d=c,c=b));if(e===!1)e=J;else if(!e)return this;g===1&&(h=e,e=function(a){f().off(a);return h.apply(this,arguments)},e.guid=h.guid||(h.guid=f.guid++));return this.each(function(){f.event.add(this,a,e,d,c)})},one:function(a,b,c,d){return this.on.call(this,a,b,c,d,1)},off:function(a,c,d){if(a&&a.preventDefault&&a.handleObj){var e=a.handleObj;f(a.delegateTarget).off(e.namespace?e.type+"."+e.namespace:e.type,e.selector,e.handler);return this}if(typeof a=="object"){for(var g in a)this.off(g,c,a[g]);return this}if(c===!1||typeof c=="function")d=c,c=b;d===!1&&(d=J);return this.each(function(){f.event.remove(this,a,d,c)})},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},live:function(a,b,c){f(this.context).on(a,this.selector,b,c);return this},die:function(a,b){f(this.context).off(a,this.selector||"**",b);return this},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return arguments.length==1?this.off(a,"**"):this.off(b,a,c)},trigger:function(a,b){return this.each(function(){f.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return f.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||f.guid++,d=0,e=function(c){var e=(f._data(this,"lastToggle"+a.guid)||0)%d;f._data(this,"lastToggle"+a.guid,e+1),c.preventDefault();return b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),f.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){f.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}if(j.nodeType===1){g||(j[d]=c,j.sizset=h);if(typeof b!="string"){if(j===b){k=!0;break}}else if(m.filter(b,[j]).length>0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}j.nodeType===1&&!g&&(j[d]=c,j.sizset=h);if(j.nodeName.toLowerCase()===b){k=j;break}j=j[a]}e[h]=k}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},m.matches=function(a,b){return m(a,null,null,b)},m.matchesSelector=function(a,b){return m(b,null,null,[a]).length>0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e<f;e++){h=o.order[e];if(g=o.leftMatch[h].exec(a)){i=g[1],g.splice(1,1);if(i.substr(i.length-1)!=="\\"){g[1]=(g[1]||"").replace(j,""),d=o.find[h](g,b,c);if(d!=null){a=a.replace(o.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},m.filter=function(a,c,d,e){var f,g,h,i,j,k,l,n,p,q=a,r=[],s=c,t=c&&c[0]&&m.isXML(c[0]);while(a&&c.length){for(h in o.filter)if((f=o.leftMatch[h].exec(a))!=null&&f[2]){k=o.filter[h],l=f[1],g=!1,f.splice(1,1);if(l.substr(l.length-1)==="\\")continue;s===r&&(r=[]);if(o.preFilter[h]){f=o.preFilter[h](f,s,d,r,e,t);if(!f)g=i=!0;else if(f===!0)continue}if(f)for(n=0;(j=s[n])!=null;n++)j&&(i=k(j,f,n,s),p=e^i,d&&i!=null?p?g=!0:s[n]=!1:p&&(r.push(j),g=!0));if(i!==b){d||(s=r),a=a.replace(o.match[h],"");if(!g)return[];break}}if(a===q)if(g==null)m.error(a);else break;q=a}return s},m.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)};var n=m.getText=function(a){var b,c,d=a.nodeType,e="";if(d){if(d===1||d===9){if(typeof a.textContent=="string")return a.textContent;if(typeof a.innerText=="string")return a.innerText.replace(k,"");for(a=a.firstChild;a;a=a.nextSibling)e+=n(a)}else if(d===3||d===4)return a.nodeValue}else for(b=0;c=a[b];b++)c.nodeType!==8&&(e+=n(c));return e},o=m.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b=="string",d=c&&!l.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1);a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&m.filter(b,a,!0)},">":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e<f;e++){c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&m.filter(b,a,!0)}},"":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("parentNode",b,f,a,d,c)},"~":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("previousSibling",b,f,a,d,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(j,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&(" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return b<c[3]-0},gt:function(a,b,c){return b>c[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h<i;h++)if(g[h]===a)return!1;return!0}m.error(e)},CHILD:function(a,b){var c,e,f,g,h,i,j,k=b[1],l=a;switch(k){case"only":case"first":while(l=l.previousSibling)if(l.nodeType===1)return!1;if(k==="first")return!0;l=a;case"last":while(l=l.nextSibling)if(l.nodeType===1)return!1;return!0;case"nth":c=b[2],e=b[3];if(c===1&&e===0)return!0;f=b[0],g=a.parentNode;if(g&&(g[d]!==f||!a.nodeIndex)){i=0;for(l=g.firstChild;l;l=l.nextSibling)l.nodeType===1&&(l.nodeIndex=++i);g[d]=f}j=a.nodeIndex-e;return c===0?j===0:j%c===0&&j/c>=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c<e;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var u,v;c.documentElement.compareDocumentPosition?u=function(a,b){if(a===b){h=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compareDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(u=function(a,b){if(a===b){h=!0;return 0}if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],g=a.parentNode,i=b.parentNode,j=g;if(g===i)return v(a,b);if(!g)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return v(e[k],f[k]);return k===c?v(a,f[k],-1):v(e[k],b,1)},v=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="<p class='TEST'></p>";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="<div class='test e'></div><div class='test'></div>";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h<i;h++)m(a,g[h],e,c);return m.filter(f,e)};m.attr=f.attr,m.selectors.attrMap={},f.find=m,f.expr=m.selectors,f.expr[":"]=f.expr.filters,f.unique=m.uniqueSort,f.text=m.getText,f.isXMLDoc=m.isXML,f.contains=m.contains}();var L=/Until$/,M=/^(?:parents|prevUntil|prevAll)/,N=/,/,O=/^.[^:#\[\.,]*$/,P=Array.prototype.slice,Q=f.expr.match.POS,R={children:!0,contents:!0,next:!0,prev:!0};f.fn.extend({find:function(a){var b=this,c,d;if(typeof a!="string")return f(a).filter(function(){for(c=0,d=b.length;c<d;c++)if(f.contains(b[c],this))return!0});var e=this.pushStack("","find",a),g,h,i;for(c=0,d=this.length;c<d;c++){g=e.length,f.find(a,this[c],e);if(c>0)for(h=g;h<e.length;h++)for(i=0;i<g;i++)if(e[i]===e[h]){e.splice(h--,1);break}}return e},has:function(a){var b=f(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(f.contains(this,b[a]))return!0})},not:function(a){return this.pushStack(T(this,a,!1),"not",a)},filter:function(a){return this.pushStack(T(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?Q.test(a)?f(a,this.context).index(this[0])>=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d<a.length;d++)f(g).is(a[d])&&c.push({selector:a[d],elem:g,level:h});g=g.parentNode,h++}return c}var i=Q.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d<e;d++){g=this[d];while(g){if(i?i.index(g)>-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/<tbody/i,_=/<|&#?\w+;/,ba=/<(?:script|style)/i,bb=/<(?:script|object|embed|option|style)/i,bc=new RegExp("<(?:"+V+")","i"),bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*<!(?:\[CDATA\[|\-\-)/,bg={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div<div>","</div>"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function()
    +{for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1></$2>");try{for(var c=0,d=this.length;c<d;c++)this[c].nodeType===1&&(f.cleanData(this[c].getElementsByTagName("*")),this[c].innerHTML=a)}catch(e){this.empty().append(a)}}else f.isFunction(a)?this.each(function(b){var c=f(this);c.html(a.call(this,b,c.html()))}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(f.isFunction(a))return this.each(function(b){var c=f(this),d=c.html();c.replaceWith(a.call(this,b,d))});typeof a!="string"&&(a=f(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;f(this).remove(),b?f(b).before(a):f(c).append(a)})}return this.length?this.pushStack(f(f.isFunction(a)?a():a),"replaceWith",a):this},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){var e,g,h,i,j=a[0],k=[];if(!f.support.checkClone&&arguments.length===3&&typeof j=="string"&&bd.test(j))return this.each(function(){f(this).domManip(a,c,d,!0)});if(f.isFunction(j))return this.each(function(e){var g=f(this);a[0]=j.call(this,e,c?g.html():b),g.domManip(a,c,d)});if(this[0]){i=j&&j.parentNode,f.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?e={fragment:i}:e=f.buildFragment(a,this,k),h=e.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&f.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)d.call(c?bi(this[l],g):this[l],e.cacheable||m>1&&l<n?f.clone(h,!0,!0):h)}k.length&&f.each(k,bp)}return this}}),f.buildFragment=function(a,b,d){var e,g,h,i,j=a[0];b&&b[0]&&(i=b[0].ownerDocument||b[0]),i.createDocumentFragment||(i=c),a.length===1&&typeof j=="string"&&j.length<512&&i===c&&j.charAt(0)==="<"&&!bb.test(j)&&(f.support.checkClone||!bd.test(j))&&(f.support.html5Clone||!bc.test(j))&&(g=!0,h=f.fragments[j],h&&h!==1&&(e=h)),e||(e=i.createDocumentFragment(),f.clean(a,i,e,d)),g&&(f.fragments[j]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||!bc.test("<"+a.nodeName)?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!_.test(k))k=b.createTextNode(k);else{k=k.replace(Y,"<$1></$2>");var l=(Z.exec(k)||["",""])[1].toLowerCase(),m=bg[l]||bg._default,n=m[0],o=b.createElement("div");b===c?bh.appendChild(o):U(b).appendChild(o),o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=$.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]==="<table>"&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&X.test(k)&&o.insertBefore(b.createTextNode(X.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i<r;i++)bn(k[i]);else bn(k);k.nodeType?h.push(k):h=f.merge(h,k)}if(d){g=function(a){return!a.type||be.test(a.type)};for(j=0;h[j];j++)if(e&&f.nodeName(h[j],"script")&&(!h[j].type||h[j].type.toLowerCase()==="text/javascript"))e.push(h[j].parentNode?h[j].parentNode.removeChild(h[j]):h[j]);else{if(h[j].nodeType===1){var s=f.grep(h[j].getElementsByTagName("script"),g);h.splice.apply(h,[j+1,0].concat(s))}d.appendChild(h[j])}}return h},cleanData:function(a){var b,c,d=f.cache,e=f.event.special,g=f.support.deleteExpando;for(var h=0,i;(i=a[h])!=null;h++){if(i.nodeName&&f.noData[i.nodeName.toLowerCase()])continue;c=i[f.expando];if(c){b=d[c];if(b&&b.events){for(var j in b.events)e[j]?f.event.remove(i,j):f.removeEvent(i,j,b.handle);b.handle&&(b.handle.elem=null)}g?delete i[f.expando]:i.removeAttribute&&i.removeAttribute(f.expando),delete d[c]}}}});var bq=/alpha\([^)]*\)/i,br=/opacity=([^)]*)/,bs=/([A-Z]|^ms)/g,bt=/^-?\d+(?:px)?$/i,bu=/^-?\d/,bv=/^([\-+])=([\-+.\de]+)/,bw={position:"absolute",visibility:"hidden",display:"block"},bx=["Left","Right"],by=["Top","Bottom"],bz,bA,bB;f.fn.css=function(a,c){if(arguments.length===2&&c===b)return this;return f.access(this,a,c,!0,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)})},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bz(a,"opacity","opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d,h==="string"&&(g=bv.exec(d))&&(d=+(g[1]+1)*+g[2]+parseFloat(f.css(a,c)),h="number");if(d==null||h==="number"&&isNaN(d))return;h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(bz)return bz(a,c)},swap:function(a,b,c){var d={};for(var e in b)d[e]=a.style[e],a.style[e]=b[e];c.call(a);for(e in b)a.style[e]=d[e]}}),f.curCSS=f.css,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){var e;if(c){if(a.offsetWidth!==0)return bC(a,b,d);f.swap(a,bw,function(){e=bC(a,b,d)});return e}},set:function(a,b){if(!bt.test(b))return b;b=parseFloat(b);if(b>=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return br.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bq,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bq.test(g)?g.replace(bq,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bz(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bA=function(a,b){var c,d,e;b=b.replace(bs,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b)));return c}),c.documentElement.currentStyle&&(bB=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f===null&&g&&(e=g[b])&&(f=e),!bt.test(f)&&bu.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f||0,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),bz=bA||bB,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bD=/%20/g,bE=/\[\]$/,bF=/\r?\n/g,bG=/#.*$/,bH=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bI=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bJ=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bK=/^(?:GET|HEAD)$/,bL=/^\/\//,bM=/\?/,bN=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bO=/^(?:select|textarea)/i,bP=/\s+/,bQ=/([?&])_=[^&]*/,bR=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bS=f.fn.load,bT={},bU={},bV,bW,bX=["*/"]+["*"];try{bV=e.href}catch(bY){bV=c.createElement("a"),bV.href="",bV=bV.href}bW=bR.exec(bV.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bS)return bS.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("<div>").append(c.replace(bN,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bO.test(this.nodeName)||bI.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bF,"\r\n")}}):{name:b.name,value:c.replace(bF,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b_(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b_(a,b);return a},ajaxSettings:{url:bV,isLocal:bJ.test(bW[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bX},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bZ(bT),ajaxTransport:bZ(bU),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?cb(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cc(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bH.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bG,"").replace(bL,bW[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bP),d.crossDomain==null&&(r=bR.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bW[1]&&r[2]==bW[2]&&(r[3]||(r[1]==="http:"?80:443))==(bW[3]||(bW[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),b$(bT,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bK.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bM.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bQ,"$1_="+x);d.url=y+(y===d.url?(bM.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bX+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=b$(bU,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)ca(g,a[g],c,e);return d.join("&").replace(bD,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cd=f.now(),ce=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cd++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ce.test(b.url)||e&&ce.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ce,l),b.url===j&&(e&&(k=k.replace(ce,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cf=a.ActiveXObject?function(){for(var a in ch)ch[a](0,1)}:!1,cg=0,ch;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ci()||cj()}:ci,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cf&&delete ch[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cg,cf&&(ch||(ch={},f(a).unload(cf)),ch[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var ck={},cl,cm,cn=/^(?:toggle|show|hide)$/,co=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cp,cq=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cr;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cu("show",3),a,b,c);for(var g=0,h=this.length;g<h;g++)d=this[g],d.style&&(e=d.style.display,!f._data(d,"olddisplay")&&e==="none"&&(e=d.style.display=""),e===""&&f.css(d,"display")==="none"&&f._data(d,"olddisplay",cv(d.nodeName)));for(g=0;g<h;g++){d=this[g];if(d.style){e=d.style.display;if(e===""||e==="none")d.style.display=f._data(d,"olddisplay")||""}}return this},hide:function(a,b,c){if(a||a===0)return this.animate(cu("hide",3),a,b,c);var d,e,g=0,h=this.length;for(;g<h;g++)d=this[g],d.style&&(e=f.css(d,"display"),e!=="none"&&!f._data(d,"olddisplay")&&f._data(d,"olddisplay",e));for(g=0;g<h;g++)this[g].style&&(this[g].style.display="none");return this},_toggle:f.fn.toggle,toggle:function(a,b,c){var d=typeof a=="boolean";f.isFunction(a)&&f.isFunction(b)?this._toggle.apply(this,arguments):a==null||d?this.each(function(){var b=d?a:f(this).is(":hidden");f(this)[b?"show":"hide"]()}):this.animate(cu("toggle",3),a,b,c);return this},fadeTo:function(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){function g(){e.queue===!1&&f._mark(this);var b=f.extend({},e),c=this.nodeType===1,d=c&&f(this).is(":hidden"),g,h,i,j,k,l,m,n,o;b.animatedProperties={};for(i in a){g=f.camelCase(i),i!==g&&(a[g]=a[i],delete a[i]),h=a[g],f.isArray(h)?(b.animatedProperties[g]=h[1],h=a[g]=h[0]):b.animatedProperties[g]=b.specialEasing&&b.specialEasing[g]||b.easing||"swing";if(h==="hide"&&d||h==="show"&&!d)return b.complete.call(this);c&&(g==="height"||g==="width")&&(b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY],f.css(this,"display")==="inline"&&f.css(this,"float")==="none"&&(!f.support.inlineBlockNeedsLayout||cv(this.nodeName)==="inline"?this.style.display="inline-block":this.style.zoom=1))}b.overflow!=null&&(this.style.overflow="hidden");for(i in a)j=new f.fx(this,b,i),h=a[i],cn.test(h)?(o=f._data(this,"toggle"+i)||(h==="toggle"?d?"show":"hide":0),o?(f._data(this,"toggle"+i,o==="show"?"hide":"show"),j[o]()):j[h]()):(k=co.exec(h),l=j.cur(),k?(m=parseFloat(k[2]),n=k[3]||(f.cssNumber[i]?"":"px"),n!=="px"&&(f.style(this,i,(m||1)+n),l=(m||1)/j.cur()*l,f.style(this,i,l+n)),k[1]&&(m=(k[1]==="-="?-1:1)*m+l),j.custom(l,m,n)):j.custom(l,h,""));return!0}var e=f.speed(b,c,d);if(f.isEmptyObject(a))return this.each(e.complete,[!1]);a=f.extend({},a);return e.queue===!1?this.each(g):this.queue(e.queue,g)},stop:function(a,c,d){typeof a!="string"&&(d=c,c=a,a=b),c&&a!==!1&&this.queue(a||"fx",[]);return this.each(function(){function h(a,b,c){var e=b[c];f.removeData(a,c,!0),e.stop(d)}var b,c=!1,e=f.timers,g=f._data(this);d||f._unmark(!0,this);if(a==null)for(b in g)g[b]&&g[b].stop&&b.indexOf(".run")===b.length-4&&h(this,g,b);else g[b=a+".run"]&&g[b].stop&&h(this,g,b);for(b=e.length;b--;)e[b].elem===this&&(a==null||e[b].queue===a)&&(d?e[b](!0):e[b].saveState(),c=!0,e.splice(b,1));(!d||!c)&&f.dequeue(this,a)})}}),f.each({slideDown:cu("show",1),slideUp:cu("hide",1),slideToggle:cu("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){f.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),f.extend({speed:function(a,b,c){var d=a&&typeof a=="object"?f.extend({},a):{complete:c||!c&&b||f.isFunction(a)&&a,duration:a,easing:c&&b||b&&!f.isFunction(b)&&b};d.duration=f.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in f.fx.speeds?f.fx.speeds[d.duration]:f.fx.speeds._default;if(d.queue==null||d.queue===!0)d.queue="fx";d.old=d.complete,d.complete=function(a){f.isFunction(d.old)&&d.old.call(this),d.queue?f.dequeue(this,d.queue):a!==!1&&f._unmark(this)};return d},easing:{linear:function(a,b,c,d){return c+d*a},swing:function(a,b,c,d){return(-Math.cos(a*Math.PI)/2+.5)*d+c}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig=b.orig||{}}}),f.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(f.fx.step[this.prop]||f.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=f.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,c,d){function h(a){return e.step(a)}var e=this,g=f.fx;this.startTime=cr||cs(),this.end=c,this.now=this.start=a,this.pos=this.state=0,this.unit=d||this.unit||(f.cssNumber[this.prop]?"":"px"),h.queue=this.options.queue,h.elem=this.elem,h.saveState=function(){e.options.hide&&f._data(e.elem,"fxshow"+e.prop)===b&&f._data(e.elem,"fxshow"+e.prop,e.start)},h()&&f.timers.push(h)&&!cp&&(cp=setInterval(g.tick,g.interval))},show:function(){var a=f._data(this.elem,"fxshow"+this.prop);this.options.orig[this.prop]=a||f.style(this.elem,this.prop),this.options.show=!0,a!==b?this.custom(this.cur(),a):this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),f(this.elem).show()},hide:function(){this.options.orig[this.prop]=f._data(this.elem,"fxshow"+this.prop)||f.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b,c,d,e=cr||cs(),g=!0,h=this.elem,i=this.options;if(a||e>=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c<b.length;c++)a=b[c],!a()&&b[c]===a&&b.splice(c--,1);b.length||f.fx.stop()},interval:13,stop:function(){clearInterval(cp),cp=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){f.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=a.now+a.unit:a.elem[a.prop]=a.now}}}),f.each(["width","height"],function(a,b){f.fx.step[b]=function(a){f.style(a.elem,b,Math.max(0,a.now)+a.unit)}}),f.expr&&f.expr.filters&&(f.expr.filters.animated=function(a){return f.grep(f.timers,function(b){return a===b.elem}).length});var cw=/^t(?:able|d|h)$/i,cx=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?f.fn.offset=function(a){var b=this[0],c;if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);try{c=b.getBoundingClientRect()}catch(d){}var e=b.ownerDocument,g=e.documentElement;if(!c||!f.contains(g,b))return c?{top:c.top,left:c.left}:{top:0,left:0};var h=e.body,i=cy(e),j=g.clientTop||h.clientTop||0,k=g.clientLeft||h.clientLeft||0,l=i.pageYOffset||f.support.boxModel&&g.scrollTop||h.scrollTop,m=i.pageXOffset||f.support.boxModel&&g.scrollLeft||h.scrollLeft,n=c.top+l-j,o=c.left+m-k;return{top:n,left:o}}:f.fn.offset=function(a){var b=this[0];if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);var c,d=b.offsetParent,e=b,g=b.ownerDocument,h=g.documentElement,i=g.body,j=g.defaultView,k=j?j.getComputedStyle(b,null):b.currentStyle,l=b.offsetTop,m=b.offsetLeft;while((b=b.parentNode)&&b!==i&&b!==h){if(f.support.fixedPosition&&k.position==="fixed")break;c=j?j.getComputedStyle(b,null):b.currentStyle,l-=b.scrollTop,m-=b.scrollLeft,b===d&&(l+=b.offsetTop,m+=b.offsetLeft,f.support.doesNotAddBorder&&(!f.support.doesAddBorderForTableAndCells||!cw.test(b.nodeName))&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),e=d,d=b.offsetParent),f.support.subtractsBorderForOverflowNotVisible&&c.overflow!=="visible"&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),k=c}if(k.position==="relative"||k.position==="static")l+=i.offsetTop,m+=i.offsetLeft;f.support.fixedPosition&&k.position==="fixed"&&(l+=Math.max(h.scrollTop,i.scrollTop),m+=Math.max(h.scrollLeft,i.scrollLeft));return{top:l,left:m}},f.offset={bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.support.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cy(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cy(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,d,"padding")):this[d]():null},f.fn["outer"+c]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,d,a?"margin":"border")):this[d]():null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c],h=e.document.body;return e.document.compatMode==="CSS1Compat"&&g||h&&h["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var i=f.css(e,d),j=parseFloat(i);return f.isNumeric(j)?j:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window);
    \ No newline at end of file
    diff --git a/lib/jquery-1.8.1-min.js b/lib/jquery-1.8.1-min.js
    new file mode 100644
    index 000000000..e7f2a292b
    --- /dev/null
    +++ b/lib/jquery-1.8.1-min.js
    @@ -0,0 +1,2 @@
    +/*! jQuery v@1.8.1 jquery.com | jquery.org/license */
    +(function(a,b){function G(a){var b=F[a]={};return p.each(a.split(s),function(a,c){b[c]=!0}),b}function J(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(I,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:+d+""===d?+d:H.test(d)?p.parseJSON(d):d}catch(f){}p.data(a,c,d)}else d=b}return d}function K(a){var b;for(b in a){if(b==="data"&&p.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function ba(){return!1}function bb(){return!0}function bh(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function bi(a,b){do a=a[b];while(a&&a.nodeType!==1);return a}function bj(a,b,c){b=b||0;if(p.isFunction(b))return p.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return p.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=p.grep(a,function(a){return a.nodeType===1});if(be.test(b))return p.filter(b,d,!c);b=p.filter(b,d)}return p.grep(a,function(a,d){return p.inArray(a,b)>=0===c})}function bk(a){var b=bl.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function bC(a,b){return a.getElementsByTagName(b)[0]||a.appendChild(a.ownerDocument.createElement(b))}function bD(a,b){if(b.nodeType!==1||!p.hasData(a))return;var c,d,e,f=p._data(a),g=p._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;d<e;d++)p.event.add(b,c,h[c][d])}g.data&&(g.data=p.extend({},g.data))}function bE(a,b){var c;if(b.nodeType!==1)return;b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase(),c==="object"?(b.parentNode&&(b.outerHTML=a.outerHTML),p.support.html5Clone&&a.innerHTML&&!p.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):c==="input"&&bv.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):c==="option"?b.selected=a.defaultSelected:c==="input"||c==="textarea"?b.defaultValue=a.defaultValue:c==="script"&&b.text!==a.text&&(b.text=a.text),b.removeAttribute(p.expando)}function bF(a){return typeof a.getElementsByTagName!="undefined"?a.getElementsByTagName("*"):typeof a.querySelectorAll!="undefined"?a.querySelectorAll("*"):[]}function bG(a){bv.test(a.type)&&(a.defaultChecked=a.checked)}function bY(a,b){if(b in a)return b;var c=b.charAt(0).toUpperCase()+b.slice(1),d=b,e=bW.length;while(e--){b=bW[e]+c;if(b in a)return b}return d}function bZ(a,b){return a=b||a,p.css(a,"display")==="none"||!p.contains(a.ownerDocument,a)}function b$(a,b){var c,d,e=[],f=0,g=a.length;for(;f<g;f++){c=a[f];if(!c.style)continue;e[f]=p._data(c,"olddisplay"),b?(!e[f]&&c.style.display==="none"&&(c.style.display=""),c.style.display===""&&bZ(c)&&(e[f]=p._data(c,"olddisplay",cc(c.nodeName)))):(d=bH(c,"display"),!e[f]&&d!=="none"&&p._data(c,"olddisplay",d))}for(f=0;f<g;f++){c=a[f];if(!c.style)continue;if(!b||c.style.display==="none"||c.style.display==="")c.style.display=b?e[f]||"":"none"}return a}function b_(a,b,c){var d=bP.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||"px"):b}function ca(a,b,c,d){var e=c===(d?"border":"content")?4:b==="width"?1:0,f=0;for(;e<4;e+=2)c==="margin"&&(f+=p.css(a,c+bV[e],!0)),d?(c==="content"&&(f-=parseFloat(bH(a,"padding"+bV[e]))||0),c!=="margin"&&(f-=parseFloat(bH(a,"border"+bV[e]+"Width"))||0)):(f+=parseFloat(bH(a,"padding"+bV[e]))||0,c!=="padding"&&(f+=parseFloat(bH(a,"border"+bV[e]+"Width"))||0));return f}function cb(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=!0,f=p.support.boxSizing&&p.css(a,"boxSizing")==="border-box";if(d<=0||d==null){d=bH(a,b);if(d<0||d==null)d=a.style[b];if(bQ.test(d))return d;e=f&&(p.support.boxSizingReliable||d===a.style[b]),d=parseFloat(d)||0}return d+ca(a,b,c||(f?"border":"content"),e)+"px"}function cc(a){if(bS[a])return bS[a];var b=p("<"+a+">").appendTo(e.body),c=b.css("display");b.remove();if(c==="none"||c===""){bI=e.body.appendChild(bI||p.extend(e.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!bJ||!bI.createElement)bJ=(bI.contentWindow||bI.contentDocument).document,bJ.write("<!doctype html><html><body>"),bJ.close();b=bJ.body.appendChild(bJ.createElement(a)),c=bH(b,"display"),e.body.removeChild(bI)}return bS[a]=c,c}function ci(a,b,c,d){var e;if(p.isArray(b))p.each(b,function(b,e){c||ce.test(a)?d(a,e):ci(a+"["+(typeof e=="object"?b:"")+"]",e,c,d)});else if(!c&&p.type(b)==="object")for(e in b)ci(a+"["+e+"]",b[e],c,d);else d(a,b)}function cz(a){return function(b,c){typeof b!="string"&&(c=b,b="*");var d,e,f,g=b.toLowerCase().split(s),h=0,i=g.length;if(p.isFunction(c))for(;h<i;h++)d=g[h],f=/^\+/.test(d),f&&(d=d.substr(1)||"*"),e=a[d]=a[d]||[],e[f?"unshift":"push"](c)}}function cA(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h,i=a[f],j=0,k=i?i.length:0,l=a===cv;for(;j<k&&(l||!h);j++)h=i[j](c,d,e),typeof h=="string"&&(!l||g[h]?h=b:(c.dataTypes.unshift(h),h=cA(a,c,d,e,h,g)));return(l||!h)&&!g["*"]&&(h=cA(a,c,d,e,"*",g)),h}function cB(a,c){var d,e,f=p.ajaxSettings.flatOptions||{};for(d in c)c[d]!==b&&((f[d]?a:e||(e={}))[d]=c[d]);e&&p.extend(!0,a,e)}function cC(a,c,d){var e,f,g,h,i=a.contents,j=a.dataTypes,k=a.responseFields;for(f in k)f in d&&(c[k[f]]=d[f]);while(j[0]==="*")j.shift(),e===b&&(e=a.mimeType||c.getResponseHeader("content-type"));if(e)for(f in i)if(i[f]&&i[f].test(e)){j.unshift(f);break}if(j[0]in d)g=j[0];else{for(f in d){if(!j[0]||a.converters[f+" "+j[0]]){g=f;break}h||(h=f)}g=g||h}if(g)return g!==j[0]&&j.unshift(g),d[g]}function cD(a,b){var c,d,e,f,g=a.dataTypes.slice(),h=g[0],i={},j=0;a.dataFilter&&(b=a.dataFilter(b,a.dataType));if(g[1])for(c in a.converters)i[c.toLowerCase()]=a.converters[c];for(;e=g[++j];)if(e!=="*"){if(h!=="*"&&h!==e){c=i[h+" "+e]||i["* "+e];if(!c)for(d in i){f=d.split(" ");if(f[1]===e){c=i[h+" "+f[0]]||i["* "+f[0]];if(c){c===!0?c=i[d]:i[d]!==!0&&(e=f[0],g.splice(j--,0,e));break}}}if(c!==!0)if(c&&a["throws"])b=c(b);else try{b=c(b)}catch(k){return{state:"parsererror",error:c?k:"No conversion from "+h+" to "+e}}}h=e}return{state:"success",data:b}}function cL(){try{return new a.XMLHttpRequest}catch(b){}}function cM(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function cU(){return setTimeout(function(){cN=b},0),cN=p.now()}function cV(a,b){p.each(b,function(b,c){var d=(cT[b]||[]).concat(cT["*"]),e=0,f=d.length;for(;e<f;e++)if(d[e].call(a,b,c))return})}function cW(a,b,c){var d,e=0,f=0,g=cS.length,h=p.Deferred().always(function(){delete i.elem}),i=function(){var b=cN||cU(),c=Math.max(0,j.startTime+j.duration-b),d=1-(c/j.duration||0),e=0,f=j.tweens.length;for(;e<f;e++)j.tweens[e].run(d);return h.notifyWith(a,[j,d,c]),d<1&&f?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:p.extend({},b),opts:p.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:cN||cU(),duration:c.duration,tweens:[],createTween:function(b,c,d){var e=p.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(e),e},stop:function(b){var c=0,d=b?j.tweens.length:0;for(;c<d;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;cX(k,j.opts.specialEasing);for(;e<g;e++){d=cS[e].call(j,a,k,j.opts);if(d)return d}return cV(j,k),p.isFunction(j.opts.start)&&j.opts.start.call(a,j),p.fx.timer(p.extend(i,{anim:j,queue:j.opts.queue,elem:a})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}function cX(a,b){var c,d,e,f,g;for(c in a){d=p.camelCase(c),e=b[d],f=a[c],p.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=p.cssHooks[d];if(g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}}function cY(a,b,c){var d,e,f,g,h,i,j,k,l=this,m=a.style,n={},o=[],q=a.nodeType&&bZ(a);c.queue||(j=p._queueHooks(a,"fx"),j.unqueued==null&&(j.unqueued=0,k=j.empty.fire,j.empty.fire=function(){j.unqueued||k()}),j.unqueued++,l.always(function(){l.always(function(){j.unqueued--,p.queue(a,"fx").length||j.empty.fire()})})),a.nodeType===1&&("height"in b||"width"in b)&&(c.overflow=[m.overflow,m.overflowX,m.overflowY],p.css(a,"display")==="inline"&&p.css(a,"float")==="none"&&(!p.support.inlineBlockNeedsLayout||cc(a.nodeName)==="inline"?m.display="inline-block":m.zoom=1)),c.overflow&&(m.overflow="hidden",p.support.shrinkWrapBlocks||l.done(function(){m.overflow=c.overflow[0],m.overflowX=c.overflow[1],m.overflowY=c.overflow[2]}));for(d in b){f=b[d];if(cP.exec(f)){delete b[d];if(f===(q?"hide":"show"))continue;o.push(d)}}g=o.length;if(g){h=p._data(a,"fxshow")||p._data(a,"fxshow",{}),q?p(a).show():l.done(function(){p(a).hide()}),l.done(function(){var b;p.removeData(a,"fxshow",!0);for(b in n)p.style(a,b,n[b])});for(d=0;d<g;d++)e=o[d],i=l.createTween(e,q?h[e]:0),n[e]=h[e]||p.style(a,e),e in h||(h[e]=i.start,q&&(i.end=i.start,i.start=e==="width"||e==="height"?1:0))}}function cZ(a,b,c,d,e){return new cZ.prototype.init(a,b,c,d,e)}function c$(a,b){var c,d={height:a},e=0;b=b?1:0;for(;e<4;e+=2-b)c=bV[e],d["margin"+c]=d["padding"+c]=a;return b&&(d.opacity=d.width=a),d}function da(a){return p.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}var c,d,e=a.document,f=a.location,g=a.navigator,h=a.jQuery,i=a.$,j=Array.prototype.push,k=Array.prototype.slice,l=Array.prototype.indexOf,m=Object.prototype.toString,n=Object.prototype.hasOwnProperty,o=String.prototype.trim,p=function(a,b){return new p.fn.init(a,b,c)},q=/[\-+]?(?:\d*\.|)\d+(?:[eE][\-+]?\d+|)/.source,r=/\S/,s=/\s+/,t=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,u=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,y=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,z=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,A=/^-ms-/,B=/-([\da-z])/gi,C=function(a,b){return(b+"").toUpperCase()},D=function(){e.addEventListener?(e.removeEventListener("DOMContentLoaded",D,!1),p.ready()):e.readyState==="complete"&&(e.detachEvent("onreadystatechange",D),p.ready())},E={};p.fn=p.prototype={constructor:p,init:function(a,c,d){var f,g,h,i;if(!a)return this;if(a.nodeType)return this.context=this[0]=a,this.length=1,this;if(typeof a=="string"){a.charAt(0)==="<"&&a.charAt(a.length-1)===">"&&a.length>=3?f=[null,a,null]:f=u.exec(a);if(f&&(f[1]||!c)){if(f[1])return c=c instanceof p?c[0]:c,i=c&&c.nodeType?c.ownerDocument||c:e,a=p.parseHTML(f[1],i,!0),v.test(f[1])&&p.isPlainObject(c)&&this.attr.call(a,c,!0),p.merge(this,a);g=e.getElementById(f[2]);if(g&&g.parentNode){if(g.id!==f[2])return d.find(a);this.length=1,this[0]=g}return this.context=e,this.selector=a,this}return!c||c.jquery?(c||d).find(a):this.constructor(c).find(a)}return p.isFunction(a)?d.ready(a):(a.selector!==b&&(this.selector=a.selector,this.context=a.context),p.makeArray(a,this))},selector:"",jquery:"1.8.1",length:0,size:function(){return this.length},toArray:function(){return k.call(this)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=p.merge(this.constructor(),a);return d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")"),d},each:function(a,b){return p.each(this,a,b)},ready:function(a){return p.ready.promise().done(a),this},eq:function(a){return a=+a,a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(k.apply(this,arguments),"slice",k.call(arguments).join(","))},map:function(a){return this.pushStack(p.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:j,sort:[].sort,splice:[].splice},p.fn.init.prototype=p.fn,p.extend=p.fn.extend=function(){var a,c,d,e,f,g,h=arguments[0]||{},i=1,j=arguments.length,k=!1;typeof h=="boolean"&&(k=h,h=arguments[1]||{},i=2),typeof h!="object"&&!p.isFunction(h)&&(h={}),j===i&&(h=this,--i);for(;i<j;i++)if((a=arguments[i])!=null)for(c in a){d=h[c],e=a[c];if(h===e)continue;k&&e&&(p.isPlainObject(e)||(f=p.isArray(e)))?(f?(f=!1,g=d&&p.isArray(d)?d:[]):g=d&&p.isPlainObject(d)?d:{},h[c]=p.extend(k,g,e)):e!==b&&(h[c]=e)}return h},p.extend({noConflict:function(b){return a.$===p&&(a.$=i),b&&a.jQuery===p&&(a.jQuery=h),p},isReady:!1,readyWait:1,holdReady:function(a){a?p.readyWait++:p.ready(!0)},ready:function(a){if(a===!0?--p.readyWait:p.isReady)return;if(!e.body)return setTimeout(p.ready,1);p.isReady=!0;if(a!==!0&&--p.readyWait>0)return;d.resolveWith(e,[p]),p.fn.trigger&&p(e).trigger("ready").off("ready")},isFunction:function(a){return p.type(a)==="function"},isArray:Array.isArray||function(a){return p.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):E[m.call(a)]||"object"},isPlainObject:function(a){if(!a||p.type(a)!=="object"||a.nodeType||p.isWindow(a))return!1;try{if(a.constructor&&!n.call(a,"constructor")&&!n.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||n.call(a,d)},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},error:function(a){throw new Error(a)},parseHTML:function(a,b,c){var d;return!a||typeof a!="string"?null:(typeof b=="boolean"&&(c=b,b=0),b=b||e,(d=v.exec(a))?[b.createElement(d[1])]:(d=p.buildFragment([a],b,c?null:[]),p.merge([],(d.cacheable?p.clone(d.fragment):d.fragment).childNodes)))},parseJSON:function(b){if(!b||typeof b!="string")return null;b=p.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(w.test(b.replace(y,"@").replace(z,"]").replace(x,"")))return(new Function("return "+b))();p.error("Invalid JSON: "+b)},parseXML:function(c){var d,e;if(!c||typeof c!="string")return null;try{a.DOMParser?(e=new DOMParser,d=e.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(f){d=b}return(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&p.error("Invalid XML: "+c),d},noop:function(){},globalEval:function(b){b&&r.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(A,"ms-").replace(B,C)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var e,f=0,g=a.length,h=g===b||p.isFunction(a);if(d){if(h){for(e in a)if(c.apply(a[e],d)===!1)break}else for(;f<g;)if(c.apply(a[f++],d)===!1)break}else if(h){for(e in a)if(c.call(a[e],e,a[e])===!1)break}else for(;f<g;)if(c.call(a[f],f,a[f++])===!1)break;return a},trim:o&&!o.call(" ")?function(a){return a==null?"":o.call(a)}:function(a){return a==null?"":a.toString().replace(t,"")},makeArray:function(a,b){var c,d=b||[];return a!=null&&(c=p.type(a),a.length==null||c==="string"||c==="function"||c==="regexp"||p.isWindow(a)?j.call(d,a):p.merge(d,a)),d},inArray:function(a,b,c){var d;if(b){if(l)return l.call(b,a,c);d=b.length,c=c?c<0?Math.max(0,d+c):c:0;for(;c<d;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,c){var d=c.length,e=a.length,f=0;if(typeof d=="number")for(;f<d;f++)a[e++]=c[f];else while(c[f]!==b)a[e++]=c[f++];return a.length=e,a},grep:function(a,b,c){var d,e=[],f=0,g=a.length;c=!!c;for(;f<g;f++)d=!!b(a[f],f),c!==d&&e.push(a[f]);return e},map:function(a,c,d){var e,f,g=[],h=0,i=a.length,j=a instanceof p||i!==b&&typeof i=="number"&&(i>0&&a[0]&&a[i-1]||i===0||p.isArray(a));if(j)for(;h<i;h++)e=c(a[h],h,d),e!=null&&(g[g.length]=e);else for(f in a)e=c(a[f],f,d),e!=null&&(g[g.length]=e);return g.concat.apply([],g)},guid:1,proxy:function(a,c){var d,e,f;return typeof c=="string"&&(d=a[c],c=a,a=d),p.isFunction(a)?(e=k.call(arguments,2),f=function(){return a.apply(c,e.concat(k.call(arguments)))},f.guid=a.guid=a.guid||f.guid||p.guid++,f):b},access:function(a,c,d,e,f,g,h){var i,j=d==null,k=0,l=a.length;if(d&&typeof d=="object"){for(k in d)p.access(a,c,k,d[k],1,g,e);f=1}else if(e!==b){i=h===b&&p.isFunction(e),j&&(i?(i=c,c=function(a,b,c){return i.call(p(a),c)}):(c.call(a,e),c=null));if(c)for(;k<l;k++)c(a[k],d,i?e.call(a[k],k,c(a[k],d)):e,h);f=1}return f?a:j?c.call(a):l?c(a[0],d):g},now:function(){return(new Date).getTime()}}),p.ready.promise=function(b){if(!d){d=p.Deferred();if(e.readyState==="complete")setTimeout(p.ready,1);else if(e.addEventListener)e.addEventListener("DOMContentLoaded",D,!1),a.addEventListener("load",p.ready,!1);else{e.attachEvent("onreadystatechange",D),a.attachEvent("onload",p.ready);var c=!1;try{c=a.frameElement==null&&e.documentElement}catch(f){}c&&c.doScroll&&function g(){if(!p.isReady){try{c.doScroll("left")}catch(a){return setTimeout(g,50)}p.ready()}}()}}return d.promise(b)},p.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){E["[object "+b+"]"]=b.toLowerCase()}),c=p(e);var F={};p.Callbacks=function(a){a=typeof a=="string"?F[a]||G(a):p.extend({},a);var c,d,e,f,g,h,i=[],j=!a.once&&[],k=function(b){c=a.memory&&b,d=!0,h=f||0,f=0,g=i.length,e=!0;for(;i&&h<g;h++)if(i[h].apply(b[0],b[1])===!1&&a.stopOnFalse){c=!1;break}e=!1,i&&(j?j.length&&k(j.shift()):c?i=[]:l.disable())},l={add:function(){if(i){var b=i.length;(function d(b){p.each(b,function(b,c){var e=p.type(c);e==="function"&&(!a.unique||!l.has(c))?i.push(c):c&&c.length&&e!=="string"&&d(c)})})(arguments),e?g=i.length:c&&(f=b,k(c))}return this},remove:function(){return i&&p.each(arguments,function(a,b){var c;while((c=p.inArray(b,i,c))>-1)i.splice(c,1),e&&(c<=g&&g--,c<=h&&h--)}),this},has:function(a){return p.inArray(a,i)>-1},empty:function(){return i=[],this},disable:function(){return i=j=c=b,this},disabled:function(){return!i},lock:function(){return j=b,c||l.disable(),this},locked:function(){return!j},fireWith:function(a,b){return b=b||[],b=[a,b.slice?b.slice():b],i&&(!d||j)&&(e?j.push(b):k(b)),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!d}};return l},p.extend({Deferred:function(a){var b=[["resolve","done",p.Callbacks("once memory"),"resolved"],["reject","fail",p.Callbacks("once memory"),"rejected"],["notify","progress",p.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return p.Deferred(function(c){p.each(b,function(b,d){var f=d[0],g=a[b];e[d[1]](p.isFunction(g)?function(){var a=g.apply(this,arguments);a&&p.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f+"With"](this===e?c:this,[a])}:c[f])}),a=null}).promise()},promise:function(a){return typeof a=="object"?p.extend(a,d):d}},e={};return d.pipe=d.then,p.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[a^1][2].disable,b[2][2].lock),e[f[0]]=g.fire,e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=k.call(arguments),d=c.length,e=d!==1||a&&p.isFunction(a.promise)?d:0,f=e===1?a:p.Deferred(),g=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?k.call(arguments):d,c===h?f.notifyWith(b,c):--e||f.resolveWith(b,c)}},h,i,j;if(d>1){h=new Array(d),i=new Array(d),j=new Array(d);for(;b<d;b++)c[b]&&p.isFunction(c[b].promise)?c[b].promise().done(g(b,j,c)).fail(f.reject).progress(g(b,i,h)):--e}return e||f.resolveWith(j,c),f.promise()}}),p.support=function(){var b,c,d,f,g,h,i,j,k,l,m,n=e.createElement("div");n.setAttribute("className","t"),n.innerHTML="  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",c=n.getElementsByTagName("*"),d=n.getElementsByTagName("a")[0],d.style.cssText="top:1px;float:left;opacity:.5";if(!c||!c.length||!d)return{};f=e.createElement("select"),g=f.appendChild(e.createElement("option")),h=n.getElementsByTagName("input")[0],b={leadingWhitespace:n.firstChild.nodeType===3,tbody:!n.getElementsByTagName("tbody").length,htmlSerialize:!!n.getElementsByTagName("link").length,style:/top/.test(d.getAttribute("style")),hrefNormalized:d.getAttribute("href")==="/a",opacity:/^0.5/.test(d.style.opacity),cssFloat:!!d.style.cssFloat,checkOn:h.value==="on",optSelected:g.selected,getSetAttribute:n.className!=="t",enctype:!!e.createElement("form").enctype,html5Clone:e.createElement("nav").cloneNode(!0).outerHTML!=="<:nav></:nav>",boxModel:e.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},h.checked=!0,b.noCloneChecked=h.cloneNode(!0).checked,f.disabled=!0,b.optDisabled=!g.disabled;try{delete n.test}catch(o){b.deleteExpando=!1}!n.addEventListener&&n.attachEvent&&n.fireEvent&&(n.attachEvent("onclick",m=function(){b.noCloneEvent=!1}),n.cloneNode(!0).fireEvent("onclick"),n.detachEvent("onclick",m)),h=e.createElement("input"),h.value="t",h.setAttribute("type","radio"),b.radioValue=h.value==="t",h.setAttribute("checked","checked"),h.setAttribute("name","t"),n.appendChild(h),i=e.createDocumentFragment(),i.appendChild(n.lastChild),b.checkClone=i.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=h.checked,i.removeChild(h),i.appendChild(n);if(n.attachEvent)for(k in{submit:!0,change:!0,focusin:!0})j="on"+k,l=j in n,l||(n.setAttribute(j,"return;"),l=typeof n[j]=="function"),b[k+"Bubbles"]=l;return p(function(){var c,d,f,g,h="padding:0;margin:0;border:0;display:block;overflow:hidden;",i=e.getElementsByTagName("body")[0];if(!i)return;c=e.createElement("div"),c.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",i.insertBefore(c,i.firstChild),d=e.createElement("div"),c.appendChild(d),d.innerHTML="<table><tr><td></td><td>t</td></tr></table>",f=d.getElementsByTagName("td"),f[0].style.cssText="padding:0;margin:0;border:0;display:none",l=f[0].offsetHeight===0,f[0].style.display="",f[1].style.display="none",b.reliableHiddenOffsets=l&&f[0].offsetHeight===0,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",b.boxSizing=d.offsetWidth===4,b.doesNotIncludeMarginInBodyOffset=i.offsetTop!==1,a.getComputedStyle&&(b.pixelPosition=(a.getComputedStyle(d,null)||{}).top!=="1%",b.boxSizingReliable=(a.getComputedStyle(d,null)||{width:"4px"}).width==="4px",g=e.createElement("div"),g.style.cssText=d.style.cssText=h,g.style.marginRight=g.style.width="0",d.style.width="1px",d.appendChild(g),b.reliableMarginRight=!parseFloat((a.getComputedStyle(g,null)||{}).marginRight)),typeof d.style.zoom!="undefined"&&(d.innerHTML="",d.style.cssText=h+"width:1px;padding:1px;display:inline;zoom:1",b.inlineBlockNeedsLayout=d.offsetWidth===3,d.style.display="block",d.style.overflow="visible",d.innerHTML="<div></div>",d.firstChild.style.width="5px",b.shrinkWrapBlocks=d.offsetWidth!==3,c.style.zoom=1),i.removeChild(c),c=d=f=g=null}),i.removeChild(n),c=d=f=g=h=i=n=null,b}();var H=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,I=/([A-Z])/g;p.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(p.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){return a=a.nodeType?p.cache[a[p.expando]]:a[p.expando],!!a&&!K(a)},data:function(a,c,d,e){if(!p.acceptData(a))return;var f,g,h=p.expando,i=typeof c=="string",j=a.nodeType,k=j?p.cache:a,l=j?a[h]:a[h]&&h;if((!l||!k[l]||!e&&!k[l].data)&&i&&d===b)return;l||(j?a[h]=l=p.deletedIds.pop()||++p.uuid:l=h),k[l]||(k[l]={},j||(k[l].toJSON=p.noop));if(typeof c=="object"||typeof c=="function")e?k[l]=p.extend(k[l],c):k[l].data=p.extend(k[l].data,c);return f=k[l],e||(f.data||(f.data={}),f=f.data),d!==b&&(f[p.camelCase(c)]=d),i?(g=f[c],g==null&&(g=f[p.camelCase(c)])):g=f,g},removeData:function(a,b,c){if(!p.acceptData(a))return;var d,e,f,g=a.nodeType,h=g?p.cache:a,i=g?a[p.expando]:p.expando;if(!h[i])return;if(b){d=c?h[i]:h[i].data;if(d){p.isArray(b)||(b in d?b=[b]:(b=p.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,f=b.length;e<f;e++)delete d[b[e]];if(!(c?K:p.isEmptyObject)(d))return}}if(!c){delete h[i].data;if(!K(h[i]))return}g?p.cleanData([a],!0):p.support.deleteExpando||h!=h.window?delete h[i]:h[i]=null},_data:function(a,b,c){return p.data(a,b,c,!0)},acceptData:function(a){var b=a.nodeName&&p.noData[a.nodeName.toLowerCase()];return!b||b!==!0&&a.getAttribute("classid")===b}}),p.fn.extend({data:function(a,c){var d,e,f,g,h,i=this[0],j=0,k=null;if(a===b){if(this.length){k=p.data(i);if(i.nodeType===1&&!p._data(i,"parsedAttrs")){f=i.attributes;for(h=f.length;j<h;j++)g=f[j].name,g.indexOf("data-")===0&&(g=p.camelCase(g.substring(5)),J(i,g,k[g]));p._data(i,"parsedAttrs",!0)}}return k}return typeof a=="object"?this.each(function(){p.data(this,a)}):(d=a.split(".",2),d[1]=d[1]?"."+d[1]:"",e=d[1]+"!",p.access(this,function(c){if(c===b)return k=this.triggerHandler("getData"+e,[d[0]]),k===b&&i&&(k=p.data(i,a),k=J(i,a,k)),k===b&&d[1]?this.data(d[0]):k;d[1]=c,this.each(function(){var b=p(this);b.triggerHandler("setData"+e,d),p.data(this,a,c),b.triggerHandler("changeData"+e,d)})},null,c,arguments.length>1,null,!1))},removeData:function(a){return this.each(function(){p.removeData(this,a)})}}),p.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=p._data(a,b),c&&(!d||p.isArray(c)?d=p._data(a,b,p.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=p.queue(a,b),d=c.length,e=c.shift(),f=p._queueHooks(a,b),g=function(){p.dequeue(a,b)};e==="inprogress"&&(e=c.shift(),d--),e&&(b==="fx"&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return p._data(a,c)||p._data(a,c,{empty:p.Callbacks("once memory").add(function(){p.removeData(a,b+"queue",!0),p.removeData(a,c,!0)})})}}),p.fn.extend({queue:function(a,c){var d=2;return typeof a!="string"&&(c=a,a="fx",d--),arguments.length<d?p.queue(this[0],a):c===b?this:this.each(function(){var b=p.queue(this,a,c);p._queueHooks(this,a),a==="fx"&&b[0]!=="inprogress"&&p.dequeue(this,a)})},dequeue:function(a){return this.each(function(){p.dequeue(this,a)})},delay:function(a,b){return a=p.fx?p.fx.speeds[a]||a:a,b=b||"fx",this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){var d,e=1,f=p.Deferred(),g=this,h=this.length,i=function(){--e||f.resolveWith(g,[g])};typeof a!="string"&&(c=a,a=b),a=a||"fx";while(h--)d=p._data(g[h],a+"queueHooks"),d&&d.empty&&(e++,d.empty.add(i));return i(),f.promise(c)}});var L,M,N,O=/[\t\r\n]/g,P=/\r/g,Q=/^(?:button|input)$/i,R=/^(?:button|input|object|select|textarea)$/i,S=/^a(?:rea|)$/i,T=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,U=p.support.getSetAttribute;p.fn.extend({attr:function(a,b){return p.access(this,p.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){p.removeAttr(this,a)})},prop:function(a,b){return p.access(this,p.prop,a,b,arguments.length>1)},removeProp:function(a){return a=p.propFix[a]||a,this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,f,g,h;if(p.isFunction(a))return this.each(function(b){p(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(s);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{f=" "+e.className+" ";for(g=0,h=b.length;g<h;g++)~f.indexOf(" "+b[g]+" ")||(f+=b[g]+" ");e.className=p.trim(f)}}}return this},removeClass:function(a){var c,d,e,f,g,h,i;if(p.isFunction(a))return this.each(function(b){p(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(s);for(h=0,i=this.length;h<i;h++){e=this[h];if(e.nodeType===1&&e.className){d=(" "+e.className+" ").replace(O," ");for(f=0,g=c.length;f<g;f++)while(d.indexOf(" "+c[f]+" ")>-1)d=d.replace(" "+c[f]+" "," ");e.className=a?p.trim(d):""}}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";return p.isFunction(a)?this.each(function(c){p(this).toggleClass(a.call(this,c,this.className,b),b)}):this.each(function(){if(c==="string"){var e,f=0,g=p(this),h=b,i=a.split(s);while(e=i[f++])h=d?h:!g.hasClass(e),g[h?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&p._data(this,"__className__",this.className),this.className=this.className||a===!1?"":p._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c<d;c++)if(this[c].nodeType===1&&(" "+this[c].className+" ").replace(O," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e,f=this[0];if(!arguments.length){if(f)return c=p.valHooks[f.type]||p.valHooks[f.nodeName.toLowerCase()],c&&"get"in c&&(d=c.get(f,"value"))!==b?d:(d=f.value,typeof d=="string"?d.replace(P,""):d==null?"":d);return}return e=p.isFunction(a),this.each(function(d){var f,g=p(this);if(this.nodeType!==1)return;e?f=a.call(this,d,g.val()):f=a,f==null?f="":typeof f=="number"?f+="":p.isArray(f)&&(f=p.map(f,function(a){return a==null?"":a+""})),c=p.valHooks[this.type]||p.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,f,"value")===b)this.value=f})}}),p.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,f=a.selectedIndex,g=[],h=a.options,i=a.type==="select-one";if(f<0)return null;c=i?f:0,d=i?f+1:h.length;for(;c<d;c++){e=h[c];if(e.selected&&(p.support.optDisabled?!e.disabled:e.getAttribute("disabled")===null)&&(!e.parentNode.disabled||!p.nodeName(e.parentNode,"optgroup"))){b=p(e).val();if(i)return b;g.push(b)}}return i&&!g.length&&h.length?p(h[f]).val():g},set:function(a,b){var c=p.makeArray(b);return p(a).find("option").each(function(){this.selected=p.inArray(p(this).val(),c)>=0}),c.length||(a.selectedIndex=-1),c}}},attrFn:{},attr:function(a,c,d,e){var f,g,h,i=a.nodeType;if(!a||i===3||i===8||i===2)return;if(e&&p.isFunction(p.fn[c]))return p(a)[c](d);if(typeof a.getAttribute=="undefined")return p.prop(a,c,d);h=i!==1||!p.isXMLDoc(a),h&&(c=c.toLowerCase(),g=p.attrHooks[c]||(T.test(c)?M:L));if(d!==b){if(d===null){p.removeAttr(a,c);return}return g&&"set"in g&&h&&(f=g.set(a,d,c))!==b?f:(a.setAttribute(c,""+d),d)}return g&&"get"in g&&h&&(f=g.get(a,c))!==null?f:(f=a.getAttribute(c),f===null?b:f)},removeAttr:function(a,b){var c,d,e,f,g=0;if(b&&a.nodeType===1){d=b.split(s);for(;g<d.length;g++)e=d[g],e&&(c=p.propFix[e]||e,f=T.test(e),f||p.attr(a,e,""),a.removeAttribute(U?e:c),f&&c in a&&(a[c]=!1))}},attrHooks:{type:{set:function(a,b){if(Q.test(a.nodeName)&&a.parentNode)p.error("type property can't be changed");else if(!p.support.radioValue&&b==="radio"&&p.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}},value:{get:function(a,b){return L&&p.nodeName(a,"button")?L.get(a,b):b in a?a.value:null},set:function(a,b,c){if(L&&p.nodeName(a,"button"))return L.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e,f,g,h=a.nodeType;if(!a||h===3||h===8||h===2)return;return g=h!==1||!p.isXMLDoc(a),g&&(c=p.propFix[c]||c,f=p.propHooks[c]),d!==b?f&&"set"in f&&(e=f.set(a,d,c))!==b?e:a[c]=d:f&&"get"in f&&(e=f.get(a,c))!==null?e:a[c]},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):R.test(a.nodeName)||S.test(a.nodeName)&&a.href?0:b}}}}),M={get:function(a,c){var d,e=p.prop(a,c);return e===!0||typeof e!="boolean"&&(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;return b===!1?p.removeAttr(a,c):(d=p.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase())),c}},U||(N={name:!0,id:!0,coords:!0},L=p.valHooks.button={get:function(a,c){var d;return d=a.getAttributeNode(c),d&&(N[c]?d.value!=="":d.specified)?d.value:b},set:function(a,b,c){var d=a.getAttributeNode(c);return d||(d=e.createAttribute(c),a.setAttributeNode(d)),d.value=b+""}},p.each(["width","height"],function(a,b){p.attrHooks[b]=p.extend(p.attrHooks[b],{set:function(a,c){if(c==="")return a.setAttribute(b,"auto"),c}})}),p.attrHooks.contenteditable={get:L.get,set:function(a,b,c){b===""&&(b="false"),L.set(a,b,c)}}),p.support.hrefNormalized||p.each(["href","src","width","height"],function(a,c){p.attrHooks[c]=p.extend(p.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),p.support.style||(p.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),p.support.optSelected||(p.propHooks.selected=p.extend(p.propHooks.selected,{get:function(a){var b=a.parentNode;return b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex),null}})),p.support.enctype||(p.propFix.enctype="encoding"),p.support.checkOn||p.each(["radio","checkbox"],function(){p.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),p.each(["radio","checkbox"],function(){p.valHooks[this]=p.extend(p.valHooks[this],{set:function(a,b){if(p.isArray(b))return a.checked=p.inArray(p(a).val(),b)>=0}})});var V=/^(?:textarea|input|select)$/i,W=/^([^\.]*|)(?:\.(.+)|)$/,X=/(?:^|\s)hover(\.\S+|)\b/,Y=/^key/,Z=/^(?:mouse|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=function(a){return p.event.special.hover?a:a.replace(X,"mouseenter$1 mouseleave$1")};p.event={add:function(a,c,d,e,f){var g,h,i,j,k,l,m,n,o,q,r;if(a.nodeType===3||a.nodeType===8||!c||!d||!(g=p._data(a)))return;d.handler&&(o=d,d=o.handler,f=o.selector),d.guid||(d.guid=p.guid++),i=g.events,i||(g.events=i={}),h=g.handle,h||(g.handle=h=function(a){return typeof p!="undefined"&&(!a||p.event.triggered!==a.type)?p.event.dispatch.apply(h.elem,arguments):b},h.elem=a),c=p.trim(_(c)).split(" ");for(j=0;j<c.length;j++){k=W.exec(c[j])||[],l=k[1],m=(k[2]||"").split(".").sort(),r=p.event.special[l]||{},l=(f?r.delegateType:r.bindType)||l,r=p.event.special[l]||{},n=p.extend({type:l,origType:k[1],data:e,handler:d,guid:d.guid,selector:f,namespace:m.join(".")},o),q=i[l];if(!q){q=i[l]=[],q.delegateCount=0;if(!r.setup||r.setup.call(a,e,m,h)===!1)a.addEventListener?a.addEventListener(l,h,!1):a.attachEvent&&a.attachEvent("on"+l,h)}r.add&&(r.add.call(a,n),n.handler.guid||(n.handler.guid=d.guid)),f?q.splice(q.delegateCount++,0,n):q.push(n),p.event.global[l]=!0}a=null},global:{},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,q,r=p.hasData(a)&&p._data(a);if(!r||!(m=r.events))return;b=p.trim(_(b||"")).split(" ");for(f=0;f<b.length;f++){g=W.exec(b[f])||[],h=i=g[1],j=g[2];if(!h){for(h in m)p.event.remove(a,h+b[f],c,d,!0);continue}n=p.event.special[h]||{},h=(d?n.delegateType:n.bindType)||h,o=m[h]||[],k=o.length,j=j?new RegExp("(^|\\.)"+j.split(".").sort().join("\\.(?:.*\\.|)")+"(\\.|$)"):null;for(l=0;l<o.length;l++)q=o[l],(e||i===q.origType)&&(!c||c.guid===q.guid)&&(!j||j.test(q.namespace))&&(!d||d===q.selector||d==="**"&&q.selector)&&(o.splice(l--,1),q.selector&&o.delegateCount--,n.remove&&n.remove.call(a,q));o.length===0&&k!==o.length&&((!n.teardown||n.teardown.call(a,j,r.handle)===!1)&&p.removeEvent(a,h,r.handle),delete m[h])}p.isEmptyObject(m)&&(delete r.handle,p.removeData(a,"events",!0))},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,f,g){if(!f||f.nodeType!==3&&f.nodeType!==8){var h,i,j,k,l,m,n,o,q,r,s=c.type||c,t=[];if($.test(s+p.event.triggered))return;s.indexOf("!")>=0&&(s=s.slice(0,-1),i=!0),s.indexOf(".")>=0&&(t=s.split("."),s=t.shift(),t.sort());if((!f||p.event.customEvent[s])&&!p.event.global[s])return;c=typeof c=="object"?c[p.expando]?c:new p.Event(s,c):new p.Event(s),c.type=s,c.isTrigger=!0,c.exclusive=i,c.namespace=t.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+t.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,m=s.indexOf(":")<0?"on"+s:"";if(!f){h=p.cache;for(j in h)h[j].events&&h[j].events[s]&&p.event.trigger(c,d,h[j].handle.elem,!0);return}c.result=b,c.target||(c.target=f),d=d!=null?p.makeArray(d):[],d.unshift(c),n=p.event.special[s]||{};if(n.trigger&&n.trigger.apply(f,d)===!1)return;q=[[f,n.bindType||s]];if(!g&&!n.noBubble&&!p.isWindow(f)){r=n.delegateType||s,k=$.test(r+s)?f:f.parentNode;for(l=f;k;k=k.parentNode)q.push([k,r]),l=k;l===(f.ownerDocument||e)&&q.push([l.defaultView||l.parentWindow||a,r])}for(j=0;j<q.length&&!c.isPropagationStopped();j++)k=q[j][0],c.type=q[j][1],o=(p._data(k,"events")||{})[c.type]&&p._data(k,"handle"),o&&o.apply(k,d),o=m&&k[m],o&&p.acceptData(k)&&o.apply(k,d)===!1&&c.preventDefault();return c.type=s,!g&&!c.isDefaultPrevented()&&(!n._default||n._default.apply(f.ownerDocument,d)===!1)&&(s!=="click"||!p.nodeName(f,"a"))&&p.acceptData(f)&&m&&f[s]&&(s!=="focus"&&s!=="blur"||c.target.offsetWidth!==0)&&!p.isWindow(f)&&(l=f[m],l&&(f[m]=null),p.event.triggered=s,f[s](),p.event.triggered=b,l&&(f[m]=l)),c.result}return},dispatch:function(c){c=p.event.fix(c||a.event);var d,e,f,g,h,i,j,k,l,m,n=(p._data(this,"events")||{})[c.type]||[],o=n.delegateCount,q=[].slice.call(arguments),r=!c.exclusive&&!c.namespace,s=p.event.special[c.type]||{},t=[];q[0]=c,c.delegateTarget=this;if(s.preDispatch&&s.preDispatch.call(this,c)===!1)return;if(o&&(!c.button||c.type!=="click"))for(f=c.target;f!=this;f=f.parentNode||this)if(f.disabled!==!0||c.type!=="click"){h={},j=[];for(d=0;d<o;d++)k=n[d],l=k.selector,h[l]===b&&(h[l]=p(l,this).index(f)>=0),h[l]&&j.push(k);j.length&&t.push({elem:f,matches:j})}n.length>o&&t.push({elem:this,matches:n.slice(o)});for(d=0;d<t.length&&!c.isPropagationStopped();d++){i=t[d],c.currentTarget=i.elem;for(e=0;e<i.matches.length&&!c.isImmediatePropagationStopped();e++){k=i.matches[e];if(r||!c.namespace&&!k.namespace||c.namespace_re&&c.namespace_re.test(k.namespace))c.data=k.data,c.handleObj=k,g=((p.event.special[k.origType]||{}).handle||k.handler).apply(i.elem,q),g!==b&&(c.result=g,g===!1&&(c.preventDefault(),c.stopPropagation()))}}return s.postDispatch&&s.postDispatch.call(this,c),c.result},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return a.which==null&&(a.which=b.charCode!=null?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,c){var d,f,g,h=c.button,i=c.fromElement;return a.pageX==null&&c.clientX!=null&&(d=a.target.ownerDocument||e,f=d.documentElement,g=d.body,a.pageX=c.clientX+(f&&f.scrollLeft||g&&g.scrollLeft||0)-(f&&f.clientLeft||g&&g.clientLeft||0),a.pageY=c.clientY+(f&&f.scrollTop||g&&g.scrollTop||0)-(f&&f.clientTop||g&&g.clientTop||0)),!a.relatedTarget&&i&&(a.relatedTarget=i===a.target?c.toElement:i),!a.which&&h!==b&&(a.which=h&1?1:h&2?3:h&4?2:0),a}},fix:function(a){if(a[p.expando])return a;var b,c,d=a,f=p.event.fixHooks[a.type]||{},g=f.props?this.props.concat(f.props):this.props;a=p.Event(d);for(b=g.length;b;)c=g[--b],a[c]=d[c];return a.target||(a.target=d.srcElement||e),a.target.nodeType===3&&(a.target=a.target.parentNode),a.metaKey=!!a.metaKey,f.filter?f.filter(a,d):a},special:{load:{noBubble:!0},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{setup:function(a,b,c){p.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}},simulate:function(a,b,c,d){var e=p.extend(new p.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?p.event.trigger(e,null,b):p.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},p.event.handle=p.event.dispatch,p.removeEvent=e.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){var d="on"+b;a.detachEvent&&(typeof a[d]=="undefined"&&(a[d]=null),a.detachEvent(d,c))},p.Event=function(a,b){if(this instanceof p.Event)a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?bb:ba):this.type=a,b&&p.extend(this,b),this.timeStamp=a&&a.timeStamp||p.now(),this[p.expando]=!0;else return new p.Event(a,b)},p.Event.prototype={preventDefault:function(){this.isDefaultPrevented=bb;var a=this.originalEvent;if(!a)return;a.preventDefault?a.preventDefault():a.returnValue=!1},stopPropagation:function(){this.isPropagationStopped=bb;var a=this.originalEvent;if(!a)return;a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=bb,this.stopPropagation()},isDefaultPrevented:ba,isPropagationStopped:ba,isImmediatePropagationStopped:ba},p.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){p.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj,g=f.selector;if(!e||e!==d&&!p.contains(d,e))a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b;return c}}}),p.support.submitBubbles||(p.event.special.submit={setup:function(){if(p.nodeName(this,"form"))return!1;p.event.add(this,"click._submit keypress._submit",function(a){var c=a.target,d=p.nodeName(c,"input")||p.nodeName(c,"button")?c.form:b;d&&!p._data(d,"_submit_attached")&&(p.event.add(d,"submit._submit",function(a){a._submit_bubble=!0}),p._data(d,"_submit_attached",!0))})},postDispatch:function(a){a._submit_bubble&&(delete a._submit_bubble,this.parentNode&&!a.isTrigger&&p.event.simulate("submit",this.parentNode,a,!0))},teardown:function(){if(p.nodeName(this,"form"))return!1;p.event.remove(this,"._submit")}}),p.support.changeBubbles||(p.event.special.change={setup:function(){if(V.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")p.event.add(this,"propertychange._change",function(a){a.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),p.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1),p.event.simulate("change",this,a,!0)});return!1}p.event.add(this,"beforeactivate._change",function(a){var b=a.target;V.test(b.nodeName)&&!p._data(b,"_change_attached")&&(p.event.add(b,"change._change",function(a){this.parentNode&&!a.isSimulated&&!a.isTrigger&&p.event.simulate("change",this.parentNode,a,!0)}),p._data(b,"_change_attached",!0))})},handle:function(a){var b=a.target;if(this!==b||a.isSimulated||a.isTrigger||b.type!=="radio"&&b.type!=="checkbox")return a.handleObj.handler.apply(this,arguments)},teardown:function(){return p.event.remove(this,"._change"),!V.test(this.nodeName)}}),p.support.focusinBubbles||p.each({focus:"focusin",blur:"focusout"},function(a,b){var c=0,d=function(a){p.event.simulate(b,a.target,p.event.fix(a),!0)};p.event.special[b]={setup:function(){c++===0&&e.addEventListener(a,d,!0)},teardown:function(){--c===0&&e.removeEventListener(a,d,!0)}}}),p.fn.extend({on:function(a,c,d,e,f){var g,h;if(typeof a=="object"){typeof c!="string"&&(d=d||c,c=b);for(h in a)this.on(h,c,d,a[h],f);return this}d==null&&e==null?(e=c,d=c=b):e==null&&(typeof c=="string"?(e=d,d=b):(e=d,d=c,c=b));if(e===!1)e=ba;else if(!e)return this;return f===1&&(g=e,e=function(a){return p().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=p.guid++)),this.each(function(){p.event.add(this,a,e,d,c)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,c,d){var e,f;if(a&&a.preventDefault&&a.handleObj)return e=a.handleObj,p(a.delegateTarget).off(e.namespace?e.origType+"."+e.namespace:e.origType,e.selector,e.handler),this;if(typeof a=="object"){for(f in a)this.off(f,c,a[f]);return this}if(c===!1||typeof c=="function")d=c,c=b;return d===!1&&(d=ba),this.each(function(){p.event.remove(this,a,d,c)})},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},live:function(a,b,c){return p(this.context).on(a,this.selector,b,c),this},die:function(a,b){return p(this.context).off(a,this.selector||"**",b),this},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return arguments.length==1?this.off(a,"**"):this.off(b,a||"**",c)},trigger:function(a,b){return this.each(function(){p.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return p.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||p.guid++,d=0,e=function(c){var e=(p._data(this,"lastToggle"+a.guid)||0)%d;return p._data(this,"lastToggle"+a.guid,e+1),c.preventDefault(),b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),p.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){p.fn[b]=function(a,c){return c==null&&(c=a,a=null),arguments.length>0?this.on(b,null,a,c):this.trigger(b)},Y.test(b)&&(p.event.fixHooks[b]=p.event.keyHooks),Z.test(b)&&(p.event.fixHooks[b]=p.event.mouseHooks)}),function(a,b){function $(a,b,c,d){c=c||[],b=b||q;var e,f,g,j,k=b.nodeType;if(k!==1&&k!==9)return[];if(!a||typeof a!="string")return c;g=h(b);if(!g&&!d)if(e=L.exec(a))if(j=e[1]){if(k===9){f=b.getElementById(j);if(!f||!f.parentNode)return c;if(f.id===j)return c.push(f),c}else if(b.ownerDocument&&(f=b.ownerDocument.getElementById(j))&&i(b,f)&&f.id===j)return c.push(f),c}else{if(e[2])return u.apply(c,t.call(b.getElementsByTagName(a),0)),c;if((j=e[3])&&X&&b.getElementsByClassName)return u.apply(c,t.call(b.getElementsByClassName(j),0)),c}return bk(a,b,c,d,g)}function _(a){return function(b){var c=b.nodeName.toLowerCase();return c==="input"&&b.type===a}}function ba(a){return function(b){var c=b.nodeName.toLowerCase();return(c==="input"||c==="button")&&b.type===a}}function bb(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}function bc(a,b,c,d){var e,g,h,i,j,k,l,m,n,p,r=!c&&b!==q,s=(r?"<s>":"")+a.replace(H,"$1<s>"),u=y[o][s];if(u)return d?0:t.call(u,0);j=a,k=[],m=0,n=f.preFilter,p=f.filter;while(j){if(!e||(g=I.exec(j)))g&&(j=j.slice(g[0].length),h.selector=l),k.push(h=[]),l="",r&&(j=" "+j);e=!1;if(g=J.exec(j))l+=g[0],j=j.slice(g[0].length),e=h.push({part:g.pop().replace(H," "),string:g[0],captures:g});for(i in p)(g=S[i].exec(j))&&(!n[i]||(g=n[i](g,b,c)))&&(l+=g[0],j=j.slice(g[0].length),e=h.push({part:i,string:g.shift(),captures:g}));if(!e)break}return l&&(h.selector=l),d?j.length:j?$.error(a):t.call(y(s,k),0)}function bd(a,b,e,f){var g=b.dir,h=s++;return a||(a=function(a){return a===e}),b.first?function(b){while(b=b[g])if(b.nodeType===1)return a(b)&&b}:f?function(b){while(b=b[g])if(b.nodeType===1&&a(b))return b}:function(b){var e,f=h+"."+c,i=f+"."+d;while(b=b[g])if(b.nodeType===1){if((e=b[o])===i)return b.sizset;if(typeof e=="string"&&e.indexOf(f)===0){if(b.sizset)return b}else{b[o]=i;if(a(b))return b.sizset=!0,b;b.sizset=!1}}}}function be(a,b){return a?function(c){var d=b(c);return d&&a(d===!0?c:d)}:b}function bf(a,b,c){var d,e,g=0;for(;d=a[g];g++)f.relative[d.part]?e=bd(e,f.relative[d.part],b,c):e=be(e,f.filter[d.part].apply(null,d.captures.concat(b,c)));return e}function bg(a){return function(b){var c,d=0;for(;c=a[d];d++)if(c(b))return!0;return!1}}function bh(a,b,c,d){var e=0,f=b.length;for(;e<f;e++)$(a,b[e],c,d)}function bi(a,b,c,d,e,g){var h,i=f.setFilters[b.toLowerCase()];return i||$.error(b),(a||!(h=e))&&bh(a||"*",d,h=[],e),h.length>0?i(h,c,g):[]}function bj(a,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q,r,s=0,t=a.length,v=S.POS,w=new RegExp("^"+v.source+"(?!"+A+")","i"),x=function(){var a=1,c=arguments.length-2;for(;a<c;a++)arguments[a]===b&&(n[a]=b)};for(;s<t;s++){f=a[s],g="",m=e;for(h=0,i=f.length;h<i;h++){j=f[h],k=j.string;if(j.part==="PSEUDO"){v.exec(""),l=0;while(n=v.exec(k)){o=!0,p=v.lastIndex=n.index+n[0].length;if(p>l){g+=k.slice(l,n.index),l=p,q=[c],J.test(g)&&(m&&(q=m),m=e);if(r=O.test(g))g=g.slice(0,-5).replace(J,"$&*"),l++;n.length>1&&n[0].replace(w,x),m=bi(g,n[1],n[2],q,m,r)}g=""}}o||(g+=k),o=!1}g?J.test(g)?bh(g,m||[c],d,e):$(g,c,d,e?e.concat(m):m):u.apply(d,m)}return t===1?d:$.uniqueSort(d)}function bk(a,b,e,g,h){a=a.replace(H,"$1");var i,k,l,m,n,o,p,q,r,s,v=bc(a,b,h),w=b.nodeType;if(S.POS.test(a))return bj(v,b,e,g);if(g)i=t.call(g,0);else if(v.length===1){if((o=t.call(v[0],0)).length>2&&(p=o[0]).part==="ID"&&w===9&&!h&&f.relative[o[1].part]){b=f.find.ID(p.captures[0].replace(R,""),b,h)[0];if(!b)return e;a=a.slice(o.shift().string.length)}r=(v=N.exec(o[0].string))&&!v.index&&b.parentNode||b,q="";for(n=o.length-1;n>=0;n--){p=o[n],s=p.part,q=p.string+q;if(f.relative[s])break;if(f.order.test(s)){i=f.find[s](p.captures[0].replace(R,""),r,h);if(i==null)continue;a=a.slice(0,a.length-q.length)+q.replace(S[s],""),a||u.apply(e,t.call(i,0));break}}}if(a){k=j(a,b,h),c=k.dirruns++,i==null&&(i=f.find.TAG("*",N.test(a)&&b.parentNode||b));for(n=0;m=i[n];n++)d=k.runs++,k(m)&&e.push(m)}return e}var c,d,e,f,g,h,i,j,k,l,m=!0,n="undefined",o=("sizcache"+Math.random()).replace(".",""),q=a.document,r=q.documentElement,s=0,t=[].slice,u=[].push,v=function(a,b){return a[o]=b||!0,a},w=function(){var a={},b=[];return v(function(c,d){return b.push(c)>f.cacheLength&&delete a[b.shift()],a[c]=d},a)},x=w(),y=w(),z=w(),A="[\\x20\\t\\r\\n\\f]",B="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",C=B.replace("w","w#"),D="([*^$|!~]?=)",E="\\["+A+"*("+B+")"+A+"*(?:"+D+A+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+C+")|)|)"+A+"*\\]",F=":("+B+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+E+")|[^:]|\\\\.)*|.*))\\)|)",G=":(nth|eq|gt|lt|first|last|even|odd)(?:\\(((?:-\\d)?\\d*)\\)|)(?=[^-]|$)",H=new RegExp("^"+A+"+|((?:^|[^\\\\])(?:\\\\.)*)"+A+"+$","g"),I=new RegExp("^"+A+"*,"+A+"*"),J=new RegExp("^"+A+"*([\\x20\\t\\r\\n\\f>+~])"+A+"*"),K=new RegExp(F),L=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,M=/^:not/,N=/[\x20\t\r\n\f]*[+~]/,O=/:not\($/,P=/h\d/i,Q=/input|select|textarea|button/i,R=/\\(?!\\)/g,S={ID:new RegExp("^#("+B+")"),CLASS:new RegExp("^\\.("+B+")"),NAME:new RegExp("^\\[name=['\"]?("+B+")['\"]?\\]"),TAG:new RegExp("^("+B.replace("w","w*")+")"),ATTR:new RegExp("^"+E),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|nth|last|first)-child(?:\\("+A+"*(even|odd|(([+-]|)(\\d*)n|)"+A+"*(?:([+-]|)"+A+"*(\\d+)|))"+A+"*\\)|)","i"),POS:new RegExp(G,"ig"),needsContext:new RegExp("^"+A+"*[>+~]|"+G,"i")},T=function(a){var b=q.createElement("div");try{return a(b)}catch(c){return!1}finally{b=null}},U=T(function(a){return a.appendChild(q.createComment("")),!a.getElementsByTagName("*").length}),V=T(function(a){return a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!==n&&a.firstChild.getAttribute("href")==="#"}),W=T(function(a){a.innerHTML="<select></select>";var b=typeof a.lastChild.getAttribute("multiple");return b!=="boolean"&&b!=="string"}),X=T(function(a){return a.innerHTML="<div class='hidden e'></div><div class='hidden'></div>",!a.getElementsByClassName||!a.getElementsByClassName("e").length?!1:(a.lastChild.className="e",a.getElementsByClassName("e").length===2)}),Y=T(function(a){a.id=o+0,a.innerHTML="<a name='"+o+"'></a><div name='"+o+"'></div>",r.insertBefore(a,r.firstChild);var b=q.getElementsByName&&q.getElementsByName(o).length===2+q.getElementsByName(o+0).length;return e=!q.getElementById(o),r.removeChild(a),b});try{t.call(r.childNodes,0)[0].nodeType}catch(Z){t=function(a){var b,c=[];for(;b=this[a];a++)c.push(b);return c}}$.matches=function(a,b){return $(a,null,null,b)},$.matchesSelector=function(a,b){return $(b,null,null,[a]).length>0},g=$.getText=function(a){var b,c="",d=0,e=a.nodeType;if(e){if(e===1||e===9||e===11){if(typeof a.textContent=="string")return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=g(a)}else if(e===3||e===4)return a.nodeValue}else for(;b=a[d];d++)c+=g(b);return c},h=$.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?b.nodeName!=="HTML":!1},i=$.contains=r.contains?function(a,b){var c=a.nodeType===9?a.documentElement:a,d=b&&b.parentNode;return a===d||!!(d&&d.nodeType===1&&c.contains&&c.contains(d))}:r.compareDocumentPosition?function(a,b){return b&&!!(a.compareDocumentPosition(b)&16)}:function(a,b){while(b=b.parentNode)if(b===a)return!0;return!1},$.attr=function(a,b){var c,d=h(a);return d||(b=b.toLowerCase()),f.attrHandle[b]?f.attrHandle[b](a):W||d?a.getAttribute(b):(c=a.getAttributeNode(b),c?typeof a[b]=="boolean"?a[b]?b:null:c.specified?c.value:null:null)},f=$.selectors={cacheLength:50,createPseudo:v,match:S,order:new RegExp("ID|TAG"+(Y?"|NAME":"")+(X?"|CLASS":"")),attrHandle:V?{}:{href:function(a){return a.getAttribute("href",2)},type:function(a){return a.getAttribute("type")}},find:{ID:e?function(a,b,c){if(typeof b.getElementById!==n&&!c){var d=b.getElementById(a);return d&&d.parentNode?[d]:[]}}:function(a,c,d){if(typeof c.getElementById!==n&&!d){var e=c.getElementById(a);return e?e.id===a||typeof e.getAttributeNode!==n&&e.getAttributeNode("id").value===a?[e]:b:[]}},TAG:U?function(a,b){if(typeof b.getElementsByTagName!==n)return b.getElementsByTagName(a)}:function(a,b){var c=b.getElementsByTagName(a);if(a==="*"){var d,e=[],f=0;for(;d=c[f];f++)d.nodeType===1&&e.push(d);return e}return c},NAME:function(a,b){if(typeof b.getElementsByName!==n)return b.getElementsByName(name)},CLASS:function(a,b,c){if(typeof b.getElementsByClassName!==n&&!c)return b.getElementsByClassName(a)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(R,""),a[3]=(a[4]||a[5]||"").replace(R,""),a[2]==="~="&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),a[1]==="nth"?(a[2]||$.error(a[0]),a[3]=+(a[3]?a[4]+(a[5]||1):2*(a[2]==="even"||a[2]==="odd")),a[4]=+(a[6]+a[7]||a[2]==="odd")):a[2]&&$.error(a[0]),a},PSEUDO:function(a,b,c){var d,e;if(S.CHILD.test(a[0]))return null;if(a[3])a[2]=a[3];else if(d=a[4])K.test(d)&&(e=bc(d,b,c,!0))&&(e=d.indexOf(")",d.length-e)-d.length)&&(d=d.slice(0,e),a[0]=a[0].slice(0,e)),a[2]=d;return a.slice(0,3)}},filter:{ID:e?function(a){return a=a.replace(R,""),function(b){return b.getAttribute("id")===a}}:function(a){return a=a.replace(R,""),function(b){var c=typeof b.getAttributeNode!==n&&b.getAttributeNode("id");return c&&c.value===a}},TAG:function(a){return a==="*"?function(){return!0}:(a=a.replace(R,"").toLowerCase(),function(b){return b.nodeName&&b.nodeName.toLowerCase()===a})},CLASS:function(a){var b=x[o][a];return b||(b=x(a,new RegExp("(^|"+A+")"+a+"("+A+"|$)"))),function(a){return b.test(a.className||typeof a.getAttribute!==n&&a.getAttribute("class")||"")}},ATTR:function(a,b,c){return b?function(d){var e=$.attr(d,a),f=e+"";if(e==null)return b==="!=";switch(b){case"=":return f===c;case"!=":return f!==c;case"^=":return c&&f.indexOf(c)===0;case"*=":return c&&f.indexOf(c)>-1;case"$=":return c&&f.substr(f.length-c.length)===c;case"~=":return(" "+f+" ").indexOf(c)>-1;case"|=":return f===c||f.substr(0,c.length+1)===c+"-"}}:function(b){return $.attr(b,a)!=null}},CHILD:function(a,b,c,d){if(a==="nth"){var e=s++;return function(a){var b,f,g=0,h=a;if(c===1&&d===0)return!0;b=a.parentNode;if(b&&(b[o]!==e||!a.sizset)){for(h=b.firstChild;h;h=h.nextSibling)if(h.nodeType===1){h.sizset=++g;if(h===a)break}b[o]=e}return f=a.sizset-d,c===0?f===0:f%c===0&&f/c>=0}}return function(b){var c=b;switch(a){case"only":case"first":while(c=c.previousSibling)if(c.nodeType===1)return!1;if(a==="first")return!0;c=b;case"last":while(c=c.nextSibling)if(c.nodeType===1)return!1;return!0}}},PSEUDO:function(a,b,c,d){var e,g=f.pseudos[a]||f.pseudos[a.toLowerCase()];return g||$.error("unsupported pseudo: "+a),g[o]?g(b,c,d):g.length>1?(e=[a,a,"",b],function(a){return g(a,0,e)}):g}},pseudos:{not:v(function(a,b,c){var d=j(a.replace(H,"$1"),b,c);return function(a){return!d(a)}}),enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&!!a.checked||b==="option"&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},parent:function(a){return!f.pseudos.empty(a)},empty:function(a){var b;a=a.firstChild;while(a){if(a.nodeName>"@"||(b=a.nodeType)===3||b===4)return!1;a=a.nextSibling}return!0},contains:v(function(a){return function(b){return(b.textContent||b.innerText||g(b)).indexOf(a)>-1}}),has:v(function(a){return function(b){return $(a,b).length>0}}),header:function(a){return P.test(a.nodeName)},text:function(a){var b,c;return a.nodeName.toLowerCase()==="input"&&(b=a.type)==="text"&&((c=a.getAttribute("type"))==null||c.toLowerCase()===b)},radio:_("radio"),checkbox:_("checkbox"),file:_("file"),password:_("password"),image:_("image"),submit:ba("submit"),reset:ba("reset"),button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&a.type==="button"||b==="button"},input:function(a){return Q.test(a.nodeName)},focus:function(a){var b=a.ownerDocument;return a===b.activeElement&&(!b.hasFocus||b.hasFocus())&&(!!a.type||!!a.href)},active:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b,c){return c?a.slice(1):[a[0]]},last:function(a,b,c){var d=a.pop();return c?a:[d]},even:function(a,b,c){var d=[],e=c?1:0,f=a.length;for(;e<f;e=e+2)d.push(a[e]);return d},odd:function(a,b,c){var d=[],e=c?0:1,f=a.length;for(;e<f;e=e+2)d.push(a[e]);return d},lt:function(a,b,c){return c?a.slice(+b):a.slice(0,+b)},gt:function(a,b,c){return c?a.slice(0,+b+1):a.slice(+b+1)},eq:function(a,b,c){var d=a.splice(+b,1);return c?a:d}}},k=r.compareDocumentPosition?function(a,b){return a===b?(l=!0,0):(!a.compareDocumentPosition||!b.compareDocumentPosition?a.compareDocumentPosition:a.compareDocumentPosition(b)&4)?-1:1}:function(a,b){if(a===b)return l=!0,0;if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],g=a.parentNode,h=b.parentNode,i=g;if(g===h)return bb(a,b);if(!g)return-1;if(!h)return 1;while(i)e.unshift(i),i=i.parentNode;i=h;while(i)f.unshift(i),i=i.parentNode;c=e.length,d=f.length;for(var j=0;j<c&&j<d;j++)if(e[j]!==f[j])return bb(e[j],f[j]);return j===c?bb(a,f[j],-1):bb(e[j],b,1)},[0,0].sort(k),m=!l,$.uniqueSort=function(a){var b,c=1;l=m,a.sort(k);if(l)for(;b=a[c];c++)b===a[c-1]&&a.splice(c--,1);return a},$.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},j=$.compile=function(a,b,c){var d,e,f,g=z[o][a];if(g&&g.context===b)return g;d=bc(a,b,c);for(e=0,f=d.length;e<f;e++)d[e]=bf(d[e],b,c);return g=z(a,bg(d)),g.context=b,g.runs=g.dirruns=0,g},q.querySelectorAll&&function(){var a,b=bk,c=/'|\\/g,d=/\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,e=[],f=[":active"],g=r.matchesSelector||r.mozMatchesSelector||r.webkitMatchesSelector||r.oMatchesSelector||r.msMatchesSelector;T(function(a){a.innerHTML="<select><option selected=''></option></select>",a.querySelectorAll("[selected]").length||e.push("\\["+A+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),a.querySelectorAll(":checked").length||e.push(":checked")}),T(function(a){a.innerHTML="<p test=''></p>",a.querySelectorAll("[test^='']").length&&e.push("[*^$]="+A+"*(?:\"\"|'')"),a.innerHTML="<input type='hidden'/>",a.querySelectorAll(":enabled").length||e.push(":enabled",":disabled")}),e=e.length&&new RegExp(e.join("|")),bk=function(a,d,f,g,h){if(!g&&!h&&(!e||!e.test(a)))if(d.nodeType===9)try{return u.apply(f,t.call(d.querySelectorAll(a),0)),f}catch(i){}else if(d.nodeType===1&&d.nodeName.toLowerCase()!=="object"){var j,k,l,m=d.getAttribute("id"),n=m||o,p=N.test(a)&&d.parentNode||d;m?n=n.replace(c,"\\$&"):d.setAttribute("id",n),j=bc(a,d,h),n="[id='"+n+"']";for(k=0,l=j.length;k<l;k++)j[k]=n+j[k].selector;try{return u.apply(f,t.call(p.querySelectorAll(j.join(",")),0)),f}catch(i){}finally{m||d.removeAttribute("id")}}return b(a,d,f,g,h)},g&&(T(function(b){a=g.call(b,"div");try{g.call(b,"[test!='']:sizzle"),f.push(S.PSEUDO.source,S.POS.source,"!=")}catch(c){}}),f=new RegExp(f.join("|")),$.matchesSelector=function(b,c){c=c.replace(d,"='$1']");if(!h(b)&&!f.test(c)&&(!e||!e.test(c)))try{var i=g.call(b,c);if(i||a||b.document&&b.document.nodeType!==11)return i}catch(j){}return $(c,null,null,[b]).length>0})}(),f.setFilters.nth=f.setFilters.eq,f.filters=f.pseudos,$.attr=p.attr,p.find=$,p.expr=$.selectors,p.expr[":"]=p.expr.pseudos,p.unique=$.uniqueSort,p.text=$.getText,p.isXMLDoc=$.isXML,p.contains=$.contains}(a);var bc=/Until$/,bd=/^(?:parents|prev(?:Until|All))/,be=/^.[^:#\[\.,]*$/,bf=p.expr.match.needsContext,bg={children:!0,contents:!0,next:!0,prev:!0};p.fn.extend({find:function(a){var b,c,d,e,f,g,h=this;if(typeof a!="string")return p(a).filter(function(){for(b=0,c=h.length;b<c;b++)if(p.contains(h[b],this))return!0});g=this.pushStack("","find",a);for(b=0,c=this.length;b<c;b++){d=g.length,p.find(a,this[b],g);if(b>0)for(e=d;e<g.length;e++)for(f=0;f<d;f++)if(g[f]===g[e]){g.splice(e--,1);break}}return g},has:function(a){var b,c=p(a,this),d=c.length;return this.filter(function(){for(b=0;b<d;b++)if(p.contains(this,c[b]))return!0})},not:function(a){return this.pushStack(bj(this,a,!1),"not",a)},filter:function(a){return this.pushStack(bj(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?bf.test(a)?p(a,this.context).index(this[0])>=0:p.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c,d=0,e=this.length,f=[],g=bf.test(a)||typeof a!="string"?p(a,b||this.context):0;for(;d<e;d++){c=this[d];while(c&&c.ownerDocument&&c!==b&&c.nodeType!==11){if(g?g.index(c)>-1:p.find.matchesSelector(c,a)){f.push(c);break}c=c.parentNode}}return f=f.length>1?p.unique(f):f,this.pushStack(f,"closest",a)},index:function(a){return a?typeof a=="string"?p.inArray(this[0],p(a)):p.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(a,b){var c=typeof a=="string"?p(a,b):p.makeArray(a&&a.nodeType?[a]:a),d=p.merge(this.get(),c);return this.pushStack(bh(c[0])||bh(d[0])?d:p.unique(d))},addBack:function(a){return this.add(a==null?this.prevObject:this.prevObject.filter(a))}}),p.fn.andSelf=p.fn.addBack,p.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return p.dir(a,"parentNode")},parentsUntil:function(a,b,c){return p.dir(a,"parentNode",c)},next:function(a){return bi(a,"nextSibling")},prev:function(a){return bi(a,"previousSibling")},nextAll:function(a){return p.dir(a,"nextSibling")},prevAll:function(a){return p.dir(a,"previousSibling")},nextUntil:function(a,b,c){return p.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return p.dir(a,"previousSibling",c)},siblings:function(a){return p.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return p.sibling(a.firstChild)},contents:function(a){return p.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:p.merge([],a.childNodes)}},function(a,b){p.fn[a]=function(c,d){var e=p.map(this,b,c);return bc.test(a)||(d=c),d&&typeof d=="string"&&(e=p.filter(d,e)),e=this.length>1&&!bg[a]?p.unique(e):e,this.length>1&&bd.test(a)&&(e=e.reverse()),this.pushStack(e,a,k.call(arguments).join(","))}}),p.extend({filter:function(a,b,c){return c&&(a=":not("+a+")"),b.length===1?p.find.matchesSelector(b[0],a)?[b[0]]:[]:p.find.matches(a,b)},dir:function(a,c,d){var e=[],f=a[c];while(f&&f.nodeType!==9&&(d===b||f.nodeType!==1||!p(f).is(d)))f.nodeType===1&&e.push(f),f=f[c];return e},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var bl="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",bm=/ jQuery\d+="(?:null|\d+)"/g,bn=/^\s+/,bo=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bp=/<([\w:]+)/,bq=/<tbody/i,br=/<|&#?\w+;/,bs=/<(?:script|style|link)/i,bt=/<(?:script|object|embed|option|style)/i,bu=new RegExp("<(?:"+bl+")[\\s/>]","i"),bv=/^(?:checkbox|radio)$/,bw=/checked\s*(?:[^=]|=\s*.checked.)/i,bx=/\/(java|ecma)script/i,by=/^\s*<!(?:\[CDATA\[|\-\-)|[\]\-]{2}>\s*$/g,bz={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},bA=bk(e),bB=bA.appendChild(e.createElement("div"));bz.optgroup=bz.option,bz.tbody=bz.tfoot=bz.colgroup=bz.caption=bz.thead,bz.th=bz.td,p.support.htmlSerialize||(bz._default=[1,"X<div>","</div>"]),p.fn.extend({text:function(a){return p.access(this,function(a){return a===b?p.text(this):this.empty().append((this[0]&&this[0].ownerDocument||e).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(p.isFunction(a))return this.each(function(b){p(this).wrapAll(a.call(this,b))});if(this[0]){var b=p(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){return p.isFunction(a)?this.each(function(b){p(this).wrapInner(a.call(this,b))}):this.each(function(){var b=p(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=p.isFunction(a);return this.each(function(c){p(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){p.nodeName(this,"body")||p(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(a,this.firstChild)})},before:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(a,this),"before",this.selector)}},after:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(this,a),"after",this.selector)}},remove:function(a,b){var c,d=0;for(;(c=this[d])!=null;d++)if(!a||p.filter(a,[c]).length)!b&&c.nodeType===1&&(p.cleanData(c.getElementsByTagName("*")),p.cleanData([c])),c.parentNode&&c.parentNode.removeChild(c);return this},empty:function(){var a,b=0;for(;(a=this[b])!=null;b++){a.nodeType===1&&p.cleanData(a.getElementsByTagName("*"));while(a.firstChild)a.removeChild(a.firstChild)}return this},clone:function(a,b){return a=a==null?!1:a,b=b==null?a:b,this.map(function(){return p.clone(this,a,b)})},html:function(a){return p.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(bm,""):b;if(typeof a=="string"&&!bs.test(a)&&(p.support.htmlSerialize||!bu.test(a))&&(p.support.leadingWhitespace||!bn.test(a))&&!bz[(bp.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(bo,"<$1></$2>");try{for(;d<e;d++)c=this[d]||{},c.nodeType===1&&(p.cleanData(c.getElementsByTagName("*")),c.innerHTML=a);c=0}catch(f){}}c&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(a){return bh(this[0])?this.length?this.pushStack(p(p.isFunction(a)?a():a),"replaceWith",a):this:p.isFunction(a)?this.each(function(b){var c=p(this),d=c.html();c.replaceWith(a.call(this,b,d))}):(typeof a!="string"&&(a=p(a).detach()),this.each(function(){var b=this.nextSibling,c=this.parentNode;p(this).remove(),b?p(b).before(a):p(c).append(a)}))},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){a=[].concat.apply([],a);var e,f,g,h,i=0,j=a[0],k=[],l=this.length;if(!p.support.checkClone&&l>1&&typeof j=="string"&&bw.test(j))return this.each(function(){p(this).domManip(a,c,d)});if(p.isFunction(j))return this.each(function(e){var f=p(this);a[0]=j.call(this,e,c?f.html():b),f.domManip(a,c,d)});if(this[0]){e=p.buildFragment(a,this,k),g=e.fragment,f=g.firstChild,g.childNodes.length===1&&(g=f);if(f){c=c&&p.nodeName(f,"tr");for(h=e.cacheable||l-1;i<l;i++)d.call(c&&p.nodeName(this[i],"table")?bC(this[i],"tbody"):this[i],i===h?g:p.clone(g,!0,!0))}g=f=null,k.length&&p.each(k,function(a,b){b.src?p.ajax?p.ajax({url:b.src,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0}):p.error("no ajax"):p.globalEval((b.text||b.textContent||b.innerHTML||"").replace(by,"")),b.parentNode&&b.parentNode.removeChild(b)})}return this}}),p.buildFragment=function(a,c,d){var f,g,h,i=a[0];return c=c||e,c=!c.nodeType&&c[0]||c,c=c.ownerDocument||c,a.length===1&&typeof i=="string"&&i.length<512&&c===e&&i.charAt(0)==="<"&&!bt.test(i)&&(p.support.checkClone||!bw.test(i))&&(p.support.html5Clone||!bu.test(i))&&(g=!0,f=p.fragments[i],h=f!==b),f||(f=c.createDocumentFragment(),p.clean(a,c,f,d),g&&(p.fragments[i]=h&&f)),{fragment:f,cacheable:g}},p.fragments={},p.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){p.fn[a]=function(c){var d,e=0,f=[],g=p(c),h=g.length,i=this.length===1&&this[0].parentNode;if((i==null||i&&i.nodeType===11&&i.childNodes.length===1)&&h===1)return g[b](this[0]),this;for(;e<h;e++)d=(e>0?this.clone(!0):this).get(),p(g[e])[b](d),f=f.concat(d);return this.pushStack(f,a,g.selector)}}),p.extend({clone:function(a,b,c){var d,e,f,g;p.support.html5Clone||p.isXMLDoc(a)||!bu.test("<"+a.nodeName+">")?g=a.cloneNode(!0):(bB.innerHTML=a.outerHTML,bB.removeChild(g=bB.firstChild));if((!p.support.noCloneEvent||!p.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!p.isXMLDoc(a)){bE(a,g),d=bF(a),e=bF(g);for(f=0;d[f];++f)e[f]&&bE(d[f],e[f])}if(b){bD(a,g);if(c){d=bF(a),e=bF(g);for(f=0;d[f];++f)bD(d[f],e[f])}}return d=e=null,g},clean:function(a,b,c,d){var f,g,h,i,j,k,l,m,n,o,q,r,s=b===e&&bA,t=[];if(!b||typeof b.createDocumentFragment=="undefined")b=e;for(f=0;(h=a[f])!=null;f++){typeof h=="number"&&(h+="");if(!h)continue;if(typeof h=="string")if(!br.test(h))h=b.createTextNode(h);else{s=s||bk(b),l=b.createElement("div"),s.appendChild(l),h=h.replace(bo,"<$1></$2>"),i=(bp.exec(h)||["",""])[1].toLowerCase(),j=bz[i]||bz._default,k=j[0],l.innerHTML=j[1]+h+j[2];while(k--)l=l.lastChild;if(!p.support.tbody){m=bq.test(h),n=i==="table"&&!m?l.firstChild&&l.firstChild.childNodes:j[1]==="<table>"&&!m?l.childNodes:[];for(g=n.length-1;g>=0;--g)p.nodeName(n[g],"tbody")&&!n[g].childNodes.length&&n[g].parentNode.removeChild(n[g])}!p.support.leadingWhitespace&&bn.test(h)&&l.insertBefore(b.createTextNode(bn.exec(h)[0]),l.firstChild),h=l.childNodes,l.parentNode.removeChild(l)}h.nodeType?t.push(h):p.merge(t,h)}l&&(h=l=s=null);if(!p.support.appendChecked)for(f=0;(h=t[f])!=null;f++)p.nodeName(h,"input")?bG(h):typeof h.getElementsByTagName!="undefined"&&p.grep(h.getElementsByTagName("input"),bG);if(c){q=function(a){if(!a.type||bx.test(a.type))return d?d.push(a.parentNode?a.parentNode.removeChild(a):a):c.appendChild(a)};for(f=0;(h=t[f])!=null;f++)if(!p.nodeName(h,"script")||!q(h))c.appendChild(h),typeof h.getElementsByTagName!="undefined"&&(r=p.grep(p.merge([],h.getElementsByTagName("script")),q),t.splice.apply(t,[f+1,0].concat(r)),f+=r.length)}return t},cleanData:function(a,b){var c,d,e,f,g=0,h=p.expando,i=p.cache,j=p.support.deleteExpando,k=p.event.special;for(;(e=a[g])!=null;g++)if(b||p.acceptData(e)){d=e[h],c=d&&i[d];if(c){if(c.events)for(f in c.events)k[f]?p.event.remove(e,f):p.removeEvent(e,f,c.handle);i[d]&&(delete i[d],j?delete e[h]:e.removeAttribute?e.removeAttribute(h):e[h]=null,p.deletedIds.push(d))}}}}),function(){var a,b;p.uaMatch=function(a){a=a.toLowerCase();var b=/(chrome)[ \/]([\w.]+)/.exec(a)||/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||a.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},a=p.uaMatch(g.userAgent),b={},a.browser&&(b[a.browser]=!0,b.version=a.version),b.chrome?b.webkit=!0:b.webkit&&(b.safari=!0),p.browser=b,p.sub=function(){function a(b,c){return new a.fn.init(b,c)}p.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function c(c,d){return d&&d instanceof p&&!(d instanceof a)&&(d=a(d)),p.fn.init.call(this,c,d,b)},a.fn.init.prototype=a.fn;var b=a(e);return a}}();var bH,bI,bJ,bK=/alpha\([^)]*\)/i,bL=/opacity=([^)]*)/,bM=/^(top|right|bottom|left)$/,bN=/^(none|table(?!-c[ea]).+)/,bO=/^margin/,bP=new RegExp("^("+q+")(.*)$","i"),bQ=new RegExp("^("+q+")(?!px)[a-z%]+$","i"),bR=new RegExp("^([-+])=("+q+")","i"),bS={},bT={position:"absolute",visibility:"hidden",display:"block"},bU={letterSpacing:0,fontWeight:400},bV=["Top","Right","Bottom","Left"],bW=["Webkit","O","Moz","ms"],bX=p.fn.toggle;p.fn.extend({css:function(a,c){return p.access(this,function(a,c,d){return d!==b?p.style(a,c,d):p.css(a,c)},a,c,arguments.length>1)},show:function(){return b$(this,!0)},hide:function(){return b$(this)},toggle:function(a,b){var c=typeof a=="boolean";return p.isFunction(a)&&p.isFunction(b)?bX.apply(this,arguments):this.each(function(){(c?a:bZ(this))?p(this).show():p(this).hide()})}}),p.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bH(a,"opacity");return c===""?"1":c}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":p.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!a||a.nodeType===3||a.nodeType===8||!a.style)return;var f,g,h,i=p.camelCase(c),j=a.style;c=p.cssProps[i]||(p.cssProps[i]=bY(j,i)),h=p.cssHooks[c]||p.cssHooks[i];if(d===b)return h&&"get"in h&&(f=h.get(a,!1,e))!==b?f:j[c];g=typeof d,g==="string"&&(f=bR.exec(d))&&(d=(f[1]+1)*f[2]+parseFloat(p.css(a,c)),g="number");if(d==null||g==="number"&&isNaN(d))return;g==="number"&&!p.cssNumber[i]&&(d+="px");if(!h||!("set"in h)||(d=h.set(a,d,e))!==b)try{j[c]=d}catch(k){}},css:function(a,c,d,e){var f,g,h,i=p.camelCase(c);return c=p.cssProps[i]||(p.cssProps[i]=bY(a.style,i)),h=p.cssHooks[c]||p.cssHooks[i],h&&"get"in h&&(f=h.get(a,!0,e)),f===b&&(f=bH(a,c)),f==="normal"&&c in bU&&(f=bU[c]),d||e!==b?(g=parseFloat(f),d||p.isNumeric(g)?g||0:f):f},swap:function(a,b,c){var d,e,f={};for(e in b)f[e]=a.style[e],a.style[e]=b[e];d=c.call(a);for(e in b)a.style[e]=f[e];return d}}),a.getComputedStyle?bH=function(b,c){var d,e,f,g,h=a.getComputedStyle(b,null),i=b.style;return h&&(d=h[c],d===""&&!p.contains(b.ownerDocument,b)&&(d=p.style(b,c)),bQ.test(d)&&bO.test(c)&&(e=i.width,f=i.minWidth,g=i.maxWidth,i.minWidth=i.maxWidth=i.width=d,d=h.width,i.width=e,i.minWidth=f,i.maxWidth=g)),d}:e.documentElement.currentStyle&&(bH=function(a,b){var c,d,e=a.currentStyle&&a.currentStyle[b],f=a.style;return e==null&&f&&f[b]&&(e=f[b]),bQ.test(e)&&!bM.test(b)&&(c=f.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":e,e=f.pixelLeft+"px",f.left=c,d&&(a.runtimeStyle.left=d)),e===""?"auto":e}),p.each(["height","width"],function(a,b){p.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth===0&&bN.test(bH(a,"display"))?p.swap(a,bT,function(){return cb(a,b,d)}):cb(a,b,d)},set:function(a,c,d){return b_(a,c,d?ca(a,b,d,p.support.boxSizing&&p.css(a,"boxSizing")==="border-box"):0)}}}),p.support.opacity||(p.cssHooks.opacity={get:function(a,b){return bL.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=p.isNumeric(b)?"alpha(opacity="+b*100+")":"",f=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&p.trim(f.replace(bK,""))===""&&c.removeAttribute){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bK.test(f)?f.replace(bK,e):f+" "+e}}),p(function(){p.support.reliableMarginRight||(p.cssHooks.marginRight={get:function(a,b){return p.swap(a,{display:"inline-block"},function(){if(b)return bH(a,"marginRight")})}}),!p.support.pixelPosition&&p.fn.position&&p.each(["top","left"],function(a,b){p.cssHooks[b]={get:function(a,c){if(c){var d=bH(a,b);return bQ.test(d)?p(a).position()[b]+"px":d}}}})}),p.expr&&p.expr.filters&&(p.expr.filters.hidden=function(a){return a.offsetWidth===0&&a.offsetHeight===0||!p.support.reliableHiddenOffsets&&(a.style&&a.style.display||bH(a,"display"))==="none"},p.expr.filters.visible=function(a){return!p.expr.filters.hidden(a)}),p.each({margin:"",padding:"",border:"Width"},function(a,b){p.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bV[d]+b]=e[d]||e[d-2]||e[0];return f}},bO.test(a)||(p.cssHooks[a+b].set=b_)});var cd=/%20/g,ce=/\[\]$/,cf=/\r?\n/g,cg=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,ch=/^(?:select|textarea)/i;p.fn.extend({serialize:function(){return p.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?p.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ch.test(this.nodeName)||cg.test(this.type))}).map(function(a,b){var c=p(this).val();return c==null?null:p.isArray(c)?p.map(c,function(a,c){return{name:b.name,value:a.replace(cf,"\r\n")}}):{name:b.name,value:c.replace(cf,"\r\n")}}).get()}}),p.param=function(a,c){var d,e=[],f=function(a,b){b=p.isFunction(b)?b():b==null?"":b,e[e.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=p.ajaxSettings&&p.ajaxSettings.traditional);if(p.isArray(a)||a.jquery&&!p.isPlainObject(a))p.each(a,function(){f(this.name,this.value)});else for(d in a)ci(d,a[d],c,f);return e.join("&").replace(cd,"+")};var cj,ck,cl=/#.*$/,cm=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,cn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,co=/^(?:GET|HEAD)$/,cp=/^\/\//,cq=/\?/,cr=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,cs=/([?&])_=[^&]*/,ct=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,cu=p.fn.load,cv={},cw={},cx=["*/"]+["*"];try{cj=f.href}catch(cy){cj=e.createElement("a"),cj.href="",cj=cj.href}ck=ct.exec(cj.toLowerCase())||[],p.fn.load=function(a,c,d){if(typeof a!="string"&&cu)return cu.apply(this,arguments);if(!this.length)return this;var e,f,g,h=this,i=a.indexOf(" ");return i>=0&&(e=a.slice(i,a.length),a=a.slice(0,i)),p.isFunction(c)?(d=c,c=b):c&&typeof c=="object"&&(f="POST"),p.ajax({url:a,type:f,dataType:"html",data:c,complete:function(a,b){d&&h.each(d,g||[a.responseText,b,a])}}).done(function(a){g=arguments,h.html(e?p("<div>").append(a.replace(cr,"")).find(e):a)}),this},p.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){p.fn[b]=function(a){return this.on(b,a)}}),p.each(["get","post"],function(a,c){p[c]=function(a,d,e,f){return p.isFunction(d)&&(f=f||e,e=d,d=b),p.ajax({type:c,url:a,data:d,success:e,dataType:f})}}),p.extend({getScript:function(a,c){return p.get(a,b,c,"script")},getJSON:function(a,b,c){return p.get(a,b,c,"json")},ajaxSetup:function(a,b){return b?cB(a,p.ajaxSettings):(b=a,a=p.ajaxSettings),cB(a,b),a},ajaxSettings:{url:cj,isLocal:cn.test(ck[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":cx},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":p.parseJSON,"text xml":p.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:cz(cv),ajaxTransport:cz(cw),ajax:function(a,c){function y(a,c,f,i){var k,s,t,u,w,y=c;if(v===2)return;v=2,h&&clearTimeout(h),g=b,e=i||"",x.readyState=a>0?4:0,f&&(u=cC(l,x,f));if(a>=200&&a<300||a===304)l.ifModified&&(w=x.getResponseHeader("Last-Modified"),w&&(p.lastModified[d]=w),w=x.getResponseHeader("Etag"),w&&(p.etag[d]=w)),a===304?(y="notmodified",k=!0):(k=cD(l,u),y=k.state,s=k.data,t=k.error,k=!t);else{t=y;if(!y||a)y="error",a<0&&(a=0)}x.status=a,x.statusText=""+(c||y),k?o.resolveWith(m,[s,y,x]):o.rejectWith(m,[x,y,t]),x.statusCode(r),r=b,j&&n.trigger("ajax"+(k?"Success":"Error"),[x,l,k?s:t]),q.fireWith(m,[x,y]),j&&(n.trigger("ajaxComplete",[x,l]),--p.active||p.event.trigger("ajaxStop"))}typeof a=="object"&&(c=a,a=b),c=c||{};var d,e,f,g,h,i,j,k,l=p.ajaxSetup({},c),m=l.context||l,n=m!==l&&(m.nodeType||m instanceof p)?p(m):p.event,o=p.Deferred(),q=p.Callbacks("once memory"),r=l.statusCode||{},t={},u={},v=0,w="canceled",x={readyState:0,setRequestHeader:function(a,b){if(!v){var c=a.toLowerCase();a=u[c]=u[c]||a,t[a]=b}return this},getAllResponseHeaders:function(){return v===2?e:null},getResponseHeader:function(a){var c;if(v===2){if(!f){f={};while(c=cm.exec(e))f[c[1].toLowerCase()]=c[2]}c=f[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){return v||(l.mimeType=a),this},abort:function(a){return a=a||w,g&&g.abort(a),y(0,a),this}};o.promise(x),x.success=x.done,x.error=x.fail,x.complete=q.add,x.statusCode=function(a){if(a){var b;if(v<2)for(b in a)r[b]=[r[b],a[b]];else b=a[x.status],x.always(b)}return this},l.url=((a||l.url)+"").replace(cl,"").replace(cp,ck[1]+"//"),l.dataTypes=p.trim(l.dataType||"*").toLowerCase().split(s),l.crossDomain==null&&(i=ct.exec(l.url.toLowerCase()),l.crossDomain=!(!i||i[1]==ck[1]&&i[2]==ck[2]&&(i[3]||(i[1]==="http:"?80:443))==(ck[3]||(ck[1]==="http:"?80:443)))),l.data&&l.processData&&typeof l.data!="string"&&(l.data=p.param(l.data,l.traditional)),cA(cv,l,c,x);if(v===2)return x;j=l.global,l.type=l.type.toUpperCase(),l.hasContent=!co.test(l.type),j&&p.active++===0&&p.event.trigger("ajaxStart");if(!l.hasContent){l.data&&(l.url+=(cq.test(l.url)?"&":"?")+l.data,delete l.data),d=l.url;if(l.cache===!1){var z=p.now(),A=l.url.replace(cs,"$1_="+z);l.url=A+(A===l.url?(cq.test(l.url)?"&":"?")+"_="+z:"")}}(l.data&&l.hasContent&&l.contentType!==!1||c.contentType)&&x.setRequestHeader("Content-Type",l.contentType),l.ifModified&&(d=d||l.url,p.lastModified[d]&&x.setRequestHeader("If-Modified-Since",p.lastModified[d]),p.etag[d]&&x.setRequestHeader("If-None-Match",p.etag[d])),x.setRequestHeader("Accept",l.dataTypes[0]&&l.accepts[l.dataTypes[0]]?l.accepts[l.dataTypes[0]]+(l.dataTypes[0]!=="*"?", "+cx+"; q=0.01":""):l.accepts["*"]);for(k in l.headers)x.setRequestHeader(k,l.headers[k]);if(!l.beforeSend||l.beforeSend.call(m,x,l)!==!1&&v!==2){w="abort";for(k in{success:1,error:1,complete:1})x[k](l[k]);g=cA(cw,l,c,x);if(!g)y(-1,"No Transport");else{x.readyState=1,j&&n.trigger("ajaxSend",[x,l]),l.async&&l.timeout>0&&(h=setTimeout(function(){x.abort("timeout")},l.timeout));try{v=1,g.send(t,y)}catch(B){if(v<2)y(-1,B);else throw B}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var cE=[],cF=/\?/,cG=/(=)\?(?=&|$)|\?\?/,cH=p.now();p.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=cE.pop()||p.expando+"_"+cH++;return this[a]=!0,a}}),p.ajaxPrefilter("json jsonp",function(c,d,e){var f,g,h,i=c.data,j=c.url,k=c.jsonp!==!1,l=k&&cG.test(j),m=k&&!l&&typeof i=="string"&&!(c.contentType||"").indexOf("application/x-www-form-urlencoded")&&cG.test(i);if(c.dataTypes[0]==="jsonp"||l||m)return f=c.jsonpCallback=p.isFunction(c.jsonpCallback)?c.jsonpCallback():c.jsonpCallback,g=a[f],l?c.url=j.replace(cG,"$1"+f):m?c.data=i.replace(cG,"$1"+f):k&&(c.url+=(cF.test(j)?"&":"?")+c.jsonp+"="+f),c.converters["script json"]=function(){return h||p.error(f+" was not called"),h[0]},c.dataTypes[0]="json",a[f]=function(){h=arguments},e.always(function(){a[f]=g,c[f]&&(c.jsonpCallback=d.jsonpCallback,cE.push(f)),h&&p.isFunction(g)&&g(h[0]),h=g=b}),"script"}),p.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){return p.globalEval(a),a}}}),p.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),p.ajaxTransport("script",function(a){if(a.crossDomain){var c,d=e.head||e.getElementsByTagName("head")[0]||e.documentElement;return{send:function(f,g){c=e.createElement("script"),c.async="async",a.scriptCharset&&(c.charset=a.scriptCharset),c.src=a.url,c.onload=c.onreadystatechange=function(a,e){if(e||!c.readyState||/loaded|complete/.test(c.readyState))c.onload=c.onreadystatechange=null,d&&c.parentNode&&d.removeChild(c),c=b,e||g(200,"success")},d.insertBefore(c,d.firstChild)},abort:function(){c&&c.onload(0,1)}}}});var cI,cJ=a.ActiveXObject?function(){for(var a in cI)cI[a](0,1)}:!1,cK=0;p.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&cL()||cM()}:cL,function(a){p.extend(p.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(p.ajaxSettings.xhr()),p.support.ajax&&p.ajaxTransport(function(c){if(!c.crossDomain||p.support.cors){var d;return{send:function(e,f){var g,h,i=c.xhr();c.username?i.open(c.type,c.url,c.async,c.username,c.password):i.open(c.type,c.url,c.async);if(c.xhrFields)for(h in c.xhrFields)i[h]=c.xhrFields[h];c.mimeType&&i.overrideMimeType&&i.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(h in e)i.setRequestHeader(h,e[h])}catch(j){}i.send(c.hasContent&&c.data||null),d=function(a,e){var h,j,k,l,m;try{if(d&&(e||i.readyState===4)){d=b,g&&(i.onreadystatechange=p.noop,cJ&&delete cI[g]);if(e)i.readyState!==4&&i.abort();else{h=i.status,k=i.getAllResponseHeaders(),l={},m=i.responseXML,m&&m.documentElement&&(l.xml=m);try{l.text=i.responseText}catch(a){}try{j=i.statusText}catch(n){j=""}!h&&c.isLocal&&!c.crossDomain?h=l.text?200:404:h===1223&&(h=204)}}}catch(o){e||f(-1,o)}l&&f(h,j,l,k)},c.async?i.readyState===4?setTimeout(d,0):(g=++cK,cJ&&(cI||(cI={},p(a).unload(cJ)),cI[g]=d),i.onreadystatechange=d):d()},abort:function(){d&&d(0,1)}}}});var cN,cO,cP=/^(?:toggle|show|hide)$/,cQ=new RegExp("^(?:([-+])=|)("+q+")([a-z%]*)$","i"),cR=/queueHooks$/,cS=[cY],cT={"*":[function(a,b){var c,d,e,f=this.createTween(a,b),g=cQ.exec(b),h=f.cur(),i=+h||0,j=1;if(g){c=+g[2],d=g[3]||(p.cssNumber[a]?"":"px");if(d!=="px"&&i){i=p.css(f.elem,a,!0)||c||1;do e=j=j||".5",i=i/j,p.style(f.elem,a,i+d),j=f.cur()/h;while(j!==1&&j!==e)}f.unit=d,f.start=i,f.end=g[1]?i+(g[1]+1)*c:c}return f}]};p.Animation=p.extend(cW,{tweener:function(a,b){p.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");var c,d=0,e=a.length;for(;d<e;d++)c=a[d],cT[c]=cT[c]||[],cT[c].unshift(b)},prefilter:function(a,b){b?cS.unshift(a):cS.push(a)}}),p.Tween=cZ,cZ.prototype={constructor:cZ,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(p.cssNumber[c]?"":"px")},cur:function(){var a=cZ.propHooks[this.prop];return a&&a.get?a.get(this):cZ.propHooks._default.get(this)},run:function(a){var b,c=cZ.propHooks[this.prop];return this.options.duration?this.pos=b=p.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):cZ.propHooks._default.set(this),this}},cZ.prototype.init.prototype=cZ.prototype,cZ.propHooks={_default:{get:function(a){var b;return a.elem[a.prop]==null||!!a.elem.style&&a.elem.style[a.prop]!=null?(b=p.css(a.elem,a.prop,!1,""),!b||b==="auto"?0:b):a.elem[a.prop]},set:function(a){p.fx.step[a.prop]?p.fx.step[a.prop](a):a.elem.style&&(a.elem.style[p.cssProps[a.prop]]!=null||p.cssHooks[a.prop])?p.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},cZ.propHooks.scrollTop=cZ.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},p.each(["toggle","show","hide"],function(a,b){var c=p.fn[b];p.fn[b]=function(d,e,f){return d==null||typeof d=="boolean"||!a&&p.isFunction(d)&&p.isFunction(e)?c.apply(this,arguments):this.animate(c$(b,!0),d,e,f)}}),p.fn.extend({fadeTo:function(a,b,c,d){return this.filter(bZ).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=p.isEmptyObject(a),f=p.speed(b,c,d),g=function(){var b=cW(this,p.extend({},a),f);e&&b.stop(!0)};return e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,c,d){var e=function(a){var b=a.stop;delete a.stop,b(d)};return typeof a!="string"&&(d=c,c=a,a=b),c&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,c=a!=null&&a+"queueHooks",f=p.timers,g=p._data(this);if(c)g[c]&&g[c].stop&&e(g[c]);else for(c in g)g[c]&&g[c].stop&&cR.test(c)&&e(g[c]);for(c=f.length;c--;)f[c].elem===this&&(a==null||f[c].queue===a)&&(f[c].anim.stop(d),b=!1,f.splice(c,1));(b||!d)&&p.dequeue(this,a)})}}),p.each({slideDown:c$("show"),slideUp:c$("hide"),slideToggle:c$("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){p.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),p.speed=function(a,b,c){var d=a&&typeof a=="object"?p.extend({},a):{complete:c||!c&&b||p.isFunction(a)&&a,duration:a,easing:c&&b||b&&!p.isFunction(b)&&b};d.duration=p.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in p.fx.speeds?p.fx.speeds[d.duration]:p.fx.speeds._default;if(d.queue==null||d.queue===!0)d.queue="fx";return d.old=d.complete,d.complete=function(){p.isFunction(d.old)&&d.old.call(this),d.queue&&p.dequeue(this,d.queue)},d},p.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},p.timers=[],p.fx=cZ.prototype.init,p.fx.tick=function(){var a,b=p.timers,c=0;for(;c<b.length;c++)a=b[c],!a()&&b[c]===a&&b.splice(c--,1);b.length||p.fx.stop()},p.fx.timer=function(a){a()&&p.timers.push(a)&&!cO&&(cO=setInterval(p.fx.tick,p.fx.interval))},p.fx.interval=13,p.fx.stop=function(){clearInterval(cO),cO=null},p.fx.speeds={slow:600,fast:200,_default:400},p.fx.step={},p.expr&&p.expr.filters&&(p.expr.filters.animated=function(a){return p.grep(p.timers,function(b){return a===b.elem}).length});var c_=/^(?:body|html)$/i;p.fn.offset=function(a){if(arguments.length)return a===b?this:this.each(function(b){p.offset.setOffset(this,a,b)});var c,d,e,f,g,h,i,j,k,l,m=this[0],n=m&&m.ownerDocument;if(!n)return;return(e=n.body)===m?p.offset.bodyOffset(m):(d=n.documentElement,p.contains(d,m)?(c=m.getBoundingClientRect(),f=da(n),g=d.clientTop||e.clientTop||0,h=d.clientLeft||e.clientLeft||0,i=f.pageYOffset||d.scrollTop,j=f.pageXOffset||d.scrollLeft,k=c.top+i-g,l=c.left+j-h,{top:k,left:l}):{top:0,left:0})},p.offset={bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;return p.support.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(p.css(a,"marginTop"))||0,c+=parseFloat(p.css(a,"marginLeft"))||0),{top:b,left:c}},setOffset:function(a,b,c){var d=p.css(a,"position");d==="static"&&(a.style.position="relative");var e=p(a),f=e.offset(),g=p.css(a,"top"),h=p.css(a,"left"),i=(d==="absolute"||d==="fixed")&&p.inArray("auto",[g,h])>-1,j={},k={},l,m;i?(k=e.position(),l=k.top,m=k.left):(l=parseFloat(g)||0,m=parseFloat(h)||0),p.isFunction(b)&&(b=b.call(a,c,f)),b.top!=null&&(j.top=b.top-f.top+l),b.left!=null&&(j.left=b.left-f.left+m),"using"in b?b.using.call(a,j):e.css(j)}},p.fn.extend({position:function(){if(!this[0])return;var a=this[0],b=this.offsetParent(),c=this.offset(),d=c_.test(b[0].nodeName)?{top:0,left:0}:b.offset();return c.top-=parseFloat(p.css(a,"marginTop"))||0,c.left-=parseFloat(p.css(a,"marginLeft"))||0,d.top+=parseFloat(p.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(p.css(b[0],"borderLeftWidth"))||0,{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||e.body;while(a&&!c_.test(a.nodeName)&&p.css(a,"position")==="static")a=a.offsetParent;return a||e.body})}}),p.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);p.fn[a]=function(e){return p.access(this,function(a,e,f){var g=da(a);if(f===b)return g?c in g?g[c]:g.document.documentElement[e]:a[e];g?g.scrollTo(d?p(g).scrollLeft():f,d?f:p(g).scrollTop()):a[e]=f},a,e,arguments.length,null)}}),p.each({Height:"height",Width:"width"},function(a,c){p.each({padding:"inner"+a,content:c,"":"outer"+a},function(d,e){p.fn[e]=function(e,f){var g=arguments.length&&(d||typeof e!="boolean"),h=d||(e===!0||f===!0?"margin":"border");return p.access(this,function(c,d,e){var f;return p.isWindow(c)?c.document.documentElement["client"+a]:c.nodeType===9?(f=c.documentElement,Math.max(c.body["scroll"+a],f["scroll"+a],c.body["offset"+a],f["offset"+a],f["client"+a])):e===b?p.css(c,d,e,h):p.style(c,d,e,h)},c,g?e:b,g,null)}})}),a.jQuery=a.$=p,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return p})})(window);
    \ No newline at end of file
    diff --git a/lib/jquery-1.9.0-min.js b/lib/jquery-1.9.0-min.js
    new file mode 100644
    index 000000000..50d1b22f2
    --- /dev/null
    +++ b/lib/jquery-1.9.0-min.js
    @@ -0,0 +1,4 @@
    +/*! jQuery v1.9.0 | (c) 2005, 2012 jQuery Foundation, Inc. | jquery.org/license */(function(e,t){"use strict";function n(e){var t=e.length,n=st.type(e);return st.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}function r(e){var t=Tt[e]={};return st.each(e.match(lt)||[],function(e,n){t[n]=!0}),t}function i(e,n,r,i){if(st.acceptData(e)){var o,a,s=st.expando,u="string"==typeof n,l=e.nodeType,c=l?st.cache:e,f=l?e[s]:e[s]&&s;if(f&&c[f]&&(i||c[f].data)||!u||r!==t)return f||(l?e[s]=f=K.pop()||st.guid++:f=s),c[f]||(c[f]={},l||(c[f].toJSON=st.noop)),("object"==typeof n||"function"==typeof n)&&(i?c[f]=st.extend(c[f],n):c[f].data=st.extend(c[f].data,n)),o=c[f],i||(o.data||(o.data={}),o=o.data),r!==t&&(o[st.camelCase(n)]=r),u?(a=o[n],null==a&&(a=o[st.camelCase(n)])):a=o,a}}function o(e,t,n){if(st.acceptData(e)){var r,i,o,a=e.nodeType,u=a?st.cache:e,l=a?e[st.expando]:st.expando;if(u[l]){if(t&&(r=n?u[l]:u[l].data)){st.isArray(t)?t=t.concat(st.map(t,st.camelCase)):t in r?t=[t]:(t=st.camelCase(t),t=t in r?[t]:t.split(" "));for(i=0,o=t.length;o>i;i++)delete r[t[i]];if(!(n?s:st.isEmptyObject)(r))return}(n||(delete u[l].data,s(u[l])))&&(a?st.cleanData([e],!0):st.support.deleteExpando||u!=u.window?delete u[l]:u[l]=null)}}}function a(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(Nt,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:wt.test(r)?st.parseJSON(r):r}catch(o){}st.data(e,n,r)}else r=t}return r}function s(e){var t;for(t in e)if(("data"!==t||!st.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}function u(){return!0}function l(){return!1}function c(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}function f(e,t,n){if(t=t||0,st.isFunction(t))return st.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return st.grep(e,function(e){return e===t===n});if("string"==typeof t){var r=st.grep(e,function(e){return 1===e.nodeType});if(Wt.test(t))return st.filter(t,r,!n);t=st.filter(t,r)}return st.grep(e,function(e){return st.inArray(e,t)>=0===n})}function p(e){var t=zt.split("|"),n=e.createDocumentFragment();if(n.createElement)for(;t.length;)n.createElement(t.pop());return n}function d(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function h(e){var t=e.getAttributeNode("type");return e.type=(t&&t.specified)+"/"+e.type,e}function g(e){var t=nn.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function m(e,t){for(var n,r=0;null!=(n=e[r]);r++)st._data(n,"globalEval",!t||st._data(t[r],"globalEval"))}function y(e,t){if(1===t.nodeType&&st.hasData(e)){var n,r,i,o=st._data(e),a=st._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)st.event.add(t,n,s[n][r])}a.data&&(a.data=st.extend({},a.data))}}function v(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!st.support.noCloneEvent&&t[st.expando]){r=st._data(t);for(i in r.events)st.removeEvent(t,i,r.handle);t.removeAttribute(st.expando)}"script"===n&&t.text!==e.text?(h(t).text=e.text,g(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),st.support.html5Clone&&e.innerHTML&&!st.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Zt.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}function b(e,n){var r,i,o=0,a=e.getElementsByTagName!==t?e.getElementsByTagName(n||"*"):e.querySelectorAll!==t?e.querySelectorAll(n||"*"):t;if(!a)for(a=[],r=e.childNodes||e;null!=(i=r[o]);o++)!n||st.nodeName(i,n)?a.push(i):st.merge(a,b(i,n));return n===t||n&&st.nodeName(e,n)?st.merge([e],a):a}function x(e){Zt.test(e.type)&&(e.defaultChecked=e.checked)}function T(e,t){if(t in e)return t;for(var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=Nn.length;i--;)if(t=Nn[i]+n,t in e)return t;return r}function w(e,t){return e=t||e,"none"===st.css(e,"display")||!st.contains(e.ownerDocument,e)}function N(e,t){for(var n,r=[],i=0,o=e.length;o>i;i++)n=e[i],n.style&&(r[i]=st._data(n,"olddisplay"),t?(r[i]||"none"!==n.style.display||(n.style.display=""),""===n.style.display&&w(n)&&(r[i]=st._data(n,"olddisplay",S(n.nodeName)))):r[i]||w(n)||st._data(n,"olddisplay",st.css(n,"display")));for(i=0;o>i;i++)n=e[i],n.style&&(t&&"none"!==n.style.display&&""!==n.style.display||(n.style.display=t?r[i]||"":"none"));return e}function C(e,t,n){var r=mn.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function k(e,t,n,r,i){for(var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;4>o;o+=2)"margin"===n&&(a+=st.css(e,n+wn[o],!0,i)),r?("content"===n&&(a-=st.css(e,"padding"+wn[o],!0,i)),"margin"!==n&&(a-=st.css(e,"border"+wn[o]+"Width",!0,i))):(a+=st.css(e,"padding"+wn[o],!0,i),"padding"!==n&&(a+=st.css(e,"border"+wn[o]+"Width",!0,i)));return a}function E(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=ln(e),a=st.support.boxSizing&&"border-box"===st.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=un(e,t,o),(0>i||null==i)&&(i=e.style[t]),yn.test(i))return i;r=a&&(st.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+k(e,t,n||(a?"border":"content"),r,o)+"px"}function S(e){var t=V,n=bn[e];return n||(n=A(e,t),"none"!==n&&n||(cn=(cn||st("<iframe frameborder='0' width='0' height='0'/>").css("cssText","display:block !important")).appendTo(t.documentElement),t=(cn[0].contentWindow||cn[0].contentDocument).document,t.write("<!doctype html><html><body>"),t.close(),n=A(e,t),cn.detach()),bn[e]=n),n}function A(e,t){var n=st(t.createElement(e)).appendTo(t.body),r=st.css(n[0],"display");return n.remove(),r}function j(e,t,n,r){var i;if(st.isArray(t))st.each(t,function(t,i){n||kn.test(e)?r(e,i):j(e+"["+("object"==typeof i?t:"")+"]",i,n,r)});else if(n||"object"!==st.type(t))r(e,t);else for(i in t)j(e+"["+i+"]",t[i],n,r)}function D(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(lt)||[];if(st.isFunction(n))for(;r=o[i++];)"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function L(e,n,r,i){function o(u){var l;return a[u]=!0,st.each(e[u]||[],function(e,u){var c=u(n,r,i);return"string"!=typeof c||s||a[c]?s?!(l=c):t:(n.dataTypes.unshift(c),o(c),!1)}),l}var a={},s=e===$n;return o(n.dataTypes[0])||!a["*"]&&o("*")}function H(e,n){var r,i,o=st.ajaxSettings.flatOptions||{};for(r in n)n[r]!==t&&((o[r]?e:i||(i={}))[r]=n[r]);return i&&st.extend(!0,e,i),e}function M(e,n,r){var i,o,a,s,u=e.contents,l=e.dataTypes,c=e.responseFields;for(o in c)o in r&&(n[c[o]]=r[o]);for(;"*"===l[0];)l.shift(),i===t&&(i=e.mimeType||n.getResponseHeader("Content-Type"));if(i)for(o in u)if(u[o]&&u[o].test(i)){l.unshift(o);break}if(l[0]in r)a=l[0];else{for(o in r){if(!l[0]||e.converters[o+" "+l[0]]){a=o;break}s||(s=o)}a=a||s}return a?(a!==l[0]&&l.unshift(a),r[a]):t}function q(e,t){var n,r,i,o,a={},s=0,u=e.dataTypes.slice(),l=u[0];if(e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u[1])for(n in e.converters)a[n.toLowerCase()]=e.converters[n];for(;i=u[++s];)if("*"!==i){if("*"!==l&&l!==i){if(n=a[l+" "+i]||a["* "+i],!n)for(r in a)if(o=r.split(" "),o[1]===i&&(n=a[l+" "+o[0]]||a["* "+o[0]])){n===!0?n=a[r]:a[r]!==!0&&(i=o[0],u.splice(s--,0,i));break}if(n!==!0)if(n&&e["throws"])t=n(t);else try{t=n(t)}catch(c){return{state:"parsererror",error:n?c:"No conversion from "+l+" to "+i}}}l=i}return{state:"success",data:t}}function _(){try{return new e.XMLHttpRequest}catch(t){}}function F(){try{return new e.ActiveXObject("Microsoft.XMLHTTP")}catch(t){}}function O(){return setTimeout(function(){Qn=t}),Qn=st.now()}function B(e,t){st.each(t,function(t,n){for(var r=(rr[t]||[]).concat(rr["*"]),i=0,o=r.length;o>i;i++)if(r[i].call(e,t,n))return})}function P(e,t,n){var r,i,o=0,a=nr.length,s=st.Deferred().always(function(){delete u.elem}),u=function(){if(i)return!1;for(var t=Qn||O(),n=Math.max(0,l.startTime+l.duration-t),r=n/l.duration||0,o=1-r,a=0,u=l.tweens.length;u>a;a++)l.tweens[a].run(o);return s.notifyWith(e,[l,o,n]),1>o&&u?n:(s.resolveWith(e,[l]),!1)},l=s.promise({elem:e,props:st.extend({},t),opts:st.extend(!0,{specialEasing:{}},n),originalProperties:t,originalOptions:n,startTime:Qn||O(),duration:n.duration,tweens:[],createTween:function(t,n){var r=st.Tween(e,l.opts,t,n,l.opts.specialEasing[t]||l.opts.easing);return l.tweens.push(r),r},stop:function(t){var n=0,r=t?l.tweens.length:0;if(i)return this;for(i=!0;r>n;n++)l.tweens[n].run(1);return t?s.resolveWith(e,[l,t]):s.rejectWith(e,[l,t]),this}}),c=l.props;for(R(c,l.opts.specialEasing);a>o;o++)if(r=nr[o].call(l,e,c,l.opts))return r;return B(l,c),st.isFunction(l.opts.start)&&l.opts.start.call(e,l),st.fx.timer(st.extend(u,{elem:e,anim:l,queue:l.opts.queue})),l.progress(l.opts.progress).done(l.opts.done,l.opts.complete).fail(l.opts.fail).always(l.opts.always)}function R(e,t){var n,r,i,o,a;for(n in e)if(r=st.camelCase(n),i=t[r],o=e[n],st.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),a=st.cssHooks[r],a&&"expand"in a){o=a.expand(o),delete e[r];for(n in o)n in e||(e[n]=o[n],t[n]=i)}else t[r]=i}function W(e,t,n){var r,i,o,a,s,u,l,c,f,p=this,d=e.style,h={},g=[],m=e.nodeType&&w(e);n.queue||(c=st._queueHooks(e,"fx"),null==c.unqueued&&(c.unqueued=0,f=c.empty.fire,c.empty.fire=function(){c.unqueued||f()}),c.unqueued++,p.always(function(){p.always(function(){c.unqueued--,st.queue(e,"fx").length||c.empty.fire()})})),1===e.nodeType&&("height"in t||"width"in t)&&(n.overflow=[d.overflow,d.overflowX,d.overflowY],"inline"===st.css(e,"display")&&"none"===st.css(e,"float")&&(st.support.inlineBlockNeedsLayout&&"inline"!==S(e.nodeName)?d.zoom=1:d.display="inline-block")),n.overflow&&(d.overflow="hidden",st.support.shrinkWrapBlocks||p.done(function(){d.overflow=n.overflow[0],d.overflowX=n.overflow[1],d.overflowY=n.overflow[2]}));for(r in t)if(o=t[r],Zn.exec(o)){if(delete t[r],u=u||"toggle"===o,o===(m?"hide":"show"))continue;g.push(r)}if(a=g.length){s=st._data(e,"fxshow")||st._data(e,"fxshow",{}),"hidden"in s&&(m=s.hidden),u&&(s.hidden=!m),m?st(e).show():p.done(function(){st(e).hide()}),p.done(function(){var t;st._removeData(e,"fxshow");for(t in h)st.style(e,t,h[t])});for(r=0;a>r;r++)i=g[r],l=p.createTween(i,m?s[i]:0),h[i]=s[i]||st.style(e,i),i in s||(s[i]=l.start,m&&(l.end=l.start,l.start="width"===i||"height"===i?1:0))}}function $(e,t,n,r,i){return new $.prototype.init(e,t,n,r,i)}function I(e,t){var n,r={height:e},i=0;for(t=t?1:0;4>i;i+=2-t)n=wn[i],r["margin"+n]=r["padding"+n]=e;return t&&(r.opacity=r.width=e),r}function z(e){return st.isWindow(e)?e:9===e.nodeType?e.defaultView||e.parentWindow:!1}var X,U,V=e.document,Y=e.location,J=e.jQuery,G=e.$,Q={},K=[],Z="1.9.0",et=K.concat,tt=K.push,nt=K.slice,rt=K.indexOf,it=Q.toString,ot=Q.hasOwnProperty,at=Z.trim,st=function(e,t){return new st.fn.init(e,t,X)},ut=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,lt=/\S+/g,ct=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,ft=/^(?:(<[\w\W]+>)[^>]*|#([\w-]*))$/,pt=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,dt=/^[\],:{}\s]*$/,ht=/(?:^|:|,)(?:\s*\[)+/g,gt=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,mt=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,yt=/^-ms-/,vt=/-([\da-z])/gi,bt=function(e,t){return t.toUpperCase()},xt=function(){V.addEventListener?(V.removeEventListener("DOMContentLoaded",xt,!1),st.ready()):"complete"===V.readyState&&(V.detachEvent("onreadystatechange",xt),st.ready())};st.fn=st.prototype={jquery:Z,constructor:st,init:function(e,n,r){var i,o;if(!e)return this;if("string"==typeof e){if(i="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:ft.exec(e),!i||!i[1]&&n)return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e);if(i[1]){if(n=n instanceof st?n[0]:n,st.merge(this,st.parseHTML(i[1],n&&n.nodeType?n.ownerDocument||n:V,!0)),pt.test(i[1])&&st.isPlainObject(n))for(i in n)st.isFunction(this[i])?this[i](n[i]):this.attr(i,n[i]);return this}if(o=V.getElementById(i[2]),o&&o.parentNode){if(o.id!==i[2])return r.find(e);this.length=1,this[0]=o}return this.context=V,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):st.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),st.makeArray(e,this))},selector:"",length:0,size:function(){return this.length},toArray:function(){return nt.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=st.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return st.each(this,e,t)},ready:function(e){return st.ready.promise().done(e),this},slice:function(){return this.pushStack(nt.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(st.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:tt,sort:[].sort,splice:[].splice},st.fn.init.prototype=st.fn,st.extend=st.fn.extend=function(){var e,n,r,i,o,a,s=arguments[0]||{},u=1,l=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},u=2),"object"==typeof s||st.isFunction(s)||(s={}),l===u&&(s=this,--u);l>u;u++)if(null!=(e=arguments[u]))for(n in e)r=s[n],i=e[n],s!==i&&(c&&i&&(st.isPlainObject(i)||(o=st.isArray(i)))?(o?(o=!1,a=r&&st.isArray(r)?r:[]):a=r&&st.isPlainObject(r)?r:{},s[n]=st.extend(c,a,i)):i!==t&&(s[n]=i));return s},st.extend({noConflict:function(t){return e.$===st&&(e.$=G),t&&e.jQuery===st&&(e.jQuery=J),st},isReady:!1,readyWait:1,holdReady:function(e){e?st.readyWait++:st.ready(!0)},ready:function(e){if(e===!0?!--st.readyWait:!st.isReady){if(!V.body)return setTimeout(st.ready);st.isReady=!0,e!==!0&&--st.readyWait>0||(U.resolveWith(V,[st]),st.fn.trigger&&st(V).trigger("ready").off("ready"))}},isFunction:function(e){return"function"===st.type(e)},isArray:Array.isArray||function(e){return"array"===st.type(e)},isWindow:function(e){return null!=e&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?Q[it.call(e)]||"object":typeof e},isPlainObject:function(e){if(!e||"object"!==st.type(e)||e.nodeType||st.isWindow(e))return!1;try{if(e.constructor&&!ot.call(e,"constructor")&&!ot.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}var r;for(r in e);return r===t||ot.call(e,r)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||V;var r=pt.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=st.buildFragment([e],t,i),i&&st(i).remove(),st.merge([],r.childNodes))},parseJSON:function(n){return e.JSON&&e.JSON.parse?e.JSON.parse(n):null===n?n:"string"==typeof n&&(n=st.trim(n),n&&dt.test(n.replace(gt,"@").replace(mt,"]").replace(ht,"")))?Function("return "+n)():(st.error("Invalid JSON: "+n),t)},parseXML:function(n){var r,i;if(!n||"string"!=typeof n)return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(o){r=t}return r&&r.documentElement&&!r.getElementsByTagName("parsererror").length||st.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&st.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(yt,"ms-").replace(vt,bt)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,r){var i,o=0,a=e.length,s=n(e);if(r){if(s)for(;a>o&&(i=t.apply(e[o],r),i!==!1);o++);else for(o in e)if(i=t.apply(e[o],r),i===!1)break}else if(s)for(;a>o&&(i=t.call(e[o],o,e[o]),i!==!1);o++);else for(o in e)if(i=t.call(e[o],o,e[o]),i===!1)break;return e},trim:at&&!at.call("\ufeff\u00a0")?function(e){return null==e?"":at.call(e)}:function(e){return null==e?"":(e+"").replace(ct,"")},makeArray:function(e,t){var r=t||[];return null!=e&&(n(Object(e))?st.merge(r,"string"==typeof e?[e]:e):tt.call(r,e)),r},inArray:function(e,t,n){var r;if(t){if(rt)return rt.call(t,e,n);for(r=t.length,n=n?0>n?Math.max(0,r+n):n:0;r>n;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,o=0;if("number"==typeof r)for(;r>o;o++)e[i++]=n[o];else for(;n[o]!==t;)e[i++]=n[o++];return e.length=i,e},grep:function(e,t,n){var r,i=[],o=0,a=e.length;for(n=!!n;a>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,r){var i,o=0,a=e.length,s=n(e),u=[];if(s)for(;a>o;o++)i=t(e[o],o,r),null!=i&&(u[u.length]=i);else for(o in e)i=t(e[o],o,r),null!=i&&(u[u.length]=i);return et.apply([],u)},guid:1,proxy:function(e,n){var r,i,o;return"string"==typeof n&&(r=e[n],n=e,e=r),st.isFunction(e)?(i=nt.call(arguments,2),o=function(){return e.apply(n||this,i.concat(nt.call(arguments)))},o.guid=e.guid=e.guid||st.guid++,o):t},access:function(e,n,r,i,o,a,s){var u=0,l=e.length,c=null==r;if("object"===st.type(r)){o=!0;for(u in r)st.access(e,n,u,r[u],!0,a,s)}else if(i!==t&&(o=!0,st.isFunction(i)||(s=!0),c&&(s?(n.call(e,i),n=null):(c=n,n=function(e,t,n){return c.call(st(e),n)})),n))for(;l>u;u++)n(e[u],r,s?i:i.call(e[u],u,n(e[u],r)));return o?e:c?n.call(e):l?n(e[0],r):a},now:function(){return(new Date).getTime()}}),st.ready.promise=function(t){if(!U)if(U=st.Deferred(),"complete"===V.readyState)setTimeout(st.ready);else if(V.addEventListener)V.addEventListener("DOMContentLoaded",xt,!1),e.addEventListener("load",st.ready,!1);else{V.attachEvent("onreadystatechange",xt),e.attachEvent("onload",st.ready);var n=!1;try{n=null==e.frameElement&&V.documentElement}catch(r){}n&&n.doScroll&&function i(){if(!st.isReady){try{n.doScroll("left")}catch(e){return setTimeout(i,50)}st.ready()}}()}return U.promise(t)},st.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){Q["[object "+t+"]"]=t.toLowerCase()}),X=st(V);var Tt={};st.Callbacks=function(e){e="string"==typeof e?Tt[e]||r(e):st.extend({},e);var n,i,o,a,s,u,l=[],c=!e.once&&[],f=function(t){for(n=e.memory&&t,i=!0,u=a||0,a=0,s=l.length,o=!0;l&&s>u;u++)if(l[u].apply(t[0],t[1])===!1&&e.stopOnFalse){n=!1;break}o=!1,l&&(c?c.length&&f(c.shift()):n?l=[]:p.disable())},p={add:function(){if(l){var t=l.length;(function r(t){st.each(t,function(t,n){var i=st.type(n);"function"===i?e.unique&&p.has(n)||l.push(n):n&&n.length&&"string"!==i&&r(n)})})(arguments),o?s=l.length:n&&(a=t,f(n))}return this},remove:function(){return l&&st.each(arguments,function(e,t){for(var n;(n=st.inArray(t,l,n))>-1;)l.splice(n,1),o&&(s>=n&&s--,u>=n&&u--)}),this},has:function(e){return st.inArray(e,l)>-1},empty:function(){return l=[],this},disable:function(){return l=c=n=t,this},disabled:function(){return!l},lock:function(){return c=t,n||p.disable(),this},locked:function(){return!c},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],!l||i&&!c||(o?c.push(t):f(t)),this},fire:function(){return p.fireWith(this,arguments),this},fired:function(){return!!i}};return p},st.extend({Deferred:function(e){var t=[["resolve","done",st.Callbacks("once memory"),"resolved"],["reject","fail",st.Callbacks("once memory"),"rejected"],["notify","progress",st.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return st.Deferred(function(n){st.each(t,function(t,o){var a=o[0],s=st.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&st.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[a+"With"](this===r?n.promise():this,s?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?st.extend(e,r):r}},i={};return r.pipe=r.then,st.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t,n,r,i=0,o=nt.call(arguments),a=o.length,s=1!==a||e&&st.isFunction(e.promise)?a:0,u=1===s?e:st.Deferred(),l=function(e,n,r){return function(i){n[e]=this,r[e]=arguments.length>1?nt.call(arguments):i,r===t?u.notifyWith(n,r):--s||u.resolveWith(n,r)}};if(a>1)for(t=Array(a),n=Array(a),r=Array(a);a>i;i++)o[i]&&st.isFunction(o[i].promise)?o[i].promise().done(l(i,r,o)).fail(u.reject).progress(l(i,n,t)):--s;return s||u.resolveWith(r,o),u.promise()}}),st.support=function(){var n,r,i,o,a,s,u,l,c,f,p=V.createElement("div");if(p.setAttribute("className","t"),p.innerHTML="  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",r=p.getElementsByTagName("*"),i=p.getElementsByTagName("a")[0],!r||!i||!r.length)return{};o=V.createElement("select"),a=o.appendChild(V.createElement("option")),s=p.getElementsByTagName("input")[0],i.style.cssText="top:1px;float:left;opacity:.5",n={getSetAttribute:"t"!==p.className,leadingWhitespace:3===p.firstChild.nodeType,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(i.getAttribute("style")),hrefNormalized:"/a"===i.getAttribute("href"),opacity:/^0.5/.test(i.style.opacity),cssFloat:!!i.style.cssFloat,checkOn:!!s.value,optSelected:a.selected,enctype:!!V.createElement("form").enctype,html5Clone:"<:nav></:nav>"!==V.createElement("nav").cloneNode(!0).outerHTML,boxModel:"CSS1Compat"===V.compatMode,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},s.checked=!0,n.noCloneChecked=s.cloneNode(!0).checked,o.disabled=!0,n.optDisabled=!a.disabled;try{delete p.test}catch(d){n.deleteExpando=!1}s=V.createElement("input"),s.setAttribute("value",""),n.input=""===s.getAttribute("value"),s.value="t",s.setAttribute("type","radio"),n.radioValue="t"===s.value,s.setAttribute("checked","t"),s.setAttribute("name","t"),u=V.createDocumentFragment(),u.appendChild(s),n.appendChecked=s.checked,n.checkClone=u.cloneNode(!0).cloneNode(!0).lastChild.checked,p.attachEvent&&(p.attachEvent("onclick",function(){n.noCloneEvent=!1}),p.cloneNode(!0).click());for(f in{submit:!0,change:!0,focusin:!0})p.setAttribute(l="on"+f,"t"),n[f+"Bubbles"]=l in e||p.attributes[l].expando===!1;return p.style.backgroundClip="content-box",p.cloneNode(!0).style.backgroundClip="",n.clearCloneStyle="content-box"===p.style.backgroundClip,st(function(){var r,i,o,a="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",s=V.getElementsByTagName("body")[0];s&&(r=V.createElement("div"),r.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",s.appendChild(r).appendChild(p),p.innerHTML="<table><tr><td></td><td>t</td></tr></table>",o=p.getElementsByTagName("td"),o[0].style.cssText="padding:0;margin:0;border:0;display:none",c=0===o[0].offsetHeight,o[0].style.display="",o[1].style.display="none",n.reliableHiddenOffsets=c&&0===o[0].offsetHeight,p.innerHTML="",p.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",n.boxSizing=4===p.offsetWidth,n.doesNotIncludeMarginInBodyOffset=1!==s.offsetTop,e.getComputedStyle&&(n.pixelPosition="1%"!==(e.getComputedStyle(p,null)||{}).top,n.boxSizingReliable="4px"===(e.getComputedStyle(p,null)||{width:"4px"}).width,i=p.appendChild(V.createElement("div")),i.style.cssText=p.style.cssText=a,i.style.marginRight=i.style.width="0",p.style.width="1px",n.reliableMarginRight=!parseFloat((e.getComputedStyle(i,null)||{}).marginRight)),p.style.zoom!==t&&(p.innerHTML="",p.style.cssText=a+"width:1px;padding:1px;display:inline;zoom:1",n.inlineBlockNeedsLayout=3===p.offsetWidth,p.style.display="block",p.innerHTML="<div></div>",p.firstChild.style.width="5px",n.shrinkWrapBlocks=3!==p.offsetWidth,s.style.zoom=1),s.removeChild(r),r=p=o=i=null)}),r=o=u=a=i=s=null,n}();var wt=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,Nt=/([A-Z])/g;st.extend({cache:{},expando:"jQuery"+(Z+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(e){return e=e.nodeType?st.cache[e[st.expando]]:e[st.expando],!!e&&!s(e)},data:function(e,t,n){return i(e,t,n,!1)},removeData:function(e,t){return o(e,t,!1)},_data:function(e,t,n){return i(e,t,n,!0)},_removeData:function(e,t){return o(e,t,!0)},acceptData:function(e){var t=e.nodeName&&st.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),st.fn.extend({data:function(e,n){var r,i,o=this[0],s=0,u=null;if(e===t){if(this.length&&(u=st.data(o),1===o.nodeType&&!st._data(o,"parsedAttrs"))){for(r=o.attributes;r.length>s;s++)i=r[s].name,i.indexOf("data-")||(i=st.camelCase(i.substring(5)),a(o,i,u[i]));st._data(o,"parsedAttrs",!0)}return u}return"object"==typeof e?this.each(function(){st.data(this,e)}):st.access(this,function(n){return n===t?o?a(o,e,st.data(o,e)):null:(this.each(function(){st.data(this,e,n)}),t)},null,n,arguments.length>1,null,!0)},removeData:function(e){return this.each(function(){st.removeData(this,e)})}}),st.extend({queue:function(e,n,r){var i;return e?(n=(n||"fx")+"queue",i=st._data(e,n),r&&(!i||st.isArray(r)?i=st._data(e,n,st.makeArray(r)):i.push(r)),i||[]):t},dequeue:function(e,t){t=t||"fx";var n=st.queue(e,t),r=n.length,i=n.shift(),o=st._queueHooks(e,t),a=function(){st.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),o.cur=i,i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return st._data(e,n)||st._data(e,n,{empty:st.Callbacks("once memory").add(function(){st._removeData(e,t+"queue"),st._removeData(e,n)})})}}),st.fn.extend({queue:function(e,n){var r=2;return"string"!=typeof e&&(n=e,e="fx",r--),r>arguments.length?st.queue(this[0],e):n===t?this:this.each(function(){var t=st.queue(this,e,n);st._queueHooks(this,e),"fx"===e&&"inprogress"!==t[0]&&st.dequeue(this,e)})},dequeue:function(e){return this.each(function(){st.dequeue(this,e)})},delay:function(e,t){return e=st.fx?st.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,o=st.Deferred(),a=this,s=this.length,u=function(){--i||o.resolveWith(a,[a])};for("string"!=typeof e&&(n=e,e=t),e=e||"fx";s--;)r=st._data(a[s],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(u));return u(),o.promise(n)}});var Ct,kt,Et=/[\t\r\n]/g,St=/\r/g,At=/^(?:input|select|textarea|button|object)$/i,jt=/^(?:a|area)$/i,Dt=/^(?:checked|selected|autofocus|autoplay|async|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped)$/i,Lt=/^(?:checked|selected)$/i,Ht=st.support.getSetAttribute,Mt=st.support.input;st.fn.extend({attr:function(e,t){return st.access(this,st.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){st.removeAttr(this,e)})},prop:function(e,t){return st.access(this,st.prop,e,t,arguments.length>1)},removeProp:function(e){return e=st.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,o,a=0,s=this.length,u="string"==typeof e&&e;if(st.isFunction(e))return this.each(function(t){st(this).addClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(lt)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(Et," "):" ")){for(o=0;i=t[o++];)0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=st.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,a=0,s=this.length,u=0===arguments.length||"string"==typeof e&&e;if(st.isFunction(e))return this.each(function(t){st(this).removeClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(lt)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(Et," "):"")){for(o=0;i=t[o++];)for(;r.indexOf(" "+i+" ")>=0;)r=r.replace(" "+i+" "," ");n.className=e?st.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e,r="boolean"==typeof t;return st.isFunction(e)?this.each(function(n){st(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n)for(var i,o=0,a=st(this),s=t,u=e.match(lt)||[];i=u[o++];)s=r?s:!a.hasClass(i),a[s?"addClass":"removeClass"](i);else("undefined"===n||"boolean"===n)&&(this.className&&st._data(this,"__className__",this.className),this.className=this.className||e===!1?"":st._data(this,"__className__")||"")})},hasClass:function(e){for(var t=" "+e+" ",n=0,r=this.length;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(Et," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,o=this[0];{if(arguments.length)return i=st.isFunction(e),this.each(function(r){var o,a=st(this);1===this.nodeType&&(o=i?e.call(this,r,a.val()):e,null==o?o="":"number"==typeof o?o+="":st.isArray(o)&&(o=st.map(o,function(e){return null==e?"":e+""})),n=st.valHooks[this.type]||st.valHooks[this.nodeName.toLowerCase()],n&&"set"in n&&n.set(this,o,"value")!==t||(this.value=o))});if(o)return n=st.valHooks[o.type]||st.valHooks[o.nodeName.toLowerCase()],n&&"get"in n&&(r=n.get(o,"value"))!==t?r:(r=o.value,"string"==typeof r?r.replace(St,""):null==r?"":r)}}}),st.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){for(var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,a=o?null:[],s=o?i+1:r.length,u=0>i?s:o?i:0;s>u;u++)if(n=r[u],!(!n.selected&&u!==i||(st.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&st.nodeName(n.parentNode,"optgroup"))){if(t=st(n).val(),o)return t;a.push(t)}return a},set:function(e,t){var n=st.makeArray(t);return st(e).find("option").each(function(){this.selected=st.inArray(st(this).val(),n)>=0}),n.length||(e.selectedIndex=-1),n}}},attr:function(e,n,r){var i,o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return e.getAttribute===t?st.prop(e,n,r):(a=1!==s||!st.isXMLDoc(e),a&&(n=n.toLowerCase(),o=st.attrHooks[n]||(Dt.test(n)?kt:Ct)),r===t?o&&a&&"get"in o&&null!==(i=o.get(e,n))?i:(e.getAttribute!==t&&(i=e.getAttribute(n)),null==i?t:i):null!==r?o&&a&&"set"in o&&(i=o.set(e,r,n))!==t?i:(e.setAttribute(n,r+""),r):(st.removeAttr(e,n),t))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(lt);if(o&&1===e.nodeType)for(;n=o[i++];)r=st.propFix[n]||n,Dt.test(n)?!Ht&&Lt.test(n)?e[st.camelCase("default-"+n)]=e[r]=!1:e[r]=!1:st.attr(e,n,""),e.removeAttribute(Ht?n:r)},attrHooks:{type:{set:function(e,t){if(!st.support.radioValue&&"radio"===t&&st.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(e,n,r){var i,o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return a=1!==s||!st.isXMLDoc(e),a&&(n=st.propFix[n]||n,o=st.propHooks[n]),r!==t?o&&"set"in o&&(i=o.set(e,r,n))!==t?i:e[n]=r:o&&"get"in o&&null!==(i=o.get(e,n))?i:e[n]},propHooks:{tabIndex:{get:function(e){var n=e.getAttributeNode("tabindex");return n&&n.specified?parseInt(n.value,10):At.test(e.nodeName)||jt.test(e.nodeName)&&e.href?0:t}}}}),kt={get:function(e,n){var r=st.prop(e,n),i="boolean"==typeof r&&e.getAttribute(n),o="boolean"==typeof r?Mt&&Ht?null!=i:Lt.test(n)?e[st.camelCase("default-"+n)]:!!i:e.getAttributeNode(n);return o&&o.value!==!1?n.toLowerCase():t},set:function(e,t,n){return t===!1?st.removeAttr(e,n):Mt&&Ht||!Lt.test(n)?e.setAttribute(!Ht&&st.propFix[n]||n,n):e[st.camelCase("default-"+n)]=e[n]=!0,n}},Mt&&Ht||(st.attrHooks.value={get:function(e,n){var r=e.getAttributeNode(n);return st.nodeName(e,"input")?e.defaultValue:r&&r.specified?r.value:t
    +},set:function(e,n,r){return st.nodeName(e,"input")?(e.defaultValue=n,t):Ct&&Ct.set(e,n,r)}}),Ht||(Ct=st.valHooks.button={get:function(e,n){var r=e.getAttributeNode(n);return r&&("id"===n||"name"===n||"coords"===n?""!==r.value:r.specified)?r.value:t},set:function(e,n,r){var i=e.getAttributeNode(r);return i||e.setAttributeNode(i=e.ownerDocument.createAttribute(r)),i.value=n+="","value"===r||n===e.getAttribute(r)?n:t}},st.attrHooks.contenteditable={get:Ct.get,set:function(e,t,n){Ct.set(e,""===t?!1:t,n)}},st.each(["width","height"],function(e,n){st.attrHooks[n]=st.extend(st.attrHooks[n],{set:function(e,r){return""===r?(e.setAttribute(n,"auto"),r):t}})})),st.support.hrefNormalized||(st.each(["href","src","width","height"],function(e,n){st.attrHooks[n]=st.extend(st.attrHooks[n],{get:function(e){var r=e.getAttribute(n,2);return null==r?t:r}})}),st.each(["href","src"],function(e,t){st.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}})),st.support.style||(st.attrHooks.style={get:function(e){return e.style.cssText||t},set:function(e,t){return e.style.cssText=t+""}}),st.support.optSelected||(st.propHooks.selected=st.extend(st.propHooks.selected,{get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}})),st.support.enctype||(st.propFix.enctype="encoding"),st.support.checkOn||st.each(["radio","checkbox"],function(){st.valHooks[this]={get:function(e){return null===e.getAttribute("value")?"on":e.value}}}),st.each(["radio","checkbox"],function(){st.valHooks[this]=st.extend(st.valHooks[this],{set:function(e,n){return st.isArray(n)?e.checked=st.inArray(st(e).val(),n)>=0:t}})});var qt=/^(?:input|select|textarea)$/i,_t=/^key/,Ft=/^(?:mouse|contextmenu)|click/,Ot=/^(?:focusinfocus|focusoutblur)$/,Bt=/^([^.]*)(?:\.(.+)|)$/;st.event={global:{},add:function(e,n,r,i,o){var a,s,u,l,c,f,p,d,h,g,m,y=3!==e.nodeType&&8!==e.nodeType&&st._data(e);if(y){for(r.handler&&(a=r,r=a.handler,o=a.selector),r.guid||(r.guid=st.guid++),(l=y.events)||(l=y.events={}),(s=y.handle)||(s=y.handle=function(e){return st===t||e&&st.event.triggered===e.type?t:st.event.dispatch.apply(s.elem,arguments)},s.elem=e),n=(n||"").match(lt)||[""],c=n.length;c--;)u=Bt.exec(n[c])||[],h=m=u[1],g=(u[2]||"").split(".").sort(),p=st.event.special[h]||{},h=(o?p.delegateType:p.bindType)||h,p=st.event.special[h]||{},f=st.extend({type:h,origType:m,data:i,handler:r,guid:r.guid,selector:o,needsContext:o&&st.expr.match.needsContext.test(o),namespace:g.join(".")},a),(d=l[h])||(d=l[h]=[],d.delegateCount=0,p.setup&&p.setup.call(e,i,g,s)!==!1||(e.addEventListener?e.addEventListener(h,s,!1):e.attachEvent&&e.attachEvent("on"+h,s))),p.add&&(p.add.call(e,f),f.handler.guid||(f.handler.guid=r.guid)),o?d.splice(d.delegateCount++,0,f):d.push(f),st.event.global[h]=!0;e=null}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,m=st.hasData(e)&&st._data(e);if(m&&(u=m.events)){for(t=(t||"").match(lt)||[""],l=t.length;l--;)if(s=Bt.exec(t[l])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){for(f=st.event.special[d]||{},d=(r?f.delegateType:f.bindType)||d,p=u[d]||[],s=s[2]&&RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;o--;)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&f.teardown.call(e,h,m.handle)!==!1||st.removeEvent(e,d,m.handle),delete u[d])}else for(d in u)st.event.remove(e,d+t[l],n,r,!0);st.isEmptyObject(u)&&(delete m.handle,st._removeData(e,"events"))}},trigger:function(n,r,i,o){var a,s,u,l,c,f,p,d=[i||V],h=n.type||n,g=n.namespace?n.namespace.split("."):[];if(s=u=i=i||V,3!==i.nodeType&&8!==i.nodeType&&!Ot.test(h+st.event.triggered)&&(h.indexOf(".")>=0&&(g=h.split("."),h=g.shift(),g.sort()),c=0>h.indexOf(":")&&"on"+h,n=n[st.expando]?n:new st.Event(h,"object"==typeof n&&n),n.isTrigger=!0,n.namespace=g.join("."),n.namespace_re=n.namespace?RegExp("(^|\\.)"+g.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,n.result=t,n.target||(n.target=i),r=null==r?[n]:st.makeArray(r,[n]),p=st.event.special[h]||{},o||!p.trigger||p.trigger.apply(i,r)!==!1)){if(!o&&!p.noBubble&&!st.isWindow(i)){for(l=p.delegateType||h,Ot.test(l+h)||(s=s.parentNode);s;s=s.parentNode)d.push(s),u=s;u===(i.ownerDocument||V)&&d.push(u.defaultView||u.parentWindow||e)}for(a=0;(s=d[a++])&&!n.isPropagationStopped();)n.type=a>1?l:p.bindType||h,f=(st._data(s,"events")||{})[n.type]&&st._data(s,"handle"),f&&f.apply(s,r),f=c&&s[c],f&&st.acceptData(s)&&f.apply&&f.apply(s,r)===!1&&n.preventDefault();if(n.type=h,!(o||n.isDefaultPrevented()||p._default&&p._default.apply(i.ownerDocument,r)!==!1||"click"===h&&st.nodeName(i,"a")||!st.acceptData(i)||!c||!i[h]||st.isWindow(i))){u=i[c],u&&(i[c]=null),st.event.triggered=h;try{i[h]()}catch(m){}st.event.triggered=t,u&&(i[c]=u)}return n.result}},dispatch:function(e){e=st.event.fix(e);var n,r,i,o,a,s=[],u=nt.call(arguments),l=(st._data(this,"events")||{})[e.type]||[],c=st.event.special[e.type]||{};if(u[0]=e,e.delegateTarget=this,!c.preDispatch||c.preDispatch.call(this,e)!==!1){for(s=st.event.handlers.call(this,e,l),n=0;(o=s[n++])&&!e.isPropagationStopped();)for(e.currentTarget=o.elem,r=0;(a=o.handlers[r++])&&!e.isImmediatePropagationStopped();)(!e.namespace_re||e.namespace_re.test(a.namespace))&&(e.handleObj=a,e.data=a.data,i=((st.event.special[a.origType]||{}).handle||a.handler).apply(o.elem,u),i!==t&&(e.result=i)===!1&&(e.preventDefault(),e.stopPropagation()));return c.postDispatch&&c.postDispatch.call(this,e),e.result}},handlers:function(e,n){var r,i,o,a,s=[],u=n.delegateCount,l=e.target;if(u&&l.nodeType&&(!e.button||"click"!==e.type))for(;l!=this;l=l.parentNode||this)if(l.disabled!==!0||"click"!==e.type){for(i=[],r=0;u>r;r++)a=n[r],o=a.selector+" ",i[o]===t&&(i[o]=a.needsContext?st(o,this).index(l)>=0:st.find(o,this,null,[l]).length),i[o]&&i.push(a);i.length&&s.push({elem:l,handlers:i})}return n.length>u&&s.push({elem:this,handlers:n.slice(u)}),s},fix:function(e){if(e[st.expando])return e;var t,n,r=e,i=st.event.fixHooks[e.type]||{},o=i.props?this.props.concat(i.props):this.props;for(e=new st.Event(r),t=o.length;t--;)n=o[t],e[n]=r[n];return e.target||(e.target=r.srcElement||V),3===e.target.nodeType&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,i.filter?i.filter(e,r):e},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,i,o,a=n.button,s=n.fromElement;return null==e.pageX&&null!=n.clientX&&(r=e.target.ownerDocument||V,i=r.documentElement,o=r.body,e.pageX=n.clientX+(i&&i.scrollLeft||o&&o.scrollLeft||0)-(i&&i.clientLeft||o&&o.clientLeft||0),e.pageY=n.clientY+(i&&i.scrollTop||o&&o.scrollTop||0)-(i&&i.clientTop||o&&o.clientTop||0)),!e.relatedTarget&&s&&(e.relatedTarget=s===e.target?n.toElement:s),e.which||a===t||(e.which=1&a?1:2&a?3:4&a?2:0),e}},special:{load:{noBubble:!0},click:{trigger:function(){return st.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):t}},focus:{trigger:function(){if(this!==V.activeElement&&this.focus)try{return this.focus(),!1}catch(e){}},delegateType:"focusin"},blur:{trigger:function(){return this===V.activeElement&&this.blur?(this.blur(),!1):t},delegateType:"focusout"},beforeunload:{postDispatch:function(e){e.result!==t&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=st.extend(new st.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?st.event.trigger(i,null,t):st.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},st.removeEvent=V.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,n,r){var i="on"+n;e.detachEvent&&(e[i]===t&&(e[i]=null),e.detachEvent(i,r))},st.Event=function(e,n){return this instanceof st.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?u:l):this.type=e,n&&st.extend(this,n),this.timeStamp=e&&e.timeStamp||st.now(),this[st.expando]=!0,t):new st.Event(e,n)},st.Event.prototype={isDefaultPrevented:l,isPropagationStopped:l,isImmediatePropagationStopped:l,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=u,e&&(e.preventDefault?e.preventDefault():e.returnValue=!1)},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=u,e&&(e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=u,this.stopPropagation()}},st.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){st.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return(!i||i!==r&&!st.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),st.support.submitBubbles||(st.event.special.submit={setup:function(){return st.nodeName(this,"form")?!1:(st.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=st.nodeName(n,"input")||st.nodeName(n,"button")?n.form:t;r&&!st._data(r,"submitBubbles")&&(st.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),st._data(r,"submitBubbles",!0))}),t)},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&st.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){return st.nodeName(this,"form")?!1:(st.event.remove(this,"._submit"),t)}}),st.support.changeBubbles||(st.event.special.change={setup:function(){return qt.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(st.event.add(this,"propertychange._change",function(e){"checked"===e.originalEvent.propertyName&&(this._just_changed=!0)}),st.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),st.event.simulate("change",this,e,!0)})),!1):(st.event.add(this,"beforeactivate._change",function(e){var t=e.target;qt.test(t.nodeName)&&!st._data(t,"changeBubbles")&&(st.event.add(t,"change._change",function(e){!this.parentNode||e.isSimulated||e.isTrigger||st.event.simulate("change",this.parentNode,e,!0)}),st._data(t,"changeBubbles",!0))}),t)},handle:function(e){var n=e.target;return this!==n||e.isSimulated||e.isTrigger||"radio"!==n.type&&"checkbox"!==n.type?e.handleObj.handler.apply(this,arguments):t},teardown:function(){return st.event.remove(this,"._change"),!qt.test(this.nodeName)}}),st.support.focusinBubbles||st.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){st.event.simulate(t,e.target,st.event.fix(e),!0)};st.event.special[t]={setup:function(){0===n++&&V.addEventListener(e,r,!0)},teardown:function(){0===--n&&V.removeEventListener(e,r,!0)}}}),st.fn.extend({on:function(e,n,r,i,o){var a,s;if("object"==typeof e){"string"!=typeof n&&(r=r||n,n=t);for(s in e)this.on(s,n,r,e[s],o);return this}if(null==r&&null==i?(i=n,r=n=t):null==i&&("string"==typeof n?(i=r,r=t):(i=r,r=n,n=t)),i===!1)i=l;else if(!i)return this;return 1===o&&(a=i,i=function(e){return st().off(e),a.apply(this,arguments)},i.guid=a.guid||(a.guid=st.guid++)),this.each(function(){st.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,o;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,st(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if("object"==typeof e){for(o in e)this.off(o,n,e[o]);return this}return(n===!1||"function"==typeof n)&&(r=n,n=t),r===!1&&(r=l),this.each(function(){st.event.remove(this,e,r,n)})},bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},trigger:function(e,t){return this.each(function(){st.event.trigger(e,t,this)})},triggerHandler:function(e,n){var r=this[0];return r?st.event.trigger(e,n,r,!0):t},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),st.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(e,t){st.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)},_t.test(t)&&(st.event.fixHooks[t]=st.event.keyHooks),Ft.test(t)&&(st.event.fixHooks[t]=st.event.mouseHooks)}),function(e,t){function n(e){return ht.test(e+"")}function r(){var e,t=[];return e=function(n,r){return t.push(n+=" ")>C.cacheLength&&delete e[t.shift()],e[n]=r}}function i(e){return e[P]=!0,e}function o(e){var t=L.createElement("div");try{return e(t)}catch(n){return!1}finally{t=null}}function a(e,t,n,r){var i,o,a,s,u,l,c,d,h,g;if((t?t.ownerDocument||t:R)!==L&&D(t),t=t||L,n=n||[],!e||"string"!=typeof e)return n;if(1!==(s=t.nodeType)&&9!==s)return[];if(!M&&!r){if(i=gt.exec(e))if(a=i[1]){if(9===s){if(o=t.getElementById(a),!o||!o.parentNode)return n;if(o.id===a)return n.push(o),n}else if(t.ownerDocument&&(o=t.ownerDocument.getElementById(a))&&O(t,o)&&o.id===a)return n.push(o),n}else{if(i[2])return Q.apply(n,K.call(t.getElementsByTagName(e),0)),n;if((a=i[3])&&W.getByClassName&&t.getElementsByClassName)return Q.apply(n,K.call(t.getElementsByClassName(a),0)),n}if(W.qsa&&!q.test(e)){if(c=!0,d=P,h=t,g=9===s&&e,1===s&&"object"!==t.nodeName.toLowerCase()){for(l=f(e),(c=t.getAttribute("id"))?d=c.replace(vt,"\\$&"):t.setAttribute("id",d),d="[id='"+d+"'] ",u=l.length;u--;)l[u]=d+p(l[u]);h=dt.test(e)&&t.parentNode||t,g=l.join(",")}if(g)try{return Q.apply(n,K.call(h.querySelectorAll(g),0)),n}catch(m){}finally{c||t.removeAttribute("id")}}}return x(e.replace(at,"$1"),t,n,r)}function s(e,t){for(var n=e&&t&&e.nextSibling;n;n=n.nextSibling)if(n===t)return-1;return e?1:-1}function u(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function l(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function c(e){return i(function(t){return t=+t,i(function(n,r){for(var i,o=e([],n.length,t),a=o.length;a--;)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}function f(e,t){var n,r,i,o,s,u,l,c=X[e+" "];if(c)return t?0:c.slice(0);for(s=e,u=[],l=C.preFilter;s;){(!n||(r=ut.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),u.push(i=[])),n=!1,(r=lt.exec(s))&&(n=r.shift(),i.push({value:n,type:r[0].replace(at," ")}),s=s.slice(n.length));for(o in C.filter)!(r=pt[o].exec(s))||l[o]&&!(r=l[o](r))||(n=r.shift(),i.push({value:n,type:o,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?a.error(e):X(e,u).slice(0)}function p(e){for(var t=0,n=e.length,r="";n>t;t++)r+=e[t].value;return r}function d(e,t,n){var r=t.dir,i=n&&"parentNode"===t.dir,o=I++;return t.first?function(t,n,o){for(;t=t[r];)if(1===t.nodeType||i)return e(t,n,o)}:function(t,n,a){var s,u,l,c=$+" "+o;if(a){for(;t=t[r];)if((1===t.nodeType||i)&&e(t,n,a))return!0}else for(;t=t[r];)if(1===t.nodeType||i)if(l=t[P]||(t[P]={}),(u=l[r])&&u[0]===c){if((s=u[1])===!0||s===N)return s===!0}else if(u=l[r]=[c],u[1]=e(t,n,a)||N,u[1]===!0)return!0}}function h(e){return e.length>1?function(t,n,r){for(var i=e.length;i--;)if(!e[i](t,n,r))return!1;return!0}:e[0]}function g(e,t,n,r,i){for(var o,a=[],s=0,u=e.length,l=null!=t;u>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),l&&t.push(s));return a}function m(e,t,n,r,o,a){return r&&!r[P]&&(r=m(r)),o&&!o[P]&&(o=m(o,a)),i(function(i,a,s,u){var l,c,f,p=[],d=[],h=a.length,m=i||b(t||"*",s.nodeType?[s]:s,[]),y=!e||!i&&t?m:g(m,p,e,s,u),v=n?o||(i?e:h||r)?[]:a:y;if(n&&n(y,v,s,u),r)for(l=g(v,d),r(l,[],s,u),c=l.length;c--;)(f=l[c])&&(v[d[c]]=!(y[d[c]]=f));if(i){if(o||e){if(o){for(l=[],c=v.length;c--;)(f=v[c])&&l.push(y[c]=f);o(null,v=[],l,u)}for(c=v.length;c--;)(f=v[c])&&(l=o?Z.call(i,f):p[c])>-1&&(i[l]=!(a[l]=f))}}else v=g(v===a?v.splice(h,v.length):v),o?o(null,a,v,u):Q.apply(a,v)})}function y(e){for(var t,n,r,i=e.length,o=C.relative[e[0].type],a=o||C.relative[" "],s=o?1:0,u=d(function(e){return e===t},a,!0),l=d(function(e){return Z.call(t,e)>-1},a,!0),c=[function(e,n,r){return!o&&(r||n!==j)||((t=n).nodeType?u(e,n,r):l(e,n,r))}];i>s;s++)if(n=C.relative[e[s].type])c=[d(h(c),n)];else{if(n=C.filter[e[s].type].apply(null,e[s].matches),n[P]){for(r=++s;i>r&&!C.relative[e[r].type];r++);return m(s>1&&h(c),s>1&&p(e.slice(0,s-1)).replace(at,"$1"),n,r>s&&y(e.slice(s,r)),i>r&&y(e=e.slice(r)),i>r&&p(e))}c.push(n)}return h(c)}function v(e,t){var n=0,r=t.length>0,o=e.length>0,s=function(i,s,u,l,c){var f,p,d,h=[],m=0,y="0",v=i&&[],b=null!=c,x=j,T=i||o&&C.find.TAG("*",c&&s.parentNode||s),w=$+=null==x?1:Math.E;for(b&&(j=s!==L&&s,N=n);null!=(f=T[y]);y++){if(o&&f){for(p=0;d=e[p];p++)if(d(f,s,u)){l.push(f);break}b&&($=w,N=++n)}r&&((f=!d&&f)&&m--,i&&v.push(f))}if(m+=y,r&&y!==m){for(p=0;d=t[p];p++)d(v,h,s,u);if(i){if(m>0)for(;y--;)v[y]||h[y]||(h[y]=G.call(l));h=g(h)}Q.apply(l,h),b&&!i&&h.length>0&&m+t.length>1&&a.uniqueSort(l)}return b&&($=w,j=x),v};return r?i(s):s}function b(e,t,n){for(var r=0,i=t.length;i>r;r++)a(e,t[r],n);return n}function x(e,t,n,r){var i,o,a,s,u,l=f(e);if(!r&&1===l.length){if(o=l[0]=l[0].slice(0),o.length>2&&"ID"===(a=o[0]).type&&9===t.nodeType&&!M&&C.relative[o[1].type]){if(t=C.find.ID(a.matches[0].replace(xt,Tt),t)[0],!t)return n;e=e.slice(o.shift().value.length)}for(i=pt.needsContext.test(e)?-1:o.length-1;i>=0&&(a=o[i],!C.relative[s=a.type]);i--)if((u=C.find[s])&&(r=u(a.matches[0].replace(xt,Tt),dt.test(o[0].type)&&t.parentNode||t))){if(o.splice(i,1),e=r.length&&p(o),!e)return Q.apply(n,K.call(r,0)),n;break}}return S(e,l)(r,t,M,n,dt.test(e)),n}function T(){}var w,N,C,k,E,S,A,j,D,L,H,M,q,_,F,O,B,P="sizzle"+-new Date,R=e.document,W={},$=0,I=0,z=r(),X=r(),U=r(),V=typeof t,Y=1<<31,J=[],G=J.pop,Q=J.push,K=J.slice,Z=J.indexOf||function(e){for(var t=0,n=this.length;n>t;t++)if(this[t]===e)return t;return-1},et="[\\x20\\t\\r\\n\\f]",tt="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",nt=tt.replace("w","w#"),rt="([*^$|!~]?=)",it="\\["+et+"*("+tt+")"+et+"*(?:"+rt+et+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+nt+")|)|)"+et+"*\\]",ot=":("+tt+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+it.replace(3,8)+")*)|.*)\\)|)",at=RegExp("^"+et+"+|((?:^|[^\\\\])(?:\\\\.)*)"+et+"+$","g"),ut=RegExp("^"+et+"*,"+et+"*"),lt=RegExp("^"+et+"*([\\x20\\t\\r\\n\\f>+~])"+et+"*"),ct=RegExp(ot),ft=RegExp("^"+nt+"$"),pt={ID:RegExp("^#("+tt+")"),CLASS:RegExp("^\\.("+tt+")"),NAME:RegExp("^\\[name=['\"]?("+tt+")['\"]?\\]"),TAG:RegExp("^("+tt.replace("w","w*")+")"),ATTR:RegExp("^"+it),PSEUDO:RegExp("^"+ot),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+et+"*(even|odd|(([+-]|)(\\d*)n|)"+et+"*(?:([+-]|)"+et+"*(\\d+)|))"+et+"*\\)|)","i"),needsContext:RegExp("^"+et+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+et+"*((?:-\\d)?\\d*)"+et+"*\\)|)(?=[^-]|$)","i")},dt=/[\x20\t\r\n\f]*[+~]/,ht=/\{\s*\[native code\]\s*\}/,gt=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,mt=/^(?:input|select|textarea|button)$/i,yt=/^h\d$/i,vt=/'|\\/g,bt=/\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,xt=/\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g,Tt=function(e,t){var n="0x"+t-65536;return n!==n?t:0>n?String.fromCharCode(n+65536):String.fromCharCode(55296|n>>10,56320|1023&n)};try{K.call(H.childNodes,0)[0].nodeType}catch(wt){K=function(e){for(var t,n=[];t=this[e];e++)n.push(t);return n}}E=a.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},D=a.setDocument=function(e){var r=e?e.ownerDocument||e:R;return r!==L&&9===r.nodeType&&r.documentElement?(L=r,H=r.documentElement,M=E(r),W.tagNameNoComments=o(function(e){return e.appendChild(r.createComment("")),!e.getElementsByTagName("*").length}),W.attributes=o(function(e){e.innerHTML="<select></select>";var t=typeof e.lastChild.getAttribute("multiple");return"boolean"!==t&&"string"!==t}),W.getByClassName=o(function(e){return e.innerHTML="<div class='hidden e'></div><div class='hidden'></div>",e.getElementsByClassName&&e.getElementsByClassName("e").length?(e.lastChild.className="e",2===e.getElementsByClassName("e").length):!1}),W.getByName=o(function(e){e.id=P+0,e.innerHTML="<a name='"+P+"'></a><div name='"+P+"'></div>",H.insertBefore(e,H.firstChild);var t=r.getElementsByName&&r.getElementsByName(P).length===2+r.getElementsByName(P+0).length;return W.getIdNotName=!r.getElementById(P),H.removeChild(e),t}),C.attrHandle=o(function(e){return e.innerHTML="<a href='#'></a>",e.firstChild&&typeof e.firstChild.getAttribute!==V&&"#"===e.firstChild.getAttribute("href")})?{}:{href:function(e){return e.getAttribute("href",2)},type:function(e){return e.getAttribute("type")}},W.getIdNotName?(C.find.ID=function(e,t){if(typeof t.getElementById!==V&&!M){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},C.filter.ID=function(e){var t=e.replace(xt,Tt);return function(e){return e.getAttribute("id")===t}}):(C.find.ID=function(e,n){if(typeof n.getElementById!==V&&!M){var r=n.getElementById(e);return r?r.id===e||typeof r.getAttributeNode!==V&&r.getAttributeNode("id").value===e?[r]:t:[]}},C.filter.ID=function(e){var t=e.replace(xt,Tt);return function(e){var n=typeof e.getAttributeNode!==V&&e.getAttributeNode("id");return n&&n.value===t}}),C.find.TAG=W.tagNameNoComments?function(e,n){return typeof n.getElementsByTagName!==V?n.getElementsByTagName(e):t}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){for(;n=o[i];i++)1===n.nodeType&&r.push(n);return r}return o},C.find.NAME=W.getByName&&function(e,n){return typeof n.getElementsByName!==V?n.getElementsByName(name):t},C.find.CLASS=W.getByClassName&&function(e,n){return typeof n.getElementsByClassName===V||M?t:n.getElementsByClassName(e)},_=[],q=[":focus"],(W.qsa=n(r.querySelectorAll))&&(o(function(e){e.innerHTML="<select><option selected=''></option></select>",e.querySelectorAll("[selected]").length||q.push("\\["+et+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),e.querySelectorAll(":checked").length||q.push(":checked")}),o(function(e){e.innerHTML="<input type='hidden' i=''/>",e.querySelectorAll("[i^='']").length&&q.push("[*^$]="+et+"*(?:\"\"|'')"),e.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),q.push(",.*:")})),(W.matchesSelector=n(F=H.matchesSelector||H.mozMatchesSelector||H.webkitMatchesSelector||H.oMatchesSelector||H.msMatchesSelector))&&o(function(e){W.disconnectedMatch=F.call(e,"div"),F.call(e,"[s!='']:x"),_.push("!=",ot)}),q=RegExp(q.join("|")),_=RegExp(_.join("|")),O=n(H.contains)||H.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)for(;t=t.parentNode;)if(t===e)return!0;return!1},B=H.compareDocumentPosition?function(e,t){var n;return e===t?(A=!0,0):(n=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t))?1&n||e.parentNode&&11===e.parentNode.nodeType?e===r||O(R,e)?-1:t===r||O(R,t)?1:0:4&n?-1:1:e.compareDocumentPosition?-1:1}:function(e,t){var n,i=0,o=e.parentNode,a=t.parentNode,u=[e],l=[t];if(e===t)return A=!0,0;if(e.sourceIndex&&t.sourceIndex)return(~t.sourceIndex||Y)-(O(R,e)&&~e.sourceIndex||Y);if(!o||!a)return e===r?-1:t===r?1:o?-1:a?1:0;if(o===a)return s(e,t);for(n=e;n=n.parentNode;)u.unshift(n);for(n=t;n=n.parentNode;)l.unshift(n);for(;u[i]===l[i];)i++;return i?s(u[i],l[i]):u[i]===R?-1:l[i]===R?1:0},A=!1,[0,0].sort(B),W.detectDuplicates=A,L):L},a.matches=function(e,t){return a(e,null,null,t)},a.matchesSelector=function(e,t){if((e.ownerDocument||e)!==L&&D(e),t=t.replace(bt,"='$1']"),!(!W.matchesSelector||M||_&&_.test(t)||q.test(t)))try{var n=F.call(e,t);if(n||W.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(r){}return a(t,L,null,[e]).length>0},a.contains=function(e,t){return(e.ownerDocument||e)!==L&&D(e),O(e,t)},a.attr=function(e,t){var n;return(e.ownerDocument||e)!==L&&D(e),M||(t=t.toLowerCase()),(n=C.attrHandle[t])?n(e):M||W.attributes?e.getAttribute(t):((n=e.getAttributeNode(t))||e.getAttribute(t))&&e[t]===!0?t:n&&n.specified?n.value:null},a.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},a.uniqueSort=function(e){var t,n=[],r=1,i=0;if(A=!W.detectDuplicates,e.sort(B),A){for(;t=e[r];r++)t===e[r-1]&&(i=n.push(r));for(;i--;)e.splice(n[i],1)}return e},k=a.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=k(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=k(t);return n},C=a.selectors={cacheLength:50,createPseudo:i,match:pt,find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(xt,Tt),e[3]=(e[4]||e[5]||"").replace(xt,Tt),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||a.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&a.error(e[0]),e},PSEUDO:function(e){var t,n=!e[5]&&e[2];return pt.CHILD.test(e[0])?null:(e[4]?e[2]=e[4]:n&&ct.test(n)&&(t=f(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){return"*"===e?function(){return!0}:(e=e.replace(xt,Tt).toLowerCase(),function(t){return t.nodeName&&t.nodeName.toLowerCase()===e})},CLASS:function(e){var t=z[e+" "];return t||(t=RegExp("(^|"+et+")"+e+"("+et+"|$)"))&&z(e,function(e){return t.test(e.className||typeof e.getAttribute!==V&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=a.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.substr(i.length-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.substr(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,f,p,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!u&&!s;if(m){if(o){for(;g;){for(f=t;f=f[g];)if(s?f.nodeName.toLowerCase()===y:1===f.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){for(c=m[P]||(m[P]={}),l=c[e]||[],d=l[0]===$&&l[1],p=l[0]===$&&l[2],f=d&&m.childNodes[d];f=++d&&f&&f[g]||(p=d=0)||h.pop();)if(1===f.nodeType&&++p&&f===t){c[e]=[$,d,p];break}}else if(v&&(l=(t[P]||(t[P]={}))[e])&&l[0]===$)p=l[1];else for(;(f=++d&&f&&f[g]||(p=d=0)||h.pop())&&((s?f.nodeName.toLowerCase()!==y:1!==f.nodeType)||!++p||(v&&((f[P]||(f[P]={}))[e]=[$,p]),f!==t)););return p-=i,p===r||0===p%r&&p/r>=0}}},PSEUDO:function(e,t){var n,r=C.pseudos[e]||C.setFilters[e.toLowerCase()]||a.error("unsupported pseudo: "+e);return r[P]?r(t):r.length>1?(n=[e,e,"",t],C.setFilters.hasOwnProperty(e.toLowerCase())?i(function(e,n){for(var i,o=r(e,t),a=o.length;a--;)i=Z.call(e,o[a]),e[i]=!(n[i]=o[a])}):function(e){return r(e,0,n)}):r}},pseudos:{not:i(function(e){var t=[],n=[],r=S(e.replace(at,"$1"));return r[P]?i(function(e,t,n,i){for(var o,a=r(e,null,i,[]),s=e.length;s--;)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:i(function(e){return function(t){return a(e,t).length>0}}),contains:i(function(e){return function(t){return(t.textContent||t.innerText||k(t)).indexOf(e)>-1}}),lang:i(function(e){return ft.test(e||"")||a.error("unsupported lang: "+e),e=e.replace(xt,Tt).toLowerCase(),function(t){var n;do if(n=M?t.getAttribute("xml:lang")||t.getAttribute("lang"):t.lang)return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===H},focus:function(e){return e===L.activeElement&&(!L.hasFocus||L.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!C.pseudos.empty(e)},header:function(e){return yt.test(e.nodeName)},input:function(e){return mt.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:c(function(){return[0]}),last:c(function(e,t){return[t-1]}),eq:c(function(e,t,n){return[0>n?n+t:n]}),even:c(function(e,t){for(var n=0;t>n;n+=2)e.push(n);return e}),odd:c(function(e,t){for(var n=1;t>n;n+=2)e.push(n);return e}),lt:c(function(e,t,n){for(var r=0>n?n+t:n;--r>=0;)e.push(r);return e}),gt:c(function(e,t,n){for(var r=0>n?n+t:n;t>++r;)e.push(r);return e})}};for(w in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})C.pseudos[w]=u(w);for(w in{submit:!0,reset:!0})C.pseudos[w]=l(w);S=a.compile=function(e,t){var n,r=[],i=[],o=U[e+" "];if(!o){for(t||(t=f(e)),n=t.length;n--;)o=y(t[n]),o[P]?r.push(o):i.push(o);o=U(e,v(i,r))}return o},C.pseudos.nth=C.pseudos.eq,C.filters=T.prototype=C.pseudos,C.setFilters=new T,D(),a.attr=st.attr,st.find=a,st.expr=a.selectors,st.expr[":"]=st.expr.pseudos,st.unique=a.uniqueSort,st.text=a.getText,st.isXMLDoc=a.isXML,st.contains=a.contains}(e);var Pt=/Until$/,Rt=/^(?:parents|prev(?:Until|All))/,Wt=/^.[^:#\[\.,]*$/,$t=st.expr.match.needsContext,It={children:!0,contents:!0,next:!0,prev:!0};st.fn.extend({find:function(e){var t,n,r;if("string"!=typeof e)return r=this,this.pushStack(st(e).filter(function(){for(t=0;r.length>t;t++)if(st.contains(r[t],this))return!0}));for(n=[],t=0;this.length>t;t++)st.find(e,this[t],n);return n=this.pushStack(st.unique(n)),n.selector=(this.selector?this.selector+" ":"")+e,n},has:function(e){var t,n=st(e,this),r=n.length;return this.filter(function(){for(t=0;r>t;t++)if(st.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(f(this,e,!1))},filter:function(e){return this.pushStack(f(this,e,!0))},is:function(e){return!!e&&("string"==typeof e?$t.test(e)?st(e,this.context).index(this[0])>=0:st.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){for(var n,r=0,i=this.length,o=[],a=$t.test(e)||"string"!=typeof e?st(e,t||this.context):0;i>r;r++)for(n=this[r];n&&n.ownerDocument&&n!==t&&11!==n.nodeType;){if(a?a.index(n)>-1:st.find.matchesSelector(n,e)){o.push(n);break}n=n.parentNode}return this.pushStack(o.length>1?st.unique(o):o)},index:function(e){return e?"string"==typeof e?st.inArray(this[0],st(e)):st.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?st(e,t):st.makeArray(e&&e.nodeType?[e]:e),r=st.merge(this.get(),n);return this.pushStack(st.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),st.fn.andSelf=st.fn.addBack,st.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return st.dir(e,"parentNode")},parentsUntil:function(e,t,n){return st.dir(e,"parentNode",n)},next:function(e){return c(e,"nextSibling")},prev:function(e){return c(e,"previousSibling")
    +},nextAll:function(e){return st.dir(e,"nextSibling")},prevAll:function(e){return st.dir(e,"previousSibling")},nextUntil:function(e,t,n){return st.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return st.dir(e,"previousSibling",n)},siblings:function(e){return st.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return st.sibling(e.firstChild)},contents:function(e){return st.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:st.merge([],e.childNodes)}},function(e,t){st.fn[e]=function(n,r){var i=st.map(this,t,n);return Pt.test(e)||(r=n),r&&"string"==typeof r&&(i=st.filter(r,i)),i=this.length>1&&!It[e]?st.unique(i):i,this.length>1&&Rt.test(e)&&(i=i.reverse()),this.pushStack(i)}}),st.extend({filter:function(e,t,n){return n&&(e=":not("+e+")"),1===t.length?st.find.matchesSelector(t[0],e)?[t[0]]:[]:st.find.matches(e,t)},dir:function(e,n,r){for(var i=[],o=e[n];o&&9!==o.nodeType&&(r===t||1!==o.nodeType||!st(o).is(r));)1===o.nodeType&&i.push(o),o=o[n];return i},sibling:function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});var zt="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",Xt=/ jQuery\d+="(?:null|\d+)"/g,Ut=RegExp("<(?:"+zt+")[\\s/>]","i"),Vt=/^\s+/,Yt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,Jt=/<([\w:]+)/,Gt=/<tbody/i,Qt=/<|&#?\w+;/,Kt=/<(?:script|style|link)/i,Zt=/^(?:checkbox|radio)$/i,en=/checked\s*(?:[^=]|=\s*.checked.)/i,tn=/^$|\/(?:java|ecma)script/i,nn=/^true\/(.*)/,rn=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,on={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],area:[1,"<map>","</map>"],param:[1,"<object>","</object>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:st.support.htmlSerialize?[0,"",""]:[1,"X<div>","</div>"]},an=p(V),sn=an.appendChild(V.createElement("div"));on.optgroup=on.option,on.tbody=on.tfoot=on.colgroup=on.caption=on.thead,on.th=on.td,st.fn.extend({text:function(e){return st.access(this,function(e){return e===t?st.text(this):this.empty().append((this[0]&&this[0].ownerDocument||V).createTextNode(e))},null,e,arguments.length)},wrapAll:function(e){if(st.isFunction(e))return this.each(function(t){st(this).wrapAll(e.call(this,t))});if(this[0]){var t=st(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){for(var e=this;e.firstChild&&1===e.firstChild.nodeType;)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return st.isFunction(e)?this.each(function(t){st(this).wrapInner(e.call(this,t))}):this.each(function(){var t=st(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=st.isFunction(e);return this.each(function(n){st(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){st.nodeName(this,"body")||st(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(e){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&this.appendChild(e)})},prepend:function(){return this.domManip(arguments,!0,function(e){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&this.insertBefore(e,this.firstChild)})},before:function(){return this.domManip(arguments,!1,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,!1,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){for(var n,r=0;null!=(n=this[r]);r++)(!e||st.filter(e,[n]).length>0)&&(t||1!==n.nodeType||st.cleanData(b(n)),n.parentNode&&(t&&st.contains(n.ownerDocument,n)&&m(b(n,"script")),n.parentNode.removeChild(n)));return this},empty:function(){for(var e,t=0;null!=(e=this[t]);t++){for(1===e.nodeType&&st.cleanData(b(e,!1));e.firstChild;)e.removeChild(e.firstChild);e.options&&st.nodeName(e,"select")&&(e.options.length=0)}return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return st.clone(this,e,t)})},html:function(e){return st.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return 1===n.nodeType?n.innerHTML.replace(Xt,""):t;if(!("string"!=typeof e||Kt.test(e)||!st.support.htmlSerialize&&Ut.test(e)||!st.support.leadingWhitespace&&Vt.test(e)||on[(Jt.exec(e)||["",""])[1].toLowerCase()])){e=e.replace(Yt,"<$1></$2>");try{for(;i>r;r++)n=this[r]||{},1===n.nodeType&&(st.cleanData(b(n,!1)),n.innerHTML=e);n=0}catch(o){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(e){var t=st.isFunction(e);return t||"string"==typeof e||(e=st(e).not(this).detach()),this.domManip([e],!0,function(e){var t=this.nextSibling,n=this.parentNode;(n&&1===this.nodeType||11===this.nodeType)&&(st(this).remove(),t?t.parentNode.insertBefore(e,t):n.appendChild(e))})},detach:function(e){return this.remove(e,!0)},domManip:function(e,n,r){e=et.apply([],e);var i,o,a,s,u,l,c=0,f=this.length,p=this,m=f-1,y=e[0],v=st.isFunction(y);if(v||!(1>=f||"string"!=typeof y||st.support.checkClone)&&en.test(y))return this.each(function(i){var o=p.eq(i);v&&(e[0]=y.call(this,i,n?o.html():t)),o.domManip(e,n,r)});if(f&&(i=st.buildFragment(e,this[0].ownerDocument,!1,this),o=i.firstChild,1===i.childNodes.length&&(i=o),o)){for(n=n&&st.nodeName(o,"tr"),a=st.map(b(i,"script"),h),s=a.length;f>c;c++)u=i,c!==m&&(u=st.clone(u,!0,!0),s&&st.merge(a,b(u,"script"))),r.call(n&&st.nodeName(this[c],"table")?d(this[c],"tbody"):this[c],u,c);if(s)for(l=a[a.length-1].ownerDocument,st.map(a,g),c=0;s>c;c++)u=a[c],tn.test(u.type||"")&&!st._data(u,"globalEval")&&st.contains(l,u)&&(u.src?st.ajax({url:u.src,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0}):st.globalEval((u.text||u.textContent||u.innerHTML||"").replace(rn,"")));i=o=null}return this}}),st.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){st.fn[e]=function(e){for(var n,r=0,i=[],o=st(e),a=o.length-1;a>=r;r++)n=r===a?this:this.clone(!0),st(o[r])[t](n),tt.apply(i,n.get());return this.pushStack(i)}}),st.extend({clone:function(e,t,n){var r,i,o,a,s,u=st.contains(e.ownerDocument,e);if(st.support.html5Clone||st.isXMLDoc(e)||!Ut.test("<"+e.nodeName+">")?s=e.cloneNode(!0):(sn.innerHTML=e.outerHTML,sn.removeChild(s=sn.firstChild)),!(st.support.noCloneEvent&&st.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||st.isXMLDoc(e)))for(r=b(s),i=b(e),a=0;null!=(o=i[a]);++a)r[a]&&v(o,r[a]);if(t)if(n)for(i=i||b(e),r=r||b(s),a=0;null!=(o=i[a]);a++)y(o,r[a]);else y(e,s);return r=b(s,"script"),r.length>0&&m(r,!u&&b(e,"script")),r=i=o=null,s},buildFragment:function(e,t,n,r){for(var i,o,a,s,u,l,c,f=e.length,d=p(t),h=[],g=0;f>g;g++)if(o=e[g],o||0===o)if("object"===st.type(o))st.merge(h,o.nodeType?[o]:o);else if(Qt.test(o)){for(s=s||d.appendChild(t.createElement("div")),a=(Jt.exec(o)||["",""])[1].toLowerCase(),u=on[a]||on._default,s.innerHTML=u[1]+o.replace(Yt,"<$1></$2>")+u[2],c=u[0];c--;)s=s.lastChild;if(!st.support.leadingWhitespace&&Vt.test(o)&&h.push(t.createTextNode(Vt.exec(o)[0])),!st.support.tbody)for(o="table"!==a||Gt.test(o)?"<table>"!==u[1]||Gt.test(o)?0:s:s.firstChild,c=o&&o.childNodes.length;c--;)st.nodeName(l=o.childNodes[c],"tbody")&&!l.childNodes.length&&o.removeChild(l);for(st.merge(h,s.childNodes),s.textContent="";s.firstChild;)s.removeChild(s.firstChild);s=d.lastChild}else h.push(t.createTextNode(o));for(s&&d.removeChild(s),st.support.appendChecked||st.grep(b(h,"input"),x),g=0;o=h[g++];)if((!r||-1===st.inArray(o,r))&&(i=st.contains(o.ownerDocument,o),s=b(d.appendChild(o),"script"),i&&m(s),n))for(c=0;o=s[c++];)tn.test(o.type||"")&&n.push(o);return s=null,d},cleanData:function(e,n){for(var r,i,o,a,s=0,u=st.expando,l=st.cache,c=st.support.deleteExpando,f=st.event.special;null!=(o=e[s]);s++)if((n||st.acceptData(o))&&(i=o[u],r=i&&l[i])){if(r.events)for(a in r.events)f[a]?st.event.remove(o,a):st.removeEvent(o,a,r.handle);l[i]&&(delete l[i],c?delete o[u]:o.removeAttribute!==t?o.removeAttribute(u):o[u]=null,K.push(i))}}});var un,ln,cn,fn=/alpha\([^)]*\)/i,pn=/opacity\s*=\s*([^)]*)/,dn=/^(top|right|bottom|left)$/,hn=/^(none|table(?!-c[ea]).+)/,gn=/^margin/,mn=RegExp("^("+ut+")(.*)$","i"),yn=RegExp("^("+ut+")(?!px)[a-z%]+$","i"),vn=RegExp("^([+-])=("+ut+")","i"),bn={BODY:"block"},xn={position:"absolute",visibility:"hidden",display:"block"},Tn={letterSpacing:0,fontWeight:400},wn=["Top","Right","Bottom","Left"],Nn=["Webkit","O","Moz","ms"];st.fn.extend({css:function(e,n){return st.access(this,function(e,n,r){var i,o,a={},s=0;if(st.isArray(n)){for(i=ln(e),o=n.length;o>s;s++)a[n[s]]=st.css(e,n[s],!1,i);return a}return r!==t?st.style(e,n,r):st.css(e,n)},e,n,arguments.length>1)},show:function(){return N(this,!0)},hide:function(){return N(this)},toggle:function(e){var t="boolean"==typeof e;return this.each(function(){(t?e:w(this))?st(this).show():st(this).hide()})}}),st.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=un(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":st.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var o,a,s,u=st.camelCase(n),l=e.style;if(n=st.cssProps[u]||(st.cssProps[u]=T(l,u)),s=st.cssHooks[n]||st.cssHooks[u],r===t)return s&&"get"in s&&(o=s.get(e,!1,i))!==t?o:l[n];if(a=typeof r,"string"===a&&(o=vn.exec(r))&&(r=(o[1]+1)*o[2]+parseFloat(st.css(e,n)),a="number"),!(null==r||"number"===a&&isNaN(r)||("number"!==a||st.cssNumber[u]||(r+="px"),st.support.clearCloneStyle||""!==r||0!==n.indexOf("background")||(l[n]="inherit"),s&&"set"in s&&(r=s.set(e,r,i))===t)))try{l[n]=r}catch(c){}}},css:function(e,n,r,i){var o,a,s,u=st.camelCase(n);return n=st.cssProps[u]||(st.cssProps[u]=T(e.style,u)),s=st.cssHooks[n]||st.cssHooks[u],s&&"get"in s&&(o=s.get(e,!0,r)),o===t&&(o=un(e,n,i)),"normal"===o&&n in Tn&&(o=Tn[n]),r?(a=parseFloat(o),r===!0||st.isNumeric(a)?a||0:o):o},swap:function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i}}),e.getComputedStyle?(ln=function(t){return e.getComputedStyle(t,null)},un=function(e,n,r){var i,o,a,s=r||ln(e),u=s?s.getPropertyValue(n)||s[n]:t,l=e.style;return s&&(""!==u||st.contains(e.ownerDocument,e)||(u=st.style(e,n)),yn.test(u)&&gn.test(n)&&(i=l.width,o=l.minWidth,a=l.maxWidth,l.minWidth=l.maxWidth=l.width=u,u=s.width,l.width=i,l.minWidth=o,l.maxWidth=a)),u}):V.documentElement.currentStyle&&(ln=function(e){return e.currentStyle},un=function(e,n,r){var i,o,a,s=r||ln(e),u=s?s[n]:t,l=e.style;return null==u&&l&&l[n]&&(u=l[n]),yn.test(u)&&!dn.test(n)&&(i=l.left,o=e.runtimeStyle,a=o&&o.left,a&&(o.left=e.currentStyle.left),l.left="fontSize"===n?"1em":u,u=l.pixelLeft+"px",l.left=i,a&&(o.left=a)),""===u?"auto":u}),st.each(["height","width"],function(e,n){st.cssHooks[n]={get:function(e,r,i){return r?0===e.offsetWidth&&hn.test(st.css(e,"display"))?st.swap(e,xn,function(){return E(e,n,i)}):E(e,n,i):t},set:function(e,t,r){var i=r&&ln(e);return C(e,t,r?k(e,n,r,st.support.boxSizing&&"border-box"===st.css(e,"boxSizing",!1,i),i):0)}}}),st.support.opacity||(st.cssHooks.opacity={get:function(e,t){return pn.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=st.isNumeric(t)?"alpha(opacity="+100*t+")":"",o=r&&r.filter||n.filter||"";n.zoom=1,(t>=1||""===t)&&""===st.trim(o.replace(fn,""))&&n.removeAttribute&&(n.removeAttribute("filter"),""===t||r&&!r.filter)||(n.filter=fn.test(o)?o.replace(fn,i):o+" "+i)}}),st(function(){st.support.reliableMarginRight||(st.cssHooks.marginRight={get:function(e,n){return n?st.swap(e,{display:"inline-block"},un,[e,"marginRight"]):t}}),!st.support.pixelPosition&&st.fn.position&&st.each(["top","left"],function(e,n){st.cssHooks[n]={get:function(e,r){return r?(r=un(e,n),yn.test(r)?st(e).position()[n]+"px":r):t}}})}),st.expr&&st.expr.filters&&(st.expr.filters.hidden=function(e){return 0===e.offsetWidth&&0===e.offsetHeight||!st.support.reliableHiddenOffsets&&"none"===(e.style&&e.style.display||st.css(e,"display"))},st.expr.filters.visible=function(e){return!st.expr.filters.hidden(e)}),st.each({margin:"",padding:"",border:"Width"},function(e,t){st.cssHooks[e+t]={expand:function(n){for(var r=0,i={},o="string"==typeof n?n.split(" "):[n];4>r;r++)i[e+wn[r]+t]=o[r]||o[r-2]||o[0];return i}},gn.test(e)||(st.cssHooks[e+t].set=C)});var Cn=/%20/g,kn=/\[\]$/,En=/\r?\n/g,Sn=/^(?:submit|button|image|reset)$/i,An=/^(?:input|select|textarea|keygen)/i;st.fn.extend({serialize:function(){return st.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=st.prop(this,"elements");return e?st.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!st(this).is(":disabled")&&An.test(this.nodeName)&&!Sn.test(e)&&(this.checked||!Zt.test(e))}).map(function(e,t){var n=st(this).val();return null==n?null:st.isArray(n)?st.map(n,function(e){return{name:t.name,value:e.replace(En,"\r\n")}}):{name:t.name,value:n.replace(En,"\r\n")}}).get()}}),st.param=function(e,n){var r,i=[],o=function(e,t){t=st.isFunction(t)?t():null==t?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};if(n===t&&(n=st.ajaxSettings&&st.ajaxSettings.traditional),st.isArray(e)||e.jquery&&!st.isPlainObject(e))st.each(e,function(){o(this.name,this.value)});else for(r in e)j(r,e[r],n,o);return i.join("&").replace(Cn,"+")};var jn,Dn,Ln=st.now(),Hn=/\?/,Mn=/#.*$/,qn=/([?&])_=[^&]*/,_n=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Fn=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,On=/^(?:GET|HEAD)$/,Bn=/^\/\//,Pn=/^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,Rn=st.fn.load,Wn={},$n={},In="*/".concat("*");try{Dn=Y.href}catch(zn){Dn=V.createElement("a"),Dn.href="",Dn=Dn.href}jn=Pn.exec(Dn.toLowerCase())||[],st.fn.load=function(e,n,r){if("string"!=typeof e&&Rn)return Rn.apply(this,arguments);var i,o,a,s=this,u=e.indexOf(" ");return u>=0&&(i=e.slice(u,e.length),e=e.slice(0,u)),st.isFunction(n)?(r=n,n=t):n&&"object"==typeof n&&(o="POST"),s.length>0&&st.ajax({url:e,type:o,dataType:"html",data:n}).done(function(e){a=arguments,s.html(i?st("<div>").append(st.parseHTML(e)).find(i):e)}).complete(r&&function(e,t){s.each(r,a||[e.responseText,t,e])}),this},st.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){st.fn[t]=function(e){return this.on(t,e)}}),st.each(["get","post"],function(e,n){st[n]=function(e,r,i,o){return st.isFunction(r)&&(o=o||i,i=r,r=t),st.ajax({url:e,type:n,dataType:o,data:r,success:i})}}),st.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Dn,type:"GET",isLocal:Fn.test(jn[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":In,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":e.String,"text html":!0,"text json":st.parseJSON,"text xml":st.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?H(H(e,st.ajaxSettings),t):H(st.ajaxSettings,e)},ajaxPrefilter:D(Wn),ajaxTransport:D($n),ajax:function(e,n){function r(e,n,r,s){var l,f,v,b,T,N=n;2!==x&&(x=2,u&&clearTimeout(u),i=t,a=s||"",w.readyState=e>0?4:0,r&&(b=M(p,w,r)),e>=200&&300>e||304===e?(p.ifModified&&(T=w.getResponseHeader("Last-Modified"),T&&(st.lastModified[o]=T),T=w.getResponseHeader("etag"),T&&(st.etag[o]=T)),304===e?(l=!0,N="notmodified"):(l=q(p,b),N=l.state,f=l.data,v=l.error,l=!v)):(v=N,(e||!N)&&(N="error",0>e&&(e=0))),w.status=e,w.statusText=(n||N)+"",l?g.resolveWith(d,[f,N,w]):g.rejectWith(d,[w,N,v]),w.statusCode(y),y=t,c&&h.trigger(l?"ajaxSuccess":"ajaxError",[w,p,l?f:v]),m.fireWith(d,[w,N]),c&&(h.trigger("ajaxComplete",[w,p]),--st.active||st.event.trigger("ajaxStop")))}"object"==typeof e&&(n=e,e=t),n=n||{};var i,o,a,s,u,l,c,f,p=st.ajaxSetup({},n),d=p.context||p,h=p.context&&(d.nodeType||d.jquery)?st(d):st.event,g=st.Deferred(),m=st.Callbacks("once memory"),y=p.statusCode||{},v={},b={},x=0,T="canceled",w={readyState:0,getResponseHeader:function(e){var t;if(2===x){if(!s)for(s={};t=_n.exec(a);)s[t[1].toLowerCase()]=t[2];t=s[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return 2===x?a:null},setRequestHeader:function(e,t){var n=e.toLowerCase();return x||(e=b[n]=b[n]||e,v[e]=t),this},overrideMimeType:function(e){return x||(p.mimeType=e),this},statusCode:function(e){var t;if(e)if(2>x)for(t in e)y[t]=[y[t],e[t]];else w.always(e[w.status]);return this},abort:function(e){var t=e||T;return i&&i.abort(t),r(0,t),this}};if(g.promise(w).complete=m.add,w.success=w.done,w.error=w.fail,p.url=((e||p.url||Dn)+"").replace(Mn,"").replace(Bn,jn[1]+"//"),p.type=n.method||n.type||p.method||p.type,p.dataTypes=st.trim(p.dataType||"*").toLowerCase().match(lt)||[""],null==p.crossDomain&&(l=Pn.exec(p.url.toLowerCase()),p.crossDomain=!(!l||l[1]===jn[1]&&l[2]===jn[2]&&(l[3]||("http:"===l[1]?80:443))==(jn[3]||("http:"===jn[1]?80:443)))),p.data&&p.processData&&"string"!=typeof p.data&&(p.data=st.param(p.data,p.traditional)),L(Wn,p,n,w),2===x)return w;c=p.global,c&&0===st.active++&&st.event.trigger("ajaxStart"),p.type=p.type.toUpperCase(),p.hasContent=!On.test(p.type),o=p.url,p.hasContent||(p.data&&(o=p.url+=(Hn.test(o)?"&":"?")+p.data,delete p.data),p.cache===!1&&(p.url=qn.test(o)?o.replace(qn,"$1_="+Ln++):o+(Hn.test(o)?"&":"?")+"_="+Ln++)),p.ifModified&&(st.lastModified[o]&&w.setRequestHeader("If-Modified-Since",st.lastModified[o]),st.etag[o]&&w.setRequestHeader("If-None-Match",st.etag[o])),(p.data&&p.hasContent&&p.contentType!==!1||n.contentType)&&w.setRequestHeader("Content-Type",p.contentType),w.setRequestHeader("Accept",p.dataTypes[0]&&p.accepts[p.dataTypes[0]]?p.accepts[p.dataTypes[0]]+("*"!==p.dataTypes[0]?", "+In+"; q=0.01":""):p.accepts["*"]);for(f in p.headers)w.setRequestHeader(f,p.headers[f]);if(p.beforeSend&&(p.beforeSend.call(d,w,p)===!1||2===x))return w.abort();T="abort";for(f in{success:1,error:1,complete:1})w[f](p[f]);if(i=L($n,p,n,w)){w.readyState=1,c&&h.trigger("ajaxSend",[w,p]),p.async&&p.timeout>0&&(u=setTimeout(function(){w.abort("timeout")},p.timeout));try{x=1,i.send(v,r)}catch(N){if(!(2>x))throw N;r(-1,N)}}else r(-1,"No Transport");return w},getScript:function(e,n){return st.get(e,t,n,"script")},getJSON:function(e,t,n){return st.get(e,t,n,"json")}}),st.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(e){return st.globalEval(e),e}}}),st.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),st.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=V.head||st("head")[0]||V.documentElement;return{send:function(t,i){n=V.createElement("script"),n.async=!0,e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,t){(t||!n.readyState||/loaded|complete/.test(n.readyState))&&(n.onload=n.onreadystatechange=null,n.parentNode&&n.parentNode.removeChild(n),n=null,t||i(200,"success"))},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(t,!0)}}}});var Xn=[],Un=/(=)\?(?=&|$)|\?\?/;st.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xn.pop()||st.expando+"_"+Ln++;return this[e]=!0,e}}),st.ajaxPrefilter("json jsonp",function(n,r,i){var o,a,s,u=n.jsonp!==!1&&(Un.test(n.url)?"url":"string"==typeof n.data&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Un.test(n.data)&&"data");return u||"jsonp"===n.dataTypes[0]?(o=n.jsonpCallback=st.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,u?n[u]=n[u].replace(Un,"$1"+o):n.jsonp!==!1&&(n.url+=(Hn.test(n.url)?"&":"?")+n.jsonp+"="+o),n.converters["script json"]=function(){return s||st.error(o+" was not called"),s[0]},n.dataTypes[0]="json",a=e[o],e[o]=function(){s=arguments},i.always(function(){e[o]=a,n[o]&&(n.jsonpCallback=r.jsonpCallback,Xn.push(o)),s&&st.isFunction(a)&&a(s[0]),s=a=t}),"script"):t});var Vn,Yn,Jn=0,Gn=e.ActiveXObject&&function(){var e;for(e in Vn)Vn[e](t,!0)};st.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&_()||F()}:_,Yn=st.ajaxSettings.xhr(),st.support.cors=!!Yn&&"withCredentials"in Yn,Yn=st.support.ajax=!!Yn,Yn&&st.ajaxTransport(function(n){if(!n.crossDomain||st.support.cors){var r;return{send:function(i,o){var a,s,u=n.xhr();if(n.username?u.open(n.type,n.url,n.async,n.username,n.password):u.open(n.type,n.url,n.async),n.xhrFields)for(s in n.xhrFields)u[s]=n.xhrFields[s];n.mimeType&&u.overrideMimeType&&u.overrideMimeType(n.mimeType),n.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");try{for(s in i)u.setRequestHeader(s,i[s])}catch(l){}u.send(n.hasContent&&n.data||null),r=function(e,i){var s,l,c,f,p;try{if(r&&(i||4===u.readyState))if(r=t,a&&(u.onreadystatechange=st.noop,Gn&&delete Vn[a]),i)4!==u.readyState&&u.abort();else{f={},s=u.status,p=u.responseXML,c=u.getAllResponseHeaders(),p&&p.documentElement&&(f.xml=p),"string"==typeof u.responseText&&(f.text=u.responseText);try{l=u.statusText}catch(d){l=""}s||!n.isLocal||n.crossDomain?1223===s&&(s=204):s=f.text?200:404}}catch(h){i||o(-1,h)}f&&o(s,l,f,c)},n.async?4===u.readyState?setTimeout(r):(a=++Jn,Gn&&(Vn||(Vn={},st(e).unload(Gn)),Vn[a]=r),u.onreadystatechange=r):r()},abort:function(){r&&r(t,!0)}}}});var Qn,Kn,Zn=/^(?:toggle|show|hide)$/,er=RegExp("^(?:([+-])=|)("+ut+")([a-z%]*)$","i"),tr=/queueHooks$/,nr=[W],rr={"*":[function(e,t){var n,r,i=this.createTween(e,t),o=er.exec(t),a=i.cur(),s=+a||0,u=1,l=20;if(o){if(n=+o[2],r=o[3]||(st.cssNumber[e]?"":"px"),"px"!==r&&s){s=st.css(i.elem,e,!0)||n||1;do u=u||".5",s/=u,st.style(i.elem,e,s+r);while(u!==(u=i.cur()/a)&&1!==u&&--l)}i.unit=r,i.start=s,i.end=o[1]?s+(o[1]+1)*n:n}return i}]};st.Animation=st.extend(P,{tweener:function(e,t){st.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");for(var n,r=0,i=e.length;i>r;r++)n=e[r],rr[n]=rr[n]||[],rr[n].unshift(t)},prefilter:function(e,t){t?nr.unshift(e):nr.push(e)}}),st.Tween=$,$.prototype={constructor:$,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||"swing",this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(st.cssNumber[n]?"":"px")},cur:function(){var e=$.propHooks[this.prop];return e&&e.get?e.get(this):$.propHooks._default.get(this)},run:function(e){var t,n=$.propHooks[this.prop];return this.pos=t=this.options.duration?st.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):$.propHooks._default.set(this),this}},$.prototype.init.prototype=$.prototype,$.propHooks={_default:{get:function(e){var t;return null==e.elem[e.prop]||e.elem.style&&null!=e.elem.style[e.prop]?(t=st.css(e.elem,e.prop,"auto"),t&&"auto"!==t?t:0):e.elem[e.prop]},set:function(e){st.fx.step[e.prop]?st.fx.step[e.prop](e):e.elem.style&&(null!=e.elem.style[st.cssProps[e.prop]]||st.cssHooks[e.prop])?st.style(e.elem,e.prop,e.now+e.unit):e.elem[e.prop]=e.now}}},$.propHooks.scrollTop=$.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},st.each(["toggle","show","hide"],function(e,t){var n=st.fn[t];st.fn[t]=function(e,r,i){return null==e||"boolean"==typeof e?n.apply(this,arguments):this.animate(I(t,!0),e,r,i)}}),st.fn.extend({fadeTo:function(e,t,n,r){return this.filter(w).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(e,t,n,r){var i=st.isEmptyObject(e),o=st.speed(t,n,r),a=function(){var t=P(this,st.extend({},e),o);a.finish=function(){t.stop(!0)},(i||st._data(this,"finish"))&&t.stop(!0)};return a.finish=a,i||o.queue===!1?this.each(a):this.queue(o.queue,a)},stop:function(e,n,r){var i=function(e){var t=e.stop;delete e.stop,t(r)};return"string"!=typeof e&&(r=n,n=e,e=t),n&&e!==!1&&this.queue(e||"fx",[]),this.each(function(){var t=!0,n=null!=e&&e+"queueHooks",o=st.timers,a=st._data(this);if(n)a[n]&&a[n].stop&&i(a[n]);else for(n in a)a[n]&&a[n].stop&&tr.test(n)&&i(a[n]);for(n=o.length;n--;)o[n].elem!==this||null!=e&&o[n].queue!==e||(o[n].anim.stop(r),t=!1,o.splice(n,1));(t||!r)&&st.dequeue(this,e)})},finish:function(e){return e!==!1&&(e=e||"fx"),this.each(function(){var t,n=st._data(this),r=n[e+"queue"],i=n[e+"queueHooks"],o=st.timers,a=r?r.length:0;for(n.finish=!0,st.queue(this,e,[]),i&&i.cur&&i.cur.finish&&i.cur.finish.call(this),t=o.length;t--;)o[t].elem===this&&o[t].queue===e&&(o[t].anim.stop(!0),o.splice(t,1));for(t=0;a>t;t++)r[t]&&r[t].finish&&r[t].finish.call(this);delete n.finish})}}),st.each({slideDown:I("show"),slideUp:I("hide"),slideToggle:I("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,t){st.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),st.speed=function(e,t,n){var r=e&&"object"==typeof e?st.extend({},e):{complete:n||!n&&t||st.isFunction(e)&&e,duration:e,easing:n&&t||t&&!st.isFunction(t)&&t};return r.duration=st.fx.off?0:"number"==typeof r.duration?r.duration:r.duration in st.fx.speeds?st.fx.speeds[r.duration]:st.fx.speeds._default,(null==r.queue||r.queue===!0)&&(r.queue="fx"),r.old=r.complete,r.complete=function(){st.isFunction(r.old)&&r.old.call(this),r.queue&&st.dequeue(this,r.queue)},r},st.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2}},st.timers=[],st.fx=$.prototype.init,st.fx.tick=function(){var e,n=st.timers,r=0;for(Qn=st.now();n.length>r;r++)e=n[r],e()||n[r]!==e||n.splice(r--,1);n.length||st.fx.stop(),Qn=t},st.fx.timer=function(e){e()&&st.timers.push(e)&&st.fx.start()},st.fx.interval=13,st.fx.start=function(){Kn||(Kn=setInterval(st.fx.tick,st.fx.interval))},st.fx.stop=function(){clearInterval(Kn),Kn=null},st.fx.speeds={slow:600,fast:200,_default:400},st.fx.step={},st.expr&&st.expr.filters&&(st.expr.filters.animated=function(e){return st.grep(st.timers,function(t){return e===t.elem}).length}),st.fn.offset=function(e){if(arguments.length)return e===t?this:this.each(function(t){st.offset.setOffset(this,e,t)});var n,r,i={top:0,left:0},o=this[0],a=o&&o.ownerDocument;if(a)return n=a.documentElement,st.contains(n,o)?(o.getBoundingClientRect!==t&&(i=o.getBoundingClientRect()),r=z(a),{top:i.top+(r.pageYOffset||n.scrollTop)-(n.clientTop||0),left:i.left+(r.pageXOffset||n.scrollLeft)-(n.clientLeft||0)}):i},st.offset={setOffset:function(e,t,n){var r=st.css(e,"position");"static"===r&&(e.style.position="relative");var i,o,a=st(e),s=a.offset(),u=st.css(e,"top"),l=st.css(e,"left"),c=("absolute"===r||"fixed"===r)&&st.inArray("auto",[u,l])>-1,f={},p={};c?(p=a.position(),i=p.top,o=p.left):(i=parseFloat(u)||0,o=parseFloat(l)||0),st.isFunction(t)&&(t=t.call(e,n,s)),null!=t.top&&(f.top=t.top-s.top+i),null!=t.left&&(f.left=t.left-s.left+o),"using"in t?t.using.call(e,f):a.css(f)}},st.fn.extend({position:function(){if(this[0]){var e,t,n={top:0,left:0},r=this[0];return"fixed"===st.css(r,"position")?t=r.getBoundingClientRect():(e=this.offsetParent(),t=this.offset(),st.nodeName(e[0],"html")||(n=e.offset()),n.top+=st.css(e[0],"borderTopWidth",!0),n.left+=st.css(e[0],"borderLeftWidth",!0)),{top:t.top-n.top-st.css(r,"marginTop",!0),left:t.left-n.left-st.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){for(var e=this.offsetParent||V.documentElement;e&&!st.nodeName(e,"html")&&"static"===st.css(e,"position");)e=e.offsetParent;return e||V.documentElement})}}),st.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);st.fn[e]=function(i){return st.access(this,function(e,i,o){var a=z(e);return o===t?a?n in a?a[n]:a.document.documentElement[i]:e[i]:(a?a.scrollTo(r?st(a).scrollLeft():o,r?o:st(a).scrollTop()):e[i]=o,t)},e,i,arguments.length,null)}}),st.each({Height:"height",Width:"width"},function(e,n){st.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){st.fn[i]=function(i,o){var a=arguments.length&&(r||"boolean"!=typeof i),s=r||(i===!0||o===!0?"margin":"border");return st.access(this,function(n,r,i){var o;return st.isWindow(n)?n.document.documentElement["client"+e]:9===n.nodeType?(o=n.documentElement,Math.max(n.body["scroll"+e],o["scroll"+e],n.body["offset"+e],o["offset"+e],o["client"+e])):i===t?st.css(n,r,s):st.style(n,r,i,s)},n,a?i:t,a,null)}})}),e.jQuery=e.$=st,"function"==typeof define&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return st})})(window);
    +//@ sourceMappingURL=jquery.min.map
    \ No newline at end of file
    diff --git a/lib/jquery-1.9.0.js b/lib/jquery-1.9.0.js
    new file mode 100644
    index 000000000..67e31603d
    --- /dev/null
    +++ b/lib/jquery-1.9.0.js
    @@ -0,0 +1,9555 @@
    +/*!
    + * jQuery JavaScript Library v1.9.0
    + * http://jquery.com/
    + *
    + * Includes Sizzle.js
    + * http://sizzlejs.com/
    + *
    + * Copyright 2005, 2012 jQuery Foundation, Inc. and other contributors
    + * Released under the MIT license
    + * http://jquery.org/license
    + *
    + * Date: 2013-1-14
    + */
    +(function( window, undefined ) {
    +"use strict";
    +var
    +	// A central reference to the root jQuery(document)
    +	rootjQuery,
    +
    +	// The deferred used on DOM ready
    +	readyList,
    +
    +	// Use the correct document accordingly with window argument (sandbox)
    +	document = window.document,
    +	location = window.location,
    +
    +	// Map over jQuery in case of overwrite
    +	_jQuery = window.jQuery,
    +
    +	// Map over the $ in case of overwrite
    +	_$ = window.$,
    +
    +	// [[Class]] -> type pairs
    +	class2type = {},
    +
    +	// List of deleted data cache ids, so we can reuse them
    +	core_deletedIds = [],
    +
    +	core_version = "1.9.0",
    +
    +	// Save a reference to some core methods
    +	core_concat = core_deletedIds.concat,
    +	core_push = core_deletedIds.push,
    +	core_slice = core_deletedIds.slice,
    +	core_indexOf = core_deletedIds.indexOf,
    +	core_toString = class2type.toString,
    +	core_hasOwn = class2type.hasOwnProperty,
    +	core_trim = core_version.trim,
    +
    +	// Define a local copy of jQuery
    +	jQuery = function( selector, context ) {
    +		// The jQuery object is actually just the init constructor 'enhanced'
    +		return new jQuery.fn.init( selector, context, rootjQuery );
    +	},
    +
    +	// Used for matching numbers
    +	core_pnum = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,
    +
    +	// Used for splitting on whitespace
    +	core_rnotwhite = /\S+/g,
    +
    +	// Make sure we trim BOM and NBSP (here's looking at you, Safari 5.0 and IE)
    +	rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,
    +
    +	// A simple way to check for HTML strings
    +	// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
    +	// Strict HTML recognition (#11290: must start with <)
    +	rquickExpr = /^(?:(<[\w\W]+>)[^>]*|#([\w-]*))$/,
    +
    +	// Match a standalone tag
    +	rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,
    +
    +	// JSON RegExp
    +	rvalidchars = /^[\],:{}\s]*$/,
    +	rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
    +	rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,
    +	rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,
    +
    +	// Matches dashed string for camelizing
    +	rmsPrefix = /^-ms-/,
    +	rdashAlpha = /-([\da-z])/gi,
    +
    +	// Used by jQuery.camelCase as callback to replace()
    +	fcamelCase = function( all, letter ) {
    +		return letter.toUpperCase();
    +	},
    +
    +	// The ready event handler and self cleanup method
    +	DOMContentLoaded = function() {
    +		if ( document.addEventListener ) {
    +			document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
    +			jQuery.ready();
    +		} else if ( document.readyState === "complete" ) {
    +			// we're here because readyState === "complete" in oldIE
    +			// which is good enough for us to call the dom ready!
    +			document.detachEvent( "onreadystatechange", DOMContentLoaded );
    +			jQuery.ready();
    +		}
    +	};
    +
    +jQuery.fn = jQuery.prototype = {
    +	// The current version of jQuery being used
    +	jquery: core_version,
    +
    +	constructor: jQuery,
    +	init: function( selector, context, rootjQuery ) {
    +		var match, elem;
    +
    +		// HANDLE: $(""), $(null), $(undefined), $(false)
    +		if ( !selector ) {
    +			return this;
    +		}
    +
    +		// Handle HTML strings
    +		if ( typeof selector === "string" ) {
    +			if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
    +				// Assume that strings that start and end with <> are HTML and skip the regex check
    +				match = [ null, selector, null ];
    +
    +			} else {
    +				match = rquickExpr.exec( selector );
    +			}
    +
    +			// Match html or make sure no context is specified for #id
    +			if ( match && (match[1] || !context) ) {
    +
    +				// HANDLE: $(html) -> $(array)
    +				if ( match[1] ) {
    +					context = context instanceof jQuery ? context[0] : context;
    +
    +					// scripts is true for back-compat
    +					jQuery.merge( this, jQuery.parseHTML(
    +						match[1],
    +						context && context.nodeType ? context.ownerDocument || context : document,
    +						true
    +					) );
    +
    +					// HANDLE: $(html, props)
    +					if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
    +						for ( match in context ) {
    +							// Properties of context are called as methods if possible
    +							if ( jQuery.isFunction( this[ match ] ) ) {
    +								this[ match ]( context[ match ] );
    +
    +							// ...and otherwise set as attributes
    +							} else {
    +								this.attr( match, context[ match ] );
    +							}
    +						}
    +					}
    +
    +					return this;
    +
    +				// HANDLE: $(#id)
    +				} else {
    +					elem = document.getElementById( match[2] );
    +
    +					// Check parentNode to catch when Blackberry 4.6 returns
    +					// nodes that are no longer in the document #6963
    +					if ( elem && elem.parentNode ) {
    +						// Handle the case where IE and Opera return items
    +						// by name instead of ID
    +						if ( elem.id !== match[2] ) {
    +							return rootjQuery.find( selector );
    +						}
    +
    +						// Otherwise, we inject the element directly into the jQuery object
    +						this.length = 1;
    +						this[0] = elem;
    +					}
    +
    +					this.context = document;
    +					this.selector = selector;
    +					return this;
    +				}
    +
    +			// HANDLE: $(expr, $(...))
    +			} else if ( !context || context.jquery ) {
    +				return ( context || rootjQuery ).find( selector );
    +
    +			// HANDLE: $(expr, context)
    +			// (which is just equivalent to: $(context).find(expr)
    +			} else {
    +				return this.constructor( context ).find( selector );
    +			}
    +
    +		// HANDLE: $(DOMElement)
    +		} else if ( selector.nodeType ) {
    +			this.context = this[0] = selector;
    +			this.length = 1;
    +			return this;
    +
    +		// HANDLE: $(function)
    +		// Shortcut for document ready
    +		} else if ( jQuery.isFunction( selector ) ) {
    +			return rootjQuery.ready( selector );
    +		}
    +
    +		if ( selector.selector !== undefined ) {
    +			this.selector = selector.selector;
    +			this.context = selector.context;
    +		}
    +
    +		return jQuery.makeArray( selector, this );
    +	},
    +
    +	// Start with an empty selector
    +	selector: "",
    +
    +	// The default length of a jQuery object is 0
    +	length: 0,
    +
    +	// The number of elements contained in the matched element set
    +	size: function() {
    +		return this.length;
    +	},
    +
    +	toArray: function() {
    +		return core_slice.call( this );
    +	},
    +
    +	// Get the Nth element in the matched element set OR
    +	// Get the whole matched element set as a clean array
    +	get: function( num ) {
    +		return num == null ?
    +
    +			// Return a 'clean' array
    +			this.toArray() :
    +
    +			// Return just the object
    +			( num < 0 ? this[ this.length + num ] : this[ num ] );
    +	},
    +
    +	// Take an array of elements and push it onto the stack
    +	// (returning the new matched element set)
    +	pushStack: function( elems ) {
    +
    +		// Build a new jQuery matched element set
    +		var ret = jQuery.merge( this.constructor(), elems );
    +
    +		// Add the old object onto the stack (as a reference)
    +		ret.prevObject = this;
    +		ret.context = this.context;
    +
    +		// Return the newly-formed element set
    +		return ret;
    +	},
    +
    +	// Execute a callback for every element in the matched set.
    +	// (You can seed the arguments with an array of args, but this is
    +	// only used internally.)
    +	each: function( callback, args ) {
    +		return jQuery.each( this, callback, args );
    +	},
    +
    +	ready: function( fn ) {
    +		// Add the callback
    +		jQuery.ready.promise().done( fn );
    +
    +		return this;
    +	},
    +
    +	slice: function() {
    +		return this.pushStack( core_slice.apply( this, arguments ) );
    +	},
    +
    +	first: function() {
    +		return this.eq( 0 );
    +	},
    +
    +	last: function() {
    +		return this.eq( -1 );
    +	},
    +
    +	eq: function( i ) {
    +		var len = this.length,
    +			j = +i + ( i < 0 ? len : 0 );
    +		return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );
    +	},
    +
    +	map: function( callback ) {
    +		return this.pushStack( jQuery.map(this, function( elem, i ) {
    +			return callback.call( elem, i, elem );
    +		}));
    +	},
    +
    +	end: function() {
    +		return this.prevObject || this.constructor(null);
    +	},
    +
    +	// For internal use only.
    +	// Behaves like an Array's method, not like a jQuery method.
    +	push: core_push,
    +	sort: [].sort,
    +	splice: [].splice
    +};
    +
    +// Give the init function the jQuery prototype for later instantiation
    +jQuery.fn.init.prototype = jQuery.fn;
    +
    +jQuery.extend = jQuery.fn.extend = function() {
    +	var options, name, src, copy, copyIsArray, clone,
    +		target = arguments[0] || {},
    +		i = 1,
    +		length = arguments.length,
    +		deep = false;
    +
    +	// Handle a deep copy situation
    +	if ( typeof target === "boolean" ) {
    +		deep = target;
    +		target = arguments[1] || {};
    +		// skip the boolean and the target
    +		i = 2;
    +	}
    +
    +	// Handle case when target is a string or something (possible in deep copy)
    +	if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
    +		target = {};
    +	}
    +
    +	// extend jQuery itself if only one argument is passed
    +	if ( length === i ) {
    +		target = this;
    +		--i;
    +	}
    +
    +	for ( ; i < length; i++ ) {
    +		// Only deal with non-null/undefined values
    +		if ( (options = arguments[ i ]) != null ) {
    +			// Extend the base object
    +			for ( name in options ) {
    +				src = target[ name ];
    +				copy = options[ name ];
    +
    +				// Prevent never-ending loop
    +				if ( target === copy ) {
    +					continue;
    +				}
    +
    +				// Recurse if we're merging plain objects or arrays
    +				if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
    +					if ( copyIsArray ) {
    +						copyIsArray = false;
    +						clone = src && jQuery.isArray(src) ? src : [];
    +
    +					} else {
    +						clone = src && jQuery.isPlainObject(src) ? src : {};
    +					}
    +
    +					// Never move original objects, clone them
    +					target[ name ] = jQuery.extend( deep, clone, copy );
    +
    +				// Don't bring in undefined values
    +				} else if ( copy !== undefined ) {
    +					target[ name ] = copy;
    +				}
    +			}
    +		}
    +	}
    +
    +	// Return the modified object
    +	return target;
    +};
    +
    +jQuery.extend({
    +	noConflict: function( deep ) {
    +		if ( window.$ === jQuery ) {
    +			window.$ = _$;
    +		}
    +
    +		if ( deep && window.jQuery === jQuery ) {
    +			window.jQuery = _jQuery;
    +		}
    +
    +		return jQuery;
    +	},
    +
    +	// Is the DOM ready to be used? Set to true once it occurs.
    +	isReady: false,
    +
    +	// A counter to track how many items to wait for before
    +	// the ready event fires. See #6781
    +	readyWait: 1,
    +
    +	// Hold (or release) the ready event
    +	holdReady: function( hold ) {
    +		if ( hold ) {
    +			jQuery.readyWait++;
    +		} else {
    +			jQuery.ready( true );
    +		}
    +	},
    +
    +	// Handle when the DOM is ready
    +	ready: function( wait ) {
    +
    +		// Abort if there are pending holds or we're already ready
    +		if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
    +			return;
    +		}
    +
    +		// Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
    +		if ( !document.body ) {
    +			return setTimeout( jQuery.ready );
    +		}
    +
    +		// Remember that the DOM is ready
    +		jQuery.isReady = true;
    +
    +		// If a normal DOM Ready event fired, decrement, and wait if need be
    +		if ( wait !== true && --jQuery.readyWait > 0 ) {
    +			return;
    +		}
    +
    +		// If there are functions bound, to execute
    +		readyList.resolveWith( document, [ jQuery ] );
    +
    +		// Trigger any bound ready events
    +		if ( jQuery.fn.trigger ) {
    +			jQuery( document ).trigger("ready").off("ready");
    +		}
    +	},
    +
    +	// See test/unit/core.js for details concerning isFunction.
    +	// Since version 1.3, DOM methods and functions like alert
    +	// aren't supported. They return false on IE (#2968).
    +	isFunction: function( obj ) {
    +		return jQuery.type(obj) === "function";
    +	},
    +
    +	isArray: Array.isArray || function( obj ) {
    +		return jQuery.type(obj) === "array";
    +	},
    +
    +	isWindow: function( obj ) {
    +		return obj != null && obj == obj.window;
    +	},
    +
    +	isNumeric: function( obj ) {
    +		return !isNaN( parseFloat(obj) ) && isFinite( obj );
    +	},
    +
    +	type: function( obj ) {
    +		if ( obj == null ) {
    +			return String( obj );
    +		}
    +		return typeof obj === "object" || typeof obj === "function" ?
    +			class2type[ core_toString.call(obj) ] || "object" :
    +			typeof obj;
    +	},
    +
    +	isPlainObject: function( obj ) {
    +		// Must be an Object.
    +		// Because of IE, we also have to check the presence of the constructor property.
    +		// Make sure that DOM nodes and window objects don't pass through, as well
    +		if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
    +			return false;
    +		}
    +
    +		try {
    +			// Not own constructor property must be Object
    +			if ( obj.constructor &&
    +				!core_hasOwn.call(obj, "constructor") &&
    +				!core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
    +				return false;
    +			}
    +		} catch ( e ) {
    +			// IE8,9 Will throw exceptions on certain host objects #9897
    +			return false;
    +		}
    +
    +		// Own properties are enumerated firstly, so to speed up,
    +		// if last one is own, then all properties are own.
    +
    +		var key;
    +		for ( key in obj ) {}
    +
    +		return key === undefined || core_hasOwn.call( obj, key );
    +	},
    +
    +	isEmptyObject: function( obj ) {
    +		var name;
    +		for ( name in obj ) {
    +			return false;
    +		}
    +		return true;
    +	},
    +
    +	error: function( msg ) {
    +		throw new Error( msg );
    +	},
    +
    +	// data: string of html
    +	// context (optional): If specified, the fragment will be created in this context, defaults to document
    +	// keepScripts (optional): If true, will include scripts passed in the html string
    +	parseHTML: function( data, context, keepScripts ) {
    +		if ( !data || typeof data !== "string" ) {
    +			return null;
    +		}
    +		if ( typeof context === "boolean" ) {
    +			keepScripts = context;
    +			context = false;
    +		}
    +		context = context || document;
    +
    +		var parsed = rsingleTag.exec( data ),
    +			scripts = !keepScripts && [];
    +
    +		// Single tag
    +		if ( parsed ) {
    +			return [ context.createElement( parsed[1] ) ];
    +		}
    +
    +		parsed = jQuery.buildFragment( [ data ], context, scripts );
    +		if ( scripts ) {
    +			jQuery( scripts ).remove();
    +		}
    +		return jQuery.merge( [], parsed.childNodes );
    +	},
    +
    +	parseJSON: function( data ) {
    +		// Attempt to parse using the native JSON parser first
    +		if ( window.JSON && window.JSON.parse ) {
    +			return window.JSON.parse( data );
    +		}
    +
    +		if ( data === null ) {
    +			return data;
    +		}
    +
    +		if ( typeof data === "string" ) {
    +
    +			// Make sure leading/trailing whitespace is removed (IE can't handle it)
    +			data = jQuery.trim( data );
    +
    +			if ( data ) {
    +				// Make sure the incoming data is actual JSON
    +				// Logic borrowed from http://json.org/json2.js
    +				if ( rvalidchars.test( data.replace( rvalidescape, "@" )
    +					.replace( rvalidtokens, "]" )
    +					.replace( rvalidbraces, "")) ) {
    +
    +					return ( new Function( "return " + data ) )();
    +				}
    +			}
    +		}
    +
    +		jQuery.error( "Invalid JSON: " + data );
    +	},
    +
    +	// Cross-browser xml parsing
    +	parseXML: function( data ) {
    +		var xml, tmp;
    +		if ( !data || typeof data !== "string" ) {
    +			return null;
    +		}
    +		try {
    +			if ( window.DOMParser ) { // Standard
    +				tmp = new DOMParser();
    +				xml = tmp.parseFromString( data , "text/xml" );
    +			} else { // IE
    +				xml = new ActiveXObject( "Microsoft.XMLDOM" );
    +				xml.async = "false";
    +				xml.loadXML( data );
    +			}
    +		} catch( e ) {
    +			xml = undefined;
    +		}
    +		if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {
    +			jQuery.error( "Invalid XML: " + data );
    +		}
    +		return xml;
    +	},
    +
    +	noop: function() {},
    +
    +	// Evaluates a script in a global context
    +	// Workarounds based on findings by Jim Driscoll
    +	// http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
    +	globalEval: function( data ) {
    +		if ( data && jQuery.trim( data ) ) {
    +			// We use execScript on Internet Explorer
    +			// We use an anonymous function so that context is window
    +			// rather than jQuery in Firefox
    +			( window.execScript || function( data ) {
    +				window[ "eval" ].call( window, data );
    +			} )( data );
    +		}
    +	},
    +
    +	// Convert dashed to camelCase; used by the css and data modules
    +	// Microsoft forgot to hump their vendor prefix (#9572)
    +	camelCase: function( string ) {
    +		return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
    +	},
    +
    +	nodeName: function( elem, name ) {
    +		return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
    +	},
    +
    +	// args is for internal usage only
    +	each: function( obj, callback, args ) {
    +		var value,
    +			i = 0,
    +			length = obj.length,
    +			isArray = isArraylike( obj );
    +
    +		if ( args ) {
    +			if ( isArray ) {
    +				for ( ; i < length; i++ ) {
    +					value = callback.apply( obj[ i ], args );
    +
    +					if ( value === false ) {
    +						break;
    +					}
    +				}
    +			} else {
    +				for ( i in obj ) {
    +					value = callback.apply( obj[ i ], args );
    +
    +					if ( value === false ) {
    +						break;
    +					}
    +				}
    +			}
    +
    +		// A special, fast, case for the most common use of each
    +		} else {
    +			if ( isArray ) {
    +				for ( ; i < length; i++ ) {
    +					value = callback.call( obj[ i ], i, obj[ i ] );
    +
    +					if ( value === false ) {
    +						break;
    +					}
    +				}
    +			} else {
    +				for ( i in obj ) {
    +					value = callback.call( obj[ i ], i, obj[ i ] );
    +
    +					if ( value === false ) {
    +						break;
    +					}
    +				}
    +			}
    +		}
    +
    +		return obj;
    +	},
    +
    +	// Use native String.trim function wherever possible
    +	trim: core_trim && !core_trim.call("\uFEFF\xA0") ?
    +		function( text ) {
    +			return text == null ?
    +				"" :
    +				core_trim.call( text );
    +		} :
    +
    +		// Otherwise use our own trimming functionality
    +		function( text ) {
    +			return text == null ?
    +				"" :
    +				( text + "" ).replace( rtrim, "" );
    +		},
    +
    +	// results is for internal usage only
    +	makeArray: function( arr, results ) {
    +		var ret = results || [];
    +
    +		if ( arr != null ) {
    +			if ( isArraylike( Object(arr) ) ) {
    +				jQuery.merge( ret,
    +					typeof arr === "string" ?
    +					[ arr ] : arr
    +				);
    +			} else {
    +				core_push.call( ret, arr );
    +			}
    +		}
    +
    +		return ret;
    +	},
    +
    +	inArray: function( elem, arr, i ) {
    +		var len;
    +
    +		if ( arr ) {
    +			if ( core_indexOf ) {
    +				return core_indexOf.call( arr, elem, i );
    +			}
    +
    +			len = arr.length;
    +			i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
    +
    +			for ( ; i < len; i++ ) {
    +				// Skip accessing in sparse arrays
    +				if ( i in arr && arr[ i ] === elem ) {
    +					return i;
    +				}
    +			}
    +		}
    +
    +		return -1;
    +	},
    +
    +	merge: function( first, second ) {
    +		var l = second.length,
    +			i = first.length,
    +			j = 0;
    +
    +		if ( typeof l === "number" ) {
    +			for ( ; j < l; j++ ) {
    +				first[ i++ ] = second[ j ];
    +			}
    +		} else {
    +			while ( second[j] !== undefined ) {
    +				first[ i++ ] = second[ j++ ];
    +			}
    +		}
    +
    +		first.length = i;
    +
    +		return first;
    +	},
    +
    +	grep: function( elems, callback, inv ) {
    +		var retVal,
    +			ret = [],
    +			i = 0,
    +			length = elems.length;
    +		inv = !!inv;
    +
    +		// Go through the array, only saving the items
    +		// that pass the validator function
    +		for ( ; i < length; i++ ) {
    +			retVal = !!callback( elems[ i ], i );
    +			if ( inv !== retVal ) {
    +				ret.push( elems[ i ] );
    +			}
    +		}
    +
    +		return ret;
    +	},
    +
    +	// arg is for internal usage only
    +	map: function( elems, callback, arg ) {
    +		var value,
    +			i = 0,
    +			length = elems.length,
    +			isArray = isArraylike( elems ),
    +			ret = [];
    +
    +		// Go through the array, translating each of the items to their
    +		if ( isArray ) {
    +			for ( ; i < length; i++ ) {
    +				value = callback( elems[ i ], i, arg );
    +
    +				if ( value != null ) {
    +					ret[ ret.length ] = value;
    +				}
    +			}
    +
    +		// Go through every key on the object,
    +		} else {
    +			for ( i in elems ) {
    +				value = callback( elems[ i ], i, arg );
    +
    +				if ( value != null ) {
    +					ret[ ret.length ] = value;
    +				}
    +			}
    +		}
    +
    +		// Flatten any nested arrays
    +		return core_concat.apply( [], ret );
    +	},
    +
    +	// A global GUID counter for objects
    +	guid: 1,
    +
    +	// Bind a function to a context, optionally partially applying any
    +	// arguments.
    +	proxy: function( fn, context ) {
    +		var tmp, args, proxy;
    +
    +		if ( typeof context === "string" ) {
    +			tmp = fn[ context ];
    +			context = fn;
    +			fn = tmp;
    +		}
    +
    +		// Quick check to determine if target is callable, in the spec
    +		// this throws a TypeError, but we will just return undefined.
    +		if ( !jQuery.isFunction( fn ) ) {
    +			return undefined;
    +		}
    +
    +		// Simulated bind
    +		args = core_slice.call( arguments, 2 );
    +		proxy = function() {
    +			return fn.apply( context || this, args.concat( core_slice.call( arguments ) ) );
    +		};
    +
    +		// Set the guid of unique handler to the same of original handler, so it can be removed
    +		proxy.guid = fn.guid = fn.guid || jQuery.guid++;
    +
    +		return proxy;
    +	},
    +
    +	// Multifunctional method to get and set values of a collection
    +	// The value/s can optionally be executed if it's a function
    +	access: function( elems, fn, key, value, chainable, emptyGet, raw ) {
    +		var i = 0,
    +			length = elems.length,
    +			bulk = key == null;
    +
    +		// Sets many values
    +		if ( jQuery.type( key ) === "object" ) {
    +			chainable = true;
    +			for ( i in key ) {
    +				jQuery.access( elems, fn, i, key[i], true, emptyGet, raw );
    +			}
    +
    +		// Sets one value
    +		} else if ( value !== undefined ) {
    +			chainable = true;
    +
    +			if ( !jQuery.isFunction( value ) ) {
    +				raw = true;
    +			}
    +
    +			if ( bulk ) {
    +				// Bulk operations run against the entire set
    +				if ( raw ) {
    +					fn.call( elems, value );
    +					fn = null;
    +
    +				// ...except when executing function values
    +				} else {
    +					bulk = fn;
    +					fn = function( elem, key, value ) {
    +						return bulk.call( jQuery( elem ), value );
    +					};
    +				}
    +			}
    +
    +			if ( fn ) {
    +				for ( ; i < length; i++ ) {
    +					fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );
    +				}
    +			}
    +		}
    +
    +		return chainable ?
    +			elems :
    +
    +			// Gets
    +			bulk ?
    +				fn.call( elems ) :
    +				length ? fn( elems[0], key ) : emptyGet;
    +	},
    +
    +	now: function() {
    +		return ( new Date() ).getTime();
    +	}
    +});
    +
    +jQuery.ready.promise = function( obj ) {
    +	if ( !readyList ) {
    +
    +		readyList = jQuery.Deferred();
    +
    +		// Catch cases where $(document).ready() is called after the browser event has already occurred.
    +		// we once tried to use readyState "interactive" here, but it caused issues like the one
    +		// discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
    +		if ( document.readyState === "complete" ) {
    +			// Handle it asynchronously to allow scripts the opportunity to delay ready
    +			setTimeout( jQuery.ready );
    +
    +		// Standards-based browsers support DOMContentLoaded
    +		} else if ( document.addEventListener ) {
    +			// Use the handy event callback
    +			document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
    +
    +			// A fallback to window.onload, that will always work
    +			window.addEventListener( "load", jQuery.ready, false );
    +
    +		// If IE event model is used
    +		} else {
    +			// Ensure firing before onload, maybe late but safe also for iframes
    +			document.attachEvent( "onreadystatechange", DOMContentLoaded );
    +
    +			// A fallback to window.onload, that will always work
    +			window.attachEvent( "onload", jQuery.ready );
    +
    +			// If IE and not a frame
    +			// continually check to see if the document is ready
    +			var top = false;
    +
    +			try {
    +				top = window.frameElement == null && document.documentElement;
    +			} catch(e) {}
    +
    +			if ( top && top.doScroll ) {
    +				(function doScrollCheck() {
    +					if ( !jQuery.isReady ) {
    +
    +						try {
    +							// Use the trick by Diego Perini
    +							// http://javascript.nwbox.com/IEContentLoaded/
    +							top.doScroll("left");
    +						} catch(e) {
    +							return setTimeout( doScrollCheck, 50 );
    +						}
    +
    +						// and execute any waiting functions
    +						jQuery.ready();
    +					}
    +				})();
    +			}
    +		}
    +	}
    +	return readyList.promise( obj );
    +};
    +
    +// Populate the class2type map
    +jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
    +	class2type[ "[object " + name + "]" ] = name.toLowerCase();
    +});
    +
    +function isArraylike( obj ) {
    +	var length = obj.length,
    +		type = jQuery.type( obj );
    +
    +	if ( jQuery.isWindow( obj ) ) {
    +		return false;
    +	}
    +
    +	if ( obj.nodeType === 1 && length ) {
    +		return true;
    +	}
    +
    +	return type === "array" || type !== "function" &&
    +		( length === 0 ||
    +		typeof length === "number" && length > 0 && ( length - 1 ) in obj );
    +}
    +
    +// All jQuery objects should point back to these
    +rootjQuery = jQuery(document);
    +// String to Object options format cache
    +var optionsCache = {};
    +
    +// Convert String-formatted options into Object-formatted ones and store in cache
    +function createOptions( options ) {
    +	var object = optionsCache[ options ] = {};
    +	jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {
    +		object[ flag ] = true;
    +	});
    +	return object;
    +}
    +
    +/*
    + * Create a callback list using the following parameters:
    + *
    + *	options: an optional list of space-separated options that will change how
    + *			the callback list behaves or a more traditional option object
    + *
    + * By default a callback list will act like an event callback list and can be
    + * "fired" multiple times.
    + *
    + * Possible options:
    + *
    + *	once:			will ensure the callback list can only be fired once (like a Deferred)
    + *
    + *	memory:			will keep track of previous values and will call any callback added
    + *					after the list has been fired right away with the latest "memorized"
    + *					values (like a Deferred)
    + *
    + *	unique:			will ensure a callback can only be added once (no duplicate in the list)
    + *
    + *	stopOnFalse:	interrupt callings when a callback returns false
    + *
    + */
    +jQuery.Callbacks = function( options ) {
    +
    +	// Convert options from String-formatted to Object-formatted if needed
    +	// (we check in cache first)
    +	options = typeof options === "string" ?
    +		( optionsCache[ options ] || createOptions( options ) ) :
    +		jQuery.extend( {}, options );
    +
    +	var // Last fire value (for non-forgettable lists)
    +		memory,
    +		// Flag to know if list was already fired
    +		fired,
    +		// Flag to know if list is currently firing
    +		firing,
    +		// First callback to fire (used internally by add and fireWith)
    +		firingStart,
    +		// End of the loop when firing
    +		firingLength,
    +		// Index of currently firing callback (modified by remove if needed)
    +		firingIndex,
    +		// Actual callback list
    +		list = [],
    +		// Stack of fire calls for repeatable lists
    +		stack = !options.once && [],
    +		// Fire callbacks
    +		fire = function( data ) {
    +			memory = options.memory && data;
    +			fired = true;
    +			firingIndex = firingStart || 0;
    +			firingStart = 0;
    +			firingLength = list.length;
    +			firing = true;
    +			for ( ; list && firingIndex < firingLength; firingIndex++ ) {
    +				if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
    +					memory = false; // To prevent further calls using add
    +					break;
    +				}
    +			}
    +			firing = false;
    +			if ( list ) {
    +				if ( stack ) {
    +					if ( stack.length ) {
    +						fire( stack.shift() );
    +					}
    +				} else if ( memory ) {
    +					list = [];
    +				} else {
    +					self.disable();
    +				}
    +			}
    +		},
    +		// Actual Callbacks object
    +		self = {
    +			// Add a callback or a collection of callbacks to the list
    +			add: function() {
    +				if ( list ) {
    +					// First, we save the current length
    +					var start = list.length;
    +					(function add( args ) {
    +						jQuery.each( args, function( _, arg ) {
    +							var type = jQuery.type( arg );
    +							if ( type === "function" ) {
    +								if ( !options.unique || !self.has( arg ) ) {
    +									list.push( arg );
    +								}
    +							} else if ( arg && arg.length && type !== "string" ) {
    +								// Inspect recursively
    +								add( arg );
    +							}
    +						});
    +					})( arguments );
    +					// Do we need to add the callbacks to the
    +					// current firing batch?
    +					if ( firing ) {
    +						firingLength = list.length;
    +					// With memory, if we're not firing then
    +					// we should call right away
    +					} else if ( memory ) {
    +						firingStart = start;
    +						fire( memory );
    +					}
    +				}
    +				return this;
    +			},
    +			// Remove a callback from the list
    +			remove: function() {
    +				if ( list ) {
    +					jQuery.each( arguments, function( _, arg ) {
    +						var index;
    +						while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
    +							list.splice( index, 1 );
    +							// Handle firing indexes
    +							if ( firing ) {
    +								if ( index <= firingLength ) {
    +									firingLength--;
    +								}
    +								if ( index <= firingIndex ) {
    +									firingIndex--;
    +								}
    +							}
    +						}
    +					});
    +				}
    +				return this;
    +			},
    +			// Control if a given callback is in the list
    +			has: function( fn ) {
    +				return jQuery.inArray( fn, list ) > -1;
    +			},
    +			// Remove all callbacks from the list
    +			empty: function() {
    +				list = [];
    +				return this;
    +			},
    +			// Have the list do nothing anymore
    +			disable: function() {
    +				list = stack = memory = undefined;
    +				return this;
    +			},
    +			// Is it disabled?
    +			disabled: function() {
    +				return !list;
    +			},
    +			// Lock the list in its current state
    +			lock: function() {
    +				stack = undefined;
    +				if ( !memory ) {
    +					self.disable();
    +				}
    +				return this;
    +			},
    +			// Is it locked?
    +			locked: function() {
    +				return !stack;
    +			},
    +			// Call all callbacks with the given context and arguments
    +			fireWith: function( context, args ) {
    +				args = args || [];
    +				args = [ context, args.slice ? args.slice() : args ];
    +				if ( list && ( !fired || stack ) ) {
    +					if ( firing ) {
    +						stack.push( args );
    +					} else {
    +						fire( args );
    +					}
    +				}
    +				return this;
    +			},
    +			// Call all the callbacks with the given arguments
    +			fire: function() {
    +				self.fireWith( this, arguments );
    +				return this;
    +			},
    +			// To know if the callbacks have already been called at least once
    +			fired: function() {
    +				return !!fired;
    +			}
    +		};
    +
    +	return self;
    +};
    +jQuery.extend({
    +
    +	Deferred: function( func ) {
    +		var tuples = [
    +				// action, add listener, listener list, final state
    +				[ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
    +				[ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
    +				[ "notify", "progress", jQuery.Callbacks("memory") ]
    +			],
    +			state = "pending",
    +			promise = {
    +				state: function() {
    +					return state;
    +				},
    +				always: function() {
    +					deferred.done( arguments ).fail( arguments );
    +					return this;
    +				},
    +				then: function( /* fnDone, fnFail, fnProgress */ ) {
    +					var fns = arguments;
    +					return jQuery.Deferred(function( newDefer ) {
    +						jQuery.each( tuples, function( i, tuple ) {
    +							var action = tuple[ 0 ],
    +								fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
    +							// deferred[ done | fail | progress ] for forwarding actions to newDefer
    +							deferred[ tuple[1] ](function() {
    +								var returned = fn && fn.apply( this, arguments );
    +								if ( returned && jQuery.isFunction( returned.promise ) ) {
    +									returned.promise()
    +										.done( newDefer.resolve )
    +										.fail( newDefer.reject )
    +										.progress( newDefer.notify );
    +								} else {
    +									newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
    +								}
    +							});
    +						});
    +						fns = null;
    +					}).promise();
    +				},
    +				// Get a promise for this deferred
    +				// If obj is provided, the promise aspect is added to the object
    +				promise: function( obj ) {
    +					return obj != null ? jQuery.extend( obj, promise ) : promise;
    +				}
    +			},
    +			deferred = {};
    +
    +		// Keep pipe for back-compat
    +		promise.pipe = promise.then;
    +
    +		// Add list-specific methods
    +		jQuery.each( tuples, function( i, tuple ) {
    +			var list = tuple[ 2 ],
    +				stateString = tuple[ 3 ];
    +
    +			// promise[ done | fail | progress ] = list.add
    +			promise[ tuple[1] ] = list.add;
    +
    +			// Handle state
    +			if ( stateString ) {
    +				list.add(function() {
    +					// state = [ resolved | rejected ]
    +					state = stateString;
    +
    +				// [ reject_list | resolve_list ].disable; progress_list.lock
    +				}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
    +			}
    +
    +			// deferred[ resolve | reject | notify ]
    +			deferred[ tuple[0] ] = function() {
    +				deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
    +				return this;
    +			};
    +			deferred[ tuple[0] + "With" ] = list.fireWith;
    +		});
    +
    +		// Make the deferred a promise
    +		promise.promise( deferred );
    +
    +		// Call given func if any
    +		if ( func ) {
    +			func.call( deferred, deferred );
    +		}
    +
    +		// All done!
    +		return deferred;
    +	},
    +
    +	// Deferred helper
    +	when: function( subordinate /* , ..., subordinateN */ ) {
    +		var i = 0,
    +			resolveValues = core_slice.call( arguments ),
    +			length = resolveValues.length,
    +
    +			// the count of uncompleted subordinates
    +			remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
    +
    +			// the master Deferred. If resolveValues consist of only a single Deferred, just use that.
    +			deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
    +
    +			// Update function for both resolve and progress values
    +			updateFunc = function( i, contexts, values ) {
    +				return function( value ) {
    +					contexts[ i ] = this;
    +					values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
    +					if( values === progressValues ) {
    +						deferred.notifyWith( contexts, values );
    +					} else if ( !( --remaining ) ) {
    +						deferred.resolveWith( contexts, values );
    +					}
    +				};
    +			},
    +
    +			progressValues, progressContexts, resolveContexts;
    +
    +		// add listeners to Deferred subordinates; treat others as resolved
    +		if ( length > 1 ) {
    +			progressValues = new Array( length );
    +			progressContexts = new Array( length );
    +			resolveContexts = new Array( length );
    +			for ( ; i < length; i++ ) {
    +				if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
    +					resolveValues[ i ].promise()
    +						.done( updateFunc( i, resolveContexts, resolveValues ) )
    +						.fail( deferred.reject )
    +						.progress( updateFunc( i, progressContexts, progressValues ) );
    +				} else {
    +					--remaining;
    +				}
    +			}
    +		}
    +
    +		// if we're not waiting on anything, resolve the master
    +		if ( !remaining ) {
    +			deferred.resolveWith( resolveContexts, resolveValues );
    +		}
    +
    +		return deferred.promise();
    +	}
    +});
    +jQuery.support = (function() {
    +
    +	var support, all, a, select, opt, input, fragment, eventName, isSupported, i,
    +		div = document.createElement("div");
    +
    +	// Setup
    +	div.setAttribute( "className", "t" );
    +	div.innerHTML = "  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>";
    +
    +	// Support tests won't run in some limited or non-browser environments
    +	all = div.getElementsByTagName("*");
    +	a = div.getElementsByTagName("a")[ 0 ];
    +	if ( !all || !a || !all.length ) {
    +		return {};
    +	}
    +
    +	// First batch of tests
    +	select = document.createElement("select");
    +	opt = select.appendChild( document.createElement("option") );
    +	input = div.getElementsByTagName("input")[ 0 ];
    +
    +	a.style.cssText = "top:1px;float:left;opacity:.5";
    +	support = {
    +		// Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)
    +		getSetAttribute: div.className !== "t",
    +
    +		// IE strips leading whitespace when .innerHTML is used
    +		leadingWhitespace: div.firstChild.nodeType === 3,
    +
    +		// Make sure that tbody elements aren't automatically inserted
    +		// IE will insert them into empty tables
    +		tbody: !div.getElementsByTagName("tbody").length,
    +
    +		// Make sure that link elements get serialized correctly by innerHTML
    +		// This requires a wrapper element in IE
    +		htmlSerialize: !!div.getElementsByTagName("link").length,
    +
    +		// Get the style information from getAttribute
    +		// (IE uses .cssText instead)
    +		style: /top/.test( a.getAttribute("style") ),
    +
    +		// Make sure that URLs aren't manipulated
    +		// (IE normalizes it by default)
    +		hrefNormalized: a.getAttribute("href") === "/a",
    +
    +		// Make sure that element opacity exists
    +		// (IE uses filter instead)
    +		// Use a regex to work around a WebKit issue. See #5145
    +		opacity: /^0.5/.test( a.style.opacity ),
    +
    +		// Verify style float existence
    +		// (IE uses styleFloat instead of cssFloat)
    +		cssFloat: !!a.style.cssFloat,
    +
    +		// Check the default checkbox/radio value ("" on WebKit; "on" elsewhere)
    +		checkOn: !!input.value,
    +
    +		// Make sure that a selected-by-default option has a working selected property.
    +		// (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
    +		optSelected: opt.selected,
    +
    +		// Tests for enctype support on a form (#6743)
    +		enctype: !!document.createElement("form").enctype,
    +
    +		// Makes sure cloning an html5 element does not cause problems
    +		// Where outerHTML is undefined, this still works
    +		html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav></:nav>",
    +
    +		// jQuery.support.boxModel DEPRECATED in 1.8 since we don't support Quirks Mode
    +		boxModel: document.compatMode === "CSS1Compat",
    +
    +		// Will be defined later
    +		deleteExpando: true,
    +		noCloneEvent: true,
    +		inlineBlockNeedsLayout: false,
    +		shrinkWrapBlocks: false,
    +		reliableMarginRight: true,
    +		boxSizingReliable: true,
    +		pixelPosition: false
    +	};
    +
    +	// Make sure checked status is properly cloned
    +	input.checked = true;
    +	support.noCloneChecked = input.cloneNode( true ).checked;
    +
    +	// Make sure that the options inside disabled selects aren't marked as disabled
    +	// (WebKit marks them as disabled)
    +	select.disabled = true;
    +	support.optDisabled = !opt.disabled;
    +
    +	// Support: IE<9
    +	try {
    +		delete div.test;
    +	} catch( e ) {
    +		support.deleteExpando = false;
    +	}
    +
    +	// Check if we can trust getAttribute("value")
    +	input = document.createElement("input");
    +	input.setAttribute( "value", "" );
    +	support.input = input.getAttribute( "value" ) === "";
    +
    +	// Check if an input maintains its value after becoming a radio
    +	input.value = "t";
    +	input.setAttribute( "type", "radio" );
    +	support.radioValue = input.value === "t";
    +
    +	// #11217 - WebKit loses check when the name is after the checked attribute
    +	input.setAttribute( "checked", "t" );
    +	input.setAttribute( "name", "t" );
    +
    +	fragment = document.createDocumentFragment();
    +	fragment.appendChild( input );
    +
    +	// Check if a disconnected checkbox will retain its checked
    +	// value of true after appended to the DOM (IE6/7)
    +	support.appendChecked = input.checked;
    +
    +	// WebKit doesn't clone checked state correctly in fragments
    +	support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;
    +
    +	// Support: IE<9
    +	// Opera does not clone events (and typeof div.attachEvent === undefined).
    +	// IE9-10 clones events bound via attachEvent, but they don't trigger with .click()
    +	if ( div.attachEvent ) {
    +		div.attachEvent( "onclick", function() {
    +			support.noCloneEvent = false;
    +		});
    +
    +		div.cloneNode( true ).click();
    +	}
    +
    +	// Support: IE<9 (lack submit/change bubble), Firefox 17+ (lack focusin event)
    +	// Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP), test/csp.php
    +	for ( i in { submit: true, change: true, focusin: true }) {
    +		div.setAttribute( eventName = "on" + i, "t" );
    +
    +		support[ i + "Bubbles" ] = eventName in window || div.attributes[ eventName ].expando === false;
    +	}
    +
    +	div.style.backgroundClip = "content-box";
    +	div.cloneNode( true ).style.backgroundClip = "";
    +	support.clearCloneStyle = div.style.backgroundClip === "content-box";
    +
    +	// Run tests that need a body at doc ready
    +	jQuery(function() {
    +		var container, marginDiv, tds,
    +			divReset = "padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",
    +			body = document.getElementsByTagName("body")[0];
    +
    +		if ( !body ) {
    +			// Return for frameset docs that don't have a body
    +			return;
    +		}
    +
    +		container = document.createElement("div");
    +		container.style.cssText = "border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px";
    +
    +		body.appendChild( container ).appendChild( div );
    +
    +		// Support: IE8
    +		// Check if table cells still have offsetWidth/Height when they are set
    +		// to display:none and there are still other visible table cells in a
    +		// table row; if so, offsetWidth/Height are not reliable for use when
    +		// determining if an element has been hidden directly using
    +		// display:none (it is still safe to use offsets if a parent element is
    +		// hidden; don safety goggles and see bug #4512 for more information).
    +		div.innerHTML = "<table><tr><td></td><td>t</td></tr></table>";
    +		tds = div.getElementsByTagName("td");
    +		tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none";
    +		isSupported = ( tds[ 0 ].offsetHeight === 0 );
    +
    +		tds[ 0 ].style.display = "";
    +		tds[ 1 ].style.display = "none";
    +
    +		// Support: IE8
    +		// Check if empty table cells still have offsetWidth/Height
    +		support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );
    +
    +		// Check box-sizing and margin behavior
    +		div.innerHTML = "";
    +		div.style.cssText = "box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;";
    +		support.boxSizing = ( div.offsetWidth === 4 );
    +		support.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== 1 );
    +
    +		// Use window.getComputedStyle because jsdom on node.js will break without it.
    +		if ( window.getComputedStyle ) {
    +			support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%";
    +			support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px";
    +
    +			// Check if div with explicit width and no margin-right incorrectly
    +			// gets computed margin-right based on width of container. (#3333)
    +			// Fails in WebKit before Feb 2011 nightlies
    +			// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
    +			marginDiv = div.appendChild( document.createElement("div") );
    +			marginDiv.style.cssText = div.style.cssText = divReset;
    +			marginDiv.style.marginRight = marginDiv.style.width = "0";
    +			div.style.width = "1px";
    +
    +			support.reliableMarginRight =
    +				!parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight );
    +		}
    +
    +		if ( typeof div.style.zoom !== "undefined" ) {
    +			// Support: IE<8
    +			// Check if natively block-level elements act like inline-block
    +			// elements when setting their display to 'inline' and giving
    +			// them layout
    +			div.innerHTML = "";
    +			div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1";
    +			support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 );
    +
    +			// Support: IE6
    +			// Check if elements with layout shrink-wrap their children
    +			div.style.display = "block";
    +			div.innerHTML = "<div></div>";
    +			div.firstChild.style.width = "5px";
    +			support.shrinkWrapBlocks = ( div.offsetWidth !== 3 );
    +
    +			// Prevent IE 6 from affecting layout for positioned elements #11048
    +			// Prevent IE from shrinking the body in IE 7 mode #12869
    +			body.style.zoom = 1;
    +		}
    +
    +		body.removeChild( container );
    +
    +		// Null elements to avoid leaks in IE
    +		container = div = tds = marginDiv = null;
    +	});
    +
    +	// Null elements to avoid leaks in IE
    +	all = select = fragment = opt = a = input = null;
    +
    +	return support;
    +})();
    +
    +var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/,
    +	rmultiDash = /([A-Z])/g;
    +	
    +function internalData( elem, name, data, pvt /* Internal Use Only */ ){
    +	if ( !jQuery.acceptData( elem ) ) {
    +		return;
    +	}
    +
    +	var thisCache, ret,
    +		internalKey = jQuery.expando,
    +		getByName = typeof name === "string",
    +
    +		// We have to handle DOM nodes and JS objects differently because IE6-7
    +		// can't GC object references properly across the DOM-JS boundary
    +		isNode = elem.nodeType,
    +
    +		// Only DOM nodes need the global jQuery cache; JS object data is
    +		// attached directly to the object so GC can occur automatically
    +		cache = isNode ? jQuery.cache : elem,
    +
    +		// Only defining an ID for JS objects if its cache already exists allows
    +		// the code to shortcut on the same path as a DOM node with no cache
    +		id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;
    +
    +	// Avoid doing any more work than we need to when trying to get data on an
    +	// object that has no data at all
    +	if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) {
    +		return;
    +	}
    +
    +	if ( !id ) {
    +		// Only DOM nodes need a new unique ID for each element since their data
    +		// ends up in the global cache
    +		if ( isNode ) {
    +			elem[ internalKey ] = id = core_deletedIds.pop() || jQuery.guid++;
    +		} else {
    +			id = internalKey;
    +		}
    +	}
    +
    +	if ( !cache[ id ] ) {
    +		cache[ id ] = {};
    +
    +		// Avoids exposing jQuery metadata on plain JS objects when the object
    +		// is serialized using JSON.stringify
    +		if ( !isNode ) {
    +			cache[ id ].toJSON = jQuery.noop;
    +		}
    +	}
    +
    +	// An object can be passed to jQuery.data instead of a key/value pair; this gets
    +	// shallow copied over onto the existing cache
    +	if ( typeof name === "object" || typeof name === "function" ) {
    +		if ( pvt ) {
    +			cache[ id ] = jQuery.extend( cache[ id ], name );
    +		} else {
    +			cache[ id ].data = jQuery.extend( cache[ id ].data, name );
    +		}
    +	}
    +
    +	thisCache = cache[ id ];
    +
    +	// jQuery data() is stored in a separate object inside the object's internal data
    +	// cache in order to avoid key collisions between internal data and user-defined
    +	// data.
    +	if ( !pvt ) {
    +		if ( !thisCache.data ) {
    +			thisCache.data = {};
    +		}
    +
    +		thisCache = thisCache.data;
    +	}
    +
    +	if ( data !== undefined ) {
    +		thisCache[ jQuery.camelCase( name ) ] = data;
    +	}
    +
    +	// Check for both converted-to-camel and non-converted data property names
    +	// If a data property was specified
    +	if ( getByName ) {
    +
    +		// First Try to find as-is property data
    +		ret = thisCache[ name ];
    +
    +		// Test for null|undefined property data
    +		if ( ret == null ) {
    +
    +			// Try to find the camelCased property
    +			ret = thisCache[ jQuery.camelCase( name ) ];
    +		}
    +	} else {
    +		ret = thisCache;
    +	}
    +
    +	return ret;
    +}
    +
    +function internalRemoveData( elem, name, pvt /* For internal use only */ ){
    +	if ( !jQuery.acceptData( elem ) ) {
    +		return;
    +	}
    +
    +	var thisCache, i, l,
    +
    +		isNode = elem.nodeType,
    +
    +		// See jQuery.data for more information
    +		cache = isNode ? jQuery.cache : elem,
    +		id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
    +
    +	// If there is already no cache entry for this object, there is no
    +	// purpose in continuing
    +	if ( !cache[ id ] ) {
    +		return;
    +	}
    +
    +	if ( name ) {
    +
    +		thisCache = pvt ? cache[ id ] : cache[ id ].data;
    +
    +		if ( thisCache ) {
    +
    +			// Support array or space separated string names for data keys
    +			if ( !jQuery.isArray( name ) ) {
    +
    +				// try the string as a key before any manipulation
    +				if ( name in thisCache ) {
    +					name = [ name ];
    +				} else {
    +
    +					// split the camel cased version by spaces unless a key with the spaces exists
    +					name = jQuery.camelCase( name );
    +					if ( name in thisCache ) {
    +						name = [ name ];
    +					} else {
    +						name = name.split(" ");
    +					}
    +				}
    +			} else {
    +				// If "name" is an array of keys...
    +				// When data is initially created, via ("key", "val") signature,
    +				// keys will be converted to camelCase.
    +				// Since there is no way to tell _how_ a key was added, remove
    +				// both plain key and camelCase key. #12786
    +				// This will only penalize the array argument path.
    +				name = name.concat( jQuery.map( name, jQuery.camelCase ) );
    +			}
    +
    +			for ( i = 0, l = name.length; i < l; i++ ) {
    +				delete thisCache[ name[i] ];
    +			}
    +
    +			// If there is no data left in the cache, we want to continue
    +			// and let the cache object itself get destroyed
    +			if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {
    +				return;
    +			}
    +		}
    +	}
    +
    +	// See jQuery.data for more information
    +	if ( !pvt ) {
    +		delete cache[ id ].data;
    +
    +		// Don't destroy the parent cache unless the internal data object
    +		// had been the only thing left in it
    +		if ( !isEmptyDataObject( cache[ id ] ) ) {
    +			return;
    +		}
    +	}
    +
    +	// Destroy the cache
    +	if ( isNode ) {
    +		jQuery.cleanData( [ elem ], true );
    +
    +	// Use delete when supported for expandos or `cache` is not a window per isWindow (#10080)
    +	} else if ( jQuery.support.deleteExpando || cache != cache.window ) {
    +		delete cache[ id ];
    +
    +	// When all else fails, null
    +	} else {
    +		cache[ id ] = null;
    +	}
    +}
    +
    +jQuery.extend({
    +	cache: {},
    +
    +	// Unique for each copy of jQuery on the page
    +	// Non-digits removed to match rinlinejQuery
    +	expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ),
    +
    +	// The following elements throw uncatchable exceptions if you
    +	// attempt to add expando properties to them.
    +	noData: {
    +		"embed": true,
    +		// Ban all objects except for Flash (which handle expandos)
    +		"object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
    +		"applet": true
    +	},
    +
    +	hasData: function( elem ) {
    +		elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
    +		return !!elem && !isEmptyDataObject( elem );
    +	},
    +
    +	data: function( elem, name, data ) {
    +		return internalData( elem, name, data, false );
    +	},
    +
    +	removeData: function( elem, name ) {
    +		return internalRemoveData( elem, name, false );
    +	},
    +
    +	// For internal use only.
    +	_data: function( elem, name, data ) {
    +		return internalData( elem, name, data, true );
    +	},
    +	
    +	_removeData: function( elem, name ) {
    +		return internalRemoveData( elem, name, true );
    +	},
    +
    +	// A method for determining if a DOM node can handle the data expando
    +	acceptData: function( elem ) {
    +		var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ];
    +
    +		// nodes accept data unless otherwise specified; rejection can be conditional
    +		return !noData || noData !== true && elem.getAttribute("classid") === noData;
    +	}
    +});
    +
    +jQuery.fn.extend({
    +	data: function( key, value ) {
    +		var attrs, name,
    +			elem = this[0],
    +			i = 0,
    +			data = null;
    +
    +		// Gets all values
    +		if ( key === undefined ) {
    +			if ( this.length ) {
    +				data = jQuery.data( elem );
    +
    +				if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
    +					attrs = elem.attributes;
    +					for ( ; i < attrs.length; i++ ) {
    +						name = attrs[i].name;
    +
    +						if ( !name.indexOf( "data-" ) ) {
    +							name = jQuery.camelCase( name.substring(5) );
    +
    +							dataAttr( elem, name, data[ name ] );
    +						}
    +					}
    +					jQuery._data( elem, "parsedAttrs", true );
    +				}
    +			}
    +
    +			return data;
    +		}
    +
    +		// Sets multiple values
    +		if ( typeof key === "object" ) {
    +			return this.each(function() {
    +				jQuery.data( this, key );
    +			});
    +		}
    +
    +		return jQuery.access( this, function( value ) {
    +
    +			if ( value === undefined ) {
    +				// Try to fetch any internally stored data first
    +				return elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : null;
    +			}
    +
    +			this.each(function() {
    +				jQuery.data( this, key, value );
    +			});
    +		}, null, value, arguments.length > 1, null, true );
    +	},
    +
    +	removeData: function( key ) {
    +		return this.each(function() {
    +			jQuery.removeData( this, key );
    +		});
    +	}
    +});
    +
    +function dataAttr( elem, key, data ) {
    +	// If nothing was found internally, try to fetch any
    +	// data from the HTML5 data-* attribute
    +	if ( data === undefined && elem.nodeType === 1 ) {
    +
    +		var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
    +
    +		data = elem.getAttribute( name );
    +
    +		if ( typeof data === "string" ) {
    +			try {
    +				data = data === "true" ? true :
    +				data === "false" ? false :
    +				data === "null" ? null :
    +				// Only convert to a number if it doesn't change the string
    +				+data + "" === data ? +data :
    +				rbrace.test( data ) ? jQuery.parseJSON( data ) :
    +					data;
    +			} catch( e ) {}
    +
    +			// Make sure we set the data so it isn't changed later
    +			jQuery.data( elem, key, data );
    +
    +		} else {
    +			data = undefined;
    +		}
    +	}
    +
    +	return data;
    +}
    +
    +// checks a cache object for emptiness
    +function isEmptyDataObject( obj ) {
    +	var name;
    +	for ( name in obj ) {
    +
    +		// if the public data object is empty, the private is still empty
    +		if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {
    +			continue;
    +		}
    +		if ( name !== "toJSON" ) {
    +			return false;
    +		}
    +	}
    +
    +	return true;
    +}
    +jQuery.extend({
    +	queue: function( elem, type, data ) {
    +		var queue;
    +
    +		if ( elem ) {
    +			type = ( type || "fx" ) + "queue";
    +			queue = jQuery._data( elem, type );
    +
    +			// Speed up dequeue by getting out quickly if this is just a lookup
    +			if ( data ) {
    +				if ( !queue || jQuery.isArray(data) ) {
    +					queue = jQuery._data( elem, type, jQuery.makeArray(data) );
    +				} else {
    +					queue.push( data );
    +				}
    +			}
    +			return queue || [];
    +		}
    +	},
    +
    +	dequeue: function( elem, type ) {
    +		type = type || "fx";
    +
    +		var queue = jQuery.queue( elem, type ),
    +			startLength = queue.length,
    +			fn = queue.shift(),
    +			hooks = jQuery._queueHooks( elem, type ),
    +			next = function() {
    +				jQuery.dequeue( elem, type );
    +			};
    +
    +		// If the fx queue is dequeued, always remove the progress sentinel
    +		if ( fn === "inprogress" ) {
    +			fn = queue.shift();
    +			startLength--;
    +		}
    +
    +		hooks.cur = fn;
    +		if ( fn ) {
    +
    +			// Add a progress sentinel to prevent the fx queue from being
    +			// automatically dequeued
    +			if ( type === "fx" ) {
    +				queue.unshift( "inprogress" );
    +			}
    +
    +			// clear up the last queue stop function
    +			delete hooks.stop;
    +			fn.call( elem, next, hooks );
    +		}
    +
    +		if ( !startLength && hooks ) {
    +			hooks.empty.fire();
    +		}
    +	},
    +
    +	// not intended for public consumption - generates a queueHooks object, or returns the current one
    +	_queueHooks: function( elem, type ) {
    +		var key = type + "queueHooks";
    +		return jQuery._data( elem, key ) || jQuery._data( elem, key, {
    +			empty: jQuery.Callbacks("once memory").add(function() {
    +				jQuery._removeData( elem, type + "queue" );
    +				jQuery._removeData( elem, key );
    +			})
    +		});
    +	}
    +});
    +
    +jQuery.fn.extend({
    +	queue: function( type, data ) {
    +		var setter = 2;
    +
    +		if ( typeof type !== "string" ) {
    +			data = type;
    +			type = "fx";
    +			setter--;
    +		}
    +
    +		if ( arguments.length < setter ) {
    +			return jQuery.queue( this[0], type );
    +		}
    +
    +		return data === undefined ?
    +			this :
    +			this.each(function() {
    +				var queue = jQuery.queue( this, type, data );
    +
    +				// ensure a hooks for this queue
    +				jQuery._queueHooks( this, type );
    +
    +				if ( type === "fx" && queue[0] !== "inprogress" ) {
    +					jQuery.dequeue( this, type );
    +				}
    +			});
    +	},
    +	dequeue: function( type ) {
    +		return this.each(function() {
    +			jQuery.dequeue( this, type );
    +		});
    +	},
    +	// Based off of the plugin by Clint Helfers, with permission.
    +	// http://blindsignals.com/index.php/2009/07/jquery-delay/
    +	delay: function( time, type ) {
    +		time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
    +		type = type || "fx";
    +
    +		return this.queue( type, function( next, hooks ) {
    +			var timeout = setTimeout( next, time );
    +			hooks.stop = function() {
    +				clearTimeout( timeout );
    +			};
    +		});
    +	},
    +	clearQueue: function( type ) {
    +		return this.queue( type || "fx", [] );
    +	},
    +	// Get a promise resolved when queues of a certain type
    +	// are emptied (fx is the type by default)
    +	promise: function( type, obj ) {
    +		var tmp,
    +			count = 1,
    +			defer = jQuery.Deferred(),
    +			elements = this,
    +			i = this.length,
    +			resolve = function() {
    +				if ( !( --count ) ) {
    +					defer.resolveWith( elements, [ elements ] );
    +				}
    +			};
    +
    +		if ( typeof type !== "string" ) {
    +			obj = type;
    +			type = undefined;
    +		}
    +		type = type || "fx";
    +
    +		while( i-- ) {
    +			tmp = jQuery._data( elements[ i ], type + "queueHooks" );
    +			if ( tmp && tmp.empty ) {
    +				count++;
    +				tmp.empty.add( resolve );
    +			}
    +		}
    +		resolve();
    +		return defer.promise( obj );
    +	}
    +});
    +var nodeHook, boolHook,
    +	rclass = /[\t\r\n]/g,
    +	rreturn = /\r/g,
    +	rfocusable = /^(?:input|select|textarea|button|object)$/i,
    +	rclickable = /^(?:a|area)$/i,
    +	rboolean = /^(?:checked|selected|autofocus|autoplay|async|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped)$/i,
    +	ruseDefault = /^(?:checked|selected)$/i,
    +	getSetAttribute = jQuery.support.getSetAttribute,
    +	getSetInput = jQuery.support.input;
    +
    +jQuery.fn.extend({
    +	attr: function( name, value ) {
    +		return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );
    +	},
    +
    +	removeAttr: function( name ) {
    +		return this.each(function() {
    +			jQuery.removeAttr( this, name );
    +		});
    +	},
    +
    +	prop: function( name, value ) {
    +		return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );
    +	},
    +
    +	removeProp: function( name ) {
    +		name = jQuery.propFix[ name ] || name;
    +		return this.each(function() {
    +			// try/catch handles cases where IE balks (such as removing a property on window)
    +			try {
    +				this[ name ] = undefined;
    +				delete this[ name ];
    +			} catch( e ) {}
    +		});
    +	},
    +
    +	addClass: function( value ) {
    +		var classes, elem, cur, clazz, j,
    +			i = 0,
    +			len = this.length,
    +			proceed = typeof value === "string" && value;
    +
    +		if ( jQuery.isFunction( value ) ) {
    +			return this.each(function( j ) {
    +				jQuery( this ).addClass( value.call( this, j, this.className ) );
    +			});
    +		}
    +
    +		if ( proceed ) {
    +			// The disjunction here is for better compressibility (see removeClass)
    +			classes = ( value || "" ).match( core_rnotwhite ) || [];
    +
    +			for ( ; i < len; i++ ) {
    +				elem = this[ i ];
    +				cur = elem.nodeType === 1 && ( elem.className ?
    +					( " " + elem.className + " " ).replace( rclass, " " ) :
    +					" "
    +				);
    +
    +				if ( cur ) {
    +					j = 0;
    +					while ( (clazz = classes[j++]) ) {
    +						if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
    +							cur += clazz + " ";
    +						}
    +					}
    +					elem.className = jQuery.trim( cur );
    +
    +				}
    +			}
    +		}
    +
    +		return this;
    +	},
    +
    +	removeClass: function( value ) {
    +		var classes, elem, cur, clazz, j,
    +			i = 0,
    +			len = this.length,
    +			proceed = arguments.length === 0 || typeof value === "string" && value;
    +
    +		if ( jQuery.isFunction( value ) ) {
    +			return this.each(function( j ) {
    +				jQuery( this ).removeClass( value.call( this, j, this.className ) );
    +			});
    +		}
    +		if ( proceed ) {
    +			classes = ( value || "" ).match( core_rnotwhite ) || [];
    +
    +			for ( ; i < len; i++ ) {
    +				elem = this[ i ];
    +				// This expression is here for better compressibility (see addClass)
    +				cur = elem.nodeType === 1 && ( elem.className ?
    +					( " " + elem.className + " " ).replace( rclass, " " ) :
    +					""
    +				);
    +
    +				if ( cur ) {
    +					j = 0;
    +					while ( (clazz = classes[j++]) ) {
    +						// Remove *all* instances
    +						while ( cur.indexOf( " " + clazz + " " ) >= 0 ) {
    +							cur = cur.replace( " " + clazz + " ", " " );
    +						}
    +					}
    +					elem.className = value ? jQuery.trim( cur ) : "";
    +				}
    +			}
    +		}
    +
    +		return this;
    +	},
    +
    +	toggleClass: function( value, stateVal ) {
    +		var type = typeof value,
    +			isBool = typeof stateVal === "boolean";
    +
    +		if ( jQuery.isFunction( value ) ) {
    +			return this.each(function( i ) {
    +				jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
    +			});
    +		}
    +
    +		return this.each(function() {
    +			if ( type === "string" ) {
    +				// toggle individual class names
    +				var className,
    +					i = 0,
    +					self = jQuery( this ),
    +					state = stateVal,
    +					classNames = value.match( core_rnotwhite ) || [];
    +
    +				while ( (className = classNames[ i++ ]) ) {
    +					// check each className given, space separated list
    +					state = isBool ? state : !self.hasClass( className );
    +					self[ state ? "addClass" : "removeClass" ]( className );
    +				}
    +
    +			// Toggle whole class name
    +			} else if ( type === "undefined" || type === "boolean" ) {
    +				if ( this.className ) {
    +					// store className if set
    +					jQuery._data( this, "__className__", this.className );
    +				}
    +
    +				// If the element has a class name or if we're passed "false",
    +				// then remove the whole classname (if there was one, the above saved it).
    +				// Otherwise bring back whatever was previously saved (if anything),
    +				// falling back to the empty string if nothing was stored.
    +				this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
    +			}
    +		});
    +	},
    +
    +	hasClass: function( selector ) {
    +		var className = " " + selector + " ",
    +			i = 0,
    +			l = this.length;
    +		for ( ; i < l; i++ ) {
    +			if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) {
    +				return true;
    +			}
    +		}
    +
    +		return false;
    +	},
    +
    +	val: function( value ) {
    +		var hooks, ret, isFunction,
    +			elem = this[0];
    +
    +		if ( !arguments.length ) {
    +			if ( elem ) {
    +				hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];
    +
    +				if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
    +					return ret;
    +				}
    +
    +				ret = elem.value;
    +
    +				return typeof ret === "string" ?
    +					// handle most common string cases
    +					ret.replace(rreturn, "") :
    +					// handle cases where value is null/undef or number
    +					ret == null ? "" : ret;
    +			}
    +
    +			return;
    +		}
    +
    +		isFunction = jQuery.isFunction( value );
    +
    +		return this.each(function( i ) {
    +			var val,
    +				self = jQuery(this);
    +
    +			if ( this.nodeType !== 1 ) {
    +				return;
    +			}
    +
    +			if ( isFunction ) {
    +				val = value.call( this, i, self.val() );
    +			} else {
    +				val = value;
    +			}
    +
    +			// Treat null/undefined as ""; convert numbers to string
    +			if ( val == null ) {
    +				val = "";
    +			} else if ( typeof val === "number" ) {
    +				val += "";
    +			} else if ( jQuery.isArray( val ) ) {
    +				val = jQuery.map(val, function ( value ) {
    +					return value == null ? "" : value + "";
    +				});
    +			}
    +
    +			hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
    +
    +			// If set returns undefined, fall back to normal setting
    +			if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
    +				this.value = val;
    +			}
    +		});
    +	}
    +});
    +
    +jQuery.extend({
    +	valHooks: {
    +		option: {
    +			get: function( elem ) {
    +				// attributes.value is undefined in Blackberry 4.7 but
    +				// uses .value. See #6932
    +				var val = elem.attributes.value;
    +				return !val || val.specified ? elem.value : elem.text;
    +			}
    +		},
    +		select: {
    +			get: function( elem ) {
    +				var value, option,
    +					options = elem.options,
    +					index = elem.selectedIndex,
    +					one = elem.type === "select-one" || index < 0,
    +					values = one ? null : [],
    +					max = one ? index + 1 : options.length,
    +					i = index < 0 ?
    +						max :
    +						one ? index : 0;
    +
    +				// Loop through all the selected options
    +				for ( ; i < max; i++ ) {
    +					option = options[ i ];
    +
    +					// oldIE doesn't update selected after form reset (#2551)
    +					if ( ( option.selected || i === index ) &&
    +							// Don't return options that are disabled or in a disabled optgroup
    +							( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) &&
    +							( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {
    +
    +						// Get the specific value for the option
    +						value = jQuery( option ).val();
    +
    +						// We don't need an array for one selects
    +						if ( one ) {
    +							return value;
    +						}
    +
    +						// Multi-Selects return an array
    +						values.push( value );
    +					}
    +				}
    +
    +				return values;
    +			},
    +
    +			set: function( elem, value ) {
    +				var values = jQuery.makeArray( value );
    +
    +				jQuery(elem).find("option").each(function() {
    +					this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
    +				});
    +
    +				if ( !values.length ) {
    +					elem.selectedIndex = -1;
    +				}
    +				return values;
    +			}
    +		}
    +	},
    +
    +	attr: function( elem, name, value ) {
    +		var ret, hooks, notxml,
    +			nType = elem.nodeType;
    +
    +		// don't get/set attributes on text, comment and attribute nodes
    +		if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
    +			return;
    +		}
    +
    +		// Fallback to prop when attributes are not supported
    +		if ( typeof elem.getAttribute === "undefined" ) {
    +			return jQuery.prop( elem, name, value );
    +		}
    +
    +		notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
    +
    +		// All attributes are lowercase
    +		// Grab necessary hook if one is defined
    +		if ( notxml ) {
    +			name = name.toLowerCase();
    +			hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook );
    +		}
    +
    +		if ( value !== undefined ) {
    +
    +			if ( value === null ) {
    +				jQuery.removeAttr( elem, name );
    +
    +			} else if ( hooks && notxml && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
    +				return ret;
    +
    +			} else {
    +				elem.setAttribute( name, value + "" );
    +				return value;
    +			}
    +
    +		} else if ( hooks && notxml && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
    +			return ret;
    +
    +		} else {
    +
    +			// In IE9+, Flash objects don't have .getAttribute (#12945)
    +			// Support: IE9+
    +			if ( typeof elem.getAttribute !== "undefined" ) {
    +				ret =  elem.getAttribute( name );
    +			}
    +
    +			// Non-existent attributes return null, we normalize to undefined
    +			return ret == null ?
    +				undefined :
    +				ret;
    +		}
    +	},
    +
    +	removeAttr: function( elem, value ) {
    +		var name, propName,
    +			i = 0,
    +			attrNames = value && value.match( core_rnotwhite );
    +
    +		if ( attrNames && elem.nodeType === 1 ) {
    +			while ( (name = attrNames[i++]) ) {
    +				propName = jQuery.propFix[ name ] || name;
    +
    +				// Boolean attributes get special treatment (#10870)
    +				if ( rboolean.test( name ) ) {
    +					// Set corresponding property to false for boolean attributes
    +					// Also clear defaultChecked/defaultSelected (if appropriate) for IE<8
    +					if ( !getSetAttribute && ruseDefault.test( name ) ) {
    +						elem[ jQuery.camelCase( "default-" + name ) ] =
    +							elem[ propName ] = false;
    +					} else {
    +						elem[ propName ] = false;
    +					}
    +
    +				// See #9699 for explanation of this approach (setting first, then removal)
    +				} else {
    +					jQuery.attr( elem, name, "" );
    +				}
    +
    +				elem.removeAttribute( getSetAttribute ? name : propName );
    +			}
    +		}
    +	},
    +
    +	attrHooks: {
    +		type: {
    +			set: function( elem, value ) {
    +				if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
    +					// Setting the type on a radio button after the value resets the value in IE6-9
    +					// Reset value to default in case type is set after value during creation
    +					var val = elem.value;
    +					elem.setAttribute( "type", value );
    +					if ( val ) {
    +						elem.value = val;
    +					}
    +					return value;
    +				}
    +			}
    +		}
    +	},
    +
    +	propFix: {
    +		tabindex: "tabIndex",
    +		readonly: "readOnly",
    +		"for": "htmlFor",
    +		"class": "className",
    +		maxlength: "maxLength",
    +		cellspacing: "cellSpacing",
    +		cellpadding: "cellPadding",
    +		rowspan: "rowSpan",
    +		colspan: "colSpan",
    +		usemap: "useMap",
    +		frameborder: "frameBorder",
    +		contenteditable: "contentEditable"
    +	},
    +
    +	prop: function( elem, name, value ) {
    +		var ret, hooks, notxml,
    +			nType = elem.nodeType;
    +
    +		// don't get/set properties on text, comment and attribute nodes
    +		if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
    +			return;
    +		}
    +
    +		notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
    +
    +		if ( notxml ) {
    +			// Fix name and attach hooks
    +			name = jQuery.propFix[ name ] || name;
    +			hooks = jQuery.propHooks[ name ];
    +		}
    +
    +		if ( value !== undefined ) {
    +			if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
    +				return ret;
    +
    +			} else {
    +				return ( elem[ name ] = value );
    +			}
    +
    +		} else {
    +			if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
    +				return ret;
    +
    +			} else {
    +				return elem[ name ];
    +			}
    +		}
    +	},
    +
    +	propHooks: {
    +		tabIndex: {
    +			get: function( elem ) {
    +				// elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
    +				// http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
    +				var attributeNode = elem.getAttributeNode("tabindex");
    +
    +				return attributeNode && attributeNode.specified ?
    +					parseInt( attributeNode.value, 10 ) :
    +					rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
    +						0 :
    +						undefined;
    +			}
    +		}
    +	}
    +});
    +
    +// Hook for boolean attributes
    +boolHook = {
    +	get: function( elem, name ) {
    +		var
    +			// Use .prop to determine if this attribute is understood as boolean
    +			prop = jQuery.prop( elem, name ),
    +
    +			// Fetch it accordingly
    +			attr = typeof prop === "boolean" && elem.getAttribute( name ),
    +			detail = typeof prop === "boolean" ?
    +
    +				getSetInput && getSetAttribute ?
    +					attr != null :
    +					// oldIE fabricates an empty string for missing boolean attributes
    +					// and conflates checked/selected into attroperties
    +					ruseDefault.test( name ) ?
    +						elem[ jQuery.camelCase( "default-" + name ) ] :
    +						!!attr :
    +
    +				// fetch an attribute node for properties not recognized as boolean
    +				elem.getAttributeNode( name );
    +
    +		return detail && detail.value !== false ?
    +			name.toLowerCase() :
    +			undefined;
    +	},
    +	set: function( elem, value, name ) {
    +		if ( value === false ) {
    +			// Remove boolean attributes when set to false
    +			jQuery.removeAttr( elem, name );
    +		} else if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) {
    +			// IE<8 needs the *property* name
    +			elem.setAttribute( !getSetAttribute && jQuery.propFix[ name ] || name, name );
    +
    +		// Use defaultChecked and defaultSelected for oldIE
    +		} else {
    +			elem[ jQuery.camelCase( "default-" + name ) ] = elem[ name ] = true;
    +		}
    +
    +		return name;
    +	}
    +};
    +
    +// fix oldIE value attroperty
    +if ( !getSetInput || !getSetAttribute ) {
    +	jQuery.attrHooks.value = {
    +		get: function( elem, name ) {
    +			var ret = elem.getAttributeNode( name );
    +			return jQuery.nodeName( elem, "input" ) ?
    +
    +				// Ignore the value *property* by using defaultValue
    +				elem.defaultValue :
    +
    +				ret && ret.specified ? ret.value : undefined;
    +		},
    +		set: function( elem, value, name ) {
    +			if ( jQuery.nodeName( elem, "input" ) ) {
    +				// Does not return so that setAttribute is also used
    +				elem.defaultValue = value;
    +			} else {
    +				// Use nodeHook if defined (#1954); otherwise setAttribute is fine
    +				return nodeHook && nodeHook.set( elem, value, name );
    +			}
    +		}
    +	};
    +}
    +
    +// IE6/7 do not support getting/setting some attributes with get/setAttribute
    +if ( !getSetAttribute ) {
    +
    +	// Use this for any attribute in IE6/7
    +	// This fixes almost every IE6/7 issue
    +	nodeHook = jQuery.valHooks.button = {
    +		get: function( elem, name ) {
    +			var ret = elem.getAttributeNode( name );
    +			return ret && ( name === "id" || name === "name" || name === "coords" ? ret.value !== "" : ret.specified ) ?
    +				ret.value :
    +				undefined;
    +		},
    +		set: function( elem, value, name ) {
    +			// Set the existing or create a new attribute node
    +			var ret = elem.getAttributeNode( name );
    +			if ( !ret ) {
    +				elem.setAttributeNode(
    +					(ret = elem.ownerDocument.createAttribute( name ))
    +				);
    +			}
    +
    +			ret.value = value += "";
    +
    +			// Break association with cloned elements by also using setAttribute (#9646)
    +			return name === "value" || value === elem.getAttribute( name ) ?
    +				value :
    +				undefined;
    +		}
    +	};
    +
    +	// Set contenteditable to false on removals(#10429)
    +	// Setting to empty string throws an error as an invalid value
    +	jQuery.attrHooks.contenteditable = {
    +		get: nodeHook.get,
    +		set: function( elem, value, name ) {
    +			nodeHook.set( elem, value === "" ? false : value, name );
    +		}
    +	};
    +
    +	// Set width and height to auto instead of 0 on empty string( Bug #8150 )
    +	// This is for removals
    +	jQuery.each([ "width", "height" ], function( i, name ) {
    +		jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
    +			set: function( elem, value ) {
    +				if ( value === "" ) {
    +					elem.setAttribute( name, "auto" );
    +					return value;
    +				}
    +			}
    +		});
    +	});
    +}
    +
    +
    +// Some attributes require a special call on IE
    +// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
    +if ( !jQuery.support.hrefNormalized ) {
    +	jQuery.each([ "href", "src", "width", "height" ], function( i, name ) {
    +		jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
    +			get: function( elem ) {
    +				var ret = elem.getAttribute( name, 2 );
    +				return ret == null ? undefined : ret;
    +			}
    +		});
    +	});
    +
    +	// href/src property should get the full normalized URL (#10299/#12915)
    +	jQuery.each([ "href", "src" ], function( i, name ) {
    +		jQuery.propHooks[ name ] = {
    +			get: function( elem ) {
    +				return elem.getAttribute( name, 4 );
    +			}
    +		};
    +	});
    +}
    +
    +if ( !jQuery.support.style ) {
    +	jQuery.attrHooks.style = {
    +		get: function( elem ) {
    +			// Return undefined in the case of empty string
    +			// Note: IE uppercases css property names, but if we were to .toLowerCase()
    +			// .cssText, that would destroy case senstitivity in URL's, like in "background"
    +			return elem.style.cssText || undefined;
    +		},
    +		set: function( elem, value ) {
    +			return ( elem.style.cssText = value + "" );
    +		}
    +	};
    +}
    +
    +// Safari mis-reports the default selected property of an option
    +// Accessing the parent's selectedIndex property fixes it
    +if ( !jQuery.support.optSelected ) {
    +	jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, {
    +		get: function( elem ) {
    +			var parent = elem.parentNode;
    +
    +			if ( parent ) {
    +				parent.selectedIndex;
    +
    +				// Make sure that it also works with optgroups, see #5701
    +				if ( parent.parentNode ) {
    +					parent.parentNode.selectedIndex;
    +				}
    +			}
    +			return null;
    +		}
    +	});
    +}
    +
    +// IE6/7 call enctype encoding
    +if ( !jQuery.support.enctype ) {
    +	jQuery.propFix.enctype = "encoding";
    +}
    +
    +// Radios and checkboxes getter/setter
    +if ( !jQuery.support.checkOn ) {
    +	jQuery.each([ "radio", "checkbox" ], function() {
    +		jQuery.valHooks[ this ] = {
    +			get: function( elem ) {
    +				// Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
    +				return elem.getAttribute("value") === null ? "on" : elem.value;
    +			}
    +		};
    +	});
    +}
    +jQuery.each([ "radio", "checkbox" ], function() {
    +	jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], {
    +		set: function( elem, value ) {
    +			if ( jQuery.isArray( value ) ) {
    +				return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
    +			}
    +		}
    +	});
    +});
    +var rformElems = /^(?:input|select|textarea)$/i,
    +	rkeyEvent = /^key/,
    +	rmouseEvent = /^(?:mouse|contextmenu)|click/,
    +	rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
    +	rtypenamespace = /^([^.]*)(?:\.(.+)|)$/;
    +
    +function returnTrue() {
    +	return true;
    +}
    +
    +function returnFalse() {
    +	return false;
    +}
    +
    +/*
    + * Helper functions for managing events -- not part of the public interface.
    + * Props to Dean Edwards' addEvent library for many of the ideas.
    + */
    +jQuery.event = {
    +
    +	global: {},
    +
    +	add: function( elem, types, handler, data, selector ) {
    +
    +		var handleObjIn, eventHandle, tmp,
    +			events, t, handleObj,
    +			special, handlers, type, namespaces, origType,
    +			// Don't attach events to noData or text/comment nodes (but allow plain objects)
    +			elemData = elem.nodeType !== 3 && elem.nodeType !== 8 && jQuery._data( elem );
    +
    +		if ( !elemData ) {
    +			return;
    +		}
    +
    +		// Caller can pass in an object of custom data in lieu of the handler
    +		if ( handler.handler ) {
    +			handleObjIn = handler;
    +			handler = handleObjIn.handler;
    +			selector = handleObjIn.selector;
    +		}
    +
    +		// Make sure that the handler has a unique ID, used to find/remove it later
    +		if ( !handler.guid ) {
    +			handler.guid = jQuery.guid++;
    +		}
    +
    +		// Init the element's event structure and main handler, if this is the first
    +		if ( !(events = elemData.events) ) {
    +			events = elemData.events = {};
    +		}
    +		if ( !(eventHandle = elemData.handle) ) {
    +			eventHandle = elemData.handle = function( e ) {
    +				// Discard the second event of a jQuery.event.trigger() and
    +				// when an event is called after a page has unloaded
    +				return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ?
    +					jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
    +					undefined;
    +			};
    +			// Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
    +			eventHandle.elem = elem;
    +		}
    +
    +		// Handle multiple events separated by a space
    +		// jQuery(...).bind("mouseover mouseout", fn);
    +		types = ( types || "" ).match( core_rnotwhite ) || [""];
    +		t = types.length;
    +		while ( t-- ) {
    +			tmp = rtypenamespace.exec( types[t] ) || [];
    +			type = origType = tmp[1];
    +			namespaces = ( tmp[2] || "" ).split( "." ).sort();
    +
    +			// If event changes its type, use the special event handlers for the changed type
    +			special = jQuery.event.special[ type ] || {};
    +
    +			// If selector defined, determine special event api type, otherwise given type
    +			type = ( selector ? special.delegateType : special.bindType ) || type;
    +
    +			// Update special based on newly reset type
    +			special = jQuery.event.special[ type ] || {};
    +
    +			// handleObj is passed to all event handlers
    +			handleObj = jQuery.extend({
    +				type: type,
    +				origType: origType,
    +				data: data,
    +				handler: handler,
    +				guid: handler.guid,
    +				selector: selector,
    +				needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
    +				namespace: namespaces.join(".")
    +			}, handleObjIn );
    +
    +			// Init the event handler queue if we're the first
    +			if ( !(handlers = events[ type ]) ) {
    +				handlers = events[ type ] = [];
    +				handlers.delegateCount = 0;
    +
    +				// Only use addEventListener/attachEvent if the special events handler returns false
    +				if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
    +					// Bind the global event handler to the element
    +					if ( elem.addEventListener ) {
    +						elem.addEventListener( type, eventHandle, false );
    +
    +					} else if ( elem.attachEvent ) {
    +						elem.attachEvent( "on" + type, eventHandle );
    +					}
    +				}
    +			}
    +
    +			if ( special.add ) {
    +				special.add.call( elem, handleObj );
    +
    +				if ( !handleObj.handler.guid ) {
    +					handleObj.handler.guid = handler.guid;
    +				}
    +			}
    +
    +			// Add to the element's handler list, delegates in front
    +			if ( selector ) {
    +				handlers.splice( handlers.delegateCount++, 0, handleObj );
    +			} else {
    +				handlers.push( handleObj );
    +			}
    +
    +			// Keep track of which events have ever been used, for event optimization
    +			jQuery.event.global[ type ] = true;
    +		}
    +
    +		// Nullify elem to prevent memory leaks in IE
    +		elem = null;
    +	},
    +
    +	// Detach an event or set of events from an element
    +	remove: function( elem, types, handler, selector, mappedTypes ) {
    +
    +		var j, origCount, tmp,
    +			events, t, handleObj,
    +			special, handlers, type, namespaces, origType,
    +			elemData = jQuery.hasData( elem ) && jQuery._data( elem );
    +
    +		if ( !elemData || !(events = elemData.events) ) {
    +			return;
    +		}
    +
    +		// Once for each type.namespace in types; type may be omitted
    +		types = ( types || "" ).match( core_rnotwhite ) || [""];
    +		t = types.length;
    +		while ( t-- ) {
    +			tmp = rtypenamespace.exec( types[t] ) || [];
    +			type = origType = tmp[1];
    +			namespaces = ( tmp[2] || "" ).split( "." ).sort();
    +
    +			// Unbind all events (on this namespace, if provided) for the element
    +			if ( !type ) {
    +				for ( type in events ) {
    +					jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
    +				}
    +				continue;
    +			}
    +
    +			special = jQuery.event.special[ type ] || {};
    +			type = ( selector ? special.delegateType : special.bindType ) || type;
    +			handlers = events[ type ] || [];
    +			tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" );
    +
    +			// Remove matching events
    +			origCount = j = handlers.length;
    +			while ( j-- ) {
    +				handleObj = handlers[ j ];
    +
    +				if ( ( mappedTypes || origType === handleObj.origType ) &&
    +					( !handler || handler.guid === handleObj.guid ) &&
    +					( !tmp || tmp.test( handleObj.namespace ) ) &&
    +					( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
    +					handlers.splice( j, 1 );
    +
    +					if ( handleObj.selector ) {
    +						handlers.delegateCount--;
    +					}
    +					if ( special.remove ) {
    +						special.remove.call( elem, handleObj );
    +					}
    +				}
    +			}
    +
    +			// Remove generic event handler if we removed something and no more handlers exist
    +			// (avoids potential for endless recursion during removal of special event handlers)
    +			if ( origCount && !handlers.length ) {
    +				if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
    +					jQuery.removeEvent( elem, type, elemData.handle );
    +				}
    +
    +				delete events[ type ];
    +			}
    +		}
    +
    +		// Remove the expando if it's no longer used
    +		if ( jQuery.isEmptyObject( events ) ) {
    +			delete elemData.handle;
    +
    +			// removeData also checks for emptiness and clears the expando if empty
    +			// so use it instead of delete
    +			jQuery._removeData( elem, "events" );
    +		}
    +	},
    +
    +	trigger: function( event, data, elem, onlyHandlers ) {
    +
    +		var i, cur, tmp, bubbleType, ontype, handle, special,
    +			eventPath = [ elem || document ],
    +			type = event.type || event,
    +			namespaces = event.namespace ? event.namespace.split(".") : [];
    +
    +		cur = tmp = elem = elem || document;
    +
    +		// Don't do events on text and comment nodes
    +		if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
    +			return;
    +		}
    +
    +		// focus/blur morphs to focusin/out; ensure we're not firing them right now
    +		if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
    +			return;
    +		}
    +
    +		if ( type.indexOf(".") >= 0 ) {
    +			// Namespaced trigger; create a regexp to match event type in handle()
    +			namespaces = type.split(".");
    +			type = namespaces.shift();
    +			namespaces.sort();
    +		}
    +		ontype = type.indexOf(":") < 0 && "on" + type;
    +
    +		// Caller can pass in a jQuery.Event object, Object, or just an event type string
    +		event = event[ jQuery.expando ] ?
    +			event :
    +			new jQuery.Event( type, typeof event === "object" && event );
    +
    +		event.isTrigger = true;
    +		event.namespace = namespaces.join(".");
    +		event.namespace_re = event.namespace ?
    +			new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) :
    +			null;
    +
    +		// Clean up the event in case it is being reused
    +		event.result = undefined;
    +		if ( !event.target ) {
    +			event.target = elem;
    +		}
    +
    +		// Clone any incoming data and prepend the event, creating the handler arg list
    +		data = data == null ?
    +			[ event ] :
    +			jQuery.makeArray( data, [ event ] );
    +
    +		// Allow special events to draw outside the lines
    +		special = jQuery.event.special[ type ] || {};
    +		if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
    +			return;
    +		}
    +
    +		// Determine event propagation path in advance, per W3C events spec (#9951)
    +		// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
    +		if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
    +
    +			bubbleType = special.delegateType || type;
    +			if ( !rfocusMorph.test( bubbleType + type ) ) {
    +				cur = cur.parentNode;
    +			}
    +			for ( ; cur; cur = cur.parentNode ) {
    +				eventPath.push( cur );
    +				tmp = cur;
    +			}
    +
    +			// Only add window if we got to document (e.g., not plain obj or detached DOM)
    +			if ( tmp === (elem.ownerDocument || document) ) {
    +				eventPath.push( tmp.defaultView || tmp.parentWindow || window );
    +			}
    +		}
    +
    +		// Fire handlers on the event path
    +		i = 0;
    +		while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {
    +
    +			event.type = i > 1 ?
    +				bubbleType :
    +				special.bindType || type;
    +
    +			// jQuery handler
    +			handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
    +			if ( handle ) {
    +				handle.apply( cur, data );
    +			}
    +
    +			// Native handler
    +			handle = ontype && cur[ ontype ];
    +			if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) {
    +				event.preventDefault();
    +			}
    +		}
    +		event.type = type;
    +
    +		// If nobody prevented the default action, do it now
    +		if ( !onlyHandlers && !event.isDefaultPrevented() ) {
    +
    +			if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) &&
    +				!(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) {
    +
    +				// Call a native DOM method on the target with the same name name as the event.
    +				// Can't use an .isFunction() check here because IE6/7 fails that test.
    +				// Don't do default actions on window, that's where global variables be (#6170)
    +				if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) {
    +
    +					// Don't re-trigger an onFOO event when we call its FOO() method
    +					tmp = elem[ ontype ];
    +
    +					if ( tmp ) {
    +						elem[ ontype ] = null;
    +					}
    +
    +					// Prevent re-triggering of the same event, since we already bubbled it above
    +					jQuery.event.triggered = type;
    +					try {
    +						elem[ type ]();
    +					} catch ( e ) {
    +						// IE<9 dies on focus/blur to hidden element (#1486,#12518)
    +						// only reproducible on winXP IE8 native, not IE9 in IE8 mode
    +					}
    +					jQuery.event.triggered = undefined;
    +
    +					if ( tmp ) {
    +						elem[ ontype ] = tmp;
    +					}
    +				}
    +			}
    +		}
    +
    +		return event.result;
    +	},
    +
    +	dispatch: function( event ) {
    +
    +		// Make a writable jQuery.Event from the native event object
    +		event = jQuery.event.fix( event );
    +
    +		var i, j, ret, matched, handleObj,
    +			handlerQueue = [],
    +			args = core_slice.call( arguments ),
    +			handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [],
    +			special = jQuery.event.special[ event.type ] || {};
    +
    +		// Use the fix-ed jQuery.Event rather than the (read-only) native event
    +		args[0] = event;
    +		event.delegateTarget = this;
    +
    +		// Call the preDispatch hook for the mapped type, and let it bail if desired
    +		if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
    +			return;
    +		}
    +
    +		// Determine handlers
    +		handlerQueue = jQuery.event.handlers.call( this, event, handlers );
    +
    +		// Run delegates first; they may want to stop propagation beneath us
    +		i = 0;
    +		while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {
    +			event.currentTarget = matched.elem;
    +
    +			j = 0;
    +			while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) {
    +
    +				// Triggered event must either 1) have no namespace, or
    +				// 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
    +				if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) {
    +
    +					event.handleObj = handleObj;
    +					event.data = handleObj.data;
    +
    +					ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
    +							.apply( matched.elem, args );
    +
    +					if ( ret !== undefined ) {
    +						if ( (event.result = ret) === false ) {
    +							event.preventDefault();
    +							event.stopPropagation();
    +						}
    +					}
    +				}
    +			}
    +		}
    +
    +		// Call the postDispatch hook for the mapped type
    +		if ( special.postDispatch ) {
    +			special.postDispatch.call( this, event );
    +		}
    +
    +		return event.result;
    +	},
    +
    +	handlers: function( event, handlers ) {
    +		var i, matches, sel, handleObj,
    +			handlerQueue = [],
    +			delegateCount = handlers.delegateCount,
    +			cur = event.target;
    +
    +		// Find delegate handlers
    +		// Black-hole SVG <use> instance trees (#13180)
    +		// Avoid non-left-click bubbling in Firefox (#3861)
    +		if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) {
    +
    +			for ( ; cur != this; cur = cur.parentNode || this ) {
    +
    +				// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
    +				if ( cur.disabled !== true || event.type !== "click" ) {
    +					matches = [];
    +					for ( i = 0; i < delegateCount; i++ ) {
    +						handleObj = handlers[ i ];
    +
    +						// Don't conflict with Object.prototype properties (#13203)
    +						sel = handleObj.selector + " ";
    +
    +						if ( matches[ sel ] === undefined ) {
    +							matches[ sel ] = handleObj.needsContext ?
    +								jQuery( sel, this ).index( cur ) >= 0 :
    +								jQuery.find( sel, this, null, [ cur ] ).length;
    +						}
    +						if ( matches[ sel ] ) {
    +							matches.push( handleObj );
    +						}
    +					}
    +					if ( matches.length ) {
    +						handlerQueue.push({ elem: cur, handlers: matches });
    +					}
    +				}
    +			}
    +		}
    +
    +		// Add the remaining (directly-bound) handlers
    +		if ( delegateCount < handlers.length ) {
    +			handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) });
    +		}
    +
    +		return handlerQueue;
    +	},
    +
    +	fix: function( event ) {
    +		if ( event[ jQuery.expando ] ) {
    +			return event;
    +		}
    +
    +		// Create a writable copy of the event object and normalize some properties
    +		var i, prop,
    +			originalEvent = event,
    +			fixHook = jQuery.event.fixHooks[ event.type ] || {},
    +			copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
    +
    +		event = new jQuery.Event( originalEvent );
    +
    +		i = copy.length;
    +		while ( i-- ) {
    +			prop = copy[ i ];
    +			event[ prop ] = originalEvent[ prop ];
    +		}
    +
    +		// Support: IE<9
    +		// Fix target property (#1925)
    +		if ( !event.target ) {
    +			event.target = originalEvent.srcElement || document;
    +		}
    +
    +		// Support: Chrome 23+, Safari?
    +		// Target should not be a text node (#504, #13143)
    +		if ( event.target.nodeType === 3 ) {
    +			event.target = event.target.parentNode;
    +		}
    +
    +		// Support: IE<9
    +		// For mouse/key events, metaKey==false if it's undefined (#3368, #11328)
    +		event.metaKey = !!event.metaKey;
    +
    +		return fixHook.filter ? fixHook.filter( event, originalEvent ) : event;
    +	},
    +
    +	// Includes some event props shared by KeyEvent and MouseEvent
    +	props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
    +
    +	fixHooks: {},
    +
    +	keyHooks: {
    +		props: "char charCode key keyCode".split(" "),
    +		filter: function( event, original ) {
    +
    +			// Add which for key events
    +			if ( event.which == null ) {
    +				event.which = original.charCode != null ? original.charCode : original.keyCode;
    +			}
    +
    +			return event;
    +		}
    +	},
    +
    +	mouseHooks: {
    +		props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
    +		filter: function( event, original ) {
    +			var eventDoc, doc, body,
    +				button = original.button,
    +				fromElement = original.fromElement;
    +
    +			// Calculate pageX/Y if missing and clientX/Y available
    +			if ( event.pageX == null && original.clientX != null ) {
    +				eventDoc = event.target.ownerDocument || document;
    +				doc = eventDoc.documentElement;
    +				body = eventDoc.body;
    +
    +				event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
    +				event.pageY = original.clientY + ( doc && doc.scrollTop  || body && body.scrollTop  || 0 ) - ( doc && doc.clientTop  || body && body.clientTop  || 0 );
    +			}
    +
    +			// Add relatedTarget, if necessary
    +			if ( !event.relatedTarget && fromElement ) {
    +				event.relatedTarget = fromElement === event.target ? original.toElement : fromElement;
    +			}
    +
    +			// Add which for click: 1 === left; 2 === middle; 3 === right
    +			// Note: button is not normalized, so don't use it
    +			if ( !event.which && button !== undefined ) {
    +				event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
    +			}
    +
    +			return event;
    +		}
    +	},
    +
    +	special: {
    +		load: {
    +			// Prevent triggered image.load events from bubbling to window.load
    +			noBubble: true
    +		},
    +		click: {
    +			// For checkbox, fire native event so checked state will be right
    +			trigger: function() {
    +				if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) {
    +					this.click();
    +					return false;
    +				}
    +			}
    +		},
    +		focus: {
    +			// Fire native event if possible so blur/focus sequence is correct
    +			trigger: function() {
    +				if ( this !== document.activeElement && this.focus ) {
    +					try {
    +						this.focus();
    +						return false;
    +					} catch ( e ) {
    +						// Support: IE<9
    +						// If we error on focus to hidden element (#1486, #12518),
    +						// let .trigger() run the handlers
    +					}
    +				}
    +			},
    +			delegateType: "focusin"
    +		},
    +		blur: {
    +			trigger: function() {
    +				if ( this === document.activeElement && this.blur ) {
    +					this.blur();
    +					return false;
    +				}
    +			},
    +			delegateType: "focusout"
    +		},
    +
    +		beforeunload: {
    +			postDispatch: function( event ) {
    +
    +				// Even when returnValue equals to undefined Firefox will still show alert
    +				if ( event.result !== undefined ) {
    +					event.originalEvent.returnValue = event.result;
    +				}
    +			}
    +		}
    +	},
    +
    +	simulate: function( type, elem, event, bubble ) {
    +		// Piggyback on a donor event to simulate a different one.
    +		// Fake originalEvent to avoid donor's stopPropagation, but if the
    +		// simulated event prevents default then we do the same on the donor.
    +		var e = jQuery.extend(
    +			new jQuery.Event(),
    +			event,
    +			{ type: type,
    +				isSimulated: true,
    +				originalEvent: {}
    +			}
    +		);
    +		if ( bubble ) {
    +			jQuery.event.trigger( e, null, elem );
    +		} else {
    +			jQuery.event.dispatch.call( elem, e );
    +		}
    +		if ( e.isDefaultPrevented() ) {
    +			event.preventDefault();
    +		}
    +	}
    +};
    +
    +jQuery.removeEvent = document.removeEventListener ?
    +	function( elem, type, handle ) {
    +		if ( elem.removeEventListener ) {
    +			elem.removeEventListener( type, handle, false );
    +		}
    +	} :
    +	function( elem, type, handle ) {
    +		var name = "on" + type;
    +
    +		if ( elem.detachEvent ) {
    +
    +			// #8545, #7054, preventing memory leaks for custom events in IE6-8
    +			// detachEvent needed property on element, by name of that event, to properly expose it to GC
    +			if ( typeof elem[ name ] === "undefined" ) {
    +				elem[ name ] = null;
    +			}
    +
    +			elem.detachEvent( name, handle );
    +		}
    +	};
    +
    +jQuery.Event = function( src, props ) {
    +	// Allow instantiation without the 'new' keyword
    +	if ( !(this instanceof jQuery.Event) ) {
    +		return new jQuery.Event( src, props );
    +	}
    +
    +	// Event object
    +	if ( src && src.type ) {
    +		this.originalEvent = src;
    +		this.type = src.type;
    +
    +		// Events bubbling up the document may have been marked as prevented
    +		// by a handler lower down the tree; reflect the correct value.
    +		this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false ||
    +			src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse;
    +
    +	// Event type
    +	} else {
    +		this.type = src;
    +	}
    +
    +	// Put explicitly provided properties onto the event object
    +	if ( props ) {
    +		jQuery.extend( this, props );
    +	}
    +
    +	// Create a timestamp if incoming event doesn't have one
    +	this.timeStamp = src && src.timeStamp || jQuery.now();
    +
    +	// Mark it as fixed
    +	this[ jQuery.expando ] = true;
    +};
    +
    +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
    +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
    +jQuery.Event.prototype = {
    +	isDefaultPrevented: returnFalse,
    +	isPropagationStopped: returnFalse,
    +	isImmediatePropagationStopped: returnFalse,
    +
    +	preventDefault: function() {
    +		var e = this.originalEvent;
    +
    +		this.isDefaultPrevented = returnTrue;
    +		if ( !e ) {
    +			return;
    +		}
    +
    +		// If preventDefault exists, run it on the original event
    +		if ( e.preventDefault ) {
    +			e.preventDefault();
    +
    +		// Support: IE
    +		// Otherwise set the returnValue property of the original event to false
    +		} else {
    +			e.returnValue = false;
    +		}
    +	},
    +	stopPropagation: function() {
    +		var e = this.originalEvent;
    +
    +		this.isPropagationStopped = returnTrue;
    +		if ( !e ) {
    +			return;
    +		}
    +		// If stopPropagation exists, run it on the original event
    +		if ( e.stopPropagation ) {
    +			e.stopPropagation();
    +		}
    +
    +		// Support: IE
    +		// Set the cancelBubble property of the original event to true
    +		e.cancelBubble = true;
    +	},
    +	stopImmediatePropagation: function() {
    +		this.isImmediatePropagationStopped = returnTrue;
    +		this.stopPropagation();
    +	}
    +};
    +
    +// Create mouseenter/leave events using mouseover/out and event-time checks
    +jQuery.each({
    +	mouseenter: "mouseover",
    +	mouseleave: "mouseout"
    +}, function( orig, fix ) {
    +	jQuery.event.special[ orig ] = {
    +		delegateType: fix,
    +		bindType: fix,
    +
    +		handle: function( event ) {
    +			var ret,
    +				target = this,
    +				related = event.relatedTarget,
    +				handleObj = event.handleObj;
    +
    +			// For mousenter/leave call the handler if related is outside the target.
    +			// NB: No relatedTarget if the mouse left/entered the browser window
    +			if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
    +				event.type = handleObj.origType;
    +				ret = handleObj.handler.apply( this, arguments );
    +				event.type = fix;
    +			}
    +			return ret;
    +		}
    +	};
    +});
    +
    +// IE submit delegation
    +if ( !jQuery.support.submitBubbles ) {
    +
    +	jQuery.event.special.submit = {
    +		setup: function() {
    +			// Only need this for delegated form submit events
    +			if ( jQuery.nodeName( this, "form" ) ) {
    +				return false;
    +			}
    +
    +			// Lazy-add a submit handler when a descendant form may potentially be submitted
    +			jQuery.event.add( this, "click._submit keypress._submit", function( e ) {
    +				// Node name check avoids a VML-related crash in IE (#9807)
    +				var elem = e.target,
    +					form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined;
    +				if ( form && !jQuery._data( form, "submitBubbles" ) ) {
    +					jQuery.event.add( form, "submit._submit", function( event ) {
    +						event._submit_bubble = true;
    +					});
    +					jQuery._data( form, "submitBubbles", true );
    +				}
    +			});
    +			// return undefined since we don't need an event listener
    +		},
    +
    +		postDispatch: function( event ) {
    +			// If form was submitted by the user, bubble the event up the tree
    +			if ( event._submit_bubble ) {
    +				delete event._submit_bubble;
    +				if ( this.parentNode && !event.isTrigger ) {
    +					jQuery.event.simulate( "submit", this.parentNode, event, true );
    +				}
    +			}
    +		},
    +
    +		teardown: function() {
    +			// Only need this for delegated form submit events
    +			if ( jQuery.nodeName( this, "form" ) ) {
    +				return false;
    +			}
    +
    +			// Remove delegated handlers; cleanData eventually reaps submit handlers attached above
    +			jQuery.event.remove( this, "._submit" );
    +		}
    +	};
    +}
    +
    +// IE change delegation and checkbox/radio fix
    +if ( !jQuery.support.changeBubbles ) {
    +
    +	jQuery.event.special.change = {
    +
    +		setup: function() {
    +
    +			if ( rformElems.test( this.nodeName ) ) {
    +				// IE doesn't fire change on a check/radio until blur; trigger it on click
    +				// after a propertychange. Eat the blur-change in special.change.handle.
    +				// This still fires onchange a second time for check/radio after blur.
    +				if ( this.type === "checkbox" || this.type === "radio" ) {
    +					jQuery.event.add( this, "propertychange._change", function( event ) {
    +						if ( event.originalEvent.propertyName === "checked" ) {
    +							this._just_changed = true;
    +						}
    +					});
    +					jQuery.event.add( this, "click._change", function( event ) {
    +						if ( this._just_changed && !event.isTrigger ) {
    +							this._just_changed = false;
    +						}
    +						// Allow triggered, simulated change events (#11500)
    +						jQuery.event.simulate( "change", this, event, true );
    +					});
    +				}
    +				return false;
    +			}
    +			// Delegated event; lazy-add a change handler on descendant inputs
    +			jQuery.event.add( this, "beforeactivate._change", function( e ) {
    +				var elem = e.target;
    +
    +				if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "changeBubbles" ) ) {
    +					jQuery.event.add( elem, "change._change", function( event ) {
    +						if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
    +							jQuery.event.simulate( "change", this.parentNode, event, true );
    +						}
    +					});
    +					jQuery._data( elem, "changeBubbles", true );
    +				}
    +			});
    +		},
    +
    +		handle: function( event ) {
    +			var elem = event.target;
    +
    +			// Swallow native change events from checkbox/radio, we already triggered them above
    +			if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) {
    +				return event.handleObj.handler.apply( this, arguments );
    +			}
    +		},
    +
    +		teardown: function() {
    +			jQuery.event.remove( this, "._change" );
    +
    +			return !rformElems.test( this.nodeName );
    +		}
    +	};
    +}
    +
    +// Create "bubbling" focus and blur events
    +if ( !jQuery.support.focusinBubbles ) {
    +	jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
    +
    +		// Attach a single capturing handler while someone wants focusin/focusout
    +		var attaches = 0,
    +			handler = function( event ) {
    +				jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
    +			};
    +
    +		jQuery.event.special[ fix ] = {
    +			setup: function() {
    +				if ( attaches++ === 0 ) {
    +					document.addEventListener( orig, handler, true );
    +				}
    +			},
    +			teardown: function() {
    +				if ( --attaches === 0 ) {
    +					document.removeEventListener( orig, handler, true );
    +				}
    +			}
    +		};
    +	});
    +}
    +
    +jQuery.fn.extend({
    +
    +	on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
    +		var origFn, type;
    +
    +		// Types can be a map of types/handlers
    +		if ( typeof types === "object" ) {
    +			// ( types-Object, selector, data )
    +			if ( typeof selector !== "string" ) {
    +				// ( types-Object, data )
    +				data = data || selector;
    +				selector = undefined;
    +			}
    +			for ( type in types ) {
    +				this.on( type, selector, data, types[ type ], one );
    +			}
    +			return this;
    +		}
    +
    +		if ( data == null && fn == null ) {
    +			// ( types, fn )
    +			fn = selector;
    +			data = selector = undefined;
    +		} else if ( fn == null ) {
    +			if ( typeof selector === "string" ) {
    +				// ( types, selector, fn )
    +				fn = data;
    +				data = undefined;
    +			} else {
    +				// ( types, data, fn )
    +				fn = data;
    +				data = selector;
    +				selector = undefined;
    +			}
    +		}
    +		if ( fn === false ) {
    +			fn = returnFalse;
    +		} else if ( !fn ) {
    +			return this;
    +		}
    +
    +		if ( one === 1 ) {
    +			origFn = fn;
    +			fn = function( event ) {
    +				// Can use an empty set, since event contains the info
    +				jQuery().off( event );
    +				return origFn.apply( this, arguments );
    +			};
    +			// Use same guid so caller can remove using origFn
    +			fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
    +		}
    +		return this.each( function() {
    +			jQuery.event.add( this, types, fn, data, selector );
    +		});
    +	},
    +	one: function( types, selector, data, fn ) {
    +		return this.on( types, selector, data, fn, 1 );
    +	},
    +	off: function( types, selector, fn ) {
    +		var handleObj, type;
    +		if ( types && types.preventDefault && types.handleObj ) {
    +			// ( event )  dispatched jQuery.Event
    +			handleObj = types.handleObj;
    +			jQuery( types.delegateTarget ).off(
    +				handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
    +				handleObj.selector,
    +				handleObj.handler
    +			);
    +			return this;
    +		}
    +		if ( typeof types === "object" ) {
    +			// ( types-object [, selector] )
    +			for ( type in types ) {
    +				this.off( type, selector, types[ type ] );
    +			}
    +			return this;
    +		}
    +		if ( selector === false || typeof selector === "function" ) {
    +			// ( types [, fn] )
    +			fn = selector;
    +			selector = undefined;
    +		}
    +		if ( fn === false ) {
    +			fn = returnFalse;
    +		}
    +		return this.each(function() {
    +			jQuery.event.remove( this, types, fn, selector );
    +		});
    +	},
    +
    +	bind: function( types, data, fn ) {
    +		return this.on( types, null, data, fn );
    +	},
    +	unbind: function( types, fn ) {
    +		return this.off( types, null, fn );
    +	},
    +
    +	delegate: function( selector, types, data, fn ) {
    +		return this.on( types, selector, data, fn );
    +	},
    +	undelegate: function( selector, types, fn ) {
    +		// ( namespace ) or ( selector, types [, fn] )
    +		return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn );
    +	},
    +
    +	trigger: function( type, data ) {
    +		return this.each(function() {
    +			jQuery.event.trigger( type, data, this );
    +		});
    +	},
    +	triggerHandler: function( type, data ) {
    +		var elem = this[0];
    +		if ( elem ) {
    +			return jQuery.event.trigger( type, data, elem, true );
    +		}
    +	},
    +
    +	hover: function( fnOver, fnOut ) {
    +		return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
    +	}
    +});
    +
    +jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
    +	"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
    +	"change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {
    +
    +	// Handle event binding
    +	jQuery.fn[ name ] = function( data, fn ) {
    +		return arguments.length > 0 ?
    +			this.on( name, null, data, fn ) :
    +			this.trigger( name );
    +	};
    +
    +	if ( rkeyEvent.test( name ) ) {
    +		jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks;
    +	}
    +
    +	if ( rmouseEvent.test( name ) ) {
    +		jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks;
    +	}
    +});
    +/*!
    + * Sizzle CSS Selector Engine
    + * Copyright 2012 jQuery Foundation and other contributors
    + * Released under the MIT license
    + * http://sizzlejs.com/
    + */
    +(function( window, undefined ) {
    +
    +var i,
    +	cachedruns,
    +	Expr,
    +	getText,
    +	isXML,
    +	compile,
    +	hasDuplicate,
    +	outermostContext,
    +
    +	// Local document vars
    +	setDocument,
    +	document,
    +	docElem,
    +	documentIsXML,
    +	rbuggyQSA,
    +	rbuggyMatches,
    +	matches,
    +	contains,
    +	sortOrder,
    +
    +	// Instance-specific data
    +	expando = "sizzle" + -(new Date()),
    +	preferredDoc = window.document,
    +	support = {},
    +	dirruns = 0,
    +	done = 0,
    +	classCache = createCache(),
    +	tokenCache = createCache(),
    +	compilerCache = createCache(),
    +
    +	// General-purpose constants
    +	strundefined = typeof undefined,
    +	MAX_NEGATIVE = 1 << 31,
    +
    +	// Array methods
    +	arr = [],
    +	pop = arr.pop,
    +	push = arr.push,
    +	slice = arr.slice,
    +	// Use a stripped-down indexOf if we can't use a native one
    +	indexOf = arr.indexOf || function( elem ) {
    +		var i = 0,
    +			len = this.length;
    +		for ( ; i < len; i++ ) {
    +			if ( this[i] === elem ) {
    +				return i;
    +			}
    +		}
    +		return -1;
    +	},
    +
    +
    +	// Regular expressions
    +
    +	// Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace
    +	whitespace = "[\\x20\\t\\r\\n\\f]",
    +	// http://www.w3.org/TR/css3-syntax/#characters
    +	characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",
    +
    +	// Loosely modeled on CSS identifier characters
    +	// An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors
    +	// Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
    +	identifier = characterEncoding.replace( "w", "w#" ),
    +
    +	// Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors
    +	operators = "([*^$|!~]?=)",
    +	attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace +
    +		"*(?:" + operators + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]",
    +
    +	// Prefer arguments quoted,
    +	//   then not containing pseudos/brackets,
    +	//   then attribute selectors/non-parenthetical expressions,
    +	//   then anything else
    +	// These preferences are here to reduce the number of selectors
    +	//   needing tokenize in the PSEUDO preFilter
    +	pseudos = ":(" + characterEncoding + ")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|" + attributes.replace( 3, 8 ) + ")*)|.*)\\)|)",
    +
    +	// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
    +	rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
    +
    +	rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
    +	rcombinators = new RegExp( "^" + whitespace + "*([\\x20\\t\\r\\n\\f>+~])" + whitespace + "*" ),
    +	rpseudo = new RegExp( pseudos ),
    +	ridentifier = new RegExp( "^" + identifier + "$" ),
    +
    +	matchExpr = {
    +		"ID": new RegExp( "^#(" + characterEncoding + ")" ),
    +		"CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ),
    +		"NAME": new RegExp( "^\\[name=['\"]?(" + characterEncoding + ")['\"]?\\]" ),
    +		"TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ),
    +		"ATTR": new RegExp( "^" + attributes ),
    +		"PSEUDO": new RegExp( "^" + pseudos ),
    +		"CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
    +			"*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
    +			"*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
    +		// For use in libraries implementing .is()
    +		// We use this for POS matching in `select`
    +		"needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
    +			whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
    +	},
    +
    +	rsibling = /[\x20\t\r\n\f]*[+~]/,
    +
    +	rnative = /\{\s*\[native code\]\s*\}/,
    +
    +	// Easily-parseable/retrievable ID or TAG or CLASS selectors
    +	rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
    +
    +	rinputs = /^(?:input|select|textarea|button)$/i,
    +	rheader = /^h\d$/i,
    +
    +	rescape = /'|\\/g,
    +	rattributeQuotes = /\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,
    +
    +	// CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
    +	runescape = /\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g,
    +	funescape = function( _, escaped ) {
    +		var high = "0x" + escaped - 0x10000;
    +		// NaN means non-codepoint
    +		return high !== high ?
    +			escaped :
    +			// BMP codepoint
    +			high < 0 ?
    +				String.fromCharCode( high + 0x10000 ) :
    +				// Supplemental Plane codepoint (surrogate pair)
    +				String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
    +	};
    +
    +// Use a stripped-down slice if we can't use a native one
    +try {
    +	slice.call( docElem.childNodes, 0 )[0].nodeType;
    +} catch ( e ) {
    +	slice = function( i ) {
    +		var elem,
    +			results = [];
    +		for ( ; (elem = this[i]); i++ ) {
    +			results.push( elem );
    +		}
    +		return results;
    +	};
    +}
    +
    +/**
    + * For feature detection
    + * @param {Function} fn The function to test for native support
    + */
    +function isNative( fn ) {
    +	return rnative.test( fn + "" );
    +}
    +
    +/**
    + * Create key-value caches of limited size
    + * @returns {Function(string, Object)} Returns the Object data after storing it on itself with
    + *	property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
    + *	deleting the oldest entry
    + */
    +function createCache() {
    +	var cache,
    +		keys = [];
    +
    +	return (cache = function( key, value ) {
    +		// Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
    +		if ( keys.push( key += " " ) > Expr.cacheLength ) {
    +			// Only keep the most recent entries
    +			delete cache[ keys.shift() ];
    +		}
    +		return (cache[ key ] = value);
    +	});
    +}
    +
    +/**
    + * Mark a function for special use by Sizzle
    + * @param {Function} fn The function to mark
    + */
    +function markFunction( fn ) {
    +	fn[ expando ] = true;
    +	return fn;
    +}
    +
    +/**
    + * Support testing using an element
    + * @param {Function} fn Passed the created div and expects a boolean result
    + */
    +function assert( fn ) {
    +	var div = document.createElement("div");
    +
    +	try {
    +		return fn( div );
    +	} catch (e) {
    +		return false;
    +	} finally {
    +		// release memory in IE
    +		div = null;
    +	}
    +}
    +
    +function Sizzle( selector, context, results, seed ) {
    +	var match, elem, m, nodeType,
    +		// QSA vars
    +		i, groups, old, nid, newContext, newSelector;
    +
    +	if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
    +		setDocument( context );
    +	}
    +
    +	context = context || document;
    +	results = results || [];
    +
    +	if ( !selector || typeof selector !== "string" ) {
    +		return results;
    +	}
    +
    +	if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) {
    +		return [];
    +	}
    +
    +	if ( !documentIsXML && !seed ) {
    +
    +		// Shortcuts
    +		if ( (match = rquickExpr.exec( selector )) ) {
    +			// Speed-up: Sizzle("#ID")
    +			if ( (m = match[1]) ) {
    +				if ( nodeType === 9 ) {
    +					elem = context.getElementById( m );
    +					// Check parentNode to catch when Blackberry 4.6 returns
    +					// nodes that are no longer in the document #6963
    +					if ( elem && elem.parentNode ) {
    +						// Handle the case where IE, Opera, and Webkit return items
    +						// by name instead of ID
    +						if ( elem.id === m ) {
    +							results.push( elem );
    +							return results;
    +						}
    +					} else {
    +						return results;
    +					}
    +				} else {
    +					// Context is not a document
    +					if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
    +						contains( context, elem ) && elem.id === m ) {
    +						results.push( elem );
    +						return results;
    +					}
    +				}
    +
    +			// Speed-up: Sizzle("TAG")
    +			} else if ( match[2] ) {
    +				push.apply( results, slice.call(context.getElementsByTagName( selector ), 0) );
    +				return results;
    +
    +			// Speed-up: Sizzle(".CLASS")
    +			} else if ( (m = match[3]) && support.getByClassName && context.getElementsByClassName ) {
    +				push.apply( results, slice.call(context.getElementsByClassName( m ), 0) );
    +				return results;
    +			}
    +		}
    +
    +		// QSA path
    +		if ( support.qsa && !rbuggyQSA.test(selector) ) {
    +			old = true;
    +			nid = expando;
    +			newContext = context;
    +			newSelector = nodeType === 9 && selector;
    +
    +			// qSA works strangely on Element-rooted queries
    +			// We can work around this by specifying an extra ID on the root
    +			// and working up from there (Thanks to Andrew Dupont for the technique)
    +			// IE 8 doesn't work on object elements
    +			if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
    +				groups = tokenize( selector );
    +
    +				if ( (old = context.getAttribute("id")) ) {
    +					nid = old.replace( rescape, "\\$&" );
    +				} else {
    +					context.setAttribute( "id", nid );
    +				}
    +				nid = "[id='" + nid + "'] ";
    +
    +				i = groups.length;
    +				while ( i-- ) {
    +					groups[i] = nid + toSelector( groups[i] );
    +				}
    +				newContext = rsibling.test( selector ) && context.parentNode || context;
    +				newSelector = groups.join(",");
    +			}
    +
    +			if ( newSelector ) {
    +				try {
    +					push.apply( results, slice.call( newContext.querySelectorAll(
    +						newSelector
    +					), 0 ) );
    +					return results;
    +				} catch(qsaError) {
    +				} finally {
    +					if ( !old ) {
    +						context.removeAttribute("id");
    +					}
    +				}
    +			}
    +		}
    +	}
    +
    +	// All others
    +	return select( selector.replace( rtrim, "$1" ), context, results, seed );
    +}
    +
    +/**
    + * Detect xml
    + * @param {Element|Object} elem An element or a document
    + */
    +isXML = Sizzle.isXML = function( elem ) {
    +	// documentElement is verified for cases where it doesn't yet exist
    +	// (such as loading iframes in IE - #4833)
    +	var documentElement = elem && (elem.ownerDocument || elem).documentElement;
    +	return documentElement ? documentElement.nodeName !== "HTML" : false;
    +};
    +
    +/**
    + * Sets document-related variables once based on the current document
    + * @param {Element|Object} [doc] An element or document object to use to set the document
    + * @returns {Object} Returns the current document
    + */
    +setDocument = Sizzle.setDocument = function( node ) {
    +	var doc = node ? node.ownerDocument || node : preferredDoc;
    +
    +	// If no document and documentElement is available, return
    +	if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
    +		return document;
    +	}
    +
    +	// Set our document
    +	document = doc;
    +	docElem = doc.documentElement;
    +
    +	// Support tests
    +	documentIsXML = isXML( doc );
    +
    +	// Check if getElementsByTagName("*") returns only elements
    +	support.tagNameNoComments = assert(function( div ) {
    +		div.appendChild( doc.createComment("") );
    +		return !div.getElementsByTagName("*").length;
    +	});
    +
    +	// Check if attributes should be retrieved by attribute nodes
    +	support.attributes = assert(function( div ) {
    +		div.innerHTML = "<select></select>";
    +		var type = typeof div.lastChild.getAttribute("multiple");
    +		// IE8 returns a string for some attributes even when not present
    +		return type !== "boolean" && type !== "string";
    +	});
    +
    +	// Check if getElementsByClassName can be trusted
    +	support.getByClassName = assert(function( div ) {
    +		// Opera can't find a second classname (in 9.6)
    +		div.innerHTML = "<div class='hidden e'></div><div class='hidden'></div>";
    +		if ( !div.getElementsByClassName || !div.getElementsByClassName("e").length ) {
    +			return false;
    +		}
    +
    +		// Safari 3.2 caches class attributes and doesn't catch changes
    +		div.lastChild.className = "e";
    +		return div.getElementsByClassName("e").length === 2;
    +	});
    +
    +	// Check if getElementById returns elements by name
    +	// Check if getElementsByName privileges form controls or returns elements by ID
    +	support.getByName = assert(function( div ) {
    +		// Inject content
    +		div.id = expando + 0;
    +		div.innerHTML = "<a name='" + expando + "'></a><div name='" + expando + "'></div>";
    +		docElem.insertBefore( div, docElem.firstChild );
    +
    +		// Test
    +		var pass = doc.getElementsByName &&
    +			// buggy browsers will return fewer than the correct 2
    +			doc.getElementsByName( expando ).length === 2 +
    +			// buggy browsers will return more than the correct 0
    +			doc.getElementsByName( expando + 0 ).length;
    +		support.getIdNotName = !doc.getElementById( expando );
    +
    +		// Cleanup
    +		docElem.removeChild( div );
    +
    +		return pass;
    +	});
    +
    +	// IE6/7 return modified attributes
    +	Expr.attrHandle = assert(function( div ) {
    +		div.innerHTML = "<a href='#'></a>";
    +		return div.firstChild && typeof div.firstChild.getAttribute !== strundefined &&
    +			div.firstChild.getAttribute("href") === "#";
    +	}) ?
    +		{} :
    +		{
    +			"href": function( elem ) {
    +				return elem.getAttribute( "href", 2 );
    +			},
    +			"type": function( elem ) {
    +				return elem.getAttribute("type");
    +			}
    +		};
    +
    +	// ID find and filter
    +	if ( support.getIdNotName ) {
    +		Expr.find["ID"] = function( id, context ) {
    +			if ( typeof context.getElementById !== strundefined && !documentIsXML ) {
    +				var m = context.getElementById( id );
    +				// Check parentNode to catch when Blackberry 4.6 returns
    +				// nodes that are no longer in the document #6963
    +				return m && m.parentNode ? [m] : [];
    +			}
    +		};
    +		Expr.filter["ID"] = function( id ) {
    +			var attrId = id.replace( runescape, funescape );
    +			return function( elem ) {
    +				return elem.getAttribute("id") === attrId;
    +			};
    +		};
    +	} else {
    +		Expr.find["ID"] = function( id, context ) {
    +			if ( typeof context.getElementById !== strundefined && !documentIsXML ) {
    +				var m = context.getElementById( id );
    +
    +				return m ?
    +					m.id === id || typeof m.getAttributeNode !== strundefined && m.getAttributeNode("id").value === id ?
    +						[m] :
    +						undefined :
    +					[];
    +			}
    +		};
    +		Expr.filter["ID"] =  function( id ) {
    +			var attrId = id.replace( runescape, funescape );
    +			return function( elem ) {
    +				var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id");
    +				return node && node.value === attrId;
    +			};
    +		};
    +	}
    +
    +	// Tag
    +	Expr.find["TAG"] = support.tagNameNoComments ?
    +		function( tag, context ) {
    +			if ( typeof context.getElementsByTagName !== strundefined ) {
    +				return context.getElementsByTagName( tag );
    +			}
    +		} :
    +		function( tag, context ) {
    +			var elem,
    +				tmp = [],
    +				i = 0,
    +				results = context.getElementsByTagName( tag );
    +
    +			// Filter out possible comments
    +			if ( tag === "*" ) {
    +				for ( ; (elem = results[i]); i++ ) {
    +					if ( elem.nodeType === 1 ) {
    +						tmp.push( elem );
    +					}
    +				}
    +
    +				return tmp;
    +			}
    +			return results;
    +		};
    +
    +	// Name
    +	Expr.find["NAME"] = support.getByName && function( tag, context ) {
    +		if ( typeof context.getElementsByName !== strundefined ) {
    +			return context.getElementsByName( name );
    +		}
    +	};
    +
    +	// Class
    +	Expr.find["CLASS"] = support.getByClassName && function( className, context ) {
    +		if ( typeof context.getElementsByClassName !== strundefined && !documentIsXML ) {
    +			return context.getElementsByClassName( className );
    +		}
    +	};
    +
    +	// QSA and matchesSelector support
    +
    +	// matchesSelector(:active) reports false when true (IE9/Opera 11.5)
    +	rbuggyMatches = [];
    +
    +	// qSa(:focus) reports false when true (Chrome 21),
    +	// no need to also add to buggyMatches since matches checks buggyQSA
    +	// A support test would require too much code (would include document ready)
    +	rbuggyQSA = [ ":focus" ];
    +
    +	if ( (support.qsa = isNative(doc.querySelectorAll)) ) {
    +		// Build QSA regex
    +		// Regex strategy adopted from Diego Perini
    +		assert(function( div ) {
    +			// Select is set to empty string on purpose
    +			// This is to test IE's treatment of not explictly
    +			// setting a boolean content attribute,
    +			// since its presence should be enough
    +			// http://bugs.jquery.com/ticket/12359
    +			div.innerHTML = "<select><option selected=''></option></select>";
    +
    +			// IE8 - Some boolean attributes are not treated correctly
    +			if ( !div.querySelectorAll("[selected]").length ) {
    +				rbuggyQSA.push( "\\[" + whitespace + "*(?:checked|disabled|ismap|multiple|readonly|selected|value)" );
    +			}
    +
    +			// Webkit/Opera - :checked should return selected option elements
    +			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
    +			// IE8 throws error here and will not see later tests
    +			if ( !div.querySelectorAll(":checked").length ) {
    +				rbuggyQSA.push(":checked");
    +			}
    +		});
    +
    +		assert(function( div ) {
    +
    +			// Opera 10-12/IE8 - ^= $= *= and empty values
    +			// Should not select anything
    +			div.innerHTML = "<input type='hidden' i=''/>";
    +			if ( div.querySelectorAll("[i^='']").length ) {
    +				rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:\"\"|'')" );
    +			}
    +
    +			// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
    +			// IE8 throws error here and will not see later tests
    +			if ( !div.querySelectorAll(":enabled").length ) {
    +				rbuggyQSA.push( ":enabled", ":disabled" );
    +			}
    +
    +			// Opera 10-11 does not throw on post-comma invalid pseudos
    +			div.querySelectorAll("*,:x");
    +			rbuggyQSA.push(",.*:");
    +		});
    +	}
    +
    +	if ( (support.matchesSelector = isNative( (matches = docElem.matchesSelector ||
    +		docElem.mozMatchesSelector ||
    +		docElem.webkitMatchesSelector ||
    +		docElem.oMatchesSelector ||
    +		docElem.msMatchesSelector) )) ) {
    +
    +		assert(function( div ) {
    +			// Check to see if it's possible to do matchesSelector
    +			// on a disconnected node (IE 9)
    +			support.disconnectedMatch = matches.call( div, "div" );
    +
    +			// This should fail with an exception
    +			// Gecko does not error, returns false instead
    +			matches.call( div, "[s!='']:x" );
    +			rbuggyMatches.push( "!=", pseudos );
    +		});
    +	}
    +
    +	rbuggyQSA = new RegExp( rbuggyQSA.join("|") );
    +	rbuggyMatches = new RegExp( rbuggyMatches.join("|") );
    +
    +	// Element contains another
    +	// Purposefully does not implement inclusive descendent
    +	// As in, an element does not contain itself
    +	contains = isNative(docElem.contains) || docElem.compareDocumentPosition ?
    +		function( a, b ) {
    +			var adown = a.nodeType === 9 ? a.documentElement : a,
    +				bup = b && b.parentNode;
    +			return a === bup || !!( bup && bup.nodeType === 1 && (
    +				adown.contains ?
    +					adown.contains( bup ) :
    +					a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
    +			));
    +		} :
    +		function( a, b ) {
    +			if ( b ) {
    +				while ( (b = b.parentNode) ) {
    +					if ( b === a ) {
    +						return true;
    +					}
    +				}
    +			}
    +			return false;
    +		};
    +
    +	// Document order sorting
    +	sortOrder = docElem.compareDocumentPosition ?
    +	function( a, b ) {
    +		var compare;
    +
    +		if ( a === b ) {
    +			hasDuplicate = true;
    +			return 0;
    +		}
    +
    +		if ( (compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b )) ) {
    +			if ( compare & 1 || a.parentNode && a.parentNode.nodeType === 11 ) {
    +				if ( a === doc || contains( preferredDoc, a ) ) {
    +					return -1;
    +				}
    +				if ( b === doc || contains( preferredDoc, b ) ) {
    +					return 1;
    +				}
    +				return 0;
    +			}
    +			return compare & 4 ? -1 : 1;
    +		}
    +
    +		return a.compareDocumentPosition ? -1 : 1;
    +	} :
    +	function( a, b ) {
    +		var cur,
    +			i = 0,
    +			aup = a.parentNode,
    +			bup = b.parentNode,
    +			ap = [ a ],
    +			bp = [ b ];
    +
    +		// The nodes are identical, we can exit early
    +		if ( a === b ) {
    +			hasDuplicate = true;
    +			return 0;
    +
    +		// Fallback to using sourceIndex (in IE) if it's available on both nodes
    +		} else if ( a.sourceIndex && b.sourceIndex ) {
    +			return ( ~b.sourceIndex || MAX_NEGATIVE ) - ( contains( preferredDoc, a ) && ~a.sourceIndex || MAX_NEGATIVE );
    +
    +		// Parentless nodes are either documents or disconnected
    +		} else if ( !aup || !bup ) {
    +			return a === doc ? -1 :
    +				b === doc ? 1 :
    +				aup ? -1 :
    +				bup ? 1 :
    +				0;
    +
    +		// If the nodes are siblings, we can do a quick check
    +		} else if ( aup === bup ) {
    +			return siblingCheck( a, b );
    +		}
    +
    +		// Otherwise we need full lists of their ancestors for comparison
    +		cur = a;
    +		while ( (cur = cur.parentNode) ) {
    +			ap.unshift( cur );
    +		}
    +		cur = b;
    +		while ( (cur = cur.parentNode) ) {
    +			bp.unshift( cur );
    +		}
    +
    +		// Walk down the tree looking for a discrepancy
    +		while ( ap[i] === bp[i] ) {
    +			i++;
    +		}
    +
    +		return i ?
    +			// Do a sibling check if the nodes have a common ancestor
    +			siblingCheck( ap[i], bp[i] ) :
    +
    +			// Otherwise nodes in our document sort first
    +			ap[i] === preferredDoc ? -1 :
    +			bp[i] === preferredDoc ? 1 :
    +			0;
    +	};
    +
    +	// Always assume the presence of duplicates if sort doesn't
    +	// pass them to our comparison function (as in Google Chrome).
    +	hasDuplicate = false;
    +	[0, 0].sort( sortOrder );
    +	support.detectDuplicates = hasDuplicate;
    +
    +	return document;
    +};
    +
    +Sizzle.matches = function( expr, elements ) {
    +	return Sizzle( expr, null, null, elements );
    +};
    +
    +Sizzle.matchesSelector = function( elem, expr ) {
    +	// Set document vars if needed
    +	if ( ( elem.ownerDocument || elem ) !== document ) {
    +		setDocument( elem );
    +	}
    +
    +	// Make sure that attribute selectors are quoted
    +	expr = expr.replace( rattributeQuotes, "='$1']" );
    +
    +	// rbuggyQSA always contains :focus, so no need for an existence check
    +	if ( support.matchesSelector && !documentIsXML && (!rbuggyMatches || !rbuggyMatches.test(expr)) && !rbuggyQSA.test(expr) ) {
    +		try {
    +			var ret = matches.call( elem, expr );
    +
    +			// IE 9's matchesSelector returns false on disconnected nodes
    +			if ( ret || support.disconnectedMatch ||
    +					// As well, disconnected nodes are said to be in a document
    +					// fragment in IE 9
    +					elem.document && elem.document.nodeType !== 11 ) {
    +				return ret;
    +			}
    +		} catch(e) {}
    +	}
    +
    +	return Sizzle( expr, document, null, [elem] ).length > 0;
    +};
    +
    +Sizzle.contains = function( context, elem ) {
    +	// Set document vars if needed
    +	if ( ( context.ownerDocument || context ) !== document ) {
    +		setDocument( context );
    +	}
    +	return contains( context, elem );
    +};
    +
    +Sizzle.attr = function( elem, name ) {
    +	var val;
    +
    +	// Set document vars if needed
    +	if ( ( elem.ownerDocument || elem ) !== document ) {
    +		setDocument( elem );
    +	}
    +
    +	if ( !documentIsXML ) {
    +		name = name.toLowerCase();
    +	}
    +	if ( (val = Expr.attrHandle[ name ]) ) {
    +		return val( elem );
    +	}
    +	if ( documentIsXML || support.attributes ) {
    +		return elem.getAttribute( name );
    +	}
    +	return ( (val = elem.getAttributeNode( name )) || elem.getAttribute( name ) ) && elem[ name ] === true ?
    +		name :
    +		val && val.specified ? val.value : null;
    +};
    +
    +Sizzle.error = function( msg ) {
    +	throw new Error( "Syntax error, unrecognized expression: " + msg );
    +};
    +
    +// Document sorting and removing duplicates
    +Sizzle.uniqueSort = function( results ) {
    +	var elem,
    +		duplicates = [],
    +		i = 1,
    +		j = 0;
    +
    +	// Unless we *know* we can detect duplicates, assume their presence
    +	hasDuplicate = !support.detectDuplicates;
    +	results.sort( sortOrder );
    +
    +	if ( hasDuplicate ) {
    +		for ( ; (elem = results[i]); i++ ) {
    +			if ( elem === results[ i - 1 ] ) {
    +				j = duplicates.push( i );
    +			}
    +		}
    +		while ( j-- ) {
    +			results.splice( duplicates[ j ], 1 );
    +		}
    +	}
    +
    +	return results;
    +};
    +
    +function siblingCheck( a, b ) {
    +	var cur = a && b && a.nextSibling;
    +
    +	for ( ; cur; cur = cur.nextSibling ) {
    +		if ( cur === b ) {
    +			return -1;
    +		}
    +	}
    +
    +	return a ? 1 : -1;
    +}
    +
    +// Returns a function to use in pseudos for input types
    +function createInputPseudo( type ) {
    +	return function( elem ) {
    +		var name = elem.nodeName.toLowerCase();
    +		return name === "input" && elem.type === type;
    +	};
    +}
    +
    +// Returns a function to use in pseudos for buttons
    +function createButtonPseudo( type ) {
    +	return function( elem ) {
    +		var name = elem.nodeName.toLowerCase();
    +		return (name === "input" || name === "button") && elem.type === type;
    +	};
    +}
    +
    +// Returns a function to use in pseudos for positionals
    +function createPositionalPseudo( fn ) {
    +	return markFunction(function( argument ) {
    +		argument = +argument;
    +		return markFunction(function( seed, matches ) {
    +			var j,
    +				matchIndexes = fn( [], seed.length, argument ),
    +				i = matchIndexes.length;
    +
    +			// Match elements found at the specified indexes
    +			while ( i-- ) {
    +				if ( seed[ (j = matchIndexes[i]) ] ) {
    +					seed[j] = !(matches[j] = seed[j]);
    +				}
    +			}
    +		});
    +	});
    +}
    +
    +/**
    + * Utility function for retrieving the text value of an array of DOM nodes
    + * @param {Array|Element} elem
    + */
    +getText = Sizzle.getText = function( elem ) {
    +	var node,
    +		ret = "",
    +		i = 0,
    +		nodeType = elem.nodeType;
    +
    +	if ( !nodeType ) {
    +		// If no nodeType, this is expected to be an array
    +		for ( ; (node = elem[i]); i++ ) {
    +			// Do not traverse comment nodes
    +			ret += getText( node );
    +		}
    +	} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
    +		// Use textContent for elements
    +		// innerText usage removed for consistency of new lines (see #11153)
    +		if ( typeof elem.textContent === "string" ) {
    +			return elem.textContent;
    +		} else {
    +			// Traverse its children
    +			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
    +				ret += getText( elem );
    +			}
    +		}
    +	} else if ( nodeType === 3 || nodeType === 4 ) {
    +		return elem.nodeValue;
    +	}
    +	// Do not include comment or processing instruction nodes
    +
    +	return ret;
    +};
    +
    +Expr = Sizzle.selectors = {
    +
    +	// Can be adjusted by the user
    +	cacheLength: 50,
    +
    +	createPseudo: markFunction,
    +
    +	match: matchExpr,
    +
    +	find: {},
    +
    +	relative: {
    +		">": { dir: "parentNode", first: true },
    +		" ": { dir: "parentNode" },
    +		"+": { dir: "previousSibling", first: true },
    +		"~": { dir: "previousSibling" }
    +	},
    +
    +	preFilter: {
    +		"ATTR": function( match ) {
    +			match[1] = match[1].replace( runescape, funescape );
    +
    +			// Move the given value to match[3] whether quoted or unquoted
    +			match[3] = ( match[4] || match[5] || "" ).replace( runescape, funescape );
    +
    +			if ( match[2] === "~=" ) {
    +				match[3] = " " + match[3] + " ";
    +			}
    +
    +			return match.slice( 0, 4 );
    +		},
    +
    +		"CHILD": function( match ) {
    +			/* matches from matchExpr["CHILD"]
    +				1 type (only|nth|...)
    +				2 what (child|of-type)
    +				3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
    +				4 xn-component of xn+y argument ([+-]?\d*n|)
    +				5 sign of xn-component
    +				6 x of xn-component
    +				7 sign of y-component
    +				8 y of y-component
    +			*/
    +			match[1] = match[1].toLowerCase();
    +
    +			if ( match[1].slice( 0, 3 ) === "nth" ) {
    +				// nth-* requires argument
    +				if ( !match[3] ) {
    +					Sizzle.error( match[0] );
    +				}
    +
    +				// numeric x and y parameters for Expr.filter.CHILD
    +				// remember that false/true cast respectively to 0/1
    +				match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) );
    +				match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" );
    +
    +			// other types prohibit arguments
    +			} else if ( match[3] ) {
    +				Sizzle.error( match[0] );
    +			}
    +
    +			return match;
    +		},
    +
    +		"PSEUDO": function( match ) {
    +			var excess,
    +				unquoted = !match[5] && match[2];
    +
    +			if ( matchExpr["CHILD"].test( match[0] ) ) {
    +				return null;
    +			}
    +
    +			// Accept quoted arguments as-is
    +			if ( match[4] ) {
    +				match[2] = match[4];
    +
    +			// Strip excess characters from unquoted arguments
    +			} else if ( unquoted && rpseudo.test( unquoted ) &&
    +				// Get excess from tokenize (recursively)
    +				(excess = tokenize( unquoted, true )) &&
    +				// advance to the next closing parenthesis
    +				(excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {
    +
    +				// excess is a negative index
    +				match[0] = match[0].slice( 0, excess );
    +				match[2] = unquoted.slice( 0, excess );
    +			}
    +
    +			// Return only captures needed by the pseudo filter method (type and argument)
    +			return match.slice( 0, 3 );
    +		}
    +	},
    +
    +	filter: {
    +
    +		"TAG": function( nodeName ) {
    +			if ( nodeName === "*" ) {
    +				return function() { return true; };
    +			}
    +
    +			nodeName = nodeName.replace( runescape, funescape ).toLowerCase();
    +			return function( elem ) {
    +				return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
    +			};
    +		},
    +
    +		"CLASS": function( className ) {
    +			var pattern = classCache[ className + " " ];
    +
    +			return pattern ||
    +				(pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
    +				classCache( className, function( elem ) {
    +					return pattern.test( elem.className || (typeof elem.getAttribute !== strundefined && elem.getAttribute("class")) || "" );
    +				});
    +		},
    +
    +		"ATTR": function( name, operator, check ) {
    +			return function( elem ) {
    +				var result = Sizzle.attr( elem, name );
    +
    +				if ( result == null ) {
    +					return operator === "!=";
    +				}
    +				if ( !operator ) {
    +					return true;
    +				}
    +
    +				result += "";
    +
    +				return operator === "=" ? result === check :
    +					operator === "!=" ? result !== check :
    +					operator === "^=" ? check && result.indexOf( check ) === 0 :
    +					operator === "*=" ? check && result.indexOf( check ) > -1 :
    +					operator === "$=" ? check && result.substr( result.length - check.length ) === check :
    +					operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 :
    +					operator === "|=" ? result === check || result.substr( 0, check.length + 1 ) === check + "-" :
    +					false;
    +			};
    +		},
    +
    +		"CHILD": function( type, what, argument, first, last ) {
    +			var simple = type.slice( 0, 3 ) !== "nth",
    +				forward = type.slice( -4 ) !== "last",
    +				ofType = what === "of-type";
    +
    +			return first === 1 && last === 0 ?
    +
    +				// Shortcut for :nth-*(n)
    +				function( elem ) {
    +					return !!elem.parentNode;
    +				} :
    +
    +				function( elem, context, xml ) {
    +					var cache, outerCache, node, diff, nodeIndex, start,
    +						dir = simple !== forward ? "nextSibling" : "previousSibling",
    +						parent = elem.parentNode,
    +						name = ofType && elem.nodeName.toLowerCase(),
    +						useCache = !xml && !ofType;
    +
    +					if ( parent ) {
    +
    +						// :(first|last|only)-(child|of-type)
    +						if ( simple ) {
    +							while ( dir ) {
    +								node = elem;
    +								while ( (node = node[ dir ]) ) {
    +									if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) {
    +										return false;
    +									}
    +								}
    +								// Reverse direction for :only-* (if we haven't yet done so)
    +								start = dir = type === "only" && !start && "nextSibling";
    +							}
    +							return true;
    +						}
    +
    +						start = [ forward ? parent.firstChild : parent.lastChild ];
    +
    +						// non-xml :nth-child(...) stores cache data on `parent`
    +						if ( forward && useCache ) {
    +							// Seek `elem` from a previously-cached index
    +							outerCache = parent[ expando ] || (parent[ expando ] = {});
    +							cache = outerCache[ type ] || [];
    +							nodeIndex = cache[0] === dirruns && cache[1];
    +							diff = cache[0] === dirruns && cache[2];
    +							node = nodeIndex && parent.childNodes[ nodeIndex ];
    +
    +							while ( (node = ++nodeIndex && node && node[ dir ] ||
    +
    +								// Fallback to seeking `elem` from the start
    +								(diff = nodeIndex = 0) || start.pop()) ) {
    +
    +								// When found, cache indexes on `parent` and break
    +								if ( node.nodeType === 1 && ++diff && node === elem ) {
    +									outerCache[ type ] = [ dirruns, nodeIndex, diff ];
    +									break;
    +								}
    +							}
    +
    +						// Use previously-cached element index if available
    +						} else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) {
    +							diff = cache[1];
    +
    +						// xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...)
    +						} else {
    +							// Use the same loop as above to seek `elem` from the start
    +							while ( (node = ++nodeIndex && node && node[ dir ] ||
    +								(diff = nodeIndex = 0) || start.pop()) ) {
    +
    +								if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) {
    +									// Cache the index of each encountered element
    +									if ( useCache ) {
    +										(node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ];
    +									}
    +
    +									if ( node === elem ) {
    +										break;
    +									}
    +								}
    +							}
    +						}
    +
    +						// Incorporate the offset, then check against cycle size
    +						diff -= last;
    +						return diff === first || ( diff % first === 0 && diff / first >= 0 );
    +					}
    +				};
    +		},
    +
    +		"PSEUDO": function( pseudo, argument ) {
    +			// pseudo-class names are case-insensitive
    +			// http://www.w3.org/TR/selectors/#pseudo-classes
    +			// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
    +			// Remember that setFilters inherits from pseudos
    +			var args,
    +				fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
    +					Sizzle.error( "unsupported pseudo: " + pseudo );
    +
    +			// The user may use createPseudo to indicate that
    +			// arguments are needed to create the filter function
    +			// just as Sizzle does
    +			if ( fn[ expando ] ) {
    +				return fn( argument );
    +			}
    +
    +			// But maintain support for old signatures
    +			if ( fn.length > 1 ) {
    +				args = [ pseudo, pseudo, "", argument ];
    +				return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
    +					markFunction(function( seed, matches ) {
    +						var idx,
    +							matched = fn( seed, argument ),
    +							i = matched.length;
    +						while ( i-- ) {
    +							idx = indexOf.call( seed, matched[i] );
    +							seed[ idx ] = !( matches[ idx ] = matched[i] );
    +						}
    +					}) :
    +					function( elem ) {
    +						return fn( elem, 0, args );
    +					};
    +			}
    +
    +			return fn;
    +		}
    +	},
    +
    +	pseudos: {
    +		// Potentially complex pseudos
    +		"not": markFunction(function( selector ) {
    +			// Trim the selector passed to compile
    +			// to avoid treating leading and trailing
    +			// spaces as combinators
    +			var input = [],
    +				results = [],
    +				matcher = compile( selector.replace( rtrim, "$1" ) );
    +
    +			return matcher[ expando ] ?
    +				markFunction(function( seed, matches, context, xml ) {
    +					var elem,
    +						unmatched = matcher( seed, null, xml, [] ),
    +						i = seed.length;
    +
    +					// Match elements unmatched by `matcher`
    +					while ( i-- ) {
    +						if ( (elem = unmatched[i]) ) {
    +							seed[i] = !(matches[i] = elem);
    +						}
    +					}
    +				}) :
    +				function( elem, context, xml ) {
    +					input[0] = elem;
    +					matcher( input, null, xml, results );
    +					return !results.pop();
    +				};
    +		}),
    +
    +		"has": markFunction(function( selector ) {
    +			return function( elem ) {
    +				return Sizzle( selector, elem ).length > 0;
    +			};
    +		}),
    +
    +		"contains": markFunction(function( text ) {
    +			return function( elem ) {
    +				return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
    +			};
    +		}),
    +
    +		// "Whether an element is represented by a :lang() selector
    +		// is based solely on the element's language value
    +		// being equal to the identifier C,
    +		// or beginning with the identifier C immediately followed by "-".
    +		// The matching of C against the element's language value is performed case-insensitively.
    +		// The identifier C does not have to be a valid language name."
    +		// http://www.w3.org/TR/selectors/#lang-pseudo
    +		"lang": markFunction( function( lang ) {
    +			// lang value must be a valid identifider
    +			if ( !ridentifier.test(lang || "") ) {
    +				Sizzle.error( "unsupported lang: " + lang );
    +			}
    +			lang = lang.replace( runescape, funescape ).toLowerCase();
    +			return function( elem ) {
    +				var elemLang;
    +				do {
    +					if ( (elemLang = documentIsXML ?
    +						elem.getAttribute("xml:lang") || elem.getAttribute("lang") :
    +						elem.lang) ) {
    +
    +						elemLang = elemLang.toLowerCase();
    +						return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
    +					}
    +				} while ( (elem = elem.parentNode) && elem.nodeType === 1 );
    +				return false;
    +			};
    +		}),
    +
    +		// Miscellaneous
    +		"target": function( elem ) {
    +			var hash = window.location && window.location.hash;
    +			return hash && hash.slice( 1 ) === elem.id;
    +		},
    +
    +		"root": function( elem ) {
    +			return elem === docElem;
    +		},
    +
    +		"focus": function( elem ) {
    +			return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
    +		},
    +
    +		// Boolean properties
    +		"enabled": function( elem ) {
    +			return elem.disabled === false;
    +		},
    +
    +		"disabled": function( elem ) {
    +			return elem.disabled === true;
    +		},
    +
    +		"checked": function( elem ) {
    +			// In CSS3, :checked should return both checked and selected elements
    +			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
    +			var nodeName = elem.nodeName.toLowerCase();
    +			return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
    +		},
    +
    +		"selected": function( elem ) {
    +			// Accessing this property makes selected-by-default
    +			// options in Safari work properly
    +			if ( elem.parentNode ) {
    +				elem.parentNode.selectedIndex;
    +			}
    +
    +			return elem.selected === true;
    +		},
    +
    +		// Contents
    +		"empty": function( elem ) {
    +			// http://www.w3.org/TR/selectors/#empty-pseudo
    +			// :empty is only affected by element nodes and content nodes(including text(3), cdata(4)),
    +			//   not comment, processing instructions, or others
    +			// Thanks to Diego Perini for the nodeName shortcut
    +			//   Greater than "@" means alpha characters (specifically not starting with "#" or "?")
    +			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
    +				if ( elem.nodeName > "@" || elem.nodeType === 3 || elem.nodeType === 4 ) {
    +					return false;
    +				}
    +			}
    +			return true;
    +		},
    +
    +		"parent": function( elem ) {
    +			return !Expr.pseudos["empty"]( elem );
    +		},
    +
    +		// Element/input types
    +		"header": function( elem ) {
    +			return rheader.test( elem.nodeName );
    +		},
    +
    +		"input": function( elem ) {
    +			return rinputs.test( elem.nodeName );
    +		},
    +
    +		"button": function( elem ) {
    +			var name = elem.nodeName.toLowerCase();
    +			return name === "input" && elem.type === "button" || name === "button";
    +		},
    +
    +		"text": function( elem ) {
    +			var attr;
    +			// IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
    +			// use getAttribute instead to test this case
    +			return elem.nodeName.toLowerCase() === "input" &&
    +				elem.type === "text" &&
    +				( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === elem.type );
    +		},
    +
    +		// Position-in-collection
    +		"first": createPositionalPseudo(function() {
    +			return [ 0 ];
    +		}),
    +
    +		"last": createPositionalPseudo(function( matchIndexes, length ) {
    +			return [ length - 1 ];
    +		}),
    +
    +		"eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
    +			return [ argument < 0 ? argument + length : argument ];
    +		}),
    +
    +		"even": createPositionalPseudo(function( matchIndexes, length ) {
    +			var i = 0;
    +			for ( ; i < length; i += 2 ) {
    +				matchIndexes.push( i );
    +			}
    +			return matchIndexes;
    +		}),
    +
    +		"odd": createPositionalPseudo(function( matchIndexes, length ) {
    +			var i = 1;
    +			for ( ; i < length; i += 2 ) {
    +				matchIndexes.push( i );
    +			}
    +			return matchIndexes;
    +		}),
    +
    +		"lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
    +			var i = argument < 0 ? argument + length : argument;
    +			for ( ; --i >= 0; ) {
    +				matchIndexes.push( i );
    +			}
    +			return matchIndexes;
    +		}),
    +
    +		"gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
    +			var i = argument < 0 ? argument + length : argument;
    +			for ( ; ++i < length; ) {
    +				matchIndexes.push( i );
    +			}
    +			return matchIndexes;
    +		})
    +	}
    +};
    +
    +// Add button/input type pseudos
    +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
    +	Expr.pseudos[ i ] = createInputPseudo( i );
    +}
    +for ( i in { submit: true, reset: true } ) {
    +	Expr.pseudos[ i ] = createButtonPseudo( i );
    +}
    +
    +function tokenize( selector, parseOnly ) {
    +	var matched, match, tokens, type,
    +		soFar, groups, preFilters,
    +		cached = tokenCache[ selector + " " ];
    +
    +	if ( cached ) {
    +		return parseOnly ? 0 : cached.slice( 0 );
    +	}
    +
    +	soFar = selector;
    +	groups = [];
    +	preFilters = Expr.preFilter;
    +
    +	while ( soFar ) {
    +
    +		// Comma and first run
    +		if ( !matched || (match = rcomma.exec( soFar )) ) {
    +			if ( match ) {
    +				// Don't consume trailing commas as valid
    +				soFar = soFar.slice( match[0].length ) || soFar;
    +			}
    +			groups.push( tokens = [] );
    +		}
    +
    +		matched = false;
    +
    +		// Combinators
    +		if ( (match = rcombinators.exec( soFar )) ) {
    +			matched = match.shift();
    +			tokens.push( {
    +				value: matched,
    +				// Cast descendant combinators to space
    +				type: match[0].replace( rtrim, " " )
    +			} );
    +			soFar = soFar.slice( matched.length );
    +		}
    +
    +		// Filters
    +		for ( type in Expr.filter ) {
    +			if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
    +				(match = preFilters[ type ]( match ))) ) {
    +				matched = match.shift();
    +				tokens.push( {
    +					value: matched,
    +					type: type,
    +					matches: match
    +				} );
    +				soFar = soFar.slice( matched.length );
    +			}
    +		}
    +
    +		if ( !matched ) {
    +			break;
    +		}
    +	}
    +
    +	// Return the length of the invalid excess
    +	// if we're just parsing
    +	// Otherwise, throw an error or return tokens
    +	return parseOnly ?
    +		soFar.length :
    +		soFar ?
    +			Sizzle.error( selector ) :
    +			// Cache the tokens
    +			tokenCache( selector, groups ).slice( 0 );
    +}
    +
    +function toSelector( tokens ) {
    +	var i = 0,
    +		len = tokens.length,
    +		selector = "";
    +	for ( ; i < len; i++ ) {
    +		selector += tokens[i].value;
    +	}
    +	return selector;
    +}
    +
    +function addCombinator( matcher, combinator, base ) {
    +	var dir = combinator.dir,
    +		checkNonElements = base && combinator.dir === "parentNode",
    +		doneName = done++;
    +
    +	return combinator.first ?
    +		// Check against closest ancestor/preceding element
    +		function( elem, context, xml ) {
    +			while ( (elem = elem[ dir ]) ) {
    +				if ( elem.nodeType === 1 || checkNonElements ) {
    +					return matcher( elem, context, xml );
    +				}
    +			}
    +		} :
    +
    +		// Check against all ancestor/preceding elements
    +		function( elem, context, xml ) {
    +			var data, cache, outerCache,
    +				dirkey = dirruns + " " + doneName;
    +
    +			// We can't set arbitrary data on XML nodes, so they don't benefit from dir caching
    +			if ( xml ) {
    +				while ( (elem = elem[ dir ]) ) {
    +					if ( elem.nodeType === 1 || checkNonElements ) {
    +						if ( matcher( elem, context, xml ) ) {
    +							return true;
    +						}
    +					}
    +				}
    +			} else {
    +				while ( (elem = elem[ dir ]) ) {
    +					if ( elem.nodeType === 1 || checkNonElements ) {
    +						outerCache = elem[ expando ] || (elem[ expando ] = {});
    +						if ( (cache = outerCache[ dir ]) && cache[0] === dirkey ) {
    +							if ( (data = cache[1]) === true || data === cachedruns ) {
    +								return data === true;
    +							}
    +						} else {
    +							cache = outerCache[ dir ] = [ dirkey ];
    +							cache[1] = matcher( elem, context, xml ) || cachedruns;
    +							if ( cache[1] === true ) {
    +								return true;
    +							}
    +						}
    +					}
    +				}
    +			}
    +		};
    +}
    +
    +function elementMatcher( matchers ) {
    +	return matchers.length > 1 ?
    +		function( elem, context, xml ) {
    +			var i = matchers.length;
    +			while ( i-- ) {
    +				if ( !matchers[i]( elem, context, xml ) ) {
    +					return false;
    +				}
    +			}
    +			return true;
    +		} :
    +		matchers[0];
    +}
    +
    +function condense( unmatched, map, filter, context, xml ) {
    +	var elem,
    +		newUnmatched = [],
    +		i = 0,
    +		len = unmatched.length,
    +		mapped = map != null;
    +
    +	for ( ; i < len; i++ ) {
    +		if ( (elem = unmatched[i]) ) {
    +			if ( !filter || filter( elem, context, xml ) ) {
    +				newUnmatched.push( elem );
    +				if ( mapped ) {
    +					map.push( i );
    +				}
    +			}
    +		}
    +	}
    +
    +	return newUnmatched;
    +}
    +
    +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
    +	if ( postFilter && !postFilter[ expando ] ) {
    +		postFilter = setMatcher( postFilter );
    +	}
    +	if ( postFinder && !postFinder[ expando ] ) {
    +		postFinder = setMatcher( postFinder, postSelector );
    +	}
    +	return markFunction(function( seed, results, context, xml ) {
    +		var temp, i, elem,
    +			preMap = [],
    +			postMap = [],
    +			preexisting = results.length,
    +
    +			// Get initial elements from seed or context
    +			elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),
    +
    +			// Prefilter to get matcher input, preserving a map for seed-results synchronization
    +			matcherIn = preFilter && ( seed || !selector ) ?
    +				condense( elems, preMap, preFilter, context, xml ) :
    +				elems,
    +
    +			matcherOut = matcher ?
    +				// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
    +				postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
    +
    +					// ...intermediate processing is necessary
    +					[] :
    +
    +					// ...otherwise use results directly
    +					results :
    +				matcherIn;
    +
    +		// Find primary matches
    +		if ( matcher ) {
    +			matcher( matcherIn, matcherOut, context, xml );
    +		}
    +
    +		// Apply postFilter
    +		if ( postFilter ) {
    +			temp = condense( matcherOut, postMap );
    +			postFilter( temp, [], context, xml );
    +
    +			// Un-match failing elements by moving them back to matcherIn
    +			i = temp.length;
    +			while ( i-- ) {
    +				if ( (elem = temp[i]) ) {
    +					matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
    +				}
    +			}
    +		}
    +
    +		if ( seed ) {
    +			if ( postFinder || preFilter ) {
    +				if ( postFinder ) {
    +					// Get the final matcherOut by condensing this intermediate into postFinder contexts
    +					temp = [];
    +					i = matcherOut.length;
    +					while ( i-- ) {
    +						if ( (elem = matcherOut[i]) ) {
    +							// Restore matcherIn since elem is not yet a final match
    +							temp.push( (matcherIn[i] = elem) );
    +						}
    +					}
    +					postFinder( null, (matcherOut = []), temp, xml );
    +				}
    +
    +				// Move matched elements from seed to results to keep them synchronized
    +				i = matcherOut.length;
    +				while ( i-- ) {
    +					if ( (elem = matcherOut[i]) &&
    +						(temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) {
    +
    +						seed[temp] = !(results[temp] = elem);
    +					}
    +				}
    +			}
    +
    +		// Add elements to results, through postFinder if defined
    +		} else {
    +			matcherOut = condense(
    +				matcherOut === results ?
    +					matcherOut.splice( preexisting, matcherOut.length ) :
    +					matcherOut
    +			);
    +			if ( postFinder ) {
    +				postFinder( null, results, matcherOut, xml );
    +			} else {
    +				push.apply( results, matcherOut );
    +			}
    +		}
    +	});
    +}
    +
    +function matcherFromTokens( tokens ) {
    +	var checkContext, matcher, j,
    +		len = tokens.length,
    +		leadingRelative = Expr.relative[ tokens[0].type ],
    +		implicitRelative = leadingRelative || Expr.relative[" "],
    +		i = leadingRelative ? 1 : 0,
    +
    +		// The foundational matcher ensures that elements are reachable from top-level context(s)
    +		matchContext = addCombinator( function( elem ) {
    +			return elem === checkContext;
    +		}, implicitRelative, true ),
    +		matchAnyContext = addCombinator( function( elem ) {
    +			return indexOf.call( checkContext, elem ) > -1;
    +		}, implicitRelative, true ),
    +		matchers = [ function( elem, context, xml ) {
    +			return ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
    +				(checkContext = context).nodeType ?
    +					matchContext( elem, context, xml ) :
    +					matchAnyContext( elem, context, xml ) );
    +		} ];
    +
    +	for ( ; i < len; i++ ) {
    +		if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
    +			matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
    +		} else {
    +			matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
    +
    +			// Return special upon seeing a positional matcher
    +			if ( matcher[ expando ] ) {
    +				// Find the next relative operator (if any) for proper handling
    +				j = ++i;
    +				for ( ; j < len; j++ ) {
    +					if ( Expr.relative[ tokens[j].type ] ) {
    +						break;
    +					}
    +				}
    +				return setMatcher(
    +					i > 1 && elementMatcher( matchers ),
    +					i > 1 && toSelector( tokens.slice( 0, i - 1 ) ).replace( rtrim, "$1" ),
    +					matcher,
    +					i < j && matcherFromTokens( tokens.slice( i, j ) ),
    +					j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
    +					j < len && toSelector( tokens )
    +				);
    +			}
    +			matchers.push( matcher );
    +		}
    +	}
    +
    +	return elementMatcher( matchers );
    +}
    +
    +function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
    +	// A counter to specify which element is currently being matched
    +	var matcherCachedRuns = 0,
    +		bySet = setMatchers.length > 0,
    +		byElement = elementMatchers.length > 0,
    +		superMatcher = function( seed, context, xml, results, expandContext ) {
    +			var elem, j, matcher,
    +				setMatched = [],
    +				matchedCount = 0,
    +				i = "0",
    +				unmatched = seed && [],
    +				outermost = expandContext != null,
    +				contextBackup = outermostContext,
    +				// We must always have either seed elements or context
    +				elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context ),
    +				// Nested matchers should use non-integer dirruns
    +				dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.E);
    +
    +			if ( outermost ) {
    +				outermostContext = context !== document && context;
    +				cachedruns = matcherCachedRuns;
    +			}
    +
    +			// Add elements passing elementMatchers directly to results
    +			for ( ; (elem = elems[i]) != null; i++ ) {
    +				if ( byElement && elem ) {
    +					for ( j = 0; (matcher = elementMatchers[j]); j++ ) {
    +						if ( matcher( elem, context, xml ) ) {
    +							results.push( elem );
    +							break;
    +						}
    +					}
    +					if ( outermost ) {
    +						dirruns = dirrunsUnique;
    +						cachedruns = ++matcherCachedRuns;
    +					}
    +				}
    +
    +				// Track unmatched elements for set filters
    +				if ( bySet ) {
    +					// They will have gone through all possible matchers
    +					if ( (elem = !matcher && elem) ) {
    +						matchedCount--;
    +					}
    +
    +					// Lengthen the array for every element, matched or not
    +					if ( seed ) {
    +						unmatched.push( elem );
    +					}
    +				}
    +			}
    +
    +			// Apply set filters to unmatched elements
    +			// `i` starts as a string, so matchedCount would equal "00" if there are no elements
    +			matchedCount += i;
    +			if ( bySet && i !== matchedCount ) {
    +				for ( j = 0; (matcher = setMatchers[j]); j++ ) {
    +					matcher( unmatched, setMatched, context, xml );
    +				}
    +
    +				if ( seed ) {
    +					// Reintegrate element matches to eliminate the need for sorting
    +					if ( matchedCount > 0 ) {
    +						while ( i-- ) {
    +							if ( !(unmatched[i] || setMatched[i]) ) {
    +								setMatched[i] = pop.call( results );
    +							}
    +						}
    +					}
    +
    +					// Discard index placeholder values to get only actual matches
    +					setMatched = condense( setMatched );
    +				}
    +
    +				// Add matches to results
    +				push.apply( results, setMatched );
    +
    +				// Seedless set matches succeeding multiple successful matchers stipulate sorting
    +				if ( outermost && !seed && setMatched.length > 0 &&
    +					( matchedCount + setMatchers.length ) > 1 ) {
    +
    +					Sizzle.uniqueSort( results );
    +				}
    +			}
    +
    +			// Override manipulation of globals by nested matchers
    +			if ( outermost ) {
    +				dirruns = dirrunsUnique;
    +				outermostContext = contextBackup;
    +			}
    +
    +			return unmatched;
    +		};
    +
    +	return bySet ?
    +		markFunction( superMatcher ) :
    +		superMatcher;
    +}
    +
    +compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) {
    +	var i,
    +		setMatchers = [],
    +		elementMatchers = [],
    +		cached = compilerCache[ selector + " " ];
    +
    +	if ( !cached ) {
    +		// Generate a function of recursive functions that can be used to check each element
    +		if ( !group ) {
    +			group = tokenize( selector );
    +		}
    +		i = group.length;
    +		while ( i-- ) {
    +			cached = matcherFromTokens( group[i] );
    +			if ( cached[ expando ] ) {
    +				setMatchers.push( cached );
    +			} else {
    +				elementMatchers.push( cached );
    +			}
    +		}
    +
    +		// Cache the compiled function
    +		cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
    +	}
    +	return cached;
    +};
    +
    +function multipleContexts( selector, contexts, results ) {
    +	var i = 0,
    +		len = contexts.length;
    +	for ( ; i < len; i++ ) {
    +		Sizzle( selector, contexts[i], results );
    +	}
    +	return results;
    +}
    +
    +function select( selector, context, results, seed ) {
    +	var i, tokens, token, type, find,
    +		match = tokenize( selector );
    +
    +	if ( !seed ) {
    +		// Try to minimize operations if there is only one group
    +		if ( match.length === 1 ) {
    +
    +			// Take a shortcut and set the context if the root selector is an ID
    +			tokens = match[0] = match[0].slice( 0 );
    +			if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
    +					context.nodeType === 9 && !documentIsXML &&
    +					Expr.relative[ tokens[1].type ] ) {
    +
    +				context = Expr.find["ID"]( token.matches[0].replace( runescape, funescape ), context )[0];
    +				if ( !context ) {
    +					return results;
    +				}
    +
    +				selector = selector.slice( tokens.shift().value.length );
    +			}
    +
    +			// Fetch a seed set for right-to-left matching
    +			for ( i = matchExpr["needsContext"].test( selector ) ? -1 : tokens.length - 1; i >= 0; i-- ) {
    +				token = tokens[i];
    +
    +				// Abort if we hit a combinator
    +				if ( Expr.relative[ (type = token.type) ] ) {
    +					break;
    +				}
    +				if ( (find = Expr.find[ type ]) ) {
    +					// Search, expanding context for leading sibling combinators
    +					if ( (seed = find(
    +						token.matches[0].replace( runescape, funescape ),
    +						rsibling.test( tokens[0].type ) && context.parentNode || context
    +					)) ) {
    +
    +						// If seed is empty or no tokens remain, we can return early
    +						tokens.splice( i, 1 );
    +						selector = seed.length && toSelector( tokens );
    +						if ( !selector ) {
    +							push.apply( results, slice.call( seed, 0 ) );
    +							return results;
    +						}
    +
    +						break;
    +					}
    +				}
    +			}
    +		}
    +	}
    +
    +	// Compile and execute a filtering function
    +	// Provide `match` to avoid retokenization if we modified the selector above
    +	compile( selector, match )(
    +		seed,
    +		context,
    +		documentIsXML,
    +		results,
    +		rsibling.test( selector )
    +	);
    +	return results;
    +}
    +
    +// Deprecated
    +Expr.pseudos["nth"] = Expr.pseudos["eq"];
    +
    +// Easy API for creating new setFilters
    +function setFilters() {}
    +Expr.filters = setFilters.prototype = Expr.pseudos;
    +Expr.setFilters = new setFilters();
    +
    +// Initialize with the default document
    +setDocument();
    +
    +// Override sizzle attribute retrieval
    +Sizzle.attr = jQuery.attr;
    +jQuery.find = Sizzle;
    +jQuery.expr = Sizzle.selectors;
    +jQuery.expr[":"] = jQuery.expr.pseudos;
    +jQuery.unique = Sizzle.uniqueSort;
    +jQuery.text = Sizzle.getText;
    +jQuery.isXMLDoc = Sizzle.isXML;
    +jQuery.contains = Sizzle.contains;
    +
    +
    +})( window );
    +var runtil = /Until$/,
    +	rparentsprev = /^(?:parents|prev(?:Until|All))/,
    +	isSimple = /^.[^:#\[\.,]*$/,
    +	rneedsContext = jQuery.expr.match.needsContext,
    +	// methods guaranteed to produce a unique set when starting from a unique set
    +	guaranteedUnique = {
    +		children: true,
    +		contents: true,
    +		next: true,
    +		prev: true
    +	};
    +
    +jQuery.fn.extend({
    +	find: function( selector ) {
    +		var i, ret, self;
    +
    +		if ( typeof selector !== "string" ) {
    +			self = this;
    +			return this.pushStack( jQuery( selector ).filter(function() {
    +				for ( i = 0; i < self.length; i++ ) {
    +					if ( jQuery.contains( self[ i ], this ) ) {
    +						return true;
    +					}
    +				}
    +			}) );
    +		}
    +
    +		ret = [];
    +		for ( i = 0; i < this.length; i++ ) {
    +			jQuery.find( selector, this[ i ], ret );
    +		}
    +
    +		// Needed because $( selector, context ) becomes $( context ).find( selector )
    +		ret = this.pushStack( jQuery.unique( ret ) );
    +		ret.selector = ( this.selector ? this.selector + " " : "" ) + selector;
    +		return ret;
    +	},
    +
    +	has: function( target ) {
    +		var i,
    +			targets = jQuery( target, this ),
    +			len = targets.length;
    +
    +		return this.filter(function() {
    +			for ( i = 0; i < len; i++ ) {
    +				if ( jQuery.contains( this, targets[i] ) ) {
    +					return true;
    +				}
    +			}
    +		});
    +	},
    +
    +	not: function( selector ) {
    +		return this.pushStack( winnow(this, selector, false) );
    +	},
    +
    +	filter: function( selector ) {
    +		return this.pushStack( winnow(this, selector, true) );
    +	},
    +
    +	is: function( selector ) {
    +		return !!selector && (
    +			typeof selector === "string" ?
    +				// If this is a positional/relative selector, check membership in the returned set
    +				// so $("p:first").is("p:last") won't return true for a doc with two "p".
    +				rneedsContext.test( selector ) ?
    +					jQuery( selector, this.context ).index( this[0] ) >= 0 :
    +					jQuery.filter( selector, this ).length > 0 :
    +				this.filter( selector ).length > 0 );
    +	},
    +
    +	closest: function( selectors, context ) {
    +		var cur,
    +			i = 0,
    +			l = this.length,
    +			ret = [],
    +			pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ?
    +				jQuery( selectors, context || this.context ) :
    +				0;
    +
    +		for ( ; i < l; i++ ) {
    +			cur = this[i];
    +
    +			while ( cur && cur.ownerDocument && cur !== context && cur.nodeType !== 11 ) {
    +				if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {
    +					ret.push( cur );
    +					break;
    +				}
    +				cur = cur.parentNode;
    +			}
    +		}
    +
    +		return this.pushStack( ret.length > 1 ? jQuery.unique( ret ) : ret );
    +	},
    +
    +	// Determine the position of an element within
    +	// the matched set of elements
    +	index: function( elem ) {
    +
    +		// No argument, return index in parent
    +		if ( !elem ) {
    +			return ( this[0] && this[0].parentNode ) ? this.first().prevAll().length : -1;
    +		}
    +
    +		// index in selector
    +		if ( typeof elem === "string" ) {
    +			return jQuery.inArray( this[0], jQuery( elem ) );
    +		}
    +
    +		// Locate the position of the desired element
    +		return jQuery.inArray(
    +			// If it receives a jQuery object, the first element is used
    +			elem.jquery ? elem[0] : elem, this );
    +	},
    +
    +	add: function( selector, context ) {
    +		var set = typeof selector === "string" ?
    +				jQuery( selector, context ) :
    +				jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ),
    +			all = jQuery.merge( this.get(), set );
    +
    +		return this.pushStack( jQuery.unique(all) );
    +	},
    +
    +	addBack: function( selector ) {
    +		return this.add( selector == null ?
    +			this.prevObject : this.prevObject.filter(selector)
    +		);
    +	}
    +});
    +
    +jQuery.fn.andSelf = jQuery.fn.addBack;
    +
    +function sibling( cur, dir ) {
    +	do {
    +		cur = cur[ dir ];
    +	} while ( cur && cur.nodeType !== 1 );
    +
    +	return cur;
    +}
    +
    +jQuery.each({
    +	parent: function( elem ) {
    +		var parent = elem.parentNode;
    +		return parent && parent.nodeType !== 11 ? parent : null;
    +	},
    +	parents: function( elem ) {
    +		return jQuery.dir( elem, "parentNode" );
    +	},
    +	parentsUntil: function( elem, i, until ) {
    +		return jQuery.dir( elem, "parentNode", until );
    +	},
    +	next: function( elem ) {
    +		return sibling( elem, "nextSibling" );
    +	},
    +	prev: function( elem ) {
    +		return sibling( elem, "previousSibling" );
    +	},
    +	nextAll: function( elem ) {
    +		return jQuery.dir( elem, "nextSibling" );
    +	},
    +	prevAll: function( elem ) {
    +		return jQuery.dir( elem, "previousSibling" );
    +	},
    +	nextUntil: function( elem, i, until ) {
    +		return jQuery.dir( elem, "nextSibling", until );
    +	},
    +	prevUntil: function( elem, i, until ) {
    +		return jQuery.dir( elem, "previousSibling", until );
    +	},
    +	siblings: function( elem ) {
    +		return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );
    +	},
    +	children: function( elem ) {
    +		return jQuery.sibling( elem.firstChild );
    +	},
    +	contents: function( elem ) {
    +		return jQuery.nodeName( elem, "iframe" ) ?
    +			elem.contentDocument || elem.contentWindow.document :
    +			jQuery.merge( [], elem.childNodes );
    +	}
    +}, function( name, fn ) {
    +	jQuery.fn[ name ] = function( until, selector ) {
    +		var ret = jQuery.map( this, fn, until );
    +
    +		if ( !runtil.test( name ) ) {
    +			selector = until;
    +		}
    +
    +		if ( selector && typeof selector === "string" ) {
    +			ret = jQuery.filter( selector, ret );
    +		}
    +
    +		ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret;
    +
    +		if ( this.length > 1 && rparentsprev.test( name ) ) {
    +			ret = ret.reverse();
    +		}
    +
    +		return this.pushStack( ret );
    +	};
    +});
    +
    +jQuery.extend({
    +	filter: function( expr, elems, not ) {
    +		if ( not ) {
    +			expr = ":not(" + expr + ")";
    +		}
    +
    +		return elems.length === 1 ?
    +			jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] :
    +			jQuery.find.matches(expr, elems);
    +	},
    +
    +	dir: function( elem, dir, until ) {
    +		var matched = [],
    +			cur = elem[ dir ];
    +
    +		while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
    +			if ( cur.nodeType === 1 ) {
    +				matched.push( cur );
    +			}
    +			cur = cur[dir];
    +		}
    +		return matched;
    +	},
    +
    +	sibling: function( n, elem ) {
    +		var r = [];
    +
    +		for ( ; n; n = n.nextSibling ) {
    +			if ( n.nodeType === 1 && n !== elem ) {
    +				r.push( n );
    +			}
    +		}
    +
    +		return r;
    +	}
    +});
    +
    +// Implement the identical functionality for filter and not
    +function winnow( elements, qualifier, keep ) {
    +
    +	// Can't pass null or undefined to indexOf in Firefox 4
    +	// Set to 0 to skip string check
    +	qualifier = qualifier || 0;
    +
    +	if ( jQuery.isFunction( qualifier ) ) {
    +		return jQuery.grep(elements, function( elem, i ) {
    +			var retVal = !!qualifier.call( elem, i, elem );
    +			return retVal === keep;
    +		});
    +
    +	} else if ( qualifier.nodeType ) {
    +		return jQuery.grep(elements, function( elem ) {
    +			return ( elem === qualifier ) === keep;
    +		});
    +
    +	} else if ( typeof qualifier === "string" ) {
    +		var filtered = jQuery.grep(elements, function( elem ) {
    +			return elem.nodeType === 1;
    +		});
    +
    +		if ( isSimple.test( qualifier ) ) {
    +			return jQuery.filter(qualifier, filtered, !keep);
    +		} else {
    +			qualifier = jQuery.filter( qualifier, filtered );
    +		}
    +	}
    +
    +	return jQuery.grep(elements, function( elem ) {
    +		return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep;
    +	});
    +}
    +function createSafeFragment( document ) {
    +	var list = nodeNames.split( "|" ),
    +		safeFrag = document.createDocumentFragment();
    +
    +	if ( safeFrag.createElement ) {
    +		while ( list.length ) {
    +			safeFrag.createElement(
    +				list.pop()
    +			);
    +		}
    +	}
    +	return safeFrag;
    +}
    +
    +var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" +
    +		"header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",
    +	rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g,
    +	rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"),
    +	rleadingWhitespace = /^\s+/,
    +	rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,
    +	rtagName = /<([\w:]+)/,
    +	rtbody = /<tbody/i,
    +	rhtml = /<|&#?\w+;/,
    +	rnoInnerhtml = /<(?:script|style|link)/i,
    +	manipulation_rcheckableType = /^(?:checkbox|radio)$/i,
    +	// checked="checked" or checked
    +	rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
    +	rscriptType = /^$|\/(?:java|ecma)script/i,
    +	rscriptTypeMasked = /^true\/(.*)/,
    +	rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,
    +
    +	// We have to close these tags to support XHTML (#13200)
    +	wrapMap = {
    +		option: [ 1, "<select multiple='multiple'>", "</select>" ],
    +		legend: [ 1, "<fieldset>", "</fieldset>" ],
    +		area: [ 1, "<map>", "</map>" ],
    +		param: [ 1, "<object>", "</object>" ],
    +		thead: [ 1, "<table>", "</table>" ],
    +		tr: [ 2, "<table><tbody>", "</tbody></table>" ],
    +		col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
    +		td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
    +
    +		// IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags,
    +		// unless wrapped in a div with non-breaking characters in front of it.
    +		_default: jQuery.support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X<div>", "</div>"  ]
    +	},
    +	safeFragment = createSafeFragment( document ),
    +	fragmentDiv = safeFragment.appendChild( document.createElement("div") );
    +
    +wrapMap.optgroup = wrapMap.option;
    +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
    +wrapMap.th = wrapMap.td;
    +
    +jQuery.fn.extend({
    +	text: function( value ) {
    +		return jQuery.access( this, function( value ) {
    +			return value === undefined ?
    +				jQuery.text( this ) :
    +				this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) );
    +		}, null, value, arguments.length );
    +	},
    +
    +	wrapAll: function( html ) {
    +		if ( jQuery.isFunction( html ) ) {
    +			return this.each(function(i) {
    +				jQuery(this).wrapAll( html.call(this, i) );
    +			});
    +		}
    +
    +		if ( this[0] ) {
    +			// The elements to wrap the target around
    +			var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);
    +
    +			if ( this[0].parentNode ) {
    +				wrap.insertBefore( this[0] );
    +			}
    +
    +			wrap.map(function() {
    +				var elem = this;
    +
    +				while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
    +					elem = elem.firstChild;
    +				}
    +
    +				return elem;
    +			}).append( this );
    +		}
    +
    +		return this;
    +	},
    +
    +	wrapInner: function( html ) {
    +		if ( jQuery.isFunction( html ) ) {
    +			return this.each(function(i) {
    +				jQuery(this).wrapInner( html.call(this, i) );
    +			});
    +		}
    +
    +		return this.each(function() {
    +			var self = jQuery( this ),
    +				contents = self.contents();
    +
    +			if ( contents.length ) {
    +				contents.wrapAll( html );
    +
    +			} else {
    +				self.append( html );
    +			}
    +		});
    +	},
    +
    +	wrap: function( html ) {
    +		var isFunction = jQuery.isFunction( html );
    +
    +		return this.each(function(i) {
    +			jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );
    +		});
    +	},
    +
    +	unwrap: function() {
    +		return this.parent().each(function() {
    +			if ( !jQuery.nodeName( this, "body" ) ) {
    +				jQuery( this ).replaceWith( this.childNodes );
    +			}
    +		}).end();
    +	},
    +
    +	append: function() {
    +		return this.domManip(arguments, true, function( elem ) {
    +			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
    +				this.appendChild( elem );
    +			}
    +		});
    +	},
    +
    +	prepend: function() {
    +		return this.domManip(arguments, true, function( elem ) {
    +			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
    +				this.insertBefore( elem, this.firstChild );
    +			}
    +		});
    +	},
    +
    +	before: function() {
    +		return this.domManip( arguments, false, function( elem ) {
    +			if ( this.parentNode ) {
    +				this.parentNode.insertBefore( elem, this );
    +			}
    +		});
    +	},
    +
    +	after: function() {
    +		return this.domManip( arguments, false, function( elem ) {
    +			if ( this.parentNode ) {
    +				this.parentNode.insertBefore( elem, this.nextSibling );
    +			}
    +		});
    +	},
    +
    +	// keepData is for internal use only--do not document
    +	remove: function( selector, keepData ) {
    +		var elem,
    +			i = 0;
    +
    +		for ( ; (elem = this[i]) != null; i++ ) {
    +			if ( !selector || jQuery.filter( selector, [ elem ] ).length > 0 ) {
    +				if ( !keepData && elem.nodeType === 1 ) {
    +					jQuery.cleanData( getAll( elem ) );
    +				}
    +
    +				if ( elem.parentNode ) {
    +					if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) {
    +						setGlobalEval( getAll( elem, "script" ) );
    +					}
    +					elem.parentNode.removeChild( elem );
    +				}
    +			}
    +		}
    +
    +		return this;
    +	},
    +
    +	empty: function() {
    +		var elem,
    +			i = 0;
    +
    +		for ( ; (elem = this[i]) != null; i++ ) {
    +			// Remove element nodes and prevent memory leaks
    +			if ( elem.nodeType === 1 ) {
    +				jQuery.cleanData( getAll( elem, false ) );
    +			}
    +
    +			// Remove any remaining nodes
    +			while ( elem.firstChild ) {
    +				elem.removeChild( elem.firstChild );
    +			}
    +
    +			// If this is a select, ensure that it displays empty (#12336)
    +			// Support: IE<9
    +			if ( elem.options && jQuery.nodeName( elem, "select" ) ) {
    +				elem.options.length = 0;
    +			}
    +		}
    +
    +		return this;
    +	},
    +
    +	clone: function( dataAndEvents, deepDataAndEvents ) {
    +		dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
    +		deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
    +
    +		return this.map( function () {
    +			return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
    +		});
    +	},
    +
    +	html: function( value ) {
    +		return jQuery.access( this, function( value ) {
    +			var elem = this[0] || {},
    +				i = 0,
    +				l = this.length;
    +
    +			if ( value === undefined ) {
    +				return elem.nodeType === 1 ?
    +					elem.innerHTML.replace( rinlinejQuery, "" ) :
    +					undefined;
    +			}
    +
    +			// See if we can take a shortcut and just use innerHTML
    +			if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
    +				( jQuery.support.htmlSerialize || !rnoshimcache.test( value )  ) &&
    +				( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) &&
    +				!wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) {
    +
    +				value = value.replace( rxhtmlTag, "<$1></$2>" );
    +
    +				try {
    +					for (; i < l; i++ ) {
    +						// Remove element nodes and prevent memory leaks
    +						elem = this[i] || {};
    +						if ( elem.nodeType === 1 ) {
    +							jQuery.cleanData( getAll( elem, false ) );
    +							elem.innerHTML = value;
    +						}
    +					}
    +
    +					elem = 0;
    +
    +				// If using innerHTML throws an exception, use the fallback method
    +				} catch(e) {}
    +			}
    +
    +			if ( elem ) {
    +				this.empty().append( value );
    +			}
    +		}, null, value, arguments.length );
    +	},
    +
    +	replaceWith: function( value ) {
    +		var isFunc = jQuery.isFunction( value );
    +
    +		// Make sure that the elements are removed from the DOM before they are inserted
    +		// this can help fix replacing a parent with child elements
    +		if ( !isFunc && typeof value !== "string" ) {
    +			value = jQuery( value ).not( this ).detach();
    +		}
    +
    +		return this.domManip( [ value ], true, function( elem ) {
    +			var next = this.nextSibling,
    +				parent = this.parentNode;
    +
    +			if ( parent && this.nodeType === 1 || this.nodeType === 11 ) {
    +
    +				jQuery( this ).remove();
    +
    +				if ( next ) {
    +					next.parentNode.insertBefore( elem, next );
    +				} else {
    +					parent.appendChild( elem );
    +				}
    +			}
    +		});
    +	},
    +
    +	detach: function( selector ) {
    +		return this.remove( selector, true );
    +	},
    +
    +	domManip: function( args, table, callback ) {
    +
    +		// Flatten any nested arrays
    +		args = core_concat.apply( [], args );
    +
    +		var fragment, first, scripts, hasScripts, node, doc,
    +			i = 0,
    +			l = this.length,
    +			set = this,
    +			iNoClone = l - 1,
    +			value = args[0],
    +			isFunction = jQuery.isFunction( value );
    +
    +		// We can't cloneNode fragments that contain checked, in WebKit
    +		if ( isFunction || !( l <= 1 || typeof value !== "string" || jQuery.support.checkClone || !rchecked.test( value ) ) ) {
    +			return this.each(function( index ) {
    +				var self = set.eq( index );
    +				if ( isFunction ) {
    +					args[0] = value.call( this, index, table ? self.html() : undefined );
    +				}
    +				self.domManip( args, table, callback );
    +			});
    +		}
    +
    +		if ( l ) {
    +			fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this );
    +			first = fragment.firstChild;
    +
    +			if ( fragment.childNodes.length === 1 ) {
    +				fragment = first;
    +			}
    +
    +			if ( first ) {
    +				table = table && jQuery.nodeName( first, "tr" );
    +				scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
    +				hasScripts = scripts.length;
    +
    +				// Use the original fragment for the last item instead of the first because it can end up
    +				// being emptied incorrectly in certain situations (#8070).
    +				for ( ; i < l; i++ ) {
    +					node = fragment;
    +
    +					if ( i !== iNoClone ) {
    +						node = jQuery.clone( node, true, true );
    +
    +						// Keep references to cloned scripts for later restoration
    +						if ( hasScripts ) {
    +							jQuery.merge( scripts, getAll( node, "script" ) );
    +						}
    +					}
    +
    +					callback.call(
    +						table && jQuery.nodeName( this[i], "table" ) ?
    +							findOrAppend( this[i], "tbody" ) :
    +							this[i],
    +						node,
    +						i
    +					);
    +				}
    +
    +				if ( hasScripts ) {
    +					doc = scripts[ scripts.length - 1 ].ownerDocument;
    +
    +					// Reenable scripts
    +					jQuery.map( scripts, restoreScript );
    +
    +					// Evaluate executable scripts on first document insertion
    +					for ( i = 0; i < hasScripts; i++ ) {
    +						node = scripts[ i ];
    +						if ( rscriptType.test( node.type || "" ) &&
    +							!jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) {
    +
    +							if ( node.src ) {
    +								// Hope ajax is available...
    +								jQuery.ajax({
    +									url: node.src,
    +									type: "GET",
    +									dataType: "script",
    +									async: false,
    +									global: false,
    +									"throws": true
    +								});
    +							} else {
    +								jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) );
    +							}
    +						}
    +					}
    +				}
    +
    +				// Fix #11809: Avoid leaking memory
    +				fragment = first = null;
    +			}
    +		}
    +
    +		return this;
    +	}
    +});
    +
    +function findOrAppend( elem, tag ) {
    +	return elem.getElementsByTagName( tag )[0] || elem.appendChild( elem.ownerDocument.createElement( tag ) );
    +}
    +
    +// Replace/restore the type attribute of script elements for safe DOM manipulation
    +function disableScript( elem ) {
    +	var attr = elem.getAttributeNode("type");
    +	elem.type = ( attr && attr.specified ) + "/" + elem.type;
    +	return elem;
    +}
    +function restoreScript( elem ) {
    +	var match = rscriptTypeMasked.exec( elem.type );
    +	if ( match ) {
    +		elem.type = match[1];
    +	} else {
    +		elem.removeAttribute("type");
    +	}
    +	return elem;
    +}
    +
    +// Mark scripts as having already been evaluated
    +function setGlobalEval( elems, refElements ) {
    +	var elem,
    +		i = 0;
    +	for ( ; (elem = elems[i]) != null; i++ ) {
    +		jQuery._data( elem, "globalEval", !refElements || jQuery._data( refElements[i], "globalEval" ) );
    +	}
    +}
    +
    +function cloneCopyEvent( src, dest ) {
    +
    +	if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
    +		return;
    +	}
    +
    +	var type, i, l,
    +		oldData = jQuery._data( src ),
    +		curData = jQuery._data( dest, oldData ),
    +		events = oldData.events;
    +
    +	if ( events ) {
    +		delete curData.handle;
    +		curData.events = {};
    +
    +		for ( type in events ) {
    +			for ( i = 0, l = events[ type ].length; i < l; i++ ) {
    +				jQuery.event.add( dest, type, events[ type ][ i ] );
    +			}
    +		}
    +	}
    +
    +	// make the cloned public data object a copy from the original
    +	if ( curData.data ) {
    +		curData.data = jQuery.extend( {}, curData.data );
    +	}
    +}
    +
    +function fixCloneNodeIssues( src, dest ) {
    +	var nodeName, data, e;
    +
    +	// We do not need to do anything for non-Elements
    +	if ( dest.nodeType !== 1 ) {
    +		return;
    +	}
    +
    +	nodeName = dest.nodeName.toLowerCase();
    +
    +	// IE6-8 copies events bound via attachEvent when using cloneNode.
    +	if ( !jQuery.support.noCloneEvent && dest[ jQuery.expando ] ) {
    +		data = jQuery._data( dest );
    +
    +		for ( e in data.events ) {
    +			jQuery.removeEvent( dest, e, data.handle );
    +		}
    +
    +		// Event data gets referenced instead of copied if the expando gets copied too
    +		dest.removeAttribute( jQuery.expando );
    +	}
    +
    +	// IE blanks contents when cloning scripts, and tries to evaluate newly-set text
    +	if ( nodeName === "script" && dest.text !== src.text ) {
    +		disableScript( dest ).text = src.text;
    +		restoreScript( dest );
    +
    +	// IE6-10 improperly clones children of object elements using classid.
    +	// IE10 throws NoModificationAllowedError if parent is null, #12132.
    +	} else if ( nodeName === "object" ) {
    +		if ( dest.parentNode ) {
    +			dest.outerHTML = src.outerHTML;
    +		}
    +
    +		// This path appears unavoidable for IE9. When cloning an object
    +		// element in IE9, the outerHTML strategy above is not sufficient.
    +		// If the src has innerHTML and the destination does not,
    +		// copy the src.innerHTML into the dest.innerHTML. #10324
    +		if ( jQuery.support.html5Clone && ( src.innerHTML && !jQuery.trim(dest.innerHTML) ) ) {
    +			dest.innerHTML = src.innerHTML;
    +		}
    +
    +	} else if ( nodeName === "input" && manipulation_rcheckableType.test( src.type ) ) {
    +		// IE6-8 fails to persist the checked state of a cloned checkbox
    +		// or radio button. Worse, IE6-7 fail to give the cloned element
    +		// a checked appearance if the defaultChecked value isn't also set
    +
    +		dest.defaultChecked = dest.checked = src.checked;
    +
    +		// IE6-7 get confused and end up setting the value of a cloned
    +		// checkbox/radio button to an empty string instead of "on"
    +		if ( dest.value !== src.value ) {
    +			dest.value = src.value;
    +		}
    +
    +	// IE6-8 fails to return the selected option to the default selected
    +	// state when cloning options
    +	} else if ( nodeName === "option" ) {
    +		dest.defaultSelected = dest.selected = src.defaultSelected;
    +
    +	// IE6-8 fails to set the defaultValue to the correct value when
    +	// cloning other types of input fields
    +	} else if ( nodeName === "input" || nodeName === "textarea" ) {
    +		dest.defaultValue = src.defaultValue;
    +	}
    +}
    +
    +jQuery.each({
    +	appendTo: "append",
    +	prependTo: "prepend",
    +	insertBefore: "before",
    +	insertAfter: "after",
    +	replaceAll: "replaceWith"
    +}, function( name, original ) {
    +	jQuery.fn[ name ] = function( selector ) {
    +		var elems,
    +			i = 0,
    +			ret = [],
    +			insert = jQuery( selector ),
    +			last = insert.length - 1;
    +
    +		for ( ; i <= last; i++ ) {
    +			elems = i === last ? this : this.clone(true);
    +			jQuery( insert[i] )[ original ]( elems );
    +
    +			// Modern browsers can apply jQuery collections as arrays, but oldIE needs a .get()
    +			core_push.apply( ret, elems.get() );
    +		}
    +
    +		return this.pushStack( ret );
    +	};
    +});
    +
    +function getAll( context, tag ) {
    +	var elems, elem,
    +		i = 0,
    +		found = typeof context.getElementsByTagName !== "undefined" ? context.getElementsByTagName( tag || "*" ) :
    +			typeof context.querySelectorAll !== "undefined" ? context.querySelectorAll( tag || "*" ) :
    +			undefined;
    +
    +	if ( !found ) {
    +		for ( found = [], elems = context.childNodes || context; (elem = elems[i]) != null; i++ ) {
    +			if ( !tag || jQuery.nodeName( elem, tag ) ) {
    +				found.push( elem );
    +			} else {
    +				jQuery.merge( found, getAll( elem, tag ) );
    +			}
    +		}
    +	}
    +
    +	return tag === undefined || tag && jQuery.nodeName( context, tag ) ?
    +		jQuery.merge( [ context ], found ) :
    +		found;
    +}
    +
    +// Used in buildFragment, fixes the defaultChecked property
    +function fixDefaultChecked( elem ) {
    +	if ( manipulation_rcheckableType.test( elem.type ) ) {
    +		elem.defaultChecked = elem.checked;
    +	}
    +}
    +
    +jQuery.extend({
    +	clone: function( elem, dataAndEvents, deepDataAndEvents ) {
    +		var destElements, srcElements, node, i, clone,
    +			inPage = jQuery.contains( elem.ownerDocument, elem );
    +
    +		if ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) {
    +			clone = elem.cloneNode( true );
    +
    +		// IE<=8 does not properly clone detached, unknown element nodes
    +		} else {
    +			fragmentDiv.innerHTML = elem.outerHTML;
    +			fragmentDiv.removeChild( clone = fragmentDiv.firstChild );
    +		}
    +
    +		if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&
    +				(elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
    +
    +			// We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2
    +			destElements = getAll( clone );
    +			srcElements = getAll( elem );
    +
    +			// Fix all IE cloning issues
    +			for ( i = 0; (node = srcElements[i]) != null; ++i ) {
    +				// Ensure that the destination node is not null; Fixes #9587
    +				if ( destElements[i] ) {
    +					fixCloneNodeIssues( node, destElements[i] );
    +				}
    +			}
    +		}
    +
    +		// Copy the events from the original to the clone
    +		if ( dataAndEvents ) {
    +			if ( deepDataAndEvents ) {
    +				srcElements = srcElements || getAll( elem );
    +				destElements = destElements || getAll( clone );
    +
    +				for ( i = 0; (node = srcElements[i]) != null; i++ ) {
    +					cloneCopyEvent( node, destElements[i] );
    +				}
    +			} else {
    +				cloneCopyEvent( elem, clone );
    +			}
    +		}
    +
    +		// Preserve script evaluation history
    +		destElements = getAll( clone, "script" );
    +		if ( destElements.length > 0 ) {
    +			setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
    +		}
    +
    +		destElements = srcElements = node = null;
    +
    +		// Return the cloned set
    +		return clone;
    +	},
    +
    +	buildFragment: function( elems, context, scripts, selection ) {
    +		var contains, elem, tag, tmp, wrap, tbody, j,
    +			l = elems.length,
    +
    +			// Ensure a safe fragment
    +			safe = createSafeFragment( context ),
    +
    +			nodes = [],
    +			i = 0;
    +
    +		for ( ; i < l; i++ ) {
    +			elem = elems[ i ];
    +
    +			if ( elem || elem === 0 ) {
    +
    +				// Add nodes directly
    +				if ( jQuery.type( elem ) === "object" ) {
    +					jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );
    +
    +				// Convert non-html into a text node
    +				} else if ( !rhtml.test( elem ) ) {
    +					nodes.push( context.createTextNode( elem ) );
    +
    +				// Convert html into DOM nodes
    +				} else {
    +					tmp = tmp || safe.appendChild( context.createElement("div") );
    +
    +					// Deserialize a standard representation
    +					tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase();
    +					wrap = wrapMap[ tag ] || wrapMap._default;
    +
    +					tmp.innerHTML = wrap[1] + elem.replace( rxhtmlTag, "<$1></$2>" ) + wrap[2];
    +
    +					// Descend through wrappers to the right content
    +					j = wrap[0];
    +					while ( j-- ) {
    +						tmp = tmp.lastChild;
    +					}
    +
    +					// Manually add leading whitespace removed by IE
    +					if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
    +						nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) );
    +					}
    +
    +					// Remove IE's autoinserted <tbody> from table fragments
    +					if ( !jQuery.support.tbody ) {
    +
    +						// String was a <table>, *may* have spurious <tbody>
    +						elem = tag === "table" && !rtbody.test( elem ) ?
    +							tmp.firstChild :
    +
    +							// String was a bare <thead> or <tfoot>
    +							wrap[1] === "<table>" && !rtbody.test( elem ) ?
    +								tmp :
    +								0;
    +
    +						j = elem && elem.childNodes.length;
    +						while ( j-- ) {
    +							if ( jQuery.nodeName( (tbody = elem.childNodes[j]), "tbody" ) && !tbody.childNodes.length ) {
    +								elem.removeChild( tbody );
    +							}
    +						}
    +					}
    +
    +					jQuery.merge( nodes, tmp.childNodes );
    +
    +					// Fix #12392 for WebKit and IE > 9
    +					tmp.textContent = "";
    +
    +					// Fix #12392 for oldIE
    +					while ( tmp.firstChild ) {
    +						tmp.removeChild( tmp.firstChild );
    +					}
    +
    +					// Remember the top-level container for proper cleanup
    +					tmp = safe.lastChild;
    +				}
    +			}
    +		}
    +
    +		// Fix #11356: Clear elements from fragment
    +		if ( tmp ) {
    +			safe.removeChild( tmp );
    +		}
    +
    +		// Reset defaultChecked for any radios and checkboxes
    +		// about to be appended to the DOM in IE 6/7 (#8060)
    +		if ( !jQuery.support.appendChecked ) {
    +			jQuery.grep( getAll( nodes, "input" ), fixDefaultChecked );
    +		}
    +
    +		i = 0;
    +		while ( (elem = nodes[ i++ ]) ) {
    +
    +			// #4087 - If origin and destination elements are the same, and this is
    +			// that element, do not do anything
    +			if ( selection && jQuery.inArray( elem, selection ) !== -1 ) {
    +				continue;
    +			}
    +
    +			contains = jQuery.contains( elem.ownerDocument, elem );
    +
    +			// Append to fragment
    +			tmp = getAll( safe.appendChild( elem ), "script" );
    +
    +			// Preserve script evaluation history
    +			if ( contains ) {
    +				setGlobalEval( tmp );
    +			}
    +
    +			// Capture executables
    +			if ( scripts ) {
    +				j = 0;
    +				while ( (elem = tmp[ j++ ]) ) {
    +					if ( rscriptType.test( elem.type || "" ) ) {
    +						scripts.push( elem );
    +					}
    +				}
    +			}
    +		}
    +
    +		tmp = null;
    +
    +		return safe;
    +	},
    +
    +	cleanData: function( elems, /* internal */ acceptData ) {
    +		var data, id, elem, type,
    +			i = 0,
    +			internalKey = jQuery.expando,
    +			cache = jQuery.cache,
    +			deleteExpando = jQuery.support.deleteExpando,
    +			special = jQuery.event.special;
    +
    +		for ( ; (elem = elems[i]) != null; i++ ) {
    +
    +			if ( acceptData || jQuery.acceptData( elem ) ) {
    +
    +				id = elem[ internalKey ];
    +				data = id && cache[ id ];
    +
    +				if ( data ) {
    +					if ( data.events ) {
    +						for ( type in data.events ) {
    +							if ( special[ type ] ) {
    +								jQuery.event.remove( elem, type );
    +
    +							// This is a shortcut to avoid jQuery.event.remove's overhead
    +							} else {
    +								jQuery.removeEvent( elem, type, data.handle );
    +							}
    +						}
    +					}
    +
    +					// Remove cache only if it was not already removed by jQuery.event.remove
    +					if ( cache[ id ] ) {
    +
    +						delete cache[ id ];
    +
    +						// IE does not allow us to delete expando properties from nodes,
    +						// nor does it have a removeAttribute function on Document nodes;
    +						// we must handle all of these cases
    +						if ( deleteExpando ) {
    +							delete elem[ internalKey ];
    +
    +						} else if ( typeof elem.removeAttribute !== "undefined" ) {
    +							elem.removeAttribute( internalKey );
    +
    +						} else {
    +							elem[ internalKey ] = null;
    +						}
    +
    +						core_deletedIds.push( id );
    +					}
    +				}
    +			}
    +		}
    +	}
    +});
    +var curCSS, getStyles, iframe,
    +	ralpha = /alpha\([^)]*\)/i,
    +	ropacity = /opacity\s*=\s*([^)]*)/,
    +	rposition = /^(top|right|bottom|left)$/,
    +	// swappable if display is none or starts with table except "table", "table-cell", or "table-caption"
    +	// see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
    +	rdisplayswap = /^(none|table(?!-c[ea]).+)/,
    +	rmargin = /^margin/,
    +	rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ),
    +	rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ),
    +	rrelNum = new RegExp( "^([+-])=(" + core_pnum + ")", "i" ),
    +	elemdisplay = { BODY: "block" },
    +
    +	cssShow = { position: "absolute", visibility: "hidden", display: "block" },
    +	cssNormalTransform = {
    +		letterSpacing: 0,
    +		fontWeight: 400
    +	},
    +
    +	cssExpand = [ "Top", "Right", "Bottom", "Left" ],
    +	cssPrefixes = [ "Webkit", "O", "Moz", "ms" ];
    +
    +// return a css property mapped to a potentially vendor prefixed property
    +function vendorPropName( style, name ) {
    +
    +	// shortcut for names that are not vendor prefixed
    +	if ( name in style ) {
    +		return name;
    +	}
    +
    +	// check for vendor prefixed names
    +	var capName = name.charAt(0).toUpperCase() + name.slice(1),
    +		origName = name,
    +		i = cssPrefixes.length;
    +
    +	while ( i-- ) {
    +		name = cssPrefixes[ i ] + capName;
    +		if ( name in style ) {
    +			return name;
    +		}
    +	}
    +
    +	return origName;
    +}
    +
    +function isHidden( elem, el ) {
    +	// isHidden might be called from jQuery#filter function;
    +	// in that case, element will be second argument
    +	elem = el || elem;
    +	return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem );
    +}
    +
    +function showHide( elements, show ) {
    +	var elem,
    +		values = [],
    +		index = 0,
    +		length = elements.length;
    +
    +	for ( ; index < length; index++ ) {
    +		elem = elements[ index ];
    +		if ( !elem.style ) {
    +			continue;
    +		}
    +		values[ index ] = jQuery._data( elem, "olddisplay" );
    +		if ( show ) {
    +			// Reset the inline display of this element to learn if it is
    +			// being hidden by cascaded rules or not
    +			if ( !values[ index ] && elem.style.display === "none" ) {
    +				elem.style.display = "";
    +			}
    +
    +			// Set elements which have been overridden with display: none
    +			// in a stylesheet to whatever the default browser style is
    +			// for such an element
    +			if ( elem.style.display === "" && isHidden( elem ) ) {
    +				values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay(elem.nodeName) );
    +			}
    +		} else if ( !values[ index ] && !isHidden( elem ) ) {
    +			jQuery._data( elem, "olddisplay", jQuery.css( elem, "display" ) );
    +		}
    +	}
    +
    +	// Set the display of most of the elements in a second loop
    +	// to avoid the constant reflow
    +	for ( index = 0; index < length; index++ ) {
    +		elem = elements[ index ];
    +		if ( !elem.style ) {
    +			continue;
    +		}
    +		if ( !show || elem.style.display === "none" || elem.style.display === "" ) {
    +			elem.style.display = show ? values[ index ] || "" : "none";
    +		}
    +	}
    +
    +	return elements;
    +}
    +
    +jQuery.fn.extend({
    +	css: function( name, value ) {
    +		return jQuery.access( this, function( elem, name, value ) {
    +			var styles, len,
    +				map = {},
    +				i = 0;
    +
    +			if ( jQuery.isArray( name ) ) {
    +				styles = getStyles( elem );
    +				len = name.length;
    +
    +				for ( ; i < len; i++ ) {
    +					map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
    +				}
    +
    +				return map;
    +			}
    +
    +			return value !== undefined ?
    +				jQuery.style( elem, name, value ) :
    +				jQuery.css( elem, name );
    +		}, name, value, arguments.length > 1 );
    +	},
    +	show: function() {
    +		return showHide( this, true );
    +	},
    +	hide: function() {
    +		return showHide( this );
    +	},
    +	toggle: function( state ) {
    +		var bool = typeof state === "boolean";
    +
    +		return this.each(function() {
    +			if ( bool ? state : isHidden( this ) ) {
    +				jQuery( this ).show();
    +			} else {
    +				jQuery( this ).hide();
    +			}
    +		});
    +	}
    +});
    +
    +jQuery.extend({
    +	// Add in style property hooks for overriding the default
    +	// behavior of getting and setting a style property
    +	cssHooks: {
    +		opacity: {
    +			get: function( elem, computed ) {
    +				if ( computed ) {
    +					// We should always get a number back from opacity
    +					var ret = curCSS( elem, "opacity" );
    +					return ret === "" ? "1" : ret;
    +				}
    +			}
    +		}
    +	},
    +
    +	// Exclude the following css properties to add px
    +	cssNumber: {
    +		"columnCount": true,
    +		"fillOpacity": true,
    +		"fontWeight": true,
    +		"lineHeight": true,
    +		"opacity": true,
    +		"orphans": true,
    +		"widows": true,
    +		"zIndex": true,
    +		"zoom": true
    +	},
    +
    +	// Add in properties whose names you wish to fix before
    +	// setting or getting the value
    +	cssProps: {
    +		// normalize float css property
    +		"float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
    +	},
    +
    +	// Get and set the style property on a DOM Node
    +	style: function( elem, name, value, extra ) {
    +		// Don't set styles on text and comment nodes
    +		if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
    +			return;
    +		}
    +
    +		// Make sure that we're working with the right name
    +		var ret, type, hooks,
    +			origName = jQuery.camelCase( name ),
    +			style = elem.style;
    +
    +		name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );
    +
    +		// gets hook for the prefixed version
    +		// followed by the unprefixed version
    +		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
    +
    +		// Check if we're setting a value
    +		if ( value !== undefined ) {
    +			type = typeof value;
    +
    +			// convert relative number strings (+= or -=) to relative numbers. #7345
    +			if ( type === "string" && (ret = rrelNum.exec( value )) ) {
    +				value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );
    +				// Fixes bug #9237
    +				type = "number";
    +			}
    +
    +			// Make sure that NaN and null values aren't set. See: #7116
    +			if ( value == null || type === "number" && isNaN( value ) ) {
    +				return;
    +			}
    +
    +			// If a number was passed in, add 'px' to the (except for certain CSS properties)
    +			if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
    +				value += "px";
    +			}
    +
    +			// Fixes #8908, it can be done more correctly by specifing setters in cssHooks,
    +			// but it would mean to define eight (for every problematic property) identical functions
    +			if ( !jQuery.support.clearCloneStyle && value === "" && name.indexOf("background") === 0 ) {
    +				style[ name ] = "inherit";
    +			}
    +
    +			// If a hook was provided, use that value, otherwise just set the specified value
    +			if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {
    +
    +				// Wrapped to prevent IE from throwing errors when 'invalid' values are provided
    +				// Fixes bug #5509
    +				try {
    +					style[ name ] = value;
    +				} catch(e) {}
    +			}
    +
    +		} else {
    +			// If a hook was provided get the non-computed value from there
    +			if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
    +				return ret;
    +			}
    +
    +			// Otherwise just get the value from the style object
    +			return style[ name ];
    +		}
    +	},
    +
    +	css: function( elem, name, extra, styles ) {
    +		var val, num, hooks,
    +			origName = jQuery.camelCase( name );
    +
    +		// Make sure that we're working with the right name
    +		name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );
    +
    +		// gets hook for the prefixed version
    +		// followed by the unprefixed version
    +		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
    +
    +		// If a hook was provided get the computed value from there
    +		if ( hooks && "get" in hooks ) {
    +			val = hooks.get( elem, true, extra );
    +		}
    +
    +		// Otherwise, if a way to get the computed value exists, use that
    +		if ( val === undefined ) {
    +			val = curCSS( elem, name, styles );
    +		}
    +
    +		//convert "normal" to computed value
    +		if ( val === "normal" && name in cssNormalTransform ) {
    +			val = cssNormalTransform[ name ];
    +		}
    +
    +		// Return, converting to number if forced or a qualifier was provided and val looks numeric
    +		if ( extra ) {
    +			num = parseFloat( val );
    +			return extra === true || jQuery.isNumeric( num ) ? num || 0 : val;
    +		}
    +		return val;
    +	},
    +
    +	// A method for quickly swapping in/out CSS properties to get correct calculations
    +	swap: function( elem, options, callback, args ) {
    +		var ret, name,
    +			old = {};
    +
    +		// Remember the old values, and insert the new ones
    +		for ( name in options ) {
    +			old[ name ] = elem.style[ name ];
    +			elem.style[ name ] = options[ name ];
    +		}
    +
    +		ret = callback.apply( elem, args || [] );
    +
    +		// Revert the old values
    +		for ( name in options ) {
    +			elem.style[ name ] = old[ name ];
    +		}
    +
    +		return ret;
    +	}
    +});
    +
    +// NOTE: we've included the "window" in window.getComputedStyle
    +// because jsdom on node.js will break without it.
    +if ( window.getComputedStyle ) {
    +	getStyles = function( elem ) {
    +		return window.getComputedStyle( elem, null );
    +	};
    +
    +	curCSS = function( elem, name, _computed ) {
    +		var width, minWidth, maxWidth,
    +			computed = _computed || getStyles( elem ),
    +
    +			// getPropertyValue is only needed for .css('filter') in IE9, see #12537
    +			ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined,
    +			style = elem.style;
    +
    +		if ( computed ) {
    +
    +			if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) {
    +				ret = jQuery.style( elem, name );
    +			}
    +
    +			// A tribute to the "awesome hack by Dean Edwards"
    +			// Chrome < 17 and Safari 5.0 uses "computed value" instead of "used value" for margin-right
    +			// Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels
    +			// this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values
    +			if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {
    +
    +				// Remember the original values
    +				width = style.width;
    +				minWidth = style.minWidth;
    +				maxWidth = style.maxWidth;
    +
    +				// Put in the new values to get a computed value out
    +				style.minWidth = style.maxWidth = style.width = ret;
    +				ret = computed.width;
    +
    +				// Revert the changed values
    +				style.width = width;
    +				style.minWidth = minWidth;
    +				style.maxWidth = maxWidth;
    +			}
    +		}
    +
    +		return ret;
    +	};
    +} else if ( document.documentElement.currentStyle ) {
    +	getStyles = function( elem ) {
    +		return elem.currentStyle;
    +	};
    +
    +	curCSS = function( elem, name, _computed ) {
    +		var left, rs, rsLeft,
    +			computed = _computed || getStyles( elem ),
    +			ret = computed ? computed[ name ] : undefined,
    +			style = elem.style;
    +
    +		// Avoid setting ret to empty string here
    +		// so we don't default to auto
    +		if ( ret == null && style && style[ name ] ) {
    +			ret = style[ name ];
    +		}
    +
    +		// From the awesome hack by Dean Edwards
    +		// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
    +
    +		// If we're not dealing with a regular pixel number
    +		// but a number that has a weird ending, we need to convert it to pixels
    +		// but not position css attributes, as those are proportional to the parent element instead
    +		// and we can't measure the parent instead because it might trigger a "stacking dolls" problem
    +		if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) {
    +
    +			// Remember the original values
    +			left = style.left;
    +			rs = elem.runtimeStyle;
    +			rsLeft = rs && rs.left;
    +
    +			// Put in the new values to get a computed value out
    +			if ( rsLeft ) {
    +				rs.left = elem.currentStyle.left;
    +			}
    +			style.left = name === "fontSize" ? "1em" : ret;
    +			ret = style.pixelLeft + "px";
    +
    +			// Revert the changed values
    +			style.left = left;
    +			if ( rsLeft ) {
    +				rs.left = rsLeft;
    +			}
    +		}
    +
    +		return ret === "" ? "auto" : ret;
    +	};
    +}
    +
    +function setPositiveNumber( elem, value, subtract ) {
    +	var matches = rnumsplit.exec( value );
    +	return matches ?
    +		// Guard against undefined "subtract", e.g., when used as in cssHooks
    +		Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) :
    +		value;
    +}
    +
    +function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
    +	var i = extra === ( isBorderBox ? "border" : "content" ) ?
    +		// If we already have the right measurement, avoid augmentation
    +		4 :
    +		// Otherwise initialize for horizontal or vertical properties
    +		name === "width" ? 1 : 0,
    +
    +		val = 0;
    +
    +	for ( ; i < 4; i += 2 ) {
    +		// both box models exclude margin, so add it if we want it
    +		if ( extra === "margin" ) {
    +			val += jQuery.css( elem, extra + cssExpand[ i ], true, styles );
    +		}
    +
    +		if ( isBorderBox ) {
    +			// border-box includes padding, so remove it if we want content
    +			if ( extra === "content" ) {
    +				val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
    +			}
    +
    +			// at this point, extra isn't border nor margin, so remove border
    +			if ( extra !== "margin" ) {
    +				val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
    +			}
    +		} else {
    +			// at this point, extra isn't content, so add padding
    +			val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
    +
    +			// at this point, extra isn't content nor padding, so add border
    +			if ( extra !== "padding" ) {
    +				val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
    +			}
    +		}
    +	}
    +
    +	return val;
    +}
    +
    +function getWidthOrHeight( elem, name, extra ) {
    +
    +	// Start with offset property, which is equivalent to the border-box value
    +	var valueIsBorderBox = true,
    +		val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
    +		styles = getStyles( elem ),
    +		isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box";
    +
    +	// some non-html elements return undefined for offsetWidth, so check for null/undefined
    +	// svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
    +	// MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
    +	if ( val <= 0 || val == null ) {
    +		// Fall back to computed then uncomputed css if necessary
    +		val = curCSS( elem, name, styles );
    +		if ( val < 0 || val == null ) {
    +			val = elem.style[ name ];
    +		}
    +
    +		// Computed unit is not pixels. Stop here and return.
    +		if ( rnumnonpx.test(val) ) {
    +			return val;
    +		}
    +
    +		// we need the check for style in case a browser which returns unreliable values
    +		// for getComputedStyle silently falls back to the reliable elem.style
    +		valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] );
    +
    +		// Normalize "", auto, and prepare for extra
    +		val = parseFloat( val ) || 0;
    +	}
    +
    +	// use the active box-sizing model to add/subtract irrelevant styles
    +	return ( val +
    +		augmentWidthOrHeight(
    +			elem,
    +			name,
    +			extra || ( isBorderBox ? "border" : "content" ),
    +			valueIsBorderBox,
    +			styles
    +		)
    +	) + "px";
    +}
    +
    +// Try to determine the default display value of an element
    +function css_defaultDisplay( nodeName ) {
    +	var doc = document,
    +		display = elemdisplay[ nodeName ];
    +
    +	if ( !display ) {
    +		display = actualDisplay( nodeName, doc );
    +
    +		// If the simple way fails, read from inside an iframe
    +		if ( display === "none" || !display ) {
    +			// Use the already-created iframe if possible
    +			iframe = ( iframe ||
    +				jQuery("<iframe frameborder='0' width='0' height='0'/>")
    +				.css( "cssText", "display:block !important" )
    +			).appendTo( doc.documentElement );
    +
    +			// Always write a new HTML skeleton so Webkit and Firefox don't choke on reuse
    +			doc = ( iframe[0].contentWindow || iframe[0].contentDocument ).document;
    +			doc.write("<!doctype html><html><body>");
    +			doc.close();
    +
    +			display = actualDisplay( nodeName, doc );
    +			iframe.detach();
    +		}
    +
    +		// Store the correct default display
    +		elemdisplay[ nodeName ] = display;
    +	}
    +
    +	return display;
    +}
    +
    +// Called ONLY from within css_defaultDisplay
    +function actualDisplay( name, doc ) {
    +	var elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ),
    +		display = jQuery.css( elem[0], "display" );
    +	elem.remove();
    +	return display;
    +}
    +
    +jQuery.each([ "height", "width" ], function( i, name ) {
    +	jQuery.cssHooks[ name ] = {
    +		get: function( elem, computed, extra ) {
    +			if ( computed ) {
    +				// certain elements can have dimension info if we invisibly show them
    +				// however, it must have a current display style that would benefit from this
    +				return elem.offsetWidth === 0 && rdisplayswap.test( jQuery.css( elem, "display" ) ) ?
    +					jQuery.swap( elem, cssShow, function() {
    +						return getWidthOrHeight( elem, name, extra );
    +					}) :
    +					getWidthOrHeight( elem, name, extra );
    +			}
    +		},
    +
    +		set: function( elem, value, extra ) {
    +			var styles = extra && getStyles( elem );
    +			return setPositiveNumber( elem, value, extra ?
    +				augmentWidthOrHeight(
    +					elem,
    +					name,
    +					extra,
    +					jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
    +					styles
    +				) : 0
    +			);
    +		}
    +	};
    +});
    +
    +if ( !jQuery.support.opacity ) {
    +	jQuery.cssHooks.opacity = {
    +		get: function( elem, computed ) {
    +			// IE uses filters for opacity
    +			return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ?
    +				( 0.01 * parseFloat( RegExp.$1 ) ) + "" :
    +				computed ? "1" : "";
    +		},
    +
    +		set: function( elem, value ) {
    +			var style = elem.style,
    +				currentStyle = elem.currentStyle,
    +				opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "",
    +				filter = currentStyle && currentStyle.filter || style.filter || "";
    +
    +			// IE has trouble with opacity if it does not have layout
    +			// Force it by setting the zoom level
    +			style.zoom = 1;
    +
    +			// if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652
    +			// if value === "", then remove inline opacity #12685
    +			if ( ( value >= 1 || value === "" ) &&
    +					jQuery.trim( filter.replace( ralpha, "" ) ) === "" &&
    +					style.removeAttribute ) {
    +
    +				// Setting style.filter to null, "" & " " still leave "filter:" in the cssText
    +				// if "filter:" is present at all, clearType is disabled, we want to avoid this
    +				// style.removeAttribute is IE Only, but so apparently is this code path...
    +				style.removeAttribute( "filter" );
    +
    +				// if there is no filter style applied in a css rule or unset inline opacity, we are done
    +				if ( value === "" || currentStyle && !currentStyle.filter ) {
    +					return;
    +				}
    +			}
    +
    +			// otherwise, set new filter values
    +			style.filter = ralpha.test( filter ) ?
    +				filter.replace( ralpha, opacity ) :
    +				filter + " " + opacity;
    +		}
    +	};
    +}
    +
    +// These hooks cannot be added until DOM ready because the support test
    +// for it is not run until after DOM ready
    +jQuery(function() {
    +	if ( !jQuery.support.reliableMarginRight ) {
    +		jQuery.cssHooks.marginRight = {
    +			get: function( elem, computed ) {
    +				if ( computed ) {
    +					// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
    +					// Work around by temporarily setting element display to inline-block
    +					return jQuery.swap( elem, { "display": "inline-block" },
    +						curCSS, [ elem, "marginRight" ] );
    +				}
    +			}
    +		};
    +	}
    +
    +	// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
    +	// getComputedStyle returns percent when specified for top/left/bottom/right
    +	// rather than make the css module depend on the offset module, we just check for it here
    +	if ( !jQuery.support.pixelPosition && jQuery.fn.position ) {
    +		jQuery.each( [ "top", "left" ], function( i, prop ) {
    +			jQuery.cssHooks[ prop ] = {
    +				get: function( elem, computed ) {
    +					if ( computed ) {
    +						computed = curCSS( elem, prop );
    +						// if curCSS returns percentage, fallback to offset
    +						return rnumnonpx.test( computed ) ?
    +							jQuery( elem ).position()[ prop ] + "px" :
    +							computed;
    +					}
    +				}
    +			};
    +		});
    +	}
    +
    +});
    +
    +if ( jQuery.expr && jQuery.expr.filters ) {
    +	jQuery.expr.filters.hidden = function( elem ) {
    +		return ( elem.offsetWidth === 0 && elem.offsetHeight === 0 ) || (!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || jQuery.css( elem, "display" )) === "none");
    +	};
    +
    +	jQuery.expr.filters.visible = function( elem ) {
    +		return !jQuery.expr.filters.hidden( elem );
    +	};
    +}
    +
    +// These hooks are used by animate to expand properties
    +jQuery.each({
    +	margin: "",
    +	padding: "",
    +	border: "Width"
    +}, function( prefix, suffix ) {
    +	jQuery.cssHooks[ prefix + suffix ] = {
    +		expand: function( value ) {
    +			var i = 0,
    +				expanded = {},
    +
    +				// assumes a single number if not a string
    +				parts = typeof value === "string" ? value.split(" ") : [ value ];
    +
    +			for ( ; i < 4; i++ ) {
    +				expanded[ prefix + cssExpand[ i ] + suffix ] =
    +					parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
    +			}
    +
    +			return expanded;
    +		}
    +	};
    +
    +	if ( !rmargin.test( prefix ) ) {
    +		jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
    +	}
    +});
    +var r20 = /%20/g,
    +	rbracket = /\[\]$/,
    +	rCRLF = /\r?\n/g,
    +	rsubmitterTypes = /^(?:submit|button|image|reset)$/i,
    +	rsubmittable = /^(?:input|select|textarea|keygen)/i;
    +
    +jQuery.fn.extend({
    +	serialize: function() {
    +		return jQuery.param( this.serializeArray() );
    +	},
    +	serializeArray: function() {
    +		return this.map(function(){
    +			// Can add propHook for "elements" to filter or add form elements
    +			var elements = jQuery.prop( this, "elements" );
    +			return elements ? jQuery.makeArray( elements ) : this;
    +		})
    +		.filter(function(){
    +			var type = this.type;
    +			// Use .is(":disabled") so that fieldset[disabled] works
    +			return this.name && !jQuery( this ).is( ":disabled" ) &&
    +				rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
    +				( this.checked || !manipulation_rcheckableType.test( type ) );
    +		})
    +		.map(function( i, elem ){
    +			var val = jQuery( this ).val();
    +
    +			return val == null ?
    +				null :
    +				jQuery.isArray( val ) ?
    +					jQuery.map( val, function( val ){
    +						return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
    +					}) :
    +					{ name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
    +		}).get();
    +	}
    +});
    +
    +//Serialize an array of form elements or a set of
    +//key/values into a query string
    +jQuery.param = function( a, traditional ) {
    +	var prefix,
    +		s = [],
    +		add = function( key, value ) {
    +			// If value is a function, invoke it and return its value
    +			value = jQuery.isFunction( value ) ? value() : ( value == null ? "" : value );
    +			s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
    +		};
    +
    +	// Set traditional to true for jQuery <= 1.3.2 behavior.
    +	if ( traditional === undefined ) {
    +		traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;
    +	}
    +
    +	// If an array was passed in, assume that it is an array of form elements.
    +	if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
    +		// Serialize the form elements
    +		jQuery.each( a, function() {
    +			add( this.name, this.value );
    +		});
    +
    +	} else {
    +		// If traditional, encode the "old" way (the way 1.3.2 or older
    +		// did it), otherwise encode params recursively.
    +		for ( prefix in a ) {
    +			buildParams( prefix, a[ prefix ], traditional, add );
    +		}
    +	}
    +
    +	// Return the resulting serialization
    +	return s.join( "&" ).replace( r20, "+" );
    +};
    +
    +function buildParams( prefix, obj, traditional, add ) {
    +	var name;
    +
    +	if ( jQuery.isArray( obj ) ) {
    +		// Serialize array item.
    +		jQuery.each( obj, function( i, v ) {
    +			if ( traditional || rbracket.test( prefix ) ) {
    +				// Treat each array item as a scalar.
    +				add( prefix, v );
    +
    +			} else {
    +				// Item is non-scalar (array or object), encode its numeric index.
    +				buildParams( prefix + "[" + ( typeof v === "object" ? i : "" ) + "]", v, traditional, add );
    +			}
    +		});
    +
    +	} else if ( !traditional && jQuery.type( obj ) === "object" ) {
    +		// Serialize object item.
    +		for ( name in obj ) {
    +			buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
    +		}
    +
    +	} else {
    +		// Serialize scalar item.
    +		add( prefix, obj );
    +	}
    +}
    +var
    +	// Document location
    +	ajaxLocParts,
    +	ajaxLocation,
    +	
    +	ajax_nonce = jQuery.now(),
    +
    +	ajax_rquery = /\?/,
    +	rhash = /#.*$/,
    +	rts = /([?&])_=[^&]*/,
    +	rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL
    +	// #7653, #8125, #8152: local protocol detection
    +	rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,
    +	rnoContent = /^(?:GET|HEAD)$/,
    +	rprotocol = /^\/\//,
    +	rurl = /^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,
    +
    +	// Keep a copy of the old load method
    +	_load = jQuery.fn.load,
    +
    +	/* Prefilters
    +	 * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
    +	 * 2) These are called:
    +	 *    - BEFORE asking for a transport
    +	 *    - AFTER param serialization (s.data is a string if s.processData is true)
    +	 * 3) key is the dataType
    +	 * 4) the catchall symbol "*" can be used
    +	 * 5) execution will start with transport dataType and THEN continue down to "*" if needed
    +	 */
    +	prefilters = {},
    +
    +	/* Transports bindings
    +	 * 1) key is the dataType
    +	 * 2) the catchall symbol "*" can be used
    +	 * 3) selection will start with transport dataType and THEN go to "*" if needed
    +	 */
    +	transports = {},
    +
    +	// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
    +	allTypes = "*/".concat("*");
    +
    +// #8138, IE may throw an exception when accessing
    +// a field from window.location if document.domain has been set
    +try {
    +	ajaxLocation = location.href;
    +} catch( e ) {
    +	// Use the href attribute of an A element
    +	// since IE will modify it given document.location
    +	ajaxLocation = document.createElement( "a" );
    +	ajaxLocation.href = "";
    +	ajaxLocation = ajaxLocation.href;
    +}
    +
    +// Segment location into parts
    +ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];
    +
    +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
    +function addToPrefiltersOrTransports( structure ) {
    +
    +	// dataTypeExpression is optional and defaults to "*"
    +	return function( dataTypeExpression, func ) {
    +
    +		if ( typeof dataTypeExpression !== "string" ) {
    +			func = dataTypeExpression;
    +			dataTypeExpression = "*";
    +		}
    +
    +		var dataType,
    +			i = 0,
    +			dataTypes = dataTypeExpression.toLowerCase().match( core_rnotwhite ) || [];
    +
    +		if ( jQuery.isFunction( func ) ) {
    +			// For each dataType in the dataTypeExpression
    +			while ( (dataType = dataTypes[i++]) ) {
    +				// Prepend if requested
    +				if ( dataType[0] === "+" ) {
    +					dataType = dataType.slice( 1 ) || "*";
    +					(structure[ dataType ] = structure[ dataType ] || []).unshift( func );
    +
    +				// Otherwise append
    +				} else {
    +					(structure[ dataType ] = structure[ dataType ] || []).push( func );
    +				}
    +			}
    +		}
    +	};
    +}
    +
    +// Base inspection function for prefilters and transports
    +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {
    +
    +	var inspected = {},
    +		seekingTransport = ( structure === transports );
    +
    +	function inspect( dataType ) {
    +		var selected;
    +		inspected[ dataType ] = true;
    +		jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {
    +			var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );
    +			if( typeof dataTypeOrTransport === "string" && !seekingTransport && !inspected[ dataTypeOrTransport ] ) {
    +				options.dataTypes.unshift( dataTypeOrTransport );
    +				inspect( dataTypeOrTransport );
    +				return false;
    +			} else if ( seekingTransport ) {
    +				return !( selected = dataTypeOrTransport );
    +			}
    +		});
    +		return selected;
    +	}
    +
    +	return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" );
    +}
    +
    +// A special extend for ajax options
    +// that takes "flat" options (not to be deep extended)
    +// Fixes #9887
    +function ajaxExtend( target, src ) {
    +	var key, deep,
    +		flatOptions = jQuery.ajaxSettings.flatOptions || {};
    +
    +	for ( key in src ) {
    +		if ( src[ key ] !== undefined ) {
    +			( flatOptions[ key ] ? target : ( deep || (deep = {}) ) )[ key ] = src[ key ];
    +		}
    +	}
    +	if ( deep ) {
    +		jQuery.extend( true, target, deep );
    +	}
    +
    +	return target;
    +}
    +
    +jQuery.fn.load = function( url, params, callback ) {
    +	if ( typeof url !== "string" && _load ) {
    +		return _load.apply( this, arguments );
    +	}
    +
    +	var selector, type, response,
    +		self = this,
    +		off = url.indexOf(" ");
    +
    +	if ( off >= 0 ) {
    +		selector = url.slice( off, url.length );
    +		url = url.slice( 0, off );
    +	}
    +
    +	// If it's a function
    +	if ( jQuery.isFunction( params ) ) {
    +
    +		// We assume that it's the callback
    +		callback = params;
    +		params = undefined;
    +
    +	// Otherwise, build a param string
    +	} else if ( params && typeof params === "object" ) {
    +		type = "POST";
    +	}
    +
    +	// If we have elements to modify, make the request
    +	if ( self.length > 0 ) {
    +		jQuery.ajax({
    +			url: url,
    +
    +			// if "type" variable is undefined, then "GET" method will be used
    +			type: type,
    +			dataType: "html",
    +			data: params
    +		}).done(function( responseText ) {
    +
    +			// Save response for use in complete callback
    +			response = arguments;
    +
    +			self.html( selector ?
    +
    +				// If a selector was specified, locate the right elements in a dummy div
    +				// Exclude scripts to avoid IE 'Permission Denied' errors
    +				jQuery("<div>").append( jQuery.parseHTML( responseText ) ).find( selector ) :
    +
    +				// Otherwise use the full result
    +				responseText );
    +
    +		}).complete( callback && function( jqXHR, status ) {
    +			self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );
    +		});
    +	}
    +
    +	return this;
    +};
    +
    +// Attach a bunch of functions for handling common AJAX events
    +jQuery.each( [ "ajaxStart", "ajaxStop", "ajaxComplete", "ajaxError", "ajaxSuccess", "ajaxSend" ], function( i, type ){
    +	jQuery.fn[ type ] = function( fn ){
    +		return this.on( type, fn );
    +	};
    +});
    +
    +jQuery.each( [ "get", "post" ], function( i, method ) {
    +	jQuery[ method ] = function( url, data, callback, type ) {
    +		// shift arguments if data argument was omitted
    +		if ( jQuery.isFunction( data ) ) {
    +			type = type || callback;
    +			callback = data;
    +			data = undefined;
    +		}
    +
    +		return jQuery.ajax({
    +			url: url,
    +			type: method,
    +			dataType: type,
    +			data: data,
    +			success: callback
    +		});
    +	};
    +});
    +
    +jQuery.extend({
    +
    +	// Counter for holding the number of active queries
    +	active: 0,
    +
    +	// Last-Modified header cache for next request
    +	lastModified: {},
    +	etag: {},
    +
    +	ajaxSettings: {
    +		url: ajaxLocation,
    +		type: "GET",
    +		isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
    +		global: true,
    +		processData: true,
    +		async: true,
    +		contentType: "application/x-www-form-urlencoded; charset=UTF-8",
    +		/*
    +		timeout: 0,
    +		data: null,
    +		dataType: null,
    +		username: null,
    +		password: null,
    +		cache: null,
    +		throws: false,
    +		traditional: false,
    +		headers: {},
    +		*/
    +
    +		accepts: {
    +			"*": allTypes,
    +			text: "text/plain",
    +			html: "text/html",
    +			xml: "application/xml, text/xml",
    +			json: "application/json, text/javascript"
    +		},
    +
    +		contents: {
    +			xml: /xml/,
    +			html: /html/,
    +			json: /json/
    +		},
    +
    +		responseFields: {
    +			xml: "responseXML",
    +			text: "responseText"
    +		},
    +
    +		// Data converters
    +		// Keys separate source (or catchall "*") and destination types with a single space
    +		converters: {
    +
    +			// Convert anything to text
    +			"* text": window.String,
    +
    +			// Text to html (true = no transformation)
    +			"text html": true,
    +
    +			// Evaluate text as a json expression
    +			"text json": jQuery.parseJSON,
    +
    +			// Parse text as xml
    +			"text xml": jQuery.parseXML
    +		},
    +
    +		// For options that shouldn't be deep extended:
    +		// you can add your own custom options here if
    +		// and when you create one that shouldn't be
    +		// deep extended (see ajaxExtend)
    +		flatOptions: {
    +			url: true,
    +			context: true
    +		}
    +	},
    +
    +	// Creates a full fledged settings object into target
    +	// with both ajaxSettings and settings fields.
    +	// If target is omitted, writes into ajaxSettings.
    +	ajaxSetup: function( target, settings ) {
    +		return settings ?
    +
    +			// Building a settings object
    +			ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :
    +
    +			// Extending ajaxSettings
    +			ajaxExtend( jQuery.ajaxSettings, target );
    +	},
    +
    +	ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
    +	ajaxTransport: addToPrefiltersOrTransports( transports ),
    +
    +	// Main method
    +	ajax: function( url, options ) {
    +
    +		// If url is an object, simulate pre-1.5 signature
    +		if ( typeof url === "object" ) {
    +			options = url;
    +			url = undefined;
    +		}
    +
    +		// Force options to be an object
    +		options = options || {};
    +
    +		var transport,
    +			// URL without anti-cache param
    +			cacheURL,
    +			// Response headers
    +			responseHeadersString,
    +			responseHeaders,
    +			// timeout handle
    +			timeoutTimer,
    +			// Cross-domain detection vars
    +			parts,
    +			// To know if global events are to be dispatched
    +			fireGlobals,
    +			// Loop variable
    +			i,
    +			// Create the final options object
    +			s = jQuery.ajaxSetup( {}, options ),
    +			// Callbacks context
    +			callbackContext = s.context || s,
    +			// Context for global events is callbackContext if it is a DOM node or jQuery collection
    +			globalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ?
    +				jQuery( callbackContext ) :
    +				jQuery.event,
    +			// Deferreds
    +			deferred = jQuery.Deferred(),
    +			completeDeferred = jQuery.Callbacks("once memory"),
    +			// Status-dependent callbacks
    +			statusCode = s.statusCode || {},
    +			// Headers (they are sent all at once)
    +			requestHeaders = {},
    +			requestHeadersNames = {},
    +			// The jqXHR state
    +			state = 0,
    +			// Default abort message
    +			strAbort = "canceled",
    +			// Fake xhr
    +			jqXHR = {
    +				readyState: 0,
    +
    +				// Builds headers hashtable if needed
    +				getResponseHeader: function( key ) {
    +					var match;
    +					if ( state === 2 ) {
    +						if ( !responseHeaders ) {
    +							responseHeaders = {};
    +							while ( (match = rheaders.exec( responseHeadersString )) ) {
    +								responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
    +							}
    +						}
    +						match = responseHeaders[ key.toLowerCase() ];
    +					}
    +					return match == null ? null : match;
    +				},
    +
    +				// Raw string
    +				getAllResponseHeaders: function() {
    +					return state === 2 ? responseHeadersString : null;
    +				},
    +
    +				// Caches the header
    +				setRequestHeader: function( name, value ) {
    +					var lname = name.toLowerCase();
    +					if ( !state ) {
    +						name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
    +						requestHeaders[ name ] = value;
    +					}
    +					return this;
    +				},
    +
    +				// Overrides response content-type header
    +				overrideMimeType: function( type ) {
    +					if ( !state ) {
    +						s.mimeType = type;
    +					}
    +					return this;
    +				},
    +
    +				// Status-dependent callbacks
    +				statusCode: function( map ) {
    +					var code;
    +					if ( map ) {
    +						if ( state < 2 ) {
    +							for ( code in map ) {
    +								// Lazy-add the new callback in a way that preserves old ones
    +								statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
    +							}
    +						} else {
    +							// Execute the appropriate callbacks
    +							jqXHR.always( map[ jqXHR.status ] );
    +						}
    +					}
    +					return this;
    +				},
    +
    +				// Cancel the request
    +				abort: function( statusText ) {
    +					var finalText = statusText || strAbort;
    +					if ( transport ) {
    +						transport.abort( finalText );
    +					}
    +					done( 0, finalText );
    +					return this;
    +				}
    +			};
    +
    +		// Attach deferreds
    +		deferred.promise( jqXHR ).complete = completeDeferred.add;
    +		jqXHR.success = jqXHR.done;
    +		jqXHR.error = jqXHR.fail;
    +
    +		// Remove hash character (#7531: and string promotion)
    +		// Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
    +		// Handle falsy url in the settings object (#10093: consistency with old signature)
    +		// We also use the url parameter if available
    +		s.url = ( ( url || s.url || ajaxLocation ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
    +
    +		// Alias method option to type as per ticket #12004
    +		s.type = options.method || options.type || s.method || s.type;
    +
    +		// Extract dataTypes list
    +		s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( core_rnotwhite ) || [""];
    +
    +		// A cross-domain request is in order when we have a protocol:host:port mismatch
    +		if ( s.crossDomain == null ) {
    +			parts = rurl.exec( s.url.toLowerCase() );
    +			s.crossDomain = !!( parts &&
    +				( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] ||
    +					( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) !=
    +						( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) )
    +			);
    +		}
    +
    +		// Convert data if not already a string
    +		if ( s.data && s.processData && typeof s.data !== "string" ) {
    +			s.data = jQuery.param( s.data, s.traditional );
    +		}
    +
    +		// Apply prefilters
    +		inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
    +
    +		// If request was aborted inside a prefilter, stop there
    +		if ( state === 2 ) {
    +			return jqXHR;
    +		}
    +
    +		// We can fire global events as of now if asked to
    +		fireGlobals = s.global;
    +
    +		// Watch for a new set of requests
    +		if ( fireGlobals && jQuery.active++ === 0 ) {
    +			jQuery.event.trigger("ajaxStart");
    +		}
    +
    +		// Uppercase the type
    +		s.type = s.type.toUpperCase();
    +
    +		// Determine if request has content
    +		s.hasContent = !rnoContent.test( s.type );
    +
    +		// Save the URL in case we're toying with the If-Modified-Since
    +		// and/or If-None-Match header later on
    +		cacheURL = s.url;
    +
    +		// More options handling for requests with no content
    +		if ( !s.hasContent ) {
    +
    +			// If data is available, append data to url
    +			if ( s.data ) {
    +				cacheURL = ( s.url += ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + s.data );
    +				// #9682: remove data so that it's not used in an eventual retry
    +				delete s.data;
    +			}
    +
    +			// Add anti-cache in url if needed
    +			if ( s.cache === false ) {
    +				s.url = rts.test( cacheURL ) ?
    +
    +					// If there is already a '_' parameter, set its value
    +					cacheURL.replace( rts, "$1_=" + ajax_nonce++ ) :
    +
    +					// Otherwise add one to the end
    +					cacheURL + ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ajax_nonce++;
    +			}
    +		}
    +
    +		// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
    +		if ( s.ifModified ) {
    +			if ( jQuery.lastModified[ cacheURL ] ) {
    +				jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
    +			}
    +			if ( jQuery.etag[ cacheURL ] ) {
    +				jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
    +			}
    +		}
    +
    +		// Set the correct header, if data is being sent
    +		if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
    +			jqXHR.setRequestHeader( "Content-Type", s.contentType );
    +		}
    +
    +		// Set the Accepts header for the server, depending on the dataType
    +		jqXHR.setRequestHeader(
    +			"Accept",
    +			s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
    +				s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
    +				s.accepts[ "*" ]
    +		);
    +
    +		// Check for headers option
    +		for ( i in s.headers ) {
    +			jqXHR.setRequestHeader( i, s.headers[ i ] );
    +		}
    +
    +		// Allow custom headers/mimetypes and early abort
    +		if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
    +			// Abort if not done already and return
    +			return jqXHR.abort();
    +		}
    +
    +		// aborting is no longer a cancellation
    +		strAbort = "abort";
    +
    +		// Install callbacks on deferreds
    +		for ( i in { success: 1, error: 1, complete: 1 } ) {
    +			jqXHR[ i ]( s[ i ] );
    +		}
    +
    +		// Get transport
    +		transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
    +
    +		// If no transport, we auto-abort
    +		if ( !transport ) {
    +			done( -1, "No Transport" );
    +		} else {
    +			jqXHR.readyState = 1;
    +
    +			// Send global event
    +			if ( fireGlobals ) {
    +				globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
    +			}
    +			// Timeout
    +			if ( s.async && s.timeout > 0 ) {
    +				timeoutTimer = setTimeout(function() {
    +					jqXHR.abort("timeout");
    +				}, s.timeout );
    +			}
    +
    +			try {
    +				state = 1;
    +				transport.send( requestHeaders, done );
    +			} catch ( e ) {
    +				// Propagate exception as error if not done
    +				if ( state < 2 ) {
    +					done( -1, e );
    +				// Simply rethrow otherwise
    +				} else {
    +					throw e;
    +				}
    +			}
    +		}
    +
    +		// Callback for when everything is done
    +		function done( status, nativeStatusText, responses, headers ) {
    +			var isSuccess, success, error, response, modified,
    +				statusText = nativeStatusText;
    +
    +			// Called once
    +			if ( state === 2 ) {
    +				return;
    +			}
    +
    +			// State is "done" now
    +			state = 2;
    +
    +			// Clear timeout if it exists
    +			if ( timeoutTimer ) {
    +				clearTimeout( timeoutTimer );
    +			}
    +
    +			// Dereference transport for early garbage collection
    +			// (no matter how long the jqXHR object will be used)
    +			transport = undefined;
    +
    +			// Cache response headers
    +			responseHeadersString = headers || "";
    +
    +			// Set readyState
    +			jqXHR.readyState = status > 0 ? 4 : 0;
    +
    +			// Get response data
    +			if ( responses ) {
    +				response = ajaxHandleResponses( s, jqXHR, responses );
    +			}
    +
    +			// If successful, handle type chaining
    +			if ( status >= 200 && status < 300 || status === 304 ) {
    +
    +				// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
    +				if ( s.ifModified ) {
    +					modified = jqXHR.getResponseHeader("Last-Modified");
    +					if ( modified ) {
    +						jQuery.lastModified[ cacheURL ] = modified;
    +					}
    +					modified = jqXHR.getResponseHeader("etag");
    +					if ( modified ) {
    +						jQuery.etag[ cacheURL ] = modified;
    +					}
    +				}
    +
    +				// If not modified
    +				if ( status === 304 ) {
    +					isSuccess = true;
    +					statusText = "notmodified";
    +
    +				// If we have data
    +				} else {
    +					isSuccess = ajaxConvert( s, response );
    +					statusText = isSuccess.state;
    +					success = isSuccess.data;
    +					error = isSuccess.error;
    +					isSuccess = !error;
    +				}
    +			} else {
    +				// We extract error from statusText
    +				// then normalize statusText and status for non-aborts
    +				error = statusText;
    +				if ( status || !statusText ) {
    +					statusText = "error";
    +					if ( status < 0 ) {
    +						status = 0;
    +					}
    +				}
    +			}
    +
    +			// Set data for the fake xhr object
    +			jqXHR.status = status;
    +			jqXHR.statusText = ( nativeStatusText || statusText ) + "";
    +
    +			// Success/Error
    +			if ( isSuccess ) {
    +				deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
    +			} else {
    +				deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
    +			}
    +
    +			// Status-dependent callbacks
    +			jqXHR.statusCode( statusCode );
    +			statusCode = undefined;
    +
    +			if ( fireGlobals ) {
    +				globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
    +					[ jqXHR, s, isSuccess ? success : error ] );
    +			}
    +
    +			// Complete
    +			completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
    +
    +			if ( fireGlobals ) {
    +				globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
    +				// Handle the global AJAX counter
    +				if ( !( --jQuery.active ) ) {
    +					jQuery.event.trigger("ajaxStop");
    +				}
    +			}
    +		}
    +
    +		return jqXHR;
    +	},
    +
    +	getScript: function( url, callback ) {
    +		return jQuery.get( url, undefined, callback, "script" );
    +	},
    +
    +	getJSON: function( url, data, callback ) {
    +		return jQuery.get( url, data, callback, "json" );
    +	}
    +});
    +
    +/* Handles responses to an ajax request:
    + * - sets all responseXXX fields accordingly
    + * - finds the right dataType (mediates between content-type and expected dataType)
    + * - returns the corresponding response
    + */
    +function ajaxHandleResponses( s, jqXHR, responses ) {
    +
    +	var ct, type, finalDataType, firstDataType,
    +		contents = s.contents,
    +		dataTypes = s.dataTypes,
    +		responseFields = s.responseFields;
    +
    +	// Fill responseXXX fields
    +	for ( type in responseFields ) {
    +		if ( type in responses ) {
    +			jqXHR[ responseFields[type] ] = responses[ type ];
    +		}
    +	}
    +
    +	// Remove auto dataType and get content-type in the process
    +	while( dataTypes[ 0 ] === "*" ) {
    +		dataTypes.shift();
    +		if ( ct === undefined ) {
    +			ct = s.mimeType || jqXHR.getResponseHeader("Content-Type");
    +		}
    +	}
    +
    +	// Check if we're dealing with a known content-type
    +	if ( ct ) {
    +		for ( type in contents ) {
    +			if ( contents[ type ] && contents[ type ].test( ct ) ) {
    +				dataTypes.unshift( type );
    +				break;
    +			}
    +		}
    +	}
    +
    +	// Check to see if we have a response for the expected dataType
    +	if ( dataTypes[ 0 ] in responses ) {
    +		finalDataType = dataTypes[ 0 ];
    +	} else {
    +		// Try convertible dataTypes
    +		for ( type in responses ) {
    +			if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
    +				finalDataType = type;
    +				break;
    +			}
    +			if ( !firstDataType ) {
    +				firstDataType = type;
    +			}
    +		}
    +		// Or just use first one
    +		finalDataType = finalDataType || firstDataType;
    +	}
    +
    +	// If we found a dataType
    +	// We add the dataType to the list if needed
    +	// and return the corresponding response
    +	if ( finalDataType ) {
    +		if ( finalDataType !== dataTypes[ 0 ] ) {
    +			dataTypes.unshift( finalDataType );
    +		}
    +		return responses[ finalDataType ];
    +	}
    +}
    +
    +// Chain conversions given the request and the original response
    +function ajaxConvert( s, response ) {
    +
    +	var conv, conv2, current, tmp,
    +		converters = {},
    +		i = 0,
    +		// Work with a copy of dataTypes in case we need to modify it for conversion
    +		dataTypes = s.dataTypes.slice(),
    +		prev = dataTypes[ 0 ];
    +
    +	// Apply the dataFilter if provided
    +	if ( s.dataFilter ) {
    +		response = s.dataFilter( response, s.dataType );
    +	}
    +
    +	// Create converters map with lowercased keys
    +	if ( dataTypes[ 1 ] ) {
    +		for ( conv in s.converters ) {
    +			converters[ conv.toLowerCase() ] = s.converters[ conv ];
    +		}
    +	}
    +
    +	// Convert to each sequential dataType, tolerating list modification
    +	for ( ; (current = dataTypes[++i]); ) {
    +
    +		// There's only work to do if current dataType is non-auto
    +		if ( current !== "*" ) {
    +
    +			// Convert response if prev dataType is non-auto and differs from current
    +			if ( prev !== "*" && prev !== current ) {
    +
    +				// Seek a direct converter
    +				conv = converters[ prev + " " + current ] || converters[ "* " + current ];
    +
    +				// If none found, seek a pair
    +				if ( !conv ) {
    +					for ( conv2 in converters ) {
    +
    +						// If conv2 outputs current
    +						tmp = conv2.split(" ");
    +						if ( tmp[ 1 ] === current ) {
    +
    +							// If prev can be converted to accepted input
    +							conv = converters[ prev + " " + tmp[ 0 ] ] ||
    +								converters[ "* " + tmp[ 0 ] ];
    +							if ( conv ) {
    +								// Condense equivalence converters
    +								if ( conv === true ) {
    +									conv = converters[ conv2 ];
    +
    +								// Otherwise, insert the intermediate dataType
    +								} else if ( converters[ conv2 ] !== true ) {
    +									current = tmp[ 0 ];
    +									dataTypes.splice( i--, 0, current );
    +								}
    +
    +								break;
    +							}
    +						}
    +					}
    +				}
    +
    +				// Apply converter (if not an equivalence)
    +				if ( conv !== true ) {
    +
    +					// Unless errors are allowed to bubble, catch and return them
    +					if ( conv && s["throws"] ) {
    +						response = conv( response );
    +					} else {
    +						try {
    +							response = conv( response );
    +						} catch ( e ) {
    +							return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current };
    +						}
    +					}
    +				}
    +			}
    +
    +			// Update prev for next iteration
    +			prev = current;
    +		}
    +	}
    +
    +	return { state: "success", data: response };
    +}
    +// Install script dataType
    +jQuery.ajaxSetup({
    +	accepts: {
    +		script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
    +	},
    +	contents: {
    +		script: /(?:java|ecma)script/
    +	},
    +	converters: {
    +		"text script": function( text ) {
    +			jQuery.globalEval( text );
    +			return text;
    +		}
    +	}
    +});
    +
    +// Handle cache's special case and global
    +jQuery.ajaxPrefilter( "script", function( s ) {
    +	if ( s.cache === undefined ) {
    +		s.cache = false;
    +	}
    +	if ( s.crossDomain ) {
    +		s.type = "GET";
    +		s.global = false;
    +	}
    +});
    +
    +// Bind script tag hack transport
    +jQuery.ajaxTransport( "script", function(s) {
    +
    +	// This transport only deals with cross domain requests
    +	if ( s.crossDomain ) {
    +
    +		var script,
    +			head = document.head || jQuery("head")[0] || document.documentElement;
    +
    +		return {
    +
    +			send: function( _, callback ) {
    +
    +				script = document.createElement("script");
    +
    +				script.async = true;
    +
    +				if ( s.scriptCharset ) {
    +					script.charset = s.scriptCharset;
    +				}
    +
    +				script.src = s.url;
    +
    +				// Attach handlers for all browsers
    +				script.onload = script.onreadystatechange = function( _, isAbort ) {
    +
    +					if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
    +
    +						// Handle memory leak in IE
    +						script.onload = script.onreadystatechange = null;
    +
    +						// Remove the script
    +						if ( script.parentNode ) {
    +							script.parentNode.removeChild( script );
    +						}
    +
    +						// Dereference the script
    +						script = null;
    +
    +						// Callback if not abort
    +						if ( !isAbort ) {
    +							callback( 200, "success" );
    +						}
    +					}
    +				};
    +
    +				// Circumvent IE6 bugs with base elements (#2709 and #4378) by prepending
    +				// Use native DOM manipulation to avoid our domManip AJAX trickery
    +				head.insertBefore( script, head.firstChild );
    +			},
    +
    +			abort: function() {
    +				if ( script ) {
    +					script.onload( undefined, true );
    +				}
    +			}
    +		};
    +	}
    +});
    +var oldCallbacks = [],
    +	rjsonp = /(=)\?(?=&|$)|\?\?/;
    +
    +// Default jsonp settings
    +jQuery.ajaxSetup({
    +	jsonp: "callback",
    +	jsonpCallback: function() {
    +		var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( ajax_nonce++ ) );
    +		this[ callback ] = true;
    +		return callback;
    +	}
    +});
    +
    +// Detect, normalize options and install callbacks for jsonp requests
    +jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
    +
    +	var callbackName, overwritten, responseContainer,
    +		jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
    +			"url" :
    +			typeof s.data === "string" && !( s.contentType || "" ).indexOf("application/x-www-form-urlencoded") && rjsonp.test( s.data ) && "data"
    +		);
    +
    +	// Handle iff the expected data type is "jsonp" or we have a parameter to set
    +	if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {
    +
    +		// Get callback name, remembering preexisting value associated with it
    +		callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?
    +			s.jsonpCallback() :
    +			s.jsonpCallback;
    +
    +		// Insert callback into url or form data
    +		if ( jsonProp ) {
    +			s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
    +		} else if ( s.jsonp !== false ) {
    +			s.url += ( ajax_rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
    +		}
    +
    +		// Use data converter to retrieve json after script execution
    +		s.converters["script json"] = function() {
    +			if ( !responseContainer ) {
    +				jQuery.error( callbackName + " was not called" );
    +			}
    +			return responseContainer[ 0 ];
    +		};
    +
    +		// force json dataType
    +		s.dataTypes[ 0 ] = "json";
    +
    +		// Install callback
    +		overwritten = window[ callbackName ];
    +		window[ callbackName ] = function() {
    +			responseContainer = arguments;
    +		};
    +
    +		// Clean-up function (fires after converters)
    +		jqXHR.always(function() {
    +			// Restore preexisting value
    +			window[ callbackName ] = overwritten;
    +
    +			// Save back as free
    +			if ( s[ callbackName ] ) {
    +				// make sure that re-using the options doesn't screw things around
    +				s.jsonpCallback = originalSettings.jsonpCallback;
    +
    +				// save the callback name for future use
    +				oldCallbacks.push( callbackName );
    +			}
    +
    +			// Call if it was a function and we have a response
    +			if ( responseContainer && jQuery.isFunction( overwritten ) ) {
    +				overwritten( responseContainer[ 0 ] );
    +			}
    +
    +			responseContainer = overwritten = undefined;
    +		});
    +
    +		// Delegate to script
    +		return "script";
    +	}
    +});
    +var xhrCallbacks, xhrSupported,
    +	xhrId = 0,
    +	// #5280: Internet Explorer will keep connections alive if we don't abort on unload
    +	xhrOnUnloadAbort = window.ActiveXObject && function() {
    +		// Abort all pending requests
    +		var key;
    +		for ( key in xhrCallbacks ) {
    +			xhrCallbacks[ key ]( undefined, true );
    +		}
    +	};
    +
    +// Functions to create xhrs
    +function createStandardXHR() {
    +	try {
    +		return new window.XMLHttpRequest();
    +	} catch( e ) {}
    +}
    +
    +function createActiveXHR() {
    +	try {
    +		return new window.ActiveXObject("Microsoft.XMLHTTP");
    +	} catch( e ) {}
    +}
    +
    +// Create the request object
    +// (This is still attached to ajaxSettings for backward compatibility)
    +jQuery.ajaxSettings.xhr = window.ActiveXObject ?
    +	/* Microsoft failed to properly
    +	 * implement the XMLHttpRequest in IE7 (can't request local files),
    +	 * so we use the ActiveXObject when it is available
    +	 * Additionally XMLHttpRequest can be disabled in IE7/IE8 so
    +	 * we need a fallback.
    +	 */
    +	function() {
    +		return !this.isLocal && createStandardXHR() || createActiveXHR();
    +	} :
    +	// For all other browsers, use the standard XMLHttpRequest object
    +	createStandardXHR;
    +
    +// Determine support properties
    +xhrSupported = jQuery.ajaxSettings.xhr();
    +jQuery.support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
    +xhrSupported = jQuery.support.ajax = !!xhrSupported;
    +
    +// Create transport if the browser can provide an xhr
    +if ( xhrSupported ) {
    +
    +	jQuery.ajaxTransport(function( s ) {
    +		// Cross domain only allowed if supported through XMLHttpRequest
    +		if ( !s.crossDomain || jQuery.support.cors ) {
    +
    +			var callback;
    +
    +			return {
    +				send: function( headers, complete ) {
    +
    +					// Get a new xhr
    +					var handle, i,
    +						xhr = s.xhr();
    +
    +					// Open the socket
    +					// Passing null username, generates a login popup on Opera (#2865)
    +					if ( s.username ) {
    +						xhr.open( s.type, s.url, s.async, s.username, s.password );
    +					} else {
    +						xhr.open( s.type, s.url, s.async );
    +					}
    +
    +					// Apply custom fields if provided
    +					if ( s.xhrFields ) {
    +						for ( i in s.xhrFields ) {
    +							xhr[ i ] = s.xhrFields[ i ];
    +						}
    +					}
    +
    +					// Override mime type if needed
    +					if ( s.mimeType && xhr.overrideMimeType ) {
    +						xhr.overrideMimeType( s.mimeType );
    +					}
    +
    +					// X-Requested-With header
    +					// For cross-domain requests, seeing as conditions for a preflight are
    +					// akin to a jigsaw puzzle, we simply never set it to be sure.
    +					// (it can always be set on a per-request basis or even using ajaxSetup)
    +					// For same-domain requests, won't change header if already provided.
    +					if ( !s.crossDomain && !headers["X-Requested-With"] ) {
    +						headers["X-Requested-With"] = "XMLHttpRequest";
    +					}
    +
    +					// Need an extra try/catch for cross domain requests in Firefox 3
    +					try {
    +						for ( i in headers ) {
    +							xhr.setRequestHeader( i, headers[ i ] );
    +						}
    +					} catch( err ) {}
    +
    +					// Do send the request
    +					// This may raise an exception which is actually
    +					// handled in jQuery.ajax (so no try/catch here)
    +					xhr.send( ( s.hasContent && s.data ) || null );
    +
    +					// Listener
    +					callback = function( _, isAbort ) {
    +
    +						var status,
    +							statusText,
    +							responseHeaders,
    +							responses,
    +							xml;
    +
    +						// Firefox throws exceptions when accessing properties
    +						// of an xhr when a network error occurred
    +						// http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)
    +						try {
    +
    +							// Was never called and is aborted or complete
    +							if ( callback && ( isAbort || xhr.readyState === 4 ) ) {
    +
    +								// Only called once
    +								callback = undefined;
    +
    +								// Do not keep as active anymore
    +								if ( handle ) {
    +									xhr.onreadystatechange = jQuery.noop;
    +									if ( xhrOnUnloadAbort ) {
    +										delete xhrCallbacks[ handle ];
    +									}
    +								}
    +
    +								// If it's an abort
    +								if ( isAbort ) {
    +									// Abort it manually if needed
    +									if ( xhr.readyState !== 4 ) {
    +										xhr.abort();
    +									}
    +								} else {
    +									responses = {};
    +									status = xhr.status;
    +									xml = xhr.responseXML;
    +									responseHeaders = xhr.getAllResponseHeaders();
    +
    +									// Construct response list
    +									if ( xml && xml.documentElement /* #4958 */ ) {
    +										responses.xml = xml;
    +									}
    +
    +									// When requesting binary data, IE6-9 will throw an exception
    +									// on any attempt to access responseText (#11426)
    +									if ( typeof xhr.responseText === "string" ) {
    +										responses.text = xhr.responseText;
    +									}
    +
    +									// Firefox throws an exception when accessing
    +									// statusText for faulty cross-domain requests
    +									try {
    +										statusText = xhr.statusText;
    +									} catch( e ) {
    +										// We normalize with Webkit giving an empty statusText
    +										statusText = "";
    +									}
    +
    +									// Filter status for non standard behaviors
    +
    +									// If the request is local and we have data: assume a success
    +									// (success with no data won't get notified, that's the best we
    +									// can do given current implementations)
    +									if ( !status && s.isLocal && !s.crossDomain ) {
    +										status = responses.text ? 200 : 404;
    +									// IE - #1450: sometimes returns 1223 when it should be 204
    +									} else if ( status === 1223 ) {
    +										status = 204;
    +									}
    +								}
    +							}
    +						} catch( firefoxAccessException ) {
    +							if ( !isAbort ) {
    +								complete( -1, firefoxAccessException );
    +							}
    +						}
    +
    +						// Call complete if needed
    +						if ( responses ) {
    +							complete( status, statusText, responses, responseHeaders );
    +						}
    +					};
    +
    +					if ( !s.async ) {
    +						// if we're in sync mode we fire the callback
    +						callback();
    +					} else if ( xhr.readyState === 4 ) {
    +						// (IE6 & IE7) if it's in cache and has been
    +						// retrieved directly we need to fire the callback
    +						setTimeout( callback );
    +					} else {
    +						handle = ++xhrId;
    +						if ( xhrOnUnloadAbort ) {
    +							// Create the active xhrs callbacks list if needed
    +							// and attach the unload handler
    +							if ( !xhrCallbacks ) {
    +								xhrCallbacks = {};
    +								jQuery( window ).unload( xhrOnUnloadAbort );
    +							}
    +							// Add to list of active xhrs callbacks
    +							xhrCallbacks[ handle ] = callback;
    +						}
    +						xhr.onreadystatechange = callback;
    +					}
    +				},
    +
    +				abort: function() {
    +					if ( callback ) {
    +						callback( undefined, true );
    +					}
    +				}
    +			};
    +		}
    +	});
    +}
    +var fxNow, timerId,
    +	rfxtypes = /^(?:toggle|show|hide)$/,
    +	rfxnum = new RegExp( "^(?:([+-])=|)(" + core_pnum + ")([a-z%]*)$", "i" ),
    +	rrun = /queueHooks$/,
    +	animationPrefilters = [ defaultPrefilter ],
    +	tweeners = {
    +		"*": [function( prop, value ) {
    +			var end, unit,
    +				tween = this.createTween( prop, value ),
    +				parts = rfxnum.exec( value ),
    +				target = tween.cur(),
    +				start = +target || 0,
    +				scale = 1,
    +				maxIterations = 20;
    +
    +			if ( parts ) {
    +				end = +parts[2];
    +				unit = parts[3] || ( jQuery.cssNumber[ prop ] ? "" : "px" );
    +
    +				// We need to compute starting value
    +				if ( unit !== "px" && start ) {
    +					// Iteratively approximate from a nonzero starting point
    +					// Prefer the current property, because this process will be trivial if it uses the same units
    +					// Fallback to end or a simple constant
    +					start = jQuery.css( tween.elem, prop, true ) || end || 1;
    +
    +					do {
    +						// If previous iteration zeroed out, double until we get *something*
    +						// Use a string for doubling factor so we don't accidentally see scale as unchanged below
    +						scale = scale || ".5";
    +
    +						// Adjust and apply
    +						start = start / scale;
    +						jQuery.style( tween.elem, prop, start + unit );
    +
    +					// Update scale, tolerating zero or NaN from tween.cur()
    +					// And breaking the loop if scale is unchanged or perfect, or if we've just had enough
    +					} while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations );
    +				}
    +
    +				tween.unit = unit;
    +				tween.start = start;
    +				// If a +=/-= token was provided, we're doing a relative animation
    +				tween.end = parts[1] ? start + ( parts[1] + 1 ) * end : end;
    +			}
    +			return tween;
    +		}]
    +	};
    +
    +// Animations created synchronously will run synchronously
    +function createFxNow() {
    +	setTimeout(function() {
    +		fxNow = undefined;
    +	});
    +	return ( fxNow = jQuery.now() );
    +}
    +
    +function createTweens( animation, props ) {
    +	jQuery.each( props, function( prop, value ) {
    +		var collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ),
    +			index = 0,
    +			length = collection.length;
    +		for ( ; index < length; index++ ) {
    +			if ( collection[ index ].call( animation, prop, value ) ) {
    +
    +				// we're done with this property
    +				return;
    +			}
    +		}
    +	});
    +}
    +
    +function Animation( elem, properties, options ) {
    +	var result,
    +		stopped,
    +		index = 0,
    +		length = animationPrefilters.length,
    +		deferred = jQuery.Deferred().always( function() {
    +			// don't match elem in the :animated selector
    +			delete tick.elem;
    +		}),
    +		tick = function() {
    +			if ( stopped ) {
    +				return false;
    +			}
    +			var currentTime = fxNow || createFxNow(),
    +				remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
    +				// archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497)
    +				temp = remaining / animation.duration || 0,
    +				percent = 1 - temp,
    +				index = 0,
    +				length = animation.tweens.length;
    +
    +			for ( ; index < length ; index++ ) {
    +				animation.tweens[ index ].run( percent );
    +			}
    +
    +			deferred.notifyWith( elem, [ animation, percent, remaining ]);
    +
    +			if ( percent < 1 && length ) {
    +				return remaining;
    +			} else {
    +				deferred.resolveWith( elem, [ animation ] );
    +				return false;
    +			}
    +		},
    +		animation = deferred.promise({
    +			elem: elem,
    +			props: jQuery.extend( {}, properties ),
    +			opts: jQuery.extend( true, { specialEasing: {} }, options ),
    +			originalProperties: properties,
    +			originalOptions: options,
    +			startTime: fxNow || createFxNow(),
    +			duration: options.duration,
    +			tweens: [],
    +			createTween: function( prop, end ) {
    +				var tween = jQuery.Tween( elem, animation.opts, prop, end,
    +						animation.opts.specialEasing[ prop ] || animation.opts.easing );
    +				animation.tweens.push( tween );
    +				return tween;
    +			},
    +			stop: function( gotoEnd ) {
    +				var index = 0,
    +					// if we are going to the end, we want to run all the tweens
    +					// otherwise we skip this part
    +					length = gotoEnd ? animation.tweens.length : 0;
    +				if ( stopped ) {
    +					return this;
    +				}
    +				stopped = true;
    +				for ( ; index < length ; index++ ) {
    +					animation.tweens[ index ].run( 1 );
    +				}
    +
    +				// resolve when we played the last frame
    +				// otherwise, reject
    +				if ( gotoEnd ) {
    +					deferred.resolveWith( elem, [ animation, gotoEnd ] );
    +				} else {
    +					deferred.rejectWith( elem, [ animation, gotoEnd ] );
    +				}
    +				return this;
    +			}
    +		}),
    +		props = animation.props;
    +
    +	propFilter( props, animation.opts.specialEasing );
    +
    +	for ( ; index < length ; index++ ) {
    +		result = animationPrefilters[ index ].call( animation, elem, props, animation.opts );
    +		if ( result ) {
    +			return result;
    +		}
    +	}
    +
    +	createTweens( animation, props );
    +
    +	if ( jQuery.isFunction( animation.opts.start ) ) {
    +		animation.opts.start.call( elem, animation );
    +	}
    +
    +	jQuery.fx.timer(
    +		jQuery.extend( tick, {
    +			elem: elem,
    +			anim: animation,
    +			queue: animation.opts.queue
    +		})
    +	);
    +
    +	// attach callbacks from options
    +	return animation.progress( animation.opts.progress )
    +		.done( animation.opts.done, animation.opts.complete )
    +		.fail( animation.opts.fail )
    +		.always( animation.opts.always );
    +}
    +
    +function propFilter( props, specialEasing ) {
    +	var index, name, easing, value, hooks;
    +
    +	// camelCase, specialEasing and expand cssHook pass
    +	for ( index in props ) {
    +		name = jQuery.camelCase( index );
    +		easing = specialEasing[ name ];
    +		value = props[ index ];
    +		if ( jQuery.isArray( value ) ) {
    +			easing = value[ 1 ];
    +			value = props[ index ] = value[ 0 ];
    +		}
    +
    +		if ( index !== name ) {
    +			props[ name ] = value;
    +			delete props[ index ];
    +		}
    +
    +		hooks = jQuery.cssHooks[ name ];
    +		if ( hooks && "expand" in hooks ) {
    +			value = hooks.expand( value );
    +			delete props[ name ];
    +
    +			// not quite $.extend, this wont overwrite keys already present.
    +			// also - reusing 'index' from above because we have the correct "name"
    +			for ( index in value ) {
    +				if ( !( index in props ) ) {
    +					props[ index ] = value[ index ];
    +					specialEasing[ index ] = easing;
    +				}
    +			}
    +		} else {
    +			specialEasing[ name ] = easing;
    +		}
    +	}
    +}
    +
    +jQuery.Animation = jQuery.extend( Animation, {
    +
    +	tweener: function( props, callback ) {
    +		if ( jQuery.isFunction( props ) ) {
    +			callback = props;
    +			props = [ "*" ];
    +		} else {
    +			props = props.split(" ");
    +		}
    +
    +		var prop,
    +			index = 0,
    +			length = props.length;
    +
    +		for ( ; index < length ; index++ ) {
    +			prop = props[ index ];
    +			tweeners[ prop ] = tweeners[ prop ] || [];
    +			tweeners[ prop ].unshift( callback );
    +		}
    +	},
    +
    +	prefilter: function( callback, prepend ) {
    +		if ( prepend ) {
    +			animationPrefilters.unshift( callback );
    +		} else {
    +			animationPrefilters.push( callback );
    +		}
    +	}
    +});
    +
    +function defaultPrefilter( elem, props, opts ) {
    +	/*jshint validthis:true */
    +	var index, prop, value, length, dataShow, toggle, tween, hooks, oldfire,
    +		anim = this,
    +		style = elem.style,
    +		orig = {},
    +		handled = [],
    +		hidden = elem.nodeType && isHidden( elem );
    +
    +	// handle queue: false promises
    +	if ( !opts.queue ) {
    +		hooks = jQuery._queueHooks( elem, "fx" );
    +		if ( hooks.unqueued == null ) {
    +			hooks.unqueued = 0;
    +			oldfire = hooks.empty.fire;
    +			hooks.empty.fire = function() {
    +				if ( !hooks.unqueued ) {
    +					oldfire();
    +				}
    +			};
    +		}
    +		hooks.unqueued++;
    +
    +		anim.always(function() {
    +			// doing this makes sure that the complete handler will be called
    +			// before this completes
    +			anim.always(function() {
    +				hooks.unqueued--;
    +				if ( !jQuery.queue( elem, "fx" ).length ) {
    +					hooks.empty.fire();
    +				}
    +			});
    +		});
    +	}
    +
    +	// height/width overflow pass
    +	if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) {
    +		// Make sure that nothing sneaks out
    +		// Record all 3 overflow attributes because IE does not
    +		// change the overflow attribute when overflowX and
    +		// overflowY are set to the same value
    +		opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
    +
    +		// Set display property to inline-block for height/width
    +		// animations on inline elements that are having width/height animated
    +		if ( jQuery.css( elem, "display" ) === "inline" &&
    +				jQuery.css( elem, "float" ) === "none" ) {
    +
    +			// inline-level elements accept inline-block;
    +			// block-level elements need to be inline with layout
    +			if ( !jQuery.support.inlineBlockNeedsLayout || css_defaultDisplay( elem.nodeName ) === "inline" ) {
    +				style.display = "inline-block";
    +
    +			} else {
    +				style.zoom = 1;
    +			}
    +		}
    +	}
    +
    +	if ( opts.overflow ) {
    +		style.overflow = "hidden";
    +		if ( !jQuery.support.shrinkWrapBlocks ) {
    +			anim.done(function() {
    +				style.overflow = opts.overflow[ 0 ];
    +				style.overflowX = opts.overflow[ 1 ];
    +				style.overflowY = opts.overflow[ 2 ];
    +			});
    +		}
    +	}
    +
    +
    +	// show/hide pass
    +	for ( index in props ) {
    +		value = props[ index ];
    +		if ( rfxtypes.exec( value ) ) {
    +			delete props[ index ];
    +			toggle = toggle || value === "toggle";
    +			if ( value === ( hidden ? "hide" : "show" ) ) {
    +				continue;
    +			}
    +			handled.push( index );
    +		}
    +	}
    +
    +	length = handled.length;
    +	if ( length ) {
    +		dataShow = jQuery._data( elem, "fxshow" ) || jQuery._data( elem, "fxshow", {} );
    +		if ( "hidden" in dataShow ) {
    +			hidden = dataShow.hidden;
    +		}
    +
    +		// store state if its toggle - enables .stop().toggle() to "reverse"
    +		if ( toggle ) {
    +			dataShow.hidden = !hidden;
    +		}
    +		if ( hidden ) {
    +			jQuery( elem ).show();
    +		} else {
    +			anim.done(function() {
    +				jQuery( elem ).hide();
    +			});
    +		}
    +		anim.done(function() {
    +			var prop;
    +			jQuery._removeData( elem, "fxshow" );
    +			for ( prop in orig ) {
    +				jQuery.style( elem, prop, orig[ prop ] );
    +			}
    +		});
    +		for ( index = 0 ; index < length ; index++ ) {
    +			prop = handled[ index ];
    +			tween = anim.createTween( prop, hidden ? dataShow[ prop ] : 0 );
    +			orig[ prop ] = dataShow[ prop ] || jQuery.style( elem, prop );
    +
    +			if ( !( prop in dataShow ) ) {
    +				dataShow[ prop ] = tween.start;
    +				if ( hidden ) {
    +					tween.end = tween.start;
    +					tween.start = prop === "width" || prop === "height" ? 1 : 0;
    +				}
    +			}
    +		}
    +	}
    +}
    +
    +function Tween( elem, options, prop, end, easing ) {
    +	return new Tween.prototype.init( elem, options, prop, end, easing );
    +}
    +jQuery.Tween = Tween;
    +
    +Tween.prototype = {
    +	constructor: Tween,
    +	init: function( elem, options, prop, end, easing, unit ) {
    +		this.elem = elem;
    +		this.prop = prop;
    +		this.easing = easing || "swing";
    +		this.options = options;
    +		this.start = this.now = this.cur();
    +		this.end = end;
    +		this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
    +	},
    +	cur: function() {
    +		var hooks = Tween.propHooks[ this.prop ];
    +
    +		return hooks && hooks.get ?
    +			hooks.get( this ) :
    +			Tween.propHooks._default.get( this );
    +	},
    +	run: function( percent ) {
    +		var eased,
    +			hooks = Tween.propHooks[ this.prop ];
    +
    +		if ( this.options.duration ) {
    +			this.pos = eased = jQuery.easing[ this.easing ](
    +				percent, this.options.duration * percent, 0, 1, this.options.duration
    +			);
    +		} else {
    +			this.pos = eased = percent;
    +		}
    +		this.now = ( this.end - this.start ) * eased + this.start;
    +
    +		if ( this.options.step ) {
    +			this.options.step.call( this.elem, this.now, this );
    +		}
    +
    +		if ( hooks && hooks.set ) {
    +			hooks.set( this );
    +		} else {
    +			Tween.propHooks._default.set( this );
    +		}
    +		return this;
    +	}
    +};
    +
    +Tween.prototype.init.prototype = Tween.prototype;
    +
    +Tween.propHooks = {
    +	_default: {
    +		get: function( tween ) {
    +			var result;
    +
    +			if ( tween.elem[ tween.prop ] != null &&
    +				(!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {
    +				return tween.elem[ tween.prop ];
    +			}
    +
    +			// passing a non empty string as a 3rd parameter to .css will automatically
    +			// attempt a parseFloat and fallback to a string if the parse fails
    +			// so, simple values such as "10px" are parsed to Float.
    +			// complex values such as "rotate(1rad)" are returned as is.
    +			result = jQuery.css( tween.elem, tween.prop, "auto" );
    +			// Empty strings, null, undefined and "auto" are converted to 0.
    +			return !result || result === "auto" ? 0 : result;
    +		},
    +		set: function( tween ) {
    +			// use step hook for back compat - use cssHook if its there - use .style if its
    +			// available and use plain properties where available
    +			if ( jQuery.fx.step[ tween.prop ] ) {
    +				jQuery.fx.step[ tween.prop ]( tween );
    +			} else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) {
    +				jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
    +			} else {
    +				tween.elem[ tween.prop ] = tween.now;
    +			}
    +		}
    +	}
    +};
    +
    +// Remove in 2.0 - this supports IE8's panic based approach
    +// to setting things on disconnected nodes
    +
    +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
    +	set: function( tween ) {
    +		if ( tween.elem.nodeType && tween.elem.parentNode ) {
    +			tween.elem[ tween.prop ] = tween.now;
    +		}
    +	}
    +};
    +
    +jQuery.each([ "toggle", "show", "hide" ], function( i, name ) {
    +	var cssFn = jQuery.fn[ name ];
    +	jQuery.fn[ name ] = function( speed, easing, callback ) {
    +		return speed == null || typeof speed === "boolean" ?
    +			cssFn.apply( this, arguments ) :
    +			this.animate( genFx( name, true ), speed, easing, callback );
    +	};
    +});
    +
    +jQuery.fn.extend({
    +	fadeTo: function( speed, to, easing, callback ) {
    +
    +		// show any hidden elements after setting opacity to 0
    +		return this.filter( isHidden ).css( "opacity", 0 ).show()
    +
    +			// animate to the value specified
    +			.end().animate({ opacity: to }, speed, easing, callback );
    +	},
    +	animate: function( prop, speed, easing, callback ) {
    +		var empty = jQuery.isEmptyObject( prop ),
    +			optall = jQuery.speed( speed, easing, callback ),
    +			doAnimation = function() {
    +				// Operate on a copy of prop so per-property easing won't be lost
    +				var anim = Animation( this, jQuery.extend( {}, prop ), optall );
    +				doAnimation.finish = function() {
    +					anim.stop( true );
    +				};
    +				// Empty animations, or finishing resolves immediately
    +				if ( empty || jQuery._data( this, "finish" ) ) {
    +					anim.stop( true );
    +				}
    +			};
    +			doAnimation.finish = doAnimation;
    +
    +		return empty || optall.queue === false ?
    +			this.each( doAnimation ) :
    +			this.queue( optall.queue, doAnimation );
    +	},
    +	stop: function( type, clearQueue, gotoEnd ) {
    +		var stopQueue = function( hooks ) {
    +			var stop = hooks.stop;
    +			delete hooks.stop;
    +			stop( gotoEnd );
    +		};
    +
    +		if ( typeof type !== "string" ) {
    +			gotoEnd = clearQueue;
    +			clearQueue = type;
    +			type = undefined;
    +		}
    +		if ( clearQueue && type !== false ) {
    +			this.queue( type || "fx", [] );
    +		}
    +
    +		return this.each(function() {
    +			var dequeue = true,
    +				index = type != null && type + "queueHooks",
    +				timers = jQuery.timers,
    +				data = jQuery._data( this );
    +
    +			if ( index ) {
    +				if ( data[ index ] && data[ index ].stop ) {
    +					stopQueue( data[ index ] );
    +				}
    +			} else {
    +				for ( index in data ) {
    +					if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
    +						stopQueue( data[ index ] );
    +					}
    +				}
    +			}
    +
    +			for ( index = timers.length; index--; ) {
    +				if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
    +					timers[ index ].anim.stop( gotoEnd );
    +					dequeue = false;
    +					timers.splice( index, 1 );
    +				}
    +			}
    +
    +			// start the next in the queue if the last step wasn't forced
    +			// timers currently will call their complete callbacks, which will dequeue
    +			// but only if they were gotoEnd
    +			if ( dequeue || !gotoEnd ) {
    +				jQuery.dequeue( this, type );
    +			}
    +		});
    +	},
    +	finish: function( type ) {
    +		if ( type !== false ) {
    +			type = type || "fx";
    +		}
    +		return this.each(function() {
    +			var index,
    +				data = jQuery._data( this ),
    +				queue = data[ type + "queue" ],
    +				hooks = data[ type + "queueHooks" ],
    +				timers = jQuery.timers,
    +				length = queue ? queue.length : 0;
    +
    +			// enable finishing flag on private data
    +			data.finish = true;
    +
    +			// empty the queue first
    +			jQuery.queue( this, type, [] );
    +
    +			if ( hooks && hooks.cur && hooks.cur.finish ) {
    +				hooks.cur.finish.call( this );
    +			}
    +
    +			// look for any active animations, and finish them
    +			for ( index = timers.length; index--; ) {
    +				if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
    +					timers[ index ].anim.stop( true );
    +					timers.splice( index, 1 );
    +				}
    +			}
    +
    +			// look for any animations in the old queue and finish them
    +			for ( index = 0; index < length; index++ ) {
    +				if ( queue[ index ] && queue[ index ].finish ) {
    +					queue[ index ].finish.call( this );
    +				}
    +			}
    +
    +			// turn off finishing flag
    +			delete data.finish;
    +		});
    +	}
    +});
    +
    +// Generate parameters to create a standard animation
    +function genFx( type, includeWidth ) {
    +	var which,
    +		attrs = { height: type },
    +		i = 0;
    +
    +	// if we include width, step value is 1 to do all cssExpand values,
    +	// if we don't include width, step value is 2 to skip over Left and Right
    +	includeWidth = includeWidth? 1 : 0;
    +	for( ; i < 4 ; i += 2 - includeWidth ) {
    +		which = cssExpand[ i ];
    +		attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
    +	}
    +
    +	if ( includeWidth ) {
    +		attrs.opacity = attrs.width = type;
    +	}
    +
    +	return attrs;
    +}
    +
    +// Generate shortcuts for custom animations
    +jQuery.each({
    +	slideDown: genFx("show"),
    +	slideUp: genFx("hide"),
    +	slideToggle: genFx("toggle"),
    +	fadeIn: { opacity: "show" },
    +	fadeOut: { opacity: "hide" },
    +	fadeToggle: { opacity: "toggle" }
    +}, function( name, props ) {
    +	jQuery.fn[ name ] = function( speed, easing, callback ) {
    +		return this.animate( props, speed, easing, callback );
    +	};
    +});
    +
    +jQuery.speed = function( speed, easing, fn ) {
    +	var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
    +		complete: fn || !fn && easing ||
    +			jQuery.isFunction( speed ) && speed,
    +		duration: speed,
    +		easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
    +	};
    +
    +	opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
    +		opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
    +
    +	// normalize opt.queue - true/undefined/null -> "fx"
    +	if ( opt.queue == null || opt.queue === true ) {
    +		opt.queue = "fx";
    +	}
    +
    +	// Queueing
    +	opt.old = opt.complete;
    +
    +	opt.complete = function() {
    +		if ( jQuery.isFunction( opt.old ) ) {
    +			opt.old.call( this );
    +		}
    +
    +		if ( opt.queue ) {
    +			jQuery.dequeue( this, opt.queue );
    +		}
    +	};
    +
    +	return opt;
    +};
    +
    +jQuery.easing = {
    +	linear: function( p ) {
    +		return p;
    +	},
    +	swing: function( p ) {
    +		return 0.5 - Math.cos( p*Math.PI ) / 2;
    +	}
    +};
    +
    +jQuery.timers = [];
    +jQuery.fx = Tween.prototype.init;
    +jQuery.fx.tick = function() {
    +	var timer,
    +		timers = jQuery.timers,
    +		i = 0;
    +
    +	fxNow = jQuery.now();
    +
    +	for ( ; i < timers.length; i++ ) {
    +		timer = timers[ i ];
    +		// Checks the timer has not already been removed
    +		if ( !timer() && timers[ i ] === timer ) {
    +			timers.splice( i--, 1 );
    +		}
    +	}
    +
    +	if ( !timers.length ) {
    +		jQuery.fx.stop();
    +	}
    +	fxNow = undefined;
    +};
    +
    +jQuery.fx.timer = function( timer ) {
    +	if ( timer() && jQuery.timers.push( timer ) ) {
    +		jQuery.fx.start();
    +	}
    +};
    +
    +jQuery.fx.interval = 13;
    +
    +jQuery.fx.start = function() {
    +	if ( !timerId ) {
    +		timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );
    +	}
    +};
    +
    +jQuery.fx.stop = function() {
    +	clearInterval( timerId );
    +	timerId = null;
    +};
    +
    +jQuery.fx.speeds = {
    +	slow: 600,
    +	fast: 200,
    +	// Default speed
    +	_default: 400
    +};
    +
    +// Back Compat <1.8 extension point
    +jQuery.fx.step = {};
    +
    +if ( jQuery.expr && jQuery.expr.filters ) {
    +	jQuery.expr.filters.animated = function( elem ) {
    +		return jQuery.grep(jQuery.timers, function( fn ) {
    +			return elem === fn.elem;
    +		}).length;
    +	};
    +}
    +jQuery.fn.offset = function( options ) {
    +	if ( arguments.length ) {
    +		return options === undefined ?
    +			this :
    +			this.each(function( i ) {
    +				jQuery.offset.setOffset( this, options, i );
    +			});
    +	}
    +
    +	var docElem, win,
    +		box = { top: 0, left: 0 },
    +		elem = this[ 0 ],
    +		doc = elem && elem.ownerDocument;
    +
    +	if ( !doc ) {
    +		return;
    +	}
    +
    +	docElem = doc.documentElement;
    +
    +	// Make sure it's not a disconnected DOM node
    +	if ( !jQuery.contains( docElem, elem ) ) {
    +		return box;
    +	}
    +
    +	// If we don't have gBCR, just use 0,0 rather than error
    +	// BlackBerry 5, iOS 3 (original iPhone)
    +	if ( typeof elem.getBoundingClientRect !== "undefined" ) {
    +		box = elem.getBoundingClientRect();
    +	}
    +	win = getWindow( doc );
    +	return {
    +		top: box.top  + ( win.pageYOffset || docElem.scrollTop )  - ( docElem.clientTop  || 0 ),
    +		left: box.left + ( win.pageXOffset || docElem.scrollLeft ) - ( docElem.clientLeft || 0 )
    +	};
    +};
    +
    +jQuery.offset = {
    +
    +	setOffset: function( elem, options, i ) {
    +		var position = jQuery.css( elem, "position" );
    +
    +		// set position first, in-case top/left are set even on static elem
    +		if ( position === "static" ) {
    +			elem.style.position = "relative";
    +		}
    +
    +		var curElem = jQuery( elem ),
    +			curOffset = curElem.offset(),
    +			curCSSTop = jQuery.css( elem, "top" ),
    +			curCSSLeft = jQuery.css( elem, "left" ),
    +			calculatePosition = ( position === "absolute" || position === "fixed" ) && jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1,
    +			props = {}, curPosition = {}, curTop, curLeft;
    +
    +		// need to be able to calculate position if either top or left is auto and position is either absolute or fixed
    +		if ( calculatePosition ) {
    +			curPosition = curElem.position();
    +			curTop = curPosition.top;
    +			curLeft = curPosition.left;
    +		} else {
    +			curTop = parseFloat( curCSSTop ) || 0;
    +			curLeft = parseFloat( curCSSLeft ) || 0;
    +		}
    +
    +		if ( jQuery.isFunction( options ) ) {
    +			options = options.call( elem, i, curOffset );
    +		}
    +
    +		if ( options.top != null ) {
    +			props.top = ( options.top - curOffset.top ) + curTop;
    +		}
    +		if ( options.left != null ) {
    +			props.left = ( options.left - curOffset.left ) + curLeft;
    +		}
    +
    +		if ( "using" in options ) {
    +			options.using.call( elem, props );
    +		} else {
    +			curElem.css( props );
    +		}
    +	}
    +};
    +
    +
    +jQuery.fn.extend({
    +
    +	position: function() {
    +		if ( !this[ 0 ] ) {
    +			return;
    +		}
    +
    +		var offsetParent, offset,
    +			parentOffset = { top: 0, left: 0 },
    +			elem = this[ 0 ];
    +
    +		// fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is it's only offset parent
    +		if ( jQuery.css( elem, "position" ) === "fixed" ) {
    +			// we assume that getBoundingClientRect is available when computed position is fixed
    +			offset = elem.getBoundingClientRect();
    +		} else {
    +			// Get *real* offsetParent
    +			offsetParent = this.offsetParent();
    +
    +			// Get correct offsets
    +			offset = this.offset();
    +			if ( !jQuery.nodeName( offsetParent[ 0 ], "html" ) ) {
    +				parentOffset = offsetParent.offset();
    +			}
    +
    +			// Add offsetParent borders
    +			parentOffset.top  += jQuery.css( offsetParent[ 0 ], "borderTopWidth", true );
    +			parentOffset.left += jQuery.css( offsetParent[ 0 ], "borderLeftWidth", true );
    +		}
    +
    +		// Subtract parent offsets and element margins
    +		// note: when an element has margin: auto the offsetLeft and marginLeft
    +		// are the same in Safari causing offset.left to incorrectly be 0
    +		return {
    +			top:  offset.top  - parentOffset.top - jQuery.css( elem, "marginTop", true ),
    +			left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true)
    +		};
    +	},
    +
    +	offsetParent: function() {
    +		return this.map(function() {
    +			var offsetParent = this.offsetParent || document.documentElement;
    +			while ( offsetParent && ( !jQuery.nodeName( offsetParent, "html" ) && jQuery.css( offsetParent, "position") === "static" ) ) {
    +				offsetParent = offsetParent.offsetParent;
    +			}
    +			return offsetParent || document.documentElement;
    +		});
    +	}
    +});
    +
    +
    +// Create scrollLeft and scrollTop methods
    +jQuery.each( {scrollLeft: "pageXOffset", scrollTop: "pageYOffset"}, function( method, prop ) {
    +	var top = /Y/.test( prop );
    +
    +	jQuery.fn[ method ] = function( val ) {
    +		return jQuery.access( this, function( elem, method, val ) {
    +			var win = getWindow( elem );
    +
    +			if ( val === undefined ) {
    +				return win ? (prop in win) ? win[ prop ] :
    +					win.document.documentElement[ method ] :
    +					elem[ method ];
    +			}
    +
    +			if ( win ) {
    +				win.scrollTo(
    +					!top ? val : jQuery( win ).scrollLeft(),
    +					top ? val : jQuery( win ).scrollTop()
    +				);
    +
    +			} else {
    +				elem[ method ] = val;
    +			}
    +		}, method, val, arguments.length, null );
    +	};
    +});
    +
    +function getWindow( elem ) {
    +	return jQuery.isWindow( elem ) ?
    +		elem :
    +		elem.nodeType === 9 ?
    +			elem.defaultView || elem.parentWindow :
    +			false;
    +}
    +// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
    +jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
    +	jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, function( defaultExtra, funcName ) {
    +		// margin is only for outerHeight, outerWidth
    +		jQuery.fn[ funcName ] = function( margin, value ) {
    +			var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
    +				extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
    +
    +			return jQuery.access( this, function( elem, type, value ) {
    +				var doc;
    +
    +				if ( jQuery.isWindow( elem ) ) {
    +					// As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there
    +					// isn't a whole lot we can do. See pull request at this URL for discussion:
    +					// https://github.com/jquery/jquery/pull/764
    +					return elem.document.documentElement[ "client" + name ];
    +				}
    +
    +				// Get document width or height
    +				if ( elem.nodeType === 9 ) {
    +					doc = elem.documentElement;
    +
    +					// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height], whichever is greatest
    +					// unfortunately, this causes bug #3838 in IE6/8 only, but there is currently no good, small way to fix it.
    +					return Math.max(
    +						elem.body[ "scroll" + name ], doc[ "scroll" + name ],
    +						elem.body[ "offset" + name ], doc[ "offset" + name ],
    +						doc[ "client" + name ]
    +					);
    +				}
    +
    +				return value === undefined ?
    +					// Get width or height on the element, requesting but not forcing parseFloat
    +					jQuery.css( elem, type, extra ) :
    +
    +					// Set width or height on the element
    +					jQuery.style( elem, type, value, extra );
    +			}, type, chainable ? margin : undefined, chainable, null );
    +		};
    +	});
    +});
    +// Limit scope pollution from any deprecated API
    +// (function() {
    +
    +// })();
    +// Expose jQuery to the global object
    +window.jQuery = window.$ = jQuery;
    +
    +// Expose jQuery as an AMD module, but only for AMD loaders that
    +// understand the issues with loading multiple versions of jQuery
    +// in a page that all might call define(). The loader will indicate
    +// they have special allowances for multiple jQuery versions by
    +// specifying define.amd.jQuery = true. Register as a named module,
    +// since jQuery can be concatenated with other files that may use define,
    +// but not use a proper concatenation script that understands anonymous
    +// AMD modules. A named AMD is safest and most robust way to register.
    +// Lowercase jquery is used because AMD module names are derived from
    +// file names, and jQuery is normally delivered in a lowercase file name.
    +// Do this after creating the global so that if an AMD module wants to call
    +// noConflict to hide this version of jQuery, it will work.
    +if ( typeof define === "function" && define.amd && define.amd.jQuery ) {
    +	define( "jquery", [], function () { return jQuery; } );
    +}
    +
    +})( window );
    diff --git a/lib/jquery-ui-1.8.13-min.js b/lib/jquery-ui-1.8.13-min.js
    new file mode 100644
    index 000000000..8a2a208ec
    --- /dev/null
    +++ b/lib/jquery-ui-1.8.13-min.js
    @@ -0,0 +1,407 @@
    +/*!
    + * jQuery UI 1.8.13
    + *
    + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
    + * Dual licensed under the MIT or GPL Version 2 licenses.
    + * http://jquery.org/license
    + *
    + * http://docs.jquery.com/UI
    + */
    +(function(a,d){function c(g,e){var i=g.nodeName.toLowerCase();if("area"===i){e=g.parentNode;i=e.name;if(!g.href||!i||e.nodeName.toLowerCase()!=="map")return false;g=a("img[usemap=#"+i+"]")[0];return!!g&&f(g)}return(/input|select|textarea|button|object/.test(i)?!g.disabled:"a"==i?g.href||e:e)&&f(g)}function f(g){return!a(g).parents().andSelf().filter(function(){return a.curCSS(this,"visibility")==="hidden"||a.expr.filters.hidden(this)}).length}a.ui=a.ui||{};if(!a.ui.version){a.extend(a.ui,{version:"1.8.13",
    +keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}});a.fn.extend({_focus:a.fn.focus,focus:function(g,e){return typeof g==="number"?this.each(function(){var i=this;setTimeout(function(){a(i).focus();
    +e&&e.call(i)},g)}):this._focus.apply(this,arguments)},scrollParent:function(){var g;g=a.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(a.curCSS(this,"position",1))&&/(auto|scroll)/.test(a.curCSS(this,"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(a.curCSS(this,"overflow",1)+a.curCSS(this,
    +"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0);return/fixed/.test(this.css("position"))||!g.length?a(document):g},zIndex:function(g){if(g!==d)return this.css("zIndex",g);if(this.length){g=a(this[0]);for(var e;g.length&&g[0]!==document;){e=g.css("position");if(e==="absolute"||e==="relative"||e==="fixed"){e=parseInt(g.css("zIndex"),10);if(!isNaN(e)&&e!==0)return e}g=g.parent()}}return 0},disableSelection:function(){return this.bind((a.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",
    +function(g){g.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}});a.each(["Width","Height"],function(g,e){function i(l,o,n,k){a.each(b,function(){o-=parseFloat(a.curCSS(l,"padding"+this,true))||0;if(n)o-=parseFloat(a.curCSS(l,"border"+this+"Width",true))||0;if(k)o-=parseFloat(a.curCSS(l,"margin"+this,true))||0});return o}var b=e==="Width"?["Left","Right"]:["Top","Bottom"],h=e.toLowerCase(),j={innerWidth:a.fn.innerWidth,innerHeight:a.fn.innerHeight,outerWidth:a.fn.outerWidth,
    +outerHeight:a.fn.outerHeight};a.fn["inner"+e]=function(l){if(l===d)return j["inner"+e].call(this);return this.each(function(){a(this).css(h,i(this,l)+"px")})};a.fn["outer"+e]=function(l,o){if(typeof l!=="number")return j["outer"+e].call(this,l);return this.each(function(){a(this).css(h,i(this,l,true,o)+"px")})}});a.extend(a.expr[":"],{data:function(g,e,i){return!!a.data(g,i[3])},focusable:function(g){return c(g,!isNaN(a.attr(g,"tabindex")))},tabbable:function(g){var e=a.attr(g,"tabindex"),i=isNaN(e);
    +return(i||e>=0)&&c(g,!i)}});a(function(){var g=document.body,e=g.appendChild(e=document.createElement("div"));a.extend(e.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0});a.support.minHeight=e.offsetHeight===100;a.support.selectstart="onselectstart"in e;g.removeChild(e).style.display="none"});a.extend(a.ui,{plugin:{add:function(g,e,i){g=a.ui[g].prototype;for(var b in i){g.plugins[b]=g.plugins[b]||[];g.plugins[b].push([e,i[b]])}},call:function(g,e,i){if((e=g.plugins[e])&&g.element[0].parentNode)for(var b=
    +0;b<e.length;b++)g.options[e[b][0]]&&e[b][1].apply(g.element,i)}},contains:function(g,e){return document.compareDocumentPosition?g.compareDocumentPosition(e)&16:g!==e&&g.contains(e)},hasScroll:function(g,e){if(a(g).css("overflow")==="hidden")return false;e=e&&e==="left"?"scrollLeft":"scrollTop";var i=false;if(g[e]>0)return true;g[e]=1;i=g[e]>0;g[e]=0;return i},isOverAxis:function(g,e,i){return g>e&&g<e+i},isOver:function(g,e,i,b,h,j){return a.ui.isOverAxis(g,i,h)&&a.ui.isOverAxis(e,b,j)}})}})(jQuery);
    +(function(a,d){if(a.cleanData){var c=a.cleanData;a.cleanData=function(g){for(var e=0,i;(i=g[e])!=null;e++)a(i).triggerHandler("remove");c(g)}}else{var f=a.fn.remove;a.fn.remove=function(g,e){return this.each(function(){if(!e)if(!g||a.filter(g,[this]).length)a("*",this).add([this]).each(function(){a(this).triggerHandler("remove")});return f.call(a(this),g,e)})}}a.widget=function(g,e,i){var b=g.split(".")[0],h;g=g.split(".")[1];h=b+"-"+g;if(!i){i=e;e=a.Widget}a.expr[":"][h]=function(j){return!!a.data(j,
    +g)};a[b]=a[b]||{};a[b][g]=function(j,l){arguments.length&&this._createWidget(j,l)};e=new e;e.options=a.extend(true,{},e.options);a[b][g].prototype=a.extend(true,e,{namespace:b,widgetName:g,widgetEventPrefix:a[b][g].prototype.widgetEventPrefix||g,widgetBaseClass:h},i);a.widget.bridge(g,a[b][g])};a.widget.bridge=function(g,e){a.fn[g]=function(i){var b=typeof i==="string",h=Array.prototype.slice.call(arguments,1),j=this;i=!b&&h.length?a.extend.apply(null,[true,i].concat(h)):i;if(b&&i.charAt(0)==="_")return j;
    +b?this.each(function(){var l=a.data(this,g),o=l&&a.isFunction(l[i])?l[i].apply(l,h):l;if(o!==l&&o!==d){j=o;return false}}):this.each(function(){var l=a.data(this,g);l?l.option(i||{})._init():a.data(this,g,new e(i,this))});return j}};a.Widget=function(g,e){arguments.length&&this._createWidget(g,e)};a.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",options:{disabled:false},_createWidget:function(g,e){a.data(e,this.widgetName,this);this.element=a(e);this.options=a.extend(true,{},this.options,
    +this._getCreateOptions(),g);var i=this;this.element.bind("remove."+this.widgetName,function(){i.destroy()});this._create();this._trigger("create");this._init()},_getCreateOptions:function(){return a.metadata&&a.metadata.get(this.element[0])[this.widgetName]},_create:function(){},_init:function(){},destroy:function(){this.element.unbind("."+this.widgetName).removeData(this.widgetName);this.widget().unbind("."+this.widgetName).removeAttr("aria-disabled").removeClass(this.widgetBaseClass+"-disabled ui-state-disabled")},
    +widget:function(){return this.element},option:function(g,e){var i=g;if(arguments.length===0)return a.extend({},this.options);if(typeof g==="string"){if(e===d)return this.options[g];i={};i[g]=e}this._setOptions(i);return this},_setOptions:function(g){var e=this;a.each(g,function(i,b){e._setOption(i,b)});return this},_setOption:function(g,e){this.options[g]=e;if(g==="disabled")this.widget()[e?"addClass":"removeClass"](this.widgetBaseClass+"-disabled ui-state-disabled").attr("aria-disabled",e);return this},
    +enable:function(){return this._setOption("disabled",false)},disable:function(){return this._setOption("disabled",true)},_trigger:function(g,e,i){var b=this.options[g];e=a.Event(e);e.type=(g===this.widgetEventPrefix?g:this.widgetEventPrefix+g).toLowerCase();i=i||{};if(e.originalEvent){g=a.event.props.length;for(var h;g;){h=a.event.props[--g];e[h]=e.originalEvent[h]}}this.element.trigger(e,i);return!(a.isFunction(b)&&b.call(this.element[0],e,i)===false||e.isDefaultPrevented())}}})(jQuery);
    +(function(a){var d=false;a(document).mousedown(function(){d=false});a.widget("ui.mouse",{options:{cancel:":input,option",distance:1,delay:0},_mouseInit:function(){var c=this;this.element.bind("mousedown."+this.widgetName,function(f){return c._mouseDown(f)}).bind("click."+this.widgetName,function(f){if(true===a.data(f.target,c.widgetName+".preventClickEvent")){a.removeData(f.target,c.widgetName+".preventClickEvent");f.stopImmediatePropagation();return false}});this.started=false},_mouseDestroy:function(){this.element.unbind("."+
    +this.widgetName)},_mouseDown:function(c){if(!d){this._mouseStarted&&this._mouseUp(c);this._mouseDownEvent=c;var f=this,g=c.which==1,e=typeof this.options.cancel=="string"?a(c.target).parents().add(c.target).filter(this.options.cancel).length:false;if(!g||e||!this._mouseCapture(c))return true;this.mouseDelayMet=!this.options.delay;if(!this.mouseDelayMet)this._mouseDelayTimer=setTimeout(function(){f.mouseDelayMet=true},this.options.delay);if(this._mouseDistanceMet(c)&&this._mouseDelayMet(c)){this._mouseStarted=
    +this._mouseStart(c)!==false;if(!this._mouseStarted){c.preventDefault();return true}}true===a.data(c.target,this.widgetName+".preventClickEvent")&&a.removeData(c.target,this.widgetName+".preventClickEvent");this._mouseMoveDelegate=function(i){return f._mouseMove(i)};this._mouseUpDelegate=function(i){return f._mouseUp(i)};a(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate);c.preventDefault();return d=true}},_mouseMove:function(c){if(a.browser.msie&&
    +!(document.documentMode>=9)&&!c.button)return this._mouseUp(c);if(this._mouseStarted){this._mouseDrag(c);return c.preventDefault()}if(this._mouseDistanceMet(c)&&this._mouseDelayMet(c))(this._mouseStarted=this._mouseStart(this._mouseDownEvent,c)!==false)?this._mouseDrag(c):this._mouseUp(c);return!this._mouseStarted},_mouseUp:function(c){a(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate);if(this._mouseStarted){this._mouseStarted=
    +false;c.target==this._mouseDownEvent.target&&a.data(c.target,this.widgetName+".preventClickEvent",true);this._mouseStop(c)}return false},_mouseDistanceMet:function(c){return Math.max(Math.abs(this._mouseDownEvent.pageX-c.pageX),Math.abs(this._mouseDownEvent.pageY-c.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return true}})})(jQuery);
    +(function(a){a.widget("ui.draggable",a.ui.mouse,{widgetEventPrefix:"drag",options:{addClasses:true,appendTo:"parent",axis:false,connectToSortable:false,containment:false,cursor:"auto",cursorAt:false,grid:false,handle:false,helper:"original",iframeFix:false,opacity:false,refreshPositions:false,revert:false,revertDuration:500,scope:"default",scroll:true,scrollSensitivity:20,scrollSpeed:20,snap:false,snapMode:"both",snapTolerance:20,stack:false,zIndex:false},_create:function(){if(this.options.helper==
    +"original"&&!/^(?:r|a|f)/.test(this.element.css("position")))this.element[0].style.position="relative";this.options.addClasses&&this.element.addClass("ui-draggable");this.options.disabled&&this.element.addClass("ui-draggable-disabled");this._mouseInit()},destroy:function(){if(this.element.data("draggable")){this.element.removeData("draggable").unbind(".draggable").removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled");this._mouseDestroy();return this}},_mouseCapture:function(d){var c=
    +this.options;if(this.helper||c.disabled||a(d.target).is(".ui-resizable-handle"))return false;this.handle=this._getHandle(d);if(!this.handle)return false;a(c.iframeFix===true?"iframe":c.iframeFix).each(function(){a('<div class="ui-draggable-iframeFix" style="background: #fff;"></div>').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1E3}).css(a(this).offset()).appendTo("body")});return true},_mouseStart:function(d){var c=this.options;this.helper=
    +this._createHelper(d);this._cacheHelperProportions();if(a.ui.ddmanager)a.ui.ddmanager.current=this;this._cacheMargins();this.cssPosition=this.helper.css("position");this.scrollParent=this.helper.scrollParent();this.offset=this.positionAbs=this.element.offset();this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left};a.extend(this.offset,{click:{left:d.pageX-this.offset.left,top:d.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});
    +this.originalPosition=this.position=this._generatePosition(d);this.originalPageX=d.pageX;this.originalPageY=d.pageY;c.cursorAt&&this._adjustOffsetFromHelper(c.cursorAt);c.containment&&this._setContainment();if(this._trigger("start",d)===false){this._clear();return false}this._cacheHelperProportions();a.ui.ddmanager&&!c.dropBehaviour&&a.ui.ddmanager.prepareOffsets(this,d);this.helper.addClass("ui-draggable-dragging");this._mouseDrag(d,true);return true},_mouseDrag:function(d,c){this.position=this._generatePosition(d);
    +this.positionAbs=this._convertPositionTo("absolute");if(!c){c=this._uiHash();if(this._trigger("drag",d,c)===false){this._mouseUp({});return false}this.position=c.position}if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";a.ui.ddmanager&&a.ui.ddmanager.drag(this,d);return false},_mouseStop:function(d){var c=false;if(a.ui.ddmanager&&!this.options.dropBehaviour)c=
    +a.ui.ddmanager.drop(this,d);if(this.dropped){c=this.dropped;this.dropped=false}if((!this.element[0]||!this.element[0].parentNode)&&this.options.helper=="original")return false;if(this.options.revert=="invalid"&&!c||this.options.revert=="valid"&&c||this.options.revert===true||a.isFunction(this.options.revert)&&this.options.revert.call(this.element,c)){var f=this;a(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){f._trigger("stop",d)!==false&&f._clear()})}else this._trigger("stop",
    +d)!==false&&this._clear();return false},_mouseUp:function(d){this.options.iframeFix===true&&a("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)});return a.ui.mouse.prototype._mouseUp.call(this,d)},cancel:function(){this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear();return this},_getHandle:function(d){var c=!this.options.handle||!a(this.options.handle,this.element).length?true:false;a(this.options.handle,this.element).find("*").andSelf().each(function(){if(this==
    +d.target)c=true});return c},_createHelper:function(d){var c=this.options;d=a.isFunction(c.helper)?a(c.helper.apply(this.element[0],[d])):c.helper=="clone"?this.element.clone().removeAttr("id"):this.element;d.parents("body").length||d.appendTo(c.appendTo=="parent"?this.element[0].parentNode:c.appendTo);d[0]!=this.element[0]&&!/(fixed|absolute)/.test(d.css("position"))&&d.css("position","absolute");return d},_adjustOffsetFromHelper:function(d){if(typeof d=="string")d=d.split(" ");if(a.isArray(d))d=
    +{left:+d[0],top:+d[1]||0};if("left"in d)this.offset.click.left=d.left+this.margins.left;if("right"in d)this.offset.click.left=this.helperProportions.width-d.right+this.margins.left;if("top"in d)this.offset.click.top=d.top+this.margins.top;if("bottom"in d)this.offset.click.top=this.helperProportions.height-d.bottom+this.margins.top},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var d=this.offsetParent.offset();if(this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&
    +a.ui.contains(this.scrollParent[0],this.offsetParent[0])){d.left+=this.scrollParent.scrollLeft();d.top+=this.scrollParent.scrollTop()}if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&a.browser.msie)d={top:0,left:0};return{top:d.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:d.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var d=
    +this.element.position();return{top:d.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:d.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}else return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions=
    +{width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var d=this.options;if(d.containment=="parent")d.containment=this.helper[0].parentNode;if(d.containment=="document"||d.containment=="window")this.containment=[(d.containment=="document"?0:a(window).scrollLeft())-this.offset.relative.left-this.offset.parent.left,(d.containment=="document"?0:a(window).scrollTop())-this.offset.relative.top-this.offset.parent.top,(d.containment=="document"?0:a(window).scrollLeft())+
    +a(d.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(d.containment=="document"?0:a(window).scrollTop())+(a(d.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(d.containment)&&d.containment.constructor!=Array){d=a(d.containment);var c=d[0];if(c){d.offset();var f=a(c).css("overflow")!="hidden";this.containment=[(parseInt(a(c).css("borderLeftWidth"),
    +10)||0)+(parseInt(a(c).css("paddingLeft"),10)||0),(parseInt(a(c).css("borderTopWidth"),10)||0)+(parseInt(a(c).css("paddingTop"),10)||0),(f?Math.max(c.scrollWidth,c.offsetWidth):c.offsetWidth)-(parseInt(a(c).css("borderLeftWidth"),10)||0)-(parseInt(a(c).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(f?Math.max(c.scrollHeight,c.offsetHeight):c.offsetHeight)-(parseInt(a(c).css("borderTopWidth"),10)||0)-(parseInt(a(c).css("paddingBottom"),10)||0)-this.helperProportions.height-
    +this.margins.top-this.margins.bottom];this.relative_container=d}}else if(d.containment.constructor==Array)this.containment=d.containment},_convertPositionTo:function(d,c){if(!c)c=this.position;d=d=="absolute"?1:-1;var f=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,g=/(html|body)/i.test(f[0].tagName);return{top:c.top+this.offset.relative.top*d+this.offset.parent.top*d-(a.browser.safari&&
    +a.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():g?0:f.scrollTop())*d),left:c.left+this.offset.relative.left*d+this.offset.parent.left*d-(a.browser.safari&&a.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():g?0:f.scrollLeft())*d)}},_generatePosition:function(d){var c=this.options,f=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],
    +this.offsetParent[0]))?this.offsetParent:this.scrollParent,g=/(html|body)/i.test(f[0].tagName),e=d.pageX,i=d.pageY;if(this.originalPosition){var b;if(this.containment){if(this.relative_container){b=this.relative_container.offset();b=[this.containment[0]+b.left,this.containment[1]+b.top,this.containment[2]+b.left,this.containment[3]+b.top]}else b=this.containment;if(d.pageX-this.offset.click.left<b[0])e=b[0]+this.offset.click.left;if(d.pageY-this.offset.click.top<b[1])i=b[1]+this.offset.click.top;
    +if(d.pageX-this.offset.click.left>b[2])e=b[2]+this.offset.click.left;if(d.pageY-this.offset.click.top>b[3])i=b[3]+this.offset.click.top}if(c.grid){i=this.originalPageY+Math.round((i-this.originalPageY)/c.grid[1])*c.grid[1];i=b?!(i-this.offset.click.top<b[1]||i-this.offset.click.top>b[3])?i:!(i-this.offset.click.top<b[1])?i-c.grid[1]:i+c.grid[1]:i;e=this.originalPageX+Math.round((e-this.originalPageX)/c.grid[0])*c.grid[0];e=b?!(e-this.offset.click.left<b[0]||e-this.offset.click.left>b[2])?e:!(e-this.offset.click.left<
    +b[0])?e-c.grid[0]:e+c.grid[0]:e}}return{top:i-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+(a.browser.safari&&a.browser.version<526&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollTop():g?0:f.scrollTop()),left:e-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+(a.browser.safari&&a.browser.version<526&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():g?0:f.scrollLeft())}},_clear:function(){this.helper.removeClass("ui-draggable-dragging");
    +this.helper[0]!=this.element[0]&&!this.cancelHelperRemoval&&this.helper.remove();this.helper=null;this.cancelHelperRemoval=false},_trigger:function(d,c,f){f=f||this._uiHash();a.ui.plugin.call(this,d,[c,f]);if(d=="drag")this.positionAbs=this._convertPositionTo("absolute");return a.Widget.prototype._trigger.call(this,d,c,f)},plugins:{},_uiHash:function(){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}});a.extend(a.ui.draggable,{version:"1.8.13"});
    +a.ui.plugin.add("draggable","connectToSortable",{start:function(d,c){var f=a(this).data("draggable"),g=f.options,e=a.extend({},c,{item:f.element});f.sortables=[];a(g.connectToSortable).each(function(){var i=a.data(this,"sortable");if(i&&!i.options.disabled){f.sortables.push({instance:i,shouldRevert:i.options.revert});i.refreshPositions();i._trigger("activate",d,e)}})},stop:function(d,c){var f=a(this).data("draggable"),g=a.extend({},c,{item:f.element});a.each(f.sortables,function(){if(this.instance.isOver){this.instance.isOver=
    +0;f.cancelHelperRemoval=true;this.instance.cancelHelperRemoval=false;if(this.shouldRevert)this.instance.options.revert=true;this.instance._mouseStop(d);this.instance.options.helper=this.instance.options._helper;f.options.helper=="original"&&this.instance.currentItem.css({top:"auto",left:"auto"})}else{this.instance.cancelHelperRemoval=false;this.instance._trigger("deactivate",d,g)}})},drag:function(d,c){var f=a(this).data("draggable"),g=this;a.each(f.sortables,function(){this.instance.positionAbs=
    +f.positionAbs;this.instance.helperProportions=f.helperProportions;this.instance.offset.click=f.offset.click;if(this.instance._intersectsWith(this.instance.containerCache)){if(!this.instance.isOver){this.instance.isOver=1;this.instance.currentItem=a(g).clone().removeAttr("id").appendTo(this.instance.element).data("sortable-item",true);this.instance.options._helper=this.instance.options.helper;this.instance.options.helper=function(){return c.helper[0]};d.target=this.instance.currentItem[0];this.instance._mouseCapture(d,
    +true);this.instance._mouseStart(d,true,true);this.instance.offset.click.top=f.offset.click.top;this.instance.offset.click.left=f.offset.click.left;this.instance.offset.parent.left-=f.offset.parent.left-this.instance.offset.parent.left;this.instance.offset.parent.top-=f.offset.parent.top-this.instance.offset.parent.top;f._trigger("toSortable",d);f.dropped=this.instance.element;f.currentItem=f.element;this.instance.fromOutside=f}this.instance.currentItem&&this.instance._mouseDrag(d)}else if(this.instance.isOver){this.instance.isOver=
    +0;this.instance.cancelHelperRemoval=true;this.instance.options.revert=false;this.instance._trigger("out",d,this.instance._uiHash(this.instance));this.instance._mouseStop(d,true);this.instance.options.helper=this.instance.options._helper;this.instance.currentItem.remove();this.instance.placeholder&&this.instance.placeholder.remove();f._trigger("fromSortable",d);f.dropped=false}})}});a.ui.plugin.add("draggable","cursor",{start:function(){var d=a("body"),c=a(this).data("draggable").options;if(d.css("cursor"))c._cursor=
    +d.css("cursor");d.css("cursor",c.cursor)},stop:function(){var d=a(this).data("draggable").options;d._cursor&&a("body").css("cursor",d._cursor)}});a.ui.plugin.add("draggable","opacity",{start:function(d,c){d=a(c.helper);c=a(this).data("draggable").options;if(d.css("opacity"))c._opacity=d.css("opacity");d.css("opacity",c.opacity)},stop:function(d,c){d=a(this).data("draggable").options;d._opacity&&a(c.helper).css("opacity",d._opacity)}});a.ui.plugin.add("draggable","scroll",{start:function(){var d=a(this).data("draggable");
    +if(d.scrollParent[0]!=document&&d.scrollParent[0].tagName!="HTML")d.overflowOffset=d.scrollParent.offset()},drag:function(d){var c=a(this).data("draggable"),f=c.options,g=false;if(c.scrollParent[0]!=document&&c.scrollParent[0].tagName!="HTML"){if(!f.axis||f.axis!="x")if(c.overflowOffset.top+c.scrollParent[0].offsetHeight-d.pageY<f.scrollSensitivity)c.scrollParent[0].scrollTop=g=c.scrollParent[0].scrollTop+f.scrollSpeed;else if(d.pageY-c.overflowOffset.top<f.scrollSensitivity)c.scrollParent[0].scrollTop=
    +g=c.scrollParent[0].scrollTop-f.scrollSpeed;if(!f.axis||f.axis!="y")if(c.overflowOffset.left+c.scrollParent[0].offsetWidth-d.pageX<f.scrollSensitivity)c.scrollParent[0].scrollLeft=g=c.scrollParent[0].scrollLeft+f.scrollSpeed;else if(d.pageX-c.overflowOffset.left<f.scrollSensitivity)c.scrollParent[0].scrollLeft=g=c.scrollParent[0].scrollLeft-f.scrollSpeed}else{if(!f.axis||f.axis!="x")if(d.pageY-a(document).scrollTop()<f.scrollSensitivity)g=a(document).scrollTop(a(document).scrollTop()-f.scrollSpeed);
    +else if(a(window).height()-(d.pageY-a(document).scrollTop())<f.scrollSensitivity)g=a(document).scrollTop(a(document).scrollTop()+f.scrollSpeed);if(!f.axis||f.axis!="y")if(d.pageX-a(document).scrollLeft()<f.scrollSensitivity)g=a(document).scrollLeft(a(document).scrollLeft()-f.scrollSpeed);else if(a(window).width()-(d.pageX-a(document).scrollLeft())<f.scrollSensitivity)g=a(document).scrollLeft(a(document).scrollLeft()+f.scrollSpeed)}g!==false&&a.ui.ddmanager&&!f.dropBehaviour&&a.ui.ddmanager.prepareOffsets(c,
    +d)}});a.ui.plugin.add("draggable","snap",{start:function(){var d=a(this).data("draggable"),c=d.options;d.snapElements=[];a(c.snap.constructor!=String?c.snap.items||":data(draggable)":c.snap).each(function(){var f=a(this),g=f.offset();this!=d.element[0]&&d.snapElements.push({item:this,width:f.outerWidth(),height:f.outerHeight(),top:g.top,left:g.left})})},drag:function(d,c){for(var f=a(this).data("draggable"),g=f.options,e=g.snapTolerance,i=c.offset.left,b=i+f.helperProportions.width,h=c.offset.top,
    +j=h+f.helperProportions.height,l=f.snapElements.length-1;l>=0;l--){var o=f.snapElements[l].left,n=o+f.snapElements[l].width,k=f.snapElements[l].top,m=k+f.snapElements[l].height;if(o-e<i&&i<n+e&&k-e<h&&h<m+e||o-e<i&&i<n+e&&k-e<j&&j<m+e||o-e<b&&b<n+e&&k-e<h&&h<m+e||o-e<b&&b<n+e&&k-e<j&&j<m+e){if(g.snapMode!="inner"){var p=Math.abs(k-j)<=e,q=Math.abs(m-h)<=e,s=Math.abs(o-b)<=e,r=Math.abs(n-i)<=e;if(p)c.position.top=f._convertPositionTo("relative",{top:k-f.helperProportions.height,left:0}).top-f.margins.top;
    +if(q)c.position.top=f._convertPositionTo("relative",{top:m,left:0}).top-f.margins.top;if(s)c.position.left=f._convertPositionTo("relative",{top:0,left:o-f.helperProportions.width}).left-f.margins.left;if(r)c.position.left=f._convertPositionTo("relative",{top:0,left:n}).left-f.margins.left}var u=p||q||s||r;if(g.snapMode!="outer"){p=Math.abs(k-h)<=e;q=Math.abs(m-j)<=e;s=Math.abs(o-i)<=e;r=Math.abs(n-b)<=e;if(p)c.position.top=f._convertPositionTo("relative",{top:k,left:0}).top-f.margins.top;if(q)c.position.top=
    +f._convertPositionTo("relative",{top:m-f.helperProportions.height,left:0}).top-f.margins.top;if(s)c.position.left=f._convertPositionTo("relative",{top:0,left:o}).left-f.margins.left;if(r)c.position.left=f._convertPositionTo("relative",{top:0,left:n-f.helperProportions.width}).left-f.margins.left}if(!f.snapElements[l].snapping&&(p||q||s||r||u))f.options.snap.snap&&f.options.snap.snap.call(f.element,d,a.extend(f._uiHash(),{snapItem:f.snapElements[l].item}));f.snapElements[l].snapping=p||q||s||r||u}else{f.snapElements[l].snapping&&
    +f.options.snap.release&&f.options.snap.release.call(f.element,d,a.extend(f._uiHash(),{snapItem:f.snapElements[l].item}));f.snapElements[l].snapping=false}}}});a.ui.plugin.add("draggable","stack",{start:function(){var d=a(this).data("draggable").options;d=a.makeArray(a(d.stack)).sort(function(f,g){return(parseInt(a(f).css("zIndex"),10)||0)-(parseInt(a(g).css("zIndex"),10)||0)});if(d.length){var c=parseInt(d[0].style.zIndex)||0;a(d).each(function(f){this.style.zIndex=c+f});this[0].style.zIndex=c+d.length}}});
    +a.ui.plugin.add("draggable","zIndex",{start:function(d,c){d=a(c.helper);c=a(this).data("draggable").options;if(d.css("zIndex"))c._zIndex=d.css("zIndex");d.css("zIndex",c.zIndex)},stop:function(d,c){d=a(this).data("draggable").options;d._zIndex&&a(c.helper).css("zIndex",d._zIndex)}})})(jQuery);
    +(function(a){a.widget("ui.droppable",{widgetEventPrefix:"drop",options:{accept:"*",activeClass:false,addClasses:true,greedy:false,hoverClass:false,scope:"default",tolerance:"intersect"},_create:function(){var d=this.options,c=d.accept;this.isover=0;this.isout=1;this.accept=a.isFunction(c)?c:function(f){return f.is(c)};this.proportions={width:this.element[0].offsetWidth,height:this.element[0].offsetHeight};a.ui.ddmanager.droppables[d.scope]=a.ui.ddmanager.droppables[d.scope]||[];a.ui.ddmanager.droppables[d.scope].push(this);
    +d.addClasses&&this.element.addClass("ui-droppable")},destroy:function(){for(var d=a.ui.ddmanager.droppables[this.options.scope],c=0;c<d.length;c++)d[c]==this&&d.splice(c,1);this.element.removeClass("ui-droppable ui-droppable-disabled").removeData("droppable").unbind(".droppable");return this},_setOption:function(d,c){if(d=="accept")this.accept=a.isFunction(c)?c:function(f){return f.is(c)};a.Widget.prototype._setOption.apply(this,arguments)},_activate:function(d){var c=a.ui.ddmanager.current;this.options.activeClass&&
    +this.element.addClass(this.options.activeClass);c&&this._trigger("activate",d,this.ui(c))},_deactivate:function(d){var c=a.ui.ddmanager.current;this.options.activeClass&&this.element.removeClass(this.options.activeClass);c&&this._trigger("deactivate",d,this.ui(c))},_over:function(d){var c=a.ui.ddmanager.current;if(!(!c||(c.currentItem||c.element)[0]==this.element[0]))if(this.accept.call(this.element[0],c.currentItem||c.element)){this.options.hoverClass&&this.element.addClass(this.options.hoverClass);
    +this._trigger("over",d,this.ui(c))}},_out:function(d){var c=a.ui.ddmanager.current;if(!(!c||(c.currentItem||c.element)[0]==this.element[0]))if(this.accept.call(this.element[0],c.currentItem||c.element)){this.options.hoverClass&&this.element.removeClass(this.options.hoverClass);this._trigger("out",d,this.ui(c))}},_drop:function(d,c){var f=c||a.ui.ddmanager.current;if(!f||(f.currentItem||f.element)[0]==this.element[0])return false;var g=false;this.element.find(":data(droppable)").not(".ui-draggable-dragging").each(function(){var e=
    +a.data(this,"droppable");if(e.options.greedy&&!e.options.disabled&&e.options.scope==f.options.scope&&e.accept.call(e.element[0],f.currentItem||f.element)&&a.ui.intersect(f,a.extend(e,{offset:e.element.offset()}),e.options.tolerance)){g=true;return false}});if(g)return false;if(this.accept.call(this.element[0],f.currentItem||f.element)){this.options.activeClass&&this.element.removeClass(this.options.activeClass);this.options.hoverClass&&this.element.removeClass(this.options.hoverClass);this._trigger("drop",
    +d,this.ui(f));return this.element}return false},ui:function(d){return{draggable:d.currentItem||d.element,helper:d.helper,position:d.position,offset:d.positionAbs}}});a.extend(a.ui.droppable,{version:"1.8.13"});a.ui.intersect=function(d,c,f){if(!c.offset)return false;var g=(d.positionAbs||d.position.absolute).left,e=g+d.helperProportions.width,i=(d.positionAbs||d.position.absolute).top,b=i+d.helperProportions.height,h=c.offset.left,j=h+c.proportions.width,l=c.offset.top,o=l+c.proportions.height;
    +switch(f){case "fit":return h<=g&&e<=j&&l<=i&&b<=o;case "intersect":return h<g+d.helperProportions.width/2&&e-d.helperProportions.width/2<j&&l<i+d.helperProportions.height/2&&b-d.helperProportions.height/2<o;case "pointer":return a.ui.isOver((d.positionAbs||d.position.absolute).top+(d.clickOffset||d.offset.click).top,(d.positionAbs||d.position.absolute).left+(d.clickOffset||d.offset.click).left,l,h,c.proportions.height,c.proportions.width);case "touch":return(i>=l&&i<=o||b>=l&&b<=o||i<l&&b>o)&&(g>=
    +h&&g<=j||e>=h&&e<=j||g<h&&e>j);default:return false}};a.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(d,c){var f=a.ui.ddmanager.droppables[d.options.scope]||[],g=c?c.type:null,e=(d.currentItem||d.element).find(":data(droppable)").andSelf(),i=0;a:for(;i<f.length;i++)if(!(f[i].options.disabled||d&&!f[i].accept.call(f[i].element[0],d.currentItem||d.element))){for(var b=0;b<e.length;b++)if(e[b]==f[i].element[0]){f[i].proportions.height=0;continue a}f[i].visible=f[i].element.css("display")!=
    +"none";if(f[i].visible){g=="mousedown"&&f[i]._activate.call(f[i],c);f[i].offset=f[i].element.offset();f[i].proportions={width:f[i].element[0].offsetWidth,height:f[i].element[0].offsetHeight}}}},drop:function(d,c){var f=false;a.each(a.ui.ddmanager.droppables[d.options.scope]||[],function(){if(this.options){if(!this.options.disabled&&this.visible&&a.ui.intersect(d,this,this.options.tolerance))f=f||this._drop.call(this,c);if(!this.options.disabled&&this.visible&&this.accept.call(this.element[0],d.currentItem||
    +d.element)){this.isout=1;this.isover=0;this._deactivate.call(this,c)}}});return f},drag:function(d,c){d.options.refreshPositions&&a.ui.ddmanager.prepareOffsets(d,c);a.each(a.ui.ddmanager.droppables[d.options.scope]||[],function(){if(!(this.options.disabled||this.greedyChild||!this.visible)){var f=a.ui.intersect(d,this,this.options.tolerance);if(f=!f&&this.isover==1?"isout":f&&this.isover==0?"isover":null){var g;if(this.options.greedy){var e=this.element.parents(":data(droppable):eq(0)");if(e.length){g=
    +a.data(e[0],"droppable");g.greedyChild=f=="isover"?1:0}}if(g&&f=="isover"){g.isover=0;g.isout=1;g._out.call(g,c)}this[f]=1;this[f=="isout"?"isover":"isout"]=0;this[f=="isover"?"_over":"_out"].call(this,c);if(g&&f=="isout"){g.isout=0;g.isover=1;g._over.call(g,c)}}}})}}})(jQuery);
    +(function(a){a.widget("ui.resizable",a.ui.mouse,{widgetEventPrefix:"resize",options:{alsoResize:false,animate:false,animateDuration:"slow",animateEasing:"swing",aspectRatio:false,autoHide:false,containment:false,ghost:false,grid:false,handles:"e,s,se",helper:false,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:1E3},_create:function(){var f=this,g=this.options;this.element.addClass("ui-resizable");a.extend(this,{_aspectRatio:!!g.aspectRatio,aspectRatio:g.aspectRatio,originalElement:this.element,
    +_proportionallyResizeElements:[],_helper:g.helper||g.ghost||g.animate?g.helper||"ui-resizable-helper":null});if(this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)){/relative/.test(this.element.css("position"))&&a.browser.opera&&this.element.css({position:"relative",top:"auto",left:"auto"});this.element.wrap(a('<div class="ui-wrapper" style="overflow: hidden;"></div>').css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),
    +top:this.element.css("top"),left:this.element.css("left")}));this.element=this.element.parent().data("resizable",this.element.data("resizable"));this.elementIsWrapper=true;this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")});this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0});this.originalResizeStyle=
    +this.originalElement.css("resize");this.originalElement.css("resize","none");this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"}));this.originalElement.css({margin:this.originalElement.css("margin")});this._proportionallyResize()}this.handles=g.handles||(!a(".ui-resizable-handle",this.element).length?"e,s,se":{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",
    +nw:".ui-resizable-nw"});if(this.handles.constructor==String){if(this.handles=="all")this.handles="n,e,s,w,se,sw,ne,nw";var e=this.handles.split(",");this.handles={};for(var i=0;i<e.length;i++){var b=a.trim(e[i]),h=a('<div class="ui-resizable-handle '+("ui-resizable-"+b)+'"></div>');/sw|se|ne|nw/.test(b)&&h.css({zIndex:++g.zIndex});"se"==b&&h.addClass("ui-icon ui-icon-gripsmall-diagonal-se");this.handles[b]=".ui-resizable-"+b;this.element.append(h)}}this._renderAxis=function(j){j=j||this.element;for(var l in this.handles){if(this.handles[l].constructor==
    +String)this.handles[l]=a(this.handles[l],this.element).show();if(this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)){var o=a(this.handles[l],this.element),n=0;n=/sw|ne|nw|se|n|s/.test(l)?o.outerHeight():o.outerWidth();o=["padding",/ne|nw|n/.test(l)?"Top":/se|sw|s/.test(l)?"Bottom":/^e$/.test(l)?"Right":"Left"].join("");j.css(o,n);this._proportionallyResize()}a(this.handles[l])}};this._renderAxis(this.element);this._handles=a(".ui-resizable-handle",this.element).disableSelection();
    +this._handles.mouseover(function(){if(!f.resizing){if(this.className)var j=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);f.axis=j&&j[1]?j[1]:"se"}});if(g.autoHide){this._handles.hide();a(this.element).addClass("ui-resizable-autohide").hover(function(){if(!g.disabled){a(this).removeClass("ui-resizable-autohide");f._handles.show()}},function(){if(!g.disabled)if(!f.resizing){a(this).addClass("ui-resizable-autohide");f._handles.hide()}})}this._mouseInit()},destroy:function(){this._mouseDestroy();
    +var f=function(e){a(e).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};if(this.elementIsWrapper){f(this.element);var g=this.element;g.after(this.originalElement.css({position:g.css("position"),width:g.outerWidth(),height:g.outerHeight(),top:g.css("top"),left:g.css("left")})).remove()}this.originalElement.css("resize",this.originalResizeStyle);f(this.originalElement);return this},_mouseCapture:function(f){var g=
    +false;for(var e in this.handles)if(a(this.handles[e])[0]==f.target)g=true;return!this.options.disabled&&g},_mouseStart:function(f){var g=this.options,e=this.element.position(),i=this.element;this.resizing=true;this.documentScroll={top:a(document).scrollTop(),left:a(document).scrollLeft()};if(i.is(".ui-draggable")||/absolute/.test(i.css("position")))i.css({position:"absolute",top:e.top,left:e.left});a.browser.opera&&/relative/.test(i.css("position"))&&i.css({position:"relative",top:"auto",left:"auto"});
    +this._renderProxy();e=d(this.helper.css("left"));var b=d(this.helper.css("top"));if(g.containment){e+=a(g.containment).scrollLeft()||0;b+=a(g.containment).scrollTop()||0}this.offset=this.helper.offset();this.position={left:e,top:b};this.size=this._helper?{width:i.outerWidth(),height:i.outerHeight()}:{width:i.width(),height:i.height()};this.originalSize=this._helper?{width:i.outerWidth(),height:i.outerHeight()}:{width:i.width(),height:i.height()};this.originalPosition={left:e,top:b};this.sizeDiff=
    +{width:i.outerWidth()-i.width(),height:i.outerHeight()-i.height()};this.originalMousePosition={left:f.pageX,top:f.pageY};this.aspectRatio=typeof g.aspectRatio=="number"?g.aspectRatio:this.originalSize.width/this.originalSize.height||1;g=a(".ui-resizable-"+this.axis).css("cursor");a("body").css("cursor",g=="auto"?this.axis+"-resize":g);i.addClass("ui-resizable-resizing");this._propagate("start",f);return true},_mouseDrag:function(f){var g=this.helper,e=this.originalMousePosition,i=this._change[this.axis];
    +if(!i)return false;e=i.apply(this,[f,f.pageX-e.left||0,f.pageY-e.top||0]);if(this._aspectRatio||f.shiftKey)e=this._updateRatio(e,f);e=this._respectSize(e,f);this._propagate("resize",f);g.css({top:this.position.top+"px",left:this.position.left+"px",width:this.size.width+"px",height:this.size.height+"px"});!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize();this._updateCache(e);this._trigger("resize",f,this.ui());return false},_mouseStop:function(f){this.resizing=
    +false;var g=this.options,e=this;if(this._helper){var i=this._proportionallyResizeElements,b=i.length&&/textarea/i.test(i[0].nodeName);i=b&&a.ui.hasScroll(i[0],"left")?0:e.sizeDiff.height;b=b?0:e.sizeDiff.width;b={width:e.helper.width()-b,height:e.helper.height()-i};i=parseInt(e.element.css("left"),10)+(e.position.left-e.originalPosition.left)||null;var h=parseInt(e.element.css("top"),10)+(e.position.top-e.originalPosition.top)||null;g.animate||this.element.css(a.extend(b,{top:h,left:i}));e.helper.height(e.size.height);
    +e.helper.width(e.size.width);this._helper&&!g.animate&&this._proportionallyResize()}a("body").css("cursor","auto");this.element.removeClass("ui-resizable-resizing");this._propagate("stop",f);this._helper&&this.helper.remove();return false},_updateCache:function(f){this.offset=this.helper.offset();if(c(f.left))this.position.left=f.left;if(c(f.top))this.position.top=f.top;if(c(f.height))this.size.height=f.height;if(c(f.width))this.size.width=f.width},_updateRatio:function(f){var g=this.position,e=this.size,
    +i=this.axis;if(f.height)f.width=e.height*this.aspectRatio;else if(f.width)f.height=e.width/this.aspectRatio;if(i=="sw"){f.left=g.left+(e.width-f.width);f.top=null}if(i=="nw"){f.top=g.top+(e.height-f.height);f.left=g.left+(e.width-f.width)}return f},_respectSize:function(f){var g=this.options,e=this.axis,i=c(f.width)&&g.maxWidth&&g.maxWidth<f.width,b=c(f.height)&&g.maxHeight&&g.maxHeight<f.height,h=c(f.width)&&g.minWidth&&g.minWidth>f.width,j=c(f.height)&&g.minHeight&&g.minHeight>f.height;if(h)f.width=
    +g.minWidth;if(j)f.height=g.minHeight;if(i)f.width=g.maxWidth;if(b)f.height=g.maxHeight;var l=this.originalPosition.left+this.originalSize.width,o=this.position.top+this.size.height,n=/sw|nw|w/.test(e);e=/nw|ne|n/.test(e);if(h&&n)f.left=l-g.minWidth;if(i&&n)f.left=l-g.maxWidth;if(j&&e)f.top=o-g.minHeight;if(b&&e)f.top=o-g.maxHeight;if((g=!f.width&&!f.height)&&!f.left&&f.top)f.top=null;else if(g&&!f.top&&f.left)f.left=null;return f},_proportionallyResize:function(){if(this._proportionallyResizeElements.length)for(var f=
    +this.helper||this.element,g=0;g<this._proportionallyResizeElements.length;g++){var e=this._proportionallyResizeElements[g];if(!this.borderDif){var i=[e.css("borderTopWidth"),e.css("borderRightWidth"),e.css("borderBottomWidth"),e.css("borderLeftWidth")],b=[e.css("paddingTop"),e.css("paddingRight"),e.css("paddingBottom"),e.css("paddingLeft")];this.borderDif=a.map(i,function(h,j){h=parseInt(h,10)||0;j=parseInt(b[j],10)||0;return h+j})}a.browser.msie&&(a(f).is(":hidden")||a(f).parents(":hidden").length)||
    +e.css({height:f.height()-this.borderDif[0]-this.borderDif[2]||0,width:f.width()-this.borderDif[1]-this.borderDif[3]||0})}},_renderProxy:function(){var f=this.options;this.elementOffset=this.element.offset();if(this._helper){this.helper=this.helper||a('<div style="overflow:hidden;"></div>');var g=a.browser.msie&&a.browser.version<7,e=g?1:0;g=g?2:-1;this.helper.addClass(this._helper).css({width:this.element.outerWidth()+g,height:this.element.outerHeight()+g,position:"absolute",left:this.elementOffset.left-
    +e+"px",top:this.elementOffset.top-e+"px",zIndex:++f.zIndex});this.helper.appendTo("body").disableSelection()}else this.helper=this.element},_change:{e:function(f,g){return{width:this.originalSize.width+g}},w:function(f,g){return{left:this.originalPosition.left+g,width:this.originalSize.width-g}},n:function(f,g,e){return{top:this.originalPosition.top+e,height:this.originalSize.height-e}},s:function(f,g,e){return{height:this.originalSize.height+e}},se:function(f,g,e){return a.extend(this._change.s.apply(this,
    +arguments),this._change.e.apply(this,[f,g,e]))},sw:function(f,g,e){return a.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[f,g,e]))},ne:function(f,g,e){return a.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[f,g,e]))},nw:function(f,g,e){return a.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[f,g,e]))}},_propagate:function(f,g){a.ui.plugin.call(this,f,[g,this.ui()]);f!="resize"&&this._trigger(f,g,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,
    +element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}});a.extend(a.ui.resizable,{version:"1.8.13"});a.ui.plugin.add("resizable","alsoResize",{start:function(){var f=a(this).data("resizable").options,g=function(e){a(e).each(function(){var i=a(this);i.data("resizable-alsoresize",{width:parseInt(i.width(),10),height:parseInt(i.height(),10),left:parseInt(i.css("left"),10),top:parseInt(i.css("top"),10),position:i.css("position")})})};
    +if(typeof f.alsoResize=="object"&&!f.alsoResize.parentNode)if(f.alsoResize.length){f.alsoResize=f.alsoResize[0];g(f.alsoResize)}else a.each(f.alsoResize,function(e){g(e)});else g(f.alsoResize)},resize:function(f,g){var e=a(this).data("resizable");f=e.options;var i=e.originalSize,b=e.originalPosition,h={height:e.size.height-i.height||0,width:e.size.width-i.width||0,top:e.position.top-b.top||0,left:e.position.left-b.left||0},j=function(l,o){a(l).each(function(){var n=a(this),k=a(this).data("resizable-alsoresize"),
    +m={},p=o&&o.length?o:n.parents(g.originalElement[0]).length?["width","height"]:["width","height","top","left"];a.each(p,function(q,s){if((q=(k[s]||0)+(h[s]||0))&&q>=0)m[s]=q||null});if(a.browser.opera&&/relative/.test(n.css("position"))){e._revertToRelativePosition=true;n.css({position:"absolute",top:"auto",left:"auto"})}n.css(m)})};typeof f.alsoResize=="object"&&!f.alsoResize.nodeType?a.each(f.alsoResize,function(l,o){j(l,o)}):j(f.alsoResize)},stop:function(){var f=a(this).data("resizable"),g=f.options,
    +e=function(i){a(i).each(function(){var b=a(this);b.css({position:b.data("resizable-alsoresize").position})})};if(f._revertToRelativePosition){f._revertToRelativePosition=false;typeof g.alsoResize=="object"&&!g.alsoResize.nodeType?a.each(g.alsoResize,function(i){e(i)}):e(g.alsoResize)}a(this).removeData("resizable-alsoresize")}});a.ui.plugin.add("resizable","animate",{stop:function(f){var g=a(this).data("resizable"),e=g.options,i=g._proportionallyResizeElements,b=i.length&&/textarea/i.test(i[0].nodeName),
    +h=b&&a.ui.hasScroll(i[0],"left")?0:g.sizeDiff.height;b={width:g.size.width-(b?0:g.sizeDiff.width),height:g.size.height-h};h=parseInt(g.element.css("left"),10)+(g.position.left-g.originalPosition.left)||null;var j=parseInt(g.element.css("top"),10)+(g.position.top-g.originalPosition.top)||null;g.element.animate(a.extend(b,j&&h?{top:j,left:h}:{}),{duration:e.animateDuration,easing:e.animateEasing,step:function(){var l={width:parseInt(g.element.css("width"),10),height:parseInt(g.element.css("height"),
    +10),top:parseInt(g.element.css("top"),10),left:parseInt(g.element.css("left"),10)};i&&i.length&&a(i[0]).css({width:l.width,height:l.height});g._updateCache(l);g._propagate("resize",f)}})}});a.ui.plugin.add("resizable","containment",{start:function(){var f=a(this).data("resizable"),g=f.element,e=f.options.containment;if(g=e instanceof a?e.get(0):/parent/.test(e)?g.parent().get(0):e){f.containerElement=a(g);if(/document/.test(e)||e==document){f.containerOffset={left:0,top:0};f.containerPosition={left:0,
    +top:0};f.parentData={element:a(document),left:0,top:0,width:a(document).width(),height:a(document).height()||document.body.parentNode.scrollHeight}}else{var i=a(g),b=[];a(["Top","Right","Left","Bottom"]).each(function(l,o){b[l]=d(i.css("padding"+o))});f.containerOffset=i.offset();f.containerPosition=i.position();f.containerSize={height:i.innerHeight()-b[3],width:i.innerWidth()-b[1]};e=f.containerOffset;var h=f.containerSize.height,j=f.containerSize.width;j=a.ui.hasScroll(g,"left")?g.scrollWidth:j;
    +h=a.ui.hasScroll(g)?g.scrollHeight:h;f.parentData={element:g,left:e.left,top:e.top,width:j,height:h}}}},resize:function(f){var g=a(this).data("resizable"),e=g.options,i=g.containerOffset,b=g.position;f=g._aspectRatio||f.shiftKey;var h={top:0,left:0},j=g.containerElement;if(j[0]!=document&&/static/.test(j.css("position")))h=i;if(b.left<(g._helper?i.left:0)){g.size.width+=g._helper?g.position.left-i.left:g.position.left-h.left;if(f)g.size.height=g.size.width/e.aspectRatio;g.position.left=e.helper?i.left:
    +0}if(b.top<(g._helper?i.top:0)){g.size.height+=g._helper?g.position.top-i.top:g.position.top;if(f)g.size.width=g.size.height*e.aspectRatio;g.position.top=g._helper?i.top:0}g.offset.left=g.parentData.left+g.position.left;g.offset.top=g.parentData.top+g.position.top;e=Math.abs((g._helper?g.offset.left-h.left:g.offset.left-h.left)+g.sizeDiff.width);i=Math.abs((g._helper?g.offset.top-h.top:g.offset.top-i.top)+g.sizeDiff.height);b=g.containerElement.get(0)==g.element.parent().get(0);h=/relative|absolute/.test(g.containerElement.css("position"));
    +if(b&&h)e-=g.parentData.left;if(e+g.size.width>=g.parentData.width){g.size.width=g.parentData.width-e;if(f)g.size.height=g.size.width/g.aspectRatio}if(i+g.size.height>=g.parentData.height){g.size.height=g.parentData.height-i;if(f)g.size.width=g.size.height*g.aspectRatio}},stop:function(){var f=a(this).data("resizable"),g=f.options,e=f.containerOffset,i=f.containerPosition,b=f.containerElement,h=a(f.helper),j=h.offset(),l=h.outerWidth()-f.sizeDiff.width;h=h.outerHeight()-f.sizeDiff.height;f._helper&&
    +!g.animate&&/relative/.test(b.css("position"))&&a(this).css({left:j.left-i.left-e.left,width:l,height:h});f._helper&&!g.animate&&/static/.test(b.css("position"))&&a(this).css({left:j.left-i.left-e.left,width:l,height:h})}});a.ui.plugin.add("resizable","ghost",{start:function(){var f=a(this).data("resizable"),g=f.options,e=f.size;f.ghost=f.originalElement.clone();f.ghost.css({opacity:0.25,display:"block",position:"relative",height:e.height,width:e.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass(typeof g.ghost==
    +"string"?g.ghost:"");f.ghost.appendTo(f.helper)},resize:function(){var f=a(this).data("resizable");f.ghost&&f.ghost.css({position:"relative",height:f.size.height,width:f.size.width})},stop:function(){var f=a(this).data("resizable");f.ghost&&f.helper&&f.helper.get(0).removeChild(f.ghost.get(0))}});a.ui.plugin.add("resizable","grid",{resize:function(){var f=a(this).data("resizable"),g=f.options,e=f.size,i=f.originalSize,b=f.originalPosition,h=f.axis;g.grid=typeof g.grid=="number"?[g.grid,g.grid]:g.grid;
    +var j=Math.round((e.width-i.width)/(g.grid[0]||1))*(g.grid[0]||1);g=Math.round((e.height-i.height)/(g.grid[1]||1))*(g.grid[1]||1);if(/^(se|s|e)$/.test(h)){f.size.width=i.width+j;f.size.height=i.height+g}else if(/^(ne)$/.test(h)){f.size.width=i.width+j;f.size.height=i.height+g;f.position.top=b.top-g}else{if(/^(sw)$/.test(h)){f.size.width=i.width+j;f.size.height=i.height+g}else{f.size.width=i.width+j;f.size.height=i.height+g;f.position.top=b.top-g}f.position.left=b.left-j}}});var d=function(f){return parseInt(f,
    +10)||0},c=function(f){return!isNaN(parseInt(f,10))}})(jQuery);
    +(function(a){a.widget("ui.selectable",a.ui.mouse,{options:{appendTo:"body",autoRefresh:true,distance:0,filter:"*",tolerance:"touch"},_create:function(){var d=this;this.element.addClass("ui-selectable");this.dragged=false;var c;this.refresh=function(){c=a(d.options.filter,d.element[0]);c.each(function(){var f=a(this),g=f.offset();a.data(this,"selectable-item",{element:this,$element:f,left:g.left,top:g.top,right:g.left+f.outerWidth(),bottom:g.top+f.outerHeight(),startselected:false,selected:f.hasClass("ui-selected"),
    +selecting:f.hasClass("ui-selecting"),unselecting:f.hasClass("ui-unselecting")})})};this.refresh();this.selectees=c.addClass("ui-selectee");this._mouseInit();this.helper=a("<div class='ui-selectable-helper'></div>")},destroy:function(){this.selectees.removeClass("ui-selectee").removeData("selectable-item");this.element.removeClass("ui-selectable ui-selectable-disabled").removeData("selectable").unbind(".selectable");this._mouseDestroy();return this},_mouseStart:function(d){var c=this;this.opos=[d.pageX,
    +d.pageY];if(!this.options.disabled){var f=this.options;this.selectees=a(f.filter,this.element[0]);this._trigger("start",d);a(f.appendTo).append(this.helper);this.helper.css({left:d.clientX,top:d.clientY,width:0,height:0});f.autoRefresh&&this.refresh();this.selectees.filter(".ui-selected").each(function(){var g=a.data(this,"selectable-item");g.startselected=true;if(!d.metaKey){g.$element.removeClass("ui-selected");g.selected=false;g.$element.addClass("ui-unselecting");g.unselecting=true;c._trigger("unselecting",
    +d,{unselecting:g.element})}});a(d.target).parents().andSelf().each(function(){var g=a.data(this,"selectable-item");if(g){var e=!d.metaKey||!g.$element.hasClass("ui-selected");g.$element.removeClass(e?"ui-unselecting":"ui-selected").addClass(e?"ui-selecting":"ui-unselecting");g.unselecting=!e;g.selecting=e;(g.selected=e)?c._trigger("selecting",d,{selecting:g.element}):c._trigger("unselecting",d,{unselecting:g.element});return false}})}},_mouseDrag:function(d){var c=this;this.dragged=true;if(!this.options.disabled){var f=
    +this.options,g=this.opos[0],e=this.opos[1],i=d.pageX,b=d.pageY;if(g>i){var h=i;i=g;g=h}if(e>b){h=b;b=e;e=h}this.helper.css({left:g,top:e,width:i-g,height:b-e});this.selectees.each(function(){var j=a.data(this,"selectable-item");if(!(!j||j.element==c.element[0])){var l=false;if(f.tolerance=="touch")l=!(j.left>i||j.right<g||j.top>b||j.bottom<e);else if(f.tolerance=="fit")l=j.left>g&&j.right<i&&j.top>e&&j.bottom<b;if(l){if(j.selected){j.$element.removeClass("ui-selected");j.selected=false}if(j.unselecting){j.$element.removeClass("ui-unselecting");
    +j.unselecting=false}if(!j.selecting){j.$element.addClass("ui-selecting");j.selecting=true;c._trigger("selecting",d,{selecting:j.element})}}else{if(j.selecting)if(d.metaKey&&j.startselected){j.$element.removeClass("ui-selecting");j.selecting=false;j.$element.addClass("ui-selected");j.selected=true}else{j.$element.removeClass("ui-selecting");j.selecting=false;if(j.startselected){j.$element.addClass("ui-unselecting");j.unselecting=true}c._trigger("unselecting",d,{unselecting:j.element})}if(j.selected)if(!d.metaKey&&
    +!j.startselected){j.$element.removeClass("ui-selected");j.selected=false;j.$element.addClass("ui-unselecting");j.unselecting=true;c._trigger("unselecting",d,{unselecting:j.element})}}}});return false}},_mouseStop:function(d){var c=this;this.dragged=false;a(".ui-unselecting",this.element[0]).each(function(){var f=a.data(this,"selectable-item");f.$element.removeClass("ui-unselecting");f.unselecting=false;f.startselected=false;c._trigger("unselected",d,{unselected:f.element})});a(".ui-selecting",this.element[0]).each(function(){var f=
    +a.data(this,"selectable-item");f.$element.removeClass("ui-selecting").addClass("ui-selected");f.selecting=false;f.selected=true;f.startselected=true;c._trigger("selected",d,{selected:f.element})});this._trigger("stop",d);this.helper.remove();return false}});a.extend(a.ui.selectable,{version:"1.8.13"})})(jQuery);
    +(function(a){a.widget("ui.sortable",a.ui.mouse,{widgetEventPrefix:"sort",options:{appendTo:"parent",axis:false,connectWith:false,containment:false,cursor:"auto",cursorAt:false,dropOnEmpty:true,forcePlaceholderSize:false,forceHelperSize:false,grid:false,handle:false,helper:"original",items:"> *",opacity:false,placeholder:false,revert:false,scroll:true,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1E3},_create:function(){var d=this.options;this.containerCache={};this.element.addClass("ui-sortable");
    +this.refresh();this.floating=this.items.length?d.axis==="x"||/left|right/.test(this.items[0].item.css("float"))||/inline|table-cell/.test(this.items[0].item.css("display")):false;this.offset=this.element.offset();this._mouseInit()},destroy:function(){this.element.removeClass("ui-sortable ui-sortable-disabled").removeData("sortable").unbind(".sortable");this._mouseDestroy();for(var d=this.items.length-1;d>=0;d--)this.items[d].item.removeData("sortable-item");return this},_setOption:function(d,c){if(d===
    +"disabled"){this.options[d]=c;this.widget()[c?"addClass":"removeClass"]("ui-sortable-disabled")}else a.Widget.prototype._setOption.apply(this,arguments)},_mouseCapture:function(d,c){if(this.reverting)return false;if(this.options.disabled||this.options.type=="static")return false;this._refreshItems(d);var f=null,g=this;a(d.target).parents().each(function(){if(a.data(this,"sortable-item")==g){f=a(this);return false}});if(a.data(d.target,"sortable-item")==g)f=a(d.target);if(!f)return false;if(this.options.handle&&
    +!c){var e=false;a(this.options.handle,f).find("*").andSelf().each(function(){if(this==d.target)e=true});if(!e)return false}this.currentItem=f;this._removeCurrentsFromItems();return true},_mouseStart:function(d,c,f){c=this.options;var g=this;this.currentContainer=this;this.refreshPositions();this.helper=this._createHelper(d);this._cacheHelperProportions();this._cacheMargins();this.scrollParent=this.helper.scrollParent();this.offset=this.currentItem.offset();this.offset={top:this.offset.top-this.margins.top,
    +left:this.offset.left-this.margins.left};this.helper.css("position","absolute");this.cssPosition=this.helper.css("position");a.extend(this.offset,{click:{left:d.pageX-this.offset.left,top:d.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});this.originalPosition=this._generatePosition(d);this.originalPageX=d.pageX;this.originalPageY=d.pageY;c.cursorAt&&this._adjustOffsetFromHelper(c.cursorAt);this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]};
    +this.helper[0]!=this.currentItem[0]&&this.currentItem.hide();this._createPlaceholder();c.containment&&this._setContainment();if(c.cursor){if(a("body").css("cursor"))this._storedCursor=a("body").css("cursor");a("body").css("cursor",c.cursor)}if(c.opacity){if(this.helper.css("opacity"))this._storedOpacity=this.helper.css("opacity");this.helper.css("opacity",c.opacity)}if(c.zIndex){if(this.helper.css("zIndex"))this._storedZIndex=this.helper.css("zIndex");this.helper.css("zIndex",c.zIndex)}if(this.scrollParent[0]!=
    +document&&this.scrollParent[0].tagName!="HTML")this.overflowOffset=this.scrollParent.offset();this._trigger("start",d,this._uiHash());this._preserveHelperProportions||this._cacheHelperProportions();if(!f)for(f=this.containers.length-1;f>=0;f--)this.containers[f]._trigger("activate",d,g._uiHash(this));if(a.ui.ddmanager)a.ui.ddmanager.current=this;a.ui.ddmanager&&!c.dropBehaviour&&a.ui.ddmanager.prepareOffsets(this,d);this.dragging=true;this.helper.addClass("ui-sortable-helper");this._mouseDrag(d);
    +return true},_mouseDrag:function(d){this.position=this._generatePosition(d);this.positionAbs=this._convertPositionTo("absolute");if(!this.lastPositionAbs)this.lastPositionAbs=this.positionAbs;if(this.options.scroll){var c=this.options,f=false;if(this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"){if(this.overflowOffset.top+this.scrollParent[0].offsetHeight-d.pageY<c.scrollSensitivity)this.scrollParent[0].scrollTop=f=this.scrollParent[0].scrollTop+c.scrollSpeed;else if(d.pageY-this.overflowOffset.top<
    +c.scrollSensitivity)this.scrollParent[0].scrollTop=f=this.scrollParent[0].scrollTop-c.scrollSpeed;if(this.overflowOffset.left+this.scrollParent[0].offsetWidth-d.pageX<c.scrollSensitivity)this.scrollParent[0].scrollLeft=f=this.scrollParent[0].scrollLeft+c.scrollSpeed;else if(d.pageX-this.overflowOffset.left<c.scrollSensitivity)this.scrollParent[0].scrollLeft=f=this.scrollParent[0].scrollLeft-c.scrollSpeed}else{if(d.pageY-a(document).scrollTop()<c.scrollSensitivity)f=a(document).scrollTop(a(document).scrollTop()-
    +c.scrollSpeed);else if(a(window).height()-(d.pageY-a(document).scrollTop())<c.scrollSensitivity)f=a(document).scrollTop(a(document).scrollTop()+c.scrollSpeed);if(d.pageX-a(document).scrollLeft()<c.scrollSensitivity)f=a(document).scrollLeft(a(document).scrollLeft()-c.scrollSpeed);else if(a(window).width()-(d.pageX-a(document).scrollLeft())<c.scrollSensitivity)f=a(document).scrollLeft(a(document).scrollLeft()+c.scrollSpeed)}f!==false&&a.ui.ddmanager&&!c.dropBehaviour&&a.ui.ddmanager.prepareOffsets(this,
    +d)}this.positionAbs=this._convertPositionTo("absolute");if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";for(c=this.items.length-1;c>=0;c--){f=this.items[c];var g=f.item[0],e=this._intersectsWithPointer(f);if(e)if(g!=this.currentItem[0]&&this.placeholder[e==1?"next":"prev"]()[0]!=g&&!a.ui.contains(this.placeholder[0],g)&&(this.options.type=="semi-dynamic"?!a.ui.contains(this.element[0],
    +g):true)){this.direction=e==1?"down":"up";if(this.options.tolerance=="pointer"||this._intersectsWithSides(f))this._rearrange(d,f);else break;this._trigger("change",d,this._uiHash());break}}this._contactContainers(d);a.ui.ddmanager&&a.ui.ddmanager.drag(this,d);this._trigger("sort",d,this._uiHash());this.lastPositionAbs=this.positionAbs;return false},_mouseStop:function(d,c){if(d){a.ui.ddmanager&&!this.options.dropBehaviour&&a.ui.ddmanager.drop(this,d);if(this.options.revert){var f=this;c=f.placeholder.offset();
    +f.reverting=true;a(this.helper).animate({left:c.left-this.offset.parent.left-f.margins.left+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollLeft),top:c.top-this.offset.parent.top-f.margins.top+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollTop)},parseInt(this.options.revert,10)||500,function(){f._clear(d)})}else this._clear(d,c);return false}},cancel:function(){var d=this;if(this.dragging){this._mouseUp({target:null});this.options.helper=="original"?this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"):
    +this.currentItem.show();for(var c=this.containers.length-1;c>=0;c--){this.containers[c]._trigger("deactivate",null,d._uiHash(this));if(this.containers[c].containerCache.over){this.containers[c]._trigger("out",null,d._uiHash(this));this.containers[c].containerCache.over=0}}}if(this.placeholder){this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]);this.options.helper!="original"&&this.helper&&this.helper[0].parentNode&&this.helper.remove();a.extend(this,{helper:null,
    +dragging:false,reverting:false,_noFinalSort:null});this.domPosition.prev?a(this.domPosition.prev).after(this.currentItem):a(this.domPosition.parent).prepend(this.currentItem)}return this},serialize:function(d){var c=this._getItemsAsjQuery(d&&d.connected),f=[];d=d||{};a(c).each(function(){var g=(a(d.item||this).attr(d.attribute||"id")||"").match(d.expression||/(.+)[-=_](.+)/);if(g)f.push((d.key||g[1]+"[]")+"="+(d.key&&d.expression?g[1]:g[2]))});!f.length&&d.key&&f.push(d.key+"=");return f.join("&")},
    +toArray:function(d){var c=this._getItemsAsjQuery(d&&d.connected),f=[];d=d||{};c.each(function(){f.push(a(d.item||this).attr(d.attribute||"id")||"")});return f},_intersectsWith:function(d){var c=this.positionAbs.left,f=c+this.helperProportions.width,g=this.positionAbs.top,e=g+this.helperProportions.height,i=d.left,b=i+d.width,h=d.top,j=h+d.height,l=this.offset.click.top,o=this.offset.click.left;l=g+l>h&&g+l<j&&c+o>i&&c+o<b;return this.options.tolerance=="pointer"||this.options.forcePointerForContainers||
    +this.options.tolerance!="pointer"&&this.helperProportions[this.floating?"width":"height"]>d[this.floating?"width":"height"]?l:i<c+this.helperProportions.width/2&&f-this.helperProportions.width/2<b&&h<g+this.helperProportions.height/2&&e-this.helperProportions.height/2<j},_intersectsWithPointer:function(d){var c=a.ui.isOverAxis(this.positionAbs.top+this.offset.click.top,d.top,d.height);d=a.ui.isOverAxis(this.positionAbs.left+this.offset.click.left,d.left,d.width);c=c&&d;d=this._getDragVerticalDirection();
    +var f=this._getDragHorizontalDirection();if(!c)return false;return this.floating?f&&f=="right"||d=="down"?2:1:d&&(d=="down"?2:1)},_intersectsWithSides:function(d){var c=a.ui.isOverAxis(this.positionAbs.top+this.offset.click.top,d.top+d.height/2,d.height);d=a.ui.isOverAxis(this.positionAbs.left+this.offset.click.left,d.left+d.width/2,d.width);var f=this._getDragVerticalDirection(),g=this._getDragHorizontalDirection();return this.floating&&g?g=="right"&&d||g=="left"&&!d:f&&(f=="down"&&c||f=="up"&&!c)},
    +_getDragVerticalDirection:function(){var d=this.positionAbs.top-this.lastPositionAbs.top;return d!=0&&(d>0?"down":"up")},_getDragHorizontalDirection:function(){var d=this.positionAbs.left-this.lastPositionAbs.left;return d!=0&&(d>0?"right":"left")},refresh:function(d){this._refreshItems(d);this.refreshPositions();return this},_connectWith:function(){var d=this.options;return d.connectWith.constructor==String?[d.connectWith]:d.connectWith},_getItemsAsjQuery:function(d){var c=[],f=[],g=this._connectWith();
    +if(g&&d)for(d=g.length-1;d>=0;d--)for(var e=a(g[d]),i=e.length-1;i>=0;i--){var b=a.data(e[i],"sortable");if(b&&b!=this&&!b.options.disabled)f.push([a.isFunction(b.options.items)?b.options.items.call(b.element):a(b.options.items,b.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),b])}f.push([a.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):a(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),
    +this]);for(d=f.length-1;d>=0;d--)f[d][0].each(function(){c.push(this)});return a(c)},_removeCurrentsFromItems:function(){for(var d=this.currentItem.find(":data(sortable-item)"),c=0;c<this.items.length;c++)for(var f=0;f<d.length;f++)d[f]==this.items[c].item[0]&&this.items.splice(c,1)},_refreshItems:function(d){this.items=[];this.containers=[this];var c=this.items,f=[[a.isFunction(this.options.items)?this.options.items.call(this.element[0],d,{item:this.currentItem}):a(this.options.items,this.element),
    +this]],g=this._connectWith();if(g)for(var e=g.length-1;e>=0;e--)for(var i=a(g[e]),b=i.length-1;b>=0;b--){var h=a.data(i[b],"sortable");if(h&&h!=this&&!h.options.disabled){f.push([a.isFunction(h.options.items)?h.options.items.call(h.element[0],d,{item:this.currentItem}):a(h.options.items,h.element),h]);this.containers.push(h)}}for(e=f.length-1;e>=0;e--){d=f[e][1];g=f[e][0];b=0;for(i=g.length;b<i;b++){h=a(g[b]);h.data("sortable-item",d);c.push({item:h,instance:d,width:0,height:0,left:0,top:0})}}},refreshPositions:function(d){if(this.offsetParent&&
    +this.helper)this.offset.parent=this._getParentOffset();for(var c=this.items.length-1;c>=0;c--){var f=this.items[c];if(!(f.instance!=this.currentContainer&&this.currentContainer&&f.item[0]!=this.currentItem[0])){var g=this.options.toleranceElement?a(this.options.toleranceElement,f.item):f.item;if(!d){f.width=g.outerWidth();f.height=g.outerHeight()}g=g.offset();f.left=g.left;f.top=g.top}}if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(c=
    +this.containers.length-1;c>=0;c--){g=this.containers[c].element.offset();this.containers[c].containerCache.left=g.left;this.containers[c].containerCache.top=g.top;this.containers[c].containerCache.width=this.containers[c].element.outerWidth();this.containers[c].containerCache.height=this.containers[c].element.outerHeight()}return this},_createPlaceholder:function(d){var c=d||this,f=c.options;if(!f.placeholder||f.placeholder.constructor==String){var g=f.placeholder;f.placeholder={element:function(){var e=
    +a(document.createElement(c.currentItem[0].nodeName)).addClass(g||c.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper")[0];if(!g)e.style.visibility="hidden";return e},update:function(e,i){if(!(g&&!f.forcePlaceholderSize)){i.height()||i.height(c.currentItem.innerHeight()-parseInt(c.currentItem.css("paddingTop")||0,10)-parseInt(c.currentItem.css("paddingBottom")||0,10));i.width()||i.width(c.currentItem.innerWidth()-parseInt(c.currentItem.css("paddingLeft")||0,10)-parseInt(c.currentItem.css("paddingRight")||
    +0,10))}}}}c.placeholder=a(f.placeholder.element.call(c.element,c.currentItem));c.currentItem.after(c.placeholder);f.placeholder.update(c,c.placeholder)},_contactContainers:function(d){for(var c=null,f=null,g=this.containers.length-1;g>=0;g--)if(!a.ui.contains(this.currentItem[0],this.containers[g].element[0]))if(this._intersectsWith(this.containers[g].containerCache)){if(!(c&&a.ui.contains(this.containers[g].element[0],c.element[0]))){c=this.containers[g];f=g}}else if(this.containers[g].containerCache.over){this.containers[g]._trigger("out",
    +d,this._uiHash(this));this.containers[g].containerCache.over=0}if(c)if(this.containers.length===1){this.containers[f]._trigger("over",d,this._uiHash(this));this.containers[f].containerCache.over=1}else if(this.currentContainer!=this.containers[f]){c=1E4;g=null;for(var e=this.positionAbs[this.containers[f].floating?"left":"top"],i=this.items.length-1;i>=0;i--)if(a.ui.contains(this.containers[f].element[0],this.items[i].item[0])){var b=this.items[i][this.containers[f].floating?"left":"top"];if(Math.abs(b-
    +e)<c){c=Math.abs(b-e);g=this.items[i]}}if(g||this.options.dropOnEmpty){this.currentContainer=this.containers[f];g?this._rearrange(d,g,null,true):this._rearrange(d,null,this.containers[f].element,true);this._trigger("change",d,this._uiHash());this.containers[f]._trigger("change",d,this._uiHash(this));this.options.placeholder.update(this.currentContainer,this.placeholder);this.containers[f]._trigger("over",d,this._uiHash(this));this.containers[f].containerCache.over=1}}},_createHelper:function(d){var c=
    +this.options;d=a.isFunction(c.helper)?a(c.helper.apply(this.element[0],[d,this.currentItem])):c.helper=="clone"?this.currentItem.clone():this.currentItem;d.parents("body").length||a(c.appendTo!="parent"?c.appendTo:this.currentItem[0].parentNode)[0].appendChild(d[0]);if(d[0]==this.currentItem[0])this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")};if(d[0].style.width==
    +""||c.forceHelperSize)d.width(this.currentItem.width());if(d[0].style.height==""||c.forceHelperSize)d.height(this.currentItem.height());return d},_adjustOffsetFromHelper:function(d){if(typeof d=="string")d=d.split(" ");if(a.isArray(d))d={left:+d[0],top:+d[1]||0};if("left"in d)this.offset.click.left=d.left+this.margins.left;if("right"in d)this.offset.click.left=this.helperProportions.width-d.right+this.margins.left;if("top"in d)this.offset.click.top=d.top+this.margins.top;if("bottom"in d)this.offset.click.top=
    +this.helperProportions.height-d.bottom+this.margins.top},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var d=this.offsetParent.offset();if(this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0])){d.left+=this.scrollParent.scrollLeft();d.top+=this.scrollParent.scrollTop()}if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&a.browser.msie)d=
    +{top:0,left:0};return{top:d.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:d.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var d=this.currentItem.position();return{top:d.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:d.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}else return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css("marginLeft"),
    +10)||0,top:parseInt(this.currentItem.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var d=this.options;if(d.containment=="parent")d.containment=this.helper[0].parentNode;if(d.containment=="document"||d.containment=="window")this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,a(d.containment=="document"?
    +document:window).width()-this.helperProportions.width-this.margins.left,(a(d.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(d.containment)){var c=a(d.containment)[0];d=a(d.containment).offset();var f=a(c).css("overflow")!="hidden";this.containment=[d.left+(parseInt(a(c).css("borderLeftWidth"),10)||0)+(parseInt(a(c).css("paddingLeft"),10)||0)-this.margins.left,d.top+(parseInt(a(c).css("borderTopWidth"),
    +10)||0)+(parseInt(a(c).css("paddingTop"),10)||0)-this.margins.top,d.left+(f?Math.max(c.scrollWidth,c.offsetWidth):c.offsetWidth)-(parseInt(a(c).css("borderLeftWidth"),10)||0)-(parseInt(a(c).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,d.top+(f?Math.max(c.scrollHeight,c.offsetHeight):c.offsetHeight)-(parseInt(a(c).css("borderTopWidth"),10)||0)-(parseInt(a(c).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top]}},_convertPositionTo:function(d,c){if(!c)c=
    +this.position;d=d=="absolute"?1:-1;var f=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,g=/(html|body)/i.test(f[0].tagName);return{top:c.top+this.offset.relative.top*d+this.offset.parent.top*d-(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():g?0:f.scrollTop())*d),left:c.left+this.offset.relative.left*d+this.offset.parent.left*d-(a.browser.safari&&
    +this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():g?0:f.scrollLeft())*d)}},_generatePosition:function(d){var c=this.options,f=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,g=/(html|body)/i.test(f[0].tagName);if(this.cssPosition=="relative"&&!(this.scrollParent[0]!=document&&this.scrollParent[0]!=this.offsetParent[0]))this.offset.relative=this._getRelativeOffset();
    +var e=d.pageX,i=d.pageY;if(this.originalPosition){if(this.containment){if(d.pageX-this.offset.click.left<this.containment[0])e=this.containment[0]+this.offset.click.left;if(d.pageY-this.offset.click.top<this.containment[1])i=this.containment[1]+this.offset.click.top;if(d.pageX-this.offset.click.left>this.containment[2])e=this.containment[2]+this.offset.click.left;if(d.pageY-this.offset.click.top>this.containment[3])i=this.containment[3]+this.offset.click.top}if(c.grid){i=this.originalPageY+Math.round((i-
    +this.originalPageY)/c.grid[1])*c.grid[1];i=this.containment?!(i-this.offset.click.top<this.containment[1]||i-this.offset.click.top>this.containment[3])?i:!(i-this.offset.click.top<this.containment[1])?i-c.grid[1]:i+c.grid[1]:i;e=this.originalPageX+Math.round((e-this.originalPageX)/c.grid[0])*c.grid[0];e=this.containment?!(e-this.offset.click.left<this.containment[0]||e-this.offset.click.left>this.containment[2])?e:!(e-this.offset.click.left<this.containment[0])?e-c.grid[0]:e+c.grid[0]:e}}return{top:i-
    +this.offset.click.top-this.offset.relative.top-this.offset.parent.top+(a.browser.safari&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollTop():g?0:f.scrollTop()),left:e-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+(a.browser.safari&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():g?0:f.scrollLeft())}},_rearrange:function(d,c,f,g){f?f[0].appendChild(this.placeholder[0]):c.item[0].parentNode.insertBefore(this.placeholder[0],
    +this.direction=="down"?c.item[0]:c.item[0].nextSibling);this.counter=this.counter?++this.counter:1;var e=this,i=this.counter;window.setTimeout(function(){i==e.counter&&e.refreshPositions(!g)},0)},_clear:function(d,c){this.reverting=false;var f=[];!this._noFinalSort&&this.currentItem[0].parentNode&&this.placeholder.before(this.currentItem);this._noFinalSort=null;if(this.helper[0]==this.currentItem[0]){for(var g in this._storedCSS)if(this._storedCSS[g]=="auto"||this._storedCSS[g]=="static")this._storedCSS[g]=
    +"";this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper")}else this.currentItem.show();this.fromOutside&&!c&&f.push(function(e){this._trigger("receive",e,this._uiHash(this.fromOutside))});if((this.fromOutside||this.domPosition.prev!=this.currentItem.prev().not(".ui-sortable-helper")[0]||this.domPosition.parent!=this.currentItem.parent()[0])&&!c)f.push(function(e){this._trigger("update",e,this._uiHash())});if(!a.ui.contains(this.element[0],this.currentItem[0])){c||f.push(function(e){this._trigger("remove",
    +e,this._uiHash())});for(g=this.containers.length-1;g>=0;g--)if(a.ui.contains(this.containers[g].element[0],this.currentItem[0])&&!c){f.push(function(e){return function(i){e._trigger("receive",i,this._uiHash(this))}}.call(this,this.containers[g]));f.push(function(e){return function(i){e._trigger("update",i,this._uiHash(this))}}.call(this,this.containers[g]))}}for(g=this.containers.length-1;g>=0;g--){c||f.push(function(e){return function(i){e._trigger("deactivate",i,this._uiHash(this))}}.call(this,
    +this.containers[g]));if(this.containers[g].containerCache.over){f.push(function(e){return function(i){e._trigger("out",i,this._uiHash(this))}}.call(this,this.containers[g]));this.containers[g].containerCache.over=0}}this._storedCursor&&a("body").css("cursor",this._storedCursor);this._storedOpacity&&this.helper.css("opacity",this._storedOpacity);if(this._storedZIndex)this.helper.css("zIndex",this._storedZIndex=="auto"?"":this._storedZIndex);this.dragging=false;if(this.cancelHelperRemoval){if(!c){this._trigger("beforeStop",
    +d,this._uiHash());for(g=0;g<f.length;g++)f[g].call(this,d);this._trigger("stop",d,this._uiHash())}return false}c||this._trigger("beforeStop",d,this._uiHash());this.placeholder[0].parentNode.removeChild(this.placeholder[0]);this.helper[0]!=this.currentItem[0]&&this.helper.remove();this.helper=null;if(!c){for(g=0;g<f.length;g++)f[g].call(this,d);this._trigger("stop",d,this._uiHash())}this.fromOutside=false;return true},_trigger:function(){a.Widget.prototype._trigger.apply(this,arguments)===false&&this.cancel()},
    +_uiHash:function(d){var c=d||this;return{helper:c.helper,placeholder:c.placeholder||a([]),position:c.position,originalPosition:c.originalPosition,offset:c.positionAbs,item:c.currentItem,sender:d?d.element:null}}});a.extend(a.ui.sortable,{version:"1.8.13"})})(jQuery);
    +jQuery.effects||function(a,d){function c(n){var k;if(n&&n.constructor==Array&&n.length==3)return n;if(k=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(n))return[parseInt(k[1],10),parseInt(k[2],10),parseInt(k[3],10)];if(k=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(n))return[parseFloat(k[1])*2.55,parseFloat(k[2])*2.55,parseFloat(k[3])*2.55];if(k=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(n))return[parseInt(k[1],
    +16),parseInt(k[2],16),parseInt(k[3],16)];if(k=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(n))return[parseInt(k[1]+k[1],16),parseInt(k[2]+k[2],16),parseInt(k[3]+k[3],16)];if(/rgba\(0, 0, 0, 0\)/.exec(n))return j.transparent;return j[a.trim(n).toLowerCase()]}function f(n,k){var m;do{m=a.curCSS(n,k);if(m!=""&&m!="transparent"||a.nodeName(n,"body"))break;k="backgroundColor"}while(n=n.parentNode);return c(m)}function g(){var n=document.defaultView?document.defaultView.getComputedStyle(this,null):this.currentStyle,
    +k={},m,p;if(n&&n.length&&n[0]&&n[n[0]])for(var q=n.length;q--;){m=n[q];if(typeof n[m]=="string"){p=m.replace(/\-(\w)/g,function(s,r){return r.toUpperCase()});k[p]=n[m]}}else for(m in n)if(typeof n[m]==="string")k[m]=n[m];return k}function e(n){var k,m;for(k in n){m=n[k];if(m==null||a.isFunction(m)||k in o||/scrollbar/.test(k)||!/color/i.test(k)&&isNaN(parseFloat(m)))delete n[k]}return n}function i(n,k){var m={_:0},p;for(p in k)if(n[p]!=k[p])m[p]=k[p];return m}function b(n,k,m,p){if(typeof n=="object"){p=
    +k;m=null;k=n;n=k.effect}if(a.isFunction(k)){p=k;m=null;k={}}if(typeof k=="number"||a.fx.speeds[k]){p=m;m=k;k={}}if(a.isFunction(m)){p=m;m=null}k=k||{};m=m||k.duration;m=a.fx.off?0:typeof m=="number"?m:m in a.fx.speeds?a.fx.speeds[m]:a.fx.speeds._default;p=p||k.complete;return[n,k,m,p]}function h(n){if(!n||typeof n==="number"||a.fx.speeds[n])return true;if(typeof n==="string"&&!a.effects[n])return true;return false}a.effects={};a.each(["backgroundColor","borderBottomColor","borderLeftColor","borderRightColor",
    +"borderTopColor","borderColor","color","outlineColor"],function(n,k){a.fx.step[k]=function(m){if(!m.colorInit){m.start=f(m.elem,k);m.end=c(m.end);m.colorInit=true}m.elem.style[k]="rgb("+Math.max(Math.min(parseInt(m.pos*(m.end[0]-m.start[0])+m.start[0],10),255),0)+","+Math.max(Math.min(parseInt(m.pos*(m.end[1]-m.start[1])+m.start[1],10),255),0)+","+Math.max(Math.min(parseInt(m.pos*(m.end[2]-m.start[2])+m.start[2],10),255),0)+")"}});var j={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,
    +0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,
    +211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0],transparent:[255,255,255]},l=["add","remove","toggle"],o={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};a.effects.animateClass=function(n,k,m,
    +p){if(a.isFunction(m)){p=m;m=null}return this.queue(function(){var q=a(this),s=q.attr("style")||" ",r=e(g.call(this)),u,v=q.attr("class");a.each(l,function(w,x){n[x]&&q[x+"Class"](n[x])});u=e(g.call(this));q.attr("class",v);q.animate(i(r,u),{queue:false,duration:k,easding:m,complete:function(){a.each(l,function(w,x){n[x]&&q[x+"Class"](n[x])});if(typeof q.attr("style")=="object"){q.attr("style").cssText="";q.attr("style").cssText=s}else q.attr("style",s);p&&p.apply(this,arguments);a.dequeue(this)}})})};
    +a.fn.extend({_addClass:a.fn.addClass,addClass:function(n,k,m,p){return k?a.effects.animateClass.apply(this,[{add:n},k,m,p]):this._addClass(n)},_removeClass:a.fn.removeClass,removeClass:function(n,k,m,p){return k?a.effects.animateClass.apply(this,[{remove:n},k,m,p]):this._removeClass(n)},_toggleClass:a.fn.toggleClass,toggleClass:function(n,k,m,p,q){return typeof k=="boolean"||k===d?m?a.effects.animateClass.apply(this,[k?{add:n}:{remove:n},m,p,q]):this._toggleClass(n,k):a.effects.animateClass.apply(this,
    +[{toggle:n},k,m,p])},switchClass:function(n,k,m,p,q){return a.effects.animateClass.apply(this,[{add:k,remove:n},m,p,q])}});a.extend(a.effects,{version:"1.8.13",save:function(n,k){for(var m=0;m<k.length;m++)k[m]!==null&&n.data("ec.storage."+k[m],n[0].style[k[m]])},restore:function(n,k){for(var m=0;m<k.length;m++)k[m]!==null&&n.css(k[m],n.data("ec.storage."+k[m]))},setMode:function(n,k){if(k=="toggle")k=n.is(":hidden")?"show":"hide";return k},getBaseline:function(n,k){var m;switch(n[0]){case "top":m=
    +0;break;case "middle":m=0.5;break;case "bottom":m=1;break;default:m=n[0]/k.height}switch(n[1]){case "left":n=0;break;case "center":n=0.5;break;case "right":n=1;break;default:n=n[1]/k.width}return{x:n,y:m}},createWrapper:function(n){if(n.parent().is(".ui-effects-wrapper"))return n.parent();var k={width:n.outerWidth(true),height:n.outerHeight(true),"float":n.css("float")},m=a("<div></div>").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0});
    +n.wrap(m);m=n.parent();if(n.css("position")=="static"){m.css({position:"relative"});n.css({position:"relative"})}else{a.extend(k,{position:n.css("position"),zIndex:n.css("z-index")});a.each(["top","left","bottom","right"],function(p,q){k[q]=n.css(q);if(isNaN(parseInt(k[q],10)))k[q]="auto"});n.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})}return m.css(k).show()},removeWrapper:function(n){if(n.parent().is(".ui-effects-wrapper"))return n.parent().replaceWith(n);return n},setTransition:function(n,
    +k,m,p){p=p||{};a.each(k,function(q,s){unit=n.cssUnit(s);if(unit[0]>0)p[s]=unit[0]*m+unit[1]});return p}});a.fn.extend({effect:function(n){var k=b.apply(this,arguments),m={options:k[1],duration:k[2],callback:k[3]};k=m.options.mode;var p=a.effects[n];if(a.fx.off||!p)return k?this[k](m.duration,m.callback):this.each(function(){m.callback&&m.callback.call(this)});return p.call(this,m)},_show:a.fn.show,show:function(n){if(h(n))return this._show.apply(this,arguments);else{var k=b.apply(this,arguments);
    +k[1].mode="show";return this.effect.apply(this,k)}},_hide:a.fn.hide,hide:function(n){if(h(n))return this._hide.apply(this,arguments);else{var k=b.apply(this,arguments);k[1].mode="hide";return this.effect.apply(this,k)}},__toggle:a.fn.toggle,toggle:function(n){if(h(n)||typeof n==="boolean"||a.isFunction(n))return this.__toggle.apply(this,arguments);else{var k=b.apply(this,arguments);k[1].mode="toggle";return this.effect.apply(this,k)}},cssUnit:function(n){var k=this.css(n),m=[];a.each(["em","px","%",
    +"pt"],function(p,q){if(k.indexOf(q)>0)m=[parseFloat(k),q]});return m}});a.easing.jswing=a.easing.swing;a.extend(a.easing,{def:"easeOutQuad",swing:function(n,k,m,p,q){return a.easing[a.easing.def](n,k,m,p,q)},easeInQuad:function(n,k,m,p,q){return p*(k/=q)*k+m},easeOutQuad:function(n,k,m,p,q){return-p*(k/=q)*(k-2)+m},easeInOutQuad:function(n,k,m,p,q){if((k/=q/2)<1)return p/2*k*k+m;return-p/2*(--k*(k-2)-1)+m},easeInCubic:function(n,k,m,p,q){return p*(k/=q)*k*k+m},easeOutCubic:function(n,k,m,p,q){return p*
    +((k=k/q-1)*k*k+1)+m},easeInOutCubic:function(n,k,m,p,q){if((k/=q/2)<1)return p/2*k*k*k+m;return p/2*((k-=2)*k*k+2)+m},easeInQuart:function(n,k,m,p,q){return p*(k/=q)*k*k*k+m},easeOutQuart:function(n,k,m,p,q){return-p*((k=k/q-1)*k*k*k-1)+m},easeInOutQuart:function(n,k,m,p,q){if((k/=q/2)<1)return p/2*k*k*k*k+m;return-p/2*((k-=2)*k*k*k-2)+m},easeInQuint:function(n,k,m,p,q){return p*(k/=q)*k*k*k*k+m},easeOutQuint:function(n,k,m,p,q){return p*((k=k/q-1)*k*k*k*k+1)+m},easeInOutQuint:function(n,k,m,p,q){if((k/=
    +q/2)<1)return p/2*k*k*k*k*k+m;return p/2*((k-=2)*k*k*k*k+2)+m},easeInSine:function(n,k,m,p,q){return-p*Math.cos(k/q*(Math.PI/2))+p+m},easeOutSine:function(n,k,m,p,q){return p*Math.sin(k/q*(Math.PI/2))+m},easeInOutSine:function(n,k,m,p,q){return-p/2*(Math.cos(Math.PI*k/q)-1)+m},easeInExpo:function(n,k,m,p,q){return k==0?m:p*Math.pow(2,10*(k/q-1))+m},easeOutExpo:function(n,k,m,p,q){return k==q?m+p:p*(-Math.pow(2,-10*k/q)+1)+m},easeInOutExpo:function(n,k,m,p,q){if(k==0)return m;if(k==q)return m+p;if((k/=
    +q/2)<1)return p/2*Math.pow(2,10*(k-1))+m;return p/2*(-Math.pow(2,-10*--k)+2)+m},easeInCirc:function(n,k,m,p,q){return-p*(Math.sqrt(1-(k/=q)*k)-1)+m},easeOutCirc:function(n,k,m,p,q){return p*Math.sqrt(1-(k=k/q-1)*k)+m},easeInOutCirc:function(n,k,m,p,q){if((k/=q/2)<1)return-p/2*(Math.sqrt(1-k*k)-1)+m;return p/2*(Math.sqrt(1-(k-=2)*k)+1)+m},easeInElastic:function(n,k,m,p,q){n=1.70158;var s=0,r=p;if(k==0)return m;if((k/=q)==1)return m+p;s||(s=q*0.3);if(r<Math.abs(p)){r=p;n=s/4}else n=s/(2*Math.PI)*Math.asin(p/
    +r);return-(r*Math.pow(2,10*(k-=1))*Math.sin((k*q-n)*2*Math.PI/s))+m},easeOutElastic:function(n,k,m,p,q){n=1.70158;var s=0,r=p;if(k==0)return m;if((k/=q)==1)return m+p;s||(s=q*0.3);if(r<Math.abs(p)){r=p;n=s/4}else n=s/(2*Math.PI)*Math.asin(p/r);return r*Math.pow(2,-10*k)*Math.sin((k*q-n)*2*Math.PI/s)+p+m},easeInOutElastic:function(n,k,m,p,q){n=1.70158;var s=0,r=p;if(k==0)return m;if((k/=q/2)==2)return m+p;s||(s=q*0.3*1.5);if(r<Math.abs(p)){r=p;n=s/4}else n=s/(2*Math.PI)*Math.asin(p/r);if(k<1)return-0.5*
    +r*Math.pow(2,10*(k-=1))*Math.sin((k*q-n)*2*Math.PI/s)+m;return r*Math.pow(2,-10*(k-=1))*Math.sin((k*q-n)*2*Math.PI/s)*0.5+p+m},easeInBack:function(n,k,m,p,q,s){if(s==d)s=1.70158;return p*(k/=q)*k*((s+1)*k-s)+m},easeOutBack:function(n,k,m,p,q,s){if(s==d)s=1.70158;return p*((k=k/q-1)*k*((s+1)*k+s)+1)+m},easeInOutBack:function(n,k,m,p,q,s){if(s==d)s=1.70158;if((k/=q/2)<1)return p/2*k*k*(((s*=1.525)+1)*k-s)+m;return p/2*((k-=2)*k*(((s*=1.525)+1)*k+s)+2)+m},easeInBounce:function(n,k,m,p,q){return p-a.easing.easeOutBounce(n,
    +q-k,0,p,q)+m},easeOutBounce:function(n,k,m,p,q){return(k/=q)<1/2.75?p*7.5625*k*k+m:k<2/2.75?p*(7.5625*(k-=1.5/2.75)*k+0.75)+m:k<2.5/2.75?p*(7.5625*(k-=2.25/2.75)*k+0.9375)+m:p*(7.5625*(k-=2.625/2.75)*k+0.984375)+m},easeInOutBounce:function(n,k,m,p,q){if(k<q/2)return a.easing.easeInBounce(n,k*2,0,p,q)*0.5+m;return a.easing.easeOutBounce(n,k*2-q,0,p,q)*0.5+p*0.5+m}})}(jQuery);
    +(function(a){a.effects.blind=function(d){return this.queue(function(){var c=a(this),f=["position","top","bottom","left","right"],g=a.effects.setMode(c,d.options.mode||"hide"),e=d.options.direction||"vertical";a.effects.save(c,f);c.show();var i=a.effects.createWrapper(c).css({overflow:"hidden"}),b=e=="vertical"?"height":"width";e=e=="vertical"?i.height():i.width();g=="show"&&i.css(b,0);var h={};h[b]=g=="show"?e:0;i.animate(h,d.duration,d.options.easing,function(){g=="hide"&&c.hide();a.effects.restore(c,
    +f);a.effects.removeWrapper(c);d.callback&&d.callback.apply(c[0],arguments);c.dequeue()})})}})(jQuery);
    +(function(a){a.effects.bounce=function(d){return this.queue(function(){var c=a(this),f=["position","top","bottom","left","right"],g=a.effects.setMode(c,d.options.mode||"effect"),e=d.options.direction||"up",i=d.options.distance||20,b=d.options.times||5,h=d.duration||250;/show|hide/.test(g)&&f.push("opacity");a.effects.save(c,f);c.show();a.effects.createWrapper(c);var j=e=="up"||e=="down"?"top":"left";e=e=="up"||e=="left"?"pos":"neg";i=d.options.distance||(j=="top"?c.outerHeight({margin:true})/3:c.outerWidth({margin:true})/
    +3);if(g=="show")c.css("opacity",0).css(j,e=="pos"?-i:i);if(g=="hide")i/=b*2;g!="hide"&&b--;if(g=="show"){var l={opacity:1};l[j]=(e=="pos"?"+=":"-=")+i;c.animate(l,h/2,d.options.easing);i/=2;b--}for(l=0;l<b;l++){var o={},n={};o[j]=(e=="pos"?"-=":"+=")+i;n[j]=(e=="pos"?"+=":"-=")+i;c.animate(o,h/2,d.options.easing).animate(n,h/2,d.options.easing);i=g=="hide"?i*2:i/2}if(g=="hide"){l={opacity:0};l[j]=(e=="pos"?"-=":"+=")+i;c.animate(l,h/2,d.options.easing,function(){c.hide();a.effects.restore(c,f);a.effects.removeWrapper(c);
    +d.callback&&d.callback.apply(this,arguments)})}else{o={};n={};o[j]=(e=="pos"?"-=":"+=")+i;n[j]=(e=="pos"?"+=":"-=")+i;c.animate(o,h/2,d.options.easing).animate(n,h/2,d.options.easing,function(){a.effects.restore(c,f);a.effects.removeWrapper(c);d.callback&&d.callback.apply(this,arguments)})}c.queue("fx",function(){c.dequeue()});c.dequeue()})}})(jQuery);
    +(function(a){a.effects.clip=function(d){return this.queue(function(){var c=a(this),f=["position","top","bottom","left","right","height","width"],g=a.effects.setMode(c,d.options.mode||"hide"),e=d.options.direction||"vertical";a.effects.save(c,f);c.show();var i=a.effects.createWrapper(c).css({overflow:"hidden"});i=c[0].tagName=="IMG"?i:c;var b={size:e=="vertical"?"height":"width",position:e=="vertical"?"top":"left"};e=e=="vertical"?i.height():i.width();if(g=="show"){i.css(b.size,0);i.css(b.position,
    +e/2)}var h={};h[b.size]=g=="show"?e:0;h[b.position]=g=="show"?0:e/2;i.animate(h,{queue:false,duration:d.duration,easing:d.options.easing,complete:function(){g=="hide"&&c.hide();a.effects.restore(c,f);a.effects.removeWrapper(c);d.callback&&d.callback.apply(c[0],arguments);c.dequeue()}})})}})(jQuery);
    +(function(a){a.effects.drop=function(d){return this.queue(function(){var c=a(this),f=["position","top","bottom","left","right","opacity"],g=a.effects.setMode(c,d.options.mode||"hide"),e=d.options.direction||"left";a.effects.save(c,f);c.show();a.effects.createWrapper(c);var i=e=="up"||e=="down"?"top":"left";e=e=="up"||e=="left"?"pos":"neg";var b=d.options.distance||(i=="top"?c.outerHeight({margin:true})/2:c.outerWidth({margin:true})/2);if(g=="show")c.css("opacity",0).css(i,e=="pos"?-b:b);var h={opacity:g==
    +"show"?1:0};h[i]=(g=="show"?e=="pos"?"+=":"-=":e=="pos"?"-=":"+=")+b;c.animate(h,{queue:false,duration:d.duration,easing:d.options.easing,complete:function(){g=="hide"&&c.hide();a.effects.restore(c,f);a.effects.removeWrapper(c);d.callback&&d.callback.apply(this,arguments);c.dequeue()}})})}})(jQuery);
    +(function(a){a.effects.explode=function(d){return this.queue(function(){var c=d.options.pieces?Math.round(Math.sqrt(d.options.pieces)):3,f=d.options.pieces?Math.round(Math.sqrt(d.options.pieces)):3;d.options.mode=d.options.mode=="toggle"?a(this).is(":visible")?"hide":"show":d.options.mode;var g=a(this).show().css("visibility","hidden"),e=g.offset();e.top-=parseInt(g.css("marginTop"),10)||0;e.left-=parseInt(g.css("marginLeft"),10)||0;for(var i=g.outerWidth(true),b=g.outerHeight(true),h=0;h<c;h++)for(var j=
    +0;j<f;j++)g.clone().appendTo("body").wrap("<div></div>").css({position:"absolute",visibility:"visible",left:-j*(i/f),top:-h*(b/c)}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:i/f,height:b/c,left:e.left+j*(i/f)+(d.options.mode=="show"?(j-Math.floor(f/2))*(i/f):0),top:e.top+h*(b/c)+(d.options.mode=="show"?(h-Math.floor(c/2))*(b/c):0),opacity:d.options.mode=="show"?0:1}).animate({left:e.left+j*(i/f)+(d.options.mode=="show"?0:(j-Math.floor(f/2))*(i/f)),top:e.top+
    +h*(b/c)+(d.options.mode=="show"?0:(h-Math.floor(c/2))*(b/c)),opacity:d.options.mode=="show"?1:0},d.duration||500);setTimeout(function(){d.options.mode=="show"?g.css({visibility:"visible"}):g.css({visibility:"visible"}).hide();d.callback&&d.callback.apply(g[0]);g.dequeue();a("div.ui-effects-explode").remove()},d.duration||500)})}})(jQuery);
    +(function(a){a.effects.fade=function(d){return this.queue(function(){var c=a(this),f=a.effects.setMode(c,d.options.mode||"hide");c.animate({opacity:f},{queue:false,duration:d.duration,easing:d.options.easing,complete:function(){d.callback&&d.callback.apply(this,arguments);c.dequeue()}})})}})(jQuery);
    +(function(a){a.effects.fold=function(d){return this.queue(function(){var c=a(this),f=["position","top","bottom","left","right"],g=a.effects.setMode(c,d.options.mode||"hide"),e=d.options.size||15,i=!!d.options.horizFirst,b=d.duration?d.duration/2:a.fx.speeds._default/2;a.effects.save(c,f);c.show();var h=a.effects.createWrapper(c).css({overflow:"hidden"}),j=g=="show"!=i,l=j?["width","height"]:["height","width"];j=j?[h.width(),h.height()]:[h.height(),h.width()];var o=/([0-9]+)%/.exec(e);if(o)e=parseInt(o[1],
    +10)/100*j[g=="hide"?0:1];if(g=="show")h.css(i?{height:0,width:e}:{height:e,width:0});i={};o={};i[l[0]]=g=="show"?j[0]:e;o[l[1]]=g=="show"?j[1]:0;h.animate(i,b,d.options.easing).animate(o,b,d.options.easing,function(){g=="hide"&&c.hide();a.effects.restore(c,f);a.effects.removeWrapper(c);d.callback&&d.callback.apply(c[0],arguments);c.dequeue()})})}})(jQuery);
    +(function(a){a.effects.highlight=function(d){return this.queue(function(){var c=a(this),f=["backgroundImage","backgroundColor","opacity"],g=a.effects.setMode(c,d.options.mode||"show"),e={backgroundColor:c.css("backgroundColor")};if(g=="hide")e.opacity=0;a.effects.save(c,f);c.show().css({backgroundImage:"none",backgroundColor:d.options.color||"#ffff99"}).animate(e,{queue:false,duration:d.duration,easing:d.options.easing,complete:function(){g=="hide"&&c.hide();a.effects.restore(c,f);g=="show"&&!a.support.opacity&&
    +this.style.removeAttribute("filter");d.callback&&d.callback.apply(this,arguments);c.dequeue()}})})}})(jQuery);
    +(function(a){a.effects.pulsate=function(d){return this.queue(function(){var c=a(this),f=a.effects.setMode(c,d.options.mode||"show");times=(d.options.times||5)*2-1;duration=d.duration?d.duration/2:a.fx.speeds._default/2;isVisible=c.is(":visible");animateTo=0;if(!isVisible){c.css("opacity",0).show();animateTo=1}if(f=="hide"&&isVisible||f=="show"&&!isVisible)times--;for(f=0;f<times;f++){c.animate({opacity:animateTo},duration,d.options.easing);animateTo=(animateTo+1)%2}c.animate({opacity:animateTo},duration,
    +d.options.easing,function(){animateTo==0&&c.hide();d.callback&&d.callback.apply(this,arguments)});c.queue("fx",function(){c.dequeue()}).dequeue()})}})(jQuery);
    +(function(a){a.effects.puff=function(d){return this.queue(function(){var c=a(this),f=a.effects.setMode(c,d.options.mode||"hide"),g=parseInt(d.options.percent,10)||150,e=g/100,i={height:c.height(),width:c.width()};a.extend(d.options,{fade:true,mode:f,percent:f=="hide"?g:100,from:f=="hide"?i:{height:i.height*e,width:i.width*e}});c.effect("scale",d.options,d.duration,d.callback);c.dequeue()})};a.effects.scale=function(d){return this.queue(function(){var c=a(this),f=a.extend(true,{},d.options),g=a.effects.setMode(c,
    +d.options.mode||"effect"),e=parseInt(d.options.percent,10)||(parseInt(d.options.percent,10)==0?0:g=="hide"?0:100),i=d.options.direction||"both",b=d.options.origin;if(g!="effect"){f.origin=b||["middle","center"];f.restore=true}b={height:c.height(),width:c.width()};c.from=d.options.from||(g=="show"?{height:0,width:0}:b);e={y:i!="horizontal"?e/100:1,x:i!="vertical"?e/100:1};c.to={height:b.height*e.y,width:b.width*e.x};if(d.options.fade){if(g=="show"){c.from.opacity=0;c.to.opacity=1}if(g=="hide"){c.from.opacity=
    +1;c.to.opacity=0}}f.from=c.from;f.to=c.to;f.mode=g;c.effect("size",f,d.duration,d.callback);c.dequeue()})};a.effects.size=function(d){return this.queue(function(){var c=a(this),f=["position","top","bottom","left","right","width","height","overflow","opacity"],g=["position","top","bottom","left","right","overflow","opacity"],e=["width","height","overflow"],i=["fontSize"],b=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],h=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],
    +j=a.effects.setMode(c,d.options.mode||"effect"),l=d.options.restore||false,o=d.options.scale||"both",n=d.options.origin,k={height:c.height(),width:c.width()};c.from=d.options.from||k;c.to=d.options.to||k;if(n){n=a.effects.getBaseline(n,k);c.from.top=(k.height-c.from.height)*n.y;c.from.left=(k.width-c.from.width)*n.x;c.to.top=(k.height-c.to.height)*n.y;c.to.left=(k.width-c.to.width)*n.x}var m={from:{y:c.from.height/k.height,x:c.from.width/k.width},to:{y:c.to.height/k.height,x:c.to.width/k.width}};
    +if(o=="box"||o=="both"){if(m.from.y!=m.to.y){f=f.concat(b);c.from=a.effects.setTransition(c,b,m.from.y,c.from);c.to=a.effects.setTransition(c,b,m.to.y,c.to)}if(m.from.x!=m.to.x){f=f.concat(h);c.from=a.effects.setTransition(c,h,m.from.x,c.from);c.to=a.effects.setTransition(c,h,m.to.x,c.to)}}if(o=="content"||o=="both")if(m.from.y!=m.to.y){f=f.concat(i);c.from=a.effects.setTransition(c,i,m.from.y,c.from);c.to=a.effects.setTransition(c,i,m.to.y,c.to)}a.effects.save(c,l?f:g);c.show();a.effects.createWrapper(c);
    +c.css("overflow","hidden").css(c.from);if(o=="content"||o=="both"){b=b.concat(["marginTop","marginBottom"]).concat(i);h=h.concat(["marginLeft","marginRight"]);e=f.concat(b).concat(h);c.find("*[width]").each(function(){child=a(this);l&&a.effects.save(child,e);var p={height:child.height(),width:child.width()};child.from={height:p.height*m.from.y,width:p.width*m.from.x};child.to={height:p.height*m.to.y,width:p.width*m.to.x};if(m.from.y!=m.to.y){child.from=a.effects.setTransition(child,b,m.from.y,child.from);
    +child.to=a.effects.setTransition(child,b,m.to.y,child.to)}if(m.from.x!=m.to.x){child.from=a.effects.setTransition(child,h,m.from.x,child.from);child.to=a.effects.setTransition(child,h,m.to.x,child.to)}child.css(child.from);child.animate(child.to,d.duration,d.options.easing,function(){l&&a.effects.restore(child,e)})})}c.animate(c.to,{queue:false,duration:d.duration,easing:d.options.easing,complete:function(){c.to.opacity===0&&c.css("opacity",c.from.opacity);j=="hide"&&c.hide();a.effects.restore(c,
    +l?f:g);a.effects.removeWrapper(c);d.callback&&d.callback.apply(this,arguments);c.dequeue()}})})}})(jQuery);
    +(function(a){a.effects.shake=function(d){return this.queue(function(){var c=a(this),f=["position","top","bottom","left","right"];a.effects.setMode(c,d.options.mode||"effect");var g=d.options.direction||"left",e=d.options.distance||20,i=d.options.times||3,b=d.duration||d.options.duration||140;a.effects.save(c,f);c.show();a.effects.createWrapper(c);var h=g=="up"||g=="down"?"top":"left",j=g=="up"||g=="left"?"pos":"neg";g={};var l={},o={};g[h]=(j=="pos"?"-=":"+=")+e;l[h]=(j=="pos"?"+=":"-=")+e*2;o[h]=
    +(j=="pos"?"-=":"+=")+e*2;c.animate(g,b,d.options.easing);for(e=1;e<i;e++)c.animate(l,b,d.options.easing).animate(o,b,d.options.easing);c.animate(l,b,d.options.easing).animate(g,b/2,d.options.easing,function(){a.effects.restore(c,f);a.effects.removeWrapper(c);d.callback&&d.callback.apply(this,arguments)});c.queue("fx",function(){c.dequeue()});c.dequeue()})}})(jQuery);
    +(function(a){a.effects.slide=function(d){return this.queue(function(){var c=a(this),f=["position","top","bottom","left","right"],g=a.effects.setMode(c,d.options.mode||"show"),e=d.options.direction||"left";a.effects.save(c,f);c.show();a.effects.createWrapper(c).css({overflow:"hidden"});var i=e=="up"||e=="down"?"top":"left";e=e=="up"||e=="left"?"pos":"neg";var b=d.options.distance||(i=="top"?c.outerHeight({margin:true}):c.outerWidth({margin:true}));if(g=="show")c.css(i,e=="pos"?isNaN(b)?"-"+b:-b:b);
    +var h={};h[i]=(g=="show"?e=="pos"?"+=":"-=":e=="pos"?"-=":"+=")+b;c.animate(h,{queue:false,duration:d.duration,easing:d.options.easing,complete:function(){g=="hide"&&c.hide();a.effects.restore(c,f);a.effects.removeWrapper(c);d.callback&&d.callback.apply(this,arguments);c.dequeue()}})})}})(jQuery);
    +(function(a){a.effects.transfer=function(d){return this.queue(function(){var c=a(this),f=a(d.options.to),g=f.offset();f={top:g.top,left:g.left,height:f.innerHeight(),width:f.innerWidth()};g=c.offset();var e=a('<div class="ui-effects-transfer"></div>').appendTo(document.body).addClass(d.options.className).css({top:g.top,left:g.left,height:c.innerHeight(),width:c.innerWidth(),position:"absolute"}).animate(f,d.duration,d.options.easing,function(){e.remove();d.callback&&d.callback.apply(c[0],arguments);
    +c.dequeue()})})}})(jQuery);
    +(function(a){a.widget("ui.accordion",{options:{active:0,animated:"slide",autoHeight:true,clearStyle:false,collapsible:false,event:"click",fillSpace:false,header:"> li > :first-child,> :not(li):even",icons:{header:"ui-icon-triangle-1-e",headerSelected:"ui-icon-triangle-1-s"},navigation:false,navigationFilter:function(){return this.href.toLowerCase()===location.href.toLowerCase()}},_create:function(){var d=this,c=d.options;d.running=0;d.element.addClass("ui-accordion ui-widget ui-helper-reset").children("li").addClass("ui-accordion-li-fix");d.headers=
    +d.element.find(c.header).addClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all").bind("mouseenter.accordion",function(){c.disabled||a(this).addClass("ui-state-hover")}).bind("mouseleave.accordion",function(){c.disabled||a(this).removeClass("ui-state-hover")}).bind("focus.accordion",function(){c.disabled||a(this).addClass("ui-state-focus")}).bind("blur.accordion",function(){c.disabled||a(this).removeClass("ui-state-focus")});d.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom");
    +if(c.navigation){var f=d.element.find("a").filter(c.navigationFilter).eq(0);if(f.length){var g=f.closest(".ui-accordion-header");d.active=g.length?g:f.closest(".ui-accordion-content").prev()}}d.active=d._findActive(d.active||c.active).addClass("ui-state-default ui-state-active").toggleClass("ui-corner-all").toggleClass("ui-corner-top");d.active.next().addClass("ui-accordion-content-active");d._createIcons();d.resize();d.element.attr("role","tablist");d.headers.attr("role","tab").bind("keydown.accordion",
    +function(e){return d._keydown(e)}).next().attr("role","tabpanel");d.headers.not(d.active||"").attr({"aria-expanded":"false","aria-selected":"false",tabIndex:-1}).next().hide();d.active.length?d.active.attr({"aria-expanded":"true","aria-selected":"true",tabIndex:0}):d.headers.eq(0).attr("tabIndex",0);a.browser.safari||d.headers.find("a").attr("tabIndex",-1);c.event&&d.headers.bind(c.event.split(" ").join(".accordion ")+".accordion",function(e){d._clickHandler.call(d,e,this);e.preventDefault()})},_createIcons:function(){var d=
    +this.options;if(d.icons){a("<span></span>").addClass("ui-icon "+d.icons.header).prependTo(this.headers);this.active.children(".ui-icon").toggleClass(d.icons.header).toggleClass(d.icons.headerSelected);this.element.addClass("ui-accordion-icons")}},_destroyIcons:function(){this.headers.children(".ui-icon").remove();this.element.removeClass("ui-accordion-icons")},destroy:function(){var d=this.options;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role");this.headers.unbind(".accordion").removeClass("ui-accordion-header ui-accordion-disabled ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top").removeAttr("role").removeAttr("aria-expanded").removeAttr("aria-selected").removeAttr("tabIndex");
    +this.headers.find("a").removeAttr("tabIndex");this._destroyIcons();var c=this.headers.next().css("display","").removeAttr("role").removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-accordion-disabled ui-state-disabled");if(d.autoHeight||d.fillHeight)c.css("height","");return a.Widget.prototype.destroy.call(this)},_setOption:function(d,c){a.Widget.prototype._setOption.apply(this,arguments);d=="active"&&this.activate(c);if(d=="icons"){this._destroyIcons();
    +c&&this._createIcons()}if(d=="disabled")this.headers.add(this.headers.next())[c?"addClass":"removeClass"]("ui-accordion-disabled ui-state-disabled")},_keydown:function(d){if(!(this.options.disabled||d.altKey||d.ctrlKey)){var c=a.ui.keyCode,f=this.headers.length,g=this.headers.index(d.target),e=false;switch(d.keyCode){case c.RIGHT:case c.DOWN:e=this.headers[(g+1)%f];break;case c.LEFT:case c.UP:e=this.headers[(g-1+f)%f];break;case c.SPACE:case c.ENTER:this._clickHandler({target:d.target},d.target);
    +d.preventDefault()}if(e){a(d.target).attr("tabIndex",-1);a(e).attr("tabIndex",0);e.focus();return false}return true}},resize:function(){var d=this.options,c;if(d.fillSpace){if(a.browser.msie){var f=this.element.parent().css("overflow");this.element.parent().css("overflow","hidden")}c=this.element.parent().height();a.browser.msie&&this.element.parent().css("overflow",f);this.headers.each(function(){c-=a(this).outerHeight(true)});this.headers.next().each(function(){a(this).height(Math.max(0,c-a(this).innerHeight()+
    +a(this).height()))}).css("overflow","auto")}else if(d.autoHeight){c=0;this.headers.next().each(function(){c=Math.max(c,a(this).height("").height())}).height(c)}return this},activate:function(d){this.options.active=d;d=this._findActive(d)[0];this._clickHandler({target:d},d);return this},_findActive:function(d){return d?typeof d==="number"?this.headers.filter(":eq("+d+")"):this.headers.not(this.headers.not(d)):d===false?a([]):this.headers.filter(":eq(0)")},_clickHandler:function(d,c){var f=this.options;
    +if(!f.disabled)if(d.target){d=a(d.currentTarget||c);c=d[0]===this.active[0];f.active=f.collapsible&&c?false:this.headers.index(d);if(!(this.running||!f.collapsible&&c)){var g=this.active;h=d.next();i=this.active.next();b={options:f,newHeader:c&&f.collapsible?a([]):d,oldHeader:this.active,newContent:c&&f.collapsible?a([]):h,oldContent:i};var e=this.headers.index(this.active[0])>this.headers.index(d[0]);this.active=c?a([]):d;this._toggle(h,i,b,c,e);g.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").children(".ui-icon").removeClass(f.icons.headerSelected).addClass(f.icons.header);
    +if(!c){d.removeClass("ui-state-default ui-corner-all").addClass("ui-state-active ui-corner-top").children(".ui-icon").removeClass(f.icons.header).addClass(f.icons.headerSelected);d.next().addClass("ui-accordion-content-active")}}}else if(f.collapsible){this.active.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").children(".ui-icon").removeClass(f.icons.headerSelected).addClass(f.icons.header);this.active.next().addClass("ui-accordion-content-active");var i=this.active.next(),
    +b={options:f,newHeader:a([]),oldHeader:f.active,newContent:a([]),oldContent:i},h=this.active=a([]);this._toggle(h,i,b)}},_toggle:function(d,c,f,g,e){var i=this,b=i.options;i.toShow=d;i.toHide=c;i.data=f;var h=function(){if(i)return i._completed.apply(i,arguments)};i._trigger("changestart",null,i.data);i.running=c.size()===0?d.size():c.size();if(b.animated){f={};f=b.collapsible&&g?{toShow:a([]),toHide:c,complete:h,down:e,autoHeight:b.autoHeight||b.fillSpace}:{toShow:d,toHide:c,complete:h,down:e,autoHeight:b.autoHeight||
    +b.fillSpace};if(!b.proxied)b.proxied=b.animated;if(!b.proxiedDuration)b.proxiedDuration=b.duration;b.animated=a.isFunction(b.proxied)?b.proxied(f):b.proxied;b.duration=a.isFunction(b.proxiedDuration)?b.proxiedDuration(f):b.proxiedDuration;g=a.ui.accordion.animations;var j=b.duration,l=b.animated;if(l&&!g[l]&&!a.easing[l])l="slide";g[l]||(g[l]=function(o){this.slide(o,{easing:l,duration:j||700})});g[l](f)}else{if(b.collapsible&&g)d.toggle();else{c.hide();d.show()}h(true)}c.prev().attr({"aria-expanded":"false",
    +"aria-selected":"false",tabIndex:-1}).blur();d.prev().attr({"aria-expanded":"true","aria-selected":"true",tabIndex:0}).focus()},_completed:function(d){this.running=d?0:--this.running;if(!this.running){this.options.clearStyle&&this.toShow.add(this.toHide).css({height:"",overflow:""});this.toHide.removeClass("ui-accordion-content-active");if(this.toHide.length)this.toHide.parent()[0].className=this.toHide.parent()[0].className;this._trigger("change",null,this.data)}}});a.extend(a.ui.accordion,{version:"1.8.13",
    +animations:{slide:function(d,c){d=a.extend({easing:"swing",duration:300},d,c);if(d.toHide.size())if(d.toShow.size()){var f=d.toShow.css("overflow"),g=0,e={},i={},b;c=d.toShow;b=c[0].style.width;c.width(parseInt(c.parent().width(),10)-parseInt(c.css("paddingLeft"),10)-parseInt(c.css("paddingRight"),10)-(parseInt(c.css("borderLeftWidth"),10)||0)-(parseInt(c.css("borderRightWidth"),10)||0));a.each(["height","paddingTop","paddingBottom"],function(h,j){i[j]="hide";h=(""+a.css(d.toShow[0],j)).match(/^([\d+-.]+)(.*)$/);
    +e[j]={value:h[1],unit:h[2]||"px"}});d.toShow.css({height:0,overflow:"hidden"}).show();d.toHide.filter(":hidden").each(d.complete).end().filter(":visible").animate(i,{step:function(h,j){if(j.prop=="height")g=j.end-j.start===0?0:(j.now-j.start)/(j.end-j.start);d.toShow[0].style[j.prop]=g*e[j.prop].value+e[j.prop].unit},duration:d.duration,easing:d.easing,complete:function(){d.autoHeight||d.toShow.css("height","");d.toShow.css({width:b,overflow:f});d.complete()}})}else d.toHide.animate({height:"hide",
    +paddingTop:"hide",paddingBottom:"hide"},d);else d.toShow.animate({height:"show",paddingTop:"show",paddingBottom:"show"},d)},bounceslide:function(d){this.slide(d,{easing:d.down?"easeOutBounce":"swing",duration:d.down?1E3:200})}}})})(jQuery);
    +(function(a){var d=0;a.widget("ui.autocomplete",{options:{appendTo:"body",autoFocus:false,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null},pending:0,_create:function(){var c=this,f=this.element[0].ownerDocument,g;this.element.addClass("ui-autocomplete-input").attr("autocomplete","off").attr({role:"textbox","aria-autocomplete":"list","aria-haspopup":"true"}).bind("keydown.autocomplete",function(e){if(!(c.options.disabled||c.element.attr("readonly"))){g=
    +false;var i=a.ui.keyCode;switch(e.keyCode){case i.PAGE_UP:c._move("previousPage",e);break;case i.PAGE_DOWN:c._move("nextPage",e);break;case i.UP:c._move("previous",e);e.preventDefault();break;case i.DOWN:c._move("next",e);e.preventDefault();break;case i.ENTER:case i.NUMPAD_ENTER:if(c.menu.active){g=true;e.preventDefault()}case i.TAB:if(!c.menu.active)return;c.menu.select(e);break;case i.ESCAPE:c.element.val(c.term);c.close(e);break;default:clearTimeout(c.searching);c.searching=setTimeout(function(){if(c.term!=
    +c.element.val()){c.selectedItem=null;c.search(null,e)}},c.options.delay);break}}}).bind("keypress.autocomplete",function(e){if(g){g=false;e.preventDefault()}}).bind("focus.autocomplete",function(){if(!c.options.disabled){c.selectedItem=null;c.previous=c.element.val()}}).bind("blur.autocomplete",function(e){if(!c.options.disabled){clearTimeout(c.searching);c.closing=setTimeout(function(){c.close(e);c._change(e)},150)}});this._initSource();this.response=function(){return c._response.apply(c,arguments)};
    +this.menu=a("<ul></ul>").addClass("ui-autocomplete").appendTo(a(this.options.appendTo||"body",f)[0]).mousedown(function(e){var i=c.menu.element[0];a(e.target).closest(".ui-menu-item").length||setTimeout(function(){a(document).one("mousedown",function(b){b.target!==c.element[0]&&b.target!==i&&!a.ui.contains(i,b.target)&&c.close()})},1);setTimeout(function(){clearTimeout(c.closing)},13)}).menu({focus:function(e,i){i=i.item.data("item.autocomplete");false!==c._trigger("focus",e,{item:i})&&/^key/.test(e.originalEvent.type)&&
    +c.element.val(i.value)},selected:function(e,i){var b=i.item.data("item.autocomplete"),h=c.previous;if(c.element[0]!==f.activeElement){c.element.focus();c.previous=h;setTimeout(function(){c.previous=h;c.selectedItem=b},1)}false!==c._trigger("select",e,{item:b})&&c.element.val(b.value);c.term=c.element.val();c.close(e);c.selectedItem=b},blur:function(){c.menu.element.is(":visible")&&c.element.val()!==c.term&&c.element.val(c.term)}}).zIndex(this.element.zIndex()+1).css({top:0,left:0}).hide().data("menu");
    +a.fn.bgiframe&&this.menu.element.bgiframe()},destroy:function(){this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete").removeAttr("role").removeAttr("aria-autocomplete").removeAttr("aria-haspopup");this.menu.element.remove();a.Widget.prototype.destroy.call(this)},_setOption:function(c,f){a.Widget.prototype._setOption.apply(this,arguments);c==="source"&&this._initSource();if(c==="appendTo")this.menu.element.appendTo(a(f||"body",this.element[0].ownerDocument)[0]);c==="disabled"&&
    +f&&this.xhr&&this.xhr.abort()},_initSource:function(){var c=this,f,g;if(a.isArray(this.options.source)){f=this.options.source;this.source=function(e,i){i(a.ui.autocomplete.filter(f,e.term))}}else if(typeof this.options.source==="string"){g=this.options.source;this.source=function(e,i){c.xhr&&c.xhr.abort();c.xhr=a.ajax({url:g,data:e,dataType:"json",autocompleteRequest:++d,success:function(b){this.autocompleteRequest===d&&i(b)},error:function(){this.autocompleteRequest===d&&i([])}})}}else this.source=
    +this.options.source},search:function(c,f){c=c!=null?c:this.element.val();this.term=this.element.val();if(c.length<this.options.minLength)return this.close(f);clearTimeout(this.closing);if(this._trigger("search",f)!==false)return this._search(c)},_search:function(c){this.pending++;this.element.addClass("ui-autocomplete-loading");this.source({term:c},this.response)},_response:function(c){if(!this.options.disabled&&c&&c.length){c=this._normalize(c);this._suggest(c);this._trigger("open")}else this.close();
    +this.pending--;this.pending||this.element.removeClass("ui-autocomplete-loading")},close:function(c){clearTimeout(this.closing);if(this.menu.element.is(":visible")){this.menu.element.hide();this.menu.deactivate();this._trigger("close",c)}},_change:function(c){this.previous!==this.element.val()&&this._trigger("change",c,{item:this.selectedItem})},_normalize:function(c){if(c.length&&c[0].label&&c[0].value)return c;return a.map(c,function(f){if(typeof f==="string")return{label:f,value:f};return a.extend({label:f.label||
    +f.value,value:f.value||f.label},f)})},_suggest:function(c){var f=this.menu.element.empty().zIndex(this.element.zIndex()+1);this._renderMenu(f,c);this.menu.deactivate();this.menu.refresh();f.show();this._resizeMenu();f.position(a.extend({of:this.element},this.options.position));this.options.autoFocus&&this.menu.next(new a.Event("mouseover"))},_resizeMenu:function(){var c=this.menu.element;c.outerWidth(Math.max(c.width("").outerWidth(),this.element.outerWidth()))},_renderMenu:function(c,f){var g=this;
    +a.each(f,function(e,i){g._renderItem(c,i)})},_renderItem:function(c,f){return a("<li></li>").data("item.autocomplete",f).append(a("<a></a>").text(f.label)).appendTo(c)},_move:function(c,f){if(this.menu.element.is(":visible"))if(this.menu.first()&&/^previous/.test(c)||this.menu.last()&&/^next/.test(c)){this.element.val(this.term);this.menu.deactivate()}else this.menu[c](f);else this.search(null,f)},widget:function(){return this.menu.element}});a.extend(a.ui.autocomplete,{escapeRegex:function(c){return c.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,
    +"\\$&")},filter:function(c,f){var g=new RegExp(a.ui.autocomplete.escapeRegex(f),"i");return a.grep(c,function(e){return g.test(e.label||e.value||e)})}})})(jQuery);
    +(function(a){a.widget("ui.menu",{_create:function(){var d=this;this.element.addClass("ui-menu ui-widget ui-widget-content ui-corner-all").attr({role:"listbox","aria-activedescendant":"ui-active-menuitem"}).click(function(c){if(a(c.target).closest(".ui-menu-item a").length){c.preventDefault();d.select(c)}});this.refresh()},refresh:function(){var d=this;this.element.children("li:not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","menuitem").children("a").addClass("ui-corner-all").attr("tabindex",
    +-1).mouseenter(function(c){d.activate(c,a(this).parent())}).mouseleave(function(){d.deactivate()})},activate:function(d,c){this.deactivate();if(this.hasScroll()){var f=c.offset().top-this.element.offset().top,g=this.element.scrollTop(),e=this.element.height();if(f<0)this.element.scrollTop(g+f);else f>=e&&this.element.scrollTop(g+f-e+c.height())}this.active=c.eq(0).children("a").addClass("ui-state-hover").attr("id","ui-active-menuitem").end();this._trigger("focus",d,{item:c})},deactivate:function(){if(this.active){this.active.children("a").removeClass("ui-state-hover").removeAttr("id");
    +this._trigger("blur");this.active=null}},next:function(d){this.move("next",".ui-menu-item:first",d)},previous:function(d){this.move("prev",".ui-menu-item:last",d)},first:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},last:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},move:function(d,c,f){if(this.active){d=this.active[d+"All"](".ui-menu-item").eq(0);d.length?this.activate(f,d):this.activate(f,this.element.children(c))}else this.activate(f,
    +this.element.children(c))},nextPage:function(d){if(this.hasScroll())if(!this.active||this.last())this.activate(d,this.element.children(".ui-menu-item:first"));else{var c=this.active.offset().top,f=this.element.height(),g=this.element.children(".ui-menu-item").filter(function(){var e=a(this).offset().top-c-f+a(this).height();return e<10&&e>-10});g.length||(g=this.element.children(".ui-menu-item:last"));this.activate(d,g)}else this.activate(d,this.element.children(".ui-menu-item").filter(!this.active||
    +this.last()?":first":":last"))},previousPage:function(d){if(this.hasScroll())if(!this.active||this.first())this.activate(d,this.element.children(".ui-menu-item:last"));else{var c=this.active.offset().top,f=this.element.height();result=this.element.children(".ui-menu-item").filter(function(){var g=a(this).offset().top-c+f-a(this).height();return g<10&&g>-10});result.length||(result=this.element.children(".ui-menu-item:first"));this.activate(d,result)}else this.activate(d,this.element.children(".ui-menu-item").filter(!this.active||
    +this.first()?":last":":first"))},hasScroll:function(){return this.element.height()<this.element[a.fn.prop?"prop":"attr"]("scrollHeight")},select:function(d){this._trigger("selected",d,{item:this.active})}})})(jQuery);
    +(function(a){var d,c=function(g){a(":ui-button",g.target.form).each(function(){var e=a(this).data("button");setTimeout(function(){e.refresh()},1)})},f=function(g){var e=g.name,i=g.form,b=a([]);if(e)b=i?a(i).find("[name='"+e+"']"):a("[name='"+e+"']",g.ownerDocument).filter(function(){return!this.form});return b};a.widget("ui.button",{options:{disabled:null,text:true,label:null,icons:{primary:null,secondary:null}},_create:function(){this.element.closest("form").unbind("reset.button").bind("reset.button",
    +c);if(typeof this.options.disabled!=="boolean")this.options.disabled=this.element.attr("disabled");this._determineButtonType();this.hasTitle=!!this.buttonElement.attr("title");var g=this,e=this.options,i=this.type==="checkbox"||this.type==="radio",b="ui-state-hover"+(!i?" ui-state-active":"");if(e.label===null)e.label=this.buttonElement.html();if(this.element.is(":disabled"))e.disabled=true;this.buttonElement.addClass("ui-button ui-widget ui-state-default ui-corner-all").attr("role","button").bind("mouseenter.button",
    +function(){if(!e.disabled){a(this).addClass("ui-state-hover");this===d&&a(this).addClass("ui-state-active")}}).bind("mouseleave.button",function(){e.disabled||a(this).removeClass(b)}).bind("focus.button",function(){a(this).addClass("ui-state-focus")}).bind("blur.button",function(){a(this).removeClass("ui-state-focus")}).bind("click.button",function(h){e.disabled&&h.stopImmediatePropagation()});i&&this.element.bind("change.button",function(){g.refresh()});if(this.type==="checkbox")this.buttonElement.bind("click.button",
    +function(){if(e.disabled)return false;a(this).toggleClass("ui-state-active");g.buttonElement.attr("aria-pressed",g.element[0].checked)});else if(this.type==="radio")this.buttonElement.bind("click.button",function(){if(e.disabled)return false;a(this).addClass("ui-state-active");g.buttonElement.attr("aria-pressed",true);var h=g.element[0];f(h).not(h).map(function(){return a(this).button("widget")[0]}).removeClass("ui-state-active").attr("aria-pressed",false)});else{this.buttonElement.bind("mousedown.button",
    +function(){if(e.disabled)return false;a(this).addClass("ui-state-active");d=this;a(document).one("mouseup",function(){d=null})}).bind("mouseup.button",function(){if(e.disabled)return false;a(this).removeClass("ui-state-active")}).bind("keydown.button",function(h){if(e.disabled)return false;if(h.keyCode==a.ui.keyCode.SPACE||h.keyCode==a.ui.keyCode.ENTER)a(this).addClass("ui-state-active")}).bind("keyup.button",function(){a(this).removeClass("ui-state-active")});this.buttonElement.is("a")&&this.buttonElement.keyup(function(h){h.keyCode===
    +a.ui.keyCode.SPACE&&a(this).click()})}this._setOption("disabled",e.disabled)},_determineButtonType:function(){this.type=this.element.is(":checkbox")?"checkbox":this.element.is(":radio")?"radio":this.element.is("input")?"input":"button";if(this.type==="checkbox"||this.type==="radio"){var g=this.element.parents().filter(":last"),e="label[for="+this.element.attr("id")+"]";this.buttonElement=g.find(e);if(!this.buttonElement.length){g=g.length?g.siblings():this.element.siblings();this.buttonElement=g.filter(e);
    +if(!this.buttonElement.length)this.buttonElement=g.find(e)}this.element.addClass("ui-helper-hidden-accessible");(g=this.element.is(":checked"))&&this.buttonElement.addClass("ui-state-active");this.buttonElement.attr("aria-pressed",g)}else this.buttonElement=this.element},widget:function(){return this.buttonElement},destroy:function(){this.element.removeClass("ui-helper-hidden-accessible");this.buttonElement.removeClass("ui-button ui-widget ui-state-default ui-corner-all ui-state-hover ui-state-active  ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only").removeAttr("role").removeAttr("aria-pressed").html(this.buttonElement.find(".ui-button-text").html());
    +this.hasTitle||this.buttonElement.removeAttr("title");a.Widget.prototype.destroy.call(this)},_setOption:function(g,e){a.Widget.prototype._setOption.apply(this,arguments);if(g==="disabled")e?this.element.attr("disabled",true):this.element.removeAttr("disabled");this._resetButton()},refresh:function(){var g=this.element.is(":disabled");g!==this.options.disabled&&this._setOption("disabled",g);if(this.type==="radio")f(this.element[0]).each(function(){a(this).is(":checked")?a(this).button("widget").addClass("ui-state-active").attr("aria-pressed",
    +true):a(this).button("widget").removeClass("ui-state-active").attr("aria-pressed",false)});else if(this.type==="checkbox")this.element.is(":checked")?this.buttonElement.addClass("ui-state-active").attr("aria-pressed",true):this.buttonElement.removeClass("ui-state-active").attr("aria-pressed",false)},_resetButton:function(){if(this.type==="input")this.options.label&&this.element.val(this.options.label);else{var g=this.buttonElement.removeClass("ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only"),
    +e=a("<span></span>").addClass("ui-button-text").html(this.options.label).appendTo(g.empty()).text(),i=this.options.icons,b=i.primary&&i.secondary,h=[];if(i.primary||i.secondary){if(this.options.text)h.push("ui-button-text-icon"+(b?"s":i.primary?"-primary":"-secondary"));i.primary&&g.prepend("<span class='ui-button-icon-primary ui-icon "+i.primary+"'></span>");i.secondary&&g.append("<span class='ui-button-icon-secondary ui-icon "+i.secondary+"'></span>");if(!this.options.text){h.push(b?"ui-button-icons-only":
    +"ui-button-icon-only");this.hasTitle||g.attr("title",e)}}else h.push("ui-button-text-only");g.addClass(h.join(" "))}}});a.widget("ui.buttonset",{options:{items:":button, :submit, :reset, :checkbox, :radio, a, :data(button)"},_create:function(){this.element.addClass("ui-buttonset")},_init:function(){this.refresh()},_setOption:function(g,e){g==="disabled"&&this.buttons.button("option",g,e);a.Widget.prototype._setOption.apply(this,arguments)},refresh:function(){this.buttons=this.element.find(this.options.items).filter(":ui-button").button("refresh").end().not(":ui-button").button().end().map(function(){return a(this).button("widget")[0]}).removeClass("ui-corner-all ui-corner-left ui-corner-right").filter(":first").addClass("ui-corner-left").end().filter(":last").addClass("ui-corner-right").end().end()},
    +destroy:function(){this.element.removeClass("ui-buttonset");this.buttons.map(function(){return a(this).button("widget")[0]}).removeClass("ui-corner-left ui-corner-right").end().button("destroy");a.Widget.prototype.destroy.call(this)}})})(jQuery);
    +(function(a,d){function c(){this.debug=false;this._curInst=null;this._keyEvent=false;this._disabledInputs=[];this._inDialog=this._datepickerShowing=false;this._mainDivId="ui-datepicker-div";this._inlineClass="ui-datepicker-inline";this._appendClass="ui-datepicker-append";this._triggerClass="ui-datepicker-trigger";this._dialogClass="ui-datepicker-dialog";this._disableClass="ui-datepicker-disabled";this._unselectableClass="ui-datepicker-unselectable";this._currentClass="ui-datepicker-current-day";this._dayOverClass=
    +"ui-datepicker-days-cell-over";this.regional=[];this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su",
    +"Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"mm/dd/yy",firstDay:0,isRTL:false,showMonthAfterYear:false,yearSuffix:""};this._defaults={showOn:"focus",showAnim:"fadeIn",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:false,hideIfNoPrevNext:false,navigationAsDateFormat:false,gotoCurrent:false,changeMonth:false,changeYear:false,yearRange:"c-10:c+10",showOtherMonths:false,selectOtherMonths:false,showWeek:false,calculateWeek:this.iso8601Week,shortYearCutoff:"+10",
    +minDate:null,maxDate:null,duration:"fast",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:true,showButtonPanel:false,autoSize:false};a.extend(this._defaults,this.regional[""]);this.dpDiv=f(a('<div id="'+this._mainDivId+'" class="ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>'))}function f(b){return b.delegate("button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a",
    +"mouseout",function(){a(this).removeClass("ui-state-hover");this.className.indexOf("ui-datepicker-prev")!=-1&&a(this).removeClass("ui-datepicker-prev-hover");this.className.indexOf("ui-datepicker-next")!=-1&&a(this).removeClass("ui-datepicker-next-hover")}).delegate("button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a","mouseover",function(){if(!a.datepicker._isDisabledDatepicker(i.inline?b.parent()[0]:i.input[0])){a(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover");
    +a(this).addClass("ui-state-hover");this.className.indexOf("ui-datepicker-prev")!=-1&&a(this).addClass("ui-datepicker-prev-hover");this.className.indexOf("ui-datepicker-next")!=-1&&a(this).addClass("ui-datepicker-next-hover")}})}function g(b,h){a.extend(b,h);for(var j in h)if(h[j]==null||h[j]==d)b[j]=h[j];return b}a.extend(a.ui,{datepicker:{version:"1.8.13"}});var e=(new Date).getTime(),i;a.extend(c.prototype,{markerClassName:"hasDatepicker",log:function(){this.debug&&console.log.apply("",arguments)},
    +_widgetDatepicker:function(){return this.dpDiv},setDefaults:function(b){g(this._defaults,b||{});return this},_attachDatepicker:function(b,h){var j=null;for(var l in this._defaults){var o=b.getAttribute("date:"+l);if(o){j=j||{};try{j[l]=eval(o)}catch(n){j[l]=o}}}l=b.nodeName.toLowerCase();o=l=="div"||l=="span";if(!b.id){this.uuid+=1;b.id="dp"+this.uuid}var k=this._newInst(a(b),o);k.settings=a.extend({},h||{},j||{});if(l=="input")this._connectDatepicker(b,k);else o&&this._inlineDatepicker(b,k)},_newInst:function(b,
    +h){return{id:b[0].id.replace(/([^A-Za-z0-9_-])/g,"\\\\$1"),input:b,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:h,dpDiv:!h?this.dpDiv:f(a('<div class="'+this._inlineClass+' ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>'))}},_connectDatepicker:function(b,h){var j=a(b);h.append=a([]);h.trigger=a([]);if(!j.hasClass(this.markerClassName)){this._attachments(j,h);j.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).keyup(this._doKeyUp).bind("setData.datepicker",
    +function(l,o,n){h.settings[o]=n}).bind("getData.datepicker",function(l,o){return this._get(h,o)});this._autoSize(h);a.data(b,"datepicker",h)}},_attachments:function(b,h){var j=this._get(h,"appendText"),l=this._get(h,"isRTL");h.append&&h.append.remove();if(j){h.append=a('<span class="'+this._appendClass+'">'+j+"</span>");b[l?"before":"after"](h.append)}b.unbind("focus",this._showDatepicker);h.trigger&&h.trigger.remove();j=this._get(h,"showOn");if(j=="focus"||j=="both")b.focus(this._showDatepicker);
    +if(j=="button"||j=="both"){j=this._get(h,"buttonText");var o=this._get(h,"buttonImage");h.trigger=a(this._get(h,"buttonImageOnly")?a("<img/>").addClass(this._triggerClass).attr({src:o,alt:j,title:j}):a('<button type="button"></button>').addClass(this._triggerClass).html(o==""?j:a("<img/>").attr({src:o,alt:j,title:j})));b[l?"before":"after"](h.trigger);h.trigger.click(function(){a.datepicker._datepickerShowing&&a.datepicker._lastInput==b[0]?a.datepicker._hideDatepicker():a.datepicker._showDatepicker(b[0]);
    +return false})}},_autoSize:function(b){if(this._get(b,"autoSize")&&!b.inline){var h=new Date(2009,11,20),j=this._get(b,"dateFormat");if(j.match(/[DM]/)){var l=function(o){for(var n=0,k=0,m=0;m<o.length;m++)if(o[m].length>n){n=o[m].length;k=m}return k};h.setMonth(l(this._get(b,j.match(/MM/)?"monthNames":"monthNamesShort")));h.setDate(l(this._get(b,j.match(/DD/)?"dayNames":"dayNamesShort"))+20-h.getDay())}b.input.attr("size",this._formatDate(b,h).length)}},_inlineDatepicker:function(b,h){var j=a(b);
    +if(!j.hasClass(this.markerClassName)){j.addClass(this.markerClassName).append(h.dpDiv).bind("setData.datepicker",function(l,o,n){h.settings[o]=n}).bind("getData.datepicker",function(l,o){return this._get(h,o)});a.data(b,"datepicker",h);this._setDate(h,this._getDefaultDate(h),true);this._updateDatepicker(h);this._updateAlternate(h);h.dpDiv.show()}},_dialogDatepicker:function(b,h,j,l,o){b=this._dialogInst;if(!b){this.uuid+=1;this._dialogInput=a('<input type="text" id="'+("dp"+this.uuid)+'" style="position: absolute; top: -100px; width: 0px; z-index: -10;"/>');
    +this._dialogInput.keydown(this._doKeyDown);a("body").append(this._dialogInput);b=this._dialogInst=this._newInst(this._dialogInput,false);b.settings={};a.data(this._dialogInput[0],"datepicker",b)}g(b.settings,l||{});h=h&&h.constructor==Date?this._formatDate(b,h):h;this._dialogInput.val(h);this._pos=o?o.length?o:[o.pageX,o.pageY]:null;if(!this._pos)this._pos=[document.documentElement.clientWidth/2-100+(document.documentElement.scrollLeft||document.body.scrollLeft),document.documentElement.clientHeight/
    +2-150+(document.documentElement.scrollTop||document.body.scrollTop)];this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px");b.settings.onSelect=j;this._inDialog=true;this.dpDiv.addClass(this._dialogClass);this._showDatepicker(this._dialogInput[0]);a.blockUI&&a.blockUI(this.dpDiv);a.data(this._dialogInput[0],"datepicker",b);return this},_destroyDatepicker:function(b){var h=a(b),j=a.data(b,"datepicker");if(h.hasClass(this.markerClassName)){var l=b.nodeName.toLowerCase();a.removeData(b,
    +"datepicker");if(l=="input"){j.append.remove();j.trigger.remove();h.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup",this._doKeyUp)}else if(l=="div"||l=="span")h.removeClass(this.markerClassName).empty()}},_enableDatepicker:function(b){var h=a(b),j=a.data(b,"datepicker");if(h.hasClass(this.markerClassName)){var l=b.nodeName.toLowerCase();if(l=="input"){b.disabled=false;j.trigger.filter("button").each(function(){this.disabled=
    +false}).end().filter("img").css({opacity:"1.0",cursor:""})}else if(l=="div"||l=="span"){h=h.children("."+this._inlineClass);h.children().removeClass("ui-state-disabled");h.find("select.ui-datepicker-month, select.ui-datepicker-year").removeAttr("disabled")}this._disabledInputs=a.map(this._disabledInputs,function(o){return o==b?null:o})}},_disableDatepicker:function(b){var h=a(b),j=a.data(b,"datepicker");if(h.hasClass(this.markerClassName)){var l=b.nodeName.toLowerCase();if(l=="input"){b.disabled=
    +true;j.trigger.filter("button").each(function(){this.disabled=true}).end().filter("img").css({opacity:"0.5",cursor:"default"})}else if(l=="div"||l=="span"){h=h.children("."+this._inlineClass);h.children().addClass("ui-state-disabled");h.find("select.ui-datepicker-month, select.ui-datepicker-year").attr("disabled","disabled")}this._disabledInputs=a.map(this._disabledInputs,function(o){return o==b?null:o});this._disabledInputs[this._disabledInputs.length]=b}},_isDisabledDatepicker:function(b){if(!b)return false;
    +for(var h=0;h<this._disabledInputs.length;h++)if(this._disabledInputs[h]==b)return true;return false},_getInst:function(b){try{return a.data(b,"datepicker")}catch(h){throw"Missing instance data for this datepicker";}},_optionDatepicker:function(b,h,j){var l=this._getInst(b);if(arguments.length==2&&typeof h=="string")return h=="defaults"?a.extend({},a.datepicker._defaults):l?h=="all"?a.extend({},l.settings):this._get(l,h):null;var o=h||{};if(typeof h=="string"){o={};o[h]=j}if(l){this._curInst==l&&
    +this._hideDatepicker();var n=this._getDateDatepicker(b,true),k=this._getMinMaxDate(l,"min"),m=this._getMinMaxDate(l,"max");g(l.settings,o);if(k!==null&&o.dateFormat!==d&&o.minDate===d)l.settings.minDate=this._formatDate(l,k);if(m!==null&&o.dateFormat!==d&&o.maxDate===d)l.settings.maxDate=this._formatDate(l,m);this._attachments(a(b),l);this._autoSize(l);this._setDate(l,n);this._updateAlternate(l);this._updateDatepicker(l)}},_changeDatepicker:function(b,h,j){this._optionDatepicker(b,h,j)},_refreshDatepicker:function(b){(b=
    +this._getInst(b))&&this._updateDatepicker(b)},_setDateDatepicker:function(b,h){if(b=this._getInst(b)){this._setDate(b,h);this._updateDatepicker(b);this._updateAlternate(b)}},_getDateDatepicker:function(b,h){(b=this._getInst(b))&&!b.inline&&this._setDateFromField(b,h);return b?this._getDate(b):null},_doKeyDown:function(b){var h=a.datepicker._getInst(b.target),j=true,l=h.dpDiv.is(".ui-datepicker-rtl");h._keyEvent=true;if(a.datepicker._datepickerShowing)switch(b.keyCode){case 9:a.datepicker._hideDatepicker();
    +j=false;break;case 13:j=a("td."+a.datepicker._dayOverClass+":not(."+a.datepicker._currentClass+")",h.dpDiv);j[0]?a.datepicker._selectDay(b.target,h.selectedMonth,h.selectedYear,j[0]):a.datepicker._hideDatepicker();return false;case 27:a.datepicker._hideDatepicker();break;case 33:a.datepicker._adjustDate(b.target,b.ctrlKey?-a.datepicker._get(h,"stepBigMonths"):-a.datepicker._get(h,"stepMonths"),"M");break;case 34:a.datepicker._adjustDate(b.target,b.ctrlKey?+a.datepicker._get(h,"stepBigMonths"):+a.datepicker._get(h,
    +"stepMonths"),"M");break;case 35:if(b.ctrlKey||b.metaKey)a.datepicker._clearDate(b.target);j=b.ctrlKey||b.metaKey;break;case 36:if(b.ctrlKey||b.metaKey)a.datepicker._gotoToday(b.target);j=b.ctrlKey||b.metaKey;break;case 37:if(b.ctrlKey||b.metaKey)a.datepicker._adjustDate(b.target,l?+1:-1,"D");j=b.ctrlKey||b.metaKey;if(b.originalEvent.altKey)a.datepicker._adjustDate(b.target,b.ctrlKey?-a.datepicker._get(h,"stepBigMonths"):-a.datepicker._get(h,"stepMonths"),"M");break;case 38:if(b.ctrlKey||b.metaKey)a.datepicker._adjustDate(b.target,
    +-7,"D");j=b.ctrlKey||b.metaKey;break;case 39:if(b.ctrlKey||b.metaKey)a.datepicker._adjustDate(b.target,l?-1:+1,"D");j=b.ctrlKey||b.metaKey;if(b.originalEvent.altKey)a.datepicker._adjustDate(b.target,b.ctrlKey?+a.datepicker._get(h,"stepBigMonths"):+a.datepicker._get(h,"stepMonths"),"M");break;case 40:if(b.ctrlKey||b.metaKey)a.datepicker._adjustDate(b.target,+7,"D");j=b.ctrlKey||b.metaKey;break;default:j=false}else if(b.keyCode==36&&b.ctrlKey)a.datepicker._showDatepicker(this);else j=false;if(j){b.preventDefault();
    +b.stopPropagation()}},_doKeyPress:function(b){var h=a.datepicker._getInst(b.target);if(a.datepicker._get(h,"constrainInput")){h=a.datepicker._possibleChars(a.datepicker._get(h,"dateFormat"));var j=String.fromCharCode(b.charCode==d?b.keyCode:b.charCode);return b.ctrlKey||b.metaKey||j<" "||!h||h.indexOf(j)>-1}},_doKeyUp:function(b){b=a.datepicker._getInst(b.target);if(b.input.val()!=b.lastVal)try{if(a.datepicker.parseDate(a.datepicker._get(b,"dateFormat"),b.input?b.input.val():null,a.datepicker._getFormatConfig(b))){a.datepicker._setDateFromField(b);
    +a.datepicker._updateAlternate(b);a.datepicker._updateDatepicker(b)}}catch(h){a.datepicker.log(h)}return true},_showDatepicker:function(b){b=b.target||b;if(b.nodeName.toLowerCase()!="input")b=a("input",b.parentNode)[0];if(!(a.datepicker._isDisabledDatepicker(b)||a.datepicker._lastInput==b)){var h=a.datepicker._getInst(b);a.datepicker._curInst&&a.datepicker._curInst!=h&&a.datepicker._curInst.dpDiv.stop(true,true);var j=a.datepicker._get(h,"beforeShow");g(h.settings,j?j.apply(b,[b,h]):{});h.lastVal=
    +null;a.datepicker._lastInput=b;a.datepicker._setDateFromField(h);if(a.datepicker._inDialog)b.value="";if(!a.datepicker._pos){a.datepicker._pos=a.datepicker._findPos(b);a.datepicker._pos[1]+=b.offsetHeight}var l=false;a(b).parents().each(function(){l|=a(this).css("position")=="fixed";return!l});if(l&&a.browser.opera){a.datepicker._pos[0]-=document.documentElement.scrollLeft;a.datepicker._pos[1]-=document.documentElement.scrollTop}j={left:a.datepicker._pos[0],top:a.datepicker._pos[1]};a.datepicker._pos=
    +null;h.dpDiv.empty();h.dpDiv.css({position:"absolute",display:"block",top:"-1000px"});a.datepicker._updateDatepicker(h);j=a.datepicker._checkOffset(h,j,l);h.dpDiv.css({position:a.datepicker._inDialog&&a.blockUI?"static":l?"fixed":"absolute",display:"none",left:j.left+"px",top:j.top+"px"});if(!h.inline){j=a.datepicker._get(h,"showAnim");var o=a.datepicker._get(h,"duration"),n=function(){var k=h.dpDiv.find("iframe.ui-datepicker-cover");if(k.length){var m=a.datepicker._getBorders(h.dpDiv);k.css({left:-m[0],
    +top:-m[1],width:h.dpDiv.outerWidth(),height:h.dpDiv.outerHeight()})}};h.dpDiv.zIndex(a(b).zIndex()+1);a.datepicker._datepickerShowing=true;a.effects&&a.effects[j]?h.dpDiv.show(j,a.datepicker._get(h,"showOptions"),o,n):h.dpDiv[j||"show"](j?o:null,n);if(!j||!o)n();h.input.is(":visible")&&!h.input.is(":disabled")&&h.input.focus();a.datepicker._curInst=h}}},_updateDatepicker:function(b){var h=a.datepicker._getBorders(b.dpDiv);i=b;b.dpDiv.empty().append(this._generateHTML(b));var j=b.dpDiv.find("iframe.ui-datepicker-cover");
    +j.length&&j.css({left:-h[0],top:-h[1],width:b.dpDiv.outerWidth(),height:b.dpDiv.outerHeight()});b.dpDiv.find("."+this._dayOverClass+" a").mouseover();h=this._getNumberOfMonths(b);j=h[1];b.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width("");j>1&&b.dpDiv.addClass("ui-datepicker-multi-"+j).css("width",17*j+"em");b.dpDiv[(h[0]!=1||h[1]!=1?"add":"remove")+"Class"]("ui-datepicker-multi");b.dpDiv[(this._get(b,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl");
    +b==a.datepicker._curInst&&a.datepicker._datepickerShowing&&b.input&&b.input.is(":visible")&&!b.input.is(":disabled")&&b.input[0]!=document.activeElement&&b.input.focus();if(b.yearshtml){var l=b.yearshtml;setTimeout(function(){l===b.yearshtml&&b.yearshtml&&b.dpDiv.find("select.ui-datepicker-year:first").replaceWith(b.yearshtml);l=b.yearshtml=null},0)}},_getBorders:function(b){var h=function(j){return{thin:1,medium:2,thick:3}[j]||j};return[parseFloat(h(b.css("border-left-width"))),parseFloat(h(b.css("border-top-width")))]},
    +_checkOffset:function(b,h,j){var l=b.dpDiv.outerWidth(),o=b.dpDiv.outerHeight(),n=b.input?b.input.outerWidth():0,k=b.input?b.input.outerHeight():0,m=document.documentElement.clientWidth+a(document).scrollLeft(),p=document.documentElement.clientHeight+a(document).scrollTop();h.left-=this._get(b,"isRTL")?l-n:0;h.left-=j&&h.left==b.input.offset().left?a(document).scrollLeft():0;h.top-=j&&h.top==b.input.offset().top+k?a(document).scrollTop():0;h.left-=Math.min(h.left,h.left+l>m&&m>l?Math.abs(h.left+l-
    +m):0);h.top-=Math.min(h.top,h.top+o>p&&p>o?Math.abs(o+k):0);return h},_findPos:function(b){for(var h=this._get(this._getInst(b),"isRTL");b&&(b.type=="hidden"||b.nodeType!=1||a.expr.filters.hidden(b));)b=b[h?"previousSibling":"nextSibling"];b=a(b).offset();return[b.left,b.top]},_hideDatepicker:function(b){var h=this._curInst;if(!(!h||b&&h!=a.data(b,"datepicker")))if(this._datepickerShowing){b=this._get(h,"showAnim");var j=this._get(h,"duration"),l=function(){a.datepicker._tidyDialog(h);this._curInst=
    +null};a.effects&&a.effects[b]?h.dpDiv.hide(b,a.datepicker._get(h,"showOptions"),j,l):h.dpDiv[b=="slideDown"?"slideUp":b=="fadeIn"?"fadeOut":"hide"](b?j:null,l);b||l();if(b=this._get(h,"onClose"))b.apply(h.input?h.input[0]:null,[h.input?h.input.val():"",h]);this._datepickerShowing=false;this._lastInput=null;if(this._inDialog){this._dialogInput.css({position:"absolute",left:"0",top:"-100px"});if(a.blockUI){a.unblockUI();a("body").append(this.dpDiv)}}this._inDialog=false}},_tidyDialog:function(b){b.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")},
    +_checkExternalClick:function(b){if(a.datepicker._curInst){b=a(b.target);b[0].id!=a.datepicker._mainDivId&&b.parents("#"+a.datepicker._mainDivId).length==0&&!b.hasClass(a.datepicker.markerClassName)&&!b.hasClass(a.datepicker._triggerClass)&&a.datepicker._datepickerShowing&&!(a.datepicker._inDialog&&a.blockUI)&&a.datepicker._hideDatepicker()}},_adjustDate:function(b,h,j){b=a(b);var l=this._getInst(b[0]);if(!this._isDisabledDatepicker(b[0])){this._adjustInstDate(l,h+(j=="M"?this._get(l,"showCurrentAtPos"):
    +0),j);this._updateDatepicker(l)}},_gotoToday:function(b){b=a(b);var h=this._getInst(b[0]);if(this._get(h,"gotoCurrent")&&h.currentDay){h.selectedDay=h.currentDay;h.drawMonth=h.selectedMonth=h.currentMonth;h.drawYear=h.selectedYear=h.currentYear}else{var j=new Date;h.selectedDay=j.getDate();h.drawMonth=h.selectedMonth=j.getMonth();h.drawYear=h.selectedYear=j.getFullYear()}this._notifyChange(h);this._adjustDate(b)},_selectMonthYear:function(b,h,j){b=a(b);var l=this._getInst(b[0]);l._selectingMonthYear=
    +false;l["selected"+(j=="M"?"Month":"Year")]=l["draw"+(j=="M"?"Month":"Year")]=parseInt(h.options[h.selectedIndex].value,10);this._notifyChange(l);this._adjustDate(b)},_clickMonthYear:function(b){var h=this._getInst(a(b)[0]);h.input&&h._selectingMonthYear&&setTimeout(function(){h.input.focus()},0);h._selectingMonthYear=!h._selectingMonthYear},_selectDay:function(b,h,j,l){var o=a(b);if(!(a(l).hasClass(this._unselectableClass)||this._isDisabledDatepicker(o[0]))){o=this._getInst(o[0]);o.selectedDay=o.currentDay=
    +a("a",l).html();o.selectedMonth=o.currentMonth=h;o.selectedYear=o.currentYear=j;this._selectDate(b,this._formatDate(o,o.currentDay,o.currentMonth,o.currentYear))}},_clearDate:function(b){b=a(b);this._getInst(b[0]);this._selectDate(b,"")},_selectDate:function(b,h){b=this._getInst(a(b)[0]);h=h!=null?h:this._formatDate(b);b.input&&b.input.val(h);this._updateAlternate(b);var j=this._get(b,"onSelect");if(j)j.apply(b.input?b.input[0]:null,[h,b]);else b.input&&b.input.trigger("change");if(b.inline)this._updateDatepicker(b);
    +else{this._hideDatepicker();this._lastInput=b.input[0];typeof b.input[0]!="object"&&b.input.focus();this._lastInput=null}},_updateAlternate:function(b){var h=this._get(b,"altField");if(h){var j=this._get(b,"altFormat")||this._get(b,"dateFormat"),l=this._getDate(b),o=this.formatDate(j,l,this._getFormatConfig(b));a(h).each(function(){a(this).val(o)})}},noWeekends:function(b){b=b.getDay();return[b>0&&b<6,""]},iso8601Week:function(b){b=new Date(b.getTime());b.setDate(b.getDate()+4-(b.getDay()||7));var h=
    +b.getTime();b.setMonth(0);b.setDate(1);return Math.floor(Math.round((h-b)/864E5)/7)+1},parseDate:function(b,h,j){if(b==null||h==null)throw"Invalid arguments";h=typeof h=="object"?h.toString():h+"";if(h=="")return null;var l=(j?j.shortYearCutoff:null)||this._defaults.shortYearCutoff;l=typeof l!="string"?l:(new Date).getFullYear()%100+parseInt(l,10);for(var o=(j?j.dayNamesShort:null)||this._defaults.dayNamesShort,n=(j?j.dayNames:null)||this._defaults.dayNames,k=(j?j.monthNamesShort:null)||this._defaults.monthNamesShort,
    +m=(j?j.monthNames:null)||this._defaults.monthNames,p=j=-1,q=-1,s=-1,r=false,u=function(y){(y=G+1<b.length&&b.charAt(G+1)==y)&&G++;return y},v=function(y){var H=u(y);y=new RegExp("^\\d{1,"+(y=="@"?14:y=="!"?20:y=="y"&&H?4:y=="o"?3:2)+"}");y=h.substring(z).match(y);if(!y)throw"Missing number at position "+z;z+=y[0].length;return parseInt(y[0],10)},w=function(y,H,N){y=a.map(u(y)?N:H,function(D,E){return[[E,D]]}).sort(function(D,E){return-(D[1].length-E[1].length)});var J=-1;a.each(y,function(D,E){D=
    +E[1];if(h.substr(z,D.length).toLowerCase()==D.toLowerCase()){J=E[0];z+=D.length;return false}});if(J!=-1)return J+1;else throw"Unknown name at position "+z;},x=function(){if(h.charAt(z)!=b.charAt(G))throw"Unexpected literal at position "+z;z++},z=0,G=0;G<b.length;G++)if(r)if(b.charAt(G)=="'"&&!u("'"))r=false;else x();else switch(b.charAt(G)){case "d":q=v("d");break;case "D":w("D",o,n);break;case "o":s=v("o");break;case "m":p=v("m");break;case "M":p=w("M",k,m);break;case "y":j=v("y");break;case "@":var C=
    +new Date(v("@"));j=C.getFullYear();p=C.getMonth()+1;q=C.getDate();break;case "!":C=new Date((v("!")-this._ticksTo1970)/1E4);j=C.getFullYear();p=C.getMonth()+1;q=C.getDate();break;case "'":if(u("'"))x();else r=true;break;default:x()}if(j==-1)j=(new Date).getFullYear();else if(j<100)j+=(new Date).getFullYear()-(new Date).getFullYear()%100+(j<=l?0:-100);if(s>-1){p=1;q=s;do{l=this._getDaysInMonth(j,p-1);if(q<=l)break;p++;q-=l}while(1)}C=this._daylightSavingAdjust(new Date(j,p-1,q));if(C.getFullYear()!=
    +j||C.getMonth()+1!=p||C.getDate()!=q)throw"Invalid date";return C},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925))*24*60*60*1E7,formatDate:function(b,h,j){if(!h)return"";var l=(j?j.dayNamesShort:null)||this._defaults.dayNamesShort,o=(j?j.dayNames:null)||this._defaults.dayNames,
    +n=(j?j.monthNamesShort:null)||this._defaults.monthNamesShort;j=(j?j.monthNames:null)||this._defaults.monthNames;var k=function(u){(u=r+1<b.length&&b.charAt(r+1)==u)&&r++;return u},m=function(u,v,w){v=""+v;if(k(u))for(;v.length<w;)v="0"+v;return v},p=function(u,v,w,x){return k(u)?x[v]:w[v]},q="",s=false;if(h)for(var r=0;r<b.length;r++)if(s)if(b.charAt(r)=="'"&&!k("'"))s=false;else q+=b.charAt(r);else switch(b.charAt(r)){case "d":q+=m("d",h.getDate(),2);break;case "D":q+=p("D",h.getDay(),l,o);break;
    +case "o":q+=m("o",(h.getTime()-(new Date(h.getFullYear(),0,0)).getTime())/864E5,3);break;case "m":q+=m("m",h.getMonth()+1,2);break;case "M":q+=p("M",h.getMonth(),n,j);break;case "y":q+=k("y")?h.getFullYear():(h.getYear()%100<10?"0":"")+h.getYear()%100;break;case "@":q+=h.getTime();break;case "!":q+=h.getTime()*1E4+this._ticksTo1970;break;case "'":if(k("'"))q+="'";else s=true;break;default:q+=b.charAt(r)}return q},_possibleChars:function(b){for(var h="",j=false,l=function(n){(n=o+1<b.length&&b.charAt(o+
    +1)==n)&&o++;return n},o=0;o<b.length;o++)if(j)if(b.charAt(o)=="'"&&!l("'"))j=false;else h+=b.charAt(o);else switch(b.charAt(o)){case "d":case "m":case "y":case "@":h+="0123456789";break;case "D":case "M":return null;case "'":if(l("'"))h+="'";else j=true;break;default:h+=b.charAt(o)}return h},_get:function(b,h){return b.settings[h]!==d?b.settings[h]:this._defaults[h]},_setDateFromField:function(b,h){if(b.input.val()!=b.lastVal){var j=this._get(b,"dateFormat"),l=b.lastVal=b.input?b.input.val():null,
    +o,n;o=n=this._getDefaultDate(b);var k=this._getFormatConfig(b);try{o=this.parseDate(j,l,k)||n}catch(m){this.log(m);l=h?"":l}b.selectedDay=o.getDate();b.drawMonth=b.selectedMonth=o.getMonth();b.drawYear=b.selectedYear=o.getFullYear();b.currentDay=l?o.getDate():0;b.currentMonth=l?o.getMonth():0;b.currentYear=l?o.getFullYear():0;this._adjustInstDate(b)}},_getDefaultDate:function(b){return this._restrictMinMax(b,this._determineDate(b,this._get(b,"defaultDate"),new Date))},_determineDate:function(b,h,
    +j){var l=function(n){var k=new Date;k.setDate(k.getDate()+n);return k},o=function(n){try{return a.datepicker.parseDate(a.datepicker._get(b,"dateFormat"),n,a.datepicker._getFormatConfig(b))}catch(k){}var m=(n.toLowerCase().match(/^c/)?a.datepicker._getDate(b):null)||new Date,p=m.getFullYear(),q=m.getMonth();m=m.getDate();for(var s=/([+-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g,r=s.exec(n);r;){switch(r[2]||"d"){case "d":case "D":m+=parseInt(r[1],10);break;case "w":case "W":m+=parseInt(r[1],10)*7;break;case "m":case "M":q+=
    +parseInt(r[1],10);m=Math.min(m,a.datepicker._getDaysInMonth(p,q));break;case "y":case "Y":p+=parseInt(r[1],10);m=Math.min(m,a.datepicker._getDaysInMonth(p,q));break}r=s.exec(n)}return new Date(p,q,m)};if(h=(h=h==null||h===""?j:typeof h=="string"?o(h):typeof h=="number"?isNaN(h)?j:l(h):new Date(h.getTime()))&&h.toString()=="Invalid Date"?j:h){h.setHours(0);h.setMinutes(0);h.setSeconds(0);h.setMilliseconds(0)}return this._daylightSavingAdjust(h)},_daylightSavingAdjust:function(b){if(!b)return null;
    +b.setHours(b.getHours()>12?b.getHours()+2:0);return b},_setDate:function(b,h,j){var l=!h,o=b.selectedMonth,n=b.selectedYear;h=this._restrictMinMax(b,this._determineDate(b,h,new Date));b.selectedDay=b.currentDay=h.getDate();b.drawMonth=b.selectedMonth=b.currentMonth=h.getMonth();b.drawYear=b.selectedYear=b.currentYear=h.getFullYear();if((o!=b.selectedMonth||n!=b.selectedYear)&&!j)this._notifyChange(b);this._adjustInstDate(b);if(b.input)b.input.val(l?"":this._formatDate(b))},_getDate:function(b){return!b.currentYear||
    +b.input&&b.input.val()==""?null:this._daylightSavingAdjust(new Date(b.currentYear,b.currentMonth,b.currentDay))},_generateHTML:function(b){var h=new Date;h=this._daylightSavingAdjust(new Date(h.getFullYear(),h.getMonth(),h.getDate()));var j=this._get(b,"isRTL"),l=this._get(b,"showButtonPanel"),o=this._get(b,"hideIfNoPrevNext"),n=this._get(b,"navigationAsDateFormat"),k=this._getNumberOfMonths(b),m=this._get(b,"showCurrentAtPos"),p=this._get(b,"stepMonths"),q=k[0]!=1||k[1]!=1,s=this._daylightSavingAdjust(!b.currentDay?
    +new Date(9999,9,9):new Date(b.currentYear,b.currentMonth,b.currentDay)),r=this._getMinMaxDate(b,"min"),u=this._getMinMaxDate(b,"max");m=b.drawMonth-m;var v=b.drawYear;if(m<0){m+=12;v--}if(u){var w=this._daylightSavingAdjust(new Date(u.getFullYear(),u.getMonth()-k[0]*k[1]+1,u.getDate()));for(w=r&&w<r?r:w;this._daylightSavingAdjust(new Date(v,m,1))>w;){m--;if(m<0){m=11;v--}}}b.drawMonth=m;b.drawYear=v;w=this._get(b,"prevText");w=!n?w:this.formatDate(w,this._daylightSavingAdjust(new Date(v,m-p,1)),this._getFormatConfig(b));
    +w=this._canAdjustMonth(b,-1,v,m)?'<a class="ui-datepicker-prev ui-corner-all" onclick="DP_jQuery_'+e+".datepicker._adjustDate('#"+b.id+"', -"+p+", 'M');\" title=\""+w+'"><span class="ui-icon ui-icon-circle-triangle-'+(j?"e":"w")+'">'+w+"</span></a>":o?"":'<a class="ui-datepicker-prev ui-corner-all ui-state-disabled" title="'+w+'"><span class="ui-icon ui-icon-circle-triangle-'+(j?"e":"w")+'">'+w+"</span></a>";var x=this._get(b,"nextText");x=!n?x:this.formatDate(x,this._daylightSavingAdjust(new Date(v,
    +m+p,1)),this._getFormatConfig(b));o=this._canAdjustMonth(b,+1,v,m)?'<a class="ui-datepicker-next ui-corner-all" onclick="DP_jQuery_'+e+".datepicker._adjustDate('#"+b.id+"', +"+p+", 'M');\" title=\""+x+'"><span class="ui-icon ui-icon-circle-triangle-'+(j?"w":"e")+'">'+x+"</span></a>":o?"":'<a class="ui-datepicker-next ui-corner-all ui-state-disabled" title="'+x+'"><span class="ui-icon ui-icon-circle-triangle-'+(j?"w":"e")+'">'+x+"</span></a>";p=this._get(b,"currentText");x=this._get(b,"gotoCurrent")&&
    +b.currentDay?s:h;p=!n?p:this.formatDate(p,x,this._getFormatConfig(b));n=!b.inline?'<button type="button" class="ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all" onclick="DP_jQuery_'+e+'.datepicker._hideDatepicker();">'+this._get(b,"closeText")+"</button>":"";l=l?'<div class="ui-datepicker-buttonpane ui-widget-content">'+(j?n:"")+(this._isInRange(b,x)?'<button type="button" class="ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all" onclick="DP_jQuery_'+
    +e+".datepicker._gotoToday('#"+b.id+"');\">"+p+"</button>":"")+(j?"":n)+"</div>":"";n=parseInt(this._get(b,"firstDay"),10);n=isNaN(n)?0:n;p=this._get(b,"showWeek");x=this._get(b,"dayNames");this._get(b,"dayNamesShort");var z=this._get(b,"dayNamesMin"),G=this._get(b,"monthNames"),C=this._get(b,"monthNamesShort"),y=this._get(b,"beforeShowDay"),H=this._get(b,"showOtherMonths"),N=this._get(b,"selectOtherMonths");this._get(b,"calculateWeek");for(var J=this._getDefaultDate(b),D="",E=0;E<k[0];E++){for(var P=
    +"",L=0;L<k[1];L++){var Q=this._daylightSavingAdjust(new Date(v,m,b.selectedDay)),B=" ui-corner-all",F="";if(q){F+='<div class="ui-datepicker-group';if(k[1]>1)switch(L){case 0:F+=" ui-datepicker-group-first";B=" ui-corner-"+(j?"right":"left");break;case k[1]-1:F+=" ui-datepicker-group-last";B=" ui-corner-"+(j?"left":"right");break;default:F+=" ui-datepicker-group-middle";B="";break}F+='">'}F+='<div class="ui-datepicker-header ui-widget-header ui-helper-clearfix'+B+'">'+(/all|left/.test(B)&&E==0?j?
    +o:w:"")+(/all|right/.test(B)&&E==0?j?w:o:"")+this._generateMonthYearHeader(b,m,v,r,u,E>0||L>0,G,C)+'</div><table class="ui-datepicker-calendar"><thead><tr>';var I=p?'<th class="ui-datepicker-week-col">'+this._get(b,"weekHeader")+"</th>":"";for(B=0;B<7;B++){var A=(B+n)%7;I+="<th"+((B+n+6)%7>=5?' class="ui-datepicker-week-end"':"")+'><span title="'+x[A]+'">'+z[A]+"</span></th>"}F+=I+"</tr></thead><tbody>";I=this._getDaysInMonth(v,m);if(v==b.selectedYear&&m==b.selectedMonth)b.selectedDay=Math.min(b.selectedDay,
    +I);B=(this._getFirstDayOfMonth(v,m)-n+7)%7;I=q?6:Math.ceil((B+I)/7);A=this._daylightSavingAdjust(new Date(v,m,1-B));for(var R=0;R<I;R++){F+="<tr>";var S=!p?"":'<td class="ui-datepicker-week-col">'+this._get(b,"calculateWeek")(A)+"</td>";for(B=0;B<7;B++){var M=y?y.apply(b.input?b.input[0]:null,[A]):[true,""],K=A.getMonth()!=m,O=K&&!N||!M[0]||r&&A<r||u&&A>u;S+='<td class="'+((B+n+6)%7>=5?" ui-datepicker-week-end":"")+(K?" ui-datepicker-other-month":"")+(A.getTime()==Q.getTime()&&m==b.selectedMonth&&
    +b._keyEvent||J.getTime()==A.getTime()&&J.getTime()==Q.getTime()?" "+this._dayOverClass:"")+(O?" "+this._unselectableClass+" ui-state-disabled":"")+(K&&!H?"":" "+M[1]+(A.getTime()==s.getTime()?" "+this._currentClass:"")+(A.getTime()==h.getTime()?" ui-datepicker-today":""))+'"'+((!K||H)&&M[2]?' title="'+M[2]+'"':"")+(O?"":' onclick="DP_jQuery_'+e+".datepicker._selectDay('#"+b.id+"',"+A.getMonth()+","+A.getFullYear()+', this);return false;"')+">"+(K&&!H?"&#xa0;":O?'<span class="ui-state-default">'+A.getDate()+
    +"</span>":'<a class="ui-state-default'+(A.getTime()==h.getTime()?" ui-state-highlight":"")+(A.getTime()==s.getTime()?" ui-state-active":"")+(K?" ui-priority-secondary":"")+'" href="#">'+A.getDate()+"</a>")+"</td>";A.setDate(A.getDate()+1);A=this._daylightSavingAdjust(A)}F+=S+"</tr>"}m++;if(m>11){m=0;v++}F+="</tbody></table>"+(q?"</div>"+(k[0]>0&&L==k[1]-1?'<div class="ui-datepicker-row-break"></div>':""):"");P+=F}D+=P}D+=l+(a.browser.msie&&parseInt(a.browser.version,10)<7&&!b.inline?'<iframe src="javascript:false;" class="ui-datepicker-cover" frameborder="0"></iframe>':
    +"");b._keyEvent=false;return D},_generateMonthYearHeader:function(b,h,j,l,o,n,k,m){var p=this._get(b,"changeMonth"),q=this._get(b,"changeYear"),s=this._get(b,"showMonthAfterYear"),r='<div class="ui-datepicker-title">',u="";if(n||!p)u+='<span class="ui-datepicker-month">'+k[h]+"</span>";else{k=l&&l.getFullYear()==j;var v=o&&o.getFullYear()==j;u+='<select class="ui-datepicker-month" onchange="DP_jQuery_'+e+".datepicker._selectMonthYear('#"+b.id+"', this, 'M');\" onclick=\"DP_jQuery_"+e+".datepicker._clickMonthYear('#"+
    +b.id+"');\">";for(var w=0;w<12;w++)if((!k||w>=l.getMonth())&&(!v||w<=o.getMonth()))u+='<option value="'+w+'"'+(w==h?' selected="selected"':"")+">"+m[w]+"</option>";u+="</select>"}s||(r+=u+(n||!(p&&q)?"&#xa0;":""));if(!b.yearshtml){b.yearshtml="";if(n||!q)r+='<span class="ui-datepicker-year">'+j+"</span>";else{m=this._get(b,"yearRange").split(":");var x=(new Date).getFullYear();k=function(z){z=z.match(/c[+-].*/)?j+parseInt(z.substring(1),10):z.match(/[+-].*/)?x+parseInt(z,10):parseInt(z,10);return isNaN(z)?
    +x:z};h=k(m[0]);m=Math.max(h,k(m[1]||""));h=l?Math.max(h,l.getFullYear()):h;m=o?Math.min(m,o.getFullYear()):m;for(b.yearshtml+='<select class="ui-datepicker-year" onchange="DP_jQuery_'+e+".datepicker._selectMonthYear('#"+b.id+"', this, 'Y');\" onclick=\"DP_jQuery_"+e+".datepicker._clickMonthYear('#"+b.id+"');\">";h<=m;h++)b.yearshtml+='<option value="'+h+'"'+(h==j?' selected="selected"':"")+">"+h+"</option>";b.yearshtml+="</select>";r+=b.yearshtml;b.yearshtml=null}}r+=this._get(b,"yearSuffix");if(s)r+=
    +(n||!(p&&q)?"&#xa0;":"")+u;r+="</div>";return r},_adjustInstDate:function(b,h,j){var l=b.drawYear+(j=="Y"?h:0),o=b.drawMonth+(j=="M"?h:0);h=Math.min(b.selectedDay,this._getDaysInMonth(l,o))+(j=="D"?h:0);l=this._restrictMinMax(b,this._daylightSavingAdjust(new Date(l,o,h)));b.selectedDay=l.getDate();b.drawMonth=b.selectedMonth=l.getMonth();b.drawYear=b.selectedYear=l.getFullYear();if(j=="M"||j=="Y")this._notifyChange(b)},_restrictMinMax:function(b,h){var j=this._getMinMaxDate(b,"min");b=this._getMinMaxDate(b,
    +"max");h=j&&h<j?j:h;return h=b&&h>b?b:h},_notifyChange:function(b){var h=this._get(b,"onChangeMonthYear");if(h)h.apply(b.input?b.input[0]:null,[b.selectedYear,b.selectedMonth+1,b])},_getNumberOfMonths:function(b){b=this._get(b,"numberOfMonths");return b==null?[1,1]:typeof b=="number"?[1,b]:b},_getMinMaxDate:function(b,h){return this._determineDate(b,this._get(b,h+"Date"),null)},_getDaysInMonth:function(b,h){return 32-this._daylightSavingAdjust(new Date(b,h,32)).getDate()},_getFirstDayOfMonth:function(b,
    +h){return(new Date(b,h,1)).getDay()},_canAdjustMonth:function(b,h,j,l){var o=this._getNumberOfMonths(b);j=this._daylightSavingAdjust(new Date(j,l+(h<0?h:o[0]*o[1]),1));h<0&&j.setDate(this._getDaysInMonth(j.getFullYear(),j.getMonth()));return this._isInRange(b,j)},_isInRange:function(b,h){var j=this._getMinMaxDate(b,"min");b=this._getMinMaxDate(b,"max");return(!j||h.getTime()>=j.getTime())&&(!b||h.getTime()<=b.getTime())},_getFormatConfig:function(b){var h=this._get(b,"shortYearCutoff");h=typeof h!=
    +"string"?h:(new Date).getFullYear()%100+parseInt(h,10);return{shortYearCutoff:h,dayNamesShort:this._get(b,"dayNamesShort"),dayNames:this._get(b,"dayNames"),monthNamesShort:this._get(b,"monthNamesShort"),monthNames:this._get(b,"monthNames")}},_formatDate:function(b,h,j,l){if(!h){b.currentDay=b.selectedDay;b.currentMonth=b.selectedMonth;b.currentYear=b.selectedYear}h=h?typeof h=="object"?h:this._daylightSavingAdjust(new Date(l,j,h)):this._daylightSavingAdjust(new Date(b.currentYear,b.currentMonth,b.currentDay));
    +return this.formatDate(this._get(b,"dateFormat"),h,this._getFormatConfig(b))}});a.fn.datepicker=function(b){if(!this.length)return this;if(!a.datepicker.initialized){a(document).mousedown(a.datepicker._checkExternalClick).find("body").append(a.datepicker.dpDiv);a.datepicker.initialized=true}var h=Array.prototype.slice.call(arguments,1);if(typeof b=="string"&&(b=="isDisabled"||b=="getDate"||b=="widget"))return a.datepicker["_"+b+"Datepicker"].apply(a.datepicker,[this[0]].concat(h));if(b=="option"&&
    +arguments.length==2&&typeof arguments[1]=="string")return a.datepicker["_"+b+"Datepicker"].apply(a.datepicker,[this[0]].concat(h));return this.each(function(){typeof b=="string"?a.datepicker["_"+b+"Datepicker"].apply(a.datepicker,[this].concat(h)):a.datepicker._attachDatepicker(this,b)})};a.datepicker=new c;a.datepicker.initialized=false;a.datepicker.uuid=(new Date).getTime();a.datepicker.version="1.8.13";window["DP_jQuery_"+e]=a})(jQuery);
    +(function(a,d){var c={buttons:true,height:true,maxHeight:true,maxWidth:true,minHeight:true,minWidth:true,width:true},f={maxHeight:true,maxWidth:true,minHeight:true,minWidth:true},g=a.attrFn||{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true,click:true};a.widget("ui.dialog",{options:{autoOpen:true,buttons:{},closeOnEscape:true,closeText:"close",dialogClass:"",draggable:true,hide:null,height:"auto",maxHeight:false,maxWidth:false,minHeight:150,minWidth:150,modal:false,
    +position:{my:"center",at:"center",collision:"fit",using:function(e){var i=a(this).css(e).offset().top;i<0&&a(this).css("top",e.top-i)}},resizable:true,show:null,stack:true,title:"",width:300,zIndex:1E3},_create:function(){this.originalTitle=this.element.attr("title");if(typeof this.originalTitle!=="string")this.originalTitle="";this.options.title=this.options.title||this.originalTitle;var e=this,i=e.options,b=i.title||"&#160;",h=a.ui.dialog.getTitleId(e.element),j=(e.uiDialog=a("<div></div>")).appendTo(document.body).hide().addClass("ui-dialog ui-widget ui-widget-content ui-corner-all "+
    +i.dialogClass).css({zIndex:i.zIndex}).attr("tabIndex",-1).css("outline",0).keydown(function(n){if(i.closeOnEscape&&n.keyCode&&n.keyCode===a.ui.keyCode.ESCAPE){e.close(n);n.preventDefault()}}).attr({role:"dialog","aria-labelledby":h}).mousedown(function(n){e.moveToTop(false,n)});e.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(j);var l=(e.uiDialogTitlebar=a("<div></div>")).addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").prependTo(j),
    +o=a('<a href="#"></a>').addClass("ui-dialog-titlebar-close ui-corner-all").attr("role","button").hover(function(){o.addClass("ui-state-hover")},function(){o.removeClass("ui-state-hover")}).focus(function(){o.addClass("ui-state-focus")}).blur(function(){o.removeClass("ui-state-focus")}).click(function(n){e.close(n);return false}).appendTo(l);(e.uiDialogTitlebarCloseText=a("<span></span>")).addClass("ui-icon ui-icon-closethick").text(i.closeText).appendTo(o);a("<span></span>").addClass("ui-dialog-title").attr("id",
    +h).html(b).prependTo(l);if(a.isFunction(i.beforeclose)&&!a.isFunction(i.beforeClose))i.beforeClose=i.beforeclose;l.find("*").add(l).disableSelection();i.draggable&&a.fn.draggable&&e._makeDraggable();i.resizable&&a.fn.resizable&&e._makeResizable();e._createButtons(i.buttons);e._isOpen=false;a.fn.bgiframe&&j.bgiframe()},_init:function(){this.options.autoOpen&&this.open()},destroy:function(){var e=this;e.overlay&&e.overlay.destroy();e.uiDialog.hide();e.element.unbind(".dialog").removeData("dialog").removeClass("ui-dialog-content ui-widget-content").hide().appendTo("body");
    +e.uiDialog.remove();e.originalTitle&&e.element.attr("title",e.originalTitle);return e},widget:function(){return this.uiDialog},close:function(e){var i=this,b,h;if(false!==i._trigger("beforeClose",e)){i.overlay&&i.overlay.destroy();i.uiDialog.unbind("keypress.ui-dialog");i._isOpen=false;if(i.options.hide)i.uiDialog.hide(i.options.hide,function(){i._trigger("close",e)});else{i.uiDialog.hide();i._trigger("close",e)}a.ui.dialog.overlay.resize();if(i.options.modal){b=0;a(".ui-dialog").each(function(){if(this!==
    +i.uiDialog[0]){h=a(this).css("z-index");isNaN(h)||(b=Math.max(b,h))}});a.ui.dialog.maxZ=b}return i}},isOpen:function(){return this._isOpen},moveToTop:function(e,i){var b=this,h=b.options;if(h.modal&&!e||!h.stack&&!h.modal)return b._trigger("focus",i);if(h.zIndex>a.ui.dialog.maxZ)a.ui.dialog.maxZ=h.zIndex;if(b.overlay){a.ui.dialog.maxZ+=1;b.overlay.$el.css("z-index",a.ui.dialog.overlay.maxZ=a.ui.dialog.maxZ)}e={scrollTop:b.element.attr("scrollTop"),scrollLeft:b.element.attr("scrollLeft")};a.ui.dialog.maxZ+=
    +1;b.uiDialog.css("z-index",a.ui.dialog.maxZ);b.element.attr(e);b._trigger("focus",i);return b},open:function(){if(!this._isOpen){var e=this,i=e.options,b=e.uiDialog;e.overlay=i.modal?new a.ui.dialog.overlay(e):null;e._size();e._position(i.position);b.show(i.show);e.moveToTop(true);i.modal&&b.bind("keypress.ui-dialog",function(h){if(h.keyCode===a.ui.keyCode.TAB){var j=a(":tabbable",this),l=j.filter(":first");j=j.filter(":last");if(h.target===j[0]&&!h.shiftKey){l.focus(1);return false}else if(h.target===
    +l[0]&&h.shiftKey){j.focus(1);return false}}});a(e.element.find(":tabbable").get().concat(b.find(".ui-dialog-buttonpane :tabbable").get().concat(b.get()))).eq(0).focus();e._isOpen=true;e._trigger("open");return e}},_createButtons:function(e){var i=this,b=false,h=a("<div></div>").addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"),j=a("<div></div>").addClass("ui-dialog-buttonset").appendTo(h);i.uiDialog.find(".ui-dialog-buttonpane").remove();typeof e==="object"&&e!==null&&a.each(e,
    +function(){return!(b=true)});if(b){a.each(e,function(l,o){o=a.isFunction(o)?{click:o,text:l}:o;var n=a('<button type="button"></button>').click(function(){o.click.apply(i.element[0],arguments)}).appendTo(j);a.each(o,function(k,m){if(k!=="click")k in g?n[k](m):n.attr(k,m)});a.fn.button&&n.button()});h.appendTo(i.uiDialog)}},_makeDraggable:function(){function e(l){return{position:l.position,offset:l.offset}}var i=this,b=i.options,h=a(document),j;i.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",
    +handle:".ui-dialog-titlebar",containment:"document",start:function(l,o){j=b.height==="auto"?"auto":a(this).height();a(this).height(a(this).height()).addClass("ui-dialog-dragging");i._trigger("dragStart",l,e(o))},drag:function(l,o){i._trigger("drag",l,e(o))},stop:function(l,o){b.position=[o.position.left-h.scrollLeft(),o.position.top-h.scrollTop()];a(this).removeClass("ui-dialog-dragging").height(j);i._trigger("dragStop",l,e(o));a.ui.dialog.overlay.resize()}})},_makeResizable:function(e){function i(l){return{originalPosition:l.originalPosition,
    +originalSize:l.originalSize,position:l.position,size:l.size}}e=e===d?this.options.resizable:e;var b=this,h=b.options,j=b.uiDialog.css("position");e=typeof e==="string"?e:"n,e,s,w,se,sw,ne,nw";b.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:b.element,maxWidth:h.maxWidth,maxHeight:h.maxHeight,minWidth:h.minWidth,minHeight:b._minHeight(),handles:e,start:function(l,o){a(this).addClass("ui-dialog-resizing");b._trigger("resizeStart",l,i(o))},resize:function(l,o){b._trigger("resize",
    +l,i(o))},stop:function(l,o){a(this).removeClass("ui-dialog-resizing");h.height=a(this).height();h.width=a(this).width();b._trigger("resizeStop",l,i(o));a.ui.dialog.overlay.resize()}}).css("position",j).find(".ui-resizable-se").addClass("ui-icon ui-icon-grip-diagonal-se")},_minHeight:function(){var e=this.options;return e.height==="auto"?e.minHeight:Math.min(e.minHeight,e.height)},_position:function(e){var i=[],b=[0,0],h;if(e){if(typeof e==="string"||typeof e==="object"&&"0"in e){i=e.split?e.split(" "):
    +[e[0],e[1]];if(i.length===1)i[1]=i[0];a.each(["left","top"],function(j,l){if(+i[j]===i[j]){b[j]=i[j];i[j]=l}});e={my:i.join(" "),at:i.join(" "),offset:b.join(" ")}}e=a.extend({},a.ui.dialog.prototype.options.position,e)}else e=a.ui.dialog.prototype.options.position;(h=this.uiDialog.is(":visible"))||this.uiDialog.show();this.uiDialog.css({top:0,left:0}).position(a.extend({of:window},e));h||this.uiDialog.hide()},_setOptions:function(e){var i=this,b={},h=false;a.each(e,function(j,l){i._setOption(j,l);
    +if(j in c)h=true;if(j in f)b[j]=l});h&&this._size();this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option",b)},_setOption:function(e,i){var b=this,h=b.uiDialog;switch(e){case "beforeclose":e="beforeClose";break;case "buttons":b._createButtons(i);break;case "closeText":b.uiDialogTitlebarCloseText.text(""+i);break;case "dialogClass":h.removeClass(b.options.dialogClass).addClass("ui-dialog ui-widget ui-widget-content ui-corner-all "+i);break;case "disabled":i?h.addClass("ui-dialog-disabled"):
    +h.removeClass("ui-dialog-disabled");break;case "draggable":var j=h.is(":data(draggable)");j&&!i&&h.draggable("destroy");!j&&i&&b._makeDraggable();break;case "position":b._position(i);break;case "resizable":(j=h.is(":data(resizable)"))&&!i&&h.resizable("destroy");j&&typeof i==="string"&&h.resizable("option","handles",i);!j&&i!==false&&b._makeResizable(i);break;case "title":a(".ui-dialog-title",b.uiDialogTitlebar).html(""+(i||"&#160;"));break}a.Widget.prototype._setOption.apply(b,arguments)},_size:function(){var e=
    +this.options,i,b,h=this.uiDialog.is(":visible");this.element.show().css({width:"auto",minHeight:0,height:0});if(e.minWidth>e.width)e.width=e.minWidth;i=this.uiDialog.css({height:"auto",width:e.width}).height();b=Math.max(0,e.minHeight-i);if(e.height==="auto")if(a.support.minHeight)this.element.css({minHeight:b,height:"auto"});else{this.uiDialog.show();e=this.element.css("height","auto").height();h||this.uiDialog.hide();this.element.height(Math.max(e,b))}else this.element.height(Math.max(e.height-
    +i,0));this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option","minHeight",this._minHeight())}});a.extend(a.ui.dialog,{version:"1.8.13",uuid:0,maxZ:0,getTitleId:function(e){e=e.attr("id");if(!e){this.uuid+=1;e=this.uuid}return"ui-dialog-title-"+e},overlay:function(e){this.$el=a.ui.dialog.overlay.create(e)}});a.extend(a.ui.dialog.overlay,{instances:[],oldInstances:[],maxZ:0,events:a.map("focus,mousedown,mouseup,keydown,keypress,click".split(","),function(e){return e+".dialog-overlay"}).join(" "),
    +create:function(e){if(this.instances.length===0){setTimeout(function(){a.ui.dialog.overlay.instances.length&&a(document).bind(a.ui.dialog.overlay.events,function(b){if(a(b.target).zIndex()<a.ui.dialog.overlay.maxZ)return false})},1);a(document).bind("keydown.dialog-overlay",function(b){if(e.options.closeOnEscape&&b.keyCode&&b.keyCode===a.ui.keyCode.ESCAPE){e.close(b);b.preventDefault()}});a(window).bind("resize.dialog-overlay",a.ui.dialog.overlay.resize)}var i=(this.oldInstances.pop()||a("<div></div>").addClass("ui-widget-overlay")).appendTo(document.body).css({width:this.width(),
    +height:this.height()});a.fn.bgiframe&&i.bgiframe();this.instances.push(i);return i},destroy:function(e){var i=a.inArray(e,this.instances);i!=-1&&this.oldInstances.push(this.instances.splice(i,1)[0]);this.instances.length===0&&a([document,window]).unbind(".dialog-overlay");e.remove();var b=0;a.each(this.instances,function(){b=Math.max(b,this.css("z-index"))});this.maxZ=b},height:function(){var e,i;if(a.browser.msie&&a.browser.version<7){e=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight);
    +i=Math.max(document.documentElement.offsetHeight,document.body.offsetHeight);return e<i?a(window).height()+"px":e+"px"}else return a(document).height()+"px"},width:function(){var e,i;if(a.browser.msie&&a.browser.version<7){e=Math.max(document.documentElement.scrollWidth,document.body.scrollWidth);i=Math.max(document.documentElement.offsetWidth,document.body.offsetWidth);return e<i?a(window).width()+"px":e+"px"}else return a(document).width()+"px"},resize:function(){var e=a([]);a.each(a.ui.dialog.overlay.instances,
    +function(){e=e.add(this)});e.css({width:0,height:0}).css({width:a.ui.dialog.overlay.width(),height:a.ui.dialog.overlay.height()})}});a.extend(a.ui.dialog.overlay.prototype,{destroy:function(){a.ui.dialog.overlay.destroy(this.$el)}})})(jQuery);
    +(function(a){a.ui=a.ui||{};var d=/left|center|right/,c=/top|center|bottom/,f=a.fn.position,g=a.fn.offset;a.fn.position=function(e){if(!e||!e.of)return f.apply(this,arguments);e=a.extend({},e);var i=a(e.of),b=i[0],h=(e.collision||"flip").split(" "),j=e.offset?e.offset.split(" "):[0,0],l,o,n;if(b.nodeType===9){l=i.width();o=i.height();n={top:0,left:0}}else if(b.setTimeout){l=i.width();o=i.height();n={top:i.scrollTop(),left:i.scrollLeft()}}else if(b.preventDefault){e.at="left top";l=o=0;n={top:e.of.pageY,
    +left:e.of.pageX}}else{l=i.outerWidth();o=i.outerHeight();n=i.offset()}a.each(["my","at"],function(){var k=(e[this]||"").split(" ");if(k.length===1)k=d.test(k[0])?k.concat(["center"]):c.test(k[0])?["center"].concat(k):["center","center"];k[0]=d.test(k[0])?k[0]:"center";k[1]=c.test(k[1])?k[1]:"center";e[this]=k});if(h.length===1)h[1]=h[0];j[0]=parseInt(j[0],10)||0;if(j.length===1)j[1]=j[0];j[1]=parseInt(j[1],10)||0;if(e.at[0]==="right")n.left+=l;else if(e.at[0]==="center")n.left+=l/2;if(e.at[1]==="bottom")n.top+=
    +o;else if(e.at[1]==="center")n.top+=o/2;n.left+=j[0];n.top+=j[1];return this.each(function(){var k=a(this),m=k.outerWidth(),p=k.outerHeight(),q=parseInt(a.curCSS(this,"marginLeft",true))||0,s=parseInt(a.curCSS(this,"marginTop",true))||0,r=m+q+(parseInt(a.curCSS(this,"marginRight",true))||0),u=p+s+(parseInt(a.curCSS(this,"marginBottom",true))||0),v=a.extend({},n),w;if(e.my[0]==="right")v.left-=m;else if(e.my[0]==="center")v.left-=m/2;if(e.my[1]==="bottom")v.top-=p;else if(e.my[1]==="center")v.top-=
    +p/2;v.left=Math.round(v.left);v.top=Math.round(v.top);w={left:v.left-q,top:v.top-s};a.each(["left","top"],function(x,z){a.ui.position[h[x]]&&a.ui.position[h[x]][z](v,{targetWidth:l,targetHeight:o,elemWidth:m,elemHeight:p,collisionPosition:w,collisionWidth:r,collisionHeight:u,offset:j,my:e.my,at:e.at})});a.fn.bgiframe&&k.bgiframe();k.offset(a.extend(v,{using:e.using}))})};a.ui.position={fit:{left:function(e,i){var b=a(window);b=i.collisionPosition.left+i.collisionWidth-b.width()-b.scrollLeft();e.left=
    +b>0?e.left-b:Math.max(e.left-i.collisionPosition.left,e.left)},top:function(e,i){var b=a(window);b=i.collisionPosition.top+i.collisionHeight-b.height()-b.scrollTop();e.top=b>0?e.top-b:Math.max(e.top-i.collisionPosition.top,e.top)}},flip:{left:function(e,i){if(i.at[0]!=="center"){var b=a(window);b=i.collisionPosition.left+i.collisionWidth-b.width()-b.scrollLeft();var h=i.my[0]==="left"?-i.elemWidth:i.my[0]==="right"?i.elemWidth:0,j=i.at[0]==="left"?i.targetWidth:-i.targetWidth,l=-2*i.offset[0];e.left+=
    +i.collisionPosition.left<0?h+j+l:b>0?h+j+l:0}},top:function(e,i){if(i.at[1]!=="center"){var b=a(window);b=i.collisionPosition.top+i.collisionHeight-b.height()-b.scrollTop();var h=i.my[1]==="top"?-i.elemHeight:i.my[1]==="bottom"?i.elemHeight:0,j=i.at[1]==="top"?i.targetHeight:-i.targetHeight,l=-2*i.offset[1];e.top+=i.collisionPosition.top<0?h+j+l:b>0?h+j+l:0}}}};if(!a.offset.setOffset){a.offset.setOffset=function(e,i){if(/static/.test(a.curCSS(e,"position")))e.style.position="relative";var b=a(e),
    +h=b.offset(),j=parseInt(a.curCSS(e,"top",true),10)||0,l=parseInt(a.curCSS(e,"left",true),10)||0;h={top:i.top-h.top+j,left:i.left-h.left+l};"using"in i?i.using.call(e,h):b.css(h)};a.fn.offset=function(e){var i=this[0];if(!i||!i.ownerDocument)return null;if(e)return this.each(function(){a.offset.setOffset(this,e)});return g.call(this)}}})(jQuery);
    +(function(a,d){a.widget("ui.progressbar",{options:{value:0,max:100},min:0,_create:function(){this.element.addClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").attr({role:"progressbar","aria-valuemin":this.min,"aria-valuemax":this.options.max,"aria-valuenow":this._value()});this.valueDiv=a("<div class='ui-progressbar-value ui-widget-header ui-corner-left'></div>").appendTo(this.element);this.oldValue=this._value();this._refreshValue()},destroy:function(){this.element.removeClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow");
    +this.valueDiv.remove();a.Widget.prototype.destroy.apply(this,arguments)},value:function(c){if(c===d)return this._value();this._setOption("value",c);return this},_setOption:function(c,f){if(c==="value"){this.options.value=f;this._refreshValue();this._value()===this.options.max&&this._trigger("complete")}a.Widget.prototype._setOption.apply(this,arguments)},_value:function(){var c=this.options.value;if(typeof c!=="number")c=0;return Math.min(this.options.max,Math.max(this.min,c))},_percentage:function(){return 100*
    +this._value()/this.options.max},_refreshValue:function(){var c=this.value(),f=this._percentage();if(this.oldValue!==c){this.oldValue=c;this._trigger("change")}this.valueDiv.toggle(c>this.min).toggleClass("ui-corner-right",c===this.options.max).width(f.toFixed(0)+"%");this.element.attr("aria-valuenow",c)}});a.extend(a.ui.progressbar,{version:"1.8.13"})})(jQuery);
    +(function(a){a.widget("ui.slider",a.ui.mouse,{widgetEventPrefix:"slide",options:{animate:false,distance:0,max:100,min:0,orientation:"horizontal",range:false,step:1,value:0,values:null},_create:function(){var d=this,c=this.options,f=this.element.find(".ui-slider-handle").addClass("ui-state-default ui-corner-all"),g=c.values&&c.values.length||1,e=[];this._mouseSliding=this._keySliding=false;this._animateOff=true;this._handleIndex=null;this._detectOrientation();this._mouseInit();this.element.addClass("ui-slider ui-slider-"+
    +this.orientation+" ui-widget ui-widget-content ui-corner-all"+(c.disabled?" ui-slider-disabled ui-disabled":""));this.range=a([]);if(c.range){if(c.range===true){if(!c.values)c.values=[this._valueMin(),this._valueMin()];if(c.values.length&&c.values.length!==2)c.values=[c.values[0],c.values[0]]}this.range=a("<div></div>").appendTo(this.element).addClass("ui-slider-range ui-widget-header"+(c.range==="min"||c.range==="max"?" ui-slider-range-"+c.range:""))}for(var i=f.length;i<g;i+=1)e.push("<a class='ui-slider-handle ui-state-default ui-corner-all' href='#'></a>");
    +this.handles=f.add(a(e.join("")).appendTo(d.element));this.handle=this.handles.eq(0);this.handles.add(this.range).filter("a").click(function(b){b.preventDefault()}).hover(function(){c.disabled||a(this).addClass("ui-state-hover")},function(){a(this).removeClass("ui-state-hover")}).focus(function(){if(c.disabled)a(this).blur();else{a(".ui-slider .ui-state-focus").removeClass("ui-state-focus");a(this).addClass("ui-state-focus")}}).blur(function(){a(this).removeClass("ui-state-focus")});this.handles.each(function(b){a(this).data("index.ui-slider-handle",
    +b)});this.handles.keydown(function(b){var h=true,j=a(this).data("index.ui-slider-handle"),l,o,n;if(!d.options.disabled){switch(b.keyCode){case a.ui.keyCode.HOME:case a.ui.keyCode.END:case a.ui.keyCode.PAGE_UP:case a.ui.keyCode.PAGE_DOWN:case a.ui.keyCode.UP:case a.ui.keyCode.RIGHT:case a.ui.keyCode.DOWN:case a.ui.keyCode.LEFT:h=false;if(!d._keySliding){d._keySliding=true;a(this).addClass("ui-state-active");l=d._start(b,j);if(l===false)return}break}n=d.options.step;l=d.options.values&&d.options.values.length?
    +(o=d.values(j)):(o=d.value());switch(b.keyCode){case a.ui.keyCode.HOME:o=d._valueMin();break;case a.ui.keyCode.END:o=d._valueMax();break;case a.ui.keyCode.PAGE_UP:o=d._trimAlignValue(l+(d._valueMax()-d._valueMin())/5);break;case a.ui.keyCode.PAGE_DOWN:o=d._trimAlignValue(l-(d._valueMax()-d._valueMin())/5);break;case a.ui.keyCode.UP:case a.ui.keyCode.RIGHT:if(l===d._valueMax())return;o=d._trimAlignValue(l+n);break;case a.ui.keyCode.DOWN:case a.ui.keyCode.LEFT:if(l===d._valueMin())return;o=d._trimAlignValue(l-
    +n);break}d._slide(b,j,o);return h}}).keyup(function(b){var h=a(this).data("index.ui-slider-handle");if(d._keySliding){d._keySliding=false;d._stop(b,h);d._change(b,h);a(this).removeClass("ui-state-active")}});this._refreshValue();this._animateOff=false},destroy:function(){this.handles.remove();this.range.remove();this.element.removeClass("ui-slider ui-slider-horizontal ui-slider-vertical ui-slider-disabled ui-widget ui-widget-content ui-corner-all").removeData("slider").unbind(".slider");this._mouseDestroy();
    +return this},_mouseCapture:function(d){var c=this.options,f,g,e,i,b;if(c.disabled)return false;this.elementSize={width:this.element.outerWidth(),height:this.element.outerHeight()};this.elementOffset=this.element.offset();f=this._normValueFromMouse({x:d.pageX,y:d.pageY});g=this._valueMax()-this._valueMin()+1;i=this;this.handles.each(function(h){var j=Math.abs(f-i.values(h));if(g>j){g=j;e=a(this);b=h}});if(c.range===true&&this.values(1)===c.min){b+=1;e=a(this.handles[b])}if(this._start(d,b)===false)return false;
    +this._mouseSliding=true;i._handleIndex=b;e.addClass("ui-state-active").focus();c=e.offset();this._clickOffset=!a(d.target).parents().andSelf().is(".ui-slider-handle")?{left:0,top:0}:{left:d.pageX-c.left-e.width()/2,top:d.pageY-c.top-e.height()/2-(parseInt(e.css("borderTopWidth"),10)||0)-(parseInt(e.css("borderBottomWidth"),10)||0)+(parseInt(e.css("marginTop"),10)||0)};this.handles.hasClass("ui-state-hover")||this._slide(d,b,f);return this._animateOff=true},_mouseStart:function(){return true},_mouseDrag:function(d){var c=
    +this._normValueFromMouse({x:d.pageX,y:d.pageY});this._slide(d,this._handleIndex,c);return false},_mouseStop:function(d){this.handles.removeClass("ui-state-active");this._mouseSliding=false;this._stop(d,this._handleIndex);this._change(d,this._handleIndex);this._clickOffset=this._handleIndex=null;return this._animateOff=false},_detectOrientation:function(){this.orientation=this.options.orientation==="vertical"?"vertical":"horizontal"},_normValueFromMouse:function(d){var c;if(this.orientation==="horizontal"){c=
    +this.elementSize.width;d=d.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)}else{c=this.elementSize.height;d=d.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)}c=d/c;if(c>1)c=1;if(c<0)c=0;if(this.orientation==="vertical")c=1-c;d=this._valueMax()-this._valueMin();return this._trimAlignValue(this._valueMin()+c*d)},_start:function(d,c){var f={handle:this.handles[c],value:this.value()};if(this.options.values&&this.options.values.length){f.value=this.values(c);
    +f.values=this.values()}return this._trigger("start",d,f)},_slide:function(d,c,f){var g;if(this.options.values&&this.options.values.length){g=this.values(c?0:1);if(this.options.values.length===2&&this.options.range===true&&(c===0&&f>g||c===1&&f<g))f=g;if(f!==this.values(c)){g=this.values();g[c]=f;d=this._trigger("slide",d,{handle:this.handles[c],value:f,values:g});this.values(c?0:1);d!==false&&this.values(c,f,true)}}else if(f!==this.value()){d=this._trigger("slide",d,{handle:this.handles[c],value:f});
    +d!==false&&this.value(f)}},_stop:function(d,c){var f={handle:this.handles[c],value:this.value()};if(this.options.values&&this.options.values.length){f.value=this.values(c);f.values=this.values()}this._trigger("stop",d,f)},_change:function(d,c){if(!this._keySliding&&!this._mouseSliding){var f={handle:this.handles[c],value:this.value()};if(this.options.values&&this.options.values.length){f.value=this.values(c);f.values=this.values()}this._trigger("change",d,f)}},value:function(d){if(arguments.length){this.options.value=
    +this._trimAlignValue(d);this._refreshValue();this._change(null,0)}else return this._value()},values:function(d,c){var f,g,e;if(arguments.length>1){this.options.values[d]=this._trimAlignValue(c);this._refreshValue();this._change(null,d)}else if(arguments.length)if(a.isArray(arguments[0])){f=this.options.values;g=arguments[0];for(e=0;e<f.length;e+=1){f[e]=this._trimAlignValue(g[e]);this._change(null,e)}this._refreshValue()}else return this.options.values&&this.options.values.length?this._values(d):
    +this.value();else return this._values()},_setOption:function(d,c){var f,g=0;if(a.isArray(this.options.values))g=this.options.values.length;a.Widget.prototype._setOption.apply(this,arguments);switch(d){case "disabled":if(c){this.handles.filter(".ui-state-focus").blur();this.handles.removeClass("ui-state-hover");this.handles.attr("disabled","disabled");this.element.addClass("ui-disabled")}else{this.handles.removeAttr("disabled");this.element.removeClass("ui-disabled")}break;case "orientation":this._detectOrientation();
    +this.element.removeClass("ui-slider-horizontal ui-slider-vertical").addClass("ui-slider-"+this.orientation);this._refreshValue();break;case "value":this._animateOff=true;this._refreshValue();this._change(null,0);this._animateOff=false;break;case "values":this._animateOff=true;this._refreshValue();for(f=0;f<g;f+=1)this._change(null,f);this._animateOff=false;break}},_value:function(){var d=this.options.value;return d=this._trimAlignValue(d)},_values:function(d){var c,f;if(arguments.length){c=this.options.values[d];
    +return c=this._trimAlignValue(c)}else{c=this.options.values.slice();for(f=0;f<c.length;f+=1)c[f]=this._trimAlignValue(c[f]);return c}},_trimAlignValue:function(d){if(d<=this._valueMin())return this._valueMin();if(d>=this._valueMax())return this._valueMax();var c=this.options.step>0?this.options.step:1,f=(d-this._valueMin())%c;alignValue=d-f;if(Math.abs(f)*2>=c)alignValue+=f>0?c:-c;return parseFloat(alignValue.toFixed(5))},_valueMin:function(){return this.options.min},_valueMax:function(){return this.options.max},
    +_refreshValue:function(){var d=this.options.range,c=this.options,f=this,g=!this._animateOff?c.animate:false,e,i={},b,h,j,l;if(this.options.values&&this.options.values.length)this.handles.each(function(o){e=(f.values(o)-f._valueMin())/(f._valueMax()-f._valueMin())*100;i[f.orientation==="horizontal"?"left":"bottom"]=e+"%";a(this).stop(1,1)[g?"animate":"css"](i,c.animate);if(f.options.range===true)if(f.orientation==="horizontal"){if(o===0)f.range.stop(1,1)[g?"animate":"css"]({left:e+"%"},c.animate);
    +if(o===1)f.range[g?"animate":"css"]({width:e-b+"%"},{queue:false,duration:c.animate})}else{if(o===0)f.range.stop(1,1)[g?"animate":"css"]({bottom:e+"%"},c.animate);if(o===1)f.range[g?"animate":"css"]({height:e-b+"%"},{queue:false,duration:c.animate})}b=e});else{h=this.value();j=this._valueMin();l=this._valueMax();e=l!==j?(h-j)/(l-j)*100:0;i[f.orientation==="horizontal"?"left":"bottom"]=e+"%";this.handle.stop(1,1)[g?"animate":"css"](i,c.animate);if(d==="min"&&this.orientation==="horizontal")this.range.stop(1,
    +1)[g?"animate":"css"]({width:e+"%"},c.animate);if(d==="max"&&this.orientation==="horizontal")this.range[g?"animate":"css"]({width:100-e+"%"},{queue:false,duration:c.animate});if(d==="min"&&this.orientation==="vertical")this.range.stop(1,1)[g?"animate":"css"]({height:e+"%"},c.animate);if(d==="max"&&this.orientation==="vertical")this.range[g?"animate":"css"]({height:100-e+"%"},{queue:false,duration:c.animate})}}});a.extend(a.ui.slider,{version:"1.8.13"})})(jQuery);
    +(function(a,d){function c(){return++g}function f(){return++e}var g=0,e=0;a.widget("ui.tabs",{options:{add:null,ajaxOptions:null,cache:false,cookie:null,collapsible:false,disable:null,disabled:[],enable:null,event:"click",fx:null,idPrefix:"ui-tabs-",load:null,panelTemplate:"<div></div>",remove:null,select:null,show:null,spinner:"<em>Loading&#8230;</em>",tabTemplate:"<li><a href='#{href}'><span>#{label}</span></a></li>"},_create:function(){this._tabify(true)},_setOption:function(i,b){if(i=="selected")this.options.collapsible&&
    +b==this.options.selected||this.select(b);else{this.options[i]=b;this._tabify()}},_tabId:function(i){return i.title&&i.title.replace(/\s/g,"_").replace(/[^\w\u00c0-\uFFFF-]/g,"")||this.options.idPrefix+c()},_sanitizeSelector:function(i){return i.replace(/:/g,"\\:")},_cookie:function(){var i=this.cookie||(this.cookie=this.options.cookie.name||"ui-tabs-"+f());return a.cookie.apply(null,[i].concat(a.makeArray(arguments)))},_ui:function(i,b){return{tab:i,panel:b,index:this.anchors.index(i)}},_cleanup:function(){this.lis.filter(".ui-state-processing").removeClass("ui-state-processing").find("span:data(label.tabs)").each(function(){var i=
    +a(this);i.html(i.data("label.tabs")).removeData("label.tabs")})},_tabify:function(i){function b(r,u){r.css("display","");!a.support.opacity&&u.opacity&&r[0].style.removeAttribute("filter")}var h=this,j=this.options,l=/^#.+/;this.list=this.element.find("ol,ul").eq(0);this.lis=a(" > li:has(a[href])",this.list);this.anchors=this.lis.map(function(){return a("a",this)[0]});this.panels=a([]);this.anchors.each(function(r,u){var v=a(u).attr("href"),w=v.split("#")[0],x;if(w&&(w===location.toString().split("#")[0]||
    +(x=a("base")[0])&&w===x.href)){v=u.hash;u.href=v}if(l.test(v))h.panels=h.panels.add(h.element.find(h._sanitizeSelector(v)));else if(v&&v!=="#"){a.data(u,"href.tabs",v);a.data(u,"load.tabs",v.replace(/#.*$/,""));v=h._tabId(u);u.href="#"+v;u=h.element.find("#"+v);if(!u.length){u=a(j.panelTemplate).attr("id",v).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").insertAfter(h.panels[r-1]||h.list);u.data("destroy.tabs",true)}h.panels=h.panels.add(u)}else j.disabled.push(r)});if(i){this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all");
    +this.list.addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all");this.lis.addClass("ui-state-default ui-corner-top");this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom");if(j.selected===d){location.hash&&this.anchors.each(function(r,u){if(u.hash==location.hash){j.selected=r;return false}});if(typeof j.selected!=="number"&&j.cookie)j.selected=parseInt(h._cookie(),10);if(typeof j.selected!=="number"&&this.lis.filter(".ui-tabs-selected").length)j.selected=
    +this.lis.index(this.lis.filter(".ui-tabs-selected"));j.selected=j.selected||(this.lis.length?0:-1)}else if(j.selected===null)j.selected=-1;j.selected=j.selected>=0&&this.anchors[j.selected]||j.selected<0?j.selected:0;j.disabled=a.unique(j.disabled.concat(a.map(this.lis.filter(".ui-state-disabled"),function(r){return h.lis.index(r)}))).sort();a.inArray(j.selected,j.disabled)!=-1&&j.disabled.splice(a.inArray(j.selected,j.disabled),1);this.panels.addClass("ui-tabs-hide");this.lis.removeClass("ui-tabs-selected ui-state-active");
    +if(j.selected>=0&&this.anchors.length){h.element.find(h._sanitizeSelector(h.anchors[j.selected].hash)).removeClass("ui-tabs-hide");this.lis.eq(j.selected).addClass("ui-tabs-selected ui-state-active");h.element.queue("tabs",function(){h._trigger("show",null,h._ui(h.anchors[j.selected],h.element.find(h._sanitizeSelector(h.anchors[j.selected].hash))[0]))});this.load(j.selected)}a(window).bind("unload",function(){h.lis.add(h.anchors).unbind(".tabs");h.lis=h.anchors=h.panels=null})}else j.selected=this.lis.index(this.lis.filter(".ui-tabs-selected"));
    +this.element[j.collapsible?"addClass":"removeClass"]("ui-tabs-collapsible");j.cookie&&this._cookie(j.selected,j.cookie);i=0;for(var o;o=this.lis[i];i++)a(o)[a.inArray(i,j.disabled)!=-1&&!a(o).hasClass("ui-tabs-selected")?"addClass":"removeClass"]("ui-state-disabled");j.cache===false&&this.anchors.removeData("cache.tabs");this.lis.add(this.anchors).unbind(".tabs");if(j.event!=="mouseover"){var n=function(r,u){u.is(":not(.ui-state-disabled)")&&u.addClass("ui-state-"+r)},k=function(r,u){u.removeClass("ui-state-"+
    +r)};this.lis.bind("mouseover.tabs",function(){n("hover",a(this))});this.lis.bind("mouseout.tabs",function(){k("hover",a(this))});this.anchors.bind("focus.tabs",function(){n("focus",a(this).closest("li"))});this.anchors.bind("blur.tabs",function(){k("focus",a(this).closest("li"))})}var m,p;if(j.fx)if(a.isArray(j.fx)){m=j.fx[0];p=j.fx[1]}else m=p=j.fx;var q=p?function(r,u){a(r).closest("li").addClass("ui-tabs-selected ui-state-active");u.hide().removeClass("ui-tabs-hide").animate(p,p.duration||"normal",
    +function(){b(u,p);h._trigger("show",null,h._ui(r,u[0]))})}:function(r,u){a(r).closest("li").addClass("ui-tabs-selected ui-state-active");u.removeClass("ui-tabs-hide");h._trigger("show",null,h._ui(r,u[0]))},s=m?function(r,u){u.animate(m,m.duration||"normal",function(){h.lis.removeClass("ui-tabs-selected ui-state-active");u.addClass("ui-tabs-hide");b(u,m);h.element.dequeue("tabs")})}:function(r,u){h.lis.removeClass("ui-tabs-selected ui-state-active");u.addClass("ui-tabs-hide");h.element.dequeue("tabs")};
    +this.anchors.bind(j.event+".tabs",function(){var r=this,u=a(r).closest("li"),v=h.panels.filter(":not(.ui-tabs-hide)"),w=h.element.find(h._sanitizeSelector(r.hash));if(u.hasClass("ui-tabs-selected")&&!j.collapsible||u.hasClass("ui-state-disabled")||u.hasClass("ui-state-processing")||h.panels.filter(":animated").length||h._trigger("select",null,h._ui(this,w[0]))===false){this.blur();return false}j.selected=h.anchors.index(this);h.abort();if(j.collapsible)if(u.hasClass("ui-tabs-selected")){j.selected=
    +-1;j.cookie&&h._cookie(j.selected,j.cookie);h.element.queue("tabs",function(){s(r,v)}).dequeue("tabs");this.blur();return false}else if(!v.length){j.cookie&&h._cookie(j.selected,j.cookie);h.element.queue("tabs",function(){q(r,w)});h.load(h.anchors.index(this));this.blur();return false}j.cookie&&h._cookie(j.selected,j.cookie);if(w.length){v.length&&h.element.queue("tabs",function(){s(r,v)});h.element.queue("tabs",function(){q(r,w)});h.load(h.anchors.index(this))}else throw"jQuery UI Tabs: Mismatching fragment identifier.";
    +a.browser.msie&&this.blur()});this.anchors.bind("click.tabs",function(){return false})},_getIndex:function(i){if(typeof i=="string")i=this.anchors.index(this.anchors.filter("[href$="+i+"]"));return i},destroy:function(){var i=this.options;this.abort();this.element.unbind(".tabs").removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible").removeData("tabs");this.list.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all");this.anchors.each(function(){var b=
    +a.data(this,"href.tabs");if(b)this.href=b;var h=a(this).unbind(".tabs");a.each(["href","load","cache"],function(j,l){h.removeData(l+".tabs")})});this.lis.unbind(".tabs").add(this.panels).each(function(){a.data(this,"destroy.tabs")?a(this).remove():a(this).removeClass("ui-state-default ui-corner-top ui-tabs-selected ui-state-active ui-state-hover ui-state-focus ui-state-disabled ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide")});i.cookie&&this._cookie(null,i.cookie);return this},add:function(i,
    +b,h){if(h===d)h=this.anchors.length;var j=this,l=this.options;b=a(l.tabTemplate.replace(/#\{href\}/g,i).replace(/#\{label\}/g,b));i=!i.indexOf("#")?i.replace("#",""):this._tabId(a("a",b)[0]);b.addClass("ui-state-default ui-corner-top").data("destroy.tabs",true);var o=j.element.find("#"+i);o.length||(o=a(l.panelTemplate).attr("id",i).data("destroy.tabs",true));o.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide");if(h>=this.lis.length){b.appendTo(this.list);o.appendTo(this.list[0].parentNode)}else{b.insertBefore(this.lis[h]);
    +o.insertBefore(this.panels[h])}l.disabled=a.map(l.disabled,function(n){return n>=h?++n:n});this._tabify();if(this.anchors.length==1){l.selected=0;b.addClass("ui-tabs-selected ui-state-active");o.removeClass("ui-tabs-hide");this.element.queue("tabs",function(){j._trigger("show",null,j._ui(j.anchors[0],j.panels[0]))});this.load(0)}this._trigger("add",null,this._ui(this.anchors[h],this.panels[h]));return this},remove:function(i){i=this._getIndex(i);var b=this.options,h=this.lis.eq(i).remove(),j=this.panels.eq(i).remove();
    +if(h.hasClass("ui-tabs-selected")&&this.anchors.length>1)this.select(i+(i+1<this.anchors.length?1:-1));b.disabled=a.map(a.grep(b.disabled,function(l){return l!=i}),function(l){return l>=i?--l:l});this._tabify();this._trigger("remove",null,this._ui(h.find("a")[0],j[0]));return this},enable:function(i){i=this._getIndex(i);var b=this.options;if(a.inArray(i,b.disabled)!=-1){this.lis.eq(i).removeClass("ui-state-disabled");b.disabled=a.grep(b.disabled,function(h){return h!=i});this._trigger("enable",null,
    +this._ui(this.anchors[i],this.panels[i]));return this}},disable:function(i){i=this._getIndex(i);var b=this.options;if(i!=b.selected){this.lis.eq(i).addClass("ui-state-disabled");b.disabled.push(i);b.disabled.sort();this._trigger("disable",null,this._ui(this.anchors[i],this.panels[i]))}return this},select:function(i){i=this._getIndex(i);if(i==-1)if(this.options.collapsible&&this.options.selected!=-1)i=this.options.selected;else return this;this.anchors.eq(i).trigger(this.options.event+".tabs");return this},
    +load:function(i){i=this._getIndex(i);var b=this,h=this.options,j=this.anchors.eq(i)[0],l=a.data(j,"load.tabs");this.abort();if(!l||this.element.queue("tabs").length!==0&&a.data(j,"cache.tabs"))this.element.dequeue("tabs");else{this.lis.eq(i).addClass("ui-state-processing");if(h.spinner){var o=a("span",j);o.data("label.tabs",o.html()).html(h.spinner)}this.xhr=a.ajax(a.extend({},h.ajaxOptions,{url:l,success:function(n,k){b.element.find(b._sanitizeSelector(j.hash)).html(n);b._cleanup();h.cache&&a.data(j,
    +"cache.tabs",true);b._trigger("load",null,b._ui(b.anchors[i],b.panels[i]));try{h.ajaxOptions.success(n,k)}catch(m){}},error:function(n,k){b._cleanup();b._trigger("load",null,b._ui(b.anchors[i],b.panels[i]));try{h.ajaxOptions.error(n,k,i,j)}catch(m){}}}));b.element.dequeue("tabs");return this}},abort:function(){this.element.queue([]);this.panels.stop(false,true);this.element.queue("tabs",this.element.queue("tabs").splice(-2,2));if(this.xhr){this.xhr.abort();delete this.xhr}this._cleanup();return this},
    +url:function(i,b){this.anchors.eq(i).removeData("cache.tabs").data("load.tabs",b);return this},length:function(){return this.anchors.length}});a.extend(a.ui.tabs,{version:"1.8.13"});a.extend(a.ui.tabs.prototype,{rotation:null,rotate:function(i,b){var h=this,j=this.options,l=h._rotate||(h._rotate=function(o){clearTimeout(h.rotation);h.rotation=setTimeout(function(){var n=j.selected;h.select(++n<h.anchors.length?n:0)},i);o&&o.stopPropagation()});b=h._unrotate||(h._unrotate=!b?function(o){o.clientX&&
    +h.rotate(null)}:function(){t=j.selected;l()});if(i){this.element.bind("tabsshow",l);this.anchors.bind(j.event+".tabs",b);l()}else{clearTimeout(h.rotation);this.element.unbind("tabsshow",l);this.anchors.unbind(j.event+".tabs",b);delete this._rotate;delete this._unrotate}return this}})})(jQuery);
    diff --git a/lib/jquery-ui-1.8.16-min.js b/lib/jquery-ui-1.8.16-min.js
    new file mode 100644
    index 000000000..0202506b5
    --- /dev/null
    +++ b/lib/jquery-ui-1.8.16-min.js
    @@ -0,0 +1,414 @@
    +/*!
    + * jQuery UI 1.8.16
    + *
    + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
    + * Dual licensed under the MIT or GPL Version 2 licenses.
    + * http://jquery.org/license
    + *
    + * http://docs.jquery.com/UI
    + */
    +(function(a,d){function c(h,g){var i=h.nodeName.toLowerCase();if("area"===i){g=h.parentNode;i=g.name;if(!h.href||!i||g.nodeName.toLowerCase()!=="map")return false;h=a("img[usemap=#"+i+"]")[0];return!!h&&e(h)}return(/input|select|textarea|button|object/.test(i)?!h.disabled:"a"==i?h.href||g:g)&&e(h)}function e(h){return!a(h).parents().andSelf().filter(function(){return a.curCSS(this,"visibility")==="hidden"||a.expr.filters.hidden(this)}).length}a.ui=a.ui||{};if(!a.ui.version){a.extend(a.ui,{version:"1.8.16",
    +keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}});a.fn.extend({propAttr:a.fn.prop||a.fn.attr,_focus:a.fn.focus,focus:function(h,g){return typeof h==="number"?this.each(function(){var i=
    +this;setTimeout(function(){a(i).focus();g&&g.call(i)},h)}):this._focus.apply(this,arguments)},scrollParent:function(){var h;h=a.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(a.curCSS(this,"position",1))&&/(auto|scroll)/.test(a.curCSS(this,"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(a.curCSS(this,
    +"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0);return/fixed/.test(this.css("position"))||!h.length?a(document):h},zIndex:function(h){if(h!==d)return this.css("zIndex",h);if(this.length){h=a(this[0]);for(var g;h.length&&h[0]!==document;){g=h.css("position");if(g==="absolute"||g==="relative"||g==="fixed"){g=parseInt(h.css("zIndex"),10);if(!isNaN(g)&&g!==0)return g}h=h.parent()}}return 0},disableSelection:function(){return this.bind((a.support.selectstart?"selectstart":
    +"mousedown")+".ui-disableSelection",function(h){h.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}});a.each(["Width","Height"],function(h,g){function i(l,o,n,k){a.each(b,function(){o-=parseFloat(a.curCSS(l,"padding"+this,true))||0;if(n)o-=parseFloat(a.curCSS(l,"border"+this+"Width",true))||0;if(k)o-=parseFloat(a.curCSS(l,"margin"+this,true))||0});return o}var b=g==="Width"?["Left","Right"]:["Top","Bottom"],f=g.toLowerCase(),j={innerWidth:a.fn.innerWidth,innerHeight:a.fn.innerHeight,
    +outerWidth:a.fn.outerWidth,outerHeight:a.fn.outerHeight};a.fn["inner"+g]=function(l){if(l===d)return j["inner"+g].call(this);return this.each(function(){a(this).css(f,i(this,l)+"px")})};a.fn["outer"+g]=function(l,o){if(typeof l!=="number")return j["outer"+g].call(this,l);return this.each(function(){a(this).css(f,i(this,l,true,o)+"px")})}});a.extend(a.expr[":"],{data:function(h,g,i){return!!a.data(h,i[3])},focusable:function(h){return c(h,!isNaN(a.attr(h,"tabindex")))},tabbable:function(h){var g=a.attr(h,
    +"tabindex"),i=isNaN(g);return(i||g>=0)&&c(h,!i)}});a(function(){var h=document.body,g=h.appendChild(g=document.createElement("div"));a.extend(g.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0});a.support.minHeight=g.offsetHeight===100;a.support.selectstart="onselectstart"in g;h.removeChild(g).style.display="none"});a.extend(a.ui,{plugin:{add:function(h,g,i){h=a.ui[h].prototype;for(var b in i){h.plugins[b]=h.plugins[b]||[];h.plugins[b].push([g,i[b]])}},call:function(h,g,i){if((g=h.plugins[g])&&
    +h.element[0].parentNode)for(var b=0;b<g.length;b++)h.options[g[b][0]]&&g[b][1].apply(h.element,i)}},contains:function(h,g){return document.compareDocumentPosition?h.compareDocumentPosition(g)&16:h!==g&&h.contains(g)},hasScroll:function(h,g){if(a(h).css("overflow")==="hidden")return false;g=g&&g==="left"?"scrollLeft":"scrollTop";var i=false;if(h[g]>0)return true;h[g]=1;i=h[g]>0;h[g]=0;return i},isOverAxis:function(h,g,i){return h>g&&h<g+i},isOver:function(h,g,i,b,f,j){return a.ui.isOverAxis(h,i,f)&&
    +a.ui.isOverAxis(g,b,j)}})}})(jQuery);
    +(function(a,d){if(a.cleanData){var c=a.cleanData;a.cleanData=function(h){for(var g=0,i;(i=h[g])!=null;g++)try{a(i).triggerHandler("remove")}catch(b){}c(h)}}else{var e=a.fn.remove;a.fn.remove=function(h,g){return this.each(function(){if(!g)if(!h||a.filter(h,[this]).length)a("*",this).add([this]).each(function(){try{a(this).triggerHandler("remove")}catch(i){}});return e.call(a(this),h,g)})}}a.widget=function(h,g,i){var b=h.split(".")[0],f;h=h.split(".")[1];f=b+"-"+h;if(!i){i=g;g=a.Widget}a.expr[":"][f]=
    +function(j){return!!a.data(j,h)};a[b]=a[b]||{};a[b][h]=function(j,l){arguments.length&&this._createWidget(j,l)};g=new g;g.options=a.extend(true,{},g.options);a[b][h].prototype=a.extend(true,g,{namespace:b,widgetName:h,widgetEventPrefix:a[b][h].prototype.widgetEventPrefix||h,widgetBaseClass:f},i);a.widget.bridge(h,a[b][h])};a.widget.bridge=function(h,g){a.fn[h]=function(i){var b=typeof i==="string",f=Array.prototype.slice.call(arguments,1),j=this;i=!b&&f.length?a.extend.apply(null,[true,i].concat(f)):
    +i;if(b&&i.charAt(0)==="_")return j;b?this.each(function(){var l=a.data(this,h),o=l&&a.isFunction(l[i])?l[i].apply(l,f):l;if(o!==l&&o!==d){j=o;return false}}):this.each(function(){var l=a.data(this,h);l?l.option(i||{})._init():a.data(this,h,new g(i,this))});return j}};a.Widget=function(h,g){arguments.length&&this._createWidget(h,g)};a.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",options:{disabled:false},_createWidget:function(h,g){a.data(g,this.widgetName,this);this.element=a(g);this.options=
    +a.extend(true,{},this.options,this._getCreateOptions(),h);var i=this;this.element.bind("remove."+this.widgetName,function(){i.destroy()});this._create();this._trigger("create");this._init()},_getCreateOptions:function(){return a.metadata&&a.metadata.get(this.element[0])[this.widgetName]},_create:function(){},_init:function(){},destroy:function(){this.element.unbind("."+this.widgetName).removeData(this.widgetName);this.widget().unbind("."+this.widgetName).removeAttr("aria-disabled").removeClass(this.widgetBaseClass+
    +"-disabled ui-state-disabled")},widget:function(){return this.element},option:function(h,g){var i=h;if(arguments.length===0)return a.extend({},this.options);if(typeof h==="string"){if(g===d)return this.options[h];i={};i[h]=g}this._setOptions(i);return this},_setOptions:function(h){var g=this;a.each(h,function(i,b){g._setOption(i,b)});return this},_setOption:function(h,g){this.options[h]=g;if(h==="disabled")this.widget()[g?"addClass":"removeClass"](this.widgetBaseClass+"-disabled ui-state-disabled").attr("aria-disabled",
    +g);return this},enable:function(){return this._setOption("disabled",false)},disable:function(){return this._setOption("disabled",true)},_trigger:function(h,g,i){var b=this.options[h];g=a.Event(g);g.type=(h===this.widgetEventPrefix?h:this.widgetEventPrefix+h).toLowerCase();i=i||{};if(g.originalEvent){h=a.event.props.length;for(var f;h;){f=a.event.props[--h];g[f]=g.originalEvent[f]}}this.element.trigger(g,i);return!(a.isFunction(b)&&b.call(this.element[0],g,i)===false||g.isDefaultPrevented())}}})(jQuery);
    +(function(a){var d=false;a(document).mouseup(function(){d=false});a.widget("ui.mouse",{options:{cancel:":input,option",distance:1,delay:0},_mouseInit:function(){var c=this;this.element.bind("mousedown."+this.widgetName,function(e){return c._mouseDown(e)}).bind("click."+this.widgetName,function(e){if(true===a.data(e.target,c.widgetName+".preventClickEvent")){a.removeData(e.target,c.widgetName+".preventClickEvent");e.stopImmediatePropagation();return false}});this.started=false},_mouseDestroy:function(){this.element.unbind("."+
    +this.widgetName)},_mouseDown:function(c){if(!d){this._mouseStarted&&this._mouseUp(c);this._mouseDownEvent=c;var e=this,h=c.which==1,g=typeof this.options.cancel=="string"&&c.target.nodeName?a(c.target).closest(this.options.cancel).length:false;if(!h||g||!this._mouseCapture(c))return true;this.mouseDelayMet=!this.options.delay;if(!this.mouseDelayMet)this._mouseDelayTimer=setTimeout(function(){e.mouseDelayMet=true},this.options.delay);if(this._mouseDistanceMet(c)&&this._mouseDelayMet(c)){this._mouseStarted=
    +this._mouseStart(c)!==false;if(!this._mouseStarted){c.preventDefault();return true}}true===a.data(c.target,this.widgetName+".preventClickEvent")&&a.removeData(c.target,this.widgetName+".preventClickEvent");this._mouseMoveDelegate=function(i){return e._mouseMove(i)};this._mouseUpDelegate=function(i){return e._mouseUp(i)};a(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate);c.preventDefault();return d=true}},_mouseMove:function(c){if(a.browser.msie&&
    +!(document.documentMode>=9)&&!c.button)return this._mouseUp(c);if(this._mouseStarted){this._mouseDrag(c);return c.preventDefault()}if(this._mouseDistanceMet(c)&&this._mouseDelayMet(c))(this._mouseStarted=this._mouseStart(this._mouseDownEvent,c)!==false)?this._mouseDrag(c):this._mouseUp(c);return!this._mouseStarted},_mouseUp:function(c){a(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate);if(this._mouseStarted){this._mouseStarted=
    +false;c.target==this._mouseDownEvent.target&&a.data(c.target,this.widgetName+".preventClickEvent",true);this._mouseStop(c)}return false},_mouseDistanceMet:function(c){return Math.max(Math.abs(this._mouseDownEvent.pageX-c.pageX),Math.abs(this._mouseDownEvent.pageY-c.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return true}})})(jQuery);
    +(function(a){a.widget("ui.draggable",a.ui.mouse,{widgetEventPrefix:"drag",options:{addClasses:true,appendTo:"parent",axis:false,connectToSortable:false,containment:false,cursor:"auto",cursorAt:false,grid:false,handle:false,helper:"original",iframeFix:false,opacity:false,refreshPositions:false,revert:false,revertDuration:500,scope:"default",scroll:true,scrollSensitivity:20,scrollSpeed:20,snap:false,snapMode:"both",snapTolerance:20,stack:false,zIndex:false},_create:function(){if(this.options.helper==
    +"original"&&!/^(?:r|a|f)/.test(this.element.css("position")))this.element[0].style.position="relative";this.options.addClasses&&this.element.addClass("ui-draggable");this.options.disabled&&this.element.addClass("ui-draggable-disabled");this._mouseInit()},destroy:function(){if(this.element.data("draggable")){this.element.removeData("draggable").unbind(".draggable").removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled");this._mouseDestroy();return this}},_mouseCapture:function(d){var c=
    +this.options;if(this.helper||c.disabled||a(d.target).is(".ui-resizable-handle"))return false;this.handle=this._getHandle(d);if(!this.handle)return false;if(c.iframeFix)a(c.iframeFix===true?"iframe":c.iframeFix).each(function(){a('<div class="ui-draggable-iframeFix" style="background: #fff;"></div>').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1E3}).css(a(this).offset()).appendTo("body")});return true},_mouseStart:function(d){var c=this.options;
    +this.helper=this._createHelper(d);this._cacheHelperProportions();if(a.ui.ddmanager)a.ui.ddmanager.current=this;this._cacheMargins();this.cssPosition=this.helper.css("position");this.scrollParent=this.helper.scrollParent();this.offset=this.positionAbs=this.element.offset();this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left};a.extend(this.offset,{click:{left:d.pageX-this.offset.left,top:d.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});
    +this.originalPosition=this.position=this._generatePosition(d);this.originalPageX=d.pageX;this.originalPageY=d.pageY;c.cursorAt&&this._adjustOffsetFromHelper(c.cursorAt);c.containment&&this._setContainment();if(this._trigger("start",d)===false){this._clear();return false}this._cacheHelperProportions();a.ui.ddmanager&&!c.dropBehaviour&&a.ui.ddmanager.prepareOffsets(this,d);this.helper.addClass("ui-draggable-dragging");this._mouseDrag(d,true);a.ui.ddmanager&&a.ui.ddmanager.dragStart(this,d);return true},
    +_mouseDrag:function(d,c){this.position=this._generatePosition(d);this.positionAbs=this._convertPositionTo("absolute");if(!c){c=this._uiHash();if(this._trigger("drag",d,c)===false){this._mouseUp({});return false}this.position=c.position}if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";a.ui.ddmanager&&a.ui.ddmanager.drag(this,d);return false},_mouseStop:function(d){var c=
    +false;if(a.ui.ddmanager&&!this.options.dropBehaviour)c=a.ui.ddmanager.drop(this,d);if(this.dropped){c=this.dropped;this.dropped=false}if((!this.element[0]||!this.element[0].parentNode)&&this.options.helper=="original")return false;if(this.options.revert=="invalid"&&!c||this.options.revert=="valid"&&c||this.options.revert===true||a.isFunction(this.options.revert)&&this.options.revert.call(this.element,c)){var e=this;a(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,
    +10),function(){e._trigger("stop",d)!==false&&e._clear()})}else this._trigger("stop",d)!==false&&this._clear();return false},_mouseUp:function(d){this.options.iframeFix===true&&a("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)});a.ui.ddmanager&&a.ui.ddmanager.dragStop(this,d);return a.ui.mouse.prototype._mouseUp.call(this,d)},cancel:function(){this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear();return this},_getHandle:function(d){var c=!this.options.handle||
    +!a(this.options.handle,this.element).length?true:false;a(this.options.handle,this.element).find("*").andSelf().each(function(){if(this==d.target)c=true});return c},_createHelper:function(d){var c=this.options;d=a.isFunction(c.helper)?a(c.helper.apply(this.element[0],[d])):c.helper=="clone"?this.element.clone().removeAttr("id"):this.element;d.parents("body").length||d.appendTo(c.appendTo=="parent"?this.element[0].parentNode:c.appendTo);d[0]!=this.element[0]&&!/(fixed|absolute)/.test(d.css("position"))&&
    +d.css("position","absolute");return d},_adjustOffsetFromHelper:function(d){if(typeof d=="string")d=d.split(" ");if(a.isArray(d))d={left:+d[0],top:+d[1]||0};if("left"in d)this.offset.click.left=d.left+this.margins.left;if("right"in d)this.offset.click.left=this.helperProportions.width-d.right+this.margins.left;if("top"in d)this.offset.click.top=d.top+this.margins.top;if("bottom"in d)this.offset.click.top=this.helperProportions.height-d.bottom+this.margins.top},_getParentOffset:function(){this.offsetParent=
    +this.helper.offsetParent();var d=this.offsetParent.offset();if(this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0])){d.left+=this.scrollParent.scrollLeft();d.top+=this.scrollParent.scrollTop()}if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&a.browser.msie)d={top:0,left:0};return{top:d.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:d.left+(parseInt(this.offsetParent.css("borderLeftWidth"),
    +10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var d=this.element.position();return{top:d.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:d.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}else return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),
    +10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var d=this.options;if(d.containment=="parent")d.containment=this.helper[0].parentNode;if(d.containment=="document"||d.containment=="window")this.containment=[d.containment=="document"?0:a(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,d.containment=="document"?0:a(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,
    +(d.containment=="document"?0:a(window).scrollLeft())+a(d.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(d.containment=="document"?0:a(window).scrollTop())+(a(d.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(d.containment)&&d.containment.constructor!=Array){d=a(d.containment);var c=d[0];if(c){d.offset();var e=a(c).css("overflow")!=
    +"hidden";this.containment=[(parseInt(a(c).css("borderLeftWidth"),10)||0)+(parseInt(a(c).css("paddingLeft"),10)||0),(parseInt(a(c).css("borderTopWidth"),10)||0)+(parseInt(a(c).css("paddingTop"),10)||0),(e?Math.max(c.scrollWidth,c.offsetWidth):c.offsetWidth)-(parseInt(a(c).css("borderLeftWidth"),10)||0)-(parseInt(a(c).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(e?Math.max(c.scrollHeight,c.offsetHeight):c.offsetHeight)-(parseInt(a(c).css("borderTopWidth"),
    +10)||0)-(parseInt(a(c).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom];this.relative_container=d}}else if(d.containment.constructor==Array)this.containment=d.containment},_convertPositionTo:function(d,c){if(!c)c=this.position;d=d=="absolute"?1:-1;var e=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,h=/(html|body)/i.test(e[0].tagName);return{top:c.top+
    +this.offset.relative.top*d+this.offset.parent.top*d-(a.browser.safari&&a.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():h?0:e.scrollTop())*d),left:c.left+this.offset.relative.left*d+this.offset.parent.left*d-(a.browser.safari&&a.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():h?0:e.scrollLeft())*d)}},_generatePosition:function(d){var c=this.options,e=this.cssPosition=="absolute"&&
    +!(this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,h=/(html|body)/i.test(e[0].tagName),g=d.pageX,i=d.pageY;if(this.originalPosition){var b;if(this.containment){if(this.relative_container){b=this.relative_container.offset();b=[this.containment[0]+b.left,this.containment[1]+b.top,this.containment[2]+b.left,this.containment[3]+b.top]}else b=this.containment;if(d.pageX-this.offset.click.left<b[0])g=b[0]+this.offset.click.left;
    +if(d.pageY-this.offset.click.top<b[1])i=b[1]+this.offset.click.top;if(d.pageX-this.offset.click.left>b[2])g=b[2]+this.offset.click.left;if(d.pageY-this.offset.click.top>b[3])i=b[3]+this.offset.click.top}if(c.grid){i=c.grid[1]?this.originalPageY+Math.round((i-this.originalPageY)/c.grid[1])*c.grid[1]:this.originalPageY;i=b?!(i-this.offset.click.top<b[1]||i-this.offset.click.top>b[3])?i:!(i-this.offset.click.top<b[1])?i-c.grid[1]:i+c.grid[1]:i;g=c.grid[0]?this.originalPageX+Math.round((g-this.originalPageX)/
    +c.grid[0])*c.grid[0]:this.originalPageX;g=b?!(g-this.offset.click.left<b[0]||g-this.offset.click.left>b[2])?g:!(g-this.offset.click.left<b[0])?g-c.grid[0]:g+c.grid[0]:g}}return{top:i-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+(a.browser.safari&&a.browser.version<526&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollTop():h?0:e.scrollTop()),left:g-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+(a.browser.safari&&a.browser.version<
    +526&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():h?0:e.scrollLeft())}},_clear:function(){this.helper.removeClass("ui-draggable-dragging");this.helper[0]!=this.element[0]&&!this.cancelHelperRemoval&&this.helper.remove();this.helper=null;this.cancelHelperRemoval=false},_trigger:function(d,c,e){e=e||this._uiHash();a.ui.plugin.call(this,d,[c,e]);if(d=="drag")this.positionAbs=this._convertPositionTo("absolute");return a.Widget.prototype._trigger.call(this,d,c,
    +e)},plugins:{},_uiHash:function(){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}});a.extend(a.ui.draggable,{version:"1.8.16"});a.ui.plugin.add("draggable","connectToSortable",{start:function(d,c){var e=a(this).data("draggable"),h=e.options,g=a.extend({},c,{item:e.element});e.sortables=[];a(h.connectToSortable).each(function(){var i=a.data(this,"sortable");if(i&&!i.options.disabled){e.sortables.push({instance:i,shouldRevert:i.options.revert});
    +i.refreshPositions();i._trigger("activate",d,g)}})},stop:function(d,c){var e=a(this).data("draggable"),h=a.extend({},c,{item:e.element});a.each(e.sortables,function(){if(this.instance.isOver){this.instance.isOver=0;e.cancelHelperRemoval=true;this.instance.cancelHelperRemoval=false;if(this.shouldRevert)this.instance.options.revert=true;this.instance._mouseStop(d);this.instance.options.helper=this.instance.options._helper;e.options.helper=="original"&&this.instance.currentItem.css({top:"auto",left:"auto"})}else{this.instance.cancelHelperRemoval=
    +false;this.instance._trigger("deactivate",d,h)}})},drag:function(d,c){var e=a(this).data("draggable"),h=this;a.each(e.sortables,function(){this.instance.positionAbs=e.positionAbs;this.instance.helperProportions=e.helperProportions;this.instance.offset.click=e.offset.click;if(this.instance._intersectsWith(this.instance.containerCache)){if(!this.instance.isOver){this.instance.isOver=1;this.instance.currentItem=a(h).clone().removeAttr("id").appendTo(this.instance.element).data("sortable-item",true);
    +this.instance.options._helper=this.instance.options.helper;this.instance.options.helper=function(){return c.helper[0]};d.target=this.instance.currentItem[0];this.instance._mouseCapture(d,true);this.instance._mouseStart(d,true,true);this.instance.offset.click.top=e.offset.click.top;this.instance.offset.click.left=e.offset.click.left;this.instance.offset.parent.left-=e.offset.parent.left-this.instance.offset.parent.left;this.instance.offset.parent.top-=e.offset.parent.top-this.instance.offset.parent.top;
    +e._trigger("toSortable",d);e.dropped=this.instance.element;e.currentItem=e.element;this.instance.fromOutside=e}this.instance.currentItem&&this.instance._mouseDrag(d)}else if(this.instance.isOver){this.instance.isOver=0;this.instance.cancelHelperRemoval=true;this.instance.options.revert=false;this.instance._trigger("out",d,this.instance._uiHash(this.instance));this.instance._mouseStop(d,true);this.instance.options.helper=this.instance.options._helper;this.instance.currentItem.remove();this.instance.placeholder&&
    +this.instance.placeholder.remove();e._trigger("fromSortable",d);e.dropped=false}})}});a.ui.plugin.add("draggable","cursor",{start:function(){var d=a("body"),c=a(this).data("draggable").options;if(d.css("cursor"))c._cursor=d.css("cursor");d.css("cursor",c.cursor)},stop:function(){var d=a(this).data("draggable").options;d._cursor&&a("body").css("cursor",d._cursor)}});a.ui.plugin.add("draggable","opacity",{start:function(d,c){d=a(c.helper);c=a(this).data("draggable").options;if(d.css("opacity"))c._opacity=
    +d.css("opacity");d.css("opacity",c.opacity)},stop:function(d,c){d=a(this).data("draggable").options;d._opacity&&a(c.helper).css("opacity",d._opacity)}});a.ui.plugin.add("draggable","scroll",{start:function(){var d=a(this).data("draggable");if(d.scrollParent[0]!=document&&d.scrollParent[0].tagName!="HTML")d.overflowOffset=d.scrollParent.offset()},drag:function(d){var c=a(this).data("draggable"),e=c.options,h=false;if(c.scrollParent[0]!=document&&c.scrollParent[0].tagName!="HTML"){if(!e.axis||e.axis!=
    +"x")if(c.overflowOffset.top+c.scrollParent[0].offsetHeight-d.pageY<e.scrollSensitivity)c.scrollParent[0].scrollTop=h=c.scrollParent[0].scrollTop+e.scrollSpeed;else if(d.pageY-c.overflowOffset.top<e.scrollSensitivity)c.scrollParent[0].scrollTop=h=c.scrollParent[0].scrollTop-e.scrollSpeed;if(!e.axis||e.axis!="y")if(c.overflowOffset.left+c.scrollParent[0].offsetWidth-d.pageX<e.scrollSensitivity)c.scrollParent[0].scrollLeft=h=c.scrollParent[0].scrollLeft+e.scrollSpeed;else if(d.pageX-c.overflowOffset.left<
    +e.scrollSensitivity)c.scrollParent[0].scrollLeft=h=c.scrollParent[0].scrollLeft-e.scrollSpeed}else{if(!e.axis||e.axis!="x")if(d.pageY-a(document).scrollTop()<e.scrollSensitivity)h=a(document).scrollTop(a(document).scrollTop()-e.scrollSpeed);else if(a(window).height()-(d.pageY-a(document).scrollTop())<e.scrollSensitivity)h=a(document).scrollTop(a(document).scrollTop()+e.scrollSpeed);if(!e.axis||e.axis!="y")if(d.pageX-a(document).scrollLeft()<e.scrollSensitivity)h=a(document).scrollLeft(a(document).scrollLeft()-
    +e.scrollSpeed);else if(a(window).width()-(d.pageX-a(document).scrollLeft())<e.scrollSensitivity)h=a(document).scrollLeft(a(document).scrollLeft()+e.scrollSpeed)}h!==false&&a.ui.ddmanager&&!e.dropBehaviour&&a.ui.ddmanager.prepareOffsets(c,d)}});a.ui.plugin.add("draggable","snap",{start:function(){var d=a(this).data("draggable"),c=d.options;d.snapElements=[];a(c.snap.constructor!=String?c.snap.items||":data(draggable)":c.snap).each(function(){var e=a(this),h=e.offset();this!=d.element[0]&&d.snapElements.push({item:this,
    +width:e.outerWidth(),height:e.outerHeight(),top:h.top,left:h.left})})},drag:function(d,c){for(var e=a(this).data("draggable"),h=e.options,g=h.snapTolerance,i=c.offset.left,b=i+e.helperProportions.width,f=c.offset.top,j=f+e.helperProportions.height,l=e.snapElements.length-1;l>=0;l--){var o=e.snapElements[l].left,n=o+e.snapElements[l].width,k=e.snapElements[l].top,m=k+e.snapElements[l].height;if(o-g<i&&i<n+g&&k-g<f&&f<m+g||o-g<i&&i<n+g&&k-g<j&&j<m+g||o-g<b&&b<n+g&&k-g<f&&f<m+g||o-g<b&&b<n+g&&k-g<j&&
    +j<m+g){if(h.snapMode!="inner"){var p=Math.abs(k-j)<=g,q=Math.abs(m-f)<=g,s=Math.abs(o-b)<=g,r=Math.abs(n-i)<=g;if(p)c.position.top=e._convertPositionTo("relative",{top:k-e.helperProportions.height,left:0}).top-e.margins.top;if(q)c.position.top=e._convertPositionTo("relative",{top:m,left:0}).top-e.margins.top;if(s)c.position.left=e._convertPositionTo("relative",{top:0,left:o-e.helperProportions.width}).left-e.margins.left;if(r)c.position.left=e._convertPositionTo("relative",{top:0,left:n}).left-e.margins.left}var u=
    +p||q||s||r;if(h.snapMode!="outer"){p=Math.abs(k-f)<=g;q=Math.abs(m-j)<=g;s=Math.abs(o-i)<=g;r=Math.abs(n-b)<=g;if(p)c.position.top=e._convertPositionTo("relative",{top:k,left:0}).top-e.margins.top;if(q)c.position.top=e._convertPositionTo("relative",{top:m-e.helperProportions.height,left:0}).top-e.margins.top;if(s)c.position.left=e._convertPositionTo("relative",{top:0,left:o}).left-e.margins.left;if(r)c.position.left=e._convertPositionTo("relative",{top:0,left:n-e.helperProportions.width}).left-e.margins.left}if(!e.snapElements[l].snapping&&
    +(p||q||s||r||u))e.options.snap.snap&&e.options.snap.snap.call(e.element,d,a.extend(e._uiHash(),{snapItem:e.snapElements[l].item}));e.snapElements[l].snapping=p||q||s||r||u}else{e.snapElements[l].snapping&&e.options.snap.release&&e.options.snap.release.call(e.element,d,a.extend(e._uiHash(),{snapItem:e.snapElements[l].item}));e.snapElements[l].snapping=false}}}});a.ui.plugin.add("draggable","stack",{start:function(){var d=a(this).data("draggable").options;d=a.makeArray(a(d.stack)).sort(function(e,h){return(parseInt(a(e).css("zIndex"),
    +10)||0)-(parseInt(a(h).css("zIndex"),10)||0)});if(d.length){var c=parseInt(d[0].style.zIndex)||0;a(d).each(function(e){this.style.zIndex=c+e});this[0].style.zIndex=c+d.length}}});a.ui.plugin.add("draggable","zIndex",{start:function(d,c){d=a(c.helper);c=a(this).data("draggable").options;if(d.css("zIndex"))c._zIndex=d.css("zIndex");d.css("zIndex",c.zIndex)},stop:function(d,c){d=a(this).data("draggable").options;d._zIndex&&a(c.helper).css("zIndex",d._zIndex)}})})(jQuery);
    +(function(a){a.widget("ui.droppable",{widgetEventPrefix:"drop",options:{accept:"*",activeClass:false,addClasses:true,greedy:false,hoverClass:false,scope:"default",tolerance:"intersect"},_create:function(){var d=this.options,c=d.accept;this.isover=0;this.isout=1;this.accept=a.isFunction(c)?c:function(e){return e.is(c)};this.proportions={width:this.element[0].offsetWidth,height:this.element[0].offsetHeight};a.ui.ddmanager.droppables[d.scope]=a.ui.ddmanager.droppables[d.scope]||[];a.ui.ddmanager.droppables[d.scope].push(this);
    +d.addClasses&&this.element.addClass("ui-droppable")},destroy:function(){for(var d=a.ui.ddmanager.droppables[this.options.scope],c=0;c<d.length;c++)d[c]==this&&d.splice(c,1);this.element.removeClass("ui-droppable ui-droppable-disabled").removeData("droppable").unbind(".droppable");return this},_setOption:function(d,c){if(d=="accept")this.accept=a.isFunction(c)?c:function(e){return e.is(c)};a.Widget.prototype._setOption.apply(this,arguments)},_activate:function(d){var c=a.ui.ddmanager.current;this.options.activeClass&&
    +this.element.addClass(this.options.activeClass);c&&this._trigger("activate",d,this.ui(c))},_deactivate:function(d){var c=a.ui.ddmanager.current;this.options.activeClass&&this.element.removeClass(this.options.activeClass);c&&this._trigger("deactivate",d,this.ui(c))},_over:function(d){var c=a.ui.ddmanager.current;if(!(!c||(c.currentItem||c.element)[0]==this.element[0]))if(this.accept.call(this.element[0],c.currentItem||c.element)){this.options.hoverClass&&this.element.addClass(this.options.hoverClass);
    +this._trigger("over",d,this.ui(c))}},_out:function(d){var c=a.ui.ddmanager.current;if(!(!c||(c.currentItem||c.element)[0]==this.element[0]))if(this.accept.call(this.element[0],c.currentItem||c.element)){this.options.hoverClass&&this.element.removeClass(this.options.hoverClass);this._trigger("out",d,this.ui(c))}},_drop:function(d,c){var e=c||a.ui.ddmanager.current;if(!e||(e.currentItem||e.element)[0]==this.element[0])return false;var h=false;this.element.find(":data(droppable)").not(".ui-draggable-dragging").each(function(){var g=
    +a.data(this,"droppable");if(g.options.greedy&&!g.options.disabled&&g.options.scope==e.options.scope&&g.accept.call(g.element[0],e.currentItem||e.element)&&a.ui.intersect(e,a.extend(g,{offset:g.element.offset()}),g.options.tolerance)){h=true;return false}});if(h)return false;if(this.accept.call(this.element[0],e.currentItem||e.element)){this.options.activeClass&&this.element.removeClass(this.options.activeClass);this.options.hoverClass&&this.element.removeClass(this.options.hoverClass);this._trigger("drop",
    +d,this.ui(e));return this.element}return false},ui:function(d){return{draggable:d.currentItem||d.element,helper:d.helper,position:d.position,offset:d.positionAbs}}});a.extend(a.ui.droppable,{version:"1.8.16"});a.ui.intersect=function(d,c,e){if(!c.offset)return false;var h=(d.positionAbs||d.position.absolute).left,g=h+d.helperProportions.width,i=(d.positionAbs||d.position.absolute).top,b=i+d.helperProportions.height,f=c.offset.left,j=f+c.proportions.width,l=c.offset.top,o=l+c.proportions.height;
    +switch(e){case "fit":return f<=h&&g<=j&&l<=i&&b<=o;case "intersect":return f<h+d.helperProportions.width/2&&g-d.helperProportions.width/2<j&&l<i+d.helperProportions.height/2&&b-d.helperProportions.height/2<o;case "pointer":return a.ui.isOver((d.positionAbs||d.position.absolute).top+(d.clickOffset||d.offset.click).top,(d.positionAbs||d.position.absolute).left+(d.clickOffset||d.offset.click).left,l,f,c.proportions.height,c.proportions.width);case "touch":return(i>=l&&i<=o||b>=l&&b<=o||i<l&&b>o)&&(h>=
    +f&&h<=j||g>=f&&g<=j||h<f&&g>j);default:return false}};a.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(d,c){var e=a.ui.ddmanager.droppables[d.options.scope]||[],h=c?c.type:null,g=(d.currentItem||d.element).find(":data(droppable)").andSelf(),i=0;a:for(;i<e.length;i++)if(!(e[i].options.disabled||d&&!e[i].accept.call(e[i].element[0],d.currentItem||d.element))){for(var b=0;b<g.length;b++)if(g[b]==e[i].element[0]){e[i].proportions.height=0;continue a}e[i].visible=e[i].element.css("display")!=
    +"none";if(e[i].visible){h=="mousedown"&&e[i]._activate.call(e[i],c);e[i].offset=e[i].element.offset();e[i].proportions={width:e[i].element[0].offsetWidth,height:e[i].element[0].offsetHeight}}}},drop:function(d,c){var e=false;a.each(a.ui.ddmanager.droppables[d.options.scope]||[],function(){if(this.options){if(!this.options.disabled&&this.visible&&a.ui.intersect(d,this,this.options.tolerance))e=e||this._drop.call(this,c);if(!this.options.disabled&&this.visible&&this.accept.call(this.element[0],d.currentItem||
    +d.element)){this.isout=1;this.isover=0;this._deactivate.call(this,c)}}});return e},dragStart:function(d,c){d.element.parents(":not(body,html)").bind("scroll.droppable",function(){d.options.refreshPositions||a.ui.ddmanager.prepareOffsets(d,c)})},drag:function(d,c){d.options.refreshPositions&&a.ui.ddmanager.prepareOffsets(d,c);a.each(a.ui.ddmanager.droppables[d.options.scope]||[],function(){if(!(this.options.disabled||this.greedyChild||!this.visible)){var e=a.ui.intersect(d,this,this.options.tolerance);
    +if(e=!e&&this.isover==1?"isout":e&&this.isover==0?"isover":null){var h;if(this.options.greedy){var g=this.element.parents(":data(droppable):eq(0)");if(g.length){h=a.data(g[0],"droppable");h.greedyChild=e=="isover"?1:0}}if(h&&e=="isover"){h.isover=0;h.isout=1;h._out.call(h,c)}this[e]=1;this[e=="isout"?"isover":"isout"]=0;this[e=="isover"?"_over":"_out"].call(this,c);if(h&&e=="isout"){h.isout=0;h.isover=1;h._over.call(h,c)}}}})},dragStop:function(d,c){d.element.parents(":not(body,html)").unbind("scroll.droppable");
    +d.options.refreshPositions||a.ui.ddmanager.prepareOffsets(d,c)}}})(jQuery);
    +(function(a){a.widget("ui.resizable",a.ui.mouse,{widgetEventPrefix:"resize",options:{alsoResize:false,animate:false,animateDuration:"slow",animateEasing:"swing",aspectRatio:false,autoHide:false,containment:false,ghost:false,grid:false,handles:"e,s,se",helper:false,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:1E3},_create:function(){var e=this,h=this.options;this.element.addClass("ui-resizable");a.extend(this,{_aspectRatio:!!h.aspectRatio,aspectRatio:h.aspectRatio,originalElement:this.element,
    +_proportionallyResizeElements:[],_helper:h.helper||h.ghost||h.animate?h.helper||"ui-resizable-helper":null});if(this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)){/relative/.test(this.element.css("position"))&&a.browser.opera&&this.element.css({position:"relative",top:"auto",left:"auto"});this.element.wrap(a('<div class="ui-wrapper" style="overflow: hidden;"></div>').css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),
    +top:this.element.css("top"),left:this.element.css("left")}));this.element=this.element.parent().data("resizable",this.element.data("resizable"));this.elementIsWrapper=true;this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")});this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0});this.originalResizeStyle=
    +this.originalElement.css("resize");this.originalElement.css("resize","none");this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"}));this.originalElement.css({margin:this.originalElement.css("margin")});this._proportionallyResize()}this.handles=h.handles||(!a(".ui-resizable-handle",this.element).length?"e,s,se":{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",
    +nw:".ui-resizable-nw"});if(this.handles.constructor==String){if(this.handles=="all")this.handles="n,e,s,w,se,sw,ne,nw";var g=this.handles.split(",");this.handles={};for(var i=0;i<g.length;i++){var b=a.trim(g[i]),f=a('<div class="ui-resizable-handle '+("ui-resizable-"+b)+'"></div>');/sw|se|ne|nw/.test(b)&&f.css({zIndex:++h.zIndex});"se"==b&&f.addClass("ui-icon ui-icon-gripsmall-diagonal-se");this.handles[b]=".ui-resizable-"+b;this.element.append(f)}}this._renderAxis=function(j){j=j||this.element;for(var l in this.handles){if(this.handles[l].constructor==
    +String)this.handles[l]=a(this.handles[l],this.element).show();if(this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)){var o=a(this.handles[l],this.element),n=0;n=/sw|ne|nw|se|n|s/.test(l)?o.outerHeight():o.outerWidth();o=["padding",/ne|nw|n/.test(l)?"Top":/se|sw|s/.test(l)?"Bottom":/^e$/.test(l)?"Right":"Left"].join("");j.css(o,n);this._proportionallyResize()}a(this.handles[l])}};this._renderAxis(this.element);this._handles=a(".ui-resizable-handle",this.element).disableSelection();
    +this._handles.mouseover(function(){if(!e.resizing){if(this.className)var j=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);e.axis=j&&j[1]?j[1]:"se"}});if(h.autoHide){this._handles.hide();a(this.element).addClass("ui-resizable-autohide").hover(function(){if(!h.disabled){a(this).removeClass("ui-resizable-autohide");e._handles.show()}},function(){if(!h.disabled)if(!e.resizing){a(this).addClass("ui-resizable-autohide");e._handles.hide()}})}this._mouseInit()},destroy:function(){this._mouseDestroy();
    +var e=function(g){a(g).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};if(this.elementIsWrapper){e(this.element);var h=this.element;h.after(this.originalElement.css({position:h.css("position"),width:h.outerWidth(),height:h.outerHeight(),top:h.css("top"),left:h.css("left")})).remove()}this.originalElement.css("resize",this.originalResizeStyle);e(this.originalElement);return this},_mouseCapture:function(e){var h=
    +false;for(var g in this.handles)if(a(this.handles[g])[0]==e.target)h=true;return!this.options.disabled&&h},_mouseStart:function(e){var h=this.options,g=this.element.position(),i=this.element;this.resizing=true;this.documentScroll={top:a(document).scrollTop(),left:a(document).scrollLeft()};if(i.is(".ui-draggable")||/absolute/.test(i.css("position")))i.css({position:"absolute",top:g.top,left:g.left});a.browser.opera&&/relative/.test(i.css("position"))&&i.css({position:"relative",top:"auto",left:"auto"});
    +this._renderProxy();g=d(this.helper.css("left"));var b=d(this.helper.css("top"));if(h.containment){g+=a(h.containment).scrollLeft()||0;b+=a(h.containment).scrollTop()||0}this.offset=this.helper.offset();this.position={left:g,top:b};this.size=this._helper?{width:i.outerWidth(),height:i.outerHeight()}:{width:i.width(),height:i.height()};this.originalSize=this._helper?{width:i.outerWidth(),height:i.outerHeight()}:{width:i.width(),height:i.height()};this.originalPosition={left:g,top:b};this.sizeDiff=
    +{width:i.outerWidth()-i.width(),height:i.outerHeight()-i.height()};this.originalMousePosition={left:e.pageX,top:e.pageY};this.aspectRatio=typeof h.aspectRatio=="number"?h.aspectRatio:this.originalSize.width/this.originalSize.height||1;h=a(".ui-resizable-"+this.axis).css("cursor");a("body").css("cursor",h=="auto"?this.axis+"-resize":h);i.addClass("ui-resizable-resizing");this._propagate("start",e);return true},_mouseDrag:function(e){var h=this.helper,g=this.originalMousePosition,i=this._change[this.axis];
    +if(!i)return false;g=i.apply(this,[e,e.pageX-g.left||0,e.pageY-g.top||0]);this._updateVirtualBoundaries(e.shiftKey);if(this._aspectRatio||e.shiftKey)g=this._updateRatio(g,e);g=this._respectSize(g,e);this._propagate("resize",e);h.css({top:this.position.top+"px",left:this.position.left+"px",width:this.size.width+"px",height:this.size.height+"px"});!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize();this._updateCache(g);this._trigger("resize",e,this.ui());return false},
    +_mouseStop:function(e){this.resizing=false;var h=this.options,g=this;if(this._helper){var i=this._proportionallyResizeElements,b=i.length&&/textarea/i.test(i[0].nodeName);i=b&&a.ui.hasScroll(i[0],"left")?0:g.sizeDiff.height;b=b?0:g.sizeDiff.width;b={width:g.helper.width()-b,height:g.helper.height()-i};i=parseInt(g.element.css("left"),10)+(g.position.left-g.originalPosition.left)||null;var f=parseInt(g.element.css("top"),10)+(g.position.top-g.originalPosition.top)||null;h.animate||this.element.css(a.extend(b,
    +{top:f,left:i}));g.helper.height(g.size.height);g.helper.width(g.size.width);this._helper&&!h.animate&&this._proportionallyResize()}a("body").css("cursor","auto");this.element.removeClass("ui-resizable-resizing");this._propagate("stop",e);this._helper&&this.helper.remove();return false},_updateVirtualBoundaries:function(e){var h=this.options,g,i,b;h={minWidth:c(h.minWidth)?h.minWidth:0,maxWidth:c(h.maxWidth)?h.maxWidth:Infinity,minHeight:c(h.minHeight)?h.minHeight:0,maxHeight:c(h.maxHeight)?h.maxHeight:
    +Infinity};if(this._aspectRatio||e){e=h.minHeight*this.aspectRatio;i=h.minWidth/this.aspectRatio;g=h.maxHeight*this.aspectRatio;b=h.maxWidth/this.aspectRatio;if(e>h.minWidth)h.minWidth=e;if(i>h.minHeight)h.minHeight=i;if(g<h.maxWidth)h.maxWidth=g;if(b<h.maxHeight)h.maxHeight=b}this._vBoundaries=h},_updateCache:function(e){this.offset=this.helper.offset();if(c(e.left))this.position.left=e.left;if(c(e.top))this.position.top=e.top;if(c(e.height))this.size.height=e.height;if(c(e.width))this.size.width=
    +e.width},_updateRatio:function(e){var h=this.position,g=this.size,i=this.axis;if(c(e.height))e.width=e.height*this.aspectRatio;else if(c(e.width))e.height=e.width/this.aspectRatio;if(i=="sw"){e.left=h.left+(g.width-e.width);e.top=null}if(i=="nw"){e.top=h.top+(g.height-e.height);e.left=h.left+(g.width-e.width)}return e},_respectSize:function(e){var h=this._vBoundaries,g=this.axis,i=c(e.width)&&h.maxWidth&&h.maxWidth<e.width,b=c(e.height)&&h.maxHeight&&h.maxHeight<e.height,f=c(e.width)&&h.minWidth&&
    +h.minWidth>e.width,j=c(e.height)&&h.minHeight&&h.minHeight>e.height;if(f)e.width=h.minWidth;if(j)e.height=h.minHeight;if(i)e.width=h.maxWidth;if(b)e.height=h.maxHeight;var l=this.originalPosition.left+this.originalSize.width,o=this.position.top+this.size.height,n=/sw|nw|w/.test(g);g=/nw|ne|n/.test(g);if(f&&n)e.left=l-h.minWidth;if(i&&n)e.left=l-h.maxWidth;if(j&&g)e.top=o-h.minHeight;if(b&&g)e.top=o-h.maxHeight;if((h=!e.width&&!e.height)&&!e.left&&e.top)e.top=null;else if(h&&!e.top&&e.left)e.left=
    +null;return e},_proportionallyResize:function(){if(this._proportionallyResizeElements.length)for(var e=this.helper||this.element,h=0;h<this._proportionallyResizeElements.length;h++){var g=this._proportionallyResizeElements[h];if(!this.borderDif){var i=[g.css("borderTopWidth"),g.css("borderRightWidth"),g.css("borderBottomWidth"),g.css("borderLeftWidth")],b=[g.css("paddingTop"),g.css("paddingRight"),g.css("paddingBottom"),g.css("paddingLeft")];this.borderDif=a.map(i,function(f,j){f=parseInt(f,10)||
    +0;j=parseInt(b[j],10)||0;return f+j})}a.browser.msie&&(a(e).is(":hidden")||a(e).parents(":hidden").length)||g.css({height:e.height()-this.borderDif[0]-this.borderDif[2]||0,width:e.width()-this.borderDif[1]-this.borderDif[3]||0})}},_renderProxy:function(){var e=this.options;this.elementOffset=this.element.offset();if(this._helper){this.helper=this.helper||a('<div style="overflow:hidden;"></div>');var h=a.browser.msie&&a.browser.version<7,g=h?1:0;h=h?2:-1;this.helper.addClass(this._helper).css({width:this.element.outerWidth()+
    +h,height:this.element.outerHeight()+h,position:"absolute",left:this.elementOffset.left-g+"px",top:this.elementOffset.top-g+"px",zIndex:++e.zIndex});this.helper.appendTo("body").disableSelection()}else this.helper=this.element},_change:{e:function(e,h){return{width:this.originalSize.width+h}},w:function(e,h){return{left:this.originalPosition.left+h,width:this.originalSize.width-h}},n:function(e,h,g){return{top:this.originalPosition.top+g,height:this.originalSize.height-g}},s:function(e,h,g){return{height:this.originalSize.height+
    +g}},se:function(e,h,g){return a.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[e,h,g]))},sw:function(e,h,g){return a.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[e,h,g]))},ne:function(e,h,g){return a.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[e,h,g]))},nw:function(e,h,g){return a.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[e,h,g]))}},_propagate:function(e,h){a.ui.plugin.call(this,e,[h,this.ui()]);
    +e!="resize"&&this._trigger(e,h,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}});a.extend(a.ui.resizable,{version:"1.8.16"});a.ui.plugin.add("resizable","alsoResize",{start:function(){var e=a(this).data("resizable").options,h=function(g){a(g).each(function(){var i=a(this);i.data("resizable-alsoresize",{width:parseInt(i.width(),
    +10),height:parseInt(i.height(),10),left:parseInt(i.css("left"),10),top:parseInt(i.css("top"),10),position:i.css("position")})})};if(typeof e.alsoResize=="object"&&!e.alsoResize.parentNode)if(e.alsoResize.length){e.alsoResize=e.alsoResize[0];h(e.alsoResize)}else a.each(e.alsoResize,function(g){h(g)});else h(e.alsoResize)},resize:function(e,h){var g=a(this).data("resizable");e=g.options;var i=g.originalSize,b=g.originalPosition,f={height:g.size.height-i.height||0,width:g.size.width-i.width||0,top:g.position.top-
    +b.top||0,left:g.position.left-b.left||0},j=function(l,o){a(l).each(function(){var n=a(this),k=a(this).data("resizable-alsoresize"),m={},p=o&&o.length?o:n.parents(h.originalElement[0]).length?["width","height"]:["width","height","top","left"];a.each(p,function(q,s){if((q=(k[s]||0)+(f[s]||0))&&q>=0)m[s]=q||null});if(a.browser.opera&&/relative/.test(n.css("position"))){g._revertToRelativePosition=true;n.css({position:"absolute",top:"auto",left:"auto"})}n.css(m)})};typeof e.alsoResize=="object"&&!e.alsoResize.nodeType?
    +a.each(e.alsoResize,function(l,o){j(l,o)}):j(e.alsoResize)},stop:function(){var e=a(this).data("resizable"),h=e.options,g=function(i){a(i).each(function(){var b=a(this);b.css({position:b.data("resizable-alsoresize").position})})};if(e._revertToRelativePosition){e._revertToRelativePosition=false;typeof h.alsoResize=="object"&&!h.alsoResize.nodeType?a.each(h.alsoResize,function(i){g(i)}):g(h.alsoResize)}a(this).removeData("resizable-alsoresize")}});a.ui.plugin.add("resizable","animate",{stop:function(e){var h=
    +a(this).data("resizable"),g=h.options,i=h._proportionallyResizeElements,b=i.length&&/textarea/i.test(i[0].nodeName),f=b&&a.ui.hasScroll(i[0],"left")?0:h.sizeDiff.height;b={width:h.size.width-(b?0:h.sizeDiff.width),height:h.size.height-f};f=parseInt(h.element.css("left"),10)+(h.position.left-h.originalPosition.left)||null;var j=parseInt(h.element.css("top"),10)+(h.position.top-h.originalPosition.top)||null;h.element.animate(a.extend(b,j&&f?{top:j,left:f}:{}),{duration:g.animateDuration,easing:g.animateEasing,
    +step:function(){var l={width:parseInt(h.element.css("width"),10),height:parseInt(h.element.css("height"),10),top:parseInt(h.element.css("top"),10),left:parseInt(h.element.css("left"),10)};i&&i.length&&a(i[0]).css({width:l.width,height:l.height});h._updateCache(l);h._propagate("resize",e)}})}});a.ui.plugin.add("resizable","containment",{start:function(){var e=a(this).data("resizable"),h=e.element,g=e.options.containment;if(h=g instanceof a?g.get(0):/parent/.test(g)?h.parent().get(0):g){e.containerElement=
    +a(h);if(/document/.test(g)||g==document){e.containerOffset={left:0,top:0};e.containerPosition={left:0,top:0};e.parentData={element:a(document),left:0,top:0,width:a(document).width(),height:a(document).height()||document.body.parentNode.scrollHeight}}else{var i=a(h),b=[];a(["Top","Right","Left","Bottom"]).each(function(l,o){b[l]=d(i.css("padding"+o))});e.containerOffset=i.offset();e.containerPosition=i.position();e.containerSize={height:i.innerHeight()-b[3],width:i.innerWidth()-b[1]};g=e.containerOffset;
    +var f=e.containerSize.height,j=e.containerSize.width;j=a.ui.hasScroll(h,"left")?h.scrollWidth:j;f=a.ui.hasScroll(h)?h.scrollHeight:f;e.parentData={element:h,left:g.left,top:g.top,width:j,height:f}}}},resize:function(e){var h=a(this).data("resizable"),g=h.options,i=h.containerOffset,b=h.position;e=h._aspectRatio||e.shiftKey;var f={top:0,left:0},j=h.containerElement;if(j[0]!=document&&/static/.test(j.css("position")))f=i;if(b.left<(h._helper?i.left:0)){h.size.width+=h._helper?h.position.left-i.left:
    +h.position.left-f.left;if(e)h.size.height=h.size.width/g.aspectRatio;h.position.left=g.helper?i.left:0}if(b.top<(h._helper?i.top:0)){h.size.height+=h._helper?h.position.top-i.top:h.position.top;if(e)h.size.width=h.size.height*g.aspectRatio;h.position.top=h._helper?i.top:0}h.offset.left=h.parentData.left+h.position.left;h.offset.top=h.parentData.top+h.position.top;g=Math.abs((h._helper?h.offset.left-f.left:h.offset.left-f.left)+h.sizeDiff.width);i=Math.abs((h._helper?h.offset.top-f.top:h.offset.top-
    +i.top)+h.sizeDiff.height);b=h.containerElement.get(0)==h.element.parent().get(0);f=/relative|absolute/.test(h.containerElement.css("position"));if(b&&f)g-=h.parentData.left;if(g+h.size.width>=h.parentData.width){h.size.width=h.parentData.width-g;if(e)h.size.height=h.size.width/h.aspectRatio}if(i+h.size.height>=h.parentData.height){h.size.height=h.parentData.height-i;if(e)h.size.width=h.size.height*h.aspectRatio}},stop:function(){var e=a(this).data("resizable"),h=e.options,g=e.containerOffset,i=e.containerPosition,
    +b=e.containerElement,f=a(e.helper),j=f.offset(),l=f.outerWidth()-e.sizeDiff.width;f=f.outerHeight()-e.sizeDiff.height;e._helper&&!h.animate&&/relative/.test(b.css("position"))&&a(this).css({left:j.left-i.left-g.left,width:l,height:f});e._helper&&!h.animate&&/static/.test(b.css("position"))&&a(this).css({left:j.left-i.left-g.left,width:l,height:f})}});a.ui.plugin.add("resizable","ghost",{start:function(){var e=a(this).data("resizable"),h=e.options,g=e.size;e.ghost=e.originalElement.clone();e.ghost.css({opacity:0.25,
    +display:"block",position:"relative",height:g.height,width:g.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass(typeof h.ghost=="string"?h.ghost:"");e.ghost.appendTo(e.helper)},resize:function(){var e=a(this).data("resizable");e.ghost&&e.ghost.css({position:"relative",height:e.size.height,width:e.size.width})},stop:function(){var e=a(this).data("resizable");e.ghost&&e.helper&&e.helper.get(0).removeChild(e.ghost.get(0))}});a.ui.plugin.add("resizable","grid",{resize:function(){var e=
    +a(this).data("resizable"),h=e.options,g=e.size,i=e.originalSize,b=e.originalPosition,f=e.axis;h.grid=typeof h.grid=="number"?[h.grid,h.grid]:h.grid;var j=Math.round((g.width-i.width)/(h.grid[0]||1))*(h.grid[0]||1);h=Math.round((g.height-i.height)/(h.grid[1]||1))*(h.grid[1]||1);if(/^(se|s|e)$/.test(f)){e.size.width=i.width+j;e.size.height=i.height+h}else if(/^(ne)$/.test(f)){e.size.width=i.width+j;e.size.height=i.height+h;e.position.top=b.top-h}else{if(/^(sw)$/.test(f)){e.size.width=i.width+j;e.size.height=
    +i.height+h}else{e.size.width=i.width+j;e.size.height=i.height+h;e.position.top=b.top-h}e.position.left=b.left-j}}});var d=function(e){return parseInt(e,10)||0},c=function(e){return!isNaN(parseInt(e,10))}})(jQuery);
    +(function(a){a.widget("ui.selectable",a.ui.mouse,{options:{appendTo:"body",autoRefresh:true,distance:0,filter:"*",tolerance:"touch"},_create:function(){var d=this;this.element.addClass("ui-selectable");this.dragged=false;var c;this.refresh=function(){c=a(d.options.filter,d.element[0]);c.each(function(){var e=a(this),h=e.offset();a.data(this,"selectable-item",{element:this,$element:e,left:h.left,top:h.top,right:h.left+e.outerWidth(),bottom:h.top+e.outerHeight(),startselected:false,selected:e.hasClass("ui-selected"),
    +selecting:e.hasClass("ui-selecting"),unselecting:e.hasClass("ui-unselecting")})})};this.refresh();this.selectees=c.addClass("ui-selectee");this._mouseInit();this.helper=a("<div class='ui-selectable-helper'></div>")},destroy:function(){this.selectees.removeClass("ui-selectee").removeData("selectable-item");this.element.removeClass("ui-selectable ui-selectable-disabled").removeData("selectable").unbind(".selectable");this._mouseDestroy();return this},_mouseStart:function(d){var c=this;this.opos=[d.pageX,
    +d.pageY];if(!this.options.disabled){var e=this.options;this.selectees=a(e.filter,this.element[0]);this._trigger("start",d);a(e.appendTo).append(this.helper);this.helper.css({left:d.clientX,top:d.clientY,width:0,height:0});e.autoRefresh&&this.refresh();this.selectees.filter(".ui-selected").each(function(){var h=a.data(this,"selectable-item");h.startselected=true;if(!d.metaKey){h.$element.removeClass("ui-selected");h.selected=false;h.$element.addClass("ui-unselecting");h.unselecting=true;c._trigger("unselecting",
    +d,{unselecting:h.element})}});a(d.target).parents().andSelf().each(function(){var h=a.data(this,"selectable-item");if(h){var g=!d.metaKey||!h.$element.hasClass("ui-selected");h.$element.removeClass(g?"ui-unselecting":"ui-selected").addClass(g?"ui-selecting":"ui-unselecting");h.unselecting=!g;h.selecting=g;(h.selected=g)?c._trigger("selecting",d,{selecting:h.element}):c._trigger("unselecting",d,{unselecting:h.element});return false}})}},_mouseDrag:function(d){var c=this;this.dragged=true;if(!this.options.disabled){var e=
    +this.options,h=this.opos[0],g=this.opos[1],i=d.pageX,b=d.pageY;if(h>i){var f=i;i=h;h=f}if(g>b){f=b;b=g;g=f}this.helper.css({left:h,top:g,width:i-h,height:b-g});this.selectees.each(function(){var j=a.data(this,"selectable-item");if(!(!j||j.element==c.element[0])){var l=false;if(e.tolerance=="touch")l=!(j.left>i||j.right<h||j.top>b||j.bottom<g);else if(e.tolerance=="fit")l=j.left>h&&j.right<i&&j.top>g&&j.bottom<b;if(l){if(j.selected){j.$element.removeClass("ui-selected");j.selected=false}if(j.unselecting){j.$element.removeClass("ui-unselecting");
    +j.unselecting=false}if(!j.selecting){j.$element.addClass("ui-selecting");j.selecting=true;c._trigger("selecting",d,{selecting:j.element})}}else{if(j.selecting)if(d.metaKey&&j.startselected){j.$element.removeClass("ui-selecting");j.selecting=false;j.$element.addClass("ui-selected");j.selected=true}else{j.$element.removeClass("ui-selecting");j.selecting=false;if(j.startselected){j.$element.addClass("ui-unselecting");j.unselecting=true}c._trigger("unselecting",d,{unselecting:j.element})}if(j.selected)if(!d.metaKey&&
    +!j.startselected){j.$element.removeClass("ui-selected");j.selected=false;j.$element.addClass("ui-unselecting");j.unselecting=true;c._trigger("unselecting",d,{unselecting:j.element})}}}});return false}},_mouseStop:function(d){var c=this;this.dragged=false;a(".ui-unselecting",this.element[0]).each(function(){var e=a.data(this,"selectable-item");e.$element.removeClass("ui-unselecting");e.unselecting=false;e.startselected=false;c._trigger("unselected",d,{unselected:e.element})});a(".ui-selecting",this.element[0]).each(function(){var e=
    +a.data(this,"selectable-item");e.$element.removeClass("ui-selecting").addClass("ui-selected");e.selecting=false;e.selected=true;e.startselected=true;c._trigger("selected",d,{selected:e.element})});this._trigger("stop",d);this.helper.remove();return false}});a.extend(a.ui.selectable,{version:"1.8.16"})})(jQuery);
    +(function(a){a.widget("ui.sortable",a.ui.mouse,{widgetEventPrefix:"sort",options:{appendTo:"parent",axis:false,connectWith:false,containment:false,cursor:"auto",cursorAt:false,dropOnEmpty:true,forcePlaceholderSize:false,forceHelperSize:false,grid:false,handle:false,helper:"original",items:"> *",opacity:false,placeholder:false,revert:false,scroll:true,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1E3},_create:function(){var d=this.options;this.containerCache={};this.element.addClass("ui-sortable");
    +this.refresh();this.floating=this.items.length?d.axis==="x"||/left|right/.test(this.items[0].item.css("float"))||/inline|table-cell/.test(this.items[0].item.css("display")):false;this.offset=this.element.offset();this._mouseInit()},destroy:function(){this.element.removeClass("ui-sortable ui-sortable-disabled").removeData("sortable").unbind(".sortable");this._mouseDestroy();for(var d=this.items.length-1;d>=0;d--)this.items[d].item.removeData("sortable-item");return this},_setOption:function(d,c){if(d===
    +"disabled"){this.options[d]=c;this.widget()[c?"addClass":"removeClass"]("ui-sortable-disabled")}else a.Widget.prototype._setOption.apply(this,arguments)},_mouseCapture:function(d,c){if(this.reverting)return false;if(this.options.disabled||this.options.type=="static")return false;this._refreshItems(d);var e=null,h=this;a(d.target).parents().each(function(){if(a.data(this,"sortable-item")==h){e=a(this);return false}});if(a.data(d.target,"sortable-item")==h)e=a(d.target);if(!e)return false;if(this.options.handle&&
    +!c){var g=false;a(this.options.handle,e).find("*").andSelf().each(function(){if(this==d.target)g=true});if(!g)return false}this.currentItem=e;this._removeCurrentsFromItems();return true},_mouseStart:function(d,c,e){c=this.options;var h=this;this.currentContainer=this;this.refreshPositions();this.helper=this._createHelper(d);this._cacheHelperProportions();this._cacheMargins();this.scrollParent=this.helper.scrollParent();this.offset=this.currentItem.offset();this.offset={top:this.offset.top-this.margins.top,
    +left:this.offset.left-this.margins.left};this.helper.css("position","absolute");this.cssPosition=this.helper.css("position");a.extend(this.offset,{click:{left:d.pageX-this.offset.left,top:d.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});this.originalPosition=this._generatePosition(d);this.originalPageX=d.pageX;this.originalPageY=d.pageY;c.cursorAt&&this._adjustOffsetFromHelper(c.cursorAt);this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]};
    +this.helper[0]!=this.currentItem[0]&&this.currentItem.hide();this._createPlaceholder();c.containment&&this._setContainment();if(c.cursor){if(a("body").css("cursor"))this._storedCursor=a("body").css("cursor");a("body").css("cursor",c.cursor)}if(c.opacity){if(this.helper.css("opacity"))this._storedOpacity=this.helper.css("opacity");this.helper.css("opacity",c.opacity)}if(c.zIndex){if(this.helper.css("zIndex"))this._storedZIndex=this.helper.css("zIndex");this.helper.css("zIndex",c.zIndex)}if(this.scrollParent[0]!=
    +document&&this.scrollParent[0].tagName!="HTML")this.overflowOffset=this.scrollParent.offset();this._trigger("start",d,this._uiHash());this._preserveHelperProportions||this._cacheHelperProportions();if(!e)for(e=this.containers.length-1;e>=0;e--)this.containers[e]._trigger("activate",d,h._uiHash(this));if(a.ui.ddmanager)a.ui.ddmanager.current=this;a.ui.ddmanager&&!c.dropBehaviour&&a.ui.ddmanager.prepareOffsets(this,d);this.dragging=true;this.helper.addClass("ui-sortable-helper");this._mouseDrag(d);
    +return true},_mouseDrag:function(d){this.position=this._generatePosition(d);this.positionAbs=this._convertPositionTo("absolute");if(!this.lastPositionAbs)this.lastPositionAbs=this.positionAbs;if(this.options.scroll){var c=this.options,e=false;if(this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"){if(this.overflowOffset.top+this.scrollParent[0].offsetHeight-d.pageY<c.scrollSensitivity)this.scrollParent[0].scrollTop=e=this.scrollParent[0].scrollTop+c.scrollSpeed;else if(d.pageY-this.overflowOffset.top<
    +c.scrollSensitivity)this.scrollParent[0].scrollTop=e=this.scrollParent[0].scrollTop-c.scrollSpeed;if(this.overflowOffset.left+this.scrollParent[0].offsetWidth-d.pageX<c.scrollSensitivity)this.scrollParent[0].scrollLeft=e=this.scrollParent[0].scrollLeft+c.scrollSpeed;else if(d.pageX-this.overflowOffset.left<c.scrollSensitivity)this.scrollParent[0].scrollLeft=e=this.scrollParent[0].scrollLeft-c.scrollSpeed}else{if(d.pageY-a(document).scrollTop()<c.scrollSensitivity)e=a(document).scrollTop(a(document).scrollTop()-
    +c.scrollSpeed);else if(a(window).height()-(d.pageY-a(document).scrollTop())<c.scrollSensitivity)e=a(document).scrollTop(a(document).scrollTop()+c.scrollSpeed);if(d.pageX-a(document).scrollLeft()<c.scrollSensitivity)e=a(document).scrollLeft(a(document).scrollLeft()-c.scrollSpeed);else if(a(window).width()-(d.pageX-a(document).scrollLeft())<c.scrollSensitivity)e=a(document).scrollLeft(a(document).scrollLeft()+c.scrollSpeed)}e!==false&&a.ui.ddmanager&&!c.dropBehaviour&&a.ui.ddmanager.prepareOffsets(this,
    +d)}this.positionAbs=this._convertPositionTo("absolute");if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";for(c=this.items.length-1;c>=0;c--){e=this.items[c];var h=e.item[0],g=this._intersectsWithPointer(e);if(g)if(h!=this.currentItem[0]&&this.placeholder[g==1?"next":"prev"]()[0]!=h&&!a.ui.contains(this.placeholder[0],h)&&(this.options.type=="semi-dynamic"?!a.ui.contains(this.element[0],
    +h):true)){this.direction=g==1?"down":"up";if(this.options.tolerance=="pointer"||this._intersectsWithSides(e))this._rearrange(d,e);else break;this._trigger("change",d,this._uiHash());break}}this._contactContainers(d);a.ui.ddmanager&&a.ui.ddmanager.drag(this,d);this._trigger("sort",d,this._uiHash());this.lastPositionAbs=this.positionAbs;return false},_mouseStop:function(d,c){if(d){a.ui.ddmanager&&!this.options.dropBehaviour&&a.ui.ddmanager.drop(this,d);if(this.options.revert){var e=this;c=e.placeholder.offset();
    +e.reverting=true;a(this.helper).animate({left:c.left-this.offset.parent.left-e.margins.left+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollLeft),top:c.top-this.offset.parent.top-e.margins.top+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollTop)},parseInt(this.options.revert,10)||500,function(){e._clear(d)})}else this._clear(d,c);return false}},cancel:function(){var d=this;if(this.dragging){this._mouseUp({target:null});this.options.helper=="original"?this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"):
    +this.currentItem.show();for(var c=this.containers.length-1;c>=0;c--){this.containers[c]._trigger("deactivate",null,d._uiHash(this));if(this.containers[c].containerCache.over){this.containers[c]._trigger("out",null,d._uiHash(this));this.containers[c].containerCache.over=0}}}if(this.placeholder){this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]);this.options.helper!="original"&&this.helper&&this.helper[0].parentNode&&this.helper.remove();a.extend(this,{helper:null,
    +dragging:false,reverting:false,_noFinalSort:null});this.domPosition.prev?a(this.domPosition.prev).after(this.currentItem):a(this.domPosition.parent).prepend(this.currentItem)}return this},serialize:function(d){var c=this._getItemsAsjQuery(d&&d.connected),e=[];d=d||{};a(c).each(function(){var h=(a(d.item||this).attr(d.attribute||"id")||"").match(d.expression||/(.+)[-=_](.+)/);if(h)e.push((d.key||h[1]+"[]")+"="+(d.key&&d.expression?h[1]:h[2]))});!e.length&&d.key&&e.push(d.key+"=");return e.join("&")},
    +toArray:function(d){var c=this._getItemsAsjQuery(d&&d.connected),e=[];d=d||{};c.each(function(){e.push(a(d.item||this).attr(d.attribute||"id")||"")});return e},_intersectsWith:function(d){var c=this.positionAbs.left,e=c+this.helperProportions.width,h=this.positionAbs.top,g=h+this.helperProportions.height,i=d.left,b=i+d.width,f=d.top,j=f+d.height,l=this.offset.click.top,o=this.offset.click.left;l=h+l>f&&h+l<j&&c+o>i&&c+o<b;return this.options.tolerance=="pointer"||this.options.forcePointerForContainers||
    +this.options.tolerance!="pointer"&&this.helperProportions[this.floating?"width":"height"]>d[this.floating?"width":"height"]?l:i<c+this.helperProportions.width/2&&e-this.helperProportions.width/2<b&&f<h+this.helperProportions.height/2&&g-this.helperProportions.height/2<j},_intersectsWithPointer:function(d){var c=a.ui.isOverAxis(this.positionAbs.top+this.offset.click.top,d.top,d.height);d=a.ui.isOverAxis(this.positionAbs.left+this.offset.click.left,d.left,d.width);c=c&&d;d=this._getDragVerticalDirection();
    +var e=this._getDragHorizontalDirection();if(!c)return false;return this.floating?e&&e=="right"||d=="down"?2:1:d&&(d=="down"?2:1)},_intersectsWithSides:function(d){var c=a.ui.isOverAxis(this.positionAbs.top+this.offset.click.top,d.top+d.height/2,d.height);d=a.ui.isOverAxis(this.positionAbs.left+this.offset.click.left,d.left+d.width/2,d.width);var e=this._getDragVerticalDirection(),h=this._getDragHorizontalDirection();return this.floating&&h?h=="right"&&d||h=="left"&&!d:e&&(e=="down"&&c||e=="up"&&!c)},
    +_getDragVerticalDirection:function(){var d=this.positionAbs.top-this.lastPositionAbs.top;return d!=0&&(d>0?"down":"up")},_getDragHorizontalDirection:function(){var d=this.positionAbs.left-this.lastPositionAbs.left;return d!=0&&(d>0?"right":"left")},refresh:function(d){this._refreshItems(d);this.refreshPositions();return this},_connectWith:function(){var d=this.options;return d.connectWith.constructor==String?[d.connectWith]:d.connectWith},_getItemsAsjQuery:function(d){var c=[],e=[],h=this._connectWith();
    +if(h&&d)for(d=h.length-1;d>=0;d--)for(var g=a(h[d]),i=g.length-1;i>=0;i--){var b=a.data(g[i],"sortable");if(b&&b!=this&&!b.options.disabled)e.push([a.isFunction(b.options.items)?b.options.items.call(b.element):a(b.options.items,b.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),b])}e.push([a.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):a(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),
    +this]);for(d=e.length-1;d>=0;d--)e[d][0].each(function(){c.push(this)});return a(c)},_removeCurrentsFromItems:function(){for(var d=this.currentItem.find(":data(sortable-item)"),c=0;c<this.items.length;c++)for(var e=0;e<d.length;e++)d[e]==this.items[c].item[0]&&this.items.splice(c,1)},_refreshItems:function(d){this.items=[];this.containers=[this];var c=this.items,e=[[a.isFunction(this.options.items)?this.options.items.call(this.element[0],d,{item:this.currentItem}):a(this.options.items,this.element),
    +this]],h=this._connectWith();if(h)for(var g=h.length-1;g>=0;g--)for(var i=a(h[g]),b=i.length-1;b>=0;b--){var f=a.data(i[b],"sortable");if(f&&f!=this&&!f.options.disabled){e.push([a.isFunction(f.options.items)?f.options.items.call(f.element[0],d,{item:this.currentItem}):a(f.options.items,f.element),f]);this.containers.push(f)}}for(g=e.length-1;g>=0;g--){d=e[g][1];h=e[g][0];b=0;for(i=h.length;b<i;b++){f=a(h[b]);f.data("sortable-item",d);c.push({item:f,instance:d,width:0,height:0,left:0,top:0})}}},refreshPositions:function(d){if(this.offsetParent&&
    +this.helper)this.offset.parent=this._getParentOffset();for(var c=this.items.length-1;c>=0;c--){var e=this.items[c];if(!(e.instance!=this.currentContainer&&this.currentContainer&&e.item[0]!=this.currentItem[0])){var h=this.options.toleranceElement?a(this.options.toleranceElement,e.item):e.item;if(!d){e.width=h.outerWidth();e.height=h.outerHeight()}h=h.offset();e.left=h.left;e.top=h.top}}if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(c=
    +this.containers.length-1;c>=0;c--){h=this.containers[c].element.offset();this.containers[c].containerCache.left=h.left;this.containers[c].containerCache.top=h.top;this.containers[c].containerCache.width=this.containers[c].element.outerWidth();this.containers[c].containerCache.height=this.containers[c].element.outerHeight()}return this},_createPlaceholder:function(d){var c=d||this,e=c.options;if(!e.placeholder||e.placeholder.constructor==String){var h=e.placeholder;e.placeholder={element:function(){var g=
    +a(document.createElement(c.currentItem[0].nodeName)).addClass(h||c.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper")[0];if(!h)g.style.visibility="hidden";return g},update:function(g,i){if(!(h&&!e.forcePlaceholderSize)){i.height()||i.height(c.currentItem.innerHeight()-parseInt(c.currentItem.css("paddingTop")||0,10)-parseInt(c.currentItem.css("paddingBottom")||0,10));i.width()||i.width(c.currentItem.innerWidth()-parseInt(c.currentItem.css("paddingLeft")||0,10)-parseInt(c.currentItem.css("paddingRight")||
    +0,10))}}}}c.placeholder=a(e.placeholder.element.call(c.element,c.currentItem));c.currentItem.after(c.placeholder);e.placeholder.update(c,c.placeholder)},_contactContainers:function(d){for(var c=null,e=null,h=this.containers.length-1;h>=0;h--)if(!a.ui.contains(this.currentItem[0],this.containers[h].element[0]))if(this._intersectsWith(this.containers[h].containerCache)){if(!(c&&a.ui.contains(this.containers[h].element[0],c.element[0]))){c=this.containers[h];e=h}}else if(this.containers[h].containerCache.over){this.containers[h]._trigger("out",
    +d,this._uiHash(this));this.containers[h].containerCache.over=0}if(c)if(this.containers.length===1){this.containers[e]._trigger("over",d,this._uiHash(this));this.containers[e].containerCache.over=1}else if(this.currentContainer!=this.containers[e]){c=1E4;h=null;for(var g=this.positionAbs[this.containers[e].floating?"left":"top"],i=this.items.length-1;i>=0;i--)if(a.ui.contains(this.containers[e].element[0],this.items[i].item[0])){var b=this.items[i][this.containers[e].floating?"left":"top"];if(Math.abs(b-
    +g)<c){c=Math.abs(b-g);h=this.items[i]}}if(h||this.options.dropOnEmpty){this.currentContainer=this.containers[e];h?this._rearrange(d,h,null,true):this._rearrange(d,null,this.containers[e].element,true);this._trigger("change",d,this._uiHash());this.containers[e]._trigger("change",d,this._uiHash(this));this.options.placeholder.update(this.currentContainer,this.placeholder);this.containers[e]._trigger("over",d,this._uiHash(this));this.containers[e].containerCache.over=1}}},_createHelper:function(d){var c=
    +this.options;d=a.isFunction(c.helper)?a(c.helper.apply(this.element[0],[d,this.currentItem])):c.helper=="clone"?this.currentItem.clone():this.currentItem;d.parents("body").length||a(c.appendTo!="parent"?c.appendTo:this.currentItem[0].parentNode)[0].appendChild(d[0]);if(d[0]==this.currentItem[0])this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")};if(d[0].style.width==
    +""||c.forceHelperSize)d.width(this.currentItem.width());if(d[0].style.height==""||c.forceHelperSize)d.height(this.currentItem.height());return d},_adjustOffsetFromHelper:function(d){if(typeof d=="string")d=d.split(" ");if(a.isArray(d))d={left:+d[0],top:+d[1]||0};if("left"in d)this.offset.click.left=d.left+this.margins.left;if("right"in d)this.offset.click.left=this.helperProportions.width-d.right+this.margins.left;if("top"in d)this.offset.click.top=d.top+this.margins.top;if("bottom"in d)this.offset.click.top=
    +this.helperProportions.height-d.bottom+this.margins.top},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var d=this.offsetParent.offset();if(this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0])){d.left+=this.scrollParent.scrollLeft();d.top+=this.scrollParent.scrollTop()}if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&a.browser.msie)d=
    +{top:0,left:0};return{top:d.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:d.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var d=this.currentItem.position();return{top:d.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:d.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}else return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css("marginLeft"),
    +10)||0,top:parseInt(this.currentItem.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var d=this.options;if(d.containment=="parent")d.containment=this.helper[0].parentNode;if(d.containment=="document"||d.containment=="window")this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,a(d.containment=="document"?
    +document:window).width()-this.helperProportions.width-this.margins.left,(a(d.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(d.containment)){var c=a(d.containment)[0];d=a(d.containment).offset();var e=a(c).css("overflow")!="hidden";this.containment=[d.left+(parseInt(a(c).css("borderLeftWidth"),10)||0)+(parseInt(a(c).css("paddingLeft"),10)||0)-this.margins.left,d.top+(parseInt(a(c).css("borderTopWidth"),
    +10)||0)+(parseInt(a(c).css("paddingTop"),10)||0)-this.margins.top,d.left+(e?Math.max(c.scrollWidth,c.offsetWidth):c.offsetWidth)-(parseInt(a(c).css("borderLeftWidth"),10)||0)-(parseInt(a(c).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,d.top+(e?Math.max(c.scrollHeight,c.offsetHeight):c.offsetHeight)-(parseInt(a(c).css("borderTopWidth"),10)||0)-(parseInt(a(c).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top]}},_convertPositionTo:function(d,c){if(!c)c=
    +this.position;d=d=="absolute"?1:-1;var e=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,h=/(html|body)/i.test(e[0].tagName);return{top:c.top+this.offset.relative.top*d+this.offset.parent.top*d-(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():h?0:e.scrollTop())*d),left:c.left+this.offset.relative.left*d+this.offset.parent.left*d-(a.browser.safari&&
    +this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():h?0:e.scrollLeft())*d)}},_generatePosition:function(d){var c=this.options,e=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,h=/(html|body)/i.test(e[0].tagName);if(this.cssPosition=="relative"&&!(this.scrollParent[0]!=document&&this.scrollParent[0]!=this.offsetParent[0]))this.offset.relative=this._getRelativeOffset();
    +var g=d.pageX,i=d.pageY;if(this.originalPosition){if(this.containment){if(d.pageX-this.offset.click.left<this.containment[0])g=this.containment[0]+this.offset.click.left;if(d.pageY-this.offset.click.top<this.containment[1])i=this.containment[1]+this.offset.click.top;if(d.pageX-this.offset.click.left>this.containment[2])g=this.containment[2]+this.offset.click.left;if(d.pageY-this.offset.click.top>this.containment[3])i=this.containment[3]+this.offset.click.top}if(c.grid){i=this.originalPageY+Math.round((i-
    +this.originalPageY)/c.grid[1])*c.grid[1];i=this.containment?!(i-this.offset.click.top<this.containment[1]||i-this.offset.click.top>this.containment[3])?i:!(i-this.offset.click.top<this.containment[1])?i-c.grid[1]:i+c.grid[1]:i;g=this.originalPageX+Math.round((g-this.originalPageX)/c.grid[0])*c.grid[0];g=this.containment?!(g-this.offset.click.left<this.containment[0]||g-this.offset.click.left>this.containment[2])?g:!(g-this.offset.click.left<this.containment[0])?g-c.grid[0]:g+c.grid[0]:g}}return{top:i-
    +this.offset.click.top-this.offset.relative.top-this.offset.parent.top+(a.browser.safari&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollTop():h?0:e.scrollTop()),left:g-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+(a.browser.safari&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():h?0:e.scrollLeft())}},_rearrange:function(d,c,e,h){e?e[0].appendChild(this.placeholder[0]):c.item[0].parentNode.insertBefore(this.placeholder[0],
    +this.direction=="down"?c.item[0]:c.item[0].nextSibling);this.counter=this.counter?++this.counter:1;var g=this,i=this.counter;window.setTimeout(function(){i==g.counter&&g.refreshPositions(!h)},0)},_clear:function(d,c){this.reverting=false;var e=[];!this._noFinalSort&&this.currentItem.parent().length&&this.placeholder.before(this.currentItem);this._noFinalSort=null;if(this.helper[0]==this.currentItem[0]){for(var h in this._storedCSS)if(this._storedCSS[h]=="auto"||this._storedCSS[h]=="static")this._storedCSS[h]=
    +"";this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper")}else this.currentItem.show();this.fromOutside&&!c&&e.push(function(g){this._trigger("receive",g,this._uiHash(this.fromOutside))});if((this.fromOutside||this.domPosition.prev!=this.currentItem.prev().not(".ui-sortable-helper")[0]||this.domPosition.parent!=this.currentItem.parent()[0])&&!c)e.push(function(g){this._trigger("update",g,this._uiHash())});if(!a.ui.contains(this.element[0],this.currentItem[0])){c||e.push(function(g){this._trigger("remove",
    +g,this._uiHash())});for(h=this.containers.length-1;h>=0;h--)if(a.ui.contains(this.containers[h].element[0],this.currentItem[0])&&!c){e.push(function(g){return function(i){g._trigger("receive",i,this._uiHash(this))}}.call(this,this.containers[h]));e.push(function(g){return function(i){g._trigger("update",i,this._uiHash(this))}}.call(this,this.containers[h]))}}for(h=this.containers.length-1;h>=0;h--){c||e.push(function(g){return function(i){g._trigger("deactivate",i,this._uiHash(this))}}.call(this,
    +this.containers[h]));if(this.containers[h].containerCache.over){e.push(function(g){return function(i){g._trigger("out",i,this._uiHash(this))}}.call(this,this.containers[h]));this.containers[h].containerCache.over=0}}this._storedCursor&&a("body").css("cursor",this._storedCursor);this._storedOpacity&&this.helper.css("opacity",this._storedOpacity);if(this._storedZIndex)this.helper.css("zIndex",this._storedZIndex=="auto"?"":this._storedZIndex);this.dragging=false;if(this.cancelHelperRemoval){if(!c){this._trigger("beforeStop",
    +d,this._uiHash());for(h=0;h<e.length;h++)e[h].call(this,d);this._trigger("stop",d,this._uiHash())}return false}c||this._trigger("beforeStop",d,this._uiHash());this.placeholder[0].parentNode.removeChild(this.placeholder[0]);this.helper[0]!=this.currentItem[0]&&this.helper.remove();this.helper=null;if(!c){for(h=0;h<e.length;h++)e[h].call(this,d);this._trigger("stop",d,this._uiHash())}this.fromOutside=false;return true},_trigger:function(){a.Widget.prototype._trigger.apply(this,arguments)===false&&this.cancel()},
    +_uiHash:function(d){var c=d||this;return{helper:c.helper,placeholder:c.placeholder||a([]),position:c.position,originalPosition:c.originalPosition,offset:c.positionAbs,item:c.currentItem,sender:d?d.element:null}}});a.extend(a.ui.sortable,{version:"1.8.16"})})(jQuery);
    +jQuery.effects||function(a,d){function c(n){var k;if(n&&n.constructor==Array&&n.length==3)return n;if(k=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(n))return[parseInt(k[1],10),parseInt(k[2],10),parseInt(k[3],10)];if(k=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(n))return[parseFloat(k[1])*2.55,parseFloat(k[2])*2.55,parseFloat(k[3])*2.55];if(k=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(n))return[parseInt(k[1],
    +16),parseInt(k[2],16),parseInt(k[3],16)];if(k=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(n))return[parseInt(k[1]+k[1],16),parseInt(k[2]+k[2],16),parseInt(k[3]+k[3],16)];if(/rgba\(0, 0, 0, 0\)/.exec(n))return j.transparent;return j[a.trim(n).toLowerCase()]}function e(n,k){var m;do{m=a.curCSS(n,k);if(m!=""&&m!="transparent"||a.nodeName(n,"body"))break;k="backgroundColor"}while(n=n.parentNode);return c(m)}function h(){var n=document.defaultView?document.defaultView.getComputedStyle(this,null):this.currentStyle,
    +k={},m,p;if(n&&n.length&&n[0]&&n[n[0]])for(var q=n.length;q--;){m=n[q];if(typeof n[m]=="string"){p=m.replace(/\-(\w)/g,function(s,r){return r.toUpperCase()});k[p]=n[m]}}else for(m in n)if(typeof n[m]==="string")k[m]=n[m];return k}function g(n){var k,m;for(k in n){m=n[k];if(m==null||a.isFunction(m)||k in o||/scrollbar/.test(k)||!/color/i.test(k)&&isNaN(parseFloat(m)))delete n[k]}return n}function i(n,k){var m={_:0},p;for(p in k)if(n[p]!=k[p])m[p]=k[p];return m}function b(n,k,m,p){if(typeof n=="object"){p=
    +k;m=null;k=n;n=k.effect}if(a.isFunction(k)){p=k;m=null;k={}}if(typeof k=="number"||a.fx.speeds[k]){p=m;m=k;k={}}if(a.isFunction(m)){p=m;m=null}k=k||{};m=m||k.duration;m=a.fx.off?0:typeof m=="number"?m:m in a.fx.speeds?a.fx.speeds[m]:a.fx.speeds._default;p=p||k.complete;return[n,k,m,p]}function f(n){if(!n||typeof n==="number"||a.fx.speeds[n])return true;if(typeof n==="string"&&!a.effects[n])return true;return false}a.effects={};a.each(["backgroundColor","borderBottomColor","borderLeftColor","borderRightColor",
    +"borderTopColor","borderColor","color","outlineColor"],function(n,k){a.fx.step[k]=function(m){if(!m.colorInit){m.start=e(m.elem,k);m.end=c(m.end);m.colorInit=true}m.elem.style[k]="rgb("+Math.max(Math.min(parseInt(m.pos*(m.end[0]-m.start[0])+m.start[0],10),255),0)+","+Math.max(Math.min(parseInt(m.pos*(m.end[1]-m.start[1])+m.start[1],10),255),0)+","+Math.max(Math.min(parseInt(m.pos*(m.end[2]-m.start[2])+m.start[2],10),255),0)+")"}});var j={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,
    +0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,
    +211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0],transparent:[255,255,255]},l=["add","remove","toggle"],o={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};a.effects.animateClass=function(n,k,m,
    +p){if(a.isFunction(m)){p=m;m=null}return this.queue(function(){var q=a(this),s=q.attr("style")||" ",r=g(h.call(this)),u,v=q.attr("class");a.each(l,function(w,x){n[x]&&q[x+"Class"](n[x])});u=g(h.call(this));q.attr("class",v);q.animate(i(r,u),{queue:false,duration:k,easing:m,complete:function(){a.each(l,function(w,x){n[x]&&q[x+"Class"](n[x])});if(typeof q.attr("style")=="object"){q.attr("style").cssText="";q.attr("style").cssText=s}else q.attr("style",s);p&&p.apply(this,arguments);a.dequeue(this)}})})};
    +a.fn.extend({_addClass:a.fn.addClass,addClass:function(n,k,m,p){return k?a.effects.animateClass.apply(this,[{add:n},k,m,p]):this._addClass(n)},_removeClass:a.fn.removeClass,removeClass:function(n,k,m,p){return k?a.effects.animateClass.apply(this,[{remove:n},k,m,p]):this._removeClass(n)},_toggleClass:a.fn.toggleClass,toggleClass:function(n,k,m,p,q){return typeof k=="boolean"||k===d?m?a.effects.animateClass.apply(this,[k?{add:n}:{remove:n},m,p,q]):this._toggleClass(n,k):a.effects.animateClass.apply(this,
    +[{toggle:n},k,m,p])},switchClass:function(n,k,m,p,q){return a.effects.animateClass.apply(this,[{add:k,remove:n},m,p,q])}});a.extend(a.effects,{version:"1.8.16",save:function(n,k){for(var m=0;m<k.length;m++)k[m]!==null&&n.data("ec.storage."+k[m],n[0].style[k[m]])},restore:function(n,k){for(var m=0;m<k.length;m++)k[m]!==null&&n.css(k[m],n.data("ec.storage."+k[m]))},setMode:function(n,k){if(k=="toggle")k=n.is(":hidden")?"show":"hide";return k},getBaseline:function(n,k){var m;switch(n[0]){case "top":m=
    +0;break;case "middle":m=0.5;break;case "bottom":m=1;break;default:m=n[0]/k.height}switch(n[1]){case "left":n=0;break;case "center":n=0.5;break;case "right":n=1;break;default:n=n[1]/k.width}return{x:n,y:m}},createWrapper:function(n){if(n.parent().is(".ui-effects-wrapper"))return n.parent();var k={width:n.outerWidth(true),height:n.outerHeight(true),"float":n.css("float")},m=a("<div></div>").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),
    +p=document.activeElement;n.wrap(m);if(n[0]===p||a.contains(n[0],p))a(p).focus();m=n.parent();if(n.css("position")=="static"){m.css({position:"relative"});n.css({position:"relative"})}else{a.extend(k,{position:n.css("position"),zIndex:n.css("z-index")});a.each(["top","left","bottom","right"],function(q,s){k[s]=n.css(s);if(isNaN(parseInt(k[s],10)))k[s]="auto"});n.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})}return m.css(k).show()},removeWrapper:function(n){var k,m=document.activeElement;
    +if(n.parent().is(".ui-effects-wrapper")){k=n.parent().replaceWith(n);if(n[0]===m||a.contains(n[0],m))a(m).focus();return k}return n},setTransition:function(n,k,m,p){p=p||{};a.each(k,function(q,s){unit=n.cssUnit(s);if(unit[0]>0)p[s]=unit[0]*m+unit[1]});return p}});a.fn.extend({effect:function(n){var k=b.apply(this,arguments),m={options:k[1],duration:k[2],callback:k[3]};k=m.options.mode;var p=a.effects[n];if(a.fx.off||!p)return k?this[k](m.duration,m.callback):this.each(function(){m.callback&&m.callback.call(this)});
    +return p.call(this,m)},_show:a.fn.show,show:function(n){if(f(n))return this._show.apply(this,arguments);else{var k=b.apply(this,arguments);k[1].mode="show";return this.effect.apply(this,k)}},_hide:a.fn.hide,hide:function(n){if(f(n))return this._hide.apply(this,arguments);else{var k=b.apply(this,arguments);k[1].mode="hide";return this.effect.apply(this,k)}},__toggle:a.fn.toggle,toggle:function(n){if(f(n)||typeof n==="boolean"||a.isFunction(n))return this.__toggle.apply(this,arguments);else{var k=b.apply(this,
    +arguments);k[1].mode="toggle";return this.effect.apply(this,k)}},cssUnit:function(n){var k=this.css(n),m=[];a.each(["em","px","%","pt"],function(p,q){if(k.indexOf(q)>0)m=[parseFloat(k),q]});return m}});a.easing.jswing=a.easing.swing;a.extend(a.easing,{def:"easeOutQuad",swing:function(n,k,m,p,q){return a.easing[a.easing.def](n,k,m,p,q)},easeInQuad:function(n,k,m,p,q){return p*(k/=q)*k+m},easeOutQuad:function(n,k,m,p,q){return-p*(k/=q)*(k-2)+m},easeInOutQuad:function(n,k,m,p,q){if((k/=q/2)<1)return p/
    +2*k*k+m;return-p/2*(--k*(k-2)-1)+m},easeInCubic:function(n,k,m,p,q){return p*(k/=q)*k*k+m},easeOutCubic:function(n,k,m,p,q){return p*((k=k/q-1)*k*k+1)+m},easeInOutCubic:function(n,k,m,p,q){if((k/=q/2)<1)return p/2*k*k*k+m;return p/2*((k-=2)*k*k+2)+m},easeInQuart:function(n,k,m,p,q){return p*(k/=q)*k*k*k+m},easeOutQuart:function(n,k,m,p,q){return-p*((k=k/q-1)*k*k*k-1)+m},easeInOutQuart:function(n,k,m,p,q){if((k/=q/2)<1)return p/2*k*k*k*k+m;return-p/2*((k-=2)*k*k*k-2)+m},easeInQuint:function(n,k,m,
    +p,q){return p*(k/=q)*k*k*k*k+m},easeOutQuint:function(n,k,m,p,q){return p*((k=k/q-1)*k*k*k*k+1)+m},easeInOutQuint:function(n,k,m,p,q){if((k/=q/2)<1)return p/2*k*k*k*k*k+m;return p/2*((k-=2)*k*k*k*k+2)+m},easeInSine:function(n,k,m,p,q){return-p*Math.cos(k/q*(Math.PI/2))+p+m},easeOutSine:function(n,k,m,p,q){return p*Math.sin(k/q*(Math.PI/2))+m},easeInOutSine:function(n,k,m,p,q){return-p/2*(Math.cos(Math.PI*k/q)-1)+m},easeInExpo:function(n,k,m,p,q){return k==0?m:p*Math.pow(2,10*(k/q-1))+m},easeOutExpo:function(n,
    +k,m,p,q){return k==q?m+p:p*(-Math.pow(2,-10*k/q)+1)+m},easeInOutExpo:function(n,k,m,p,q){if(k==0)return m;if(k==q)return m+p;if((k/=q/2)<1)return p/2*Math.pow(2,10*(k-1))+m;return p/2*(-Math.pow(2,-10*--k)+2)+m},easeInCirc:function(n,k,m,p,q){return-p*(Math.sqrt(1-(k/=q)*k)-1)+m},easeOutCirc:function(n,k,m,p,q){return p*Math.sqrt(1-(k=k/q-1)*k)+m},easeInOutCirc:function(n,k,m,p,q){if((k/=q/2)<1)return-p/2*(Math.sqrt(1-k*k)-1)+m;return p/2*(Math.sqrt(1-(k-=2)*k)+1)+m},easeInElastic:function(n,k,m,
    +p,q){n=1.70158;var s=0,r=p;if(k==0)return m;if((k/=q)==1)return m+p;s||(s=q*0.3);if(r<Math.abs(p)){r=p;n=s/4}else n=s/(2*Math.PI)*Math.asin(p/r);return-(r*Math.pow(2,10*(k-=1))*Math.sin((k*q-n)*2*Math.PI/s))+m},easeOutElastic:function(n,k,m,p,q){n=1.70158;var s=0,r=p;if(k==0)return m;if((k/=q)==1)return m+p;s||(s=q*0.3);if(r<Math.abs(p)){r=p;n=s/4}else n=s/(2*Math.PI)*Math.asin(p/r);return r*Math.pow(2,-10*k)*Math.sin((k*q-n)*2*Math.PI/s)+p+m},easeInOutElastic:function(n,k,m,p,q){n=1.70158;var s=
    +0,r=p;if(k==0)return m;if((k/=q/2)==2)return m+p;s||(s=q*0.3*1.5);if(r<Math.abs(p)){r=p;n=s/4}else n=s/(2*Math.PI)*Math.asin(p/r);if(k<1)return-0.5*r*Math.pow(2,10*(k-=1))*Math.sin((k*q-n)*2*Math.PI/s)+m;return r*Math.pow(2,-10*(k-=1))*Math.sin((k*q-n)*2*Math.PI/s)*0.5+p+m},easeInBack:function(n,k,m,p,q,s){if(s==d)s=1.70158;return p*(k/=q)*k*((s+1)*k-s)+m},easeOutBack:function(n,k,m,p,q,s){if(s==d)s=1.70158;return p*((k=k/q-1)*k*((s+1)*k+s)+1)+m},easeInOutBack:function(n,k,m,p,q,s){if(s==d)s=1.70158;
    +if((k/=q/2)<1)return p/2*k*k*(((s*=1.525)+1)*k-s)+m;return p/2*((k-=2)*k*(((s*=1.525)+1)*k+s)+2)+m},easeInBounce:function(n,k,m,p,q){return p-a.easing.easeOutBounce(n,q-k,0,p,q)+m},easeOutBounce:function(n,k,m,p,q){return(k/=q)<1/2.75?p*7.5625*k*k+m:k<2/2.75?p*(7.5625*(k-=1.5/2.75)*k+0.75)+m:k<2.5/2.75?p*(7.5625*(k-=2.25/2.75)*k+0.9375)+m:p*(7.5625*(k-=2.625/2.75)*k+0.984375)+m},easeInOutBounce:function(n,k,m,p,q){if(k<q/2)return a.easing.easeInBounce(n,k*2,0,p,q)*0.5+m;return a.easing.easeOutBounce(n,
    +k*2-q,0,p,q)*0.5+p*0.5+m}})}(jQuery);
    +(function(a){a.effects.blind=function(d){return this.queue(function(){var c=a(this),e=["position","top","bottom","left","right"],h=a.effects.setMode(c,d.options.mode||"hide"),g=d.options.direction||"vertical";a.effects.save(c,e);c.show();var i=a.effects.createWrapper(c).css({overflow:"hidden"}),b=g=="vertical"?"height":"width";g=g=="vertical"?i.height():i.width();h=="show"&&i.css(b,0);var f={};f[b]=h=="show"?g:0;i.animate(f,d.duration,d.options.easing,function(){h=="hide"&&c.hide();a.effects.restore(c,
    +e);a.effects.removeWrapper(c);d.callback&&d.callback.apply(c[0],arguments);c.dequeue()})})}})(jQuery);
    +(function(a){a.effects.bounce=function(d){return this.queue(function(){var c=a(this),e=["position","top","bottom","left","right"],h=a.effects.setMode(c,d.options.mode||"effect"),g=d.options.direction||"up",i=d.options.distance||20,b=d.options.times||5,f=d.duration||250;/show|hide/.test(h)&&e.push("opacity");a.effects.save(c,e);c.show();a.effects.createWrapper(c);var j=g=="up"||g=="down"?"top":"left";g=g=="up"||g=="left"?"pos":"neg";i=d.options.distance||(j=="top"?c.outerHeight({margin:true})/3:c.outerWidth({margin:true})/
    +3);if(h=="show")c.css("opacity",0).css(j,g=="pos"?-i:i);if(h=="hide")i/=b*2;h!="hide"&&b--;if(h=="show"){var l={opacity:1};l[j]=(g=="pos"?"+=":"-=")+i;c.animate(l,f/2,d.options.easing);i/=2;b--}for(l=0;l<b;l++){var o={},n={};o[j]=(g=="pos"?"-=":"+=")+i;n[j]=(g=="pos"?"+=":"-=")+i;c.animate(o,f/2,d.options.easing).animate(n,f/2,d.options.easing);i=h=="hide"?i*2:i/2}if(h=="hide"){l={opacity:0};l[j]=(g=="pos"?"-=":"+=")+i;c.animate(l,f/2,d.options.easing,function(){c.hide();a.effects.restore(c,e);a.effects.removeWrapper(c);
    +d.callback&&d.callback.apply(this,arguments)})}else{o={};n={};o[j]=(g=="pos"?"-=":"+=")+i;n[j]=(g=="pos"?"+=":"-=")+i;c.animate(o,f/2,d.options.easing).animate(n,f/2,d.options.easing,function(){a.effects.restore(c,e);a.effects.removeWrapper(c);d.callback&&d.callback.apply(this,arguments)})}c.queue("fx",function(){c.dequeue()});c.dequeue()})}})(jQuery);
    +(function(a){a.effects.clip=function(d){return this.queue(function(){var c=a(this),e=["position","top","bottom","left","right","height","width"],h=a.effects.setMode(c,d.options.mode||"hide"),g=d.options.direction||"vertical";a.effects.save(c,e);c.show();var i=a.effects.createWrapper(c).css({overflow:"hidden"});i=c[0].tagName=="IMG"?i:c;var b={size:g=="vertical"?"height":"width",position:g=="vertical"?"top":"left"};g=g=="vertical"?i.height():i.width();if(h=="show"){i.css(b.size,0);i.css(b.position,
    +g/2)}var f={};f[b.size]=h=="show"?g:0;f[b.position]=h=="show"?0:g/2;i.animate(f,{queue:false,duration:d.duration,easing:d.options.easing,complete:function(){h=="hide"&&c.hide();a.effects.restore(c,e);a.effects.removeWrapper(c);d.callback&&d.callback.apply(c[0],arguments);c.dequeue()}})})}})(jQuery);
    +(function(a){a.effects.drop=function(d){return this.queue(function(){var c=a(this),e=["position","top","bottom","left","right","opacity"],h=a.effects.setMode(c,d.options.mode||"hide"),g=d.options.direction||"left";a.effects.save(c,e);c.show();a.effects.createWrapper(c);var i=g=="up"||g=="down"?"top":"left";g=g=="up"||g=="left"?"pos":"neg";var b=d.options.distance||(i=="top"?c.outerHeight({margin:true})/2:c.outerWidth({margin:true})/2);if(h=="show")c.css("opacity",0).css(i,g=="pos"?-b:b);var f={opacity:h==
    +"show"?1:0};f[i]=(h=="show"?g=="pos"?"+=":"-=":g=="pos"?"-=":"+=")+b;c.animate(f,{queue:false,duration:d.duration,easing:d.options.easing,complete:function(){h=="hide"&&c.hide();a.effects.restore(c,e);a.effects.removeWrapper(c);d.callback&&d.callback.apply(this,arguments);c.dequeue()}})})}})(jQuery);
    +(function(a){a.effects.explode=function(d){return this.queue(function(){var c=d.options.pieces?Math.round(Math.sqrt(d.options.pieces)):3,e=d.options.pieces?Math.round(Math.sqrt(d.options.pieces)):3;d.options.mode=d.options.mode=="toggle"?a(this).is(":visible")?"hide":"show":d.options.mode;var h=a(this).show().css("visibility","hidden"),g=h.offset();g.top-=parseInt(h.css("marginTop"),10)||0;g.left-=parseInt(h.css("marginLeft"),10)||0;for(var i=h.outerWidth(true),b=h.outerHeight(true),f=0;f<c;f++)for(var j=
    +0;j<e;j++)h.clone().appendTo("body").wrap("<div></div>").css({position:"absolute",visibility:"visible",left:-j*(i/e),top:-f*(b/c)}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:i/e,height:b/c,left:g.left+j*(i/e)+(d.options.mode=="show"?(j-Math.floor(e/2))*(i/e):0),top:g.top+f*(b/c)+(d.options.mode=="show"?(f-Math.floor(c/2))*(b/c):0),opacity:d.options.mode=="show"?0:1}).animate({left:g.left+j*(i/e)+(d.options.mode=="show"?0:(j-Math.floor(e/2))*(i/e)),top:g.top+
    +f*(b/c)+(d.options.mode=="show"?0:(f-Math.floor(c/2))*(b/c)),opacity:d.options.mode=="show"?1:0},d.duration||500);setTimeout(function(){d.options.mode=="show"?h.css({visibility:"visible"}):h.css({visibility:"visible"}).hide();d.callback&&d.callback.apply(h[0]);h.dequeue();a("div.ui-effects-explode").remove()},d.duration||500)})}})(jQuery);
    +(function(a){a.effects.fade=function(d){return this.queue(function(){var c=a(this),e=a.effects.setMode(c,d.options.mode||"hide");c.animate({opacity:e},{queue:false,duration:d.duration,easing:d.options.easing,complete:function(){d.callback&&d.callback.apply(this,arguments);c.dequeue()}})})}})(jQuery);
    +(function(a){a.effects.fold=function(d){return this.queue(function(){var c=a(this),e=["position","top","bottom","left","right"],h=a.effects.setMode(c,d.options.mode||"hide"),g=d.options.size||15,i=!!d.options.horizFirst,b=d.duration?d.duration/2:a.fx.speeds._default/2;a.effects.save(c,e);c.show();var f=a.effects.createWrapper(c).css({overflow:"hidden"}),j=h=="show"!=i,l=j?["width","height"]:["height","width"];j=j?[f.width(),f.height()]:[f.height(),f.width()];var o=/([0-9]+)%/.exec(g);if(o)g=parseInt(o[1],
    +10)/100*j[h=="hide"?0:1];if(h=="show")f.css(i?{height:0,width:g}:{height:g,width:0});i={};o={};i[l[0]]=h=="show"?j[0]:g;o[l[1]]=h=="show"?j[1]:0;f.animate(i,b,d.options.easing).animate(o,b,d.options.easing,function(){h=="hide"&&c.hide();a.effects.restore(c,e);a.effects.removeWrapper(c);d.callback&&d.callback.apply(c[0],arguments);c.dequeue()})})}})(jQuery);
    +(function(a){a.effects.highlight=function(d){return this.queue(function(){var c=a(this),e=["backgroundImage","backgroundColor","opacity"],h=a.effects.setMode(c,d.options.mode||"show"),g={backgroundColor:c.css("backgroundColor")};if(h=="hide")g.opacity=0;a.effects.save(c,e);c.show().css({backgroundImage:"none",backgroundColor:d.options.color||"#ffff99"}).animate(g,{queue:false,duration:d.duration,easing:d.options.easing,complete:function(){h=="hide"&&c.hide();a.effects.restore(c,e);h=="show"&&!a.support.opacity&&
    +this.style.removeAttribute("filter");d.callback&&d.callback.apply(this,arguments);c.dequeue()}})})}})(jQuery);
    +(function(a){a.effects.pulsate=function(d){return this.queue(function(){var c=a(this),e=a.effects.setMode(c,d.options.mode||"show");times=(d.options.times||5)*2-1;duration=d.duration?d.duration/2:a.fx.speeds._default/2;isVisible=c.is(":visible");animateTo=0;if(!isVisible){c.css("opacity",0).show();animateTo=1}if(e=="hide"&&isVisible||e=="show"&&!isVisible)times--;for(e=0;e<times;e++){c.animate({opacity:animateTo},duration,d.options.easing);animateTo=(animateTo+1)%2}c.animate({opacity:animateTo},duration,
    +d.options.easing,function(){animateTo==0&&c.hide();d.callback&&d.callback.apply(this,arguments)});c.queue("fx",function(){c.dequeue()}).dequeue()})}})(jQuery);
    +(function(a){a.effects.puff=function(d){return this.queue(function(){var c=a(this),e=a.effects.setMode(c,d.options.mode||"hide"),h=parseInt(d.options.percent,10)||150,g=h/100,i={height:c.height(),width:c.width()};a.extend(d.options,{fade:true,mode:e,percent:e=="hide"?h:100,from:e=="hide"?i:{height:i.height*g,width:i.width*g}});c.effect("scale",d.options,d.duration,d.callback);c.dequeue()})};a.effects.scale=function(d){return this.queue(function(){var c=a(this),e=a.extend(true,{},d.options),h=a.effects.setMode(c,
    +d.options.mode||"effect"),g=parseInt(d.options.percent,10)||(parseInt(d.options.percent,10)==0?0:h=="hide"?0:100),i=d.options.direction||"both",b=d.options.origin;if(h!="effect"){e.origin=b||["middle","center"];e.restore=true}b={height:c.height(),width:c.width()};c.from=d.options.from||(h=="show"?{height:0,width:0}:b);g={y:i!="horizontal"?g/100:1,x:i!="vertical"?g/100:1};c.to={height:b.height*g.y,width:b.width*g.x};if(d.options.fade){if(h=="show"){c.from.opacity=0;c.to.opacity=1}if(h=="hide"){c.from.opacity=
    +1;c.to.opacity=0}}e.from=c.from;e.to=c.to;e.mode=h;c.effect("size",e,d.duration,d.callback);c.dequeue()})};a.effects.size=function(d){return this.queue(function(){var c=a(this),e=["position","top","bottom","left","right","width","height","overflow","opacity"],h=["position","top","bottom","left","right","overflow","opacity"],g=["width","height","overflow"],i=["fontSize"],b=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],f=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],
    +j=a.effects.setMode(c,d.options.mode||"effect"),l=d.options.restore||false,o=d.options.scale||"both",n=d.options.origin,k={height:c.height(),width:c.width()};c.from=d.options.from||k;c.to=d.options.to||k;if(n){n=a.effects.getBaseline(n,k);c.from.top=(k.height-c.from.height)*n.y;c.from.left=(k.width-c.from.width)*n.x;c.to.top=(k.height-c.to.height)*n.y;c.to.left=(k.width-c.to.width)*n.x}var m={from:{y:c.from.height/k.height,x:c.from.width/k.width},to:{y:c.to.height/k.height,x:c.to.width/k.width}};
    +if(o=="box"||o=="both"){if(m.from.y!=m.to.y){e=e.concat(b);c.from=a.effects.setTransition(c,b,m.from.y,c.from);c.to=a.effects.setTransition(c,b,m.to.y,c.to)}if(m.from.x!=m.to.x){e=e.concat(f);c.from=a.effects.setTransition(c,f,m.from.x,c.from);c.to=a.effects.setTransition(c,f,m.to.x,c.to)}}if(o=="content"||o=="both")if(m.from.y!=m.to.y){e=e.concat(i);c.from=a.effects.setTransition(c,i,m.from.y,c.from);c.to=a.effects.setTransition(c,i,m.to.y,c.to)}a.effects.save(c,l?e:h);c.show();a.effects.createWrapper(c);
    +c.css("overflow","hidden").css(c.from);if(o=="content"||o=="both"){b=b.concat(["marginTop","marginBottom"]).concat(i);f=f.concat(["marginLeft","marginRight"]);g=e.concat(b).concat(f);c.find("*[width]").each(function(){child=a(this);l&&a.effects.save(child,g);var p={height:child.height(),width:child.width()};child.from={height:p.height*m.from.y,width:p.width*m.from.x};child.to={height:p.height*m.to.y,width:p.width*m.to.x};if(m.from.y!=m.to.y){child.from=a.effects.setTransition(child,b,m.from.y,child.from);
    +child.to=a.effects.setTransition(child,b,m.to.y,child.to)}if(m.from.x!=m.to.x){child.from=a.effects.setTransition(child,f,m.from.x,child.from);child.to=a.effects.setTransition(child,f,m.to.x,child.to)}child.css(child.from);child.animate(child.to,d.duration,d.options.easing,function(){l&&a.effects.restore(child,g)})})}c.animate(c.to,{queue:false,duration:d.duration,easing:d.options.easing,complete:function(){c.to.opacity===0&&c.css("opacity",c.from.opacity);j=="hide"&&c.hide();a.effects.restore(c,
    +l?e:h);a.effects.removeWrapper(c);d.callback&&d.callback.apply(this,arguments);c.dequeue()}})})}})(jQuery);
    +(function(a){a.effects.shake=function(d){return this.queue(function(){var c=a(this),e=["position","top","bottom","left","right"];a.effects.setMode(c,d.options.mode||"effect");var h=d.options.direction||"left",g=d.options.distance||20,i=d.options.times||3,b=d.duration||d.options.duration||140;a.effects.save(c,e);c.show();a.effects.createWrapper(c);var f=h=="up"||h=="down"?"top":"left",j=h=="up"||h=="left"?"pos":"neg";h={};var l={},o={};h[f]=(j=="pos"?"-=":"+=")+g;l[f]=(j=="pos"?"+=":"-=")+g*2;o[f]=
    +(j=="pos"?"-=":"+=")+g*2;c.animate(h,b,d.options.easing);for(g=1;g<i;g++)c.animate(l,b,d.options.easing).animate(o,b,d.options.easing);c.animate(l,b,d.options.easing).animate(h,b/2,d.options.easing,function(){a.effects.restore(c,e);a.effects.removeWrapper(c);d.callback&&d.callback.apply(this,arguments)});c.queue("fx",function(){c.dequeue()});c.dequeue()})}})(jQuery);
    +(function(a){a.effects.slide=function(d){return this.queue(function(){var c=a(this),e=["position","top","bottom","left","right"],h=a.effects.setMode(c,d.options.mode||"show"),g=d.options.direction||"left";a.effects.save(c,e);c.show();a.effects.createWrapper(c).css({overflow:"hidden"});var i=g=="up"||g=="down"?"top":"left";g=g=="up"||g=="left"?"pos":"neg";var b=d.options.distance||(i=="top"?c.outerHeight({margin:true}):c.outerWidth({margin:true}));if(h=="show")c.css(i,g=="pos"?isNaN(b)?"-"+b:-b:b);
    +var f={};f[i]=(h=="show"?g=="pos"?"+=":"-=":g=="pos"?"-=":"+=")+b;c.animate(f,{queue:false,duration:d.duration,easing:d.options.easing,complete:function(){h=="hide"&&c.hide();a.effects.restore(c,e);a.effects.removeWrapper(c);d.callback&&d.callback.apply(this,arguments);c.dequeue()}})})}})(jQuery);
    +(function(a){a.effects.transfer=function(d){return this.queue(function(){var c=a(this),e=a(d.options.to),h=e.offset();e={top:h.top,left:h.left,height:e.innerHeight(),width:e.innerWidth()};h=c.offset();var g=a('<div class="ui-effects-transfer"></div>').appendTo(document.body).addClass(d.options.className).css({top:h.top,left:h.left,height:c.innerHeight(),width:c.innerWidth(),position:"absolute"}).animate(e,d.duration,d.options.easing,function(){g.remove();d.callback&&d.callback.apply(c[0],arguments);
    +c.dequeue()})})}})(jQuery);
    +(function(a){a.widget("ui.accordion",{options:{active:0,animated:"slide",autoHeight:true,clearStyle:false,collapsible:false,event:"click",fillSpace:false,header:"> li > :first-child,> :not(li):even",icons:{header:"ui-icon-triangle-1-e",headerSelected:"ui-icon-triangle-1-s"},navigation:false,navigationFilter:function(){return this.href.toLowerCase()===location.href.toLowerCase()}},_create:function(){var d=this,c=d.options;d.running=0;d.element.addClass("ui-accordion ui-widget ui-helper-reset").children("li").addClass("ui-accordion-li-fix");d.headers=
    +d.element.find(c.header).addClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all").bind("mouseenter.accordion",function(){c.disabled||a(this).addClass("ui-state-hover")}).bind("mouseleave.accordion",function(){c.disabled||a(this).removeClass("ui-state-hover")}).bind("focus.accordion",function(){c.disabled||a(this).addClass("ui-state-focus")}).bind("blur.accordion",function(){c.disabled||a(this).removeClass("ui-state-focus")});d.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom");
    +if(c.navigation){var e=d.element.find("a").filter(c.navigationFilter).eq(0);if(e.length){var h=e.closest(".ui-accordion-header");d.active=h.length?h:e.closest(".ui-accordion-content").prev()}}d.active=d._findActive(d.active||c.active).addClass("ui-state-default ui-state-active").toggleClass("ui-corner-all").toggleClass("ui-corner-top");d.active.next().addClass("ui-accordion-content-active");d._createIcons();d.resize();d.element.attr("role","tablist");d.headers.attr("role","tab").bind("keydown.accordion",
    +function(g){return d._keydown(g)}).next().attr("role","tabpanel");d.headers.not(d.active||"").attr({"aria-expanded":"false","aria-selected":"false",tabIndex:-1}).next().hide();d.active.length?d.active.attr({"aria-expanded":"true","aria-selected":"true",tabIndex:0}):d.headers.eq(0).attr("tabIndex",0);a.browser.safari||d.headers.find("a").attr("tabIndex",-1);c.event&&d.headers.bind(c.event.split(" ").join(".accordion ")+".accordion",function(g){d._clickHandler.call(d,g,this);g.preventDefault()})},_createIcons:function(){var d=
    +this.options;if(d.icons){a("<span></span>").addClass("ui-icon "+d.icons.header).prependTo(this.headers);this.active.children(".ui-icon").toggleClass(d.icons.header).toggleClass(d.icons.headerSelected);this.element.addClass("ui-accordion-icons")}},_destroyIcons:function(){this.headers.children(".ui-icon").remove();this.element.removeClass("ui-accordion-icons")},destroy:function(){var d=this.options;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role");this.headers.unbind(".accordion").removeClass("ui-accordion-header ui-accordion-disabled ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top").removeAttr("role").removeAttr("aria-expanded").removeAttr("aria-selected").removeAttr("tabIndex");
    +this.headers.find("a").removeAttr("tabIndex");this._destroyIcons();var c=this.headers.next().css("display","").removeAttr("role").removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-accordion-disabled ui-state-disabled");if(d.autoHeight||d.fillHeight)c.css("height","");return a.Widget.prototype.destroy.call(this)},_setOption:function(d,c){a.Widget.prototype._setOption.apply(this,arguments);d=="active"&&this.activate(c);if(d=="icons"){this._destroyIcons();
    +c&&this._createIcons()}if(d=="disabled")this.headers.add(this.headers.next())[c?"addClass":"removeClass"]("ui-accordion-disabled ui-state-disabled")},_keydown:function(d){if(!(this.options.disabled||d.altKey||d.ctrlKey)){var c=a.ui.keyCode,e=this.headers.length,h=this.headers.index(d.target),g=false;switch(d.keyCode){case c.RIGHT:case c.DOWN:g=this.headers[(h+1)%e];break;case c.LEFT:case c.UP:g=this.headers[(h-1+e)%e];break;case c.SPACE:case c.ENTER:this._clickHandler({target:d.target},d.target);
    +d.preventDefault()}if(g){a(d.target).attr("tabIndex",-1);a(g).attr("tabIndex",0);g.focus();return false}return true}},resize:function(){var d=this.options,c;if(d.fillSpace){if(a.browser.msie){var e=this.element.parent().css("overflow");this.element.parent().css("overflow","hidden")}c=this.element.parent().height();a.browser.msie&&this.element.parent().css("overflow",e);this.headers.each(function(){c-=a(this).outerHeight(true)});this.headers.next().each(function(){a(this).height(Math.max(0,c-a(this).innerHeight()+
    +a(this).height()))}).css("overflow","auto")}else if(d.autoHeight){c=0;this.headers.next().each(function(){c=Math.max(c,a(this).height("").height())}).height(c)}return this},activate:function(d){this.options.active=d;d=this._findActive(d)[0];this._clickHandler({target:d},d);return this},_findActive:function(d){return d?typeof d==="number"?this.headers.filter(":eq("+d+")"):this.headers.not(this.headers.not(d)):d===false?a([]):this.headers.filter(":eq(0)")},_clickHandler:function(d,c){var e=this.options;
    +if(!e.disabled)if(d.target){d=a(d.currentTarget||c);c=d[0]===this.active[0];e.active=e.collapsible&&c?false:this.headers.index(d);if(!(this.running||!e.collapsible&&c)){var h=this.active;f=d.next();i=this.active.next();b={options:e,newHeader:c&&e.collapsible?a([]):d,oldHeader:this.active,newContent:c&&e.collapsible?a([]):f,oldContent:i};var g=this.headers.index(this.active[0])>this.headers.index(d[0]);this.active=c?a([]):d;this._toggle(f,i,b,c,g);h.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").children(".ui-icon").removeClass(e.icons.headerSelected).addClass(e.icons.header);
    +if(!c){d.removeClass("ui-state-default ui-corner-all").addClass("ui-state-active ui-corner-top").children(".ui-icon").removeClass(e.icons.header).addClass(e.icons.headerSelected);d.next().addClass("ui-accordion-content-active")}}}else if(e.collapsible){this.active.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").children(".ui-icon").removeClass(e.icons.headerSelected).addClass(e.icons.header);this.active.next().addClass("ui-accordion-content-active");var i=this.active.next(),
    +b={options:e,newHeader:a([]),oldHeader:e.active,newContent:a([]),oldContent:i},f=this.active=a([]);this._toggle(f,i,b)}},_toggle:function(d,c,e,h,g){var i=this,b=i.options;i.toShow=d;i.toHide=c;i.data=e;var f=function(){if(i)return i._completed.apply(i,arguments)};i._trigger("changestart",null,i.data);i.running=c.size()===0?d.size():c.size();if(b.animated){e={};e=b.collapsible&&h?{toShow:a([]),toHide:c,complete:f,down:g,autoHeight:b.autoHeight||b.fillSpace}:{toShow:d,toHide:c,complete:f,down:g,autoHeight:b.autoHeight||
    +b.fillSpace};if(!b.proxied)b.proxied=b.animated;if(!b.proxiedDuration)b.proxiedDuration=b.duration;b.animated=a.isFunction(b.proxied)?b.proxied(e):b.proxied;b.duration=a.isFunction(b.proxiedDuration)?b.proxiedDuration(e):b.proxiedDuration;h=a.ui.accordion.animations;var j=b.duration,l=b.animated;if(l&&!h[l]&&!a.easing[l])l="slide";h[l]||(h[l]=function(o){this.slide(o,{easing:l,duration:j||700})});h[l](e)}else{if(b.collapsible&&h)d.toggle();else{c.hide();d.show()}f(true)}c.prev().attr({"aria-expanded":"false",
    +"aria-selected":"false",tabIndex:-1}).blur();d.prev().attr({"aria-expanded":"true","aria-selected":"true",tabIndex:0}).focus()},_completed:function(d){this.running=d?0:--this.running;if(!this.running){this.options.clearStyle&&this.toShow.add(this.toHide).css({height:"",overflow:""});this.toHide.removeClass("ui-accordion-content-active");if(this.toHide.length)this.toHide.parent()[0].className=this.toHide.parent()[0].className;this._trigger("change",null,this.data)}}});a.extend(a.ui.accordion,{version:"1.8.16",
    +animations:{slide:function(d,c){d=a.extend({easing:"swing",duration:300},d,c);if(d.toHide.size())if(d.toShow.size()){var e=d.toShow.css("overflow"),h=0,g={},i={},b;c=d.toShow;b=c[0].style.width;c.width(parseInt(c.parent().width(),10)-parseInt(c.css("paddingLeft"),10)-parseInt(c.css("paddingRight"),10)-(parseInt(c.css("borderLeftWidth"),10)||0)-(parseInt(c.css("borderRightWidth"),10)||0));a.each(["height","paddingTop","paddingBottom"],function(f,j){i[j]="hide";f=(""+a.css(d.toShow[0],j)).match(/^([\d+-.]+)(.*)$/);
    +g[j]={value:f[1],unit:f[2]||"px"}});d.toShow.css({height:0,overflow:"hidden"}).show();d.toHide.filter(":hidden").each(d.complete).end().filter(":visible").animate(i,{step:function(f,j){if(j.prop=="height")h=j.end-j.start===0?0:(j.now-j.start)/(j.end-j.start);d.toShow[0].style[j.prop]=h*g[j.prop].value+g[j.prop].unit},duration:d.duration,easing:d.easing,complete:function(){d.autoHeight||d.toShow.css("height","");d.toShow.css({width:b,overflow:e});d.complete()}})}else d.toHide.animate({height:"hide",
    +paddingTop:"hide",paddingBottom:"hide"},d);else d.toShow.animate({height:"show",paddingTop:"show",paddingBottom:"show"},d)},bounceslide:function(d){this.slide(d,{easing:d.down?"easeOutBounce":"swing",duration:d.down?1E3:200})}}})})(jQuery);
    +(function(a){var d=0;a.widget("ui.autocomplete",{options:{appendTo:"body",autoFocus:false,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null},pending:0,_create:function(){var c=this,e=this.element[0].ownerDocument,h;this.element.addClass("ui-autocomplete-input").attr("autocomplete","off").attr({role:"textbox","aria-autocomplete":"list","aria-haspopup":"true"}).bind("keydown.autocomplete",function(g){if(!(c.options.disabled||c.element.propAttr("readOnly"))){h=
    +false;var i=a.ui.keyCode;switch(g.keyCode){case i.PAGE_UP:c._move("previousPage",g);break;case i.PAGE_DOWN:c._move("nextPage",g);break;case i.UP:c._move("previous",g);g.preventDefault();break;case i.DOWN:c._move("next",g);g.preventDefault();break;case i.ENTER:case i.NUMPAD_ENTER:if(c.menu.active){h=true;g.preventDefault()}case i.TAB:if(!c.menu.active)return;c.menu.select(g);break;case i.ESCAPE:c.element.val(c.term);c.close(g);break;default:clearTimeout(c.searching);c.searching=setTimeout(function(){if(c.term!=
    +c.element.val()){c.selectedItem=null;c.search(null,g)}},c.options.delay);break}}}).bind("keypress.autocomplete",function(g){if(h){h=false;g.preventDefault()}}).bind("focus.autocomplete",function(){if(!c.options.disabled){c.selectedItem=null;c.previous=c.element.val()}}).bind("blur.autocomplete",function(g){if(!c.options.disabled){clearTimeout(c.searching);c.closing=setTimeout(function(){c.close(g);c._change(g)},150)}});this._initSource();this.response=function(){return c._response.apply(c,arguments)};
    +this.menu=a("<ul></ul>").addClass("ui-autocomplete").appendTo(a(this.options.appendTo||"body",e)[0]).mousedown(function(g){var i=c.menu.element[0];a(g.target).closest(".ui-menu-item").length||setTimeout(function(){a(document).one("mousedown",function(b){b.target!==c.element[0]&&b.target!==i&&!a.ui.contains(i,b.target)&&c.close()})},1);setTimeout(function(){clearTimeout(c.closing)},13)}).menu({focus:function(g,i){i=i.item.data("item.autocomplete");false!==c._trigger("focus",g,{item:i})&&/^key/.test(g.originalEvent.type)&&
    +c.element.val(i.value)},selected:function(g,i){var b=i.item.data("item.autocomplete"),f=c.previous;if(c.element[0]!==e.activeElement){c.element.focus();c.previous=f;setTimeout(function(){c.previous=f;c.selectedItem=b},1)}false!==c._trigger("select",g,{item:b})&&c.element.val(b.value);c.term=c.element.val();c.close(g);c.selectedItem=b},blur:function(){c.menu.element.is(":visible")&&c.element.val()!==c.term&&c.element.val(c.term)}}).zIndex(this.element.zIndex()+1).css({top:0,left:0}).hide().data("menu");
    +a.fn.bgiframe&&this.menu.element.bgiframe()},destroy:function(){this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete").removeAttr("role").removeAttr("aria-autocomplete").removeAttr("aria-haspopup");this.menu.element.remove();a.Widget.prototype.destroy.call(this)},_setOption:function(c,e){a.Widget.prototype._setOption.apply(this,arguments);c==="source"&&this._initSource();if(c==="appendTo")this.menu.element.appendTo(a(e||"body",this.element[0].ownerDocument)[0]);c==="disabled"&&
    +e&&this.xhr&&this.xhr.abort()},_initSource:function(){var c=this,e,h;if(a.isArray(this.options.source)){e=this.options.source;this.source=function(g,i){i(a.ui.autocomplete.filter(e,g.term))}}else if(typeof this.options.source==="string"){h=this.options.source;this.source=function(g,i){c.xhr&&c.xhr.abort();c.xhr=a.ajax({url:h,data:g,dataType:"json",autocompleteRequest:++d,success:function(b){this.autocompleteRequest===d&&i(b)},error:function(){this.autocompleteRequest===d&&i([])}})}}else this.source=
    +this.options.source},search:function(c,e){c=c!=null?c:this.element.val();this.term=this.element.val();if(c.length<this.options.minLength)return this.close(e);clearTimeout(this.closing);if(this._trigger("search",e)!==false)return this._search(c)},_search:function(c){this.pending++;this.element.addClass("ui-autocomplete-loading");this.source({term:c},this.response)},_response:function(c){if(!this.options.disabled&&c&&c.length){c=this._normalize(c);this._suggest(c);this._trigger("open")}else this.close();
    +this.pending--;this.pending||this.element.removeClass("ui-autocomplete-loading")},close:function(c){clearTimeout(this.closing);if(this.menu.element.is(":visible")){this.menu.element.hide();this.menu.deactivate();this._trigger("close",c)}},_change:function(c){this.previous!==this.element.val()&&this._trigger("change",c,{item:this.selectedItem})},_normalize:function(c){if(c.length&&c[0].label&&c[0].value)return c;return a.map(c,function(e){if(typeof e==="string")return{label:e,value:e};return a.extend({label:e.label||
    +e.value,value:e.value||e.label},e)})},_suggest:function(c){var e=this.menu.element.empty().zIndex(this.element.zIndex()+1);this._renderMenu(e,c);this.menu.deactivate();this.menu.refresh();e.show();this._resizeMenu();e.position(a.extend({of:this.element},this.options.position));this.options.autoFocus&&this.menu.next(new a.Event("mouseover"))},_resizeMenu:function(){var c=this.menu.element;c.outerWidth(Math.max(c.width("").outerWidth(),this.element.outerWidth()))},_renderMenu:function(c,e){var h=this;
    +a.each(e,function(g,i){h._renderItem(c,i)})},_renderItem:function(c,e){return a("<li></li>").data("item.autocomplete",e).append(a("<a></a>").text(e.label)).appendTo(c)},_move:function(c,e){if(this.menu.element.is(":visible"))if(this.menu.first()&&/^previous/.test(c)||this.menu.last()&&/^next/.test(c)){this.element.val(this.term);this.menu.deactivate()}else this.menu[c](e);else this.search(null,e)},widget:function(){return this.menu.element}});a.extend(a.ui.autocomplete,{escapeRegex:function(c){return c.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,
    +"\\$&")},filter:function(c,e){var h=new RegExp(a.ui.autocomplete.escapeRegex(e),"i");return a.grep(c,function(g){return h.test(g.label||g.value||g)})}})})(jQuery);
    +(function(a){a.widget("ui.menu",{_create:function(){var d=this;this.element.addClass("ui-menu ui-widget ui-widget-content ui-corner-all").attr({role:"listbox","aria-activedescendant":"ui-active-menuitem"}).click(function(c){if(a(c.target).closest(".ui-menu-item a").length){c.preventDefault();d.select(c)}});this.refresh()},refresh:function(){var d=this;this.element.children("li:not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","menuitem").children("a").addClass("ui-corner-all").attr("tabindex",
    +-1).mouseenter(function(c){d.activate(c,a(this).parent())}).mouseleave(function(){d.deactivate()})},activate:function(d,c){this.deactivate();if(this.hasScroll()){var e=c.offset().top-this.element.offset().top,h=this.element.scrollTop(),g=this.element.height();if(e<0)this.element.scrollTop(h+e);else e>=g&&this.element.scrollTop(h+e-g+c.height())}this.active=c.eq(0).children("a").addClass("ui-state-hover").attr("id","ui-active-menuitem").end();this._trigger("focus",d,{item:c})},deactivate:function(){if(this.active){this.active.children("a").removeClass("ui-state-hover").removeAttr("id");
    +this._trigger("blur");this.active=null}},next:function(d){this.move("next",".ui-menu-item:first",d)},previous:function(d){this.move("prev",".ui-menu-item:last",d)},first:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},last:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},move:function(d,c,e){if(this.active){d=this.active[d+"All"](".ui-menu-item").eq(0);d.length?this.activate(e,d):this.activate(e,this.element.children(c))}else this.activate(e,
    +this.element.children(c))},nextPage:function(d){if(this.hasScroll())if(!this.active||this.last())this.activate(d,this.element.children(".ui-menu-item:first"));else{var c=this.active.offset().top,e=this.element.height(),h=this.element.children(".ui-menu-item").filter(function(){var g=a(this).offset().top-c-e+a(this).height();return g<10&&g>-10});h.length||(h=this.element.children(".ui-menu-item:last"));this.activate(d,h)}else this.activate(d,this.element.children(".ui-menu-item").filter(!this.active||
    +this.last()?":first":":last"))},previousPage:function(d){if(this.hasScroll())if(!this.active||this.first())this.activate(d,this.element.children(".ui-menu-item:last"));else{var c=this.active.offset().top,e=this.element.height();result=this.element.children(".ui-menu-item").filter(function(){var h=a(this).offset().top-c+e-a(this).height();return h<10&&h>-10});result.length||(result=this.element.children(".ui-menu-item:first"));this.activate(d,result)}else this.activate(d,this.element.children(".ui-menu-item").filter(!this.active||
    +this.first()?":last":":first"))},hasScroll:function(){return this.element.height()<this.element[a.fn.prop?"prop":"attr"]("scrollHeight")},select:function(d){this._trigger("selected",d,{item:this.active})}})})(jQuery);
    +(function(a){var d,c,e,h,g=function(){var b=a(this).find(":ui-button");setTimeout(function(){b.button("refresh")},1)},i=function(b){var f=b.name,j=b.form,l=a([]);if(f)l=j?a(j).find("[name='"+f+"']"):a("[name='"+f+"']",b.ownerDocument).filter(function(){return!this.form});return l};a.widget("ui.button",{options:{disabled:null,text:true,label:null,icons:{primary:null,secondary:null}},_create:function(){this.element.closest("form").unbind("reset.button").bind("reset.button",g);if(typeof this.options.disabled!==
    +"boolean")this.options.disabled=this.element.propAttr("disabled");this._determineButtonType();this.hasTitle=!!this.buttonElement.attr("title");var b=this,f=this.options,j=this.type==="checkbox"||this.type==="radio",l="ui-state-hover"+(!j?" ui-state-active":"");if(f.label===null)f.label=this.buttonElement.html();if(this.element.is(":disabled"))f.disabled=true;this.buttonElement.addClass("ui-button ui-widget ui-state-default ui-corner-all").attr("role","button").bind("mouseenter.button",function(){if(!f.disabled){a(this).addClass("ui-state-hover");
    +this===d&&a(this).addClass("ui-state-active")}}).bind("mouseleave.button",function(){f.disabled||a(this).removeClass(l)}).bind("click.button",function(o){if(f.disabled){o.preventDefault();o.stopImmediatePropagation()}});this.element.bind("focus.button",function(){b.buttonElement.addClass("ui-state-focus")}).bind("blur.button",function(){b.buttonElement.removeClass("ui-state-focus")});if(j){this.element.bind("change.button",function(){h||b.refresh()});this.buttonElement.bind("mousedown.button",function(o){if(!f.disabled){h=
    +false;c=o.pageX;e=o.pageY}}).bind("mouseup.button",function(o){if(!f.disabled)if(c!==o.pageX||e!==o.pageY)h=true})}if(this.type==="checkbox")this.buttonElement.bind("click.button",function(){if(f.disabled||h)return false;a(this).toggleClass("ui-state-active");b.buttonElement.attr("aria-pressed",b.element[0].checked)});else if(this.type==="radio")this.buttonElement.bind("click.button",function(){if(f.disabled||h)return false;a(this).addClass("ui-state-active");b.buttonElement.attr("aria-pressed","true");
    +var o=b.element[0];i(o).not(o).map(function(){return a(this).button("widget")[0]}).removeClass("ui-state-active").attr("aria-pressed","false")});else{this.buttonElement.bind("mousedown.button",function(){if(f.disabled)return false;a(this).addClass("ui-state-active");d=this;a(document).one("mouseup",function(){d=null})}).bind("mouseup.button",function(){if(f.disabled)return false;a(this).removeClass("ui-state-active")}).bind("keydown.button",function(o){if(f.disabled)return false;if(o.keyCode==a.ui.keyCode.SPACE||
    +o.keyCode==a.ui.keyCode.ENTER)a(this).addClass("ui-state-active")}).bind("keyup.button",function(){a(this).removeClass("ui-state-active")});this.buttonElement.is("a")&&this.buttonElement.keyup(function(o){o.keyCode===a.ui.keyCode.SPACE&&a(this).click()})}this._setOption("disabled",f.disabled);this._resetButton()},_determineButtonType:function(){this.type=this.element.is(":checkbox")?"checkbox":this.element.is(":radio")?"radio":this.element.is("input")?"input":"button";if(this.type==="checkbox"||this.type===
    +"radio"){var b=this.element.parents().filter(":last"),f="label[for='"+this.element.attr("id")+"']";this.buttonElement=b.find(f);if(!this.buttonElement.length){b=b.length?b.siblings():this.element.siblings();this.buttonElement=b.filter(f);if(!this.buttonElement.length)this.buttonElement=b.find(f)}this.element.addClass("ui-helper-hidden-accessible");(b=this.element.is(":checked"))&&this.buttonElement.addClass("ui-state-active");this.buttonElement.attr("aria-pressed",b)}else this.buttonElement=this.element},
    +widget:function(){return this.buttonElement},destroy:function(){this.element.removeClass("ui-helper-hidden-accessible");this.buttonElement.removeClass("ui-button ui-widget ui-state-default ui-corner-all ui-state-hover ui-state-active  ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only").removeAttr("role").removeAttr("aria-pressed").html(this.buttonElement.find(".ui-button-text").html());this.hasTitle||this.buttonElement.removeAttr("title");
    +a.Widget.prototype.destroy.call(this)},_setOption:function(b,f){a.Widget.prototype._setOption.apply(this,arguments);if(b==="disabled")f?this.element.propAttr("disabled",true):this.element.propAttr("disabled",false);else this._resetButton()},refresh:function(){var b=this.element.is(":disabled");b!==this.options.disabled&&this._setOption("disabled",b);if(this.type==="radio")i(this.element[0]).each(function(){a(this).is(":checked")?a(this).button("widget").addClass("ui-state-active").attr("aria-pressed",
    +"true"):a(this).button("widget").removeClass("ui-state-active").attr("aria-pressed","false")});else if(this.type==="checkbox")this.element.is(":checked")?this.buttonElement.addClass("ui-state-active").attr("aria-pressed","true"):this.buttonElement.removeClass("ui-state-active").attr("aria-pressed","false")},_resetButton:function(){if(this.type==="input")this.options.label&&this.element.val(this.options.label);else{var b=this.buttonElement.removeClass("ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only"),
    +f=a("<span></span>").addClass("ui-button-text").html(this.options.label).appendTo(b.empty()).text(),j=this.options.icons,l=j.primary&&j.secondary,o=[];if(j.primary||j.secondary){if(this.options.text)o.push("ui-button-text-icon"+(l?"s":j.primary?"-primary":"-secondary"));j.primary&&b.prepend("<span class='ui-button-icon-primary ui-icon "+j.primary+"'></span>");j.secondary&&b.append("<span class='ui-button-icon-secondary ui-icon "+j.secondary+"'></span>");if(!this.options.text){o.push(l?"ui-button-icons-only":
    +"ui-button-icon-only");this.hasTitle||b.attr("title",f)}}else o.push("ui-button-text-only");b.addClass(o.join(" "))}}});a.widget("ui.buttonset",{options:{items:":button, :submit, :reset, :checkbox, :radio, a, :data(button)"},_create:function(){this.element.addClass("ui-buttonset")},_init:function(){this.refresh()},_setOption:function(b,f){b==="disabled"&&this.buttons.button("option",b,f);a.Widget.prototype._setOption.apply(this,arguments)},refresh:function(){var b=this.element.css("direction")===
    +"ltr";this.buttons=this.element.find(this.options.items).filter(":ui-button").button("refresh").end().not(":ui-button").button().end().map(function(){return a(this).button("widget")[0]}).removeClass("ui-corner-all ui-corner-left ui-corner-right").filter(":first").addClass(b?"ui-corner-left":"ui-corner-right").end().filter(":last").addClass(b?"ui-corner-right":"ui-corner-left").end().end()},destroy:function(){this.element.removeClass("ui-buttonset");this.buttons.map(function(){return a(this).button("widget")[0]}).removeClass("ui-corner-left ui-corner-right").end().button("destroy");
    +a.Widget.prototype.destroy.call(this)}})})(jQuery);
    +(function(a,d){function c(){this.debug=false;this._curInst=null;this._keyEvent=false;this._disabledInputs=[];this._inDialog=this._datepickerShowing=false;this._mainDivId="ui-datepicker-div";this._inlineClass="ui-datepicker-inline";this._appendClass="ui-datepicker-append";this._triggerClass="ui-datepicker-trigger";this._dialogClass="ui-datepicker-dialog";this._disableClass="ui-datepicker-disabled";this._unselectableClass="ui-datepicker-unselectable";this._currentClass="ui-datepicker-current-day";this._dayOverClass=
    +"ui-datepicker-days-cell-over";this.regional=[];this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su",
    +"Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"mm/dd/yy",firstDay:0,isRTL:false,showMonthAfterYear:false,yearSuffix:""};this._defaults={showOn:"focus",showAnim:"fadeIn",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:false,hideIfNoPrevNext:false,navigationAsDateFormat:false,gotoCurrent:false,changeMonth:false,changeYear:false,yearRange:"c-10:c+10",showOtherMonths:false,selectOtherMonths:false,showWeek:false,calculateWeek:this.iso8601Week,shortYearCutoff:"+10",
    +minDate:null,maxDate:null,duration:"fast",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:true,showButtonPanel:false,autoSize:false,disabled:false};a.extend(this._defaults,this.regional[""]);this.dpDiv=e(a('<div id="'+this._mainDivId+'" class="ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>'))}function e(b){return b.bind("mouseout",
    +function(f){f=a(f.target).closest("button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a");f.length&&f.removeClass("ui-state-hover ui-datepicker-prev-hover ui-datepicker-next-hover")}).bind("mouseover",function(f){f=a(f.target).closest("button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a");if(!(a.datepicker._isDisabledDatepicker(i.inline?b.parent()[0]:i.input[0])||!f.length)){f.parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover");
    +f.addClass("ui-state-hover");f.hasClass("ui-datepicker-prev")&&f.addClass("ui-datepicker-prev-hover");f.hasClass("ui-datepicker-next")&&f.addClass("ui-datepicker-next-hover")}})}function h(b,f){a.extend(b,f);for(var j in f)if(f[j]==null||f[j]==d)b[j]=f[j];return b}a.extend(a.ui,{datepicker:{version:"1.8.16"}});var g=(new Date).getTime(),i;a.extend(c.prototype,{markerClassName:"hasDatepicker",maxRows:4,log:function(){this.debug&&console.log.apply("",arguments)},_widgetDatepicker:function(){return this.dpDiv},
    +setDefaults:function(b){h(this._defaults,b||{});return this},_attachDatepicker:function(b,f){var j=null;for(var l in this._defaults){var o=b.getAttribute("date:"+l);if(o){j=j||{};try{j[l]=eval(o)}catch(n){j[l]=o}}}l=b.nodeName.toLowerCase();o=l=="div"||l=="span";if(!b.id){this.uuid+=1;b.id="dp"+this.uuid}var k=this._newInst(a(b),o);k.settings=a.extend({},f||{},j||{});if(l=="input")this._connectDatepicker(b,k);else o&&this._inlineDatepicker(b,k)},_newInst:function(b,f){return{id:b[0].id.replace(/([^A-Za-z0-9_-])/g,
    +"\\\\$1"),input:b,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:f,dpDiv:!f?this.dpDiv:e(a('<div class="'+this._inlineClass+' ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>'))}},_connectDatepicker:function(b,f){var j=a(b);f.append=a([]);f.trigger=a([]);if(!j.hasClass(this.markerClassName)){this._attachments(j,f);j.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).keyup(this._doKeyUp).bind("setData.datepicker",
    +function(l,o,n){f.settings[o]=n}).bind("getData.datepicker",function(l,o){return this._get(f,o)});this._autoSize(f);a.data(b,"datepicker",f);f.settings.disabled&&this._disableDatepicker(b)}},_attachments:function(b,f){var j=this._get(f,"appendText"),l=this._get(f,"isRTL");f.append&&f.append.remove();if(j){f.append=a('<span class="'+this._appendClass+'">'+j+"</span>");b[l?"before":"after"](f.append)}b.unbind("focus",this._showDatepicker);f.trigger&&f.trigger.remove();j=this._get(f,"showOn");if(j==
    +"focus"||j=="both")b.focus(this._showDatepicker);if(j=="button"||j=="both"){j=this._get(f,"buttonText");var o=this._get(f,"buttonImage");f.trigger=a(this._get(f,"buttonImageOnly")?a("<img/>").addClass(this._triggerClass).attr({src:o,alt:j,title:j}):a('<button type="button"></button>').addClass(this._triggerClass).html(o==""?j:a("<img/>").attr({src:o,alt:j,title:j})));b[l?"before":"after"](f.trigger);f.trigger.click(function(){a.datepicker._datepickerShowing&&a.datepicker._lastInput==b[0]?a.datepicker._hideDatepicker():
    +a.datepicker._showDatepicker(b[0]);return false})}},_autoSize:function(b){if(this._get(b,"autoSize")&&!b.inline){var f=new Date(2009,11,20),j=this._get(b,"dateFormat");if(j.match(/[DM]/)){var l=function(o){for(var n=0,k=0,m=0;m<o.length;m++)if(o[m].length>n){n=o[m].length;k=m}return k};f.setMonth(l(this._get(b,j.match(/MM/)?"monthNames":"monthNamesShort")));f.setDate(l(this._get(b,j.match(/DD/)?"dayNames":"dayNamesShort"))+20-f.getDay())}b.input.attr("size",this._formatDate(b,f).length)}},_inlineDatepicker:function(b,
    +f){var j=a(b);if(!j.hasClass(this.markerClassName)){j.addClass(this.markerClassName).append(f.dpDiv).bind("setData.datepicker",function(l,o,n){f.settings[o]=n}).bind("getData.datepicker",function(l,o){return this._get(f,o)});a.data(b,"datepicker",f);this._setDate(f,this._getDefaultDate(f),true);this._updateDatepicker(f);this._updateAlternate(f);f.settings.disabled&&this._disableDatepicker(b);f.dpDiv.css("display","block")}},_dialogDatepicker:function(b,f,j,l,o){b=this._dialogInst;if(!b){this.uuid+=
    +1;this._dialogInput=a('<input type="text" id="'+("dp"+this.uuid)+'" style="position: absolute; top: -100px; width: 0px; z-index: -10;"/>');this._dialogInput.keydown(this._doKeyDown);a("body").append(this._dialogInput);b=this._dialogInst=this._newInst(this._dialogInput,false);b.settings={};a.data(this._dialogInput[0],"datepicker",b)}h(b.settings,l||{});f=f&&f.constructor==Date?this._formatDate(b,f):f;this._dialogInput.val(f);this._pos=o?o.length?o:[o.pageX,o.pageY]:null;if(!this._pos)this._pos=[document.documentElement.clientWidth/
    +2-100+(document.documentElement.scrollLeft||document.body.scrollLeft),document.documentElement.clientHeight/2-150+(document.documentElement.scrollTop||document.body.scrollTop)];this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px");b.settings.onSelect=j;this._inDialog=true;this.dpDiv.addClass(this._dialogClass);this._showDatepicker(this._dialogInput[0]);a.blockUI&&a.blockUI(this.dpDiv);a.data(this._dialogInput[0],"datepicker",b);return this},_destroyDatepicker:function(b){var f=
    +a(b),j=a.data(b,"datepicker");if(f.hasClass(this.markerClassName)){var l=b.nodeName.toLowerCase();a.removeData(b,"datepicker");if(l=="input"){j.append.remove();j.trigger.remove();f.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup",this._doKeyUp)}else if(l=="div"||l=="span")f.removeClass(this.markerClassName).empty()}},_enableDatepicker:function(b){var f=a(b),j=a.data(b,"datepicker");if(f.hasClass(this.markerClassName)){var l=
    +b.nodeName.toLowerCase();if(l=="input"){b.disabled=false;j.trigger.filter("button").each(function(){this.disabled=false}).end().filter("img").css({opacity:"1.0",cursor:""})}else if(l=="div"||l=="span"){f=f.children("."+this._inlineClass);f.children().removeClass("ui-state-disabled");f.find("select.ui-datepicker-month, select.ui-datepicker-year").removeAttr("disabled")}this._disabledInputs=a.map(this._disabledInputs,function(o){return o==b?null:o})}},_disableDatepicker:function(b){var f=a(b),j=a.data(b,
    +"datepicker");if(f.hasClass(this.markerClassName)){var l=b.nodeName.toLowerCase();if(l=="input"){b.disabled=true;j.trigger.filter("button").each(function(){this.disabled=true}).end().filter("img").css({opacity:"0.5",cursor:"default"})}else if(l=="div"||l=="span"){f=f.children("."+this._inlineClass);f.children().addClass("ui-state-disabled");f.find("select.ui-datepicker-month, select.ui-datepicker-year").attr("disabled","disabled")}this._disabledInputs=a.map(this._disabledInputs,function(o){return o==
    +b?null:o});this._disabledInputs[this._disabledInputs.length]=b}},_isDisabledDatepicker:function(b){if(!b)return false;for(var f=0;f<this._disabledInputs.length;f++)if(this._disabledInputs[f]==b)return true;return false},_getInst:function(b){try{return a.data(b,"datepicker")}catch(f){throw"Missing instance data for this datepicker";}},_optionDatepicker:function(b,f,j){var l=this._getInst(b);if(arguments.length==2&&typeof f=="string")return f=="defaults"?a.extend({},a.datepicker._defaults):l?f=="all"?
    +a.extend({},l.settings):this._get(l,f):null;var o=f||{};if(typeof f=="string"){o={};o[f]=j}if(l){this._curInst==l&&this._hideDatepicker();var n=this._getDateDatepicker(b,true),k=this._getMinMaxDate(l,"min"),m=this._getMinMaxDate(l,"max");h(l.settings,o);if(k!==null&&o.dateFormat!==d&&o.minDate===d)l.settings.minDate=this._formatDate(l,k);if(m!==null&&o.dateFormat!==d&&o.maxDate===d)l.settings.maxDate=this._formatDate(l,m);this._attachments(a(b),l);this._autoSize(l);this._setDate(l,n);this._updateAlternate(l);
    +this._updateDatepicker(l)}},_changeDatepicker:function(b,f,j){this._optionDatepicker(b,f,j)},_refreshDatepicker:function(b){(b=this._getInst(b))&&this._updateDatepicker(b)},_setDateDatepicker:function(b,f){if(b=this._getInst(b)){this._setDate(b,f);this._updateDatepicker(b);this._updateAlternate(b)}},_getDateDatepicker:function(b,f){(b=this._getInst(b))&&!b.inline&&this._setDateFromField(b,f);return b?this._getDate(b):null},_doKeyDown:function(b){var f=a.datepicker._getInst(b.target),j=true,l=f.dpDiv.is(".ui-datepicker-rtl");
    +f._keyEvent=true;if(a.datepicker._datepickerShowing)switch(b.keyCode){case 9:a.datepicker._hideDatepicker();j=false;break;case 13:j=a("td."+a.datepicker._dayOverClass+":not(."+a.datepicker._currentClass+")",f.dpDiv);j[0]&&a.datepicker._selectDay(b.target,f.selectedMonth,f.selectedYear,j[0]);if(b=a.datepicker._get(f,"onSelect")){j=a.datepicker._formatDate(f);b.apply(f.input?f.input[0]:null,[j,f])}else a.datepicker._hideDatepicker();return false;case 27:a.datepicker._hideDatepicker();break;case 33:a.datepicker._adjustDate(b.target,
    +b.ctrlKey?-a.datepicker._get(f,"stepBigMonths"):-a.datepicker._get(f,"stepMonths"),"M");break;case 34:a.datepicker._adjustDate(b.target,b.ctrlKey?+a.datepicker._get(f,"stepBigMonths"):+a.datepicker._get(f,"stepMonths"),"M");break;case 35:if(b.ctrlKey||b.metaKey)a.datepicker._clearDate(b.target);j=b.ctrlKey||b.metaKey;break;case 36:if(b.ctrlKey||b.metaKey)a.datepicker._gotoToday(b.target);j=b.ctrlKey||b.metaKey;break;case 37:if(b.ctrlKey||b.metaKey)a.datepicker._adjustDate(b.target,l?+1:-1,"D");j=
    +b.ctrlKey||b.metaKey;if(b.originalEvent.altKey)a.datepicker._adjustDate(b.target,b.ctrlKey?-a.datepicker._get(f,"stepBigMonths"):-a.datepicker._get(f,"stepMonths"),"M");break;case 38:if(b.ctrlKey||b.metaKey)a.datepicker._adjustDate(b.target,-7,"D");j=b.ctrlKey||b.metaKey;break;case 39:if(b.ctrlKey||b.metaKey)a.datepicker._adjustDate(b.target,l?-1:+1,"D");j=b.ctrlKey||b.metaKey;if(b.originalEvent.altKey)a.datepicker._adjustDate(b.target,b.ctrlKey?+a.datepicker._get(f,"stepBigMonths"):+a.datepicker._get(f,
    +"stepMonths"),"M");break;case 40:if(b.ctrlKey||b.metaKey)a.datepicker._adjustDate(b.target,+7,"D");j=b.ctrlKey||b.metaKey;break;default:j=false}else if(b.keyCode==36&&b.ctrlKey)a.datepicker._showDatepicker(this);else j=false;if(j){b.preventDefault();b.stopPropagation()}},_doKeyPress:function(b){var f=a.datepicker._getInst(b.target);if(a.datepicker._get(f,"constrainInput")){f=a.datepicker._possibleChars(a.datepicker._get(f,"dateFormat"));var j=String.fromCharCode(b.charCode==d?b.keyCode:b.charCode);
    +return b.ctrlKey||b.metaKey||j<" "||!f||f.indexOf(j)>-1}},_doKeyUp:function(b){b=a.datepicker._getInst(b.target);if(b.input.val()!=b.lastVal)try{if(a.datepicker.parseDate(a.datepicker._get(b,"dateFormat"),b.input?b.input.val():null,a.datepicker._getFormatConfig(b))){a.datepicker._setDateFromField(b);a.datepicker._updateAlternate(b);a.datepicker._updateDatepicker(b)}}catch(f){a.datepicker.log(f)}return true},_showDatepicker:function(b){b=b.target||b;if(b.nodeName.toLowerCase()!="input")b=a("input",
    +b.parentNode)[0];if(!(a.datepicker._isDisabledDatepicker(b)||a.datepicker._lastInput==b)){var f=a.datepicker._getInst(b);if(a.datepicker._curInst&&a.datepicker._curInst!=f){a.datepicker._datepickerShowing&&a.datepicker._triggerOnClose(a.datepicker._curInst);a.datepicker._curInst.dpDiv.stop(true,true)}var j=a.datepicker._get(f,"beforeShow");j=j?j.apply(b,[b,f]):{};if(j!==false){h(f.settings,j);f.lastVal=null;a.datepicker._lastInput=b;a.datepicker._setDateFromField(f);if(a.datepicker._inDialog)b.value=
    +"";if(!a.datepicker._pos){a.datepicker._pos=a.datepicker._findPos(b);a.datepicker._pos[1]+=b.offsetHeight}var l=false;a(b).parents().each(function(){l|=a(this).css("position")=="fixed";return!l});if(l&&a.browser.opera){a.datepicker._pos[0]-=document.documentElement.scrollLeft;a.datepicker._pos[1]-=document.documentElement.scrollTop}j={left:a.datepicker._pos[0],top:a.datepicker._pos[1]};a.datepicker._pos=null;f.dpDiv.empty();f.dpDiv.css({position:"absolute",display:"block",top:"-1000px"});a.datepicker._updateDatepicker(f);
    +j=a.datepicker._checkOffset(f,j,l);f.dpDiv.css({position:a.datepicker._inDialog&&a.blockUI?"static":l?"fixed":"absolute",display:"none",left:j.left+"px",top:j.top+"px"});if(!f.inline){j=a.datepicker._get(f,"showAnim");var o=a.datepicker._get(f,"duration"),n=function(){var k=f.dpDiv.find("iframe.ui-datepicker-cover");if(k.length){var m=a.datepicker._getBorders(f.dpDiv);k.css({left:-m[0],top:-m[1],width:f.dpDiv.outerWidth(),height:f.dpDiv.outerHeight()})}};f.dpDiv.zIndex(a(b).zIndex()+1);a.datepicker._datepickerShowing=
    +true;a.effects&&a.effects[j]?f.dpDiv.show(j,a.datepicker._get(f,"showOptions"),o,n):f.dpDiv[j||"show"](j?o:null,n);if(!j||!o)n();f.input.is(":visible")&&!f.input.is(":disabled")&&f.input.focus();a.datepicker._curInst=f}}}},_updateDatepicker:function(b){this.maxRows=4;var f=a.datepicker._getBorders(b.dpDiv);i=b;b.dpDiv.empty().append(this._generateHTML(b));var j=b.dpDiv.find("iframe.ui-datepicker-cover");j.length&&j.css({left:-f[0],top:-f[1],width:b.dpDiv.outerWidth(),height:b.dpDiv.outerHeight()});
    +b.dpDiv.find("."+this._dayOverClass+" a").mouseover();f=this._getNumberOfMonths(b);j=f[1];b.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width("");j>1&&b.dpDiv.addClass("ui-datepicker-multi-"+j).css("width",17*j+"em");b.dpDiv[(f[0]!=1||f[1]!=1?"add":"remove")+"Class"]("ui-datepicker-multi");b.dpDiv[(this._get(b,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl");b==a.datepicker._curInst&&a.datepicker._datepickerShowing&&b.input&&b.input.is(":visible")&&
    +!b.input.is(":disabled")&&b.input[0]!=document.activeElement&&b.input.focus();if(b.yearshtml){var l=b.yearshtml;setTimeout(function(){l===b.yearshtml&&b.yearshtml&&b.dpDiv.find("select.ui-datepicker-year:first").replaceWith(b.yearshtml);l=b.yearshtml=null},0)}},_getBorders:function(b){var f=function(j){return{thin:1,medium:2,thick:3}[j]||j};return[parseFloat(f(b.css("border-left-width"))),parseFloat(f(b.css("border-top-width")))]},_checkOffset:function(b,f,j){var l=b.dpDiv.outerWidth(),o=b.dpDiv.outerHeight(),
    +n=b.input?b.input.outerWidth():0,k=b.input?b.input.outerHeight():0,m=document.documentElement.clientWidth+a(document).scrollLeft(),p=document.documentElement.clientHeight+a(document).scrollTop();f.left-=this._get(b,"isRTL")?l-n:0;f.left-=j&&f.left==b.input.offset().left?a(document).scrollLeft():0;f.top-=j&&f.top==b.input.offset().top+k?a(document).scrollTop():0;f.left-=Math.min(f.left,f.left+l>m&&m>l?Math.abs(f.left+l-m):0);f.top-=Math.min(f.top,f.top+o>p&&p>o?Math.abs(o+k):0);return f},_findPos:function(b){for(var f=
    +this._get(this._getInst(b),"isRTL");b&&(b.type=="hidden"||b.nodeType!=1||a.expr.filters.hidden(b));)b=b[f?"previousSibling":"nextSibling"];b=a(b).offset();return[b.left,b.top]},_triggerOnClose:function(b){var f=this._get(b,"onClose");if(f)f.apply(b.input?b.input[0]:null,[b.input?b.input.val():"",b])},_hideDatepicker:function(b){var f=this._curInst;if(!(!f||b&&f!=a.data(b,"datepicker")))if(this._datepickerShowing){b=this._get(f,"showAnim");var j=this._get(f,"duration"),l=function(){a.datepicker._tidyDialog(f);
    +this._curInst=null};a.effects&&a.effects[b]?f.dpDiv.hide(b,a.datepicker._get(f,"showOptions"),j,l):f.dpDiv[b=="slideDown"?"slideUp":b=="fadeIn"?"fadeOut":"hide"](b?j:null,l);b||l();a.datepicker._triggerOnClose(f);this._datepickerShowing=false;this._lastInput=null;if(this._inDialog){this._dialogInput.css({position:"absolute",left:"0",top:"-100px"});if(a.blockUI){a.unblockUI();a("body").append(this.dpDiv)}}this._inDialog=false}},_tidyDialog:function(b){b.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")},
    +_checkExternalClick:function(b){if(a.datepicker._curInst){b=a(b.target);b[0].id!=a.datepicker._mainDivId&&b.parents("#"+a.datepicker._mainDivId).length==0&&!b.hasClass(a.datepicker.markerClassName)&&!b.hasClass(a.datepicker._triggerClass)&&a.datepicker._datepickerShowing&&!(a.datepicker._inDialog&&a.blockUI)&&a.datepicker._hideDatepicker()}},_adjustDate:function(b,f,j){b=a(b);var l=this._getInst(b[0]);if(!this._isDisabledDatepicker(b[0])){this._adjustInstDate(l,f+(j=="M"?this._get(l,"showCurrentAtPos"):
    +0),j);this._updateDatepicker(l)}},_gotoToday:function(b){b=a(b);var f=this._getInst(b[0]);if(this._get(f,"gotoCurrent")&&f.currentDay){f.selectedDay=f.currentDay;f.drawMonth=f.selectedMonth=f.currentMonth;f.drawYear=f.selectedYear=f.currentYear}else{var j=new Date;f.selectedDay=j.getDate();f.drawMonth=f.selectedMonth=j.getMonth();f.drawYear=f.selectedYear=j.getFullYear()}this._notifyChange(f);this._adjustDate(b)},_selectMonthYear:function(b,f,j){b=a(b);var l=this._getInst(b[0]);l["selected"+(j=="M"?
    +"Month":"Year")]=l["draw"+(j=="M"?"Month":"Year")]=parseInt(f.options[f.selectedIndex].value,10);this._notifyChange(l);this._adjustDate(b)},_selectDay:function(b,f,j,l){var o=a(b);if(!(a(l).hasClass(this._unselectableClass)||this._isDisabledDatepicker(o[0]))){o=this._getInst(o[0]);o.selectedDay=o.currentDay=a("a",l).html();o.selectedMonth=o.currentMonth=f;o.selectedYear=o.currentYear=j;this._selectDate(b,this._formatDate(o,o.currentDay,o.currentMonth,o.currentYear))}},_clearDate:function(b){b=a(b);
    +this._getInst(b[0]);this._selectDate(b,"")},_selectDate:function(b,f){b=this._getInst(a(b)[0]);f=f!=null?f:this._formatDate(b);b.input&&b.input.val(f);this._updateAlternate(b);var j=this._get(b,"onSelect");if(j)j.apply(b.input?b.input[0]:null,[f,b]);else b.input&&b.input.trigger("change");if(b.inline)this._updateDatepicker(b);else{this._hideDatepicker();this._lastInput=b.input[0];typeof b.input[0]!="object"&&b.input.focus();this._lastInput=null}},_updateAlternate:function(b){var f=this._get(b,"altField");
    +if(f){var j=this._get(b,"altFormat")||this._get(b,"dateFormat"),l=this._getDate(b),o=this.formatDate(j,l,this._getFormatConfig(b));a(f).each(function(){a(this).val(o)})}},noWeekends:function(b){b=b.getDay();return[b>0&&b<6,""]},iso8601Week:function(b){b=new Date(b.getTime());b.setDate(b.getDate()+4-(b.getDay()||7));var f=b.getTime();b.setMonth(0);b.setDate(1);return Math.floor(Math.round((f-b)/864E5)/7)+1},parseDate:function(b,f,j){if(b==null||f==null)throw"Invalid arguments";f=typeof f=="object"?
    +f.toString():f+"";if(f=="")return null;var l=(j?j.shortYearCutoff:null)||this._defaults.shortYearCutoff;l=typeof l!="string"?l:(new Date).getFullYear()%100+parseInt(l,10);for(var o=(j?j.dayNamesShort:null)||this._defaults.dayNamesShort,n=(j?j.dayNames:null)||this._defaults.dayNames,k=(j?j.monthNamesShort:null)||this._defaults.monthNamesShort,m=(j?j.monthNames:null)||this._defaults.monthNames,p=j=-1,q=-1,s=-1,r=false,u=function(z){(z=H+1<b.length&&b.charAt(H+1)==z)&&H++;return z},v=function(z){var I=
    +u(z);z=new RegExp("^\\d{1,"+(z=="@"?14:z=="!"?20:z=="y"&&I?4:z=="o"?3:2)+"}");z=f.substring(y).match(z);if(!z)throw"Missing number at position "+y;y+=z[0].length;return parseInt(z[0],10)},w=function(z,I,N){z=a.map(u(z)?N:I,function(D,E){return[[E,D]]}).sort(function(D,E){return-(D[1].length-E[1].length)});var J=-1;a.each(z,function(D,E){D=E[1];if(f.substr(y,D.length).toLowerCase()==D.toLowerCase()){J=E[0];y+=D.length;return false}});if(J!=-1)return J+1;else throw"Unknown name at position "+y;},x=
    +function(){if(f.charAt(y)!=b.charAt(H))throw"Unexpected literal at position "+y;y++},y=0,H=0;H<b.length;H++)if(r)if(b.charAt(H)=="'"&&!u("'"))r=false;else x();else switch(b.charAt(H)){case "d":q=v("d");break;case "D":w("D",o,n);break;case "o":s=v("o");break;case "m":p=v("m");break;case "M":p=w("M",k,m);break;case "y":j=v("y");break;case "@":var C=new Date(v("@"));j=C.getFullYear();p=C.getMonth()+1;q=C.getDate();break;case "!":C=new Date((v("!")-this._ticksTo1970)/1E4);j=C.getFullYear();p=C.getMonth()+
    +1;q=C.getDate();break;case "'":if(u("'"))x();else r=true;break;default:x()}if(y<f.length)throw"Extra/unparsed characters found in date: "+f.substring(y);if(j==-1)j=(new Date).getFullYear();else if(j<100)j+=(new Date).getFullYear()-(new Date).getFullYear()%100+(j<=l?0:-100);if(s>-1){p=1;q=s;do{l=this._getDaysInMonth(j,p-1);if(q<=l)break;p++;q-=l}while(1)}C=this._daylightSavingAdjust(new Date(j,p-1,q));if(C.getFullYear()!=j||C.getMonth()+1!=p||C.getDate()!=q)throw"Invalid date";return C},ATOM:"yy-mm-dd",
    +COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925))*24*60*60*1E7,formatDate:function(b,f,j){if(!f)return"";var l=(j?j.dayNamesShort:null)||this._defaults.dayNamesShort,o=(j?j.dayNames:null)||this._defaults.dayNames,n=(j?j.monthNamesShort:null)||this._defaults.monthNamesShort;j=(j?j.monthNames:
    +null)||this._defaults.monthNames;var k=function(u){(u=r+1<b.length&&b.charAt(r+1)==u)&&r++;return u},m=function(u,v,w){v=""+v;if(k(u))for(;v.length<w;)v="0"+v;return v},p=function(u,v,w,x){return k(u)?x[v]:w[v]},q="",s=false;if(f)for(var r=0;r<b.length;r++)if(s)if(b.charAt(r)=="'"&&!k("'"))s=false;else q+=b.charAt(r);else switch(b.charAt(r)){case "d":q+=m("d",f.getDate(),2);break;case "D":q+=p("D",f.getDay(),l,o);break;case "o":q+=m("o",Math.round(((new Date(f.getFullYear(),f.getMonth(),f.getDate())).getTime()-
    +(new Date(f.getFullYear(),0,0)).getTime())/864E5),3);break;case "m":q+=m("m",f.getMonth()+1,2);break;case "M":q+=p("M",f.getMonth(),n,j);break;case "y":q+=k("y")?f.getFullYear():(f.getYear()%100<10?"0":"")+f.getYear()%100;break;case "@":q+=f.getTime();break;case "!":q+=f.getTime()*1E4+this._ticksTo1970;break;case "'":if(k("'"))q+="'";else s=true;break;default:q+=b.charAt(r)}return q},_possibleChars:function(b){for(var f="",j=false,l=function(n){(n=o+1<b.length&&b.charAt(o+1)==n)&&o++;return n},o=
    +0;o<b.length;o++)if(j)if(b.charAt(o)=="'"&&!l("'"))j=false;else f+=b.charAt(o);else switch(b.charAt(o)){case "d":case "m":case "y":case "@":f+="0123456789";break;case "D":case "M":return null;case "'":if(l("'"))f+="'";else j=true;break;default:f+=b.charAt(o)}return f},_get:function(b,f){return b.settings[f]!==d?b.settings[f]:this._defaults[f]},_setDateFromField:function(b,f){if(b.input.val()!=b.lastVal){var j=this._get(b,"dateFormat"),l=b.lastVal=b.input?b.input.val():null,o,n;o=n=this._getDefaultDate(b);
    +var k=this._getFormatConfig(b);try{o=this.parseDate(j,l,k)||n}catch(m){this.log(m);l=f?"":l}b.selectedDay=o.getDate();b.drawMonth=b.selectedMonth=o.getMonth();b.drawYear=b.selectedYear=o.getFullYear();b.currentDay=l?o.getDate():0;b.currentMonth=l?o.getMonth():0;b.currentYear=l?o.getFullYear():0;this._adjustInstDate(b)}},_getDefaultDate:function(b){return this._restrictMinMax(b,this._determineDate(b,this._get(b,"defaultDate"),new Date))},_determineDate:function(b,f,j){var l=function(n){var k=new Date;
    +k.setDate(k.getDate()+n);return k},o=function(n){try{return a.datepicker.parseDate(a.datepicker._get(b,"dateFormat"),n,a.datepicker._getFormatConfig(b))}catch(k){}var m=(n.toLowerCase().match(/^c/)?a.datepicker._getDate(b):null)||new Date,p=m.getFullYear(),q=m.getMonth();m=m.getDate();for(var s=/([+-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g,r=s.exec(n);r;){switch(r[2]||"d"){case "d":case "D":m+=parseInt(r[1],10);break;case "w":case "W":m+=parseInt(r[1],10)*7;break;case "m":case "M":q+=parseInt(r[1],10);m=
    +Math.min(m,a.datepicker._getDaysInMonth(p,q));break;case "y":case "Y":p+=parseInt(r[1],10);m=Math.min(m,a.datepicker._getDaysInMonth(p,q));break}r=s.exec(n)}return new Date(p,q,m)};if(f=(f=f==null||f===""?j:typeof f=="string"?o(f):typeof f=="number"?isNaN(f)?j:l(f):new Date(f.getTime()))&&f.toString()=="Invalid Date"?j:f){f.setHours(0);f.setMinutes(0);f.setSeconds(0);f.setMilliseconds(0)}return this._daylightSavingAdjust(f)},_daylightSavingAdjust:function(b){if(!b)return null;b.setHours(b.getHours()>
    +12?b.getHours()+2:0);return b},_setDate:function(b,f,j){var l=!f,o=b.selectedMonth,n=b.selectedYear;f=this._restrictMinMax(b,this._determineDate(b,f,new Date));b.selectedDay=b.currentDay=f.getDate();b.drawMonth=b.selectedMonth=b.currentMonth=f.getMonth();b.drawYear=b.selectedYear=b.currentYear=f.getFullYear();if((o!=b.selectedMonth||n!=b.selectedYear)&&!j)this._notifyChange(b);this._adjustInstDate(b);if(b.input)b.input.val(l?"":this._formatDate(b))},_getDate:function(b){return!b.currentYear||b.input&&
    +b.input.val()==""?null:this._daylightSavingAdjust(new Date(b.currentYear,b.currentMonth,b.currentDay))},_generateHTML:function(b){var f=new Date;f=this._daylightSavingAdjust(new Date(f.getFullYear(),f.getMonth(),f.getDate()));var j=this._get(b,"isRTL"),l=this._get(b,"showButtonPanel"),o=this._get(b,"hideIfNoPrevNext"),n=this._get(b,"navigationAsDateFormat"),k=this._getNumberOfMonths(b),m=this._get(b,"showCurrentAtPos"),p=this._get(b,"stepMonths"),q=k[0]!=1||k[1]!=1,s=this._daylightSavingAdjust(!b.currentDay?
    +new Date(9999,9,9):new Date(b.currentYear,b.currentMonth,b.currentDay)),r=this._getMinMaxDate(b,"min"),u=this._getMinMaxDate(b,"max");m=b.drawMonth-m;var v=b.drawYear;if(m<0){m+=12;v--}if(u){var w=this._daylightSavingAdjust(new Date(u.getFullYear(),u.getMonth()-k[0]*k[1]+1,u.getDate()));for(w=r&&w<r?r:w;this._daylightSavingAdjust(new Date(v,m,1))>w;){m--;if(m<0){m=11;v--}}}b.drawMonth=m;b.drawYear=v;w=this._get(b,"prevText");w=!n?w:this.formatDate(w,this._daylightSavingAdjust(new Date(v,m-p,1)),this._getFormatConfig(b));
    +w=this._canAdjustMonth(b,-1,v,m)?'<a class="ui-datepicker-prev ui-corner-all" onclick="DP_jQuery_'+g+".datepicker._adjustDate('#"+b.id+"', -"+p+", 'M');\" title=\""+w+'"><span class="ui-icon ui-icon-circle-triangle-'+(j?"e":"w")+'">'+w+"</span></a>":o?"":'<a class="ui-datepicker-prev ui-corner-all ui-state-disabled" title="'+w+'"><span class="ui-icon ui-icon-circle-triangle-'+(j?"e":"w")+'">'+w+"</span></a>";var x=this._get(b,"nextText");x=!n?x:this.formatDate(x,this._daylightSavingAdjust(new Date(v,
    +m+p,1)),this._getFormatConfig(b));o=this._canAdjustMonth(b,+1,v,m)?'<a class="ui-datepicker-next ui-corner-all" onclick="DP_jQuery_'+g+".datepicker._adjustDate('#"+b.id+"', +"+p+", 'M');\" title=\""+x+'"><span class="ui-icon ui-icon-circle-triangle-'+(j?"w":"e")+'">'+x+"</span></a>":o?"":'<a class="ui-datepicker-next ui-corner-all ui-state-disabled" title="'+x+'"><span class="ui-icon ui-icon-circle-triangle-'+(j?"w":"e")+'">'+x+"</span></a>";p=this._get(b,"currentText");x=this._get(b,"gotoCurrent")&&
    +b.currentDay?s:f;p=!n?p:this.formatDate(p,x,this._getFormatConfig(b));n=!b.inline?'<button type="button" class="ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all" onclick="DP_jQuery_'+g+'.datepicker._hideDatepicker();">'+this._get(b,"closeText")+"</button>":"";l=l?'<div class="ui-datepicker-buttonpane ui-widget-content">'+(j?n:"")+(this._isInRange(b,x)?'<button type="button" class="ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all" onclick="DP_jQuery_'+
    +g+".datepicker._gotoToday('#"+b.id+"');\">"+p+"</button>":"")+(j?"":n)+"</div>":"";n=parseInt(this._get(b,"firstDay"),10);n=isNaN(n)?0:n;p=this._get(b,"showWeek");x=this._get(b,"dayNames");this._get(b,"dayNamesShort");var y=this._get(b,"dayNamesMin"),H=this._get(b,"monthNames"),C=this._get(b,"monthNamesShort"),z=this._get(b,"beforeShowDay"),I=this._get(b,"showOtherMonths"),N=this._get(b,"selectOtherMonths");this._get(b,"calculateWeek");for(var J=this._getDefaultDate(b),D="",E=0;E<k[0];E++){var P=
    +"";this.maxRows=4;for(var L=0;L<k[1];L++){var Q=this._daylightSavingAdjust(new Date(v,m,b.selectedDay)),B=" ui-corner-all",F="";if(q){F+='<div class="ui-datepicker-group';if(k[1]>1)switch(L){case 0:F+=" ui-datepicker-group-first";B=" ui-corner-"+(j?"right":"left");break;case k[1]-1:F+=" ui-datepicker-group-last";B=" ui-corner-"+(j?"left":"right");break;default:F+=" ui-datepicker-group-middle";B="";break}F+='">'}F+='<div class="ui-datepicker-header ui-widget-header ui-helper-clearfix'+B+'">'+(/all|left/.test(B)&&
    +E==0?j?o:w:"")+(/all|right/.test(B)&&E==0?j?w:o:"")+this._generateMonthYearHeader(b,m,v,r,u,E>0||L>0,H,C)+'</div><table class="ui-datepicker-calendar"><thead><tr>';var G=p?'<th class="ui-datepicker-week-col">'+this._get(b,"weekHeader")+"</th>":"";for(B=0;B<7;B++){var A=(B+n)%7;G+="<th"+((B+n+6)%7>=5?' class="ui-datepicker-week-end"':"")+'><span title="'+x[A]+'">'+y[A]+"</span></th>"}F+=G+"</tr></thead><tbody>";G=this._getDaysInMonth(v,m);if(v==b.selectedYear&&m==b.selectedMonth)b.selectedDay=Math.min(b.selectedDay,
    +G);B=(this._getFirstDayOfMonth(v,m)-n+7)%7;G=Math.ceil((B+G)/7);this.maxRows=G=q?this.maxRows>G?this.maxRows:G:G;A=this._daylightSavingAdjust(new Date(v,m,1-B));for(var R=0;R<G;R++){F+="<tr>";var S=!p?"":'<td class="ui-datepicker-week-col">'+this._get(b,"calculateWeek")(A)+"</td>";for(B=0;B<7;B++){var M=z?z.apply(b.input?b.input[0]:null,[A]):[true,""],K=A.getMonth()!=m,O=K&&!N||!M[0]||r&&A<r||u&&A>u;S+='<td class="'+((B+n+6)%7>=5?" ui-datepicker-week-end":"")+(K?" ui-datepicker-other-month":"")+(A.getTime()==
    +Q.getTime()&&m==b.selectedMonth&&b._keyEvent||J.getTime()==A.getTime()&&J.getTime()==Q.getTime()?" "+this._dayOverClass:"")+(O?" "+this._unselectableClass+" ui-state-disabled":"")+(K&&!I?"":" "+M[1]+(A.getTime()==s.getTime()?" "+this._currentClass:"")+(A.getTime()==f.getTime()?" ui-datepicker-today":""))+'"'+((!K||I)&&M[2]?' title="'+M[2]+'"':"")+(O?"":' onclick="DP_jQuery_'+g+".datepicker._selectDay('#"+b.id+"',"+A.getMonth()+","+A.getFullYear()+', this);return false;"')+">"+(K&&!I?"&#xa0;":O?'<span class="ui-state-default">'+
    +A.getDate()+"</span>":'<a class="ui-state-default'+(A.getTime()==f.getTime()?" ui-state-highlight":"")+(A.getTime()==s.getTime()?" ui-state-active":"")+(K?" ui-priority-secondary":"")+'" href="#">'+A.getDate()+"</a>")+"</td>";A.setDate(A.getDate()+1);A=this._daylightSavingAdjust(A)}F+=S+"</tr>"}m++;if(m>11){m=0;v++}F+="</tbody></table>"+(q?"</div>"+(k[0]>0&&L==k[1]-1?'<div class="ui-datepicker-row-break"></div>':""):"");P+=F}D+=P}D+=l+(a.browser.msie&&parseInt(a.browser.version,10)<7&&!b.inline?'<iframe src="javascript:false;" class="ui-datepicker-cover" frameborder="0"></iframe>':
    +"");b._keyEvent=false;return D},_generateMonthYearHeader:function(b,f,j,l,o,n,k,m){var p=this._get(b,"changeMonth"),q=this._get(b,"changeYear"),s=this._get(b,"showMonthAfterYear"),r='<div class="ui-datepicker-title">',u="";if(n||!p)u+='<span class="ui-datepicker-month">'+k[f]+"</span>";else{k=l&&l.getFullYear()==j;var v=o&&o.getFullYear()==j;u+='<select class="ui-datepicker-month" onchange="DP_jQuery_'+g+".datepicker._selectMonthYear('#"+b.id+"', this, 'M');\" >";for(var w=0;w<12;w++)if((!k||w>=l.getMonth())&&
    +(!v||w<=o.getMonth()))u+='<option value="'+w+'"'+(w==f?' selected="selected"':"")+">"+m[w]+"</option>";u+="</select>"}s||(r+=u+(n||!(p&&q)?"&#xa0;":""));if(!b.yearshtml){b.yearshtml="";if(n||!q)r+='<span class="ui-datepicker-year">'+j+"</span>";else{m=this._get(b,"yearRange").split(":");var x=(new Date).getFullYear();k=function(y){y=y.match(/c[+-].*/)?j+parseInt(y.substring(1),10):y.match(/[+-].*/)?x+parseInt(y,10):parseInt(y,10);return isNaN(y)?x:y};f=k(m[0]);m=Math.max(f,k(m[1]||""));f=l?Math.max(f,
    +l.getFullYear()):f;m=o?Math.min(m,o.getFullYear()):m;for(b.yearshtml+='<select class="ui-datepicker-year" onchange="DP_jQuery_'+g+".datepicker._selectMonthYear('#"+b.id+"', this, 'Y');\" >";f<=m;f++)b.yearshtml+='<option value="'+f+'"'+(f==j?' selected="selected"':"")+">"+f+"</option>";b.yearshtml+="</select>";r+=b.yearshtml;b.yearshtml=null}}r+=this._get(b,"yearSuffix");if(s)r+=(n||!(p&&q)?"&#xa0;":"")+u;r+="</div>";return r},_adjustInstDate:function(b,f,j){var l=b.drawYear+(j=="Y"?f:0),o=b.drawMonth+
    +(j=="M"?f:0);f=Math.min(b.selectedDay,this._getDaysInMonth(l,o))+(j=="D"?f:0);l=this._restrictMinMax(b,this._daylightSavingAdjust(new Date(l,o,f)));b.selectedDay=l.getDate();b.drawMonth=b.selectedMonth=l.getMonth();b.drawYear=b.selectedYear=l.getFullYear();if(j=="M"||j=="Y")this._notifyChange(b)},_restrictMinMax:function(b,f){var j=this._getMinMaxDate(b,"min");b=this._getMinMaxDate(b,"max");f=j&&f<j?j:f;return f=b&&f>b?b:f},_notifyChange:function(b){var f=this._get(b,"onChangeMonthYear");if(f)f.apply(b.input?
    +b.input[0]:null,[b.selectedYear,b.selectedMonth+1,b])},_getNumberOfMonths:function(b){b=this._get(b,"numberOfMonths");return b==null?[1,1]:typeof b=="number"?[1,b]:b},_getMinMaxDate:function(b,f){return this._determineDate(b,this._get(b,f+"Date"),null)},_getDaysInMonth:function(b,f){return 32-this._daylightSavingAdjust(new Date(b,f,32)).getDate()},_getFirstDayOfMonth:function(b,f){return(new Date(b,f,1)).getDay()},_canAdjustMonth:function(b,f,j,l){var o=this._getNumberOfMonths(b);j=this._daylightSavingAdjust(new Date(j,
    +l+(f<0?f:o[0]*o[1]),1));f<0&&j.setDate(this._getDaysInMonth(j.getFullYear(),j.getMonth()));return this._isInRange(b,j)},_isInRange:function(b,f){var j=this._getMinMaxDate(b,"min");b=this._getMinMaxDate(b,"max");return(!j||f.getTime()>=j.getTime())&&(!b||f.getTime()<=b.getTime())},_getFormatConfig:function(b){var f=this._get(b,"shortYearCutoff");f=typeof f!="string"?f:(new Date).getFullYear()%100+parseInt(f,10);return{shortYearCutoff:f,dayNamesShort:this._get(b,"dayNamesShort"),dayNames:this._get(b,
    +"dayNames"),monthNamesShort:this._get(b,"monthNamesShort"),monthNames:this._get(b,"monthNames")}},_formatDate:function(b,f,j,l){if(!f){b.currentDay=b.selectedDay;b.currentMonth=b.selectedMonth;b.currentYear=b.selectedYear}f=f?typeof f=="object"?f:this._daylightSavingAdjust(new Date(l,j,f)):this._daylightSavingAdjust(new Date(b.currentYear,b.currentMonth,b.currentDay));return this.formatDate(this._get(b,"dateFormat"),f,this._getFormatConfig(b))}});a.fn.datepicker=function(b){if(!this.length)return this;
    +if(!a.datepicker.initialized){a(document).mousedown(a.datepicker._checkExternalClick).find("body").append(a.datepicker.dpDiv);a.datepicker.initialized=true}var f=Array.prototype.slice.call(arguments,1);if(typeof b=="string"&&(b=="isDisabled"||b=="getDate"||b=="widget"))return a.datepicker["_"+b+"Datepicker"].apply(a.datepicker,[this[0]].concat(f));if(b=="option"&&arguments.length==2&&typeof arguments[1]=="string")return a.datepicker["_"+b+"Datepicker"].apply(a.datepicker,[this[0]].concat(f));return this.each(function(){typeof b==
    +"string"?a.datepicker["_"+b+"Datepicker"].apply(a.datepicker,[this].concat(f)):a.datepicker._attachDatepicker(this,b)})};a.datepicker=new c;a.datepicker.initialized=false;a.datepicker.uuid=(new Date).getTime();a.datepicker.version="1.8.16";window["DP_jQuery_"+g]=a})(jQuery);
    +(function(a,d){var c={buttons:true,height:true,maxHeight:true,maxWidth:true,minHeight:true,minWidth:true,width:true},e={maxHeight:true,maxWidth:true,minHeight:true,minWidth:true},h=a.attrFn||{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true,click:true};a.widget("ui.dialog",{options:{autoOpen:true,buttons:{},closeOnEscape:true,closeText:"close",dialogClass:"",draggable:true,hide:null,height:"auto",maxHeight:false,maxWidth:false,minHeight:150,minWidth:150,modal:false,
    +position:{my:"center",at:"center",collision:"fit",using:function(g){var i=a(this).css(g).offset().top;i<0&&a(this).css("top",g.top-i)}},resizable:true,show:null,stack:true,title:"",width:300,zIndex:1E3},_create:function(){this.originalTitle=this.element.attr("title");if(typeof this.originalTitle!=="string")this.originalTitle="";this.options.title=this.options.title||this.originalTitle;var g=this,i=g.options,b=i.title||"&#160;",f=a.ui.dialog.getTitleId(g.element),j=(g.uiDialog=a("<div></div>")).appendTo(document.body).hide().addClass("ui-dialog ui-widget ui-widget-content ui-corner-all "+
    +i.dialogClass).css({zIndex:i.zIndex}).attr("tabIndex",-1).css("outline",0).keydown(function(n){if(i.closeOnEscape&&!n.isDefaultPrevented()&&n.keyCode&&n.keyCode===a.ui.keyCode.ESCAPE){g.close(n);n.preventDefault()}}).attr({role:"dialog","aria-labelledby":f}).mousedown(function(n){g.moveToTop(false,n)});g.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(j);var l=(g.uiDialogTitlebar=a("<div></div>")).addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").prependTo(j),
    +o=a('<a href="#"></a>').addClass("ui-dialog-titlebar-close ui-corner-all").attr("role","button").hover(function(){o.addClass("ui-state-hover")},function(){o.removeClass("ui-state-hover")}).focus(function(){o.addClass("ui-state-focus")}).blur(function(){o.removeClass("ui-state-focus")}).click(function(n){g.close(n);return false}).appendTo(l);(g.uiDialogTitlebarCloseText=a("<span></span>")).addClass("ui-icon ui-icon-closethick").text(i.closeText).appendTo(o);a("<span></span>").addClass("ui-dialog-title").attr("id",
    +f).html(b).prependTo(l);if(a.isFunction(i.beforeclose)&&!a.isFunction(i.beforeClose))i.beforeClose=i.beforeclose;l.find("*").add(l).disableSelection();i.draggable&&a.fn.draggable&&g._makeDraggable();i.resizable&&a.fn.resizable&&g._makeResizable();g._createButtons(i.buttons);g._isOpen=false;a.fn.bgiframe&&j.bgiframe()},_init:function(){this.options.autoOpen&&this.open()},destroy:function(){var g=this;g.overlay&&g.overlay.destroy();g.uiDialog.hide();g.element.unbind(".dialog").removeData("dialog").removeClass("ui-dialog-content ui-widget-content").hide().appendTo("body");
    +g.uiDialog.remove();g.originalTitle&&g.element.attr("title",g.originalTitle);return g},widget:function(){return this.uiDialog},close:function(g){var i=this,b,f;if(false!==i._trigger("beforeClose",g)){i.overlay&&i.overlay.destroy();i.uiDialog.unbind("keypress.ui-dialog");i._isOpen=false;if(i.options.hide)i.uiDialog.hide(i.options.hide,function(){i._trigger("close",g)});else{i.uiDialog.hide();i._trigger("close",g)}a.ui.dialog.overlay.resize();if(i.options.modal){b=0;a(".ui-dialog").each(function(){if(this!==
    +i.uiDialog[0]){f=a(this).css("z-index");isNaN(f)||(b=Math.max(b,f))}});a.ui.dialog.maxZ=b}return i}},isOpen:function(){return this._isOpen},moveToTop:function(g,i){var b=this,f=b.options;if(f.modal&&!g||!f.stack&&!f.modal)return b._trigger("focus",i);if(f.zIndex>a.ui.dialog.maxZ)a.ui.dialog.maxZ=f.zIndex;if(b.overlay){a.ui.dialog.maxZ+=1;b.overlay.$el.css("z-index",a.ui.dialog.overlay.maxZ=a.ui.dialog.maxZ)}g={scrollTop:b.element.scrollTop(),scrollLeft:b.element.scrollLeft()};a.ui.dialog.maxZ+=1;
    +b.uiDialog.css("z-index",a.ui.dialog.maxZ);b.element.attr(g);b._trigger("focus",i);return b},open:function(){if(!this._isOpen){var g=this,i=g.options,b=g.uiDialog;g.overlay=i.modal?new a.ui.dialog.overlay(g):null;g._size();g._position(i.position);b.show(i.show);g.moveToTop(true);i.modal&&b.bind("keypress.ui-dialog",function(f){if(f.keyCode===a.ui.keyCode.TAB){var j=a(":tabbable",this),l=j.filter(":first");j=j.filter(":last");if(f.target===j[0]&&!f.shiftKey){l.focus(1);return false}else if(f.target===
    +l[0]&&f.shiftKey){j.focus(1);return false}}});a(g.element.find(":tabbable").get().concat(b.find(".ui-dialog-buttonpane :tabbable").get().concat(b.get()))).eq(0).focus();g._isOpen=true;g._trigger("open");return g}},_createButtons:function(g){var i=this,b=false,f=a("<div></div>").addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"),j=a("<div></div>").addClass("ui-dialog-buttonset").appendTo(f);i.uiDialog.find(".ui-dialog-buttonpane").remove();typeof g==="object"&&g!==null&&a.each(g,
    +function(){return!(b=true)});if(b){a.each(g,function(l,o){o=a.isFunction(o)?{click:o,text:l}:o;var n=a('<button type="button"></button>').click(function(){o.click.apply(i.element[0],arguments)}).appendTo(j);a.each(o,function(k,m){if(k!=="click")k in h?n[k](m):n.attr(k,m)});a.fn.button&&n.button()});f.appendTo(i.uiDialog)}},_makeDraggable:function(){function g(l){return{position:l.position,offset:l.offset}}var i=this,b=i.options,f=a(document),j;i.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",
    +handle:".ui-dialog-titlebar",containment:"document",start:function(l,o){j=b.height==="auto"?"auto":a(this).height();a(this).height(a(this).height()).addClass("ui-dialog-dragging");i._trigger("dragStart",l,g(o))},drag:function(l,o){i._trigger("drag",l,g(o))},stop:function(l,o){b.position=[o.position.left-f.scrollLeft(),o.position.top-f.scrollTop()];a(this).removeClass("ui-dialog-dragging").height(j);i._trigger("dragStop",l,g(o));a.ui.dialog.overlay.resize()}})},_makeResizable:function(g){function i(l){return{originalPosition:l.originalPosition,
    +originalSize:l.originalSize,position:l.position,size:l.size}}g=g===d?this.options.resizable:g;var b=this,f=b.options,j=b.uiDialog.css("position");g=typeof g==="string"?g:"n,e,s,w,se,sw,ne,nw";b.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:b.element,maxWidth:f.maxWidth,maxHeight:f.maxHeight,minWidth:f.minWidth,minHeight:b._minHeight(),handles:g,start:function(l,o){a(this).addClass("ui-dialog-resizing");b._trigger("resizeStart",l,i(o))},resize:function(l,o){b._trigger("resize",
    +l,i(o))},stop:function(l,o){a(this).removeClass("ui-dialog-resizing");f.height=a(this).height();f.width=a(this).width();b._trigger("resizeStop",l,i(o));a.ui.dialog.overlay.resize()}}).css("position",j).find(".ui-resizable-se").addClass("ui-icon ui-icon-grip-diagonal-se")},_minHeight:function(){var g=this.options;return g.height==="auto"?g.minHeight:Math.min(g.minHeight,g.height)},_position:function(g){var i=[],b=[0,0],f;if(g){if(typeof g==="string"||typeof g==="object"&&"0"in g){i=g.split?g.split(" "):
    +[g[0],g[1]];if(i.length===1)i[1]=i[0];a.each(["left","top"],function(j,l){if(+i[j]===i[j]){b[j]=i[j];i[j]=l}});g={my:i.join(" "),at:i.join(" "),offset:b.join(" ")}}g=a.extend({},a.ui.dialog.prototype.options.position,g)}else g=a.ui.dialog.prototype.options.position;(f=this.uiDialog.is(":visible"))||this.uiDialog.show();this.uiDialog.css({top:0,left:0}).position(a.extend({of:window},g));f||this.uiDialog.hide()},_setOptions:function(g){var i=this,b={},f=false;a.each(g,function(j,l){i._setOption(j,l);
    +if(j in c)f=true;if(j in e)b[j]=l});f&&this._size();this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option",b)},_setOption:function(g,i){var b=this,f=b.uiDialog;switch(g){case "beforeclose":g="beforeClose";break;case "buttons":b._createButtons(i);break;case "closeText":b.uiDialogTitlebarCloseText.text(""+i);break;case "dialogClass":f.removeClass(b.options.dialogClass).addClass("ui-dialog ui-widget ui-widget-content ui-corner-all "+i);break;case "disabled":i?f.addClass("ui-dialog-disabled"):
    +f.removeClass("ui-dialog-disabled");break;case "draggable":var j=f.is(":data(draggable)");j&&!i&&f.draggable("destroy");!j&&i&&b._makeDraggable();break;case "position":b._position(i);break;case "resizable":(j=f.is(":data(resizable)"))&&!i&&f.resizable("destroy");j&&typeof i==="string"&&f.resizable("option","handles",i);!j&&i!==false&&b._makeResizable(i);break;case "title":a(".ui-dialog-title",b.uiDialogTitlebar).html(""+(i||"&#160;"));break}a.Widget.prototype._setOption.apply(b,arguments)},_size:function(){var g=
    +this.options,i,b,f=this.uiDialog.is(":visible");this.element.show().css({width:"auto",minHeight:0,height:0});if(g.minWidth>g.width)g.width=g.minWidth;i=this.uiDialog.css({height:"auto",width:g.width}).height();b=Math.max(0,g.minHeight-i);if(g.height==="auto")if(a.support.minHeight)this.element.css({minHeight:b,height:"auto"});else{this.uiDialog.show();g=this.element.css("height","auto").height();f||this.uiDialog.hide();this.element.height(Math.max(g,b))}else this.element.height(Math.max(g.height-
    +i,0));this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option","minHeight",this._minHeight())}});a.extend(a.ui.dialog,{version:"1.8.16",uuid:0,maxZ:0,getTitleId:function(g){g=g.attr("id");if(!g){this.uuid+=1;g=this.uuid}return"ui-dialog-title-"+g},overlay:function(g){this.$el=a.ui.dialog.overlay.create(g)}});a.extend(a.ui.dialog.overlay,{instances:[],oldInstances:[],maxZ:0,events:a.map("focus,mousedown,mouseup,keydown,keypress,click".split(","),function(g){return g+".dialog-overlay"}).join(" "),
    +create:function(g){if(this.instances.length===0){setTimeout(function(){a.ui.dialog.overlay.instances.length&&a(document).bind(a.ui.dialog.overlay.events,function(b){if(a(b.target).zIndex()<a.ui.dialog.overlay.maxZ)return false})},1);a(document).bind("keydown.dialog-overlay",function(b){if(g.options.closeOnEscape&&!b.isDefaultPrevented()&&b.keyCode&&b.keyCode===a.ui.keyCode.ESCAPE){g.close(b);b.preventDefault()}});a(window).bind("resize.dialog-overlay",a.ui.dialog.overlay.resize)}var i=(this.oldInstances.pop()||
    +a("<div></div>").addClass("ui-widget-overlay")).appendTo(document.body).css({width:this.width(),height:this.height()});a.fn.bgiframe&&i.bgiframe();this.instances.push(i);return i},destroy:function(g){var i=a.inArray(g,this.instances);i!=-1&&this.oldInstances.push(this.instances.splice(i,1)[0]);this.instances.length===0&&a([document,window]).unbind(".dialog-overlay");g.remove();var b=0;a.each(this.instances,function(){b=Math.max(b,this.css("z-index"))});this.maxZ=b},height:function(){var g,i;if(a.browser.msie&&
    +a.browser.version<7){g=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight);i=Math.max(document.documentElement.offsetHeight,document.body.offsetHeight);return g<i?a(window).height()+"px":g+"px"}else return a(document).height()+"px"},width:function(){var g,i;if(a.browser.msie){g=Math.max(document.documentElement.scrollWidth,document.body.scrollWidth);i=Math.max(document.documentElement.offsetWidth,document.body.offsetWidth);return g<i?a(window).width()+"px":g+"px"}else return a(document).width()+
    +"px"},resize:function(){var g=a([]);a.each(a.ui.dialog.overlay.instances,function(){g=g.add(this)});g.css({width:0,height:0}).css({width:a.ui.dialog.overlay.width(),height:a.ui.dialog.overlay.height()})}});a.extend(a.ui.dialog.overlay.prototype,{destroy:function(){a.ui.dialog.overlay.destroy(this.$el)}})})(jQuery);
    +(function(a){a.ui=a.ui||{};var d=/left|center|right/,c=/top|center|bottom/,e=a.fn.position,h=a.fn.offset;a.fn.position=function(g){if(!g||!g.of)return e.apply(this,arguments);g=a.extend({},g);var i=a(g.of),b=i[0],f=(g.collision||"flip").split(" "),j=g.offset?g.offset.split(" "):[0,0],l,o,n;if(b.nodeType===9){l=i.width();o=i.height();n={top:0,left:0}}else if(b.setTimeout){l=i.width();o=i.height();n={top:i.scrollTop(),left:i.scrollLeft()}}else if(b.preventDefault){g.at="left top";l=o=0;n={top:g.of.pageY,
    +left:g.of.pageX}}else{l=i.outerWidth();o=i.outerHeight();n=i.offset()}a.each(["my","at"],function(){var k=(g[this]||"").split(" ");if(k.length===1)k=d.test(k[0])?k.concat(["center"]):c.test(k[0])?["center"].concat(k):["center","center"];k[0]=d.test(k[0])?k[0]:"center";k[1]=c.test(k[1])?k[1]:"center";g[this]=k});if(f.length===1)f[1]=f[0];j[0]=parseInt(j[0],10)||0;if(j.length===1)j[1]=j[0];j[1]=parseInt(j[1],10)||0;if(g.at[0]==="right")n.left+=l;else if(g.at[0]==="center")n.left+=l/2;if(g.at[1]==="bottom")n.top+=
    +o;else if(g.at[1]==="center")n.top+=o/2;n.left+=j[0];n.top+=j[1];return this.each(function(){var k=a(this),m=k.outerWidth(),p=k.outerHeight(),q=parseInt(a.curCSS(this,"marginLeft",true))||0,s=parseInt(a.curCSS(this,"marginTop",true))||0,r=m+q+(parseInt(a.curCSS(this,"marginRight",true))||0),u=p+s+(parseInt(a.curCSS(this,"marginBottom",true))||0),v=a.extend({},n),w;if(g.my[0]==="right")v.left-=m;else if(g.my[0]==="center")v.left-=m/2;if(g.my[1]==="bottom")v.top-=p;else if(g.my[1]==="center")v.top-=
    +p/2;v.left=Math.round(v.left);v.top=Math.round(v.top);w={left:v.left-q,top:v.top-s};a.each(["left","top"],function(x,y){a.ui.position[f[x]]&&a.ui.position[f[x]][y](v,{targetWidth:l,targetHeight:o,elemWidth:m,elemHeight:p,collisionPosition:w,collisionWidth:r,collisionHeight:u,offset:j,my:g.my,at:g.at})});a.fn.bgiframe&&k.bgiframe();k.offset(a.extend(v,{using:g.using}))})};a.ui.position={fit:{left:function(g,i){var b=a(window);b=i.collisionPosition.left+i.collisionWidth-b.width()-b.scrollLeft();g.left=
    +b>0?g.left-b:Math.max(g.left-i.collisionPosition.left,g.left)},top:function(g,i){var b=a(window);b=i.collisionPosition.top+i.collisionHeight-b.height()-b.scrollTop();g.top=b>0?g.top-b:Math.max(g.top-i.collisionPosition.top,g.top)}},flip:{left:function(g,i){if(i.at[0]!=="center"){var b=a(window);b=i.collisionPosition.left+i.collisionWidth-b.width()-b.scrollLeft();var f=i.my[0]==="left"?-i.elemWidth:i.my[0]==="right"?i.elemWidth:0,j=i.at[0]==="left"?i.targetWidth:-i.targetWidth,l=-2*i.offset[0];g.left+=
    +i.collisionPosition.left<0?f+j+l:b>0?f+j+l:0}},top:function(g,i){if(i.at[1]!=="center"){var b=a(window);b=i.collisionPosition.top+i.collisionHeight-b.height()-b.scrollTop();var f=i.my[1]==="top"?-i.elemHeight:i.my[1]==="bottom"?i.elemHeight:0,j=i.at[1]==="top"?i.targetHeight:-i.targetHeight,l=-2*i.offset[1];g.top+=i.collisionPosition.top<0?f+j+l:b>0?f+j+l:0}}}};if(!a.offset.setOffset){a.offset.setOffset=function(g,i){if(/static/.test(a.curCSS(g,"position")))g.style.position="relative";var b=a(g),
    +f=b.offset(),j=parseInt(a.curCSS(g,"top",true),10)||0,l=parseInt(a.curCSS(g,"left",true),10)||0;f={top:i.top-f.top+j,left:i.left-f.left+l};"using"in i?i.using.call(g,f):b.css(f)};a.fn.offset=function(g){var i=this[0];if(!i||!i.ownerDocument)return null;if(g)return this.each(function(){a.offset.setOffset(this,g)});return h.call(this)}}})(jQuery);
    +(function(a,d){a.widget("ui.progressbar",{options:{value:0,max:100},min:0,_create:function(){this.element.addClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").attr({role:"progressbar","aria-valuemin":this.min,"aria-valuemax":this.options.max,"aria-valuenow":this._value()});this.valueDiv=a("<div class='ui-progressbar-value ui-widget-header ui-corner-left'></div>").appendTo(this.element);this.oldValue=this._value();this._refreshValue()},destroy:function(){this.element.removeClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow");
    +this.valueDiv.remove();a.Widget.prototype.destroy.apply(this,arguments)},value:function(c){if(c===d)return this._value();this._setOption("value",c);return this},_setOption:function(c,e){if(c==="value"){this.options.value=e;this._refreshValue();this._value()===this.options.max&&this._trigger("complete")}a.Widget.prototype._setOption.apply(this,arguments)},_value:function(){var c=this.options.value;if(typeof c!=="number")c=0;return Math.min(this.options.max,Math.max(this.min,c))},_percentage:function(){return 100*
    +this._value()/this.options.max},_refreshValue:function(){var c=this.value(),e=this._percentage();if(this.oldValue!==c){this.oldValue=c;this._trigger("change")}this.valueDiv.toggle(c>this.min).toggleClass("ui-corner-right",c===this.options.max).width(e.toFixed(0)+"%");this.element.attr("aria-valuenow",c)}});a.extend(a.ui.progressbar,{version:"1.8.16"})})(jQuery);
    +(function(a){a.widget("ui.slider",a.ui.mouse,{widgetEventPrefix:"slide",options:{animate:false,distance:0,max:100,min:0,orientation:"horizontal",range:false,step:1,value:0,values:null},_create:function(){var d=this,c=this.options,e=this.element.find(".ui-slider-handle").addClass("ui-state-default ui-corner-all"),h=c.values&&c.values.length||1,g=[];this._mouseSliding=this._keySliding=false;this._animateOff=true;this._handleIndex=null;this._detectOrientation();this._mouseInit();this.element.addClass("ui-slider ui-slider-"+
    +this.orientation+" ui-widget ui-widget-content ui-corner-all"+(c.disabled?" ui-slider-disabled ui-disabled":""));this.range=a([]);if(c.range){if(c.range===true){if(!c.values)c.values=[this._valueMin(),this._valueMin()];if(c.values.length&&c.values.length!==2)c.values=[c.values[0],c.values[0]]}this.range=a("<div></div>").appendTo(this.element).addClass("ui-slider-range ui-widget-header"+(c.range==="min"||c.range==="max"?" ui-slider-range-"+c.range:""))}for(var i=e.length;i<h;i+=1)g.push("<a class='ui-slider-handle ui-state-default ui-corner-all' href='#'></a>");
    +this.handles=e.add(a(g.join("")).appendTo(d.element));this.handle=this.handles.eq(0);this.handles.add(this.range).filter("a").click(function(b){b.preventDefault()}).hover(function(){c.disabled||a(this).addClass("ui-state-hover")},function(){a(this).removeClass("ui-state-hover")}).focus(function(){if(c.disabled)a(this).blur();else{a(".ui-slider .ui-state-focus").removeClass("ui-state-focus");a(this).addClass("ui-state-focus")}}).blur(function(){a(this).removeClass("ui-state-focus")});this.handles.each(function(b){a(this).data("index.ui-slider-handle",
    +b)});this.handles.keydown(function(b){var f=true,j=a(this).data("index.ui-slider-handle"),l,o,n;if(!d.options.disabled){switch(b.keyCode){case a.ui.keyCode.HOME:case a.ui.keyCode.END:case a.ui.keyCode.PAGE_UP:case a.ui.keyCode.PAGE_DOWN:case a.ui.keyCode.UP:case a.ui.keyCode.RIGHT:case a.ui.keyCode.DOWN:case a.ui.keyCode.LEFT:f=false;if(!d._keySliding){d._keySliding=true;a(this).addClass("ui-state-active");l=d._start(b,j);if(l===false)return}break}n=d.options.step;l=d.options.values&&d.options.values.length?
    +(o=d.values(j)):(o=d.value());switch(b.keyCode){case a.ui.keyCode.HOME:o=d._valueMin();break;case a.ui.keyCode.END:o=d._valueMax();break;case a.ui.keyCode.PAGE_UP:o=d._trimAlignValue(l+(d._valueMax()-d._valueMin())/5);break;case a.ui.keyCode.PAGE_DOWN:o=d._trimAlignValue(l-(d._valueMax()-d._valueMin())/5);break;case a.ui.keyCode.UP:case a.ui.keyCode.RIGHT:if(l===d._valueMax())return;o=d._trimAlignValue(l+n);break;case a.ui.keyCode.DOWN:case a.ui.keyCode.LEFT:if(l===d._valueMin())return;o=d._trimAlignValue(l-
    +n);break}d._slide(b,j,o);return f}}).keyup(function(b){var f=a(this).data("index.ui-slider-handle");if(d._keySliding){d._keySliding=false;d._stop(b,f);d._change(b,f);a(this).removeClass("ui-state-active")}});this._refreshValue();this._animateOff=false},destroy:function(){this.handles.remove();this.range.remove();this.element.removeClass("ui-slider ui-slider-horizontal ui-slider-vertical ui-slider-disabled ui-widget ui-widget-content ui-corner-all").removeData("slider").unbind(".slider");this._mouseDestroy();
    +return this},_mouseCapture:function(d){var c=this.options,e,h,g,i,b;if(c.disabled)return false;this.elementSize={width:this.element.outerWidth(),height:this.element.outerHeight()};this.elementOffset=this.element.offset();e=this._normValueFromMouse({x:d.pageX,y:d.pageY});h=this._valueMax()-this._valueMin()+1;i=this;this.handles.each(function(f){var j=Math.abs(e-i.values(f));if(h>j){h=j;g=a(this);b=f}});if(c.range===true&&this.values(1)===c.min){b+=1;g=a(this.handles[b])}if(this._start(d,b)===false)return false;
    +this._mouseSliding=true;i._handleIndex=b;g.addClass("ui-state-active").focus();c=g.offset();this._clickOffset=!a(d.target).parents().andSelf().is(".ui-slider-handle")?{left:0,top:0}:{left:d.pageX-c.left-g.width()/2,top:d.pageY-c.top-g.height()/2-(parseInt(g.css("borderTopWidth"),10)||0)-(parseInt(g.css("borderBottomWidth"),10)||0)+(parseInt(g.css("marginTop"),10)||0)};this.handles.hasClass("ui-state-hover")||this._slide(d,b,e);return this._animateOff=true},_mouseStart:function(){return true},_mouseDrag:function(d){var c=
    +this._normValueFromMouse({x:d.pageX,y:d.pageY});this._slide(d,this._handleIndex,c);return false},_mouseStop:function(d){this.handles.removeClass("ui-state-active");this._mouseSliding=false;this._stop(d,this._handleIndex);this._change(d,this._handleIndex);this._clickOffset=this._handleIndex=null;return this._animateOff=false},_detectOrientation:function(){this.orientation=this.options.orientation==="vertical"?"vertical":"horizontal"},_normValueFromMouse:function(d){var c;if(this.orientation==="horizontal"){c=
    +this.elementSize.width;d=d.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)}else{c=this.elementSize.height;d=d.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)}c=d/c;if(c>1)c=1;if(c<0)c=0;if(this.orientation==="vertical")c=1-c;d=this._valueMax()-this._valueMin();return this._trimAlignValue(this._valueMin()+c*d)},_start:function(d,c){var e={handle:this.handles[c],value:this.value()};if(this.options.values&&this.options.values.length){e.value=this.values(c);
    +e.values=this.values()}return this._trigger("start",d,e)},_slide:function(d,c,e){var h;if(this.options.values&&this.options.values.length){h=this.values(c?0:1);if(this.options.values.length===2&&this.options.range===true&&(c===0&&e>h||c===1&&e<h))e=h;if(e!==this.values(c)){h=this.values();h[c]=e;d=this._trigger("slide",d,{handle:this.handles[c],value:e,values:h});this.values(c?0:1);d!==false&&this.values(c,e,true)}}else if(e!==this.value()){d=this._trigger("slide",d,{handle:this.handles[c],value:e});
    +d!==false&&this.value(e)}},_stop:function(d,c){var e={handle:this.handles[c],value:this.value()};if(this.options.values&&this.options.values.length){e.value=this.values(c);e.values=this.values()}this._trigger("stop",d,e)},_change:function(d,c){if(!this._keySliding&&!this._mouseSliding){var e={handle:this.handles[c],value:this.value()};if(this.options.values&&this.options.values.length){e.value=this.values(c);e.values=this.values()}this._trigger("change",d,e)}},value:function(d){if(arguments.length){this.options.value=
    +this._trimAlignValue(d);this._refreshValue();this._change(null,0)}else return this._value()},values:function(d,c){var e,h,g;if(arguments.length>1){this.options.values[d]=this._trimAlignValue(c);this._refreshValue();this._change(null,d)}else if(arguments.length)if(a.isArray(arguments[0])){e=this.options.values;h=arguments[0];for(g=0;g<e.length;g+=1){e[g]=this._trimAlignValue(h[g]);this._change(null,g)}this._refreshValue()}else return this.options.values&&this.options.values.length?this._values(d):
    +this.value();else return this._values()},_setOption:function(d,c){var e,h=0;if(a.isArray(this.options.values))h=this.options.values.length;a.Widget.prototype._setOption.apply(this,arguments);switch(d){case "disabled":if(c){this.handles.filter(".ui-state-focus").blur();this.handles.removeClass("ui-state-hover");this.handles.propAttr("disabled",true);this.element.addClass("ui-disabled")}else{this.handles.propAttr("disabled",false);this.element.removeClass("ui-disabled")}break;case "orientation":this._detectOrientation();
    +this.element.removeClass("ui-slider-horizontal ui-slider-vertical").addClass("ui-slider-"+this.orientation);this._refreshValue();break;case "value":this._animateOff=true;this._refreshValue();this._change(null,0);this._animateOff=false;break;case "values":this._animateOff=true;this._refreshValue();for(e=0;e<h;e+=1)this._change(null,e);this._animateOff=false;break}},_value:function(){var d=this.options.value;return d=this._trimAlignValue(d)},_values:function(d){var c,e;if(arguments.length){c=this.options.values[d];
    +return c=this._trimAlignValue(c)}else{c=this.options.values.slice();for(e=0;e<c.length;e+=1)c[e]=this._trimAlignValue(c[e]);return c}},_trimAlignValue:function(d){if(d<=this._valueMin())return this._valueMin();if(d>=this._valueMax())return this._valueMax();var c=this.options.step>0?this.options.step:1,e=(d-this._valueMin())%c;d=d-e;if(Math.abs(e)*2>=c)d+=e>0?c:-c;return parseFloat(d.toFixed(5))},_valueMin:function(){return this.options.min},_valueMax:function(){return this.options.max},_refreshValue:function(){var d=
    +this.options.range,c=this.options,e=this,h=!this._animateOff?c.animate:false,g,i={},b,f,j,l;if(this.options.values&&this.options.values.length)this.handles.each(function(o){g=(e.values(o)-e._valueMin())/(e._valueMax()-e._valueMin())*100;i[e.orientation==="horizontal"?"left":"bottom"]=g+"%";a(this).stop(1,1)[h?"animate":"css"](i,c.animate);if(e.options.range===true)if(e.orientation==="horizontal"){if(o===0)e.range.stop(1,1)[h?"animate":"css"]({left:g+"%"},c.animate);if(o===1)e.range[h?"animate":"css"]({width:g-
    +b+"%"},{queue:false,duration:c.animate})}else{if(o===0)e.range.stop(1,1)[h?"animate":"css"]({bottom:g+"%"},c.animate);if(o===1)e.range[h?"animate":"css"]({height:g-b+"%"},{queue:false,duration:c.animate})}b=g});else{f=this.value();j=this._valueMin();l=this._valueMax();g=l!==j?(f-j)/(l-j)*100:0;i[e.orientation==="horizontal"?"left":"bottom"]=g+"%";this.handle.stop(1,1)[h?"animate":"css"](i,c.animate);if(d==="min"&&this.orientation==="horizontal")this.range.stop(1,1)[h?"animate":"css"]({width:g+"%"},
    +c.animate);if(d==="max"&&this.orientation==="horizontal")this.range[h?"animate":"css"]({width:100-g+"%"},{queue:false,duration:c.animate});if(d==="min"&&this.orientation==="vertical")this.range.stop(1,1)[h?"animate":"css"]({height:g+"%"},c.animate);if(d==="max"&&this.orientation==="vertical")this.range[h?"animate":"css"]({height:100-g+"%"},{queue:false,duration:c.animate})}}});a.extend(a.ui.slider,{version:"1.8.16"})})(jQuery);
    +(function(a,d){function c(){return++h}function e(){return++g}var h=0,g=0;a.widget("ui.tabs",{options:{add:null,ajaxOptions:null,cache:false,cookie:null,collapsible:false,disable:null,disabled:[],enable:null,event:"click",fx:null,idPrefix:"ui-tabs-",load:null,panelTemplate:"<div></div>",remove:null,select:null,show:null,spinner:"<em>Loading&#8230;</em>",tabTemplate:"<li><a href='#{href}'><span>#{label}</span></a></li>"},_create:function(){this._tabify(true)},_setOption:function(i,b){if(i=="selected")this.options.collapsible&&
    +b==this.options.selected||this.select(b);else{this.options[i]=b;this._tabify()}},_tabId:function(i){return i.title&&i.title.replace(/\s/g,"_").replace(/[^\w\u00c0-\uFFFF-]/g,"")||this.options.idPrefix+c()},_sanitizeSelector:function(i){return i.replace(/:/g,"\\:")},_cookie:function(){var i=this.cookie||(this.cookie=this.options.cookie.name||"ui-tabs-"+e());return a.cookie.apply(null,[i].concat(a.makeArray(arguments)))},_ui:function(i,b){return{tab:i,panel:b,index:this.anchors.index(i)}},_cleanup:function(){this.lis.filter(".ui-state-processing").removeClass("ui-state-processing").find("span:data(label.tabs)").each(function(){var i=
    +a(this);i.html(i.data("label.tabs")).removeData("label.tabs")})},_tabify:function(i){function b(r,u){r.css("display","");!a.support.opacity&&u.opacity&&r[0].style.removeAttribute("filter")}var f=this,j=this.options,l=/^#.+/;this.list=this.element.find("ol,ul").eq(0);this.lis=a(" > li:has(a[href])",this.list);this.anchors=this.lis.map(function(){return a("a",this)[0]});this.panels=a([]);this.anchors.each(function(r,u){var v=a(u).attr("href"),w=v.split("#")[0],x;if(w&&(w===location.toString().split("#")[0]||
    +(x=a("base")[0])&&w===x.href)){v=u.hash;u.href=v}if(l.test(v))f.panels=f.panels.add(f.element.find(f._sanitizeSelector(v)));else if(v&&v!=="#"){a.data(u,"href.tabs",v);a.data(u,"load.tabs",v.replace(/#.*$/,""));v=f._tabId(u);u.href="#"+v;u=f.element.find("#"+v);if(!u.length){u=a(j.panelTemplate).attr("id",v).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").insertAfter(f.panels[r-1]||f.list);u.data("destroy.tabs",true)}f.panels=f.panels.add(u)}else j.disabled.push(r)});if(i){this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all");
    +this.list.addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all");this.lis.addClass("ui-state-default ui-corner-top");this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom");if(j.selected===d){location.hash&&this.anchors.each(function(r,u){if(u.hash==location.hash){j.selected=r;return false}});if(typeof j.selected!=="number"&&j.cookie)j.selected=parseInt(f._cookie(),10);if(typeof j.selected!=="number"&&this.lis.filter(".ui-tabs-selected").length)j.selected=
    +this.lis.index(this.lis.filter(".ui-tabs-selected"));j.selected=j.selected||(this.lis.length?0:-1)}else if(j.selected===null)j.selected=-1;j.selected=j.selected>=0&&this.anchors[j.selected]||j.selected<0?j.selected:0;j.disabled=a.unique(j.disabled.concat(a.map(this.lis.filter(".ui-state-disabled"),function(r){return f.lis.index(r)}))).sort();a.inArray(j.selected,j.disabled)!=-1&&j.disabled.splice(a.inArray(j.selected,j.disabled),1);this.panels.addClass("ui-tabs-hide");this.lis.removeClass("ui-tabs-selected ui-state-active");
    +if(j.selected>=0&&this.anchors.length){f.element.find(f._sanitizeSelector(f.anchors[j.selected].hash)).removeClass("ui-tabs-hide");this.lis.eq(j.selected).addClass("ui-tabs-selected ui-state-active");f.element.queue("tabs",function(){f._trigger("show",null,f._ui(f.anchors[j.selected],f.element.find(f._sanitizeSelector(f.anchors[j.selected].hash))[0]))});this.load(j.selected)}a(window).bind("unload",function(){f.lis.add(f.anchors).unbind(".tabs");f.lis=f.anchors=f.panels=null})}else j.selected=this.lis.index(this.lis.filter(".ui-tabs-selected"));
    +this.element[j.collapsible?"addClass":"removeClass"]("ui-tabs-collapsible");j.cookie&&this._cookie(j.selected,j.cookie);i=0;for(var o;o=this.lis[i];i++)a(o)[a.inArray(i,j.disabled)!=-1&&!a(o).hasClass("ui-tabs-selected")?"addClass":"removeClass"]("ui-state-disabled");j.cache===false&&this.anchors.removeData("cache.tabs");this.lis.add(this.anchors).unbind(".tabs");if(j.event!=="mouseover"){var n=function(r,u){u.is(":not(.ui-state-disabled)")&&u.addClass("ui-state-"+r)},k=function(r,u){u.removeClass("ui-state-"+
    +r)};this.lis.bind("mouseover.tabs",function(){n("hover",a(this))});this.lis.bind("mouseout.tabs",function(){k("hover",a(this))});this.anchors.bind("focus.tabs",function(){n("focus",a(this).closest("li"))});this.anchors.bind("blur.tabs",function(){k("focus",a(this).closest("li"))})}var m,p;if(j.fx)if(a.isArray(j.fx)){m=j.fx[0];p=j.fx[1]}else m=p=j.fx;var q=p?function(r,u){a(r).closest("li").addClass("ui-tabs-selected ui-state-active");u.hide().removeClass("ui-tabs-hide").animate(p,p.duration||"normal",
    +function(){b(u,p);f._trigger("show",null,f._ui(r,u[0]))})}:function(r,u){a(r).closest("li").addClass("ui-tabs-selected ui-state-active");u.removeClass("ui-tabs-hide");f._trigger("show",null,f._ui(r,u[0]))},s=m?function(r,u){u.animate(m,m.duration||"normal",function(){f.lis.removeClass("ui-tabs-selected ui-state-active");u.addClass("ui-tabs-hide");b(u,m);f.element.dequeue("tabs")})}:function(r,u){f.lis.removeClass("ui-tabs-selected ui-state-active");u.addClass("ui-tabs-hide");f.element.dequeue("tabs")};
    +this.anchors.bind(j.event+".tabs",function(){var r=this,u=a(r).closest("li"),v=f.panels.filter(":not(.ui-tabs-hide)"),w=f.element.find(f._sanitizeSelector(r.hash));if(u.hasClass("ui-tabs-selected")&&!j.collapsible||u.hasClass("ui-state-disabled")||u.hasClass("ui-state-processing")||f.panels.filter(":animated").length||f._trigger("select",null,f._ui(this,w[0]))===false){this.blur();return false}j.selected=f.anchors.index(this);f.abort();if(j.collapsible)if(u.hasClass("ui-tabs-selected")){j.selected=
    +-1;j.cookie&&f._cookie(j.selected,j.cookie);f.element.queue("tabs",function(){s(r,v)}).dequeue("tabs");this.blur();return false}else if(!v.length){j.cookie&&f._cookie(j.selected,j.cookie);f.element.queue("tabs",function(){q(r,w)});f.load(f.anchors.index(this));this.blur();return false}j.cookie&&f._cookie(j.selected,j.cookie);if(w.length){v.length&&f.element.queue("tabs",function(){s(r,v)});f.element.queue("tabs",function(){q(r,w)});f.load(f.anchors.index(this))}else throw"jQuery UI Tabs: Mismatching fragment identifier.";
    +a.browser.msie&&this.blur()});this.anchors.bind("click.tabs",function(){return false})},_getIndex:function(i){if(typeof i=="string")i=this.anchors.index(this.anchors.filter("[href$="+i+"]"));return i},destroy:function(){var i=this.options;this.abort();this.element.unbind(".tabs").removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible").removeData("tabs");this.list.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all");this.anchors.each(function(){var b=
    +a.data(this,"href.tabs");if(b)this.href=b;var f=a(this).unbind(".tabs");a.each(["href","load","cache"],function(j,l){f.removeData(l+".tabs")})});this.lis.unbind(".tabs").add(this.panels).each(function(){a.data(this,"destroy.tabs")?a(this).remove():a(this).removeClass("ui-state-default ui-corner-top ui-tabs-selected ui-state-active ui-state-hover ui-state-focus ui-state-disabled ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide")});i.cookie&&this._cookie(null,i.cookie);return this},add:function(i,
    +b,f){if(f===d)f=this.anchors.length;var j=this,l=this.options;b=a(l.tabTemplate.replace(/#\{href\}/g,i).replace(/#\{label\}/g,b));i=!i.indexOf("#")?i.replace("#",""):this._tabId(a("a",b)[0]);b.addClass("ui-state-default ui-corner-top").data("destroy.tabs",true);var o=j.element.find("#"+i);o.length||(o=a(l.panelTemplate).attr("id",i).data("destroy.tabs",true));o.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide");if(f>=this.lis.length){b.appendTo(this.list);o.appendTo(this.list[0].parentNode)}else{b.insertBefore(this.lis[f]);
    +o.insertBefore(this.panels[f])}l.disabled=a.map(l.disabled,function(n){return n>=f?++n:n});this._tabify();if(this.anchors.length==1){l.selected=0;b.addClass("ui-tabs-selected ui-state-active");o.removeClass("ui-tabs-hide");this.element.queue("tabs",function(){j._trigger("show",null,j._ui(j.anchors[0],j.panels[0]))});this.load(0)}this._trigger("add",null,this._ui(this.anchors[f],this.panels[f]));return this},remove:function(i){i=this._getIndex(i);var b=this.options,f=this.lis.eq(i).remove(),j=this.panels.eq(i).remove();
    +if(f.hasClass("ui-tabs-selected")&&this.anchors.length>1)this.select(i+(i+1<this.anchors.length?1:-1));b.disabled=a.map(a.grep(b.disabled,function(l){return l!=i}),function(l){return l>=i?--l:l});this._tabify();this._trigger("remove",null,this._ui(f.find("a")[0],j[0]));return this},enable:function(i){i=this._getIndex(i);var b=this.options;if(a.inArray(i,b.disabled)!=-1){this.lis.eq(i).removeClass("ui-state-disabled");b.disabled=a.grep(b.disabled,function(f){return f!=i});this._trigger("enable",null,
    +this._ui(this.anchors[i],this.panels[i]));return this}},disable:function(i){i=this._getIndex(i);var b=this.options;if(i!=b.selected){this.lis.eq(i).addClass("ui-state-disabled");b.disabled.push(i);b.disabled.sort();this._trigger("disable",null,this._ui(this.anchors[i],this.panels[i]))}return this},select:function(i){i=this._getIndex(i);if(i==-1)if(this.options.collapsible&&this.options.selected!=-1)i=this.options.selected;else return this;this.anchors.eq(i).trigger(this.options.event+".tabs");return this},
    +load:function(i){i=this._getIndex(i);var b=this,f=this.options,j=this.anchors.eq(i)[0],l=a.data(j,"load.tabs");this.abort();if(!l||this.element.queue("tabs").length!==0&&a.data(j,"cache.tabs"))this.element.dequeue("tabs");else{this.lis.eq(i).addClass("ui-state-processing");if(f.spinner){var o=a("span",j);o.data("label.tabs",o.html()).html(f.spinner)}this.xhr=a.ajax(a.extend({},f.ajaxOptions,{url:l,success:function(n,k){b.element.find(b._sanitizeSelector(j.hash)).html(n);b._cleanup();f.cache&&a.data(j,
    +"cache.tabs",true);b._trigger("load",null,b._ui(b.anchors[i],b.panels[i]));try{f.ajaxOptions.success(n,k)}catch(m){}},error:function(n,k){b._cleanup();b._trigger("load",null,b._ui(b.anchors[i],b.panels[i]));try{f.ajaxOptions.error(n,k,i,j)}catch(m){}}}));b.element.dequeue("tabs");return this}},abort:function(){this.element.queue([]);this.panels.stop(false,true);this.element.queue("tabs",this.element.queue("tabs").splice(-2,2));if(this.xhr){this.xhr.abort();delete this.xhr}this._cleanup();return this},
    +url:function(i,b){this.anchors.eq(i).removeData("cache.tabs").data("load.tabs",b);return this},length:function(){return this.anchors.length}});a.extend(a.ui.tabs,{version:"1.8.16"});a.extend(a.ui.tabs.prototype,{rotation:null,rotate:function(i,b){var f=this,j=this.options,l=f._rotate||(f._rotate=function(o){clearTimeout(f.rotation);f.rotation=setTimeout(function(){var n=j.selected;f.select(++n<f.anchors.length?n:0)},i);o&&o.stopPropagation()});b=f._unrotate||(f._unrotate=!b?function(o){o.clientX&&
    +f.rotate(null)}:function(){t=j.selected;l()});if(i){this.element.bind("tabsshow",l);this.anchors.bind(j.event+".tabs",b);l()}else{clearTimeout(f.rotation);this.element.unbind("tabsshow",l);this.anchors.unbind(j.event+".tabs",b);delete this._rotate;delete this._unrotate}return this}})})(jQuery);
    diff --git a/lib/jquery-ui-1.8.23-min.js b/lib/jquery-ui-1.8.23-min.js
    new file mode 100644
    index 000000000..4c42e68fc
    --- /dev/null
    +++ b/lib/jquery-ui-1.8.23-min.js
    @@ -0,0 +1,5 @@
    +/*! jQuery UI - v1.8.23 - 2012-08-15
    +* https://github.com/jquery/jquery-ui
    +* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.draggable.js, jquery.ui.droppable.js, jquery.ui.resizable.js, jquery.ui.selectable.js, jquery.ui.sortable.js, jquery.effects.core.js, jquery.effects.blind.js, jquery.effects.bounce.js, jquery.effects.clip.js, jquery.effects.drop.js, jquery.effects.explode.js, jquery.effects.fade.js, jquery.effects.fold.js, jquery.effects.highlight.js, jquery.effects.pulsate.js, jquery.effects.scale.js, jquery.effects.shake.js, jquery.effects.slide.js, jquery.effects.transfer.js, jquery.ui.accordion.js, jquery.ui.autocomplete.js, jquery.ui.button.js, jquery.ui.datepicker.js, jquery.ui.dialog.js, jquery.ui.position.js, jquery.ui.progressbar.js, jquery.ui.slider.js, jquery.ui.tabs.js
    +* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
    +(function(a,b){function c(b,c){var e=b.nodeName.toLowerCase();if("area"===e){var f=b.parentNode,g=f.name,h;return!b.href||!g||f.nodeName.toLowerCase()!=="map"?!1:(h=a("img[usemap=#"+g+"]")[0],!!h&&d(h))}return(/input|select|textarea|button|object/.test(e)?!b.disabled:"a"==e?b.href||c:c)&&d(b)}function d(b){return!a(b).parents().andSelf().filter(function(){return a.curCSS(this,"visibility")==="hidden"||a.expr.filters.hidden(this)}).length}a.ui=a.ui||{};if(a.ui.version)return;a.extend(a.ui,{version:"1.8.23",keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}}),a.fn.extend({propAttr:a.fn.prop||a.fn.attr,_focus:a.fn.focus,focus:function(b,c){return typeof b=="number"?this.each(function(){var d=this;setTimeout(function(){a(d).focus(),c&&c.call(d)},b)}):this._focus.apply(this,arguments)},scrollParent:function(){var b;return a.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?b=this.parents().filter(function(){return/(relative|absolute|fixed)/.test(a.curCSS(this,"position",1))&&/(auto|scroll)/.test(a.curCSS(this,"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0):b=this.parents().filter(function(){return/(auto|scroll)/.test(a.curCSS(this,"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0),/fixed/.test(this.css("position"))||!b.length?a(document):b},zIndex:function(c){if(c!==b)return this.css("zIndex",c);if(this.length){var d=a(this[0]),e,f;while(d.length&&d[0]!==document){e=d.css("position");if(e==="absolute"||e==="relative"||e==="fixed"){f=parseInt(d.css("zIndex"),10);if(!isNaN(f)&&f!==0)return f}d=d.parent()}}return 0},disableSelection:function(){return this.bind((a.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),a("<a>").outerWidth(1).jquery||a.each(["Width","Height"],function(c,d){function h(b,c,d,f){return a.each(e,function(){c-=parseFloat(a.curCSS(b,"padding"+this,!0))||0,d&&(c-=parseFloat(a.curCSS(b,"border"+this+"Width",!0))||0),f&&(c-=parseFloat(a.curCSS(b,"margin"+this,!0))||0)}),c}var e=d==="Width"?["Left","Right"]:["Top","Bottom"],f=d.toLowerCase(),g={innerWidth:a.fn.innerWidth,innerHeight:a.fn.innerHeight,outerWidth:a.fn.outerWidth,outerHeight:a.fn.outerHeight};a.fn["inner"+d]=function(c){return c===b?g["inner"+d].call(this):this.each(function(){a(this).css(f,h(this,c)+"px")})},a.fn["outer"+d]=function(b,c){return typeof b!="number"?g["outer"+d].call(this,b):this.each(function(){a(this).css(f,h(this,b,!0,c)+"px")})}}),a.extend(a.expr[":"],{data:a.expr.createPseudo?a.expr.createPseudo(function(b){return function(c){return!!a.data(c,b)}}):function(b,c,d){return!!a.data(b,d[3])},focusable:function(b){return c(b,!isNaN(a.attr(b,"tabindex")))},tabbable:function(b){var d=a.attr(b,"tabindex"),e=isNaN(d);return(e||d>=0)&&c(b,!e)}}),a(function(){var b=document.body,c=b.appendChild(c=document.createElement("div"));c.offsetHeight,a.extend(c.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0}),a.support.minHeight=c.offsetHeight===100,a.support.selectstart="onselectstart"in c,b.removeChild(c).style.display="none"}),a.curCSS||(a.curCSS=a.css),a.extend(a.ui,{plugin:{add:function(b,c,d){var e=a.ui[b].prototype;for(var f in d)e.plugins[f]=e.plugins[f]||[],e.plugins[f].push([c,d[f]])},call:function(a,b,c){var d=a.plugins[b];if(!d||!a.element[0].parentNode)return;for(var e=0;e<d.length;e++)a.options[d[e][0]]&&d[e][1].apply(a.element,c)}},contains:function(a,b){return document.compareDocumentPosition?a.compareDocumentPosition(b)&16:a!==b&&a.contains(b)},hasScroll:function(b,c){if(a(b).css("overflow")==="hidden")return!1;var d=c&&c==="left"?"scrollLeft":"scrollTop",e=!1;return b[d]>0?!0:(b[d]=1,e=b[d]>0,b[d]=0,e)},isOverAxis:function(a,b,c){return a>b&&a<b+c},isOver:function(b,c,d,e,f,g){return a.ui.isOverAxis(b,d,f)&&a.ui.isOverAxis(c,e,g)}})})(jQuery),function(a,b){if(a.cleanData){var c=a.cleanData;a.cleanData=function(b){for(var d=0,e;(e=b[d])!=null;d++)try{a(e).triggerHandler("remove")}catch(f){}c(b)}}else{var d=a.fn.remove;a.fn.remove=function(b,c){return this.each(function(){return c||(!b||a.filter(b,[this]).length)&&a("*",this).add([this]).each(function(){try{a(this).triggerHandler("remove")}catch(b){}}),d.call(a(this),b,c)})}}a.widget=function(b,c,d){var e=b.split(".")[0],f;b=b.split(".")[1],f=e+"-"+b,d||(d=c,c=a.Widget),a.expr[":"][f]=function(c){return!!a.data(c,b)},a[e]=a[e]||{},a[e][b]=function(a,b){arguments.length&&this._createWidget(a,b)};var g=new c;g.options=a.extend(!0,{},g.options),a[e][b].prototype=a.extend(!0,g,{namespace:e,widgetName:b,widgetEventPrefix:a[e][b].prototype.widgetEventPrefix||b,widgetBaseClass:f},d),a.widget.bridge(b,a[e][b])},a.widget.bridge=function(c,d){a.fn[c]=function(e){var f=typeof e=="string",g=Array.prototype.slice.call(arguments,1),h=this;return e=!f&&g.length?a.extend.apply(null,[!0,e].concat(g)):e,f&&e.charAt(0)==="_"?h:(f?this.each(function(){var d=a.data(this,c),f=d&&a.isFunction(d[e])?d[e].apply(d,g):d;if(f!==d&&f!==b)return h=f,!1}):this.each(function(){var b=a.data(this,c);b?b.option(e||{})._init():a.data(this,c,new d(e,this))}),h)}},a.Widget=function(a,b){arguments.length&&this._createWidget(a,b)},a.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",options:{disabled:!1},_createWidget:function(b,c){a.data(c,this.widgetName,this),this.element=a(c),this.options=a.extend(!0,{},this.options,this._getCreateOptions(),b);var d=this;this.element.bind("remove."+this.widgetName,function(){d.destroy()}),this._create(),this._trigger("create"),this._init()},_getCreateOptions:function(){return a.metadata&&a.metadata.get(this.element[0])[this.widgetName]},_create:function(){},_init:function(){},destroy:function(){this.element.unbind("."+this.widgetName).removeData(this.widgetName),this.widget().unbind("."+this.widgetName).removeAttr("aria-disabled").removeClass(this.widgetBaseClass+"-disabled "+"ui-state-disabled")},widget:function(){return this.element},option:function(c,d){var e=c;if(arguments.length===0)return a.extend({},this.options);if(typeof c=="string"){if(d===b)return this.options[c];e={},e[c]=d}return this._setOptions(e),this},_setOptions:function(b){var c=this;return a.each(b,function(a,b){c._setOption(a,b)}),this},_setOption:function(a,b){return this.options[a]=b,a==="disabled"&&this.widget()[b?"addClass":"removeClass"](this.widgetBaseClass+"-disabled"+" "+"ui-state-disabled").attr("aria-disabled",b),this},enable:function(){return this._setOption("disabled",!1)},disable:function(){return this._setOption("disabled",!0)},_trigger:function(b,c,d){var e,f,g=this.options[b];d=d||{},c=a.Event(c),c.type=(b===this.widgetEventPrefix?b:this.widgetEventPrefix+b).toLowerCase(),c.target=this.element[0],f=c.originalEvent;if(f)for(e in f)e in c||(c[e]=f[e]);return this.element.trigger(c,d),!(a.isFunction(g)&&g.call(this.element[0],c,d)===!1||c.isDefaultPrevented())}}}(jQuery),function(a,b){var c=!1;a(document).mouseup(function(a){c=!1}),a.widget("ui.mouse",{options:{cancel:":input,option",distance:1,delay:0},_mouseInit:function(){var b=this;this.element.bind("mousedown."+this.widgetName,function(a){return b._mouseDown(a)}).bind("click."+this.widgetName,function(c){if(!0===a.data(c.target,b.widgetName+".preventClickEvent"))return a.removeData(c.target,b.widgetName+".preventClickEvent"),c.stopImmediatePropagation(),!1}),this.started=!1},_mouseDestroy:function(){this.element.unbind("."+this.widgetName),this._mouseMoveDelegate&&a(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(b){if(c)return;this._mouseStarted&&this._mouseUp(b),this._mouseDownEvent=b;var d=this,e=b.which==1,f=typeof this.options.cancel=="string"&&b.target.nodeName?a(b.target).closest(this.options.cancel).length:!1;if(!e||f||!this._mouseCapture(b))return!0;this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){d.mouseDelayMet=!0},this.options.delay));if(this._mouseDistanceMet(b)&&this._mouseDelayMet(b)){this._mouseStarted=this._mouseStart(b)!==!1;if(!this._mouseStarted)return b.preventDefault(),!0}return!0===a.data(b.target,this.widgetName+".preventClickEvent")&&a.removeData(b.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(a){return d._mouseMove(a)},this._mouseUpDelegate=function(a){return d._mouseUp(a)},a(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate),b.preventDefault(),c=!0,!0},_mouseMove:function(b){return!a.browser.msie||document.documentMode>=9||!!b.button?this._mouseStarted?(this._mouseDrag(b),b.preventDefault()):(this._mouseDistanceMet(b)&&this._mouseDelayMet(b)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,b)!==!1,this._mouseStarted?this._mouseDrag(b):this._mouseUp(b)),!this._mouseStarted):this._mouseUp(b)},_mouseUp:function(b){return a(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,b.target==this._mouseDownEvent.target&&a.data(b.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(b)),!1},_mouseDistanceMet:function(a){return Math.max(Math.abs(this._mouseDownEvent.pageX-a.pageX),Math.abs(this._mouseDownEvent.pageY-a.pageY))>=this.options.distance},_mouseDelayMet:function(a){return this.mouseDelayMet},_mouseStart:function(a){},_mouseDrag:function(a){},_mouseStop:function(a){},_mouseCapture:function(a){return!0}})}(jQuery),function(a,b){a.widget("ui.draggable",a.ui.mouse,{widgetEventPrefix:"drag",options:{addClasses:!0,appendTo:"parent",axis:!1,connectToSortable:!1,containment:!1,cursor:"auto",cursorAt:!1,grid:!1,handle:!1,helper:"original",iframeFix:!1,opacity:!1,refreshPositions:!1,revert:!1,revertDuration:500,scope:"default",scroll:!0,scrollSensitivity:20,scrollSpeed:20,snap:!1,snapMode:"both",snapTolerance:20,stack:!1,zIndex:!1},_create:function(){this.options.helper=="original"&&!/^(?:r|a|f)/.test(this.element.css("position"))&&(this.element[0].style.position="relative"),this.options.addClasses&&this.element.addClass("ui-draggable"),this.options.disabled&&this.element.addClass("ui-draggable-disabled"),this._mouseInit()},destroy:function(){if(!this.element.data("draggable"))return;return this.element.removeData("draggable").unbind(".draggable").removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled"),this._mouseDestroy(),this},_mouseCapture:function(b){var c=this.options;return this.helper||c.disabled||a(b.target).is(".ui-resizable-handle")?!1:(this.handle=this._getHandle(b),this.handle?(c.iframeFix&&a(c.iframeFix===!0?"iframe":c.iframeFix).each(function(){a('<div class="ui-draggable-iframeFix" style="background: #fff;"></div>').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1e3}).css(a(this).offset()).appendTo("body")}),!0):!1)},_mouseStart:function(b){var c=this.options;return this.helper=this._createHelper(b),this.helper.addClass("ui-draggable-dragging"),this._cacheHelperProportions(),a.ui.ddmanager&&(a.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(),this.offset=this.positionAbs=this.element.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},a.extend(this.offset,{click:{left:b.pageX-this.offset.left,top:b.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.originalPosition=this.position=this._generatePosition(b),this.originalPageX=b.pageX,this.originalPageY=b.pageY,c.cursorAt&&this._adjustOffsetFromHelper(c.cursorAt),c.containment&&this._setContainment(),this._trigger("start",b)===!1?(this._clear(),!1):(this._cacheHelperProportions(),a.ui.ddmanager&&!c.dropBehaviour&&a.ui.ddmanager.prepareOffsets(this,b),this._mouseDrag(b,!0),a.ui.ddmanager&&a.ui.ddmanager.dragStart(this,b),!0)},_mouseDrag:function(b,c){this.position=this._generatePosition(b),this.positionAbs=this._convertPositionTo("absolute");if(!c){var d=this._uiHash();if(this._trigger("drag",b,d)===!1)return this._mouseUp({}),!1;this.position=d.position}if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";return a.ui.ddmanager&&a.ui.ddmanager.drag(this,b),!1},_mouseStop:function(b){var c=!1;a.ui.ddmanager&&!this.options.dropBehaviour&&(c=a.ui.ddmanager.drop(this,b)),this.dropped&&(c=this.dropped,this.dropped=!1);var d=this.element[0],e=!1;while(d&&(d=d.parentNode))d==document&&(e=!0);if(!e&&this.options.helper==="original")return!1;if(this.options.revert=="invalid"&&!c||this.options.revert=="valid"&&c||this.options.revert===!0||a.isFunction(this.options.revert)&&this.options.revert.call(this.element,c)){var f=this;a(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){f._trigger("stop",b)!==!1&&f._clear()})}else this._trigger("stop",b)!==!1&&this._clear();return!1},_mouseUp:function(b){return this.options.iframeFix===!0&&a("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)}),a.ui.ddmanager&&a.ui.ddmanager.dragStop(this,b),a.ui.mouse.prototype._mouseUp.call(this,b)},cancel:function(){return this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear(),this},_getHandle:function(b){var c=!this.options.handle||!a(this.options.handle,this.element).length?!0:!1;return a(this.options.handle,this.element).find("*").andSelf().each(function(){this==b.target&&(c=!0)}),c},_createHelper:function(b){var c=this.options,d=a.isFunction(c.helper)?a(c.helper.apply(this.element[0],[b])):c.helper=="clone"?this.element.clone().removeAttr("id"):this.element;return d.parents("body").length||d.appendTo(c.appendTo=="parent"?this.element[0].parentNode:c.appendTo),d[0]!=this.element[0]&&!/(fixed|absolute)/.test(d.css("position"))&&d.css("position","absolute"),d},_adjustOffsetFromHelper:function(b){typeof b=="string"&&(b=b.split(" ")),a.isArray(b)&&(b={left:+b[0],top:+b[1]||0}),"left"in b&&(this.offset.click.left=b.left+this.margins.left),"right"in b&&(this.offset.click.left=this.helperProportions.width-b.right+this.margins.left),"top"in b&&(this.offset.click.top=b.top+this.margins.top),"bottom"in b&&(this.offset.click.top=this.helperProportions.height-b.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var b=this.offsetParent.offset();this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0])&&(b.left+=this.scrollParent.scrollLeft(),b.top+=this.scrollParent.scrollTop());if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&a.browser.msie)b={top:0,left:0};return{top:b.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:b.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var a=this.element.position();return{top:a.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:a.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var b=this.options;b.containment=="parent"&&(b.containment=this.helper[0].parentNode);if(b.containment=="document"||b.containment=="window")this.containment=[b.containment=="document"?0:a(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,b.containment=="document"?0:a(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,(b.containment=="document"?0:a(window).scrollLeft())+a(b.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(b.containment=="document"?0:a(window).scrollTop())+(a(b.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(b.containment)&&b.containment.constructor!=Array){var c=a(b.containment),d=c[0];if(!d)return;var e=c.offset(),f=a(d).css("overflow")!="hidden";this.containment=[(parseInt(a(d).css("borderLeftWidth"),10)||0)+(parseInt(a(d).css("paddingLeft"),10)||0),(parseInt(a(d).css("borderTopWidth"),10)||0)+(parseInt(a(d).css("paddingTop"),10)||0),(f?Math.max(d.scrollWidth,d.offsetWidth):d.offsetWidth)-(parseInt(a(d).css("borderLeftWidth"),10)||0)-(parseInt(a(d).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(f?Math.max(d.scrollHeight,d.offsetHeight):d.offsetHeight)-(parseInt(a(d).css("borderTopWidth"),10)||0)-(parseInt(a(d).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom],this.relative_container=c}else b.containment.constructor==Array&&(this.containment=b.containment)},_convertPositionTo:function(b,c){c||(c=this.position);var d=b=="absolute"?1:-1,e=this.options,f=this.cssPosition=="absolute"&&(this.scrollParent[0]==document||!a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,g=/(html|body)/i.test(f[0].tagName);return{top:c.top+this.offset.relative.top*d+this.offset.parent.top*d-(a.browser.safari&&a.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():g?0:f.scrollTop())*d),left:c.left+this.offset.relative.left*d+this.offset.parent.left*d-(a.browser.safari&&a.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():g?0:f.scrollLeft())*d)}},_generatePosition:function(b){var c=this.options,d=this.cssPosition=="absolute"&&(this.scrollParent[0]==document||!a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,e=/(html|body)/i.test(d[0].tagName),f=b.pageX,g=b.pageY;if(this.originalPosition){var h;if(this.containment){if(this.relative_container){var i=this.relative_container.offset();h=[this.containment[0]+i.left,this.containment[1]+i.top,this.containment[2]+i.left,this.containment[3]+i.top]}else h=this.containment;b.pageX-this.offset.click.left<h[0]&&(f=h[0]+this.offset.click.left),b.pageY-this.offset.click.top<h[1]&&(g=h[1]+this.offset.click.top),b.pageX-this.offset.click.left>h[2]&&(f=h[2]+this.offset.click.left),b.pageY-this.offset.click.top>h[3]&&(g=h[3]+this.offset.click.top)}if(c.grid){var j=c.grid[1]?this.originalPageY+Math.round((g-this.originalPageY)/c.grid[1])*c.grid[1]:this.originalPageY;g=h?j-this.offset.click.top<h[1]||j-this.offset.click.top>h[3]?j-this.offset.click.top<h[1]?j+c.grid[1]:j-c.grid[1]:j:j;var k=c.grid[0]?this.originalPageX+Math.round((f-this.originalPageX)/c.grid[0])*c.grid[0]:this.originalPageX;f=h?k-this.offset.click.left<h[0]||k-this.offset.click.left>h[2]?k-this.offset.click.left<h[0]?k+c.grid[0]:k-c.grid[0]:k:k}}return{top:g-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+(a.browser.safari&&a.browser.version<526&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollTop():e?0:d.scrollTop()),left:f-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+(a.browser.safari&&a.browser.version<526&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():e?0:d.scrollLeft())}},_clear:function(){this.helper.removeClass("ui-draggable-dragging"),this.helper[0]!=this.element[0]&&!this.cancelHelperRemoval&&this.helper.remove(),this.helper=null,this.cancelHelperRemoval=!1},_trigger:function(b,c,d){return d=d||this._uiHash(),a.ui.plugin.call(this,b,[c,d]),b=="drag"&&(this.positionAbs=this._convertPositionTo("absolute")),a.Widget.prototype._trigger.call(this,b,c,d)},plugins:{},_uiHash:function(a){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}}),a.extend(a.ui.draggable,{version:"1.8.23"}),a.ui.plugin.add("draggable","connectToSortable",{start:function(b,c){var d=a(this).data("draggable"),e=d.options,f=a.extend({},c,{item:d.element});d.sortables=[],a(e.connectToSortable).each(function(){var c=a.data(this,"sortable");c&&!c.options.disabled&&(d.sortables.push({instance:c,shouldRevert:c.options.revert}),c.refreshPositions(),c._trigger("activate",b,f))})},stop:function(b,c){var d=a(this).data("draggable"),e=a.extend({},c,{item:d.element});a.each(d.sortables,function(){this.instance.isOver?(this.instance.isOver=0,d.cancelHelperRemoval=!0,this.instance.cancelHelperRemoval=!1,this.shouldRevert&&(this.instance.options.revert=!0),this.instance._mouseStop(b),this.instance.options.helper=this.instance.options._helper,d.options.helper=="original"&&this.instance.currentItem.css({top:"auto",left:"auto"})):(this.instance.cancelHelperRemoval=!1,this.instance._trigger("deactivate",b,e))})},drag:function(b,c){var d=a(this).data("draggable"),e=this,f=function(b){var c=this.offset.click.top,d=this.offset.click.left,e=this.positionAbs.top,f=this.positionAbs.left,g=b.height,h=b.width,i=b.top,j=b.left;return a.ui.isOver(e+c,f+d,i,j,g,h)};a.each(d.sortables,function(f){this.instance.positionAbs=d.positionAbs,this.instance.helperProportions=d.helperProportions,this.instance.offset.click=d.offset.click,this.instance._intersectsWith(this.instance.containerCache)?(this.instance.isOver||(this.instance.isOver=1,this.instance.currentItem=a(e).clone().removeAttr("id").appendTo(this.instance.element).data("sortable-item",!0),this.instance.options._helper=this.instance.options.helper,this.instance.options.helper=function(){return c.helper[0]},b.target=this.instance.currentItem[0],this.instance._mouseCapture(b,!0),this.instance._mouseStart(b,!0,!0),this.instance.offset.click.top=d.offset.click.top,this.instance.offset.click.left=d.offset.click.left,this.instance.offset.parent.left-=d.offset.parent.left-this.instance.offset.parent.left,this.instance.offset.parent.top-=d.offset.parent.top-this.instance.offset.parent.top,d._trigger("toSortable",b),d.dropped=this.instance.element,d.currentItem=d.element,this.instance.fromOutside=d),this.instance.currentItem&&this.instance._mouseDrag(b)):this.instance.isOver&&(this.instance.isOver=0,this.instance.cancelHelperRemoval=!0,this.instance.options.revert=!1,this.instance._trigger("out",b,this.instance._uiHash(this.instance)),this.instance._mouseStop(b,!0),this.instance.options.helper=this.instance.options._helper,this.instance.currentItem.remove(),this.instance.placeholder&&this.instance.placeholder.remove(),d._trigger("fromSortable",b),d.dropped=!1)})}}),a.ui.plugin.add("draggable","cursor",{start:function(b,c){var d=a("body"),e=a(this).data("draggable").options;d.css("cursor")&&(e._cursor=d.css("cursor")),d.css("cursor",e.cursor)},stop:function(b,c){var d=a(this).data("draggable").options;d._cursor&&a("body").css("cursor",d._cursor)}}),a.ui.plugin.add("draggable","opacity",{start:function(b,c){var d=a(c.helper),e=a(this).data("draggable").options;d.css("opacity")&&(e._opacity=d.css("opacity")),d.css("opacity",e.opacity)},stop:function(b,c){var d=a(this).data("draggable").options;d._opacity&&a(c.helper).css("opacity",d._opacity)}}),a.ui.plugin.add("draggable","scroll",{start:function(b,c){var d=a(this).data("draggable");d.scrollParent[0]!=document&&d.scrollParent[0].tagName!="HTML"&&(d.overflowOffset=d.scrollParent.offset())},drag:function(b,c){var d=a(this).data("draggable"),e=d.options,f=!1;if(d.scrollParent[0]!=document&&d.scrollParent[0].tagName!="HTML"){if(!e.axis||e.axis!="x")d.overflowOffset.top+d.scrollParent[0].offsetHeight-b.pageY<e.scrollSensitivity?d.scrollParent[0].scrollTop=f=d.scrollParent[0].scrollTop+e.scrollSpeed:b.pageY-d.overflowOffset.top<e.scrollSensitivity&&(d.scrollParent[0].scrollTop=f=d.scrollParent[0].scrollTop-e.scrollSpeed);if(!e.axis||e.axis!="y")d.overflowOffset.left+d.scrollParent[0].offsetWidth-b.pageX<e.scrollSensitivity?d.scrollParent[0].scrollLeft=f=d.scrollParent[0].scrollLeft+e.scrollSpeed:b.pageX-d.overflowOffset.left<e.scrollSensitivity&&(d.scrollParent[0].scrollLeft=f=d.scrollParent[0].scrollLeft-e.scrollSpeed)}else{if(!e.axis||e.axis!="x")b.pageY-a(document).scrollTop()<e.scrollSensitivity?f=a(document).scrollTop(a(document).scrollTop()-e.scrollSpeed):a(window).height()-(b.pageY-a(document).scrollTop())<e.scrollSensitivity&&(f=a(document).scrollTop(a(document).scrollTop()+e.scrollSpeed));if(!e.axis||e.axis!="y")b.pageX-a(document).scrollLeft()<e.scrollSensitivity?f=a(document).scrollLeft(a(document).scrollLeft()-e.scrollSpeed):a(window).width()-(b.pageX-a(document).scrollLeft())<e.scrollSensitivity&&(f=a(document).scrollLeft(a(document).scrollLeft()+e.scrollSpeed))}f!==!1&&a.ui.ddmanager&&!e.dropBehaviour&&a.ui.ddmanager.prepareOffsets(d,b)}}),a.ui.plugin.add("draggable","snap",{start:function(b,c){var d=a(this).data("draggable"),e=d.options;d.snapElements=[],a(e.snap.constructor!=String?e.snap.items||":data(draggable)":e.snap).each(function(){var b=a(this),c=b.offset();this!=d.element[0]&&d.snapElements.push({item:this,width:b.outerWidth(),height:b.outerHeight(),top:c.top,left:c.left})})},drag:function(b,c){var d=a(this).data("draggable"),e=d.options,f=e.snapTolerance,g=c.offset.left,h=g+d.helperProportions.width,i=c.offset.top,j=i+d.helperProportions.height;for(var k=d.snapElements.length-1;k>=0;k--){var l=d.snapElements[k].left,m=l+d.snapElements[k].width,n=d.snapElements[k].top,o=n+d.snapElements[k].height;if(!(l-f<g&&g<m+f&&n-f<i&&i<o+f||l-f<g&&g<m+f&&n-f<j&&j<o+f||l-f<h&&h<m+f&&n-f<i&&i<o+f||l-f<h&&h<m+f&&n-f<j&&j<o+f)){d.snapElements[k].snapping&&d.options.snap.release&&d.options.snap.release.call(d.element,b,a.extend(d._uiHash(),{snapItem:d.snapElements[k].item})),d.snapElements[k].snapping=!1;continue}if(e.snapMode!="inner"){var p=Math.abs(n-j)<=f,q=Math.abs(o-i)<=f,r=Math.abs(l-h)<=f,s=Math.abs(m-g)<=f;p&&(c.position.top=d._convertPositionTo("relative",{top:n-d.helperProportions.height,left:0}).top-d.margins.top),q&&(c.position.top=d._convertPositionTo("relative",{top:o,left:0}).top-d.margins.top),r&&(c.position.left=d._convertPositionTo("relative",{top:0,left:l-d.helperProportions.width}).left-d.margins.left),s&&(c.position.left=d._convertPositionTo("relative",{top:0,left:m}).left-d.margins.left)}var t=p||q||r||s;if(e.snapMode!="outer"){var p=Math.abs(n-i)<=f,q=Math.abs(o-j)<=f,r=Math.abs(l-g)<=f,s=Math.abs(m-h)<=f;p&&(c.position.top=d._convertPositionTo("relative",{top:n,left:0}).top-d.margins.top),q&&(c.position.top=d._convertPositionTo("relative",{top:o-d.helperProportions.height,left:0}).top-d.margins.top),r&&(c.position.left=d._convertPositionTo("relative",{top:0,left:l}).left-d.margins.left),s&&(c.position.left=d._convertPositionTo("relative",{top:0,left:m-d.helperProportions.width}).left-d.margins.left)}!d.snapElements[k].snapping&&(p||q||r||s||t)&&d.options.snap.snap&&d.options.snap.snap.call(d.element,b,a.extend(d._uiHash(),{snapItem:d.snapElements[k].item})),d.snapElements[k].snapping=p||q||r||s||t}}}),a.ui.plugin.add("draggable","stack",{start:function(b,c){var d=a(this).data("draggable").options,e=a.makeArray(a(d.stack)).sort(function(b,c){return(parseInt(a(b).css("zIndex"),10)||0)-(parseInt(a(c).css("zIndex"),10)||0)});if(!e.length)return;var f=parseInt(e[0].style.zIndex)||0;a(e).each(function(a){this.style.zIndex=f+a}),this[0].style.zIndex=f+e.length}}),a.ui.plugin.add("draggable","zIndex",{start:function(b,c){var d=a(c.helper),e=a(this).data("draggable").options;d.css("zIndex")&&(e._zIndex=d.css("zIndex")),d.css("zIndex",e.zIndex)},stop:function(b,c){var d=a(this).data("draggable").options;d._zIndex&&a(c.helper).css("zIndex",d._zIndex)}})}(jQuery),function(a,b){a.widget("ui.droppable",{widgetEventPrefix:"drop",options:{accept:"*",activeClass:!1,addClasses:!0,greedy:!1,hoverClass:!1,scope:"default",tolerance:"intersect"},_create:function(){var b=this.options,c=b.accept;this.isover=0,this.isout=1,this.accept=a.isFunction(c)?c:function(a){return a.is(c)},this.proportions={width:this.element[0].offsetWidth,height:this.element[0].offsetHeight},a.ui.ddmanager.droppables[b.scope]=a.ui.ddmanager.droppables[b.scope]||[],a.ui.ddmanager.droppables[b.scope].push(this),b.addClasses&&this.element.addClass("ui-droppable")},destroy:function(){var b=a.ui.ddmanager.droppables[this.options.scope];for(var c=0;c<b.length;c++)b[c]==this&&b.splice(c,1);return this.element.removeClass("ui-droppable ui-droppable-disabled").removeData("droppable").unbind(".droppable"),this},_setOption:function(b,c){b=="accept"&&(this.accept=a.isFunction(c)?c:function(a){return a.is(c)}),a.Widget.prototype._setOption.apply(this,arguments)},_activate:function(b){var c=a.ui.ddmanager.current;this.options.activeClass&&this.element.addClass(this.options.activeClass),c&&this._trigger("activate",b,this.ui(c))},_deactivate:function(b){var c=a.ui.ddmanager.current;this.options.activeClass&&this.element.removeClass(this.options.activeClass),c&&this._trigger("deactivate",b,this.ui(c))},_over:function(b){var c=a.ui.ddmanager.current;if(!c||(c.currentItem||c.element)[0]==this.element[0])return;this.accept.call(this.element[0],c.currentItem||c.element)&&(this.options.hoverClass&&this.element.addClass(this.options.hoverClass),this._trigger("over",b,this.ui(c)))},_out:function(b){var c=a.ui.ddmanager.current;if(!c||(c.currentItem||c.element)[0]==this.element[0])return;this.accept.call(this.element[0],c.currentItem||c.element)&&(this.options.hoverClass&&this.element.removeClass(this.options.hoverClass),this._trigger("out",b,this.ui(c)))},_drop:function(b,c){var d=c||a.ui.ddmanager.current;if(!d||(d.currentItem||d.element)[0]==this.element[0])return!1;var e=!1;return this.element.find(":data(droppable)").not(".ui-draggable-dragging").each(function(){var b=a.data(this,"droppable");if(b.options.greedy&&!b.options.disabled&&b.options.scope==d.options.scope&&b.accept.call(b.element[0],d.currentItem||d.element)&&a.ui.intersect(d,a.extend(b,{offset:b.element.offset()}),b.options.tolerance))return e=!0,!1}),e?!1:this.accept.call(this.element[0],d.currentItem||d.element)?(this.options.activeClass&&this.element.removeClass(this.options.activeClass),this.options.hoverClass&&this.element.removeClass(this.options.hoverClass),this._trigger("drop",b,this.ui(d)),this.element):!1},ui:function(a){return{draggable:a.currentItem||a.element,helper:a.helper,position:a.position,offset:a.positionAbs}}}),a.extend(a.ui.droppable,{version:"1.8.23"}),a.ui.intersect=function(b,c,d){if(!c.offset)return!1;var e=(b.positionAbs||b.position.absolute).left,f=e+b.helperProportions.width,g=(b.positionAbs||b.position.absolute).top,h=g+b.helperProportions.height,i=c.offset.left,j=i+c.proportions.width,k=c.offset.top,l=k+c.proportions.height;switch(d){case"fit":return i<=e&&f<=j&&k<=g&&h<=l;case"intersect":return i<e+b.helperProportions.width/2&&f-b.helperProportions.width/2<j&&k<g+b.helperProportions.height/2&&h-b.helperProportions.height/2<l;case"pointer":var m=(b.positionAbs||b.position.absolute).left+(b.clickOffset||b.offset.click).left,n=(b.positionAbs||b.position.absolute).top+(b.clickOffset||b.offset.click).top,o=a.ui.isOver(n,m,k,i,c.proportions.height,c.proportions.width);return o;case"touch":return(g>=k&&g<=l||h>=k&&h<=l||g<k&&h>l)&&(e>=i&&e<=j||f>=i&&f<=j||e<i&&f>j);default:return!1}},a.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(b,c){var d=a.ui.ddmanager.droppables[b.options.scope]||[],e=c?c.type:null,f=(b.currentItem||b.element).find(":data(droppable)").andSelf();g:for(var h=0;h<d.length;h++){if(d[h].options.disabled||b&&!d[h].accept.call(d[h].element[0],b.currentItem||b.element))continue;for(var i=0;i<f.length;i++)if(f[i]==d[h].element[0]){d[h].proportions.height=0;continue g}d[h].visible=d[h].element.css("display")!="none";if(!d[h].visible)continue;e=="mousedown"&&d[h]._activate.call(d[h],c),d[h].offset=d[h].element.offset(),d[h].proportions={width:d[h].element[0].offsetWidth,height:d[h].element[0].offsetHeight}}},drop:function(b,c){var d=!1;return a.each(a.ui.ddmanager.droppables[b.options.scope]||[],function(){if(!this.options)return;!this.options.disabled&&this.visible&&a.ui.intersect(b,this,this.options.tolerance)&&(d=this._drop.call(this,c)||d),!this.options.disabled&&this.visible&&this.accept.call(this.element[0],b.currentItem||b.element)&&(this.isout=1,this.isover=0,this._deactivate.call(this,c))}),d},dragStart:function(b,c){b.element.parents(":not(body,html)").bind("scroll.droppable",function(){b.options.refreshPositions||a.ui.ddmanager.prepareOffsets(b,c)})},drag:function(b,c){b.options.refreshPositions&&a.ui.ddmanager.prepareOffsets(b,c),a.each(a.ui.ddmanager.droppables[b.options.scope]||[],function(){if(this.options.disabled||this.greedyChild||!this.visible)return;var d=a.ui.intersect(b,this,this.options.tolerance),e=!d&&this.isover==1?"isout":d&&this.isover==0?"isover":null;if(!e)return;var f;if(this.options.greedy){var g=this.element.parents(":data(droppable):eq(0)");g.length&&(f=a.data(g[0],"droppable"),f.greedyChild=e=="isover"?1:0)}f&&e=="isover"&&(f.isover=0,f.isout=1,f._out.call(f,c)),this[e]=1,this[e=="isout"?"isover":"isout"]=0,this[e=="isover"?"_over":"_out"].call(this,c),f&&e=="isout"&&(f.isout=0,f.isover=1,f._over.call(f,c))})},dragStop:function(b,c){b.element.parents(":not(body,html)").unbind("scroll.droppable"),b.options.refreshPositions||a.ui.ddmanager.prepareOffsets(b,c)}}}(jQuery),function(a,b){a.widget("ui.resizable",a.ui.mouse,{widgetEventPrefix:"resize",options:{alsoResize:!1,animate:!1,animateDuration:"slow",animateEasing:"swing",aspectRatio:!1,autoHide:!1,containment:!1,ghost:!1,grid:!1,handles:"e,s,se",helper:!1,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:1e3},_create:function(){var b=this,c=this.options;this.element.addClass("ui-resizable"),a.extend(this,{_aspectRatio:!!c.aspectRatio,aspectRatio:c.aspectRatio,originalElement:this.element,_proportionallyResizeElements:[],_helper:c.helper||c.ghost||c.animate?c.helper||"ui-resizable-helper":null}),this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)&&(this.element.wrap(a('<div class="ui-wrapper" style="overflow: hidden;"></div>').css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("resizable",this.element.data("resizable")),this.elementIsWrapper=!0,this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")}),this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0}),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css({margin:this.originalElement.css("margin")}),this._proportionallyResize()),this.handles=c.handles||(a(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se");if(this.handles.constructor==String){this.handles=="all"&&(this.handles="n,e,s,w,se,sw,ne,nw");var d=this.handles.split(",");this.handles={};for(var e=0;e<d.length;e++){var f=a.trim(d[e]),g="ui-resizable-"+f,h=a('<div class="ui-resizable-handle '+g+'"></div>');h.css({zIndex:c.zIndex}),"se"==f&&h.addClass("ui-icon ui-icon-gripsmall-diagonal-se"),this.handles[f]=".ui-resizable-"+f,this.element.append(h)}}this._renderAxis=function(b){b=b||this.element;for(var c in this.handles){this.handles[c].constructor==String&&(this.handles[c]=a(this.handles[c],this.element).show());if(this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)){var d=a(this.handles[c],this.element),e=0;e=/sw|ne|nw|se|n|s/.test(c)?d.outerHeight():d.outerWidth();var f=["padding",/ne|nw|n/.test(c)?"Top":/se|sw|s/.test(c)?"Bottom":/^e$/.test(c)?"Right":"Left"].join("");b.css(f,e),this._proportionallyResize()}if(!a(this.handles[c]).length)continue}},this._renderAxis(this.element),this._handles=a(".ui-resizable-handle",this.element).disableSelection(),this._handles.mouseover(function(){if(!b.resizing){if(this.className)var a=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);b.axis=a&&a[1]?a[1]:"se"}}),c.autoHide&&(this._handles.hide(),a(this.element).addClass("ui-resizable-autohide").hover(function(){if(c.disabled)return;a(this).removeClass("ui-resizable-autohide"),b._handles.show()},function(){if(c.disabled)return;b.resizing||(a(this).addClass("ui-resizable-autohide"),b._handles.hide())})),this._mouseInit()},destroy:function(){this._mouseDestroy();var b=function(b){a(b).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};if(this.elementIsWrapper){b(this.element);var c=this.element;c.after(this.originalElement.css({position:c.css("position"),width:c.outerWidth(),height:c.outerHeight(),top:c.css("top"),left:c.css("left")})).remove()}return this.originalElement.css("resize",this.originalResizeStyle),b(this.originalElement),this},_mouseCapture:function(b){var c=!1;for(var d in this.handles)a(this.handles[d])[0]==b.target&&(c=!0);return!this.options.disabled&&c},_mouseStart:function(b){var d=this.options,e=this.element.position(),f=this.element;this.resizing=!0,this.documentScroll={top:a(document).scrollTop(),left:a(document).scrollLeft()},(f.is(".ui-draggable")||/absolute/.test(f.css("position")))&&f.css({position:"absolute",top:e.top,left:e.left}),this._renderProxy();var g=c(this.helper.css("left")),h=c(this.helper.css("top"));d.containment&&(g+=a(d.containment).scrollLeft()||0,h+=a(d.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:g,top:h},this.size=this._helper?{width:f.outerWidth(),height:f.outerHeight()}:{width:f.width(),height:f.height()},this.originalSize=this._helper?{width:f.outerWidth(),height:f.outerHeight()}:{width:f.width(),height:f.height()},this.originalPosition={left:g,top:h},this.sizeDiff={width:f.outerWidth()-f.width(),height:f.outerHeight()-f.height()},this.originalMousePosition={left:b.pageX,top:b.pageY},this.aspectRatio=typeof d.aspectRatio=="number"?d.aspectRatio:this.originalSize.width/this.originalSize.height||1;var i=a(".ui-resizable-"+this.axis).css("cursor");return a("body").css("cursor",i=="auto"?this.axis+"-resize":i),f.addClass("ui-resizable-resizing"),this._propagate("start",b),!0},_mouseDrag:function(b){var c=this.helper,d=this.options,e={},f=this,g=this.originalMousePosition,h=this.axis,i=b.pageX-g.left||0,j=b.pageY-g.top||0,k=this._change[h];if(!k)return!1;var l=k.apply(this,[b,i,j]),m=a.browser.msie&&a.browser.version<7,n=this.sizeDiff;this._updateVirtualBoundaries(b.shiftKey);if(this._aspectRatio||b.shiftKey)l=this._updateRatio(l,b);return l=this._respectSize(l,b),this._propagate("resize",b),c.css({top:this.position.top+"px",left:this.position.left+"px",width:this.size.width+"px",height:this.size.height+"px"}),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),this._updateCache(l),this._trigger("resize",b,this.ui()),!1},_mouseStop:function(b){this.resizing=!1;var c=this.options,d=this;if(this._helper){var e=this._proportionallyResizeElements,f=e.length&&/textarea/i.test(e[0].nodeName),g=f&&a.ui.hasScroll(e[0],"left")?0:d.sizeDiff.height,h=f?0:d.sizeDiff.width,i={width:d.helper.width()-h,height:d.helper.height()-g},j=parseInt(d.element.css("left"),10)+(d.position.left-d.originalPosition.left)||null,k=parseInt(d.element.css("top"),10)+(d.position.top-d.originalPosition.top)||null;c.animate||this.element.css(a.extend(i,{top:k,left:j})),d.helper.height(d.size.height),d.helper.width(d.size.width),this._helper&&!c.animate&&this._proportionallyResize()}return a("body").css("cursor","auto"),this.element.removeClass("ui-resizable-resizing"),this._propagate("stop",b),this._helper&&this.helper.remove(),!1},_updateVirtualBoundaries:function(a){var b=this.options,c,e,f,g,h;h={minWidth:d(b.minWidth)?b.minWidth:0,maxWidth:d(b.maxWidth)?b.maxWidth:Infinity,minHeight:d(b.minHeight)?b.minHeight:0,maxHeight:d(b.maxHeight)?b.maxHeight:Infinity};if(this._aspectRatio||a)c=h.minHeight*this.aspectRatio,f=h.minWidth/this.aspectRatio,e=h.maxHeight*this.aspectRatio,g=h.maxWidth/this.aspectRatio,c>h.minWidth&&(h.minWidth=c),f>h.minHeight&&(h.minHeight=f),e<h.maxWidth&&(h.maxWidth=e),g<h.maxHeight&&(h.maxHeight=g);this._vBoundaries=h},_updateCache:function(a){var b=this.options;this.offset=this.helper.offset(),d(a.left)&&(this.position.left=a.left),d(a.top)&&(this.position.top=a.top),d(a.height)&&(this.size.height=a.height),d(a.width)&&(this.size.width=a.width)},_updateRatio:function(a,b){var c=this.options,e=this.position,f=this.size,g=this.axis;return d(a.height)?a.width=a.height*this.aspectRatio:d(a.width)&&(a.height=a.width/this.aspectRatio),g=="sw"&&(a.left=e.left+(f.width-a.width),a.top=null),g=="nw"&&(a.top=e.top+(f.height-a.height),a.left=e.left+(f.width-a.width)),a},_respectSize:function(a,b){var c=this.helper,e=this._vBoundaries,f=this._aspectRatio||b.shiftKey,g=this.axis,h=d(a.width)&&e.maxWidth&&e.maxWidth<a.width,i=d(a.height)&&e.maxHeight&&e.maxHeight<a.height,j=d(a.width)&&e.minWidth&&e.minWidth>a.width,k=d(a.height)&&e.minHeight&&e.minHeight>a.height;j&&(a.width=e.minWidth),k&&(a.height=e.minHeight),h&&(a.width=e.maxWidth),i&&(a.height=e.maxHeight);var l=this.originalPosition.left+this.originalSize.width,m=this.position.top+this.size.height,n=/sw|nw|w/.test(g),o=/nw|ne|n/.test(g);j&&n&&(a.left=l-e.minWidth),h&&n&&(a.left=l-e.maxWidth),k&&o&&(a.top=m-e.minHeight),i&&o&&(a.top=m-e.maxHeight);var p=!a.width&&!a.height;return p&&!a.left&&a.top?a.top=null:p&&!a.top&&a.left&&(a.left=null),a},_proportionallyResize:function(){var b=this.options;if(!this._proportionallyResizeElements.length)return;var c=this.helper||this.element;for(var d=0;d<this._proportionallyResizeElements.length;d++){var e=this._proportionallyResizeElements[d];if(!this.borderDif){var f=[e.css("borderTopWidth"),e.css("borderRightWidth"),e.css("borderBottomWidth"),e.css("borderLeftWidth")],g=[e.css("paddingTop"),e.css("paddingRight"),e.css("paddingBottom"),e.css("paddingLeft")];this.borderDif=a.map(f,function(a,b){var c=parseInt(a,10)||0,d=parseInt(g[b],10)||0;return c+d})}if(!a.browser.msie||!a(c).is(":hidden")&&!a(c).parents(":hidden").length)e.css({height:c.height()-this.borderDif[0]-this.borderDif[2]||0,width:c.width()-this.borderDif[1]-this.borderDif[3]||0});else continue}},_renderProxy:function(){var b=this.element,c=this.options;this.elementOffset=b.offset();if(this._helper){this.helper=this.helper||a('<div style="overflow:hidden;"></div>');var d=a.browser.msie&&a.browser.version<7,e=d?1:0,f=d?2:-1;this.helper.addClass(this._helper).css({width:this.element.outerWidth()+f,height:this.element.outerHeight()+f,position:"absolute",left:this.elementOffset.left-e+"px",top:this.elementOffset.top-e+"px",zIndex:++c.zIndex}),this.helper.appendTo("body").disableSelection()}else this.helper=this.element},_change:{e:function(a,b,c){return{width:this.originalSize.width+b}},w:function(a,b,c){var d=this.options,e=this.originalSize,f=this.originalPosition;return{left:f.left+b,width:e.width-b}},n:function(a,b,c){var d=this.options,e=this.originalSize,f=this.originalPosition;return{top:f.top+c,height:e.height-c}},s:function(a,b,c){return{height:this.originalSize.height+c}},se:function(b,c,d){return a.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[b,c,d]))},sw:function(b,c,d){return a.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[b,c,d]))},ne:function(b,c,d){return a.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[b,c,d]))},nw:function(b,c,d){return a.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[b,c,d]))}},_propagate:function(b,c){a.ui.plugin.call(this,b,[c,this.ui()]),b!="resize"&&this._trigger(b,c,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),a.extend(a.ui.resizable,{version:"1.8.23"}),a.ui.plugin.add("resizable","alsoResize",{start:function(b,c){var d=a(this).data("resizable"),e=d.options,f=function(b){a(b).each(function(){var b=a(this);b.data("resizable-alsoresize",{width:parseInt(b.width(),10),height:parseInt(b.height(),10),left:parseInt(b.css("left"),10),top:parseInt(b.css("top"),10)})})};typeof e.alsoResize=="object"&&!e.alsoResize.parentNode?e.alsoResize.length?(e.alsoResize=e.alsoResize[0],f(e.alsoResize)):a.each(e.alsoResize,function(a){f(a)}):f(e.alsoResize)},resize:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d.originalSize,g=d.originalPosition,h={height:d.size.height-f.height||0,width:d.size.width-f.width||0,top:d.position.top-g.top||0,left:d.position.left-g.left||0},i=function(b,d){a(b).each(function(){var b=a(this),e=a(this).data("resizable-alsoresize"),f={},g=d&&d.length?d:b.parents(c.originalElement[0]).length?["width","height"]:["width","height","top","left"];a.each(g,function(a,b){var c=(e[b]||0)+(h[b]||0);c&&c>=0&&(f[b]=c||null)}),b.css(f)})};typeof e.alsoResize=="object"&&!e.alsoResize.nodeType?a.each(e.alsoResize,function(a,b){i(a,b)}):i(e.alsoResize)},stop:function(b,c){a(this).removeData("resizable-alsoresize")}}),a.ui.plugin.add("resizable","animate",{stop:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d._proportionallyResizeElements,g=f.length&&/textarea/i.test(f[0].nodeName),h=g&&a.ui.hasScroll(f[0],"left")?0:d.sizeDiff.height,i=g?0:d.sizeDiff.width,j={width:d.size.width-i,height:d.size.height-h},k=parseInt(d.element.css("left"),10)+(d.position.left-d.originalPosition.left)||null,l=parseInt(d.element.css("top"),10)+(d.position.top-d.originalPosition.top)||null;d.element.animate(a.extend(j,l&&k?{top:l,left:k}:{}),{duration:e.animateDuration,easing:e.animateEasing,step:function(){var c={width:parseInt(d.element.css("width"),10),height:parseInt(d.element.css("height"),10),top:parseInt(d.element.css("top"),10),left:parseInt(d.element.css("left"),10)};f&&f.length&&a(f[0]).css({width:c.width,height:c.height}),d._updateCache(c),d._propagate("resize",b)}})}}),a.ui.plugin.add("resizable","containment",{start:function(b,d){var e=a(this).data("resizable"),f=e.options,g=e.element,h=f.containment,i=h instanceof a?h.get(0):/parent/.test(h)?g.parent().get(0):h;if(!i)return;e.containerElement=a(i);if(/document/.test(h)||h==document)e.containerOffset={left:0,top:0},e.containerPosition={left:0,top:0},e.parentData={element:a(document),left:0,top:0,width:a(document).width(),height:a(document).height()||document.body.parentNode.scrollHeight};else{var j=a(i),k=[];a(["Top","Right","Left","Bottom"]).each(function(a,b){k[a]=c(j.css("padding"+b))}),e.containerOffset=j.offset(),e.containerPosition=j.position(),e.containerSize={height:j.innerHeight()-k[3],width:j.innerWidth()-k[1]};var l=e.containerOffset,m=e.containerSize.height,n=e.containerSize.width,o=a.ui.hasScroll(i,"left")?i.scrollWidth:n,p=a.ui.hasScroll(i)?i.scrollHeight:m;e.parentData={element:i,left:l.left,top:l.top,width:o,height:p}}},resize:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d.containerSize,g=d.containerOffset,h=d.size,i=d.position,j=d._aspectRatio||b.shiftKey,k={top:0,left:0},l=d.containerElement;l[0]!=document&&/static/.test(l.css("position"))&&(k=g),i.left<(d._helper?g.left:0)&&(d.size.width=d.size.width+(d._helper?d.position.left-g.left:d.position.left-k.left),j&&(d.size.height=d.size.width/d.aspectRatio),d.position.left=e.helper?g.left:0),i.top<(d._helper?g.top:0)&&(d.size.height=d.size.height+(d._helper?d.position.top-g.top:d.position.top),j&&(d.size.width=d.size.height*d.aspectRatio),d.position.top=d._helper?g.top:0),d.offset.left=d.parentData.left+d.position.left,d.offset.top=d.parentData.top+d.position.top;var m=Math.abs((d._helper?d.offset.left-k.left:d.offset.left-k.left)+d.sizeDiff.width),n=Math.abs((d._helper?d.offset.top-k.top:d.offset.top-g.top)+d.sizeDiff.height),o=d.containerElement.get(0)==d.element.parent().get(0),p=/relative|absolute/.test(d.containerElement.css("position"));o&&p&&(m-=d.parentData.left),m+d.size.width>=d.parentData.width&&(d.size.width=d.parentData.width-m,j&&(d.size.height=d.size.width/d.aspectRatio)),n+d.size.height>=d.parentData.height&&(d.size.height=d.parentData.height-n,j&&(d.size.width=d.size.height*d.aspectRatio))},stop:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d.position,g=d.containerOffset,h=d.containerPosition,i=d.containerElement,j=a(d.helper),k=j.offset(),l=j.outerWidth()-d.sizeDiff.width,m=j.outerHeight()-d.sizeDiff.height;d._helper&&!e.animate&&/relative/.test(i.css("position"))&&a(this).css({left:k.left-h.left-g.left,width:l,height:m}),d._helper&&!e.animate&&/static/.test(i.css("position"))&&a(this).css({left:k.left-h.left-g.left,width:l,height:m})}}),a.ui.plugin.add("resizable","ghost",{start:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d.size;d.ghost=d.originalElement.clone(),d.ghost.css({opacity:.25,display:"block",position:"relative",height:f.height,width:f.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass(typeof e.ghost=="string"?e.ghost:""),d.ghost.appendTo(d.helper)},resize:function(b,c){var d=a(this).data("resizable"),e=d.options;d.ghost&&d.ghost.css({position:"relative",height:d.size.height,width:d.size.width})},stop:function(b,c){var d=a(this).data("resizable"),e=d.options;d.ghost&&d.helper&&d.helper.get(0).removeChild(d.ghost.get(0))}}),a.ui.plugin.add("resizable","grid",{resize:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d.size,g=d.originalSize,h=d.originalPosition,i=d.axis,j=e._aspectRatio||b.shiftKey;e.grid=typeof e.grid=="number"?[e.grid,e.grid]:e.grid;var k=Math.round((f.width-g.width)/(e.grid[0]||1))*(e.grid[0]||1),l=Math.round((f.height-g.height)/(e.grid[1]||1))*(e.grid[1]||1);/^(se|s|e)$/.test(i)?(d.size.width=g.width+k,d.size.height=g.height+l):/^(ne)$/.test(i)?(d.size.width=g.width+k,d.size.height=g.height+l,d.position.top=h.top-l):/^(sw)$/.test(i)?(d.size.width=g.width+k,d.size.height=g.height+l,d.position.left=h.left-k):(d.size.width=g.width+k,d.size.height=g.height+l,d.position.top=h.top-l,d.position.left=h.left-k)}});var c=function(a){return parseInt(a,10)||0},d=function(a){return!isNaN(parseInt(a,10))}}(jQuery),function(a,b){a.widget("ui.selectable",a.ui.mouse,{options:{appendTo:"body",autoRefresh:!0,distance:0,filter:"*",tolerance:"touch"},_create:function(){var b=this;this.element.addClass("ui-selectable"),this.dragged=!1;var c;this.refresh=function(){c=a(b.options.filter,b.element[0]),c.addClass("ui-selectee"),c.each(function(){var b=a(this),c=b.offset();a.data(this,"selectable-item",{element:this,$element:b,left:c.left,top:c.top,right:c.left+b.outerWidth(),bottom:c.top+b.outerHeight(),startselected:!1,selected:b.hasClass("ui-selected"),selecting:b.hasClass("ui-selecting"),unselecting:b.hasClass("ui-unselecting")})})},this.refresh(),this.selectees=c.addClass("ui-selectee"),this._mouseInit(),this.helper=a("<div class='ui-selectable-helper'></div>")},destroy:function(){return this.selectees.removeClass("ui-selectee").removeData("selectable-item"),this.element.removeClass("ui-selectable ui-selectable-disabled").removeData("selectable").unbind(".selectable"),this._mouseDestroy(),this},_mouseStart:function(b){var c=this;this.opos=[b.pageX,b.pageY];if(this.options.disabled)return;var d=this.options;this.selectees=a(d.filter,this.element[0]),this._trigger("start",b),a(d.appendTo).append(this.helper),this.helper.css({left:b.clientX,top:b.clientY,width:0,height:0}),d.autoRefresh&&this.refresh(),this.selectees.filter(".ui-selected").each(function(){var d=a.data(this,"selectable-item");d.startselected=!0,!b.metaKey&&!b.ctrlKey&&(d.$element.removeClass("ui-selected"),d.selected=!1,d.$element.addClass("ui-unselecting"),d.unselecting=!0,c._trigger("unselecting",b,{unselecting:d.element}))}),a(b.target).parents().andSelf().each(function(){var d=a.data(this,"selectable-item");if(d){var e=!b.metaKey&&!b.ctrlKey||!d.$element.hasClass("ui-selected");return d.$element.removeClass(e?"ui-unselecting":"ui-selected").addClass(e?"ui-selecting":"ui-unselecting"),d.unselecting=!e,d.selecting=e,d.selected=e,e?c._trigger("selecting",b,{selecting:d.element}):c._trigger("unselecting",b,{unselecting:d.element}),!1}})},_mouseDrag:function(b){var c=this;this.dragged=!0;if(this.options.disabled)return;var d=this.options,e=this.opos[0],f=this.opos[1],g=b.pageX,h=b.pageY;if(e>g){var i=g;g=e,e=i}if(f>h){var i=h;h=f,f=i}return this.helper.css({left:e,top:f,width:g-e,height:h-f}),this.selectees.each(function(){var i=a.data(this,"selectable-item");if(!i||i.element==c.element[0])return;var j=!1;d.tolerance=="touch"?j=!(i.left>g||i.right<e||i.top>h||i.bottom<f):d.tolerance=="fit"&&(j=i.left>e&&i.right<g&&i.top>f&&i.bottom<h),j?(i.selected&&(i.$element.removeClass("ui-selected"),i.selected=!1),i.unselecting&&(i.$element.removeClass("ui-unselecting"),i.unselecting=!1),i.selecting||(i.$element.addClass("ui-selecting"),i.selecting=!0,c._trigger("selecting",b,{selecting:i.element}))):(i.selecting&&((b.metaKey||b.ctrlKey)&&i.startselected?(i.$element.removeClass("ui-selecting"),i.selecting=!1,i.$element.addClass("ui-selected"),i.selected=!0):(i.$element.removeClass("ui-selecting"),i.selecting=!1,i.startselected&&(i.$element.addClass("ui-unselecting"),i.unselecting=!0),c._trigger("unselecting",b,{unselecting:i.element}))),i.selected&&!b.metaKey&&!b.ctrlKey&&!i.startselected&&(i.$element.removeClass("ui-selected"),i.selected=!1,i.$element.addClass("ui-unselecting"),i.unselecting=!0,c._trigger("unselecting",b,{unselecting:i.element})))}),!1},_mouseStop:function(b){var c=this;this.dragged=!1;var d=this.options;return a(".ui-unselecting",this.element[0]).each(function(){var d=a.data(this,"selectable-item");d.$element.removeClass("ui-unselecting"),d.unselecting=!1,d.startselected=!1,c._trigger("unselected",b,{unselected:d.element})}),a(".ui-selecting",this.element[0]).each(function(){var d=a.data(this,"selectable-item");d.$element.removeClass("ui-selecting").addClass("ui-selected"),d.selecting=!1,d.selected=!0,d.startselected=!0,c._trigger("selected",b,{selected:d.element})}),this._trigger("stop",b),this.helper.remove(),!1}}),a.extend(a.ui.selectable,{version:"1.8.23"})}(jQuery),function(a,b){a.widget("ui.sortable",a.ui.mouse,{widgetEventPrefix:"sort",ready:!1,options:{appendTo:"parent",axis:!1,connectWith:!1,containment:!1,cursor:"auto",cursorAt:!1,dropOnEmpty:!0,forcePlaceholderSize:!1,forceHelperSize:!1,grid:!1,handle:!1,helper:"original",items:"> *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1e3},_create:function(){var a=this.options;this.containerCache={},this.element.addClass("ui-sortable"),this.refresh(),this.floating=this.items.length?a.axis==="x"||/left|right/.test(this.items[0].item.css("float"))||/inline|table-cell/.test(this.items[0].item.css("display")):!1,this.offset=this.element.offset(),this._mouseInit(),this.ready=!0},destroy:function(){a.Widget.prototype.destroy.call(this),this.element.removeClass("ui-sortable ui-sortable-disabled"),this._mouseDestroy();for(var b=this.items.length-1;b>=0;b--)this.items[b].item.removeData(this.widgetName+"-item");return this},_setOption:function(b,c){b==="disabled"?(this.options[b]=c,this.widget()[c?"addClass":"removeClass"]("ui-sortable-disabled")):a.Widget.prototype._setOption.apply(this,arguments)},_mouseCapture:function(b,c){var d=this;if(this.reverting)return!1;if(this.options.disabled||this.options.type=="static")return!1;this._refreshItems(b);var e=null,f=this,g=a(b.target).parents().each(function(){if(a.data(this,d.widgetName+"-item")==f)return e=a(this),!1});a.data(b.target,d.widgetName+"-item")==f&&(e=a(b.target));if(!e)return!1;if(this.options.handle&&!c){var h=!1;a(this.options.handle,e).find("*").andSelf().each(function(){this==b.target&&(h=!0)});if(!h)return!1}return this.currentItem=e,this._removeCurrentsFromItems(),!0},_mouseStart:function(b,c,d){var e=this.options,f=this;this.currentContainer=this,this.refreshPositions(),this.helper=this._createHelper(b),this._cacheHelperProportions(),this._cacheMargins(),this.scrollParent=this.helper.scrollParent(),this.offset=this.currentItem.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},a.extend(this.offset,{click:{left:b.pageX-this.offset.left,top:b.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.helper.css("position","absolute"),this.cssPosition=this.helper.css("position"),this.originalPosition=this._generatePosition(b),this.originalPageX=b.pageX,this.originalPageY=b.pageY,e.cursorAt&&this._adjustOffsetFromHelper(e.cursorAt),this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]},this.helper[0]!=this.currentItem[0]&&this.currentItem.hide(),this._createPlaceholder(),e.containment&&this._setContainment(),e.cursor&&(a("body").css("cursor")&&(this._storedCursor=a("body").css("cursor")),a("body").css("cursor",e.cursor)),e.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",e.opacity)),e.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",e.zIndex)),this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"&&(this.overflowOffset=this.scrollParent.offset()),this._trigger("start",b,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions();if(!d)for(var g=this.containers.length-1;g>=0;g--)this.containers[g]._trigger("activate",b,f._uiHash(this));return a.ui.ddmanager&&(a.ui.ddmanager.current=this),a.ui.ddmanager&&!e.dropBehaviour&&a.ui.ddmanager.prepareOffsets(this,b),this.dragging=!0,this.helper.addClass("ui-sortable-helper"),this._mouseDrag(b),!0},_mouseDrag:function(b){this.position=this._generatePosition(b),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs);if(this.options.scroll){var c=this.options,d=!1;this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-b.pageY<c.scrollSensitivity?this.scrollParent[0].scrollTop=d=this.scrollParent[0].scrollTop+c.scrollSpeed:b.pageY-this.overflowOffset.top<c.scrollSensitivity&&(this.scrollParent[0].scrollTop=d=this.scrollParent[0].scrollTop-c.scrollSpeed),this.overflowOffset.left+this.scrollParent[0].offsetWidth-b.pageX<c.scrollSensitivity?this.scrollParent[0].scrollLeft=d=this.scrollParent[0].scrollLeft+c.scrollSpeed:b.pageX-this.overflowOffset.left<c.scrollSensitivity&&(this.scrollParent[0].scrollLeft=d=this.scrollParent[0].scrollLeft-c.scrollSpeed)):(b.pageY-a(document).scrollTop()<c.scrollSensitivity?d=a(document).scrollTop(a(document).scrollTop()-c.scrollSpeed):a(window).height()-(b.pageY-a(document).scrollTop())<c.scrollSensitivity&&(d=a(document).scrollTop(a(document).scrollTop()+c.scrollSpeed)),b.pageX-a(document).scrollLeft()<c.scrollSensitivity?d=a(document).scrollLeft(a(document).scrollLeft()-c.scrollSpeed):a(window).width()-(b.pageX-a(document).scrollLeft())<c.scrollSensitivity&&(d=a(document).scrollLeft(a(document).scrollLeft()+c.scrollSpeed))),d!==!1&&a.ui.ddmanager&&!c.dropBehaviour&&a.ui.ddmanager.prepareOffsets(this,b)}this.positionAbs=this._convertPositionTo("absolute");if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";for(var e=this.items.length-1;e>=0;e--){var f=this.items[e],g=f.item[0],h=this._intersectsWithPointer(f);if(!h)continue;if(g!=this.currentItem[0]&&this.placeholder[h==1?"next":"prev"]()[0]!=g&&!a.ui.contains(this.placeholder[0],g)&&(this.options.type=="semi-dynamic"?!a.ui.contains(this.element[0],g):!0)){this.direction=h==1?"down":"up";if(this.options.tolerance=="pointer"||this._intersectsWithSides(f))this._rearrange(b,f);else break;this._trigger("change",b,this._uiHash());break}}return this._contactContainers(b),a.ui.ddmanager&&a.ui.ddmanager.drag(this,b),this._trigger("sort",b,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(b,c){if(!b)return;a.ui.ddmanager&&!this.options.dropBehaviour&&a.ui.ddmanager.drop(this,b);if(this.options.revert){var d=this,e=d.placeholder.offset();d.reverting=!0,a(this.helper).animate({left:e.left-this.offset.parent.left-d.margins.left+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollLeft),top:e.top-this.offset.parent.top-d.margins.top+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollTop)},parseInt(this.options.revert,10)||500,function(){d._clear(b)})}else this._clear(b,c);return!1},cancel:function(){var b=this;if(this.dragging){this._mouseUp({target:null}),this.options.helper=="original"?this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"):this.currentItem.show();for(var c=this.containers.length-1;c>=0;c--)this.containers[c]._trigger("deactivate",null,b._uiHash(this)),this.containers[c].containerCache.over&&(this.containers[c]._trigger("out",null,b._uiHash(this)),this.containers[c].containerCache.over=0)}return this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.options.helper!="original"&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),a.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?a(this.domPosition.prev).after(this.currentItem):a(this.domPosition.parent).prepend(this.currentItem)),this},serialize:function(b){var c=this._getItemsAsjQuery(b&&b.connected),d=[];return b=b||{},a(c).each(function(){var c=(a(b.item||this).attr(b.attribute||"id")||"").match(b.expression||/(.+)[-=_](.+)/);c&&d.push((b.key||c[1]+"[]")+"="+(b.key&&b.expression?c[1]:c[2]))}),!d.length&&b.key&&d.push(b.key+"="),d.join("&")},toArray:function(b){var c=this._getItemsAsjQuery(b&&b.connected),d=[];return b=b||{},c.each(function(){d.push(a(b.item||this).attr(b.attribute||"id")||"")}),d},_intersectsWith:function(a){var b=this.positionAbs.left,c=b+this.helperProportions.width,d=this.positionAbs.top,e=d+this.helperProportions.height,f=a.left,g=f+a.width,h=a.top,i=h+a.height,j=this.offset.click.top,k=this.offset.click.left,l=d+j>h&&d+j<i&&b+k>f&&b+k<g;return this.options.tolerance=="pointer"||this.options.forcePointerForContainers||this.options.tolerance!="pointer"&&this.helperProportions[this.floating?"width":"height"]>a[this.floating?"width":"height"]?l:f<b+this.helperProportions.width/2&&c-this.helperProportions.width/2<g&&h<d+this.helperProportions.height/2&&e-this.helperProportions.height/2<i},_intersectsWithPointer:function(b){var c=this.options.axis==="x"||a.ui.isOverAxis(this.positionAbs.top+this.offset.click.top,b.top,b.height),d=this.options.axis==="y"||a.ui.isOverAxis(this.positionAbs.left+this.offset.click.left,b.left,b.width),e=c&&d,f=this._getDragVerticalDirection(),g=this._getDragHorizontalDirection();return e?this.floating?g&&g=="right"||f=="down"?2:1:f&&(f=="down"?2:1):!1},_intersectsWithSides:function(b){var c=a.ui.isOverAxis(this.positionAbs.top+this.offset.click.top,b.top+b.height/2,b.height),d=a.ui.isOverAxis(this.positionAbs.left+this.offset.click.left,b.left+b.width/2,b.width),e=this._getDragVerticalDirection(),f=this._getDragHorizontalDirection();return this.floating&&f?f=="right"&&d||f=="left"&&!d:e&&(e=="down"&&c||e=="up"&&!c)},_getDragVerticalDirection:function(){var a=this.positionAbs.top-this.lastPositionAbs.top;return a!=0&&(a>0?"down":"up")},_getDragHorizontalDirection:function(){var a=this.positionAbs.left-this.lastPositionAbs.left;return a!=0&&(a>0?"right":"left")},refresh:function(a){return this._refreshItems(a),this.refreshPositions(),this},_connectWith:function(){var a=this.options;return a.connectWith.constructor==String?[a.connectWith]:a.connectWith},_getItemsAsjQuery:function(b){var c=this,d=[],e=[],f=this._connectWith();if(f&&b)for(var g=f.length-1;g>=0;g--){var h=a(f[g]);for(var i=h.length-1;i>=0;i--){var j=a.data(h[i],this.widgetName);j&&j!=this&&!j.options.disabled&&e.push([a.isFunction(j.options.items)?j.options.items.call(j.element):a(j.options.items,j.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),j])}}e.push([a.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):a(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]);for(var g=e.length-1;g>=0;g--)e[g][0].each(function(){d.push(this)});return a(d)},_removeCurrentsFromItems:function(){var a=this.currentItem.find(":data("+this.widgetName+"-item)");for(var b=0;b<this.items.length;b++)for(var c=0;c<a.length;c++)a[c]==this.items[b].item[0]&&this.items.splice(b,1)},_refreshItems:function(b){this.items=[],this.containers=[this];var c=this.items,d=this,e=[[a.isFunction(this.options.items)?this.options.items.call(this.element[0],b,{item:this.currentItem}):a(this.options.items,this.element),this]],f=this._connectWith();if(f&&this.ready)for(var g=f.length-1;g>=0;g--){var h=a(f[g]);for(var i=h.length-1;i>=0;i--){var j=a.data(h[i],this.widgetName);j&&j!=this&&!j.options.disabled&&(e.push([a.isFunction(j.options.items)?j.options.items.call(j.element[0],b,{item:this.currentItem}):a(j.options.items,j.element),j]),this.containers.push(j))}}for(var g=e.length-1;g>=0;g--){var k=e[g][1],l=e[g][0];for(var i=0,m=l.length;i<m;i++){var n=a(l[i]);n.data(this.widgetName+"-item",k),c.push({item:n,instance:k,width:0,height:0,left:0,top:0})}}},refreshPositions:function(b){this.offsetParent&&this.helper&&(this.offset.parent=this._getParentOffset());for(var c=this.items.length-1;c>=0;c--){var d=this.items[c];if(d.instance!=this.currentContainer&&this.currentContainer&&d.item[0]!=this.currentItem[0])continue;var e=this.options.toleranceElement?a(this.options.toleranceElement,d.item):d.item;b||(d.width=e.outerWidth(),d.height=e.outerHeight());var f=e.offset();d.left=f.left,d.top=f.top}if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(var c=this.containers.length-1;c>=0;c--){var f=this.containers[c].element.offset();this.containers[c].containerCache.left=f.left,this.containers[c].containerCache.top=f.top,this.containers[c].containerCache.width=this.containers[c].element.outerWidth(),this.containers[c].containerCache.height=this.containers[c].element.outerHeight()}return this},_createPlaceholder:function(b){var c=b||this,d=c.options;if(!d.placeholder||d.placeholder.constructor==String){var e=d.placeholder;d.placeholder={element:function(){var b=a(document.createElement(c.currentItem[0].nodeName)).addClass(e||c.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper")[0];return e||(b.style.visibility="hidden"),b},update:function(a,b){if(e&&!d.forcePlaceholderSize)return;b.height()||b.height(c.currentItem.innerHeight()-parseInt(c.currentItem.css("paddingTop")||0,10)-parseInt(c.currentItem.css("paddingBottom")||0,10)),b.width()||b.width(c.currentItem.innerWidth()-parseInt(c.currentItem.css("paddingLeft")||0,10)-parseInt(c.currentItem.css("paddingRight")||0,10))}}}c.placeholder=a(d.placeholder.element.call(c.element,c.currentItem)),c.currentItem.after(c.placeholder),d.placeholder.update(c,c.placeholder)},_contactContainers:function(b){var c=null,d=null;for(var e=this.containers.length-1;e>=0;e--){if(a.ui.contains(this.currentItem[0],this.containers[e].element[0]))continue;if(this._intersectsWith(this.containers[e].containerCache)){if(c&&a.ui.contains(this.containers[e].element[0],c.element[0]))continue;c=this.containers[e],d=e}else this.containers[e].containerCache.over&&(this.containers[e]._trigger("out",b,this._uiHash(this)),this.containers[e].containerCache.over=0)}if(!c)return;if(this.containers.length===1)this.containers[d]._trigger("over",b,this._uiHash(this)),this.containers[d].containerCache.over=1;else if(this.currentContainer!=this.containers[d]){var f=1e4,g=null,h=this.positionAbs[this.containers[d].floating?"left":"top"];for(var i=this.items.length-1;i>=0;i--){if(!a.ui.contains(this.containers[d].element[0],this.items[i].item[0]))continue;var j=this.containers[d].floating?this.items[i].item.offset().left:this.items[i].item.offset().top;Math.abs(j-h)<f&&(f=Math.abs(j-h),g=this.items[i],this.direction=j-h>0?"down":"up")}if(!g&&!this.options.dropOnEmpty)return;this.currentContainer=this.containers[d],g?this._rearrange(b,g,null,!0):this._rearrange(b,null,this.containers[d].element,!0),this._trigger("change",b,this._uiHash()),this.containers[d]._trigger("change",b,this._uiHash(this)),this.options.placeholder.update(this.currentContainer,this.placeholder),this.containers[d]._trigger("over",b,this._uiHash(this)),this.containers[d].containerCache.over=1}},_createHelper:function(b){var c=this.options,d=a.isFunction(c.helper)?a(c.helper.apply(this.element[0],[b,this.currentItem])):c.helper=="clone"?this.currentItem.clone():this.currentItem;return d.parents("body").length||a(c.appendTo!="parent"?c.appendTo:this.currentItem[0].parentNode)[0].appendChild(d[0]),d[0]==this.currentItem[0]&&(this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")}),(d[0].style.width==""||c.forceHelperSize)&&d.width(this.currentItem.width()),(d[0].style.height==""||c.forceHelperSize)&&d.height(this.currentItem.height()),d},_adjustOffsetFromHelper:function(b){typeof b=="string"&&(b=b.split(" ")),a.isArray(b)&&(b={left:+b[0],top:+b[1]||0}),"left"in b&&(this.offset.click.left=b.left+this.margins.left),"right"in b&&(this.offset.click.left=this.helperProportions.width-b.right+this.margins.left),"top"in b&&(this.offset.click.top=b.top+this.margins.top),"bottom"in b&&(this.offset.click.top=this.helperProportions.height-b.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var b=this.offsetParent.offset();this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0])&&(b.left+=this.scrollParent.scrollLeft(),b.top+=this.scrollParent.scrollTop());if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&a.browser.msie)b={top:0,left:0};return{top:b.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:b.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var a=this.currentItem.position();return{top:a.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:a.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css("marginLeft"),10)||0,top:parseInt(this.currentItem.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var b=this.options;b.containment=="parent"&&(b.containment=this.helper[0].parentNode);if(b.containment=="document"||b.containment=="window")this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,a(b.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(a(b.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(b.containment)){var c=a(b.containment)[0],d=a(b.containment).offset(),e=a(c).css("overflow")!="hidden";this.containment=[d.left+(parseInt(a(c).css("borderLeftWidth"),10)||0)+(parseInt(a(c).css("paddingLeft"),10)||0)-this.margins.left,d.top+(parseInt(a(c).css("borderTopWidth"),10)||0)+(parseInt(a(c).css("paddingTop"),10)||0)-this.margins.top,d.left+(e?Math.max(c.scrollWidth,c.offsetWidth):c.offsetWidth)-(parseInt(a(c).css("borderLeftWidth"),10)||0)-(parseInt(a(c).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,d.top+(e?Math.max(c.scrollHeight,c.offsetHeight):c.offsetHeight)-(parseInt(a(c).css("borderTopWidth"),10)||0)-(parseInt(a(c).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top]}},_convertPositionTo:function(b,c){c||(c=this.position);var d=b=="absolute"?1:-1,e=this.options,f=this.cssPosition=="absolute"&&(this.scrollParent[0]==document||!a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,g=/(html|body)/i.test(f[0].tagName);return{top:c.top+this.offset.relative.top*d+this.offset.parent.top*d-(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():g?0:f.scrollTop())*d),left:c.left+this.offset.relative.left*d+this.offset.parent.left*d-(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():g?0:f.scrollLeft())*d)}},_generatePosition:function(b){var c=this.options,d=this.cssPosition=="absolute"&&(this.scrollParent[0]==document||!a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,e=/(html|body)/i.test(d[0].tagName);this.cssPosition=="relative"&&(this.scrollParent[0]==document||this.scrollParent[0]==this.offsetParent[0])&&(this.offset.relative=this._getRelativeOffset());var f=b.pageX,g=b.pageY;if(this.originalPosition){this.containment&&(b.pageX-this.offset.click.left<this.containment[0]&&(f=this.containment[0]+this.offset.click.left),b.pageY-this.offset.click.top<this.containment[1]&&(g=this.containment[1]+this.offset.click.top),b.pageX-this.offset.click.left>this.containment[2]&&(f=this.containment[2]+this.offset.click.left),b.pageY-this.offset.click.top>this.containment[3]&&(g=this.containment[3]+this.offset.click.top));if(c.grid){var h=this.originalPageY+Math.round((g-this.originalPageY)/c.grid[1])*c.grid[1];g=this.containment?h-this.offset.click.top<this.containment[1]||h-this.offset.click.top>this.containment[3]?h-this.offset.click.top<this.containment[1]?h+c.grid[1]:h-c.grid[1]:h:h;var i=this.originalPageX+Math.round((f-this.originalPageX)/c.grid[0])*c.grid[0];f=this.containment?i-this.offset.click.left<this.containment[0]||i-this.offset.click.left>this.containment[2]?i-this.offset.click.left<this.containment[0]?i+c.grid[0]:i-c.grid[0]:i:i}}return{top:g-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+(a.browser.safari&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollTop():e?0:d.scrollTop()),left:f-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+(a.browser.safari&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():e?0:d.scrollLeft())}},_rearrange:function(a,b,c,d){c?c[0].appendChild(this.placeholder[0]):b.item[0].parentNode.insertBefore(this.placeholder[0],this.direction=="down"?b.item[0]:b.item[0].nextSibling),this.counter=this.counter?++this.counter:1;var e=this,f=this.counter;window.setTimeout(function(){f==e.counter&&e.refreshPositions(!d)},0)},_clear:function(b,c){this.reverting=!1;var d=[],e=this;!this._noFinalSort&&this.currentItem.parent().length&&this.placeholder.before(this.currentItem),this._noFinalSort=null;if(this.helper[0]==this.currentItem[0]){for(var f in this._storedCSS)if(this._storedCSS[f]=="auto"||this._storedCSS[f]=="static")this._storedCSS[f]="";this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper")}else this.currentItem.show();this.fromOutside&&!c&&d.push(function(a){this._trigger("receive",a,this._uiHash(this.fromOutside))}),(this.fromOutside||this.domPosition.prev!=this.currentItem.prev().not(".ui-sortable-helper")[0]||this.domPosition.parent!=this.currentItem.parent()[0])&&!c&&d.push(function(a){this._trigger("update",a,this._uiHash())});if(!a.ui.contains(this.element[0],this.currentItem[0])){c||d.push(function(a){this._trigger("remove",a,this._uiHash())});for(var f=this.containers.length-1;f>=0;f--)a.ui.contains(this.containers[f].element[0],this.currentItem[0])&&!c&&(d.push(function(a){return function(b){a._trigger("receive",b,this._uiHash(this))}}.call(this,this.containers[f])),d.push(function(a){return function(b){a._trigger("update",b,this._uiHash(this))}}.call(this,this.containers[f])))}for(var f=this.containers.length-1;f>=0;f--)c||d.push(function(a){return function(b){a._trigger("deactivate",b,this._uiHash(this))}}.call(this,this.containers[f])),this.containers[f].containerCache.over&&(d.push(function(a){return function(b){a._trigger("out",b,this._uiHash(this))}}.call(this,this.containers[f])),this.containers[f].containerCache.over=0);this._storedCursor&&a("body").css("cursor",this._storedCursor),this._storedOpacity&&this.helper.css("opacity",this._storedOpacity),this._storedZIndex&&this.helper.css("zIndex",this._storedZIndex=="auto"?"":this._storedZIndex),this.dragging=!1;if(this.cancelHelperRemoval){if(!c){this._trigger("beforeStop",b,this._uiHash());for(var f=0;f<d.length;f++)d[f].call(this,b);this._trigger("stop",b,this._uiHash())}return this.fromOutside=!1,!1}c||this._trigger("beforeStop",b,this._uiHash()),this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.helper[0]!=this.currentItem[0]&&this.helper.remove(),this.helper=null;if(!c){for(var f=0;f<d.length;f++)d[f].call(this,b);this._trigger("stop",b,this._uiHash())}return this.fromOutside=!1,!0},_trigger:function(){a.Widget.prototype._trigger.apply(this,arguments)===!1&&this.cancel()},_uiHash:function(b){var c=b||this;return{helper:c.helper,placeholder:c.placeholder||a([]),position:c.position,originalPosition:c.originalPosition,offset:c.positionAbs,item:c.currentItem,sender:b?b.element:null}}}),a.extend(a.ui.sortable,{version:"1.8.23"})}(jQuery),jQuery.effects||function(a,b){function c(b){var c;return b&&b.constructor==Array&&b.length==3?b:(c=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(b))?[parseInt(c[1],10),parseInt(c[2],10),parseInt(c[3],10)]:(c=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(b))?[parseFloat(c[1])*2.55,parseFloat(c[2])*2.55,parseFloat(c[3])*2.55]:(c=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(b))?[parseInt(c[1],16),parseInt(c[2],16),parseInt(c[3],16)]:(c=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(b))?[parseInt(c[1]+c[1],16),parseInt(c[2]+c[2],16),parseInt(c[3]+c[3],16)]:(c=/rgba\(0, 0, 0, 0\)/.exec(b))?e.transparent:e[a.trim(b).toLowerCase()]}function d(b,d){var e;do{e=(a.curCSS||a.css)(b,d);if(e!=""&&e!="transparent"||a.nodeName(b,"body"))break;d="backgroundColor"}while(b=b.parentNode);return c(e)}function h(){var a=document.defaultView?document.defaultView.getComputedStyle(this,null):this.currentStyle,b={},c,d;if(a&&a.length&&a[0]&&a[a[0]]){var e=a.length;while(e--)c=a[e],typeof a[c]=="string"&&(d=c.replace(/\-(\w)/g,function(a,b){return b.toUpperCase()}),b[d]=a[c])}else for(c in a)typeof a[c]=="string"&&(b[c]=a[c]);return b}function i(b){var c,d;for(c in b)d=b[c],(d==null||a.isFunction(d)||c in g||/scrollbar/.test(c)||!/color/i.test(c)&&isNaN(parseFloat(d)))&&delete b[c];return b}function j(a,b){var c={_:0},d;for(d in b)a[d]!=b[d]&&(c[d]=b[d]);return c}function k(b,c,d,e){typeof b=="object"&&(e=c,d=null,c=b,b=c.effect),a.isFunction(c)&&(e=c,d=null,c={});if(typeof c=="number"||a.fx.speeds[c])e=d,d=c,c={};return a.isFunction(d)&&(e=d,d=null),c=c||{},d=d||c.duration,d=a.fx.off?0:typeof d=="number"?d:d in a.fx.speeds?a.fx.speeds[d]:a.fx.speeds._default,e=e||c.complete,[b,c,d,e]}function l(b){return!b||typeof b=="number"||a.fx.speeds[b]?!0:typeof b=="string"&&!a.effects[b]?!0:!1}a.effects={},a.each(["backgroundColor","borderBottomColor","borderLeftColor","borderRightColor","borderTopColor","borderColor","color","outlineColor"],function(b,e){a.fx.step[e]=function(a){a.colorInit||(a.start=d(a.elem,e),a.end=c(a.end),a.colorInit=!0),a.elem.style[e]="rgb("+Math.max(Math.min(parseInt(a.pos*(a.end[0]-a.start[0])+a.start[0],10),255),0)+","+Math.max(Math.min(parseInt(a.pos*(a.end[1]-a.start[1])+a.start[1],10),255),0)+","+Math.max(Math.min(parseInt(a.pos*(a.end[2]-a.start[2])+a.start[2],10),255),0)+")"}});var e={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0],transparent:[255,255,255]},f=["add","remove","toggle"],g={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};a.effects.animateClass=function(b,c,d,e){return a.isFunction(d)&&(e=d,d=null),this.queue(function(){var g=a(this),k=g.attr("style")||" ",l=i(h.call(this)),m,n=g.attr("class")||"";a.each(f,function(a,c){b[c]&&g[c+"Class"](b[c])}),m=i(h.call(this)),g.attr("class",n),g.animate(j(l,m),{queue:!1,duration:c,easing:d,complete:function(){a.each(f,function(a,c){b[c]&&g[c+"Class"](b[c])}),typeof g.attr("style")=="object"?(g.attr("style").cssText="",g.attr("style").cssText=k):g.attr("style",k),e&&e.apply(this,arguments),a.dequeue(this)}})})},a.fn.extend({_addClass:a.fn.addClass,addClass:function(b,c,d,e){return c?a.effects.animateClass.apply(this,[{add:b},c,d,e]):this._addClass(b)},_removeClass:a.fn.removeClass,removeClass:function(b,c,d,e){return c?a.effects.animateClass.apply(this,[{remove:b},c,d,e]):this._removeClass(b)},_toggleClass:a.fn.toggleClass,toggleClass:function(c,d,e,f,g){return typeof d=="boolean"||d===b?e?a.effects.animateClass.apply(this,[d?{add:c}:{remove:c},e,f,g]):this._toggleClass(c,d):a.effects.animateClass.apply(this,[{toggle:c},d,e,f])},switchClass:function(b,c,d,e,f){return a.effects.animateClass.apply(this,[{add:c,remove:b},d,e,f])}}),a.extend(a.effects,{version:"1.8.23",save:function(a,b){for(var c=0;c<b.length;c++)b[c]!==null&&a.data("ec.storage."+b[c],a[0].style[b[c]])},restore:function(a,b){for(var c=0;c<b.length;c++)b[c]!==null&&a.css(b[c],a.data("ec.storage."+b[c]))},setMode:function(a,b){return b=="toggle"&&(b=a.is(":hidden")?"show":"hide"),b},getBaseline:function(a,b){var c,d;switch(a[0]){case"top":c=0;break;case"middle":c=.5;break;case"bottom":c=1;break;default:c=a[0]/b.height}switch(a[1]){case"left":d=0;break;case"center":d=.5;break;case"right":d=1;break;default:d=a[1]/b.width}return{x:d,y:c}},createWrapper:function(b){if(b.parent().is(".ui-effects-wrapper"))return b.parent();var c={width:b.outerWidth(!0),height:b.outerHeight(!0),"float":b.css("float")},d=a("<div></div>").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),e=document.activeElement;try{e.id}catch(f){e=document.body}return b.wrap(d),(b[0]===e||a.contains(b[0],e))&&a(e).focus(),d=b.parent(),b.css("position")=="static"?(d.css({position:"relative"}),b.css({position:"relative"})):(a.extend(c,{position:b.css("position"),zIndex:b.css("z-index")}),a.each(["top","left","bottom","right"],function(a,d){c[d]=b.css(d),isNaN(parseInt(c[d],10))&&(c[d]="auto")}),b.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),d.css(c).show()},removeWrapper:function(b){var c,d=document.activeElement;return b.parent().is(".ui-effects-wrapper")?(c=b.parent().replaceWith(b),(b[0]===d||a.contains(b[0],d))&&a(d).focus(),c):b},setTransition:function(b,c,d,e){return e=e||{},a.each(c,function(a,c){var f=b.cssUnit(c);f[0]>0&&(e[c]=f[0]*d+f[1])}),e}}),a.fn.extend({effect:function(b,c,d,e){var f=k.apply(this,arguments),g={options:f[1],duration:f[2],callback:f[3]},h=g.options.mode,i=a.effects[b];return a.fx.off||!i?h?this[h](g.duration,g.callback):this.each(function(){g.callback&&g.callback.call(this)}):i.call(this,g)},_show:a.fn.show,show:function(a){if(l(a))return this._show.apply(this,arguments);var b=k.apply(this,arguments);return b[1].mode="show",this.effect.apply(this,b)},_hide:a.fn.hide,hide:function(a){if(l(a))return this._hide.apply(this,arguments);var b=k.apply(this,arguments);return b[1].mode="hide",this.effect.apply(this,b)},__toggle:a.fn.toggle,toggle:function(b){if(l(b)||typeof b=="boolean"||a.isFunction(b))return this.__toggle.apply(this,arguments);var c=k.apply(this,arguments);return c[1].mode="toggle",this.effect.apply(this,c)},cssUnit:function(b){var c=this.css(b),d=[];return a.each(["em","px","%","pt"],function(a,b){c.indexOf(b)>0&&(d=[parseFloat(c),b])}),d}});var m={};a.each(["Quad","Cubic","Quart","Quint","Expo"],function(a,b){m[b]=function(b){return Math.pow(b,a+2)}}),a.extend(m,{Sine:function(a){return 1-Math.cos(a*Math.PI/2)},Circ:function(a){return 1-Math.sqrt(1-a*a)},Elastic:function(a){return a===0||a===1?a:-Math.pow(2,8*(a-1))*Math.sin(((a-1)*80-7.5)*Math.PI/15)},Back:function(a){return a*a*(3*a-2)},Bounce:function(a){var b,c=4;while(a<((b=Math.pow(2,--c))-1)/11);return 1/Math.pow(4,3-c)-7.5625*Math.pow((b*3-2)/22-a,2)}}),a.each(m,function(b,c){a.easing["easeIn"+b]=c,a.easing["easeOut"+b]=function(a){return 1-c(1-a)},a.easing["easeInOut"+b]=function(a){return a<.5?c(a*2)/2:c(a*-2+2)/-2+1}})}(jQuery),function(a,b){a.effects.blind=function(b){return this.queue(function(){var c=a(this),d=["position","top","bottom","left","right"],e=a.effects.setMode(c,b.options.mode||"hide"),f=b.options.direction||"vertical";a.effects.save(c,d),c.show();var g=a.effects.createWrapper(c).css({overflow:"hidden"}),h=f=="vertical"?"height":"width",i=f=="vertical"?g.height():g.width();e=="show"&&g.css(h,0);var j={};j[h]=e=="show"?i:0,g.animate(j,b.duration,b.options.easing,function(){e=="hide"&&c.hide(),a.effects.restore(c,d),a.effects.removeWrapper(c),b.callback&&b.callback.apply(c[0],arguments),c.dequeue()})})}}(jQuery),function(a,b){a.effects.bounce=function(b){return this.queue(function(){var c=a(this),d=["position","top","bottom","left","right"],e=a.effects.setMode(c,b.options.mode||"effect"),f=b.options.direction||"up",g=b.options.distance||20,h=b.options.times||5,i=b.duration||250;/show|hide/.test(e)&&d.push("opacity"),a.effects.save(c,d),c.show(),a.effects.createWrapper(c);var j=f=="up"||f=="down"?"top":"left",k=f=="up"||f=="left"?"pos":"neg",g=b.options.distance||(j=="top"?c.outerHeight(!0)/3:c.outerWidth(!0)/3);e=="show"&&c.css("opacity",0).css(j,k=="pos"?-g:g),e=="hide"&&(g=g/(h*2)),e!="hide"&&h--;if(e=="show"){var l={opacity:1};l[j]=(k=="pos"?"+=":"-=")+g,c.animate(l,i/2,b.options.easing),g=g/2,h--}for(var m=0;m<h;m++){var n={},p={};n[j]=(k=="pos"?"-=":"+=")+g,p[j]=(k=="pos"?"+=":"-=")+g,c.animate(n,i/2,b.options.easing).animate(p,i/2,b.options.easing),g=e=="hide"?g*2:g/2}if(e=="hide"){var l={opacity:0};l[j]=(k=="pos"?"-=":"+=")+g,c.animate(l,i/2,b.options.easing,function(){c.hide(),a.effects.restore(c,d),a.effects.removeWrapper(c),b.callback&&b.callback.apply(this,arguments)})}else{var n={},p={};n[j]=(k=="pos"?"-=":"+=")+g,p[j]=(k=="pos"?"+=":"-=")+g,c.animate(n,i/2,b.options.easing).animate(p,i/2,b.options.easing,function(){a.effects.restore(c,d),a.effects.removeWrapper(c),b.callback&&b.callback.apply(this,arguments)})}c.queue("fx",function(){c.dequeue()}),c.dequeue()})}}(jQuery),function(a,b){a.effects.clip=function(b){return this.queue(function(){var c=a(this),d=["position","top","bottom","left","right","height","width"],e=a.effects.setMode(c,b.options.mode||"hide"),f=b.options.direction||"vertical";a.effects.save(c,d),c.show();var g=a.effects.createWrapper(c).css({overflow:"hidden"}),h=c[0].tagName=="IMG"?g:c,i={size:f=="vertical"?"height":"width",position:f=="vertical"?"top":"left"},j=f=="vertical"?h.height():h.width();e=="show"&&(h.css(i.size,0),h.css(i.position,j/2));var k={};k[i.size]=e=="show"?j:0,k[i.position]=e=="show"?0:j/2,h.animate(k,{queue:!1,duration:b.duration,easing:b.options.easing,complete:function(){e=="hide"&&c.hide(),a.effects.restore(c,d),a.effects.removeWrapper(c),b.callback&&b.callback.apply(c[0],arguments),c.dequeue()}})})}}(jQuery),function(a,b){a.effects.drop=function(b){return this.queue(function(){var c=a(this),d=["position","top","bottom","left","right","opacity"],e=a.effects.setMode(c,b.options.mode||"hide"),f=b.options.direction||"left";a.effects.save(c,d),c.show(),a.effects.createWrapper(c);var g=f=="up"||f=="down"?"top":"left",h=f=="up"||f=="left"?"pos":"neg",i=b.options.distance||(g=="top"?c.outerHeight(!0)/2:c.outerWidth(!0)/2);e=="show"&&c.css("opacity",0).css(g,h=="pos"?-i:i);var j={opacity:e=="show"?1:0};j[g]=(e=="show"?h=="pos"?"+=":"-=":h=="pos"?"-=":"+=")+i,c.animate(j,{queue:!1,duration:b.duration,easing:b.options.easing,complete:function(){e=="hide"&&c.hide(),a.effects.restore(c,d),a.effects.removeWrapper(c),b.callback&&b.callback.apply(this,arguments),c.dequeue()}})})}}(jQuery),function(a,b){a.effects.explode=function(b){return this.queue(function(){var c=b.options.pieces?Math.round(Math.sqrt(b.options.pieces)):3,d=b.options.pieces?Math.round(Math.sqrt(b.options.pieces)):3;b.options.mode=b.options.mode=="toggle"?a(this).is(":visible")?"hide":"show":b.options.mode;var e=a(this).show().css("visibility","hidden"),f=e.offset();f.top-=parseInt(e.css("marginTop"),10)||0,f.left-=parseInt(e.css("marginLeft"),10)||0;var g=e.outerWidth(!0),h=e.outerHeight(!0);for(var i=0;i<c;i++)for(var j=0;j<d;j++)e.clone().appendTo("body").wrap("<div></div>").css({position:"absolute",visibility:"visible",left:-j*(g/d),top:-i*(h/c)}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:g/d,height:h/c,left:f.left+j*(g/d)+(b.options.mode=="show"?(j-Math.floor(d/2))*(g/d):0),top:f.top+i*(h/c)+(b.options.mode=="show"?(i-Math.floor(c/2))*(h/c):0),opacity:b.options.mode=="show"?0:1}).animate({left:f.left+j*(g/d)+(b.options.mode=="show"?0:(j-Math.floor(d/2))*(g/d)),top:f.top+i*(h/c)+(b.options.mode=="show"?0:(i-Math.floor(c/2))*(h/c)),opacity:b.options.mode=="show"?1:0},b.duration||500);setTimeout(function(){b.options.mode=="show"?e.css({visibility:"visible"}):e.css({visibility:"visible"}).hide(),b.callback&&b.callback.apply(e[0]),e.dequeue(),a("div.ui-effects-explode").remove()},b.duration||500)})}}(jQuery),function(a,b){a.effects.fade=function(b){return this.queue(function(){var c=a(this),d=a.effects.setMode(c,b.options.mode||"hide");c.animate({opacity:d},{queue:!1,duration:b.duration,easing:b.options.easing,complete:function(){b.callback&&b.callback.apply(this,arguments),c.dequeue()}})})}}(jQuery),function(a,b){a.effects.fold=function(b){return this.queue(function(){var c=a(this),d=["position","top","bottom","left","right"],e=a.effects.setMode(c,b.options.mode||"hide"),f=b.options.size||15,g=!!b.options.horizFirst,h=b.duration?b.duration/2:a.fx.speeds._default/2;a.effects.save(c,d),c.show();var i=a.effects.createWrapper(c).css({overflow:"hidden"}),j=e=="show"!=g,k=j?["width","height"]:["height","width"],l=j?[i.width(),i.height()]:[i.height(),i.width()],m=/([0-9]+)%/.exec(f);m&&(f=parseInt(m[1],10)/100*l[e=="hide"?0:1]),e=="show"&&i.css(g?{height:0,width:f}:{height:f,width:0});var n={},p={};n[k[0]]=e=="show"?l[0]:f,p[k[1]]=e=="show"?l[1]:0,i.animate(n,h,b.options.easing).animate(p,h,b.options.easing,function(){e=="hide"&&c.hide(),a.effects.restore(c,d),a.effects.removeWrapper(c),b.callback&&b.callback.apply(c[0],arguments),c.dequeue()})})}}(jQuery),function(a,b){a.effects.highlight=function(b){return this.queue(function(){var c=a(this),d=["backgroundImage","backgroundColor","opacity"],e=a.effects.setMode(c,b.options.mode||"show"),f={backgroundColor:c.css("backgroundColor")};e=="hide"&&(f.opacity=0),a.effects.save(c,d),c.show().css({backgroundImage:"none",backgroundColor:b.options.color||"#ffff99"}).animate(f,{queue:!1,duration:b.duration,easing:b.options.easing,complete:function(){e=="hide"&&c.hide(),a.effects.restore(c,d),e=="show"&&!a.support.opacity&&this.style.removeAttribute("filter"),b.callback&&b.callback.apply(this,arguments),c.dequeue()}})})}}(jQuery),function(a,b){a.effects.pulsate=function(b){return this.queue(function(){var c=a(this),d=a.effects.setMode(c,b.options.mode||"show"),e=(b.options.times||5)*2-1,f=b.duration?b.duration/2:a.fx.speeds._default/2,g=c.is(":visible"),h=0;g||(c.css("opacity",0).show(),h=1),(d=="hide"&&g||d=="show"&&!g)&&e--;for(var i=0;i<e;i++)c.animate({opacity:h},f,b.options.easing),h=(h+1)%2;c.animate({opacity:h},f,b.options.easing,function(){h==0&&c.hide(),b.callback&&b.callback.apply(this,arguments)}),c.queue("fx",function(){c.dequeue()}).dequeue()})}}(jQuery),function(a,b){a.effects.puff=function(b){return this.queue(function(){var c=a(this),d=a.effects.setMode(c,b.options.mode||"hide"),e=parseInt(b.options.percent,10)||150,f=e/100,g={height:c.height(),width:c.width()};a.extend(b.options,{fade:!0,mode:d,percent:d=="hide"?e:100,from:d=="hide"?g:{height:g.height*f,width:g.width*f}}),c.effect("scale",b.options,b.duration,b.callback),c.dequeue()})},a.effects.scale=function(b){return this.queue(function(){var c=a(this),d=a.extend(!0,{},b.options),e=a.effects.setMode(c,b.options.mode||"effect"),f=parseInt(b.options.percent,10)||(parseInt(b.options.percent,10)==0?0:e=="hide"?0:100),g=b.options.direction||"both",h=b.options.origin;e!="effect"&&(d.origin=h||["middle","center"],d.restore=!0);var i={height:c.height(),width:c.width()};c.from=b.options.from||(e=="show"?{height:0,width:0}:i);var j={y:g!="horizontal"?f/100:1,x:g!="vertical"?f/100:1};c.to={height:i.height*j.y,width:i.width*j.x},b.options.fade&&(e=="show"&&(c.from.opacity=0,c.to.opacity=1),e=="hide"&&(c.from.opacity=1,c.to.opacity=0)),d.from=c.from,d.to=c.to,d.mode=e,c.effect("size",d,b.duration,b.callback),c.dequeue()})},a.effects.size=function(b){return this.queue(function(){var c=a(this),d=["position","top","bottom","left","right","width","height","overflow","opacity"],e=["position","top","bottom","left","right","overflow","opacity"],f=["width","height","overflow"],g=["fontSize"],h=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],i=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],j=a.effects.setMode(c,b.options.mode||"effect"),k=b.options.restore||!1,l=b.options.scale||"both",m=b.options.origin,n={height:c.height(),width:c.width()};c.from=b.options.from||n,c.to=b.options.to||n;if(m){var p=a.effects.getBaseline(m,n);c.from.top=(n.height-c.from.height)*p.y,c.from.left=(n.width-c.from.width)*p.x,c.to.top=(n.height-c.to.height)*p.y,c.to.left=(n.width-c.to.width)*p.x}var q={from:{y:c.from.height/n.height,x:c.from.width/n.width},to:{y:c.to.height/n.height,x:c.to.width/n.width}};if(l=="box"||l=="both")q.from.y!=q.to.y&&(d=d.concat(h),c.from=a.effects.setTransition(c,h,q.from.y,c.from),c.to=a.effects.setTransition(c,h,q.to.y,c.to)),q.from.x!=q.to.x&&(d=d.concat(i),c.from=a.effects.setTransition(c,i,q.from.x,c.from),c.to=a.effects.setTransition(c,i,q.to.x,c.to));(l=="content"||l=="both")&&q.from.y!=q.to.y&&(d=d.concat(g),c.from=a.effects.setTransition(c,g,q.from.y,c.from),c.to=a.effects.setTransition(c,g,q.to.y,c.to)),a.effects.save(c,k?d:e),c.show(),a.effects.createWrapper(c),c.css("overflow","hidden").css(c.from);if(l=="content"||l=="both")h=h.concat(["marginTop","marginBottom"]).concat(g),i=i.concat(["marginLeft","marginRight"]),f=d.concat(h).concat(i),c.find("*[width]").each(function(){var c=a(this);k&&a.effects.save(c,f);var d={height:c.height(),width:c.width()};c.from={height:d.height*q.from.y,width:d.width*q.from.x},c.to={height:d.height*q.to.y,width:d.width*q.to.x},q.from.y!=q.to.y&&(c.from=a.effects.setTransition(c,h,q.from.y,c.from),c.to=a.effects.setTransition(c,h,q.to.y,c.to)),q.from.x!=q.to.x&&(c.from=a.effects.setTransition(c,i,q.from.x,c.from),c.to=a.effects.setTransition(c,i,q.to.x,c.to)),c.css(c.from),c.animate(c.to,b.duration,b.options.easing,function(){k&&a.effects.restore(c,f)})});c.animate(c.to,{queue:!1,duration:b.duration,easing:b.options.easing,complete:function(){c.to.opacity===0&&c.css("opacity",c.from.opacity),j=="hide"&&c.hide(),a.effects.restore(c,k?d:e),a.effects.removeWrapper(c),b.callback&&b.callback.apply(this,arguments),c.dequeue()}})})}}(jQuery),function(a,b){a.effects.shake=function(b){return this.queue(function(){var c=a(this),d=["position","top","bottom","left","right"],e=a.effects.setMode(c,b.options.mode||"effect"),f=b.options.direction||"left",g=b.options.distance||20,h=b.options.times||3,i=b.duration||b.options.duration||140;a.effects.save(c,d),c.show(),a.effects.createWrapper(c);var j=f=="up"||f=="down"?"top":"left",k=f=="up"||f=="left"?"pos":"neg",l={},m={},n={};l[j]=(k=="pos"?"-=":"+=")+g,m[j]=(k=="pos"?"+=":"-=")+g*2,n[j]=(k=="pos"?"-=":"+=")+g*2,c.animate(l,i,b.options.easing);for(var p=1;p<h;p++)c.animate(m,i,b.options.easing).animate(n,i,b.options.easing);c.animate(m,i,b.options.easing).animate(l,i/2,b.options.easing,function(){a.effects.restore(c,d),a.effects.removeWrapper(c),b.callback&&b.callback.apply(this,arguments)}),c.queue("fx",function(){c.dequeue()}),c.dequeue()})}}(jQuery),function(a,b){a.effects.slide=function(b){return this.queue(function(){var c=a(this),d=["position","top","bottom","left","right"],e=a.effects.setMode(c,b.options.mode||"show"),f=b.options.direction||"left";a.effects.save(c,d),c.show(),a.effects.createWrapper(c).css({overflow:"hidden"});var g=f=="up"||f=="down"?"top":"left",h=f=="up"||f=="left"?"pos":"neg",i=b.options.distance||(g=="top"?c.outerHeight(!0):c.outerWidth(!0));e=="show"&&c.css(g,h=="pos"?isNaN(i)?"-"+i:-i:i);var j={};j[g]=(e=="show"?h=="pos"?"+=":"-=":h=="pos"?"-=":"+=")+i,c.animate(j,{queue:!1,duration:b.duration,easing:b.options.easing,complete:function(){e=="hide"&&c.hide(),a.effects.restore(c,d),a.effects.removeWrapper(c),b.callback&&b.callback.apply(this,arguments),c.dequeue()}})})}}(jQuery),function(a,b){a.effects.transfer=function(b){return this.queue(function(){var c=a(this),d=a(b.options.to),e=d.offset(),f={top:e.top,left:e.left,height:d.innerHeight(),width:d.innerWidth()},g=c.offset(),h=a('<div class="ui-effects-transfer"></div>').appendTo(document.body).addClass(b.options.className).css({top:g.top,left:g.left,height:c.innerHeight(),width:c.innerWidth(),position:"absolute"}).animate(f,b.duration,b.options.easing,function(){h.remove(),b.callback&&b.callback.apply(c[0],arguments),c.dequeue()})})}}(jQuery),function(a,b){a.widget("ui.accordion",{options:{active:0,animated:"slide",autoHeight:!0,clearStyle:!1,collapsible:!1,event:"click",fillSpace:!1,header:"> li > :first-child,> :not(li):even",icons:{header:"ui-icon-triangle-1-e",headerSelected:"ui-icon-triangle-1-s"},navigation:!1,navigationFilter:function(){return this.href.toLowerCase()===location.href.toLowerCase()}},_create:function(){var b=this,c=b.options;b.running=0,b.element.addClass("ui-accordion ui-widget ui-helper-reset").children("li").addClass("ui-accordion-li-fix"),b.headers=b.element.find(c.header).addClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all").bind("mouseenter.accordion",function(){if(c.disabled)return;a(this).addClass("ui-state-hover")}).bind("mouseleave.accordion",function(){if(c.disabled)return;a(this).removeClass("ui-state-hover")}).bind("focus.accordion",function(){if(c.disabled)return;a(this).addClass("ui-state-focus")}).bind("blur.accordion",function(){if(c.disabled)return;a(this).removeClass("ui-state-focus")}),b.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom");if(c.navigation){var d=b.element.find("a").filter(c.navigationFilter).eq(0);if(d.length){var e=d.closest(".ui-accordion-header");e.length?b.active=e:b.active=d.closest(".ui-accordion-content").prev()}}b.active=b._findActive(b.active||c.active).addClass("ui-state-default ui-state-active").toggleClass("ui-corner-all").toggleClass("ui-corner-top"),b.active.next().addClass("ui-accordion-content-active"),b._createIcons(),b.resize(),b.element.attr("role","tablist"),b.headers.attr("role","tab").bind("keydown.accordion",function(a){return b._keydown(a)}).next().attr("role","tabpanel"),b.headers.not(b.active||"").attr({"aria-expanded":"false","aria-selected":"false",tabIndex:-1}).next().hide(),b.active.length?b.active.attr({"aria-expanded":"true","aria-selected":"true",tabIndex:0}):b.headers.eq(0).attr("tabIndex",0),a.browser.safari||b.headers.find("a").attr("tabIndex",-1),c.event&&b.headers.bind(c.event.split(" ").join(".accordion ")+".accordion",function(a){b._clickHandler.call(b,a,this),a.preventDefault()})},_createIcons:function(){var b=this.options;b.icons&&(a("<span></span>").addClass("ui-icon "+b.icons.header).prependTo(this.headers),this.active.children(".ui-icon").toggleClass(b.icons.header).toggleClass(b.icons.headerSelected),this.element.addClass("ui-accordion-icons"))},_destroyIcons:function(){this.headers.children(".ui-icon").remove(),this.element.removeClass("ui-accordion-icons")},destroy:function(){var b=this.options;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role"),this.headers.unbind(".accordion").removeClass("ui-accordion-header ui-accordion-disabled ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top").removeAttr("role").removeAttr("aria-expanded").removeAttr("aria-selected").removeAttr("tabIndex"),this.headers.find("a").removeAttr("tabIndex"),this._destroyIcons();var c=this.headers.next().css("display","").removeAttr("role").removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-accordion-disabled ui-state-disabled");return(b.autoHeight||b.fillHeight)&&c.css("height",""),a.Widget.prototype.destroy.call(this)},_setOption:function(b,c){a.Widget.prototype._setOption.apply(this,arguments),b=="active"&&this.activate(c),b=="icons"&&(this._destroyIcons(),c&&this._createIcons()),b=="disabled"&&this.headers.add(this.headers.next())[c?"addClass":"removeClass"]("ui-accordion-disabled ui-state-disabled")},_keydown:function(b){if(this.options.disabled||b.altKey||b.ctrlKey)return;var c=a.ui.keyCode,d=this.headers.length,e=this.headers.index(b.target),f=!1;switch(b.keyCode){case c.RIGHT:case c.DOWN:f=this.headers[(e+1)%d];break;case c.LEFT:case c.UP:f=this.headers[(e-1+d)%d];break;case c.SPACE:case c.ENTER:this._clickHandler({target:b.target},b.target),b.preventDefault()}return f?(a(b.target).attr("tabIndex",-1),a(f).attr("tabIndex",0),f.focus(),!1):!0},resize:function(){var b=this.options,c;if(b.fillSpace){if(a.browser.msie){var d=this.element.parent().css("overflow");this.element.parent().css("overflow","hidden")}c=this.element.parent().height(),a.browser.msie&&this.element.parent().css("overflow",d),this.headers.each(function(){c-=a(this).outerHeight(!0)}),this.headers.next().each(function(){a(this).height(Math.max(0,c-a(this).innerHeight()+a(this).height()))}).css("overflow","auto")}else b.autoHeight&&(c=0,this.headers.next().each(function(){c=Math.max(c,a(this).height("").height())}).height(c));return this},activate:function(a){this.options.active=a;var b=this._findActive(a)[0];return this._clickHandler({target:b},b),this},_findActive:function(b){return b?typeof b=="number"?this.headers.filter(":eq("+b+")"):this.headers.not(this.headers.not(b)):b===!1?a([]):this.headers.filter(":eq(0)")},_clickHandler:function(b,c){var d=this.options;if(d.disabled)return;if(!b.target){if(!d.collapsible)return;this.active.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").children(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header),this.active.next().addClass("ui-accordion-content-active");var e=this.active.next(),f={options:d,newHeader:a([]),oldHeader:d.active,newContent:a([]),oldContent:e},g=this.active=a([]);this._toggle(g,e,f);return}var h=a(b.currentTarget||c),i=h[0]===this.active[0];d.active=d.collapsible&&i?!1:this.headers.index(h);if(this.running||!d.collapsible&&i)return;var j=this.active,g=h.next(),e=this.active.next(),f={options:d,newHeader:i&&d.collapsible?a([]):h,oldHeader:this.active,newContent:i&&d.collapsible?a([]):g,oldContent:e},k=this.headers.index(this.active[0])>this.headers.index(h[0]);this.active=i?a([]):h,this._toggle(g,e,f,i,k),j.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").children(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header),i||(h.removeClass("ui-state-default ui-corner-all").addClass("ui-state-active ui-corner-top").children(".ui-icon").removeClass(d.icons.header).addClass(d.icons.headerSelected),h.next().addClass("ui-accordion-content-active"));return},_toggle:function(b,c,d,e,f){var g=this,h=g.options;g.toShow=b,g.toHide=c,g.data=d;var i=function(){if(!g)return;return g._completed.apply(g,arguments)};g._trigger("changestart",null,g.data),g.running=c.size()===0?b.size():c.size();if(h.animated){var j={};h.collapsible&&e?j={toShow:a([]),toHide:c,complete:i,down:f,autoHeight:h.autoHeight||h.fillSpace}:j={toShow:b,toHide:c,complete:i,down:f,autoHeight:h.autoHeight||h.fillSpace},h.proxied||(h.proxied=h.animated),h.proxiedDuration||(h.proxiedDuration=h.duration),h.animated=a.isFunction(h.proxied)?h.proxied(j):h.proxied,h.duration=a.isFunction(h.proxiedDuration)?h.proxiedDuration(j):h.proxiedDuration;var k=a.ui.accordion.animations,l=h.duration,m=h.animated;m&&!k[m]&&!a.easing[m]&&(m="slide"),k[m]||(k[m]=function(a){this.slide(a,{easing:m,duration:l||700})}),k[m](j)}else h.collapsible&&e?b.toggle():(c.hide(),b.show()),i(!0);c.prev().attr({"aria-expanded":"false","aria-selected":"false",tabIndex:-1}).blur(),b.prev().attr({"aria-expanded":"true","aria-selected":"true",tabIndex:0}).focus()},_completed:function(a){this.running=a?0:--this.running;if(this.running)return;this.options.clearStyle&&this.toShow.add(this.toHide).css({height:"",overflow:""}),this.toHide.removeClass("ui-accordion-content-active"),this.toHide.length&&(this.toHide.parent()[0].className=this.toHide.parent()[0].className),this._trigger("change",null,this.data)}}),a.extend(a.ui.accordion,{version:"1.8.23",animations:{slide:function(b,c){b=a.extend({easing:"swing",duration:300},b,c);if(!b.toHide.size()){b.toShow.animate({height:"show",paddingTop:"show",paddingBottom:"show"},b);return}if(!b.toShow.size()){b.toHide.animate({height:"hide",paddingTop:"hide",paddingBottom:"hide"},b);return}var d=b.toShow.css("overflow"),e=0,f={},g={},h=["height","paddingTop","paddingBottom"],i,j=b.toShow;i=j[0].style.width,j.width(j.parent().width()-parseFloat(j.css("paddingLeft"))-parseFloat(j.css("paddingRight"))-(parseFloat(j.css("borderLeftWidth"))||0)-(parseFloat(j.css("borderRightWidth"))||0)),a.each(h,function(c,d){g[d]="hide";var e=(""+a.css(b.toShow[0],d)).match(/^([\d+-.]+)(.*)$/);f[d]={value:e[1],unit:e[2]||"px"}}),b.toShow.css({height:0,overflow:"hidden"}).show(),b.toHide.filter(":hidden").each(b.complete).end().filter(":visible").animate(g,{step:function(a,c){c.prop=="height"&&(e=c.end-c.start===0?0:(c.now-c.start)/(c.end-c.start)),b.toShow[0].style[c.prop]=e*f[c.prop].value+f[c.prop].unit},duration:b.duration,easing:b.easing,complete:function(){b.autoHeight||b.toShow.css("height",""),b.toShow.css({width:i,overflow:d}),b.complete()}})},bounceslide:function(a){this.slide(a,{easing:a.down?"easeOutBounce":"swing",duration:a.down?1e3:200})}}})}(jQuery),function(a,b){var c=0;a.widget("ui.autocomplete",{options:{appendTo:"body",autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null},pending:0,_create:function(){var b=this,c=this.element[0].ownerDocument,d;this.isMultiLine=this.element.is("textarea"),this.element.addClass("ui-autocomplete-input").attr("autocomplete","off").attr({role:"textbox","aria-autocomplete":"list","aria-haspopup":"true"}).bind("keydown.autocomplete",function(c){if(b.options.disabled||b.element.propAttr("readOnly"))return;d=!1;var e=a.ui.keyCode;switch(c.keyCode){case e.PAGE_UP:b._move("previousPage",c);break;case e.PAGE_DOWN:b._move("nextPage",c);break;case e.UP:b._keyEvent("previous",c);break;case e.DOWN:b._keyEvent("next",c);break;case e.ENTER:case e.NUMPAD_ENTER:b.menu.active&&(d=!0,c.preventDefault());case e.TAB:if(!b.menu.active)return;b.menu.select(c);break;case e.ESCAPE:b.element.val(b.term),b.close(c);break;default:clearTimeout(b.searching),b.searching=setTimeout(function(){b.term!=b.element.val()&&(b.selectedItem=null,b.search(null,c))},b.options.delay)}}).bind("keypress.autocomplete",function(a){d&&(d=!1,a.preventDefault())}).bind("focus.autocomplete",function(){if(b.options.disabled)return;b.selectedItem=null,b.previous=b.element.val()}).bind("blur.autocomplete",function(a){if(b.options.disabled)return;clearTimeout(b.searching),b.closing=setTimeout(function(){b.close(a),b._change(a)},150)}),this._initSource(),this.menu=a("<ul></ul>").addClass("ui-autocomplete").appendTo(a(this.options.appendTo||"body",c)[0]).mousedown(function(c){var d=b.menu.element[0];a(c.target).closest(".ui-menu-item").length||setTimeout(function(){a(document).one("mousedown",function(c){c.target!==b.element[0]&&c.target!==d&&!a.ui.contains(d,c.target)&&b.close()})},1),setTimeout(function(){clearTimeout(b.closing)},13)}).menu({focus:function(a,c){var d=c.item.data("item.autocomplete");!1!==b._trigger("focus",a,{item:d})&&/^key/.test(a.originalEvent.type)&&b.element.val(d.value)},selected:function(a,d){var e=d.item.data("item.autocomplete"),f=b.previous;b.element[0]!==c.activeElement&&(b.element.focus(),b.previous=f,setTimeout(function(){b.previous=f,b.selectedItem=e},1)),!1!==b._trigger("select",a,{item:e})&&b.element.val(e.value),b.term=b.element.val(),b.close(a),b.selectedItem=e},blur:function(a,c){b.menu.element.is(":visible")&&b.element.val()!==b.term&&b.element.val(b.term)}}).zIndex(this.element.zIndex()+1).css({top:0,left:0}).hide().data("menu"),a.fn.bgiframe&&this.menu.element.bgiframe(),b.beforeunloadHandler=function(){b.element.removeAttr("autocomplete")},a(window).bind("beforeunload",b.beforeunloadHandler)},destroy:function(){this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete").removeAttr("role").removeAttr("aria-autocomplete").removeAttr("aria-haspopup"),this.menu.element.remove(),a(window).unbind("beforeunload",this.beforeunloadHandler),a.Widget.prototype.destroy.call(this)},_setOption:function(b,c){a.Widget.prototype._setOption.apply(this,arguments),b==="source"&&this._initSource(),b==="appendTo"&&this.menu.element.appendTo(a(c||"body",this.element[0].ownerDocument)[0]),b==="disabled"&&c&&this.xhr&&this.xhr.abort()},_initSource:function(){var b=this,c,d;a.isArray(this.options.source)?(c=this.options.source,this.source=function(b,d){d(a.ui.autocomplete.filter(c,b.term))}):typeof this.options.source=="string"?(d=this.options.source,this.source=function(c,e){b.xhr&&b.xhr.abort(),b.xhr=a.ajax({url:d,data:c,dataType:"json",success:function(a,b){e(a)},error:function(){e([])}})}):this.source=this.options.source},search:function(a,b){a=a!=null?a:this.element.val(),this.term=this.element.val();if(a.length<this.options.minLength)return this.close(b);clearTimeout(this.closing);if(this._trigger("search",b)===!1)return;return this._search(a)},_search:function(a){this.pending++,this.element.addClass("ui-autocomplete-loading"),this.source({term:a},this._response())},_response:function(){var a=this,b=++c;return function(d){b===c&&a.__response(d),a.pending--,a.pending||a.element.removeClass("ui-autocomplete-loading")}},__response:function(a){!this.options.disabled&&a&&a.length?(a=this._normalize(a),this._suggest(a),this._trigger("open")):this.close()},close:function(a){clearTimeout(this.closing),this.menu.element.is(":visible")&&(this.menu.element.hide(),this.menu.deactivate(),this._trigger("close",a))},_change:function(a){this.previous!==this.element.val()&&this._trigger("change",a,{item:this.selectedItem})},_normalize:function(b){return b.length&&b[0].label&&b[0].value?b:a.map(b,function(b){return typeof b=="string"?{label:b,value:b}:a.extend({label:b.label||b.value,value:b.value||b.label},b)})},_suggest:function(b){var c=this.menu.element.empty().zIndex(this.element.zIndex()+1);this._renderMenu(c,b),this.menu.deactivate(),this.menu.refresh(),c.show(),this._resizeMenu(),c.position(a.extend({of:this.element},this.options.position)),this.options.autoFocus&&this.menu.next(new a.Event("mouseover"))},_resizeMenu:function(){var a=this.menu.element;a.outerWidth(Math.max(a.width("").outerWidth()+1,this.element.outerWidth()))},_renderMenu:function(b,c){var d=this;a.each(c,function(a,c){d._renderItem(b,c)})},_renderItem:function(b,c){return a("<li></li>").data("item.autocomplete",c).append(a("<a></a>").text(c.label)).appendTo(b)},_move:function(a,b){if(!this.menu.element.is(":visible")){this.search(null,b);return}if(this.menu.first()&&/^previous/.test(a)||this.menu.last()&&/^next/.test(a)){this.element.val(this.term),this.menu.deactivate();return}this.menu[a](b)},widget:function(){return this.menu.element},_keyEvent:function(a,b){if(!this.isMultiLine||this.menu.element.is(":visible"))this._move(a,b),b.preventDefault()}}),a.extend(a.ui.autocomplete,{escapeRegex:function(a){return a.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&")},filter:function(b,c){var d=new RegExp(a.ui.autocomplete.escapeRegex(c),"i");return a.grep(b,function(a){return d.test(a.label||a.value||a)})}})}(jQuery),function(a){a.widget("ui.menu",{_create:function(){var b=this;this.element.addClass("ui-menu ui-widget ui-widget-content ui-corner-all").attr({role:"listbox","aria-activedescendant":"ui-active-menuitem"}).click(function(c){if(!a(c.target).closest(".ui-menu-item a").length)return;c.preventDefault(),b.select(c)}),this.refresh()},refresh:function(){var b=this,c=this.element.children("li:not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","menuitem");c.children("a").addClass("ui-corner-all").attr("tabindex",-1).mouseenter(function(c){b.activate(c,a(this).parent())}).mouseleave(function(){b.deactivate()})},activate:function(a,b){this.deactivate();if(this.hasScroll()){var c=b.offset().top-this.element.offset().top,d=this.element.scrollTop(),e=this.element.height();c<0?this.element.scrollTop(d+c):c>=e&&this.element.scrollTop(d+c-e+b.height())}this.active=b.eq(0).children("a").addClass("ui-state-hover").attr("id","ui-active-menuitem").end(),this._trigger("focus",a,{item:b})},deactivate:function(){if(!this.active)return;this.active.children("a").removeClass("ui-state-hover").removeAttr("id"),this._trigger("blur"),this.active=null},next:function(a){this.move("next",".ui-menu-item:first",a)},previous:function(a){this.move("prev",".ui-menu-item:last",a)},first:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},last:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},move:function(a,b,c){if(!this.active){this.activate(c,this.element.children(b));return}var d=this.active[a+"All"](".ui-menu-item").eq(0);d.length?this.activate(c,d):this.activate(c,this.element.children(b))},nextPage:function(b){if(this.hasScroll()){if(!this.active||this.last()){this.activate(b,this.element.children(".ui-menu-item:first"));return}var c=this.active.offset().top,d=this.element.height(),e=this.element.children(".ui-menu-item").filter(function(){var b=a(this).offset().top-c-d+a(this).height();return b<10&&b>-10});e.length||(e=this.element.children(".ui-menu-item:last")),this.activate(b,e)}else this.activate(b,this.element.children(".ui-menu-item").filter(!this.active||this.last()?":first":":last"))},previousPage:function(b){if(this.hasScroll()){if(!this.active||this.first()){this.activate(b,this.element.children(".ui-menu-item:last"));return}var c=this.active.offset().top,d=this.element.height(),e=this.element.children(".ui-menu-item").filter(function(){var b=a(this).offset().top-c+d-a(this).height();return b<10&&b>-10});e.length||(e=this.element.children(".ui-menu-item:first")),this.activate(b,e)}else this.activate(b,this.element.children(".ui-menu-item").filter(!this.active||this.first()?":last":":first"))},hasScroll:function(){return this.element.height()<this.element[a.fn.prop?"prop":"attr"]("scrollHeight")},select:function(a){this._trigger("selected",a,{item:this.active})}})}(jQuery),function(a,b){var c,d,e,f,g="ui-button ui-widget ui-state-default ui-corner-all",h="ui-state-hover ui-state-active ",i="ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only",j=function(){var b=a(this).find(":ui-button");setTimeout(function(){b.button("refresh")},1)},k=function(b){var c=b.name,d=b.form,e=a([]);return c&&(d?e=a(d).find("[name='"+c+"']"):e=a("[name='"+c+"']",b.ownerDocument).filter(function(){return!this.form})),e};a.widget("ui.button",{options:{disabled:null,text:!0,label:null,icons:{primary:null,secondary:null}},_create:function(){this.element.closest("form").unbind("reset.button").bind("reset.button",j),typeof this.options.disabled!="boolean"?this.options.disabled=!!this.element.propAttr("disabled"):this.element.propAttr("disabled",this.options.disabled),this._determineButtonType(),this.hasTitle=!!this.buttonElement.attr("title");var b=this,h=this.options,i=this.type==="checkbox"||this.type==="radio",l="ui-state-hover"+(i?"":" ui-state-active"),m="ui-state-focus";h.label===null&&(h.label=this.buttonElement.html()),this.buttonElement.addClass(g).attr("role","button").bind("mouseenter.button",function(){if(h.disabled)return;a(this).addClass("ui-state-hover"),this===c&&a(this).addClass("ui-state-active")}).bind("mouseleave.button",function(){if(h.disabled)return;a(this).removeClass(l)}).bind("click.button",function(a){h.disabled&&(a.preventDefault(),a.stopImmediatePropagation())}),this.element.bind("focus.button",function(){b.buttonElement.addClass(m)}).bind("blur.button",function(){b.buttonElement.removeClass(m)}),i&&(this.element.bind("change.button",function(){if(f)return;b.refresh()}),this.buttonElement.bind("mousedown.button",function(a){if(h.disabled)return;f=!1,d=a.pageX,e=a.pageY}).bind("mouseup.button",function(a){if(h.disabled)return;if(d!==a.pageX||e!==a.pageY)f=!0})),this.type==="checkbox"?this.buttonElement.bind("click.button",function(){if(h.disabled||f)return!1;a(this).toggleClass("ui-state-active"),b.buttonElement.attr("aria-pressed",b.element[0].checked)}):this.type==="radio"?this.buttonElement.bind("click.button",function(){if(h.disabled||f)return!1;a(this).addClass("ui-state-active"),b.buttonElement.attr("aria-pressed","true");var c=b.element[0];k(c).not(c).map(function(){return a(this).button("widget")[0]}).removeClass("ui-state-active").attr("aria-pressed","false")}):(this.buttonElement.bind("mousedown.button",function(){if(h.disabled)return!1;a(this).addClass("ui-state-active"),c=this,a(document).one("mouseup",function(){c=null})}).bind("mouseup.button",function(){if(h.disabled)return!1;a(this).removeClass("ui-state-active")}).bind("keydown.button",function(b){if(h.disabled)return!1;(b.keyCode==a.ui.keyCode.SPACE||b.keyCode==a.ui.keyCode.ENTER)&&a(this).addClass("ui-state-active")}).bind("keyup.button",function(){a(this).removeClass("ui-state-active")}),this.buttonElement.is("a")&&this.buttonElement.keyup(function(b){b.keyCode===a.ui.keyCode.SPACE&&a(this).click()})),this._setOption("disabled",h.disabled),this._resetButton()},_determineButtonType:function(){this.element.is(":checkbox")?this.type="checkbox":this.element.is(":radio")?this.type="radio":this.element.is("input")?this.type="input":this.type="button";if(this.type==="checkbox"||this.type==="radio"){var a=this.element.parents().filter(":last"),b="label[for='"+this.element.attr("id")+"']";this.buttonElement=a.find(b),this.buttonElement.length||(a=a.length?a.siblings():this.element.siblings(),this.buttonElement=a.filter(b),this.buttonElement.length||(this.buttonElement=a.find(b))),this.element.addClass("ui-helper-hidden-accessible");var c=this.element.is(":checked");c&&this.buttonElement.addClass("ui-state-active"),this.buttonElement.attr("aria-pressed",c)}else this.buttonElement=this.element},widget:function(){return this.buttonElement},destroy:function(){this.element.removeClass("ui-helper-hidden-accessible"),this.buttonElement.removeClass(g+" "+h+" "+i).removeAttr("role").removeAttr("aria-pressed").html(this.buttonElement.find(".ui-button-text").html()),this.hasTitle||this.buttonElement.removeAttr("title"),a.Widget.prototype.destroy.call(this)},_setOption:function(b,c){a.Widget.prototype._setOption.apply(this,arguments);if(b==="disabled"){c?this.element.propAttr("disabled",!0):this.element.propAttr("disabled",!1);return}this._resetButton()},refresh:function(){var b=this.element.is(":disabled");b!==this.options.disabled&&this._setOption("disabled",b),this.type==="radio"?k(this.element[0]).each(function(){a(this).is(":checked")?a(this).button("widget").addClass("ui-state-active").attr("aria-pressed","true"):a(this).button("widget").removeClass("ui-state-active").attr("aria-pressed","false")}):this.type==="checkbox"&&(this.element.is(":checked")?this.buttonElement.addClass("ui-state-active").attr("aria-pressed","true"):this.buttonElement.removeClass("ui-state-active").attr("aria-pressed","false"))},_resetButton:function(){if(this.type==="input"){this.options.label&&this.element.val(this.options.label);return}var b=this.buttonElement.removeClass(i),c=a("<span></span>",this.element[0].ownerDocument).addClass("ui-button-text").html(this.options.label).appendTo(b.empty()).text(),d=this.options.icons,e=d.primary&&d.secondary,f=[];d.primary||d.secondary?(this.options.text&&f.push("ui-button-text-icon"+(e?"s":d.primary?"-primary":"-secondary")),d.primary&&b.prepend("<span class='ui-button-icon-primary ui-icon "+d.primary+"'></span>"),d.secondary&&b.append("<span class='ui-button-icon-secondary ui-icon "+d.secondary+"'></span>"),this.options.text||(f.push(e?"ui-button-icons-only":"ui-button-icon-only"),this.hasTitle||b.attr("title",c))):f.push("ui-button-text-only"),b.addClass(f.join(" "))}}),a.widget("ui.buttonset",{options:{items:":button, :submit, :reset, :checkbox, :radio, a, :data(button)"},_create:function(){this.element.addClass("ui-buttonset")},_init:function(){this.refresh()},_setOption:function(b,c){b==="disabled"&&this.buttons.button("option",b,c),a.Widget.prototype._setOption.apply(this,arguments)},refresh:function(){var b=this.element.css("direction")==="rtl";this.buttons=this.element.find(this.options.items).filter(":ui-button").button("refresh").end().not(":ui-button").button().end().map(function(){return a(this).button("widget")[0]}).removeClass("ui-corner-all ui-corner-left ui-corner-right").filter(":first").addClass(b?"ui-corner-right":"ui-corner-left").end().filter(":last").addClass(b?"ui-corner-left":"ui-corner-right").end().end()},destroy:function(){this.element.removeClass("ui-buttonset"),this.buttons.map(function(){return a(this).button("widget")[0]}).removeClass("ui-corner-left ui-corner-right").end().button("destroy"),a.Widget.prototype.destroy.call(this)}})}(jQuery),function($,undefined){function Datepicker(){this.debug=!1,this._curInst=null,this._keyEvent=!1,this._disabledInputs=[],this._datepickerShowing=!1,this._inDialog=!1,this._mainDivId="ui-datepicker-div",this._inlineClass="ui-datepicker-inline",this._appendClass="ui-datepicker-append",this._triggerClass="ui-datepicker-trigger",this._dialogClass="ui-datepicker-dialog",this._disableClass="ui-datepicker-disabled",this._unselectableClass="ui-datepicker-unselectable",this._currentClass="ui-datepicker-current-day",this._dayOverClass="ui-datepicker-days-cell-over",this.regional=[],this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"mm/dd/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},this._defaults={showOn:"focus",showAnim:"fadeIn",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:!1,hideIfNoPrevNext:!1,navigationAsDateFormat:!1,gotoCurrent:!1,changeMonth:!1,changeYear:!1,yearRange:"c-10:c+10",showOtherMonths:!1,selectOtherMonths:!1,showWeek:!1,calculateWeek:this.iso8601Week,shortYearCutoff:"+10",minDate:null,maxDate:null,duration:"fast",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:!0,showButtonPanel:!1,autoSize:!1,disabled:!1},$.extend(this._defaults,this.regional[""]),this.dpDiv=bindHover($('<div id="'+this._mainDivId+'" class="ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>'))}function bindHover(a){var b="button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";return a.bind("mouseout",function(a){var c=$(a.target).closest(b);if(!c.length)return;c.removeClass("ui-state-hover ui-datepicker-prev-hover ui-datepicker-next-hover")}).bind("mouseover",function(c){var d=$(c.target).closest(b);if($.datepicker._isDisabledDatepicker(instActive.inline?a.parent()[0]:instActive.input[0])||!d.length)return;d.parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"),d.addClass("ui-state-hover"),d.hasClass("ui-datepicker-prev")&&d.addClass("ui-datepicker-prev-hover"),d.hasClass("ui-datepicker-next")&&d.addClass("ui-datepicker-next-hover")})}function extendRemove(a,b){$.extend(a,b);for(var c in b)if(b[c]==null||b[c]==undefined)a[c]=b[c];return a}function isArray(a){return a&&($.browser.safari&&typeof a=="object"&&a.length||a.constructor&&a.constructor.toString().match(/\Array\(\)/))}$.extend($.ui,{datepicker:{version:"1.8.23"}});var PROP_NAME="datepicker",dpuuid=(new Date).getTime(),instActive;$.extend(Datepicker.prototype,{markerClassName:"hasDatepicker",maxRows:4,log:function(){this.debug&&console.log.apply("",arguments)},_widgetDatepicker:function(){return this.dpDiv},setDefaults:function(a){return extendRemove(this._defaults,a||{}),this},_attachDatepicker:function(target,settings){var inlineSettings=null;for(var attrName in this._defaults){var attrValue=target.getAttribute("date:"+attrName);if(attrValue){inlineSettings=inlineSettings||{};try{inlineSettings[attrName]=eval(attrValue)}catch(err){inlineSettings[attrName]=attrValue}}}var nodeName=target.nodeName.toLowerCase(),inline=nodeName=="div"||nodeName=="span";target.id||(this.uuid+=1,target.id="dp"+this.uuid);var inst=this._newInst($(target),inline);inst.settings=$.extend({},settings||{},inlineSettings||{}),nodeName=="input"?this._connectDatepicker(target,inst):inline&&this._inlineDatepicker(target,inst)},_newInst:function(a,b){var c=a[0].id.replace(/([^A-Za-z0-9_-])/g,"\\\\$1");return{id:c,input:a,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:b,dpDiv:b?bindHover($('<div class="'+this._inlineClass+' ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>')):this.dpDiv}},_connectDatepicker:function(a,b){var c=$(a);b.append=$([]),b.trigger=$([]);if(c.hasClass(this.markerClassName))return;this._attachments(c,b),c.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).keyup(this._doKeyUp).bind("setData.datepicker",function(a,c,d){b.settings[c]=d}).bind("getData.datepicker",function(a,c){return this._get(b,c)}),this._autoSize(b),$.data(a,PROP_NAME,b),b.settings.disabled&&this._disableDatepicker(a)},_attachments:function(a,b){var c=this._get(b,"appendText"),d=this._get(b,"isRTL");b.append&&b.append.remove(),c&&(b.append=$('<span class="'+this._appendClass+'">'+c+"</span>"),a[d?"before":"after"](b.append)),a.unbind("focus",this._showDatepicker),b.trigger&&b.trigger.remove();var e=this._get(b,"showOn");(e=="focus"||e=="both")&&a.focus(this._showDatepicker);if(e=="button"||e=="both"){var f=this._get(b,"buttonText"),g=this._get(b,"buttonImage");b.trigger=$(this._get(b,"buttonImageOnly")?$("<img/>").addClass(this._triggerClass).attr({src:g,alt:f,title:f}):$('<button type="button"></button>').addClass(this._triggerClass).html(g==""?f:$("<img/>").attr({src:g,alt:f,title:f}))),a[d?"before":"after"](b.trigger),b.trigger.click(function(){return $.datepicker._datepickerShowing&&$.datepicker._lastInput==a[0]?$.datepicker._hideDatepicker():$.datepicker._datepickerShowing&&$.datepicker._lastInput!=a[0]?($.datepicker._hideDatepicker(),$.datepicker._showDatepicker(a[0])):$.datepicker._showDatepicker(a[0]),!1})}},_autoSize:function(a){if(this._get(a,"autoSize")&&!a.inline){var b=new Date(2009,11,20),c=this._get(a,"dateFormat");if(c.match(/[DM]/)){var d=function(a){var b=0,c=0;for(var d=0;d<a.length;d++)a[d].length>b&&(b=a[d].length,c=d);return c};b.setMonth(d(this._get(a,c.match(/MM/)?"monthNames":"monthNamesShort"))),b.setDate(d(this._get(a,c.match(/DD/)?"dayNames":"dayNamesShort"))+20-b.getDay())}a.input.attr("size",this._formatDate(a,b).length)}},_inlineDatepicker:function(a,b){var c=$(a);if(c.hasClass(this.markerClassName))return;c.addClass(this.markerClassName).append(b.dpDiv).bind("setData.datepicker",function(a,c,d){b.settings[c]=d}).bind("getData.datepicker",function(a,c){return this._get(b,c)}),$.data(a,PROP_NAME,b),this._setDate(b,this._getDefaultDate(b),!0),this._updateDatepicker(b),this._updateAlternate(b),b.settings.disabled&&this._disableDatepicker(a),b.dpDiv.css("display","block")},_dialogDatepicker:function(a,b,c,d,e){var f=this._dialogInst;if(!f){this.uuid+=1;var g="dp"+this.uuid;this._dialogInput=$('<input type="text" id="'+g+'" style="position: absolute; top: -100px; width: 0px;"/>'),this._dialogInput.keydown(this._doKeyDown),$("body").append(this._dialogInput),f=this._dialogInst=this._newInst(this._dialogInput,!1),f.settings={},$.data(this._dialogInput[0],PROP_NAME,f)}extendRemove(f.settings,d||{}),b=b&&b.constructor==Date?this._formatDate(f,b):b,this._dialogInput.val(b),this._pos=e?e.length?e:[e.pageX,e.pageY]:null;if(!this._pos){var h=document.documentElement.clientWidth,i=document.documentElement.clientHeight,j=document.documentElement.scrollLeft||document.body.scrollLeft,k=document.documentElement.scrollTop||document.body.scrollTop;this._pos=[h/2-100+j,i/2-150+k]}return this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px"),f.settings.onSelect=c,this._inDialog=!0,this.dpDiv.addClass(this._dialogClass),this._showDatepicker(this._dialogInput[0]),$.blockUI&&$.blockUI(this.dpDiv),$.data(this._dialogInput[0],PROP_NAME,f),this},_destroyDatepicker:function(a){var b=$(a),c=$.data(a,PROP_NAME);if(!b.hasClass(this.markerClassName))return;var d=a.nodeName.toLowerCase();$.removeData(a,PROP_NAME),d=="input"?(c.append.remove(),c.trigger.remove(),b.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup",this._doKeyUp)):(d=="div"||d=="span")&&b.removeClass(this.markerClassName).empty()},_enableDatepicker:function(a){var b=$(a),c=$.data(a,PROP_NAME);if(!b.hasClass(this.markerClassName))return;var d=a.nodeName.toLowerCase();if(d=="input")a.disabled=!1,c.trigger.filter("button").each(function(){this.disabled=!1}).end().filter("img").css({opacity:"1.0",cursor:""});else if(d=="div"||d=="span"){var e=b.children("."+this._inlineClass);e.children().removeClass("ui-state-disabled"),e.find("select.ui-datepicker-month, select.ui-datepicker-year").removeAttr("disabled")}this._disabledInputs=$.map(this._disabledInputs,function(b){return b==a?null:b})},_disableDatepicker:function(a){var b=$(a),c=$.data(a,PROP_NAME);if(!b.hasClass(this.markerClassName))return;var d=a.nodeName.toLowerCase();if(d=="input")a.disabled=!0,c.trigger.filter("button").each(function(){this.disabled=!0}).end().filter("img").css({opacity:"0.5",cursor:"default"});else if(d=="div"||d=="span"){var e=b.children("."+this._inlineClass);e.children().addClass("ui-state-disabled"),e.find("select.ui-datepicker-month, select.ui-datepicker-year").attr("disabled","disabled")}this._disabledInputs=$.map(this._disabledInputs,function(b){return b==a?null:b}),this._disabledInputs[this._disabledInputs.length]=a},_isDisabledDatepicker:function(a){if(!a)return!1;for(var b=0;b<this._disabledInputs.length;b++)if(this._disabledInputs[b]==a)return!0;return!1},_getInst:function(a){try{return $.data(a,PROP_NAME)}catch(b){throw"Missing instance data for this datepicker"}},_optionDatepicker:function(a,b,c){var d=this._getInst(a);if(arguments.length==2&&typeof b=="string")return b=="defaults"?$.extend({},$.datepicker._defaults):d?b=="all"?$.extend({},d.settings):this._get(d,b):null;var e=b||{};typeof b=="string"&&(e={},e[b]=c);if(d){this._curInst==d&&this._hideDatepicker();var f=this._getDateDatepicker(a,!0),g=this._getMinMaxDate(d,"min"),h=this._getMinMaxDate(d,"max");extendRemove(d.settings,e),g!==null&&e.dateFormat!==undefined&&e.minDate===undefined&&(d.settings.minDate=this._formatDate(d,g)),h!==null&&e.dateFormat!==undefined&&e.maxDate===undefined&&(d.settings.maxDate=this._formatDate(d,h)),this._attachments($(a),d),this._autoSize(d),this._setDate(d,f),this._updateAlternate(d),this._updateDatepicker(d)}},_changeDatepicker:function(a,b,c){this._optionDatepicker(a,b,c)},_refreshDatepicker:function(a){var b=this._getInst(a);b&&this._updateDatepicker(b)},_setDateDatepicker:function(a,b){var c=this._getInst(a);c&&(this._setDate(c,b),this._updateDatepicker(c),this._updateAlternate(c))},_getDateDatepicker:function(a,b){var c=this._getInst(a);return c&&!c.inline&&this._setDateFromField(c,b),c?this._getDate(c):null},_doKeyDown:function(a){var b=$.datepicker._getInst(a.target),c=!0,d=b.dpDiv.is(".ui-datepicker-rtl");b._keyEvent=!0;if($.datepicker._datepickerShowing)switch(a.keyCode){case 9:$.datepicker._hideDatepicker(),c=!1;break;case 13:var e=$("td."+$.datepicker._dayOverClass+":not(."+$.datepicker._currentClass+")",b.dpDiv);e[0]&&$.datepicker._selectDay(a.target,b.selectedMonth,b.selectedYear,e[0]);var f=$.datepicker._get(b,"onSelect");if(f){var g=$.datepicker._formatDate(b);f.apply(b.input?b.input[0]:null,[g,b])}else $.datepicker._hideDatepicker();return!1;case 27:$.datepicker._hideDatepicker();break;case 33:$.datepicker._adjustDate(a.target,a.ctrlKey?-$.datepicker._get(b,"stepBigMonths"):-$.datepicker._get(b,"stepMonths"),"M");break;case 34:$.datepicker._adjustDate(a.target,a.ctrlKey?+$.datepicker._get(b,"stepBigMonths"):+$.datepicker._get(b,"stepMonths"),"M");break;case 35:(a.ctrlKey||a.metaKey)&&$.datepicker._clearDate(a.target),c=a.ctrlKey||a.metaKey;break;case 36:(a.ctrlKey||a.metaKey)&&$.datepicker._gotoToday(a.target),c=a.ctrlKey||a.metaKey;break;case 37:(a.ctrlKey||a.metaKey)&&$.datepicker._adjustDate(a.target,d?1:-1,"D"),c=a.ctrlKey||a.metaKey,a.originalEvent.altKey&&$.datepicker._adjustDate(a.target,a.ctrlKey?-$.datepicker._get(b,"stepBigMonths"):-$.datepicker._get(b,"stepMonths"),"M");break;case 38:(a.ctrlKey||a.metaKey)&&$.datepicker._adjustDate(a.target,-7,"D"),c=a.ctrlKey||a.metaKey;break;case 39:(a.ctrlKey||a.metaKey)&&$.datepicker._adjustDate(a.target,d?-1:1,"D"),c=a.ctrlKey||a.metaKey,a.originalEvent.altKey&&$.datepicker._adjustDate(a.target,a.ctrlKey?+$.datepicker._get(b,"stepBigMonths"):+$.datepicker._get(b,"stepMonths"),"M");break;case 40:(a.ctrlKey||a.metaKey)&&$.datepicker._adjustDate(a.target,7,"D"),c=a.ctrlKey||a.metaKey;break;default:c=!1}else a.keyCode==36&&a.ctrlKey?$.datepicker._showDatepicker(this):c=!1;c&&(a.preventDefault(),a.stopPropagation())},_doKeyPress:function(a){var b=$.datepicker._getInst(a.target);if($.datepicker._get(b,"constrainInput")){var c=$.datepicker._possibleChars($.datepicker._get(b,"dateFormat")),d=String.fromCharCode(a.charCode==undefined?a.keyCode:a.charCode);return a.ctrlKey||a.metaKey||d<" "||!c||c.indexOf(d)>-1}},_doKeyUp:function(a){var b=$.datepicker._getInst(a.target);if(b.input.val()!=b.lastVal)try{var c=$.datepicker.parseDate($.datepicker._get(b,"dateFormat"),b.input?b.input.val():null,$.datepicker._getFormatConfig(b));c&&($.datepicker._setDateFromField(b),$.datepicker._updateAlternate(b),$.datepicker._updateDatepicker(b))}catch(d){$.datepicker.log(d)}return!0},_showDatepicker:function(a){a=a.target||a,a.nodeName.toLowerCase()!="input"&&(a=$("input",a.parentNode)[0]);if($.datepicker._isDisabledDatepicker(a)||$.datepicker._lastInput==a)return;var b=$.datepicker._getInst(a);$.datepicker._curInst&&$.datepicker._curInst!=b&&($.datepicker._curInst.dpDiv.stop(!0,!0),b&&$.datepicker._datepickerShowing&&$.datepicker._hideDatepicker($.datepicker._curInst.input[0]));var c=$.datepicker._get(b,"beforeShow"),d=c?c.apply(a,[a,b]):{};if(d===!1)return;extendRemove(b.settings,d),b.lastVal=null,$.datepicker._lastInput=a,$.datepicker._setDateFromField(b),$.datepicker._inDialog&&(a.value=""),$.datepicker._pos||($.datepicker._pos=$.datepicker._findPos(a),$.datepicker._pos[1]+=a.offsetHeight);var e=!1;$(a).parents().each(function(){return e|=$(this).css("position")=="fixed",!e}),e&&$.browser.opera&&($.datepicker._pos[0]-=document.documentElement.scrollLeft,$.datepicker._pos[1]-=document.documentElement.scrollTop);var f={left:$.datepicker._pos[0],top:$.datepicker._pos[1]};$.datepicker._pos=null,b.dpDiv.empty(),b.dpDiv.css({position:"absolute",display:"block",top:"-1000px"}),$.datepicker._updateDatepicker(b),f=$.datepicker._checkOffset(b,f,e),b.dpDiv.css({position:$.datepicker._inDialog&&$.blockUI?"static":e?"fixed":"absolute",display:"none",left:f.left+"px",top:f.top+"px"});if(!b.inline){var g=$.datepicker._get(b,"showAnim"),h=$.datepicker._get(b,"duration"),i=function(){var a=b.dpDiv.find("iframe.ui-datepicker-cover");if(!!a.length){var c=$.datepicker._getBorders(b.dpDiv);a.css({left:-c[0],top:-c[1],width:b.dpDiv.outerWidth(),height:b.dpDiv.outerHeight()})}};b.dpDiv.zIndex($(a).zIndex()+1),$.datepicker._datepickerShowing=!0,$.effects&&$.effects[g]?b.dpDiv.show(g,$.datepicker._get(b,"showOptions"),h,i):b.dpDiv[g||"show"](g?h:null,i),(!g||!h)&&i(),b.input.is(":visible")&&!b.input.is(":disabled")&&b.input.focus(),$.datepicker._curInst=b}},_updateDatepicker:function(a){var b=this;b.maxRows=4;var c=$.datepicker._getBorders(a.dpDiv);instActive=a,a.dpDiv.empty().append(this._generateHTML(a)),this._attachHandlers(a);var d=a.dpDiv.find("iframe.ui-datepicker-cover");!d.length||d.css({left:-c[0],top:-c[1],width:a.dpDiv.outerWidth(),height:a.dpDiv.outerHeight()}),a.dpDiv.find("."+this._dayOverClass+" a").mouseover();var e=this._getNumberOfMonths(a),f=e[1],g=17;a.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width(""),f>1&&a.dpDiv.addClass("ui-datepicker-multi-"+f).css("width",g*f+"em"),a.dpDiv[(e[0]!=1||e[1]!=1?"add":"remove")+"Class"]("ui-datepicker-multi"),a.dpDiv[(this._get(a,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl"),a==$.datepicker._curInst&&$.datepicker._datepickerShowing&&a.input&&a.input.is(":visible")&&!a.input.is(":disabled")&&a.input[0]!=document.activeElement&&a.input.focus();if(a.yearshtml){var h=a.yearshtml;setTimeout(function(){h===a.yearshtml&&a.yearshtml&&a.dpDiv.find("select.ui-datepicker-year:first").replaceWith(a.yearshtml),h=a.yearshtml=null},0)}},_getBorders:function(a){var b=function(a){return{thin:1,medium:2,thick:3}[a]||a};return[parseFloat(b(a.css("border-left-width"))),parseFloat(b(a.css("border-top-width")))]},_checkOffset:function(a,b,c){var d=a.dpDiv.outerWidth(),e=a.dpDiv.outerHeight(),f=a.input?a.input.outerWidth():0,g=a.input?a.input.outerHeight():0,h=document.documentElement.clientWidth+(c?0:$(document).scrollLeft()),i=document.documentElement.clientHeight+(c?0:$(document).scrollTop());return b.left-=this._get(a,"isRTL")?d-f:0,b.left-=c&&b.left==a.input.offset().left?$(document).scrollLeft():0,b.top-=c&&b.top==a.input.offset().top+g?$(document).scrollTop():0,b.left-=Math.min(b.left,b.left+d>h&&h>d?Math.abs(b.left+d-h):0),b.top-=Math.min(b.top,b.top+e>i&&i>e?Math.abs(e+g):0),b},_findPos:function(a){var b=this._getInst(a),c=this._get(b,"isRTL");while(a&&(a.type=="hidden"||a.nodeType!=1||$.expr.filters.hidden(a)))a=a[c?"previousSibling":"nextSibling"];var d=$(a).offset();return[d.left,d.top]},_hideDatepicker:function(a){var b=this._curInst;if(!b||a&&b!=$.data(a,PROP_NAME))return;if(this._datepickerShowing){var c=this._get(b,"showAnim"),d=this._get(b,"duration"),e=function(){$.datepicker._tidyDialog(b)};$.effects&&$.effects[c]?b.dpDiv.hide(c,$.datepicker._get(b,"showOptions"),d,e):b.dpDiv[c=="slideDown"?"slideUp":c=="fadeIn"?"fadeOut":"hide"](c?d:null,e),c||e(),this._datepickerShowing=!1;var f=this._get(b,"onClose");f&&f.apply(b.input?b.input[0]:null,[b.input?b.input.val():"",b]),this._lastInput=null,this._inDialog&&(this._dialogInput.css({position:"absolute",left:"0",top:"-100px"}),$.blockUI&&($.unblockUI(),$("body").append(this.dpDiv))),this._inDialog=!1}},_tidyDialog:function(a){a.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")},_checkExternalClick:function(a){if(!$.datepicker._curInst)return;var b=$(a.target),c=$.datepicker._getInst(b[0]);(b[0].id!=$.datepicker._mainDivId&&b.parents("#"+$.datepicker._mainDivId).length==0&&!b.hasClass($.datepicker.markerClassName)&&!b.closest("."+$.datepicker._triggerClass).length&&$.datepicker._datepickerShowing&&(!$.datepicker._inDialog||!$.blockUI)||b.hasClass($.datepicker.markerClassName)&&$.datepicker._curInst!=c)&&$.datepicker._hideDatepicker()},_adjustDate:function(a,b,c){var d=$(a),e=this._getInst(d[0]);if(this._isDisabledDatepicker(d[0]))return;this._adjustInstDate(e,b+(c=="M"?this._get(e,"showCurrentAtPos"):0),c),this._updateDatepicker(e)},_gotoToday:function(a){var b=$(a),c=this._getInst(b[0]);if(this._get(c,"gotoCurrent")&&c.currentDay)c.selectedDay=c.currentDay,c.drawMonth=c.selectedMonth=c.currentMonth,c.drawYear=c.selectedYear=c.currentYear;else{var d=new Date;c.selectedDay=d.getDate(),c.drawMonth=c.selectedMonth=d.getMonth(),c.drawYear=c.selectedYear=d.getFullYear()}this._notifyChange(c),this._adjustDate(b)},_selectMonthYear:function(a,b,c){var d=$(a),e=this._getInst(d[0]);e["selected"+(c=="M"?"Month":"Year")]=e["draw"+(c=="M"?"Month":"Year")]=parseInt(b.options[b.selectedIndex].value,10),this._notifyChange(e),this._adjustDate(d)},_selectDay:function(a,b,c,d){var e=$(a);if($(d).hasClass(this._unselectableClass)||this._isDisabledDatepicker(e[0]))return;var f=this._getInst(e[0]);f.selectedDay=f.currentDay=$("a",d).html(),f.selectedMonth=f.currentMonth=b,f.selectedYear=f.currentYear=c,this._selectDate(a,this._formatDate(f,f.currentDay,f.currentMonth,f.currentYear))},_clearDate:function(a){var b=$(a),c=this._getInst(b[0]);this._selectDate(b,"")},_selectDate:function(a,b){var c=$(a),d=this._getInst(c[0]);b=b!=null?b:this._formatDate(d),d.input&&d.input.val(b),this._updateAlternate(d);var e=this._get(d,"onSelect");e?e.apply(d.input?d.input[0]:null,[b,d]):d.input&&d.input.trigger("change"),d.inline?this._updateDatepicker(d):(this._hideDatepicker(),this._lastInput=d.input[0],typeof d.input[0]!="object"&&d.input.focus(),this._lastInput=null)},_updateAlternate:function(a){var b=this._get(a,"altField");if(b){var c=this._get(a,"altFormat")||this._get(a,"dateFormat"),d=this._getDate(a),e=this.formatDate(c,d,this._getFormatConfig(a));$(b).each(function(){$(this).val(e)})}},noWeekends:function(a){var b=a.getDay();return[b>0&&b<6,""]},iso8601Week:function(a){var b=new Date(a.getTime());b.setDate(b.getDate()+4-(b.getDay()||7));var c=b.getTime();return b.setMonth(0),b.setDate(1),Math.floor(Math.round((c-b)/864e5)/7)+1},parseDate:function(a,b,c){if(a==null||b==null)throw"Invalid arguments";b=typeof b=="object"?b.toString():b+"";if(b=="")return null;var d=(c?c.shortYearCutoff:null)||this._defaults.shortYearCutoff;d=typeof d!="string"?d:(new Date).getFullYear()%100+parseInt(d,10);var e=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,f=(c?c.dayNames:null)||this._defaults.dayNames,g=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort,h=(c?c.monthNames:null)||this._defaults.monthNames,i=-1,j=-1,k=-1,l=-1,m=!1,n=function(b){var c=s+1<a.length&&a.charAt(s+1)==b;return c&&s++,c},o=function(a){var c=n(a),d=a=="@"?14:a=="!"?20:a=="y"&&c?4:a=="o"?3:2,e=new RegExp("^\\d{1,"+d+"}"),f=b.substring(r).match(e);if(!f)throw"Missing number at position "+r;return r+=f[0].length,parseInt(f[0],10)},p=function(a,c,d){var e=$.map(n(a)?d:c,function(a,b){return[[b,a]]}).sort(function(a,b){return-(a[1].length-b[1].length)}),f=-1;$.each(e,function(a,c){var d=c[1];if(b.substr(r,d.length).toLowerCase()==d.toLowerCase())return f=c[0],r+=d.length,!1});if(f!=-1)return f+1;throw"Unknown name at position "+r},q=function(){if(b.charAt(r)!=a.charAt(s))throw"Unexpected literal at position "+r;r++},r=0;for(var s=0;s<a.length;s++)if(m)a.charAt(s)=="'"&&!n("'")?m=!1:q();else switch(a.charAt(s)){case"d":k=o("d");break;case"D":p("D",e,f);break;case"o":l=o("o");break;case"m":j=o("m");break;case"M":j=p("M",g,h);break;case"y":i=o("y");break;case"@":var t=new Date(o("@"));i=t.getFullYear(),j=t.getMonth()+1,k=t.getDate();break;case"!":var t=new Date((o("!")-this._ticksTo1970)/1e4);i=t.getFullYear(),j=t.getMonth()+1,k=t.getDate();break;case"'":n("'")?q():m=!0;break;default:q()}if(r<b.length)throw"Extra/unparsed characters found in date: "+b.substring(r);i==-1?i=(new Date).getFullYear():i<100&&(i+=(new Date).getFullYear()-(new Date).getFullYear()%100+(i<=d?0:-100));if(l>-1){j=1,k=l;do{var u=this._getDaysInMonth(i,j-1);if(k<=u)break;j++,k-=u}while(!0)}var t=this._daylightSavingAdjust(new Date(i,j-1,k));if(t.getFullYear()!=i||t.getMonth()+1!=j||t.getDate()!=k)throw"Invalid date";return t},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925))*24*60*60*1e7,formatDate:function(a,b,c){if(!b)return"";var d=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,e=(c?c.dayNames:null)||this._defaults.dayNames,f=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort,g=(c?c.monthNames:null)||this._defaults.monthNames,h=function(b){var c=m+1<a.length&&a.charAt(m+1)==b;return c&&m++,c},i=function(a,b,c){var d=""+b;if(h(a))while(d.length<c)d="0"+d;return d},j=function(a,b,c,d){return h(a)?d[b]:c[b]},k="",l=!1;if(b)for(var m=0;m<a.length;m++)if(l)a.charAt(m)=="'"&&!h("'")?l=!1:k+=a.charAt(m);else switch(a.charAt(m)){case"d":k+=i("d",b.getDate(),2);break;case"D":k+=j("D",b.getDay(),d,e);break;case"o":k+=i("o",Math.round(((new Date(b.getFullYear(),b.getMonth(),b.getDate())).getTime()-(new Date(b.getFullYear(),0,0)).getTime())/864e5),3);break;case"m":k+=i("m",b.getMonth()+1,2);break;case"M":k+=j("M",b.getMonth(),f,g);break;case"y":k+=h("y")?b.getFullYear():(b.getYear()%100<10?"0":"")+b.getYear()%100;break;case"@":k+=b.getTime();break;case"!":k+=b.getTime()*1e4+this._ticksTo1970;break;case"'":h("'")?k+="'":l=!0;break;default:k+=a.charAt(m)}return k},_possibleChars:function(a){var b="",c=!1,d=function(b){var c=e+1<a.length&&a.charAt(e+1)==b;return c&&e++,c};for(var e=0;e<a.length;e++)if(c)a.charAt(e)=="'"&&!d("'")?c=!1:b+=a.charAt(e);else switch(a.charAt(e)){case"d":case"m":case"y":case"@":b+="0123456789";break;case"D":case"M":return null;case"'":d("'")?b+="'":c=!0;break;default:b+=a.charAt(e)}return b},_get:function(a,b){return a.settings[b]!==undefined?a.settings[b]:this._defaults[b]},_setDateFromField:function(a,b){if(a.input.val()==a.lastVal)return;var c=this._get(a,"dateFormat"),d=a.lastVal=a.input?a.input.val():null,e,f;e=f=this._getDefaultDate(a);var g=this._getFormatConfig(a);try{e=this.parseDate(c,d,g)||f}catch(h){this.log(h),d=b?"":d}a.selectedDay=e.getDate(),a.drawMonth=a.selectedMonth=e.getMonth(),a.drawYear=a.selectedYear=e.getFullYear(),a.currentDay=d?e.getDate():0,a.currentMonth=d?e.getMonth():0,a.currentYear=d?e.getFullYear():0,this._adjustInstDate(a)},_getDefaultDate:function(a){return this._restrictMinMax(a,this._determineDate(a,this._get(a,"defaultDate"),new Date))},_determineDate:function(a,b,c){var d=function(a){var b=new Date;return b.setDate(b.getDate()+a),b},e=function(b){try{return $.datepicker.parseDate($.datepicker._get(a,"dateFormat"),b,$.datepicker._getFormatConfig(a))}catch(c){}var d=(b.toLowerCase().match(/^c/)?$.datepicker._getDate(a):null)||new Date,e=d.getFullYear(),f=d.getMonth(),g=d.getDate(),h=/([+-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g,i=h.exec(b);while(i){switch(i[2]||"d"){case"d":case"D":g+=parseInt(i[1],10);break;case"w":case"W":g+=parseInt(i[1],10)*7;break;case"m":case"M":f+=parseInt(i[1],10),g=Math.min(g,$.datepicker._getDaysInMonth(e,f));break;case"y":case"Y":e+=parseInt(i[1],10),g=Math.min(g,$.datepicker._getDaysInMonth(e,f))}i=h.exec(b)}return new Date(e,f,g)},f=b==null||b===""?c:typeof b=="string"?e(b):typeof b=="number"?isNaN(b)?c:d(b):new Date(b.getTime());return f=f&&f.toString()=="Invalid Date"?c:f,f&&(f.setHours(0),f.setMinutes(0),f.setSeconds(0),f.setMilliseconds(0)),this._daylightSavingAdjust(f)},_daylightSavingAdjust:function(a){return a?(a.setHours(a.getHours()>12?a.getHours()+2:0),a):null},_setDate:function(a,b,c){var d=!b,e=a.selectedMonth,f=a.selectedYear,g=this._restrictMinMax(a,this._determineDate(a,b,new Date));a.selectedDay=a.currentDay=g.getDate(),a.drawMonth=a.selectedMonth=a.currentMonth=g.getMonth(),a.drawYear=a.selectedYear=a.currentYear=g.getFullYear(),(e!=a.selectedMonth||f!=a.selectedYear)&&!c&&this._notifyChange(a),this._adjustInstDate(a),a.input&&a.input.val(d?"":this._formatDate(a))},_getDate:function(a){var b=!a.currentYear||a.input&&a.input.val()==""?null:this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return b},_attachHandlers:function(a){var b=this._get(a,"stepMonths"),c="#"+a.id.replace(/\\\\/g,"\\");a.dpDiv.find("[data-handler]").map(function(){var a={prev:function(){window["DP_jQuery_"+dpuuid].datepicker._adjustDate(c,-b,"M")},next:function(){window["DP_jQuery_"+dpuuid].datepicker._adjustDate(c,+b,"M")},hide:function(){window["DP_jQuery_"+dpuuid].datepicker._hideDatepicker()},today:function(){window["DP_jQuery_"+dpuuid].datepicker._gotoToday(c)},selectDay:function(){return window["DP_jQuery_"+dpuuid].datepicker._selectDay(c,+this.getAttribute("data-month"),+this.getAttribute("data-year"),this),!1},selectMonth:function(){return window["DP_jQuery_"+dpuuid].datepicker._selectMonthYear(c,this,"M"),!1},selectYear:function(){return window["DP_jQuery_"+dpuuid].datepicker._selectMonthYear(c,this,"Y"),!1}};$(this).bind(this.getAttribute("data-event"),a[this.getAttribute("data-handler")])})},_generateHTML:function(a){var b=new Date;b=this._daylightSavingAdjust(new Date(b.getFullYear(),b.getMonth(),b.getDate()));var c=this._get(a,"isRTL"),d=this._get(a,"showButtonPanel"),e=this._get(a,"hideIfNoPrevNext"),f=this._get(a,"navigationAsDateFormat"),g=this._getNumberOfMonths(a),h=this._get(a,"showCurrentAtPos"),i=this._get(a,"stepMonths"),j=g[0]!=1||g[1]!=1,k=this._daylightSavingAdjust(a.currentDay?new Date(a.currentYear,a.currentMonth,a.currentDay):new Date(9999,9,9)),l=this._getMinMaxDate(a,"min"),m=this._getMinMaxDate(a,"max"),n=a.drawMonth-h,o=a.drawYear;n<0&&(n+=12,o--);if(m){var p=this._daylightSavingAdjust(new Date(m.getFullYear(),m.getMonth()-g[0]*g[1]+1,m.getDate()));p=l&&p<l?l:p;while(this._daylightSavingAdjust(new Date(o,n,1))>p)n--,n<0&&(n=11,o--)}a.drawMonth=n,a.drawYear=o;var q=this._get(a,"prevText");q=f?this.formatDate(q,this._daylightSavingAdjust(new Date(o,n-i,1)),this._getFormatConfig(a)):q;var r=this._canAdjustMonth(a,-1,o,n)?'<a class="ui-datepicker-prev ui-corner-all" data-handler="prev" data-event="click" title="'+q+'"><span class="ui-icon ui-icon-circle-triangle-'+(c?"e":"w")+'">'+q+"</span></a>":e?"":'<a class="ui-datepicker-prev ui-corner-all ui-state-disabled" title="'+q+'"><span class="ui-icon ui-icon-circle-triangle-'+(c?"e":"w")+'">'+q+"</span></a>",s=this._get(a,"nextText");s=f?this.formatDate(s,this._daylightSavingAdjust(new Date(o,n+i,1)),this._getFormatConfig(a)):s;var t=this._canAdjustMonth(a,1,o,n)?'<a class="ui-datepicker-next ui-corner-all" data-handler="next" data-event="click" title="'+s+'"><span class="ui-icon ui-icon-circle-triangle-'+(c?"w":"e")+'">'+s+"</span></a>":e?"":'<a class="ui-datepicker-next ui-corner-all ui-state-disabled" title="'+s+'"><span class="ui-icon ui-icon-circle-triangle-'+(c?"w":"e")+'">'+s+"</span></a>",u=this._get(a,"currentText"),v=this._get(a,"gotoCurrent")&&a.currentDay?k:b;u=f?this.formatDate(u,v,this._getFormatConfig(a)):u;var w=a.inline?"":'<button type="button" class="ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all" data-handler="hide" data-event="click">'+this._get(a,"closeText")+"</button>",x=d?'<div class="ui-datepicker-buttonpane ui-widget-content">'+(c?w:"")+(this._isInRange(a,v)?'<button type="button" class="ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all" data-handler="today" data-event="click">'+u+"</button>":"")+(c?"":w)+"</div>":"",y=parseInt(this._get(a,"firstDay"),10);y=isNaN(y)?0:y;var z=this._get(a,"showWeek"),A=this._get(a,"dayNames"),B=this._get(a,"dayNamesShort"),C=this._get(a,"dayNamesMin"),D=this._get(a,"monthNames"),E=this._get(a,"monthNamesShort"),F=this._get(a,"beforeShowDay"),G=this._get(a,"showOtherMonths"),H=this._get(a,"selectOtherMonths"),I=this._get(a,"calculateWeek")||this.iso8601Week,J=this._getDefaultDate(a),K="";for(var L=0;L<g[0];L++){var M="";this.maxRows=4;for(var N=0;N<g[1];N++){var O=this._daylightSavingAdjust(new Date(o,n,a.selectedDay)),P=" ui-corner-all",Q="";if(j){Q+='<div class="ui-datepicker-group';if(g[1]>1)switch(N){case 0:Q+=" ui-datepicker-group-first",P=" ui-corner-"+(c?"right":"left");break;case g[1]-1:Q+=" ui-datepicker-group-last",P=" ui-corner-"+(c?"left":"right");break;default:Q+=" ui-datepicker-group-middle",P=""}Q+='">'}Q+='<div class="ui-datepicker-header ui-widget-header ui-helper-clearfix'+P+'">'+(/all|left/.test(P)&&L==0?c?t:r:"")+(/all|right/.test(P)&&L==0?c?r:t:"")+this._generateMonthYearHeader(a,n,o,l,m,L>0||N>0,D,E)+'</div><table class="ui-datepicker-calendar"><thead>'+"<tr>";var R=z?'<th class="ui-datepicker-week-col">'+this._get(a,"weekHeader")+"</th>":"";for(var S=0;S<7;S++){var T=(S+y)%7;R+="<th"+((S+y+6)%7>=5?' class="ui-datepicker-week-end"':"")+">"+'<span title="'+A[T]+'">'+C[T]+"</span></th>"}Q+=R+"</tr></thead><tbody>";var U=this._getDaysInMonth(o,n);o==a.selectedYear&&n==a.selectedMonth&&(a.selectedDay=Math.min(a.selectedDay,U));var V=(this._getFirstDayOfMonth(o,n)-y+7)%7,W=Math.ceil((V+U)/7),X=j?this.maxRows>W?this.maxRows:W:W;this.maxRows=X;var Y=this._daylightSavingAdjust(new Date(o,n,1-V));for(var Z=0;Z<X;Z++){Q+="<tr>";var _=z?'<td class="ui-datepicker-week-col">'+this._get(a,"calculateWeek")(Y)+"</td>":"";for(var S=0;S<7;S++){var ba=F?F.apply(a.input?a.input[0]:null,[Y]):[!0,""],bb=Y.getMonth()!=n,bc=bb&&!H||!ba[0]||l&&Y<l||m&&Y>m;_+='<td class="'+((S+y+6)%7>=5?" ui-datepicker-week-end":"")+(bb?" ui-datepicker-other-month":"")+(Y.getTime()==O.getTime()&&n==a.selectedMonth&&a._keyEvent||J.getTime()==Y.getTime()&&J.getTime()==O.getTime()?" "+this._dayOverClass:"")+(bc?" "+this._unselectableClass+" ui-state-disabled":"")+(bb&&!G?"":" "+ba[1]+(Y.getTime()==k.getTime()?" "+this._currentClass:"")+(Y.getTime()==b.getTime()?" ui-datepicker-today":""))+'"'+((!bb||G)&&ba[2]?' title="'+ba[2]+'"':"")+(bc?"":' data-handler="selectDay" data-event="click" data-month="'+Y.getMonth()+'" data-year="'+Y.getFullYear()+'"')+">"+(bb&&!G?"&#xa0;":bc?'<span class="ui-state-default">'+Y.getDate()+"</span>":'<a class="ui-state-default'+(Y.getTime()==b.getTime()?" ui-state-highlight":"")+(Y.getTime()==k.getTime()?" ui-state-active":"")+(bb?" ui-priority-secondary":"")+'" href="#">'+Y.getDate()+"</a>")+"</td>",Y.setDate(Y.getDate()+1),Y=this._daylightSavingAdjust(Y)}Q+=_+"</tr>"}n++,n>11&&(n=0,o++),Q+="</tbody></table>"+(j?"</div>"+(g[0]>0&&N==g[1]-1?'<div class="ui-datepicker-row-break"></div>':""):""),M+=Q}K+=M}return K+=x+($.browser.msie&&parseInt($.browser.version,10)<7&&!a.inline?'<iframe src="javascript:false;" class="ui-datepicker-cover" frameborder="0"></iframe>':""),a._keyEvent=!1,K},_generateMonthYearHeader:function(a,b,c,d,e,f,g,h){var i=this._get(a,"changeMonth"),j=this._get(a,"changeYear"),k=this._get(a,"showMonthAfterYear"),l='<div class="ui-datepicker-title">',m="";if(f||!i)m+='<span class="ui-datepicker-month">'+g[b]+"</span>";else{var n=d&&d.getFullYear()==c,o=e&&e.getFullYear()==c;m+='<select class="ui-datepicker-month" data-handler="selectMonth" data-event="change">';for(var p=0;p<12;p++)(!n||p>=d.getMonth())&&(!o||p<=e.getMonth())&&(m+='<option value="'+p+'"'+(p==b?' selected="selected"':"")+">"+h[p]+"</option>");m+="</select>"}k||(l+=m+(f||!i||!j?"&#xa0;":""));if(!a.yearshtml){a.yearshtml="";if(f||!j)l+='<span class="ui-datepicker-year">'+c+"</span>";else{var q=this._get(a,"yearRange").split(":"),r=(new Date).getFullYear(),s=function(a){var b=a.match(/c[+-].*/)?c+parseInt(a.substring(1),10):a.match(/[+-].*/)?r+parseInt(a,10):parseInt(a,10);return isNaN(b)?r:b},t=s(q[0]),u=Math.max(t,s(q[1]||""));t=d?Math.max(t,d.getFullYear()):t,u=e?Math.min(u,e.getFullYear()):u,a.yearshtml+='<select class="ui-datepicker-year" data-handler="selectYear" data-event="change">';for(;t<=u;t++)a.yearshtml+='<option value="'+t+'"'+(t==c?' selected="selected"':"")+">"+t+"</option>";a.yearshtml+="</select>",l+=a.yearshtml,a.yearshtml=null}}return l+=this._get(a,"yearSuffix"),k&&(l+=(f||!i||!j?"&#xa0;":"")+m),l+="</div>",l},_adjustInstDate:function(a,b,c){var d=a.drawYear+(c=="Y"?b:0),e=a.drawMonth+(c=="M"?b:0),f=Math.min(a.selectedDay,this._getDaysInMonth(d,e))+(c=="D"?b:0),g=this._restrictMinMax(a,this._daylightSavingAdjust(new Date(d,e,f)));a.selectedDay=g.getDate(),a.drawMonth=a.selectedMonth=g.getMonth(),a.drawYear=a.selectedYear=g.getFullYear(),(c=="M"||c=="Y")&&this._notifyChange(a)},_restrictMinMax:function(a,b){var c=this._getMinMaxDate(a,"min"),d=this._getMinMaxDate(a,"max"),e=c&&b<c?c:b;return e=d&&e>d?d:e,e},_notifyChange:function(a){var b=this._get(a,"onChangeMonthYear");b&&b.apply(a.input?a.input[0]:null,[a.selectedYear,a.selectedMonth+1,a])},_getNumberOfMonths:function(a){var b=this._get(a,"numberOfMonths");return b==null?[1,1]:typeof b=="number"?[1,b]:b},_getMinMaxDate:function(a,b){return this._determineDate(a,this._get(a,b+"Date"),null)},_getDaysInMonth:function(a,b){return 32-this._daylightSavingAdjust(new Date(a,b,32)).getDate()},_getFirstDayOfMonth:function(a,b){return(new Date(a,b,1)).getDay()},_canAdjustMonth:function(a,b,c,d){var e=this._getNumberOfMonths(a),f=this._daylightSavingAdjust(new Date(c,d+(b<0?b:e[0]*e[1]),1));return b<0&&f.setDate(this._getDaysInMonth(f.getFullYear(),f.getMonth())),this._isInRange(a,f)},_isInRange:function(a,b){var c=this._getMinMaxDate(a,"min"),d=this._getMinMaxDate(a,"max");return(!c||b.getTime()>=c.getTime())&&(!d||b.getTime()<=d.getTime())},_getFormatConfig:function(a){var b=this._get(a,"shortYearCutoff");return b=typeof b!="string"?b:(new Date).getFullYear()%100+parseInt(b,10),{shortYearCutoff:b,dayNamesShort:this._get(a,"dayNamesShort"),dayNames:this._get(a,"dayNames"),monthNamesShort:this._get(a,"monthNamesShort"),monthNames:this._get(a,"monthNames")}},_formatDate:function(a,b,c,d){b||(a.currentDay=a.selectedDay,a.currentMonth=a.selectedMonth,a.currentYear=a.selectedYear);var e=b?typeof b=="object"?b:this._daylightSavingAdjust(new Date(d,c,b)):this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return this.formatDate(this._get(a,"dateFormat"),e,this._getFormatConfig(a))}}),$.fn.datepicker=function(a){if(!this.length)return this;$.datepicker.initialized||($(document).mousedown($.datepicker._checkExternalClick).find("body").append($.datepicker.dpDiv),$.datepicker.initialized=!0);var b=Array.prototype.slice.call(arguments,1);return typeof a!="string"||a!="isDisabled"&&a!="getDate"&&a!="widget"?a=="option"&&arguments.length==2&&typeof arguments[1]=="string"?$.datepicker["_"+a+"Datepicker"].apply($.datepicker,[this[0]].concat(b)):this.each(function(){typeof a=="string"?$.datepicker["_"+a+"Datepicker"].apply($.datepicker,[this].concat(b)):$.datepicker._attachDatepicker(this,a)}):$.datepicker["_"+a+"Datepicker"].apply($.datepicker,[this[0]].concat(b))},$.datepicker=new Datepicker,$.datepicker.initialized=!1,$.datepicker.uuid=(new Date).getTime(),$.datepicker.version="1.8.23",window["DP_jQuery_"+dpuuid]=$}(jQuery),function(a,b){var c="ui-dialog ui-widget ui-widget-content ui-corner-all ",d={buttons:!0,height:!0,maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0,width:!0},e={maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0};a.widget("ui.dialog",{options:{autoOpen:!0,buttons:{},closeOnEscape:!0,closeText:"close",dialogClass:"",draggable:!0,hide:null,height:"auto",maxHeight:!1,maxWidth:!1,minHeight:150,minWidth:150,modal:!1,position:{my:"center",at:"center",collision:"fit",using:function(b){var c=a(this).css(b).offset().top;c<0&&a(this).css("top",b.top-c)}},resizable:!0,show:null,stack:!0,title:"",width:300,zIndex:1e3},_create:function(){this.originalTitle=this.element.attr("title"),typeof this.originalTitle!="string"&&(this.originalTitle=""),this.options.title=this.options.title||this.originalTitle;var b=this,d=b.options,e=d.title||"&#160;",f=a.ui.dialog.getTitleId(b.element),g=(b.uiDialog=a("<div></div>")).appendTo(document.body).hide().addClass(c+d.dialogClass).css({zIndex:d.zIndex}).attr("tabIndex",-1).css("outline",0).keydown(function(c){d.closeOnEscape&&!c.isDefaultPrevented()&&c.keyCode&&c.keyCode===a.ui.keyCode.ESCAPE&&(b.close(c),c.preventDefault())}).attr({role:"dialog","aria-labelledby":f}).mousedown(function(a){b.moveToTop(!1,a)}),h=b.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(g),i=(b.uiDialogTitlebar=a("<div></div>")).addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").prependTo(g),j=a('<a href="#"></a>').addClass("ui-dialog-titlebar-close ui-corner-all").attr("role","button").hover(function(){j.addClass("ui-state-hover")},function(){j.removeClass("ui-state-hover")}).focus(function(){j.addClass("ui-state-focus")}).blur(function(){j.removeClass("ui-state-focus")}).click(function(a){return b.close(a),!1}).appendTo(i),k=(b.uiDialogTitlebarCloseText=a("<span></span>")).addClass("ui-icon ui-icon-closethick").text(d.closeText).appendTo(j),l=a("<span></span>").addClass("ui-dialog-title").attr("id",f).html(e).prependTo(i);a.isFunction(d.beforeclose)&&!a.isFunction(d.beforeClose)&&(d.beforeClose=d.beforeclose),i.find("*").add(i).disableSelection(),d.draggable&&a.fn.draggable&&b._makeDraggable(),d.resizable&&a.fn.resizable&&b._makeResizable(),b._createButtons(d.buttons),b._isOpen=!1,a.fn.bgiframe&&g.bgiframe()},_init:function(){this.options.autoOpen&&this.open()},destroy:function(){var a=this;return a.overlay&&a.overlay.destroy(),a.uiDialog.hide(),a.element.unbind(".dialog").removeData("dialog").removeClass("ui-dialog-content ui-widget-content").hide().appendTo("body"),a.uiDialog.remove(),a.originalTitle&&a.element.attr("title",a.originalTitle),a},widget:function(){return this.uiDialog},close:function(b){var c=this,d,e;if(!1===c._trigger("beforeClose",b))return;return c.overlay&&c.overlay.destroy(),c.uiDialog.unbind("keypress.ui-dialog"),c._isOpen=!1,c.options.hide?c.uiDialog.hide(c.options.hide,function(){c._trigger("close",b)}):(c.uiDialog.hide(),c._trigger("close",b)),a.ui.dialog.overlay.resize(),c.options.modal&&(d=0,a(".ui-dialog").each(function(){this!==c.uiDialog[0]&&(e=a(this).css("z-index"),isNaN(e)||(d=Math.max(d,e)))}),a.ui.dialog.maxZ=d),c},isOpen:function(){return this._isOpen},moveToTop:function(b,c){var d=this,e=d.options,f;return e.modal&&!b||!e.stack&&!e.modal?d._trigger("focus",c):(e.zIndex>a.ui.dialog.maxZ&&(a.ui.dialog.maxZ=e.zIndex),d.overlay&&(a.ui.dialog.maxZ+=1,d.overlay.$el.css("z-index",a.ui.dialog.overlay.maxZ=a.ui.dialog.maxZ)),f={scrollTop:d.element.scrollTop(),scrollLeft:d.element.scrollLeft()},a.ui.dialog.maxZ+=1,d.uiDialog.css("z-index",a.ui.dialog.maxZ),d.element.attr(f),d._trigger("focus",c),d)},open:function(){if(this._isOpen)return;var b=this,c=b.options,d=b.uiDialog;return b.overlay=c.modal?new a.ui.dialog.overlay(b):null,b._size(),b._position(c.position),d.show(c.show),b.moveToTop(!0),c.modal&&d.bind("keydown.ui-dialog",function(b){if(b.keyCode!==a.ui.keyCode.TAB)return;var c=a(":tabbable",this),d=c.filter(":first"),e=c.filter(":last");if(b.target===e[0]&&!b.shiftKey)return d.focus(1),!1;if(b.target===d[0]&&b.shiftKey)return e.focus(1),!1}),a(b.element.find(":tabbable").get().concat(d.find(".ui-dialog-buttonpane :tabbable").get().concat(d.get()))).eq(0).focus(),b._isOpen=!0,b._trigger("open"),b},_createButtons:function(b){var c=this,d=!1,e=a("<div></div>").addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"),f=a("<div></div>").addClass("ui-dialog-buttonset").appendTo(e);c.uiDialog.find(".ui-dialog-buttonpane").remove(),typeof b=="object"&&b!==null&&a.each(b,function(){return!(d=!0)}),d&&(a.each(b,function(b,d){d=a.isFunction(d)?{click:d,text:b}:d;var e=a('<button type="button"></button>').click(function(){d.click.apply(c.element[0],arguments)}).appendTo(f);a.each(d,function(a,b){if(a==="click")return;a in e?e[a](b):e.attr(a,b)}),a.fn.button&&e.button()}),e.appendTo(c.uiDialog))},_makeDraggable:function(){function f(a){return{position:a.position,offset:a.offset}}var b=this,c=b.options,d=a(document),e;b.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",handle:".ui-dialog-titlebar",containment:"document",start:function(d,g){e=c.height==="auto"?"auto":a(this).height(),a(this).height(a(this).height()).addClass("ui-dialog-dragging"),b._trigger("dragStart",d,f(g))},drag:function(a,c){b._trigger("drag",a,f(c))},stop:function(g,h){c.position=[h.position.left-d.scrollLeft(),h.position.top-d.scrollTop()],a(this).removeClass("ui-dialog-dragging").height(e),b._trigger("dragStop",g,f(h)),a.ui.dialog.overlay.resize()}})},_makeResizable:function(c){function h(a){return{originalPosition:a.originalPosition,originalSize:a.originalSize,position:a.position,size:a.size}}c=c===b?this.options.resizable:c;var d=this,e=d.options,f=d.uiDialog.css("position"),g=typeof c=="string"?c:"n,e,s,w,se,sw,ne,nw";d.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:d.element,maxWidth:e.maxWidth,maxHeight:e.maxHeight,minWidth:e.minWidth,minHeight:d._minHeight(),handles:g,start:function(b,c){a(this).addClass("ui-dialog-resizing"),d._trigger("resizeStart",b,h(c))},resize:function(a,b){d._trigger("resize",a,h(b))},stop:function(b,c){a(this).removeClass("ui-dialog-resizing"),e.height=a(this).height(),e.width=a(this).width(),d._trigger("resizeStop",b,h(c)),a.ui.dialog.overlay.resize()}}).css("position",f).find(".ui-resizable-se").addClass("ui-icon ui-icon-grip-diagonal-se")},_minHeight:function(){var a=this.options;return a.height==="auto"?a.minHeight:Math.min(a.minHeight,a.height)},_position:function(b){var c=[],d=[0,0],e;if(b){if(typeof b=="string"||typeof b=="object"&&"0"in b)c=b.split?b.split(" "):[b[0],b[1]],c.length===1&&(c[1]=c[0]),a.each(["left","top"],function(a,b){+c[a]===c[a]&&(d[a]=c[a],c[a]=b)}),b={my:c.join(" "),at:c.join(" "),offset:d.join(" ")};b=a.extend({},a.ui.dialog.prototype.options.position,b)}else b=a.ui.dialog.prototype.options.position;e=this.uiDialog.is(":visible"),e||this.uiDialog.show(),this.uiDialog.css({top:0,left:0}).position(a.extend({of:window},b)),e||this.uiDialog.hide()},_setOptions:function(b){var c=this,f={},g=!1;a.each(b,function(a,b){c._setOption(a,b),a in d&&(g=!0),a in e&&(f[a]=b)}),g&&this._size(),this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option",f)},_setOption:function(b,d){var e=this,f=e.uiDialog;switch(b){case"beforeclose":b="beforeClose";break;case"buttons":e._createButtons(d);break;case"closeText":e.uiDialogTitlebarCloseText.text(""+d);break;case"dialogClass":f.removeClass(e.options.dialogClass).addClass(c+d);break;case"disabled":d?f.addClass("ui-dialog-disabled"):f.removeClass("ui-dialog-disabled");break;case"draggable":var g=f.is(":data(draggable)");g&&!d&&f.draggable("destroy"),!g&&d&&e._makeDraggable();break;case"position":e._position(d);break;case"resizable":var h=f.is(":data(resizable)");h&&!d&&f.resizable("destroy"),h&&typeof d=="string"&&f.resizable("option","handles",d),!h&&d!==!1&&e._makeResizable(d);break;case"title":a(".ui-dialog-title",e.uiDialogTitlebar).html(""+(d||"&#160;"))}a.Widget.prototype._setOption.apply(e,arguments)},_size:function(){var b=this.options,c,d,e=this.uiDialog.is(":visible");this.element.show().css({width:"auto",minHeight:0,height:0}),b.minWidth>b.width&&(b.width=b.minWidth),c=this.uiDialog.css({height:"auto",width:b.width}).height(),d=Math.max(0,b.minHeight-c);if(b.height==="auto")if(a.support.minHeight)this.element.css({minHeight:d,height:"auto"});else{this.uiDialog.show();var f=this.element.css("height","auto").height();e||this.uiDialog.hide(),this.element.height(Math.max(f,d))}else this.element.height(Math.max(b.height-c,0));this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option","minHeight",this._minHeight())}}),a.extend(a.ui.dialog,{version:"1.8.23",uuid:0,maxZ:0,getTitleId:function(a){var b=a.attr("id");return b||(this.uuid+=1,b=this.uuid),"ui-dialog-title-"+b},overlay:function(b){this.$el=a.ui.dialog.overlay.create(b)}}),a.extend(a.ui.dialog.overlay,{instances:[],oldInstances:[],maxZ:0,events:a.map("focus,mousedown,mouseup,keydown,keypress,click".split(","),function(a){return a+".dialog-overlay"}).join(" "),create:function(b){this.instances.length===0&&(setTimeout(function(){a.ui.dialog.overlay.instances.length&&a(document).bind(a.ui.dialog.overlay.events,function(b){if(a(b.target).zIndex()<a.ui.dialog.overlay.maxZ)return!1})},1),a(document).bind("keydown.dialog-overlay",function(c){b.options.closeOnEscape&&!c.isDefaultPrevented()&&c.keyCode&&c.keyCode===a.ui.keyCode.ESCAPE&&(b.close(c),c.preventDefault())}),a(window).bind("resize.dialog-overlay",a.ui.dialog.overlay.resize));var c=(this.oldInstances.pop()||a("<div></div>").addClass("ui-widget-overlay")).appendTo(document.body).css({width:this.width(),height:this.height()});return a.fn.bgiframe&&c.bgiframe(),this.instances.push(c),c},destroy:function(b){var c=a.inArray(b,this.instances);c!=-1&&this.oldInstances.push(this.instances.splice(c,1)[0]),this.instances.length===0&&a([document,window]).unbind(".dialog-overlay"),b.remove();var d=0;a.each(this.instances,function(){d=Math.max(d,this.css("z-index"))}),this.maxZ=d},height:function(){var b,c;return a.browser.msie&&a.browser.version<7?(b=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight),c=Math.max(document.documentElement.offsetHeight,document.body.offsetHeight),b<c?a(window).height()+"px":b+"px"):a(document).height()+"px"},width:function(){var b,c;return a.browser.msie?(b=Math.max(document.documentElement.scrollWidth,document.body.scrollWidth),c=Math.max(document.documentElement.offsetWidth,document.body.offsetWidth),b<c?a(window).width()+"px":b+"px"):a(document).width()+"px"},resize:function(){var b=a([]);a.each(a.ui.dialog.overlay.instances,function(){b=b.add(this)}),b.css({width:0,height:0}).css({width:a.ui.dialog.overlay.width(),height:a.ui.dialog.overlay.height()})}}),a.extend(a.ui.dialog.overlay.prototype,{destroy:function(){a.ui.dialog.overlay.destroy(this.$el)}})}(jQuery),function(a,b){a.ui=a.ui||{};var c=/left|center|right/,d=/top|center|bottom/,e="center",f={},g=a.fn.position,h=a.fn.offset;a.fn.position=function(b){if(!b||!b.of)return g.apply(this,arguments);b=a.extend({},b);var h=a(b.of),i=h[0],j=(b.collision||"flip").split(" "),k=b.offset?b.offset.split(" "):[0,0],l,m,n;return i.nodeType===9?(l=h.width(),m=h.height(),n={top:0,left:0}):i.setTimeout?(l=h.width(),m=h.height(),n={top:h.scrollTop(),left:h.scrollLeft()}):i.preventDefault?(b.at="left top",l=m=0,n={top:b.of.pageY,left:b.of.pageX}):(l=h.outerWidth(),m=h.outerHeight(),n=h.offset()),a.each(["my","at"],function(){var a=(b[this]||"").split(" ");a.length===1&&(a=c.test(a[0])?a.concat([e]):d.test(a[0])?[e].concat(a):[e,e]),a[0]=c.test(a[0])?a[0]:e,a[1]=d.test(a[1])?a[1]:e,b[this]=a}),j.length===1&&(j[1]=j[0]),k[0]=parseInt(k[0],10)||0,k.length===1&&(k[1]=k[0]),k[1]=parseInt(k[1],10)||0,b.at[0]==="right"?n.left+=l:b.at[0]===e&&(n.left+=l/2),b.at[1]==="bottom"?n.top+=m:b.at[1]===e&&(n.top+=m/2),n.left+=k[0],n.top+=k[1],this.each(function(){var c=a(this),d=c.outerWidth(),g=c.outerHeight(),h=parseInt(a.curCSS(this,"marginLeft",!0))||0,i=parseInt(a.curCSS(this,"marginTop",!0))||0,o=d+h+(parseInt(a.curCSS(this,"marginRight",!0))||0),p=g+i+(parseInt(a.curCSS(this,"marginBottom",!0))||0),q=a.extend({},n),r;b.my[0]==="right"?q.left-=d:b.my[0]===e&&(q.left-=d/2),b.my[1]==="bottom"?q.top-=g:b.my[1]===e&&(q.top-=g/2),f.fractions||(q.left=Math.round(q.left),q.top=Math.round(q.top)),r={left:q.left-h,top:q.top-i},a.each(["left","top"],function(c,e){a.ui.position[j[c]]&&a.ui.position[j[c]][e](q,{targetWidth:l,targetHeight:m,elemWidth:d,elemHeight:g,collisionPosition:r,collisionWidth:o,collisionHeight:p,offset:k,my:b.my,at:b.at})}),a.fn.bgiframe&&c.bgiframe(),c.offset(a.extend(q,{using:b.using}))})},a.ui.position={fit:{left:function(b,c){var d=a(window),e=c.collisionPosition.left+c.collisionWidth-d.width()-d.scrollLeft();b.left=e>0?b.left-e:Math.max(b.left-c.collisionPosition.left,b.left)},top:function(b,c){var d=a(window),e=c.collisionPosition.top+c.collisionHeight-d.height()-d.scrollTop();b.top=e>0?b.top-e:Math.max(b.top-c.collisionPosition.top,b.top)}},flip:{left:function(b,c){if(c.at[0]===e)return;var d=a(window),f=c.collisionPosition.left+c.collisionWidth-d.width()-d.scrollLeft(),g=c.my[0]==="left"?-c.elemWidth:c.my[0]==="right"?c.elemWidth:0,h=c.at[0]==="left"?c.targetWidth:-c.targetWidth,i=-2*c.offset[0];b.left+=c.collisionPosition.left<0?g+h+i:f>0?g+h+i:0},top:function(b,c){if(c.at[1]===e)return;var d=a(window),f=c.collisionPosition.top+c.collisionHeight-d.height()-d.scrollTop(),g=c.my[1]==="top"?-c.elemHeight:c.my[1]==="bottom"?c.elemHeight:0,h=c.at[1]==="top"?c.targetHeight:-c.targetHeight,i=-2*c.offset[1];b.top+=c.collisionPosition.top<0?g+h+i:f>0?g+h+i:0}}},a.offset.setOffset||(a.offset.setOffset=function(b,c){/static/.test(a.curCSS(b,"position"))&&(b.style.position="relative");var d=a(b),e=d.offset(),f=parseInt(a.curCSS(b,"top",!0),10)||0,g=parseInt(a.curCSS(b,"left",!0),10)||0,h={top:c.top-e.top+f,left:c.left-e.left+g};"using"in c?c.using.call(b,h):d.css(h)},a.fn.offset=function(b){var c=this[0];return!c||!c.ownerDocument?null:b?a.isFunction(b)?this.each(function(c){a(this).offset(b.call(this,c,a(this).offset()))}):this.each(function(){a.offset.setOffset(this,b)}):h.call(this)}),a.curCSS||(a.curCSS=a.css),function(){var b=document.getElementsByTagName("body")[0],c=document.createElement("div"),d,e,g,h,i;d=document.createElement(b?"div":"body"),g={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},b&&a.extend(g,{position:"absolute",left:"-1000px",top:"-1000px"});for(var j in g)d.style[j]=g[j];d.appendChild(c),e=b||document.documentElement,e.insertBefore(d,e.firstChild),c.style.cssText="position: absolute; left: 10.7432222px; top: 10.432325px; height: 30px; width: 201px;",h=a(c).offset(function(a,b){return b}).offset(),d.innerHTML="",e.removeChild(d),i=h.top+h.left+(b?2e3:0),f.fractions=i>21&&i<22}()}(jQuery),function(a,b){a.widget("ui.progressbar",{options:{value:0,max:100},min:0,_create:function(){this.element.addClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").attr({role:"progressbar","aria-valuemin":this.min,"aria-valuemax":this.options.max,"aria-valuenow":this._value()}),this.valueDiv=a("<div class='ui-progressbar-value ui-widget-header ui-corner-left'></div>").appendTo(this.element),this.oldValue=this._value(),this._refreshValue()},destroy:function(){this.element.removeClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow"),this.valueDiv.remove(),a.Widget.prototype.destroy.apply(this,arguments)},value:function(a){return a===b?this._value():(this._setOption("value",a),this)},_setOption:function(b,c){b==="value"&&(this.options.value=c,this._refreshValue(),this._value()===this.options.max&&this._trigger("complete")),a.Widget.prototype._setOption.apply(this,arguments)},_value:function(){var a=this.options.value;return typeof a!="number"&&(a=0),Math.min(this.options.max,Math.max(this.min,a))},_percentage:function(){return 100*this._value()/this.options.max},_refreshValue:function(){var a=this.value(),b=this._percentage();this.oldValue!==a&&(this.oldValue=a,this._trigger("change")),this.valueDiv.toggle(a>this.min).toggleClass("ui-corner-right",a===this.options.max).width(b.toFixed(0)+"%"),this.element.attr("aria-valuenow",a)}}),a.extend(a.ui.progressbar,{version:"1.8.23"})}(jQuery),function(a,b){var c=5;a.widget("ui.slider",a.ui.mouse,{widgetEventPrefix:"slide",options:{animate:!1,distance:0,max:100,min:0,orientation:"horizontal",range:!1,step:1,value:0,values:null},_create:function(){var b=this,d=this.options,e=this.element.find(".ui-slider-handle").addClass("ui-state-default ui-corner-all"),f="<a class='ui-slider-handle ui-state-default ui-corner-all' href='#'></a>",g=d.values&&d.values.length||1,h=[];this._keySliding=!1,this._mouseSliding=!1,this._animateOff=!0,this._handleIndex=null,this._detectOrientation(),this._mouseInit(),this.element.addClass("ui-slider ui-slider-"+this.orientation+" ui-widget"+" ui-widget-content"+" ui-corner-all"+(d.disabled?" ui-slider-disabled ui-disabled":"")),this.range=a([]),d.range&&(d.range===!0&&(d.values||(d.values=[this._valueMin(),this._valueMin()]),d.values.length&&d.values.length!==2&&(d.values=[d.values[0],d.values[0]])),this.range=a("<div></div>").appendTo(this.element).addClass("ui-slider-range ui-widget-header"+(d.range==="min"||d.range==="max"?" ui-slider-range-"+d.range:"")));for(var i=e.length;i<g;i+=1)h.push(f);this.handles=e.add(a(h.join("")).appendTo(b.element)),this.handle=this.handles.eq(0),this.handles.add(this.range).filter("a").click(function(a){a.preventDefault()}).hover(function(){d.disabled||a(this).addClass("ui-state-hover")},function(){a(this).removeClass("ui-state-hover")}).focus(function(){d.disabled?a(this).blur():(a(".ui-slider .ui-state-focus").removeClass("ui-state-focus"),a(this).addClass("ui-state-focus"))}).blur(function(){a(this).removeClass("ui-state-focus")}),this.handles.each(function(b){a(this).data("index.ui-slider-handle",b)}),this.handles.keydown(function(d){var e=a(this).data("index.ui-slider-handle"),f,g,h,i;if(b.options.disabled)return;switch(d.keyCode){case a.ui.keyCode.HOME:case a.ui.keyCode.END:case a.ui.keyCode.PAGE_UP:case a.ui.keyCode.PAGE_DOWN:case a.ui.keyCode.UP:case a.ui.keyCode.RIGHT:case a.ui.keyCode.DOWN:case a.ui.keyCode.LEFT:d.preventDefault();if(!b._keySliding){b._keySliding=!0,a(this).addClass("ui-state-active"),f=b._start(d,e);if(f===!1)return}}i=b.options.step,b.options.values&&b.options.values.length?g=h=b.values(e):g=h=b.value();switch(d.keyCode){case a.ui.keyCode.HOME:h=b._valueMin();break;case a.ui.keyCode.END:h=b._valueMax();break;case a.ui.keyCode.PAGE_UP:h=b._trimAlignValue(g+(b._valueMax()-b._valueMin())/c);break;case a.ui.keyCode.PAGE_DOWN:h=b._trimAlignValue(g-(b._valueMax()-b._valueMin())/c);break;case a.ui.keyCode.UP:case a.ui.keyCode.RIGHT:if(g===b._valueMax())return;h=b._trimAlignValue(g+i);break;case a.ui.keyCode.DOWN:case a.ui.keyCode.LEFT:if(g===b._valueMin())return;h=b._trimAlignValue(g-i)}b._slide(d,e,h)}).keyup(function(c){var d=a(this).data("index.ui-slider-handle");b._keySliding&&(b._keySliding=!1,b._stop(c,d),b._change(c,d),a(this).removeClass("ui-state-active"))}),this._refreshValue(),this._animateOff=!1},destroy:function(){return this.handles.remove(),this.range.remove(),this.element.removeClass("ui-slider ui-slider-horizontal ui-slider-vertical ui-slider-disabled ui-widget ui-widget-content ui-corner-all").removeData("slider").unbind(".slider"),this._mouseDestroy(),this},_mouseCapture:function(b){var c=this.options,d,e,f,g,h,i,j,k,l;return c.disabled?!1:(this.elementSize={width:this.element.outerWidth(),height:this.element.outerHeight()},this.elementOffset=this.element.offset(),d={x:b.pageX,y:b.pageY},e=this._normValueFromMouse(d),f=this._valueMax()-this._valueMin()+1,h=this,this.handles.each(function(b){var c=Math.abs(e-h.values(b));f>c&&(f=c,g=a(this),i=b)}),c.range===!0&&this.values(1)===c.min&&(i+=1,g=a(this.handles[i])),j=this._start(b,i),j===!1?!1:(this._mouseSliding=!0,h._handleIndex=i,g.addClass("ui-state-active").focus(),k=g.offset(),l=!a(b.target).parents().andSelf().is(".ui-slider-handle"),this._clickOffset=l?{left:0,top:0}:{left:b.pageX-k.left-g.width()/2,top:b.pageY-k.top-g.height()/2-(parseInt(g.css("borderTopWidth"),10)||0)-(parseInt(g.css("borderBottomWidth"),10)||0)+(parseInt(g.css("marginTop"),10)||0)},this.handles.hasClass("ui-state-hover")||this._slide(b,i,e),this._animateOff=!0,!0))},_mouseStart:function(a){return!0},_mouseDrag:function(a){var b={x:a.pageX,y:a.pageY},c=this._normValueFromMouse(b);return this._slide(a,this._handleIndex,c),!1},_mouseStop:function(a){return this.handles.removeClass("ui-state-active"),this._mouseSliding=!1,this._stop(a,this._handleIndex),this._change(a,this._handleIndex),this._handleIndex=null,this._clickOffset=null,this._animateOff=!1,!1},_detectOrientation:function(){this.orientation=this.options.orientation==="vertical"?"vertical":"horizontal"},_normValueFromMouse:function(a){var b,c,d,e,f;return this.orientation==="horizontal"?(b=this.elementSize.width,c=a.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)):(b=this.elementSize.height,c=a.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)),d=c/b,d>1&&(d=1),d<0&&(d=0),this.orientation==="vertical"&&(d=1-d),e=this._valueMax()-this._valueMin(),f=this._valueMin()+d*e,this._trimAlignValue(f)},_start:function(a,b){var c={handle:this.handles[b],value:this.value()};return this.options.values&&this.options.values.length&&(c.value=this.values(b),c.values=this.values()),this._trigger("start",a,c)},_slide:function(a,b,c){var d,e,f;this.options.values&&this.options.values.length?(d=this.values(b?0:1),this.options.values.length===2&&this.options.range===!0&&(b===0&&c>d||b===1&&c<d)&&(c=d),c!==this.values(b)&&(e=this.values(),e[b]=c,f=this._trigger("slide",a,{handle:this.handles[b],value:c,values:e}),d=this.values(b?0:1),f!==!1&&this.values(b,c,!0))):c!==this.value()&&(f=this._trigger("slide",a,{handle:this.handles[b],value:c}),f!==!1&&this.value(c))},_stop:function(a,b){var c={handle:this.handles[b],value:this.value()};this.options.values&&this.options.values.length&&(c.value=this.values(b),c.values=this.values()),this._trigger("stop",a,c)},_change:function(a,b){if(!this._keySliding&&!this._mouseSliding){var c={handle:this.handles[b],value:this.value()};this.options.values&&this.options.values.length&&(c.value=this.values(b),c.values=this.values()),this._trigger("change",a,c)}},value:function(a){if(arguments.length){this.options.value=this._trimAlignValue(a),this._refreshValue(),this._change(null,0);return}return this._value()},values:function(b,c){var d,e,f;if(arguments.length>1){this.options.values[b]=this._trimAlignValue(c),this._refreshValue(),this._change(null,b);return}if(!arguments.length)return this._values();if(!a.isArray(arguments[0]))return this.options.values&&this.options.values.length?this._values(b):this.value();d=this.options.values,e=arguments[0];for(f=0;f<d.length;f+=1)d[f]=this._trimAlignValue(e[f]),this._change(null,f);this._refreshValue()},_setOption:function(b,c){var d,e=0;a.isArray(this.options.values)&&(e=this.options.values.length),a.Widget.prototype._setOption.apply(this,arguments);switch(b){case"disabled":c?(this.handles.filter(".ui-state-focus").blur(),this.handles.removeClass("ui-state-hover"),this.handles.propAttr("disabled",!0),this.element.addClass("ui-disabled")):(this.handles.propAttr("disabled",!1),this.element.removeClass("ui-disabled"));break;case"orientation":this._detectOrientation(),this.element.removeClass("ui-slider-horizontal ui-slider-vertical").addClass("ui-slider-"+this.orientation),this._refreshValue();break;case"value":this._animateOff=!0,this._refreshValue(),this._change(null,0),this._animateOff=!1;break;case"values":this._animateOff=!0,this._refreshValue();for(d=0;d<e;d+=1)this._change(null,d);this._animateOff=!1}},_value:function(){var a=this.options.value;return a=this._trimAlignValue(a),a},_values:function(a){var b,c,d;if(arguments.length)return b=this.options.values[a],b=this._trimAlignValue(b),b;c=this.options.values.slice();for(d=0;d<c.length;d+=1)c[d]=this._trimAlignValue(c[d]);return c},_trimAlignValue:function(a){if(a<=this._valueMin())return this._valueMin();if(a>=this._valueMax())return this._valueMax();var b=this.options.step>0?this.options.step:1,c=(a-this._valueMin())%b,d=a-c;return Math.abs(c)*2>=b&&(d+=c>0?b:-b),parseFloat(d.toFixed(5))},_valueMin:function(){return this.options.min},_valueMax:function(){return this.options.max},_refreshValue:function(){var b=this.options.range,c=this.options,d=this,e=this._animateOff?!1:c.animate,f,g={},h,i,j,k;this.options.values&&this.options.values.length?this.handles.each(function(b,i){f=(d.values(b)-d._valueMin())/(d._valueMax()-d._valueMin())*100,g[d.orientation==="horizontal"?"left":"bottom"]=f+"%",a(this).stop(1,1)[e?"animate":"css"](g,c.animate),d.options.range===!0&&(d.orientation==="horizontal"?(b===0&&d.range.stop(1,1)[e?"animate":"css"]({left:f+"%"},c.animate),b===1&&d.range[e?"animate":"css"]({width:f-h+"%"},{queue:!1,duration:c.animate})):(b===0&&d.range.stop(1,1)[e?"animate":"css"]({bottom:f+"%"},c.animate),b===1&&d.range[e?"animate":"css"]({height:f-h+"%"},{queue:!1,duration:c.animate}))),h=f}):(i=this.value(),j=this._valueMin(),k=this._valueMax(),f=k!==j?(i-j)/(k-j)*100:0,g[d.orientation==="horizontal"?"left":"bottom"]=f+"%",this.handle.stop(1,1)[e?"animate":"css"](g,c.animate),b==="min"&&this.orientation==="horizontal"&&this.range.stop(1,1)[e?"animate":"css"]({width:f+"%"},c.animate),b==="max"&&this.orientation==="horizontal"&&this.range[e?"animate":"css"]({width:100-f+"%"},{queue:!1,duration:c.animate}),b==="min"&&this.orientation==="vertical"&&this.range.stop(1,1)[e?"animate":"css"]({height:f+"%"},c.animate),b==="max"&&this.orientation==="vertical"&&this.range[e?"animate":"css"]({height:100-f+"%"},{queue:!1,duration:c.animate}))}}),a.extend(a.ui.slider,{version:"1.8.23"})}(jQuery),function(a,b){function e(){return++c}function f(){return++d}var c=0,d=0;a.widget("ui.tabs",{options:{add:null,ajaxOptions:null,cache:!1,cookie:null,collapsible:!1,disable:null,disabled:[],enable:null,event:"click",fx:null,idPrefix:"ui-tabs-",load:null,panelTemplate:"<div></div>",remove:null,select:null,show:null,spinner:"<em>Loading&#8230;</em>",tabTemplate:"<li><a href='#{href}'><span>#{label}</span></a></li>"},_create:function(){this._tabify(!0)},_setOption:function(a,b){if(a=="selected"){if(this.options.collapsible&&b==this.options.selected)return;this.select(b)}else this.options[a]=b,this._tabify()},_tabId:function(a){return a.title&&a.title.replace(/\s/g,"_").replace(/[^\w\u00c0-\uFFFF-]/g,"")||this.options.idPrefix+e()},_sanitizeSelector:function(a){return a.replace(/:/g,"\\:")},_cookie:function(){var b=this.cookie||(this.cookie=this.options.cookie.name||"ui-tabs-"+f());return a.cookie.apply(null,[b].concat(a.makeArray(arguments)))},_ui:function(a,b){return{tab:a,panel:b,index:this.anchors.index(a)}},_cleanup:function(){this.lis.filter(".ui-state-processing").removeClass("ui-state-processing").find("span:data(label.tabs)").each(function(){var b=a(this);b.html(b.data("label.tabs")).removeData("label.tabs")})},_tabify:function(c){function m(b,c){b.css("display",""),!a.support.opacity&&c.opacity&&b[0].style.removeAttribute("filter")}var d=this,e=this.options,f=/^#.+/;this.list=this.element.find("ol,ul").eq(0),this.lis=a(" > li:has(a[href])",this.list),this.anchors=this.lis.map(function(){return a("a",this)[0]}),this.panels=a([]),this.anchors.each(function(b,c){var g=a(c).attr("href"),h=g.split("#")[0],i;h&&(h===location.toString().split("#")[0]||(i=a("base")[0])&&h===i.href)&&(g=c.hash,c.href=g);if(f.test(g))d.panels=d.panels.add(d.element.find(d._sanitizeSelector(g)));else if(g&&g!=="#"){a.data(c,"href.tabs",g),a.data(c,"load.tabs",g.replace(/#.*$/,""));var j=d._tabId(c);c.href="#"+j;var k=d.element.find("#"+j);k.length||(k=a(e.panelTemplate).attr("id",j).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").insertAfter(d.panels[b-1]||d.list),k.data("destroy.tabs",!0)),d.panels=d.panels.add(k)}else e.disabled.push(b)}),c?(this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all"),this.list.addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all"),this.lis.addClass("ui-state-default ui-corner-top"),this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom"),e.selected===b?(location.hash&&this.anchors.each(function(a,b){if(b.hash==location.hash)return e.selected=a,!1}),typeof e.selected!="number"&&e.cookie&&(e.selected=parseInt(d._cookie(),10)),typeof e.selected!="number"&&this.lis.filter(".ui-tabs-selected").length&&(e.selected=this.lis.index(this.lis.filter(".ui-tabs-selected"))),e.selected=e.selected||(this.lis.length?0:-1)):e.selected===null&&(e.selected=-1),e.selected=e.selected>=0&&this.anchors[e.selected]||e.selected<0?e.selected:0,e.disabled=a.unique(e.disabled.concat(a.map(this.lis.filter(".ui-state-disabled"),function(a,b){return d.lis.index(a)}))).sort(),a.inArray(e.selected,e.disabled)!=-1&&e.disabled.splice(a.inArray(e.selected,e.disabled),1),this.panels.addClass("ui-tabs-hide"),this.lis.removeClass("ui-tabs-selected ui-state-active"),e.selected>=0&&this.anchors.length&&(d.element.find(d._sanitizeSelector(d.anchors[e.selected].hash)).removeClass("ui-tabs-hide"),this.lis.eq(e.selected).addClass("ui-tabs-selected ui-state-active"),d.element.queue("tabs",function(){d._trigger("show",null,d._ui(d.anchors[e.selected],d.element.find(d._sanitizeSelector(d.anchors[e.selected].hash))[0]))}),this.load(e.selected)),a(window).bind("unload",function(){d.lis.add(d.anchors).unbind(".tabs"),d.lis=d.anchors=d.panels=null})):e.selected=this.lis.index(this.lis.filter(".ui-tabs-selected")),this.element[e.collapsible?"addClass":"removeClass"]("ui-tabs-collapsible"),e.cookie&&this._cookie(e.selected,e.cookie);for(var g=0,h;h=this.lis[g];g++)a(h)[a.inArray(g,e.disabled)!=-1&&!a(h).hasClass("ui-tabs-selected")?"addClass":"removeClass"]("ui-state-disabled");e.cache===!1&&this.anchors.removeData("cache.tabs"),this.lis.add(this.anchors).unbind(".tabs");if(e.event!=="mouseover"){var i=function(a,b){b.is(":not(.ui-state-disabled)")&&b.addClass("ui-state-"+a)},j=function(a,b){b.removeClass("ui-state-"+a)};this.lis.bind("mouseover.tabs",function(){i("hover",a(this))}),this.lis.bind("mouseout.tabs",function(){j("hover",a(this))}),this.anchors.bind("focus.tabs",function(){i("focus",a(this).closest("li"))}),this.anchors.bind("blur.tabs",function(){j("focus",a(this).closest("li"))})}var k,l;e.fx&&(a.isArray(e.fx)?(k=e.fx[0],l=e.fx[1]):k=l=e.fx);var n=l?function(b,c){a(b).closest("li").addClass("ui-tabs-selected ui-state-active"),c.hide().removeClass("ui-tabs-hide").animate(l,l.duration||"normal",function(){m(c,l),d._trigger("show",null,d._ui(b,c[0]))})}:function(b,c){a(b).closest("li").addClass("ui-tabs-selected ui-state-active"),c.removeClass("ui-tabs-hide"),d._trigger("show",null,d._ui(b,c[0]))},o=k?function(a,b){b.animate(k,k.duration||"normal",function(){d.lis.removeClass("ui-tabs-selected ui-state-active"),b.addClass("ui-tabs-hide"),m(b,k),d.element.dequeue("tabs")})}:function(a,b,c){d.lis.removeClass("ui-tabs-selected ui-state-active"),b.addClass("ui-tabs-hide"),d.element.dequeue("tabs")};this.anchors.bind(e.event+".tabs",function(){var b=this,c=a(b).closest("li"),f=d.panels.filter(":not(.ui-tabs-hide)"),g=d.element.find(d._sanitizeSelector(b.hash));if(c.hasClass("ui-tabs-selected")&&!e.collapsible||c.hasClass("ui-state-disabled")||c.hasClass("ui-state-processing")||d.panels.filter(":animated").length||d._trigger("select",null,d._ui(this,g[0]))===!1)return this.blur(),!1;e.selected=d.anchors.index(this),d.abort();if(e.collapsible){if(c.hasClass("ui-tabs-selected"))return e.selected=-1,e.cookie&&d._cookie(e.selected,e.cookie),d.element.queue("tabs",function(){o(b,f)}).dequeue("tabs"),this.blur(),!1;if(!f.length)return e.cookie&&d._cookie(e.selected,e.cookie),d.element.queue("tabs",function(){n(b,g)}),d.load(d.anchors.index(this)),this.blur(),!1}e.cookie&&d._cookie(e.selected,e.cookie);if(g.length)f.length&&d.element.queue("tabs",function(){o(b,f)}),d.element.queue("tabs",function(){n(b,g)}),d.load(d.anchors.index(this));else throw"jQuery UI Tabs: Mismatching fragment identifier.";a.browser.msie&&this.blur()}),this.anchors.bind("click.tabs",function(){return!1})},_getIndex:function(a){return typeof a=="string"&&(a=this.anchors.index(this.anchors.filter("[href$='"+a+"']"))),a},destroy:function(){var b=this.options;return this.abort(),this.element.unbind(".tabs").removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible").removeData("tabs"),this.list.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all"),this.anchors.each(function(){var b=a.data(this,"href.tabs");b&&(this.href=b);var c=a(this).unbind(".tabs");a.each(["href","load","cache"],function(a,b){c.removeData(b+".tabs")})}),this.lis.unbind(".tabs").add(this.panels).each(function(){a.data(this,"destroy.tabs")?a(this).remove():a(this).removeClass(["ui-state-default","ui-corner-top","ui-tabs-selected","ui-state-active","ui-state-hover","ui-state-focus","ui-state-disabled","ui-tabs-panel","ui-widget-content","ui-corner-bottom","ui-tabs-hide"].join(" "))}),b.cookie&&this._cookie(null,b.cookie),this},add:function(c,d,e){e===b&&(e=this.anchors.length);var f=this,g=this.options,h=a(g.tabTemplate.replace(/#\{href\}/g,c).replace(/#\{label\}/g,d)),i=c.indexOf("#")?this._tabId(a("a",h)[0]):c.replace("#","");h.addClass("ui-state-default ui-corner-top").data("destroy.tabs",!0);var j=f.element.find("#"+i);return j.length||(j=a(g.panelTemplate).attr("id",i).data("destroy.tabs",!0)),j.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide"),e>=this.lis.length?(h.appendTo(this.list),j.appendTo(this.list[0].parentNode)):(h.insertBefore(this.lis[e]),j.insertBefore(this.panels[e])),g.disabled=a.map(g.disabled,function(a,b){return a>=e?++a:a}),this._tabify(),this.anchors.length==1&&(g.selected=0,h.addClass("ui-tabs-selected ui-state-active"),j.removeClass("ui-tabs-hide"),this.element.queue("tabs",function(){f._trigger("show",null,f._ui(f.anchors[0],f.panels[0]))}),this.load(0)),this._trigger("add",null,this._ui(this.anchors[e],this.panels[e])),this},remove:function(b){b=this._getIndex(b);var c=this.options,d=this.lis.eq(b).remove(),e=this.panels.eq(b).remove();return d.hasClass("ui-tabs-selected")&&this.anchors.length>1&&this.select(b+(b+1<this.anchors.length?1:-1)),c.disabled=a.map(a.grep(c.disabled,function(a,c){return a!=b}),function(a,c){return a>=b?--a:a}),this._tabify(),this._trigger("remove",null,this._ui(d.find("a")[0],e[0])),this},enable:function(b){b=this._getIndex(b);var c=this.options;if(a.inArray(b,c.disabled)==-1)return;return this.lis.eq(b).removeClass("ui-state-disabled"),c.disabled=a.grep(c.disabled,function(a,c){return a!=b}),this._trigger("enable",null,this._ui(this.anchors[b],this.panels[b])),this},disable:function(a){a=this._getIndex(a);var b=this,c=this.options;return a!=c.selected&&(this.lis.eq(a).addClass("ui-state-disabled"),c.disabled.push(a),c.disabled.sort(),this._trigger("disable",null,this._ui(this.anchors[a],this.panels[a]))),this},select:function(a){a=this._getIndex(a);if(a==-1)if(this.options.collapsible&&this.options.selected!=-1)a=this.options.selected;else return this;return this.anchors.eq(a).trigger(this.options.event+".tabs"),this},load:function(b){b=this._getIndex(b);var c=this,d=this.options,e=this.anchors.eq(b)[0],f=a.data(e,"load.tabs");this.abort();if(!f||this.element.queue("tabs").length!==0&&a.data(e,"cache.tabs")){this.element.dequeue("tabs");return}this.lis.eq(b).addClass("ui-state-processing");if(d.spinner){var g=a("span",e);g.data("label.tabs",g.html()).html(d.spinner)}return this.xhr=a.ajax(a.extend({},d.ajaxOptions,{url:f,success:function(f,g){c.element.find(c._sanitizeSelector(e.hash)).html(f),c._cleanup(),d.cache&&a.data(e,"cache.tabs",!0),c._trigger("load",null,c._ui(c.anchors[b],c.panels[b]));try{d.ajaxOptions.success(f,g)}catch(h){}},error:function(a,f,g){c._cleanup(),c._trigger("load",null,c._ui(c.anchors[b],c.panels[b]));try{d.ajaxOptions.error(a,f,b,e)}catch(g){}}})),c.element.dequeue("tabs"),this},abort:function(){return this.element.queue([]),this.panels.stop(!1,!0),this.element.queue("tabs",this.element.queue("tabs").splice(-2,2)),this.xhr&&(this.xhr.abort(),delete this.xhr),this._cleanup(),this},url:function(a,b){return this.anchors.eq(a).removeData("cache.tabs").data("load.tabs",b),this},length:function(){return this.anchors.length}}),a.extend(a.ui.tabs,{version:"1.8.23"}),a.extend(a.ui.tabs.prototype,{rotation:null,rotate:function(a,b){var c=this,d=this.options,e=c._rotate||(c._rotate=function(b){clearTimeout(c.rotation),c.rotation=setTimeout(function(){var a=d.selected;c.select(++a<c.anchors.length?a:0)},a),b&&b.stopPropagation()}),f=c._unrotate||(c._unrotate=b?function(a){e()}:function(a){a.clientX&&c.rotate(null)});return a?(this.element.bind("tabsshow",e),this.anchors.bind(d.event+".tabs",f),e()):(clearTimeout(c.rotation),this.element.unbind("tabsshow",e),this.anchors.unbind(d.event+".tabs",f),delete this._rotate,delete this._unrotate),this}})}(jQuery);
    \ No newline at end of file
    diff --git a/lib/jquery-ui-1.9.2-min.js b/lib/jquery-ui-1.9.2-min.js
    new file mode 100644
    index 000000000..bc013548d
    --- /dev/null
    +++ b/lib/jquery-ui-1.9.2-min.js
    @@ -0,0 +1,5 @@
    +/*! jQuery UI - v1.9.2 - 2012-11-23
    +* http://jqueryui.com
    +* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.draggable.js, jquery.ui.droppable.js, jquery.ui.resizable.js, jquery.ui.selectable.js, jquery.ui.sortable.js, jquery.ui.effect.js, jquery.ui.accordion.js, jquery.ui.autocomplete.js, jquery.ui.button.js, jquery.ui.datepicker.js, jquery.ui.dialog.js, jquery.ui.effect-blind.js, jquery.ui.effect-bounce.js, jquery.ui.effect-clip.js, jquery.ui.effect-drop.js, jquery.ui.effect-explode.js, jquery.ui.effect-fade.js, jquery.ui.effect-fold.js, jquery.ui.effect-highlight.js, jquery.ui.effect-pulsate.js, jquery.ui.effect-scale.js, jquery.ui.effect-shake.js, jquery.ui.effect-slide.js, jquery.ui.effect-transfer.js, jquery.ui.menu.js, jquery.ui.position.js, jquery.ui.progressbar.js, jquery.ui.slider.js, jquery.ui.spinner.js, jquery.ui.tabs.js, jquery.ui.tooltip.js
    +* Copyright 2012 jQuery Foundation and other contributors; Licensed MIT */
    +(function(e,t){function i(t,n){var r,i,o,u=t.nodeName.toLowerCase();return"area"===u?(r=t.parentNode,i=r.name,!t.href||!i||r.nodeName.toLowerCase()!=="map"?!1:(o=e("img[usemap=#"+i+"]")[0],!!o&&s(o))):(/input|select|textarea|button|object/.test(u)?!t.disabled:"a"===u?t.href||n:n)&&s(t)}function s(t){return e.expr.filters.visible(t)&&!e(t).parents().andSelf().filter(function(){return e.css(this,"visibility")==="hidden"}).length}var n=0,r=/^ui-id-\d+$/;e.ui=e.ui||{};if(e.ui.version)return;e.extend(e.ui,{version:"1.9.2",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),e.fn.extend({_focus:e.fn.focus,focus:function(t,n){return typeof t=="number"?this.each(function(){var r=this;setTimeout(function(){e(r).focus(),n&&n.call(r)},t)}):this._focus.apply(this,arguments)},scrollParent:function(){var t;return e.ui.ie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?t=this.parents().filter(function(){return/(relative|absolute|fixed)/.test(e.css(this,"position"))&&/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0):t=this.parents().filter(function(){return/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0),/fixed/.test(this.css("position"))||!t.length?e(document):t},zIndex:function(n){if(n!==t)return this.css("zIndex",n);if(this.length){var r=e(this[0]),i,s;while(r.length&&r[0]!==document){i=r.css("position");if(i==="absolute"||i==="relative"||i==="fixed"){s=parseInt(r.css("zIndex"),10);if(!isNaN(s)&&s!==0)return s}r=r.parent()}}return 0},uniqueId:function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++n)})},removeUniqueId:function(){return this.each(function(){r.test(this.id)&&e(this).removeAttr("id")})}}),e.extend(e.expr[":"],{data:e.expr.createPseudo?e.expr.createPseudo(function(t){return function(n){return!!e.data(n,t)}}):function(t,n,r){return!!e.data(t,r[3])},focusable:function(t){return i(t,!isNaN(e.attr(t,"tabindex")))},tabbable:function(t){var n=e.attr(t,"tabindex"),r=isNaN(n);return(r||n>=0)&&i(t,!r)}}),e(function(){var t=document.body,n=t.appendChild(n=document.createElement("div"));n.offsetHeight,e.extend(n.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0}),e.support.minHeight=n.offsetHeight===100,e.support.selectstart="onselectstart"in n,t.removeChild(n).style.display="none"}),e("<a>").outerWidth(1).jquery||e.each(["Width","Height"],function(n,r){function u(t,n,r,s){return e.each(i,function(){n-=parseFloat(e.css(t,"padding"+this))||0,r&&(n-=parseFloat(e.css(t,"border"+this+"Width"))||0),s&&(n-=parseFloat(e.css(t,"margin"+this))||0)}),n}var i=r==="Width"?["Left","Right"]:["Top","Bottom"],s=r.toLowerCase(),o={innerWidth:e.fn.innerWidth,innerHeight:e.fn.innerHeight,outerWidth:e.fn.outerWidth,outerHeight:e.fn.outerHeight};e.fn["inner"+r]=function(n){return n===t?o["inner"+r].call(this):this.each(function(){e(this).css(s,u(this,n)+"px")})},e.fn["outer"+r]=function(t,n){return typeof t!="number"?o["outer"+r].call(this,t):this.each(function(){e(this).css(s,u(this,t,!0,n)+"px")})}}),e("<a>").data("a-b","a").removeData("a-b").data("a-b")&&(e.fn.removeData=function(t){return function(n){return arguments.length?t.call(this,e.camelCase(n)):t.call(this)}}(e.fn.removeData)),function(){var t=/msie ([\w.]+)/.exec(navigator.userAgent.toLowerCase())||[];e.ui.ie=t.length?!0:!1,e.ui.ie6=parseFloat(t[1],10)===6}(),e.fn.extend({disableSelection:function(){return this.bind((e.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(e){e.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),e.extend(e.ui,{plugin:{add:function(t,n,r){var i,s=e.ui[t].prototype;for(i in r)s.plugins[i]=s.plugins[i]||[],s.plugins[i].push([n,r[i]])},call:function(e,t,n){var r,i=e.plugins[t];if(!i||!e.element[0].parentNode||e.element[0].parentNode.nodeType===11)return;for(r=0;r<i.length;r++)e.options[i[r][0]]&&i[r][1].apply(e.element,n)}},contains:e.contains,hasScroll:function(t,n){if(e(t).css("overflow")==="hidden")return!1;var r=n&&n==="left"?"scrollLeft":"scrollTop",i=!1;return t[r]>0?!0:(t[r]=1,i=t[r]>0,t[r]=0,i)},isOverAxis:function(e,t,n){return e>t&&e<t+n},isOver:function(t,n,r,i,s,o){return e.ui.isOverAxis(t,r,s)&&e.ui.isOverAxis(n,i,o)}})})(jQuery),function(e,t){var n=0,r=Array.prototype.slice,i=e.cleanData;e.cleanData=function(t){for(var n=0,r;(r=t[n])!=null;n++)try{e(r).triggerHandler("remove")}catch(s){}i(t)},e.widget=function(t,n,r){var i,s,o,u,a=t.split(".")[0];t=t.split(".")[1],i=a+"-"+t,r||(r=n,n=e.Widget),e.expr[":"][i.toLowerCase()]=function(t){return!!e.data(t,i)},e[a]=e[a]||{},s=e[a][t],o=e[a][t]=function(e,t){if(!this._createWidget)return new o(e,t);arguments.length&&this._createWidget(e,t)},e.extend(o,s,{version:r.version,_proto:e.extend({},r),_childConstructors:[]}),u=new n,u.options=e.widget.extend({},u.options),e.each(r,function(t,i){e.isFunction(i)&&(r[t]=function(){var e=function(){return n.prototype[t].apply(this,arguments)},r=function(e){return n.prototype[t].apply(this,e)};return function(){var t=this._super,n=this._superApply,s;return this._super=e,this._superApply=r,s=i.apply(this,arguments),this._super=t,this._superApply=n,s}}())}),o.prototype=e.widget.extend(u,{widgetEventPrefix:s?u.widgetEventPrefix:t},r,{constructor:o,namespace:a,widgetName:t,widgetBaseClass:i,widgetFullName:i}),s?(e.each(s._childConstructors,function(t,n){var r=n.prototype;e.widget(r.namespace+"."+r.widgetName,o,n._proto)}),delete s._childConstructors):n._childConstructors.push(o),e.widget.bridge(t,o)},e.widget.extend=function(n){var i=r.call(arguments,1),s=0,o=i.length,u,a;for(;s<o;s++)for(u in i[s])a=i[s][u],i[s].hasOwnProperty(u)&&a!==t&&(e.isPlainObject(a)?n[u]=e.isPlainObject(n[u])?e.widget.extend({},n[u],a):e.widget.extend({},a):n[u]=a);return n},e.widget.bridge=function(n,i){var s=i.prototype.widgetFullName||n;e.fn[n]=function(o){var u=typeof o=="string",a=r.call(arguments,1),f=this;return o=!u&&a.length?e.widget.extend.apply(null,[o].concat(a)):o,u?this.each(function(){var r,i=e.data(this,s);if(!i)return e.error("cannot call methods on "+n+" prior to initialization; "+"attempted to call method '"+o+"'");if(!e.isFunction(i[o])||o.charAt(0)==="_")return e.error("no such method '"+o+"' for "+n+" widget instance");r=i[o].apply(i,a);if(r!==i&&r!==t)return f=r&&r.jquery?f.pushStack(r.get()):r,!1}):this.each(function(){var t=e.data(this,s);t?t.option(o||{})._init():e.data(this,s,new i(o,this))}),f}},e.Widget=function(){},e.Widget._childConstructors=[],e.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"<div>",options:{disabled:!1,create:null},_createWidget:function(t,r){r=e(r||this.defaultElement||this)[0],this.element=e(r),this.uuid=n++,this.eventNamespace="."+this.widgetName+this.uuid,this.options=e.widget.extend({},this.options,this._getCreateOptions(),t),this.bindings=e(),this.hoverable=e(),this.focusable=e(),r!==this&&(e.data(r,this.widgetName,this),e.data(r,this.widgetFullName,this),this._on(!0,this.element,{remove:function(e){e.target===r&&this.destroy()}}),this.document=e(r.style?r.ownerDocument:r.document||r),this.window=e(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:e.noop,_getCreateEventData:e.noop,_create:e.noop,_init:e.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(e.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled "+"ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:e.noop,widget:function(){return this.element},option:function(n,r){var i=n,s,o,u;if(arguments.length===0)return e.widget.extend({},this.options);if(typeof n=="string"){i={},s=n.split("."),n=s.shift();if(s.length){o=i[n]=e.widget.extend({},this.options[n]);for(u=0;u<s.length-1;u++)o[s[u]]=o[s[u]]||{},o=o[s[u]];n=s.pop();if(r===t)return o[n]===t?null:o[n];o[n]=r}else{if(r===t)return this.options[n]===t?null:this.options[n];i[n]=r}}return this._setOptions(i),this},_setOptions:function(e){var t;for(t in e)this._setOption(t,e[t]);return this},_setOption:function(e,t){return this.options[e]=t,e==="disabled"&&(this.widget().toggleClass(this.widgetFullName+"-disabled ui-state-disabled",!!t).attr("aria-disabled",t),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")),this},enable:function(){return this._setOption("disabled",!1)},disable:function(){return this._setOption("disabled",!0)},_on:function(t,n,r){var i,s=this;typeof t!="boolean"&&(r=n,n=t,t=!1),r?(n=i=e(n),this.bindings=this.bindings.add(n)):(r=n,n=this.element,i=this.widget()),e.each(r,function(r,o){function u(){if(!t&&(s.options.disabled===!0||e(this).hasClass("ui-state-disabled")))return;return(typeof o=="string"?s[o]:o).apply(s,arguments)}typeof o!="string"&&(u.guid=o.guid=o.guid||u.guid||e.guid++);var a=r.match(/^(\w+)\s*(.*)$/),f=a[1]+s.eventNamespace,l=a[2];l?i.delegate(l,f,u):n.bind(f,u)})},_off:function(e,t){t=(t||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,e.unbind(t).undelegate(t)},_delay:function(e,t){function n(){return(typeof e=="string"?r[e]:e).apply(r,arguments)}var r=this;return setTimeout(n,t||0)},_hoverable:function(t){this.hoverable=this.hoverable.add(t),this._on(t,{mouseenter:function(t){e(t.currentTarget).addClass("ui-state-hover")},mouseleave:function(t){e(t.currentTarget).removeClass("ui-state-hover")}})},_focusable:function(t){this.focusable=this.focusable.add(t),this._on(t,{focusin:function(t){e(t.currentTarget).addClass("ui-state-focus")},focusout:function(t){e(t.currentTarget).removeClass("ui-state-focus")}})},_trigger:function(t,n,r){var i,s,o=this.options[t];r=r||{},n=e.Event(n),n.type=(t===this.widgetEventPrefix?t:this.widgetEventPrefix+t).toLowerCase(),n.target=this.element[0],s=n.originalEvent;if(s)for(i in s)i in n||(n[i]=s[i]);return this.element.trigger(n,r),!(e.isFunction(o)&&o.apply(this.element[0],[n].concat(r))===!1||n.isDefaultPrevented())}},e.each({show:"fadeIn",hide:"fadeOut"},function(t,n){e.Widget.prototype["_"+t]=function(r,i,s){typeof i=="string"&&(i={effect:i});var o,u=i?i===!0||typeof i=="number"?n:i.effect||n:t;i=i||{},typeof i=="number"&&(i={duration:i}),o=!e.isEmptyObject(i),i.complete=s,i.delay&&r.delay(i.delay),o&&e.effects&&(e.effects.effect[u]||e.uiBackCompat!==!1&&e.effects[u])?r[t](i):u!==t&&r[u]?r[u](i.duration,i.easing,s):r.queue(function(n){e(this)[t](),s&&s.call(r[0]),n()})}}),e.uiBackCompat!==!1&&(e.Widget.prototype._getCreateOptions=function(){return e.metadata&&e.metadata.get(this.element[0])[this.widgetName]})}(jQuery),function(e,t){var n=!1;e(document).mouseup(function(e){n=!1}),e.widget("ui.mouse",{version:"1.9.2",options:{cancel:"input,textarea,button,select,option",distance:1,delay:0},_mouseInit:function(){var t=this;this.element.bind("mousedown."+this.widgetName,function(e){return t._mouseDown(e)}).bind("click."+this.widgetName,function(n){if(!0===e.data(n.target,t.widgetName+".preventClickEvent"))return e.removeData(n.target,t.widgetName+".preventClickEvent"),n.stopImmediatePropagation(),!1}),this.started=!1},_mouseDestroy:function(){this.element.unbind("."+this.widgetName),this._mouseMoveDelegate&&e(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(t){if(n)return;this._mouseStarted&&this._mouseUp(t),this._mouseDownEvent=t;var r=this,i=t.which===1,s=typeof this.options.cancel=="string"&&t.target.nodeName?e(t.target).closest(this.options.cancel).length:!1;if(!i||s||!this._mouseCapture(t))return!0;this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){r.mouseDelayMet=!0},this.options.delay));if(this._mouseDistanceMet(t)&&this._mouseDelayMet(t)){this._mouseStarted=this._mouseStart(t)!==!1;if(!this._mouseStarted)return t.preventDefault(),!0}return!0===e.data(t.target,this.widgetName+".preventClickEvent")&&e.removeData(t.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(e){return r._mouseMove(e)},this._mouseUpDelegate=function(e){return r._mouseUp(e)},e(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate),t.preventDefault(),n=!0,!0},_mouseMove:function(t){return!e.ui.ie||document.documentMode>=9||!!t.button?this._mouseStarted?(this._mouseDrag(t),t.preventDefault()):(this._mouseDistanceMet(t)&&this._mouseDelayMet(t)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,t)!==!1,this._mouseStarted?this._mouseDrag(t):this._mouseUp(t)),!this._mouseStarted):this._mouseUp(t)},_mouseUp:function(t){return e(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,t.target===this._mouseDownEvent.target&&e.data(t.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(t)),!1},_mouseDistanceMet:function(e){return Math.max(Math.abs(this._mouseDownEvent.pageX-e.pageX),Math.abs(this._mouseDownEvent.pageY-e.pageY))>=this.options.distance},_mouseDelayMet:function(e){return this.mouseDelayMet},_mouseStart:function(e){},_mouseDrag:function(e){},_mouseStop:function(e){},_mouseCapture:function(e){return!0}})}(jQuery),function(e,t){e.widget("ui.draggable",e.ui.mouse,{version:"1.9.2",widgetEventPrefix:"drag",options:{addClasses:!0,appendTo:"parent",axis:!1,connectToSortable:!1,containment:!1,cursor:"auto",cursorAt:!1,grid:!1,handle:!1,helper:"original",iframeFix:!1,opacity:!1,refreshPositions:!1,revert:!1,revertDuration:500,scope:"default",scroll:!0,scrollSensitivity:20,scrollSpeed:20,snap:!1,snapMode:"both",snapTolerance:20,stack:!1,zIndex:!1},_create:function(){this.options.helper=="original"&&!/^(?:r|a|f)/.test(this.element.css("position"))&&(this.element[0].style.position="relative"),this.options.addClasses&&this.element.addClass("ui-draggable"),this.options.disabled&&this.element.addClass("ui-draggable-disabled"),this._mouseInit()},_destroy:function(){this.element.removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled"),this._mouseDestroy()},_mouseCapture:function(t){var n=this.options;return this.helper||n.disabled||e(t.target).is(".ui-resizable-handle")?!1:(this.handle=this._getHandle(t),this.handle?(e(n.iframeFix===!0?"iframe":n.iframeFix).each(function(){e('<div class="ui-draggable-iframeFix" style="background: #fff;"></div>').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1e3}).css(e(this).offset()).appendTo("body")}),!0):!1)},_mouseStart:function(t){var n=this.options;return this.helper=this._createHelper(t),this.helper.addClass("ui-draggable-dragging"),this._cacheHelperProportions(),e.ui.ddmanager&&(e.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(),this.offset=this.positionAbs=this.element.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},e.extend(this.offset,{click:{left:t.pageX-this.offset.left,top:t.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.originalPosition=this.position=this._generatePosition(t),this.originalPageX=t.pageX,this.originalPageY=t.pageY,n.cursorAt&&this._adjustOffsetFromHelper(n.cursorAt),n.containment&&this._setContainment(),this._trigger("start",t)===!1?(this._clear(),!1):(this._cacheHelperProportions(),e.ui.ddmanager&&!n.dropBehaviour&&e.ui.ddmanager.prepareOffsets(this,t),this._mouseDrag(t,!0),e.ui.ddmanager&&e.ui.ddmanager.dragStart(this,t),!0)},_mouseDrag:function(t,n){this.position=this._generatePosition(t),this.positionAbs=this._convertPositionTo("absolute");if(!n){var r=this._uiHash();if(this._trigger("drag",t,r)===!1)return this._mouseUp({}),!1;this.position=r.position}if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";return e.ui.ddmanager&&e.ui.ddmanager.drag(this,t),!1},_mouseStop:function(t){var n=!1;e.ui.ddmanager&&!this.options.dropBehaviour&&(n=e.ui.ddmanager.drop(this,t)),this.dropped&&(n=this.dropped,this.dropped=!1);var r=this.element[0],i=!1;while(r&&(r=r.parentNode))r==document&&(i=!0);if(!i&&this.options.helper==="original")return!1;if(this.options.revert=="invalid"&&!n||this.options.revert=="valid"&&n||this.options.revert===!0||e.isFunction(this.options.revert)&&this.options.revert.call(this.element,n)){var s=this;e(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){s._trigger("stop",t)!==!1&&s._clear()})}else this._trigger("stop",t)!==!1&&this._clear();return!1},_mouseUp:function(t){return e("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)}),e.ui.ddmanager&&e.ui.ddmanager.dragStop(this,t),e.ui.mouse.prototype._mouseUp.call(this,t)},cancel:function(){return this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear(),this},_getHandle:function(t){var n=!this.options.handle||!e(this.options.handle,this.element).length?!0:!1;return e(this.options.handle,this.element).find("*").andSelf().each(function(){this==t.target&&(n=!0)}),n},_createHelper:function(t){var n=this.options,r=e.isFunction(n.helper)?e(n.helper.apply(this.element[0],[t])):n.helper=="clone"?this.element.clone().removeAttr("id"):this.element;return r.parents("body").length||r.appendTo(n.appendTo=="parent"?this.element[0].parentNode:n.appendTo),r[0]!=this.element[0]&&!/(fixed|absolute)/.test(r.css("position"))&&r.css("position","absolute"),r},_adjustOffsetFromHelper:function(t){typeof t=="string"&&(t=t.split(" ")),e.isArray(t)&&(t={left:+t[0],top:+t[1]||0}),"left"in t&&(this.offset.click.left=t.left+this.margins.left),"right"in t&&(this.offset.click.left=this.helperProportions.width-t.right+this.margins.left),"top"in t&&(this.offset.click.top=t.top+this.margins.top),"bottom"in t&&(this.offset.click.top=this.helperProportions.height-t.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var t=this.offsetParent.offset();this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&e.contains(this.scrollParent[0],this.offsetParent[0])&&(t.left+=this.scrollParent.scrollLeft(),t.top+=this.scrollParent.scrollTop());if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&e.ui.ie)t={top:0,left:0};return{top:t.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:t.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var e=this.element.position();return{top:e.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:e.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var t=this.options;t.containment=="parent"&&(t.containment=this.helper[0].parentNode);if(t.containment=="document"||t.containment=="window")this.containment=[t.containment=="document"?0:e(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,t.containment=="document"?0:e(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,(t.containment=="document"?0:e(window).scrollLeft())+e(t.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(t.containment=="document"?0:e(window).scrollTop())+(e(t.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(t.containment)&&t.containment.constructor!=Array){var n=e(t.containment),r=n[0];if(!r)return;var i=n.offset(),s=e(r).css("overflow")!="hidden";this.containment=[(parseInt(e(r).css("borderLeftWidth"),10)||0)+(parseInt(e(r).css("paddingLeft"),10)||0),(parseInt(e(r).css("borderTopWidth"),10)||0)+(parseInt(e(r).css("paddingTop"),10)||0),(s?Math.max(r.scrollWidth,r.offsetWidth):r.offsetWidth)-(parseInt(e(r).css("borderLeftWidth"),10)||0)-(parseInt(e(r).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(s?Math.max(r.scrollHeight,r.offsetHeight):r.offsetHeight)-(parseInt(e(r).css("borderTopWidth"),10)||0)-(parseInt(e(r).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom],this.relative_container=n}else t.containment.constructor==Array&&(this.containment=t.containment)},_convertPositionTo:function(t,n){n||(n=this.position);var r=t=="absolute"?1:-1,i=this.options,s=this.cssPosition!="absolute"||this.scrollParent[0]!=document&&!!e.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,o=/(html|body)/i.test(s[0].tagName);return{top:n.top+this.offset.relative.top*r+this.offset.parent.top*r-(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():o?0:s.scrollTop())*r,left:n.left+this.offset.relative.left*r+this.offset.parent.left*r-(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():o?0:s.scrollLeft())*r}},_generatePosition:function(t){var n=this.options,r=this.cssPosition!="absolute"||this.scrollParent[0]!=document&&!!e.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,i=/(html|body)/i.test(r[0].tagName),s=t.pageX,o=t.pageY;if(this.originalPosition){var u;if(this.containment){if(this.relative_container){var a=this.relative_container.offset();u=[this.containment[0]+a.left,this.containment[1]+a.top,this.containment[2]+a.left,this.containment[3]+a.top]}else u=this.containment;t.pageX-this.offset.click.left<u[0]&&(s=u[0]+this.offset.click.left),t.pageY-this.offset.click.top<u[1]&&(o=u[1]+this.offset.click.top),t.pageX-this.offset.click.left>u[2]&&(s=u[2]+this.offset.click.left),t.pageY-this.offset.click.top>u[3]&&(o=u[3]+this.offset.click.top)}if(n.grid){var f=n.grid[1]?this.originalPageY+Math.round((o-this.originalPageY)/n.grid[1])*n.grid[1]:this.originalPageY;o=u?f-this.offset.click.top<u[1]||f-this.offset.click.top>u[3]?f-this.offset.click.top<u[1]?f+n.grid[1]:f-n.grid[1]:f:f;var l=n.grid[0]?this.originalPageX+Math.round((s-this.originalPageX)/n.grid[0])*n.grid[0]:this.originalPageX;s=u?l-this.offset.click.left<u[0]||l-this.offset.click.left>u[2]?l-this.offset.click.left<u[0]?l+n.grid[0]:l-n.grid[0]:l:l}}return{top:o-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():i?0:r.scrollTop()),left:s-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():i?0:r.scrollLeft())}},_clear:function(){this.helper.removeClass("ui-draggable-dragging"),this.helper[0]!=this.element[0]&&!this.cancelHelperRemoval&&this.helper.remove(),this.helper=null,this.cancelHelperRemoval=!1},_trigger:function(t,n,r){return r=r||this._uiHash(),e.ui.plugin.call(this,t,[n,r]),t=="drag"&&(this.positionAbs=this._convertPositionTo("absolute")),e.Widget.prototype._trigger.call(this,t,n,r)},plugins:{},_uiHash:function(e){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}}),e.ui.plugin.add("draggable","connectToSortable",{start:function(t,n){var r=e(this).data("draggable"),i=r.options,s=e.extend({},n,{item:r.element});r.sortables=[],e(i.connectToSortable).each(function(){var n=e.data(this,"sortable");n&&!n.options.disabled&&(r.sortables.push({instance:n,shouldRevert:n.options.revert}),n.refreshPositions(),n._trigger("activate",t,s))})},stop:function(t,n){var r=e(this).data("draggable"),i=e.extend({},n,{item:r.element});e.each(r.sortables,function(){this.instance.isOver?(this.instance.isOver=0,r.cancelHelperRemoval=!0,this.instance.cancelHelperRemoval=!1,this.shouldRevert&&(this.instance.options.revert=!0),this.instance._mouseStop(t),this.instance.options.helper=this.instance.options._helper,r.options.helper=="original"&&this.instance.currentItem.css({top:"auto",left:"auto"})):(this.instance.cancelHelperRemoval=!1,this.instance._trigger("deactivate",t,i))})},drag:function(t,n){var r=e(this).data("draggable"),i=this,s=function(t){var n=this.offset.click.top,r=this.offset.click.left,i=this.positionAbs.top,s=this.positionAbs.left,o=t.height,u=t.width,a=t.top,f=t.left;return e.ui.isOver(i+n,s+r,a,f,o,u)};e.each(r.sortables,function(s){var o=!1,u=this;this.instance.positionAbs=r.positionAbs,this.instance.helperProportions=r.helperProportions,this.instance.offset.click=r.offset.click,this.instance._intersectsWith(this.instance.containerCache)&&(o=!0,e.each(r.sortables,function(){return this.instance.positionAbs=r.positionAbs,this.instance.helperProportions=r.helperProportions,this.instance.offset.click=r.offset.click,this!=u&&this.instance._intersectsWith(this.instance.containerCache)&&e.ui.contains(u.instance.element[0],this.instance.element[0])&&(o=!1),o})),o?(this.instance.isOver||(this.instance.isOver=1,this.instance.currentItem=e(i).clone().removeAttr("id").appendTo(this.instance.element).data("sortable-item",!0),this.instance.options._helper=this.instance.options.helper,this.instance.options.helper=function(){return n.helper[0]},t.target=this.instance.currentItem[0],this.instance._mouseCapture(t,!0),this.instance._mouseStart(t,!0,!0),this.instance.offset.click.top=r.offset.click.top,this.instance.offset.click.left=r.offset.click.left,this.instance.offset.parent.left-=r.offset.parent.left-this.instance.offset.parent.left,this.instance.offset.parent.top-=r.offset.parent.top-this.instance.offset.parent.top,r._trigger("toSortable",t),r.dropped=this.instance.element,r.currentItem=r.element,this.instance.fromOutside=r),this.instance.currentItem&&this.instance._mouseDrag(t)):this.instance.isOver&&(this.instance.isOver=0,this.instance.cancelHelperRemoval=!0,this.instance.options.revert=!1,this.instance._trigger("out",t,this.instance._uiHash(this.instance)),this.instance._mouseStop(t,!0),this.instance.options.helper=this.instance.options._helper,this.instance.currentItem.remove(),this.instance.placeholder&&this.instance.placeholder.remove(),r._trigger("fromSortable",t),r.dropped=!1)})}}),e.ui.plugin.add("draggable","cursor",{start:function(t,n){var r=e("body"),i=e(this).data("draggable").options;r.css("cursor")&&(i._cursor=r.css("cursor")),r.css("cursor",i.cursor)},stop:function(t,n){var r=e(this).data("draggable").options;r._cursor&&e("body").css("cursor",r._cursor)}}),e.ui.plugin.add("draggable","opacity",{start:function(t,n){var r=e(n.helper),i=e(this).data("draggable").options;r.css("opacity")&&(i._opacity=r.css("opacity")),r.css("opacity",i.opacity)},stop:function(t,n){var r=e(this).data("draggable").options;r._opacity&&e(n.helper).css("opacity",r._opacity)}}),e.ui.plugin.add("draggable","scroll",{start:function(t,n){var r=e(this).data("draggable");r.scrollParent[0]!=document&&r.scrollParent[0].tagName!="HTML"&&(r.overflowOffset=r.scrollParent.offset())},drag:function(t,n){var r=e(this).data("draggable"),i=r.options,s=!1;if(r.scrollParent[0]!=document&&r.scrollParent[0].tagName!="HTML"){if(!i.axis||i.axis!="x")r.overflowOffset.top+r.scrollParent[0].offsetHeight-t.pageY<i.scrollSensitivity?r.scrollParent[0].scrollTop=s=r.scrollParent[0].scrollTop+i.scrollSpeed:t.pageY-r.overflowOffset.top<i.scrollSensitivity&&(r.scrollParent[0].scrollTop=s=r.scrollParent[0].scrollTop-i.scrollSpeed);if(!i.axis||i.axis!="y")r.overflowOffset.left+r.scrollParent[0].offsetWidth-t.pageX<i.scrollSensitivity?r.scrollParent[0].scrollLeft=s=r.scrollParent[0].scrollLeft+i.scrollSpeed:t.pageX-r.overflowOffset.left<i.scrollSensitivity&&(r.scrollParent[0].scrollLeft=s=r.scrollParent[0].scrollLeft-i.scrollSpeed)}else{if(!i.axis||i.axis!="x")t.pageY-e(document).scrollTop()<i.scrollSensitivity?s=e(document).scrollTop(e(document).scrollTop()-i.scrollSpeed):e(window).height()-(t.pageY-e(document).scrollTop())<i.scrollSensitivity&&(s=e(document).scrollTop(e(document).scrollTop()+i.scrollSpeed));if(!i.axis||i.axis!="y")t.pageX-e(document).scrollLeft()<i.scrollSensitivity?s=e(document).scrollLeft(e(document).scrollLeft()-i.scrollSpeed):e(window).width()-(t.pageX-e(document).scrollLeft())<i.scrollSensitivity&&(s=e(document).scrollLeft(e(document).scrollLeft()+i.scrollSpeed))}s!==!1&&e.ui.ddmanager&&!i.dropBehaviour&&e.ui.ddmanager.prepareOffsets(r,t)}}),e.ui.plugin.add("draggable","snap",{start:function(t,n){var r=e(this).data("draggable"),i=r.options;r.snapElements=[],e(i.snap.constructor!=String?i.snap.items||":data(draggable)":i.snap).each(function(){var t=e(this),n=t.offset();this!=r.element[0]&&r.snapElements.push({item:this,width:t.outerWidth(),height:t.outerHeight(),top:n.top,left:n.left})})},drag:function(t,n){var r=e(this).data("draggable"),i=r.options,s=i.snapTolerance,o=n.offset.left,u=o+r.helperProportions.width,a=n.offset.top,f=a+r.helperProportions.height;for(var l=r.snapElements.length-1;l>=0;l--){var c=r.snapElements[l].left,h=c+r.snapElements[l].width,p=r.snapElements[l].top,d=p+r.snapElements[l].height;if(!(c-s<o&&o<h+s&&p-s<a&&a<d+s||c-s<o&&o<h+s&&p-s<f&&f<d+s||c-s<u&&u<h+s&&p-s<a&&a<d+s||c-s<u&&u<h+s&&p-s<f&&f<d+s)){r.snapElements[l].snapping&&r.options.snap.release&&r.options.snap.release.call(r.element,t,e.extend(r._uiHash(),{snapItem:r.snapElements[l].item})),r.snapElements[l].snapping=!1;continue}if(i.snapMode!="inner"){var v=Math.abs(p-f)<=s,m=Math.abs(d-a)<=s,g=Math.abs(c-u)<=s,y=Math.abs(h-o)<=s;v&&(n.position.top=r._convertPositionTo("relative",{top:p-r.helperProportions.height,left:0}).top-r.margins.top),m&&(n.position.top=r._convertPositionTo("relative",{top:d,left:0}).top-r.margins.top),g&&(n.position.left=r._convertPositionTo("relative",{top:0,left:c-r.helperProportions.width}).left-r.margins.left),y&&(n.position.left=r._convertPositionTo("relative",{top:0,left:h}).left-r.margins.left)}var b=v||m||g||y;if(i.snapMode!="outer"){var v=Math.abs(p-a)<=s,m=Math.abs(d-f)<=s,g=Math.abs(c-o)<=s,y=Math.abs(h-u)<=s;v&&(n.position.top=r._convertPositionTo("relative",{top:p,left:0}).top-r.margins.top),m&&(n.position.top=r._convertPositionTo("relative",{top:d-r.helperProportions.height,left:0}).top-r.margins.top),g&&(n.position.left=r._convertPositionTo("relative",{top:0,left:c}).left-r.margins.left),y&&(n.position.left=r._convertPositionTo("relative",{top:0,left:h-r.helperProportions.width}).left-r.margins.left)}!r.snapElements[l].snapping&&(v||m||g||y||b)&&r.options.snap.snap&&r.options.snap.snap.call(r.element,t,e.extend(r._uiHash(),{snapItem:r.snapElements[l].item})),r.snapElements[l].snapping=v||m||g||y||b}}}),e.ui.plugin.add("draggable","stack",{start:function(t,n){var r=e(this).data("draggable").options,i=e.makeArray(e(r.stack)).sort(function(t,n){return(parseInt(e(t).css("zIndex"),10)||0)-(parseInt(e(n).css("zIndex"),10)||0)});if(!i.length)return;var s=parseInt(i[0].style.zIndex)||0;e(i).each(function(e){this.style.zIndex=s+e}),this[0].style.zIndex=s+i.length}}),e.ui.plugin.add("draggable","zIndex",{start:function(t,n){var r=e(n.helper),i=e(this).data("draggable").options;r.css("zIndex")&&(i._zIndex=r.css("zIndex")),r.css("zIndex",i.zIndex)},stop:function(t,n){var r=e(this).data("draggable").options;r._zIndex&&e(n.helper).css("zIndex",r._zIndex)}})}(jQuery),function(e,t){e.widget("ui.droppable",{version:"1.9.2",widgetEventPrefix:"drop",options:{accept:"*",activeClass:!1,addClasses:!0,greedy:!1,hoverClass:!1,scope:"default",tolerance:"intersect"},_create:function(){var t=this.options,n=t.accept;this.isover=0,this.isout=1,this.accept=e.isFunction(n)?n:function(e){return e.is(n)},this.proportions={width:this.element[0].offsetWidth,height:this.element[0].offsetHeight},e.ui.ddmanager.droppables[t.scope]=e.ui.ddmanager.droppables[t.scope]||[],e.ui.ddmanager.droppables[t.scope].push(this),t.addClasses&&this.element.addClass("ui-droppable")},_destroy:function(){var t=e.ui.ddmanager.droppables[this.options.scope];for(var n=0;n<t.length;n++)t[n]==this&&t.splice(n,1);this.element.removeClass("ui-droppable ui-droppable-disabled")},_setOption:function(t,n){t=="accept"&&(this.accept=e.isFunction(n)?n:function(e){return e.is(n)}),e.Widget.prototype._setOption.apply(this,arguments)},_activate:function(t){var n=e.ui.ddmanager.current;this.options.activeClass&&this.element.addClass(this.options.activeClass),n&&this._trigger("activate",t,this.ui(n))},_deactivate:function(t){var n=e.ui.ddmanager.current;this.options.activeClass&&this.element.removeClass(this.options.activeClass),n&&this._trigger("deactivate",t,this.ui(n))},_over:function(t){var n=e.ui.ddmanager.current;if(!n||(n.currentItem||n.element)[0]==this.element[0])return;this.accept.call(this.element[0],n.currentItem||n.element)&&(this.options.hoverClass&&this.element.addClass(this.options.hoverClass),this._trigger("over",t,this.ui(n)))},_out:function(t){var n=e.ui.ddmanager.current;if(!n||(n.currentItem||n.element)[0]==this.element[0])return;this.accept.call(this.element[0],n.currentItem||n.element)&&(this.options.hoverClass&&this.element.removeClass(this.options.hoverClass),this._trigger("out",t,this.ui(n)))},_drop:function(t,n){var r=n||e.ui.ddmanager.current;if(!r||(r.currentItem||r.element)[0]==this.element[0])return!1;var i=!1;return this.element.find(":data(droppable)").not(".ui-draggable-dragging").each(function(){var t=e.data(this,"droppable");if(t.options.greedy&&!t.options.disabled&&t.options.scope==r.options.scope&&t.accept.call(t.element[0],r.currentItem||r.element)&&e.ui.intersect(r,e.extend(t,{offset:t.element.offset()}),t.options.tolerance))return i=!0,!1}),i?!1:this.accept.call(this.element[0],r.currentItem||r.element)?(this.options.activeClass&&this.element.removeClass(this.options.activeClass),this.options.hoverClass&&this.element.removeClass(this.options.hoverClass),this._trigger("drop",t,this.ui(r)),this.element):!1},ui:function(e){return{draggable:e.currentItem||e.element,helper:e.helper,position:e.position,offset:e.positionAbs}}}),e.ui.intersect=function(t,n,r){if(!n.offset)return!1;var i=(t.positionAbs||t.position.absolute).left,s=i+t.helperProportions.width,o=(t.positionAbs||t.position.absolute).top,u=o+t.helperProportions.height,a=n.offset.left,f=a+n.proportions.width,l=n.offset.top,c=l+n.proportions.height;switch(r){case"fit":return a<=i&&s<=f&&l<=o&&u<=c;case"intersect":return a<i+t.helperProportions.width/2&&s-t.helperProportions.width/2<f&&l<o+t.helperProportions.height/2&&u-t.helperProportions.height/2<c;case"pointer":var h=(t.positionAbs||t.position.absolute).left+(t.clickOffset||t.offset.click).left,p=(t.positionAbs||t.position.absolute).top+(t.clickOffset||t.offset.click).top,d=e.ui.isOver(p,h,l,a,n.proportions.height,n.proportions.width);return d;case"touch":return(o>=l&&o<=c||u>=l&&u<=c||o<l&&u>c)&&(i>=a&&i<=f||s>=a&&s<=f||i<a&&s>f);default:return!1}},e.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(t,n){var r=e.ui.ddmanager.droppables[t.options.scope]||[],i=n?n.type:null,s=(t.currentItem||t.element).find(":data(droppable)").andSelf();e:for(var o=0;o<r.length;o++){if(r[o].options.disabled||t&&!r[o].accept.call(r[o].element[0],t.currentItem||t.element))continue;for(var u=0;u<s.length;u++)if(s[u]==r[o].element[0]){r[o].proportions.height=0;continue e}r[o].visible=r[o].element.css("display")!="none";if(!r[o].visible)continue;i=="mousedown"&&r[o]._activate.call(r[o],n),r[o].offset=r[o].element.offset(),r[o].proportions={width:r[o].element[0].offsetWidth,height:r[o].element[0].offsetHeight}}},drop:function(t,n){var r=!1;return e.each(e.ui.ddmanager.droppables[t.options.scope]||[],function(){if(!this.options)return;!this.options.disabled&&this.visible&&e.ui.intersect(t,this,this.options.tolerance)&&(r=this._drop.call(this,n)||r),!this.options.disabled&&this.visible&&this.accept.call(this.element[0],t.currentItem||t.element)&&(this.isout=1,this.isover=0,this._deactivate.call(this,n))}),r},dragStart:function(t,n){t.element.parentsUntil("body").bind("scroll.droppable",function(){t.options.refreshPositions||e.ui.ddmanager.prepareOffsets(t,n)})},drag:function(t,n){t.options.refreshPositions&&e.ui.ddmanager.prepareOffsets(t,n),e.each(e.ui.ddmanager.droppables[t.options.scope]||[],function(){if(this.options.disabled||this.greedyChild||!this.visible)return;var r=e.ui.intersect(t,this,this.options.tolerance),i=!r&&this.isover==1?"isout":r&&this.isover==0?"isover":null;if(!i)return;var s;if(this.options.greedy){var o=this.options.scope,u=this.element.parents(":data(droppable)").filter(function(){return e.data(this,"droppable").options.scope===o});u.length&&(s=e.data(u[0],"droppable"),s.greedyChild=i=="isover"?1:0)}s&&i=="isover"&&(s.isover=0,s.isout=1,s._out.call(s,n)),this[i]=1,this[i=="isout"?"isover":"isout"]=0,this[i=="isover"?"_over":"_out"].call(this,n),s&&i=="isout"&&(s.isout=0,s.isover=1,s._over.call(s,n))})},dragStop:function(t,n){t.element.parentsUntil("body").unbind("scroll.droppable"),t.options.refreshPositions||e.ui.ddmanager.prepareOffsets(t,n)}}}(jQuery),function(e,t){e.widget("ui.resizable",e.ui.mouse,{version:"1.9.2",widgetEventPrefix:"resize",options:{alsoResize:!1,animate:!1,animateDuration:"slow",animateEasing:"swing",aspectRatio:!1,autoHide:!1,containment:!1,ghost:!1,grid:!1,handles:"e,s,se",helper:!1,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:1e3},_create:function(){var t=this,n=this.options;this.element.addClass("ui-resizable"),e.extend(this,{_aspectRatio:!!n.aspectRatio,aspectRatio:n.aspectRatio,originalElement:this.element,_proportionallyResizeElements:[],_helper:n.helper||n.ghost||n.animate?n.helper||"ui-resizable-helper":null}),this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)&&(this.element.wrap(e('<div class="ui-wrapper" style="overflow: hidden;"></div>').css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("resizable",this.element.data("resizable")),this.elementIsWrapper=!0,this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")}),this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0}),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css({margin:this.originalElement.css("margin")}),this._proportionallyResize()),this.handles=n.handles||(e(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se");if(this.handles.constructor==String){this.handles=="all"&&(this.handles="n,e,s,w,se,sw,ne,nw");var r=this.handles.split(",");this.handles={};for(var i=0;i<r.length;i++){var s=e.trim(r[i]),o="ui-resizable-"+s,u=e('<div class="ui-resizable-handle '+o+'"></div>');u.css({zIndex:n.zIndex}),"se"==s&&u.addClass("ui-icon ui-icon-gripsmall-diagonal-se"),this.handles[s]=".ui-resizable-"+s,this.element.append(u)}}this._renderAxis=function(t){t=t||this.element;for(var n in this.handles){this.handles[n].constructor==String&&(this.handles[n]=e(this.handles[n],this.element).show());if(this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)){var r=e(this.handles[n],this.element),i=0;i=/sw|ne|nw|se|n|s/.test(n)?r.outerHeight():r.outerWidth();var s=["padding",/ne|nw|n/.test(n)?"Top":/se|sw|s/.test(n)?"Bottom":/^e$/.test(n)?"Right":"Left"].join("");t.css(s,i),this._proportionallyResize()}if(!e(this.handles[n]).length)continue}},this._renderAxis(this.element),this._handles=e(".ui-resizable-handle",this.element).disableSelection(),this._handles.mouseover(function(){if(!t.resizing){if(this.className)var e=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);t.axis=e&&e[1]?e[1]:"se"}}),n.autoHide&&(this._handles.hide(),e(this.element).addClass("ui-resizable-autohide").mouseenter(function(){if(n.disabled)return;e(this).removeClass("ui-resizable-autohide"),t._handles.show()}).mouseleave(function(){if(n.disabled)return;t.resizing||(e(this).addClass("ui-resizable-autohide"),t._handles.hide())})),this._mouseInit()},_destroy:function(){this._mouseDestroy();var t=function(t){e(t).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").removeData("ui-resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};if(this.elementIsWrapper){t(this.element);var n=this.element;this.originalElement.css({position:n.css("position"),width:n.outerWidth(),height:n.outerHeight(),top:n.css("top"),left:n.css("left")}).insertAfter(n),n.remove()}return this.originalElement.css("resize",this.originalResizeStyle),t(this.originalElement),this},_mouseCapture:function(t){var n=!1;for(var r in this.handles)e(this.handles[r])[0]==t.target&&(n=!0);return!this.options.disabled&&n},_mouseStart:function(t){var r=this.options,i=this.element.position(),s=this.element;this.resizing=!0,this.documentScroll={top:e(document).scrollTop(),left:e(document).scrollLeft()},(s.is(".ui-draggable")||/absolute/.test(s.css("position")))&&s.css({position:"absolute",top:i.top,left:i.left}),this._renderProxy();var o=n(this.helper.css("left")),u=n(this.helper.css("top"));r.containment&&(o+=e(r.containment).scrollLeft()||0,u+=e(r.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:o,top:u},this.size=this._helper?{width:s.outerWidth(),height:s.outerHeight()}:{width:s.width(),height:s.height()},this.originalSize=this._helper?{width:s.outerWidth(),height:s.outerHeight()}:{width:s.width(),height:s.height()},this.originalPosition={left:o,top:u},this.sizeDiff={width:s.outerWidth()-s.width(),height:s.outerHeight()-s.height()},this.originalMousePosition={left:t.pageX,top:t.pageY},this.aspectRatio=typeof r.aspectRatio=="number"?r.aspectRatio:this.originalSize.width/this.originalSize.height||1;var a=e(".ui-resizable-"+this.axis).css("cursor");return e("body").css("cursor",a=="auto"?this.axis+"-resize":a),s.addClass("ui-resizable-resizing"),this._propagate("start",t),!0},_mouseDrag:function(e){var t=this.helper,n=this.options,r={},i=this,s=this.originalMousePosition,o=this.axis,u=e.pageX-s.left||0,a=e.pageY-s.top||0,f=this._change[o];if(!f)return!1;var l=f.apply(this,[e,u,a]);this._updateVirtualBoundaries(e.shiftKey);if(this._aspectRatio||e.shiftKey)l=this._updateRatio(l,e);return l=this._respectSize(l,e),this._propagate("resize",e),t.css({top:this.position.top+"px",left:this.position.left+"px",width:this.size.width+"px",height:this.size.height+"px"}),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),this._updateCache(l),this._trigger("resize",e,this.ui()),!1},_mouseStop:function(t){this.resizing=!1;var n=this.options,r=this;if(this._helper){var i=this._proportionallyResizeElements,s=i.length&&/textarea/i.test(i[0].nodeName),o=s&&e.ui.hasScroll(i[0],"left")?0:r.sizeDiff.height,u=s?0:r.sizeDiff.width,a={width:r.helper.width()-u,height:r.helper.height()-o},f=parseInt(r.element.css("left"),10)+(r.position.left-r.originalPosition.left)||null,l=parseInt(r.element.css("top"),10)+(r.position.top-r.originalPosition.top)||null;n.animate||this.element.css(e.extend(a,{top:l,left:f})),r.helper.height(r.size.height),r.helper.width(r.size.width),this._helper&&!n.animate&&this._proportionallyResize()}return e("body").css("cursor","auto"),this.element.removeClass("ui-resizable-resizing"),this._propagate("stop",t),this._helper&&this.helper.remove(),!1},_updateVirtualBoundaries:function(e){var t=this.options,n,i,s,o,u;u={minWidth:r(t.minWidth)?t.minWidth:0,maxWidth:r(t.maxWidth)?t.maxWidth:Infinity,minHeight:r(t.minHeight)?t.minHeight:0,maxHeight:r(t.maxHeight)?t.maxHeight:Infinity};if(this._aspectRatio||e)n=u.minHeight*this.aspectRatio,s=u.minWidth/this.aspectRatio,i=u.maxHeight*this.aspectRatio,o=u.maxWidth/this.aspectRatio,n>u.minWidth&&(u.minWidth=n),s>u.minHeight&&(u.minHeight=s),i<u.maxWidth&&(u.maxWidth=i),o<u.maxHeight&&(u.maxHeight=o);this._vBoundaries=u},_updateCache:function(e){var t=this.options;this.offset=this.helper.offset(),r(e.left)&&(this.position.left=e.left),r(e.top)&&(this.position.top=e.top),r(e.height)&&(this.size.height=e.height),r(e.width)&&(this.size.width=e.width)},_updateRatio:function(e,t){var n=this.options,i=this.position,s=this.size,o=this.axis;return r(e.height)?e.width=e.height*this.aspectRatio:r(e.width)&&(e.height=e.width/this.aspectRatio),o=="sw"&&(e.left=i.left+(s.width-e.width),e.top=null),o=="nw"&&(e.top=i.top+(s.height-e.height),e.left=i.left+(s.width-e.width)),e},_respectSize:function(e,t){var n=this.helper,i=this._vBoundaries,s=this._aspectRatio||t.shiftKey,o=this.axis,u=r(e.width)&&i.maxWidth&&i.maxWidth<e.width,a=r(e.height)&&i.maxHeight&&i.maxHeight<e.height,f=r(e.width)&&i.minWidth&&i.minWidth>e.width,l=r(e.height)&&i.minHeight&&i.minHeight>e.height;f&&(e.width=i.minWidth),l&&(e.height=i.minHeight),u&&(e.width=i.maxWidth),a&&(e.height=i.maxHeight);var c=this.originalPosition.left+this.originalSize.width,h=this.position.top+this.size.height,p=/sw|nw|w/.test(o),d=/nw|ne|n/.test(o);f&&p&&(e.left=c-i.minWidth),u&&p&&(e.left=c-i.maxWidth),l&&d&&(e.top=h-i.minHeight),a&&d&&(e.top=h-i.maxHeight);var v=!e.width&&!e.height;return v&&!e.left&&e.top?e.top=null:v&&!e.top&&e.left&&(e.left=null),e},_proportionallyResize:function(){var t=this.options;if(!this._proportionallyResizeElements.length)return;var n=this.helper||this.element;for(var r=0;r<this._proportionallyResizeElements.length;r++){var i=this._proportionallyResizeElements[r];if(!this.borderDif){var s=[i.css("borderTopWidth"),i.css("borderRightWidth"),i.css("borderBottomWidth"),i.css("borderLeftWidth")],o=[i.css("paddingTop"),i.css("paddingRight"),i.css("paddingBottom"),i.css("paddingLeft")];this.borderDif=e.map(s,function(e,t){var n=parseInt(e,10)||0,r=parseInt(o[t],10)||0;return n+r})}i.css({height:n.height()-this.borderDif[0]-this.borderDif[2]||0,width:n.width()-this.borderDif[1]-this.borderDif[3]||0})}},_renderProxy:function(){var t=this.element,n=this.options;this.elementOffset=t.offset();if(this._helper){this.helper=this.helper||e('<div style="overflow:hidden;"></div>');var r=e.ui.ie6?1:0,i=e.ui.ie6?2:-1;this.helper.addClass(this._helper).css({width:this.element.outerWidth()+i,height:this.element.outerHeight()+i,position:"absolute",left:this.elementOffset.left-r+"px",top:this.elementOffset.top-r+"px",zIndex:++n.zIndex}),this.helper.appendTo("body").disableSelection()}else this.helper=this.element},_change:{e:function(e,t,n){return{width:this.originalSize.width+t}},w:function(e,t,n){var r=this.options,i=this.originalSize,s=this.originalPosition;return{left:s.left+t,width:i.width-t}},n:function(e,t,n){var r=this.options,i=this.originalSize,s=this.originalPosition;return{top:s.top+n,height:i.height-n}},s:function(e,t,n){return{height:this.originalSize.height+n}},se:function(t,n,r){return e.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[t,n,r]))},sw:function(t,n,r){return e.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[t,n,r]))},ne:function(t,n,r){return e.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[t,n,r]))},nw:function(t,n,r){return e.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[t,n,r]))}},_propagate:function(t,n){e.ui.plugin.call(this,t,[n,this.ui()]),t!="resize"&&this._trigger(t,n,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),e.ui.plugin.add("resizable","alsoResize",{start:function(t,n){var r=e(this).data("resizable"),i=r.options,s=function(t){e(t).each(function(){var t=e(this);t.data("resizable-alsoresize",{width:parseInt(t.width(),10),height:parseInt(t.height(),10),left:parseInt(t.css("left"),10),top:parseInt(t.css("top"),10)})})};typeof i.alsoResize=="object"&&!i.alsoResize.parentNode?i.alsoResize.length?(i.alsoResize=i.alsoResize[0],s(i.alsoResize)):e.each(i.alsoResize,function(e){s(e)}):s(i.alsoResize)},resize:function(t,n){var r=e(this).data("resizable"),i=r.options,s=r.originalSize,o=r.originalPosition,u={height:r.size.height-s.height||0,width:r.size.width-s.width||0,top:r.position.top-o.top||0,left:r.position.left-o.left||0},a=function(t,r){e(t).each(function(){var t=e(this),i=e(this).data("resizable-alsoresize"),s={},o=r&&r.length?r:t.parents(n.originalElement[0]).length?["width","height"]:["width","height","top","left"];e.each(o,function(e,t){var n=(i[t]||0)+(u[t]||0);n&&n>=0&&(s[t]=n||null)}),t.css(s)})};typeof i.alsoResize=="object"&&!i.alsoResize.nodeType?e.each(i.alsoResize,function(e,t){a(e,t)}):a(i.alsoResize)},stop:function(t,n){e(this).removeData("resizable-alsoresize")}}),e.ui.plugin.add("resizable","animate",{stop:function(t,n){var r=e(this).data("resizable"),i=r.options,s=r._proportionallyResizeElements,o=s.length&&/textarea/i.test(s[0].nodeName),u=o&&e.ui.hasScroll(s[0],"left")?0:r.sizeDiff.height,a=o?0:r.sizeDiff.width,f={width:r.size.width-a,height:r.size.height-u},l=parseInt(r.element.css("left"),10)+(r.position.left-r.originalPosition.left)||null,c=parseInt(r.element.css("top"),10)+(r.position.top-r.originalPosition.top)||null;r.element.animate(e.extend(f,c&&l?{top:c,left:l}:{}),{duration:i.animateDuration,easing:i.animateEasing,step:function(){var n={width:parseInt(r.element.css("width"),10),height:parseInt(r.element.css("height"),10),top:parseInt(r.element.css("top"),10),left:parseInt(r.element.css("left"),10)};s&&s.length&&e(s[0]).css({width:n.width,height:n.height}),r._updateCache(n),r._propagate("resize",t)}})}}),e.ui.plugin.add("resizable","containment",{start:function(t,r){var i=e(this).data("resizable"),s=i.options,o=i.element,u=s.containment,a=u instanceof e?u.get(0):/parent/.test(u)?o.parent().get(0):u;if(!a)return;i.containerElement=e(a);if(/document/.test(u)||u==document)i.containerOffset={left:0,top:0},i.containerPosition={left:0,top:0},i.parentData={element:e(document),left:0,top:0,width:e(document).width(),height:e(document).height()||document.body.parentNode.scrollHeight};else{var f=e(a),l=[];e(["Top","Right","Left","Bottom"]).each(function(e,t){l[e]=n(f.css("padding"+t))}),i.containerOffset=f.offset(),i.containerPosition=f.position(),i.containerSize={height:f.innerHeight()-l[3],width:f.innerWidth()-l[1]};var c=i.containerOffset,h=i.containerSize.height,p=i.containerSize.width,d=e.ui.hasScroll(a,"left")?a.scrollWidth:p,v=e.ui.hasScroll(a)?a.scrollHeight:h;i.parentData={element:a,left:c.left,top:c.top,width:d,height:v}}},resize:function(t,n){var r=e(this).data("resizable"),i=r.options,s=r.containerSize,o=r.containerOffset,u=r.size,a=r.position,f=r._aspectRatio||t.shiftKey,l={top:0,left:0},c=r.containerElement;c[0]!=document&&/static/.test(c.css("position"))&&(l=o),a.left<(r._helper?o.left:0)&&(r.size.width=r.size.width+(r._helper?r.position.left-o.left:r.position.left-l.left),f&&(r.size.height=r.size.width/r.aspectRatio),r.position.left=i.helper?o.left:0),a.top<(r._helper?o.top:0)&&(r.size.height=r.size.height+(r._helper?r.position.top-o.top:r.position.top),f&&(r.size.width=r.size.height*r.aspectRatio),r.position.top=r._helper?o.top:0),r.offset.left=r.parentData.left+r.position.left,r.offset.top=r.parentData.top+r.position.top;var h=Math.abs((r._helper?r.offset.left-l.left:r.offset.left-l.left)+r.sizeDiff.width),p=Math.abs((r._helper?r.offset.top-l.top:r.offset.top-o.top)+r.sizeDiff.height),d=r.containerElement.get(0)==r.element.parent().get(0),v=/relative|absolute/.test(r.containerElement.css("position"));d&&v&&(h-=r.parentData.left),h+r.size.width>=r.parentData.width&&(r.size.width=r.parentData.width-h,f&&(r.size.height=r.size.width/r.aspectRatio)),p+r.size.height>=r.parentData.height&&(r.size.height=r.parentData.height-p,f&&(r.size.width=r.size.height*r.aspectRatio))},stop:function(t,n){var r=e(this).data("resizable"),i=r.options,s=r.position,o=r.containerOffset,u=r.containerPosition,a=r.containerElement,f=e(r.helper),l=f.offset(),c=f.outerWidth()-r.sizeDiff.width,h=f.outerHeight()-r.sizeDiff.height;r._helper&&!i.animate&&/relative/.test(a.css("position"))&&e(this).css({left:l.left-u.left-o.left,width:c,height:h}),r._helper&&!i.animate&&/static/.test(a.css("position"))&&e(this).css({left:l.left-u.left-o.left,width:c,height:h})}}),e.ui.plugin.add("resizable","ghost",{start:function(t,n){var r=e(this).data("resizable"),i=r.options,s=r.size;r.ghost=r.originalElement.clone(),r.ghost.css({opacity:.25,display:"block",position:"relative",height:s.height,width:s.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass(typeof i.ghost=="string"?i.ghost:""),r.ghost.appendTo(r.helper)},resize:function(t,n){var r=e(this).data("resizable"),i=r.options;r.ghost&&r.ghost.css({position:"relative",height:r.size.height,width:r.size.width})},stop:function(t,n){var r=e(this).data("resizable"),i=r.options;r.ghost&&r.helper&&r.helper.get(0).removeChild(r.ghost.get(0))}}),e.ui.plugin.add("resizable","grid",{resize:function(t,n){var r=e(this).data("resizable"),i=r.options,s=r.size,o=r.originalSize,u=r.originalPosition,a=r.axis,f=i._aspectRatio||t.shiftKey;i.grid=typeof i.grid=="number"?[i.grid,i.grid]:i.grid;var l=Math.round((s.width-o.width)/(i.grid[0]||1))*(i.grid[0]||1),c=Math.round((s.height-o.height)/(i.grid[1]||1))*(i.grid[1]||1);/^(se|s|e)$/.test(a)?(r.size.width=o.width+l,r.size.height=o.height+c):/^(ne)$/.test(a)?(r.size.width=o.width+l,r.size.height=o.height+c,r.position.top=u.top-c):/^(sw)$/.test(a)?(r.size.width=o.width+l,r.size.height=o.height+c,r.position.left=u.left-l):(r.size.width=o.width+l,r.size.height=o.height+c,r.position.top=u.top-c,r.position.left=u.left-l)}});var n=function(e){return parseInt(e,10)||0},r=function(e){return!isNaN(parseInt(e,10))}}(jQuery),function(e,t){e.widget("ui.selectable",e.ui.mouse,{version:"1.9.2",options:{appendTo:"body",autoRefresh:!0,distance:0,filter:"*",tolerance:"touch"},_create:function(){var t=this;this.element.addClass("ui-selectable"),this.dragged=!1;var n;this.refresh=function(){n=e(t.options.filter,t.element[0]),n.addClass("ui-selectee"),n.each(function(){var t=e(this),n=t.offset();e.data(this,"selectable-item",{element:this,$element:t,left:n.left,top:n.top,right:n.left+t.outerWidth(),bottom:n.top+t.outerHeight(),startselected:!1,selected:t.hasClass("ui-selected"),selecting:t.hasClass("ui-selecting"),unselecting:t.hasClass("ui-unselecting")})})},this.refresh(),this.selectees=n.addClass("ui-selectee"),this._mouseInit(),this.helper=e("<div class='ui-selectable-helper'></div>")},_destroy:function(){this.selectees.removeClass("ui-selectee").removeData("selectable-item"),this.element.removeClass("ui-selectable ui-selectable-disabled"),this._mouseDestroy()},_mouseStart:function(t){var n=this;this.opos=[t.pageX,t.pageY];if(this.options.disabled)return;var r=this.options;this.selectees=e(r.filter,this.element[0]),this._trigger("start",t),e(r.appendTo).append(this.helper),this.helper.css({left:t.clientX,top:t.clientY,width:0,height:0}),r.autoRefresh&&this.refresh(),this.selectees.filter(".ui-selected").each(function(){var r=e.data(this,"selectable-item");r.startselected=!0,!t.metaKey&&!t.ctrlKey&&(r.$element.removeClass("ui-selected"),r.selected=!1,r.$element.addClass("ui-unselecting"),r.unselecting=!0,n._trigger("unselecting",t,{unselecting:r.element}))}),e(t.target).parents().andSelf().each(function(){var r=e.data(this,"selectable-item");if(r){var i=!t.metaKey&&!t.ctrlKey||!r.$element.hasClass("ui-selected");return r.$element.removeClass(i?"ui-unselecting":"ui-selected").addClass(i?"ui-selecting":"ui-unselecting"),r.unselecting=!i,r.selecting=i,r.selected=i,i?n._trigger("selecting",t,{selecting:r.element}):n._trigger("unselecting",t,{unselecting:r.element}),!1}})},_mouseDrag:function(t){var n=this;this.dragged=!0;if(this.options.disabled)return;var r=this.options,i=this.opos[0],s=this.opos[1],o=t.pageX,u=t.pageY;if(i>o){var a=o;o=i,i=a}if(s>u){var a=u;u=s,s=a}return this.helper.css({left:i,top:s,width:o-i,height:u-s}),this.selectees.each(function(){var a=e.data(this,"selectable-item");if(!a||a.element==n.element[0])return;var f=!1;r.tolerance=="touch"?f=!(a.left>o||a.right<i||a.top>u||a.bottom<s):r.tolerance=="fit"&&(f=a.left>i&&a.right<o&&a.top>s&&a.bottom<u),f?(a.selected&&(a.$element.removeClass("ui-selected"),a.selected=!1),a.unselecting&&(a.$element.removeClass("ui-unselecting"),a.unselecting=!1),a.selecting||(a.$element.addClass("ui-selecting"),a.selecting=!0,n._trigger("selecting",t,{selecting:a.element}))):(a.selecting&&((t.metaKey||t.ctrlKey)&&a.startselected?(a.$element.removeClass("ui-selecting"),a.selecting=!1,a.$element.addClass("ui-selected"),a.selected=!0):(a.$element.removeClass("ui-selecting"),a.selecting=!1,a.startselected&&(a.$element.addClass("ui-unselecting"),a.unselecting=!0),n._trigger("unselecting",t,{unselecting:a.element}))),a.selected&&!t.metaKey&&!t.ctrlKey&&!a.startselected&&(a.$element.removeClass("ui-selected"),a.selected=!1,a.$element.addClass("ui-unselecting"),a.unselecting=!0,n._trigger("unselecting",t,{unselecting:a.element})))}),!1},_mouseStop:function(t){var n=this;this.dragged=!1;var r=this.options;return e(".ui-unselecting",this.element[0]).each(function(){var r=e.data(this,"selectable-item");r.$element.removeClass("ui-unselecting"),r.unselecting=!1,r.startselected=!1,n._trigger("unselected",t,{unselected:r.element})}),e(".ui-selecting",this.element[0]).each(function(){var r=e.data(this,"selectable-item");r.$element.removeClass("ui-selecting").addClass("ui-selected"),r.selecting=!1,r.selected=!0,r.startselected=!0,n._trigger("selected",t,{selected:r.element})}),this._trigger("stop",t),this.helper.remove(),!1}})}(jQuery),function(e,t){e.widget("ui.sortable",e.ui.mouse,{version:"1.9.2",widgetEventPrefix:"sort",ready:!1,options:{appendTo:"parent",axis:!1,connectWith:!1,containment:!1,cursor:"auto",cursorAt:!1,dropOnEmpty:!0,forcePlaceholderSize:!1,forceHelperSize:!1,grid:!1,handle:!1,helper:"original",items:"> *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1e3},_create:function(){var e=this.options;this.containerCache={},this.element.addClass("ui-sortable"),this.refresh(),this.floating=this.items.length?e.axis==="x"||/left|right/.test(this.items[0].item.css("float"))||/inline|table-cell/.test(this.items[0].item.css("display")):!1,this.offset=this.element.offset(),this._mouseInit(),this.ready=!0},_destroy:function(){this.element.removeClass("ui-sortable ui-sortable-disabled"),this._mouseDestroy();for(var e=this.items.length-1;e>=0;e--)this.items[e].item.removeData(this.widgetName+"-item");return this},_setOption:function(t,n){t==="disabled"?(this.options[t]=n,this.widget().toggleClass("ui-sortable-disabled",!!n)):e.Widget.prototype._setOption.apply(this,arguments)},_mouseCapture:function(t,n){var r=this;if(this.reverting)return!1;if(this.options.disabled||this.options.type=="static")return!1;this._refreshItems(t);var i=null,s=e(t.target).parents().each(function(){if(e.data(this,r.widgetName+"-item")==r)return i=e(this),!1});e.data(t.target,r.widgetName+"-item")==r&&(i=e(t.target));if(!i)return!1;if(this.options.handle&&!n){var o=!1;e(this.options.handle,i).find("*").andSelf().each(function(){this==t.target&&(o=!0)});if(!o)return!1}return this.currentItem=i,this._removeCurrentsFromItems(),!0},_mouseStart:function(t,n,r){var i=this.options;this.currentContainer=this,this.refreshPositions(),this.helper=this._createHelper(t),this._cacheHelperProportions(),this._cacheMargins(),this.scrollParent=this.helper.scrollParent(),this.offset=this.currentItem.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},e.extend(this.offset,{click:{left:t.pageX-this.offset.left,top:t.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.helper.css("position","absolute"),this.cssPosition=this.helper.css("position"),this.originalPosition=this._generatePosition(t),this.originalPageX=t.pageX,this.originalPageY=t.pageY,i.cursorAt&&this._adjustOffsetFromHelper(i.cursorAt),this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]},this.helper[0]!=this.currentItem[0]&&this.currentItem.hide(),this._createPlaceholder(),i.containment&&this._setContainment(),i.cursor&&(e("body").css("cursor")&&(this._storedCursor=e("body").css("cursor")),e("body").css("cursor",i.cursor)),i.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",i.opacity)),i.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",i.zIndex)),this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"&&(this.overflowOffset=this.scrollParent.offset()),this._trigger("start",t,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions();if(!r)for(var s=this.containers.length-1;s>=0;s--)this.containers[s]._trigger("activate",t,this._uiHash(this));return e.ui.ddmanager&&(e.ui.ddmanager.current=this),e.ui.ddmanager&&!i.dropBehaviour&&e.ui.ddmanager.prepareOffsets(this,t),this.dragging=!0,this.helper.addClass("ui-sortable-helper"),this._mouseDrag(t),!0},_mouseDrag:function(t){this.position=this._generatePosition(t),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs);if(this.options.scroll){var n=this.options,r=!1;this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-t.pageY<n.scrollSensitivity?this.scrollParent[0].scrollTop=r=this.scrollParent[0].scrollTop+n.scrollSpeed:t.pageY-this.overflowOffset.top<n.scrollSensitivity&&(this.scrollParent[0].scrollTop=r=this.scrollParent[0].scrollTop-n.scrollSpeed),this.overflowOffset.left+this.scrollParent[0].offsetWidth-t.pageX<n.scrollSensitivity?this.scrollParent[0].scrollLeft=r=this.scrollParent[0].scrollLeft+n.scrollSpeed:t.pageX-this.overflowOffset.left<n.scrollSensitivity&&(this.scrollParent[0].scrollLeft=r=this.scrollParent[0].scrollLeft-n.scrollSpeed)):(t.pageY-e(document).scrollTop()<n.scrollSensitivity?r=e(document).scrollTop(e(document).scrollTop()-n.scrollSpeed):e(window).height()-(t.pageY-e(document).scrollTop())<n.scrollSensitivity&&(r=e(document).scrollTop(e(document).scrollTop()+n.scrollSpeed)),t.pageX-e(document).scrollLeft()<n.scrollSensitivity?r=e(document).scrollLeft(e(document).scrollLeft()-n.scrollSpeed):e(window).width()-(t.pageX-e(document).scrollLeft())<n.scrollSensitivity&&(r=e(document).scrollLeft(e(document).scrollLeft()+n.scrollSpeed))),r!==!1&&e.ui.ddmanager&&!n.dropBehaviour&&e.ui.ddmanager.prepareOffsets(this,t)}this.positionAbs=this._convertPositionTo("absolute");if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";for(var i=this.items.length-1;i>=0;i--){var s=this.items[i],o=s.item[0],u=this._intersectsWithPointer(s);if(!u)continue;if(s.instance!==this.currentContainer)continue;if(o!=this.currentItem[0]&&this.placeholder[u==1?"next":"prev"]()[0]!=o&&!e.contains(this.placeholder[0],o)&&(this.options.type=="semi-dynamic"?!e.contains(this.element[0],o):!0)){this.direction=u==1?"down":"up";if(this.options.tolerance!="pointer"&&!this._intersectsWithSides(s))break;this._rearrange(t,s),this._trigger("change",t,this._uiHash());break}}return this._contactContainers(t),e.ui.ddmanager&&e.ui.ddmanager.drag(this,t),this._trigger("sort",t,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(t,n){if(!t)return;e.ui.ddmanager&&!this.options.dropBehaviour&&e.ui.ddmanager.drop(this,t);if(this.options.revert){var r=this,i=this.placeholder.offset();this.reverting=!0,e(this.helper).animate({left:i.left-this.offset.parent.left-this.margins.left+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollLeft),top:i.top-this.offset.parent.top-this.margins.top+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollTop)},parseInt(this.options.revert,10)||500,function(){r._clear(t)})}else this._clear(t,n);return!1},cancel:function(){if(this.dragging){this._mouseUp({target:null}),this.options.helper=="original"?this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"):this.currentItem.show();for(var t=this.containers.length-1;t>=0;t--)this.containers[t]._trigger("deactivate",null,this._uiHash(this)),this.containers[t].containerCache.over&&(this.containers[t]._trigger("out",null,this._uiHash(this)),this.containers[t].containerCache.over=0)}return this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.options.helper!="original"&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),e.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?e(this.domPosition.prev).after(this.currentItem):e(this.domPosition.parent).prepend(this.currentItem)),this},serialize:function(t){var n=this._getItemsAsjQuery(t&&t.connected),r=[];return t=t||{},e(n).each(function(){var n=(e(t.item||this).attr(t.attribute||"id")||"").match(t.expression||/(.+)[-=_](.+)/);n&&r.push((t.key||n[1]+"[]")+"="+(t.key&&t.expression?n[1]:n[2]))}),!r.length&&t.key&&r.push(t.key+"="),r.join("&")},toArray:function(t){var n=this._getItemsAsjQuery(t&&t.connected),r=[];return t=t||{},n.each(function(){r.push(e(t.item||this).attr(t.attribute||"id")||"")}),r},_intersectsWith:function(e){var t=this.positionAbs.left,n=t+this.helperProportions.width,r=this.positionAbs.top,i=r+this.helperProportions.height,s=e.left,o=s+e.width,u=e.top,a=u+e.height,f=this.offset.click.top,l=this.offset.click.left,c=r+f>u&&r+f<a&&t+l>s&&t+l<o;return this.options.tolerance=="pointer"||this.options.forcePointerForContainers||this.options.tolerance!="pointer"&&this.helperProportions[this.floating?"width":"height"]>e[this.floating?"width":"height"]?c:s<t+this.helperProportions.width/2&&n-this.helperProportions.width/2<o&&u<r+this.helperProportions.height/2&&i-this.helperProportions.height/2<a},_intersectsWithPointer:function(t){var n=this.options.axis==="x"||e.ui.isOverAxis(this.positionAbs.top+this.offset.click.top,t.top,t.height),r=this.options.axis==="y"||e.ui.isOverAxis(this.positionAbs.left+this.offset.click.left,t.left,t.width),i=n&&r,s=this._getDragVerticalDirection(),o=this._getDragHorizontalDirection();return i?this.floating?o&&o=="right"||s=="down"?2:1:s&&(s=="down"?2:1):!1},_intersectsWithSides:function(t){var n=e.ui.isOverAxis(this.positionAbs.top+this.offset.click.top,t.top+t.height/2,t.height),r=e.ui.isOverAxis(this.positionAbs.left+this.offset.click.left,t.left+t.width/2,t.width),i=this._getDragVerticalDirection(),s=this._getDragHorizontalDirection();return this.floating&&s?s=="right"&&r||s=="left"&&!r:i&&(i=="down"&&n||i=="up"&&!n)},_getDragVerticalDirection:function(){var e=this.positionAbs.top-this.lastPositionAbs.top;return e!=0&&(e>0?"down":"up")},_getDragHorizontalDirection:function(){var e=this.positionAbs.left-this.lastPositionAbs.left;return e!=0&&(e>0?"right":"left")},refresh:function(e){return this._refreshItems(e),this.refreshPositions(),this},_connectWith:function(){var e=this.options;return e.connectWith.constructor==String?[e.connectWith]:e.connectWith},_getItemsAsjQuery:function(t){var n=[],r=[],i=this._connectWith();if(i&&t)for(var s=i.length-1;s>=0;s--){var o=e(i[s]);for(var u=o.length-1;u>=0;u--){var a=e.data(o[u],this.widgetName);a&&a!=this&&!a.options.disabled&&r.push([e.isFunction(a.options.items)?a.options.items.call(a.element):e(a.options.items,a.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),a])}}r.push([e.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):e(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]);for(var s=r.length-1;s>=0;s--)r[s][0].each(function(){n.push(this)});return e(n)},_removeCurrentsFromItems:function(){var t=this.currentItem.find(":data("+this.widgetName+"-item)");this.items=e.grep(this.items,function(e){for(var n=0;n<t.length;n++)if(t[n]==e.item[0])return!1;return!0})},_refreshItems:function(t){this.items=[],this.containers=[this];var n=this.items,r=[[e.isFunction(this.options.items)?this.options.items.call(this.element[0],t,{item:this.currentItem}):e(this.options.items,this.element),this]],i=this._connectWith();if(i&&this.ready)for(var s=i.length-1;s>=0;s--){var o=e(i[s]);for(var u=o.length-1;u>=0;u--){var a=e.data(o[u],this.widgetName);a&&a!=this&&!a.options.disabled&&(r.push([e.isFunction(a.options.items)?a.options.items.call(a.element[0],t,{item:this.currentItem}):e(a.options.items,a.element),a]),this.containers.push(a))}}for(var s=r.length-1;s>=0;s--){var f=r[s][1],l=r[s][0];for(var u=0,c=l.length;u<c;u++){var h=e(l[u]);h.data(this.widgetName+"-item",f),n.push({item:h,instance:f,width:0,height:0,left:0,top:0})}}},refreshPositions:function(t){this.offsetParent&&this.helper&&(this.offset.parent=this._getParentOffset());for(var n=this.items.length-1;n>=0;n--){var r=this.items[n];if(r.instance!=this.currentContainer&&this.currentContainer&&r.item[0]!=this.currentItem[0])continue;var i=this.options.toleranceElement?e(this.options.toleranceElement,r.item):r.item;t||(r.width=i.outerWidth(),r.height=i.outerHeight());var s=i.offset();r.left=s.left,r.top=s.top}if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(var n=this.containers.length-1;n>=0;n--){var s=this.containers[n].element.offset();this.containers[n].containerCache.left=s.left,this.containers[n].containerCache.top=s.top,this.containers[n].containerCache.width=this.containers[n].element.outerWidth(),this.containers[n].containerCache.height=this.containers[n].element.outerHeight()}return this},_createPlaceholder:function(t){t=t||this;var n=t.options;if(!n.placeholder||n.placeholder.constructor==String){var r=n.placeholder;n.placeholder={element:function(){var n=e(document.createElement(t.currentItem[0].nodeName)).addClass(r||t.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper")[0];return r||(n.style.visibility="hidden"),n},update:function(e,i){if(r&&!n.forcePlaceholderSize)return;i.height()||i.height(t.currentItem.innerHeight()-parseInt(t.currentItem.css("paddingTop")||0,10)-parseInt(t.currentItem.css("paddingBottom")||0,10)),i.width()||i.width(t.currentItem.innerWidth()-parseInt(t.currentItem.css("paddingLeft")||0,10)-parseInt(t.currentItem.css("paddingRight")||0,10))}}}t.placeholder=e(n.placeholder.element.call(t.element,t.currentItem)),t.currentItem.after(t.placeholder),n.placeholder.update(t,t.placeholder)},_contactContainers:function(t){var n=null,r=null;for(var i=this.containers.length-1;i>=0;i--){if(e.contains(this.currentItem[0],this.containers[i].element[0]))continue;if(this._intersectsWith(this.containers[i].containerCache)){if(n&&e.contains(this.containers[i].element[0],n.element[0]))continue;n=this.containers[i],r=i}else this.containers[i].containerCache.over&&(this.containers[i]._trigger("out",t,this._uiHash(this)),this.containers[i].containerCache.over=0)}if(!n)return;if(this.containers.length===1)this.containers[r]._trigger("over",t,this._uiHash(this)),this.containers[r].containerCache.over=1;else{var s=1e4,o=null,u=this.containers[r].floating?"left":"top",a=this.containers[r].floating?"width":"height",f=this.positionAbs[u]+this.offset.click[u];for(var l=this.items.length-1;l>=0;l--){if(!e.contains(this.containers[r].element[0],this.items[l].item[0]))continue;if(this.items[l].item[0]==this.currentItem[0])continue;var c=this.items[l].item.offset()[u],h=!1;Math.abs(c-f)>Math.abs(c+this.items[l][a]-f)&&(h=!0,c+=this.items[l][a]),Math.abs(c-f)<s&&(s=Math.abs(c-f),o=this.items[l],this.direction=h?"up":"down")}if(!o&&!this.options.dropOnEmpty)return;this.currentContainer=this.containers[r],o?this._rearrange(t,o,null,!0):this._rearrange(t,null,this.containers[r].element,!0),this._trigger("change",t,this._uiHash()),this.containers[r]._trigger("change",t,this._uiHash(this)),this.options.placeholder.update(this.currentContainer,this.placeholder),this.containers[r]._trigger("over",t,this._uiHash(this)),this.containers[r].containerCache.over=1}},_createHelper:function(t){var n=this.options,r=e.isFunction(n.helper)?e(n.helper.apply(this.element[0],[t,this.currentItem])):n.helper=="clone"?this.currentItem.clone():this.currentItem;return r.parents("body").length||e(n.appendTo!="parent"?n.appendTo:this.currentItem[0].parentNode)[0].appendChild(r[0]),r[0]==this.currentItem[0]&&(this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")}),(r[0].style.width==""||n.forceHelperSize)&&r.width(this.currentItem.width()),(r[0].style.height==""||n.forceHelperSize)&&r.height(this.currentItem.height()),r},_adjustOffsetFromHelper:function(t){typeof t=="string"&&(t=t.split(" ")),e.isArray(t)&&(t={left:+t[0],top:+t[1]||0}),"left"in t&&(this.offset.click.left=t.left+this.margins.left),"right"in t&&(this.offset.click.left=this.helperProportions.width-t.right+this.margins.left),"top"in t&&(this.offset.click.top=t.top+this.margins.top),"bottom"in t&&(this.offset.click.top=this.helperProportions.height-t.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var t=this.offsetParent.offset();this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&e.contains(this.scrollParent[0],this.offsetParent[0])&&(t.left+=this.scrollParent.scrollLeft(),t.top+=this.scrollParent.scrollTop());if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&e.ui.ie)t={top:0,left:0};return{top:t.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:t.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var e=this.currentItem.position();return{top:e.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:e.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css("marginLeft"),10)||0,top:parseInt(this.currentItem.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var t=this.options;t.containment=="parent"&&(t.containment=this.helper[0].parentNode);if(t.containment=="document"||t.containment=="window")this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,e(t.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(e(t.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(t.containment)){var n=e(t.containment)[0],r=e(t.containment).offset(),i=e(n).css("overflow")!="hidden";this.containment=[r.left+(parseInt(e(n).css("borderLeftWidth"),10)||0)+(parseInt(e(n).css("paddingLeft"),10)||0)-this.margins.left,r.top+(parseInt(e(n).css("borderTopWidth"),10)||0)+(parseInt(e(n).css("paddingTop"),10)||0)-this.margins.top,r.left+(i?Math.max(n.scrollWidth,n.offsetWidth):n.offsetWidth)-(parseInt(e(n).css("borderLeftWidth"),10)||0)-(parseInt(e(n).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,r.top+(i?Math.max(n.scrollHeight,n.offsetHeight):n.offsetHeight)-(parseInt(e(n).css("borderTopWidth"),10)||0)-(parseInt(e(n).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top]}},_convertPositionTo:function(t,n){n||(n=this.position);var r=t=="absolute"?1:-1,i=this.options,s=this.cssPosition!="absolute"||this.scrollParent[0]!=document&&!!e.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,o=/(html|body)/i.test(s[0].tagName);return{top:n.top+this.offset.relative.top*r+this.offset.parent.top*r-(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():o?0:s.scrollTop())*r,left:n.left+this.offset.relative.left*r+this.offset.parent.left*r-(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():o?0:s.scrollLeft())*r}},_generatePosition:function(t){var n=this.options,r=this.cssPosition!="absolute"||this.scrollParent[0]!=document&&!!e.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,i=/(html|body)/i.test(r[0].tagName);this.cssPosition=="relative"&&(this.scrollParent[0]==document||this.scrollParent[0]==this.offsetParent[0])&&(this.offset.relative=this._getRelativeOffset());var s=t.pageX,o=t.pageY;if(this.originalPosition){this.containment&&(t.pageX-this.offset.click.left<this.containment[0]&&(s=this.containment[0]+this.offset.click.left),t.pageY-this.offset.click.top<this.containment[1]&&(o=this.containment[1]+this.offset.click.top),t.pageX-this.offset.click.left>this.containment[2]&&(s=this.containment[2]+this.offset.click.left),t.pageY-this.offset.click.top>this.containment[3]&&(o=this.containment[3]+this.offset.click.top));if(n.grid){var u=this.originalPageY+Math.round((o-this.originalPageY)/n.grid[1])*n.grid[1];o=this.containment?u-this.offset.click.top<this.containment[1]||u-this.offset.click.top>this.containment[3]?u-this.offset.click.top<this.containment[1]?u+n.grid[1]:u-n.grid[1]:u:u;var a=this.originalPageX+Math.round((s-this.originalPageX)/n.grid[0])*n.grid[0];s=this.containment?a-this.offset.click.left<this.containment[0]||a-this.offset.click.left>this.containment[2]?a-this.offset.click.left<this.containment[0]?a+n.grid[0]:a-n.grid[0]:a:a}}return{top:o-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():i?0:r.scrollTop()),left:s-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():i?0:r.scrollLeft())}},_rearrange:function(e,t,n,r){n?n[0].appendChild(this.placeholder[0]):t.item[0].parentNode.insertBefore(this.placeholder[0],this.direction=="down"?t.item[0]:t.item[0].nextSibling),this.counter=this.counter?++this.counter:1;var i=this.counter;this._delay(function(){i==this.counter&&this.refreshPositions(!r)})},_clear:function(t,n){this.reverting=!1;var r=[];!this._noFinalSort&&this.currentItem.parent().length&&this.placeholder.before(this.currentItem),this._noFinalSort=null;if(this.helper[0]==this.currentItem[0]){for(var i in this._storedCSS)if(this._storedCSS[i]=="auto"||this._storedCSS[i]=="static")this._storedCSS[i]="";this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper")}else this.currentItem.show();this.fromOutside&&!n&&r.push(function(e){this._trigger("receive",e,this._uiHash(this.fromOutside))}),(this.fromOutside||this.domPosition.prev!=this.currentItem.prev().not(".ui-sortable-helper")[0]||this.domPosition.parent!=this.currentItem.parent()[0])&&!n&&r.push(function(e){this._trigger("update",e,this._uiHash())}),this!==this.currentContainer&&(n||(r.push(function(e){this._trigger("remove",e,this._uiHash())}),r.push(function(e){return function(t){e._trigger("receive",t,this._uiHash(this))}}.call(this,this.currentContainer)),r.push(function(e){return function(t){e._trigger("update",t,this._uiHash(this))}}.call(this,this.currentContainer))));for(var i=this.containers.length-1;i>=0;i--)n||r.push(function(e){return function(t){e._trigger("deactivate",t,this._uiHash(this))}}.call(this,this.containers[i])),this.containers[i].containerCache.over&&(r.push(function(e){return function(t){e._trigger("out",t,this._uiHash(this))}}.call(this,this.containers[i])),this.containers[i].containerCache.over=0);this._storedCursor&&e("body").css("cursor",this._storedCursor),this._storedOpacity&&this.helper.css("opacity",this._storedOpacity),this._storedZIndex&&this.helper.css("zIndex",this._storedZIndex=="auto"?"":this._storedZIndex),this.dragging=!1;if(this.cancelHelperRemoval){if(!n){this._trigger("beforeStop",t,this._uiHash());for(var i=0;i<r.length;i++)r[i].call(this,t);this._trigger("stop",t,this._uiHash())}return this.fromOutside=!1,!1}n||this._trigger("beforeStop",t,this._uiHash()),this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.helper[0]!=this.currentItem[0]&&this.helper.remove(),this.helper=null;if(!n){for(var i=0;i<r.length;i++)r[i].call(this,t);this._trigger("stop",t,this._uiHash())}return this.fromOutside=!1,!0},_trigger:function(){e.Widget.prototype._trigger.apply(this,arguments)===!1&&this.cancel()},_uiHash:function(t){var n=t||this;return{helper:n.helper,placeholder:n.placeholder||e([]),position:n.position,originalPosition:n.originalPosition,offset:n.positionAbs,item:n.currentItem,sender:t?t.element:null}}})}(jQuery),jQuery.effects||function(e,t){var n=e.uiBackCompat!==!1,r="ui-effects-";e.effects={effect:{}},function(t,n){function p(e,t,n){var r=a[t.type]||{};return e==null?n||!t.def?null:t.def:(e=r.floor?~~e:parseFloat(e),isNaN(e)?t.def:r.mod?(e+r.mod)%r.mod:0>e?0:r.max<e?r.max:e)}function d(e){var n=o(),r=n._rgba=[];return e=e.toLowerCase(),h(s,function(t,i){var s,o=i.re.exec(e),a=o&&i.parse(o),f=i.space||"rgba";if(a)return s=n[f](a),n[u[f].cache]=s[u[f].cache],r=n._rgba=s._rgba,!1}),r.length?(r.join()==="0,0,0,0"&&t.extend(r,c.transparent),n):c[e]}function v(e,t,n){return n=(n+1)%1,n*6<1?e+(t-e)*n*6:n*2<1?t:n*3<2?e+(t-e)*(2/3-n)*6:e}var r="backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor".split(" "),i=/^([\-+])=\s*(\d+\.?\d*)/,s=[{re:/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,parse:function(e){return[e[1],e[2],e[3],e[4]]}},{re:/rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,parse:function(e){return[e[1]*2.55,e[2]*2.55,e[3]*2.55,e[4]]}},{re:/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,parse:function(e){return[parseInt(e[1],16),parseInt(e[2],16),parseInt(e[3],16)]}},{re:/#([a-f0-9])([a-f0-9])([a-f0-9])/,parse:function(e){return[parseInt(e[1]+e[1],16),parseInt(e[2]+e[2],16),parseInt(e[3]+e[3],16)]}},{re:/hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,space:"hsla",parse:function(e){return[e[1],e[2]/100,e[3]/100,e[4]]}}],o=t.Color=function(e,n,r,i){return new t.Color.fn.parse(e,n,r,i)},u={rgba:{props:{red:{idx:0,type:"byte"},green:{idx:1,type:"byte"},blue:{idx:2,type:"byte"}}},hsla:{props:{hue:{idx:0,type:"degrees"},saturation:{idx:1,type:"percent"},lightness:{idx:2,type:"percent"}}}},a={"byte":{floor:!0,max:255},percent:{max:1},degrees:{mod:360,floor:!0}},f=o.support={},l=t("<p>")[0],c,h=t.each;l.style.cssText="background-color:rgba(1,1,1,.5)",f.rgba=l.style.backgroundColor.indexOf("rgba")>-1,h(u,function(e,t){t.cache="_"+e,t.props.alpha={idx:3,type:"percent",def:1}}),o.fn=t.extend(o.prototype,{parse:function(r,i,s,a){if(r===n)return this._rgba=[null,null,null,null],this;if(r.jquery||r.nodeType)r=t(r).css(i),i=n;var f=this,l=t.type(r),v=this._rgba=[];i!==n&&(r=[r,i,s,a],l="array");if(l==="string")return this.parse(d(r)||c._default);if(l==="array")return h(u.rgba.props,function(e,t){v[t.idx]=p(r[t.idx],t)}),this;if(l==="object")return r instanceof o?h(u,function(e,t){r[t.cache]&&(f[t.cache]=r[t.cache].slice())}):h(u,function(t,n){var i=n.cache;h(n.props,function(e,t){if(!f[i]&&n.to){if(e==="alpha"||r[e]==null)return;f[i]=n.to(f._rgba)}f[i][t.idx]=p(r[e],t,!0)}),f[i]&&e.inArray(null,f[i].slice(0,3))<0&&(f[i][3]=1,n.from&&(f._rgba=n.from(f[i])))}),this},is:function(e){var t=o(e),n=!0,r=this;return h(u,function(e,i){var s,o=t[i.cache];return o&&(s=r[i.cache]||i.to&&i.to(r._rgba)||[],h(i.props,function(e,t){if(o[t.idx]!=null)return n=o[t.idx]===s[t.idx],n})),n}),n},_space:function(){var e=[],t=this;return h(u,function(n,r){t[r.cache]&&e.push(n)}),e.pop()},transition:function(e,t){var n=o(e),r=n._space(),i=u[r],s=this.alpha()===0?o("transparent"):this,f=s[i.cache]||i.to(s._rgba),l=f.slice();return n=n[i.cache],h(i.props,function(e,r){var i=r.idx,s=f[i],o=n[i],u=a[r.type]||{};if(o===null)return;s===null?l[i]=o:(u.mod&&(o-s>u.mod/2?s+=u.mod:s-o>u.mod/2&&(s-=u.mod)),l[i]=p((o-s)*t+s,r))}),this[r](l)},blend:function(e){if(this._rgba[3]===1)return this;var n=this._rgba.slice(),r=n.pop(),i=o(e)._rgba;return o(t.map(n,function(e,t){return(1-r)*i[t]+r*e}))},toRgbaString:function(){var e="rgba(",n=t.map(this._rgba,function(e,t){return e==null?t>2?1:0:e});return n[3]===1&&(n.pop(),e="rgb("),e+n.join()+")"},toHslaString:function(){var e="hsla(",n=t.map(this.hsla(),function(e,t){return e==null&&(e=t>2?1:0),t&&t<3&&(e=Math.round(e*100)+"%"),e});return n[3]===1&&(n.pop(),e="hsl("),e+n.join()+")"},toHexString:function(e){var n=this._rgba.slice(),r=n.pop();return e&&n.push(~~(r*255)),"#"+t.map(n,function(e){return e=(e||0).toString(16),e.length===1?"0"+e:e}).join("")},toString:function(){return this._rgba[3]===0?"transparent":this.toRgbaString()}}),o.fn.parse.prototype=o.fn,u.hsla.to=function(e){if(e[0]==null||e[1]==null||e[2]==null)return[null,null,null,e[3]];var t=e[0]/255,n=e[1]/255,r=e[2]/255,i=e[3],s=Math.max(t,n,r),o=Math.min(t,n,r),u=s-o,a=s+o,f=a*.5,l,c;return o===s?l=0:t===s?l=60*(n-r)/u+360:n===s?l=60*(r-t)/u+120:l=60*(t-n)/u+240,f===0||f===1?c=f:f<=.5?c=u/a:c=u/(2-a),[Math.round(l)%360,c,f,i==null?1:i]},u.hsla.from=function(e){if(e[0]==null||e[1]==null||e[2]==null)return[null,null,null,e[3]];var t=e[0]/360,n=e[1],r=e[2],i=e[3],s=r<=.5?r*(1+n):r+n-r*n,o=2*r-s;return[Math.round(v(o,s,t+1/3)*255),Math.round(v(o,s,t)*255),Math.round(v(o,s,t-1/3)*255),i]},h(u,function(e,r){var s=r.props,u=r.cache,a=r.to,f=r.from;o.fn[e]=function(e){a&&!this[u]&&(this[u]=a(this._rgba));if(e===n)return this[u].slice();var r,i=t.type(e),l=i==="array"||i==="object"?e:arguments,c=this[u].slice();return h(s,function(e,t){var n=l[i==="object"?e:t.idx];n==null&&(n=c[t.idx]),c[t.idx]=p(n,t)}),f?(r=o(f(c)),r[u]=c,r):o(c)},h(s,function(n,r){if(o.fn[n])return;o.fn[n]=function(s){var o=t.type(s),u=n==="alpha"?this._hsla?"hsla":"rgba":e,a=this[u](),f=a[r.idx],l;return o==="undefined"?f:(o==="function"&&(s=s.call(this,f),o=t.type(s)),s==null&&r.empty?this:(o==="string"&&(l=i.exec(s),l&&(s=f+parseFloat(l[2])*(l[1]==="+"?1:-1))),a[r.idx]=s,this[u](a)))}})}),h(r,function(e,n){t.cssHooks[n]={set:function(e,r){var i,s,u="";if(t.type(r)!=="string"||(i=d(r))){r=o(i||r);if(!f.rgba&&r._rgba[3]!==1){s=n==="backgroundColor"?e.parentNode:e;while((u===""||u==="transparent")&&s&&s.style)try{u=t.css(s,"backgroundColor"),s=s.parentNode}catch(a){}r=r.blend(u&&u!=="transparent"?u:"_default")}r=r.toRgbaString()}try{e.style[n]=r}catch(l){}}},t.fx.step[n]=function(e){e.colorInit||(e.start=o(e.elem,n),e.end=o(e.end),e.colorInit=!0),t.cssHooks[n].set(e.elem,e.start.transition(e.end,e.pos))}}),t.cssHooks.borderColor={expand:function(e){var t={};return h(["Top","Right","Bottom","Left"],function(n,r){t["border"+r+"Color"]=e}),t}},c=t.Color.names={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#008000",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00",transparent:[null,null,null,0],_default:"#ffffff"}}(jQuery),function(){function i(){var t=this.ownerDocument.defaultView?this.ownerDocument.defaultView.getComputedStyle(this,null):this.currentStyle,n={},r,i;if(t&&t.length&&t[0]&&t[t[0]]){i=t.length;while(i--)r=t[i],typeof t[r]=="string"&&(n[e.camelCase(r)]=t[r])}else for(r in t)typeof t[r]=="string"&&(n[r]=t[r]);return n}function s(t,n){var i={},s,o;for(s in n)o=n[s],t[s]!==o&&!r[s]&&(e.fx.step[s]||!isNaN(parseFloat(o)))&&(i[s]=o);return i}var n=["add","remove","toggle"],r={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};e.each(["borderLeftStyle","borderRightStyle","borderBottomStyle","borderTopStyle"],function(t,n){e.fx.step[n]=function(e){if(e.end!=="none"&&!e.setAttr||e.pos===1&&!e.setAttr)jQuery.style(e.elem,n,e.end),e.setAttr=!0}}),e.effects.animateClass=function(t,r,o,u){var a=e.speed(r,o,u);return this.queue(function(){var r=e(this),o=r.attr("class")||"",u,f=a.children?r.find("*").andSelf():r;f=f.map(function(){var t=e(this);return{el:t,start:i.call(this)}}),u=function(){e.each(n,function(e,n){t[n]&&r[n+"Class"](t[n])})},u(),f=f.map(function(){return this.end=i.call(this.el[0]),this.diff=s(this.start,this.end),this}),r.attr("class",o),f=f.map(function(){var t=this,n=e.Deferred(),r=jQuery.extend({},a,{queue:!1,complete:function(){n.resolve(t)}});return this.el.animate(this.diff,r),n.promise()}),e.when.apply(e,f.get()).done(function(){u(),e.each(arguments,function(){var t=this.el;e.each(this.diff,function(e){t.css(e,"")})}),a.complete.call(r[0])})})},e.fn.extend({_addClass:e.fn.addClass,addClass:function(t,n,r,i){return n?e.effects.animateClass.call(this,{add:t},n,r,i):this._addClass(t)},_removeClass:e.fn.removeClass,removeClass:function(t,n,r,i){return n?e.effects.animateClass.call(this,{remove:t},n,r,i):this._removeClass(t)},_toggleClass:e.fn.toggleClass,toggleClass:function(n,r,i,s,o){return typeof r=="boolean"||r===t?i?e.effects.animateClass.call(this,r?{add:n}:{remove:n},i,s,o):this._toggleClass(n,r):e.effects.animateClass.call(this,{toggle:n},r,i,s)},switchClass:function(t,n,r,i,s){return e.effects.animateClass.call(this,{add:n,remove:t},r,i,s)}})}(),function(){function i(t,n,r,i){e.isPlainObject(t)&&(n=t,t=t.effect),t={effect:t},n==null&&(n={}),e.isFunction(n)&&(i=n,r=null,n={});if(typeof n=="number"||e.fx.speeds[n])i=r,r=n,n={};return e.isFunction(r)&&(i=r,r=null),n&&e.extend(t,n),r=r||n.duration,t.duration=e.fx.off?0:typeof r=="number"?r:r in e.fx.speeds?e.fx.speeds[r]:e.fx.speeds._default,t.complete=i||n.complete,t}function s(t){return!t||typeof t=="number"||e.fx.speeds[t]?!0:typeof t=="string"&&!e.effects.effect[t]?n&&e.effects[t]?!1:!0:!1}e.extend(e.effects,{version:"1.9.2",save:function(e,t){for(var n=0;n<t.length;n++)t[n]!==null&&e.data(r+t[n],e[0].style[t[n]])},restore:function(e,n){var i,s;for(s=0;s<n.length;s++)n[s]!==null&&(i=e.data(r+n[s]),i===t&&(i=""),e.css(n[s],i))},setMode:function(e,t){return t==="toggle"&&(t=e.is(":hidden")?"show":"hide"),t},getBaseline:function(e,t){var n,r;switch(e[0]){case"top":n=0;break;case"middle":n=.5;break;case"bottom":n=1;break;default:n=e[0]/t.height}switch(e[1]){case"left":r=0;break;case"center":r=.5;break;case"right":r=1;break;default:r=e[1]/t.width}return{x:r,y:n}},createWrapper:function(t){if(t.parent().is(".ui-effects-wrapper"))return t.parent();var n={width:t.outerWidth(!0),height:t.outerHeight(!0),"float":t.css("float")},r=e("<div></div>").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),i={width:t.width(),height:t.height()},s=document.activeElement;try{s.id}catch(o){s=document.body}return t.wrap(r),(t[0]===s||e.contains(t[0],s))&&e(s).focus(),r=t.parent(),t.css("position")==="static"?(r.css({position:"relative"}),t.css({position:"relative"})):(e.extend(n,{position:t.css("position"),zIndex:t.css("z-index")}),e.each(["top","left","bottom","right"],function(e,r){n[r]=t.css(r),isNaN(parseInt(n[r],10))&&(n[r]="auto")}),t.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),t.css(i),r.css(n).show()},removeWrapper:function(t){var n=document.activeElement;return t.parent().is(".ui-effects-wrapper")&&(t.parent().replaceWith(t),(t[0]===n||e.contains(t[0],n))&&e(n).focus()),t},setTransition:function(t,n,r,i){return i=i||{},e.each(n,function(e,n){var s=t.cssUnit(n);s[0]>0&&(i[n]=s[0]*r+s[1])}),i}}),e.fn.extend({effect:function(){function a(n){function u(){e.isFunction(i)&&i.call(r[0]),e.isFunction(n)&&n()}var r=e(this),i=t.complete,s=t.mode;(r.is(":hidden")?s==="hide":s==="show")?u():o.call(r[0],t,u)}var t=i.apply(this,arguments),r=t.mode,s=t.queue,o=e.effects.effect[t.effect],u=!o&&n&&e.effects[t.effect];return e.fx.off||!o&&!u?r?this[r](t.duration,t.complete):this.each(function(){t.complete&&t.complete.call(this)}):o?s===!1?this.each(a):this.queue(s||"fx",a):u.call(this,{options:t,duration:t.duration,callback:t.complete,mode:t.mode})},_show:e.fn.show,show:function(e){if(s(e))return this._show.apply(this,arguments);var t=i.apply(this,arguments);return t.mode="show",this.effect.call(this,t)},_hide:e.fn.hide,hide:function(e){if(s(e))return this._hide.apply(this,arguments);var t=i.apply(this,arguments);return t.mode="hide",this.effect.call(this,t)},__toggle:e.fn.toggle,toggle:function(t){if(s(t)||typeof t=="boolean"||e.isFunction(t))return this.__toggle.apply(this,arguments);var n=i.apply(this,arguments);return n.mode="toggle",this.effect.call(this,n)},cssUnit:function(t){var n=this.css(t),r=[];return e.each(["em","px","%","pt"],function(e,t){n.indexOf(t)>0&&(r=[parseFloat(n),t])}),r}})}(),function(){var t={};e.each(["Quad","Cubic","Quart","Quint","Expo"],function(e,n){t[n]=function(t){return Math.pow(t,e+2)}}),e.extend(t,{Sine:function(e){return 1-Math.cos(e*Math.PI/2)},Circ:function(e){return 1-Math.sqrt(1-e*e)},Elastic:function(e){return e===0||e===1?e:-Math.pow(2,8*(e-1))*Math.sin(((e-1)*80-7.5)*Math.PI/15)},Back:function(e){return e*e*(3*e-2)},Bounce:function(e){var t,n=4;while(e<((t=Math.pow(2,--n))-1)/11);return 1/Math.pow(4,3-n)-7.5625*Math.pow((t*3-2)/22-e,2)}}),e.each(t,function(t,n){e.easing["easeIn"+t]=n,e.easing["easeOut"+t]=function(e){return 1-n(1-e)},e.easing["easeInOut"+t]=function(e){return e<.5?n(e*2)/2:1-n(e*-2+2)/2}})}()}(jQuery),function(e,t){var n=0,r={},i={};r.height=r.paddingTop=r.paddingBottom=r.borderTopWidth=r.borderBottomWidth="hide",i.height=i.paddingTop=i.paddingBottom=i.borderTopWidth=i.borderBottomWidth="show",e.widget("ui.accordion",{version:"1.9.2",options:{active:0,animate:{},collapsible:!1,event:"click",header:"> li > :first-child,> :not(li):even",heightStyle:"auto",icons:{activeHeader:"ui-icon-triangle-1-s",header:"ui-icon-triangle-1-e"},activate:null,beforeActivate:null},_create:function(){var t=this.accordionId="ui-accordion-"+(this.element.attr("id")||++n),r=this.options;this.prevShow=this.prevHide=e(),this.element.addClass("ui-accordion ui-widget ui-helper-reset"),this.headers=this.element.find(r.header).addClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all"),this._hoverable(this.headers),this._focusable(this.headers),this.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom").hide(),!r.collapsible&&(r.active===!1||r.active==null)&&(r.active=0),r.active<0&&(r.active+=this.headers.length),this.active=this._findActive(r.active).addClass("ui-accordion-header-active ui-state-active").toggleClass("ui-corner-all ui-corner-top"),this.active.next().addClass("ui-accordion-content-active").show(),this._createIcons(),this.refresh(),this.element.attr("role","tablist"),this.headers.attr("role","tab").each(function(n){var r=e(this),i=r.attr("id"),s=r.next(),o=s.attr("id");i||(i=t+"-header-"+n,r.attr("id",i)),o||(o=t+"-panel-"+n,s.attr("id",o)),r.attr("aria-controls",o),s.attr("aria-labelledby",i)}).next().attr("role","tabpanel"),this.headers.not(this.active).attr({"aria-selected":"false",tabIndex:-1}).next().attr({"aria-expanded":"false","aria-hidden":"true"}).hide(),this.active.length?this.active.attr({"aria-selected":"true",tabIndex:0}).next().attr({"aria-expanded":"true","aria-hidden":"false"}):this.headers.eq(0).attr("tabIndex",0),this._on(this.headers,{keydown:"_keydown"}),this._on(this.headers.next(),{keydown:"_panelKeyDown"}),this._setupEvents(r.event)},_getCreateEventData:function(){return{header:this.active,content:this.active.length?this.active.next():e()}},_createIcons:function(){var t=this.options.icons;t&&(e("<span>").addClass("ui-accordion-header-icon ui-icon "+t.header).prependTo(this.headers),this.active.children(".ui-accordion-header-icon").removeClass(t.header).addClass(t.activeHeader),this.headers.addClass("ui-accordion-icons"))},_destroyIcons:function(){this.headers.removeClass("ui-accordion-icons").children(".ui-accordion-header-icon").remove()},_destroy:function(){var e;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role"),this.headers.removeClass("ui-accordion-header ui-accordion-header-active ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top").removeAttr("role").removeAttr("aria-selected").removeAttr("aria-controls").removeAttr("tabIndex").each(function(){/^ui-accordion/.test(this.id)&&this.removeAttribute("id")}),this._destroyIcons(),e=this.headers.next().css("display","").removeAttr("role").removeAttr("aria-expanded").removeAttr("aria-hidden").removeAttr("aria-labelledby").removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-state-disabled").each(function(){/^ui-accordion/.test(this.id)&&this.removeAttribute("id")}),this.options.heightStyle!=="content"&&e.css("height","")},_setOption:function(e,t){if(e==="active"){this._activate(t);return}e==="event"&&(this.options.event&&this._off(this.headers,this.options.event),this._setupEvents(t)),this._super(e,t),e==="collapsible"&&!t&&this.options.active===!1&&this._activate(0),e==="icons"&&(this._destroyIcons(),t&&this._createIcons()),e==="disabled"&&this.headers.add(this.headers.next()).toggleClass("ui-state-disabled",!!t)},_keydown:function(t){if(t.altKey||t.ctrlKey)return;var n=e.ui.keyCode,r=this.headers.length,i=this.headers.index(t.target),s=!1;switch(t.keyCode){case n.RIGHT:case n.DOWN:s=this.headers[(i+1)%r];break;case n.LEFT:case n.UP:s=this.headers[(i-1+r)%r];break;case n.SPACE:case n.ENTER:this._eventHandler(t);break;case n.HOME:s=this.headers[0];break;case n.END:s=this.headers[r-1]}s&&(e(t.target).attr("tabIndex",-1),e(s).attr("tabIndex",0),s.focus(),t.preventDefault())},_panelKeyDown:function(t){t.keyCode===e.ui.keyCode.UP&&t.ctrlKey&&e(t.currentTarget).prev().focus()},refresh:function(){var t,n,r=this.options.heightStyle,i=this.element.parent();r==="fill"?(e.support.minHeight||(n=i.css("overflow"),i.css("overflow","hidden")),t=i.height(),this.element.siblings(":visible").each(function(){var n=e(this),r=n.css("position");if(r==="absolute"||r==="fixed")return;t-=n.outerHeight(!0)}),n&&i.css("overflow",n),this.headers.each(function(){t-=e(this).outerHeight(!0)}),this.headers.next().each(function(){e(this).height(Math.max(0,t-e(this).innerHeight()+e(this).height()))}).css("overflow","auto")):r==="auto"&&(t=0,this.headers.next().each(function(){t=Math.max(t,e(this).css("height","").height())}).height(t))},_activate:function(t){var n=this._findActive(t)[0];if(n===this.active[0])return;n=n||this.active[0],this._eventHandler({target:n,currentTarget:n,preventDefault:e.noop})},_findActive:function(t){return typeof t=="number"?this.headers.eq(t):e()},_setupEvents:function(t){var n={};if(!t)return;e.each(t.split(" "),function(e,t){n[t]="_eventHandler"}),this._on(this.headers,n)},_eventHandler:function(t){var n=this.options,r=this.active,i=e(t.currentTarget),s=i[0]===r[0],o=s&&n.collapsible,u=o?e():i.next(),a=r.next(),f={oldHeader:r,oldPanel:a,newHeader:o?e():i,newPanel:u};t.preventDefault();if(s&&!n.collapsible||this._trigger("beforeActivate",t,f)===!1)return;n.active=o?!1:this.headers.index(i),this.active=s?e():i,this._toggle(f),r.removeClass("ui-accordion-header-active ui-state-active"),n.icons&&r.children(".ui-accordion-header-icon").removeClass(n.icons.activeHeader).addClass(n.icons.header),s||(i.removeClass("ui-corner-all").addClass("ui-accordion-header-active ui-state-active ui-corner-top"),n.icons&&i.children(".ui-accordion-header-icon").removeClass(n.icons.header).addClass(n.icons.activeHeader),i.next().addClass("ui-accordion-content-active"))},_toggle:function(t){var n=t.newPanel,r=this.prevShow.length?this.prevShow:t.oldPanel;this.prevShow.add(this.prevHide).stop(!0,!0),this.prevShow=n,this.prevHide=r,this.options.animate?this._animate(n,r,t):(r.hide(),n.show(),this._toggleComplete(t)),r.attr({"aria-expanded":"false","aria-hidden":"true"}),r.prev().attr("aria-selected","false"),n.length&&r.length?r.prev().attr("tabIndex",-1):n.length&&this.headers.filter(function(){return e(this).attr("tabIndex")===0}).attr("tabIndex",-1),n.attr({"aria-expanded":"true","aria-hidden":"false"}).prev().attr({"aria-selected":"true",tabIndex:0})},_animate:function(e,t,n){var s,o,u,a=this,f=0,l=e.length&&(!t.length||e.index()<t.index()),c=this.options.animate||{},h=l&&c.down||c,p=function(){a._toggleComplete(n)};typeof h=="number"&&(u=h),typeof h=="string"&&(o=h),o=o||h.easing||c.easing,u=u||h.duration||c.duration;if(!t.length)return e.animate(i,u,o,p);if(!e.length)return t.animate(r,u,o,p);s=e.show().outerHeight(),t.animate(r,{duration:u,easing:o,step:function(e,t){t.now=Math.round(e)}}),e.hide().animate(i,{duration:u,easing:o,complete:p,step:function(e,n){n.now=Math.round(e),n.prop!=="height"?f+=n.now:a.options.heightStyle!=="content"&&(n.now=Math.round(s-t.outerHeight()-f),f=0)}})},_toggleComplete:function(e){var t=e.oldPanel;t.removeClass("ui-accordion-content-active").prev().removeClass("ui-corner-top").addClass("ui-corner-all"),t.length&&(t.parent()[0].className=t.parent()[0].className),this._trigger("activate",null,e)}}),e.uiBackCompat!==!1&&(function(e,t){e.extend(t.options,{navigation:!1,navigationFilter:function(){return this.href.toLowerCase()===location.href.toLowerCase()}});var n=t._create;t._create=function(){if(this.options.navigation){var t=this,r=this.element.find(this.options.header),i=r.next(),s=r.add(i).find("a").filter(this.options.navigationFilter)[0];s&&r.add(i).each(function(n){if(e.contains(this,s))return t.options.active=Math.floor(n/2),!1})}n.call(this)}}(jQuery,jQuery.ui.accordion.prototype),function(e,t){e.extend(t.options,{heightStyle:null,autoHeight:!0,clearStyle:!1,fillSpace:!1});var n=t._create,r=t._setOption;e.extend(t,{_create:function(){this.options.heightStyle=this.options.heightStyle||this._mergeHeightStyle(),n.call(this)},_setOption:function(e){if(e==="autoHeight"||e==="clearStyle"||e==="fillSpace")this.options.heightStyle=this._mergeHeightStyle();r.apply(this,arguments)},_mergeHeightStyle:function(){var e=this.options;if(e.fillSpace)return"fill";if(e.clearStyle)return"content";if(e.autoHeight)return"auto"}})}(jQuery,jQuery.ui.accordion.prototype),function(e,t){e.extend(t.options.icons,{activeHeader:null,headerSelected:"ui-icon-triangle-1-s"});var n=t._createIcons;t._createIcons=function(){this.options.icons&&(this.options.icons.activeHeader=this.options.icons.activeHeader||this.options.icons.headerSelected),n.call(this)}}(jQuery,jQuery.ui.accordion.prototype),function(e,t){t.activate=t._activate;var n=t._findActive;t._findActive=function(e){return e===-1&&(e=!1),e&&typeof e!="number"&&(e=this.headers.index(this.headers.filter(e)),e===-1&&(e=!1)),n.call(this,e)}}(jQuery,jQuery.ui.accordion.prototype),jQuery.ui.accordion.prototype.resize=jQuery.ui.accordion.prototype.refresh,function(e,t){e.extend(t.options,{change:null,changestart:null});var n=t._trigger;t._trigger=function(e,t,r){var i=n.apply(this,arguments);return i?(e==="beforeActivate"?i=n.call(this,"changestart",t,{oldHeader:r.oldHeader,oldContent:r.oldPanel,newHeader:r.newHeader,newContent:r.newPanel}):e==="activate"&&(i=n.call(this,"change",t,{oldHeader:r.oldHeader,oldContent:r.oldPanel,newHeader:r.newHeader,newContent:r.newPanel})),i):!1}}(jQuery,jQuery.ui.accordion.prototype),function(e,t){e.extend(t.options,{animate:null,animated:"slide"});var n=t._create;t._create=function(){var e=this.options;e.animate===null&&(e.animated?e.animated==="slide"?e.animate=300:e.animated==="bounceslide"?e.animate={duration:200,down:{easing:"easeOutBounce",duration:1e3}}:e.animate=e.animated:e.animate=!1),n.call(this)}}(jQuery,jQuery.ui.accordion.prototype))}(jQuery),function(e,t){var n=0;e.widget("ui.autocomplete",{version:"1.9.2",defaultElement:"<input>",options:{appendTo:"body",autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},pending:0,_create:function(){var t,n,r;this.isMultiLine=this._isMultiLine(),this.valueMethod=this.element[this.element.is("input,textarea")?"val":"text"],this.isNewMenu=!0,this.element.addClass("ui-autocomplete-input").attr("autocomplete","off"),this._on(this.element,{keydown:function(i){if(this.element.prop("readOnly")){t=!0,r=!0,n=!0;return}t=!1,r=!1,n=!1;var s=e.ui.keyCode;switch(i.keyCode){case s.PAGE_UP:t=!0,this._move("previousPage",i);break;case s.PAGE_DOWN:t=!0,this._move("nextPage",i);break;case s.UP:t=!0,this._keyEvent("previous",i);break;case s.DOWN:t=!0,this._keyEvent("next",i);break;case s.ENTER:case s.NUMPAD_ENTER:this.menu.active&&(t=!0,i.preventDefault(),this.menu.select(i));break;case s.TAB:this.menu.active&&this.menu.select(i);break;case s.ESCAPE:this.menu.element.is(":visible")&&(this._value(this.term),this.close(i),i.preventDefault());break;default:n=!0,this._searchTimeout(i)}},keypress:function(r){if(t){t=!1,r.preventDefault();return}if(n)return;var i=e.ui.keyCode;switch(r.keyCode){case i.PAGE_UP:this._move("previousPage",r);break;case i.PAGE_DOWN:this._move("nextPage",r);break;case i.UP:this._keyEvent("previous",r);break;case i.DOWN:this._keyEvent("next",r)}},input:function(e){if(r){r=!1,e.preventDefault();return}this._searchTimeout(e)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(e){if(this.cancelBlur){delete this.cancelBlur;return}clearTimeout(this.searching),this.close(e),this._change(e)}}),this._initSource(),this.menu=e("<ul>").addClass("ui-autocomplete").appendTo(this.document.find(this.options.appendTo||"body")[0]).menu({input:e(),role:null}).zIndex(this.element.zIndex()+1).hide().data("menu"),this._on(this.menu.element,{mousedown:function(t){t.preventDefault(),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur});var n=this.menu.element[0];e(t.target).closest(".ui-menu-item").length||this._delay(function(){var t=this;this.document.one("mousedown",function(r){r.target!==t.element[0]&&r.target!==n&&!e.contains(n,r.target)&&t.close()})})},menufocus:function(t,n){if(this.isNewMenu){this.isNewMenu=!1;if(t.originalEvent&&/^mouse/.test(t.originalEvent.type)){this.menu.blur(),this.document.one("mousemove",function(){e(t.target).trigger(t.originalEvent)});return}}var r=n.item.data("ui-autocomplete-item")||n.item.data("item.autocomplete");!1!==this._trigger("focus",t,{item:r})?t.originalEvent&&/^key/.test(t.originalEvent.type)&&this._value(r.value):this.liveRegion.text(r.value)},menuselect:function(e,t){var n=t.item.data("ui-autocomplete-item")||t.item.data("item.autocomplete"),r=this.previous;this.element[0]!==this.document[0].activeElement&&(this.element.focus(),this.previous=r,this._delay(function(){this.previous=r,this.selectedItem=n})),!1!==this._trigger("select",e,{item:n})&&this._value(n.value),this.term=this._value(),this.close(e),this.selectedItem=n}}),this.liveRegion=e("<span>",{role:"status","aria-live":"polite"}).addClass("ui-helper-hidden-accessible").insertAfter(this.element),e.fn.bgiframe&&this.menu.element.bgiframe(),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_destroy:function(){clearTimeout(this.searching),this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete"),this.menu.element.remove(),this.liveRegion.remove()},_setOption:function(e,t){this._super(e,t),e==="source"&&this._initSource(),e==="appendTo"&&this.menu.element.appendTo(this.document.find(t||"body")[0]),e==="disabled"&&t&&this.xhr&&this.xhr.abort()},_isMultiLine:function(){return this.element.is("textarea")?!0:this.element.is("input")?!1:this.element.prop("isContentEditable")},_initSource:function(){var t,n,r=this;e.isArray(this.options.source)?(t=this.options.source,this.source=function(n,r){r(e.ui.autocomplete.filter(t,n.term))}):typeof this.options.source=="string"?(n=this.options.source,this.source=function(t,i){r.xhr&&r.xhr.abort(),r.xhr=e.ajax({url:n,data:t,dataType:"json",success:function(e){i(e)},error:function(){i([])}})}):this.source=this.options.source},_searchTimeout:function(e){clearTimeout(this.searching),this.searching=this._delay(function(){this.term!==this._value()&&(this.selectedItem=null,this.search(null,e))},this.options.delay)},search:function(e,t){e=e!=null?e:this._value(),this.term=this._value();if(e.length<this.options.minLength)return this.close(t);if(this._trigger("search",t)===!1)return;return this._search(e)},_search:function(e){this.pending++,this.element.addClass("ui-autocomplete-loading"),this.cancelSearch=!1,this.source({term:e},this._response())},_response:function(){var e=this,t=++n;return function(r){t===n&&e.__response(r),e.pending--,e.pending||e.element.removeClass("ui-autocomplete-loading")}},__response:function(e){e&&(e=this._normalize(e)),this._trigger("response",null,{content:e}),!this.options.disabled&&e&&e.length&&!this.cancelSearch?(this._suggest(e),this._trigger("open")):this._close()},close:function(e){this.cancelSearch=!0,this._close(e)},_close:function(e){this.menu.element.is(":visible")&&(this.menu.element.hide(),this.menu.blur(),this.isNewMenu=!0,this._trigger("close",e))},_change:function(e){this.previous!==this._value()&&this._trigger("change",e,{item:this.selectedItem})},_normalize:function(t){return t.length&&t[0].label&&t[0].value?t:e.map(t,function(t){return typeof t=="string"?{label:t,value:t}:e.extend({label:t.label||t.value,value:t.value||t.label},t)})},_suggest:function(t){var n=this.menu.element.empty().zIndex(this.element.zIndex()+1);this._renderMenu(n,t),this.menu.refresh(),n.show(),this._resizeMenu(),n.position(e.extend({of:this.element},this.options.position)),this.options.autoFocus&&this.menu.next()},_resizeMenu:function(){var e=this.menu.element;e.outerWidth(Math.max(e.width("").outerWidth()+1,this.element.outerWidth()))},_renderMenu:function(t,n){var r=this;e.each(n,function(e,n){r._renderItemData(t,n)})},_renderItemData:function(e,t){return this._renderItem(e,t).data("ui-autocomplete-item",t)},_renderItem:function(t,n){return e("<li>").append(e("<a>").text(n.label)).appendTo(t)},_move:function(e,t){if(!this.menu.element.is(":visible")){this.search(null,t);return}if(this.menu.isFirstItem()&&/^previous/.test(e)||this.menu.isLastItem()&&/^next/.test(e)){this._value(this.term),this.menu.blur();return}this.menu[e](t)},widget:function(){return this.menu.element},_value:function(){return this.valueMethod.apply(this.element,arguments)},_keyEvent:function(e,t){if(!this.isMultiLine||this.menu.element.is(":visible"))this._move(e,t),t.preventDefault()}}),e.extend(e.ui.autocomplete,{escapeRegex:function(e){return e.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")},filter:function(t,n){var r=new RegExp(e.ui.autocomplete.escapeRegex(n),"i");return e.grep(t,function(e){return r.test(e.label||e.value||e)})}}),e.widget("ui.autocomplete",e.ui.autocomplete,{options:{messages:{noResults:"No search results.",results:function(e){return e+(e>1?" results are":" result is")+" available, use up and down arrow keys to navigate."}}},__response:function(e){var t;this._superApply(arguments);if(this.options.disabled||this.cancelSearch)return;e&&e.length?t=this.options.messages.results(e.length):t=this.options.messages.noResults,this.liveRegion.text(t)}})}(jQuery),function(e,t){var n,r,i,s,o="ui-button ui-widget ui-state-default ui-corner-all",u="ui-state-hover ui-state-active ",a="ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only",f=function(){var t=e(this).find(":ui-button");setTimeout(function(){t.button("refresh")},1)},l=function(t){var n=t.name,r=t.form,i=e([]);return n&&(r?i=e(r).find("[name='"+n+"']"):i=e("[name='"+n+"']",t.ownerDocument).filter(function(){return!this.form})),i};e.widget("ui.button",{version:"1.9.2",defaultElement:"<button>",options:{disabled:null,text:!0,label:null,icons:{primary:null,secondary:null}},_create:function(){this.element.closest("form").unbind("reset"+this.eventNamespace).bind("reset"+this.eventNamespace,f),typeof this.options.disabled!="boolean"?this.options.disabled=!!this.element.prop("disabled"):this.element.prop("disabled",this.options.disabled),this._determineButtonType(),this.hasTitle=!!this.buttonElement.attr("title");var t=this,u=this.options,a=this.type==="checkbox"||this.type==="radio",c=a?"":"ui-state-active",h="ui-state-focus";u.label===null&&(u.label=this.type==="input"?this.buttonElement.val():this.buttonElement.html()),this._hoverable(this.buttonElement),this.buttonElement.addClass(o).attr("role","button").bind("mouseenter"+this.eventNamespace,function(){if(u.disabled)return;this===n&&e(this).addClass("ui-state-active")}).bind("mouseleave"+this.eventNamespace,function(){if(u.disabled)return;e(this).removeClass(c)}).bind("click"+this.eventNamespace,function(e){u.disabled&&(e.preventDefault(),e.stopImmediatePropagation())}),this.element.bind("focus"+this.eventNamespace,function(){t.buttonElement.addClass(h)}).bind("blur"+this.eventNamespace,function(){t.buttonElement.removeClass(h)}),a&&(this.element.bind("change"+this.eventNamespace,function(){if(s)return;t.refresh()}),this.buttonElement.bind("mousedown"+this.eventNamespace,function(e){if(u.disabled)return;s=!1,r=e.pageX,i=e.pageY}).bind("mouseup"+this.eventNamespace,function(e){if(u.disabled)return;if(r!==e.pageX||i!==e.pageY)s=!0})),this.type==="checkbox"?this.buttonElement.bind("click"+this.eventNamespace,function(){if(u.disabled||s)return!1;e(this).toggleClass("ui-state-active"),t.buttonElement.attr("aria-pressed",t.element[0].checked)}):this.type==="radio"?this.buttonElement.bind("click"+this.eventNamespace,function(){if(u.disabled||s)return!1;e(this).addClass("ui-state-active"),t.buttonElement.attr("aria-pressed","true");var n=t.element[0];l(n).not(n).map(function(){return e(this).button("widget")[0]}).removeClass("ui-state-active").attr("aria-pressed","false")}):(this.buttonElement.bind("mousedown"+this.eventNamespace,function(){if(u.disabled)return!1;e(this).addClass("ui-state-active"),n=this,t.document.one("mouseup",function(){n=null})}).bind("mouseup"+this.eventNamespace,function(){if(u.disabled)return!1;e(this).removeClass("ui-state-active")}).bind("keydown"+this.eventNamespace,function(t){if(u.disabled)return!1;(t.keyCode===e.ui.keyCode.SPACE||t.keyCode===e.ui.keyCode.ENTER)&&e(this).addClass("ui-state-active")}).bind("keyup"+this.eventNamespace,function(){e(this).removeClass("ui-state-active")}),this.buttonElement.is("a")&&this.buttonElement.keyup(function(t){t.keyCode===e.ui.keyCode.SPACE&&e(this).click()})),this._setOption("disabled",u.disabled),this._resetButton()},_determineButtonType:function(){var e,t,n;this.element.is("[type=checkbox]")?this.type="checkbox":this.element.is("[type=radio]")?this.type="radio":this.element.is("input")?this.type="input":this.type="button",this.type==="checkbox"||this.type==="radio"?(e=this.element.parents().last(),t="label[for='"+this.element.attr("id")+"']",this.buttonElement=e.find(t),this.buttonElement.length||(e=e.length?e.siblings():this.element.siblings(),this.buttonElement=e.filter(t),this.buttonElement.length||(this.buttonElement=e.find(t))),this.element.addClass("ui-helper-hidden-accessible"),n=this.element.is(":checked"),n&&this.buttonElement.addClass("ui-state-active"),this.buttonElement.prop("aria-pressed",n)):this.buttonElement=this.element},widget:function(){return this.buttonElement},_destroy:function(){this.element.removeClass("ui-helper-hidden-accessible"),this.buttonElement.removeClass(o+" "+u+" "+a).removeAttr("role").removeAttr("aria-pressed").html(this.buttonElement.find(".ui-button-text").html()),this.hasTitle||this.buttonElement.removeAttr("title")},_setOption:function(e,t){this._super(e,t);if(e==="disabled"){t?this.element.prop("disabled",!0):this.element.prop("disabled",!1);return}this._resetButton()},refresh:function(){var t=this.element.is("input, button")?this.element.is(":disabled"):this.element.hasClass("ui-button-disabled");t!==this.options.disabled&&this._setOption("disabled",t),this.type==="radio"?l(this.element[0]).each(function(){e(this).is(":checked")?e(this).button("widget").addClass("ui-state-active").attr("aria-pressed","true"):e(this).button("widget").removeClass("ui-state-active").attr("aria-pressed","false")}):this.type==="checkbox"&&(this.element.is(":checked")?this.buttonElement.addClass("ui-state-active").attr("aria-pressed","true"):this.buttonElement.removeClass("ui-state-active").attr("aria-pressed","false"))},_resetButton:function(){if(this.type==="input"){this.options.label&&this.element.val(this.options.label);return}var t=this.buttonElement.removeClass(a),n=e("<span></span>",this.document[0]).addClass("ui-button-text").html(this.options.label).appendTo(t.empty()).text(),r=this.options.icons,i=r.primary&&r.secondary,s=[];r.primary||r.secondary?(this.options.text&&s.push("ui-button-text-icon"+(i?"s":r.primary?"-primary":"-secondary")),r.primary&&t.prepend("<span class='ui-button-icon-primary ui-icon "+r.primary+"'></span>"),r.secondary&&t.append("<span class='ui-button-icon-secondary ui-icon "+r.secondary+"'></span>"),this.options.text||(s.push(i?"ui-button-icons-only":"ui-button-icon-only"),this.hasTitle||t.attr("title",e.trim(n)))):s.push("ui-button-text-only"),t.addClass(s.join(" "))}}),e.widget("ui.buttonset",{version:"1.9.2",options:{items:"button, input[type=button], input[type=submit], input[type=reset], input[type=checkbox], input[type=radio], a, :data(button)"},_create:function(){this.element.addClass("ui-buttonset")},_init:function(){this.refresh()},_setOption:function(e,t){e==="disabled"&&this.buttons.button("option",e,t),this._super(e,t)},refresh:function(){var t=this.element.css("direction")==="rtl";this.buttons=this.element.find(this.options.items).filter(":ui-button").button("refresh").end().not(":ui-button").button().end().map(function(){return e(this).button("widget")[0]}).removeClass("ui-corner-all ui-corner-left ui-corner-right").filter(":first").addClass(t?"ui-corner-right":"ui-corner-left").end().filter(":last").addClass(t?"ui-corner-left":"ui-corner-right").end().end()},_destroy:function(){this.element.removeClass("ui-buttonset"),this.buttons.map(function(){return e(this).button("widget")[0]}).removeClass("ui-corner-left ui-corner-right").end().button("destroy")}})}(jQuery),function($,undefined){function Datepicker(){this.debug=!1,this._curInst=null,this._keyEvent=!1,this._disabledInputs=[],this._datepickerShowing=!1,this._inDialog=!1,this._mainDivId="ui-datepicker-div",this._inlineClass="ui-datepicker-inline",this._appendClass="ui-datepicker-append",this._triggerClass="ui-datepicker-trigger",this._dialogClass="ui-datepicker-dialog",this._disableClass="ui-datepicker-disabled",this._unselectableClass="ui-datepicker-unselectable",this._currentClass="ui-datepicker-current-day",this._dayOverClass="ui-datepicker-days-cell-over",this.regional=[],this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"mm/dd/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},this._defaults={showOn:"focus",showAnim:"fadeIn",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:!1,hideIfNoPrevNext:!1,navigationAsDateFormat:!1,gotoCurrent:!1,changeMonth:!1,changeYear:!1,yearRange:"c-10:c+10",showOtherMonths:!1,selectOtherMonths:!1,showWeek:!1,calculateWeek:this.iso8601Week,shortYearCutoff:"+10",minDate:null,maxDate:null,duration:"fast",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:!0,showButtonPanel:!1,autoSize:!1,disabled:!1},$.extend(this._defaults,this.regional[""]),this.dpDiv=bindHover($('<div id="'+this._mainDivId+'" class="ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>'))}function bindHover(e){var t="button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";return e.delegate(t,"mouseout",function(){$(this).removeClass("ui-state-hover"),this.className.indexOf("ui-datepicker-prev")!=-1&&$(this).removeClass("ui-datepicker-prev-hover"),this.className.indexOf("ui-datepicker-next")!=-1&&$(this).removeClass("ui-datepicker-next-hover")}).delegate(t,"mouseover",function(){$.datepicker._isDisabledDatepicker(instActive.inline?e.parent()[0]:instActive.input[0])||($(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"),$(this).addClass("ui-state-hover"),this.className.indexOf("ui-datepicker-prev")!=-1&&$(this).addClass("ui-datepicker-prev-hover"),this.className.indexOf("ui-datepicker-next")!=-1&&$(this).addClass("ui-datepicker-next-hover"))})}function extendRemove(e,t){$.extend(e,t);for(var n in t)if(t[n]==null||t[n]==undefined)e[n]=t[n];return e}$.extend($.ui,{datepicker:{version:"1.9.2"}});var PROP_NAME="datepicker",dpuuid=(new Date).getTime(),instActive;$.extend(Datepicker.prototype,{markerClassName:"hasDatepicker",maxRows:4,log:function(){this.debug&&console.log.apply("",arguments)},_widgetDatepicker:function(){return this.dpDiv},setDefaults:function(e){return extendRemove(this._defaults,e||{}),this},_attachDatepicker:function(target,settings){var inlineSettings=null;for(var attrName in this._defaults){var attrValue=target.getAttribute("date:"+attrName);if(attrValue){inlineSettings=inlineSettings||{};try{inlineSettings[attrName]=eval(attrValue)}catch(err){inlineSettings[attrName]=attrValue}}}var nodeName=target.nodeName.toLowerCase(),inline=nodeName=="div"||nodeName=="span";target.id||(this.uuid+=1,target.id="dp"+this.uuid);var inst=this._newInst($(target),inline);inst.settings=$.extend({},settings||{},inlineSettings||{}),nodeName=="input"?this._connectDatepicker(target,inst):inline&&this._inlineDatepicker(target,inst)},_newInst:function(e,t){var n=e[0].id.replace(/([^A-Za-z0-9_-])/g,"\\\\$1");return{id:n,input:e,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:t,dpDiv:t?bindHover($('<div class="'+this._inlineClass+' ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>')):this.dpDiv}},_connectDatepicker:function(e,t){var n=$(e);t.append=$([]),t.trigger=$([]);if(n.hasClass(this.markerClassName))return;this._attachments(n,t),n.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).keyup(this._doKeyUp).bind("setData.datepicker",function(e,n,r){t.settings[n]=r}).bind("getData.datepicker",function(e,n){return this._get(t,n)}),this._autoSize(t),$.data(e,PROP_NAME,t),t.settings.disabled&&this._disableDatepicker(e)},_attachments:function(e,t){var n=this._get(t,"appendText"),r=this._get(t,"isRTL");t.append&&t.append.remove(),n&&(t.append=$('<span class="'+this._appendClass+'">'+n+"</span>"),e[r?"before":"after"](t.append)),e.unbind("focus",this._showDatepicker),t.trigger&&t.trigger.remove();var i=this._get(t,"showOn");(i=="focus"||i=="both")&&e.focus(this._showDatepicker);if(i=="button"||i=="both"){var s=this._get(t,"buttonText"),o=this._get(t,"buttonImage");t.trigger=$(this._get(t,"buttonImageOnly")?$("<img/>").addClass(this._triggerClass).attr({src:o,alt:s,title:s}):$('<button type="button"></button>').addClass(this._triggerClass).html(o==""?s:$("<img/>").attr({src:o,alt:s,title:s}))),e[r?"before":"after"](t.trigger),t.trigger.click(function(){return $.datepicker._datepickerShowing&&$.datepicker._lastInput==e[0]?$.datepicker._hideDatepicker():$.datepicker._datepickerShowing&&$.datepicker._lastInput!=e[0]?($.datepicker._hideDatepicker(),$.datepicker._showDatepicker(e[0])):$.datepicker._showDatepicker(e[0]),!1})}},_autoSize:function(e){if(this._get(e,"autoSize")&&!e.inline){var t=new Date(2009,11,20),n=this._get(e,"dateFormat");if(n.match(/[DM]/)){var r=function(e){var t=0,n=0;for(var r=0;r<e.length;r++)e[r].length>t&&(t=e[r].length,n=r);return n};t.setMonth(r(this._get(e,n.match(/MM/)?"monthNames":"monthNamesShort"))),t.setDate(r(this._get(e,n.match(/DD/)?"dayNames":"dayNamesShort"))+20-t.getDay())}e.input.attr("size",this._formatDate(e,t).length)}},_inlineDatepicker:function(e,t){var n=$(e);if(n.hasClass(this.markerClassName))return;n.addClass(this.markerClassName).append(t.dpDiv).bind("setData.datepicker",function(e,n,r){t.settings[n]=r}).bind("getData.datepicker",function(e,n){return this._get(t,n)}),$.data(e,PROP_NAME,t),this._setDate(t,this._getDefaultDate(t),!0),this._updateDatepicker(t),this._updateAlternate(t),t.settings.disabled&&this._disableDatepicker(e),t.dpDiv.css("display","block")},_dialogDatepicker:function(e,t,n,r,i){var s=this._dialogInst;if(!s){this.uuid+=1;var o="dp"+this.uuid;this._dialogInput=$('<input type="text" id="'+o+'" style="position: absolute; top: -100px; width: 0px;"/>'),this._dialogInput.keydown(this._doKeyDown),$("body").append(this._dialogInput),s=this._dialogInst=this._newInst(this._dialogInput,!1),s.settings={},$.data(this._dialogInput[0],PROP_NAME,s)}extendRemove(s.settings,r||{}),t=t&&t.constructor==Date?this._formatDate(s,t):t,this._dialogInput.val(t),this._pos=i?i.length?i:[i.pageX,i.pageY]:null;if(!this._pos){var u=document.documentElement.clientWidth,a=document.documentElement.clientHeight,f=document.documentElement.scrollLeft||document.body.scrollLeft,l=document.documentElement.scrollTop||document.body.scrollTop;this._pos=[u/2-100+f,a/2-150+l]}return this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px"),s.settings.onSelect=n,this._inDialog=!0,this.dpDiv.addClass(this._dialogClass),this._showDatepicker(this._dialogInput[0]),$.blockUI&&$.blockUI(this.dpDiv),$.data(this._dialogInput[0],PROP_NAME,s),this},_destroyDatepicker:function(e){var t=$(e),n=$.data(e,PROP_NAME);if(!t.hasClass(this.markerClassName))return;var r=e.nodeName.toLowerCase();$.removeData(e,PROP_NAME),r=="input"?(n.append.remove(),n.trigger.remove(),t.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup",this._doKeyUp)):(r=="div"||r=="span")&&t.removeClass(this.markerClassName).empty()},_enableDatepicker:function(e){var t=$(e),n=$.data(e,PROP_NAME);if(!t.hasClass(this.markerClassName))return;var r=e.nodeName.toLowerCase();if(r=="input")e.disabled=!1,n.trigger.filter("button").each(function(){this.disabled=!1}).end().filter("img").css({opacity:"1.0",cursor:""});else if(r=="div"||r=="span"){var i=t.children("."+this._inlineClass);i.children().removeClass("ui-state-disabled"),i.find("select.ui-datepicker-month, select.ui-datepicker-year").prop("disabled",!1)}this._disabledInputs=$.map(this._disabledInputs,function(t){return t==e?null:t})},_disableDatepicker:function(e){var t=$(e),n=$.data(e,PROP_NAME);if(!t.hasClass(this.markerClassName))return;var r=e.nodeName.toLowerCase();if(r=="input")e.disabled=!0,n.trigger.filter("button").each(function(){this.disabled=!0}).end().filter("img").css({opacity:"0.5",cursor:"default"});else if(r=="div"||r=="span"){var i=t.children("."+this._inlineClass);i.children().addClass("ui-state-disabled"),i.find("select.ui-datepicker-month, select.ui-datepicker-year").prop("disabled",!0)}this._disabledInputs=$.map(this._disabledInputs,function(t){return t==e?null:t}),this._disabledInputs[this._disabledInputs.length]=e},_isDisabledDatepicker:function(e){if(!e)return!1;for(var t=0;t<this._disabledInputs.length;t++)if(this._disabledInputs[t]==e)return!0;return!1},_getInst:function(e){try{return $.data(e,PROP_NAME)}catch(t){throw"Missing instance data for this datepicker"}},_optionDatepicker:function(e,t,n){var r=this._getInst(e);if(arguments.length==2&&typeof t=="string")return t=="defaults"?$.extend({},$.datepicker._defaults):r?t=="all"?$.extend({},r.settings):this._get(r,t):null;var i=t||{};typeof t=="string"&&(i={},i[t]=n);if(r){this._curInst==r&&this._hideDatepicker();var s=this._getDateDatepicker(e,!0),o=this._getMinMaxDate(r,"min"),u=this._getMinMaxDate(r,"max");extendRemove(r.settings,i),o!==null&&i.dateFormat!==undefined&&i.minDate===undefined&&(r.settings.minDate=this._formatDate(r,o)),u!==null&&i.dateFormat!==undefined&&i.maxDate===undefined&&(r.settings.maxDate=this._formatDate(r,u)),this._attachments($(e),r),this._autoSize(r),this._setDate(r,s),this._updateAlternate(r),this._updateDatepicker(r)}},_changeDatepicker:function(e,t,n){this._optionDatepicker(e,t,n)},_refreshDatepicker:function(e){var t=this._getInst(e);t&&this._updateDatepicker(t)},_setDateDatepicker:function(e,t){var n=this._getInst(e);n&&(this._setDate(n,t),this._updateDatepicker(n),this._updateAlternate(n))},_getDateDatepicker:function(e,t){var n=this._getInst(e);return n&&!n.inline&&this._setDateFromField(n,t),n?this._getDate(n):null},_doKeyDown:function(e){var t=$.datepicker._getInst(e.target),n=!0,r=t.dpDiv.is(".ui-datepicker-rtl");t._keyEvent=!0;if($.datepicker._datepickerShowing)switch(e.keyCode){case 9:$.datepicker._hideDatepicker(),n=!1;break;case 13:var i=$("td."+$.datepicker._dayOverClass+":not(."+$.datepicker._currentClass+")",t.dpDiv);i[0]&&$.datepicker._selectDay(e.target,t.selectedMonth,t.selectedYear,i[0]);var s=$.datepicker._get(t,"onSelect");if(s){var o=$.datepicker._formatDate(t);s.apply(t.input?t.input[0]:null,[o,t])}else $.datepicker._hideDatepicker();return!1;case 27:$.datepicker._hideDatepicker();break;case 33:$.datepicker._adjustDate(e.target,e.ctrlKey?-$.datepicker._get(t,"stepBigMonths"):-$.datepicker._get(t,"stepMonths"),"M");break;case 34:$.datepicker._adjustDate(e.target,e.ctrlKey?+$.datepicker._get(t,"stepBigMonths"):+$.datepicker._get(t,"stepMonths"),"M");break;case 35:(e.ctrlKey||e.metaKey)&&$.datepicker._clearDate(e.target),n=e.ctrlKey||e.metaKey;break;case 36:(e.ctrlKey||e.metaKey)&&$.datepicker._gotoToday(e.target),n=e.ctrlKey||e.metaKey;break;case 37:(e.ctrlKey||e.metaKey)&&$.datepicker._adjustDate(e.target,r?1:-1,"D"),n=e.ctrlKey||e.metaKey,e.originalEvent.altKey&&$.datepicker._adjustDate(e.target,e.ctrlKey?-$.datepicker._get(t,"stepBigMonths"):-$.datepicker._get(t,"stepMonths"),"M");break;case 38:(e.ctrlKey||e.metaKey)&&$.datepicker._adjustDate(e.target,-7,"D"),n=e.ctrlKey||e.metaKey;break;case 39:(e.ctrlKey||e.metaKey)&&$.datepicker._adjustDate(e.target,r?-1:1,"D"),n=e.ctrlKey||e.metaKey,e.originalEvent.altKey&&$.datepicker._adjustDate(e.target,e.ctrlKey?+$.datepicker._get(t,"stepBigMonths"):+$.datepicker._get(t,"stepMonths"),"M");break;case 40:(e.ctrlKey||e.metaKey)&&$.datepicker._adjustDate(e.target,7,"D"),n=e.ctrlKey||e.metaKey;break;default:n=!1}else e.keyCode==36&&e.ctrlKey?$.datepicker._showDatepicker(this):n=!1;n&&(e.preventDefault(),e.stopPropagation())},_doKeyPress:function(e){var t=$.datepicker._getInst(e.target);if($.datepicker._get(t,"constrainInput")){var n=$.datepicker._possibleChars($.datepicker._get(t,"dateFormat")),r=String.fromCharCode(e.charCode==undefined?e.keyCode:e.charCode);return e.ctrlKey||e.metaKey||r<" "||!n||n.indexOf(r)>-1}},_doKeyUp:function(e){var t=$.datepicker._getInst(e.target);if(t.input.val()!=t.lastVal)try{var n=$.datepicker.parseDate($.datepicker._get(t,"dateFormat"),t.input?t.input.val():null,$.datepicker._getFormatConfig(t));n&&($.datepicker._setDateFromField(t),$.datepicker._updateAlternate(t),$.datepicker._updateDatepicker(t))}catch(r){$.datepicker.log(r)}return!0},_showDatepicker:function(e){e=e.target||e,e.nodeName.toLowerCase()!="input"&&(e=$("input",e.parentNode)[0]);if($.datepicker._isDisabledDatepicker(e)||$.datepicker._lastInput==e)return;var t=$.datepicker._getInst(e);$.datepicker._curInst&&$.datepicker._curInst!=t&&($.datepicker._curInst.dpDiv.stop(!0,!0),t&&$.datepicker._datepickerShowing&&$.datepicker._hideDatepicker($.datepicker._curInst.input[0]));var n=$.datepicker._get(t,"beforeShow"),r=n?n.apply(e,[e,t]):{};if(r===!1)return;extendRemove(t.settings,r),t.lastVal=null,$.datepicker._lastInput=e,$.datepicker._setDateFromField(t),$.datepicker._inDialog&&(e.value=""),$.datepicker._pos||($.datepicker._pos=$.datepicker._findPos(e),$.datepicker._pos[1]+=e.offsetHeight);var i=!1;$(e).parents().each(function(){return i|=$(this).css("position")=="fixed",!i});var s={left:$.datepicker._pos[0],top:$.datepicker._pos[1]};$.datepicker._pos=null,t.dpDiv.empty(),t.dpDiv.css({position:"absolute",display:"block",top:"-1000px"}),$.datepicker._updateDatepicker(t),s=$.datepicker._checkOffset(t,s,i),t.dpDiv.css({position:$.datepicker._inDialog&&$.blockUI?"static":i?"fixed":"absolute",display:"none",left:s.left+"px",top:s.top+"px"});if(!t.inline){var o=$.datepicker._get(t,"showAnim"),u=$.datepicker._get(t,"duration"),a=function(){var e=t.dpDiv.find("iframe.ui-datepicker-cover");if(!!e.length){var n=$.datepicker._getBorders(t.dpDiv);e.css({left:-n[0],top:-n[1],width:t.dpDiv.outerWidth(),height:t.dpDiv.outerHeight()})}};t.dpDiv.zIndex($(e).zIndex()+1),$.datepicker._datepickerShowing=!0,$.effects&&($.effects.effect[o]||$.effects[o])?t.dpDiv.show(o,$.datepicker._get(t,"showOptions"),u,a):t.dpDiv[o||"show"](o?u:null,a),(!o||!u)&&a(),t.input.is(":visible")&&!t.input.is(":disabled")&&t.input.focus(),$.datepicker._curInst=t}},_updateDatepicker:function(e){this.maxRows=4;var t=$.datepicker._getBorders(e.dpDiv);instActive=e,e.dpDiv.empty().append(this._generateHTML(e)),this._attachHandlers(e);var n=e.dpDiv.find("iframe.ui-datepicker-cover");!n.length||n.css({left:-t[0],top:-t[1],width:e.dpDiv.outerWidth(),height:e.dpDiv.outerHeight()}),e.dpDiv.find("."+this._dayOverClass+" a").mouseover();var r=this._getNumberOfMonths(e),i=r[1],s=17;e.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width(""),i>1&&e.dpDiv.addClass("ui-datepicker-multi-"+i).css("width",s*i+"em"),e.dpDiv[(r[0]!=1||r[1]!=1?"add":"remove")+"Class"]("ui-datepicker-multi"),e.dpDiv[(this._get(e,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl"),e==$.datepicker._curInst&&$.datepicker._datepickerShowing&&e.input&&e.input.is(":visible")&&!e.input.is(":disabled")&&e.input[0]!=document.activeElement&&e.input.focus();if(e.yearshtml){var o=e.yearshtml;setTimeout(function(){o===e.yearshtml&&e.yearshtml&&e.dpDiv.find("select.ui-datepicker-year:first").replaceWith(e.yearshtml),o=e.yearshtml=null},0)}},_getBorders:function(e){var t=function(e){return{thin:1,medium:2,thick:3}[e]||e};return[parseFloat(t(e.css("border-left-width"))),parseFloat(t(e.css("border-top-width")))]},_checkOffset:function(e,t,n){var r=e.dpDiv.outerWidth(),i=e.dpDiv.outerHeight(),s=e.input?e.input.outerWidth():0,o=e.input?e.input.outerHeight():0,u=document.documentElement.clientWidth+(n?0:$(document).scrollLeft()),a=document.documentElement.clientHeight+(n?0:$(document).scrollTop());return t.left-=this._get(e,"isRTL")?r-s:0,t.left-=n&&t.left==e.input.offset().left?$(document).scrollLeft():0,t.top-=n&&t.top==e.input.offset().top+o?$(document).scrollTop():0,t.left-=Math.min(t.left,t.left+r>u&&u>r?Math.abs(t.left+r-u):0),t.top-=Math.min(t.top,t.top+i>a&&a>i?Math.abs(i+o):0),t},_findPos:function(e){var t=this._getInst(e),n=this._get(t,"isRTL");while(e&&(e.type=="hidden"||e.nodeType!=1||$.expr.filters.hidden(e)))e=e[n?"previousSibling":"nextSibling"];var r=$(e).offset();return[r.left,r.top]},_hideDatepicker:function(e){var t=this._curInst;if(!t||e&&t!=$.data(e,PROP_NAME))return;if(this._datepickerShowing){var n=this._get(t,"showAnim"),r=this._get(t,"duration"),i=function(){$.datepicker._tidyDialog(t)};$.effects&&($.effects.effect[n]||$.effects[n])?t.dpDiv.hide(n,$.datepicker._get(t,"showOptions"),r,i):t.dpDiv[n=="slideDown"?"slideUp":n=="fadeIn"?"fadeOut":"hide"](n?r:null,i),n||i(),this._datepickerShowing=!1;var s=this._get(t,"onClose");s&&s.apply(t.input?t.input[0]:null,[t.input?t.input.val():"",t]),this._lastInput=null,this._inDialog&&(this._dialogInput.css({position:"absolute",left:"0",top:"-100px"}),$.blockUI&&($.unblockUI(),$("body").append(this.dpDiv))),this._inDialog=!1}},_tidyDialog:function(e){e.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")},_checkExternalClick:function(e){if(!$.datepicker._curInst)return;var t=$(e.target),n=$.datepicker._getInst(t[0]);(t[0].id!=$.datepicker._mainDivId&&t.parents("#"+$.datepicker._mainDivId).length==0&&!t.hasClass($.datepicker.markerClassName)&&!t.closest("."+$.datepicker._triggerClass).length&&$.datepicker._datepickerShowing&&(!$.datepicker._inDialog||!$.blockUI)||t.hasClass($.datepicker.markerClassName)&&$.datepicker._curInst!=n)&&$.datepicker._hideDatepicker()},_adjustDate:function(e,t,n){var r=$(e),i=this._getInst(r[0]);if(this._isDisabledDatepicker(r[0]))return;this._adjustInstDate(i,t+(n=="M"?this._get(i,"showCurrentAtPos"):0),n),this._updateDatepicker(i)},_gotoToday:function(e){var t=$(e),n=this._getInst(t[0]);if(this._get(n,"gotoCurrent")&&n.currentDay)n.selectedDay=n.currentDay,n.drawMonth=n.selectedMonth=n.currentMonth,n.drawYear=n.selectedYear=n.currentYear;else{var r=new Date;n.selectedDay=r.getDate(),n.drawMonth=n.selectedMonth=r.getMonth(),n.drawYear=n.selectedYear=r.getFullYear()}this._notifyChange(n),this._adjustDate(t)},_selectMonthYear:function(e,t,n){var r=$(e),i=this._getInst(r[0]);i["selected"+(n=="M"?"Month":"Year")]=i["draw"+(n=="M"?"Month":"Year")]=parseInt(t.options[t.selectedIndex].value,10),this._notifyChange(i),this._adjustDate(r)},_selectDay:function(e,t,n,r){var i=$(e);if($(r).hasClass(this._unselectableClass)||this._isDisabledDatepicker(i[0]))return;var s=this._getInst(i[0]);s.selectedDay=s.currentDay=$("a",r).html(),s.selectedMonth=s.currentMonth=t,s.selectedYear=s.currentYear=n,this._selectDate(e,this._formatDate(s,s.currentDay,s.currentMonth,s.currentYear))},_clearDate:function(e){var t=$(e),n=this._getInst(t[0]);this._selectDate(t,"")},_selectDate:function(e,t){var n=$(e),r=this._getInst(n[0]);t=t!=null?t:this._formatDate(r),r.input&&r.input.val(t),this._updateAlternate(r);var i=this._get(r,"onSelect");i?i.apply(r.input?r.input[0]:null,[t,r]):r.input&&r.input.trigger("change"),r.inline?this._updateDatepicker(r):(this._hideDatepicker(),this._lastInput=r.input[0],typeof r.input[0]!="object"&&r.input.focus(),this._lastInput=null)},_updateAlternate:function(e){var t=this._get(e,"altField");if(t){var n=this._get(e,"altFormat")||this._get(e,"dateFormat"),r=this._getDate(e),i=this.formatDate(n,r,this._getFormatConfig(e));$(t).each(function(){$(this).val(i)})}},noWeekends:function(e){var t=e.getDay();return[t>0&&t<6,""]},iso8601Week:function(e){var t=new Date(e.getTime());t.setDate(t.getDate()+4-(t.getDay()||7));var n=t.getTime();return t.setMonth(0),t.setDate(1),Math.floor(Math.round((n-t)/864e5)/7)+1},parseDate:function(e,t,n){if(e==null||t==null)throw"Invalid arguments";t=typeof t=="object"?t.toString():t+"";if(t=="")return null;var r=(n?n.shortYearCutoff:null)||this._defaults.shortYearCutoff;r=typeof r!="string"?r:(new Date).getFullYear()%100+parseInt(r,10);var i=(n?n.dayNamesShort:null)||this._defaults.dayNamesShort,s=(n?n.dayNames:null)||this._defaults.dayNames,o=(n?n.monthNamesShort:null)||this._defaults.monthNamesShort,u=(n?n.monthNames:null)||this._defaults.monthNames,a=-1,f=-1,l=-1,c=-1,h=!1,p=function(t){var n=y+1<e.length&&e.charAt(y+1)==t;return n&&y++,n},d=function(e){var n=p(e),r=e=="@"?14:e=="!"?20:e=="y"&&n?4:e=="o"?3:2,i=new RegExp("^\\d{1,"+r+"}"),s=t.substring(g).match(i);if(!s)throw"Missing number at position "+g;return g+=s[0].length,parseInt(s[0],10)},v=function(e,n,r){var i=$.map(p(e)?r:n,function(e,t){return[[t,e]]}).sort(function(e,t){return-(e[1].length-t[1].length)}),s=-1;$.each(i,function(e,n){var r=n[1];if(t.substr(g,r.length).toLowerCase()==r.toLowerCase())return s=n[0],g+=r.length,!1});if(s!=-1)return s+1;throw"Unknown name at position "+g},m=function(){if(t.charAt(g)!=e.charAt(y))throw"Unexpected literal at position "+g;g++},g=0;for(var y=0;y<e.length;y++)if(h)e.charAt(y)=="'"&&!p("'")?h=!1:m();else switch(e.charAt(y)){case"d":l=d("d");break;case"D":v("D",i,s);break;case"o":c=d("o");break;case"m":f=d("m");break;case"M":f=v("M",o,u);break;case"y":a=d("y");break;case"@":var b=new Date(d("@"));a=b.getFullYear(),f=b.getMonth()+1,l=b.getDate();break;case"!":var b=new Date((d("!")-this._ticksTo1970)/1e4);a=b.getFullYear(),f=b.getMonth()+1,l=b.getDate();break;case"'":p("'")?m():h=!0;break;default:m()}if(g<t.length){var w=t.substr(g);if(!/^\s+/.test(w))throw"Extra/unparsed characters found in date: "+w}a==-1?a=(new Date).getFullYear():a<100&&(a+=(new Date).getFullYear()-(new Date).getFullYear()%100+(a<=r?0:-100));if(c>-1){f=1,l=c;do{var E=this._getDaysInMonth(a,f-1);if(l<=E)break;f++,l-=E}while(!0)}var b=this._daylightSavingAdjust(new Date(a,f-1,l));if(b.getFullYear()!=a||b.getMonth()+1!=f||b.getDate()!=l)throw"Invalid date";return b},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925))*24*60*60*1e7,formatDate:function(e,t,n){if(!t)return"";var r=(n?n.dayNamesShort:null)||this._defaults.dayNamesShort,i=(n?n.dayNames:null)||this._defaults.dayNames,s=(n?n.monthNamesShort:null)||this._defaults.monthNamesShort,o=(n?n.monthNames:null)||this._defaults.monthNames,u=function(t){var n=h+1<e.length&&e.charAt(h+1)==t;return n&&h++,n},a=function(e,t,n){var r=""+t;if(u(e))while(r.length<n)r="0"+r;return r},f=function(e,t,n,r){return u(e)?r[t]:n[t]},l="",c=!1;if(t)for(var h=0;h<e.length;h++)if(c)e.charAt(h)=="'"&&!u("'")?c=!1:l+=e.charAt(h);else switch(e.charAt(h)){case"d":l+=a("d",t.getDate(),2);break;case"D":l+=f("D",t.getDay(),r,i);break;case"o":l+=a("o",Math.round(((new Date(t.getFullYear(),t.getMonth(),t.getDate())).getTime()-(new Date(t.getFullYear(),0,0)).getTime())/864e5),3);break;case"m":l+=a("m",t.getMonth()+1,2);break;case"M":l+=f("M",t.getMonth(),s,o);break;case"y":l+=u("y")?t.getFullYear():(t.getYear()%100<10?"0":"")+t.getYear()%100;break;case"@":l+=t.getTime();break;case"!":l+=t.getTime()*1e4+this._ticksTo1970;break;case"'":u("'")?l+="'":c=!0;break;default:l+=e.charAt(h)}return l},_possibleChars:function(e){var t="",n=!1,r=function(t){var n=i+1<e.length&&e.charAt(i+1)==t;return n&&i++,n};for(var i=0;i<e.length;i++)if(n)e.charAt(i)=="'"&&!r("'")?n=!1:t+=e.charAt(i);else switch(e.charAt(i)){case"d":case"m":case"y":case"@":t+="0123456789";break;case"D":case"M":return null;case"'":r("'")?t+="'":n=!0;break;default:t+=e.charAt(i)}return t},_get:function(e,t){return e.settings[t]!==undefined?e.settings[t]:this._defaults[t]},_setDateFromField:function(e,t){if(e.input.val()==e.lastVal)return;var n=this._get(e,"dateFormat"),r=e.lastVal=e.input?e.input.val():null,i,s;i=s=this._getDefaultDate(e);var o=this._getFormatConfig(e);try{i=this.parseDate(n,r,o)||s}catch(u){this.log(u),r=t?"":r}e.selectedDay=i.getDate(),e.drawMonth=e.selectedMonth=i.getMonth(),e.drawYear=e.selectedYear=i.getFullYear(),e.currentDay=r?i.getDate():0,e.currentMonth=r?i.getMonth():0,e.currentYear=r?i.getFullYear():0,this._adjustInstDate(e)},_getDefaultDate:function(e){return this._restrictMinMax(e,this._determineDate(e,this._get(e,"defaultDate"),new Date))},_determineDate:function(e,t,n){var r=function(e){var t=new Date;return t.setDate(t.getDate()+e),t},i=function(t){try{return $.datepicker.parseDate($.datepicker._get(e,"dateFormat"),t,$.datepicker._getFormatConfig(e))}catch(n){}var r=(t.toLowerCase().match(/^c/)?$.datepicker._getDate(e):null)||new Date,i=r.getFullYear(),s=r.getMonth(),o=r.getDate(),u=/([+-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g,a=u.exec(t);while(a){switch(a[2]||"d"){case"d":case"D":o+=parseInt(a[1],10);break;case"w":case"W":o+=parseInt(a[1],10)*7;break;case"m":case"M":s+=parseInt(a[1],10),o=Math.min(o,$.datepicker._getDaysInMonth(i,s));break;case"y":case"Y":i+=parseInt(a[1],10),o=Math.min(o,$.datepicker._getDaysInMonth(i,s))}a=u.exec(t)}return new Date(i,s,o)},s=t==null||t===""?n:typeof t=="string"?i(t):typeof t=="number"?isNaN(t)?n:r(t):new Date(t.getTime());return s=s&&s.toString()=="Invalid Date"?n:s,s&&(s.setHours(0),s.setMinutes(0),s.setSeconds(0),s.setMilliseconds(0)),this._daylightSavingAdjust(s)},_daylightSavingAdjust:function(e){return e?(e.setHours(e.getHours()>12?e.getHours()+2:0),e):null},_setDate:function(e,t,n){var r=!t,i=e.selectedMonth,s=e.selectedYear,o=this._restrictMinMax(e,this._determineDate(e,t,new Date));e.selectedDay=e.currentDay=o.getDate(),e.drawMonth=e.selectedMonth=e.currentMonth=o.getMonth(),e.drawYear=e.selectedYear=e.currentYear=o.getFullYear(),(i!=e.selectedMonth||s!=e.selectedYear)&&!n&&this._notifyChange(e),this._adjustInstDate(e),e.input&&e.input.val(r?"":this._formatDate(e))},_getDate:function(e){var t=!e.currentYear||e.input&&e.input.val()==""?null:this._daylightSavingAdjust(new Date(e.currentYear,e.currentMonth,e.currentDay));return t},_attachHandlers:function(e){var t=this._get(e,"stepMonths"),n="#"+e.id.replace(/\\\\/g,"\\");e.dpDiv.find("[data-handler]").map(function(){var e={prev:function(){window["DP_jQuery_"+dpuuid].datepicker._adjustDate(n,-t,"M")},next:function(){window["DP_jQuery_"+dpuuid].datepicker._adjustDate(n,+t,"M")},hide:function(){window["DP_jQuery_"+dpuuid].datepicker._hideDatepicker()},today:function(){window["DP_jQuery_"+dpuuid].datepicker._gotoToday(n)},selectDay:function(){return window["DP_jQuery_"+dpuuid].datepicker._selectDay(n,+this.getAttribute("data-month"),+this.getAttribute("data-year"),this),!1},selectMonth:function(){return window["DP_jQuery_"+dpuuid].datepicker._selectMonthYear(n,this,"M"),!1},selectYear:function(){return window["DP_jQuery_"+dpuuid].datepicker._selectMonthYear(n,this,"Y"),!1}};$(this).bind(this.getAttribute("data-event"),e[this.getAttribute("data-handler")])})},_generateHTML:function(e){var t=new Date;t=this._daylightSavingAdjust(new Date(t.getFullYear(),t.getMonth(),t.getDate()));var n=this._get(e,"isRTL"),r=this._get(e,"showButtonPanel"),i=this._get(e,"hideIfNoPrevNext"),s=this._get(e,"navigationAsDateFormat"),o=this._getNumberOfMonths(e),u=this._get(e,"showCurrentAtPos"),a=this._get(e,"stepMonths"),f=o[0]!=1||o[1]!=1,l=this._daylightSavingAdjust(e.currentDay?new Date(e.currentYear,e.currentMonth,e.currentDay):new Date(9999,9,9)),c=this._getMinMaxDate(e,"min"),h=this._getMinMaxDate(e,"max"),p=e.drawMonth-u,d=e.drawYear;p<0&&(p+=12,d--);if(h){var v=this._daylightSavingAdjust(new Date(h.getFullYear(),h.getMonth()-o[0]*o[1]+1,h.getDate()));v=c&&v<c?c:v;while(this._daylightSavingAdjust(new Date(d,p,1))>v)p--,p<0&&(p=11,d--)}e.drawMonth=p,e.drawYear=d;var m=this._get(e,"prevText");m=s?this.formatDate(m,this._daylightSavingAdjust(new Date(d,p-a,1)),this._getFormatConfig(e)):m;var g=this._canAdjustMonth(e,-1,d,p)?'<a class="ui-datepicker-prev ui-corner-all" data-handler="prev" data-event="click" title="'+m+'"><span class="ui-icon ui-icon-circle-triangle-'+(n?"e":"w")+'">'+m+"</span></a>":i?"":'<a class="ui-datepicker-prev ui-corner-all ui-state-disabled" title="'+m+'"><span class="ui-icon ui-icon-circle-triangle-'+(n?"e":"w")+'">'+m+"</span></a>",y=this._get(e,"nextText");y=s?this.formatDate(y,this._daylightSavingAdjust(new Date(d,p+a,1)),this._getFormatConfig(e)):y;var b=this._canAdjustMonth(e,1,d,p)?'<a class="ui-datepicker-next ui-corner-all" data-handler="next" data-event="click" title="'+y+'"><span class="ui-icon ui-icon-circle-triangle-'+(n?"w":"e")+'">'+y+"</span></a>":i?"":'<a class="ui-datepicker-next ui-corner-all ui-state-disabled" title="'+y+'"><span class="ui-icon ui-icon-circle-triangle-'+(n?"w":"e")+'">'+y+"</span></a>",w=this._get(e,"currentText"),E=this._get(e,"gotoCurrent")&&e.currentDay?l:t;w=s?this.formatDate(w,E,this._getFormatConfig(e)):w;var S=e.inline?"":'<button type="button" class="ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all" data-handler="hide" data-event="click">'+this._get(e,"closeText")+"</button>",x=r?'<div class="ui-datepicker-buttonpane ui-widget-content">'+(n?S:"")+(this._isInRange(e,E)?'<button type="button" class="ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all" data-handler="today" data-event="click">'+w+"</button>":"")+(n?"":S)+"</div>":"",T=parseInt(this._get(e,"firstDay"),10);T=isNaN(T)?0:T;var N=this._get(e,"showWeek"),C=this._get(e,"dayNames"),k=this._get(e,"dayNamesShort"),L=this._get(e,"dayNamesMin"),A=this._get(e,"monthNames"),O=this._get(e,"monthNamesShort"),M=this._get(e,"beforeShowDay"),_=this._get(e,"showOtherMonths"),D=this._get(e,"selectOtherMonths"),P=this._get(e,"calculateWeek")||this.iso8601Week,H=this._getDefaultDate(e),B="";for(var j=0;j<o[0];j++){var F="";this.maxRows=4;for(var I=0;I<o[1];I++){var q=this._daylightSavingAdjust(new Date(d,p,e.selectedDay)),R=" ui-corner-all",U="";if(f){U+='<div class="ui-datepicker-group';if(o[1]>1)switch(I){case 0:U+=" ui-datepicker-group-first",R=" ui-corner-"+(n?"right":"left");break;case o[1]-1:U+=" ui-datepicker-group-last",R=" ui-corner-"+(n?"left":"right");break;default:U+=" ui-datepicker-group-middle",R=""}U+='">'}U+='<div class="ui-datepicker-header ui-widget-header ui-helper-clearfix'+R+'">'+(/all|left/.test(R)&&j==0?n?b:g:"")+(/all|right/.test(R)&&j==0?n?g:b:"")+this._generateMonthYearHeader(e,p,d,c,h,j>0||I>0,A,O)+'</div><table class="ui-datepicker-calendar"><thead>'+"<tr>";var z=N?'<th class="ui-datepicker-week-col">'+this._get(e,"weekHeader")+"</th>":"";for(var W=0;W<7;W++){var X=(W+T)%7;z+="<th"+((W+T+6)%7>=5?' class="ui-datepicker-week-end"':"")+">"+'<span title="'+C[X]+'">'+L[X]+"</span></th>"}U+=z+"</tr></thead><tbody>";var V=this._getDaysInMonth(d,p);d==e.selectedYear&&p==e.selectedMonth&&(e.selectedDay=Math.min(e.selectedDay,V));var J=(this._getFirstDayOfMonth(d,p)-T+7)%7,K=Math.ceil((J+V)/7),Q=f?this.maxRows>K?this.maxRows:K:K;this.maxRows=Q;var G=this._daylightSavingAdjust(new Date(d,p,1-J));for(var Y=0;Y<Q;Y++){U+="<tr>";var Z=N?'<td class="ui-datepicker-week-col">'+this._get(e,"calculateWeek")(G)+"</td>":"";for(var W=0;W<7;W++){var et=M?M.apply(e.input?e.input[0]:null,[G]):[!0,""],tt=G.getMonth()!=p,nt=tt&&!D||!et[0]||c&&G<c||h&&G>h;Z+='<td class="'+((W+T+6)%7>=5?" ui-datepicker-week-end":"")+(tt?" ui-datepicker-other-month":"")+(G.getTime()==q.getTime()&&p==e.selectedMonth&&e._keyEvent||H.getTime()==G.getTime()&&H.getTime()==q.getTime()?" "+this._dayOverClass:"")+(nt?" "+this._unselectableClass+" ui-state-disabled":"")+(tt&&!_?"":" "+et[1]+(G.getTime()==l.getTime()?" "+this._currentClass:"")+(G.getTime()==t.getTime()?" ui-datepicker-today":""))+'"'+((!tt||_)&&et[2]?' title="'+et[2]+'"':"")+(nt?"":' data-handler="selectDay" data-event="click" data-month="'+G.getMonth()+'" data-year="'+G.getFullYear()+'"')+">"+(tt&&!_?"&#xa0;":nt?'<span class="ui-state-default">'+G.getDate()+"</span>":'<a class="ui-state-default'+(G.getTime()==t.getTime()?" ui-state-highlight":"")+(G.getTime()==l.getTime()?" ui-state-active":"")+(tt?" ui-priority-secondary":"")+'" href="#">'+G.getDate()+"</a>")+"</td>",G.setDate(G.getDate()+1),G=this._daylightSavingAdjust(G)}U+=Z+"</tr>"}p++,p>11&&(p=0,d++),U+="</tbody></table>"+(f?"</div>"+(o[0]>0&&I==o[1]-1?'<div class="ui-datepicker-row-break"></div>':""):""),F+=U}B+=F}return B+=x+($.ui.ie6&&!e.inline?'<iframe src="javascript:false;" class="ui-datepicker-cover" frameborder="0"></iframe>':""),e._keyEvent=!1,B},_generateMonthYearHeader:function(e,t,n,r,i,s,o,u){var a=this._get(e,"changeMonth"),f=this._get(e,"changeYear"),l=this._get(e,"showMonthAfterYear"),c='<div class="ui-datepicker-title">',h="";if(s||!a)h+='<span class="ui-datepicker-month">'+o[t]+"</span>";else{var p=r&&r.getFullYear()==n,d=i&&i.getFullYear()==n;h+='<select class="ui-datepicker-month" data-handler="selectMonth" data-event="change">';for(var v=0;v<12;v++)(!p||v>=r.getMonth())&&(!d||v<=i.getMonth())&&(h+='<option value="'+v+'"'+(v==t?' selected="selected"':"")+">"+u[v]+"</option>");h+="</select>"}l||(c+=h+(s||!a||!f?"&#xa0;":""));if(!e.yearshtml){e.yearshtml="";if(s||!f)c+='<span class="ui-datepicker-year">'+n+"</span>";else{var m=this._get(e,"yearRange").split(":"),g=(new Date).getFullYear(),y=function(e){var t=e.match(/c[+-].*/)?n+parseInt(e.substring(1),10):e.match(/[+-].*/)?g+parseInt(e,10):parseInt(e,10);return isNaN(t)?g:t},b=y(m[0]),w=Math.max(b,y(m[1]||""));b=r?Math.max(b,r.getFullYear()):b,w=i?Math.min(w,i.getFullYear()):w,e.yearshtml+='<select class="ui-datepicker-year" data-handler="selectYear" data-event="change">';for(;b<=w;b++)e.yearshtml+='<option value="'+b+'"'+(b==n?' selected="selected"':"")+">"+b+"</option>";e.yearshtml+="</select>",c+=e.yearshtml,e.yearshtml=null}}return c+=this._get(e,"yearSuffix"),l&&(c+=(s||!a||!f?"&#xa0;":"")+h),c+="</div>",c},_adjustInstDate:function(e,t,n){var r=e.drawYear+(n=="Y"?t:0),i=e.drawMonth+(n=="M"?t:0),s=Math.min(e.selectedDay,this._getDaysInMonth(r,i))+(n=="D"?t:0),o=this._restrictMinMax(e,this._daylightSavingAdjust(new Date(r,i,s)));e.selectedDay=o.getDate(),e.drawMonth=e.selectedMonth=o.getMonth(),e.drawYear=e.selectedYear=o.getFullYear(),(n=="M"||n=="Y")&&this._notifyChange(e)},_restrictMinMax:function(e,t){var n=this._getMinMaxDate(e,"min"),r=this._getMinMaxDate(e,"max"),i=n&&t<n?n:t;return i=r&&i>r?r:i,i},_notifyChange:function(e){var t=this._get(e,"onChangeMonthYear");t&&t.apply(e.input?e.input[0]:null,[e.selectedYear,e.selectedMonth+1,e])},_getNumberOfMonths:function(e){var t=this._get(e,"numberOfMonths");return t==null?[1,1]:typeof t=="number"?[1,t]:t},_getMinMaxDate:function(e,t){return this._determineDate(e,this._get(e,t+"Date"),null)},_getDaysInMonth:function(e,t){return 32-this._daylightSavingAdjust(new Date(e,t,32)).getDate()},_getFirstDayOfMonth:function(e,t){return(new Date(e,t,1)).getDay()},_canAdjustMonth:function(e,t,n,r){var i=this._getNumberOfMonths(e),s=this._daylightSavingAdjust(new Date(n,r+(t<0?t:i[0]*i[1]),1));return t<0&&s.setDate(this._getDaysInMonth(s.getFullYear(),s.getMonth())),this._isInRange(e,s)},_isInRange:function(e,t){var n=this._getMinMaxDate(e,"min"),r=this._getMinMaxDate(e,"max");return(!n||t.getTime()>=n.getTime())&&(!r||t.getTime()<=r.getTime())},_getFormatConfig:function(e){var t=this._get(e,"shortYearCutoff");return t=typeof t!="string"?t:(new Date).getFullYear()%100+parseInt(t,10),{shortYearCutoff:t,dayNamesShort:this._get(e,"dayNamesShort"),dayNames:this._get(e,"dayNames"),monthNamesShort:this._get(e,"monthNamesShort"),monthNames:this._get(e,"monthNames")}},_formatDate:function(e,t,n,r){t||(e.currentDay=e.selectedDay,e.currentMonth=e.selectedMonth,e.currentYear=e.selectedYear);var i=t?typeof t=="object"?t:this._daylightSavingAdjust(new Date(r,n,t)):this._daylightSavingAdjust(new Date(e.currentYear,e.currentMonth,e.currentDay));return this.formatDate(this._get(e,"dateFormat"),i,this._getFormatConfig(e))}}),$.fn.datepicker=function(e){if(!this.length)return this;$.datepicker.initialized||($(document).mousedown($.datepicker._checkExternalClick).find(document.body).append($.datepicker.dpDiv),$.datepicker.initialized=!0);var t=Array.prototype.slice.call(arguments,1);return typeof e!="string"||e!="isDisabled"&&e!="getDate"&&e!="widget"?e=="option"&&arguments.length==2&&typeof arguments[1]=="string"?$.datepicker["_"+e+"Datepicker"].apply($.datepicker,[this[0]].concat(t)):this.each(function(){typeof e=="string"?$.datepicker["_"+e+"Datepicker"].apply($.datepicker,[this].concat(t)):$.datepicker._attachDatepicker(this,e)}):$.datepicker["_"+e+"Datepicker"].apply($.datepicker,[this[0]].concat(t))},$.datepicker=new Datepicker,$.datepicker.initialized=!1,$.datepicker.uuid=(new Date).getTime(),$.datepicker.version="1.9.2",window["DP_jQuery_"+dpuuid]=$}(jQuery),function(e,t){var n="ui-dialog ui-widget ui-widget-content ui-corner-all ",r={buttons:!0,height:!0,maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0,width:!0},i={maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0};e.widget("ui.dialog",{version:"1.9.2",options:{autoOpen:!0,buttons:{},closeOnEscape:!0,closeText:"close",dialogClass:"",draggable:!0,hide:null,height:"auto",maxHeight:!1,maxWidth:!1,minHeight:150,minWidth:150,modal:!1,position:{my:"center",at:"center",of:window,collision:"fit",using:function(t){var n=e(this).css(t).offset().top;n<0&&e(this).css("top",t.top-n)}},resizable:!0,show:null,stack:!0,title:"",width:300,zIndex:1e3},_create:function(){this.originalTitle=this.element.attr("title"),typeof this.originalTitle!="string"&&(this.originalTitle=""),this.oldPosition={parent:this.element.parent(),index:this.element.parent().children().index(this.element)},this.options.title=this.options.title||this.originalTitle;var t=this,r=this.options,i=r.title||"&#160;",s,o,u,a,f;s=(this.uiDialog=e("<div>")).addClass(n+r.dialogClass).css({display:"none",outline:0,zIndex:r.zIndex}).attr("tabIndex",-1).keydown(function(n){r.closeOnEscape&&!n.isDefaultPrevented()&&n.keyCode&&n.keyCode===e.ui.keyCode.ESCAPE&&(t.close(n),n.preventDefault())}).mousedown(function(e){t.moveToTop(!1,e)}).appendTo("body"),this.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(s),o=(this.uiDialogTitlebar=e("<div>")).addClass("ui-dialog-titlebar  ui-widget-header  ui-corner-all  ui-helper-clearfix").bind("mousedown",function(){s.focus()}).prependTo(s),u=e("<a href='#'></a>").addClass("ui-dialog-titlebar-close  ui-corner-all").attr("role","button").click(function(e){e.preventDefault(),t.close(e)}).appendTo(o),(this.uiDialogTitlebarCloseText=e("<span>")).addClass("ui-icon ui-icon-closethick").text(r.closeText).appendTo(u),a=e("<span>").uniqueId().addClass("ui-dialog-title").html(i).prependTo(o),f=(this.uiDialogButtonPane=e("<div>")).addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"),(this.uiButtonSet=e("<div>")).addClass("ui-dialog-buttonset").appendTo(f),s.attr({role:"dialog","aria-labelledby":a.attr("id")}),o.find("*").add(o).disableSelection(),this._hoverable(u),this._focusable(u),r.draggable&&e.fn.draggable&&this._makeDraggable(),r.resizable&&e.fn.resizable&&this._makeResizable(),this._createButtons(r.buttons),this._isOpen=!1,e.fn.bgiframe&&s.bgiframe(),this._on(s,{keydown:function(t){if(!r.modal||t.keyCode!==e.ui.keyCode.TAB)return;var n=e(":tabbable",s),i=n.filter(":first"),o=n.filter(":last");if(t.target===o[0]&&!t.shiftKey)return i.focus(1),!1;if(t.target===i[0]&&t.shiftKey)return o.focus(1),!1}})},_init:function(){this.options.autoOpen&&this.open()},_destroy:function(){var e,t=this.oldPosition;this.overlay&&this.overlay.destroy(),this.uiDialog.hide(),this.element.removeClass("ui-dialog-content ui-widget-content").hide().appendTo("body"),this.uiDialog.remove(),this.originalTitle&&this.element.attr("title",this.originalTitle),e=t.parent.children().eq(t.index),e.length&&e[0]!==this.element[0]?e.before(this.element):t.parent.append(this.element)},widget:function(){return this.uiDialog},close:function(t){var n=this,r,i;if(!this._isOpen)return;if(!1===this._trigger("beforeClose",t))return;return this._isOpen=!1,this.overlay&&this.overlay.destroy(),this.options.hide?this._hide(this.uiDialog,this.options.hide,function(){n._trigger("close",t)}):(this.uiDialog.hide(),this._trigger("close",t)),e.ui.dialog.overlay.resize(),this.options.modal&&(r=0,e(".ui-dialog").each(function(){this!==n.uiDialog[0]&&(i=e(this).css("z-index"),isNaN(i)||(r=Math.max(r,i)))}),e.ui.dialog.maxZ=r),this},isOpen:function(){return this._isOpen},moveToTop:function(t,n){var r=this.options,i;return r.modal&&!t||!r.stack&&!r.modal?this._trigger("focus",n):(r.zIndex>e.ui.dialog.maxZ&&(e.ui.dialog.maxZ=r.zIndex),this.overlay&&(e.ui.dialog.maxZ+=1,e.ui.dialog.overlay.maxZ=e.ui.dialog.maxZ,this.overlay.$el.css("z-index",e.ui.dialog.overlay.maxZ)),i={scrollTop:this.element.scrollTop(),scrollLeft:this.element.scrollLeft()},e.ui.dialog.maxZ+=1,this.uiDialog.css("z-index",e.ui.dialog.maxZ),this.element.attr(i),this._trigger("focus",n),this)},open:function(){if(this._isOpen)return;var t,n=this.options,r=this.uiDialog;return this._size(),this._position(n.position),r.show(n.show),this.overlay=n.modal?new e.ui.dialog.overlay(this):null,this.moveToTop(!0),t=this.element.find(":tabbable"),t.length||(t=this.uiDialogButtonPane.find(":tabbable"),t.length||(t=r)),t.eq(0).focus(),this._isOpen=!0,this._trigger("open"),this},_createButtons:function(t){var n=this,r=!1;this.uiDialogButtonPane.remove(),this.uiButtonSet.empty(),typeof t=="object"&&t!==null&&e.each(t,function(){return!(r=!0)}),r?(e.each(t,function(t,r){var i,s;r=e.isFunction(r)?{click:r,text:t}:r,r=e.extend({type:"button"},r),s=r.click,r.click=function(){s.apply(n.element[0],arguments)},i=e("<button></button>",r).appendTo(n.uiButtonSet),e.fn.button&&i.button()}),this.uiDialog.addClass("ui-dialog-buttons"),this.uiDialogButtonPane.appendTo(this.uiDialog)):this.uiDialog.removeClass("ui-dialog-buttons")},_makeDraggable:function(){function r(e){return{position:e.position,offset:e.offset}}var t=this,n=this.options;this.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",handle:".ui-dialog-titlebar",containment:"document",start:function(n,i){e(this).addClass("ui-dialog-dragging"),t._trigger("dragStart",n,r(i))},drag:function(e,n){t._trigger("drag",e,r(n))},stop:function(i,s){n.position=[s.position.left-t.document.scrollLeft(),s.position.top-t.document.scrollTop()],e(this).removeClass("ui-dialog-dragging"),t._trigger("dragStop",i,r(s)),e.ui.dialog.overlay.resize()}})},_makeResizable:function(n){function u(e){return{originalPosition:e.originalPosition,originalSize:e.originalSize,position:e.position,size:e.size}}n=n===t?this.options.resizable:n;var r=this,i=this.options,s=this.uiDialog.css("position"),o=typeof n=="string"?n:"n,e,s,w,se,sw,ne,nw";this.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:this.element,maxWidth:i.maxWidth,maxHeight:i.maxHeight,minWidth:i.minWidth,minHeight:this._minHeight(),handles:o,start:function(t,n){e(this).addClass("ui-dialog-resizing"),r._trigger("resizeStart",t,u(n))},resize:function(e,t){r._trigger("resize",e,u(t))},stop:function(t,n){e(this).removeClass("ui-dialog-resizing"),i.height=e(this).height(),i.width=e(this).width(),r._trigger("resizeStop",t,u(n)),e.ui.dialog.overlay.resize()}}).css("position",s).find(".ui-resizable-se").addClass("ui-icon ui-icon-grip-diagonal-se")},_minHeight:function(){var e=this.options;return e.height==="auto"?e.minHeight:Math.min(e.minHeight,e.height)},_position:function(t){var n=[],r=[0,0],i;if(t){if(typeof t=="string"||typeof t=="object"&&"0"in t)n=t.split?t.split(" "):[t[0],t[1]],n.length===1&&(n[1]=n[0]),e.each(["left","top"],function(e,t){+n[e]===n[e]&&(r[e]=n[e],n[e]=t)}),t={my:n[0]+(r[0]<0?r[0]:"+"+r[0])+" "+n[1]+(r[1]<0?r[1]:"+"+r[1]),at:n.join(" ")};t=e.extend({},e.ui.dialog.prototype.options.position,t)}else t=e.ui.dialog.prototype.options.position;i=this.uiDialog.is(":visible"),i||this.uiDialog.show(),this.uiDialog.position(t),i||this.uiDialog.hide()},_setOptions:function(t){var n=this,s={},o=!1;e.each(t,function(e,t){n._setOption(e,t),e in r&&(o=!0),e in i&&(s[e]=t)}),o&&this._size(),this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option",s)},_setOption:function(t,r){var i,s,o=this.uiDialog;switch(t){case"buttons":this._createButtons(r);break;case"closeText":this.uiDialogTitlebarCloseText.text(""+r);break;case"dialogClass":o.removeClass(this.options.dialogClass).addClass(n+r);break;case"disabled":r?o.addClass("ui-dialog-disabled"):o.removeClass("ui-dialog-disabled");break;case"draggable":i=o.is(":data(draggable)"),i&&!r&&o.draggable("destroy"),!i&&r&&this._makeDraggable();break;case"position":this._position(r);break;case"resizable":s=o.is(":data(resizable)"),s&&!r&&o.resizable("destroy"),s&&typeof r=="string"&&o.resizable("option","handles",r),!s&&r!==!1&&this._makeResizable(r);break;case"title":e(".ui-dialog-title",this.uiDialogTitlebar).html(""+(r||"&#160;"))}this._super(t,r)},_size:function(){var t,n,r,i=this.options,s=this.uiDialog.is(":visible");this.element.show().css({width:"auto",minHeight:0,height:0}),i.minWidth>i.width&&(i.width=i.minWidth),t=this.uiDialog.css({height:"auto",width:i.width}).outerHeight(),n=Math.max(0,i.minHeight-t),i.height==="auto"?e.support.minHeight?this.element.css({minHeight:n,height:"auto"}):(this.uiDialog.show(),r=this.element.css("height","auto").height(),s||this.uiDialog.hide(),this.element.height(Math.max(r,n))):this.element.height(Math.max(i.height-t,0)),this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option","minHeight",this._minHeight())}}),e.extend(e.ui.dialog,{uuid:0,maxZ:0,getTitleId:function(e){var t=e.attr("id");return t||(this.uuid+=1,t=this.uuid),"ui-dialog-title-"+t},overlay:function(t){this.$el=e.ui.dialog.overlay.create(t)}}),e.extend(e.ui.dialog.overlay,{instances:[],oldInstances:[],maxZ:0,events:e.map("focus,mousedown,mouseup,keydown,keypress,click".split(","),function(e){return e+".dialog-overlay"}).join(" "),create:function(t){this.instances.length===0&&(setTimeout(function(){e.ui.dialog.overlay.instances.length&&e(document).bind(e.ui.dialog.overlay.events,function(t){if(e(t.target).zIndex()<e.ui.dialog.overlay.maxZ)return!1})},1),e(window).bind("resize.dialog-overlay",e.ui.dialog.overlay.resize));var n=this.oldInstances.pop()||e("<div>").addClass("ui-widget-overlay");return e(document).bind("keydown.dialog-overlay",function(r){var i=e.ui.dialog.overlay.instances;i.length!==0&&i[i.length-1]===n&&t.options.closeOnEscape&&!r.isDefaultPrevented()&&r.keyCode&&r.keyCode===e.ui.keyCode.ESCAPE&&(t.close(r),r.preventDefault())}),n.appendTo(document.body).css({width:this.width(),height:this.height()}),e.fn.bgiframe&&n.bgiframe(),this.instances.push(n),n},destroy:function(t){var n=e.inArray(t,this.instances),r=0;n!==-1&&this.oldInstances.push(this.instances.splice(n,1)[0]),this.instances.length===0&&e([document,window]).unbind(".dialog-overlay"),t.height(0).width(0).remove(),e.each(this.instances,function(){r=Math.max(r,this.css("z-index"))}),this.maxZ=r},height:function(){var t,n;return e.ui.ie?(t=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight),n=Math.max(document.documentElement.offsetHeight,document.body.offsetHeight),t<n?e(window).height()+"px":t+"px"):e(document).height()+"px"},width:function(){var t,n;return e.ui.ie?(t=Math.max(document.documentElement.scrollWidth,document.body.scrollWidth),n=Math.max(document.documentElement.offsetWidth,document.body.offsetWidth),t<n?e(window).width()+"px":t+"px"):e(document).width()+"px"},resize:function(){var t=e([]);e.each(e.ui.dialog.overlay.instances,function(){t=t.add(this)}),t.css({width:0,height:0}).css({width:e.ui.dialog.overlay.width(),height:e.ui.dialog.overlay.height()})}}),e.extend(e.ui.dialog.overlay.prototype,{destroy:function(){e.ui.dialog.overlay.destroy(this.$el)}})}(jQuery),function(e,t){var n=/up|down|vertical/,r=/up|left|vertical|horizontal/;e.effects.effect.blind=function(t,i){var s=e(this),o=["position","top","bottom","left","right","height","width"],u=e.effects.setMode(s,t.mode||"hide"),a=t.direction||"up",f=n.test(a),l=f?"height":"width",c=f?"top":"left",h=r.test(a),p={},d=u==="show",v,m,g;s.parent().is(".ui-effects-wrapper")?e.effects.save(s.parent(),o):e.effects.save(s,o),s.show(),v=e.effects.createWrapper(s).css({overflow:"hidden"}),m=v[l](),g=parseFloat(v.css(c))||0,p[l]=d?m:0,h||(s.css(f?"bottom":"right",0).css(f?"top":"left","auto").css({position:"absolute"}),p[c]=d?g:m+g),d&&(v.css(l,0),h||v.css(c,g+m)),v.animate(p,{duration:t.duration,easing:t.easing,queue:!1,complete:function(){u==="hide"&&s.hide(),e.effects.restore(s,o),e.effects.removeWrapper(s),i()}})}}(jQuery),function(e,t){e.effects.effect.bounce=function(t,n){var r=e(this),i=["position","top","bottom","left","right","height","width"],s=e.effects.setMode(r,t.mode||"effect"),o=s==="hide",u=s==="show",a=t.direction||"up",f=t.distance,l=t.times||5,c=l*2+(u||o?1:0),h=t.duration/c,p=t.easing,d=a==="up"||a==="down"?"top":"left",v=a==="up"||a==="left",m,g,y,b=r.queue(),w=b.length;(u||o)&&i.push("opacity"),e.effects.save(r,i),r.show(),e.effects.createWrapper(r),f||(f=r[d==="top"?"outerHeight":"outerWidth"]()/3),u&&(y={opacity:1},y[d]=0,r.css("opacity",0).css(d,v?-f*2:f*2).animate(y,h,p)),o&&(f/=Math.pow(2,l-1)),y={},y[d]=0;for(m=0;m<l;m++)g={},g[d]=(v?"-=":"+=")+f,r.animate(g,h,p).animate(y,h,p),f=o?f*2:f/2;o&&(g={opacity:0},g[d]=(v?"-=":"+=")+f,r.animate(g,h,p)),r.queue(function(){o&&r.hide(),e.effects.restore(r,i),e.effects.removeWrapper(r),n()}),w>1&&b.splice.apply(b,[1,0].concat(b.splice(w,c+1))),r.dequeue()}}(jQuery),function(e,t){e.effects.effect.clip=function(t,n){var r=e(this),i=["position","top","bottom","left","right","height","width"],s=e.effects.setMode(r,t.mode||"hide"),o=s==="show",u=t.direction||"vertical",a=u==="vertical",f=a?"height":"width",l=a?"top":"left",c={},h,p,d;e.effects.save(r,i),r.show(),h=e.effects.createWrapper(r).css({overflow:"hidden"}),p=r[0].tagName==="IMG"?h:r,d=p[f](),o&&(p.css(f,0),p.css(l,d/2)),c[f]=o?d:0,c[l]=o?0:d/2,p.animate(c,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){o||r.hide(),e.effects.restore(r,i),e.effects.removeWrapper(r),n()}})}}(jQuery),function(e,t){e.effects.effect.drop=function(t,n){var r=e(this),i=["position","top","bottom","left","right","opacity","height","width"],s=e.effects.setMode(r,t.mode||"hide"),o=s==="show",u=t.direction||"left",a=u==="up"||u==="down"?"top":"left",f=u==="up"||u==="left"?"pos":"neg",l={opacity:o?1:0},c;e.effects.save(r,i),r.show(),e.effects.createWrapper(r),c=t.distance||r[a==="top"?"outerHeight":"outerWidth"](!0)/2,o&&r.css("opacity",0).css(a,f==="pos"?-c:c),l[a]=(o?f==="pos"?"+=":"-=":f==="pos"?"-=":"+=")+c,r.animate(l,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){s==="hide"&&r.hide(),e.effects.restore(r,i),e.effects.removeWrapper(r),n()}})}}(jQuery),function(e,t){e.effects.effect.explode=function(t,n){function y(){c.push(this),c.length===r*i&&b()}function b(){s.css({visibility:"visible"}),e(c).remove(),u||s.hide(),n()}var r=t.pieces?Math.round(Math.sqrt(t.pieces)):3,i=r,s=e(this),o=e.effects.setMode(s,t.mode||"hide"),u=o==="show",a=s.show().css("visibility","hidden").offset(),f=Math.ceil(s.outerWidth()/i),l=Math.ceil(s.outerHeight()/r),c=[],h,p,d,v,m,g;for(h=0;h<r;h++){v=a.top+h*l,g=h-(r-1)/2;for(p=0;p<i;p++)d=a.left+p*f,m=p-(i-1)/2,s.clone().appendTo("body").wrap("<div></div>").css({position:"absolute",visibility:"visible",left:-p*f,top:-h*l}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:f,height:l,left:d+(u?m*f:0),top:v+(u?g*l:0),opacity:u?0:1}).animate({left:d+(u?0:m*f),top:v+(u?0:g*l),opacity:u?1:0},t.duration||500,t.easing,y)}}}(jQuery),function(e,t){e.effects.effect.fade=function(t,n){var r=e(this),i=e.effects.setMode(r,t.mode||"toggle");r.animate({opacity:i},{queue:!1,duration:t.duration,easing:t.easing,complete:n})}}(jQuery),function(e,t){e.effects.effect.fold=function(t,n){var r=e(this),i=["position","top","bottom","left","right","height","width"],s=e.effects.setMode(r,t.mode||"hide"),o=s==="show",u=s==="hide",a=t.size||15,f=/([0-9]+)%/.exec(a),l=!!t.horizFirst,c=o!==l,h=c?["width","height"]:["height","width"],p=t.duration/2,d,v,m={},g={};e.effects.save(r,i),r.show(),d=e.effects.createWrapper(r).css({overflow:"hidden"}),v=c?[d.width(),d.height()]:[d.height(),d.width()],f&&(a=parseInt(f[1],10)/100*v[u?0:1]),o&&d.css(l?{height:0,width:a}:{height:a,width:0}),m[h[0]]=o?v[0]:a,g[h[1]]=o?v[1]:0,d.animate(m,p,t.easing).animate(g,p,t.easing,function(){u&&r.hide(),e.effects.restore(r,i),e.effects.removeWrapper(r),n()})}}(jQuery),function(e,t){e.effects.effect.highlight=function(t,n){var r=e(this),i=["backgroundImage","backgroundColor","opacity"],s=e.effects.setMode(r,t.mode||"show"),o={backgroundColor:r.css("backgroundColor")};s==="hide"&&(o.opacity=0),e.effects.save(r,i),r.show().css({backgroundImage:"none",backgroundColor:t.color||"#ffff99"}).animate(o,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){s==="hide"&&r.hide(),e.effects.restore(r,i),n()}})}}(jQuery),function(e,t){e.effects.effect.pulsate=function(t,n){var r=e(this),i=e.effects.setMode(r,t.mode||"show"),s=i==="show",o=i==="hide",u=s||i==="hide",a=(t.times||5)*2+(u?1:0),f=t.duration/a,l=0,c=r.queue(),h=c.length,p;if(s||!r.is(":visible"))r.css("opacity",0).show(),l=1;for(p=1;p<a;p++)r.animate({opacity:l},f,t.easing),l=1-l;r.animate({opacity:l},f,t.easing),r.queue(function(){o&&r.hide(),n()}),h>1&&c.splice.apply(c,[1,0].concat(c.splice(h,a+1))),r.dequeue()}}(jQuery),function(e,t){e.effects.effect.puff=function(t,n){var r=e(this),i=e.effects.setMode(r,t.mode||"hide"),s=i==="hide",o=parseInt(t.percent,10)||150,u=o/100,a={height:r.height(),width:r.width(),outerHeight:r.outerHeight(),outerWidth:r.outerWidth()};e.extend(t,{effect:"scale",queue:!1,fade:!0,mode:i,complete:n,percent:s?o:100,from:s?a:{height:a.height*u,width:a.width*u,outerHeight:a.outerHeight*u,outerWidth:a.outerWidth*u}}),r.effect(t)},e.effects.effect.scale=function(t,n){var r=e(this),i=e.extend(!0,{},t),s=e.effects.setMode(r,t.mode||"effect"),o=parseInt(t.percent,10)||(parseInt(t.percent,10)===0?0:s==="hide"?0:100),u=t.direction||"both",a=t.origin,f={height:r.height(),width:r.width(),outerHeight:r.outerHeight(),outerWidth:r.outerWidth()},l={y:u!=="horizontal"?o/100:1,x:u!=="vertical"?o/100:1};i.effect="size",i.queue=!1,i.complete=n,s!=="effect"&&(i.origin=a||["middle","center"],i.restore=!0),i.from=t.from||(s==="show"?{height:0,width:0,outerHeight:0,outerWidth:0}:f),i.to={height:f.height*l.y,width:f.width*l.x,outerHeight:f.outerHeight*l.y,outerWidth:f.outerWidth*l.x},i.fade&&(s==="show"&&(i.from.opacity=0,i.to.opacity=1),s==="hide"&&(i.from.opacity=1,i.to.opacity=0)),r.effect(i)},e.effects.effect.size=function(t,n){var r,i,s,o=e(this),u=["position","top","bottom","left","right","width","height","overflow","opacity"],a=["position","top","bottom","left","right","overflow","opacity"],f=["width","height","overflow"],l=["fontSize"],c=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],h=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],p=e.effects.setMode(o,t.mode||"effect"),d=t.restore||p!=="effect",v=t.scale||"both",m=t.origin||["middle","center"],g=o.css("position"),y=d?u:a,b={height:0,width:0,outerHeight:0,outerWidth:0};p==="show"&&o.show(),r={height:o.height(),width:o.width(),outerHeight:o.outerHeight(),outerWidth:o.outerWidth()},t.mode==="toggle"&&p==="show"?(o.from=t.to||b,o.to=t.from||r):(o.from=t.from||(p==="show"?b:r),o.to=t.to||(p==="hide"?b:r)),s={from:{y:o.from.height/r.height,x:o.from.width/r.width},to:{y:o.to.height/r.height,x:o.to.width/r.width}};if(v==="box"||v==="both")s.from.y!==s.to.y&&(y=y.concat(c),o.from=e.effects.setTransition(o,c,s.from.y,o.from),o.to=e.effects.setTransition(o,c,s.to.y,o.to)),s.from.x!==s.to.x&&(y=y.concat(h),o.from=e.effects.setTransition(o,h,s.from.x,o.from),o.to=e.effects.setTransition(o,h,s.to.x,o.to));(v==="content"||v==="both")&&s.from.y!==s.to.y&&(y=y.concat(l).concat(f),o.from=e.effects.setTransition(o,l,s.from.y,o.from),o.to=e.effects.setTransition(o,l,s.to.y,o.to)),e.effects.save(o,y),o.show(),e.effects.createWrapper(o),o.css("overflow","hidden").css(o.from),m&&(i=e.effects.getBaseline(m,r),o.from.top=(r.outerHeight-o.outerHeight())*i.y,o.from.left=(r.outerWidth-o.outerWidth())*i.x,o.to.top=(r.outerHeight-o.to.outerHeight)*i.y,o.to.left=(r.outerWidth-o.to.outerWidth)*i.x),o.css(o.from);if(v==="content"||v==="both")c=c.concat(["marginTop","marginBottom"]).concat(l),h=h.concat(["marginLeft","marginRight"]),f=u.concat(c).concat(h),o.find("*[width]").each(function(){var n=e(this),r={height:n.height(),width:n.width(),outerHeight:n.outerHeight(),outerWidth:n.outerWidth()};d&&e.effects.save(n,f),n.from={height:r.height*s.from.y,width:r.width*s.from.x,outerHeight:r.outerHeight*s.from.y,outerWidth:r.outerWidth*s.from.x},n.to={height:r.height*s.to.y,width:r.width*s.to.x,outerHeight:r.height*s.to.y,outerWidth:r.width*s.to.x},s.from.y!==s.to.y&&(n.from=e.effects.setTransition(n,c,s.from.y,n.from),n.to=e.effects.setTransition(n,c,s.to.y,n.to)),s.from.x!==s.to.x&&(n.from=e.effects.setTransition(n,h,s.from.x,n.from),n.to=e.effects.setTransition(n,h,s.to.x,n.to)),n.css(n.from),n.animate(n.to,t.duration,t.easing,function(){d&&e.effects.restore(n,f)})});o.animate(o.to,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){o.to.opacity===0&&o.css("opacity",o.from.opacity),p==="hide"&&o.hide(),e.effects.restore(o,y),d||(g==="static"?o.css({position:"relative",top:o.to.top,left:o.to.left}):e.each(["top","left"],function(e,t){o.css(t,function(t,n){var r=parseInt(n,10),i=e?o.to.left:o.to.top;return n==="auto"?i+"px":r+i+"px"})})),e.effects.removeWrapper(o),n()}})}}(jQuery),function(e,t){e.effects.effect.shake=function(t,n){var r=e(this),i=["position","top","bottom","left","right","height","width"],s=e.effects.setMode(r,t.mode||"effect"),o=t.direction||"left",u=t.distance||20,a=t.times||3,f=a*2+1,l=Math.round(t.duration/f),c=o==="up"||o==="down"?"top":"left",h=o==="up"||o==="left",p={},d={},v={},m,g=r.queue(),y=g.length;e.effects.save(r,i),r.show(),e.effects.createWrapper(r),p[c]=(h?"-=":"+=")+u,d[c]=(h?"+=":"-=")+u*2,v[c]=(h?"-=":"+=")+u*2,r.animate(p,l,t.easing);for(m=1;m<a;m++)r.animate(d,l,t.easing).animate(v,l,t.easing);r.animate(d,l,t.easing).animate(p,l/2,t.easing).queue(function(){s==="hide"&&r.hide(),e.effects.restore(r,i),e.effects.removeWrapper(r),n()}),y>1&&g.splice.apply(g,[1,0].concat(g.splice(y,f+1))),r.dequeue()}}(jQuery),function(e,t){e.effects.effect.slide=function(t,n){var r=e(this),i=["position","top","bottom","left","right","width","height"],s=e.effects.setMode(r,t.mode||"show"),o=s==="show",u=t.direction||"left",a=u==="up"||u==="down"?"top":"left",f=u==="up"||u==="left",l,c={};e.effects.save(r,i),r.show(),l=t.distance||r[a==="top"?"outerHeight":"outerWidth"](!0),e.effects.createWrapper(r).css({overflow:"hidden"}),o&&r.css(a,f?isNaN(l)?"-"+l:-l:l),c[a]=(o?f?"+=":"-=":f?"-=":"+=")+l,r.animate(c,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){s==="hide"&&r.hide(),e.effects.restore(r,i),e.effects.removeWrapper(r),n()}})}}(jQuery),function(e,t){e.effects.effect.transfer=function(t,n){var r=e(this),i=e(t.to),s=i.css("position")==="fixed",o=e("body"),u=s?o.scrollTop():0,a=s?o.scrollLeft():0,f=i.offset(),l={top:f.top-u,left:f.left-a,height:i.innerHeight(),width:i.innerWidth()},c=r.offset(),h=e('<div class="ui-effects-transfer"></div>').appendTo(document.body).addClass(t.className).css({top:c.top-u,left:c.left-a,height:r.innerHeight(),width:r.innerWidth(),position:s?"fixed":"absolute"}).animate(l,t.duration,t.easing,function(){h.remove(),n()})}}(jQuery),function(e,t){var n=!1;e.widget("ui.menu",{version:"1.9.2",defaultElement:"<ul>",delay:300,options:{icons:{submenu:"ui-icon-carat-1-e"},menus:"ul",position:{my:"left top",at:"right top"},role:"menu",blur:null,focus:null,select:null},_create:function(){this.activeMenu=this.element,this.element.uniqueId().addClass("ui-menu ui-widget ui-widget-content ui-corner-all").toggleClass("ui-menu-icons",!!this.element.find(".ui-icon").length).attr({role:this.options.role,tabIndex:0}).bind("click"+this.eventNamespace,e.proxy(function(e){this.options.disabled&&e.preventDefault()},this)),this.options.disabled&&this.element.addClass("ui-state-disabled").attr("aria-disabled","true"),this._on({"mousedown .ui-menu-item > a":function(e){e.preventDefault()},"click .ui-state-disabled > a":function(e){e.preventDefault()},"click .ui-menu-item:has(a)":function(t){var r=e(t.target).closest(".ui-menu-item");!n&&r.not(".ui-state-disabled").length&&(n=!0,this.select(t),r.has(".ui-menu").length?this.expand(t):this.element.is(":focus")||(this.element.trigger("focus",[!0]),this.active&&this.active.parents(".ui-menu").length===1&&clearTimeout(this.timer)))},"mouseenter .ui-menu-item":function(t){var n=e(t.currentTarget);n.siblings().children(".ui-state-active").removeClass("ui-state-active"),this.focus(t,n)},mouseleave:"collapseAll","mouseleave .ui-menu":"collapseAll",focus:function(e,t){var n=this.active||this.element.children(".ui-menu-item").eq(0);t||this.focus(e,n)},blur:function(t){this._delay(function(){e.contains(this.element[0],this.document[0].activeElement)||this.collapseAll(t)})},keydown:"_keydown"}),this.refresh(),this._on(this.document,{click:function(t){e(t.target).closest(".ui-menu").length||this.collapseAll(t),n=!1}})},_destroy:function(){this.element.removeAttr("aria-activedescendant").find(".ui-menu").andSelf().removeClass("ui-menu ui-widget ui-widget-content ui-corner-all ui-menu-icons").removeAttr("role").removeAttr("tabIndex").removeAttr("aria-labelledby").removeAttr("aria-expanded").removeAttr("aria-hidden").removeAttr("aria-disabled").removeUniqueId().show(),this.element.find(".ui-menu-item").removeClass("ui-menu-item").removeAttr("role").removeAttr("aria-disabled").children("a").removeUniqueId().removeClass("ui-corner-all ui-state-hover").removeAttr("tabIndex").removeAttr("role").removeAttr("aria-haspopup").children().each(function(){var t=e(this);t.data("ui-menu-submenu-carat")&&t.remove()}),this.element.find(".ui-menu-divider").removeClass("ui-menu-divider ui-widget-content")},_keydown:function(t){function a(e){return e.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")}var n,r,i,s,o,u=!0;switch(t.keyCode){case e.ui.keyCode.PAGE_UP:this.previousPage(t);break;case e.ui.keyCode.PAGE_DOWN:this.nextPage(t);break;case e.ui.keyCode.HOME:this._move("first","first",t);break;case e.ui.keyCode.END:this._move("last","last",t);break;case e.ui.keyCode.UP:this.previous(t);break;case e.ui.keyCode.DOWN:this.next(t);break;case e.ui.keyCode.LEFT:this.collapse(t);break;case e.ui.keyCode.RIGHT:this.active&&!this.active.is(".ui-state-disabled")&&this.expand(t);break;case e.ui.keyCode.ENTER:case e.ui.keyCode.SPACE:this._activate(t);break;case e.ui.keyCode.ESCAPE:this.collapse(t);break;default:u=!1,r=this.previousFilter||"",i=String.fromCharCode(t.keyCode),s=!1,clearTimeout(this.filterTimer),i===r?s=!0:i=r+i,o=new RegExp("^"+a(i),"i"),n=this.activeMenu.children(".ui-menu-item").filter(function(){return o.test(e(this).children("a").text())}),n=s&&n.index(this.active.next())!==-1?this.active.nextAll(".ui-menu-item"):n,n.length||(i=String.fromCharCode(t.keyCode),o=new RegExp("^"+a(i),"i"),n=this.activeMenu.children(".ui-menu-item").filter(function(){return o.test(e(this).children("a").text())})),n.length?(this.focus(t,n),n.length>1?(this.previousFilter=i,this.filterTimer=this._delay(function(){delete this.previousFilter},1e3)):delete this.previousFilter):delete this.previousFilter}u&&t.preventDefault()},_activate:function(e){this.active.is(".ui-state-disabled")||(this.active.children("a[aria-haspopup='true']").length?this.expand(e):this.select(e))},refresh:function(){var t,n=this.options.icons.submenu,r=this.element.find(this.options.menus);r.filter(":not(.ui-menu)").addClass("ui-menu ui-widget ui-widget-content ui-corner-all").hide().attr({role:this.options.role,"aria-hidden":"true","aria-expanded":"false"}).each(function(){var t=e(this),r=t.prev("a"),i=e("<span>").addClass("ui-menu-icon ui-icon "+n).data("ui-menu-submenu-carat",!0);r.attr("aria-haspopup","true").prepend(i),t.attr("aria-labelledby",r.attr("id"))}),t=r.add(this.element),t.children(":not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","presentation").children("a").uniqueId().addClass("ui-corner-all").attr({tabIndex:-1,role:this._itemRole()}),t.children(":not(.ui-menu-item)").each(function(){var t=e(this);/[^\-—–\s]/.test(t.text())||t.addClass("ui-widget-content ui-menu-divider")}),t.children(".ui-state-disabled").attr("aria-disabled","true"),this.active&&!e.contains(this.element[0],this.active[0])&&this.blur()},_itemRole:function(){return{menu:"menuitem",listbox:"option"}[this.options.role]},focus:function(e,t){var n,r;this.blur(e,e&&e.type==="focus"),this._scrollIntoView(t),this.active=t.first(),r=this.active.children("a").addClass("ui-state-focus"),this.options.role&&this.element.attr("aria-activedescendant",r.attr("id")),this.active.parent().closest(".ui-menu-item").children("a:first").addClass("ui-state-active"),e&&e.type==="keydown"?this._close():this.timer=this._delay(function(){this._close()},this.delay),n=t.children(".ui-menu"),n.length&&/^mouse/.test(e.type)&&this._startOpening(n),this.activeMenu=t.parent(),this._trigger("focus",e,{item:t})},_scrollIntoView:function(t){var n,r,i,s,o,u;this._hasScroll()&&(n=parseFloat(e.css(this.activeMenu[0],"borderTopWidth"))||0,r=parseFloat(e.css(this.activeMenu[0],"paddingTop"))||0,i=t.offset().top-this.activeMenu.offset().top-n-r,s=this.activeMenu.scrollTop(),o=this.activeMenu.height(),u=t.height(),i<0?this.activeMenu.scrollTop(s+i):i+u>o&&this.activeMenu.scrollTop(s+i-o+u))},blur:function(e,t){t||clearTimeout(this.timer);if(!this.active)return;this.active.children("a").removeClass("ui-state-focus"),this.active=null,this._trigger("blur",e,{item:this.active})},_startOpening:function(e){clearTimeout(this.timer);if(e.attr("aria-hidden")!=="true")return;this.timer=this._delay(function(){this._close(),this._open(e)},this.delay)},_open:function(t){var n=e.extend({of:this.active},this.options.position);clearTimeout(this.timer),this.element.find(".ui-menu").not(t.parents(".ui-menu")).hide().attr("aria-hidden","true"),t.show().removeAttr("aria-hidden").attr("aria-expanded","true").position(n)},collapseAll:function(t,n){clearTimeout(this.timer),this.timer=this._delay(function(){var r=n?this.element:e(t&&t.target).closest(this.element.find(".ui-menu"));r.length||(r=this.element),this._close(r),this.blur(t),this.activeMenu=r},this.delay)},_close:function(e){e||(e=this.active?this.active.parent():this.element),e.find(".ui-menu").hide().attr("aria-hidden","true").attr("aria-expanded","false").end().find("a.ui-state-active").removeClass("ui-state-active")},collapse:function(e){var t=this.active&&this.active.parent().closest(".ui-menu-item",this.element);t&&t.length&&(this._close(),this.focus(e,t))},expand:function(e){var t=this.active&&this.active.children(".ui-menu ").children(".ui-menu-item").first();t&&t.length&&(this._open(t.parent()),this._delay(function(){this.focus(e,t)}))},next:function(e){this._move("next","first",e)},previous:function(e){this._move("prev","last",e)},isFirstItem:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},isLastItem:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},_move:function(e,t,n){var r;this.active&&(e==="first"||e==="last"?r=this.active[e==="first"?"prevAll":"nextAll"](".ui-menu-item").eq(-1):r=this.active[e+"All"](".ui-menu-item").eq(0));if(!r||!r.length||!this.active)r=this.activeMenu.children(".ui-menu-item")[t]();this.focus(n,r)},nextPage:function(t){var n,r,i;if(!this.active){this.next(t);return}if(this.isLastItem())return;this._hasScroll()?(r=this.active.offset().top,i=this.element.height(),this.active.nextAll(".ui-menu-item").each(function(){return n=e(this),n.offset().top-r-i<0}),this.focus(t,n)):this.focus(t,this.activeMenu.children(".ui-menu-item")[this.active?"last":"first"]())},previousPage:function(t){var n,r,i;if(!this.active){this.next(t);return}if(this.isFirstItem())return;this._hasScroll()?(r=this.active.offset().top,i=this.element.height(),this.active.prevAll(".ui-menu-item").each(function(){return n=e(this),n.offset().top-r+i>0}),this.focus(t,n)):this.focus(t,this.activeMenu.children(".ui-menu-item").first())},_hasScroll:function(){return this.element.outerHeight()<this.element.prop("scrollHeight")},select:function(t){this.active=this.active||e(t.target).closest(".ui-menu-item");var n={item:this.active};this.active.has(".ui-menu").length||this.collapseAll(t,!0),this._trigger("select",t,n)}})}(jQuery),function(e,t){function h(e,t,n){return[parseInt(e[0],10)*(l.test(e[0])?t/100:1),parseInt(e[1],10)*(l.test(e[1])?n/100:1)]}function p(t,n){return parseInt(e.css(t,n),10)||0}e.ui=e.ui||{};var n,r=Math.max,i=Math.abs,s=Math.round,o=/left|center|right/,u=/top|center|bottom/,a=/[\+\-]\d+%?/,f=/^\w+/,l=/%$/,c=e.fn.position;e.position={scrollbarWidth:function(){if(n!==t)return n;var r,i,s=e("<div style='display:block;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>"),o=s.children()[0];return e("body").append(s),r=o.offsetWidth,s.css("overflow","scroll"),i=o.offsetWidth,r===i&&(i=s[0].clientWidth),s.remove(),n=r-i},getScrollInfo:function(t){var n=t.isWindow?"":t.element.css("overflow-x"),r=t.isWindow?"":t.element.css("overflow-y"),i=n==="scroll"||n==="auto"&&t.width<t.element[0].scrollWidth,s=r==="scroll"||r==="auto"&&t.height<t.element[0].scrollHeight;return{width:i?e.position.scrollbarWidth():0,height:s?e.position.scrollbarWidth():0}},getWithinInfo:function(t){var n=e(t||window),r=e.isWindow(n[0]);return{element:n,isWindow:r,offset:n.offset()||{left:0,top:0},scrollLeft:n.scrollLeft(),scrollTop:n.scrollTop(),width:r?n.width():n.outerWidth(),height:r?n.height():n.outerHeight()}}},e.fn.position=function(t){if(!t||!t.of)return c.apply(this,arguments);t=e.extend({},t);var n,l,d,v,m,g=e(t.of),y=e.position.getWithinInfo(t.within),b=e.position.getScrollInfo(y),w=g[0],E=(t.collision||"flip").split(" "),S={};return w.nodeType===9?(l=g.width(),d=g.height(),v={top:0,left:0}):e.isWindow(w)?(l=g.width(),d=g.height(),v={top:g.scrollTop(),left:g.scrollLeft()}):w.preventDefault?(t.at="left top",l=d=0,v={top:w.pageY,left:w.pageX}):(l=g.outerWidth(),d=g.outerHeight(),v=g.offset()),m=e.extend({},v),e.each(["my","at"],function(){var e=(t[this]||"").split(" "),n,r;e.length===1&&(e=o.test(e[0])?e.concat(["center"]):u.test(e[0])?["center"].concat(e):["center","center"]),e[0]=o.test(e[0])?e[0]:"center",e[1]=u.test(e[1])?e[1]:"center",n=a.exec(e[0]),r=a.exec(e[1]),S[this]=[n?n[0]:0,r?r[0]:0],t[this]=[f.exec(e[0])[0],f.exec(e[1])[0]]}),E.length===1&&(E[1]=E[0]),t.at[0]==="right"?m.left+=l:t.at[0]==="center"&&(m.left+=l/2),t.at[1]==="bottom"?m.top+=d:t.at[1]==="center"&&(m.top+=d/2),n=h(S.at,l,d),m.left+=n[0],m.top+=n[1],this.each(function(){var o,u,a=e(this),f=a.outerWidth(),c=a.outerHeight(),w=p(this,"marginLeft"),x=p(this,"marginTop"),T=f+w+p(this,"marginRight")+b.width,N=c+x+p(this,"marginBottom")+b.height,C=e.extend({},m),k=h(S.my,a.outerWidth(),a.outerHeight());t.my[0]==="right"?C.left-=f:t.my[0]==="center"&&(C.left-=f/2),t.my[1]==="bottom"?C.top-=c:t.my[1]==="center"&&(C.top-=c/2),C.left+=k[0],C.top+=k[1],e.support.offsetFractions||(C.left=s(C.left),C.top=s(C.top)),o={marginLeft:w,marginTop:x},e.each(["left","top"],function(r,i){e.ui.position[E[r]]&&e.ui.position[E[r]][i](C,{targetWidth:l,targetHeight:d,elemWidth:f,elemHeight:c,collisionPosition:o,collisionWidth:T,collisionHeight:N,offset:[n[0]+k[0],n[1]+k[1]],my:t.my,at:t.at,within:y,elem:a})}),e.fn.bgiframe&&a.bgiframe(),t.using&&(u=function(e){var n=v.left-C.left,s=n+l-f,o=v.top-C.top,u=o+d-c,h={target:{element:g,left:v.left,top:v.top,width:l,height:d},element:{element:a,left:C.left,top:C.top,width:f,height:c},horizontal:s<0?"left":n>0?"right":"center",vertical:u<0?"top":o>0?"bottom":"middle"};l<f&&i(n+s)<l&&(h.horizontal="center"),d<c&&i(o+u)<d&&(h.vertical="middle"),r(i(n),i(s))>r(i(o),i(u))?h.important="horizontal":h.important="vertical",t.using.call(this,e,h)}),a.offset(e.extend(C,{using:u}))})},e.ui.position={fit:{left:function(e,t){var n=t.within,i=n.isWindow?n.scrollLeft:n.offset.left,s=n.width,o=e.left-t.collisionPosition.marginLeft,u=i-o,a=o+t.collisionWidth-s-i,f;t.collisionWidth>s?u>0&&a<=0?(f=e.left+u+t.collisionWidth-s-i,e.left+=u-f):a>0&&u<=0?e.left=i:u>a?e.left=i+s-t.collisionWidth:e.left=i:u>0?e.left+=u:a>0?e.left-=a:e.left=r(e.left-o,e.left)},top:function(e,t){var n=t.within,i=n.isWindow?n.scrollTop:n.offset.top,s=t.within.height,o=e.top-t.collisionPosition.marginTop,u=i-o,a=o+t.collisionHeight-s-i,f;t.collisionHeight>s?u>0&&a<=0?(f=e.top+u+t.collisionHeight-s-i,e.top+=u-f):a>0&&u<=0?e.top=i:u>a?e.top=i+s-t.collisionHeight:e.top=i:u>0?e.top+=u:a>0?e.top-=a:e.top=r(e.top-o,e.top)}},flip:{left:function(e,t){var n=t.within,r=n.offset.left+n.scrollLeft,s=n.width,o=n.isWindow?n.scrollLeft:n.offset.left,u=e.left-t.collisionPosition.marginLeft,a=u-o,f=u+t.collisionWidth-s-o,l=t.my[0]==="left"?-t.elemWidth:t.my[0]==="right"?t.elemWidth:0,c=t.at[0]==="left"?t.targetWidth:t.at[0]==="right"?-t.targetWidth:0,h=-2*t.offset[0],p,d;if(a<0){p=e.left+l+c+h+t.collisionWidth-s-r;if(p<0||p<i(a))e.left+=l+c+h}else if(f>0){d=e.left-t.collisionPosition.marginLeft+l+c+h-o;if(d>0||i(d)<f)e.left+=l+c+h}},top:function(e,t){var n=t.within,r=n.offset.top+n.scrollTop,s=n.height,o=n.isWindow?n.scrollTop:n.offset.top,u=e.top-t.collisionPosition.marginTop,a=u-o,f=u+t.collisionHeight-s-o,l=t.my[1]==="top",c=l?-t.elemHeight:t.my[1]==="bottom"?t.elemHeight:0,h=t.at[1]==="top"?t.targetHeight:t.at[1]==="bottom"?-t.targetHeight:0,p=-2*t.offset[1],d,v;a<0?(v=e.top+c+h+p+t.collisionHeight-s-r,e.top+c+h+p>a&&(v<0||v<i(a))&&(e.top+=c+h+p)):f>0&&(d=e.top-t.collisionPosition.marginTop+c+h+p-o,e.top+c+h+p>f&&(d>0||i(d)<f)&&(e.top+=c+h+p))}},flipfit:{left:function(){e.ui.position.flip.left.apply(this,arguments),e.ui.position.fit.left.apply(this,arguments)},top:function(){e.ui.position.flip.top.apply(this,arguments),e.ui.position.fit.top.apply(this,arguments)}}},function(){var t,n,r,i,s,o=document.getElementsByTagName("body")[0],u=document.createElement("div");t=document.createElement(o?"div":"body"),r={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},o&&e.extend(r,{position:"absolute",left:"-1000px",top:"-1000px"});for(s in r)t.style[s]=r[s];t.appendChild(u),n=o||document.documentElement,n.insertBefore(t,n.firstChild),u.style.cssText="position: absolute; left: 10.7432222px;",i=e(u).offset().left,e.support.offsetFractions=i>10&&i<11,t.innerHTML="",n.removeChild(t)}(),e.uiBackCompat!==!1&&function(e){var n=e.fn.position;e.fn.position=function(r){if(!r||!r.offset)return n.call(this,r);var i=r.offset.split(" "),s=r.at.split(" ");return i.length===1&&(i[1]=i[0]),/^\d/.test(i[0])&&(i[0]="+"+i[0]),/^\d/.test(i[1])&&(i[1]="+"+i[1]),s.length===1&&(/left|center|right/.test(s[0])?s[1]="center":(s[1]=s[0],s[0]="center")),n.call(this,e.extend(r,{at:s[0]+i[0]+" "+s[1]+i[1],offset:t}))}}(jQuery)}(jQuery),function(e,t){e.widget("ui.progressbar",{version:"1.9.2",options:{value:0,max:100},min:0,_create:function(){this.element.addClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").attr({role:"progressbar","aria-valuemin":this.min,"aria-valuemax":this.options.max,"aria-valuenow":this._value()}),this.valueDiv=e("<div class='ui-progressbar-value ui-widget-header ui-corner-left'></div>").appendTo(this.element),this.oldValue=this._value(),this._refreshValue()},_destroy:function(){this.element.removeClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow"),this.valueDiv.remove()},value:function(e){return e===t?this._value():(this._setOption("value",e),this)},_setOption:function(e,t){e==="value"&&(this.options.value=t,this._refreshValue(),this._value()===this.options.max&&this._trigger("complete")),this._super(e,t)},_value:function(){var e=this.options.value;return typeof e!="number"&&(e=0),Math.min(this.options.max,Math.max(this.min,e))},_percentage:function(){return 100*this._value()/this.options.max},_refreshValue:function(){var e=this.value(),t=this._percentage();this.oldValue!==e&&(this.oldValue=e,this._trigger("change")),this.valueDiv.toggle(e>this.min).toggleClass("ui-corner-right",e===this.options.max).width(t.toFixed(0)+"%"),this.element.attr("aria-valuenow",e)}})}(jQuery),function(e,t){var n=5;e.widget("ui.slider",e.ui.mouse,{version:"1.9.2",widgetEventPrefix:"slide",options:{animate:!1,distance:0,max:100,min:0,orientation:"horizontal",range:!1,step:1,value:0,values:null},_create:function(){var t,r,i=this.options,s=this.element.find(".ui-slider-handle").addClass("ui-state-default ui-corner-all"),o="<a class='ui-slider-handle ui-state-default ui-corner-all' href='#'></a>",u=[];this._keySliding=!1,this._mouseSliding=!1,this._animateOff=!0,this._handleIndex=null,this._detectOrientation(),this._mouseInit(),this.element.addClass("ui-slider ui-slider-"+this.orientation+" ui-widget"+" ui-widget-content"+" ui-corner-all"+(i.disabled?" ui-slider-disabled ui-disabled":"")),this.range=e([]),i.range&&(i.range===!0&&(i.values||(i.values=[this._valueMin(),this._valueMin()]),i.values.length&&i.values.length!==2&&(i.values=[i.values[0],i.values[0]])),this.range=e("<div></div>").appendTo(this.element).addClass("ui-slider-range ui-widget-header"+(i.range==="min"||i.range==="max"?" ui-slider-range-"+i.range:""))),r=i.values&&i.values.length||1;for(t=s.length;t<r;t++)u.push(o);this.handles=s.add(e(u.join("")).appendTo(this.element)),this.handle=this.handles.eq(0),this.handles.add(this.range).filter("a").click(function(e){e.preventDefault()}).mouseenter(function(){i.disabled||e(this).addClass("ui-state-hover")}).mouseleave(function(){e(this).removeClass("ui-state-hover")}).focus(function(){i.disabled?e(this).blur():(e(".ui-slider .ui-state-focus").removeClass("ui-state-focus"),e(this).addClass("ui-state-focus"))}).blur(function(){e(this).removeClass("ui-state-focus")}),this.handles.each(function(t){e(this).data("ui-slider-handle-index",t)}),this._on(this.handles,{keydown:function(t){var r,i,s,o,u=e(t.target).data("ui-slider-handle-index");switch(t.keyCode){case e.ui.keyCode.HOME:case e.ui.keyCode.END:case e.ui.keyCode.PAGE_UP:case e.ui.keyCode.PAGE_DOWN:case e.ui.keyCode.UP:case e.ui.keyCode.RIGHT:case e.ui.keyCode.DOWN:case e.ui.keyCode.LEFT:t.preventDefault();if(!this._keySliding){this._keySliding=!0,e(t.target).addClass("ui-state-active"),r=this._start(t,u);if(r===!1)return}}o=this.options.step,this.options.values&&this.options.values.length?i=s=this.values(u):i=s=this.value();switch(t.keyCode){case e.ui.keyCode.HOME:s=this._valueMin();break;case e.ui.keyCode.END:s=this._valueMax();break;case e.ui.keyCode.PAGE_UP:s=this._trimAlignValue(i+(this._valueMax()-this._valueMin())/n);break;case e.ui.keyCode.PAGE_DOWN:s=this._trimAlignValue(i-(this._valueMax()-this._valueMin())/n);break;case e.ui.keyCode.UP:case e.ui.keyCode.RIGHT:if(i===this._valueMax())return;s=this._trimAlignValue(i+o);break;case e.ui.keyCode.DOWN:case e.ui.keyCode.LEFT:if(i===this._valueMin())return;s=this._trimAlignValue(i-o)}this._slide(t,u,s)},keyup:function(t){var n=e(t.target).data("ui-slider-handle-index");this._keySliding&&(this._keySliding=!1,this._stop(t,n),this._change(t,n),e(t.target).removeClass("ui-state-active"))}}),this._refreshValue(),this._animateOff=!1},_destroy:function(){this.handles.remove(),this.range.remove(),this.element.removeClass("ui-slider ui-slider-horizontal ui-slider-vertical ui-slider-disabled ui-widget ui-widget-content ui-corner-all"),this._mouseDestroy()},_mouseCapture:function(t){var n,r,i,s,o,u,a,f,l=this,c=this.options;return c.disabled?!1:(this.elementSize={width:this.element.outerWidth(),height:this.element.outerHeight()},this.elementOffset=this.element.offset(),n={x:t.pageX,y:t.pageY},r=this._normValueFromMouse(n),i=this._valueMax()-this._valueMin()+1,this.handles.each(function(t){var n=Math.abs(r-l.values(t));i>n&&(i=n,s=e(this),o=t)}),c.range===!0&&this.values(1)===c.min&&(o+=1,s=e(this.handles[o])),u=this._start(t,o),u===!1?!1:(this._mouseSliding=!0,this._handleIndex=o,s.addClass("ui-state-active").focus(),a=s.offset(),f=!e(t.target).parents().andSelf().is(".ui-slider-handle"),this._clickOffset=f?{left:0,top:0}:{left:t.pageX-a.left-s.width()/2,top:t.pageY-a.top-s.height()/2-(parseInt(s.css("borderTopWidth"),10)||0)-(parseInt(s.css("borderBottomWidth"),10)||0)+(parseInt(s.css("marginTop"),10)||0)},this.handles.hasClass("ui-state-hover")||this._slide(t,o,r),this._animateOff=!0,!0))},_mouseStart:function(){return!0},_mouseDrag:function(e){var t={x:e.pageX,y:e.pageY},n=this._normValueFromMouse(t);return this._slide(e,this._handleIndex,n),!1},_mouseStop:function(e){return this.handles.removeClass("ui-state-active"),this._mouseSliding=!1,this._stop(e,this._handleIndex),this._change(e,this._handleIndex),this._handleIndex=null,this._clickOffset=null,this._animateOff=!1,!1},_detectOrientation:function(){this.orientation=this.options.orientation==="vertical"?"vertical":"horizontal"},_normValueFromMouse:function(e){var t,n,r,i,s;return this.orientation==="horizontal"?(t=this.elementSize.width,n=e.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)):(t=this.elementSize.height,n=e.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)),r=n/t,r>1&&(r=1),r<0&&(r=0),this.orientation==="vertical"&&(r=1-r),i=this._valueMax()-this._valueMin(),s=this._valueMin()+r*i,this._trimAlignValue(s)},_start:function(e,t){var n={handle:this.handles[t],value:this.value()};return this.options.values&&this.options.values.length&&(n.value=this.values(t),n.values=this.values()),this._trigger("start",e,n)},_slide:function(e,t,n){var r,i,s;this.options.values&&this.options.values.length?(r=this.values(t?0:1),this.options.values.length===2&&this.options.range===!0&&(t===0&&n>r||t===1&&n<r)&&(n=r),n!==this.values(t)&&(i=this.values(),i[t]=n,s=this._trigger("slide",e,{handle:this.handles[t],value:n,values:i}),r=this.values(t?0:1),s!==!1&&this.values(t,n,!0))):n!==this.value()&&(s=this._trigger("slide",e,{handle:this.handles[t],value:n}),s!==!1&&this.value(n))},_stop:function(e,t){var n={handle:this.handles[t],value:this.value()};this.options.values&&this.options.values.length&&(n.value=this.values(t),n.values=this.values()),this._trigger("stop",e,n)},_change:function(e,t){if(!this._keySliding&&!this._mouseSliding){var n={handle:this.handles[t],value:this.value()};this.options.values&&this.options.values.length&&(n.value=this.values(t),n.values=this.values()),this._trigger("change",e,n)}},value:function(e){if(arguments.length){this.options.value=this._trimAlignValue(e),this._refreshValue(),this._change(null,0);return}return this._value()},values:function(t,n){var r,i,s;if(arguments.length>1){this.options.values[t]=this._trimAlignValue(n),this._refreshValue(),this._change(null,t);return}if(!arguments.length)return this._values();if(!e.isArray(arguments[0]))return this.options.values&&this.options.values.length?this._values(t):this.value();r=this.options.values,i=arguments[0];for(s=0;s<r.length;s+=1)r[s]=this._trimAlignValue(i[s]),this._change(null,s);this._refreshValue()},_setOption:function(t,n){var r,i=0;e.isArray(this.options.values)&&(i=this.options.values.length),e.Widget.prototype._setOption.apply(this,arguments);switch(t){case"disabled":n?(this.handles.filter(".ui-state-focus").blur(),this.handles.removeClass("ui-state-hover"),this.handles.prop("disabled",!0),this.element.addClass("ui-disabled")):(this.handles.prop("disabled",!1),this.element.removeClass("ui-disabled"));break;case"orientation":this._detectOrientation(),this.element.removeClass("ui-slider-horizontal ui-slider-vertical").addClass("ui-slider-"+this.orientation),this._refreshValue();break;case"value":this._animateOff=!0,this._refreshValue(),this._change(null,0),this._animateOff=!1;break;case"values":this._animateOff=!0,this._refreshValue();for(r=0;r<i;r+=1)this._change(null,r);this._animateOff=!1;break;case"min":case"max":this._animateOff=!0,this._refreshValue(),this._animateOff=!1}},_value:function(){var e=this.options.value;return e=this._trimAlignValue(e),e},_values:function(e){var t,n,r;if(arguments.length)return t=this.options.values[e],t=this._trimAlignValue(t),t;n=this.options.values.slice();for(r=0;r<n.length;r+=1)n[r]=this._trimAlignValue(n[r]);return n},_trimAlignValue:function(e){if(e<=this._valueMin())return this._valueMin();if(e>=this._valueMax())return this._valueMax();var t=this.options.step>0?this.options.step:1,n=(e-this._valueMin())%t,r=e-n;return Math.abs(n)*2>=t&&(r+=n>0?t:-t),parseFloat(r.toFixed(5))},_valueMin:function(){return this.options.min},_valueMax:function(){return this.options.max},_refreshValue:function(){var t,n,r,i,s,o=this.options.range,u=this.options,a=this,f=this._animateOff?!1:u.animate,l={};this.options.values&&this.options.values.length?this.handles.each(function(r){n=(a.values(r)-a._valueMin())/(a._valueMax()-a._valueMin())*100,l[a.orientation==="horizontal"?"left":"bottom"]=n+"%",e(this).stop(1,1)[f?"animate":"css"](l,u.animate),a.options.range===!0&&(a.orientation==="horizontal"?(r===0&&a.range.stop(1,1)[f?"animate":"css"]({left:n+"%"},u.animate),r===1&&a.range[f?"animate":"css"]({width:n-t+"%"},{queue:!1,duration:u.animate})):(r===0&&a.range.stop(1,1)[f?"animate":"css"]({bottom:n+"%"},u.animate),r===1&&a.range[f?"animate":"css"]({height:n-t+"%"},{queue:!1,duration:u.animate}))),t=n}):(r=this.value(),i=this._valueMin(),s=this._valueMax(),n=s!==i?(r-i)/(s-i)*100:0,l[this.orientation==="horizontal"?"left":"bottom"]=n+"%",this.handle.stop(1,1)[f?"animate":"css"](l,u.animate),o==="min"&&this.orientation==="horizontal"&&this.range.stop(1,1)[f?"animate":"css"]({width:n+"%"},u.animate),o==="max"&&this.orientation==="horizontal"&&this.range[f?"animate":"css"]({width:100-n+"%"},{queue:!1,duration:u.animate}),o==="min"&&this.orientation==="vertical"&&this.range.stop(1,1)[f?"animate":"css"]({height:n+"%"},u.animate),o==="max"&&this.orientation==="vertical"&&this.range[f?"animate":"css"]({height:100-n+"%"},{queue:!1,duration:u.animate}))}})}(jQuery),function(e){function t(e){return function(){var t=this.element.val();e.apply(this,arguments),this._refresh(),t!==this.element.val()&&this._trigger("change")}}e.widget("ui.spinner",{version:"1.9.2",defaultElement:"<input>",widgetEventPrefix:"spin",options:{culture:null,icons:{down:"ui-icon-triangle-1-s",up:"ui-icon-triangle-1-n"},incremental:!0,max:null,min:null,numberFormat:null,page:10,step:1,change:null,spin:null,start:null,stop:null},_create:function(){this._setOption("max",this.options.max),this._setOption("min",this.options.min),this._setOption("step",this.options.step),this._value(this.element.val(),!0),this._draw(),this._on(this._events),this._refresh(),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_getCreateOptions:function(){var t={},n=this.element;return e.each(["min","max","step"],function(e,r){var i=n.attr(r);i!==undefined&&i.length&&(t[r]=i)}),t},_events:{keydown:function(e){this._start(e)&&this._keydown(e)&&e.preventDefault()},keyup:"_stop",focus:function(){this.previous=this.element.val()},blur:function(e){if(this.cancelBlur){delete this.cancelBlur;return}this._refresh(),this.previous!==this.element.val()&&this._trigger("change",e)},mousewheel:function(e,t){if(!t)return;if(!this.spinning&&!this._start(e))return!1;this._spin((t>0?1:-1)*this.options.step,e),clearTimeout(this.mousewheelTimer),this.mousewheelTimer=this._delay(function(){this.spinning&&this._stop(e)},100),e.preventDefault()},"mousedown .ui-spinner-button":function(t){function r(){var e=this.element[0]===this.document[0].activeElement;e||(this.element.focus(),this.previous=n,this._delay(function(){this.previous=n}))}var n;n=this.element[0]===this.document[0].activeElement?this.previous:this.element.val(),t.preventDefault(),r.call(this),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur,r.call(this)});if(this._start(t)===!1)return;this._repeat(null,e(t.currentTarget).hasClass("ui-spinner-up")?1:-1,t)},"mouseup .ui-spinner-button":"_stop","mouseenter .ui-spinner-button":function(t){if(!e(t.currentTarget).hasClass("ui-state-active"))return;if(this._start(t)===!1)return!1;this._repeat(null,e(t.currentTarget).hasClass("ui-spinner-up")?1:-1,t)},"mouseleave .ui-spinner-button":"_stop"},_draw:function(){var e=this.uiSpinner=this.element.addClass("ui-spinner-input").attr("autocomplete","off").wrap(this._uiSpinnerHtml()).parent().append(this._buttonHtml());this.element.attr("role","spinbutton"),this.buttons=e.find(".ui-spinner-button").attr("tabIndex",-1).button().removeClass("ui-corner-all"),this.buttons.height()>Math.ceil(e.height()*.5)&&e.height()>0&&e.height(e.height()),this.options.disabled&&this.disable()},_keydown:function(t){var n=this.options,r=e.ui.keyCode;switch(t.keyCode){case r.UP:return this._repeat(null,1,t),!0;case r.DOWN:return this._repeat(null,-1,t),!0;case r.PAGE_UP:return this._repeat(null,n.page,t),!0;case r.PAGE_DOWN:return this._repeat(null,-n.page,t),!0}return!1},_uiSpinnerHtml:function(){return"<span class='ui-spinner ui-widget ui-widget-content ui-corner-all'></span>"},_buttonHtml:function(){return"<a class='ui-spinner-button ui-spinner-up ui-corner-tr'><span class='ui-icon "+this.options.icons.up+"'>&#9650;</span>"+"</a>"+"<a class='ui-spinner-button ui-spinner-down ui-corner-br'>"+"<span class='ui-icon "+this.options.icons.down+"'>&#9660;</span>"+"</a>"},_start:function(e){return!this.spinning&&this._trigger("start",e)===!1?!1:(this.counter||(this.counter=1),this.spinning=!0,!0)},_repeat:function(e,t,n){e=e||500,clearTimeout(this.timer),this.timer=this._delay(function(){this._repeat(40,t,n)},e),this._spin(t*this.options.step,n)},_spin:function(e,t){var n=this.value()||0;this.counter||(this.counter=1),n=this._adjustValue(n+e*this._increment(this.counter));if(!this.spinning||this._trigger("spin",t,{value:n})!==!1)this._value(n),this.counter++},_increment:function(t){var n=this.options.incremental;return n?e.isFunction(n)?n(t):Math.floor(t*t*t/5e4-t*t/500+17*t/200+1):1},_precision:function(){var e=this._precisionOf(this.options.step);return this.options.min!==null&&(e=Math.max(e,this._precisionOf(this.options.min))),e},_precisionOf:function(e){var t=e.toString(),n=t.indexOf(".");return n===-1?0:t.length-n-1},_adjustValue:function(e){var t,n,r=this.options;return t=r.min!==null?r.min:0,n=e-t,n=Math.round(n/r.step)*r.step,e=t+n,e=parseFloat(e.toFixed(this._precision())),r.max!==null&&e>r.max?r.max:r.min!==null&&e<r.min?r.min:e},_stop:function(e){if(!this.spinning)return;clearTimeout(this.timer),clearTimeout(this.mousewheelTimer),this.counter=0,this.spinning=!1,this._trigger("stop",e)},_setOption:function(e,t){if(e==="culture"||e==="numberFormat"){var n=this._parse(this.element.val());this.options[e]=t,this.element.val(this._format(n));return}(e==="max"||e==="min"||e==="step")&&typeof t=="string"&&(t=this._parse(t)),this._super(e,t),e==="disabled"&&(t?(this.element.prop("disabled",!0),this.buttons.button("disable")):(this.element.prop("disabled",!1),this.buttons.button("enable")))},_setOptions:t(function(e){this._super(e),this._value(this.element.val())}),_parse:function(e){return typeof e=="string"&&e!==""&&(e=window.Globalize&&this.options.numberFormat?Globalize.parseFloat(e,10,this.options.culture):+e),e===""||isNaN(e)?null:e},_format:function(e){return e===""?"":window.Globalize&&this.options.numberFormat?Globalize.format(e,this.options.numberFormat,this.options.culture):e},_refresh:function(){this.element.attr({"aria-valuemin":this.options.min,"aria-valuemax":this.options.max,"aria-valuenow":this._parse(this.element.val())})},_value:function(e,t){var n;e!==""&&(n=this._parse(e),n!==null&&(t||(n=this._adjustValue(n)),e=this._format(n))),this.element.val(e),this._refresh()},_destroy:function(){this.element.removeClass("ui-spinner-input").prop("disabled",!1).removeAttr("autocomplete").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow"),this.uiSpinner.replaceWith(this.element)},stepUp:t(function(e){this._stepUp(e)}),_stepUp:function(e){this._spin((e||1)*this.options.step)},stepDown:t(function(e){this._stepDown(e)}),_stepDown:function(e){this._spin((e||1)*-this.options.step)},pageUp:t(function(e){this._stepUp((e||1)*this.options.page)}),pageDown:t(function(e){this._stepDown((e||1)*this.options.page)}),value:function(e){if(!arguments.length)return this._parse(this.element.val());t(this._value).call(this,e)},widget:function(){return this.uiSpinner}})}(jQuery),function(e,t){function i(){return++n}function s(e){return e.hash.length>1&&e.href.replace(r,"")===location.href.replace(r,"").replace(/\s/g,"%20")}var n=0,r=/#.*$/;e.widget("ui.tabs",{version:"1.9.2",delay:300,options:{active:null,collapsible:!1,event:"click",heightStyle:"content",hide:null,show:null,activate:null,beforeActivate:null,beforeLoad:null,load:null},_create:function(){var t=this,n=this.options,r=n.active,i=location.hash.substring(1);this.running=!1,this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all").toggleClass("ui-tabs-collapsible",n.collapsible).delegate(".ui-tabs-nav > li","mousedown"+this.eventNamespace,function(t){e(this).is(".ui-state-disabled")&&t.preventDefault()}).delegate(".ui-tabs-anchor","focus"+this.eventNamespace,function(){e(this).closest("li").is(".ui-state-disabled")&&this.blur()}),this._processTabs();if(r===null){i&&this.tabs.each(function(t,n){if(e(n).attr("aria-controls")===i)return r=t,!1}),r===null&&(r=this.tabs.index(this.tabs.filter(".ui-tabs-active")));if(r===null||r===-1)r=this.tabs.length?0:!1}r!==!1&&(r=this.tabs.index(this.tabs.eq(r)),r===-1&&(r=n.collapsible?!1:0)),n.active=r,!n.collapsible&&n.active===!1&&this.anchors.length&&(n.active=0),e.isArray(n.disabled)&&(n.disabled=e.unique(n.disabled.concat(e.map(this.tabs.filter(".ui-state-disabled"),function(e){return t.tabs.index(e)}))).sort()),this.options.active!==!1&&this.anchors.length?this.active=this._findActive(this.options.active):this.active=e(),this._refresh(),this.active.length&&this.load(n.active)},_getCreateEventData:function(){return{tab:this.active,panel:this.active.length?this._getPanelForTab(this.active):e()}},_tabKeydown:function(t){var n=e(this.document[0].activeElement).closest("li"),r=this.tabs.index(n),i=!0;if(this._handlePageNav(t))return;switch(t.keyCode){case e.ui.keyCode.RIGHT:case e.ui.keyCode.DOWN:r++;break;case e.ui.keyCode.UP:case e.ui.keyCode.LEFT:i=!1,r--;break;case e.ui.keyCode.END:r=this.anchors.length-1;break;case e.ui.keyCode.HOME:r=0;break;case e.ui.keyCode.SPACE:t.preventDefault(),clearTimeout(this.activating),this._activate(r);return;case e.ui.keyCode.ENTER:t.preventDefault(),clearTimeout(this.activating),this._activate(r===this.options.active?!1:r);return;default:return}t.preventDefault(),clearTimeout(this.activating),r=this._focusNextTab(r,i),t.ctrlKey||(n.attr("aria-selected","false"),this.tabs.eq(r).attr("aria-selected","true"),this.activating=this._delay(function(){this.option("active",r)},this.delay))},_panelKeydown:function(t){if(this._handlePageNav(t))return;t.ctrlKey&&t.keyCode===e.ui.keyCode.UP&&(t.preventDefault(),this.active.focus())},_handlePageNav:function(t){if(t.altKey&&t.keyCode===e.ui.keyCode.PAGE_UP)return this._activate(this._focusNextTab(this.options.active-1,!1)),!0;if(t.altKey&&t.keyCode===e.ui.keyCode.PAGE_DOWN)return this._activate(this._focusNextTab(this.options.active+1,!0)),!0},_findNextTab:function(t,n){function i(){return t>r&&(t=0),t<0&&(t=r),t}var r=this.tabs.length-1;while(e.inArray(i(),this.options.disabled)!==-1)t=n?t+1:t-1;return t},_focusNextTab:function(e,t){return e=this._findNextTab(e,t),this.tabs.eq(e).focus(),e},_setOption:function(e,t){if(e==="active"){this._activate(t);return}if(e==="disabled"){this._setupDisabled(t);return}this._super(e,t),e==="collapsible"&&(this.element.toggleClass("ui-tabs-collapsible",t),!t&&this.options.active===!1&&this._activate(0)),e==="event"&&this._setupEvents(t),e==="heightStyle"&&this._setupHeightStyle(t)},_tabId:function(e){return e.attr("aria-controls")||"ui-tabs-"+i()},_sanitizeSelector:function(e){return e?e.replace(/[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g,"\\$&"):""},refresh:function(){var t=this.options,n=this.tablist.children(":has(a[href])");t.disabled=e.map(n.filter(".ui-state-disabled"),function(e){return n.index(e)}),this._processTabs(),t.active===!1||!this.anchors.length?(t.active=!1,this.active=e()):this.active.length&&!e.contains(this.tablist[0],this.active[0])?this.tabs.length===t.disabled.length?(t.active=!1,this.active=e()):this._activate(this._findNextTab(Math.max(0,t.active-1),!1)):t.active=this.tabs.index(this.active),this._refresh()},_refresh:function(){this._setupDisabled(this.options.disabled),this._setupEvents(this.options.event),this._setupHeightStyle(this.options.heightStyle),this.tabs.not(this.active).attr({"aria-selected":"false",tabIndex:-1}),this.panels.not(this._getPanelForTab(this.active)).hide().attr({"aria-expanded":"false","aria-hidden":"true"}),this.active.length?(this.active.addClass("ui-tabs-active ui-state-active").attr({"aria-selected":"true",tabIndex:0}),this._getPanelForTab(this.active).show().attr({"aria-expanded":"true","aria-hidden":"false"})):this.tabs.eq(0).attr("tabIndex",0)},_processTabs:function(){var t=this;this.tablist=this._getList().addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all").attr("role","tablist"),this.tabs=this.tablist.find("> li:has(a[href])").addClass("ui-state-default ui-corner-top").attr({role:"tab",tabIndex:-1}),this.anchors=this.tabs.map(function(){return e("a",this)[0]}).addClass("ui-tabs-anchor").attr({role:"presentation",tabIndex:-1}),this.panels=e(),this.anchors.each(function(n,r){var i,o,u,a=e(r).uniqueId().attr("id"),f=e(r).closest("li"),l=f.attr("aria-controls");s(r)?(i=r.hash,o=t.element.find(t._sanitizeSelector(i))):(u=t._tabId(f),i="#"+u,o=t.element.find(i),o.length||(o=t._createPanel(u),o.insertAfter(t.panels[n-1]||t.tablist)),o.attr("aria-live","polite")),o.length&&(t.panels=t.panels.add(o)),l&&f.data("ui-tabs-aria-controls",l),f.attr({"aria-controls":i.substring(1),"aria-labelledby":a}),o.attr("aria-labelledby",a)}),this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").attr("role","tabpanel")},_getList:function(){return this.element.find("ol,ul").eq(0)},_createPanel:function(t){return e("<div>").attr("id",t).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").data("ui-tabs-destroy",!0)},_setupDisabled:function(t){e.isArray(t)&&(t.length?t.length===this.anchors.length&&(t=!0):t=!1);for(var n=0,r;r=this.tabs[n];n++)t===!0||e.inArray(n,t)!==-1?e(r).addClass("ui-state-disabled").attr("aria-disabled","true"):e(r).removeClass("ui-state-disabled").removeAttr("aria-disabled");this.options.disabled=t},_setupEvents:function(t){var n={click:function(e){e.preventDefault()}};t&&e.each(t.split(" "),function(e,t){n[t]="_eventHandler"}),this._off(this.anchors.add(this.tabs).add(this.panels)),this._on(this.anchors,n),this._on(this.tabs,{keydown:"_tabKeydown"}),this._on(this.panels,{keydown:"_panelKeydown"}),this._focusable(this.tabs),this._hoverable(this.tabs)},_setupHeightStyle:function(t){var n,r,i=this.element.parent();t==="fill"?(e.support.minHeight||(r=i.css("overflow"),i.css("overflow","hidden")),n=i.height(),this.element.siblings(":visible").each(function(){var t=e(this),r=t.css("position");if(r==="absolute"||r==="fixed")return;n-=t.outerHeight(!0)}),r&&i.css("overflow",r),this.element.children().not(this.panels).each(function(){n-=e(this).outerHeight(!0)}),this.panels.each(function(){e(this).height(Math.max(0,n-e(this).innerHeight()+e(this).height()))}).css("overflow","auto")):t==="auto"&&(n=0,this.panels.each(function(){n=Math.max(n,e(this).height("").height())}).height(n))},_eventHandler:function(t){var n=this.options,r=this.active,i=e(t.currentTarget),s=i.closest("li"),o=s[0]===r[0],u=o&&n.collapsible,a=u?e():this._getPanelForTab(s),f=r.length?this._getPanelForTab(r):e(),l={oldTab:r,oldPanel:f,newTab:u?e():s,newPanel:a};t.preventDefault();if(s.hasClass("ui-state-disabled")||s.hasClass("ui-tabs-loading")||this.running||o&&!n.collapsible||this._trigger("beforeActivate",t,l)===!1)return;n.active=u?!1:this.tabs.index(s),this.active=o?e():s,this.xhr&&this.xhr.abort(),!f.length&&!a.length&&e.error("jQuery UI Tabs: Mismatching fragment identifier."),a.length&&this.load(this.tabs.index(s),t),this._toggle(t,l)},_toggle:function(t,n){function o(){r.running=!1,r._trigger("activate",t,n)}function u(){n.newTab.closest("li").addClass("ui-tabs-active ui-state-active"),i.length&&r.options.show?r._show(i,r.options.show,o):(i.show(),o())}var r=this,i=n.newPanel,s=n.oldPanel;this.running=!0,s.length&&this.options.hide?this._hide(s,this.options.hide,function(){n.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),u()}):(n.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),s.hide(),u()),s.attr({"aria-expanded":"false","aria-hidden":"true"}),n.oldTab.attr("aria-selected","false"),i.length&&s.length?n.oldTab.attr("tabIndex",-1):i.length&&this.tabs.filter(function(){return e(this).attr("tabIndex")===0}).attr("tabIndex",-1),i.attr({"aria-expanded":"true","aria-hidden":"false"}),n.newTab.attr({"aria-selected":"true",tabIndex:0})},_activate:function(t){var n,r=this._findActive(t);if(r[0]===this.active[0])return;r.length||(r=this.active),n=r.find(".ui-tabs-anchor")[0],this._eventHandler({target:n,currentTarget:n,preventDefault:e.noop})},_findActive:function(t){return t===!1?e():this.tabs.eq(t)},_getIndex:function(e){return typeof e=="string"&&(e=this.anchors.index(this.anchors.filter("[href$='"+e+"']"))),e},_destroy:function(){this.xhr&&this.xhr.abort(),this.element.removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible"),this.tablist.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all").removeAttr("role"),this.anchors.removeClass("ui-tabs-anchor").removeAttr("role").removeAttr("tabIndex").removeData("href.tabs").removeData("load.tabs").removeUniqueId(),this.tabs.add(this.panels).each(function(){e.data(this,"ui-tabs-destroy")?e(this).remove():e(this).removeClass("ui-state-default ui-state-active ui-state-disabled ui-corner-top ui-corner-bottom ui-widget-content ui-tabs-active ui-tabs-panel").removeAttr("tabIndex").removeAttr("aria-live").removeAttr("aria-busy").removeAttr("aria-selected").removeAttr("aria-labelledby").removeAttr("aria-hidden").removeAttr("aria-expanded").removeAttr("role")}),this.tabs.each(function(){var t=e(this),n=t.data("ui-tabs-aria-controls");n?t.attr("aria-controls",n):t.removeAttr("aria-controls")}),this.panels.show(),this.options.heightStyle!=="content"&&this.panels.css("height","")},enable:function(n){var r=this.options.disabled;if(r===!1)return;n===t?r=!1:(n=this._getIndex(n),e.isArray(r)?r=e.map(r,function(e){return e!==n?e:null}):r=e.map(this.tabs,function(e,t){return t!==n?t:null})),this._setupDisabled(r)},disable:function(n){var r=this.options.disabled;if(r===!0)return;if(n===t)r=!0;else{n=this._getIndex(n);if(e.inArray(n,r)!==-1)return;e.isArray(r)?r=e.merge([n],r).sort():r=[n]}this._setupDisabled(r)},load:function(t,n){t=this._getIndex(t);var r=this,i=this.tabs.eq(t),o=i.find(".ui-tabs-anchor"),u=this._getPanelForTab(i),a={tab:i,panel:u};if(s(o[0]))return;this.xhr=e.ajax(this._ajaxSettings(o,n,a)),this.xhr&&this.xhr.statusText!=="canceled"&&(i.addClass("ui-tabs-loading"),u.attr("aria-busy","true"),this.xhr.success(function(e){setTimeout(function(){u.html(e),r._trigger("load",n,a)},1)}).complete(function(e,t){setTimeout(function(){t==="abort"&&r.panels.stop(!1,!0),i.removeClass("ui-tabs-loading"),u.removeAttr("aria-busy"),e===r.xhr&&delete r.xhr},1)}))},_ajaxSettings:function(t,n,r){var i=this;return{url:t.attr("href"),beforeSend:function(t,s){return i._trigger("beforeLoad",n,e.extend({jqXHR:t,ajaxSettings:s},r))}}},_getPanelForTab:function(t){var n=e(t).attr("aria-controls");return this.element.find(this._sanitizeSelector("#"+n))}}),e.uiBackCompat!==!1&&(e.ui.tabs.prototype._ui=function(e,t){return{tab:e,panel:t,index:this.anchors.index(e)}},e.widget("ui.tabs",e.ui.tabs,{url:function(e,t){this.anchors.eq(e).attr("href",t)}}),e.widget("ui.tabs",e.ui.tabs,{options:{ajaxOptions:null,cache:!1},_create:function(){this._super();var t=this;this._on({tabsbeforeload:function(n,r){if(e.data(r.tab[0],"cache.tabs")){n.preventDefault();return}r.jqXHR.success(function(){t.options.cache&&e.data(r.tab[0],"cache.tabs",!0)})}})},_ajaxSettings:function(t,n,r){var i=this.options.ajaxOptions;return e.extend({},i,{error:function(e,t){try{i.error(e,t,r.tab.closest("li").index(),r.tab[0])}catch(n){}}},this._superApply(arguments))},_setOption:function(e,t){e==="cache"&&t===!1&&this.anchors.removeData("cache.tabs"),this._super(e,t)},_destroy:function(){this.anchors.removeData("cache.tabs"),this._super()},url:function(e){this.anchors.eq(e).removeData("cache.tabs"),this._superApply(arguments)}}),e.widget("ui.tabs",e.ui.tabs,{abort:function(){this.xhr&&this.xhr.abort()}}),e.widget("ui.tabs",e.ui.tabs,{options:{spinner:"<em>Loading&#8230;</em>"},_create:function(){this._super(),this._on({tabsbeforeload:function(e,t){if(e.target!==this.element[0]||!this.options.spinner)return;var n=t.tab.find("span"),r=n.html();n.html(this.options.spinner),t.jqXHR.complete(function(){n.html(r)})}})}}),e.widget("ui.tabs",e.ui.tabs,{options:{enable:null,disable:null},enable:function(t){var n=this.options,r;if(t&&n.disabled===!0||e.isArray(n.disabled)&&e.inArray(t,n.disabled)!==-1)r=!0;this._superApply(arguments),r&&this._trigger("enable",null,this._ui(this.anchors[t],this.panels[t]))},disable:function(t){var n=this.options,r;if(t&&n.disabled===!1||e.isArray(n.disabled)&&e.inArray(t,n.disabled)===-1)r=!0;this._superApply(arguments),r&&this._trigger("disable",null,this._ui(this.anchors[t],this.panels[t]))}}),e.widget("ui.tabs",e.ui.tabs,{options:{add:null,remove:null,tabTemplate:"<li><a href='#{href}'><span>#{label}</span></a></li>"},add:function(n,r,i){i===t&&(i=this.anchors.length);var s,o,u=this.options,a=e(u.tabTemplate.replace(/#\{href\}/g,n).replace(/#\{label\}/g,r)),f=n.indexOf("#")?this._tabId(a):n.replace("#","");return a.addClass("ui-state-default ui-corner-top").data("ui-tabs-destroy",!0),a.attr("aria-controls",f),s=i>=this.tabs.length,o=this.element.find("#"+f),o.length||(o=this._createPanel(f),s?i>0?o.insertAfter(this.panels.eq(-1)):o.appendTo(this.element):o.insertBefore(this.panels[i])),o.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").hide(),s?a.appendTo(this.tablist):a.insertBefore(this.tabs[i]),u.disabled=e.map(u.disabled,function(e){return e>=i?++e:e}),this.refresh(),this.tabs.length===1&&u.active===!1&&this.option("active",0),this._trigger("add",null,this._ui(this.anchors[i],this.panels[i])),this},remove:function(t){t=this._getIndex(t);var n=this.options,r=this.tabs.eq(t).remove(),i=this._getPanelForTab(r).remove();return r.hasClass("ui-tabs-active")&&this.anchors.length>2&&this._activate(t+(t+1<this.anchors.length?1:-1)),n.disabled=e.map(e.grep(n.disabled,function(e){return e!==t}),function(e){return e>=t?--e:e}),this.refresh(),this._trigger("remove",null,this._ui(r.find("a")[0],i[0])),this}}),e.widget("ui.tabs",e.ui.tabs,{length:function(){return this.anchors.length}}),e.widget("ui.tabs",e.ui.tabs,{options:{idPrefix:"ui-tabs-"},_tabId:function(t){var n=t.is("li")?t.find("a[href]"):t;return n=n[0],e(n).closest("li").attr("aria-controls")||n.title&&n.title.replace(/\s/g,"_").replace(/[^\w\u00c0-\uFFFF\-]/g,"")||this.options.idPrefix+i()}}),e.widget("ui.tabs",e.ui.tabs,{options:{panelTemplate:"<div></div>"},_createPanel:function(t){return e(this.options.panelTemplate).attr("id",t).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").data("ui-tabs-destroy",!0)}}),e.widget("ui.tabs",e.ui.tabs,{_create:function(){var e=this.options;e.active===null&&e.selected!==t&&(e.active=e.selected===-1?!1:e.selected),this._super(),e.selected=e.active,e.selected===!1&&(e.selected=-1)},_setOption:function(e,t){if(e!=="selected")return this._super(e,t);var n=this.options;this._super("active",t===-1?!1:t),n.selected=n.active,n.selected===!1&&(n.selected=-1)},_eventHandler:function(){this._superApply(arguments),this.options.selected=this.options.active,this.options.selected===!1&&(this.options.selected=-1)}}),e.widget("ui.tabs",e.ui.tabs,{options:{show:null,select:null},_create:function(){this._super(),this.options.active!==!1&&this._trigger("show",null,this._ui(this.active.find(".ui-tabs-anchor")[0],this._getPanelForTab(this.active)[0]))},_trigger:function(e,t,n){var r,i,s=this._superApply(arguments);return s?(e==="beforeActivate"?(r=n.newTab.length?n.newTab:n.oldTab,i=n.newPanel.length?n.newPanel:n.oldPanel,s=this._super("select",t,{tab:r.find(".ui-tabs-anchor")[0],panel:i[0],index:r.closest("li").index()})):e==="activate"&&n.newTab.length&&(s=this._super("show",t,{tab:n.newTab.find(".ui-tabs-anchor")[0],panel:n.newPanel[0],index:n.newTab.closest("li").index()})),s):!1}}),e.widget("ui.tabs",e.ui.tabs,{select:function(e){e=this._getIndex(e);if(e===-1){if(!this.options.collapsible||this.options.selected===-1)return;e=this.options.selected}this.anchors.eq(e).trigger(this.options.event+this.eventNamespace)}}),function(){var t=0;e.widget("ui.tabs",e.ui.tabs,{options:{cookie:null},_create:function(){var e=this.options,t;e.active==null&&e.cookie&&(t=parseInt(this._cookie(),10),t===-1&&(t=!1),e.active=t),this._super()},_cookie:function(n){var r=[this.cookie||(this.cookie=this.options.cookie.name||"ui-tabs-"+ ++t)];return arguments.length&&(r.push(n===!1?-1:n),r.push(this.options.cookie)),e.cookie.apply(null,r)},_refresh:function(){this._super(),this.options.cookie&&this._cookie(this.options.active,this.options.cookie)},_eventHandler:function(){this._superApply(arguments),this.options.cookie&&this._cookie(this.options.active,this.options.cookie)},_destroy:function(){this._super(),this.options.cookie&&this._cookie(null,this.options.cookie)}})}(),e.widget("ui.tabs",e.ui.tabs,{_trigger:function(t,n,r){var i=e.extend({},r);return t==="load"&&(i.panel=i.panel[0],i.tab=i.tab.find(".ui-tabs-anchor")[0]),this._super(t,n,i)}}),e.widget("ui.tabs",e.ui.tabs,{options:{fx:null},_getFx:function(){var t,n,r=this.options.fx;return r&&(e.isArray(r)?(t=r[0],n=r[1]):t=n=r),r?{show:n,hide:t}:null},_toggle:function(e,t){function o(){n.running=!1,n._trigger("activate",e,t)}function u(){t.newTab.closest("li").addClass("ui-tabs-active ui-state-active"),r.length&&s.show?r.animate(s.show,s.show.duration,function(){o()}):(r.show(),o())}var n=this,r=t.newPanel,i=t.oldPanel,s=this._getFx();if(!s)return this._super(e,t);n.running=!0,i.length&&s.hide?i.animate(s.hide,s.hide.duration,function(){t.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),u()}):(t.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),i.hide(),u())}}))}(jQuery),function(e){function n(t,n){var r=(t.attr("aria-describedby")||"").split(/\s+/);r.push(n),t.data("ui-tooltip-id",n).attr("aria-describedby",e.trim(r.join(" ")))}function r(t){var n=t.data("ui-tooltip-id"),r=(t.attr("aria-describedby")||"").split(/\s+/),i=e.inArray(n,r);i!==-1&&r.splice(i,1),t.removeData("ui-tooltip-id"),r=e.trim(r.join(" ")),r?t.attr("aria-describedby",r):t.removeAttr("aria-describedby")}var t=0;e.widget("ui.tooltip",{version:"1.9.2",options:{content:function(){return e(this).attr("title")},hide:!0,items:"[title]:not([disabled])",position:{my:"left top+15",at:"left bottom",collision:"flipfit flip"},show:!0,tooltipClass:null,track:!1,close:null,open:null},_create:function(){this._on({mouseover:"open",focusin:"open"}),this.tooltips={},this.parents={},this.options.disabled&&this._disable()},_setOption:function(t,n){var r=this;if(t==="disabled"){this[n?"_disable":"_enable"](),this.options[t]=n;return}this._super(t,n),t==="content"&&e.each(this.tooltips,function(e,t){r._updateContent(t)})},_disable:function(){var t=this;e.each(this.tooltips,function(n,r){var i=e.Event("blur");i.target=i.currentTarget=r[0],t.close(i,!0)}),this.element.find(this.options.items).andSelf().each(function(){var t=e(this);t.is("[title]")&&t.data("ui-tooltip-title",t.attr("title")).attr("title","")})},_enable:function(){this.element.find(this.options.items).andSelf().each(function(){var t=e(this);t.data("ui-tooltip-title")&&t.attr("title",t.data("ui-tooltip-title"))})},open:function(t){var n=this,r=e(t?t.target:this.element).closest(this.options.items);if(!r.length||r.data("ui-tooltip-id"))return;r.attr("title")&&r.data("ui-tooltip-title",r.attr("title")),r.data("ui-tooltip-open",!0),t&&t.type==="mouseover"&&r.parents().each(function(){var t=e(this),r;t.data("ui-tooltip-open")&&(r=e.Event("blur"),r.target=r.currentTarget=this,n.close(r,!0)),t.attr("title")&&(t.uniqueId(),n.parents[this.id]={element:this,title:t.attr("title")},t.attr("title",""))}),this._updateContent(r,t)},_updateContent:function(e,t){var n,r=this.options.content,i=this,s=t?t.type:null;if(typeof r=="string")return this._open(t,e,r);n=r.call(e[0],function(n){if(!e.data("ui-tooltip-open"))return;i._delay(function(){t&&(t.type=s),this._open(t,e,n)})}),n&&this._open(t,e,n)},_open:function(t,r,i){function f(e){a.of=e;if(s.is(":hidden"))return;s.position(a)}var s,o,u,a=e.extend({},this.options.position);if(!i)return;s=this._find(r);if(s.length){s.find(".ui-tooltip-content").html(i);return}r.is("[title]")&&(t&&t.type==="mouseover"?r.attr("title",""):r.removeAttr("title")),s=this._tooltip(r),n(r,s.attr("id")),s.find(".ui-tooltip-content").html(i),this.options.track&&t&&/^mouse/.test(t.type)?(this._on(this.document,{mousemove:f}),f(t)):s.position(e.extend({of:r},this.options.position)),s.hide(),this._show(s,this.options.show),this.options.show&&this.options.show.delay&&(u=setInterval(function(){s.is(":visible")&&(f(a.of),clearInterval(u))},e.fx.interval)),this._trigger("open",t,{tooltip:s}),o={keyup:function(t){if(t.keyCode===e.ui.keyCode.ESCAPE){var n=e.Event(t);n.currentTarget=r[0],this.close(n,!0)}},remove:function(){this._removeTooltip(s)}};if(!t||t.type==="mouseover")o.mouseleave="close";if(!t||t.type==="focusin")o.focusout="close";this._on(!0,r,o)},close:function(t){var n=this,i=e(t?t.currentTarget:this.element),s=this._find(i);if(this.closing)return;i.data("ui-tooltip-title")&&i.attr("title",i.data("ui-tooltip-title")),r(i),s.stop(!0),this._hide(s,this.options.hide,function(){n._removeTooltip(e(this))}),i.removeData("ui-tooltip-open"),this._off(i,"mouseleave focusout keyup"),i[0]!==this.element[0]&&this._off(i,"remove"),this._off(this.document,"mousemove"),t&&t.type==="mouseleave"&&e.each(this.parents,function(t,r){e(r.element).attr("title",r.title),delete n.parents[t]}),this.closing=!0,this._trigger("close",t,{tooltip:s}),this.closing=!1},_tooltip:function(n){var r="ui-tooltip-"+t++,i=e("<div>").attr({id:r,role:"tooltip"}).addClass("ui-tooltip ui-widget ui-corner-all ui-widget-content "+(this.options.tooltipClass||""));return e("<div>").addClass("ui-tooltip-content").appendTo(i),i.appendTo(this.document[0].body),e.fn.bgiframe&&i.bgiframe(),this.tooltips[r]=n,i},_find:function(t){var n=t.data("ui-tooltip-id");return n?e("#"+n):e()},_removeTooltip:function(e){e.remove(),delete this.tooltips[e.attr("id")]},_destroy:function(){var t=this;e.each(this.tooltips,function(n,r){var i=e.Event("blur");i.target=i.currentTarget=r[0],t.close(i,!0),e("#"+n).remove(),r.data("ui-tooltip-title")&&(r.attr("title",r.data("ui-tooltip-title")),r.removeData("ui-tooltip-title"))})}})}(jQuery);
    \ No newline at end of file
    diff --git a/lib/jquery.ui.touch-punch.min.js b/lib/jquery.ui.touch-punch.min.js
    new file mode 100644
    index 000000000..33d6f97e5
    --- /dev/null
    +++ b/lib/jquery.ui.touch-punch.min.js
    @@ -0,0 +1,11 @@
    +/*
    + * jQuery UI Touch Punch 0.2.2
    + *
    + * Copyright 2011, Dave Furfero
    + * Dual licensed under the MIT or GPL Version 2 licenses.
    + *
    + * Depends:
    + *  jquery.ui.widget.js
    + *  jquery.ui.mouse.js
    + */
    +(function(b){b.support.touch="ontouchend" in document;if(!b.support.touch){return;}var c=b.ui.mouse.prototype,e=c._mouseInit,a;function d(g,h){if(g.originalEvent.touches.length>1){return;}g.preventDefault();var i=g.originalEvent.changedTouches[0],f=document.createEvent("MouseEvents");f.initMouseEvent(h,true,true,window,1,i.screenX,i.screenY,i.clientX,i.clientY,false,false,false,false,0,null);g.target.dispatchEvent(f);}c._touchStart=function(g){var f=this;if(a||!f._mouseCapture(g.originalEvent.changedTouches[0])){return;}a=true;f._touchMoved=false;d(g,"mouseover");d(g,"mousemove");d(g,"mousedown");};c._touchMove=function(f){if(!a){return;}this._touchMoved=true;d(f,"mousemove");};c._touchEnd=function(f){if(!a){return;}d(f,"mouseup");d(f,"mouseout");if(!this._touchMoved){d(f,"click");}a=false;};c._mouseInit=function(){var f=this;f.element.bind("touchstart",b.proxy(f,"_touchStart")).bind("touchmove",b.proxy(f,"_touchMove")).bind("touchend",b.proxy(f,"_touchEnd"));e.call(f);};})(jQuery);
    \ No newline at end of file
    diff --git a/lib/jsBezier-0.3-min.js b/lib/jsBezier-0.3-min.js
    new file mode 100644
    index 000000000..ac8b101db
    --- /dev/null
    +++ b/lib/jsBezier-0.3-min.js
    @@ -0,0 +1,7 @@
    +(function(){if("undefined"==typeof Math.sgn)Math.sgn=function(a){return 0==a?0:0<a?1:-1};var l={subtract:function(a,b){return{x:a.x-b.x,y:a.y-b.y}},dotProduct:function(a,b){return a.x*b.x+a.y*b.y},square:function(a){return Math.sqrt(a.x*a.x+a.y*a.y)},scale:function(a,b){return{x:a.x*b,y:a.y*b}}},w=Math.pow(2,-65),u=function(a,b){for(var f=[],d=b.length-1,h=2*d-1,g=[],c=[],k=[],i=[],m=[[1,0.6,0.3,0.1],[0.4,0.6,0.6,0.4],[0.1,0.3,0.6,1]],e=0;e<=d;e++)g[e]=l.subtract(b[e],a);for(e=0;e<=d-1;e++)c[e]=l.subtract(b[e+
    +1],b[e]),c[e]=l.scale(c[e],3);for(e=0;e<=d-1;e++)for(var n=0;n<=d;n++)k[e]||(k[e]=[]),k[e][n]=l.dotProduct(c[e],g[n]);for(e=0;e<=h;e++)i[e]||(i[e]=[]),i[e].y=0,i[e].x=parseFloat(e)/h;h=d-1;for(g=0;g<=d+h;g++){e=Math.max(0,g-h);for(c=Math.min(g,d);e<=c;e++)j=g-e,i[e+j].y+=k[j][e]*m[j][e]}d=b.length-1;i=q(i,2*d-1,f,0);h=l.subtract(a,b[0]);k=l.square(h);for(e=m=0;e<i;e++)h=l.subtract(a,r(b,d,f[e],null,null)),h=l.square(h),h<k&&(k=h,m=f[e]);h=l.subtract(a,b[d]);h=l.square(h);h<k&&(k=h,m=1);return{location:m,
    +distance:k}},q=function(a,b,f,d){var h=[],g=[],c=[],k=[],i=0,m,e;e=Math.sgn(a[0].y);for(var n=1;n<=b;n++)m=Math.sgn(a[n].y),m!=e&&i++,e=m;switch(i){case 0:return 0;case 1:if(64<=d)return f[0]=(a[0].x+a[b].x)/2,1;var o,i=a[0].y-a[b].y;m=a[b].x-a[0].x;e=a[0].x*a[b].y-a[b].x*a[0].y;n=max_distance_below=0;for(o=1;o<b;o++){var l=i*a[o].x+m*a[o].y+e;l>n?n=l:l<max_distance_below&&(max_distance_below=l)}o=m;n=(1*(e-n)-0*o)*(1/(0*o-1*i));o=m;i=(1*(e-max_distance_below)-0*o)*(1/(0*o-1*i));m=Math.min(n,i);if(Math.max(n,
    +i)-m<w)return c=a[b].x-a[0].x,k=a[b].y-a[0].y,f[0]=0+1*(c*(a[0].y-0)-k*(a[0].x-0))*(1/(0*c-1*k)),1}r(a,b,0.5,h,g);a=q(h,b,c,d+1);b=q(g,b,k,d+1);for(d=0;d<a;d++)f[d]=c[d];for(d=0;d<b;d++)f[d+a]=k[d];return a+b},r=function(a,b,f,d,h){for(var g=[[]],c=0;c<=b;c++)g[0][c]=a[c];for(a=1;a<=b;a++)for(c=0;c<=b-a;c++)g[a]||(g[a]=[]),g[a][c]||(g[a][c]={}),g[a][c].x=(1-f)*g[a-1][c].x+f*g[a-1][c+1].x,g[a][c].y=(1-f)*g[a-1][c].y+f*g[a-1][c+1].y;if(null!=d)for(c=0;c<=b;c++)d[c]=g[c][0];if(null!=h)for(c=0;c<=b;c++)h[c]=
    +g[b-c][c];return g[b][0]},v={},x=function(a){var b=v[a];if(!b){var b=[],f=function(a){return function(){return a}},d=function(){return function(a){return a}},h=function(){return function(a){return 1-a}},g=function(a){return function(b){for(var c=1,d=0;d<a.length;d++)c*=a[d](b);return c}};b.push(new function(){return function(b){return Math.pow(b,a)}});for(var c=1;c<a;c++){for(var k=[new f(a)],i=0;i<a-c;i++)k.push(new d);for(i=0;i<c;i++)k.push(new h);b.push(new g(k))}b.push(new function(){return function(b){return Math.pow(1-
    +b,a)}});v[a]=b}return b},p=function(a,b){for(var f=x(a.length-1),d=0,h=0,g=0;g<a.length;g++)d+=a[g].x*f[g](b),h+=a[g].y*f[g](b);return{x:d,y:h}},s=function(a,b,f){for(var d=p(a,b),h=0,g=0<f?1:-1,c=null;h<Math.abs(f);)b+=0.005*g,c=p(a,b),h+=Math.sqrt(Math.pow(c.x-d.x,2)+Math.pow(c.y-d.y,2)),d=c;return{point:c,location:b}},t=function(a,b){var f=p(a,b),d=p(a.slice(0,a.length-1),b),h=d.y-f.y,f=d.x-f.x;return 0==h?Infinity:Math.atan(h/f)};window.jsBezier={distanceFromCurve:u,gradientAtPoint:t,gradientAtPointAlongCurveFrom:function(a,
    +b,f){b=s(a,b,f);if(1<b.location)b.location=1;if(0>b.location)b.location=0;return t(a,b.location)},nearestPointOnCurve:function(a,b){var f=u(a,b);return{point:r(b,b.length-1,f.location,null,null),location:f.location}},pointOnCurve:p,pointAlongCurveFrom:function(a,b,f){return s(a,b,f).point},perpendicularToCurveAt:function(a,b,f,d){b=s(a,b,null==d?0:d);a=t(a,b.location);d=Math.atan(-1/a);a=f/2*Math.sin(d);f=f/2*Math.cos(d);return[{x:b.point.x+f,y:b.point.y+a},{x:b.point.x-f,y:b.point.y-a}]}}})();
    \ No newline at end of file
    diff --git a/lib/jsBezier-0.3.js b/lib/jsBezier-0.3.js
    new file mode 100644
    index 000000000..b2e091257
    --- /dev/null
    +++ b/lib/jsBezier-0.3.js
    @@ -0,0 +1,375 @@
    +/**
    +* jsBezier-0.3
    +*
    +* Copyright (c) 2010 - 2011 Simon Porritt (simon.porritt@gmail.com)
    +*
    +* licensed under the MIT license.
    +* 
    +* a set of Bezier curve functions that deal with Beziers, used by jsPlumb, and perhaps useful for other people.  These functions work with Bezier
    +* curves of arbitrary degree.
    +*
    +* - functions are all in the 'jsBezier' namespace.  
    +* 
    +* - all input points should be in the format {x:.., y:..}. all output points are in this format too.
    +* 
    +* - all input curves should be in the format [ {x:.., y:..}, {x:.., y:..}, {x:.., y:..}, {x:.., y:..} ]
    +* 
    +* - 'location' as used as an input here refers to a decimal in the range 0-1 inclusive, which indicates a point some proportion along the length
    +* of the curve.  location as output has the same format and meaning.
    +* 
    +* 
    +* Function List:
    +* --------------
    +* 
    +* distanceFromCurve(point, curve)
    +* 
    +* 	Calculates the distance that the given point lies from the given Bezier.  Note that it is computed relative to the center of the Bezier,
    +* so if you have stroked the curve with a wide pen you may wish to take that into account!  The distance returned is relative to the values 
    +* of the curve and the point - it will most likely be pixels.
    +* 
    +* gradientAtPoint(curve, location)
    +* 
    +* 	Calculates the gradient to the curve at the given location, as a decimal between 0 and 1 inclusive.
    +*
    +* gradientAtPointAlongCurveFrom (curve, location)
    +*
    +*	Calculates the gradient at the point on the given curve that is 'distance' units from location. 
    +* 
    +* nearestPointOnCurve(point, curve) 
    +* 
    +*	Calculates the nearest point to the given point on the given curve.  The return value of this is a JS object literal, containing both the
    +*point's coordinates and also the 'location' of the point (see above), for example:  { point:{x:551,y:150}, location:0.263365 }.
    +* 
    +* pointOnCurve(curve, location)
    +* 
    +* 	Calculates the coordinates of the point on the given Bezier curve at the given location.  
    +* 		
    +* pointAlongCurveFrom(curve, location, distance)
    +* 
    +* 	Calculates the coordinates of the point on the given curve that is 'distance' units from location.  'distance' should be in the same coordinate
    +* space as that used to construct the Bezier curve.  For an HTML Canvas usage, for example, distance would be a measure of pixels.
    +* 
    +* perpendicularToCurveAt(curve, location, length, distance)
    +* 
    +* 	Calculates the perpendicular to the given curve at the given location.  length is the length of the line you wish for (it will be centered
    +* on the point at 'location'). distance is optional, and allows you to specify a point along the path from the given location as the center of
    +* the perpendicular returned.  The return value of this is an array of two points: [ {x:...,y:...}, {x:...,y:...} ].  
    +*  
    +* 
    +*/
    +
    +(function() {
    +	
    +	if(typeof Math.sgn == "undefined") {
    +		Math.sgn = function(x) { return x == 0 ? 0 : x > 0 ? 1 :-1; };
    +	}
    +	
    +	var Vectors = {
    +			subtract 	: 	function(v1, v2) { return {x:v1.x - v2.x, y:v1.y - v2.y }; },
    +			dotProduct	: 	function(v1, v2) { return (v1.x * v2.x)  + (v1.y * v2.y); },
    +			square		:	function(v) { return Math.sqrt((v.x * v.x) + (v.y * v.y)); },
    +			scale		:	function(v, s) { return {x:v.x * s, y:v.y * s }; }
    +		},
    +		
    +		maxRecursion = 64, 
    +		flatnessTolerance = Math.pow(2.0,-maxRecursion-1);
    +
    +	/**
    +	 * Calculates the distance that the point lies from the curve.
    +	 * 
    +	 * @param point a point in the form {x:567, y:3342}
    +	 * @param curve a Bezier curve in the form [{x:..., y:...}, {x:..., y:...}, {x:..., y:...}, {x:..., y:...}].  note that this is currently
    +	 * hardcoded to assume cubiz beziers, but would be better off supporting any degree. 
    +	 * @return a JS object literal containing location and distance, for example: {location:0.35, distance:10}.  Location is analogous to the location
    +	 * argument you pass to the pointOnPath function: it is a ratio of distance travelled along the curve.  Distance is the distance in pixels from
    +	 * the point to the curve. 
    +	 */
    +	var _distanceFromCurve = function(point, curve) {
    +		var candidates = [],     
    +	    	w = _convertToBezier(point, curve),
    +	    	degree = curve.length - 1, higherDegree = (2 * degree) - 1,
    +	    	numSolutions = _findRoots(w, higherDegree, candidates, 0),
    +			v = Vectors.subtract(point, curve[0]), dist = Vectors.square(v), t = 0.0;
    +
    +	    for (var i = 0; i < numSolutions; i++) {
    +			v = Vectors.subtract(point, _bezier(curve, degree, candidates[i], null, null));
    +	    	var newDist = Vectors.square(v);
    +	    	if (newDist < dist) {
    +	            dist = newDist;
    +	        	t = candidates[i];
    +		    }
    +	    }
    +	    v = Vectors.subtract(point, curve[degree]);
    +		newDist = Vectors.square(v);
    +	    if (newDist < dist) {
    +	        dist = newDist;
    +	    	t = 1.0;
    +	    }
    +		return {location:t, distance:dist};
    +	};
    +	/**
    +	 * finds the nearest point on the curve to the given point.
    +	 */
    +	var _nearestPointOnCurve = function(point, curve) {    
    +		var td = _distanceFromCurve(point, curve);
    +	    return {point:_bezier(curve, curve.length - 1, td.location, null, null), location:td.location};
    +	};
    +	var _convertToBezier = function(point, curve) {
    +		var degree = curve.length - 1, higherDegree = (2 * degree) - 1,
    +	    	c = [], d = [], cdTable = [], w = [],
    +	    	z = [ [1.0, 0.6, 0.3, 0.1], [0.4, 0.6, 0.6, 0.4], [0.1, 0.3, 0.6, 1.0] ];	
    +	    	
    +	    for (var i = 0; i <= degree; i++) c[i] = Vectors.subtract(curve[i], point);
    +	    for (var i = 0; i <= degree - 1; i++) { 
    +			d[i] = Vectors.subtract(curve[i+1], curve[i]);
    +			d[i] = Vectors.scale(d[i], 3.0);
    +	    }
    +	    for (var row = 0; row <= degree - 1; row++) {
    +			for (var column = 0; column <= degree; column++) {
    +				if (!cdTable[row]) cdTable[row] = [];
    +		    	cdTable[row][column] = Vectors.dotProduct(d[row], c[column]);
    +			}
    +	    }
    +	    for (i = 0; i <= higherDegree; i++) {
    +			if (!w[i]) w[i] = [];
    +			w[i].y = 0.0;
    +			w[i].x = parseFloat(i) / higherDegree;
    +	    }
    +	    var n = degree, m = degree-1;
    +	    for (var k = 0; k <= n + m; k++) {
    +			var lb = Math.max(0, k - m),
    +				ub = Math.min(k, n);
    +			for (i = lb; i <= ub; i++) {
    +		    	j = k - i;
    +		    	w[i+j].y += cdTable[j][i] * z[j][i];
    +			}
    +	    }
    +	    return w;
    +	};
    +	/**
    +	 * counts how many roots there are.
    +	 */
    +	var _findRoots = function(w, degree, t, depth) {  
    +	    var left = [], right = [],	
    +	    	left_count, right_count,	
    +	    	left_t = [], right_t = [];
    +	    	
    +	    switch (_getCrossingCount(w, degree)) {
    +	       	case 0 : {	
    +	       		return 0;	
    +	       	}
    +	       	case 1 : {	
    +	       		if (depth >= maxRecursion) {
    +	       			t[0] = (w[0].x + w[degree].x) / 2.0;
    +	       			return 1;
    +	       		}
    +	       		if (_isFlatEnough(w, degree)) {
    +	       			t[0] = _computeXIntercept(w, degree);
    +	       			return 1;
    +	       		}
    +	       		break;
    +	       	}
    +	    }
    +	    _bezier(w, degree, 0.5, left, right);
    +	    left_count  = _findRoots(left,  degree, left_t, depth+1);
    +	    right_count = _findRoots(right, degree, right_t, depth+1);
    +	    for (var i = 0; i < left_count; i++) t[i] = left_t[i];
    +	    for (var i = 0; i < right_count; i++) t[i+left_count] = right_t[i];    
    +		return (left_count+right_count);
    +	};
    +	var _getCrossingCount = function(curve, degree) {
    +	    var n_crossings = 0, sign, old_sign;		    	
    +	    sign = old_sign = Math.sgn(curve[0].y);
    +	    for (var i = 1; i <= degree; i++) {
    +			sign = Math.sgn(curve[i].y);
    +			if (sign != old_sign) n_crossings++;
    +			old_sign = sign;
    +	    }
    +	    return n_crossings;
    +	};
    +	var _isFlatEnough = function(curve, degree) {
    +	    var  error,
    +	    	intercept_1, intercept_2, left_intercept, right_intercept,
    +	    	a, b, c, det, dInv, a1, b1, c1, a2, b2, c2;
    +	    a = curve[0].y - curve[degree].y;
    +	    b = curve[degree].x - curve[0].x;
    +	    c = curve[0].x * curve[degree].y - curve[degree].x * curve[0].y;
    +	
    +	    var max_distance_above = max_distance_below = 0.0;
    +	    
    +	    for (var i = 1; i < degree; i++) {
    +	        var value = a * curve[i].x + b * curve[i].y + c;       
    +	        if (value > max_distance_above)
    +	            max_distance_above = value;
    +	        else if (value < max_distance_below)
    +	        	max_distance_below = value;
    +	    }
    +	    
    +	    a1 = 0.0; b1 = 1.0; c1 = 0.0; a2 = a; b2 = b;
    +	    c2 = c - max_distance_above;
    +	    det = a1 * b2 - a2 * b1;
    +	    dInv = 1.0/det;
    +	    intercept_1 = (b1 * c2 - b2 * c1) * dInv;
    +	    a2 = a; b2 = b; c2 = c - max_distance_below;
    +	    det = a1 * b2 - a2 * b1;
    +	    dInv = 1.0/det;
    +	    intercept_2 = (b1 * c2 - b2 * c1) * dInv;
    +	    left_intercept = Math.min(intercept_1, intercept_2);
    +	    right_intercept = Math.max(intercept_1, intercept_2);
    +	    error = right_intercept - left_intercept;
    +	    return (error < flatnessTolerance)? 1 : 0;
    +	};
    +	var _computeXIntercept = function(curve, degree) {
    +	    var XLK = 1.0, YLK = 0.0,
    +	    	XNM = curve[degree].x - curve[0].x, YNM = curve[degree].y - curve[0].y,
    +	    	XMK = curve[0].x - 0.0, YMK = curve[0].y - 0.0,
    +	    	det = XNM*YLK - YNM*XLK, detInv = 1.0/det,
    +	    	S = (XNM*YMK - YNM*XMK) * detInv; 
    +	    return 0.0 + XLK * S;
    +	};
    +	var _bezier = function(curve, degree, t, left, right) {
    +	    var temp = [[]];
    +	    for (var j =0; j <= degree; j++) temp[0][j] = curve[j];
    +	    for (var i = 1; i <= degree; i++) {	
    +			for (var j =0 ; j <= degree - i; j++) {
    +				if (!temp[i]) temp[i] = [];
    +				if (!temp[i][j]) temp[i][j] = {};
    +		    	temp[i][j].x = (1.0 - t) * temp[i-1][j].x + t * temp[i-1][j+1].x;
    +		    	temp[i][j].y = (1.0 - t) * temp[i-1][j].y + t * temp[i-1][j+1].y;
    +			}
    +	    }    
    +	    if (left != null) 
    +	    	for (j = 0; j <= degree; j++) left[j]  = temp[j][0];
    +	    if (right != null)
    +			for (j = 0; j <= degree; j++) right[j] = temp[degree-j][j];
    +	    
    +	    return (temp[degree][0]);
    +	};
    +	
    +	var _curveFunctionCache = {};
    +	var _getCurveFunctions = function(order) {
    +		var fns = _curveFunctionCache[order];
    +		if (!fns) {
    +			fns = [];			
    +			var f_term = function() { return function(t) { return Math.pow(t, order); }; },
    +				l_term = function() { return function(t) { return Math.pow((1-t), order); }; },
    +				c_term = function(c) { return function(t) { return c; }; },
    +				t_term = function() { return function(t) { return t; }; },
    +				one_minus_t_term = function() { return function(t) { return 1-t; }; },
    +				_termFunc = function(terms) {
    +					return function(t) {
    +						var p = 1;
    +						for (var i = 0; i < terms.length; i++) p = p * terms[i](t);
    +						return p;
    +					};
    +				};
    +			
    +			fns.push(new f_term());  // first is t to the power of the curve order		
    +			for (var i = 1; i < order; i++) {
    +				var terms = [new c_term(order)];
    +				for (var j = 0 ; j < (order - i); j++) terms.push(new t_term());
    +				for (var j = 0 ; j < i; j++) terms.push(new one_minus_t_term());
    +				fns.push(new _termFunc(terms));
    +			}
    +			fns.push(new l_term());  // last is (1-t) to the power of the curve order
    +		
    +			_curveFunctionCache[order] = fns;
    +		}
    +			
    +		return fns;
    +	};
    +	
    +	
    +	/**
    +	 * calculates a point on the curve, for a Bezier of arbitrary order.
    +	 * @param curve an array of control points, eg [{x:10,y:20}, {x:50,y:50}, {x:100,y:100}, {x:120,y:100}].  For a cubic bezier this should have four points.
    +	 * @param location a decimal indicating the distance along the curve the point should be located at.  this is the distance along the curve as it travels, taking the way it bends into account.  should be a number from 0 to 1, inclusive.
    +	 */
    +	var _pointOnPath = function(curve, location) {		
    +		var cc = _getCurveFunctions(curve.length - 1),
    +			_x = 0, _y = 0;
    +		for (var i = 0; i < curve.length ; i++) {
    +			_x = _x + (curve[i].x * cc[i](location));
    +			_y = _y + (curve[i].y * cc[i](location));
    +		}
    +		
    +		return {x:_x, y:_y};
    +	};	
    +	
    +	/**
    +	 * finds the point that is 'distance' along the path from 'location'.  this method returns both the x,y location of the point and also
    +	 * its 'location' (proportion of travel along the path); the method below - _pointAlongPathFrom - calls this method and just returns the
    +	 * point.
    +	 */
    +	var _pointAlongPath = function(curve, location, distance) {
    +		var _dist = function(p1,p2) { return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2)); },
    +			prev = _pointOnPath(curve, location), 
    +			tally = 0, 
    +			curLoc = location, 
    +			direction = distance > 0 ? 1 : -1, 
    +			cur = null;
    +		while (tally < Math.abs(distance)) {
    +			curLoc += (0.005 * direction);
    +			cur = _pointOnPath(curve, curLoc);
    +			tally += _dist(cur, prev);	
    +			prev = cur;
    +		}
    +		return {point:cur, location:curLoc};        	
    +	};
    +	
    +	/**
    +	 * finds the point that is 'distance' along the path from 'location'.  this method returns both the x,y location of the point and also
    +	 * its 'location' (proportion of travel along the path).
    +	 */
    +	var _pointAlongPathFrom = function(curve, location, distance) {
    +		return _pointAlongPath(curve, location, distance).point;
    +	};
    +	
    +	/**
    +	 * returns the gradient of the curve at the given location, which is a decimal between 0 and 1 inclusive.
    +	 * 
    +	 * thanks // http://bimixual.org/AnimationLibrary/beziertangents.html
    +	 */
    +	var _gradientAtPoint = function(curve, location) {
    +		var p1 = _pointOnPath(curve, location),	
    +			p2 = _pointOnPath(curve.slice(0, curve.length - 1), location),
    +			dy = p2.y - p1.y, dx = p2.x - p1.x;
    +		return dy == 0 ? Infinity : Math.atan(dy / dx);		
    +	};
    +	
    +	/**
    +	returns the gradient of the curve at the point which is 'distance' from the given location.
    +	if this point is greater than location 1, the gradient at location 1 is returned.
    +	if this point is less than location 0, the gradient at location 0 is returned.
    +	*/
    +	var _gradientAtPointAlongPathFrom = function(curve, location, distance) {
    +		var p = _pointAlongPath(curve, location, distance);
    +		if (p.location > 1) p.location = 1;
    +		if (p.location < 0) p.location = 0;		
    +		return _gradientAtPoint(curve, p.location);		
    +	};
    +
    +	/**
    +	 * calculates a line that is 'length' pixels long, perpendicular to, and centered on, the path at 'distance' pixels from the given location.
    +	 * if distance is not supplied, the perpendicular for the given location is computed (ie. we set distance to zero).
    +	 */
    +	var _perpendicularToPathAt = function(curve, location, length, distance) {
    +		distance = distance == null ? 0 : distance;
    +		var p = _pointAlongPath(curve, location, distance),
    +			m = _gradientAtPoint(curve, p.location),
    +			_theta2 = Math.atan(-1 / m),
    +			y =  length / 2 * Math.sin(_theta2),
    +			x =  length / 2 * Math.cos(_theta2);
    +		return [{x:p.point.x + x, y:p.point.y + y}, {x:p.point.x - x, y:p.point.y - y}];
    +	};
    +	
    +	var jsBezier = window.jsBezier = {
    +		distanceFromCurve : _distanceFromCurve,
    +		gradientAtPoint : _gradientAtPoint,
    +		gradientAtPointAlongCurveFrom : _gradientAtPointAlongPathFrom,
    +		nearestPointOnCurve : _nearestPointOnCurve,
    +		pointOnCurve : _pointOnPath,		
    +		pointAlongCurveFrom : _pointAlongPathFrom,
    +		perpendicularToCurveAt : _perpendicularToPathAt
    +	};
    +})();
    diff --git a/lib/jsBezier-0.4-min.js b/lib/jsBezier-0.4-min.js
    new file mode 100644
    index 000000000..7fa867d03
    --- /dev/null
    +++ b/lib/jsBezier-0.4-min.js
    @@ -0,0 +1,8 @@
    +(function(){"undefined"==typeof Math.sgn&&(Math.sgn=function(a){return 0==a?0:0<a?1:-1});var m={subtract:function(a,b){return{x:a.x-b.x,y:a.y-b.y}},dotProduct:function(a,b){return a.x*b.x+a.y*b.y},square:function(a){return Math.sqrt(a.x*a.x+a.y*a.y)},scale:function(a,b){return{x:a.x*b,y:a.y*b}}},w=Math.pow(2,-65),u=function(a,b){for(var f=[],d=b.length-1,h=2*d-1,g=[],c=[],k=[],i=[],l=[[1,0.6,0.3,0.1],[0.4,0.6,0.6,0.4],[0.1,0.3,0.6,1]],e=0;e<=d;e++)g[e]=m.subtract(b[e],a);for(e=0;e<=d-1;e++){c[e]=
    +m.subtract(b[e+1],b[e]);c[e]=m.scale(c[e],3)}for(e=0;e<=d-1;e++)for(var n=0;n<=d;n++){k[e]||(k[e]=[]);k[e][n]=m.dotProduct(c[e],g[n])}for(e=0;e<=h;e++){i[e]||(i[e]=[]);i[e].y=0;i[e].x=parseFloat(e)/h}h=d-1;for(g=0;g<=d+h;g++){e=Math.max(0,g-h);for(c=Math.min(g,d);e<=c;e++){j=g-e;i[e+j].y=i[e+j].y+k[j][e]*l[j][e]}}d=b.length-1;i=r(i,2*d-1,f,0);h=m.subtract(a,b[0]);k=m.square(h);for(e=l=0;e<i;e++){h=m.subtract(a,s(b,d,f[e],null,null));h=m.square(h);if(h<k){k=h;l=f[e]}}h=m.subtract(a,b[d]);h=m.square(h);
    +if(h<k){k=h;l=1}return{location:l,distance:k}},r=function(a,b,f,d){var h=[],g=[],c=[],k=[],i=0,l,e;e=Math.sgn(a[0].y);for(var n=1;n<=b;n++){l=Math.sgn(a[n].y);l!=e&&i++;e=l}switch(i){case 0:return 0;case 1:if(d>=64){f[0]=(a[0].x+a[b].x)/2;return 1}var o,i=a[0].y-a[b].y;e=a[b].x-a[0].x;n=a[0].x*a[b].y-a[b].x*a[0].y;l=max_distance_below=0;for(o=1;o<b;o++){var m=i*a[o].x+e*a[o].y+n;m>l?l=m:m<max_distance_below&&(max_distance_below=m)}o=e;l=(1*(n-l)-o*0)*(1/(0*o-i*1));o=e;e=n-max_distance_below;i=(1*
    +e-o*0)*(1/(0*o-i*1));e=Math.min(l,i);if(Math.max(l,i)-e<w){c=a[b].x-a[0].x;k=a[b].y-a[0].y;f[0]=0+1*(c*(a[0].y-0)-k*(a[0].x-0))*(1/(c*0-k*1));return 1}}s(a,b,0.5,h,g);a=r(h,b,c,d+1);b=r(g,b,k,d+1);for(d=0;d<a;d++)f[d]=c[d];for(d=0;d<b;d++)f[d+a]=k[d];return a+b},s=function(a,b,f,d,h){for(var g=[[]],c=0;c<=b;c++)g[0][c]=a[c];for(a=1;a<=b;a++)for(c=0;c<=b-a;c++){g[a]||(g[a]=[]);g[a][c]||(g[a][c]={});g[a][c].x=(1-f)*g[a-1][c].x+f*g[a-1][c+1].x;g[a][c].y=(1-f)*g[a-1][c].y+f*g[a-1][c+1].y}if(d!=null)for(c=
    +0;c<=b;c++)d[c]=g[c][0];if(h!=null)for(c=0;c<=b;c++)h[c]=g[b-c][c];return g[b][0]},v={},x=function(a){var b=v[a];if(!b){var b=[],f=function(a){return function(){return a}},d=function(){return function(a){return a}},h=function(){return function(a){return 1-a}},g=function(a){return function(b){for(var c=1,d=0;d<a.length;d++)c=c*a[d](b);return c}};b.push(new function(){return function(b){return Math.pow(b,a)}});for(var c=1;c<a;c++){for(var k=[new f(a)],i=0;i<a-c;i++)k.push(new d);for(i=0;i<c;i++)k.push(new h);
    +b.push(new g(k))}b.push(new function(){return function(b){return Math.pow(1-b,a)}});v[a]=b}return b},p=function(a,b){for(var f=x(a.length-1),d=0,h=0,g=0;g<a.length;g++){d=d+a[g].x*f[g](b);h=h+a[g].y*f[g](b)}return{x:d,y:h}},q=function(a,b,f){for(var d=p(a,b),h=0,g=f>0?1:-1,c=null;h<Math.abs(f);){b=b+0.005*g;c=p(a,b);h=h+Math.sqrt(Math.pow(c.x-d.x,2)+Math.pow(c.y-d.y,2));d=c}return{point:c,location:b}},t=function(a,b){var f=p(a,b),d=p(a.slice(0,a.length-1),b),h=d.y-f.y,f=d.x-f.x;return h==0?Infinity:
    +Math.atan(h/f)};window.jsBezier={distanceFromCurve:u,gradientAtPoint:t,gradientAtPointAlongCurveFrom:function(a,b,f){b=q(a,b,f);if(b.location>1)b.location=1;if(b.location<0)b.location=0;return t(a,b.location)},nearestPointOnCurve:function(a,b){var f=u(a,b);return{point:s(b,b.length-1,f.location,null,null),location:f.location}},pointOnCurve:p,pointAlongCurveFrom:function(a,b,f){return q(a,b,f).point},perpendicularToCurveAt:function(a,b,f,d){b=q(a,b,d==null?0:d);a=t(a,b.location);d=Math.atan(-1/a);
    +a=f/2*Math.sin(d);f=f/2*Math.cos(d);return[{x:b.point.x+f,y:b.point.y+a},{x:b.point.x-f,y:b.point.y-a}]},locationAlongCurveFrom:function(a,b,f){return q(a,b,f).location}}})();
    \ No newline at end of file
    diff --git a/lib/jsBezier-0.4.js b/lib/jsBezier-0.4.js
    new file mode 100644
    index 000000000..f958ed4e1
    --- /dev/null
    +++ b/lib/jsBezier-0.4.js
    @@ -0,0 +1,387 @@
    +/**
    +* jsBezier-0.3
    +*
    +* Copyright (c) 2010 - 2011 Simon Porritt (simon.porritt@gmail.com)
    +*
    +* licensed under the MIT license.
    +* 
    +* a set of Bezier curve functions that deal with Beziers, used by jsPlumb, and perhaps useful for other people.  These functions work with Bezier
    +* curves of arbitrary degree.
    +*
    +* - functions are all in the 'jsBezier' namespace.  
    +* 
    +* - all input points should be in the format {x:.., y:..}. all output points are in this format too.
    +* 
    +* - all input curves should be in the format [ {x:.., y:..}, {x:.., y:..}, {x:.., y:..}, {x:.., y:..} ]
    +* 
    +* - 'location' as used as an input here refers to a decimal in the range 0-1 inclusive, which indicates a point some proportion along the length
    +* of the curve.  location as output has the same format and meaning.
    +* 
    +* 
    +* Function List:
    +* --------------
    +* 
    +* distanceFromCurve(point, curve)
    +* 
    +* 	Calculates the distance that the given point lies from the given Bezier.  Note that it is computed relative to the center of the Bezier,
    +* so if you have stroked the curve with a wide pen you may wish to take that into account!  The distance returned is relative to the values 
    +* of the curve and the point - it will most likely be pixels.
    +* 
    +* gradientAtPoint(curve, location)
    +* 
    +* 	Calculates the gradient to the curve at the given location, as a decimal between 0 and 1 inclusive.
    +*
    +* gradientAtPointAlongCurveFrom (curve, location)
    +*
    +*	Calculates the gradient at the point on the given curve that is 'distance' units from location. 
    +* 
    +* nearestPointOnCurve(point, curve) 
    +* 
    +*	Calculates the nearest point to the given point on the given curve.  The return value of this is a JS object literal, containing both the
    +*point's coordinates and also the 'location' of the point (see above), for example:  { point:{x:551,y:150}, location:0.263365 }.
    +* 
    +* pointOnCurve(curve, location)
    +* 
    +* 	Calculates the coordinates of the point on the given Bezier curve at the given location.  
    +* 		
    +* pointAlongCurveFrom(curve, location, distance)
    +* 
    +* 	Calculates the coordinates of the point on the given curve that is 'distance' units from location.  'distance' should be in the same coordinate
    +* space as that used to construct the Bezier curve.  For an HTML Canvas usage, for example, distance would be a measure of pixels.
    +*
    +* locationAlongCurveFrom(curve, location, distance)
    +* 
    +* 	Calculates the location on the given curve that is 'distance' units from location.  'distance' should be in the same coordinate
    +* space as that used to construct the Bezier curve.  For an HTML Canvas usage, for example, distance would be a measure of pixels.
    +* 
    +* perpendicularToCurveAt(curve, location, length, distance)
    +* 
    +* 	Calculates the perpendicular to the given curve at the given location.  length is the length of the line you wish for (it will be centered
    +* on the point at 'location'). distance is optional, and allows you to specify a point along the path from the given location as the center of
    +* the perpendicular returned.  The return value of this is an array of two points: [ {x:...,y:...}, {x:...,y:...} ].  
    +*  
    +* 
    +*/
    +
    +(function() {
    +	
    +	if(typeof Math.sgn == "undefined") {
    +		Math.sgn = function(x) { return x == 0 ? 0 : x > 0 ? 1 :-1; };
    +	}
    +	
    +	var Vectors = {
    +			subtract 	: 	function(v1, v2) { return {x:v1.x - v2.x, y:v1.y - v2.y }; },
    +			dotProduct	: 	function(v1, v2) { return (v1.x * v2.x)  + (v1.y * v2.y); },
    +			square		:	function(v) { return Math.sqrt((v.x * v.x) + (v.y * v.y)); },
    +			scale		:	function(v, s) { return {x:v.x * s, y:v.y * s }; }
    +		},
    +		
    +		maxRecursion = 64, 
    +		flatnessTolerance = Math.pow(2.0,-maxRecursion-1);
    +
    +	/**
    +	 * Calculates the distance that the point lies from the curve.
    +	 * 
    +	 * @param point a point in the form {x:567, y:3342}
    +	 * @param curve a Bezier curve in the form [{x:..., y:...}, {x:..., y:...}, {x:..., y:...}, {x:..., y:...}].  note that this is currently
    +	 * hardcoded to assume cubiz beziers, but would be better off supporting any degree. 
    +	 * @return a JS object literal containing location and distance, for example: {location:0.35, distance:10}.  Location is analogous to the location
    +	 * argument you pass to the pointOnPath function: it is a ratio of distance travelled along the curve.  Distance is the distance in pixels from
    +	 * the point to the curve. 
    +	 */
    +	var _distanceFromCurve = function(point, curve) {
    +		var candidates = [],     
    +	    	w = _convertToBezier(point, curve),
    +	    	degree = curve.length - 1, higherDegree = (2 * degree) - 1,
    +	    	numSolutions = _findRoots(w, higherDegree, candidates, 0),
    +			v = Vectors.subtract(point, curve[0]), dist = Vectors.square(v), t = 0.0;
    +
    +	    for (var i = 0; i < numSolutions; i++) {
    +			v = Vectors.subtract(point, _bezier(curve, degree, candidates[i], null, null));
    +	    	var newDist = Vectors.square(v);
    +	    	if (newDist < dist) {
    +	            dist = newDist;
    +	        	t = candidates[i];
    +		    }
    +	    }
    +	    v = Vectors.subtract(point, curve[degree]);
    +		newDist = Vectors.square(v);
    +	    if (newDist < dist) {
    +	        dist = newDist;
    +	    	t = 1.0;
    +	    }
    +		return {location:t, distance:dist};
    +	};
    +	/**
    +	 * finds the nearest point on the curve to the given point.
    +	 */
    +	var _nearestPointOnCurve = function(point, curve) {    
    +		var td = _distanceFromCurve(point, curve);
    +	    return {point:_bezier(curve, curve.length - 1, td.location, null, null), location:td.location};
    +	};
    +	var _convertToBezier = function(point, curve) {
    +		var degree = curve.length - 1, higherDegree = (2 * degree) - 1,
    +	    	c = [], d = [], cdTable = [], w = [],
    +	    	z = [ [1.0, 0.6, 0.3, 0.1], [0.4, 0.6, 0.6, 0.4], [0.1, 0.3, 0.6, 1.0] ];	
    +	    	
    +	    for (var i = 0; i <= degree; i++) c[i] = Vectors.subtract(curve[i], point);
    +	    for (var i = 0; i <= degree - 1; i++) { 
    +			d[i] = Vectors.subtract(curve[i+1], curve[i]);
    +			d[i] = Vectors.scale(d[i], 3.0);
    +	    }
    +	    for (var row = 0; row <= degree - 1; row++) {
    +			for (var column = 0; column <= degree; column++) {
    +				if (!cdTable[row]) cdTable[row] = [];
    +		    	cdTable[row][column] = Vectors.dotProduct(d[row], c[column]);
    +			}
    +	    }
    +	    for (i = 0; i <= higherDegree; i++) {
    +			if (!w[i]) w[i] = [];
    +			w[i].y = 0.0;
    +			w[i].x = parseFloat(i) / higherDegree;
    +	    }
    +	    var n = degree, m = degree-1;
    +	    for (var k = 0; k <= n + m; k++) {
    +			var lb = Math.max(0, k - m),
    +				ub = Math.min(k, n);
    +			for (i = lb; i <= ub; i++) {
    +		    	j = k - i;
    +		    	w[i+j].y += cdTable[j][i] * z[j][i];
    +			}
    +	    }
    +	    return w;
    +	};
    +	/**
    +	 * counts how many roots there are.
    +	 */
    +	var _findRoots = function(w, degree, t, depth) {  
    +	    var left = [], right = [],	
    +	    	left_count, right_count,	
    +	    	left_t = [], right_t = [];
    +	    	
    +	    switch (_getCrossingCount(w, degree)) {
    +	       	case 0 : {	
    +	       		return 0;	
    +	       	}
    +	       	case 1 : {	
    +	       		if (depth >= maxRecursion) {
    +	       			t[0] = (w[0].x + w[degree].x) / 2.0;
    +	       			return 1;
    +	       		}
    +	       		if (_isFlatEnough(w, degree)) {
    +	       			t[0] = _computeXIntercept(w, degree);
    +	       			return 1;
    +	       		}
    +	       		break;
    +	       	}
    +	    }
    +	    _bezier(w, degree, 0.5, left, right);
    +	    left_count  = _findRoots(left,  degree, left_t, depth+1);
    +	    right_count = _findRoots(right, degree, right_t, depth+1);
    +	    for (var i = 0; i < left_count; i++) t[i] = left_t[i];
    +	    for (var i = 0; i < right_count; i++) t[i+left_count] = right_t[i];    
    +		return (left_count+right_count);
    +	};
    +	var _getCrossingCount = function(curve, degree) {
    +	    var n_crossings = 0, sign, old_sign;		    	
    +	    sign = old_sign = Math.sgn(curve[0].y);
    +	    for (var i = 1; i <= degree; i++) {
    +			sign = Math.sgn(curve[i].y);
    +			if (sign != old_sign) n_crossings++;
    +			old_sign = sign;
    +	    }
    +	    return n_crossings;
    +	};
    +	var _isFlatEnough = function(curve, degree) {
    +	    var  error,
    +	    	intercept_1, intercept_2, left_intercept, right_intercept,
    +	    	a, b, c, det, dInv, a1, b1, c1, a2, b2, c2;
    +	    a = curve[0].y - curve[degree].y;
    +	    b = curve[degree].x - curve[0].x;
    +	    c = curve[0].x * curve[degree].y - curve[degree].x * curve[0].y;
    +	
    +	    var max_distance_above = max_distance_below = 0.0;
    +	    
    +	    for (var i = 1; i < degree; i++) {
    +	        var value = a * curve[i].x + b * curve[i].y + c;       
    +	        if (value > max_distance_above)
    +	            max_distance_above = value;
    +	        else if (value < max_distance_below)
    +	        	max_distance_below = value;
    +	    }
    +	    
    +	    a1 = 0.0; b1 = 1.0; c1 = 0.0; a2 = a; b2 = b;
    +	    c2 = c - max_distance_above;
    +	    det = a1 * b2 - a2 * b1;
    +	    dInv = 1.0/det;
    +	    intercept_1 = (b1 * c2 - b2 * c1) * dInv;
    +	    a2 = a; b2 = b; c2 = c - max_distance_below;
    +	    det = a1 * b2 - a2 * b1;
    +	    dInv = 1.0/det;
    +	    intercept_2 = (b1 * c2 - b2 * c1) * dInv;
    +	    left_intercept = Math.min(intercept_1, intercept_2);
    +	    right_intercept = Math.max(intercept_1, intercept_2);
    +	    error = right_intercept - left_intercept;
    +	    return (error < flatnessTolerance)? 1 : 0;
    +	};
    +	var _computeXIntercept = function(curve, degree) {
    +	    var XLK = 1.0, YLK = 0.0,
    +	    	XNM = curve[degree].x - curve[0].x, YNM = curve[degree].y - curve[0].y,
    +	    	XMK = curve[0].x - 0.0, YMK = curve[0].y - 0.0,
    +	    	det = XNM*YLK - YNM*XLK, detInv = 1.0/det,
    +	    	S = (XNM*YMK - YNM*XMK) * detInv; 
    +	    return 0.0 + XLK * S;
    +	};
    +	var _bezier = function(curve, degree, t, left, right) {
    +	    var temp = [[]];
    +	    for (var j =0; j <= degree; j++) temp[0][j] = curve[j];
    +	    for (var i = 1; i <= degree; i++) {	
    +			for (var j =0 ; j <= degree - i; j++) {
    +				if (!temp[i]) temp[i] = [];
    +				if (!temp[i][j]) temp[i][j] = {};
    +		    	temp[i][j].x = (1.0 - t) * temp[i-1][j].x + t * temp[i-1][j+1].x;
    +		    	temp[i][j].y = (1.0 - t) * temp[i-1][j].y + t * temp[i-1][j+1].y;
    +			}
    +	    }    
    +	    if (left != null) 
    +	    	for (j = 0; j <= degree; j++) left[j]  = temp[j][0];
    +	    if (right != null)
    +			for (j = 0; j <= degree; j++) right[j] = temp[degree-j][j];
    +	    
    +	    return (temp[degree][0]);
    +	};
    +	
    +	var _curveFunctionCache = {};
    +	var _getCurveFunctions = function(order) {
    +		var fns = _curveFunctionCache[order];
    +		if (!fns) {
    +			fns = [];			
    +			var f_term = function() { return function(t) { return Math.pow(t, order); }; },
    +				l_term = function() { return function(t) { return Math.pow((1-t), order); }; },
    +				c_term = function(c) { return function(t) { return c; }; },
    +				t_term = function() { return function(t) { return t; }; },
    +				one_minus_t_term = function() { return function(t) { return 1-t; }; },
    +				_termFunc = function(terms) {
    +					return function(t) {
    +						var p = 1;
    +						for (var i = 0; i < terms.length; i++) p = p * terms[i](t);
    +						return p;
    +					};
    +				};
    +			
    +			fns.push(new f_term());  // first is t to the power of the curve order		
    +			for (var i = 1; i < order; i++) {
    +				var terms = [new c_term(order)];
    +				for (var j = 0 ; j < (order - i); j++) terms.push(new t_term());
    +				for (var j = 0 ; j < i; j++) terms.push(new one_minus_t_term());
    +				fns.push(new _termFunc(terms));
    +			}
    +			fns.push(new l_term());  // last is (1-t) to the power of the curve order
    +		
    +			_curveFunctionCache[order] = fns;
    +		}
    +			
    +		return fns;
    +	};
    +	
    +	
    +	/**
    +	 * calculates a point on the curve, for a Bezier of arbitrary order.
    +	 * @param curve an array of control points, eg [{x:10,y:20}, {x:50,y:50}, {x:100,y:100}, {x:120,y:100}].  For a cubic bezier this should have four points.
    +	 * @param location a decimal indicating the distance along the curve the point should be located at.  this is the distance along the curve as it travels, taking the way it bends into account.  should be a number from 0 to 1, inclusive.
    +	 */
    +	var _pointOnPath = function(curve, location) {		
    +		var cc = _getCurveFunctions(curve.length - 1),
    +			_x = 0, _y = 0;
    +		for (var i = 0; i < curve.length ; i++) {
    +			_x = _x + (curve[i].x * cc[i](location));
    +			_y = _y + (curve[i].y * cc[i](location));
    +		}
    +		
    +		return {x:_x, y:_y};
    +	};	
    +	
    +	/**
    +	 * finds the point that is 'distance' along the path from 'location'.  this method returns both the x,y location of the point and also
    +	 * its 'location' (proportion of travel along the path); the method below - _pointAlongPathFrom - calls this method and just returns the
    +	 * point.
    +	 */
    +	var _pointAlongPath = function(curve, location, distance) {
    +		var _dist = function(p1,p2) { return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2)); },
    +			prev = _pointOnPath(curve, location), 
    +			tally = 0, 
    +			curLoc = location, 
    +			direction = distance > 0 ? 1 : -1, 
    +			cur = null;
    +		while (tally < Math.abs(distance)) {
    +			curLoc += (0.005 * direction);
    +			cur = _pointOnPath(curve, curLoc);
    +			tally += _dist(cur, prev);	
    +			prev = cur;
    +		}
    +		return {point:cur, location:curLoc};        	
    +	};
    +	
    +	/**
    +	 * finds the point that is 'distance' along the path from 'location'.  
    +	 */
    +	var _pointAlongPathFrom = function(curve, location, distance) {
    +		return _pointAlongPath(curve, location, distance).point;
    +	};
    +
    +	/**
    +	 * finds the location that is 'distance' along the path from 'location'.  
    +	 */
    +	var _locationAlongPathFrom = function(curve, location, distance) {
    +		return _pointAlongPath(curve, location, distance).location;
    +	};
    +	
    +	/**
    +	 * returns the gradient of the curve at the given location, which is a decimal between 0 and 1 inclusive.
    +	 * 
    +	 * thanks // http://bimixual.org/AnimationLibrary/beziertangents.html
    +	 */
    +	var _gradientAtPoint = function(curve, location) {
    +		var p1 = _pointOnPath(curve, location),	
    +			p2 = _pointOnPath(curve.slice(0, curve.length - 1), location),
    +			dy = p2.y - p1.y, dx = p2.x - p1.x;
    +		return dy == 0 ? Infinity : Math.atan(dy / dx);		
    +	};
    +	
    +	/**
    +	returns the gradient of the curve at the point which is 'distance' from the given location.
    +	if this point is greater than location 1, the gradient at location 1 is returned.
    +	if this point is less than location 0, the gradient at location 0 is returned.
    +	*/
    +	var _gradientAtPointAlongPathFrom = function(curve, location, distance) {
    +		var p = _pointAlongPath(curve, location, distance);
    +		if (p.location > 1) p.location = 1;
    +		if (p.location < 0) p.location = 0;		
    +		return _gradientAtPoint(curve, p.location);		
    +	};
    +
    +	/**
    +	 * calculates a line that is 'length' pixels long, perpendicular to, and centered on, the path at 'distance' pixels from the given location.
    +	 * if distance is not supplied, the perpendicular for the given location is computed (ie. we set distance to zero).
    +	 */
    +	var _perpendicularToPathAt = function(curve, location, length, distance) {
    +		distance = distance == null ? 0 : distance;
    +		var p = _pointAlongPath(curve, location, distance),
    +			m = _gradientAtPoint(curve, p.location),
    +			_theta2 = Math.atan(-1 / m),
    +			y =  length / 2 * Math.sin(_theta2),
    +			x =  length / 2 * Math.cos(_theta2);
    +		return [{x:p.point.x + x, y:p.point.y + y}, {x:p.point.x - x, y:p.point.y - y}];
    +	};
    +	
    +	var jsBezier = window.jsBezier = {
    +		distanceFromCurve : _distanceFromCurve,
    +		gradientAtPoint : _gradientAtPoint,
    +		gradientAtPointAlongCurveFrom : _gradientAtPointAlongPathFrom,
    +		nearestPointOnCurve : _nearestPointOnCurve,
    +		pointOnCurve : _pointOnPath,		
    +		pointAlongCurveFrom : _pointAlongPathFrom,
    +		perpendicularToCurveAt : _perpendicularToPathAt,
    +		locationAlongCurveFrom:_locationAlongPathFrom
    +	};
    +})();
    diff --git a/lib/jsBezier-0.5-min.js b/lib/jsBezier-0.5-min.js
    new file mode 100644
    index 000000000..51ac62f08
    --- /dev/null
    +++ b/lib/jsBezier-0.5-min.js
    @@ -0,0 +1,8 @@
    +(function(){"undefined"==typeof Math.sgn&&(Math.sgn=function(a){return 0==a?0:0<a?1:-1});var p={subtract:function(a,b){return{x:a.x-b.x,y:a.y-b.y}},dotProduct:function(a,b){return a.x*b.x+a.y*b.y},square:function(a){return Math.sqrt(a.x*a.x+a.y*a.y)},scale:function(a,b){return{x:a.x*b,y:a.y*b}}},z=Math.pow(2,-65),w=function(a,b){for(var f=[],d=b.length-1,g=2*d-1,h=[],e=[],l=[],i=[],k=[[1,0.6,0.3,0.1],[0.4,0.6,0.6,0.4],[0.1,0.3,0.6,1]],c=0;c<=d;c++)h[c]=p.subtract(b[c],a);for(c=0;c<=d-1;c++)e[c]=p.subtract(b[c+
    +1],b[c]),e[c]=p.scale(e[c],3);for(c=0;c<=d-1;c++)for(var m=0;m<=d;m++)l[c]||(l[c]=[]),l[c][m]=p.dotProduct(e[c],h[m]);for(c=0;c<=g;c++)i[c]||(i[c]=[]),i[c].y=0,i[c].x=parseFloat(c)/g;g=d-1;for(h=0;h<=d+g;h++){c=Math.max(0,h-g);for(e=Math.min(h,d);c<=e;c++)j=h-c,i[c+j].y+=l[j][c]*k[j][c]}d=b.length-1;i=t(i,2*d-1,f,0);g=p.subtract(a,b[0]);l=p.square(g);for(c=k=0;c<i;c++)g=p.subtract(a,u(b,d,f[c],null,null)),g=p.square(g),g<l&&(l=g,k=f[c]);g=p.subtract(a,b[d]);g=p.square(g);g<l&&(l=g,k=1);return{location:k,
    +distance:l}},t=function(a,b,f,d){var g=[],h=[],e=[],l=[],i=0,k,c;c=Math.sgn(a[0].y);for(var m=1;m<=b;m++)k=Math.sgn(a[m].y),k!=c&&i++,c=k;switch(i){case 0:return 0;case 1:if(64<=d)return f[0]=(a[0].x+a[b].x)/2,1;var q,n,i=a[0].y-a[b].y;c=a[b].x-a[0].x;m=a[0].x*a[b].y-a[b].x*a[0].y;k=max_distance_below=0;for(q=1;q<b;q++)n=i*a[q].x+c*a[q].y+m,n>k?k=n:n<max_distance_below&&(max_distance_below=n);n=c;q=0*n-1*i;k=(1*(m-k)-0*n)*(1/q);n=c;c=m-max_distance_below;q=0*n-1*i;i=(1*c-0*n)*(1/q);c=Math.min(k,i);
    +if(Math.max(k,i)-c<z)return e=a[b].x-a[0].x,l=a[b].y-a[0].y,f[0]=0+1*(e*(a[0].y-0)-l*(a[0].x-0))*(1/(0*e-1*l)),1}u(a,b,0.5,g,h);a=t(g,b,e,d+1);b=t(h,b,l,d+1);for(d=0;d<a;d++)f[d]=e[d];for(d=0;d<b;d++)f[d+a]=l[d];return a+b},u=function(a,b,f,d,g){for(var h=[[]],e=0;e<=b;e++)h[0][e]=a[e];for(a=1;a<=b;a++)for(e=0;e<=b-a;e++)h[a]||(h[a]=[]),h[a][e]||(h[a][e]={}),h[a][e].x=(1-f)*h[a-1][e].x+f*h[a-1][e+1].x,h[a][e].y=(1-f)*h[a-1][e].y+f*h[a-1][e+1].y;if(null!=d)for(e=0;e<=b;e++)d[e]=h[e][0];if(null!=g)for(e=
    +0;e<=b;e++)g[e]=h[b-e][e];return h[b][0]},x={},r=function(a,b){var f,d=a.length-1;f=x[d];if(!f){f=[];var g=function(a){return function(){return a}},h=function(){return function(a){return a}},e=function(){return function(a){return 1-a}},l=function(a){return function(b){for(var c=1,d=0;d<a.length;d++)c*=a[d](b);return c}};f.push(new function(){return function(a){return Math.pow(a,d)}});for(var i=1;i<d;i++){for(var k=[new g(d)],c=0;c<d-i;c++)k.push(new h);for(c=0;c<i;c++)k.push(new e);f.push(new l(k))}f.push(new function(){return function(a){return Math.pow(1-
    +a,d)}});x[d]=f}for(e=h=g=0;e<a.length;e++)g+=a[e].x*f[e](b),h+=a[e].y*f[e](b);return{x:g,y:h}},y=function(a,b){return Math.sqrt(Math.pow(a.x-b.x,2)+Math.pow(a.y-b.y,2))},s=function(a,b,f){for(var d=r(a,b),g=0,h=0<f?1:-1,e=null;g<Math.abs(f);)b+=0.005*h,e=r(a,b),g+=y(e,d),d=e;return{point:e,location:b}},v=function(a,b){var f=r(a,b),d=r(a.slice(0,a.length-1),b),g=d.y-f.y,f=d.x-f.x;return 0==g?Infinity:Math.atan(g/f)};window.jsBezier={distanceFromCurve:w,gradientAtPoint:v,gradientAtPointAlongCurveFrom:function(a,
    +b,f){b=s(a,b,f);1<b.location&&(b.location=1);0>b.location&&(b.location=0);return v(a,b.location)},nearestPointOnCurve:function(a,b){var f=w(a,b);return{point:u(b,b.length-1,f.location,null,null),location:f.location}},pointOnCurve:r,pointAlongCurveFrom:function(a,b,f){return s(a,b,f).point},perpendicularToCurveAt:function(a,b,f,d){b=s(a,b,null==d?0:d);a=v(a,b.location);d=Math.atan(-1/a);a=f/2*Math.sin(d);f=f/2*Math.cos(d);return[{x:b.point.x+f,y:b.point.y+a},{x:b.point.x-f,y:b.point.y-a}]},locationAlongCurveFrom:function(a,
    +b,f){return s(a,b,f).location},getLength:function(a){for(var b=r(a,0),f=0,d=0,g=null;1>d;)d+=0.005,g=r(a,d),f+=y(g,b),b=g;return f}}})();
    \ No newline at end of file
    diff --git a/lib/jsBezier-0.5.js b/lib/jsBezier-0.5.js
    new file mode 100644
    index 000000000..f738a1cda
    --- /dev/null
    +++ b/lib/jsBezier-0.5.js
    @@ -0,0 +1,408 @@
    +/**
    +* jsBezier-0.5
    +*
    +* Copyright (c) 2010 - 2011 Simon Porritt (simon.porritt@gmail.com)
    +*
    +* licensed under the MIT license.
    +* 
    +* a set of Bezier curve functions that deal with Beziers, used by jsPlumb, and perhaps useful for other people.  These functions work with Bezier
    +* curves of arbitrary degree.
    +*
    +* - functions are all in the 'jsBezier' namespace.  
    +* 
    +* - all input points should be in the format {x:.., y:..}. all output points are in this format too.
    +* 
    +* - all input curves should be in the format [ {x:.., y:..}, {x:.., y:..}, {x:.., y:..}, {x:.., y:..} ]
    +* 
    +* - 'location' as used as an input here refers to a decimal in the range 0-1 inclusive, which indicates a point some proportion along the length
    +* of the curve.  location as output has the same format and meaning.
    +* 
    +* 
    +* Function List:
    +* --------------
    +* 
    +* distanceFromCurve(point, curve)
    +* 
    +* 	Calculates the distance that the given point lies from the given Bezier.  Note that it is computed relative to the center of the Bezier,
    +* so if you have stroked the curve with a wide pen you may wish to take that into account!  The distance returned is relative to the values 
    +* of the curve and the point - it will most likely be pixels.
    +* 
    +* gradientAtPoint(curve, location)
    +* 
    +* 	Calculates the gradient to the curve at the given location, as a decimal between 0 and 1 inclusive.
    +*
    +* gradientAtPointAlongCurveFrom (curve, location)
    +*
    +*	Calculates the gradient at the point on the given curve that is 'distance' units from location. 
    +* 
    +* nearestPointOnCurve(point, curve) 
    +* 
    +*	Calculates the nearest point to the given point on the given curve.  The return value of this is a JS object literal, containing both the
    +*point's coordinates and also the 'location' of the point (see above), for example:  { point:{x:551,y:150}, location:0.263365 }.
    +* 
    +* pointOnCurve(curve, location)
    +* 
    +* 	Calculates the coordinates of the point on the given Bezier curve at the given location.  
    +* 		
    +* pointAlongCurveFrom(curve, location, distance)
    +* 
    +* 	Calculates the coordinates of the point on the given curve that is 'distance' units from location.  'distance' should be in the same coordinate
    +* space as that used to construct the Bezier curve.  For an HTML Canvas usage, for example, distance would be a measure of pixels.
    +*
    +* locationAlongCurveFrom(curve, location, distance)
    +* 
    +* 	Calculates the location on the given curve that is 'distance' units from location.  'distance' should be in the same coordinate
    +* space as that used to construct the Bezier curve.  For an HTML Canvas usage, for example, distance would be a measure of pixels.
    +* 
    +* perpendicularToCurveAt(curve, location, length, distance)
    +* 
    +* 	Calculates the perpendicular to the given curve at the given location.  length is the length of the line you wish for (it will be centered
    +* on the point at 'location'). distance is optional, and allows you to specify a point along the path from the given location as the center of
    +* the perpendicular returned.  The return value of this is an array of two points: [ {x:...,y:...}, {x:...,y:...} ].  
    +*  
    +* 
    +*/
    +
    +(function() {
    +	
    +	if(typeof Math.sgn == "undefined") {
    +		Math.sgn = function(x) { return x == 0 ? 0 : x > 0 ? 1 :-1; };
    +	}
    +	
    +	var Vectors = {
    +			subtract 	: 	function(v1, v2) { return {x:v1.x - v2.x, y:v1.y - v2.y }; },
    +			dotProduct	: 	function(v1, v2) { return (v1.x * v2.x)  + (v1.y * v2.y); },
    +			square		:	function(v) { return Math.sqrt((v.x * v.x) + (v.y * v.y)); },
    +			scale		:	function(v, s) { return {x:v.x * s, y:v.y * s }; }
    +		},
    +		
    +		maxRecursion = 64, 
    +		flatnessTolerance = Math.pow(2.0,-maxRecursion-1);
    +
    +	/**
    +	 * Calculates the distance that the point lies from the curve.
    +	 * 
    +	 * @param point a point in the form {x:567, y:3342}
    +	 * @param curve a Bezier curve in the form [{x:..., y:...}, {x:..., y:...}, {x:..., y:...}, {x:..., y:...}].  note that this is currently
    +	 * hardcoded to assume cubiz beziers, but would be better off supporting any degree. 
    +	 * @return a JS object literal containing location and distance, for example: {location:0.35, distance:10}.  Location is analogous to the location
    +	 * argument you pass to the pointOnPath function: it is a ratio of distance travelled along the curve.  Distance is the distance in pixels from
    +	 * the point to the curve. 
    +	 */
    +	var _distanceFromCurve = function(point, curve) {
    +		var candidates = [],     
    +	    	w = _convertToBezier(point, curve),
    +	    	degree = curve.length - 1, higherDegree = (2 * degree) - 1,
    +	    	numSolutions = _findRoots(w, higherDegree, candidates, 0),
    +			v = Vectors.subtract(point, curve[0]), dist = Vectors.square(v), t = 0.0;
    +
    +	    for (var i = 0; i < numSolutions; i++) {
    +			v = Vectors.subtract(point, _bezier(curve, degree, candidates[i], null, null));
    +	    	var newDist = Vectors.square(v);
    +	    	if (newDist < dist) {
    +	            dist = newDist;
    +	        	t = candidates[i];
    +		    }
    +	    }
    +	    v = Vectors.subtract(point, curve[degree]);
    +		newDist = Vectors.square(v);
    +	    if (newDist < dist) {
    +	        dist = newDist;
    +	    	t = 1.0;
    +	    }
    +		return {location:t, distance:dist};
    +	};
    +	/**
    +	 * finds the nearest point on the curve to the given point.
    +	 */
    +	var _nearestPointOnCurve = function(point, curve) {    
    +		var td = _distanceFromCurve(point, curve);
    +	    return {point:_bezier(curve, curve.length - 1, td.location, null, null), location:td.location};
    +	};
    +	var _convertToBezier = function(point, curve) {
    +		var degree = curve.length - 1, higherDegree = (2 * degree) - 1,
    +	    	c = [], d = [], cdTable = [], w = [],
    +	    	z = [ [1.0, 0.6, 0.3, 0.1], [0.4, 0.6, 0.6, 0.4], [0.1, 0.3, 0.6, 1.0] ];	
    +	    	
    +	    for (var i = 0; i <= degree; i++) c[i] = Vectors.subtract(curve[i], point);
    +	    for (var i = 0; i <= degree - 1; i++) { 
    +			d[i] = Vectors.subtract(curve[i+1], curve[i]);
    +			d[i] = Vectors.scale(d[i], 3.0);
    +	    }
    +	    for (var row = 0; row <= degree - 1; row++) {
    +			for (var column = 0; column <= degree; column++) {
    +				if (!cdTable[row]) cdTable[row] = [];
    +		    	cdTable[row][column] = Vectors.dotProduct(d[row], c[column]);
    +			}
    +	    }
    +	    for (i = 0; i <= higherDegree; i++) {
    +			if (!w[i]) w[i] = [];
    +			w[i].y = 0.0;
    +			w[i].x = parseFloat(i) / higherDegree;
    +	    }
    +	    var n = degree, m = degree-1;
    +	    for (var k = 0; k <= n + m; k++) {
    +			var lb = Math.max(0, k - m),
    +				ub = Math.min(k, n);
    +			for (i = lb; i <= ub; i++) {
    +		    	j = k - i;
    +		    	w[i+j].y += cdTable[j][i] * z[j][i];
    +			}
    +	    }
    +	    return w;
    +	};
    +	/**
    +	 * counts how many roots there are.
    +	 */
    +	var _findRoots = function(w, degree, t, depth) {  
    +	    var left = [], right = [],	
    +	    	left_count, right_count,	
    +	    	left_t = [], right_t = [];
    +	    	
    +	    switch (_getCrossingCount(w, degree)) {
    +	       	case 0 : {	
    +	       		return 0;	
    +	       	}
    +	       	case 1 : {	
    +	       		if (depth >= maxRecursion) {
    +	       			t[0] = (w[0].x + w[degree].x) / 2.0;
    +	       			return 1;
    +	       		}
    +	       		if (_isFlatEnough(w, degree)) {
    +	       			t[0] = _computeXIntercept(w, degree);
    +	       			return 1;
    +	       		}
    +	       		break;
    +	       	}
    +	    }
    +	    _bezier(w, degree, 0.5, left, right);
    +	    left_count  = _findRoots(left,  degree, left_t, depth+1);
    +	    right_count = _findRoots(right, degree, right_t, depth+1);
    +	    for (var i = 0; i < left_count; i++) t[i] = left_t[i];
    +	    for (var i = 0; i < right_count; i++) t[i+left_count] = right_t[i];    
    +		return (left_count+right_count);
    +	};
    +	var _getCrossingCount = function(curve, degree) {
    +	    var n_crossings = 0, sign, old_sign;		    	
    +	    sign = old_sign = Math.sgn(curve[0].y);
    +	    for (var i = 1; i <= degree; i++) {
    +			sign = Math.sgn(curve[i].y);
    +			if (sign != old_sign) n_crossings++;
    +			old_sign = sign;
    +	    }
    +	    return n_crossings;
    +	};
    +	var _isFlatEnough = function(curve, degree) {
    +	    var  error,
    +	    	intercept_1, intercept_2, left_intercept, right_intercept,
    +	    	a, b, c, det, dInv, a1, b1, c1, a2, b2, c2;
    +	    a = curve[0].y - curve[degree].y;
    +	    b = curve[degree].x - curve[0].x;
    +	    c = curve[0].x * curve[degree].y - curve[degree].x * curve[0].y;
    +	
    +	    var max_distance_above = max_distance_below = 0.0;
    +	    
    +	    for (var i = 1; i < degree; i++) {
    +	        var value = a * curve[i].x + b * curve[i].y + c;       
    +	        if (value > max_distance_above)
    +	            max_distance_above = value;
    +	        else if (value < max_distance_below)
    +	        	max_distance_below = value;
    +	    }
    +	    
    +	    a1 = 0.0; b1 = 1.0; c1 = 0.0; a2 = a; b2 = b;
    +	    c2 = c - max_distance_above;
    +	    det = a1 * b2 - a2 * b1;
    +	    dInv = 1.0/det;
    +	    intercept_1 = (b1 * c2 - b2 * c1) * dInv;
    +	    a2 = a; b2 = b; c2 = c - max_distance_below;
    +	    det = a1 * b2 - a2 * b1;
    +	    dInv = 1.0/det;
    +	    intercept_2 = (b1 * c2 - b2 * c1) * dInv;
    +	    left_intercept = Math.min(intercept_1, intercept_2);
    +	    right_intercept = Math.max(intercept_1, intercept_2);
    +	    error = right_intercept - left_intercept;
    +	    return (error < flatnessTolerance)? 1 : 0;
    +	};
    +	var _computeXIntercept = function(curve, degree) {
    +	    var XLK = 1.0, YLK = 0.0,
    +	    	XNM = curve[degree].x - curve[0].x, YNM = curve[degree].y - curve[0].y,
    +	    	XMK = curve[0].x - 0.0, YMK = curve[0].y - 0.0,
    +	    	det = XNM*YLK - YNM*XLK, detInv = 1.0/det,
    +	    	S = (XNM*YMK - YNM*XMK) * detInv; 
    +	    return 0.0 + XLK * S;
    +	};
    +	var _bezier = function(curve, degree, t, left, right) {
    +	    var temp = [[]];
    +	    for (var j =0; j <= degree; j++) temp[0][j] = curve[j];
    +	    for (var i = 1; i <= degree; i++) {	
    +			for (var j =0 ; j <= degree - i; j++) {
    +				if (!temp[i]) temp[i] = [];
    +				if (!temp[i][j]) temp[i][j] = {};
    +		    	temp[i][j].x = (1.0 - t) * temp[i-1][j].x + t * temp[i-1][j+1].x;
    +		    	temp[i][j].y = (1.0 - t) * temp[i-1][j].y + t * temp[i-1][j+1].y;
    +			}
    +	    }    
    +	    if (left != null) 
    +	    	for (j = 0; j <= degree; j++) left[j]  = temp[j][0];
    +	    if (right != null)
    +			for (j = 0; j <= degree; j++) right[j] = temp[degree-j][j];
    +	    
    +	    return (temp[degree][0]);
    +	};
    +	
    +	var _curveFunctionCache = {};
    +	var _getCurveFunctions = function(order) {
    +		var fns = _curveFunctionCache[order];
    +		if (!fns) {
    +			fns = [];			
    +			var f_term = function() { return function(t) { return Math.pow(t, order); }; },
    +				l_term = function() { return function(t) { return Math.pow((1-t), order); }; },
    +				c_term = function(c) { return function(t) { return c; }; },
    +				t_term = function() { return function(t) { return t; }; },
    +				one_minus_t_term = function() { return function(t) { return 1-t; }; },
    +				_termFunc = function(terms) {
    +					return function(t) {
    +						var p = 1;
    +						for (var i = 0; i < terms.length; i++) p = p * terms[i](t);
    +						return p;
    +					};
    +				};
    +			
    +			fns.push(new f_term());  // first is t to the power of the curve order		
    +			for (var i = 1; i < order; i++) {
    +				var terms = [new c_term(order)];
    +				for (var j = 0 ; j < (order - i); j++) terms.push(new t_term());
    +				for (var j = 0 ; j < i; j++) terms.push(new one_minus_t_term());
    +				fns.push(new _termFunc(terms));
    +			}
    +			fns.push(new l_term());  // last is (1-t) to the power of the curve order
    +		
    +			_curveFunctionCache[order] = fns;
    +		}
    +			
    +		return fns;
    +	};
    +	
    +	
    +	/**
    +	 * calculates a point on the curve, for a Bezier of arbitrary order.
    +	 * @param curve an array of control points, eg [{x:10,y:20}, {x:50,y:50}, {x:100,y:100}, {x:120,y:100}].  For a cubic bezier this should have four points.
    +	 * @param location a decimal indicating the distance along the curve the point should be located at.  this is the distance along the curve as it travels, taking the way it bends into account.  should be a number from 0 to 1, inclusive.
    +	 */
    +	var _pointOnPath = function(curve, location) {		
    +		var cc = _getCurveFunctions(curve.length - 1),
    +			_x = 0, _y = 0;
    +		for (var i = 0; i < curve.length ; i++) {
    +			_x = _x + (curve[i].x * cc[i](location));
    +			_y = _y + (curve[i].y * cc[i](location));
    +		}
    +		
    +		return {x:_x, y:_y};
    +	};
    +	
    +	var _dist = function(p1,p2) {
    +		return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
    +	};
    +	
    +	/**
    +	 * finds the point that is 'distance' along the path from 'location'.  this method returns both the x,y location of the point and also
    +	 * its 'location' (proportion of travel along the path); the method below - _pointAlongPathFrom - calls this method and just returns the
    +	 * point.
    +	 */
    +	var _pointAlongPath = function(curve, location, distance) {
    +		var prev = _pointOnPath(curve, location), 
    +			tally = 0, 
    +			curLoc = location, 
    +			direction = distance > 0 ? 1 : -1, 
    +			cur = null;
    +			
    +		while (tally < Math.abs(distance)) {
    +			curLoc += (0.005 * direction);
    +			cur = _pointOnPath(curve, curLoc);
    +			tally += _dist(cur, prev);	
    +			prev = cur;
    +		}
    +		return {point:cur, location:curLoc};        	
    +	};
    +	
    +	var _length = function(curve) {
    +		var prev = _pointOnPath(curve, 0),
    +			tally = 0,
    +			curLoc = 0,
    +			direction = 1,
    +			cur = null;
    +			
    +		while (curLoc < 1) {
    +			curLoc += (0.005 * direction);
    +			cur = _pointOnPath(curve, curLoc);
    +			tally += _dist(cur, prev);	
    +			prev = cur;
    +		}
    +		return tally;
    +	};
    +	
    +	/**
    +	 * finds the point that is 'distance' along the path from 'location'.  
    +	 */
    +	var _pointAlongPathFrom = function(curve, location, distance) {
    +		return _pointAlongPath(curve, location, distance).point;
    +	};
    +
    +	/**
    +	 * finds the location that is 'distance' along the path from 'location'.  
    +	 */
    +	var _locationAlongPathFrom = function(curve, location, distance) {
    +		return _pointAlongPath(curve, location, distance).location;
    +	};
    +	
    +	/**
    +	 * returns the gradient of the curve at the given location, which is a decimal between 0 and 1 inclusive.
    +	 * 
    +	 * thanks // http://bimixual.org/AnimationLibrary/beziertangents.html
    +	 */
    +	var _gradientAtPoint = function(curve, location) {
    +		var p1 = _pointOnPath(curve, location),	
    +			p2 = _pointOnPath(curve.slice(0, curve.length - 1), location),
    +			dy = p2.y - p1.y, dx = p2.x - p1.x;
    +		return dy == 0 ? Infinity : Math.atan(dy / dx);		
    +	};
    +	
    +	/**
    +	returns the gradient of the curve at the point which is 'distance' from the given location.
    +	if this point is greater than location 1, the gradient at location 1 is returned.
    +	if this point is less than location 0, the gradient at location 0 is returned.
    +	*/
    +	var _gradientAtPointAlongPathFrom = function(curve, location, distance) {
    +		var p = _pointAlongPath(curve, location, distance);
    +		if (p.location > 1) p.location = 1;
    +		if (p.location < 0) p.location = 0;		
    +		return _gradientAtPoint(curve, p.location);		
    +	};
    +
    +	/**
    +	 * calculates a line that is 'length' pixels long, perpendicular to, and centered on, the path at 'distance' pixels from the given location.
    +	 * if distance is not supplied, the perpendicular for the given location is computed (ie. we set distance to zero).
    +	 */
    +	var _perpendicularToPathAt = function(curve, location, length, distance) {
    +		distance = distance == null ? 0 : distance;
    +		var p = _pointAlongPath(curve, location, distance),
    +			m = _gradientAtPoint(curve, p.location),
    +			_theta2 = Math.atan(-1 / m),
    +			y =  length / 2 * Math.sin(_theta2),
    +			x =  length / 2 * Math.cos(_theta2);
    +		return [{x:p.point.x + x, y:p.point.y + y}, {x:p.point.x - x, y:p.point.y - y}];
    +	};
    +	
    +	var jsBezier = window.jsBezier = {
    +		distanceFromCurve : _distanceFromCurve,
    +		gradientAtPoint : _gradientAtPoint,
    +		gradientAtPointAlongCurveFrom : _gradientAtPointAlongPathFrom,
    +		nearestPointOnCurve : _nearestPointOnCurve,
    +		pointOnCurve : _pointOnPath,		
    +		pointAlongCurveFrom : _pointAlongPathFrom,
    +		perpendicularToCurveAt : _perpendicularToPathAt,
    +		locationAlongCurveFrom:_locationAlongPathFrom,
    +		getLength:_length
    +	};
    +})();
    diff --git a/lib/jsBezier-0.6-min.js b/lib/jsBezier-0.6-min.js
    new file mode 100644
    index 000000000..0054c6c93
    --- /dev/null
    +++ b/lib/jsBezier-0.6-min.js
    @@ -0,0 +1,8 @@
    +(function(){"undefined"==typeof Math.sgn&&(Math.sgn=function(a){return 0==a?0:0<a?1:-1});var q={subtract:function(a,b){return{x:a.x-b.x,y:a.y-b.y}},dotProduct:function(a,b){return a.x*b.x+a.y*b.y},square:function(a){return Math.sqrt(a.x*a.x+a.y*a.y)},scale:function(a,b){return{x:a.x*b,y:a.y*b}}},B=Math.pow(2,-65),x=function(a,b){for(var f=[],d=b.length-1,g=2*d-1,h=[],e=[],m=[],k=[],l=[[1,0.6,0.3,0.1],[0.4,0.6,0.6,0.4],[0.1,0.3,0.6,1]],c=0;c<=d;c++)h[c]=q.subtract(b[c],a);for(c=0;c<=d-1;c++)e[c]=q.subtract(b[c+
    +1],b[c]),e[c]=q.scale(e[c],3);for(c=0;c<=d-1;c++)for(var n=0;n<=d;n++)m[c]||(m[c]=[]),m[c][n]=q.dotProduct(e[c],h[n]);for(c=0;c<=g;c++)k[c]||(k[c]=[]),k[c].y=0,k[c].x=parseFloat(c)/g;g=d-1;for(h=0;h<=d+g;h++){c=Math.max(0,h-g);for(e=Math.min(h,d);c<=e;c++)j=h-c,k[c+j].y+=m[j][c]*l[j][c]}d=b.length-1;k=u(k,2*d-1,f,0);g=q.subtract(a,b[0]);m=q.square(g);for(c=l=0;c<k;c++)g=q.subtract(a,v(b,d,f[c],null,null)),g=q.square(g),g<m&&(m=g,l=f[c]);g=q.subtract(a,b[d]);g=q.square(g);g<m&&(m=g,l=1);return{location:l,
    +distance:m}},u=function(a,b,f,d){var g=[],h=[],e=[],m=[],k=0,l,c;c=Math.sgn(a[0].y);for(var n=1;n<=b;n++)l=Math.sgn(a[n].y),l!=c&&k++,c=l;switch(k){case 0:return 0;case 1:if(64<=d)return f[0]=(a[0].x+a[b].x)/2,1;var r,p,k=a[0].y-a[b].y;c=a[b].x-a[0].x;n=a[0].x*a[b].y-a[b].x*a[0].y;l=max_distance_below=0;for(r=1;r<b;r++)p=k*a[r].x+c*a[r].y+n,p>l?l=p:p<max_distance_below&&(max_distance_below=p);p=c;r=0*p-1*k;l=(1*(n-l)-0*p)*(1/r);p=c;c=n-max_distance_below;r=0*p-1*k;k=(1*c-0*p)*(1/r);c=Math.min(l,k);
    +if(Math.max(l,k)-c<B)return e=a[b].x-a[0].x,m=a[b].y-a[0].y,f[0]=0+1*(e*(a[0].y-0)-m*(a[0].x-0))*(1/(0*e-1*m)),1}v(a,b,0.5,g,h);a=u(g,b,e,d+1);b=u(h,b,m,d+1);for(d=0;d<a;d++)f[d]=e[d];for(d=0;d<b;d++)f[d+a]=m[d];return a+b},v=function(a,b,f,d,g){for(var h=[[]],e=0;e<=b;e++)h[0][e]=a[e];for(a=1;a<=b;a++)for(e=0;e<=b-a;e++)h[a]||(h[a]=[]),h[a][e]||(h[a][e]={}),h[a][e].x=(1-f)*h[a-1][e].x+f*h[a-1][e+1].x,h[a][e].y=(1-f)*h[a-1][e].y+f*h[a-1][e+1].y;if(null!=d)for(e=0;e<=b;e++)d[e]=h[e][0];if(null!=g)for(e=
    +0;e<=b;e++)g[e]=h[b-e][e];return h[b][0]},y={},s=function(a,b){var f,d=a.length-1;f=y[d];if(!f){f=[];var g=function(a){return function(){return a}},h=function(){return function(a){return a}},e=function(){return function(a){return 1-a}},m=function(a){return function(b){for(var c=1,d=0;d<a.length;d++)c*=a[d](b);return c}};f.push(new function(){return function(a){return Math.pow(a,d)}});for(var k=1;k<d;k++){for(var l=[new g(d)],c=0;c<d-k;c++)l.push(new h);for(c=0;c<k;c++)l.push(new e);f.push(new m(l))}f.push(new function(){return function(a){return Math.pow(1-
    +a,d)}});y[d]=f}for(e=h=g=0;e<a.length;e++)g+=a[e].x*f[e](b),h+=a[e].y*f[e](b);return{x:g,y:h}},z=function(a,b){return Math.sqrt(Math.pow(a.x-b.x,2)+Math.pow(a.y-b.y,2))},A=function(a){return a[0].x==a[1].x&&a[0].y==a[1].y},t=function(a,b,f){if(A(a))return{point:a[0],location:b};for(var d=s(a,b),g=0,h=0<f?1:-1,e=null;g<Math.abs(f);)b+=0.005*h,e=s(a,b),g+=z(e,d),d=e;return{point:e,location:b}},w=function(a,b){var f=s(a,b),d=s(a.slice(0,a.length-1),b),g=d.y-f.y,f=d.x-f.x;return 0==g?Infinity:Math.atan(g/
    +f)};window.jsBezier={distanceFromCurve:x,gradientAtPoint:w,gradientAtPointAlongCurveFrom:function(a,b,f){b=t(a,b,f);1<b.location&&(b.location=1);0>b.location&&(b.location=0);return w(a,b.location)},nearestPointOnCurve:function(a,b){var f=x(a,b);return{point:v(b,b.length-1,f.location,null,null),location:f.location}},pointOnCurve:s,pointAlongCurveFrom:function(a,b,f){return t(a,b,f).point},perpendicularToCurveAt:function(a,b,f,d){b=t(a,b,null==d?0:d);a=w(a,b.location);d=Math.atan(-1/a);a=f/2*Math.sin(d);
    +f=f/2*Math.cos(d);return[{x:b.point.x+f,y:b.point.y+a},{x:b.point.x-f,y:b.point.y-a}]},locationAlongCurveFrom:function(a,b,f){return t(a,b,f).location},getLength:function(a){if(A(a))return 0;for(var b=s(a,0),f=0,d=0,g=null;1>d;)d+=0.005,g=s(a,d),f+=z(g,b),b=g;return f}}})();
    \ No newline at end of file
    diff --git a/lib/jsBezier-0.6.js b/lib/jsBezier-0.6.js
    new file mode 100644
    index 000000000..47ad1b17d
    --- /dev/null
    +++ b/lib/jsBezier-0.6.js
    @@ -0,0 +1,422 @@
    +/**
    +* jsBezier-0.6
    +*
    +* Copyright (c) 2010 - 2013 Simon Porritt (simon.porritt@gmail.com)
    +*
    +* licensed under the MIT license.
    +* 
    +* a set of Bezier curve functions that deal with Beziers, used by jsPlumb, and perhaps useful for other people.  These functions work with Bezier
    +* curves of arbitrary degree.
    +*
    +* - functions are all in the 'jsBezier' namespace.  
    +* 
    +* - all input points should be in the format {x:.., y:..}. all output points are in this format too.
    +* 
    +* - all input curves should be in the format [ {x:.., y:..}, {x:.., y:..}, {x:.., y:..}, {x:.., y:..} ]
    +* 
    +* - 'location' as used as an input here refers to a decimal in the range 0-1 inclusive, which indicates a point some proportion along the length
    +* of the curve.  location as output has the same format and meaning.
    +* 
    +* 
    +* Function List:
    +* --------------
    +* 
    +* distanceFromCurve(point, curve)
    +* 
    +* 	Calculates the distance that the given point lies from the given Bezier.  Note that it is computed relative to the center of the Bezier,
    +* so if you have stroked the curve with a wide pen you may wish to take that into account!  The distance returned is relative to the values 
    +* of the curve and the point - it will most likely be pixels.
    +* 
    +* gradientAtPoint(curve, location)
    +* 
    +* 	Calculates the gradient to the curve at the given location, as a decimal between 0 and 1 inclusive.
    +*
    +* gradientAtPointAlongCurveFrom (curve, location)
    +*
    +*	Calculates the gradient at the point on the given curve that is 'distance' units from location. 
    +* 
    +* nearestPointOnCurve(point, curve) 
    +* 
    +*	Calculates the nearest point to the given point on the given curve.  The return value of this is a JS object literal, containing both the
    +*point's coordinates and also the 'location' of the point (see above), for example:  { point:{x:551,y:150}, location:0.263365 }.
    +* 
    +* pointOnCurve(curve, location)
    +* 
    +* 	Calculates the coordinates of the point on the given Bezier curve at the given location.  
    +* 		
    +* pointAlongCurveFrom(curve, location, distance)
    +* 
    +* 	Calculates the coordinates of the point on the given curve that is 'distance' units from location.  'distance' should be in the same coordinate
    +* space as that used to construct the Bezier curve.  For an HTML Canvas usage, for example, distance would be a measure of pixels.
    +*
    +* locationAlongCurveFrom(curve, location, distance)
    +* 
    +* 	Calculates the location on the given curve that is 'distance' units from location.  'distance' should be in the same coordinate
    +* space as that used to construct the Bezier curve.  For an HTML Canvas usage, for example, distance would be a measure of pixels.
    +* 
    +* perpendicularToCurveAt(curve, location, length, distance)
    +* 
    +* 	Calculates the perpendicular to the given curve at the given location.  length is the length of the line you wish for (it will be centered
    +* on the point at 'location'). distance is optional, and allows you to specify a point along the path from the given location as the center of
    +* the perpendicular returned.  The return value of this is an array of two points: [ {x:...,y:...}, {x:...,y:...} ].  
    +*  
    +* 
    +*/
    +
    +(function() {
    +	
    +	if(typeof Math.sgn == "undefined") {
    +		Math.sgn = function(x) { return x == 0 ? 0 : x > 0 ? 1 :-1; };
    +	}
    +	
    +	var Vectors = {
    +			subtract 	: 	function(v1, v2) { return {x:v1.x - v2.x, y:v1.y - v2.y }; },
    +			dotProduct	: 	function(v1, v2) { return (v1.x * v2.x)  + (v1.y * v2.y); },
    +			square		:	function(v) { return Math.sqrt((v.x * v.x) + (v.y * v.y)); },
    +			scale		:	function(v, s) { return {x:v.x * s, y:v.y * s }; }
    +		},
    +		
    +		maxRecursion = 64, 
    +		flatnessTolerance = Math.pow(2.0,-maxRecursion-1);
    +
    +	/**
    +	 * Calculates the distance that the point lies from the curve.
    +	 * 
    +	 * @param point a point in the form {x:567, y:3342}
    +	 * @param curve a Bezier curve in the form [{x:..., y:...}, {x:..., y:...}, {x:..., y:...}, {x:..., y:...}].  note that this is currently
    +	 * hardcoded to assume cubiz beziers, but would be better off supporting any degree. 
    +	 * @return a JS object literal containing location and distance, for example: {location:0.35, distance:10}.  Location is analogous to the location
    +	 * argument you pass to the pointOnPath function: it is a ratio of distance travelled along the curve.  Distance is the distance in pixels from
    +	 * the point to the curve. 
    +	 */
    +	var _distanceFromCurve = function(point, curve) {
    +		var candidates = [],     
    +	    	w = _convertToBezier(point, curve),
    +	    	degree = curve.length - 1, higherDegree = (2 * degree) - 1,
    +	    	numSolutions = _findRoots(w, higherDegree, candidates, 0),
    +			v = Vectors.subtract(point, curve[0]), dist = Vectors.square(v), t = 0.0;
    +
    +	    for (var i = 0; i < numSolutions; i++) {
    +			v = Vectors.subtract(point, _bezier(curve, degree, candidates[i], null, null));
    +	    	var newDist = Vectors.square(v);
    +	    	if (newDist < dist) {
    +	            dist = newDist;
    +	        	t = candidates[i];
    +		    }
    +	    }
    +	    v = Vectors.subtract(point, curve[degree]);
    +		newDist = Vectors.square(v);
    +	    if (newDist < dist) {
    +	        dist = newDist;
    +	    	t = 1.0;
    +	    }
    +		return {location:t, distance:dist};
    +	};
    +	/**
    +	 * finds the nearest point on the curve to the given point.
    +	 */
    +	var _nearestPointOnCurve = function(point, curve) {    
    +		var td = _distanceFromCurve(point, curve);
    +	    return {point:_bezier(curve, curve.length - 1, td.location, null, null), location:td.location};
    +	};
    +	var _convertToBezier = function(point, curve) {
    +		var degree = curve.length - 1, higherDegree = (2 * degree) - 1,
    +	    	c = [], d = [], cdTable = [], w = [],
    +	    	z = [ [1.0, 0.6, 0.3, 0.1], [0.4, 0.6, 0.6, 0.4], [0.1, 0.3, 0.6, 1.0] ];	
    +	    	
    +	    for (var i = 0; i <= degree; i++) c[i] = Vectors.subtract(curve[i], point);
    +	    for (var i = 0; i <= degree - 1; i++) { 
    +			d[i] = Vectors.subtract(curve[i+1], curve[i]);
    +			d[i] = Vectors.scale(d[i], 3.0);
    +	    }
    +	    for (var row = 0; row <= degree - 1; row++) {
    +			for (var column = 0; column <= degree; column++) {
    +				if (!cdTable[row]) cdTable[row] = [];
    +		    	cdTable[row][column] = Vectors.dotProduct(d[row], c[column]);
    +			}
    +	    }
    +	    for (i = 0; i <= higherDegree; i++) {
    +			if (!w[i]) w[i] = [];
    +			w[i].y = 0.0;
    +			w[i].x = parseFloat(i) / higherDegree;
    +	    }
    +	    var n = degree, m = degree-1;
    +	    for (var k = 0; k <= n + m; k++) {
    +			var lb = Math.max(0, k - m),
    +				ub = Math.min(k, n);
    +			for (i = lb; i <= ub; i++) {
    +		    	j = k - i;
    +		    	w[i+j].y += cdTable[j][i] * z[j][i];
    +			}
    +	    }
    +	    return w;
    +	};
    +	/**
    +	 * counts how many roots there are.
    +	 */
    +	var _findRoots = function(w, degree, t, depth) {  
    +	    var left = [], right = [],	
    +	    	left_count, right_count,	
    +	    	left_t = [], right_t = [];
    +	    	
    +	    switch (_getCrossingCount(w, degree)) {
    +	       	case 0 : {	
    +	       		return 0;	
    +	       	}
    +	       	case 1 : {	
    +	       		if (depth >= maxRecursion) {
    +	       			t[0] = (w[0].x + w[degree].x) / 2.0;
    +	       			return 1;
    +	       		}
    +	       		if (_isFlatEnough(w, degree)) {
    +	       			t[0] = _computeXIntercept(w, degree);
    +	       			return 1;
    +	       		}
    +	       		break;
    +	       	}
    +	    }
    +	    _bezier(w, degree, 0.5, left, right);
    +	    left_count  = _findRoots(left,  degree, left_t, depth+1);
    +	    right_count = _findRoots(right, degree, right_t, depth+1);
    +	    for (var i = 0; i < left_count; i++) t[i] = left_t[i];
    +	    for (var i = 0; i < right_count; i++) t[i+left_count] = right_t[i];    
    +		return (left_count+right_count);
    +	};
    +	var _getCrossingCount = function(curve, degree) {
    +	    var n_crossings = 0, sign, old_sign;		    	
    +	    sign = old_sign = Math.sgn(curve[0].y);
    +	    for (var i = 1; i <= degree; i++) {
    +			sign = Math.sgn(curve[i].y);
    +			if (sign != old_sign) n_crossings++;
    +			old_sign = sign;
    +	    }
    +	    return n_crossings;
    +	};
    +	var _isFlatEnough = function(curve, degree) {
    +	    var  error,
    +	    	intercept_1, intercept_2, left_intercept, right_intercept,
    +	    	a, b, c, det, dInv, a1, b1, c1, a2, b2, c2;
    +	    a = curve[0].y - curve[degree].y;
    +	    b = curve[degree].x - curve[0].x;
    +	    c = curve[0].x * curve[degree].y - curve[degree].x * curve[0].y;
    +	
    +	    var max_distance_above = max_distance_below = 0.0;
    +	    
    +	    for (var i = 1; i < degree; i++) {
    +	        var value = a * curve[i].x + b * curve[i].y + c;       
    +	        if (value > max_distance_above)
    +	            max_distance_above = value;
    +	        else if (value < max_distance_below)
    +	        	max_distance_below = value;
    +	    }
    +	    
    +	    a1 = 0.0; b1 = 1.0; c1 = 0.0; a2 = a; b2 = b;
    +	    c2 = c - max_distance_above;
    +	    det = a1 * b2 - a2 * b1;
    +	    dInv = 1.0/det;
    +	    intercept_1 = (b1 * c2 - b2 * c1) * dInv;
    +	    a2 = a; b2 = b; c2 = c - max_distance_below;
    +	    det = a1 * b2 - a2 * b1;
    +	    dInv = 1.0/det;
    +	    intercept_2 = (b1 * c2 - b2 * c1) * dInv;
    +	    left_intercept = Math.min(intercept_1, intercept_2);
    +	    right_intercept = Math.max(intercept_1, intercept_2);
    +	    error = right_intercept - left_intercept;
    +	    return (error < flatnessTolerance)? 1 : 0;
    +	};
    +	var _computeXIntercept = function(curve, degree) {
    +	    var XLK = 1.0, YLK = 0.0,
    +	    	XNM = curve[degree].x - curve[0].x, YNM = curve[degree].y - curve[0].y,
    +	    	XMK = curve[0].x - 0.0, YMK = curve[0].y - 0.0,
    +	    	det = XNM*YLK - YNM*XLK, detInv = 1.0/det,
    +	    	S = (XNM*YMK - YNM*XMK) * detInv; 
    +	    return 0.0 + XLK * S;
    +	};
    +	var _bezier = function(curve, degree, t, left, right) {
    +	    var temp = [[]];
    +	    for (var j =0; j <= degree; j++) temp[0][j] = curve[j];
    +	    for (var i = 1; i <= degree; i++) {	
    +			for (var j =0 ; j <= degree - i; j++) {
    +				if (!temp[i]) temp[i] = [];
    +				if (!temp[i][j]) temp[i][j] = {};
    +		    	temp[i][j].x = (1.0 - t) * temp[i-1][j].x + t * temp[i-1][j+1].x;
    +		    	temp[i][j].y = (1.0 - t) * temp[i-1][j].y + t * temp[i-1][j+1].y;
    +			}
    +	    }    
    +	    if (left != null) 
    +	    	for (j = 0; j <= degree; j++) left[j]  = temp[j][0];
    +	    if (right != null)
    +			for (j = 0; j <= degree; j++) right[j] = temp[degree-j][j];
    +	    
    +	    return (temp[degree][0]);
    +	};
    +	
    +	var _curveFunctionCache = {};
    +	var _getCurveFunctions = function(order) {
    +		var fns = _curveFunctionCache[order];
    +		if (!fns) {
    +			fns = [];			
    +			var f_term = function() { return function(t) { return Math.pow(t, order); }; },
    +				l_term = function() { return function(t) { return Math.pow((1-t), order); }; },
    +				c_term = function(c) { return function(t) { return c; }; },
    +				t_term = function() { return function(t) { return t; }; },
    +				one_minus_t_term = function() { return function(t) { return 1-t; }; },
    +				_termFunc = function(terms) {
    +					return function(t) {
    +						var p = 1;
    +						for (var i = 0; i < terms.length; i++) p = p * terms[i](t);
    +						return p;
    +					};
    +				};
    +			
    +			fns.push(new f_term());  // first is t to the power of the curve order		
    +			for (var i = 1; i < order; i++) {
    +				var terms = [new c_term(order)];
    +				for (var j = 0 ; j < (order - i); j++) terms.push(new t_term());
    +				for (var j = 0 ; j < i; j++) terms.push(new one_minus_t_term());
    +				fns.push(new _termFunc(terms));
    +			}
    +			fns.push(new l_term());  // last is (1-t) to the power of the curve order
    +		
    +			_curveFunctionCache[order] = fns;
    +		}
    +			
    +		return fns;
    +	};
    +	
    +	
    +	/**
    +	 * calculates a point on the curve, for a Bezier of arbitrary order.
    +	 * @param curve an array of control points, eg [{x:10,y:20}, {x:50,y:50}, {x:100,y:100}, {x:120,y:100}].  For a cubic bezier this should have four points.
    +	 * @param location a decimal indicating the distance along the curve the point should be located at.  this is the distance along the curve as it travels, taking the way it bends into account.  should be a number from 0 to 1, inclusive.
    +	 */
    +	var _pointOnPath = function(curve, location) {		
    +		var cc = _getCurveFunctions(curve.length - 1),
    +			_x = 0, _y = 0;
    +		for (var i = 0; i < curve.length ; i++) {
    +			_x = _x + (curve[i].x * cc[i](location));
    +			_y = _y + (curve[i].y * cc[i](location));
    +		}
    +		
    +		return {x:_x, y:_y};
    +	};
    +	
    +	var _dist = function(p1,p2) {
    +		return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
    +	};
    +
    +	var _isPoint = function(curve) {
    +		return curve[0].x == curve[1].x && curve[0].y == curve[1].y;
    +	};
    +	
    +	/**
    +	 * finds the point that is 'distance' along the path from 'location'.  this method returns both the x,y location of the point and also
    +	 * its 'location' (proportion of travel along the path); the method below - _pointAlongPathFrom - calls this method and just returns the
    +	 * point.
    +	 */
    +	var _pointAlongPath = function(curve, location, distance) {
    +
    +		if (_isPoint(curve)) {
    +			return {
    +				point:curve[0],
    +				location:location
    +			};
    +		}
    +
    +		var prev = _pointOnPath(curve, location), 
    +			tally = 0, 
    +			curLoc = location, 
    +			direction = distance > 0 ? 1 : -1, 
    +			cur = null;
    +			
    +		while (tally < Math.abs(distance)) {
    +			curLoc += (0.005 * direction);
    +			cur = _pointOnPath(curve, curLoc);
    +			tally += _dist(cur, prev);	
    +			prev = cur;
    +		}
    +		return {point:cur, location:curLoc};        	
    +	};
    +	
    +	var _length = function(curve) {
    +		if (_isPoint(curve)) return 0;
    +
    +		var prev = _pointOnPath(curve, 0),
    +			tally = 0,
    +			curLoc = 0,
    +			direction = 1,
    +			cur = null;
    +			
    +		while (curLoc < 1) {
    +			curLoc += (0.005 * direction);
    +			cur = _pointOnPath(curve, curLoc);
    +			tally += _dist(cur, prev);	
    +			prev = cur;
    +		}
    +		return tally;
    +	};
    +	
    +	/**
    +	 * finds the point that is 'distance' along the path from 'location'.  
    +	 */
    +	var _pointAlongPathFrom = function(curve, location, distance) {
    +		return _pointAlongPath(curve, location, distance).point;
    +	};
    +
    +	/**
    +	 * finds the location that is 'distance' along the path from 'location'.  
    +	 */
    +	var _locationAlongPathFrom = function(curve, location, distance) {
    +		return _pointAlongPath(curve, location, distance).location;
    +	};
    +	
    +	/**
    +	 * returns the gradient of the curve at the given location, which is a decimal between 0 and 1 inclusive.
    +	 * 
    +	 * thanks // http://bimixual.org/AnimationLibrary/beziertangents.html
    +	 */
    +	var _gradientAtPoint = function(curve, location) {
    +		var p1 = _pointOnPath(curve, location),	
    +			p2 = _pointOnPath(curve.slice(0, curve.length - 1), location),
    +			dy = p2.y - p1.y, dx = p2.x - p1.x;
    +		return dy == 0 ? Infinity : Math.atan(dy / dx);		
    +	};
    +	
    +	/**
    +	returns the gradient of the curve at the point which is 'distance' from the given location.
    +	if this point is greater than location 1, the gradient at location 1 is returned.
    +	if this point is less than location 0, the gradient at location 0 is returned.
    +	*/
    +	var _gradientAtPointAlongPathFrom = function(curve, location, distance) {
    +		var p = _pointAlongPath(curve, location, distance);
    +		if (p.location > 1) p.location = 1;
    +		if (p.location < 0) p.location = 0;		
    +		return _gradientAtPoint(curve, p.location);		
    +	};
    +
    +	/**
    +	 * calculates a line that is 'length' pixels long, perpendicular to, and centered on, the path at 'distance' pixels from the given location.
    +	 * if distance is not supplied, the perpendicular for the given location is computed (ie. we set distance to zero).
    +	 */
    +	var _perpendicularToPathAt = function(curve, location, length, distance) {
    +		distance = distance == null ? 0 : distance;
    +		var p = _pointAlongPath(curve, location, distance),
    +			m = _gradientAtPoint(curve, p.location),
    +			_theta2 = Math.atan(-1 / m),
    +			y =  length / 2 * Math.sin(_theta2),
    +			x =  length / 2 * Math.cos(_theta2);
    +		return [{x:p.point.x + x, y:p.point.y + y}, {x:p.point.x - x, y:p.point.y - y}];
    +	};
    +	
    +	var jsBezier = window.jsBezier = {
    +		distanceFromCurve : _distanceFromCurve,
    +		gradientAtPoint : _gradientAtPoint,
    +		gradientAtPointAlongCurveFrom : _gradientAtPointAlongPathFrom,
    +		nearestPointOnCurve : _nearestPointOnCurve,
    +		pointOnCurve : _pointOnPath,		
    +		pointAlongCurveFrom : _pointAlongPathFrom,
    +		perpendicularToCurveAt : _perpendicularToPathAt,
    +		locationAlongCurveFrom:_locationAlongPathFrom,
    +		getLength:_length
    +	};
    +})();
    diff --git a/lib/mootools-1.2.4.4-more.js b/lib/mootools-1.2.4.4-more.js
    new file mode 100644
    index 000000000..79d787bac
    --- /dev/null
    +++ b/lib/mootools-1.2.4.4-more.js
    @@ -0,0 +1,35 @@
    +//MooTools More, <http://mootools.net/more>. Copyright (c) 2006-2009 Aaron Newton <http://clientcide.com/>, Valerio Proietti <http://mad4milk.net> & the MooTools team <http://mootools.net/developers>, MIT Style License.
    +
    +MooTools.More={version:"1.2.4.4",build:"6f6057dc645fdb7547689183b2311063bd653ddf"};var Drag=new Class({Implements:[Events,Options],options:{snap:6,unit:"px",grid:false,style:true,limit:false,handle:false,invert:false,preventDefault:false,stopPropagation:false,modifiers:{x:"left",y:"top"}},initialize:function(){var b=Array.link(arguments,{options:Object.type,element:$defined});
    +this.element=document.id(b.element);this.document=this.element.getDocument();this.setOptions(b.options||{});var a=$type(this.options.handle);this.handles=((a=="array"||a=="collection")?$$(this.options.handle):document.id(this.options.handle))||this.element;
    +this.mouse={now:{},pos:{}};this.value={start:{},now:{}};this.selection=(Browser.Engine.trident)?"selectstart":"mousedown";this.bound={start:this.start.bind(this),check:this.check.bind(this),drag:this.drag.bind(this),stop:this.stop.bind(this),cancel:this.cancel.bind(this),eventStop:$lambda(false)};
    +this.attach();},attach:function(){this.handles.addEvent("mousedown",this.bound.start);return this;},detach:function(){this.handles.removeEvent("mousedown",this.bound.start);
    +return this;},start:function(c){if(c.rightClick){return;}if(this.options.preventDefault){c.preventDefault();}if(this.options.stopPropagation){c.stopPropagation();
    +}this.mouse.start=c.page;this.fireEvent("beforeStart",this.element);var a=this.options.limit;this.limit={x:[],y:[]};for(var d in this.options.modifiers){if(!this.options.modifiers[d]){continue;
    +}if(this.options.style){this.value.now[d]=this.element.getStyle(this.options.modifiers[d]).toInt();}else{this.value.now[d]=this.element[this.options.modifiers[d]];
    +}if(this.options.invert){this.value.now[d]*=-1;}this.mouse.pos[d]=c.page[d]-this.value.now[d];if(a&&a[d]){for(var b=2;b--;b){if($chk(a[d][b])){this.limit[d][b]=$lambda(a[d][b])();
    +}}}}if($type(this.options.grid)=="number"){this.options.grid={x:this.options.grid,y:this.options.grid};}this.document.addEvents({mousemove:this.bound.check,mouseup:this.bound.cancel});
    +this.document.addEvent(this.selection,this.bound.eventStop);},check:function(a){if(this.options.preventDefault){a.preventDefault();}var b=Math.round(Math.sqrt(Math.pow(a.page.x-this.mouse.start.x,2)+Math.pow(a.page.y-this.mouse.start.y,2)));
    +if(b>this.options.snap){this.cancel();this.document.addEvents({mousemove:this.bound.drag,mouseup:this.bound.stop});this.fireEvent("start",[this.element,a]).fireEvent("snap",this.element);
    +}},drag:function(a){if(this.options.preventDefault){a.preventDefault();}this.mouse.now=a.page;for(var b in this.options.modifiers){if(!this.options.modifiers[b]){continue;
    +}this.value.now[b]=this.mouse.now[b]-this.mouse.pos[b];if(this.options.invert){this.value.now[b]*=-1;}if(this.options.limit&&this.limit[b]){if($chk(this.limit[b][1])&&(this.value.now[b]>this.limit[b][1])){this.value.now[b]=this.limit[b][1];
    +}else{if($chk(this.limit[b][0])&&(this.value.now[b]<this.limit[b][0])){this.value.now[b]=this.limit[b][0];}}}if(this.options.grid[b]){this.value.now[b]-=((this.value.now[b]-(this.limit[b][0]||0))%this.options.grid[b]);
    +}if(this.options.style){this.element.setStyle(this.options.modifiers[b],this.value.now[b]+this.options.unit);}else{this.element[this.options.modifiers[b]]=this.value.now[b];
    +}}this.fireEvent("drag",[this.element,a]);},cancel:function(a){this.document.removeEvent("mousemove",this.bound.check);this.document.removeEvent("mouseup",this.bound.cancel);
    +if(a){this.document.removeEvent(this.selection,this.bound.eventStop);this.fireEvent("cancel",this.element);}},stop:function(a){this.document.removeEvent(this.selection,this.bound.eventStop);
    +this.document.removeEvent("mousemove",this.bound.drag);this.document.removeEvent("mouseup",this.bound.stop);if(a){this.fireEvent("complete",[this.element,a]);
    +}}});Element.implement({makeResizable:function(a){var b=new Drag(this,$merge({modifiers:{x:"width",y:"height"}},a));this.store("resizer",b);return b.addEvent("drag",function(){this.fireEvent("resize",b);
    +}.bind(this));}});Drag.Move=new Class({Extends:Drag,options:{droppables:[],container:false,precalculate:false,includeMargins:true,checkDroppables:true},initialize:function(b,a){this.parent(b,a);
    +b=this.element;this.droppables=$$(this.options.droppables);this.container=document.id(this.options.container);if(this.container&&$type(this.container)!="element"){this.container=document.id(this.container.getDocument().body);
    +}var c=b.getStyles("left","top","position");if(c.left=="auto"||c.top=="auto"){b.setPosition(b.getPosition(b.getOffsetParent()));}if(c.position=="static"){b.setStyle("position","absolute");
    +}this.addEvent("start",this.checkDroppables,true);this.overed=null;},start:function(a){if(this.container){this.options.limit=this.calculateLimit();}if(this.options.precalculate){this.positions=this.droppables.map(function(b){return b.getCoordinates();
    +});}this.parent(a);},calculateLimit:function(){var d=this.element.getOffsetParent(),g=this.container.getCoordinates(d),f={},c={},b={},i={},k={};["top","right","bottom","left"].each(function(o){f[o]=this.container.getStyle("border-"+o).toInt();
    +b[o]=this.element.getStyle("border-"+o).toInt();c[o]=this.element.getStyle("margin-"+o).toInt();i[o]=this.container.getStyle("margin-"+o).toInt();k[o]=d.getStyle("padding-"+o).toInt();
    +},this);var e=this.element.offsetWidth+c.left+c.right,n=this.element.offsetHeight+c.top+c.bottom,h=0,j=0,m=g.right-f.right-e,a=g.bottom-f.bottom-n;if(this.options.includeMargins){h+=c.left;
    +j+=c.top;}else{m+=c.right;a+=c.bottom;}if(this.element.getStyle("position")=="relative"){var l=this.element.getCoordinates(d);l.left-=this.element.getStyle("left").toInt();
    +l.top-=this.element.getStyle("top").toInt();h+=f.left-l.left;j+=f.top-l.top;m+=c.left-l.left;a+=c.top-l.top;if(this.container!=d){h+=i.left+k.left;j+=(Browser.Engine.trident4?0:i.top)+k.top;
    +}}else{h-=c.left;j-=c.top;if(this.container==d){m-=f.left;a-=f.top;}else{h+=g.left+f.left;j+=g.top+f.top;}}return{x:[h,m],y:[j,a]};},checkAgainst:function(c,b){c=(this.positions)?this.positions[b]:c.getCoordinates();
    +var a=this.mouse.now;return(a.x>c.left&&a.x<c.right&&a.y<c.bottom&&a.y>c.top);},checkDroppables:function(){var a=this.droppables.filter(this.checkAgainst,this).getLast();
    +if(this.overed!=a){if(this.overed){this.fireEvent("leave",[this.element,this.overed]);}if(a){this.fireEvent("enter",[this.element,a]);}this.overed=a;}},drag:function(a){this.parent(a);
    +if(this.options.checkDroppables&&this.droppables.length){this.checkDroppables();}},stop:function(a){this.checkDroppables();this.fireEvent("drop",[this.element,this.overed,a]);
    +this.overed=null;return this.parent(a);}});Element.implement({makeDraggable:function(a){var b=new Drag.Move(this,a);this.store("dragger",b);return b;}});
    diff --git a/lib/mootools-1.3.2-yui-compressed.js b/lib/mootools-1.3.2-yui-compressed.js
    new file mode 100644
    index 000000000..1e45607d9
    --- /dev/null
    +++ b/lib/mootools-1.3.2-yui-compressed.js
    @@ -0,0 +1,486 @@
    +/*
    +---
    +MooTools: the javascript framework
    +
    +web build:
    + - http://mootools.net/core/7c56cfef9dddcf170a5d68e3fb61cfd7
    +
    +packager build:
    + - packager build Core/Core Core/Array Core/String Core/Number Core/Function Core/Object Core/Event Core/Browser Core/Class Core/Class.Extras Core/Slick.Parser Core/Slick.Finder Core/Element Core/Element.Style Core/Element.Event Core/Element.Dimensions Core/Fx Core/Fx.CSS Core/Fx.Tween Core/Fx.Morph Core/Fx.Transitions Core/Request Core/Request.HTML Core/Request.JSON Core/Cookie Core/JSON Core/DOMReady Core/Swiff
    +
    +copyrights:
    +  - [MooTools](http://mootools.net)
    +
    +licenses:
    +  - [MIT License](http://mootools.net/license.txt)
    +...
    +*/
    +(function(){this.MooTools={version:"1.3.2",build:"c9f1ff10e9e7facb65e9481049ed1b450959d587"};var e=this.typeOf=function(i){if(i==null){return"null";}if(i.$family){return i.$family();
    +}if(i.nodeName){if(i.nodeType==1){return"element";}if(i.nodeType==3){return(/\S/).test(i.nodeValue)?"textnode":"whitespace";}}else{if(typeof i.length=="number"){if(i.callee){return"arguments";
    +}if("item" in i){return"collection";}}}return typeof i;};var u=this.instanceOf=function(w,i){if(w==null){return false;}var v=w.$constructor||w.constructor;
    +while(v){if(v===i){return true;}v=v.parent;}return w instanceof i;};var f=this.Function;var r=true;for(var q in {toString:1}){r=null;}if(r){r=["hasOwnProperty","valueOf","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","constructor"];
    +}f.prototype.overloadSetter=function(v){var i=this;return function(x,w){if(x==null){return this;}if(v||typeof x!="string"){for(var y in x){i.call(this,y,x[y]);
    +}if(r){for(var z=r.length;z--;){y=r[z];if(x.hasOwnProperty(y)){i.call(this,y,x[y]);}}}}else{i.call(this,x,w);}return this;};};f.prototype.overloadGetter=function(v){var i=this;
    +return function(x){var y,w;if(v||typeof x!="string"){y=x;}else{if(arguments.length>1){y=arguments;}}if(y){w={};for(var z=0;z<y.length;z++){w[y[z]]=i.call(this,y[z]);
    +}}else{w=i.call(this,x);}return w;};};f.prototype.extend=function(i,v){this[i]=v;}.overloadSetter();f.prototype.implement=function(i,v){this.prototype[i]=v;
    +}.overloadSetter();var o=Array.prototype.slice;f.from=function(i){return(e(i)=="function")?i:function(){return i;};};Array.from=function(i){if(i==null){return[];
    +}return(k.isEnumerable(i)&&typeof i!="string")?(e(i)=="array")?i:o.call(i):[i];};Number.from=function(v){var i=parseFloat(v);return isFinite(i)?i:null;
    +};String.from=function(i){return i+"";};f.implement({hide:function(){this.$hidden=true;return this;},protect:function(){this.$protected=true;return this;
    +}});var k=this.Type=function(x,w){if(x){var v=x.toLowerCase();var i=function(y){return(e(y)==v);};k["is"+x]=i;if(w!=null){w.prototype.$family=(function(){return v;
    +}).hide();w.type=i;}}if(w==null){return null;}w.extend(this);w.$constructor=k;w.prototype.$constructor=w;return w;};var p=Object.prototype.toString;k.isEnumerable=function(i){return(i!=null&&typeof i.length=="number"&&p.call(i)!="[object Function]");
    +};var b={};var d=function(i){var v=e(i.prototype);return b[v]||(b[v]=[]);};var h=function(w,A){if(A&&A.$hidden){return;}var v=d(this);for(var x=0;x<v.length;
    +x++){var z=v[x];if(e(z)=="type"){h.call(z,w,A);}else{z.call(this,w,A);}}var y=this.prototype[w];if(y==null||!y.$protected){this.prototype[w]=A;}if(this[w]==null&&e(A)=="function"){t.call(this,w,function(i){return A.apply(i,o.call(arguments,1));
    +});}};var t=function(i,w){if(w&&w.$hidden){return;}var v=this[i];if(v==null||!v.$protected){this[i]=w;}};k.implement({implement:h.overloadSetter(),extend:t.overloadSetter(),alias:function(i,v){h.call(this,i,this.prototype[v]);
    +}.overloadSetter(),mirror:function(i){d(this).push(i);return this;}});new k("Type",k);var c=function(v,z,x){var w=(z!=Object),D=z.prototype;if(w){z=new k(v,z);
    +}for(var A=0,y=x.length;A<y;A++){var E=x[A],C=z[E],B=D[E];if(C){C.protect();}if(w&&B){delete D[E];D[E]=B.protect();}}if(w){z.implement(D);}return c;};c("String",String,["charAt","charCodeAt","concat","indexOf","lastIndexOf","match","quote","replace","search","slice","split","substr","substring","toLowerCase","toUpperCase"])("Array",Array,["pop","push","reverse","shift","sort","splice","unshift","concat","join","slice","indexOf","lastIndexOf","filter","forEach","every","map","some","reduce","reduceRight"])("Number",Number,["toExponential","toFixed","toLocaleString","toPrecision"])("Function",f,["apply","call","bind"])("RegExp",RegExp,["exec","test"])("Object",Object,["create","defineProperty","defineProperties","keys","getPrototypeOf","getOwnPropertyDescriptor","getOwnPropertyNames","preventExtensions","isExtensible","seal","isSealed","freeze","isFrozen"])("Date",Date,["now"]);
    +Object.extend=t.overloadSetter();Date.extend("now",function(){return +(new Date);});new k("Boolean",Boolean);Number.prototype.$family=function(){return isFinite(this)?"number":"null";
    +}.hide();Number.extend("random",function(v,i){return Math.floor(Math.random()*(i-v+1)+v);});var l=Object.prototype.hasOwnProperty;Object.extend("forEach",function(i,w,x){for(var v in i){if(l.call(i,v)){w.call(x,i[v],v,i);
    +}}});Object.each=Object.forEach;Array.implement({forEach:function(x,y){for(var w=0,v=this.length;w<v;w++){if(w in this){x.call(y,this[w],w,this);}}},each:function(i,v){Array.forEach(this,i,v);
    +return this;}});var s=function(i){switch(e(i)){case"array":return i.clone();case"object":return Object.clone(i);default:return i;}};Array.implement("clone",function(){var v=this.length,w=new Array(v);
    +while(v--){w[v]=s(this[v]);}return w;});var a=function(v,i,w){switch(e(w)){case"object":if(e(v[i])=="object"){Object.merge(v[i],w);}else{v[i]=Object.clone(w);
    +}break;case"array":v[i]=w.clone();break;default:v[i]=w;}return v;};Object.extend({merge:function(C,y,x){if(e(y)=="string"){return a(C,y,x);}for(var B=1,w=arguments.length;
    +B<w;B++){var z=arguments[B];for(var A in z){a(C,A,z[A]);}}return C;},clone:function(i){var w={};for(var v in i){w[v]=s(i[v]);}return w;},append:function(z){for(var y=1,w=arguments.length;
    +y<w;y++){var v=arguments[y]||{};for(var x in v){z[x]=v[x];}}return z;}});["Object","WhiteSpace","TextNode","Collection","Arguments"].each(function(i){new k(i);
    +});var j=Date.now();String.extend("uniqueID",function(){return(j++).toString(36);});var g=this.Hash=new k("Hash",function(i){if(e(i)=="hash"){i=Object.clone(i.getClean());
    +}for(var v in i){this[v]=i[v];}return this;});g.implement({forEach:function(i,v){Object.forEach(this,i,v);},getClean:function(){var v={};for(var i in this){if(this.hasOwnProperty(i)){v[i]=this[i];
    +}}return v;},getLength:function(){var v=0;for(var i in this){if(this.hasOwnProperty(i)){v++;}}return v;}});g.alias("each","forEach");Object.type=k.isObject;
    +var n=this.Native=function(i){return new k(i.name,i.initialize);};n.type=k.type;n.implement=function(x,v){for(var w=0;w<x.length;w++){x[w].implement(v);
    +}return n;};var m=Array.type;Array.type=function(i){return u(i,Array)||m(i);};this.$A=function(i){return Array.from(i).slice();};this.$arguments=function(v){return function(){return arguments[v];
    +};};this.$chk=function(i){return !!(i||i===0);};this.$clear=function(i){clearTimeout(i);clearInterval(i);return null;};this.$defined=function(i){return(i!=null);
    +};this.$each=function(w,v,x){var i=e(w);((i=="arguments"||i=="collection"||i=="array"||i=="elements")?Array:Object).each(w,v,x);};this.$empty=function(){};
    +this.$extend=function(v,i){return Object.append(v,i);};this.$H=function(i){return new g(i);};this.$merge=function(){var i=Array.slice(arguments);i.unshift({});
    +return Object.merge.apply(null,i);};this.$lambda=f.from;this.$mixin=Object.merge;this.$random=Number.random;this.$splat=Array.from;this.$time=Date.now;
    +this.$type=function(i){var v=e(i);if(v=="elements"){return"array";}return(v=="null")?false:v;};this.$unlink=function(i){switch(e(i)){case"object":return Object.clone(i);
    +case"array":return Array.clone(i);case"hash":return new g(i);default:return i;}};})();Array.implement({every:function(c,d){for(var b=0,a=this.length;b<a;
    +b++){if((b in this)&&!c.call(d,this[b],b,this)){return false;}}return true;},filter:function(d,e){var c=[];for(var b=0,a=this.length;b<a;b++){if((b in this)&&d.call(e,this[b],b,this)){c.push(this[b]);
    +}}return c;},indexOf:function(c,d){var a=this.length;for(var b=(d<0)?Math.max(0,a+d):d||0;b<a;b++){if(this[b]===c){return b;}}return -1;},map:function(d,e){var c=[];
    +for(var b=0,a=this.length;b<a;b++){if(b in this){c[b]=d.call(e,this[b],b,this);}}return c;},some:function(c,d){for(var b=0,a=this.length;b<a;b++){if((b in this)&&c.call(d,this[b],b,this)){return true;
    +}}return false;},clean:function(){return this.filter(function(a){return a!=null;});},invoke:function(a){var b=Array.slice(arguments,1);return this.map(function(c){return c[a].apply(c,b);
    +});},associate:function(c){var d={},b=Math.min(this.length,c.length);for(var a=0;a<b;a++){d[c[a]]=this[a];}return d;},link:function(c){var a={};for(var e=0,b=this.length;
    +e<b;e++){for(var d in c){if(c[d](this[e])){a[d]=this[e];delete c[d];break;}}}return a;},contains:function(a,b){return this.indexOf(a,b)!=-1;},append:function(a){this.push.apply(this,a);
    +return this;},getLast:function(){return(this.length)?this[this.length-1]:null;},getRandom:function(){return(this.length)?this[Number.random(0,this.length-1)]:null;
    +},include:function(a){if(!this.contains(a)){this.push(a);}return this;},combine:function(c){for(var b=0,a=c.length;b<a;b++){this.include(c[b]);}return this;
    +},erase:function(b){for(var a=this.length;a--;){if(this[a]===b){this.splice(a,1);}}return this;},empty:function(){this.length=0;return this;},flatten:function(){var d=[];
    +for(var b=0,a=this.length;b<a;b++){var c=typeOf(this[b]);if(c=="null"){continue;}d=d.concat((c=="array"||c=="collection"||c=="arguments"||instanceOf(this[b],Array))?Array.flatten(this[b]):this[b]);
    +}return d;},pick:function(){for(var b=0,a=this.length;b<a;b++){if(this[b]!=null){return this[b];}}return null;},hexToRgb:function(b){if(this.length!=3){return null;
    +}var a=this.map(function(c){if(c.length==1){c+=c;}return c.toInt(16);});return(b)?a:"rgb("+a+")";},rgbToHex:function(d){if(this.length<3){return null;}if(this.length==4&&this[3]==0&&!d){return"transparent";
    +}var b=[];for(var a=0;a<3;a++){var c=(this[a]-0).toString(16);b.push((c.length==1)?"0"+c:c);}return(d)?b:"#"+b.join("");}});Array.alias("extend","append");
    +var $pick=function(){return Array.from(arguments).pick();};String.implement({test:function(a,b){return((typeOf(a)=="regexp")?a:new RegExp(""+a,b)).test(this);
    +},contains:function(a,b){return(b)?(b+this+b).indexOf(b+a+b)>-1:this.indexOf(a)>-1;},trim:function(){return this.replace(/^\s+|\s+$/g,"");},clean:function(){return this.replace(/\s+/g," ").trim();
    +},camelCase:function(){return this.replace(/-\D/g,function(a){return a.charAt(1).toUpperCase();});},hyphenate:function(){return this.replace(/[A-Z]/g,function(a){return("-"+a.charAt(0).toLowerCase());
    +});},capitalize:function(){return this.replace(/\b[a-z]/g,function(a){return a.toUpperCase();});},escapeRegExp:function(){return this.replace(/([-.*+?^${}()|[\]\/\\])/g,"\\$1");
    +},toInt:function(a){return parseInt(this,a||10);},toFloat:function(){return parseFloat(this);},hexToRgb:function(b){var a=this.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
    +return(a)?a.slice(1).hexToRgb(b):null;},rgbToHex:function(b){var a=this.match(/\d{1,3}/g);return(a)?a.rgbToHex(b):null;},substitute:function(a,b){return this.replace(b||(/\\?\{([^{}]+)\}/g),function(d,c){if(d.charAt(0)=="\\"){return d.slice(1);
    +}return(a[c]!=null)?a[c]:"";});}});Number.implement({limit:function(b,a){return Math.min(a,Math.max(b,this));},round:function(a){a=Math.pow(10,a||0).toFixed(a<0?-a:0);
    +return Math.round(this*a)/a;},times:function(b,c){for(var a=0;a<this;a++){b.call(c,a,this);}},toFloat:function(){return parseFloat(this);},toInt:function(a){return parseInt(this,a||10);
    +}});Number.alias("each","times");(function(b){var a={};b.each(function(c){if(!Number[c]){a[c]=function(){return Math[c].apply(null,[this].concat(Array.from(arguments)));
    +};}});Number.implement(a);})(["abs","acos","asin","atan","atan2","ceil","cos","exp","floor","log","max","min","pow","sin","sqrt","tan"]);Function.extend({attempt:function(){for(var b=0,a=arguments.length;
    +b<a;b++){try{return arguments[b]();}catch(c){}}return null;}});Function.implement({attempt:function(a,c){try{return this.apply(c,Array.from(a));}catch(b){}return null;
    +},bind:function(c){var a=this,b=(arguments.length>1)?Array.slice(arguments,1):null;return function(){if(!b&&!arguments.length){return a.call(c);}if(b&&arguments.length){return a.apply(c,b.concat(Array.from(arguments)));
    +}return a.apply(c,b||arguments);};},pass:function(b,c){var a=this;if(b!=null){b=Array.from(b);}return function(){return a.apply(c,b||arguments);};},delay:function(b,c,a){return setTimeout(this.pass((a==null?[]:a),c),b);
    +},periodical:function(c,b,a){return setInterval(this.pass((a==null?[]:a),b),c);}});delete Function.prototype.bind;Function.implement({create:function(b){var a=this;
    +b=b||{};return function(d){var c=b.arguments;c=(c!=null)?Array.from(c):Array.slice(arguments,(b.event)?1:0);if(b.event){c=[d||window.event].extend(c);}var e=function(){return a.apply(b.bind||null,c);
    +};if(b.delay){return setTimeout(e,b.delay);}if(b.periodical){return setInterval(e,b.periodical);}if(b.attempt){return Function.attempt(e);}return e();};
    +},bind:function(c,b){var a=this;if(b!=null){b=Array.from(b);}return function(){return a.apply(c,b||arguments);};},bindWithEvent:function(c,b){var a=this;
    +if(b!=null){b=Array.from(b);}return function(d){return a.apply(c,(b==null)?arguments:[d].concat(b));};},run:function(a,b){return this.apply(b,Array.from(a));
    +}});var $try=Function.attempt;(function(){var a=Object.prototype.hasOwnProperty;Object.extend({subset:function(d,g){var f={};for(var e=0,b=g.length;e<b;
    +e++){var c=g[e];if(c in d){f[c]=d[c];}}return f;},map:function(b,e,f){var d={};for(var c in b){if(a.call(b,c)){d[c]=e.call(f,b[c],c,b);}}return d;},filter:function(b,e,g){var d={};
    +for(var c in b){var f=b[c];if(a.call(b,c)&&e.call(g,f,c,b)){d[c]=f;}}return d;},every:function(b,d,e){for(var c in b){if(a.call(b,c)&&!d.call(e,b[c],c)){return false;
    +}}return true;},some:function(b,d,e){for(var c in b){if(a.call(b,c)&&d.call(e,b[c],c)){return true;}}return false;},keys:function(b){var d=[];for(var c in b){if(a.call(b,c)){d.push(c);
    +}}return d;},values:function(c){var b=[];for(var d in c){if(a.call(c,d)){b.push(c[d]);}}return b;},getLength:function(b){return Object.keys(b).length;},keyOf:function(b,d){for(var c in b){if(a.call(b,c)&&b[c]===d){return c;
    +}}return null;},contains:function(b,c){return Object.keyOf(b,c)!=null;},toQueryString:function(b,c){var d=[];Object.each(b,function(h,g){if(c){g=c+"["+g+"]";
    +}var f;switch(typeOf(h)){case"object":f=Object.toQueryString(h,g);break;case"array":var e={};h.each(function(k,j){e[j]=k;});f=Object.toQueryString(e,g);
    +break;default:f=g+"="+encodeURIComponent(h);}if(h!=null){d.push(f);}});return d.join("&");}});})();Hash.implement({has:Object.prototype.hasOwnProperty,keyOf:function(a){return Object.keyOf(this,a);
    +},hasValue:function(a){return Object.contains(this,a);},extend:function(a){Hash.each(a||{},function(c,b){Hash.set(this,b,c);},this);return this;},combine:function(a){Hash.each(a||{},function(c,b){Hash.include(this,b,c);
    +},this);return this;},erase:function(a){if(this.hasOwnProperty(a)){delete this[a];}return this;},get:function(a){return(this.hasOwnProperty(a))?this[a]:null;
    +},set:function(a,b){if(!this[a]||this.hasOwnProperty(a)){this[a]=b;}return this;},empty:function(){Hash.each(this,function(b,a){delete this[a];},this);
    +return this;},include:function(a,b){if(this[a]==null){this[a]=b;}return this;},map:function(a,b){return new Hash(Object.map(this,a,b));},filter:function(a,b){return new Hash(Object.filter(this,a,b));
    +},every:function(a,b){return Object.every(this,a,b);},some:function(a,b){return Object.some(this,a,b);},getKeys:function(){return Object.keys(this);},getValues:function(){return Object.values(this);
    +},toQueryString:function(a){return Object.toQueryString(this,a);}});Hash.extend=Object.append;Hash.alias({indexOf:"keyOf",contains:"hasValue"});(function(){var l=this.document;
    +var j=l.window=this;var b=1;this.$uid=(j.ActiveXObject)?function(e){return(e.uid||(e.uid=[b++]))[0];}:function(e){return e.uid||(e.uid=b++);};$uid(j);$uid(l);
    +var a=navigator.userAgent.toLowerCase(),c=navigator.platform.toLowerCase(),k=a.match(/(opera|ie|firefox|chrome|version)[\s\/:]([\w\d\.]+)?.*?(safari|version[\s\/:]([\w\d\.]+)|$)/)||[null,"unknown",0],g=k[1]=="ie"&&l.documentMode;
    +var p=this.Browser={extend:Function.prototype.extend,name:(k[1]=="version")?k[3]:k[1],version:g||parseFloat((k[1]=="opera"&&k[4])?k[4]:k[2]),Platform:{name:a.match(/ip(?:ad|od|hone)/)?"ios":(a.match(/(?:webos|android)/)||c.match(/mac|win|linux/)||["other"])[0]},Features:{xpath:!!(l.evaluate),air:!!(j.runtime),query:!!(l.querySelector),json:!!(j.JSON)},Plugins:{}};
    +p[p.name]=true;p[p.name+parseInt(p.version,10)]=true;p.Platform[p.Platform.name]=true;p.Request=(function(){var r=function(){return new XMLHttpRequest();
    +};var q=function(){return new ActiveXObject("MSXML2.XMLHTTP");};var e=function(){return new ActiveXObject("Microsoft.XMLHTTP");};return Function.attempt(function(){r();
    +return r;},function(){q();return q;},function(){e();return e;});})();p.Features.xhr=!!(p.Request);var i=(Function.attempt(function(){return navigator.plugins["Shockwave Flash"].description;
    +},function(){return new ActiveXObject("ShockwaveFlash.ShockwaveFlash").GetVariable("$version");})||"0 r0").match(/\d+/g);p.Plugins.Flash={version:Number(i[0]||"0."+i[1])||0,build:Number(i[2])||0};
    +p.exec=function(q){if(!q){return q;}if(j.execScript){j.execScript(q);}else{var e=l.createElement("script");e.setAttribute("type","text/javascript");e.text=q;
    +l.head.appendChild(e);l.head.removeChild(e);}return q;};String.implement("stripScripts",function(q){var e="";var r=this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi,function(s,t){e+=t+"\n";
    +return"";});if(q===true){p.exec(e);}else{if(typeOf(q)=="function"){q(e,r);}}return r;});p.extend({Document:this.Document,Window:this.Window,Element:this.Element,Event:this.Event});
    +this.Window=this.$constructor=new Type("Window",function(){});this.$family=Function.from("window").hide();Window.mirror(function(e,q){j[e]=q;});this.Document=l.$constructor=new Type("Document",function(){});
    +l.$family=Function.from("document").hide();Document.mirror(function(e,q){l[e]=q;});l.html=l.documentElement;if(!l.head){l.head=l.getElementsByTagName("head")[0];
    +}if(l.execCommand){try{l.execCommand("BackgroundImageCache",false,true);}catch(h){}}if(this.attachEvent&&!this.addEventListener){var d=function(){this.detachEvent("onunload",d);
    +l.head=l.html=l.window=null;};this.attachEvent("onunload",d);}var n=Array.from;try{n(l.html.childNodes);}catch(h){Array.from=function(q){if(typeof q!="string"&&Type.isEnumerable(q)&&typeOf(q)!="array"){var e=q.length,r=new Array(e);
    +while(e--){r[e]=q[e];}return r;}return n(q);};var m=Array.prototype,o=m.slice;["pop","push","reverse","shift","sort","splice","unshift","concat","join","slice"].each(function(e){var q=m[e];
    +Array[e]=function(r){return q.apply(Array.from(r),o.call(arguments,1));};});}if(p.Platform.ios){p.Platform.ipod=true;}p.Engine={};var f=function(q,e){p.Engine.name=q;
    +p.Engine[q+e]=true;p.Engine.version=e;};if(p.ie){p.Engine.trident=true;switch(p.version){case 6:f("trident",4);break;case 7:f("trident",5);break;case 8:f("trident",6);
    +}}if(p.firefox){p.Engine.gecko=true;if(p.version>=3){f("gecko",19);}else{f("gecko",18);}}if(p.safari||p.chrome){p.Engine.webkit=true;switch(p.version){case 2:f("webkit",419);
    +break;case 3:f("webkit",420);break;case 4:f("webkit",525);}}if(p.opera){p.Engine.presto=true;if(p.version>=9.6){f("presto",960);}else{if(p.version>=9.5){f("presto",950);
    +}else{f("presto",925);}}}if(p.name=="unknown"){switch((a.match(/(?:webkit|khtml|gecko)/)||[])[0]){case"webkit":case"khtml":p.Engine.webkit=true;break;case"gecko":p.Engine.gecko=true;
    +}}this.$exec=p.exec;})();var Event=new Type("Event",function(a,i){if(!i){i=window;}var o=i.document;a=a||i.event;if(a.$extended){return a;}this.$extended=true;
    +var n=a.type,k=a.target||a.srcElement,m={},c={},q=null,h,l,b,p;while(k&&k.nodeType==3){k=k.parentNode;}if(n.indexOf("key")!=-1){b=a.which||a.keyCode;p=Object.keyOf(Event.Keys,b);
    +if(n=="keydown"){var d=b-111;if(d>0&&d<13){p="f"+d;}}if(!p){p=String.fromCharCode(b).toLowerCase();}}else{if((/click|mouse|menu/i).test(n)){o=(!o.compatMode||o.compatMode=="CSS1Compat")?o.html:o.body;
    +m={x:(a.pageX!=null)?a.pageX:a.clientX+o.scrollLeft,y:(a.pageY!=null)?a.pageY:a.clientY+o.scrollTop};c={x:(a.pageX!=null)?a.pageX-i.pageXOffset:a.clientX,y:(a.pageY!=null)?a.pageY-i.pageYOffset:a.clientY};
    +if((/DOMMouseScroll|mousewheel/).test(n)){l=(a.wheelDelta)?a.wheelDelta/120:-(a.detail||0)/3;}h=(a.which==3)||(a.button==2);if((/over|out/).test(n)){q=a.relatedTarget||a[(n=="mouseover"?"from":"to")+"Element"];
    +var j=function(){while(q&&q.nodeType==3){q=q.parentNode;}return true;};var g=(Browser.firefox2)?j.attempt():j();q=(g)?q:null;}}else{if((/gesture|touch/i).test(n)){this.rotation=a.rotation;
    +this.scale=a.scale;this.targetTouches=a.targetTouches;this.changedTouches=a.changedTouches;var f=this.touches=a.touches;if(f&&f[0]){var e=f[0];m={x:e.pageX,y:e.pageY};
    +c={x:e.clientX,y:e.clientY};}}}}return Object.append(this,{event:a,type:n,page:m,client:c,rightClick:h,wheel:l,relatedTarget:document.id(q),target:document.id(k),code:b,key:p,shift:a.shiftKey,control:a.ctrlKey,alt:a.altKey,meta:a.metaKey});
    +});Event.Keys={enter:13,up:38,down:40,left:37,right:39,esc:27,space:32,backspace:8,tab:9,"delete":46};Event.Keys=new Hash(Event.Keys);Event.implement({stop:function(){return this.stopPropagation().preventDefault();
    +},stopPropagation:function(){if(this.event.stopPropagation){this.event.stopPropagation();}else{this.event.cancelBubble=true;}return this;},preventDefault:function(){if(this.event.preventDefault){this.event.preventDefault();
    +}else{this.event.returnValue=false;}return this;}});(function(){var a=this.Class=new Type("Class",function(h){if(instanceOf(h,Function)){h={initialize:h};
    +}var g=function(){e(this);if(g.$prototyping){return this;}this.$caller=null;var i=(this.initialize)?this.initialize.apply(this,arguments):this;this.$caller=this.caller=null;
    +return i;}.extend(this).implement(h);g.$constructor=a;g.prototype.$constructor=g;g.prototype.parent=c;return g;});var c=function(){if(!this.$caller){throw new Error('The method "parent" cannot be called.');
    +}var g=this.$caller.$name,h=this.$caller.$owner.parent,i=(h)?h.prototype[g]:null;if(!i){throw new Error('The method "'+g+'" has no parent.');}return i.apply(this,arguments);
    +};var e=function(g){for(var h in g){var j=g[h];switch(typeOf(j)){case"object":var i=function(){};i.prototype=j;g[h]=e(new i);break;case"array":g[h]=j.clone();
    +break;}}return g;};var b=function(g,h,j){if(j.$origin){j=j.$origin;}var i=function(){if(j.$protected&&this.$caller==null){throw new Error('The method "'+h+'" cannot be called.');
    +}var l=this.caller,m=this.$caller;this.caller=m;this.$caller=i;var k=j.apply(this,arguments);this.$caller=m;this.caller=l;return k;}.extend({$owner:g,$origin:j,$name:h});
    +return i;};var f=function(h,i,g){if(a.Mutators.hasOwnProperty(h)){i=a.Mutators[h].call(this,i);if(i==null){return this;}}if(typeOf(i)=="function"){if(i.$hidden){return this;
    +}this.prototype[h]=(g)?i:b(this,h,i);}else{Object.merge(this.prototype,h,i);}return this;};var d=function(g){g.$prototyping=true;var h=new g;delete g.$prototyping;
    +return h;};a.implement("implement",f.overloadSetter());a.Mutators={Extends:function(g){this.parent=g;this.prototype=d(g);},Implements:function(g){Array.from(g).each(function(j){var h=new j;
    +for(var i in h){f.call(this,i,h[i],true);}},this);}};})();(function(){this.Chain=new Class({$chain:[],chain:function(){this.$chain.append(Array.flatten(arguments));
    +return this;},callChain:function(){return(this.$chain.length)?this.$chain.shift().apply(this,arguments):false;},clearChain:function(){this.$chain.empty();
    +return this;}});var a=function(b){return b.replace(/^on([A-Z])/,function(c,d){return d.toLowerCase();});};this.Events=new Class({$events:{},addEvent:function(d,c,b){d=a(d);
    +if(c==$empty){return this;}this.$events[d]=(this.$events[d]||[]).include(c);if(b){c.internal=true;}return this;},addEvents:function(b){for(var c in b){this.addEvent(c,b[c]);
    +}return this;},fireEvent:function(e,c,b){e=a(e);var d=this.$events[e];if(!d){return this;}c=Array.from(c);d.each(function(f){if(b){f.delay(b,this,c);}else{f.apply(this,c);
    +}},this);return this;},removeEvent:function(e,d){e=a(e);var c=this.$events[e];if(c&&!d.internal){var b=c.indexOf(d);if(b!=-1){delete c[b];}}return this;
    +},removeEvents:function(d){var e;if(typeOf(d)=="object"){for(e in d){this.removeEvent(e,d[e]);}return this;}if(d){d=a(d);}for(e in this.$events){if(d&&d!=e){continue;
    +}var c=this.$events[e];for(var b=c.length;b--;){if(b in c){this.removeEvent(e,c[b]);}}}return this;}});this.Options=new Class({setOptions:function(){var b=this.options=Object.merge.apply(null,[{},this.options].append(arguments));
    +if(this.addEvent){for(var c in b){if(typeOf(b[c])!="function"||!(/^on[A-Z]/).test(c)){continue;}this.addEvent(c,b[c]);delete b[c];}}return this;}});})();
    +(function(){var k,n,l,g,a={},c={},m=/\\/g;var e=function(q,p){if(q==null){return null;}if(q.Slick===true){return q;}q=(""+q).replace(/^\s+|\s+$/g,"");g=!!p;
    +var o=(g)?c:a;if(o[q]){return o[q];}k={Slick:true,expressions:[],raw:q,reverse:function(){return e(this.raw,true);}};n=-1;while(q!=(q=q.replace(j,b))){}k.length=k.expressions.length;
    +return o[k.raw]=(g)?h(k):k;};var i=function(o){if(o==="!"){return" ";}else{if(o===" "){return"!";}else{if((/^!/).test(o)){return o.replace(/^!/,"");}else{return"!"+o;
    +}}}};var h=function(u){var r=u.expressions;for(var p=0;p<r.length;p++){var t=r[p];var q={parts:[],tag:"*",combinator:i(t[0].combinator)};for(var o=0;o<t.length;
    +o++){var s=t[o];if(!s.reverseCombinator){s.reverseCombinator=" ";}s.combinator=s.reverseCombinator;delete s.reverseCombinator;}t.reverse().push(q);}return u;
    +};var f=function(o){return o.replace(/[-[\]{}()*+?.\\^$|,#\s]/g,function(p){return"\\"+p;});};var j=new RegExp("^(?:\\s*(,)\\s*|\\s*(<combinator>+)\\s*|(\\s+)|(<unicode>+|\\*)|\\#(<unicode>+)|\\.(<unicode>+)|\\[\\s*(<unicode1>+)(?:\\s*([*^$!~|]?=)(?:\\s*(?:([\"']?)(.*?)\\9)))?\\s*\\](?!\\])|(:+)(<unicode>+)(?:\\((?:(?:([\"'])([^\\13]*)\\13)|((?:\\([^)]+\\)|[^()]*)+))\\))?)".replace(/<combinator>/,"["+f(">+~`!@$%^&={}\\;</")+"]").replace(/<unicode>/g,"(?:[\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])").replace(/<unicode1>/g,"(?:[:\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])"));
    +function b(x,s,D,z,r,C,q,B,A,y,u,F,G,v,p,w){if(s||n===-1){k.expressions[++n]=[];l=-1;if(s){return"";}}if(D||z||l===-1){D=D||" ";var t=k.expressions[n];
    +if(g&&t[l]){t[l].reverseCombinator=i(D);}t[++l]={combinator:D,tag:"*"};}var o=k.expressions[n][l];if(r){o.tag=r.replace(m,"");}else{if(C){o.id=C.replace(m,"");
    +}else{if(q){q=q.replace(m,"");if(!o.classList){o.classList=[];}if(!o.classes){o.classes=[];}o.classList.push(q);o.classes.push({value:q,regexp:new RegExp("(^|\\s)"+f(q)+"(\\s|$)")});
    +}else{if(G){w=w||p;w=w?w.replace(m,""):null;if(!o.pseudos){o.pseudos=[];}o.pseudos.push({key:G.replace(m,""),value:w,type:F.length==1?"class":"element"});
    +}else{if(B){B=B.replace(m,"");u=(u||"").replace(m,"");var E,H;switch(A){case"^=":H=new RegExp("^"+f(u));break;case"$=":H=new RegExp(f(u)+"$");break;case"~=":H=new RegExp("(^|\\s)"+f(u)+"(\\s|$)");
    +break;case"|=":H=new RegExp("^"+f(u)+"(-|$)");break;case"=":E=function(I){return u==I;};break;case"*=":E=function(I){return I&&I.indexOf(u)>-1;};break;
    +case"!=":E=function(I){return u!=I;};break;default:E=function(I){return !!I;};}if(u==""&&(/^[*$^]=$/).test(A)){E=function(){return false;};}if(!E){E=function(I){return I&&H.test(I);
    +};}if(!o.attributes){o.attributes=[];}o.attributes.push({key:B,operator:A,value:u,test:E});}}}}}return"";}var d=(this.Slick||{});d.parse=function(o){return e(o);
    +};d.escapeRegExp=f;if(!this.Slick){this.Slick=d;}}).apply((typeof exports!="undefined")?exports:this);(function(){var j={},l={},b=Object.prototype.toString;
    +j.isNativeCode=function(c){return(/\{\s*\[native code\]\s*\}/).test(""+c);};j.isXML=function(c){return(!!c.xmlVersion)||(!!c.xml)||(b.call(c)=="[object XMLDocument]")||(c.nodeType==9&&c.documentElement.nodeName!="HTML");
    +};j.setDocument=function(w){var t=w.nodeType;if(t==9){}else{if(t){w=w.ownerDocument;}else{if(w.navigator){w=w.document;}else{return;}}}if(this.document===w){return;
    +}this.document=w;var y=w.documentElement,u=this.getUIDXML(y),o=l[u],A;if(o){for(A in o){this[A]=o[A];}return;}o=l[u]={};o.root=y;o.isXMLDocument=this.isXML(w);
    +o.brokenStarGEBTN=o.starSelectsClosedQSA=o.idGetsName=o.brokenMixedCaseQSA=o.brokenGEBCN=o.brokenCheckedQSA=o.brokenEmptyAttributeQSA=o.isHTMLDocument=o.nativeMatchesSelector=false;
    +var m,n,x,q,r;var s,c="slick_uniqueid";var z=w.createElement("div");var p=w.body||w.getElementsByTagName("body")[0]||y;p.appendChild(z);try{z.innerHTML='<a id="'+c+'"></a>';
    +o.isHTMLDocument=!!w.getElementById(c);}catch(v){}if(o.isHTMLDocument){z.style.display="none";z.appendChild(w.createComment(""));n=(z.getElementsByTagName("*").length>1);
    +try{z.innerHTML="foo</foo>";s=z.getElementsByTagName("*");m=(s&&!!s.length&&s[0].nodeName.charAt(0)=="/");}catch(v){}o.brokenStarGEBTN=n||m;try{z.innerHTML='<a name="'+c+'"></a><b id="'+c+'"></b>';
    +o.idGetsName=w.getElementById(c)===z.firstChild;}catch(v){}if(z.getElementsByClassName){try{z.innerHTML='<a class="f"></a><a class="b"></a>';z.getElementsByClassName("b").length;
    +z.firstChild.className="b";q=(z.getElementsByClassName("b").length!=2);}catch(v){}try{z.innerHTML='<a class="a"></a><a class="f b a"></a>';x=(z.getElementsByClassName("a").length!=2);
    +}catch(v){}o.brokenGEBCN=q||x;}if(z.querySelectorAll){try{z.innerHTML="foo</foo>";s=z.querySelectorAll("*");o.starSelectsClosedQSA=(s&&!!s.length&&s[0].nodeName.charAt(0)=="/");
    +}catch(v){}try{z.innerHTML='<a class="MiX"></a>';o.brokenMixedCaseQSA=!z.querySelectorAll(".MiX").length;}catch(v){}try{z.innerHTML='<select><option selected="selected">a</option></select>';
    +o.brokenCheckedQSA=(z.querySelectorAll(":checked").length==0);}catch(v){}try{z.innerHTML='<a class=""></a>';o.brokenEmptyAttributeQSA=(z.querySelectorAll('[class*=""]').length!=0);
    +}catch(v){}}try{z.innerHTML='<form action="s"><input id="action"/></form>';r=(z.firstChild.getAttribute("action")!="s");}catch(v){}o.nativeMatchesSelector=y.matchesSelector||y.mozMatchesSelector||y.webkitMatchesSelector;
    +if(o.nativeMatchesSelector){try{o.nativeMatchesSelector.call(y,":slick");o.nativeMatchesSelector=null;}catch(v){}}}try{y.slick_expando=1;delete y.slick_expando;
    +o.getUID=this.getUIDHTML;}catch(v){o.getUID=this.getUIDXML;}p.removeChild(z);z=s=p=null;o.getAttribute=(o.isHTMLDocument&&r)?function(D,B){var E=this.attributeGetters[B];
    +if(E){return E.call(D);}var C=D.getAttributeNode(B);return(C)?C.nodeValue:null;}:function(C,B){var D=this.attributeGetters[B];return(D)?D.call(C):C.getAttribute(B);
    +};o.hasAttribute=(y&&this.isNativeCode(y.hasAttribute))?function(C,B){return C.hasAttribute(B);}:function(C,B){C=C.getAttributeNode(B);return !!(C&&(C.specified||C.nodeValue));
    +};o.contains=(y&&this.isNativeCode(y.contains))?function(B,C){return B.contains(C);}:(y&&y.compareDocumentPosition)?function(B,C){return B===C||!!(B.compareDocumentPosition(C)&16);
    +}:function(B,C){if(C){do{if(C===B){return true;}}while((C=C.parentNode));}return false;};o.documentSorter=(y.compareDocumentPosition)?function(C,B){if(!C.compareDocumentPosition||!B.compareDocumentPosition){return 0;
    +}return C.compareDocumentPosition(B)&4?-1:C===B?0:1;}:("sourceIndex" in y)?function(C,B){if(!C.sourceIndex||!B.sourceIndex){return 0;}return C.sourceIndex-B.sourceIndex;
    +}:(w.createRange)?function(E,C){if(!E.ownerDocument||!C.ownerDocument){return 0;}var D=E.ownerDocument.createRange(),B=C.ownerDocument.createRange();D.setStart(E,0);
    +D.setEnd(E,0);B.setStart(C,0);B.setEnd(C,0);return D.compareBoundaryPoints(Range.START_TO_END,B);}:null;y=null;for(A in o){this[A]=o[A];}};var e=/^([#.]?)((?:[\w-]+|\*))$/,g=/\[.+[*$^]=(?:""|'')?\]/,f={};
    +j.search=function(U,z,H,s){var p=this.found=(s)?null:(H||[]);if(!U){return p;}else{if(U.navigator){U=U.document;}else{if(!U.nodeType){return p;}}}var F,O,V=this.uniques={},I=!!(H&&H.length),y=(U.nodeType==9);
    +if(this.document!==(y?U:U.ownerDocument)){this.setDocument(U);}if(I){for(O=p.length;O--;){V[this.getUID(p[O])]=true;}}if(typeof z=="string"){var r=z.match(e);
    +simpleSelectors:if(r){var u=r[1],v=r[2],A,E;if(!u){if(v=="*"&&this.brokenStarGEBTN){break simpleSelectors;}E=U.getElementsByTagName(v);if(s){return E[0]||null;
    +}for(O=0;A=E[O++];){if(!(I&&V[this.getUID(A)])){p.push(A);}}}else{if(u=="#"){if(!this.isHTMLDocument||!y){break simpleSelectors;}A=U.getElementById(v);
    +if(!A){return p;}if(this.idGetsName&&A.getAttributeNode("id").nodeValue!=v){break simpleSelectors;}if(s){return A||null;}if(!(I&&V[this.getUID(A)])){p.push(A);
    +}}else{if(u=="."){if(!this.isHTMLDocument||((!U.getElementsByClassName||this.brokenGEBCN)&&U.querySelectorAll)){break simpleSelectors;}if(U.getElementsByClassName&&!this.brokenGEBCN){E=U.getElementsByClassName(v);
    +if(s){return E[0]||null;}for(O=0;A=E[O++];){if(!(I&&V[this.getUID(A)])){p.push(A);}}}else{var T=new RegExp("(^|\\s)"+d.escapeRegExp(v)+"(\\s|$)");E=U.getElementsByTagName("*");
    +for(O=0;A=E[O++];){className=A.className;if(!(className&&T.test(className))){continue;}if(s){return A;}if(!(I&&V[this.getUID(A)])){p.push(A);}}}}}}if(I){this.sort(p);
    +}return(s)?null:p;}querySelector:if(U.querySelectorAll){if(!this.isHTMLDocument||f[z]||this.brokenMixedCaseQSA||(this.brokenCheckedQSA&&z.indexOf(":checked")>-1)||(this.brokenEmptyAttributeQSA&&g.test(z))||(!y&&z.indexOf(",")>-1)||d.disableQSA){break querySelector;
    +}var S=z,x=U;if(!y){var C=x.getAttribute("id"),t="slickid__";x.setAttribute("id",t);S="#"+t+" "+S;U=x.parentNode;}try{if(s){return U.querySelector(S)||null;
    +}else{E=U.querySelectorAll(S);}}catch(Q){f[z]=1;break querySelector;}finally{if(!y){if(C){x.setAttribute("id",C);}else{x.removeAttribute("id");}U=x;}}if(this.starSelectsClosedQSA){for(O=0;
    +A=E[O++];){if(A.nodeName>"@"&&!(I&&V[this.getUID(A)])){p.push(A);}}}else{for(O=0;A=E[O++];){if(!(I&&V[this.getUID(A)])){p.push(A);}}}if(I){this.sort(p);
    +}return p;}F=this.Slick.parse(z);if(!F.length){return p;}}else{if(z==null){return p;}else{if(z.Slick){F=z;}else{if(this.contains(U.documentElement||U,z)){(p)?p.push(z):p=z;
    +return p;}else{return p;}}}}this.posNTH={};this.posNTHLast={};this.posNTHType={};this.posNTHTypeLast={};this.push=(!I&&(s||(F.length==1&&F.expressions[0].length==1)))?this.pushArray:this.pushUID;
    +if(p==null){p=[];}var M,L,K;var B,J,D,c,q,G,W;var N,P,o,w,R=F.expressions;search:for(O=0;(P=R[O]);O++){for(M=0;(o=P[M]);M++){B="combinator:"+o.combinator;
    +if(!this[B]){continue search;}J=(this.isXMLDocument)?o.tag:o.tag.toUpperCase();D=o.id;c=o.classList;q=o.classes;G=o.attributes;W=o.pseudos;w=(M===(P.length-1));
    +this.bitUniques={};if(w){this.uniques=V;this.found=p;}else{this.uniques={};this.found=[];}if(M===0){this[B](U,J,D,q,G,W,c);if(s&&w&&p.length){break search;
    +}}else{if(s&&w){for(L=0,K=N.length;L<K;L++){this[B](N[L],J,D,q,G,W,c);if(p.length){break search;}}}else{for(L=0,K=N.length;L<K;L++){this[B](N[L],J,D,q,G,W,c);
    +}}}N=this.found;}}if(I||(F.expressions.length>1)){this.sort(p);}return(s)?(p[0]||null):p;};j.uidx=1;j.uidk="slick-uniqueid";j.getUIDXML=function(m){var c=m.getAttribute(this.uidk);
    +if(!c){c=this.uidx++;m.setAttribute(this.uidk,c);}return c;};j.getUIDHTML=function(c){return c.uniqueNumber||(c.uniqueNumber=this.uidx++);};j.sort=function(c){if(!this.documentSorter){return c;
    +}c.sort(this.documentSorter);return c;};j.cacheNTH={};j.matchNTH=/^([+-]?\d*)?([a-z]+)?([+-]\d+)?$/;j.parseNTHArgument=function(p){var n=p.match(this.matchNTH);
    +if(!n){return false;}var o=n[2]||false;var m=n[1]||1;if(m=="-"){m=-1;}var c=+n[3]||0;n=(o=="n")?{a:m,b:c}:(o=="odd")?{a:2,b:1}:(o=="even")?{a:2,b:0}:{a:0,b:m};
    +return(this.cacheNTH[p]=n);};j.createNTHPseudo=function(o,m,c,n){return function(r,p){var t=this.getUID(r);if(!this[c][t]){var z=r.parentNode;if(!z){return false;
    +}var q=z[o],s=1;if(n){var y=r.nodeName;do{if(q.nodeName!=y){continue;}this[c][this.getUID(q)]=s++;}while((q=q[m]));}else{do{if(q.nodeType!=1){continue;
    +}this[c][this.getUID(q)]=s++;}while((q=q[m]));}}p=p||"n";var u=this.cacheNTH[p]||this.parseNTHArgument(p);if(!u){return false;}var x=u.a,w=u.b,v=this[c][t];
    +if(x==0){return w==v;}if(x>0){if(v<w){return false;}}else{if(w<v){return false;}}return((v-w)%x)==0;};};j.pushArray=function(o,c,q,n,m,p){if(this.matchSelector(o,c,q,n,m,p)){this.found.push(o);
    +}};j.pushUID=function(p,c,r,o,m,q){var n=this.getUID(p);if(!this.uniques[n]&&this.matchSelector(p,c,r,o,m,q)){this.uniques[n]=true;this.found.push(p);}};
    +j.matchNode=function(m,n){if(this.isHTMLDocument&&this.nativeMatchesSelector){try{return this.nativeMatchesSelector.call(m,n.replace(/\[([^=]+)=\s*([^'"\]]+?)\s*\]/g,'[$1="$2"]'));
    +}catch(u){}}var t=this.Slick.parse(n);if(!t){return true;}var r=t.expressions,p,s=0,q;for(q=0;(currentExpression=r[q]);q++){if(currentExpression.length==1){var o=currentExpression[0];
    +if(this.matchSelector(m,(this.isXMLDocument)?o.tag:o.tag.toUpperCase(),o.id,o.classes,o.attributes,o.pseudos)){return true;}s++;}}if(s==t.length){return false;
    +}var c=this.search(this.document,t),v;for(q=0;v=c[q++];){if(v===m){return true;}}return false;};j.matchPseudo=function(p,c,o){var m="pseudo:"+c;if(this[m]){return this[m](p,o);
    +}var n=this.getAttribute(p,c);return(o)?o==n:!!n;};j.matchSelector=function(n,u,c,o,p,r){if(u){var s=(this.isXMLDocument)?n.nodeName:n.nodeName.toUpperCase();
    +if(u=="*"){if(s<"@"){return false;}}else{if(s!=u){return false;}}}if(c&&n.getAttribute("id")!=c){return false;}var q,m,t;if(o){for(q=o.length;q--;){t=n.getAttribute("class")||n.className;
    +if(!(t&&o[q].regexp.test(t))){return false;}}}if(p){for(q=p.length;q--;){m=p[q];if(m.operator?!m.test(this.getAttribute(n,m.key)):!this.hasAttribute(n,m.key)){return false;
    +}}}if(r){for(q=r.length;q--;){m=r[q];if(!this.matchPseudo(n,m.key,m.value)){return false;}}}return true;};var i={" ":function(p,v,m,q,r,t,o){var s,u,n;
    +if(this.isHTMLDocument){getById:if(m){u=this.document.getElementById(m);if((!u&&p.all)||(this.idGetsName&&u&&u.getAttributeNode("id").nodeValue!=m)){n=p.all[m];
    +if(!n){return;}if(!n[0]){n=[n];}for(s=0;u=n[s++];){var c=u.getAttributeNode("id");if(c&&c.nodeValue==m){this.push(u,v,null,q,r,t);break;}}return;}if(!u){if(this.contains(this.root,p)){return;
    +}else{break getById;}}else{if(this.document!==p&&!this.contains(p,u)){return;}}this.push(u,v,null,q,r,t);return;}getByClass:if(q&&p.getElementsByClassName&&!this.brokenGEBCN){n=p.getElementsByClassName(o.join(" "));
    +if(!(n&&n.length)){break getByClass;}for(s=0;u=n[s++];){this.push(u,v,m,null,r,t);}return;}}getByTag:{n=p.getElementsByTagName(v);if(!(n&&n.length)){break getByTag;
    +}if(!this.brokenStarGEBTN){v=null;}for(s=0;u=n[s++];){this.push(u,v,m,q,r,t);}}},">":function(o,c,q,n,m,p){if((o=o.firstChild)){do{if(o.nodeType==1){this.push(o,c,q,n,m,p);
    +}}while((o=o.nextSibling));}},"+":function(o,c,q,n,m,p){while((o=o.nextSibling)){if(o.nodeType==1){this.push(o,c,q,n,m,p);break;}}},"^":function(o,c,q,n,m,p){o=o.firstChild;
    +if(o){if(o.nodeType==1){this.push(o,c,q,n,m,p);}else{this["combinator:+"](o,c,q,n,m,p);}}},"~":function(p,c,r,o,m,q){while((p=p.nextSibling)){if(p.nodeType!=1){continue;
    +}var n=this.getUID(p);if(this.bitUniques[n]){break;}this.bitUniques[n]=true;this.push(p,c,r,o,m,q);}},"++":function(o,c,q,n,m,p){this["combinator:+"](o,c,q,n,m,p);
    +this["combinator:!+"](o,c,q,n,m,p);},"~~":function(o,c,q,n,m,p){this["combinator:~"](o,c,q,n,m,p);this["combinator:!~"](o,c,q,n,m,p);},"!":function(o,c,q,n,m,p){while((o=o.parentNode)){if(o!==this.document){this.push(o,c,q,n,m,p);
    +}}},"!>":function(o,c,q,n,m,p){o=o.parentNode;if(o!==this.document){this.push(o,c,q,n,m,p);}},"!+":function(o,c,q,n,m,p){while((o=o.previousSibling)){if(o.nodeType==1){this.push(o,c,q,n,m,p);
    +break;}}},"!^":function(o,c,q,n,m,p){o=o.lastChild;if(o){if(o.nodeType==1){this.push(o,c,q,n,m,p);}else{this["combinator:!+"](o,c,q,n,m,p);}}},"!~":function(p,c,r,o,m,q){while((p=p.previousSibling)){if(p.nodeType!=1){continue;
    +}var n=this.getUID(p);if(this.bitUniques[n]){break;}this.bitUniques[n]=true;this.push(p,c,r,o,m,q);}}};for(var h in i){j["combinator:"+h]=i[h];}var k={empty:function(c){var m=c.firstChild;
    +return !(m&&m.nodeType==1)&&!(c.innerText||c.textContent||"").length;},not:function(c,m){return !this.matchNode(c,m);},contains:function(c,m){return(c.innerText||c.textContent||"").indexOf(m)>-1;
    +},"first-child":function(c){while((c=c.previousSibling)){if(c.nodeType==1){return false;}}return true;},"last-child":function(c){while((c=c.nextSibling)){if(c.nodeType==1){return false;
    +}}return true;},"only-child":function(n){var m=n;while((m=m.previousSibling)){if(m.nodeType==1){return false;}}var c=n;while((c=c.nextSibling)){if(c.nodeType==1){return false;
    +}}return true;},"nth-child":j.createNTHPseudo("firstChild","nextSibling","posNTH"),"nth-last-child":j.createNTHPseudo("lastChild","previousSibling","posNTHLast"),"nth-of-type":j.createNTHPseudo("firstChild","nextSibling","posNTHType",true),"nth-last-of-type":j.createNTHPseudo("lastChild","previousSibling","posNTHTypeLast",true),index:function(m,c){return this["pseudo:nth-child"](m,""+c+1);
    +},even:function(c){return this["pseudo:nth-child"](c,"2n");},odd:function(c){return this["pseudo:nth-child"](c,"2n+1");},"first-of-type":function(c){var m=c.nodeName;
    +while((c=c.previousSibling)){if(c.nodeName==m){return false;}}return true;},"last-of-type":function(c){var m=c.nodeName;while((c=c.nextSibling)){if(c.nodeName==m){return false;
    +}}return true;},"only-of-type":function(n){var m=n,o=n.nodeName;while((m=m.previousSibling)){if(m.nodeName==o){return false;}}var c=n;while((c=c.nextSibling)){if(c.nodeName==o){return false;
    +}}return true;},enabled:function(c){return !c.disabled;},disabled:function(c){return c.disabled;},checked:function(c){return c.checked||c.selected;},focus:function(c){return this.isHTMLDocument&&this.document.activeElement===c&&(c.href||c.type||this.hasAttribute(c,"tabindex"));
    +},root:function(c){return(c===this.root);},selected:function(c){return c.selected;}};for(var a in k){j["pseudo:"+a]=k[a];}j.attributeGetters={"class":function(){return this.getAttribute("class")||this.className;
    +},"for":function(){return("htmlFor" in this)?this.htmlFor:this.getAttribute("for");},href:function(){return("href" in this)?this.getAttribute("href",2):this.getAttribute("href");
    +},style:function(){return(this.style)?this.style.cssText:this.getAttribute("style");},tabindex:function(){var c=this.getAttributeNode("tabindex");return(c&&c.specified)?c.nodeValue:null;
    +},type:function(){return this.getAttribute("type");}};var d=j.Slick=(this.Slick||{});d.version="1.1.5";d.search=function(m,n,c){return j.search(m,n,c);
    +};d.find=function(c,m){return j.search(c,m,null,true);};d.contains=function(c,m){j.setDocument(c);return j.contains(c,m);};d.getAttribute=function(m,c){return j.getAttribute(m,c);
    +};d.match=function(m,c){if(!(m&&c)){return false;}if(!c||c===m){return true;}j.setDocument(m);return j.matchNode(m,c);};d.defineAttributeGetter=function(c,m){j.attributeGetters[c]=m;
    +return this;};d.lookupAttributeGetter=function(c){return j.attributeGetters[c];};d.definePseudo=function(c,m){j["pseudo:"+c]=function(o,n){return m.call(o,n);
    +};return this;};d.lookupPseudo=function(c){var m=j["pseudo:"+c];if(m){return function(n){return m.call(this,n);};}return null;};d.override=function(m,c){j.override(m,c);
    +return this;};d.isXML=j.isXML;d.uidOf=function(c){return j.getUIDHTML(c);};if(!this.Slick){this.Slick=d;}}).apply((typeof exports!="undefined")?exports:this);
    +var Element=function(b,g){var h=Element.Constructors[b];if(h){return h(g);}if(typeof b!="string"){return document.id(b).set(g);}if(!g){g={};}if(!(/^[\w-]+$/).test(b)){var e=Slick.parse(b).expressions[0][0];
    +b=(e.tag=="*")?"div":e.tag;if(e.id&&g.id==null){g.id=e.id;}var d=e.attributes;if(d){for(var f=0,c=d.length;f<c;f++){var a=d[f];if(g[a.key]!=null){continue;
    +}if(a.value!=null&&a.operator=="="){g[a.key]=a.value;}else{if(!a.value&&!a.operator){g[a.key]=true;}}}}if(e.classList&&g["class"]==null){g["class"]=e.classList.join(" ");
    +}}return document.newElement(b,g);};if(Browser.Element){Element.prototype=Browser.Element.prototype;}new Type("Element",Element).mirror(function(a){if(Array.prototype[a]){return;
    +}var b={};b[a]=function(){var h=[],e=arguments,j=true;for(var g=0,d=this.length;g<d;g++){var f=this[g],c=h[g]=f[a].apply(f,e);j=(j&&typeOf(c)=="element");
    +}return(j)?new Elements(h):h;};Elements.implement(b);});if(!Browser.Element){Element.parent=Object;Element.Prototype={"$family":Function.from("element").hide()};
    +Element.mirror(function(a,b){Element.Prototype[a]=b;});}Element.Constructors={};Element.Constructors=new Hash;var IFrame=new Type("IFrame",function(){var e=Array.link(arguments,{properties:Type.isObject,iframe:function(f){return(f!=null);
    +}});var c=e.properties||{},b;if(e.iframe){b=document.id(e.iframe);}var d=c.onload||function(){};delete c.onload;c.id=c.name=[c.id,c.name,b?(b.id||b.name):"IFrame_"+String.uniqueID()].pick();
    +b=new Element(b||"iframe",c);var a=function(){d.call(b.contentWindow);};if(window.frames[c.id]){a();}else{b.addListener("load",a);}return b;});var Elements=this.Elements=function(a){if(a&&a.length){var e={},d;
    +for(var c=0;d=a[c++];){var b=Slick.uidOf(d);if(!e[b]){e[b]=true;this.push(d);}}}};Elements.prototype={length:0};Elements.parent=Array;new Type("Elements",Elements).implement({filter:function(a,b){if(!a){return this;
    +}return new Elements(Array.filter(this,(typeOf(a)=="string")?function(c){return c.match(a);}:a,b));}.protect(),push:function(){var d=this.length;for(var b=0,a=arguments.length;
    +b<a;b++){var c=document.id(arguments[b]);if(c){this[d++]=c;}}return(this.length=d);}.protect(),unshift:function(){var b=[];for(var c=0,a=arguments.length;
    +c<a;c++){var d=document.id(arguments[c]);if(d){b.push(d);}}return Array.prototype.unshift.apply(this,b);}.protect(),concat:function(){var b=new Elements(this);
    +for(var c=0,a=arguments.length;c<a;c++){var d=arguments[c];if(Type.isEnumerable(d)){b.append(d);}else{b.push(d);}}return b;}.protect(),append:function(c){for(var b=0,a=c.length;
    +b<a;b++){this.push(c[b]);}return this;}.protect(),empty:function(){while(this.length){delete this[--this.length];}return this;}.protect()});Elements.alias("extend","append");
    +(function(){var g=Array.prototype.splice,b={"0":0,"1":1,length:2};g.call(b,1,1);if(b[1]==1){Elements.implement("splice",function(){var e=this.length;g.apply(this,arguments);
    +while(e>=this.length){delete this[e--];}return this;}.protect());}Elements.implement(Array.prototype);Array.mirror(Elements);var f;try{var a=document.createElement("<input name=x>");
    +f=(a.name=="x");}catch(c){}var d=function(e){return(""+e).replace(/&/g,"&amp;").replace(/"/g,"&quot;");};Document.implement({newElement:function(e,h){if(h&&h.checked!=null){h.defaultChecked=h.checked;
    +}if(f&&h){e="<"+e;if(h.name){e+=' name="'+d(h.name)+'"';}if(h.type){e+=' type="'+d(h.type)+'"';}e+=">";delete h.name;delete h.type;}return this.id(this.createElement(e)).set(h);
    +}});})();Document.implement({newTextNode:function(a){return this.createTextNode(a);},getDocument:function(){return this;},getWindow:function(){return this.window;
    +},id:(function(){var a={string:function(d,c,b){d=Slick.find(b,"#"+d.replace(/(\W)/g,"\\$1"));return(d)?a.element(d,c):null;},element:function(b,c){$uid(b);
    +if(!c&&!b.$family&&!(/^(?:object|embed)$/i).test(b.tagName)){Object.append(b,Element.Prototype);}return b;},object:function(c,d,b){if(c.toElement){return a.element(c.toElement(b),d);
    +}return null;}};a.textnode=a.whitespace=a.window=a.document=function(b){return b;};return function(c,e,d){if(c&&c.$family&&c.uid){return c;}var b=typeOf(c);
    +return(a[b])?a[b](c,e,d||document):null;};})()});if(window.$==null){Window.implement("$",function(a,b){return document.id(a,b,this.document);});}Window.implement({getDocument:function(){return this.document;
    +},getWindow:function(){return this;}});[Document,Element].invoke("implement",{getElements:function(a){return Slick.search(this,a,new Elements);},getElement:function(a){return document.id(Slick.find(this,a));
    +}});(function(b,d,a){this.Selectors={};var e=this.Selectors.Pseudo=new Hash();var c=function(){for(var f in e){if(e.hasOwnProperty(f)){Slick.definePseudo(f,e[f]);
    +delete e[f];}}};Slick.search=function(g,h,f){c();return b.call(this,g,h,f);};Slick.find=function(f,g){c();return d.call(this,f,g);};Slick.match=function(g,f){c();
    +return a.call(this,g,f);};})(Slick.search,Slick.find,Slick.match);if(window.$$==null){Window.implement("$$",function(a){var f=new Elements;if(arguments.length==1&&typeof a=="string"){return Slick.search(this.document,a,f);
    +}var c=Array.flatten(arguments);for(var d=0,b=c.length;d<b;d++){var e=c[d];switch(typeOf(e)){case"element":f.push(e);break;case"string":Slick.search(this.document,e,f);
    +}}return f;});}if(window.$$==null){Window.implement("$$",function(a){if(arguments.length==1){if(typeof a=="string"){return Slick.search(this.document,a,new Elements);
    +}else{if(Type.isEnumerable(a)){return new Elements(a);}}}return new Elements(arguments);});}(function(){var k={},i={};var n={input:"checked",option:"selected",textarea:"value"};
    +var e=function(p){return(i[p]||(i[p]={}));};var j=function(q){var p=q.uid;if(q.removeEvents){q.removeEvents();}if(q.clearAttributes){q.clearAttributes();
    +}if(p!=null){delete k[p];delete i[p];}return q;};var o=["defaultValue","accessKey","cellPadding","cellSpacing","colSpan","frameBorder","maxLength","readOnly","rowSpan","tabIndex","useMap"];
    +var d=["compact","nowrap","ismap","declare","noshade","checked","disabled","readOnly","multiple","selected","noresize","defer","defaultChecked"];var g={html:"innerHTML","class":"className","for":"htmlFor",text:(function(){var p=document.createElement("div");
    +return(p.textContent==null)?"innerText":"textContent";})()};var m=["type"];var h=["value","defaultValue"];var l=/^(?:href|src|usemap)$/i;d=d.associate(d);
    +o=o.associate(o.map(String.toLowerCase));m=m.associate(m);Object.append(g,h.associate(h));var c={before:function(q,p){var r=p.parentNode;if(r){r.insertBefore(q,p);
    +}},after:function(q,p){var r=p.parentNode;if(r){r.insertBefore(q,p.nextSibling);}},bottom:function(q,p){p.appendChild(q);},top:function(q,p){p.insertBefore(q,p.firstChild);
    +}};c.inside=c.bottom;Object.each(c,function(q,r){r=r.capitalize();var p={};p["inject"+r]=function(s){q(this,document.id(s,true));return this;};p["grab"+r]=function(s){q(document.id(s,true),this);
    +return this;};Element.implement(p);});var b=function(s,r){if(!s){return r;}s=Object.clone(Slick.parse(s));var q=s.expressions;for(var p=q.length;p--;){q[p][0].combinator=r;
    +}return s;};Element.implement({set:function(r,q){var p=Element.Properties[r];(p&&p.set)?p.set.call(this,q):this.setProperty(r,q);}.overloadSetter(),get:function(q){var p=Element.Properties[q];
    +return(p&&p.get)?p.get.apply(this):this.getProperty(q);}.overloadGetter(),erase:function(q){var p=Element.Properties[q];(p&&p.erase)?p.erase.apply(this):this.removeProperty(q);
    +return this;},setProperty:function(q,r){q=o[q]||q;if(r==null){return this.removeProperty(q);}var p=g[q];(p)?this[p]=r:(d[q])?this[q]=!!r:this.setAttribute(q,""+r);
    +return this;},setProperties:function(p){for(var q in p){this.setProperty(q,p[q]);}return this;},getProperty:function(q){q=o[q]||q;var p=g[q]||m[q];return(p)?this[p]:(d[q])?!!this[q]:(l.test(q)?this.getAttribute(q,2):(p=this.getAttributeNode(q))?p.nodeValue:null)||null;
    +},getProperties:function(){var p=Array.from(arguments);return p.map(this.getProperty,this).associate(p);},removeProperty:function(q){q=o[q]||q;var p=g[q];
    +(p)?this[p]="":(d[q])?this[q]=false:this.removeAttribute(q);return this;},removeProperties:function(){Array.each(arguments,this.removeProperty,this);return this;
    +},hasClass:function(p){return this.className.clean().contains(p," ");},addClass:function(p){if(!this.hasClass(p)){this.className=(this.className+" "+p).clean();
    +}return this;},removeClass:function(p){this.className=this.className.replace(new RegExp("(^|\\s)"+p+"(?:\\s|$)"),"$1");return this;},toggleClass:function(p,q){if(q==null){q=!this.hasClass(p);
    +}return(q)?this.addClass(p):this.removeClass(p);},adopt:function(){var s=this,p,u=Array.flatten(arguments),t=u.length;if(t>1){s=p=document.createDocumentFragment();
    +}for(var r=0;r<t;r++){var q=document.id(u[r],true);if(q){s.appendChild(q);}}if(p){this.appendChild(p);}return this;},appendText:function(q,p){return this.grab(this.getDocument().newTextNode(q),p);
    +},grab:function(q,p){c[p||"bottom"](document.id(q,true),this);return this;},inject:function(q,p){c[p||"bottom"](this,document.id(q,true));return this;},replaces:function(p){p=document.id(p,true);
    +p.parentNode.replaceChild(this,p);return this;},wraps:function(q,p){q=document.id(q,true);return this.replaces(q).grab(q,p);},getPrevious:function(p){return document.id(Slick.find(this,b(p,"!~")));
    +},getAllPrevious:function(p){return Slick.search(this,b(p,"!~"),new Elements);},getNext:function(p){return document.id(Slick.find(this,b(p,"~")));},getAllNext:function(p){return Slick.search(this,b(p,"~"),new Elements);
    +},getFirst:function(p){return document.id(Slick.search(this,b(p,">"))[0]);},getLast:function(p){return document.id(Slick.search(this,b(p,">")).getLast());
    +},getParent:function(p){return document.id(Slick.find(this,b(p,"!")));},getParents:function(p){return Slick.search(this,b(p,"!"),new Elements);},getSiblings:function(p){return Slick.search(this,b(p,"~~"),new Elements);
    +},getChildren:function(p){return Slick.search(this,b(p,">"),new Elements);},getWindow:function(){return this.ownerDocument.window;},getDocument:function(){return this.ownerDocument;
    +},getElementById:function(p){return document.id(Slick.find(this,"#"+(""+p).replace(/(\W)/g,"\\$1")));},getSelected:function(){this.selectedIndex;return new Elements(Array.from(this.options).filter(function(p){return p.selected;
    +}));},toQueryString:function(){var p=[];this.getElements("input, select, textarea").each(function(r){var q=r.type;if(!r.name||r.disabled||q=="submit"||q=="reset"||q=="file"||q=="image"){return;
    +}var s=(r.get("tag")=="select")?r.getSelected().map(function(t){return document.id(t).get("value");}):((q=="radio"||q=="checkbox")&&!r.checked)?null:r.get("value");
    +Array.from(s).each(function(t){if(typeof t!="undefined"){p.push(encodeURIComponent(r.name)+"="+encodeURIComponent(t));}});});return p.join("&");},destroy:function(){var p=j(this).getElementsByTagName("*");
    +Array.each(p,j);Element.dispose(this);return null;},empty:function(){Array.from(this.childNodes).each(Element.dispose);return this;},dispose:function(){return(this.parentNode)?this.parentNode.removeChild(this):this;
    +},match:function(p){return !p||Slick.match(this,p);}});var a=function(t,s,q){if(!q){t.setAttributeNode(document.createAttribute("id"));}if(t.clearAttributes){t.clearAttributes();
    +t.mergeAttributes(s);t.removeAttribute("uid");if(t.options){var u=t.options,p=s.options;for(var r=u.length;r--;){u[r].selected=p[r].selected;}}}var v=n[s.tagName.toLowerCase()];
    +if(v&&s[v]){t[v]=s[v];}};Element.implement("clone",function(r,p){r=r!==false;var w=this.cloneNode(r),q;if(r){var s=w.getElementsByTagName("*"),u=this.getElementsByTagName("*");
    +for(q=s.length;q--;){a(s[q],u[q],p);}}a(w,this,p);if(Browser.ie){var t=w.getElementsByTagName("object"),v=this.getElementsByTagName("object");for(q=t.length;
    +q--;){t[q].outerHTML=v[q].outerHTML;}}return document.id(w);});var f={contains:function(p){return Slick.contains(this,p);}};if(!document.contains){Document.implement(f);
    +}if(!document.createElement("div").contains){Element.implement(f);}Element.implement("hasChild",function(p){return this!==p&&this.contains(p);});[Element,Window,Document].invoke("implement",{addListener:function(s,r){if(s=="unload"){var p=r,q=this;
    +r=function(){q.removeListener("unload",r);p();};}else{k[$uid(this)]=this;}if(this.addEventListener){this.addEventListener(s,r,!!arguments[2]);}else{this.attachEvent("on"+s,r);
    +}return this;},removeListener:function(q,p){if(this.removeEventListener){this.removeEventListener(q,p,!!arguments[2]);}else{this.detachEvent("on"+q,p);
    +}return this;},retrieve:function(q,p){var s=e($uid(this)),r=s[q];if(p!=null&&r==null){r=s[q]=p;}return r!=null?r:null;},store:function(q,p){var r=e($uid(this));
    +r[q]=p;return this;},eliminate:function(p){var q=e($uid(this));delete q[p];return this;}});if(window.attachEvent&&!window.addEventListener){window.addListener("unload",function(){Object.each(k,j);
    +if(window.CollectGarbage){CollectGarbage();}});}})();Element.Properties={};Element.Properties=new Hash;Element.Properties.style={set:function(a){this.style.cssText=a;
    +},get:function(){return this.style.cssText;},erase:function(){this.style.cssText="";}};Element.Properties.tag={get:function(){return this.tagName.toLowerCase();
    +}};(function(a){if(a!=null){Element.Properties.maxlength=Element.Properties.maxLength={get:function(){var b=this.getAttribute("maxLength");return b==a?null:b;
    +}};}})(document.createElement("input").getAttribute("maxLength"));Element.Properties.html=(function(){var c=Function.attempt(function(){var e=document.createElement("table");
    +e.innerHTML="<tr><td></td></tr>";});var d=document.createElement("div");var a={table:[1,"<table>","</table>"],select:[1,"<select>","</select>"],tbody:[2,"<table><tbody>","</tbody></table>"],tr:[3,"<table><tbody><tr>","</tr></tbody></table>"]};
    +a.thead=a.tfoot=a.tbody;var b={set:function(){var f=Array.flatten(arguments).join("");var g=(!c&&a[this.get("tag")]);if(g){var h=d;h.innerHTML=g[1]+f+g[2];
    +for(var e=g[0];e--;){h=h.firstChild;}this.empty().adopt(h.childNodes);}else{this.innerHTML=f;}}};b.erase=b.set;return b;})();(function(){var c=document.html;
    +Element.Properties.styles={set:function(f){this.setStyles(f);}};var e=(c.style.opacity!=null);var d=/alpha\(opacity=([\d.]+)\)/i;var b=function(g,f){if(!g.currentStyle||!g.currentStyle.hasLayout){g.style.zoom=1;
    +}if(e){g.style.opacity=f;}else{f=(f*100).limit(0,100).round();f=(f==100)?"":"alpha(opacity="+f+")";var h=g.style.filter||g.getComputedStyle("filter")||"";
    +g.style.filter=d.test(h)?h.replace(d,f):h+f;}};Element.Properties.opacity={set:function(g){var f=this.style.visibility;if(g==0&&f!="hidden"){this.style.visibility="hidden";
    +}else{if(g!=0&&f!="visible"){this.style.visibility="visible";}}b(this,g);},get:(e)?function(){var f=this.style.opacity||this.getComputedStyle("opacity");
    +return(f=="")?1:f;}:function(){var f,g=(this.style.filter||this.getComputedStyle("filter"));if(g){f=g.match(d);}return(f==null||g==null)?1:(f[1]/100);}};
    +var a=(c.style.cssFloat==null)?"styleFloat":"cssFloat";Element.implement({getComputedStyle:function(h){if(this.currentStyle){return this.currentStyle[h.camelCase()];
    +}var g=Element.getDocument(this).defaultView,f=g?g.getComputedStyle(this,null):null;return(f)?f.getPropertyValue((h==a)?"float":h.hyphenate()):null;},setOpacity:function(f){b(this,f);
    +return this;},getOpacity:function(){return this.get("opacity");},setStyle:function(g,f){switch(g){case"opacity":return this.set("opacity",parseFloat(f));
    +case"float":g=a;}g=g.camelCase();if(typeOf(f)!="string"){var h=(Element.Styles[g]||"@").split(" ");f=Array.from(f).map(function(k,j){if(!h[j]){return"";
    +}return(typeOf(k)=="number")?h[j].replace("@",Math.round(k)):k;}).join(" ");}else{if(f==String(Number(f))){f=Math.round(f);}}this.style[g]=f;return this;
    +},getStyle:function(l){switch(l){case"opacity":return this.get("opacity");case"float":l=a;}l=l.camelCase();var f=this.style[l];if(!f||l=="zIndex"){f=[];
    +for(var k in Element.ShortStyles){if(l!=k){continue;}for(var j in Element.ShortStyles[k]){f.push(this.getStyle(j));}return f.join(" ");}f=this.getComputedStyle(l);
    +}if(f){f=String(f);var h=f.match(/rgba?\([\d\s,]+\)/);if(h){f=f.replace(h[0],h[0].rgbToHex());}}if(Browser.opera||(Browser.ie&&isNaN(parseFloat(f)))){if((/^(height|width)$/).test(l)){var g=(l=="width")?["left","right"]:["top","bottom"],i=0;
    +g.each(function(m){i+=this.getStyle("border-"+m+"-width").toInt()+this.getStyle("padding-"+m).toInt();},this);return this["offset"+l.capitalize()]-i+"px";
    +}if(Browser.opera&&String(f).indexOf("px")!=-1){return f;}if((/^border(.+)Width|margin|padding/).test(l)){return"0px";}}return f;},setStyles:function(g){for(var f in g){this.setStyle(f,g[f]);
    +}return this;},getStyles:function(){var f={};Array.flatten(arguments).each(function(g){f[g]=this.getStyle(g);},this);return f;}});Element.Styles={left:"@px",top:"@px",bottom:"@px",right:"@px",width:"@px",height:"@px",maxWidth:"@px",maxHeight:"@px",minWidth:"@px",minHeight:"@px",backgroundColor:"rgb(@, @, @)",backgroundPosition:"@px @px",color:"rgb(@, @, @)",fontSize:"@px",letterSpacing:"@px",lineHeight:"@px",clip:"rect(@px @px @px @px)",margin:"@px @px @px @px",padding:"@px @px @px @px",border:"@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)",borderWidth:"@px @px @px @px",borderStyle:"@ @ @ @",borderColor:"rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)",zIndex:"@",zoom:"@",fontWeight:"@",textIndent:"@px",opacity:"@"};
    +Element.Styles=new Hash(Element.Styles);Element.ShortStyles={margin:{},padding:{},border:{},borderWidth:{},borderStyle:{},borderColor:{}};["Top","Right","Bottom","Left"].each(function(l){var k=Element.ShortStyles;
    +var g=Element.Styles;["margin","padding"].each(function(m){var n=m+l;k[m][n]=g[n]="@px";});var j="border"+l;k.border[j]=g[j]="@px @ rgb(@, @, @)";var i=j+"Width",f=j+"Style",h=j+"Color";
    +k[j]={};k.borderWidth[i]=k[j][i]=g[i]="@px";k.borderStyle[f]=k[j][f]=g[f]="@";k.borderColor[h]=k[j][h]=g[h]="rgb(@, @, @)";});})();(function(){Element.Properties.events={set:function(b){this.addEvents(b);
    +}};[Element,Window,Document].invoke("implement",{addEvent:function(f,h){var i=this.retrieve("events",{});if(!i[f]){i[f]={keys:[],values:[]};}if(i[f].keys.contains(h)){return this;
    +}i[f].keys.push(h);var g=f,b=Element.Events[f],d=h,j=this;if(b){if(b.onAdd){b.onAdd.call(this,h);}if(b.condition){d=function(k){if(b.condition.call(this,k)){return h.call(this,k);
    +}return true;};}g=b.base||g;}var e=function(){return h.call(j);};var c=Element.NativeEvents[g];if(c){if(c==2){e=function(k){k=new Event(k,j.getWindow());
    +if(d.call(j,k)===false){k.stop();}};}this.addListener(g,e,arguments[2]);}i[f].values.push(e);return this;},removeEvent:function(e,d){var c=this.retrieve("events");
    +if(!c||!c[e]){return this;}var h=c[e];var b=h.keys.indexOf(d);if(b==-1){return this;}var g=h.values[b];delete h.keys[b];delete h.values[b];var f=Element.Events[e];
    +if(f){if(f.onRemove){f.onRemove.call(this,d);}e=f.base||e;}return(Element.NativeEvents[e])?this.removeListener(e,g,arguments[2]):this;},addEvents:function(b){for(var c in b){this.addEvent(c,b[c]);
    +}return this;},removeEvents:function(b){var d;if(typeOf(b)=="object"){for(d in b){this.removeEvent(d,b[d]);}return this;}var c=this.retrieve("events");
    +if(!c){return this;}if(!b){for(d in c){this.removeEvents(d);}this.eliminate("events");}else{if(c[b]){c[b].keys.each(function(e){this.removeEvent(b,e);},this);
    +delete c[b];}}return this;},fireEvent:function(e,c,b){var d=this.retrieve("events");if(!d||!d[e]){return this;}c=Array.from(c);d[e].keys.each(function(f){if(b){f.delay(b,this,c);
    +}else{f.apply(this,c);}},this);return this;},cloneEvents:function(e,d){e=document.id(e);var c=e.retrieve("events");if(!c){return this;}if(!d){for(var b in c){this.cloneEvents(e,b);
    +}}else{if(c[d]){c[d].keys.each(function(f){this.addEvent(d,f);},this);}}return this;}});Element.NativeEvents={click:2,dblclick:2,mouseup:2,mousedown:2,contextmenu:2,mousewheel:2,DOMMouseScroll:2,mouseover:2,mouseout:2,mousemove:2,selectstart:2,selectend:2,keydown:2,keypress:2,keyup:2,orientationchange:2,touchstart:2,touchmove:2,touchend:2,touchcancel:2,gesturestart:2,gesturechange:2,gestureend:2,focus:2,blur:2,change:2,reset:2,select:2,submit:2,load:2,unload:1,beforeunload:2,resize:1,move:1,DOMContentLoaded:1,readystatechange:1,error:1,abort:1,scroll:1};
    +var a=function(b){var c=b.relatedTarget;if(c==null){return true;}if(!c){return false;}return(c!=this&&c.prefix!="xul"&&typeOf(this)!="document"&&!this.contains(c));
    +};Element.Events={mouseenter:{base:"mouseover",condition:a},mouseleave:{base:"mouseout",condition:a},mousewheel:{base:(Browser.firefox)?"DOMMouseScroll":"mousewheel"}};
    +Element.Events=new Hash(Element.Events);})();(function(){var h=document.createElement("div"),e=document.createElement("div");h.style.height="0";h.appendChild(e);
    +var d=(e.offsetParent===h);h=e=null;var l=function(m){return k(m,"position")!="static"||a(m);};var i=function(m){return l(m)||(/^(?:table|td|th)$/i).test(m.tagName);
    +};Element.implement({scrollTo:function(m,n){if(a(this)){this.getWindow().scrollTo(m,n);}else{this.scrollLeft=m;this.scrollTop=n;}return this;},getSize:function(){if(a(this)){return this.getWindow().getSize();
    +}return{x:this.offsetWidth,y:this.offsetHeight};},getScrollSize:function(){if(a(this)){return this.getWindow().getScrollSize();}return{x:this.scrollWidth,y:this.scrollHeight};
    +},getScroll:function(){if(a(this)){return this.getWindow().getScroll();}return{x:this.scrollLeft,y:this.scrollTop};},getScrolls:function(){var n=this.parentNode,m={x:0,y:0};
    +while(n&&!a(n)){m.x+=n.scrollLeft;m.y+=n.scrollTop;n=n.parentNode;}return m;},getOffsetParent:d?function(){var m=this;if(a(m)||k(m,"position")=="fixed"){return null;
    +}var n=(k(m,"position")=="static")?i:l;while((m=m.parentNode)){if(n(m)){return m;}}return null;}:function(){var m=this;if(a(m)||k(m,"position")=="fixed"){return null;
    +}try{return m.offsetParent;}catch(n){}return null;},getOffsets:function(){if(this.getBoundingClientRect&&!Browser.Platform.ios){var r=this.getBoundingClientRect(),o=document.id(this.getDocument().documentElement),q=o.getScroll(),t=this.getScrolls(),s=(k(this,"position")=="fixed");
    +return{x:r.left.toInt()+t.x+((s)?0:q.x)-o.clientLeft,y:r.top.toInt()+t.y+((s)?0:q.y)-o.clientTop};}var n=this,m={x:0,y:0};if(a(this)){return m;}while(n&&!a(n)){m.x+=n.offsetLeft;
    +m.y+=n.offsetTop;if(Browser.firefox){if(!c(n)){m.x+=b(n);m.y+=g(n);}var p=n.parentNode;if(p&&k(p,"overflow")!="visible"){m.x+=b(p);m.y+=g(p);}}else{if(n!=this&&Browser.safari){m.x+=b(n);
    +m.y+=g(n);}}n=n.offsetParent;}if(Browser.firefox&&!c(this)){m.x-=b(this);m.y-=g(this);}return m;},getPosition:function(p){if(a(this)){return{x:0,y:0};}var q=this.getOffsets(),n=this.getScrolls();
    +var m={x:q.x-n.x,y:q.y-n.y};if(p&&(p=document.id(p))){var o=p.getPosition();return{x:m.x-o.x-b(p),y:m.y-o.y-g(p)};}return m;},getCoordinates:function(o){if(a(this)){return this.getWindow().getCoordinates();
    +}var m=this.getPosition(o),n=this.getSize();var p={left:m.x,top:m.y,width:n.x,height:n.y};p.right=p.left+p.width;p.bottom=p.top+p.height;return p;},computePosition:function(m){return{left:m.x-j(this,"margin-left"),top:m.y-j(this,"margin-top")};
    +},setPosition:function(m){return this.setStyles(this.computePosition(m));}});[Document,Window].invoke("implement",{getSize:function(){var m=f(this);return{x:m.clientWidth,y:m.clientHeight};
    +},getScroll:function(){var n=this.getWindow(),m=f(this);return{x:n.pageXOffset||m.scrollLeft,y:n.pageYOffset||m.scrollTop};},getScrollSize:function(){var o=f(this),n=this.getSize(),m=this.getDocument().body;
    +return{x:Math.max(o.scrollWidth,m.scrollWidth,n.x),y:Math.max(o.scrollHeight,m.scrollHeight,n.y)};},getPosition:function(){return{x:0,y:0};},getCoordinates:function(){var m=this.getSize();
    +return{top:0,left:0,bottom:m.y,right:m.x,height:m.y,width:m.x};}});var k=Element.getComputedStyle;function j(m,n){return k(m,n).toInt()||0;}function c(m){return k(m,"-moz-box-sizing")=="border-box";
    +}function g(m){return j(m,"border-top-width");}function b(m){return j(m,"border-left-width");}function a(m){return(/^(?:body|html)$/i).test(m.tagName);
    +}function f(m){var n=m.getDocument();return(!n.compatMode||n.compatMode=="CSS1Compat")?n.html:n.body;}})();Element.alias({position:"setPosition"});[Window,Document,Element].invoke("implement",{getHeight:function(){return this.getSize().y;
    +},getWidth:function(){return this.getSize().x;},getScrollTop:function(){return this.getScroll().y;},getScrollLeft:function(){return this.getScroll().x;
    +},getScrollHeight:function(){return this.getScrollSize().y;},getScrollWidth:function(){return this.getScrollSize().x;},getTop:function(){return this.getPosition().y;
    +},getLeft:function(){return this.getPosition().x;}});(function(){var f=this.Fx=new Class({Implements:[Chain,Events,Options],options:{fps:60,unit:false,duration:500,frames:null,frameSkip:true,link:"ignore"},initialize:function(g){this.subject=this.subject||this;
    +this.setOptions(g);},getTransition:function(){return function(g){return -(Math.cos(Math.PI*g)-1)/2;};},step:function(g){if(this.options.frameSkip){var h=(this.time!=null)?(g-this.time):0,i=h/this.frameInterval;
    +this.time=g;this.frame+=i;}else{this.frame++;}if(this.frame<this.frames){var j=this.transition(this.frame/this.frames);this.set(this.compute(this.from,this.to,j));
    +}else{this.frame=this.frames;this.set(this.compute(this.from,this.to,1));this.stop();}},set:function(g){return g;},compute:function(i,h,g){return f.compute(i,h,g);
    +},check:function(){if(!this.isRunning()){return true;}switch(this.options.link){case"cancel":this.cancel();return true;case"chain":this.chain(this.caller.pass(arguments,this));
    +return false;}return false;},start:function(k,j){if(!this.check(k,j)){return this;}this.from=k;this.to=j;this.frame=(this.options.frameSkip)?0:-1;this.time=null;
    +this.transition=this.getTransition();var i=this.options.frames,h=this.options.fps,g=this.options.duration;this.duration=f.Durations[g]||g.toInt();this.frameInterval=1000/h;
    +this.frames=i||Math.round(this.duration/this.frameInterval);this.fireEvent("start",this.subject);b.call(this,h);return this;},stop:function(){if(this.isRunning()){this.time=null;
    +d.call(this,this.options.fps);if(this.frames==this.frame){this.fireEvent("complete",this.subject);if(!this.callChain()){this.fireEvent("chainComplete",this.subject);
    +}}else{this.fireEvent("stop",this.subject);}}return this;},cancel:function(){if(this.isRunning()){this.time=null;d.call(this,this.options.fps);this.frame=this.frames;
    +this.fireEvent("cancel",this.subject).clearChain();}return this;},pause:function(){if(this.isRunning()){this.time=null;d.call(this,this.options.fps);}return this;
    +},resume:function(){if((this.frame<this.frames)&&!this.isRunning()){b.call(this,this.options.fps);}return this;},isRunning:function(){var g=e[this.options.fps];
    +return g&&g.contains(this);}});f.compute=function(i,h,g){return(h-i)*g+i;};f.Durations={"short":250,normal:500,"long":1000};var e={},c={};var a=function(){var h=Date.now();
    +for(var j=this.length;j--;){var g=this[j];if(g){g.step(h);}}};var b=function(h){var g=e[h]||(e[h]=[]);g.push(this);if(!c[h]){c[h]=a.periodical(Math.round(1000/h),g);
    +}};var d=function(h){var g=e[h];if(g){g.erase(this);if(!g.length&&c[h]){delete e[h];c[h]=clearInterval(c[h]);}}};})();Fx.CSS=new Class({Extends:Fx,prepare:function(c,d,b){b=Array.from(b);
    +if(b[1]==null){b[1]=b[0];b[0]=c.getStyle(d);}var a=b.map(this.parse);return{from:a[0],to:a[1]};},parse:function(a){a=Function.from(a)();a=(typeof a=="string")?a.split(" "):Array.from(a);
    +return a.map(function(c){c=String(c);var b=false;Object.each(Fx.CSS.Parsers,function(f,e){if(b){return;}var d=f.parse(c);if(d||d===0){b={value:d,parser:f};
    +}});b=b||{value:c,parser:Fx.CSS.Parsers.String};return b;});},compute:function(d,c,b){var a=[];(Math.min(d.length,c.length)).times(function(e){a.push({value:d[e].parser.compute(d[e].value,c[e].value,b),parser:d[e].parser});
    +});a.$family=Function.from("fx:css:value");return a;},serve:function(c,b){if(typeOf(c)!="fx:css:value"){c=this.parse(c);}var a=[];c.each(function(d){a=a.concat(d.parser.serve(d.value,b));
    +});return a;},render:function(a,d,c,b){a.setStyle(d,this.serve(c,b));},search:function(a){if(Fx.CSS.Cache[a]){return Fx.CSS.Cache[a];}var c={},b=new RegExp("^"+a.escapeRegExp()+"$");
    +Array.each(document.styleSheets,function(f,e){var d=f.href;if(d&&d.contains("://")&&!d.contains(document.domain)){return;}var g=f.rules||f.cssRules;Array.each(g,function(k,h){if(!k.style){return;
    +}var j=(k.selectorText)?k.selectorText.replace(/^\w+/,function(i){return i.toLowerCase();}):null;if(!j||!b.test(j)){return;}Object.each(Element.Styles,function(l,i){if(!k.style[i]||Element.ShortStyles[i]){return;
    +}l=String(k.style[i]);c[i]=((/^rgb/).test(l))?l.rgbToHex():l;});});});return Fx.CSS.Cache[a]=c;}});Fx.CSS.Cache={};Fx.CSS.Parsers={Color:{parse:function(a){if(a.match(/^#[0-9a-f]{3,6}$/i)){return a.hexToRgb(true);
    +}return((a=a.match(/(\d+),\s*(\d+),\s*(\d+)/)))?[a[1],a[2],a[3]]:false;},compute:function(c,b,a){return c.map(function(e,d){return Math.round(Fx.compute(c[d],b[d],a));
    +});},serve:function(a){return a.map(Number);}},Number:{parse:parseFloat,compute:Fx.compute,serve:function(b,a){return(a)?b+a:b;}},String:{parse:Function.from(false),compute:function(b,a){return a;
    +},serve:function(a){return a;}}};Fx.CSS.Parsers=new Hash(Fx.CSS.Parsers);Fx.Tween=new Class({Extends:Fx.CSS,initialize:function(b,a){this.element=this.subject=document.id(b);
    +this.parent(a);},set:function(b,a){if(arguments.length==1){a=b;b=this.property||this.options.property;}this.render(this.element,b,a,this.options.unit);
    +return this;},start:function(c,e,d){if(!this.check(c,e,d)){return this;}var b=Array.flatten(arguments);this.property=this.options.property||b.shift();var a=this.prepare(this.element,this.property,b);
    +return this.parent(a.from,a.to);}});Element.Properties.tween={set:function(a){this.get("tween").cancel().setOptions(a);return this;},get:function(){var a=this.retrieve("tween");
    +if(!a){a=new Fx.Tween(this,{link:"cancel"});this.store("tween",a);}return a;}};Element.implement({tween:function(a,c,b){this.get("tween").start(arguments);
    +return this;},fade:function(c){var e=this.get("tween"),d="opacity",a;c=[c,"toggle"].pick();switch(c){case"in":e.start(d,1);break;case"out":e.start(d,0);
    +break;case"show":e.set(d,1);break;case"hide":e.set(d,0);break;case"toggle":var b=this.retrieve("fade:flag",this.get("opacity")==1);e.start(d,(b)?0:1);this.store("fade:flag",!b);
    +a=true;break;default:e.start(d,arguments);}if(!a){this.eliminate("fade:flag");}return this;},highlight:function(c,a){if(!a){a=this.retrieve("highlight:original",this.getStyle("background-color"));
    +a=(a=="transparent")?"#fff":a;}var b=this.get("tween");b.start("background-color",c||"#ffff88",a).chain(function(){this.setStyle("background-color",this.retrieve("highlight:original"));
    +b.callChain();}.bind(this));return this;}});Fx.Morph=new Class({Extends:Fx.CSS,initialize:function(b,a){this.element=this.subject=document.id(b);this.parent(a);
    +},set:function(a){if(typeof a=="string"){a=this.search(a);}for(var b in a){this.render(this.element,b,a[b],this.options.unit);}return this;},compute:function(e,d,c){var a={};
    +for(var b in e){a[b]=this.parent(e[b],d[b],c);}return a;},start:function(b){if(!this.check(b)){return this;}if(typeof b=="string"){b=this.search(b);}var e={},d={};
    +for(var c in b){var a=this.prepare(this.element,c,b[c]);e[c]=a.from;d[c]=a.to;}return this.parent(e,d);}});Element.Properties.morph={set:function(a){this.get("morph").cancel().setOptions(a);
    +return this;},get:function(){var a=this.retrieve("morph");if(!a){a=new Fx.Morph(this,{link:"cancel"});this.store("morph",a);}return a;}};Element.implement({morph:function(a){this.get("morph").start(a);
    +return this;}});Fx.implement({getTransition:function(){var a=this.options.transition||Fx.Transitions.Sine.easeInOut;if(typeof a=="string"){var b=a.split(":");
    +a=Fx.Transitions;a=a[b[0]]||a[b[0].capitalize()];if(b[1]){a=a["ease"+b[1].capitalize()+(b[2]?b[2].capitalize():"")];}}return a;}});Fx.Transition=function(c,b){b=Array.from(b);
    +var a=function(d){return c(d,b);};return Object.append(a,{easeIn:a,easeOut:function(d){return 1-c(1-d,b);},easeInOut:function(d){return(d<=0.5?c(2*d,b):(2-c(2*(1-d),b)))/2;
    +}});};Fx.Transitions={linear:function(a){return a;}};Fx.Transitions=new Hash(Fx.Transitions);Fx.Transitions.extend=function(a){for(var b in a){Fx.Transitions[b]=new Fx.Transition(a[b]);
    +}};Fx.Transitions.extend({Pow:function(b,a){return Math.pow(b,a&&a[0]||6);},Expo:function(a){return Math.pow(2,8*(a-1));},Circ:function(a){return 1-Math.sin(Math.acos(a));
    +},Sine:function(a){return 1-Math.cos(a*Math.PI/2);},Back:function(b,a){a=a&&a[0]||1.618;return Math.pow(b,2)*((a+1)*b-a);},Bounce:function(f){var e;for(var d=0,c=1;
    +1;d+=c,c/=2){if(f>=(7-4*d)/11){e=c*c-Math.pow((11-6*d-11*f)/4,2);break;}}return e;},Elastic:function(b,a){return Math.pow(2,10*--b)*Math.cos(20*b*Math.PI*(a&&a[0]||1)/3);
    +}});["Quad","Cubic","Quart","Quint"].each(function(b,a){Fx.Transitions[b]=new Fx.Transition(function(c){return Math.pow(c,a+2);});});(function(){var d=function(){},a=("onprogress" in new Browser.Request);
    +var c=this.Request=new Class({Implements:[Chain,Events,Options],options:{url:"",data:"",headers:{"X-Requested-With":"XMLHttpRequest",Accept:"text/javascript, text/html, application/xml, text/xml, */*"},async:true,format:false,method:"post",link:"ignore",isSuccess:null,emulation:true,urlEncoded:true,encoding:"utf-8",evalScripts:false,evalResponse:false,timeout:0,noCache:false},initialize:function(e){this.xhr=new Browser.Request();
    +this.setOptions(e);this.headers=this.options.headers;},onStateChange:function(){var e=this.xhr;if(e.readyState!=4||!this.running){return;}this.running=false;
    +this.status=0;Function.attempt(function(){var f=e.status;this.status=(f==1223)?204:f;}.bind(this));e.onreadystatechange=d;if(a){e.onprogress=e.onloadstart=d;
    +}clearTimeout(this.timer);this.response={text:this.xhr.responseText||"",xml:this.xhr.responseXML};if(this.options.isSuccess.call(this,this.status)){this.success(this.response.text,this.response.xml);
    +}else{this.failure();}},isSuccess:function(){var e=this.status;return(e>=200&&e<300);},isRunning:function(){return !!this.running;},processScripts:function(e){if(this.options.evalResponse||(/(ecma|java)script/).test(this.getHeader("Content-type"))){return Browser.exec(e);
    +}return e.stripScripts(this.options.evalScripts);},success:function(f,e){this.onSuccess(this.processScripts(f),e);},onSuccess:function(){this.fireEvent("complete",arguments).fireEvent("success",arguments).callChain();
    +},failure:function(){this.onFailure();},onFailure:function(){this.fireEvent("complete").fireEvent("failure",this.xhr);},loadstart:function(e){this.fireEvent("loadstart",[e,this.xhr]);
    +},progress:function(e){this.fireEvent("progress",[e,this.xhr]);},timeout:function(){this.fireEvent("timeout",this.xhr);},setHeader:function(e,f){this.headers[e]=f;
    +return this;},getHeader:function(e){return Function.attempt(function(){return this.xhr.getResponseHeader(e);}.bind(this));},check:function(){if(!this.running){return true;
    +}switch(this.options.link){case"cancel":this.cancel();return true;case"chain":this.chain(this.caller.pass(arguments,this));return false;}return false;},send:function(o){if(!this.check(o)){return this;
    +}this.options.isSuccess=this.options.isSuccess||this.isSuccess;this.running=true;var l=typeOf(o);if(l=="string"||l=="element"){o={data:o};}var h=this.options;
    +o=Object.append({data:h.data,url:h.url,method:h.method},o);var j=o.data,f=String(o.url),e=o.method.toLowerCase();switch(typeOf(j)){case"element":j=document.id(j).toQueryString();
    +break;case"object":case"hash":j=Object.toQueryString(j);}if(this.options.format){var m="format="+this.options.format;j=(j)?m+"&"+j:m;}if(this.options.emulation&&!["get","post"].contains(e)){var k="_method="+e;
    +j=(j)?k+"&"+j:k;e="post";}if(this.options.urlEncoded&&["post","put"].contains(e)){var g=(this.options.encoding)?"; charset="+this.options.encoding:"";this.headers["Content-type"]="application/x-www-form-urlencoded"+g;
    +}if(!f){f=document.location.pathname;}var i=f.lastIndexOf("/");if(i>-1&&(i=f.indexOf("#"))>-1){f=f.substr(0,i);}if(this.options.noCache){f+=(f.contains("?")?"&":"?")+String.uniqueID();
    +}if(j&&e=="get"){f+=(f.contains("?")?"&":"?")+j;j=null;}var n=this.xhr;if(a){n.onloadstart=this.loadstart.bind(this);n.onprogress=this.progress.bind(this);
    +}n.open(e.toUpperCase(),f,this.options.async,this.options.user,this.options.password);if(this.options.user&&"withCredentials" in n){n.withCredentials=true;
    +}n.onreadystatechange=this.onStateChange.bind(this);Object.each(this.headers,function(q,p){try{n.setRequestHeader(p,q);}catch(r){this.fireEvent("exception",[p,q]);
    +}},this);this.fireEvent("request");n.send(j);if(!this.options.async){this.onStateChange();}if(this.options.timeout){this.timer=this.timeout.delay(this.options.timeout,this);
    +}return this;},cancel:function(){if(!this.running){return this;}this.running=false;var e=this.xhr;e.abort();clearTimeout(this.timer);e.onreadystatechange=d;
    +if(a){e.onprogress=e.onloadstart=d;}this.xhr=new Browser.Request();this.fireEvent("cancel");return this;}});var b={};["get","post","put","delete","GET","POST","PUT","DELETE"].each(function(e){b[e]=function(g){var f={method:e};
    +if(g!=null){f.data=g;}return this.send(f);};});c.implement(b);Element.Properties.send={set:function(e){var f=this.get("send").cancel();f.setOptions(e);
    +return this;},get:function(){var e=this.retrieve("send");if(!e){e=new c({data:this,link:"cancel",method:this.get("method")||"post",url:this.get("action")});
    +this.store("send",e);}return e;}};Element.implement({send:function(e){var f=this.get("send");f.send({data:this,url:e||f.options.url});return this;}});})();
    +Request.HTML=new Class({Extends:Request,options:{update:false,append:false,evalScripts:true,filter:false,headers:{Accept:"text/html, application/xml, text/xml, */*"}},success:function(e){var d=this.options,b=this.response;
    +b.html=e.stripScripts(function(f){b.javascript=f;});var c=b.html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);if(c){b.html=c[1];}var a=new Element("div").set("html",b.html);
    +b.tree=a.childNodes;b.elements=a.getElements("*");if(d.filter){b.tree=b.elements.filter(d.filter);}if(d.update){document.id(d.update).empty().set("html",b.html);
    +}else{if(d.append){document.id(d.append).adopt(a.getChildren());}}if(d.evalScripts){Browser.exec(b.javascript);}this.onSuccess(b.tree,b.elements,b.html,b.javascript);
    +}});Element.Properties.load={set:function(a){var b=this.get("load").cancel();b.setOptions(a);return this;},get:function(){var a=this.retrieve("load");if(!a){a=new Request.HTML({data:this,link:"cancel",update:this,method:"get"});
    +this.store("load",a);}return a;}};Element.implement({load:function(){this.get("load").send(Array.link(arguments,{data:Type.isObject,url:Type.isString}));
    +return this;}});if(typeof JSON=="undefined"){this.JSON={};}JSON=new Hash({stringify:JSON.stringify,parse:JSON.parse});(function(){var special={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"};
    +var escape=function(chr){return special[chr]||"\\u"+("0000"+chr.charCodeAt(0).toString(16)).slice(-4);};JSON.validate=function(string){string=string.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"");
    +return(/^[\],:{}\s]*$/).test(string);};JSON.encode=JSON.stringify?function(obj){return JSON.stringify(obj);}:function(obj){if(obj&&obj.toJSON){obj=obj.toJSON();
    +}switch(typeOf(obj)){case"string":return'"'+obj.replace(/[\x00-\x1f\\"]/g,escape)+'"';case"array":return"["+obj.map(JSON.encode).clean()+"]";case"object":case"hash":var string=[];
    +Object.each(obj,function(value,key){var json=JSON.encode(value);if(json){string.push(JSON.encode(key)+":"+json);}});return"{"+string+"}";case"number":case"boolean":return""+obj;
    +case"null":return"null";}return null;};JSON.decode=function(string,secure){if(!string||typeOf(string)!="string"){return null;}if(secure||JSON.secure){if(JSON.parse){return JSON.parse(string);
    +}if(!JSON.validate(string)){throw new Error("JSON could not decode the input; security is enabled and the value is not secure.");}}return eval("("+string+")");
    +};})();Request.JSON=new Class({Extends:Request,options:{secure:true},initialize:function(a){this.parent(a);Object.append(this.headers,{Accept:"application/json","X-Request":"JSON"});
    +},success:function(c){var b;try{b=this.response.json=JSON.decode(c,this.options.secure);}catch(a){this.fireEvent("error",[c,a]);return;}if(b==null){this.onFailure();
    +}else{this.onSuccess(b,c);}}});var Cookie=new Class({Implements:Options,options:{path:"/",domain:false,duration:false,secure:false,document:document,encode:true},initialize:function(b,a){this.key=b;
    +this.setOptions(a);},write:function(b){if(this.options.encode){b=encodeURIComponent(b);}if(this.options.domain){b+="; domain="+this.options.domain;}if(this.options.path){b+="; path="+this.options.path;
    +}if(this.options.duration){var a=new Date();a.setTime(a.getTime()+this.options.duration*24*60*60*1000);b+="; expires="+a.toGMTString();}if(this.options.secure){b+="; secure";
    +}this.options.document.cookie=this.key+"="+b;return this;},read:function(){var a=this.options.document.cookie.match("(?:^|;)\\s*"+this.key.escapeRegExp()+"=([^;]*)");
    +return(a)?decodeURIComponent(a[1]):null;},dispose:function(){new Cookie(this.key,Object.merge({},this.options,{duration:-1})).write("");return this;}});
    +Cookie.write=function(b,c,a){return new Cookie(b,a).write(c);};Cookie.read=function(a){return new Cookie(a).read();};Cookie.dispose=function(b,a){return new Cookie(b,a).dispose();
    +};(function(i,k){var l,f,e=[],c,b,d=k.createElement("div");var g=function(){clearTimeout(b);if(l){return;}Browser.loaded=l=true;k.removeListener("DOMContentLoaded",g).removeListener("readystatechange",a);
    +k.fireEvent("domready");i.fireEvent("domready");};var a=function(){for(var m=e.length;m--;){if(e[m]()){g();return true;}}return false;};var j=function(){clearTimeout(b);
    +if(!a()){b=setTimeout(j,10);}};k.addListener("DOMContentLoaded",g);var h=function(){try{d.doScroll();return true;}catch(m){}return false;};if(d.doScroll&&!h()){e.push(h);
    +c=true;}if(k.readyState){e.push(function(){var m=k.readyState;return(m=="loaded"||m=="complete");});}if("onreadystatechange" in k){k.addListener("readystatechange",a);
    +}else{c=true;}if(c){j();}Element.Events.domready={onAdd:function(m){if(l){m.call(this);}}};Element.Events.load={base:"load",onAdd:function(m){if(f&&this==i){m.call(this);
    +}},condition:function(){if(this==i){g();delete Element.Events.load;}return true;}};i.addEvent("load",function(){f=true;});})(window,document);(function(){var Swiff=this.Swiff=new Class({Implements:Options,options:{id:null,height:1,width:1,container:null,properties:{},params:{quality:"high",allowScriptAccess:"always",wMode:"window",swLiveConnect:true},callBacks:{},vars:{}},toElement:function(){return this.object;
    +},initialize:function(path,options){this.instance="Swiff_"+String.uniqueID();this.setOptions(options);options=this.options;var id=this.id=options.id||this.instance;
    +var container=document.id(options.container);Swiff.CallBacks[this.instance]={};var params=options.params,vars=options.vars,callBacks=options.callBacks;
    +var properties=Object.append({height:options.height,width:options.width},options.properties);var self=this;for(var callBack in callBacks){Swiff.CallBacks[this.instance][callBack]=(function(option){return function(){return option.apply(self.object,arguments);
    +};})(callBacks[callBack]);vars[callBack]="Swiff.CallBacks."+this.instance+"."+callBack;}params.flashVars=Object.toQueryString(vars);if(Browser.ie){properties.classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000";
    +params.movie=path;}else{properties.type="application/x-shockwave-flash";}properties.data=path;var build='<object id="'+id+'"';for(var property in properties){build+=" "+property+'="'+properties[property]+'"';
    +}build+=">";for(var param in params){if(params[param]){build+='<param name="'+param+'" value="'+params[param]+'" />';}}build+="</object>";this.object=((container)?container.empty():new Element("div")).set("html",build).firstChild;
    +},replaces:function(element){element=document.id(element,true);element.parentNode.replaceChild(this.toElement(),element);return this;},inject:function(element){document.id(element,true).appendChild(this.toElement());
    +return this;},remote:function(){return Swiff.remote.apply(Swiff,[this.toElement()].append(arguments));}});Swiff.CallBacks={};Swiff.remote=function(obj,fn){var rs=obj.CallFunction('<invoke name="'+fn+'" returntype="javascript">'+__flash__argumentsToXML(arguments,2)+"</invoke>");
    +return eval(rs);};})();
    \ No newline at end of file
    diff --git a/lib/mootools-1.3.2.1-more.js b/lib/mootools-1.3.2.1-more.js
    new file mode 100644
    index 000000000..78a833393
    --- /dev/null
    +++ b/lib/mootools-1.3.2.1-more.js
    @@ -0,0 +1,46 @@
    +// MooTools: the javascript framework.
    +// Load this file's selection again by visiting: http://mootools.net/more/1d2dc9d3a7450af66a62d7ff13dd3b98 
    +// Or build this file again with packager using: packager build More/More More/Drag.Move
    +/*
    +---
    +copyrights:
    +  - [MooTools](http://mootools.net)
    +
    +licenses:
    +  - [MIT License](http://mootools.net/license.txt)
    +...
    +*/
    +MooTools.More={version:"1.3.2.1",build:"e586bcd2496e9b22acfde32e12f84d49ce09e59d"};var Drag=new Class({Implements:[Events,Options],options:{snap:6,unit:"px",grid:false,style:true,limit:false,handle:false,invert:false,preventDefault:false,stopPropagation:false,modifiers:{x:"left",y:"top"}},initialize:function(){var b=Array.link(arguments,{options:Type.isObject,element:function(c){return c!=null;
    +}});this.element=document.id(b.element);this.document=this.element.getDocument();this.setOptions(b.options||{});var a=typeOf(this.options.handle);this.handles=((a=="array"||a=="collection")?$$(this.options.handle):document.id(this.options.handle))||this.element;
    +this.mouse={now:{},pos:{}};this.value={start:{},now:{}};this.selection=(Browser.ie)?"selectstart":"mousedown";if(Browser.ie&&!Drag.ondragstartFixed){document.ondragstart=Function.from(false);
    +Drag.ondragstartFixed=true;}this.bound={start:this.start.bind(this),check:this.check.bind(this),drag:this.drag.bind(this),stop:this.stop.bind(this),cancel:this.cancel.bind(this),eventStop:Function.from(false)};
    +this.attach();},attach:function(){this.handles.addEvent("mousedown",this.bound.start);return this;},detach:function(){this.handles.removeEvent("mousedown",this.bound.start);
    +return this;},start:function(a){var j=this.options;if(a.rightClick){return;}if(j.preventDefault){a.preventDefault();}if(j.stopPropagation){a.stopPropagation();
    +}this.mouse.start=a.page;this.fireEvent("beforeStart",this.element);var c=j.limit;this.limit={x:[],y:[]};var e,g;for(e in j.modifiers){if(!j.modifiers[e]){continue;
    +}var b=this.element.getStyle(j.modifiers[e]);if(b&&!b.match(/px$/)){if(!g){g=this.element.getCoordinates(this.element.getOffsetParent());}b=g[j.modifiers[e]];
    +}if(j.style){this.value.now[e]=(b||0).toInt();}else{this.value.now[e]=this.element[j.modifiers[e]];}if(j.invert){this.value.now[e]*=-1;}this.mouse.pos[e]=a.page[e]-this.value.now[e];
    +if(c&&c[e]){var d=2;while(d--){var f=c[e][d];if(f||f===0){this.limit[e][d]=(typeof f=="function")?f():f;}}}}if(typeOf(this.options.grid)=="number"){this.options.grid={x:this.options.grid,y:this.options.grid};
    +}var h={mousemove:this.bound.check,mouseup:this.bound.cancel};h[this.selection]=this.bound.eventStop;this.document.addEvents(h);},check:function(a){if(this.options.preventDefault){a.preventDefault();
    +}var b=Math.round(Math.sqrt(Math.pow(a.page.x-this.mouse.start.x,2)+Math.pow(a.page.y-this.mouse.start.y,2)));if(b>this.options.snap){this.cancel();this.document.addEvents({mousemove:this.bound.drag,mouseup:this.bound.stop});
    +this.fireEvent("start",[this.element,a]).fireEvent("snap",this.element);}},drag:function(b){var a=this.options;if(a.preventDefault){b.preventDefault();
    +}this.mouse.now=b.page;for(var c in a.modifiers){if(!a.modifiers[c]){continue;}this.value.now[c]=this.mouse.now[c]-this.mouse.pos[c];if(a.invert){this.value.now[c]*=-1;
    +}if(a.limit&&this.limit[c]){if((this.limit[c][1]||this.limit[c][1]===0)&&(this.value.now[c]>this.limit[c][1])){this.value.now[c]=this.limit[c][1];}else{if((this.limit[c][0]||this.limit[c][0]===0)&&(this.value.now[c]<this.limit[c][0])){this.value.now[c]=this.limit[c][0];
    +}}}if(a.grid[c]){this.value.now[c]-=((this.value.now[c]-(this.limit[c][0]||0))%a.grid[c]);}if(a.style){this.element.setStyle(a.modifiers[c],this.value.now[c]+a.unit);
    +}else{this.element[a.modifiers[c]]=this.value.now[c];}}this.fireEvent("drag",[this.element,b]);},cancel:function(a){this.document.removeEvents({mousemove:this.bound.check,mouseup:this.bound.cancel});
    +if(a){this.document.removeEvent(this.selection,this.bound.eventStop);this.fireEvent("cancel",this.element);}},stop:function(b){var a={mousemove:this.bound.drag,mouseup:this.bound.stop};
    +a[this.selection]=this.bound.eventStop;this.document.removeEvents(a);if(b){this.fireEvent("complete",[this.element,b]);}}});Element.implement({makeResizable:function(a){var b=new Drag(this,Object.merge({modifiers:{x:"width",y:"height"}},a));
    +this.store("resizer",b);return b.addEvent("drag",function(){this.fireEvent("resize",b);}.bind(this));}});Drag.Move=new Class({Extends:Drag,options:{droppables:[],container:false,precalculate:false,includeMargins:true,checkDroppables:true},initialize:function(b,a){this.parent(b,a);
    +b=this.element;this.droppables=$$(this.options.droppables);this.container=document.id(this.options.container);if(this.container&&typeOf(this.container)!="element"){this.container=document.id(this.container.getDocument().body);
    +}if(this.options.style){if(this.options.modifiers.x=="left"&&this.options.modifiers.y=="top"){var c=b.getOffsetParent(),d=b.getStyles("left","top");if(c&&(d.left=="auto"||d.top=="auto")){b.setPosition(b.getPosition(c));
    +}}if(b.getStyle("position")=="static"){b.setStyle("position","absolute");}}this.addEvent("start",this.checkDroppables,true);this.overed=null;},start:function(a){if(this.container){this.options.limit=this.calculateLimit();
    +}if(this.options.precalculate){this.positions=this.droppables.map(function(b){return b.getCoordinates();});}this.parent(a);},calculateLimit:function(){var j=this.element,e=this.container,d=document.id(j.getOffsetParent())||document.body,h=e.getCoordinates(d),c={},b={},k={},g={},m={};
    +["top","right","bottom","left"].each(function(q){c[q]=j.getStyle("margin-"+q).toInt();b[q]=j.getStyle("border-"+q).toInt();k[q]=e.getStyle("margin-"+q).toInt();
    +g[q]=e.getStyle("border-"+q).toInt();m[q]=d.getStyle("padding-"+q).toInt();},this);var f=j.offsetWidth+c.left+c.right,p=j.offsetHeight+c.top+c.bottom,i=0,l=0,o=h.right-g.right-f,a=h.bottom-g.bottom-p;
    +if(this.options.includeMargins){i+=c.left;l+=c.top;}else{o+=c.right;a+=c.bottom;}if(j.getStyle("position")=="relative"){var n=j.getCoordinates(d);n.left-=j.getStyle("left").toInt();
    +n.top-=j.getStyle("top").toInt();i-=n.left;l-=n.top;if(e.getStyle("position")!="relative"){i+=g.left;l+=g.top;}o+=c.left-n.left;a+=c.top-n.top;if(e!=d){i+=k.left+m.left;
    +l+=((Browser.ie6||Browser.ie7)?0:k.top)+m.top;}}else{i-=c.left;l-=c.top;if(e!=d){i+=h.left+g.left;l+=h.top+g.top;}}return{x:[i,o],y:[l,a]};},getDroppableCoordinates:function(c){var b=c.getCoordinates();
    +if(c.getStyle("position")=="fixed"){var a=window.getScroll();b.left+=a.x;b.right+=a.x;b.top+=a.y;b.bottom+=a.y;}return b;},checkDroppables:function(){var a=this.droppables.filter(function(d,c){d=this.positions?this.positions[c]:this.getDroppableCoordinates(d);
    +var b=this.mouse.now;return(b.x>d.left&&b.x<d.right&&b.y<d.bottom&&b.y>d.top);},this).getLast();if(this.overed!=a){if(this.overed){this.fireEvent("leave",[this.element,this.overed]);
    +}if(a){this.fireEvent("enter",[this.element,a]);}this.overed=a;}},drag:function(a){this.parent(a);if(this.options.checkDroppables&&this.droppables.length){this.checkDroppables();
    +}},stop:function(a){this.checkDroppables();this.fireEvent("drop",[this.element,this.overed,a]);this.overed=null;return this.parent(a);}});Element.implement({makeDraggable:function(a){var b=new Drag.Move(this,a);
    +this.store("dragger",b);return b;}});
    \ No newline at end of file
    diff --git a/lib/qunit.js b/lib/qunit.js
    new file mode 100644
    index 000000000..e00cca902
    --- /dev/null
    +++ b/lib/qunit.js
    @@ -0,0 +1,1448 @@
    +/**
    + * QUnit - A JavaScript Unit Testing Framework
    + *
    + * http://docs.jquery.com/QUnit
    + *
    + * Copyright (c) 2011 John Resig, Jörn Zaefferer
    + * Dual licensed under the MIT (MIT-LICENSE.txt)
    + * or GPL (GPL-LICENSE.txt) licenses.
    + */
    +
    +(function(window) {
    +
    +var defined = {
    +	setTimeout: typeof window.setTimeout !== "undefined",
    +	sessionStorage: (function() {
    +		try {
    +			return !!sessionStorage.getItem;
    +		} catch(e){
    +			return false;
    +		}
    +  })()
    +};
    +
    +var testId = 0;
    +
    +var Test = function(name, testName, expected, testEnvironmentArg, async, callback) {
    +	this.name = name;
    +	this.testName = testName;
    +	this.expected = expected;
    +	this.testEnvironmentArg = testEnvironmentArg;
    +	this.async = async;
    +	this.callback = callback;
    +	this.assertions = [];
    +};
    +Test.prototype = {
    +	init: function() {
    +		var tests = id("qunit-tests");
    +		if (tests) {
    +			var b = document.createElement("strong");
    +				b.innerHTML = "Running " + this.name;
    +			var li = document.createElement("li");
    +				li.appendChild( b );
    +				li.className = "running";
    +				li.id = this.id = "test-output" + testId++;
    +			tests.appendChild( li );
    +		}
    +	},
    +	setup: function() {
    +		if (this.module != config.previousModule) {
    +			if ( config.previousModule ) {
    +				QUnit.moduleDone( {
    +					name: config.previousModule,
    +					failed: config.moduleStats.bad,
    +					passed: config.moduleStats.all - config.moduleStats.bad,
    +					total: config.moduleStats.all
    +				} );
    +			}
    +			config.previousModule = this.module;
    +			config.moduleStats = { all: 0, bad: 0 };
    +			QUnit.moduleStart( {
    +				name: this.module
    +			} );
    +		}
    +
    +		config.current = this;
    +		this.testEnvironment = extend({
    +			setup: function() {},
    +			teardown: function() {}
    +		}, this.moduleTestEnvironment);
    +		if (this.testEnvironmentArg) {
    +			extend(this.testEnvironment, this.testEnvironmentArg);
    +		}
    +
    +		QUnit.testStart( {
    +			name: this.testName
    +		} );
    +
    +		// allow utility functions to access the current test environment
    +		// TODO why??
    +		QUnit.current_testEnvironment = this.testEnvironment;
    +
    +		try {
    +			if ( !config.pollution ) {
    +				saveGlobal();
    +			}
    +
    +			this.testEnvironment.setup.call(this.testEnvironment);
    +		} catch(e) {
    +			QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message );
    +		}
    +	},
    +	run: function() {
    +		if ( this.async ) {
    +			QUnit.stop();
    +		}
    +
    +		if ( config.notrycatch ) {
    +			this.callback.call(this.testEnvironment);
    +			return;
    +		}
    +		try {
    +			this.callback.call(this.testEnvironment);
    +		} catch(e) {
    +			fail("Test " + this.testName + " died, exception and test follows", e, this.callback);
    +			QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) );
    +			// else next test will carry the responsibility
    +			saveGlobal();
    +
    +			// Restart the tests if they're blocking
    +			if ( config.blocking ) {
    +				start();
    +			}
    +		}
    +	},
    +	teardown: function() {
    +		try {
    +			this.testEnvironment.teardown.call(this.testEnvironment);
    +			checkPollution();
    +		} catch(e) {
    +			QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message );
    +		}
    +	},
    +	finish: function() {
    +		if ( this.expected && this.expected != this.assertions.length ) {
    +			QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" );
    +		}
    +
    +		var good = 0, bad = 0,
    +			tests = id("qunit-tests");
    +
    +		config.stats.all += this.assertions.length;
    +		config.moduleStats.all += this.assertions.length;
    +
    +		if ( tests ) {
    +			var ol  = document.createElement("ol");
    +
    +			for ( var i = 0; i < this.assertions.length; i++ ) {
    +				var assertion = this.assertions[i];
    +
    +				var li = document.createElement("li");
    +				li.className = assertion.result ? "pass" : "fail";
    +				li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed");
    +				ol.appendChild( li );
    +
    +				if ( assertion.result ) {
    +					good++;
    +				} else {
    +					bad++;
    +					config.stats.bad++;
    +					config.moduleStats.bad++;
    +				}
    +			}
    +
    +			// store result when possible
    +			if ( QUnit.config.reorder && defined.sessionStorage ) {
    +				if (bad) {
    +					sessionStorage.setItem("qunit-" + this.module + "-" + this.testName, bad);
    +				} else {
    +					sessionStorage.removeItem("qunit-" + this.module + "-" + this.testName);
    +				}
    +			}
    +
    +			if (bad == 0) {
    +				ol.style.display = "none";
    +			}
    +
    +			var b = document.createElement("strong");
    +			b.innerHTML = this.name + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>";
    +
    +			var a = document.createElement("a");
    +			a.innerHTML = "Rerun";
    +			a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
    +
    +			addEvent(b, "click", function() {
    +				var next = b.nextSibling.nextSibling,
    +					display = next.style.display;
    +				next.style.display = display === "none" ? "block" : "none";
    +			});
    +
    +			addEvent(b, "dblclick", function(e) {
    +				var target = e && e.target ? e.target : window.event.srcElement;
    +				if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {
    +					target = target.parentNode;
    +				}
    +				if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
    +					window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
    +				}
    +			});
    +
    +			var li = id(this.id);
    +			li.className = bad ? "fail" : "pass";
    +			li.removeChild( li.firstChild );
    +			li.appendChild( b );
    +			li.appendChild( a );
    +			li.appendChild( ol );
    +
    +		} else {
    +			for ( var i = 0; i < this.assertions.length; i++ ) {
    +				if ( !this.assertions[i].result ) {
    +					bad++;
    +					config.stats.bad++;
    +					config.moduleStats.bad++;
    +				}
    +			}
    +		}
    +
    +		try {
    +			QUnit.reset();
    +		} catch(e) {
    +			fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset);
    +		}
    +
    +		QUnit.testDone( {
    +			name: this.testName,
    +			failed: bad,
    +			passed: this.assertions.length - bad,
    +			total: this.assertions.length
    +		} );
    +	},
    +
    +	queue: function() {
    +		var test = this;
    +		synchronize(function() {
    +			test.init();
    +		});
    +		function run() {
    +			// each of these can by async
    +			synchronize(function() {
    +				test.setup();
    +			});
    +			synchronize(function() {
    +				test.run();
    +			});
    +			synchronize(function() {
    +				test.teardown();
    +			});
    +			synchronize(function() {
    +				test.finish();
    +			});
    +		}
    +		// defer when previous test run passed, if storage is available
    +		var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.module + "-" + this.testName);
    +		if (bad) {
    +			run();
    +		} else {
    +			synchronize(run);
    +		};
    +	}
    +
    +};
    +
    +var QUnit = {
    +
    +	// call on start of module test to prepend name to all tests
    +	module: function(name, testEnvironment) {
    +		config.currentModule = name;
    +		config.currentModuleTestEnviroment = testEnvironment;
    +	},
    +
    +	asyncTest: function(testName, expected, callback) {
    +		if ( arguments.length === 2 ) {
    +			callback = expected;
    +			expected = 0;
    +		}
    +
    +		QUnit.test(testName, expected, callback, true);
    +	},
    +
    +	test: function(testName, expected, callback, async) {
    +		var name = '<span class="test-name">' + testName + '</span>', testEnvironmentArg;
    +
    +		if ( arguments.length === 2 ) {
    +			callback = expected;
    +			expected = null;
    +		}
    +		// is 2nd argument a testEnvironment?
    +		if ( expected && typeof expected === 'object') {
    +			testEnvironmentArg =  expected;
    +			expected = null;
    +		}
    +
    +		if ( config.currentModule ) {
    +			name = '<span class="module-name">' + config.currentModule + "</span>: " + name;
    +		}
    +
    +		if ( !validTest(config.currentModule + ": " + testName) ) {
    +			return;
    +		}
    +
    +		var test = new Test(name, testName, expected, testEnvironmentArg, async, callback);
    +		test.module = config.currentModule;
    +		test.moduleTestEnvironment = config.currentModuleTestEnviroment;
    +		test.queue();
    +	},
    +
    +	/**
    +	 * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
    +	 */
    +	expect: function(asserts) {
    +		config.current.expected = asserts;
    +	},
    +
    +	/**
    +	 * Asserts true.
    +	 * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
    +	 */
    +	ok: function(a, msg) {
    +		a = !!a;
    +		var details = {
    +			result: a,
    +			message: msg
    +		};
    +		msg = escapeHtml(msg);
    +		QUnit.log(details);
    +		config.current.assertions.push({
    +			result: a,
    +			message: msg
    +		});
    +	},
    +
    +	/**
    +	 * Checks that the first two arguments are equal, with an optional message.
    +	 * Prints out both actual and expected values.
    +	 *
    +	 * Prefered to ok( actual == expected, message )
    +	 *
    +	 * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." );
    +	 *
    +	 * @param Object actual
    +	 * @param Object expected
    +	 * @param String message (optional)
    +	 */
    +	equal: function(actual, expected, message) {
    +		QUnit.push(expected == actual, actual, expected, message);
    +	},
    +
    +	notEqual: function(actual, expected, message) {
    +		QUnit.push(expected != actual, actual, expected, message);
    +	},
    +
    +	deepEqual: function(actual, expected, message) {
    +		QUnit.push(QUnit.equiv(actual, expected), actual, expected, message);
    +	},
    +
    +	notDeepEqual: function(actual, expected, message) {
    +		QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message);
    +	},
    +
    +	strictEqual: function(actual, expected, message) {
    +		QUnit.push(expected === actual, actual, expected, message);
    +	},
    +
    +	notStrictEqual: function(actual, expected, message) {
    +		QUnit.push(expected !== actual, actual, expected, message);
    +	},
    +
    +	raises: function(block, expected, message) {
    +		var actual, ok = false;
    +
    +		if (typeof expected === 'string') {
    +			message = expected;
    +			expected = null;
    +		}
    +
    +		try {
    +			block();
    +		} catch (e) {
    +			actual = e;
    +		}
    +
    +		if (actual) {
    +			// we don't want to validate thrown error
    +			if (!expected) {
    +				ok = true;
    +			// expected is a regexp
    +			} else if (QUnit.objectType(expected) === "regexp") {
    +				ok = expected.test(actual);
    +			// expected is a constructor
    +			} else if (actual instanceof expected) {
    +				ok = true;
    +			// expected is a validation function which returns true is validation passed
    +			} else if (expected.call({}, actual) === true) {
    +				ok = true;
    +			}
    +		}
    +
    +		QUnit.ok(ok, message);
    +	},
    +
    +	start: function() {
    +		config.semaphore--;
    +		if (config.semaphore > 0) {
    +			// don't start until equal number of stop-calls
    +			return;
    +		}
    +		if (config.semaphore < 0) {
    +			// ignore if start is called more often then stop
    +			config.semaphore = 0;
    +		}
    +		// A slight delay, to avoid any current callbacks
    +		if ( defined.setTimeout ) {
    +			window.setTimeout(function() {
    +				if ( config.timeout ) {
    +					clearTimeout(config.timeout);
    +				}
    +
    +				config.blocking = false;
    +				process();
    +			}, 13);
    +		} else {
    +			config.blocking = false;
    +			process();
    +		}
    +	},
    +
    +	stop: function(timeout) {
    +		config.semaphore++;
    +		config.blocking = true;
    +
    +		if ( timeout && defined.setTimeout ) {
    +			clearTimeout(config.timeout);
    +			config.timeout = window.setTimeout(function() {
    +				QUnit.ok( false, "Test timed out" );
    +				QUnit.start();
    +			}, timeout);
    +		}
    +	}
    +};
    +
    +// Backwards compatibility, deprecated
    +QUnit.equals = QUnit.equal;
    +QUnit.same = QUnit.deepEqual;
    +
    +// Maintain internal state
    +var config = {
    +	// The queue of tests to run
    +	queue: [],
    +
    +	// block until document ready
    +	blocking: true,
    +
    +	// by default, run previously failed tests first
    +	// very useful in combination with "Hide passed tests" checked
    +	reorder: true,
    +
    +	noglobals: false,
    +	notrycatch: false
    +};
    +
    +// Load paramaters
    +(function() {
    +	var location = window.location || { search: "", protocol: "file:" },
    +		params = location.search.slice( 1 ).split( "&" ),
    +		length = params.length,
    +		urlParams = {},
    +		current;
    +
    +	if ( params[ 0 ] ) {
    +		for ( var i = 0; i < length; i++ ) {
    +			current = params[ i ].split( "=" );
    +			current[ 0 ] = decodeURIComponent( current[ 0 ] );
    +			// allow just a key to turn on a flag, e.g., test.html?noglobals
    +			current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
    +			urlParams[ current[ 0 ] ] = current[ 1 ];
    +			if ( current[ 0 ] in config ) {
    +				config[ current[ 0 ] ] = current[ 1 ];
    +			}
    +		}
    +	}
    +
    +	QUnit.urlParams = urlParams;
    +	config.filter = urlParams.filter;
    +
    +	// Figure out if we're running the tests from a server or not
    +	QUnit.isLocal = !!(location.protocol === 'file:');
    +})();
    +
    +// Expose the API as global variables, unless an 'exports'
    +// object exists, in that case we assume we're in CommonJS
    +if ( typeof exports === "undefined" || typeof require === "undefined" ) {
    +	extend(window, QUnit);
    +	window.QUnit = QUnit;
    +} else {
    +	extend(exports, QUnit);
    +	exports.QUnit = QUnit;
    +}
    +
    +// define these after exposing globals to keep them in these QUnit namespace only
    +extend(QUnit, {
    +	config: config,
    +
    +	// Initialize the configuration options
    +	init: function() {
    +		extend(config, {
    +			stats: { all: 0, bad: 0 },
    +			moduleStats: { all: 0, bad: 0 },
    +			started: +new Date,
    +			updateRate: 1000,
    +			blocking: false,
    +			autostart: true,
    +			autorun: false,
    +			filter: "",
    +			queue: [],
    +			semaphore: 0
    +		});
    +
    +		var tests = id( "qunit-tests" ),
    +			banner = id( "qunit-banner" ),
    +			result = id( "qunit-testresult" );
    +
    +		if ( tests ) {
    +			tests.innerHTML = "";
    +		}
    +
    +		if ( banner ) {
    +			banner.className = "";
    +		}
    +
    +		if ( result ) {
    +			result.parentNode.removeChild( result );
    +		}
    +
    +		if ( tests ) {
    +			result = document.createElement( "p" );
    +			result.id = "qunit-testresult";
    +			result.className = "result";
    +			tests.parentNode.insertBefore( result, tests );
    +			result.innerHTML = 'Running...<br/>&nbsp;';
    +		}
    +	},
    +
    +	/**
    +	 * Resets the test setup. Useful for tests that modify the DOM.
    +	 *
    +	 * If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
    +	 */
    +	reset: function() {
    +		if ( window.jQuery ) {
    +			jQuery( "#qunit-fixture" ).html( config.fixture );
    +		} else {
    +			var main = id( 'qunit-fixture' );
    +			if ( main ) {
    +				main.innerHTML = config.fixture;
    +			}
    +		}
    +	},
    +
    +	/**
    +	 * Trigger an event on an element.
    +	 *
    +	 * @example triggerEvent( document.body, "click" );
    +	 *
    +	 * @param DOMElement elem
    +	 * @param String type
    +	 */
    +	triggerEvent: function( elem, type, event ) {
    +		if ( document.createEvent ) {
    +			event = document.createEvent("MouseEvents");
    +			event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
    +				0, 0, 0, 0, 0, false, false, false, false, 0, null);
    +			elem.dispatchEvent( event );
    +
    +		} else if ( elem.fireEvent ) {
    +			elem.fireEvent("on"+type);
    +		}
    +	},
    +
    +	// Safe object type checking
    +	is: function( type, obj ) {
    +		return QUnit.objectType( obj ) == type;
    +	},
    +
    +	objectType: function( obj ) {
    +		if (typeof obj === "undefined") {
    +				return "undefined";
    +
    +		// consider: typeof null === object
    +		}
    +		if (obj === null) {
    +				return "null";
    +		}
    +
    +		var type = Object.prototype.toString.call( obj )
    +			.match(/^\[object\s(.*)\]$/)[1] || '';
    +
    +		switch (type) {
    +				case 'Number':
    +						if (isNaN(obj)) {
    +								return "nan";
    +						} else {
    +								return "number";
    +						}
    +				case 'String':
    +				case 'Boolean':
    +				case 'Array':
    +				case 'Date':
    +				case 'RegExp':
    +				case 'Function':
    +						return type.toLowerCase();
    +		}
    +		if (typeof obj === "object") {
    +				return "object";
    +		}
    +		return undefined;
    +	},
    +
    +	push: function(result, actual, expected, message) {
    +		var details = {
    +			result: result,
    +			message: message,
    +			actual: actual,
    +			expected: expected
    +		};
    +
    +		message = escapeHtml(message) || (result ? "okay" : "failed");
    +		message = '<span class="test-message">' + message + "</span>";
    +		expected = escapeHtml(QUnit.jsDump.parse(expected));
    +		actual = escapeHtml(QUnit.jsDump.parse(actual));
    +		var output = message + '<table><tr class="test-expected"><th>Expected: </th><td><pre>' + expected + '</pre></td></tr>';
    +		if (actual != expected) {
    +			output += '<tr class="test-actual"><th>Result: </th><td><pre>' + actual + '</pre></td></tr>';
    +			output += '<tr class="test-diff"><th>Diff: </th><td><pre>' + QUnit.diff(expected, actual) +'</pre></td></tr>';
    +		}
    +		if (!result) {
    +			var source = sourceFromStacktrace();
    +			if (source) {
    +				details.source = source;
    +				output += '<tr class="test-source"><th>Source: </th><td><pre>' + escapeHtml(source) + '</pre></td></tr>';
    +			}
    +		}
    +		output += "</table>";
    +
    +		QUnit.log(details);
    +
    +		config.current.assertions.push({
    +			result: !!result,
    +			message: output
    +		});
    +	},
    +
    +	url: function( params ) {
    +		params = extend( extend( {}, QUnit.urlParams ), params );
    +		var querystring = "?",
    +			key;
    +		for ( key in params ) {
    +			querystring += encodeURIComponent( key ) + "=" +
    +				encodeURIComponent( params[ key ] ) + "&";
    +		}
    +		return window.location.pathname + querystring.slice( 0, -1 );
    +	},
    +
    +	// Logging callbacks; all receive a single argument with the listed properties
    +	// run test/logs.html for any related changes
    +	begin: function() {},
    +	// done: { failed, passed, total, runtime }
    +	done: function() {},
    +	// log: { result, actual, expected, message }
    +	log: function() {},
    +	// testStart: { name }
    +	testStart: function() {},
    +	// testDone: { name, failed, passed, total }
    +	testDone: function() {},
    +	// moduleStart: { name }
    +	moduleStart: function() {},
    +	// moduleDone: { name, failed, passed, total }
    +	moduleDone: function() {}
    +});
    +
    +if ( typeof document === "undefined" || document.readyState === "complete" ) {
    +	config.autorun = true;
    +}
    +
    +addEvent(window, "load", function() {
    +	QUnit.begin({});
    +
    +	// Initialize the config, saving the execution queue
    +	var oldconfig = extend({}, config);
    +	QUnit.init();
    +	extend(config, oldconfig);
    +
    +	config.blocking = false;
    +
    +	var userAgent = id("qunit-userAgent");
    +	if ( userAgent ) {
    +		userAgent.innerHTML = navigator.userAgent;
    +	}
    +	var banner = id("qunit-header");
    +	if ( banner ) {
    +		banner.innerHTML = '<a href="' + QUnit.url({ filter: undefined }) + '"> ' + banner.innerHTML + '</a> ' +
    +			'<label><input name="noglobals" type="checkbox"' + ( config.noglobals ? ' checked="checked"' : '' ) + '>noglobals</label>' +
    +			'<label><input name="notrycatch" type="checkbox"' + ( config.notrycatch ? ' checked="checked"' : '' ) + '>notrycatch</label>';
    +		addEvent( banner, "change", function( event ) {
    +			var params = {};
    +			params[ event.target.name ] = event.target.checked ? true : undefined;
    +			window.location = QUnit.url( params );
    +		});
    +	}
    +
    +	var toolbar = id("qunit-testrunner-toolbar");
    +	if ( toolbar ) {
    +		var filter = document.createElement("input");
    +		filter.type = "checkbox";
    +		filter.id = "qunit-filter-pass";
    +		addEvent( filter, "click", function() {
    +			var ol = document.getElementById("qunit-tests");
    +			if ( filter.checked ) {
    +				ol.className = ol.className + " hidepass";
    +			} else {
    +				var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " ";
    +				ol.className = tmp.replace(/ hidepass /, " ");
    +			}
    +			if ( defined.sessionStorage ) {
    +				if (filter.checked) {
    +					sessionStorage.setItem("qunit-filter-passed-tests",  "true");
    +				} else {
    +					sessionStorage.removeItem("qunit-filter-passed-tests");
    +				}
    +			}
    +		});
    +		if ( defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) {
    +			filter.checked = true;
    +			var ol = document.getElementById("qunit-tests");
    +			ol.className = ol.className + " hidepass";
    +		}
    +		toolbar.appendChild( filter );
    +
    +		var label = document.createElement("label");
    +		label.setAttribute("for", "qunit-filter-pass");
    +		label.innerHTML = "Hide passed tests";
    +		toolbar.appendChild( label );
    +	}
    +
    +	var main = id('qunit-fixture');
    +	if ( main ) {
    +		config.fixture = main.innerHTML;
    +	}
    +
    +	if (config.autostart) {
    +		QUnit.start();
    +	}
    +});
    +
    +function done() {
    +	config.autorun = true;
    +
    +	// Log the last module results
    +	if ( config.currentModule ) {
    +		QUnit.moduleDone( {
    +			name: config.currentModule,
    +			failed: config.moduleStats.bad,
    +			passed: config.moduleStats.all - config.moduleStats.bad,
    +			total: config.moduleStats.all
    +		} );
    +	}
    +
    +	var banner = id("qunit-banner"),
    +		tests = id("qunit-tests"),
    +		runtime = +new Date - config.started,
    +		passed = config.stats.all - config.stats.bad,
    +		html = [
    +			'Tests completed in ',
    +			runtime,
    +			' milliseconds.<br/>',
    +			'<span class="passed">',
    +			passed,
    +			'</span> tests of <span class="total">',
    +			config.stats.all,
    +			'</span> passed, <span class="failed">',
    +			config.stats.bad,
    +			'</span> failed.'
    +		].join('');
    +
    +	if ( banner ) {
    +		banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass");
    +	}
    +
    +	if ( tests ) {
    +		id( "qunit-testresult" ).innerHTML = html;
    +	}
    +
    +	if ( typeof document !== "undefined" && document.title ) {
    +		// show ✖ for good, ✔ for bad suite result in title
    +		// use escape sequences in case file gets loaded with non-utf-8-charset
    +		document.title = (config.stats.bad ? "\u2716" : "\u2714") + " " + document.title;
    +	}
    +
    +	QUnit.done( {
    +		failed: config.stats.bad,
    +		passed: passed,
    +		total: config.stats.all,
    +		runtime: runtime
    +	} );
    +}
    +
    +function validTest( name ) {
    +	var filter = config.filter,
    +		run = false;
    +
    +	if ( !filter ) {
    +		return true;
    +	}
    +
    +	var not = filter.charAt( 0 ) === "!";
    +	if ( not ) {
    +		filter = filter.slice( 1 );
    +	}
    +
    +	if ( name.indexOf( filter ) !== -1 ) {
    +		return !not;
    +	}
    +
    +	if ( not ) {
    +		run = true;
    +	}
    +
    +	return run;
    +}
    +
    +// so far supports only Firefox, Chrome and Opera (buggy)
    +// could be extended in the future to use something like https://github.com/csnover/TraceKit
    +function sourceFromStacktrace() {
    +	try {
    +		throw new Error();
    +	} catch ( e ) {
    +		if (e.stacktrace) {
    +			// Opera
    +			return e.stacktrace.split("\n")[6];
    +		} else if (e.stack) {
    +			// Firefox, Chrome
    +			return e.stack.split("\n")[4];
    +		}
    +	}
    +}
    +
    +function escapeHtml(s) {
    +	if (!s) {
    +		return "";
    +	}
    +	s = s + "";
    +	return s.replace(/[\&"<>\\]/g, function(s) {
    +		switch(s) {
    +			case "&": return "&amp;";
    +			case "\\": return "\\\\";
    +			case '"': return '\"';
    +			case "<": return "&lt;";
    +			case ">": return "&gt;";
    +			default: return s;
    +		}
    +	});
    +}
    +
    +function synchronize( callback ) {
    +	config.queue.push( callback );
    +
    +	if ( config.autorun && !config.blocking ) {
    +		process();
    +	}
    +}
    +
    +function process() {
    +	var start = (new Date()).getTime();
    +
    +	while ( config.queue.length && !config.blocking ) {
    +		if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) {
    +			config.queue.shift()();
    +		} else {
    +			window.setTimeout( process, 13 );
    +			break;
    +		}
    +	}
    +  if (!config.blocking && !config.queue.length) {
    +    done();
    +  }
    +}
    +
    +function saveGlobal() {
    +	config.pollution = [];
    +
    +	if ( config.noglobals ) {
    +		for ( var key in window ) {
    +			config.pollution.push( key );
    +		}
    +	}
    +}
    +
    +function checkPollution( name ) {
    +	var old = config.pollution;
    +	saveGlobal();
    +
    +	var newGlobals = diff( config.pollution, old );
    +	if ( newGlobals.length > 0 ) {
    +		ok( false, "Introduced global variable(s): " + newGlobals.join(", ") );
    +	}
    +
    +	var deletedGlobals = diff( old, config.pollution );
    +	if ( deletedGlobals.length > 0 ) {
    +		ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") );
    +	}
    +}
    +
    +// returns a new Array with the elements that are in a but not in b
    +function diff( a, b ) {
    +	var result = a.slice();
    +	for ( var i = 0; i < result.length; i++ ) {
    +		for ( var j = 0; j < b.length; j++ ) {
    +			if ( result[i] === b[j] ) {
    +				result.splice(i, 1);
    +				i--;
    +				break;
    +			}
    +		}
    +	}
    +	return result;
    +}
    +
    +function fail(message, exception, callback) {
    +	if ( typeof console !== "undefined" && console.error && console.warn ) {
    +		console.error(message);
    +		console.error(exception);
    +		console.warn(callback.toString());
    +
    +	} else if ( window.opera && opera.postError ) {
    +		opera.postError(message, exception, callback.toString);
    +	}
    +}
    +
    +function extend(a, b) {
    +	for ( var prop in b ) {
    +		if ( b[prop] === undefined ) {
    +			delete a[prop];
    +		} else {
    +			a[prop] = b[prop];
    +		}
    +	}
    +
    +	return a;
    +}
    +
    +function addEvent(elem, type, fn) {
    +	if ( elem.addEventListener ) {
    +		elem.addEventListener( type, fn, false );
    +	} else if ( elem.attachEvent ) {
    +		elem.attachEvent( "on" + type, fn );
    +	} else {
    +		fn();
    +	}
    +}
    +
    +function id(name) {
    +	return !!(typeof document !== "undefined" && document && document.getElementById) &&
    +		document.getElementById( name );
    +}
    +
    +// Test for equality any JavaScript type.
    +// Discussions and reference: http://philrathe.com/articles/equiv
    +// Test suites: http://philrathe.com/tests/equiv
    +// Author: Philippe Rathé <prathe@gmail.com>
    +QUnit.equiv = function () {
    +
    +    var innerEquiv; // the real equiv function
    +    var callers = []; // stack to decide between skip/abort functions
    +    var parents = []; // stack to avoiding loops from circular referencing
    +
    +    // Call the o related callback with the given arguments.
    +    function bindCallbacks(o, callbacks, args) {
    +        var prop = QUnit.objectType(o);
    +        if (prop) {
    +            if (QUnit.objectType(callbacks[prop]) === "function") {
    +                return callbacks[prop].apply(callbacks, args);
    +            } else {
    +                return callbacks[prop]; // or undefined
    +            }
    +        }
    +    }
    +
    +    var callbacks = function () {
    +
    +        // for string, boolean, number and null
    +        function useStrictEquality(b, a) {
    +            if (b instanceof a.constructor || a instanceof b.constructor) {
    +                // to catch short annotaion VS 'new' annotation of a declaration
    +                // e.g. var i = 1;
    +                //      var j = new Number(1);
    +                return a == b;
    +            } else {
    +                return a === b;
    +            }
    +        }
    +
    +        return {
    +            "string": useStrictEquality,
    +            "boolean": useStrictEquality,
    +            "number": useStrictEquality,
    +            "null": useStrictEquality,
    +            "undefined": useStrictEquality,
    +
    +            "nan": function (b) {
    +                return isNaN(b);
    +            },
    +
    +            "date": function (b, a) {
    +                return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf();
    +            },
    +
    +            "regexp": function (b, a) {
    +                return QUnit.objectType(b) === "regexp" &&
    +                    a.source === b.source && // the regex itself
    +                    a.global === b.global && // and its modifers (gmi) ...
    +                    a.ignoreCase === b.ignoreCase &&
    +                    a.multiline === b.multiline;
    +            },
    +
    +            // - skip when the property is a method of an instance (OOP)
    +            // - abort otherwise,
    +            //   initial === would have catch identical references anyway
    +            "function": function () {
    +                var caller = callers[callers.length - 1];
    +                return caller !== Object &&
    +                        typeof caller !== "undefined";
    +            },
    +
    +            "array": function (b, a) {
    +                var i, j, loop;
    +                var len;
    +
    +                // b could be an object literal here
    +                if ( ! (QUnit.objectType(b) === "array")) {
    +                    return false;
    +                }
    +
    +                len = a.length;
    +                if (len !== b.length) { // safe and faster
    +                    return false;
    +                }
    +
    +                //track reference to avoid circular references
    +                parents.push(a);
    +                for (i = 0; i < len; i++) {
    +                    loop = false;
    +                    for(j=0;j<parents.length;j++){
    +                        if(parents[j] === a[i]){
    +                            loop = true;//dont rewalk array
    +                        }
    +                    }
    +                    if (!loop && ! innerEquiv(a[i], b[i])) {
    +                        parents.pop();
    +                        return false;
    +                    }
    +                }
    +                parents.pop();
    +                return true;
    +            },
    +
    +            "object": function (b, a) {
    +                var i, j, loop;
    +                var eq = true; // unless we can proove it
    +                var aProperties = [], bProperties = []; // collection of strings
    +
    +                // comparing constructors is more strict than using instanceof
    +                if ( a.constructor !== b.constructor) {
    +                    return false;
    +                }
    +
    +                // stack constructor before traversing properties
    +                callers.push(a.constructor);
    +                //track reference to avoid circular references
    +                parents.push(a);
    +
    +                for (i in a) { // be strict: don't ensures hasOwnProperty and go deep
    +                    loop = false;
    +                    for(j=0;j<parents.length;j++){
    +                        if(parents[j] === a[i])
    +                            loop = true; //don't go down the same path twice
    +                    }
    +                    aProperties.push(i); // collect a's properties
    +
    +                    if (!loop && ! innerEquiv(a[i], b[i])) {
    +                        eq = false;
    +                        break;
    +                    }
    +                }
    +
    +                callers.pop(); // unstack, we are done
    +                parents.pop();
    +
    +                for (i in b) {
    +                    bProperties.push(i); // collect b's properties
    +                }
    +
    +                // Ensures identical properties name
    +                return eq && innerEquiv(aProperties.sort(), bProperties.sort());
    +            }
    +        };
    +    }();
    +
    +    innerEquiv = function () { // can take multiple arguments
    +        var args = Array.prototype.slice.apply(arguments);
    +        if (args.length < 2) {
    +            return true; // end transition
    +        }
    +
    +        return (function (a, b) {
    +            if (a === b) {
    +                return true; // catch the most you can
    +            } else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || QUnit.objectType(a) !== QUnit.objectType(b)) {
    +                return false; // don't lose time with error prone cases
    +            } else {
    +                return bindCallbacks(a, callbacks, [b, a]);
    +            }
    +
    +        // apply transition with (1..n) arguments
    +        })(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1));
    +    };
    +
    +    return innerEquiv;
    +
    +}();
    +
    +/**
    + * jsDump
    + * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
    + * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php)
    + * Date: 5/15/2008
    + * @projectDescription Advanced and extensible data dumping for Javascript.
    + * @version 1.0.0
    + * @author Ariel Flesler
    + * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
    + */
    +QUnit.jsDump = (function() {
    +	function quote( str ) {
    +		return '"' + str.toString().replace(/"/g, '\\"') + '"';
    +	};
    +	function literal( o ) {
    +		return o + '';
    +	};
    +	function join( pre, arr, post ) {
    +		var s = jsDump.separator(),
    +			base = jsDump.indent(),
    +			inner = jsDump.indent(1);
    +		if ( arr.join )
    +			arr = arr.join( ',' + s + inner );
    +		if ( !arr )
    +			return pre + post;
    +		return [ pre, inner + arr, base + post ].join(s);
    +	};
    +	function array( arr ) {
    +		var i = arr.length,	ret = Array(i);
    +		this.up();
    +		while ( i-- )
    +			ret[i] = this.parse( arr[i] );
    +		this.down();
    +		return join( '[', ret, ']' );
    +	};
    +
    +	var reName = /^function (\w+)/;
    +
    +	var jsDump = {
    +		parse:function( obj, type ) { //type is used mostly internally, you can fix a (custom)type in advance
    +			var	parser = this.parsers[ type || this.typeOf(obj) ];
    +			type = typeof parser;
    +
    +			return type == 'function' ? parser.call( this, obj ) :
    +				   type == 'string' ? parser :
    +				   this.parsers.error;
    +		},
    +		typeOf:function( obj ) {
    +			var type;
    +			if ( obj === null ) {
    +				type = "null";
    +			} else if (typeof obj === "undefined") {
    +				type = "undefined";
    +			} else if (QUnit.is("RegExp", obj)) {
    +				type = "regexp";
    +			} else if (QUnit.is("Date", obj)) {
    +				type = "date";
    +			} else if (QUnit.is("Function", obj)) {
    +				type = "function";
    +			} else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") {
    +				type = "window";
    +			} else if (obj.nodeType === 9) {
    +				type = "document";
    +			} else if (obj.nodeType) {
    +				type = "node";
    +			} else if (typeof obj === "object" && typeof obj.length === "number" && obj.length >= 0) {
    +				type = "array";
    +			} else {
    +				type = typeof obj;
    +			}
    +			return type;
    +		},
    +		separator:function() {
    +			return this.multiline ?	this.HTML ? '<br />' : '\n' : this.HTML ? '&nbsp;' : ' ';
    +		},
    +		indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing
    +			if ( !this.multiline )
    +				return '';
    +			var chr = this.indentChar;
    +			if ( this.HTML )
    +				chr = chr.replace(/\t/g,'   ').replace(/ /g,'&nbsp;');
    +			return Array( this._depth_ + (extra||0) ).join(chr);
    +		},
    +		up:function( a ) {
    +			this._depth_ += a || 1;
    +		},
    +		down:function( a ) {
    +			this._depth_ -= a || 1;
    +		},
    +		setParser:function( name, parser ) {
    +			this.parsers[name] = parser;
    +		},
    +		// The next 3 are exposed so you can use them
    +		quote:quote,
    +		literal:literal,
    +		join:join,
    +		//
    +		_depth_: 1,
    +		// This is the list of parsers, to modify them, use jsDump.setParser
    +		parsers:{
    +			window: '[Window]',
    +			document: '[Document]',
    +			error:'[ERROR]', //when no parser is found, shouldn't happen
    +			unknown: '[Unknown]',
    +			'null':'null',
    +			'undefined':'undefined',
    +			'function':function( fn ) {
    +				var ret = 'function',
    +					name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE
    +				if ( name )
    +					ret += ' ' + name;
    +				ret += '(';
    +
    +				ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join('');
    +				return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' );
    +			},
    +			array: array,
    +			nodelist: array,
    +			arguments: array,
    +			object:function( map ) {
    +				var ret = [ ];
    +				QUnit.jsDump.up();
    +				for ( var key in map )
    +					ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(map[key]) );
    +				QUnit.jsDump.down();
    +				return join( '{', ret, '}' );
    +			},
    +			node:function( node ) {
    +				var open = QUnit.jsDump.HTML ? '&lt;' : '<',
    +					close = QUnit.jsDump.HTML ? '&gt;' : '>';
    +
    +				var tag = node.nodeName.toLowerCase(),
    +					ret = open + tag;
    +
    +				for ( var a in QUnit.jsDump.DOMAttrs ) {
    +					var val = node[QUnit.jsDump.DOMAttrs[a]];
    +					if ( val )
    +						ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' );
    +				}
    +				return ret + close + open + '/' + tag + close;
    +			},
    +			functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function
    +				var l = fn.length;
    +				if ( !l ) return '';
    +
    +				var args = Array(l);
    +				while ( l-- )
    +					args[l] = String.fromCharCode(97+l);//97 is 'a'
    +				return ' ' + args.join(', ') + ' ';
    +			},
    +			key:quote, //object calls it internally, the key part of an item in a map
    +			functionCode:'[code]', //function calls it internally, it's the content of the function
    +			attribute:quote, //node calls it internally, it's an html attribute value
    +			string:quote,
    +			date:quote,
    +			regexp:literal, //regex
    +			number:literal,
    +			'boolean':literal
    +		},
    +		DOMAttrs:{//attributes to dump from nodes, name=>realName
    +			id:'id',
    +			name:'name',
    +			'class':'className'
    +		},
    +		HTML:false,//if true, entities are escaped ( <, >, \t, space and \n )
    +		indentChar:'  ',//indentation unit
    +		multiline:true //if true, items in a collection, are separated by a \n, else just a space.
    +	};
    +
    +	return jsDump;
    +})();
    +
    +// from Sizzle.js
    +function getText( elems ) {
    +	var ret = "", elem;
    +
    +	for ( var i = 0; elems[i]; i++ ) {
    +		elem = elems[i];
    +
    +		// Get the text from text nodes and CDATA nodes
    +		if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
    +			ret += elem.nodeValue;
    +
    +		// Traverse everything else, except comment nodes
    +		} else if ( elem.nodeType !== 8 ) {
    +			ret += getText( elem.childNodes );
    +		}
    +	}
    +
    +	return ret;
    +};
    +
    +/*
    + * Javascript Diff Algorithm
    + *  By John Resig (http://ejohn.org/)
    + *  Modified by Chu Alan "sprite"
    + *
    + * Released under the MIT license.
    + *
    + * More Info:
    + *  http://ejohn.org/projects/javascript-diff-algorithm/
    + *
    + * Usage: QUnit.diff(expected, actual)
    + *
    + * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the  quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over"
    + */
    +QUnit.diff = (function() {
    +	function diff(o, n){
    +		var ns = new Object();
    +		var os = new Object();
    +
    +		for (var i = 0; i < n.length; i++) {
    +			if (ns[n[i]] == null)
    +				ns[n[i]] = {
    +					rows: new Array(),
    +					o: null
    +				};
    +			ns[n[i]].rows.push(i);
    +		}
    +
    +		for (var i = 0; i < o.length; i++) {
    +			if (os[o[i]] == null)
    +				os[o[i]] = {
    +					rows: new Array(),
    +					n: null
    +				};
    +			os[o[i]].rows.push(i);
    +		}
    +
    +		for (var i in ns) {
    +			if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) {
    +				n[ns[i].rows[0]] = {
    +					text: n[ns[i].rows[0]],
    +					row: os[i].rows[0]
    +				};
    +				o[os[i].rows[0]] = {
    +					text: o[os[i].rows[0]],
    +					row: ns[i].rows[0]
    +				};
    +			}
    +		}
    +
    +		for (var i = 0; i < n.length - 1; i++) {
    +			if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null &&
    +			n[i + 1] == o[n[i].row + 1]) {
    +				n[i + 1] = {
    +					text: n[i + 1],
    +					row: n[i].row + 1
    +				};
    +				o[n[i].row + 1] = {
    +					text: o[n[i].row + 1],
    +					row: i + 1
    +				};
    +			}
    +		}
    +
    +		for (var i = n.length - 1; i > 0; i--) {
    +			if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null &&
    +			n[i - 1] == o[n[i].row - 1]) {
    +				n[i - 1] = {
    +					text: n[i - 1],
    +					row: n[i].row - 1
    +				};
    +				o[n[i].row - 1] = {
    +					text: o[n[i].row - 1],
    +					row: i - 1
    +				};
    +			}
    +		}
    +
    +		return {
    +			o: o,
    +			n: n
    +		};
    +	}
    +
    +	return function(o, n){
    +		o = o.replace(/\s+$/, '');
    +		n = n.replace(/\s+$/, '');
    +		var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/));
    +
    +		var str = "";
    +
    +		var oSpace = o.match(/\s+/g);
    +		if (oSpace == null) {
    +			oSpace = [" "];
    +		}
    +		else {
    +			oSpace.push(" ");
    +		}
    +		var nSpace = n.match(/\s+/g);
    +		if (nSpace == null) {
    +			nSpace = [" "];
    +		}
    +		else {
    +			nSpace.push(" ");
    +		}
    +
    +		if (out.n.length == 0) {
    +			for (var i = 0; i < out.o.length; i++) {
    +				str += '<del>' + out.o[i] + oSpace[i] + "</del>";
    +			}
    +		}
    +		else {
    +			if (out.n[0].text == null) {
    +				for (n = 0; n < out.o.length && out.o[n].text == null; n++) {
    +					str += '<del>' + out.o[n] + oSpace[n] + "</del>";
    +				}
    +			}
    +
    +			for (var i = 0; i < out.n.length; i++) {
    +				if (out.n[i].text == null) {
    +					str += '<ins>' + out.n[i] + nSpace[i] + "</ins>";
    +				}
    +				else {
    +					var pre = "";
    +
    +					for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) {
    +						pre += '<del>' + out.o[n] + oSpace[n] + "</del>";
    +					}
    +					str += " " + out.n[i].text + nSpace[i] + pre;
    +				}
    +			}
    +		}
    +
    +		return str;
    +	};
    +})();
    +
    +})(this);
    diff --git a/lib/touch-adapter-0.1.js b/lib/touch-adapter-0.1.js
    new file mode 100644
    index 000000000..07259e7ae
    --- /dev/null
    +++ b/lib/touch-adapter-0.1.js
    @@ -0,0 +1,177 @@
    +/*
    +	Wraps touch events and presents them as mouse events: you register for standard mouse events such as 
    +	click, mousedown, mouseup and mousemove, and the touch adapter will automatically register corresponding
    +	touch events for each of these.  note that 'click' is achieved through setting a timer on touchstart and
    +	firing an event on touchend if the timer has not yet expired. The delay for this timer can be set on 
    +	the touchadapter's constructor (clickDelay); the default is 150ms.
    +
    +	Note that TouchAdapter can run without any supporting library - it contains event bind and unbind
    +	functions internally, which operate on DOM nodes - but is also designed to support any library of
    +	your choice. It does this by allowing you to provide 'bind' and 'unbind' functions to the constructor. 
    +	This arrangement gives us the flexibility to create wrappers that provide different implementations of
    +	bind and unbind: for instance, we could create a jQuery 'on', delegate' or 'live' version, as well as one
    +	that uses the standard 'bind' and 'unbind' functions.
    +
    +	An additional function that is required if you wrap the basic DOM functionality is an 'unwrap' function,
    +	which is used to get the original browser event from some event wrapped by the library.
    +
    +*/
    +;(function() {
    +
    +	var isIOS = ((/iphone|ipad/gi).test(navigator.appVersion)),
    +		isAndroid = false,
    +		click = "click", dblclick = "dblclick", mousedown = "mousedown", mouseup = "mouseup", mousemove = "mousemove",
    +		touchstart = "touchstart", touchend = "touchend", touchmove = "touchmove",
    +		contextmenu = "contextmenu", ta_is_down = "__touchAdaptorIsDown", ta_click_timeout = "__touchAdaptorClickTimeout",
    +		ta_context_menu_timeout = "__touchAdaptorContextMenuTimeout",
    +		ta_down = "__touchAdapterDown", ta_up = "__touchAdapterUp", 
    +		ta_context_down = "__touchAdapterContextDown", ta_context_up = "__touchAdapterContextUp",
    +		//http://www.quirksmode.org/blog/archives/2005/10/_and_the_winner_1.html
    +		addEvent = function( obj, type, fn ) {
    +			if (obj.addEventListener)
    +				obj.addEventListener( type, fn, false );
    +			else if (obj.attachEvent)
    +			{
    +				obj["e"+type+fn] = fn;
    +				obj[type+fn] = function() { obj["e"+type+fn]( window.event ); }
    +				obj.attachEvent( "on"+type, obj[type+fn] );
    +			}
    +		},
    +		removeEvent = function( obj, type, fn ) {
    +			if (obj.removeEventListener)
    +				obj.removeEventListener( type, fn, false );
    +			else if (obj.detachEvent)
    +			{
    +				obj.detachEvent( "on"+type, obj[type+fn] );
    +				obj[type+fn] = null;
    +				obj["e"+type+fn] = null;
    +			}
    +		};	
    +
    +	window.TouchAdapter = function(params) {
    +		params = params || {};
    +		var self = this, _bind = params.bind || addEvent,
    +			_unbind = params.unbind || removeEvent,
    +			_unwrap = params.unwrap || function(e) { return e; },
    +			wrapClick = params.wrapClick !== false,
    +			clickDelay = params.clickDelay || 150,
    +			wrapDblClick = params.wrapDblClick !== false,
    +			doubleClickThreshold = params.doubleClickThreshold || 250,
    +			wrapContextMenu = params.wrapContextMenu !== false,
    +			wrapDown = params.wrapDown !== false,
    +			wrapUp = params.wrapUp !== false,
    +			wrapMove = params.wrapMove !== false,
    +			_getTouchCount = function(e) {
    +				var t = 1;
    +				if (e.touches) {
    +					t = e.touches.length;	  // what about android? and other mobile browsers?
    +				}				
    +				return t;
    +			},
    +			_addClickWrapper = function(obj, fn, touchCount, downId, upId, supportDoubleClick) {
    +				var handler = {
    +					down:false,
    +					touches:0,
    +					originalEvent:null,
    +					lastClick:null,
    +					timeout:null
    +				};
    +				var down = function(e) {						
    +					var ee = _unwrap(e), self = this, tc = _getTouchCount(ee);					
    +					if (tc == touchCount) {
    +				
    +						handler.originalEvent = ee;	
    +						handler.touches = tc;										
    +						handler.down = true;							
    +						handler.timeout = window.setTimeout(function() {														
    +							handler.down = null;
    +						}, clickDelay);
    +					}
    +				};
    +				fn[downId] = down;
    +				_bind(obj, touchstart, down);	
    +				var up = function(e) {										
    +					var ee =  _unwrap(e);					
    +					if (handler.down) {
    +						// if supporting double click, check if their is a timestamp for a recent click
    +						if (supportDoubleClick) {
    +							var t = new Date().getTime();
    +							if (handler.lastClick) {							
    +								if (t - handler.lastClick < doubleClickThreshold)
    +									fn(handler.originalEvent);
    +							}
    +
    +							handler.lastClick = t;
    +						}					
    +						else 	
    +							fn(handler.originalEvent);						
    +					}
    +					handler.down = null;
    +					window.clearTimeout(handler.timeout);						
    +				};				
    +				fn[upId] = up;	
    +				_bind(obj, touchend, up);
    +			};
    +		
    +		this.bind = function(obj, evt, fn) {
    +			if (isIOS) {			
    +				if (evt === click && wrapClick) {
    +					_addClickWrapper(obj, fn, 1, ta_down, ta_up);
    +				}
    +				else if (evt === dblclick && wrapDblClick) {
    +					_addClickWrapper(obj, fn, 1, ta_down, ta_up, true);
    +				}
    +				else if (evt === contextmenu && wrapContextMenu) {
    +					_addClickWrapper(obj, fn, 2, ta_context_down, ta_context_up);
    +				}
    +				else if (evt === mousedown && wrapDown) {
    +					_bind(obj, touchstart, fn);	
    +				}
    +				else if (evt === mouseup && wrapUp) {
    +					_bind(obj, touchend, fn);
    +				}			
    +				else if (evt === mousemove && wrapMove) {
    +					_bind(obj, touchmove, fn);
    +				}
    +				else
    +					_bind(obj, evt, fn);
    +			}
    +			else 
    +				_bind(obj, evt, fn);
    +
    +			return self;
    +		};
    +
    +		this.unbind = function(obj, evt, fn) {
    +			if (isIOS) {
    +				if (evt === click && wrapClick) {					
    +					_unbind(obj, touchstart, fn[ta_down]);
    +					fn[ta_down] = null;
    +					_unbind(obj, touchend, fn[ta_up]);
    +					fn[ta_up] = null;
    +				}
    +				else if (evt === contextmenu && wrapContextMenu) {
    +					_unbind(obj, touchstart, fn[ta_context_down]);
    +					fn[ta_context_down] = null;
    +					_unbind(obj, touchend, fn[ta_context_up]);
    +					fn[ta_context_up] = null;
    +				}
    +				else if (evt == mousedown && wrapDown) {
    +					_unbind(obj, touchstart, fn);	
    +				}
    +				else if (evt == mouseup && wrapUp) {
    +					_unbind(obj, touchend, fn);
    +				}	
    +				else if (evt == mousemove && wrapMove) {
    +					_unbind(obj, touchmove, fn);
    +				}
    +				else
    +					_unbind(obj, evt, fn);
    +			}
    +			_unbind(obj, evt, fn);
    +
    +			return self;
    +		};
    +	};
    +
    +})();
    diff --git a/lib/yui-min.js b/lib/yui-min.js
    new file mode 100644
    index 000000000..e916ad869
    --- /dev/null
    +++ b/lib/yui-min.js
    @@ -0,0 +1,12 @@
    +/*
    +Copyright (c) 2010, Yahoo! Inc. All rights reserved.
    +Code licensed under the BSD License:
    +http://developer.yahoo.com/yui/license.html
    +version: 3.1.1
    +build: 47
    +*/
    +if(typeof YUI==="undefined"){var YUI=function(){var D=this,B=arguments,C,A=B.length,E=(typeof YUI_config!=="undefined")&&YUI_config;if(!(D instanceof YUI)){D=new YUI();for(C=0;C<A;C++){D._config(B[C]);}return D;}else{D._init();if(E){D._config(E);}for(C=0;C<A;C++){D._config(B[C]);}D._setup();return D;}};}(function(){var L,B,M="3.1.1",K="http://yui.yahooapis.com/",P="yui3-js-enabled",I=function(){},G=Array.prototype.slice,N={"io.xdrReady":1,"io.xdrResponse":1,"SWF.eventHandler":1},F=(typeof window!="undefined"),E=(F)?window:null,R=(F)?E.document:null,D=R&&R.documentElement,A=D&&D.className,C={},H=new Date().getTime(),J=function(V,U,T,S){if(V&&V.addEventListener){V.addEventListener(U,T,S);}else{if(V&&V.attachEvent){V.attachEvent("on"+U,T);}}},Q=function(W,V,U,S){if(W&&W.removeEventListener){try{W.removeEventListener(V,U,S);}catch(T){}}else{if(W&&W.detachEvent){W.detachEvent("on"+V,U);}}},O=function(){YUI.Env.windowLoaded=true;YUI.Env.DOMReady=true;if(F){Q(window,"load",O);}};if(D&&A.indexOf(P)==-1){if(A){A+=" ";}A+=P;D.className=A;}if(M.indexOf("@")>-1){M="3.0.0";}YUI.prototype={_config:function(Y){Y=Y||{};var T,V,W,U=this.config,X=U.modules,S=U.groups;for(V in Y){T=Y[V];if(X&&V=="modules"){for(W in T){X[W]=T[W];}}else{if(S&&V=="groups"){for(W in T){S[W]=T[W];}}else{if(V=="win"){U[V]=T.contentWindow||T;U.doc=U[V].document;}else{U[V]=T;}}}}},_init:function(){var U,V=this,S=YUI.Env,T=V.Env;V.version=M;if(!T){V.Env={mods:{},base:K,cdn:K+M+"/build/",bootstrapped:false,_idx:0,_used:{},_attached:{},_yidx:0,_uidx:0,_guidp:"y",_loaded:{},getBase:function(c,a){var W,X,Z,d,Y;X=(R&&R.getElementsByTagName("script"))||[];for(Z=0;Z<X.length;Z=Z+1){d=X[Z].src;if(d){Y=d.match(c);W=Y&&Y[1];if(W){U=Y[2];Y=d.match(a);if(Y&&Y[3]){W=Y[1]+Y[3];}break;}}}return W||T.cdn;}};T=V.Env;T._loaded[M]={};if(S&&V!==YUI){T._yidx=++S._yidx;T._guidp=("yui_"+M+"_"+T._yidx+"_"+H).replace(/\./g,"_");}V.id=V.stamp(V);C[V.id]=V;}V.constructor=YUI;V.config=V.config||{win:E,doc:R,debug:true,useBrowserConsole:true,throwFail:true,bootstrap:true,fetchCSS:true};V.config.base=YUI.config.base||V.Env.getBase(/^(.*)yui\/yui([\.\-].*)js(\?.*)?$/,/^(.*\?)(.*\&)(.*)yui\/yui[\.\-].*js(\?.*)?$/);V.config.loaderPath=YUI.config.loaderPath||"loader/loader"+(U||"-min.")+"js";},_setup:function(X){var T,W=this,S=[],V=YUI.Env.mods,U=W.config.core||["get","intl-base","loader","yui-log","yui-later","yui-throttle"];for(T=0;T<U.length;T++){if(V[U[T]]){S.push(U[T]);}}W.use("yui-base");W.use.apply(W,S);},applyTo:function(Y,X,U){if(!(X in N)){this.log(X+": applyTo not allowed","warn","yui");return null;}var T=C[Y],W,S,V;if(T){W=X.split(".");S=T;for(V=0;V<W.length;V=V+1){S=S[W[V]];if(!S){this.log("applyTo not found: "+X,"warn","yui");}}return S.apply(T,U);}return null;},add:function(T,V,S,U){U=U||{};YUI.Env.mods[T]={name:T,fn:V,version:S,details:U};return this;},_attach:function(S,W){var Y,V,b,T,a,U,c=YUI.Env.mods,X=this.Env._attached,Z=S.length;for(Y=0;Y<Z;Y++){V=S[Y];b=c[V];if(!X[V]&&b){X[V]=true;T=b.details;a=T.requires;U=T.use;if(a&&a.length){this._attach(this.Array(a));}if(b.fn){b.fn(this,V);}if(U&&U.length){this._attach(this.Array(U));}}}},use:function(){if(!this.Array){this._attach(["yui-base"]);}var i,c,j,T=this,k=YUI.Env,U=G.call(arguments,0),V=k.mods,S=T.Env,Z=S._used,g=k._loaderQueue,m=U[0],W=U[U.length-1],b=T.Array,l=T.config,a=l.bootstrap,h=[],e=[],X=l.fetchCSS,f=function(o){e.push(o);if(Z[o]){return;}var Y=V[o],p,n;if(Y){Z[o]=true;p=Y.details.requires;n=Y.details.use;}else{if(!k._loaded[M][o]){h.push(o);}else{Z[o]=true;}}if(p){b.each(b(p),f);}if(n){b.each(b(n),f);}},d=function(q){var o=q||{success:true,msg:"not dynamic"},p,n,Y,r=o.data;T._loading=false;if(r){Y=h.concat();h=[];T.Array.each(r,f);n=h.length;if(n){if(h.sort().join()==Y.sort().join()){n=false;}}}if(n&&r){p=r.concat();p.push(function(){T._attach(r);if(W){W(T,o);}});T._loading=false;T.use.apply(T,p);}else{if(r){T._attach(r);}if(W){W(T,o);}}if(T._useQueue&&T._useQueue.size()&&!T._loading){T.use.apply(T,T._useQueue.next());}};if(T._loading){T._useQueue=T._useQueue||new T.Queue();T._useQueue.add(U);return T;}if(typeof W==="function"){U.pop();}else{W=null;}if(m==="*"){U=T.Object.keys(V);}if(T.Loader){c=new T.Loader(l);c.require(U);c.ignoreRegistered=true;c.calculate(null,(X)?null:"js");U=c.sorted;}b.each(U,f);i=h.length;if(i){h=T.Object.keys(b.hash(h));i=h.length;}if(a&&i&&T.Loader){T._loading=true;c=new T.Loader(l);c.onEnd=d;c.context=T;c.attaching=U;c.data=U;c.require((X)?h:U);c.insert(null,(X)?null:"js");}else{if(a&&i&&T.Get&&!S.bootstrapped){T._loading=true;U=b(arguments,0,true);j=function(){T._loading=false;g.running=false;S.bootstrapped=true;T._attach(["loader"]);T.use.apply(T,U);};if(k._bootstrapping){g.add(j);}else{k._bootstrapping=true;T.Get.script(l.base+l.loaderPath,{onEnd:j});}}else{if(i){T.message("Requirement NOT loaded: "+h,"warn","yui");}T._attach(e);d();}}return T;},namespace:function(){var S=arguments,W=null,U,T,V;for(U=0;U<S.length;U=U+1){V=(""+S[U]).split(".");W=this;for(T=(V[0]=="YAHOO")?1:0;T<V.length;T=T+1){W[V[T]]=W[V[T]]||{};W=W[V[T]];}}return W;},log:I,message:I,error:function(T,S){if(this.config.throwFail){throw (S||new Error(T));}else{this.message(T,"error");}return this;},guid:function(S){var T=this.Env._guidp+(++this.Env._uidx);return(S)?(S+T):T;},stamp:function(U,V){if(!U){return U;}var S=(typeof U==="string")?U:U._yuid;if(!S){S=this.guid();if(!V){try{U._yuid=S;}catch(T){S=null;}}}return S;}};L=YUI.prototype;for(B in L){YUI[B]=L[B];}YUI._init();if(F){J(window,"load",O);}else{O();}YUI.Env.add=J;YUI.Env.remove=Q;if(typeof exports=="object"){exports.YUI=YUI;}})();YUI.add("yui-base",function(B){(function(){B.Lang=B.Lang||{};var R=B.Lang,G="array",I="boolean",D="date",M="error",S="function",H="number",K="null",F="object",O="regexp",N="string",C=Object.prototype.toString,P="undefined",E={"undefined":P,"number":H,"boolean":I,"string":N,"[object Function]":S,"[object RegExp]":O,"[object Array]":G,"[object Date]":D,"[object Error]":M},J=/^\s+|\s+$/g,Q="";R.isArray=function(L){return R.type(L)===G;
    +};R.isBoolean=function(L){return typeof L===I;};R.isFunction=function(L){return R.type(L)===S;};R.isDate=function(L){return R.type(L)===D&&L.toString()!=="Invalid Date"&&!isNaN(L);};R.isNull=function(L){return L===null;};R.isNumber=function(L){return typeof L===H&&isFinite(L);};R.isObject=function(U,T){var L=typeof U;return(U&&(L===F||(!T&&(L===S||R.isFunction(U)))))||false;};R.isString=function(L){return typeof L===N;};R.isUndefined=function(L){return typeof L===P;};R.trim=function(L){try{return L.replace(J,Q);}catch(T){return L;}};R.isValue=function(T){var L=R.type(T);switch(L){case H:return isFinite(T);case K:case P:return false;default:return !!(L);}};R.type=function(L){return E[typeof L]||E[C.call(L)]||(L?F:K);};})();(function(){var C=B.Lang,D=Array.prototype,E="length",F=function(M,K,I){var J=(I)?2:F.test(M),H,G,N=K||0;if(J){try{return D.slice.call(M,N);}catch(L){G=[];H=M.length;for(;N<H;N++){G.push(M[N]);}return G;}}else{return[M];}};B.Array=F;F.test=function(I){var G=0;if(C.isObject(I)){if(C.isArray(I)){G=1;}else{try{if((E in I)&&!I.tagName&&!I.alert&&!I.apply){G=2;}}catch(H){}}}return G;};F.each=(D.forEach)?function(G,H,I){D.forEach.call(G||[],H,I||B);return B;}:function(H,J,K){var G=(H&&H.length)||0,I;for(I=0;I<G;I=I+1){J.call(K||B,H[I],I,H);}return B;};F.hash=function(I,H){var L={},G=I.length,K=H&&H.length,J;for(J=0;J<G;J=J+1){if(I[J]){L[I[J]]=(K&&K>J)?H[J]:true;}}return L;};F.indexOf=(D.indexOf)?function(G,H){return D.indexOf.call(G,H);}:function(G,I){for(var H=0;H<G.length;H=H+1){if(G[H]===I){return H;}}return -1;};F.numericSort=function(H,G){return(H-G);};F.some=(D.some)?function(G,H,I){return D.some.call(G,H,I);}:function(H,J,K){var G=H.length,I;for(I=0;I<G;I=I+1){if(J.call(K,H[I],I,H)){return true;}}return false;};})();function A(){this._init();this.add.apply(this,arguments);}A.prototype={_init:function(){this._q=[];},next:function(){return this._q.shift();},last:function(){return this._q.pop();},add:function(){B.Array.each(B.Array(arguments,0,true),function(C){this._q.push(C);},this);return this;},size:function(){return this._q.length;}};B.Queue=A;YUI.Env._loaderQueue=YUI.Env._loaderQueue||new A();(function(){var D=B.Lang,C="__",E=function(H,G){var F=G.toString;if(D.isFunction(F)&&F!=Object.prototype.toString){H.toString=F;}};B.merge=function(){var G=arguments,I={},H,F=G.length;for(H=0;H<F;H=H+1){B.mix(I,G[H],true);}return I;};B.mix=function(F,O,H,N,K,M){if(!O||!F){return F||B;}if(K){switch(K){case 1:return B.mix(F.prototype,O.prototype,H,N,0,M);case 2:B.mix(F.prototype,O.prototype,H,N,0,M);break;case 3:return B.mix(F,O.prototype,H,N,0,M);case 4:return B.mix(F.prototype,O,H,N,0,M);default:}}var J,I,G,L;if(N&&N.length){for(J=0,I=N.length;J<I;++J){G=N[J];L=D.type(F[G]);if(O.hasOwnProperty(G)){if(M&&L=="object"){B.mix(F[G],O[G]);}else{if(H||!(G in F)){F[G]=O[G];}}}}}else{for(J in O){if(O.hasOwnProperty(J)){if(M&&D.isObject(F[J],true)){B.mix(F[J],O[J],H,N,0,true);}else{if(H||!(J in F)){F[J]=O[J];}}}}if(B.UA.ie){E(F,O);}}return F;};B.cached=function(H,F,G){F=F||{};return function(K,J){var I=(J)?Array.prototype.join.call(arguments,C):K;if(!(I in F)||(G&&F[I]==G)){F[I]=H.apply(H,arguments);}return F[I];};};})();(function(){B.Object=function(H){var G=function(){};G.prototype=H;return new G();};var E=B.Object,F=function(H,G){return H&&H.hasOwnProperty&&H.hasOwnProperty(G);},D=undefined,C=function(K,J){var I=(J===2),G=(I)?0:[],H;for(H in K){if(F(K,H)){if(I){G++;}else{G.push((J)?K[H]:H);}}}return G;};E.keys=function(G){return C(G);};E.values=function(G){return C(G,1);};E.size=function(G){return C(G,2);};E.hasKey=F;E.hasValue=function(H,G){return(B.Array.indexOf(E.values(H),G)>-1);};E.owns=F;E.each=function(K,J,L,I){var H=L||B,G;for(G in K){if(I||F(K,G)){J.call(H,K[G],G,K);}}return B;};E.some=function(K,J,L,I){var H=L||B,G;for(G in K){if(I||F(K,G)){if(J.call(H,K[G],G,K)){return true;}}}return false;};E.getValue=function(K,J){if(!B.Lang.isObject(K)){return D;}var H,I=B.Array(J),G=I.length;for(H=0;K!==D&&H<G;H++){K=K[I[H]];}return K;};E.setValue=function(M,K,L){var G,J=B.Array(K),I=J.length-1,H=M;if(I>=0){for(G=0;H!==D&&G<I;G++){H=H[J[G]];}if(H!==D){H[J[G]]=L;}else{return D;}}return M;};})();B.UA=function(){var F=function(K){var L=0;return parseFloat(K.replace(/\./g,function(){return(L++==1)?"":".";}));},G=B.config.win,J=G&&G.navigator,I={ie:0,opera:0,gecko:0,webkit:0,chrome:0,mobile:null,air:0,caja:J&&J.cajaVersion,secure:false,os:null},E=J&&J.userAgent,H=G&&G.location,D=H&&H.href,C;I.secure=D&&(D.toLowerCase().indexOf("https")===0);if(E){if((/windows|win32/i).test(E)){I.os="windows";}else{if((/macintosh/i).test(E)){I.os="macintosh";}else{if((/rhino/i).test(E)){I.os="rhino";}}}if((/KHTML/).test(E)){I.webkit=1;}C=E.match(/AppleWebKit\/([^\s]*)/);if(C&&C[1]){I.webkit=F(C[1]);if(/ Mobile\//.test(E)){I.mobile="Apple";}else{C=E.match(/NokiaN[^\/]*|Android \d\.\d|webOS\/\d\.\d/);if(C){I.mobile=C[0];}}C=E.match(/Chrome\/([^\s]*)/);if(C&&C[1]){I.chrome=F(C[1]);}else{C=E.match(/AdobeAIR\/([^\s]*)/);if(C){I.air=C[0];}}}if(!I.webkit){C=E.match(/Opera[\s\/]([^\s]*)/);if(C&&C[1]){I.opera=F(C[1]);C=E.match(/Opera Mini[^;]*/);if(C){I.mobile=C[0];}}else{C=E.match(/MSIE\s([^;]*)/);if(C&&C[1]){I.ie=F(C[1]);}else{C=E.match(/Gecko\/([^\s]*)/);if(C){I.gecko=1;C=E.match(/rv:([^\s\)]*)/);if(C&&C[1]){I.gecko=F(C[1]);}}}}}}return I;}();},"3.1.1");YUI.add("get",function(A){(function(){var C=A.UA,B=A.Lang,E="text/javascript",F="text/css",D="stylesheet";A.Get=function(){var M,N,J,L={},K=0,U,W=function(a,X,b){var Y=b||A.config.win,c=Y.document,e=c.createElement(a),Z;for(Z in X){if(X[Z]&&X.hasOwnProperty(Z)){e.setAttribute(Z,X[Z]);}}return e;},T=function(Y,Z,X){var a={id:A.guid(),type:F,rel:D,href:Y};if(X){A.mix(a,X);}return W("link",a,Z);},S=function(Y,Z,X){var a={id:A.guid(),type:E};if(X){A.mix(a,X);}a.src=Y;return W("script",a,Z);},P=function(Y,Z,X){return{tId:Y.tId,win:Y.win,data:Y.data,nodes:Y.nodes,msg:Z,statusText:X,purge:function(){N(this.tId);}};},O=function(b,a,X){var Y=L[b],Z;if(Y&&Y.onEnd){Z=Y.context||Y;
    +Y.onEnd.call(Z,P(Y,a,X));}},V=function(a,Z){var X=L[a],Y;if(X.timer){clearTimeout(X.timer);}if(X.onFailure){Y=X.context||X;X.onFailure.call(Y,P(X,Z));}O(a,Z,"failure");},I=function(a){var X=L[a],Z,Y;if(X.timer){clearTimeout(X.timer);}X.finished=true;if(X.aborted){Z="transaction "+a+" was aborted";V(a,Z);return;}if(X.onSuccess){Y=X.context||X;X.onSuccess.call(Y,P(X));}O(a,Z,"OK");},Q=function(Z){var X=L[Z],Y;if(X.onTimeout){Y=X.context||X;X.onTimeout.call(Y,P(X));}O(Z,"timeout","timeout");},H=function(Z,c){var Y=L[Z],b,g,f,e,a,X,i;if(Y.timer){clearTimeout(Y.timer);}if(Y.aborted){b="transaction "+Z+" was aborted";V(Z,b);return;}if(c){Y.url.shift();if(Y.varName){Y.varName.shift();}}else{Y.url=(B.isString(Y.url))?[Y.url]:Y.url;if(Y.varName){Y.varName=(B.isString(Y.varName))?[Y.varName]:Y.varName;}}g=Y.win;f=g.document;e=f.getElementsByTagName("head")[0];if(Y.url.length===0){I(Z);return;}X=Y.url[0];if(!X){Y.url.shift();return H(Z);}if(Y.timeout){Y.timer=setTimeout(function(){Q(Z);},Y.timeout);}if(Y.type==="script"){a=S(X,g,Y.attributes);}else{a=T(X,g,Y.attributes);}J(Y.type,a,Z,X,g,Y.url.length);Y.nodes.push(a);if(Y.insertBefore){i=M(Y.insertBefore,Z);if(i){i.parentNode.insertBefore(a,i);}}else{e.appendChild(a);}if((C.webkit||C.gecko)&&Y.type==="css"){H(Z,X);}},G=function(){if(U){return;}U=true;var X,Y;for(X in L){if(L.hasOwnProperty(X)){Y=L[X];if(Y.autopurge&&Y.finished){N(Y.tId);delete L[X];}}}U=false;},R=function(Y,X,Z){Z=Z||{};var c="q"+(K++),a,b=Z.purgethreshold||A.Get.PURGE_THRESH;if(K%b===0){G();}L[c]=A.merge(Z,{tId:c,type:Y,url:X,finished:false,nodes:[]});a=L[c];a.win=a.win||A.config.win;a.context=a.context||a;a.autopurge=("autopurge" in a)?a.autopurge:(Y==="script")?true:false;a.attributes=a.attributes||{};a.attributes.charset=Z.charset||a.attributes.charset||"utf-8";setTimeout(function(){H(c);},0);return{tId:c};};J=function(Z,e,d,Y,c,b,X){var a=X||H;if(C.ie){e.onreadystatechange=function(){var f=this.readyState;if("loaded"===f||"complete"===f){e.onreadystatechange=null;a(d,Y);}};}else{if(C.webkit){if(Z==="script"){e.addEventListener("load",function(){a(d,Y);});}}else{e.onload=function(){a(d,Y);};e.onerror=function(f){V(d,f+": "+Y);};}}};M=function(X,a){var Y=L[a],Z=(B.isString(X))?Y.win.document.getElementById(X):X;if(!Z){V(a,"target node not found: "+X);}return Z;};N=function(c){var Y,a,g,e,j,b,Z,f,X=L[c];if(X){Y=X.nodes;a=Y.length;g=X.win.document;e=g.getElementsByTagName("head")[0];if(X.insertBefore){j=M(X.insertBefore,c);if(j){e=j.parentNode;}}for(b=0;b<a;b=b+1){Z=Y[b];if(Z.clearAttributes){Z.clearAttributes();}else{for(f in Z){if(Z.hasOwnProperty(f)){delete Z[f];}}}e.removeChild(Z);}}X.nodes=[];};return{PURGE_THRESH:20,_finalize:function(X){setTimeout(function(){I(X);},0);},abort:function(Y){var Z=(B.isString(Y))?Y:Y.tId,X=L[Z];if(X){X.aborted=true;}},script:function(X,Y){return R("script",X,Y);},css:function(X,Y){return R("css",X,Y);}};}();})();},"3.1.1");YUI.add("intl-base",function(B){var A=/[, ]/;B.mix(B.namespace("Intl"),{lookupBestLang:function(G,H){var F,I,C,E;function D(K){var J;for(J=0;J<H.length;J+=1){if(K.toLowerCase()===H[J].toLowerCase()){return H[J];}}}if(B.Lang.isString(G)){G=G.split(A);}for(F=0;F<G.length;F+=1){I=G[F];if(!I||I==="*"){continue;}while(I.length>0){C=D(I);if(C){return C;}else{E=I.lastIndexOf("-");if(E>=0){I=I.substring(0,E);if(E>=2&&I.charAt(E-2)==="-"){I=I.substring(0,E-2);}}else{break;}}}}return"";}});},"3.1.1",{requires:["yui-base"]});YUI.add("yui-log",function(A){(function(){var E,D=A,F="yui:log",B="undefined",C={debug:1,info:1,warn:1,error:1};D.log=function(I,Q,G,O){var K,N,L,J,M,H=D,P=H.config;if(P.debug){if(G){N=P.logExclude;L=P.logInclude;if(L&&!(G in L)){K=1;}else{if(N&&(G in N)){K=1;}}}if(!K){if(P.useBrowserConsole){J=(G)?G+": "+I:I;if(H.Lang.isFunction(P.logFn)){P.logFn(I,Q,G);}else{if(typeof console!=B&&console.log){M=(Q&&console[Q]&&(Q in C))?Q:"log";console[M](J);}else{if(typeof opera!=B){opera.postError(J);}}}}if(H.fire&&!O){if(!E){H.publish(F,{broadcast:2});E=1;}H.fire(F,{msg:I,cat:Q,src:G});}}}return H;};D.message=function(){return D.log.apply(D,arguments);};})();},"3.1.1",{requires:["yui-base"]});YUI.add("yui-later",function(A){(function(){var B=A.Lang,C=function(K,E,L,G,H){K=K||0;E=E||{};var F=L,J=A.Array(G),I,D;if(B.isString(L)){F=E[L];}if(!F){}I=function(){F.apply(E,J);};D=(H)?setInterval(I,K):setTimeout(I,K);return{id:D,interval:H,cancel:function(){if(this.interval){clearInterval(D);}else{clearTimeout(D);}}};};A.later=C;B.later=C;})();},"3.1.1",{requires:["yui-base"]});YUI.add("yui-throttle",function(Y){
    +/* Based on work by Simon Willison: http://gist.github.com/292562 */
    +var throttle=function(fn,ms){ms=(ms)?ms:(Y.config.throttleTime||150);if(ms===-1){return(function(){fn.apply(null,arguments);});}var last=(new Date()).getTime();return(function(){var now=(new Date()).getTime();if(now-last>ms){last=now;fn.apply(null,arguments);}});};Y.throttle=throttle;},"3.1.1",{requires:["yui-base"]});YUI.add("yui",function(A){},"3.1.1",{use:["yui-base","get","intl-base","yui-log","yui-later","yui-throttle"]});
    diff --git a/src/dom.jsPlumb.js b/src/dom.jsPlumb.js
    new file mode 100644
    index 000000000..f0b366338
    --- /dev/null
    +++ b/src/dom.jsPlumb.js
    @@ -0,0 +1,426 @@
    +/*
    + * jsPlumb
    + * 
    + * Title:jsPlumb 1.4.0
    + * 
    + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
    + * elements, or VML.  
    + * 
    + * This file contains the basic adapter that does not require any support library such as jquery, yui or mootools.
    +
    +	but it's not yet there.  currently this is a copy of the jquery adapter.
    +
    + *
    + * Copyright (c) 2010 - 2013 Simon Porritt (http://jsplumb.org)
    + * 
    + * http://jsplumb.org
    + * http://github.com/sporritt/jsplumb
    + * http://code.google.com/p/jsplumb
    + * 
    + * Dual licensed under the MIT and GPL2 licenses.
    + */ 
    +/* 
    + * the library specific functions, such as find offset, get id, get attribute, extend etc.  
    + * the full list is:
    + * 
    + * addClass				adds a class to the given element
    + * animate				calls the underlying library's animate functionality
    + * appendElement		appends a child element to a parent element.
    + * bind					binds some event to an element
    + * dragEvents			a dictionary of event names
    + * extend				extend some js object with another.  probably not overly necessary; jsPlumb could just do this internally.
    + * getAttribute			gets some attribute from an element
    + * getDragObject		gets the object that is being dragged, by extracting it from the arguments passed to a drag callback
    + * getDragScope			gets the drag scope for a given element.
    + * getDropScope			gets the drop scope for a given element.
    + * getElementObject		turns an id or dom element into an element object of the underlying library's type.
    + * getOffset			gets an element's offset
    + * getOriginalEvent     gets the original browser event from some wrapper event
    + * getPageXY			gets the page event's xy location.
    + * getParent			gets the parent of some element.
    + * getScrollLeft		gets an element's scroll left.  TODO: is this actually used?  will it be?
    + * getScrollTop			gets an element's scroll top.  TODO: is this actually used?  will it be?
    + * getSize				gets an element's size.
    + * getUIPosition		gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback.
    + * hasClass				returns whether or not the given element has the given class.
    + * initDraggable		initializes an element to be draggable 
    + * initDroppable		initializes an element to be droppable
    + * isDragSupported		returns whether or not drag is supported for some element.
    + * isDropSupported		returns whether or not drop is supported for some element.
    + * removeClass			removes a class from a given element.
    + * removeElement		removes some element completely from the DOM.
    + * setAttribute			sets an attribute on some element.
    + * setDraggable			sets whether or not some element should be draggable.
    + * setDragScope			sets the drag scope for a given element.
    + * setOffset			sets the offset of some element.
    + * trigger				triggers some event on an element.
    + * unbind				unbinds some listener from some element.
    + */
    +(function($) {	
    +	
    +	//var getBoundingClientRectSupported = "getBoundingClientRect" in document.documentElement;
    +    
    +    var _setClassName = function(el, val) {        
    +            if (el.className.baseVal) 
    +                el.className.baseVal = val;
    +            else 
    +                el.className = val;
    +        },
    +        _getClassName = function(el) {
    +            return el.className.baseVal != null ? el.className.baseVal : el.className;
    +        },
    +        _classManip = function(el, add, clazz) {
    +		  var classesToAddOrRemove = clazz.split(" "),			
    +			  curClasses = _getClassName(el).split(" ");
    +			
    +            for (var i = 0; i < classesToAddOrRemove.length; i++) {
    +                if (add) {
    +                    if (curClasses.indexOf(classesToAddOrRemove[i]) == -1)
    +                        curClasses.push(classesToAddOrRemove[i]);
    +                }
    +                else {
    +                    var idx = curClasses.indexOf(classesToAddOrRemove[i]);
    +                    if (idx != -1)
    +                        curClasses.splice(idx, 1);
    +                }
    +            }
    +            
    +            _setClassName(el, curClasses.join(" "));
    +        },
    +        _addClass = function(el, clazz) {
    +            _classManip(el, true, clazz);
    +        },
    +        _removeClass = function(el, clazz) {
    +            _classManip(el, false, clazz);
    +        };
    +    
    +
    +	jsPlumb.CurrentLibrary = {					        
    +		
    +		/**
    +		 * adds the given class to the element object.
    +		 */
    +		addClass : function(el, clazz) {
    +			el = jsPlumb.CurrentLibrary.getElementObject(el);
    +			try {
    +				if (el[0].className.constructor == SVGAnimatedString) {
    +					jsPlumbUtil.svg.addClass(el[0], clazz);                    
    +				}
    +			}
    +			catch (e) {
    +				// SVGAnimatedString not supported; no problem.
    +			}
    +            try {                
    +                el.addClass(clazz);
    +            }
    +            catch (e) {
    +                // you probably have jQuery 1.9 and Firefox.  
    +            }
    +		},
    +		
    +		/**
    +		 * animates the given element.
    +		 */
    +		animate : function(el, properties, options) {
    +			el.animate(properties, options);
    +		},				
    +		
    +		/**
    +		 * appends the given child to the given parent.
    +		 */
    +		appendElement : function(child, parent) {
    +			jsPlumb.CurrentLibrary.getElementObject(parent).append(child);			
    +		},   
    +
    +		/**
    +		* executes an ajax call.
    +		*/
    +		ajax : function(params) {
    +			params = params || {};
    +			params.type = params.type || "get";
    +			$.ajax(params);
    +		},
    +		
    +		/**
    +		 * event binding wrapper.  it just so happens that jQuery uses 'bind' also.  yui3, for example,
    +		 * uses 'on'.
    +		 */
    +		bind : function(el, event, callback) {
    +			el = jsPlumb.CurrentLibrary.getElementObject(el);
    +			el.bind(event, callback);
    +		},
    +		
    +		/**
    +         * mapping of drag events for jQuery
    +         */
    +		dragEvents : {
    +			'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step',
    +			'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete'
    +		},
    +				
    +		/**
    +		 * wrapper around the library's 'extend' functionality (which it hopefully has.
    +		 * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you
    +		 * instead.  it's not like its hard.
    +		 */
    +		extend : function(o1, o2) {
    +			return $.extend(o1, o2);
    +		},
    +		
    +		/**
    +		 * gets the named attribute from the given element object.  
    +		 */
    +		getAttribute : function(el, attName) {
    +			return el.attr(attName);
    +		},
    +		
    +		getClientXY : function(eventObject) {
    +			return [eventObject.clientX, eventObject.clientY];
    +		},
    +		
    +		/**
    +		 * takes the args passed to an event function and returns you an object representing that which is being dragged.
    +		 */
    +		getDragObject : function(eventArgs) {
    +			return eventArgs[1].draggable || eventArgs[1].helper;
    +		},
    +		
    +		getDragScope : function(el) {
    +			return el.draggable("option", "scope");
    +		},
    +
    +		getDropEvent : function(args) {
    +			return args[0];
    +		},
    +		
    +		getDropScope : function(el) {
    +			return el.droppable("option", "scope");		
    +		},
    +
    +		/**
    +		* gets a DOM element from the given input, which might be a string (in which case we just do document.getElementById),
    +		* a selector (in which case we return el[0]), or a DOM element already (we assume this if it's not either of the other
    +		* two cases).  this is the opposite of getElementObject below.
    +		*/
    +		getDOMElement : function(el) {
    +			if (typeof(el) == "string") return document.getElementById(el);
    +			else if (el.context || el.length != null) return el[0];
    +			else return el;
    +		},
    +	
    +		/**
    +		 * gets an "element object" from the given input.  this means an object that is used by the
    +		 * underlying library on which jsPlumb is running.  'el' may already be one of these objects,
    +		 * in which case it is returned as-is.  otherwise, 'el' is a String, the library's lookup 
    +		 * function is used to find the element, using the given String as the element's id.
    +		 * 
    +		 */		
    +		getElementObject : function(el) {			
    +			return typeof(el) == "string" ? $("#" + el) : $(el);
    +		},
    +		
    +		/**
    +		  * gets the offset for the element object.  this should return a js object like this:
    +		  *
    +		  * { left:xxx, top: xxx }
    +		 */
    +		getOffset : function(el) {
    +			return el.offset();
    +		},
    +
    +		getOriginalEvent : function(e) {
    +			return e.originalEvent;
    +		},
    +		
    +		getPageXY : function(eventObject) {
    +			return [eventObject.pageX, eventObject.pageY];
    +		},
    +		
    +		getParent : function(el) {
    +			return jsPlumb.CurrentLibrary.getElementObject(el).parent();
    +		},
    +														
    +		getScrollLeft : function(el) {
    +			return el.scrollLeft();
    +		},
    +		
    +		getScrollTop : function(el) {
    +			return el.scrollTop();
    +		},
    +		
    +		getSelector : function(context, spec) {
    +            if (arguments.length == 2)
    +                return jsPlumb.CurrentLibrary.getElementObject(context).find(spec);
    +            else
    +                return $(context);
    +		},
    +		
    +		/**
    +		 * gets the size for the element object, in an array : [ width, height ].
    +		 */
    +		getSize : function(el) {
    +			return [el.outerWidth(), el.outerHeight()];
    +		},
    +
    +        getTagName : function(el) {
    +            var e = jsPlumb.CurrentLibrary.getElementObject(el);
    +            return e.length > 0 ? e[0].tagName : null;
    +        },
    +		
    +		/**
    +		 * takes the args passed to an event function and returns you an object that gives the
    +		 * position of the object being moved, as a js object with the same params as the result of
    +		 * getOffset, ie: { left: xxx, top: xxx }.
    +		 * 
    +		 * different libraries have different signatures for their event callbacks.  
    +		 * see getDragObject as well
    +		 */
    +		getUIPosition : function(eventArgs, zoom) {
    +			
    +			zoom = zoom || 1;
    +			// this code is a workaround for the case that the element being dragged has a margin set on it. jquery UI passes
    +			// in the wrong offset if the element has a margin (it doesn't take the margin into account).  the getBoundingClientRect
    +			// method, which is in pretty much all browsers now, reports the right numbers.  but it introduces a noticeable lag, which
    +			// i don't like.
    +            
    +			/*if ( getBoundingClientRectSupported ) {
    +				var r = eventArgs[1].helper[0].getBoundingClientRect();
    +				return { left : r.left, top: r.top };
    +			} else {*/
    +			if (eventArgs.length == 1) {
    +				ret = { left: eventArgs[0].pageX, top:eventArgs[0].pageY };
    +			}
    +			else {
    +				var ui = eventArgs[1],
    +				  _offset = ui.offset;
    +				  
    +				ret = _offset || ui.absolutePosition;
    +				
    +				// adjust ui position to account for zoom, because jquery ui does not do this.
    +				ui.position.left /= zoom;
    +				ui.position.top /= zoom;
    +			}
    +            return { left:ret.left / zoom, top: ret.top / zoom };
    +		},		
    +		
    +		hasClass : function(el, clazz) {
    +			return el.hasClass(clazz);
    +		},
    +		
    +		/**
    +		 * initialises the given element to be draggable.
    +		 */
    +		initDraggable : function(el, options, isPlumbedComponent) {
    +			options = options || {};
    +			// remove helper directive if present and no override
    +			if (!options.doNotRemoveHelper)
    +				options.helper = null;
    +			if (isPlumbedComponent)
    +				options['scope'] = options['scope'] || jsPlumb.Defaults.Scope;
    +			el.draggable(options);
    +		},
    +		
    +		/**
    +		 * initialises the given element to be droppable.
    +		 */
    +		initDroppable : function(el, options) {
    +			options['scope'] = options['scope'] || jsPlumb.Defaults.Scope;
    +			el.droppable(options);
    +		},
    +		
    +		isAlreadyDraggable : function(el) {
    +			el = jsPlumb.CurrentLibrary.getElementObject(el);
    +			return el.hasClass("ui-draggable");
    +		},
    +		
    +		/**
    +		 * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element.
    +		 */
    +		isDragSupported : function(el, options) {
    +			return el.draggable;
    +		},				
    +						
    +		/**
    +		 * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element.
    +		 */
    +		isDropSupported : function(el, options) {
    +			return el.droppable;
    +		},							
    +		
    +		/**
    +		 * removes the given class from the element object.
    +		 */
    +		removeClass : function(el, clazz) {
    +			el = jsPlumb.CurrentLibrary.getElementObject(el);
    +			try {
    +				if (el[0].className.constructor == SVGAnimatedString) {
    +					jsPlumbUtil.svg.removeClass(el[0], clazz);
    +                    return;
    +				}
    +			}
    +			catch (e) {
    +				// SVGAnimatedString not supported; no problem.
    +			}
    +			el.removeClass(clazz);
    +		},
    +		
    +		removeElement : function(element) {			
    +			jsPlumb.CurrentLibrary.getElementObject(element).remove();
    +		},
    +		
    +		/**
    +		 * sets the named attribute on the given element object.  
    +		 */
    +		setAttribute : function(el, attName, attValue) {
    +			el.attr(attName, attValue);
    +		},
    +		
    +		/**
    +		 * sets the draggable state for the given element
    +		 */
    +		setDraggable : function(el, draggable) {
    +			el.draggable("option", "disabled", !draggable);
    +		},
    +		
    +		/**
    +		 * sets the drag scope.  probably time for a setDragOption method (roll this and the one above together)
    +		 * @param el
    +		 * @param scope
    +		 */
    +		setDragScope : function(el, scope) {
    +			el.draggable("option", "scope", scope);
    +		},
    +		
    +		setOffset : function(el, o) {
    +			jsPlumb.CurrentLibrary.getElementObject(el).offset(o);
    +		},
    +		
    +		/**
    +		 * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself.
    +		 * the other libraries do not.  yui, in fact, cannot even pass an original event.  we have to pull out stuff
    +		 * from the originalEvent to put in an options object for YUI. 
    +		 * @param el
    +		 * @param event
    +		 * @param originalEvent
    +		 */
    +		trigger : function(el, event, originalEvent) {
    +			//originalEvent.stopPropagation();
    +			//jsPlumb.CurrentLibrary.getElementObject(el).trigger(originalEvent);
    +            var h = jQuery._data(jsPlumb.CurrentLibrary.getElementObject(el)[0], "handle");
    +            h(originalEvent);
    +            //originalEvent.stopPropagation();
    +		},
    +		
    +		/**
    +		 * event unbinding wrapper.  it just so happens that jQuery uses 'unbind' also.  yui3, for example,
    +		 * uses..something else.
    +		 */
    +		unbind : function(el, event, callback) {
    +			el = jsPlumb.CurrentLibrary.getElementObject(el);
    +			el.unbind(event, callback);
    +		}
    +	};
    +	
    +	$(document).ready(jsPlumb.init);
    +	
    +})(jQuery);
    diff --git a/src/jquery.jsPlumb.js b/src/jquery.jsPlumb.js
    new file mode 100644
    index 000000000..1d8a6b74d
    --- /dev/null
    +++ b/src/jquery.jsPlumb.js
    @@ -0,0 +1,409 @@
    +/*
    + * jsPlumb
    + * 
    + * Title:jsPlumb 1.4.0
    + * 
    + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
    + * elements, or VML.  
    + * 
    + * This file contains the jQuery adapter.
    + *
    + * Copyright (c) 2010 - 2013 Simon Porritt (http://jsplumb.org)
    + * 
    + * http://jsplumb.org
    + * http://github.com/sporritt/jsplumb
    + * http://code.google.com/p/jsplumb
    + * 
    + * Dual licensed under the MIT and GPL2 licenses.
    + */ 
    +/* 
    + * the library specific functions, such as find offset, get id, get attribute, extend etc.  
    + * the full list is:
    + * 
    + * addClass				adds a class to the given element
    + * animate				calls the underlying library's animate functionality
    + * appendElement		appends a child element to a parent element.
    + * bind					binds some event to an element
    + * dragEvents			a dictionary of event names
    + * extend				extend some js object with another.  probably not overly necessary; jsPlumb could just do this internally.
    + * getAttribute			gets some attribute from an element
    + * getDragObject		gets the object that is being dragged, by extracting it from the arguments passed to a drag callback
    + * getDragScope			gets the drag scope for a given element.
    + * getDropScope			gets the drop scope for a given element.
    + * getElementObject		turns an id or dom element into an element object of the underlying library's type.
    + * getOffset			gets an element's offset
    + * getOriginalEvent     gets the original browser event from some wrapper event
    + * getPageXY			gets the page event's xy location.
    + * getParent			gets the parent of some element.
    + * getScrollLeft		gets an element's scroll left.  TODO: is this actually used?  will it be?
    + * getScrollTop			gets an element's scroll top.  TODO: is this actually used?  will it be?
    + * getSize				gets an element's size.
    + * getUIPosition		gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback.
    + * hasClass				returns whether or not the given element has the given class.
    + * initDraggable		initializes an element to be draggable 
    + * initDroppable		initializes an element to be droppable
    + * isDragSupported		returns whether or not drag is supported for some element.
    + * isDropSupported		returns whether or not drop is supported for some element.
    + * removeClass			removes a class from a given element.
    + * removeElement		removes some element completely from the DOM.
    + * setAttribute			sets an attribute on some element.
    + * setDragFilter		sets a filter for some element that indicates areas of the element that should not respond to dragging.
    + * setDraggable			sets whether or not some element should be draggable.
    + * setDragScope			sets the drag scope for a given element.
    + * setOffset			sets the offset of some element.
    + * trigger				triggers some event on an element.
    + * unbind				unbinds some listener from some element.
    + */
    +(function($) {	
    +	
    +	//var getBoundingClientRectSupported = "getBoundingClientRect" in document.documentElement;
    +
    +	var _getElementObject = function(el) {			
    +		return typeof(el) == "string" ? $("#" + el) : $(el);
    +	};
    +
    +	jsPlumb.CurrentLibrary = {					        
    +		
    +		/**
    +		 * adds the given class to the element object.
    +		 */
    +		addClass : function(el, clazz) {
    +			el = _getElementObject(el);
    +			try {
    +				if (el[0].className.constructor == SVGAnimatedString) {
    +					jsPlumbUtil.svg.addClass(el[0], clazz);                    
    +				}
    +			}
    +			catch (e) {
    +				// SVGAnimatedString not supported; no problem.
    +			}
    +            try {                
    +                el.addClass(clazz);
    +            }
    +            catch (e) {
    +                // you probably have jQuery 1.9 and Firefox.  
    +            }
    +		},
    +		
    +		/**
    +		 * animates the given element.
    +		 */
    +		animate : function(el, properties, options) {
    +			el.animate(properties, options);
    +		},				
    +		
    +		/**
    +		 * appends the given child to the given parent.
    +		 */
    +		appendElement : function(child, parent) {
    +			_getElementObject(parent).append(child);			
    +		},   
    +
    +		/**
    +		* executes an ajax call.
    +		*/
    +		ajax : function(params) {
    +			params = params || {};
    +			params.type = params.type || "get";
    +			$.ajax(params);
    +		},
    +		
    +		/**
    +		 * event binding wrapper.  it just so happens that jQuery uses 'bind' also.  yui3, for example,
    +		 * uses 'on'.
    +		 */
    +		bind : function(el, event, callback) {
    +			el = _getElementObject(el);
    +			el.bind(event, callback);
    +		},
    +		
    +		/**
    +         * mapping of drag events for jQuery
    +         */
    +		dragEvents : {
    +			'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step',
    +			'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete'
    +		},
    +				
    +		/**
    +		 * wrapper around the library's 'extend' functionality (which it hopefully has.
    +		 * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you
    +		 * instead.  it's not like its hard.
    +		 */
    +		extend : function(o1, o2) {
    +			return $.extend(o1, o2);
    +		},
    +		
    +		/**
    +		 * gets the named attribute from the given element object.  
    +		 */
    +		getAttribute : function(el, attName) {
    +			return el.attr(attName);
    +		},
    +		
    +		getClientXY : function(eventObject) {
    +			return [eventObject.clientX, eventObject.clientY];
    +		},
    +		
    +		/**
    +		 * takes the args passed to an event function and returns you an object representing that which is being dragged.
    +		 */
    +		getDragObject : function(eventArgs) {
    +			return eventArgs[1].draggable || eventArgs[1].helper;
    +		},
    +		
    +		getDragScope : function(el) {
    +			return el.draggable("option", "scope");
    +		},
    +
    +		getDropEvent : function(args) {
    +			return args[0];
    +		},
    +		
    +		getDropScope : function(el) {
    +			return el.droppable("option", "scope");		
    +		},
    +
    +		/**
    +		* gets a DOM element from the given input, which might be a string (in which case we just do document.getElementById),
    +		* a selector (in which case we return el[0]), or a DOM element already (we assume this if it's not either of the other
    +		* two cases).  this is the opposite of getElementObject below.
    +		*/
    +		getDOMElement : function(el) {
    +			if (typeof(el) == "string") return document.getElementById(el);
    +			else if (el.context || el.length != null) return el[0];
    +			else return el;
    +		},
    +	
    +		/**
    +		 * gets an "element object" from the given input.  this means an object that is used by the
    +		 * underlying library on which jsPlumb is running.  'el' may already be one of these objects,
    +		 * in which case it is returned as-is.  otherwise, 'el' is a String, the library's lookup 
    +		 * function is used to find the element, using the given String as the element's id.
    +		 * 
    +		 */		
    +		getElementObject : _getElementObject,
    +		
    +		/**
    +		  * gets the offset for the element object.  this should return a js object like this:
    +		  *
    +		  * { left:xxx, top: xxx }
    +		 */
    +		getOffset : function(el) {
    +			return el.offset();
    +		},
    +
    +		getOriginalEvent : function(e) {
    +			return e.originalEvent;
    +		},
    +		
    +		getPageXY : function(eventObject) {
    +			return [eventObject.pageX, eventObject.pageY];
    +		},
    +		
    +		getParent : function(el) {
    +			return _getElementObject(el).parent();
    +		},
    +														
    +		getScrollLeft : function(el) {
    +			return el.scrollLeft();
    +		},
    +		
    +		getScrollTop : function(el) {
    +			return el.scrollTop();
    +		},
    +		
    +		getSelector : function(context, spec) {
    +            if (arguments.length == 2)
    +                return _getElementObject(context).find(spec);
    +            else
    +                return $(context);
    +		},
    +		
    +		/**
    +		 * gets the size for the element object, in an array : [ width, height ].
    +		 */
    +		getSize : function(el) {
    +			return [el.outerWidth(), el.outerHeight()];
    +		},
    +
    +        getTagName : function(el) {
    +            var e = _getElementObject(el);
    +            return e.length > 0 ? e[0].tagName : null;
    +        },
    +		
    +		/**
    +		 * takes the args passed to an event function and returns you an object that gives the
    +		 * position of the object being moved, as a js object with the same params as the result of
    +		 * getOffset, ie: { left: xxx, top: xxx }.
    +		 * 
    +		 * different libraries have different signatures for their event callbacks.  
    +		 * see getDragObject as well
    +		 */
    +		getUIPosition : function(eventArgs, zoom) {
    +			
    +			zoom = zoom || 1;
    +			// this code is a workaround for the case that the element being dragged has a margin set on it. jquery UI passes
    +			// in the wrong offset if the element has a margin (it doesn't take the margin into account).  the getBoundingClientRect
    +			// method, which is in pretty much all browsers now, reports the right numbers.  but it introduces a noticeable lag, which
    +			// i don't like.
    +            
    +			/*if ( getBoundingClientRectSupported ) {
    +				var r = eventArgs[1].helper[0].getBoundingClientRect();
    +				return { left : r.left, top: r.top };
    +			} else {*/
    +			if (eventArgs.length == 1) {
    +				ret = { left: eventArgs[0].pageX, top:eventArgs[0].pageY };
    +			}
    +			else {
    +				var ui = eventArgs[1],
    +				  _offset = ui.offset;
    +				  
    +				ret = _offset || ui.absolutePosition;
    +				
    +				// adjust ui position to account for zoom, because jquery ui does not do this.
    +				ui.position.left /= zoom;
    +				ui.position.top /= zoom;
    +			}
    +            return { left:ret.left / zoom, top: ret.top / zoom };
    +		},		
    +		
    +		hasClass : function(el, clazz) {
    +			return el.hasClass(clazz);
    +		},
    +		
    +		/**
    +		 * initialises the given element to be draggable.
    +		 */
    +		initDraggable : function(el, options, isPlumbedComponent, _jsPlumb) {
    +			options = options || {};
    +
    +/*
    +			// css3 transforms
    +			// http://gungfoo.wordpress.com/2013/02/15/jquery-ui-resizabledraggable-with-transform-scale-set/
    +			options.start = _jsPlumb.wrap(options["start"], function(e, ui) {
    +				// TODO why is this 0?				
    +			    ui.position.left = 0;
    +			    ui.position.top = 0;
    +			});
    +
    +			options.drag = _jsPlumb.wrap(options["drag"], function(e, ui) {
    +
    +				console.log("original", ui.originalPosition.left, ui.originalPosition.top);
    +				console.log("current", ui.position.left, ui.position.top);
    +
    +				//var changeLeft = ui.position.left - ui.originalPosition.left; // find change in left
    +			    //var newLeft = ui.originalPosition.left + (changeLeft * _jsPlumb.getZoom()); // adjust new left by our zoomScale
    +			 
    +			    //var changeTop = ui.position.top - ui.originalPosition.top; // find change in top
    +			    //var newTop = ui.originalPosition.top + (changeTop * _jsPlumb.getZoom()); // adjust new top by our zoomScale
    +			 
    +			    //ui.position.left = newLeft;
    +			    //ui.position.top = newTop;
    +
    +			    ui.position.left *= _jsPlumb.getZoom();
    +			    ui.position.top *= _jsPlumb.getZoom();
    +
    +			});
    +*/
    +
    +
    +			// remove helper directive if present and no override
    +			if (!options.doNotRemoveHelper)
    +				options.helper = null;
    +			if (isPlumbedComponent)
    +				options['scope'] = options['scope'] || jsPlumb.Defaults.Scope;
    +			el.draggable(options);
    +		},
    +		
    +		/**
    +		 * initialises the given element to be droppable.
    +		 */
    +		initDroppable : function(el, options) {
    +			options['scope'] = options['scope'] || jsPlumb.Defaults.Scope;
    +			el.droppable(options);
    +		},
    +		
    +		isAlreadyDraggable : function(el) {
    +			return _getElementObject(el).hasClass("ui-draggable");
    +		},
    +		
    +		/**
    +		 * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element.
    +		 */
    +		isDragSupported : function(el, options) {
    +			return el.draggable;
    +		},				
    +						
    +		/**
    +		 * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element.
    +		 */
    +		isDropSupported : function(el, options) {
    +			return el.droppable;
    +		},							
    +		
    +		/**
    +		 * removes the given class from the element object.
    +		 */
    +		removeClass : function(el, clazz) {
    +			el = _getElementObject(el);
    +			try {
    +				if (el[0].className.constructor == SVGAnimatedString) {
    +					jsPlumbUtil.svg.removeClass(el[0], clazz);
    +                    return;
    +				}
    +			}
    +			catch (e) {
    +				// SVGAnimatedString not supported; no problem.
    +			}
    +			el.removeClass(clazz);
    +		},
    +		
    +		removeElement : function(element) {			
    +			_getElementObject(element).remove();
    +		},
    +		
    +		setAttribute : function(el, attName, attValue) {
    +			el.attr(attName, attValue);
    +		},
    +
    +		setDragFilter : function(el, filter) {
    +			if (jsPlumb.CurrentLibrary.isAlreadyDraggable(el))
    +				el.draggable("option", "cancel", filter);
    +		},
    +		
    +		setDraggable : function(el, draggable) {
    +			el.draggable("option", "disabled", !draggable);
    +		},
    +		
    +		setDragScope : function(el, scope) {
    +			el.draggable("option", "scope", scope);
    +		},
    +		
    +		setOffset : function(el, o) {
    +			_getElementObject(el).offset(o);
    +		},
    +		
    +		/**
    +		 * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself.
    +		 * the other libraries do not.  yui, in fact, cannot even pass an original event.  we have to pull out stuff
    +		 * from the originalEvent to put in an options object for YUI. 
    +		 * @param el
    +		 * @param event
    +		 * @param originalEvent
    +		 */
    +		trigger : function(el, event, originalEvent) {
    +			var h = jQuery._data(_getElementObject(el)[0], "handle");
    +            h(originalEvent);
    +		},
    +		
    +		unbind : function(el, event, callback) {
    +			el = _getElementObject(el);
    +			el.unbind(event, callback);
    +		}
    +	};
    +	
    +	$(document).ready(jsPlumb.init);
    +	
    +})(jQuery);
    +
    diff --git a/src/jsPlumb-anchors.js b/src/jsPlumb-anchors.js
    new file mode 100644
    index 000000000..b2612d82e
    --- /dev/null
    +++ b/src/jsPlumb-anchors.js
    @@ -0,0 +1,972 @@
    +/*
    + * jsPlumb
    + * 
    + * Title:jsPlumb 1.4.0
    + * 
    + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
    + * elements, or VML.  
    + * 
    + * This file contains the code for creating and manipulating anchors.
    + *
    + * Copyright (c) 2010 - 2013 Simon Porritt (simon.porritt@gmail.com)
    + * 
    + * http://jsplumb.org
    + * http://github.com/sporritt/jsplumb
    + * http://code.google.com/p/jsplumb
    + * 
    + * Dual licensed under the MIT and GPL2 licenses.
    + */
    +;(function() {	
    +    
    +    //
    +	// manages anchors for all elements.
    +	//
    +	jsPlumb.AnchorManager = function(params) {
    +		var _amEndpoints = {},
    +            continuousAnchors = {},
    +            continuousAnchorLocations = {},
    +            userDefinedContinuousAnchorLocations = {},        
    +            continuousAnchorOrientations = {},
    +            Orientation = { HORIZONTAL : "horizontal", VERTICAL : "vertical", DIAGONAL : "diagonal", IDENTITY:"identity" },
    +			connectionsByElementId = {},
    +			self = this,
    +            anchorLists = {},
    +            jsPlumbInstance = params.jsPlumbInstance,
    +            jpcl = jsPlumb.CurrentLibrary,
    +            floatingConnections = {},
    +            // TODO this functions uses a crude method of determining orientation between two elements.
    +	       // 'diagonal' should be chosen when the angle of the line between the two centers is around
    +	       // one of 45, 135, 225 and 315 degrees. maybe +- 15 degrees.
    +            // used by AnchorManager.redraw
    +            calculateOrientation = function(sourceId, targetId, sd, td, sourceAnchor, targetAnchor) {
    +        
    +                if (sourceId === targetId) return {
    +                    orientation:Orientation.IDENTITY,
    +                    a:["top", "top"]
    +                };
    +        
    +                var theta = Math.atan2((td.centery - sd.centery) , (td.centerx - sd.centerx)),
    +                    theta2 = Math.atan2((sd.centery - td.centery) , (sd.centerx - td.centerx)),
    +                    h = ((sd.left <= td.left && sd.right >= td.left) || (sd.left <= td.right && sd.right >= td.right) ||
    +                        (sd.left <= td.left && sd.right >= td.right) || (td.left <= sd.left && td.right >= sd.right)),
    +                    v = ((sd.top <= td.top && sd.bottom >= td.top) || (sd.top <= td.bottom && sd.bottom >= td.bottom) ||
    +                        (sd.top <= td.top && sd.bottom >= td.bottom) || (td.top <= sd.top && td.bottom >= sd.bottom)),
    +                    possiblyTranslateEdges = function(edges) {
    +                        // this function checks to see if either anchor is Continuous, and if so, runs the suggested edge
    +                        // through the anchor: Continuous anchors can say which faces they support, and they get to choose 
    +                        // whether a certain face is honoured, or, if not, which face to replace it with. the behaviour when
    +                        // choosing an alternate face is to try for the opposite face first, then the next one clockwise, and then
    +                        // the opposite of that one.
    +                        return [
    +                            sourceAnchor.isContinuous ? sourceAnchor.verifyEdge(edges[0]) : edges[0],    
    +                            targetAnchor.isContinuous ? targetAnchor.verifyEdge(edges[1]) : edges[1]
    +                        ];
    +                    },
    +                    out = {
    +                        orientation:Orientation.DIAGONAL,
    +                        theta:theta,
    +                        theta2:theta2
    +                    };                        
    +                
    +                if (! (h || v)) {                    
    +                    if (td.left > sd.left && td.top > sd.top)
    +                        out.a = ["right", "top"];
    +                    else if (td.left > sd.left && sd.top > td.top)
    +                        out.a = [ "top", "left"];
    +                    else if (td.left < sd.left && td.top < sd.top)
    +                        out.a = [ "top", "right"];
    +                    else if (td.left < sd.left && td.top > sd.top)
    +                        out.a = ["left", "top" ];                            
    +                }
    +                else if (h) {
    +                    out.orientation = Orientation.HORIZONTAL;
    +                    out.a = sd.top < td.top ? ["bottom", "top"] : ["top", "bottom"];                    
    +                }
    +                else {
    +                    out.orientation = Orientation.VERTICAL;
    +                    out.a = sd.left < td.left ? ["right", "left"] : ["left", "right"];
    +                }
    +                
    +                out.a = possiblyTranslateEdges(out.a);
    +                return out;
    +            },
    +                // used by placeAnchors function
    +            placeAnchorsOnLine = function(desc, elementDimensions, elementPosition,
    +                            connections, horizontal, otherMultiplier, reverse) {
    +                var a = [], step = elementDimensions[horizontal ? 0 : 1] / (connections.length + 1);
    +        
    +                for (var i = 0; i < connections.length; i++) {
    +                    var val = (i + 1) * step, other = otherMultiplier * elementDimensions[horizontal ? 1 : 0];
    +                    if (reverse)
    +                      val = elementDimensions[horizontal ? 0 : 1] - val;
    +        
    +                    var dx = (horizontal ? val : other), x = elementPosition[0] + dx,  xp = dx / elementDimensions[0],
    +                        dy = (horizontal ? other : val), y = elementPosition[1] + dy, yp = dy / elementDimensions[1];
    +        
    +                    a.push([ x, y, xp, yp, connections[i][1], connections[i][2] ]);
    +                }
    +        
    +                return a;
    +            },
    +            // used by edgeSortFunctions        
    +            currySort = function(reverseAngles) {
    +                return function(a,b) {
    +                    var r = true;
    +                    if (reverseAngles) {
    +                        if (a[0][0] < b[0][0])
    +                            r = true;
    +                        else
    +                            r = a[0][1] > b[0][1];
    +                    }
    +                    else {
    +                        if (a[0][0] > b[0][0])
    +                            r= true;
    +                        else
    +                            r =a[0][1] > b[0][1];
    +                    }
    +                    return r === false ? -1 : 1;
    +                };
    +            },
    +                // used by edgeSortFunctions
    +            leftSort = function(a,b) {
    +                // first get adjusted values
    +                var p1 = a[0][0] < 0 ? -Math.PI - a[0][0] : Math.PI - a[0][0],
    +                p2 = b[0][0] < 0 ? -Math.PI - b[0][0] : Math.PI - b[0][0];
    +                if (p1 > p2) return 1;
    +                else return a[0][1] > b[0][1] ? 1 : -1;
    +            },
    +                // used by placeAnchors
    +            edgeSortFunctions = {
    +                "top":function(a, b) { return a[0] > b[0] ? 1 : -1 },
    +                "right":currySort(true),
    +                "bottom":currySort(true),
    +                "left":leftSort
    +            },
    +                // used by placeAnchors
    +            _sortHelper = function(_array, _fn) { return _array.sort(_fn); },
    +                // used by AnchorManager.redraw
    +            placeAnchors = function(elementId, _anchorLists) {		
    +                var cd = jsPlumbInstance.getCachedData(elementId), sS = cd.s, sO = cd.o,
    +                placeSomeAnchors = function(desc, elementDimensions, elementPosition, unsortedConnections, isHorizontal, otherMultiplier, orientation) {
    +                    if (unsortedConnections.length > 0) {
    +                        var sc = _sortHelper(unsortedConnections, edgeSortFunctions[desc]), // puts them in order based on the target element's pos on screen			    
    +                            reverse = desc === "right" || desc === "top",
    +                            anchors = placeAnchorsOnLine(desc, elementDimensions,
    +                                                     elementPosition, sc,
    +                                                     isHorizontal, otherMultiplier, reverse );
    +        
    +                        // takes a computed anchor position and adjusts it for parent offset and scroll, then stores it.
    +                        var _setAnchorLocation = function(endpoint, anchorPos) {
    +                            var a = jsPlumbInstance.adjustForParentOffsetAndScroll([anchorPos[0], anchorPos[1]], endpoint.canvas);
    +                            continuousAnchorLocations[endpoint.id] = [ a[0], a[1], anchorPos[2], anchorPos[3] ];
    +                            continuousAnchorOrientations[endpoint.id] = orientation;
    +                        };
    +        
    +                        for (var i = 0; i < anchors.length; i++) {
    +                            var c = anchors[i][4], weAreSource = c.endpoints[0].elementId === elementId, weAreTarget = c.endpoints[1].elementId === elementId;
    +                            if (weAreSource)
    +                                _setAnchorLocation(c.endpoints[0], anchors[i]);
    +                            else if (weAreTarget)
    +                                _setAnchorLocation(c.endpoints[1], anchors[i]);
    +                        }
    +                    }
    +                };
    +        
    +                placeSomeAnchors("bottom", sS, [sO.left,sO.top], _anchorLists.bottom, true, 1, [0,1]);
    +                placeSomeAnchors("top", sS, [sO.left,sO.top], _anchorLists.top, true, 0, [0,-1]);
    +                placeSomeAnchors("left", sS, [sO.left,sO.top], _anchorLists.left, false, 0, [-1,0]);
    +                placeSomeAnchors("right", sS, [sO.left,sO.top], _anchorLists.right, false, 1, [1,0]);
    +            };
    +
    +        this.reset = function() {
    +        	_amEndpoints = {};
    +        	connectionsByElementId = {};
    +            anchorLists = {};
    +        };			
    +        this.addFloatingConnection = function(key, conn) {
    +            floatingConnections[key] = conn;
    +        };
    +        this.removeFloatingConnection = function(key) {
    +            delete floatingConnections[key];
    +        };                                                 
    + 		this.newConnection = function(conn) {
    +			var sourceId = conn.sourceId, targetId = conn.targetId,
    +				ep = conn.endpoints,
    +                doRegisterTarget = true,
    +			    registerConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) {
    +					if ((sourceId == targetId) && otherAnchor.isContinuous){
    +                       // remove the target endpoint's canvas.  we dont need it.
    +                        jpcl.removeElement(ep[1].canvas);
    +                        doRegisterTarget = false;
    +                    }
    +					jsPlumbUtil.addToList(connectionsByElementId, elId, [c, otherEndpoint, otherAnchor.constructor == jsPlumb.DynamicAnchor]);
    +			    };
    +
    +			registerConnection(0, ep[0], ep[0].anchor, targetId, conn);
    +            if (doRegisterTarget)
    +            	registerConnection(1, ep[1], ep[1].anchor, sourceId, conn);
    +		};
    +        var removeEndpointFromAnchorLists = function(endpoint) {
    +            (function(list, eId) {
    +                if (list) {  // transient anchors dont get entries in this list.
    +                    var f = function(e) { return e[4] == eId; };
    +                    jsPlumbUtil.removeWithFunction(list["top"], f);
    +                    jsPlumbUtil.removeWithFunction(list["left"], f);
    +                    jsPlumbUtil.removeWithFunction(list["bottom"], f);
    +                    jsPlumbUtil.removeWithFunction(list["right"], f);
    +                }
    +            })(anchorLists[endpoint.elementId], endpoint.id);
    +        };
    +		this.connectionDetached = function(connInfo) {
    +            var connection = connInfo.connection || connInfo,
    +			    sourceId = connection.sourceId,
    +                targetId = connection.targetId,
    +				ep = connection.endpoints,
    +				removeConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) {
    +					if (otherAnchor.constructor == jsPlumb.FloatingAnchor) {
    +						// no-op
    +					}
    +					else {
    +						jsPlumbUtil.removeWithFunction(connectionsByElementId[elId], function(_c) {
    +							return _c[0].id == c.id;
    +						});
    +					}
    +				};
    +				
    +			removeConnection(1, ep[1], ep[1].anchor, sourceId, connection);
    +			removeConnection(0, ep[0], ep[0].anchor, targetId, connection);
    +
    +            // remove from anchorLists            
    +            removeEndpointFromAnchorLists(connection.endpoints[0]);
    +            removeEndpointFromAnchorLists(connection.endpoints[1]);
    +
    +            self.redraw(connection.sourceId);
    +            self.redraw(connection.targetId);
    +		};
    +		this.add = function(endpoint, elementId) {
    +			jsPlumbUtil.addToList(_amEndpoints, elementId, endpoint);
    +		};
    +		this.changeId = function(oldId, newId) {
    +			connectionsByElementId[newId] = connectionsByElementId[oldId];
    +			_amEndpoints[newId] = _amEndpoints[oldId];
    +			delete connectionsByElementId[oldId];
    +			delete _amEndpoints[oldId];	
    +		};
    +		this.getConnectionsFor = function(elementId) {
    +			return connectionsByElementId[elementId] || [];
    +		};
    +		this.getEndpointsFor = function(elementId) {
    +			return _amEndpoints[elementId] || [];
    +		};
    +		this.deleteEndpoint = function(endpoint) {
    +			jsPlumbUtil.removeWithFunction(_amEndpoints[endpoint.elementId], function(e) {
    +				return e.id == endpoint.id;
    +			});
    +            removeEndpointFromAnchorLists(endpoint);
    +		};
    +		this.clearFor = function(elementId) {
    +			delete _amEndpoints[elementId];
    +			_amEndpoints[elementId] = [];
    +		};
    +        // updates the given anchor list by either updating an existing anchor's info, or adding it. this function
    +        // also removes the anchor from its previous list, if the edge it is on has changed.
    +        // all connections found along the way (those that are connected to one of the faces this function
    +        // operates on) are added to the connsToPaint list, as are their endpoints. in this way we know to repaint
    +        // them wthout having to calculate anything else about them.
    +        var _updateAnchorList = function(lists, theta, order, conn, aBoolean, otherElId, idx, reverse, edgeId, elId, connsToPaint, endpointsToPaint) {        
    +            // first try to find the exact match, but keep track of the first index of a matching element id along the way.s
    +            var exactIdx = -1,
    +                firstMatchingElIdx = -1,
    +                endpoint = conn.endpoints[idx],
    +                endpointId = endpoint.id,
    +                oIdx = [1,0][idx],
    +                values = [ [ theta, order ], conn, aBoolean, otherElId, endpointId ],
    +                listToAddTo = lists[edgeId],
    +                listToRemoveFrom = endpoint._continuousAnchorEdge ? lists[endpoint._continuousAnchorEdge] : null;
    +
    +            if (listToRemoveFrom) {
    +                var rIdx = jsPlumbUtil.findWithFunction(listToRemoveFrom, function(e) { return e[4] == endpointId });
    +                if (rIdx != -1) {
    +                    listToRemoveFrom.splice(rIdx, 1);
    +                    // get all connections from this list
    +                    for (var i = 0; i < listToRemoveFrom.length; i++) {
    +                        jsPlumbUtil.addWithFunction(connsToPaint, listToRemoveFrom[i][1], function(c) { return c.id == listToRemoveFrom[i][1].id });
    +                        jsPlumbUtil.addWithFunction(endpointsToPaint, listToRemoveFrom[i][1].endpoints[idx], function(e) { return e.id == listToRemoveFrom[i][1].endpoints[idx].id });
    +                    }
    +                }
    +            }
    +
    +            for (var i = 0; i < listToAddTo.length; i++) {
    +                if (params.idx == 1 && listToAddTo[i][3] === otherElId && firstMatchingElIdx == -1)
    +                    firstMatchingElIdx = i;
    +                jsPlumbUtil.addWithFunction(connsToPaint, listToAddTo[i][1], function(c) { return c.id == listToAddTo[i][1].id });                
    +                jsPlumbUtil.addWithFunction(endpointsToPaint, listToAddTo[i][1].endpoints[idx], function(e) { return e.id == listToAddTo[i][1].endpoints[idx].id });
    +            }
    +            if (exactIdx != -1) {
    +                listToAddTo[exactIdx] = values;
    +            }
    +            else {
    +                var insertIdx = reverse ? firstMatchingElIdx != -1 ? firstMatchingElIdx : 0 : listToAddTo.length; // of course we will get this from having looked through the array shortly.
    +                listToAddTo.splice(insertIdx, 0, values);
    +            }
    +
    +            // store this for next time.
    +            endpoint._continuousAnchorEdge = edgeId;
    +        };
    +		this.redraw = function(elementId, ui, timestamp, offsetToUI, clearEdits) {
    +		
    +			if (!jsPlumbInstance.isSuspendDrawing()) {
    +				// get all the endpoints for this element
    +				var ep = _amEndpoints[elementId] || [],
    +					endpointConnections = connectionsByElementId[elementId] || [],
    +					connectionsToPaint = [],
    +					endpointsToPaint = [],
    +	                anchorsToUpdate = [];
    +	            
    +				timestamp = timestamp || jsPlumbInstance.timestamp();
    +				// offsetToUI are values that would have been calculated in the dragManager when registering
    +				// an endpoint for an element that had a parent (somewhere in the hierarchy) that had been
    +				// registered as draggable.
    +				offsetToUI = offsetToUI || {left:0, top:0};
    +				if (ui) {
    +					ui = {
    +						left:ui.left + offsetToUI.left,
    +						top:ui.top + offsetToUI.top
    +					}
    +				}
    +									
    +				// valid for one paint cycle.
    +				var myOffset = jsPlumbInstance.updateOffset( { elId : elementId, offset : ui, recalc : false, timestamp : timestamp }),
    +	                myWH = jsPlumbInstance.getSize(elementId),
    +	                orientationCache = {};
    +				
    +				// actually, first we should compute the orientation of this element to all other elements to which
    +				// this element is connected with a continuous anchor (whether both ends of the connection have
    +				// a continuous anchor or just one)
    +	                        
    +	            for (var i = 0; i < endpointConnections.length; i++) {
    +	                var conn = endpointConnections[i][0],
    +						sourceId = conn.sourceId,
    +	                    targetId = conn.targetId,
    +	                    sourceContinuous = conn.endpoints[0].anchor.isContinuous,
    +	                    targetContinuous = conn.endpoints[1].anchor.isContinuous;
    +	
    +	                if (sourceContinuous || targetContinuous) {
    +		                var oKey = sourceId + "_" + targetId,
    +		                    oKey2 = targetId + "_" + sourceId,
    +		                    o = orientationCache[oKey],
    +		                    oIdx = conn.sourceId == elementId ? 1 : 0;
    +	
    +		                if (sourceContinuous && !anchorLists[sourceId]) anchorLists[sourceId] = { top:[], right:[], bottom:[], left:[] };
    +		                if (targetContinuous && !anchorLists[targetId]) anchorLists[targetId] = { top:[], right:[], bottom:[], left:[] };
    +	
    +		                if (elementId != targetId) jsPlumbInstance.updateOffset( { elId : targetId, timestamp : timestamp }); 
    +		                if (elementId != sourceId) jsPlumbInstance.updateOffset( { elId : sourceId, timestamp : timestamp }); 
    +	
    +		                var td = jsPlumbInstance.getCachedData(targetId),
    +							sd = jsPlumbInstance.getCachedData(sourceId);
    +	
    +		                if (targetId == sourceId && (sourceContinuous || targetContinuous)) {
    +		                    // here we may want to improve this by somehow determining the face we'd like
    +						    // to put the connector on.  ideally, when drawing, the face should be calculated
    +						    // by determining which face is closest to the point at which the mouse button
    +							// was released.  for now, we're putting it on the top face.                            
    +		                    _updateAnchorList(
    +                                anchorLists[sourceId], 
    +                                -Math.PI / 2, 
    +                                0, 
    +                                conn, 
    +                                false, 
    +                                targetId, 
    +                                0, false, "top", sourceId, connectionsToPaint, endpointsToPaint);
    +						}
    +		                else {
    +		                    if (!o) {
    +		                        o = calculateOrientation(sourceId, targetId, sd.o, td.o, conn.endpoints[0].anchor, conn.endpoints[1].anchor);
    +		                        orientationCache[oKey] = o;
    +		                        // this would be a performance enhancement, but the computed angles need to be clamped to
    +		                        //the (-PI/2 -> PI/2) range in order for the sorting to work properly.
    +		                    /*  orientationCache[oKey2] = {
    +		                            orientation:o.orientation,
    +		                            a:[o.a[1], o.a[0]],
    +		                            theta:o.theta + Math.PI,
    +		                            theta2:o.theta2 + Math.PI
    +		                        };*/
    +		                    }
    +		                    if (sourceContinuous) _updateAnchorList(anchorLists[sourceId], o.theta, 0, conn, false, targetId, 0, false, o.a[0], sourceId, connectionsToPaint, endpointsToPaint);
    +		                    if (targetContinuous) _updateAnchorList(anchorLists[targetId], o.theta2, -1, conn, true, sourceId, 1, true, o.a[1], targetId, connectionsToPaint, endpointsToPaint);
    +		                }
    +	
    +		                if (sourceContinuous) jsPlumbUtil.addWithFunction(anchorsToUpdate, sourceId, function(a) { return a === sourceId; });
    +		                if (targetContinuous) jsPlumbUtil.addWithFunction(anchorsToUpdate, targetId, function(a) { return a === targetId; });
    +		                jsPlumbUtil.addWithFunction(connectionsToPaint, conn, function(c) { return c.id == conn.id; });
    +		                if ((sourceContinuous && oIdx == 0) || (targetContinuous && oIdx == 1))
    +		                	jsPlumbUtil.addWithFunction(endpointsToPaint, conn.endpoints[oIdx], function(e) { return e.id == conn.endpoints[oIdx].id; });
    +		            }
    +	            }				
    +				// place Endpoints whose anchors are continuous but have no Connections
    +				for (var i = 0; i < ep.length; i++) {
    +					if (ep[i].connections.length == 0 && ep[i].anchor.isContinuous) {
    +						if (!anchorLists[elementId]) anchorLists[elementId] = { top:[], right:[], bottom:[], left:[] };
    +						_updateAnchorList(anchorLists[elementId], -Math.PI / 2, 0, {endpoints:[ep[i], ep[i]], paint:function(){}}, false, elementId, 0, false, "top", elementId, connectionsToPaint, endpointsToPaint)
    +						jsPlumbUtil.addWithFunction(anchorsToUpdate, elementId, function(a) { return a === elementId; })
    +					}
    +				}
    +	            // now place all the continuous anchors we need to;
    +	            for (var i = 0; i < anchorsToUpdate.length; i++) {
    +					placeAnchors(anchorsToUpdate[i], anchorLists[anchorsToUpdate[i]]);
    +				}
    +
    +				// now that continuous anchors have been placed, paint all the endpoints for this element
    +	            // TODO performance: add the endpoint ids to a temp array, and then when iterating in the next
    +	            // loop, check that we didn't just paint that endpoint. we can probably shave off a few more milliseconds this way.
    +				for (var i = 0; i < ep.length; i++) {				
    +                    ep[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH });
    +				}
    +	            // ... and any other endpoints we came across as a result of the continuous anchors.
    +	            for (var i = 0; i < endpointsToPaint.length; i++) {
    +                    var cd = jsPlumbInstance.getCachedData(endpointsToPaint[i].elementId);
    +                    endpointsToPaint[i].paint( { timestamp : timestamp, offset : cd, dimensions : cd.s });
    +				}
    +
    +				// paint all the standard and "dynamic connections", which are connections whose other anchor is
    +				// static and therefore does need to be recomputed; we make sure that happens only one time.
    +	
    +				// TODO we could have compiled a list of these in the first pass through connections; might save some time.
    +				for (var i = 0; i < endpointConnections.length; i++) {
    +					var otherEndpoint = endpointConnections[i][1];
    +					if (otherEndpoint.anchor.constructor == jsPlumb.DynamicAnchor) {			 							
    +						otherEndpoint.paint({ elementWithPrecedence:elementId });								
    +	                    jsPlumbUtil.addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; });
    +						// all the connections for the other endpoint now need to be repainted
    +						for (var k = 0; k < otherEndpoint.connections.length; k++) {
    +							if (otherEndpoint.connections[k] !== endpointConnections[i][0])							
    +	                            jsPlumbUtil.addWithFunction(connectionsToPaint, otherEndpoint.connections[k], function(c) { return c.id == otherEndpoint.connections[k].id; });
    +						}
    +					} else if (otherEndpoint.anchor.constructor == jsPlumb.Anchor) {					
    +	                    jsPlumbUtil.addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; });
    +					}
    +				}
    +				// paint current floating connection for this element, if there is one.
    +				var fc = floatingConnections[elementId];
    +				if (fc) 
    +					fc.paint({timestamp:timestamp, recalc:false, elId:elementId});
    +				                
    +				// paint all the connections
    +				for (var i = 0; i < connectionsToPaint.length; i++) {
    +					connectionsToPaint[i].paint({elId:elementId, timestamp:timestamp, recalc:false, clearEdits:clearEdits});
    +				}
    +			}
    +		};
    +		this.rehomeEndpoint = function(currentId, element) {
    +			var eps = _amEndpoints[currentId] || [], 
    +				elementId = jsPlumbInstance.getId(element);
    +			if (elementId !== currentId) {
    +				for (var i = 0; i < eps.length; i++) {
    +					self.add(eps[i], elementId);
    +				}
    +				eps.splice(0, eps.length);
    +			}
    +		};
    +        
    +        var ContinuousAnchor = function(anchorParams) {
    +            jsPlumbUtil.EventGenerator.apply(this);
    +            this.type = "Continuous";
    +            this.isDynamic = true;
    +            this.isContinuous = true;
    +            var faces = anchorParams.faces || ["top", "right", "bottom", "left"],
    +                clockwise = !(anchorParams.clockwise === false),
    +                availableFaces = { },
    +                opposites = { "top":"bottom", "right":"left","left":"right","bottom":"top" },
    +                clockwiseOptions = { "top":"right", "right":"bottom","left":"top","bottom":"left" },
    +                antiClockwiseOptions = { "top":"left", "right":"top","left":"bottom","bottom":"right" },
    +                secondBest = clockwise ? clockwiseOptions : antiClockwiseOptions,
    +                lastChoice = clockwise ? antiClockwiseOptions : clockwiseOptions,
    +                cssClass = anchorParams.cssClass || "";
    +            
    +            for (var i = 0; i < faces.length; i++) { availableFaces[faces[i]] = true; }
    +          
    +            // if the given edge is suported, returns it. otherwise looks for a substitute that _is_
    +            // supported. if none supported we also return the request edge.
    +            this.verifyEdge = function(edge) {
    +                if (availableFaces[edge]) return edge;
    +                else if (availableFaces[opposites[edge]]) return opposites[edge];
    +                else if (availableFaces[secondBest[edge]]) return secondBest[edge];
    +                else if (availableFaces[lastChoice[edge]]) return lastChoice[edge];
    +                return edge; // we have to give them something.
    +            };
    +            
    +            this.compute = function(params) {
    +                return userDefinedContinuousAnchorLocations[params.element.id] || continuousAnchorLocations[params.element.id] || [0,0];
    +            };
    +            this.getCurrentLocation = function(endpoint) {
    +                return userDefinedContinuousAnchorLocations[endpoint.id] || continuousAnchorLocations[endpoint.id] || [0,0];
    +            };
    +            this.getOrientation = function(endpoint) {
    +                return continuousAnchorOrientations[endpoint.id] || [0,0];
    +            };
    +            this.clearUserDefinedLocation = function() { 
    +                delete userDefinedContinuousAnchorLocations[anchorParams.elementId]; 
    +            };
    +            this.setUserDefinedLocation = function(loc) { 
    +                userDefinedContinuousAnchorLocations[anchorParams.elementId] = loc; 
    +            };            
    +            this.getCssClass = function() { return cssClass; };
    +            this.setCssClass = function(c) { cssClass = c; };
    +        };        
    +        
    +        // continuous anchors
    +        jsPlumbInstance.continuousAnchorFactory = {
    +            get:function(params) {
    +                var existing = continuousAnchors[params.elementId];
    +                if (!existing) {
    +                    existing = new ContinuousAnchor(params);                    
    +                    continuousAnchors[params.elementId] = existing;
    +                }
    +                return existing;
    +            }
    +        };
    +	};
    +    
    +    /**
    +     * Anchors model a position on some element at which an Endpoint may be located.  They began as a first class citizen of jsPlumb, ie. a user
    +     * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"),
    +     * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle").  jsPlumb now handles all of the
    +     * creation of Anchors without user intervention.
    +     */
    +    jsPlumb.Anchor = function(params) {
    +        var self = this;
    +        this.x = params.x || 0;
    +        this.y = params.y || 0;
    +        this.elementId = params.elementId;        
    +
    +        jsPlumbUtil.EventGenerator.apply(this);
    +        
    +        var orientation = params.orientation || [ 0, 0 ],
    +            jsPlumbInstance = params.jsPlumbInstance,
    +            lastTimestamp = null, lastReturnValue = null, userDefinedLocation = null,
    +            cssClass = params.cssClass || "";
    +
    +        this.getCssClass = function() { return cssClass; };
    +        
    +        this.offsets = params.offsets || [ 0, 0 ];
    +        self.timestamp = null;        
    +        this.compute = function(params) {
    +            
    +            var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp;                    
    +            if(params.clearUserDefinedLocation)
    +                userDefinedLocation = null;
    +            
    +            if (timestamp && timestamp === self.timestamp)
    +                return lastReturnValue;        
    +            
    +            if (userDefinedLocation != null) {
    +                lastReturnValue = userDefinedLocation;
    +            }
    +            else {                
    +                
    +                lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ];                    
    +                // adjust loc if there is an offsetParent
    +                lastReturnValue = jsPlumbInstance.adjustForParentOffsetAndScroll(lastReturnValue, element.canvas);
    +            }
    +            
    +            self.timestamp = timestamp;
    +            return lastReturnValue;
    +        };
    +
    +        this.getOrientation = function(_endpoint) { return orientation; };
    +
    +        this.equals = function(anchor) {
    +            if (!anchor) return false;
    +            var ao = anchor.getOrientation();
    +            var o = this.getOrientation();
    +            return this.x == anchor.x && this.y == anchor.y
    +                    && this.offsets[0] == anchor.offsets[0]
    +                    && this.offsets[1] == anchor.offsets[1]
    +                    && o[0] == ao[0] && o[1] == ao[1];
    +        };
    +
    +        this.getCurrentLocation = function() { return lastReturnValue; };
    +        
    +        this.getUserDefinedLocation = function() { 
    +            return userDefinedLocation;
    +        };
    +        
    +        this.setUserDefinedLocation = function(l) {
    +            userDefinedLocation = l;
    +        };
    +        this.clearUserDefinedLocation = function() {
    +            userDefinedLocation = null;
    +        };
    +    };
    +
    +    /**
    +     * An Anchor that floats. its orientation is computed dynamically from
    +     * its position relative to the anchor it is floating relative to.  It is used when creating 
    +     * a connection through drag and drop.
    +     * 
    +     * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly.
    +     */
    +    jsPlumb.FloatingAnchor = function(params) {
    +        
    +        jsPlumb.Anchor.apply(this, arguments);
    +
    +        // this is the anchor that this floating anchor is referenced to for
    +        // purposes of calculating the orientation.
    +        var ref = params.reference,
    +            jpcl = jsPlumb.CurrentLibrary,
    +            jsPlumbInstance = params.jsPlumbInstance,
    +            // the canvas this refers to.
    +            refCanvas = params.referenceCanvas,
    +            size = jpcl.getSize(jpcl.getElementObject(refCanvas)),                
    +
    +        // these are used to store the current relative position of our
    +        // anchor wrt the reference anchor. they only indicate
    +        // direction, so have a value of 1 or -1 (or, very rarely, 0). these
    +        // values are written by the compute method, and read
    +        // by the getOrientation method.
    +        xDir = 0, yDir = 0,
    +        // temporary member used to store an orientation when the floating
    +        // anchor is hovering over another anchor.
    +        orientation = null,
    +        _lastResult = null;
    +
    +        // set these to 0 each; they are used by certain types of connectors in the loopback case,
    +        // when the connector is trying to clear the element it is on. but for floating anchor it's not
    +        // very important.
    +        this.x = 0; this.y = 0;
    +
    +        this.isFloating = true;
    +
    +        this.compute = function(params) {
    +            var xy = params.xy, element = params.element,
    +            result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy.
    +                        
    +            // adjust loc if there is an offsetParent
    +            result = jsPlumbInstance.adjustForParentOffsetAndScroll(result, element.canvas);
    +            
    +            _lastResult = result;
    +            return result;
    +        };
    +
    +        this.getOrientation = function(_endpoint) {
    +            if (orientation) return orientation;
    +            else {
    +                var o = ref.getOrientation(_endpoint);
    +                // here we take into account the orientation of the other
    +                // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come
    +                // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense.
    +                return [ Math.abs(o[0]) * xDir * -1,
    +                        Math.abs(o[1]) * yDir * -1 ];
    +            }
    +        };
    +
    +        /**
    +         * notification the endpoint associated with this anchor is hovering
    +         * over another anchor; we want to assume that anchor's orientation
    +         * for the duration of the hover.
    +         */
    +        this.over = function(anchor) { 
    +            orientation = anchor.getOrientation(); 
    +        };
    +
    +        /**
    +         * notification the endpoint associated with this anchor is no
    +         * longer hovering over another anchor; we should resume calculating
    +         * orientation as we normally do.
    +         */
    +        this.out = function() { orientation = null; };
    +
    +        this.getCurrentLocation = function() { return _lastResult; };
    +    };
    +
    +    /* 
    +     * A DynamicAnchor is an Anchor that contains a list of other Anchors, which it cycles
    +     * through at compute time to find the one that is located closest to
    +     * the center of the target element, and returns that Anchor's compute
    +     * method result. this causes endpoints to follow each other with
    +     * respect to the orientation of their target elements, which is a useful
    +     * feature for some applications.
    +     * 
    +     */
    +    jsPlumb.DynamicAnchor = function(params) {
    +        jsPlumb.Anchor.apply(this, arguments);
    +        
    +        this.isSelective = true;
    +        this.isDynamic = true;			
    +        var _anchors = [], self = this,            
    +            _convert = function(anchor) { 
    +                return anchor.constructor == jsPlumb.Anchor ? anchor: params.jsPlumbInstance.makeAnchor(anchor, params.elementId, params.jsPlumbInstance); 
    +            };
    +
    +        for (var i = 0; i < params.anchors.length; i++) 
    +            _anchors[i] = _convert(params.anchors[i]);			
    +        this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); };
    +        this.getAnchors = function() { return _anchors; };
    +        this.locked = false;
    +        var _curAnchor = _anchors.length > 0 ? _anchors[0] : null,
    +            _curIndex = _anchors.length > 0 ? 0 : -1,
    +            _lastAnchor = _curAnchor,
    +            self = this,
    +        
    +            // helper method to calculate the distance between the centers of the two elements.
    +            _distance = function(anchor, cx, cy, xy, wh) {
    +                var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]),				
    +                    acx = xy[0] + (wh[0] / 2), acy = xy[1] + (wh[1] / 2);
    +                return (Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)) +
    +                        Math.sqrt(Math.pow(acx - ax, 2) + Math.pow(acy - ay, 2)));
    +            },        
    +            // default method uses distance between element centers.  you can provide your own method in the dynamic anchor
    +            // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: 
    +            // xy - xy loc of the anchor's element
    +            // wh - anchor's element's dimensions
    +            // txy - xy loc of the element of the other anchor in the connection
    +            // twh - dimensions of the element of the other anchor in the connection.
    +            // anchors - the list of selectable anchors
    +            _anchorSelector = params.selector || function(xy, wh, txy, twh, anchors) {
    +                var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2);
    +                var minIdx = -1, minDist = Infinity;
    +                for ( var i = 0; i < anchors.length; i++) {
    +                    var d = _distance(anchors[i], cx, cy, xy, wh);
    +                    if (d < minDist) {
    +                        minIdx = i + 0;
    +                        minDist = d;
    +                    }
    +                }
    +                return anchors[minIdx];
    +            };
    +        
    +        this.compute = function(params) {				
    +            var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh;				
    +            
    +            if(params.clearUserDefinedLocation)
    +                userDefinedLocation = null;
    +            
    +            var udl = self.getUserDefinedLocation();
    +            if (udl != null) {
    +                return udl;
    +            }
    +            
    +            // if anchor is locked or an opposite element was not given, we
    +            // maintain our state. anchor will be locked
    +            // if it is the source of a drag and drop.
    +            if (self.locked || txy == null || twh == null)
    +                return _curAnchor.compute(params);				
    +            else
    +                params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute.
    +            
    +            _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors);
    +            self.x = _curAnchor.x;
    +            self.y = _curAnchor.y;        
    +
    +            if (_curAnchor != _lastAnchor)
    +                self.fire("anchorChanged", _curAnchor);
    +
    +            _lastAnchor = _curAnchor;
    +            
    +            return _curAnchor.compute(params);
    +        };
    +
    +        this.getCurrentLocation = function() {
    +            return self.getUserDefinedLocation() || (_curAnchor != null ? _curAnchor.getCurrentLocation() : null);
    +        };
    +
    +        this.getOrientation = function(_endpoint) { return _curAnchor != null ? _curAnchor.getOrientation(_endpoint) : [ 0, 0 ]; };
    +        this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); };
    +        this.out = function() { if (_curAnchor != null) _curAnchor.out(); };
    +
    +        this.getCssClass = function() { return (_curAnchor && _curAnchor.getCssClass()) || ""; };
    +    };            
    +    
    +// -------- basic anchors ------------------    
    +    var _curryAnchor = function(x, y, ox, oy, type, fnInit) {
    +        jsPlumb.Anchors[type] = function(params) {
    +            var a = params.jsPlumbInstance.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance);
    +            a.type = type;
    +            if (fnInit) fnInit(a, params);
    +            return a;
    +        };
    +    };
    +    	
    +	_curryAnchor(0.5, 0, 0,-1, "TopCenter");
    +    _curryAnchor(0.5, 1, 0, 1, "BottomCenter");
    +    _curryAnchor(0, 0.5, -1, 0, "LeftMiddle");
    +    _curryAnchor(1, 0.5, 1, 0, "RightMiddle");
    +    // from 1.4.0: Top, Right, Bottom, Left
    +    _curryAnchor(0.5, 0, 0,-1, "Top");
    +    _curryAnchor(0.5, 1, 0, 1, "Bottom");
    +    _curryAnchor(0, 0.5, -1, 0, "Left");
    +    _curryAnchor(1, 0.5, 1, 0, "Right");
    +    _curryAnchor(0.5, 0.5, 0, 0, "Center");
    +    _curryAnchor(1, 0, 0,-1, "TopRight");
    +    _curryAnchor(1, 1, 0, 1, "BottomRight");
    +    _curryAnchor(0, 0, 0, -1, "TopLeft");
    +    _curryAnchor(0, 1, 0, 1, "BottomLeft");
    +    
    +// ------- dynamic anchors -------------------    
    +			
    +    // default dynamic anchors chooses from Top, Right, Bottom, Left
    +	jsPlumb.Defaults.DynamicAnchors = function(params) {
    +		return params.jsPlumbInstance.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"], params.elementId, params.jsPlumbInstance);
    +	};
    +    
    +    // default dynamic anchors bound to name 'AutoDefault'
    +	jsPlumb.Anchors["AutoDefault"]  = function(params) { 
    +		var a = params.jsPlumbInstance.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors(params));
    +		a.type = "AutoDefault";
    +		return a;
    +	};	
    +    
    +// ------- continuous anchors -------------------    
    +    
    +    var _curryContinuousAnchor = function(type, faces) {
    +        jsPlumb.Anchors[type] = function(params) {
    +            var a = params.jsPlumbInstance.makeAnchor(["Continuous", { faces:faces }], params.elementId, params.jsPlumbInstance);
    +            a.type = type;
    +            return a;
    +        };
    +    };
    +    
    +    jsPlumb.Anchors["Continuous"] = function(params) {
    +		return params.jsPlumbInstance.continuousAnchorFactory.get(params);
    +	};
    +                
    +    _curryContinuousAnchor("ContinuousLeft", ["left"]);    
    +    _curryContinuousAnchor("ContinuousTop", ["top"]);                 
    +    _curryContinuousAnchor("ContinuousBottom", ["bottom"]);                 
    +    _curryContinuousAnchor("ContinuousRight", ["right"]); 
    +    
    +// ------- position assign anchors -------------------    
    +    
    +    // this anchor type lets you assign the position at connection time.
    +	jsPlumb.Anchors["Assign"] = _curryAnchor(0, 0, 0, 0, "Assign", function(anchor, params) {
    +		// find what to use as the "position finder". the user may have supplied a String which represents
    +		// the id of a position finder in jsPlumb.AnchorPositionFinders, or the user may have supplied the
    +		// position finder as a function.  we find out what to use and then set it on the anchor.
    +		var pf = params.position || "Fixed";
    +		anchor.positionFinder = pf.constructor == String ? params.jsPlumbInstance.AnchorPositionFinders[pf] : pf;
    +		// always set the constructor params; the position finder might need them later (the Grid one does,
    +		// for example)
    +		anchor.constructorParams = params;
    +	});	
    +
    +    // these are the default anchor positions finders, which are used by the makeTarget function.  supplying
    +    // a position finder argument to that function allows you to specify where the resulting anchor will
    +    // be located
    +	jsPlumb.AnchorPositionFinders = {
    +		"Fixed": function(dp, ep, es, params) {
    +			return [ (dp.left - ep.left) / es[0], (dp.top - ep.top) / es[1] ];	
    +		},
    +		"Grid":function(dp, ep, es, params) {
    +			var dx = dp.left - ep.left, dy = dp.top - ep.top,
    +				gx = es[0] / (params.grid[0]), gy = es[1] / (params.grid[1]),
    +				mx = Math.floor(dx / gx), my = Math.floor(dy / gy);
    +			return [ ((mx * gx) + (gx / 2)) / es[0], ((my * gy) + (gy / 2)) / es[1] ];
    +		}
    +	};
    +    
    +// ------- perimeter anchors -------------------    
    +		
    +	jsPlumb.Anchors["Perimeter"] = function(params) {
    +		params = params || {};
    +		var anchorCount = params.anchorCount || 60,
    +			shape = params.shape;
    +		
    +		if (!shape) throw new Error("no shape supplied to Perimeter Anchor type");		
    +		
    +		var _circle = function() {
    +                var r = 0.5, step = Math.PI * 2 / anchorCount, current = 0, a = [];
    +                for (var i = 0; i < anchorCount; i++) {
    +                    var x = r + (r * Math.sin(current)),
    +                        y = r + (r * Math.cos(current));                                
    +                    a.push( [ x, y, 0, 0 ] );
    +                    current += step;
    +                }
    +                return a;	
    +            },
    +            _path = function(segments) {
    +                var anchorsPerFace = anchorCount / segments.length, a = [],
    +                    _computeFace = function(x1, y1, x2, y2, fractionalLength) {
    +                        anchorsPerFace = anchorCount * fractionalLength;
    +                        var dx = (x2 - x1) / anchorsPerFace, dy = (y2 - y1) / anchorsPerFace;
    +                        for (var i = 0; i < anchorsPerFace; i++) {
    +                            a.push( [
    +                                x1 + (dx * i),
    +                                y1 + (dy * i),
    +                                0,
    +                                0
    +                            ]);
    +                        }
    +                    };
    +								
    +                for (var i = 0; i < segments.length; i++)
    +                    _computeFace.apply(null, segments[i]);
    +														
    +                return a;					
    +            },
    +			_shape = function(faces) {												
    +                var s = [];
    +                for (var i = 0; i < faces.length; i++) {
    +                    s.push([faces[i][0], faces[i][1], faces[i][2], faces[i][3], 1 / faces.length]);
    +                }
    +                return _path(s);
    +			},
    +			_rectangle = function() {
    +				return _shape([
    +					[ 0, 0, 1, 0 ], [ 1, 0, 1, 1 ], [ 1, 1, 0, 1 ], [ 0, 1, 0, 0 ]
    +				]);		
    +			};
    +		
    +		var _shapes = {
    +			"Circle":_circle,
    +			"Ellipse":_circle,
    +			"Diamond":function() {
    +				return _shape([
    +						[ 0.5, 0, 1, 0.5 ], [ 1, 0.5, 0.5, 1 ], [ 0.5, 1, 0, 0.5 ], [ 0, 0.5, 0.5, 0 ]
    +				]);
    +			},
    +			"Rectangle":_rectangle,
    +			"Square":_rectangle,
    +			"Triangle":function() {
    +				return _shape([
    +						[ 0.5, 0, 1, 1 ], [ 1, 1, 0, 1 ], [ 0, 1, 0.5, 0]
    +				]);	
    +			},
    +			"Path":function(params) {
    +                var points = params.points, p = [], tl = 0;
    +				for (var i = 0; i < points.length - 1; i++) {
    +                    var l = Math.sqrt(Math.pow(points[i][2] - points[i][0]) + Math.pow(points[i][3] - points[i][1]));
    +                    tl += l;
    +					p.push([points[i][0], points[i][1], points[i+1][0], points[i+1][1], l]);						
    +				}
    +                for (var i = 0; i < p.length; i++) {
    +                    p[i][4] = p[i][4] / tl;
    +                }
    +				return _path(p);
    +			}
    +		},
    +        _rotate = function(points, amountInDegrees) {
    +            var o = [], theta = amountInDegrees / 180 * Math.PI ;
    +            for (var i = 0; i < points.length; i++) {
    +                var _x = points[i][0] - 0.5,
    +                    _y = points[i][1] - 0.5;
    +                    
    +                o.push([
    +                    0.5 + ((_x * Math.cos(theta)) - (_y * Math.sin(theta))),
    +                    0.5 + ((_x * Math.sin(theta)) + (_y * Math.cos(theta))),
    +                    points[i][2],
    +                    points[i][3]
    +                ]);
    +            }
    +            return o;
    +        };
    +		
    +		if (!_shapes[shape]) throw new Error("Shape [" + shape + "] is unknown by Perimeter Anchor type");
    +		
    +		var da = _shapes[shape](params);
    +        if (params.rotation) da = _rotate(da, params.rotation);
    +        var a = params.jsPlumbInstance.makeDynamicAnchor(da);
    +		a.type = "Perimeter";
    +		return a;
    +	};
    +})();
    \ No newline at end of file
    diff --git a/src/jsPlumb-apidoc.js b/src/jsPlumb-apidoc.js
    new file mode 100644
    index 000000000..cd5db1cfd
    --- /dev/null
    +++ b/src/jsPlumb-apidoc.js
    @@ -0,0 +1,1915 @@
    +// ---------------- JSPLUMB ----------------------------------------
    +
    +/**
    + * Class: jsPlumb
    + * The jsPlumb engine, of which there is one instance registered as a static object in the window.  You
    + * can use the jsPlumb.getInstance function to get a new instance of jsPlumb. This object contains all of the methods you will use to create and maintain Connections and Endpoints.
    + */			
    +		
    +/*
    + * Function: bind(event, callback)
    + * Bind to an event on jsPlumb.  
    + * 
    + * Parameters:
    + * 	event - the event to bind.  Available events on jsPlumb are:
    + *         - *connection* 			: 	notification that a new Connection was established.  jsPlumb passes the new Connection to the callback.
    + *         - *connectionDetached* 	: 	notification that a Connection was detached.  jsPlumb passes the detached Connection to the callback.
    + *         - *click*						:	notification that a Connection was clicked.  jsPlumb passes the Connection that was clicked to the callback.
    + *         - *dblclick*						:	notification that a Connection was double clicked.  jsPlumb passes the Connection that was double clicked to the callback.
    + *         - *endpointClick*				:	notification that an Endpoint was clicked.  jsPlumb passes the Endpoint that was clicked to the callback.
    + *         - *endpointDblClick*				:	notification that an Endpoint was double clicked.  jsPlumb passes the Endpoint that was double clicked to the callback.
    + *         - *beforeDrop*					: 	notification that a Connection is about to be dropped. Returning false from this method cancels the drop. jsPlumb passes { sourceId, targetId, scope, connection, dropEndpoint } to your callback. For more information, refer to the jsPlumb documentation.
    + *         - *beforeDetach*					: 	notification that a Connection is about to be detached. Returning false from this method cancels the detach. jsPlumb passes the Connection to your callback. For more information, refer to the jsPlumb documentation.
    + *		   - *connectionDrag* 				:   notification that an existing Connection is being dragged. jsPlumb passes the Connection to your callback function.
    + *         - *connectionDragEnd*            :   notification that the drag of an existing Connection has ended.  jsPlumb passes the Connection to your callback function.
    + *         
    + *  callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event.    
    + */
    +
    + /*
    + * Function: importDefaults(defaults)
    + * Imports all the given defaults into this instance of jsPlumb.		
    + *
    + * Parameters:
    + * 	defaults - The defaults to import
    + */
    +		 
    + /*
    + * Function: restoreDefaults()
    + * Restores the default settings to "factory" values.
    + */
    +		
    +/*
    + * Function: unbind([event])
    + * Clears either all listeners, or listeners for some specific event.
    + * 
    + * Parameters:
    + * 	event	-	optional. constrains the clear to just listeners for this event.
    + */
    +		
    +/*
    +* Function: addClass(el, clazz)
    +* Add class(es) to some element(s).
    +*
    +* Parameters:
    +* 	el			-	element id, dom element, or selector representing the element(s) to add class(es) to.
    +* 	clazz		-	string representing the class(es) to add. may contain multiple classes separated by spaces.
    +*/
    +
    +/*
    +* Function: registerConnectionType(typeId, type)
    +* Registers the given connection type on this instance of jsPlumb.
    +*
    +* Parameters:
    +* typeId - id of the type
    +* type - a JS object containing the type specifications.
    +*/
    +		
    +/*
    +* Function: registerConnectionTypes(types)
    +* Registers the given map of Connection types on this instance of jsPlumb.
    +*
    +* Parameters:
    +* types - a JS object whose keys are type ids and whose values are type specifications.
    +*/
    +
    +/*
    +* Function: registerEndpointType(typeId, type)
    +* Registers the given endpoint type on this instance of jsPlumb.
    +*
    +* Parameters:
    +* typeId - id of the type
    +* type - a JS object containing the type specifications.
    +*/
    +
    +/*
    +* Function: getType(id, typeDescriptor)
    +* Gets the given type's specification.
    +*
    +* Parameters:
    +* id - id of the type to retrieve
    +* typeDescriptor - "endpoint" or "connection"
    +*/
    +
    +/*
    +* Function: getInstanceIndex()
    +* Returns:
    +* The "instance index" for this instance of jsPlumb. Probably not something you will need very often.
    +*/
    +		
    +/*
    +* Function: registerEndpointTypes(types)
    +* Registers the given map of Endpoint types on this instance of jsPlumb.
    +*
    +* Parameters:
    +* types - a JS object whose keys are type ids and whose values are type specifications.
    +*/
    +
    +/*
    +* Function: removeClass(el, clazz)
    +* Remove class from some element(s).
    +*
    +* Parameters:
    +* 	el			-	element id, dom element, or selector representing the element(s) to remove a class from.
    +* 	clazz		-	string representing the class to remove. 
    +*/
    +		
    +/*
    +* Function: hasClass(el, clazz)
    +* Checks if an element has some class.
    +*
    +* Parameters:
    +* 	el			-	element id, dom element, or selector representing the element to test. If the selector matches multiple elements, we return the test for the first element in the selector only.
    +* 	clazz		-	string representing the class to test for. 
    +*/
    +
    +/*
    +  Function: addEndpoint (el, [params], [referenceParams])
    +  	
    +  Adds an <Endpoint> to a given element or elements.
    +  			  
    +  Parameters:
    +   
    +  	el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. 
    +  	params - Object containing Endpoint constructor arguments.  For more information, see <Endpoint>.
    +  	referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb.  You would use this if you had some 
    +  					  shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in
    +  					  this object are anything that 'params' can contain.  See <Endpoint>.
    +  	 
    +  Returns: 
    +  	The newly created <Endpoint>, if el referred to a single element.  Otherwise, an array of newly created <Endpoint>s. 
    +  	
    +  See Also: 
    +  	<addEndpoints>
    + */
    +
    + /*
    +   Function: addEndpoints(target, endpoints, [referenceParams]) 
    +   Adds a list of <Endpoint>s to a given element or elements.
    +   
    +   Parameters: 
    +   	target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these. 
    +   	endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list.  See <Endpoint>'s constructor documentation. 
    + 	referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb.  You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements.		  	 
    +
    +   Returns: 
    +   	List of newly created <Endpoint>s, one for each entry in the 'endpoints' argument. 
    +   	
    +   See Also:
    +   	<addEndpoint>
    +  */
    +
    +  /*
    +    Function: animate 
    +    This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating
    +    the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as
    +    the second arg. MooTools has only one method, a two arg one. Which is handy.  YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI.
    +     
    +    Parameters: 
    +    	el - Element to animate. Either an id, or a selector representing the element. 
    +    	properties - The 'properties' argument you want passed to the library's animate call. 
    +     	options - The 'options' argument you want passed to the library's animate call.      
    +   */
    +
    +   /*
    +     Function: connect (params, [referenceParams])
    +     Establishes a <Connection> between two elements (or <Endpoint>s, which are themselves registered to elements).
    +     
    +     Parameters: 
    +       params - Object containing constructor arguments for the Connection. See <Connection>'s constructor documentation.
    +       referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of 
    +       Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection.
    +        
    +     Returns: 
    +     	The newly created <Connection>.
    +    */
    +
    +    /*
    +     Function: deleteEndpoint(object)		 
    +     Deletes an <Endpoint> and removes all <Connection>s it has (which removes the Connections from the other Endpoints involved too)
    +     
    +     Parameters:
    +     	object - either an <Endpoint> object (such as from an addEndpoint call), or a String UUID.
    +     	
    +     Returns:
    +     	The jsPlumb instance		  
    +     */
    +
    +/*
    + Function: deleteEveryEndpoint()
    +  Deletes every <Endpoint>, and their associated <Connection>s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference
    +between this method and jsPlumb.reset). 
    +   Returns:
    +   	The jsPlumb instance   
    + */
    +
    + /*
    +   Function: detach(connection, [params]) 
    +   Detaches and then removes a <Connection>.  
    +   		   
    +   Parameters: 
    +     connection  -   the <Connection> to detach
    +     params      -   optional parameters to the detach call.  valid values here are
    +                     fireEvent   :   defaults to false; indicates you want jsPlumb to fire a connection
    +                                     detached event. The thinking behind this is that if you made a programmatic
    +                                     call to detach an event, you probably don't need the callback.
    +                     forceDetach :   defaults to false. allows you to override any beforeDetach listeners that may be registered.
    +
    +     Returns: 
    +     	true if successful, false if not.
    +  */ 
    +
    +  /*
    +    Function: detachAllConnections(el, [params])
    +    Removes all an element's Connections.
    +     
    +    Parameters:
    +    	el - either the id of the element, or a selector for the element.
    +    	params - optional parameters.  alowed values:
    +    	        fireEvent : defaults to true, whether or not to fire the detach event.
    +    	
    +    Returns: 
    +    	The jsPlumb instance.
    +   */  
    +
    +   /*
    +     Function: detachEveryConnection([params]) 
    +     Remove all Connections from all elements, but leaves Endpoints in place.
    +
    +     Parameters:
    +       params  - optional params object containing:
    +               fireEvent : whether or not to fire detach events. defaults to true.
    +
    +      
    +     Returns: 
    +     	The jsPlumbInstance
    +     	 
    +     See Also:
    +     	<deleteEveryEndpoint>
    +    */   
    +
    +    /*
    +      Function: draggable(el, [options])
    +      Initialises the draggability of some element or elements.  You should use this instead of your 
    +      library's draggable method so that jsPlumb can setup the appropriate callbacks.  Your 
    +      underlying library's drag method is always called from this method.
    +      
    +      Parameters: 
    +      	el - either an element id, a list of element ids, or a selector. 
    +      	options - options to pass through to the underlying library
    +      	 
    +      Returns: 
    +      	The jsPlumb instance.
    +     */    
    +
    +     /*
    +       Function: extend(o1, o2)
    +       Wraps the underlying library's extend functionality.
    +       
    +       Parameters: 
    +       	o1 - object to extend 
    +       	o2 - object to extend o1 with
    +       	
    +       Returns: 
    +       	o1, extended with all properties from o2.
    +      */ 
    +
    +/*
    +* Function: getDefaultEndpointType()
    +* 	Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass.
    +*  you would make a call like this in your class's constructor:
    +*  
    +*  : jsPlumb.getDefaultEndpointType().apply(this, arguments);
    +*  
    +* 
    +* Returns:
    +* 	the default Endpoint function used by jsPlumb.
    +*/
    +
    +/*
    + * Function: getDefaultConnectionType()
    + * 	Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass.
    + *  you would make a call like this in your class's constructor:
    + *  
    + *  : jsPlumb.getDefaultConnectionType().apply(this, arguments);
    + * 
    + * Returns:
    + * 	the default Connection function used by jsPlumb.
    + */
    +
    + /*
    +  * Function: getConnections(params) 
    +  * Gets all or a subset of connections currently managed by this jsPlumb instance.  If only one scope is passed in to this method,
    +  * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the
    +  * default scope).
    +  *
    +  * If multiple scopes are passed in, the return value will be a map of
    +  *
    +  * : { scope -> [ connection... ] }
    +  * 
    +  *  Parameters:
    +  *  	scope	-	if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list
    +  *                  of connections that are in the given scope. use '*' for all scopes.
    +  *      options	-	if the argument is a JS object, you can specify a finer-grained filter:
    +  *      
    +  *      		-	*scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes. Also may have the value '*', indicating any scope.
    +  *      		-	*source* either a string representing an element id, a selector, or an array of ids. Also may have the value '*', indicating any source.  Constrains the result to connections having this/these element(s) as source.
    +  *      		-	*target* either a string representing an element id, a selector, or an array of ids. Also may have the value '*', indicating any target.  Constrains the result to connections having this/these element(s) as target.		 
    +  *		flat    -	return results in a flat array (don't return an object whose keys are scopes and whose values are lists per scope).
    +  * 
    +  * Returns:
    +  * If only one scope was requested, a list of Connections that match the criteria. Otherwise, a map of [scope->connection lists].
    +  */ 
    +
    +  		/*
    +  		* Function: select(params)
    +  		* Selects a set of Connections, using the filter options from the getConnections method, and returns an object
    +  		* that allows you to perform an operation on all of the Connections at once.
    +  		*
    +  		* The return value from any of these operations is the original list of Connections, allowing operations to be
    +  		* chained (for 'setter' type operations). 'getter' type operations return an array of values, where each entry is
    +  		* of the form:
    +  		*
    +  		* : [ Connection, return value ]
    +  		* 
    +  		* Parameters:
    +  		*	scope - see getConnections
    +  		* 	source - see getConnections
    +  		*	target - see getConnections
    +          *   connections - an existing list of Connections.  If you supply this, 'source' and 'target' will be ignored.
    +  		*
    +  		* Returns:
    +  		* A list of Connections on which operations may be executed. 'Setter' type operations can be chained; 'getter' type operations
    +  		* return an array of [Connection, value] pairs, one entry for each Connection in the list returned. The full list of operations 
    +  		* is as follows (where not specified, the operation's effect or return value is the same as the corresponding method on Connection) :
    +  		* 				
    +          *   -   addClass
    +  		*	-	addOverlay
    +  		*	-	addType
    +  		*	-	detach : detaches all the connections in the list. not chainable and does not return anything.		
    +  		*	-	each(function(connection)...) : allows you to specify your own function to execute; this function is chainable.		
    +  		*	-	get(index) : returns the Connection at 'index' in the list.			
    +  		*	-	getHoverPaintStyle		
    +  		*   - 	getLabel
    +  		*	-	getOverlay
    +  		*	-	getPaintStyle		
    +  		*	-	getParameter
    +  		*	-	getParameters
    +  		*	-	getType
    +  		*	-	getZIndex
    +  		*	-	hasType		
    +  		*	-	hideOverlay
    +  		*	-	hideOverlays		
    +  		*	-	isDetachable		
    +  		*	-	isHover
    +  		*	-	isReattach
    +  		*	-	isVisible		
    +  		*	-	length : returns the length of the list.
    +  		*	-	removeAllOverlays		
    +          *   -   removeClass
    +  		*	-	removeOverlay
    +  		*	-	removeOverlays
    +  		*	-	removeType
    +  		*	-	repaint	
    +  		*	-	setConnector		
    +  		*	-	setDetachable
    +  		*	-	setHover		
    +  		*	-	setHoverPaintStyle		
    +  		*	-	setLabel		
    +  		*	-	setPaintStyle		
    +  		*	-	setParameter
    +  		*	-	setParameters
    +  		*	- 	setReattach	
    +  		*	-	setType	
    +  		*	-	showOverlay		
    +  		*	-	showOverlays
    +  		*/  
    +
    +/*
    +* Function: selectEndpoints(params)
    +* Selects a set of Endpoints and returns an object that allows you to execute various different methods on them at once. The return 
    +* value from any of these operations is the original list of Endpoints, allowing operations to be chained (for 'setter' type 
    +* operations). 'getter' type operations return an array of values, where each entry is of the form:
    +*
    +* : [ Endpoint, return value ]
    +* 
    +* Parameters:
    +*	scope - either a string or an array of strings.
    +* 	source - either a string, a dom element, or a selector, or an array of any mix of these. limits returned endpoints to those that are declared as a source endpoint on any elements identified.
    +*	target - either a string, a dom element, or a selector, or an array of any mix of these. limits returned endpoints to those that are declared as a target endpoint on any elements identified.
    +*	element - either a string, a dom element, or a selector, or an array of any mix of these. limits returned endpoints to those that are declared as either a source OR a target endpoint on any elements identified.		
    +*
    +* Returns:
    +* A list of Endpoints on which operations may be executed. 'Setter' type operations can be chained; 'getter' type operations
    +* return an array of [Endpoint, value] pairs, one entry for each Endpoint in the list returned. 
    +*
    +* The full list of operations is as follows (where not specified, the operation's effect or return value is the
    +* same as the corresponding method on Endpoint) :
    +* 
    +*	-	setHover								
    +*	-	removeAllOverlays
    +*	-	setLabel
    +*   -   addClass
    +*	-	addOverlay
    +*   -   removeClass
    +*	-	removeOverlay
    +*	-	removeOverlays
    +*	-	showOverlay
    +*	-	hideOverlay
    +*	-	showOverlays
    +*	-	hideOverlays
    +*	-	setPaintStyle
    +*	-	setHoverPaintStyle
    +*	-	setParameter
    +*	-	setParameters
    +*	-	setAnchor
    +*   - 	getLabel
    +*	-	getOverlay
    +*	-	isHover
    +*	-	isDetachable
    +*	-	getParameter
    +*	-	getParameters
    +*	-	getPaintStyle
    +*	-	getHoverPaintStyle
    +*	-	detachAll : Detaches all the Connections from every Endpoint in the list. not chainable and does not return anything.
    +*	-	delete : Deletes every Endpoint in the list. not chainable and does not return anything.		
    +*	-	length : returns the length of the list.
    +*	-	get(index) : returns the Endpoint at 'index' in the list.
    +*	-	each(function(endpoint)...) : allows you to specify your own function to execute; this function is chainable.
    +*/  		
    +
    +/*
    + * Property: Defaults 
    + * 
    + * These are the default settings for jsPlumb.  They are what will be used if you do not supply specific pieces of information 
    + * to the various API calls. A convenient way to implement your own look and feel can be to override these defaults 
    + * by including a script somewhere after the jsPlumb include, but before you make any calls to jsPlumb.
    + * 
    + * Properties:
    + * 	-	*Anchor*				    The default anchor to use for all connections (both source and target). Default is "BottomCenter".
    + * 	-	*Anchors*				    The default anchors to use ([source, target]) for all connections. Defaults are ["BottomCenter", "BottomCenter"].
    + *  -   *ConnectionsDetachable*		Whether or not connections are detachable by default (using the mouse). Defaults to true.
    + *  -   *ConnectionOverlays*		The default overlay definitions for Connections. Defaults to an empty list.
    + * 	-	*Connector*				The default connector definition to use for all connections.  Default is "Bezier".
    + *  -   *Container*				Optional selector or element id that instructs jsPlumb to append elements it creates to a specific element.
    + *	-	*DoNotThrowErrors*		Defaults to false; whether or not to throw errors if a user specifies an unknown anchor, endpoint or connector type.
    + * 	-	*DragOptions*			The default drag options to pass in to connect, makeTarget and addEndpoint calls. Default is empty.
    + * 	-	*DropOptions*			The default drop options to pass in to connect, makeTarget and addEndpoint calls. Default is empty.
    + * 	-	*Endpoint*				The default endpoint definition to use for all connections (both source and target).  Default is "Dot".
    + *  -   *EndpointOverlays*		The default overlay definitions for Endpoints. Defaults to an empty list.
    + * 	-	*Endpoints*				The default endpoint definitions ([ source, target ]) to use for all connections.  Defaults are ["Dot", "Dot"].
    + * 	-	*EndpointStyle*			The default style definition to use for all endpoints. Default is fillStyle:"#456".
    + * 	-	*EndpointStyles*		The default style definitions ([ source, target ]) to use for all endpoints.  Defaults are empty.
    + * 	-	*EndpointHoverStyle*	The default hover style definition to use for all endpoints. Default is null.
    + * 	-	*EndpointHoverStyles*	The default hover style definitions ([ source, target ]) to use for all endpoints. Defaults are null.
    + * 	-	*HoverPaintStyle*		The default hover style definition to use for all connections. Defaults are null.
    + * 	-	*LabelStyle*			The default style to use for label overlays on connections.
    + * 	-	*LogEnabled*			Whether or not the jsPlumb log is enabled. defaults to false.
    + * 	-	*Overlays*				The default overlay definitions (for both Connections and Endpoint). Defaults to an empty list.
    + * 	-	*MaxConnections*		The default maximum number of connections for an Endpoint.  Defaults to 1.		 
    + * 	-	*PaintStyle*			The default paint style for a connection. Default is line width of 8 pixels, with color "#456".
    + * 	-	*ReattachConnections*	Whether or not to reattach Connections that a user has detached with the mouse and then dropped. Default is false.
    + * 	-	*RenderMode*			What mode to use to paint with.  If you're on IE<9, you don't really get to choose this.  You'll just get VML.  Otherwise, the jsPlumb default is to use SVG.
    + * 	-	*Scope*				The default "scope" to use for connections. Scope lets you assign connections to different categories. 
    + */
    +
    + /*
    +  * Function: getAllConnections()
    +  * Returns:
    +  * All connections, as a map of the form:
    +  *
    +  *  { scope -> [ connection... ] } 
    +  */
    +
    +  /*
    +   * Function: getDefaultScope()
    +   * Gets the default scope for connections and  endpoints.
    +   *
    +   * A scope defines a type of endpoint/connection; supplying a
    +   * scope to an Endpoint or Connection allows you to support different
    +   * types of connections in the same UI. but if you're only interested in
    +   * one type of connection, you don't need to supply a scope. this method
    +   * will probably be used by very few people; it's good for testing
    +   * though.
    +   */  
    +
    +   /**
    +    * Function: getEndpoints(el)
    +    * Gets the list of Endpoints for a given element.
    +    *
    +    * Parameters:
    +    * 	el - element id, dom element, or selector.
    +    *
    +    * Returns:
    +    * 	An array of Endpoints for the specified element.
    +    */   
    +
    +/*
    + Function: getEndpoint(uuid)
    + Gets an Endpoint by UUID
    +  
    + Parameters: 
    + 	uuid - the UUID for the Endpoint
    + 	 
    + Returns: 
    + 	Endpoint with the given UUID, null if nothing found.
    +*/   
    +
    +/*
    + * Function: getSelector([context], spec)
    + * This method takes the given selector spec and, using the current underlying library, turns it into
    + * a selector from that library.  This method exists really as a helper function for those applications
    + * where you're writing jsPlumb code that will target more than one library (such as in the case of the
    + * jsPlumb demo pages).
    + *
    + * Parameters:
    + *  context  -   an element to search from. may be omitted (not null: omitted. as in you only pass one argument to the function)
    + * 	spec 	-	a valid selector string.
    + *
    + */ 
    +
    + /*
    + * Function: isHoverSuspended()
    + * Returns:
    + * whether or not hover effects are currently suspended.
    + */
    +
    + /*
    + * Function: setHoverSuspended(s)
    + * Sets whether or not hover effects should be suspended. jsPlumb uses this internally during various
    + * drag/drop operations, and it is exposed because it might also be useful for you too.
    + * Parameters:
    + * s - whether or not to set hover suspended.
    + */
    +
    + /*
    +   Function: hide (el, [changeEndpoints])
    +   Sets an element's connections to be hidden.
    +   
    +   Parameters: 
    +   	el - either the id of the element, or a selector for the element.
    +   	changeEndpoints - whether not to also hide endpoints on the element. by default this is false.  
    +   	 
    +   Returns: 
    +   	The jsPlumb instance.
    +  */
    +
    +  /**
    +   * Function: makeTarget(el, [params])
    +   * Makes some DOM element a Connection target, allowing you to drag connections to it
    +   * without having to register any Endpoints on it first.  When a Connection is established,
    +   * the endpoint spec that was passed in to this method is used to create a suitable 
    +   * Endpoint (the default will be used if you do not provide one).
    +   * 
    +   * Parameters:
    +   *  el		-	string id or element selector for the element to make a target.
    +   * 	params	-	JS object containing parameters:
    +   * 	  - *endpoint*	optional.	specification of an Endpoint to create when a Connection is established.
    +   * 	  - *scope*		optional.   scope for the drop zone.
    +   * 	  - *dropOptions* optional. same stuff as you would pass to dropOptions of an Endpoint definition.
    +   * 	  - *deleteEndpointsOnDetach*  optional, defaults to true. whether or not to delete
    +   *                             any Endpoints created by a connection to this target if
    +   *                             the connection is subsequently detached. this will not 
    +   *                             remove Endpoints that have had more Connections attached
    +   *                             to them after they were created.
    +   *   - *maxConnections*  optional. Specifies the maximum number of Connections that can be made to this element as a target. Default is no limit.
    +   *   - *onMaxConnections* optional. Function to call when user attempts to drop a connection but the limit has been reached.
    +   *   		The callback is passed two arguments: a JS object with:
    +   *   		: { element, connection, maxConnection }
    +   *   		...and the original event.		 
    +   */
    +
    +   /*
    +   *	Function: unmakeTarget(el)
    +   *	Sets the given element to no longer be a connection target.
    +   *	
    +   *	Parameters:
    +   *		el - a string id, a dom element, or a selector representing the element.
    +   *		
    +   *	Returns:
    +   *	The current jsPlumb instance.
    +   */ 
    +
    +     /*
    +      * Function: makeTargets(els, [params], [referenceParams])
    +      * Makes all elements in some array or a selector connection targets.
    +      * 
    +      * Parameters:
    +      * 	els 	- 	either an array of ids or a selector
    +      *  params  -   parameters to configure each element as a target with
    +      *  referenceParams - extra parameters to configure each element as a target with.
    +      *
    +      * Returns:
    +      * The current jsPlumb instance.
    +      */
    +
    +      /*
    +       * Function: makeSource(el, [params])
    +       * Makes some DOM element a Connection source, allowing you to drag connections from it
    +       * without having to register any Endpoints on it first.  When a Connection is established,
    +       * the endpoint spec that was passed in to this method is used to create a suitable 
    +       * Endpoint (the default will be used if you do not provide one).
    +       * 
    +       * Parameters:
    +       *  el		-	string id or element selector for the element to make a source.
    +       * 	params	-	JS object containing parameters:
    +       * 	  - *endpoint*	optional.	specification of an endpoint to create when a connection is created.
    +       * 	  - *parent*	optional.   the element to add Endpoints to when a Connection is established.  if you omit this, 
    +       *                          Endpoints will be added to 'el'.
    +       * 	  - *scope*		optional.   scope for the connections dragged from this element.
    +       * 	  - *dragOptions* optional. same stuff as you would pass to dragOptions of an Endpoint definition.
    +       * 	  - *deleteEndpointsOnDetach*  optional, defaults to false. whether or not to delete
    +       *                             any Endpoints created by a connection from this source if
    +       *                             the connection is subsequently detached. this will not 
    +       *                             remove Endpoints that have had more Connections attached
    +       *                             to them after they were created.
    +       * 	  - *filter* - optional function to call when the user presses the mouse button to start a drag. This function is passed the original 
    +       * event and the element on which the associated makeSource call was made.  If it returns anything other than false,
    +       * the drag begins as usual. But if it returns false (the boolean false, not just something falsey), the drag is aborted.		 
    +       */      
    +
    +
    +/*
    +*	Function: unmakeSource(el)
    +*	Sets the given element to no longer be a connection source.
    +*	
    +*	Parameters:
    +*		el - a string id, a dom element, or a selector representing the element.
    +*		
    +*	Returns:
    +*	The current jsPlumb instance.
    +*/
    +
    +/*
    +*	Function: unmakeEverySource()
    +*	Resets all elements in this instance of jsPlumb so that none of them are connection sources.
    +*	
    +*	Returns:
    +*	The current jsPlumb instance.
    +*/
    +/*
    +*	Function: unmakeEveryTarget()
    +*	Resets all elements in this instance of jsPlumb so that none of them are connection targets.
    +*	
    +*	Returns:
    +*	The current jsPlumb instance.
    +*/
    +
    +/*
    + * Function: makeSources(els, [params], [referenceParams])
    + * Makes all elements in some array or a selector connection sources.
    + * 
    + * Parameters:
    + * 	els 	- 	either an array of ids or a selector
    + *  params  -   parameters to configure each element as a source with
    + *  referenceParams - extra parameters to configure each element as a source with.
    + *
    + * Returns:
    + * The current jsPlumb instance.
    + */
    +
    + /*
    + * Function: setSourceEnabled(el, state)
    + * Sets the enabled state of one or more elements that were previously made a connection source with the makeSource
    + * method.
    + *
    + * Parameters:
    + *	el 	- 	either a string representing some element's id, or an array of ids, or a selector.
    + *	state - true to enable the element(s), false to disable it.
    + *
    + * Returns:
    + *	The current jsPlumb instance.
    + */
    +
    + /*
    + *	Function: toggleSourceEnabled(el)
    + *	Toggles the source enabled state of the given element or elements.
    + *
    + * 	Parameters:
    + *		el 	- 	either a string representing some element's id, or an array of ids, or a selector.
    + *
    + *	Returns:
    + *	The current enabled state of the source.
    + */
    +
    + /*
    + *	Function: isSource(el)
    + *	Returns whether or not the given element is registered as a connection source.
    + *
    + *	Parameters:
    + *		el 	- 	a string id, a dom element, or a selector representing a single element.
    + *
    + *	Returns:
    + *	True if source, false if not.
    + */
    +
    + /*
    + *	Function: isSourceEnabled(el)
    + *	Returns whether or not the given connection source is enabled.
    + *
    + *	Parameters:
    + *	el 	- 	a string id, a dom element, or a selector representing a single element.
    + *
    + *	Returns:
    + *	True if enabled, false if not.
    + */
    +
    + /*
    + *	Function: setTargetEnabled(el, state)
    + *	Sets the enabled state of one or more elements that were previously made a connection target with the makeTarget method.
    + *	method.
    + *
    + *	Parameters:
    + *		el 	- 	either a string representing some element's id, or an array of ids, or a selector.
    + *		state - true to enable the element(s), false to disable it.
    + *
    + *	Returns:
    + *	The current jsPlumb instance.
    + */
    +
    + /*
    + *	Function: toggleTargetEnabled(el)
    + *	Toggles the target enabled state of the given element or elements.
    + *
    + *	Parameters:
    + *		el 	- 	either a string representing some element's id, or an array of ids, or a selector.
    + *
    + *	Returns:
    + *	The current enabled state of the target.
    + */
    +
    + /*
    + 	Function: isTarget(el)
    + 	Returns whether or not the given element is registered as a connection target.
    +
    + 	Parameters:
    + 		el 	- 	a string id, a dom element, or a selector representing a single element.
    +
    + 	Returns:
    + 	True if source, false if not.
    + */
    +
    + /*
    + 	Function: isTargetEnabled(el)
    + 	Returns whether or not the given connection target is enabled.
    +
    + 	Parameters:
    + 		el 	- 	a string id, a dom element, or a selector representing a single element.
    +
    + 	Returns:
    + 	True if enabled, false if not.
    + */
    +
    + /*
    + * Function: ready(fn)
    + * Helper method to bind a function to jsPlumb's ready event. You should use this method instead of your
    + * library's equivalent, to ensure that jsPlumb has loaded properly before you start to use it. This is
    + * particularly true in the case of YUI, because of the asynchronous nature of the module loading process.
    + * 
    + * Parameters:
    + * fn - function to call once the instance is ready.
    + */
    +
    + /*
    + * Function: repaint (el)
    + * Repaints an element and its connections. This method gets new sizes for the elements before painting anything.
    + *  
    + * Parameters: 
    + *	el - id of the element, a dom element, or a selector representing the element.
    + *  	 
    + * Returns: 
    + *	The current jsPlumb instance.
    + *  	 
    + * See Also: 
    + *	<repaintEverything>
    + */
    +
    + /*
    + * Function: repaintEverything() 
    + * Repaints all connections and endpoints.
    + *  
    + * Returns: 
    + *	The current jsPlumb instance.
    + * 	
    + * See Also: 
    + *	<repaint>
    + */
    +
    + 		/*
    + 		* Function: removeAllEndpoints(el, [recurse]) 
    + 		* Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes.
    + 		* 
    + 		* Parameters: 
    + 		*	el - either an element id, or a selector for an element.
    +         *   recurse - whether or not to recurse down through this elements children and remove their endpoints too. defaults to false.
    + 		*  	 
    + 		* Returns: 
    + 		* The current jsPlumb instance.
    + 		*  	 
    + 		* See Also: 
    + 		* <deleteEndpoint>
    + 		*/
    +
    + 		/*
    + 		* Function: remove(el)
    + 		* Removes the given element from the DOM, along with all Endpoints associated with it,
    + 		* and their connections.  This is present in jsPlumb since version 1.4.0.
    + 		*
    + 		* Parameters:
    + 		*  el - either an element id, a DOM element, or a selector for the element.
    + 		*/ 	
    +
    + 		/*
    + 		* Function:reset()
    + 		* Removes all endpoints and connections and clears the listener list. To keep listeners call
    + 		* : jsPlumb.deleteEveryEndpoint()
    + 		* instead of this.
    + 		*/
    +
    + 		/*
    + 		 * Function: setDefaultScope(scope)
    + 		 * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a
    + 		 * scope to an Endpoint or Connection allows you to support different
    + 		 * types of Connections in the same UI.  If you're only interested in
    + 		 * one type of Connection, you don't need to supply a scope. This method
    + 		 * will probably be used by very few people; it just instructs jsPlumb
    + 		 * to use a different key for the default scope.
    + 		 * 
    + 		 * Parameters:
    + 		 * 	scope - scope to set as default.
    + 		 *
    + 		 * Returns:
    + 		 * The current jsPlumb instance.
    + 		 */ 
    +
    + 		 /*
    + 		  * Function: setDraggable(el, draggable) 
    + 		  * Sets whether or not a given element is
    + 		  * draggable, regardless of what any jsPlumb command may request.
    + 		  * 
    + 		  * Parameters: 
    + 		  * 	el - either the id for the element, or a selector representing the element.
    + 		  * 	draggable - whether or not the element should be draggable.
    + 		  *  
    + 		  * Returns: 
    + 		  * 	void
    + 		  */
    +
    + 		  /*
    + 		  * Function: setId(el, newId, [doNotSetAttribute])
    + 		  * Changes the id of some element, adjusting all connections and endpoints
    + 		  *
    + 		  * Parameters:
    + 		  * 	el - a selector, a DOM element, or a string. 
    + 		  * 	newId - string.
    + 		  *		doNotSetAttributes - defaults to false; if true, the id on the DOM element wont be changed.
    + 		  */ 
    +
    +/*
    +* Function: setIdChanged(oldId, newId)
    +* Notify jsPlumb that the element with oldId has had its id changed to newId.
    +*
    +* This method is equivalent to what jsPlumb does itself in the second step of the setId method above.
    +*
    +* Parameters:
    +* 	oldId	- 	previous element id
    +* 	newId	-	element's new id
    +*
    +* See Also:
    +* 	<setId>
    +*/
    +
    +/*
    + * Function: setSuspendDrawing(val, [repaintAfterwards])
    + * Suspends drawing operations.  This can (and should!) be used when you have a lot of connections to make or endpoints to register;
    + * it will save you a lot of time.
    + *
    + * Parameters:
    + * 	val	-	boolean indicating whether to suspend or not
    + * 	repaintAfterwards	-	optional boolean instructing jsPlumb to do a full repaint after changing the suspension
    + * 	state. defaults to false.
    + */
    +
    + /*
    + * Function: isSuspendDrawing()
    + * Returns:
    + * Whether or not drawing is currently suspended.
    + */ 
    +
    + /*
    +  * Function: show(el, [changeEndpoints])
    +  * Sets an element's connections to be visible.
    +  * 
    +  * Parameters: 
    +  * 	el - either the id of the element, or a selector for the element.
    +  *  changeEndpoints - whether or not to also change the visible state of the endpoints on the element.  this also has a bearing on
    +  *  other connections on those endpoints: if their other endpoint is also visible, the connections are made visible.  
    +  *  
    +  * Returns: 
    +  * 	The current jsPlumb instance.
    +  */
    +
    +  /*
    +   * Function: toggleVisible(el, [changeEndpoints]) 
    +   * Toggles visibility of an element's Connections.
    +   *  
    +   * Parameters: 
    +   * 	el - either the element's id, or a selector representing the element.
    +   *  changeEndpoints - whether or not to also toggle the endpoints on the element.
    +   *  
    +   * Returns: 
    +   * 	void, but should be updated to return the current state
    +   */
    +
    +   /*
    +    * Function: toggleDraggable(el)
    +    * Toggles draggability (sic?) of an element's Connections.
    +    *  
    +    * Parameters: 
    +    * 	el - either the element's id, or a selector representing the element.
    +    *  
    +    * Returns: 
    +    * 	The current draggable state.
    +    */   
    +
    +    /*
    +     * Function: recalculateOffsets(el)
    +     * Recalculates the offsets of all child elements of some element. If you have Endpoints registered on the
    +     * descendants of some element and you make changes to that element's markup, it is possible that the location
    +     * of each Endpooint relative to the origin of the element may have changed. So you call this to tell jsPlumb to
    +     * recalculate.  You need to do this because, for performance reasons, jsplumb won't calculate these offsets on
    +     * the fly.
    +     *
    +     * Parameters:
    +     * el - either a string id, or a selector.
    +     */
    +
    + /*
    + * Property: connectorClass 
    + *   The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is.
    + */
    + /*
    + * Property: hoverClass 
    + *   The CSS class to set on Connection or Endpoint elements when hovering. This value is a String and can have multiple classes; the entire String is appended as-is.
    + */
    + /*
    + * Property: endpointClass 
    + *   The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is.
    + */
    +/*
    +* Property: endpointConnectedClass 
    +*  The CSS class to set on an Endpoint element when its Endpoint has at least one connection. This value is a String and can have multiple classes; the entire String is appended as-is.
    +*/
    +/*
    +* Property: endpointFullClass 
    +*  The CSS class to set on a full Endpoint element. This value is a String and can have multiple classes; the entire String is appended as-is.
    +*/
    +/*
    +* Property: endpointDropAllowedClass 
    +*  The CSS class to set on an Endpoint on which a drop will be allowed (during drag and drop). This value is a String and can have multiple classes; the entire String is appended as-is.
    +*/
    +/*
    +* Property: endpointDropForbiddenClass 
    +*  The CSS class to set on an Endpoint on which a drop will be forbidden (during drag and drop). This value is a String and can have multiple classes; the entire String is appended as-is.
    +*/
    +/*
    +* Property: overlayClass 
    +* The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is.
    +*/ 
    +/*
    +* Property: draggingClass 
    +* The CSS class to set on connections that are being dragged. This value is a String and can have multiple classes; the entire String is appended as-is.
    +*/
    +/*
    +* Property: elementDraggingClass 
    +* The CSS class to set on connections whose source or target element is being dragged, and
    +* on their endpoints too. This value is a String and can have multiple classes; the entire String is appended as-is.
    +*/  
    +/*
    +* Property: endpointAnchorClassPrefix
    +* The prefix for the CSS class to set on endpoints that have dynamic anchors whose individual locations
    +* have declared an associated CSS class. This value is a String and, unlike the other classes, is expected
    +* to contain a single value, as it is used as a prefix for the final class: '_***' is appended,
    +* where "***" is the CSS class associated with the current dynamic anchor location.
    +*/
    +
    +
    +/*
    + * Property: Anchors.Top
    + * An Anchor that is located at the top center of the element.
    + */
    +/*
    + * Property: Anchors.Bottom
    + * An Anchor that is located at the bottom center of the element.
    + */
    +/*
    + * Property: Anchors.Left
    + * An Anchor that is located at the left middle of the element.
    + */
    +/*
    + * Property: Anchors.Right
    + * An Anchor that is located at the right middle of the element.
    + */
    +/*
    + * Property: Anchors.Center
    + * An Anchor that is located at the center of the element.
    + */
    +/*
    + * Property: Anchors.TopRight
    + * An Anchor that is located at the top right corner of the element.
    + */
    +/*
    + * Property: Anchors.BottomRight
    + * An Anchor that is located at the bottom right corner of the element.
    + */
    +/*
    + * Property: Anchors.TopLeft
    + * An Anchor that is located at the top left corner of the element.
    + */
    +/*
    + * Property: Anchors.BottomLeft
    + * An Anchor that is located at the bototm left corner of the element.
    + */
    +/*
    + * Property: Anchors.AutoDefault
    + * Default DynamicAnchors - chooses from TopCenter, RightMiddle, BottomCenter, LeftMiddle.
    + */
    +/*
    + * Property: Anchors.Assign
    + * An Anchor whose location is assigned at connection time, through an AnchorPositionFinder. Used in conjunction
    + * with the 'makeTarget' function. jsPlumb has two of these - 'Fixed' and 'Grid', and you can also write your own.
    + */
    +/*
    + * Property: Anchors.Continuous
    + * An Anchor that tracks the other element in the connection, choosing the closest face.
    + */
    +/*
    +* Property: Anchors.ContinuousLeft
    +* A continuous anchor that uses only the left edge of the element.
    +*/
    +/*
    +* Property: Anchors.ContinuousTop
    +* A continuous anchor that uses only the top edge of the element.
    +*/            
    +/*
    +* Property: Anchors.ContinuousBottom
    +* A continuous anchor that uses only the bottom edge of the element.
    +*/
    +/*
    +* Property: Anchors.ContinuousRight
    +* A continuous anchor that uses only the right edge of the element.
    +*/
    +/*
    + * Property: Anchors.Perimeter
    + * An Anchor that tracks the perimeter of some shape, approximating it with a given number of dynamically
    + * positioned locations.
    + *
    + * Parameters:
    + *
    + * anchorCount  -   optional number of anchors to use to approximate the perimeter. default is 60.
    + * shape        -   required. the name of the shape. valid values are 'rectangle', 'square', 'ellipse', 'circle', 'triangle' and 'diamond'
    + * rotation     -   optional rotation, in degrees, to apply. 
    + */
    +
    +
    +
    +// ---------------- ENDPOINT -----------------------------------------------------
    +
    +/*
    + * Class: Endpoint 
    + * 
    + * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1 
    + * to allow unlimited).  Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't
    + * actually deal with the underlying Endpoint objects.  But if you wish to support drag and drop Connections, one of the ways you
    + * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or
    + * 'target' Endpoints for Connections. 
    + *
    + * You never need to create one of these directly; jsPlumb will create them as needed.  
    + * 
    + * 
    + */
    +
    +/*
    +* Property: canvas
    +* The Endpoint's drawing area.
    +*/
    +/*
    +* Property: connections
    +* List of Connections this Endpoint is attached to.
    +*/
    +/*
    +* Property: scope
    +* Scope descriptor for this Endpoint.
    +*/
    + /*
    +* Property: overlays
    +* List of Overlays for this Endpoint.
    +*/
    +
    +/*
    + * Function: Endpoint 
    + * 
    + * Endpoint constructor.
    + * 
    + * Parameters: 
    + * anchor - definition of the Anchor for the endpoint.  You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection.  Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]).  To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions.
    + * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ].
    + * enabled - optional, defaults to true. Indicates whether or not the Endpoint should be enabled for mouse events (drag/drop).
    + * paintStyle - endpoint style, a js object. may be null. 
    + * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null.
    + * cssClass - optional CSS class to set on the display element associated with this Endpoint.
    + * hoverClass - optional CSS class to set on the display element associated with this Endpoint when it is in hover state.
    + * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required.
    + * canvas - canvas element to use. may be, and most often is, null.
    + * container - optional id or selector instructing jsPlumb where to attach the element it creates for this endpoint.  you should read the documentation for a full discussion of this.
    + * connections - optional list of Connections to configure the Endpoint with. 
    + * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false.
    + * maxConnections - integer; defaults to 1.  a value of -1 means no upper limit. 
    + * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null. 
    + * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null.
    + * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null.
    + * connector - optional Connector type to use.  Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ].
    + * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint. 
    + * connectorClass - optional CSS class to set on Connections emanating from this Endpoint.
    + * connectorHoverClass - optional CSS class to set on to set on Connections emanating from this Endpoint when they are in hover state.		 
    + * connectionsDetachable - optional, defaults to true. Sets whether connections to/from this Endpoint should be detachable or not.
    + * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false.
    + * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null. 
    + * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted.
    + * parameters - Optional JS object containing parameters to set on the Endpoint. These parameters are then available via the getParameter method.  When a connection is made involving this Endpoint, the parameters from this Endpoint are copied into that Connection. Source Endpoint parameters override target Endpoint parameters if they both have a parameter with the same name.
    + * connector-pointer-events - Optional. a value for the 'pointer-events' property of any SVG elements that are created to render connections from this endoint.
    + */
    +
    +/*
    + * Function: addConnection(connection)
    + *   Adds a Connection to this Endpoint.
    + *   
    + * Parameters:
    + *   connection - the Connection to add.
    + */
    + /*
    + * Function: addOverlay(overlaySpec)
    + * Adds an Overlay to the Endpoint.
    + * 
    + * Parameters:
    + * 	overlaySpec - Overlay to add. This is not an Overlay object but rather a specification in the format that jsPlumb uses.
    + */
    + /*
    + * Function: addType(typeId)
    + * Adds the specified type to the Endpoint. Types are registered using registerEndpointType on the associated jsPlumb instance.  See the wiki page for a full discussion.
    + * 
    + * Parameters:
    + * 	typeId - Id of the type to add.
    + */
    +
    + /*
    +  * Function: bind(event, callback)
    +  * Bind to an event on the Endpoint.  
    +  * 
    +  * Parameters:
    +  * 	event - the event to bind.  Available events on an Endpoint are:
    +  *         - *click*						:	notification that a Endpoint was clicked.  
    +  *         - *dblclick*						:	notification that a Endpoint was double clicked.
    +  *         - *mouseenter*					:	notification that the mouse is over a Endpoint. 
    +  *         - *mouseexit*					:	notification that the mouse exited a Endpoint.
    +  * 		   - *contextmenu*                  :   notification that the user right-clicked on the Endpoint.
    +  *         
    +  *  callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event.    
    +  */
    +
    +/*
    + * Function: detach(connection, [ignoreTarget])
    + *   Detaches the given Connection from this Endpoint.
    + *   
    + * Parameters:
    + *   connection - the Connection to detach.
    + *   ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached.  The default behaviour is to notify the target.
    + */
    +/*
    + * Function: detachAll([fireEvent])
    + *   Detaches all Connections this Endpoint has.
    + *
    + * Parameters:
    + *  fireEvent   -   whether or not to fire the detach event.  defaults to false.
    + */
    +/*
    + * Function: detachFrom(targetEndpoint, [fireEvent])
    + *   Removes any connections from this Endpoint that are connected to the given target endpoint.
    + *   
    + * Parameters:
    + *   targetEndpoint     - Endpoint from which to detach all Connections from this Endpoint.
    + *   fireEvent          - whether or not to fire the detach event. defaults to false.
    + */
    +/*
    + * Function: detachFromConnection(connection)
    + *   Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging.
    + *   
    + * Parameters:
    + *   connection - Connection to detach from.
    + */
    +/*
    + * Function: getElement()
    + *
    + * Returns:
    + * 	The DOM element this Endpoint is attached to.
    + */
    +
    + /*
    + * Function: getLabel()
    + * Returns:
    + * The label text for this Endpoint (or a function if you are labelling with a function).
    + * This does not return the overlay itself; this is a convenience method which is a pair with
    + * setLabel; together they allow you to add and access a Label Overlay without having to create the
    + * Overlay object itself.  For access to the underlying label overlay that jsPlumb has created,
    + * use getLabelOverlay.
    + */
    +
    + /*
    + * Function: getLabelOverlay()
    + * Returns:
    + * The underlying internal label overlay, which will exist if you specified a label on
    + * an addEndpoint call, or have called setLabel at any stage.   
    + */
    +
    + /*
    + * Function: getPaintStyle()
    + * Returns:
    + * The Endpoint's paint style. This is not necessarily the paint style in use at the time;
    + * this is the paint style for the Endpoint when the mouse it not hovering over it.
    + */
    +
    + /*
    +  * Function: getOverlay(overlayId)
    +  * Gets an overlay, by ID. Note: by ID.  You would pass an 'id' parameter
    +  * in to the Overlay's constructor arguments, and then use that to retrieve
    +  * it via this method.
    +  *
    +  * Parameters:
    +  *	overlayId 	-	String id for the overlay.
    +  *
    +  * Returns:
    +  * an Overlay or null if no overlay with that id is registered.
    +  */
    +
    + /*
    + * Function: getOverlays()
    + * Returns:
    + * An array of all Overlays for the component.
    + */
    +
    + /*
    + * Function: getParameter(key)
    + * Gets the named parameter; returns null if no parameter with that name is set. Parameters may have been set on the Endpoint in the 'addEndpoint' call, or they may have been set with the setParameter function.
    + *
    + * Parameters:
    + *   key - Parameter name.
    + *
    + * Returns:
    + * The value stored against the given parameter name, null if not found.
    + */
    +
    + /*
    + * Function: getParameters()
    + * Returns:
    + * All of the Endpoint's parameters. Note that you can edit the return value of this method and this will change the parameters on the Endpoint.
    + */
    +
    + /* 
    + * Function: getType()
    + * Returns:
    + * The Endpoint's current type(s)
    + */
    +
    +/*
    + * Function: getUuid()
    + * Returns: 
    + * The UUID for this Endpoint, if there is one. Otherwise returns null.
    + */
    +
    + /*
    + * Function: hasType(typeId)
    + * Returns:
    + * Whether or not the Endpoint has the given type.
    + */
    +
    + /*
    +  * Function: hideOverlay(overlayId)
    +  * Hides the overlay specified by the given id.
    +  *
    +  * Parameters:
    +  *		overlayId 	- 	id of the overlay to hide.
    +  */
    +
    + /*
    +  * Function: hideOverlays()
    +  * Hides all Overlays
    +  */
    +
    +/*
    + * Function: isConnectedTo(endpoint)
    + * 
    + * Parameters:
    + *   endpoint - Endpoint to test.
    + *
    + * Returns:
    + * True if connected to the given Endpoint, false otherwise.
    + */
    +
    + /*
    + * Function: isEnabled()
    + * Returns:
    + * True if the Endpoint is enabled for drag/drop connections enabled, false otherwise.
    + */
    +
    +/*
    + * Function: isFull()
    + * Returns:
    + * True if the Endpoint cannot accept any more Connections, false otherwise.
    + */
    +
    + /*
    + * Function: isVisible()
    + * Returns:
    + * Whether or not the Endpoint is currently visible.
    + */ 
    +
    + /*
    +  * Function: paint(params)
    +  *   Paints the Endpoint, recalculating offset and anchor positions if necessary. This does NOT paint
    +  *   any of the Endpoint's connections.  NOTE: the arguments to this method should be in a js object, not
    +  * individual arguments.
    +  *   
    +  * Parameters:
    +  *   timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again.
    +  *   canvas - optional Canvas to paint on.  Only used internally by jsPlumb in certain obscure situations.
    +  *   connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied.
    +  */
    +
    +/*
    +* Function: reapplyTypes(data, [doNotRepaint])  
    +* Reapplies all of the current types, but with the given data.
    +*
    +* Parameters:
    +* data - data to use for parameterised types
    +* doNotRepaint - if true, dont repaint after reapplying types.
    +*/
    +
    +/*
    +* Function: removeAllOverlays()
    +* Removes all overlays from the Endpoint, and then repaints.
    +*/
    +
    +/*
    +* Function: removeOverlay(overlayId)
    +* Removes an overlay by ID.  Note: by ID.  this is a string you set in the overlay spec.
    +*
    +* Parameters:
    +* overlayId - id of the overlay to remove.
    +*/
    +
    +/*
    +* Function: removeOverlays(overlayIds...)
    +* Removes a set of overlays by ID.  Note: by ID.  this is a string you set in the overlay spec.
    +*
    +* Parameters:
    +* overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id.
    +*/
    +
    +/*
    +* Function: removeType(typeId)
    +* Removes the specified type from the Endpoint. Types are registered using registerEndpointType on the associated jsPlumb instance.  See the wiki page for a full discussion.
    +* 
    +* Parameters:
    +* 	typeId - Id of the type to remove.
    +*/
    +
    +/*
    +* Function: setAnchor(anchorParams, [doNotRepaint])
    +* Sets the Endpoint's anchor. This takes an anchor in any supported form in a jsPlumb.connect
    +* or jsPlumb.addEndpoint call.
    +*
    +* Parameters:
    +* 	anchorParams - anchor spec
    +* 	doNotRepaint - optional, defaults to false.
    +*/	
    +
    +/*
    + * Function: setDragAllowedWhenFull(allowed)
    + *   Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in 
    + *   which you're going to provide some other way of breaking connections, if you need to break them at all. This property 
    + *   is by default true; use it in conjunction with the 'reattach' option on a connect call.
    + *   
    + * Parameters:
    + *   allowed - whether drag is allowed or not when the Endpoint is full.
    + */   
    +
    + /*
    +  * Function: setElement(el, [container])
    +  * Sets the DOM element this Endpoint is attached to.
    +  *
    +  * Parameters:
    +  * 	el	-	dom element or selector
    +  * 	container	-	optional, specifies the actual parent element to use as the parent for this Endpoint's visual representation.
    +  * 	See the jsPlumb documentation for a discussion about this.
    +  */ 
    +
    + /*
    + * Function: setEnabled(enabled)
    + * Sets whether or not the Endpoint is enabled for drag/drop connections.
    + *
    + * Parameters:
    + * 	enabled - whether or not the Endpoint is enabled.			
    + */
    +
    + /*
    + * Function: setEndpoint(endpointSpec)
    + * Sets the Endpoint's visual representation and behaviour. The format of the value you pass to
    + * this method is the same as you use for the second argument to a jsPlumb.addEndpoint call.
    + *
    + * Parameters:
    + * 	endpointSpec - endpoint spec
    + */	
    +
    + /*
    +  * Function: setHover(hover, [ignoreAttachedElements])
    +  * Sets/unsets the hover state of this Endpoint.
    +  * 
    +  * Parameters:
    +  * 	hover - hover state boolean
    +  * 	ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state.  used mostly to avoid infinite loops.
    +  */
    +
    + /*
    +  * Function: setHoverPaintStyle(style, [doNotRepaint])
    +  * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default.
    +  * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace
    +  * it.  This is because people will most likely want to change just one thing when hovering, say the
    +  * color for example, but leave the rest of the appearance the same.
    +  * 
    +  * Parameters:
    +  *  style - Style to use when the mouse is hovering.
    +  *  doNotRepaint - if true, the Endpoint will not be repainted.  useful when setting things up initially.
    +  */
    +
    +  /*
    +   * Function: setLabel(label)
    +   * Sets the Endpoint's label.  
    +   * 
    +   * Parameters:
    +   * 	label	- label to set. May be a String, a Function that returns a String, or a params object containing { "label", "labelStyle", "location", "cssClass" }.  Note that this uses innerHTML on the label div, so keep
    +   * that in mind if you need escaped HTML.
    +   */  
    +
    +/*
    + * Function: setPaintStyle(style)
    + * Sets the Endpoint's paint style and then repaints the Endpoint.
    + * 
    + * Parameters:
    + * 	style - Style to use.
    + */
    +
    + /*
    + * Function: setParameter(key, value)
    + * Sets the named parameter to the given value.
    + *
    + * Parameters:
    + *   key - Parameter name.
    + *   value - Parameter value.
    + */
    +
    + /*
    + * Function: setParameters(parameters)
    + * Sets the Endpoint's parameters.
    + *
    + * Parameters:
    + *   params - JS object containing [key,value] pairs.
    + */
    +
    + /*
    + * Function: setType(typeId)
    + * Sets the specified type of the Endpoint.  This replaces all existing types. Types are registered using registerEndpointType on the associated jsPlumb instance.  See the wiki page for a full discussion.
    + * 
    + * Parameters:
    + * 	typeId - Id of the type to set on the Endpoint.
    + */
    +   
    + /*
    +* Function: setVisible(visible, [doNotChangeConnections], [doNotNotifyOtherEndpoint])
    +* Sets whether or not the Endpoint is currently visible.
    +* 
    +* Parameters:
    +* visible - whether or not the Endpoint should be visible.
    +* doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false.
    +* doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false. 
    + */
    +
    +/*
    + * Function: showOverlay(overlayId)
    + * Shows the overlay specified by the given id.
    + *
    + * Parameters:
    + *	overlayId - id of the overlay to show.
    + */
    +
    +/*
    + * Function: showOverlays()
    + * Shows all Overlays 
    + */
    +
    + /*
    + * Function: toggleType(typeId)
    + * Toggles the specified type on the Endpoint. Types are registered using registerEndpointType on the associated jsPlumb instance.  See the wiki page for a full discussion.
    + * 
    + * Parameters:
    + * 	typeId - Id of the type to toggle.
    + */
    +
    +// ---------------- / ENDPOINT -----------------------------------------------------
    +
    +// --------------- CONNECTION ----------------------
    +
    +/*
    + * Class: Connection
    + * The connecting line between two elements.  A Connection consists of two Endpoints and a Connector.    
    + */
    +
    +/*
    +* Property: sourceId
    +* Id of the source element in the connection.
    +*/
    +
    +/*
    +* Property: targetId
    +* Id of the target element in the connection.
    +*/
    +
    +/*
    +* Property: scope
    +* Optional scope descriptor for the connection.
    +*/
    +
    +/*
    +* Property: endpoints
    +* Array of Endpoints.
    +*/
    +
    +/*
    +*	Property: source
    +*	The source element for this Connection.
    +*/
    +
    +/*
    +*	Property: target
    +*	The target element for this Connection.
    +*/
    +
    +/*
    +* Property: overlays
    +* List of Overlays for this component.
    +*/
    +
    +/*
    + * Function: Connection
    + * Connection constructor. You should not ever create one of these directly. If you make a call to jsPlumb.connect, all of
    + * the parameters that you pass in to that function will be passed to the Connection constructor; if your UI
    + * uses the various Endpoint-centric methods like addEndpoint/makeSource/makeTarget, along with drag and drop,
    + * then the parameters you set on those functions are translated and passed in to the Connection constructor. So
    + * you should check the documentation for each of those methods.
    + * 
    + * Parameters:
    + * 	source 	- either an element id, a selector for an element, or an Endpoint.
    + * 	target	- either an element id, a selector for an element, or an Endpoint
    + * 	scope	- scope descriptor for this connection. optional.
    + *  container - optional id or selector instructing jsPlumb where to attach all the elements it creates for this connection.  you should read the documentation for a full discussion of this.
    + *  detachable - optional, defaults to true. Defines whether or not the connection may be detached using the mouse.
    + *  reattach	- optional, defaults to false. Defines whether not the connection should be retached if it was dragged off an Endpoint and then dropped in whitespace.
    + *  endpoint - Optional. Endpoint definition to use for both ends of the connection.
    + *  endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters.
    + *  endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection.
    + *  endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters.
    + *  paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here.
    + *  hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null).
    + *  cssClass - optional CSS class to set on the display element associated with this Connection.
    + *  hoverClass - optional CSS class to set on the display element associated with this Connection when it is in hover state.
    + *  overlays - Optional array of Overlay definitions to appear on this Connection.
    + *  drawEndpoints - if false, instructs jsPlumb to not draw the endpoints for this Connection.  Be careful with this: it only really works when you tell jsPlumb to attach elements to the document body. Read the documentation for a full discussion of this. 
    + *  parameters - Optional JS object containing parameters to set on the Connection. These parameters are then available via the getParameter method.
    + */
    +
    + /*
    +  * Function: addOverlay(overlaySpec)
    +  * Adds an Overlay to the Connection.
    +  * 
    +  * Parameters:
    +  * 	overlaySpec - specification of the Overlay to add.
    +  */ 
    +
    +  /*
    +  * Function: addType(typeId)
    +  * Adds the specified type to the Connection. Types are registered using registerConnectionType on the associated jsPlumb instance.  See the wiki page for a full discussion.
    +  * 
    +  * Parameters:
    +  * 	typeId - Id of the type to add.
    +  */  
    +
    +/*
    +* Function: bind(event, callback)
    +* Bind to an event on the Connection.  
    +* 
    +* Parameters:
    +* 	event - the event to bind.  Available events on a Connection are:
    +*         - *click*						:	notification that a Connection was clicked.  
    +*         - *dblclick*						:	notification that a Connection was double clicked.
    +*         - *mouseenter*					:	notification that the mouse is over a Connection. 
    +*         - *mouseexit*					:	notification that the mouse exited a Connection.
    +* 		   - *contextmenu*                  :   notification that the user right-clicked on the Connection.
    +*         
    +*  callback - function to callback. This function will be passed the Connection that caused the event, and also the original event.    
    +*/
    +
    +/*
    +* Function: getConnector()
    +* Gets The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc)
    +*/
    +
    +/*
    +* Function: getLabel()
    +* Returns the label text for this Connection (or a function if you are labelling with a function).
    +* 
    +* This does not return the overlay itself; this is a convenience method which is a pair with
    +* <setLabel>; together they allow you to add and access a Label Overlay without having to create the
    +* Overlay object itself.  For access to the underlying label overlay that jsPlumb has created,
    +* use <getLabelOverlay>.
    +*
    +* See Also:
    +* 	<getOverlay>
    +* 	<getLabelOverlay>
    +*/
    +
    +/*
    +* Function: getLabelOverlay()
    +* Returns:
    +* The underlying internal label overlay, which will exist if you specified a label on
    +* a connect call, or have called setLabel at any stage.   
    +*/
    +
    +/*
    + * Function: getOverlay(overlayId)
    + * Gets an overlay, by ID. Note: by ID.  You would pass an 'id' parameter
    + * in to the Overlay's constructor arguments, and then use that to retrieve
    + * it via this method.
    + *
    + * Parameters:
    + * 	overlayId 	-	id of the overlay to retrieve.
    + *
    + * Returns:
    + * The overlay stored against the given id, null if not found.
    + */
    +
    +/*
    + * Function: getOverlays()
    + * Gets all the overlays for this component.
    + */
    +
    +/*
    +* Function: getPaintStyle()
    +* Gets the Connection's paint style. This is not necessarily the paint style in use at the time;
    +* this is the paint style for the connection when the mouse it not hovering over it.
    +* Returns:
    +* The current non-hover paint style.
    +*/
    +
    +/*
    +* Function: getParameter(key)
    +* Gets the named parameter; returns null if no parameter with that name is set. Parameter values may have been supplied to a 'connect' or 'addEndpoint' call (connections made with the mouse get a copy of all parameters set on each of their Endpoints), or the parameter value may have been set with setParameter.
    +*
    +* Parameters:
    +*   key - Parameter name.
    +* 
    +* Returns:
    +* a value stored against the given key, or null if none found.
    +*/
    +
    +/*
    +* Function: getParameters()
    +* Gets all parameters for the Connection
    +*
    +* Returns:
    +* All of the Connection's parameters. Note that you can edit the return value of this method and this will change the parameters on the Connection.
    +*/
    +
    + /* 
    + * Function: getType()
    + * Returns:
    + * The Endpoint's current type(s)
    + */
    +
    + /*
    + * Function: hasType(typeId)
    + * Returns:
    + * Whether or not the Endpoint has the given type.
    + */
    +
    +/*
    + * Function: hideOverlay(overlayId)
    + * Hides the overlay specified by the given id.
    + *
    + * Parameters:
    + * 	overlayId 	-	id of the overlay to hide.
    + */
    +
    +/*
    + * Function: hideOverlays()
    + * Hides all Overlays
    + */
    +
    +/*
    + * Function: isDetachable()
    + * Returns whether or not this connection can be detached from its target/source endpoint.  by default this
    + * is false; use it in conjunction with the 'reattach' parameter.
    + */
    +
    + /*
    + * Function: isEditable()
    + * Returns whether or not the Connection is editable.
    + */
    +
    +/*
    +* Function: isReattach()
    +* Returns whether or not this connection will be reattached after having been detached via the mouse and dropped.  by default this
    +* is false; use it in conjunction with the 'detachable' parameter.
    +*/
    +
    + /*
    + * Function: isVisible()
    + * Returns whether or not the Connection is currently visible.
    + */
    +
    + /*
    + * Function: reapplyTypes(data, [doNotRepaint])  
    + * Reapplies all of the current types, but with the given data.
    + *
    + * Parameters:
    + * data - data to use for parameterised types
    + * doNotRepaint - if true, dont repaint after reapplying types.
    + */
    +
    + /*
    +  * Function: removeAllOverlays()
    +  * Removes all overlays from the Connection, and then repaints.
    +  */
    +
    + /*
    +  * Function: removeOverlay(overlayId)
    +  * Removes an overlay by ID.  Note: by ID.  this is a string you set in the overlay spec.
    +  * 
    +  * Parameters:
    +  * 	overlayId - id of the overlay to remove.
    +  */
    +
    +/*
    +* Function: removeOverlays(overlayIds...)
    +* Removes a set of overlays by ID.  Note: by ID.  this is a string you set in the overlay spec.
    +* 
    +* Parameters:
    +* 	overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id.
    +*/ 
    +
    +/*
    +* Function: removeType(typeId)
    +* Removes the specified type from the Connection. Types are registered using registerConnectionType on the associated jsPlumb instance.  See the wiki page for a full discussion.
    +* 
    +* Parameters:
    +* 	typeId - Id of the type to remove.
    +*/    
    +
    +/*
    +* Function: setConnector(connector)
    +* Sets the Connection's connector (eg "Bezier", "Flowchart", etc).  You pass a Connector definition into this method, the same
    +* thing that you would set as the 'connector' property on a jsPlumb.connect call.
    +* 
    +* Parameters:
    +* 	connector		-	Connector definition
    +*/   
    +
    +/*
    +* Function: setDetachable(detachable)
    +* Sets whether or not this connection is detachable.
    +*
    +* Parameters:
    +* 	detachable - whether or not to set the Connection to be detachable.
    +*/
    +
    +/*
    +* Function: setEditable(editable)
    +* Sets whether or not the Connection is editable. This will only be honoured if
    +* the underlying Connector is editable - not all types are.
    +*
    +* Parameters:
    +* 	editable - whether or not to set the Connection to be editable
    +*/
    +
    +/*
    + * Function: setHover(hover, [ignoreAttachedElements])
    + * Sets/unsets the hover state of this Connection.
    + * 
    + * Parameters:
    + * 	hover - hover state boolean
    + * 	ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state.  used mostly to avoid infinite loops.
    + */
    +
    +/*
    + * Function: setHoverPaintStyle(style, [doNotRepaint])
    + * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default.
    + * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace
    + * it.  This is because people will most likely want to change just one thing when hovering, say the
    + * color for example, but leave the rest of the appearance the same.
    + * 
    + * Parameters:
    + * 	style - Style to use when the mouse is hovering.
    + *  doNotRepaint - if true, the Connection will not be repainted.  useful when setting things up initially.
    + */
    +
    + /*
    +  * Function: setLabel(label)
    +  * Sets the Connection's label.  
    +  * 
    +  * Parameters:
    +  * 	label	- label to set. May be a String, a Function that returns a String, or a params object containing { "label", "labelStyle", "location", "cssClass" }.  Note that this uses innerHTML on the label div, so keep
    +  * that in mind if you need escaped HTML.
    +  */
    +        
    +/*
    + * Function: setPaintStyle(style)
    + * Sets the Connection's paint style and then repaints the Connection.
    + * 
    + * Parameters:
    + * 	style - Style to use.
    + */
    +                       
    + /*
    + * Function: setParameter(key, value)
    + * Sets the named parameter to the given value.
    + *
    + * Parameters:
    + *   key - Parameter name.
    + *   value - Parameter value.
    + */
    +
    + /*
    + * Function: setParameters(parameters)
    + * Sets the Connection's parameters.
    + *
    + * Parameters:
    + *   params - JS object containing [key,value] pairs.
    + */
    +
    + /*
    + * Function: setReattach(reattach)
    + * Sets whether or not this connection will reattach after having been detached via the mouse and dropped.
    + *
    + * Parameters:
    + * 	reattach	-	whether or not to set the Connection to reattach after drop in whitespace.
    + */
    +
    + /*
    + * Function: setType(typeId)
    + * Sets the type of the Connection. This replaces all existing types. Types are registered using registerConnectionType on the associated jsPlumb instance.  See the wiki page for a full discussion.
    + * 
    + * Parameters:
    + * 	typeId - Id of the type to set.
    + */
    +
    + /*
    + * Function: setVisible(visible)
    + * Sets whether or not the Connection should be visible.
    + *
    + * Parameters:
    + *	visible - boolean indicating desired visible state.
    + */
    +                                                 
    +/*
    + * Function: showOverlay(overlayId)
    + * Shows the overlay specified by the given id.
    + *
    + * Parameters:
    + * 	overlayId 	-	id of the overlay to show. 
    + */
    +
    +/*
    + * Function: showOverlays()
    + * Shows all Overlays 
    + */
    +
    + /*
    + * Function: toggleType(typeId)
    + * Toggles the specified type on the Connection. Types are registered using registerConnectionType on the associated jsPlumb instance.  See the wiki page for a full discussion.
    + * 
    + * Parameters:
    + * 	typeId - Id of the type to toggle.
    + */
    +    	
    +    	
    +        
    +    	
    +         
    diff --git a/src/jsPlumb-connection.js b/src/jsPlumb-connection.js
    new file mode 100644
    index 000000000..e7b92692f
    --- /dev/null
    +++ b/src/jsPlumb-connection.js
    @@ -0,0 +1,520 @@
    +;(function() {
    +    
    +    jsPlumb.Connection = function(params) {
    +        var self = this, visible = true, _internalHover, _superClassHover,
    +            _jsPlumb = params["_jsPlumb"],
    +            jpcl = jsPlumb.CurrentLibrary,
    +            _att = jpcl.getAttribute,
    +            _gel = jpcl.getElementObject,
    +            _ju = jsPlumbUtil,
    +            _getOffset = jpcl.getOffset,
    +            _newConnection = params.newConnection,
    +            _newEndpoint = params.newEndpoint,
    +            connector = null;
    +        
    +        self.idPrefix = "_jsplumb_c_";
    +        self.defaultLabelLocation = 0.5;
    +        self.defaultOverlayKeys = ["Overlays", "ConnectionOverlays"];
    +        this.parent = params.parent;
    +        overlayCapableJsPlumbUIComponent.apply(this, arguments);
    +        // ************** get the source and target and register the connection. *******************
    +        
    +// VISIBILITY						
    +        
    +        this.isVisible = function() { return visible; };
    +        
    +        this.setVisible = function(v) {
    +            visible = v;
    +            self[v ? "showOverlays" : "hideOverlays"]();
    +            if (connector && connector.canvas) connector.canvas.style.display = v ? "block" : "none";
    +            self.repaint();
    +        };
    +// END VISIBILITY	
    +                    
    +// EDITABLE
    +        
    +        var editable = params.editable === true;        
    +        this.setEditable = function(e) {
    +            if (connector && connector.isEditable())
    +                editable = e;
    +            
    +            return editable;
    +        };        
    +        this.isEditable = function() { return editable; };
    +        this.editStarted = function() {            
    +            self.fire("editStarted", {
    +                path:connector.getPath()
    +            });            
    +            _jsPlumb.setHoverSuspended(true);
    +        };
    +        this.editCompleted = function() {            
    +            self.fire("editCompleted", {
    +                path:connector.getPath()
    +            });       
    +            self.setHover(false);     
    +            _jsPlumb.setHoverSuspended(false);
    +        };
    +        this.editCanceled = function() {
    +            self.fire("editCanceled", {
    +                path:connector.getPath()
    +            });
    +            self.setHover(false);
    +            _jsPlumb.setHoverSuspended(false);
    +        };
    +       
    +// END EDITABLE            
    +        
    +// ADD CLASS/REMOVE CLASS - override to support adding/removing to/from endpoints
    +        var _ac = this.addClass, _rc = this.removeClass;
    +        this.addClass = function(c, informEndpoints) {
    +            _ac(c);
    +            if (informEndpoints) {
    +                self.endpoints[0].addClass(c);
    +                self.endpoints[1].addClass(c);                    
    +            }
    +        };
    +        this.removeClass = function(c, informEndpoints) {
    +            _rc(c);
    +            if (informEndpoints) {
    +                self.endpoints[0].removeClass(c);
    +                self.endpoints[1].removeClass(c);                    
    +            }
    +        };            
    +        
    +// TYPE		
    +        
    +        this.getTypeDescriptor = function() { return "connection"; };
    +        this.getDefaultType = function() {
    +            return {
    +                parameters:{},
    +                scope:null,
    +                detachable:self._jsPlumb.Defaults.ConnectionsDetachable,
    +                rettach:self._jsPlumb.Defaults.ReattachConnections,
    +                paintStyle:self._jsPlumb.Defaults.PaintStyle || jsPlumb.Defaults.PaintStyle,
    +                connector:self._jsPlumb.Defaults.Connector || jsPlumb.Defaults.Connector,
    +                hoverPaintStyle:self._jsPlumb.Defaults.HoverPaintStyle || jsPlumb.Defaults.HoverPaintStyle,				
    +                overlays:self._jsPlumb.Defaults.ConnectorOverlays || jsPlumb.Defaults.ConnectorOverlays
    +            };
    +        };
    +        var superAt = this.applyType;
    +        this.applyType = function(t, doNotRepaint) {
    +            superAt(t, doNotRepaint);
    +            if (t.detachable != null) self.setDetachable(t.detachable);
    +            if (t.reattach != null) self.setReattach(t.reattach);
    +            if (t.scope) self.scope = t.scope;
    +            editable = t.editable;
    +            self.setConnector(t.connector, doNotRepaint);
    +        };			
    +// END TYPE
    +
    +// HOVER			
    +        // override setHover to pass it down to the underlying connector
    +        _superClassHover = self.setHover;
    +        self.setHover = function(state) {
    +            connector.setHover.apply(connector, arguments);				
    +            _superClassHover.apply(self, arguments);
    +        };
    +        
    +        _internalHover = function(state) {
    +            if (!_jsPlumb.isConnectionBeingDragged()) {
    +                self.setHover(state, false);
    +            }
    +        };
    +// END HOVER
    +
    +        var makeConnector = function(renderMode, connectorName, connectorArgs) {
    +            var c = new Object();
    +            jsPlumb.Connectors[connectorName].apply(c, [connectorArgs]);
    +            jsPlumb.ConnectorRenderers[renderMode].apply(c, [connectorArgs]);	
    +            return c;
    +        };                        
    +                
    +        this.setConnector = function(connectorSpec, doNotRepaint) {
    +            if (connector != null) _ju.removeElements(connector.getDisplayElements());
    +            var connectorArgs = { 
    +                _jsPlumb:self._jsPlumb, 
    +                parent:params.parent, 
    +                cssClass:params.cssClass, 
    +                container:params.container, 
    +                tooltip:self.tooltip,
    +                "pointer-events":params["pointer-events"]
    +            },
    +            renderMode = _jsPlumb.getRenderMode();
    +            
    +            if (_ju.isString(connectorSpec)) 
    +                connector = makeConnector(renderMode, connectorSpec, connectorArgs); // lets you use a string as shorthand.
    +            else if (_ju.isArray(connectorSpec)) {
    +                if (connectorSpec.length == 1)
    +                    connector = makeConnector(renderMode, connectorSpec[0], connectorArgs);
    +                else
    +                    connector = makeConnector(renderMode, connectorSpec[0], _ju.merge(connectorSpec[1], connectorArgs));
    +            }
    +            // binds mouse listeners to the current connector.
    +            self.bindListeners(connector, self, _internalHover);
    +            
    +            self.canvas = connector.canvas;
    +
    +            if (editable && jsPlumb.ConnectorEditors != null && jsPlumb.ConnectorEditors[connector.type] && connector.isEditable()) {
    +                new jsPlumb.ConnectorEditors[connector.type]({
    +                    connector:connector,
    +                    connection:self,
    +                    params:params.editorParams || { }
    +                });
    +            }
    +            else {                    
    +                editable = false;
    +            }                
    +                
    +            if (!doNotRepaint) self.repaint();
    +        };
    +
    +        this.getConnector = function() { return connector; };
    +        
    +// INITIALISATION CODE			
    +                    
    +        this.source = _gel(params.source);
    +        this.target = _gel(params.target);
    +        // sourceEndpoint and targetEndpoint override source/target, if they are present. but 
    +        // source is not overridden if the Endpoint has declared it is not the final target of a connection;
    +        // instead we use the source that the Endpoint declares will be the final source element.
    +        if (params.sourceEndpoint) this.source = params.sourceEndpoint.endpointWillMoveTo || params.sourceEndpoint.getElement();			
    +        if (params.targetEndpoint) this.target = params.targetEndpoint.getElement();
    +        
    +        // if a new connection is the result of moving some existing connection, params.previousConnection
    +        // will have that Connection in it. listeners for the jsPlumbConnection event can look for that
    +        // member and take action if they need to.
    +        self.previousConnection = params.previousConnection;
    +                    
    +        this.sourceId = _att(this.source, "id");
    +        this.targetId = _att(this.target, "id");
    +        this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints.			
    +        this.endpoints = [];
    +        this.endpointStyles = [];
    +        // wrapped the main function to return null if no input given. this lets us cascade defaults properly.
    +        var _makeAnchor = function(anchorParams, elementId) {
    +            return (anchorParams) ? _jsPlumb.makeAnchor(anchorParams, elementId, _jsPlumb) : null;
    +        },
    +        prepareEndpoint = function(existing, index, params, element, elementId, connectorPaintStyle, connectorHoverPaintStyle) {
    +            var e;
    +            if (existing) {
    +                self.endpoints[index] = existing;
    +                existing.addConnection(self);					
    +            } else {
    +                if (!params.endpoints) params.endpoints = [ null, null ];
    +                var ep = params.endpoints[index] 
    +                        || params.endpoint
    +                        || _jsPlumb.Defaults.Endpoints[index]
    +                        || jsPlumb.Defaults.Endpoints[index]
    +                        || _jsPlumb.Defaults.Endpoint
    +                        || jsPlumb.Defaults.Endpoint;
    +
    +                if (!params.endpointStyles) params.endpointStyles = [ null, null ];
    +                if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ];
    +                var es = params.endpointStyles[index] || params.endpointStyle || _jsPlumb.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _jsPlumb.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle;
    +                // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified.
    +                if (es.fillStyle == null && connectorPaintStyle != null)
    +                    es.fillStyle = connectorPaintStyle.strokeStyle;
    +                
    +                // TODO: decide if the endpoint should derive the connection's outline width and color.  currently it does:
    +                //*
    +                if (es.outlineColor == null && connectorPaintStyle != null) 
    +                    es.outlineColor = connectorPaintStyle.outlineColor;
    +                if (es.outlineWidth == null && connectorPaintStyle != null) 
    +                    es.outlineWidth = connectorPaintStyle.outlineWidth;
    +                //*/
    +                
    +                var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _jsPlumb.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _jsPlumb.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle;
    +                // endpoint hover fill style is derived from connector's hover stroke style.  TODO: do we want to do this by default? for sure?
    +                if (connectorHoverPaintStyle != null) {
    +                    if (ehs == null) ehs = {};
    +                    if (ehs.fillStyle == null) {
    +                        ehs.fillStyle = connectorHoverPaintStyle.strokeStyle;
    +                    }
    +                }
    +                var a = params.anchors ? params.anchors[index] : 
    +                    params.anchor ? params.anchor :
    +                    _makeAnchor(_jsPlumb.Defaults.Anchors[index], elementId) || 
    +                    _makeAnchor(jsPlumb.Defaults.Anchors[index], elementId) || 
    +                    _makeAnchor(_jsPlumb.Defaults.Anchor, elementId) || 
    +                    _makeAnchor(jsPlumb.Defaults.Anchor, elementId),					
    +                u = params.uuids ? params.uuids[index] : null;
    +                e = _newEndpoint({ 
    +                    paintStyle : es,  hoverPaintStyle:ehs,  endpoint : ep,  connections : [ self ], 
    +                    uuid : u,  anchor : a,  source : element, scope  : params.scope, container:params.container,
    +                    reattach:params.reattach || _jsPlumb.Defaults.ReattachConnections,
    +                    detachable:params.detachable || _jsPlumb.Defaults.ConnectionsDetachable
    +                });
    +                self.endpoints[index] = e;
    +                
    +                if (params.drawEndpoints === false) e.setVisible(false, true, true);
    +                                    
    +            }
    +            return e;
    +        };					
    +
    +        var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source,
    +                                 self.sourceId, params.paintStyle, params.hoverPaintStyle);			
    +        if (eS) _ju.addToList(params.endpointsByElement, this.sourceId, eS);						
    +        var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target, 
    +                                 self.targetId, params.paintStyle, params.hoverPaintStyle);
    +        if (eT) _ju.addToList(params.endpointsByElement, this.targetId, eT);
    +        // if scope not set, set it to be the scope for the source endpoint.
    +        if (!this.scope) this.scope = this.endpoints[0].scope;		
    +        
    +        // if delete endpoints on detach, keep a record of just exactly which endpoints they are.
    +        self.endpointsToDeleteOnDetach = [null, null];
    +        if (params.deleteEndpointsOnDetach) {
    +            if (params.sourceIsNew) self.endpointsToDeleteOnDetach[0] = self.endpoints[0];
    +            if (params.targetIsNew) self.endpointsToDeleteOnDetach[1] = self.endpoints[1];
    +        }
    +        // or if the endpoints were supplied, use them.
    +        if (params.endpointsToDeleteOnDetach)
    +            self.endpointsToDeleteOnDetach = params.endpointsToDeleteOnDetach;
    +                    
    +        // TODO these could surely be refactored into some method that tries them one at a time until something exists
    +        self.setConnector(this.endpoints[0].connector || 
    +                          this.endpoints[1].connector || 
    +                          params.connector || 
    +                          _jsPlumb.Defaults.Connector || 
    +                          jsPlumb.Defaults.Connector, true);
    +
    +        if (params.path)
    +            connector.setPath(params.path);
    +        
    +        this.setPaintStyle(this.endpoints[0].connectorStyle || 
    +                           this.endpoints[1].connectorStyle || 
    +                           params.paintStyle || 
    +                           _jsPlumb.Defaults.PaintStyle || 
    +                           jsPlumb.Defaults.PaintStyle, true);
    +                    
    +        this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || 
    +                                this.endpoints[1].connectorHoverStyle || 
    +                                params.hoverPaintStyle || 
    +                                _jsPlumb.Defaults.HoverPaintStyle || 
    +                                jsPlumb.Defaults.HoverPaintStyle, true);
    +        
    +        this.paintStyleInUse = this.getPaintStyle();
    +        
    +        var _suspendedAt = _jsPlumb.getSuspendedAt();
    +        _jsPlumb.updateOffset( { elId : this.sourceId, timestamp:_suspendedAt });
    +        _jsPlumb.updateOffset( { elId : this.targetId, timestamp:_suspendedAt });
    +        
    +        // paint the endpoints
    +        var myInfo = _jsPlumb.getCachedData(this.sourceId),
    +            myOffset = myInfo.o, myWH = myInfo.s,
    +            otherInfo = _jsPlumb.getCachedData(this.targetId),
    +            otherOffset = otherInfo.o,
    +            otherWH = otherInfo.s,
    +            initialTimestamp = _suspendedAt || _jsPlumb.timestamp(),
    +            anchorLoc = this.endpoints[0].anchor.compute( {
    +                xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0],
    +                elementId:this.endpoints[0].elementId,
    +                txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1],
    +                timestamp:initialTimestamp
    +            });
    +
    +        this.endpoints[0].paint( { anchorLoc : anchorLoc, timestamp:initialTimestamp });
    +
    +        anchorLoc = this.endpoints[1].anchor.compute( {
    +            xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1],
    +            elementId:this.endpoints[1].elementId,				
    +            txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0],
    +            timestamp:initialTimestamp				
    +        });
    +        this.endpoints[1].paint({ anchorLoc : anchorLoc, timestamp:initialTimestamp });
    +                                
    +// END INITIALISATION CODE			
    +        
    +// DETACHABLE 				
    +        var _detachable = _jsPlumb.Defaults.ConnectionsDetachable;
    +        if (params.detachable === false) _detachable = false;
    +        if(self.endpoints[0].connectionsDetachable === false) _detachable = false;
    +        if(self.endpoints[1].connectionsDetachable === false) _detachable = false;        
    +        this.isDetachable = function() {
    +            return _detachable === true;
    +        };        
    +        this.setDetachable = function(detachable) {
    +          _detachable = detachable === true;
    +        };
    +// END DETACHABLE
    +
    +// REATTACH
    +        var _reattach = params.reattach ||
    +            self.endpoints[0].reattachConnections ||
    +            self.endpoints[1].reattachConnections ||
    +            _jsPlumb.Defaults.ReattachConnections;        
    +        this.isReattach = function() {
    +            return _reattach === true;
    +        };        
    +        this.setReattach = function(reattach) {
    +          _reattach = reattach === true;
    +        };
    +
    +// END REATTACH
    +        
    +// COST + DIRECTIONALITY
    +        // if cost not supplied, try to inherit from source endpoint
    +        var _cost = params.cost || self.endpoints[0].getConnectionCost();			
    +        self.getCost = function() { return _cost; };
    +        self.setCost = function(c) { _cost = c; };			
    +        var directed = params.directed;
    +        // inherit directed flag if set no source endpoint
    +        if (params.directed == null) directed = self.endpoints[0].areConnectionsDirected();
    +        self.isDirected = function() { return directed === true; };
    +// END COST + DIRECTIONALITY
    +                    
    +// PARAMETERS						
    +        // merge all the parameters objects into the connection.  parameters set
    +        // on the connection take precedence; then target endpoint params, then
    +        // finally source endpoint params.
    +        // TODO jsPlumb.extend could be made to take more than two args, and it would
    +        // apply the second through nth args in order.
    +        var _p = jsPlumb.extend({}, this.endpoints[0].getParameters());
    +        jsPlumb.extend(_p, this.endpoints[1].getParameters());
    +        jsPlumb.extend(_p, self.getParameters());
    +        self.setParameters(_p);
    +// END PARAMETERS
    +                    
    +// MISCELLANEOUS	
    +        
    +        this.getAttachedElements = function() {
    +            return self.endpoints;
    +        };
    +        
    +        //
    +        // changes the parent element of this connection to newParent.  not exposed for the public API.
    +        //
    +        this.moveParent = function(newParent) {
    +            var jpcl = jsPlumb.CurrentLibrary, curParent = jpcl.getParent(connector.canvas);				
    +            if (connector.bgCanvas) {
    +                jpcl.removeElement(connector.bgCanvas);
    +                jpcl.appendElement(connector.bgCanvas, newParent);
    +            }
    +            jpcl.removeElement(connector.canvas);
    +            jpcl.appendElement(connector.canvas, newParent);                
    +            // this only applies for DOMOverlays
    +            for (var i = 0; i < self.overlays.length; i++) {
    +                if (self.overlays[i].isAppendedAtTopLevel) {
    +                    jpcl.removeElement(self.overlays[i].canvas);
    +                    jpcl.appendElement(self.overlays[i].canvas, newParent);
    +                    if (self.overlays[i].reattachListeners) 
    +                        self.overlays[i].reattachListeners(connector);
    +                }
    +            }
    +            if (connector.reattachListeners)		// this is for SVG/VML; change an element's parent and you have to reinit its listeners.
    +                connector.reattachListeners();     // the Canvas implementation doesn't have to care about this
    +        };
    +        
    +// END MISCELLANEOUS
    +
    +// PAINTING
    +            
    +        /*
    +         * Paints the Connection.  Not exposed for public usage. 
    +         * 
    +         * Parameters:
    +         * 	elId - Id of the element that is in motion.
    +         * 	ui - current library's event system ui object (present if we came from a drag to get here).
    +         *  recalc - whether or not to recalculate all anchors etc before painting. 
    +         *  timestamp - timestamp of this paint.  If the Connection was last painted with the same timestamp, it does not paint again.
    +         */
    +        var lastPaintedAt = null;			
    +        this.paint = function(params) {
    +            
    +            if (visible) {
    +                    
    +                params = params || {};
    +                var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp,
    +                    // if the moving object is not the source we must transpose the two references.
    +                    swap = false,
    +                    tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId,
    +                    tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0;
    +
    +                if (timestamp == null || timestamp != lastPaintedAt) {                        
    +                    var sourceInfo = _jsPlumb.updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }).o,
    +                        targetInfo = _jsPlumb.updateOffset( { elId : tId, timestamp : timestamp }).o, // update the target if this is a forced repaint. otherwise, only the source has been moved.
    +                        sE = this.endpoints[sIdx], tE = this.endpoints[tIdx];
    +
    +                    if (params.clearEdits) {
    +                        sE.anchor.clearUserDefinedLocation();
    +                        tE.anchor.clearUserDefinedLocation();
    +                        connector.setEdited(false);
    +                    }
    +                    
    +                    var sAnchorP = sE.anchor.getCurrentLocation(sE),				
    +                        tAnchorP = tE.anchor.getCurrentLocation(tE);                                
    +                        
    +                    connector.resetBounds();
    +
    +                    connector.compute({
    +                        sourcePos:sAnchorP,
    +                        targetPos:tAnchorP, 
    +                        sourceEndpoint:this.endpoints[sIdx],
    +                        targetEndpoint:this.endpoints[tIdx],
    +                        lineWidth:self.paintStyleInUse.lineWidth,                        					
    +                        sourceInfo:sourceInfo,
    +                        targetInfo:targetInfo,
    +                        clearEdits:params.clearEdits === true
    +                    });                                                                                        
    +
    +                    var overlayExtents = {
    +                        minX:Infinity,
    +                        minY:Infinity,
    +                        maxX:-Infinity,
    +                        maxY:-Infinity
    +                    };                    
    +                    // compute overlays. we do this first so we can get their placements, and adjust the
    +                    // container if needs be (if an overlay would be clipped)
    +                    for ( var i = 0; i < self.overlays.length; i++) {
    +                        var o = self.overlays[i];
    +                        if (o.isVisible()) {
    +                            self.overlayPlacements[i] = o.draw(connector, self.paintStyleInUse);
    +                            overlayExtents.minX = Math.min(overlayExtents.minX, self.overlayPlacements[i].minX);
    +                            overlayExtents.maxX = Math.max(overlayExtents.maxX, self.overlayPlacements[i].maxX);
    +                            overlayExtents.minY = Math.min(overlayExtents.minY, self.overlayPlacements[i].minY);
    +                            overlayExtents.maxY = Math.max(overlayExtents.maxY, self.overlayPlacements[i].maxY);
    +                        }
    +                    }
    +
    +                    var lineWidth = (self.paintStyleInUse.lineWidth || 1) / 2,
    +                        outlineWidth = self.paintStyleInUse.lineWidth || 0,
    +                        extents = {
    +                            xmin : Math.min(connector.bounds.minX - (lineWidth + outlineWidth), overlayExtents.minX),
    +                            ymin : Math.min(connector.bounds.minY - (lineWidth + outlineWidth), overlayExtents.minY),
    +                            xmax : Math.max(connector.bounds.maxX + (lineWidth + outlineWidth), overlayExtents.maxX),
    +                            ymax : Math.max(connector.bounds.maxY + (lineWidth + outlineWidth), overlayExtents.maxY)
    +                        };
    +
    +                    // paint the connector.
    +                    connector.paint(self.paintStyleInUse, null, extents);  
    +                    // and then the overlays
    +                    for ( var i = 0; i < self.overlays.length; i++) {
    +                        var o = self.overlays[i];
    +                        if (o.isVisible()) {
    +                            o.paint(self.overlayPlacements[i], extents);    
    +                        }
    +                    }                  
    +                                                            
    +                }
    +                lastPaintedAt = timestamp;						
    +            }		
    +        };			
    +
    +        /*
    +         * Function: repaint
    +         * Repaints the Connection. No parameters exposed to public API.
    +         */
    +        this.repaint = function(params) {
    +            params = params || {};
    +            var recalc = !(params.recalc === false);
    +            this.paint({ elId : this.sourceId, recalc : recalc, timestamp:params.timestamp, clearEdits:params.clearEdits });
    +        };
    +        
    +        // the very last thing we do is check to see if a 'type' was supplied in the params
    +        var _type = params.type || self.endpoints[0].connectionType || self.endpoints[1].connectionType;
    +        if (_type)
    +            self.addType(_type, params.data, _jsPlumb.isSuspendDrawing());
    +        
    +// END PAINTING    
    +    }; // END Connection class            
    +})();
    \ No newline at end of file
    diff --git a/src/jsPlumb-connector-editors.js b/src/jsPlumb-connector-editors.js
    new file mode 100644
    index 000000000..46abe0d5b
    --- /dev/null
    +++ b/src/jsPlumb-connector-editors.js
    @@ -0,0 +1,271 @@
    +/*
    + * jsPlumb
    + * 
    + * Title:jsPlumb 1.4.0
    + * 
    + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
    + * elements, or VML.  
    + * 
    + * This file contains the jsPlumb connector editors.  It is not deployed wth the released versions of jsPlumb; you need to
    + * include it as an extra script.
    + *
    + * Copyright (c) 2010 - 2013 Simon Porritt (simon.porritt@gmail.com)
    + * 
    + * http://jsplumb.org
    + * http://github.com/sporritt/jsplumb
    + * http://code.google.com/p/jsplumb
    + * 
    + * Dual licensed under the MIT and GPL2 licenses.
    + */
    +;(function() {
    +    
    +    var AbstractEditor = function(params) {
    +        var self = this;        
    +    };
    +    
    +    // TODO: this is for a Straight segment.it would be better to have these all available somewjere, keyed
    +    // by segment type
    +    var findClosestPointOnPath = function(seg, x, y, i) {
    +        var m = seg[0] == seg[2] ? Infinity : 0,
    +            m2 = -1 / m,
    +            out = { s:seg, m:m, i:i, x:-1, y:-1, d:Infinity };
    +        
    +        if (m == 0) {
    +            // a horizontal line. if x is in the range of this line then distance is delta y. otherwise we consider it to be
    +            // infinity.
    +            if ( (seg[0] <= x && x <= seg[2]) || (seg[2] <= x && x <= seg[0])) {
    +                out.x = x,
    +                out.y = seg[1];
    +                out.d = Math.abs(y - seg[1]);
    +            }
    +        }
    +        else if (m == Infinity || m == -Infinity) {
    +            // a vertical line. if y is in the range of this line then distance is delta x. otherwise we consider it to be
    +            // infinity.
    +            if ((seg[1] <= y && y <= seg[3]) || (seg[3] <= y && y <= seg[1])){
    +                out.x = seg[0];
    +                out.y = y;
    +                out.d = Math.abs(x - seg[0]);
    +            }                        
    +        }
    +        else {
    +            // closest point lies on normal from given point to this line.  
    +            var b = seg[1] - (m * seg[0]),
    +                b2 = y - (m2 * x),
    +            // now we know that
    +            // y1 = m.x1 + b   and   y1 = m2.x1 + b2
    +            // so:  m.x1 + b = m2.x1 + b2
    +            //      x1(m - m2) = b2 - b
    +            //      x1 = (b2 - b) / (m - m2)
    +                _x1 = (b2 -b) / (m - m2),
    +                _y1 = (m * _x1) + b,
    +                d = jsPlumbUtil.lineLength([ x, y ], [ _x1, _y1 ]),
    +                fractionInSegment = jsPlumbUtil.lineLength([ _x1, _y1 ], [ seg[0], seg[1] ]);
    +            
    +            out.d = d;
    +            out.x = _x1;
    +            out.y = _y1;
    +            out.l = fractionInSegment / length;
    +        }
    +        return out;
    +    };
    +    
    +    jsPlumb.ConnectorEditors = {
    +        /*
    +            Function: FlowchartConnectorEditor
    +            lets you drag the segments of a flowchart connection around.
    +        */
    +        "Flowchart":function(params) {
    +            AbstractEditor.apply(this, arguments);            
    +            
    +            var jpcl = jsPlumb.CurrentLibrary,
    +                documentMouseUp = function(e) { 
    +                    e.stopPropagation();
    +                    e.preventDefault();
    +                    jpcl.unbind(document, "mouseup", documentMouseUp);
    +                    jpcl.unbind(document, "mousemove", documentMouseMove);                    
    +                    downAt = null;
    +                    currentSegments = null;
    +                    selectedSegment = null; 
    +                    segmentCoords = null;
    +                    params.connection.setHover(false);                    
    +                    params.connector.setSuspendEvents(false); 
    +                    params.connection.endpoints[0].setSuspendEvents(false);                
    +                    params.connection.endpoints[1].setSuspendEvents(false);
    +                    params.connection.editCompleted();
    +                },
    +                downAt = null,
    +                currentSegments = null,
    +                selectedSegment = null,
    +                segmentCoords = null,
    +                anchorsMoveable = params.params.anchorsMoveable,
    +                sgn = function(p1, p2) {
    +                    if (p1[0] == p2[0])
    +                        return p1[1] < p2[1]  ? 1 : -1;
    +                    else
    +                        return p1[0] < p2[0]  ? 1 : -1;
    +                },
    +                // collapses currentSegments by joining subsequent segments that are in the
    +                // same axis. we do this because it doesn't matter about stubs any longer once a user
    +                // is editing a connector. so it is best to reduce the number of segments to the 
    +                // minimum.
    +                _collapseSegments = function() {                       
    +                    var _last = null, _lastAxis = null, s = [];
    +                    for (var i = 0; i < currentSegments.length; i++) {
    +                        var seg = currentSegments[i], axis = seg[4], axisIndex = (axis == "v" ? 3 : 2);
    +                        if (_last != null && _lastAxis === axis) {
    +                            _last[axisIndex] = seg[axisIndex];                            
    +                        }
    +                        else {
    +                            s.push(seg);
    +                            _last = seg;
    +                            _lastAxis = seg[4];
    +                        }
    +                    }
    +                    currentSegments = s;                   
    +                },
    +                // attempt to shift anchor
    +                _shiftAnchor = function(endpoint, horizontal, value) {                    
    +                    var elementSize = jpcl.getSize(endpoint.element),
    +                        sizeValue = elementSize[horizontal ? 1 : 0],
    +                        off = jpcl.getOffset(endpoint.element), 
    +                        cc = jpcl.getElementObject(params.connector.canvas.parentNode),
    +                        co = jpcl.getOffset(cc),
    +                        offValue = off[horizontal ? "top" : "left"] - co[horizontal ? "top" : "left"], 
    +                        ap = endpoint.anchor.getCurrentLocation(),
    +                        desiredLoc = horizontal ? params.connector.y + value : params.connector.x + value;
    +                    
    +                    if (anchorsMoveable) {                        
    +                        
    +                        if (offValue < desiredLoc && desiredLoc < offValue + sizeValue) {
    +                            // if still on the element, okay to move.
    +                            var udl = [ ap[0], ap[1] ];
    +                            ap[horizontal ? 1 : 0] = desiredLoc;
    +                            endpoint.anchor.setUserDefinedLocation(ap);
    +                            return value;
    +                        }
    +                        else {                        
    +                            // otherwise, clamp to element edge
    +                            var edgeVal = desiredLoc < offValue ? offValue : offValue + sizeValue;
    +                            return edgeVal - (horizontal ? params.connector.y: params.connector.x);                         
    +                        }                    
    +                    }
    +                    else {
    +                        // otherwise, return the current anchor point.
    +                        return ap[horizontal ? 1 : 0] - params.connector[horizontal ? "y" : "x"];
    +                    }
    +                },
    +                _updateSegmentOrientation = function(seg) {
    +                    if (seg[0] != seg[2]) seg[5] = (seg[0] < seg[2]) ? 1 : -1;
    +                    if (seg[1] != seg[3]) seg[6] = (seg[1] < seg[3]) ? 1 : -1;
    +                },
    +                documentMouseMove = function(e) {
    +                    if (selectedSegment != null) {
    +                        var m = selectedSegment.m, s = selectedSegment.s,
    +                            x = (e.pageX || e.page.x), y = (e.pageY || e.page.y),
    +                            dx = m == 0 ? 0 : x - downAt[0], dy = m == 0 ? y - downAt[1] : 0,
    +                            newX1 = segmentCoords[0] + dx,
    +                            newY1 = segmentCoords[1] + dy,
    +                            newX2 = segmentCoords[2] + dx,
    +                            newY2 = segmentCoords[3] + dy,
    +                            horizontal = s[4] == "h";
    +                        
    +                        // so here we know the new x,y values we would like to set for the start
    +                        // and end of this segment. but we may not be able to set these values: if this
    +                        // is the first segment, for example, then we are constrained by how far the anchor
    +                        // can move (before it slides off its element). same thing goes if this is the last
    +                        // segment. if this is not the first or last segment then there are other considerations.
    +                        // we know, from having run collapse segments, that there will never be two
    +                        // consecutive segments that are not at right angles to each other, so what we need to
    +                        // know is whether we can adjust the endpoint of the previous segment to the values we
    +                        // want, and the same question for the start values of the next segment.  the answer to
    +                        // that is whether or not the segment in question would be rendered too small by such
    +                        // a change. if that is the case (and the same goes for anchors) then we want to know
    +                        // what an agreeable value is, and we use that.
    +                        
    +                        if (selectedSegment.i == 0) {
    +                                                        
    +                            var anchorLoc = _shiftAnchor(params.connection.endpoints[0], horizontal, horizontal ? newY1 : newX1);                            
    +                            if (horizontal) 
    +                                newY1 = newY2 = anchorLoc; 
    +                            else
    +                                newX1 = newX2 = anchorLoc;
    +                        
    +                            currentSegments[1][0] = newX2;
    +                            currentSegments[1][1] = newY2;
    +                            _updateSegmentOrientation(currentSegments[1]);                                                                                            
    +                        }
    +                        else if (selectedSegment.i == currentSegments.length - 1) {
    +                            var anchorLoc = _shiftAnchor(params.connection.endpoints[1], horizontal, horizontal ? newY1 : newX1);                          
    +                            if (horizontal) 
    +                                newY1 = newY2 = anchorLoc; 
    +                            else
    +                                newX1 = newX2 = anchorLoc;
    +                            
    +                            currentSegments[currentSegments.length - 2][2] = newX1;
    +                            currentSegments[currentSegments.length - 2][3] = newY1;
    +                            _updateSegmentOrientation(currentSegments[currentSegments.length - 2]);
    +                        }
    +                        else {
    +                            if (!horizontal) {
    +                                currentSegments[selectedSegment.i - 1][2] = newX1;
    +                                currentSegments[selectedSegment.i + 1][0] = newX2;                                                                
    +                            }
    +                            else {
    +                                currentSegments[selectedSegment.i - 1][3] = newY1;                            
    +                                currentSegments[selectedSegment.i + 1][1] = newY2;
    +                            }
    +                            _updateSegmentOrientation(currentSegments[selectedSegment.i + 1]);
    +                            _updateSegmentOrientation(currentSegments[selectedSegment.i - 1]);                            
    +                        }
    +                                                                                                
    +                        s[0] = newX1;
    +                        s[1] = newY1;
    +                        s[2] = newX2;
    +                        s[3] = newY2;                                              
    +                        
    +                        params.connector.setSegments(currentSegments);
    +                        params.connection.repaint();                        
    +                        params.connection.endpoints[0].repaint();
    +                        params.connection.endpoints[1].repaint();
    +                        params.connector.setEdited(true);
    +                    }
    +                };
    +                        
    +            // bind to mousedown and mouseup, for editing
    +            params.connector.bind("mousedown", function(c, e) {
    +                var x = (e.pageX || e.page.x),
    +                    y = (e.pageY || e.page.y),
    +                    oe = jpcl.getElementObject(params.connection.getConnector().canvas),
    +                    o = jpcl.getOffset(oe),                    
    +                    minD = Infinity;
    +                
    +                params.connection.setHover(true);
    +                params.connector.setSuspendEvents(true);
    +                params.connection.endpoints[0].setSuspendEvents(true);                
    +                params.connection.endpoints[1].setSuspendEvents(true);                
    +                
    +                currentSegments = params.connector.getOriginalSegments();
    +                _collapseSegments();
    +                for (var i = 0; i < currentSegments.length; i++) {
    +                    var _s = findClosestPointOnPath(currentSegments[i], x - o.left, y - o.top, i);
    +                    if (_s.d < minD) {
    +                        selectedSegment = _s;
    +                        segmentCoords = [ _s.s[0], _s.s[1], _s.s[2], _s.s[3] ]; // copy the coords at mousedown
    +                        minD = _s.d;
    +                    }
    +                }
    +                
    +                downAt = [ x, y ];
    +                
    +                jpcl.bind(document, "mouseup", documentMouseUp);
    +                jpcl.bind(document, "mousemove", documentMouseMove);  
    +
    +                if (selectedSegment != null) {
    +                    params.connection.editStarted();
    +                }                              
    +            });
    +        }
    +    };
    +        
    +})();
    \ No newline at end of file
    diff --git a/src/jsPlumb-connectors-defaults.js b/src/jsPlumb-connectors-defaults.js
    new file mode 100644
    index 000000000..6e5369d6f
    --- /dev/null
    +++ b/src/jsPlumb-connectors-defaults.js
    @@ -0,0 +1,1437 @@
    +/*
    + * jsPlumb
    + * 
    + * Title:jsPlumb 1.4.0
    + * 
    + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
    + * elements, or VML.  
    + * 
    + * This file contains the default Connectors, Endpoint and Overlay definitions.
    + *
    + * Copyright (c) 2010 - 2013 Simon Porritt (http://jsplumb.org)
    + * 
    + * http://jsplumb.org
    + * http://github.com/sporritt/jsplumb
    + * http://code.google.com/p/jsplumb
    + * 
    + * Dual licensed under the MIT and GPL2 licenses.
    + */
    +
    +;(function() {	
    +				
    +	/**
    +	 * 
    +	 * Helper class to consume unused mouse events by components that are DOM elements and
    +	 * are used by all of the different rendering modes.
    +	 * 
    +	 */
    +	jsPlumb.DOMElementComponent = function(params) {
    +		jsPlumb.jsPlumbUIComponent.apply(this, arguments);
    +		// when render mode is canvas, these functions may be called by the canvas mouse handler.  
    +		// this component is safe to pipe this stuff to /dev/null.
    +		this.mousemove = 
    +		this.dblclick  = 
    +		this.click = 
    +		this.mousedown = 
    +		this.mouseup = function(e) { };					
    +	};
    +	
    +	jsPlumb.Segments = {
    +        	
    +        /*
    +         * Class: AbstractSegment
    +         * A Connector is made up of 1..N Segments, each of which has a Type, such as 'Straight', 'Arc',
    +         * 'Bezier'. This is new from 1.4.0, and gives us a lot more flexibility when drawing connections: things such
    +         * as rounded corners for flowchart connectors, for example, or a straight line stub for Bezier connections, are
    +         * much easier to do now.
    +         *
    +         * A Segment is responsible for providing coordinates for painting it, and also must be able to report its length.
    +         * 
    +         */ 
    +        AbstractSegment : function(params) { 
    +            this.params = params;
    +            
    +            /**
    +            * Function: findClosestPointOnPath
    +            * Finds the closest point on this segment to the given [x, y], 
    +            * returning both the x and y of the point plus its distance from
    +            * the supplied point, and its location along the length of the
    +            * path inscribed by the segment.  This implementation returns
    +            * Infinity for distance and null values for everything else;
    +            * subclasses are expected to override.
    +            */
    +            this.findClosestPointOnPath = function(x, y) {
    +                return {
    +                    d:Infinity,
    +                    x:null,
    +                    y:null,
    +                    l:null
    +                };
    +            };
    +
    +            this.getBounds = function() {
    +                return {
    +                    minX:Math.min(params.x1, params.x2),
    +                    minY:Math.min(params.y1, params.y2),
    +                    maxX:Math.max(params.x1, params.x2),
    +                    maxY:Math.max(params.y1, params.y2)
    +                };
    +            };
    +        },
    +        Straight : function(params) {
    +            var self = this,
    +                _super = jsPlumb.Segments.AbstractSegment.apply(this, arguments),
    +                length, m, m2, x1, x2, y1, y2,
    +                _recalc = function() {
    +                    length = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
    +                    m = jsPlumbUtil.gradient({x:x1, y:y1}, {x:x2, y:y2});
    +                    m2 = -1 / m;                
    +                };
    +                
    +            this.type = "Straight";
    +            
    +            self.getLength = function() { return length; };
    +            self.getGradient = function() { return m; };
    +                
    +            this.getCoordinates = function() {
    +                return { x1:x1,y1:y1,x2:x2,y2:y2 };
    +            };
    +            this.setCoordinates = function(coords) {
    +                x1 = coords.x1; y1 = coords.y1; x2 = coords.x2; y2 = coords.y2;
    +                _recalc();
    +            };
    +            this.setCoordinates({x1:params.x1, y1:params.y1, x2:params.x2, y2:params.y2});
    +
    +            this.getBounds = function() {
    +                return {
    +                    minX:Math.min(x1, x2),
    +                    minY:Math.min(y1, y2),
    +                    maxX:Math.max(x1, x2),
    +                    maxY:Math.max(y1, y2)
    +                };
    +            };
    +            
    +            /**
    +             * returns the point on the segment's path that is 'location' along the length of the path, where 'location' is a decimal from
    +             * 0 to 1 inclusive. for the straight line segment this is simple maths.
    +             */
    +             this.pointOnPath = function(location, absolute) {
    +                if (location == 0 && !absolute)
    +                    return { x:x1, y:y1 };
    +                else if (location == 1 && !absolute)
    +                    return { x:x2, y:y2 };
    +                else {
    +                    var l = absolute ? location > 0 ? location : length + location : location * length;
    +                    return jsPlumbUtil.pointOnLine({x:x1, y:y1}, {x:x2, y:y2}, l);
    +                }
    +            };
    +            
    +            /**
    +             * returns the gradient of the segment at the given point - which for us is constant.
    +             */
    +            this.gradientAtPoint = function(_) {
    +                return m;
    +            };
    +            
    +            /**
    +             * returns the point on the segment's path that is 'distance' along the length of the path from 'location', where 
    +             * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels.
    +             * this hands off to jsPlumbUtil to do the maths, supplying two points and the distance.
    +             */            
    +            this.pointAlongPathFrom = function(location, distance, absolute) {            
    +                var p = self.pointOnPath(location, absolute),
    +                    farAwayPoint = location == 1 ? {
    +                        x:x1 + ((x2 - x1) * 10),
    +                        y:y1 + ((y1 - y2) * 10)
    +                    } : distance <= 0 ? {x:x1, y:y1} : {x:x2, y:y2 };
    +    
    +                if (distance <= 0 && Math.abs(distance) > 1) distance *= -1;
    +    
    +                return jsPlumbUtil.pointOnLine(p, farAwayPoint, distance);
    +            };
    +            
    +            /**
    +                Function: findClosestPointOnPath
    +                Finds the closest point on this segment to [x,y]. See
    +                notes on this method in AbstractSegment.
    +            */
    +            this.findClosestPointOnPath = function(x, y) {
    +                if (m == 0) {
    +                    return {
    +                        x:x,
    +                        y:y1,
    +                        d:Math.abs(y - y1)
    +                    };
    +                }
    +                else if (m == Infinity || m == -Infinity) {
    +                    return {
    +                        x:x1,
    +                        y:y,
    +                        d:Math.abs(x - 1)
    +                    };
    +                }
    +                else {
    +                    // closest point lies on normal from given point to this line.  
    +                    var b = y1 - (m * x1),
    +                        b2 = y - (m2 * x),                    
    +                    // y1 = m.x1 + b and y1 = m2.x1 + b2
    +                    // so m.x1 + b = m2.x1 + b2
    +                    // x1(m - m2) = b2 - b
    +                    // x1 = (b2 - b) / (m - m2)
    +                        _x1 = (b2 -b) / (m - m2),
    +                        _y1 = (m * _x1) + b,
    +                        d = jsPlumbUtil.lineLength([ x, y ], [ _x1, _y1 ]),
    +                        fractionInSegment = jsPlumbUtil.lineLength([ _x1, _y1 ], [ x1, y1 ]);
    +                    
    +                    return { d:d, x:_x1, y:_y1, l:fractionInSegment / length};            
    +                }
    +            };
    +        },
    +	
    +        /*
    +            Arc Segment. You need to supply:
    +    
    +            r   -   radius
    +            cx  -   center x for the arc
    +            cy  -   center y for the arc
    +            ac  -   whether the arc is anticlockwise or not. default is clockwise.
    +    
    +            and then either:
    +    
    +            startAngle  -   startAngle for the arc.
    +            endAngle    -   endAngle for the arc.
    +    
    +            or:
    +    
    +            x1          -   x for start point
    +            y1          -   y for start point
    +            x2          -   x for end point
    +            y2          -   y for end point
    +    
    +        */
    +        Arc : function(params) {
    +            var self = this,
    +                _super = jsPlumb.Segments.AbstractSegment.apply(this, arguments),
    +                _calcAngle = function(_x, _y) {
    +                    return jsPlumbUtil.theta([params.cx, params.cy], [_x, _y]);    
    +                },
    +                _calcAngleForLocation = function(location) {
    +                    if (self.anticlockwise) {
    +                        var sa = self.startAngle < self.endAngle ? self.startAngle + TWO_PI : self.startAngle,
    +                            s = Math.abs(sa - self.endAngle);
    +                        return sa - (s * location);                    
    +                    }
    +                    else {
    +                        var ea = self.endAngle < self.startAngle ? self.endAngle + TWO_PI : self.endAngle,
    +                            s = Math.abs (ea - self.startAngle);
    +                    
    +                        return self.startAngle + (s * location);
    +                    }
    +                },
    +                TWO_PI = 2 * Math.PI;
    +            
    +            this.radius = params.r;
    +            this.anticlockwise = params.ac;			
    +            this.type = "Arc";
    +                
    +            if (params.startAngle && params.endAngle) {
    +                this.startAngle = params.startAngle;
    +                this.endAngle = params.endAngle;            
    +                this.x1 = params.cx + (self.radius * Math.cos(params.startAngle));     
    +                this.y1 = params.cy + (self.radius * Math.sin(params.startAngle));            
    +                this.x2 = params.cx + (self.radius * Math.cos(params.endAngle));     
    +                this.y2 = params.cy + (self.radius * Math.sin(params.endAngle));                        
    +            }
    +            else {
    +                this.startAngle = _calcAngle(params.x1, params.y1);
    +                this.endAngle = _calcAngle(params.x2, params.y2);            
    +                this.x1 = params.x1;
    +                this.y1 = params.y1;
    +                this.x2 = params.x2;
    +                this.y2 = params.y2;            
    +            }
    +            
    +            if (this.endAngle < 0) this.endAngle += TWO_PI;
    +            if (this.startAngle < 0) this.startAngle += TWO_PI;   
    +
    +            // segment is used by vml     
    +            this.segment = jsPlumbUtil.segment([this.x1, this.y1], [this.x2, this.y2]);
    +            
    +            // we now have startAngle and endAngle as positive numbers, meaning the
    +            // absolute difference (|d|) between them is the sweep (s) of this arc, unless the
    +            // arc is 'anticlockwise' in which case 's' is given by 2PI - |d|.
    +            
    +            var ea = self.endAngle < self.startAngle ? self.endAngle + TWO_PI : self.endAngle;
    +            self.sweep = Math.abs (ea - self.startAngle);
    +            if (self.anticlockwise) self.sweep = TWO_PI - self.sweep;
    +            var circumference = 2 * Math.PI * self.radius,
    +                frac = self.sweep / TWO_PI,
    +                length = circumference * frac;
    +            
    +            this.getLength = function() {
    +                return length;
    +            };
    +
    +            this.getBounds = function() {
    +                return {
    +                    minX:params.cx - params.r,
    +                    maxX:params.cx + params.r,
    +                    minY:params.cy - params.r,
    +                    maxY:params.cy + params.r
    +                }
    +            };
    +            
    +            var VERY_SMALL_VALUE = 0.0000000001,
    +                gentleRound = function(n) {
    +                    var f = Math.floor(n), r = Math.ceil(n);
    +                    if (n - f < VERY_SMALL_VALUE) 
    +                        return f;    
    +                    else if (r - n < VERY_SMALL_VALUE)
    +                        return r;
    +                    return n;
    +                };
    +            
    +            /**
    +             * returns the point on the segment's path that is 'location' along the length of the path, where 'location' is a decimal from
    +             * 0 to 1 inclusive. 
    +             */
    +            this.pointOnPath = function(location, absolute) {            
    +                
    +                if (location == 0) {
    +                    return { x:self.x1, y:self.y1, theta:self.startAngle };    
    +                }
    +                else if (location == 1) {
    +                    return { x:self.x2, y:self.y2, theta:self.endAngle };                    
    +                }
    +                
    +                if (absolute) {
    +                    location = location / length;
    +                }
    +    
    +                var angle = _calcAngleForLocation(location),
    +                    _x = params.cx + (params.r * Math.cos(angle)),
    +                    _y  = params.cy + (params.r * Math.sin(angle));					
    +    
    +                return { x:gentleRound(_x), y:gentleRound(_y), theta:angle };
    +            };
    +            
    +            /**
    +             * returns the gradient of the segment at the given point.
    +             */
    +            this.gradientAtPoint = function(location, absolute) {
    +                var p = self.pointOnPath(location, absolute);
    +                var m = jsPlumbUtil.normal( [ params.cx, params.cy ], [p.x, p.y ] );
    +                if (!self.anticlockwise && (m == Infinity || m == -Infinity)) m *= -1;
    +                return m;
    +            };	              
    +                    
    +            this.pointAlongPathFrom = function(location, distance, absolute) {
    +                var p = self.pointOnPath(location, absolute),
    +                    arcSpan = distance / circumference * 2 * Math.PI,
    +                    dir = self.anticlockwise ? -1 : 1,
    +                    startAngle = p.theta + (dir * arcSpan),				
    +                    startX = params.cx + (self.radius * Math.cos(startAngle)),
    +                    startY = params.cy + (self.radius * Math.sin(startAngle));	
    +    
    +                return {x:startX, y:startY};
    +            };	            
    +        },
    +	
    +        Bezier : function(params) {
    +            var self = this,
    +                _super = jsPlumb.Segments.AbstractSegment.apply(this, arguments),
    +                curve = [	
    +                    { x:params.x1, y:params.y1},
    +                    { x:params.cp1x, y:params.cp1y },
    +                    { x:params.cp2x, y:params.cp2y },
    +                    { x:params.x2, y:params.y2 }
    +                ],
    +                // although this is not a strictly rigorous determination of bounds
    +                // of a bezier curve, it works for the types of curves that this segment
    +                // type produces.
    +                bounds = {
    +                    minX:Math.min(params.x1, params.x2, params.cp1x, params.cp2x),
    +                    minY:Math.min(params.y1, params.y2, params.cp1y, params.cp2y),
    +                    maxX:Math.max(params.x1, params.x2, params.cp1x, params.cp2x),
    +                    maxY:Math.max(params.y1, params.y2, params.cp1y, params.cp2y)
    +                };
    +                
    +            this.type = "Bezier";            
    +            
    +            var _translateLocation = function(_curve, location, absolute) {
    +                if (absolute)
    +                    location = jsBezier.locationAlongCurveFrom(_curve, location > 0 ? 0 : 1, location);
    +    
    +                return location;
    +            };		
    +            
    +            /**
    +             * returns the point on the segment's path that is 'location' along the length of the path, where 'location' is a decimal from
    +             * 0 to 1 inclusive. 
    +             */
    +            this.pointOnPath = function(location, absolute) {
    +                location = _translateLocation(curve, location, absolute);                
    +                return jsBezier.pointOnCurve(curve, location);
    +            };
    +            
    +            /**
    +             * returns the gradient of the segment at the given point.
    +             */
    +            this.gradientAtPoint = function(location, absolute) {
    +                location = _translateLocation(curve, location, absolute);
    +                return jsBezier.gradientAtPoint(curve, location);        	
    +            };	              
    +            
    +            this.pointAlongPathFrom = function(location, distance, absolute) {
    +                location = _translateLocation(curve, location, absolute);
    +                return jsBezier.pointAlongCurveFrom(curve, location, distance);
    +            };
    +            
    +            this.getLength = function() {
    +                return jsBezier.getLength(curve);				
    +            };
    +
    +            this.getBounds = function() {
    +                return bounds;
    +            };
    +        }
    +    };
    +
    +    /*
    +        Class: AbstractComponent
    +        Superclass for AbstractConnector and AbstractEndpoint.
    +    */
    +    var AbstractComponent = function() {
    +        var self = this;
    +        self.resetBounds = function() {
    +            self.bounds = { minX:Infinity, minY:Infinity, maxX:-Infinity, maxY:-Infinity };
    +        };
    +        self.resetBounds();        
    +    };
    +	
    +	/*
    +	 * Class: AbstractConnector
    +	 * Superclass for all Connectors; here is where Segments are managed.  This is exposed on jsPlumb just so it
    +	 * can be accessed from other files. You should not try to instantiate one of these directly.
    +	 *
    +	 * When this class is asked for a pointOnPath, or gradient etc, it must first figure out which segment to dispatch
    +	 * that request to. This is done by keeping track of the total connector length as segments are added, and also
    +	 * their cumulative ratios to the total length.  Then when the right segment is found it is a simple case of dispatching
    +	 * the request to it (and adjusting 'location' so that it is relative to the beginning of that segment.)
    +	 */ 
    +	jsPlumb.Connectors.AbstractConnector = function(params) {
    +		
    +        AbstractComponent.apply(this, arguments);
    +
    +		var self = this,
    +            segments = [],
    +            editing = false,
    +			totalLength = 0,
    +			segmentProportions = [],
    +			segmentProportionalLengths = [],        
    +            stub = params.stub || 0, 
    +            sourceStub = jsPlumbUtil.isArray(stub) ? stub[0] : stub,
    +            targetStub = jsPlumbUtil.isArray(stub) ? stub[1] : stub,
    +            gap = params.gap || 0,
    +            sourceGap = jsPlumbUtil.isArray(gap) ? gap[0] : gap,
    +            targetGap = jsPlumbUtil.isArray(gap) ? gap[1] : gap,
    +            userProvidedSegments = null,
    +            edited = false,
    +            paintInfo = null;            
    +        
    +        // subclasses should override.
    +        this.isEditable = function() { return false; };                
    +        
    +        this.setEdited = function(ed) {
    +            edited = ed;
    +        };
    +
    +        // to be overridden by subclasses.
    +        this.getPath = function() { };
    +        this.setPath = function(path) { };
    +        
    +        /**
    +        * Function: findSegmentForPoint
    +        * Returns the segment that is closest to the given [x,y],
    +        * null if nothing found.  This function returns a JS 
    +        * object with:
    +        *
    +        *   d   -   distance from segment
    +        *   l   -   proportional location in segment
    +        *   x   -   x point on the segment
    +        *   y   -   y point on the segment
    +        *   s   -   the segment itself.
    +        */ 
    +        this.findSegmentForPoint = function(x, y) {
    +            var out = { d:Infinity, s:null, x:null, y:null, l:null };
    +            for (var i = 0; i < segments.length; i++) {
    +                var _s = segments[i].findClosestPointOnPath(x, y);
    +                if (_s.d < out.d) {
    +                    out.d = _s.d; 
    +                    out.l = _s.l; 
    +                    out.x = _s.x;
    +                    out.y = _s.y; 
    +                    out.s = segments[i];
    +                }
    +            }
    +            
    +            return out;                
    +        };
    +			
    +		var _updateSegmentProportions = function() {
    +                var curLoc = 0;
    +                for (var i = 0; i < segments.length; i++) {
    +                    var sl = segments[i].getLength();
    +                    segmentProportionalLengths[i] = sl / totalLength;
    +                    segmentProportions[i] = [curLoc, (curLoc += (sl / totalLength)) ];
    +                }
    +            },
    +		
    +            /**
    +             * returns [segment, proportion of travel in segment, segment index] for the segment 
    +             * that contains the point which is 'location' distance along the entire path, where 
    +             * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths 
    +             * are made up of a list of segments, each of which contributes some fraction to
    +             * the total length. 
    +             * From 1.3.10 this also supports the 'absolute' property, which lets us specify a location
    +             * as the absolute distance in pixels, rather than a proportion of the total path. 
    +             */
    +            _findSegmentForLocation = function(location, absolute) {
    +                if (absolute) {
    +                    location = location > 0 ? location / totalLength : (totalLength + location) / totalLength;
    +                }
    +    
    +                var idx = segmentProportions.length - 1, inSegmentProportion = 1;
    +                //if (location < 1) {
    +                    for (var i = 0; i < segmentProportions.length; i++) {
    +                        if (segmentProportions[i][1] >= location) {
    +                            idx = i;
    +                            // todo is this correct for all connector path types?
    +                            inSegmentProportion = location == 1 ? 1 : location == 0 ? 0 : (location - segmentProportions[i][0]) / segmentProportionalLengths[i];                    
    +                            break;
    +                        }
    +                    }
    +                //}
    +                return { segment:segments[idx], proportion:inSegmentProportion, index:idx };
    +            },		
    +            _addSegment = function(type, params) {
    +                var s = new jsPlumb.Segments[type](params);
    +                segments.push(s);
    +                totalLength += s.getLength();	
    +                self.updateBounds(s);	                
    +            },					
    +            _clearSegments = function() {
    +                totalLength = 0;
    +                segments.splice(0, segments.length);
    +                segmentProportions.splice(0, segmentProportions.length);
    +                segmentProportionalLengths.splice(0, segmentProportionalLengths.length);
    +            };
    +        
    +        this.setSegments = function(_segs) {
    +            userProvidedSegments = [];
    +            totalLength = 0;
    +            for (var i = 0; i < _segs.length; i++) {      
    +                userProvidedSegments.push(_segs[i]);
    +                totalLength += _segs[i].getLength();			            
    +            }            
    +        };  
    +        
    +        var _prepareCompute = function(params) {
    +            self.lineWidth = params.lineWidth;
    +            var segment = jsPlumbUtil.segment(params.sourcePos, params.targetPos),
    +                swapX = params.targetPos[0] < params.sourcePos[0],
    +                swapY = params.targetPos[1] < params.sourcePos[1],
    +                lw = params.lineWidth || 1,       
    +                so = params.sourceEndpoint.anchor.orientation || params.sourceEndpoint.anchor.getOrientation(params.sourceEndpoint), 
    +                to = params.targetEndpoint.anchor.orientation || params.targetEndpoint.anchor.getOrientation(params.targetEndpoint),
    +                x = swapX ? params.targetPos[0] : params.sourcePos[0], 
    +                y = swapY ? params.targetPos[1] : params.sourcePos[1],
    +                w = Math.abs(params.targetPos[0] - params.sourcePos[0]),
    +                h = Math.abs(params.targetPos[1] - params.sourcePos[1]);
    +            
    +            // if either anchor does not have an orientation set, we derive one from their relative
    +            // positions.  we fix the axis to be the one in which the two elements are further apart, and
    +            // point each anchor at the other element.  this is also used when dragging a new connection.
    +            if (so[0] == 0 && so[1] == 0 || to[0] == 0 && to[1] == 0) {
    +                var index = w > h ? 0 : 1, oIndex = [1,0][index];
    +                so = []; to = [];
    +                so[index] = params.sourcePos[index] > params.targetPos[index] ? -1 : 1;
    +                to[index] = params.sourcePos[index] > params.targetPos[index] ? 1 : -1;
    +                so[oIndex] = 0; to[oIndex] = 0;
    +            }                    
    +            
    +            var sx = swapX ? w + (sourceGap * so[0])  : sourceGap * so[0], 
    +                sy = swapY ? h + (sourceGap * so[1])  : sourceGap * so[1], 
    +                tx = swapX ? targetGap * to[0] : w + (targetGap * to[0]),
    +                ty = swapY ? targetGap * to[1] : h + (targetGap * to[1]),
    +                oProduct = ((so[0] * to[0]) + (so[1] * to[1]));        
    +            
    +            var result = {
    +                sx:sx, sy:sy, tx:tx, ty:ty, lw:lw, 
    +                xSpan:Math.abs(tx - sx),
    +                ySpan:Math.abs(ty - sy),                
    +                mx:(sx + tx) / 2,
    +                my:(sy + ty) / 2,                
    +                so:so, to:to, x:x, y:y, w:w, h:h,
    +                segment : segment,
    +                startStubX : sx + (so[0] * sourceStub), 
    +                startStubY : sy + (so[1] * sourceStub),
    +                endStubX : tx + (to[0] * targetStub), 
    +                endStubY : ty + (to[1] * targetStub),
    +                isXGreaterThanStubTimes2 : Math.abs(sx - tx) > (sourceStub + targetStub),
    +                isYGreaterThanStubTimes2 : Math.abs(sy - ty) > (sourceStub + targetStub),
    +                opposite:oProduct == -1,
    +                perpendicular:oProduct == 0,
    +                orthogonal:oProduct == 1,
    +                sourceAxis : so[0] == 0 ? "y" : "x",
    +                points:[x, y, w, h, sx, sy, tx, ty ]
    +            };
    +            result.anchorOrientation = result.opposite ? "opposite" : result.orthogonal ? "orthogonal" : "perpendicular";
    +            return result;
    +        };
    +		
    +		this.getSegments = function() { return segments; };
    +
    +        self.updateBounds = function(segment) {
    +            var segBounds = segment.getBounds();
    +            self.bounds.minX = Math.min(self.bounds.minX, segBounds.minX);
    +            self.bounds.maxX = Math.max(self.bounds.maxX, segBounds.maxX);
    +            self.bounds.minY = Math.min(self.bounds.minY, segBounds.minY);
    +            self.bounds.maxY = Math.max(self.bounds.maxY, segBounds.maxY);              
    +        };
    +        
    +        var dumpSegmentsToConsole = function() {
    +            console.log("SEGMENTS:");
    +            for (var i = 0; i < segments.length; i++) {
    +                console.log(segments[i].type, segments[i].getLength(), segmentProportions[i]);
    +            }
    +        };
    +		
    +		this.pointOnPath = function(location, absolute) {
    +            //console.log("point on path", location);
    +			var seg = _findSegmentForLocation(location, absolute);		
    +            //console.log("point on path", location, seg);	
    +			return seg.segment.pointOnPath(seg.proportion, absolute);
    +		};
    +		
    +		this.gradientAtPoint = function(location) {
    +			var seg = _findSegmentForLocation(location, absolute);			
    +			return seg.segment.gradientAtPoint(seg.proportion, absolute);
    +		};
    +		
    +		this.pointAlongPathFrom = function(location, distance, absolute) {
    +			var seg = _findSegmentForLocation(location, absolute);
    +			// TODO what happens if this crosses to the next segment?
    +			return seg.segment.pointAlongPathFrom(seg.proportion, distance, absolute);
    +		};
    +		
    +		this.compute = function(params)  {
    +            if (!edited)
    +                paintInfo = _prepareCompute(params);
    +            
    +            _clearSegments();
    +            this._compute(paintInfo, params);
    +            self.x = paintInfo.points[0];
    +            self.y = paintInfo.points[1];
    +            self.w = paintInfo.points[2];
    +            self.h = paintInfo.points[3];               
    +            self.segment = paintInfo.segment;         
    +            _updateSegmentProportions();            
    +		};
    +		
    +		return {
    +			addSegment:_addSegment,
    +            prepareCompute:_prepareCompute,
    +            sourceStub:sourceStub,
    +            targetStub:targetStub,
    +            maxStub:Math.max(sourceStub, targetStub),            
    +            sourceGap:sourceGap,
    +            targetGap:targetGap,
    +            maxGap:Math.max(sourceGap, targetGap)
    +		};		
    +	};
    +	
    +    /**
    +     * Class: Connectors.Straight
    +     * The Straight connector draws a simple straight line between the two anchor points.  It does not have any constructor parameters.
    +     */
    +    jsPlumb.Connectors.Straight = function() {
    +    	this.type = "Straight";
    +		var _super =  jsPlumb.Connectors.AbstractConnector.apply(this, arguments);		
    +
    +        this._compute = function(paintInfo, _) {                        
    +            _super.addSegment("Straight", {x1:paintInfo.sx, y1:paintInfo.sy, x2:paintInfo.startStubX, y2:paintInfo.startStubY});                                                
    +            _super.addSegment("Straight", {x1:paintInfo.startStubX, y1:paintInfo.startStubY, x2:paintInfo.endStubX, y2:paintInfo.endStubY});                        
    +            _super.addSegment("Straight", {x1:paintInfo.endStubX, y1:paintInfo.endStubY, x2:paintInfo.tx, y2:paintInfo.ty});                                    
    +        };                    
    +    };
    +                    
    +    /**
    +     * Class:Connectors.Bezier
    +     * This Connector draws a Bezier curve with two control points.  You can provide a 'curviness' value which gets applied to jsPlumb's
    +     * internal voodoo machine and ends up generating locations for the two control points.  See the constructor documentation below.
    +     */
    +    /**
    +     * Function:Constructor
    +     * 
    +     * Parameters:
    +     * 	curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not 
    +     * actually touch the given point, but it has the tendency to lean towards it.  The larger this value, the greater the curve is pulled from a straight line.
    +     * Optional; defaults to 150.
    +     * stub - optional value for a distance to travel from the connector's endpoint before beginning the Bezier curve. defaults to 0.
    +     * 
    +     */
    +    jsPlumb.Connectors.Bezier = function(params) {
    +        params = params || {};
    +
    +    	var self = this,
    +			_super =  jsPlumb.Connectors.AbstractConnector.apply(this, arguments),
    +            stub = params.stub || 50,
    +            majorAnchor = params.curviness || 150,
    +            minorAnchor = 10;            
    +
    +        this.type = "Bezier";	
    +        this.getCurviness = function() { return majorAnchor; };	
    +        
    +        this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceEndpoint, targetEndpoint) {
    +        	// determine if the two anchors are perpendicular to each other in their orientation.  we swap the control 
    +        	// points around if so (code could be tightened up)
    +        	var soo = sourceEndpoint.anchor.getOrientation(sourceEndpoint), 
    +        		too = targetEndpoint.anchor.getOrientation(targetEndpoint),
    +        		perpendicular = soo[0] != too[0] || soo[1] == too[1],
    +            	p = [];                
    +            	
    +            if (!perpendicular) {
    +                if (soo[0] == 0) // X
    +                    p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + minorAnchor : point[0] - minorAnchor);
    +                else p.push(point[0] - (majorAnchor * soo[0]));
    +                                 
    +                if (soo[1] == 0) // Y
    +                	p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + minorAnchor : point[1] - minorAnchor);
    +                else p.push(point[1] + (majorAnchor * too[1]));
    +            }
    +             else {
    +                if (too[0] == 0) // X
    +                	p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + minorAnchor : point[0] - minorAnchor);
    +                else p.push(point[0] + (majorAnchor * too[0]));
    +                
    +                if (too[1] == 0) // Y
    +                	p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + minorAnchor : point[1] - minorAnchor);
    +                else p.push(point[1] + (majorAnchor * soo[1]));
    +             }
    +
    +            return p;                
    +        };        
    +
    +        this._compute = function(paintInfo, p) {                                
    +			var sp = p.sourcePos,
    +				tp = p.targetPos,				
    +                _w = Math.abs(sp[0] - tp[0]),
    +                _h = Math.abs(sp[1] - tp[1]),            
    +                _sx = sp[0] < tp[0] ? _w : 0,
    +                _sy = sp[1] < tp[1] ? _h : 0,
    +                _tx = sp[0] < tp[0] ? 0 : _w,
    +                _ty = sp[1] < tp[1] ? 0 : _h,
    +                _CP = self._findControlPoint([_sx, _sy], sp, tp, p.sourceEndpoint, p.targetEndpoint),
    +                _CP2 = self._findControlPoint([_tx, _ty], tp, sp, p.targetEndpoint, p.sourceEndpoint);
    +
    +			_super.addSegment("Bezier", {
    +				x1:_sx, y1:_sy, x2:_tx, y2:_ty,
    +				cp1x:_CP[0], cp1y:_CP[1], cp2x:_CP2[0], cp2y:_CP2[1]
    +			});                    
    +        };               
    +    };        
    +    
    + // ********************************* END OF CONNECTOR TYPES *******************************************************************
    +    
    + // ********************************* ENDPOINT TYPES *******************************************************************
    +    
    +    jsPlumb.Endpoints.AbstractEndpoint = function(params) {
    +        AbstractComponent.apply(this, arguments);
    +        var self = this;    
    +        this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {    
    +            var out = self._compute.apply(self, arguments);
    +            self.x = out[0];
    +            self.y = out[1];
    +            self.w = out[2];
    +            self.h = out[3];
    +            self.bounds.minX = self.x;
    +            self.bounds.minY = self.y;
    +            self.bounds.maxX = self.x + self.w;
    +            self.bounds.maxY = self.y + self.h;
    +            return out;
    +        };
    +        return {
    +            compute:self.compute,
    +            cssClass:params.cssClass
    +        };
    +    };
    +    
    +    /**
    +     * Class: Endpoints.Dot
    +     * A round endpoint, with default radius 10 pixels.
    +     */    	
    +    	
    +	/**
    +	 * Function: Constructor
    +	 * 
    +	 * Parameters:
    +	 * 
    +	 * 	radius	-	radius of the endpoint.  defaults to 10 pixels.
    +	 */
    +	jsPlumb.Endpoints.Dot = function(params) {        
    +		this.type = "Dot";
    +		var self = this,
    +            _super = jsPlumb.Endpoints.AbstractEndpoint.apply(this, arguments);
    +		params = params || {};				
    +		this.radius = params.radius || 10;
    +		this.defaultOffset = 0.5 * this.radius;
    +		this.defaultInnerRadius = this.radius / 3;			
    +		
    +		this._compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
    +			self.radius = endpointStyle.radius || self.radius;
    +			var	x = anchorPoint[0] - self.radius,
    +				y = anchorPoint[1] - self.radius,
    +                w = self.radius * 2,
    +                h = self.radius * 2;
    +
    +            if (endpointStyle.strokeStyle) {
    +                var lw = endpointStyle.lineWidth || 1;
    +                x -= lw;
    +                y -= lw;
    +                w += (lw * 2);
    +                h += (lw * 2);
    +            }
    +			return [ x, y, w, h, self.radius ];
    +		};
    +	};
    +	
    +	/**
    +	 * Class: Endpoints.Rectangle
    +	 * A Rectangular Endpoint, with default size 20x20.
    +	 */
    +	/**
    +	 * Function: Constructor
    +	 * 
    +	 * Parameters:
    +	 * 
    +	 * 	width	- width of the endpoint. defaults to 20 pixels.
    +	 * 	height	- height of the endpoint. defaults to 20 pixels.	
    +	 */
    +	jsPlumb.Endpoints.Rectangle = function(params) {
    +		this.type = "Rectangle";
    +		var self = this,
    +            _super = jsPlumb.Endpoints.AbstractEndpoint.apply(this, arguments);
    +		params = params || {};
    +		this.width = params.width || 20;
    +		this.height = params.height || 20;
    +		
    +		this._compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
    +			var width = endpointStyle.width || self.width,
    +				height = endpointStyle.height || self.height,
    +				x = anchorPoint[0] - (width/2),
    +				y = anchorPoint[1] - (height/2);
    +                
    +			return [ x, y, width, height];
    +		};
    +	};
    +	
    +
    +    var DOMElementEndpoint = function(params) {
    +        jsPlumb.DOMElementComponent.apply(this, arguments);
    +        var self = this;
    +
    +        var displayElements = [  ];
    +        this.getDisplayElements = function() { 
    +            return displayElements; 
    +        };
    +        
    +        this.appendDisplayElement = function(el) {
    +            displayElements.push(el);
    +        };            
    +    };
    +	/**
    +	 * Class: Endpoints.Image
    +	 * Draws an image as the Endpoint.
    +	 */
    +	/**
    +	 * Function: Constructor
    +	 * 
    +	 * Parameters:
    +	 * 
    +	 * 	src	-	location of the image to use.
    +	 */
    +	jsPlumb.Endpoints.Image = function(params) {
    +				
    +		this.type = "Image";
    +		DOMElementEndpoint.apply(this, arguments);
    +		
    +		var self = this,
    +            _super = jsPlumb.Endpoints.AbstractEndpoint.apply(this, arguments), 
    +			initialized = false,
    +			deleted = false,
    +			widthToUse = params.width,
    +			heightToUse = params.height,
    +            _onload = null,
    +            _endpoint = params.endpoint;
    +			
    +		this.img = new Image();
    +		self.ready = false;
    +
    +		this.img.onload = function() {
    +			self.ready = true;
    +			widthToUse = widthToUse || self.img.width;
    +			heightToUse = heightToUse || self.img.height;
    +            if (_onload) {
    +                _onload(self);
    +            }
    +		};
    +
    +        /*
    +            Function: setImage
    +            Sets the Image to use in this Endpoint.  
    +
    +            Parameters:
    +            img         -   may be a URL or an Image object
    +            onload      -   optional; a callback to execute once the image has loaded.
    +        */
    +        _endpoint.setImage = function(img, onload) {
    +            var s = img.constructor == String ? img : img.src;
    +            _onload = onload;
    +            self.img.src = img;
    +
    +            if (self.canvas != null)
    +                self.canvas.setAttribute("src", img);
    +        };
    +
    +        _endpoint.setImage(params.src || params.url, params.onload);
    +
    +		this._compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
    +			self.anchorPoint = anchorPoint;
    +			if (self.ready) return [anchorPoint[0] - widthToUse / 2, anchorPoint[1] - heightToUse / 2, 
    +									widthToUse, heightToUse];
    +			else return [0,0,0,0];
    +		};
    +		
    +		self.canvas = document.createElement("img"), initialized = false;
    +		self.canvas.style["margin"] = 0;
    +		self.canvas.style["padding"] = 0;
    +		self.canvas.style["outline"] = 0;
    +		self.canvas.style["position"] = "absolute";
    +		var clazz = params.cssClass ? " " + params.cssClass : "";
    +		self.canvas.className = jsPlumb.endpointClass + clazz;
    +		if (widthToUse) self.canvas.setAttribute("width", widthToUse);
    +		if (heightToUse) self.canvas.setAttribute("height", heightToUse);		
    +		jsPlumb.appendElement(self.canvas, params.parent);
    +		self.attachListeners(self.canvas, self);
    +		
    +		self.cleanup = function() {
    +			deleted = true;
    +		};
    +		
    +		var actuallyPaint = function(d, style, anchor) {
    +			if (!deleted) {
    +				if (!initialized) {
    +					self.canvas.setAttribute("src", self.img.src);
    +					self.appendDisplayElement(self.canvas);
    +					initialized = true;
    +				}
    +				var x = self.anchorPoint[0] - (widthToUse / 2),
    +					y = self.anchorPoint[1] - (heightToUse / 2);
    +				jsPlumb.sizeCanvas(self.canvas, x, y, widthToUse, heightToUse);
    +			}
    +		};
    +		
    +		this.paint = function(style, anchor) {
    +			if (self.ready) {
    +    			actuallyPaint(style, anchor);
    +			}
    +			else { 
    +				window.setTimeout(function() {    					
    +					self.paint(style, anchor);
    +				}, 200);
    +			}
    +		};				
    +	};
    +	
    +	/*
    +	 * Class: Endpoints.Blank
    +	 * An Endpoint that paints nothing (visible) on the screen.  Supports cssClass and hoverClass parameters like all Endpoints.
    +	 */
    +	jsPlumb.Endpoints.Blank = function(params) {
    +		var self = this,
    +            _super = jsPlumb.Endpoints.AbstractEndpoint.apply(this, arguments);
    +		this.type = "Blank";
    +		DOMElementEndpoint.apply(this, arguments);		
    +		this._compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
    +			return [anchorPoint[0], anchorPoint[1],10,0];
    +		};
    +		
    +		self.canvas = document.createElement("div");
    +		self.canvas.style.display = "block";
    +		self.canvas.style.width = "1px";
    +		self.canvas.style.height = "1px";
    +		self.canvas.style.background = "transparent";
    +		self.canvas.style.position = "absolute";
    +		self.canvas.className = self._jsPlumb.endpointClass;
    +		jsPlumb.appendElement(self.canvas, params.parent);
    +		
    +		this.paint = function(style, anchor) {
    +			jsPlumb.sizeCanvas(self.canvas, self.x, self.y, self.w, self.h);	
    +		};
    +	};
    +	
    +	/*
    +	 * Class: Endpoints.Triangle
    +	 * A triangular Endpoint.  
    +	 */
    +	/*
    +	 * Function: Constructor
    +	 * 
    +	 * Parameters:
    +	 * 
    +	 * 	width	-	width of the triangle's base.  defaults to 55 pixels.
    +	 * 	height	-	height of the triangle from base to apex.  defaults to 55 pixels.
    +	 */
    +	jsPlumb.Endpoints.Triangle = function(params) {        
    +		this.type = "Triangle";
    +        var self = this,
    +            _super = jsPlumb.Endpoints.AbstractEndpoint.apply(this, arguments);
    +		params = params || {  };
    +		params.width = params.width || 55;
    +		params.height = params.height || 55;
    +		this.width = params.width;
    +		this.height = params.height;
    +		this._compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
    +			var width = endpointStyle.width || self.width,
    +			height = endpointStyle.height || self.height,
    +			x = anchorPoint[0] - (width/2),
    +			y = anchorPoint[1] - (height/2);
    +			return [ x, y, width, height ];
    +		};
    +	};
    +// ********************************* END OF ENDPOINT TYPES *******************************************************************
    +	
    +
    +// ********************************* OVERLAY DEFINITIONS ***********************************************************************    
    +
    +	var AbstractOverlay = jsPlumb.Overlays.AbstractOverlay = function(params) {
    +		var visible = true, self = this;
    +        this.isAppendedAtTopLevel = true;
    +		this.component = params.component;
    +		this.loc = params.location == null ? 0.5 : params.location;
    +        this.endpointLoc = params.endpointLocation == null ? [ 0.5, 0.5] : params.endpointLocation;
    +		this.setVisible = function(val) { 
    +			visible = val;
    +			self.component.repaint();
    +		};
    +    	this.isVisible = function() { return visible; };
    +    	this.hide = function() { self.setVisible(false); };
    +    	this.show = function() { self.setVisible(true); };
    +    	
    +    	this.incrementLocation = function(amount) {
    +    		self.loc += amount;
    +    		self.component.repaint();
    +    	};
    +    	this.setLocation = function(l) {
    +    		self.loc = l;
    +    		self.component.repaint();
    +    	};
    +    	this.getLocation = function() {
    +    		return self.loc;
    +    	};
    +	};
    +	
    +	
    +	/*
    +	 * Class: Overlays.Arrow
    +	 * 
    +	 * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length
    +	 * of the arrow that lines from each tail point converge into.  The foldback point is defined using a decimal that indicates some fraction
    +	 * of the length of the arrow and has a default value of 0.623.  A foldback point value of 1 would mean that the arrow had a straight line
    +	 * across the tail.  
    +	 */
    +	/*
    +	 * Function: Constructor
    +	 * 
    +	 * Parameters:
    +	 * 
    +	 * 	length - distance in pixels from head to tail baseline. default 20.
    +	 * 	width - width in pixels of the tail baseline. default 20.
    +	 * 	fillStyle - style to use when filling the arrow.  defaults to "black".
    +	 * 	strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked.
    +	 * 	lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null.
    +	 * 	foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to.  defaults to 0.623.
    +	 * 	location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5.
    +	 * 	direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default.
    +	 */
    +	jsPlumb.Overlays.Arrow = function(params) {
    +		this.type = "Arrow";
    +		AbstractOverlay.apply(this, arguments);
    +        this.isAppendedAtTopLevel = false;
    +		params = params || {};
    +		var self = this, _ju = jsPlumbUtil;
    +		
    +    	this.length = params.length || 20;
    +    	this.width = params.width || 20;
    +    	this.id = params.id;
    +    	var direction = (params.direction || 1) < 0 ? -1 : 1,
    +    	    paintStyle = params.paintStyle || { lineWidth:1 },
    +    	    // how far along the arrow the lines folding back in come to. default is 62.3%.
    +    	    foldback = params.foldback || 0.623;
    +    	    	
    +    	this.computeMaxSize = function() { return self.width * 1.5; };    	
    +    	this.cleanup = function() { };  // nothing to clean up for Arrows    
    +    	this.draw = function(component, currentConnectionPaintStyle) {
    +
    +            var hxy, mid, txy, tail, cxy;
    +            if (component.pointAlongPathFrom) {
    +
    +                if (_ju.isString(self.loc) || self.loc > 1 || self.loc < 0) {                    
    +                    var l = parseInt(self.loc);
    +                    hxy = component.pointAlongPathFrom(l, direction * self.length / 2, true),
    +                    mid = component.pointOnPath(l, true),
    +                    txy = _ju.pointOnLine(hxy, mid, self.length);
    +                }
    +                else if (self.loc == 1) {                
    +					hxy = component.pointOnPath(self.loc);
    +					mid = component.pointAlongPathFrom(self.loc, -1);                    
    +					txy = _ju.pointOnLine(hxy, mid, self.length);
    +					
    +					if (direction == -1) {
    +						var _ = txy;
    +						txy = hxy;
    +						hxy = _;
    +					}
    +                }
    +                else if (self.loc == 0) {					                    
    +					txy = component.pointOnPath(self.loc);                    
    +					mid = component.pointAlongPathFrom(self.loc, 1);                    
    +					hxy = _ju.pointOnLine(txy, mid, self.length);                    
    +					if (direction == -1) {
    +						var _ = txy;
    +						txy = hxy;
    +						hxy = _;
    +					}
    +                }
    +                else {                    
    +    			    hxy = component.pointAlongPathFrom(self.loc, direction * self.length / 2),
    +                    mid = component.pointOnPath(self.loc),
    +                    txy = _ju.pointOnLine(hxy, mid, self.length);
    +                }
    +
    +                tail = _ju.perpendicularLineTo(hxy, txy, self.width);
    +                cxy = _ju.pointOnLine(hxy, txy, foldback * self.length);    			
    +    			
    +    			var d = { hxy:hxy, tail:tail, cxy:cxy },
    +    			    strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle,
    +    			    fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle,
    +    			    lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth,
    +                    info = {
    +                        component:component, 
    +                        d:d, 
    +                        lineWidth:lineWidth, 
    +                        strokeStyle:strokeStyle, 
    +                        fillStyle:fillStyle,
    +                        minX:Math.min(hxy.x, tail[0].x, tail[1].x),
    +                        maxX:Math.max(hxy.x, tail[0].x, tail[1].x),
    +                        minY:Math.min(hxy.y, tail[0].y, tail[1].y),
    +                        maxY:Math.max(hxy.y, tail[0].y, tail[1].y)
    +                    };    			
    +						    
    +                return info;
    +            }
    +            else return {component:component, minX:0,maxX:0,minY:0,maxY:0};
    +    	};
    +    };          
    +    
    +    /*
    +     * Class: Overlays.PlainArrow
    +	 * 
    +	 * A basic arrow.  This is in fact just one instance of the more generic case in which the tail folds back on itself to some
    +	 * point along the length of the arrow: in this case, that foldback point is the full length of the arrow.  so it just does
    +	 * a 'call' to Arrow with foldback set appropriately.       
    +	 */
    +    /*
    +     * Function: Constructor
    +     * See <Overlays.Arrow> for allowed parameters for this overlay.
    +     */
    +    jsPlumb.Overlays.PlainArrow = function(params) {
    +    	params = params || {};    	
    +    	var p = jsPlumb.extend(params, {foldback:1});
    +    	jsPlumb.Overlays.Arrow.call(this, p);
    +    	this.type = "PlainArrow";
    +    };
    +        
    +    /*
    +     * Class: Overlays.Diamond
    +     * 
    +	 * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just
    +	 * happens that in this case, that point is greater than the length of the the arrow.    
    +	 * 
    +	 *      this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the
    +	 *      center is actually 1/4 of the way along for this guy.  but we don't have any knowledge of pixels at this point, so we're kind of
    +	 *      stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value
    +	 *      would be -l/4 in this case - move along one quarter of the total length.
    +	 */
    +    /*
    +     * Function: Constructor
    +     * See <Overlays.Arrow> for allowed parameters for this overlay.
    +     */
    +    jsPlumb.Overlays.Diamond = function(params) {
    +    	params = params || {};    	
    +    	var l = params.length || 40,
    +    	    p = jsPlumb.extend(params, {length:l/2, foldback:2});
    +    	jsPlumb.Overlays.Arrow.call(this, p);
    +    	this.type = "Diamond";
    +    };
    +    
    +	
    +	// abstract superclass for overlays that add an element to the DOM.
    +    var AbstractDOMOverlay = function(params) {
    +		jsPlumb.DOMElementComponent.apply(this, arguments);
    +    	AbstractOverlay.apply(this, arguments);
    +		
    +		var self = this, initialised = false, jpcl = jsPlumb.CurrentLibrary;
    +		params = params || {};
    +		this.id = params.id;
    +		var div;
    +		
    +		var makeDiv = function() {
    +			div = params.create(params.component);
    +			div = jpcl.getDOMElement(div);
    +			div.style["position"] 	= 	"absolute";    	
    +			var clazz = params["_jsPlumb"].overlayClass + " " + 
    +				(self.cssClass ? self.cssClass : 
    +				params.cssClass ? params.cssClass : "");    	
    +			div.className =	clazz;
    +			params["_jsPlumb"].appendElement(div, params.component.parent);
    +			params["_jsPlumb"].getId(div);		
    +	    	self.attachListeners(div, self);
    +	    	self.canvas = div;
    +		};
    +		
    +		this.getElement = function() {
    +			if (div == null) {
    +				makeDiv();
    +			}
    +    		return div;
    +    	};
    +		
    +		this.getDimensions = function() {
    +    		return jpcl.getSize(jpcl.getElementObject(self.getElement()));
    +    	};
    +		
    +		var cachedDimensions = null,
    +			_getDimensions = function(component) {
    +				if (cachedDimensions == null)
    +					cachedDimensions = self.getDimensions();
    +				return cachedDimensions;
    +			};
    +		
    +		/*
    +		 * Function: clearCachedDimensions
    +		 * Clears the cached dimensions for the label. As a performance enhancement, label dimensions are
    +		 * cached from 1.3.12 onwards. The cache is cleared when you change the label text, of course, but
    +		 * there are other reasons why the text dimensions might change - if you make a change through CSS, for
    +		 * example, you might change the font size.  in that case you should explicitly call this method.
    +		 */
    +		this.clearCachedDimensions = function() {
    +			cachedDimensions = null;
    +		};
    +		
    +		this.computeMaxSize = function() {
    +    		var td = _getDimensions();
    +			return Math.max(td[0], td[1]);
    +    	}; 
    +		
    +		//override setVisible
    +    	var osv = self.setVisible;
    +    	self.setVisible = function(state) {
    +    		osv(state); // call superclass
    +    		div.style.display = state ? "block" : "none";
    +    	};
    +		
    +		this.cleanup = function() {
    +    		if (div != null) jpcl.removeElement(div);
    +    	};
    +		
    +		this.paint = function(params, containerExtents) {
    +			if (!initialised) {
    +				self.getElement();
    +				params.component.appendDisplayElement(div);
    +				self.attachListeners(div, params.component);
    +				initialised = true;
    +			}
    +			div.style.left = (params.component.x + params.d.minx) + "px";
    +			div.style.top = (params.component.y + params.d.miny) + "px";			
    +    	};
    +				
    +		this.draw = function(component, currentConnectionPaintStyle) {
    +	    	var td = _getDimensions();
    +	    	if (td != null && td.length == 2) {
    +				var cxy = {x:0,y:0};
    +                if (component.pointOnPath) {
    +                    var loc = self.loc, absolute = false;
    +                    if (jsPlumbUtil.isString(self.loc) || self.loc < 0 || self.loc > 1) {
    +                        loc = parseInt(self.loc);
    +                        absolute = true;
    +                    }
    +                    cxy = component.pointOnPath(loc, absolute);  // a connection
    +                }
    +                else {
    +                    var locToUse = self.loc.constructor == Array ? self.loc : self.endpointLoc;
    +                    cxy = { x:locToUse[0] * component.w,
    +                            y:locToUse[1] * component.h };      
    +                } 
    +                           
    +				var minx = cxy.x - (td[0] / 2),
    +				    miny = cxy.y - (td[1] / 2);
    +
    +                return {
    +                    component:component, 
    +                    d:{ minx:minx, miny:miny, td:td, cxy:cxy },
    +                    minX:minx, 
    +                    maxX:minx + td[0], 
    +                    minY:miny, 
    +                    maxY:miny + td[1]
    +                };								
    +        	}
    +	    	else return {minX:0,maxX:0,minY:0,maxY:0};
    +	    };
    +	    
    +	    this.reattachListeners = function(connector) {
    +	    	if (div) {
    +	    		self.reattachListenersForElement(div, self, connector);
    +	    	}
    +	    };
    +		
    +	};
    +	
    +	/*
    +     * Class: Overlays.Custom
    +     * A Custom overlay. You supply a 'create' function which returns some DOM element, and jsPlumb positions it.
    +     * The 'create' function is passed a Connection or Endpoint.
    +     */
    +    /*
    +     * Function: Constructor
    +     * 
    +     * Parameters:
    +     * 	create - function for jsPlumb to call that returns a DOM element.
    +     * 	location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5.
    +     * 	id - optional id to use for later retrieval of this overlay.
    +     * 	
    +     */
    +    jsPlumb.Overlays.Custom = function(params) {
    +    	this.type = "Custom";    	
    +    	AbstractDOMOverlay.apply(this, arguments);		    	        		    	    		
    +    };
    +
    +    jsPlumb.Overlays.GuideLines = function() {
    +        var self = this;
    +        self.length = 50;
    +        self.lineWidth = 5;
    +        this.type = "GuideLines";
    +        AbstractOverlay.apply(this, arguments);
    +        jsPlumb.jsPlumbUIComponent.apply(this, arguments);
    +        this.draw = function(connector, currentConnectionPaintStyle) {
    +
    +            var head = connector.pointAlongPathFrom(self.loc, self.length / 2),
    +                mid = connector.pointOnPath(self.loc),
    +                tail = jsPlumbUtil.pointOnLine(head, mid, self.length),
    +                tailLine = jsPlumbUtil.perpendicularLineTo(head, tail, 40),
    +                headLine = jsPlumbUtil.perpendicularLineTo(tail, head, 20);
    +
    +            return {
    +                connector:connector,
    +                head:head,
    +                tail:tail,
    +                headLine:headLine,
    +                tailLine:tailLine,                
    +                minX:Math.min(head.x, tail.x, headLine[0].x, headLine[1].x), 
    +                minY:Math.min(head.y, tail.y, headLine[0].y, headLine[1].y), 
    +                maxX:Math.max(head.x, tail.x, headLine[0].x, headLine[1].x), 
    +                maxY:Math.max(head.y, tail.y, headLine[0].y, headLine[1].y)
    +            };
    +        };
    +
    +        this.cleanup = function() { };  // nothing to clean up for GuideLines
    +    };
    +    
    +    /*
    +     * Class: Overlays.Label
    +     * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV.  Version 1.3.0 of jsPlumb
    +     * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label.  The 'labelStyle' parameter
    +     * is still supported in 1.3.0 but its usage is deprecated.  Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it 
    +     * puts on the Label's 'style' attribute, so the end result is the same. 
    +     */
    +    /*
    +     * Function: Constructor
    +     * 
    +     * Parameters:
    +     * 	cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes
    +     *             defined.  This parameter is preferred to using labelStyle, borderWidth and borderStyle.
    +     * 	label - the label to paint.  May be a string or a function that returns a string.  Nothing will be painted if your label is null or your
    +     *         label function returns null.  empty strings _will_ be painted.
    +     * 	location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5.
    +     * 	id - optional id to use for later retrieval of this overlay.
    +     * 	
    +     */
    +    jsPlumb.Overlays.Label = function(params) {
    +		var self = this;    	
    +		this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle;
    +		this.cssClass = this.labelStyle != null ? this.labelStyle.cssClass : null;
    +		params.create = function() {
    +			return document.createElement("div");
    +		};
    +    	jsPlumb.Overlays.Custom.apply(this, arguments);
    +		this.type = "Label";
    +    	
    +        var label = params.label || "",
    +            self = this,    	    
    +            labelText = null;
    +    	
    +    	/*
    +    	 * Function: setLabel
    +    	 * sets the label's, um, label.  you would think i'd call this function
    +    	 * 'setText', but you can pass either a Function or a String to this, so
    +    	 * it makes more sense as 'setLabel'. This uses innerHTML on the label div, so keep
    +         * that in mind if you need escaped HTML.
    +    	 */
    +    	this.setLabel = function(l) {
    +    		label = l;
    +    		labelText = null;
    +			self.clearCachedDimensions();
    +			_update();
    +    		self.component.repaint();
    +    	};
    +    	
    +		var _update = function() {
    +			if (typeof label == "function") {
    +    			var lt = label(self);
    +    			self.getElement().innerHTML = lt.replace(/\r\n/g, "<br/>");
    +    		}
    +    		else {
    +    			if (labelText == null) {
    +    				labelText = label;
    +    				self.getElement().innerHTML = labelText.replace(/\r\n/g, "<br/>");
    +    			}
    +    		}
    +		};
    +		
    +    	this.getLabel = function() {
    +    		return label;
    +    	};
    +    	
    +		var superGD = this.getDimensions;		
    +		this.getDimensions = function() {				
    +    		_update();
    +			return superGD();
    +    	};
    +		
    +    };
    +		
    +
    + // ********************************* END OF OVERLAY DEFINITIONS ***********************************************************************
    +    
    +})();
    \ No newline at end of file
    diff --git a/src/jsPlumb-connectors-flowchart.js b/src/jsPlumb-connectors-flowchart.js
    new file mode 100644
    index 000000000..77e9d1e7d
    --- /dev/null
    +++ b/src/jsPlumb-connectors-flowchart.js
    @@ -0,0 +1,315 @@
    +/*
    + * jsPlumb
    + * 
    + * Title:jsPlumb 1.4.0
    + * 
    + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
    + * elements, or VML.  
    + * 
    + * This file contains the 'flowchart' connectors, consisting of vertical and horizontal line segments.
    + *
    + * Copyright (c) 2010 - 2013 Simon Porritt (simon.porritt@gmail.com)
    + * 
    + * http://jsplumb.org
    + * http://github.com/sporritt/jsplumb
    + * http://code.google.com/p/jsplumb
    + * 
    + * Dual licensed under the MIT and GPL2 licenses.
    + */
    +;(function() {
    +   
    +    /**
    +     * Function: Constructor
    +     * 
    +     * Parameters:
    +     * 	stub - minimum length for the stub at each end of the connector. This can be an integer, giving a value for both ends of the connections, 
    +     * or an array of two integers, giving separate values for each end. The default is an integer with value 30 (pixels). 
    +     *  gap  - gap to leave between the end of the connector and the element on which the endpoint resides. if you make this larger than stub then you will see some odd looking behaviour.  
    +                Like stub, this can be an array or a single value. defaults to 0 pixels for each end.     
    +     * cornerRadius - optional, defines the radius of corners between segments. defaults to 0 (hard edged corners).
    +     */
    +    jsPlumb.Connectors.Flowchart = function(params) {
    +        this.type = "Flowchart";
    +        params = params || {};
    +        params.stub = params.stub || 30;
    +        var self = this,
    +            _super =  jsPlumb.Connectors.AbstractConnector.apply(this, arguments),		
    +            midpoint = params.midpoint || 0.5,
    +            points = [], segments = [],
    +            grid = params.grid,
    +            userSuppliedSegments = null,
    +            lastx = null, lasty = null, lastOrientation,	
    +            cornerRadius = params.cornerRadius != null ? params.cornerRadius : 10,	
    +            sgn = function(n) { return n < 0 ? -1 : n == 0 ? 0 : 1; },            
    +            /**
    +             * helper method to add a segment.
    +             */
    +            addSegment = function(segments, x, y, sx, sy) {
    +                // if segment would have length zero, dont add it.
    +                if (sx == lastx && sy == lasty) return;
    +                if (x == lastx && y == lasty) return;
    +                
    +                var lx = lastx == null ? sx : lastx,
    +                    ly = lasty == null ? sy : lasty,
    +                    o = lx == x ? "v" : "h",
    +                    sgnx = sgn(x - lx),
    +                    sgny = sgn(y - ly);
    +                    
    +                lastx = x;
    +                lasty = y;				    		                
    +                segments.push([lx, ly, x, y, o, sgnx, sgny]);				
    +            },
    +            segLength = function(s) {
    +                return Math.sqrt(Math.pow(s[0] - s[2], 2) + Math.pow(s[1] - s[3], 2));    
    +            },
    +            _cloneArray = function(a) { var _a = []; _a.push.apply(_a, a); return _a;},
    +            updateMinMax = function(a1) {
    +                self.bounds.minX = Math.min(self.bounds.minX, a1[2]);
    +                self.bounds.maxX = Math.max(self.bounds.maxX, a1[2]);
    +                self.bounds.minY = Math.min(self.bounds.minY, a1[3]);
    +                self.bounds.maxY = Math.max(self.bounds.maxY, a1[3]);    
    +            },
    +            writeSegments = function(segments, paintInfo) {
    +                var current, next;                
    +                for (var i = 0; i < segments.length - 1; i++) {
    +                    
    +                    current = current || _cloneArray(segments[i]);
    +                    next = _cloneArray(segments[i + 1]);
    +                    if (cornerRadius > 0 && current[4] != next[4]) {
    +                        var radiusToUse = Math.min(cornerRadius, segLength(current), segLength(next));
    +                        // right angle. adjust current segment's end point, and next segment's start point.
    +                        current[2] -= current[5] * radiusToUse;
    +                        current[3] -= current[6] * radiusToUse;
    +                        next[0] += next[5] * radiusToUse;
    +                        next[1] += next[6] * radiusToUse;														                         			
    +                        var ac = (current[6] == next[5] && next[5] == 1) ||
    +                                 ((current[6] == next[5] && next[5] == 0) && current[5] != next[6]) ||
    +                                 (current[6] == next[5] && next[5] == -1),
    +                            sgny = next[1] > current[3] ? 1 : -1,
    +                            sgnx = next[0] > current[2] ? 1 : -1,
    +                            sgnEqual = sgny == sgnx,
    +                            cx = (sgnEqual && ac || (!sgnEqual && !ac)) ? next[0] : current[2],
    +                            cy = (sgnEqual && ac || (!sgnEqual && !ac)) ? current[3] : next[1];                                                        
    +                        
    +                        _super.addSegment("Straight", {
    +                            x1:current[0], y1:current[1], x2:current[2], y2:current[3]
    +                        });
    +                            
    +                        _super.addSegment("Arc", {
    +                            r:radiusToUse, 
    +                            x1:current[2], 
    +                            y1:current[3], 
    +                            x2:next[0], 
    +                            y2:next[1],
    +                            cx:cx,
    +                            cy:cy,
    +                            ac:ac
    +                        });	                                            
    +                    }
    +                    else {
    +                        _super.addSegment("Straight", {
    +                            x1:current[0], y1:current[1], x2:current[2], y2:current[3]
    +                        });
    +                    }                    
    +                    current = next;
    +                }
    +                // last segment
    +                _super.addSegment("Straight", {
    +                    x1:next[0], y1:next[1], x2:next[2], y2:next[3]
    +                });                             
    +            };
    +        
    +        this.setSegments = function(s) {
    +            userSuppliedSegments = s;
    +        };
    +        
    +        this.isEditable = function() { return true; };
    +        
    +        /*
    +            Function: getOriginalSegments
    +            Gets the segments before the addition of rounded corners. This is used by the flowchart
    +            connector editor, since it only wants to concern itself with the original segments.
    +        */
    +        this.getOriginalSegments = function() {
    +            return userSuppliedSegments || segments;
    +        };
    +        
    +        this._compute = function(paintInfo, params) {
    +            
    +            if (params.clearEdits)
    +                userSuppliedSegments = null;
    +            
    +            if (userSuppliedSegments != null) {
    +                writeSegments(userSuppliedSegments, paintInfo);                
    +                return;
    +            }
    +            
    +            segments = [];
    +            lastx = null; lasty = null;
    +            lastOrientation = null;          
    +            
    +            var midx = paintInfo.startStubX + ((paintInfo.endStubX - paintInfo.startStubX) * midpoint),
    +                midy = paintInfo.startStubY + ((paintInfo.endStubY - paintInfo.startStubY) * midpoint);
    +                                                                                         
    +            // add the start stub segment.
    +            addSegment(segments, paintInfo.startStubX, paintInfo.startStubY, paintInfo.sx, paintInfo.sy);			
    +    
    +            var findClearedLine = function(start, mult, anchorPos, dimension) {
    +                    return start + (mult * (( 1 - anchorPos) * dimension) + _super.maxStub);
    +                },
    +                orientations = { x:[ 0, 1 ], y:[ 1, 0 ] },
    +                lineCalculators = {
    +                    perpendicular : function(axis) {
    +                        with (paintInfo) {
    +                            var sis = {
    +                                x:[ [ [ 1,2,3,4 ], null, [ 2,1,4,3 ] ], null, [ [ 4,3,2,1 ], null, [ 3,4,1,2 ] ] ],
    +                                y:[ [ [ 3,2,1,4 ], null, [ 2,3,4,1 ] ], null, [ [ 4,1,2,3 ], null, [ 1,4,3,2 ] ] ]
    +                            },
    +                            stubs = { 
    +                                x:[ [ startStubX, endStubX ] , null, [ endStubX, startStubX ] ],
    +                                y:[ [ startStubY, endStubY ] , null, [ endStubY, startStubY ] ]
    +                            },
    +                            midLines = {
    +                                x:[ [ midx, startStubY ], [ midx, endStubY ] ],
    +                                y:[ [ startStubX, midy ], [ endStubX, midy ] ]
    +                            },
    +                            linesToEnd = {
    +                                x:[ [ endStubX, startStubY ] ],
    +                                y:[ [ startStubX, endStubY ] ]
    +                            },
    +                            startToEnd = {
    +                                x:[ [ startStubX, endStubY ], [ endStubX, endStubY ] ],        
    +                                y:[ [ endStubX, startStubY ], [ endStubX, endStubY ] ]
    +                            },
    +                            startToMidToEnd = {
    +                                x:[ [ startStubX, midy ], [ endStubX, midy ], [ endStubX, endStubY ] ],
    +                                y:[ [ midx, startStubY ], [ midx, endStubY ], [ endStubX, endStubY ] ]
    +                            },
    +                            otherStubs = {
    +                                x:[ startStubY, endStubY ],
    +                                y:[ startStubX, endStubX ]                                    
    +                            },
    +                                        
    +                            soIdx = orientations[axis][0], toIdx = orientations[axis][1],
    +                            _so = so[soIdx] + 1,
    +                            _to = to[toIdx] + 1,
    +                            otherFlipped = (to[toIdx] == -1 && (otherStubs[axis][1] < otherStubs[axis][0])) || (to[toIdx] == 1 && (otherStubs[axis][1] > otherStubs[axis][0])),
    +                            stub1 = stubs[axis][_so][0],
    +                            stub2 = stubs[axis][_so][1],
    +                            segmentIndexes = sis[axis][_so][_to];
    +                            
    +                            if (segment == segmentIndexes[3] || (segment == segmentIndexes[2] && otherFlipped)) {
    +                                return midLines[axis];       
    +                            }
    +                            else if (segment == segmentIndexes[2] && stub2 < stub1) {
    +                                return linesToEnd[axis];
    +                            }
    +                            else if ((segment == segmentIndexes[2] && stub2 >= stub1) || (segment == segmentIndexes[1] && !otherFlipped)) {
    +                                return startToMidToEnd[axis];
    +                            }
    +                            else if (segment == segmentIndexes[0] || (segment == segmentIndexes[1] && otherFlipped)) {
    +                                return startToEnd[axis];  
    +                            }                                
    +                        }                                
    +                    },
    +                    orthogonal : function(axis) {                    
    +                        var pi = paintInfo,                                            
    +                            extent = {
    +                                "x":pi.so[0] == -1 ? Math.min(pi.startStubX, pi.endStubX) : Math.max(pi.startStubX, pi.endStubX),
    +                                "y":pi.so[1] == -1 ? Math.min(pi.startStubY, pi.endStubY) : Math.max(pi.startStubY, pi.endStubY)
    +                            }[axis];
    +                                                
    +                        return {
    +                            "x":[ [ extent, pi.startStubY ],[ extent, pi.endStubY ], [ pi.endStubX, pi.endStubY ] ],
    +                            "y":[ [ pi.startStubX, extent ], [ pi.endStubX, extent ],[ pi.endStubX, pi.endStubY ] ]
    +                        }[axis];                    
    +                    },
    +                    opposite : function(axis) {                                                
    +                        var pi = paintInfo,
    +                            otherAxis = {"x":"y","y":"x"}[axis], 
    +                            stub = "Stub" + axis.toUpperCase(),
    +                            otherStub = "Stub" + otherAxis.toUpperCase(),
    +                            otherStartStub = pi["start" + otherStub],
    +                            startStub = pi["start" + stub],
    +                            otherEndStub = pi["end" + otherStub],
    +                            endStub = pi["end" + stub],
    +                            dim = {"x":"height","y":"width"}[axis],
    +                            comparator = pi["is" + axis.toUpperCase() + "GreaterThanStubTimes2"],
    +                            idx = axis == "x" ? 0 : 1;
    +
    +                        if (params.sourceEndpoint.elementId == params.targetEndpoint.elementId) {
    +                            var _val = otherStartStub + ((1 - params.sourceAnchor[otherAxis]) * params.sourceInfo[dim]) + _super.maxStub;
    +                            return {
    +                                "x":[ [ startStub, _val ], [ endStub, _val ] ],
    +                                "y":[ [ _val, startStub ], [ _val, endStub ] ]
    +                            }[axis];
    +                            
    +                        }                                                        
    +                        else if (!comparator || (pi.so[idx] == 1 && startStub > endStub)
    +                           || (pi.so[idx] == -1 && startStub < endStub)) {
    +                            return {
    +                                "x":[[ startStub, midy ], [ endStub, midy ]],
    +                                "y":[[ midx, startStub ], [ midx, endStub ]]
    +                            }[axis];
    +                        }
    +                        else if ((pi.so[idx] == 1 && startStub < endStub) || (pi.so[idx] == -1 && startStub > endStub)) {
    +                            return {
    +                                "x":[[ midx, pi.sy ], [ midx, pi.ty ]],
    +                                "y":[[ pi.sx, midy ], [ pi.tx, midy ]]
    +                            }[axis];
    +                        }                        
    +                    }
    +                },
    +                p = lineCalculators[paintInfo.anchorOrientation](paintInfo.sourceAxis);
    +                
    +            if (p) {
    +                for (var i = 0; i < p.length; i++) {                	
    +                    addSegment(segments, p[i][0], p[i][1]);
    +                }
    +            }          
    +            
    +            addSegment(segments, paintInfo.endStubX, paintInfo.endStubY);
    +    
    +            // end stub
    +            addSegment(segments, paintInfo.tx, paintInfo.ty);               
    +            
    +            writeSegments(segments, paintInfo);                            
    +        };	
    +
    +        this.getPath = function() {
    +            var _last = null, _lastAxis = null, s = [], segs = userSuppliedSegments || segments;
    +            for (var i = 0; i < segs.length; i++) {
    +                var seg = segs[i], axis = seg[4], axisIndex = (axis == "v" ? 3 : 2);
    +                if (_last != null && _lastAxis === axis) {
    +                    _last[axisIndex] = seg[axisIndex];                            
    +                }
    +                else {
    +                    if (seg[0] != seg[2] || seg[1] != seg[3]) {
    +                        s.push({
    +                            start:[ seg[0], seg[1] ],
    +                            end:[ seg[2], seg[3] ]
    +                        });                    
    +                        _last = seg;
    +                        _lastAxis = seg[4];
    +                    }
    +                }
    +            }
    +            return s;
    +        };	
    +
    +        this.setPath = function(path) {
    +            userSuppliedSegments = [];
    +            for (var i = 0; i < path.length; i++) {
    +                 var lx = path[i].start[0],
    +                    ly = path[i].start[1],
    +                    x = path[i].end[0],
    +                    y = path[i].end[1],
    +                    o = lx == x ? "v" : "h",
    +                    sgnx = sgn(x - lx),
    +                    sgny = sgn(y - ly);
    +
    +                userSuppliedSegments.push([lx, ly, x, y, o, sgnx, sgny]);
    +            }
    +        };
    +    };
    +})();
    \ No newline at end of file
    diff --git a/src/jsPlumb-connectors-statemachine.js b/src/jsPlumb-connectors-statemachine.js
    new file mode 100644
    index 000000000..ac431e623
    --- /dev/null
    +++ b/src/jsPlumb-connectors-statemachine.js
    @@ -0,0 +1,267 @@
    +/*
    + * jsPlumb
    + *
    + * Title:jsPlumb 1.4.0
    + *
    + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
    + * elements, or VML.
    + *
    + * This file contains the state machine connectors.
    + *
    + * Thanks to Brainstorm Mobile Solutions for supporting the development of these.
    + *
    + * Copyright (c) 2010 - 2013 Simon Porritt (simon.porritt@gmail.com)
    + *
    + * http://jsplumb.org
    + * http://github.com/sporritt/jsplumb
    + * http://code.google.com/p/jsplumb
    + *
    + * Dual licensed under the MIT and GPL2 licenses.
    + */
    +
    +;(function() {
    +
    +	var Line = function(x1, y1, x2, y2) {
    +
    +		this.m = (y2 - y1) / (x2 - x1);
    +		this.b = -1 * ((this.m * x1) - y1);
    +	
    +		this.rectIntersect = function(x,y,w,h) {
    +			var results = [];
    +		
    +			// 	try top face
    +			// 	the equation of the top face is y = (0 * x) + b; y = b.
    +			var xInt = (y - this.b) / this.m;
    +			// test that the X value is in the line's range.
    +			if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]);
    +		
    +			// try right face
    +			var yInt = (this.m * (x + w)) + this.b;
    +			if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]);
    +		
    +			// 	bottom face
    +			var xInt = ((y + h) - this.b) / this.m;
    +			// test that the X value is in the line's range.
    +			if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]);
    +		
    +			// try left face
    +			var yInt = (this.m * x) + this.b;
    +			if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]);
    +
    +			if (results.length == 2) {
    +				var midx = (results[0][0] + results[1][0]) / 2, midy = (results[0][1] + results[1][1]) / 2;
    +				results.push([ midx,midy ]);
    +				// now calculate the segment inside the rectangle where the midpoint lies.
    +				var xseg = midx <= x + (w / 2) ? -1 : 1,
    +					yseg = midy <= y + (h / 2) ? -1 : 1;
    +				results.push([xseg, yseg]);
    +				return results;
    +			}
    +		
    +			return null;
    +
    +		};
    +	},
    +	_segment = function(x1, y1, x2, y2) {
    +		if (x1 <= x2 && y2 <= y1) return 1;
    +		else if (x1 <= x2 && y1 <= y2) return 2;
    +		else if (x2 <= x1 && y2 >= y1) return 3;
    +		return 4;
    +	},
    +		
    +		// the control point we will use depends on the faces to which each end of the connection is assigned, specifically whether or not the
    +		// two faces are parallel or perpendicular.  if they are parallel then the control point lies on the midpoint of the axis in which they
    +		// are parellel and varies only in the other axis; this variation is proportional to the distance that the anchor points lie from the
    +		// center of that face.  if the two faces are perpendicular then the control point is at some distance from both the midpoints; the amount and
    +		// direction are dependent on the orientation of the two elements. 'seg', passed in to this method, tells you which segment the target element
    +		// lies in with respect to the source: 1 is top right, 2 is bottom right, 3 is bottom left, 4 is top left.
    +		//
    +		// sourcePos and targetPos are arrays of info about where on the source and target each anchor is located.  their contents are:
    +		//
    +		// 0 - absolute x
    +		// 1 - absolute y
    +		// 2 - proportional x in element (0 is left edge, 1 is right edge)
    +		// 3 - proportional y in element (0 is top edge, 1 is bottom edge)
    +		// 	
    +	_findControlPoint = function(midx, midy, segment, sourceEdge, targetEdge, dx, dy, distance, proximityLimit) {
    +        // TODO (maybe)
    +        // - if anchor pos is 0.5, make the control point take into account the relative position of the elements.
    +        if (distance <= proximityLimit) return [midx, midy];
    +
    +        if (segment === 1) {
    +            if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ];
    +            else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ];
    +            else return [ midx + (-1 * dx) , midy + (-1 * dy) ];
    +        }
    +        else if (segment === 2) {
    +            if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ];
    +            else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ];
    +            else return [ midx + (1 * dx) , midy + (-1 * dy) ];
    +        }
    +        else if (segment === 3) {
    +            if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ];
    +            else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ];
    +            else return [ midx + (-1 * dx) , midy + (-1 * dy) ];
    +        }
    +        else if (segment === 4) {
    +            if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ];
    +            else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ];
    +            else return [ midx + (1 * dx) , midy + (-1 * dy) ];
    +        }
    +
    +	};	
    +	
    +	/**
    +     * Class: Connectors.StateMachine
    +     * Provides 'state machine' connectors.
    +     */
    +	/*
    +	 * Function: Constructor
    +	 * 
    +	 * Parameters:
    +	 * curviness -	measure of how "curvy" the connectors will be.  this is translated as the distance that the
    +     *                Bezier curve's control point is from the midpoint of the straight line connecting the two
    +     *              endpoints, and does not mean that the connector is this wide.  The Bezier curve never reaches
    +     *              its control points; they act as gravitational masses. defaults to 10.
    +	 * margin	-	distance from element to start and end connectors, in pixels.  defaults to 5.
    +	 * proximityLimit  -   sets the distance beneath which the elements are consider too close together to bother
    +	 *						with fancy curves. by default this is 80 pixels.
    +	 * loopbackRadius	-	the radius of a loopback connector.  optional; defaults to 25.
    +	 * showLoopback   -   If set to false this tells the connector that it is ok to paint connections whose source and target is the same element with a connector running through the element. The default value for this is true; the connector always makes a loopback connection loop around the element rather than passing through it.
    +	*/
    +	jsPlumb.Connectors.StateMachine = function(params) {
    +		params = params || {};
    +		this.type = "StateMachine";
    +
    +		var self = this,
    +			_super =  jsPlumb.Connectors.AbstractConnector.apply(this, arguments),
    +			curviness = params.curviness || 10,
    +			margin = params.margin || 5,
    +			proximityLimit = params.proximityLimit || 80,
    +			clockwise = params.orientation && params.orientation === "clockwise",
    +			loopbackRadius = params.loopbackRadius || 25,
    +			showLoopback = params.showLoopback !== false;
    +		
    +		this._compute = function(paintInfo, params) {
    +			var w = Math.abs(params.sourcePos[0] - params.targetPos[0]),
    +				h = Math.abs(params.sourcePos[1] - params.targetPos[1]),
    +				x = Math.min(params.sourcePos[0], params.targetPos[0]),
    +				y = Math.min(params.sourcePos[1], params.targetPos[1]);				
    +		
    +			if (!showLoopback || (params.sourceEndpoint.elementId !== params.targetEndpoint.elementId)) {                            
    +				var _sx = params.sourcePos[0] < params.targetPos[0] ? 0  : w,
    +					_sy = params.sourcePos[1] < params.targetPos[1] ? 0:h,
    +					_tx = params.sourcePos[0] < params.targetPos[0] ? w : 0,
    +					_ty = params.sourcePos[1] < params.targetPos[1] ? h : 0;
    +            
    +				// now adjust for the margin
    +				if (params.sourcePos[2] === 0) _sx -= margin;
    +            	if (params.sourcePos[2] === 1) _sx += margin;
    +            	if (params.sourcePos[3] === 0) _sy -= margin;
    +            	if (params.sourcePos[3] === 1) _sy += margin;
    +            	if (params.targetPos[2] === 0) _tx -= margin;
    +            	if (params.targetPos[2] === 1) _tx += margin;
    +            	if (params.targetPos[3] === 0) _ty -= margin;
    +            	if (params.targetPos[3] === 1) _ty += margin;
    +
    +            	//
    +	            // these connectors are quadratic bezier curves, having a single control point. if both anchors 
    +    	        // are located at 0.5 on their respective faces, the control point is set to the midpoint and you
    +        	    // get a straight line.  this is also the case if the two anchors are within 'proximityLimit', since
    +           	 	// it seems to make good aesthetic sense to do that. outside of that, the control point is positioned 
    +           	 	// at 'curviness' pixels away along the normal to the straight line connecting the two anchors.
    +	            // 
    +   	        	// there may be two improvements to this.  firstly, we might actually support the notion of avoiding nodes
    +            	// in the UI, or at least making a good effort at doing so.  if a connection would pass underneath some node,
    +            	// for example, we might increase the distance the control point is away from the midpoint in a bid to
    +            	// steer it around that node.  this will work within limits, but i think those limits would also be the likely
    +            	// limits for, once again, aesthetic good sense in the layout of a chart using these connectors.
    +            	//
    +            	// the second possible change is actually two possible changes: firstly, it is possible we should gradually
    +            	// decrease the 'curviness' as the distance between the anchors decreases; start tailing it off to 0 at some
    +            	// point (which should be configurable).  secondly, we might slightly increase the 'curviness' for connectors
    +            	// with respect to how far their anchor is from the center of its respective face. this could either look cool,
    +            	// or stupid, and may indeed work only in a way that is so subtle as to have been a waste of time.
    +            	//
    +
    +				var _midx = (_sx + _tx) / 2, _midy = (_sy + _ty) / 2, 
    +            	    m2 = (-1 * _midx) / _midy, theta2 = Math.atan(m2),
    +            	    dy =  (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.sin(theta2)),
    +				    dx =  (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.cos(theta2)),
    +				    segment = _segment(_sx, _sy, _tx, _ty),
    +				    distance = Math.sqrt(Math.pow(_tx - _sx, 2) + Math.pow(_ty - _sy, 2)),			
    +	            	// calculate the control point.  this code will be where we'll put in a rudimentary element avoidance scheme; it
    +	            	// will work by extending the control point to force the curve to be, um, curvier.
    +					_controlPoint = _findControlPoint(_midx,
    +                                                  _midy,
    +                                                  segment,
    +                                                  params.sourcePos,
    +                                                  params.targetPos,
    +                                                  curviness, curviness,
    +                                                  distance,
    +                                                  proximityLimit);
    +
    +				_super.addSegment("Bezier", {
    +					x1:_tx, y1:_ty, x2:_sx, y2:_sy,
    +					cp1x:_controlPoint[0], cp1y:_controlPoint[1],
    +					cp2x:_controlPoint[0], cp2y:_controlPoint[1]
    +				});				
    +            }
    +            else {
    +            	// a loopback connector.  draw an arc from one anchor to the other.            	
    +        		var x1 = params.sourcePos[0], x2 = params.sourcePos[0], y1 = params.sourcePos[1] - margin, y2 = params.sourcePos[1] - margin, 				
    +					cx = x1, cy = y1 - loopbackRadius;
    +				
    +					// canvas sizing stuff, to ensure the whole painted area is visible.
    +					w = 2 * loopbackRadius, 
    +					h = 2 * loopbackRadius,
    +					x = cx - loopbackRadius, 
    +					y = cy - loopbackRadius;
    +
    +				paintInfo.points[0] = x;
    +				paintInfo.points[1] = y;
    +				paintInfo.points[2] = w;
    +				paintInfo.points[3] = h;
    +				
    +				// ADD AN ARC SEGMENT.
    +				_super.addSegment("Arc", {
    +					x1:(x1-x) + 4,
    +					y1:y1-y,
    +					startAngle:0,
    +					endAngle: 2 * Math.PI,
    +					r:loopbackRadius,
    +					ac:!clockwise,
    +					x2:(x1-x) - 4,
    +					y2:y1-y,
    +					cx:cx-x,
    +					cy:cy-y
    +				});
    +            }                           
    +        };                        
    +	};
    +})();
    +
    +/*
    +    	// a possible rudimentary avoidance scheme, old now, perhaps not useful.
    +        //      if (avoidSelector) {
    +		//		    var testLine = new Line(sourcePos[0] + _sx,sourcePos[1] + _sy,sourcePos[0] + _tx,sourcePos[1] + _ty);
    +		//		    var sel = jsPlumb.getSelector(avoidSelector);
    +		//		    for (var i = 0; i < sel.length; i++) {
    +		//			    var id = jsPlumb.getId(sel[i]);
    +		//			    if (id != sourceEndpoint.elementId && id != targetEndpoint.elementId) {
    +		//				    o = jsPlumb.getOffset(id), s = jsPlumb.getSize(id);
    +//
    +//						    if (o && s) {
    +//							    var collision = testLine.rectIntersect(o.left,o.top,s[0],s[1]);
    +//							    if (collision) {
    +								    // set the control point to be a certain distance from the midpoint of the two points that
    +								    // the line crosses on the rectangle.
    +								    // TODO where will this 75 number come from?
    +					//			    _controlX = collision[2][0] + (75 * collision[3][0]);
    +				//	/			    _controlY = collision[2][1] + (75 * collision[3][1]);
    +//							    }
    +//						    }
    +					//  }
    +	//			    }
    +              //}
    +    */
    \ No newline at end of file
    diff --git a/src/jsPlumb-defaults.js b/src/jsPlumb-defaults.js
    new file mode 100644
    index 000000000..6e5369d6f
    --- /dev/null
    +++ b/src/jsPlumb-defaults.js
    @@ -0,0 +1,1437 @@
    +/*
    + * jsPlumb
    + * 
    + * Title:jsPlumb 1.4.0
    + * 
    + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
    + * elements, or VML.  
    + * 
    + * This file contains the default Connectors, Endpoint and Overlay definitions.
    + *
    + * Copyright (c) 2010 - 2013 Simon Porritt (http://jsplumb.org)
    + * 
    + * http://jsplumb.org
    + * http://github.com/sporritt/jsplumb
    + * http://code.google.com/p/jsplumb
    + * 
    + * Dual licensed under the MIT and GPL2 licenses.
    + */
    +
    +;(function() {	
    +				
    +	/**
    +	 * 
    +	 * Helper class to consume unused mouse events by components that are DOM elements and
    +	 * are used by all of the different rendering modes.
    +	 * 
    +	 */
    +	jsPlumb.DOMElementComponent = function(params) {
    +		jsPlumb.jsPlumbUIComponent.apply(this, arguments);
    +		// when render mode is canvas, these functions may be called by the canvas mouse handler.  
    +		// this component is safe to pipe this stuff to /dev/null.
    +		this.mousemove = 
    +		this.dblclick  = 
    +		this.click = 
    +		this.mousedown = 
    +		this.mouseup = function(e) { };					
    +	};
    +	
    +	jsPlumb.Segments = {
    +        	
    +        /*
    +         * Class: AbstractSegment
    +         * A Connector is made up of 1..N Segments, each of which has a Type, such as 'Straight', 'Arc',
    +         * 'Bezier'. This is new from 1.4.0, and gives us a lot more flexibility when drawing connections: things such
    +         * as rounded corners for flowchart connectors, for example, or a straight line stub for Bezier connections, are
    +         * much easier to do now.
    +         *
    +         * A Segment is responsible for providing coordinates for painting it, and also must be able to report its length.
    +         * 
    +         */ 
    +        AbstractSegment : function(params) { 
    +            this.params = params;
    +            
    +            /**
    +            * Function: findClosestPointOnPath
    +            * Finds the closest point on this segment to the given [x, y], 
    +            * returning both the x and y of the point plus its distance from
    +            * the supplied point, and its location along the length of the
    +            * path inscribed by the segment.  This implementation returns
    +            * Infinity for distance and null values for everything else;
    +            * subclasses are expected to override.
    +            */
    +            this.findClosestPointOnPath = function(x, y) {
    +                return {
    +                    d:Infinity,
    +                    x:null,
    +                    y:null,
    +                    l:null
    +                };
    +            };
    +
    +            this.getBounds = function() {
    +                return {
    +                    minX:Math.min(params.x1, params.x2),
    +                    minY:Math.min(params.y1, params.y2),
    +                    maxX:Math.max(params.x1, params.x2),
    +                    maxY:Math.max(params.y1, params.y2)
    +                };
    +            };
    +        },
    +        Straight : function(params) {
    +            var self = this,
    +                _super = jsPlumb.Segments.AbstractSegment.apply(this, arguments),
    +                length, m, m2, x1, x2, y1, y2,
    +                _recalc = function() {
    +                    length = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
    +                    m = jsPlumbUtil.gradient({x:x1, y:y1}, {x:x2, y:y2});
    +                    m2 = -1 / m;                
    +                };
    +                
    +            this.type = "Straight";
    +            
    +            self.getLength = function() { return length; };
    +            self.getGradient = function() { return m; };
    +                
    +            this.getCoordinates = function() {
    +                return { x1:x1,y1:y1,x2:x2,y2:y2 };
    +            };
    +            this.setCoordinates = function(coords) {
    +                x1 = coords.x1; y1 = coords.y1; x2 = coords.x2; y2 = coords.y2;
    +                _recalc();
    +            };
    +            this.setCoordinates({x1:params.x1, y1:params.y1, x2:params.x2, y2:params.y2});
    +
    +            this.getBounds = function() {
    +                return {
    +                    minX:Math.min(x1, x2),
    +                    minY:Math.min(y1, y2),
    +                    maxX:Math.max(x1, x2),
    +                    maxY:Math.max(y1, y2)
    +                };
    +            };
    +            
    +            /**
    +             * returns the point on the segment's path that is 'location' along the length of the path, where 'location' is a decimal from
    +             * 0 to 1 inclusive. for the straight line segment this is simple maths.
    +             */
    +             this.pointOnPath = function(location, absolute) {
    +                if (location == 0 && !absolute)
    +                    return { x:x1, y:y1 };
    +                else if (location == 1 && !absolute)
    +                    return { x:x2, y:y2 };
    +                else {
    +                    var l = absolute ? location > 0 ? location : length + location : location * length;
    +                    return jsPlumbUtil.pointOnLine({x:x1, y:y1}, {x:x2, y:y2}, l);
    +                }
    +            };
    +            
    +            /**
    +             * returns the gradient of the segment at the given point - which for us is constant.
    +             */
    +            this.gradientAtPoint = function(_) {
    +                return m;
    +            };
    +            
    +            /**
    +             * returns the point on the segment's path that is 'distance' along the length of the path from 'location', where 
    +             * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels.
    +             * this hands off to jsPlumbUtil to do the maths, supplying two points and the distance.
    +             */            
    +            this.pointAlongPathFrom = function(location, distance, absolute) {            
    +                var p = self.pointOnPath(location, absolute),
    +                    farAwayPoint = location == 1 ? {
    +                        x:x1 + ((x2 - x1) * 10),
    +                        y:y1 + ((y1 - y2) * 10)
    +                    } : distance <= 0 ? {x:x1, y:y1} : {x:x2, y:y2 };
    +    
    +                if (distance <= 0 && Math.abs(distance) > 1) distance *= -1;
    +    
    +                return jsPlumbUtil.pointOnLine(p, farAwayPoint, distance);
    +            };
    +            
    +            /**
    +                Function: findClosestPointOnPath
    +                Finds the closest point on this segment to [x,y]. See
    +                notes on this method in AbstractSegment.
    +            */
    +            this.findClosestPointOnPath = function(x, y) {
    +                if (m == 0) {
    +                    return {
    +                        x:x,
    +                        y:y1,
    +                        d:Math.abs(y - y1)
    +                    };
    +                }
    +                else if (m == Infinity || m == -Infinity) {
    +                    return {
    +                        x:x1,
    +                        y:y,
    +                        d:Math.abs(x - 1)
    +                    };
    +                }
    +                else {
    +                    // closest point lies on normal from given point to this line.  
    +                    var b = y1 - (m * x1),
    +                        b2 = y - (m2 * x),                    
    +                    // y1 = m.x1 + b and y1 = m2.x1 + b2
    +                    // so m.x1 + b = m2.x1 + b2
    +                    // x1(m - m2) = b2 - b
    +                    // x1 = (b2 - b) / (m - m2)
    +                        _x1 = (b2 -b) / (m - m2),
    +                        _y1 = (m * _x1) + b,
    +                        d = jsPlumbUtil.lineLength([ x, y ], [ _x1, _y1 ]),
    +                        fractionInSegment = jsPlumbUtil.lineLength([ _x1, _y1 ], [ x1, y1 ]);
    +                    
    +                    return { d:d, x:_x1, y:_y1, l:fractionInSegment / length};            
    +                }
    +            };
    +        },
    +	
    +        /*
    +            Arc Segment. You need to supply:
    +    
    +            r   -   radius
    +            cx  -   center x for the arc
    +            cy  -   center y for the arc
    +            ac  -   whether the arc is anticlockwise or not. default is clockwise.
    +    
    +            and then either:
    +    
    +            startAngle  -   startAngle for the arc.
    +            endAngle    -   endAngle for the arc.
    +    
    +            or:
    +    
    +            x1          -   x for start point
    +            y1          -   y for start point
    +            x2          -   x for end point
    +            y2          -   y for end point
    +    
    +        */
    +        Arc : function(params) {
    +            var self = this,
    +                _super = jsPlumb.Segments.AbstractSegment.apply(this, arguments),
    +                _calcAngle = function(_x, _y) {
    +                    return jsPlumbUtil.theta([params.cx, params.cy], [_x, _y]);    
    +                },
    +                _calcAngleForLocation = function(location) {
    +                    if (self.anticlockwise) {
    +                        var sa = self.startAngle < self.endAngle ? self.startAngle + TWO_PI : self.startAngle,
    +                            s = Math.abs(sa - self.endAngle);
    +                        return sa - (s * location);                    
    +                    }
    +                    else {
    +                        var ea = self.endAngle < self.startAngle ? self.endAngle + TWO_PI : self.endAngle,
    +                            s = Math.abs (ea - self.startAngle);
    +                    
    +                        return self.startAngle + (s * location);
    +                    }
    +                },
    +                TWO_PI = 2 * Math.PI;
    +            
    +            this.radius = params.r;
    +            this.anticlockwise = params.ac;			
    +            this.type = "Arc";
    +                
    +            if (params.startAngle && params.endAngle) {
    +                this.startAngle = params.startAngle;
    +                this.endAngle = params.endAngle;            
    +                this.x1 = params.cx + (self.radius * Math.cos(params.startAngle));     
    +                this.y1 = params.cy + (self.radius * Math.sin(params.startAngle));            
    +                this.x2 = params.cx + (self.radius * Math.cos(params.endAngle));     
    +                this.y2 = params.cy + (self.radius * Math.sin(params.endAngle));                        
    +            }
    +            else {
    +                this.startAngle = _calcAngle(params.x1, params.y1);
    +                this.endAngle = _calcAngle(params.x2, params.y2);            
    +                this.x1 = params.x1;
    +                this.y1 = params.y1;
    +                this.x2 = params.x2;
    +                this.y2 = params.y2;            
    +            }
    +            
    +            if (this.endAngle < 0) this.endAngle += TWO_PI;
    +            if (this.startAngle < 0) this.startAngle += TWO_PI;   
    +
    +            // segment is used by vml     
    +            this.segment = jsPlumbUtil.segment([this.x1, this.y1], [this.x2, this.y2]);
    +            
    +            // we now have startAngle and endAngle as positive numbers, meaning the
    +            // absolute difference (|d|) between them is the sweep (s) of this arc, unless the
    +            // arc is 'anticlockwise' in which case 's' is given by 2PI - |d|.
    +            
    +            var ea = self.endAngle < self.startAngle ? self.endAngle + TWO_PI : self.endAngle;
    +            self.sweep = Math.abs (ea - self.startAngle);
    +            if (self.anticlockwise) self.sweep = TWO_PI - self.sweep;
    +            var circumference = 2 * Math.PI * self.radius,
    +                frac = self.sweep / TWO_PI,
    +                length = circumference * frac;
    +            
    +            this.getLength = function() {
    +                return length;
    +            };
    +
    +            this.getBounds = function() {
    +                return {
    +                    minX:params.cx - params.r,
    +                    maxX:params.cx + params.r,
    +                    minY:params.cy - params.r,
    +                    maxY:params.cy + params.r
    +                }
    +            };
    +            
    +            var VERY_SMALL_VALUE = 0.0000000001,
    +                gentleRound = function(n) {
    +                    var f = Math.floor(n), r = Math.ceil(n);
    +                    if (n - f < VERY_SMALL_VALUE) 
    +                        return f;    
    +                    else if (r - n < VERY_SMALL_VALUE)
    +                        return r;
    +                    return n;
    +                };
    +            
    +            /**
    +             * returns the point on the segment's path that is 'location' along the length of the path, where 'location' is a decimal from
    +             * 0 to 1 inclusive. 
    +             */
    +            this.pointOnPath = function(location, absolute) {            
    +                
    +                if (location == 0) {
    +                    return { x:self.x1, y:self.y1, theta:self.startAngle };    
    +                }
    +                else if (location == 1) {
    +                    return { x:self.x2, y:self.y2, theta:self.endAngle };                    
    +                }
    +                
    +                if (absolute) {
    +                    location = location / length;
    +                }
    +    
    +                var angle = _calcAngleForLocation(location),
    +                    _x = params.cx + (params.r * Math.cos(angle)),
    +                    _y  = params.cy + (params.r * Math.sin(angle));					
    +    
    +                return { x:gentleRound(_x), y:gentleRound(_y), theta:angle };
    +            };
    +            
    +            /**
    +             * returns the gradient of the segment at the given point.
    +             */
    +            this.gradientAtPoint = function(location, absolute) {
    +                var p = self.pointOnPath(location, absolute);
    +                var m = jsPlumbUtil.normal( [ params.cx, params.cy ], [p.x, p.y ] );
    +                if (!self.anticlockwise && (m == Infinity || m == -Infinity)) m *= -1;
    +                return m;
    +            };	              
    +                    
    +            this.pointAlongPathFrom = function(location, distance, absolute) {
    +                var p = self.pointOnPath(location, absolute),
    +                    arcSpan = distance / circumference * 2 * Math.PI,
    +                    dir = self.anticlockwise ? -1 : 1,
    +                    startAngle = p.theta + (dir * arcSpan),				
    +                    startX = params.cx + (self.radius * Math.cos(startAngle)),
    +                    startY = params.cy + (self.radius * Math.sin(startAngle));	
    +    
    +                return {x:startX, y:startY};
    +            };	            
    +        },
    +	
    +        Bezier : function(params) {
    +            var self = this,
    +                _super = jsPlumb.Segments.AbstractSegment.apply(this, arguments),
    +                curve = [	
    +                    { x:params.x1, y:params.y1},
    +                    { x:params.cp1x, y:params.cp1y },
    +                    { x:params.cp2x, y:params.cp2y },
    +                    { x:params.x2, y:params.y2 }
    +                ],
    +                // although this is not a strictly rigorous determination of bounds
    +                // of a bezier curve, it works for the types of curves that this segment
    +                // type produces.
    +                bounds = {
    +                    minX:Math.min(params.x1, params.x2, params.cp1x, params.cp2x),
    +                    minY:Math.min(params.y1, params.y2, params.cp1y, params.cp2y),
    +                    maxX:Math.max(params.x1, params.x2, params.cp1x, params.cp2x),
    +                    maxY:Math.max(params.y1, params.y2, params.cp1y, params.cp2y)
    +                };
    +                
    +            this.type = "Bezier";            
    +            
    +            var _translateLocation = function(_curve, location, absolute) {
    +                if (absolute)
    +                    location = jsBezier.locationAlongCurveFrom(_curve, location > 0 ? 0 : 1, location);
    +    
    +                return location;
    +            };		
    +            
    +            /**
    +             * returns the point on the segment's path that is 'location' along the length of the path, where 'location' is a decimal from
    +             * 0 to 1 inclusive. 
    +             */
    +            this.pointOnPath = function(location, absolute) {
    +                location = _translateLocation(curve, location, absolute);                
    +                return jsBezier.pointOnCurve(curve, location);
    +            };
    +            
    +            /**
    +             * returns the gradient of the segment at the given point.
    +             */
    +            this.gradientAtPoint = function(location, absolute) {
    +                location = _translateLocation(curve, location, absolute);
    +                return jsBezier.gradientAtPoint(curve, location);        	
    +            };	              
    +            
    +            this.pointAlongPathFrom = function(location, distance, absolute) {
    +                location = _translateLocation(curve, location, absolute);
    +                return jsBezier.pointAlongCurveFrom(curve, location, distance);
    +            };
    +            
    +            this.getLength = function() {
    +                return jsBezier.getLength(curve);				
    +            };
    +
    +            this.getBounds = function() {
    +                return bounds;
    +            };
    +        }
    +    };
    +
    +    /*
    +        Class: AbstractComponent
    +        Superclass for AbstractConnector and AbstractEndpoint.
    +    */
    +    var AbstractComponent = function() {
    +        var self = this;
    +        self.resetBounds = function() {
    +            self.bounds = { minX:Infinity, minY:Infinity, maxX:-Infinity, maxY:-Infinity };
    +        };
    +        self.resetBounds();        
    +    };
    +	
    +	/*
    +	 * Class: AbstractConnector
    +	 * Superclass for all Connectors; here is where Segments are managed.  This is exposed on jsPlumb just so it
    +	 * can be accessed from other files. You should not try to instantiate one of these directly.
    +	 *
    +	 * When this class is asked for a pointOnPath, or gradient etc, it must first figure out which segment to dispatch
    +	 * that request to. This is done by keeping track of the total connector length as segments are added, and also
    +	 * their cumulative ratios to the total length.  Then when the right segment is found it is a simple case of dispatching
    +	 * the request to it (and adjusting 'location' so that it is relative to the beginning of that segment.)
    +	 */ 
    +	jsPlumb.Connectors.AbstractConnector = function(params) {
    +		
    +        AbstractComponent.apply(this, arguments);
    +
    +		var self = this,
    +            segments = [],
    +            editing = false,
    +			totalLength = 0,
    +			segmentProportions = [],
    +			segmentProportionalLengths = [],        
    +            stub = params.stub || 0, 
    +            sourceStub = jsPlumbUtil.isArray(stub) ? stub[0] : stub,
    +            targetStub = jsPlumbUtil.isArray(stub) ? stub[1] : stub,
    +            gap = params.gap || 0,
    +            sourceGap = jsPlumbUtil.isArray(gap) ? gap[0] : gap,
    +            targetGap = jsPlumbUtil.isArray(gap) ? gap[1] : gap,
    +            userProvidedSegments = null,
    +            edited = false,
    +            paintInfo = null;            
    +        
    +        // subclasses should override.
    +        this.isEditable = function() { return false; };                
    +        
    +        this.setEdited = function(ed) {
    +            edited = ed;
    +        };
    +
    +        // to be overridden by subclasses.
    +        this.getPath = function() { };
    +        this.setPath = function(path) { };
    +        
    +        /**
    +        * Function: findSegmentForPoint
    +        * Returns the segment that is closest to the given [x,y],
    +        * null if nothing found.  This function returns a JS 
    +        * object with:
    +        *
    +        *   d   -   distance from segment
    +        *   l   -   proportional location in segment
    +        *   x   -   x point on the segment
    +        *   y   -   y point on the segment
    +        *   s   -   the segment itself.
    +        */ 
    +        this.findSegmentForPoint = function(x, y) {
    +            var out = { d:Infinity, s:null, x:null, y:null, l:null };
    +            for (var i = 0; i < segments.length; i++) {
    +                var _s = segments[i].findClosestPointOnPath(x, y);
    +                if (_s.d < out.d) {
    +                    out.d = _s.d; 
    +                    out.l = _s.l; 
    +                    out.x = _s.x;
    +                    out.y = _s.y; 
    +                    out.s = segments[i];
    +                }
    +            }
    +            
    +            return out;                
    +        };
    +			
    +		var _updateSegmentProportions = function() {
    +                var curLoc = 0;
    +                for (var i = 0; i < segments.length; i++) {
    +                    var sl = segments[i].getLength();
    +                    segmentProportionalLengths[i] = sl / totalLength;
    +                    segmentProportions[i] = [curLoc, (curLoc += (sl / totalLength)) ];
    +                }
    +            },
    +		
    +            /**
    +             * returns [segment, proportion of travel in segment, segment index] for the segment 
    +             * that contains the point which is 'location' distance along the entire path, where 
    +             * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths 
    +             * are made up of a list of segments, each of which contributes some fraction to
    +             * the total length. 
    +             * From 1.3.10 this also supports the 'absolute' property, which lets us specify a location
    +             * as the absolute distance in pixels, rather than a proportion of the total path. 
    +             */
    +            _findSegmentForLocation = function(location, absolute) {
    +                if (absolute) {
    +                    location = location > 0 ? location / totalLength : (totalLength + location) / totalLength;
    +                }
    +    
    +                var idx = segmentProportions.length - 1, inSegmentProportion = 1;
    +                //if (location < 1) {
    +                    for (var i = 0; i < segmentProportions.length; i++) {
    +                        if (segmentProportions[i][1] >= location) {
    +                            idx = i;
    +                            // todo is this correct for all connector path types?
    +                            inSegmentProportion = location == 1 ? 1 : location == 0 ? 0 : (location - segmentProportions[i][0]) / segmentProportionalLengths[i];                    
    +                            break;
    +                        }
    +                    }
    +                //}
    +                return { segment:segments[idx], proportion:inSegmentProportion, index:idx };
    +            },		
    +            _addSegment = function(type, params) {
    +                var s = new jsPlumb.Segments[type](params);
    +                segments.push(s);
    +                totalLength += s.getLength();	
    +                self.updateBounds(s);	                
    +            },					
    +            _clearSegments = function() {
    +                totalLength = 0;
    +                segments.splice(0, segments.length);
    +                segmentProportions.splice(0, segmentProportions.length);
    +                segmentProportionalLengths.splice(0, segmentProportionalLengths.length);
    +            };
    +        
    +        this.setSegments = function(_segs) {
    +            userProvidedSegments = [];
    +            totalLength = 0;
    +            for (var i = 0; i < _segs.length; i++) {      
    +                userProvidedSegments.push(_segs[i]);
    +                totalLength += _segs[i].getLength();			            
    +            }            
    +        };  
    +        
    +        var _prepareCompute = function(params) {
    +            self.lineWidth = params.lineWidth;
    +            var segment = jsPlumbUtil.segment(params.sourcePos, params.targetPos),
    +                swapX = params.targetPos[0] < params.sourcePos[0],
    +                swapY = params.targetPos[1] < params.sourcePos[1],
    +                lw = params.lineWidth || 1,       
    +                so = params.sourceEndpoint.anchor.orientation || params.sourceEndpoint.anchor.getOrientation(params.sourceEndpoint), 
    +                to = params.targetEndpoint.anchor.orientation || params.targetEndpoint.anchor.getOrientation(params.targetEndpoint),
    +                x = swapX ? params.targetPos[0] : params.sourcePos[0], 
    +                y = swapY ? params.targetPos[1] : params.sourcePos[1],
    +                w = Math.abs(params.targetPos[0] - params.sourcePos[0]),
    +                h = Math.abs(params.targetPos[1] - params.sourcePos[1]);
    +            
    +            // if either anchor does not have an orientation set, we derive one from their relative
    +            // positions.  we fix the axis to be the one in which the two elements are further apart, and
    +            // point each anchor at the other element.  this is also used when dragging a new connection.
    +            if (so[0] == 0 && so[1] == 0 || to[0] == 0 && to[1] == 0) {
    +                var index = w > h ? 0 : 1, oIndex = [1,0][index];
    +                so = []; to = [];
    +                so[index] = params.sourcePos[index] > params.targetPos[index] ? -1 : 1;
    +                to[index] = params.sourcePos[index] > params.targetPos[index] ? 1 : -1;
    +                so[oIndex] = 0; to[oIndex] = 0;
    +            }                    
    +            
    +            var sx = swapX ? w + (sourceGap * so[0])  : sourceGap * so[0], 
    +                sy = swapY ? h + (sourceGap * so[1])  : sourceGap * so[1], 
    +                tx = swapX ? targetGap * to[0] : w + (targetGap * to[0]),
    +                ty = swapY ? targetGap * to[1] : h + (targetGap * to[1]),
    +                oProduct = ((so[0] * to[0]) + (so[1] * to[1]));        
    +            
    +            var result = {
    +                sx:sx, sy:sy, tx:tx, ty:ty, lw:lw, 
    +                xSpan:Math.abs(tx - sx),
    +                ySpan:Math.abs(ty - sy),                
    +                mx:(sx + tx) / 2,
    +                my:(sy + ty) / 2,                
    +                so:so, to:to, x:x, y:y, w:w, h:h,
    +                segment : segment,
    +                startStubX : sx + (so[0] * sourceStub), 
    +                startStubY : sy + (so[1] * sourceStub),
    +                endStubX : tx + (to[0] * targetStub), 
    +                endStubY : ty + (to[1] * targetStub),
    +                isXGreaterThanStubTimes2 : Math.abs(sx - tx) > (sourceStub + targetStub),
    +                isYGreaterThanStubTimes2 : Math.abs(sy - ty) > (sourceStub + targetStub),
    +                opposite:oProduct == -1,
    +                perpendicular:oProduct == 0,
    +                orthogonal:oProduct == 1,
    +                sourceAxis : so[0] == 0 ? "y" : "x",
    +                points:[x, y, w, h, sx, sy, tx, ty ]
    +            };
    +            result.anchorOrientation = result.opposite ? "opposite" : result.orthogonal ? "orthogonal" : "perpendicular";
    +            return result;
    +        };
    +		
    +		this.getSegments = function() { return segments; };
    +
    +        self.updateBounds = function(segment) {
    +            var segBounds = segment.getBounds();
    +            self.bounds.minX = Math.min(self.bounds.minX, segBounds.minX);
    +            self.bounds.maxX = Math.max(self.bounds.maxX, segBounds.maxX);
    +            self.bounds.minY = Math.min(self.bounds.minY, segBounds.minY);
    +            self.bounds.maxY = Math.max(self.bounds.maxY, segBounds.maxY);              
    +        };
    +        
    +        var dumpSegmentsToConsole = function() {
    +            console.log("SEGMENTS:");
    +            for (var i = 0; i < segments.length; i++) {
    +                console.log(segments[i].type, segments[i].getLength(), segmentProportions[i]);
    +            }
    +        };
    +		
    +		this.pointOnPath = function(location, absolute) {
    +            //console.log("point on path", location);
    +			var seg = _findSegmentForLocation(location, absolute);		
    +            //console.log("point on path", location, seg);	
    +			return seg.segment.pointOnPath(seg.proportion, absolute);
    +		};
    +		
    +		this.gradientAtPoint = function(location) {
    +			var seg = _findSegmentForLocation(location, absolute);			
    +			return seg.segment.gradientAtPoint(seg.proportion, absolute);
    +		};
    +		
    +		this.pointAlongPathFrom = function(location, distance, absolute) {
    +			var seg = _findSegmentForLocation(location, absolute);
    +			// TODO what happens if this crosses to the next segment?
    +			return seg.segment.pointAlongPathFrom(seg.proportion, distance, absolute);
    +		};
    +		
    +		this.compute = function(params)  {
    +            if (!edited)
    +                paintInfo = _prepareCompute(params);
    +            
    +            _clearSegments();
    +            this._compute(paintInfo, params);
    +            self.x = paintInfo.points[0];
    +            self.y = paintInfo.points[1];
    +            self.w = paintInfo.points[2];
    +            self.h = paintInfo.points[3];               
    +            self.segment = paintInfo.segment;         
    +            _updateSegmentProportions();            
    +		};
    +		
    +		return {
    +			addSegment:_addSegment,
    +            prepareCompute:_prepareCompute,
    +            sourceStub:sourceStub,
    +            targetStub:targetStub,
    +            maxStub:Math.max(sourceStub, targetStub),            
    +            sourceGap:sourceGap,
    +            targetGap:targetGap,
    +            maxGap:Math.max(sourceGap, targetGap)
    +		};		
    +	};
    +	
    +    /**
    +     * Class: Connectors.Straight
    +     * The Straight connector draws a simple straight line between the two anchor points.  It does not have any constructor parameters.
    +     */
    +    jsPlumb.Connectors.Straight = function() {
    +    	this.type = "Straight";
    +		var _super =  jsPlumb.Connectors.AbstractConnector.apply(this, arguments);		
    +
    +        this._compute = function(paintInfo, _) {                        
    +            _super.addSegment("Straight", {x1:paintInfo.sx, y1:paintInfo.sy, x2:paintInfo.startStubX, y2:paintInfo.startStubY});                                                
    +            _super.addSegment("Straight", {x1:paintInfo.startStubX, y1:paintInfo.startStubY, x2:paintInfo.endStubX, y2:paintInfo.endStubY});                        
    +            _super.addSegment("Straight", {x1:paintInfo.endStubX, y1:paintInfo.endStubY, x2:paintInfo.tx, y2:paintInfo.ty});                                    
    +        };                    
    +    };
    +                    
    +    /**
    +     * Class:Connectors.Bezier
    +     * This Connector draws a Bezier curve with two control points.  You can provide a 'curviness' value which gets applied to jsPlumb's
    +     * internal voodoo machine and ends up generating locations for the two control points.  See the constructor documentation below.
    +     */
    +    /**
    +     * Function:Constructor
    +     * 
    +     * Parameters:
    +     * 	curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not 
    +     * actually touch the given point, but it has the tendency to lean towards it.  The larger this value, the greater the curve is pulled from a straight line.
    +     * Optional; defaults to 150.
    +     * stub - optional value for a distance to travel from the connector's endpoint before beginning the Bezier curve. defaults to 0.
    +     * 
    +     */
    +    jsPlumb.Connectors.Bezier = function(params) {
    +        params = params || {};
    +
    +    	var self = this,
    +			_super =  jsPlumb.Connectors.AbstractConnector.apply(this, arguments),
    +            stub = params.stub || 50,
    +            majorAnchor = params.curviness || 150,
    +            minorAnchor = 10;            
    +
    +        this.type = "Bezier";	
    +        this.getCurviness = function() { return majorAnchor; };	
    +        
    +        this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceEndpoint, targetEndpoint) {
    +        	// determine if the two anchors are perpendicular to each other in their orientation.  we swap the control 
    +        	// points around if so (code could be tightened up)
    +        	var soo = sourceEndpoint.anchor.getOrientation(sourceEndpoint), 
    +        		too = targetEndpoint.anchor.getOrientation(targetEndpoint),
    +        		perpendicular = soo[0] != too[0] || soo[1] == too[1],
    +            	p = [];                
    +            	
    +            if (!perpendicular) {
    +                if (soo[0] == 0) // X
    +                    p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + minorAnchor : point[0] - minorAnchor);
    +                else p.push(point[0] - (majorAnchor * soo[0]));
    +                                 
    +                if (soo[1] == 0) // Y
    +                	p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + minorAnchor : point[1] - minorAnchor);
    +                else p.push(point[1] + (majorAnchor * too[1]));
    +            }
    +             else {
    +                if (too[0] == 0) // X
    +                	p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + minorAnchor : point[0] - minorAnchor);
    +                else p.push(point[0] + (majorAnchor * too[0]));
    +                
    +                if (too[1] == 0) // Y
    +                	p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + minorAnchor : point[1] - minorAnchor);
    +                else p.push(point[1] + (majorAnchor * soo[1]));
    +             }
    +
    +            return p;                
    +        };        
    +
    +        this._compute = function(paintInfo, p) {                                
    +			var sp = p.sourcePos,
    +				tp = p.targetPos,				
    +                _w = Math.abs(sp[0] - tp[0]),
    +                _h = Math.abs(sp[1] - tp[1]),            
    +                _sx = sp[0] < tp[0] ? _w : 0,
    +                _sy = sp[1] < tp[1] ? _h : 0,
    +                _tx = sp[0] < tp[0] ? 0 : _w,
    +                _ty = sp[1] < tp[1] ? 0 : _h,
    +                _CP = self._findControlPoint([_sx, _sy], sp, tp, p.sourceEndpoint, p.targetEndpoint),
    +                _CP2 = self._findControlPoint([_tx, _ty], tp, sp, p.targetEndpoint, p.sourceEndpoint);
    +
    +			_super.addSegment("Bezier", {
    +				x1:_sx, y1:_sy, x2:_tx, y2:_ty,
    +				cp1x:_CP[0], cp1y:_CP[1], cp2x:_CP2[0], cp2y:_CP2[1]
    +			});                    
    +        };               
    +    };        
    +    
    + // ********************************* END OF CONNECTOR TYPES *******************************************************************
    +    
    + // ********************************* ENDPOINT TYPES *******************************************************************
    +    
    +    jsPlumb.Endpoints.AbstractEndpoint = function(params) {
    +        AbstractComponent.apply(this, arguments);
    +        var self = this;    
    +        this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {    
    +            var out = self._compute.apply(self, arguments);
    +            self.x = out[0];
    +            self.y = out[1];
    +            self.w = out[2];
    +            self.h = out[3];
    +            self.bounds.minX = self.x;
    +            self.bounds.minY = self.y;
    +            self.bounds.maxX = self.x + self.w;
    +            self.bounds.maxY = self.y + self.h;
    +            return out;
    +        };
    +        return {
    +            compute:self.compute,
    +            cssClass:params.cssClass
    +        };
    +    };
    +    
    +    /**
    +     * Class: Endpoints.Dot
    +     * A round endpoint, with default radius 10 pixels.
    +     */    	
    +    	
    +	/**
    +	 * Function: Constructor
    +	 * 
    +	 * Parameters:
    +	 * 
    +	 * 	radius	-	radius of the endpoint.  defaults to 10 pixels.
    +	 */
    +	jsPlumb.Endpoints.Dot = function(params) {        
    +		this.type = "Dot";
    +		var self = this,
    +            _super = jsPlumb.Endpoints.AbstractEndpoint.apply(this, arguments);
    +		params = params || {};				
    +		this.radius = params.radius || 10;
    +		this.defaultOffset = 0.5 * this.radius;
    +		this.defaultInnerRadius = this.radius / 3;			
    +		
    +		this._compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
    +			self.radius = endpointStyle.radius || self.radius;
    +			var	x = anchorPoint[0] - self.radius,
    +				y = anchorPoint[1] - self.radius,
    +                w = self.radius * 2,
    +                h = self.radius * 2;
    +
    +            if (endpointStyle.strokeStyle) {
    +                var lw = endpointStyle.lineWidth || 1;
    +                x -= lw;
    +                y -= lw;
    +                w += (lw * 2);
    +                h += (lw * 2);
    +            }
    +			return [ x, y, w, h, self.radius ];
    +		};
    +	};
    +	
    +	/**
    +	 * Class: Endpoints.Rectangle
    +	 * A Rectangular Endpoint, with default size 20x20.
    +	 */
    +	/**
    +	 * Function: Constructor
    +	 * 
    +	 * Parameters:
    +	 * 
    +	 * 	width	- width of the endpoint. defaults to 20 pixels.
    +	 * 	height	- height of the endpoint. defaults to 20 pixels.	
    +	 */
    +	jsPlumb.Endpoints.Rectangle = function(params) {
    +		this.type = "Rectangle";
    +		var self = this,
    +            _super = jsPlumb.Endpoints.AbstractEndpoint.apply(this, arguments);
    +		params = params || {};
    +		this.width = params.width || 20;
    +		this.height = params.height || 20;
    +		
    +		this._compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
    +			var width = endpointStyle.width || self.width,
    +				height = endpointStyle.height || self.height,
    +				x = anchorPoint[0] - (width/2),
    +				y = anchorPoint[1] - (height/2);
    +                
    +			return [ x, y, width, height];
    +		};
    +	};
    +	
    +
    +    var DOMElementEndpoint = function(params) {
    +        jsPlumb.DOMElementComponent.apply(this, arguments);
    +        var self = this;
    +
    +        var displayElements = [  ];
    +        this.getDisplayElements = function() { 
    +            return displayElements; 
    +        };
    +        
    +        this.appendDisplayElement = function(el) {
    +            displayElements.push(el);
    +        };            
    +    };
    +	/**
    +	 * Class: Endpoints.Image
    +	 * Draws an image as the Endpoint.
    +	 */
    +	/**
    +	 * Function: Constructor
    +	 * 
    +	 * Parameters:
    +	 * 
    +	 * 	src	-	location of the image to use.
    +	 */
    +	jsPlumb.Endpoints.Image = function(params) {
    +				
    +		this.type = "Image";
    +		DOMElementEndpoint.apply(this, arguments);
    +		
    +		var self = this,
    +            _super = jsPlumb.Endpoints.AbstractEndpoint.apply(this, arguments), 
    +			initialized = false,
    +			deleted = false,
    +			widthToUse = params.width,
    +			heightToUse = params.height,
    +            _onload = null,
    +            _endpoint = params.endpoint;
    +			
    +		this.img = new Image();
    +		self.ready = false;
    +
    +		this.img.onload = function() {
    +			self.ready = true;
    +			widthToUse = widthToUse || self.img.width;
    +			heightToUse = heightToUse || self.img.height;
    +            if (_onload) {
    +                _onload(self);
    +            }
    +		};
    +
    +        /*
    +            Function: setImage
    +            Sets the Image to use in this Endpoint.  
    +
    +            Parameters:
    +            img         -   may be a URL or an Image object
    +            onload      -   optional; a callback to execute once the image has loaded.
    +        */
    +        _endpoint.setImage = function(img, onload) {
    +            var s = img.constructor == String ? img : img.src;
    +            _onload = onload;
    +            self.img.src = img;
    +
    +            if (self.canvas != null)
    +                self.canvas.setAttribute("src", img);
    +        };
    +
    +        _endpoint.setImage(params.src || params.url, params.onload);
    +
    +		this._compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
    +			self.anchorPoint = anchorPoint;
    +			if (self.ready) return [anchorPoint[0] - widthToUse / 2, anchorPoint[1] - heightToUse / 2, 
    +									widthToUse, heightToUse];
    +			else return [0,0,0,0];
    +		};
    +		
    +		self.canvas = document.createElement("img"), initialized = false;
    +		self.canvas.style["margin"] = 0;
    +		self.canvas.style["padding"] = 0;
    +		self.canvas.style["outline"] = 0;
    +		self.canvas.style["position"] = "absolute";
    +		var clazz = params.cssClass ? " " + params.cssClass : "";
    +		self.canvas.className = jsPlumb.endpointClass + clazz;
    +		if (widthToUse) self.canvas.setAttribute("width", widthToUse);
    +		if (heightToUse) self.canvas.setAttribute("height", heightToUse);		
    +		jsPlumb.appendElement(self.canvas, params.parent);
    +		self.attachListeners(self.canvas, self);
    +		
    +		self.cleanup = function() {
    +			deleted = true;
    +		};
    +		
    +		var actuallyPaint = function(d, style, anchor) {
    +			if (!deleted) {
    +				if (!initialized) {
    +					self.canvas.setAttribute("src", self.img.src);
    +					self.appendDisplayElement(self.canvas);
    +					initialized = true;
    +				}
    +				var x = self.anchorPoint[0] - (widthToUse / 2),
    +					y = self.anchorPoint[1] - (heightToUse / 2);
    +				jsPlumb.sizeCanvas(self.canvas, x, y, widthToUse, heightToUse);
    +			}
    +		};
    +		
    +		this.paint = function(style, anchor) {
    +			if (self.ready) {
    +    			actuallyPaint(style, anchor);
    +			}
    +			else { 
    +				window.setTimeout(function() {    					
    +					self.paint(style, anchor);
    +				}, 200);
    +			}
    +		};				
    +	};
    +	
    +	/*
    +	 * Class: Endpoints.Blank
    +	 * An Endpoint that paints nothing (visible) on the screen.  Supports cssClass and hoverClass parameters like all Endpoints.
    +	 */
    +	jsPlumb.Endpoints.Blank = function(params) {
    +		var self = this,
    +            _super = jsPlumb.Endpoints.AbstractEndpoint.apply(this, arguments);
    +		this.type = "Blank";
    +		DOMElementEndpoint.apply(this, arguments);		
    +		this._compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
    +			return [anchorPoint[0], anchorPoint[1],10,0];
    +		};
    +		
    +		self.canvas = document.createElement("div");
    +		self.canvas.style.display = "block";
    +		self.canvas.style.width = "1px";
    +		self.canvas.style.height = "1px";
    +		self.canvas.style.background = "transparent";
    +		self.canvas.style.position = "absolute";
    +		self.canvas.className = self._jsPlumb.endpointClass;
    +		jsPlumb.appendElement(self.canvas, params.parent);
    +		
    +		this.paint = function(style, anchor) {
    +			jsPlumb.sizeCanvas(self.canvas, self.x, self.y, self.w, self.h);	
    +		};
    +	};
    +	
    +	/*
    +	 * Class: Endpoints.Triangle
    +	 * A triangular Endpoint.  
    +	 */
    +	/*
    +	 * Function: Constructor
    +	 * 
    +	 * Parameters:
    +	 * 
    +	 * 	width	-	width of the triangle's base.  defaults to 55 pixels.
    +	 * 	height	-	height of the triangle from base to apex.  defaults to 55 pixels.
    +	 */
    +	jsPlumb.Endpoints.Triangle = function(params) {        
    +		this.type = "Triangle";
    +        var self = this,
    +            _super = jsPlumb.Endpoints.AbstractEndpoint.apply(this, arguments);
    +		params = params || {  };
    +		params.width = params.width || 55;
    +		params.height = params.height || 55;
    +		this.width = params.width;
    +		this.height = params.height;
    +		this._compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
    +			var width = endpointStyle.width || self.width,
    +			height = endpointStyle.height || self.height,
    +			x = anchorPoint[0] - (width/2),
    +			y = anchorPoint[1] - (height/2);
    +			return [ x, y, width, height ];
    +		};
    +	};
    +// ********************************* END OF ENDPOINT TYPES *******************************************************************
    +	
    +
    +// ********************************* OVERLAY DEFINITIONS ***********************************************************************    
    +
    +	var AbstractOverlay = jsPlumb.Overlays.AbstractOverlay = function(params) {
    +		var visible = true, self = this;
    +        this.isAppendedAtTopLevel = true;
    +		this.component = params.component;
    +		this.loc = params.location == null ? 0.5 : params.location;
    +        this.endpointLoc = params.endpointLocation == null ? [ 0.5, 0.5] : params.endpointLocation;
    +		this.setVisible = function(val) { 
    +			visible = val;
    +			self.component.repaint();
    +		};
    +    	this.isVisible = function() { return visible; };
    +    	this.hide = function() { self.setVisible(false); };
    +    	this.show = function() { self.setVisible(true); };
    +    	
    +    	this.incrementLocation = function(amount) {
    +    		self.loc += amount;
    +    		self.component.repaint();
    +    	};
    +    	this.setLocation = function(l) {
    +    		self.loc = l;
    +    		self.component.repaint();
    +    	};
    +    	this.getLocation = function() {
    +    		return self.loc;
    +    	};
    +	};
    +	
    +	
    +	/*
    +	 * Class: Overlays.Arrow
    +	 * 
    +	 * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length
    +	 * of the arrow that lines from each tail point converge into.  The foldback point is defined using a decimal that indicates some fraction
    +	 * of the length of the arrow and has a default value of 0.623.  A foldback point value of 1 would mean that the arrow had a straight line
    +	 * across the tail.  
    +	 */
    +	/*
    +	 * Function: Constructor
    +	 * 
    +	 * Parameters:
    +	 * 
    +	 * 	length - distance in pixels from head to tail baseline. default 20.
    +	 * 	width - width in pixels of the tail baseline. default 20.
    +	 * 	fillStyle - style to use when filling the arrow.  defaults to "black".
    +	 * 	strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked.
    +	 * 	lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null.
    +	 * 	foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to.  defaults to 0.623.
    +	 * 	location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5.
    +	 * 	direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default.
    +	 */
    +	jsPlumb.Overlays.Arrow = function(params) {
    +		this.type = "Arrow";
    +		AbstractOverlay.apply(this, arguments);
    +        this.isAppendedAtTopLevel = false;
    +		params = params || {};
    +		var self = this, _ju = jsPlumbUtil;
    +		
    +    	this.length = params.length || 20;
    +    	this.width = params.width || 20;
    +    	this.id = params.id;
    +    	var direction = (params.direction || 1) < 0 ? -1 : 1,
    +    	    paintStyle = params.paintStyle || { lineWidth:1 },
    +    	    // how far along the arrow the lines folding back in come to. default is 62.3%.
    +    	    foldback = params.foldback || 0.623;
    +    	    	
    +    	this.computeMaxSize = function() { return self.width * 1.5; };    	
    +    	this.cleanup = function() { };  // nothing to clean up for Arrows    
    +    	this.draw = function(component, currentConnectionPaintStyle) {
    +
    +            var hxy, mid, txy, tail, cxy;
    +            if (component.pointAlongPathFrom) {
    +
    +                if (_ju.isString(self.loc) || self.loc > 1 || self.loc < 0) {                    
    +                    var l = parseInt(self.loc);
    +                    hxy = component.pointAlongPathFrom(l, direction * self.length / 2, true),
    +                    mid = component.pointOnPath(l, true),
    +                    txy = _ju.pointOnLine(hxy, mid, self.length);
    +                }
    +                else if (self.loc == 1) {                
    +					hxy = component.pointOnPath(self.loc);
    +					mid = component.pointAlongPathFrom(self.loc, -1);                    
    +					txy = _ju.pointOnLine(hxy, mid, self.length);
    +					
    +					if (direction == -1) {
    +						var _ = txy;
    +						txy = hxy;
    +						hxy = _;
    +					}
    +                }
    +                else if (self.loc == 0) {					                    
    +					txy = component.pointOnPath(self.loc);                    
    +					mid = component.pointAlongPathFrom(self.loc, 1);                    
    +					hxy = _ju.pointOnLine(txy, mid, self.length);                    
    +					if (direction == -1) {
    +						var _ = txy;
    +						txy = hxy;
    +						hxy = _;
    +					}
    +                }
    +                else {                    
    +    			    hxy = component.pointAlongPathFrom(self.loc, direction * self.length / 2),
    +                    mid = component.pointOnPath(self.loc),
    +                    txy = _ju.pointOnLine(hxy, mid, self.length);
    +                }
    +
    +                tail = _ju.perpendicularLineTo(hxy, txy, self.width);
    +                cxy = _ju.pointOnLine(hxy, txy, foldback * self.length);    			
    +    			
    +    			var d = { hxy:hxy, tail:tail, cxy:cxy },
    +    			    strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle,
    +    			    fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle,
    +    			    lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth,
    +                    info = {
    +                        component:component, 
    +                        d:d, 
    +                        lineWidth:lineWidth, 
    +                        strokeStyle:strokeStyle, 
    +                        fillStyle:fillStyle,
    +                        minX:Math.min(hxy.x, tail[0].x, tail[1].x),
    +                        maxX:Math.max(hxy.x, tail[0].x, tail[1].x),
    +                        minY:Math.min(hxy.y, tail[0].y, tail[1].y),
    +                        maxY:Math.max(hxy.y, tail[0].y, tail[1].y)
    +                    };    			
    +						    
    +                return info;
    +            }
    +            else return {component:component, minX:0,maxX:0,minY:0,maxY:0};
    +    	};
    +    };          
    +    
    +    /*
    +     * Class: Overlays.PlainArrow
    +	 * 
    +	 * A basic arrow.  This is in fact just one instance of the more generic case in which the tail folds back on itself to some
    +	 * point along the length of the arrow: in this case, that foldback point is the full length of the arrow.  so it just does
    +	 * a 'call' to Arrow with foldback set appropriately.       
    +	 */
    +    /*
    +     * Function: Constructor
    +     * See <Overlays.Arrow> for allowed parameters for this overlay.
    +     */
    +    jsPlumb.Overlays.PlainArrow = function(params) {
    +    	params = params || {};    	
    +    	var p = jsPlumb.extend(params, {foldback:1});
    +    	jsPlumb.Overlays.Arrow.call(this, p);
    +    	this.type = "PlainArrow";
    +    };
    +        
    +    /*
    +     * Class: Overlays.Diamond
    +     * 
    +	 * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just
    +	 * happens that in this case, that point is greater than the length of the the arrow.    
    +	 * 
    +	 *      this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the
    +	 *      center is actually 1/4 of the way along for this guy.  but we don't have any knowledge of pixels at this point, so we're kind of
    +	 *      stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value
    +	 *      would be -l/4 in this case - move along one quarter of the total length.
    +	 */
    +    /*
    +     * Function: Constructor
    +     * See <Overlays.Arrow> for allowed parameters for this overlay.
    +     */
    +    jsPlumb.Overlays.Diamond = function(params) {
    +    	params = params || {};    	
    +    	var l = params.length || 40,
    +    	    p = jsPlumb.extend(params, {length:l/2, foldback:2});
    +    	jsPlumb.Overlays.Arrow.call(this, p);
    +    	this.type = "Diamond";
    +    };
    +    
    +	
    +	// abstract superclass for overlays that add an element to the DOM.
    +    var AbstractDOMOverlay = function(params) {
    +		jsPlumb.DOMElementComponent.apply(this, arguments);
    +    	AbstractOverlay.apply(this, arguments);
    +		
    +		var self = this, initialised = false, jpcl = jsPlumb.CurrentLibrary;
    +		params = params || {};
    +		this.id = params.id;
    +		var div;
    +		
    +		var makeDiv = function() {
    +			div = params.create(params.component);
    +			div = jpcl.getDOMElement(div);
    +			div.style["position"] 	= 	"absolute";    	
    +			var clazz = params["_jsPlumb"].overlayClass + " " + 
    +				(self.cssClass ? self.cssClass : 
    +				params.cssClass ? params.cssClass : "");    	
    +			div.className =	clazz;
    +			params["_jsPlumb"].appendElement(div, params.component.parent);
    +			params["_jsPlumb"].getId(div);		
    +	    	self.attachListeners(div, self);
    +	    	self.canvas = div;
    +		};
    +		
    +		this.getElement = function() {
    +			if (div == null) {
    +				makeDiv();
    +			}
    +    		return div;
    +    	};
    +		
    +		this.getDimensions = function() {
    +    		return jpcl.getSize(jpcl.getElementObject(self.getElement()));
    +    	};
    +		
    +		var cachedDimensions = null,
    +			_getDimensions = function(component) {
    +				if (cachedDimensions == null)
    +					cachedDimensions = self.getDimensions();
    +				return cachedDimensions;
    +			};
    +		
    +		/*
    +		 * Function: clearCachedDimensions
    +		 * Clears the cached dimensions for the label. As a performance enhancement, label dimensions are
    +		 * cached from 1.3.12 onwards. The cache is cleared when you change the label text, of course, but
    +		 * there are other reasons why the text dimensions might change - if you make a change through CSS, for
    +		 * example, you might change the font size.  in that case you should explicitly call this method.
    +		 */
    +		this.clearCachedDimensions = function() {
    +			cachedDimensions = null;
    +		};
    +		
    +		this.computeMaxSize = function() {
    +    		var td = _getDimensions();
    +			return Math.max(td[0], td[1]);
    +    	}; 
    +		
    +		//override setVisible
    +    	var osv = self.setVisible;
    +    	self.setVisible = function(state) {
    +    		osv(state); // call superclass
    +    		div.style.display = state ? "block" : "none";
    +    	};
    +		
    +		this.cleanup = function() {
    +    		if (div != null) jpcl.removeElement(div);
    +    	};
    +		
    +		this.paint = function(params, containerExtents) {
    +			if (!initialised) {
    +				self.getElement();
    +				params.component.appendDisplayElement(div);
    +				self.attachListeners(div, params.component);
    +				initialised = true;
    +			}
    +			div.style.left = (params.component.x + params.d.minx) + "px";
    +			div.style.top = (params.component.y + params.d.miny) + "px";			
    +    	};
    +				
    +		this.draw = function(component, currentConnectionPaintStyle) {
    +	    	var td = _getDimensions();
    +	    	if (td != null && td.length == 2) {
    +				var cxy = {x:0,y:0};
    +                if (component.pointOnPath) {
    +                    var loc = self.loc, absolute = false;
    +                    if (jsPlumbUtil.isString(self.loc) || self.loc < 0 || self.loc > 1) {
    +                        loc = parseInt(self.loc);
    +                        absolute = true;
    +                    }
    +                    cxy = component.pointOnPath(loc, absolute);  // a connection
    +                }
    +                else {
    +                    var locToUse = self.loc.constructor == Array ? self.loc : self.endpointLoc;
    +                    cxy = { x:locToUse[0] * component.w,
    +                            y:locToUse[1] * component.h };      
    +                } 
    +                           
    +				var minx = cxy.x - (td[0] / 2),
    +				    miny = cxy.y - (td[1] / 2);
    +
    +                return {
    +                    component:component, 
    +                    d:{ minx:minx, miny:miny, td:td, cxy:cxy },
    +                    minX:minx, 
    +                    maxX:minx + td[0], 
    +                    minY:miny, 
    +                    maxY:miny + td[1]
    +                };								
    +        	}
    +	    	else return {minX:0,maxX:0,minY:0,maxY:0};
    +	    };
    +	    
    +	    this.reattachListeners = function(connector) {
    +	    	if (div) {
    +	    		self.reattachListenersForElement(div, self, connector);
    +	    	}
    +	    };
    +		
    +	};
    +	
    +	/*
    +     * Class: Overlays.Custom
    +     * A Custom overlay. You supply a 'create' function which returns some DOM element, and jsPlumb positions it.
    +     * The 'create' function is passed a Connection or Endpoint.
    +     */
    +    /*
    +     * Function: Constructor
    +     * 
    +     * Parameters:
    +     * 	create - function for jsPlumb to call that returns a DOM element.
    +     * 	location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5.
    +     * 	id - optional id to use for later retrieval of this overlay.
    +     * 	
    +     */
    +    jsPlumb.Overlays.Custom = function(params) {
    +    	this.type = "Custom";    	
    +    	AbstractDOMOverlay.apply(this, arguments);		    	        		    	    		
    +    };
    +
    +    jsPlumb.Overlays.GuideLines = function() {
    +        var self = this;
    +        self.length = 50;
    +        self.lineWidth = 5;
    +        this.type = "GuideLines";
    +        AbstractOverlay.apply(this, arguments);
    +        jsPlumb.jsPlumbUIComponent.apply(this, arguments);
    +        this.draw = function(connector, currentConnectionPaintStyle) {
    +
    +            var head = connector.pointAlongPathFrom(self.loc, self.length / 2),
    +                mid = connector.pointOnPath(self.loc),
    +                tail = jsPlumbUtil.pointOnLine(head, mid, self.length),
    +                tailLine = jsPlumbUtil.perpendicularLineTo(head, tail, 40),
    +                headLine = jsPlumbUtil.perpendicularLineTo(tail, head, 20);
    +
    +            return {
    +                connector:connector,
    +                head:head,
    +                tail:tail,
    +                headLine:headLine,
    +                tailLine:tailLine,                
    +                minX:Math.min(head.x, tail.x, headLine[0].x, headLine[1].x), 
    +                minY:Math.min(head.y, tail.y, headLine[0].y, headLine[1].y), 
    +                maxX:Math.max(head.x, tail.x, headLine[0].x, headLine[1].x), 
    +                maxY:Math.max(head.y, tail.y, headLine[0].y, headLine[1].y)
    +            };
    +        };
    +
    +        this.cleanup = function() { };  // nothing to clean up for GuideLines
    +    };
    +    
    +    /*
    +     * Class: Overlays.Label
    +     * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV.  Version 1.3.0 of jsPlumb
    +     * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label.  The 'labelStyle' parameter
    +     * is still supported in 1.3.0 but its usage is deprecated.  Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it 
    +     * puts on the Label's 'style' attribute, so the end result is the same. 
    +     */
    +    /*
    +     * Function: Constructor
    +     * 
    +     * Parameters:
    +     * 	cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes
    +     *             defined.  This parameter is preferred to using labelStyle, borderWidth and borderStyle.
    +     * 	label - the label to paint.  May be a string or a function that returns a string.  Nothing will be painted if your label is null or your
    +     *         label function returns null.  empty strings _will_ be painted.
    +     * 	location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5.
    +     * 	id - optional id to use for later retrieval of this overlay.
    +     * 	
    +     */
    +    jsPlumb.Overlays.Label = function(params) {
    +		var self = this;    	
    +		this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle;
    +		this.cssClass = this.labelStyle != null ? this.labelStyle.cssClass : null;
    +		params.create = function() {
    +			return document.createElement("div");
    +		};
    +    	jsPlumb.Overlays.Custom.apply(this, arguments);
    +		this.type = "Label";
    +    	
    +        var label = params.label || "",
    +            self = this,    	    
    +            labelText = null;
    +    	
    +    	/*
    +    	 * Function: setLabel
    +    	 * sets the label's, um, label.  you would think i'd call this function
    +    	 * 'setText', but you can pass either a Function or a String to this, so
    +    	 * it makes more sense as 'setLabel'. This uses innerHTML on the label div, so keep
    +         * that in mind if you need escaped HTML.
    +    	 */
    +    	this.setLabel = function(l) {
    +    		label = l;
    +    		labelText = null;
    +			self.clearCachedDimensions();
    +			_update();
    +    		self.component.repaint();
    +    	};
    +    	
    +		var _update = function() {
    +			if (typeof label == "function") {
    +    			var lt = label(self);
    +    			self.getElement().innerHTML = lt.replace(/\r\n/g, "<br/>");
    +    		}
    +    		else {
    +    			if (labelText == null) {
    +    				labelText = label;
    +    				self.getElement().innerHTML = labelText.replace(/\r\n/g, "<br/>");
    +    			}
    +    		}
    +		};
    +		
    +    	this.getLabel = function() {
    +    		return label;
    +    	};
    +    	
    +		var superGD = this.getDimensions;		
    +		this.getDimensions = function() {				
    +    		_update();
    +			return superGD();
    +    	};
    +		
    +    };
    +		
    +
    + // ********************************* END OF OVERLAY DEFINITIONS ***********************************************************************
    +    
    +})();
    \ No newline at end of file
    diff --git a/src/jsPlumb-dom-adapter.js b/src/jsPlumb-dom-adapter.js
    new file mode 100644
    index 000000000..cefe85eb4
    --- /dev/null
    +++ b/src/jsPlumb-dom-adapter.js
    @@ -0,0 +1,236 @@
    +/*
    + * jsPlumb
    + * 
    + * Title:jsPlumb 1.4.0
    + * 
    + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
    + * elements, or VML.  
    + * 
    + * This file contains the base functionality for DOM type adapters. 
    + *
    + * Copyright (c) 2010 - 2013 Simon Porritt (http://jsplumb.org)
    + * 
    + * http://jsplumb.org
    + * http://github.com/sporritt/jsplumb
    + * http://code.google.com/p/jsplumb
    + * 
    + * Dual licensed under the MIT and GPL2 licenses.
    + */
    +;(function() {
    +    
    +		var canvasAvailable = !!document.createElement('canvas').getContext,
    +		svgAvailable = !!window.SVGAngle || document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"),
    +		// http://stackoverflow.com/questions/654112/how-do-you-detect-support-for-vml-or-svg-in-a-browser
    +		vmlAvailable = function() {		    
    +            if (vmlAvailable.vml == undefined) { 
    +                    var a = document.body.appendChild(document.createElement('div'));
    +            a.innerHTML = '<v:shape id="vml_flag1" adj="1" />';
    +            var b = a.firstChild;
    +            b.style.behavior = "url(#default#VML)";
    +            vmlAvailable.vml = b ? typeof b.adj == "object": true;
    +            a.parentNode.removeChild(a);
    +            }
    +            return vmlAvailable.vml;
    +		};
    +        
    +    /**
    +		Manages dragging for some instance of jsPlumb.
    +	*/
    +	var DragManager = function(_currentInstance) {		
    +		var _draggables = {}, _dlist = [], _delements = {}, _elementsWithEndpoints = {},			
    +			// elementids mapped to the draggable to which they belong.
    +			_draggablesForElements = {};
    +
    +        /**
    +            register some element as draggable.  right now the drag init stuff is done elsewhere, and it is
    +            possible that will continue to be the case.
    +        */
    +		this.register = function(el) {
    +            var jpcl = jsPlumb.CurrentLibrary;
    +            el = jpcl.getElementObject(el);
    +            var id = _currentInstance.getId(el),
    +                domEl = jpcl.getDOMElement(el),
    +                parentOffset = jpcl.getOffset(el);
    +                    
    +            if (!_draggables[id]) {
    +                _draggables[id] = el;
    +                _dlist.push(el);
    +                _delements[id] = {};
    +            }
    +				
    +			// look for child elements that have endpoints and register them against this draggable.
    +			var _oneLevel = function(p, startOffset) {
    +                if (p) {											
    +                    for (var i = 0; i < p.childNodes.length; i++) {
    +                        if (p.childNodes[i].nodeType != 3 && p.childNodes[i].nodeType != 8) {
    +                            var cEl = jpcl.getElementObject(p.childNodes[i]),
    +                                cid = _currentInstance.getId(cEl, null, true);
    +                            if (cid && _elementsWithEndpoints[cid] && _elementsWithEndpoints[cid] > 0) {
    +                                var cOff = jpcl.getOffset(cEl);
    +                                _delements[id][cid] = {
    +                                    id:cid,
    +                                    offset:{
    +                                        left:cOff.left - parentOffset.left,
    +                                        top:cOff.top - parentOffset.top
    +                                    }
    +                                };
    +                                _draggablesForElements[cid] = id;
    +                            }
    +                            _oneLevel(p.childNodes[i]);
    +                        }	
    +                    }
    +                }
    +			};
    +
    +			_oneLevel(domEl);
    +		};
    +		
    +		// refresh the offsets for child elements of this element. 
    +		this.updateOffsets = function(elId) {
    +			var jpcl = jsPlumb.CurrentLibrary,
    +				el = jpcl.getElementObject(elId),
    +				id = _currentInstance.getId(el),
    +				children = _delements[id],
    +				parentOffset = jpcl.getOffset(el);
    +				
    +			if (children) {
    +				for (var i in children) {
    +					var cel = jpcl.getElementObject(i),
    +						cOff = jpcl.getOffset(cel);
    +						
    +					_delements[id][i] = {
    +						id:i,
    +						offset:{
    +							left:cOff.left - parentOffset.left,
    +							top:cOff.top - parentOffset.top
    +						}
    +					};
    +					_draggablesForElements[i] = id;
    +				}
    +			}
    +		};
    +
    +		/**
    +			notification that an endpoint was added to the given el.  we go up from that el's parent
    +			node, looking for a parent that has been registered as a draggable. if we find one, we add this
    +			el to that parent's list of elements to update on drag (if it is not there already)
    +		*/
    +		this.endpointAdded = function(el) {
    +			var jpcl = jsPlumb.CurrentLibrary, b = document.body, id = _currentInstance.getId(el), c = jpcl.getDOMElement(el), 
    +				p = c.parentNode, done = p == b;
    +
    +			_elementsWithEndpoints[id] = _elementsWithEndpoints[id] ? _elementsWithEndpoints[id] + 1 : 1;
    +
    +			while (p != null && p != b) {
    +				var pid = _currentInstance.getId(p, null, true);
    +				if (pid && _draggables[pid]) {
    +					var idx = -1, pEl = jpcl.getElementObject(p), pLoc = jpcl.getOffset(pEl);
    +					
    +					if (_delements[pid][id] == null) {
    +						var cLoc = jsPlumb.CurrentLibrary.getOffset(el);
    +						_delements[pid][id] = {
    +							id:id,
    +							offset:{
    +								left:cLoc.left - pLoc.left,
    +								top:cLoc.top - pLoc.top
    +							}
    +						};
    +						_draggablesForElements[id] = pid;
    +					}
    +					break;
    +				}
    +				p = p.parentNode;
    +			}	
    +		};
    +
    +		this.endpointDeleted = function(endpoint) {
    +			if (_elementsWithEndpoints[endpoint.elementId]) {
    +				_elementsWithEndpoints[endpoint.elementId]--;
    +				if (_elementsWithEndpoints[endpoint.elementId] <= 0) {
    +					for (var i in _delements) {
    +						if (_delements[i]) {
    +                            delete _delements[i][endpoint.elementId];
    +                            delete _draggablesForElements[endpoint.elementId];
    +                        }
    +					}
    +				}
    +			}		
    +		};	
    +		
    +		this.changeId = function(oldId, newId) {				
    +			_delements[newId] = _delements[oldId];			
    +			_delements[oldId] = {};
    +			_draggablesForElements[newId] = _draggablesForElements[oldId];
    +			_draggablesForElements[oldId] = null;			
    +		};
    +
    +		this.getElementsForDraggable = function(id) {
    +			return _delements[id];	
    +		};
    +
    +		this.elementRemoved = function(elementId) {
    +			var elId = _draggablesForElements[elementId];
    +			if (elId) {
    +				delete _delements[elId][elementId];
    +				delete _draggablesForElements[elementId];
    +			}
    +		};
    +
    +		this.reset = function() {
    +			_draggables = {};
    +			_dlist = [];
    +			_delements = {};
    +			_elementsWithEndpoints = {};
    +		};
    +		
    +	};
    +        
    +    // for those browsers that dont have it.  they still don't have it! but at least they won't crash.
    +	if (!window.console)
    +		window.console = { time:function(){}, timeEnd:function(){}, group:function(){}, groupEnd:function(){}, log:function(){} };
    +            
    +    window.jsPlumbAdapter = {
    +        
    +        headless:false,
    +        
    +        appendToRoot : function(node) {
    +            document.body.appendChild(node);
    +        },
    +        getRenderModes : function() {
    +            return [ "canvas", "svg", "vml" ]
    +        },
    +        isRenderModeAvailable : function(m) {
    +            return {
    +                "canvas":canvasAvailable,
    +                "svg":svgAvailable,
    +                "vml":vmlAvailable()
    +            }[m];
    +        },
    +        getDragManager : function(_jsPlumb) {
    +            return new DragManager(_jsPlumb);
    +        },
    +        setRenderMode : function(mode) {
    +            var renderMode;
    +            
    +            if (mode) {
    +				mode = mode.toLowerCase();            
    +			            
    +                var canvasAvailable = this.isRenderModeAvailable("canvas"),
    +                    svgAvailable = this.isRenderModeAvailable("svg"),
    +                    vmlAvailable = this.isRenderModeAvailable("vml");
    +                
    +                // now test we actually have the capability to do this.						
    +                if (mode === "svg") {
    +                    if (svgAvailable) renderMode = "svg"
    +                    else if (canvasAvailable) renderMode = "canvas"
    +                    else if (vmlAvailable) renderMode = "vml"
    +                }
    +                else if (mode === "canvas" && canvasAvailable) renderMode = "canvas";
    +                else if (vmlAvailable) renderMode = "vml";
    +            }
    +
    +			return renderMode;
    +        }
    +    };
    +    
    +})();
    \ No newline at end of file
    diff --git a/src/jsPlumb-drag.js b/src/jsPlumb-drag.js
    new file mode 100644
    index 000000000..d71db029e
    --- /dev/null
    +++ b/src/jsPlumb-drag.js
    @@ -0,0 +1,61 @@
    +/*
    + * this is experimental and probably will not be used.  solutions exist for most libraries.  but of course if
    + * i want to support multiple scopes at some stage then i will have to do dragging inside jsPlumb.
    + */ 
    +;(function() {
    +    
    +    window.jsPlumbDrag = function(_jsPlumb) {
    +      
    +        var ta = new TouchAdapter();
    +        
    +        this.draggable = function(selector) {
    +            var el, elId, da = [], elo, d = false,
    +                isInSelector = function(el) {
    +                    if (typeof selector == "string")
    +                        return selector === _jsPlumb.getId(el);
    +                    
    +                    for (var i = 0; i < selector.length; i++) {
    +                        var _sel = jsPlumb.CurrentLibrary.getDOMElement(selector[i]);
    +                        if (_sel == el) return true;
    +                    }
    +                    return false;
    +                };
    +				
    +			ta.bind(document, "mousedown", function(e) {
    +                var target = e.target || e.srcElement;
    +                if (isInSelector(target)) {
    +                    el = jsPlumb.CurrentLibrary.getElementObject(target);
    +                    elId = _jsPlumb.getId(el);
    +                    elo = jsPlumb.CurrentLibrary.getOffset(el);
    +                    da = [e.pageX, e.pageY];
    +                    d = true;
    +                }
    +			});
    +			
    +			ta.bind(document, "mousemove", function(e) {
    +				if (d) {
    +					var dx = e.pageX - da[0],
    +						dy = e.pageY - da[1];
    +						
    +					jsPlumb.CurrentLibrary.setOffset(el, {
    +						left:elo.left + dx,
    +						top:elo.top + dy
    +					});
    +					_jsPlumb.repaint(elId);
    +					e.preventDefault();
    +					e.stopPropagation();
    +				}
    +			});
    +			ta.bind(document, "mouseup", function(e) {
    +				el = null;
    +				d = false;				
    +			});    
    +        };
    +        
    +        var isIOS = ((/iphone|ipad/gi).test(navigator.appVersion));
    +        if (isIOS)
    +            _jsPlumb.draggable = this.draggable;
    +        
    +    };
    +    
    +})();
    \ No newline at end of file
    diff --git a/src/jsPlumb-endpoint.js b/src/jsPlumb-endpoint.js
    new file mode 100644
    index 000000000..2fb817c21
    --- /dev/null
    +++ b/src/jsPlumb-endpoint.js
    @@ -0,0 +1,967 @@
    +;(function() {
    +        
    +    // create the drag handler for a connection
    +    var _makeConnectionDragHandler = function(placeholder, _jsPlumb) {
    +        var stopped = false;
    +        return {
    +            drag : function() {
    +                if (stopped) {
    +                    stopped = false;
    +                    return true;
    +                }
    +                var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments, _jsPlumb.getZoom());
    +        
    +                if (placeholder.element) {
    +                    jsPlumb.CurrentLibrary.setOffset(placeholder.element, _ui);                    
    +                    _jsPlumb.repaint(placeholder.element, _ui);
    +                }
    +            },
    +            stopDrag : function() {
    +                stopped = true;
    +            }
    +        };
    +    };
    +        
    +    // creates a placeholder div for dragging purposes, adds it to the DOM, and pre-computes its offset.    
    +    var _makeDraggablePlaceholder = function(placeholder, parent, _jsPlumb) {
    +        var n = document.createElement("div");
    +        n.style.position = "absolute";
    +        var placeholderDragElement = jsPlumb.CurrentLibrary.getElementObject(n);
    +        jsPlumb.CurrentLibrary.appendElement(n, parent);
    +        var id = _jsPlumb.getId(placeholderDragElement);
    +        _jsPlumb.updateOffset( { elId : id });
    +        // create and assign an id, and initialize the offset.
    +        placeholder.id = id;
    +        placeholder.element = placeholderDragElement;
    +    };
    +    
    +    // create a floating endpoint (for drag connections)
    +    var _makeFloatingEndpoint = function(paintStyle, referenceAnchor, endpoint, referenceCanvas, sourceElement, _jsPlumb, _newEndpoint) {			
    +        var floatingAnchor = new jsPlumb.FloatingAnchor( { reference : referenceAnchor, referenceCanvas : referenceCanvas, jsPlumbInstance:_jsPlumb });
    +        //setting the scope here should not be the way to fix that mootools issue.  it should be fixed by not
    +        // adding the floating endpoint as a droppable.  that makes more sense anyway!
    +        return _newEndpoint({ paintStyle : paintStyle, endpoint : endpoint, anchor : floatingAnchor, source : sourceElement, scope:"__floating" });
    +    };
    +
    +    var typeParameters = [ "connectorStyle", "connectorHoverStyle", "connectorOverlays",
    +                "connector", "connectionType", "connectorClass", "connectorHoverClass" ];
    +
    +    jsPlumb.Endpoint = function(params) {
    +        var self = this, 
    +            _jsPlumb = params["_jsPlumb"],
    +            jpcl = jsPlumb.CurrentLibrary,
    +            _att = jpcl.getAttribute,
    +            _gel = jpcl.getElementObject,
    +            _ju = jsPlumbUtil,
    +            _getOffset = jpcl.getOffset,
    +            _newConnection = params.newConnection,
    +            _newEndpoint = params.newEndpoint,
    +            _finaliseConnection = params.finaliseConnection,
    +            _fireDetachEvent = params.fireDetachEvent,
    +            floatingConnections = params.floatingConnections;
    +        
    +        self.idPrefix = "_jsplumb_e_";			
    +        self.defaultLabelLocation = [ 0.5, 0.5 ];
    +        self.defaultOverlayKeys = ["Overlays", "EndpointOverlays"];
    +        this.parent = params.parent;
    +        overlayCapableJsPlumbUIComponent.apply(this, arguments);
    +        params = params || {};
    +        
    +// TYPE		
    +        
    +        this.getTypeDescriptor = function() { return "endpoint"; };
    +        this.getDefaultType = function() {								
    +            return {
    +                parameters:{},
    +                scope:null,
    +                maxConnections:self._jsPlumb.Defaults.MaxConnections,
    +                paintStyle:self._jsPlumb.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle,
    +                endpoint:self._jsPlumb.Defaults.Endpoint || jsPlumb.Defaults.Endpoint,
    +                hoverPaintStyle:self._jsPlumb.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle,				
    +                overlays:self._jsPlumb.Defaults.EndpointOverlays || jsPlumb.Defaults.EndpointOverlays,
    +                connectorStyle:params.connectorStyle,				
    +                connectorHoverStyle:params.connectorHoverStyle,
    +                connectorClass:params.connectorClass,
    +                connectorHoverClass:params.connectorHoverClass,
    +                connectorOverlays:params.connectorOverlays,
    +                connector:params.connector,
    +                connectorTooltip:params.connectorTooltip
    +            };
    +        };
    +        var superAt = this.applyType;
    +        this.applyType = function(t, doNotRepaint) {
    +            superAt(t, doNotRepaint);
    +            if (t.maxConnections != null) _maxConnections = t.maxConnections;
    +            if (t.scope) self.scope = t.scope;
    +            _ju.copyValues(typeParameters, t, self);
    +        };			
    +// END TYPE
    +    
    +        var visible = true, __enabled = !(params.enabled === false);
    +        
    +        this.isVisible = function() { return visible; };        
    +        this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) {
    +            visible = v;
    +            if (self.canvas) self.canvas.style.display = v ? "block" : "none";
    +            self[v ? "showOverlays" : "hideOverlays"]();
    +            if (!doNotChangeConnections) {
    +                for (var i = 0; i < self.connections.length; i++) {
    +                    self.connections[i].setVisible(v);
    +                    if (!doNotNotifyOtherEndpoint) {
    +                        var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0;
    +                        // only change the other endpoint if this is its only connection.
    +                        if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true);
    +                    }
    +                }
    +            }
    +        };			
    +        
    +        this.isEnabled = function() { return __enabled; };
    +        this.setEnabled = function(e) { __enabled = e; };
    +
    +        var _element = params.source,  _uuid = params.uuid, floatingEndpoint = null,  inPlaceCopy = null;
    +        if (_uuid) params.endpointsByUUID[_uuid] = self;
    +        var _elementId = _att(_element, "id");
    +        this.elementId = _elementId;
    +        this.element = _element;
    +        
    +        self.setElementId = function(_elId) {
    +            _elementId = _elId;
    +            self.elementId = _elId;
    +            self.anchor.elementId = _elId
    +        };
    +        
    +        self.setReferenceElement = function(_el) {
    +            _element = _el;
    +            self.element = _el;
    +        };
    +        
    +        var _connectionCost = params.connectionCost;
    +        this.getConnectionCost = function() { return _connectionCost; };
    +        this.setConnectionCost = function(c) {
    +            _connectionCost = c; 
    +        };
    +        
    +        var _connectionsDirected = params.connectionsDirected;
    +        this.areConnectionsDirected = function() { return _connectionsDirected; };
    +        this.setConnectionsDirected = function(b) { _connectionsDirected = b; };                        
    +
    +        var _currentAnchorClass = "",
    +            _updateAnchorClass = function() {
    +                jpcl.removeClass(_element, _jsPlumb.endpointAnchorClassPrefix + "_" + _currentAnchorClass);
    +                self.removeClass(_jsPlumb.endpointAnchorClassPrefix + "_" + _currentAnchorClass);
    +                _currentAnchorClass = self.anchor.getCssClass();
    +                self.addClass(_jsPlumb.endpointAnchorClassPrefix + "_" + _currentAnchorClass);
    +                jpcl.addClass(_element, _jsPlumb.endpointAnchorClassPrefix + "_" + _currentAnchorClass);
    +            };
    +
    +        this.setAnchor = function(anchorParams, doNotRepaint) {
    +            self.anchor = _jsPlumb.makeAnchor(anchorParams, _elementId, _jsPlumb);
    +            _updateAnchorClass();
    +            self.anchor.bind("anchorChanged", function(currentAnchor) {
    +                self.fire("anchorChanged", {endpoint:self, anchor:currentAnchor});
    +                _updateAnchorClass();
    +            });
    +            if (!doNotRepaint)
    +                _jsPlumb.repaint(_elementId);
    +        };
    +
    +        this.cleanup = function() {
    +            jpcl.removeClass(_element, _jsPlumb.endpointAnchorClassPrefix + "_" + _currentAnchorClass);
    +        };
    +
    +        var anchorParamsToUse = params.anchor ? params.anchor : params.anchors ? params.anchors : (_jsPlumb.Defaults.Anchor || "Top");
    +        self.setAnchor(anchorParamsToUse, true);
    +            
    +        // ANCHOR MANAGER
    +        if (!params._transient) // in place copies, for example, are transient.  they will never need to be retrieved during a paint cycle, because they dont move, and then they are deleted.
    +            _jsPlumb.anchorManager.add(self, _elementId);
    +
    +        var _endpoint = null, originalEndpoint = null;
    +        this.setEndpoint = function(ep) {
    +            var endpointArgs = {
    +                _jsPlumb:self._jsPlumb,
    +                cssClass:params.cssClass,
    +                parent:params.parent,
    +                container:params.container,
    +                tooltip:params.tooltip,
    +                connectorTooltip:params.connectorTooltip,
    +                endpoint:self
    +            };
    +            if (_ju.isString(ep)) 
    +                _endpoint = new jsPlumb.Endpoints[_jsPlumb.getRenderMode()][ep](endpointArgs);
    +            else if (_ju.isArray(ep)) {
    +                endpointArgs = _ju.merge(ep[1], endpointArgs);
    +                _endpoint = new jsPlumb.Endpoints[_jsPlumb.getRenderMode()][ep[0]](endpointArgs);
    +            }
    +            else {
    +                _endpoint = ep.clone();
    +            }
    +
    +            // assign a clone function using a copy of endpointArgs. this is used when a drag starts: the endpoint that was dragged is cloned,
    +            // and the clone is left in its place while the original one goes off on a magical journey. 
    +            // the copy is to get around a closure problem, in which endpointArgs ends up getting shared by
    +            // the whole world.
    +            var argsForClone = jsPlumb.extend({}, endpointArgs);						
    +            _endpoint.clone = function() {
    +                var o = new Object();
    +                _endpoint.constructor.apply(o, [argsForClone]);
    +                return o;
    +            };
    +
    +            self.endpoint = _endpoint;
    +            self.type = self.endpoint.type;
    +        };
    +         
    +        this.setEndpoint(params.endpoint || _jsPlumb.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot");							
    +        originalEndpoint = _endpoint;        
    +
    +        // override setHover to pass it down to the underlying endpoint
    +        var _sh = self.setHover;
    +        self.setHover = function() {
    +            self.endpoint.setHover.apply(self.endpoint, arguments);
    +            _sh.apply(self, arguments);
    +        };
    +        // endpoint delegates to first connection for hover, if there is one.
    +        var internalHover = function(state) {
    +          if (self.connections.length > 0)
    +            self.connections[0].setHover(state, false);
    +          else
    +            self.setHover(state);
    +        };
    +        
    +        // bind listeners from endpoint to self, with the internal hover function defined above.
    +        self.bindListeners(self.endpoint, self, internalHover);
    +                                
    +        this.setPaintStyle(params.paintStyle || 
    +                           params.style || 
    +                           _jsPlumb.Defaults.EndpointStyle || 
    +                           jsPlumb.Defaults.EndpointStyle, true);
    +        this.setHoverPaintStyle(params.hoverPaintStyle || 
    +                                _jsPlumb.Defaults.EndpointHoverStyle || 
    +                                jsPlumb.Defaults.EndpointHoverStyle, true);
    +        this.paintStyleInUse = this.getPaintStyle();
    +        var originalPaintStyle = this.getPaintStyle();
    +
    +        _ju.copyValues(typeParameters, params, this);        
    +
    +        this.isSource = params.isSource || false;
    +        this.isTarget = params.isTarget || false;
    +        
    +        var _maxConnections = params.maxConnections || _jsPlumb.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of.
    +                    
    +        this.getAttachedElements = function() {
    +            return self.connections;
    +        };
    +                    
    +        this.canvas = this.endpoint.canvas;		
    +        // add anchor class (need to do this on construction because we set anchor first)
    +        self.addClass(_jsPlumb.endpointAnchorClassPrefix + "_" + _currentAnchorClass);	
    +        jpcl.addClass(_element, _jsPlumb.endpointAnchorClassPrefix + "_" + _currentAnchorClass);
    +        this.connections = params.connections || [];
    +        this.connectorPointerEvents = params["connector-pointer-events"];
    +        
    +        this.scope = params.scope || _jsPlumb.getDefaultScope();        
    +        this.timestamp = null;
    +        self.reattachConnections = params.reattach || _jsPlumb.Defaults.ReattachConnections;
    +        self.connectionsDetachable = _jsPlumb.Defaults.ConnectionsDetachable;
    +        if (params.connectionsDetachable === false || params.detachable === false)
    +            self.connectionsDetachable = false;
    +        var dragAllowedWhenFull = params.dragAllowedWhenFull || true;
    +        
    +        if (params.onMaxConnections)
    +            self.bind("maxConnections", params.onMaxConnections);
    +
    +        this.computeAnchor = function(params) {
    +            return self.anchor.compute(params);
    +        };
    +        
    +        this.addConnection = function(connection) {
    +            self.connections.push(connection);                  
    +            self[(self.connections.length > 0 ? "add" : "remove") + "Class"](_jsPlumb.endpointConnectedClass);       
    +            self[(self.isFull() ? "add" : "remove") + "Class"](_jsPlumb.endpointFullClass); 
    +        };		        
    +        this.detach = function(connection, ignoreTarget, forceDetach, fireEvent, originalEvent) {
    +            var idx = _ju.findWithFunction(self.connections, function(c) { return c.id == connection.id}), 
    +                actuallyDetached = false;
    +            fireEvent = (fireEvent !== false);
    +            if (idx >= 0) {		
    +                // 1. does the connection have a before detach (note this also checks jsPlumb's bound
    +                // detach handlers; but then Endpoint's check will, too, hmm.)
    +                if (forceDetach || connection._forceDetach || connection.isDetachable() || connection.isDetachAllowed(connection)) {
    +                    // get the target endpoint
    +                    var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0];
    +                    if (forceDetach || connection._forceDetach || (self.isDetachAllowed(connection) /*&& t.isDetachAllowed(connection)*/)) {                
    +                        self.connections.splice(idx, 1);										
    +                        // avoid circular loop
    +                        if (!ignoreTarget) {                        
    +                            t.detach(connection, true, forceDetach);
    +                            // check connection to see if we want to delete the endpoints associated with it.
    +                            // we only detach those that have just this connection; this scenario is most
    +                            // likely if we got to this bit of code because it is set by the methods that
    +                            // create their own endpoints, like .connect or .makeTarget. the user is
    +                            // not likely to have interacted with those endpoints.
    +                            if (connection.endpointsToDeleteOnDetach){
    +                                for (var i = 0; i < connection.endpointsToDeleteOnDetach.length; i++) {
    +                                    var cde = connection.endpointsToDeleteOnDetach[i];
    +                                    if (cde && cde.connections.length == 0) 
    +                                        _jsPlumb.deleteEndpoint(cde);							
    +                                }
    +                            }
    +                        }
    +                        _ju.removeElements(connection.getConnector().getDisplayElements(), connection.parent);
    +                        _ju.removeWithFunction(params.connectionsByScope[connection.scope], function(c) {
    +                            return c.id == connection.id;
    +                        });
    +                        self[(self.connections.length > 0 ? "add" : "remove") + "Class"](_jsPlumb.endpointConnectedClass);       
    +                        self[(self.isFull() ? "add" : "remove") + "Class"](_jsPlumb.endpointFullClass); 
    +                        actuallyDetached = true;                        
    +                        _fireDetachEvent(connection, (!ignoreTarget && fireEvent), originalEvent);
    +                    }
    +                }
    +            }
    +            return actuallyDetached;
    +        };			
    +        
    +        this.detachAll = function(fireEvent, originalEvent) {
    +            while (self.connections.length > 0) {
    +                self.detach(self.connections[0], false, true, fireEvent, originalEvent);
    +            }
    +        };
    +            
    +        this.detachFrom = function(targetEndpoint, fireEvent, originalEvent) {
    +            var c = [];
    +            for ( var i = 0; i < self.connections.length; i++) {
    +                if (self.connections[i].endpoints[1] == targetEndpoint
    +                        || self.connections[i].endpoints[0] == targetEndpoint) {
    +                    c.push(self.connections[i]);
    +                }
    +            }
    +            for ( var i = 0; i < c.length; i++) {
    +                if (self.detach(c[i], false, true, fireEvent, originalEvent))
    +                    c[i].setHover(false, false);					
    +            }
    +        };	
    +        
    +        this.detachFromConnection = function(connection) {
    +            var idx =  _ju.findWithFunction(self.connections, function(c) { return c.id == connection.id});
    +            if (idx >= 0) {
    +                self.connections.splice(idx, 1);
    +                self[(self.connections.length > 0 ? "add" : "remove") + "Class"](_jsPlumb.endpointConnectedClass);       
    +                self[(self.isFull() ? "add" : "remove") + "Class"](_jsPlumb.endpointFullClass); 
    +            }
    +        };
    +        
    +        this.getElement = function() {
    +            return _element;
    +        };		
    +                 
    +        this.setElement = function(el, container) {
    +            var parentId = _jsPlumb.getId(el);
    +            // remove the endpoint from the list for the current endpoint's element
    +            _ju.removeWithFunction(params.endpointsByElement[self.elementId], function(e) {
    +                return e.id == self.id;
    +            });
    +            _element = _gel(el);
    +            _elementId = _jsPlumb.getId(_element);
    +            self.elementId = _elementId;
    +            // need to get the new parent now
    +            var newParentElement = params.getParentFromParams({source:parentId, container:container}),
    +            curParent = jpcl.getParent(self.canvas);
    +            jpcl.removeElement(self.canvas, curParent);
    +            jpcl.appendElement(self.canvas, newParentElement);								
    +            
    +            // now move connection(s)...i would expect there to be only one but we will iterate.
    +            for (var i = 0; i < self.connections.length; i++) {
    +                self.connections[i].moveParent(newParentElement);
    +                self.connections[i].sourceId = _elementId;
    +                self.connections[i].source = _element;					
    +            }	
    +            _ju.addToList(params.endpointsByElement, parentId, self);
    +            
    +        };
    +        
    +        this.getUuid = function() {
    +            return _uuid;
    +        };
    +
    +        /**
    +         * private but must be exposed.
    +         */
    +        self.makeInPlaceCopy = function() {
    +            var loc = self.anchor.getCurrentLocation(self),
    +                o = self.anchor.getOrientation(self),
    +                acc = self.anchor.getCssClass(),
    +                inPlaceAnchor = {
    +                    bind:function() { },
    +                    compute:function() { return [ loc[0], loc[1] ]},
    +                    getCurrentLocation : function() { return [ loc[0], loc[1] ]},
    +                    getOrientation:function() { return o; },
    +                    getCssClass:function() { return acc; }
    +                };
    +
    +            return _newEndpoint( { 
    +                anchor : inPlaceAnchor, 
    +                source : _element, 
    +                paintStyle : this.getPaintStyle(), 
    +                endpoint : params.hideOnDrag ? "Blank" : _endpoint,
    +                _transient:true,
    +                scope:self.scope
    +            });
    +        };
    +        
    +        this.isConnectedTo = function(endpoint) {
    +            var found = false;
    +            if (endpoint) {
    +                for ( var i = 0; i < self.connections.length; i++) {
    +                    if (self.connections[i].endpoints[1] == endpoint) {
    +                        found = true;
    +                        break;
    +                    }
    +                }
    +            }
    +            return found;
    +        };
    +
    +        /**
    +         * private but needs to be exposed.
    +         */
    +        this.isFloating = function() {
    +            return floatingEndpoint != null;
    +        };
    +        
    +        /**
    +         * returns a connection from the pool; used when dragging starts.  just gets the head of the array if it can.
    +         */
    +        this.connectorSelector = function() {
    +            var candidate = self.connections[0];
    +            if (self.isTarget && candidate) return candidate;
    +            else {
    +                return (self.connections.length < _maxConnections) || _maxConnections == -1 ? null : candidate;
    +            }
    +        };
    +        
    +        this.isFull = function() {
    +            return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections);				
    +        };
    +                    
    +        this.setDragAllowedWhenFull = function(allowed) {
    +            dragAllowedWhenFull = allowed;
    +        };
    +            
    +        
    +        this.setStyle = self.setPaintStyle;
    +
    +        /**
    +         * a deep equals check. everything must match, including the anchor,
    +         * styles, everything. TODO: finish Endpoint.equals
    +         */
    +        this.equals = function(endpoint) {
    +            return this.anchor.equals(endpoint.anchor);
    +        };
    +        
    +        // a helper function that tries to find a connection to the given element, and returns it if so. if elementWithPrecedence is null,
    +        // or no connection to it is found, we return the first connection in our list.
    +        var findConnectionToUseForDynamicAnchor = function(elementWithPrecedence) {
    +            var idx = 0;
    +            if (elementWithPrecedence != null) {
    +                for (var i = 0; i < self.connections.length; i++) {
    +                    if (self.connections[i].sourceId == elementWithPrecedence || self.connections[i].targetId == elementWithPrecedence) {
    +                        idx = i;
    +                        break;
    +                    }
    +                }
    +            }
    +            
    +            return self.connections[idx];
    +        };
    +        
    +        this.paint = function(params) {
    +            params = params || {};
    +            var timestamp = params.timestamp, recalc = !(params.recalc === false);								
    +            if (!timestamp || self.timestamp !== timestamp) {						
    +                var info = _jsPlumb.updateOffset({ elId:_elementId, timestamp:timestamp, recalc:recalc });
    +                var xy = params.offset ? params.offset.o : info.o;
    +                if(xy) {
    +                    var ap = params.anchorPoint,connectorPaintStyle = params.connectorPaintStyle;
    +                    if (ap == null) {
    +                        var wh = params.dimensions || info.s;
    +                        if (xy == null || wh == null) {
    +                            info = _jsPlumb.updateOffset( { elId : _elementId, timestamp : timestamp });
    +                            xy = info.o;
    +                            wh = info.s;
    +                        }
    +                        var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp };
    +                        if (recalc && self.anchor.isDynamic && self.connections.length > 0) {
    +                            var c = findConnectionToUseForDynamicAnchor(params.elementWithPrecedence),
    +                                oIdx = c.endpoints[0] == self ? 1 : 0,
    +                                oId = oIdx == 0 ? c.sourceId : c.targetId,
    +                                oInfo = _jsPlumb.getCachedData(oId),
    +                                oOffset = oInfo.o, oWH = oInfo.s;
    +                            anchorParams.txy = [ oOffset.left, oOffset.top ];
    +                            anchorParams.twh = oWH;
    +                            anchorParams.tElement = c.endpoints[oIdx];
    +                        }
    +                        ap = self.anchor.compute(anchorParams);
    +                    }
    +                                        
    +                    _endpoint.compute(ap, self.anchor.getOrientation(self), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse);
    +                    _endpoint.paint(self.paintStyleInUse, self.anchor);					
    +                    self.timestamp = timestamp;
    +
    +                    // paint overlays
    +                    for ( var i = 0; i < self.overlays.length; i++) {
    +                        var o = self.overlays[i];
    +                        if (o.isVisible()) { 
    +                            self.overlayPlacements[i] = o.draw(self.endpoint, self.paintStyleInUse);
    +                            o.paint(self.overlayPlacements[i]);    
    +                        }
    +                    }
    +                }
    +            }
    +        };
    +
    +        this.repaint = this.paint;        
    +
    +        // is this a connection source? we make it draggable and have the
    +        // drag listener maintain a connection with a floating endpoint.
    +        if (jpcl.isDragSupported(_element)) {
    +            var placeholderInfo = { id:null, element:null },
    +                jpc = null,
    +                existingJpc = false,
    +                existingJpcParams = null,
    +                _dragHandler = _makeConnectionDragHandler(placeholderInfo, _jsPlumb);
    +
    +            var start = function() {	
    +            // drag might have started on an endpoint that is not actually a source, but which has
    +            // one or more connections.
    +                jpc = self.connectorSelector();
    +                var _continue = true;
    +                // if not enabled, return
    +                if (!self.isEnabled()) _continue = false;
    +                // if no connection and we're not a source, return.
    +                if (jpc == null && !params.isSource) _continue = false;
    +                // otherwise if we're full and not allowed to drag, also return false.
    +                if (params.isSource && self.isFull() && !dragAllowedWhenFull) _continue = false;
    +                // if the connection was setup as not detachable or one of its endpoints
    +                // was setup as connectionsDetachable = false, or Defaults.ConnectionsDetachable
    +                // is set to false...
    +                if (jpc != null && !jpc.isDetachable()) _continue = false;
    +
    +                if (_continue === false) {
    +                    // this is for mootools and yui. returning false from this causes jquery to stop drag.
    +                    // the events are wrapped in both mootools and yui anyway, but i don't think returning
    +                    // false from the start callback would stop a drag.
    +                    if (jpcl.stopDrag) jpcl.stopDrag();
    +                    _dragHandler.stopDrag();
    +                    return false;
    +                }
    +
    +                self.addClass("endpointDrag");
    +
    +                // if we're not full but there was a connection, make it null. we'll create a new one.
    +                if (jpc && !self.isFull() && params.isSource) jpc = null;
    +
    +                _jsPlumb.updateOffset( { elId : _elementId });
    +                inPlaceCopy = self.makeInPlaceCopy();
    +                inPlaceCopy.referenceEndpoint = self;
    +                inPlaceCopy.paint();																
    +                
    +                _makeDraggablePlaceholder(placeholderInfo, self.parent, _jsPlumb);
    +                
    +                // set the offset of this div to be where 'inPlaceCopy' is, to start with.
    +                // TODO merge this code with the code in both Anchor and FloatingAnchor, because it
    +                // does the same stuff.
    +                var ipcoel = _gel(inPlaceCopy.canvas),
    +                    ipco = _getOffset(ipcoel, _jsPlumb),
    +                    po = _jsPlumb.adjustForParentOffsetAndScroll([ipco.left, ipco.top], inPlaceCopy.canvas),
    +                    canvasElement = _gel(self.canvas);                               
    +                    
    +                jpcl.setOffset(placeholderInfo.element, {left:po[0], top:po[1]});															
    +                
    +                // when using makeSource and a parent, we first draw the source anchor on the source element, then
    +                // move it to the parent.  note that this happens after drawing the placeholder for the
    +                // first time.
    +                if (self.parentAnchor) self.anchor = _jsPlumb.makeAnchor(self.parentAnchor, self.elementId, _jsPlumb);
    +                
    +                // store the id of the dragging div and the source element. the drop function will pick these up.					
    +                jpcl.setAttribute(canvasElement, "dragId", placeholderInfo.id);
    +                jpcl.setAttribute(canvasElement, "elId", _elementId);
    +                floatingEndpoint = _makeFloatingEndpoint(self.getPaintStyle(), self.anchor, _endpoint, self.canvas, placeholderInfo.element, _jsPlumb, _newEndpoint);
    +                self.canvas.style.visibility = "hidden";            
    +                
    +                if (jpc == null) {                                                                                                                                                         
    +                    self.anchor.locked = true;
    +                    self.setHover(false, false);                        
    +                    // create a connection. one end is this endpoint, the other is a floating endpoint.                    
    +                    jpc = _newConnection({
    +                        sourceEndpoint : self,
    +                        targetEndpoint : floatingEndpoint,
    +                        source : self.endpointWillMoveTo || _element,  // for makeSource with parent option.  ensure source element is represented correctly.
    +                        target : placeholderInfo.element,
    +                        anchors : [ self.anchor, floatingEndpoint.anchor ],
    +                        paintStyle : params.connectorStyle, // this can be null. Connection will use the default.
    +                        hoverPaintStyle:params.connectorHoverStyle,
    +                        connector : params.connector, // this can also be null. Connection will use the default.
    +                        overlays : params.connectorOverlays,
    +                        type:self.connectionType,
    +                        cssClass:self.connectorClass,
    +                        hoverClass:self.connectorHoverClass
    +                    });
    +                    jpc.addClass(_jsPlumb.draggingClass);
    +                    floatingEndpoint.addClass(_jsPlumb.draggingClass);
    +                    // fire an event that informs that a connection is being dragged						
    +                    _jsPlumb.fire("connectionDrag", jpc);
    +
    +                } else {
    +                    existingJpc = true;
    +                    jpc.setHover(false);						
    +                    // if existing connection, allow to be dropped back on the source endpoint (issue 51).
    +                    _initDropTarget(ipcoel, false, true);
    +                    // new anchor idx
    +                    var anchorIdx = jpc.endpoints[0].id == self.id ? 0 : 1;
    +                    jpc.floatingAnchorIndex = anchorIdx;					// save our anchor index as the connection's floating index.						
    +                    self.detachFromConnection(jpc);							// detach from the connection while dragging is occurring.
    +                    
    +                    // store the original scope (issue 57)
    +                    var dragScope = jsPlumb.CurrentLibrary.getDragScope(canvasElement);
    +                    jpcl.setAttribute(canvasElement, "originalScope", dragScope);
    +                    // now we want to get this endpoint's DROP scope, and set it for now: we can only be dropped on drop zones
    +                    // that have our drop scope (issue 57).
    +                    var dropScope = jpcl.getDropScope(canvasElement);
    +                    jpcl.setDragScope(canvasElement, dropScope);
    +            
    +                    // now we replace ourselves with the temporary div we created above:
    +                    if (anchorIdx == 0) {
    +                        existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ];
    +                        jpc.source = placeholderInfo.element;
    +                        jpc.sourceId = placeholderInfo.id;
    +                    } else {
    +                        existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ];
    +                        jpc.target = placeholderInfo.element;
    +                        jpc.targetId = placeholderInfo.id;
    +                    }
    +
    +                    // lock the other endpoint; if it is dynamic it will not move while the drag is occurring.
    +                    jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true;
    +                    // store the original endpoint and assign the new floating endpoint for the drag.
    +                    jpc.suspendedEndpoint = jpc.endpoints[anchorIdx];
    +                    jpc.suspendedEndpoint.setHover(false);
    +                    floatingEndpoint.referenceEndpoint = jpc.suspendedEndpoint;
    +                    jpc.endpoints[anchorIdx] = floatingEndpoint;
    +
    +                    jpc.addClass(_jsPlumb.draggingClass);
    +                    floatingEndpoint.addClass(_jsPlumb.draggingClass);
    +                    // fire an event that informs that a connection is being dragged
    +                    _jsPlumb.fire("connectionDrag", jpc);
    +
    +                }
    +                // register it and register connection on it.
    +                floatingConnections[placeholderInfo.id] = jpc;
    +                _jsPlumb.anchorManager.addFloatingConnection(placeholderInfo.id, jpc);
    +                floatingEndpoint.addConnection(jpc);
    +                // only register for the target endpoint; we will not be dragging the source at any time
    +                // before this connection is either discarded or made into a permanent connection.
    +                _ju.addToList(params.endpointsByElement, placeholderInfo.id, floatingEndpoint);
    +                // tell jsplumb about it
    +                _jsPlumb.currentlyDragging = true;
    +            };
    +
    +            var dragOptions = params.dragOptions || {},
    +                defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions),
    +                startEvent = jpcl.dragEvents["start"],
    +                stopEvent = jpcl.dragEvents["stop"],
    +                dragEvent = jpcl.dragEvents["drag"];
    +            
    +            dragOptions = jsPlumb.extend(defaultOpts, dragOptions);
    +            dragOptions.scope = dragOptions.scope || self.scope;
    +            dragOptions[startEvent] = _jsPlumb.wrap(dragOptions[startEvent], start);
    +            // extracted drag handler function so can be used by makeSource
    +            dragOptions[dragEvent] = _jsPlumb.wrap(dragOptions[dragEvent], _dragHandler.drag);
    +            dragOptions[stopEvent] = _jsPlumb.wrap(dragOptions[stopEvent],
    +                function() {
    +                    var originalEvent = jpcl.getDropEvent(arguments);					
    +                    _ju.removeWithFunction(params.endpointsByElement[placeholderInfo.id], function(e) {
    +                        return e.id == floatingEndpoint.id;
    +                    });
    +                    _ju.removeElement(inPlaceCopy.canvas, _element);
    +                    _jsPlumb.anchorManager.clearFor(placeholderInfo.id);						
    +                    var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex;
    +                    jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false;
    +                
    +                // commented out pending decision on drag proxy stuff.
    +                //	self.setPaintStyle(originalPaintStyle); // reset to original; may have been changed by drag proxy.
    +                
    +                    if (jpc.endpoints[idx] == floatingEndpoint) {
    +                        // if the connection was an existing one:
    +                        if (existingJpc && jpc.suspendedEndpoint) {
    +                            // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the
    +                            // floating endpoint has been replaced.
    +                            if (idx == 0) {
    +                                jpc.source = existingJpcParams[0];
    +                                jpc.sourceId = existingJpcParams[1];
    +                            } else {
    +                                jpc.target = existingJpcParams[0];
    +                                jpc.targetId = existingJpcParams[1];
    +                            }
    +                            
    +                            // restore the original scope (issue 57)
    +                            jpcl.setDragScope(existingJpcParams[2], existingJpcParams[3]);
    +                            jpc.endpoints[idx] = jpc.suspendedEndpoint;
    +                            if (jpc.isReattach() || jpc._forceReattach || jpc._forceDetach || !jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc, false, false, true, originalEvent)) {									
    +                                jpc.setHover(false);
    +                                jpc.floatingAnchorIndex = null;
    +                                jpc.suspendedEndpoint.addConnection(jpc);
    +                                _jsPlumb.repaint(existingJpcParams[1]);
    +                            }
    +                            jpc._forceDetach = null;
    +                            jpc._forceReattach = null;
    +                        } else {
    +                            // TODO this looks suspiciously kind of like an Endpoint.detach call too.
    +                            // i wonder if this one should post an event though.  maybe this is good like this.
    +                            _ju.removeElements(jpc.getConnector().getDisplayElements(), self.parent);
    +                            self.detachFromConnection(jpc);								
    +                        }																
    +                    }
    +                    
    +                    // remove floating endpoint _after_ checking beforeDetach 
    +                    _ju.removeElements( [ placeholderInfo.element[0], floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted)
    +                    _jsPlumb.dragManager.elementRemoved(floatingEndpoint.elementId);
    +                    self.canvas.style.visibility = "visible";
    +                    
    +                    self.anchor.locked = false;												
    +                    self.paint({recalc:false});
    +
    +                    jpc.removeClass(_jsPlumb.draggingClass);
    +                    floatingEndpoint.removeClass(_jsPlumb.draggingClass);
    +                    _jsPlumb.fire("connectionDragStop", jpc);
    +
    +                    jpc = null;						
    +                    inPlaceCopy = null;							
    +                    delete params.endpointsByElement[floatingEndpoint.elementId];
    +                    floatingEndpoint.anchor = null;
    +                    floatingEndpoint = null;
    +                    _jsPlumb.currentlyDragging = false;
    +
    +                });
    +            
    +            var i = _gel(self.canvas);				
    +            jpcl.initDraggable(i, dragOptions, true, _jsPlumb);
    +        }
    +
    +        // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections
    +        // back onto the endpoint you detached it from.
    +        var _initDropTarget = function(canvas, forceInit, isTransient, endpoint) {
    +            if ((params.isTarget || forceInit) && jpcl.isDropSupported(_element)) {
    +                var dropOptions = params.dropOptions || _jsPlumb.Defaults.DropOptions || jsPlumb.Defaults.DropOptions;
    +                dropOptions = jsPlumb.extend( {}, dropOptions);
    +                dropOptions.scope = dropOptions.scope || self.scope;
    +                var dropEvent = jpcl.dragEvents['drop'],
    +                    overEvent = jpcl.dragEvents['over'],
    +                    outEvent = jpcl.dragEvents['out'],
    +                    drop = function() {
    +
    +                        self["removeClass"](_jsPlumb.endpointDropAllowedClass);
    +                        self["removeClass"](_jsPlumb.endpointDropForbiddenClass);
    +                                                    
    +                        var originalEvent = jpcl.getDropEvent(arguments),
    +                            draggable = _gel(jpcl.getDragObject(arguments)),
    +                            id = _att(draggable, "dragId"),
    +                            elId = _att(draggable, "elId"),						
    +                            scope = _att(draggable, "originalScope"),
    +                            jpc = floatingConnections[id];
    +                            
    +                        // if this is a drop back where the connection came from, mark it force rettach and
    +                        // return; the stop handler will reattach. without firing an event.
    +                        var redrop = jpc.suspendedEndpoint && (jpc.suspendedEndpoint.id == self.id ||
    +                                        self.referenceEndpoint && jpc.suspendedEndpoint.id == self.referenceEndpoint.id) ;							
    +                        if (redrop) {								
    +                            jpc._forceReattach = true;
    +                            return;
    +                        }
    +
    +                        if (jpc != null) {
    +                            var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0;
    +                            
    +                            // restore the original scope if necessary (issue 57)						
    +                            if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope);							
    +                            
    +                            var endpointEnabled = endpoint != null ? endpoint.isEnabled() : true;
    +                            
    +                            if (self.isFull()) {
    +                                self.fire("maxConnections", { 
    +                                    endpoint:self, 
    +                                    connection:jpc, 
    +                                    maxConnections:_maxConnections 
    +                                }, originalEvent);
    +                            }
    +                                                            
    +                            if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget) && endpointEnabled) {
    +                                var _doContinue = true;
    +
    +                                // the second check here is for the case that the user is dropping it back
    +                                // where it came from.
    +                                if (jpc.suspendedEndpoint && jpc.suspendedEndpoint.id != self.id) {
    +                                    if (idx == 0) {
    +                                        jpc.source = jpc.suspendedEndpoint.element;
    +                                        jpc.sourceId = jpc.suspendedEndpoint.elementId;
    +                                    } else {
    +                                        jpc.target = jpc.suspendedEndpoint.element;
    +                                        jpc.targetId = jpc.suspendedEndpoint.elementId;
    +                                    }
    +
    +                                    if (!jpc.isDetachAllowed(jpc) || !jpc.endpoints[idx].isDetachAllowed(jpc) || !jpc.suspendedEndpoint.isDetachAllowed(jpc) || !_jsPlumb.checkCondition("beforeDetach", jpc))
    +                                        _doContinue = false;								
    +                                }
    +            
    +                                // these have to be set before testing for beforeDrop.
    +                                if (idx == 0) {
    +                                    jpc.source = self.element;
    +                                    jpc.sourceId = self.elementId;
    +                                } else {
    +                                    jpc.target = self.element;
    +                                    jpc.targetId = self.elementId;
    +                                }
    +                                                            
    +// ------------ wrap the execution path in a function so we can support asynchronous beforeDrop																
    +                                    
    +                                // we want to execute this regardless.
    +                                var commonFunction = function() {
    +                                    jpc.floatingAnchorIndex = null;
    +                                };	
    +                                                                                                
    +                                var continueFunction = function() {
    +                                    // remove this jpc from the current endpoint
    +                                    jpc.endpoints[idx].detachFromConnection(jpc);
    +                                    if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc);
    +                                    jpc.endpoints[idx] = self;
    +                                    self.addConnection(jpc);
    +                                    
    +                                    // copy our parameters in to the connection:
    +                                    var params = self.getParameters();
    +                                    for (var aParam in params)
    +                                        jpc.setParameter(aParam, params[aParam]);
    +
    +                                    if (!jpc.suspendedEndpoint) {  
    +                                        if (params.draggable)
    +                                            jsPlumb.CurrentLibrary.initDraggable(self.element, dragOptions, true, _jsPlumb);
    +                                    }
    +                                    else {
    +                                        var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId;
    +                                        // fire a detach event
    +                                        _fireDetachEvent({
    +                                            source : idx == 0 ? suspendedElement : jpc.source, 
    +                                            target : idx == 1 ? suspendedElement : jpc.target,
    +                                            sourceId : idx == 0 ? suspendedElementId : jpc.sourceId, 
    +                                            targetId : idx == 1 ? suspendedElementId : jpc.targetId,
    +                                            sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], 
    +                                            targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1],
    +                                            connection : jpc
    +                                        }, true, originalEvent);
    +                                    }
    +
    +                                    // finalise will inform the anchor manager and also add to
    +                                    // connectionsByScope if necessary.
    +                                    _finaliseConnection(jpc, null, originalEvent);
    +                                    
    +                                    commonFunction();
    +                                };
    +                                
    +                                var dontContinueFunction = function() {
    +                                    // otherwise just put it back on the endpoint it was on before the drag.
    +                                    if (jpc.suspendedEndpoint) {									
    +                                        jpc.endpoints[idx] = jpc.suspendedEndpoint;
    +                                        jpc.setHover(false);
    +                                        jpc._forceDetach = true;
    +                                        if (idx == 0) {
    +                                            jpc.source = jpc.suspendedEndpoint.element;
    +                                            jpc.sourceId = jpc.suspendedEndpoint.elementId;
    +                                        } else {
    +                                            jpc.target = jpc.suspendedEndpoint.element;
    +                                            jpc.targetId = jpc.suspendedEndpoint.elementId;;
    +                                        }
    +                                        jpc.suspendedEndpoint.addConnection(jpc);
    +
    +                                        jpc.endpoints[0].repaint();
    +                                        jpc.repaint();
    +                                        _jsPlumb.repaint(jpc.sourceId);
    +                                        jpc._forceDetach = false;
    +                                    }
    +                                    
    +                                    commonFunction();
    +                                };
    +                                
    +// --------------------------------------
    +                                // now check beforeDrop.  this will be available only on Endpoints that are setup to
    +                                // have a beforeDrop condition (although, secretly, under the hood all Endpoints and 
    +                                // the Connection have them, because they are on jsPlumbUIComponent.  shhh!), because
    +                                // it only makes sense to have it on a target endpoint.
    +                                _doContinue = _doContinue && self.isDropAllowed(jpc.sourceId, jpc.targetId, jpc.scope, jpc, self);
    +                                                                                                                    
    +                                if (_doContinue) {
    +                                    continueFunction();
    +                                }
    +                                else {
    +                                    dontContinueFunction();
    +                                }
    +
    +                                //commonFunction();
    +                            }
    +                            _jsPlumb.currentlyDragging = false;
    +                            delete floatingConnections[id];		
    +                            _jsPlumb.anchorManager.removeFloatingConnection(id);
    +                        }
    +                    };
    +                
    +                dropOptions[dropEvent] = _jsPlumb.wrap(dropOptions[dropEvent], drop);
    +                dropOptions[overEvent] = _jsPlumb.wrap(dropOptions[overEvent], function() {					
    +                    var draggable = jpcl.getDragObject(arguments),
    +                        id = _att( _gel(draggable), "dragId"),
    +                        _jpc = floatingConnections[id];
    +                        
    +                    if (_jpc != null) {								
    +                        var idx = _jpc.floatingAnchorIndex == null ? 1 : _jpc.floatingAnchorIndex;
    +                        // here we should fire the 'over' event if we are a target and this is a new connection,
    +                        // or we are the same as the floating endpoint.								
    +                        var _cont = (self.isTarget && _jpc.floatingAnchorIndex != 0) || (_jpc.suspendedEndpoint && self.referenceEndpoint && self.referenceEndpoint.id == _jpc.suspendedEndpoint.id);
    +                        if (_cont) {
    +                            var bb = _jsPlumb.checkCondition("checkDropAllowed", { 
    +                                sourceEndpoint:_jpc.endpoints[idx], 
    +                                targetEndpoint:self,
    +                                connection:_jpc
    +                            }); 
    +                            self[(bb ? "add" : "remove") + "Class"](_jsPlumb.endpointDropAllowedClass);
    +                            self[(bb ? "remove" : "add") + "Class"](_jsPlumb.endpointDropForbiddenClass);
    +                            _jpc.endpoints[idx].anchor.over(self.anchor);
    +                        }
    +                    }						
    +                });	
    +                dropOptions[outEvent] = _jsPlumb.wrap(dropOptions[outEvent], function() {					
    +                    var draggable = jpcl.getDragObject(arguments),
    +                        id = _att( _gel(draggable), "dragId"),
    +                        _jpc = floatingConnections[id];
    +                        
    +                    if (_jpc != null) {
    +                        var idx = _jpc.floatingAnchorIndex == null ? 1 : _jpc.floatingAnchorIndex;
    +                        var _cont = (self.isTarget && _jpc.floatingAnchorIndex != 0) || (_jpc.suspendedEndpoint && self.referenceEndpoint && self.referenceEndpoint.id == _jpc.suspendedEndpoint.id);
    +                        if (_cont) {
    +                            self["removeClass"](_jsPlumb.endpointDropAllowedClass);
    +                            self["removeClass"](_jsPlumb.endpointDropForbiddenClass);
    +                            _jpc.endpoints[idx].anchor.out();
    +                        }
    +                    }
    +                });
    +                jpcl.initDroppable(canvas, dropOptions, true, isTransient);
    +            }
    +        };
    +        
    +        // initialise the endpoint's canvas as a drop target.  this will be ignored if the endpoint is not a target or drag is not supported.
    +        _initDropTarget(_gel(self.canvas), true, !(params._transient || self.anchor.isFloating), self);
    +        
    +         // finally, set type if it was provided
    +         if (params.type)
    +            self.addType(params.type, params.data, _jsPlumb.isSuspendDrawing());
    +
    +        return self;        					
    +    };	
    +})();
    \ No newline at end of file
    diff --git a/src/jsPlumb-overlays-guidelines.js b/src/jsPlumb-overlays-guidelines.js
    new file mode 100644
    index 000000000..72b63b009
    --- /dev/null
    +++ b/src/jsPlumb-overlays-guidelines.js
    @@ -0,0 +1,73 @@
    +    // this is really just a test overlay, so its undocumented and doesnt take any parameters. but i was loth to delete it.
    +    jsPlumb.Overlays.GuideLines = function() {
    +        var self = this;
    +        self.length = 50;
    +        self.lineWidth = 5;
    +        this.type = "GuideLines";
    +		AbstractOverlay.apply(this, arguments);
    +        jsPlumb.jsPlumbUIComponent.apply(this, arguments);
    +        this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) {
    +
    +            var head = connector.pointAlongPathFrom(self.loc, self.length / 2),
    +                mid = connector.pointOnPath(self.loc),
    +                tail = jsPlumbUtil.pointOnLine(head, mid, self.length),
    +                tailLine = jsPlumbUtil.perpendicularLineTo(head, tail, 40),
    +                headLine = jsPlumbUtil.perpendicularLineTo(tail, head, 20);
    +
    +            self.paint(connector, [head, tail, tailLine, headLine], self.lineWidth, "red", null, connectorDimensions);
    +
    +            return [Math.min(head.x, tail.x), Math.min(head.y, tail.y), Math.max(head.x, tail.x), Math.max(head.y,tail.y)];
    +        };
    +
    +        this.computeMaxSize = function() { return 50; };
    +
    +    	this.cleanup = function() { };  // nothing to clean up for GuideLines
    +    };
    +
    +// a test
    +    jsPlumb.Overlays.svg.GuideLines = function() {
    +        var path = null, self = this, path2 = null, p1_1, p1_2;
    +        jsPlumb.Overlays.GuideLines.apply(this, arguments);
    +        this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) {
    +    		if (path == null) {
    +    			path = _node("path");
    +    			connector.svg.appendChild(path);
    +    			self.attachListeners(path, connector);
    +    			self.attachListeners(path, self);
    +
    +                p1_1 = _node("path");
    +    			connector.svg.appendChild(p1_1);
    +    			self.attachListeners(p1_1, connector);
    +    			self.attachListeners(p1_1, self);
    +
    +                p1_2 = _node("path");
    +    			connector.svg.appendChild(p1_2);
    +    			self.attachListeners(p1_2, connector);
    +    			self.attachListeners(p1_2, self);
    +
    +    		}
    +
    +    		_attr(path, {
    +    			"d"		:	makePath(d[0], d[1]),
    +    			stroke 	: 	"red",
    +    			fill 	: 	null
    +    		});
    +
    +            _attr(p1_1, {
    +    			"d"		:	makePath(d[2][0], d[2][1]),
    +    			stroke 	: 	"blue",
    +    			fill 	: 	null
    +    		});
    +
    +            _attr(p1_2, {
    +    			"d"		:	makePath(d[3][0], d[3][1]),
    +    			stroke 	: 	"green",
    +    			fill 	: 	null
    +    		});
    +    	};
    +
    +        var makePath = function(d1, d2) {
    +            return "M " + d1.x + "," + d1.y +
    +                   " L" + d2.x + "," + d2.y;
    +        };
    +    };
    \ No newline at end of file
    diff --git a/src/jsPlumb-renderers-canvas.js b/src/jsPlumb-renderers-canvas.js
    new file mode 100644
    index 000000000..b61daff95
    --- /dev/null
    +++ b/src/jsPlumb-renderers-canvas.js
    @@ -0,0 +1,534 @@
    +/*
    + * jsPlumb
    + * 
    + * Title:jsPlumb 1.4.0
    + * 
    + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
    + * elements, or VML.  
    + * 
    + * This file contains the HTML5 canvas renderers.  Support for canvas was dropped in 1.4.0.
    + * This is being kept around because canvas might make a comeback as a single-page solution
    + * that also supports node rendering.
    + *
    + * Copyright (c) 2010 - 2013 Simon Porritt (http://jsplumb.org)
    + * 
    + * http://jsplumb.org
    + * http://github.com/sporritt/jsplumb
    + * http://code.google.com/p/jsplumb
    + * 
    + * Dual licensed under the MIT and GPL2 licenses.
    + */
    +
    +;(function() {
    +
    +// event binding from jsplumb.  canvas no longer supported.  but it may make a comeback in 
    +// the form of a single-page canvas.
    +
    +/*var bindOne = function(event) {
    +                    jsPlumb.CurrentLibrary.bind(document, event, function(e) {
    +                        if (!_currentInstance.currentlyDragging && renderMode == jsPlumb.CANVAS) {
    +                            // try connections first
    +                            for (var scope in connectionsByScope) {
    +                                var c = connectionsByScope[scope];
    +                                for (var i = 0, ii = c.length; i < ii; i++) {
    +                                    var t = c[i].getConnector()[event](e);
    +                                    if (t) return;	
    +                                }
    +                            }
    +                            for (var el in endpointsByElement) {
    +                                var ee = endpointsByElement[el];
    +                                for (var i = 0, ii = ee.length; i < ii; i++) {
    +                                    if (ee[i].endpoint[event](e)) return;
    +                                }
    +                            }
    +                        }
    +                    });					
    +				};
    +				bindOne("click");bindOne("dblclick");bindOne("mousemove");bindOne("mousedown");bindOne("mouseup");bindOne("contextmenu");
    +				*/
    +
    +	
    +// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS *******************************************************************
    +		
    +	// TODO refactor to renderer common script.  put a ref to jsPlumb.sizeCanvas in there too.
    +	var _connectionBeingDragged = null,
    +	    _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); },
    +	    _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); },
    +	    _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); },
    +	    _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); },
    +	    _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); };
    +	
    +	/*
    +	 * Class:CanvasMouseAdapter
    +	 * Provides support for mouse events on canvases.  
    +	 */
    +	var CanvasMouseAdapter = function() {
    +		var self = this;
    +		self.overlayPlacements = [];
    +		jsPlumb.jsPlumbUIComponent.apply(this, arguments);
    +		jsPlumbUtil.EventGenerator.apply(this, arguments);
    +		/**
    +		 * returns whether or not the given event is ojver a painted area of the canvas. 
    +		 */
    +	    this._over = function(e) {		    			  		    	
    +			var o = _getOffset(_getElementObject(self.canvas)),
    +				pageXY = _pageXY(e),
    +				x = pageXY[0] - o.left, y = pageXY[1] - o.top;
    +			if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) {
    +				// first check overlays
    +				for ( var i = 0; i < self.overlayPlacements.length; i++) {
    +					var p = self.overlayPlacements[i];
    +					if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y))
    +						return true;
    +				}		    	
    +				// then the canvas
    +				var d = self.canvas.getContext("2d").getImageData(parseInt(x, 10), parseInt(y, 10), 1, 1);
    +				return d.data[0] !== 0 || d.data[1] !== 0 || d.data[2] !== 0 || d.data[3] !== 0;		  
    +			}
    +			return false;
    +	    };
    +	    
    +	    var _mouseover = false, _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false,
    +		    _nullSafeHasClass = function(el, clazz) {
    +		    	return el !== null && _hasClass(el, clazz);
    +		    };
    +	    this.mousemove = function(e) {		    
    +	    	var pageXY = _pageXY(e), clientXY = _clientXY(e),	   
    +	    	ee = document.elementFromPoint(clientXY[0], clientXY[1]),
    +	    	eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay");	    	
    +			var _continue = _connectionBeingDragged === null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector"));
    +			if (!_mouseover && _continue && self._over(e)) {
    +				_mouseover = true;
    +				self.fire("mouseenter", self, e);		
    +				return true;
    +			}
    +			// TODO here there is a remote chance that the overlay the mouse moved onto
    +			// is actually not an overlay for the current component. a more thorough check would
    +			// be to ensure the overlay belonged to the current component.  
    +			else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) {
    +				_mouseover = false;
    +				self.fire("mouseexit", self, e);				
    +			}
    +			self.fire("mousemove", self, e);
    +	    };
    +	    		    		    
    +	    this.click = function(e) {	    		
    +			if (_mouseover && self._over(e) && !_mouseWasDown) 
    +	    		self.fire("click", self, e);		    	
    +	    	_mouseWasDown = false;
    +	    };
    +	    
    +	    this.dblclick = function(e) {
    +	    	if (_mouseover && self._over(e) && !_mouseWasDown) 
    +	    		self.fire("dblclick", self, e);		    	
    +	    	_mouseWasDown = false;
    +	    };
    +	    
    +	    this.mousedown = function(e) {
    +	    	if(self._over(e) && !_mouseDown) {
    +	    		_mouseDown = true;	    		
    +	    		_posWhenMouseDown = _getOffset(_getElementObject(self.canvas));
    +	    		self.fire("mousedown", self, e);
    +	    	}
    +	    };
    +	    
    +	    this.mouseup = function(e) {
    +	    	_mouseDown = false;
    +	    	self.fire("mouseup", self, e);
    +	    };
    +
    +        this.contextmenu = function(e) {
    +          if (_mouseover && self._over(e) && !_mouseWasDown)
    +            self.fire("contextmenu", self, e);
    +          _mouseWasDown = false;
    +        };
    +	};
    +	
    +	var _newCanvas = function(params) {
    +		var canvas = document.createElement("canvas");
    +		params["_jsPlumb"].appendElement(canvas, params.parent);
    +		canvas.style.position = "absolute";
    +		if (params["class"]) canvas.className = params["class"];
    +		// set an id. if no id on the element and if uuid was supplied it
    +		// will be used, otherwise we'll create one.
    +		params["_jsPlumb"].getId(canvas, params.uuid);
    +		if (params.tooltip) canvas.setAttribute("title", params.tooltip);
    +
    +		return canvas;
    +	};	
    +
    +	var CanvasComponent = function(params) {
    +		CanvasMouseAdapter.apply(this, arguments);
    +
    +		var displayElements = [ ];
    +		this.getDisplayElements = function() { return displayElements; };
    +		this.appendDisplayElement = function(el) { displayElements.push(el); };
    +	};
    +	
    +	var segmentMultipliers = [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ];
    +	var maybeMakeGradient = function(ctx, style, gradientFunction) {
    +		if (style.gradient) {
    +			var g = gradientFunction();
    +			for ( var i = 0; i < style.gradient.stops.length; i++)
    +				g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]);
    +			ctx.strokeStyle = g;
    +		}
    +	};
    +	var segmentRenderer = function(segment, ctx, style) {	
    +		({
    +			"Straight":function(segment, ctx, style) {
    +				var d = segment.params;
    +				maybeMakeGradient(ctx, style, function() { return ctx.createLinearGradient(d.x1, d.y1, d.x2, d.y2); });
    +				ctx.beginPath();
    +				if (style.dashstyle && style.dashstyle.split(" ").length === 2) {			
    +					// only a very simple dashed style is supported - having two values, which define the stroke length 
    +					// (as a multiple of the stroke width) and then the space length (also as a multiple of stroke width). 
    +					var ds = style.dashstyle.split(" ");
    +					if (ds.length !== 2) ds = [2, 2];
    +					var dss = [ ds[0] * style.lineWidth, ds[1] * style.lineWidth ],
    +						m = (d.x2- d.x1) / (d.y2 - d.y1),
    +						s = jsPlumbUtil.segment([d.x1, d.y1], [ d.x2, d.y2 ]),
    +						sm = segmentMultipliers[s],
    +						theta = Math.atan(m),
    +						l = Math.sqrt(Math.pow(d.x2 - d.x1, 2) + Math.pow(d.y2 - d.y1, 2)),
    +						repeats = Math.floor(l / (dss[0] + dss[1])),
    +						curPos = [d.x1, d.y1];
    +
    +					
    +					// TODO: the question here is why could we not support this in all connector types? it's really
    +					// just a case of going along and asking jsPlumb for the next point on the path a few times, until it
    +					// reaches the end. every type of connector supports that method, after all.  but right now its only the
    +					// bezier connector that gives you back the new location on the path along with the x,y coordinates, which
    +					// we would need. we'd start out at loc=0 and ask for the point along the path that is dss[0] pixels away.
    +					// we then ask for the point that is (dss[0] + dss[1]) pixels away; and from that one we need not just the
    +					// x,y but the location, cos we're gonna plug that location back in in order to find where that dash ends.
    +					//
    +					// it also strikes me that it should be trivial to support arbitrary dash styles (having more or less than two
    +					// entries). you'd just iterate that array using a step size of 2, and generify the (rss[0] + rss[1])
    +					// computation to be sum(rss[0]..rss[n]).
    +
    +					for (var i = 0; i < repeats; i++) {
    +						ctx.moveTo(curPos[0], curPos[1]);
    +
    +						var nextEndX = curPos[0] + (Math.abs(Math.sin(theta) * dss[0]) * sm[0]),
    +							nextEndY = curPos[1] + (Math.abs(Math.cos(theta) * dss[0]) * sm[1]),
    +							nextStartX = curPos[0] + (Math.abs(Math.sin(theta) * (dss[0] + dss[1]))  * sm[0]),
    +							nextStartY = curPos[1] + (Math.abs(Math.cos(theta) * (dss[0] + dss[1])) * sm[1]);
    +
    +						ctx.lineTo(nextEndX, nextEndY);
    +						curPos = [nextStartX, nextStartY];					
    +					}
    +
    +					// now draw the last bit
    +					ctx.moveTo(curPos[0], curPos[1]);
    +					ctx.lineTo(d.x2, d.y2);		
    +
    +				}	        
    +		        else {
    +					ctx.moveTo(d.x1, d.y1);
    +					ctx.lineTo(d.x2, d.y2);
    +		        }				
    +
    +				ctx.stroke();
    +			},
    +			"Bezier":function(segment, ctx, style) {
    +				var d = segment.params;
    +				maybeMakeGradient(ctx, style, function() { return ctx.createLinearGradient(d.x2, d.y2, d.x1, d.y1); });
    +				ctx.beginPath();
    +				ctx.moveTo(d.x1, d.y1);
    +				ctx.bezierCurveTo(d.cp1x, d.cp1y, d.cp2x, d.cp2y, d.x2, d.y2);
    +				ctx.stroke();
    +			},
    +			"Arc":function(segment, ctx, style) {
    +				var d = segment.params;
    +				ctx.beginPath();
    +				// arcTo is supported in most browsers i think; this is what we will use once the arc segment is a little more clever.
    +				// right now its up to the connector to figure out the geometry. well, maybe that's ok.
    +				//ctx.moveTo(d.x1, d.y1);
    +				//ctx.arcTo((d.x1 + d.x2) / 2, (d.y1 + d.y2) / 2, d.r);
    +				ctx.arc(d.cx, d.cy, d.r, d.startAngle, d.endAngle, d.c);
    +				ctx.stroke();
    +			}
    +		})[segment.type](segment, ctx, style);	
    +	};
    +	
    +	/**
    +	 * Class:CanvasConnector
    +	 * Superclass for Canvas Connector renderers.
    +	 */
    +	var CanvasConnector = jsPlumb.ConnectorRenderers.canvas = function(params) {
    +		var self = this;
    +		CanvasComponent.apply(this, arguments);
    +		
    +		var _paintOneStyle = function(aStyle) {
    +			self.ctx.save();
    +			jsPlumb.extend(self.ctx, aStyle);
    +
    +			var segments = self.getSegments();				
    +			for (var i = 0; i < segments.length; i++) {
    +				segmentRenderer(segments[i], self.ctx, aStyle);
    +			}
    +			self.ctx.restore();
    +		};
    +
    +		var self = this,
    +		clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || "");
    +		self.canvas = _newCanvas({ 
    +			"class":clazz, 
    +			_jsPlumb:self._jsPlumb,
    +			parent:params.parent,
    +			tooltip:params.tooltip
    +		});	
    +		self.ctx = self.canvas.getContext("2d");
    +		
    +		self.appendDisplayElement(self.canvas);
    +		
    +		//self.paint = function(dim, style) {						
    +		self.paint = function(style, anchor, extents) {						
    +			if (style != null) {																				
    +				jsPlumb.sizeCanvas(self.canvas, self.x, self.y, self.w, self.h);				
    +				if (style.outlineColor != null) {
    +					var outlineWidth = style.outlineWidth || 1,
    +					outlineStrokeWidth = style.lineWidth + (2 * outlineWidth),
    +					outlineStyle = {
    +						strokeStyle:style.outlineColor,
    +						lineWidth:outlineStrokeWidth
    +					};
    +					_paintOneStyle(outlineStyle);
    +				}
    +				_paintOneStyle(style);
    +			}
    +		};				
    +	};		
    +	
    +	
    +	
    +	/**
    +	 * Class:CanvasEndpoint
    +	 * Superclass for Canvas Endpoint renderers.
    +	 */
    +	var CanvasEndpoint = function(params) {
    +		var self = this;				
    +		CanvasComponent.apply(this, arguments);		
    +		var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""),
    +			canvasParams = { 
    +			"class":clazz, 
    +			_jsPlumb:self._jsPlumb,
    +			parent:params.parent,
    +			tooltip:self.tooltip
    +		};
    +		self.canvas = _newCanvas(canvasParams);	
    +		self.ctx = self.canvas.getContext("2d");
    +
    +		self.appendDisplayElement(self.canvas);
    +		
    +		this.paint = function(style, anchor, extents) {
    +			jsPlumb.sizeCanvas(self.canvas, self.x, self.y, self.w, self.h);			
    +			if (style.outlineColor != null) {
    +				var outlineWidth = style.outlineWidth || 1,
    +				outlineStrokeWidth = style.lineWidth + (2 * outlineWidth);
    +				var outlineStyle = {
    +					strokeStyle:style.outlineColor,
    +					lineWidth:outlineStrokeWidth
    +				};
    +			}
    +			
    +			self._paint.apply(this, arguments);
    +		};
    +	};
    +	
    +	jsPlumb.Endpoints.canvas.Dot = function(params) {		
    +		jsPlumb.Endpoints.Dot.apply(this, arguments);
    +		CanvasEndpoint.apply(this, arguments);
    +		var self = this,		
    +		parseValue = function(value) {
    +			try { return parseInt(value); }
    +			catch(e) {
    +				if (value.substring(value.length - 1) == '%')
    +					return parseInt(value.substring(0, value - 1));
    +			}
    +		},					    	
    +		calculateAdjustments = function(gradient) {
    +			var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius;
    +			gradient.offset && (offsetAdjustment = parseValue(gradient.offset));
    +        	gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius));
    +        	return [offsetAdjustment, innerRadius];
    +		};
    +		this._paint = function(style) {
    +			if (style != null) {			
    +				var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(self);
    +				jsPlumb.extend(ctx, style);							
    +	            if (style.gradient) {            	
    +	            	var adjustments = calculateAdjustments(style.gradient), 
    +	            	yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0],
    +	            	xAdjust = orientation[0] == 1 ? adjustments[0] * -1:  adjustments[0],
    +	            	g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]);
    +		            for (var i = 0; i < style.gradient.stops.length; i++)
    +		            	g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]);
    +		            ctx.fillStyle = g;
    +	            }				
    +				ctx.beginPath();    		
    +				ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true);
    +				ctx.closePath();				
    +				if (style.fillStyle || style.gradient) ctx.fill();
    +				if (style.strokeStyle) ctx.stroke();
    +			}
    +    	};
    +	};	
    +		
    +	jsPlumb.Endpoints.canvas.Rectangle = function(params) {
    +		
    +		var self = this;
    +		jsPlumb.Endpoints.Rectangle.apply(this, arguments);
    +		CanvasEndpoint.apply(this, arguments);				
    +		
    +    	this._paint = function(style) {
    +				
    +			var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(self);
    +			jsPlumb.extend(ctx, style);
    +			
    +			/* canvas gradient */
    +		    if (style.gradient) {
    +		    	// first figure out which direction to run the gradient in (it depends on the orientation of the anchors)
    +		    	var y1 = orientation[1] == 1 ? self.h : orientation[1] == 0 ? self.h / 2 : 0;
    +				var y2 = orientation[1] == -1 ? self.h : orientation[1] == 0 ? self.h / 2 : 0;
    +				var x1 = orientation[0] == 1 ? self.w : orientation[0] == 0 ? self.w / 2 : 0;
    +				var x2 = orientation[0] == -1 ? self.w : orientation[0] == 0 ? self.w / 2 : 0;
    +			    var g = ctx.createLinearGradient(x1,y1,x2,y2);
    +			    for (var i = 0; i < style.gradient.stops.length; i++)
    +	            	g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]);
    +	            ctx.fillStyle = g;
    +		    }
    +			
    +			ctx.beginPath();
    +			ctx.rect(0, 0, self.w, self.h);
    +			ctx.closePath();				
    +			if (style.fillStyle || style.gradient) ctx.fill();
    +			if (style.strokeStyle) ctx.stroke();
    +    	};
    +	};		
    +	
    +	jsPlumb.Endpoints.canvas.Triangle = function(params) {
    +	        			
    +		var self = this;
    +		jsPlumb.Endpoints.Triangle.apply(this, arguments);
    +		CanvasEndpoint.apply(this, arguments);			
    +		
    +    	this._paint = function(style)
    +		{    		
    +			var width = d[2], height = d[3], x = d[0], y = d[1],			
    +			ctx = self.canvas.getContext('2d'),
    +			offsetX = 0, offsetY = 0, angle = 0,
    +			orientation = anchor.getOrientation(self);
    +			
    +			if( orientation[0] == 1 ) {
    +				offsetX = width;
    +				offsetY = height;
    +				angle = 180;
    +			}
    +			if( orientation[1] == -1 ) {
    +				offsetX = width;
    +				angle = 90;
    +			}
    +			if( orientation[1] == 1 ) {
    +				offsetY = height;
    +				angle = -90;
    +			}
    +			
    +			ctx.fillStyle = style.fillStyle;
    +			
    +			ctx.translate(offsetX, offsetY);
    +			ctx.rotate(angle * Math.PI/180);
    +
    +			ctx.beginPath();
    +			ctx.moveTo(0, 0);
    +			ctx.lineTo(width/2, height/2);
    +			ctx.lineTo(0, height);
    +			ctx.closePath();
    +			if (style.fillStyle || style.gradient) ctx.fill();
    +			if (style.strokeStyle) ctx.stroke();				
    +    	};
    +	};	
    +	
    +	/*
    +	 * Canvas Image Endpoint: uses the default version, which creates an <img> tag.
    +	 */
    +	jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image;
    +	
    +	/*
    +	 * Blank endpoint in all renderers is just the default Blank endpoint.
    +	 */
    +	jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank;
    +	
    +	/*
    +     * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element.
    +     */
    +    jsPlumb.Connectors.canvas.Bezier = function() {
    +    	jsPlumb.Connectors.Bezier.apply(this, arguments); 
    +    	CanvasConnector.apply(this, arguments);    	        
    +    };
    +    
    +    /*
    +     * Canvas straight line Connector. Draws a straight line onto a Canvas element.
    +     */
    +    jsPlumb.Connectors.canvas.Straight = function() {   	 
    +		jsPlumb.Connectors.Straight.apply(this, arguments);
    +		CanvasConnector.apply(this, arguments);		
    +    };
    +    
    +    jsPlumb.Connectors.canvas.Flowchart = function() {
    +    	jsPlumb.Connectors.Flowchart.apply(this, arguments);
    +		CanvasConnector.apply(this, arguments);
    +    };
    +    
    +// ********************************* END OF CANVAS RENDERERS *******************************************************************    
    +    
    +    jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label;
    +	jsPlumb.Overlays.canvas.Custom = jsPlumb.Overlays.Custom;
    +    
    +    /**
    +     * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. 
    +     */
    +    var CanvasOverlay = function() { 
    +    	jsPlumb.jsPlumbUIComponent.apply(this, arguments);
    +    };
    +    
    +    var AbstractCanvasArrowOverlay = function(superclass, originalArgs) {
    +    	superclass.apply(this, originalArgs);
    +    	CanvasOverlay.apply(this, originalArgs);
    +    	//this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) {
    +    	this.paint = function(params, containerExtents) {
    +    		var ctx = params.component.ctx, d = params.d;
    +    		
    +			ctx.lineWidth = params.lineWidth;
    +			ctx.beginPath();
    +			ctx.moveTo(d.hxy.x, d.hxy.y);
    +			ctx.lineTo(d.tail[0].x, d.tail[0].y);
    +			ctx.lineTo(d.cxy.x, d.cxy.y);
    +			ctx.lineTo(d.tail[1].x, d.tail[1].y);
    +			ctx.lineTo(d.hxy.x, d.hxy.y);
    +			ctx.closePath();						
    +						
    +			if (params.strokeStyle) {
    +				ctx.strokeStyle = params.strokeStyle;
    +				ctx.stroke();
    +			}
    +			if (params.fillStyle) {
    +				ctx.fillStyle = params.fillStyle;			
    +				ctx.fill();
    +			}
    +    	};
    +    }; 
    +    
    +    jsPlumb.Overlays.canvas.Arrow = function() {
    +    	AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]);    	
    +    };
    +    
    +    jsPlumb.Overlays.canvas.PlainArrow = function() {
    +    	AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]);    	
    +    };
    +    
    +    jsPlumb.Overlays.canvas.Diamond = function() {
    +    	AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]);    	
    +    };		
    +})();
    \ No newline at end of file
    diff --git a/src/jsPlumb-renderers-svg.js b/src/jsPlumb-renderers-svg.js
    new file mode 100644
    index 000000000..3158b27c1
    --- /dev/null
    +++ b/src/jsPlumb-renderers-svg.js
    @@ -0,0 +1,593 @@
    +/*
    + * jsPlumb
    + * 
    + * Title:jsPlumb 1.4.0
    + * 
    + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
    + * elements, or VML.  
    + * 
    + * This file contains the SVG renderers.
    + *
    + * Copyright (c) 2010 - 2013 Simon Porritt (http://jsplumb.org)
    + * 
    + * http://jsplumb.org
    + * http://github.com/sporritt/jsplumb
    + * http://code.google.com/p/jsplumb
    + * 
    + * Dual licensed under the MIT and GPL2 licenses.
    + */
    +
    +/**
    + * SVG support for jsPlumb.
    + * 
    + * things to investigate:
    + * 
    + * gradients:  https://developer.mozilla.org/en/svg_in_html_introduction
    + * css:http://tutorials.jenkov.com/svg/svg-and-css.html
    + * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath
    + * pointer events: https://developer.mozilla.org/en/css/pointer-events
    + *
    + * IE9 hover jquery: http://forum.jquery.com/topic/1-6-2-broke-svg-hover-events
    + *
    + */
    +;(function() {
    +	
    +// ************************** SVG utility methods ********************************************	
    +	
    +	var svgAttributeMap = {
    +		"joinstyle":"stroke-linejoin",
    +		"stroke-linejoin":"stroke-linejoin",		
    +		"stroke-dashoffset":"stroke-dashoffset",
    +		"stroke-linecap":"stroke-linecap"
    +	},
    +	STROKE_DASHARRAY = "stroke-dasharray",
    +	DASHSTYLE = "dashstyle",
    +	LINEAR_GRADIENT = "linearGradient",
    +	RADIAL_GRADIENT = "radialGradient",
    +	FILL = "fill",
    +	STOP = "stop",
    +	STROKE = "stroke",
    +	STROKE_WIDTH = "stroke-width",
    +	STYLE = "style",
    +	NONE = "none",
    +	JSPLUMB_GRADIENT = "jsplumb_gradient_",
    +	LINE_WIDTH = "lineWidth",
    +	ns = {
    +		svg:"http://www.w3.org/2000/svg",
    +		xhtml:"http://www.w3.org/1999/xhtml"
    +	},
    +	_attr = function(node, attributes) {
    +		for (var i in attributes)
    +			node.setAttribute(i, "" + attributes[i]);
    +	},	
    +	_node = function(name, attributes) {
    +		var n = document.createElementNS(ns.svg, name);
    +		attributes = attributes || {};
    +		attributes["version"] = "1.1";
    +		attributes["xmlns"] = ns.xhtml;
    +		_attr(n, attributes);
    +		return n;
    +	},
    +	_pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; },	
    +	_clearGradient = function(parent) {
    +		for (var i = 0; i < parent.childNodes.length; i++) {
    +			if (parent.childNodes[i].tagName == LINEAR_GRADIENT || parent.childNodes[i].tagName == RADIAL_GRADIENT)
    +				parent.removeChild(parent.childNodes[i]);
    +		}
    +	},		
    +	_updateGradient = function(parent, node, style, dimensions, uiComponent) {
    +		var id = JSPLUMB_GRADIENT + uiComponent._jsPlumb.idstamp();
    +		// first clear out any existing gradient
    +		_clearGradient(parent);
    +		// this checks for an 'offset' property in the gradient, and in the absence of it, assumes
    +		// we want a linear gradient. if it's there, we create a radial gradient.
    +		// it is possible that a more explicit means of defining the gradient type would be
    +		// better. relying on 'offset' means that we can never have a radial gradient that uses
    +		// some default offset, for instance.
    +		// issue 244 suggested the 'gradientUnits' attribute; without this, straight/flowchart connectors with gradients would
    +		// not show gradients when the line was perfectly horizontal or vertical.
    +		var g;
    +		if (!style.gradient.offset) {
    +			g = _node(LINEAR_GRADIENT, {id:id, gradientUnits:"userSpaceOnUse"});
    +		}
    +		else {
    +			g = _node(RADIAL_GRADIENT, {
    +				id:id
    +			});			
    +		}
    +		
    +		parent.appendChild(g);
    +		
    +		// the svg radial gradient seems to treat stops in the reverse 
    +		// order to how canvas does it.  so we want to keep all the maths the same, but
    +		// iterate the actual style declarations in reverse order, if the x indexes are not in order.
    +		for (var i = 0; i < style.gradient.stops.length; i++) {
    +			var styleToUse = uiComponent.segment == 1 ||  uiComponent.segment == 2 ? i: style.gradient.stops.length - 1 - i,			
    +				stopColor = jsPlumbUtil.convertStyle(style.gradient.stops[styleToUse][1], true),
    +				s = _node(STOP, {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor});
    +
    +			g.appendChild(s);
    +		}
    +		var applyGradientTo = style.strokeStyle ? STROKE : FILL;
    +		node.setAttribute(STYLE, applyGradientTo + ":url(#" + id + ")");
    +	},
    +	_applyStyles = function(parent, node, style, dimensions, uiComponent) {
    +		
    +		if (style.gradient) {
    +			_updateGradient(parent, node, style, dimensions, uiComponent);			
    +		}
    +		else {
    +			// make sure we clear any existing gradient
    +			_clearGradient(parent);
    +			node.setAttribute(STYLE, "");
    +		}
    +		
    +		node.setAttribute(FILL, style.fillStyle ? jsPlumbUtil.convertStyle(style.fillStyle, true) : NONE);
    +		node.setAttribute(STROKE, style.strokeStyle ? jsPlumbUtil.convertStyle(style.strokeStyle, true) : NONE);		
    +		if (style.lineWidth) {
    +			node.setAttribute(STROKE_WIDTH, style.lineWidth);
    +		}
    +	
    +		// in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like
    +		// the syntax in VML but is actually kind of nasty: values are given in the pixel
    +		// coordinate space, whereas in VML they are multiples of the width of the stroked
    +		// line, which makes a lot more sense.  for that reason, jsPlumb is supporting both
    +		// the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from
    +		// VML, which will be the preferred method.  the code below this converts a dashstyle
    +		// attribute given in terms of stroke width into a pixel representation, by using the
    +		// stroke's lineWidth. 
    +		if (style[DASHSTYLE] && style[LINE_WIDTH] && !style[STROKE_DASHARRAY]) {
    +			var sep = style[DASHSTYLE].indexOf(",") == -1 ? " " : ",",
    +			parts = style[DASHSTYLE].split(sep),
    +			styleToUse = "";
    +			parts.forEach(function(p) {
    +				styleToUse += (Math.floor(p * style.lineWidth) + sep);
    +			});
    +			node.setAttribute(STROKE_DASHARRAY, styleToUse);
    +		}		
    +		else if(style[STROKE_DASHARRAY]) {
    +			node.setAttribute(STROKE_DASHARRAY, style[STROKE_DASHARRAY]);
    +		}
    +		
    +		// extra attributes such as join type, dash offset.
    +		for (var i in svgAttributeMap) {
    +			if (style[i]) {
    +				node.setAttribute(svgAttributeMap[i], style[i]);
    +			}
    +		}
    +	},
    +	_decodeFont = function(f) {
    +		var r = /([0-9].)(p[xt])\s(.*)/, 
    +			bits = f.match(r);
    +
    +		return {size:bits[1] + bits[2], font:bits[3]};		
    +	},
    +	_classManip = function(el, add, clazz) {
    +		var classesToAddOrRemove = clazz.split(" "),
    +			className = el.className,
    +			curClasses = className.baseVal.split(" ");
    +			
    +		for (var i = 0; i < classesToAddOrRemove.length; i++) {
    +			if (add) {
    +				if (curClasses.indexOf(classesToAddOrRemove[i]) == -1)
    +					curClasses.push(classesToAddOrRemove[i]);
    +			}
    +			else {
    +				var idx = curClasses.indexOf(classesToAddOrRemove[i]);
    +				if (idx != -1)
    +					curClasses.splice(idx, 1);
    +			}
    +		}
    +		
    +		el.className.baseVal = curClasses.join(" ");
    +	},
    +	_addClass = function(el, clazz) { _classManip(el, true, clazz); },
    +	_removeClass = function(el, clazz) { _classManip(el, false, clazz); },
    +	_appendAtIndex = function(svg, path, idx) {
    +		if (svg.childNodes.length > idx) {
    +			svg.insertBefore(path, svg.childNodes[idx]);
    +		}
    +		else svg.appendChild(path);
    +	};
    +	
    +	/**
    +		utility methods for other objects to use.
    +	*/
    +	jsPlumbUtil.svg = {
    +		addClass:_addClass,
    +		removeClass:_removeClass,
    +		node:_node,
    +		attr:_attr,
    +		pos:_pos
    +	};
    +	
    + // ************************** / SVG utility methods ********************************************	
    +	
    +	/*
    +	 * Base class for SVG components.
    +	 */	
    +	var SvgComponent = function(params) {
    +		var self = this,
    +			pointerEventsSpec = params.pointerEventsSpec || "all",
    +			renderer = {};
    +			
    +		jsPlumb.jsPlumbUIComponent.apply(this, params.originalArgs);
    +		self.canvas = null, self.path = null, self.svg = null; 
    +	
    +		var clazz = params.cssClass + " " + (params.originalArgs[0].cssClass || ""),		
    +			svgParams = {
    +				"style":"",
    +				"width":0,
    +				"height":0,
    +				"pointer-events":pointerEventsSpec,
    +				"position":"absolute"
    +			};				
    +		self.svg = _node("svg", svgParams);
    +		if (params.useDivWrapper) {
    +			self.canvas = document.createElement("div");
    +			self.canvas.style["position"] = "absolute";
    +			jsPlumb.sizeCanvas(self.canvas,0,0,1,1);
    +			self.canvas.className = clazz;
    +		}
    +		else {
    +			_attr(self.svg, { "class":clazz });
    +			self.canvas = self.svg;
    +		}
    +			
    +		params._jsPlumb.appendElement(self.canvas, params.originalArgs[0]["parent"]);
    +		if (params.useDivWrapper) self.canvas.appendChild(self.svg);
    +		
    +		// TODO this displayElement stuff is common between all components, across all
    +		// renderers.  would be best moved to jsPlumbUIComponent.
    +		var displayElements = [ self.canvas ];
    +		this.getDisplayElements = function() { 
    +			return displayElements; 
    +		};
    +		
    +		this.appendDisplayElement = function(el) {
    +			displayElements.push(el);
    +		};	
    +		
    +		this.paint = function(style, anchor, extents) {	   			
    +			if (style != null) {
    +				
    +				var xy = [ self.x, self.y ], wh = [ self.w, self.h ], p;
    +				if (extents != null) {
    +					if (extents.xmin < 0) xy[0] += extents.xmin;
    +					if (extents.ymin < 0) xy[1] += extents.ymin;
    +					wh[0] = extents.xmax + ((extents.xmin < 0) ? -extents.xmin : 0);
    +					wh[1] = extents.ymax + ((extents.ymin < 0) ? -extents.ymin : 0);
    +				}
    +
    +				if (params.useDivWrapper) {					
    +					jsPlumb.sizeCanvas(self.canvas, xy[0], xy[1], wh[0], wh[1]);
    +					xy[0] = 0, xy[1] = 0;
    +					p = _pos([ 0, 0 ]);
    +				}
    +				else
    +					p = _pos([ xy[0], xy[1] ]);
    +                
    +                renderer.paint.apply(this, arguments);		    			    	
    +                
    +		    	_attr(self.svg, {
    +	    			"style":p,
    +	    			"width": wh[0],
    +	    			"height": wh[1]
    +	    		});		    		    		    	
    +			}
    +	    };
    +		
    +		return {
    +			renderer:renderer
    +		};
    +	};
    +	
    +	/*
    +	 * Base class for SVG connectors.
    +	 */ 
    +	var SvgConnector = jsPlumb.ConnectorRenderers.svg = function(params) {
    +		var self = this,
    +			_super = SvgComponent.apply(this, [ { 
    +				cssClass:params["_jsPlumb"].connectorClass, 
    +				originalArgs:arguments, 
    +				pointerEventsSpec:"none", 
    +				_jsPlumb:params["_jsPlumb"] 
    +			} ]);				
    +
    +		_super.renderer.paint = function(style, anchor, extents) {
    +			
    +			var segments = self.getSegments(), p = "", offset = [0,0];			
    +			if (extents.xmin < 0) offset[0] = -extents.xmin;
    +			if (extents.ymin < 0) offset[1] = -extents.ymin;			
    +			
    +			// create path from segments.	
    +			for (var i = 0; i < segments.length; i++) {
    +				p += jsPlumb.Segments.svg.SegmentRenderer.getPath(segments[i]);
    +				p += " ";
    +			}			
    +			
    +			var a = { 
    +					d:p,
    +					transform:"translate(" + offset[0] + "," + offset[1] + ")",
    +					"pointer-events":params["pointer-events"] || "visibleStroke"
    +				}, 
    +                outlineStyle = null,
    +                d = [self.x,self.y,self.w,self.h];						
    +			
    +			// outline style.  actually means drawing an svg object underneath the main one.
    +			if (style.outlineColor) {
    +				var outlineWidth = style.outlineWidth || 1,
    +				outlineStrokeWidth = style.lineWidth + (2 * outlineWidth),
    +				outlineStyle = jsPlumb.CurrentLibrary.extend({}, style);
    +				outlineStyle.strokeStyle = jsPlumbUtil.convertStyle(style.outlineColor);
    +				outlineStyle.lineWidth = outlineStrokeWidth;
    +				
    +				if (self.bgPath == null) {
    +					self.bgPath = _node("path", a);
    +			    	_appendAtIndex(self.svg, self.bgPath, 0);
    +		    		self.attachListeners(self.bgPath, self);
    +				}
    +				else {
    +					_attr(self.bgPath, a);
    +				}
    +				
    +				_applyStyles(self.svg, self.bgPath, outlineStyle, d, self);
    +			}			
    +			
    +	    	if (self.path == null) {
    +		    	self.path = _node("path", a);
    +		    	_appendAtIndex(self.svg, self.path, style.outlineColor ? 1 : 0);
    +	    		self.attachListeners(self.path, self);	    		    		
    +	    	}
    +	    	else {
    +	    		_attr(self.path, a);
    +	    	}
    +	    		    	
    +	    	_applyStyles(self.svg, self.path, style, d, self);
    +		};
    +		
    +		this.reattachListeners = function() {
    +			if (self.bgPath) self.reattachListenersForElement(self.bgPath, self);
    +			if (self.path) self.reattachListenersForElement(self.path, self);
    +		};
    +	};
    +		
    +// ******************************* svg segment renderer *****************************************************	
    +		
    +	jsPlumb.Segments.svg = {
    +		SegmentRenderer : {		
    +			getPath : function(segment) {
    +				return ({
    +					"Straight":function() {
    +						var d = segment.getCoordinates();
    +						return "M " + d.x1 + " " + d.y1 + " L " + d.x2 + " " + d.y2;	
    +					},
    +					"Bezier":function() {
    +						var d = segment.params;
    +						return "M " + d.x1 + " " + d.y1 + 
    +							" C " + d.cp1x + " " + d.cp1y + " " + d.cp2x + " " + d.cp2y + " " + d.x2 + " " + d.y2;			
    +					},
    +					"Arc":function() {
    +						var d = segment.params,
    +							laf = segment.sweep > Math.PI ? 1 : 0,
    +							sf = segment.anticlockwise ? 0 : 1;			
    +
    +						return "M" + segment.x1 + " " + segment.y1 + " A " + segment.radius + " " + d.r + " 0 " + laf + "," + sf + " " + segment.x2 + " " + segment.y2;
    +					}
    +				})[segment.type]();	
    +			}
    +		}
    +	};
    +	
    +// ******************************* /svg segments *****************************************************
    +   
    +    /*
    +	 * Base class for SVG endpoints.
    +	 */
    +	var SvgEndpoint = window.SvgEndpoint = function(params) {
    +		var self = this,
    +			_super = SvgComponent.apply(this, [ {
    +				cssClass:params["_jsPlumb"].endpointClass, 
    +				originalArgs:arguments, 
    +				pointerEventsSpec:"all",
    +				useDivWrapper:true,
    +				_jsPlumb:params["_jsPlumb"]
    +			} ]);
    +			
    +		_super.renderer.paint = function(style) {
    +			var s = jsPlumb.extend({}, style);
    +			if (s.outlineColor) {
    +				s.strokeWidth = s.outlineWidth;
    +				s.strokeStyle = jsPlumbUtil.convertStyle(s.outlineColor, true);
    +			}
    +			
    +			if (self.node == null) {
    +				self.node = self.makeNode(s);
    +				self.svg.appendChild(self.node);
    +				self.attachListeners(self.node, self);
    +			}
    +			else if (self.updateNode != null) {
    +				self.updateNode(self.node);
    +			}
    +			_applyStyles(self.svg, self.node, s, [ self.x, self.y, self.w, self.h ], self);
    +			_pos(self.node, [ self.x, self.y ]);
    +		};
    +		
    +		this.reattachListeners = function() {
    +			if (self.node) self.reattachListenersForElement(self.node, self);
    +		};
    +	};
    +	
    +	/*
    +	 * SVG Dot Endpoint
    +	 */
    +	jsPlumb.Endpoints.svg.Dot = function() {
    +		jsPlumb.Endpoints.Dot.apply(this, arguments);
    +		SvgEndpoint.apply(this, arguments);		
    +		this.makeNode = function(style) { 
    +			return _node("circle", {
    +                "cx"	:	this.w / 2,
    +                "cy"	:	this.h / 2,
    +                "r"		:	this.radius
    +            });			
    +		};
    +		this.updateNode = function(node) {
    +			_attr(node, {
    +				"cx":this.w / 2,
    +				"cy":this.h  / 2,
    +				"r":this.radius
    +			});
    +		};
    +	};
    +	
    +	/*
    +	 * SVG Rectangle Endpoint 
    +	 */
    +	jsPlumb.Endpoints.svg.Rectangle = function() {
    +		jsPlumb.Endpoints.Rectangle.apply(this, arguments);
    +		SvgEndpoint.apply(this, arguments);		
    +		this.makeNode = function(style) {
    +			return _node("rect", {
    +				"width"     :   this.w,
    +				"height"    :   this.h
    +			});
    +		};
    +		this.updateNode = function(node) {
    +			_attr(node, {
    +				"width":this.w,
    +				"height":this.h
    +			});
    +		};			
    +	};		
    +	
    +	/*
    +	 * SVG Image Endpoint is the default image endpoint.
    +	 */
    +	jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image;
    +	/*
    +	 * Blank endpoint in svg renderer is the default Blank endpoint.
    +	 */
    +	jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank;	
    +	/*
    +	 * Label overlay in svg renderer is the default Label overlay.
    +	 */
    +	jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label;
    +	/*
    +	 * Custom overlay in svg renderer is the default Custom overlay.
    +	 */
    +	jsPlumb.Overlays.svg.Custom = jsPlumb.Overlays.Custom;
    +		
    +	var AbstractSvgArrowOverlay = function(superclass, originalArgs) {
    +    	superclass.apply(this, originalArgs);
    +    	jsPlumb.jsPlumbUIComponent.apply(this, originalArgs);
    +        this.isAppendedAtTopLevel = false;
    +    	var self = this, path = null;
    +    	this.paint = function(params, containerExtents) {
    +    		// only draws on connections, not endpoints.
    +    		if (params.component.svg && containerExtents) {
    +	    		if (path == null) {
    +	    			path = _node("path", {
    +	    				"pointer-events":"all"	
    +	    			});
    +	    			params.component.svg.appendChild(path);
    +	    			
    +	    			self.attachListeners(path, params.component);
    +	    			self.attachListeners(path, self);
    +	    		}
    +	    		var clazz = originalArgs && (originalArgs.length == 1) ? (originalArgs[0].cssClass || "") : "",
    +	    			offset = [0,0];
    +
    +	    		if (containerExtents.xmin < 0) offset[0] = -containerExtents.xmin;
    +	    		if (containerExtents.ymin < 0) offset[1] = -containerExtents.ymin;
    +	    		
    +	    		_attr(path, { 
    +	    			"d"			:	makePath(params.d),
    +	    			"class" 	:	clazz,
    +	    			stroke 		: 	params.strokeStyle ? params.strokeStyle : null,
    +	    			fill 		: 	params.fillStyle ? params.fillStyle : null,
    +	    			transform	: 	"translate(" + offset[0] + "," + offset[1] + ")"
    +	    		});    		
    +	    	}
    +    	};
    +    	var makePath = function(d) {
    +    		return "M" + d.hxy.x + "," + d.hxy.y +
    +    				" L" + d.tail[0].x + "," + d.tail[0].y + 
    +    				" L" + d.cxy.x + "," + d.cxy.y + 
    +    				" L" + d.tail[1].x + "," + d.tail[1].y + 
    +    				" L" + d.hxy.x + "," + d.hxy.y;
    +    	};
    +    	this.reattachListeners = function() {
    +			if (path) self.reattachListenersForElement(path, self);
    +		};
    +		this.cleanup = function() {
    +    		if (path != null) jsPlumb.CurrentLibrary.removeElement(path);
    +    	};
    +    };
    +    
    +    jsPlumb.Overlays.svg.Arrow = function() {
    +    	AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]);    	
    +    };
    +    
    +    jsPlumb.Overlays.svg.PlainArrow = function() {
    +    	AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]);    	
    +    };
    +    
    +    jsPlumb.Overlays.svg.Diamond = function() {
    +    	AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]);    	
    +    };
    +
    +    // a test
    +    jsPlumb.Overlays.svg.GuideLines = function() {
    +        var path = null, self = this, p1_1, p1_2;        
    +        jsPlumb.Overlays.GuideLines.apply(this, arguments);
    +        this.paint = function(params, containerExtents) {
    +    		if (path == null) {
    +    			path = _node("path");
    +    			params.connector.svg.appendChild(path);
    +    			self.attachListeners(path, params.connector);
    +    			self.attachListeners(path, self);
    +
    +                p1_1 = _node("path");
    +    			params.connector.svg.appendChild(p1_1);
    +    			self.attachListeners(p1_1, params.connector);
    +    			self.attachListeners(p1_1, self);
    +
    +                p1_2 = _node("path");
    +    			params.connector.svg.appendChild(p1_2);
    +    			self.attachListeners(p1_2, params.connector);
    +    			self.attachListeners(p1_2, self);
    +    		}
    +
    +    		var offset =[0,0];
    +    		if (containerExtents.xmin < 0) offset[0] = -containerExtents.xmin;
    +    		if (containerExtents.ymin < 0) offset[1] = -containerExtents.ymin;
    +
    +    		_attr(path, {
    +    			"d"		:	makePath(params.head, params.tail),
    +    			stroke 	: 	"red",
    +    			fill 	: 	null,
    +    			transform:"translate(" + offset[0] + "," + offset[1] + ")"
    +    		});
    +
    +            _attr(p1_1, {
    +    			"d"		:	makePath(params.tailLine[0], params.tailLine[1]),
    +    			stroke 	: 	"blue",
    +    			fill 	: 	null,
    +    			transform:"translate(" + offset[0] + "," + offset[1] + ")"
    +    		});
    +
    +            _attr(p1_2, {
    +    			"d"		:	makePath(params.headLine[0], params.headLine[1]),
    +    			stroke 	: 	"green",
    +    			fill 	: 	null,
    +    			transform:"translate(" + offset[0] + "," + offset[1] + ")"
    +    		});
    +    	};
    +
    +        var makePath = function(d1, d2) {
    +            return "M " + d1.x + "," + d1.y +
    +                   " L" + d2.x + "," + d2.y;
    +        };        
    +
    +    };
    +})();
    \ No newline at end of file
    diff --git a/src/jsPlumb-renderers-vml.js b/src/jsPlumb-renderers-vml.js
    new file mode 100644
    index 000000000..f7226ebc9
    --- /dev/null
    +++ b/src/jsPlumb-renderers-vml.js
    @@ -0,0 +1,486 @@
    +/*
    + * jsPlumb
    + * 
    + * Title:jsPlumb 1.4.0
    + * 
    + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
    + * elements, or VML.  
    + * 
    + * This file contains the VML renderers.
    + *
    + * Copyright (c) 2010 - 2013 Simon Porritt (http://jsplumb.org)
    + * 
    + * http://jsplumb.org
    + * http://github.com/sporritt/jsplumb
    + * http://code.google.com/p/jsplumb
    + * 
    + * Dual licensed under the MIT and GPL2 licenses.
    + */
    +
    +;(function() {
    +	
    +	// http://ajaxian.com/archives/the-vml-changes-in-ie-8
    +	// http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/
    +	// http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/
    +	
    +	var vmlAttributeMap = {
    +		"stroke-linejoin":"joinstyle",
    +		"joinstyle":"joinstyle",		
    +		"endcap":"endcap",
    +		"miterlimit":"miterlimit"
    +	},
    +	jsPlumbStylesheet = null;
    +	
    +	if (document.createStyleSheet && document.namespaces) {			
    +		
    +		var ruleClasses = [
    +				".jsplumb_vml", "jsplumb\\:textbox", "jsplumb\\:oval", "jsplumb\\:rect", 
    +				"jsplumb\\:stroke", "jsplumb\\:shape", "jsplumb\\:group"
    +			],
    +			rule = "behavior:url(#default#VML);position:absolute;";
    +
    +		jsPlumbStylesheet = document.createStyleSheet();
    +
    +		for (var i = 0; i < ruleClasses.length; i++)
    +			jsPlumbStylesheet.addRule(ruleClasses[i], rule);
    +
    +		// in this page it is also mentioned that IE requires the extra arg to the namespace
    +		// http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/
    +		// but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either.
    +		// var iev = document.documentMode;
    +		//if (!iev || iev < 8)
    +			document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml");
    +		//else
    +		//	document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML");
    +	}
    +	
    +	jsPlumb.vml = {};
    +	
    +	var scale = 1000,
    +
    +    _groupMap = {},
    +    _getGroup = function(container, connectorClass) {
    +        var id = jsPlumb.getId(container),
    +            g = _groupMap[id];
    +        if(!g) {
    +            g = _node("group", [0,0,scale, scale], {"class":connectorClass});
    +            //g.style.position=absolute;
    +            //g["coordsize"] = "1000,1000";
    +            g.style.backgroundColor="red";
    +            _groupMap[id] = g;
    +            jsPlumb.appendElement(g, container);  // todo if this gets reinstated, remember to use the current jsplumb instance.
    +            //document.body.appendChild(g);
    +        }
    +        return g;
    +    },
    +	_atts = function(o, atts) {
    +		for (var i in atts) { 
    +			// IE8 fix: setattribute does not work after an element has been added to the dom!
    +			// http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/
    +			//o.setAttribute(i, atts[i]);
    +
    +			/*There is an additional problem when accessing VML elements by using get/setAttribute. The simple solution is following:
    +
    +			if (document.documentMode==8) {
    +			ele.opacity=1;
    +			} else {
    +			ele.setAttribute(‘opacity’,1);
    +			}
    +			*/
    +
    +			o[i] = atts[i];
    +		}
    +	},
    +	_node = function(name, d, atts, parent, _jsPlumb, deferToJsPlumbContainer) {
    +		atts = atts || {};
    +		var o = document.createElement("jsplumb:" + name);
    +		if (deferToJsPlumbContainer)
    +			_jsPlumb.appendElement(o, parent);
    +		else
    +			jsPlumb.CurrentLibrary.appendElement(o, parent);
    +		o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml";
    +		_pos(o, d);
    +		_atts(o, atts);
    +		return o;
    +	},
    +	_pos = function(o,d, zIndex) {
    +		o.style.left = d[0] + "px";		
    +		o.style.top =  d[1] + "px";
    +		o.style.width= d[2] + "px";
    +		o.style.height= d[3] + "px";
    +		o.style.position = "absolute";
    +		if (zIndex)
    +			o.style.zIndex = zIndex;
    +	},
    +	_conv = jsPlumb.vml.convertValue = function(v) {
    +		return Math.floor(v * scale);
    +	},	
    +	// tests if the given style is "transparent" and then sets the appropriate opacity node to 0 if so,
    +	// or 1 if not.  TODO in the future, support variable opacity.
    +	_maybeSetOpacity = function(styleToWrite, styleToCheck, type, component) {
    +		if ("transparent" === styleToCheck)
    +			component.setOpacity(type, "0.0");
    +		else
    +			component.setOpacity(type, "1.0");
    +	},
    +	_applyStyles = function(node, style, component, _jsPlumb) {
    +		var styleToWrite = {};
    +		if (style.strokeStyle) {
    +			styleToWrite["stroked"] = "true";
    +			var strokeColor = jsPlumbUtil.convertStyle(style.strokeStyle, true);
    +			styleToWrite["strokecolor"] = strokeColor;
    +			_maybeSetOpacity(styleToWrite, strokeColor, "stroke", component);
    +			styleToWrite["strokeweight"] = style.lineWidth + "px";
    +		}
    +		else styleToWrite["stroked"] = "false";
    +		
    +		if (style.fillStyle) {
    +			styleToWrite["filled"] = "true";
    +			var fillColor = jsPlumbUtil.convertStyle(style.fillStyle, true);
    +			styleToWrite["fillcolor"] = fillColor;
    +			_maybeSetOpacity(styleToWrite, fillColor, "fill", component);
    +		}
    +		else styleToWrite["filled"] = "false";
    +		
    +		if(style["dashstyle"]) {
    +			if (component.strokeNode == null) {
    +				component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] }, node, _jsPlumb);				
    +			}
    +			else
    +				component.strokeNode.dashstyle = style["dashstyle"];
    +		}					
    +		else if (style["stroke-dasharray"] && style["lineWidth"]) {
    +			var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",",
    +			parts = style["stroke-dasharray"].split(sep),
    +			styleToUse = "";
    +			for(var i = 0; i < parts.length; i++) {
    +				styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep);
    +			}
    +			if (component.strokeNode == null) {
    +				component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }, node, _jsPlumb);				
    +			}
    +			else
    +				component.strokeNode.dashstyle = styleToUse;
    +		}
    +		
    +		_atts(node, styleToWrite);
    +	},
    +	/*
    +	 * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. 
    +	 */
    +	VmlComponent = function() {				
    +		var self = this, renderer = {};
    +		jsPlumb.jsPlumbUIComponent.apply(this, arguments);		
    +		this.opacityNodes = {
    +			"stroke":null,
    +			"fill":null
    +		};
    +		this.initOpacityNodes = function(vml) {
    +			self.opacityNodes["stroke"] = _node("stroke", [0,0,1,1], {opacity:"0.0"}, vml, self._jsPlumb);
    +			self.opacityNodes["fill"] = _node("fill", [0,0,1,1], {opacity:"0.0"}, vml, self._jsPlumb);							
    +		};
    +		this.setOpacity = function(type, value) {
    +			var node = self.opacityNodes[type];
    +			if (node) node["opacity"] = "" + value;
    +		};
    +		var displayElements = [ ];
    +		this.getDisplayElements = function() { 
    +			return displayElements; 
    +		};
    +		
    +		this.appendDisplayElement = function(el, doNotAppendToCanvas) {
    +			if (!doNotAppendToCanvas) self.canvas.parentNode.appendChild(el);
    +			displayElements.push(el);
    +		};
    +	},	
    +	/*
    +	 * Base class for Vml connectors. extends VmlComponent.
    +	 */
    +	VmlConnector = jsPlumb.ConnectorRenderers.vml = function(params) {
    +		var self = this;
    +		self.strokeNode = null;
    +		self.canvas = null;
    +		var _super = VmlComponent.apply(this, arguments);
    +		var clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : "");
    +		this.paint = function(style) {		
    +			if (style !== null) {				
    +				var segments = self.getSegments(), p = { "path":"" },
    +                    d = [self.x,self.y,self.w,self.h];
    +				
    +				// create path from segments.	
    +				for (var i = 0; i < segments.length; i++) {
    +					p.path += jsPlumb.Segments.vml.SegmentRenderer.getPath(segments[i]);
    +					p.path += " ";
    +				}
    +
    +                //*
    +				if (style.outlineColor) {
    +					var outlineWidth = style.outlineWidth || 1,
    +					outlineStrokeWidth = style.lineWidth + (2 * outlineWidth),
    +					outlineStyle = {
    +						strokeStyle : jsPlumbUtil.convertStyle(style.outlineColor),
    +						lineWidth : outlineStrokeWidth
    +					};
    +					for (var aa in vmlAttributeMap) outlineStyle[aa] = style[aa];
    +					
    +					if (self.bgCanvas == null) {						
    +						p["class"] = clazz;
    +						p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale);
    +						self.bgCanvas = _node("shape", d, p, params.parent, self._jsPlumb, true);						
    +						_pos(self.bgCanvas, d);
    +						self.appendDisplayElement(self.bgCanvas, true);	
    +						self.attachListeners(self.bgCanvas, self);					
    +						self.initOpacityNodes(self.bgCanvas, ["stroke"]);		
    +					}
    +					else {
    +						p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale);
    +						_pos(self.bgCanvas, d);
    +						_atts(self.bgCanvas, p);
    +					}
    +					
    +					_applyStyles(self.bgCanvas, outlineStyle, self);
    +				}
    +				//*/
    +				
    +				if (self.canvas == null) {										
    +					p["class"] = clazz;
    +					p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale);					
    +					self.canvas = _node("shape", d, p, params.parent, self._jsPlumb, true);					                
    +                    //var group = _getGroup(params.parent);                   // test of append everything to a group
    +                    //group.appendChild(self.canvas);                           // sort of works but not exactly;
    +					//params["_jsPlumb"].appendElement(self.canvas, params.parent);    //before introduction of groups
    +
    +					self.appendDisplayElement(self.canvas, true);										
    +					self.attachListeners(self.canvas, self);					
    +					self.initOpacityNodes(self.canvas, ["stroke"]);		
    +				}
    +				else {
    +					p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale);
    +					_pos(self.canvas, d);
    +					_atts(self.canvas, p);
    +				}
    +				
    +				_applyStyles(self.canvas, style, self, self._jsPlumb);
    +			}
    +		};	
    +		
    +		this.reattachListeners = function() {
    +			if (self.canvas) self.reattachListenersForElement(self.canvas, self);
    +		};
    +	},		
    +	
    +	/*
    +	 * 
    +	 * Base class for Vml Endpoints. extends VmlComponent.
    +	 * 
    +	 */
    +	VmlEndpoint = window.VmlEndpoint = function(params) {
    +		VmlComponent.apply(this, arguments);
    +		var vml = null, self = this, opacityStrokeNode = null, opacityFillNode = null;
    +		self.canvas = document.createElement("div");
    +		self.canvas.style["position"] = "absolute";
    +
    +		var clazz = self._jsPlumb.endpointClass + (params.cssClass ? (" " + params.cssClass) : "");
    +
    +		//var group = _getGroup(params.parent);
    +        //group.appendChild(self.canvas);
    +		params["_jsPlumb"].appendElement(self.canvas, params.parent);
    +
    +		this.paint = function(style, anchor) {
    +			var p = { };					
    +			
    +			jsPlumb.sizeCanvas(self.canvas, self.x, self.y, self.w, self.h);
    +			if (vml == null) {
    +				p["class"] = clazz;
    +				vml = self.getVml([0,0, self.w, self.h], p, anchor, self.canvas, self._jsPlumb);				
    +				self.attachListeners(vml, self);
    +
    +				self.appendDisplayElement(vml, true);
    +				self.appendDisplayElement(self.canvas, true);
    +				
    +				self.initOpacityNodes(vml, ["fill"]);			
    +			}
    +			else {				
    +				_pos(vml, [0,0, self.w, self.h]);
    +				_atts(vml, p);
    +			}
    +			
    +			_applyStyles(vml, style, self);
    +		};
    +		
    +		this.reattachListeners = function() {
    +			if (vml) self.reattachListenersForElement(vml, self);
    +		};
    +	};
    +	
    +// ******************************* vml segments *****************************************************	
    +		
    +	jsPlumb.Segments.vml = {
    +		SegmentRenderer : {		
    +			getPath : function(segment) {
    +				return ({
    +					"Straight":function(segment) {
    +						var d = segment.params;
    +						return "m" + _conv(d.x1) + "," + _conv(d.y1) + " l" + _conv(d.x2) + "," + _conv(d.y2) + " e";
    +					},
    +					"Bezier":function(segment) {
    +						var d = segment.params;
    +						return "m" + _conv(d.x1) + "," + _conv(d.y1) + 
    +				   			" c" + _conv(d.cp1x) + "," + _conv(d.cp1y) + "," + _conv(d.cp2x) + "," + _conv(d.cp2y) + "," + _conv(d.x2) + "," + _conv(d.y2) + " e";
    +					},
    +					"Arc":function(segment) {					
    +						var d = segment.params,
    +							xmin = Math.min(d.x1, d.x2),
    +							xmax = Math.max(d.x1, d.x2),
    +							ymin = Math.min(d.y1, d.y2),
    +							ymax = Math.max(d.y1, d.y2),														
    +							sf = segment.anticlockwise ? 1 : 0,
    +							pathType = (segment.anticlockwise ? "at " : "wa "),
    +							makePosString = function() {
    +								var xy = [
    +										null,
    +										[ function() { return [xmin, ymin ];}, function() { return [xmin - d.r, ymin - d.r ];}],
    +										[ function() { return [xmin - d.r, ymin ];}, function() { return [xmin, ymin - d.r ];}],
    +										[ function() { return [xmin - d.r, ymin - d.r ];}, function() { return [xmin, ymin ];}],
    +										[ function() { return [xmin, ymin - d.r ];}, function() { return [xmin - d.r, ymin ];}]
    +									][segment.segment][sf]();
    +
    +								return _conv(xy[0]) + "," + _conv(xy[1]) + "," + _conv(xy[0] + (2*d.r)) + "," + _conv(xy[1] + (2*d.r));
    +							};
    +
    +						
    +						return pathType + makePosString() + "," + _conv(d.x1) + ","
    +								+ _conv(d.y1) + "," + _conv(d.x2) + "," + _conv(d.y2) + " e";						
    +					}
    +						
    +				})[segment.type](segment);	
    +			}
    +		}
    +	};
    +	
    +// ******************************* /vml segments *****************************************************	
    +
    +// ******************************* vml endpoints *****************************************************
    +	
    +	jsPlumb.Endpoints.vml.Dot = function() {
    +		jsPlumb.Endpoints.Dot.apply(this, arguments);
    +		VmlEndpoint.apply(this, arguments);
    +		this.getVml = function(d, atts, anchor, parent, _jsPlumb) { return _node("oval", d, atts, parent, _jsPlumb); };
    +	};
    +	
    +	jsPlumb.Endpoints.vml.Rectangle = function() {
    +		jsPlumb.Endpoints.Rectangle.apply(this, arguments);
    +		VmlEndpoint.apply(this, arguments);
    +		this.getVml = function(d, atts, anchor, parent, _jsPlumb) { return _node("rect", d, atts, parent, _jsPlumb); };
    +	};
    +	
    +	/*
    +	 * VML Image Endpoint is the same as the default image endpoint.
    +	 */
    +	jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image;
    +	
    +	/**
    +	 * placeholder for Blank endpoint in vml renderer.
    +	 */
    +	jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank;
    +	
    +// ******************************* /vml endpoints *****************************************************	
    +
    +// ******************************* vml overlays *****************************************************
    +	
    +	/**
    +	 * VML Label renderer. uses the default label renderer (which adds an element to the DOM)
    +	 */
    +	jsPlumb.Overlays.vml.Label  = jsPlumb.Overlays.Label;
    +	
    +	/**
    +	 * VML Custom renderer. uses the default Custom renderer (which adds an element to the DOM)
    +	 */
    +	jsPlumb.Overlays.vml.Custom = jsPlumb.Overlays.Custom;
    +	
    +	/**
    +	 * Abstract VML arrow superclass
    +	 */
    +	var AbstractVmlArrowOverlay = function(superclass, originalArgs) {
    +    	superclass.apply(this, originalArgs);
    +    	VmlComponent.apply(this, originalArgs);
    +    	var self = this, path = null;
    +    	self.canvas = null; 
    +    	self.isAppendedAtTopLevel = true;
    +    	var getPath = function(d) {    		
    +    		return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) +
    +    		       " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + 
    +    		       " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + 
    +    		       " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + 
    +    		       " x e";
    +    	};
    +    	this.paint = function(params, containerExtents) {
    +    		var p = {}, d = params.d, connector = params.component;
    +			if (params.strokeStyle) {
    +				p["stroked"] = "true";
    +				p["strokecolor"] = jsPlumbUtil.convertStyle(params.strokeStyle, true);    				
    +			}
    +			if (params.lineWidth) p["strokeweight"] = params.lineWidth + "px";
    +			if (params.fillStyle) {
    +				p["filled"] = "true";
    +				p["fillcolor"] = params.fillStyle;
    +			}
    +			var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x),
    +				ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y),
    +				xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x),
    +				ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y),
    +				w = Math.abs(xmax - xmin),
    +				h = Math.abs(ymax - ymin),
    +				dim = [xmin, ymin, w, h];
    +			
    +			// for VML, we create overlays using shapes that have the same dimensions and
    +			// coordsize as their connector - overlays calculate themselves relative to the
    +			// connector (it's how it's been done since the original canvas implementation, because
    +			// for canvas that makes sense).
    +			p["path"] = getPath(d);
    +			p["coordsize"] = (connector.w * scale) + "," + (connector.h * scale);
    +			
    +			dim[0] = connector.x;
    +			dim[1] = connector.y;
    +			dim[2] = connector.w;
    +			dim[3] = connector.h;
    +			
    +    		if (self.canvas == null) {
    +    			var overlayClass = connector._jsPlumb.overlayClass || "";
    +    			var clazz = originalArgs && (originalArgs.length == 1) ? (originalArgs[0].cssClass || "") : "";
    +    			p["class"] = clazz + " " + overlayClass;
    +				self.canvas = _node("shape", dim, p, connector.canvas.parentNode, connector._jsPlumb, true);								
    +				connector.appendDisplayElement(self.canvas, true);
    +				self.attachListeners(self.canvas, connector);
    +				self.attachListeners(self.canvas, self);
    +			}
    +			else {				
    +				_pos(self.canvas, dim);
    +				_atts(self.canvas, p);
    +			}    		
    +    	};
    +    	
    +    	this.reattachListeners = function() {
    +			if (self.canvas) self.reattachListenersForElement(self.canvas, self);
    +		};
    +
    +		this.cleanup = function() {
    +    		if (self.canvas != null) jsPlumb.CurrentLibrary.removeElement(self.canvas);
    +    	};
    +    };
    +	
    +	jsPlumb.Overlays.vml.Arrow = function() {
    +    	AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]);    	
    +    };
    +    
    +    jsPlumb.Overlays.vml.PlainArrow = function() {
    +    	AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]);    	
    +    };
    +    
    +    jsPlumb.Overlays.vml.Diamond = function() {
    +    	AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]);    	
    +    };
    +    
    +// ******************************* /vml overlays *****************************************************    
    +    
    +})();
    \ No newline at end of file
    diff --git a/src/jsPlumb-tests.js b/src/jsPlumb-tests.js
    new file mode 100644
    index 000000000..3d7520369
    --- /dev/null
    +++ b/src/jsPlumb-tests.js
    @@ -0,0 +1,5566 @@
    +// _jsPlumb qunit tests.
    +
    +QUnit.config.reorder = false;
    +
    +var _getContextNode = function() {
    +	return $("#container");
    +};
    +
    +var assertContextExists = function() {
    +	ok(_getContextNode().length == 1, "context node exists");
    +};
    +
    +var assertContextSize = function(elementCount) {
    +	//equal(_getContextNode().children().length - _divs.length, elementCount, 'context has ' + elementCount + ' children');
    +};
    +
    +var assertContextEmpty = function() {
    +	equal(_getContextNode().children.length, 0, "context empty");
    +};
    +
    +var assertEndpointCount = function(elId, count, _jsPlumb) {
    +	equal(_jsPlumb.getTestHarness().endpointCount(elId), count, elId + " has " + count + ((count > 1 || count == 0) ? " endpoints" : " endpoint"));
    +	equal(_jsPlumb.anchorManager.getEndpointsFor(elId).length, count, "anchor manager has " + count + ((count > 1 || count == 0) ? " endpoints" : " endpoint") + " for " + elId);
    +};
    +
    +var assertConnectionCount = function(endpoint, count) {
    +	equal(endpoint.connections.length, count, "endpoint has " + count + " connections");
    +};
    +
    +var assertConnectionByScopeCount = function(scope, count, _jsPlumb) {
    +	equal(_jsPlumb.getTestHarness().connectionCount(scope), count, 'Scope ' + scope + " has " + count + (count > 1) ? "connections" : "connection");
    +};
    +
    +var VERY_SMALL_NUMBER = 0.00000000001;
    +// helper to test that a value is the same as some target, within our tolerance
    +// sometimes the trigonometry stuff needs a little bit of leeway.
    +var within = function(val, target, _ok, msg) {
    +    _ok(Math.abs(val - target) < VERY_SMALL_NUMBER, msg + "[expected: " + target + " got " + val + "] [diff:" + (Math.abs(val - target)) + "]");
    +};
    +
    +var _divs = [];
    +var _addDiv = function(id, parent) {
    +	var d1 = document.createElement("div");
    +	if (parent) parent.append(d1); else _getContextNode().append(d1);
    +	d1 = jsPlumb.CurrentLibrary.getElementObject(d1);
    +	jsPlumb.CurrentLibrary.setAttribute(d1, "id", id);
    +	_divs.push(id);
    +	return d1;
    +};
    +
    +var _triggerEvent = function(el, eventId) {
    +    var o = $(el).offset();
    +    var evt = jQuery.Event(eventId);
    +    evt.which = 0;
    +    evt.pageX = o.left;
    +    evt.pageY = o.top;
    +    $(el).trigger(evt);
    +};
    +
    +var defaults = null,
    +	_cleanup = function(_jsPlumb) {	
    +	
    +	_jsPlumb.reset();
    +	_jsPlumb.Defaults = defaults;
    +	
    +	for (var i in _divs) {
    +		$("#" + _divs[i]).remove();		
    +	}	
    +	_divs.splice(0, _divs.length - 1);
    +
    +	$("#container").empty();
    +};
    +
    +var testSuite = function(renderMode, _jsPlumb) {
    +	
    +
    +	module("jsPlumb", {
    +		teardown: function() { 
    +			_cleanup(_jsPlumb); 
    +		},
    +		setup:function() {
    +			defaults = jsPlumb.extend({}, _jsPlumb.Defaults);
    +		}
    +	});
    +	
    +	// setup the container
    +	var container = document.createElement("div");
    +	container.id = "container";
    +	document.body.appendChild(container);
    +
    +	var jpcl = jsPlumb.CurrentLibrary;
    +
    +	_jsPlumb.setRenderMode(renderMode);
    +
    +	test(renderMode + " : getElementObject", function() {
    +		var e = document.createElement("div");
    +		e.id = "FOO";
    +		document.body.appendChild(e);
    +		var el = jpcl.getElementObject(e);
    +		equal(jpcl.getAttribute(el, "id"), "FOO");
    +	});
    +
    +	test(renderMode + " : getDOMElement", function() {
    +		var e = document.createElement("div");
    +		e.id = "FOO";
    +		document.body.appendChild(e);
    +		var el = jpcl.getElementObject(e);
    +		var e2 = jpcl.getDOMElement(el);
    +		equal(e2.id, "FOO");
    +	});
    +	
    +	test(renderMode + ': _jsPlumb setup', function() {
    +		ok(_jsPlumb, "loaded");
    +	});
    +	
    +	test(renderMode + ': getId', function() {
    +		var d10 = _addDiv('d10');
    +		equal(_jsPlumb.getTestHarness().getId(d10), "d10");
    +	});
    +	
    +	test(renderMode + ': create a simple endpoint', function() {
    +		var d1 = _addDiv("d1");
    +		var e = _jsPlumb.addEndpoint($("#d1"), {});
    +		ok(e, 'endpoint exists');  
    +		assertEndpointCount("d1", 1, _jsPlumb);
    +		ok(e.id != null, "endpoint has had an id assigned");
    +	});
    +	
    +	test(renderMode + ': create and remove a simple endpoint', function() {
    +		var d1 = _addDiv("d1");
    +		var ee = _jsPlumb.addEndpoint(d1, {uuid:"78978597593"});
    +		ok(ee != null, "endpoint exists");
    +		var e = _jsPlumb.getEndpoint("78978597593");
    +		ok(e != null, "the endpoint could be retrieved by UUID");
    +		ok(e.id != null, "the endpoint has had an id assigned to it");
    +		assertEndpointCount("d1", 1, _jsPlumb);
    +		assertContextSize(1); // one Endpoint canvas.
    +		_jsPlumb.deleteEndpoint(ee);	 
    +		assertEndpointCount("d1", 0, _jsPlumb);
    +		e = _jsPlumb.getEndpoint("78978597593");
    +		equal(e, null, "the endpoint has been deleted");
    +		assertContextSize(0); // no Endpoint canvases.
    +	});
    +	
    +	test(renderMode + ': create two simple endpoints, registered using a selector', function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		d1.addClass("window");d2.addClass("window");
    +		var endpoints = _jsPlumb.addEndpoint($(".window"), {});
    +		equal(endpoints.length, 2, "endpoint added to both windows");  
    +		assertEndpointCount("d1", 1, _jsPlumb);
    +		assertEndpointCount("d2", 1, _jsPlumb);
    +	});
    +	
    +	test(renderMode + ': create two simple endpoints, registered using an array of element ids', function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		d1.addClass("window");d2.addClass("window");
    +		var endpoints = _jsPlumb.addEndpoint(["d1", "d2"], {});
    +		equal(endpoints.length, 2, "endpoint added to both windows");  
    +		assertEndpointCount("d1", 1, _jsPlumb);
    +		assertEndpointCount("d2", 1, _jsPlumb);
    +	});
    +	
    +	test(renderMode + ': draggable silently ignored when jquery ui not present', function() {
    +		var d1 = _addDiv("d1");
    +		var e = _jsPlumb.addEndpoint(d1, {isSource:true});
    +		ok(e, 'endpoint exists');
    +	});
    +	
    +	test(renderMode + ': droppable silently ignored when jquery ui not present', function() {
    +		var d1 = _addDiv("d1")
    +		var e = _jsPlumb.addEndpoint(d1, {isTarget:true});
    +		ok(e, 'endpoint exists');
    +	});
    +	
    +	test(renderMode + ': draggable in nested element does not cause extra ids to be created', function() {
    +	  var d = _addDiv("d1");
    +	  var d2 = document.createElement("div");
    +	  d2.setAttribute("foo", "ff");
    +	  d.append(d2);
    +	  var d3 = document.createElement("div");
    +	  d2.appendChild(d3);
    +	  ok(d2.getAttribute("id") == null, "no id on d2");
    +	  _jsPlumb.draggable(d);
    +	  _jsPlumb.addEndpoint(d3);
    +	  ok(d2.getAttribute("id") == null, "no id on d2");
    +	  ok(d3.getAttribute("id") != null, "id on d3");
    +	});
    +	
    +	test(renderMode + ': defaultEndpointMaxConnections', function() {
    +		var d3 = _addDiv("d3"), d4 = _addDiv("d4");
    +		var e3 = _jsPlumb.addEndpoint(d3, {isSource:true});
    +		ok(e3.anchor, 'endpoint 3 has an anchor');
    +		var e4 = _jsPlumb.addEndpoint(d4, {isSource:true});
    +		assertEndpointCount("d3", 1, _jsPlumb);
    +		assertEndpointCount("d4", 1, _jsPlumb);
    +		assertContextSize(2);						// one canvas for each of our two endpoints.
    +		ok(!e3.isFull(), "endpoint 3 is not full.");
    +		_jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4});
    +		assertConnectionCount(e3, 1);   // we have one connection
    +		_jsPlumb.connect({source:d3, target:'d4', sourceEndpoint:e3, targetEndpoint:e4});
    +		assertConnectionCount(e3, 1);  // should have refused the connection; default max is 1.
    +		assertContextSize(3); // now we have one more canvas, for the Connection that was accepted.
    +	});
    +	
    +	test(renderMode + ': specifiedEndpointMaxConnections', function() {
    +		var d5 = _addDiv("d5"), d6 = _addDiv("d6");
    +		var e5 = _jsPlumb.addEndpoint(d5, {isSource:true, maxConnections:3});
    +		ok(e5.anchor, 'endpoint 5 has an anchor');
    +		var e6 = _jsPlumb.addEndpoint(d6, {isSource:true, maxConnections:2});  // this one has max TWO
    +		assertEndpointCount("d5", 1, _jsPlumb); assertEndpointCount("d6", 1, _jsPlumb);
    +		assertContextSize(2);
    +		ok(!e5.isFull(), "endpoint 5 is not full.");
    +		_jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6});
    +		assertConnectionCount(e5, 1);   // we have one connection
    +		assertContextSize(3);
    +		_jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6});
    +		assertConnectionCount(e5, 2);  // two connections
    +		assertContextSize(4);
    +		_jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e6});
    +		assertConnectionCount(e5, 2);  // should have refused; max is 2, for d4.
    +		assertContextSize(4);
    +	});
    +	
    +	test(renderMode + ': noEndpointMaxConnections', function() {
    +		var d3 = _addDiv("d3"), d4 = _addDiv("d4");
    +		var e3 = _jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1});
    +		var e4 = _jsPlumb.addEndpoint(d4, {isSource:true, maxConnections:-1});
    +		_jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4});
    +		assertContextSize(3);
    +		assertConnectionCount(e3, 1);   // we have one connection
    +		_jsPlumb.connect({sourceEndpoint:e3, targetEndpoint:e4});
    +		assertConnectionCount(e3, 2);  // we have two.  etc (default was one. this proves max is working).
    +		assertContextSize(4);
    +		var d5 = _addDiv("d5"), d6 = _addDiv("d6");
    +		var e5 = _jsPlumb.addEndpoint(d3, {isSource:true, maxConnections:-1});
    +		_jsPlumb.connect({sourceEndpoint:e5, targetEndpoint:e4});
    +		assertConnectionCount(e4, 3);
    +		assertContextSize(6);
    +	});
    +
    +// ************** ANCHORS ********************************************	
    +	
    +	test(renderMode + ': anchors equal', function() {
    +		var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb);
    +		var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb);
    +		ok(a1.equals(a2), "anchors are the same");
    +	});
    +	
    +	test(renderMode + ': anchors equal with offsets', function() {
    +		var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb);
    +		var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb);
    +		ok(a1.equals(a2), "anchors are the same");
    +	});
    +	
    +	test(renderMode + ': anchors not equal', function() {
    +		var a1 = _jsPlumb.makeAnchor([0, 1, 0, 1], null, _jsPlumb);
    +		var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb);
    +		ok(!a1.equals(a2), "anchors are different");
    +	});
    +	
    +	test(renderMode + ': anchor not equal with offsets', function() {
    +		var a1 = _jsPlumb.makeAnchor([0, 1, 1, 1, 10, 13], null, _jsPlumb);
    +		var a2 = _jsPlumb.makeAnchor([0, 1, 1, 1], null, _jsPlumb);
    +		ok(!a1.equals(a2), "anchors are different");
    +	});
    +
    +	test(renderMode + ": unknown anchor type should throw Error", function() {
    +		try {
    +			_addDiv("d1");_addDiv("d2");
    +			_jsPlumb.connect({source:"d1", target:"d2", anchor:"FOO"});			
    +		}
    +		catch (e) {
    +			// ok	
    +			ok(e.msg == "jsPlumb: unknown anchor type 'FOO'", "useful error message");		
    +		}
    +	});
    +
    +	test(renderMode + ": unknown anchor type should not throw Error because it is suppressed in Defaults", function() {
    +		try {
    +			_addDiv("d1");_addDiv("d2");
    +			_jsPlumb.Defaults.DoNotThrowErrors = true;
    +			_jsPlumb.connect({source:"d1", target:"d2", anchor:"FOO"});			
    +		}
    +		catch (e) {
    +			// ok	
    +			ok(e.msg != "jsPlumb: unknown anchor type 'FOO'", "no error message");		
    +		}
    +	});
    +	
    +// ************** / ANCHORS ********************************************
    +
    +
    +	test(renderMode + ': detach does not fail when no arguments are provided', function() {
    +		var d3 = _addDiv("d3"), d4 = _addDiv("d4");
    +		_jsPlumb.connect({source:d3, target:d4});
    +		_jsPlumb.detach();	
    +		expect(0);
    +	});
    +
    +    // test that detach does not fire an event by default
    +	test(renderMode + ': _jsPlumb.detach should fire detach event by default', function() {
    +		var d5 = _addDiv("d5"), d6 = _addDiv("d6");
    +		var conn = _jsPlumb.connect({source:d5, target:d6});
    +		var eventCount = 0;
    +		_jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; });
    +		_jsPlumb.detach(conn);
    +		equal(eventCount, 1);
    +	});
    +
    +    // test that detach does not fire an event by default
    +	test(renderMode + ': _jsPlumb.detach should fire detach event by default, using params object', function() {
    +		var d5 = _addDiv("d5"), d6 = _addDiv("d6");
    +		var conn = _jsPlumb.connect({source:d5, target:d6});
    +		var eventCount = 0;
    +		_jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; });
    +		_jsPlumb.detach({connection:conn});
    +		equal(eventCount, 1);
    +	});
    +
    +    // test that detach fires an event when instructed to do so
    +	test(renderMode + ': _jsPlumb.detach should not fire detach event when instructed to not do so', function() {
    +		var d5 = _addDiv("d5"), d6 = _addDiv("d6");
    +		var conn = _jsPlumb.connect({source:d5, target:d6});
    +		var eventCount = 0;
    +		_jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; });
    +		_jsPlumb.detach(conn, {fireEvent:false});
    +		equal(eventCount, 0);
    +	});
    +	
    +	// issue 81
    +	test(renderMode + ': _jsPlumb.detach should fire only one detach event (pass Connection as argument)', function() {
    +		var d5 = _addDiv("d5"), d6 = _addDiv("d6");
    +		var conn = _jsPlumb.connect({source:d5, target:d6});
    +		var eventCount = 0;
    +		_jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; });
    +		_jsPlumb.detach(conn);
    +		equal(eventCount, 1);
    +	});
    +	
    +	// issue 81
    +	test(renderMode + ': _jsPlumb.detach should fire only one detach event (pass Connection as param in argument)', function() {
    +		var d5 = _addDiv("d5"), d6 = _addDiv("d6");
    +		var conn = _jsPlumb.connect({source:d5, target:d6});
    +		var eventCount = 0;
    +		_jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; });
    +		_jsPlumb.detach({connection:conn});
    +		equal(eventCount, 1);
    +	});
    +	
    +	// issue 81
    +	test(renderMode + ': detach should fire only one detach event (pass source and targets as strings as arguments in params object)', function() {
    +		var d5 = _addDiv("d5"), d6 = _addDiv("d6");
    +		var conn = _jsPlumb.connect({source:d5, target:d6});
    +		var eventCount = 0;
    +		_jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; });
    +		_jsPlumb.detach({source:"d5", target:"d6"});
    +		equal(eventCount, 1);
    +	});
    +	
    +	// issue 81
    +	test(renderMode + ': detach should fire only one detach event (pass source and targets as divs as arguments in params object)', function() {
    +		var d5 = _addDiv("d5"), d6 = _addDiv("d6");
    +		var conn = _jsPlumb.connect({source:d5, target:d6});
    +		var eventCount = 0;
    +		_jsPlumb.bind("jsPlumbConnectionDetached", function(c) { eventCount++; });
    +		_jsPlumb.detach({source:d5, target:d6, fireEvent:true});
    +		equal(eventCount, 1);
    +	});
    +	
    +	//TODO make sure you run this test with a single detach call, to ensure that
    +	// single detach calls result in the connection being removed. detachEveryConnection can
    +	// just blow away the connectionsByScope array and recreate it.
    +	test(renderMode + ': getConnections (simple case, default scope)', function() {
    +		var d5 = _addDiv("d5"), d6 = _addDiv("d6");
    +		_jsPlumb.connect({source:d5, target:d6});
    +		assertContextSize(3);
    +		var c = _jsPlumb.getConnections();  // will get all connections in the default scope.
    +		equal(c.length, 1, "there is one connection");
    +	});
    +	
    +	test(renderMode + ': getConnections (simple case, default scope; detach by element id using params object)', function() {
    +		var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7");
    +		_jsPlumb.connect({source:d5, target:d6});
    +		_jsPlumb.connect({source:d6, target:d7});
    +		assertContextSize(6);
    +		var c = _jsPlumb.getConnections();  // will get all connections
    +		equal(c.length, 2, "there are two connections initially");
    +		_jsPlumb.detach({source:'d5', target:'d6'});
    +		c = _jsPlumb.getConnections();  // will get all connections
    +		equal(c.length, 1, "after detaching one, there is now one connection.");
    +		assertContextSize(5);
    +	});
    +
    +    test(renderMode + ': getConnections (simple case, default scope; detach by id using params object)', function() {
    +		var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7");
    +		_jsPlumb.connect({source:d5, target:d6});
    +		_jsPlumb.connect({source:d6, target:d7});
    +		assertContextSize(6);
    +		var c = _jsPlumb.getConnections();  // will get all connections
    +		equal(c.length, 2, "there are two connections initially");
    +		_jsPlumb.detach({source:"d5", target:"d6"});
    +		c = _jsPlumb.getConnections();  // will get all connections
    +		equal(c.length, 1, "after detaching one, there is now one connection.");
    +		assertContextSize(5);
    +	});
    +
    +	test(renderMode + ': getConnections (simple case, default scope; detach by element object using params object)', function() {
    +		var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7");
    +		_jsPlumb.connect({source:d5, target:d6});
    +		_jsPlumb.connect({source:d6, target:d7});
    +		assertContextSize(6);
    +		var c = _jsPlumb.getConnections();  // will get all connections
    +		equal(c.length, 2, "there are two connections initially");
    +		_jsPlumb.detach({source:d5, target:d6});
    +		c = _jsPlumb.getConnections();  // will get all connections
    +		equal(c.length, 1, "after detaching one, there is now one connection.");
    +		assertContextSize(5);
    +	});
    +	
    +	test(renderMode + ': getConnections (simple case, default scope; detach by Connection)', function() {
    +		var d5 = _addDiv("d5"), d6 = _addDiv("d6"), d7 = _addDiv("d7");
    +		var c56 = _jsPlumb.connect({source:d5, target:d6});
    +		var c67 = _jsPlumb.connect({source:d6, target:d7});
    +		assertContextSize(6);
    +		var c = _jsPlumb.getConnections();  // will get all connections
    +		equal(c.length, 2, "there are two connections initially");
    +		_jsPlumb.detach(c56);
    +		assertContextSize(5);		// check that the connection canvas was removed.
    +		c = _jsPlumb.getConnections();  // will get all connections
    +		equal(c.length, 1, "after detaching one, there is now one connection.");		
    +	});
    +
    +// beforeDetach functionality
    +	
    +	test(renderMode + ": detach; beforeDetach on connect call returns false", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { return false; }});
    +		var beforeDetachCount = 0;
    +		_jsPlumb.bind("beforeDetach", function(connection) {
    +			beforeDetachCount++;			
    +		});
    +		equal(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially");
    +		_jsPlumb.detach(c);
    +		equal(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied");
    +		equal(beforeDetachCount, 0, "jsplumb before detach was not called");
    +	});
    +	
    +	test(renderMode + ": detach; beforeDetach on connect call returns true", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { return true; }});
    +		equal(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially");
    +		_jsPlumb.detach(c);
    +		equal(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed");
    +	});
    +
    +	test(renderMode + ": detach; beforeDetach on connect call throws an exception; we treat it with the contempt it deserves and pretend it said the detach was ok.", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var c = _jsPlumb.connect({source:d1, target:d2, beforeDetach:function(conn) { throw "i am an example of badly coded beforeDetach, but i don't break jsPlumb "; }});
    +		equal(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially");
    +		_jsPlumb.detach(c);
    +		equal(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed");
    +	});
    +	
    +	test(renderMode + ": detach; beforeDetach on addEndpoint call to source Endpoint returns false", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return false; } }),
    +		e2 = _jsPlumb.addEndpoint(d2, { isTarget:true });
    +		var c = _jsPlumb.connect({source:e1,target:e2});
    +		var beforeDetachCount = 0;
    +		_jsPlumb.bind("beforeDetach", function(connection) {
    +			beforeDetachCount++;			
    +		});
    +		equal(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially");
    +		_jsPlumb.detach(c);
    +		equal(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied");
    +		equal(beforeDetachCount, 0, "jsplumb before detach was not called");
    +	});
    +	
    +	test(renderMode + ": detach; beforeDetach on addEndpoint call to source Endpoint returns true", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return true; } }),
    +		e2 = _jsPlumb.addEndpoint(d2, { isTarget:true });
    +		var c = _jsPlumb.connect({source:e1,target:e2});
    +		equal(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially");
    +		_jsPlumb.detach(c);
    +		equal(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was allowed");
    +	});
    +	
    +
    +	test(renderMode + ": Endpoint.detach; beforeDetach on addEndpoint call to source Endpoint returns false; Endpoint.detach returns false too (the UI needs it to)", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var e1 = _jsPlumb.addEndpoint(d1, { isSource:true, beforeDetach:function(conn) { return false; } }),
    +		e2 = _jsPlumb.addEndpoint(d2, { isTarget:true });
    +		var c = _jsPlumb.connect({source:e1,target:e2});
    +		equal(c.endpoints[0].connections.length, 1, "source endpoint has a connection initially");
    +		var success = e1.detach(c);
    +		equal(c.endpoints[0].connections.length, 1, "source endpoint has a connection after detach call was denied");
    +		ok(!success, "Endpoint reported detach did not execute");
    +	});
    +
    +	test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns false", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }),
    +		e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } });
    +		var c = _jsPlumb.connect({source:e1,target:e2});
    +		equal(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially");
    +		_jsPlumb.detach(c);
    +		equal(c.endpoints[1].connections.length, 1, "target endpoint has a connection after detach call was denied");
    +	});
    +
    +    test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns false but detach is forced", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }),
    +		e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } });
    +		var c = _jsPlumb.connect({source:e1,target:e2});
    +		equal(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially");
    +		_jsPlumb.detach(c, {forceDetach:true});
    +        equal(c.endpoints[0].connections.length, 0, "source endpoint has no connections after detach call was denied but forced anyway");
    +		equal(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was denied but forced anyway");
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.detach; beforeDetach on addEndpoint call to target Endpoint returns true", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }),
    +		e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return true; } });
    +		var c = _jsPlumb.connect({source:e1,target:e2});
    +		equal(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially");
    +		_jsPlumb.detach(c);
    +		equal(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was allowed");
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.detach; beforeDetach bound to _jsPlumb returns false", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }),
    +		e2 = _jsPlumb.addEndpoint(d2, { isTarget:true });
    +		var c = _jsPlumb.connect({source:e1,target:e2});
    +		var beforeDetachCount = 0;
    +		_jsPlumb.bind("beforeDetach", function(connection) {
    +			ok(connection.sourceId === "d1", "connection is provided and configured with correct source");
    +			ok(connection.targetId === "d2", "connection is provided and configured with correct target");
    +			beforeDetachCount++;
    +			return false;
    +		});
    +		equal(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially");
    +		_jsPlumb.detach(c);
    +		equal(c.endpoints[1].connections.length, 1, "target endpoint has a connection after detach call was denied");
    +		equal(beforeDetachCount, 1, "beforeDetach was called only one time");
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.detach; beforeDetach bound to _jsPlumb returns true", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }),
    +		e2 = _jsPlumb.addEndpoint(d2, { isTarget:true });
    +		var c = _jsPlumb.connect({source:e1,target:e2});
    +		_jsPlumb.bind("beforeDetach", function(connection) {
    +			ok(connection.sourceId === "d1", "connection is provided and configured with correct source");
    +			ok(connection.targetId === "d2", "connection is provided and configured with correct target");
    +			return true;
    +		});
    +		equal(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially");
    +		_jsPlumb.detach(c);
    +		equal(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detach call was denied");
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.detachAllConnections ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }),
    +		e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } });
    +		var c = _jsPlumb.connect({source:e1,target:e2});
    +		equal(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially");
    +		_jsPlumb.detachAllConnections(d1);
    +		equal(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachAll was called");
    +	});
    +
    +	test(renderMode + ": _jsPlumb.detachEveryConnection ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }),
    +		e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } });
    +		var c = _jsPlumb.connect({source:e1,target:e2});
    +		equal(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially");
    +		_jsPlumb.detachEveryConnection();
    +		equal(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachEveryConnection was called");
    +	});
    +
    +	test(renderMode + ": Endpoint.detachAll ; beforeDetach on addEndpoint call to target Endpoint returns false but we should detach anyway", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var e1 = _jsPlumb.addEndpoint(d1, { isSource:true }),
    +		e2 = _jsPlumb.addEndpoint(d2, { isTarget:true, beforeDetach:function(conn) { return false; } });
    +		var c = _jsPlumb.connect({source:e1,target:e2});
    +		equal(c.endpoints[1].connections.length, 1, "target endpoint has a connection initially");
    +		e1.detachAll();
    +		equal(c.endpoints[1].connections.length, 0, "target endpoint has no connections after detachAllConnections was called");
    +	});
    +	
    +// ******** end of beforeDetach tests **************
    +
    +// detachEveryConnection/detachAllConnections fireEvent overrides tests
    +
    +    test(renderMode + ": _jsPlumb.detachEveryConnection fires events", function() {
    +        var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0;
    +        _jsPlumb.bind("jsPlumbConnection", function() { connCount++; });
    +        _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; });
    +        _jsPlumb.connect({source:d1, target:d2});
    +        _jsPlumb.connect({source:d1, target:d2});
    +        equal(connCount, 2, "two connections registered");
    +        _jsPlumb.detachEveryConnection();
    +        equal(connCount, 0, "no connections registered");
    +    });
    +
    +    test(renderMode + ": _jsPlumb.detachEveryConnection doesn't fire events when instructed not to", function() {
    +        var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0;
    +        _jsPlumb.bind("jsPlumbConnection", function() { connCount++; });
    +        _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; });
    +        _jsPlumb.connect({source:d1, target:d2});
    +        _jsPlumb.connect({source:d1, target:d2});
    +        equal(connCount, 2, "two connections registered");
    +        _jsPlumb.detachEveryConnection({fireEvent:false});
    +        equal(connCount, 2, "two connections registered");
    +    });
    +
    +     test(renderMode + ": _jsPlumb.detachAllConnections fires events", function() {
    +        var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0,
    +            e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2);
    +        _jsPlumb.bind("jsPlumbConnection", function() { connCount++; });
    +        _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; });
    +        _jsPlumb.connect({source:d1, target:d2});
    +        _jsPlumb.connect({source:d1, target:d2});
    +        equal(connCount, 2, "two connections registered");
    +        _jsPlumb.detachAllConnections("d1");
    +        equal(connCount, 0, "no connections registered");
    +    });
    +
    +    test(renderMode + ": _jsPlumb.detachAllConnections doesn't fire events when instructed not to", function() {
    +        var d1 = _addDiv("d1"), d2 = _addDiv("d2"), connCount = 0,
    +            e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2);
    +        _jsPlumb.bind("jsPlumbConnection", function() { connCount++; });
    +        _jsPlumb.bind("jsPlumbConnectionDetached", function() { connCount--; });
    +        _jsPlumb.connect({source:d1, target:d2});
    +        _jsPlumb.connect({source:d1, target:d2});
    +        equal(connCount, 2, "two connections registered");
    +        _jsPlumb.detachAllConnections("d1", {fireEvent:false});
    +        equal(connCount, 2, "two connections registered");
    +    });
    +	
    +	test(renderMode + ': getConnections (scope testScope)', function() {
    +		var d5 = _addDiv("d5"), d6 = _addDiv("d6");
    +		_jsPlumb.connect({source:d5, target:d6, scope:'testScope'});
    +		var c = _jsPlumb.getConnections("testScope");  // will get all connections in testScope	
    +		equal(c.length, 1, "there is one connection");
    +		equal(c[0].sourceId, 'd5', "the connection's source is d5");
    +		equal(c[0].targetId, 'd6', "the connection's source is d6");
    +		c = _jsPlumb.getConnections();  // will get all connections in default scope; should be none.
    +		equal(c.length, 0, "there are no connections in the default scope");
    +	});
    +	
    +	test(renderMode + ': _jsPlumb.getAllConnections (filtered by scope)', function() {
    +		var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10');
    +		_jsPlumb.connect({source:d8, target:d9, scope:'testScope'});
    +		_jsPlumb.connect({source:d9, target:d10}); // default scope
    +		var c = _jsPlumb.getAllConnections();  // will get all connections	
    +		equal(c[_jsPlumb.getDefaultScope()].length, 1);
    +		equal(c['testScope'].length, 1);	
    +		// now supply a list of scopes
    +		c = _jsPlumb.getConnections();  	
    +		equal(c.length, 1);
    +		c = _jsPlumb.getConnections("testScope");
    +		equal(c.length, 1, "there is one connection in 'testScope'");
    +	});
    +	
    +	test(renderMode + ': _jsPlumb.getConnections (filtered by scope and sourceId)', function() {
    +		var d8 = _addDiv("d8"), d9 = _addDiv("d9"), d10 = _addDiv('d10');
    +		_jsPlumb.connect({source:d8, target:d9, scope:'testScope'});
    +		_jsPlumb.connect({source:d9, target:d8, scope:'testScope'});
    +		_jsPlumb.connect({source:d9, target:d10}); // default scope
    +		var c = _jsPlumb.getConnections({scope:'testScope', source:'d8'});  // will get all connections with sourceId 'd8'	
    +		equal(c.length, 1, "there is one connection in 'testScope' from d8");	
    +	});
    +	
    +	test(renderMode + ': _jsPlumb.getConnections (filtered by scope, source id and target id)', function() {
    +		var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13');
    +		_jsPlumb.connect({source:d11, target:d12, scope:'testScope'});
    +		_jsPlumb.connect({source:d12, target:d13, scope:'testScope'});
    +		_jsPlumb.connect({source:d11, target:d13, scope:'testScope'});
    +		var c = _jsPlumb.getConnections({scope:'testScope', source:'d11', target:'d13'});  
    +		equal(c.length, 1, "there is one connection from d11 to d13");	
    +	});
    +	
    +	test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes)', function() {
    +		var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13');
    +		_jsPlumb.connect({source:d11, target:d12, scope:'testScope'});
    +		_jsPlumb.connect({source:d12, target:d13, scope:'testScope2'});
    +		_jsPlumb.connect({source:d11, target:d13, scope:'testScope3'});
    +		var c = _jsPlumb.getConnections({scope:['testScope','testScope3']});  
    +		equal(c['testScope'].length, 1, 'there is one connection in testScope');
    +		equal(c['testScope3'].length, 1, 'there is one connection in testScope3');
    +		equal(c['testScope2'], null, 'there are no connections in testScope2');
    +	});
    +	
    +	test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes and source ids)', function() {
    +		var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13');
    +		_jsPlumb.connect({source:d11, target:d12, scope:'testScope'});
    +		_jsPlumb.connect({source:d13, target:d12, scope:'testScope'});
    +		_jsPlumb.connect({source:d12, target:d13, scope:'testScope2'});
    +		_jsPlumb.connect({source:d11, target:d13, scope:'testScope3'});
    +		var c = _jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11']});  
    +		equal(c['testScope'].length, 1, 'there is one connection in testScope');
    +		equal(c['testScope3'].length, 1, 'there is one connection in testScope3');
    +		equal(c['testScope2'], null, 'there are no connections in testScope2');
    +	});
    +	
    +	test(renderMode + ': _jsPlumb.getConnections (filtered by a list of scopes, source ids and target ids)', function() {
    +		_jsPlumb.detachEveryConnection();
    +		var d11 = _addDiv("d11"), d12 = _addDiv("d12"), d13 = _addDiv('d13'), d14=_addDiv("d14"), d15=_addDiv("d15");
    +		_jsPlumb.connect({source:d11, target:d12, scope:'testScope'});
    +		_jsPlumb.connect({source:d13, target:d12, scope:'testScope'});
    +		_jsPlumb.connect({source:d11, target:d14, scope:'testScope'});
    +		_jsPlumb.connect({source:d11, target:d15, scope:'testScope'});
    +		_jsPlumb.connect({source:d12, target:d13, scope:'testScope2'});
    +		_jsPlumb.connect({source:d11, target:d13, scope:'testScope3'});
    +		assertContextSize(18);
    +		var c = _jsPlumb.getConnections({scope:['testScope','testScope3'], source:['d11'], target:['d14', 'd15']});  
    +		equal(c['testScope'].length, 2, 'there are two connections in testScope');
    +		equal(c['testScope3'], null, 'there are no connections in testScope3');
    +		equal(c['testScope2'], null, 'there are no connections in testScope2');
    +		var anEntry = c['testScope'][0];
    +		ok(anEntry.endpoints[0] != null, "Source endpoint is set");
    +		ok(anEntry.endpoints[1] != null, "Target endpoint is set");
    +		equal(anEntry.source.attr("id"), "d11", "Source is div d11");
    +		equal(anEntry.target.attr("id"), "d14", "Target is div d14");
    +	});
    +	
    +	test(renderMode + ': getEndpoints, one Endpoint added by addEndpoint, get Endpoints by selector', function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		_jsPlumb.addEndpoint(d1);
    +		var e = _jsPlumb.getEndpoints(d1);
    +		equal(e.length, 1, "there is one endpoint for element d1");
    +	});
    +	
    +	test(renderMode + ': getEndpoints, one Endpoint added by addEndpoint, get Endpoints by id', function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		_jsPlumb.addEndpoint(d1);
    +		var e = _jsPlumb.getEndpoints("d1");
    +		equal(e.length, 1, "there is one endpoint for element d1");
    +	});
    +
    +	test(renderMode + ': addEndpoint, css class on anchor added to endpoint artefact and element', function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var ep =_jsPlumb.addEndpoint(d1, { anchor:[0,0,1,1,0,0,"foo" ]});
    +		ok($(ep.canvas).hasClass("_jsPlumb_endpoint_anchor_foo"), "class set on endpoint");
    +		ok(d1.hasClass("_jsPlumb_endpoint_anchor_foo"), "class set on element");
    +		_jsPlumb.deleteEndpoint(ep);
    +		ok(!d1.hasClass("_jsPlumb_endpoint_anchor_foo"), "class removed from element");
    +	});
    +	
    +	test(renderMode + ': connection event listeners', function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var returnedParams = null/*, returnedParams2 = null*/;
    +		_jsPlumb.bind("jsPlumbConnection", function(params) {
    +				returnedParams = $.extend({}, params);
    +		});
    +		var c = _jsPlumb.connect({source:d1, target:d2});
    +		ok(returnedParams != null, "new connection listener event was fired");
    +		ok(returnedParams.connection != null, 'connection is set');
    +		equal(returnedParams.sourceId, "d1", 'sourceid is set');
    +		equal(returnedParams.targetId, "d2", 'targetid is set');
    +		equal(returnedParams.source.attr("id"), "d1", 'source is set');
    +		equal(returnedParams.target.attr("id"), "d2" , 'target is set');
    +		ok(returnedParams.sourceEndpoint != null, "source endpoint is not null");
    +		ok(returnedParams.targetEndpoint != null, "target endpoint is not null");
    +		_jsPlumb.bind("jsPlumbConnectionDetached", function(params) {
    +			returnedParams = $.extend({}, params);
    +		});
    +		_jsPlumb.detach(c);
    +		ok(returnedParams.connection != null, 'connection is set');		
    +	});
    +	
    +	test(renderMode + ': detach event listeners (detach by connection)', function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var returnedParams = null;
    +		_jsPlumb.bind("jsPlumbConnectionDetached", function(params) {
    +				returnedParams = $.extend({}, params);
    +		});
    +		var conn = _jsPlumb.connect({source:d1, target:d2});
    +		_jsPlumb.detach(conn);
    +		ok(returnedParams != null, "removed connection listener event was fired");
    +		ok(returnedParams.connection != null, "removed connection listener event passed in connection");
    +	});
    +	
    +	test(renderMode + ': detach event listeners (detach by element ids)', function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var returnedParams = null;
    +		_jsPlumb.bind("jsPlumbConnectionDetached", function(params) {
    +				returnedParams = $.extend({}, params);
    +		});
    +		var conn = _jsPlumb.connect({source:d1, target:d2});
    +		_jsPlumb.detach({source:"d1",target:"d2"});
    +		ok(returnedParams != null, "removed connection listener event was fired");
    +	});
    +	
    +	test(renderMode + ': detach event listeners (detach by elements)', function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var returnedParams = null;
    +		_jsPlumb.bind("jsPlumbConnectionDetached", function(params) {
    +				returnedParams = $.extend({}, params);
    +			});
    +		var conn = _jsPlumb.connect({source:d1, target:d2});
    +		_jsPlumb.detach({source:d1,target:d2});
    +		ok(returnedParams != null, "removed connection listener event was fired");
    +	});
    +	
    +	test(renderMode + ': detach event listeners (via Endpoint.detach method)', function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var e1 = _jsPlumb.addEndpoint(d1, {});
    +		var e2 = _jsPlumb.addEndpoint(d2, {});
    +		var returnedParams = null;
    +		_jsPlumb.bind("connectionDetached", function(params) {
    +				returnedParams = $.extend({}, params);
    +			});
    +		var conn = _jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2});		
    +		e1.detach(conn);
    +		ok(returnedParams != null, "removed connection listener event was fired");		
    +	});
    +	
    +	test(renderMode + ': detach event listeners (via Endpoint.detachFrom method)', function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var e1 = _jsPlumb.addEndpoint(d1, {});
    +		var e2 = _jsPlumb.addEndpoint(d2, {});
    +		var returnedParams = null;
    +		_jsPlumb.bind("jsPlumbConnectionDetached", function(params) {
    +				returnedParams = $.extend({}, params);
    +			});
    +		_jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2});
    +		assertContextSize(3);
    +		e1.detachFrom(e2);
    +		ok(returnedParams != null, "removed connection listener event was fired");
    +		assertContextSize(2);
    +	});
    +	
    +	test(renderMode + ': detach event listeners (via Endpoint.detachAll method)', function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var e1 = _jsPlumb.addEndpoint(d1, {});
    +		var e2 = _jsPlumb.addEndpoint(d2, {});
    +		var returnedParams = null;
    +		_jsPlumb.bind("jsPlumbConnectionDetached", function(params) {
    +				returnedParams = $.extend({}, params);
    +			});
    +		_jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2});
    +		assertContextSize(3);
    +		e1.detachAll();
    +		ok(returnedParams != null, "removed connection listener event was fired");
    +		assertContextSize(2);
    +	});
    +	
    +	test(renderMode + ': detach event listeners (via _jsPlumb.deleteEndpoint method)', function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var e1 = _jsPlumb.addEndpoint(d1, {});
    +		var e2 = _jsPlumb.addEndpoint(d2, {});
    +		var returnedParams = null;
    +		_jsPlumb.bind("jsPlumbConnectionDetached", function(params) {
    +				returnedParams = $.extend({}, params);
    +			});
    +		_jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2});
    +		assertContextSize(3);
    +		_jsPlumb.deleteEndpoint(e1);
    +		ok(returnedParams != null, "removed connection listener event was fired");
    +		assertContextSize(1);
    +	});
    +	
    +	test(renderMode + ': detach event listeners (ensure cleared by _jsPlumb.reset)', function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var returnedParams = null;
    +		_jsPlumb.bind("jsPlumbConnectionDetached", function(params) {
    +				returnedParams = $.extend({}, params);
    +			});
    +		var conn = _jsPlumb.connect({source:d1, target:d2});
    +		_jsPlumb.detach({source:d1,target:d2, fireEvent:true});
    +		ok(returnedParams != null, "removed connection listener event was fired");
    +		returnedParams = null;
    +		
    +		_jsPlumb.reset();
    +		var conn = _jsPlumb.connect({source:d1, target:d2});
    +		_jsPlumb.detach(d1,d2);
    +		ok(returnedParams == null, "connection listener was cleared by _jsPlumb.reset()");
    +	});
    +	
    +	test(renderMode + ': connection events that throw errors', function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var returnedParams = null, returnedParams2 = null;
    +		_jsPlumb.bind("jsPlumbConnection", function(params) {
    +				returnedParams = $.extend({}, params);
    +				throw "oh no!";
    +			});
    +		_jsPlumb.connect({source:d1, target:d2});
    +		var d3 = _addDiv("d3"), d4 = _addDiv("d4");
    +		_jsPlumb.connect({source:d3, target:d4});
    +		ok(returnedParams != null, "new connection listener event was fired; we threw an error, _jsPlumb survived.");
    +	});
    +
    +	test(renderMode + ': unbinding connection event listeners, connection', function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var count = 0;
    +		_jsPlumb.bind("jsPlumbConnection", function(params) {
    +			count++;
    +		});
    +		var c = _jsPlumb.connect({source:d1, target:d2});
    +		ok(count == 1, "received one event");
    +		_jsPlumb.unbind("jsPlumbConnection");
    +		var c2 = _jsPlumb.connect({source:d1, target:d2});
    +		ok(count == 1, "still received only one event");
    +		
    +		_jsPlumb.bind("jsPlumbConnectionDetached", function(params) {
    +			count--;
    +		});
    +		_jsPlumb.detach(c);
    +		ok(count == 0, "count of events is now zero");		
    +	});
    +
    +	test(renderMode + ': unbinding connection event listeners, detach', function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var count = 0;
    +		_jsPlumb.bind("jsPlumbConnection", function(params) {
    +			count++;
    +		});
    +		var c = _jsPlumb.connect({source:d1, target:d2});
    +		ok(count == 1, "received one event");		
    +		var c2 = _jsPlumb.connect({source:d1, target:d2});
    +		ok(count == 2, "received two events");
    +		
    +		_jsPlumb.bind("jsPlumbConnectionDetached", function(params) {
    +			count--;
    +		});
    +		_jsPlumb.detach(c);
    +		ok(count == 1, "count of events is now one");		
    +		_jsPlumb.unbind("jsPlumbConnectionDetached");
    +		_jsPlumb.detach(c2);
    +		ok(count == 1, "count of events is still one");		
    +	});
    +
    +	test(renderMode + ': unbinding connection event listeners, all listeners', function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var count = 0;
    +		_jsPlumb.bind("jsPlumbConnection", function(params) {
    +			count++;
    +		});
    +		var c = _jsPlumb.connect({source:d1, target:d2}),
    +			c2 = _jsPlumb.connect({source:d1, target:d2}),
    +			c3 = _jsPlumb.connect({source:d1, target:d2});
    +
    +		ok(count == 3, "received three events");					
    +		
    +		_jsPlumb.bind("jsPlumbConnectionDetached", function(params) {
    +			count--;
    +		});
    +		_jsPlumb.detach(c);
    +		ok(count == 2, "count of events is now two");	
    +			
    +		_jsPlumb.unbind();  // unbind everything
    +
    +		_jsPlumb.detach(c2);
    +		_jsPlumb.detach(c3);
    +		_jsPlumb.connect({source:d1, target:d2})
    +		_jsPlumb.connect({source:d1, target:d2})
    +		_jsPlumb.connect({source:d1, target:d2})
    +		_jsPlumb.connect({source:d1, target:d2})
    +
    +		ok(count == 2, "count of events is still two");		
    +	});
    +	
    +	test(renderMode + ": Endpoint.detachFrom", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17");
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:true});
    +		ok(e16.anchor, 'endpoint 16 has an anchor');
    +		var e17 = _jsPlumb.addEndpoint(d17, {isSource:true});
    +		var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17});
    +		assertConnectionCount(e16, 1);
    +		assertConnectionCount(e17, 1);
    +		assertContextSize(3);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb);
    +		e16.detachFrom(e17);	
    +		//assertContextSize(2);				// the endpoint canvases should remain
    +		// but the connection should be gone, meaning not registered by _jsPlumb and not registered on either Endpoint:
    +		assertConnectionCount(e16, 0);
    +		assertConnectionCount(e17, 0);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb);  
    +	});
    +	
    +	test(renderMode + ": Endpoint.detachFromConnection", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17");
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:true});
    +		ok(e16.anchor, 'endpoint 16 has an anchor');
    +		var e17 = _jsPlumb.addEndpoint(d17, {isSource:true});
    +		var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17});
    +		assertConnectionCount(e16, 1);
    +		assertConnectionCount(e17, 1);
    +		assertContextSize(3);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb);
    +		e16.detachFromConnection(conn);	
    +		assertContextSize(3);				// all canvases should remain; the connection was not removed.
    +		// but endpoint e16 should have no connections now.
    +		assertConnectionCount(e16, 0);
    +		assertConnectionCount(e17, 1);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb);  
    +	});
    +	
    +	test(renderMode + ": Endpoint.detachAll", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18");
    +		var e16 = _jsPlumb.addEndpoint($("#d16"), {isSource:true,maxConnections:-1});
    +		ok(e16.anchor, 'endpoint 16 has an anchor');
    +		var e17 = _jsPlumb.addEndpoint($("#d17"), {isSource:true});
    +		var e18 = _jsPlumb.addEndpoint($("#d18"), {isSource:true});
    +		_jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17});
    +		_jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e18});
    +		assertConnectionCount(e16, 2);
    +		assertConnectionCount(e17, 1);
    +		assertConnectionCount(e18, 1);
    +		assertContextSize(5);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 2, _jsPlumb);  
    +		e16.detachAll();
    +		assertConnectionCount(e16, 0);
    +		assertConnectionCount(e17, 0);
    +		assertContextSize(3);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb);
    +	});
    +	
    +	test(renderMode + ": Endpoint.detach", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17");
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:true});
    +		ok(e16.anchor, 'endpoint 16 has an anchor');
    +		var e17 = _jsPlumb.addEndpoint(d17, {isSource:true});
    +		var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17});
    +		assertConnectionCount(e16, 1);
    +		assertConnectionCount(e17, 1);
    +		assertContextSize(3);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb);
    +		e16.detach(conn);	
    +		assertContextSize(2);				// the endpoint canvases should remain
    +		// but the connection should be gone, meaning not registered by _jsPlumb and not registered on either Endpoint:
    +		assertConnectionCount(e16, 0);
    +		assertConnectionCount(e17, 0);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb);  
    +	});
    +	
    +	test(renderMode + ": Endpoint.isConnectedTo", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17");
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:true});
    +		ok(e16.anchor, 'endpoint 16 has an anchor');
    +		var e17 = _jsPlumb.addEndpoint(d17, {isSource:true});
    +		var conn = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17});
    +		equal(e16.isConnectedTo(e17), true, "e16 and e17 are connected");  
    +	});
    +
    +	asyncTest(renderMode + " jsPlumbUtil.setImage on Endpoint, with supplied onload", function() {
    +        var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +        e = {
    +            endpoint:[ "Image", {
    +                src:"../img/endpointTest1.png",
    +                onload:function(imgEp) {                	
    +                	_jsPlumb.repaint("d1");
    +                    ok(imgEp.img.src.indexOf("endpointTest1.png") != -1, "image source is correct");                                        
    +                    ok(imgEp.img.src.indexOf("endpointTest1.png") != -1, "image elementsource is correct");                                        
    +                    
    +                    imgEp.canvas.setAttribute("id", "iwilllookforthis");
    +                    
    +                    _jsPlumb.removeAllEndpoints("d1");
    +                    ok(document.getElementById("iwilllookforthis") == null, "image element was removed after remove endpoint");
    +                }
    +            } ]
    +        };
    +        start();
    +        _jsPlumb.addEndpoint(d1, e);
    +        expect(3);
    +    });
    +	
    +	test(renderMode + ": setting endpoint uuid", function() {
    +		var uuid = "14785937583175927504313";
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18");
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid});
    +		equal(e16.getUuid(), uuid, "endpoint's uuid was set correctly");
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.getEndpoint (by uuid)", function() {
    +		var uuid = "14785937583175927504313";
    +		var d16 = _addDiv("d16");
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid});
    +		var e = _jsPlumb.getEndpoint(uuid);
    +		equal(e.getUuid(), uuid, "retrieved endpoint by uuid");
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.deleteEndpoint (by uuid, simple case)", function() {
    +		var uuid = "14785937583175927504313";
    +		var d16 = _addDiv("d16");
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid});
    +		var e = _jsPlumb.getEndpoint(uuid);
    +		equal(e.getUuid(), uuid, "retrieved endpoint by uuid");
    +		_jsPlumb.deleteEndpoint(uuid);
    +		var f = _jsPlumb.getEndpoint(uuid);
    +		equal(f, null, "endpoint has been deleted");
    +		var ebe = _jsPlumb.getTestHarness().endpointsByElement["d16"];
    +		ok(ebe == null, "no endpoints registered for element d16 anymore");
    +		assertContextSize(0);
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.deleteEndpoint (by uuid, connections too)", function() {
    +		// create two endpoints (one with a uuid), add them to two divs and then connect them.
    +		var uuid = "14785937583175927504313";
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17");
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid});
    +		var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1});
    +		_jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17});
    +		// check that the connection was ok.
    +		equal(e16.connections.length, 1, "e16 has one connection");
    +		equal(e17.connections.length, 1, "e17 has one connection");		
    +		
    +		// delete the endpoint that has a uuid.  verify that the endpoint cannot be retrieved and that the connection has been removed, but that
    +		// element d17 still has its Endpoint.
    +		_jsPlumb.deleteEndpoint(uuid);
    +		var f = _jsPlumb.getEndpoint(uuid);
    +		equal(f, null, "endpoint has been deleted");
    +		equal(e16.connections.length, 0, "e16 has no connections");
    +		equal(e17.connections.length, 0, "e17 has no connections");
    +		var ebe = _jsPlumb.getTestHarness().endpointsByElement["d16"];
    +		ok(ebe == null, "no endpoints registered for element d16 anymore");
    +		ebe = _jsPlumb.getTestHarness().endpointsByElement["d17"];
    +		equal(ebe.length, 1, "element d17 still has its Endpoint");
    +		assertContextSize(1);
    +		
    +		// now delete d17's endpoint and check that it has gone.
    +		_jsPlumb.deleteEndpoint(e17);
    +		f = _jsPlumb.getEndpoint(e17);
    +		equal(f, null, "endpoint has been deleted");
    +		ebe = _jsPlumb.getTestHarness().endpointsByElement["d17"];
    +		ok(ebe == null, "element d17 no longer has any Endpoints");
    +		assertContextSize(0);
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.deleteEndpoint (by reference, simple case)", function() {
    +		var uuid = "14785937583175927504313";
    +		var d16 = _addDiv("d16");
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid});
    +		var e = _jsPlumb.getEndpoint(uuid);
    +		equal(e.getUuid(), uuid, "retrieved endpoint by uuid");
    +		_jsPlumb.deleteEndpoint(e16);
    +		var f = _jsPlumb.getEndpoint(uuid);
    +		equal(f, null, "endpoint has been deleted");
    +		assertContextSize(0);
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.deleteEndpoint (by reference, connections too)", function() {
    +		var uuid = "14785937583175927504313";
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17");
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid});
    +		var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1});
    +		_jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17});
    +		equal(e16.connections.length, 1, "e16 has one connection");
    +		equal(e17.connections.length, 1, "e17 has one connection");
    +		assertContextSize(3);
    +		
    +		_jsPlumb.deleteEndpoint(e16);
    +		var f = _jsPlumb.getEndpoint(uuid);
    +		equal(f, null, "endpoint has been deleted");
    +		equal(e16.connections.length, 0, "e16 has no connections");
    +		equal(e17.connections.length, 0, "e17 has no connections");
    +		assertContextSize(1);
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.deleteEveryEndpoint", function() {
    +		var uuid = "14785937583175927504313";
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17");
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid});
    +		var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1});
    +		assertContextSize(2);
    +		var e = _jsPlumb.getEndpoint(uuid);
    +		equal(e.getUuid(), uuid, "retrieved endpoint by uuid");
    +		
    +		_jsPlumb.deleteEveryEndpoint();
    +		
    +		var f = _jsPlumb.getEndpoint(uuid);
    +		equal(f, null, "endpoint e16 has been deleted");
    +		var g = _jsPlumb.getEndpoint(e17);
    +		equal(g, null, "endpoint e17 has been deleted");
    +		assertContextSize(0);
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.deleteEveryEndpoint (connections too)", function() {
    +		var uuid = "14785937583175927504313";
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17");
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:true,maxConnections:-1, uuid:uuid});
    +		var e17 = _jsPlumb.addEndpoint(d17, {isSource:true,maxConnections:-1});
    +		_jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17});
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb);
    +		assertContextSize(3);
    +		var e = _jsPlumb.getEndpoint(uuid);
    +		equal(e.getUuid(), uuid, "retrieved endpoint by uuid");
    +		
    +		_jsPlumb.deleteEveryEndpoint();
    +		
    +		var f = _jsPlumb.getEndpoint(uuid);
    +		equal(f, null, "endpoint e16 has been deleted");
    +		var g = _jsPlumb.getEndpoint(e17);
    +		equal(g, null, "endpoint e17 has been deleted");
    +		assertContextSize(0);								// no canvases. so all connection canvases have been cleaned up too.
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb);
    +	});
    +    
    +    test(renderMode + ": removeAllEndpoints, referenced as string", function() {
    +        var d1 = _addDiv("d1");
    +        _jsPlumb.addEndpoint(d1);
    +        _jsPlumb.addEndpoint(d1);
    +        _jsPlumb.addEndpoint(d1);
    +        
    +        _jsPlumb.removeAllEndpoints("d1");        
    +        _jsPlumb.repaintEverything();
    +
    +        _jsPlumb.addEndpoint(d1);        
    +        equal(_jsPlumb.getTestHarness().endpointsByElement["d1"].length, 1, "one endpoint for the given element");        
    +        
    +        expect(1);
    +    });
    +    
    +    test(renderMode + ": removeAllEndpoints, referenced as selector", function() {
    +        var d1 = _addDiv("d1");
    +        _jsPlumb.addEndpoint(d1);
    +        _jsPlumb.addEndpoint(d1);
    +        _jsPlumb.addEndpoint(d1);
    +        
    +        _jsPlumb.removeAllEndpoints(d1);                
    +
    +        _jsPlumb.repaintEverything();
    +
    +        _jsPlumb.addEndpoint(d1);        
    +        equal(_jsPlumb.getTestHarness().endpointsByElement["d1"].length, 1, "one endpoint for the given element");        
    +        
    +        expect(1);
    +    });    
    +	
    +    test(renderMode + ": removeAllEndpoints - element already deleted", function() {
    +        var d1 = _addDiv("d1");
    +        _jsPlumb.addEndpoint(d1);
    +        _jsPlumb.addEndpoint(d1);
    +        _jsPlumb.addEndpoint(d1);
    +        
    +        d1.remove();
    +        _jsPlumb.removeAllEndpoints("d1");        
    +        _jsPlumb.repaintEverything();
    +        
    +        expect(0);
    +    });
    +    
    +    test(renderMode + ": jsPlumb.remove, element identified by string", function() {
    +        var d1 = _addDiv("d1");
    +        _jsPlumb.addEndpoint(d1);
    +        _jsPlumb.addEndpoint(d1);
    +        _jsPlumb.addEndpoint(d1);
    +        
    +        _jsPlumb.remove("d1");
    +
    +        _jsPlumb.repaintEverything(); // shouldn't complain
    +        
    +        ok(_jsPlumb.getTestHarness().endpointsByElement["d1"] ==  null, "no endpoints for the given element");                
    +        
    +        expect(1);
    +    });
    +    
    +    test(renderMode + ": jsPlumb.remove, element identified by selector", function() {
    +        var d1 = _addDiv("d1");
    +        _jsPlumb.addEndpoint(d1);
    +        _jsPlumb.addEndpoint(d1);
    +        _jsPlumb.addEndpoint(d1);
    +        
    +        _jsPlumb.remove(d1);
    +
    +        _jsPlumb.repaintEverything(); // shouldn't complain
    +        
    +        ok(_jsPlumb.getTestHarness().endpointsByElement["d1"] ==  null, "no endpoints for the given element");                
    +        
    +        expect(1);
    +    });    
    +    
    +    test(renderMode + ": jsPlumb.remove, element identified by string, nested endpoints", function() {
    +        var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +        d1.append(d2);
    +        _jsPlumb.addEndpoint(d2);
    +        _jsPlumb.addEndpoint(d2);
    +        _jsPlumb.addEndpoint(d2);
    +        
    +        _jsPlumb.remove("d1");
    +
    +        _jsPlumb.repaintEverything(); // shouldn't complain
    +        
    +        ok(_jsPlumb.getTestHarness().endpointsByElement["d1"] ==  null, "no endpoints for the main div");                
    +        ok(_jsPlumb.getTestHarness().endpointsByElement["d2"] ==  null, "no endpoints for the nested div");                        
    +        
    +        expect(2);
    +    });
    +    
    +    test(renderMode + ": jsPlumb.remove, nested element, element identified by string, nested endpoints", function() {
    +        var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +        d1.append(d2);
    +        _jsPlumb.addEndpoint(d2);
    +        _jsPlumb.addEndpoint(d2);
    +        _jsPlumb.addEndpoint(d2);
    +        
    +        _jsPlumb.remove("d2");        
    +
    +        _jsPlumb.repaint("d1"); // shouldn't complain
    +        _jsPlumb.recalculateOffsets();
    +        
    +        ok(_jsPlumb.getTestHarness().endpointsByElement["d1"] ==  null, "no endpoints for the main div");                
    +        ok(_jsPlumb.getTestHarness().endpointsByElement["d2"] ==  null, "no endpoints for the nested div");                        
    +        
    +        expect(2);
    +    });    
    +    
    +    
    +	test(renderMode + ": _jsPlumb.addEndpoint (simple case)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var e16 = _jsPlumb.addEndpoint(d16, {anchor:[0,0.5,0,-1]});
    +		var e17 = _jsPlumb.addEndpoint(d17, {anchor:"TopCenter"});
    +		assertContextSize(2);
    +		equal(e16.anchor.x, 0);
    +		equal(e16.anchor.y, 0.5);
    +		equal(e17.anchor.x, 0.5);
    +		equal(e17.anchor.y, 0);
    +	});
    +	
    +	
    +	test(renderMode + ": _jsPlumb.addEndpoint (simple case, dynamic anchors)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var e16 = _jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]});
    +		var e17 = _jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]});
    +		assertContextSize(2);
    +		equal(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor");
    +		equal(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor");
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.addEndpoint (simple case, two arg method)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchor:[0,0.5,0,-1]});
    +		var e17 = _jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchor:"TopCenter"});
    +		assertContextSize(2);
    +		equal(e16.anchor.x, 0);
    +		equal(e16.anchor.y, 0.5);
    +		equal(false, e16.isTarget);
    +		equal(true, e16.isSource);
    +		equal(e17.anchor.x, 0.5);
    +		equal(e17.anchor.y, 0);
    +		equal(true, e17.isTarget);
    +		equal(false, e17.isSource);
    +	});
    +	
    +	
    +	test(renderMode + ": _jsPlumb.addEndpoints (simple case)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var e16 = _jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false, anchor:[0,0.5,0,-1] }, { isTarget:true, isSource:false, anchor:"TopCenter" }]);
    +		assertContextSize(2);
    +		equal(e16[0].anchor.x, 0);
    +		equal(e16[0].anchor.y, 0.5);
    +		equal(false, e16[0].isTarget);
    +		equal(true, e16[0].isSource);
    +		equal(e16[1].anchor.x, 0.5);
    +		equal(e16[1].anchor.y, 0);
    +		equal(true, e16[1].isTarget);
    +		equal(false, e16[1].isSource);
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.addEndpoints (with reference params)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var refParams = {anchor:"RightMiddle"};
    +		var e16 = _jsPlumb.addEndpoints(d16, [{isSource:true, isTarget:false}, { isTarget:true, isSource:false }], refParams);
    +		assertContextSize(2);
    +		equal(e16[0].anchor.x, 1);
    +		equal(e16[0].anchor.y, 0.5);
    +		equal(false, e16[0].isTarget);
    +		equal(true, e16[0].isSource);
    +		equal(e16[1].anchor.x, 1);
    +		equal(e16[1].anchor.y, 0.5);
    +		equal(true, e16[1].isTarget);
    +		equal(false, e16[1].isSource);
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.addEndpoint (simple case, dynamic anchors, two arg method)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]});
    +		var e17 = _jsPlumb.addEndpoint(d17, {isTarget:true, isSource:false}, {anchors:["TopCenter", "BottomCenter"]});
    +		assertContextSize(2);
    +		equal(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor");
    +		equal(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor");
    +	});
    +
    +	test(renderMode + ": _jsPlumb.addEndpoints (default overlays)", function() {
    +		_jsPlumb.Defaults.Overlays = [
    +			[ "Label", { id:"label" } ]
    +		];
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"),
    +			e1 = _jsPlumb.addEndpoint(d16),
    +			e2 = _jsPlumb.addEndpoint(d17);
    +
    +		ok(e1.getOverlay("label") != null, "endpoint 1 has overlay from defaults");
    +	});
    +
    +	test(renderMode + ": _jsPlumb.addEndpoints (default overlays)", function() {
    +		_jsPlumb.Defaults.Overlays = [
    +			[ "Label", { id:"label" } ]
    +		];
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"),
    +			e1 = _jsPlumb.addEndpoint(d16, {
    +				overlays:[
    +					["Label", { id:"label2", location:[ 0.5, 1 ] } ]
    +				]
    +			}),
    +			e2 = _jsPlumb.addEndpoint(d17);
    +
    +		ok(e1.getOverlay("label") != null, "endpoint 1 has overlay from defaults");
    +		ok(e1.getOverlay("label2") != null, "endpoint 1 has overlay from addEndpoint call");
    +	});
    +
    +	test(renderMode + ": _jsPlumb.addEndpoints (end point set label)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"),
    +			e1 = _jsPlumb.addEndpoint(d16),
    +			e2 = _jsPlumb.addEndpoint(d17);
    +
    +		e1.setLabel("FOO");
    +		equal(e1.getLabel(), "FOO", "endpoint's label is correct");
    +	});
    +
    +	test(renderMode + ": _jsPlumb.addEndpoints (end point set label in constructor, as string)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"),
    +			e1 = _jsPlumb.addEndpoint(d16, {label:"FOO"}),
    +			e2 = _jsPlumb.addEndpoint(d17);
    +
    +		equal(e1.getLabel(), "FOO", "endpoint's label is correct");
    +	});
    +
    +	test(renderMode + ": _jsPlumb.addEndpoints (end point set label in constructor, as function)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"),
    +			e1 = _jsPlumb.addEndpoint(d16, {label:function() { return "BAZ"; }, labelLocation:0.1}),
    +			e2 = _jsPlumb.addEndpoint(d17);
    +
    +		equal(e1.getLabel()(), "BAZ", "endpoint's label is correct");
    +		equal(e1.getLabelOverlay().getLocation(), 0.1, "endpoint's label's location is correct");
    +	});
    +
    +	test(renderMode + ": jsPlumb.addEndpoint (events)", function() {
    +		var d16 = _addDiv("d16"), 
    +			click = 0,
    +			e16 = _jsPlumb.addEndpoint(d16, {
    +				isSource:true, 
    +				isTarget:false, 
    +				anchor:[0,0.5,0,-1],
    +				events:{
    +					click:function(ep) {
    +						click++;
    +					}
    +				}
    +			});
    +		e16.fire("click", function() {
    +			click++;
    +		});
    +		equal(click, 1, "click event was fired once");
    +	});
    +	
    +// ***************** setConnector ************************************************************
    +
    +	test(renderMode + ": setConnector, check the connector is set", function() {
    +		_addDiv("d1"); _addDiv("d2");
    +		var def = {
    +			Connector : [ "Bezier", { curviness:45 } ]
    +		};
    +		var j = jsPlumb.getInstance(def);
    +		var c = j.connect({source:"d1",target:"d2"});
    +		equal(c.getConnector().type, "Bezier", "connector is the default");
    +		c.setConnector(["Bezier", { curviness:789 }]);
    +		equal(def.Connector[1].curviness, 45, "curviness unchanged by setConnector call");
    +	});
    +
    +// ******************  makeTarget (and associated methods) tests ********************************************	
    +	
    +	test(renderMode + ": _jsPlumb.makeTarget (simple case)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]});
    +		_jsPlumb.makeTarget(d17, { isTarget:true,anchor:"TopCenter"  });
    +		equal(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable"));
    +	});
    +
    +	test(renderMode + ": _jsPlumb.makeTarget (specify two divs in an array)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		_jsPlumb.makeTarget([d16, d17], { isTarget:true,anchor:"TopCenter"  });
    +		equal(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable"));
    +		equal(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable"));
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.makeTarget (specify two divs by id in an array)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		_jsPlumb.makeTarget(["d16", "d17"], { isTarget:true,anchor:"TopCenter"  });
    +		equal(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable"));
    +		equal(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable"));
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.makeTarget (specify divs by selector)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		d16.addClass("FOO");d17.addClass("FOO");
    +		_jsPlumb.makeTarget($(".FOO"), { isTarget:true,anchor:"TopCenter"  });
    +		equal(true, jsPlumb.CurrentLibrary.hasClass(d16, "ui-droppable"));
    +		equal(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable"));
    +	});
    +
    +	test(renderMode + ": _jsPlumb.connect after makeTarget (simple case)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]});
    +		_jsPlumb.makeTarget(d17, { isTarget:true,anchor:"LeftMiddle"  }); // give it a non-default anchor, we will check this below.
    +		equal(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable"));
    +		_jsPlumb.connect({source:e16, target:"d17"});
    +		assertEndpointCount("d16", 1, _jsPlumb);
    +		assertEndpointCount("d17", 1, _jsPlumb);
    +		var e = _jsPlumb.getEndpoints("d17");
    +		equal(e.length, 1, "d17 has one endpoint");
    +		equal(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget
    +		equal(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget
    +	});
    +
    +	test(renderMode + ": _jsPlumb.connect after makeTarget (simple case, two connect calls)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]});
    +		_jsPlumb.makeTarget(d17, { isTarget:true,anchor:"LeftMiddle"  }); // give it a non-default anchor, we will check this below.
    +		equal(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable"));
    +		_jsPlumb.connect({source:e16, target:"d17"});
    +		_jsPlumb.connect({source:e16, target:"d17"});
    +		assertEndpointCount("d16", 1, _jsPlumb);
    +		assertEndpointCount("d17", 2, _jsPlumb);
    +		var e = _jsPlumb.getEndpoints("d17");		
    +		equal(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget
    +		equal(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget
    +	});
    +
    +	test(renderMode + ": _jsPlumb.connect after makeTarget (simple case, two connect calls, uniqueEndpoint set)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]});
    +		_jsPlumb.makeTarget(d17, { isTarget:true, anchor:"LeftMiddle", uniqueEndpoint:true, maxConnections:-1  }); // give it a non-default anchor, we will check this below.
    +		equal(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable"));
    +		_jsPlumb.connect({source:e16, target:"d17"});
    +		_jsPlumb.connect({source:e16, target:"d17"});
    +		assertEndpointCount("d16", 1, _jsPlumb);
    +		assertEndpointCount("d17", 1, _jsPlumb);
    +		var e = _jsPlumb.getEndpoints("d17");		
    +		equal(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget
    +		equal(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget
    +		equal(e[0].connections.length, 2, "endpoint on d17 has two connections");
    +	});
    +
    +	test(renderMode + ": _jsPlumb.connect after makeTarget (newConnection:true specified)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]});
    +		_jsPlumb.makeTarget(d17, { isTarget:true,anchor:"LeftMiddle"  }); // give it a non-default anchor, we will check this below.
    +		equal(true, jsPlumb.CurrentLibrary.hasClass(d17, "ui-droppable"));
    +		_jsPlumb.connect({source:e16, target:"d17", newConnection:true});
    +		assertEndpointCount("d16", 1, _jsPlumb);
    +		assertEndpointCount("d17", 1, _jsPlumb);
    +		var e = _jsPlumb.getEndpoints("d17");		
    +		equal(e[0].anchor.x, 0.5, "anchor is BottomCenter"); //here we should be seeing the default anchor
    +		equal(e[0].anchor.y, 1, "anchor is BottomCenter"); //here we should be seeing the default anchor
    +	});
    +
    +// jsPlumb.connect, after makeSource has been called on some element
    +	test(renderMode + ": _jsPlumb.connect after makeSource (simple case)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]});
    +		_jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle"  }); // give it a non-default anchor, we will check this below.
    +		_jsPlumb.connect({source:"d17", target:e16});
    +		assertEndpointCount("d16", 1, _jsPlumb);
    +		assertEndpointCount("d17", 1, _jsPlumb);
    +		var e = _jsPlumb.getEndpoints("d17");		
    +		equal(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget
    +		equal(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget
    +	});
    +
    +	test(renderMode + ": _jsPlumb.connect after makeSource (simple case, two connect calls)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]});
    +		_jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle"  }); // give it a non-default anchor, we will check this below.		
    +		_jsPlumb.connect({source:"d17", target:e16});
    +		_jsPlumb.connect({source:"d17", target:e16});
    +		assertEndpointCount("d16", 1, _jsPlumb);
    +		assertEndpointCount("d17", 2, _jsPlumb);
    +		var e = _jsPlumb.getEndpoints("d17");		
    +		equal(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget
    +		equal(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget
    +	});
    +    
    +	test(renderMode + ": _jsPlumb.connect after makeSource (parameters)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]});
    +		_jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle", parameters:{ foo:"bar"}  }); // give it a non-default anchor, we will check this below.
    +		_jsPlumb.connect({source:"d17", target:e16});
    +		assertEndpointCount("d16", 1, _jsPlumb);
    +		assertEndpointCount("d17", 1, _jsPlumb);
    +		var e = _jsPlumb.getEndpoints("d17");		
    +        equal(e[0].getParameter("foo"), "bar", "parameter was set on endpoint made from makeSource call");
    +	});    
    +
    +	test(renderMode + ": _jsPlumb.connect after makeTarget (simple case, two connect calls, uniqueEndpoint set)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true, maxConnections:-1}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]});
    +		_jsPlumb.makeSource(d17, { isSource:true, anchor:"LeftMiddle", uniqueEndpoint:true, maxConnections:-1  }); // give it a non-default anchor, we will check this below.		
    +		_jsPlumb.connect({source:"d17", target:e16});
    +		_jsPlumb.connect({source:"d17", target:e16});
    +		assertEndpointCount("d16", 1, _jsPlumb);
    +		assertEndpointCount("d17", 1, _jsPlumb);
    +		var e = _jsPlumb.getEndpoints("d17");		
    +		equal(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget
    +		equal(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget
    +		equal(e[0].connections.length, 2, "endpoint on d17 has two connections");
    +	});
    +
    +	test(renderMode + ": _jsPlumb.connect after makeTarget (newConnection:true specified)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]});
    +		_jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle"  }); // give it a non-default anchor, we will check this below.		
    +		_jsPlumb.connect({source:"d17", target:e16, newConnection:true});
    +		assertEndpointCount("d16", 1, _jsPlumb);
    +		assertEndpointCount("d17", 1, _jsPlumb);
    +		var e = _jsPlumb.getEndpoints("d17");		
    +		equal(e[0].anchor.x, 0.5, "anchor is BottomCenter"); //here we should be seeing the default anchor
    +		equal(e[0].anchor.y, 1, "anchor is BottomCenter"); //here we should be seeing the default anchor
    +	});
    +
    +	test(renderMode + ": _jsPlumb.connect after makeSource on child; with parent set (parent should be recognised)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18", d17);
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]});
    +		_jsPlumb.makeSource(d18, { isSource:true,anchor:"LeftMiddle", parent:d17  }); // give it a non-default anchor, we will check this below.
    +		_jsPlumb.connect({source:"d17", target:e16});
    +		assertEndpointCount("d16", 1, _jsPlumb);
    +		assertEndpointCount("d17", 1, _jsPlumb);
    +		assertEndpointCount("d18", 0, _jsPlumb);
    +		var e = _jsPlumb.getEndpoints("d17");		
    +		equal(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget
    +		equal(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget
    +	});
    +
    +	test(renderMode + ": _jsPlumb.connect after makeSource on child; with parent set (parent is string 'parent')", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18", d17);		
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]});
    +		_jsPlumb.makeSource(d18, { isSource:true,anchor:"LeftMiddle", parent:"parent"  }); // give it a non-default anchor, we will check this below.
    +		_jsPlumb.connect({source:"d17", target:e16});
    +		assertEndpointCount("d16", 1, _jsPlumb);
    +		assertEndpointCount("d17", 1, _jsPlumb);
    +		assertEndpointCount("d18", 0, _jsPlumb);
    +		var e = _jsPlumb.getEndpoints("d17");		
    +		equal(e[0].anchor.x, 0, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget
    +		equal(e[0].anchor.y, 0.5, "anchor is LeftMiddle"); //here we should be seeing the anchor we setup via makeTarget
    +	});
    +
    +	// makeSource, then disable it. should not be able to make a connection from it.
    +	test(renderMode + ": _jsPlumb.connect after makeSource and setSourceEnabled(false) (string id as argument)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]});
    +		_jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle"  }); // give it a non-default anchor, we will check this below.
    +		_jsPlumb.setSourceEnabled(d17, false);
    +		_jsPlumb.connect({source:"d17", target:e16});
    +		assertEndpointCount("d16", 1, _jsPlumb);
    +		assertEndpointCount("d17", 0, _jsPlumb);		
    +	});
    +
    +	// makeSource, then disable it. should not be able to make a connection from it.
    +	test(renderMode + ": _jsPlumb.connect after makeSource and setSourceEnabled(false) (selector as argument)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]});
    +		_jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle"  }); // give it a non-default anchor, we will check this below.
    +		_jsPlumb.setSourceEnabled($("div"), false);
    +		_jsPlumb.connect({source:"d17", target:e16});
    +		assertEndpointCount("d16", 1, _jsPlumb);
    +		assertEndpointCount("d17", 0, _jsPlumb);		
    +	});
    +
    +	// makeSource, then toggle its enabled state. should not be able to make a connection from it.
    +	test(renderMode + ": _jsPlumb.connect after makeSource and toggleSourceEnabled() (string id as argument)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]});
    +		_jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle"  }); // give it a non-default anchor, we will check this below.
    +		_jsPlumb.toggleSourceEnabled(d17);
    +		_jsPlumb.connect({source:"d17", target:e16});
    +		assertEndpointCount("d16", 1, _jsPlumb);
    +		assertEndpointCount("d17", 0, _jsPlumb);		
    +
    +		_jsPlumb.toggleSourceEnabled(d17);
    +		_jsPlumb.connect({source:"d17", target:e16});
    +		assertEndpointCount("d16", 1, _jsPlumb);
    +		assertEndpointCount("d17", 1, _jsPlumb);		
    +	});
    +
    +	// makeSource, then disable it. should not be able to make a connection from it.
    +	test(renderMode + ": _jsPlumb.connect after makeSource and toggleSourceEnabled() (selector as argument)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]});
    +		_jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle"  }); // give it a non-default anchor, we will check this below.
    +		_jsPlumb.toggleSourceEnabled($("div"));
    +		_jsPlumb.connect({source:"d17", target:e16});
    +		assertEndpointCount("d16", 1, _jsPlumb);
    +		assertEndpointCount("d17", 0, _jsPlumb);		
    +		_jsPlumb.toggleSourceEnabled($("div"));
    +		_jsPlumb.connect({source:"d17", target:e16});
    +		assertEndpointCount("d16", 1, _jsPlumb);
    +		assertEndpointCount("d17", 1, _jsPlumb);		
    +	});
    +		
    +	test(renderMode + ": jsPlumb.isSource and jsPlumb.isSourceEnabled", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]});
    +		_jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle"  }); // give it a non-default anchor, we will check this below.
    +		ok(_jsPlumb.isSource(d17) == true, "d17 is recognised as connection source");
    +		ok(_jsPlumb.isSourceEnabled(d17) == true, "d17 is recognised as enabled");
    +		_jsPlumb.setSourceEnabled(d17, false);
    +		ok(_jsPlumb.isSourceEnabled(d17) == false, "d17 is recognised as disabled");
    +	});
    +
    +	// makeSource, then disable it. should not be able to make a connection to it.
    +	test(renderMode + ": _jsPlumb.connect after makeTarget and setTargetEnabled(false) (string id as argument)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]});
    +		_jsPlumb.makeTarget(d17, { anchor:"LeftMiddle"  }); // give it a non-default anchor, we will check this below.
    +		_jsPlumb.setTargetEnabled(d17, false);
    +		_jsPlumb.connect({source:e16, target:"d17"});
    +		assertEndpointCount("d16", 1, _jsPlumb);
    +		assertEndpointCount("d17", 0, _jsPlumb);		
    +	});
    +
    +	// makeTarget, then disable it. should not be able to make a connection to it.
    +	test(renderMode + ": _jsPlumb.connect after makeTarget and setTargetEnabled(false) (selector as argument)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]});
    +		_jsPlumb.makeTarget(d17, { anchor:"LeftMiddle"  }); // give it a non-default anchor, we will check this below.
    +		_jsPlumb.setTargetEnabled($("div"), false);
    +		_jsPlumb.connect({source:e16, target:"d17"});
    +		assertEndpointCount("d16", 1, _jsPlumb);
    +		assertEndpointCount("d17", 0, _jsPlumb);		
    +	});
    +
    +	// makeTarget, then toggle its enabled state. should not be able to make a connection to it.
    +	test(renderMode + ": _jsPlumb.connect after makeTarget and toggleTargetEnabled() (string id as argument)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]});
    +		_jsPlumb.makeTarget(d17, { anchor:"LeftMiddle"  }); // give it a non-default anchor, we will check this below.
    +		_jsPlumb.toggleTargetEnabled(d17);
    +		_jsPlumb.connect({source:e16, target:"d17"});
    +		assertEndpointCount("d16", 1, _jsPlumb);
    +		assertEndpointCount("d17", 0, _jsPlumb);		
    +
    +		_jsPlumb.toggleTargetEnabled(d17);
    +		_jsPlumb.connect({source:e16, target:"d17"});
    +		assertEndpointCount("d16", 1, _jsPlumb);
    +		assertEndpointCount("d17", 1, _jsPlumb);		
    +	});
    +
    +	// makeTarget, then disable it. should not be able to make a connection to it.
    +	test(renderMode + ": _jsPlumb.connect after makeTarget and toggleTargetEnabled() (selector as argument)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]});
    +		_jsPlumb.makeTarget(d17, { anchor:"LeftMiddle"  }); // give it a non-default anchor, we will check this below.
    +		_jsPlumb.toggleTargetEnabled($("div"));
    +		_jsPlumb.connect({source:e16, target:"d17"});
    +		assertEndpointCount("d16", 1, _jsPlumb);
    +		assertEndpointCount("d17", 0, _jsPlumb);		
    +		_jsPlumb.toggleTargetEnabled($("div"));
    +		_jsPlumb.connect({source:e16, target:"d17"});
    +		assertEndpointCount("d16", 1, _jsPlumb);
    +		assertEndpointCount("d17", 1, _jsPlumb);		
    +	});
    +		
    +	test(renderMode + ": jsPlumb.isTarget and jsPlumb.isTargetEnabled", function() {
    +		var d17 = _addDiv("d17"); 
    +		_jsPlumb.makeTarget(d17, { isSource:true,anchor:"LeftMiddle"  }); // give it a non-default anchor, we will check this below.
    +		ok(_jsPlumb.isTarget(d17) == true, "d17 is recognised as connection target");
    +		ok(_jsPlumb.isTargetEnabled(d17) == true, "d17 is recognised as enabled");
    +		_jsPlumb.setTargetEnabled(d17, false);
    +		ok(_jsPlumb.isTargetEnabled(d17) == false, "d17 is recognised as disabled");
    +	});
    +    
    +    test(renderMode + ": _jsPlumb.makeTarget - endpoints deleted by default.", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		_jsPlumb.makeSource(d16);
    +		_jsPlumb.makeTarget(d17);
    +
    +		var c = _jsPlumb.connect({source:"d16", target:"d17"});
    +		assertEndpointCount("d16", 1, _jsPlumb);
    +		assertEndpointCount("d17", 1, _jsPlumb);		
    +        _jsPlumb.detach(c);        
    +		assertEndpointCount("d16", 0, _jsPlumb);
    +		assertEndpointCount("d17", 0, _jsPlumb);		
    +	});
    +    
    +    /*
    +
    +    What i would *like* this test to do is to fake the user having dragged a connection from
    +    d16 to d17.  the mousedown on d16 is recognised, and an endpoint is added. but the rest of it
    +    is not.  so the test fails by saying that there's 1 endpoint on d16 when i expected none, and
    +    also that the callback was not called.
    +
    +    if i add this,
    +
    +    _trigger(d16, "mouseup");
    +
    +    then the endpoint is actually removed. so it looks like it's just not interacting well with the
    +    jquery ui drag stuff.  another clue about this is that it does not matter if i have fired
    +    'mousemove' and 'mouseout' on d16 before calling 'mouseup'.  
    +
    +    test(renderMode + ": _jsPlumb.makeTarget - endpoints deleted when detached on callback.", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		_jsPlumb.makeSource(d16);
    +		_jsPlumb.makeTarget(d17);
    +        var detached = false;
    +        _jsPlumb.bind("connection", function(i) {
    +            _jsPlumb.detach(i.connection);    
    +            detached = true;
    +        });
    +
    +        _triggerEvent(d16, "mousedown");
    +        _triggerEvent(d16, "mousemove");        
    +        _triggerEvent(d16, "mouseout");                
    +        
    +        _triggerEvent(d17, "mouseover");        
    +        _triggerEvent(d17, "mousemove");                
    +        _triggerEvent(d17, "mouseup");        
    +    
    +        equal(detached, true, "callback was called");
    +		assertEndpointCount("d16", 0, _jsPlumb);
    +		assertEndpointCount("d17", 0, _jsPlumb);		
    +
    +	});
    +    */
    +    
    +    test(renderMode + ": _jsPlumb.makeSource (parameters)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"),
    +            params = { "foo":"foo" },
    +            e16 = _jsPlumb.addEndpoint("d16", { parameters:params });
    +        
    +		_jsPlumb.makeSource(d17, { 
    +            isSource:true, 
    +            parameters:params
    +        }); 
    +        
    +		_jsPlumb.connect({source:"d17", target:e16});
    +		assertEndpointCount("d16", 1, _jsPlumb);
    +		assertEndpointCount("d17", 1, _jsPlumb);
    +		var e = _jsPlumb.getEndpoints("d17");		
    +		equal(e[0].getParameter("foo"), "foo", "makeSource created endpoint has parameters");
    +		equal(e16.getParameter("foo"), "foo", "normally created endpoint has parameters");        
    +	});
    +
    +	// makeSource, then unmake it. should not be able to make a connection from it. then connect to it, which should succeed,
    +	// because jsPlumb will just add a new endpoint.
    +	test(renderMode + ": jsPlumb.unmakeSource (string id as argument)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:false, isTarget:true}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]});
    +		_jsPlumb.makeSource(d17, { isSource:true,anchor:"LeftMiddle"  }); // give it a non-default anchor, we will check this below.
    +		ok(_jsPlumb.isSource(d17) == true, "d17 is currently a source");
    +		// unmake source
    +		_jsPlumb.unmakeSource(d17);		
    +		ok(_jsPlumb.isSource(d17) == false, "d17 is no longer a source");
    +
    +		// this should succeed, because d17 is no longer a source and so jsPlumb will just create and add a new endpoint to d17.
    +		_jsPlumb.connect({source:"d17", target:e16});
    +		assertEndpointCount("d16", 1, _jsPlumb);
    +		assertEndpointCount("d17", 1, _jsPlumb);				
    +	});
    +
    +	// maketarget, then unmake it. should not be able to make a connection to it. 
    +	test(renderMode + ": jsPlumb.unmakeTarget (string id as argument)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, isTarget:false}, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]});
    +		_jsPlumb.makeTarget(d17, { isSource:true,anchor:"LeftMiddle"  }); // give it a non-default anchor, we will check this below.
    +		ok(_jsPlumb.isTarget(d17) == true, "d17 is currently a target");
    +		// unmake target
    +		_jsPlumb.unmakeTarget(d17);		
    +		ok(_jsPlumb.isTarget(d17) == false, "d17 is no longer a target");
    +
    +		// this should succeed, because d17 is no longer a target and so jsPlumb will just create and add a new endpoint to d17.
    +		_jsPlumb.connect({source:e16, target:"d17"});
    +		assertEndpointCount("d16", 1, _jsPlumb);
    +		assertEndpointCount("d17", 1, _jsPlumb);				
    +	});
    +
    +
    +	test(renderMode + ": jsPlumb.removeEverySource and removeEveryTarget (string id as argument)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18");
    +		_jsPlumb.makeSource(d16).makeTarget(d17).makeSource(d18);
    +		ok(_jsPlumb.isSource(d16) == true, "d16 is a source");
    +		ok(_jsPlumb.isTarget(d17) == true, "d17 is a target");
    +		ok(_jsPlumb.isSource(d18) == true, "d18 is a source");
    +
    +		_jsPlumb.unmakeEverySource();
    +		_jsPlumb.unmakeEveryTarget();
    +
    +		ok(_jsPlumb.isSource(d16) == false, "d16 is no longer a source");
    +		ok(_jsPlumb.isTarget(d17) == false, "d17 is no longer a target");
    +		ok(_jsPlumb.isSource(d18) == false, "d18 is no longer a source");
    +	});
    +
    +// *********************** end of makeTarget (and associated methods) tests ************************ 
    +	
    +	test(renderMode + ': _jsPlumb.connect (between two Endpoints)', function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var e = _jsPlumb.addEndpoint(d1, {});
    +		var e2 = _jsPlumb.addEndpoint(d2, {});
    +		ok(e, 'endpoint e exists');
    +		ok(e2, 'endpoint e2 exists');
    +		assertContextSize(2);				// should have a canvas for each endpoint now.  
    +		assertEndpointCount("d1", 1, _jsPlumb);
    +		assertEndpointCount("d2", 1, _jsPlumb);
    +		var c = _jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2});
    +		assertEndpointCount("d1", 1, _jsPlumb);		// no new endpoint should have been added
    +		assertEndpointCount("d2", 1, _jsPlumb); 		// no new endpoint should have been added
    +		assertContextSize(3);				// now we should also have a canvas for the connection.
    +		ok(c.id != null, "connection has had an id assigned");
    +	});
    +		
    +	
    +	test(renderMode + ': _jsPlumb.connect (between two Endpoints, and dont supply any parameters to the Endpoints.)', function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var e = _jsPlumb.addEndpoint(d1);
    +		var e2 = _jsPlumb.addEndpoint(d2);
    +		ok(e, 'endpoint e exists');
    +		ok(e2, 'endpoint e2 exists');
    +		assertContextSize(2);				// should have a canvas for each endpoint now.  
    +		assertEndpointCount("d1", 1, _jsPlumb);
    +		assertEndpointCount("d2", 1, _jsPlumb);
    +		_jsPlumb.connect({target:'d2', sourceEndpoint:e, targetEndpoint:e2});
    +		assertEndpointCount("d1", 1, _jsPlumb);		// no new endpoint should have been added
    +		assertEndpointCount("d2", 1, _jsPlumb); 		// no new endpoint should have been added
    +		assertContextSize(3);				// now we should also have a canvas for the connection.
    +	});
    +
    +	test(renderMode + " : _jsPlumb.connect, passing 'anchors' array" ,function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +			c = _jsPlumb.connect({source:d1, target:d2, anchors:["LeftMiddle", "RightMiddle"]});
    +
    +		equal(c.endpoints[0].anchor.x, 0, "source anchor is at x=0");
    +		equal(c.endpoints[0].anchor.y, 0.5, "source anchor is at y=0.5");
    +		equal(c.endpoints[1].anchor.x, 1, "target anchor is at x=1");
    +		equal(c.endpoints[1].anchor.y, 0.5, "target anchor is at y=0.5");
    +	});
    +	
    +	test(renderMode + ': _jsPlumb.connect (by endpoint)', function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17");
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:true});
    +		ok(e16.anchor, 'endpoint 16 has an anchor');
    +		var e17 = _jsPlumb.addEndpoint(d17, {isSource:true});
    +		_jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17});
    +		assertContextSize(3);
    +	});
    +	
    +	test(renderMode + ': _jsPlumb.connect (cost)', function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17");
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:true});
    +		ok(e16.anchor, 'endpoint 16 has an anchor');
    +		var e17 = _jsPlumb.addEndpoint(d17, {isSource:true});
    +		var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17, cost:567});
    +		assertContextSize(3);
    +		equal(c.getCost(), 567, "connection cost is 567");
    +	});
    +	
    +	test(renderMode + ': _jsPlumb.connect (default cost)', function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17");
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:true});
    +		ok(e16.anchor, 'endpoint 16 has an anchor');
    +		var e17 = _jsPlumb.addEndpoint(d17, {isSource:true});
    +		var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17});
    +		assertContextSize(3);
    +		equal(c.getCost(), undefined, "default connection cost is 1");
    +	});
    +	
    +	test(renderMode + ': _jsPlumb.connect (set cost)', function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17");
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:true});
    +		ok(e16.anchor, 'endpoint 16 has an anchor');
    +		var e17 = _jsPlumb.addEndpoint(d17, {isSource:true});
    +		var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17});
    +		assertContextSize(3);
    +		equal(c.getCost(), undefined, "default connection cost is 1");
    +		c.setCost(8989);
    +		equal(c.getCost(), 8989, "connection cost is 8989");
    +	});
    +	
    +	test(renderMode + ': _jsPlumb.connect two endpoints (connectionCost)', function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17");
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionCost:567});
    +		ok(e16.anchor, 'endpoint 16 has an anchor');
    +		var e17 = _jsPlumb.addEndpoint(d17, {isSource:true});
    +		var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17});
    +		assertContextSize(3);
    +		equal(c.getCost(), 567, "connection cost is 567");
    +	});
    +	
    +	test(renderMode + ': _jsPlumb.connect two endpoints (change connectionCost)', function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"),
    +			e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionCost:567, maxConnections:-1}),
    +			e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1});
    +			c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17});
    +		assertContextSize(3);
    +		equal(c.getCost(), 567, "connection cost is 567");
    +		e16.setConnectionCost(23);
    +		var c2 = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17});
    +		equal(c2.getCost(), 23, "connection cost is 23 after change on endpoint");
    +	});
    +	
    +	test(renderMode + ': _jsPlumb.connect (directed is false by default)', function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"),
    +			e16 = _jsPlumb.addEndpoint(d16, {isSource:true}),
    +			e17 = _jsPlumb.addEndpoint(d17, {isSource:true}),
    +			c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17});
    +		assertContextSize(3);
    +		equal(c.isDirected(), false, "default connection is not directed");
    +	});
    +	
    +	test(renderMode + ': _jsPlumb.connect (directed true)', function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"),
    +			e16 = _jsPlumb.addEndpoint(d16, {isSource:true}),
    +			e17 = _jsPlumb.addEndpoint(d17, {isSource:true}),
    +			c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17, directed:true});
    +		assertContextSize(3);
    +		equal(c.isDirected(), true, "connection is directed");
    +	});
    +	
    +	test(renderMode + ': _jsPlumb.connect two endpoints (connectionsDirected)', function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17");
    +		var e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionsDirected:true, maxConnections:-1});
    +		ok(e16.anchor, 'endpoint 16 has an anchor');
    +		var e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1});
    +		var c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17});
    +		assertContextSize(3);
    +		equal(c.isDirected(), true, "connection is directed");
    +	});
    +	
    +	test(renderMode + ': _jsPlumb.connect two endpoints (change connectionsDirected)', function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"),
    +			e16 = _jsPlumb.addEndpoint(d16, {isSource:true, connectionsDirected:true, maxConnections:-1}),
    +			e17 = _jsPlumb.addEndpoint(d17, {isSource:true, maxConnections:-1});
    +			c = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17});
    +		assertContextSize(3);
    +		equal(c.isDirected(), true, "connection is directed");
    +		e16.setConnectionsDirected(false);
    +		var c2 = _jsPlumb.connect({sourceEndpoint:e16, targetEndpoint:e17});
    +		equal(c2.isDirected(), false, "connection is not directed");
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.connect (two Endpoints - that have been already added - by UUID)", function() {
    +		var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325";
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var e1 = _jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1, uuid:srcEndpointUuid});
    +		var e2 = _jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1, uuid:dstEndpointUuid});
    +		_jsPlumb.connect({ uuids:  [ srcEndpointUuid, dstEndpointUuid  ] });
    +		assertConnectionCount(e1, 1);
    +		assertConnectionCount(e2, 1);
    +		assertContextSize(3);
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.connect (two Endpoints - that have not been already added - by UUID)", function() {
    +		var srcEndpointUuid = "14785937583175927504313", dstEndpointUuid = "14785937583175927534325";
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		_jsPlumb.connect({ uuids:  [ srcEndpointUuid, dstEndpointUuid  ], source:d16, target:d17 });
    +		assertContextSize(3);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb);
    +		var e1 = _jsPlumb.getEndpoint(srcEndpointUuid);
    +		ok(e1 != null, "endpoint with src uuid added");
    +		ok(e1.canvas != null);
    +		var e2 = _jsPlumb.getEndpoint(dstEndpointUuid);
    +		ok(e2 != null, "endpoint with target uuid added");
    +		assertConnectionCount(e1, 1);
    +		assertConnectionCount(e2, 1);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb);
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.connect (two Endpoints - that have been already added - by endpoint reference)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var e1 = _jsPlumb.addEndpoint("d16", {isSource:true, maxConnections:-1});
    +		var e2 = _jsPlumb.addEndpoint("d17", {isSource:true, maxConnections:-1});
    +		_jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 });
    +		assertContextSize(3);
    +		assertConnectionCount(e1, 1);
    +		assertConnectionCount(e2, 1);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb);
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.connect (two elements)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		_jsPlumb.connect({ source:d16, target:d17 });
    +		assertContextSize(3);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb);
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.connect (Connector test, straight)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight" });
    +		assertContextSize(3);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb);
    +		equal(conn.getConnector().type, "Straight", "Straight connector chosen for connection");
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.connect (Connector test, bezier, no params)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Bezier" });
    +		assertContextSize(3);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb);
    +		equal(conn.getConnector().type, "Bezier", "Bezier connector chosen for connection");
    +		equal(conn.getConnector().getCurviness(), 150, "Bezier connector chose 150 curviness");		
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.connect (Connector test, bezier, curviness as int)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var conn = _jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", { curviness:200 }] });
    +		assertContextSize(3);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb);
    +		equal(conn.getConnector().type, "Bezier", "Canvas Bezier connector chosen for connection");
    +		equal(conn.getConnector().getCurviness(), 200, "Bezier connector chose 200 curviness");
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.connect (Connector test, bezier, curviness as named option)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var conn = _jsPlumb.connect({ source:d16, target:d17, connector:["Bezier", {curviness:300}] });
    +		assertContextSize(3);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb);
    +		equal(conn.getConnector().type, "Bezier", "Bezier connector chosen for connection");
    +		equal(conn.getConnector().getCurviness(), 300, "Bezier connector chose 300 curviness");
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] });
    +		assertContextSize(3);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb);
    +		equal(conn.getConnector().type, "Straight", "Canvas Straight connector chosen for connection");
    +		equal(0.3, conn.endpoints[0].anchor.x, "source anchor x");
    +		equal(0.3, conn.endpoints[0].anchor.y, "source anchor y");
    +		equal(0.7, conn.endpoints[1].anchor.x, "target anchor x");
    +		equal(0.7, conn.endpoints[1].anchor.y, "target anchor y");
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as strings)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:["LeftMiddle", "RightMiddle"] });
    +		assertContextSize(3);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb);
    +		equal(conn.getConnector().type, "Straight", "Straight connector chosen for connection");
    +		equal(0, conn.endpoints[0].anchor.x, "source anchor x");
    +		equal(0.5, conn.endpoints[0].anchor.y, "source anchor y");
    +		equal(1, conn.endpoints[1].anchor.x, "target anchor x");
    +		equal(0.5, conn.endpoints[1].anchor.y, "target anchor y");
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.connect (anchors registered correctly; source and target anchors given, as arrays)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] });
    +		assertContextSize(3);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb);
    +		equal(conn.getConnector().type, "Straight", "Straight connector chosen for connection");
    +		equal(0.3, conn.endpoints[0].anchor.x, "source anchor x");
    +		equal(0.3, conn.endpoints[0].anchor.y, "source anchor y");
    +		equal(0.7, conn.endpoints[1].anchor.x, "target anchor x");
    +		equal(0.7, conn.endpoints[1].anchor.y, "target anchor y");
    +	});
    +	
    +	
    +	test(renderMode + ": _jsPlumb.connect (two argument method in which some data is reused across connections)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"), d18 = _addDiv("d18"), d19 = _addDiv("d19");
    +		var sharedData = { connector:"Straight", anchors:[[0.3,0.3,1,0], [0.7,0.7,0,1]] };
    +		var conn = _jsPlumb.connect({ source:d16, target:d17}, sharedData);
    +		var conn2 = _jsPlumb.connect({ source:d18, target:d19}, sharedData);
    +		assertContextSize(6);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 2, _jsPlumb);
    +		equal(conn.getConnector().type, "Straight", "Straight connector chosen for connection");
    +		equal(conn2.getConnector().type, "Straight", "Straight connector chosen for connection");
    +		equal(0.3, conn.endpoints[0].anchor.x, "source anchor x");
    +		equal(0.3, conn.endpoints[0].anchor.y, "source anchor y");
    +		equal(0.7, conn.endpoints[1].anchor.x, "target anchor x");
    +		equal(0.7, conn.endpoints[1].anchor.y, "target anchor y");
    +		equal(0.3, conn2.endpoints[0].anchor.x, "source anchor x");
    +		equal(0.3, conn2.endpoints[0].anchor.y, "source anchor y");
    +		equal(0.7, conn2.endpoints[1].anchor.x, "target anchor x");
    +		equal(0.7, conn2.endpoints[1].anchor.y, "target anchor y");
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.connect (Connector as string test)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var conn = _jsPlumb.connect({ source:d16, target:d17, connector:"Straight" });
    +		assertContextSize(3);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb);
    +		equal(conn.getConnector().type, "Straight", "Straight connector chosen for connection");
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.connect (Endpoint test)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" });
    +		assertContextSize(3);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb);
    +		equal(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source");
    +		equal(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection target");
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.connect (Endpoint as string test)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Rectangle" });
    +		assertContextSize(3);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb);
    +		equal(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source");
    +		equal(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection target");
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.connect (Endpoints test)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] });
    +		assertContextSize(3);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb);
    +		equal(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source");
    +		equal(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Dot, "Dot endpoint chosen for connection target");
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.connect (Blank Endpoint specified via 'endpoint' param)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var conn = _jsPlumb.connect({ source:d16, target:d17, endpoint:"Blank" });
    +		assertContextSize(3);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb);
    +		equal(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection source");
    +		equal(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection target");
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.connect (Blank Endpoint specified via 'endpoints' param)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Blank", "Blank" ] });
    +		assertContextSize(3);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb);
    +		equal(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection source");
    +		equal(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Blank, "Blank endpoint chosen for connection target");
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.connect (Endpoint as string test)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var conn = _jsPlumb.connect({ source:d16, target:d17, endpoints:["Rectangle", "Dot" ] });
    +		assertContextSize(3);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb);
    +		equal(conn.endpoints[0].endpoint.constructor, jsPlumb.Endpoints[renderMode].Rectangle, "Rectangle endpoint chosen for connection source");
    +		equal(conn.endpoints[1].endpoint.constructor, jsPlumb.Endpoints[renderMode].Dot, "Dot endpoint chosen for connection target");
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.connect (by Endpoints, connector test)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var e16 = _jsPlumb.addEndpoint(d16, {});
    +		var e17 = _jsPlumb.addEndpoint(d17, {});
    +		var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" });
    +		assertContextSize(3);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb);
    +		equal(e16.connections[0].getConnector().type, "Straight", "Straight connector chosen for connection");
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.connect (by Endpoints, connector as string test)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17");
    +		var e16 = _jsPlumb.addEndpoint(d16, {});
    +		var e17 = _jsPlumb.addEndpoint(d17, {});
    +		var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" });
    +		assertContextSize(3);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb);
    +		equal(e16.connections[0].getConnector().type, "Straight", "Straight connector chosen for connection");
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.connect (by Endpoints, anchors as string test)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17");
    +		var a16 = "TopCenter", a17 = "BottomCenter";
    +		var e16 = _jsPlumb.addEndpoint(d16, {anchor:a16});
    +		var e17 = _jsPlumb.addEndpoint(d17, {anchor:a17});
    +		var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" });
    +		assertContextSize(3);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb);
    +		equal(e16.connections[0].getConnector().type, "Straight", "Straight connector chosen for connection");
    +		equal(e16.anchor.x, 0.5, "endpoint 16 is at top center");equal(e16.anchor.y, 0, "endpoint 16 is at top center");
    +		equal(e17.anchor.x, 0.5, "endpoint 17 is at bottom center");equal(e17.anchor.y, 1, "endpoint 17 is at bottom center");
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create anchors)", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17");
    +		var a16 = [0,0.5,0,-1], a17 = [1,0.0,-1,-1];
    +		var e16 = _jsPlumb.addEndpoint(d16, {anchor:a16});
    +		var e17 = _jsPlumb.addEndpoint(d17, {anchor:a17});
    +		var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" });
    +		assertContextSize(3);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb);
    +		equal(e16.connections[0].getConnector().type, "Straight", "Straight connector chosen for connection");
    +		equal(e16.anchor.x, a16[0]);equal(e16.anchor.y, a16[1]);
    +		equal(e17.anchor.x, a17[0]);equal(e17.anchor.y, a17[1]);
    +		equal(e16.anchor.getOrientation()[0], a16[2]); equal(e16.anchor.getOrientation()[1], a16[3]);
    +		equal(e17.anchor.getOrientation()[0], a17[2]); equal(e17.anchor.getOrientation()[1], a17[3]);
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchor')", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var e16 = _jsPlumb.addEndpoint(d16, {anchor:[[0,0.5,0,-1], [1,0.5,0,1]]});
    +		var e17 = _jsPlumb.addEndpoint(d17, {anchor:["TopCenter", "BottomCenter"]});
    +		var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" });
    +		assertContextSize(3);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb);
    +		equal(e16.connections[0].getConnector().type, "Straight", "Straight connector chosen for connection");
    +		equal(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor");
    +		equal(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor");
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.connect (by Endpoints, endpoints create dynamic anchors; anchors specified by 'anchors')", function() {
    +		var d16 = _addDiv("d16"), d17 = _addDiv("d17"); 
    +		var e16 = _jsPlumb.addEndpoint(d16, {anchors:[[0,0.5,0,-1], [1,0.5,0,1]]});
    +		var e17 = _jsPlumb.addEndpoint(d17, {anchors:["TopCenter", "BottomCenter"]});
    +		var conn = _jsPlumb.connect({ sourceEndpoint:e16, targetEndpoint:e17, connector:"Straight" });
    +		assertContextSize(3);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb);
    +		equal(e16.connections[0].getConnector().type, "Straight", "Straight connector chosen for connection");
    +		equal(e16.anchor.isDynamic, true, "Endpoint 16 has a dynamic anchor");
    +		equal(e17.anchor.isDynamic, true, "Endpoint 17 has a dynamic anchor");
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.connect (connect by element, default endpoint, supplied dynamic anchors)", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3");
    +		var anchors = [ [0.25, 0, 0, -1], [1, 0.25, 1, 0], [0.75, 1, 0, 1], [0, 0.75, -1, 0] ];
    +		_jsPlumb.connect({source:d1, target:d2, dynamicAnchors:anchors});                // auto connect with default endpoint and provided anchors
    +		assertContextSize(3);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb);
    +		assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb);
    +		_jsPlumb.detach({source:d1, target:d2});
    +		// this changed in 1.3.5, because auto generated endpoints are now removed by detach.  so i added the test below this one
    +		// to check that the deleteEndpointsOnDetach flag is honoured.
    +		assertEndpointCount("d1", 0, _jsPlumb);assertEndpointCount("d2", 0, _jsPlumb);
    +		assertContextSize(2);
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.connect (connect by element, default endpoint, supplied dynamic anchors, delete on detach false)", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3");
    +		var anchors = [ [0.25, 0, 0, -1], [1, 0.25, 1, 0], [0.75, 1, 0, 1], [0, 0.75, -1, 0] ];
    +		_jsPlumb.connect({
    +			source:d1, 
    +			target:d2, 
    +			dynamicAnchors:anchors,
    +			deleteEndpointsOnDetach:false
    +		});                // auto connect with default endpoint and provided anchors
    +		assertContextSize(3);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb);
    +		assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb);
    +		_jsPlumb.detach({source:d1, target:d2});
    +		// this changed in 1.3.5, because auto generated endpoints are now removed by detach.  so i added this test
    +		// to check that the deleteEndpointsOnDetach flag is honoured.
    +		assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb);
    +		assertContextSize(2);
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.connect (connect by element, supplied endpoint and dynamic anchors)", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3");
    +		var endpoint = { isSource:true };
    +		var e1 = _jsPlumb.addEndpoint(d1, endpoint);
    +		var e2 = _jsPlumb.addEndpoint(d2, endpoint);
    +		var anchors = [ "TopCenter", "BottomCenter" ];
    +		_jsPlumb.connect({sourceEndpoint:e1, targetEndpoint:e2, dynamicAnchors:anchors});
    +		assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb);
    +		assertContextSize(3);
    +		_jsPlumb.detach({source:d1, target:d2});
    +		assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb);
    +		assertContextSize(2);
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.connect (connect by element, supplied endpoints using 'source' and 'target' (this test is identical to the one above apart from the param names), and dynamic anchors)", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3");
    +		var endpoint = { isSource:true };
    +		var e1 = _jsPlumb.addEndpoint(d1, endpoint);
    +		var e2 = _jsPlumb.addEndpoint(d2, endpoint);
    +		var anchors = [ "TopCenter", "BottomCenter" ];
    +		_jsPlumb.connect({source:e1, target:e2, dynamicAnchors:anchors});
    +		assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb);
    +		assertContextSize(3);
    +		_jsPlumb.detach({source:d1, target:d2});
    +		assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb);
    +		assertContextSize(2);
    +	});
    +
    +	test(renderMode + ": jsPlumb.connect, events specified", function() {
    +		var d1 = _addDiv("d1"), _d2 = _addDiv("d2"), 
    +			clicked = 0,
    +			c = _jsPlumb.connect({
    +				source:d1,
    +				target:d2,
    +				events:{
    +					click:function(conn) {
    +						clicked++;
    +					}
    +				}
    +			});
    +
    +		c.fire("click", c);
    +		equal(1, clicked, "connection was clicked once");
    +	});
    +
    +    test(renderMode + " detachable parameter defaults to true on _jsPlumb.connect", function() {
    +        var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +            c = _jsPlumb.connect({source:d1, target:d2});
    +        equal(c.isDetachable(), true, "connections detachable by default");
    +    });
    +
    +    test(renderMode + " detachable parameter set to false on _jsPlumb.connect", function() {
    +        var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +            c = _jsPlumb.connect({source:d1, target:d2, detachable:false});
    +        equal(c.isDetachable(), false, "connection detachable");
    +    });
    +
    +    test(renderMode + " setDetachable on initially detachable connection", function() {
    +        var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +            c = _jsPlumb.connect({source:d1, target:d2});
    +        equal(c.isDetachable(), true, "connection initially detachable");
    +        c.setDetachable(false);
    +        equal(c.isDetachable(), false, "connection not detachable");
    +    });
    +
    +    test(renderMode + " setDetachable on initially not detachable connection", function() {
    +        var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +            c = _jsPlumb.connect({source:d1, target:d2, detachable:false });
    +        equal(c.isDetachable(), false, "connection not initially detachable");
    +        c.setDetachable(true);
    +        equal(c.isDetachable(), true, "connection now detachable");
    +    });
    +
    +    test(renderMode + " _jsPlumb.Defaults.ConnectionsDetachable", function() {
    +        _jsPlumb.Defaults.ConnectionsDetachable = false;
    +        var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +            c = _jsPlumb.connect({source:d1, target:d2});
    +        equal(c.isDetachable(), false, "connections not detachable by default (overrode the defaults)");
    +        _jsPlumb.Defaults.ConnectionsDetachable = true;
    +    });
    +	
    +	
    +	test(renderMode + ": _jsPlumb.connect (testing for connection event callback)", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3");
    +		var connectCallback = null, detachCallback = null;
    +		_jsPlumb.bind("jsPlumbConnection", function(params) {
    +				connectCallback = $.extend({}, params);
    +			});
    +		_jsPlumb.bind("jsPlumbConnectionDetached", function(params) {
    +				detachCallback = $.extend({}, params);
    +			});
    +		_jsPlumb.connect({source:d1, target:d2});                // auto connect with default endpoint and anchor set
    +		ok(connectCallback != null, "connect callback was made");
    +		assertContextSize(3);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb);
    +		assertEndpointCount("d1", 1, _jsPlumb);assertEndpointCount("d2", 1, _jsPlumb);
    +		_jsPlumb.detach({source:d1, target:d2});
    +		assertContextSize(2);
    +		ok(detachCallback != null, "detach callback was made");
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.connect (setting cssClass on Connector)", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var c = _jsPlumb.connect({source:d1,target:d2,cssClass:"CSS"});
    +		var has = function(clazz) { 
    +			var cn = c.getConnector().canvas.className,
    +				cns = cn.constructor == String ? cn : cn.baseVal; 
    +			
    +			return cns.indexOf(clazz) != -1; 
    +		};		
    +		ok(has("CSS"), "custom cssClass set correctly");
    +		ok(has(_jsPlumb.connectorClass), "basic connector class set correctly");
    +	});
    +    
    +	test(renderMode + ": _jsPlumb.addEndpoint (setting cssClass on Endpoint)", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var e = _jsPlumb.addEndpoint(d1, {cssClass:"CSS"});
    +		var has = function(clazz) { 
    +			var cn = e.endpoint.canvas.className,
    +				cns = cn.constructor == String ? cn : cn.baseVal; 
    +			
    +			return cns.indexOf(clazz) != -1; 
    +		};		
    +		ok(has("CSS"), "custom cssClass set correctly");
    +		ok(has(_jsPlumb.endpointClass), "basic endpoint class set correctly");
    +	});    
    +	
    +	test(renderMode + ": _jsPlumb.connect (overlays, long-hand version)", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3");
    +		var imageEventListener = function() { };
    +		var arrowSpec = {width:40,length:40,location:0.7, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"}};
    +		var connection1 = _jsPlumb.connect({
    +		source:d1, 
    +	   	target:d2, 
    +	   	anchors:["BottomCenter", [ 0.75,0,0,-1 ]], 
    +	   	overlays : [ ["Label",{label:"CONNECTION 1", location:0.3}],
    +					["Arrow",arrowSpec ] ]
    +		});
    +		equal(2, connection1.overlays.length);
    +		equal(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor);
    +		
    +		equal(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor);
    +		equal(0.7, connection1.overlays[1].loc);
    +		equal(40, connection1.overlays[1].width);
    +		equal(40, connection1.overlays[1].length);
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.connect (overlays, long-hand version, IDs specified)", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3");
    +		var imageEventListener = function() { };
    +		var arrowSpec = { 
    +				width:40,
    +				length:40,
    +				location:0.7, 
    +				foldback:0, 
    +				paintStyle:{lineWidth:1, strokeStyle:"#000000"},
    +				id:"anArrow"
    +		};
    +		var connection1 = _jsPlumb.connect({
    +		source:d1, 
    +	   	target:d2, 
    +	   	anchors:["BottomCenter", [ 0.75,0,0,-1 ]], 
    +	   	overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}],
    +					["Arrow",arrowSpec ] ]
    +		});
    +		equal(2, connection1.overlays.length);
    +		equal(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor);
    +		equal("aLabel", connection1.overlays[0].id);
    +		
    +		equal(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor);
    +		equal(0.7, connection1.overlays[1].loc);
    +		equal(40, connection1.overlays[1].width);
    +		equal(40, connection1.overlays[1].length);
    +		equal("anArrow", connection1.overlays[1].id);
    +	});
    +
    +	test(renderMode + ": _jsPlumb.connect (default overlays)", function() {
    +		_jsPlumb.Defaults.Overlays = [
    +			["Arrow",{ location:0.1, id:"arrow" }]
    +		];
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +			c = _jsPlumb.connect({source:d1, target:d2});
    +
    +		ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults");
    +	});
    +
    +	test(renderMode + ": _jsPlumb.connect (default overlays + overlays specified in connect call)", function() {
    +		_jsPlumb.Defaults.Overlays = [
    +			["Arrow",{ location:0.1, id:"arrow" }]
    +		];
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +			c = _jsPlumb.connect({source:d1, target:d2, overlays:[
    +				["Label", {id:"label"}]
    +			]});
    +
    +		ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults");
    +		ok(c.getOverlay("label") != null, "Label overlay created from connect call");
    +	});
    +
    +	test(renderMode + ": _jsPlumb.connect (default connection overlays)", function() {
    +		_jsPlumb.Defaults.ConnectionOverlays = [
    +			["Arrow",{ location:0.1, id:"arrow" }]
    +		];
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +			c = _jsPlumb.connect({source:d1, target:d2});
    +
    +		ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults");
    +	});
    +
    +	test(renderMode + ": _jsPlumb.connect (default connection overlays + overlays specified in connect call)", function() {
    +		_jsPlumb.Defaults.ConnectionOverlays = [
    +			["Arrow",{ location:0.1, id:"arrow" }]
    +		];
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +			c = _jsPlumb.connect({source:d1, target:d2, overlays:[
    +				["Label", {id:"label"}]
    +			]});
    +
    +		ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults");
    +		ok(c.getOverlay("label") != null, "Label overlay created from connect call");
    +	});
    +
    +	test(renderMode + ": _jsPlumb.connect (default overlays + default connection overlays)", function() {
    +		_jsPlumb.Defaults.ConnectionOverlays = [
    +			["Arrow",{ location:0.1, id:"arrow" }]
    +		];
    +		_jsPlumb.Defaults.Overlays = [
    +			["Arrow",{ location:0.1, id:"arrow2" }]
    +		];
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +			c = _jsPlumb.connect({source:d1, target:d2});
    +
    +		ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults");
    +		ok(c.getOverlay("arrow2") != null, "Arrow overlay created from connection defaults");
    +	});
    +
    +	test(renderMode + ": _jsPlumb.connect (default overlays + default connection overlays)", function() {
    +		_jsPlumb.Defaults.ConnectionOverlays = [
    +			["Arrow",{ location:0.1, id:"arrow" }]
    +		];
    +		_jsPlumb.Defaults.Overlays = [
    +			["Arrow",{ location:0.1, id:"arrow2" }]
    +		];
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +			c = _jsPlumb.connect({source:d1, target:d2, overlays:[
    +					["Label", {id:"label"}]
    +				]});
    +
    +		ok(c.getOverlay("arrow") != null, "Arrow overlay created from defaults");
    +		ok(c.getOverlay("arrow2") != null, "Arrow overlay created from connection defaults");
    +		ok(c.getOverlay("label") != null, "Label overlay created from connect call");
    +	});
    +
    +	test(renderMode + ": _jsPlumb.connect (label overlay set using 'label')", function() {		
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +			c = _jsPlumb.connect({
    +				source:d1, 
    +				target:d2, 
    +				label:"FOO"
    +			});
    +
    +		equal(c.getLabel(), "FOO", "label is set correctly");
    +	});
    +
    +	test(renderMode + ": _jsPlumb.connect (set label after construction, with string)", function() {		
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +			c = _jsPlumb.connect({
    +				source:d1, 
    +				target:d2});
    +
    +		c.setLabel("FOO");
    +		equal(c.getLabel(), "FOO", "label is set correctly");
    +	});
    +
    +	test(renderMode + ": _jsPlumb.connect (set label after construction, with function)", function() {		
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +			c = _jsPlumb.connect({
    +				source:d1, 
    +				target:d2});
    +
    +		c.setLabel(function() { return "BAR"; });
    +		equal(c.getLabel()(), "BAR", "label is set correctly");
    +	});
    +
    +	test(renderMode + ": _jsPlumb.connect (set label after construction, with params object)", function() {		
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +			c = _jsPlumb.connect({
    +				source:d1, 
    +				target:d2});
    +
    +		c.setLabel({
    +			label:"BAZ",
    +			cssClass:"CLASSY",
    +			location:0.9
    +		});
    +		var lo = c.getLabelOverlay();
    +		ok(lo != null, "label overlay exists");
    +		equal(lo.getLabel(), "BAZ", "label overlay has correct value");
    +		equal(lo.getLocation(), 0.9, "label overlay has correct location");
    +	});
    +
    +	test(renderMode + ": _jsPlumb.connect (set label after construction with existing label set, with params object)", function() {		
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +			c = _jsPlumb.connect({
    +				source:d1, 
    +				target:d2,
    +				label:"FOO",
    +				labelLocation:0.2
    +			});
    +
    +		var lo = c.getLabelOverlay();
    +		equal(lo.getLocation(), 0.2, "label overlay has correct location");
    +
    +		c.setLabel({
    +			label:"BAZ",
    +			cssClass:"CLASSY",
    +			location:0.9
    +		});
    +		
    +		ok(lo != null, "label overlay exists");
    +		equal(lo.getLabel(), "BAZ", "label overlay has correct value");
    +		equal(lo.getLocation(), 0.9, "label overlay has correct location");
    +	});
    +
    +	test(renderMode + ": _jsPlumb.connect (getLabelOverlay, label on connect call)", function() {		
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +			c = _jsPlumb.connect({
    +				source:d1, 
    +				target:d2, 
    +				label:"FOO"
    +			}),
    +			lo = c.getLabelOverlay();
    +
    +		ok(lo != null, "label overlay exists");
    +		equal(lo.getLabel(), "FOO", "label overlay has correct value");
    +		equal(lo.getLocation(), 0.5, "label overlay has correct location");
    +	});
    +
    +	test(renderMode + ": _jsPlumb.connect (getLabelOverlay, label on connect call, location set)", function() {		
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +			c = _jsPlumb.connect({
    +				source:d1, 
    +				target:d2, 
    +				label:"FOO",
    +				labelLocation:0.2
    +			}),
    +			lo = c.getLabelOverlay();
    +
    +		ok(lo != null, "label overlay exists");
    +		equal(lo.getLabel(), "FOO", "label overlay has correct value");
    +		equal(lo.getLocation(), 0.2, "label overlay has correct location");
    +	});	
    +	
    +	test(renderMode + ": _jsPlumb.connect (remove single overlay by id)", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var arrowSpec = { 
    +				width:40,
    +				length:40,
    +				location:0.7, 
    +				foldback:0, 
    +				paintStyle:{lineWidth:1, strokeStyle:"#000000"},
    +				id:"anArrow"
    +		};
    +		var connection1 = _jsPlumb.connect({
    +		source:d1, 
    +	   	target:d2, 
    +	   	anchors:["BottomCenter", [ 0.75,0,0,-1 ]], 
    +	   	overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}],
    +					["Arrow",arrowSpec ] ]
    +		});
    +		equal(2, connection1.overlays.length);
    +		connection1.removeOverlay("aLabel");
    +		equal(1, connection1.overlays.length);
    +		equal("anArrow", connection1.overlays[0].id);
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.connect (remove multiple overlays by id)", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var arrowSpec = { 
    +				width:40,
    +				length:40,
    +				location:0.7, 
    +				foldback:0, 
    +				paintStyle:{lineWidth:1, strokeStyle:"#000000"},
    +				id:"anArrow"
    +		};
    +		var connection1 = _jsPlumb.connect({
    +		source:d1, 
    +	   	target:d2, 
    +	   	anchors:["BottomCenter", [ 0.75,0,0,-1 ]], 
    +	   	overlays : [ ["Label",{label:"CONNECTION 1", location:0.3, id:"aLabel"}],
    +					["Arrow",arrowSpec ] ]
    +		});
    +		equal(2, connection1.overlays.length);
    +		connection1.removeOverlays("aLabel", "anArrow");
    +		equal(0, connection1.overlays.length);
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.connect (overlays, short-hand version)", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3");
    +		var imageEventListener = function() { };
    +		var loc = { location:0.7 };
    +		var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} };
    +		var connection1 = _jsPlumb.connect({
    +		source:d1, 
    +	   	target:d2, 
    +	   	anchors:["BottomCenter", [ 0.75,0,0,-1 ]], 
    +	   	overlays : [ ["Label",  {label:"CONNECTION 1", location:0.3}],
    +					["Arrow", arrowSpec, loc] ]
    +		});
    +		equal(2, connection1.overlays.length);
    +		equal(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor);
    +		
    +		equal(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor);
    +		equal(0.7, connection1.overlays[1].loc);
    +		equal(40, connection1.overlays[1].width);
    +		equal(40, connection1.overlays[1].length);
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.connect (removeAllOverlays)", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3");
    +		var imageEventListener = function() { };
    +		var loc = { location:0.7 };
    +		var arrowSpec = { width:40,length:40, foldback:0, paintStyle:{lineWidth:1, strokeStyle:"#000000"} };
    +		var connection1 = _jsPlumb.connect({
    +		source:d1, 
    +	   	target:d2, 
    +	   	anchors:["BottomCenter", [ 0.75,0,0,-1 ]], 
    +	   	overlays : [ ["Label",  {label:"CONNECTION 1", location:0.3, cssClass:"PPPP"}],
    +					["Arrow", arrowSpec, loc] ]
    +		});
    +		equal(2, connection1.overlays.length);
    +		equal(jsPlumb.Overlays[renderMode].Label, connection1.overlays[0].constructor);
    +		
    +		equal(jsPlumb.Overlays[renderMode].Arrow, connection1.overlays[1].constructor);
    +		equal(0.7, connection1.overlays[1].loc);
    +		equal(40, connection1.overlays[1].width);
    +		equal(40, connection1.overlays[1].length);
    +		
    +		connection1.removeAllOverlays();
    +		equal(0, connection1.overlays.length);
    +		equal(0, $(".PPPP").length);
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.connect, specify arrow overlay using string identifier only", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3");
    +		var conn = _jsPlumb.connect({source:d1,target:d2,overlays:["Arrow"]});
    +		equal(jsPlumb.Overlays[renderMode].Arrow, conn.overlays[0].constructor);
    +	});
    +	
    +	test(renderMode + ": Connection.getOverlay method, existing overlay", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] });
    +		var overlay = conn.getOverlay("arrowOverlay");
    +		ok(overlay != null);
    +	});
    +	
    +	test(renderMode + ": Connection.getOverlay method, non-existent overlay", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] });
    +		var overlay = conn.getOverlay("IDONTEXIST");
    +		ok(overlay == null);
    +	});
    +	
    +	test(renderMode + ": Overlay.setVisible method", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var conn = _jsPlumb.connect({source:d1,target:d2,overlays:[ [ "Arrow", { id:"arrowOverlay" } ] ] });
    +		var overlay = conn.getOverlay("arrowOverlay");
    +		ok(overlay.isVisible());
    +		overlay.setVisible(false);
    +		ok(!overlay.isVisible());
    +		overlay.setVisible(true);
    +		ok(overlay.isVisible());
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.connect (custom label overlay, set on Defaults, return plain DOM element)", function() {
    +		_jsPlumb.Defaults.ConnectionOverlays = [
    +			["Custom",{ id:"custom", create:function(connection) {
    +				ok(connection != null, "we were passed in a connection");
    +				var d = document.createElement("div");
    +				d.setAttribute("custom", "true");
    +				d.innerHTML = connection.id;
    +				return d;
    +			}}]
    +		];
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +			c = _jsPlumb.connect({source:d1, target:d2});
    +
    +		var o = c.getOverlay("custom");
    +		equal(o.getElement().getAttribute("custom"), "true", "custom overlay created correctly");
    +		equal(o.getElement().innerHTML, c.id, "custom overlay has correct value");		
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.connect (custom label overlay, set on Defaults, return selector)", function() {
    +		_jsPlumb.Defaults.ConnectionOverlays = [
    +			["Custom",{ id:"custom", create:function(connection) {
    +				ok(connection != null, "we were passed in a connection");
    +				return $("<div custom='true'>" + connection.id + "</div>");
    +			}}]
    +		];
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +			c = _jsPlumb.connect({source:d1, target:d2});
    +
    +		var o = c.getOverlay("custom");
    +		equal(o.getElement().getAttribute("custom"), "true", "custom overlay created correctly");
    +		equal(o.getElement().innerHTML, c.id, "custom overlay has correct value");		
    +	});
    +
    +	test(renderMode + ": overlay events", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3");
    +		var clicked = 0;
    +		var connection1 = _jsPlumb.connect({
    +			source:d1, 
    +	   		target:d2, 	   	
    +	   		overlays : [ ["Label",{
    +	   			label:"CONNECTION 1", 
    +	   			location:0.3,
    +	   			id:"label",
    +	   			events:{
    +	   				click:function(label, e) {
    +	   					clicked++;
    +	   				}
    +	   			} 
    +	   		}]]
    +		});
    +		var l = connection1.getOverlay("label");
    +		l.fire("click", l);
    +		equal(clicked, 1, "click event was fired once");
    +	});
    +	
    +	// this test is for the original detach function; it should stay working after i mess with it
    +	// a little.
    +	test(renderMode + ": _jsPlumb.detach (by element ids)", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var e1 = _jsPlumb.addEndpoint(d1);
    +		var e2 = _jsPlumb.addEndpoint(d2);
    +		var e3 = _jsPlumb.addEndpoint(d1);
    +		var e4 = _jsPlumb.addEndpoint(d2);
    +		_jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 });
    +		_jsPlumb.connect({ sourceEndpoint:e3, targetEndpoint:e4 });  // make two connections to be sure this works ;)
    +		assertConnectionCount(e1, 1);
    +		assertConnectionCount(e2, 1);
    +		assertConnectionCount(e3, 1);
    +		assertConnectionCount(e4, 1);
    +		
    +		_jsPlumb.detach({source:"d1", target:"d2"});
    +		
    +		assertConnectionCount(e1, 0);
    +		assertConnectionCount(e2, 0);
    +		assertConnectionCount(e3, 0);
    +		assertConnectionCount(e4, 0);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb);
    +	});
    +	
    +	// detach is being made to operate more like connect - by taking one argument with a whole 
    +	// bunch of possible params in it.  if two args are passed in it will continue working
    +	// in the old way.
    +	test(renderMode + ": _jsPlumb.detach (params object, using element ids)", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var e1 = _jsPlumb.addEndpoint(d1);
    +		var e2 = _jsPlumb.addEndpoint(d2);
    +		_jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 });
    +		assertConnectionCount(e1, 1);
    +		assertConnectionCount(e2, 1);
    +		_jsPlumb.detach({source:"d1", target:"d2"});
    +		assertConnectionCount(e1, 0);
    +		assertConnectionCount(e2, 0);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb);
    +	});
    +/*
    +    test(renderMode + ": _jsPlumb.detach (params object, using target only)", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"),
    +		    e1 = _jsPlumb.addEndpoint(d1, {maxConnections:2}),
    +		    e2 = _jsPlumb.addEndpoint(d2),
    +            e3 = _jsPlumb.addEndpoint(d3);
    +		_jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 });
    +        _jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e3 });
    +		assertConnectionCount(e1, 2);
    +		assertConnectionCount(e2, 1);
    +        assertConnectionCount(e3, 1);
    +		_jsPlumb.detach({target:"d2"});
    +		assertConnectionCount(e1, 1);
    +		assertConnectionCount(e2, 0);
    +        assertConnectionCount(e3, 1);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1);
    +	});
    +*/
    +	test(renderMode + ": _jsPlumb.detach (params object, using element objects)", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var e1 = _jsPlumb.addEndpoint(d1);
    +		var e2 = _jsPlumb.addEndpoint(d2);
    +		_jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 });
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb);
    +		assertContextSize(3);
    +		assertConnectionCount(e1, 1);
    +		assertConnectionCount(e2, 1);
    +		_jsPlumb.detach({source:d1, target:d2});
    +		assertConnectionCount(e1, 0);
    +		assertConnectionCount(e2, 0);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb);
    +		assertContextSize(2);
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.detach (source and target as endpoint UUIDs)", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var e1 = _jsPlumb.addEndpoint(d1, {uuid:"abcdefg"});
    +		ok(_jsPlumb.getEndpoint("abcdefg") != null, "e1 exists");	
    +		var e2 = _jsPlumb.addEndpoint(d2, {uuid:"hijklmn"});
    +		ok(_jsPlumb.getEndpoint("hijklmn") != null, "e2 exists");
    +		_jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 });
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb);
    +		assertContextSize(3);
    +		assertConnectionCount(e1, 1);
    +		assertConnectionCount(e2, 1);
    +		_jsPlumb.detach({uuids:["abcdefg", "hijklmn"]});
    +		assertConnectionCount(e1, 0);
    +		assertConnectionCount(e2, 0);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb);
    +		assertContextSize(2);
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.detach (sourceEndpoint and targetEndpoint supplied)", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var e1 = _jsPlumb.addEndpoint(d1);
    +		var e2 = _jsPlumb.addEndpoint(d2);
    +		_jsPlumb.connect({ sourceEndpoint:e1, targetEndpoint:e2 });
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 1, _jsPlumb);
    +		assertContextSize(3);
    +		assertConnectionCount(e1, 1);
    +		assertConnectionCount(e2, 1);
    +		_jsPlumb.detach({ sourceEndpoint:e1, targetEndpoint:e2 });
    +		assertConnectionCount(e1, 0);
    +		assertConnectionCount(e2, 0);
    +		assertConnectionByScopeCount(_jsPlumb.getDefaultScope(), 0, _jsPlumb);
    +		assertContextSize(2);
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.makeDynamicAnchors (longhand)", function() {
    +		var anchors = [_jsPlumb.makeAnchor([0.2, 0, 0, -1], null, _jsPlumb), _jsPlumb.makeAnchor([1, 0.2, 1, 0], null, _jsPlumb), 
    +					   _jsPlumb.makeAnchor([0.8, 1, 0, 1], null, _jsPlumb), _jsPlumb.makeAnchor([0, 0.8, -1, 0], null, _jsPlumb) ];				   				
    +		var dynamicAnchor = _jsPlumb.makeDynamicAnchor(anchors);
    +		var a = dynamicAnchor.getAnchors();
    +		equal(a.length, 4, "Dynamic Anchors has four anchors");
    +		for (var i = 0; i < a.length; i++)
    +			ok(a[i].compute.constructor == Function, "anchor " + i + " well formed");
    +	});
    +	
    +	test(renderMode + ": _jsPlumb.makeDynamicAnchors (shorthand)", function() {
    +		var anchors = [[0.2, 0, 0, -1], [1, 0.2, 1, 0], 
    +					   [0.8, 1, 0, 1], [0, 0.8, -1, 0] ];				   				
    +		var dynamicAnchor = _jsPlumb.makeDynamicAnchor(anchors);
    +		var a = dynamicAnchor.getAnchors();
    +		equal(a.length, 4, "Dynamic Anchors has four anchors");
    +		for (var i = 0; i < a.length; i++)
    +			ok(a[i].compute.constructor == Function, "anchor " + i + " well formed");
    +	});
    +	
    +	test(renderMode + ": Connection.isVisible/setVisible", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var c1 = _jsPlumb.connect({source:d1,target:d2});
    +		equal(true, c1.isVisible(), "Connection is visible after creation.");
    +		c1.setVisible(false);
    +		equal(false, c1.isVisible(), "Connection is not visible after calling setVisible(false).");
    +		equal($(c1.getConnector().canvas).css("display"), "none");
    +		c1.setVisible(true);
    +		equal(true, c1.isVisible(), "Connection is visible after calling setVisible(true).");
    +		equal($(c1.getConnector().canvas).css("display"), "block");
    +	});
    +	
    +	test(renderMode + ": Endpoint.isVisible/setVisible basic test (no connections)", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var e1 = _jsPlumb.addEndpoint(d1);
    +		equal(true, e1.isVisible(), "Endpoint is visible after creation.");
    +		e1.setVisible(false);
    +		equal(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false).");
    +		equal($(e1.canvas).css("display"), "none");
    +		e1.setVisible(true);
    +		equal(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true).");
    +		equal($(e1.canvas).css("display"), "block");
    +	});
    +	
    +	test(renderMode + ": Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should track changes in the source, because it has only this connection.)", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2);
    +		equal(true, e1.isVisible(), "Endpoint is visible after creation.");
    +		var c1 = _jsPlumb.connect({source:e1, target:e2});
    +		e1.setVisible(false);
    +		equal(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false).");
    +		equal(false, e2.isVisible(), "other Endpoint is not visible either.");
    +		equal(false, c1.isVisible(), "connection between the two is not visible either.");
    +		
    +		e1.setVisible(true);
    +		equal(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true).");
    +		equal(true, e2.isVisible(), "other Endpoint is visible too");
    +		equal(true, c1.isVisible(), "connection between the two is visible too.");
    +	});
    +	
    +	test(renderMode + ": Endpoint.isVisible/setVisible (one connection, other Endpoint's visibility should not track changes in the source, because it has another connection.)", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3");
    +		var e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2, { maxConnections:2 }), e3 = _jsPlumb.addEndpoint(d3);
    +		equal(true, e1.isVisible(), "Endpoint is visible after creation.");
    +		var c1 = _jsPlumb.connect({source:e1, target:e2});
    +		var c2 = _jsPlumb.connect({source:e2, target:e3});
    +		
    +		e1.setVisible(false);
    +		equal(false, e1.isVisible(), "Endpoint is not visible after calling setVisible(false).");
    +		equal(true, e2.isVisible(), "other Endpoint should still be visible.");
    +		equal(true, e3.isVisible(), "third Endpoint should still be visible.");
    +		equal(false, c1.isVisible(), "connection between the two is not visible either.");
    +		equal(true, c2.isVisible(), "other connection is visible.");
    +		
    +		e1.setVisible(true);
    +		equal(true, e1.isVisible(), "Endpoint is visible after calling setVisible(true).");
    +		equal(true, e2.isVisible(), "other Endpoint is visible too");
    +		equal(true, c1.isVisible(), "connection between the two is visible too.");
    +		equal(true, c2.isVisible(), "other connection is visible.");
    +	});
    +	
    +	// tests of the functionality that allows a user to specify that they want elements appended to the document body
    +	test(renderMode + " _jsPlumb.Defaults.Container, specified with a selector", function() {
    +		_jsPlumb.Defaults.Container = $("body");
    +		equal($("#container")[0].childNodes.length, 0, "container has no nodes");
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		equal($("#container")[0].childNodes.length, 2, "container has two div elements");  // the divs we added have been added to the 'container' div.
    +		// but we have told _jsPlumb to add its canvas to the body, so this connect call should not add another few elements to the container:
    +		_jsPlumb.connect({source:d1, target:d2});
    +		equal($("#container")[0].childNodes.length, 2, "container still has two div elements");
    +	});
    +	
    +	// tests of the functionality that allows a user to specify that they want elements appended to some specific container.
    +	test(renderMode + " _jsPlumb.Defaults.Container, specified with DOM element", function() {		
    +		_jsPlumb.Defaults.Container = document.getElementsByTagName("body")[0];
    +		equal(0, $("#container")[0].childNodes.length);
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");		
    +		equal(2, $("#container")[0].childNodes.length, "two divs added to the container");  // the divs we added have been added to the 'container' div.
    +		// but we have told _jsPlumb to add its canvas to the body, so this connect call should not add another few elements to the container:
    +		var bodyElementCount = $("body")[0].childNodes.length;
    +		_jsPlumb.connect({source:d1, target:d2});
    +		equal(2, $("#container")[0].childNodes.length, "still only two children in container; elements were added to the body by _jsPlumb");
    +		// test to see if 3 elements have been added
    +		equal(bodyElementCount + 3, $("body")[0].childNodes.length, "3 new elements added to the document body");
    +	});
    +	
    +	test(renderMode + " container specified to connect call, with a selector", function() {
    +		equal(0, $("#container")[0].childNodes.length);
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		equal(2, $("#container")[0].childNodes.length);  // the divs we added have been added to the 'container' div.
    +		var bodyElementCount = $("body")[0].childNodes.length;
    +		// but here we tell _jsPlumb to add its elements to the body, so this connect call should not add another few elements to the container:
    +		_jsPlumb.connect({source:d1, target:d2, container:$("body")});
    +		equal(2, $("#container")[0].childNodes.length);
    +		equal(bodyElementCount + 3, $("body")[0].childNodes.length, "3 new elements added to the document body");
    +	});
    +	
    +	test(renderMode + " container specified to connect call, with a string ID", function() {
    +		equal(0, $("#container")[0].childNodes.length);
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3");
    +		equal(3, $("#container")[0].childNodes.length, "container has divs we added");  // the divs we added have been added to the 'container' div.
    +		var d3ElementCount = $("#d3")[0].childNodes.length;
    +		// but here we tell _jsPlumb to add its elements to "d3", so this connect call should not add another few elements to the container:
    +		_jsPlumb.connect({source:d1, target:d2, container:"d3"});
    +		equal(3, $("#container")[0].childNodes.length, "container still has only the divs we added");
    +		equal(d3ElementCount + 3, $("#d3")[0].childNodes.length, "3 new elements added to div d3");
    +	});	
    +	
    +	test(renderMode + " container specified to addEndpoint call, with a selector", function() {
    +		equal(0, $("#container")[0].childNodes.length);
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		equal(2, $("#container")[0].childNodes.length);  // the divs we added have been added to the 'container' div.
    +		var bodyElementCount = $("body")[0].childNodes.length;
    +		// but here we tell _jsPlumb to add its elements to the body, so this connect call should not add another few elements to the container:
    +		_jsPlumb.addEndpoint(d1, {container:$("body")});
    +		equal(2, $("#container")[0].childNodes.length);
    +		equal(bodyElementCount + 1, $("body")[0].childNodes.length, "1 new element added to the document body");
    +	});
    +	
    +	test(renderMode + " container specified to addEndpoint call, with a string ID", function() {
    +		equal(0, $("#container")[0].childNodes.length);
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3");
    +		equal(3, $("#container")[0].childNodes.length, "container has divs we added");  // the divs we added have been added to the 'container' div.
    +		var d3ElementCount = $("#d3")[0].childNodes.length;
    +		// but here we tell _jsPlumb to add its elements to "d3", so this connect call should not add another few elements to the container:
    +		_jsPlumb.addEndpoint(d1, { container:"d3" });
    +		equal(3, $("#container")[0].childNodes.length, "container still has only the divs we added");
    +		equal(d3ElementCount + 1, $("#d3")[0].childNodes.length, "1 new element added to div d3");
    +	});
    +
    +    test(renderMode + " detachable defaults to true when connection made between two endpoints", function() {
    +        var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +            e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2),
    +            c = _jsPlumb.connect({source:e1, target:e2});
    +        equal(c.isDetachable(), true, "connection not detachable");
    +    });
    +
    +    test(renderMode + " connection detachable when target endpoint has connectionsDetachable set to true", function() {
    +        var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +            e1 = _jsPlumb.addEndpoint(d1), e2 = _jsPlumb.addEndpoint(d2, {connectionsDetachable:true}),
    +            c = _jsPlumb.connect({source:e1, target:e2});
    +        equal(c.isDetachable(), true, "connection detachable because connectionsDetachable was set on target endpoint");
    +    });
    +
    +    test(renderMode + " connection detachable when source endpoint has connectionsDetachable set to true", function() {
    +        var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +            e1 = _jsPlumb.addEndpoint(d1, {connectionsDetachable:true}), e2 = _jsPlumb.addEndpoint(d2),
    +            c = _jsPlumb.connect({source:e1, target:e2});
    +        equal(c.isDetachable(), true, "connection detachable because connectionsDetachable was set on source endpoint");
    +    });
    +	
    +	test(renderMode + " Connector has 'type' member set", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		
    +		var c = _jsPlumb.connect({source:d1, target:d2});
    +		equal(c.getConnector().type, "Bezier", "Bezier connector has type set");
    +		
    +		var c2 = _jsPlumb.connect({source:d1, target:d2, connector:"Straight"});
    +		equal(c2.getConnector().type, "Straight", "Straight connector has type set");
    +		
    +		var c3 = _jsPlumb.connect({source:d1, target:d2, connector:"Flowchart"});
    +		equal(c3.getConnector().type, "Flowchart", "Flowchart connector has type set");
    +	});
    +	
    +	test(renderMode + " Endpoints have 'type' member set", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		
    +		var c = _jsPlumb.connect({source:d1, target:d2});
    +		equal(c.endpoints[0].type, "Dot", "Dot endpoint has type set");
    +		
    +		var c2 = _jsPlumb.connect({source:d1, target:d2, endpoints:["Rectangle", "Blank"]});
    +		equal(c2.endpoints[1].type, "Blank", "Blank endpoint has type set");
    +		equal(c2.endpoints[0].type, "Rectangle", "Rectangle endpoint has type set");		
    +	});
    +	
    +	test(renderMode + " Overlays have 'type' member set", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		
    +		var c = _jsPlumb.connect({
    +			source:d1, 
    +			target:d2,
    +			overlays:[ "Arrow", "Label", "PlainArrow", "Diamond" ]
    +		});
    +		equal(c.overlays[0].type, "Arrow", "Arrow overlay has type set");
    +		equal(c.overlays[1].type, "Label", "Label overlay has type set");
    +		equal(c.overlays[2].type, "PlainArrow", "PlainArrow overlay has type set");
    +		equal(c.overlays[3].type, "Diamond", "Diamond overlay has type set");		
    +	});
    +	
    +	test(renderMode + " _jsPlumb.hide, original one-arg version", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +		e = { isSource:true, isTarget:true, maxConnections:-1 },
    +		e1 = _jsPlumb.addEndpoint(d1, e),
    +		e2 = _jsPlumb.addEndpoint(d2, e),
    +		c1 = _jsPlumb.connect({source:e1, target:e2});
    +		
    +		equal(true, c1.isVisible(), "Connection 1 is visible after creation.");
    +		equal(true, e1.isVisible(), "endpoint 1 is visible after creation.");
    +		equal(true, e2.isVisible(), "endpoint 2 is visible after creation.");
    +		
    +		_jsPlumb.hide(d1);
    +		
    +		equal(false, c1.isVisible(), "Connection 1 is no longer visible.");
    +		equal(true, e1.isVisible(), "endpoint 1 is still visible.");
    +		equal(true, e2.isVisible(), "endpoint 2 is still visible.");
    +		
    +		_jsPlumb.show(d1);
    +		
    +		equal(true, c1.isVisible(), "Connection 1 is visible again.");
    +	});
    +	
    +	test(renderMode + " _jsPlumb.hide, two-arg version, endpoints should also be hidden", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +		e = { isSource:true, isTarget:true, maxConnections:-1 },
    +		e1 = _jsPlumb.addEndpoint(d1, e),
    +		e2 = _jsPlumb.addEndpoint(d2, e),
    +		c1 = _jsPlumb.connect({source:e1, target:e2});
    +		
    +		equal(true, c1.isVisible(), "Connection 1 is visible after creation.");
    +		equal(true, e1.isVisible(), "endpoint 1 is visible after creation.");
    +		equal(true, e2.isVisible(), "endpoint 2 is visible after creation.");
    +		
    +		_jsPlumb.hide("d1", true);
    +		
    +		equal(false, c1.isVisible(), "Connection 1 is no longer visible.");
    +		equal(false, e1.isVisible(), "endpoint 1 is no longer visible.");
    +		equal(true, e2.isVisible(), "endpoint 2 is still visible.");
    +		
    +		_jsPlumb.show(d1);  // now show d1, but do not alter the endpoints. e1 should still be hidden
    +		
    +		equal(true, c1.isVisible(), "Connection 1 is visible again.");
    +		equal(false, e1.isVisible(), "endpoint 1 is no longer visible.");
    +		equal(true, e2.isVisible(), "endpoint 2 is still visible.");
    +	});
    +	
    +	test(renderMode + " _jsPlumb.show, two-arg version, endpoints should become visible", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +		e = { isSource:true, isTarget:true, maxConnections:-1 },
    +		e1 = _jsPlumb.addEndpoint(d1, e),
    +		e2 = _jsPlumb.addEndpoint(d2, e),
    +		c1 = _jsPlumb.connect({source:e1, target:e2});
    +				
    +		_jsPlumb.hide("d1", true);
    +		
    +		equal(false, c1.isVisible(), "Connection 1 is no longer visible.");
    +		equal(false, e1.isVisible(), "endpoint 1 is no longer visible.");
    +		equal(true, e2.isVisible(), "endpoint 2 is still visible.");
    +		
    +		_jsPlumb.show(d1, true);  // now show d1, and alter the endpoints. e1 should be visible.
    +		
    +		equal(true, c1.isVisible(), "Connection 1 is visible again.");
    +		equal(true, e1.isVisible(), "endpoint 1 is visible again.");
    +		equal(true, e2.isVisible(), "endpoint 2 is still visible.");
    +	});
    +	
    +	test(renderMode + " _jsPlumb.show, two-arg version, endpoints should become visible, but not all connections, because some other endpoints are  not visible.", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"),
    +		e = { isSource:true, isTarget:true, maxConnections:-1 },
    +		e1 = _jsPlumb.addEndpoint(d1, e),
    +		e11 = _jsPlumb.addEndpoint(d1, e),
    +		e2 = _jsPlumb.addEndpoint(d2, e),
    +		e3 = _jsPlumb.addEndpoint(d3, e),
    +		c1 = _jsPlumb.connect({source:e1, target:e2}),
    +		c2 = _jsPlumb.connect({source:e11, target:e3});
    +			
    +		// we now have d1 connected to both d3 and d2.  we'll hide d1, and everything on d1 should be hidden.
    +		
    +		_jsPlumb.hide("d1", true);
    +		
    +		equal(false, c1.isVisible(), "connection 1 is no longer visible.");
    +		equal(false, c2.isVisible(), "connection 2 is no longer visible.");
    +		equal(false, e1.isVisible(), "endpoint 1 is no longer visible.");
    +		equal(false, e11.isVisible(), "endpoint 1 is no longer visible.");
    +		equal(true, e2.isVisible(), "endpoint 2 is still visible.");
    +		equal(true, e3.isVisible(), "endpoint 3 is still visible.");
    +		
    +		// now, we will also hide d3. making d1 visible again should NOT result in c2 becoming visible, because the other endpoint
    +		// for c2 is e3, which is not visible.
    +		_jsPlumb.hide(d3, true);
    +		equal(false, e3.isVisible(), "endpoint 3 is no longer visible.");
    +		
    +		_jsPlumb.show(d1, true);  // now show d1, and alter the endpoints. e1 should be visible, c1 should be visible, but c2 should not.
    +		
    +		equal(true, c1.isVisible(), "Connection 1 is visible again.");
    +		equal(false, c2.isVisible(), "Connection 2 is not visible.");
    +		equal(true, e1.isVisible(), "endpoint 1 is visible again.");
    +		equal(true, e11.isVisible(), "endpoint 11 is visible again.");
    +		equal(true, e2.isVisible(), "endpoint 2 is still visible.");
    +		equal(false, e3.isVisible(), "endpoint 3 is still not visible.");
    +	});
    +	
    +	/*
    +	test(renderMode + " _jsPlumb.hide, two-arg version, endpoints should also be hidden", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +		e = { isSource:true, isTarget:true, maxConnections:-1 },
    +		e1 = _jsPlumb.addEndpoint(d1, e),
    +		e2 = _jsPlumb.addEndpoint(d2, e),
    +		c1 = _jsPlumb.connect({source:e1, target:e2});
    +		
    +		equal(true, c1.isVisible(), "Connection 1 is visible after creation.");
    +		equal(true, e1.isVisible(), "endpoint 1 is visible after creation.");
    +		equal(true, e2.isVisible(), "endpoint 2 is visible after creation.");
    +		
    +		_jsPlumb.hide("d1", true);
    +		
    +		equal(false, c1.isVisible(), "Connection 1 is no longer visible.");
    +		equal(false, e1.isVisible(), "endpoint 1 is no longer visible.");
    +		equal(true, e2.isVisible(), "endpoint 2 is still visible.");
    +	});
    +	
    +	/**
    +	 * test for issue 132: label leaves its element in the DOM after it has been 
    +	 * removed from a connection. 
    +	 */
    +	test(renderMode + " label cleans itself up properly", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var c = _jsPlumb.connect({source:d1,target:d2, overlays:[
    +		    [ "Label", {id:"label", cssClass:"foo"}]                                                    		    
    +		]});
    +		ok($(".foo").length == 1, "label element exists in DOM");
    +		c.removeOverlay("label");
    +		ok($(".foo").length == 0, "label element does not exist in DOM");
    +	});
    +	
    +	test(renderMode + " arrow cleans itself up properly", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var c = _jsPlumb.connect({source:d1,target:d2, overlays:[
    +		    [ "Arrow", {id:"arrow"}]                                                    		    
    +		]});
    +		ok(c.getOverlay("arrow") != null, "arrow overlay exists");
    +		c.removeOverlay("arrow");
    +		ok(c.getOverlay("arrow") == null, "arrow overlay has been removed");
    +	});
    +	
    +	test(renderMode + " label overlay getElement function", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var c = _jsPlumb.connect({source:d1,target:d2, overlays:[
    +		    [ "Label", {id:"label"}]                                                    		    
    +		]});
    +		ok(c.getOverlay("label").getElement() != null, "label overlay exposes element via getElement method");
    +	});
    +	
    +	test(renderMode + " label overlay provides getLabel and setLabel methods", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var c = _jsPlumb.connect({source:d1,target:d2, overlays:[
    +		    [ "Label", {id:"label", label:"foo"}]                                                    		    
    +		]});
    +		var o = c.getOverlay("label"), e = o.getElement();
    +		ok(e.innerHTML == "foo", "label text is set to original value");
    +		o.setLabel("baz");		
    +		equal(e.innerHTML, "baz", "label text is set to new value 'baz'");
    +		equal(o.getLabel(), "baz", "getLabel function works correctly with String");
    +		// now try functions
    +		var aFunction = function() { return "aFunction"; };
    +		o.setLabel(aFunction);
    +		equal(e.innerHTML, "aFunction", "label text is set to new value from Function");
    +		equal(o.getLabel(), aFunction, "getLabel function works correctly with Function");
    +	});
    +	
    +	test(renderMode + " parameters object works for Endpoint", function() {
    +		var d1 = _addDiv("d1"),
    +		f = function() { alert("FOO!"); },
    +		e = _jsPlumb.addEndpoint(d1, {
    +			isSource:true,
    +			parameters:{
    +				"string":"param1",
    +				"int":4,
    +				"function":f
    +			}
    +		});
    +		ok(e.getParameter("string") === "param1", "getParameter(String) works correctly");
    +		ok(e.getParameter("int") === 4, "getParameter(int) works correctly");
    +		ok(e.getParameter("function") == f, "getParameter(Function) works correctly");
    +	});
    +	
    +	test(renderMode + " parameters object works for Connection", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +		f = function() { alert("FOO!"); };
    +		var c = _jsPlumb.connect({
    +			source:d1,
    +			target:d2,
    +			parameters:{
    +				"string":"param1",
    +				"int":4,
    +				"function":f
    +			}
    +		});
    +		ok(c.getParameter("string") === "param1", "getParameter(String) works correctly");
    +		ok(c.getParameter("int") === 4, "getParameter(int) works correctly");
    +		ok(c.getParameter("function") == f, "getParameter(Function) works correctly");
    +	});
    +	
    +	test(renderMode + " parameters set on Endpoints and Connections are all merged, and merged correctly at that.", function() {
    +		var d1 = _addDiv("d1"),
    +		d2 = _addDiv("d2"),
    +		e = _jsPlumb.addEndpoint(d1, {
    +			isSource:true,
    +			parameters:{
    +				"string":"sourceEndpoint",
    +				"int":0,
    +				"function":function() { return "sourceEndpoint"; }
    +			}
    +		}),
    +		e2 = _jsPlumb.addEndpoint(d2, {
    +			isTarget:true,
    +			parameters:{
    +				"int":1,
    +				"function":function() { return "targetEndpoint"; }
    +			}
    +		}),
    +		c = _jsPlumb.connect({source:e, target:e2, parameters:{
    +			"function":function() { return "connection"; }
    +		}});
    +		
    +		ok(c.getParameter("string") === "sourceEndpoint", "getParameter(String) works correctly");
    +		ok(c.getParameter("int") === 1, "getParameter(int) works correctly");
    +		ok(c.getParameter("function")() == "connection", "getParameter(Function) works correctly");		
    +	});
    +	
    +	// anchor manager tests.  a new and more comprehensive way of managing the paint, introduced in 1.3.5
    +	test(renderMode + " anchorManager registers standard connection", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2");
    +		var c = _jsPlumb.connect({source:d1, target:d2});
    +		equal(_jsPlumb.anchorManager.getConnectionsFor("d1").length, 1);
    +		equal(_jsPlumb.anchorManager.getEndpointsFor("d1").length, 1);		
    +		equal(_jsPlumb.anchorManager.getConnectionsFor("d2").length, 1);
    +		equal(_jsPlumb.anchorManager.getEndpointsFor("d2").length, 1);				
    +		var c2 = _jsPlumb.connect({source:d1, target:d2});
    +		equal(_jsPlumb.anchorManager.getConnectionsFor("d1").length, 2);
    +		equal(_jsPlumb.anchorManager.getConnectionsFor("d2").length, 2);
    +		equal(_jsPlumb.anchorManager.getEndpointsFor("d1").length, 2);
    +		equal(_jsPlumb.anchorManager.getEndpointsFor("d2").length, 2);				
    +	});
    +	
    +	// anchor manager tests.  a new and more comprehensive way of managing the paint, introduced in 1.3.5
    +	test(renderMode + " anchorManager registers dynamic anchor connection, and removes it.", function() {
    +		var d3 = _addDiv("d3"), d4 = _addDiv("d4");
    +		var c = _jsPlumb.connect({source:d3, target:d4, anchors:["AutoDefault", "AutoDefault"]});
    +
    +		equal(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 1);
    +		
    +		var c2 = _jsPlumb.connect({source:d3, target:d4});
    +		equal(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 2);
    +		equal(_jsPlumb.anchorManager.getConnectionsFor("d4").length, 2);		
    +
    +		equal(_jsPlumb.anchorManager.getEndpointsFor("d3").length, 2);			
    +		_jsPlumb.detach(c);
    +		equal(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 1);						
    +	});
    +	
    +	// anchor manager tests.  a new and more comprehensive way of managing the paint, introduced in 1.3.5
    +	test(renderMode + " anchorManager registers continuous anchor connection, and removes it.", function() {
    +		var d3 = _addDiv("d3"), d4 = _addDiv("d4");
    +		var c = _jsPlumb.connect({source:d3, target:d4, anchors:["Continuous", "Continuous"]});
    +
    +		equal(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 1);
    +		equal(_jsPlumb.anchorManager.getConnectionsFor("d4").length, 1);							
    +	
    +		_jsPlumb.detach(c);
    +		equal(_jsPlumb.anchorManager.getConnectionsFor("d3").length, 0);		
    +		equal(_jsPlumb.anchorManager.getConnectionsFor("d4").length, 0);	
    +		
    +		_jsPlumb.reset();
    +		equal(_jsPlumb.anchorManager.getEndpointsFor("d4").length, 0);	
    +	});
    +	
    +    test(renderMode + " setImage on Endpoint", function() {
    +        var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +        originalUrl = "../../img/endpointTest1.png",
    +        e = {
    +            endpoint:[ "Image", { src:originalUrl } ]
    +        },
    +        ep = _jsPlumb.addEndpoint(d1, e);        
    +        expect(0);
    +    });
    +    asyncTest(renderMode + "setImage on Endpoint, with supplied onload", function() {
    +    start();
    +        var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +        e = {
    +            endpoint:[ "Image", {
    +                src:"../../img/endpointTest1.png",
    +                onload:function(imgEp) {
    +                    equal("../../img/endpointTest1.png", imgEp.img.src);
    +                    equal(ep.endpoint.canvas.getAttribute("src"), imgEp.img.src);
    +                }
    +            } ]
    +        },
    +        ep = _jsPlumb.addEndpoint(d1, e);
    +        ep.setImage("../../img/littledot.png", function(imgEp) {
    +            equal("../../img/littledot.png", imgEp.img.src);
    +            equal(ep.endpoint.canvas.getAttribute("src"), imgEp.img.src);
    +        });
    +        expect(0);
    +    });
    +    
    +    test(renderMode + "attach endpoint to table when desired element was td", function() {
    +        var table = document.createElement("table"),
    +            tr = document.createElement("tr"),
    +            td = document.createElement("td");
    +        table.appendChild(tr); tr.appendChild(td);
    +        document.body.appendChild(table);
    +        var ep = _jsPlumb.addEndpoint(td);
    +        equal(ep.canvas.parentNode.tagName.toLowerCase(), "table");
    +    });
    +
    +// issue 190 - regressions with getInstance.  these tests ensure that generated ids are unique across
    +// instances.    
    +
    +    test(renderMode + " id clashes between instances", function() {
    +    	var d1 = document.createElement("div"),
    +    		d2 = document.createElement("div"),
    +    		_jsp2 = jsPlumb.getInstance();
    +    	
    +    	document.body.appendChild(d1);
    +    	document.body.appendChild(d2);
    +
    +    	_jsPlumb.addEndpoint(d1);
    +    	_jsp2.addEndpoint(d2);
    +
    +    	var id1 = d1.getAttribute("id"),
    +    		id2 = d2.getAttribute("id");
    +
    +    	var idx = id1.indexOf("_"), idx2 = id1.lastIndexOf("_"), v1 = id1.substring(idx, idx2);
    +    	var idx3 = id2.indexOf("_"), idx4 = id2.lastIndexOf("_"), v2 = id2.substring(idx3, idx4);
    +
    +    	ok (v1 != v2, "instance versions are different : " + v1 + " : " + v2);
    +    });
    +
    +    test(renderMode + " id clashes between instances", function() {
    +    	var d1 = document.createElement("div"),
    +    		d2 = document.createElement("div"),
    +    		_jsp2 = jsPlumb.getInstance();
    +    	
    +    	document.body.appendChild(d1);
    +    	document.body.appendChild(d2);
    +
    +    	_jsPlumb.addEndpoint(d1);
    +    	_jsPlumb.addEndpoint(d2);
    +
    +    	var id1 = d1.getAttribute("id"),
    +    		id2 = d2.getAttribute("id");
    +
    +    	var idx = id1.indexOf("_"), idx2 = id1.lastIndexOf("_"), v1 = id1.substring(idx, idx2);
    +    	var idx3 = id2.indexOf("_"), idx4 = id2.lastIndexOf("_"), v2 = id2.substring(idx3, idx4);
    +
    +    	ok (v1 == v2, "instance versions are the same : " + v1 + " : " + v2);
    +    });
    +
    +    test(renderMode + " importDefaults", function() {
    +    	_jsPlumb.Defaults.Anchors = ["LeftMiddle", "RightMiddle"];
    +    	var d1 = _addDiv("d1"), 
    +    		d2 = _addDiv(d2), 
    +    		c = _jsPlumb.connect({source:d1, target:d2}),
    +    		e = c.endpoints[0];
    +
    +    	equal(e.anchor.x, 0, "left middle anchor");
    +    	equal(e.anchor.y, 0.5, "left middle anchor");
    +
    +    	_jsPlumb.importDefaults({
    +    		Anchors:["TopLeft", "TopRight"]
    +    	});
    +
    +    	var conn = _jsPlumb.connect({source:d1, target:d2}),
    +    		e1 = conn.endpoints[0], e2 = conn.endpoints[1];
    +    	
    +    	equal(e1.anchor.x, 0, "top leftanchor");
    +    	equal(e2.anchor.y, 0, "top left anchor");
    +    	equal(e2.anchor.x, 1, "top right anchor");
    +    	equal(e2.anchor.y, 0, "top right anchor");
    +
    +    });
    +
    +    test(renderMode + " restoreDefaults", function() {
    +    	_jsPlumb.importDefaults({
    +    		Anchors:["TopLeft", "TopRight"]
    +    	});
    +
    +    	var d1 = _addDiv("d1"), d2 = _addDiv("d2"), conn = _jsPlumb.connect({source:d1, target:d2}),
    +    		e1 = conn.endpoints[0], e2 = conn.endpoints[1];
    +    	
    +    	equal(e1.anchor.x, 0, "top leftanchor");
    +    	equal(e2.anchor.y, 0, "top left anchor");
    +    	equal(e2.anchor.x, 1, "top right anchor");
    +    	equal(e2.anchor.y, 0, "top right anchor");
    +
    +    	_jsPlumb.restoreDefaults();
    +    	
    +    	var conn2 = _jsPlumb.connect({source:d1, target:d2}),
    +    		e3 = conn2.endpoints[0], e4 = conn2.endpoints[1];
    +    	
    +    	equal(e3.anchor.x, 0.5, "bottom center anchor");
    +    	equal(e3.anchor.y, 1, "bottom center anchor");
    +    	equal(e4.anchor.x, 0.5, "bottom center anchor");
    +    	equal(e4.anchor.y, 1, "bottom center anchor");    	
    +    });
    +
    +// setId function
    +
    +	test(renderMode + " setId, taking two strings, only default scope", function() {
    +		_addDiv("d1"); _addDiv("d2");
    +
    +		_jsPlumb.Defaults.MaxConnections = -1;
    +		var e1 = _jsPlumb.addEndpoint("d1"),
    +			e2 = _jsPlumb.addEndpoint("d2"),
    +			e3 = _jsPlumb.addEndpoint("d1");
    +
    +		assertEndpointCount("d1", 2, _jsPlumb);
    +		equal(e1.elementId, "d1", "endpoint has correct element id");
    +		equal(e3.elementId, "d1", "endpoint has correct element id");
    +		equal(e1.anchor.elementId, "d1", "anchor has correct element id");
    +		equal(e3.anchor.elementId, "d1", "anchor has correct element id");
    +
    +		var c = _jsPlumb.connect({source:e1, target:e2}),
    +			c2 = _jsPlumb.connect({source:e2, target:e1});
    +
    +		_jsPlumb.setId("d1", "d3");
    +		assertEndpointCount("d3", 2, _jsPlumb);		
    +		assertEndpointCount("d1", 0, _jsPlumb);
    +
    +		equal(e1.elementId, "d3", "endpoint has correct element id");
    +		equal(e3.elementId, "d3", "endpoint has correct element id");
    +		equal(e1.anchor.elementId, "d3", "anchor has correct element id");
    +		equal(e3.anchor.elementId, "d3", "anchor has correct element id");
    +
    +		equal(c.sourceId, "d3", "connection's sourceId has changed");
    +		equal(c.source.attr("id"), "d3", "connection's source has changed");
    +		equal(c2.targetId, "d3", "connection's targetId has changed");
    +		equal(c2.target.attr("id"), "d3", "connection's target has changed");
    +	});   
    +	
    +	test(renderMode + " setId, taking a selector and a string, only default scope", function() {
    +		_addDiv("d1"); _addDiv("d2");
    +
    +		_jsPlumb.Defaults.MaxConnections = -1;
    +		var e1 = _jsPlumb.addEndpoint("d1"),
    +			e2 = _jsPlumb.addEndpoint("d2"),
    +			e3 = _jsPlumb.addEndpoint("d1");
    +		
    +		assertEndpointCount("d1", 2, _jsPlumb);
    +		equal(e1.elementId, "d1", "endpoint has correct element id");
    +		equal(e3.elementId, "d1", "endpoint has correct element id");
    +		equal(e1.anchor.elementId, "d1", "anchor has correct element id");
    +		equal(e3.anchor.elementId, "d1", "anchor has correct element id");
    +
    +		var c = _jsPlumb.connect({source:e1, target:e2}),
    +			c2 = _jsPlumb.connect({source:e2, target:e1});
    +
    +		_jsPlumb.setId($("#d1"), "d3");
    +		assertEndpointCount("d3", 2, _jsPlumb);
    +		assertEndpointCount("d1", 0, _jsPlumb);
    +
    +		equal(e1.elementId, "d3", "endpoint has correct element id");
    +		equal(e3.elementId, "d3", "endpoint has correct element id");
    +		equal(e1.anchor.elementId, "d3", "anchor has correct element id");
    +		equal(e3.anchor.elementId, "d3", "anchor has correct element id");
    +
    +		equal(c.sourceId, "d3", "connection's sourceId has changed");
    +		equal(c.source.attr("id"), "d3", "connection's source has changed");
    +		equal(c2.targetId, "d3", "connection's targetId has changed");
    +		equal(c2.target.attr("id"), "d3", "connection's target has changed");
    +	});   
    +
    +	test(renderMode + " setId, taking a DOM element and a string, only default scope", function() {
    +		_addDiv("d1"); _addDiv("d2");
    +
    +		_jsPlumb.Defaults.MaxConnections = -1;
    +		var e1 = _jsPlumb.addEndpoint("d1"),
    +			e2 = _jsPlumb.addEndpoint("d2"),
    +			e3 = _jsPlumb.addEndpoint("d1");
    +
    +		assertEndpointCount("d1", 2, _jsPlumb);
    +		equal(e1.elementId, "d1", "endpoint has correct element id");
    +		equal(e3.elementId, "d1", "endpoint has correct element id");
    +		equal(e1.anchor.elementId, "d1", "anchor has correct element id");
    +		equal(e3.anchor.elementId, "d1", "anchor has correct element id");
    +
    +		var c = _jsPlumb.connect({source:e1, target:e2}),
    +			c2 = _jsPlumb.connect({source:e2, target:e1});
    +
    +		_jsPlumb.setId($("#d1")[0], "d3");
    +		assertEndpointCount("d3", 2, _jsPlumb);
    +		assertEndpointCount("d1", 0, _jsPlumb);
    +
    +		equal(e1.elementId, "d3", "endpoint has correct element id");
    +		equal(e3.elementId, "d3", "endpoint has correct element id");
    +		equal(e1.anchor.elementId, "d3", "anchor has correct element id");
    +		equal(e3.anchor.elementId, "d3", "anchor has correct element id");
    +
    +		equal(c.sourceId, "d3", "connection's sourceId has changed");
    +		equal(c.source.attr("id"), "d3", "connection's source has changed");
    +		equal(c2.targetId, "d3", "connection's targetId has changed");
    +		equal(c2.target.attr("id"), "d3", "connection's target has changed");
    +	}); 
    +
    +	test(renderMode + " setId, taking two strings, mix of scopes", function() {
    +		_addDiv("d1"); _addDiv("d2");
    +
    +		_jsPlumb.Defaults.MaxConnections = -1;
    +		var e1 = _jsPlumb.addEndpoint("d1"),
    +			e2 = _jsPlumb.addEndpoint("d2"),
    +			e3 = _jsPlumb.addEndpoint("d1");
    +
    +		assertEndpointCount("d1", 2, _jsPlumb);
    +		equal(e1.elementId, "d1", "endpoint has correct element id");
    +		equal(e3.elementId, "d1", "endpoint has correct element id");
    +		equal(e1.anchor.elementId, "d1", "anchor has correct element id");
    +		equal(e3.anchor.elementId, "d1", "anchor has correct element id");
    +
    +		var c = _jsPlumb.connect({source:e1, target:e2, scope:"FOO"}),
    +			c2 = _jsPlumb.connect({source:e2, target:e1});
    +
    +		_jsPlumb.setId("d1", "d3");
    +		assertEndpointCount("d3", 2, _jsPlumb);
    +		assertEndpointCount("d1", 0, _jsPlumb);
    +
    +		equal(e1.elementId, "d3", "endpoint has correct element id");
    +		equal(e3.elementId, "d3", "endpoint has correct element id");
    +		equal(e1.anchor.elementId, "d3", "anchor has correct element id");
    +		equal(e3.anchor.elementId, "d3", "anchor has correct element id");
    +
    +		equal(c.sourceId, "d3", "connection's sourceId has changed");
    +		equal(c.source.attr("id"), "d3", "connection's source has changed");
    +		equal(c2.targetId, "d3", "connection's targetId has changed");
    +		equal(c2.target.attr("id"), "d3", "connection's target has changed");
    +	});   
    +	
    +	test(renderMode + " setId, taking a selector and a string, mix of scopes", function() {
    +		_addDiv("d1"); _addDiv("d2");
    +
    +		_jsPlumb.Defaults.MaxConnections = -1;
    +		var e1 = _jsPlumb.addEndpoint("d1"),
    +			e2 = _jsPlumb.addEndpoint("d2"),
    +			e3 = _jsPlumb.addEndpoint("d1");
    +		
    +		assertEndpointCount("d1", 2, _jsPlumb);
    +		equal(e1.elementId, "d1", "endpoint has correct element id");
    +		equal(e3.elementId, "d1", "endpoint has correct element id");
    +		equal(e1.anchor.elementId, "d1", "anchor has correct element id");
    +		equal(e3.anchor.elementId, "d1", "anchor has correct element id");
    +
    +		var c = _jsPlumb.connect({source:e1, target:e2, scope:"FOO"}),
    +			c2 = _jsPlumb.connect({source:e2, target:e1});
    +
    +		_jsPlumb.setId($("#d1"), "d3");
    +		assertEndpointCount("d3", 2, _jsPlumb);
    +		assertEndpointCount("d1", 0, _jsPlumb);
    +
    +		equal(e1.elementId, "d3", "endpoint has correct element id");
    +		equal(e3.elementId, "d3", "endpoint has correct element id");
    +		equal(e1.anchor.elementId, "d3", "anchor has correct element id");
    +		equal(e3.anchor.elementId, "d3", "anchor has correct element id");
    +
    +		equal(c.sourceId, "d3", "connection's sourceId has changed");
    +		equal(c.source.attr("id"), "d3", "connection's source has changed");
    +		equal(c2.targetId, "d3", "connection's targetId has changed");
    +		equal(c2.target.attr("id"), "d3", "connection's target has changed");
    +	});   
    +
    +	test(renderMode + " setId, taking a DOM element and a string, mix of scopes", function() {
    +		_addDiv("d1"); _addDiv("d2");
    +
    +		_jsPlumb.Defaults.MaxConnections = -1;
    +		var e1 = _jsPlumb.addEndpoint("d1"),
    +			e2 = _jsPlumb.addEndpoint("d2"),
    +			e3 = _jsPlumb.addEndpoint("d1");
    +
    +		assertEndpointCount("d1", 2, _jsPlumb);
    +		equal(e1.elementId, "d1", "endpoint has correct element id");
    +		equal(e3.elementId, "d1", "endpoint has correct element id");
    +		equal(e1.anchor.elementId, "d1", "anchor has correct element id");
    +		equal(e3.anchor.elementId, "d1", "anchor has correct element id");
    +
    +		var c = _jsPlumb.connect({source:e1, target:e2, scope:"FOO"}),
    +			c2 = _jsPlumb.connect({source:e2, target:e1});
    +
    +		_jsPlumb.setId($("#d1")[0], "d3");
    +		assertEndpointCount("d3", 2, _jsPlumb);
    +		assertEndpointCount("d1", 0, _jsPlumb);
    +
    +		equal(e1.elementId, "d3", "endpoint has correct element id");
    +		equal(e3.elementId, "d3", "endpoint has correct element id");
    +		equal(e1.anchor.elementId, "d3", "anchor has correct element id");
    +		equal(e3.anchor.elementId, "d3", "anchor has correct element id");
    +
    +		equal(c.sourceId, "d3", "connection's sourceId has changed");
    +		equal(c.source.attr("id"), "d3", "connection's source has changed");
    +		equal(c2.targetId, "d3", "connection's targetId has changed");
    +		equal(c2.target.attr("id"), "d3", "connection's target has changed");
    +	});
    +
    +	test(renderMode + " setIdChanged, ", function() {
    +		_addDiv("d1"); _addDiv("d2");
    +
    +		_jsPlumb.Defaults.MaxConnections = -1;
    +		var e1 = _jsPlumb.addEndpoint("d1"),
    +			e2 = _jsPlumb.addEndpoint("d2"),
    +			e3 = _jsPlumb.addEndpoint("d1");
    +
    +		assertEndpointCount("d1", 2, _jsPlumb);
    +		equal(e1.elementId, "d1", "endpoint has correct element id");
    +		equal(e3.elementId, "d1", "endpoint has correct element id");
    +		equal(e1.anchor.elementId, "d1", "anchor has correct element id");
    +		equal(e3.anchor.elementId, "d1", "anchor has correct element id");
    +
    +		var c = _jsPlumb.connect({source:e1, target:e2}),
    +			c2 = _jsPlumb.connect({source:e2, target:e1});
    +
    +		$("#d1").attr("id", "d3");
    +
    +		_jsPlumb.setIdChanged("d1", "d3");
    +		
    +		assertEndpointCount("d3", 2, _jsPlumb);		
    +		assertEndpointCount("d1", 0, _jsPlumb);
    +
    +		equal(e1.elementId, "d3", "endpoint has correct element id");
    +		equal(e3.elementId, "d3", "endpoint has correct element id");
    +		equal(e1.anchor.elementId, "d3", "anchor has correct element id");
    +		equal(e3.anchor.elementId, "d3", "anchor has correct element id");
    +
    +		equal(c.sourceId, "d3", "connection's sourceId has changed");
    +		equal(c.source.attr("id"), "d3", "connection's source has changed");
    +		equal(c2.targetId, "d3", "connection's targetId has changed");
    +		equal(c2.target.attr("id"), "d3", "connection's target has changed");
    +	});  
    +
    +	test(renderMode + " endpoint hide/show should hide/show overlays", function() {
    +		_addDiv("d1");
    +		var e1 = _jsPlumb.addEndpoint("d1", {
    +			overlays:[
    +				[ "Label", { id:"label", label:"foo" } ]
    +			]
    +		}),
    +		o = e1.getOverlay("label");
    +
    +		ok(o.isVisible(), "overlay is initially visible");
    +		_jsPlumb.hide("d1", true);
    +		ok(!o.isVisible(), "overlay is no longer visible");
    +	});
    +    
    +    test(renderMode + " connection hide/show should hide/show overlays", function() {
    +		_addDiv("d1");_addDiv("d2");
    +		var c = _jsPlumb.connect({source:"d1", target:"d2", 
    +			overlays:[
    +				[ "Label", { id:"label", label:"foo" } ]
    +			]
    +		}),
    +		o = c.getOverlay("label");
    +
    +		ok(o.isVisible(), "overlay is initially visible");
    +		_jsPlumb.hide("d1", true);
    +		ok(!o.isVisible(), "overlay is no longer visible");
    +	});
    +
    +	test(renderMode + " select, basic test", function() {
    +		_addDiv("d1"); _addDiv("d2");
    +		var c = _jsPlumb.connect({source:"d1", target:"d2"}),
    +			s = _jsPlumb.select({source:"d1"});
    +
    +		equal(s.length, 1, "one connection selected");
    +		equal(s.get(0).sourceId, "d1", "d1 is connection source");
    +
    +		s.setHover(true);
    +		ok(s.get(0).isHover(), "connection has had hover set to true");
    +		s.setHover(false);
    +		ok(!(s.get(0).isHover()), "connection has had hover set to false");
    +	});
    +
    +	test(renderMode + " select, basic test with multiple scopes; dont filter on scope.", function() {
    +		_addDiv("d1"); _addDiv("d2");
    +		var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}),
    +			c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}),
    +			s = _jsPlumb.select({source:"d1"});
    +
    +		equal(s.length, 2, "two connections selected");
    +		equal(s.get(0).sourceId, "d1", "d1 is connection source");
    +
    +		s.setHover(true);
    +		ok(s.get(0).isHover(), "connection has had hover set to true");
    +		s.setHover(false);
    +		ok(!(s.get(0).isHover()), "connection has had hover set to false");
    +	});
    +
    +	test(renderMode + " select, basic test with multiple scopes; filter on scope", function() {
    +		_addDiv("d1"); _addDiv("d2");
    +		var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}),
    +			c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}),
    +			s = _jsPlumb.select({source:"d1", scope:"FOO"});
    +
    +		equal(s.length, 1, "one connection selected");
    +		equal(s.get(0).sourceId, "d1", "d1 is connection source");
    +
    +		s.setHover(true);
    +		ok(s.get(0).isHover(), "connection has had hover set to true");
    +		s.setHover(false);
    +		ok(!(s.get(0).isHover()), "connection has had hover set to false");
    +	});
    +
    +	test(renderMode + " select, basic test with multiple scopes; filter on scopes", function() {
    +		_addDiv("d1"); _addDiv("d2");
    +		var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}),
    +			c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}),
    +			c3 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAZ"})
    +			s = _jsPlumb.select({source:"d1", scope:["FOO","BAR"]});
    +
    +		equal(s.length, 2, "two connections selected");
    +		equal(s.get(0).sourceId, "d1", "d1 is connection source");
    +
    +		s.setHover(true);
    +		ok(s.get(0).isHover(), "connection has had hover set to true");
    +		s.setHover(false);
    +		ok(!(s.get(0).isHover()), "connection has had hover set to false");
    +	});
    +
    +	test(renderMode + " select, basic test with multiple scopes; scope but no scope filter; single source id", function() {
    +		_addDiv("d1"); _addDiv("d2");
    +		var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}),
    +			c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}),
    +			c3 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAZ"}),
    +			c4 = _jsPlumb.connect({source:"d2", target:"d1", scope:"BOZ"}),
    +			s = _jsPlumb.select({source:"d1"});
    +
    +		equal(s.length, 3, "three connections selected");
    +		equal(s.get(0).sourceId, "d1", "d1 is connection source");
    +
    +		s.setHover(true);
    +		ok(s.get(0).isHover(), "connection has had hover set to true");
    +		s.setHover(false);
    +		ok(!(s.get(0).isHover()), "connection has had hover set to false");
    +	});
    +
    +	test(renderMode + " select, basic test with multiple scopes; filter on scopes; single source id", function() {
    +		_addDiv("d1"); _addDiv("d2");
    +		var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}),
    +			c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}),
    +			c3 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAZ"}),
    +			c4 = _jsPlumb.connect({source:"d2", target:"d1", scope:"BOZ"}),
    +			s = _jsPlumb.select({source:"d1", scope:["FOO","BAR", "BOZ"]});
    +
    +		equal(s.length, 2, "two connections selected");
    +		equal(s.get(0).sourceId, "d1", "d1 is connection source");
    +
    +		s.setHover(true);
    +		ok(s.get(0).isHover(), "connection has had hover set to true");
    +		s.setHover(false);
    +		ok(!(s.get(0).isHover()), "connection has had hover set to false");
    +	});
    +
    +	test(renderMode + " select, basic test with multiple scopes; filter on scope; dont supply sourceid", function() {
    +		_addDiv("d1"); _addDiv("d2");
    +		var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}),
    +			c2 = _jsPlumb.connect({source:"d1", target:"d2", scope:"BAR"}),
    +			s = _jsPlumb.select({ scope:"FOO" });
    +
    +		equal(s.length, 1, "two connections selected");
    +		equal(s.get(0).sourceId, "d1", "d1 is connection source");
    +
    +		s.setHover(true);
    +		ok(s.get(0).isHover(), "connection has had hover set to true");
    +		s.setHover(false);
    +		ok(!(s.get(0).isHover()), "connection has had hover set to false");
    +	});
    +
    +	test(renderMode + " select, basic test with multiple scopes; filter on scope; dont supply sourceid", function() {
    +		_addDiv("d1"); _addDiv("d2");
    +		var c = _jsPlumb.connect({source:"d1", target:"d2", scope:"FOO"}),
    +			c2 = _jsPlumb.connect({source:"d2", target:"d1", scope:"BAR"}),
    +			s = _jsPlumb.select({ scope:"FOO" });
    +
    +		equal(s.length, 1, "two connections selected");
    +		equal(s.get(0).sourceId, "d1", "d1 is connection source");
    +
    +		s.setHover(true);
    +		ok(s.get(0).isHover(), "connection has had hover set to true");
    +		s.setHover(false);
    +		ok(!(s.get(0).isHover()), "connection has had hover set to false");
    +	});
    +
    +	test(renderMode + " select, two connections, with overlays", function() {
    +		_addDiv("d1"); _addDiv("d2");
    +		var c = _jsPlumb.connect({
    +				source:"d1", 
    +				target:"d2",
    +				overlays:[
    +					["Label", {id:"l"}]
    +				]
    +			}),
    +			c2 = _jsPlumb.connect({
    +				source:"d1", 
    +				target:"d2",
    +				overlays:[
    +					["Label", {id:"l"}]
    +				]
    +			}),
    +			s = _jsPlumb.select({source:"d1"});
    +		
    +		equal(s.length, 2, "two connections selected");
    +		ok(s.get(0).getOverlay("l") != null, "connection has overlay");
    +		ok(s.get(1).getOverlay("l") != null, "connection has overlay");
    +	});
    +
    +	test(renderMode + " select, chaining with setHover and hideOverlay", function() {
    +		_addDiv("d1"); _addDiv("d2");
    +		var c = _jsPlumb.connect({
    +				source:"d1", 
    +				target:"d2",
    +				overlays:[
    +					["Label", {id:"l"}]
    +				]
    +			});
    +			s = _jsPlumb.select({source:"d1"});
    +		
    +		s.setHover(false).hideOverlay("l");
    +
    +		ok(!(s.get(0).isHover()), "connection is not hover");
    +		ok(!(s.get(0).getOverlay("l").isVisible()), "overlay is not visible");
    +	});
    +
    +	test(renderMode + " select, .each function", function() {
    +		for (var i = 1; i < 6; i++) {
    +			_addDiv("d" + i); 	_addDiv("d" + (i * 10));
    +			_jsPlumb.connect({
    +				source:"d" + i, 
    +				target:"d" + (i*10)
    +			});
    +		}
    +				
    +		var s = _jsPlumb.select();
    +		equal(s.length, 5, "there are five connections");
    +
    +		var t = "";
    +		s.each(function(connection) {
    +			t += "f";
    +		});
    +		equal("fffff", t, ".each is working");
    +	});
    +
    +	test(renderMode + " select, multiple connections + chaining", function() {
    +		for (var i = 1; i < 6; i++) {
    +			_addDiv("d" + i); 	_addDiv("d" + (i * 10));
    +			_jsPlumb.connect({
    +				source:"d" + i, 
    +				target:"d" + (i*10),
    +				overlays:[
    +					["Arrow", {location:0.3}],
    +					["Arrow", {location:0.7}]
    +				]
    +			});
    +		}
    +				
    +		var s = _jsPlumb.select().removeAllOverlays().setParameter("foo", "bar").setHover(false).setLabel("baz");
    +		equal(s.length, 5, "there are five connections");
    +
    +		for (var j = 0; j < 5; j++) {
    +			equal(s.get(j).getOverlays().length, 1, "one overlay: the label");
    +			equal(s.get(j).getParameter("foo"), "bar", "parameter foo has value 'bar'");
    +			ok(!(s.get(j).isHover()), "hover is set to false");
    +			equal(s.get(j).getLabel(), "baz", "label is set to 'baz'");
    +		}			
    +	});
    +
    +	test(renderMode + " select, simple getter", function() {
    +		for (var i = 1; i < 6; i++) {
    +			_addDiv("d" + i); 	_addDiv("d" + (i * 10));
    +			_jsPlumb.connect({
    +				source:"d" + i, 
    +				target:"d" + (i*10),
    +				label:"FOO"
    +			});
    +		}
    +				
    +		var lbls = _jsPlumb.select().getLabel();
    +		equal(lbls.length, 5, "there are five labels");
    +
    +		for (var j = 0; j < 5; j++) {
    +			equal(lbls[j][0], "FOO", "label has value 'FOO'");
    +		}			
    +	});
    +
    +	test(renderMode + " select, getter + chaining", function() {
    +		for (var i = 1; i < 6; i++) {
    +			_addDiv("d" + i); 	_addDiv("d" + (i * 10));
    +			_jsPlumb.connect({
    +				source:"d" + i, 
    +				target:"d" + (i*10),
    +				label:"FOO"
    +			});
    +		}
    +				
    +		var params = _jsPlumb.select().removeAllOverlays().setParameter("foo", "bar").getParameter("foo");
    +		equal(params.length, 5, "there are five params");
    +
    +		for (var j = 0; j < 5; j++) {
    +			equal(params[j][0], "bar", "parameter has value 'bar'");
    +		}			
    +	});
    +
    +
    +	test(renderMode + " select, detach method", function() {
    +		for (var i = 1; i < 6; i++) {
    +			_addDiv("d" + i); 	_addDiv("d" + (i * 10));
    +			_jsPlumb.connect({
    +				source:"d" + i, 
    +				target:"d" + (i*10),
    +				label:"FOO"
    +			});
    +		}
    +				
    +		var params = _jsPlumb.select().detach();
    +
    +		equal(_jsPlumb.select().length, 0, "there are no connections");
    +	});
    +	
    +	test(renderMode + " select, repaint method", function() {
    +		for (var i = 1; i < 6; i++) {
    +			_addDiv("d" + i); 	_addDiv("d" + (i * 10));
    +			_jsPlumb.connect({
    +				source:"d" + i, 
    +				target:"d" + (i*10),
    +				label:"FOO"
    +			});
    +		}
    +				
    +		var len = _jsPlumb.select().repaint().length;
    +
    +		equal(len, 5, "there are five connections");
    +	});	
    +	
    +	
    +	// selectEndpoints
    +	test(renderMode + " selectEndpoints, basic tests", function() {
    +		var d1 = _addDiv("d1"), _d2 = _addDiv("d2"),
    +			e1 = _jsPlumb.addEndpoint(d1),
    +			e2 = _jsPlumb.addEndpoint(d1);
    +			
    +		equal(_jsPlumb.selectEndpoints().length, 2, "there are two endpoints");
    +		equal(_jsPlumb.selectEndpoints({element:"d1"}).length, 2, "there are two endpoints on d1");
    +		equal(_jsPlumb.selectEndpoints({element:"d2"}).length, 0, "there are 0 endpoints on d2");
    +		
    +		equal(_jsPlumb.selectEndpoints({source:"d1"}).length, 0, "there are zero source endpoints on d1");
    +		equal(_jsPlumb.selectEndpoints({target:"d1"}).length, 0, "there are zero target endpoints on d1");		
    +		equal(_jsPlumb.selectEndpoints({source:"d2"}).length, 0, "there are zero source endpoints on d2");
    +		equal(_jsPlumb.selectEndpoints({target:"d2"}).length, 0, "there are zero target endpoints on d2");		
    +		
    +		equal(_jsPlumb.selectEndpoints({source:"d1", scope:"FOO"}).length, 0, "there are zero source endpoints on d1 with scope FOO");
    +		
    +		_jsPlumb.addEndpoint("d2", { scope:"FOO", isSource:true });
    +		equal(_jsPlumb.selectEndpoints({source:"d2", scope:"FOO"}).length, 1, "there is one source endpoint on d2 with scope FOO");
    +			
    +		equal(_jsPlumb.selectEndpoints({element:["d2", "d1"]}).length, 3, "there are three endpoints between d2 and d1");				
    +	});
    +	
    +	test(renderMode + " selectEndpoints, basic tests, various input argument formats", function() {
    +		var d1 = _addDiv("d1"), _d2 = _addDiv("d2"),
    +			e1 = _jsPlumb.addEndpoint(d1),
    +			e2 = _jsPlumb.addEndpoint(d1);
    +			
    +		equal(_jsPlumb.selectEndpoints({element:"d1"}).length, 2, "using id, there are two endpoints on d1");
    +		equal(_jsPlumb.selectEndpoints({element:d1}).length, 2, "using dom element, there are two endpoints on d1");
    +		equal(_jsPlumb.selectEndpoints({element:$("#d1")}).length, 2, "using selector, there are two endpoints on d1");
    +		equal(_jsPlumb.selectEndpoints({element:$(d1)}).length, 2, "using selector with dom element, there are two endpoints on d1");		
    +		
    +	});
    +	
    +	test(renderMode + " selectEndpoints, basic tests, scope", function() {
    +		var d1 = _addDiv("d1"), _d2 = _addDiv("d2"),
    +			e1 = _jsPlumb.addEndpoint(d1, {scope:"FOO"}),
    +			e2 = _jsPlumb.addEndpoint(d1);
    +			
    +		equal(_jsPlumb.selectEndpoints({element:"d1"}).length, 2, "using id, there are two endpoints on d1");
    +		equal(_jsPlumb.selectEndpoints({element:"d1", scope:"FOO"}).length, 1, "using id, there is one endpoint on d1 with scope 'FOO'");		
    +		_jsPlumb.addEndpoint(d1, {scope:"BAR"}),		
    +		equal(_jsPlumb.selectEndpoints({element:"d1", scope:"FOO"}).length, 1, "using id, there is one endpoint on d1 with scope 'BAR'");	
    +		equal(_jsPlumb.selectEndpoints({element:"d1", scope:["BAR", "FOO"]}).length, 2, "using id, there are two endpoints on d1 with scope 'BAR' or 'FOO'");					
    +	});
    +	
    +	test(renderMode + " selectEndpoints, isSource tests", function() {
    +		var d1 = _addDiv("d1"), _d2 = _addDiv("d2"),
    +			e1 = _jsPlumb.addEndpoint(d1, {isSource:true}),
    +			e2 = _jsPlumb.addEndpoint(d1),
    +			e3 = _jsPlumb.addEndpoint(d2, {isSource:true});
    +			
    +		equal(_jsPlumb.selectEndpoints({source:"d1"}).length, 1, "there is one source endpoint on d1");
    +		equal(_jsPlumb.selectEndpoints({target:"d1"}).length, 0, "there are zero target endpoints on d1");		
    +		
    +		equal(_jsPlumb.selectEndpoints({source:"d2"}).length, 1, "there is one source endpoint on d2");
    +		
    +		equal(_jsPlumb.selectEndpoints({source:["d2", "d1"]}).length, 2, "there are two source endpoints between d1 and d2");			
    +	});
    +	
    +	test(renderMode + " selectEndpoints, isTarget tests", function() {
    +		var d1 = _addDiv("d1"), _d2 = _addDiv("d2"),
    +			e1 = _jsPlumb.addEndpoint(d1, {isTarget:true}),
    +			e2 = _jsPlumb.addEndpoint(d1),
    +			e3 = _jsPlumb.addEndpoint(d2, {isTarget:true});
    +			
    +		equal(_jsPlumb.selectEndpoints({target:"d1"}).length, 1, "there is one target endpoint on d1");
    +		equal(_jsPlumb.selectEndpoints({source:"d1"}).length, 0, "there are zero source endpoints on d1");		
    +		
    +		equal(_jsPlumb.selectEndpoints({target:"d2"}).length, 1, "there is one target endpoint on d2");
    +		
    +		equal(_jsPlumb.selectEndpoints({target:["d2", "d1"]}).length, 2, "there are two target endpoints between d1 and d2");			
    +	});
    +	
    +	test(renderMode + " selectEndpoints, isSource + isTarget tests", function() {
    +		var d1 = _addDiv("d1"), _d2 = _addDiv("d2"),
    +			e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true}),
    +			e2 = _jsPlumb.addEndpoint(d1),
    +			e3 = _jsPlumb.addEndpoint(d1, {isSource:true}),			
    +			e4 = _jsPlumb.addEndpoint(d1, {isTarget:true});
    +			
    +		equal(_jsPlumb.selectEndpoints({source:"d1"}).length, 2, "there are two source endpoints on d1");
    +		equal(_jsPlumb.selectEndpoints({target:"d1"}).length, 2, "there are two target endpoints on d1");		
    +		
    +		equal(_jsPlumb.selectEndpoints({target:"d1", source:"d1"}).length, 1, "there is one source and target endpoint on d1");	
    +		
    +		equal(_jsPlumb.selectEndpoints({element:"d1"}).length, 4, "there are four endpoints on d1");	
    +		
    +	});
    +	
    +	test(renderMode + " selectEndpoints, delete endpoints", function() {
    +		var d1 = _addDiv("d1"), _d2 = _addDiv("d2"),
    +			e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true});
    +		
    +		equal(_jsPlumb.selectEndpoints({element:"d1"}).length, 1, "there is one endpoint on d1");	
    +		_jsPlumb.selectEndpoints({source:"d1"}).delete();
    +		equal(_jsPlumb.selectEndpoints({element:"d1"}).length, 0, "there are zero endpoints on d1");						
    +	});
    +	
    +	test(renderMode + " selectEndpoints, detach connections", function() {
    +		var d1 = _addDiv("d1"), _d2 = _addDiv("d2"),
    +			e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true}),
    +			e2 = _jsPlumb.addEndpoint(d2, {isSource:true, isTarget:true});			
    +			
    +		_jsPlumb.connect({source:e1, target:e2});
    +		
    +		equal(e1.connections.length, 1, "there is one connection on d1's endpoint");	
    +		equal(e2.connections.length, 1, "there is one connection on d2's endpoint");			
    +		
    +		_jsPlumb.selectEndpoints({source:"d1"}).detachAll();
    +		
    +		equal(e1.connections.length, 0, "there are zero connections on d1's endpoint");	
    +		equal(e2.connections.length, 0, "there are zero connections on d2's endpoint");			
    +	});
    +	
    +	test(renderMode + " selectEndpoints, hover tests", function() {
    +		var d1 = _addDiv("d1"), _d2 = _addDiv("d2"),
    +			e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true});
    +			
    +		equal(e1.isHover(), false, "hover not set");		
    +		_jsPlumb.selectEndpoints({source:"d1"}).setHover(true);
    +		equal(e1.isHover(), true, "hover set");		
    +		_jsPlumb.selectEndpoints({source:"d1"}).setHover(false);
    +		equal(e1.isHover(), false, "hover no longer set");		
    +	});
    +	
    +	test(renderMode + " selectEndpoints, setEnabled tests", function() {
    +		var d1 = _addDiv("d1"), _d2 = _addDiv("d2"),
    +			e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true});
    +			
    +		equal(e1.isEnabled(), true, "endpoint is enabled");		
    +		_jsPlumb.selectEndpoints({source:"d1"}).setEnabled(false);
    +		equal(e1.isEnabled(), false, "endpoint not enabled");				
    +	});
    +	
    +	test(renderMode + " selectEndpoints, setEnabled tests", function() {
    +		var d1 = _addDiv("d1"), _d2 = _addDiv("d2"),
    +			e1 = _jsPlumb.addEndpoint(d1, {isSource:true, isTarget:true});
    +			
    +		equal(e1.isEnabled(), true, "endpoint is enabled");		
    +		var e = _jsPlumb.selectEndpoints({source:"d1"}).isEnabled();
    +		equal(e[0][0], true, "endpoint enabled");
    +		_jsPlumb.selectEndpoints({source:"d1"}).setEnabled(false);
    +		e = _jsPlumb.selectEndpoints({source:"d1"}).isEnabled();
    +		equal(e[0][0], false, "endpoint not enabled");
    +	});
    +
    +// setPaintStyle/getPaintStyle tests
    +
    +	test(renderMode + " setPaintStyle", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"), c = _jsPlumb.connect({source:d1, target:d2});
    +		c.setPaintStyle({strokeStyle:"FOO", lineWidth:999});
    +		equal(c.paintStyleInUse.strokeStyle, "FOO", "strokeStyle was set");
    +		equal(c.paintStyleInUse.lineWidth, 999, "lineWidth was set");
    +
    +		c.setHoverPaintStyle({strokeStyle:"BAZ",  lineWidth:444});
    +		c.setHover(true);
    +		equal(c.paintStyleInUse.strokeStyle, "BAZ", "strokeStyle was set");
    +		equal(c.paintStyleInUse.lineWidth, 444, "lineWidth was set");
    +
    +		equal(c.getPaintStyle().strokeStyle, "FOO", "getPaintStyle returns correct value");
    +		equal(c.getHoverPaintStyle().strokeStyle, "BAZ", "getHoverPaintStyle returns correct value");
    +	});	
    +
    +// ******************* getEndpoints ************************************************
    +
    +	test(renderMode + " getEndpoints", function() {
    +		_addDiv("d1");_addDiv("d2");
    +		
    +		_jsPlumb.addEndpoint("d1");
    +		_jsPlumb.addEndpoint("d2");
    +		_jsPlumb.addEndpoint("d1");
    +
    +		var e = _jsPlumb.getEndpoints("d1"),
    +			e2 = _jsPlumb.getEndpoints("d2");
    +		equal(e.length, 2, "two endpoints on d1");
    +		equal(e2.length, 1, "one endpoint on d2");
    +	});
    +	
    +// ******************  connection type tests - types, type extension, set types, get types etc. *****************
    +
    +	test(renderMode + " set connection type on existing connection", function() {
    +		var basicType = {
    +			connector:"Flowchart",
    +			paintStyle:{ strokeStyle:"yellow", lineWidth:4 },
    +			hoverPaintStyle:{ strokeStyle:"blue" }
    +		};
    +		
    +		_jsPlumb.registerConnectionType("basic", basicType);
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +			c = _jsPlumb.connect({source:d1, target:d2});
    +			
    +		c.setType("basic");
    +		equal(c.getPaintStyle().lineWidth, 4, "paintStyle lineWidth is 4");
    +		equal(c.getPaintStyle().strokeStyle, "yellow", "paintStyle strokeStyle is yellow");
    +		equal(c.getHoverPaintStyle().strokeStyle, "blue", "paintStyle strokeStyle is yellow");
    +		equal(c.getHoverPaintStyle().lineWidth, 4, "hoverPaintStyle linewidth is 6");
    +	});
    +	
    +	test(renderMode + " set connection type on existing connection then change type", function() {
    +		var basicType = {
    +			connector:"Flowchart",
    +			paintStyle:{ strokeStyle:"yellow", lineWidth:4 },
    +			hoverPaintStyle:{ strokeStyle:"blue" }
    +		};
    +		var otherType = {
    +			connector:"Bezier",
    +			paintStyle:{ strokeStyle:"red", lineWidth:14 },
    +			hoverPaintStyle:{ strokeStyle:"green" }
    +		};
    +		
    +		_jsPlumb.registerConnectionType("basic", basicType);
    +		_jsPlumb.registerConnectionType("other", otherType);
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +			c = _jsPlumb.connect({source:d1, target:d2});
    +			
    +		c.setType("basic");
    +		equal(c.getPaintStyle().lineWidth, 4, "paintStyle lineWidth is 4");
    +		equal(c.getPaintStyle().strokeStyle, "yellow", "paintStyle strokeStyle is yellow");
    +		equal(c.getHoverPaintStyle().strokeStyle, "blue", "hoverPaintStyle strokeStyle is blue");
    +		equal(c.getHoverPaintStyle().lineWidth, 4, "hoverPaintStyle linewidth is 6");
    +		
    +		c.setType("other");
    +		equal(c.getPaintStyle().lineWidth, 14, "paintStyle lineWidth is 14");
    +		equal(c.getPaintStyle().strokeStyle, "red", "paintStyle strokeStyle is red");
    +		equal(c.getHoverPaintStyle().strokeStyle, "green", "hoverPaintStyle strokeStyle is green");
    +		equal(c.getHoverPaintStyle().lineWidth, 14, "hoverPaintStyle linewidth is 14");
    +	});
    +	
    +	test(renderMode + " set connection type on existing connection, overlays should be set", function() {
    +		var basicType = {
    +			connector:"Flowchart",
    +			paintStyle:{ strokeStyle:"yellow", lineWidth:4 },
    +			hoverPaintStyle:{ strokeStyle:"blue" },
    +			overlays:[
    +				"Arrow"
    +			]
    +		};
    +		
    +		_jsPlumb.registerConnectionType("basic", basicType);
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +			c = _jsPlumb.connect({source:d1, target:d2});
    +			
    +		c.setType("basic");
    +		equal(c.getOverlays().length, 1, "one overlay");
    +	});
    +	
    +	test(renderMode + " set connection type on existing connection, overlays should be removed with second type", function() {
    +		var basicType = {
    +			connector:"Flowchart",
    +			paintStyle:{ strokeStyle:"yellow", lineWidth:4 },
    +			hoverPaintStyle:{ strokeStyle:"blue" },
    +			overlays:[
    +				"Arrow"
    +			]
    +		};		
    +		var otherType = {
    +			connector:"Bezier"
    +		};		
    +		_jsPlumb.registerConnectionType("basic", basicType);
    +		_jsPlumb.registerConnectionType("other", otherType);
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +			c = _jsPlumb.connect({source:d1, target:d2});
    +			
    +		c.setType("basic");
    +		equal(c.getOverlays().length, 1, "one overlay");
    +		c.setType("other");
    +		equal(c.getOverlays().length, 0, "no overlays");
    +		equal(c.getPaintStyle().lineWidth, _jsPlumb.Defaults.PaintStyle.lineWidth, "paintStyle lineWidth is default");
    +	});	
    +	
    +	test(renderMode + " set connection type on existing connection, hasType + toggleType", function() {
    +		var basicType = {
    +			connector:"Flowchart",
    +			paintStyle:{ strokeStyle:"yellow", lineWidth:4 },
    +			hoverPaintStyle:{ strokeStyle:"blue" },
    +			overlays:[
    +				"Arrow"
    +			]
    +		};
    +		
    +		_jsPlumb.registerConnectionTypes({
    +			"basic": basicType
    +		});
    +		
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +			c = _jsPlumb.connect({source:d1, target:d2});
    +			
    +		c.setType("basic");
    +		equal(c.hasType("basic"), true, "connection has 'basic' type");
    +		c.toggleType("basic");
    +		equal(c.hasType("basic"), false, "connection does not have 'basic' type");
    +		equal(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style");
    +		c.toggleType("basic");
    +		equal(c.hasType("basic"), true, "connection has 'basic' type");
    +		equal(c.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style");
    +		equal(c.getOverlays().length, 1, "one overlay");
    +		
    +	});
    +	
    +	test(renderMode + " set connection type on existing connection, merge tests", function() {
    +		var basicType = {
    +			connector:"Flowchart",
    +			paintStyle:{ strokeStyle:"yellow", lineWidth:4 },
    +			hoverPaintStyle:{ strokeStyle:"blue" },
    +			overlays:[
    +				"Arrow"
    +			]
    +		};
    +		// this tests all three merge types: connector should overwrite, linewidth should be inserted into
    +		// basic type's params, and arrow overlay should be added to list to end up with two overlays
    +		var otherType = {
    +			connector:"Bezier",
    +			paintStyle:{ lineWidth:14 },
    +			overlays:[
    +				["Arrow", {location:0.25}]
    +			]
    +		};		
    +		_jsPlumb.registerConnectionTypes({
    +			"basic": basicType,
    +			"other": otherType
    +		});
    +		
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +			c = _jsPlumb.connect({source:d1, target:d2});
    +			
    +		c.setType("basic");
    +		equal(c.hasType("basic"), true, "connection has 'basic' type");		
    +		equal(c.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style");
    +		equal(c.getPaintStyle().lineWidth, 4, "connection has linewidth 4");
    +		equal(c.getOverlays().length, 1, "one overlay");
    +		
    +		c.addType("other");
    +		equal(c.hasType("basic"), true, "connection has 'basic' type");
    +		equal(c.hasType("other"), true, "connection has 'other' type");	
    +		equal(c.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style");
    +		equal(c.getPaintStyle().lineWidth, 14, "connection has linewidth 14");
    +		equal(c.getOverlays().length, 2, "two overlays");
    +		
    +		c.removeType("basic");
    +		equal(c.hasType("basic"), false, "connection does not have 'basic' type");
    +		equal(c.hasType("other"), true, "connection has 'other' type");	
    +		equal(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style");
    +		equal(c.getPaintStyle().lineWidth, 14, "connection has linewidth 14");
    +		equal(c.getOverlays().length, 1, "one overlay");
    +		
    +		c.toggleType("other");
    +		equal(c.hasType("other"), false, "connection does not have 'other' type");
    +		equal(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style");
    +		equal(c.getPaintStyle().lineWidth, _jsPlumb.Defaults.PaintStyle.lineWidth, "connection has default linewidth");
    +		equal(c.getOverlays().length, 0, "nooverlays");
    +	});
    +	
    +	test(renderMode + " connection type tests, space separated arguments", function() {
    +		var basicType = {
    +			connector:"Flowchart",
    +			paintStyle:{ strokeStyle:"yellow", lineWidth:4 },
    +			hoverPaintStyle:{ strokeStyle:"blue" },
    +			overlays:[
    +				"Arrow"
    +			]
    +		};
    +		// this tests all three merge types: connector should overwrite, linewidth should be inserted into
    +		// basic type's params, and arrow overlay should be added to list to end up with two overlays
    +		var otherType = {
    +			connector:"Bezier",
    +			paintStyle:{ lineWidth:14 },
    +			overlays:[
    +				["Arrow", {location:0.25}]
    +			]
    +		};		
    +		_jsPlumb.registerConnectionTypes({
    +			"basic": basicType,
    +			"other": otherType
    +		});
    +		
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +			c = _jsPlumb.connect({source:d1, target:d2});
    +			
    +		c.setType("basic other");
    +		equal(c.hasType("basic"), true, "connection has 'basic' type");
    +		equal(c.hasType("other"), true, "connection has 'other' type");
    +		equal(c.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style");
    +		equal(c.getPaintStyle().lineWidth, 14, "connection has linewidth 14");
    +		equal(c.getOverlays().length, 2, "two overlays");
    +		
    +		c.toggleType("other basic");
    +		equal(c.hasType("basic"), false, "after toggle, connection does not have 'basic' type");
    +		equal(c.hasType("other"), false, "after toggle, connection does not have 'other' type");	
    +		equal(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "after toggle, connection has default stroke style");
    +		equal(c.getPaintStyle().lineWidth, _jsPlumb.Defaults.PaintStyle.lineWidth, "after toggle, connection has default linewidth");
    +		equal(c.getOverlays().length, 0, "after toggle, no overlays");
    +		
    +		c.toggleType("basic other");
    +		equal(c.hasType("basic"), true, "after toggle again, connection has 'basic' type");
    +		equal(c.hasType("other"), true, "after toggle again, connection has 'other' type");
    +		equal(c.getPaintStyle().strokeStyle, "yellow", "after toggle again, connection has yellow stroke style");
    +		equal(c.getPaintStyle().lineWidth, 14, "after toggle again, connection has linewidth 14");
    +		equal(c.getOverlays().length, 2, "after toggle again, two overlays");
    +		
    +		c.removeType("other basic");
    +		equal(c.hasType("basic"), false, "after remove, connection does not have 'basic' type");
    +		equal(c.hasType("other"), false, "after remove, connection does not have 'other' type");	
    +		equal(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "after remove, connection has default stroke style");
    +		equal(c.getPaintStyle().lineWidth, _jsPlumb.Defaults.PaintStyle.lineWidth, "after remove, connection has default linewidth");
    +		equal(c.getOverlays().length, 0, "after remove, no overlays");
    +		
    +		c.addType("other basic");
    +		equal(c.hasType("basic"), true, "after add, connection has 'basic' type");
    +		equal(c.hasType("other"), true, "after add, connection has 'other' type");
    +		equal(c.getPaintStyle().strokeStyle, "yellow", "after add, connection has yellow stroke style");
    +		// NOTE here we added the types in the other order to before, so lineWidth 4 - from basic - should win.
    +		equal(c.getPaintStyle().lineWidth, 4, "after add, connection has linewidth 4");
    +		equal(c.getOverlays().length, 2, "after add, two overlays");
    +	});
    +	
    +	test(renderMode + " connection type tests, fluid interface", function() {
    +		var basicType = {
    +			connector:"Flowchart",
    +			paintStyle:{ strokeStyle:"yellow", lineWidth:4 },
    +			hoverPaintStyle:{ strokeStyle:"blue" },
    +			overlays:[
    +				"Arrow"
    +			]
    +		};
    +		// this tests all three merge types: connector should overwrite, linewidth should be inserted into
    +		// basic type's params, and arrow overlay should be added to list to end up with two overlays
    +		var otherType = {
    +			connector:"Bezier",
    +			paintStyle:{ lineWidth:14 },
    +			overlays:[
    +				["Arrow", {location:0.25}]
    +			]
    +		};		
    +		_jsPlumb.registerConnectionTypes({
    +			"basic": basicType,
    +			"other": otherType
    +		});
    +		
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"),
    +			c = _jsPlumb.connect({source:d1, target:d2}),
    +			c2 = _jsPlumb.connect({source:d2, target:d3});
    +			
    +		_jsPlumb.select().addType("basic");		
    +		equal(c.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style");
    +		equal(c2.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style");
    +		
    +		_jsPlumb.select().toggleType("basic");
    +		equal(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style");
    +		equal(c2.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style");
    +		
    +		_jsPlumb.select().addType("basic");		
    +		equal(c.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style");
    +		equal(c2.getPaintStyle().strokeStyle, "yellow", "connection has yellow stroke style");
    +		
    +		_jsPlumb.select().removeType("basic").addType("other");		
    +		equal(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style");
    +		equal(c2.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style");
    +		
    +		
    +	});
    +	
    +	test(renderMode + " connection type tests, two types, check separation", function() {
    +		var basicType = {
    +			connector:"Flowchart",
    +			paintStyle:{ strokeStyle:"yellow", lineWidth:4 }
    +		};
    +		// this tests all three merge types: connector should overwrite, linewidth should be inserted into
    +		// basic type's params, and arrow overlay should be added to list to end up with two overlays
    +		var otherType = {
    +			paintStyle:{ strokeStyle:"red", lineWidth:14 }
    +		};		
    +		_jsPlumb.registerConnectionTypes({
    +			"basic": basicType,
    +			"other": otherType
    +		});
    +		
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"),
    +			c = _jsPlumb.connect({source:d1, target:d2}),
    +			c2 = _jsPlumb.connect({source:d2, target:d3});
    +		c.setType("basic");
    +			equal(c.getPaintStyle().strokeStyle, "yellow", "first connection has yellow stroke style");
    +		c2.setType("other");
    +		
    +		equal(c.getPaintStyle().strokeStyle, "yellow", "first connection has yellow stroke style");
    +	
    +		
    +	});
    +	
    +	test(renderMode + " setType when null", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"),
    +			c = _jsPlumb.connect({source:d1, target:d2});
    +			
    +		c.setType(null);		
    +		equal(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style");		
    +		
    +	});
    +	
    +	test(renderMode + " setType to unknown type", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"),
    +			c = _jsPlumb.connect({source:d1, target:d2});
    +			
    +		c.setType("foo");		
    +		equal(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style");		
    +		
    +	});
    +	
    +	test(renderMode + " setType to mix of known and unknown types", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"),
    +			c = _jsPlumb.connect({source:d1, target:d2});
    +			
    +		_jsPlumb.registerConnectionType("basic", {
    +			connector:"Flowchart",
    +			paintStyle:{ strokeStyle:"yellow", lineWidth:4 },
    +			hoverPaintStyle:{ strokeStyle:"blue" },
    +			overlays:[
    +				"Arrow"
    +			]
    +		});
    +			
    +		c.setType("basic foo");		
    +		equal(c.getPaintStyle().strokeStyle, "yellow", "connection has basic type's stroke style");
    +		
    +		c.toggleType("foo");		
    +		equal(c.getPaintStyle().strokeStyle, "yellow", "connection has basic type's stroke style");
    +		
    +		c.removeType("basic baz");		
    +		equal(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style");
    +		
    +		c.addType("basic foo bar baz");		
    +		equal(c.getPaintStyle().strokeStyle, "yellow", "connection has basic type's stroke style");
    +		
    +	});
    +	
    +	test(renderMode + " create connection using type parameter", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3");
    +			
    +		_jsPlumb.Defaults.PaintStyle = {strokeStyle:"blue", lineWidth:34};
    +		
    +		_jsPlumb.registerConnectionTypes({
    +			"basic": {
    +				connector:"Flowchart",
    +				paintStyle:{ strokeStyle:"yellow", lineWidth:4 },
    +				hoverPaintStyle:{ strokeStyle:"blue" },
    +				overlays:[
    +					"Arrow"
    +				]
    +			},
    +			"other":{
    +				paintStyle:{ lineWidth:14 }
    +			}
    +		});
    +		
    +		equal(_jsPlumb.Defaults.PaintStyle.strokeStyle, "blue", "default value has not been messed up");
    +		
    +		c = _jsPlumb.connect({source:d1, target:d2});
    +		equal(c.getPaintStyle().strokeStyle, _jsPlumb.Defaults.PaintStyle.strokeStyle, "connection has default stroke style");
    +		
    +		c = _jsPlumb.connect({source:d1, target:d2, type:"basic other"});
    +		equal(c.getPaintStyle().strokeStyle, "yellow", "connection has basic type's stroke style");
    +		equal(c.getPaintStyle().lineWidth, 14, "connection has other type's lineWidth");		
    +				
    +	});
    +	
    +	test(renderMode + " setType, scope", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"),
    +			c = _jsPlumb.connect({source:d1, target:d2});
    +			
    +		_jsPlumb.registerConnectionType("basic", {
    +			connector:"Flowchart",
    +			scope:"BANANA",
    +			detachable:false,
    +			paintStyle:{ strokeStyle:"yellow", lineWidth:4 },
    +			hoverPaintStyle:{ strokeStyle:"blue" },
    +			overlays:[
    +				"Arrow"
    +			]
    +		});
    +		
    +		_jsPlumb.Defaults.ConnectionsDetachable = true;//just make sure we've setup the test correctly.
    +			
    +		c.setType("basic");		
    +		equal(c.scope, "BANANA", "scope is correct");
    +		equal(c.isDetachable(), false, "not detachable");
    +		
    +	});
    +	
    +	test(renderMode + " setType, parameters", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3");			
    +			
    +		_jsPlumb.registerConnectionType("basic", {
    +			parameters:{
    +				foo:1,
    +				bar:2,
    +				baz:6785962437582
    +			}
    +		});
    +		
    +		_jsPlumb.registerConnectionType("frank", {			
    +			parameters:{
    +				bar:5
    +			}
    +		});
    +		
    +		// first try creating one with the parameters
    +		c = _jsPlumb.connect({source:d1, target:d2, type:"basic"});
    +		
    +		equal(c.getParameter("foo"), 1, "foo param correct");
    +		equal(c.getParameter("bar"), 2, "bar param correct");
    +		
    +		c.addType("frank");
    +		equal(c.getParameter("foo"), 1, "foo param correct");
    +		equal(c.getParameter("bar"), 5, "bar param correct");
    +	});
    +	
    +	test(renderMode + " set connection type on existing connection, parameterised type", function() {
    +		var basicType = {
    +			connector:"Flowchart",
    +			paintStyle:{ strokeStyle:"${strokeColor}", lineWidth:4 },
    +			hoverPaintStyle:{ strokeStyle:"blue" }
    +		};
    +		
    +		_jsPlumb.registerConnectionType("basic", basicType);
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +			c = _jsPlumb.connect({source:d1, target:d2});
    +			
    +		c.setType("basic", { strokeColor:"yellow" });
    +		equal(c.getPaintStyle().lineWidth, 4, "paintStyle lineWidth is 4");
    +		equal(c.getPaintStyle().strokeStyle, "yellow", "paintStyle strokeStyle is yellow");
    +		equal(c.getHoverPaintStyle().strokeStyle, "blue", "paintStyle strokeStyle is yellow");
    +		equal(c.getHoverPaintStyle().lineWidth, 4, "hoverPaintStyle linewidth is 6");
    +	});	
    +	
    +	test(renderMode + " create connection with parameterised type", function() {
    +		var basicType = {
    +			connector:"Flowchart",
    +			paintStyle:{ strokeStyle:"${strokeColor}", lineWidth:4 },
    +			hoverPaintStyle:{ strokeStyle:"blue" }
    +		};
    +		
    +		_jsPlumb.registerConnectionType("basic", basicType);
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +			c = _jsPlumb.connect({
    +				source:d1, 
    +				target:d2, 
    +				type:"basic",
    +				data:{ strokeColor:"yellow" }
    +			});
    +			
    +		equal(c.getPaintStyle().lineWidth, 4, "paintStyle lineWidth is 4");
    +		equal(c.getPaintStyle().strokeStyle, "yellow", "paintStyle strokeStyle is yellow");
    +		equal(c.getHoverPaintStyle().strokeStyle, "blue", "paintStyle strokeStyle is yellow");
    +		equal(c.getHoverPaintStyle().lineWidth, 4, "hoverPaintStyle linewidth is 6");
    +	});		
    +
    +	test(renderMode + " reapply parameterised type", function() {
    +		var basicType = {
    +			connector:"Flowchart",
    +			paintStyle:{ strokeStyle:"${strokeColor}", lineWidth:4 },
    +			hoverPaintStyle:{ strokeStyle:"blue" }
    +		};
    +		
    +		_jsPlumb.registerConnectionType("basic", basicType);
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"),
    +			c = _jsPlumb.connect({
    +				source:d1, 
    +				target:d2
    +			});
    +			
    +		c.addType("basic", { strokeColor:"yellow" });
    +		equal(c.getPaintStyle().lineWidth, 4, "paintStyle lineWidth is 4");
    +		equal(c.getPaintStyle().strokeStyle, "yellow", "paintStyle strokeStyle is yellow");
    +		equal(c.getHoverPaintStyle().strokeStyle, "blue", "paintStyle strokeStyle is yellow");
    +		equal(c.getHoverPaintStyle().lineWidth, 4, "hoverPaintStyle linewidth is 6");
    +
    +		c.reapplyTypes({ strokeColor:"green" });
    +		equal(c.getPaintStyle().strokeStyle, "green", "paintStyle strokeStyle is now green");
    +	});
    +	
    +	test(renderMode + " setType, scope, two types", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"),
    +			c = _jsPlumb.connect({source:d1, target:d2});
    +			
    +		_jsPlumb.registerConnectionType("basic", {
    +			connector:"Flowchart",
    +			scope:"BANANA",
    +			detachable:false,
    +			paintStyle:{ strokeStyle:"yellow", lineWidth:4 },
    +			hoverPaintStyle:{ strokeStyle:"blue" },
    +			overlays:[
    +				"Arrow"
    +			]
    +		});
    +		
    +		_jsPlumb.registerConnectionType("frank", {			
    +			scope:"OVERRIDE",
    +			detachable:true
    +		});
    +		
    +		_jsPlumb.Defaults.ConnectionsDetachable = true;//just make sure we've setup the test correctly.
    +			
    +		c.setType("basic frank");		
    +		equal(c.scope, "OVERRIDE", "scope is correct");
    +		equal(c.isDetachable(), true, "detachable");
    +		
    +	});
    +	
    +	test(renderMode + " create connection from Endpoints - type should be passed through.", function() {
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"),
    +			e1 = _jsPlumb.addEndpoint(d1, {
    +				connectionType:"basic"
    +			}),
    +			e2 = _jsPlumb.addEndpoint(d2, {
    +				connectionType:"basic"
    +			});
    +			
    +		_jsPlumb.registerConnectionTypes({
    +			"basic": {
    +				connector:"Flowchart",
    +				paintStyle:{ strokeStyle:"blue", lineWidth:4 },
    +				hoverPaintStyle:{ strokeStyle:"red" },
    +				overlays:[
    +					"Arrow"
    +				]
    +			},
    +			"other":{
    +				paintStyle:{ lineWidth:14 }
    +			}
    +		});	
    +		
    +		c = _jsPlumb.connect({source:e1, target:e2});
    +		equal(c.getPaintStyle().strokeStyle, "blue", "connection has default stroke style");
    +		equal(c.getConnector().type, "Flowchart", "connector is flowchart");
    +	});
    +	
    +	test(renderMode + " simple Endpoint type tests.", function() {
    +		_jsPlumb.registerEndpointType("basic", {
    +			paintStyle:{fillStyle:"blue"}
    +		});
    +		
    +		var d = _addDiv('d1'), e = _jsPlumb.addEndpoint(d);
    +		e.setType("basic");
    +		equal(e.getPaintStyle().fillStyle, "blue", "fill style is correct");
    +		
    +		var d2 = _addDiv('d2'), e2 = _jsPlumb.addEndpoint(d2, {type:"basic"});
    +		equal(e2.getPaintStyle().fillStyle, "blue", "fill style is correct");
    +	});
    +	
    +	test(renderMode + " create connection from Endpoints - with connector settings in Endpoint type.", function() {
    +			
    +		_jsPlumb.registerEndpointTypes({
    +			"basic": {
    +				connector:"Flowchart",
    +				connectorOverlays:[
    +					"Arrow"
    +				],
    +				connectorStyle:{strokeStyle:"green" },
    +				connectorHoverStyle:{lineWidth:534 },
    +				paintStyle:{ fillStyle:"blue" },
    +				hoverPaintStyle:{ strokeStyle:"red" },
    +				overlays:[
    +					"Arrow"
    +				]
    +			},
    +			"other":{
    +				paintStyle:{ fillStyle:"red" }
    +			}
    +		});
    +		
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"),
    +			e1 = _jsPlumb.addEndpoint(d1, {
    +				type:"basic"
    +			}),
    +			e2 = _jsPlumb.addEndpoint(d2);
    +		
    +		c = _jsPlumb.connect({source:e1, target:e2});
    +		equal(e1.getPaintStyle().fillStyle, "blue", "endpoint has fill style specified in Endpoint type");
    +		equal(c.getPaintStyle().strokeStyle, "green", "connection has stroke style specified in Endpoint type");
    +		equal(c.getHoverPaintStyle().lineWidth, 534, "connection has hover style specified in Endpoint type");
    +		equal(c.getConnector().type, "Flowchart", "connector is Flowchart");
    +		equal(c.overlays.length, 1, "connector has one overlay");
    +		equal(e1.overlays.length, 1, "endpoint has one overlay");
    +	});
    +	
    +	test(renderMode + " create connection from Endpoints - type should be passed through.", function() {		
    +			
    +		_jsPlumb.registerConnectionTypes({
    +			"basic": {
    +				connector:"Flowchart",
    +				paintStyle:{ strokeStyle:"bazona", lineWidth:4 },
    +				hoverPaintStyle:{ strokeStyle:"red" },
    +				overlays:[
    +					"Arrow"
    +				]
    +			}
    +		});
    +		
    +		_jsPlumb.registerEndpointType("basic", {
    +			connectionType:"basic",
    +			paintStyle:{fillStyle:"GAZOODA"}
    +		});
    +		
    +		var d1 = _addDiv("d1"), d2 = _addDiv("d2"), d3 = _addDiv("d3"),
    +			e1 = _jsPlumb.addEndpoint(d1, {
    +				type:"basic"
    +			}),
    +			e2 = _jsPlumb.addEndpoint(d2);
    +		
    +		c = _jsPlumb.connect({source:e1, target:e2});
    +		equal(e1.getPaintStyle().fillStyle, "GAZOODA", "endpoint has correct paint style, from type.");
    +		equal(c.getPaintStyle().strokeStyle, "bazona", "connection has paint style from connection type, as specified in endpoint type. sweet!");
    +		equal(c.getConnector().type, "Flowchart", "connector is flowchart - this also came from connection type as specified by endpoint type.");
    +	});
    +	
    +	test(renderMode + " endpoint type", function() {
    +		_jsPlumb.registerEndpointTypes({"example": {hoverPaintStyle: null}});	
    +		//OR
    +		//jsPlumb.registerEndpointType("example", {hoverPaintStyle: null});
    +
    +		var d = _addDiv("d");
    +		_jsPlumb.addEndpoint(d, {type: "example"});
    +		_jsPlumb.repaint(d);
    +		
    +		expect(0);
    +	});
    +	
    +// ------------- utility functions - math stuff, mostly --------------------------
    +
    +    var tolerance = 0.00000005, withinTolerance = function(v1, v2, msg) {
    +        if (Math.abs(v1 - v2) < tolerance) ok(true, msg + "; expected " + v1 + " and got it");
    +        else {
    +            ok(false, msg + "; expected " + v1 + " got " + v2);
    +        }
    +    };
    +    
    +    test(renderMode + "jsPlumbUtil.copyValues", function() {
    +    	var n = ["foo", "bar", "baz"],
    +    		t = {"hello":"hello", "foo":"replaced"},
    +    		f = {"foo":"new", "bar":"bar"};
    +
    +    	jsPlumbUtil.copyValues(n, f, t);
    +    	equal(t.foo, "new");
    +    	equal(t.hello, "hello");
    +    	equal(t.bar,"bar");
    +    })
    +
    +    test(renderMode + " jsPlumbUtil.segment, segment 1", function() {
    +		var p1 = [2,0], p2 = [3,-1], s = jsPlumbUtil.segment(p1,p2);
    +		equal(s, 1, "segment 1 correct");
    +	});        
    +    
    +    test(renderMode + " jsPlumbUtil.segment, segment 2", function() {
    +		var p1 = [2,0], p2 = [3,1], s = jsPlumbUtil.segment(p1,p2);
    +		equal(s, 2, "segment 2 correct");
    +	});    
    +    
    +    test(renderMode + " jsPlumbUtil.segment, segment 3", function() {
    +		var p1 = [416.7166748046875, 116.13333129882812], 
    +            p2 = [95.10000610351562, 391.6666564941406], 
    +            s = jsPlumbUtil.segment(p1,p2);
    +        
    +		equal(s, 3, "segment 3 correct");
    +	});    
    +    
    +    test(renderMode + " jsPlumbUtil.segment, segment 3", function() {
    +		var p1 = [2,0], p2 = [1,1], s = jsPlumbUtil.segment(p1,p2);
    +		equal(s, 3, "segment 3 correct");
    +	});        
    +    
    +    test(renderMode + " jsPlumbUtil.segment, segment 4", function() {
    +		var p1 = [2,0], p2 = [1,-1], s = jsPlumbUtil.segment(p1,p2);
    +		equal(s, 4, "segment 4 correct");
    +	});        
    +    
    +    test(renderMode + " jsPlumbUtil.segment, segment 1 edge case", function() {
    +		var p1 = [2,0], p2 = [3,0], s = jsPlumbUtil.segment(p1,p2);
    +		equal(s, 1, "segment 1 correct");
    +	});        
    +    
    +    test(renderMode + " jsPlumbUtil.segment, segment 2 edge case", function() {
    +		var p1 = [2,0], p2 = [2,1], s = jsPlumbUtil.segment(p1,p2);
    +		equal(s, 2, "segment 2 correct");
    +	});   
    +    
    +    test(renderMode + " jsPlumbUtil.segment, segment 3 edge case", function() {
    +		var p1 = [2,0], p2 = [1,0], s = jsPlumbUtil.segment(p1,p2);
    +		equal(s, 4, "segment 4 correct");
    +	});        
    +    
    +    test(renderMode + " jsPlumbUtil.segment, segment 4 edge case", function() {
    +		var p1 = [2,0], p2 = [2,-1], s = jsPlumbUtil.segment(p1,p2);
    +		equal(s, 1, "segment 1 correct");
    +	});        
    +    
    +    
    +    test(renderMode + " jsPlumbUtil.gradient, horizontal line", function() {
    +		var p1 = [2,0], p2 = [3,0], m = jsPlumbUtil.gradient(p1,p2);
    +		equal(m, 0, "gradient calculated correctly for horizontal line");
    +	});    
    +
    +    test(renderMode + " jsPlumbUtil.gradient, vertical line", function() {
    +		var p1 = [0,2], p2 = [0,3], m = jsPlumbUtil.gradient(p1,p2);
    +		equal(m, Infinity, "gradient calculated correctly for vertical line");
    +	});        
    +    
    +    test(renderMode + " jsPlumbUtil.gradient, segment 1", function() {
    +		var p1 = [2,2], p2 = [3,1], m = jsPlumbUtil.gradient(p1,p2);
    +		equal(m, -1, "gradient calculated correctly for simple case");
    +	});
    +	test(renderMode + " jsPlumbUtil.normal, segment 1", function() {
    +		var p1 = [2,2], p2 = [3,1], m = jsPlumbUtil.normal(p1,p2);
    +		equal(m, 1, "normal calculated correctly for simple case");
    +	});
    +	test(renderMode + " jsPlumbUtil.gradient, segment 2", function() {
    +		var p1 = [2,2], p2 = [3,3], m = jsPlumbUtil.gradient(p1,p2);
    +		equal(m, 1, "gradient calculated correctly for simple case");
    +	});
    +	test(renderMode + " jsPlumbUtil.normal, segment 2", function() {
    +		var p1 = [2,2], p2 = [3,3], m = jsPlumbUtil.normal(p1,p2);
    +		equal(m, -1, "normal calculated correctly for simple case");
    +	});
    +    test(renderMode + " jsPlumbUtil.gradient, segment 3", function() {
    +		var p1 = [2,2], p2 = [1,3], m = jsPlumbUtil.gradient(p1,p2);
    +		equal(m, -1, "gradient calculated correctly for simple case");
    +	});
    +	test(renderMode + " jsPlumbUtil.normal, segment 3", function() {
    +		var p1 = [2,2], p2 = [1,3], m = jsPlumbUtil.normal(p1,p2);
    +		equal(m, 1, "normal calculated correctly for simple case");
    +	});
    +    test(renderMode + " jsPlumbUtil.gradient, segment 4", function() {
    +		var p1 = [2,2], p2 = [1,1], m = jsPlumbUtil.gradient(p1,p2);
    +		equal(m, 1, "gradient calculated correctly for simple case");
    +	});
    +	test(renderMode + " jsPlumbUtil.normal, segment 4", function() {
    +		var p1 = [2,2], p2 = [1,1], m = jsPlumbUtil.normal(p1,p2);
    +		equal(m, -1, "normal calculated correctly for simple case");
    +	});
    +    test(renderMode + "jsPlumbUtil.pointOnLine, segment 1", function() {
    +       var p1 = {x:2,y:2}, p2={x:3, y:1},
    +           target = jsPlumbUtil.pointOnLine(p1, p2, Math.sqrt(2));
    +        withinTolerance(p2.x, target.x, "x is calculated correctly");
    +        withinTolerance(p2.y, target.y, "y is calculated correctly");
    +    });
    +    test(renderMode + "jsPlumbUtil.pointOnLine, segment 2", function() {
    +       var p1 = {x:2,y:2}, p2={x:3, y:3},
    +           target = jsPlumbUtil.pointOnLine(p1, p2, Math.sqrt(2));
    +        withinTolerance(p2.x, target.x, "x is calculated correctly");
    +        withinTolerance(p2.y, target.y, "y is calculated correctly");
    +    });
    +    test(renderMode + "jsPlumbUtil.pointOnLine, segment 3", function() {
    +       var p1 = {x:2,y:2}, p2={x:1, y:3},
    +           target = jsPlumbUtil.pointOnLine(p1, p2, Math.sqrt(2));
    +        withinTolerance(p2.x, target.x, "x is calculated correctly");
    +        withinTolerance(p2.y, target.y, "y is calculated correctly");
    +    });
    +    test(renderMode + "jsPlumbUtil.pointOnLine, segment 4", function() {
    +       var p1 = {x:2,y:2}, p2={x:1, y:1},
    +           target = jsPlumbUtil.pointOnLine(p1, p2, Math.sqrt(2));
    +        withinTolerance(p2.x, target.x, "x is calculated correctly");
    +        withinTolerance(p2.y, target.y, "y is calculated correctly");
    +    });
    +    test(renderMode + "jsPlumbUtil.perpendicularLineTo, segment 1", function() {
    +        var p1 = {x:2, y:2}, p2={x:3, y:1}, m = jsPlumbUtil.gradient(p1, p2),
    +            l = jsPlumbUtil.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2));
    +
    +        withinTolerance(4, l[0].x, "point 1 x is correct");
    +        withinTolerance(2, l[0].y, "point 1 y is correct");
    +
    +        withinTolerance(2, l[1].x, "point 2 x is correct");
    +        withinTolerance(0, l[1].y, "point 2 y is correct");
    +    });
    +	test(renderMode + "jsPlumbUtil.perpendicularLineTo, segment 2", function() {
    +        var p1 = {x:2, y:2}, p2={x:3, y:3}, m = jsPlumbUtil.gradient(p1, p2),
    +            l = jsPlumbUtil.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2));
    +
    +        withinTolerance(4, l[0].x, "point 1 x is correct");
    +        withinTolerance(2, l[0].y, "point 1 y is correct");
    +
    +        withinTolerance(2, l[1].x, "point 2 x is correct");
    +        withinTolerance(4, l[1].y, "point 2 y is correct");
    +    });
    +    test(renderMode + "jsPlumbUtil.perpendicularLineTo, segment 3", function() {
    +        var p1 = {x:2, y:2}, p2={x:1, y:3}, m = jsPlumbUtil.gradient(p1, p2),
    +            l = jsPlumbUtil.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2));
    +
    +        withinTolerance(2, l[0].x, "point 1 x is correct");
    +        withinTolerance(4, l[0].y, "point 1 y is correct");
    +
    +        withinTolerance(0, l[1].x, "point 2 x is correct");
    +        withinTolerance(2, l[1].y, "point 2 y is correct");
    +    });
    +    test(renderMode + "jsPlumbUtil.perpendicularLineTo, segment 4", function() {
    +        var p1 = {x:2, y:2}, p2={x:1, y:1}, m = jsPlumbUtil.gradient(p1, p2),
    +            l = jsPlumbUtil.perpendicularLineTo(p1, p2, 2 * Math.sqrt(2));
    +
    +        withinTolerance(2, l[0].x, "point 1 x is correct");
    +        withinTolerance(0, l[0].y, "point 1 y is correct");
    +
    +        withinTolerance(0, l[1].x, "point 2 x is correct");
    +        withinTolerance(2, l[1].y, "point 2 y is correct");
    +    });
    +    test(renderMode + "jsPlumbUtil.intersects, with intersection", function() {
    +    	var r1 = { x: 2, y:2, w:4, h:6},
    +    		r2 = { x: 3, y:4, w:3, h:3};
    +
    +    	ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect");
    +    });
    +    test(renderMode + "jsPlumbUtil.intersects, with no intersection", function() {
    +    	var r1 = { x: 2, y:2, w:4, h:6},
    +    		r2 = { x: 13, y:4, w:3, h:3};
    +
    +    	ok(!jsPlumbUtil.intersects(r1, r2), "r1 and r2 do not intersect");
    +    });
    +    test(renderMode + "jsPlumbUtil.intersects, with intersection, equal Y", function() {
    +    	var r1 = { x: 2, y:2, w:4, h:6},
    +    		r2 = { x: 1, y:2, w:3, h:6};
    +
    +    	ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect");
    +    });
    +    test(renderMode + "jsPlumbUtil.intersects, with intersection, equal X", function() {
    +    	var r1 = { x: 2, y:2, w:4, h:6},
    +    		r2 = { x: 2, y:1, w:4, h:6};
    +
    +    	ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect");
    +    });
    +    test(renderMode + "jsPlumbUtil.intersects, identical rectangles", function() {
    +    	var r1 = { x: 2, y:2, w:4, h:6},
    +    		r2 = { x: 2, y:2, w:4, h:6};
    +
    +    	ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect");
    +    });
    +    test(renderMode + " jsPlumbUtil.intersects, corners touch (intersection)", function() {
    +    	var r1 = { x: 2, y:2, w:4, h:6},
    +    		r2 = { x: 6, y:8, w:3, h:3};
    +
    +    	ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect");
    +    });
    +    test(renderMode + " jsPlumbUtil.intersects, one rectangle contained within the other", function() {
    +    	var r1 = { x: 2, y:2, w:4, h:6},
    +    		r2 = { x: 0, y:0, w:23, h:23};
    +
    +    	ok(jsPlumbUtil.intersects(r1, r2), "r1 and r2 intersect");
    +    });	
    +	
    +	/*
    +	 * test the merge function in jsplumb util: it should create an entirely new object
    +	 */
    +    test(renderMode + " jsPlumbUtil.merge", function() {
    +        var a = {
    +            foo:"a_foo",
    +            bar:"a_bar",
    +            nested:{
    +                foo:"a_foo",
    +                bar:"a_bar"
    +            }
    +        },
    +        b = {
    +            foo:"b_foo",
    +            nested :{
    +                foo:"b_foo"
    +            }
    +        },
    +        c = jsPlumbUtil.merge(a, b);
    +        equal(c.foo, "b_foo", "c has b's foo");
    +        equal(c.nested.foo, "b_foo", "c has b's nested foo");
    +        // now change c's foo. b should be unchanged.
    +        c.foo = "c_foo";
    +        equal(b.foo, "b_foo", "b has b's foo");
    +        c.nested.foo = "c_foo";
    +        equal(b.nested.foo, "b_foo", "b has b's nested foo");
    +        equal(a.nested.foo, "a_foo", "a has a's nested foo");
    +    });
    +	
    +	// tests for a bug that i found in 1.3.16, in which an array would not overwrite an existing string.	
    +	test(renderMode + " jsPlumbUtil.merge, array overwriting string", function() {		
    +		var a = {
    +				foo:"foo",
    +				bar:"bar"
    +			},
    +			b = {
    +				foo:[ "bar", "baz" ],
    +				bar:{
    +					bar:"baz"
    +				}
    +			},
    +			c = jsPlumbUtil.merge(a, b);
    +			
    +		equal(c.foo[0], "bar", "array was copied correctly");
    +		equal(c.bar.bar, "baz", "object was copied correctly");		
    +	});
    +	
    +	test(renderMode + " jsPlumbUtil.clone", function() {
    +		var a = {
    +			nested:{
    +				foo:"a_foo"
    +			}
    +		},
    +		b = jsPlumbUtil.clone(a);
    +		equal(b.nested.foo, "a_foo", "b has a's nested foo");
    +		equal(a.nested.foo, "a_foo", "a has a's nested foo");
    +		b.nested.foo="b_foo";
    +		equal(b.nested.foo, "b_foo", "b has b's nested foo");
    +		equal(a.nested.foo, "a_foo", "a has a's nested foo");
    +	});
    +	
    +	test(renderMode + " jsPlumbUtil.clampToGrid", function() {
    +		var grid = [20,20];
    +		var test1 = [15,15],  
    +			test2 = [9,9],
    +			test3 = [30,30],
    +			test4 = [29,31],
    +			test5 = [175,570];
    +			
    +		var r1 = jsPlumbUtil.clampToGrid(test1[0], test1[1], grid),
    +			r2 = jsPlumbUtil.clampToGrid(test2[0], test2[1], grid),
    +			r3 = jsPlumbUtil.clampToGrid(test3[0], test3[1], grid),
    +			r4 = jsPlumbUtil.clampToGrid(test4[0], test4[1], grid),
    +			r5 = jsPlumbUtil.clampToGrid(test5[0], test5[1], grid);			
    +			
    +		equal(r1[0], 20);		
    +		equal(r1[1], 20);
    +		equal(r2[0], 0);		
    +		equal(r2[1], 0);		
    +		equal(r3[0], 40);		
    +		equal(r3[1], 40);
    +		equal(r4[0], 20);		
    +		equal(r4[1], 40);
    +		equal(r5[0], 180);
    +		equal(r5[1], 580);
    +	});
    +    
    +    test(renderMode + " arc segment tests", function() {							
    +        var r = 10, circ = 2 * Math.PI * r;
    +        // first, an arc up and to the right (clockwise)
    +        var params = { r:r, x1:0, y1:0, x2:10,  y2:-10, cx:10, cy:0 };
    +        var s = new jsPlumb.Segments["Arc"](params);
    +        // segment should be one quarter of the circumference
    +        equal(s.getLength(), 0.25 * circ, "length of segment is correct");
    +        // point 0 is (0,0)
    +        var p1 = s.pointOnPath(0);
    +        within(p1.x, 0, ok, "start x is correct");
    +        within(p1.y, 0, ok, "start y is correct");   
    +        // point 1 is (10, -10)
    +        var p2 = s.pointOnPath(1);
    +        within(p2.x, 10, ok, "end x is correct");
    +        within(p2.y, -10, ok, "end y is correct");                
    +        // point at loc 0.5 is (2.92, -7.07))
    +        var p3 = s.pointOnPath(0.5);
    +        within(p3.x, 10 - (Math.sqrt(2) / 2 * 10), ok, "end x is correct");
    +        within(p3.y, -(Math.sqrt(2) / 2 * 10), ok, "end y is correct");                        
    +        // gradients
    +        equal(s.gradientAtPoint(0), -Infinity, "gradient at location 0 is -Infinity");
    +        equal(s.gradientAtPoint(1), 0, "gradient at location 1 is 0");        
    +        within(s.gradientAtPoint(0.5), -1, ok, "gradient at location 0.5 is -1");                
    +        
    +        // an arc up and to the left (anticlockwise)
    +        params = { r:r, x1:0, y1:0, x2:-10,  y2:-10, cx:-10, cy:0, ac:true };  
    +        s = new jsPlumb.Segments["Arc"](params);
    +        equal(s.getLength(), 0.25 * circ, "length of segment is correct");    
    +        // point 0 is (0,0)
    +        p1 = s.pointOnPath(0);
    +        within(p1.x, 0, ok, "start x is correct");
    +        within(p1.y, 0, ok, "start y is correct");   
    +        // point 1 is (-10, -10)
    +        p2 = s.pointOnPath(1);
    +        within(p2.x, -10, ok, "end x is correct");
    +        within(p2.y, -10, ok, "end y is correct");                
    +        // point at loc 0.5 is (-2.92, -7.07))
    +        p3 = s.pointOnPath(0.5);
    +        within(p3.x, -2.9289321881345245, ok, "end x is correct");
    +        within(p3.y, -7.071067811865477, ok, "end y is correct");                        
    +        // gradients
    +        equal(s.gradientAtPoint(0), -Infinity, "gradient at location 0 is -Infinity");
    +        equal(s.gradientAtPoint(1), 0, "gradient at location 1 is 0");        
    +        within(s.gradientAtPoint(0.5), 1, ok, "gradient at location 0.5 is 1");                
    +
    +        
    +        
    +        
    +        // clockwise, 180 degrees
    +        params = { r:r, x1:0, y1:0, x2:0,  y2:20, cx:0, cy:10 };  
    +        s = new jsPlumb.Segments["Arc"](params);
    +        equal(s.getLength(), 0.5 * circ, "length of segment is correct");
    +        p1 = s.pointOnPath(0);
    +        within(p1.x, 0, ok, "start x is correct");
    +        within(p1.y, 0, ok, "start y is correct");   
    +        p2 = s.pointOnPath(1);
    +        within(p2.x, 0, ok, "end x is correct");
    +        within(p2.y, 20, ok, "end y is correct");                
    +        var p3 = s.pointOnPath(0.5);
    +        within(p3.x, 10, ok, "end x is correct");
    +        within(p3.y, 10, ok, "end y is correct");                        
    +        // gradients
    +        equal(s.gradientAtPoint(0), 0, "gradient at location 0 is 0");
    +        equal(s.gradientAtPoint(1), 0, "gradient at location 1 is 0");        
    +        equal(s.gradientAtPoint(0.5), Infinity, "gradient at location 0.5 is Infinity");                        
    +        
    +        
    +        // anticlockwise, 180 degrees
    +        params = { r:r, x1:0, y1:0, x2:0,  y2:-20, cx:0, cy:-10, ac:true };  
    +        s = new jsPlumb.Segments["Arc"](params);
    +        equal(s.getLength(), 0.5 * circ, "length of segment is correct");   
    +        p1 = s.pointOnPath(0);
    +        within(p1.x, 0, ok, "start x is correct");
    +        within(p1.y, 0, ok, "start y is correct");   
    +        p2 = s.pointOnPath(1);
    +        within(p2.x, 0, ok, "end x is correct");
    +        within(p2.y, -20, ok, "end y is correct");    
    +        var p3 = s.pointOnPath(0.5);
    +        within(p3.x, 10, ok, "end x is correct");
    +        within(p3.y, -10, ok, "end y is correct");                                
    +        
    +        
    +        // clockwise, 270 degrees
    +        params = { r:r, x1:0, y1:0, x2:-10,  y2:10, cx:0, cy:10 };  
    +        s = new jsPlumb.Segments["Arc"](params);
    +        equal(s.getLength(), 0.75 * circ, "length of segment is correct");
    +        p1 = s.pointOnPath(0);
    +        within(p1.x, 0, ok, "start x is correct");
    +        within(p1.y, 0, ok, "start y is correct");   
    +        p2 = s.pointOnPath(1);
    +        within(p2.x, -10, ok, "end x is correct");
    +        within(p2.y, 10, ok, "end y is correct");     
    +        var p3 = s.pointOnPath(0.5);
    +        within(p3.x, 7.071067811865477, ok, "end x is correct");
    +        within(p3.y, 17.071067811865477, ok, "end y is correct");                                
    +        
    +        
    +        // anticlockwise, 90 degrees
    +        params = { r:r, x1:0, y1:0, x2:-10,  y2:10, cx:0, cy:10, ac:true };  
    +        s = new jsPlumb.Segments["Arc"](params);
    +        equal(s.getLength(), 0.25 * circ, "length of segment is correct");        
    +        p1 = s.pointOnPath(0);
    +        within(p1.x, 0, ok, "start x is correct");
    +        within(p1.y, 0, ok, "start y is correct");   
    +        p2 = s.pointOnPath(1);
    +        within(p2.x, -10, ok, "end x is correct");
    +        within(p2.y, 10, ok, "end y is correct");      
    +        var p3 = s.pointOnPath(0.5);
    +        within(p3.x, -7.071067811865477, ok, "end x is correct");
    +        within(p3.y, 2.9289321881345245, ok, "end y is correct");                                
    +        
    +        
    +        // anticlockwise, 270 degrees
    +        params = { r:r, x1:0, y1:0, x2:10,  y2:10, cx:0, cy:10, ac:true };  
    +        s = new jsPlumb.Segments["Arc"](params);
    +        equal(s.getLength(), 0.75 * circ, "length of segment is correct");  
    +        p1 = s.pointOnPath(0);
    +        within(p1.x, 0, ok, "start x is correct");
    +        within(p1.y, 0, ok, "start y is correct");   
    +        p2 = s.pointOnPath(1);
    +        within(p2.x, 10, ok, "end x is correct");
    +        within(p2.y, 10, ok, "end y is correct");                
    +        var p3 = s.pointOnPath(0.5);
    +        within(p3.x, -7.071067811865477, ok, "end x is correct");
    +        within(p3.y, 17.071067811865477, ok, "end y is correct");                                
    +        
    +        
    +    });
    +    
    +    
    +    test(renderMode + " jsPlumbUtil.theta", function() {
    +        var x1 = 0, y1 = 0, 
    +            x2 = 10, y2 = 0,   // 0 degrees : 0 PI or 2 PI
    +            x3 = 10, y3 = 10,  // 45 degrees : 0.25 PI
    +            x4 = 0, y4 = 10,   // 90 degrees : 0.5 PI
    +            x5 = -10, y5 = 10, // 135 degrees : 0.75 PI
    +            x6 = -10, y6 = 0,  // 180 degrees : PI
    +            x7 = -10, y7 = -10, // 225 degress : 1.25 PI
    +            x8 = 0, y8 = -10,   // 270 degrees : 1.5 PI        
    +            x9 = 10, y9 = -10;  // 315 degrees : 1.75 PI            
    +        
    +        var t2 = jsPlumbUtil.theta([x1,y1],[x2,y2]),
    +            t3 = jsPlumbUtil.theta([x1,y1],[x3,y3]),
    +            t4 = jsPlumbUtil.theta([x1,y1],[x4,y4]),
    +            t5 = jsPlumbUtil.theta([x1,y1],[x5,y5]),
    +            t6 = jsPlumbUtil.theta([x1,y1],[x6,y6]),
    +            t7 = jsPlumbUtil.theta([x1,y1],[x7,y7]),
    +            t8 = jsPlumbUtil.theta([x1,y1],[x8,y8]),            
    +            t9 = jsPlumbUtil.theta([x1,y1],[x9,y9]);            
    +        
    +        equal(t2, 0, "t2 is zero degrees");
    +        equal(t3, 0.25 * Math.PI, "t3 is 45 degrees");
    +        equal(t4, 0.5 * Math.PI, "t4 is 90 degrees");
    +        equal(t5, 0.75 * Math.PI, "t5 is 135 degrees");
    +        equal(t6, Math.PI, "t6 is 180 degrees");
    +        equal(t7, 1.25 * Math.PI, "t7 is 225 degrees");
    +        equal(t8, 1.5 * Math.PI, "t8 is 270 degrees");        
    +        equal(t9, 1.75 * Math.PI, "t9 is 315 degrees");                
    +    });
    +    
    +    test(renderMode + " jsPlumb.getSelector, simple case", function() {
    +        _addDiv("d1");
    +        var s = _jsPlumb.getSelector("#d1");
    +        equal(s.length, 1, "d1 found by getSelector");        
    +    });
    +    
    +    test(renderMode + " jsPlumb.getSelector, with context node given as selector", function() {
    +        var d1 = _addDiv("d1");
    +        var d = $("<div id='foo'></div>");
    +        d1.append(d);
    +        var s = _jsPlumb.getSelector(d1, "#foo");
    +        equal(s.length, 1, "foo found by getSelector with context d1");        
    +        equal(s[0].getAttribute("id"), "foo", "foo found by getSelector with context d1");                
    +    });    
    +    
    +    test(renderMode + " jsPlumb.getSelector, with context node given as DOM element", function() {
    +        var d1 = _addDiv("d1");
    +        var d = $("<div id='foo'></div>");
    +        d1.append(d);
    +        var s = _jsPlumb.getSelector(d1[0], "#foo");
    +        equal(s.length, 1, "foo found by getSelector with context d1");        
    +        equal(s[0].getAttribute("id"), "foo", "foo found by getSelector with context d1");                
    +    });   
    +    
    +    test(renderMode + " addClass method of Connection", function() {
    +        _addDiv("d1"); _addDiv("d2");
    +        var c = _jsPlumb.connect({source:"d1", target:"d2"});
    +        c.addClass("foo");
    +        ok(!($(c.endpoints[0].canvas).hasClass("foo")), "endpoint does not have class 'foo'");
    +        ok(c.canvas.className.baseVal.indexOf("foo") != -1, "connection has class 'bar'");        
    +        c.addClass("bar", true);
    +        ok($(c.endpoints[0].canvas).hasClass("bar"), "endpoint has class 'bar'");        
    +        c.removeClass("bar", true);
    +        ok(c.canvas.className.baseVal.indexOf("bar") == -1, "connection doesn't have class 'bar'");                
    +        ok(!$(c.endpoints[0].canvas).hasClass("bar"), "endpoint doesnt have class 'bar'");  
    +    });
    +    
    +    test(renderMode + " addClass via jsPlumb.select", function() {
    +        _addDiv("d1"); _addDiv("d2");
    +        var c = _jsPlumb.connect({source:"d1", target:"d2"});
    +        _jsPlumb.select().addClass("foo");
    +        ok(!($(c.endpoints[0].canvas).hasClass("foo")), "endpoint does not have class 'foo'");
    +        _jsPlumb.select().addClass("bar", true);
    +        ok($(c.endpoints[0].canvas).hasClass("bar"), "endpoint hasclass 'bar'");        
    +        _jsPlumb.select().removeClass("bar", true);
    +        ok(!($(c.endpoints[0].canvas).hasClass("bar")), "endpoint doesn't have class 'bar'");                
    +    });   
    +    
    +// ******************* override pointer events ********************
    +    test(renderMode + "pointer-events, jsPlumb.connect", function() {
    +        _addDiv("d1");_addDiv("d2");
    +        var c = _jsPlumb.connect({source:"d1",target:"d2", "pointer-events":"BANANA"});
    +        equal($(c.getConnector().canvas).find("path").attr("pointer-events"), "BANANA", "pointer events passed through to svg elements");
    +    });
    +    
    +    test(renderMode + "connector-pointer-events, jsPlumb.addEndpoint", function() {
    +        _addDiv("d1");_addDiv("d2");
    +        var e1 = _jsPlumb.addEndpoint("d1", { "connector-pointer-events":"BANANA" });
    +        var c = _jsPlumb.connect({source:e1,target:"d2"});
    +        equal($(c.getConnector().canvas).find("path").attr("pointer-events"), "BANANA", "pointer events passed through to svg elements");
    +    });   
    +
    +// ******************** flowchart get segments ***************
    +	test(renderMode + " get segments from flowchart connector", function() {
    +		var d1 = _addDiv("d1")[0], d2 = _addDiv("d2")[0];
    +		d1.style.position="absolute";
    +		d2.style.position="absolute";		
    +		d1.style.left = "0px";
    +		d2.style.left = "100px";
    +		d1.style.top = "0px";
    +		d2.style.top = "100px";
    +		d1.style.width = "10px";d1.style.height = "10px";
    +		d2.style.width = "10px";d2.style.height = "10px";
    +
    +		var c = _jsPlumb.connect({
    +			source:d1,
    +			target:d2,
    +			connector:"Flowchart",
    +			anchors:["Right", "Top"]
    +		}),
    +		s = c.getConnector().getPath();
    +		//equal(s[0].start[0], 0);
    +		equal(s[0].start[1], 0);
    +		equal(s[0].end[0], 30);
    +		equal(s[0].end[1], 0);
    +		equal(s[1].start[0], 95);
    +		equal(s[1].start[1], 0);
    +		equal(s[1].end[0], 95);
    +		equal(s[1].end[1], 65);
    +
    +		var c2 = _jsPlumb.connect({
    +			source:d1,
    +			target:d2,
    +			connector:"Flowchart",
    +			anchors:["Bottom", "Top"]
    +		}),
    +		s2 = c2.getConnector().getPath();
    +		equal(s2.length, 3, "3 segments");
    +		
    +		equal(s2[0].start[0], 0);
    +		equal(s2[0].start[1], 0);
    +		equal(s2[0].end[0], 0);
    +		equal(s2[0].end[1], 30);
    +		
    +		equal(s2[1].start[0], 0);
    +		equal(s2[1].start[1], 45);
    +		equal(s2[1].end[0], 100);
    +		equal(s2[1].end[1], 45);
    +
    +		equal(s2[2].start[0], 100);
    +		equal(s2[2].start[1], 45);
    +		equal(s2[2].end[0], 100);
    +		equal(s2[2].end[1], 60);
    +
    +
    +		// now set the segments on the connection.
    +		var c3 = _jsPlumb.connect({
    +			source:d1,
    +			target:d2,
    +			connector:"Flowchart",
    +			anchors:["Right", "Top"],
    +			path:[
    +				{ start:[5,5], end:[5,55] },
    +				{ start:[5,55], end:[105,55] },
    +				{ start:[105,55], end:[105,105] }
    +			]
    +		}),
    +		s3 = c3.getConnector().getPath();
    +		equal(s3[0].start[0], 5);
    +		equal(s3[0].start[1], 5);
    +		equal(s3[0].end[0], 5);
    +		equal(s3[0].end[1], 55);
    +		equal(s3[1].start[0], 5);
    +		equal(s3[1].start[1], 55);
    +		equal(s3[1].end[0], 105);
    +		equal(s3[1].end[1],55);
    +		equal(s3[2].start[0], 105);
    +		equal(s3[2].start[1], 55);
    +		equal(s3[2].end[0], 105);
    +		equal(s3[2].end[1],105);
    +	});
    +};
    +
    diff --git a/src/jsPlumb-util.js b/src/jsPlumb-util.js
    new file mode 100644
    index 000000000..7a0f55230
    --- /dev/null
    +++ b/src/jsPlumb-util.js
    @@ -0,0 +1,393 @@
    +/*
    + * jsPlumb
    + * 
    + * Title:jsPlumb 1.4.0
    + * 
    + * Provides a way to visually connect elements on an HTML page, using either SVG or VML.  
    + * 
    + * This file contains the util functions
    + *
    + * Copyright (c) 2010 - 2013 Simon Porritt (http://jsplumb.org)
    + * 
    + * http://jsplumb.org
    + * http://github.com/sporritt/jsplumb
    + * http://code.google.com/p/jsplumb
    + * 
    + * Dual licensed under the MIT and GPL2 licenses.
    + */
    +;(function() {
    +    
    +    var pointHelper = function(p1, p2, fn) {
    +        p1 = jsPlumbUtil.isArray(p1) ? p1 : [p1.x, p1.y];
    +        p2 = jsPlumbUtil.isArray(p2) ? p2 : [p2.x, p2.y];    
    +        return fn(p1, p2);
    +    };
    +    
    +    jsPlumbUtil = {
    +        isArray : function(a) {
    +            return Object.prototype.toString.call(a) === "[object Array]";	
    +        },
    +        isString : function(s) {
    +            return typeof s === "string";
    +        },
    +        isBoolean: function(s) {
    +            return typeof s === "boolean";
    +        },
    +        isObject : function(o) {
    +            return Object.prototype.toString.call(o) === "[object Object]";	
    +        },
    +        isDate : function(o) {
    +            return Object.prototype.toString.call(o) === "[object Date]";
    +        },
    +        isFunction: function(o) {
    +            return Object.prototype.toString.call(o) === "[object Function]";
    +        },
    +        clone : function(a) {
    +            if (this.isString(a)) return "" + a;
    +            else if (this.isBoolean(a)) return !!a;
    +            else if (this.isDate(a)) return new Date(a.getTime());
    +            else if (this.isFunction(a)) return a;
    +            else if (this.isArray(a)) {
    +                var b = [];
    +                for (var i = 0; i < a.length; i++)
    +                    b.push(this.clone(a[i]));
    +                return b;
    +            }
    +            else if (this.isObject(a)) {
    +                var b = {};
    +                for (var i in a)
    +                    b[i] = this.clone(a[i]);
    +                return b;		
    +            }
    +            else return a;
    +        },
    +        merge : function(a, b) {		
    +            var c = this.clone(a);		
    +            for (var i in b) {
    +                if (c[i] == null || this.isString(b[i]) || this.isBoolean(b[i]))
    +                    c[i] = b[i];
    +                else {
    +                    if (this.isArray(b[i])/* && this.isArray(c[i])*/) {
    +                        var ar = [];
    +                        // if c's object is also an array we can keep its values.
    +                        if (this.isArray(c[i])) ar.push.apply(ar, c[i]);
    +                        ar.push.apply(ar, b[i]);
    +                        c[i] = ar;
    +                    }
    +                    else if(this.isObject(b[i])) {	
    +                        // overwite c's value with an object if it is not already one.
    +                        if (!this.isObject(c[i])) 
    +                            c[i] = {};
    +                        for (var j in b[i])
    +                            c[i][j] = b[i][j];
    +                    }
    +                }
    +            }
    +            return c;
    +        },
    +        copyValues:function(names, from, to) {
    +            for (var i = 0; i < names.length; i++)
    +                to[names[i]] = from[names[i]];
    +        },
    +        //
    +        // chain a list of functions, supplied by [ object, method name, args ], and return on the first
    +        // one that returns the failValue. if none return the failValue, return the successValue.
    +        //
    +        functionChain : function(successValue, failValue, fns) {        
    +            for (var i = 0; i < fns.length; i++) {
    +                var o = fns[i][0][fns[i][1]].apply(fns[i][0], fns[i][2]);
    +                if (o === failValue) {
    +                    return o;
    +                }
    +            }                
    +            return successValue;
    +        },
    +        // take the given model and expand out any parameters.
    +        populate : function(model, values) {		
    +            // for a string, see if it has parameter matches, and if so, try to make the substitutions.
    +            var getValue = function(fromString) {
    +                    var matches = fromString.match(/(\${.*?})/g);
    +                    if (matches != null) {
    +                        for (var i = 0; i < matches.length; i++) {
    +                            var val = values[matches[i].substring(2, matches[i].length - 1)];
    +                            if (val) {
    +                                fromString = fromString.replace(matches[i], val);
    +                            }
    +                        }							
    +                    }
    +                    return fromString;
    +                },		
    +                // process one entry.
    +                _one = function(d) {
    +                    if (d != null) {
    +                        if (jsPlumbUtil.isString(d)) {
    +                            return getValue(d);
    +                        }
    +                        else if (jsPlumbUtil.isArray(d)) {
    +                            var r = [];	
    +                            for (var i = 0; i < d.length; i++)
    +                                r.push(_one(d[i]));
    +                            return r;
    +                        }
    +                        else if (jsPlumbUtil.isObject(d)) {
    +                            var r = {};
    +                            for (var i in d) {
    +                                r[i] = _one(d[i]);
    +                            }
    +                            return r;
    +                        }
    +                        else {
    +                            return d;
    +                        }
    +                    }
    +                };
    +            
    +            return _one(model);	
    +        },
    +        convertStyle : function(s, ignoreAlpha) {
    +            // TODO: jsPlumb should support a separate 'opacity' style member.
    +            if ("transparent" === s) return s;
    +            var o = s,
    +                pad = function(n) { return n.length == 1 ? "0" + n : n; },
    +                hex = function(k) { return pad(Number(k).toString(16)); },
    +                pattern = /(rgb[a]?\()(.*)(\))/;
    +            if (s.match(pattern)) {
    +                var parts = s.match(pattern)[2].split(",");
    +                o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]);
    +                if (!ignoreAlpha && parts.length == 4) 
    +                    o = o + hex(parts[3]);
    +            }
    +            return o;
    +        },
    +        gradient : function(p1, p2) {
    +            return pointHelper(p1, p2, function(_p1, _p2) { 
    +                if (_p2[0] == _p1[0])
    +                    return _p2[1] > _p1[1] ? Infinity : -Infinity;
    +                else if (_p2[1] == _p1[1]) 
    +                    return _p2[0] > _p1[0] ? 0 : -0;
    +                else 
    +                    return (_p2[1] - _p1[1]) / (_p2[0] - _p1[0]); 
    +            });		
    +        },
    +        normal : function(p1, p2) {
    +            return -1 / this.gradient(p1, p2);
    +        },
    +        lineLength : function(p1, p2) {
    +            return pointHelper(p1, p2, function(_p1, _p2) {
    +                return Math.sqrt(Math.pow(_p2[1] - _p1[1], 2) + Math.pow(_p2[0] - _p1[0], 2));			
    +            });
    +        },
    +        segment : function(p1, p2) {
    +            return pointHelper(p1, p2, function(_p1, _p2) {
    +                if (_p2[0] > _p1[0]) {
    +                    return (_p2[1] > _p1[1]) ? 2 : 1;
    +                }
    +                else if (_p2[0] == _p1[0]) {
    +                    return _p2[1] > _p1[1] ? 2 : 1;    
    +                }
    +                else {
    +                    return (_p2[1] > _p1[1]) ? 3 : 4;
    +                }
    +            });
    +        },
    +        theta : function(p1, p2) {
    +            return pointHelper(p1, p2, function(_p1, _p2) {
    +                var m = jsPlumbUtil.gradient(_p1, _p2),
    +                    t = Math.atan(m),
    +                    s = jsPlumbUtil.segment(_p1, _p2);
    +                if ((s == 4 || s== 3)) t += Math.PI;
    +                if (t < 0) t += (2 * Math.PI);
    +            
    +                return t;
    +            });
    +        },
    +        intersects : function(r1, r2) {
    +            var x1 = r1.x, x2 = r1.x + r1.w, y1 = r1.y, y2 = r1.y + r1.h,
    +                a1 = r2.x, a2 = r2.x + r2.w, b1 = r2.y, b2 = r2.y + r2.h;
    +        
    +        return  ( (x1 <= a1 && a1 <= x2) && (y1 <= b1 && b1 <= y2) ) ||
    +                ( (x1 <= a2 && a2 <= x2) && (y1 <= b1 && b1 <= y2) ) ||
    +                ( (x1 <= a1 && a1 <= x2) && (y1 <= b2 && b2 <= y2) ) ||
    +                ( (x1 <= a2 && a1 <= x2) && (y1 <= b2 && b2 <= y2) ) ||	
    +                ( (a1 <= x1 && x1 <= a2) && (b1 <= y1 && y1 <= b2) ) ||
    +                ( (a1 <= x2 && x2 <= a2) && (b1 <= y1 && y1 <= b2) ) ||
    +                ( (a1 <= x1 && x1 <= a2) && (b1 <= y2 && y2 <= b2) ) ||
    +                ( (a1 <= x2 && x1 <= a2) && (b1 <= y2 && y2 <= b2) );
    +        },
    +        segmentMultipliers : [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ],
    +        inverseSegmentMultipliers : [null, [-1, -1], [-1, 1], [1, 1], [1, -1] ],
    +        pointOnLine : function(fromPoint, toPoint, distance) {
    +            var m = jsPlumbUtil.gradient(fromPoint, toPoint),
    +                s = jsPlumbUtil.segment(fromPoint, toPoint),
    +                segmentMultiplier = distance > 0 ? jsPlumbUtil.segmentMultipliers[s] : jsPlumbUtil.inverseSegmentMultipliers[s],
    +                theta = Math.atan(m),
    +                y = Math.abs(distance * Math.sin(theta)) * segmentMultiplier[1],
    +                x =  Math.abs(distance * Math.cos(theta)) * segmentMultiplier[0];
    +            return { x:fromPoint.x + x, y:fromPoint.y + y };
    +        },
    +        /**
    +         * calculates a perpendicular to the line fromPoint->toPoint, that passes through toPoint and is 'length' long.
    +         * @param fromPoint
    +         * @param toPoint
    +         * @param length
    +         */
    +        perpendicularLineTo : function(fromPoint, toPoint, length) {
    +            var m = jsPlumbUtil.gradient(fromPoint, toPoint),
    +                theta2 = Math.atan(-1 / m),
    +                y =  length / 2 * Math.sin(theta2),
    +                x =  length / 2 * Math.cos(theta2);
    +            return [{x:toPoint.x + x, y:toPoint.y + y}, {x:toPoint.x - x, y:toPoint.y - y}];
    +        },
    +        findWithFunction : function(a, f) {
    +            if (a)
    +                for (var i = 0; i < a.length; i++) if (f(a[i])) return i;
    +            return -1;
    +        },
    +        clampToGrid : function(x, y, grid, dontClampX, dontClampY) {
    +            var _gridClamp = function(n, g) { 
    +                var e = n % g, 
    +                    f = Math.floor(n / g), 
    +                    inc = e >= (g / 2) ? 1 : 0; 
    +                return (f + inc) * g; 
    +            };
    +            return [
    +                dontClampX || grid == null ? x : _gridClamp(x, grid[0]),
    +                dontClampY || grid == null ? y : _gridClamp(y, grid[1])
    +            ];		
    +        },
    +        indexOf : function(l, v) {
    +            return jsPlumbUtil.findWithFunction(l, function(_v) { return _v == v; });	
    +        },
    +        removeWithFunction : function(a, f) {
    +            var idx = jsPlumbUtil.findWithFunction(a, f);
    +            if (idx > -1) a.splice(idx, 1);
    +            return idx != -1;
    +        },
    +        remove : function(l, v) {
    +            var idx = jsPlumbUtil.indexOf(l, v);	
    +            if (idx > -1) l.splice(idx, 1);
    +            return idx != -1;
    +        },
    +        // TODO support insert index
    +        addWithFunction : function(list, item, hashFunction) {
    +            if (jsPlumbUtil.findWithFunction(list, hashFunction) == -1) list.push(item);
    +        },
    +        addToList : function(map, key, value) {
    +            var l = map[key];
    +            if (l == null) {
    +                l = [], map[key] = l;
    +            }
    +            l.push(value);
    +            return l;
    +        },
    +        /**
    +         * EventGenerator
    +         * Superclass for objects that generate events - jsPlumb extends this, as does jsPlumbUIComponent, which all the UI elements extend.
    +         */
    +        EventGenerator : function() {
    +            var _listeners = {}, self = this, eventsSuspended = false;
    +            
    +            // this is a list of events that should re-throw any errors that occur during their dispatch. as of 1.3.0 this is private to
    +            // jsPlumb, but it seems feasible that people might want to manipulate this list.  the thinking is that we don't want event
    +            // listeners to bring down jsPlumb - or do we.  i can't make up my mind about this, but i know i want to hear about it if the "ready"
    +            // event fails, because then my page has most likely not initialised.  so i have this halfway-house solution.  it will be interesting
    +            // to hear what other people think.
    +            var eventsToDieOn = [ "ready" ];
    +                                    
    +            /*
    +             * Binds a listener to an event.  
    +             * 
    +             * Parameters:
    +             * 	event		-	name of the event to bind to.
    +             * 	listener	-	function to execute.
    +             */
    +            this.bind = function(event, listener) {
    +                jsPlumbUtil.addToList(_listeners, event, listener);		
    +                return self;		
    +            };
    +            /*
    +             * Fires an update for the given event.
    +             * 
    +             * Parameters:
    +             * 	event				-	event to fire
    +             * 	value				-	value to pass to the event listener(s).
    +             *  originalEvent	 	- 	the original event from the browser
    +             */			
    +            this.fire = function(event, value, originalEvent) {
    +                if (!eventsSuspended && _listeners[event]) {
    +                    for ( var i = 0; i < _listeners[event].length; i++) {
    +                        // doing it this way rather than catching and then possibly re-throwing means that an error propagated by this
    +                        // method will have the whole call stack available in the debugger.
    +                        if (jsPlumbUtil.findWithFunction(eventsToDieOn, function(e) { return e === event}) != -1)
    +                            _listeners[event][i](value, originalEvent);
    +                        else {
    +                            // for events we don't want to die on, catch and log.
    +                            try {
    +                                _listeners[event][i](value, originalEvent);
    +                            } catch (e) {
    +                                jsPlumbUtil.log("jsPlumb: fire failed for event " + event + " : " + e);
    +                            }
    +                        }
    +                    }
    +                }
    +                return self;
    +            };
    +            /*
    +             * Clears either all listeners, or listeners for some specific event.
    +             * 
    +             * Parameters:
    +             * 	event	-	optional. constrains the clear to just listeners for this event.
    +             */
    +            this.unbind = function(event) {
    +                if (event)
    +                    delete _listeners[event];
    +                else {
    +                    _listeners = {};
    +                }
    +                return self;
    +            };
    +            
    +            this.getListener = function(forEvent) {
    +                return _listeners[forEvent];
    +            };		
    +            
    +            this.setSuspendEvents = function(val) {
    +                eventsSuspended = val;    
    +            };
    +            
    +            this.isSuspendEvents = function() {
    +                return eventsSuspended;
    +            };
    +        },
    +        logEnabled : true,
    +        log : function() {
    +            if (jsPlumbUtil.logEnabled && typeof console != "undefined") {
    +                try {
    +                    var msg = arguments[arguments.length - 1];
    +                    console.log(msg);
    +                }
    +                catch (e) {} 
    +            }
    +        },
    +        group : function(g) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.group(g); },
    +        groupEnd : function(g) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.groupEnd(g); },
    +        time : function(t) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.time(t); },
    +        timeEnd : function(t) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.timeEnd(t); },
    +        
    +        /**
    +		 * helper to remove an element from the DOM.
    +		 */
    +		removeElement : function(element) {
    +			if (element != null && element.parentNode != null) {
    +				element.parentNode.removeChild(element);
    +			}
    +		},
    +        /**
    +		 * helper to remove a list of elements from the DOM.
    +		 */
    +		removeElements : function(elements) {
    +			for ( var i = 0; i < elements.length; i++)
    +				jsPlumbUtil.removeElement(elements[i]);
    +		}
    +    };
    +})();
    \ No newline at end of file
    diff --git a/src/jsPlumb.js b/src/jsPlumb.js
    new file mode 100644
    index 000000000..2e32b8331
    --- /dev/null
    +++ b/src/jsPlumb.js
    @@ -0,0 +1,2931 @@
    +/*
    + * jsPlumb
    + * 
    + * Title:jsPlumb 1.4.0
    + * 
    + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
    + * elements, or VML.  
    + * 
    + * This file contains the jsPlumb core code.
    + *
    + * Copyright (c) 2010 - 2013 Simon Porritt (simon.porritt@gmail.com)
    + * 
    + * http://jsplumb.org
    + * http://github.com/sporritt/jsplumb
    + * http://code.google.com/p/jsplumb
    + * 
    + * Dual licensed under the MIT and GPL2 licenses.
    + */
    +
    +;(function() {
    +			
    +    var _findWithFunction = jsPlumbUtil.findWithFunction,
    +	_indexOf = jsPlumbUtil.indexOf,
    +    _removeWithFunction = jsPlumbUtil.removeWithFunction,
    +    _remove = jsPlumbUtil.remove,
    +    // TODO support insert index
    +    _addWithFunction = jsPlumbUtil.addWithFunction,
    +    _addToList = jsPlumbUtil.addToList,
    +	/**
    +		an isArray function that even works across iframes...see here:
    +		
    +		http://tobyho.com/2011/01/28/checking-types-in-javascript/
    +
    +		i was originally using "a.constructor == Array" as a test.
    +	*/
    +	_isArray = jsPlumbUtil.isArray,
    +	_isString = jsPlumbUtil.isString,
    +	_isObject = jsPlumbUtil.isObject;
    +		
    +	var _att = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_gel(el), attName); },
    +		_setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_gel(el), attName, attValue); },
    +		_addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_gel(el), clazz); },
    +		_hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_gel(el), clazz); },
    +		_removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_gel(el), clazz); },
    +		_gel = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); },
    +		_getOffset = function(el, _instance) {
    +            var o = jsPlumb.CurrentLibrary.getOffset(_gel(el));
    +			if (_instance != null) {
    +                var z = _instance.getZoom();
    +                return {left:o.left / z, top:o.top / z };    
    +            }
    +            else
    +                return o;
    +        },		
    +		_getSize = function(el) {
    +            return jsPlumb.CurrentLibrary.getSize(_gel(el));
    +        },
    +		_log = jsPlumbUtil.log,
    +		_group = jsPlumbUtil.group,
    +		_groupEnd = jsPlumbUtil.groupEnd,
    +		_time = jsPlumbUtil.time,
    +		_timeEnd = jsPlumbUtil.timeEnd,
    +		
    +		/**
    +		 * creates a timestamp, using milliseconds since 1970, but as a string.
    +		 */
    +		_timestamp = function() { return "" + (new Date()).getTime(); },
    +		
    +		/*
    +		 * Class:jsPlumbUIComponent
    +		 * Abstract superclass for UI components Endpoint and Connection.  Provides the abstraction of paintStyle/hoverPaintStyle,
    +		 * and also extends jsPlumbUtil.EventGenerator to provide the bind and fire methods.
    +		 */
    +		jsPlumbUIComponent = window.jsPlumbUIComponent = function(params) {
    +			var self = this, 
    +				a = arguments, 
    +				_hover = false, 
    +				parameters = params.parameters || {}, 
    +				idPrefix = self.idPrefix,
    +				id = idPrefix + (new Date()).getTime(),
    +				paintStyle = null,
    +				hoverPaintStyle = null;
    +
    +			self._jsPlumb = params["_jsPlumb"];			
    +			self.getId = function() { return id; };			
    +			self.hoverClass = params.hoverClass || self._jsPlumb.Defaults.HoverClass || jsPlumb.Defaults.HoverClass;				
    +			
    +			// all components can generate events
    +			jsPlumbUtil.EventGenerator.apply(this);
    +			if (params.events) {
    +				for (var i in params.events)
    +					self.bind(i, params.events[i]);
    +			}
    +
    +			// all components get this clone function.
    +			// TODO issue 116 showed a problem with this - it seems 'a' that is in
    +			// the clone function's scope is shared by all invocations of it, the classic
    +			// JS closure problem.  for now, jsPlumb does a version of this inline where 
    +			// it used to call clone.  but it would be nice to find some time to look
    +			// further at this.
    +			this.clone = function() {
    +				var o = new Object();
    +				self.constructor.apply(o, a);
    +				return o;
    +			};
    +			
    +			this.getParameter = function(name) { return parameters[name]; },
    +			this.getParameters = function() { 
    +				return parameters; 
    +			},
    +			this.setParameter = function(name, value) { parameters[name] = value; },
    +			this.setParameters = function(p) { parameters = p; },			
    +			this.overlayPlacements = [];			
    +			
    +			// user can supply a beforeDetach callback, which will be executed before a detach
    +			// is performed; returning false prevents the detach.
    +			var beforeDetach = params.beforeDetach;
    +			this.isDetachAllowed = function(connection) {
    +				var r = true;
    +				if (beforeDetach) {
    +					try { 
    +						r = beforeDetach(connection); 
    +					}
    +					catch (e) { _log("jsPlumb: beforeDetach callback failed", e); }
    +				}
    +				return r;
    +			};
    +			
    +			// user can supply a beforeDrop callback, which will be executed before a dropped
    +			// connection is confirmed. user can return false to reject connection.
    +			var beforeDrop = params.beforeDrop;
    +			this.isDropAllowed = function(sourceId, targetId, scope, connection, dropEndpoint) {
    +				var r = self._jsPlumb.checkCondition("beforeDrop", { 
    +					sourceId:sourceId, 
    +					targetId:targetId, 
    +					scope:scope,
    +					connection:connection,
    +					dropEndpoint:dropEndpoint 
    +				});
    +				if (beforeDrop) {
    +					try { 
    +						r = beforeDrop({ 
    +							sourceId:sourceId, 
    +							targetId:targetId, 
    +							scope:scope, 
    +							connection:connection,
    +							dropEndpoint:dropEndpoint
    +						}); 
    +					}
    +					catch (e) { _log("jsPlumb: beforeDrop callback failed", e); }
    +				}
    +				return r;
    +			};
    +									
    +			// helper method to update the hover style whenever it, or paintStyle, changes.
    +			// we use paintStyle as the foundation and merge hoverPaintStyle over the
    +			// top.
    +			var _updateHoverStyle = function() {
    +				if (paintStyle && hoverPaintStyle) {
    +					var mergedHoverStyle = {};
    +					jsPlumb.extend(mergedHoverStyle, paintStyle);
    +					jsPlumb.extend(mergedHoverStyle, hoverPaintStyle);
    +					delete self["hoverPaintStyle"];
    +					// we want the fillStyle of paintStyle to override a gradient, if possible.
    +					if (mergedHoverStyle.gradient && paintStyle.fillStyle)
    +						delete mergedHoverStyle["gradient"];
    +					hoverPaintStyle = mergedHoverStyle;
    +				}
    +			};
    +			
    +			/*
    +		     * Sets the paint style and then repaints the element.
    +		     * 
    +		     * Parameters:
    +		     * 	style - Style to use.
    +		     */
    +		    this.setPaintStyle = function(style, doNotRepaint) {
    +		    	paintStyle = style;
    +		    	self.paintStyleInUse = paintStyle;
    +		    	_updateHoverStyle();
    +		    	if (!doNotRepaint) self.repaint();
    +		    };
    +
    +		    /**
    +		    * Gets the component's paint style.
    +		    *
    +		    * Returns:
    +		    * the component's paint style. if there is no hoverPaintStyle set then this will be the paint style used all the time, otherwise this is the style used when the mouse is not hovering.
    +		    */
    +		    this.getPaintStyle = function() {
    +		    	return paintStyle;
    +		    };
    +		    
    +		    /*
    +		     * Sets the paint style to use when the mouse is hovering over the element. This is null by default.
    +		     * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace
    +		     * it.  This is because people will most likely want to change just one thing when hovering, say the
    +		     * color for example, but leave the rest of the appearance the same.
    +		     * 
    +		     * Parameters:
    +		     * 	style - Style to use when the mouse is hovering.
    +		     *  doNotRepaint - if true, the component will not be repainted.  useful when setting things up initially.
    +		     */
    +		    this.setHoverPaintStyle = function(style, doNotRepaint) {		    	
    +		    	hoverPaintStyle = style;
    +		    	_updateHoverStyle();
    +		    	if (!doNotRepaint) self.repaint();
    +		    };
    +
    +		    /**
    +		    * Gets the component's hover paint style.
    +		    *
    +		    * Returns:
    +		    * the component's hover paint style. may be null.
    +		    */
    +		    this.getHoverPaintStyle = function() {
    +		    	return hoverPaintStyle;
    +		    };
    +		    
    +		    /*
    +		     * sets/unsets the hover state of this element.
    +		     * 
    +		     * Parameters:
    +		     * 	hover - hover state boolean
    +		     * 	ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state.  used mostly to avoid infinite loops.
    +		     */
    +		    this.setHover = function(hover, ignoreAttachedElements, timestamp) {
    +		    	// while dragging, we ignore these events.  this keeps the UI from flashing and
    +		    	// swishing and whatevering.
    +				if (!self._jsPlumb.currentlyDragging && !self._jsPlumb.isHoverSuspended()) {
    +		    
    +			    	_hover = hover;
    +                        
    +                    if (self.canvas != null) {
    +                        if (self.hoverClass != null) {
    +                            if (hover) 
    +                                jpcl.addClass(self.canvas, self.hoverClass);						
    +                            else
    +                                jpcl.removeClass(self.canvas, self.hoverClass);
    +                        }
    +                        
    +                        if (hover) 
    +                            jpcl.addClass(self.canvas, self._jsPlumb.hoverClass);						
    +                        else
    +                            jpcl.removeClass(self.canvas, self._jsPlumb.hoverClass);
    +                    }
    +		   		 	if (hoverPaintStyle != null) {
    +						self.paintStyleInUse = hover ? hoverPaintStyle : paintStyle;
    +						timestamp = timestamp || _timestamp();
    +						self.repaint({timestamp:timestamp, recalc:false});
    +					}
    +					// get the list of other affected elements, if supported by this component.
    +					// for a connection, its the endpoints.  for an endpoint, its the connections! surprise.
    +					if (self.getAttachedElements && !ignoreAttachedElements)
    +						_updateAttachedElements(hover, _timestamp(), self);
    +				}
    +		    };
    +		    
    +		    this.isHover = function() { return _hover; };
    +            
    +            this.bindListeners = function(obj, _self, _hoverFunction) {
    +                obj.bind("click", function(ep, e) { _self.fire("click", _self, e); });
    +                obj.bind("dblclick", function(ep, e) { _self.fire("dblclick", _self, e); });
    +                obj.bind("contextmenu", function(ep, e) { _self.fire("contextmenu", _self, e); });
    +                obj.bind("mouseenter", function(ep, e) {
    +                    if (!_self.isHover()) {
    +                        _hoverFunction(true);
    +                        _self.fire("mouseenter", _self, e);
    +                    }
    +                });
    +                obj.bind("mouseexit", function(ep, e) {
    +                    if (_self.isHover()) {
    +                        _hoverFunction(false);
    +                        _self.fire("mouseexit", _self, e);
    +                    }
    +                });	  
    +                obj.bind("mousedown", function(ep, e) { _self.fire("mousedown", _self, e); });
    +                obj.bind("mouseup", function(ep, e) { _self.fire("mouseup", _self, e); });
    +            };
    +		
    +			var jpcl = jsPlumb.CurrentLibrary,
    +				events = [ "click", "dblclick", "mouseenter", "mouseout", "mousemove", "mousedown", "mouseup", "contextmenu" ],
    +				eventFilters = { "mouseout":"mouseexit" },
    +				bindOne = function(o, c, evt) {
    +					var filteredEvent = eventFilters[evt] || evt;
    +					jpcl.bind(o, evt, function(ee) {
    +						c.fire(filteredEvent, c, ee);
    +					});
    +				},
    +				unbindOne = function(o, evt) {
    +					var filteredEvent = eventFilters[evt] || evt;
    +					jpcl.unbind(o, evt);
    +				};
    +		    
    +		    this.attachListeners = function(o, c) {
    +				for (var i = 0, j = events.length; i < j; i++) {
    +					bindOne(o, c, events[i]); 			
    +				}
    +			};
    +		    
    +		    var _updateAttachedElements = function(state, timestamp, sourceElement) {
    +		    	var affectedElements = self.getAttachedElements();		// implemented in subclasses
    +		    	if (affectedElements) {
    +		    		for (var i = 0, j = affectedElements.length; i < j; i++) {
    +		    			if (!sourceElement || sourceElement != affectedElements[i])
    +		    				affectedElements[i].setHover(state, true, timestamp);			// tell the attached elements not to inform their own attached elements.
    +		    		}
    +		    	}
    +		    };
    +		    
    +		    this.reattachListenersForElement = function(o) {
    +			    if (arguments.length > 1) {
    +		    		for (var i = 0, j = events.length; i < j; i++)
    +		    			unbindOne(o, events[i]);
    +			    	for (var i = 1, j = arguments.length; i < j; i++)
    +		    			self.attachListeners(o, arguments[i]);
    +		    	}
    +		    };		    	    
    +			
    +			/*
    +			 * TYPES
    +			 */
    +			var _types = [],
    +				_splitType = function(t) { return t == null ? null : t.split(" ")},				
    +				_applyTypes = function(params, doNotRepaint) {
    +					if (self.getDefaultType) {
    +						var td = self.getTypeDescriptor();
    +							
    +						var o = jsPlumbUtil.merge({}, self.getDefaultType());
    +						for (var i = 0, j = _types.length; i < j; i++)
    +							o = jsPlumbUtil.merge(o, self._jsPlumb.getType(_types[i], td));						
    +							
    +						if (params) {
    +							o = jsPlumbUtil.populate(o, params);
    +						}
    +					
    +						self.applyType(o, doNotRepaint);					
    +						if (!doNotRepaint) self.repaint();
    +					}
    +				};
    +			
    +			/*
    +				Function: setType	
    +				Sets the type, removing all existing types.
    +			*/
    +			self.setType = function(typeId, params, doNotRepaint) {				
    +				_types = _splitType(typeId) || [];
    +				_applyTypes(params, doNotRepaint);									
    +			};
    +			
    +			/*
    +			 * Function : getType
    +			 * Gets the 'types' of this component.
    +			 */
    +			self.getType = function() {
    +				return _types;
    +			};
    +
    +			/**
    +				Function: reapplyTypes
    +				Reapply all existing types, but with the given new params.
    +			*/
    +			self.reapplyTypes = function(params, doNotRepaint) {
    +				_applyTypes(params, doNotRepaint);
    +			};
    +			
    +			self.hasType = function(typeId) {
    +				return jsPlumbUtil.indexOf(_types, typeId) != -1;
    +			};
    +			
    +			/*
    +				Function: addType
    +				adds a type. will not be re-added it already exists.
    +			*/
    +			self.addType = function(typeId, params, doNotRepaint) {
    +				var t = _splitType(typeId), _cont = false;
    +				if (t != null) {
    +					for (var i = 0, j = t.length; i < j; i++) {
    +						if (!self.hasType(t[i])) {
    +							_types.push(t[i]);
    +							_cont = true;						
    +						}
    +					}
    +					if (_cont) _applyTypes(params, doNotRepaint);
    +				}
    +			};
    +			
    +			self.removeType = function(typeId, doNotRepaint) {
    +				var t = _splitType(typeId), _cont = false, _one = function(tt) {
    +					var idx = jsPlumbUtil.indexOf(_types, tt);
    +					if (idx != -1) {
    +						_types.splice(idx, 1);
    +						return true;
    +					}
    +					return false;
    +				};
    +				
    +				if (t != null) {
    +					for (var i = 0,j = t.length; i < j; i++) {
    +						_cont = _one(t[i]) || _cont;
    +					}
    +					if (_cont) _applyTypes(null, doNotRepaint);
    +				}
    +			};
    +			
    +			self.toggleType = function(typeId, params, doNotRepaint) {
    +				var t = _splitType(typeId);
    +				if (t != null) {
    +					for (var i = 0, j = t.length; i < j; i++) {
    +						var idx = jsPlumbUtil.indexOf(_types, t[i]);
    +						if (idx != -1)
    +							_types.splice(idx, 1);
    +						else
    +							_types.push(t[i]);
    +					}
    +						
    +					_applyTypes(params, doNotRepaint);
    +				}
    +			};
    +			
    +			this.applyType = function(t, doNotRepaint) {
    +				self.setPaintStyle(t.paintStyle, doNotRepaint);				
    +				self.setHoverPaintStyle(t.hoverPaintStyle, doNotRepaint);
    +				if (t.parameters){
    +					for (var i in t.parameters)
    +						self.setParameter(i, t.parameters[i]);
    +				}
    +			};
    +            
    +            // CSS classes
    +            this.addClass = function(clazz) {
    +                if (self.canvas != null)
    +                    _addClass(self.canvas, clazz);
    +            };
    +			
    +            this.removeClass = function(clazz) {
    +                if (self.canvas != null)
    +                    _removeClass(self.canvas, clazz);
    +            };                    
    +		},
    +
    +		overlayCapableJsPlumbUIComponent = window.overlayCapableJsPlumbUIComponent = function(params) {
    +			jsPlumbUIComponent.apply(this, arguments);
    +			var self = this;			
    +			this.overlays = [];
    +
    +			var processOverlay = function(o) {
    +				var _newOverlay = null;
    +				if (_isArray(o)) {	// this is for the shorthand ["Arrow", { width:50 }] syntax
    +					// there's also a three arg version:
    +					// ["Arrow", { width:50 }, {location:0.7}] 
    +					// which merges the 3rd arg into the 2nd.
    +					var type = o[0],
    +						// make a copy of the object so as not to mess up anyone else's reference...
    +						p = jsPlumb.extend({component:self, _jsPlumb:self._jsPlumb}, o[1]);
    +					if (o.length == 3) jsPlumb.extend(p, o[2]);
    +					_newOverlay = new jsPlumb.Overlays[self._jsPlumb.getRenderMode()][type](p);					
    +				} else if (o.constructor == String) {
    +					_newOverlay = new jsPlumb.Overlays[self._jsPlumb.getRenderMode()][o]({component:self, _jsPlumb:self._jsPlumb});
    +				} else {
    +					_newOverlay = o;
    +				}										
    +					
    +				self.overlays.push(_newOverlay);
    +			},
    +			calculateOverlaysToAdd = function(params) {
    +				var defaultKeys = self.defaultOverlayKeys || [],
    +					o = params.overlays,
    +					checkKey = function(k) {
    +						return self._jsPlumb.Defaults[k] || jsPlumb.Defaults[k] || [];
    +					};
    +				
    +				if (!o) o = [];
    +
    +				for (var i = 0, j = defaultKeys.length; i < j; i++)
    +					o.unshift.apply(o, checkKey(defaultKeys[i]));
    +				
    +				return o;
    +			}
    +
    +			var _overlays = calculateOverlaysToAdd(params);
    +			if (_overlays) {
    +				for (var i = 0, j = _overlays.length; i < j; i++) {
    +					processOverlay(_overlays[i]);
    +				}
    +			}
    +
    +		    // overlay finder helper method
    +			var _getOverlayIndex = function(id) {
    +				var idx = -1;
    +				for (var i = 0, j = self.overlays.length; i < j; i++) {
    +					if (id === self.overlays[i].id) {
    +						idx = i;
    +						break;
    +					}
    +				}
    +				return idx;
    +			};
    +						
    +			this.addOverlay = function(overlay, doNotRepaint) { 
    +				processOverlay(overlay); 
    +				if (!doNotRepaint) self.repaint();
    +			};
    +						
    +			this.getOverlay = function(id) {
    +				var idx = _getOverlayIndex(id);
    +				return idx >= 0 ? self.overlays[idx] : null;
    +			};
    +			
    +			this.getOverlays = function() {
    +				return self.overlays;
    +			};			
    +			
    +			this.hideOverlay = function(id) {
    +				var o = self.getOverlay(id);
    +				if (o) o.hide();
    +			};
    +
    +			this.hideOverlays = function() {
    +				for (var i = 0, j = self.overlays.length; i < j; i++)
    +					self.overlays[i].hide();
    +			};
    +						
    +			this.showOverlay = function(id) {
    +				var o = self.getOverlay(id);
    +				if (o) o.show();
    +			};
    +
    +			this.showOverlays = function() {
    +				for (var i = 0, j = self.overlays.length; i < j; i++)
    +					self.overlays[i].show();
    +			};
    +			
    +			this.removeAllOverlays = function() {
    +				for (var i = 0, j = self.overlays.length; i < j; i++) {
    +					if (self.overlays[i].cleanup) self.overlays[i].cleanup();
    +				}
    +
    +				self.overlays.splice(0, self.overlays.length);
    +				self.repaint();
    +			};
    +						
    +			this.removeOverlay = function(overlayId) {
    +				var idx = _getOverlayIndex(overlayId);
    +				if (idx != -1) {
    +					var o = self.overlays[idx];
    +					if (o.cleanup) o.cleanup();
    +					self.overlays.splice(idx, 1);
    +				}
    +			};
    +						
    +			this.removeOverlays = function() {
    +				for (var i = 0, j = arguments.length; i < j; i++)
    +					self.removeOverlay(arguments[i]);
    +			};
    +
    +			// this is a shortcut helper method to let people add a label as
    +			// overlay.			
    +			var _internalLabelOverlayId = "__label",
    +			_makeLabelOverlay = function(params) {
    +
    +				var _params = {
    +					cssClass:params.cssClass,
    +					labelStyle : this.labelStyle,					
    +					id:_internalLabelOverlayId,
    +					component:self,
    +					_jsPlumb:self._jsPlumb
    +				},
    +				mergedParams = jsPlumb.extend(_params, params);
    +
    +				return new jsPlumb.Overlays[self._jsPlumb.getRenderMode()].Label( mergedParams );
    +			};
    +			if (params.label) {
    +				var loc = params.labelLocation || self.defaultLabelLocation || 0.5,
    +					labelStyle = params.labelStyle || self._jsPlumb.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle;			
    +				this.overlays.push(_makeLabelOverlay({
    +					label:params.label,
    +					location:loc,
    +					labelStyle:labelStyle
    +				}));
    +			}
    +			
    +			this.setLabel = function(l) {
    +				var lo = self.getOverlay(_internalLabelOverlayId);
    +				if (!lo) {
    +					var params = l.constructor == String || l.constructor == Function ? { label:l } : l;
    +					lo = _makeLabelOverlay(params);	
    +					this.overlays.push(lo);
    +				}
    +				else {
    +					if (l.constructor == String || l.constructor == Function) lo.setLabel(l);
    +					else {
    +						if (l.label) lo.setLabel(l.label);
    +						if (l.location) lo.setLocation(l.location);
    +					}
    +				}
    +				
    +				if (!self._jsPlumb.isSuspendDrawing()) 
    +					self.repaint();
    +			};
    +
    +			
    +			this.getLabel = function() {
    +				var lo = self.getOverlay(_internalLabelOverlayId);
    +				return lo != null ? lo.getLabel() : null;
    +			};
    +
    +			
    +			this.getLabelOverlay = function() {
    +				return self.getOverlay(_internalLabelOverlayId);
    +			};
    +			
    +			var superAt = this.applyType;
    +			this.applyType = function(t, doNotRepaint) {
    +				superAt(t, doNotRepaint);
    +				self.removeAllOverlays();
    +				if (t.overlays) {
    +					for (var i = 0, j = t.overlays.length; i < j; i++)
    +						self.addOverlay(t.overlays[i], true);
    +				}
    +			};
    +            
    +            var superHover = this.setHover;
    +            this.setHover = function(hover, ignoreAttachedElements, timestamp) {
    +                superHover.apply(self, arguments);    
    +                for (var i = 0, j = self.overlays.length; i < j; i++) {
    +					self.overlays[i][hover ? "addClass":"removeClass"](self._jsPlumb.hoverClass);
    +				}
    +            };
    +		};		
    +		
    +		var _jsPlumbInstanceIndex = 0,
    +			getInstanceIndex = function() {
    +				var i = _jsPlumbInstanceIndex + 1;
    +				_jsPlumbInstanceIndex++;
    +				return i;
    +			};
    +
    +		var jsPlumbInstance = function(_defaults) {
    +		
    +		
    +		this.Defaults = {
    +			Anchor : "BottomCenter",
    +			Anchors : [ null, null ],
    +            ConnectionsDetachable : true,
    +            ConnectionOverlays : [ ],
    +            Connector : "Bezier",
    +			Container : null,
    +			DoNotThrowErrors:false,
    +			DragOptions : { },
    +			DropOptions : { },
    +			Endpoint : "Dot",
    +			EndpointOverlays : [ ],
    +			Endpoints : [ null, null ],
    +			EndpointStyle : { fillStyle : "#456" },
    +			EndpointStyles : [ null, null ],
    +			EndpointHoverStyle : null,
    +			EndpointHoverStyles : [ null, null ],
    +			HoverPaintStyle : null,
    +			LabelStyle : { color : "black" },
    +			LogEnabled : false,
    +			Overlays : [ ],
    +			MaxConnections : 1, 
    +			PaintStyle : { lineWidth : 8, strokeStyle : "#456" },            
    +			ReattachConnections:false,
    +			RenderMode : "svg",
    +			Scope : "jsPlumb_DefaultScope"
    +		};
    +		if (_defaults) jsPlumb.extend(this.Defaults, _defaults);
    +		
    +		this.logEnabled = this.Defaults.LogEnabled;
    +		
    +		var _connectionTypes = { }, _endpointTypes = {};
    +		this.registerConnectionType = function(id, type) {
    +			_connectionTypes[id] = jsPlumb.extend({}, type);
    +		};
    +		this.registerConnectionTypes = function(types) {
    +			for (var i in types)
    +				_connectionTypes[i] = jsPlumb.extend({}, types[i]);
    +		};
    +		this.registerEndpointType = function(id, type) {
    +			_endpointTypes[id] = jsPlumb.extend({}, type);
    +		};
    +		this.registerEndpointTypes = function(types) {
    +			for (var i in types)
    +				_endpointTypes[i] = jsPlumb.extend({}, types[i]);
    +		};
    +		this.getType = function(id, typeDescriptor) {
    +			return typeDescriptor ===  "connection" ? _connectionTypes[id] : _endpointTypes[id];
    +		};
    +
    +		jsPlumbUtil.EventGenerator.apply(this);
    +		var _currentInstance = this,
    +			_instanceIndex = getInstanceIndex(),
    +			_bb = _currentInstance.bind,
    +			_initialDefaults = {},
    +            _zoom = 1;
    +            
    +        this.getInstanceIndex = function() {
    +            return _instanceIndex;
    +        };
    +            
    +        this.setZoom = function(z, repaintEverything) {
    +            _zoom = z;
    +            if (repaintEverything) _currentInstance.repaintEverything();
    +        };
    +        this.getZoom = function() { return _zoom; };
    +                        
    +		for (var i in this.Defaults)
    +			_initialDefaults[i] = this.Defaults[i];
    +
    +		this.bind = function(event, fn) {		
    +			if ("ready" === event && initialized) fn();
    +			else _bb.apply(_currentInstance,[event, fn]);
    +		};
    +
    +		_currentInstance.importDefaults = function(d) {
    +			for (var i in d) {
    +				_currentInstance.Defaults[i] = d[i];
    +			}	
    +		};
    +		
    +		_currentInstance.restoreDefaults = function() {
    +			_currentInstance.Defaults = jsPlumb.extend({}, _initialDefaults);
    +		};
    +		
    +    var log = null,
    +        resizeTimer = null,
    +        initialized = false,
    +        _connectionBeingDragged = null,        
    +        connectionsByScope = {},
    +        /**
    +         * map of element id -> endpoint lists. an element can have an arbitrary
    +         * number of endpoints on it, and not all of them have to be connected
    +         * to anything.
    +         */
    +        endpointsByElement = {},
    +        endpointsByUUID = {},
    +        offsets = {},
    +        offsetTimestamps = {},
    +        floatingConnections = {},
    +        draggableStates = {},		
    +        canvasList = [],
    +        sizes = [],
    +        //listeners = {}, // a map: keys are event types, values are lists of listeners.
    +        DEFAULT_SCOPE = this.Defaults.Scope,
    +        renderMode = null,  // will be set in init()							
    +		
    +
    +		/**
    +		 * appends an element to some other element, which is calculated as follows:
    +		 * 
    +		 * 1. if _currentInstance.Defaults.Container exists, use that element.
    +		 * 2. if the 'parent' parameter exists, use that.
    +		 * 3. otherwise just use the root element (for DOM usage, the document body).
    +		 * 
    +		 */
    +		_appendElement = function(el, parent) {
    +			if (_currentInstance.Defaults.Container)
    +				jsPlumb.CurrentLibrary.appendElement(el, _currentInstance.Defaults.Container);
    +			else if (!parent)
    +				jsPlumbAdapter.appendToRoot(el);
    +			else
    +				jsPlumb.CurrentLibrary.appendElement(el, parent);
    +		},
    +
    +		_curIdStamp = 1,
    +		_idstamp = function() { return "" + _curIdStamp++; },		
    +		
    +		/**
    +		 * YUI, for some reason, put the result of a Y.all call into an object that contains
    +		 * a '_nodes' array, instead of handing back an array-like object like the other
    +		 * libraries do.
    +		 */
    +		_convertYUICollection = function(c) {
    +			return c._nodes ? c._nodes : c;
    +		},                
    +
    +		/**
    +		 * Draws an endpoint and its connections. this is the main entry point into drawing connections as well
    +		 * as endpoints, since jsPlumb is endpoint-centric under the hood.
    +		 * 
    +		 * @param element element to draw (of type library specific element object)
    +		 * @param ui UI object from current library's event system. optional.
    +		 * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do.
    +		 */
    +		_draw = function(element, ui, timestamp, clearEdits) {
    +			
    +			// TODO is it correct to filter by headless at this top level? how would a headless adapter ever repaint?
    +            if (!jsPlumbAdapter.headless && !_suspendDrawing) {
    +			    var id = _att(element, "id"),
    +			    	repaintEls = _currentInstance.dragManager.getElementsForDraggable(id);			    
    +
    +			    if (timestamp == null) timestamp = _timestamp();
    +
    +			    _currentInstance.anchorManager.redraw(id, ui, timestamp, null, clearEdits);
    +			    if (repaintEls) {
    +				    for (var i in repaintEls) {
    +						_currentInstance.anchorManager.redraw(repaintEls[i].id, ui, timestamp, repaintEls[i].offset, clearEdits);			    	
    +				    }
    +				}
    +            }
    +		},
    +
    +		/**
    +		 * executes the given function against the given element if the first
    +		 * argument is an object, or the list of elements, if the first argument
    +		 * is a list. the function passed in takes (element, elementId) as
    +		 * arguments.
    +		 */
    +		_elementProxy = function(element, fn) {
    +			var retVal = null;
    +			if (_isArray(element)) {
    +				retVal = [];
    +				for ( var i = 0, j = element.length; i < j; i++) {
    +					var el = _gel(element[i]), id = _att(el, "id");
    +					retVal.push(fn(el, id)); // append return values to what we will return
    +				}
    +			} else {
    +				var el = _gel(element), id = _att(el, "id");
    +				retVal = fn(el, id);
    +			}
    +			return retVal;
    +		},				
    +
    +		/**
    +		 * gets an Endpoint by uuid.
    +		 */
    +		_getEndpoint = function(uuid) { return endpointsByUUID[uuid]; },
    +
    +		/**
    +		 * inits a draggable if it's not already initialised.
    +		 */
    +		_initDraggableIfNecessary = function(element, isDraggable, dragOptions) {
    +			// TODO move to DragManager?
    +			if (!jsPlumbAdapter.headless) {
    +				var draggable = isDraggable == null ? false : isDraggable, jpcl = jsPlumb.CurrentLibrary;
    +				if (draggable) {
    +					if (jpcl.isDragSupported(element) && !jpcl.isAlreadyDraggable(element)) {
    +						var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions;
    +						options = jsPlumb.extend( {}, options); // make a copy.
    +						var dragEvent = jpcl.dragEvents["drag"],
    +							stopEvent = jpcl.dragEvents["stop"],
    +							startEvent = jpcl.dragEvents["start"];
    +	
    +						options[startEvent] = _wrap(options[startEvent], function() {
    +							_currentInstance.setHoverSuspended(true);							
    +							_currentInstance.select({source:element}).addClass(_currentInstance.elementDraggingClass + " " + _currentInstance.sourceElementDraggingClass, true);
    +							_currentInstance.select({target:element}).addClass(_currentInstance.elementDraggingClass + " " + _currentInstance.targetElementDraggingClass, true);
    +						});
    +	
    +						options[dragEvent] = _wrap(options[dragEvent], function() {                            
    +							var ui = jpcl.getUIPosition(arguments, _currentInstance.getZoom());
    +							_draw(element, ui, null, true);
    +							_addClass(element, "jsPlumb_dragged");
    +						});
    +						options[stopEvent] = _wrap(options[stopEvent], function() {
    +							var ui = jpcl.getUIPosition(arguments, _currentInstance.getZoom());
    +							_draw(element, ui);
    +							_removeClass(element, "jsPlumb_dragged");
    +							_currentInstance.setHoverSuspended(false);							
    +							_currentInstance.select({source:element}).removeClass(_currentInstance.elementDraggingClass + " " + _currentInstance.sourceElementDraggingClass, true);
    +							_currentInstance.select({target:element}).removeClass(_currentInstance.elementDraggingClass + " " + _currentInstance.targetElementDraggingClass, true);
    +						});
    +						var elId = _getId(element); // need ID
    +						draggableStates[elId] = true;  
    +						var draggable = draggableStates[elId];
    +						options.disabled = draggable == null ? false : !draggable;
    +						jpcl.initDraggable(element, options, false, _currentInstance);
    +						_currentInstance.dragManager.register(element);
    +					}
    +				}
    +			}
    +		},
    +		
    +		/*
    +		* prepares a final params object that can be passed to _newConnection, taking into account defaults, events, etc.
    +		*/
    +		_prepareConnectionParams = function(params, referenceParams) {
    +			var _p = jsPlumb.extend( {
    +				sourceIsNew:true,
    +				targetIsNew:true
    +			}, params);
    +			if (referenceParams) jsPlumb.extend(_p, referenceParams);
    +			
    +			// hotwire endpoints passed as source or target to sourceEndpoint/targetEndpoint, respectively.
    +			if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source;
    +			if (_p.target && _p.target.endpoint) _p.targetEndpoint = _p.target;
    +			
    +			// test for endpoint uuids to connect
    +			if (params.uuids) {
    +				_p.sourceEndpoint = _getEndpoint(params.uuids[0]);
    +				_p.targetEndpoint = _getEndpoint(params.uuids[1]);
    +			}						
    +
    +			// now ensure that if we do have Endpoints already, they're not full.
    +			// source:
    +			if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) {
    +				_log(_currentInstance, "could not add connection; source endpoint is full");
    +				return;
    +			}
    +
    +			// target:
    +			if (_p.targetEndpoint && _p.targetEndpoint.isFull()) {
    +				_log(_currentInstance, "could not add connection; target endpoint is full");
    +				return;
    +			}
    +			
    +			// at this point, if we have source or target Endpoints, they were not new and we should mark the
    +			// flag to reflect that.  this is for use later with the deleteEndpointsOnDetach flag.
    +			if (_p.sourceEndpoint) _p.sourceIsNew = false;
    +			if (_p.targetEndpoint) _p.targetIsNew = false;
    +			
    +			// if source endpoint mandates connection type and nothing specified in our params, use it.
    +			if (!_p.type && _p.sourceEndpoint)
    +				_p.type = _p.sourceEndpoint.connectionType;
    +			
    +			// copy in any connectorOverlays that were specified on the source endpoint.
    +			// it doesnt copy target endpoint overlays.  i'm not sure if we want it to or not.
    +			if (_p.sourceEndpoint && _p.sourceEndpoint.connectorOverlays) {
    +				_p.overlays = _p.overlays || [];
    +				for (var i = 0, j = _p.sourceEndpoint.connectorOverlays.length; i < j; i++) {
    +					_p.overlays.push(_p.sourceEndpoint.connectorOverlays[i]);
    +				}
    +			}		
    +            
    +            // pointer events
    +            if (!_p["pointer-events"] && _p.sourceEndpoint && _p.sourceEndpoint.connectorPointerEvents)
    +                _p["pointer-events"] = _p.sourceEndpoint.connectorPointerEvents;
    +						
    +			
    +			// if there's a target specified (which of course there should be), and there is no
    +			// target endpoint specified, and 'newConnection' was not set to true, then we check to
    +			// see if a prior call to makeTarget has provided us with the specs for the target endpoint, and
    +			// we use those if so.  additionally, if the makeTarget call was specified with 'uniqueEndpoint' set
    +			// to true, then if that target endpoint has already been created, we re-use it.
    +			if (_p.target && !_p.target.endpoint && !_p.targetEndpoint && !_p.newConnection) {				
    +				var tid = _getId(_p.target),
    +					tep =_targetEndpointDefinitions[tid],
    +					existingUniqueEndpoint = _targetEndpoints[tid];				
    +
    +				if (tep) {			
    +					// if target not enabled, return.
    +					if (!_targetsEnabled[tid]) return;
    +
    +					// check for max connections??						
    +					var newEndpoint = existingUniqueEndpoint != null ? existingUniqueEndpoint : _currentInstance.addEndpoint(_p.target, tep);
    +					if (_targetEndpointsUnique[tid]) _targetEndpoints[tid] = newEndpoint;
    +					 _p.targetEndpoint = newEndpoint;
    +					 newEndpoint._makeTargetCreator = true;
    +					 _p.targetIsNew = true;
    +				}
    +			}
    +
    +			// same thing, but for source.
    +			if (_p.source && !_p.source.endpoint && !_p.sourceEndpoint && !_p.newConnection) {
    +				var tid = _getId(_p.source),
    +					tep = _sourceEndpointDefinitions[tid],
    +					existingUniqueEndpoint = _sourceEndpoints[tid];				
    +
    +				if (tep) {
    +					// if source not enabled, return.					
    +					if (!_sourcesEnabled[tid]) return;
    +				
    +					var newEndpoint = existingUniqueEndpoint != null ? existingUniqueEndpoint : _currentInstance.addEndpoint(_p.source, tep);
    +					if (_sourceEndpointsUnique[tid]) _sourceEndpoints[tid] = newEndpoint;
    +					 _p.sourceEndpoint = newEndpoint;
    +					 _p.sourceIsNew = true;
    +				}
    +			}
    +			
    +			return _p;
    +		},
    +		
    +		_newConnection = function(params) {
    +			var connectionFunc = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(),
    +			    endpointFunc = _currentInstance.Defaults.EndpointType || jsPlumb.Endpoint,
    +			    parent = jsPlumb.CurrentLibrary.getParent;
    +			
    +			if (params.container)
    +				params["parent"] = params.container;
    +			else {
    +				if (params.sourceEndpoint)
    +					params["parent"] = params.sourceEndpoint.parent;
    +				else if (params.source.constructor == endpointFunc)
    +					params["parent"] = params.source.parent;
    +				else params["parent"] = parent(params.source);
    +			}
    +			
    +			params["_jsPlumb"] = _currentInstance;
    +            params.newConnection = _newConnection;
    +            params.newEndpoint = _newEndpoint;
    +            params.endpointsByUUID = endpointsByUUID;             
    +            params.endpointsByElement = endpointsByElement;  
    +            params.finaliseConnection = _finaliseConnection;
    +			var con = new connectionFunc(params);
    +			con.id = "con_" + _idstamp();
    +			_eventFireProxy("click", "click", con);
    +			_eventFireProxy("dblclick", "dblclick", con);
    +            _eventFireProxy("contextmenu", "contextmenu", con);
    +			return con;
    +		},
    +		
    +		/**
    +		* adds the connection to the backing model, fires an event if necessary and then redraws
    +		*/
    +		_finaliseConnection = function(jpc, params, originalEvent) {
    +            params = params || {};
    +			// add to list of connections (by scope).
    +            if (!jpc.suspendedEndpoint)
    +			    _addToList(connectionsByScope, jpc.scope, jpc);					
    +			
    +            // always inform the anchor manager
    +            // except that if jpc has a suspended endpoint it's not true to say the
    +            // connection is new; it has just (possibly) moved. the question is whether
    +            // to make that call here or in the anchor manager.  i think perhaps here.
    +            _currentInstance.anchorManager.newConnection(jpc);
    +			// force a paint
    +			_draw(jpc.source);
    +			
    +			// fire an event
    +			if (!params.doNotFireConnectionEvent && params.fireEvent !== false) {
    +			
    +				var eventArgs = {
    +					connection:jpc,
    +					source : jpc.source, target : jpc.target,
    +					sourceId : jpc.sourceId, targetId : jpc.targetId,
    +					sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1]
    +				};
    +			
    +				_currentInstance.fire("jsPlumbConnection", eventArgs, originalEvent);
    +				// this is from 1.3.11 onwards. "jsPlumbConnection" always felt so unnecessary, so
    +				// I've added this alias in 1.3.11, with a view to removing "jsPlumbConnection" completely in a future version. be aware, of course, you should only register listeners for one or the other of these events.
    +				_currentInstance.fire("connection", eventArgs, originalEvent);
    +			}
    +		},
    +		
    +		_eventFireProxy = function(event, proxyEvent, obj) {
    +			obj.bind(event, function(originalObject, originalEvent) {
    +				_currentInstance.fire(proxyEvent, obj, originalEvent);
    +			});
    +		},
    +		
    +		/**
    +		 * for the given endpoint params, returns an appropriate parent element for the UI elements that will be added.
    +		 * this function is used by _newEndpoint (directly below), and also in the makeSource function in jsPlumb.
    +		 * 
    +		 *   the logic is to first look for a "container" member of params, and pass that back if found.  otherwise we
    +		 *   handoff to the 'getParent' function in the current library.
    +		 */
    +		_getParentFromParams = function(params) {
    +			if (params.container)
    +				return params.container;
    +			else {
    +                var tag = jsPlumb.CurrentLibrary.getTagName(params.source),
    +                    p = jsPlumb.CurrentLibrary.getParent(params.source);
    +                if (tag && tag.toLowerCase() === "td")
    +                    return jsPlumb.CurrentLibrary.getParent(p);
    +                else return p;
    +            }
    +		},
    +		
    +		/**
    +			factory method to prepare a new endpoint.  this should always be used instead of creating Endpoints
    +			manually, since this method attaches event listeners and an id.
    +		*/
    +		_newEndpoint = function(params) {
    +				var endpointFunc = _currentInstance.Defaults.EndpointType || jsPlumb.Endpoint;
    +				var _p = jsPlumb.extend({}, params);				
    +				_p.parent = _getParentFromParams(_p);
    +				_p["_jsPlumb"] = _currentInstance;
    +                _p.newConnection = _newConnection;
    +                _p.newEndpoint = _newEndpoint;                
    +                _p.endpointsByUUID = endpointsByUUID;             
    +                _p.endpointsByElement = endpointsByElement;  
    +                _p.finaliseConnection = _finaliseConnection;
    +                _p.fireDetachEvent = fireDetachEvent;
    +                _p.floatingConnections = floatingConnections;
    +                _p.getParentFromParams = _getParentFromParams;
    +                _p.connectionsByScope = connectionsByScope;
    +				var ep = new endpointFunc(_p);
    +				ep.id = "ep_" + _idstamp();
    +				_eventFireProxy("click", "endpointClick", ep);
    +				_eventFireProxy("dblclick", "endpointDblClick", ep);
    +				_eventFireProxy("contextmenu", "contextmenu", ep);
    +				if (!jsPlumbAdapter.headless)
    +					_currentInstance.dragManager.endpointAdded(params.source);
    +			return ep;
    +		},
    +		
    +		/**
    +		 * performs the given function operation on all the connections found
    +		 * for the given element id; this means we find all the endpoints for
    +		 * the given element, and then for each endpoint find the connectors
    +		 * connected to it. then we pass each connection in to the given
    +		 * function.
    +		 */
    +		_operation = function(elId, func, endpointFunc) {
    +			var endpoints = endpointsByElement[elId];
    +			if (endpoints && endpoints.length) {
    +				for ( var i = 0, ii = endpoints.length; i < ii; i++) {
    +					for ( var j = 0, jj = endpoints[i].connections.length; j < jj; j++) {
    +						var retVal = func(endpoints[i].connections[j]);
    +						// if the function passed in returns true, we exit.
    +						// most functions return false.
    +						if (retVal) return;
    +					}
    +					if (endpointFunc) endpointFunc(endpoints[i]);
    +				}
    +			}
    +		},
    +		/**
    +		 * perform an operation on all elements.
    +		 */
    +		_operationOnAll = function(func) {
    +			for ( var elId in endpointsByElement) {
    +				_operation(elId, func);
    +			}
    +		},		
    +				        
    +		/**
    +		 * Sets whether or not the given element(s) should be draggable,
    +		 * regardless of what a particular plumb command may request.
    +		 * 
    +		 * @param element
    +		 *            May be a string, a element objects, or a list of
    +		 *            strings/elements.
    +		 * @param draggable
    +		 *            Whether or not the given element(s) should be draggable.
    +		 */
    +		_setDraggable = function(element, draggable) {
    +			return _elementProxy(element, function(el, id) {
    +				draggableStates[id] = draggable;
    +				if (jsPlumb.CurrentLibrary.isDragSupported(el)) {
    +					jsPlumb.CurrentLibrary.setDraggable(el, draggable);
    +				}
    +			});
    +		},
    +		/**
    +		 * private method to do the business of hiding/showing.
    +		 * 
    +		 * @param el
    +		 *            either Id of the element in question or a library specific
    +		 *            object for the element.
    +		 * @param state
    +		 *            String specifying a value for the css 'display' property
    +		 *            ('block' or 'none').
    +		 */
    +		_setVisible = function(el, state, alsoChangeEndpoints) {
    +			state = state === "block";
    +			var endpointFunc = null;
    +			if (alsoChangeEndpoints) {
    +				if (state) endpointFunc = function(ep) {
    +					ep.setVisible(true, true, true);
    +				};
    +				else endpointFunc = function(ep) {
    +					ep.setVisible(false, true, true);
    +				};
    +			}
    +			var id = _att(el, "id");
    +			_operation(id, function(jpc) {
    +				if (state && alsoChangeEndpoints) {		
    +					// this test is necessary because this functionality is new, and i wanted to maintain backwards compatibility.
    +					// this block will only set a connection to be visible if the other endpoint in the connection is also visible.
    +					var oidx = jpc.sourceId === id ? 1 : 0;
    +					if (jpc.endpoints[oidx].isVisible()) jpc.setVisible(true);
    +				}
    +				else  // the default behaviour for show, and what always happens for hide, is to just set the visibility without getting clever.
    +					jpc.setVisible(state);
    +			}, endpointFunc);
    +		},
    +		/**
    +		 * toggles the draggable state of the given element(s).
    +		 * 
    +		 * @param el
    +		 *            either an id, or an element object, or a list of
    +		 *            ids/element objects.
    +		 */
    +		_toggleDraggable = function(el) {
    +			return _elementProxy(el, function(el, elId) {
    +				var state = draggableStates[elId] == null ? false : draggableStates[elId];
    +				state = !state;
    +				draggableStates[elId] = state;
    +				jsPlumb.CurrentLibrary.setDraggable(el, state);
    +				return state;
    +			});
    +		},
    +		/**
    +		 * private method to do the business of toggling hiding/showing.
    +		 * 
    +		 * @param elId
    +		 *            Id of the element in question
    +		 */
    +		_toggleVisible = function(elId, changeEndpoints) {
    +			var endpointFunc = null;
    +			if (changeEndpoints) {
    +				endpointFunc = function(ep) {
    +					var state = ep.isVisible();
    +					ep.setVisible(!state);
    +				};
    +			}
    +			_operation(elId, function(jpc) {
    +				var state = jpc.isVisible();
    +				jpc.setVisible(!state);				
    +			}, endpointFunc);
    +			// todo this should call _elementProxy, and pass in the
    +			// _operation(elId, f) call as a function. cos _toggleDraggable does
    +			// that.
    +		},
    +		/**
    +		 * updates the offset and size for a given element, and stores the
    +		 * values. if 'offset' is not null we use that (it would have been
    +		 * passed in from a drag call) because it's faster; but if it is null,
    +		 * or if 'recalc' is true in order to force a recalculation, we get the current values.
    +		 */
    +		_updateOffset = function(params) {
    +			var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId;
    +			if (_suspendDrawing && !timestamp) timestamp = _suspendedAt;
    +			if (!recalc) {
    +				if (timestamp && timestamp === offsetTimestamps[elId])
    +					return {o:offsets[elId], s:sizes[elId]};
    +			}
    +			if (recalc || !offset) { // if forced repaint or no offset available, we recalculate.
    +				// get the current size and offset, and store them
    +				var s = _gel(elId);
    +				if (s != null) {						
    +					sizes[elId] = _getSize(s);
    +					offsets[elId] = _getOffset(s, _currentInstance);
    +					offsetTimestamps[elId] = timestamp;
    +				}
    +			} else {
    +				offsets[elId] = offset;
    +                if (sizes[elId] == null) {
    +                    var s = _gel(elId);
    +                    if (s != null) sizes[elId] = _getSize(s);
    +                }
    +            }
    +			
    +			if(offsets[elId] && !offsets[elId].right) {
    +				offsets[elId].right = offsets[elId].left + sizes[elId][0];
    +				offsets[elId].bottom = offsets[elId].top + sizes[elId][1];	
    +				offsets[elId].width = sizes[elId][0];
    +				offsets[elId].height = sizes[elId][1];	
    +				offsets[elId].centerx = offsets[elId].left + (offsets[elId].width / 2);
    +				offsets[elId].centery = offsets[elId].top + (offsets[elId].height / 2);				
    +			}
    +			//return offsets[elId];
    +            return {o:offsets[elId], s:sizes[elId]};
    +		},
    +
    +		// TODO comparison performance
    +		_getCachedData = function(elId) {
    +			var o = offsets[elId];
    +			if (!o) 
    +                return _updateOffset({elId:elId});
    +			else
    +                return {o:o, s:sizes[elId]};
    +		},
    +
    +		/**
    +		 * gets an id for the given element, creating and setting one if
    +		 * necessary.  the id is of the form
    +		 *
    +		 *	jsPlumb_<instance index>_<index in instance>
    +		 *
    +		 * where "index in instance" is a monotonically increasing integer that starts at 0,
    +		 * for each instance.  this method is used not only to assign ids to elements that do not
    +		 * have them but also to connections and endpoints.
    +		 */
    +		_getId = function(element, uuid, doNotCreateIfNotFound) {
    +			var ele = _gel(element);
    +			var id = _att(ele, "id");
    +			if (!id || id == "undefined") {
    +				// check if fixed uuid parameter is given
    +				if (arguments.length == 2 && arguments[1] != undefined)
    +					id = uuid;
    +				else if (arguments.length == 1 || (arguments.length == 3 && !arguments[2]))
    +					id = "jsPlumb_" + _instanceIndex + "_" + _idstamp();
    +				
    +                if (!doNotCreateIfNotFound) _setAttribute(ele, "id", id);
    +			}
    +			return id;
    +		},		
    +
    +		/**
    +		 * wraps one function with another, creating a placeholder for the
    +		 * wrapped function if it was null. this is used to wrap the various
    +		 * drag/drop event functions - to allow jsPlumb to be notified of
    +		 * important lifecycle events without imposing itself on the user's
    +		 * drag/drop functionality. TODO: determine whether or not we should
    +		 * support an error handler concept, if one of the functions fails.
    +		 * 
    +		 * @param wrappedFunction original function to wrap; may be null.
    +		 * @param newFunction function to wrap the original with.
    +		 * @param returnOnThisValue Optional. Indicates that the wrappedFunction should 
    +		 * not be executed if the newFunction returns a value matching 'returnOnThisValue'.
    +		 * note that this is a simple comparison and only works for primitives right now.
    +		 */
    +        // TODO move to util.
    +		_wrap = function(wrappedFunction, newFunction, returnOnThisValue) {
    +			wrappedFunction = wrappedFunction || function() { };
    +			newFunction = newFunction || function() { };
    +			return function() {
    +				var r = null;
    +				try {
    +					r = newFunction.apply(this, arguments);
    +				} catch (e) {
    +					_log(_currentInstance, "jsPlumb function failed : " + e);
    +				}
    +				if (returnOnThisValue == null || (r !== returnOnThisValue)) {
    +					try {
    +						wrappedFunction.apply(this, arguments);
    +					} catch (e) {
    +						_log(_currentInstance, "wrapped function failed : " + e);
    +					}
    +				}
    +				return r;
    +			};
    +		};	
    +
    +        this.isConnectionBeingDragged = function() { return _connectionBeingDragged != null; };
    +        this.setConnectionBeingDragged = function(c) {_connectionBeingDragged = c; };
    +            
    +		this.connectorClass = "_jsPlumb_connector";            		
    +		this.hoverClass = "_jsPlumb_hover";            		
    +		this.endpointClass = "_jsPlumb_endpoint";		
    +		this.endpointConnectedClass = "_jsPlumb_endpoint_connected";		
    +		this.endpointFullClass = "_jsPlumb_endpoint_full";		
    +		this.endpointDropAllowedClass = "_jsPlumb_endpoint_drop_allowed";		
    +		this.endpointDropForbiddenClass = "_jsPlumb_endpoint_drop_forbidden";		
    +		this.overlayClass = "_jsPlumb_overlay";				
    +		this.draggingClass = "_jsPlumb_dragging";		
    +		this.elementDraggingClass = "_jsPlumb_element_dragging";			
    +		this.sourceElementDraggingClass = "_jsPlumb_source_element_dragging";
    +		this.targetElementDraggingClass = "_jsPlumb_target_element_dragging";
    +		this.endpointAnchorClassPrefix = "_jsPlumb_endpoint_anchor";	
    +
    +		this.Anchors = {};		
    +		this.Connectors = {  "canvas":{}, "svg":{}, "vml":{} };				
    +		this.Endpoints = { "canvas":{}, "svg":{}, "vml":{} };
    +		this.Overlays = { "canvas":{}, "svg":{}, "vml":{}};		
    +		this.ConnectorRenderers = {};
    +				
    +
    +// --------------------------- jsPLumbInstance public API ---------------------------------------------------------
    +		
    +		this.addClass = function(el, clazz) { return jsPlumb.CurrentLibrary.addClass(el, clazz); };		
    +		this.removeClass = function(el, clazz) { return jsPlumb.CurrentLibrary.removeClass(el, clazz); };		
    +		this.hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(el, clazz); };
    +				
    +		this.addEndpoint = function(el, params, referenceParams) {
    +			referenceParams = referenceParams || {};
    +			var p = jsPlumb.extend({}, referenceParams);
    +			jsPlumb.extend(p, params);
    +			p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint;
    +			p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle;
    +            // YUI wrapper
    +			el = _convertYUICollection(el);			
    +			
    +			var results = [], inputs = el.length && el.constructor != String ? el : [ el ];
    +						
    +			for (var i = 0, j = inputs.length; i < j; i++) {
    +				var _el = _gel(inputs[i]), id = _getId(_el);
    +				p.source = _el;
    +                _updateOffset({ elId : id, timestamp:_suspendedAt });
    +				var e = _newEndpoint(p);
    +				if (p.parentAnchor) e.parentAnchor = p.parentAnchor;
    +				_addToList(endpointsByElement, id, e);
    +				var myOffset = offsets[id], myWH = sizes[id];
    +				var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e, timestamp:_suspendedAt });
    +				var endpointPaintParams = { anchorLoc : anchorLoc, timestamp:_suspendedAt };
    +				if (_suspendDrawing) endpointPaintParams.recalc = false;
    +				e.paint(endpointPaintParams);
    +				results.push(e);
    +				//if (!jsPlumbAdapter.headless)
    +					//_currentInstance.dragManager.endpointAdded(_el);
    +			}
    +			
    +			return results.length == 1 ? results[0] : results;
    +		};
    +		
    +		
    +		this.addEndpoints = function(el, endpoints, referenceParams) {
    +			var results = [];
    +			for ( var i = 0, j = endpoints.length; i < j; i++) {
    +				var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams);
    +				if (_isArray(e))
    +					Array.prototype.push.apply(results, e);
    +				else results.push(e);
    +			}
    +			return results;
    +		};
    +
    +		
    +		this.animate = function(el, properties, options) {
    +			var ele = _gel(el), id = _att(el, "id");
    +			options = options || {};
    +			var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step'];
    +			var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete'];
    +			options[stepFunction] = _wrap(options[stepFunction], function() {
    +				_currentInstance.repaint(id);
    +			});
    +
    +			// onComplete repaints, just to make sure everything looks good at the end of the animation.
    +			options[completeFunction] = _wrap(options[completeFunction],
    +					function() {
    +						_currentInstance.repaint(id);
    +					});
    +
    +			jsPlumb.CurrentLibrary.animate(ele, properties, options);
    +		};		
    +		
    +		/**
    +		* checks for a listener for the given condition, executing it if found, passing in the given value.
    +		* condition listeners would have been attached using "bind" (which is, you could argue, now overloaded, since
    +		* firing click events etc is a bit different to what this does).  i thought about adding a "bindCondition"
    +		* or something, but decided against it, for the sake of simplicity. jsPlumb will never fire one of these
    +		* condition events anyway.
    +		*/
    +		this.checkCondition = function(conditionName, value) {
    +			var l = _currentInstance.getListener(conditionName),
    +				r = true;
    +				
    +			if (l && l.length > 0) {
    +				try {
    +					for (var i = 0, j = l.length; i < j; i++) {
    +						r = r && l[i](value); 
    +					}
    +				}
    +				catch (e) { 
    +					_log(_currentInstance, "cannot check condition [" + conditionName + "]" + e); 
    +				}
    +			}
    +			return r;
    +		};
    +		
    +		/**
    +		 * checks a condition asynchronously: fires the event handler and passes the handler
    +		 * a 'proceed' function and a 'stop' function. The handler MUST execute one or other
    +		 * of these once it has made up its mind.
    +		 *
    +		 * Note that although this reads the listener list for the given condition, it
    +		 * does not loop through and hit each listener, because that, with asynchronous
    +		 * callbacks, would be messy. so it uses only the first listener registered.
    +		 */ 
    +		this.checkASyncCondition = function(conditionName, value, proceed, stop) {
    +			var l = _currentInstance.getListener(conditionName);
    +				
    +			if (l && l.length > 0) {
    +				try {
    +					l[0](value, proceed, stop); 					
    +				}
    +				catch (e) { 
    +					_log(_currentInstance, "cannot asynchronously check condition [" + conditionName + "]" + e); 
    +				}
    +			}	
    +		};
    +
    +		
    +		this.connect = function(params, referenceParams) {
    +			// prepare a final set of parameters to create connection with
    +			var _p = _prepareConnectionParams(params, referenceParams), jpc;
    +			// TODO probably a nicer return value if the connection was not made.  _prepareConnectionParams
    +			// will return null (and log something) if either endpoint was full.  what would be nicer is to 
    +			// create a dedicated 'error' object.
    +			if (_p) {
    +				// a connect call will delete its created endpoints on detach, unless otherwise specified.
    +				// this is because the endpoints belong to this connection only, and are no use to
    +				// anyone else, so they hang around like a bad smell.
    +				if (_p.deleteEndpointsOnDetach == null)
    +					_p.deleteEndpointsOnDetach = true;
    +
    +				// create the connection.  it is not yet registered 
    +				jpc = _newConnection(_p);
    +				// now add it the model, fire an event, and redraw
    +				_finaliseConnection(jpc, _p);										
    +			}
    +			return jpc;
    +		};
    +		
    +		// delete the given endpoint: either an Endpoint here, or its UUID.
    +		this.deleteEndpoint = function(object) {
    +			var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object;			
    +			if (endpoint) {					
    +				var uuid = endpoint.getUuid();
    +				if (uuid) endpointsByUUID[uuid] = null;				
    +				endpoint.detachAll();
    +				endpoint.cleanup();
    +				if (endpoint.endpoint.cleanup) endpoint.endpoint.cleanup();
    +				jsPlumbUtil.removeElements(endpoint.endpoint.getDisplayElements());
    +				_currentInstance.anchorManager.deleteEndpoint(endpoint);
    +				for (var e in endpointsByElement) {
    +					var endpoints = endpointsByElement[e];
    +					if (endpoints) {
    +						var newEndpoints = [];
    +						for (var i = 0, j = endpoints.length; i < j; i++)
    +							if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]);
    +						
    +						endpointsByElement[e] = newEndpoints;
    +					}
    +					if(endpointsByElement[e].length <1){
    +						delete endpointsByElement[e];
    +					}
    +				}				
    +				if (!jsPlumbAdapter.headless)
    +					_currentInstance.dragManager.endpointDeleted(endpoint);								
    +			}
    +			return _currentInstance;									
    +		};
    +		
    +		
    +		// delete every endpoint and their connections. distinct from reset because we dont clear listeners here.
    +		this.deleteEveryEndpoint = function() {
    +			_currentInstance.setSuspendDrawing(true);
    +			for ( var id in endpointsByElement) {
    +				var endpoints = endpointsByElement[id];
    +				if (endpoints && endpoints.length) {
    +					for ( var i = 0, j = endpoints.length; i < j; i++) {
    +						_currentInstance.deleteEndpoint(endpoints[i]);
    +					}
    +				}
    +			}			
    +			endpointsByElement = {};			
    +			endpointsByUUID = {};
    +			_currentInstance.anchorManager.reset();
    +			_currentInstance.dragManager.reset();			
    +			_currentInstance.setSuspendDrawing(false, true);
    +			return _currentInstance;
    +		};
    +
    +		var fireDetachEvent = function(jpc, doFireEvent, originalEvent) {
    +            // may have been given a connection, or in special cases, an object
    +            var connType =  _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(),
    +                argIsConnection = jpc.constructor == connType,
    +                params = argIsConnection ? {
    +                    connection:jpc,
    +				    source : jpc.source, target : jpc.target,
    +				    sourceId : jpc.sourceId, targetId : jpc.targetId,
    +				    sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1]
    +                } : jpc;
    +
    +			if (doFireEvent) {
    +				_currentInstance.fire("jsPlumbConnectionDetached", params, originalEvent);
    +				// introduced in 1.3.11..an alias because the original event name is unwieldy.  in future versions this will be the only event and the other will no longer be fired.
    +				_currentInstance.fire("connectionDetached", params, originalEvent);
    +			}
    +            _currentInstance.anchorManager.connectionDetached(params);
    +		};	
    +
    +		// detach a connection
    +		this.detach = function() {
    +
    +            if (arguments.length == 0) return;
    +            var connType =  _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(),
    +                firstArgIsConnection = arguments[0].constructor == connType,
    +                params = arguments.length == 2 ? firstArgIsConnection ? (arguments[1] || {}) : arguments[0] : arguments[0],
    +                fireEvent = (params.fireEvent !== false),
    +                forceDetach = params.forceDetach,
    +                conn = firstArgIsConnection ? arguments[0] : params.connection;
    +                                                    
    +				if (conn) {             
    +                    if (forceDetach || jsPlumbUtil.functionChain(true, false, [
    +                            [ conn.endpoints[0], "isDetachAllowed", [ conn ] ],    
    +                            [ conn.endpoints[1], "isDetachAllowed", [ conn ] ],
    +                            [ conn, "isDetachAllowed", [ conn ] ],
    +                            [ _currentInstance, "checkCondition", [ "beforeDetach", conn ] ] ])) {
    +                        
    +                        conn.endpoints[0].detach(conn, false, true, fireEvent); 
    +                    }
    +                }
    +                else {
    +					var _p = jsPlumb.extend( {}, params); // a backwards compatibility hack: source should be thought of as 'params' in this case.
    +					// test for endpoint uuids to detach
    +					if (_p.uuids) {
    +						_getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1]), fireEvent);
    +					} else if (_p.sourceEndpoint && _p.targetEndpoint) {
    +						_p.sourceEndpoint.detachFrom(_p.targetEndpoint);
    +					} else {
    +						var sourceId = _getId(_p.source),
    +						    targetId = _getId(_p.target);
    +						_operation(sourceId, function(jpc) {
    +						    if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) {
    +							    if (_currentInstance.checkCondition("beforeDetach", jpc)) {
    +                                    jpc.endpoints[0].detach(jpc, false, true, fireEvent);
    +								}
    +							}
    +						});
    +					}
    +				}
    +		};
    +
    +		// detach all connections from some element.
    +		this.detachAllConnections = function(el, params) {
    +            params = params || {};
    +            el = _gel(el);
    +			var id = _att(el, "id"),
    +                endpoints = endpointsByElement[id];
    +			if (endpoints && endpoints.length) {
    +				for ( var i = 0, j = endpoints.length; i < j; i++) {
    +					endpoints[i].detachAll(params.fireEvent);
    +				}
    +			}
    +			return _currentInstance;
    +		};
    +
    +		// detach every connection but leave endpoints in place (unless a connection is set to auto delete them)
    +		this.detachEveryConnection = function(params) {
    +            params = params || {};
    +			for ( var id in endpointsByElement) {
    +				var endpoints = endpointsByElement[id];
    +				if (endpoints && endpoints.length) {
    +					for ( var i = 0, j = endpoints.length; i < j; i++) {
    +						endpoints[i].detachAll(params.fireEvent);
    +					}
    +				}
    +			}
    +			connectionsByScope = {};
    +			return _currentInstance;
    +		};
    +
    +
    +		 
    +		this.draggable = function(el, options) {
    +			if (typeof el == 'object' && el.length) {
    +				for ( var i = 0, j = el.length; i < j; i++) {
    +					var ele = _gel(el[i]);
    +					if (ele) _initDraggableIfNecessary(ele, true, options);
    +				}
    +			} 
    +			else if (el._nodes) { 	// TODO this is YUI specific; really the logic should be forced
    +				// into the library adapters (for jquery and mootools aswell)
    +				for ( var i = 0, j = el._nodes.length; i < j; i++) {
    +					var ele = _gel(el._nodes[i]);
    +					if (ele) _initDraggableIfNecessary(ele, true, options);
    +				}
    +			}
    +			else {
    +				var ele = _gel(el);
    +				if (ele) _initDraggableIfNecessary(ele, true, options);
    +			}
    +			return _currentInstance;
    +		};
    +
    +
    +		// just a library-agnostic wrapper.
    +		this.extend = function(o1, o2) {
    +			return jsPlumb.CurrentLibrary.extend(o1, o2);
    +		};
    +		
    +		// gets the default endpoint type. used when subclassing. see wiki.
    +		this.getDefaultEndpointType = function() {
    +			return jsPlumb.Endpoint;
    +		};
    +		
    +		// gets the default connection type. used when subclassing.  see wiki.
    +		this.getDefaultConnectionType = function() {
    +			return jsPlumb.Connection;
    +		};
    +
    +		// helpers for select/selectEndpoints
    +		var _setOperation = function(list, func, args, selector) {
    +				for (var i = 0, j = list.length; i < j; i++) {
    +					list[i][func].apply(list[i], args);
    +				}	
    +				return selector(list);
    +			},
    +			_getOperation = function(list, func, args) {
    +				var out = [];
    +				for (var i = 0, j = list.length; i < j; i++) {					
    +					out.push([ list[i][func].apply(list[i], args), list[i] ]);
    +				}	
    +				return out;
    +			},
    +			setter = function(list, func, selector) {
    +				return function() {
    +					return _setOperation(list, func, arguments, selector);
    +				};
    +			},
    +			getter = function(list, func) {
    +				return function() {
    +					return _getOperation(list, func, arguments);
    +				};	
    +			},
    +			prepareList = function(input, doNotGetIds) {
    +				var r = [];
    +				if (input) {
    +					if (typeof input == 'string') {
    +						if (input === "*") return input;
    +						r.push(input);
    +					}
    +					else {
    +						if (doNotGetIds) r = input;
    +						else { 
    +							for (var i = 0, j = input.length; i < j; i++) 
    +								r.push(_getId(_gel(input[i])));
    +						}	
    +					}
    +				}
    +				return r;
    +			},
    +			filterList = function(list, value, missingIsFalse) {
    +				if (list === "*") return true;
    +				return list.length > 0 ? _indexOf(list, value) != -1 : !missingIsFalse;
    +			};
    +
    +		// get some connections, specifying source/target/scope
    +		this.getConnections = function(options, flat) {
    +			if (!options) {
    +				options = {};
    +			} else if (options.constructor == String) {
    +				options = { "scope": options };
    +			}
    +			var
    +			scope = options.scope || _currentInstance.getDefaultScope(),
    +			scopes = prepareList(scope, true),
    +			sources = prepareList(options.source),
    +			targets = prepareList(options.target),			
    +			results = (!flat && scopes.length > 1) ? {} : [],
    +			_addOne = function(scope, obj) {
    +				if (!flat && scopes.length > 1) {
    +					var ss = results[scope];
    +					if (ss == null) {
    +						ss = []; results[scope] = ss;
    +					}
    +					ss.push(obj);
    +				} else results.push(obj);
    +			};
    +			for ( var i in connectionsByScope) {
    +				if (filterList(scopes, i)) {
    +					for ( var j = 0, jj = connectionsByScope[i].length; j < jj; j++) {
    +						var c = connectionsByScope[i][j];
    +						if (filterList(sources, c.sourceId) && filterList(targets, c.targetId))
    +							_addOne(i, c);
    +					}
    +				}
    +			}
    +			return results;
    +		};
    +		
    +		var _curryEach = function(list, executor) {
    +				return function(f) {
    +					for (var i = 0, ii = list.length; i < ii; i++) {
    +						f(list[i]);
    +					}
    +					return executor(list);
    +				};		
    +			},
    +			_curryGet = function(list) {
    +				return function(idx) {
    +					return list[idx];
    +				};
    +			};
    +			
    +		var _makeCommonSelectHandler = function(list, executor) {
    +            var out = {
    +                    length:list.length,
    +				    each:_curryEach(list, executor),
    +				    get:_curryGet(list)
    +                },
    +                setters = ["setHover", "removeAllOverlays", "setLabel", "addClass", "addOverlay", "removeOverlay", 
    +                           "removeOverlays", "showOverlay", "hideOverlay", "showOverlays", "hideOverlays", "setPaintStyle",
    +                           "setHoverPaintStyle", "setSuspendEvents", "setParameter", "setParameters", "setVisible", 
    +                           "repaint", "addType", "toggleType", "removeType", "removeClass", "setType", "bind", "unbind" ],
    +                
    +                getters = ["getLabel", "getOverlay", "isHover", "getParameter", "getParameters", "getPaintStyle",
    +                           "getHoverPaintStyle", "isVisible", "hasType", "getType", "isSuspendEvents" ];
    +            
    +            for (var i = 0, ii = setters.length; i < ii; i++)
    +                out[setters[i]] = setter(list, setters[i], executor);
    +            
    +            for (var i = 0, ii = getters.length; i < ii; i++)
    +                out[getters[i]] = getter(list, getters[i]);       
    +            
    +            return out;
    +		};
    +		
    +		var	_makeConnectionSelectHandler = function(list) {
    +			var common = _makeCommonSelectHandler(list, _makeConnectionSelectHandler);
    +			return jsPlumb.CurrentLibrary.extend(common, {
    +				// setters									
    +				setDetachable:setter(list, "setDetachable", _makeConnectionSelectHandler),
    +				setReattach:setter(list, "setReattach", _makeConnectionSelectHandler),
    +				setConnector:setter(list, "setConnector", _makeConnectionSelectHandler),			
    +				detach:function() {
    +					for (var i = 0, ii = list.length; i < ii; i++)
    +						_currentInstance.detach(list[i]);
    +				},				
    +				// getters
    +				isDetachable:getter(list, "isDetachable"),
    +				isReattach:getter(list, "isReattach")
    +			});
    +		};
    +		
    +		var	_makeEndpointSelectHandler = function(list) {
    +			var common = _makeCommonSelectHandler(list, _makeEndpointSelectHandler);
    +			return jsPlumb.CurrentLibrary.extend(common, {
    +				setEnabled:setter(list, "setEnabled", _makeEndpointSelectHandler),				
    +				setAnchor:setter(list, "setAnchor", _makeEndpointSelectHandler),
    +				isEnabled:getter(list, "isEnabled"),
    +				detachAll:function() {
    +					for (var i = 0, ii = list.length; i < ii; i++)
    +						list[i].detachAll();
    +				},
    +				"delete":function() {
    +					for (var i = 0, ii = list.length; i < ii; i++)
    +						_currentInstance.deleteEndpoint(list[i]);
    +				}
    +			});
    +		};
    +			
    +
    +		this.select = function(params) {
    +			params = params || {};
    +			params.scope = params.scope || "*";
    +			var c = params.connections || _currentInstance.getConnections(params, true);
    +			return _makeConnectionSelectHandler(c);							
    +		};
    +		
    +
    +		this.selectEndpoints = function(params) {
    +			params = params || {};
    +			params.scope = params.scope || "*";
    +			var noElementFilters = !params.element && !params.source && !params.target,			
    +				elements = noElementFilters ? "*" : prepareList(params.element),
    +				sources = noElementFilters ? "*" : prepareList(params.source),
    +				targets = noElementFilters ? "*" : prepareList(params.target),
    +				scopes = prepareList(params.scope, true);
    +			
    +			var ep = [];
    +			
    +			for (var el in endpointsByElement) {
    +				var either = filterList(elements, el, true),
    +					source = filterList(sources, el, true),
    +					sourceMatchExact = sources != "*",
    +					target = filterList(targets, el, true),
    +					targetMatchExact = targets != "*"; 
    +					
    +				// if they requested 'either' then just match scope. otherwise if they requested 'source' (not as a wildcard) then we have to match only endpoints that have isSource set to to true, and the same thing with isTarget.  
    +				if ( either || source  || target ) {
    +					inner:
    +					for (var i = 0, ii = endpointsByElement[el].length; i < ii; i++) {
    +						var _ep = endpointsByElement[el][i];
    +						if (filterList(scopes, _ep.scope, true)) {
    +						
    +							var noMatchSource = (sourceMatchExact && sources.length > 0 && !_ep.isSource),
    +								noMatchTarget = (targetMatchExact && targets.length > 0 && !_ep.isTarget);
    +						
    +							if (noMatchSource || noMatchTarget)								  
    +								  continue inner; 
    +							 							
    +							ep.push(_ep);		
    +						}
    +					}
    +				}					
    +			}
    +			
    +			return _makeEndpointSelectHandler(ep);
    +		};
    +
    +		// get all connections managed by the instance of jsplumb.
    +		this.getAllConnections = function() {
    +			return connectionsByScope;
    +		};
    +
    +
    +		this.getDefaultScope = function() {
    +			return DEFAULT_SCOPE;
    +		};
    +
    +		// get an endpoint by uuid.
    +		this.getEndpoint = _getEndpoint;
    +				
    +		// get endpoints for some element.
    +		this.getEndpoints = function(el) {
    +			return endpointsByElement[_getId(el)];
    +		};		
    +
    +		/*
    +		 * Gets an element's id, creating one if necessary. really only exposed
    +		 * for the lib-specific functionality to access; would be better to pass
    +		 * the current instance into the lib-specific code (even though this is
    +		 * a static call. i just don't want to expose it to the public API).
    +		 */
    +		this.getId = _getId;
    +		this.getOffset = function(id) { 
    +			var o = offsets[id]; 
    +			return _updateOffset({elId:id});
    +		};
    +		
    +		// gets a library-agnostic selector.  not necessary for use outside of jsplumb, since
    +		// you already know what library you're using it with.	
    +		this.getSelector = function() {
    +			return jsPlumb.CurrentLibrary.getSelector.apply(null, arguments);
    +		};
    +		
    +		// get the size of the element with the given id, perhaps from cache.
    +		this.getSize = function(id) { 
    +			var s = sizes[id]; 
    +			if (!s) _updateOffset({elId:id});
    +			return sizes[id];
    +		};		
    +		
    +		this.appendElement = _appendElement;
    +		
    +		var _hoverSuspended = false;
    +		this.isHoverSuspended = function() { return _hoverSuspended; };
    +		this.setHoverSuspended = function(s) { _hoverSuspended = s; };
    +
    +		var _isAvailable = function(m) {
    +			return function() {
    +				return jsPlumbAdapter.isRenderModeAvailable(m);
    +			};
    +		}
    +		this.isCanvasAvailable = _isAvailable("canvas");
    +		this.isSVGAvailable = _isAvailable("svg");
    +		this.isVMLAvailable = _isAvailable("vml");
    +
    +		// set an element's connections to be hidden
    +		this.hide = function(el, changeEndpoints) {
    +			_setVisible(el, "none", changeEndpoints);
    +			return _currentInstance;
    +		};
    +		
    +		// exposed for other objects to use to get a unique id.
    +		this.idstamp = _idstamp;
    +		
    +		/**
    +		 * callback from the current library to tell us to prepare ourselves (attach
    +		 * mouse listeners etc; can't do that until the library has provided a bind method)		 
    +		 */
    +		this.init = function() {
    +			if (!initialized) {                
    +                _currentInstance.anchorManager = new jsPlumb.AnchorManager({jsPlumbInstance:_currentInstance});                
    +				_currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode);  // calling the method forces the capability logic to be run.										
    +				initialized = true;
    +				_currentInstance.fire("ready", _currentInstance);
    +			}
    +		};
    +		
    +		this.log = log;
    +		this.jsPlumbUIComponent = jsPlumbUIComponent;		
    +
    +		/*
    +		 * Creates an anchor with the given params.
    +		 * 
    +		 * 
    +		 * Returns: The newly created Anchor.
    +		 * Throws: an error if a named anchor was not found.
    +		 */
    +		this.makeAnchor = function() {
    +			var _a = function(t, p) {
    +				if (jsPlumb.Anchors[t]) return new jsPlumb.Anchors[t](p);
    +				if (!_currentInstance.Defaults.DoNotThrowErrors)
    +					throw { msg:"jsPlumb: unknown anchor type '" + t + "'" };
    +			}
    +			if (arguments.length == 0) return null;
    +			var specimen = arguments[0], elementId = arguments[1], jsPlumbInstance = arguments[2], newAnchor = null;			
    +			// if it appears to be an anchor already...
    +			if (specimen.compute && specimen.getOrientation) return specimen;  //TODO hazy here about whether it should be added or is already added somehow.
    +			// is it the name of an anchor type?
    +			else if (typeof specimen == "string") {
    +				//newAnchor = jsPlumb.Anchors[arguments[0]]({elementId:elementId, jsPlumbInstance:_currentInstance});
    +				newAnchor = _a(arguments[0], {elementId:elementId, jsPlumbInstance:_currentInstance});
    +			}
    +			// is it an array? it will be one of:
    +			// 		an array of [name, params] - this defines a single anchor
    +			//		an array of arrays - this defines some dynamic anchors
    +			//		an array of numbers - this defines a single anchor.				
    +			else if (_isArray(specimen)) {
    +				if (_isArray(specimen[0]) || _isString(specimen[0])) {
    +					if (specimen.length == 2 && _isString(specimen[0]) && _isObject(specimen[1])) {
    +						var pp = jsPlumb.extend({elementId:elementId, jsPlumbInstance:_currentInstance}, specimen[1]);
    +						//newAnchor = new jsPlumb.Anchors[specimen[0]](pp);
    +						newAnchor = _a(specimen[0], pp);
    +					}
    +					else
    +						newAnchor = new jsPlumb.DynamicAnchor({anchors:specimen, selector:null, elementId:elementId, jsPlumbInstance:jsPlumbInstance});
    +				}
    +				else {
    +					var anchorParams = {
    +						x:specimen[0], y:specimen[1],
    +						orientation : (specimen.length >= 4) ? [ specimen[2], specimen[3] ] : [0,0],
    +						offsets : (specimen.length >= 6) ? [ specimen[4], specimen[5] ] : [ 0, 0 ],
    +						elementId:elementId,
    +                        jsPlumbInstance:jsPlumbInstance,
    +                        cssClass:specimen.length == 7 ? specimen[6] : null
    +					};						
    +					newAnchor = new jsPlumb.Anchor(anchorParams);
    +					newAnchor.clone = function() { return new jsPlumb.Anchor(anchorParams); };						 					
    +				}
    +			}
    +			
    +			if (!newAnchor.id) newAnchor.id = "anchor_" + _idstamp();
    +			return newAnchor;
    +		};
    +
    +		/**
    +		 * makes a list of anchors from the given list of types or coords, eg
    +		 * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ]
    +		 */
    +		this.makeAnchors = function(types, elementId, jsPlumbInstance) {
    +			var r = [];
    +			for ( var i = 0, ii = types.length; i < ii; i++) {
    +				if (typeof types[i] == "string")
    +					r.push(jsPlumb.Anchors[types[i]]({elementId:elementId, jsPlumbInstance:jsPlumbInstance}));
    +				else if (_isArray(types[i]))
    +					r.push(_currentInstance.makeAnchor(types[i], elementId, jsPlumbInstance));
    +			}
    +			return r;
    +		};
    +
    +		/**
    +		 * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor
    +		 * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will
    +		 * not need to provide this - i think). 
    +		 */
    +		this.makeDynamicAnchor = function(anchors, anchorSelector) {
    +			return new jsPlumb.DynamicAnchor({anchors:anchors, selector:anchorSelector, elementId:null, jsPlumbInstance:_currentInstance});
    +		};
    +		
    +		
    +		var _targetEndpointDefinitions = {},
    +			_targetEndpoints = {},
    +			_targetEndpointsUnique = {},
    +			_targetMaxConnections = {},
    +			_setEndpointPaintStylesAndAnchor = function(ep, epIndex) {
    +				ep.paintStyle = ep.paintStyle ||
    +				 				_currentInstance.Defaults.EndpointStyles[epIndex] ||
    +	                            _currentInstance.Defaults.EndpointStyle ||
    +	                            jsPlumb.Defaults.EndpointStyles[epIndex] ||
    +	                            jsPlumb.Defaults.EndpointStyle;
    +				ep.hoverPaintStyle = ep.hoverPaintStyle ||
    +	                           _currentInstance.Defaults.EndpointHoverStyles[epIndex] ||
    +	                           _currentInstance.Defaults.EndpointHoverStyle ||
    +	                           jsPlumb.Defaults.EndpointHoverStyles[epIndex] ||
    +	                           jsPlumb.Defaults.EndpointHoverStyle;                            
    +
    +				ep.anchor = ep.anchor ||
    +	                      	_currentInstance.Defaults.Anchors[epIndex] ||
    +	                      	_currentInstance.Defaults.Anchor ||
    +	                      	jsPlumb.Defaults.Anchors[epIndex] ||
    +	                      	jsPlumb.Defaults.Anchor;                           
    +					
    +				ep.endpoint = ep.endpoint ||
    +							  _currentInstance.Defaults.Endpoints[epIndex] ||
    +							  _currentInstance.Defaults.Endpoint ||
    +							  jsPlumb.Defaults.Endpoints[epIndex] ||
    +							  jsPlumb.Defaults.Endpoint;
    +			};
    +
    +		// see API docs
    +		this.makeTarget = function(el, params, referenceParams) {						
    +			
    +			var p = jsPlumb.extend({_jsPlumb:_currentInstance}, referenceParams);
    +			jsPlumb.extend(p, params);
    +			_setEndpointPaintStylesAndAnchor(p, 1);                                                    
    +			var jpcl = jsPlumb.CurrentLibrary,
    +			    targetScope = p.scope || _currentInstance.Defaults.Scope,
    +			    deleteEndpointsOnDetach = !(p.deleteEndpointsOnDetach === false),
    +			    maxConnections = p.maxConnections || -1,
    +				onMaxConnections = p.onMaxConnections;
    +			_doOne = function(_el) {
    +				
    +				// get the element's id and store the endpoint definition for it.  jsPlumb.connect calls will look for one of these,
    +				// and use the endpoint definition if found.
    +				var elid = _getId(_el);
    +				_targetEndpointDefinitions[elid] = p;
    +				_targetEndpointsUnique[elid] = p.uniqueEndpoint,
    +				_targetMaxConnections[elid] = maxConnections,
    +				_targetsEnabled[elid] = true,
    +				proxyComponent = new jsPlumbUIComponent(p);								
    +				
    +				var dropOptions = jsPlumb.extend({}, p.dropOptions || {}),
    +				_drop = function() {
    +
    +					var originalEvent = jsPlumb.CurrentLibrary.getDropEvent(arguments),
    +						targetCount = _currentInstance.select({target:elid}).length;																							
    +
    +					_currentInstance.currentlyDragging = false;
    +					var draggable = _gel(jpcl.getDragObject(arguments)),
    +						id = _att(draggable, "dragId"),				
    +						// restore the original scope if necessary (issue 57)
    +						scope = _att(draggable, "originalScope"),
    +						jpc = floatingConnections[id],
    +						source = jpc.endpoints[0],
    +						_endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : {};
    +						
    +					if (!_targetsEnabled[elid] || _targetMaxConnections[elid] > 0 && targetCount >= _targetMaxConnections[elid]){
    +						if (onMaxConnections) {
    +							onMaxConnections({
    +								element:_el,
    +								connection:jpc
    +							}, originalEvent);
    +						}
    +						return false;
    +					}
    +
    +					// unlock the source anchor to allow it to refresh its position if necessary
    +					source.anchor.locked = false;					
    +										
    +					if (scope) jpcl.setDragScope(draggable, scope);				
    +					
    +					// check if drop is allowed here.					
    +					//var _continue = jpc.isDropAllowed(jpc.sourceId, _getId(_el), jpc.scope);		
    +					var _continue = proxyComponent.isDropAllowed(jpc.sourceId, _getId(_el), jpc.scope, jpc, null);		
    +					
    +					// regardless of whether the connection is ok, reconfigure the existing connection to 
    +					// point at the current info. we need this to be correct for the detach event that will follow.
    +					// clear the source endpoint from the list to detach. we will detach this connection at this
    +					// point, but we want to keep the source endpoint.  the target is a floating endpoint and should
    +					// be removed.  TODO need to figure out whether this code can result in endpoints kicking around
    +					// when they shouldnt be.  like is this a full detach of a connection?  can it be?
    +					if (jpc.endpointsToDeleteOnDetach) {
    +						if (source === jpc.endpointsToDeleteOnDetach[0])
    +							jpc.endpointsToDeleteOnDetach[0] = null;
    +						else if (source === jpc.endpointsToDeleteOnDetach[1])
    +							jpc.endpointsToDeleteOnDetach[1] = null;
    +					}
    +					// reinstate any suspended endpoint; this just puts the connection back into
    +					// a state in which it will report sensible values if someone asks it about
    +					// its target.  we're going to throw this connection away shortly so it doesnt matter
    +					// if we manipulate it a bit.
    +					if (jpc.suspendedEndpoint) {
    +						jpc.targetId = jpc.suspendedEndpoint.elementId;
    +						jpc.target = jpcl.getElementObject(jpc.suspendedEndpoint.elementId);
    +						jpc.endpoints[1] = jpc.suspendedEndpoint;
    +					}																										
    +					
    +					if (_continue) {
    +					
    +						// detach this connection from the source.						
    +						source.detach(jpc, false, true, false);
    +					
    +						// make a new Endpoint for the target												
    +						var newEndpoint = _targetEndpoints[elid] || _currentInstance.addEndpoint(_el, p);
    +						if (p.uniqueEndpoint) _targetEndpoints[elid] = newEndpoint;  // may of course just store what it just pulled out. that's ok.
    +						newEndpoint._makeTargetCreator = true;
    +																
    +						// if the anchor has a 'positionFinder' set, then delegate to that function to find
    +						// out where to locate the anchor.
    +						if (newEndpoint.anchor.positionFinder != null) {
    +							var dropPosition = jpcl.getUIPosition(arguments, _currentInstance.getZoom()),
    +							elPosition = _getOffset(_el, _currentInstance),
    +							elSize = _getSize(_el),
    +							ap = newEndpoint.anchor.positionFinder(dropPosition, elPosition, elSize, newEndpoint.anchor.constructorParams);
    +							newEndpoint.anchor.x = ap[0];
    +							newEndpoint.anchor.y = ap[1];
    +							// now figure an orientation for it..kind of hard to know what to do actually. probably the best thing i can do is to
    +							// support specifying an orientation in the anchor's spec. if one is not supplied then i will make the orientation 
    +							// be what will cause the most natural link to the source: it will be pointing at the source, but it needs to be
    +							// specified in one axis only, and so how to make that choice? i think i will use whichever axis is the one in which
    +							// the target is furthest away from the source.
    +						}
    +						var c = _currentInstance.connect({
    +							source:source,
    +							target:newEndpoint,
    +							scope:scope,
    +							previousConnection:jpc,
    +							container:jpc.parent,
    +							deleteEndpointsOnDetach:deleteEndpointsOnDetach,
    +                            endpointsToDeleteOnDetach : deleteEndpointsOnDetach ? [ source, newEndpoint ] : null,
    +							// 'endpointWillMoveAfterConnection' is set by the makeSource function, and it indicates that the
    +							// given endpoint will actually transfer from the element it is currently attached to to some other
    +							// element after a connection has been established.  in that case, we do not want to fire the
    +							// connection event, since it will have the wrong data in it; makeSource will do it for us.
    +							// this is controlled by the 'parent' parameter on a makeSource call.
    +							doNotFireConnectionEvent:source.endpointWillMoveAfterConnection
    +						});
    +
    +						// delete the original target endpoint.  but only want to do this if the endpoint was created
    +						// automatically and has no other connections.
    +						if (jpc.endpoints[1]._makeTargetCreator && jpc.endpoints[1].connections.length < 2)
    +							_currentInstance.deleteEndpoint(jpc.endpoints[1]);
    +
    +						c.repaint();
    +					}				
    +					// if not allowed to drop...
    +					else {
    +						// TODO this code is identical (pretty much) to what happens when a connection
    +						// dragged from a normal endpoint is in this situation. refactor.
    +						// is this an existing connection, and will we reattach?
    +						if (jpc.suspendedEndpoint) {
    +							//if (source.isReattach) {
    +							if (jpc.isReattach()) {
    +								jpc.setHover(false);
    +								jpc.floatingAnchorIndex = null;
    +								jpc.suspendedEndpoint.addConnection(jpc);
    +								_currentInstance.repaint(source.elementId);
    +							}
    +							else
    +								source.detach(jpc, false, true, true, originalEvent);  // otherwise, detach the connection and tell everyone about it.
    +						}
    +						
    +					}														
    +				};
    +				
    +				var dropEvent = jpcl.dragEvents['drop'];
    +				dropOptions["scope"] = dropOptions["scope"] || targetScope;
    +				dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop);
    +				
    +				jpcl.initDroppable(_el, dropOptions, true);
    +			};
    +			
    +			el = _convertYUICollection(el);			
    +			
    +			var inputs = el.length && el.constructor != String ? el : [ el ];
    +						
    +			for (var i = 0, ii = inputs.length; i < ii; i++) {			
    +				_doOne(_gel(inputs[i]));
    +			}
    +
    +			return _currentInstance;
    +		};
    +
    +		// see api docs
    +		this.unmakeTarget = function(el, doNotClearArrays) {
    +			el = jsPlumb.CurrentLibrary.getElementObject(el);
    +			var elid = _getId(el);			
    +
    +			// TODO this is not an exhaustive unmake of a target, since it does not remove the droppable stuff from
    +			// the element.  the effect will be to prevent it form behaving as a target, but it's not completely purged.
    +			if (!doNotClearArrays) {
    +				delete _targetEndpointDefinitions[elid];
    +				delete _targetEndpointsUnique[elid];
    +				delete _targetMaxConnections[elid];
    +				delete _targetsEnabled[elid];                
    +			}
    +
    +			return _currentInstance;
    +		};
    +		
    +		// see api docs
    +		this.makeTargets = function(els, params, referenceParams) {
    +			for ( var i = 0, ii = els.length; i < ii; i++) {
    +				_currentInstance.makeTarget(els[i], params, referenceParams);				
    +			}
    +		};
    +		
    +		
    +		var _sourceEndpointDefinitions = {},
    +			_sourceEndpoints = {},
    +			_sourceEndpointsUnique = {},
    +			_sourcesEnabled = {},
    +			_sourceTriggers = {},
    +			_sourceMaxConnections = {},
    +			_targetsEnabled = {},
    +			selectorFilter = function(evt, _el, selector) {	            
    +                var t = evt.target || evt.srcElement, ok = false, 
    +                    sel = _currentInstance.getSelector(_el, selector);
    +                for (var j = 0; j < sel.length; j++) {
    +                    if (sel[j] == t) {
    +                        ok = true;
    +                        break;
    +                    }
    +                }
    +                return ok;	            
    +	        };
    +
    +	    // see api docs
    +		this.makeSource = function(el, params, referenceParams) {
    +			var p = jsPlumb.extend({}, referenceParams);
    +			jsPlumb.extend(p, params);
    +			_setEndpointPaintStylesAndAnchor(p, 0);   
    +			var jpcl = jsPlumb.CurrentLibrary,
    +				maxConnections = p.maxConnections || -1,
    +				onMaxConnections = p.onMaxConnections,
    +				_doOne = function(_el) {
    +					// get the element's id and store the endpoint definition for it.  jsPlumb.connect calls will look for one of these,
    +					// and use the endpoint definition if found.
    +					var elid = _getId(_el),
    +						parentElement = function() {
    +							return p.parent == null ? p.parent : p.parent === "parent" ? jpcl.getElementObject(jpcl.getDOMElement(_el).parentNode) : jpcl.getElementObject(p.parent);
    +						},
    +						idToRegisterAgainst = p.parent != null ? _currentInstance.getId(parentElement()) : elid;
    +					
    +					_sourceEndpointDefinitions[idToRegisterAgainst] = p;
    +					_sourceEndpointsUnique[idToRegisterAgainst] = p.uniqueEndpoint;
    +					_sourcesEnabled[idToRegisterAgainst] = true;
    +
    +					var stopEvent = jpcl.dragEvents["stop"],
    +						dragEvent = jpcl.dragEvents["drag"],
    +						dragOptions = jsPlumb.extend({ }, p.dragOptions || {}),
    +						existingDrag = dragOptions.drag,
    +						existingStop = dragOptions.stop,
    +						ep = null,
    +						endpointAddedButNoDragYet = false;
    +				
    +					_sourceMaxConnections[idToRegisterAgainst] = maxConnections;	
    +
    +					// set scope if its not set in dragOptions but was passed in in params
    +					dragOptions["scope"] = dragOptions["scope"] || p.scope;
    +
    +					dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() {
    +						if (existingDrag) existingDrag.apply(this, arguments);
    +						endpointAddedButNoDragYet = false;
    +					});
    +					
    +					dragOptions[stopEvent] = _wrap(dragOptions[stopEvent], function() { 							
    +						if (existingStop) existingStop.apply(this, arguments);								
    +
    +	                    //_currentlyDown = false;
    +						_currentInstance.currentlyDragging = false;
    +						
    +						if (ep.connections.length == 0)
    +							_currentInstance.deleteEndpoint(ep);
    +						else {
    +							
    +							jpcl.unbind(ep.canvas, "mousedown"); 
    +									
    +							// reset the anchor to the anchor that was initially provided. the one we were using to drag
    +							// the connection was just a placeholder that was located at the place the user pressed the
    +							// mouse button to initiate the drag.
    +							var anchorDef = p.anchor || _currentInstance.Defaults.Anchor,
    +								oldAnchor = ep.anchor,
    +								oldConnection = ep.connections[0];
    +
    +							ep.setAnchor(_currentInstance.makeAnchor(anchorDef, elid, _currentInstance));																							
    +							
    +							if (p.parent) {						
    +								var parent = parentElement();
    +								if (parent) {	
    +									var currentId = ep.elementId,
    +										potentialParent = p.container || _currentInstance.Defaults.Container || jsPlumb.Defaults.Container;			
    +																	
    +									ep.setElement(parent, potentialParent);
    +									ep.endpointWillMoveAfterConnection = false;														
    +									_currentInstance.anchorManager.rehomeEndpoint(currentId, parent);																					
    +									oldConnection.previousConnection = null;
    +									// remove from connectionsByScope
    +									_removeWithFunction(connectionsByScope[oldConnection.scope], function(c) {
    +										return c.id === oldConnection.id;
    +									});										
    +									_currentInstance.anchorManager.connectionDetached({
    +										sourceId:oldConnection.sourceId,
    +										targetId:oldConnection.targetId,
    +										connection:oldConnection
    +									});											
    +									_finaliseConnection(oldConnection);					
    +								}
    +							}						
    +							
    +							ep.repaint();			
    +							_currentInstance.repaint(ep.elementId);																		
    +							_currentInstance.repaint(oldConnection.targetId);
    +						}				
    +					});
    +					// when the user presses the mouse, add an Endpoint, if we are enabled.
    +					var mouseDownListener = function(e) {
    +
    +						// if disabled, return.
    +						if (!_sourcesEnabled[idToRegisterAgainst]) return;
    +	                    
    +	                    // if a filter was given, run it, and return if it says no.
    +						if (p.filter) {
    +							var evt = jpcl.getOriginalEvent(e),
    +								r = jsPlumbUtil.isString(p.filter) ? selectorFilter(evt, _el, p.filter) : p.filter(evt, _el);
    +							
    +							if (r === false) return;
    +						}
    +						
    +						// if maxConnections reached
    +						var sourceCount = _currentInstance.select({source:idToRegisterAgainst}).length
    +						if (_sourceMaxConnections[idToRegisterAgainst] >= 0 && sourceCount >= _sourceMaxConnections[idToRegisterAgainst]) {
    +							if (onMaxConnections) {
    +								onMaxConnections({
    +									element:_el,
    +									maxConnections:maxConnections
    +								}, e);
    +							}
    +							return false;
    +						}					
    +
    +						// make sure we have the latest offset for this div 
    +						var myOffsetInfo = _updateOffset({elId:elid}).o,
    +							z = _currentInstance.getZoom(),		
    +							x = ( ((e.pageX || e.page.x) / z) - myOffsetInfo.left) / myOffsetInfo.width, 
    +						    y = ( ((e.pageY || e.page.y) / z) - myOffsetInfo.top) / myOffsetInfo.height,
    +						    parentX = x, 
    +						    parentY = y;					
    +								
    +						// if there is a parent, the endpoint will actually be added to it now, rather than the div
    +						// that was the source.  in that case, we have to adjust the anchor position so it refers to
    +						// the parent.
    +						if (p.parent) {
    +							var pEl = parentElement(), pId = _getId(pEl);
    +							myOffsetInfo = _updateOffset({elId:pId}).o;
    +							parentX = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width, 
    +						    parentY = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height;
    +						}											
    +						
    +						// we need to override the anchor in here, and force 'isSource', but we don't want to mess with
    +						// the params passed in, because after a connection is established we're going to reset the endpoint
    +						// to have the anchor we were given.
    +						var tempEndpointParams = {};
    +						jsPlumb.extend(tempEndpointParams, p);
    +						tempEndpointParams.isSource = true;
    +						tempEndpointParams.anchor = [x,y,0,0];
    +						tempEndpointParams.parentAnchor = [ parentX, parentY, 0, 0 ];
    +						tempEndpointParams.dragOptions = dragOptions;
    +						// if a parent was given we need to turn that into a "container" argument.  this is, by default,
    +						// the parent of the element we will move to, so parent of p.parent in this case.  however, if
    +						// the user has specified a 'container' on the endpoint definition or on 
    +						// the defaults, we should use that.
    +						if (p.parent) {
    +							var potentialParent = tempEndpointParams.container || _currentInstance.Defaults.Container || jsPlumb.Defaults.Container;
    +							if (potentialParent)
    +								tempEndpointParams.container = potentialParent;
    +							else
    +								tempEndpointParams.container = jsPlumb.CurrentLibrary.getParent(parentElement());
    +						}
    +						
    +						ep = _currentInstance.addEndpoint(elid, tempEndpointParams);
    +
    +						endpointAddedButNoDragYet = true;
    +						// we set this to prevent connections from firing attach events before this function has had a chance
    +						// to move the endpoint.
    +						ep.endpointWillMoveAfterConnection = p.parent != null;
    +						ep.endpointWillMoveTo = p.parent ? parentElement() : null;
    +
    +	                    var _delTempEndpoint = function() {
    +							// this mouseup event is fired only if no dragging occurred, by jquery and yui, but for mootools
    +							// it is fired even if dragging has occurred, in which case we would blow away a perfectly
    +							// legitimate endpoint, were it not for this check.  the flag is set after adding an
    +							// endpoint and cleared in a drag listener we set in the dragOptions above.
    +							if(endpointAddedButNoDragYet) {
    +								_currentInstance.deleteEndpoint(ep);
    +	                        }
    +						};
    +
    +						_currentInstance.registerListener(ep.canvas, "mouseup", _delTempEndpoint);
    +	                    _currentInstance.registerListener(_el, "mouseup", _delTempEndpoint);
    +						
    +						// and then trigger its mousedown event, which will kick off a drag, which will start dragging
    +						// a new connection from this endpoint.
    +						jpcl.trigger(ep.canvas, "mousedown", e);
    +						
    +					};
    +	               
    +	                // register this on jsPlumb so that it can be cleared by a reset.
    +	                _currentInstance.registerListener(_el, "mousedown", mouseDownListener);
    +	                _sourceTriggers[elid] = mouseDownListener;
    +
    +	                // lastly, if a filter was provided, set it as a dragFilter on the element,
    +	                // to prevent the element drag function from kicking in when we want to
    +	                // drag a new connection
    +	                if (p.filter && jsPlumbUtil.isString(p.filter)) {
    +	                	jpcl.setDragFilter(_el, p.filter);
    +	                }
    +				};
    +			
    +			el = _convertYUICollection(el);			
    +			
    +			var inputs = el.length && el.constructor != String ? el : [ el ];
    +						
    +			for (var i = 0, ii = inputs.length; i < ii; i++) {			
    +				_doOne(_gel(inputs[i]));
    +			}
    +
    +			return _currentInstance;
    +		};
    +	
    +		// see api docs		
    +		this.unmakeSource = function(el, doNotClearArrays) {
    +			el = jsPlumb.CurrentLibrary.getElementObject(el);
    +			var id = _getId(el),
    +				mouseDownListener = _sourceTriggers[id];
    +			
    +			if (mouseDownListener) 
    +				_currentInstance.unregisterListener(el, "mousedown", mouseDownListener);
    +
    +			if (!doNotClearArrays) {
    +				delete _sourceEndpointDefinitions[id];
    +				delete _sourceEndpointsUnique[id];
    +				delete _sourcesEnabled[id];
    +				delete _sourceTriggers[id];
    +				delete _sourceMaxConnections[id];
    +			}
    +
    +			return _currentInstance;
    +		};
    +
    +		// see api docs
    +		this.unmakeEverySource = function() {
    +			for (var i in _sourcesEnabled)
    +				_currentInstance.unmakeSource(i, true);
    +
    +			_sourceEndpointDefinitions = {};
    +			_sourceEndpointsUnique = {};
    +			_sourcesEnabled = {};
    +			_sourceTriggers = {};
    +		};
    +		
    +		// see api docs
    +		this.unmakeEveryTarget = function() {
    +			for (var i in _targetsEnabled)
    +				_currentInstance.unmakeTarget(i, true);
    +			
    +			_targetEndpointDefinitions = {};
    +			_targetEndpointsUnique = {};
    +			_targetMaxConnections = {};
    +			_targetsEnabled = {};
    +
    +			return _currentInstance;
    +		};
    +		
    +		
    +		this.makeSources = function(els, params, referenceParams) {
    +			for ( var i = 0, ii = els.length; i < ii; i++) {
    +				_currentInstance.makeSource(els[i], params, referenceParams);				
    +			}
    +
    +			return _currentInstance;
    +		};
    +
    +		// does the work of setting a source enabled or disabled.
    +		var _setEnabled = function(type, el, state, toggle) {
    +			var a = type == "source" ? _sourcesEnabled : _targetsEnabled;									
    +
    +			if (_isString(el)) a[el] = toggle ? !a[el] : state;
    +			else if (el.length) {
    +				el = _convertYUICollection(el);
    +				for (var i = 0, ii = el.length; i < ii; i++) {
    +					var id = _el = jsPlumb.CurrentLibrary.getElementObject(el[i]), id = _getId(_el);
    +					a[id] = toggle ? !a[id] : state;
    +				}
    +			}	
    +			return _currentInstance;
    +		};
    +
    +		
    +		this.setSourceEnabled = function(el, state) {
    +			return _setEnabled("source", el, state);
    +		};
    +
    +			
    +		this.toggleSourceEnabled = function(el) {
    +			_setEnabled("source", el, null, true);	
    +			return _currentInstance.isSourceEnabled(el);
    +		};
    +
    +		
    +		this.isSource = function(el) {
    +			el = jsPlumb.CurrentLibrary.getElementObject(el);
    +			return _sourcesEnabled[_getId(el)] != null;
    +		};
    +
    +		
    +		this.isSourceEnabled = function(el) {
    +			el = jsPlumb.CurrentLibrary.getElementObject(el);
    +			return _sourcesEnabled[_getId(el)] === true;
    +		};
    +
    +		
    +		this.setTargetEnabled = function(el, state) {
    +			return _setEnabled("target", el, state);
    +		};
    +
    +			
    +		this.toggleTargetEnabled = function(el) {
    +			_setEnabled("target", el, null, true);	
    +			return _currentInstance.isTargetEnabled(el);
    +		};
    +
    +		
    +		this.isTarget = function(el) {
    +			el = jsPlumb.CurrentLibrary.getElementObject(el);
    +			return _targetsEnabled[_getId(el)] != null;
    +		};
    +
    +		
    +		this.isTargetEnabled = function(el) {
    +			el = jsPlumb.CurrentLibrary.getElementObject(el);
    +			return _targetsEnabled[_getId(el)] === true;
    +		};
    +				
    +		this.ready = function(fn) {
    +			_currentInstance.bind("ready", fn);
    +		};
    +
    +		// repaint some element's endpoints and connections
    +		this.repaint = function(el, ui, timestamp) {
    +			// support both lists...
    +			if (typeof el == 'object' && el.length)
    +				for ( var i = 0, ii = el.length; i < ii; i++) {			
    +					_draw(_gel(el[i]), ui, timestamp);
    +				}
    +			else // ...and single strings.				
    +				_draw(_gel(el), ui, timestamp);
    +				
    +			return _currentInstance;
    +		};
    +
    +		// repaint every endpoint and connection.
    +		this.repaintEverything = function() {	
    +			var timestamp = _timestamp();			
    +			for ( var elId in endpointsByElement) {
    +				_draw(_gel(elId), null, timestamp);				
    +			}
    +			return _currentInstance;
    +		};
    +
    +		
    +		this.removeAllEndpoints = function(el, recurse) {
    +            var _one = function(_el) {                
    +                var elId = jsPlumbUtil.isString(_el) ? _el : _getId(_gel(_el)),
    +                    ebe = endpointsByElement[elId];
    +                if (ebe) {
    +                    for ( var i = 0, ii = ebe.length; i < ii; i++) 
    +                        _currentInstance.deleteEndpoint(ebe[i]);
    +                }
    +                delete endpointsByElement[elId];
    +                
    +                if (recurse) {
    +                    var del = jsPlumb.CurrentLibrary.getDOMElement(_gel(_el));
    +                    if (del && del.nodeType != 3 && del.nodeType != 8 ) {
    +                        for (var i = 0, ii = del.childNodes.length; i < ii; i++) {
    +                            _one(del.childNodes[i]);
    +                        }
    +                    }
    +                }
    +                
    +            };
    +            _one(el);
    +			return _currentInstance;
    +		};
    +                    
    +        this.remove = function(el, doNotFireEvent) {
    +            el = _gel(el);
    +            _currentInstance.removeAllEndpoints(el, true);
    +            jsPlumb.CurrentLibrary.removeElement(el);
    +        };
    +
    +		var _registeredListeners = {},
    +			_unbindRegisteredListeners = function() {
    +				for (var i in _registeredListeners) {
    +					for (var j = 0, jj = _registeredListeners[i].length; j < jj; j++) {
    +						var info = _registeredListeners[i][j];
    +						jsPlumb.CurrentLibrary.unbind(info.el, info.event, info.listener);
    +					}
    +				}
    +				_registeredListeners = {};
    +			};
    +
    +        // internal register listener method.  gives us a hook to clean things up
    +        // with if the user calls jsPlumb.reset.
    +        this.registerListener = function(el, type, listener) {
    +            jsPlumb.CurrentLibrary.bind(el, type, listener);
    +            _addToList(_registeredListeners, type, {el:el, event:type, listener:listener});
    +        };
    +
    +        this.unregisterListener = function(el, type, listener) {
    +        	jsPlumb.CurrentLibrary.unbind(el, type, listener);
    +        	_removeWithFunction(_registeredListeners, function(rl) {
    +        		return rl.type == type && rl.listener == listener;
    +        	});
    +        };
    +
    +		
    +		this.reset = function() {			
    +			_currentInstance.deleteEveryEndpoint();
    +			_currentInstance.unbind();
    +			_targetEndpointDefinitions = {};
    +			_targetEndpoints = {};
    +			_targetEndpointsUnique = {};
    +			_targetMaxConnections = {};
    +			_sourceEndpointDefinitions = {};
    +			_sourceEndpoints = {};
    +			_sourceEndpointsUnique = {};
    +			_sourceMaxConnections = {};
    +			_unbindRegisteredListeners();
    +			_currentInstance.anchorManager.reset();
    +			if (!jsPlumbAdapter.headless)
    +				_currentInstance.dragManager.reset();
    +		};
    +		
    +
    +		this.setDefaultScope = function(scope) {
    +			DEFAULT_SCOPE = scope;
    +			return _currentInstance;
    +		};
    +
    +		// sets whether or not some element should be currently draggable.
    +		this.setDraggable = _setDraggable;
    +
    +		// sets the id of some element, changing whatever we need to to keep track.
    +		this.setId = function(el, newId, doNotSetAttribute) {
    +		
    +			var id = el.constructor == String ? el : _currentInstance.getId(el),
    +				sConns = _currentInstance.getConnections({source:id, scope:'*'}, true),
    +				tConns = _currentInstance.getConnections({target:id, scope:'*'}, true);
    +
    +			newId = "" + newId;
    +							
    +			if (!doNotSetAttribute) {
    +				el = jsPlumb.CurrentLibrary.getElementObject(id);
    +				jsPlumb.CurrentLibrary.setAttribute(el, "id", newId);
    +			}
    +			
    +			el = jsPlumb.CurrentLibrary.getElementObject(newId);
    +			
    +
    +			endpointsByElement[newId] = endpointsByElement[id] || [];
    +			for (var i = 0, ii = endpointsByElement[newId].length; i < ii; i++) {
    +				endpointsByElement[newId][i].setElementId(newId);
    +				endpointsByElement[newId][i].setReferenceElement(el);
    +			}
    +			delete endpointsByElement[id];
    +
    +			_currentInstance.anchorManager.changeId(id, newId);
    +			if (!jsPlumbAdapter.headless)		
    +				_currentInstance.dragManager.changeId(id, newId);
    +
    +			var _conns = function(list, epIdx, type) {
    +				for (var i = 0, ii = list.length; i < ii; i++) {
    +					list[i].endpoints[epIdx].setElementId(newId);
    +					list[i].endpoints[epIdx].setReferenceElement(el);
    +					list[i][type + "Id"] = newId;
    +					list[i][type] = el;
    +				}
    +			};
    +			_conns(sConns, 0, "source");
    +			_conns(tConns, 1, "target");
    +			
    +			_currentInstance.repaint(newId);
    +		};
    +
    +		// called to notify us that an id WAS changed, and we should do our changes, but we
    +		// dont need to change the element's DOM attribute.
    +		this.setIdChanged = function(oldId, newId) {
    +			_currentInstance.setId(oldId, newId, true);
    +		};
    +
    +		this.setDebugLog = function(debugLog) {
    +			log = debugLog;
    +		};
    +
    +		
    +		var _suspendDrawing = false,
    +            _suspendedAt = null;
    +
    +         // set whether or not drawing is suspended. you should use this when doing bulk painting,
    +         // like when first drawing a UI.
    +		this.setSuspendDrawing = function(val, repaintAfterwards) {
    +		    _suspendDrawing = val;
    +				if (val) _suspendedAt = new Date().getTime(); else _suspendedAt = null;
    +		    if (repaintAfterwards) _currentInstance.repaintEverything();
    +		};
    +        	
    +        // returns whether or not drawing is currently suspended.		
    +		this.isSuspendDrawing = function() {
    +			return _suspendDrawing;
    +		};
    +            
    +        this.getSuspendedAt = function() { return _suspendedAt; };
    +            
    +        this.updateOffset = _updateOffset;
    +        this.getOffset = function(elId) { return offsets[elId]; };
    +        this.getSize = function(elId) { return sizes[elId]; };            
    +        this.getCachedData = _getCachedData;
    +        this.timestamp = _timestamp;
    +		
    +		/*
    +		 * Property: SVG
    +		 * Constant for use with the setRenderMode method
    +		 */
    +		 /*
    +		  * Property: VML
    +		  * Constant for use with the setRenderMode method
    +		  */
    +		this.SVG = "svg";
    +		
    +		
    +		this.VML = "vml";
    +		
    +		/*
    +		 * Function: setRenderMode
    +		 * Sets render mode: jsPlumb.SVG or jsPlumb.VML.  jsPlumb will fall back to VML if it determines that
    +		 * what you asked for is not supported (and that VML is).  If you asked for VML but the browser does
    +		 * not support it, jsPlumb uses SVG.
    +		 *
    +		 * Parameters:
    +		 * mode	-	a string representing the mode. Use one of the jsPlumb render mode constants as discussed above.
    +		 * 
    +		 * Returns:
    +		 * The render mode that jsPlumb set, which of course may be different from that requested.
    +		 */
    +		this.setRenderMode = function(mode) {			
    +			renderMode = jsPlumbAdapter.setRenderMode(mode);
    +			return renderMode;
    +		};
    +		
    +		/*
    +		 * Function: getRenderMode
    +		 *
    +		 * Returns:
    +		 * The current render mode.
    +		 */
    +		this.getRenderMode = function() { return renderMode; };
    +
    +		
    +		this.show = function(el, changeEndpoints) {
    +			_setVisible(el, "block", changeEndpoints);
    +			return _currentInstance;
    +		};
    +
    +		/*
    +		 * Function: sizeCanvas 
    +		 * Helper to size a canvas. You would typically use
    +		 * this when writing your own Connector or Endpoint implementation.
    +		 * 
    +		 * Parameters: 
    +		 * 	x - [int] x position for the Canvas origin 
    +		 * 	y - [int] y position for the Canvas origin 
    +		 * 	w - [int] width of the canvas 
    +		 * 	h - [int] height of the canvas
    +		 *  
    +		 * Returns: 
    +		 * 	The current jsPlumb instance
    +		 */
    +		this.sizeCanvas = function(canvas, x, y, w, h) {
    +			if (canvas) {
    +				canvas.style.height = h + "px";
    +				canvas.height = h;
    +				canvas.style.width = w + "px";
    +				canvas.width = w;
    +				canvas.style.left = x + "px";
    +				canvas.style.top = y + "px";
    +			}
    +			return _currentInstance;
    +		};
    +
    +		/**
    +		 * gets some test hooks. nothing writable.
    +		 */
    +		this.getTestHarness = function() {
    +			return {
    +				endpointsByElement : endpointsByElement,  
    +				endpointCount : function(elId) {
    +					var e = endpointsByElement[elId];
    +					return e ? e.length : 0;
    +				},
    +				connectionCount : function(scope) {
    +					scope = scope || DEFAULT_SCOPE;
    +					var c = connectionsByScope[scope];
    +					return c ? c.length : 0;
    +				},
    +				//findIndex : _findIndex,
    +				getId : _getId,
    +				makeAnchor:self.makeAnchor,
    +				makeDynamicAnchor:self.makeDynamicAnchor
    +			};
    +		};
    +		
    +		
    +		// TODO: update this method to return the current state.
    +		this.toggleVisible = _toggleVisible;
    +		this.toggleDraggable = _toggleDraggable;		
    +
    +		/*
    +		 * Helper method to wrap an existing function with one of
    +		 * your own. This is used by the various implementations to wrap event
    +		 * callbacks for drag/drop etc; it allows jsPlumb to be transparent in
    +		 * its handling of these things. If a user supplies their own event
    +		 * callback, for anything, it will always be called. 
    +		 */
    +		this.wrap = _wrap;			
    +		this.addListener = this.bind;
    +		
    +        /*
    +            helper method to take an xy location and adjust it for the parent's offset and scroll.
    +        */
    +		this.adjustForParentOffsetAndScroll = function(xy, el) {
    +
    +			var offsetParent = null, result = xy;
    +			if (el.tagName.toLowerCase() === "svg" && el.parentNode) {
    +				offsetParent = el.parentNode;
    +			}
    +			else if (el.offsetParent) {
    +				offsetParent = el.offsetParent;					
    +			}
    +			if (offsetParent != null) {
    +				var po = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(offsetParent, _currentInstance),
    +					so = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : {left:offsetParent.scrollLeft, top:offsetParent.scrollTop};					
    +
    +
    +				// i thought it might be cool to do this:
    +				//	lastReturnValue[0] = lastReturnValue[0] - offsetParent.offsetLeft + offsetParent.scrollLeft;
    +				//	lastReturnValue[1] = lastReturnValue[1] - offsetParent.offsetTop + offsetParent.scrollTop;					
    +				// but i think it ignores margins.  my reasoning was that it's quicker to not hand off to some underlying					
    +				// library.
    +				
    +				result[0] = xy[0] - po.left + so.left;
    +				result[1] = xy[1] - po.top + so.top;
    +			}
    +		
    +			return result;
    +			
    +		};
    +
    +		if (!jsPlumbAdapter.headless) {
    +			_currentInstance.dragManager = jsPlumbAdapter.getDragManager(_currentInstance);
    +			_currentInstance.recalculateOffsets = _currentInstance.dragManager.updateOffsets;
    +	    }
    +				    
    +    };
    +
    +// --------------------- static instance + AMD registration -------------------------------------------	
    +	
    +// create static instance and assign to window if window exists.	
    +	var jsPlumb = new jsPlumbInstance();
    +	if (typeof window != 'undefined') window.jsPlumb = jsPlumb;
    +// add 'getInstance' method to static instance
    +	jsPlumb.getInstance = function(_defaults) {
    +		var j = new jsPlumbInstance(_defaults);
    +		j.init();
    +		return j;
    +	};
    +// maybe register static instance as an AMD module, and getInstance method too.
    +	if ( typeof define === "function") {
    +		define( "jsplumb", [], function () { return jsPlumb; } );
    +		define( "jsplumbinstance", [], function () { return jsPlumb.getInstance(); } );
    +	}
    + // CommonJS 
    +	if (typeof exports !== 'undefined') {
    +      exports.jsPlumb = jsPlumb;
    +  	}
    +	
    +	
    +// --------------------- end static instance + AMD registration -------------------------------------------		
    +	
    +})();
    diff --git a/src/mootools.jsPlumb.js b/src/mootools.jsPlumb.js
    new file mode 100644
    index 000000000..0ce5ae8aa
    --- /dev/null
    +++ b/src/mootools.jsPlumb.js
    @@ -0,0 +1,448 @@
    +/*
    + * jsPlumb
    + * 
    + * Title:jsPlumb 1.4.0
    + * 
    + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
    + * elements, or VML.  
    + * 
    + * This file contains the MooTools adapter.
    + *
    + * Copyright (c) 2010 - 2013 Simon Porritt (http://jsplumb.org)
    + * 
    + * http://jsplumb.org
    + * http://github.com/sporritt/jsplumb
    + * http://code.google.com/p/jsplumb
    + * 
    + * Dual licensed under the MIT and GPL2 licenses.
    + */
    +
    +;(function() {
    +	
    +	/*
    +	 * overrides the FX class to inject 'step' functionality, which MooTools does not
    +	 * offer, and which makes me sad.  they don't seem keen to add it, either, despite
    +	 * the fact that it could be useful:
    +	 * 
    +	 * https://mootools.lighthouseapp.com/projects/2706/tickets/668
    +	 * 
    +	 */
    +	var jsPlumbMorph = new Class({
    +		Extends:Fx.Morph,
    +		onStep : null,
    +		initialize : function(el, options) {
    +			this.parent(el, options);
    +			if (options['onStep']) {
    +				this.onStep = options['onStep'];
    +			}
    +		},
    +		step : function(now) {
    +			this.parent(now);
    +			if (this.onStep) { 
    +				try { this.onStep(); } 
    +				catch(e) { } 
    +			}
    +		}
    +	});
    +	
    +	var _droppables = {},
    +	_droppableOptions = {},
    +	_draggablesByScope = {},
    +	_draggablesById = {},
    +	_droppableScopesById = {};
    +	/*
    +	 * 
    +	 */
    +	var _executeDroppableOption = function(el, dr, event, originalEvent) {
    +		if (dr) {
    +			var id = dr.get("id");
    +			if (id) {
    +				var options = _droppableOptions[id];
    +				if (options) {
    +					if (options[event]) {
    +						options[event](el, dr, originalEvent);
    +					}
    +				}
    +			}
    +		}
    +	};	
    +	
    +	var _checkHover = function(el, entering) {
    +		if (el) {
    +			var id = el.get("id");
    +			if (id) {
    +				var options = _droppableOptions[id];
    +				if (options) {
    +					if (options['hoverClass']) {
    +						if (entering) el.addClass(options['hoverClass']);
    +						else el.removeClass(options['hoverClass']);
    +					}
    +				}
    +			}
    +		}
    +	};
    +
    +	/**
    +	 * adds the given value to the given list, with the given scope. creates the scoped list
    +	 * if necessary.
    +	 * used by initDraggable and initDroppable.
    +	 */
    +	var _add = function(list, _scope, value) {
    +		var l = list[_scope];
    +		if (!l) {
    +			l = [];
    +			list[_scope] = l;
    +		}
    +		l.push(value);
    +	};
    +	
    +	/*
    +	 * gets an "element object" from the given input.  this means an object that is used by the
    +	 * underlying library on which jsPlumb is running.  'el' may already be one of these objects,
    +	 * in which case it is returned as-is.  otherwise, 'el' is a String, the library's lookup 
    +	 * function is used to find the element, using the given String as the element's id.
    +	 */
    +	var _getElementObject = function(el) {
    +	  return $(el);
    +	};
    +		
    +	jsPlumb.CurrentLibrary = {				
    +		
    +		/**
    +		 * adds the given class to the element object.
    +		 */
    +		addClass : function(el, clazz) {
    +			el = jsPlumb.CurrentLibrary.getElementObject(el)						
    +			try {
    +				if (el.className.constructor == SVGAnimatedString) {
    +					jsPlumbUtil.svg.addClass(el, clazz);
    +				}
    +				else el.addClass(clazz);
    +			}
    +			catch (e) {				
    +				// SVGAnimatedString not supported; no problem.
    +				el.addClass(clazz);
    +			}						
    +		},	
    +			
    +		animate : function(el, properties, options) {			
    +			var m = new jsPlumbMorph(el, options);
    +			m.start(properties);
    +		},
    +		
    +		appendElement : function(child, parent) {
    +			_getElementObject(parent).grab(child);			
    +		},
    +		
    +		bind : function(el, event, callback) {
    +			el = _getElementObject(el);
    +			el.addEvent(event, callback);
    +		},
    +		
    +		dragEvents : {
    +			'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep',
    +			'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete'
    +		},
    +
    +		/*
    +		 * wrapper around the library's 'extend' functionality (which it hopefully has.
    +		 * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you
    +		 * instead.  it's not like its hard.
    +		 */
    +		extend : function(o1, o2) {
    +			return $extend(o1, o2);
    +		},
    +		
    +		/**
    +		 * gets the named attribute from the given element object.  
    +		 */
    +		getAttribute : function(el, attName) {
    +			return el.get(attName);
    +		},
    +		
    +		getClientXY : function(eventObject) {
    +			return [eventObject.event.clientX, eventObject.event.clientY];
    +		},
    +		
    +		getDragObject : function(eventArgs) {
    +			return eventArgs[0];
    +		},
    +		
    +		getDragScope : function(el) {
    +			var id = jsPlumb.getId(el),
    +			    drags = _draggablesById[id];
    +			return drags[0].scope;
    +		},
    +	
    +		getDropEvent : function(args) {			
    +			return args[2];
    +		},
    +		
    +		getDropScope : function(el) {
    +			var id = jsPlumb.getId(el);
    +			return _droppableScopesById[id];
    +		},
    +		
    +		getDOMElement : function(el) { 
    +			// MooTools just decorates the DOM elements. so we have either an ID or an Element here.
    +			return typeof(el) == "String" ? document.getElementById(el) : el; 
    +		},
    +							
    +		getElementObject : _getElementObject,
    +		
    +		/*
    +		  gets the offset for the element object.  this should return a js object like this:
    +		  
    +		  { left:xxx, top: xxx}
    +		 */
    +		getOffset : function(el) {
    +			var p = el.getPosition();
    +			return { left:p.x, top:p.y };
    +		},	
    +		
    +		getOriginalEvent : function(e) {
    +			return e.event;
    +		},			
    +		
    +		getPageXY : function(eventObject) {
    +			return [eventObject.event.pageX, eventObject.event.pageY];
    +		},
    +		
    +		getParent : function(el) {
    +			return jsPlumb.CurrentLibrary.getElementObject(el).getParent();
    +		},
    +		
    +		getScrollLeft : function(el) {
    +			return null;
    +		},
    +		
    +		getScrollTop : function(el) {
    +			return null;
    +		},
    +		
    +		getSelector : function(context, spec) {
    +            if (arguments.length == 2) {
    +                return jsPlumb.CurrentLibrary.getElementObject(context).getElements(spec);
    +            }
    +            else
    +			     return $$(context);
    +		},
    +		
    +		getSize : function(el) {
    +			var s = el.getSize();
    +			return [s.x, s.y];
    +		},
    +
    +        getTagName : function(el) {
    +            var e = jsPlumb.CurrentLibrary.getElementObject(el);
    +            return e != null ? e.tagName : null;
    +        },
    +		
    +		/*
    +		 * takes the args passed to an event function and returns you an object that gives the
    +		 * position of the object being moved, as a js object with the same params as the result of
    +		 * getOffset, ie: { left: xxx, top: xxx }.
    +		 */
    +		getUIPosition : function(eventArgs, zoom) {
    +		  var ui = eventArgs[0],
    +			  el = jsPlumb.CurrentLibrary.getElementObject(ui),
    +			  p = el.getPosition();
    +			
    +		  zoom = zoom || 1;		  
    +			
    +		  return { left:p.x / zoom, top:p.y / zoom};
    +		},		
    +		
    +		hasClass : function(el, clazz) {
    +			return el.hasClass(clazz);
    +		},
    +		
    +		initDraggable : function(el, options, isPlumbedComponent, _jsPlumb) {
    +			var id = jsPlumb.getId(el);
    +			var drag = _draggablesById[id];
    +			if (!drag) {
    +				var originalZIndex = 0,
    +                    originalCursor = null,
    +				    dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000;
    +                
    +				options['onStart'] = jsPlumb.wrap(options['onStart'], function() {
    +                    originalZIndex = this.element.getStyle('z-index');
    +					this.element.setStyle('z-index', dragZIndex);
    +                    drag.originalZIndex = originalZIndex;
    +					if (jsPlumb.Defaults.DragOptions.cursor) {
    +						originalCursor = this.element.getStyle('cursor');
    +						this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor);
    +					}
    +				});
    +				
    +				options['onComplete'] = jsPlumb.wrap(options['onComplete'], function() {
    +					this.element.setStyle('z-index', originalZIndex);
    +					if (originalCursor) {
    +						this.element.setStyle('cursor', originalCursor);
    +					}                    
    +				});
    +				
    +				// DROPPABLES - only relevant if this is a plumbed component, ie. not just the result of the user making some DOM element
    +                // draggable.  this is the only library adapter that has to care about this parameter.
    +				var scope = "" + (options["scope"] || jsPlumb.Defaults.Scope),
    +				    filterFunc = function(entry) {
    +					    return entry.get("id") != el.get("id");
    +				    },
    +				    droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : [];
    +
    +                if (isPlumbedComponent) {
    +
    +				    options['droppables'] = droppables;
    +				    options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) {
    +		    			if (dr) {
    +			    			_checkHover(dr, false);
    +				    		_executeDroppableOption(el, dr, 'onLeave');
    +					    }
    +				    });
    +				    options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) {
    +					    if (dr) {
    +						    _checkHover(dr, true);
    +						    _executeDroppableOption(el, dr, 'onEnter');
    +					    }
    +				    });
    +				    options['onDrop'] = function(el, dr, event) {
    +					    if (dr) {
    +						    _checkHover(dr, false);
    +						    _executeDroppableOption(el, dr, 'onDrop', event);
    +					    }
    +				    };
    +                }
    +                else
    +                    options["droppables"] = [];
    +				
    +				drag = new Drag.Move(el, options);
    +				drag.scope = scope;
    +                drag.originalZIndex = originalZIndex;
    +                _add(_draggablesById, el.get("id"), drag);
    +				// again, only keep a record of this for scope stuff if this is a plumbed component (an endpoint)
    +                if (isPlumbedComponent) {
    +				    _add(_draggablesByScope, scope, drag);
    +                }
    +				// test for disabled.
    +				if (options.disabled) drag.detach();
    +			}
    +			return drag;
    +		},
    +		
    +		initDroppable : function(el, options, isPlumbedComponent, isPermanent) {
    +			var scope = options["scope"];
    +            _add(_droppables, scope, el);
    +			var id = jsPlumb.getId(el);
    +
    +            el.setAttribute("_isPermanentDroppable", isPermanent);  // we use this to clear out droppables on drag complete.
    +			_droppableOptions[id] = options;
    +			_droppableScopesById[id] = scope;
    +			var filterFunc = function(entry) { return entry.element != el; },
    +			    draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : [];
    +			for (var i = 0; i < draggables.length; i++) {
    +				draggables[i].droppables.push(el);
    +			}
    +		},
    +		
    +		isAlreadyDraggable : function(el) {
    +			return _draggablesById[jsPlumb.getId(el)] != null;
    +		},										
    +		
    +		isDragSupported : function(el, options) {
    +			return typeof Drag != 'undefined' ;
    +		},	
    +		
    +		/*
    +		 * you need Drag.Move imported to make drop work.
    +		 */
    +		isDropSupported : function(el, options) {
    +			return (typeof Drag != undefined && typeof Drag.Move != undefined);
    +		},
    +		
    +		/**
    +		 * removes the given class from the element object.
    +		 */
    +		removeClass : function(el, clazz) {
    +			el = jsPlumb.CurrentLibrary.getElementObject(el);
    +			try {
    +				if (el.className.constructor == SVGAnimatedString) {
    +					jsPlumbUtil.svg.removeClass(el, clazz);
    +				}
    +				else el.removeClass(clazz);
    +			}
    +			catch (e) {				
    +				// SVGAnimatedString not supported; no problem.
    +				el.removeClass(clazz);
    +			}
    +		},
    +		
    +		removeElement : function(element, parent) {
    +            var el = _getElementObject(element);
    +			if (el) el.dispose();  // ??
    +		},
    +		
    +		/**
    +		 * sets the named attribute on the given element object.  
    +		 */
    +		setAttribute : function(el, attName, attValue) {
    +			el.set(attName, attValue);
    +		},
    +
    +		setDragFilter : function(el, filter) {
    +			jsPlumb.log("NOT IMPLEMENTED: setDragFilter")
    +		},
    +		
    +		setDraggable : function(el, draggable) {
    +			var draggables = _draggablesById[el.get("id")];
    +			if (draggables) {
    +				draggables.each(function(d) {
    +					if (draggable) d.attach(); else d.detach();
    +				});
    +			}
    +		},
    +		
    +		setDragScope : function(el, scope) {
    +			var drag = _draggablesById[el.get("id")];
    +			var filterFunc = function(entry) {
    +				return entry.get("id") != el.get("id");
    +			};
    +			var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : [];
    +			drag[0].droppables = droppables;
    +		},
    +		
    +		setOffset : function(el, o) {
    +			_getElementObject(el).setPosition({x:o.left, y:o.top});
    +		},
    +
    +        stopDrag : function() {
    +            for (var i in _draggablesById) {
    +                for (var j = 0; j < _draggablesById[i].length; j++) {
    +                    var d = _draggablesById[i][j];
    +                    d.stop();
    +                    if (d.originalZIndex != 0)
    +                        d.element.setStyle("z-index", d.originalZIndex);
    +                }
    +            }
    +        },
    +		
    +		/**
    +		 * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself.
    +		 * the other libraries do not.  yui, in fact, cannot even pass an original event.  we have to pull out stuff
    +		 * from the originalEvent to put in an options object for YUI. 
    +		 * @param el
    +		 * @param event
    +		 * @param originalEvent
    +		 */
    +		trigger : function(el, event, originalEvent) {
    +			originalEvent.stopPropagation();
    +			_getElementObject(el).fireEvent(event, originalEvent);
    +		},
    +		
    +		unbind : function(el, event, callback) {
    +			el = _getElementObject(el);
    +			el.removeEvent(event, callback);
    +		}
    +	};
    +	
    +	window.addEvent('domready', jsPlumb.init);
    +})();
    diff --git a/src/yui.jsPlumb.js b/src/yui.jsPlumb.js
    new file mode 100644
    index 000000000..2185d80f7
    --- /dev/null
    +++ b/src/yui.jsPlumb.js
    @@ -0,0 +1,402 @@
    +/*
    + * jsPlumb
    + * 
    + * Title:jsPlumb 1.4.0
    + * 
    + * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
    + * elements, or VML.  
    + * 
    + * This file contains the YUI3 adapter.
    + *
    + * Copyright (c) 2010 - 2013 Simon Porritt (http://jsplumb.org)
    + * 
    + * http://jsplumb.org
    + * http://github.com/sporritt/jsplumb
    + * http://code.google.com/p/jsplumb
    + * 
    + * Dual licensed under the MIT and GPL2 licenses.
    + */
    +
    +/**
    + * addClass				adds a class to the given element
    + * animate				calls the underlying library's animate functionality
    + * appendElement		appends a child element to a parent element.
    + * bind					binds some event to an element
    + * dragEvents			a dictionary of event names
    + * extend				extend some js object with another.  probably not overly necessary; jsPlumb could just do this internally.
    + * getAttribute			gets some attribute from an element
    + * getDragObject		gets the object that is being dragged, by extracting it from the arguments passed to a drag callback
    + * getDragScope			gets the drag scope for a given element.
    + * getElementObject		turns an id or dom element into an element object of the underlying library's type.
    + * getOffset			gets an element's offset
    + * getOriginalEvent     gets the original browser event from some wrapper event.
    + * getScrollLeft		gets an element's scroll left.  TODO: is this actually used?  will it be?
    + * getScrollTop			gets an element's scroll top.  TODO: is this actually used?  will it be?
    + * getSize				gets an element's size.
    + * getUIPosition		gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback.
    + * initDraggable		initializes an element to be draggable 
    + * initDroppable		initializes an element to be droppable
    + * isDragSupported		returns whether or not drag is supported for some element.
    + * isDropSupported		returns whether or not drop is supported for some element.
    + * removeClass			removes a class from a given element.
    + * removeElement		removes some element completely from the DOM.
    + * setAttribute			sets an attribute on some element.
    + * setDraggable			sets whether or not some element should be draggable.
    + * setDragScope			sets the drag scope for a given element.
    + * setOffset			sets the offset of some element.
    + */
    +(function() {
    +	
    +	if (!Array.prototype.indexOf) {
    +		Array.prototype.indexOf = function( v, b, s ) {
    +			for( var i = +b || 0, l = this.length; i < l; i++ ) {
    +	  			if( this[i]===v || s && this[i]==v ) { return i; }
    +	 		}
    +	 		return -1;
    +		};
    +	}
    +	
    +	var Y;
    +	
    +	YUI().use('node', 'dd', 'dd-constrain', 'anim', 'node-event-simulate', function(_Y) {
    +		Y = _Y;	
    +		Y.on("domready", function() { jsPlumb.init(); });
    +	});
    +	
    +	/**
    +	 * adds the given value to the given list, with the given scope. creates the scoped list
    +	 * if necessary.
    +	 * used by initDraggable and initDroppable.
    +	 */
    +	var _add = function(list, scope, value) {
    +		var l = list[scope];
    +		if (!l) {
    +			l = [];
    +			list[scope] = l;
    +		}
    +		l.push(value);
    +	},	
    +	ddEvents = [ "drag:mouseDown", "drag:afterMouseDown", "drag:mouseup",
    +	     "drag:align", "drag:removeHandle", "drag:addHandle", "drag:removeInvalid", "drag:addInvalid",
    +	     "drag:start", "drag:end", "drag:drag", "drag:over", "drag:enter",
    +	     "drag:exit", "drag:drophit", "drag:dropmiss", "drop:over", "drop:enter", "drop:exit", "drop:hit"	     	               
    +	],	
    +	animEvents = [ "tween" ],	
    +	/**
    +	 * helper function to curry callbacks for some element. 
    +	 */
    +	_wrapper = function(fn) {
    +		return function() {
    +			try {
    +				return fn.apply(this, arguments);
    +			}
    +			catch (e) { }
    +		};
    +	},	
    +	/**
    +	 * extracts options from the given options object, leaving out event handlers.
    +	 */
    +	_getDDOptions = function(options) {
    +		var o = {};
    +		for (var i in options) if (ddEvents.indexOf(i) == -1) o[i] = options[i];
    +		return o;
    +	},	
    +	/**
    +	 * attaches all event handlers found in options to the given dragdrop object, and registering
    +	 * the given el as the element of interest.
    +	 */
    +	_attachListeners = function(dd, options, eventList) {	
    +	    for (var ev in options) {
    +	    	if (eventList.indexOf(ev) != -1) {
    +	    		var w = _wrapper(options[ev]);
    +	    		dd.on(ev, w);
    +	    	}
    +	    }
    +	},
    +	_droppables = {},
    +	_droppableOptions = {},
    +	_draggablesByScope = {},
    +	_draggablesById = {},
    +	_droppableScopesById = {},
    +	_checkHover = function(el, entering) {
    +		if (el) {
    +			var id = el.get("id");
    +			if (id) {
    +				var options = _droppableOptions[id];
    +				if (options) {
    +					if (options['hoverClass']) {
    +						if (entering) el.addClass(options['hoverClass']);
    +						else el.removeClass(options['hoverClass']);
    +					}
    +				}
    +			}
    +		}
    +	},
    +	_lastDragObject = null,
    +	_extend = function(o1, o2) {
    +		for (var i in o2)
    +			o1[i] = o2[i];
    +		return o1;
    +	},
    +	_getAttribute = function(el, attributeId) {
    +		return el.getAttribute(attributeId);
    +	},
    +	_getElementObject = function(el) {
    +		if (el == null) return null;
    +		var eee = null;
    +        eee = typeof el == 'string' ? Y.one('#' + el) : el._node ? el : Y.one(el);        
    +        return eee;
    +	};
    +	
    +	jsPlumb.CurrentLibrary = {
    +			
    +		addClass : function(el, clazz) {
    +			jsPlumb.CurrentLibrary.getElementObject(el).addClass(clazz);
    +		},	
    +		
    +		/**
    +		 * animates the given element.
    +		 */
    +		animate : function(el, properties, options) {
    +			var o = _extend({node:el, to:properties}, options),			
    +				id = _getAttribute(el, "id");
    +			o["tween"] = jsPlumb.wrap(properties["tween"], function() {
    +				// TODO should use a current instance.
    +				jsPlumb.repaint(id);
    +			});
    +			var a = new Y.Anim(o);
    +			_attachListeners(a, o, animEvents);
    +			a.run();
    +		},
    +		
    +		appendElement : function(child, parent) {
    +			_getElementObject (parent).append(child);			
    +		},
    +		
    +		/**
    +		 * event binding wrapper.  
    +		 */
    +		bind : function(el, event, callback) {
    +			_getElementObject(el).on(event, callback);
    +		},
    +			
    +		dragEvents : {
    +			"start":"drag:start", "stop":"drag:end", "drag":"drag:drag", "step":"step",
    +			"over":"drop:enter", "out":"drop:exit", "drop":"drop:hit"
    +		},								
    +			
    +		extend : _extend,
    +		
    +		getAttribute : _getAttribute,
    +		
    +		getClientXY : function(eventObject) {
    +			return [eventObject.clientX, eventObject.clientY];
    +		},
    +		
    +		/**
    +		 * takes the args passed to an event function and returns you an object representing that which is being dragged.
    +		 */
    +		getDragObject : function(eventArgs) {
    +			// this is a workaround for the unfortunate fact that in YUI3, the 'drop:exit' event does
    +			// not contain a reference to the drag that just exited.  single-threaded js to the 
    +			// rescue: we'll just keep it for ourselves.
    +			if (eventArgs[0].drag) _lastDragObject = eventArgs[0].drag.el;
    +			return _lastDragObject;
    +		},
    +		
    +		getDragScope : function(el) {
    +			var id = jsPlumb.getId(el),
    +				dd = _draggablesById[id];
    +			return dd.scope;
    +		},
    +
    +		getDropEvent : function(args) {
    +			return args[0];
    +		},
    +		
    +		getDropScope : function(el) {
    +			var id = jsPlumb.getId(el);
    +			return _droppableScopesById[id];
    +		},
    +		
    +		getDOMElement : function(el) { 			
    +			if (typeof(el) == "String") 
    +				return document.getElementById(el);
    +			else if (el._node) 
    +				return el._node;
    +			else return el;
    +		},
    +		
    +		getElementObject : _getElementObject,
    +		
    +		getOffset : function(el) {			
    +			var o = Y.DOM.getXY(el._node);
    +			return {left:o[0], top:o[1]};
    +		},
    +
    +		getOriginalEvent : function(e) {
    +			return e._event;
    +		},
    +		
    +		getPageXY : function(eventObject) {
    +			return [eventObject.pageX, eventObject.pageY];
    +		},
    +		
    +		getParent : function(el) {
    +			return jsPlumb.CurrentLibrary.getElementObject(el).get("parentNode");
    +		},
    +		
    +		getScrollLeft : function(el) {
    +			return 0; 
    +		},
    +		
    +		getScrollTop : function(el) {
    +			return 0;
    +		},
    +		
    +		getSelector : function(context, spec) {
    +			var _convert = function(s) { return s && s ._nodes ? s._nodes : []; };
    +            
    +            if (arguments.length == 2) {            
    +                return _convert(jsPlumb.CurrentLibrary.getElementObject(context).all(spec));
    +            }
    +            else {
    +			     return _convert(Y.all(context));
    +            }            
    +		},
    +		
    +		getSize : function(el) {
    +			return [ el._node.offsetWidth, el._node.offsetHeight ];
    +		},
    +
    +        getTagName : function(el) {
    +            var e = jsPlumb.CurrentLibrary.getElementObject(el);
    +            return e != null && e._node != null ? e._node.tagName : null;
    +        },
    +		
    +		getUIPosition : function(args, zoom) {
    +			zoom = zoom || 1;
    +			var n = args[0].currentTarget.el._node,
    +			o = Y.DOM.getXY(n);
    +			return {left:o[0] / zoom, top:o[1] / zoom};
    +		},		
    +		
    +		hasClass : function(el, clazz) {
    +			return el.hasClass(clazz);
    +		},
    +				
    +		initDraggable : function(el, options, isPlumbedComponent, _jsPlumb) {
    +			var _opts = _getDDOptions(options),
    +				id = _jsPlumb.getId(el);
    +			_opts.node = "#" + id;		
    +			var dd = new Y.DD.Drag(_opts), 
    +                containment = options.constrain2node || options.containment;
    +			dd.el = el;	
    +            
    +            if (containment) {
    +                dd.plug(Y.Plugin.DDConstrained, {
    +                    constrain2node: containment
    +                });
    +            }
    +			
    +			if (isPlumbedComponent) {
    +				var scope = options['scope'] || _jsPlumb.Defaults.Scope;
    +				dd.scope = scope;
    +				_add(_draggablesByScope, scope, dd);
    +			}
    +			
    +			_draggablesById[id] = dd;			
    +			
    +			_attachListeners(dd, options, ddEvents);
    +		},
    +		
    +		initDroppable : function(el, options) {
    +			var _opts = _getDDOptions(options),
    +				id = jsPlumb.getId(el);
    +			_opts.node = "#" + id;			
    +			var dd = new Y.DD.Drop(_opts);
    +			
    +			_droppableOptions[id] = options;
    +			
    +			options = _extend({}, options);
    +			var scope = options['scope'] || jsPlumb.Defaults.Scope;					
    +			_droppableScopesById[id] = scope;
    +			
    +			options["drop:enter"] = jsPlumb.wrap(options["drop:enter"], function(e) {
    +				if (e.drag.scope !== scope) return true;
    +				_checkHover(el, true);
    +			}, true);
    +			options["drop:exit"] = jsPlumb.wrap(options["drop:exit"], function(e) {
    +				_checkHover(el, false);
    +			});
    +			options["drop:hit"] = jsPlumb.wrap(options["drop:hit"], function(e) {
    +				if (e.drag.scope !== scope) return true;
    +				_checkHover(el, false);
    +			}, true);
    +			
    +			_attachListeners(dd, options, ddEvents);
    +		},
    +		
    +		isAlreadyDraggable : function(el) {
    +			el = _getElementObject(el);
    +			return el.hasClass("yui3-dd-draggable");
    +		},
    +		
    +		isDragSupported : function(el) { return true; },		
    +		isDropSupported : function(el) { return true; },										
    +		removeClass : function(el, clazz) { 
    +			jsPlumb.CurrentLibrary.getElementObject(el).removeClass(clazz); 
    +		},		
    +		removeElement : function(el) { _getElementObject(el).remove(); },
    +		
    +		setAttribute : function(el, attributeName, attributeValue) {
    +			el.setAttribute(attributeName, attributeValue);
    +		},
    +
    +		setDragFilter : function(el, filter) {
    +			jsPlumb.log("NOT IMPLEMENTED: setDragFilter")
    +		},
    +		
    +		/**
    +		 * sets the draggable state for the given element
    +		 */
    +		setDraggable : function(el, draggable) {
    +			var id = jsPlumb.getId(el),
    +				dd = _draggablesById[id];
    +			if (dd) dd.set("lock", !draggable);
    +		},
    +		
    +		setDragScope : function(el, scope) {
    +			var id = jsPlumb.getId(el),
    +				dd = _draggablesById[id];
    +			if (dd) dd.scope = scope;
    +		},
    +		
    +		setOffset : function(el, o) {
    +			el = _getElementObject(el);
    +			el.set("top", o.top);
    +			el.set("left", o.left);
    +		},
    +
    +        stopDrag : function() {
    +            Y.DD.DDM.stopDrag();
    +        },
    +		
    +		trigger : function(el, event, originalEvent) {
    +			originalEvent.stopPropagation();
    +			_getElementObject(el).simulate(event, {
    +				pageX:originalEvent.pageX, 
    +				pageY:originalEvent.pageY, 
    +				clientX:originalEvent.clientX, 
    +				clientY:originalEvent.clientY
    +			});			
    +		},
    +		
    +		/**
    +		 * event unbinding wrapper.  
    +		 */
    +		unbind : function(el, event, callback) {
    +			_getElementObject(el).detach(event, callback);
    +		}
    +	};				
    +})();
    \ No newline at end of file